From e886bc8939923d6395c6fc0aeb53e878ddc5fd4f Mon Sep 17 00:00:00 2001 From: yanuino <36410910+yanuino@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:32:46 +0100 Subject: [PATCH 0001/1691] Read min/max number of showers from state for DomesticHotWaterProduction in Overkiz integration (#111535) * Read min/max number of showers from state * Rewrite code for Read min/max number of showers from state * Set _attr_ instead of inherited value --- homeassistant/components/overkiz/number.py | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index c15a7bd3acc..b53dbb5db75 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -20,6 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import HomeAssistantOverkizData from .const import DOMAIN, IGNORED_OVERKIZ_DEVICES +from .coordinator import OverkizDataUpdateCoordinator from .entity import OverkizDescriptiveEntity BOOST_MODE_DURATION_DELAY = 1 @@ -37,6 +38,8 @@ class OverkizNumberDescriptionMixin: class OverkizNumberDescription(NumberEntityDescription, OverkizNumberDescriptionMixin): """Class to describe an Overkiz number.""" + min_value_state_name: str | None = None + max_value_state_name: str | None = None inverted: bool = False set_native_value: Callable[ [float, Callable[..., Awaitable[None]]], Awaitable[None] @@ -94,6 +97,8 @@ NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ command=OverkizCommand.SET_EXPECTED_NUMBER_OF_SHOWER, native_min_value=2, native_max_value=4, + min_value_state_name=OverkizState.CORE_MINIMAL_SHOWER_MANUAL_MODE, + max_value_state_name=OverkizState.CORE_MAXIMAL_SHOWER_MANUAL_MODE, entity_category=EntityCategory.CONFIG, ), # SomfyHeatingTemperatureInterface @@ -200,6 +205,29 @@ class OverkizNumber(OverkizDescriptiveEntity, NumberEntity): entity_description: OverkizNumberDescription + def __init__( + self, + device_url: str, + coordinator: OverkizDataUpdateCoordinator, + description: OverkizNumberDescription, + ) -> None: + """Initialize a device.""" + super().__init__(device_url, coordinator, description) + + if self.entity_description.min_value_state_name and ( + state := self.device.states.get( + self.entity_description.min_value_state_name + ) + ): + self._attr_native_min_value = cast(float, state.value) + + if self.entity_description.max_value_state_name and ( + state := self.device.states.get( + self.entity_description.max_value_state_name + ) + ): + self._attr_native_max_value = cast(float, state.value) + @property def native_value(self) -> float | None: """Return the entity value to represent the entity state.""" From fb10ef9ac0800e5bd76524fdc11da0e289bf83e7 Mon Sep 17 00:00:00 2001 From: Isak Nyberg <36712644+IsakNyberg@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:55:47 +0100 Subject: [PATCH 0002/1691] Add permobil entity (#111761) * add permobil entity * small fixes * remove sensor init --- .coveragerc | 1 + homeassistant/components/permobil/entity.py | 29 +++++++++++++++++++++ homeassistant/components/permobil/sensor.py | 18 ++----------- 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/permobil/entity.py diff --git a/.coveragerc b/.coveragerc index 378532dfd88..626c2122d6f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -983,6 +983,7 @@ omit = homeassistant/components/pencom/switch.py homeassistant/components/permobil/__init__.py homeassistant/components/permobil/coordinator.py + homeassistant/components/permobil/entity.py homeassistant/components/permobil/sensor.py homeassistant/components/philips_js/__init__.py homeassistant/components/philips_js/light.py diff --git a/homeassistant/components/permobil/entity.py b/homeassistant/components/permobil/entity.py new file mode 100644 index 00000000000..702781aa361 --- /dev/null +++ b/homeassistant/components/permobil/entity.py @@ -0,0 +1,29 @@ +"""PermobilEntity class.""" + +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import MyPermobilCoordinator + + +class PermobilEntity(CoordinatorEntity[MyPermobilCoordinator]): + """Representation of a permobil Entity.""" + + _attr_has_entity_name = True + + def __init__( + self, + coordinator: MyPermobilCoordinator, + description: EntityDescription, + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.p_api.email}_{description.key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, coordinator.p_api.email)}, + manufacturer="Permobil", + name="Permobil Wheelchair", + ) diff --git a/homeassistant/components/permobil/sensor.py b/homeassistant/components/permobil/sensor.py index 8a504248f5a..d01be68d775 100644 --- a/homeassistant/components/permobil/sensor.py +++ b/homeassistant/components/permobil/sensor.py @@ -32,10 +32,10 @@ from homeassistant.components.sensor import ( from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfLength, UnitOfTime from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import BATTERY_ASSUMED_VOLTAGE, DOMAIN, KM, MILES from .coordinator import MyPermobilCoordinator +from .entity import PermobilEntity _LOGGER = logging.getLogger(__name__) @@ -202,28 +202,14 @@ async def async_setup_entry( ) -class PermobilSensor(CoordinatorEntity[MyPermobilCoordinator], SensorEntity): +class PermobilSensor(PermobilEntity, SensorEntity): """Representation of a Sensor. This implements the common functions of all sensors. """ - _attr_has_entity_name = True _attr_suggested_display_precision = 0 entity_description: PermobilSensorEntityDescription - _available = True - - def __init__( - self, - coordinator: MyPermobilCoordinator, - description: PermobilSensorEntityDescription, - ) -> None: - """Initialize the sensor.""" - super().__init__(coordinator=coordinator) - self.entity_description = description - self._attr_unique_id = ( - f"{coordinator.p_api.email}_{self.entity_description.key}" - ) @property def native_unit_of_measurement(self) -> str | None: From eeb87247e9b6a64726a35cb21a0bddf87d5b1ef6 Mon Sep 17 00:00:00 2001 From: Jeremy TRUFIER Date: Wed, 28 Feb 2024 23:16:03 +0100 Subject: [PATCH 0003/1691] Add overkiz support for Atlantic Shogun ZoneControl 2.0 (AtlanticPassAPCHeatingAndCoolingZone) (#110510) * Add Overkiz support for AtlanticPassAPCHeatingAndCoolingZone widget * Add support for AUTO HVAC mode for Atlantic Pass APC ZC devices that support it * Add support for multiple IO controllers for same widget (mainly for Atlantic APC) * Implement PR feedback * Small PR fixes * Fix constant inversion typo --- homeassistant/components/overkiz/climate.py | 16 ++ .../overkiz/climate_entities/__init__.py | 27 +- .../atlantic_pass_apc_heating_zone.py | 10 +- .../atlantic_pass_apc_zone_control.py | 47 +++- .../atlantic_pass_apc_zone_control_zone.py | 252 ++++++++++++++++++ 5 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py diff --git a/homeassistant/components/overkiz/climate.py b/homeassistant/components/overkiz/climate.py index b6d31a8e685..2c24ca4f832 100644 --- a/homeassistant/components/overkiz/climate.py +++ b/homeassistant/components/overkiz/climate.py @@ -1,6 +1,8 @@ """Support for Overkiz climate devices.""" from __future__ import annotations +from typing import cast + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -8,8 +10,10 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import HomeAssistantOverkizData from .climate_entities import ( + WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY, WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY, WIDGET_TO_CLIMATE_ENTITY, + Controllable, ) from .const import DOMAIN @@ -28,6 +32,18 @@ async def async_setup_entry( if device.widget in WIDGET_TO_CLIMATE_ENTITY ) + # Match devices based on the widget and controllableName + # This is for example used for Atlantic APC, where devices with different functionality share the same uiClass and widget. + async_add_entities( + WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget][ + cast(Controllable, device.controllable_name) + ](device.device_url, data.coordinator) + for device in data.platforms[Platform.CLIMATE] + if device.widget in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY + and device.controllable_name + in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget] + ) + # Hitachi Air To Air Heat Pumps async_add_entities( WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget][device.protocol]( diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py index c74ff2829cc..331823c594a 100644 --- a/homeassistant/components/overkiz/climate_entities/__init__.py +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -1,4 +1,6 @@ """Climate entities for the Overkiz (by Somfy) integration.""" +from enum import StrEnum, unique + from pyoverkiz.enums import Protocol from pyoverkiz.enums.ui import UIWidget @@ -10,18 +12,30 @@ from .atlantic_electrical_towel_dryer import AtlanticElectricalTowelDryer from .atlantic_heat_recovery_ventilation import AtlanticHeatRecoveryVentilation from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone from .atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl +from .atlantic_pass_apc_zone_control_zone import AtlanticPassAPCZoneControlZone from .hitachi_air_to_air_heat_pump_hlrrwifi import HitachiAirToAirHeatPumpHLRRWIFI from .somfy_heating_temperature_interface import SomfyHeatingTemperatureInterface from .somfy_thermostat import SomfyThermostat from .valve_heating_temperature_interface import ValveHeatingTemperatureInterface + +@unique +class Controllable(StrEnum): + """Enum for widget controllables.""" + + IO_ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE = ( + "io:AtlanticPassAPCHeatingAndCoolingZoneComponent" + ) + IO_ATLANTIC_PASS_APC_ZONE_CONTROL_ZONE = ( + "io:AtlanticPassAPCZoneControlZoneComponent" + ) + + WIDGET_TO_CLIMATE_ENTITY = { UIWidget.ATLANTIC_ELECTRICAL_HEATER: AtlanticElectricalHeater, UIWidget.ATLANTIC_ELECTRICAL_HEATER_WITH_ADJUSTABLE_TEMPERATURE_SETPOINT: AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint, UIWidget.ATLANTIC_ELECTRICAL_TOWEL_DRYER: AtlanticElectricalTowelDryer, UIWidget.ATLANTIC_HEAT_RECOVERY_VENTILATION: AtlanticHeatRecoveryVentilation, - # ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE works exactly the same as ATLANTIC_PASS_APC_HEATING_ZONE - UIWidget.ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: AtlanticPassAPCHeatingZone, UIWidget.ATLANTIC_PASS_APC_HEATING_ZONE: AtlanticPassAPCHeatingZone, UIWidget.ATLANTIC_PASS_APC_ZONE_CONTROL: AtlanticPassAPCZoneControl, UIWidget.SOMFY_HEATING_TEMPERATURE_INTERFACE: SomfyHeatingTemperatureInterface, @@ -29,6 +43,15 @@ WIDGET_TO_CLIMATE_ENTITY = { UIWidget.VALVE_HEATING_TEMPERATURE_INTERFACE: ValveHeatingTemperatureInterface, } +# For Atlantic APC, some devices are standalone and control themselves, some others needs to be +# managed by a ZoneControl device. Widget name is the same in the two cases. +WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY = { + UIWidget.ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: { + Controllable.IO_ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: AtlanticPassAPCHeatingZone, + Controllable.IO_ATLANTIC_PASS_APC_ZONE_CONTROL_ZONE: AtlanticPassAPCZoneControlZone, + } +} + # Hitachi air-to-air heatpumps come in 2 flavors (HLRRWIFI and OVP) that are separated in 2 classes WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY = { UIWidget.HITACHI_AIR_TO_AIR_HEAT_PUMP: { diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py index 25dab7c1d7e..157ec72a249 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py @@ -49,7 +49,15 @@ OVERKIZ_TO_PRESET_MODES: dict[str, str] = { OverkizCommandParam.INTERNAL_SCHEDULING: PRESET_HOME, } -PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_PRESET_MODES.items()} +PRESET_MODES_TO_OVERKIZ: dict[str, str] = { + PRESET_COMFORT: OverkizCommandParam.COMFORT, + PRESET_AWAY: OverkizCommandParam.ABSENCE, + PRESET_ECO: OverkizCommandParam.ECO, + PRESET_FROST_PROTECTION: OverkizCommandParam.FROSTPROTECTION, + PRESET_EXTERNAL: OverkizCommandParam.EXTERNAL_SCHEDULING, + PRESET_HOME: OverkizCommandParam.INTERNAL_SCHEDULING, +} + OVERKIZ_TO_PROFILE_MODES: dict[str, str] = { OverkizCommandParam.OFF: PRESET_SLEEP, diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py index fe9f20b05fc..cfb92067875 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py @@ -10,6 +10,7 @@ from homeassistant.components.climate import ( ) from homeassistant.const import UnitOfTemperature +from ..coordinator import OverkizDataUpdateCoordinator from ..entity import OverkizEntity OVERKIZ_TO_HVAC_MODE: dict[str, HVACMode] = { @@ -25,16 +26,48 @@ HVAC_MODE_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_HVAC_MODE.items()} class AtlanticPassAPCZoneControl(OverkizEntity, ClimateEntity): """Representation of Atlantic Pass APC Zone Control.""" - _attr_hvac_modes = [*HVAC_MODE_TO_OVERKIZ] _attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_supported_features = ( ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON ) _enable_turn_on_off_backwards_compatibility = False + def __init__( + self, device_url: str, coordinator: OverkizDataUpdateCoordinator + ) -> None: + """Init method.""" + super().__init__(device_url, coordinator) + + self._attr_hvac_modes = [*HVAC_MODE_TO_OVERKIZ] + + # Cooling is supported by a separate command + if self.is_auto_hvac_mode_available: + self._attr_hvac_modes.append(HVACMode.AUTO) + + @property + def is_auto_hvac_mode_available(self) -> bool: + """Check if auto mode is available on the ZoneControl.""" + + return self.executor.has_command( + OverkizCommand.SET_HEATING_COOLING_AUTO_SWITCH + ) and self.executor.has_state(OverkizState.CORE_HEATING_COOLING_AUTO_SWITCH) + @property def hvac_mode(self) -> HVACMode: """Return hvac operation ie. heat, cool mode.""" + + if ( + self.is_auto_hvac_mode_available + and cast( + str, + self.executor.select_state( + OverkizState.CORE_HEATING_COOLING_AUTO_SWITCH + ), + ) + == OverkizCommandParam.ON + ): + return HVACMode.AUTO + return OVERKIZ_TO_HVAC_MODE[ cast( str, self.executor.select_state(OverkizState.IO_PASS_APC_OPERATING_MODE) @@ -43,6 +76,18 @@ class AtlanticPassAPCZoneControl(OverkizEntity, ClimateEntity): async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" + + if self.is_auto_hvac_mode_available: + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_COOLING_AUTO_SWITCH, + OverkizCommandParam.ON + if hvac_mode == HVACMode.AUTO + else OverkizCommandParam.OFF, + ) + + if hvac_mode == HVACMode.AUTO: + return + await self.executor.async_execute_command( OverkizCommand.SET_PASS_APC_OPERATING_MODE, HVAC_MODE_TO_OVERKIZ[hvac_mode] ) diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py new file mode 100644 index 00000000000..a30cb93f287 --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py @@ -0,0 +1,252 @@ +"""Support for Atlantic Pass APC Heating Control.""" +from __future__ import annotations + +from asyncio import sleep +from typing import Any, cast + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.climate import PRESET_NONE, HVACMode +from homeassistant.const import ATTR_TEMPERATURE + +from ..coordinator import OverkizDataUpdateCoordinator +from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone +from .atlantic_pass_apc_zone_control import OVERKIZ_TO_HVAC_MODE + +PRESET_SCHEDULE = "schedule" +PRESET_MANUAL = "manual" + +OVERKIZ_MODE_TO_PRESET_MODES: dict[str, str] = { + OverkizCommandParam.MANU: PRESET_MANUAL, + OverkizCommandParam.INTERNAL_SCHEDULING: PRESET_SCHEDULE, +} + +PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_MODE_TO_PRESET_MODES.items()} + +TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 1 + + +# Those device depends on a main probe that choose the operating mode (heating, cooling, ...) +class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): + """Representation of Atlantic Pass APC Heating And Cooling Zone Control.""" + + def __init__( + self, device_url: str, coordinator: OverkizDataUpdateCoordinator + ) -> None: + """Init method.""" + super().__init__(device_url, coordinator) + + # There is less supported functions, because they depend on the ZoneControl. + if not self.is_using_derogated_temperature_fallback: + # Modes are not configurable, they will follow current HVAC Mode of Zone Control. + self._attr_hvac_modes = [] + + # Those are available and tested presets on Shogun. + self._attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] + + # Those APC Heating and Cooling probes depends on the zone control device (main probe). + # Only the base device (#1) can be used to get/set some states. + # Like to retrieve and set the current operating mode (heating, cooling, drying, off). + self.zone_control_device = self.executor.linked_device( + TEMPERATURE_ZONECONTROL_DEVICE_INDEX + ) + + @property + def is_using_derogated_temperature_fallback(self) -> bool: + """Check if the device behave like the Pass APC Heating Zone.""" + + return self.executor.has_command( + OverkizCommand.SET_DEROGATED_TARGET_TEMPERATURE + ) + + @property + def zone_control_hvac_mode(self) -> HVACMode: + """Return hvac operation ie. heat, cool, dry, off mode.""" + + if ( + state := self.zone_control_device.states[ + OverkizState.IO_PASS_APC_OPERATING_MODE + ] + ) is not None and (value := state.value_as_str) is not None: + return OVERKIZ_TO_HVAC_MODE[value] + return HVACMode.OFF + + @property + def hvac_mode(self) -> HVACMode: + """Return hvac operation ie. heat, cool, dry, off mode.""" + + if self.is_using_derogated_temperature_fallback: + return super().hvac_mode + + zone_control_hvac_mode = self.zone_control_hvac_mode + + # Should be same, because either thermostat or this integration change both. + on_off_state = cast( + str, + self.executor.select_state( + OverkizState.CORE_COOLING_ON_OFF + if zone_control_hvac_mode == HVACMode.COOL + else OverkizState.CORE_HEATING_ON_OFF + ), + ) + + # Device is Stopped, it means the air flux is flowing but its venting door is closed. + if on_off_state == OverkizCommandParam.OFF: + hvac_mode = HVACMode.OFF + else: + hvac_mode = zone_control_hvac_mode + + # It helps keep it consistent with the Zone Control, within the interface. + if self._attr_hvac_modes != [zone_control_hvac_mode, HVACMode.OFF]: + self._attr_hvac_modes = [zone_control_hvac_mode, HVACMode.OFF] + self.async_write_ha_state() + + return hvac_mode + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + + if self.is_using_derogated_temperature_fallback: + return await super().async_set_hvac_mode(hvac_mode) + + # They are mainly managed by the Zone Control device + # However, it make sense to map the OFF Mode to the Overkiz STOP Preset + + if hvac_mode == HVACMode.OFF: + await self.executor.async_execute_command( + OverkizCommand.SET_COOLING_ON_OFF, + OverkizCommandParam.OFF, + ) + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_ON_OFF, + OverkizCommandParam.OFF, + ) + else: + await self.executor.async_execute_command( + OverkizCommand.SET_COOLING_ON_OFF, + OverkizCommandParam.ON, + ) + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_ON_OFF, + OverkizCommandParam.ON, + ) + + await self.async_refresh_modes() + + @property + def preset_mode(self) -> str: + """Return the current preset mode, e.g., schedule, manual.""" + + if self.is_using_derogated_temperature_fallback: + return super().preset_mode + + mode = OVERKIZ_MODE_TO_PRESET_MODES[ + cast( + str, + self.executor.select_state( + OverkizState.IO_PASS_APC_COOLING_MODE + if self.zone_control_hvac_mode == HVACMode.COOL + else OverkizState.IO_PASS_APC_HEATING_MODE + ), + ) + ] + + return mode if mode is not None else PRESET_NONE + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + + if self.is_using_derogated_temperature_fallback: + return await super().async_set_preset_mode(preset_mode) + + mode = PRESET_MODES_TO_OVERKIZ[preset_mode] + + # For consistency, it is better both are synced like on the Thermostat. + await self.executor.async_execute_command( + OverkizCommand.SET_PASS_APC_HEATING_MODE, mode + ) + await self.executor.async_execute_command( + OverkizCommand.SET_PASS_APC_COOLING_MODE, mode + ) + + await self.async_refresh_modes() + + @property + def target_temperature(self) -> float: + """Return hvac target temperature.""" + + if self.is_using_derogated_temperature_fallback: + return super().target_temperature + + if self.zone_control_hvac_mode == HVACMode.COOL: + return cast( + float, + self.executor.select_state( + OverkizState.CORE_COOLING_TARGET_TEMPERATURE + ), + ) + + if self.zone_control_hvac_mode == HVACMode.HEAT: + return cast( + float, + self.executor.select_state( + OverkizState.CORE_HEATING_TARGET_TEMPERATURE + ), + ) + + return cast( + float, self.executor.select_state(OverkizState.CORE_TARGET_TEMPERATURE) + ) + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new temperature.""" + + if self.is_using_derogated_temperature_fallback: + return await super().async_set_temperature(**kwargs) + + temperature = kwargs[ATTR_TEMPERATURE] + + # Change both (heating/cooling) temperature is a good way to have consistency + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_TARGET_TEMPERATURE, + temperature, + ) + await self.executor.async_execute_command( + OverkizCommand.SET_COOLING_TARGET_TEMPERATURE, + temperature, + ) + await self.executor.async_execute_command( + OverkizCommand.SET_DEROGATION_ON_OFF_STATE, + OverkizCommandParam.OFF, + ) + + # Target temperature may take up to 1 minute to get refreshed. + await self.executor.async_execute_command( + OverkizCommand.REFRESH_TARGET_TEMPERATURE + ) + + async def async_refresh_modes(self) -> None: + """Refresh the device modes to have new states.""" + + # The device needs a bit of time to update everything before a refresh. + await sleep(2) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_HEATING_MODE + ) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_HEATING_PROFILE + ) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_COOLING_MODE + ) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_COOLING_PROFILE + ) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_TARGET_TEMPERATURE + ) From 016f2c75817886c89791c03503a17adc49304009 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:20:19 -0500 Subject: [PATCH 0004/1691] Improve ZHA group color modes (#111669) * Set the color mode based on supported color modes * Replace `zha` with `tuya` in unit test --- homeassistant/components/light/__init__.py | 4 +-- homeassistant/components/zha/light.py | 40 +++++++++++++++------- tests/components/light/test_init.py | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 562789abd26..795975b5c3e 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1336,5 +1336,5 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """Return if light color mode issues should be reported.""" if not self.platform: return True - # philips_js, tuya and zha have known issues, we don't need users to open issues - return self.platform.platform_name not in {"philips_js", "tuya", "zha"} + # philips_js and tuya have known issues, we don't need users to open issues + return self.platform.platform_name not in {"philips_js", "tuya"} diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index 84399f3da32..aa117c7ef9b 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -1185,7 +1185,7 @@ class LightGroup(BaseLight, ZhaGroupEntity): self._zha_config_enhanced_light_transition = False self._attr_color_mode = ColorMode.UNKNOWN - self._attr_supported_color_modes = set() + self._attr_supported_color_modes = {ColorMode.ONOFF} # remove this when all ZHA platforms and base entities are updated @property @@ -1285,6 +1285,19 @@ class LightGroup(BaseLight, ZhaGroupEntity): effects_count = Counter(itertools.chain(all_effects)) self._attr_effect = effects_count.most_common(1)[0][0] + supported_color_modes = {ColorMode.ONOFF} + all_supported_color_modes: list[set[ColorMode]] = list( + helpers.find_state_attributes(states, light.ATTR_SUPPORTED_COLOR_MODES) + ) + if all_supported_color_modes: + # Merge all color modes. + supported_color_modes = filter_supported_color_modes( + set().union(*all_supported_color_modes) + ) + + self._attr_supported_color_modes = supported_color_modes + + self._attr_color_mode = ColorMode.UNKNOWN all_color_modes = list( helpers.find_state_attributes(on_states, light.ATTR_COLOR_MODE) ) @@ -1292,25 +1305,26 @@ class LightGroup(BaseLight, ZhaGroupEntity): # Report the most common color mode, select brightness and onoff last color_mode_count = Counter(itertools.chain(all_color_modes)) if ColorMode.ONOFF in color_mode_count: - color_mode_count[ColorMode.ONOFF] = -1 + if ColorMode.ONOFF in supported_color_modes: + color_mode_count[ColorMode.ONOFF] = -1 + else: + color_mode_count.pop(ColorMode.ONOFF) if ColorMode.BRIGHTNESS in color_mode_count: - color_mode_count[ColorMode.BRIGHTNESS] = 0 - self._attr_color_mode = color_mode_count.most_common(1)[0][0] + if ColorMode.BRIGHTNESS in supported_color_modes: + color_mode_count[ColorMode.BRIGHTNESS] = 0 + else: + color_mode_count.pop(ColorMode.BRIGHTNESS) + if color_mode_count: + self._attr_color_mode = color_mode_count.most_common(1)[0][0] + else: + self._attr_color_mode = next(iter(supported_color_modes)) + if self._attr_color_mode == ColorMode.HS and ( color_mode_count[ColorMode.HS] != len(self._group.members) or self._zha_config_always_prefer_xy_color_mode ): # switch to XY if all members do not support HS self._attr_color_mode = ColorMode.XY - all_supported_color_modes: list[set[ColorMode]] = list( - helpers.find_state_attributes(states, light.ATTR_SUPPORTED_COLOR_MODES) - ) - if all_supported_color_modes: - # Merge all color modes. - self._attr_supported_color_modes = filter_supported_color_modes( - set().union(*all_supported_color_modes) - ) - self._attr_supported_features = LightEntityFeature(0) for support in helpers.find_state_attributes(states, ATTR_SUPPORTED_FEATURES): # Merge supported features by emulating support for every feature diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 4becbd87c1b..ca25611f890 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -2791,7 +2791,7 @@ def test_report_invalid_color_mode( ( light.ColorMode.ONOFF, {light.ColorMode.ONOFF, light.ColorMode.BRIGHTNESS}, - "zha", # We don't log issues for zha + "tuya", # We don't log issues for tuya False, ), ], From 826009cdc530ad1fcb1282d9df804fe4a0c093e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 12:39:19 -1000 Subject: [PATCH 0005/1691] Import isy994 in the executor to avoid blocking the event loop (#111766) --- homeassistant/components/isy994/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 3aa81027b4f..8c9815cd425 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -21,6 +21,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/isy994", + "import_executor": true, "integration_type": "hub", "iot_class": "local_push", "loggers": ["pyisy"], From 09c16ffb3d767d881dd6d3076a1b8322481d941f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 29 Feb 2024 00:46:32 +0100 Subject: [PATCH 0006/1691] Bump version to 2024.4.0.dev0 (#111755) --- .github/workflows/ci.yaml | 2 +- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9a898d11aa5..e6a17d1090c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,7 +36,7 @@ env: CACHE_VERSION: 5 PIP_CACHE_VERSION: 4 MYPY_CACHE_VERSION: 7 - HA_SHORT_VERSION: "2024.3" + HA_SHORT_VERSION: "2024.4" DEFAULT_PYTHON: "3.11" ALL_PYTHON_VERSIONS: "['3.11', '3.12']" # 10.3 is the oldest supported version diff --git a/homeassistant/const.py b/homeassistant/const.py index cd68b98b2e3..ed9c280d39d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -15,7 +15,7 @@ from .helpers.deprecation import ( APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 -MINOR_VERSION: Final = 3 +MINOR_VERSION: Final = 4 PATCH_VERSION: Final = "0.dev0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" diff --git a/pyproject.toml b/pyproject.toml index 003cb73231e..c8959fb97ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.3.0.dev0" +version = "2024.4.0.dev0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From ab74c11d339f5b73c6261a3f178a9f0b99224133 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 13:58:23 -1000 Subject: [PATCH 0007/1691] Import opower in the executor to avoid blocking the event loop (#111778) fixes #111777 --- homeassistant/components/opower/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 418f2a5723b..820aac5d20a 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -5,6 +5,7 @@ "config_flow": true, "dependencies": ["recorder"], "documentation": "https://www.home-assistant.io/integrations/opower", + "import_executor": true, "iot_class": "cloud_polling", "loggers": ["opower"], "requirements": ["opower==0.3.1"] From bd07b654d5b4a7f432d3129b9d354d7088f4f5a9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 13:59:13 -1000 Subject: [PATCH 0008/1691] Import coinbase in the executor to avoid blocking the event loop (#111774) fixes #111773 --- homeassistant/components/coinbase/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/coinbase/manifest.json b/homeassistant/components/coinbase/manifest.json index 515fe9f9abb..dbb40b24fcc 100644 --- a/homeassistant/components/coinbase/manifest.json +++ b/homeassistant/components/coinbase/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@tombrien"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/coinbase", + "import_executor": true, "iot_class": "cloud_polling", "loggers": ["coinbase"], "requirements": ["coinbase==2.1.0"] From 73f283435d78b06d42bc9771048fced95bd24cb7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 13:59:26 -1000 Subject: [PATCH 0009/1691] Import blink in the executor to avoid blocking the event loop (#111772) fixes #111771 --- homeassistant/components/blink/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index 445a469b141..48db78b572c 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -18,6 +18,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/blink", + "import_executor": true, "iot_class": "cloud_polling", "loggers": ["blinkpy"], "requirements": ["blinkpy==0.22.6"] From 220e66faad92dbc7c8f5a69da41e91f9441c3250 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 29 Feb 2024 00:59:44 +0100 Subject: [PATCH 0010/1691] Bump aiohue to 4.7.1 (#111770) bump aiohue to 4.7.1 --- homeassistant/components/hue/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index 4cd6ca143cb..e8d214da3c8 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -11,6 +11,6 @@ "iot_class": "local_push", "loggers": ["aiohue"], "quality_scale": "platinum", - "requirements": ["aiohue==4.7.0"], + "requirements": ["aiohue==4.7.1"], "zeroconf": ["_hue._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 8b456bcef33..c3060d0f182 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -276,7 +276,7 @@ aiohttp-zlib-ng==0.3.1 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.7.0 +aiohue==4.7.1 # homeassistant.components.imap aioimaplib==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6dde980cb46..e9bc5a78a25 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -252,7 +252,7 @@ aiohttp-zlib-ng==0.3.1 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.7.0 +aiohue==4.7.1 # homeassistant.components.imap aioimaplib==1.0.1 From 61f6df527e5b08d67b282d8f4fba7b941a9cbc3c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 14:00:17 -1000 Subject: [PATCH 0011/1691] Import cryptography early since importing openssl is not thread-safe (#111768) --- homeassistant/bootstrap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 428220685eb..c3da28752d7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -14,6 +14,9 @@ import threading from time import monotonic from typing import TYPE_CHECKING, Any +# Import cryptography early since import openssl is not thread-safe +# _frozen_importlib._DeadlockError: deadlock detected by _ModuleLock('cryptography.hazmat.backends.openssl.backend') +import cryptography # noqa: F401 import voluptuous as vol import yarl From 42a28f4e67327013c376f23c2784ac40c89205fe Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 29 Feb 2024 01:00:31 +0100 Subject: [PATCH 0012/1691] Improve zha coordinator typing (#111767) --- homeassistant/components/zha/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zha/update.py b/homeassistant/components/zha/update.py index d45c24253be..12d89972380 100644 --- a/homeassistant/components/zha/update.py +++ b/homeassistant/components/zha/update.py @@ -71,7 +71,7 @@ async def async_setup_entry( config_entry.async_on_unload(unsub) -class ZHAFirmwareUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=hass-enforce-coordinator-module +class ZHAFirmwareUpdateCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-enforce-coordinator-module """Firmware update coordinator that broadcasts updates network-wide.""" def __init__( From c861bd6c5610c9defbed1c441973f4f59fefce8b Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 29 Feb 2024 01:01:11 +0100 Subject: [PATCH 0013/1691] Bump Python Matter Server to 5.7.0 (#111765) --- homeassistant/components/matter/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index f8ce353f894..0e1ed4e80b6 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/matter", "import_executor": true, "iot_class": "local_push", - "requirements": ["python-matter-server==5.5.0"] + "requirements": ["python-matter-server==5.7.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index c3060d0f182..0035b29c0ce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2251,7 +2251,7 @@ python-kasa[speedups]==0.6.2.1 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==5.5.0 +python-matter-server==5.7.0 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e9bc5a78a25..d647d11dab9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1727,7 +1727,7 @@ python-izone==1.2.9 python-kasa[speedups]==0.6.2.1 # homeassistant.components.matter -python-matter-server==5.5.0 +python-matter-server==5.7.0 # homeassistant.components.xiaomi_miio python-miio==0.5.12 From 3c8fcaf91235112b0ca93900229749d8c2564af5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 14:01:39 -1000 Subject: [PATCH 0014/1691] Import androidtv_remote in the executor to avoid blocking the event loop (#111776) fixes #111775 --- homeassistant/components/androidtv_remote/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/androidtv_remote/manifest.json b/homeassistant/components/androidtv_remote/manifest.json index f45dee34afe..02197a61681 100644 --- a/homeassistant/components/androidtv_remote/manifest.json +++ b/homeassistant/components/androidtv_remote/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@tronikos", "@Drafteed"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/androidtv_remote", + "import_executor": true, "integration_type": "device", "iot_class": "local_push", "loggers": ["androidtvremote2"], From 5bf7a009892a27550206441b28adfbbe38424a10 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 14:09:51 -1000 Subject: [PATCH 0015/1691] Move DATA_LOGGING constant to homeassistant.const (#111763) --- homeassistant/bootstrap.py | 2 +- homeassistant/components/api/__init__.py | 2 +- homeassistant/const.py | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index c3da28752d7..0c51c6d2e08 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -24,6 +24,7 @@ from . import config as conf_util, config_entries, core, loader, requirements from .components import http from .const import ( FORMAT_DATETIME, + KEY_DATA_LOGGING as DATA_LOGGING, REQUIRED_NEXT_PYTHON_HA_RELEASE, REQUIRED_NEXT_PYTHON_VER, SIGNAL_BOOTSTRAP_INTEGRATIONS, @@ -65,7 +66,6 @@ _LOGGER = logging.getLogger(__name__) ERROR_LOG_FILENAME = "home-assistant.log" # hass.data key for logging information. -DATA_LOGGING = "logging" DATA_REGISTRIES_LOADED = "bootstrap_registries_loaded" LOG_SLOW_STARTUP_INTERVAL = 60 diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index ad162d143dd..01a84cf606a 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -12,7 +12,6 @@ import voluptuous as vol from homeassistant.auth.models import User from homeassistant.auth.permissions.const import POLICY_READ -from homeassistant.bootstrap import DATA_LOGGING from homeassistant.components.http import ( KEY_HASS, KEY_HASS_USER, @@ -23,6 +22,7 @@ from homeassistant.const import ( CONTENT_TYPE_JSON, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, + KEY_DATA_LOGGING as DATA_LOGGING, MATCH_ALL, URL_API, URL_API_COMPONENTS, diff --git a/homeassistant/const.py b/homeassistant/const.py index ed9c280d39d..31b08becee6 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1602,6 +1602,11 @@ HASSIO_USER_NAME = "Supervisor" SIGNAL_BOOTSTRAP_INTEGRATIONS = "bootstrap_integrations" + +# hass.data key for logging information. +KEY_DATA_LOGGING = "logging" + + # Date/Time formats FORMAT_DATE: Final = "%Y-%m-%d" FORMAT_TIME: Final = "%H:%M:%S" From f1398dd127af83b248fe2ded30c78ed8dfc30f9c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 14:20:10 -1000 Subject: [PATCH 0016/1691] Import backup in the executor to avoid blocking the event loop (#111781) --- homeassistant/components/backup/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/backup/manifest.json b/homeassistant/components/backup/manifest.json index afa4483e95a..2ba9cebb7dd 100644 --- a/homeassistant/components/backup/manifest.json +++ b/homeassistant/components/backup/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@home-assistant/core"], "dependencies": ["http", "websocket_api"], "documentation": "https://www.home-assistant.io/integrations/backup", + "import_executor": true, "integration_type": "system", "iot_class": "calculated", "quality_scale": "internal", From f31244bac422f7e41f4a3da0c0d86690f11f41af Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 29 Feb 2024 01:31:33 +0100 Subject: [PATCH 0017/1691] Add normalized name registry items base class (#111666) * Add normalized name base registry items class * Add tests --- homeassistant/helpers/area_registry.py | 75 ++++-------------- homeassistant/helpers/floor_registry.py | 75 ++++-------------- homeassistant/helpers/label_registry.py | 77 ++++--------------- .../helpers/normalized_name_base_registry.py | 67 ++++++++++++++++ .../test_normalized_name_base_registry.py | 67 ++++++++++++++++ 5 files changed, 174 insertions(+), 187 deletions(-) create mode 100644 homeassistant/helpers/normalized_name_base_registry.py create mode 100644 tests/helpers/test_normalized_name_base_registry.py diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 38c554ffda3..f1731f43473 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,8 +1,7 @@ """Provide a way to connect devices to one physical location.""" from __future__ import annotations -from collections import UserDict -from collections.abc import Iterable, ValuesView +from collections.abc import Iterable import dataclasses from typing import Any, Literal, TypedDict, cast @@ -10,6 +9,11 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify from . import device_registry as dr, entity_registry as er +from .normalized_name_base_registry import ( + NormalizedNameBaseRegistryEntry, + NormalizedNameBaseRegistryItems, + normalize_name, +) from .storage import Store from .typing import UNDEFINED, UndefinedType @@ -29,7 +33,7 @@ class EventAreaRegistryUpdatedData(TypedDict): @dataclasses.dataclass(frozen=True, kw_only=True, slots=True) -class AreaEntry: +class AreaEntry(NormalizedNameBaseRegistryEntry): """Area Registry Entry.""" aliases: set[str] @@ -37,57 +41,9 @@ class AreaEntry: icon: str | None id: str labels: set[str] = dataclasses.field(default_factory=set) - name: str - normalized_name: str picture: str | None -class AreaRegistryItems(UserDict[str, AreaEntry]): - """Container for area registry items, maps area id -> entry. - - Maintains an additional index: - - normalized name -> entry - """ - - def __init__(self) -> None: - """Initialize the container.""" - super().__init__() - self._normalized_names: dict[str, AreaEntry] = {} - - def values(self) -> ValuesView[AreaEntry]: - """Return the underlying values to avoid __iter__ overhead.""" - return self.data.values() - - def __setitem__(self, key: str, entry: AreaEntry) -> None: - """Add an item.""" - data = self.data - normalized_name = normalize_area_name(entry.name) - - if key in data: - old_entry = data[key] - if ( - normalized_name != old_entry.normalized_name - and normalized_name in self._normalized_names - ): - raise ValueError( - f"The name {entry.name} ({normalized_name}) is already in use" - ) - del self._normalized_names[old_entry.normalized_name] - data[key] = entry - self._normalized_names[normalized_name] = entry - - def __delitem__(self, key: str) -> None: - """Remove an item.""" - entry = self[key] - normalized_name = normalize_area_name(entry.name) - del self._normalized_names[normalized_name] - super().__delitem__(key) - - def get_area_by_name(self, name: str) -> AreaEntry | None: - """Get area by name.""" - return self._normalized_names.get(normalize_area_name(name)) - - class AreaRegistryStore(Store[dict[str, list[dict[str, Any]]]]): """Store area registry data.""" @@ -133,7 +89,7 @@ class AreaRegistryStore(Store[dict[str, list[dict[str, Any]]]]): class AreaRegistry: """Class to hold a registry of areas.""" - areas: AreaRegistryItems + areas: NormalizedNameBaseRegistryItems[AreaEntry] _area_data: dict[str, AreaEntry] def __init__(self, hass: HomeAssistant) -> None: @@ -159,7 +115,7 @@ class AreaRegistry: @callback def async_get_area_by_name(self, name: str) -> AreaEntry | None: """Get area by name.""" - return self.areas.get_area_by_name(name) + return self.areas.get_by_name(name) @callback def async_list_areas(self) -> Iterable[AreaEntry]: @@ -185,7 +141,7 @@ class AreaRegistry: picture: str | None = None, ) -> AreaEntry: """Create a new area.""" - normalized_name = normalize_area_name(name) + normalized_name = normalize_name(name) if self.async_get_area_by_name(name): raise ValueError(f"The name {name} ({normalized_name}) is already in use") @@ -281,7 +237,7 @@ class AreaRegistry: if name is not UNDEFINED and name != old.name: new_values["name"] = name - new_values["normalized_name"] = normalize_area_name(name) + new_values["normalized_name"] = normalize_name(name) if not new_values: return old @@ -297,12 +253,12 @@ class AreaRegistry: data = await self._store.async_load() - areas = AreaRegistryItems() + areas = NormalizedNameBaseRegistryItems[AreaEntry]() if data is not None: for area in data["areas"]: assert area["name"] is not None and area["id"] is not None - normalized_name = normalize_area_name(area["name"]) + normalized_name = normalize_name(area["name"]) areas[area["id"]] = AreaEntry( aliases=set(area["aliases"]), floor_id=area["floor_id"], @@ -421,8 +377,3 @@ def async_entries_for_floor(registry: AreaRegistry, floor_id: str) -> list[AreaE def async_entries_for_label(registry: AreaRegistry, label_id: str) -> list[AreaEntry]: """Return entries that match a label.""" return [area for area in registry.areas.values() if label_id in area.labels] - - -def normalize_area_name(area_name: str) -> str: - """Normalize an area name by removing whitespace and case folding.""" - return area_name.casefold().replace(" ", "") diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index 1149bbd1729..978471d7cd2 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -1,8 +1,7 @@ """Provide a way to assign areas to floors in one's home.""" from __future__ import annotations -from collections import UserDict -from collections.abc import Iterable, ValuesView +from collections.abc import Iterable import dataclasses from dataclasses import dataclass from typing import TYPE_CHECKING, Literal, TypedDict, cast @@ -10,6 +9,11 @@ from typing import TYPE_CHECKING, Literal, TypedDict, cast from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify +from .normalized_name_base_registry import ( + NormalizedNameBaseRegistryEntry, + NormalizedNameBaseRegistryItems, + normalize_name, +) from .storage import Store from .typing import UNDEFINED, EventType, UndefinedType @@ -31,67 +35,19 @@ EventFloorRegistryUpdated = EventType[EventFloorRegistryUpdatedData] @dataclass(slots=True, kw_only=True, frozen=True) -class FloorEntry: +class FloorEntry(NormalizedNameBaseRegistryEntry): """Floor registry entry.""" aliases: set[str] floor_id: str icon: str | None = None level: int = 0 - name: str - normalized_name: str - - -class FloorRegistryItems(UserDict[str, FloorEntry]): - """Container for floor registry items, maps floor id -> entry. - - Maintains an additional index: - - normalized name -> entry - """ - - def __init__(self) -> None: - """Initialize the container.""" - super().__init__() - self._normalized_names: dict[str, FloorEntry] = {} - - def values(self) -> ValuesView[FloorEntry]: - """Return the underlying values to avoid __iter__ overhead.""" - return self.data.values() - - def __setitem__(self, key: str, entry: FloorEntry) -> None: - """Add an item.""" - data = self.data - normalized_name = _normalize_floor_name(entry.name) - - if key in data: - old_entry = data[key] - if ( - normalized_name != old_entry.normalized_name - and normalized_name in self._normalized_names - ): - raise ValueError( - f"The name {entry.name} ({normalized_name}) is already in use" - ) - del self._normalized_names[old_entry.normalized_name] - data[key] = entry - self._normalized_names[normalized_name] = entry - - def __delitem__(self, key: str) -> None: - """Remove an item.""" - entry = self[key] - normalized_name = _normalize_floor_name(entry.name) - del self._normalized_names[normalized_name] - super().__delitem__(key) - - def get_floor_by_name(self, name: str) -> FloorEntry | None: - """Get floor by name.""" - return self._normalized_names.get(_normalize_floor_name(name)) class FloorRegistry: """Class to hold a registry of floors.""" - floors: FloorRegistryItems + floors: NormalizedNameBaseRegistryItems[FloorEntry] _floor_data: dict[str, FloorEntry] def __init__(self, hass: HomeAssistant) -> None: @@ -118,7 +74,7 @@ class FloorRegistry: @callback def async_get_floor_by_name(self, name: str) -> FloorEntry | None: """Get floor by name.""" - return self.floors.get_floor_by_name(name) + return self.floors.get_by_name(name) @callback def async_list_floors(self) -> Iterable[FloorEntry]: @@ -150,7 +106,7 @@ class FloorRegistry: f"The name {name} ({floor.normalized_name}) is already in use" ) - normalized_name = _normalize_floor_name(name) + normalized_name = normalize_name(name) floor = FloorEntry( aliases=aliases or set(), @@ -208,7 +164,7 @@ class FloorRegistry: } if name is not UNDEFINED and name != old.name: changes["name"] = name - changes["normalized_name"] = _normalize_floor_name(name) + changes["normalized_name"] = normalize_name(name) if not changes: return old @@ -229,7 +185,7 @@ class FloorRegistry: async def async_load(self) -> None: """Load the floor registry.""" data = await self._store.async_load() - floors = FloorRegistryItems() + floors = NormalizedNameBaseRegistryItems[FloorEntry]() if data is not None: for floor in data["floors"]: @@ -240,7 +196,7 @@ class FloorRegistry: assert isinstance(floor["name"], str) assert isinstance(floor["floor_id"], str) - normalized_name = _normalize_floor_name(floor["name"]) + normalized_name = normalize_name(floor["name"]) floors[floor["floor_id"]] = FloorEntry( aliases=set(floor["aliases"]), icon=floor["icon"], @@ -286,8 +242,3 @@ async def async_load(hass: HomeAssistant) -> None: assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = FloorRegistry(hass) await hass.data[DATA_REGISTRY].async_load() - - -def _normalize_floor_name(floor_name: str) -> str: - """Normalize a floor name by removing whitespace and case folding.""" - return floor_name.casefold().replace(" ", "") diff --git a/homeassistant/helpers/label_registry.py b/homeassistant/helpers/label_registry.py index 9c7f20a6515..ef3abc19d8c 100644 --- a/homeassistant/helpers/label_registry.py +++ b/homeassistant/helpers/label_registry.py @@ -1,8 +1,7 @@ """Provide a way to label and group anything.""" from __future__ import annotations -from collections import UserDict -from collections.abc import Iterable, ValuesView +from collections.abc import Iterable import dataclasses from dataclasses import dataclass from typing import Literal, TypedDict, cast @@ -10,6 +9,11 @@ from typing import Literal, TypedDict, cast from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify +from .normalized_name_base_registry import ( + NormalizedNameBaseRegistryEntry, + NormalizedNameBaseRegistryItems, + normalize_name, +) from .storage import Store from .typing import UNDEFINED, EventType, UndefinedType @@ -30,68 +34,20 @@ class EventLabelRegistryUpdatedData(TypedDict): EventLabelRegistryUpdated = EventType[EventLabelRegistryUpdatedData] -@dataclass(slots=True, frozen=True) -class LabelEntry: +@dataclass(slots=True, frozen=True, kw_only=True) +class LabelEntry(NormalizedNameBaseRegistryEntry): """Label Registry Entry.""" label_id: str - name: str - normalized_name: str description: str | None = None color: str | None = None icon: str | None = None -class LabelRegistryItems(UserDict[str, LabelEntry]): - """Container for label registry items, maps label id -> entry. - - Maintains an additional index: - - normalized name -> entry - """ - - def __init__(self) -> None: - """Initialize the container.""" - super().__init__() - self._normalized_names: dict[str, LabelEntry] = {} - - def values(self) -> ValuesView[LabelEntry]: - """Return the underlying values to avoid __iter__ overhead.""" - return self.data.values() - - def __setitem__(self, key: str, entry: LabelEntry) -> None: - """Add an item.""" - data = self.data - normalized_name = _normalize_label_name(entry.name) - - if key in data: - old_entry = data[key] - if ( - normalized_name != old_entry.normalized_name - and normalized_name in self._normalized_names - ): - raise ValueError( - f"The name {entry.name} ({normalized_name}) is already in use" - ) - del self._normalized_names[old_entry.normalized_name] - data[key] = entry - self._normalized_names[normalized_name] = entry - - def __delitem__(self, key: str) -> None: - """Remove an item.""" - entry = self[key] - normalized_name = _normalize_label_name(entry.name) - del self._normalized_names[normalized_name] - super().__delitem__(key) - - def get_label_by_name(self, name: str) -> LabelEntry | None: - """Get label by name.""" - return self._normalized_names.get(_normalize_label_name(name)) - - class LabelRegistry: """Class to hold a registry of labels.""" - labels: LabelRegistryItems + labels: NormalizedNameBaseRegistryItems[LabelEntry] _label_data: dict[str, LabelEntry] def __init__(self, hass: HomeAssistant) -> None: @@ -116,7 +72,7 @@ class LabelRegistry: @callback def async_get_label_by_name(self, name: str) -> LabelEntry | None: """Get label by name.""" - return self.labels.get_label_by_name(name) + return self.labels.get_by_name(name) @callback def async_list_labels(self) -> Iterable[LabelEntry]: @@ -148,7 +104,7 @@ class LabelRegistry: f"The name {name} ({label.normalized_name}) is already in use" ) - normalized_name = _normalize_label_name(name) + normalized_name = normalize_name(name) label = LabelEntry( color=color, @@ -207,7 +163,7 @@ class LabelRegistry: if name is not UNDEFINED and name != old.name: changes["name"] = name - changes["normalized_name"] = _normalize_label_name(name) + changes["normalized_name"] = normalize_name(name) if not changes: return old @@ -228,7 +184,7 @@ class LabelRegistry: async def async_load(self) -> None: """Load the label registry.""" data = await self._store.async_load() - labels = LabelRegistryItems() + labels = NormalizedNameBaseRegistryItems[LabelEntry]() if data is not None: for label in data["labels"]: @@ -236,7 +192,7 @@ class LabelRegistry: if label["label_id"] is None or label["name"] is None: continue - normalized_name = _normalize_label_name(label["name"]) + normalized_name = normalize_name(label["name"]) labels[label["label_id"]] = LabelEntry( color=label["color"], description=label["description"], @@ -282,8 +238,3 @@ async def async_load(hass: HomeAssistant) -> None: assert DATA_REGISTRY not in hass.data hass.data[DATA_REGISTRY] = LabelRegistry(hass) await hass.data[DATA_REGISTRY].async_load() - - -def _normalize_label_name(label_name: str) -> str: - """Normalize a label name by removing whitespace and case folding.""" - return label_name.casefold().replace(" ", "") diff --git a/homeassistant/helpers/normalized_name_base_registry.py b/homeassistant/helpers/normalized_name_base_registry.py new file mode 100644 index 00000000000..13a4cb10312 --- /dev/null +++ b/homeassistant/helpers/normalized_name_base_registry.py @@ -0,0 +1,67 @@ +"""Provide a base class for registries that use a normalized name index.""" +from collections import UserDict +from collections.abc import ValuesView +from dataclasses import dataclass +from typing import TypeVar + + +@dataclass(slots=True, frozen=True, kw_only=True) +class NormalizedNameBaseRegistryEntry: + """Normalized Name Base Registry Entry.""" + + name: str + normalized_name: str + + +_VT = TypeVar("_VT", bound=NormalizedNameBaseRegistryEntry) + + +def normalize_name(name: str) -> str: + """Normalize a name by removing whitespace and case folding.""" + return name.casefold().replace(" ", "") + + +class NormalizedNameBaseRegistryItems(UserDict[str, _VT]): + """Base container for normalized name registry items, maps key -> entry. + + Maintains an additional index: + - normalized name -> entry + """ + + def __init__(self) -> None: + """Initialize the container.""" + super().__init__() + self._normalized_names: dict[str, _VT] = {} + + def values(self) -> ValuesView[_VT]: + """Return the underlying values to avoid __iter__ overhead.""" + return self.data.values() + + def __setitem__(self, key: str, entry: _VT) -> None: + """Add an item.""" + data = self.data + normalized_name = normalize_name(entry.name) + + if key in data: + old_entry = data[key] + if ( + normalized_name != old_entry.normalized_name + and normalized_name in self._normalized_names + ): + raise ValueError( + f"The name {entry.name} ({normalized_name}) is already in use" + ) + del self._normalized_names[old_entry.normalized_name] + data[key] = entry + self._normalized_names[normalized_name] = entry + + def __delitem__(self, key: str) -> None: + """Remove an item.""" + entry = self[key] + normalized_name = normalize_name(entry.name) + del self._normalized_names[normalized_name] + super().__delitem__(key) + + def get_by_name(self, name: str) -> _VT | None: + """Get entry by name.""" + return self._normalized_names.get(normalize_name(name)) diff --git a/tests/helpers/test_normalized_name_base_registry.py b/tests/helpers/test_normalized_name_base_registry.py new file mode 100644 index 00000000000..0b0e53abe83 --- /dev/null +++ b/tests/helpers/test_normalized_name_base_registry.py @@ -0,0 +1,67 @@ +"""Tests for the normalized name base registry helper.""" +import pytest + +from homeassistant.helpers.normalized_name_base_registry import ( + NormalizedNameBaseRegistryEntry, + NormalizedNameBaseRegistryItems, + normalize_name, +) + + +@pytest.fixture +def registry_items(): + """Fixture for registry items.""" + return NormalizedNameBaseRegistryItems[NormalizedNameBaseRegistryEntry]() + + +def test_normalize_name(): + """Test normalize_name.""" + assert normalize_name("Hello World") == "helloworld" + assert normalize_name("HELLO WORLD") == "helloworld" + assert normalize_name(" Hello World ") == "helloworld" + + +def test_registry_items( + registry_items: NormalizedNameBaseRegistryItems[NormalizedNameBaseRegistryEntry], +): + """Test registry items.""" + entry = NormalizedNameBaseRegistryEntry( + name="Hello World", normalized_name="helloworld" + ) + registry_items["key"] = entry + assert registry_items["key"] == entry + assert list(registry_items.values()) == [entry] + assert registry_items.get_by_name("Hello World") == entry + + # test update entry + entry2 = NormalizedNameBaseRegistryEntry( + name="Hello World 2", normalized_name="helloworld2" + ) + registry_items["key"] = entry2 + assert registry_items["key"] == entry2 + assert list(registry_items.values()) == [entry2] + assert registry_items.get_by_name("Hello World 2") == entry2 + + # test delete entry + del registry_items["key"] + assert "key" not in registry_items + assert list(registry_items.values()) == [] + + +def test_key_already_in_use( + registry_items: NormalizedNameBaseRegistryItems[NormalizedNameBaseRegistryEntry], +): + """Test key already in use.""" + entry = NormalizedNameBaseRegistryEntry( + name="Hello World", normalized_name="helloworld" + ) + registry_items["key"] = entry + + # should raise ValueError if we update a + # key with a entry with the same normalized name + with pytest.raises(ValueError): + entry = NormalizedNameBaseRegistryEntry( + name="Hello World 2", normalized_name="helloworld2" + ) + registry_items["key2"] = entry + registry_items["key"] = entry From 675ddaf7421a428c06905c70b25f10560a80401c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 14:31:41 -1000 Subject: [PATCH 0018/1691] Bump securetar to 2024.2.1 (#111782) --- homeassistant/components/backup/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/backup/manifest.json b/homeassistant/components/backup/manifest.json index 2ba9cebb7dd..be75e4717ef 100644 --- a/homeassistant/components/backup/manifest.json +++ b/homeassistant/components/backup/manifest.json @@ -8,5 +8,5 @@ "integration_type": "system", "iot_class": "calculated", "quality_scale": "internal", - "requirements": ["securetar==2024.2.0"] + "requirements": ["securetar==2024.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0035b29c0ce..ef645cd7c95 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2502,7 +2502,7 @@ screenlogicpy==0.10.0 scsgate==0.1.0 # homeassistant.components.backup -securetar==2024.2.0 +securetar==2024.2.1 # homeassistant.components.sendgrid sendgrid==6.8.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d647d11dab9..ef254d56aac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1915,7 +1915,7 @@ samsungtvws[async,encrypted]==2.6.0 screenlogicpy==0.10.0 # homeassistant.components.backup -securetar==2024.2.0 +securetar==2024.2.1 # homeassistant.components.emulated_kasa # homeassistant.components.sense From 3fbeb7e400f24aef6a0475c350804c8d38ec434d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 14:51:27 -1000 Subject: [PATCH 0019/1691] Fix time trigger tests with leap year (#111785) --- .../homeassistant/triggers/test_time.py | 4 +- .../triggers/test_time_pattern.py | 40 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/components/homeassistant/triggers/test_time.py b/tests/components/homeassistant/triggers/test_time.py index 513827b5432..ab5eb383f96 100644 --- a/tests/components/homeassistant/triggers/test_time.py +++ b/tests/components/homeassistant/triggers/test_time.py @@ -210,7 +210,7 @@ async def test_if_not_fires_using_wrong_at( now = dt_util.utcnow() time_that_will_not_match_right_away = now.replace( - year=now.year + 1, hour=1, minute=0, second=0 + year=now.year + 1, day=1, hour=1, minute=0, second=0 ) freezer.move_to(time_that_will_not_match_right_away) @@ -233,7 +233,7 @@ async def test_if_not_fires_using_wrong_at( assert hass.states.get("automation.automation_0").state == STATE_UNAVAILABLE async_fire_time_changed( - hass, now.replace(year=now.year + 1, hour=1, minute=0, second=5) + hass, now.replace(year=now.year + 1, day=1, hour=1, minute=0, second=5) ) await hass.async_block_till_done() diff --git a/tests/components/homeassistant/triggers/test_time_pattern.py b/tests/components/homeassistant/triggers/test_time_pattern.py index 0f6a075eb6e..e505dd4f3f5 100644 --- a/tests/components/homeassistant/triggers/test_time_pattern.py +++ b/tests/components/homeassistant/triggers/test_time_pattern.py @@ -33,7 +33,7 @@ async def test_if_fires_when_hour_matches( """Test for firing if hour is matching.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, hour=3 + year=now.year + 1, day=1, hour=3 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -55,7 +55,7 @@ async def test_if_fires_when_hour_matches( }, ) - async_fire_time_changed(hass, now.replace(year=now.year + 2, hour=0)) + async_fire_time_changed(hass, now.replace(year=now.year + 2, day=1, hour=0)) await hass.async_block_till_done() assert len(calls) == 1 @@ -66,7 +66,7 @@ async def test_if_fires_when_hour_matches( blocking=True, ) - async_fire_time_changed(hass, now.replace(year=now.year + 1, hour=0)) + async_fire_time_changed(hass, now.replace(year=now.year + 1, day=1, hour=0)) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].data["id"] == 0 @@ -78,7 +78,7 @@ async def test_if_fires_when_minute_matches( """Test for firing if minutes are matching.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, minute=30 + year=now.year + 1, day=1, minute=30 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -97,7 +97,7 @@ async def test_if_fires_when_minute_matches( }, ) - async_fire_time_changed(hass, now.replace(year=now.year + 2, minute=0)) + async_fire_time_changed(hass, now.replace(year=now.year + 2, day=1, minute=0)) await hass.async_block_till_done() assert len(calls) == 1 @@ -109,7 +109,7 @@ async def test_if_fires_when_second_matches( """Test for firing if seconds are matching.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, second=30 + year=now.year + 1, day=1, second=30 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -128,7 +128,7 @@ async def test_if_fires_when_second_matches( }, ) - async_fire_time_changed(hass, now.replace(year=now.year + 2, second=0)) + async_fire_time_changed(hass, now.replace(year=now.year + 2, day=1, second=0)) await hass.async_block_till_done() assert len(calls) == 1 @@ -140,7 +140,7 @@ async def test_if_fires_when_second_as_string_matches( """Test for firing if seconds are matching.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, second=15 + year=now.year + 1, day=1, second=15 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -173,7 +173,7 @@ async def test_if_fires_when_all_matches( """Test for firing if everything matches.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, hour=4 + year=now.year + 1, day=1, hour=4 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -193,7 +193,7 @@ async def test_if_fires_when_all_matches( ) async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=1, minute=2, second=3) + hass, now.replace(year=now.year + 2, day=1, hour=1, minute=2, second=3) ) await hass.async_block_till_done() @@ -206,7 +206,7 @@ async def test_if_fires_periodic_seconds( """Test for firing periodically every second.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, second=1 + year=now.year + 1, day=1, second=1 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -226,7 +226,7 @@ async def test_if_fires_periodic_seconds( ) async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=0, minute=0, second=10) + hass, now.replace(year=now.year + 2, day=1, hour=0, minute=0, second=10) ) await hass.async_block_till_done() @@ -240,7 +240,7 @@ async def test_if_fires_periodic_minutes( now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, minute=1 + year=now.year + 1, day=1, minute=1 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -260,7 +260,7 @@ async def test_if_fires_periodic_minutes( ) async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=0, minute=2, second=0) + hass, now.replace(year=now.year + 2, day=1, hour=0, minute=2, second=0) ) await hass.async_block_till_done() @@ -273,7 +273,7 @@ async def test_if_fires_periodic_hours( """Test for firing periodically every hour.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, hour=1 + year=now.year + 1, day=1, hour=1 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -293,7 +293,7 @@ async def test_if_fires_periodic_hours( ) async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=2, minute=0, second=0) + hass, now.replace(year=now.year + 2, day=1, hour=2, minute=0, second=0) ) await hass.async_block_till_done() @@ -306,7 +306,7 @@ async def test_default_values( """Test for firing at 2 minutes every hour.""" now = dt_util.utcnow() time_that_will_not_match_right_away = dt_util.utcnow().replace( - year=now.year + 1, minute=1 + year=now.year + 1, day=1, minute=1 ) freezer.move_to(time_that_will_not_match_right_away) assert await async_setup_component( @@ -321,21 +321,21 @@ async def test_default_values( ) async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=1, minute=2, second=0) + hass, now.replace(year=now.year + 2, day=1, hour=1, minute=2, second=0) ) await hass.async_block_till_done() assert len(calls) == 1 async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=1, minute=2, second=1) + hass, now.replace(year=now.year + 2, day=1, hour=1, minute=2, second=1) ) await hass.async_block_till_done() assert len(calls) == 1 async_fire_time_changed( - hass, now.replace(year=now.year + 2, hour=2, minute=2, second=0) + hass, now.replace(year=now.year + 2, day=1, hour=2, minute=2, second=0) ) await hass.async_block_till_done() From 943bd179f87ad305e49e1529bf555c0b930fec2b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 15:09:12 -1000 Subject: [PATCH 0020/1691] Import analytics_insights in the executor to avoid blocking the event loop (#111786) fixes #111780 --- homeassistant/components/analytics_insights/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/analytics_insights/manifest.json b/homeassistant/components/analytics_insights/manifest.json index d33bb23b1b7..b55e08a8141 100644 --- a/homeassistant/components/analytics_insights/manifest.json +++ b/homeassistant/components/analytics_insights/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@joostlek"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/analytics_insights", + "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["python_homeassistant_analytics"], From 9f8fbb747eac1f65b9eb88c64771210b6e4d964a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 15:40:16 -1000 Subject: [PATCH 0021/1691] Pre-import api, config, and lovelace in bootstrap to avoid loading them at runtime (#111752) --- homeassistant/bootstrap.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0c51c6d2e08..1a26f8e25c7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -21,7 +21,18 @@ import voluptuous as vol import yarl from . import config as conf_util, config_entries, core, loader, requirements -from .components import http + +# Pre-import config and lovelace which have no requirements here to avoid +# loading them at run time and blocking the event loop. We do this ahead +# of time so that we do not have to flag frontends deps with `import_executor` +# as it would create a thundering heard of executor jobs trying to import +# frontend deps at the same time. +from .components import ( + api as api_pre_import, # noqa: F401 + config as config_pre_import, # noqa: F401 + http, + lovelace as lovelace_pre_import, # noqa: F401 +) from .const import ( FORMAT_DATETIME, KEY_DATA_LOGGING as DATA_LOGGING, From 4eb0f86a653c3090c94d07e8a348273fc122d13c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Feb 2024 20:53:03 -0500 Subject: [PATCH 0022/1691] Remove 'values' from services validation (#111440) * Remove 'values' from services validation * Remove extra blank line --- script/hassfest/services.py | 1 - 1 file changed, 1 deletion(-) diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 580294705cf..8f59d33830d 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -29,7 +29,6 @@ CORE_INTEGRATION_FIELD_SCHEMA = vol.Schema( { vol.Optional("example"): exists, vol.Optional("default"): exists, - vol.Optional("values"): exists, vol.Optional("required"): bool, vol.Optional("advanced"): bool, vol.Optional(CONF_SELECTOR): selector.validate_selector, From 7527e87e263661ef8b9778237653b4bc42e4dc28 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 16:00:47 -1000 Subject: [PATCH 0023/1691] Fix steamist blocking startup by waiting for discovery (#111789) Fix steamist blocking statup by waiting for discovery --- homeassistant/components/steamist/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/steamist/__init__.py b/homeassistant/components/steamist/__init__.py index ee46d644847..e84615b9352 100644 --- a/homeassistant/components/steamist/__init__.py +++ b/homeassistant/components/steamist/__init__.py @@ -14,7 +14,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType -from .const import DISCOVER_SCAN_TIMEOUT, DISCOVERY, DOMAIN, STARTUP_SCAN_TIMEOUT +from .const import DISCOVER_SCAN_TIMEOUT, DISCOVERY, DOMAIN from .coordinator import SteamistDataUpdateCoordinator from .discovery import ( async_discover_device, @@ -32,14 +32,16 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the steamist component.""" domain_data = hass.data.setdefault(DOMAIN, {}) - domain_data[DISCOVERY] = await async_discover_devices(hass, STARTUP_SCAN_TIMEOUT) + domain_data[DISCOVERY] = [] async def _async_discovery(*_: Any) -> None: async_trigger_discovery( hass, await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT) ) - async_trigger_discovery(hass, domain_data[DISCOVERY]) + hass.async_create_background_task( + _async_discovery(), "steamist-discovery", eager_start=True + ) async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL) return True From b40978597cfa3cf0643635aa56c9546d95914f9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 16:02:25 -1000 Subject: [PATCH 0024/1691] Import discord in the executor to avoid blocking the event loop (#111790) `2024-02-28 19:20:04.485 DEBUG (MainThread) [homeassistant.loader] Component discord import took 1.181 seconds (loaded_executor=False)` --- homeassistant/components/discord/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/discord/manifest.json b/homeassistant/components/discord/manifest.json index 5f1ba2a13ef..78d4dc203e2 100644 --- a/homeassistant/components/discord/manifest.json +++ b/homeassistant/components/discord/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@tkdrob"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/discord", + "import_executor": true, "integration_type": "service", "iot_class": "cloud_push", "loggers": ["discord"], From b11e97e13201a1c24e3136e4a2feb3521f616adf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 16:16:38 -1000 Subject: [PATCH 0025/1691] Fix flux_led blocking startup by waiting for discovery (#111787) * Avoid blocking startup by waiting for discovery in flux_led * remove started discovery --- homeassistant/components/flux_led/__init__.py | 16 ++++++---------- tests/components/flux_led/test_init.py | 12 ++---------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py index 100d63d8bf7..2d9dddd3684 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -11,7 +11,7 @@ from flux_led.const import ATTR_ID, WhiteChannelType from flux_led.scanner import FluxLEDDiscovery from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED, Platform +from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import ( @@ -37,7 +37,6 @@ from .const import ( FLUX_LED_DISCOVERY_SIGNAL, FLUX_LED_EXCEPTIONS, SIGNAL_STATE_UPDATED, - STARTUP_SCAN_TIMEOUT, ) from .coordinator import FluxLedUpdateCoordinator from .discovery import ( @@ -89,24 +88,21 @@ def async_wifi_bulb_for_host( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the flux_led component.""" domain_data = hass.data.setdefault(DOMAIN, {}) - domain_data[FLUX_LED_DISCOVERY] = await async_discover_devices( - hass, STARTUP_SCAN_TIMEOUT - ) + domain_data[FLUX_LED_DISCOVERY] = [] @callback def _async_start_background_discovery(*_: Any) -> None: """Run discovery in the background.""" - hass.async_create_background_task(_async_discovery(), "flux_led-discovery") + hass.async_create_background_task( + _async_discovery(), "flux_led-discovery", eager_start=True + ) async def _async_discovery(*_: Any) -> None: async_trigger_discovery( hass, await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT) ) - async_trigger_discovery(hass, domain_data[FLUX_LED_DISCOVERY]) - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STARTED, _async_start_background_discovery - ) + _async_start_background_discovery() async_track_time_interval( hass, _async_start_background_discovery, diff --git a/tests/components/flux_led/test_init.py b/tests/components/flux_led/test_init.py index 7c709bafe73..d75644c7599 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -19,7 +19,6 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, CONF_HOST, CONF_NAME, - EVENT_HOMEASSISTANT_STARTED, STATE_ON, STATE_UNAVAILABLE, ) @@ -57,13 +56,10 @@ async def test_configuring_flux_led_causes_discovery(hass: HomeAssistant) -> Non await hass.async_block_till_done() assert len(scan.mock_calls) == 1 - hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) - await hass.async_block_till_done() - assert len(scan.mock_calls) == 2 async_fire_time_changed(hass, utcnow() + flux_led.DISCOVERY_INTERVAL) await hass.async_block_till_done() - assert len(scan.mock_calls) == 3 + assert len(scan.mock_calls) == 2 @pytest.mark.usefixtures("mock_multiple_broadcast_addresses") @@ -79,15 +75,11 @@ async def test_configuring_flux_led_causes_discovery_multiple_addresses( discover.return_value = [FLUX_DISCOVERY] await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() - assert len(scan.mock_calls) == 2 - hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) - await hass.async_block_till_done() - assert len(scan.mock_calls) == 4 async_fire_time_changed(hass, utcnow() + flux_led.DISCOVERY_INTERVAL) await hass.async_block_till_done() - assert len(scan.mock_calls) == 6 + assert len(scan.mock_calls) == 4 async def test_config_entry_reload(hass: HomeAssistant) -> None: From 59b7f8d103b0cffaf73ff9c09819bf973ce6a6ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 16:34:08 -1000 Subject: [PATCH 0026/1691] Fix tplink blocking startup by waiting for discovery (#111788) * Fix tplink blocking statup by waiting for discovery * remove started --- homeassistant/components/tplink/__init__.py | 8 +++----- tests/components/tplink/test_init.py | 9 ++------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py index e2342e617de..b8510f7ef81 100644 --- a/homeassistant/components/tplink/__init__.py +++ b/homeassistant/components/tplink/__init__.py @@ -28,7 +28,6 @@ from homeassistant.const import ( CONF_MODEL, CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STARTED, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady @@ -112,14 +111,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the TP-Link component.""" hass.data.setdefault(DOMAIN, {}) - if discovered_devices := await async_discover_devices(hass): - async_trigger_discovery(hass, discovered_devices) - async def _async_discovery(*_: Any) -> None: if discovered := await async_discover_devices(hass): async_trigger_discovery(hass, discovered) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_discovery) + hass.async_create_background_task( + _async_discovery(), "tplink first discovery", eager_start=True + ) async_track_time_interval( hass, _async_discovery, DISCOVERY_INTERVAL, cancel_on_shutdown=True ) diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 7bee7823013..4af4a80c927 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -17,7 +17,6 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STARTED, STATE_ON, STATE_UNAVAILABLE, ) @@ -52,17 +51,13 @@ async def test_configuring_tplink_causes_discovery(hass: HomeAssistant) -> None: call_count = len(discover.mock_calls) assert discover.mock_calls - hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) await hass.async_block_till_done() assert len(discover.mock_calls) == call_count * 2 - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) - await hass.async_block_till_done() - assert len(discover.mock_calls) == call_count * 3 - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30)) await hass.async_block_till_done() - assert len(discover.mock_calls) == call_count * 4 + assert len(discover.mock_calls) == call_count * 3 async def test_config_entry_reload(hass: HomeAssistant) -> None: From 1eac7bcbec4807ff45cc200731a8ae4e03c26c2a Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Wed, 28 Feb 2024 18:45:51 -0800 Subject: [PATCH 0027/1691] Fix calendar trigger to survive config entry reloads (#111334) * Fix calendar trigger to survive config entry reloads * Apply suggestions from code review --------- Co-authored-by: Martin Hjelmare --- homeassistant/components/calendar/trigger.py | 31 ++++++--- tests/components/calendar/conftest.py | 40 ++++++----- tests/components/calendar/test_init.py | 7 +- tests/components/calendar/test_recorder.py | 9 ++- tests/components/calendar/test_trigger.py | 71 ++++++++++++++++++-- 5 files changed, 119 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/calendar/trigger.py b/homeassistant/components/calendar/trigger.py index 073c41fc0df..e4fe5d22efd 100644 --- a/homeassistant/components/calendar/trigger.py +++ b/homeassistant/components/calendar/trigger.py @@ -91,11 +91,24 @@ EventFetcher = Callable[[Timespan], Awaitable[list[CalendarEvent]]] QueuedEventFetcher = Callable[[Timespan], Awaitable[list[QueuedCalendarEvent]]] -def event_fetcher(hass: HomeAssistant, entity: CalendarEntity) -> EventFetcher: +def get_entity(hass: HomeAssistant, entity_id: str) -> CalendarEntity: + """Get the calendar entity for the provided entity_id.""" + component: EntityComponent[CalendarEntity] = hass.data[DOMAIN] + if not (entity := component.get_entity(entity_id)) or not isinstance( + entity, CalendarEntity + ): + raise HomeAssistantError( + f"Entity does not exist {entity_id} or is not a calendar entity" + ) + return entity + + +def event_fetcher(hass: HomeAssistant, entity_id: str) -> EventFetcher: """Build an async_get_events wrapper to fetch events during a time span.""" async def async_get_events(timespan: Timespan) -> list[CalendarEvent]: """Return events active in the specified time span.""" + entity = get_entity(hass, entity_id) # Expand by one second to make the end time exclusive end_time = timespan.end + datetime.timedelta(seconds=1) return await entity.async_get_events(hass, timespan.start, end_time) @@ -237,7 +250,10 @@ class CalendarEventListener: self._dispatch_events(now) self._clear_event_listener() self._timespan = self._timespan.next_upcoming(now, UPDATE_INTERVAL) - self._events.extend(await self._fetcher(self._timespan)) + try: + self._events.extend(await self._fetcher(self._timespan)) + except HomeAssistantError as ex: + _LOGGER.error("Calendar trigger failed to fetch events: %s", ex) self._listen_next_calendar_event() @@ -252,13 +268,8 @@ async def async_attach_trigger( event_type = config[CONF_EVENT] offset = config[CONF_OFFSET] - component: EntityComponent[CalendarEntity] = hass.data[DOMAIN] - if not (entity := component.get_entity(entity_id)) or not isinstance( - entity, CalendarEntity - ): - raise HomeAssistantError( - f"Entity does not exist {entity_id} or is not a calendar entity" - ) + # Validate the entity id is valid + get_entity(hass, entity_id) trigger_data = { **trigger_info["trigger_data"], @@ -270,7 +281,7 @@ async def async_attach_trigger( hass, HassJob(action), trigger_data, - queued_event_fetcher(event_fetcher(hass, entity), event_type, offset), + queued_event_fetcher(event_fetcher(hass, entity_id), event_type, offset), ) await listener.async_attach() return listener.async_detach diff --git a/tests/components/calendar/conftest.py b/tests/components/calendar/conftest.py index f42cc6fd508..29d4bb9f5ff 100644 --- a/tests/components/calendar/conftest.py +++ b/tests/components/calendar/conftest.py @@ -99,8 +99,20 @@ def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]: yield +@pytest.fixture(name="config_entry") +async def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: + """Create a mock config entry.""" + config_entry = MockConfigEntry(domain=TEST_DOMAIN) + config_entry.add_to_hass(hass) + return config_entry + + @pytest.fixture -def mock_setup_integration(hass: HomeAssistant, config_flow_fixture: None) -> None: +def mock_setup_integration( + hass: HomeAssistant, + config_flow_fixture: None, + test_entities: list[CalendarEntity], +) -> None: """Fixture to set up a mock integration.""" async def async_setup_entry_init( @@ -129,20 +141,16 @@ def mock_setup_integration(hass: HomeAssistant, config_flow_fixture: None) -> No ), ) - -async def create_mock_platform( - hass: HomeAssistant, - entities: list[CalendarEntity], -) -> MockConfigEntry: - """Create a calendar platform with the specified entities.""" - async def async_setup_entry_platform( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up test event platform via config entry.""" - async_add_entities(entities) + new_entities = create_test_entities() + test_entities.clear() + test_entities.extend(new_entities) + async_add_entities(test_entities) mock_platform( hass, @@ -150,17 +158,15 @@ async def create_mock_platform( MockPlatform(async_setup_entry=async_setup_entry_platform), ) - config_entry = MockConfigEntry(domain=TEST_DOMAIN) - config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - return config_entry - @pytest.fixture(name="test_entities") def mock_test_entities() -> list[MockCalendarEntity]: - """Fixture to create fake entities used in the test.""" + """Fixture that holdes the fake entities created during the test.""" + return [] + + +def create_test_entities() -> list[MockCalendarEntity]: + """Create test entities used during the test.""" half_hour_from_now = dt_util.now() + datetime.timedelta(minutes=30) entity1 = MockCalendarEntity( "Calendar 1", diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index 52d5855271d..d786ce8d8ad 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -21,7 +21,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.issue_registry import IssueRegistry import homeassistant.util.dt as dt_util -from .conftest import TEST_DOMAIN, MockCalendarEntity, create_mock_platform +from .conftest import TEST_DOMAIN, MockCalendarEntity, MockConfigEntry from tests.typing import ClientSessionGenerator, WebSocketGenerator @@ -51,10 +51,11 @@ async def mock_setup_platform( set_time_zone: Any, frozen_time: Any, mock_setup_integration: Any, - test_entities: list[MockCalendarEntity], + config_entry: MockConfigEntry, ) -> None: """Fixture to setup platforms used in the test and fixtures are set up in the right order.""" - await create_mock_platform(hass, test_entities) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() async def test_events_http_api( diff --git a/tests/components/calendar/test_recorder.py b/tests/components/calendar/test_recorder.py index 441d757aa4e..ef6c7658a89 100644 --- a/tests/components/calendar/test_recorder.py +++ b/tests/components/calendar/test_recorder.py @@ -10,9 +10,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util -from .conftest import MockCalendarEntity, create_mock_platform - -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed from tests.components.recorder.common import async_wait_recording_done @@ -22,10 +20,11 @@ async def mock_setup_dependencies( hass: HomeAssistant, set_time_zone: Any, mock_setup_integration: None, - test_entities: list[MockCalendarEntity], + config_entry: MockConfigEntry, ) -> None: """Fixture that ensures the recorder is setup in the right order.""" - await create_mock_platform(hass, test_entities) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() async def test_exclude_attributes(hass: HomeAssistant) -> None: diff --git a/tests/components/calendar/test_trigger.py b/tests/components/calendar/test_trigger.py index 120d2e8bfca..0111f11c27b 100644 --- a/tests/components/calendar/test_trigger.py +++ b/tests/components/calendar/test_trigger.py @@ -27,9 +27,9 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from .conftest import MockCalendarEntity, create_mock_platform +from .conftest import MockCalendarEntity -from tests.common import async_fire_time_changed, async_mock_service +from tests.common import MockConfigEntry, async_fire_time_changed, async_mock_service _LOGGER = logging.getLogger(__name__) @@ -105,10 +105,11 @@ def mock_test_entity(test_entities: list[MockCalendarEntity]) -> MockCalendarEnt async def mock_setup_platform( hass: HomeAssistant, mock_setup_integration: Any, - test_entities: list[MockCalendarEntity], + config_entry: MockConfigEntry, ) -> None: """Fixture to setup platforms used in the test.""" - await create_mock_platform(hass, test_entities) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() @asynccontextmanager @@ -745,3 +746,65 @@ async def test_event_start_trigger_dst( "calendar_event": event3_data, }, ] + + +async def test_config_entry_reload( + hass: HomeAssistant, + calls: Callable[[], list[dict[str, Any]]], + fake_schedule: FakeSchedule, + test_entities: list[MockCalendarEntity], + setup_platform: None, + config_entry: MockConfigEntry, +) -> None: + """Test the a calendar trigger after a config entry reload. + + This sets ups a config entry, sets up an automation for an entity in that + config entry, then reloads the config entry. This reproduces a bug where + the automation kept a reference to the specific entity which would be + invalid after a config entry was reloaded. + """ + async with create_automation(hass, EVENT_START): + assert len(calls()) == 0 + + assert await hass.config_entries.async_reload(config_entry.entry_id) + + # Ensure the reloaded entity has events upcoming. + test_entity = test_entities[1] + event_data = test_entity.create_event( + start=datetime.datetime.fromisoformat("2022-04-19 11:00:00+00:00"), + end=datetime.datetime.fromisoformat("2022-04-19 11:30:00+00:00"), + ) + + await fake_schedule.fire_until( + datetime.datetime.fromisoformat("2022-04-19 11:15:00+00:00"), + ) + + assert calls() == [ + { + "platform": "calendar", + "event": EVENT_START, + "calendar_event": event_data, + } + ] + + +async def test_config_entry_unload( + hass: HomeAssistant, + calls: Callable[[], list[dict[str, Any]]], + fake_schedule: FakeSchedule, + test_entities: list[MockCalendarEntity], + setup_platform: None, + config_entry: MockConfigEntry, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test an automation that references a calendar entity that is unloaded.""" + async with create_automation(hass, EVENT_START): + assert len(calls()) == 0 + + assert await hass.config_entries.async_unload(config_entry.entry_id) + + await fake_schedule.fire_until( + datetime.datetime.fromisoformat("2022-04-19 11:15:00+00:00"), + ) + + assert "Entity does not exist calendar.calendar_2" in caplog.text From c6fd9e25ced8e1023088f670df14796b4bafd480 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 28 Feb 2024 23:09:48 -0500 Subject: [PATCH 0028/1691] get_matter_device_info: Test the Matter config entry is set up (#111792) Ensure the Matter config entry is set up --- homeassistant/components/matter/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/matter/__init__.py b/homeassistant/components/matter/__init__.py index aa89856074f..06c205859bb 100644 --- a/homeassistant/components/matter/__init__.py +++ b/homeassistant/components/matter/__init__.py @@ -1,4 +1,5 @@ """The Matter integration.""" + from __future__ import annotations import asyncio @@ -45,7 +46,10 @@ def get_matter_device_info( hass: HomeAssistant, device_id: str ) -> MatterDeviceInfo | None: """Return Matter device info or None if device does not exist.""" - if not (node := node_from_ha_device_id(hass, device_id)): + # Test hass.data[DOMAIN] to ensure config entry is set up + if not hass.data.get(DOMAIN, False) or not ( + node := node_from_ha_device_id(hass, device_id) + ): return None return MatterDeviceInfo( From 224f6dbdbbcabbcc082a5e77f26cdb4e2a5515f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Thu, 29 Feb 2024 05:14:08 +0100 Subject: [PATCH 0029/1691] Use proper constant in Airzone Cloud climate tests (#111747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests: airzone_cloud: avoid using airzone const Signed-off-by: Álvaro Fernández Rojas --- tests/components/airzone_cloud/test_climate.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/airzone_cloud/test_climate.py b/tests/components/airzone_cloud/test_climate.py index 7c273dc8bc2..913d41b072a 100644 --- a/tests/components/airzone_cloud/test_climate.py +++ b/tests/components/airzone_cloud/test_climate.py @@ -1,10 +1,10 @@ """The climate tests for the Airzone Cloud platform.""" from unittest.mock import patch +from aioairzone_cloud.const import API_DEFAULT_TEMP_STEP from aioairzone_cloud.exceptions import AirzoneCloudError import pytest -from homeassistant.components.airzone.const import API_TEMPERATURE_STEP from homeassistant.components.climate import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, @@ -53,7 +53,7 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: ] assert state.attributes[ATTR_MAX_TEMP] == 30 assert state.attributes[ATTR_MIN_TEMP] == 15 - assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_TEMPERATURE_STEP + assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_DEFAULT_TEMP_STEP assert state.attributes[ATTR_TEMPERATURE] == 22.0 state = hass.states.get("climate.bron_pro") @@ -71,7 +71,7 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: ] assert state.attributes[ATTR_MAX_TEMP] == 30 assert state.attributes[ATTR_MIN_TEMP] == 15 - assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_TEMPERATURE_STEP + assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_DEFAULT_TEMP_STEP assert state.attributes[ATTR_TEMPERATURE] == 22.0 # Groups @@ -89,7 +89,7 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: ] assert state.attributes[ATTR_MAX_TEMP] == 30 assert state.attributes[ATTR_MIN_TEMP] == 15 - assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_TEMPERATURE_STEP + assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_DEFAULT_TEMP_STEP assert state.attributes[ATTR_TEMPERATURE] == 24.0 # Installations @@ -108,7 +108,7 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: ] assert state.attributes[ATTR_MAX_TEMP] == 30 assert state.attributes[ATTR_MIN_TEMP] == 15 - assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_TEMPERATURE_STEP + assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_DEFAULT_TEMP_STEP assert state.attributes[ATTR_TEMPERATURE] == 23.0 # Zones @@ -126,7 +126,7 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: ] assert state.attributes[ATTR_MAX_TEMP] == 30 assert state.attributes[ATTR_MIN_TEMP] == 15 - assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_TEMPERATURE_STEP + assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_DEFAULT_TEMP_STEP assert state.attributes[ATTR_TEMPERATURE] == 24.0 state = hass.states.get("climate.salon") @@ -143,7 +143,7 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: ] assert state.attributes[ATTR_MAX_TEMP] == 30 assert state.attributes[ATTR_MIN_TEMP] == 15 - assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_TEMPERATURE_STEP + assert state.attributes[ATTR_TARGET_TEMP_STEP] == API_DEFAULT_TEMP_STEP assert state.attributes[ATTR_TEMPERATURE] == 24.0 From 458391ee2b7d1192a2cb06733b0203da84ff4a1e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 29 Feb 2024 05:14:50 +0100 Subject: [PATCH 0030/1691] Axis improve coverage binary tests (#111758) * Parametrize binary sensor tests * Add test coverage to the different *guard apps * Add object analytics tests --- tests/components/axis/conftest.py | 43 ++++--- tests/components/axis/const.py | 46 ++++++- tests/components/axis/test_binary_sensor.py | 127 ++++++++++++++++---- 3 files changed, 171 insertions(+), 45 deletions(-) diff --git a/tests/components/axis/conftest.py b/tests/components/axis/conftest.py index 8de16ee7990..0e83f9007bf 100644 --- a/tests/components/axis/conftest.py +++ b/tests/components/axis/conftest.py @@ -21,6 +21,8 @@ from homeassistant.const import ( from .const import ( API_DISCOVERY_RESPONSE, + APP_AOA_RESPONSE, + APP_VMD4_RESPONSE, APPLICATIONS_LIST_RESPONSE, BASIC_DEVICE_INFO_RESPONSE, BRAND_RESPONSE, @@ -36,7 +38,6 @@ from .const import ( PTZ_RESPONSE, STREAM_PROFILES_RESPONSE, VIEW_AREAS_RESPONSE, - VMD4_RESPONSE, ) from tests.common import MockConfigEntry @@ -105,88 +106,90 @@ def default_request_fixture( """Mock default Vapix requests responses.""" def __mock_default_requests(host): - path = f"http://{host}:80" + respx_mock(base_url=f"http://{host}:80") if host != DEFAULT_HOST: - respx.post(f"{path}/axis-cgi/apidiscovery.cgi").respond( + respx.post("/axis-cgi/apidiscovery.cgi").respond( json=API_DISCOVERY_RESPONSE, ) - respx.post(f"{path}/axis-cgi/basicdeviceinfo.cgi").respond( + respx.post("/axis-cgi/basicdeviceinfo.cgi").respond( json=BASIC_DEVICE_INFO_RESPONSE, ) - respx.post(f"{path}/axis-cgi/io/portmanagement.cgi").respond( + respx.post("/axis-cgi/io/portmanagement.cgi").respond( json=port_management_payload, ) - respx.post(f"{path}/axis-cgi/mqtt/client.cgi").respond( + respx.post("/axis-cgi/mqtt/client.cgi").respond( json=MQTT_CLIENT_RESPONSE, ) - respx.post(f"{path}/axis-cgi/streamprofile.cgi").respond( + respx.post("/axis-cgi/streamprofile.cgi").respond( json=STREAM_PROFILES_RESPONSE, ) - respx.post(f"{path}/axis-cgi/viewarea/info.cgi").respond( - json=VIEW_AREAS_RESPONSE - ) + respx.post("/axis-cgi/viewarea/info.cgi").respond(json=VIEW_AREAS_RESPONSE) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.Brand"}, ).respond( text=BRAND_RESPONSE, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.Image"}, ).respond( text=IMAGE_RESPONSE, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.Input"}, ).respond( text=PORTS_RESPONSE, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.IOPort"}, ).respond( text=param_ports_payload, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.Output"}, ).respond( text=PORTS_RESPONSE, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.Properties"}, ).respond( text=param_properties_payload, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.PTZ"}, ).respond( text=PTZ_RESPONSE, headers={"Content-Type": "text/plain"}, ) respx.post( - f"{path}/axis-cgi/param.cgi", + "/axis-cgi/param.cgi", data={"action": "list", "group": "root.StreamProfile"}, ).respond( text=STREAM_PROFILES_RESPONSE, headers={"Content-Type": "text/plain"}, ) - respx.post(f"{path}/axis-cgi/applications/list.cgi").respond( + respx.post("/axis-cgi/applications/list.cgi").respond( text=APPLICATIONS_LIST_RESPONSE, headers={"Content-Type": "text/xml"}, ) - respx.post(f"{path}/local/vmd/control.cgi").respond(json=VMD4_RESPONSE) + respx.post("/local/fenceguard/control.cgi").respond(json=APP_VMD4_RESPONSE) + respx.post("/local/loiteringguard/control.cgi").respond(json=APP_VMD4_RESPONSE) + respx.post("/local/motionguard/control.cgi").respond(json=APP_VMD4_RESPONSE) + respx.post("/local/vmd/control.cgi").respond(json=APP_VMD4_RESPONSE) + respx.post("/local/objectanalytics/control.cgi").respond(json=APP_AOA_RESPONSE) return __mock_default_requests diff --git a/tests/components/axis/const.py b/tests/components/axis/const.py index df1d0aa4529..8dffac5866a 100644 --- a/tests/components/axis/const.py +++ b/tests/components/axis/const.py @@ -35,7 +35,11 @@ API_DISCOVERY_PORT_MANAGEMENT = { } APPLICATIONS_LIST_RESPONSE = """ + + + + """ BASIC_DEVICE_INFO_RESPONSE = { @@ -95,7 +99,7 @@ PORT_MANAGEMENT_RESPONSE = { }, } -VMD4_RESPONSE = { +APP_VMD4_RESPONSE = { "apiVersion": "1.4", "method": "getConfiguration", "context": CONTEXT, @@ -108,6 +112,46 @@ VMD4_RESPONSE = { }, } +APP_AOA_RESPONSE = { + "apiVersion": "1.0", + "context": "Axis library", + "data": { + "devices": [{"id": 1, "rotation": 180, "type": "camera"}], + "metadataOverlay": [], + "perspectives": [], + "scenarios": [ + { + "devices": [{"id": 1}], + "filters": [ + {"distance": 5, "type": "distanceSwayingObject"}, + {"time": 1, "type": "timeShortLivedLimit"}, + {"height": 3, "type": "sizePercentage", "width": 3}, + ], + "id": 1, + "name": "Scenario 1", + "objectClassifications": [], + "perspectives": [], + "presets": [], + "triggers": [ + { + "type": "includeArea", + "vertices": [ + [-0.97, -0.97], + [-0.97, 0.97], + [0.97, 0.97], + [0.97, -0.97], + ], + } + ], + "type": "motion", + }, + ], + "status": {}, + }, + "method": "getConfiguration", +} + + BRAND_RESPONSE = """root.Brand.Brand=AXIS root.Brand.ProdFullName=AXIS M1065-LW Network Camera root.Brand.ProdNbr=M1065-LW diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index 45fd8fe2f6c..d928d3beaa9 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -1,4 +1,6 @@ """Axis binary sensor platform tests.""" +import pytest + from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, @@ -30,23 +32,10 @@ async def test_no_binary_sensors(hass: HomeAssistant, setup_config_entry) -> Non assert not hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN) -async def test_binary_sensors( +async def test_unsupported_binary_sensors( hass: HomeAssistant, setup_config_entry, mock_rtsp_event ) -> None: - """Test that sensors are loaded properly.""" - mock_rtsp_event( - topic="tns1:Device/tnsaxis:Sensor/PIR", - data_type="state", - data_value="0", - source_name="sensor", - source_idx="0", - ) - mock_rtsp_event( - topic="tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile1", - data_type="active", - data_value="1", - ) - # Unsupported event + """Test that unsupported sensors are not loaded.""" mock_rtsp_event( topic="tns1:PTZController/tnsaxis:PTZPresets/Channel_1", data_type="on_preset", @@ -56,14 +45,104 @@ async def test_binary_sensors( ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2 + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 0 - pir = hass.states.get(f"{BINARY_SENSOR_DOMAIN}.{NAME}_pir_0") - assert pir.state == STATE_OFF - assert pir.name == f"{NAME} PIR 0" - assert pir.attributes["device_class"] == BinarySensorDeviceClass.MOTION - vmd4 = hass.states.get(f"{BINARY_SENSOR_DOMAIN}.{NAME}_vmd4_profile_1") - assert vmd4.state == STATE_ON - assert vmd4.name == f"{NAME} VMD4 Profile 1" - assert vmd4.attributes["device_class"] == BinarySensorDeviceClass.MOTION +@pytest.mark.parametrize( + ("event", "entity"), + [ + ( + { + "topic": "tns1:Device/tnsaxis:Sensor/PIR", + "data_type": "state", + "data_value": "0", + "source_name": "sensor", + "source_idx": "0", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_pir_0", + "state": STATE_OFF, + "name": f"{NAME} PIR 0", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/FenceGuard/Camera1Profile1", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_fence_guard_profile_1", + "state": STATE_ON, + "name": f"{NAME} Fence Guard Profile 1", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/MotionGuard/Camera1Profile1", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_motion_guard_profile_1", + "state": STATE_ON, + "name": f"{NAME} Motion Guard Profile 1", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/LoiteringGuard/Camera1Profile1", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_loitering_guard_profile_1", + "state": STATE_ON, + "name": f"{NAME} Loitering Guard Profile 1", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile1", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_vmd4_profile_1", + "state": STATE_ON, + "name": f"{NAME} VMD4 Profile 1", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario1", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_object_analytics_scenario_1", + "state": STATE_ON, + "name": f"{NAME} Object Analytics Scenario 1", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ], +) +async def test_binary_sensors( + hass: HomeAssistant, setup_config_entry, mock_rtsp_event, event, entity +) -> None: + """Test that sensors are loaded properly.""" + mock_rtsp_event(**event) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1 + + state = hass.states.get(entity["id"]) + assert state.state == entity["state"] + assert state.name == entity["name"] + assert state.attributes["device_class"] == entity["device_class"] From 0985a7ab91cb59ad7d599eb474bbd6e388155b3f Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 28 Feb 2024 22:25:33 -0600 Subject: [PATCH 0031/1691] Bump intents and add sentence tests (#111791) --- .../components/conversation/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../test_default_agent_intents.py | 250 ++++++++++++++++++ 5 files changed, 254 insertions(+), 4 deletions(-) create mode 100644 tests/components/conversation/test_default_agent_intents.py diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index e4317052b04..6f484941a3d 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.2.2"] + "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.2.28"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 69dd386482c..976c6c514f5 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -31,7 +31,7 @@ hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240228.0 -home-assistant-intents==2024.2.2 +home-assistant-intents==2024.2.28 httpx==0.27.0 ifaddr==0.2.0 janus==1.0.0 diff --git a/requirements_all.txt b/requirements_all.txt index ef645cd7c95..a0c3ea77efa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ holidays==0.43 home-assistant-frontend==20240228.0 # homeassistant.components.conversation -home-assistant-intents==2024.2.2 +home-assistant-intents==2024.2.28 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef254d56aac..692e4d397a8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ holidays==0.43 home-assistant-frontend==20240228.0 # homeassistant.components.conversation -home-assistant-intents==2024.2.2 +home-assistant-intents==2024.2.28 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/tests/components/conversation/test_default_agent_intents.py b/tests/components/conversation/test_default_agent_intents.py new file mode 100644 index 00000000000..e796a6893a8 --- /dev/null +++ b/tests/components/conversation/test_default_agent_intents.py @@ -0,0 +1,250 @@ +"""Test intents for the default agent.""" + + +import pytest + +from homeassistant.components import conversation, cover, media_player, vacuum, valve +from homeassistant.components.cover import intent as cover_intent +from homeassistant.components.homeassistant.exposed_entities import async_expose_entity +from homeassistant.components.media_player import intent as media_player_intent +from homeassistant.components.vacuum import intent as vaccum_intent +from homeassistant.components.valve import intent as valve_intent +from homeassistant.const import STATE_CLOSED +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers import intent +from homeassistant.setup import async_setup_component + +from tests.common import async_mock_service + + +@pytest.fixture +async def init_components(hass: HomeAssistant): + """Initialize relevant components with empty configs.""" + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, "conversation", {}) + assert await async_setup_component(hass, "intent", {}) + + +async def test_cover_set_position( + hass: HomeAssistant, + init_components, +) -> None: + """Test the open/close/set position for covers.""" + await cover_intent.async_setup_intents(hass) + + entity_id = f"{cover.DOMAIN}.garage_door" + hass.states.async_set(entity_id, STATE_CLOSED) + async_expose_entity(hass, conversation.DOMAIN, entity_id, True) + + # open + calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_OPEN_COVER) + result = await conversation.async_converse( + hass, "open the garage door", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Opened" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # close + calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_CLOSE_COVER) + result = await conversation.async_converse( + hass, "close garage door", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Closed" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # set position + calls = async_mock_service(hass, cover.DOMAIN, cover.SERVICE_SET_COVER_POSITION) + result = await conversation.async_converse( + hass, "set garage door to 50%", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Position set" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id, cover.ATTR_POSITION: 50} + + +async def test_valve_intents( + hass: HomeAssistant, + init_components, +) -> None: + """Test open/close/set position for valves.""" + await valve_intent.async_setup_intents(hass) + + entity_id = f"{valve.DOMAIN}.main_valve" + hass.states.async_set(entity_id, STATE_CLOSED) + async_expose_entity(hass, conversation.DOMAIN, entity_id, True) + + # open + calls = async_mock_service(hass, valve.DOMAIN, valve.SERVICE_OPEN_VALVE) + result = await conversation.async_converse( + hass, "open the main valve", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Opened" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # close + calls = async_mock_service(hass, valve.DOMAIN, valve.SERVICE_CLOSE_VALVE) + result = await conversation.async_converse( + hass, "close main valve", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Closed" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # set position + calls = async_mock_service(hass, valve.DOMAIN, valve.SERVICE_SET_VALVE_POSITION) + result = await conversation.async_converse( + hass, "set main valve position to 25", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Position set" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id, valve.ATTR_POSITION: 25} + + +async def test_vacuum_intents( + hass: HomeAssistant, + init_components, +) -> None: + """Test start/return to base for vacuums.""" + await vaccum_intent.async_setup_intents(hass) + + entity_id = f"{vacuum.DOMAIN}.rover" + hass.states.async_set(entity_id, STATE_CLOSED) + async_expose_entity(hass, conversation.DOMAIN, entity_id, True) + + # start + calls = async_mock_service(hass, vacuum.DOMAIN, vacuum.SERVICE_START) + result = await conversation.async_converse( + hass, "start rover", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Started" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # return to base + calls = async_mock_service(hass, vacuum.DOMAIN, vacuum.SERVICE_RETURN_TO_BASE) + result = await conversation.async_converse( + hass, "return rover to base", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Returning" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + +async def test_media_player_intents( + hass: HomeAssistant, + init_components, +) -> None: + """Test pause/unpause/next/set volume for media players.""" + await media_player_intent.async_setup_intents(hass) + + entity_id = f"{media_player.DOMAIN}.tv" + hass.states.async_set(entity_id, media_player.STATE_PLAYING) + async_expose_entity(hass, conversation.DOMAIN, entity_id, True) + + # pause + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE + ) + result = await conversation.async_converse(hass, "pause tv", None, Context(), None) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Paused" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # unpause + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY + ) + result = await conversation.async_converse( + hass, "unpause tv", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Unpaused" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # next + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_NEXT_TRACK + ) + result = await conversation.async_converse( + hass, "next item on tv", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Playing next" + assert len(calls) == 1 + call = calls[0] + assert call.data == {"entity_id": entity_id} + + # volume + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_VOLUME_SET + ) + result = await conversation.async_converse( + hass, "set tv volume to 75 percent", None, Context(), None + ) + await hass.async_block_till_done() + + response = result.response + assert response.response_type == intent.IntentResponseType.ACTION_DONE + assert response.speech["plain"]["speech"] == "Volume set" + assert len(calls) == 1 + call = calls[0] + assert call.data == { + "entity_id": entity_id, + media_player.ATTR_MEDIA_VOLUME_LEVEL: 0.75, + } From cb2c845c0462468da6d7be3b25f7cfec2f2553b2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 09:22:35 +0100 Subject: [PATCH 0032/1691] Add icon translations to Hue (#111725) --- homeassistant/components/hue/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/hue/icons.json diff --git a/homeassistant/components/hue/icons.json b/homeassistant/components/hue/icons.json new file mode 100644 index 00000000000..9371ae5843e --- /dev/null +++ b/homeassistant/components/hue/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "hue_activate_scene": "mdi:palette", + "activate_scene": "mdi:palette" + } +} From af4771a198fab3af72246abc644a6c2d3368dc4b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 28 Feb 2024 22:25:55 -1000 Subject: [PATCH 0033/1691] Import enphase_envoy in the executor to avoid blocking the event loop (#111805) --- homeassistant/components/enphase_envoy/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 63e10547ead..8885819ba5e 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@bdraco", "@cgarwood", "@dgomes", "@joostlek", "@catsmanac"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", + "import_executor": true, "iot_class": "local_polling", "loggers": ["pyenphase"], "requirements": ["pyenphase==1.19.1"], From bc6b4d01c80e195aaa37a96deb31b53d7b9e14e1 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 29 Feb 2024 12:25:46 +0100 Subject: [PATCH 0034/1691] Deprecate `hass.components` and log warning if used inside custom component (#111508) * Deprecate @bind_hass and log error if used inside custom component * Log also when accessing `hass.components` * Log warning only when `hass.components` is used * Change version * Process code review --- homeassistant/helpers/frame.py | 7 +++++-- homeassistant/loader.py | 13 +++++++++++++ tests/conftest.py | 27 +++++++++++++++++++++++++++ tests/helpers/test_frame.py | 33 +++++---------------------------- tests/test_loader.py | 34 +++++++++++++++++++++++++++++++++- 5 files changed, 83 insertions(+), 31 deletions(-) diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 920c7150f6d..9c17e17a2c6 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -86,6 +86,7 @@ def report( exclude_integrations: set | None = None, error_if_core: bool = True, level: int = logging.WARNING, + log_custom_component_only: bool = False, ) -> None: """Report incorrect usage. @@ -99,10 +100,12 @@ def report( msg = f"Detected code that {what}. Please report this issue." if error_if_core: raise RuntimeError(msg) from err - _LOGGER.warning(msg, stack_info=True) + if not log_custom_component_only: + _LOGGER.warning(msg, stack_info=True) return - _report_integration(what, integration_frame, level) + if not log_custom_component_only or integration_frame.custom_integration: + _report_integration(what, integration_frame, level) def _report_integration( diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 8aac185cac0..1bbd22d4070 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1247,6 +1247,19 @@ class Components: if component is None: raise ImportError(f"Unable to load {comp_name}") + # Local import to avoid circular dependencies + from .helpers.frame import report # pylint: disable=import-outside-toplevel + + report( + ( + f"accesses hass.components.{comp_name}." + " This is deprecated and will stop working in Home Assistant 2024.6, it" + f" should be updated to import functions used from {comp_name} directly" + ), + error_if_core=False, + log_custom_component_only=True, + ) + wrapped = ModuleWrapper(self._hass, component) setattr(self, comp_name, wrapped) return wrapped diff --git a/tests/conftest.py b/tests/conftest.py index 18645034c29..3be03e1e3ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1579,6 +1579,33 @@ def mock_bleak_scanner_start() -> Generator[MagicMock, None, None]: yield mock_bleak_scanner_start +@pytest.fixture +def mock_integration_frame() -> Generator[Mock, None, None]: + """Mock as if we're calling code from inside an integration.""" + correct_frame = Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ) + with patch( + "homeassistant.helpers.frame.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ], + ): + yield correct_frame + + @pytest.fixture def mock_bluetooth( mock_bleak_scanner_start: MagicMock, mock_bluetooth_adapters: None diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index f1547f36e39..5010c459345 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -1,6 +1,5 @@ """Test the frame helper.""" -from collections.abc import Generator from unittest.mock import ANY, Mock, patch import pytest @@ -9,33 +8,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import frame -@pytest.fixture -def mock_integration_frame() -> Generator[Mock, None, None]: - """Mock as if we're calling code from inside an integration.""" - correct_frame = Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ) - with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], - ): - yield correct_frame - - async def test_extract_frame_integration( caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock ) -> None: @@ -174,3 +146,8 @@ async def test_report_missing_integration_frame( frame.report(what, error_if_core=False) assert what in caplog.text assert caplog.text.count(what) == 1 + + caplog.clear() + + frame.report(what, error_if_core=False, log_custom_component_only=True) + assert caplog.text == "" diff --git a/tests/test_loader.py b/tests/test_loader.py index 27fe3b94cf2..d173e3e8aa6 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,6 +1,6 @@ """Test to verify that we can load components.""" import asyncio -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest @@ -8,6 +8,7 @@ from homeassistant import loader from homeassistant.components import http, hue from homeassistant.components.hue import light as hue_light from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import frame from .common import MockModule, async_get_persistent_notifications, mock_integration @@ -287,6 +288,7 @@ async def test_get_integration_custom_component( ) -> None: """Test resolving integration.""" integration = await loader.async_get_integration(hass, "test_package") + assert integration.get_component().DOMAIN == "test_package" assert integration.name == "Test Package" @@ -1001,3 +1003,33 @@ async def test_config_folder_not_in_path(hass): # Verify that we are able to load the file with absolute path import tests.testing_config.check_config_not_in_path # noqa: F401 + + +async def test_hass_components_use_reported( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock +) -> None: + """Test that use of hass.components is reported.""" + mock_integration_frame.filename = ( + "/home/paulus/homeassistant/custom_components/demo/light.py" + ) + integration_frame = frame.IntegrationFrame( + custom_integration=True, + frame=mock_integration_frame, + integration="test_integration_frame", + module="custom_components.test_integration_frame", + relative_filename="custom_components/test_integration_frame/__init__.py", + ) + + with patch( + "homeassistant.helpers.frame.get_integration_frame", + return_value=integration_frame, + ), patch( + "homeassistant.components.http.start_http_server_and_save_config", + return_value=None, + ): + hass.components.http.start_http_server_and_save_config(hass, [], None) + + assert ( + "Detected that custom integration 'test_integration_frame'" + " accesses hass.components.http. This is deprecated" + ) in caplog.text From 63c3d6e113ba1ee28b2a265387406ba2289a0f90 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 04:47:36 -1000 Subject: [PATCH 0035/1691] Fix race in config entry setup again (#111800) Because the setup again was scheduled as a task, it would not unset self._async_cancel_retry_setup in time and we would try to unsub self._async_cancel_retry_setup after it had already fired. Change it to call a callback that runs right away so it unsets self._async_cancel_retry_setup as soon as its called so there is no race fixes #111796 --- homeassistant/config_entries.py | 37 ++++++++++++++++++--------------- tests/test_config_entries.py | 3 ++- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 864ad90344a..1ca40886da2 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -33,6 +33,7 @@ from .core import ( CoreState, Event, HassJob, + HassJobType, HomeAssistant, callback, ) @@ -363,7 +364,6 @@ class ConfigEntry: self._integration_for_domain: loader.Integration | None = None self._tries = 0 - self._setup_again_job: HassJob | None = None def __repr__(self) -> str: """Representation of ConfigEntry.""" @@ -555,12 +555,18 @@ class ConfigEntry: if hass.state is CoreState.running: self._async_cancel_retry_setup = async_call_later( - hass, wait_time, self._async_get_setup_again_job(hass) + hass, + wait_time, + HassJob( + functools.partial(self._async_setup_again, hass), + job_type=HassJobType.Callback, + ), ) else: - self._async_cancel_retry_setup = hass.bus.async_listen_once( + self._async_cancel_retry_setup = hass.bus.async_listen( EVENT_HOMEASSISTANT_STARTED, functools.partial(self._async_setup_again, hass), + run_immediately=True, ) await self._async_process_on_unload(hass) @@ -585,28 +591,25 @@ class ConfigEntry: if not domain_is_integration: return + self.async_cancel_retry_setup() + if result: self._async_set_state(hass, ConfigEntryState.LOADED, None) else: self._async_set_state(hass, ConfigEntryState.SETUP_ERROR, error_reason) - async def _async_setup_again(self, hass: HomeAssistant, *_: Any) -> None: - """Run setup again.""" + @callback + def _async_setup_again(self, hass: HomeAssistant, *_: Any) -> None: + """Schedule setup again. + + This method is a callback to ensure that _async_cancel_retry_setup + is unset as soon as its callback is called. + """ + self._async_cancel_retry_setup = None # Check again when we fire in case shutdown # has started so we do not block shutdown if not hass.is_stopping: - self._async_cancel_retry_setup = None - await self.async_setup(hass) - - @callback - def _async_get_setup_again_job(self, hass: HomeAssistant) -> HassJob: - """Get a job that will call setup again.""" - if not self._setup_again_job: - self._setup_again_job = HassJob( - functools.partial(self._async_setup_again, hass), - cancel_on_shutdown=True, - ) - return self._setup_again_job + hass.async_create_task(self.async_setup(hass), eager_start=True) @callback def async_shutdown(self) -> None: diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index ab1942b5c43..672dbb9ae64 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1174,7 +1174,8 @@ async def test_setup_raise_not_ready( mock_setup_entry.side_effect = None mock_setup_entry.return_value = True - await hass.async_run_hass_job(p_setup, None) + hass.async_run_hass_job(p_setup, None) + await hass.async_block_till_done() assert entry.state is config_entries.ConfigEntryState.LOADED assert entry.reason is None From b70eea7fb29446505cd76b3a3bf3f7f4a28feede Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 16:09:47 +0100 Subject: [PATCH 0036/1691] Add icon translations to IPP (#111846) * Add icon translations to IPP * Add icon translations to IPP --- homeassistant/components/ipp/icons.json | 15 +++++++++++++++ homeassistant/components/ipp/sensor.py | 4 +--- tests/components/ipp/test_sensor.py | 14 +------------- 3 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/ipp/icons.json diff --git a/homeassistant/components/ipp/icons.json b/homeassistant/components/ipp/icons.json new file mode 100644 index 00000000000..08abebf674d --- /dev/null +++ b/homeassistant/components/ipp/icons.json @@ -0,0 +1,15 @@ +{ + "entity": { + "sensor": { + "printer": { + "default": "mdi:printer" + }, + "uptime": { + "default": "mdi:clock-outline" + }, + "marker": { + "default": "mdi:water" + } + } + } +} diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index d1acbe9bd96..72fe0751f9f 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -70,7 +70,6 @@ PRINTER_SENSORS: tuple[IPPSensorEntityDescription, ...] = ( key="printer", name=None, translation_key="printer", - icon="mdi:printer", device_class=SensorDeviceClass.ENUM, options=["idle", "printing", "stopped"], attributes_fn=lambda printer: { @@ -87,7 +86,6 @@ PRINTER_SENSORS: tuple[IPPSensorEntityDescription, ...] = ( IPPSensorEntityDescription( key="uptime", translation_key="uptime", - icon="mdi:clock-outline", device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, @@ -118,7 +116,7 @@ async def async_setup_entry( IPPSensorEntityDescription( key=f"marker_{index}", name=marker.name, - icon="mdi:water", + translation_key="marker", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, attributes_fn=_get_marker_attributes_fn( diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index 52e8cdedf91..9673d614c10 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -4,12 +4,7 @@ from unittest.mock import AsyncMock import pytest from homeassistant.components.sensor import ATTR_OPTIONS -from homeassistant.const import ( - ATTR_ICON, - ATTR_UNIT_OF_MEASUREMENT, - PERCENTAGE, - EntityCategory, -) +from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -26,7 +21,6 @@ async def test_sensors( """Test the creation and values of the IPP sensors.""" state = hass.states.get("sensor.test_ha_1000_series") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:printer" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None assert state.attributes.get(ATTR_OPTIONS) == ["idle", "printing", "stopped"] @@ -36,37 +30,31 @@ async def test_sensors( state = hass.states.get("sensor.test_ha_1000_series_black_ink") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:water" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is PERCENTAGE assert state.state == "58" state = hass.states.get("sensor.test_ha_1000_series_photo_black_ink") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:water" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is PERCENTAGE assert state.state == "98" state = hass.states.get("sensor.test_ha_1000_series_cyan_ink") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:water" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is PERCENTAGE assert state.state == "91" state = hass.states.get("sensor.test_ha_1000_series_yellow_ink") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:water" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is PERCENTAGE assert state.state == "95" state = hass.states.get("sensor.test_ha_1000_series_magenta_ink") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:water" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is PERCENTAGE assert state.state == "73" state = hass.states.get("sensor.test_ha_1000_series_uptime") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:clock-outline" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None assert state.state == "2019-11-11T09:10:02+00:00" From 9512fb420d0f057dfa114e9047e0e57f897b3ad1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Feb 2024 10:28:32 -0500 Subject: [PATCH 0037/1691] Import cryptography OpenSSL backend (#111840) * Import cryptography OpenSSL backend * No need to impor top-level. Included. * Update homeassistant/bootstrap.py --- homeassistant/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 1a26f8e25c7..4fc9073b146 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Any # Import cryptography early since import openssl is not thread-safe # _frozen_importlib._DeadlockError: deadlock detected by _ModuleLock('cryptography.hazmat.backends.openssl.backend') -import cryptography # noqa: F401 +import cryptography.hazmat.backends.openssl.backend # noqa: F401 import voluptuous as vol import yarl From f59268b2ee35e55dbec7de6ff812c576aebd45f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 05:30:29 -1000 Subject: [PATCH 0038/1691] Include filename in exception when loading a json file fails (#111802) * Include filename in exception when loading a json file fails * fix --- homeassistant/util/json.py | 6 +++--- tests/helpers/test_storage.py | 8 ++++---- tests/util/test_json.py | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 65f93020cc6..3a337cf0e18 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -79,12 +79,12 @@ def load_json( except FileNotFoundError: # This is not a fatal error _LOGGER.debug("JSON file not found: %s", filename) - except ValueError as error: + except JSON_DECODE_EXCEPTIONS as error: _LOGGER.exception("Could not parse JSON content: %s", filename) - raise HomeAssistantError(error) from error + raise HomeAssistantError(f"Error while loading {filename}: {error}") from error except OSError as error: _LOGGER.exception("JSON file reading failed: %s", filename) - raise HomeAssistantError(error) from error + raise HomeAssistantError(f"Error while loading {filename}: {error}") from error return {} if default is _SENTINEL else default diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 363f6051b96..66dd8c10463 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -706,8 +706,8 @@ async def test_loading_corrupt_core_file( assert issue_entry.translation_placeholders["storage_key"] == storage_key assert issue_entry.issue_domain == HOMEASSISTANT_DOMAIN assert ( - issue_entry.translation_placeholders["error"] - == "unexpected character: line 1 column 1 (char 0)" + "unexpected character: line 1 column 1 (char 0)" + in issue_entry.translation_placeholders["error"] ) files = await hass.async_add_executor_job( @@ -767,8 +767,8 @@ async def test_loading_corrupt_file_known_domain( assert issue_entry.translation_placeholders["storage_key"] == storage_key assert issue_entry.issue_domain == "testdomain" assert ( - issue_entry.translation_placeholders["error"] - == "unexpected content after document: line 1 column 17 (char 16)" + "unexpected content after document: line 1 column 17 (char 16)" + in issue_entry.translation_placeholders["error"] ) files = await hass.async_add_executor_job( diff --git a/tests/util/test_json.py b/tests/util/test_json.py index ff0f1ed8392..ba07c7cbb6c 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -1,5 +1,6 @@ """Test Home Assistant json utility functions.""" from pathlib import Path +import re import orjson import pytest @@ -21,11 +22,11 @@ TEST_BAD_SERIALIED = "THIS IS NOT JSON\n" def test_load_bad_data(tmp_path: Path) -> None: - """Test error from trying to load unserialisable data.""" + """Test error from trying to load unserializable data.""" fname = tmp_path / "test5.json" with open(fname, "w") as fh: fh.write(TEST_BAD_SERIALIED) - with pytest.raises(HomeAssistantError) as err: + with pytest.raises(HomeAssistantError, match=re.escape(str(fname))) as err: load_json(fname) assert isinstance(err.value.__cause__, ValueError) @@ -33,7 +34,7 @@ def test_load_bad_data(tmp_path: Path) -> None: def test_load_json_os_error() -> None: """Test trying to load JSON data from a directory.""" fname = "/" - with pytest.raises(HomeAssistantError) as err: + with pytest.raises(HomeAssistantError, match=re.escape(str(fname))) as err: load_json(fname) assert isinstance(err.value.__cause__, OSError) From 58d966a18f55b9b8f77dd6c313b061fa65fc606c Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 29 Feb 2024 16:35:34 +0100 Subject: [PATCH 0039/1691] Import discovergy in the executor to avoid blocking the event loop (#111824) --- homeassistant/components/discovergy/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/discovergy/manifest.json b/homeassistant/components/discovergy/manifest.json index da9fb117353..97bed214609 100644 --- a/homeassistant/components/discovergy/manifest.json +++ b/homeassistant/components/discovergy/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@jpbede"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/discovergy", + "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "requirements": ["pydiscovergy==3.0.0"] From db9cda4fd27742f121603932326b3a9a35620bc3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 05:35:50 -1000 Subject: [PATCH 0040/1691] Import homekit in the executor to avoid blocking the event loop (#111809) `homekit import took 0.635 seconds (loaded_executor=False)` not sure how I missed this one --- homeassistant/components/homekit/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 17d1237e579..a7e2412270b 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -6,6 +6,7 @@ "config_flow": true, "dependencies": ["ffmpeg", "http", "network"], "documentation": "https://www.home-assistant.io/integrations/homekit", + "import_executor": true, "iot_class": "local_push", "loggers": ["pyhap"], "requirements": [ From a664f296e253c7c5d2be9dd9a26e3558645944b2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 05:36:22 -1000 Subject: [PATCH 0041/1691] Import upnp in the executor to avoid blocking the event loop (#111808) `2024-02-29 01:23:54.490 DEBUG (MainThread) [homeassistant.loader] Component upnp import took 0.349 seconds (loaded_executor=False) ` --- homeassistant/components/upnp/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index edfde84a2ac..e4e94d90430 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -5,6 +5,7 @@ "config_flow": true, "dependencies": ["network", "ssdp"], "documentation": "https://www.home-assistant.io/integrations/upnp", + "import_executor": true, "integration_type": "device", "iot_class": "local_polling", "loggers": ["async_upnp_client"], From 94224c4c733bd333294fc383478af1a53eed97f1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 05:36:54 -1000 Subject: [PATCH 0042/1691] Import logbook in the executor to avoid blocking the event loop (#111807) This one is likely because of all the sqlalchemy object construction for the queries `Component logbook import took 0.245 seconds (loaded_executor=False)` --- homeassistant/components/logbook/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/logbook/manifest.json b/homeassistant/components/logbook/manifest.json index b6b68a1489e..923a3fa87b4 100644 --- a/homeassistant/components/logbook/manifest.json +++ b/homeassistant/components/logbook/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@home-assistant/core"], "dependencies": ["frontend", "http", "recorder"], "documentation": "https://www.home-assistant.io/integrations/logbook", + "import_executor": true, "integration_type": "system", "quality_scale": "internal" } From aa183ed09e2ac4ea8043d948ca6f583b2e1165ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 05:37:07 -1000 Subject: [PATCH 0043/1691] Import flux_led in the executor to avoid blocking the event loop (#111806) `Component flux_led import took 0.313 seconds (loaded_executor=False)` --- homeassistant/components/flux_led/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index a55ae028342..327ee0f5b4e 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -51,6 +51,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/flux_led", + "import_executor": true, "iot_class": "local_push", "loggers": ["flux_led"], "requirements": ["flux-led==1.0.4"] From f44b759a9985e10e8e70077814c2787c6e64d8cf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 05:37:36 -1000 Subject: [PATCH 0044/1691] Import rest in the executor to avoid blocking the event loop (#111804) fixes #111803 `2024-02-28 15:43:53.708 DEBUG (MainThread) [homeassistant.loader] Component rest import took 1.646 seconds (loaded_executor=False) ` --- homeassistant/components/rest/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/rest/manifest.json b/homeassistant/components/rest/manifest.json index d638c20d2a4..d8dc1b19f1c 100644 --- a/homeassistant/components/rest/manifest.json +++ b/homeassistant/components/rest/manifest.json @@ -3,6 +3,7 @@ "name": "RESTful", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/rest", + "import_executor": true, "iot_class": "local_polling", "requirements": ["jsonpath==0.82.2", "xmltodict==0.13.0"] } From 73b6e2bac82bcf08bd5073b6ffe5147959e6db37 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Thu, 29 Feb 2024 10:38:21 -0500 Subject: [PATCH 0045/1691] Add support for ZHA entities exposed by Zigpy quirks (#111176) * Add counter entities to the ZHA coordinator device * rework to prepare for non coordinator device counters * Initial scaffolding to support quirks v2 entities * update for zigpy changes * add assertion error message * clean up test * update group entity discovery kwargs * constants and clearer names * apply custom device configuration * quirks switches * quirks select entities * quirks sensor entities * update discovery * move call to super * add complex quirks v2 discovery test * remove duplicate replaces * add quirks v2 button entity support * add quirks v2 binary sensor entity support * fix exception in counter entitiy discovery * oops * update formatting * support custom on and off values * logging * don't filter out entities quirks says should be created * fix type alias warnings * sync up with zigpy changes and additions * add a binary sensor test * button coverage * switch coverage * initial select coverage * number coverage * sensor coverage * update discovery after rebase * coverage * single line * line lengths * fix double underscore * review comments * set category from quirks in base entity * line lengths * move comment * imports * simplify * simplify --- homeassistant/components/zha/binary_sensor.py | 12 +- homeassistant/components/zha/button.py | 52 ++- homeassistant/components/zha/core/const.py | 6 + homeassistant/components/zha/core/device.py | 4 + .../components/zha/core/discovery.py | 211 ++++++++++- homeassistant/components/zha/core/endpoint.py | 15 +- homeassistant/components/zha/entity.py | 29 +- homeassistant/components/zha/number.py | 23 +- homeassistant/components/zha/select.py | 17 +- homeassistant/components/zha/sensor.py | 55 ++- homeassistant/components/zha/switch.py | 39 +- tests/components/zha/test_button.py | 150 +++++++- tests/components/zha/test_discover.py | 349 +++++++++++++++++- tests/components/zha/test_select.py | 85 ++++- tests/components/zha/test_sensor.py | 78 ++++ tests/components/zha/test_switch.py | 269 +++++++++++++- 16 files changed, 1340 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 5ec829fcb05..aed0a16a681 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations import functools from typing import Any +from zigpy.quirks.v2 import BinarySensorMetadata, EntityMetadata import zigpy.types as t from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasZone @@ -26,6 +27,7 @@ from .core.const import ( CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, CLUSTER_HANDLER_ZONE, + QUIRK_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -76,8 +78,16 @@ class BinarySensor(ZhaEntity, BinarySensorEntity): def __init__(self, unique_id, zha_device, cluster_handlers, **kwargs) -> None: """Initialize the ZHA binary sensor.""" - super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) self._cluster_handler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + binary_sensor_metadata: BinarySensorMetadata = entity_metadata.entity_metadata + self._attribute_name = binary_sensor_metadata.attribute_name async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index e16ae082eda..2c0028cd3d1 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -1,11 +1,16 @@ """Support for ZHA button.""" from __future__ import annotations -import abc import functools import logging from typing import TYPE_CHECKING, Any, Self +from zigpy.quirks.v2 import ( + EntityMetadata, + WriteAttributeButtonMetadata, + ZCLCommandButtonMetadata, +) + from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory, Platform @@ -14,7 +19,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery -from .core.const import CLUSTER_HANDLER_IDENTIFY, SIGNAL_ADD_ENTITIES +from .core.const import CLUSTER_HANDLER_IDENTIFY, QUIRK_METADATA, SIGNAL_ADD_ENTITIES from .core.helpers import get_zha_data from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity @@ -58,6 +63,8 @@ class ZHAButton(ZhaEntity, ButtonEntity): """Defines a ZHA button.""" _command_name: str + _args: list[Any] + _kwargs: dict[str, Any] def __init__( self, @@ -67,18 +74,33 @@ class ZHAButton(ZhaEntity, ButtonEntity): **kwargs: Any, ) -> None: """Init this button.""" - super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) self._cluster_handler: ClusterHandler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + button_metadata: ZCLCommandButtonMetadata = entity_metadata.entity_metadata + self._command_name = button_metadata.command_name + self._args = button_metadata.args + self._kwargs = button_metadata.kwargs - @abc.abstractmethod def get_args(self) -> list[Any]: """Return the arguments to use in the command.""" + return list(self._args) if self._args else [] + + def get_kwargs(self) -> dict[str, Any]: + """Return the keyword arguments to use in the command.""" + return self._kwargs async def async_press(self) -> None: """Send out a update command.""" command = getattr(self._cluster_handler, self._command_name) - arguments = self.get_args() - await command(*arguments) + arguments = self.get_args() or [] + kwargs = self.get_kwargs() or {} + await command(*arguments, **kwargs) @MULTI_MATCH(cluster_handler_names=CLUSTER_HANDLER_IDENTIFY) @@ -106,11 +128,8 @@ class ZHAIdentifyButton(ZHAButton): _attr_device_class = ButtonDeviceClass.IDENTIFY _attr_entity_category = EntityCategory.DIAGNOSTIC _command_name = "identify" - - def get_args(self) -> list[Any]: - """Return the arguments to use in the command.""" - - return [DEFAULT_DURATION] + _kwargs = {} + _args = [DEFAULT_DURATION] class ZHAAttributeButton(ZhaEntity, ButtonEntity): @@ -127,8 +146,17 @@ class ZHAAttributeButton(ZhaEntity, ButtonEntity): **kwargs: Any, ) -> None: """Init this button.""" - super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) self._cluster_handler: ClusterHandler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + button_metadata: WriteAttributeButtonMetadata = entity_metadata.entity_metadata + self._attribute_name = button_metadata.attribute_name + self._attribute_value = button_metadata.attribute_value async def async_press(self) -> None: """Write attribute with defined value.""" diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index cb0aa466046..fd54351739e 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -64,6 +64,8 @@ ATTR_WARNING_DEVICE_STROBE_INTENSITY = "intensity" BAUD_RATES = [2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 128000, 256000] BINDINGS = "bindings" +CLUSTER_DETAILS = "cluster_details" + CLUSTER_HANDLER_ACCELEROMETER = "accelerometer" CLUSTER_HANDLER_BINARY_INPUT = "binary_input" CLUSTER_HANDLER_ANALOG_INPUT = "analog_input" @@ -230,6 +232,10 @@ PRESET_SCHEDULE = "Schedule" PRESET_COMPLEX = "Complex" PRESET_TEMP_MANUAL = "Temporary manual" +QUIRK_METADATA = "quirk_metadata" + +ZCL_INIT_ATTRS = "ZCL_INIT_ATTRS" + ZHA_ALARM_OPTIONS = "zha_alarm_options" ZHA_OPTIONS = "zha_options" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 0d473bf0810..f1b7ec60728 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -15,6 +15,7 @@ from zigpy.device import Device as ZigpyDevice import zigpy.exceptions from zigpy.profiles import PROFILES import zigpy.quirks +from zigpy.quirks.v2 import CustomDeviceV2 from zigpy.types.named import EUI64, NWK from zigpy.zcl.clusters import Cluster from zigpy.zcl.clusters.general import Groups, Identify @@ -582,6 +583,9 @@ class ZHADevice(LogMixin): await asyncio.gather( *(endpoint.async_configure() for endpoint in self._endpoints.values()) ) + if isinstance(self._zigpy_device, CustomDeviceV2): + self.debug("applying quirks v2 custom device configuration") + await self._zigpy_device.apply_custom_configuration() async_dispatcher_send( self.hass, const.ZHA_CLUSTER_HANDLER_MSG, diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 5575d633593..221c601827e 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -4,10 +4,22 @@ from __future__ import annotations from collections import Counter from collections.abc import Callable import logging -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast from slugify import slugify +from zigpy.quirks.v2 import ( + BinarySensorMetadata, + CustomDeviceV2, + EntityType, + NumberMetadata, + SwitchMetadata, + WriteAttributeButtonMetadata, + ZCLCommandButtonMetadata, + ZCLEnumMetadata, + ZCLSensorMetadata, +) from zigpy.state import State +from zigpy.zcl import ClusterType from zigpy.zcl.clusters.general import Ota from homeassistant.const import CONF_TYPE, Platform @@ -66,6 +78,59 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +QUIRKS_ENTITY_META_TO_ENTITY_CLASS = { + ( + Platform.BUTTON, + WriteAttributeButtonMetadata, + EntityType.CONFIG, + ): button.ZHAAttributeButton, + (Platform.BUTTON, ZCLCommandButtonMetadata, EntityType.CONFIG): button.ZHAButton, + ( + Platform.BUTTON, + ZCLCommandButtonMetadata, + EntityType.DIAGNOSTIC, + ): button.ZHAButton, + ( + Platform.BINARY_SENSOR, + BinarySensorMetadata, + EntityType.CONFIG, + ): binary_sensor.BinarySensor, + ( + Platform.BINARY_SENSOR, + BinarySensorMetadata, + EntityType.DIAGNOSTIC, + ): binary_sensor.BinarySensor, + ( + Platform.BINARY_SENSOR, + BinarySensorMetadata, + EntityType.STANDARD, + ): binary_sensor.BinarySensor, + (Platform.SENSOR, ZCLEnumMetadata, EntityType.DIAGNOSTIC): sensor.EnumSensor, + (Platform.SENSOR, ZCLEnumMetadata, EntityType.STANDARD): sensor.EnumSensor, + (Platform.SENSOR, ZCLSensorMetadata, EntityType.DIAGNOSTIC): sensor.Sensor, + (Platform.SENSOR, ZCLSensorMetadata, EntityType.STANDARD): sensor.Sensor, + (Platform.SELECT, ZCLEnumMetadata, EntityType.CONFIG): select.ZCLEnumSelectEntity, + ( + Platform.SELECT, + ZCLEnumMetadata, + EntityType.DIAGNOSTIC, + ): select.ZCLEnumSelectEntity, + ( + Platform.NUMBER, + NumberMetadata, + EntityType.CONFIG, + ): number.ZHANumberConfigurationEntity, + (Platform.NUMBER, NumberMetadata, EntityType.DIAGNOSTIC): number.ZhaNumber, + (Platform.NUMBER, NumberMetadata, EntityType.STANDARD): number.ZhaNumber, + ( + Platform.SWITCH, + SwitchMetadata, + EntityType.CONFIG, + ): switch.ZHASwitchConfigurationEntity, + (Platform.SWITCH, SwitchMetadata, EntityType.STANDARD): switch.Switch, +} + + @callback async def async_add_entities( _async_add_entities: AddEntitiesCallback, @@ -73,6 +138,7 @@ async def async_add_entities( tuple[ type[ZhaEntity], tuple[str, ZHADevice, list[ClusterHandler]], + dict[str, Any], ] ], **kwargs, @@ -80,7 +146,11 @@ async def async_add_entities( """Add entities helper.""" if not entities: return - to_add = [ent_cls.create_entity(*args, **kwargs) for ent_cls, args in entities] + + to_add = [ + ent_cls.create_entity(*args, **{**kwargs, **kw_args}) + for ent_cls, args, kw_args in entities + ] entities_to_add = [entity for entity in to_add if entity is not None] _async_add_entities(entities_to_add, update_before_add=False) entities.clear() @@ -118,6 +188,129 @@ class ProbeEndpoint: if device.is_coordinator: self.discover_coordinator_device_entities(device) + return + + self.discover_quirks_v2_entities(device) + zha_regs.ZHA_ENTITIES.clean_up() + + @callback + def discover_quirks_v2_entities(self, device: ZHADevice) -> None: + """Discover entities for a ZHA device exposed by quirks v2.""" + _LOGGER.debug( + "Attempting to discover quirks v2 entities for device: %s-%s", + str(device.ieee), + device.name, + ) + + if not isinstance(device.device, CustomDeviceV2): + _LOGGER.debug( + "Device: %s-%s is not a quirks v2 device - skipping " + "discover_quirks_v2_entities", + str(device.ieee), + device.name, + ) + return + + zigpy_device: CustomDeviceV2 = device.device + + if not zigpy_device.exposes_metadata: + _LOGGER.debug( + "Device: %s-%s does not expose any quirks v2 entities", + str(device.ieee), + device.name, + ) + return + + for ( + cluster_details, + quirk_metadata_list, + ) in zigpy_device.exposes_metadata.items(): + endpoint_id, cluster_id, cluster_type = cluster_details + + if endpoint_id not in device.endpoints: + _LOGGER.warning( + "Device: %s-%s does not have an endpoint with id: %s - unable to " + "create entity with cluster details: %s", + str(device.ieee), + device.name, + endpoint_id, + cluster_details, + ) + continue + + endpoint: Endpoint = device.endpoints[endpoint_id] + cluster = ( + endpoint.zigpy_endpoint.in_clusters.get(cluster_id) + if cluster_type is ClusterType.Server + else endpoint.zigpy_endpoint.out_clusters.get(cluster_id) + ) + + if cluster is None: + _LOGGER.warning( + "Device: %s-%s does not have a cluster with id: %s - " + "unable to create entity with cluster details: %s", + str(device.ieee), + device.name, + cluster_id, + cluster_details, + ) + continue + + cluster_handler_id = f"{endpoint.id}:0x{cluster.cluster_id:04x}" + cluster_handler = ( + endpoint.all_cluster_handlers.get(cluster_handler_id) + if cluster_type is ClusterType.Server + else endpoint.client_cluster_handlers.get(cluster_handler_id) + ) + assert cluster_handler + + for quirk_metadata in quirk_metadata_list: + platform = Platform(quirk_metadata.entity_platform.value) + metadata_type = type(quirk_metadata.entity_metadata) + entity_class = QUIRKS_ENTITY_META_TO_ENTITY_CLASS.get( + (platform, metadata_type, quirk_metadata.entity_type) + ) + + if entity_class is None: + _LOGGER.warning( + "Device: %s-%s has an entity with details: %s that does not" + " have an entity class mapping - unable to create entity", + str(device.ieee), + device.name, + { + zha_const.CLUSTER_DETAILS: cluster_details, + zha_const.QUIRK_METADATA: quirk_metadata, + }, + ) + continue + + # automatically add the attribute to ZCL_INIT_ATTRS for the cluster + # handler if it is not already in the list + if ( + hasattr(quirk_metadata.entity_metadata, "attribute_name") + and quirk_metadata.entity_metadata.attribute_name + not in cluster_handler.ZCL_INIT_ATTRS + ): + init_attrs = cluster_handler.ZCL_INIT_ATTRS.copy() + init_attrs[ + quirk_metadata.entity_metadata.attribute_name + ] = quirk_metadata.attribute_initialized_from_cache + cluster_handler.__dict__[zha_const.ZCL_INIT_ATTRS] = init_attrs + + endpoint.async_new_entity( + platform, + entity_class, + endpoint.unique_id, + [cluster_handler], + quirk_metadata=quirk_metadata, + ) + + _LOGGER.debug( + "'%s' platform -> '%s' using %s", + platform, + entity_class.__name__, + [cluster_handler.name], + ) @callback def discover_coordinator_device_entities(self, device: ZHADevice) -> None: @@ -144,14 +337,20 @@ class ProbeEndpoint: counter_group, counter, ), + {}, ) ) + _LOGGER.debug( + "'%s' platform -> '%s' using %s", + Platform.SENSOR, + sensor.DeviceCounterSensor.__name__, + f"counter groups[{counter_groups}] counter group[{counter_group}] counter[{counter}]", + ) process_counters("counters") process_counters("broadcast_counters") process_counters("device_counters") process_counters("group_counters") - zha_regs.ZHA_ENTITIES.clean_up() @callback def discover_by_device_type(self, endpoint: Endpoint) -> None: @@ -309,7 +508,7 @@ class ProbeEndpoint: for platform, ent_n_handler_list in matches.items(): for entity_and_handler in ent_n_handler_list: _LOGGER.debug( - "'%s' component -> '%s' using %s", + "'%s' platform -> '%s' using %s", platform, entity_and_handler.entity_class.__name__, [ch.name for ch in entity_and_handler.claimed_cluster_handlers], @@ -317,7 +516,8 @@ class ProbeEndpoint: for platform, ent_n_handler_list in matches.items(): for entity_and_handler in ent_n_handler_list: if platform == cmpt_by_dev_type: - # for well known device types, like thermostats we'll take only 1st class + # for well known device types, + # like thermostats we'll take only 1st class endpoint.async_new_entity( platform, entity_and_handler.entity_class, @@ -405,6 +605,7 @@ class GroupProbe: group.group_id, zha_gateway.coordinator_zha_device, ), + {}, ) ) async_dispatcher_send(self._hass, zha_const.SIGNAL_ADD_ENTITIES) diff --git a/homeassistant/components/zha/core/endpoint.py b/homeassistant/components/zha/core/endpoint.py index 490a4e05ea2..37a2c951a7f 100644 --- a/homeassistant/components/zha/core/endpoint.py +++ b/homeassistant/components/zha/core/endpoint.py @@ -7,8 +7,6 @@ import functools import logging from typing import TYPE_CHECKING, Any, Final, TypeVar -from zigpy.typing import EndpointType as ZigpyEndpointType - from homeassistant.const import Platform from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -19,6 +17,8 @@ from .cluster_handlers import ClusterHandler from .helpers import get_zha_data if TYPE_CHECKING: + from zigpy import Endpoint as ZigpyEndpoint + from .cluster_handlers import ClientClusterHandler from .device import ZHADevice @@ -34,11 +34,11 @@ CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) class Endpoint: """Endpoint for a zha device.""" - def __init__(self, zigpy_endpoint: ZigpyEndpointType, device: ZHADevice) -> None: + def __init__(self, zigpy_endpoint: ZigpyEndpoint, device: ZHADevice) -> None: """Initialize instance.""" assert zigpy_endpoint is not None assert device is not None - self._zigpy_endpoint: ZigpyEndpointType = zigpy_endpoint + self._zigpy_endpoint: ZigpyEndpoint = zigpy_endpoint self._device: ZHADevice = device self._all_cluster_handlers: dict[str, ClusterHandler] = {} self._claimed_cluster_handlers: dict[str, ClusterHandler] = {} @@ -66,7 +66,7 @@ class Endpoint: return self._client_cluster_handlers @property - def zigpy_endpoint(self) -> ZigpyEndpointType: + def zigpy_endpoint(self) -> ZigpyEndpoint: """Return endpoint of zigpy device.""" return self._zigpy_endpoint @@ -104,7 +104,7 @@ class Endpoint: ) @classmethod - def new(cls, zigpy_endpoint: ZigpyEndpointType, device: ZHADevice) -> Endpoint: + def new(cls, zigpy_endpoint: ZigpyEndpoint, device: ZHADevice) -> Endpoint: """Create new endpoint and populate cluster handlers.""" endpoint = cls(zigpy_endpoint, device) endpoint.add_all_cluster_handlers() @@ -211,6 +211,7 @@ class Endpoint: entity_class: CALLABLE_T, unique_id: str, cluster_handlers: list[ClusterHandler], + **kwargs: Any, ) -> None: """Create a new entity.""" from .device import DeviceStatus # pylint: disable=import-outside-toplevel @@ -220,7 +221,7 @@ class Endpoint: zha_data = get_zha_data(self.device.hass) zha_data.platforms[platform].append( - (entity_class, (unique_id, self.device, cluster_handlers)) + (entity_class, (unique_id, self.device, cluster_handlers), kwargs or {}) ) @callback diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index ef1b89f1095..3f127c74c0e 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -7,7 +7,9 @@ import functools import logging from typing import TYPE_CHECKING, Any, Self -from homeassistant.const import ATTR_NAME +from zigpy.quirks.v2 import EntityMetadata, EntityType + +from homeassistant.const import ATTR_NAME, EntityCategory from homeassistant.core import CALLBACK_TYPE, callback from homeassistant.helpers import entity from homeassistant.helpers.debounce import Debouncer @@ -175,6 +177,31 @@ class ZhaEntity(BaseZhaEntity, RestoreEntity): """ return cls(unique_id, zha_device, cluster_handlers, **kwargs) + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + if entity_metadata.initially_disabled: + self._attr_entity_registry_enabled_default = False + + if entity_metadata.translation_key: + self._attr_translation_key = entity_metadata.translation_key + + if hasattr(entity_metadata.entity_metadata, "attribute_name"): + if not entity_metadata.translation_key: + self._attr_translation_key = ( + entity_metadata.entity_metadata.attribute_name + ) + self._unique_id_suffix = entity_metadata.entity_metadata.attribute_name + elif hasattr(entity_metadata.entity_metadata, "command_name"): + if not entity_metadata.translation_key: + self._attr_translation_key = ( + entity_metadata.entity_metadata.command_name + ) + self._unique_id_suffix = entity_metadata.entity_metadata.command_name + if entity_metadata.entity_type is EntityType.CONFIG: + self._attr_entity_category = EntityCategory.CONFIG + elif entity_metadata.entity_type is EntityType.DIAGNOSTIC: + self._attr_entity_category = EntityCategory.DIAGNOSTIC + @property def available(self) -> bool: """Return entity availability.""" diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index a4568b5a14c..c452752f14b 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -5,6 +5,7 @@ import functools import logging from typing import TYPE_CHECKING, Any, Self +from zigpy.quirks.v2 import EntityMetadata, NumberMetadata from zigpy.zcl.clusters.hvac import Thermostat from homeassistant.components.number import NumberEntity, NumberMode @@ -24,6 +25,7 @@ from .core.const import ( CLUSTER_HANDLER_LEVEL, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_THERMOSTAT, + QUIRK_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -400,7 +402,7 @@ class ZHANumberConfigurationEntity(ZhaEntity, NumberEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if ( + if QUIRK_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name or cluster_handler.cluster.get(cls._attribute_name) is None @@ -423,8 +425,27 @@ class ZHANumberConfigurationEntity(ZhaEntity, NumberEntity): ) -> None: """Init this number configuration entity.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + number_metadata: NumberMetadata = entity_metadata.entity_metadata + self._attribute_name = number_metadata.attribute_name + + if number_metadata.min is not None: + self._attr_native_min_value = number_metadata.min + if number_metadata.max is not None: + self._attr_native_max_value = number_metadata.max + if number_metadata.step is not None: + self._attr_native_step = number_metadata.step + if number_metadata.unit is not None: + self._attr_native_unit_of_measurement = number_metadata.unit + if number_metadata.multiplier is not None: + self._attr_multiplier = number_metadata.multiplier + @property def native_value(self) -> float: """Return the current value.""" diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index 3736858d599..53acc5cdd02 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -10,6 +10,7 @@ from zhaquirks.quirk_ids import TUYA_PLUG_MANUFACTURER, TUYA_PLUG_ONOFF from zhaquirks.xiaomi.aqara.magnet_ac01 import OppleCluster as MagnetAC01OppleCluster from zhaquirks.xiaomi.aqara.switch_acn047 import OppleCluster as T2RelayOppleCluster from zigpy import types +from zigpy.quirks.v2 import EntityMetadata, ZCLEnumMetadata from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasWd @@ -27,6 +28,7 @@ from .core.const import ( CLUSTER_HANDLER_INOVELLI, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, + QUIRK_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, Strobe, @@ -82,9 +84,9 @@ class ZHAEnumSelectEntity(ZhaEntity, SelectEntity): **kwargs: Any, ) -> None: """Init this select entity.""" + self._cluster_handler: ClusterHandler = cluster_handlers[0] self._attribute_name = self._enum.__name__ self._attr_options = [entry.name.replace("_", " ") for entry in self._enum] - self._cluster_handler: ClusterHandler = cluster_handlers[0] super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) @property @@ -176,7 +178,7 @@ class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if ( + if QUIRK_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name or cluster_handler.cluster.get(cls._attribute_name) is None @@ -198,10 +200,19 @@ class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): **kwargs: Any, ) -> None: """Init this select entity.""" - self._attr_options = [entry.name.replace("_", " ") for entry in self._enum] self._cluster_handler: ClusterHandler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + self._attr_options = [entry.name.replace("_", " ") for entry in self._enum] super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + zcl_enum_metadata: ZCLEnumMetadata = entity_metadata.entity_metadata + self._attribute_name = zcl_enum_metadata.attribute_name + self._enum = zcl_enum_metadata.enum + @property def current_option(self) -> str | None: """Return the selected entity option to represent the entity state.""" diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index c4e620a8b0e..6a68b55a8be 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -6,11 +6,13 @@ from dataclasses import dataclass from datetime import timedelta import enum import functools +import logging import numbers import random from typing import TYPE_CHECKING, Any, Self from zigpy import types +from zigpy.quirks.v2 import EntityMetadata, ZCLEnumMetadata, ZCLSensorMetadata from zigpy.state import Counter, State from zigpy.zcl.clusters.closures import WindowCovering from zigpy.zcl.clusters.general import Basic @@ -68,6 +70,7 @@ from .core.const import ( CLUSTER_HANDLER_TEMPERATURE, CLUSTER_HANDLER_THERMOSTAT, DATA_ZHA, + QUIRK_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -95,6 +98,8 @@ BATTERY_SIZES = { 255: "Unknown", } +_LOGGER = logging.getLogger(__name__) + CLUSTER_HANDLER_ST_HUMIDITY_CLUSTER = ( f"cluster_handler_0x{SMARTTHINGS_HUMIDITY_CLUSTER:04x}" ) @@ -135,17 +140,6 @@ class Sensor(ZhaEntity, SensorEntity): _divisor: int = 1 _multiplier: int | float = 1 - def __init__( - self, - unique_id: str, - zha_device: ZHADevice, - cluster_handlers: list[ClusterHandler], - **kwargs: Any, - ) -> None: - """Init this sensor.""" - super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - self._cluster_handler: ClusterHandler = cluster_handlers[0] - @classmethod def create_entity( cls, @@ -159,14 +153,44 @@ class Sensor(ZhaEntity, SensorEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if ( + if QUIRK_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name ): + _LOGGER.debug( + "%s is not supported - skipping %s entity creation", + cls._attribute_name, + cls.__name__, + ) return None return cls(unique_id, zha_device, cluster_handlers, **kwargs) + def __init__( + self, + unique_id: str, + zha_device: ZHADevice, + cluster_handlers: list[ClusterHandler], + **kwargs: Any, + ) -> None: + """Init this sensor.""" + self._cluster_handler: ClusterHandler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + sensor_metadata: ZCLSensorMetadata = entity_metadata.entity_metadata + self._attribute_name = sensor_metadata.attribute_name + if sensor_metadata.divisor is not None: + self._divisor = sensor_metadata.divisor + if sensor_metadata.multiplier is not None: + self._multiplier = sensor_metadata.multiplier + if sensor_metadata.unit is not None: + self._attr_native_unit_of_measurement = sensor_metadata.unit + async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" await super().async_added_to_hass() @@ -330,6 +354,13 @@ class EnumSensor(Sensor): _attr_device_class: SensorDeviceClass = SensorDeviceClass.ENUM _enum: type[enum.Enum] + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + ZhaEntity._init_from_quirks_metadata(self, entity_metadata) # pylint: disable=protected-access + sensor_metadata: ZCLEnumMetadata = entity_metadata.entity_metadata + self._attribute_name = sensor_metadata.attribute_name + self._enum = sensor_metadata.enum + def formatter(self, value: int) -> str | None: """Use name of enum.""" assert self._enum is not None diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index afc73baca70..960124c4a8a 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -6,6 +6,7 @@ import logging from typing import TYPE_CHECKING, Any, Self from zhaquirks.quirk_ids import TUYA_PLUG_ONOFF +from zigpy.quirks.v2 import EntityMetadata, SwitchMetadata from zigpy.zcl.clusters.closures import ConfigStatus, WindowCovering, WindowCoveringMode from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import Status @@ -23,6 +24,7 @@ from .core.const import ( CLUSTER_HANDLER_COVER, CLUSTER_HANDLER_INOVELLI, CLUSTER_HANDLER_ON_OFF, + QUIRK_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -173,6 +175,8 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): _attribute_name: str _inverter_attribute_name: str | None = None _force_inverted: bool = False + _off_value: int = 0 + _on_value: int = 1 @classmethod def create_entity( @@ -187,7 +191,7 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if ( + if QUIRK_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name or cluster_handler.cluster.get(cls._attribute_name) is None @@ -210,8 +214,22 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): ) -> None: """Init this number configuration entity.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] + if QUIRK_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + """Init this entity from the quirks metadata.""" + super()._init_from_quirks_metadata(entity_metadata) + switch_metadata: SwitchMetadata = entity_metadata.entity_metadata + self._attribute_name = switch_metadata.attribute_name + if switch_metadata.invert_attribute_name: + self._inverter_attribute_name = switch_metadata.invert_attribute_name + if switch_metadata.force_inverted: + self._force_inverted = switch_metadata.force_inverted + self._off_value = switch_metadata.off_value + self._on_value = switch_metadata.on_value + async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" await super().async_added_to_hass() @@ -236,14 +254,25 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): @property def is_on(self) -> bool: """Return if the switch is on based on the statemachine.""" - val = bool(self._cluster_handler.cluster.get(self._attribute_name)) + if self._on_value != 1: + val = self._cluster_handler.cluster.get(self._attribute_name) + val = val == self._on_value + else: + val = bool(self._cluster_handler.cluster.get(self._attribute_name)) return (not val) if self.inverted else val async def async_turn_on_off(self, state: bool) -> None: """Turn the entity on or off.""" - await self._cluster_handler.write_attributes_safe( - {self._attribute_name: not state if self.inverted else state} - ) + if self.inverted: + state = not state + if state: + await self._cluster_handler.write_attributes_safe( + {self._attribute_name: self._on_value} + ) + else: + await self._cluster_handler.write_attributes_safe( + {self._attribute_name: self._off_value} + ) self.async_write_ha_state() async def async_turn_on(self, **kwargs: Any) -> None: diff --git a/tests/components/zha/test_button.py b/tests/components/zha/test_button.py index cc0b5079fd3..9eab72b435b 100644 --- a/tests/components/zha/test_button.py +++ b/tests/components/zha/test_button.py @@ -1,4 +1,5 @@ """Test ZHA button.""" +from typing import Final from unittest.mock import call, patch from freezegun import freeze_time @@ -15,6 +16,7 @@ from zigpy.const import SIG_EP_PROFILE from zigpy.exceptions import ZigbeeException import zigpy.profiles.zha as zha from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.quirks.v2 import add_to_registry_v2 import zigpy.types as t import zigpy.zcl.clusters.general as general from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster @@ -33,7 +35,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from .common import find_entity_id +from .common import find_entity_id, update_attribute_cache from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_TYPE @@ -56,7 +58,9 @@ def button_platform_only(): @pytest.fixture -async def contact_sensor(hass, zigpy_device_mock, zha_device_joined_restored): +async def contact_sensor( + hass: HomeAssistant, zigpy_device_mock, zha_device_joined_restored +): """Contact sensor fixture.""" zigpy_device = zigpy_device_mock( @@ -102,7 +106,9 @@ class FrostLockQuirk(CustomDevice): @pytest.fixture -async def tuya_water_valve(hass, zigpy_device_mock, zha_device_joined_restored): +async def tuya_water_valve( + hass: HomeAssistant, zigpy_device_mock, zha_device_joined_restored +): """Tuya Water Valve fixture.""" zigpy_device = zigpy_device_mock( @@ -224,3 +230,141 @@ async def test_frost_unlock(hass: HomeAssistant, tuya_water_valve) -> None: call({"frost_lock_reset": 0}, manufacturer=None), call({"frost_lock_reset": 0}, manufacturer=None), ] + + +class FakeManufacturerCluster(CustomCluster, ManufacturerSpecificCluster): + """Fake manufacturer cluster.""" + + cluster_id: Final = 0xFFF3 + ep_attribute: Final = "mfg_identify" + + class AttributeDefs(zcl_f.BaseAttributeDefs): + """Attribute definitions.""" + + feed: Final = zcl_f.ZCLAttributeDef( + id=0x0000, type=t.uint8_t, access="rw", is_manufacturer_specific=True + ) + + class ServerCommandDefs(zcl_f.BaseCommandDefs): + """Server command definitions.""" + + self_test: Final = zcl_f.ZCLCommandDef( + id=0x00, schema={"identify_time": t.uint16_t}, direction=False + ) + + +( + add_to_registry_v2("Fake_Model", "Fake_Manufacturer") + .replaces(FakeManufacturerCluster) + .command_button( + FakeManufacturerCluster.ServerCommandDefs.self_test.name, + FakeManufacturerCluster.cluster_id, + command_args=(5,), + ) + .write_attr_button( + FakeManufacturerCluster.AttributeDefs.feed.name, + 2, + FakeManufacturerCluster.cluster_id, + ) +) + + +@pytest.fixture +async def custom_button_device( + hass: HomeAssistant, zigpy_device_mock, zha_device_joined_restored +): + """Button device fixture for quirks button tests.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + general.Basic.cluster_id, + FakeManufacturerCluster.cluster_id, + ], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.REMOTE_CONTROL, + SIG_EP_PROFILE: zha.PROFILE_ID, + } + }, + manufacturer="Fake_Model", + model="Fake_Manufacturer", + ) + + zigpy_device.endpoints[1].mfg_identify.PLUGGED_ATTR_READS = { + FakeManufacturerCluster.AttributeDefs.feed.name: 0, + } + update_attribute_cache(zigpy_device.endpoints[1].mfg_identify) + zha_device = await zha_device_joined_restored(zigpy_device) + return zha_device, zigpy_device.endpoints[1].mfg_identify + + +@freeze_time("2021-11-04 17:37:00", tz_offset=-1) +async def test_quirks_command_button(hass: HomeAssistant, custom_button_device) -> None: + """Test ZHA button platform.""" + + zha_device, cluster = custom_button_device + assert cluster is not None + entity_id = find_entity_id(DOMAIN, zha_device, hass, qualifier="self_test") + assert entity_id is not None + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNKNOWN + + with patch( + "zigpy.zcl.Cluster.request", + return_value=[0x00, zcl_f.Status.SUCCESS], + ): + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + assert len(cluster.request.mock_calls) == 1 + assert cluster.request.call_args[0][0] is False + assert cluster.request.call_args[0][1] == 0 + assert cluster.request.call_args[0][3] == 5 # duration in seconds + + state = hass.states.get(entity_id) + assert state + assert state.state == "2021-11-04T16:37:00+00:00" + + +@freeze_time("2021-11-04 17:37:00", tz_offset=-1) +async def test_quirks_write_attr_button( + hass: HomeAssistant, custom_button_device +) -> None: + """Test ZHA button platform.""" + + zha_device, cluster = custom_button_device + assert cluster is not None + entity_id = find_entity_id(DOMAIN, zha_device, hass, qualifier="feed") + assert entity_id is not None + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNKNOWN + assert cluster.get(cluster.AttributeDefs.feed.name) == 0 + + with patch( + "zigpy.zcl.Cluster.request", + return_value=[0x00, zcl_f.Status.SUCCESS], + ): + await hass.services.async_call( + DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + assert cluster.write_attributes.mock_calls == [ + call({cluster.AttributeDefs.feed.name: 2}, manufacturer=None) + ] + + state = hass.states.get(entity_id) + assert state + assert state.state == "2021-11-04T16:37:00+00:00" + assert cluster.get(cluster.AttributeDefs.feed.name) == 2 diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index 1491b46005b..c8eba90a372 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -6,10 +6,23 @@ from unittest import mock from unittest.mock import AsyncMock, Mock, patch import pytest +from zhaquirks.ikea import PowerConfig1CRCluster, ScenesCluster +from zhaquirks.xiaomi import ( + BasicCluster, + LocalIlluminanceMeasurementCluster, + XiaomiPowerConfigurationPercent, +) +from zhaquirks.xiaomi.aqara.driver_curtain_e1 import ( + WindowCoveringE1, + XiaomiAqaraDriverE1, +) from zigpy.const import SIG_ENDPOINTS, SIG_MANUFACTURER, SIG_MODEL, SIG_NODE_DESC import zigpy.profiles.zha import zigpy.quirks +from zigpy.quirks.v2 import EntityType, add_to_registry_v2 +from zigpy.quirks.v2.homeassistant import UnitOfTime import zigpy.types +from zigpy.zcl import ClusterType import zigpy.zcl.clusters.closures import zigpy.zcl.clusters.general import zigpy.zcl.clusters.security @@ -22,11 +35,12 @@ import homeassistant.components.zha.core.discovery as disc from homeassistant.components.zha.core.endpoint import Endpoint from homeassistant.components.zha.core.helpers import get_zha_gateway import homeassistant.components.zha.core.registries as zha_regs -from homeassistant.const import Platform +from homeassistant.const import STATE_OFF, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import EntityPlatform +from .common import find_entity_id, update_attribute_cache from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE from .zha_devices_list import ( DEV_SIG_ATTRIBUTES, @@ -147,7 +161,9 @@ async def test_devices( for (platform, unique_id), ent_info in device[DEV_SIG_ENT_MAP].items(): no_tail_id = NO_TAIL_ID.sub("", ent_info[DEV_SIG_ENT_MAP_ID]) ha_entity_id = entity_registry.async_get_entity_id(platform, "zha", unique_id) - assert ha_entity_id is not None + message1 = f"No entity found for platform[{platform}] unique_id[{unique_id}]" + message2 = f"no_tail_id[{no_tail_id}] with entity_id[{ha_entity_id}]" + assert ha_entity_id is not None, f"{message1} {message2}" assert ha_entity_id.startswith(no_tail_id) entity = created_entities[ha_entity_id] @@ -461,3 +477,332 @@ async def test_group_probe_cleanup_called( await config_entry.async_unload(hass_disable_services) await hass_disable_services.async_block_till_done() disc.GROUP_PROBE.cleanup.assert_called() + + +async def test_quirks_v2_entity_discovery( + hass, + zigpy_device_mock, + zha_device_joined, +) -> None: + """Test quirks v2 discovery.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + zigpy.zcl.clusters.general.PowerConfiguration.cluster_id, + zigpy.zcl.clusters.general.Groups.cluster_id, + zigpy.zcl.clusters.general.OnOff.cluster_id, + ], + SIG_EP_OUTPUT: [ + zigpy.zcl.clusters.general.Scenes.cluster_id, + ], + SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.NON_COLOR_CONTROLLER, + } + }, + ieee="01:2d:6f:00:0a:90:69:e8", + manufacturer="Ikea of Sweden", + model="TRADFRI remote control", + ) + + ( + add_to_registry_v2( + "Ikea of Sweden", "TRADFRI remote control", zigpy.quirks._DEVICE_REGISTRY + ) + .replaces(PowerConfig1CRCluster) + .replaces(ScenesCluster, cluster_type=ClusterType.Client) + .number( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + min_value=1, + max_value=100, + step=1, + unit=UnitOfTime.SECONDS, + multiplier=1, + ) + ) + + zigpy_device = zigpy.quirks._DEVICE_REGISTRY.get_device(zigpy_device) + zigpy_device.endpoints[1].power.PLUGGED_ATTR_READS = { + "battery_voltage": 3, + "battery_percentage_remaining": 100, + } + update_attribute_cache(zigpy_device.endpoints[1].power) + zigpy_device.endpoints[1].on_off.PLUGGED_ATTR_READS = { + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name: 3, + } + update_attribute_cache(zigpy_device.endpoints[1].on_off) + + zha_device = await zha_device_joined(zigpy_device) + + entity_id = find_entity_id( + Platform.NUMBER, + zha_device, + hass, + ) + assert entity_id is not None + + state = hass.states.get(entity_id) + assert state is not None + + +async def test_quirks_v2_entity_discovery_e1_curtain( + hass, + zigpy_device_mock, + zha_device_joined, +) -> None: + """Test quirks v2 discovery for e1 curtain motor.""" + aqara_E1_device = zigpy_device_mock( + { + 1: { + SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.WINDOW_COVERING_DEVICE, + SIG_EP_INPUT: [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.PowerConfiguration.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.general.Time.cluster_id, + WindowCoveringE1.cluster_id, + XiaomiAqaraDriverE1.cluster_id, + ], + SIG_EP_OUTPUT: [ + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.general.Time.cluster_id, + zigpy.zcl.clusters.general.Ota.cluster_id, + XiaomiAqaraDriverE1.cluster_id, + ], + } + }, + ieee="01:2d:6f:00:0a:90:69:e8", + manufacturer="LUMI", + model="lumi.curtain.agl006", + ) + + class AqaraE1HookState(zigpy.types.enum8): + """Aqara hook state.""" + + Unlocked = 0x00 + Locked = 0x01 + Locking = 0x02 + Unlocking = 0x03 + + class FakeXiaomiAqaraDriverE1(XiaomiAqaraDriverE1): + """Fake XiaomiAqaraDriverE1 cluster.""" + + attributes = XiaomiAqaraDriverE1.attributes.copy() + attributes.update( + { + 0x9999: ("error_detected", zigpy.types.Bool, True), + } + ) + + ( + add_to_registry_v2("LUMI", "lumi.curtain.agl006") + .adds(LocalIlluminanceMeasurementCluster) + .replaces(BasicCluster) + .replaces(XiaomiPowerConfigurationPercent) + .replaces(WindowCoveringE1) + .replaces(FakeXiaomiAqaraDriverE1) + .removes(FakeXiaomiAqaraDriverE1, cluster_type=ClusterType.Client) + .enum( + BasicCluster.AttributeDefs.power_source.name, + BasicCluster.PowerSource, + BasicCluster.cluster_id, + entity_platform=Platform.SENSOR, + entity_type=EntityType.DIAGNOSTIC, + ) + .enum( + "hooks_state", + AqaraE1HookState, + FakeXiaomiAqaraDriverE1.cluster_id, + entity_platform=Platform.SENSOR, + entity_type=EntityType.DIAGNOSTIC, + ) + .binary_sensor("error_detected", FakeXiaomiAqaraDriverE1.cluster_id) + ) + + aqara_E1_device = zigpy.quirks._DEVICE_REGISTRY.get_device(aqara_E1_device) + + aqara_E1_device.endpoints[1].opple_cluster.PLUGGED_ATTR_READS = { + "hand_open": 0, + "positions_stored": 0, + "hooks_lock": 0, + "hooks_state": AqaraE1HookState.Unlocked, + "light_level": 0, + "error_detected": 0, + } + update_attribute_cache(aqara_E1_device.endpoints[1].opple_cluster) + + aqara_E1_device.endpoints[1].basic.PLUGGED_ATTR_READS = { + BasicCluster.AttributeDefs.power_source.name: BasicCluster.PowerSource.Mains_single_phase, + } + update_attribute_cache(aqara_E1_device.endpoints[1].basic) + + WCAttrs = zigpy.zcl.clusters.closures.WindowCovering.AttributeDefs + WCT = zigpy.zcl.clusters.closures.WindowCovering.WindowCoveringType + WCCS = zigpy.zcl.clusters.closures.WindowCovering.ConfigStatus + aqara_E1_device.endpoints[1].window_covering.PLUGGED_ATTR_READS = { + WCAttrs.current_position_lift_percentage.name: 0, + WCAttrs.window_covering_type.name: WCT.Drapery, + WCAttrs.config_status.name: WCCS(~WCCS.Open_up_commands_reversed), + } + update_attribute_cache(aqara_E1_device.endpoints[1].window_covering) + + zha_device = await zha_device_joined(aqara_E1_device) + + power_source_entity_id = find_entity_id( + Platform.SENSOR, + zha_device, + hass, + qualifier=BasicCluster.AttributeDefs.power_source.name, + ) + assert power_source_entity_id is not None + state = hass.states.get(power_source_entity_id) + assert state is not None + assert state.state == BasicCluster.PowerSource.Mains_single_phase.name + + hook_state_entity_id = find_entity_id( + Platform.SENSOR, + zha_device, + hass, + qualifier="hooks_state", + ) + assert hook_state_entity_id is not None + state = hass.states.get(hook_state_entity_id) + assert state is not None + assert state.state == AqaraE1HookState.Unlocked.name + + error_detected_entity_id = find_entity_id( + Platform.BINARY_SENSOR, + zha_device, + hass, + ) + assert error_detected_entity_id is not None + state = hass.states.get(error_detected_entity_id) + assert state is not None + assert state.state == STATE_OFF + + +def _get_test_device(zigpy_device_mock, manufacturer: str, model: str): + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + zigpy.zcl.clusters.general.PowerConfiguration.cluster_id, + zigpy.zcl.clusters.general.Groups.cluster_id, + zigpy.zcl.clusters.general.OnOff.cluster_id, + ], + SIG_EP_OUTPUT: [ + zigpy.zcl.clusters.general.Scenes.cluster_id, + ], + SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.NON_COLOR_CONTROLLER, + } + }, + ieee="01:2d:6f:00:0a:90:69:e8", + manufacturer=manufacturer, + model=model, + ) + + ( + add_to_registry_v2(manufacturer, model, zigpy.quirks._DEVICE_REGISTRY) + .replaces(PowerConfig1CRCluster) + .replaces(ScenesCluster, cluster_type=ClusterType.Client) + .number( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + endpoint_id=3, + min_value=1, + max_value=100, + step=1, + unit=UnitOfTime.SECONDS, + multiplier=1, + ) + .number( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.Time.cluster_id, + min_value=1, + max_value=100, + step=1, + unit=UnitOfTime.SECONDS, + multiplier=1, + ) + .sensor( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + entity_type=EntityType.CONFIG, + ) + ) + + zigpy_device = zigpy.quirks._DEVICE_REGISTRY.get_device(zigpy_device) + zigpy_device.endpoints[1].power.PLUGGED_ATTR_READS = { + "battery_voltage": 3, + "battery_percentage_remaining": 100, + } + update_attribute_cache(zigpy_device.endpoints[1].power) + zigpy_device.endpoints[1].on_off.PLUGGED_ATTR_READS = { + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name: 3, + } + update_attribute_cache(zigpy_device.endpoints[1].on_off) + return zigpy_device + + +async def test_quirks_v2_entity_no_metadata( + hass: HomeAssistant, + zigpy_device_mock, + zha_device_joined, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test quirks v2 discovery skipped - no metadata.""" + + zigpy_device = _get_test_device( + zigpy_device_mock, "Ikea of Sweden2", "TRADFRI remote control2" + ) + setattr(zigpy_device, "_exposes_metadata", {}) + zha_device = await zha_device_joined(zigpy_device) + assert ( + f"Device: {str(zigpy_device.ieee)}-{zha_device.name} does not expose any quirks v2 entities" + in caplog.text + ) + + +async def test_quirks_v2_entity_discovery_errors( + hass: HomeAssistant, + zigpy_device_mock, + zha_device_joined, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test quirks v2 discovery skipped - errors.""" + + zigpy_device = _get_test_device( + zigpy_device_mock, "Ikea of Sweden3", "TRADFRI remote control3" + ) + zha_device = await zha_device_joined(zigpy_device) + + m1 = f"Device: {str(zigpy_device.ieee)}-{zha_device.name} does not have an" + m2 = " endpoint with id: 3 - unable to create entity with cluster" + m3 = " details: (3, 6, )" + assert f"{m1}{m2}{m3}" in caplog.text + + time_cluster_id = zigpy.zcl.clusters.general.Time.cluster_id + + m1 = f"Device: {str(zigpy_device.ieee)}-{zha_device.name} does not have a" + m2 = f" cluster with id: {time_cluster_id} - unable to create entity with " + m3 = f"cluster details: (1, {time_cluster_id}, )" + assert f"{m1}{m2}{m3}" in caplog.text + + # fmt: off + entity_details = ( + "{'cluster_details': (1, 6, ), " + "'quirk_metadata': EntityMetadata(entity_metadata=ZCLSensorMetadata(" + "attribute_name='off_wait_time', divisor=1, multiplier=1, unit=None, " + "device_class=None, state_class=None), entity_platform=, entity_type=, " + "cluster_id=6, endpoint_id=1, cluster_type=, " + "initially_disabled=False, attribute_initialized_from_cache=True, " + "translation_key=None)}" + ) + # fmt: on + + m1 = f"Device: {str(zigpy_device.ieee)}-{zha_device.name} has an entity with " + m2 = f"details: {entity_details} that does not have an entity class mapping - " + m3 = "unable to create entity" + assert f"{m1}{m2}{m3}" in caplog.text diff --git a/tests/components/zha/test_select.py b/tests/components/zha/test_select.py index d2d1b79c92f..549a123aefb 100644 --- a/tests/components/zha/test_select.py +++ b/tests/components/zha/test_select.py @@ -12,6 +12,7 @@ from zhaquirks import ( from zigpy.const import SIG_EP_PROFILE import zigpy.profiles.zha as zha from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.quirks.v2 import CustomDeviceV2, add_to_registry_v2 import zigpy.types as t import zigpy.zcl.clusters.general as general from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster @@ -349,13 +350,19 @@ class MotionSensitivityQuirk(CustomDevice): ep_attribute = "opple_cluster" attributes = { 0x010C: ("motion_sensitivity", t.uint8_t, True), + 0x020C: ("motion_sensitivity_disabled", t.uint8_t, True), } def __init__(self, *args, **kwargs): """Initialize.""" super().__init__(*args, **kwargs) # populate cache to create config entity - self._attr_cache.update({0x010C: AqaraMotionSensitivities.Medium}) + self._attr_cache.update( + { + 0x010C: AqaraMotionSensitivities.Medium, + 0x020C: AqaraMotionSensitivities.Medium, + } + ) replacement = { ENDPOINTS: { @@ -413,3 +420,79 @@ async def test_on_off_select_attribute_report( hass, cluster, {"motion_sensitivity": AqaraMotionSensitivities.Low} ) assert hass.states.get(entity_id).state == AqaraMotionSensitivities.Low.name + + +( + add_to_registry_v2("Fake_Manufacturer", "Fake_Model") + .replaces(MotionSensitivityQuirk.OppleCluster) + .enum( + "motion_sensitivity", + AqaraMotionSensitivities, + MotionSensitivityQuirk.OppleCluster.cluster_id, + ) + .enum( + "motion_sensitivity_disabled", + AqaraMotionSensitivities, + MotionSensitivityQuirk.OppleCluster.cluster_id, + translation_key="motion_sensitivity_translation_key", + initially_disabled=True, + ) +) + + +@pytest.fixture +async def zigpy_device_aqara_sensor_v2( + hass: HomeAssistant, zigpy_device_mock, zha_device_joined_restored +): + """Device tracker zigpy Aqara motion sensor device.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + general.Basic.cluster_id, + MotionSensitivityQuirk.OppleCluster.cluster_id, + ], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.OCCUPANCY_SENSOR, + } + }, + manufacturer="Fake_Manufacturer", + model="Fake_Model", + ) + + zha_device = await zha_device_joined_restored(zigpy_device) + return zha_device, zigpy_device.endpoints[1].opple_cluster + + +async def test_on_off_select_attribute_report_v2( + hass: HomeAssistant, zigpy_device_aqara_sensor_v2 +) -> None: + """Test ZHA attribute report parsing for select platform.""" + + zha_device, cluster = zigpy_device_aqara_sensor_v2 + assert isinstance(zha_device.device, CustomDeviceV2) + entity_id = find_entity_id( + Platform.SELECT, zha_device, hass, qualifier="motion_sensitivity" + ) + assert entity_id is not None + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + + # test that the state is in default medium state + assert hass.states.get(entity_id).state == AqaraMotionSensitivities.Medium.name + + # send attribute report from device + await send_attributes_report( + hass, cluster, {"motion_sensitivity": AqaraMotionSensitivities.Low} + ) + assert hass.states.get(entity_id).state == AqaraMotionSensitivities.Low.name + + entity_registry = er.async_get(hass) + # none in id because the translation key does not exist + entity_entry = entity_registry.async_get("select.fake_manufacturer_fake_model_none") + assert entity_entry + assert entity_entry.entity_category == EntityCategory.CONFIG + assert entity_entry.disabled is True + assert entity_entry.translation_key == "motion_sensitivity_translation_key" diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index 7b96d43aed3..a7047b8dcd4 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -5,8 +5,13 @@ from unittest.mock import MagicMock, patch import pytest import zigpy.profiles.zha +from zigpy.quirks import CustomCluster +from zigpy.quirks.v2 import CustomDeviceV2, add_to_registry_v2 +from zigpy.quirks.v2.homeassistant import UnitOfMass +import zigpy.types as t from zigpy.zcl.clusters import general, homeautomation, hvac, measurement, smartenergy from zigpy.zcl.clusters.hvac import Thermostat +from zigpy.zcl.clusters.manufacturer_specific import ManufacturerSpecificCluster from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.zha.core import ZHADevice @@ -1187,6 +1192,79 @@ async def test_elec_measurement_skip_unsupported_attribute( assert read_attrs == supported_attributes +class OppleCluster(CustomCluster, ManufacturerSpecificCluster): + """Aqara manufacturer specific cluster.""" + + cluster_id = 0xFCC0 + ep_attribute = "opple_cluster" + attributes = { + 0x010C: ("last_feeding_size", t.uint16_t, True), + } + + def __init__(self, *args, **kwargs) -> None: + """Initialize.""" + super().__init__(*args, **kwargs) + # populate cache to create config entity + self._attr_cache.update({0x010C: 10}) + + +( + add_to_registry_v2("Fake_Manufacturer_sensor", "Fake_Model_sensor") + .replaces(OppleCluster) + .sensor( + "last_feeding_size", + OppleCluster.cluster_id, + divisor=1, + multiplier=1, + unit=UnitOfMass.GRAMS, + ) +) + + +@pytest.fixture +async def zigpy_device_aqara_sensor_v2( + hass: HomeAssistant, zigpy_device_mock, zha_device_joined_restored +): + """Device tracker zigpy Aqara motion sensor device.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [ + general.Basic.cluster_id, + OppleCluster.cluster_id, + ], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.OCCUPANCY_SENSOR, + } + }, + manufacturer="Fake_Manufacturer_sensor", + model="Fake_Model_sensor", + ) + + zha_device = await zha_device_joined_restored(zigpy_device) + return zha_device, zigpy_device.endpoints[1].opple_cluster + + +async def test_last_feeding_size_sensor_v2( + hass: HomeAssistant, zigpy_device_aqara_sensor_v2 +) -> None: + """Test quirks defined sensor.""" + + zha_device, cluster = zigpy_device_aqara_sensor_v2 + assert isinstance(zha_device.device, CustomDeviceV2) + entity_id = find_entity_id( + Platform.SENSOR, zha_device, hass, qualifier="last_feeding_size" + ) + assert entity_id is not None + + await send_attributes_report(hass, cluster, {0x010C: 1}) + assert_state(hass, entity_id, "1.0", UnitOfMass.GRAMS) + + await send_attributes_report(hass, cluster, {0x010C: 5}) + assert_state(hass, entity_id, "5.0", UnitOfMass.GRAMS) + + @pytest.fixture async def coordinator(hass: HomeAssistant, zigpy_device_mock, zha_device_joined): """Test ZHA fan platform.""" diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 6bfd7e051f1..cb1d87210a7 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -11,7 +11,8 @@ from zhaquirks.const import ( ) from zigpy.exceptions import ZigbeeException import zigpy.profiles.zha as zha -from zigpy.quirks import CustomCluster, CustomDevice +from zigpy.quirks import _DEVICE_REGISTRY, CustomCluster, CustomDevice +from zigpy.quirks.v2 import CustomDeviceV2, add_to_registry_v2 import zigpy.types as t import zigpy.zcl.clusters.closures as closures import zigpy.zcl.clusters.general as general @@ -564,6 +565,272 @@ async def test_switch_configurable( await async_test_rejoin(hass, zigpy_device_tuya, [cluster], (0,)) +async def test_switch_configurable_custom_on_off_values( + hass: HomeAssistant, zha_device_joined_restored, zigpy_device_mock +) -> None: + """Test ZHA configurable switch platform.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + } + }, + manufacturer="manufacturer", + model="model", + ) + + ( + add_to_registry_v2(zigpy_device.manufacturer, zigpy_device.model) + .adds(WindowDetectionFunctionQuirk.TuyaManufCluster) + .switch( + "window_detection_function", + WindowDetectionFunctionQuirk.TuyaManufCluster.cluster_id, + on_value=3, + off_value=5, + ) + ) + + zigpy_device = _DEVICE_REGISTRY.get_device(zigpy_device) + + assert isinstance(zigpy_device, CustomDeviceV2) + cluster = zigpy_device.endpoints[1].tuya_manufacturer + cluster.PLUGGED_ATTR_READS = {"window_detection_function": 5} + update_attribute_cache(cluster) + + zha_device = await zha_device_joined_restored(zigpy_device) + + entity_id = find_entity_id(Platform.SWITCH, zha_device, hass) + assert entity_id is not None + + assert hass.states.get(entity_id).state == STATE_OFF + await async_enable_traffic(hass, [zha_device], enabled=False) + # test that the switch was created and that its state is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + + # test that the state has changed from unavailable to off + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on at switch + await send_attributes_report(hass, cluster, {"window_detection_function": 3}) + assert hass.states.get(entity_id).state == STATE_ON + + # turn off at switch + await send_attributes_report(hass, cluster, {"window_detection_function": 5}) + assert hass.states.get(entity_id).state == STATE_OFF + + # turn on from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]], + ): + # turn on via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True + ) + assert cluster.write_attributes.mock_calls == [ + call({"window_detection_function": 3}, manufacturer=None) + ] + cluster.write_attributes.reset_mock() + + # turn off from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]], + ): + # turn off via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True + ) + assert cluster.write_attributes.mock_calls == [ + call({"window_detection_function": 5}, manufacturer=None) + ] + + +async def test_switch_configurable_custom_on_off_values_force_inverted( + hass: HomeAssistant, zha_device_joined_restored, zigpy_device_mock +) -> None: + """Test ZHA configurable switch platform.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + } + }, + manufacturer="manufacturer2", + model="model2", + ) + + ( + add_to_registry_v2(zigpy_device.manufacturer, zigpy_device.model) + .adds(WindowDetectionFunctionQuirk.TuyaManufCluster) + .switch( + "window_detection_function", + WindowDetectionFunctionQuirk.TuyaManufCluster.cluster_id, + on_value=3, + off_value=5, + force_inverted=True, + ) + ) + + zigpy_device = _DEVICE_REGISTRY.get_device(zigpy_device) + + assert isinstance(zigpy_device, CustomDeviceV2) + cluster = zigpy_device.endpoints[1].tuya_manufacturer + cluster.PLUGGED_ATTR_READS = {"window_detection_function": 5} + update_attribute_cache(cluster) + + zha_device = await zha_device_joined_restored(zigpy_device) + + entity_id = find_entity_id(Platform.SWITCH, zha_device, hass) + assert entity_id is not None + + assert hass.states.get(entity_id).state == STATE_ON + await async_enable_traffic(hass, [zha_device], enabled=False) + # test that the switch was created and that its state is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + + # test that the state has changed from unavailable to off + assert hass.states.get(entity_id).state == STATE_ON + + # turn on at switch + await send_attributes_report(hass, cluster, {"window_detection_function": 3}) + assert hass.states.get(entity_id).state == STATE_OFF + + # turn off at switch + await send_attributes_report(hass, cluster, {"window_detection_function": 5}) + assert hass.states.get(entity_id).state == STATE_ON + + # turn on from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]], + ): + # turn on via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True + ) + assert cluster.write_attributes.mock_calls == [ + call({"window_detection_function": 5}, manufacturer=None) + ] + cluster.write_attributes.reset_mock() + + # turn off from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]], + ): + # turn off via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True + ) + assert cluster.write_attributes.mock_calls == [ + call({"window_detection_function": 3}, manufacturer=None) + ] + + +async def test_switch_configurable_custom_on_off_values_inverter_attribute( + hass: HomeAssistant, zha_device_joined_restored, zigpy_device_mock +) -> None: + """Test ZHA configurable switch platform.""" + + zigpy_device = zigpy_device_mock( + { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [], + SIG_EP_TYPE: zha.DeviceType.ON_OFF_SWITCH, + } + }, + manufacturer="manufacturer3", + model="model3", + ) + + ( + add_to_registry_v2(zigpy_device.manufacturer, zigpy_device.model) + .adds(WindowDetectionFunctionQuirk.TuyaManufCluster) + .switch( + "window_detection_function", + WindowDetectionFunctionQuirk.TuyaManufCluster.cluster_id, + on_value=3, + off_value=5, + invert_attribute_name="window_detection_function_inverter", + ) + ) + + zigpy_device = _DEVICE_REGISTRY.get_device(zigpy_device) + + assert isinstance(zigpy_device, CustomDeviceV2) + cluster = zigpy_device.endpoints[1].tuya_manufacturer + cluster.PLUGGED_ATTR_READS = { + "window_detection_function": 5, + "window_detection_function_inverter": t.Bool(True), + } + update_attribute_cache(cluster) + + zha_device = await zha_device_joined_restored(zigpy_device) + + entity_id = find_entity_id(Platform.SWITCH, zha_device, hass) + assert entity_id is not None + + assert hass.states.get(entity_id).state == STATE_ON + await async_enable_traffic(hass, [zha_device], enabled=False) + # test that the switch was created and that its state is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + + # test that the state has changed from unavailable to off + assert hass.states.get(entity_id).state == STATE_ON + + # turn on at switch + await send_attributes_report(hass, cluster, {"window_detection_function": 3}) + assert hass.states.get(entity_id).state == STATE_OFF + + # turn off at switch + await send_attributes_report(hass, cluster, {"window_detection_function": 5}) + assert hass.states.get(entity_id).state == STATE_ON + + # turn on from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]], + ): + # turn on via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_on", {"entity_id": entity_id}, blocking=True + ) + assert cluster.write_attributes.mock_calls == [ + call({"window_detection_function": 5}, manufacturer=None) + ] + cluster.write_attributes.reset_mock() + + # turn off from HA + with patch( + "zigpy.zcl.Cluster.write_attributes", + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]], + ): + # turn off via UI + await hass.services.async_call( + SWITCH_DOMAIN, "turn_off", {"entity_id": entity_id}, blocking=True + ) + assert cluster.write_attributes.mock_calls == [ + call({"window_detection_function": 3}, manufacturer=None) + ] + + WCAttrs = closures.WindowCovering.AttributeDefs WCT = closures.WindowCovering.WindowCoveringType WCCS = closures.WindowCovering.ConfigStatus From 3a8b6412edede21045a91aa5c886ccb68b22947a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 16:52:26 +0100 Subject: [PATCH 0046/1691] Remove areas template filter (#111827) --- homeassistant/helpers/template.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 86e3385a21b..f7f12c59710 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -2620,7 +2620,6 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.filters["device_id"] = self.globals["device_id"] self.globals["areas"] = hassfunction(areas) - self.filters["areas"] = self.globals["areas"] self.globals["area_id"] = hassfunction(area_id) self.filters["area_id"] = self.globals["area_id"] From a0e558c4576cd8228ea8af9d3533438721b22d4f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 16:52:39 +0100 Subject: [PATCH 0047/1691] Add generic classes BaseFlowHandler and BaseFlowManager (#111814) * Add generic classes BaseFlowHandler and BaseFlowManager * Migrate zwave_js * Update tests * Update tests * Address review comments --- homeassistant/auth/__init__.py | 4 +- homeassistant/auth/mfa_modules/__init__.py | 2 + homeassistant/auth/providers/__init__.py | 2 + .../components/auth/mfa_setup_flow.py | 4 +- .../components/repairs/issue_handler.py | 6 +- homeassistant/components/repairs/models.py | 4 +- .../components/zwave_js/config_flow.py | 100 +++++---- homeassistant/config_entries.py | 87 ++++---- homeassistant/data_entry_flow.py | 199 ++++++++++-------- homeassistant/helpers/config_entry_flow.py | 31 +-- .../helpers/config_entry_oauth2_flow.py | 17 +- homeassistant/helpers/data_entry_flow.py | 2 +- homeassistant/helpers/discovery_flow.py | 4 +- .../helpers/schema_config_entry_flow.py | 46 ++-- pylint/plugins/hass_enforce_type_hints.py | 49 +++-- .../config_flow/integration/config_flow.py | 7 +- tests/components/cloud/test_repairs.py | 2 - .../components/config/test_config_entries.py | 2 - tests/components/hassio/test_repairs.py | 12 -- tests/components/kitchen_sink/test_init.py | 2 - .../components/repairs/test_websocket_api.py | 2 - tests/helpers/test_discovery_flow.py | 2 +- .../helpers/test_schema_config_entry_flow.py | 10 +- tests/pylint/test_enforce_type_hints.py | 4 +- tests/test_data_entry_flow.py | 14 +- 25 files changed, 341 insertions(+), 273 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index f99e90dbc05..a68f8bc95eb 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -91,6 +91,8 @@ async def auth_manager_from_config( class AuthManagerFlowManager(data_entry_flow.FlowManager): """Manage authentication flows.""" + _flow_result = FlowResult + def __init__(self, hass: HomeAssistant, auth_manager: AuthManager) -> None: """Init auth manager flows.""" super().__init__(hass) @@ -110,7 +112,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager): return await auth_provider.async_login_flow(context) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: FlowResult + self, flow: data_entry_flow.BaseFlowHandler, result: FlowResult ) -> FlowResult: """Return a user as result of login flow.""" flow = cast(LoginFlow, flow) diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index aa28710d8c6..3c8c0e3a096 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -96,6 +96,8 @@ class MultiFactorAuthModule: class SetupFlow(data_entry_flow.FlowHandler): """Handler for the setup flow.""" + _flow_result = FlowResult + def __init__( self, auth_module: MultiFactorAuthModule, setup_schema: vol.Schema, user_id: str ) -> None: diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 7d74dd2dc26..577955d7c75 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -184,6 +184,8 @@ async def load_auth_provider_module( class LoginFlow(data_entry_flow.FlowHandler): """Handler for the login flow.""" + _flow_result = FlowResult + def __init__(self, auth_provider: AuthProvider) -> None: """Initialize the login flow.""" self._auth_provider = auth_provider diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index a7999af666a..58c45c56b85 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -38,6 +38,8 @@ _LOGGER = logging.getLogger(__name__) class MfaFlowManager(data_entry_flow.FlowManager): """Manage multi factor authentication flows.""" + _flow_result = data_entry_flow.FlowResult + async def async_create_flow( # type: ignore[override] self, handler_key: str, @@ -54,7 +56,7 @@ class MfaFlowManager(data_entry_flow.FlowManager): return await mfa_module.async_setup_flow(user_id) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult + self, flow: data_entry_flow.BaseFlowHandler, result: data_entry_flow.FlowResult ) -> data_entry_flow.FlowResult: """Complete an mfs setup flow.""" _LOGGER.debug("flow_result: %s", result) diff --git a/homeassistant/components/repairs/issue_handler.py b/homeassistant/components/repairs/issue_handler.py index f2ce3bac84e..388edc56254 100644 --- a/homeassistant/components/repairs/issue_handler.py +++ b/homeassistant/components/repairs/issue_handler.py @@ -48,9 +48,11 @@ class ConfirmRepairFlow(RepairsFlow): ) -class RepairsFlowManager(data_entry_flow.FlowManager): +class RepairsFlowManager(data_entry_flow.BaseFlowManager[data_entry_flow.FlowResult]): """Manage repairs flows.""" + _flow_result = data_entry_flow.FlowResult + async def async_create_flow( self, handler_key: str, @@ -82,7 +84,7 @@ class RepairsFlowManager(data_entry_flow.FlowManager): return flow async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult + self, flow: data_entry_flow.BaseFlowHandler, result: data_entry_flow.FlowResult ) -> data_entry_flow.FlowResult: """Complete a fix flow.""" if result.get("type") != data_entry_flow.FlowResultType.ABORT: diff --git a/homeassistant/components/repairs/models.py b/homeassistant/components/repairs/models.py index 6ae175b29e9..63b3199141b 100644 --- a/homeassistant/components/repairs/models.py +++ b/homeassistant/components/repairs/models.py @@ -7,9 +7,11 @@ from homeassistant import data_entry_flow from homeassistant.core import HomeAssistant -class RepairsFlow(data_entry_flow.FlowHandler): +class RepairsFlow(data_entry_flow.BaseFlowHandler[data_entry_flow.FlowResult]): """Handle a flow for fixing an issue.""" + _flow_result = data_entry_flow.FlowResult + issue_id: str data: dict[str, str | int | float | None] | None diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index c3fd2836048..9eccb032120 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -11,7 +11,6 @@ from serial.tools import list_ports import voluptuous as vol from zwave_js_server.version import VersionInfo, get_server_version -from homeassistant import config_entries, exceptions from homeassistant.components import usb from homeassistant.components.hassio import ( AddonError, @@ -22,14 +21,21 @@ from homeassistant.components.hassio import ( is_hassio, ) from homeassistant.components.zeroconf import ZeroconfServiceInfo +from homeassistant.config_entries import ( + SOURCE_USB, + ConfigEntriesFlowManager, + ConfigEntry, + ConfigEntryBaseFlow, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, + OptionsFlowManager, +) from homeassistant.const import CONF_NAME, CONF_URL from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import ( - AbortFlow, - FlowHandler, - FlowManager, - FlowResult, -) +from homeassistant.data_entry_flow import AbortFlow, BaseFlowManager +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import disconnect_client @@ -156,7 +162,7 @@ async def async_get_usb_ports(hass: HomeAssistant) -> dict[str, str]: return await hass.async_add_executor_job(get_usb_ports) -class BaseZwaveJSFlow(FlowHandler, ABC): +class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC): """Represent the base config flow for Z-Wave JS.""" def __init__(self) -> None: @@ -176,12 +182,12 @@ class BaseZwaveJSFlow(FlowHandler, ABC): @property @abstractmethod - def flow_manager(self) -> FlowManager: + def flow_manager(self) -> BaseFlowManager: """Return the flow manager of the flow.""" async def async_step_install_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Install Z-Wave JS add-on.""" if not self.install_task: self.install_task = self.hass.async_create_task(self._async_install_addon()) @@ -207,13 +213,13 @@ class BaseZwaveJSFlow(FlowHandler, ABC): async def async_step_install_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on installation failed.""" return self.async_abort(reason="addon_install_failed") async def async_step_start_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start Z-Wave JS add-on.""" if not self.start_task: self.start_task = self.hass.async_create_task(self._async_start_addon()) @@ -237,7 +243,7 @@ class BaseZwaveJSFlow(FlowHandler, ABC): async def async_step_start_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on start failed.""" return self.async_abort(reason="addon_start_failed") @@ -275,13 +281,13 @@ class BaseZwaveJSFlow(FlowHandler, ABC): @abstractmethod async def async_step_configure_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Ask for config for Z-Wave JS add-on.""" @abstractmethod async def async_step_finish_addon_setup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare info needed to complete the config entry. Get add-on discovery info and server version info. @@ -325,7 +331,7 @@ class BaseZwaveJSFlow(FlowHandler, ABC): return discovery_info_config -class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): +class ZWaveJSConfigFlow(BaseZwaveJSFlow, ConfigFlow, domain=DOMAIN): """Handle a config flow for Z-Wave JS.""" VERSION = 1 @@ -338,19 +344,19 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): self._usb_discovery = False @property - def flow_manager(self) -> config_entries.ConfigEntriesFlowManager: + def flow_manager(self) -> ConfigEntriesFlowManager: """Return the correct flow manager.""" return self.hass.config_entries.flow @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Return the options flow.""" return OptionsFlowHandler(config_entry) - async def async_step_import(self, data: dict[str, Any]) -> FlowResult: + async def async_step_import(self, data: dict[str, Any]) -> ConfigFlowResult: """Handle imported data. This step will be used when importing data @@ -364,7 +370,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if is_hassio(self.hass): return await self.async_step_on_supervisor() @@ -373,7 +379,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" home_id = str(discovery_info.properties["homeId"]) await self.async_set_unique_id(home_id) @@ -384,7 +390,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the setup.""" if user_input is not None: return await self.async_step_manual({CONF_URL: self.ws_address}) @@ -398,7 +404,9 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle USB Discovery.""" if not is_hassio(self.hass): return self.async_abort(reason="discovery_requires_supervisor") @@ -441,7 +449,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_usb_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle USB Discovery confirmation.""" if user_input is None: return self.async_show_form( @@ -455,7 +463,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_manual( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a manual configuration.""" if user_input is None: return self.async_show_form( @@ -491,7 +499,9 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): step_id="manual", data_schema=get_manual_schema(user_input), errors=errors ) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Receive configuration from add-on discovery info. This flow is triggered by the Z-Wave JS add-on. @@ -517,7 +527,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the add-on discovery.""" if user_input is not None: return await self.async_step_on_supervisor( @@ -528,7 +538,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when on Supervisor host.""" if user_input is None: return self.async_show_form( @@ -563,7 +573,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_configure_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Ask for config for Z-Wave JS add-on.""" addon_info = await self._async_get_addon_info() addon_config = addon_info.options @@ -628,7 +638,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): async def async_step_finish_addon_setup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare info needed to complete the config entry. Get add-on discovery info and server version info. @@ -638,7 +648,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): discovery_info = await self._async_get_addon_discovery_info() self.ws_address = f"ws://{discovery_info['host']}:{discovery_info['port']}" - if not self.unique_id or self.context["source"] == config_entries.SOURCE_USB: + if not self.unique_id or self.context["source"] == SOURCE_USB: if not self.version_info: try: self.version_info = await async_get_version_info( @@ -664,7 +674,7 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): return self._async_create_entry_from_vars() @callback - def _async_create_entry_from_vars(self) -> FlowResult: + def _async_create_entry_from_vars(self) -> ConfigFlowResult: """Return a config entry for the flow.""" # Abort any other flows that may be in progress for progress in self._async_in_progress(): @@ -685,10 +695,10 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): +class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow): """Handle an options flow for Z-Wave JS.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Set up the options flow.""" super().__init__() self.config_entry = config_entry @@ -696,7 +706,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): self.revert_reason: str | None = None @property - def flow_manager(self) -> config_entries.OptionsFlowManager: + def flow_manager(self) -> OptionsFlowManager: """Return the correct flow manager.""" return self.hass.config_entries.options @@ -707,7 +717,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if is_hassio(self.hass): return await self.async_step_on_supervisor() @@ -716,7 +726,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): async def async_step_manual( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a manual configuration.""" if user_input is None: return self.async_show_form( @@ -759,7 +769,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when on Supervisor host.""" if user_input is None: return self.async_show_form( @@ -780,7 +790,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): async def async_step_configure_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Ask for config for Z-Wave JS add-on.""" addon_info = await self._async_get_addon_info() addon_config = addon_info.options @@ -819,7 +829,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): if ( self.config_entry.data.get(CONF_USE_ADDON) - and self.config_entry.state == config_entries.ConfigEntryState.LOADED + and self.config_entry.state == ConfigEntryState.LOADED ): # Disconnect integration before restarting add-on. await disconnect_client(self.hass, self.config_entry) @@ -868,13 +878,13 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): async def async_step_start_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on start failed.""" return await self.async_revert_addon_config(reason="addon_start_failed") async def async_step_finish_addon_setup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare info needed to complete the config entry update. Get add-on discovery info and server version info. @@ -918,7 +928,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id) return self.async_create_entry(title=TITLE, data={}) - async def async_revert_addon_config(self, reason: str) -> FlowResult: + async def async_revert_addon_config(self, reason: str) -> ConfigFlowResult: """Abort the options flow. If the add-on options have been changed, revert those and restart add-on. @@ -944,11 +954,11 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): return await self.async_step_configure_addon(addon_config_input) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Indicate connection error.""" -class InvalidInput(exceptions.HomeAssistantError): +class InvalidInput(HomeAssistantError): """Error to indicate input data is invalid.""" def __init__(self, error: str) -> None: diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 1ca40886da2..2200831e576 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -242,6 +242,9 @@ UPDATE_ENTRY_CONFIG_ENTRY_ATTRS = { } +ConfigFlowResult = FlowResult + + class ConfigEntry: """Hold a configuration entry.""" @@ -903,7 +906,7 @@ class ConfigEntry: @callback def async_get_active_flows( self, hass: HomeAssistant, sources: set[str] - ) -> Generator[FlowResult, None, None]: + ) -> Generator[ConfigFlowResult, None, None]: """Get any active flows of certain sources for this entry.""" return ( flow @@ -970,9 +973,11 @@ class FlowCancelledError(Exception): """Error to indicate that a flow has been cancelled.""" -class ConfigEntriesFlowManager(data_entry_flow.FlowManager): +class ConfigEntriesFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult]): """Manage all the config entry flows that are in progress.""" + _flow_result = ConfigFlowResult + def __init__( self, hass: HomeAssistant, @@ -1010,7 +1015,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): async def async_init( self, handler: str, *, context: dict[str, Any] | None = None, data: Any = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start a configuration flow.""" if not context or "source" not in context: raise KeyError("Context not set or doesn't have a source set") @@ -1024,7 +1029,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): and await _support_single_config_entry_only(self.hass, handler) and self.config_entries.async_entries(handler, include_ignore=False) ): - return FlowResult( + return ConfigFlowResult( type=data_entry_flow.FlowResultType.ABORT, flow_id=flow_id, handler=handler, @@ -1065,7 +1070,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): handler: str, context: dict, data: Any, - ) -> tuple[data_entry_flow.FlowHandler, FlowResult]: + ) -> tuple[ConfigFlow, ConfigFlowResult]: """Run the init in a task to allow it to be canceled at shutdown.""" flow = await self.async_create_flow(handler, context=context, data=data) if not flow: @@ -1093,8 +1098,8 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): self._discovery_debouncer.async_shutdown() async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult - ) -> data_entry_flow.FlowResult: + self, flow: data_entry_flow.BaseFlowHandler, result: ConfigFlowResult + ) -> ConfigFlowResult: """Finish a config flow and add an entry.""" flow = cast(ConfigFlow, flow) @@ -1128,7 +1133,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): and flow.context["source"] != SOURCE_IGNORE and self.config_entries.async_entries(flow.handler, include_ignore=False) ): - return FlowResult( + return ConfigFlowResult( type=data_entry_flow.FlowResultType.ABORT, flow_id=flow.flow_id, handler=flow.handler, @@ -1213,7 +1218,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): return flow async def async_post_init( - self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult + self, flow: data_entry_flow.BaseFlowHandler, result: ConfigFlowResult ) -> None: """After a flow is initialised trigger new flow notifications.""" source = flow.context["source"] @@ -1852,7 +1857,13 @@ def _async_abort_entries_match( raise data_entry_flow.AbortFlow("already_configured") -class ConfigFlow(data_entry_flow.FlowHandler): +class ConfigEntryBaseFlow(data_entry_flow.BaseFlowHandler[ConfigFlowResult]): + """Base class for config and option flows.""" + + _flow_result = ConfigFlowResult + + +class ConfigFlow(ConfigEntryBaseFlow): """Base class for config flows with some helpers.""" def __init_subclass__(cls, *, domain: str | None = None, **kwargs: Any) -> None: @@ -2008,7 +2019,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): self, include_uninitialized: bool = False, match_context: dict[str, Any] | None = None, - ) -> list[data_entry_flow.FlowResult]: + ) -> list[ConfigFlowResult]: """Return other in progress flows for current domain.""" return [ flw @@ -2020,22 +2031,18 @@ class ConfigFlow(data_entry_flow.FlowHandler): if flw["flow_id"] != self.flow_id ] - async def async_step_ignore( - self, user_input: dict[str, Any] - ) -> data_entry_flow.FlowResult: + async def async_step_ignore(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Ignore this config flow.""" await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False) return self.async_create_entry(title=user_input["title"], data={}) - async def async_step_unignore( - self, user_input: dict[str, Any] - ) -> data_entry_flow.FlowResult: + async def async_step_unignore(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Rediscover a config entry by it's unique_id.""" return self.async_abort(reason="not_implemented") async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" return self.async_abort(reason="not_implemented") @@ -2068,14 +2075,14 @@ class ConfigFlow(data_entry_flow.FlowHandler): async def _async_step_discovery_without_unique_id( self, - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" await self._async_handle_discovery_without_unique_id() return await self.async_step_user() async def async_step_discovery( self, discovery_info: DiscoveryInfoType - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" return await self._async_step_discovery_without_unique_id() @@ -2085,7 +2092,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): *, reason: str, description_placeholders: Mapping[str, str] | None = None, - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Abort the config flow.""" # Remove reauth notification if no reauth flows are in progress if self.source == SOURCE_REAUTH and not any( @@ -2104,55 +2111,53 @@ class ConfigFlow(data_entry_flow.FlowHandler): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by Bluetooth discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_dhcp( self, discovery_info: DhcpServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by DHCP discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_hassio( self, discovery_info: HassioServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by HASS IO discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by integration specific discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_homekit( self, discovery_info: ZeroconfServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by Homekit discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_mqtt( self, discovery_info: MqttServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by MQTT discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_ssdp( self, discovery_info: SsdpServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by SSDP discovery.""" return await self._async_step_discovery_without_unique_id() - async def async_step_usb( - self, discovery_info: UsbServiceInfo - ) -> data_entry_flow.FlowResult: + async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult: """Handle a flow initialized by USB discovery.""" return await self._async_step_discovery_without_unique_id() async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by Zeroconf discovery.""" return await self._async_step_discovery_without_unique_id() @@ -2165,7 +2170,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): description: str | None = None, description_placeholders: Mapping[str, str] | None = None, options: Mapping[str, Any] | None = None, - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Finish config flow and create a config entry.""" result = super().async_create_entry( title=title, @@ -2175,6 +2180,8 @@ class ConfigFlow(data_entry_flow.FlowHandler): ) result["options"] = options or {} + result["minor_version"] = self.MINOR_VERSION + result["version"] = self.VERSION return result @@ -2188,7 +2195,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): data: Mapping[str, Any] | UndefinedType = UNDEFINED, options: Mapping[str, Any] | UndefinedType = UNDEFINED, reason: str = "reauth_successful", - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Update config entry, reload config entry and finish config flow.""" result = self.hass.config_entries.async_update_entry( entry=entry, @@ -2202,9 +2209,11 @@ class ConfigFlow(data_entry_flow.FlowHandler): return self.async_abort(reason=reason) -class OptionsFlowManager(data_entry_flow.FlowManager): +class OptionsFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult]): """Flow to set options for a configuration entry.""" + _flow_result = ConfigFlowResult + def _async_get_config_entry(self, config_entry_id: str) -> ConfigEntry: """Return config entry or raise if not found.""" entry = self.hass.config_entries.async_get_entry(config_entry_id) @@ -2229,8 +2238,8 @@ class OptionsFlowManager(data_entry_flow.FlowManager): return handler.async_get_options_flow(entry) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult - ) -> data_entry_flow.FlowResult: + self, flow: data_entry_flow.BaseFlowHandler, result: ConfigFlowResult + ) -> ConfigFlowResult: """Finish an options flow and update options for configuration entry. Flow.handler and entry_id is the same thing to map flow with entry. @@ -2249,7 +2258,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager): result["result"] = True return result - async def _async_setup_preview(self, flow: data_entry_flow.FlowHandler) -> None: + async def _async_setup_preview(self, flow: data_entry_flow.BaseFlowHandler) -> None: """Set up preview for an option flow handler.""" entry = self._async_get_config_entry(flow.handler) await _load_integration(self.hass, entry.domain, {}) @@ -2258,7 +2267,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager): await flow.async_setup_preview(self.hass) -class OptionsFlow(data_entry_flow.FlowHandler): +class OptionsFlow(ConfigEntryBaseFlow): """Base class for config options flows.""" handler: str diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index bbb6621cfcc..b573f528945 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -11,7 +11,7 @@ from enum import StrEnum from functools import partial import logging from types import MappingProxyType -from typing import Any, Required, TypedDict +from typing import Any, Generic, Required, TypedDict, TypeVar import voluptuous as vol @@ -75,6 +75,7 @@ FLOW_NOT_COMPLETE_STEPS = { FlowResultType.MENU, } + STEP_ID_OPTIONAL_STEPS = { FlowResultType.EXTERNAL_STEP, FlowResultType.FORM, @@ -83,6 +84,9 @@ STEP_ID_OPTIONAL_STEPS = { } +_FlowResultT = TypeVar("_FlowResultT", bound="FlowResult") + + @dataclass(slots=True) class BaseServiceInfo: """Base class for discovery ServiceInfo.""" @@ -163,26 +167,6 @@ class FlowResult(TypedDict, total=False): version: int -@callback -def _async_flow_handler_to_flow_result( - flows: Iterable[FlowHandler], include_uninitialized: bool -) -> list[FlowResult]: - """Convert a list of FlowHandler to a partial FlowResult that can be serialized.""" - results = [] - for flow in flows: - if not include_uninitialized and flow.cur_step is None: - continue - result = FlowResult( - flow_id=flow.flow_id, - handler=flow.handler, - context=flow.context, - ) - if flow.cur_step: - result["step_id"] = flow.cur_step["step_id"] - results.append(result) - return results - - def _map_error_to_schema_errors( schema_errors: dict[str, Any], error: vol.Invalid, @@ -206,9 +190,11 @@ def _map_error_to_schema_errors( schema_errors[path_part_str] = error.error_message -class FlowManager(abc.ABC): +class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): """Manage all the flows that are in progress.""" + _flow_result: Callable[..., _FlowResultT] + def __init__( self, hass: HomeAssistant, @@ -216,9 +202,9 @@ class FlowManager(abc.ABC): """Initialize the flow manager.""" self.hass = hass self._preview: set[str] = set() - self._progress: dict[str, FlowHandler] = {} - self._handler_progress_index: dict[str, set[FlowHandler]] = {} - self._init_data_process_index: dict[type, set[FlowHandler]] = {} + self._progress: dict[str, BaseFlowHandler] = {} + self._handler_progress_index: dict[str, set[BaseFlowHandler]] = {} + self._init_data_process_index: dict[type, set[BaseFlowHandler]] = {} @abc.abstractmethod async def async_create_flow( @@ -227,7 +213,7 @@ class FlowManager(abc.ABC): *, context: dict[str, Any] | None = None, data: dict[str, Any] | None = None, - ) -> FlowHandler: + ) -> BaseFlowHandler[_FlowResultT]: """Create a flow for specified handler. Handler key is the domain of the component that we want to set up. @@ -235,11 +221,13 @@ class FlowManager(abc.ABC): @abc.abstractmethod async def async_finish_flow( - self, flow: FlowHandler, result: FlowResult - ) -> FlowResult: + self, flow: BaseFlowHandler, result: _FlowResultT + ) -> _FlowResultT: """Finish a data entry flow.""" - async def async_post_init(self, flow: FlowHandler, result: FlowResult) -> None: + async def async_post_init( + self, flow: BaseFlowHandler, result: _FlowResultT + ) -> None: """Entry has finished executing its first step asynchronously.""" @callback @@ -262,16 +250,16 @@ class FlowManager(abc.ABC): return False @callback - def async_get(self, flow_id: str) -> FlowResult: + def async_get(self, flow_id: str) -> _FlowResultT: """Return a flow in progress as a partial FlowResult.""" if (flow := self._progress.get(flow_id)) is None: raise UnknownFlow - return _async_flow_handler_to_flow_result([flow], False)[0] + return self._async_flow_handler_to_flow_result([flow], False)[0] @callback - def async_progress(self, include_uninitialized: bool = False) -> list[FlowResult]: + def async_progress(self, include_uninitialized: bool = False) -> list[_FlowResultT]: """Return the flows in progress as a partial FlowResult.""" - return _async_flow_handler_to_flow_result( + return self._async_flow_handler_to_flow_result( self._progress.values(), include_uninitialized ) @@ -281,13 +269,13 @@ class FlowManager(abc.ABC): handler: str, include_uninitialized: bool = False, match_context: dict[str, Any] | None = None, - ) -> list[FlowResult]: + ) -> list[_FlowResultT]: """Return the flows in progress by handler as a partial FlowResult. If match_context is specified, only return flows with a context that is a superset of match_context. """ - return _async_flow_handler_to_flow_result( + return self._async_flow_handler_to_flow_result( self._async_progress_by_handler(handler, match_context), include_uninitialized, ) @@ -298,9 +286,9 @@ class FlowManager(abc.ABC): init_data_type: type, matcher: Callable[[Any], bool], include_uninitialized: bool = False, - ) -> list[FlowResult]: + ) -> list[_FlowResultT]: """Return flows in progress init matching by data type as a partial FlowResult.""" - return _async_flow_handler_to_flow_result( + return self._async_flow_handler_to_flow_result( ( progress for progress in self._init_data_process_index.get(init_data_type, set()) @@ -312,7 +300,7 @@ class FlowManager(abc.ABC): @callback def _async_progress_by_handler( self, handler: str, match_context: dict[str, Any] | None - ) -> list[FlowHandler]: + ) -> list[BaseFlowHandler[_FlowResultT]]: """Return the flows in progress by handler. If match_context is specified, only return flows with a context that @@ -329,7 +317,7 @@ class FlowManager(abc.ABC): async def async_init( self, handler: str, *, context: dict[str, Any] | None = None, data: Any = None - ) -> FlowResult: + ) -> _FlowResultT: """Start a data entry flow.""" if context is None: context = {} @@ -352,9 +340,9 @@ class FlowManager(abc.ABC): async def async_configure( self, flow_id: str, user_input: dict | None = None - ) -> FlowResult: + ) -> _FlowResultT: """Continue a data entry flow.""" - result: FlowResult | None = None + result: _FlowResultT | None = None while not result or result["type"] == FlowResultType.SHOW_PROGRESS_DONE: result = await self._async_configure(flow_id, user_input) flow = self._progress.get(flow_id) @@ -364,7 +352,7 @@ class FlowManager(abc.ABC): async def _async_configure( self, flow_id: str, user_input: dict | None = None - ) -> FlowResult: + ) -> _FlowResultT: """Continue a data entry flow.""" if (flow := self._progress.get(flow_id)) is None: raise UnknownFlow @@ -458,7 +446,7 @@ class FlowManager(abc.ABC): self._async_remove_flow_progress(flow_id) @callback - def _async_add_flow_progress(self, flow: FlowHandler) -> None: + def _async_add_flow_progress(self, flow: BaseFlowHandler[_FlowResultT]) -> None: """Add a flow to in progress.""" if flow.init_data is not None: init_data_type = type(flow.init_data) @@ -467,7 +455,9 @@ class FlowManager(abc.ABC): self._handler_progress_index.setdefault(flow.handler, set()).add(flow) @callback - def _async_remove_flow_from_index(self, flow: FlowHandler) -> None: + def _async_remove_flow_from_index( + self, flow: BaseFlowHandler[_FlowResultT] + ) -> None: """Remove a flow from in progress.""" if flow.init_data is not None: init_data_type = type(flow.init_data) @@ -492,17 +482,24 @@ class FlowManager(abc.ABC): _LOGGER.exception("Error removing %s flow: %s", flow.handler, err) async def _async_handle_step( - self, flow: FlowHandler, step_id: str, user_input: dict | BaseServiceInfo | None - ) -> FlowResult: + self, + flow: BaseFlowHandler[_FlowResultT], + step_id: str, + user_input: dict | BaseServiceInfo | None, + ) -> _FlowResultT: """Handle a step of a flow.""" self._raise_if_step_does_not_exist(flow, step_id) method = f"async_step_{step_id}" try: - result: FlowResult = await getattr(flow, method)(user_input) + result: _FlowResultT = await getattr(flow, method)(user_input) except AbortFlow as err: - result = _create_abort_data( - flow.flow_id, flow.handler, err.reason, err.description_placeholders + result = self._flow_result( + type=FlowResultType.ABORT, + flow_id=flow.flow_id, + handler=flow.handler, + reason=err.reason, + description_placeholders=err.description_placeholders, ) # Setup the flow handler's preview if needed @@ -521,7 +518,8 @@ class FlowManager(abc.ABC): if ( result["type"] == FlowResultType.SHOW_PROGRESS - and (progress_task := result.pop("progress_task", None)) + # Mypy does not agree with using pop on _FlowResultT + and (progress_task := result.pop("progress_task", None)) # type: ignore[arg-type] and progress_task != flow.async_get_progress_task() ): # The flow's progress task was changed, register a callback on it @@ -532,8 +530,9 @@ class FlowManager(abc.ABC): def schedule_configure(_: asyncio.Task) -> None: self.hass.async_create_task(call_configure()) - progress_task.add_done_callback(schedule_configure) - flow.async_set_progress_task(progress_task) + # The mypy ignores are a consequence of mypy not accepting the pop above + progress_task.add_done_callback(schedule_configure) # type: ignore[attr-defined] + flow.async_set_progress_task(progress_task) # type: ignore[arg-type] elif result["type"] != FlowResultType.SHOW_PROGRESS: flow.async_cancel_progress_task() @@ -560,7 +559,9 @@ class FlowManager(abc.ABC): return result - def _raise_if_step_does_not_exist(self, flow: FlowHandler, step_id: str) -> None: + def _raise_if_step_does_not_exist( + self, flow: BaseFlowHandler, step_id: str + ) -> None: """Raise if the step does not exist.""" method = f"async_step_{step_id}" @@ -570,18 +571,45 @@ class FlowManager(abc.ABC): f"Handler {self.__class__.__name__} doesn't support step {step_id}" ) - async def _async_setup_preview(self, flow: FlowHandler) -> None: + async def _async_setup_preview(self, flow: BaseFlowHandler) -> None: """Set up preview for a flow handler.""" if flow.handler not in self._preview: self._preview.add(flow.handler) await flow.async_setup_preview(self.hass) + @callback + def _async_flow_handler_to_flow_result( + self, flows: Iterable[BaseFlowHandler], include_uninitialized: bool + ) -> list[_FlowResultT]: + """Convert a list of FlowHandler to a partial FlowResult that can be serialized.""" + results = [] + for flow in flows: + if not include_uninitialized and flow.cur_step is None: + continue + result = self._flow_result( + flow_id=flow.flow_id, + handler=flow.handler, + context=flow.context, + ) + if flow.cur_step: + result["step_id"] = flow.cur_step["step_id"] + results.append(result) + return results -class FlowHandler: + +class FlowManager(BaseFlowManager[FlowResult]): + """Manage all the flows that are in progress.""" + + _flow_result = FlowResult + + +class BaseFlowHandler(Generic[_FlowResultT]): """Handle a data entry flow.""" + _flow_result: Callable[..., _FlowResultT] + # Set by flow manager - cur_step: FlowResult | None = None + cur_step: _FlowResultT | None = None # While not purely typed, it makes typehinting more useful for us # and removes the need for constant None checks or asserts. @@ -657,12 +685,12 @@ class FlowHandler: description_placeholders: Mapping[str, str | None] | None = None, last_step: bool | None = None, preview: str | None = None, - ) -> FlowResult: + ) -> _FlowResultT: """Return the definition of a form to gather user input. The step_id parameter is deprecated and will be removed in a future release. """ - flow_result = FlowResult( + flow_result = self._flow_result( type=FlowResultType.FORM, flow_id=self.flow_id, handler=self.handler, @@ -684,11 +712,9 @@ class FlowHandler: data: Mapping[str, Any], description: str | None = None, description_placeholders: Mapping[str, str] | None = None, - ) -> FlowResult: + ) -> _FlowResultT: """Finish flow.""" - flow_result = FlowResult( - version=self.VERSION, - minor_version=self.MINOR_VERSION, + flow_result = self._flow_result( type=FlowResultType.CREATE_ENTRY, flow_id=self.flow_id, handler=self.handler, @@ -707,10 +733,14 @@ class FlowHandler: *, reason: str, description_placeholders: Mapping[str, str] | None = None, - ) -> FlowResult: + ) -> _FlowResultT: """Abort the flow.""" - return _create_abort_data( - self.flow_id, self.handler, reason, description_placeholders + return self._flow_result( + type=FlowResultType.ABORT, + flow_id=self.flow_id, + handler=self.handler, + reason=reason, + description_placeholders=description_placeholders, ) @callback @@ -720,12 +750,12 @@ class FlowHandler: step_id: str | None = None, url: str, description_placeholders: Mapping[str, str] | None = None, - ) -> FlowResult: + ) -> _FlowResultT: """Return the definition of an external step for the user to take. The step_id parameter is deprecated and will be removed in a future release. """ - flow_result = FlowResult( + flow_result = self._flow_result( type=FlowResultType.EXTERNAL_STEP, flow_id=self.flow_id, handler=self.handler, @@ -737,9 +767,9 @@ class FlowHandler: return flow_result @callback - def async_external_step_done(self, *, next_step_id: str) -> FlowResult: + def async_external_step_done(self, *, next_step_id: str) -> _FlowResultT: """Return the definition of an external step for the user to take.""" - return FlowResult( + return self._flow_result( type=FlowResultType.EXTERNAL_STEP_DONE, flow_id=self.flow_id, handler=self.handler, @@ -754,7 +784,7 @@ class FlowHandler: progress_action: str, description_placeholders: Mapping[str, str] | None = None, progress_task: asyncio.Task[Any] | None = None, - ) -> FlowResult: + ) -> _FlowResultT: """Show a progress message to the user, without user input allowed. The step_id parameter is deprecated and will be removed in a future release. @@ -777,7 +807,7 @@ class FlowHandler: if progress_task is None: self.deprecated_show_progress = True - flow_result = FlowResult( + flow_result = self._flow_result( type=FlowResultType.SHOW_PROGRESS, flow_id=self.flow_id, handler=self.handler, @@ -790,9 +820,9 @@ class FlowHandler: return flow_result @callback - def async_show_progress_done(self, *, next_step_id: str) -> FlowResult: + def async_show_progress_done(self, *, next_step_id: str) -> _FlowResultT: """Mark the progress done.""" - return FlowResult( + return self._flow_result( type=FlowResultType.SHOW_PROGRESS_DONE, flow_id=self.flow_id, handler=self.handler, @@ -806,13 +836,13 @@ class FlowHandler: step_id: str | None = None, menu_options: list[str] | dict[str, str], description_placeholders: Mapping[str, str] | None = None, - ) -> FlowResult: + ) -> _FlowResultT: """Show a navigation menu to the user. Options dict maps step_id => i18n label The step_id parameter is deprecated and will be removed in a future release. """ - flow_result = FlowResult( + flow_result = self._flow_result( type=FlowResultType.MENU, flow_id=self.flow_id, handler=self.handler, @@ -853,21 +883,10 @@ class FlowHandler: self.__progress_task = progress_task -@callback -def _create_abort_data( - flow_id: str, - handler: str, - reason: str, - description_placeholders: Mapping[str, str] | None = None, -) -> FlowResult: - """Return the definition of an external step for the user to take.""" - return FlowResult( - type=FlowResultType.ABORT, - flow_id=flow_id, - handler=handler, - reason=reason, - description_placeholders=description_placeholders, - ) +class FlowHandler(BaseFlowHandler[FlowResult]): + """Handle a data entry flow.""" + + _flow_result = FlowResult # These can be removed if no deprecated constant are in this module anymore diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 6cdedf98f97..b645fdb06bd 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast from homeassistant import config_entries from homeassistant.components import onboarding from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from .typing import DiscoveryInfoType @@ -46,7 +45,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -57,7 +56,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Confirm setup.""" if user_input is None and onboarding.async_is_onboarded(self.hass): self._set_confirm_only() @@ -87,7 +86,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): async def async_step_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -98,7 +97,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by bluetooth discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -107,7 +106,9 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by dhcp discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -118,7 +119,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): async def async_step_homekit( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by Homekit discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -127,7 +128,9 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: + async def async_step_mqtt( + self, discovery_info: MqttServiceInfo + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by mqtt discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -138,7 +141,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by Zeroconf discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -147,7 +150,9 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_ssdp(self, discovery_info: SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: SsdpServiceInfo + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by Ssdp discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -156,7 +161,9 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() - async def async_step_import(self, _: dict[str, Any] | None) -> FlowResult: + async def async_step_import( + self, _: dict[str, Any] | None + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by import.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -205,7 +212,7 @@ class WebhookFlowHandler(config_entries.ConfigFlow): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a user initiated set up flow to create a webhook.""" if not self._allow_multiple and self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index d99cc1d4f76..337e6ca92b6 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -25,7 +25,6 @@ from yarl import URL from homeassistant import config_entries from homeassistant.components import http from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.loader import async_get_application_credentials from .aiohttp_client import async_get_clientsession @@ -253,7 +252,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): async def async_step_pick_implementation( self, user_input: dict | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow start.""" implementations = await async_get_implementations(self.hass, self.DOMAIN) @@ -286,7 +285,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): async def async_step_auth( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Create an entry for auth.""" # Flow has been triggered by external data if user_input is not None: @@ -314,7 +313,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): async def async_step_creation( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Create config entry from external data.""" _LOGGER.debug("Creating config entry from external data") @@ -353,14 +352,18 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): {"auth_implementation": self.flow_impl.domain, "token": token} ) - async def async_step_authorize_rejected(self, data: None = None) -> FlowResult: + async def async_step_authorize_rejected( + self, data: None = None + ) -> config_entries.ConfigFlowResult: """Step to handle flow rejection.""" return self.async_abort( reason="user_rejected_authorize", description_placeholders={"error": self.external_data["error"]}, ) - async def async_oauth_create_entry(self, data: dict) -> FlowResult: + async def async_oauth_create_entry( + self, data: dict + ) -> config_entries.ConfigFlowResult: """Create an entry for the flow. Ok to override if you want to fetch extra info or even add another step. @@ -369,7 +372,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow start.""" return await self.async_step_pick_implementation(user_input) diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 695fbbf7633..6a6e48caa7e 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -18,7 +18,7 @@ from . import config_validation as cv class _BaseFlowManagerView(HomeAssistantView): """Foundation for flow manager views.""" - def __init__(self, flow_mgr: data_entry_flow.FlowManager) -> None: + def __init__(self, flow_mgr: data_entry_flow.BaseFlowManager) -> None: """Initialize the flow manager index view.""" self._flow_mgr = flow_mgr diff --git a/homeassistant/helpers/discovery_flow.py b/homeassistant/helpers/discovery_flow.py index c4698de1f52..a24e87325ae 100644 --- a/homeassistant/helpers/discovery_flow.py +++ b/homeassistant/helpers/discovery_flow.py @@ -4,9 +4,9 @@ from __future__ import annotations from collections.abc import Coroutine from typing import Any, NamedTuple +from homeassistant.config_entries import ConfigFlowResult from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import CoreState, Event, HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.loader import bind_hass from homeassistant.util.async_ import gather_with_limited_concurrency @@ -40,7 +40,7 @@ def async_create_flow( @callback def _async_init_flow( hass: HomeAssistant, domain: str, context: dict[str, Any], data: Any -) -> Coroutine[None, None, FlowResult] | None: +) -> Coroutine[None, None, ConfigFlowResult] | None: """Create a discovery flow.""" # Avoid spawning flows that have the same initial discovery data # as ones in progress as it may cause additional device probing diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index 2bbad0ed63a..d5563c995ff 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -10,9 +10,15 @@ from typing import Any, cast import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, + OptionsFlowWithConfigEntry, +) from homeassistant.core import HomeAssistant, callback, split_entity_id -from homeassistant.data_entry_flow import FlowResult, UnknownHandler +from homeassistant.data_entry_flow import UnknownHandler from . import entity_registry as er, selector from .typing import UNDEFINED, UndefinedType @@ -126,7 +132,7 @@ class SchemaCommonFlowHandler: async def async_step( self, step_id: str, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a step.""" if isinstance(self._flow[step_id], SchemaFlowFormStep): return await self._async_form_step(step_id, user_input) @@ -141,7 +147,7 @@ class SchemaCommonFlowHandler: async def _async_form_step( self, step_id: str, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a form step.""" form_step: SchemaFlowFormStep = cast(SchemaFlowFormStep, self._flow[step_id]) @@ -204,7 +210,7 @@ class SchemaCommonFlowHandler: async def _show_next_step_or_create_entry( self, form_step: SchemaFlowFormStep - ) -> FlowResult: + ) -> ConfigFlowResult: next_step_id_or_end_flow: str | None if callable(form_step.next_step): @@ -222,7 +228,7 @@ class SchemaCommonFlowHandler: next_step_id: str, error: SchemaFlowError | None = None, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show form for next step.""" if isinstance(self._flow[next_step_id], SchemaFlowMenuStep): menu_step = cast(SchemaFlowMenuStep, self._flow[next_step_id]) @@ -271,7 +277,7 @@ class SchemaCommonFlowHandler: async def _async_menu_step( self, step_id: str, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a menu step.""" menu_step: SchemaFlowMenuStep = cast(SchemaFlowMenuStep, self._flow[step_id]) return self._handler.async_show_menu( @@ -280,7 +286,7 @@ class SchemaCommonFlowHandler: ) -class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): +class SchemaConfigFlowHandler(ConfigFlow, ABC): """Handle a schema based config flow.""" config_flow: Mapping[str, SchemaFlowStep] @@ -294,8 +300,8 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): @callback def _async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" if cls.options_flow is None: raise UnknownHandler @@ -324,9 +330,7 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): @classmethod @callback - def async_supports_options_flow( - cls, config_entry: config_entries.ConfigEntry - ) -> bool: + def async_supports_options_flow(cls, config_entry: ConfigEntry) -> bool: """Return options flow support for this handler.""" return cls.options_flow is not None @@ -335,13 +339,13 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): step_id: str, ) -> Callable[ [SchemaConfigFlowHandler, dict[str, Any] | None], - Coroutine[Any, Any, FlowResult], + Coroutine[Any, Any, ConfigFlowResult], ]: """Generate a step handler.""" async def _async_step( self: SchemaConfigFlowHandler, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a config flow step.""" # pylint: disable-next=protected-access result = await self._common_handler.async_step(step_id, user_input) @@ -382,7 +386,7 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): self, data: Mapping[str, Any], **kwargs: Any, - ) -> FlowResult: + ) -> ConfigFlowResult: """Finish config flow and create a config entry.""" self.async_config_flow_finished(data) return super().async_create_entry( @@ -390,12 +394,12 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): ) -class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): +class SchemaOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle a schema based options flow.""" def __init__( self, - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, options_flow: Mapping[str, SchemaFlowStep], async_options_flow_finished: Callable[[HomeAssistant, Mapping[str, Any]], None] | None = None, @@ -430,13 +434,13 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): step_id: str, ) -> Callable[ [SchemaConfigFlowHandler, dict[str, Any] | None], - Coroutine[Any, Any, FlowResult], + Coroutine[Any, Any, ConfigFlowResult], ]: """Generate a step handler.""" async def _async_step( self: SchemaConfigFlowHandler, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle an options flow step.""" # pylint: disable-next=protected-access result = await self._common_handler.async_step(step_id, user_input) @@ -449,7 +453,7 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): self, data: Mapping[str, Any], **kwargs: Any, - ) -> FlowResult: + ) -> ConfigFlowResult: """Finish config flow and create a config entry.""" if self._async_options_flow_finished: self._async_options_flow_finished(self.hass, data) diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index b2620dd3e1e..602bd8a443d 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -55,11 +55,12 @@ class TypeHintMatch: ) -@dataclass +@dataclass(kw_only=True) class ClassTypeHintMatch: """Class for pattern matching.""" base_class: str + exclude_base_classes: set[str] | None = None matches: list[TypeHintMatch] @@ -481,6 +482,7 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = { "config_flow": [ ClassTypeHintMatch( base_class="FlowHandler", + exclude_base_classes={"ConfigEntryBaseFlow"}, matches=[ TypeHintMatch( function_name="async_step_*", @@ -492,6 +494,11 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = { ClassTypeHintMatch( base_class="ConfigFlow", matches=[ + TypeHintMatch( + function_name="async_step123_*", + arg_types={}, + return_type=["ConfigFlowResult", "FlowResult"], + ), TypeHintMatch( function_name="async_get_options_flow", arg_types={ @@ -504,56 +511,66 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = { arg_types={ 1: "DhcpServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_hassio", arg_types={ 1: "HassioServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_homekit", arg_types={ 1: "ZeroconfServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_mqtt", arg_types={ 1: "MqttServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_reauth", arg_types={ 1: "Mapping[str, Any]", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_ssdp", arg_types={ 1: "SsdpServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_usb", arg_types={ 1: "UsbServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], ), TypeHintMatch( function_name="async_step_zeroconf", arg_types={ 1: "ZeroconfServiceInfo", }, - return_type="FlowResult", + return_type=["ConfigFlowResult", "FlowResult"], + ), + ], + ), + ClassTypeHintMatch( + base_class="OptionsFlow", + matches=[ + TypeHintMatch( + function_name="async_step_*", + arg_types={}, + return_type=["ConfigFlowResult", "FlowResult"], ), ], ), @@ -3126,11 +3143,19 @@ class HassTypeHintChecker(BaseChecker): ancestor: nodes.ClassDef checked_class_methods: set[str] = set() ancestors = list(node.ancestors()) # cache result for inside loop - for class_matches in self._class_matchers: + for class_matcher in self._class_matchers: + skip_matcher = False + if exclude_base_classes := class_matcher.exclude_base_classes: + for ancestor in ancestors: + if ancestor.name in exclude_base_classes: + skip_matcher = True + break + if skip_matcher: + continue for ancestor in ancestors: - if ancestor.name == class_matches.base_class: + if ancestor.name == class_matcher.base_class: self._visit_class_functions( - node, class_matches.matches, checked_class_methods + node, class_matcher.matches, checked_class_methods ) def _visit_class_functions( diff --git a/script/scaffold/templates/config_flow/integration/config_flow.py b/script/scaffold/templates/config_flow/integration/config_flow.py index caef6c2e729..f68059584f7 100644 --- a/script/scaffold/templates/config_flow/integration/config_flow.py +++ b/script/scaffold/templates/config_flow/integration/config_flow.py @@ -6,10 +6,9 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -68,14 +67,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return {"title": "Name of the device"} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for NEW_NAME.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/tests/components/cloud/test_repairs.py b/tests/components/cloud/test_repairs.py index 0e662c30ee7..9380cec2ebd 100644 --- a/tests/components/cloud/test_repairs.py +++ b/tests/components/cloud/test_repairs.py @@ -147,13 +147,11 @@ async def test_legacy_subscription_repair_flow( flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": DOMAIN, "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue( diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 844b4bdb3b4..6573a83b061 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -941,10 +941,8 @@ async def test_two_step_options_flow(hass: HomeAssistant, client) -> None: "handler": "test1", "type": "create_entry", "title": "Enable disable", - "version": 1, "description": None, "description_placeholders": None, - "minor_version": 1, } diff --git a/tests/components/hassio/test_repairs.py b/tests/components/hassio/test_repairs.py index 5dd73a21615..6a9287e0331 100644 --- a/tests/components/hassio/test_repairs.py +++ b/tests/components/hassio/test_repairs.py @@ -94,13 +94,11 @@ async def test_supervisor_issue_repair_flow( flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": "hassio", "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234") @@ -190,13 +188,11 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions( flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": "hassio", "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234") @@ -305,13 +301,11 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions_and_confir flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": "hassio", "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234") @@ -386,13 +380,11 @@ async def test_supervisor_issue_repair_flow_skip_confirmation( flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": "hassio", "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234") @@ -486,13 +478,11 @@ async def test_mount_failed_repair_flow( flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": "hassio", "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234") @@ -598,13 +588,11 @@ async def test_supervisor_issue_docker_config_repair_flow( flow_id = data["flow_id"] assert data == { - "version": 1, "type": "create_entry", "flow_id": flow_id, "handler": "hassio", "description": None, "description_placeholders": None, - "minor_version": 1, } assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234") diff --git a/tests/components/kitchen_sink/test_init.py b/tests/components/kitchen_sink/test_init.py index b3f303fcfe1..fb81c87008e 100644 --- a/tests/components/kitchen_sink/test_init.py +++ b/tests/components/kitchen_sink/test_init.py @@ -244,9 +244,7 @@ async def test_issues_created( "description_placeholders": None, "flow_id": flow_id, "handler": DOMAIN, - "minor_version": 1, "type": "create_entry", - "version": 1, } await ws_client.send_json({"id": 4, "type": "repairs/list_issues"}) diff --git a/tests/components/repairs/test_websocket_api.py b/tests/components/repairs/test_websocket_api.py index 0cf6b22dc0c..ef08095ca79 100644 --- a/tests/components/repairs/test_websocket_api.py +++ b/tests/components/repairs/test_websocket_api.py @@ -338,9 +338,7 @@ async def test_fix_issue( "description_placeholders": None, "flow_id": flow_id, "handler": domain, - "minor_version": 1, "type": "create_entry", - "version": 1, } await ws_client.send_json({"id": 4, "type": "repairs/list_issues"}) diff --git a/tests/helpers/test_discovery_flow.py b/tests/helpers/test_discovery_flow.py index 0b3386f8e04..7dcf6256a59 100644 --- a/tests/helpers/test_discovery_flow.py +++ b/tests/helpers/test_discovery_flow.py @@ -63,7 +63,7 @@ async def test_async_create_flow_checks_existing_flows_after_startup( """Test existing flows prevent an identical ones from being after startup.""" hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) with patch( - "homeassistant.data_entry_flow.FlowManager.async_has_matching_flow", + "homeassistant.data_entry_flow.BaseFlowManager.async_has_matching_flow", return_value=True, ): discovery_flow.async_create_flow( diff --git a/tests/helpers/test_schema_config_entry_flow.py b/tests/helpers/test_schema_config_entry_flow.py index 58f6a261aef..6778a168dd7 100644 --- a/tests/helpers/test_schema_config_entry_flow.py +++ b/tests/helpers/test_schema_config_entry_flow.py @@ -45,7 +45,7 @@ def manager_fixture(): handlers = Registry() entries = [] - class FlowManager(data_entry_flow.FlowManager): + class FlowManager(data_entry_flow.BaseFlowManager): """Test flow manager.""" async def async_create_flow(self, handler_key, *, context, data): @@ -105,7 +105,7 @@ async def test_name(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> @pytest.mark.parametrize("marker", (vol.Required, vol.Optional)) async def test_config_flow_advanced_option( - hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker + hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager, marker ) -> None: """Test handling of advanced options in config flow.""" manager.hass = hass @@ -200,7 +200,7 @@ async def test_config_flow_advanced_option( @pytest.mark.parametrize("marker", (vol.Required, vol.Optional)) async def test_options_flow_advanced_option( - hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker + hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager, marker ) -> None: """Test handling of advanced options in options flow.""" manager.hass = hass @@ -475,7 +475,7 @@ async def test_next_step_function(hass: HomeAssistant) -> None: async def test_suggested_values( - hass: HomeAssistant, manager: data_entry_flow.FlowManager + hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager ) -> None: """Test suggested_values handling in SchemaFlowFormStep.""" manager.hass = hass @@ -667,7 +667,7 @@ async def test_options_flow_state(hass: HomeAssistant) -> None: async def test_options_flow_omit_optional_keys( - hass: HomeAssistant, manager: data_entry_flow.FlowManager + hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager ) -> None: """Test handling of advanced options in options flow.""" manager.hass = hass diff --git a/tests/pylint/test_enforce_type_hints.py b/tests/pylint/test_enforce_type_hints.py index d23d5a849dd..2a03343cb82 100644 --- a/tests/pylint/test_enforce_type_hints.py +++ b/tests/pylint/test_enforce_type_hints.py @@ -346,7 +346,7 @@ def test_invalid_config_flow_step( pylint.testutils.MessageTest( msg_id="hass-return-type", node=func_node, - args=("FlowResult", "async_step_zeroconf"), + args=(["ConfigFlowResult", "FlowResult"], "async_step_zeroconf"), line=11, col_offset=4, end_line=11, @@ -374,7 +374,7 @@ def test_valid_config_flow_step( async def async_step_zeroconf( self, device_config: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: pass """, "homeassistant.components.pylint_test.config_flow", diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index d39c8faccef..96bd45d4e36 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -24,9 +24,11 @@ def manager(): handlers = Registry() entries = [] - class FlowManager(data_entry_flow.FlowManager): + class FlowManager(data_entry_flow.BaseFlowManager): """Test flow manager.""" + _flow_result = data_entry_flow.FlowResult + async def async_create_flow(self, handler_key, *, context, data): """Test create flow.""" handler = handlers.get(handler_key) @@ -79,7 +81,7 @@ async def test_configure_reuses_handler_instance(manager) -> None: assert len(manager.mock_created_entries) == 0 -async def test_configure_two_steps(manager: data_entry_flow.FlowManager) -> None: +async def test_configure_two_steps(manager: data_entry_flow.BaseFlowManager) -> None: """Test that we reuse instances.""" @manager.mock_reg_handler("test") @@ -211,7 +213,6 @@ async def test_create_saves_data(manager) -> None: assert len(manager.mock_created_entries) == 1 entry = manager.mock_created_entries[0] - assert entry["version"] == 5 assert entry["handler"] == "test" assert entry["title"] == "Test Title" assert entry["data"] == "Test Data" @@ -237,7 +238,6 @@ async def test_discovery_init_flow(manager) -> None: assert len(manager.mock_created_entries) == 1 entry = manager.mock_created_entries[0] - assert entry["version"] == 5 assert entry["handler"] == "test" assert entry["title"] == "hello" assert entry["data"] == data @@ -258,7 +258,7 @@ async def test_finish_callback_change_result_type(hass: HomeAssistant) -> None: step_id="init", data_schema=vol.Schema({"count": int}) ) - class FlowManager(data_entry_flow.FlowManager): + class FlowManager(data_entry_flow.BaseFlowManager): async def async_create_flow(self, handler_name, *, context, data): """Create a test flow.""" return TestFlow() @@ -775,7 +775,7 @@ async def test_async_get_unknown_flow(manager) -> None: async def test_async_has_matching_flow( - hass: HomeAssistant, manager: data_entry_flow.FlowManager + hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager ) -> None: """Test we can check for matching flows.""" manager.hass = hass @@ -951,7 +951,7 @@ async def test_show_menu(hass: HomeAssistant, manager, menu_options) -> None: async def test_find_flows_by_init_data_type( - manager: data_entry_flow.FlowManager, + manager: data_entry_flow.BaseFlowManager, ) -> None: """Test we can find flows by init data type.""" From bfdc640e04c1b9c9009351a6ae88e59cca00a62d Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Thu, 29 Feb 2024 16:54:02 +0100 Subject: [PATCH 0048/1691] Remove unused translation key from Ecovacs mower (#111754) * Remove unused translation key * Update snapshots --- homeassistant/components/ecovacs/lawn_mower.py | 4 +--- tests/components/ecovacs/snapshots/test_lawn_mower.ambr | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/ecovacs/lawn_mower.py b/homeassistant/components/ecovacs/lawn_mower.py index e33e87bc5fb..d8b42105ae2 100644 --- a/homeassistant/components/ecovacs/lawn_mower.py +++ b/homeassistant/components/ecovacs/lawn_mower.py @@ -62,9 +62,7 @@ class EcovacsMower( | LawnMowerEntityFeature.START_MOWING ) - entity_description = LawnMowerEntityEntityDescription( - key="mower", translation_key="mower", name=None - ) + entity_description = LawnMowerEntityEntityDescription(key="mower", name=None) def __init__(self, device: Device[MowerCapabilities]) -> None: """Initialize the mower.""" diff --git a/tests/components/ecovacs/snapshots/test_lawn_mower.ambr b/tests/components/ecovacs/snapshots/test_lawn_mower.ambr index 9446bb805ac..29c710a5cb7 100644 --- a/tests/components/ecovacs/snapshots/test_lawn_mower.ambr +++ b/tests/components/ecovacs/snapshots/test_lawn_mower.ambr @@ -27,7 +27,7 @@ 'platform': 'ecovacs', 'previous_unique_id': None, 'supported_features': , - 'translation_key': 'mower', + 'translation_key': None, 'unique_id': '8516fbb1-17f1-4194-0000000_mower', 'unit_of_measurement': None, }) @@ -60,7 +60,7 @@ 'platform': 'ecovacs', 'previous_unique_id': None, 'supported_features': , - 'translation_key': 'mower', + 'translation_key': None, 'unique_id': '8516fbb1-17f1-4194-0000000_mower', 'unit_of_measurement': None, }) From 6e6d3e7565e8141b3a419cf651f82c7c3f89bc07 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 17:00:15 +0100 Subject: [PATCH 0049/1691] Add icon translations to Laundrify (#111856) --- homeassistant/components/laundrify/binary_sensor.py | 2 +- homeassistant/components/laundrify/icons.json | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/laundrify/icons.json diff --git a/homeassistant/components/laundrify/binary_sensor.py b/homeassistant/components/laundrify/binary_sensor.py index 5cca6870b6c..099575f226f 100644 --- a/homeassistant/components/laundrify/binary_sensor.py +++ b/homeassistant/components/laundrify/binary_sensor.py @@ -40,10 +40,10 @@ class LaundrifyPowerPlug( """Representation of a laundrify Power Plug.""" _attr_device_class = BinarySensorDeviceClass.RUNNING - _attr_icon = "mdi:washing-machine" _attr_unique_id: str _attr_has_entity_name = True _attr_name = None + _attr_translation_key = "laundrify_power_plug" def __init__( self, coordinator: LaundrifyUpdateCoordinator, device: LaundrifyDevice diff --git a/homeassistant/components/laundrify/icons.json b/homeassistant/components/laundrify/icons.json new file mode 100644 index 00000000000..370adb1f953 --- /dev/null +++ b/homeassistant/components/laundrify/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "binary_sensor": { + "laundrify_power_plug": { + "default": "mdi:washing-machine" + } + } + } +} From 36a5c71dc77fc317f1b1359f05c072e3e58d2c4e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 17:01:09 +0100 Subject: [PATCH 0050/1691] Add icon translations to Lastfm (#111855) * Add icon translations to Lastfm * Add icon translations to Lastfm --- homeassistant/components/lastfm/icons.json | 9 +++++++++ homeassistant/components/lastfm/sensor.py | 2 +- tests/components/lastfm/snapshots/test_sensor.ambr | 2 -- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/lastfm/icons.json diff --git a/homeassistant/components/lastfm/icons.json b/homeassistant/components/lastfm/icons.json new file mode 100644 index 00000000000..fe453dc53c2 --- /dev/null +++ b/homeassistant/components/lastfm/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "lastfm": { + "default": "mdi:radio-fm" + } + } + } +} diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index 2b022a00107..045fac0c727 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -43,7 +43,7 @@ class LastFmSensor(CoordinatorEntity[LastFMDataUpdateCoordinator], SensorEntity) """A class for the Last.fm account.""" _attr_attribution = "Data provided by Last.fm" - _attr_icon = "mdi:radio-fm" + _attr_translation_key = "lastfm" _attr_has_entity_name = True _attr_name = None diff --git a/tests/components/lastfm/snapshots/test_sensor.ambr b/tests/components/lastfm/snapshots/test_sensor.ambr index 30ad40df428..33f9b7a56fa 100644 --- a/tests/components/lastfm/snapshots/test_sensor.ambr +++ b/tests/components/lastfm/snapshots/test_sensor.ambr @@ -5,7 +5,6 @@ 'attribution': 'Data provided by Last.fm', 'entity_picture': 'image', 'friendly_name': 'LastFM testaccount1', - 'icon': 'mdi:radio-fm', 'last_played': 'artist - title', 'play_count': 1, 'top_played': 'artist - title', @@ -23,7 +22,6 @@ 'attribution': 'Data provided by Last.fm', 'entity_picture': 'image', 'friendly_name': 'LastFM testaccount1', - 'icon': 'mdi:radio-fm', 'last_played': None, 'play_count': 0, 'top_played': None, From 0d85e316a297559187b578dd25def041c0c1a6c0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 17:19:56 +0100 Subject: [PATCH 0051/1691] Add icon translations to Justnimbus (#111849) --- .../components/justnimbus/icons.json | 27 +++++++++++++++++++ homeassistant/components/justnimbus/sensor.py | 7 ----- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/justnimbus/icons.json diff --git a/homeassistant/components/justnimbus/icons.json b/homeassistant/components/justnimbus/icons.json new file mode 100644 index 00000000000..ed2ea39d08b --- /dev/null +++ b/homeassistant/components/justnimbus/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "sensor": { + "pump_pressure": { + "default": "mdi:water-pump" + }, + "reservoir_temperature": { + "default": "mdi:coolant-temperature" + }, + "reservoir_content": { + "default": "mdi:car-coolant-level" + }, + "water_saved": { + "default": "mdi:water-opacity" + }, + "water_used": { + "default": "mdi:chart-donut" + }, + "reservoir_capacity": { + "default": "mdi:waves" + }, + "pump_type": { + "default": "mdi:pump" + } + } + } +} diff --git a/homeassistant/components/justnimbus/sensor.py b/homeassistant/components/justnimbus/sensor.py index 14b89b6c2c1..6a570a56003 100644 --- a/homeassistant/components/justnimbus/sensor.py +++ b/homeassistant/components/justnimbus/sensor.py @@ -46,7 +46,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="pump_pressure", translation_key="pump_pressure", - icon="mdi:water-pump", native_unit_of_measurement=UnitOfPressure.BAR, device_class=SensorDeviceClass.PRESSURE, state_class=SensorStateClass.MEASUREMENT, @@ -56,7 +55,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="reservoir_temp", translation_key="reservoir_temperature", - icon="mdi:coolant-temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -66,7 +64,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="reservoir_content", translation_key="reservoir_content", - icon="mdi:car-coolant-level", native_unit_of_measurement=UnitOfVolume.LITERS, device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.TOTAL, @@ -76,7 +73,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="water_saved", translation_key="water_saved", - icon="mdi:water-opacity", native_unit_of_measurement=UnitOfVolume.LITERS, device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.TOTAL_INCREASING, @@ -86,7 +82,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="water_used", translation_key="water_used", - icon="mdi:chart-donut", native_unit_of_measurement=UnitOfVolume.LITERS, device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.TOTAL_INCREASING, @@ -96,7 +91,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="reservoir_capacity", translation_key="reservoir_capacity", - icon="mdi:waves", native_unit_of_measurement=UnitOfVolume.LITERS, device_class=SensorDeviceClass.VOLUME, state_class=SensorStateClass.TOTAL, @@ -106,7 +100,6 @@ SENSOR_TYPES = ( JustNimbusEntityDescription( key="pump_type", translation_key="pump_type", - icon="mdi:pump", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda coordinator: coordinator.data.pump_type, ), From 87632dcb6a8fcded9bfe7bf993957e62841f626b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 17:20:12 +0100 Subject: [PATCH 0052/1691] Add icon translations to Kaleidescape (#111850) --- .../components/kaleidescape/icons.json | 54 +++++++++++++++++++ .../components/kaleidescape/sensor.py | 16 ------ 2 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/kaleidescape/icons.json diff --git a/homeassistant/components/kaleidescape/icons.json b/homeassistant/components/kaleidescape/icons.json new file mode 100644 index 00000000000..2a9408b6843 --- /dev/null +++ b/homeassistant/components/kaleidescape/icons.json @@ -0,0 +1,54 @@ +{ + "entity": { + "sensor": { + "media_location": { + "default": "mdi:monitor" + }, + "play_status": { + "default": "mdi:monitor" + }, + "play_speed": { + "default": "mdi:monitor" + }, + "video_mode": { + "default": "mdi:monitor-screenshot" + }, + "video_color_eotf": { + "default": "mdi:monitor-eye" + }, + "video_color_space": { + "default": "mdi:monitor-eye" + }, + "video_color_depth": { + "default": "mdi:monitor-eye" + }, + "video_color_sampling": { + "default": "mdi:monitor-eye" + }, + "screen_mask_ratio": { + "default": "mdi:monitor-screenshot" + }, + "screen_mask_top_trim_rel": { + "default": "mdi:monitor-screenshot" + }, + "screen_mask_bottom_trim_rel": { + "default": "mdi:monitor-screenshot" + }, + "screen_mask_conservative_ratio": { + "default": "mdi:monitor-screenshot" + }, + "screen_mask_top_mask_abs": { + "default": "mdi:monitor-screenshot" + }, + "screen_mask_bottom_mask_abs": { + "default": "mdi:monitor-screenshot" + }, + "cinemascape_mask": { + "default": "mdi:monitor-star" + }, + "cinemascape_mode": { + "default": "mdi:monitor-star" + } + } + } +} diff --git a/homeassistant/components/kaleidescape/sensor.py b/homeassistant/components/kaleidescape/sensor.py index ba9eaca1e95..1871238177c 100644 --- a/homeassistant/components/kaleidescape/sensor.py +++ b/homeassistant/components/kaleidescape/sensor.py @@ -40,67 +40,57 @@ SENSOR_TYPES: tuple[KaleidescapeSensorEntityDescription, ...] = ( KaleidescapeSensorEntityDescription( key="media_location", translation_key="media_location", - icon="mdi:monitor", value_fn=lambda device: device.automation.movie_location, ), KaleidescapeSensorEntityDescription( key="play_status", translation_key="play_status", - icon="mdi:monitor", value_fn=lambda device: device.movie.play_status, ), KaleidescapeSensorEntityDescription( key="play_speed", translation_key="play_speed", - icon="mdi:monitor", value_fn=lambda device: device.movie.play_speed, ), KaleidescapeSensorEntityDescription( key="video_mode", translation_key="video_mode", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.video_mode, ), KaleidescapeSensorEntityDescription( key="video_color_eotf", translation_key="video_color_eotf", - icon="mdi:monitor-eye", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.video_color_eotf, ), KaleidescapeSensorEntityDescription( key="video_color_space", translation_key="video_color_space", - icon="mdi:monitor-eye", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.video_color_space, ), KaleidescapeSensorEntityDescription( key="video_color_depth", translation_key="video_color_depth", - icon="mdi:monitor-eye", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.video_color_depth, ), KaleidescapeSensorEntityDescription( key="video_color_sampling", translation_key="video_color_sampling", - icon="mdi:monitor-eye", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.video_color_sampling, ), KaleidescapeSensorEntityDescription( key="screen_mask_ratio", translation_key="screen_mask_ratio", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.screen_mask_ratio, ), KaleidescapeSensorEntityDescription( key="screen_mask_top_trim_rel", translation_key="screen_mask_top_trim_rel", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, value_fn=lambda device: device.automation.screen_mask_top_trim_rel / 10.0, @@ -108,7 +98,6 @@ SENSOR_TYPES: tuple[KaleidescapeSensorEntityDescription, ...] = ( KaleidescapeSensorEntityDescription( key="screen_mask_bottom_trim_rel", translation_key="screen_mask_bottom_trim_rel", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, value_fn=lambda device: device.automation.screen_mask_bottom_trim_rel / 10.0, @@ -116,14 +105,12 @@ SENSOR_TYPES: tuple[KaleidescapeSensorEntityDescription, ...] = ( KaleidescapeSensorEntityDescription( key="screen_mask_conservative_ratio", translation_key="screen_mask_conservative_ratio", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.screen_mask_conservative_ratio, ), KaleidescapeSensorEntityDescription( key="screen_mask_top_mask_abs", translation_key="screen_mask_top_mask_abs", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, value_fn=lambda device: device.automation.screen_mask_top_mask_abs / 10.0, @@ -131,7 +118,6 @@ SENSOR_TYPES: tuple[KaleidescapeSensorEntityDescription, ...] = ( KaleidescapeSensorEntityDescription( key="screen_mask_bottom_mask_abs", translation_key="screen_mask_bottom_mask_abs", - icon="mdi:monitor-screenshot", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, value_fn=lambda device: device.automation.screen_mask_bottom_mask_abs / 10.0, @@ -139,14 +125,12 @@ SENSOR_TYPES: tuple[KaleidescapeSensorEntityDescription, ...] = ( KaleidescapeSensorEntityDescription( key="cinemascape_mask", translation_key="cinemascape_mask", - icon="mdi:monitor-star", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.cinemascape_mask, ), KaleidescapeSensorEntityDescription( key="cinemascape_mode", translation_key="cinemascape_mode", - icon="mdi:monitor-star", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.automation.cinemascape_mode, ), From eaff66477d131c0c0ab5e0eee1d638d0e2ba4af2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 17:21:00 +0100 Subject: [PATCH 0053/1691] Add icon translations to Intellifire (#111845) --- .../components/intellifire/binary_sensor.py | 6 --- .../components/intellifire/icons.json | 45 +++++++++++++++++++ .../components/intellifire/number.py | 1 - .../components/intellifire/sensor.py | 3 -- .../components/intellifire/switch.py | 1 - 5 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/intellifire/icons.json diff --git a/homeassistant/components/intellifire/binary_sensor.py b/homeassistant/components/intellifire/binary_sensor.py index 503b97f183d..b630828d7e9 100644 --- a/homeassistant/components/intellifire/binary_sensor.py +++ b/homeassistant/components/intellifire/binary_sensor.py @@ -39,25 +39,21 @@ INTELLIFIRE_BINARY_SENSORS: tuple[IntellifireBinarySensorEntityDescription, ...] IntellifireBinarySensorEntityDescription( key="on_off", # This is the sensor name translation_key="flame", # This is the translation key - icon="mdi:fire", value_fn=lambda data: data.is_on, ), IntellifireBinarySensorEntityDescription( key="timer_on", translation_key="timer_on", - icon="mdi:camera-timer", value_fn=lambda data: data.timer_on, ), IntellifireBinarySensorEntityDescription( key="pilot_light_on", translation_key="pilot_light_on", - icon="mdi:fire-alert", value_fn=lambda data: data.pilot_on, ), IntellifireBinarySensorEntityDescription( key="thermostat_on", translation_key="thermostat_on", - icon="mdi:home-thermometer-outline", value_fn=lambda data: data.thermostat_on, ), IntellifireBinarySensorEntityDescription( @@ -77,7 +73,6 @@ INTELLIFIRE_BINARY_SENSORS: tuple[IntellifireBinarySensorEntityDescription, ...] IntellifireBinarySensorEntityDescription( key="error_fan_delay", translation_key="fan_delay_error", - icon="mdi:fan-alert", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda data: data.error_fan_delay, device_class=BinarySensorDeviceClass.PROBLEM, @@ -99,7 +94,6 @@ INTELLIFIRE_BINARY_SENSORS: tuple[IntellifireBinarySensorEntityDescription, ...] IntellifireBinarySensorEntityDescription( key="error_fan", translation_key="fan_error", - icon="mdi:fan-alert", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda data: data.error_fan, device_class=BinarySensorDeviceClass.PROBLEM, diff --git a/homeassistant/components/intellifire/icons.json b/homeassistant/components/intellifire/icons.json new file mode 100644 index 00000000000..6dca69484b6 --- /dev/null +++ b/homeassistant/components/intellifire/icons.json @@ -0,0 +1,45 @@ +{ + "entity": { + "binary_sensor": { + "flame": { + "default": "mdi:fire" + }, + "timer_on": { + "default": "mdi:camera-timer" + }, + "pilot_light_on": { + "default": "mdi:fire-alert" + }, + "thermostat_on": { + "default": "mdi:home-thermometer-outline" + }, + "fan_delay_error": { + "default": "mdi:fan-alert" + }, + "fan_error": { + "default": "mdi:fan-alert" + } + }, + "number": { + "flame_control": { + "default": "mdi:arrow-expand-vertical" + } + }, + "sensor": { + "flame_height": { + "default": "mdi:fire-circle" + }, + "fan_speed": { + "default": "mdi:fan" + }, + "timer_end_timestamp": { + "default": "mdi:timer-sand" + } + }, + "switch": { + "pilot_light": { + "default": "mdi:fire-alert" + } + } + } +} diff --git a/homeassistant/components/intellifire/number.py b/homeassistant/components/intellifire/number.py index efcafd2acd8..4cedb3de5d9 100644 --- a/homeassistant/components/intellifire/number.py +++ b/homeassistant/components/intellifire/number.py @@ -28,7 +28,6 @@ async def async_setup_entry( description = NumberEntityDescription( key="flame_control", translation_key="flame_control", - icon="mdi:arrow-expand-vertical", ) async_add_entities( diff --git a/homeassistant/components/intellifire/sensor.py b/homeassistant/components/intellifire/sensor.py index c974378fb71..583e2dc22f9 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -57,7 +57,6 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( IntellifireSensorEntityDescription( key="flame_height", translation_key="flame_height", - icon="mdi:fire-circle", state_class=SensorStateClass.MEASUREMENT, # UI uses 1-5 for flame height, backing lib uses 0-4 value_fn=lambda data: (data.flameheight + 1), @@ -80,14 +79,12 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( IntellifireSensorEntityDescription( key="fan_speed", translation_key="fan_speed", - icon="mdi:fan", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data.fanspeed, ), IntellifireSensorEntityDescription( key="timer_end_timestamp", translation_key="timer_end_timestamp", - icon="mdi:timer-sand", state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TIMESTAMP, value_fn=_time_remaining_to_timestamp, diff --git a/homeassistant/components/intellifire/switch.py b/homeassistant/components/intellifire/switch.py index 03e3a2be0a2..161d75de2d8 100644 --- a/homeassistant/components/intellifire/switch.py +++ b/homeassistant/components/intellifire/switch.py @@ -45,7 +45,6 @@ INTELLIFIRE_SWITCHES: tuple[IntellifireSwitchEntityDescription, ...] = ( IntellifireSwitchEntityDescription( key="pilot", translation_key="pilot_light", - icon="mdi:fire-alert", on_fn=lambda control_api: control_api.pilot_on(), off_fn=lambda control_api: control_api.pilot_off(), value_fn=lambda data: data.pilot_on, From 66b17a8e0dd5228fc2121eb9fa1f891b06e06762 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 29 Feb 2024 18:05:22 +0100 Subject: [PATCH 0054/1691] Update frontend to 20240228.1 (#111859) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 7e24b8d0880..3bbee2eae58 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240228.0"] + "requirements": ["home-assistant-frontend==20240228.1"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 976c6c514f5..c1f80804b5c 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.1 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240228.0 +home-assistant-frontend==20240228.1 home-assistant-intents==2024.2.28 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index a0c3ea77efa..1257caa2ec9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ hole==0.8.0 holidays==0.43 # homeassistant.components.frontend -home-assistant-frontend==20240228.0 +home-assistant-frontend==20240228.1 # homeassistant.components.conversation home-assistant-intents==2024.2.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 692e4d397a8..d93e5dcdd27 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ hole==0.8.0 holidays==0.43 # homeassistant.components.frontend -home-assistant-frontend==20240228.0 +home-assistant-frontend==20240228.1 # homeassistant.components.conversation home-assistant-intents==2024.2.28 From f0deae319e7f820498b7be3135c439b785f401c4 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 29 Feb 2024 12:09:38 -0600 Subject: [PATCH 0055/1691] Use correct service name with Wyoming satellite + local wake word detection (#111870) * Use correct service name with satellite + local wake word detection * Don't load platforms for satellite services * Update homeassistant/components/wyoming/data.py Co-authored-by: Paulus Schoutsen * Fix ruff error --------- Co-authored-by: Paulus Schoutsen --- homeassistant/components/wyoming/data.py | 33 +++++++++++++----------- tests/components/wyoming/test_data.py | 31 +++++++++++++++++++--- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/wyoming/data.py b/homeassistant/components/wyoming/data.py index adcb472d5e0..e333a740741 100644 --- a/homeassistant/components/wyoming/data.py +++ b/homeassistant/components/wyoming/data.py @@ -1,10 +1,11 @@ """Base class for Wyoming providers.""" + from __future__ import annotations import asyncio from wyoming.client import AsyncTcpClient -from wyoming.info import Describe, Info, Satellite +from wyoming.info import Describe, Info from homeassistant.const import Platform @@ -23,14 +24,19 @@ class WyomingService: self.host = host self.port = port self.info = info - platforms = [] + self.platforms = [] + + if (self.info.satellite is not None) and self.info.satellite.installed: + # Don't load platforms for satellite services, such as local wake + # word detection. + return + if any(asr.installed for asr in info.asr): - platforms.append(Platform.STT) + self.platforms.append(Platform.STT) if any(tts.installed for tts in info.tts): - platforms.append(Platform.TTS) + self.platforms.append(Platform.TTS) if any(wake.installed for wake in info.wake): - platforms.append(Platform.WAKE_WORD) - self.platforms = platforms + self.platforms.append(Platform.WAKE_WORD) def has_services(self) -> bool: """Return True if services are installed that Home Assistant can use.""" @@ -43,6 +49,12 @@ class WyomingService: def get_name(self) -> str | None: """Return name of first installed usable service.""" + + # Wyoming satellite + # Must be checked first because satellites may contain wake services, etc. + if (self.info.satellite is not None) and self.info.satellite.installed: + return self.info.satellite.name + # ASR = automated speech recognition (speech-to-text) asr_installed = [asr for asr in self.info.asr if asr.installed] if asr_installed: @@ -58,15 +70,6 @@ class WyomingService: if wake_installed: return wake_installed[0].name - # satellite - satellite_installed: Satellite | None = None - - if (self.info.satellite is not None) and self.info.satellite.installed: - satellite_installed = self.info.satellite - - if satellite_installed: - return satellite_installed.name - return None @classmethod diff --git a/tests/components/wyoming/test_data.py b/tests/components/wyoming/test_data.py index b7de9dbfdc1..282326b2ce0 100644 --- a/tests/components/wyoming/test_data.py +++ b/tests/components/wyoming/test_data.py @@ -1,9 +1,11 @@ """Test tts.""" + from __future__ import annotations from unittest.mock import patch from syrupy.assertion import SnapshotAssertion +from wyoming.info import Info from homeassistant.components.wyoming.data import WyomingService, load_wyoming_info from homeassistant.core import HomeAssistant @@ -27,10 +29,13 @@ async def test_load_info_oserror(hass: HomeAssistant) -> None: """Test loading info and error raising.""" mock_client = MockAsyncTcpClient([STT_INFO.event()]) - with patch( - "homeassistant.components.wyoming.data.AsyncTcpClient", - mock_client, - ), patch.object(mock_client, "read_event", side_effect=OSError("Boom!")): + with ( + patch( + "homeassistant.components.wyoming.data.AsyncTcpClient", + mock_client, + ), + patch.object(mock_client, "read_event", side_effect=OSError("Boom!")), + ): info = await load_wyoming_info( "localhost", 1234, @@ -75,3 +80,21 @@ async def test_service_name(hass: HomeAssistant) -> None: service = await WyomingService.create("localhost", 1234) assert service is not None assert service.get_name() == SATELLITE_INFO.satellite.name + + +async def test_satellite_with_wake_word(hass: HomeAssistant) -> None: + """Test that wake word info with satellite doesn't overwrite the service name.""" + # Info for local wake word detection + satellite_info = Info( + satellite=SATELLITE_INFO.satellite, + wake=WAKE_WORD_INFO.wake, + ) + + with patch( + "homeassistant.components.wyoming.data.AsyncTcpClient", + MockAsyncTcpClient([satellite_info.event()]), + ): + service = await WyomingService.create("localhost", 1234) + assert service is not None + assert service.get_name() == satellite_info.satellite.name + assert not service.platforms From cac2c46a425453da012ae558cd744ef9c87712a4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 29 Feb 2024 19:12:05 +0100 Subject: [PATCH 0056/1691] Add icon translations to KNX (#111852) * Add icon translations to KNX * Update homeassistant/components/knx/icons.json Co-authored-by: Matthias Alphart * Update homeassistant/components/knx/icons.json Co-authored-by: Matthias Alphart * Update homeassistant/components/knx/icons.json Co-authored-by: Matthias Alphart * Update homeassistant/components/knx/icons.json Co-authored-by: Matthias Alphart * Update homeassistant/components/knx/icons.json Co-authored-by: Matthias Alphart --------- Co-authored-by: Matthias Alphart --- homeassistant/components/knx/icons.json | 31 +++++++++++++++++++++++++ homeassistant/components/knx/sensor.py | 6 ----- 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/knx/icons.json diff --git a/homeassistant/components/knx/icons.json b/homeassistant/components/knx/icons.json new file mode 100644 index 00000000000..736923375ee --- /dev/null +++ b/homeassistant/components/knx/icons.json @@ -0,0 +1,31 @@ +{ + "entity": { + "sensor": { + "individual_address": { + "default": "mdi:router-network" + }, + "telegrams_incoming": { + "default": "mdi:upload-network" + }, + "telegrams_incoming_error": { + "default": "mdi:help-network" + }, + "telegrams_outgoing": { + "default": "mdi:download-network" + }, + "telegrams_outgoing_error": { + "default": "mdi:close-network" + }, + "telegram_count": { + "default": "mdi:plus-network" + } + } + }, + "services": { + "send": "mdi:email-arrow-right", + "read": "mdi:email-search", + "event_register": "mdi:home-import-outline", + "exposure_register": "mdi:home-export-outline", + "reload": "mdi:reload" + } +} diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index 2f09f7e8ed6..be6359c783d 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -55,7 +55,6 @@ SYSTEM_ENTITY_DESCRIPTIONS = ( KNXSystemEntityDescription( key="individual_address", always_available=False, - icon="mdi:router-network", should_poll=False, value_fn=lambda knx: str(knx.xknx.current_address), ), @@ -76,7 +75,6 @@ SYSTEM_ENTITY_DESCRIPTIONS = ( ), KNXSystemEntityDescription( key="telegrams_incoming", - icon="mdi:upload-network", entity_registry_enabled_default=False, force_update=True, state_class=SensorStateClass.TOTAL_INCREASING, @@ -84,13 +82,11 @@ SYSTEM_ENTITY_DESCRIPTIONS = ( ), KNXSystemEntityDescription( key="telegrams_incoming_error", - icon="mdi:help-network", state_class=SensorStateClass.TOTAL_INCREASING, value_fn=lambda knx: knx.xknx.connection_manager.cemi_count_incoming_error, ), KNXSystemEntityDescription( key="telegrams_outgoing", - icon="mdi:download-network", entity_registry_enabled_default=False, force_update=True, state_class=SensorStateClass.TOTAL_INCREASING, @@ -98,13 +94,11 @@ SYSTEM_ENTITY_DESCRIPTIONS = ( ), KNXSystemEntityDescription( key="telegrams_outgoing_error", - icon="mdi:close-network", state_class=SensorStateClass.TOTAL_INCREASING, value_fn=lambda knx: knx.xknx.connection_manager.cemi_count_outgoing_error, ), KNXSystemEntityDescription( key="telegram_count", - icon="mdi:plus-network", force_update=True, state_class=SensorStateClass.TOTAL_INCREASING, value_fn=lambda knx: knx.xknx.connection_manager.cemi_count_outgoing From af625a6696badf72e7f5de64339fda6c053f748c Mon Sep 17 00:00:00 2001 From: dotvav Date: Thu, 29 Feb 2024 19:18:59 +0100 Subject: [PATCH 0057/1691] Support HitachiAirToAirHeatPump (ovp:HLinkMainController) in Overkiz integration (#102159) * Support OVP devices Support OVP devices * Fix coding style * Fix coding style and unnecessary constants * Move fanmodes inside class * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker * Remove duplicate widget * Update homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py Co-authored-by: Joost Lekkerkerker * Format ruff * Fix mypy --------- Co-authored-by: Mick Vleeshouwer Co-authored-by: Joost Lekkerkerker --- .../overkiz/climate_entities/__init__.py | 2 + .../hitachi_air_to_air_heat_pump_ovp.py | 357 ++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py index 331823c594a..72230c99a05 100644 --- a/homeassistant/components/overkiz/climate_entities/__init__.py +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -14,6 +14,7 @@ from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone from .atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl from .atlantic_pass_apc_zone_control_zone import AtlanticPassAPCZoneControlZone from .hitachi_air_to_air_heat_pump_hlrrwifi import HitachiAirToAirHeatPumpHLRRWIFI +from .hitachi_air_to_air_heat_pump_ovp import HitachiAirToAirHeatPumpOVP from .somfy_heating_temperature_interface import SomfyHeatingTemperatureInterface from .somfy_thermostat import SomfyThermostat from .valve_heating_temperature_interface import ValveHeatingTemperatureInterface @@ -56,5 +57,6 @@ WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY = { WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY = { UIWidget.HITACHI_AIR_TO_AIR_HEAT_PUMP: { Protocol.HLRR_WIFI: HitachiAirToAirHeatPumpHLRRWIFI, + Protocol.OVP: HitachiAirToAirHeatPumpOVP, }, } diff --git a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py new file mode 100644 index 00000000000..bf6bb5f95d5 --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py @@ -0,0 +1,357 @@ +"""Support for HitachiAirToAirHeatPump.""" +from __future__ import annotations + +from typing import Any + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.climate import ( + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + PRESET_NONE, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, + SWING_VERTICAL, + ClimateEntity, + ClimateEntityFeature, + HVACMode, +) +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature + +from ..const import DOMAIN +from ..coordinator import OverkizDataUpdateCoordinator +from ..entity import OverkizEntity + +PRESET_HOLIDAY_MODE = "holiday_mode" +FAN_SILENT = "silent" +TEMP_MIN = 16 +TEMP_MAX = 32 +TEMP_AUTO_MIN = 22 +TEMP_AUTO_MAX = 28 +AUTO_PIVOT_TEMPERATURE = 25 +AUTO_TEMPERATURE_CHANGE_MIN = TEMP_AUTO_MIN - AUTO_PIVOT_TEMPERATURE +AUTO_TEMPERATURE_CHANGE_MAX = TEMP_AUTO_MAX - AUTO_PIVOT_TEMPERATURE + +OVERKIZ_TO_HVAC_MODES: dict[str, HVACMode] = { + OverkizCommandParam.AUTOHEATING: HVACMode.AUTO, + OverkizCommandParam.AUTOCOOLING: HVACMode.AUTO, + OverkizCommandParam.ON: HVACMode.HEAT, + OverkizCommandParam.OFF: HVACMode.OFF, + OverkizCommandParam.HEATING: HVACMode.HEAT, + OverkizCommandParam.FAN: HVACMode.FAN_ONLY, + OverkizCommandParam.DEHUMIDIFY: HVACMode.DRY, + OverkizCommandParam.COOLING: HVACMode.COOL, +} + +HVAC_MODES_TO_OVERKIZ: dict[HVACMode, str] = { + HVACMode.AUTO: OverkizCommandParam.AUTO, + HVACMode.HEAT: OverkizCommandParam.HEATING, + HVACMode.OFF: OverkizCommandParam.HEATING, + HVACMode.FAN_ONLY: OverkizCommandParam.FAN, + HVACMode.DRY: OverkizCommandParam.DEHUMIDIFY, + HVACMode.COOL: OverkizCommandParam.COOLING, +} + +OVERKIZ_TO_SWING_MODES: dict[str, str] = { + OverkizCommandParam.BOTH: SWING_BOTH, + OverkizCommandParam.HORIZONTAL: SWING_HORIZONTAL, + OverkizCommandParam.STOP: SWING_OFF, + OverkizCommandParam.VERTICAL: SWING_VERTICAL, +} + +SWING_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_SWING_MODES.items()} + +OVERKIZ_TO_FAN_MODES: dict[str, str] = { + OverkizCommandParam.AUTO: FAN_AUTO, + OverkizCommandParam.HIGH: FAN_HIGH, # fallback, state can be exposed as HIGH, new state = hi + OverkizCommandParam.HI: FAN_HIGH, + OverkizCommandParam.LOW: FAN_LOW, + OverkizCommandParam.LO: FAN_LOW, + OverkizCommandParam.MEDIUM: FAN_MEDIUM, # fallback, state can be exposed as MEDIUM, new state = med + OverkizCommandParam.MED: FAN_MEDIUM, + OverkizCommandParam.SILENT: OverkizCommandParam.SILENT, +} + +FAN_MODES_TO_OVERKIZ: dict[str, str] = { + FAN_AUTO: OverkizCommandParam.AUTO, + FAN_HIGH: OverkizCommandParam.HI, + FAN_LOW: OverkizCommandParam.LO, + FAN_MEDIUM: OverkizCommandParam.MED, + FAN_SILENT: OverkizCommandParam.SILENT, +} + + +class HitachiAirToAirHeatPumpOVP(OverkizEntity, ClimateEntity): + """Representation of Hitachi Air To Air HeatPump.""" + + _attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ] + _attr_fan_modes = [*FAN_MODES_TO_OVERKIZ] + _attr_preset_modes = [PRESET_NONE, PRESET_HOLIDAY_MODE] + _attr_swing_modes = [*SWING_MODES_TO_OVERKIZ] + _attr_target_temperature_step = 1.0 + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_translation_key = DOMAIN + + def __init__( + self, device_url: str, coordinator: OverkizDataUpdateCoordinator + ) -> None: + """Init method.""" + super().__init__(device_url, coordinator) + + self._attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + ) + + if self.device.states.get(OverkizState.OVP_SWING): + self._attr_supported_features |= ClimateEntityFeature.SWING_MODE + + if self._attr_device_info: + self._attr_device_info["manufacturer"] = "Hitachi" + + @property + def hvac_mode(self) -> HVACMode: + """Return hvac operation ie. heat, cool mode.""" + if ( + main_op_state := self.device.states[OverkizState.OVP_MAIN_OPERATION] + ) and main_op_state.value_as_str: + if main_op_state.value_as_str.lower() == OverkizCommandParam.OFF: + return HVACMode.OFF + + if ( + mode_change_state := self.device.states[OverkizState.OVP_MODE_CHANGE] + ) and mode_change_state.value_as_str: + # The OVP protocol has 'auto cooling' and 'auto heating' values + # that are equivalent to the HLRRWIFI protocol without spaces + sanitized_value = mode_change_state.value_as_str.replace(" ", "").lower() + return OVERKIZ_TO_HVAC_MODES[sanitized_value] + + return HVACMode.OFF + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + if hvac_mode == HVACMode.OFF: + await self._global_control(main_operation=OverkizCommandParam.OFF) + else: + await self._global_control( + main_operation=OverkizCommandParam.ON, + hvac_mode=HVAC_MODES_TO_OVERKIZ[hvac_mode], + ) + + @property + def fan_mode(self) -> str | None: + """Return the fan setting.""" + if ( + state := self.device.states[OverkizState.OVP_FAN_SPEED] + ) and state.value_as_str: + return OVERKIZ_TO_FAN_MODES[state.value_as_str] + + return None + + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set new target fan mode.""" + await self._global_control(fan_mode=FAN_MODES_TO_OVERKIZ[fan_mode]) + + @property + def swing_mode(self) -> str | None: + """Return the swing setting.""" + if (state := self.device.states[OverkizState.OVP_SWING]) and state.value_as_str: + return OVERKIZ_TO_SWING_MODES[state.value_as_str] + + return None + + async def async_set_swing_mode(self, swing_mode: str) -> None: + """Set new target swing operation.""" + await self._global_control(swing_mode=SWING_MODES_TO_OVERKIZ[swing_mode]) + + @property + def target_temperature(self) -> int | None: + """Return the target temperature.""" + if ( + temperature := self.device.states[OverkizState.CORE_TARGET_TEMPERATURE] + ) and temperature.value_as_int: + return temperature.value_as_int + + return None + + @property + def current_temperature(self) -> int | None: + """Return current temperature.""" + if ( + state := self.device.states[OverkizState.OVP_ROOM_TEMPERATURE] + ) and state.value_as_int: + return state.value_as_int + + return None + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new temperature.""" + await self._global_control(target_temperature=int(kwargs[ATTR_TEMPERATURE])) + + @property + def preset_mode(self) -> str | None: + """Return the current preset mode, e.g., home, away, temp.""" + if ( + state := self.device.states[OverkizState.CORE_HOLIDAYS_MODE] + ) and state.value_as_str: + if state.value_as_str == OverkizCommandParam.ON: + return PRESET_HOLIDAY_MODE + + if state.value_as_str == OverkizCommandParam.OFF: + return PRESET_NONE + + return None + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + if preset_mode == PRESET_HOLIDAY_MODE: + await self.executor.async_execute_command( + OverkizCommand.SET_HOLIDAYS, + OverkizCommandParam.ON, + ) + if preset_mode == PRESET_NONE: + await self.executor.async_execute_command( + OverkizCommand.SET_HOLIDAYS, + OverkizCommandParam.OFF, + ) + + # OVP has this property to control the unit's timer mode + @property + def auto_manu_mode(self) -> str | None: + """Return auto/manu mode.""" + if ( + state := self.device.states[OverkizState.CORE_AUTO_MANU_MODE] + ) and state.value_as_str: + return state.value_as_str + return None + + # OVP has this property to control the target temperature delta in auto mode + @property + def temperature_change(self) -> int | None: + """Return temperature change state.""" + if ( + state := self.device.states[OverkizState.OVP_TEMPERATURE_CHANGE] + ) and state.value_as_int: + return state.value_as_int + + return None + + @property + def min_temp(self) -> float: + """Return the minimum temperature.""" + if self.hvac_mode == HVACMode.AUTO: + return TEMP_AUTO_MIN + return TEMP_MIN + + @property + def max_temp(self) -> float: + """Return the maximum temperature.""" + if self.hvac_mode == HVACMode.AUTO: + return TEMP_AUTO_MAX + return TEMP_MAX + + def _control_backfill( + self, value: str | None, state_name: str, fallback_value: str + ) -> str: + """Return a parameter value which will be accepted in a command by Overkiz. + + Overkiz doesn't accept commands with undefined parameters. This function + is guaranteed to return a `str` which is the provided `value` if set, or + the current device state if set, or the provided `fallback_value` otherwise. + """ + if value: + return value + if (state := self.device.states[state_name]) is not None and ( + value := state.value_as_str + ) is not None: + return value + return fallback_value + + async def _global_control( + self, + main_operation: str | None = None, + target_temperature: int | None = None, + fan_mode: str | None = None, + hvac_mode: str | None = None, + swing_mode: str | None = None, + leave_home: str | None = None, + ) -> None: + """Execute globalControl command with all parameters. + + There is no option to only set a single parameter, without passing + all other values. + """ + + main_operation = self._control_backfill( + main_operation, OverkizState.OVP_MAIN_OPERATION, OverkizCommandParam.ON + ) + fan_mode = self._control_backfill( + fan_mode, + OverkizState.OVP_FAN_SPEED, + OverkizCommandParam.AUTO, + ) + hvac_mode = self._control_backfill( + hvac_mode, + OverkizState.OVP_MODE_CHANGE, + OverkizCommandParam.AUTO, + ).lower() # Overkiz returns uppercase states that are not acceptable commands + if hvac_mode.replace(" ", "") in [ + # Overkiz returns compound states like 'auto cooling' or 'autoHeating' + # that are not valid commands and need to be mapped to 'auto' + OverkizCommandParam.AUTOCOOLING, + OverkizCommandParam.AUTOHEATING, + ]: + hvac_mode = OverkizCommandParam.AUTO + + swing_mode = self._control_backfill( + swing_mode, + OverkizState.OVP_SWING, + OverkizCommandParam.STOP, + ) + + # AUTO_MANU parameter is not controlled by HA and is turned "off" when the device is on Holiday mode + auto_manu_mode = self._control_backfill( + None, OverkizState.CORE_AUTO_MANU_MODE, OverkizCommandParam.MANU + ) + if self.preset_mode == PRESET_HOLIDAY_MODE: + auto_manu_mode = OverkizCommandParam.OFF + + # In all the hvac modes except AUTO, the temperature command parameter is the target temperature + temperature_command = None + target_temperature = target_temperature or self.target_temperature + if hvac_mode == OverkizCommandParam.AUTO: + # In hvac mode AUTO, the temperature command parameter is a temperature_change + # which is the delta between a pivot temperature (25) and the target temperature + temperature_change = 0 + + if target_temperature: + temperature_change = target_temperature - AUTO_PIVOT_TEMPERATURE + elif self.temperature_change: + temperature_change = self.temperature_change + + # Keep temperature_change in the API accepted range + temperature_change = min( + max(temperature_change, AUTO_TEMPERATURE_CHANGE_MIN), + AUTO_TEMPERATURE_CHANGE_MAX, + ) + + temperature_command = temperature_change + else: + # In other modes, the temperature command is the target temperature + temperature_command = target_temperature + + command_data = [ + main_operation, # Main Operation + temperature_command, # Temperature Command + fan_mode, # Fan Mode + hvac_mode, # Mode + auto_manu_mode, # Auto Manu Mode + ] + + await self.executor.async_execute_command( + OverkizCommand.GLOBAL_CONTROL, command_data + ) From ba4120d779c0acea285e89af04af8f76c3d9262f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 09:01:03 -1000 Subject: [PATCH 0058/1691] Fallback to event loop import on deadlock (#111868) --- homeassistant/loader.py | 32 +++++++- tests/test_loader.py | 162 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 5 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 1bbd22d4070..1ff98ff6ff2 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -852,7 +852,14 @@ class Integration: # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. if load_executor: - comp = await self.hass.async_add_executor_job(self.get_component) + try: + comp = await self.hass.async_add_executor_job(self.get_component) + except ImportError as ex: + load_executor = False + _LOGGER.debug("Failed to import %s in executor", domain, exc_info=ex) + # If importing in the executor deadlocks because there is a circular + # dependency, we fall back to the event loop. + comp = self.get_component() else: comp = self.get_component() @@ -885,6 +892,9 @@ class Integration: ) except ImportError: raise + except RuntimeError as err: + # _DeadlockError inherits from RuntimeError + raise ImportError(f"RuntimeError importing {self.pkg_path}: {err}") from err except Exception as err: _LOGGER.exception( "Unexpected exception importing component %s", self.pkg_path @@ -913,9 +923,18 @@ class Integration: ) try: if load_executor: - platform = await self.hass.async_add_executor_job( - self._load_platform, platform_name - ) + try: + platform = await self.hass.async_add_executor_job( + self._load_platform, platform_name + ) + except ImportError as ex: + _LOGGER.debug( + "Failed to import %s in executor", domain, exc_info=ex + ) + load_executor = False + # If importing in the executor deadlocks because there is a circular + # dependency, we fall back to the event loop. + platform = self._load_platform(platform_name) else: platform = self._load_platform(platform_name) import_future.set_result(platform) @@ -983,6 +1002,11 @@ class Integration: ] missing_platforms_cache[full_name] = ex raise + except RuntimeError as err: + # _DeadlockError inherits from RuntimeError + raise ImportError( + f"RuntimeError importing {self.pkg_path}.{platform_name}: {err}" + ) from err except Exception as err: _LOGGER.exception( "Unexpected exception importing platform %s.%s", diff --git a/tests/test_loader.py b/tests/test_loader.py index d173e3e8aa6..babe1abcdd2 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,6 +1,8 @@ """Test to verify that we can load components.""" import asyncio -from unittest.mock import Mock, patch +import sys +from typing import Any +from unittest.mock import MagicMock, Mock, patch import pytest @@ -1033,3 +1035,161 @@ async def test_hass_components_use_reported( "Detected that custom integration 'test_integration_frame'" " accesses hass.components.http. This is deprecated" ) in caplog.text + + +async def test_async_get_component_deadlock_fallback( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_component fallback to importing in the event loop on deadlock.""" + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + module_mock = MagicMock() + import_attempts = 0 + + def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: + nonlocal import_attempts + if module == "homeassistant.components.executor_import": + import_attempts += 1 + + if import_attempts == 1: + # _DeadlockError inherits from RuntimeError + raise RuntimeError( + "Detected deadlock trying to import homeassistant.components.executor_import" + ) + + return module_mock + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + with patch("homeassistant.loader.importlib.import_module", mock_import): + module = await executor_import_integration.async_get_component() + + assert ( + "Detected deadlock trying to import homeassistant.components.executor_import" + in caplog.text + ) + assert "loaded_executor=False" in caplog.text + assert module is module_mock + + +async def test_async_get_component_raises_after_import_failure( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_component raises if we fail to import in both the executor and loop.""" + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + module_mock = MagicMock() + import_attempts = 0 + + def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: + nonlocal import_attempts + if module == "homeassistant.components.executor_import": + import_attempts += 1 + + if import_attempts == 1: + # _DeadlockError inherits from RuntimeError + raise RuntimeError( + "Detected deadlock trying to import homeassistant.components.executor_import" + ) + + if import_attempts == 2: + raise ImportError("Failed import homeassistant.components.executor_import") + return module_mock + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + with patch( + "homeassistant.loader.importlib.import_module", mock_import + ), pytest.raises(ImportError): + await executor_import_integration.async_get_component() + + assert ( + "Detected deadlock trying to import homeassistant.components.executor_import" + in caplog.text + ) + assert "loaded_executor=False" not in caplog.text + + +async def test_async_get_platform_deadlock_fallback( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_platform fallback to importing in the event loop on deadlock.""" + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + module_mock = MagicMock() + import_attempts = 0 + + def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: + nonlocal import_attempts + if module == "homeassistant.components.executor_import.config_flow": + import_attempts += 1 + + if import_attempts == 1: + # _DeadlockError inherits from RuntimeError + raise RuntimeError( + "Detected deadlock trying to import homeassistant.components.executor_import" + ) + + return module_mock + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + with patch("homeassistant.loader.importlib.import_module", mock_import): + module = await executor_import_integration.async_get_platform("config_flow") + + assert ( + "Detected deadlock trying to import homeassistant.components.executor_import" + in caplog.text + ) + assert "loaded_executor=False" in caplog.text + assert module is module_mock + + +async def test_async_get_platform_raises_after_import_failure( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_platform raises if we fail to import in both the executor and loop.""" + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + module_mock = MagicMock() + import_attempts = 0 + + def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: + nonlocal import_attempts + if module == "homeassistant.components.executor_import.config_flow": + import_attempts += 1 + + if import_attempts == 1: + # _DeadlockError inherits from RuntimeError + raise RuntimeError( + "Detected deadlock trying to import homeassistant.components.executor_import" + ) + + if import_attempts == 2: + # _DeadlockError inherits from RuntimeError + raise ImportError( + "Error trying to import homeassistant.components.executor_import" + ) + + return module_mock + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + with patch( + "homeassistant.loader.importlib.import_module", mock_import + ), pytest.raises(ImportError): + await executor_import_integration.async_get_platform("config_flow") + + assert ( + "Detected deadlock trying to import homeassistant.components.executor_import" + in caplog.text + ) + assert "loaded_executor=False" not in caplog.text From 6fe28d376415cba6f7dce1b562421c0f8b040763 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 20:07:14 +0100 Subject: [PATCH 0059/1691] Migrate integrations a-d to generic flowhandler (#111861) --- homeassistant/components/abode/config_flow.py | 21 ++++---- .../components/accuweather/config_flow.py | 8 ++- .../components/acmeda/config_flow.py | 9 ++-- homeassistant/components/adax/config_flow.py | 11 ++-- .../components/adguard/config_flow.py | 15 +++--- .../components/advantage_air/config_flow.py | 7 ++- homeassistant/components/aemet/config_flow.py | 9 ++-- .../components/aftership/config_flow.py | 7 ++- .../components/agent_dvr/config_flow.py | 7 ++- homeassistant/components/airly/config_flow.py | 7 ++- .../components/airnow/config_flow.py | 11 ++-- homeassistant/components/airq/config_flow.py | 7 ++- .../components/airthings/config_flow.py | 7 ++- .../components/airthings_ble/config_flow.py | 9 ++-- .../components/airtouch4/config_flow.py | 4 +- .../components/airtouch5/config_flow.py | 3 +- .../components/airvisual/config_flow.py | 24 ++++----- .../components/airvisual_pro/config_flow.py | 18 ++++--- .../components/airzone/config_flow.py | 14 +++--- .../components/airzone_cloud/config_flow.py | 9 ++-- .../components/aladdin_connect/config_flow.py | 15 +++--- .../components/alarmdecoder/config_flow.py | 29 ++++++----- .../components/amberelectric/config_flow.py | 9 ++-- .../components/ambiclimate/config_flow.py | 11 ++-- .../components/ambient_station/config_flow.py | 9 ++-- .../analytics_insights/config_flow.py | 6 +-- .../android_ip_webcam/config_flow.py | 7 ++- .../components/androidtv/config_flow.py | 20 ++++---- .../androidtv_remote/config_flow.py | 20 ++++---- homeassistant/components/anova/config_flow.py | 5 +- .../components/anthemav/config_flow.py | 5 +- .../components/aosmith/config_flow.py | 13 ++--- .../components/apcupsd/config_flow.py | 5 +- .../components/apple_tv/config_flow.py | 50 +++++++++++-------- .../components/aprilaire/config_flow.py | 3 +- .../components/aranet/config_flow.py | 12 ++--- .../components/arcam_fmj/config_flow.py | 17 ++++--- .../components/aseko_pool_live/config_flow.py | 7 ++- .../components/asuswrt/config_flow.py | 11 ++-- homeassistant/components/atag/config_flow.py | 11 ++-- .../components/august/config_flow.py | 21 ++++---- .../components/aurora/config_flow.py | 9 ++-- .../aurora_abb_powerone/config_flow.py | 10 ++-- .../aussie_broadband/config_flow.py | 13 ++--- homeassistant/components/awair/config_flow.py | 21 ++++---- homeassistant/components/aws/config_flow.py | 9 ++-- homeassistant/components/axis/config_flow.py | 40 +++++++++------ .../components/azure_devops/config_flow.py | 15 +++--- .../components/azure_event_hub/config_flow.py | 17 ++++--- homeassistant/components/baf/config_flow.py | 11 ++-- .../components/balboa/config_flow.py | 9 ++-- .../components/bang_olufsen/config_flow.py | 11 ++-- .../components/blebox/config_flow.py | 9 ++-- homeassistant/components/blink/config_flow.py | 13 ++--- .../components/blue_current/config_flow.py | 13 ++--- .../components/bluemaestro/config_flow.py | 9 ++-- .../components/bluetooth/config_flow.py | 15 +++--- .../bmw_connected_drive/config_flow.py | 37 ++++++++------ homeassistant/components/bond/config_flow.py | 15 +++--- .../components/bosch_shc/config_flow.py | 19 +++---- .../components/braviatv/config_flow.py | 28 ++++++----- homeassistant/components/bring/config_flow.py | 3 +- .../components/broadlink/config_flow.py | 25 +++++++--- .../components/brother/config_flow.py | 14 +++--- .../brottsplatskartan/config_flow.py | 7 ++- homeassistant/components/brunt/config_flow.py | 11 ++-- .../components/bsblan/config_flow.py | 9 ++-- .../components/bthome/config_flow.py | 19 ++++--- .../components/buienradar/config_flow.py | 8 ++- .../components/caldav/config_flow.py | 15 +++--- .../components/canary/config_flow.py | 14 ++++-- homeassistant/components/cast/config_flow.py | 18 ++++--- homeassistant/components/ccm15/config_flow.py | 7 ++- .../components/cert_expiry/config_flow.py | 11 ++-- homeassistant/components/cloud/config_flow.py | 5 +- .../components/cloudflare/config_flow.py | 15 +++--- .../components/co2signal/config_flow.py | 18 +++---- .../components/coinbase/config_flow.py | 43 ++++++++-------- .../components/color_extractor/config_flow.py | 8 +-- .../components/comelit/config_flow.py | 22 ++++---- .../components/control4/config_flow.py | 15 +++--- .../components/coolmaster/config_flow.py | 7 ++- .../components/cpuspeed/config_flow.py | 5 +- .../components/crownstone/config_flow.py | 27 ++++++---- .../components/daikin/config_flow.py | 13 +++-- .../components/deconz/config_flow.py | 38 ++++++++------ .../components/deluge/config_flow.py | 9 ++-- homeassistant/components/demo/config_flow.py | 26 ++++++---- .../components/denonavr/config_flow.py | 30 ++++++----- .../components/devialet/config_flow.py | 11 ++-- .../devolo_home_control/config_flow.py | 22 ++++---- .../devolo_home_network/config_flow.py | 20 ++++---- .../components/dexcom/config_flow.py | 10 ++-- .../components/directv/config_flow.py | 13 ++--- .../components/discord/config_flow.py | 13 ++--- .../components/discovergy/config_flow.py | 14 +++--- homeassistant/components/dlink/config_flow.py | 13 ++--- .../components/dlna_dmr/config_flow.py | 42 ++++++++++------ .../components/dlna_dms/config_flow.py | 16 +++--- homeassistant/components/dnsip/config_flow.py | 20 +++++--- .../components/doorbird/config_flow.py | 31 ++++++------ .../components/dormakaba_dkey/config_flow.py | 21 ++++---- .../dremel_3d_printer/config_flow.py | 7 ++- .../components/drop_connect/config_flow.py | 13 ++--- homeassistant/components/dsmr/config_flow.py | 34 +++++++------ .../components/dsmr_reader/config_flow.py | 4 +- .../components/dunehd/config_flow.py | 12 ++--- .../components/duotecno/config_flow.py | 7 ++- .../dwd_weather_warnings/config_flow.py | 5 +- .../components/dynalite/config_flow.py | 11 ++-- .../components/aussie_broadband/test_init.py | 2 +- 111 files changed, 841 insertions(+), 758 deletions(-) diff --git a/homeassistant/components/abode/config_flow.py b/homeassistant/components/abode/config_flow.py index 56cd673bc1b..21d8872088f 100644 --- a/homeassistant/components/abode/config_flow.py +++ b/homeassistant/components/abode/config_flow.py @@ -14,16 +14,15 @@ from jaraco.abode.helpers.errors import MFA_CODE_REQUIRED from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_POLLING, DOMAIN, LOGGER CONF_MFA = "mfa_code" -class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AbodeFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Abode.""" VERSION = 1 @@ -43,7 +42,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._polling: bool = False self._username: str | None = None - async def _async_abode_login(self, step_id: str) -> FlowResult: + async def _async_abode_login(self, step_id: str) -> ConfigFlowResult: """Handle login with Abode.""" errors = {} @@ -74,7 +73,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._async_create_entry() - async def _async_abode_mfa_login(self) -> FlowResult: + async def _async_abode_mfa_login(self) -> ConfigFlowResult: """Handle multi-factor authentication (MFA) login with Abode.""" try: # Create instance to access login method for passing MFA code @@ -92,7 +91,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._async_create_entry() - async def _async_create_entry(self) -> FlowResult: + async def _async_create_entry(self) -> ConfigFlowResult: """Create the config entry.""" config_data = { CONF_USERNAME: self._username, @@ -118,7 +117,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -135,7 +134,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_mfa( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a multi-factor authentication (MFA) flow.""" if user_input is None: return self.async_show_form( @@ -146,7 +145,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._async_abode_mfa_login() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauthorization request from Abode.""" self._username = entry_data[CONF_USERNAME] @@ -154,7 +155,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/accuweather/config_flow.py b/homeassistant/components/accuweather/config_flow.py index b3fc7872c85..fe9565a9e2e 100644 --- a/homeassistant/components/accuweather/config_flow.py +++ b/homeassistant/components/accuweather/config_flow.py @@ -9,11 +9,9 @@ from aiohttp import ClientError from aiohttp.client_exceptions import ClientConnectorError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( @@ -33,14 +31,14 @@ OPTIONS_FLOW = { } -class AccuWeatherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for AccuWeather.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # Under the terms of use of the API, one user can use one free API key. Due to # the small number of requests allowed, we only allow one integration instance. diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index 56a11aff200..f705e78d483 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -8,14 +8,13 @@ from typing import Any import aiopulse import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_ID -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN -class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AcmedaFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Acmeda config flow.""" VERSION = 1 @@ -26,7 +25,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if ( user_input is not None @@ -66,7 +65,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ), ) - async def async_create(self, hub: aiopulse.Hub) -> FlowResult: + async def async_create(self, hub: aiopulse.Hub) -> ConfigFlowResult: """Create the Acmeda Hub entry.""" await self.async_set_unique_id(hub.id, raise_on_progress=False) return self.async_create_entry(title=hub.id, data={CONF_HOST: hub.host}) diff --git a/homeassistant/components/adax/config_flow.py b/homeassistant/components/adax/config_flow.py index b614c968d48..6defde6f508 100644 --- a/homeassistant/components/adax/config_flow.py +++ b/homeassistant/components/adax/config_flow.py @@ -8,14 +8,13 @@ import adax import adax_local import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_IP_ADDRESS, CONF_PASSWORD, CONF_TOKEN, CONF_UNIQUE_ID, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -31,14 +30,14 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AdaxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Adax.""" VERSION = 2 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" data_schema = vol.Schema( { @@ -63,7 +62,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_local( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the local step.""" data_schema = vol.Schema( {vol.Required(WIFI_SSID): str, vol.Required(WIFI_PSWD): str} @@ -110,7 +109,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_cloud( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the cloud step.""" data_schema = vol.Schema( {vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str} diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index bc9b11c9a72..cf88674a815 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -7,7 +7,7 @@ from adguardhome import AdGuardHome, AdGuardHomeConnectionError import voluptuous as vol from homeassistant.components.hassio import HassioServiceInfo -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -16,7 +16,6 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -31,7 +30,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): async def _show_setup_form( self, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -50,7 +49,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): async def _show_hassio_form( self, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the Hass.io confirmation form to the user.""" assert self._hassio_discovery return self.async_show_form( @@ -61,7 +60,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return await self._show_setup_form(user_input) @@ -104,7 +103,9 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a Hass.io AdGuard Home add-on. This flow is triggered by the discovery component. @@ -116,7 +117,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm Supervisor discovery.""" if user_input is None: return await self._show_hassio_form() diff --git a/homeassistant/components/advantage_air/config_flow.py b/homeassistant/components/advantage_air/config_flow.py index 7b5acab55f0..e80e884e4d9 100644 --- a/homeassistant/components/advantage_air/config_flow.py +++ b/homeassistant/components/advantage_air/config_flow.py @@ -6,9 +6,8 @@ from typing import Any from advantage_air import ApiError, advantage_air import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ADVANTAGE_AIR_RETRY, DOMAIN @@ -23,7 +22,7 @@ ADVANTAGE_AIR_SCHEMA = vol.Schema( ) -class AdvantageAirConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AdvantageAirConfigFlow(ConfigFlow, domain=DOMAIN): """Config Advantage Air API connection.""" VERSION = 1 @@ -32,7 +31,7 @@ class AdvantageAirConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Get configuration from the user.""" errors = {} if user_input: diff --git a/homeassistant/components/aemet/config_flow.py b/homeassistant/components/aemet/config_flow.py index bb73311aa55..a402eb290b8 100644 --- a/homeassistant/components/aemet/config_flow.py +++ b/homeassistant/components/aemet/config_flow.py @@ -7,10 +7,9 @@ from aemet_opendata.exceptions import AuthError from aemet_opendata.interface import AEMET, ConnectionOptions import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -29,12 +28,12 @@ OPTIONS_FLOW = { } -class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AemetConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for AEMET OpenData.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -75,7 +74,7 @@ class AemetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SchemaOptionsFlowHandler: """Get the options flow for this handler.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) diff --git a/homeassistant/components/aftership/config_flow.py b/homeassistant/components/aftership/config_flow.py index 94578091501..9d377ae8ce7 100644 --- a/homeassistant/components/aftership/config_flow.py +++ b/homeassistant/components/aftership/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from pyaftership import AfterShip, AfterShipException import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_NAME from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -26,7 +25,7 @@ class AfterShipConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -49,7 +48,7 @@ class AfterShipConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + async def async_step_import(self, config: dict[str, Any]) -> ConfigFlowResult: """Import configuration from yaml.""" async_create_issue( self.hass, diff --git a/homeassistant/components/agent_dvr/config_flow.py b/homeassistant/components/agent_dvr/config_flow.py index 9143d40352f..065209e8199 100644 --- a/homeassistant/components/agent_dvr/config_flow.py +++ b/homeassistant/components/agent_dvr/config_flow.py @@ -6,9 +6,8 @@ from agent import AgentConnectionError, AgentError from agent.a import Agent import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, SERVER_URL @@ -17,12 +16,12 @@ from .helpers import generate_url DEFAULT_PORT = 8090 -class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AgentFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an Agent config flow.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle an Agent config flow.""" errors = {} diff --git a/homeassistant/components/airly/config_flow.py b/homeassistant/components/airly/config_flow.py index 27c7b0f91e3..3fd27d76c60 100644 --- a/homeassistant/components/airly/config_flow.py +++ b/homeassistant/components/airly/config_flow.py @@ -10,23 +10,22 @@ from airly import Airly from airly.exceptions import AirlyError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from .const import CONF_USE_NEAREST, DOMAIN, NO_AIRLY_SENSORS -class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AirlyFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Airly.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} use_nearest = False diff --git a/homeassistant/components/airnow/config_flow.py b/homeassistant/components/airnow/config_flow.py index a6fa7aa5088..e6ffbf1931a 100644 --- a/homeassistant/components/airnow/config_flow.py +++ b/homeassistant/components/airnow/config_flow.py @@ -6,16 +6,15 @@ from pyairnow import WebServiceAPI from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError import voluptuous as vol -from homeassistant import core from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlow, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS -from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -62,7 +61,7 @@ class AirNowConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -117,7 +116,7 @@ class AirNowConfigFlow(ConfigFlow, domain=DOMAIN): ) @staticmethod - @core.callback + @callback def async_get_options_flow( config_entry: ConfigEntry, ) -> OptionsFlow: @@ -130,7 +129,7 @@ class AirNowOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(data=user_input) diff --git a/homeassistant/components/airq/config_flow.py b/homeassistant/components/airq/config_flow.py index 33d76ec75bc..b260c372efc 100644 --- a/homeassistant/components/airq/config_flow.py +++ b/homeassistant/components/airq/config_flow.py @@ -8,9 +8,8 @@ from aioairq import AirQ, InvalidAuth from aiohttp.client_exceptions import ClientConnectionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -25,14 +24,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AirQConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for air-Q.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial (authentication) configuration step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/airthings/config_flow.py b/homeassistant/components/airthings/config_flow.py index 62f66213a0f..e5c800dfb55 100644 --- a/homeassistant/components/airthings/config_flow.py +++ b/homeassistant/components/airthings/config_flow.py @@ -7,9 +7,8 @@ from typing import Any import airthings import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_SECRET, DOMAIN @@ -24,14 +23,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Airthings.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/airthings_ble/config_flow.py b/homeassistant/components/airthings_ble/config_flow.py index 4228fea50d7..5f08f198761 100644 --- a/homeassistant/components/airthings_ble/config_flow.py +++ b/homeassistant/components/airthings_ble/config_flow.py @@ -15,9 +15,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfo, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, MFCT_ID @@ -93,7 +92,7 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" _LOGGER.debug("Discovered BT device: %s", discovery_info) await self.async_set_unique_id(discovery_info.address) @@ -114,7 +113,7 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" if user_input is not None: return self.async_create_entry( @@ -129,7 +128,7 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/airtouch4/config_flow.py b/homeassistant/components/airtouch4/config_flow.py index e395c71349b..18050308cc0 100644 --- a/homeassistant/components/airtouch4/config_flow.py +++ b/homeassistant/components/airtouch4/config_flow.py @@ -2,7 +2,7 @@ from airtouch4pyapi import AirTouch, AirTouchStatus import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST from .const import DOMAIN @@ -10,7 +10,7 @@ from .const import DOMAIN DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str}) -class AirtouchConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AirtouchConfigFlow(ConfigFlow, domain=DOMAIN): """Handle an Airtouch config flow.""" VERSION = 1 diff --git a/homeassistant/components/airtouch5/config_flow.py b/homeassistant/components/airtouch5/config_flow.py index e5df2844653..4f3b69de42c 100644 --- a/homeassistant/components/airtouch5/config_flow.py +++ b/homeassistant/components/airtouch5/config_flow.py @@ -9,7 +9,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -25,7 +24,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] | None = None if user_input is not None: diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index 23a26e2cca6..6cea9499314 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -15,8 +15,7 @@ from pyairvisual.cloud_api import ( from pyairvisual.errors import AirVisualError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_COUNTRY, @@ -26,7 +25,6 @@ from homeassistant.const import ( CONF_STATE, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -70,7 +68,7 @@ OPTIONS_FLOW = { } -class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AirVisualFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an AirVisual config flow.""" VERSION = 3 @@ -96,7 +94,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _async_finish_geography( self, user_input: dict[str, str], integration_type: str - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate a Cloud API key.""" errors = {} websession = aiohttp_client.async_get_clientsession(self.hass) @@ -155,7 +153,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _async_init_geography( self, user_input: dict[str, str], integration_type: str - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initialization of the integration via the cloud API.""" self._geo_id = async_get_geography_id(user_input) await self._async_set_unique_id(self._geo_id) @@ -173,7 +171,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Define the config flow to handle options.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) - async def async_step_import(self, import_data: dict[str, str]) -> FlowResult: + async def async_step_import(self, import_data: dict[str, str]) -> ConfigFlowResult: """Handle import of config entry version 1 data.""" import_source = import_data.pop("import_source") if import_source == "geography_by_coords": @@ -182,7 +180,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_geography_by_coords( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initialization of the cloud API based on latitude/longitude.""" if not user_input: return self.async_show_form( @@ -195,7 +193,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_geography_by_name( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initialization of the cloud API based on city/state/country.""" if not user_input: return self.async_show_form( @@ -206,7 +204,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): user_input, INTEGRATION_TYPE_GEOGRAPHY_NAME ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._entry_data_for_reauth = entry_data self._geo_id = async_get_geography_id(entry_data) @@ -214,7 +214,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" if not user_input: return self.async_show_form( @@ -229,7 +229,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return self.async_show_form( diff --git a/homeassistant/components/airvisual_pro/config_flow.py b/homeassistant/components/airvisual_pro/config_flow.py index 23da39150c5..af362c7318e 100644 --- a/homeassistant/components/airvisual_pro/config_flow.py +++ b/homeassistant/components/airvisual_pro/config_flow.py @@ -13,10 +13,8 @@ from pyairvisual.node import ( ) import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, LOGGER @@ -72,7 +70,7 @@ async def async_validate_credentials( return ValidationResult(errors=errors) -class AirVisualProFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an AirVisual Pro config flow.""" VERSION = 1 @@ -81,11 +79,15 @@ class AirVisualProFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize.""" self._reauth_entry: ConfigEntry | None = None - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -94,7 +96,7 @@ class AirVisualProFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the re-auth step.""" if user_input is None: return self.async_show_form( @@ -124,7 +126,7 @@ class AirVisualProFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if not user_input: return self.async_show_form(step_id="user", data_schema=STEP_USER_SCHEMA) diff --git a/homeassistant/components/airzone/config_flow.py b/homeassistant/components/airzone/config_flow.py index 7a8fdbf884b..02a7a3f378a 100644 --- a/homeassistant/components/airzone/config_flow.py +++ b/homeassistant/components/airzone/config_flow.py @@ -9,10 +9,10 @@ from aioairzone.exceptions import AirzoneError, InvalidSystem from aioairzone.localapi import AirzoneLocalApi, ConnectionOptions import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_ID, CONF_PORT -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import aiohttp_client from homeassistant.helpers.device_registry import format_mac @@ -38,7 +38,7 @@ def short_mac(addr: str) -> str: return addr.replace(":", "")[-4:].upper() -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN): """Handle config flow for an Airzone device.""" _discovered_ip: str | None = None @@ -46,7 +46,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" data_schema = CONFIG_SCHEMA errors = {} @@ -91,7 +91,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle DHCP discovery.""" self._discovered_ip = discovery_info.ip self._discovered_mac = discovery_info.macaddress @@ -118,7 +120,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovered_connection( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_ip is not None assert self._discovered_mac is not None diff --git a/homeassistant/components/airzone_cloud/config_flow.py b/homeassistant/components/airzone_cloud/config_flow.py index 32274d4e8ef..e523ba23cbe 100644 --- a/homeassistant/components/airzone_cloud/config_flow.py +++ b/homeassistant/components/airzone_cloud/config_flow.py @@ -9,9 +9,8 @@ from aioairzone_cloud.const import AZD_ID, AZD_NAME, AZD_WEBSERVERS from aioairzone_cloud.exceptions import AirzoneCloudError, LoginError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from homeassistant.helpers.selector import ( SelectOptionDict, @@ -23,14 +22,14 @@ from homeassistant.helpers.selector import ( from .const import DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AirZoneCloudConfigFlow(ConfigFlow, domain=DOMAIN): """Handle config flow for an Airzone Cloud device.""" airzone: AirzoneCloudApi async def async_step_inst_pick( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the installation selection.""" errors = {} options: dict[str, str] = {} @@ -81,7 +80,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/aladdin_connect/config_flow.py b/homeassistant/components/aladdin_connect/config_flow.py index d14b7b7c35e..e3a6867445c 100644 --- a/homeassistant/components/aladdin_connect/config_flow.py +++ b/homeassistant/components/aladdin_connect/config_flow.py @@ -9,10 +9,9 @@ import AIOAladdinConnect.session_manager as Aladdin from aiohttp.client_exceptions import ClientError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -48,13 +47,15 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: raise InvalidAuth from ex -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AladdinConnectConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Aladdin Connect.""" VERSION = 1 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Aladdin Connect.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -62,7 +63,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Aladdin Connect.""" errors: dict[str, str] = {} @@ -102,7 +103,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/alarmdecoder/config_flow.py b/homeassistant/components/alarmdecoder/config_flow.py index 1b2bcf083ba..0b1ebdf8af7 100644 --- a/homeassistant/components/alarmdecoder/config_flow.py +++ b/homeassistant/components/alarmdecoder/config_flow.py @@ -9,14 +9,17 @@ from alarmdecoder.devices import SerialDevice, SocketDevice from alarmdecoder.util import NoDeviceError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA, ) -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PROTOCOL from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_ALT_NIGHT_MODE, @@ -52,7 +55,7 @@ EDIT_SETTINGS = "Arming Settings" _LOGGER = logging.getLogger(__name__) -class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AlarmDecoderFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a AlarmDecoder config flow.""" VERSION = 1 @@ -64,14 +67,14 @@ class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> AlarmDecoderOptionsFlowHandler: """Get the options flow for AlarmDecoder.""" return AlarmDecoderOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: self.protocol = user_input[CONF_PROTOCOL] @@ -90,7 +93,7 @@ class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_protocol( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle AlarmDecoder protocol setup.""" errors = {} if user_input is not None: @@ -150,12 +153,12 @@ class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow): +class AlarmDecoderOptionsFlowHandler(OptionsFlow): """Handle AlarmDecoder options.""" selected_zone: str | None = None - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize AlarmDecoder options flow.""" self.arm_options = config_entry.options.get(OPTIONS_ARM, DEFAULT_ARM_OPTIONS) self.zone_options = config_entry.options.get( @@ -164,7 +167,7 @@ class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: if user_input[EDIT_KEY] == EDIT_SETTINGS: @@ -185,7 +188,7 @@ class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_arm_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Arming options form.""" if user_input is not None: return self.async_create_entry( @@ -214,7 +217,7 @@ class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_zone_select( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Zone selection form.""" errors = _validate_zone_input(user_input) @@ -232,7 +235,7 @@ class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_zone_details( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Zone details form.""" errors = _validate_zone_input(user_input) diff --git a/homeassistant/components/amberelectric/config_flow.py b/homeassistant/components/amberelectric/config_flow.py index 765e219b6d7..ee94f445498 100644 --- a/homeassistant/components/amberelectric/config_flow.py +++ b/homeassistant/components/amberelectric/config_flow.py @@ -6,9 +6,8 @@ from amberelectric.api import amber_api from amberelectric.model.site import Site, SiteStatus import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( SelectOptionDict, SelectSelector, @@ -43,7 +42,7 @@ def filter_sites(sites: list[Site]) -> list[Site]: return filtered -class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AmberElectricConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -73,7 +72,7 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" self._errors = {} self._sites = None @@ -107,7 +106,7 @@ class AmberElectricConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_site( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step to select site.""" self._errors = {} diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 383a11055e4..598a8748412 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -5,11 +5,10 @@ from typing import Any from aiohttp import web import ambiclimate -from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.network import get_url from homeassistant.helpers.storage import Store @@ -44,7 +43,7 @@ def register_flow_implementation( } -class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AmbiclimateFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -56,7 +55,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle external yaml configuration.""" self._async_abort_entries_match() @@ -70,7 +69,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_auth( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" self._async_abort_entries_match() @@ -91,7 +90,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_code(self, code: str | None = None) -> FlowResult: + async def async_step_code(self, code: str | None = None) -> ConfigFlowResult: """Received code for authentication.""" self._async_abort_entries_match() diff --git a/homeassistant/components/ambient_station/config_flow.py b/homeassistant/components/ambient_station/config_flow.py index 2c2d231b33e..8e9ff87a5f6 100644 --- a/homeassistant/components/ambient_station/config_flow.py +++ b/homeassistant/components/ambient_station/config_flow.py @@ -5,15 +5,14 @@ from aioambient import API from aioambient.errors import AmbientError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import CONF_APP_KEY, DOMAIN -class AmbientStationFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AmbientStationFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an Ambient PWS config flow.""" VERSION = 2 @@ -24,7 +23,7 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): {vol.Required(CONF_API_KEY): str, vol.Required(CONF_APP_KEY): str} ) - async def _show_form(self, errors: dict | None = None) -> FlowResult: + async def _show_form(self, errors: dict | None = None) -> ConfigFlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", @@ -32,7 +31,7 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors if errors else {}, ) - async def async_step_user(self, user_input: dict | None = None) -> FlowResult: + async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return await self._show_form() diff --git a/homeassistant/components/analytics_insights/config_flow.py b/homeassistant/components/analytics_insights/config_flow.py index d2ebdd943a2..edc69a0d161 100644 --- a/homeassistant/components/analytics_insights/config_flow.py +++ b/homeassistant/components/analytics_insights/config_flow.py @@ -13,11 +13,11 @@ import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlow, OptionsFlowWithConfigEntry, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( SelectOptionDict, @@ -50,7 +50,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" self._async_abort_entries_match() errors: dict[str, str] = {} @@ -120,7 +120,7 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/android_ip_webcam/config_flow.py b/homeassistant/components/android_ip_webcam/config_flow.py index 2a26292fdd7..920c9cdf702 100644 --- a/homeassistant/components/android_ip_webcam/config_flow.py +++ b/homeassistant/components/android_ip_webcam/config_flow.py @@ -7,10 +7,9 @@ from pydroid_ipcam import PyDroidIPCam from pydroid_ipcam.exceptions import PyDroidIPCamException, Unauthorized import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -50,14 +49,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return errors -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AndroidIPWebcamConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Android IP Webcam.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index e688b0a92de..5df4bf2f89b 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -11,11 +11,11 @@ import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import ( ObjectSelector, @@ -81,7 +81,7 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, error: str | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" host = user_input.get(CONF_HOST, "") if user_input else "" data_schema = vol.Schema( @@ -144,7 +144,7 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" error = None @@ -199,7 +199,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): self._conf_rule_id: str | None = None @callback - def _save_config(self, data: dict[str, Any]) -> FlowResult: + def _save_config(self, data: dict[str, Any]) -> ConfigFlowResult: """Save the updated options.""" new_data = { k: v @@ -215,7 +215,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: if sel_app := user_input.get(CONF_APPS): @@ -227,7 +227,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): return self._async_init_form() @callback - def _async_init_form(self) -> FlowResult: + def _async_init_form(self) -> ConfigFlowResult: """Return initial configuration form.""" apps_list = {k: f"{v} ({k})" if v else k for k, v in self._apps.items()} @@ -280,7 +280,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_apps( self, user_input: dict[str, Any] | None = None, app_id: str | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow for apps list.""" if app_id is not None: self._conf_app_id = app_id if app_id != APPS_NEW_ID else None @@ -297,7 +297,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): return await self.async_step_init() @callback - def _async_apps_form(self, app_id: str) -> FlowResult: + def _async_apps_form(self, app_id: str) -> ConfigFlowResult: """Return configuration form for apps.""" app_schema = { vol.Optional( @@ -322,7 +322,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_rules( self, user_input: dict[str, Any] | None = None, rule_id: str | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow for detection rules.""" if rule_id is not None: self._conf_rule_id = rule_id if rule_id != RULES_NEW_ID else None @@ -348,7 +348,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): @callback def _async_rules_form( self, rule_id: str, default_id: str = "", errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Return configuration form for detection rules.""" rule_schema = { vol.Optional( diff --git a/homeassistant/components/androidtv_remote/config_flow.py b/homeassistant/components/androidtv_remote/config_flow.py index 03e09c6ecb0..78f039d83fc 100644 --- a/homeassistant/components/androidtv_remote/config_flow.py +++ b/homeassistant/components/androidtv_remote/config_flow.py @@ -16,11 +16,11 @@ from homeassistant.components import zeroconf from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import CONF_ENABLE_IME, DOMAIN @@ -54,7 +54,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -78,7 +78,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def _async_start_pair(self) -> FlowResult: + async def _async_start_pair(self) -> ConfigFlowResult: """Start pairing with the Android TV. Navigate to the pair flow to enter the PIN shown on screen.""" assert self.host self.api = create_api(self.hass, self.host, enable_ime=False) @@ -88,7 +88,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_pair( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the pair step.""" errors: dict[str, str] = {} if user_input is not None: @@ -136,7 +136,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self.host = discovery_info.host self.name = discovery_info.name.removesuffix("._androidtvremote2._tcp.local.") @@ -152,7 +152,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is not None: try: @@ -166,7 +166,9 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): description_placeholders={CONF_NAME: self.name}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.host = entry_data[CONF_HOST] self.name = entry_data[CONF_NAME] @@ -178,7 +180,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors: dict[str, str] = {} if user_input is not None: @@ -207,7 +209,7 @@ class AndroidTVRemoteOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/anova/config_flow.py b/homeassistant/components/anova/config_flow.py index d0846fbffc7..13f62451e19 100644 --- a/homeassistant/components/anova/config_flow.py +++ b/homeassistant/components/anova/config_flow.py @@ -4,9 +4,8 @@ from __future__ import annotations from anova_wifi import AnovaApi, InvalidLogin, NoDevicesFound import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICES, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -20,7 +19,7 @@ class AnovaConfligFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/anthemav/config_flow.py b/homeassistant/components/anthemav/config_flow.py index 892c40cde0e..51124451156 100644 --- a/homeassistant/components/anthemav/config_flow.py +++ b/homeassistant/components/anthemav/config_flow.py @@ -9,9 +9,8 @@ from anthemav.connection import Connection from anthemav.device_error import DeviceError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_MAC, CONF_MODEL, CONF_PORT -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -44,7 +43,7 @@ class AnthemAVConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/aosmith/config_flow.py b/homeassistant/components/aosmith/config_flow.py index 899b7382359..65262890868 100644 --- a/homeassistant/components/aosmith/config_flow.py +++ b/homeassistant/components/aosmith/config_flow.py @@ -8,9 +8,8 @@ from typing import Any from py_aosmith import AOSmithAPIClient, AOSmithInvalidCredentialsException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -18,7 +17,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AOSmithConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for A. O. Smith.""" VERSION = 1 @@ -44,7 +43,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -73,14 +72,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth if the user credentials have changed.""" self._reauth_email = entry_data[CONF_EMAIL] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user's reauth credentials.""" errors: dict[str, str] = {} if user_input is not None and self._reauth_email is not None: diff --git a/homeassistant/components/apcupsd/config_flow.py b/homeassistant/components/apcupsd/config_flow.py index 25a1ccf7e02..0e4c5ecb413 100644 --- a/homeassistant/components/apcupsd/config_flow.py +++ b/homeassistant/components/apcupsd/config_flow.py @@ -5,9 +5,8 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import UpdateFailed @@ -38,7 +37,7 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index 2bb4608dca1..bd95aa6ca39 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -16,11 +16,17 @@ from pyatv.helpers import get_unique_id from pyatv.interface import BaseConfig, PairingHandler import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + SOURCE_IGNORE, + SOURCE_ZEROCONF, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PIN from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.schema_config_entry_flow import ( @@ -85,7 +91,7 @@ async def device_scan( return None, None -class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Apple TV.""" VERSION = 1 @@ -100,7 +106,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SchemaOptionsFlowHandler: """Get options flow for this handler.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) @@ -141,7 +147,9 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return entry.unique_id return None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initial step when updating invalid credentials.""" self.context["title_placeholders"] = { "name": entry_data[CONF_NAME], @@ -153,7 +161,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reconfigure( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Inform user that reconfiguration is about to start.""" if user_input is not None: return await self.async_find_device_wrapper( @@ -164,7 +172,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -194,7 +202,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle device found via zeroconf.""" if discovery_info.ip_address.version == 6: return self.async_abort(reason="ipv6_not_supported") @@ -276,7 +284,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): for flow in self._async_in_progress(include_uninitialized=True): context = flow["context"] if ( - context.get("source") != config_entries.SOURCE_ZEROCONF + context.get("source") != SOURCE_ZEROCONF or context.get(CONF_ADDRESS) != host ): continue @@ -290,7 +298,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_found_zeroconf_device( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle device found after Zeroconf discovery.""" assert self.atv self.context["all_identifiers"] = self.atv.all_identifiers @@ -306,9 +314,9 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_find_device_wrapper( self, - next_func: Callable[[], Awaitable[FlowResult]], + next_func: Callable[[], Awaitable[ConfigFlowResult]], allow_exist: bool = False, - ) -> FlowResult: + ) -> ConfigFlowResult: """Find a specific device and call another function when done. This function will do error handling and bail out when an error @@ -370,7 +378,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_IDENTIFIERS: list(combined_identifiers), }, ) - if entry.source != config_entries.SOURCE_IGNORE: + if entry.source != SOURCE_IGNORE: self.hass.async_create_task( self.hass.config_entries.async_reload(entry.entry_id) ) @@ -379,7 +387,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" assert self.atv if user_input is not None: @@ -407,7 +415,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_pair_next_protocol(self) -> FlowResult: + async def async_pair_next_protocol(self) -> ConfigFlowResult: """Start pairing process for the next available protocol.""" await self._async_cleanup() @@ -481,7 +489,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_protocol_disabled( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Inform user that a protocol is disabled and cannot be paired.""" assert self.protocol if user_input is not None: @@ -493,7 +501,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pair_with_pin( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle pairing step where a PIN is required from the user.""" errors = {} assert self.pairing @@ -520,7 +528,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pair_no_pin( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle step where user has to enter a PIN on the device.""" assert self.pairing assert self.protocol @@ -545,7 +553,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_service_problem( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Inform user that a service will not be added.""" assert self.protocol if user_input is not None: @@ -558,7 +566,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_password( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Inform user that password is not supported.""" assert self.protocol if user_input is not None: @@ -575,7 +583,7 @@ class AppleTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.pairing.close() self.pairing = None - async def _async_get_entry(self) -> FlowResult: + async def _async_get_entry(self) -> ConfigFlowResult: """Return config entry or update existing config entry.""" # Abort if no protocols were paired if not self.credentials: diff --git a/homeassistant/components/aprilaire/config_flow.py b/homeassistant/components/aprilaire/config_flow.py index 0e38b385450..14437e5f3f2 100644 --- a/homeassistant/components/aprilaire/config_flow.py +++ b/homeassistant/components/aprilaire/config_flow.py @@ -10,7 +10,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -34,7 +33,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" if user_input is None: diff --git a/homeassistant/components/aranet/config_flow.py b/homeassistant/components/aranet/config_flow.py index 029ee251ae7..8d813884a82 100644 --- a/homeassistant/components/aranet/config_flow.py +++ b/homeassistant/components/aranet/config_flow.py @@ -7,13 +7,13 @@ from typing import Any from aranet4.client import Aranet4Advertisement, Version as AranetVersion import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from .const import DOMAIN @@ -22,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) MIN_VERSION = AranetVersion(1, 2, 0) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AranetConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Aranet.""" VERSION = 1 @@ -45,7 +45,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the Bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -58,7 +58,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None adv = self._discovered_device @@ -77,7 +77,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/arcam_fmj/config_flow.py b/homeassistant/components/arcam_fmj/config_flow.py index 09944328c4a..481657bcd85 100644 --- a/homeassistant/components/arcam_fmj/config_flow.py +++ b/homeassistant/components/arcam_fmj/config_flow.py @@ -8,23 +8,22 @@ from arcam.fmj.client import Client, ConnectionFailed from arcam.fmj.utils import get_uniqueid_from_host, get_uniqueid_from_udn import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN, DOMAIN_DATA_ENTRIES -def get_entry_client(hass: HomeAssistant, entry: config_entries.ConfigEntry) -> Client: +def get_entry_client(hass: HomeAssistant, entry: ConfigEntry) -> Client: """Retrieve client associated with a config entry.""" client: Client = hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id] return client -class ArcamFmjFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN): """Handle config flow.""" VERSION = 1 @@ -35,7 +34,7 @@ class ArcamFmjFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(uuid) self._abort_if_unique_id_configured({CONF_HOST: host, CONF_PORT: port}) - async def _async_check_and_create(self, host: str, port: int) -> FlowResult: + async def _async_check_and_create(self, host: str, port: int) -> ConfigFlowResult: client = Client(host, port) try: await client.start() @@ -51,7 +50,7 @@ class ArcamFmjFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered device.""" errors: dict[str, str] = {} @@ -79,7 +78,7 @@ class ArcamFmjFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" context = self.context placeholders = { @@ -96,7 +95,9 @@ class ArcamFmjFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="confirm", description_placeholders=placeholders ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered device.""" host = str(urlparse(discovery_info.ssdp_location).hostname) port = DEFAULT_PORT diff --git a/homeassistant/components/aseko_pool_live/config_flow.py b/homeassistant/components/aseko_pool_live/config_flow.py index c8f96db3bc8..ef9ea9ef29e 100644 --- a/homeassistant/components/aseko_pool_live/config_flow.py +++ b/homeassistant/components/aseko_pool_live/config_flow.py @@ -7,14 +7,13 @@ from typing import Any from aioaseko import APIUnavailable, InvalidAuthCredentials, MobileAccount, WebAccount import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_EMAIL, CONF_PASSWORD, CONF_UNIQUE_ID, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -22,7 +21,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AsekoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Aseko Pool Live.""" VERSION = 1 @@ -45,7 +44,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/asuswrt/config_flow.py b/homeassistant/components/asuswrt/config_flow.py index 1e320bdd72d..e456b1c55ba 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, ) -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_BASE, CONF_HOST, @@ -25,7 +25,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( SchemaCommonFlowHandler, @@ -139,7 +138,7 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): self._config_data: dict[str, Any] = {} @callback - def _show_setup_form(self, error: str | None = None) -> FlowResult: + def _show_setup_form(self, error: str | None = None) -> ConfigFlowResult: """Show the setup form to the user.""" user_input = self._config_data @@ -228,7 +227,7 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" # if there's one entry without unique ID, we abort config flow @@ -276,7 +275,7 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_legacy( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow for legacy settings.""" if user_input is None: return self.async_show_form(step_id="legacy", data_schema=LEGACY_SCHEMA) @@ -284,7 +283,7 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN): self._config_data.update(user_input) return await self._async_save_entry() - async def _async_save_entry(self) -> FlowResult: + async def _async_save_entry(self) -> ConfigFlowResult: """Save entry data if unique id is valid.""" return self.async_create_entry( title=self._config_data[CONF_HOST], diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index 8dd7020acfb..f4e60bc6062 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -4,9 +4,8 @@ from typing import Any import pyatag import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import DOMAIN @@ -17,14 +16,14 @@ DATA_SCHEMA = { } -class AtagConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AtagConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for Atag.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if not user_input: @@ -44,7 +43,9 @@ class AtagConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=atag.id, data=user_input) - async def _show_form(self, errors: dict[str, str] | None = None) -> FlowResult: + async def _show_form( + self, errors: dict[str, str] | None = None + ) -> ConfigFlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index 8aaf1b1a05b..3c0208412b9 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -9,10 +9,9 @@ import voluptuous as vol from yalexs.authenticator import ValidationResult from yalexs.const import BRANDS, DEFAULT_BRAND -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_ACCESS_TOKEN_CACHE_FILE, @@ -75,7 +74,7 @@ class ValidateResult: description_placeholders: dict[str, str] -class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AugustConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for August.""" VERSION = 1 @@ -91,13 +90,13 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" return await self.async_step_user_validate() async def async_step_user_validate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle authentication.""" errors: dict[str, str] = {} description_placeholders: dict[str, str] = {} @@ -137,7 +136,7 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_validation( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle validation (2fa) step.""" if user_input: if self._mode == "reauth": @@ -174,7 +173,9 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._aiohttp_session.detach() self._august_gateway = None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._user_auth_details = dict(entry_data) self._mode = "reauth" @@ -183,7 +184,7 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_validate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth and validation.""" errors: dict[str, str] = {} description_placeholders: dict[str, str] = {} @@ -261,7 +262,9 @@ class AugustConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): validation_required, info, errors, description_placeholders ) - async def _async_update_or_create_entry(self, info: dict[str, Any]) -> FlowResult: + async def _async_update_or_create_entry( + self, info: dict[str, Any] + ) -> ConfigFlowResult: """Update existing entry or create a new one.""" self._async_shutdown_gateway() diff --git a/homeassistant/components/aurora/config_flow.py b/homeassistant/components/aurora/config_flow.py index a1971884ead..81e67acc27a 100644 --- a/homeassistant/components/aurora/config_flow.py +++ b/homeassistant/components/aurora/config_flow.py @@ -8,10 +8,9 @@ from aiohttp import ClientError from auroranoaa import AuroraForecast import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -34,7 +33,7 @@ OPTIONS_FLOW = { } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AuroraConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for NOAA Aurora Integration.""" VERSION = 1 @@ -42,14 +41,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SchemaOptionsFlowHandler: """Get the options flow for this handler.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/aurora_abb_powerone/config_flow.py b/homeassistant/components/aurora_abb_powerone/config_flow.py index 32295c3bf47..37d802502bb 100644 --- a/homeassistant/components/aurora_abb_powerone/config_flow.py +++ b/homeassistant/components/aurora_abb_powerone/config_flow.py @@ -9,9 +9,9 @@ from aurorapy.client import AuroraError, AuroraSerialClient import serial.tools.list_ports import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ATTR_SERIAL_NUMBER, CONF_ADDRESS, CONF_PORT -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant from .const import ( ATTR_FIRMWARE, @@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) def validate_and_connect( - hass: core.HomeAssistant, data: Mapping[str, Any] + hass: HomeAssistant, data: Mapping[str, Any] ) -> dict[str, str]: """Validate the user input allows us to connect. @@ -69,7 +69,7 @@ def scan_comports() -> tuple[list[str] | None, str | None]: return None, None -class AuroraABBConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AuroraABBConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Aurora ABB PowerOne.""" VERSION = 1 @@ -82,7 +82,7 @@ class AuroraABBConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialised by the user.""" errors = {} diff --git a/homeassistant/components/aussie_broadband/config_flow.py b/homeassistant/components/aussie_broadband/config_flow.py index c71570b73fb..dedab9684e6 100644 --- a/homeassistant/components/aussie_broadband/config_flow.py +++ b/homeassistant/components/aussie_broadband/config_flow.py @@ -9,15 +9,14 @@ from aussiebb.asyncio import AussieBB, AuthenticationException from aussiebb.const import FETCH_TYPES import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_SERVICES, DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AussieBroadbandConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Aussie Broadband.""" VERSION = 1 @@ -47,7 +46,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] | None = None if user_input is not None: @@ -77,7 +76,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth on credential failure.""" self._reauth_username = entry_data[CONF_USERNAME] @@ -85,7 +86,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle users reauth credentials.""" errors: dict[str, str] | None = None diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index c68d46f7d39..751bcf6847d 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -11,10 +11,9 @@ from python_awair.user import AwairUser import voluptuous as vol from homeassistant.components import onboarding, zeroconf -from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow +from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DEVICE, CONF_HOST from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, LOGGER @@ -29,7 +28,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" host = discovery_info.host @@ -58,7 +57,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): title = f"{self._device.model} ({self._device.device_id})" @@ -79,12 +78,12 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return self.async_show_menu(step_id="user", menu_options=["local", "cloud"]) - async def async_step_cloud(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_cloud(self, user_input: Mapping[str, Any]) -> ConfigFlowResult: """Handle collecting and verifying Awair Cloud API credentials.""" errors = {} @@ -129,7 +128,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_local( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show how to enable local API.""" if user_input is not None: return await self.async_step_local_pick() @@ -143,7 +142,7 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_local_pick( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle collecting and verifying Awair Local API hosts.""" errors = {} @@ -188,13 +187,15 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-auth if token invalid.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} diff --git a/homeassistant/components/aws/config_flow.py b/homeassistant/components/aws/config_flow.py index e0829ef2914..8c80b0d487d 100644 --- a/homeassistant/components/aws/config_flow.py +++ b/homeassistant/components/aws/config_flow.py @@ -3,18 +3,19 @@ from collections.abc import Mapping from typing import Any -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN -class AWSFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AWSFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 - async def async_step_import(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_import( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Import a config entry.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index cbba23b8b51..1a46388dbd5 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -9,9 +9,14 @@ from urllib.parse import urlsplit import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp, ssdp, zeroconf -from homeassistant.config_entries import SOURCE_IGNORE, ConfigEntry +from homeassistant.config_entries import ( + SOURCE_IGNORE, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -22,7 +27,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from homeassistant.util.network import is_link_local @@ -40,7 +44,7 @@ AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"} DEFAULT_PORT = 80 -class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): +class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): """Handle a Axis config flow.""" VERSION = 3 @@ -58,7 +62,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a Axis config flow start. Manage device specific parameters. @@ -111,7 +115,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): errors=errors, ) - async def _create_entry(self, serial: str) -> FlowResult: + async def _create_entry(self, serial: str) -> ConfigFlowResult: """Create entry for device. Generate a name to be used as a prefix for device entities. @@ -134,7 +138,9 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): title = f"{model} - {serial}" return self.async_create_entry(title=title, data=self.device_config) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Trigger a reauthentication flow.""" self.context["title_placeholders"] = { CONF_NAME: entry_data[CONF_NAME], @@ -150,7 +156,9 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): return await self.async_step_user() - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a DHCP discovered Axis device.""" return await self._process_discovered_device( { @@ -161,7 +169,9 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): } ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a SSDP discovered Axis device.""" url = urlsplit(discovery_info.upnp[ssdp.ATTR_UPNP_PRESENTATION_URL]) return await self._process_discovered_device( @@ -175,7 +185,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare configuration for a Zeroconf discovered Axis device.""" return await self._process_discovered_device( { @@ -186,7 +196,9 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): } ) - async def _process_discovered_device(self, device: dict[str, Any]) -> FlowResult: + async def _process_discovered_device( + self, device: dict[str, Any] + ) -> ConfigFlowResult: """Prepare configuration for a discovered Axis device.""" if device[CONF_MAC][:8] not in AXIS_OUI: return self.async_abort(reason="not_axis_device") @@ -223,21 +235,21 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): return await self.async_step_user() -class AxisOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): +class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle Axis device options.""" device: AxisNetworkDevice async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Axis device options.""" self.device = self.hass.data[AXIS_DOMAIN][self.config_entry.entry_id] return await self.async_step_configure_stream() async def async_step_configure_stream( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Axis device stream options.""" if user_input is not None: self.options.update(user_input) diff --git a/homeassistant/components/azure_devops/config_flow.py b/homeassistant/components/azure_devops/config_flow.py index 81cfc2e8d45..4b7320eeba5 100644 --- a/homeassistant/components/azure_devops/config_flow.py +++ b/homeassistant/components/azure_devops/config_flow.py @@ -8,8 +8,7 @@ from aioazuredevops.client import DevOpsClient import aiohttp import voluptuous as vol -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import CONF_ORG, CONF_PAT, CONF_PROJECT, DOMAIN @@ -27,7 +26,7 @@ class AzureDevOpsFlowHandler(ConfigFlow, domain=DOMAIN): async def _show_setup_form( self, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -41,7 +40,7 @@ class AzureDevOpsFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors or {}, ) - async def _show_reauth_form(self, errors: dict[str, str]) -> FlowResult: + async def _show_reauth_form(self, errors: dict[str, str]) -> ConfigFlowResult: """Show the reauth form to the user.""" return self.async_show_form( step_id="reauth", @@ -75,7 +74,7 @@ class AzureDevOpsFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return await self._show_setup_form() @@ -92,7 +91,9 @@ class AzureDevOpsFlowHandler(ConfigFlow, domain=DOMAIN): return await self._show_setup_form(errors) return self._async_create_entry() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" if entry_data.get(CONF_ORG) and entry_data.get(CONF_PROJECT): self._organization = entry_data[CONF_ORG] @@ -121,7 +122,7 @@ class AzureDevOpsFlowHandler(ConfigFlow, domain=DOMAIN): ) return self.async_abort(reason="reauth_successful") - def _async_create_entry(self) -> FlowResult: + def _async_create_entry(self) -> ConfigFlowResult: """Handle create entry.""" return self.async_create_entry( title=f"{self._organization}/{self._project}", diff --git a/homeassistant/components/azure_event_hub/config_flow.py b/homeassistant/components/azure_event_hub/config_flow.py index 3573d5e72aa..17144da0b30 100644 --- a/homeassistant/components/azure_event_hub/config_flow.py +++ b/homeassistant/components/azure_event_hub/config_flow.py @@ -8,9 +8,8 @@ from typing import Any from azure.eventhub.exceptions import EventHubError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, SchemaOptionsFlowHandler, @@ -79,7 +78,7 @@ async def validate_data(data: dict[str, Any]) -> dict[str, str] | None: return None -class AEHConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class AEHConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for azure event hub.""" VERSION: int = 1 @@ -93,14 +92,14 @@ class AEHConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SchemaOptionsFlowHandler: """Get the options flow for this handler.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial user step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -116,7 +115,7 @@ class AEHConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_conn_string( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the connection string steps.""" errors = await self.async_update_and_validate_data(user_input) if user_input is None or errors is not None: @@ -136,7 +135,7 @@ class AEHConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_sas( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the sas steps.""" errors = await self.async_update_and_validate_data(user_input) if user_input is None or errors is not None: @@ -154,7 +153,9 @@ class AEHConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): options=self._options, ) - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import config from configuration.yaml.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/baf/config_flow.py b/homeassistant/components/baf/config_flow.py index 0aaf2189c28..d5ac869526e 100644 --- a/homeassistant/components/baf/config_flow.py +++ b/homeassistant/components/baf/config_flow.py @@ -9,10 +9,9 @@ from aiobafi6 import Device, Service from aiobafi6.discovery import PORT import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, RUN_TIMEOUT from .models import BAFDiscovery @@ -34,7 +33,7 @@ async def async_try_connect(ip_address: str) -> Device: return device -class BAFFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class BAFFlowHandler(ConfigFlow, domain=DOMAIN): """Handle BAF discovery config flow.""" VERSION = 1 @@ -45,7 +44,7 @@ class BAFFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" if discovery_info.ip_address.version == 6: return self.async_abort(reason="ipv6_not_supported") @@ -61,7 +60,7 @@ class BAFFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self.discovery is not None discovery = self.discovery @@ -83,7 +82,7 @@ class BAFFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} ip_address = (user_input or {}).get(CONF_IP_ADDRESS, "") diff --git a/homeassistant/components/balboa/config_flow.py b/homeassistant/components/balboa/config_flow.py index 73f19f0e327..7705695f538 100644 --- a/homeassistant/components/balboa/config_flow.py +++ b/homeassistant/components/balboa/config_flow.py @@ -8,11 +8,10 @@ from pybalboa import SpaClient from pybalboa.exceptions import SpaConnectionError import voluptuous as vol -from homeassistant import exceptions -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -65,7 +64,7 @@ class BalboaSpaClientFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -87,5 +86,5 @@ class BalboaSpaClientFlowHandler(ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/bang_olufsen/config_flow.py b/homeassistant/components/bang_olufsen/config_flow.py index 6a26c4c5984..73b6586adb9 100644 --- a/homeassistant/components/bang_olufsen/config_flow.py +++ b/homeassistant/components/bang_olufsen/config_flow.py @@ -10,9 +10,8 @@ from mozart_api.mozart_client import MozartClient import voluptuous as vol from homeassistant.components.zeroconf import ZeroconfServiceInfo -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_MODEL -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig from .const import ( @@ -62,7 +61,7 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" data_schema = vol.Schema( { @@ -121,7 +120,7 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery using Zeroconf.""" # Check if the discovered device is a Mozart device @@ -149,7 +148,7 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_zeroconf_confirm() - async def _create_entry(self) -> FlowResult: + async def _create_entry(self) -> ConfigFlowResult: """Create the config entry for a discovered or manually configured Bang & Olufsen device.""" # Ensure that created entities have a unique and easily identifiable id and not a "friendly name" self._name = f"{self._model}-{self._serial_number}" @@ -166,7 +165,7 @@ class BangOlufsenConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the configuration of the device.""" if user_input is not None: return await self._create_entry() diff --git a/homeassistant/components/blebox/config_flow.py b/homeassistant/components/blebox/config_flow.py index 977e704eb98..86b6380ff9f 100644 --- a/homeassistant/components/blebox/config_flow.py +++ b/homeassistant/components/blebox/config_flow.py @@ -14,10 +14,9 @@ from blebox_uniapi.error import ( from blebox_uniapi.session import ApiHost import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import get_maybe_authenticated_session @@ -65,7 +64,7 @@ LOG_MSG = { } -class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BleBoxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for BleBox devices.""" VERSION = 1 @@ -89,7 +88,7 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" hass = self.hass ipaddress = (discovery_info.host, discovery_info.port) @@ -126,7 +125,7 @@ class BleBoxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery confirmation.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/blink/config_flow.py b/homeassistant/components/blink/config_flow.py index aaacbb9390c..c49ef67be98 100644 --- a/homeassistant/components/blink/config_flow.py +++ b/homeassistant/components/blink/config_flow.py @@ -9,10 +9,9 @@ from blinkpy.auth import Auth, LoginError, TokenRefreshFailed from blinkpy.blinkpy import Blink, BlinkSetupError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_PIN, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -51,7 +50,7 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: @@ -86,7 +85,7 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_2fa( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle 2FA step.""" errors = {} if user_input is not None: @@ -113,12 +112,14 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon migration of old entries.""" return await self.async_step_user(dict(entry_data)) @callback - def _async_finish_flow(self) -> FlowResult: + def _async_finish_flow(self) -> ConfigFlowResult: """Finish with setup.""" assert self.auth return self.async_create_entry(title=DOMAIN, data=self.auth.login_attributes) diff --git a/homeassistant/components/blue_current/config_flow.py b/homeassistant/components/blue_current/config_flow.py index 68a30fcdf7f..56980649c0a 100644 --- a/homeassistant/components/blue_current/config_flow.py +++ b/homeassistant/components/blue_current/config_flow.py @@ -13,24 +13,23 @@ from bluecurrent_api.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_TOKEN -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, LOGGER DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_TOKEN): str}) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BlueCurrentConfigFlow(ConfigFlow, domain=DOMAIN): """Handle the config flow for Blue Current.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -75,7 +74,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/bluemaestro/config_flow.py b/homeassistant/components/bluemaestro/config_flow.py index ccb548fa42b..b8bfec40f19 100644 --- a/homeassistant/components/bluemaestro/config_flow.py +++ b/homeassistant/components/bluemaestro/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class BlueMaestroConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class BlueMaestroConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class BlueMaestroConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/bluetooth/config_flow.py b/homeassistant/components/bluetooth/config_flow.py index 76cf167790f..a71c32fc3ca 100644 --- a/homeassistant/components/bluetooth/config_flow.py +++ b/homeassistant/components/bluetooth/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure the Bluetooth integration.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any, cast +from typing import Any, cast from bluetooth_adapters import ( ADAPTER_ADDRESS, @@ -13,7 +13,7 @@ from bluetooth_adapters import ( import voluptuous as vol from homeassistant.components import onboarding -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.core import callback from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -24,9 +24,6 @@ from homeassistant.helpers.typing import DiscoveryInfoType from . import models from .const import CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, DOMAIN -if TYPE_CHECKING: - from homeassistant.data_entry_flow import FlowResult - OPTIONS_SCHEMA = vol.Schema( { vol.Required(CONF_PASSIVE, default=False): bool, @@ -50,7 +47,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" self._adapter = cast(str, discovery_info[CONF_ADAPTER]) self._details = cast(AdapterDetails, discovery_info[CONF_DETAILS]) @@ -63,7 +60,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_single_adapter( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select an adapter.""" adapter = self._adapter details = self._details @@ -86,7 +83,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_multiple_adapters( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: assert self._adapters is not None @@ -138,7 +135,7 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self.async_step_multiple_adapters() diff --git a/homeassistant/components/bmw_connected_drive/config_flow.py b/homeassistant/components/bmw_connected_drive/config_flow.py index 926706397a6..3fbee2a79bb 100644 --- a/homeassistant/components/bmw_connected_drive/config_flow.py +++ b/homeassistant/components/bmw_connected_drive/config_flow.py @@ -10,10 +10,15 @@ from bimmer_connected.models import MyBMWAPIError, MyBMWAuthError from httpx import RequestError import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_SOURCE, CONF_USERNAME -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from . import DOMAIN from .const import CONF_ALLOWED_REGIONS, CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN @@ -27,9 +32,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -56,16 +59,16 @@ async def validate_input( return retval -class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BMWConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for MyBMW.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -112,7 +115,9 @@ class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -122,24 +127,24 @@ class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> BMWOptionsFlow: """Return a MyBMW option flow.""" return BMWOptionsFlow(config_entry) -class BMWOptionsFlow(config_entries.OptionsFlowWithConfigEntry): +class BMWOptionsFlow(OptionsFlowWithConfigEntry): """Handle a option flow for MyBMW.""" async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" return await self.async_step_account_options() async def async_step_account_options( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: # Manually update & reload the config entry after options change. @@ -166,9 +171,9 @@ class BMWOptionsFlow(config_entries.OptionsFlowWithConfigEntry): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 33b5d2bf2c4..210dd3b3c4e 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -10,12 +10,11 @@ from aiohttp import ClientConnectionError, ClientResponseError from bond_async import Bond import voluptuous as vol -from homeassistant import config_entries, exceptions from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigEntryState +from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -66,7 +65,7 @@ async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> tuple[st return hub.bond_id, hub.name -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BondConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Bond.""" VERSION = 1 @@ -98,7 +97,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf discovery.""" name: str = discovery_info.name host: str = discovery_info.host @@ -132,7 +131,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle confirmation flow for discovered bond hub.""" errors = {} if user_input is not None: @@ -173,7 +172,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -191,7 +190,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class InputValidationError(exceptions.HomeAssistantError): +class InputValidationError(HomeAssistantError): """Error to indicate we cannot proceed due to invalid input.""" def __init__(self, base: str) -> None: diff --git a/homeassistant/components/bosch_shc/config_flow.py b/homeassistant/components/bosch_shc/config_flow.py index c19ab7726b2..28bf3a797b5 100644 --- a/homeassistant/components/bosch_shc/config_flow.py +++ b/homeassistant/components/bosch_shc/config_flow.py @@ -15,11 +15,10 @@ from boschshcpy.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_TOKEN from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_HOSTNAME, @@ -87,20 +86,22 @@ def get_info_from_host( return {"title": information.name, "unique_id": information.unique_id} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BoschSHCConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Bosch SHC.""" VERSION = 1 info: dict[str, str | None] host: str - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form( @@ -113,7 +114,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -136,7 +137,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_credentials( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the credentials step.""" errors: dict[str, str] = {} if user_input is not None: @@ -201,7 +202,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" if not discovery_info.name.startswith("Bosch SHC"): return self.async_abort(reason="not_bosch_shc") @@ -222,7 +223,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery confirm.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index fd72203b249..6fc5c07130b 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -9,11 +9,9 @@ from aiohttp import CookieJar from pybravia import BraviaAuthError, BraviaClient, BraviaError, BraviaNotSupported import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CLIENT_ID, CONF_HOST, CONF_MAC, CONF_NAME, CONF_PIN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import instance_id from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.util.network import is_host_valid @@ -29,7 +27,7 @@ from .const import ( ) -class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BraviaTVConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Bravia TV integration.""" VERSION = 1 @@ -69,7 +67,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.client.connect(pin=pin, clientid=client_id, nickname=nickname) await self.client.set_wol_mode(True) - async def async_create_device(self) -> FlowResult: + async def async_create_device(self) -> ConfigFlowResult: """Create Bravia TV device from config.""" assert self.client await self.async_connect_device() @@ -85,7 +83,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=title, data=self.device_config) - async def async_reauth_device(self) -> FlowResult: + async def async_reauth_device(self) -> ConfigFlowResult: """Reauthorize Bravia TV device from config.""" assert self.entry assert self.client @@ -97,7 +95,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -117,7 +115,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_authorize( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle authorize step.""" self.create_client() @@ -138,7 +136,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pin( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle PIN authorize step.""" errors: dict[str, str] = {} client_id, nickname = await self.gen_instance_ids() @@ -177,7 +175,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_psk( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle PSK authorize step.""" errors: dict[str, str] = {} @@ -204,7 +202,9 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered device.""" parsed_url = urlparse(discovery_info.ssdp_location) host = parsed_url.hostname @@ -234,14 +234,16 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Allow the user to confirm adding the device.""" if user_input is not None: return await self.async_step_authorize() return self.async_show_form(step_id="confirm") - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) self.device_config = {**entry_data} diff --git a/homeassistant/components/bring/config_flow.py b/homeassistant/components/bring/config_flow.py index efd99fd938a..de4e278407b 100644 --- a/homeassistant/components/bring/config_flow.py +++ b/homeassistant/components/bring/config_flow.py @@ -10,7 +10,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( TextSelector, @@ -45,7 +44,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index 531119db6df..e3d6dda3488 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -14,10 +14,15 @@ from broadlink.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ( + SOURCE_IMPORT, + SOURCE_REAUTH, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT, CONF_TYPE -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import config_validation as cv from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DEVICE_TYPES, DOMAIN @@ -26,7 +31,7 @@ from .helpers import format_mac _LOGGER = logging.getLogger(__name__) -class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class BroadlinkFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Broadlink config flow.""" VERSION = 1 @@ -58,7 +63,9 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): "host": device.host[0], } - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" host = discovery_info.ip unique_id = discovery_info.macaddress.lower().replace(":", "") @@ -112,7 +119,7 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): else: device.timeout = timeout - if self.source != config_entries.SOURCE_REAUTH: + if self.source != SOURCE_REAUTH: await self.async_set_device(device) self._abort_if_unique_id_configured( updates={CONF_HOST: device.host[0], CONF_TIMEOUT: timeout} @@ -131,7 +138,7 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.error("Failed to connect to the device at %s: %s", host, err_msg) - if self.source == config_entries.SOURCE_IMPORT: + if self.source == SOURCE_IMPORT: return self.async_abort(reason=errors["base"]) data_schema = { @@ -175,7 +182,7 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): else: await self.async_set_unique_id(device.mac.hex()) - if self.source == config_entries.SOURCE_IMPORT: + if self.source == SOURCE_IMPORT: _LOGGER.warning( ( "%s (%s at %s) is ready to be configured. Click " @@ -305,7 +312,9 @@ class BroadlinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._async_abort_entries_match({CONF_HOST: import_info[CONF_HOST]}) return await self.async_step_user(import_info) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Reauthenticate to the device.""" device = blk.gendevice( entry_data[CONF_TYPE], diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 55d47bb0c2c..13668224de3 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -6,10 +6,10 @@ from typing import Any from brother import Brother, SnmpError, UnsupportedModelError import voluptuous as vol -from homeassistant import config_entries, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_TYPE -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.util.network import is_host_valid from .const import DOMAIN, PRINTER_TYPES @@ -23,7 +23,7 @@ DATA_SCHEMA = vol.Schema( ) -class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BrotherConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Brother Printer.""" VERSION = 1 @@ -35,7 +35,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -71,7 +71,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self.host = discovery_info.host @@ -107,7 +107,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is not None: title = f"{self.brother.model} {self.brother.serial}" @@ -127,5 +127,5 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class InvalidHost(exceptions.HomeAssistantError): +class InvalidHost(HomeAssistantError): """Error to indicate that hostname/IP address is invalid.""" diff --git a/homeassistant/components/brottsplatskartan/config_flow.py b/homeassistant/components/brottsplatskartan/config_flow.py index 39c7421fa92..070708ee379 100644 --- a/homeassistant/components/brottsplatskartan/config_flow.py +++ b/homeassistant/components/brottsplatskartan/config_flow.py @@ -7,9 +7,8 @@ import uuid from brottsplatskartan import AREAS import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .const import CONF_APP_ID, CONF_AREA, DEFAULT_NAME, DOMAIN @@ -29,14 +28,14 @@ DATA_SCHEMA = vol.Schema( ) -class BPKConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class BPKConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Brottsplatskartan integration.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/brunt/config_flow.py b/homeassistant/components/brunt/config_flow.py index cfd3bfa69cb..8a9cf13a786 100644 --- a/homeassistant/components/brunt/config_flow.py +++ b/homeassistant/components/brunt/config_flow.py @@ -10,9 +10,8 @@ from aiohttp.client_exceptions import ServerDisconnectedError from brunt import BruntClientAsync import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -60,7 +59,7 @@ class BruntConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA) @@ -78,7 +77,9 @@ class BruntConfigFlow(ConfigFlow, domain=DOMAIN): data=user_input, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -87,7 +88,7 @@ class BruntConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" assert self._reauth_entry username = self._reauth_entry.data[CONF_USERNAME] diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index e12e6e5c6cf..8935bff26a3 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -6,10 +6,9 @@ from typing import Any from bsblan import BSBLAN, BSBLANError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac @@ -30,7 +29,7 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -49,7 +48,7 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN): return self._async_create_entry() @callback - def _show_setup_form(self, errors: dict | None = None) -> FlowResult: + def _show_setup_form(self, errors: dict | None = None) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -66,7 +65,7 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry(self) -> FlowResult: + def _async_create_entry(self) -> ConfigFlowResult: return self.async_create_entry( title=format_mac(self.mac), data={ diff --git a/homeassistant/components/bthome/config_flow.py b/homeassistant/components/bthome/config_flow.py index 41440cb435f..48b24f1229a 100644 --- a/homeassistant/components/bthome/config_flow.py +++ b/homeassistant/components/bthome/config_flow.py @@ -14,9 +14,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfo, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -47,7 +46,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -67,7 +66,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_get_encryption_key( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Enter a bindkey for an encrypted BTHome device.""" assert self._discovery_info assert self._discovered_device @@ -101,7 +100,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): return self._async_get_or_create_entry() @@ -114,7 +113,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] @@ -157,7 +156,9 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({vol.Required(CONF_ADDRESS): vol.In(titles)}), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a flow initialized by a reauth event.""" entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry is not None @@ -173,7 +174,9 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): # Otherwise there wasn't actually encryption so abort return self.async_abort(reason="reauth_successful") - def _async_get_or_create_entry(self, bindkey: str | None = None) -> FlowResult: + def _async_get_or_create_entry( + self, bindkey: str | None = None + ) -> ConfigFlowResult: data: dict[str, Any] = {} if bindkey: data["bindkey"] = bindkey diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index 1e77693f7fb..db9e6b02614 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -6,11 +6,9 @@ from typing import Any, cast import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_COUNTRY_CODE, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector import homeassistant.helpers.config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( @@ -73,7 +71,7 @@ OPTIONS_FLOW = { } -class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class BuienradarFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for buienradar.""" VERSION = 1 @@ -88,7 +86,7 @@ class BuienradarFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: lat = user_input.get(CONF_LATITUDE) diff --git a/homeassistant/components/caldav/config_flow.py b/homeassistant/components/caldav/config_flow.py index f2fa51c7f60..3710f7f1b4b 100644 --- a/homeassistant/components/caldav/config_flow.py +++ b/homeassistant/components/caldav/config_flow.py @@ -9,9 +9,8 @@ from caldav.lib.error import AuthorizationError, DAVError import requests import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import DOMAIN @@ -29,15 +28,15 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class CalDavConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for caldav.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -88,7 +87,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return "unknown" return None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -97,7 +98,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} assert self._reauth_entry diff --git a/homeassistant/components/canary/config_flow.py b/homeassistant/components/canary/config_flow.py index 6b3176f6bbd..22bc26ee0f4 100644 --- a/homeassistant/components/canary/config_flow.py +++ b/homeassistant/components/canary/config_flow.py @@ -8,10 +8,14 @@ from canary.api import Api from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_FFMPEG_ARGUMENTS, @@ -51,13 +55,13 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_import( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by configuration file.""" return await self.async_step_user(user_input) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -107,7 +111,7 @@ class CanaryOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Canary options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index e58bcb71b28..6a6bb667b82 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -5,11 +5,15 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import onboarding, zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_UUID from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import CONF_IGNORE_CEC, CONF_KNOWN_HOSTS, DOMAIN @@ -19,7 +23,7 @@ KNOWN_HOSTS_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) WANTED_UUID_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string])) -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -33,7 +37,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> CastOptionsFlowHandler: """Get the options flow for this handler.""" return CastOptionsFlowHandler(config_entry) @@ -47,7 +51,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -101,10 +105,10 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } -class CastOptionsFlowHandler(config_entries.OptionsFlow): +class CastOptionsFlowHandler(OptionsFlow): """Handle Google Cast options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Google Cast options flow.""" self.config_entry = config_entry self.updated_config: dict[str, Any] = {} diff --git a/homeassistant/components/ccm15/config_flow.py b/homeassistant/components/ccm15/config_flow.py index efde47b8d30..e4ebb758cd8 100644 --- a/homeassistant/components/ccm15/config_flow.py +++ b/homeassistant/components/ccm15/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from ccm15 import CCM15Device import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import DEFAULT_TIMEOUT, DOMAIN @@ -24,14 +23,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class CCM15ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Midea ccm15 AC Controller.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index b3ceb95d301..2201bf2714f 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -7,9 +7,8 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import SOURCE_IMPORT, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_PORT, DOMAIN from .errors import ( @@ -23,7 +22,7 @@ from .helper import get_cert_expiry_timestamp _LOGGER = logging.getLogger(__name__) -class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class CertexpiryConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -57,7 +56,7 @@ class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: Mapping[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" self._errors = {} if user_input is not None: @@ -73,7 +72,7 @@ class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): title=title, data={CONF_HOST: host, CONF_PORT: port}, ) - if self.context["source"] == config_entries.SOURCE_IMPORT: + if self.context["source"] == SOURCE_IMPORT: _LOGGER.error("Config import failed for %s", user_input[CONF_HOST]) return self.async_abort(reason="import_failed") else: @@ -97,7 +96,7 @@ class CertexpiryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import( self, user_input: Mapping[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Import a config entry. Only host was required in the yaml file all other fields are optional diff --git a/homeassistant/components/cloud/config_flow.py b/homeassistant/components/cloud/config_flow.py index a9554d97294..0cf4b941680 100644 --- a/homeassistant/components/cloud/config_flow.py +++ b/homeassistant/components/cloud/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -16,7 +15,7 @@ class CloudConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_system( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the system step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/cloudflare/config_flow.py b/homeassistant/components/cloudflare/config_flow.py index 99f6109be4a..e92ba43c503 100644 --- a/homeassistant/components/cloudflare/config_flow.py +++ b/homeassistant/components/cloudflare/config_flow.py @@ -9,10 +9,9 @@ import pycfdns import voluptuous as vol from homeassistant.components import persistent_notification -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_TOKEN, CONF_ZONE from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -85,14 +84,16 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): self.zones: list[pycfdns.ZoneModel] | None = None self.records: list[pycfdns.RecordModel] | None = None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with Cloudflare.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with Cloudflare.""" errors: dict[str, str] = {} @@ -122,7 +123,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -145,7 +146,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zone( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the picking the zone.""" errors: dict[str, str] = {} @@ -167,7 +168,7 @@ class CloudflareConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_records( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the picking the zone records.""" if user_input is not None: diff --git a/homeassistant/components/co2signal/config_flow.py b/homeassistant/components/co2signal/config_flow.py index a952f016671..37f5edaa227 100644 --- a/homeassistant/components/co2signal/config_flow.py +++ b/homeassistant/components/co2signal/config_flow.py @@ -12,15 +12,13 @@ from aioelectricitymaps import ( ) import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_COUNTRY_CODE, CONF_LATITUDE, CONF_LONGITUDE, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( @@ -38,7 +36,7 @@ TYPE_SPECIFY_COORDINATES = "specify_coordinates" TYPE_SPECIFY_COUNTRY = "specify_country_code" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Co2signal.""" VERSION = 1 @@ -47,7 +45,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" data_schema = vol.Schema( { @@ -86,7 +84,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_coordinates( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate coordinates.""" data_schema = vol.Schema( { @@ -109,7 +107,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_country( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate country.""" data_schema = vol.Schema( { @@ -125,7 +123,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): "country", data_schema, {**self._data, **user_input} ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle the reauth step.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -140,7 +140,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _validate_and_create( self, step_id: str, data_schema: vol.Schema, data: Mapping[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate data and show form if it is invalid.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/coinbase/config_flow.py b/homeassistant/components/coinbase/config_flow.py index 38053295411..3ffb93b5ab9 100644 --- a/homeassistant/components/coinbase/config_flow.py +++ b/homeassistant/components/coinbase/config_flow.py @@ -8,10 +8,15 @@ from coinbase.wallet.client import Client from coinbase.wallet.error import AuthenticationError import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from . import get_accounts @@ -48,7 +53,7 @@ def get_user_from_client(api_key, api_token): return user -async def validate_api(hass: core.HomeAssistant, data): +async def validate_api(hass: HomeAssistant, data): """Validate the credentials.""" try: @@ -72,9 +77,7 @@ async def validate_api(hass: core.HomeAssistant, data): return {"title": user["name"]} -async def validate_options( - hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry, options -): +async def validate_options(hass: HomeAssistant, config_entry: ConfigEntry, options): """Validate the requested resources are provided by API.""" client = hass.data[DOMAIN][config_entry.entry_id].client @@ -100,14 +103,14 @@ async def validate_options( return True -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class CoinbaseConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Coinbase.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is None: @@ -139,22 +142,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for Coinbase.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors = {} @@ -216,29 +219,29 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class InvalidSecret(exceptions.HomeAssistantError): +class InvalidSecret(HomeAssistantError): """Error to indicate auth failed due to invalid secret.""" -class InvalidKey(exceptions.HomeAssistantError): +class InvalidKey(HomeAssistantError): """Error to indicate auth failed due to invalid key.""" -class AlreadyConfigured(exceptions.HomeAssistantError): +class AlreadyConfigured(HomeAssistantError): """Error to indicate Coinbase API Key is already configured.""" -class CurrencyUnavailable(exceptions.HomeAssistantError): +class CurrencyUnavailable(HomeAssistantError): """Error to indicate the requested currency resource is not provided by the API.""" -class ExchangeRateUnavailable(exceptions.HomeAssistantError): +class ExchangeRateUnavailable(HomeAssistantError): """Error to indicate the requested exchange rate resource is not provided by the API.""" diff --git a/homeassistant/components/color_extractor/config_flow.py b/homeassistant/components/color_extractor/config_flow.py index 32b803d14f9..dea971913b2 100644 --- a/homeassistant/components/color_extractor/config_flow.py +++ b/homeassistant/components/color_extractor/config_flow.py @@ -3,9 +3,9 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import FlowResult, FlowResultType +from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import DEFAULT_NAME, DOMAIN @@ -18,7 +18,7 @@ class ColorExtractorConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -28,7 +28,7 @@ class ColorExtractorConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user") - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Handle import from configuration.yaml.""" result = await self.async_step_user(user_input) if result["type"] == FlowResultType.CREATE_ENTRY: diff --git a/homeassistant/components/comelit/config_flow.py b/homeassistant/components/comelit/config_flow.py index bbb671a29a7..54d5cfc0d32 100644 --- a/homeassistant/components/comelit/config_flow.py +++ b/homeassistant/components/comelit/config_flow.py @@ -13,10 +13,10 @@ from aiocomelit.api import ComelitCommonApi from aiocomelit.const import BRIDGE import voluptuous as vol -from homeassistant import core, exceptions -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from .const import _LOGGER, DEFAULT_PORT, DEVICE_TYPE_LIST, DOMAIN @@ -41,9 +41,7 @@ def user_form_schema(user_input: dict[str, Any] | None) -> vol.Schema: STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PIN): cv.positive_int}) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect.""" api: ComelitCommonApi @@ -76,7 +74,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -103,7 +101,9 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=user_form_schema(user_input), errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth flow.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -117,7 +117,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth confirm.""" assert self._reauth_entry errors = {} @@ -163,9 +163,9 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/control4/config_flow.py b/homeassistant/components/control4/config_flow.py index b93e586b7ca..d0af1d03ed7 100644 --- a/homeassistant/components/control4/config_flow.py +++ b/homeassistant/components/control4/config_flow.py @@ -9,7 +9,7 @@ from pyControl4.director import C4Director from pyControl4.error_handling import NotFound, Unauthorized import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -17,6 +17,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -86,7 +87,7 @@ class Control4Validator: return False -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class Control4ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Control4.""" VERSION = 1 @@ -137,16 +138,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for Control4.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry @@ -168,9 +169,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=data_schema) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/coolmaster/config_flow.py b/homeassistant/components/coolmaster/config_flow.py index ad3817b77ce..41a96df1e7a 100644 --- a/homeassistant/components/coolmaster/config_flow.py +++ b/homeassistant/components/coolmaster/config_flow.py @@ -7,10 +7,9 @@ from pycoolmasternet_async import CoolMasterNet import voluptuous as vol from homeassistant.components.climate import HVACMode -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import CONF_SUPPORTED_MODES, CONF_SWING_SUPPORT, DEFAULT_PORT, DOMAIN @@ -46,7 +45,7 @@ class CoolmasterConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 @callback - def _async_get_entry(self, data: dict[str, Any]) -> FlowResult: + def _async_get_entry(self, data: dict[str, Any]) -> ConfigFlowResult: supported_modes = [ key for (key, value) in data.items() if key in AVAILABLE_MODES and value ] @@ -62,7 +61,7 @@ class CoolmasterConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA) diff --git a/homeassistant/components/cpuspeed/config_flow.py b/homeassistant/components/cpuspeed/config_flow.py index 3c7d529364c..a6d760d49f3 100644 --- a/homeassistant/components/cpuspeed/config_flow.py +++ b/homeassistant/components/cpuspeed/config_flow.py @@ -5,8 +5,7 @@ from typing import Any from cpuinfo import cpuinfo -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -20,7 +19,7 @@ class CPUSpeedFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" await self.async_set_unique_id(DOMAIN) self._abort_if_unique_id_configured() diff --git a/homeassistant/components/crownstone/config_flow.py b/homeassistant/components/crownstone/config_flow.py index 7c0ea4fd27d..424d0a93314 100644 --- a/homeassistant/components/crownstone/config_flow.py +++ b/homeassistant/components/crownstone/config_flow.py @@ -14,10 +14,15 @@ from serial.tools.list_ports_common import ListPortInfo import voluptuous as vol from homeassistant.components import usb -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryBaseFlow, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowHandler, FlowResult from homeassistant.helpers import aiohttp_client from .const import ( @@ -37,13 +42,13 @@ CONFIG_FLOW = "config_flow" OPTIONS_FLOW = "options_flow" -class BaseCrownstoneFlowHandler(FlowHandler): +class BaseCrownstoneFlowHandler(ConfigEntryBaseFlow): """Represent the base flow for Crownstone.""" cloud: CrownstoneCloud def __init__( - self, flow_type: str, create_entry_cb: Callable[..., FlowResult] + self, flow_type: str, create_entry_cb: Callable[..., ConfigFlowResult] ) -> None: """Set up flow instance.""" self.flow_type = flow_type @@ -53,7 +58,7 @@ class BaseCrownstoneFlowHandler(FlowHandler): async def async_step_usb_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Set up a Crownstone USB dongle.""" list_of_ports = await self.hass.async_add_executor_job( serial.tools.list_ports.comports @@ -91,7 +96,7 @@ class BaseCrownstoneFlowHandler(FlowHandler): async def async_step_usb_manual_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manually enter Crownstone USB dongle path.""" if user_input is None: return self.async_show_form( @@ -104,7 +109,7 @@ class BaseCrownstoneFlowHandler(FlowHandler): async def async_step_usb_sphere_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select a Crownstone sphere that the USB operates in.""" spheres = {sphere.name: sphere.cloud_id for sphere in self.cloud.cloud_data} # no need to select if there's only 1 option @@ -146,7 +151,7 @@ class CrownstoneConfigFlowHandler(BaseCrownstoneFlowHandler, ConfigFlow, domain= async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is None: @@ -189,7 +194,7 @@ class CrownstoneConfigFlowHandler(BaseCrownstoneFlowHandler, ConfigFlow, domain= self.login_info = user_input return await self.async_step_usb_config() - def async_create_new_entry(self) -> FlowResult: + def async_create_new_entry(self) -> ConfigFlowResult: """Create a new entry.""" return super().async_create_entry( title=f"Account: {self.login_info[CONF_EMAIL]}", @@ -212,7 +217,7 @@ class CrownstoneOptionsFlowHandler(BaseCrownstoneFlowHandler, OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Crownstone options.""" self.cloud: CrownstoneCloud = self.hass.data[DOMAIN][self.entry.entry_id].cloud @@ -250,7 +255,7 @@ class CrownstoneOptionsFlowHandler(BaseCrownstoneFlowHandler, OptionsFlow): return self.async_show_form(step_id="init", data_schema=options_schema) - def async_create_new_entry(self) -> FlowResult: + def async_create_new_entry(self) -> ConfigFlowResult: """Create a new entry.""" # these attributes will only change when a usb was configured if self.usb_path is not None and self.usb_sphere_id is not None: diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index abd2d78c7fb..25e504f915a 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -11,10 +11,9 @@ from pydaikin.daikin_base import Appliance, DaikinException from pydaikin.discovery import Discovery import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_UUID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, KEY_MAC, TIMEOUT @@ -22,7 +21,7 @@ from .const import DOMAIN, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -49,7 +48,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): key: str | None = None, uuid: str | None = None, password: str | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Register new entry.""" if not self.unique_id: await self.async_set_unique_id(mac) @@ -68,7 +67,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _create_device( self, host: str, key: str | None = None, password: str | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create device.""" # BRP07Cxx devices needs uuid together with key if key: @@ -122,7 +121,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """User initiated config flow.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=self.schema) @@ -141,7 +140,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare configuration for a discovered Daikin device.""" _LOGGER.debug("Zeroconf user_input: %s", discovery_info) devices = Discovery().poll(ip=discovery_info.host) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 99fa6412364..3eb6b808805 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -19,13 +19,17 @@ from pydeconz.utils import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.hassio import HassioServiceInfo -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + SOURCE_HASSIO, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import ( @@ -80,7 +84,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a deCONZ config flow start. Let user choose between discovered bridges and manual configuration. @@ -126,7 +130,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_manual_input( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manual configuration.""" if user_input: self.host = user_input[CONF_HOST] @@ -145,7 +149,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to link with the deCONZ bridge.""" errors: dict[str, str] = {} @@ -173,7 +177,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="link", errors=errors) - async def _create_entry(self) -> FlowResult: + async def _create_entry(self) -> ConfigFlowResult: """Create entry for gateway.""" if not self.bridge_id: session = aiohttp_client.async_get_clientsession(self.hass) @@ -205,7 +209,9 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Trigger a reauthentication flow.""" self.context["title_placeholders"] = {CONF_HOST: entry_data[CONF_HOST]} @@ -214,7 +220,9 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_link() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered deCONZ bridge.""" if LOGGER.isEnabledFor(logging.DEBUG): LOGGER.debug("deCONZ SSDP discovery %s", pformat(discovery_info)) @@ -223,7 +231,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): parsed_url = urlparse(discovery_info.ssdp_location) entry = await self.async_set_unique_id(self.bridge_id) - if entry and entry.source == config_entries.SOURCE_HASSIO: + if entry and entry.source == SOURCE_HASSIO: return self.async_abort(reason="already_configured") self.host = cast(str, parsed_url.hostname) @@ -245,7 +253,9 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_link() - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a Hass.io deCONZ bridge. This flow is triggered by the discovery component. @@ -275,7 +285,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm a Hass.io discovery.""" if user_input is not None: @@ -299,13 +309,13 @@ class DeconzOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the deCONZ options.""" return await self.async_step_deconz_devices() async def async_step_deconz_devices( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the deconz devices options.""" if user_input is not None: self.options.update(user_input) diff --git a/homeassistant/components/deluge/config_flow.py b/homeassistant/components/deluge/config_flow.py index db2598e1f67..ae28f32767d 100644 --- a/homeassistant/components/deluge/config_flow.py +++ b/homeassistant/components/deluge/config_flow.py @@ -8,7 +8,7 @@ from typing import Any from deluge_client.client import DelugeRPCClient import voluptuous as vol -from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow +from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -16,7 +16,6 @@ from homeassistant.const import ( CONF_SOURCE, CONF_USERNAME, ) -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import ( @@ -33,7 +32,7 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -75,7 +74,9 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN): ) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" return await self.async_step_user() diff --git a/homeassistant/components/demo/config_flow.py b/homeassistant/components/demo/config_flow.py index 75439e48c08..5178b04b527 100644 --- a/homeassistant/components/demo/config_flow.py +++ b/homeassistant/components/demo/config_flow.py @@ -5,9 +5,13 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from . import DOMAIN @@ -19,7 +23,7 @@ CONF_SELECT = "select" CONF_MULTISELECT = "multi" -class DemoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DemoConfigFlow(ConfigFlow, domain=DOMAIN): """Demo configuration flow.""" VERSION = 1 @@ -27,33 +31,33 @@ class DemoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) - async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult: """Set the config entry up from yaml.""" return self.async_create_entry(title="Demo", data=import_info) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" return await self.async_step_options_1() async def async_step_options_1( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: self.options.update(user_input) @@ -78,7 +82,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_options_2( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options 2.""" if user_input is not None: self.options.update(user_input) @@ -109,6 +113,6 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ), ) - async def _update_options(self) -> FlowResult: + async def _update_options(self) -> ConfigFlowResult: """Update config entry options.""" return self.async_create_entry(title="", data=self.options) diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index e93fba09a9d..9225844285e 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -9,11 +9,15 @@ import denonavr from denonavr.exceptions import AvrNetworkError, AvrTimoutError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_TYPE from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.httpx_client import get_async_client from .receiver import ConnectDenonAVR @@ -44,16 +48,16 @@ DEFAULT_USE_TELNET_NEW_INSTALL = True CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str}) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Options for the component.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init object.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -92,7 +96,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=settings_schema) -class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DenonAvrFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Denon AVR config flow.""" VERSION = 1 @@ -111,14 +115,14 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow.""" return OptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -145,7 +149,7 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle multiple receivers found.""" errors: dict[str, str] = {} if user_input is not None: @@ -166,7 +170,7 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Allow the user to confirm adding the device.""" if user_input is not None: return await self.async_step_connect() @@ -176,7 +180,7 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_connect( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Connect to the receiver.""" assert self.host connect_denonavr = ConnectDenonAVR( @@ -230,7 +234,9 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): options={CONF_USE_TELNET: DEFAULT_USE_TELNET_NEW_INSTALL}, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered Denon AVR. This flow is triggered by the SSDP component. It will check if the diff --git a/homeassistant/components/devialet/config_flow.py b/homeassistant/components/devialet/config_flow.py index de52788de50..e244bfeb895 100644 --- a/homeassistant/components/devialet/config_flow.py +++ b/homeassistant/components/devialet/config_flow.py @@ -8,9 +8,8 @@ from devialet.devialet_api import DevialetApi import voluptuous as vol from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -31,7 +30,7 @@ class DevialetFlowHandler(ConfigFlow, domain=DOMAIN): self._serial: str | None = None self._errors: dict[str, str] = {} - async def async_validate_input(self) -> FlowResult | None: + async def async_validate_input(self) -> ConfigFlowResult | None: """Validate the input using the Devialet API.""" self._errors.clear() @@ -53,7 +52,7 @@ class DevialetFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user or zeroconf.""" if user_input is not None: @@ -70,7 +69,7 @@ class DevialetFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf discovery.""" LOGGER.info("Devialet device found via ZEROCONF: %s", discovery_info) @@ -87,7 +86,7 @@ class DevialetFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" title = f"{self._name} ({self._model})" diff --git a/homeassistant/components/devolo_home_control/config_flow.py b/homeassistant/components/devolo_home_control/config_flow.py index aef9592d2e9..9ae31432b53 100644 --- a/homeassistant/components/devolo_home_control/config_flow.py +++ b/homeassistant/components/devolo_home_control/config_flow.py @@ -6,19 +6,17 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from . import configure_mydevolo from .const import CONF_MYDEVOLO, DEFAULT_MYDEVOLO, DOMAIN, SUPPORTED_MODEL_TYPES from .exceptions import CredentialsInvalid, UuidChanged -class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DevoloHomeControlFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a devolo HomeControl config flow.""" VERSION = 1 @@ -34,7 +32,7 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if self.show_advanced_options: self.data_schema[vol.Required(CONF_MYDEVOLO, default=self._url)] = str @@ -47,7 +45,7 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" # Check if it is a gateway if discovery_info.properties.get("MT") in SUPPORTED_MODEL_TYPES: @@ -57,7 +55,7 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is None: return self._show_form(step_id="zeroconf_confirm") @@ -68,7 +66,9 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="zeroconf_confirm", errors={"base": "invalid_auth"} ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauthentication.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -82,7 +82,7 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by reauthentication.""" if user_input is None: return self._show_form(step_id="reauth_confirm") @@ -97,7 +97,7 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="reauth_confirm", errors={"base": "reauth_failed"} ) - async def _connect_mydevolo(self, user_input: dict[str, Any]) -> FlowResult: + async def _connect_mydevolo(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Connect to mydevolo.""" user_input[CONF_MYDEVOLO] = user_input.get(CONF_MYDEVOLO, self._url) mydevolo = configure_mydevolo(conf=user_input) @@ -135,7 +135,7 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @callback def _show_form( self, step_id: str, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the form to the user.""" return self.async_show_form( step_id=step_id, diff --git a/homeassistant/components/devolo_home_network/config_flow.py b/homeassistant/components/devolo_home_network/config_flow.py index 08892e19e4e..3f0ec0a02b7 100644 --- a/homeassistant/components/devolo_home_network/config_flow.py +++ b/homeassistant/components/devolo_home_network/config_flow.py @@ -9,10 +9,10 @@ from devolo_plc_api.device import Device from devolo_plc_api.exceptions.device import DeviceNotFound import voluptuous as vol -from homeassistant import config_entries, core from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS, CONF_NAME, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant from homeassistant.helpers.httpx_client import get_async_client from .const import DOMAIN, PRODUCT, SERIAL_NUMBER, TITLE @@ -23,9 +23,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_IP_ADDRESS): str}) STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Optional(CONF_PASSWORD): str}) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. @@ -44,14 +42,14 @@ async def validate_input( } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DevoloHomeNetworkConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for devolo Home Network.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict = {} @@ -79,7 +77,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" if discovery_info.properties["MT"] in ["2600", "2601"]: return self.async_abort(reason="home_control") @@ -99,7 +97,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" title = self.context["title_placeholders"][CONF_NAME] if user_input is not None: @@ -113,7 +111,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={"host_name": title}, ) - async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, data: Mapping[str, Any]) -> ConfigFlowResult: """Handle reauthentication.""" self.context[CONF_HOST] = data[CONF_IP_ADDRESS] self.context["title_placeholders"][PRODUCT] = self.hass.data[DOMAIN][ @@ -123,7 +121,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by reauthentication.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/dexcom/config_flow.py b/homeassistant/components/dexcom/config_flow.py index 6ccb09881af..b099a97e0b2 100644 --- a/homeassistant/components/dexcom/config_flow.py +++ b/homeassistant/components/dexcom/config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations from pydexcom import AccountError, Dexcom, SessionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_PASSWORD, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME from homeassistant.core import callback @@ -19,7 +19,7 @@ DATA_SCHEMA = vol.Schema( ) -class DexcomConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DexcomConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Dexcom.""" VERSION = 1 @@ -56,16 +56,16 @@ class DexcomConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> DexcomOptionsFlowHandler: """Get the options flow for this handler.""" return DexcomOptionsFlowHandler(config_entry) -class DexcomOptionsFlowHandler(config_entries.OptionsFlow): +class DexcomOptionsFlowHandler(OptionsFlow): """Handle a option flow for Dexcom.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/directv/config_flow.py b/homeassistant/components/directv/config_flow.py index b3209638012..6c5654b185e 100644 --- a/homeassistant/components/directv/config_flow.py +++ b/homeassistant/components/directv/config_flow.py @@ -9,10 +9,9 @@ from directv import DIRECTV, DIRECTVError import voluptuous as vol from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_RECEIVER_ID, DOMAIN @@ -46,7 +45,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -66,7 +65,9 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=user_input[CONF_HOST], data=user_input) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle SSDP discovery.""" host = urlparse(discovery_info.ssdp_location).hostname receiver_id = None @@ -101,7 +102,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_ssdp_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a confirmation flow initiated by SSDP.""" if user_input is None: return self.async_show_form( @@ -115,7 +116,7 @@ class DirecTVConfigFlow(ConfigFlow, domain=DOMAIN): data=self.discovery_info, ) - def _show_setup_form(self, errors: dict | None = None) -> FlowResult: + def _show_setup_form(self, errors: dict | None = None) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/discord/config_flow.py b/homeassistant/components/discord/config_flow.py index 14c2cc6c040..e93f2cc565f 100644 --- a/homeassistant/components/discord/config_flow.py +++ b/homeassistant/components/discord/config_flow.py @@ -9,9 +9,8 @@ from aiohttp.client_exceptions import ClientConnectorError import nextcord import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_TOKEN, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, URL_PLACEHOLDER @@ -20,16 +19,18 @@ _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema({vol.Required(CONF_API_TOKEN): str}) -class DiscordFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DiscordFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Discord.""" - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} @@ -52,7 +53,7 @@ class DiscordFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} diff --git a/homeassistant/components/discovergy/config_flow.py b/homeassistant/components/discovergy/config_flow.py index d0e0c272d24..ab57bb138de 100644 --- a/homeassistant/components/discovergy/config_flow.py +++ b/homeassistant/components/discovergy/config_flow.py @@ -10,10 +10,8 @@ from pydiscovergy.authentication import BasicAuth import pydiscovergy.error as discovergyError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.selector import ( TextSelector, @@ -48,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DiscovergyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Discovergy.""" VERSION = 1 @@ -57,7 +55,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -67,14 +65,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._validate_and_save(user_input) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle the initial step.""" self._existing_entry = await self.async_set_unique_id(self.context["unique_id"]) return await self._validate_and_save(entry_data, step_id="reauth") async def _validate_and_save( self, user_input: Mapping[str, Any] | None = None, step_id: str = "user" - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate user input and create config entry.""" errors = {} diff --git a/homeassistant/components/dlink/config_flow.py b/homeassistant/components/dlink/config_flow.py index 09df5571a78..a600c0e148f 100644 --- a/homeassistant/components/dlink/config_flow.py +++ b/homeassistant/components/dlink/config_flow.py @@ -7,24 +7,25 @@ from typing import Any from pyW215.pyW215 import SmartPlug import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_USE_LEGACY_PROTOCOL, DEFAULT_NAME, DEFAULT_USERNAME, DOMAIN _LOGGER = logging.getLogger(__name__) -class DLinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DLinkFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for D-Link Power Plug.""" def __init__(self) -> None: """Initialize a D-Link Power Plug flow.""" self.ip_address: str | None = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" await self.async_set_unique_id(discovery_info.macaddress) self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip}) @@ -41,7 +42,7 @@ class DLinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Allow the user to confirm adding the device.""" errors = {} if user_input is not None: @@ -74,7 +75,7 @@ class DLinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/dlna_dmr/config_flow.py b/homeassistant/components/dlna_dmr/config_flow.py index 1ad29c72c26..c98ed6e6dce 100644 --- a/homeassistant/components/dlna_dmr/config_flow.py +++ b/homeassistant/components/dlna_dmr/config_flow.py @@ -15,11 +15,15 @@ from async_upnp_client.profiles.profile import find_device_of_type from getmac import get_mac_address import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_DEVICE_ID, CONF_HOST, CONF_MAC, CONF_TYPE, CONF_URL from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import IntegrationError from homeassistant.helpers import config_validation as cv, device_registry as dr @@ -42,7 +46,7 @@ class ConnectError(IntegrationError): """Error occurred when trying to connect to a device.""" -class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DlnaDmrFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a DLNA DMR config flow. The Unique Device Name (UDN) of the DMR device is used as the unique_id for @@ -65,12 +69,12 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Define the config flow to handle options.""" return DlnaDmrOptionsFlowHandler(config_entry) - async def async_step_user(self, user_input: FlowInput = None) -> FlowResult: + async def async_step_user(self, user_input: FlowInput = None) -> ConfigFlowResult: """Handle a flow initialized by the user. Let user choose from a list of found and unconfigured devices or to @@ -102,7 +106,7 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_show_form(step_id="user", data_schema=data_schema) - async def async_step_manual(self, user_input: FlowInput = None) -> FlowResult: + async def async_step_manual(self, user_input: FlowInput = None) -> ConfigFlowResult: """Manual URL entry by the user.""" LOGGER.debug("async_step_manual: user_input: %s", user_input) @@ -124,7 +128,9 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="manual", data_schema=data_schema, errors=errors ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by SSDP discovery.""" if LOGGER.isEnabledFor(logging.DEBUG): LOGGER.debug("async_step_ssdp: discovery_info %s", pformat(discovery_info)) @@ -151,7 +157,9 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() - async def async_step_ignore(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_ignore( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Ignore this config flow, and add MAC address as secondary identifier. Not all DMR devices correctly implement the spec, so their UDN may @@ -185,7 +193,9 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_unignore(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_unignore( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Rediscover previously ignored devices by their unique_id.""" LOGGER.debug("async_step_unignore: user_input: %s", user_input) self._udn = user_input["unique_id"] @@ -208,7 +218,9 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() - async def async_step_confirm(self, user_input: FlowInput = None) -> FlowResult: + async def async_step_confirm( + self, user_input: FlowInput = None + ) -> ConfigFlowResult: """Allow the user to confirm adding the device.""" LOGGER.debug("async_step_confirm: %s", user_input) @@ -256,7 +268,7 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if not self._mac and (host := urlparse(self._location).hostname): self._mac = await _async_get_mac_address(self.hass, host) - def _create_entry(self) -> FlowResult: + def _create_entry(self) -> ConfigFlowResult: """Create a config entry, assuming all required information is now known.""" LOGGER.debug( "_async_create_entry: location: %s, UDN: %s", self._location, self._udn @@ -333,19 +345,19 @@ class DlnaDmrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return discoveries -class DlnaDmrOptionsFlowHandler(config_entries.OptionsFlow): +class DlnaDmrOptionsFlowHandler(OptionsFlow): """Handle a DLNA DMR options flow. Configures the single instance and updates the existing config entry. """ - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors: dict[str, str] = {} # Don't modify existing (read-only) options -- copy and update instead diff --git a/homeassistant/components/dlna_dms/config_flow.py b/homeassistant/components/dlna_dms/config_flow.py index e147055df05..0df2068ec71 100644 --- a/homeassistant/components/dlna_dms/config_flow.py +++ b/homeassistant/components/dlna_dms/config_flow.py @@ -9,10 +9,10 @@ from urllib.parse import urlparse from async_upnp_client.profiles.dlna import DmsDevice import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE_ID, CONF_HOST, CONF_URL -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from .const import CONF_SOURCE_ID, CONFIG_VERSION, DEFAULT_NAME, DOMAIN from .util import generate_source_id @@ -20,7 +20,7 @@ from .util import generate_source_id LOGGER = logging.getLogger(__name__) -class DlnaDmsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DlnaDmsFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a DLNA DMS config flow. The Unique Service Name (USN) of the DMS device is used as the unique_id for @@ -39,7 +39,7 @@ class DlnaDmsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user by listing unconfigured devices.""" LOGGER.debug("async_step_user: user_input: %s", user_input) @@ -65,7 +65,9 @@ class DlnaDmsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema = vol.Schema({vol.Optional(CONF_HOST): vol.In(discovery_choices)}) return self.async_show_form(step_id="user", data_schema=data_schema) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by SSDP discovery.""" if LOGGER.isEnabledFor(logging.DEBUG): LOGGER.debug("async_step_ssdp: discovery_info %s", pformat(discovery_info)) @@ -101,7 +103,7 @@ class DlnaDmsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Allow the user to confirm adding the device.""" if user_input is not None: return self._create_entry() @@ -109,7 +111,7 @@ class DlnaDmsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._set_confirm_only() return self.async_show_form(step_id="confirm") - def _create_entry(self) -> FlowResult: + def _create_entry(self) -> ConfigFlowResult: """Create a config entry, assuming all required information is now known.""" LOGGER.debug( "_create_entry: name: %s, location: %s, USN: %s", diff --git a/homeassistant/components/dnsip/config_flow.py b/homeassistant/components/dnsip/config_flow.py index 6a1437d5159..b9e3c2a2dfe 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -9,10 +9,14 @@ import aiodns from aiodns.error import DNSError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import ( @@ -72,7 +76,7 @@ async def async_validate_hostname( return result -class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for dnsip integration.""" VERSION = 1 @@ -80,14 +84,14 @@ class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> DnsIPOptionsFlowHandler: """Return Option handler.""" return DnsIPOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -141,16 +145,16 @@ class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class DnsIPOptionsFlowHandler(config_entries.OptionsFlow): +class DnsIPOptionsFlowHandler(OptionsFlow): """Handle a option config flow for dnsip integration.""" - def __init__(self, entry: config_entries.ConfigEntry) -> None: + def __init__(self, entry: ConfigEntry) -> None: """Initialize options flow.""" self.entry = entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/doorbird/config_flow.py b/homeassistant/components/doorbird/config_flow.py index 983e56e64da..b91c072f85b 100644 --- a/homeassistant/components/doorbird/config_flow.py +++ b/homeassistant/components/doorbird/config_flow.py @@ -9,11 +9,16 @@ from doorbirdpy import DoorBird import requests import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from .const import CONF_EVENTS, DOMAIN, DOORBIRD_OUI from .util import get_mac_address_from_door_station_info @@ -39,9 +44,7 @@ def _check_device(device: DoorBird) -> tuple[tuple[bool, int], dict[str, Any]]: return device.ready(), device.info() -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect.""" device = DoorBird(data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD]) try: @@ -75,7 +78,7 @@ async def async_verify_supported_device(hass: HomeAssistant, host: str) -> bool: return False -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DoorBirdConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for DoorBird.""" VERSION = 1 @@ -86,7 +89,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -101,7 +104,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare configuration for a discovered doorbird device.""" macaddress = discovery_info.properties["macaddress"] @@ -152,22 +155,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for doorbird.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: events = [event.strip() for event in user_input[CONF_EVENTS].split(",")] @@ -184,9 +187,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=options_schema) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/dormakaba_dkey/config_flow.py b/homeassistant/components/dormakaba_dkey/config_flow.py index f03861d015e..c2cdf1854e2 100644 --- a/homeassistant/components/dormakaba_dkey/config_flow.py +++ b/homeassistant/components/dormakaba_dkey/config_flow.py @@ -9,14 +9,13 @@ from bleak import BleakError from py_dormakaba_dkey import DKEYLock, device_filter, errors as dkey_errors import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, async_last_service_info, ) +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import CONF_ASSOCIATION_DATA, DOMAIN @@ -29,12 +28,12 @@ STEP_ASSOCIATE_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DormkabaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Dormakaba dKey.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None def __init__(self) -> None: """Initialize the config flow.""" @@ -46,7 +45,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} @@ -92,7 +91,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the Bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -103,7 +102,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle bluetooth confirm step.""" # mypy is not aware that we can't get here without having these set already assert self._discovery_info is not None @@ -117,7 +116,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_associate() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauthorization request.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -126,7 +127,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" errors = {} reauth_entry = self._reauth_entry @@ -149,7 +150,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_associate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle associate step.""" # mypy is not aware that we can't get here without having these set already assert self._discovery_info is not None diff --git a/homeassistant/components/dremel_3d_printer/config_flow.py b/homeassistant/components/dremel_3d_printer/config_flow.py index 6fa4d2e0a5b..f9dc96b8287 100644 --- a/homeassistant/components/dremel_3d_printer/config_flow.py +++ b/homeassistant/components/dremel_3d_printer/config_flow.py @@ -8,9 +8,8 @@ from dremel3dpy import Dremel3DPrinter from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import DOMAIN, LOGGER @@ -20,14 +19,14 @@ def _schema_with_defaults(host: str = "") -> vol.Schema: return vol.Schema({vol.Required(CONF_HOST, default=host): cv.string}) -class Dremel3DPrinterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class Dremel3DPrinterConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Dremel 3D Printer.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/drop_connect/config_flow.py b/homeassistant/components/drop_connect/config_flow.py index a2b93ad1da1..c315616ceba 100644 --- a/homeassistant/components/drop_connect/config_flow.py +++ b/homeassistant/components/drop_connect/config_flow.py @@ -6,8 +6,7 @@ from typing import TYPE_CHECKING, Any from dropmqttapi.discovery import DropDiscovery -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from .const import ( @@ -26,14 +25,16 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle DROP config flow.""" VERSION = 1 _drop_discovery: DropDiscovery | None = None - async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: + async def async_step_mqtt( + self, discovery_info: MqttServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by MQTT discovery.""" # Abort if the topic does not match our discovery topic or the payload is empty. @@ -64,7 +65,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the setup.""" if TYPE_CHECKING: assert self._drop_discovery is not None @@ -93,6 +94,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return self.async_abort(reason="not_supported") diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index a38326c1346..422ae285b4c 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -17,11 +17,15 @@ import serial import serial.tools.list_ports import voluptuous as vol -from homeassistant import config_entries, core, exceptions -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PROTOCOL, CONF_TYPE -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from .const import ( CONF_DSMR_VERSION, @@ -75,7 +79,7 @@ class DSMRConnection: return identifier return None - async def validate_connect(self, hass: core.HomeAssistant) -> bool: + async def validate_connect(self, hass: HomeAssistant) -> bool: """Test if we can validate connection with the device.""" def update_telegram(telegram: dict[str, DSMRObject]) -> None: @@ -131,7 +135,7 @@ class DSMRConnection: async def _validate_dsmr_connection( - hass: core.HomeAssistant, data: dict[str, Any], protocol: str + hass: HomeAssistant, data: dict[str, Any], protocol: str ) -> dict[str, str | None]: """Validate the user input allows us to connect.""" conn = DSMRConnection( @@ -157,7 +161,7 @@ async def _validate_dsmr_connection( } -class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DSMRFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for DSMR.""" VERSION = 1 @@ -172,7 +176,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" if user_input is not None: user_selection = user_input[CONF_TYPE] @@ -188,7 +192,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_network( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when setting up network configuration.""" errors: dict[str, str] = {} if user_input is not None: @@ -213,7 +217,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_serial( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when setting up serial configuration.""" errors: dict[str, str] = {} if user_input is not None: @@ -257,7 +261,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_serial_manual_path( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select path manually.""" if user_input is not None: validate_data = { @@ -303,7 +307,7 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return data -class DSMROptionFlowHandler(config_entries.OptionsFlow): +class DSMROptionFlowHandler(OptionsFlow): """Handle options.""" def __init__(self, entry: ConfigEntry) -> None: @@ -312,7 +316,7 @@ class DSMROptionFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -344,9 +348,9 @@ def get_serial_by_id(dev_path: str) -> str: return dev_path -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class CannotCommunicate(exceptions.HomeAssistantError): +class CannotCommunicate(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/dsmr_reader/config_flow.py b/homeassistant/components/dsmr_reader/config_flow.py index 44ff6663654..87b0e562efd 100644 --- a/homeassistant/components/dsmr_reader/config_flow.py +++ b/homeassistant/components/dsmr_reader/config_flow.py @@ -4,8 +4,8 @@ from __future__ import annotations from collections.abc import Awaitable from typing import Any +from homeassistant.config_entries import ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.config_entry_flow import DiscoveryFlowHandler from .const import DOMAIN @@ -27,7 +27,7 @@ class DsmrReaderFlowHandler(DiscoveryFlowHandler[Awaitable[bool]], domain=DOMAIN async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm setup.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/dunehd/config_flow.py b/homeassistant/components/dunehd/config_flow.py index fac2e245633..718d5b4ee6f 100644 --- a/homeassistant/components/dunehd/config_flow.py +++ b/homeassistant/components/dunehd/config_flow.py @@ -6,15 +6,15 @@ from typing import Any from pdunehd import DuneHDPlayer import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.util.network import is_host_valid from .const import DOMAIN -class DuneHDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DuneHDConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Dune HD integration.""" VERSION = 1 @@ -28,7 +28,7 @@ class DuneHDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -63,9 +63,9 @@ class DuneHDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return host in existing_hosts -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class AlreadyConfigured(exceptions.HomeAssistantError): +class AlreadyConfigured(HomeAssistantError): """Error to indicate device is already configured.""" diff --git a/homeassistant/components/duotecno/config_flow.py b/homeassistant/components/duotecno/config_flow.py index 6f08b025835..069988dbb17 100644 --- a/homeassistant/components/duotecno/config_flow.py +++ b/homeassistant/components/duotecno/config_flow.py @@ -8,9 +8,8 @@ from duotecno.controller import PyDuotecno from duotecno.exceptions import InvalidPassword import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -25,14 +24,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DuoTecnoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for duotecno.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/dwd_weather_warnings/config_flow.py b/homeassistant/components/dwd_weather_warnings/config_flow.py index 1e0bb797c7a..5076dbae187 100644 --- a/homeassistant/components/dwd_weather_warnings/config_flow.py +++ b/homeassistant/components/dwd_weather_warnings/config_flow.py @@ -7,8 +7,7 @@ from typing import Any from dwdwfsapi import DwdWeatherWarningsAPI import voluptuous as vol -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult import homeassistant.helpers.config_validation as cv from .const import CONF_REGION_IDENTIFIER, DOMAIN @@ -21,7 +20,7 @@ class DwdWeatherWarningsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict = {} diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index 7cced80c97e..2bb4327f517 100644 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -5,10 +5,9 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -17,7 +16,7 @@ from .const import DEFAULT_PORT, DOMAIN, LOGGER from .convert_config import convert_config -class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class DynaliteFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Dynalite config flow.""" VERSION = 1 @@ -26,7 +25,7 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the Dynalite flow.""" self.host = None - async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult: """Import a new bridge as a config entry.""" LOGGER.debug("Starting async_step_import (deprecated) - %s", import_info) # Raise an issue that this is deprecated and has been imported @@ -60,7 +59,7 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" if user_input is not None: return await self._try_create(user_input) @@ -73,7 +72,7 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_show_form(step_id="user", data_schema=schema) - async def _try_create(self, info: dict[str, Any]) -> FlowResult: + async def _try_create(self, info: dict[str, Any]) -> ConfigFlowResult: """Try to connect and if successful, create entry.""" host = info[CONF_HOST] configured_hosts = [ diff --git a/tests/components/aussie_broadband/test_init.py b/tests/components/aussie_broadband/test_init.py index e16a721f5dc..9ac7f6628b4 100644 --- a/tests/components/aussie_broadband/test_init.py +++ b/tests/components/aussie_broadband/test_init.py @@ -22,7 +22,7 @@ async def test_unload(hass: HomeAssistant) -> None: async def test_auth_failure(hass: HomeAssistant) -> None: """Test init with an authentication failure.""" with patch( - "homeassistant.components.aussie_broadband.config_flow.ConfigFlow.async_step_reauth", + "homeassistant.components.aussie_broadband.config_flow.AussieBroadbandConfigFlow.async_step_reauth", return_value={ "type": data_entry_flow.FlowResultType.FORM, "flow_id": "mock_flow", From e06446d0fa0ad7b13482b3e0e50959a02f3e2299 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 20:07:36 +0100 Subject: [PATCH 0060/1691] Migrate integrations e-h to generic flowhandler (#111862) --- homeassistant/components/eafm/config_flow.py | 4 +- .../components/easyenergy/config_flow.py | 5 +- .../components/ecobee/config_flow.py | 4 +- .../components/ecoforest/config_flow.py | 7 +-- .../components/econet/config_flow.py | 4 +- .../components/ecovacs/config_flow.py | 10 +-- .../components/ecowitt/config_flow.py | 7 +-- homeassistant/components/edl21/config_flow.py | 7 +-- .../components/efergy/config_flow.py | 11 ++-- .../components/electrasmart/config_flow.py | 19 +++--- .../components/electric_kiwi/config_flow.py | 11 ++-- .../components/elgato/config_flow.py | 13 ++-- homeassistant/components/elkm1/config_flow.py | 28 +++++---- homeassistant/components/elmax/config_flow.py | 17 ++--- homeassistant/components/elvia/config_flow.py | 9 +-- .../components/emonitor/config_flow.py | 12 ++-- .../components/emulated_roku/config_flow.py | 4 +- .../components/energyzero/config_flow.py | 5 +- .../components/enocean/config_flow.py | 4 +- .../components/enphase_envoy/config_flow.py | 15 ++--- .../environment_canada/config_flow.py | 4 +- homeassistant/components/epion/config_flow.py | 5 +- homeassistant/components/epson/config_flow.py | 4 +- .../components/esphome/config_flow.py | 42 ++++++++----- .../components/eufylife_ble/config_flow.py | 9 ++- .../evil_genius_labs/config_flow.py | 7 +-- homeassistant/components/ezviz/config_flow.py | 26 +++++--- .../components/faa_delays/config_flow.py | 7 +-- .../components/fastdotcom/config_flow.py | 7 +-- .../components/fibaro/config_flow.py | 15 ++--- .../components/filesize/config_flow.py | 5 +- .../components/fireservicerota/config_flow.py | 11 ++-- .../components/firmata/config_flow.py | 9 +-- .../components/fitbit/config_flow.py | 15 ++--- homeassistant/components/fivem/config_flow.py | 7 +-- .../components/flexit_bacnet/config_flow.py | 7 +-- .../components/flick_electric/config_flow.py | 9 +-- homeassistant/components/flipr/config_flow.py | 9 ++- homeassistant/components/flo/config_flow.py | 10 +-- homeassistant/components/flume/config_flow.py | 23 ++++--- .../components/flux_led/config_flow.py | 49 +++++++++------ .../components/forecast_solar/config_flow.py | 12 ++-- .../components/forked_daapd/config_flow.py | 18 +++--- .../components/foscam/config_flow.py | 11 ++-- .../components/freebox/config_flow.py | 11 ++-- .../components/freedompro/config_flow.py | 12 ++-- homeassistant/components/fritz/config_flow.py | 28 +++++---- .../components/fritzbox/config_flow.py | 19 +++--- .../fritzbox_callmonitor/config_flow.py | 27 ++++---- .../components/fronius/config_flow.py | 13 ++-- .../frontier_silicon/config_flow.py | 21 ++++--- .../components/fully_kiosk/config_flow.py | 19 +++--- .../garages_amsterdam/config_flow.py | 7 +-- .../gardena_bluetooth/config_flow.py | 12 ++-- homeassistant/components/gdacs/config_flow.py | 11 ++-- .../components/generic/config_flow.py | 17 +++-- .../components/geo_json_events/config_flow.py | 7 +-- .../components/geocaching/config_flow.py | 10 +-- .../components/geonetnz_quakes/config_flow.py | 4 +- .../geonetnz_volcano/config_flow.py | 4 +- homeassistant/components/gios/config_flow.py | 7 +-- .../components/github/config_flow.py | 26 ++++---- .../components/glances/config_flow.py | 15 ++--- .../components/goalzero/config_flow.py | 13 ++-- .../components/gogogate2/config_flow.py | 14 +++-- .../components/goodwe/config_flow.py | 7 +-- .../components/google/config_flow.py | 29 ++++----- .../google_assistant/config_flow.py | 4 +- .../google_assistant_sdk/config_flow.py | 22 +++---- .../config_flow.py | 24 ++++--- .../components/google_mail/config_flow.py | 11 ++-- .../components/google_sheets/config_flow.py | 11 ++-- .../components/google_tasks/config_flow.py | 4 +- .../google_translate/config_flow.py | 9 ++- .../google_travel_time/config_flow.py | 20 +++--- .../components/govee_ble/config_flow.py | 9 ++- homeassistant/components/gpsd/config_flow.py | 9 ++- .../components/growatt_server/config_flow.py | 4 +- .../components/guardian/config_flow.py | 17 ++--- .../components/habitica/config_flow.py | 12 ++-- .../components/hardkernel/config_flow.py | 7 ++- .../components/harmony/config_flow.py | 31 +++++---- .../components/hassio/config_flow.py | 7 +-- homeassistant/components/heos/config_flow.py | 9 +-- .../here_travel_time/config_flow.py | 40 ++++++------ homeassistant/components/hive/config_flow.py | 27 +++++--- homeassistant/components/hko/config_flow.py | 7 +-- .../components/hlk_sw16/config_flow.py | 4 +- .../components/holiday/config_flow.py | 9 ++- .../homeassistant_green/config_flow.py | 16 +++-- .../silabs_multiprotocol_addon.py | 63 ++++++++++--------- .../homeassistant_sky_connect/config_flow.py | 7 ++- .../homeassistant_yellow/config_flow.py | 21 ++++--- .../components/homekit/config_flow.py | 42 +++++++------ .../homekit_controller/config_flow.py | 26 ++++---- .../homematicip_cloud/config_flow.py | 13 ++-- .../components/homewizard/config_flow.py | 16 ++--- .../components/honeywell/config_flow.py | 28 +++++---- .../components/huawei_lte/config_flow.py | 34 ++++++---- homeassistant/components/hue/config_flow.py | 38 ++++++----- .../components/huisbaasje/config_flow.py | 4 +- .../hunterdouglas_powerview/config_flow.py | 27 ++++---- .../husqvarna_automower/config_flow.py | 4 +- homeassistant/components/huum/config_flow.py | 3 +- .../components/hvv_departures/config_flow.py | 10 +-- .../components/hydrawise/config_flow.py | 18 +++--- .../components/hyperion/config_flow.py | 28 +++++---- 107 files changed, 842 insertions(+), 703 deletions(-) diff --git a/homeassistant/components/eafm/config_flow.py b/homeassistant/components/eafm/config_flow.py index 75e5a62a2a0..a29905a00b0 100644 --- a/homeassistant/components/eafm/config_flow.py +++ b/homeassistant/components/eafm/config_flow.py @@ -2,13 +2,13 @@ from aioeafm import get_stations import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN -class UKFloodsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class UKFloodsFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a UK Environment Agency flood monitoring config flow.""" VERSION = 1 diff --git a/homeassistant/components/easyenergy/config_flow.py b/homeassistant/components/easyenergy/config_flow.py index a8c23f2c6e2..9a196061391 100644 --- a/homeassistant/components/easyenergy/config_flow.py +++ b/homeassistant/components/easyenergy/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -16,7 +15,7 @@ class EasyEnergyFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" await self.async_set_unique_id(DOMAIN) diff --git a/homeassistant/components/ecobee/config_flow.py b/homeassistant/components/ecobee/config_flow.py index 0bd7306e54e..5f0066b6c10 100644 --- a/homeassistant/components/ecobee/config_flow.py +++ b/homeassistant/components/ecobee/config_flow.py @@ -7,7 +7,7 @@ from pyecobee import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_API_KEY from homeassistant.exceptions import HomeAssistantError from homeassistant.util.json import load_json_object @@ -15,7 +15,7 @@ from homeassistant.util.json import load_json_object from .const import _LOGGER, CONF_REFRESH_TOKEN, DATA_ECOBEE_CONFIG, DOMAIN -class EcobeeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class EcobeeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an ecobee config flow.""" VERSION = 1 diff --git a/homeassistant/components/ecoforest/config_flow.py b/homeassistant/components/ecoforest/config_flow.py index 0afc46c2370..1e535870e1c 100644 --- a/homeassistant/components/ecoforest/config_flow.py +++ b/homeassistant/components/ecoforest/config_flow.py @@ -9,9 +9,8 @@ from pyecoforest.api import EcoforestApi from pyecoforest.exceptions import EcoforestAuthenticationRequired import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, MANUFACTURER @@ -26,14 +25,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EcoForestConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Ecoforest.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/econet/config_flow.py b/homeassistant/components/econet/config_flow.py index d25ee7973fd..237047b4974 100644 --- a/homeassistant/components/econet/config_flow.py +++ b/homeassistant/components/econet/config_flow.py @@ -3,13 +3,13 @@ from pyeconet import EcoNetApiInterface from pyeconet.errors import InvalidCredentialsError, PyeconetError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from .const import DOMAIN -class EcoNetFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class EcoNetFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an EcoNet config flow.""" VERSION = 1 diff --git a/homeassistant/components/ecovacs/config_flow.py b/homeassistant/components/ecovacs/config_flow.py index db3c60fa9e7..fbf9ac0911a 100644 --- a/homeassistant/components/ecovacs/config_flow.py +++ b/homeassistant/components/ecovacs/config_flow.py @@ -15,10 +15,10 @@ from deebot_client.util import md5 from deebot_client.util.continents import COUNTRIES_TO_CONTINENTS, get_continent import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_COUNTRY, CONF_MODE, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import aiohttp_client, selector from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.loader import async_get_issue_tracker @@ -136,7 +136,7 @@ class EcovacsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if not self.show_advanced_options: @@ -166,7 +166,7 @@ class EcovacsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_auth( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the auth step.""" errors = {} @@ -217,7 +217,7 @@ class EcovacsConfigFlow(ConfigFlow, domain=DOMAIN): last_step=True, ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Import configuration from yaml.""" def create_repair( diff --git a/homeassistant/components/ecowitt/config_flow.py b/homeassistant/components/ecowitt/config_flow.py index 2b6744790bf..cc32405072c 100644 --- a/homeassistant/components/ecowitt/config_flow.py +++ b/homeassistant/components/ecowitt/config_flow.py @@ -6,17 +6,16 @@ from typing import Any from yarl import URL -from homeassistant import config_entries from homeassistant.components import webhook +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_WEBHOOK_ID -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.network import get_url from .const import DOMAIN -class EcowittConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EcowittConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for the Ecowitt.""" VERSION = 1 @@ -24,7 +23,7 @@ class EcowittConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: self._webhook_id = secrets.token_hex(16) diff --git a/homeassistant/components/edl21/config_flow.py b/homeassistant/components/edl21/config_flow.py index 0bedcc515ef..5a5db507bff 100644 --- a/homeassistant/components/edl21/config_flow.py +++ b/homeassistant/components/edl21/config_flow.py @@ -2,8 +2,7 @@ import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import CONF_SERIAL_PORT, DEFAULT_TITLE, DOMAIN @@ -14,14 +13,14 @@ DATA_SCHEMA = vol.Schema( ) -class EDL21ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EDL21ConfigFlow(ConfigFlow, domain=DOMAIN): """EDL21 config flow.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user setup step.""" if user_input is not None: self._async_abort_entries_match( diff --git a/homeassistant/components/efergy/config_flow.py b/homeassistant/components/efergy/config_flow.py index b2f2a368a9e..487bd6ea510 100644 --- a/homeassistant/components/efergy/config_flow.py +++ b/homeassistant/components/efergy/config_flow.py @@ -7,22 +7,21 @@ from typing import Any from pyefergy import Efergy, exceptions import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DEFAULT_NAME, DOMAIN, LOGGER -class EfergyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class EfergyFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Efergy.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: @@ -53,7 +52,9 @@ class EfergyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" return await self.async_step_user() diff --git a/homeassistant/components/electrasmart/config_flow.py b/homeassistant/components/electrasmart/config_flow.py index 946a9f2854d..818480080c9 100644 --- a/homeassistant/components/electrasmart/config_flow.py +++ b/homeassistant/components/electrasmart/config_flow.py @@ -8,9 +8,8 @@ from electrasmart.api import STATUS_SUCCESS, Attributes, ElectraAPI, ElectraApiE from electrasmart.api.utils import generate_imei import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_IMEI, CONF_OTP, CONF_PHONE_NUMBER, DOMAIN @@ -18,7 +17,7 @@ from .const import CONF_IMEI, CONF_OTP, CONF_PHONE_NUMBER, DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ElectraSmartConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Electra Air Conditioner.""" VERSION = 1 @@ -34,7 +33,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if not self._api: @@ -52,7 +51,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): user_input: dict[str, str] | None = None, errors: dict[str, str] | None = None, step_id: str = "user", - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" if user_input is None: user_input = {} @@ -73,7 +72,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=self._description_placeholders, ) - async def _validate_phone_number(self, user_input: dict[str, str]) -> FlowResult: + async def _validate_phone_number( + self, user_input: dict[str, str] + ) -> ConfigFlowResult: """Check if config is valid and create entry if so.""" self._phone_number = user_input[CONF_PHONE_NUMBER] @@ -102,7 +103,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _validate_one_time_password( self, user_input: dict[str, str] - ) -> FlowResult: + ) -> ConfigFlowResult: self._otp = user_input[CONF_OTP] assert isinstance(self._api, ElectraAPI) @@ -135,7 +136,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Ask the verification code to the user.""" if errors is None: errors = {} @@ -148,7 +149,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _show_otp_form( self, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the verification_code form to the user.""" return self.async_show_form( diff --git a/homeassistant/components/electric_kiwi/config_flow.py b/homeassistant/components/electric_kiwi/config_flow.py index c2c80aaa402..4dac4041c1f 100644 --- a/homeassistant/components/electric_kiwi/config_flow.py +++ b/homeassistant/components/electric_kiwi/config_flow.py @@ -5,8 +5,7 @@ from collections.abc import Mapping import logging from typing import Any -from homeassistant.config_entries import ConfigEntry -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN, SCOPE_VALUES @@ -34,7 +33,9 @@ class ElectricKiwiOauth2FlowHandler( """Extra data that needs to be appended to the authorize url.""" return {"scope": SCOPE_VALUES} - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -43,13 +44,13 @@ class ElectricKiwiOauth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict) -> FlowResult: + async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: """Create an entry for Electric Kiwi.""" existing_entry = await self.async_set_unique_id(DOMAIN) if existing_entry: diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index 9e63df0a503..36f30ada1ab 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -7,10 +7,9 @@ from elgato import Elgato, ElgatoError import voluptuous as vol from homeassistant.components import onboarding, zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -28,7 +27,7 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._async_show_setup_form() @@ -45,7 +44,7 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self.host = discovery_info.host self.mac = discovery_info.properties.get("id") @@ -67,14 +66,14 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, _: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" return self._async_create_entry() @callback def _async_show_setup_form( self, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -88,7 +87,7 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry(self) -> FlowResult: + def _async_create_entry(self) -> ConfigFlowResult: return self.async_create_entry( title=self.serial_number, data={ diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index e8d3f8cb0e4..50a1c795b81 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -8,8 +8,8 @@ from elkm1_lib.discovery import ElkSystem from elkm1_lib.elk import Elk import voluptuous as vol -from homeassistant import config_entries, exceptions from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_ADDRESS, CONF_HOST, @@ -18,7 +18,7 @@ from homeassistant.const import ( CONF_PROTOCOL, CONF_USERNAME, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.util import slugify @@ -126,7 +126,7 @@ def _placeholders_from_device(device: ElkSystem) -> dict[str, str]: } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class Elkm1ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Elk-M1 Control.""" VERSION = 1 @@ -136,7 +136,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_device: ElkSystem | None = None self._discovered_devices: dict[str, ElkSystem] = {} - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" self._discovered_device = ElkSystem( discovery_info.macaddress, discovery_info.ip, 0 @@ -146,7 +148,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle integration discovery.""" self._discovered_device = ElkSystem( discovery_info["mac_address"], @@ -158,7 +160,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self._async_handle_discovery() - async def _async_handle_discovery(self) -> FlowResult: + async def _async_handle_discovery(self) -> ConfigFlowResult: """Handle any discovery.""" device = self._discovered_device assert device is not None @@ -191,7 +193,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None self.context["title_placeholders"] = _placeholders_from_device( @@ -201,7 +203,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: if mac := user_input[CONF_DEVICE]: @@ -236,7 +238,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_create_or_error( self, user_input: dict[str, Any], importing: bool - ) -> tuple[dict[str, str] | None, FlowResult | None]: + ) -> tuple[dict[str, str] | None, ConfigFlowResult | None]: """Try to connect and create the entry or error.""" if self._url_already_configured(_make_url_from_data(user_input)): return None, self.async_abort(reason="address_already_configured") @@ -267,7 +269,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovered_connection( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle connecting the device when we have a discovery.""" errors: dict[str, str] | None = {} device = self._discovered_device @@ -299,7 +301,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_manual_connection( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle connecting the device when we need manual entry.""" errors: dict[str, str] | None = {} if user_input is not None: @@ -334,7 +336,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Handle import.""" _LOGGER.debug("Elk is importing from yaml") url = _make_url_from_data(user_input) @@ -371,5 +373,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return hostname_from_url(url) in existing_hosts -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/elmax/config_flow.py b/homeassistant/components/elmax/config_flow.py index 5b9bb3b1085..ec3dece500d 100644 --- a/homeassistant/components/elmax/config_flow.py +++ b/homeassistant/components/elmax/config_flow.py @@ -10,8 +10,7 @@ from elmax_api.http import Elmax from elmax_api.model.panel import PanelEntry import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.exceptions import HomeAssistantError from .const import ( @@ -55,7 +54,7 @@ def _store_panel_by_name( panel_names[panel_name] = panel_id -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for elmax-cloud.""" VERSION = 1 @@ -64,11 +63,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _password: str _panels_schema: vol.Schema _panel_names: dict - _entry: config_entries.ConfigEntry | None + _entry: ConfigEntry | None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # When invokes without parameters, show the login form. if user_input is None: @@ -133,7 +132,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_panels( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Panel selection step.""" errors: dict[str, Any] = {} if user_input is None: @@ -175,14 +174,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="panels", data_schema=self._panels_schema, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/elvia/config_flow.py b/homeassistant/components/elvia/config_flow.py index fb50842e39b..9e9b8919e4a 100644 --- a/homeassistant/components/elvia/config_flow.py +++ b/homeassistant/components/elvia/config_flow.py @@ -13,9 +13,6 @@ from homeassistant.util import dt as dt_util from .const import CONF_METERING_POINT_ID, DOMAIN, LOGGER -if TYPE_CHECKING: - from homeassistant.data_entry_flow import FlowResult - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Elvia.""" @@ -28,7 +25,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -77,7 +74,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select_meter( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle selecting a metering point ID.""" if TYPE_CHECKING: assert self._metering_point_ids is not None @@ -105,7 +102,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, api_token: str, metering_point_id: str, - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Store metering point ID and API token.""" if (await self.async_set_unique_id(metering_point_id)) is not None: return self.async_abort( diff --git a/homeassistant/components/emonitor/config_flow.py b/homeassistant/components/emonitor/config_flow.py index 6ffd4f1059b..5d0ba30946a 100644 --- a/homeassistant/components/emonitor/config_flow.py +++ b/homeassistant/components/emonitor/config_flow.py @@ -5,10 +5,10 @@ from aioemonitor import Emonitor import aiohttp import voluptuous as vol -from homeassistant import config_entries, core from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client from homeassistant.helpers.device_registry import format_mac @@ -18,7 +18,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -async def fetch_mac_and_title(hass: core.HomeAssistant, host): +async def fetch_mac_and_title(hass: HomeAssistant, host): """Validate the user input allows us to connect.""" session = aiohttp_client.async_get_clientsession(hass) emonitor = Emonitor(host, session) @@ -27,7 +27,7 @@ async def fetch_mac_and_title(hass: core.HomeAssistant, host): return {"title": name_short_mac(mac_address[-6:]), "mac_address": mac_address} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EmonitorConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for SiteSage Emonitor.""" VERSION = 1 @@ -63,7 +63,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" self.discovered_ip = discovery_info.ip await self.async_set_unique_id(format_mac(discovery_info.macaddress)) diff --git a/homeassistant/components/emulated_roku/config_flow.py b/homeassistant/components/emulated_roku/config_flow.py index dd7cd87c96a..cdfd0d93843 100644 --- a/homeassistant/components/emulated_roku/config_flow.py +++ b/homeassistant/components/emulated_roku/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure emulated_roku component.""" import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_NAME from homeassistant.core import callback @@ -16,7 +16,7 @@ def configured_servers(hass): } -class EmulatedRokuFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class EmulatedRokuFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an emulated_roku config flow.""" VERSION = 1 diff --git a/homeassistant/components/energyzero/config_flow.py b/homeassistant/components/energyzero/config_flow.py index 55fffbdec91..af86ef4d216 100644 --- a/homeassistant/components/energyzero/config_flow.py +++ b/homeassistant/components/energyzero/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -16,7 +15,7 @@ class EnergyZeroFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" await self.async_set_unique_id(DOMAIN) diff --git a/homeassistant/components/enocean/config_flow.py b/homeassistant/components/enocean/config_flow.py index e47cb4c0589..1137eb23256 100644 --- a/homeassistant/components/enocean/config_flow.py +++ b/homeassistant/components/enocean/config_flow.py @@ -2,14 +2,14 @@ import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_DEVICE from . import dongle from .const import DOMAIN, ERROR_INVALID_DONGLE_PATH, LOGGER -class EnOceanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class EnOceanFlowHandler(ConfigFlow, domain=DOMAIN): """Handle the enOcean config flows.""" VERSION = 1 diff --git a/homeassistant/components/enphase_envoy/config_flow.py b/homeassistant/components/enphase_envoy/config_flow.py index ee1966d5e51..743935b906e 100644 --- a/homeassistant/components/enphase_envoy/config_flow.py +++ b/homeassistant/components/enphase_envoy/config_flow.py @@ -9,11 +9,10 @@ from awesomeversion import AwesomeVersion from pyenphase import AUTH_TOKEN_MIN_VERSION, Envoy, EnvoyError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.httpx_client import get_async_client from .const import DOMAIN, INVALID_AUTH_ERRORS @@ -37,7 +36,7 @@ async def validate_input( return envoy -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Enphase Envoy.""" VERSION = 1 @@ -47,7 +46,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.ip_address: str | None = None self.username = None self.protovers: str | None = None - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None @callback def _async_generate_schema(self) -> vol.Schema: @@ -87,7 +86,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf discovery.""" if discovery_info.ip_address.version != 4: return self.async_abort(reason="not_ipv4_address") @@ -109,7 +108,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -125,7 +126,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} description_placeholders: dict[str, str] = {} diff --git a/homeassistant/components/environment_canada/config_flow.py b/homeassistant/components/environment_canada/config_flow.py index f4b9ee792c3..9249a22ba1d 100644 --- a/homeassistant/components/environment_canada/config_flow.py +++ b/homeassistant/components/environment_canada/config_flow.py @@ -6,7 +6,7 @@ import aiohttp from env_canada import ECWeather, ec_exc import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.helpers import config_validation as cv @@ -40,7 +40,7 @@ async def validate_input(data): } -class EnvironmentCanadaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EnvironmentCanadaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Environment Canada weather.""" VERSION = 1 diff --git a/homeassistant/components/epion/config_flow.py b/homeassistant/components/epion/config_flow.py index 7c89df94519..f0e598a9af4 100644 --- a/homeassistant/components/epion/config_flow.py +++ b/homeassistant/components/epion/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from epion import Epion, EpionAuthenticationError, EpionConnectionError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -23,7 +22,7 @@ class EpionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input: diff --git a/homeassistant/components/epson/config_flow.py b/homeassistant/components/epson/config_flow.py index b1ac34b1099..32a41a38b55 100644 --- a/homeassistant/components/epson/config_flow.py +++ b/homeassistant/components/epson/config_flow.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from . import validate_projector @@ -20,7 +20,7 @@ DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EpsonConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for epson.""" VERSION = 1 diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 9962b9144ea..98f64818b42 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -21,10 +21,14 @@ import voluptuous as vol from homeassistant.components import dhcp, zeroconf from homeassistant.components.hassio import HassioServiceInfo -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import ( @@ -64,7 +68,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def _async_step_user_base( self, user_input: dict[str, Any] | None = None, error: str | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: if user_input is not None: self._host = user_input[CONF_HOST] self._port = user_input[CONF_PORT] @@ -87,11 +91,13 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self._async_step_user_base(user_input=user_input) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a flow initialized by a reauth event.""" entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry is not None @@ -119,7 +125,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" errors = {} @@ -151,7 +157,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): self.context[CONF_NAME] = value self.context["title_placeholders"] = {"name": self._name} - async def _async_try_fetch_device_info(self) -> FlowResult: + async def _async_try_fetch_device_info(self) -> ConfigFlowResult: """Try to fetch device info and return any errors.""" response: str | None if self._noise_required: @@ -193,7 +199,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): return await self._async_step_user_base(error=response) return await self._async_authenticate_or_add() - async def _async_authenticate_or_add(self) -> FlowResult: + async def _async_authenticate_or_add(self) -> ConfigFlowResult: # Only show authentication step if device uses password assert self._device_info is not None if self._device_info.uses_password: @@ -204,7 +210,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" if user_input is not None: return await self._async_try_fetch_device_info() @@ -214,7 +220,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" mac_address: str | None = discovery_info.properties.get("mac") @@ -243,7 +249,9 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_discovery_confirm() - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.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}) @@ -251,7 +259,9 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): # for configured devices. return self.async_abort(reason="already_configured") - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Handle Supervisor service discovery.""" await async_set_dashboard_info( self.hass, @@ -262,7 +272,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="service_received") @callback - def _async_get_entry(self) -> FlowResult: + def _async_get_entry(self) -> ConfigFlowResult: config_data = { CONF_HOST: self._host, CONF_PORT: self._port, @@ -288,7 +298,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_encryption_key( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle getting psk for transport encryption.""" errors = {} if user_input is not None: @@ -307,7 +317,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_authenticate( self, user_input: dict[str, Any] | None = None, error: str | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle getting password for authentication.""" if user_input is not None: self._password = user_input[CONF_PASSWORD] @@ -444,7 +454,7 @@ class OptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/eufylife_ble/config_flow.py b/homeassistant/components/eufylife_ble/config_flow.py index e3a1a301f25..1f2c851e484 100644 --- a/homeassistant/components/eufylife_ble/config_flow.py +++ b/homeassistant/components/eufylife_ble/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS, CONF_MODEL -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -29,7 +28,7 @@ class EufyLifeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -42,7 +41,7 @@ class EufyLifeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovery_info is not None discovery_info = self._discovery_info @@ -64,7 +63,7 @@ class EufyLifeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/evil_genius_labs/config_flow.py b/homeassistant/components/evil_genius_labs/config_flow.py index ab2e116b2a6..919863a0a4f 100644 --- a/homeassistant/components/evil_genius_labs/config_flow.py +++ b/homeassistant/components/evil_genius_labs/config_flow.py @@ -9,9 +9,8 @@ import aiohttp import pyevilgenius import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client @@ -40,14 +39,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return {"title": data["name"]["value"], "unique_id": info["wiFiChipId"]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class EvilGeniusLabsConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Evil Genius Labs.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/ezviz/config_flow.py b/homeassistant/components/ezviz/config_flow.py index 77598ad6a1c..28c80ad7bc0 100644 --- a/homeassistant/components/ezviz/config_flow.py +++ b/homeassistant/components/ezviz/config_flow.py @@ -16,7 +16,12 @@ from pyezviz.exceptions import ( from pyezviz.test_cam_rtsp import TestRTSPAuth import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_CUSTOMIZE, CONF_IP_ADDRESS, @@ -27,7 +32,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( ATTR_SERIAL, @@ -90,7 +94,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 - async def _validate_and_create_camera_rtsp(self, data: dict) -> FlowResult: + async def _validate_and_create_camera_rtsp(self, data: dict) -> ConfigFlowResult: """Try DESCRIBE on RTSP camera with credentials.""" # Get EZVIZ cloud credentials from config entry @@ -146,7 +150,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" # Check if EZVIZ cloud account is present in entry config, @@ -213,7 +217,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user_custom_url( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user for custom region url.""" errors = {} auth_data = {} @@ -262,7 +266,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: dict[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow for discovered camera without rtsp config entry.""" await self.async_set_unique_id(discovery_info[ATTR_SERIAL]) @@ -275,7 +279,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm and create entry from discovery step.""" errors = {} @@ -315,14 +319,16 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a flow for reauthentication with password.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a Confirm flow for reauthentication with password.""" auth_data = {} errors = {} @@ -390,7 +396,7 @@ class EzvizOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage EZVIZ options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/faa_delays/config_flow.py b/homeassistant/components/faa_delays/config_flow.py index 2f91ce9f797..91a1cbfdb48 100644 --- a/homeassistant/components/faa_delays/config_flow.py +++ b/homeassistant/components/faa_delays/config_flow.py @@ -6,9 +6,8 @@ from aiohttp import ClientConnectionError import faadelays import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -18,14 +17,14 @@ _LOGGER = logging.getLogger(__name__) DATA_SCHEMA = vol.Schema({vol.Required(CONF_ID): str}) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FAADelaysConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for FAA Delays.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/fastdotcom/config_flow.py b/homeassistant/components/fastdotcom/config_flow.py index 5ca35fd6802..a8491a820ab 100644 --- a/homeassistant/components/fastdotcom/config_flow.py +++ b/homeassistant/components/fastdotcom/config_flow.py @@ -3,9 +3,8 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import DEFAULT_NAME, DOMAIN @@ -18,7 +17,7 @@ class FastdotcomConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -30,7 +29,7 @@ class FastdotcomConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_import( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by configuration file.""" async_create_issue( self.hass, diff --git a/homeassistant/components/fibaro/config_flow.py b/homeassistant/components/fibaro/config_flow.py index 0a06f88c5af..5d62d410a87 100644 --- a/homeassistant/components/fibaro/config_flow.py +++ b/homeassistant/components/fibaro/config_flow.py @@ -8,10 +8,9 @@ from typing import Any from slugify import slugify import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from . import FibaroAuthFailed, FibaroConnectFailed, FibaroController from .const import CONF_IMPORT_PLUGINS, DOMAIN @@ -65,18 +64,18 @@ def _normalize_url(url: str) -> str: return url -class FibaroConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FibaroConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Fibaro.""" VERSION = 1 def __init__(self) -> None: """Initialize.""" - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -97,7 +96,9 @@ class FibaroConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauthentication.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -106,7 +107,7 @@ class FibaroConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by reauthentication.""" errors = {} diff --git a/homeassistant/components/filesize/config_flow.py b/homeassistant/components/filesize/config_flow.py index 8633e6ec466..6d156aa4c0a 100644 --- a/homeassistant/components/filesize/config_flow.py +++ b/homeassistant/components/filesize/config_flow.py @@ -7,10 +7,9 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_FILE_PATH from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -43,7 +42,7 @@ class FilesizeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, Any] = {} diff --git a/homeassistant/components/fireservicerota/config_flow.py b/homeassistant/components/fireservicerota/config_flow.py index d4d2b0763d9..9eeb018ca02 100644 --- a/homeassistant/components/fireservicerota/config_flow.py +++ b/homeassistant/components/fireservicerota/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from pyfireservicerota import FireServiceRota, InvalidAuthError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_URL, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, URL_LIST @@ -22,7 +21,7 @@ DATA_SCHEMA = vol.Schema( ) -class FireServiceRotaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FireServiceRotaFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a FireServiceRota config flow.""" VERSION = 1 @@ -116,7 +115,9 @@ class FireServiceRotaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=self._description_placeholders, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Initialise re-authentication.""" await self.async_set_unique_id(entry_data[CONF_USERNAME]) self._existing_entry = {**entry_data} @@ -125,7 +126,7 @@ class FireServiceRotaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Get new tokens for a config entry that can't authenticate.""" if user_input is None: return self._show_setup_form(step_id="reauth_confirm") diff --git a/homeassistant/components/firmata/config_flow.py b/homeassistant/components/firmata/config_flow.py index f5b7cb5af40..571df351b25 100644 --- a/homeassistant/components/firmata/config_flow.py +++ b/homeassistant/components/firmata/config_flow.py @@ -5,9 +5,8 @@ from typing import Any from pymata_express.pymata_express_serial import serial -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .board import get_board from .const import CONF_SERIAL_PORT, DOMAIN @@ -15,12 +14,14 @@ from .const import CONF_SERIAL_PORT, DOMAIN _LOGGER = logging.getLogger(__name__) -class FirmataFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FirmataFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a firmata config flow.""" VERSION = 1 - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import a firmata board as a config entry. This flow is triggered by `async_setup` for configured boards. diff --git a/homeassistant/components/fitbit/config_flow.py b/homeassistant/components/fitbit/config_flow.py index 7ef6ecbfa28..0ae1973b5fb 100644 --- a/homeassistant/components/fitbit/config_flow.py +++ b/homeassistant/components/fitbit/config_flow.py @@ -4,9 +4,8 @@ from collections.abc import Mapping import logging from typing import Any -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from . import api @@ -38,7 +37,9 @@ class OAuth2FlowHandler( "prompt": "consent" if not self.reauth_entry else "none", } - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -47,7 +48,7 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") @@ -55,7 +56,7 @@ class OAuth2FlowHandler( async def async_step_creation( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create config entry from external data with Fitbit specific error handling.""" try: return await super().async_step_creation() @@ -68,7 +69,7 @@ class OAuth2FlowHandler( _LOGGER.error("Failed to create Fitbit credentials: %s", err) return self.async_abort(reason="cannot_connect") - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" client = api.ConfigFlowFitbitApi(self.hass, data[CONF_TOKEN]) @@ -92,6 +93,6 @@ class OAuth2FlowHandler( self._abort_if_unique_id_configured() return self.async_create_entry(title=profile.display_name, data=data) - async def async_step_import(self, data: dict[str, Any]) -> FlowResult: + async def async_step_import(self, data: dict[str, Any]) -> ConfigFlowResult: """Handle import from YAML.""" return await self.async_oauth_create_entry(data) diff --git a/homeassistant/components/fivem/config_flow.py b/homeassistant/components/fivem/config_flow.py index e564faa81b7..b985b161af4 100644 --- a/homeassistant/components/fivem/config_flow.py +++ b/homeassistant/components/fivem/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from fivem import FiveM, FiveMServerOfflineError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import DOMAIN @@ -37,14 +36,14 @@ async def validate_input(data: dict[str, Any]) -> None: raise InvalidGameNameError -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FiveMConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for FiveM.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/flexit_bacnet/config_flow.py b/homeassistant/components/flexit_bacnet/config_flow.py index 2c87dfc5b97..9b0e3188508 100644 --- a/homeassistant/components/flexit_bacnet/config_flow.py +++ b/homeassistant/components/flexit_bacnet/config_flow.py @@ -9,9 +9,8 @@ from flexit_bacnet import FlexitBACnet from flexit_bacnet.bacnet import DecodingError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE_ID, CONF_IP_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -27,14 +26,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FlexitBacnetConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Flexit Nordic (BACnet).""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/flick_electric/config_flow.py b/homeassistant/components/flick_electric/config_flow.py index 842706172f1..372429bf7b5 100644 --- a/homeassistant/components/flick_electric/config_flow.py +++ b/homeassistant/components/flick_electric/config_flow.py @@ -6,13 +6,14 @@ from pyflick.authentication import AuthException, SimpleFlickAuth from pyflick.const import DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_PASSWORD, CONF_USERNAME, ) +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -29,7 +30,7 @@ DATA_SCHEMA = vol.Schema( ) -class FlickConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FlickConfigFlow(ConfigFlow, domain=DOMAIN): """Flick config flow.""" VERSION = 1 @@ -82,9 +83,9 @@ class FlickConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/flipr/config_flow.py b/homeassistant/components/flipr/config_flow.py index 5c8cc6f76fb..187e43d7426 100644 --- a/homeassistant/components/flipr/config_flow.py +++ b/homeassistant/components/flipr/config_flow.py @@ -7,16 +7,15 @@ from flipr_api import FliprAPIRestClient from requests.exceptions import HTTPError, Timeout import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from .const import CONF_FLIPR_ID, DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FliprConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Flipr.""" VERSION = 1 @@ -28,7 +27,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self._show_setup_form() @@ -97,7 +96,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_flipr_id( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if not user_input: # Creation of a select with the proposal of flipr ids values found by API. diff --git a/homeassistant/components/flo/config_flow.py b/homeassistant/components/flo/config_flow.py index c34753c3295..c9bc39024a0 100644 --- a/homeassistant/components/flo/config_flow.py +++ b/homeassistant/components/flo/config_flow.py @@ -3,8 +3,10 @@ from aioflo import async_get_api from aioflo.errors import RequestError import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, LOGGER @@ -14,7 +16,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -28,7 +30,7 @@ async def validate_input(hass: core.HomeAssistant, data): raise CannotConnect from request_error -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FloConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for flo.""" VERSION = 1 @@ -52,5 +54,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/flume/config_flow.py b/homeassistant/components/flume/config_flow.py index df2a697ed8d..c5bf6b2a323 100644 --- a/homeassistant/components/flume/config_flow.py +++ b/homeassistant/components/flume/config_flow.py @@ -10,14 +10,15 @@ from pyflume import FlumeAuth, FlumeDeviceList from requests.exceptions import RequestException import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_PASSWORD, CONF_USERNAME, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import BASE_TOKEN_FILENAME, DOMAIN @@ -39,7 +40,7 @@ DATA_SCHEMA = vol.Schema( def _validate_input( - hass: core.HomeAssistant, data: dict[str, Any], clear_token_file: bool + hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool ) -> FlumeDeviceList: """Validate in the executor.""" flume_token_full_path = hass.config.path( @@ -60,7 +61,7 @@ def _validate_input( async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any], clear_token_file: bool = False + hass: HomeAssistant, data: dict[str, Any], clear_token_file: bool = False ) -> dict[str, Any]: """Validate the user input allows us to connect. @@ -82,7 +83,7 @@ async def validate_input( return {"title": data[CONF_USERNAME]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FlumeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for flume.""" VERSION = 1 @@ -93,7 +94,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -112,14 +113,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth.""" self._reauth_unique_id = self.context["unique_id"] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth input.""" errors: dict[str, str] = {} existing_entry = await self.async_set_unique_id(self._reauth_unique_id) @@ -153,9 +156,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index d50e6a08b5a..6b22676ee60 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -15,11 +15,18 @@ from flux_led.const import ( from flux_led.scanner import FluxLEDDiscovery import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ( + SOURCE_IGNORE, + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_DEVICE, CONF_HOST from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import DiscoveryInfoType @@ -48,7 +55,7 @@ from .discovery import ( from .util import format_as_flux_mac, mac_matches_by_one -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Magic Home Integration.""" VERSION = 1 @@ -61,11 +68,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry: config_entries.ConfigEntry) -> OptionsFlow: + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Get the options flow for the Flux LED component.""" - return OptionsFlow(config_entry) + return FluxLedOptionsFlow(config_entry) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" self._discovered_device = FluxLEDDiscovery( ipaddr=discovery_info.ip, @@ -84,7 +93,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle integration discovery.""" self._allow_update_mac = True self._discovered_device = cast(FluxLEDDiscovery, discovery_info) @@ -113,7 +122,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) ): continue - if entry.source == config_entries.SOURCE_IGNORE: + if entry.source == SOURCE_IGNORE: raise AbortFlow("already_configured") if ( async_update_entry_from_discovery( @@ -121,10 +130,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) and entry.state not in ( - config_entries.ConfigEntryState.SETUP_IN_PROGRESS, - config_entries.ConfigEntryState.NOT_LOADED, + ConfigEntryState.SETUP_IN_PROGRESS, + ConfigEntryState.NOT_LOADED, ) - ) or entry.state == config_entries.ConfigEntryState.SETUP_RETRY: + ) or entry.state == ConfigEntryState.SETUP_RETRY: self.hass.config_entries.async_schedule_reload(entry.entry_id) else: async_dispatcher_send( @@ -133,7 +142,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) raise AbortFlow("already_configured") - async def _async_handle_discovery(self) -> FlowResult: + async def _async_handle_discovery(self) -> ConfigFlowResult: """Handle any discovery.""" device = self._discovered_device assert device is not None @@ -165,7 +174,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -186,7 +195,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry_from_device(self, device: FluxLEDDiscovery) -> FlowResult: + def _async_create_entry_from_device( + self, device: FluxLEDDiscovery + ) -> ConfigFlowResult: """Create a config entry from a device.""" self._async_abort_entries_match({CONF_HOST: device[ATTR_IPADDR]}) name = async_name_from_discovery(device) @@ -199,7 +210,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -225,7 +236,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step to pick discovered device.""" if user_input is not None: mac = user_input[CONF_DEVICE] @@ -298,16 +309,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlow(config_entries.OptionsFlow): +class FluxLedOptionsFlow(OptionsFlow): """Handle flux_led options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize the flux_led options flow.""" self._config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure the options.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/forecast_solar/config_flow.py b/homeassistant/components/forecast_solar/config_flow.py index 6066c85e74e..da4f979478e 100644 --- a/homeassistant/components/forecast_solar/config_flow.py +++ b/homeassistant/components/forecast_solar/config_flow.py @@ -6,10 +6,14 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import ( @@ -40,7 +44,7 @@ class ForecastSolarFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is not None: return self.async_create_entry( @@ -92,7 +96,7 @@ class ForecastSolarOptionFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index 84ddbbc7f0e..fad1de7d1fe 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -5,11 +5,15 @@ import logging from pyforked_daapd import ForkedDaapdAPI import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -43,10 +47,10 @@ TEST_CONNECTION_ERROR_DICT = { } -class ForkedDaapdOptionsFlowHandler(config_entries.OptionsFlow): +class ForkedDaapdOptionsFlowHandler(OptionsFlow): """Handle a forked-daapd options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry @@ -99,7 +103,7 @@ def fill_in_schema_dict(some_input): return schema_dict -class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class ForkedDaapdFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a forked-daapd config flow.""" VERSION = 1 @@ -111,7 +115,7 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> ForkedDaapdOptionsFlowHandler: """Return options flow handler.""" return ForkedDaapdOptionsFlowHandler(config_entry) @@ -159,7 +163,7 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare configuration for a discovered forked-daapd device.""" version_num = 0 zeroconf_properties = discovery_info.properties diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index 8d19220130d..c30e3a73754 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -7,7 +7,7 @@ from libpyfoscam.foscam import ( ) import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -16,6 +16,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.data_entry_flow import AbortFlow +from homeassistant.exceptions import HomeAssistantError from .const import CONF_RTSP_PORT, CONF_STREAM, DOMAIN, LOGGER @@ -37,7 +38,7 @@ DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FoscamConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for foscam.""" VERSION = 2 @@ -117,13 +118,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class InvalidResponse(exceptions.HomeAssistantError): +class InvalidResponse(HomeAssistantError): """Error to indicate there is invalid response.""" diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 7441def7d4d..de46087c60c 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -5,10 +5,9 @@ from typing import Any from freebox_api.exceptions import AuthorizationError, HttpRequestError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN from .router import get_api, get_hosts_list_if_supported @@ -16,7 +15,7 @@ from .router import get_api, get_hosts_list_if_supported _LOGGER = logging.getLogger(__name__) -class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FreeboxFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -27,7 +26,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self.async_show_form( @@ -51,7 +50,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to link with the Freebox router. Given a configured host, will ask the user to press the button @@ -100,7 +99,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Initialize flow from zeroconf.""" zeroconf_properties = discovery_info.properties host = zeroconf_properties["api_domain"] diff --git a/homeassistant/components/freedompro/config_flow.py b/homeassistant/components/freedompro/config_flow.py index c1288e61406..6b224c87610 100644 --- a/homeassistant/components/freedompro/config_flow.py +++ b/homeassistant/components/freedompro/config_flow.py @@ -2,8 +2,10 @@ from pyfreedompro import get_list import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_API_KEY +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -26,7 +28,7 @@ class Hub: ) -async def validate_input(hass: core.HomeAssistant, api_key): +async def validate_input(hass: HomeAssistant, api_key): """Validate api key.""" hub = Hub(hass, api_key) result = await hub.authenticate() @@ -37,7 +39,7 @@ async def validate_input(hass: core.HomeAssistant, api_key): raise CannotConnect -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FreedomProConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -65,9 +67,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/fritz/config_flow.py b/homeassistant/components/fritz/config_flow.py index 03bcc3b77f7..99a1917e932 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -20,12 +20,12 @@ from homeassistant.components.device_tracker import ( from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlow, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_OLD_DISCOVERY, @@ -110,7 +110,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): return None @callback - def _async_create_entry(self) -> FlowResult: + def _async_create_entry(self) -> ConfigFlowResult: """Async create flow handler entry.""" return self.async_create_entry( title=self._name, @@ -126,7 +126,9 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" ssdp_location: ParseResult = urlparse(discovery_info.ssdp_location or "") self._host = ssdp_location.hostname @@ -166,7 +168,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" if user_input is None: return self._show_setup_form_confirm() @@ -184,7 +186,9 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): return self._async_create_entry() - def _show_setup_form_init(self, errors: dict[str, str] | None = None) -> FlowResult: + def _show_setup_form_init( + self, errors: dict[str, str] | None = None + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -201,7 +205,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): def _show_setup_form_confirm( self, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="confirm", @@ -217,7 +221,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form_init() @@ -237,7 +241,9 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): return self._async_create_entry() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle flow upon an API authentication error.""" self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) self._host = entry_data[CONF_HOST] @@ -248,7 +254,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): def _show_setup_form_reauth_confirm( self, user_input: dict[str, Any], errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the reauth form to the user.""" default_username = user_input.get(CONF_USERNAME) return self.async_show_form( @@ -265,7 +271,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self._show_setup_form_reauth_confirm( @@ -299,7 +305,7 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index b4e86b92568..cf42a41b179 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -11,9 +11,8 @@ from requests.exceptions import HTTPError import voluptuous as vol from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN @@ -51,7 +50,7 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): self._password: str | None = None self._username: str | None = None - def _get_entry(self, name: str) -> FlowResult: + def _get_entry(self, name: str) -> ConfigFlowResult: return self.async_create_entry( title=name, data={ @@ -92,7 +91,7 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -116,7 +115,9 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" host = urlparse(discovery_info.ssdp_location).hostname assert isinstance(host, str) @@ -153,7 +154,7 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" errors = {} @@ -176,7 +177,9 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Trigger a reauthentication flow.""" entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry is not None @@ -189,7 +192,7 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" errors = {} diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index 5065aa65b4d..5150abc395c 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -9,7 +9,13 @@ from fritzconnection.core.exceptions import FritzConnectionException, FritzSecur from requests.exceptions import ConnectionError as RequestsConnectionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + SOURCE_IMPORT, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -18,7 +24,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .base import FritzBoxPhonebook from .const import ( @@ -54,7 +59,7 @@ class ConnectResult(StrEnum): SUCCESS = "success" -class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FritzBoxCallMonitorConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a fritzbox_callmonitor config flow.""" VERSION = 1 @@ -73,7 +78,7 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize flow.""" self._phonebook_names: list[str] | None = None - def _get_config_entry(self) -> FlowResult: + def _get_config_entry(self) -> ConfigFlowResult: """Create and return an config entry.""" return self.async_create_entry( title=self._phonebook_name, @@ -132,14 +137,14 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> FritzBoxCallMonitorOptionsFlowHandler: """Get the options flow for this handler.""" return FritzBoxCallMonitorOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is None: @@ -164,7 +169,7 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if result != ConnectResult.SUCCESS: return self.async_abort(reason=result) - if self.context["source"] == config_entries.SOURCE_IMPORT: + if self.context["source"] == SOURCE_IMPORT: self._phonebook_id = user_input[CONF_PHONEBOOK] self._phonebook_name = user_input[CONF_NAME] @@ -182,7 +187,7 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_phonebook( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow to chose one of multiple available phonebooks.""" if self._phonebook_names is None: @@ -206,10 +211,10 @@ class FritzBoxCallMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self._get_config_entry() -class FritzBoxCallMonitorOptionsFlowHandler(config_entries.OptionsFlow): +class FritzBoxCallMonitorOptionsFlowHandler(OptionsFlow): """Handle a fritzbox_callmonitor options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry @@ -240,7 +245,7 @@ class FritzBoxCallMonitorOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" option_schema_prefixes = self._get_option_schema_prefixes() diff --git a/homeassistant/components/fronius/config_flow.py b/homeassistant/components/fronius/config_flow.py index 15b8cd7a3b8..18abac5e38a 100644 --- a/homeassistant/components/fronius/config_flow.py +++ b/homeassistant/components/fronius/config_flow.py @@ -8,11 +8,10 @@ from typing import Any, Final from pyfronius import Fronius, FroniusError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.dhcp import DhcpServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -73,7 +72,7 @@ async def validate_host( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FroniusConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Fronius.""" VERSION = 1 @@ -84,7 +83,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -110,7 +109,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initiated by the DHCP client.""" for entry in self._async_current_entries(include_ignore=False): if entry.data[CONF_HOST].lstrip("http://").rstrip("/").lower() in ( @@ -133,7 +134,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to confirm.""" title = create_title(self.info) if user_input is not None: diff --git a/homeassistant/components/frontier_silicon/config_flow.py b/homeassistant/components/frontier_silicon/config_flow.py index 470be7d9b26..7ee3743860d 100644 --- a/homeassistant/components/frontier_silicon/config_flow.py +++ b/homeassistant/components/frontier_silicon/config_flow.py @@ -14,10 +14,9 @@ from afsapi import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_WEBFSAPI_URL, @@ -51,18 +50,18 @@ def hostname_from_url(url: str) -> str: return str(urlparse(url).hostname) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FrontierSiliconConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Frontier Silicon Media Player.""" VERSION = 1 _name: str _webfsapi_url: str - _reauth_entry: config_entries.ConfigEntry | None = None # Only used in reauth flows + _reauth_entry: ConfigEntry | None = None # Only used in reauth flows async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step of manual configuration.""" errors = {} @@ -87,7 +86,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Process entity discovered via SSDP.""" device_url = discovery_info.ssdp_location @@ -131,7 +132,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() - async def _async_step_device_config_if_needed(self) -> FlowResult: + async def _async_step_device_config_if_needed(self) -> ConfigFlowResult: """Most users will not have changed the default PIN on their radio. We try to use this default PIN, and only if this fails ask for it via `async_step_device_config` @@ -159,7 +160,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Allow the user to confirm adding the device. Used when the default PIN could successfully be used.""" if user_input is not None: @@ -170,7 +171,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="confirm", description_placeholders={"name": self._name} ) - async def async_step_reauth(self, config: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, config: Mapping[str, Any]) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._webfsapi_url = config[CONF_WEBFSAPI_URL] @@ -182,7 +183,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_device_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle device configuration step. We ask for the PIN in this step. diff --git a/homeassistant/components/fully_kiosk/config_flow.py b/homeassistant/components/fully_kiosk/config_flow.py index 00eb1dd7101..0261e2ae52b 100644 --- a/homeassistant/components/fully_kiosk/config_flow.py +++ b/homeassistant/components/fully_kiosk/config_flow.py @@ -10,8 +10,8 @@ from fullykiosk import FullyKiosk from fullykiosk.exceptions import FullyKioskError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.dhcp import DhcpServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -19,7 +19,6 @@ from homeassistant.const import ( CONF_SSL, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.service_info.mqtt import MqttServiceInfo @@ -27,7 +26,7 @@ from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from .const import DEFAULT_PORT, DOMAIN, LOGGER -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class FullyKioskConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Fully Kiosk Browser.""" VERSION = 1 @@ -42,7 +41,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): user_input: dict[str, Any], errors: dict[str, str], description_placeholders: dict[str, str] | Any = None, - ) -> FlowResult | None: + ) -> ConfigFlowResult | None: fully = FullyKiosk( async_get_clientsession(self.hass), host, @@ -85,7 +84,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} placeholders: dict[str, str] = {} @@ -110,7 +109,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" mac = format_mac(discovery_info.macaddress) @@ -129,7 +130,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" errors: dict[str, str] = {} if user_input is not None: @@ -157,7 +158,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: + async def async_step_mqtt( + self, discovery_info: MqttServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by MQTT discovery.""" device_info: dict[str, Any] = json.loads(discovery_info.payload) device_id: str = device_info["deviceId"] diff --git a/homeassistant/components/garages_amsterdam/config_flow.py b/homeassistant/components/garages_amsterdam/config_flow.py index 65a2d359747..c1f9acafc7e 100644 --- a/homeassistant/components/garages_amsterdam/config_flow.py +++ b/homeassistant/components/garages_amsterdam/config_flow.py @@ -8,8 +8,7 @@ from aiohttp import ClientResponseError from odp_amsterdam import ODPAmsterdam, VehicleType import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -17,7 +16,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GaragesAmsterdamConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Garages Amsterdam.""" VERSION = 1 @@ -25,7 +24,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._options is None: self._options = [] diff --git a/homeassistant/components/gardena_bluetooth/config_flow.py b/homeassistant/components/gardena_bluetooth/config_flow.py index 7b34edd29af..78e82f56e72 100644 --- a/homeassistant/components/gardena_bluetooth/config_flow.py +++ b/homeassistant/components/gardena_bluetooth/config_flow.py @@ -10,13 +10,13 @@ from gardena_bluetooth.exceptions import CharacteristicNotFound, CommunicationFa from gardena_bluetooth.parse import ManufacturerData, ProductType import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import ( BluetoothServiceInfo, async_discovered_service_info, ) +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from . import get_connection from .const import DOMAIN @@ -55,7 +55,7 @@ def _get_name(discovery_info: BluetoothServiceInfo): return PRODUCT_NAMES.get(product_type, "Gardena Device") -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GardenaBluetoothConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Gardena Bluetooth.""" VERSION = 1 @@ -82,7 +82,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" _LOGGER.debug("Discovered device: %s", discovery_info) if not _is_supported(discovery_info): @@ -96,7 +96,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self.address title = self.devices[self.address] @@ -117,7 +117,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: self.address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/gdacs/config_flow.py b/homeassistant/components/gdacs/config_flow.py index acc3bbc1991..535b31ff77a 100644 --- a/homeassistant/components/gdacs/config_flow.py +++ b/homeassistant/components/gdacs/config_flow.py @@ -4,14 +4,13 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_SCAN_INTERVAL, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import CONF_CATEGORIES, DEFAULT_RADIUS, DEFAULT_SCAN_INTERVAL, DOMAIN @@ -23,10 +22,12 @@ DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class GdacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GdacsFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a GDACS config flow.""" - async def _show_form(self, errors: dict[str, str] | None = None) -> FlowResult: + async def _show_form( + self, errors: dict[str, str] | None = None + ) -> ConfigFlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, errors=errors or {} @@ -34,7 +35,7 @@ class GdacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" _LOGGER.debug("User input: %s", user_input) if not user_input: diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 4eb5c3a2a4c..b5bbd79def1 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -30,7 +30,12 @@ from homeassistant.components.stream import ( SOURCE_TIMEOUT, create_stream, ) -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_AUTHENTICATION, CONF_NAME, @@ -41,7 +46,7 @@ from homeassistant.const import ( HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult, UnknownFlow +from homeassistant.data_entry_flow import UnknownFlow from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv, template as template_helper from homeassistant.helpers.httpx_client import get_async_client @@ -313,7 +318,7 @@ class GenericIPCamConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" errors = {} hass = self.hass @@ -357,7 +362,7 @@ class GenericIPCamConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user_confirm_still( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user clicking confirm after still preview.""" if user_input: if not user_input.get(CONF_CONFIRMED_OK): @@ -389,7 +394,7 @@ class GenericOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Generic IP Camera options.""" errors: dict[str, str] = {} hass = self.hass @@ -430,7 +435,7 @@ class GenericOptionsFlowHandler(OptionsFlow): async def async_step_confirm_still( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user clicking confirm after still preview.""" if user_input: if not user_input.get(CONF_CONFIRMED_OK): diff --git a/homeassistant/components/geo_json_events/config_flow.py b/homeassistant/components/geo_json_events/config_flow.py index ffa1c2070e9..2eb3443b3e5 100644 --- a/homeassistant/components/geo_json_events/config_flow.py +++ b/homeassistant/components/geo_json_events/config_flow.py @@ -6,7 +6,7 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_LATITUDE, CONF_LOCATION, @@ -15,7 +15,6 @@ from homeassistant.const import ( CONF_URL, UnitOfLength, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv, selector from homeassistant.util.unit_conversion import DistanceConverter @@ -31,12 +30,12 @@ DATA_SCHEMA = vol.Schema( ) -class GeoJsonEventsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GeoJsonEventsFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a GeoJSON events config flow.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: suggested_values: Mapping[str, Any] = { diff --git a/homeassistant/components/geocaching/config_flow.py b/homeassistant/components/geocaching/config_flow.py index 56fa56a1f82..f8b2391afba 100644 --- a/homeassistant/components/geocaching/config_flow.py +++ b/homeassistant/components/geocaching/config_flow.py @@ -7,7 +7,7 @@ from typing import Any from geocachingapi.geocachingapi import GeocachingApi -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler @@ -25,19 +25,21 @@ class GeocachingFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): """Return logger.""" return logging.getLogger(__name__) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an oauth config entry or update existing entry for reauth.""" api = GeocachingApi( environment=ENVIRONMENT, diff --git a/homeassistant/components/geonetnz_quakes/config_flow.py b/homeassistant/components/geonetnz_quakes/config_flow.py index 64db0ac2d7b..56cd45a13aa 100644 --- a/homeassistant/components/geonetnz_quakes/config_flow.py +++ b/homeassistant/components/geonetnz_quakes/config_flow.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, @@ -34,7 +34,7 @@ DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class GeonetnzQuakesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GeonetnzQuakesFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a GeoNet NZ Quakes config flow.""" async def _show_form(self, errors=None): diff --git a/homeassistant/components/geonetnz_volcano/config_flow.py b/homeassistant/components/geonetnz_volcano/config_flow.py index 34e16970d4f..d42f285481a 100644 --- a/homeassistant/components/geonetnz_volcano/config_flow.py +++ b/homeassistant/components/geonetnz_volcano/config_flow.py @@ -1,7 +1,7 @@ """Config flow to configure the GeoNet NZ Volcano integration.""" import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, @@ -31,7 +31,7 @@ def configured_instances(hass): } -class GeonetnzVolcanoFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GeonetnzVolcanoFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a GeoNet NZ Volcano config flow.""" async def _show_form(self, errors=None): diff --git a/homeassistant/components/gios/config_flow.py b/homeassistant/components/gios/config_flow.py index 1595b7ad131..bad7c37eebd 100644 --- a/homeassistant/components/gios/config_flow.py +++ b/homeassistant/components/gios/config_flow.py @@ -8,22 +8,21 @@ from aiohttp.client_exceptions import ClientConnectorError from gios import ApiError, Gios, InvalidSensorsDataError, NoStationError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import API_TIMEOUT, CONF_STATION_ID, DOMAIN -class GiosFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GiosFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for GIOS.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/github/config_flow.py b/homeassistant/components/github/config_flow.py index aa7ec7b6f86..36f1b19eea7 100644 --- a/homeassistant/components/github/config_flow.py +++ b/homeassistant/components/github/config_flow.py @@ -14,10 +14,14 @@ from aiogithubapi import ( from aiogithubapi.const import OAUTH_USER_LOGIN import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import ( SERVER_SOFTWARE, async_get_clientsession, @@ -88,7 +92,7 @@ async def get_repositories(hass: HomeAssistant, access_token: str) -> list[str]: ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GitHubConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for GitHub.""" VERSION = 1 @@ -104,7 +108,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="already_configured") @@ -114,7 +118,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_device( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle device steps.""" async def _wait_for_login() -> None: @@ -167,7 +171,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_repositories( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle repositories step.""" if TYPE_CHECKING: @@ -196,30 +200,30 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_could_not_register( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle issues that need transition await from progress step.""" return self.async_abort(reason="could_not_register") @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for GitHub.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if not user_input: configured_repositories: list[str] = self.config_entry.options[ diff --git a/homeassistant/components/glances/config_flow.py b/homeassistant/components/glances/config_flow.py index 81d3a118729..9fcf25aae0d 100644 --- a/homeassistant/components/glances/config_flow.py +++ b/homeassistant/components/glances/config_flow.py @@ -10,7 +10,7 @@ from glances_api.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -19,7 +19,6 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from . import ServerVersionMismatch, get_api from .const import DEFAULT_HOST, DEFAULT_PORT, DOMAIN @@ -36,13 +35,15 @@ DATA_SCHEMA = vol.Schema( ) -class GlancesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GlancesFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Glances config flow.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None + _reauth_entry: ConfigEntry | None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -51,7 +52,7 @@ class GlancesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} assert self._reauth_entry @@ -85,7 +86,7 @@ class GlancesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/goalzero/config_flow.py b/homeassistant/components/goalzero/config_flow.py index 2312b6bd183..e4d997d41f6 100644 --- a/homeassistant/components/goalzero/config_flow.py +++ b/homeassistant/components/goalzero/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from goalzero import Yeti, exceptions import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac @@ -19,7 +18,7 @@ from .const import DEFAULT_NAME, DOMAIN, MANUFACTURER _LOGGER = logging.getLogger(__name__) -class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GoalZeroFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Goal Zero Yeti.""" VERSION = 1 @@ -28,7 +27,9 @@ class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize a Goal Zero Yeti flow.""" self.ip_address: str | None = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" self.ip_address = discovery_info.ip @@ -43,7 +44,7 @@ class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Allow the user to confirm adding the device.""" if user_input is not None: return self.async_create_entry( @@ -65,7 +66,7 @@ class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/gogogate2/config_flow.py b/homeassistant/components/gogogate2/config_flow.py index 344e8473984..d0a63260107 100644 --- a/homeassistant/components/gogogate2/config_flow.py +++ b/homeassistant/components/gogogate2/config_flow.py @@ -10,14 +10,14 @@ from ismartgate.const import GogoGate2ApiErrorCode, ISmartGateApiErrorCode import voluptuous as vol from homeassistant.components import dhcp, zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_DEVICE, CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME, ) -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from .common import get_api from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN @@ -40,19 +40,21 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle homekit discovery.""" await self.async_set_unique_id( discovery_info.properties[zeroconf.ATTR_PROPERTIES_ID] ) return await self._async_discovery_handler(discovery_info.host) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" await self.async_set_unique_id(discovery_info.macaddress) return await self._async_discovery_handler(discovery_info.ip) - async def _async_discovery_handler(self, ip_address: str) -> FlowResult: + async def _async_discovery_handler(self, ip_address: str) -> ConfigFlowResult: """Start the user flow from any discovery.""" self.context[CONF_IP_ADDRESS] = ip_address self._abort_if_unique_id_configured({CONF_IP_ADDRESS: ip_address}) @@ -69,7 +71,7 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user initiated flow.""" user_input = user_input or {} errors = {} diff --git a/homeassistant/components/goodwe/config_flow.py b/homeassistant/components/goodwe/config_flow.py index ab82d4c453f..7e87f0307c8 100644 --- a/homeassistant/components/goodwe/config_flow.py +++ b/homeassistant/components/goodwe/config_flow.py @@ -6,9 +6,8 @@ import logging from goodwe import InverterError, connect import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from .const import CONF_MODEL_FAMILY, DEFAULT_NAME, DOMAIN @@ -21,12 +20,12 @@ CONFIG_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class GoodweFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GoodweFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Goodwe config flow.""" VERSION = 1 - async def async_step_user(self, user_input: dict | None = None) -> FlowResult: + async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/google/config_flow.py b/homeassistant/components/google/config_flow.py index ab38e67479f..cc6810df216 100644 --- a/homeassistant/components/google/config_flow.py +++ b/homeassistant/components/google/config_flow.py @@ -10,9 +10,8 @@ from gcal_sync.api import GoogleCalendarService from gcal_sync.exceptions import ApiException, ApiForbiddenException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult, OptionsFlow from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -74,7 +73,7 @@ class OAuth2FlowHandler( def __init__(self) -> None: """Set up instance.""" super().__init__() - self._reauth_config_entry: config_entries.ConfigEntry | None = None + self._reauth_config_entry: ConfigEntry | None = None self._device_flow: DeviceFlow | None = None # First attempt is device auth, then fallback to web auth self._web_auth = False @@ -94,7 +93,7 @@ class OAuth2FlowHandler( "prompt": "consent", } - async def async_step_import(self, info: dict[str, Any]) -> FlowResult: + async def async_step_import(self, info: dict[str, Any]) -> ConfigFlowResult: """Import existing auth into a new config entry.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -108,7 +107,7 @@ class OAuth2FlowHandler( async def async_step_auth( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create an entry for auth.""" # The default behavior from the parent class is to redirect the # user with an external step. When using the device flow, we instead @@ -179,13 +178,13 @@ class OAuth2FlowHandler( async def async_step_creation( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle external yaml configuration.""" if not self._web_auth and self.external_data.get(DEVICE_AUTH_CREDS) is None: return self.async_abort(reason="code_expired") return await super().async_step_creation(user_input) - async def async_oauth_create_entry(self, data: dict) -> FlowResult: + async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" data[CONF_CREDENTIAL_TYPE] = ( CredentialType.WEB_AUTH if self._web_auth else CredentialType.DEVICE_AUTH @@ -230,7 +229,9 @@ class OAuth2FlowHandler( }, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_config_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -240,7 +241,7 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") @@ -249,22 +250,22 @@ class OAuth2FlowHandler( @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create an options flow.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Google Calendar options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/google_assistant/config_flow.py b/homeassistant/components/google_assistant/config_flow.py index e8e0d9962f9..9504c623138 100644 --- a/homeassistant/components/google_assistant/config_flow.py +++ b/homeassistant/components/google_assistant/config_flow.py @@ -1,11 +1,11 @@ """Config flow for google assistant component.""" -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from .const import CONF_PROJECT_ID, DOMAIN -class GoogleAssistantHandler(config_entries.ConfigFlow, domain=DOMAIN): +class GoogleAssistantHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 diff --git a/homeassistant/components/google_assistant_sdk/config_flow.py b/homeassistant/components/google_assistant_sdk/config_flow.py index b4f617ca029..3813abb9b3a 100644 --- a/homeassistant/components/google_assistant_sdk/config_flow.py +++ b/homeassistant/components/google_assistant_sdk/config_flow.py @@ -7,10 +7,8 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult, OptionsFlow from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import CONF_LANGUAGE_CODE, DEFAULT_NAME, DOMAIN, SUPPORTED_LANGUAGE_CODES @@ -43,7 +41,9 @@ class OAuth2FlowHandler( "prompt": "consent", } - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -52,13 +52,13 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" if self.reauth_entry: self.hass.config_entries.async_update_entry(self.reauth_entry, data=data) @@ -80,22 +80,22 @@ class OAuth2FlowHandler( @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create the options flow.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Google Assistant SDK options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/google_generative_ai_conversation/config_flow.py b/homeassistant/components/google_generative_ai_conversation/config_flow.py index 74ba3c478df..e386e924a7b 100644 --- a/homeassistant/components/google_generative_ai_conversation/config_flow.py +++ b/homeassistant/components/google_generative_ai_conversation/config_flow.py @@ -11,10 +11,14 @@ from google.api_core.exceptions import ClientError import google.generativeai as genai import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( NumberSelector, NumberSelectorConfig, @@ -66,14 +70,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: await hass.async_add_executor_job(partial(genai.list_models)) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GoogleGenerativeAIConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Google Generative AI Conversation.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -103,22 +107,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create the options flow.""" - return OptionsFlow(config_entry) + return GoogleGenerativeAIOptionsFlow(config_entry) -class OptionsFlow(config_entries.OptionsFlow): +class GoogleGenerativeAIOptionsFlow(OptionsFlow): """Google Generative AI config flow options handler.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/google_mail/config_flow.py b/homeassistant/components/google_mail/config_flow.py index b57947302cc..19f82663c34 100644 --- a/homeassistant/components/google_mail/config_flow.py +++ b/homeassistant/components/google_mail/config_flow.py @@ -8,9 +8,8 @@ from typing import Any, cast from google.oauth2.credentials import Credentials from googleapiclient.discovery import build -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DEFAULT_ACCESS, DOMAIN @@ -40,7 +39,9 @@ class OAuth2FlowHandler( "prompt": "consent", } - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -49,13 +50,13 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" def _get_profile() -> str: diff --git a/homeassistant/components/google_sheets/config_flow.py b/homeassistant/components/google_sheets/config_flow.py index 3805ee9d38b..4c1455f3c2e 100644 --- a/homeassistant/components/google_sheets/config_flow.py +++ b/homeassistant/components/google_sheets/config_flow.py @@ -8,9 +8,8 @@ from typing import Any from google.oauth2.credentials import Credentials from gspread import Client, GSpreadException -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DEFAULT_ACCESS, DEFAULT_NAME, DOMAIN @@ -42,7 +41,9 @@ class OAuth2FlowHandler( "prompt": "consent", } - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -51,13 +52,13 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" service = Client(Credentials(data[CONF_TOKEN][CONF_ACCESS_TOKEN])) diff --git a/homeassistant/components/google_tasks/config_flow.py b/homeassistant/components/google_tasks/config_flow.py index b8e5e26f42c..6fd71cd3d06 100644 --- a/homeassistant/components/google_tasks/config_flow.py +++ b/homeassistant/components/google_tasks/config_flow.py @@ -7,8 +7,8 @@ from googleapiclient.discovery import build from googleapiclient.errors import HttpError from googleapiclient.http import HttpRequest +from homeassistant.config_entries import ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN, OAUTH2_SCOPES @@ -36,7 +36,7 @@ class OAuth2FlowHandler( "prompt": "consent", } - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow.""" try: resource = build( diff --git a/homeassistant/components/google_translate/config_flow.py b/homeassistant/components/google_translate/config_flow.py index 3996d41df35..a5b94bafd90 100644 --- a/homeassistant/components/google_translate/config_flow.py +++ b/homeassistant/components/google_translate/config_flow.py @@ -5,9 +5,8 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.tts import CONF_LANG -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import ( CONF_TLD, @@ -26,14 +25,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GoogleTranslateConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Google Translate text-to-speech.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: self._async_abort_entries_match( @@ -50,7 +49,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_onboarding( self, data: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by onboarding.""" return self.async_create_entry( title="Google Translate text-to-speech", diff --git a/homeassistant/components/google_travel_time/config_flow.py b/homeassistant/components/google_travel_time/config_flow.py index 73a4bf87b7e..8076df53433 100644 --- a/homeassistant/components/google_travel_time/config_flow.py +++ b/homeassistant/components/google_travel_time/config_flow.py @@ -3,10 +3,14 @@ from __future__ import annotations import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_LANGUAGE, CONF_MODE, CONF_NAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( SelectSelector, @@ -124,14 +128,14 @@ def default_options(hass: HomeAssistant) -> dict[str, str]: } -class GoogleOptionsFlow(config_entries.OptionsFlow): +class GoogleOptionsFlow(OptionsFlow): """Handle an options flow for Google Travel Time.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize google options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: time_type = user_input.pop(CONF_TIME_TYPE) @@ -159,7 +163,7 @@ class GoogleOptionsFlow(config_entries.OptionsFlow): ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Google Maps Travel Time.""" VERSION = 1 @@ -167,12 +171,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> GoogleOptionsFlow: """Get the options flow for this handler.""" return GoogleOptionsFlow(config_entry) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle the initial step.""" errors = {} user_input = user_input or {} diff --git a/homeassistant/components/govee_ble/config_flow.py b/homeassistant/components/govee_ble/config_flow.py index fc6fe7b310d..ecd39cfbff0 100644 --- a/homeassistant/components/govee_ble/config_flow.py +++ b/homeassistant/components/govee_ble/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class GoveeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class GoveeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class GoveeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/gpsd/config_flow.py b/homeassistant/components/gpsd/config_flow.py index db1f9c5b0c1..9979fd6c922 100644 --- a/homeassistant/components/gpsd/config_flow.py +++ b/homeassistant/components/gpsd/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from gps3.agps3threaded import GPSD_PORT as DEFAULT_PORT, HOST as DEFAULT_HOST import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import DOMAIN @@ -22,18 +21,18 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class GPSDConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GPSDConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for GPSD.""" VERSION = 1 - async def async_step_import(self, import_data: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_data) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: self._async_abort_entries_match(user_input) diff --git a/homeassistant/components/growatt_server/config_flow.py b/homeassistant/components/growatt_server/config_flow.py index a4dcd25173f..e04a623c72d 100644 --- a/homeassistant/components/growatt_server/config_flow.py +++ b/homeassistant/components/growatt_server/config_flow.py @@ -2,7 +2,7 @@ import growattServer import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME from homeassistant.core import callback @@ -15,7 +15,7 @@ from .const import ( ) -class GrowattServerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GrowattServerConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow class.""" VERSION = 1 diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index c027fe8bc20..fb160c7f959 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -7,11 +7,10 @@ from aioguardian import Client from aioguardian.errors import GuardianError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp, zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from .const import CONF_UID, DOMAIN, LOGGER @@ -52,7 +51,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class GuardianConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Elexa Guardian.""" VERSION = 1 @@ -76,7 +75,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle configuration via the UI.""" if user_input is None: return self.async_show_form( @@ -100,7 +99,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): title=info[CONF_UID], data={CONF_UID: info["uid"], **user_input} ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle the configuration via dhcp.""" self.discovery_info = { CONF_IP_ADDRESS: discovery_info.ip, @@ -113,7 +114,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the configuration via zeroconf.""" self.discovery_info = { CONF_IP_ADDRESS: discovery_info.host, @@ -123,7 +124,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self._async_set_unique_id(pin) return await self._async_handle_discovery() - async def _async_handle_discovery(self) -> FlowResult: + async def _async_handle_discovery(self) -> ConfigFlowResult: """Handle any discovery.""" self.context[CONF_IP_ADDRESS] = self.discovery_info[CONF_IP_ADDRESS] if any( @@ -136,7 +137,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Finish the configuration via any discovery.""" if user_input is None: self._set_confirm_only() diff --git a/homeassistant/components/habitica/config_flow.py b/homeassistant/components/habitica/config_flow.py index 5e016dfe605..4c607ea4a38 100644 --- a/homeassistant/components/habitica/config_flow.py +++ b/homeassistant/components/habitica/config_flow.py @@ -7,8 +7,10 @@ from aiohttp import ClientResponseError from habitipy.aio import HabitipyAsync import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_API_USER, DEFAULT_URL, DOMAIN @@ -25,9 +27,7 @@ DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, str] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect.""" websession = async_get_clientsession(hass) @@ -48,7 +48,7 @@ async def validate_input( raise InvalidAuth() from ex -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HabiticaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for habitica.""" VERSION = 1 @@ -81,5 +81,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user(import_data) -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/hardkernel/config_flow.py b/homeassistant/components/hardkernel/config_flow.py index b0445fae231..457ad3b97b5 100644 --- a/homeassistant/components/hardkernel/config_flow.py +++ b/homeassistant/components/hardkernel/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -14,7 +13,9 @@ class HardkernelConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult: + async def async_step_system( + self, data: dict[str, Any] | None = None + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index ad041e75f1a..5732740dd20 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -10,16 +10,21 @@ from aioharmony.hubconnector_websocket import HubConnector import aiohttp import voluptuous as vol -from homeassistant import config_entries, exceptions from homeassistant.components import ssdp from homeassistant.components.remote import ( ATTR_ACTIVITY, ATTR_DELAY_SECS, DEFAULT_DELAY_SECS, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN, HARMONY_DATA, PREVIOUS_ACTIVE_ACTIVITY, UNIQUE_ID from .util import ( @@ -51,7 +56,7 @@ async def validate_input(data: dict[str, Any]) -> dict[str, Any]: } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HarmonyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Logitech Harmony Hub.""" VERSION = 1 @@ -62,7 +67,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -86,7 +91,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered Harmony device.""" _LOGGER.debug("SSDP discovery_info: %s", discovery_info) @@ -120,7 +127,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to link with the Harmony.""" errors: dict[str, str] = {} @@ -144,14 +151,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) async def _async_create_entry_from_valid_input( self, validated: dict[str, Any], user_input: dict[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: """Single path to create the config entry from validated input.""" data = { @@ -174,16 +181,16 @@ def _options_from_user_input(user_input: dict[str, Any]) -> dict[str, Any]: return options -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for Harmony.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -209,5 +216,5 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=data_schema) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py index ef09f07b4de..af4e778f91f 100644 --- a/homeassistant/components/hassio/config_flow.py +++ b/homeassistant/components/hassio/config_flow.py @@ -4,22 +4,21 @@ from __future__ import annotations import logging from typing import Any -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from . import DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HassIoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Home Assistant Supervisor.""" VERSION = 1 async def async_step_system( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" # We only need one Hass.io config entry await self.async_set_unique_id(DOMAIN) diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 63a020c41d9..9e7e288dffc 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -5,10 +5,9 @@ from urllib.parse import urlparse from pyheos import Heos, HeosError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from .const import DATA_DISCOVERED_HOSTS, DOMAIN @@ -18,12 +17,14 @@ def format_title(host: str) -> str: return f"Controller ({host})" -class HeosFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class HeosFlowHandler(ConfigFlow, domain=DOMAIN): """Define a flow for HEOS.""" VERSION = 1 - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered Heos device.""" # Store discovered host if TYPE_CHECKING: diff --git a/homeassistant/components/here_travel_time/config_flow.py b/homeassistant/components/here_travel_time/config_flow.py index 3db4a841d53..9bca80b7843 100644 --- a/homeassistant/components/here_travel_time/config_flow.py +++ b/homeassistant/components/here_travel_time/config_flow.py @@ -14,7 +14,12 @@ from here_routing import ( from here_transit import HERETransitError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, @@ -23,7 +28,6 @@ from homeassistant.const import ( CONF_NAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( EntitySelector, @@ -91,7 +95,7 @@ def get_user_step_schema(data: dict[str, Any]) -> vol.Schema: ) -class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for HERE Travel Time.""" VERSION = 1 @@ -103,14 +107,14 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> HERETravelTimeOptionsFlow: """Get the options flow.""" return HERETravelTimeOptionsFlow(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} user_input = user_input or {} @@ -129,7 +133,7 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=get_user_step_schema(user_input), errors=errors ) - async def async_step_origin_menu(self, _: None = None) -> FlowResult: + async def async_step_origin_menu(self, _: None = None) -> ConfigFlowResult: """Show the origin menu.""" return self.async_show_menu( step_id="origin_menu", @@ -138,7 +142,7 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_origin_coordinates( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure origin by using gps coordinates.""" if user_input is not None: self._config[CONF_ORIGIN_LATITUDE] = user_input[CONF_ORIGIN][CONF_LATITUDE] @@ -159,7 +163,7 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_show_form(step_id="origin_coordinates", data_schema=schema) - async def async_step_destination_menu(self, _: None = None) -> FlowResult: + async def async_step_destination_menu(self, _: None = None) -> ConfigFlowResult: """Show the destination menu.""" return self.async_show_menu( step_id="destination_menu", @@ -168,7 +172,7 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_origin_entity( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure origin by using an entity.""" if user_input is not None: self._config[CONF_ORIGIN_ENTITY_ID] = user_input[CONF_ORIGIN_ENTITY_ID] @@ -179,7 +183,7 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_destination_coordinates( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure destination by using gps coordinates.""" if user_input is not None: self._config[CONF_DESTINATION_LATITUDE] = user_input[CONF_DESTINATION][ @@ -211,7 +215,7 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_destination_entity( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure destination by using an entity.""" if user_input is not None: self._config[CONF_DESTINATION_ENTITY_ID] = user_input[ @@ -228,17 +232,17 @@ class HERETravelTimeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="destination_entity", data_schema=schema) -class HERETravelTimeOptionsFlow(config_entries.OptionsFlow): +class HERETravelTimeOptionsFlow(OptionsFlow): """Handle HERE Travel Time options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize HERE Travel Time options flow.""" self.config_entry = config_entry self._config: dict[str, Any] = {} async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the HERE Travel Time options.""" if user_input is not None: self._config = user_input @@ -257,7 +261,7 @@ class HERETravelTimeOptionsFlow(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=schema) - async def async_step_time_menu(self, _: None = None) -> FlowResult: + async def async_step_time_menu(self, _: None = None) -> ConfigFlowResult: """Show the time menu.""" return self.async_show_menu( step_id="time_menu", @@ -266,13 +270,13 @@ class HERETravelTimeOptionsFlow(config_entries.OptionsFlow): async def async_step_no_time( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create Options Entry.""" return self.async_create_entry(title="", data=self._config) async def async_step_arrival_time( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure arrival time.""" if user_input is not None: self._config[CONF_ARRIVAL_TIME] = user_input[CONF_ARRIVAL_TIME] @@ -286,7 +290,7 @@ class HERETravelTimeOptionsFlow(config_entries.OptionsFlow): async def async_step_departure_time( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure departure time.""" if user_input is not None: self._config[CONF_DEPARTURE_TIME] = user_input[CONF_DEPARTURE_TIME] diff --git a/homeassistant/components/hive/config_flow.py b/homeassistant/components/hive/config_flow.py index ec1c4f78e87..436c1e35da1 100644 --- a/homeassistant/components/hive/config_flow.py +++ b/homeassistant/components/hive/config_flow.py @@ -13,15 +13,20 @@ from apyhiveapi.helper.hive_exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import CONF_CODE, CONF_DEVICE_NAME, CONFIG_ENTRY_VERSION, DOMAIN -class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class HiveFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Hive config flow.""" VERSION = CONFIG_ENTRY_VERSION @@ -47,7 +52,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Get user from existing entry and abort if already setup self.entry = await self.async_set_unique_id(self.data[CONF_USERNAME]) - if self.context["source"] != config_entries.SOURCE_REAUTH: + if self.context["source"] != SOURCE_REAUTH: self._abort_if_unique_id_configured() # Login to the Hive. @@ -94,7 +99,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "no_internet_available" if not errors: - if self.context["source"] == config_entries.SOURCE_REAUTH: + if self.context["source"] == SOURCE_REAUTH: return await self.async_setup_hive_entry() self.device_registration = True return await self.async_step_configuration() @@ -132,7 +137,7 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Setup the config entry self.data["tokens"] = self.tokens - if self.context["source"] == config_entries.SOURCE_REAUTH: + if self.context["source"] == SOURCE_REAUTH: self.hass.config_entries.async_update_entry( self.entry, title=self.data["username"], data=self.data ) @@ -140,7 +145,9 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") return self.async_create_entry(title=self.data["username"], data=self.data) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Re Authenticate a user.""" data = { CONF_USERNAME: entry_data[CONF_USERNAME], @@ -155,16 +162,16 @@ class HiveFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> HiveOptionsFlowHandler: """Hive options callback.""" return HiveOptionsFlowHandler(config_entry) -class HiveOptionsFlowHandler(config_entries.OptionsFlow): +class HiveOptionsFlowHandler(OptionsFlow): """Config flow options for Hive.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Hive options flow.""" self.hive = None self.config_entry = config_entry diff --git a/homeassistant/components/hko/config_flow.py b/homeassistant/components/hko/config_flow.py index 21697d2dd53..17d74f59f8f 100644 --- a/homeassistant/components/hko/config_flow.py +++ b/homeassistant/components/hko/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from hko import HKO, LOCATIONS, HKOError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LOCATION -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig @@ -30,14 +29,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HKOConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Hong Kong Observatory.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/hlk_sw16/config_flow.py b/homeassistant/components/hlk_sw16/config_flow.py index 6ea5f9d43db..dc96a560298 100644 --- a/homeassistant/components/hlk_sw16/config_flow.py +++ b/homeassistant/components/hlk_sw16/config_flow.py @@ -4,7 +4,7 @@ import asyncio from hlk_sw16 import create_hlk_sw16_connection import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant @@ -63,7 +63,7 @@ async def validate_input(hass: HomeAssistant, user_input): client.stop() -class SW16FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SW16FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a HLK-SW16 config flow.""" VERSION = 1 diff --git a/homeassistant/components/holiday/config_flow.py b/homeassistant/components/holiday/config_flow.py index 07da19167d7..c99efae789d 100644 --- a/homeassistant/components/holiday/config_flow.py +++ b/homeassistant/components/holiday/config_flow.py @@ -7,9 +7,8 @@ from babel import Locale, UnknownLocaleError from holidays import list_supported_countries import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_COUNTRY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( CountrySelector, CountrySelectorConfig, @@ -23,7 +22,7 @@ from .const import CONF_PROVINCE, DOMAIN SUPPORTED_COUNTRIES = list_supported_countries(include_aliases=False) -class HolidayConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HolidayConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Holiday.""" VERSION = 1 @@ -34,7 +33,7 @@ class HolidayConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: self.data = user_input @@ -71,7 +70,7 @@ class HolidayConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_province( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the province step.""" if user_input is not None: combined_input: dict[str, Any] = {**self.data, **user_input} diff --git a/homeassistant/components/homeassistant_green/config_flow.py b/homeassistant/components/homeassistant_green/config_flow.py index c3491de430e..83b16ac9799 100644 --- a/homeassistant/components/homeassistant_green/config_flow.py +++ b/homeassistant/components/homeassistant_green/config_flow.py @@ -14,9 +14,13 @@ from homeassistant.components.hassio import ( async_set_green_settings, is_hassio, ) -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .const import DOMAIN @@ -46,7 +50,9 @@ class HomeAssistantGreenConfigFlow(ConfigFlow, domain=DOMAIN): """Return the options flow.""" return HomeAssistantGreenOptionsFlow() - async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult: + async def async_step_system( + self, data: dict[str, Any] | None = None + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -61,7 +67,7 @@ class HomeAssistantGreenOptionsFlow(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if not is_hassio(self.hass): return self.async_abort(reason="not_hassio") @@ -70,7 +76,7 @@ class HomeAssistantGreenOptionsFlow(OptionsFlow): async def async_step_hardware_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle hardware settings.""" if user_input is not None: diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index ef953213fc8..2a2d617c5b4 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -10,7 +10,6 @@ from typing import Any, Protocol import voluptuous as vol import yarl -from homeassistant import config_entries from homeassistant.components.hassio import ( AddonError, AddonInfo, @@ -19,8 +18,14 @@ from homeassistant.components.hassio import ( hostname_from_addon_slug, is_hassio, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlowResult, + OptionsFlow, + OptionsFlowManager, +) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, @@ -295,10 +300,10 @@ def is_multiprotocol_url(url: str) -> bool: return parsed.host == hostname -class OptionsFlowHandler(config_entries.OptionsFlow, ABC): +class OptionsFlowHandler(OptionsFlow, ABC): """Handle an options flow for the Silicon Labs Multiprotocol add-on.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Set up the options flow.""" # pylint: disable-next=import-outside-toplevel from homeassistant.components.zha.radio_manager import ( @@ -334,7 +339,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): """Return the ZHA name.""" @property - def flow_manager(self) -> config_entries.OptionsFlowManager: + def flow_manager(self) -> OptionsFlowManager: """Return the correct flow manager.""" return self.hass.config_entries.options @@ -363,7 +368,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if not is_hassio(self.hass): return self.async_abort(reason="not_hassio") @@ -372,7 +377,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when on Supervisor host.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) addon_info = await self._async_get_addon_info(multipan_manager) @@ -383,7 +388,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_addon_not_installed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when the addon is not yet installed.""" if user_input is None: return self.async_show_form( @@ -400,7 +405,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_install_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Install Silicon Labs Multiprotocol add-on.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) @@ -430,7 +435,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_install_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on installation failed.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) return self.async_abort( @@ -440,7 +445,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_configure_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure the Silicon Labs Multiprotocol add-on.""" # pylint: disable-next=import-outside-toplevel from homeassistant.components.zha import DOMAIN as ZHA_DOMAIN @@ -509,7 +514,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_start_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start Silicon Labs Multiprotocol add-on.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) @@ -538,7 +543,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_start_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on start failed.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) return self.async_abort( @@ -548,7 +553,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_finish_addon_setup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare info needed to complete the config entry update.""" # Always reload entry after installing the addon. self.hass.async_create_task( @@ -567,7 +572,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_addon_installed_other_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show dialog explaining the addon is in use by another device.""" if user_input is None: return self.async_show_form(step_id="addon_installed_other_device") @@ -575,7 +580,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_addon_installed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when the addon is already installed.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) addon_info = await self._async_get_addon_info(multipan_manager) @@ -587,7 +592,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_addon_menu( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show menu options for the addon.""" return self.async_show_menu( step_id="addon_menu", @@ -599,7 +604,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_reconfigure_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Reconfigure the addon.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) active_platforms = await multipan_manager.async_active_platforms() @@ -609,7 +614,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_notify_unknown_multipan_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Notify that there may be unknown multipan platforms.""" if user_input is None: return self.async_show_form( @@ -619,7 +624,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_change_channel( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Change the channel.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) if user_input is None: @@ -651,7 +656,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_notify_channel_change( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Notify that the channel change will take about five minutes.""" if user_input is None: return self.async_show_form( @@ -664,7 +669,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_uninstall_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Uninstall the addon and revert the firmware.""" if user_input is None: return self.async_show_form( @@ -681,7 +686,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_firmware_revert( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Install the flasher addon, if necessary.""" flasher_manager = get_flasher_addon_manager(self.hass) @@ -701,7 +706,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_install_flasher_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show progress dialog for installing flasher addon.""" flasher_manager = get_flasher_addon_manager(self.hass) addon_info = await self._async_get_addon_info(flasher_manager) @@ -734,7 +739,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_configure_flasher_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Perform initial backup and reconfigure ZHA.""" # pylint: disable-next=import-outside-toplevel from homeassistant.components.zha import DOMAIN as ZHA_DOMAIN @@ -794,7 +799,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_uninstall_multiprotocol_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Uninstall Silicon Labs Multiprotocol add-on.""" multipan_manager = await get_multiprotocol_addon_manager(self.hass) @@ -821,7 +826,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_start_flasher_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start Silicon Labs Flasher add-on.""" flasher_manager = get_flasher_addon_manager(self.hass) @@ -856,7 +861,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_flasher_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Flasher add-on start failed.""" flasher_manager = get_flasher_addon_manager(self.hass) return self.async_abort( @@ -866,7 +871,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow, ABC): async def async_step_flashing_complete( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Finish flashing and update the config entry.""" flasher_manager = get_flasher_addon_manager(self.hass) await flasher_manager.async_uninstall_addon_waiting() diff --git a/homeassistant/components/homeassistant_sky_connect/config_flow.py b/homeassistant/components/homeassistant_sky_connect/config_flow.py index fce731777b1..002fc9c4f62 100644 --- a/homeassistant/components/homeassistant_sky_connect/config_flow.py +++ b/homeassistant/components/homeassistant_sky_connect/config_flow.py @@ -5,9 +5,8 @@ from typing import Any from homeassistant.components import usb from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN from .util import get_usb_service_info @@ -26,7 +25,9 @@ class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN): """Return the options flow.""" return HomeAssistantSkyConnectOptionsFlow(config_entry) - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle usb discovery.""" device = discovery_info.device vid = discovery_info.vid diff --git a/homeassistant/components/homeassistant_yellow/config_flow.py b/homeassistant/components/homeassistant_yellow/config_flow.py index 7681d6d3847..94dd511cac5 100644 --- a/homeassistant/components/homeassistant_yellow/config_flow.py +++ b/homeassistant/components/homeassistant_yellow/config_flow.py @@ -15,9 +15,8 @@ from homeassistant.components.hassio import ( async_set_yellow_settings, ) from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .const import DOMAIN, ZHA_HW_DISCOVERY_DATA @@ -46,7 +45,9 @@ class HomeAssistantYellowConfigFlow(ConfigFlow, domain=DOMAIN): """Return the options flow.""" return HomeAssistantYellowOptionsFlow(config_entry) - async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult: + async def async_step_system( + self, data: dict[str, Any] | None = None + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -61,11 +62,11 @@ class HomeAssistantYellowOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandl async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when on Supervisor host.""" return await self.async_step_main_menu() - async def async_step_main_menu(self, _: None = None) -> FlowResult: + async def async_step_main_menu(self, _: None = None) -> ConfigFlowResult: """Show the main menu.""" return self.async_show_menu( step_id="main_menu", @@ -77,7 +78,7 @@ class HomeAssistantYellowOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandl async def async_step_hardware_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle hardware settings.""" if user_input is not None: @@ -108,7 +109,7 @@ class HomeAssistantYellowOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandl async def async_step_reboot_menu( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reboot host.""" return self.async_show_menu( step_id="reboot_menu", @@ -120,20 +121,20 @@ class HomeAssistantYellowOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandl async def async_step_reboot_now( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Reboot now.""" await async_reboot_host(self.hass) return self.async_create_entry(data={}) async def async_step_reboot_later( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Reboot later.""" return self.async_create_entry(data={}) async def async_step_multipan_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle multipan settings.""" return await super().async_step_on_supervisor(user_input) diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index d7c8ea65e2d..772e2bbce1d 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -11,13 +11,18 @@ from typing import Any, Final, TypedDict import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import device_automation from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import ( + SOURCE_IMPORT, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( ATTR_FRIENDLY_NAME, CONF_DEVICES, @@ -28,7 +33,6 @@ from homeassistant.const import ( CONF_PORT, ) from homeassistant.core import HomeAssistant, callback, split_entity_id -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -191,7 +195,7 @@ async def _async_name_to_type_map(hass: HomeAssistant) -> dict[str, str]: } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HomeKitConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for HomeKit.""" VERSION = 1 @@ -202,7 +206,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose specific domains in bridge mode.""" if user_input is not None: self.hk_data[CONF_FILTER] = _make_entity_filter( @@ -228,7 +232,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pairing( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Pairing instructions.""" hk_data = self.hk_data @@ -278,7 +282,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) ) - async def async_step_accessory(self, accessory_input: dict[str, Any]) -> FlowResult: + async def async_step_accessory( + self, accessory_input: dict[str, Any] + ) -> ConfigFlowResult: """Handle creation a single accessory in accessory mode.""" entity_id = accessory_input[CONF_ENTITY_ID] port = accessory_input[CONF_PORT] @@ -302,7 +308,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): title=f"{name}:{entry_data[CONF_PORT]}", data=entry_data ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Handle import from yaml.""" if not self._async_is_unique_name_port(user_input): return self.async_abort(reason="port_name_in_use") @@ -349,16 +355,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for homekit.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self.hk_options: dict[str, Any] = {} @@ -366,7 +372,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_yaml( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """No options for yaml managed entries.""" if user_input is not None: # Apparently not possible to abort an options flow @@ -377,7 +383,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_advanced( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose advanced options.""" hk_options = self.hk_options show_advanced_options = self.show_advanced_options @@ -414,7 +420,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_cameras( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose camera config.""" hk_options = self.hk_options all_entity_config: dict[str, dict[str, Any]] @@ -465,7 +471,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_accessory( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose entity for the accessory.""" hk_options = self.hk_options domains = hk_options[CONF_DOMAINS] @@ -508,7 +514,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_include( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose entities to include from the domain on the bridge.""" hk_options = self.hk_options domains = hk_options[CONF_DOMAINS] @@ -546,7 +552,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_exclude( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose entities to exclude from the domain on the bridge.""" hk_options = self.hk_options domains = hk_options[CONF_DOMAINS] @@ -598,7 +604,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if self.config_entry.source == SOURCE_IMPORT: return await self.async_step_yaml(user_input) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 7f0f288400d..f0931e382dd 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -18,10 +18,10 @@ from aiohomekit.model.status_flags import StatusFlags from aiohomekit.utils import domain_supported, domain_to_name, serialize_broadcast_key import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import device_registry as dr from .const import DOMAIN, KNOWN_DEVICES @@ -95,7 +95,7 @@ def ensure_pin_format(pin: str, allow_insecure_setup_codes: Any = None) -> str: return "-".join(match.groups()) -class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class HomekitControllerFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a HomeKit config flow.""" VERSION = 1 @@ -116,7 +116,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" errors: dict[str, str] = {} @@ -166,7 +166,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ), ) - async def async_step_unignore(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_unignore(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Rediscover a previously ignored discover.""" unique_id = user_input["unique_id"] await self.async_set_unique_id(unique_id) @@ -208,7 +208,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered HomeKit accessory. This flow is triggered by the discovery component. @@ -361,7 +361,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: bluetooth.BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" if not aiohomekit_const.BLE_TRANSPORT_SUPPORTED: return self.async_abort(reason="ignored_model") @@ -409,7 +409,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pair( self, pair_info: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Pair with a new HomeKit accessory.""" # If async_step_pair is called with no pairing code then we do the M1 # phase of pairing. If this is successful the device enters pairing @@ -516,7 +516,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_busy_error( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Retry pairing after the accessory is busy.""" if user_input is not None: return await self.async_step_pair() @@ -525,7 +525,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_max_tries_error( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Retry pairing after the accessory has reached max tries.""" if user_input is not None: return await self.async_step_pair() @@ -534,7 +534,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_protocol_error( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Retry pairing after the accessory has a protocol error.""" if user_input is not None: return await self.async_step_pair() @@ -546,7 +546,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, errors: dict[str, str] | None = None, description_placeholders: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: assert self.category placeholders = self.context["title_placeholders"] = { @@ -565,7 +565,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema=vol.Schema(schema), ) - async def _entry_from_accessory(self, pairing: AbstractPairing) -> FlowResult: + async def _entry_from_accessory(self, pairing: AbstractPairing) -> ConfigFlowResult: """Return a config entry from an initialized bridge.""" # The bulk of the pairing record is stored on the config entry. # A specific exception is the 'accessories' key. This is more diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py index 8581367d4ee..643a525ea25 100644 --- a/homeassistant/components/homematicip_cloud/config_flow.py +++ b/homeassistant/components/homematicip_cloud/config_flow.py @@ -5,14 +5,13 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import _LOGGER, DOMAIN, HMIPC_AUTHTOKEN, HMIPC_HAPID, HMIPC_NAME, HMIPC_PIN from .hap import HomematicipAuth -class HomematicipCloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class HomematicipCloudFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for the HomematicIP Cloud component.""" VERSION = 1 @@ -24,13 +23,13 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self.async_step_init(user_input) async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" errors = {} @@ -61,7 +60,7 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_link(self, user_input: None = None) -> FlowResult: + async def async_step_link(self, user_input: None = None) -> ConfigFlowResult: """Attempt to link with the HomematicIP Cloud access point.""" errors = {} @@ -83,7 +82,7 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="link", errors=errors) - async def async_step_import(self, import_info: dict[str, str]) -> FlowResult: + async def async_step_import(self, import_info: dict[str, str]) -> ConfigFlowResult: """Import a new access point as a config entry.""" hapid = import_info[HMIPC_HAPID].replace("-", "").upper() authtoken = import_info[HMIPC_AUTHTOKEN] diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index bf425fe5c41..067007589bb 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -11,9 +11,9 @@ from homewizard_energy.models import Device from voluptuous import Required, Schema from homeassistant.components import onboarding, zeroconf -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_PATH -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError from .const import ( @@ -46,7 +46,7 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors: dict[str, str] | None = None if user_input is not None: @@ -77,7 +77,7 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" if ( CONF_API_ENABLED not in discovery_info.properties @@ -109,7 +109,7 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" errors: dict[str, str] | None = None if user_input is not None or not onboarding.async_is_onboarded(self.hass): @@ -143,14 +143,16 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-auth if API was disabled.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors: dict[str, str] | None = None if user_input is not None: diff --git a/homeassistant/components/honeywell/config_flow.py b/homeassistant/components/honeywell/config_flow.py index aeb72899e11..8fb05f7e47d 100644 --- a/homeassistant/components/honeywell/config_flow.py +++ b/homeassistant/components/honeywell/config_flow.py @@ -7,10 +7,14 @@ from typing import Any import aiosomecomfort import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -29,13 +33,15 @@ REAUTH_SCHEMA = vol.Schema( ) -class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HoneywellConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a honeywell config flow.""" VERSION = 1 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Honeywell.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -43,7 +49,7 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Honeywell.""" errors: dict[str, str] = {} assert self.entry is not None @@ -81,7 +87,7 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Create config entry. Show the setup form to the user.""" errors = {} if user_input is not None: @@ -124,20 +130,20 @@ class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> HoneywellOptionsFlowHandler: """Options callback for Honeywell.""" return HoneywellOptionsFlowHandler(config_entry) -class HoneywellOptionsFlowHandler(config_entries.OptionsFlow): +class HoneywellOptionsFlowHandler(OptionsFlow): """Config flow options for Honeywell.""" - def __init__(self, entry: config_entries.ConfigEntry) -> None: + def __init__(self, entry: ConfigEntry) -> None: """Initialize Honeywell options flow.""" self.config_entry = entry - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title=DOMAIN, data=user_input) diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index c97c8d6367b..9138a709c62 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -20,8 +20,13 @@ from requests.exceptions import SSLError, Timeout from url_normalize import url_normalize import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_MAC, CONF_NAME, @@ -32,7 +37,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_MANUFACTURER, @@ -50,7 +54,7 @@ from .utils import get_device_macs, non_verifying_requests_session _LOGGER = logging.getLogger(__name__) -class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class ConfigFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Huawei LTE config flow.""" VERSION = 3 @@ -58,7 +62,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get options flow.""" return OptionsFlowHandler(config_entry) @@ -67,7 +71,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: if user_input is None: user_input = {} return self.async_show_form( @@ -103,7 +107,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any], errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: return self.async_show_form( step_id="reauth_confirm", data_schema=vol.Schema( @@ -181,7 +185,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user initiated config flow.""" if user_input is None: return await self._async_show_user_form() @@ -256,7 +260,9 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=title, data=user_input) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle SSDP initiated config flow.""" if TYPE_CHECKING: @@ -302,13 +308,15 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return await self._async_show_user_form() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry @@ -335,16 +343,16 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Huawei LTE options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" # Recipients are persisted as a list, but handled as comma separated string in UI diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index a1345cf3bba..b7624f2c0f0 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -12,11 +12,15 @@ from aiohue.util import normalize_bridge_id import slugify as unicode_slug import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_API_VERSION, CONF_HOST from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import ( aiohttp_client, config_validation as cv, @@ -40,7 +44,7 @@ HUE_IGNORED_BRIDGE_NAMES = ["Home Assistant Bridge", "Espalexa"] HUE_MANUAL_BRIDGE_ID = "manual" -class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class HueFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Hue config flow.""" VERSION = 1 @@ -48,7 +52,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> HueV1OptionsFlowHandler | HueV2OptionsFlowHandler: """Get the options flow for this handler.""" if config_entry.data.get(CONF_API_VERSION, 1) == 1: @@ -62,7 +66,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # This is for backwards compatibility. return await self.async_step_init(user_input) @@ -90,7 +94,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" # Check if user chooses manual entry if user_input is not None and user_input["id"] == HUE_MANUAL_BRIDGE_ID: @@ -141,7 +145,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_manual( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle manual bridge setup.""" if user_input is None: return self.async_show_form( @@ -157,7 +161,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to link with the Hue bridge. Given a configured host, will ask the user to press the link button @@ -210,7 +214,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered Hue bridge. This flow is triggered by the Zeroconf component. It will check if the @@ -239,7 +243,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered Hue bridge on HomeKit. The bridge ID communicated over HomeKit differs, so we cannot use that @@ -253,7 +257,7 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await self._async_handle_discovery_without_unique_id() return await self.async_step_link() - async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult: """Import a new bridge as a config entry. This flow is triggered by `async_setup` for both configured and @@ -272,16 +276,16 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_link() -class HueV1OptionsFlowHandler(config_entries.OptionsFlow): +class HueV1OptionsFlowHandler(OptionsFlow): """Handle Hue options for V1 implementation.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Hue options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Hue options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -307,16 +311,16 @@ class HueV1OptionsFlowHandler(config_entries.OptionsFlow): ) -class HueV2OptionsFlowHandler(config_entries.OptionsFlow): +class HueV2OptionsFlowHandler(OptionsFlow): """Handle Hue options for V2 implementation.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Hue options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Hue options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/huisbaasje/config_flow.py b/homeassistant/components/huisbaasje/config_flow.py index fc3a1c06a15..4c94f25914a 100644 --- a/homeassistant/components/huisbaasje/config_flow.py +++ b/homeassistant/components/huisbaasje/config_flow.py @@ -4,7 +4,7 @@ import logging from energyflip import EnergyFlip, EnergyFlipConnectionException, EnergyFlipException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import AbortFlow @@ -17,7 +17,7 @@ DATA_SCHEMA = vol.Schema( ) -class HuisbaasjeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HuisbaasjeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Huisbaasje.""" VERSION = 1 diff --git a/homeassistant/components/hunterdouglas_powerview/config_flow.py b/homeassistant/components/hunterdouglas_powerview/config_flow.py index 97e04b7d522..cee5d65fd0a 100644 --- a/homeassistant/components/hunterdouglas_powerview/config_flow.py +++ b/homeassistant/components/hunterdouglas_powerview/config_flow.py @@ -9,10 +9,11 @@ from aiopvapi.helpers.aiorequest import AioRequest from aiopvapi.hub import Hub import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import dhcp, zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_VERSION, CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import async_get_device_info @@ -25,7 +26,7 @@ POWERVIEW_G2_SUFFIX = "._powerview._tcp.local." POWERVIEW_G3_SUFFIX = "._powerview-g3._tcp.local." -async def validate_input(hass: core.HomeAssistant, hub_address: str) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, hub_address: str) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -59,7 +60,7 @@ async def validate_input(hass: core.HomeAssistant, hub_address: str) -> dict[str } -class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PowerviewConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Hunter Douglas PowerView.""" VERSION = 1 @@ -73,7 +74,7 @@ class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -120,7 +121,9 @@ class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return info, None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle DHCP discovery.""" self.discovered_ip = discovery_info.ip self.discovered_name = discovery_info.hostname @@ -128,7 +131,7 @@ class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self.discovered_ip = discovery_info.host name = discovery_info.name.removesuffix(POWERVIEW_G2_SUFFIX) @@ -138,14 +141,14 @@ class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle HomeKit discovery.""" self.discovered_ip = discovery_info.host name = discovery_info.name.removesuffix(HAP_SUFFIX) self.discovered_name = name return await self.async_step_discovery_confirm() - async def async_step_discovery_confirm(self) -> FlowResult: + async def async_step_discovery_confirm(self) -> ConfigFlowResult: """Confirm dhcp or homekit discovery.""" # If we already have the host configured do # not open connections to it if we can avoid it. @@ -177,7 +180,7 @@ class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to link with Powerview.""" if user_input is not None: return self.async_create_entry( @@ -195,9 +198,9 @@ class PowerviewConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class UnsupportedDevice(exceptions.HomeAssistantError): +class UnsupportedDevice(HomeAssistantError): """Error to indicate the device is not supported.""" diff --git a/homeassistant/components/husqvarna_automower/config_flow.py b/homeassistant/components/husqvarna_automower/config_flow.py index cafe942a894..11d3abb29a0 100644 --- a/homeassistant/components/husqvarna_automower/config_flow.py +++ b/homeassistant/components/husqvarna_automower/config_flow.py @@ -4,8 +4,8 @@ from typing import Any from aioautomower.utils import async_structure_token +from homeassistant.config_entries import ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN, NAME @@ -23,7 +23,7 @@ class HusqvarnaConfigFlowHandler( VERSION = 1 DOMAIN = DOMAIN - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow.""" token = data[CONF_TOKEN] user_id = token[CONF_USER_ID] diff --git a/homeassistant/components/huum/config_flow.py b/homeassistant/components/huum/config_flow.py index 31f4c9a137c..76a9f3563bb 100644 --- a/homeassistant/components/huum/config_flow.py +++ b/homeassistant/components/huum/config_flow.py @@ -10,7 +10,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -32,7 +31,7 @@ class HuumConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/hvv_departures/config_flow.py b/homeassistant/components/hvv_departures/config_flow.py index 24fb9c32a7d..e0ab65ba1c4 100644 --- a/homeassistant/components/hvv_departures/config_flow.py +++ b/homeassistant/components/hvv_departures/config_flow.py @@ -8,7 +8,7 @@ from pygti.auth import GTI_DEFAULT_HOST from pygti.exceptions import CannotConnect, InvalidAuth import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_HOST, CONF_OFFSET, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -38,7 +38,7 @@ SCHEMA_STEP_OPTIONS = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HVVDeparturesConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for HVV.""" VERSION = 1 @@ -125,16 +125,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get options flow.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Options flow handler.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize HVV Departures options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) diff --git a/homeassistant/components/hydrawise/config_flow.py b/homeassistant/components/hydrawise/config_flow.py index 72df86606d7..7087c4d2c34 100644 --- a/homeassistant/components/hydrawise/config_flow.py +++ b/homeassistant/components/hydrawise/config_flow.py @@ -9,23 +9,23 @@ from aiohttp import ClientError from pydrawise import legacy import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import AbortFlow, FlowResult, FlowResultType +from homeassistant.data_entry_flow import AbortFlow, FlowResultType from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import DOMAIN, LOGGER -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class HydrawiseConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Hydrawise.""" VERSION = 1 async def _create_entry( - self, api_key: str, *, on_failure: Callable[[str], FlowResult] - ) -> FlowResult: + self, api_key: str, *, on_failure: Callable[[str], ConfigFlowResult] + ) -> ConfigFlowResult: """Create the config entry.""" api = legacy.LegacyHydrawiseAsync(api_key) try: @@ -42,7 +42,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title="Hydrawise", data={CONF_API_KEY: api_key}) - def _import_issue(self, error_type: str) -> FlowResult: + def _import_issue(self, error_type: str) -> ConfigFlowResult: """Create an issue about a YAML import failure.""" async_create_issue( self.hass, @@ -75,14 +75,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial setup.""" if user_input is not None: api_key = user_input[CONF_API_KEY] return await self._create_entry(api_key, on_failure=self._show_form) return self._show_form() - def _show_form(self, error_type: str | None = None) -> FlowResult: + def _show_form(self, error_type: str | None = None) -> ConfigFlowResult: errors = {} if error_type is not None: errors["base"] = error_type @@ -92,7 +92,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, import_data: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult: """Import data from YAML.""" try: result = await self._create_entry( diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 52c4647f11c..3770402dbf0 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -16,6 +16,7 @@ from homeassistant.config_entries import ( SOURCE_REAUTH, ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlow, ) from homeassistant.const import ( @@ -27,7 +28,6 @@ from homeassistant.const import ( CONF_TOKEN, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from . import create_hyperion_client @@ -129,7 +129,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def _advance_to_auth_step_if_necessary( self, hyperion_client: client.HyperionClient - ) -> FlowResult: + ) -> ConfigFlowResult: """Determine if auth is required.""" auth_resp = await hyperion_client.async_is_auth_required() @@ -141,7 +141,9 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_auth() return await self.async_step_confirm() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthentication flow.""" self._data = dict(entry_data) async with self._create_client(raw_connection=True) as hyperion_client: @@ -149,7 +151,9 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="cannot_connect") return await self._advance_to_auth_step_if_necessary(hyperion_client) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initiated by SSDP.""" # Sample data provided by SSDP: { # 'ssdp_location': 'http://192.168.0.1:8090/description.xml', @@ -221,7 +225,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input: @@ -292,7 +296,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_auth( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the auth step of a flow.""" errors = {} if user_input: @@ -321,7 +325,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_create_token( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Send a request for a new token.""" if user_input is None: self._auth_id = client.generate_random_auth_id() @@ -347,7 +351,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_create_token_external( self, auth_resp: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle completion of the request for a new token.""" if auth_resp is not None and client.ResponseOK(auth_resp): token = auth_resp.get(const.KEY_INFO, {}).get(const.KEY_TOKEN) @@ -360,7 +364,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_create_token_success( self, _: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create an entry after successful token creation.""" # Clean-up the request task. await self._cancel_request_token_task() @@ -376,7 +380,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_create_token_fail( self, _: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show an error on the auth form.""" # Clean-up the request task. await self._cancel_request_token_task() @@ -384,7 +388,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Get final confirmation before entry creation.""" if user_input is None and self._require_confirm: return self.async_show_form( @@ -444,7 +448,7 @@ class HyperionOptionsFlow(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" effects = {} From 9ec9ac4fd4e388dd003de04975e2e86133243a8c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 20:08:16 +0100 Subject: [PATCH 0061/1691] Migrate integrations u-z to generic flowhandler (#111866) --- .../components/ukraine_alarm/config_flow.py | 4 +- homeassistant/components/unifi/config_flow.py | 44 +++++----- .../components/unifiprotect/config_flow.py | 56 ++++++++----- homeassistant/components/upb/config_flow.py | 9 ++- .../components/upcloud/config_flow.py | 22 ++--- homeassistant/components/upnp/config_flow.py | 19 ++--- .../components/uptime/config_flow.py | 5 +- .../components/uptimerobot/config_flow.py | 13 +-- homeassistant/components/v2c/config_flow.py | 7 +- .../components/vallox/config_flow.py | 7 +- .../components/velbus/config_flow.py | 15 ++-- homeassistant/components/velux/config_flow.py | 7 +- .../components/venstar/config_flow.py | 7 +- homeassistant/components/vera/config_flow.py | 27 ++++--- .../components/verisure/config_flow.py | 24 +++--- .../components/version/config_flow.py | 9 +-- .../components/vesync/config_flow.py | 4 +- .../components/vicare/config_flow.py | 19 +++-- homeassistant/components/vilfo/config_flow.py | 14 ++-- homeassistant/components/vizio/config_flow.py | 29 ++++--- .../components/vlc_telnet/config_flow.py | 28 ++++--- .../vodafone_station/config_flow.py | 12 +-- homeassistant/components/voip/config_flow.py | 22 ++--- .../components/volumio/config_flow.py | 10 +-- .../components/volvooncall/config_flow.py | 13 +-- .../components/vulcan/config_flow.py | 9 ++- .../components/wallbox/config_flow.py | 18 ++--- homeassistant/components/waqi/config_flow.py | 14 ++-- .../components/watttime/config_flow.py | 29 ++++--- .../waze_travel_time/config_flow.py | 20 +++-- .../components/weatherflow/config_flow.py | 7 +- .../weatherflow_cloud/config_flow.py | 7 +- .../components/weatherkit/config_flow.py | 6 +- .../components/webostv/config_flow.py | 25 ++++-- homeassistant/components/wemo/config_flow.py | 5 +- .../components/whirlpool/config_flow.py | 27 ++++--- homeassistant/components/whois/config_flow.py | 5 +- homeassistant/components/wiffi/config_flow.py | 10 +-- .../components/wilight/config_flow.py | 7 +- .../components/withings/config_flow.py | 11 +-- homeassistant/components/wiz/config_flow.py | 18 +++-- homeassistant/components/wled/config_flow.py | 16 ++-- .../components/wolflink/config_flow.py | 4 +- .../components/workday/config_flow.py | 9 ++- homeassistant/components/ws66i/config_flow.py | 22 ++--- .../components/wyoming/config_flow.py | 15 ++-- .../components/xiaomi_aqara/config_flow.py | 7 +- .../components/xiaomi_ble/config_flow.py | 23 +++--- .../components/xiaomi_miio/config_flow.py | 36 +++++---- .../yale_smart_alarm/config_flow.py | 18 +++-- .../components/yalexs_ble/config_flow.py | 39 +++++---- .../yamaha_musiccast/config_flow.py | 10 ++- .../components/yardian/config_flow.py | 7 +- .../components/yeelight/config_flow.py | 29 ++++--- .../components/yolink/config_flow.py | 13 +-- .../components/youless/config_flow.py | 5 +- .../components/youtube/config_flow.py | 19 +++-- homeassistant/components/zamg/config_flow.py | 7 +- .../components/zeversolar/config_flow.py | 7 +- homeassistant/components/zha/config_flow.py | 81 +++++++++++-------- .../components/zodiac/config_flow.py | 5 +- homeassistant/components/zone/config_flow.py | 4 +- .../components/zwave_me/config_flow.py | 9 +-- tests/components/wallbox/test_config_flow.py | 2 +- 64 files changed, 577 insertions(+), 454 deletions(-) diff --git a/homeassistant/components/ukraine_alarm/config_flow.py b/homeassistant/components/ukraine_alarm/config_flow.py index db17b55b2e9..a9d50443092 100644 --- a/homeassistant/components/ukraine_alarm/config_flow.py +++ b/homeassistant/components/ukraine_alarm/config_flow.py @@ -7,7 +7,7 @@ import aiohttp from uasiren.client import Client import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_NAME, CONF_REGION from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -16,7 +16,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class UkraineAlarmConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class UkraineAlarmConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for Ukraine Alarm.""" VERSION = 1 diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index fabdc9849fa..0342707c657 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -17,8 +17,13 @@ from urllib.parse import urlparse from aiounifi.interfaces.sites import Sites import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -27,7 +32,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -61,7 +65,7 @@ MODEL_PORTS = { } -class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): +class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN): """Handle a UniFi Network config flow.""" VERSION = 1 @@ -71,7 +75,7 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> UnifiOptionsFlowHandler: """Get the options flow for this handler.""" return UnifiOptionsFlowHandler(config_entry) @@ -79,12 +83,12 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): def __init__(self) -> None: """Initialize the UniFi Network flow.""" self.config: dict[str, Any] = {} - self.reauth_config_entry: config_entries.ConfigEntry | None = None + self.reauth_config_entry: ConfigEntry | None = None self.reauth_schema: dict[vol.Marker, Any] = {} async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -144,7 +148,7 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): async def async_step_site( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select site to control.""" if user_input is not None: unique_id = user_input[CONF_SITE_ID] @@ -181,7 +185,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): data_schema=vol.Schema({vol.Required(CONF_SITE_ID): vol.In(site_names)}), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Trigger a reauthentication flow.""" config_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -206,7 +212,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): return await self.async_step_user() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered UniFi device.""" parsed_url = urlparse(discovery_info.ssdp_location) model_description = discovery_info.upnp[ssdp.ATTR_UPNP_MODEL_DESCRIPTION] @@ -235,19 +243,19 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): return await self.async_step_user() -class UnifiOptionsFlowHandler(config_entries.OptionsFlow): +class UnifiOptionsFlowHandler(OptionsFlow): """Handle Unifi Network options.""" hub: UnifiHub - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize UniFi Network options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the UniFi Network options.""" if self.config_entry.entry_id not in self.hass.data[UNIFI_DOMAIN]: return self.async_abort(reason="integration_not_setup") @@ -261,7 +269,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_simple_options( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """For users without advanced settings enabled.""" if user_input is not None: self.options.update(user_input) @@ -296,7 +304,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_configure_entity_sources( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select sources for entities.""" if user_input is not None: self.options.update(user_input) @@ -329,7 +337,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_device_tracker( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the device tracker options.""" if user_input is not None: self.options.update(user_input) @@ -390,7 +398,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_client_control( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage configuration of network access controlled clients.""" if user_input is not None: self.options.update(user_input) @@ -429,7 +437,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_statistics_sensors( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the statistics sensors options.""" if user_input is not None: self.options.update(user_input) @@ -452,7 +460,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): last_step=True, ) - async def _update_options(self) -> FlowResult: + async def _update_options(self) -> ConfigFlowResult: """Update config entry options.""" return self.async_create_entry(title="", data=self.options) diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 29718c8ef35..55c93be58b6 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -13,8 +13,15 @@ from pyunifiprotect.exceptions import ClientError, NotAuthorized from unifi_discovery import async_console_is_alive import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp, ssdp +from homeassistant.config_entries import ( + SOURCE_IGNORE, + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_ID, @@ -24,7 +31,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import ( async_create_clientsession, async_get_clientsession, @@ -54,8 +60,8 @@ from .utils import _async_resolve, _async_short_mac, _async_unifi_mac_from_hass _LOGGER = logging.getLogger(__name__) ENTRY_FAILURE_STATES = ( - config_entries.ConfigEntryState.SETUP_ERROR, - config_entries.ConfigEntryState.SETUP_RETRY, + ConfigEntryState.SETUP_ERROR, + ConfigEntryState.SETUP_RETRY, ) @@ -72,7 +78,7 @@ def _host_is_direct_connect(host: str) -> bool: async def _async_console_is_offline( hass: HomeAssistant, - entry: config_entries.ConfigEntry, + entry: ConfigEntry, ) -> bool: """Check if a console is offline. @@ -89,7 +95,7 @@ async def _async_console_is_offline( ) -class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class ProtectFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a UniFi Protect config flow.""" VERSION = 2 @@ -97,20 +103,24 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Init the config flow.""" super().__init__() - self.entry: config_entries.ConfigEntry | None = None + self.entry: ConfigEntry | None = None self._discovered_device: dict[str, str] = {} - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" _LOGGER.debug("Starting discovery via: %s", discovery_info) return await self._async_discovery_handoff() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered UniFi device.""" _LOGGER.debug("Starting discovery via: %s", discovery_info) return await self._async_discovery_handoff() - async def _async_discovery_handoff(self) -> FlowResult: + async def _async_discovery_handoff(self) -> ConfigFlowResult: """Ensure discovery is active.""" # Discovery requires an additional check so we use # SSDP and DHCP to tell us to start it so it only @@ -120,7 +130,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle integration discovery.""" self._discovered_device = discovery_info mac = _async_unifi_mac_from_hass(discovery_info["hw_addr"]) @@ -128,7 +138,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): source_ip = discovery_info["source_ip"] direct_connect_domain = discovery_info["direct_connect_domain"] for entry in self._async_current_entries(): - if entry.source == config_entries.SOURCE_IGNORE: + if entry.source == SOURCE_IGNORE: if entry.unique_id == mac: return self.async_abort(reason="already_configured") continue @@ -164,7 +174,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" errors: dict[str, str] = {} discovery_info = self._discovered_device @@ -212,13 +222,13 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) @callback - def _async_create_entry(self, title: str, data: dict[str, Any]) -> FlowResult: + def _async_create_entry(self, title: str, data: dict[str, Any]) -> ConfigFlowResult: return self.async_create_entry( title=title, data={**data, CONF_ID: title}, @@ -279,7 +289,9 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return nvr_data, errors - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -287,7 +299,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth.""" errors: dict[str, str] = {} assert self.entry is not None @@ -321,7 +333,7 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors: dict[str, str] = {} @@ -362,16 +374,16 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index 6d85febed9f..8d47ce3d64a 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -7,8 +7,9 @@ from urllib.parse import urlparse import upb_lib import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ADDRESS, CONF_FILE_PATH, CONF_HOST, CONF_PROTOCOL +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -70,7 +71,7 @@ def _make_url_from_data(data): return f"{protocol}{address}" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class UPBConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for UPB PIM.""" VERSION = 1 @@ -128,9 +129,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return urlparse(url).hostname in existing_hosts -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidUpbFile(exceptions.HomeAssistantError): +class InvalidUpbFile(HomeAssistantError): """Error to indicate there is invalid or missing UPB config file.""" diff --git a/homeassistant/components/upcloud/config_flow.py b/homeassistant/components/upcloud/config_flow.py index fda6c1d561b..20860df5553 100644 --- a/homeassistant/components/upcloud/config_flow.py +++ b/homeassistant/components/upcloud/config_flow.py @@ -9,17 +9,21 @@ import requests.exceptions import upcloud_api import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_SCAN_INTERVAL, DOMAIN _LOGGER = logging.getLogger(__name__) -class UpCloudConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class UpCloudConfigFlow(ConfigFlow, domain=DOMAIN): """UpCloud config flow.""" VERSION = 1 @@ -29,7 +33,7 @@ class UpCloudConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user initiated flow.""" if user_input is None: return self._async_show_form(step_id="user") @@ -66,7 +70,7 @@ class UpCloudConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id: str, user_input: dict[str, Any] | None = None, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show our form.""" if user_input is None: user_input = {} @@ -88,22 +92,22 @@ class UpCloudConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> UpCloudOptionsFlow: """Get options flow.""" return UpCloudOptionsFlow(config_entry) -class UpCloudOptionsFlow(config_entries.OptionsFlow): +class UpCloudOptionsFlow(OptionsFlow): """UpCloud options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index fa33d4b29d3..939be005f64 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -7,11 +7,10 @@ from urllib.parse import urlparse import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.ssdp import SsdpServiceInfo +from homeassistant.config_entries import SOURCE_IGNORE, ConfigFlow, ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from .const import ( CONFIG_ENTRY_HOST, @@ -74,7 +73,7 @@ def _is_igd_device(discovery_info: ssdp.SsdpServiceInfo) -> bool: return root_device_info.get(ssdp.ATTR_UPNP_DEVICE_TYPE) in {ST_IGD_V1, ST_IGD_V2} -class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class UpnpFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a UPnP/IGD config flow.""" VERSION = 1 @@ -100,7 +99,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" LOGGER.debug("async_step_user: user_input: %s", user_input) @@ -151,7 +150,9 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema=data_schema, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered UPnP/IGD device. This flow is triggered by the SSDP component. It will check if the @@ -201,7 +202,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Check ssdp_st to prevent swapping between IGDv1 and IGDv2. continue - if entry.source == config_entries.SOURCE_IGNORE: + if entry.source == SOURCE_IGNORE: # Host was already ignored. Don't update ignored entries. return self.async_abort(reason="discovery_ignored") @@ -225,7 +226,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_ssdp_confirm( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm integration via SSDP.""" LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) if user_input is None: @@ -235,7 +236,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): discovery = self._remove_discovery(self.unique_id) return await self._async_create_entry_from_discovery(discovery) - async def async_step_ignore(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_ignore(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Ignore this config flow.""" usn = user_input["unique_id"] discovery = self._remove_discovery(usn) @@ -255,7 +256,7 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _async_create_entry_from_discovery( self, discovery: SsdpServiceInfo, - ) -> FlowResult: + ) -> ConfigFlowResult: """Create an entry from discovery.""" LOGGER.debug( "_async_create_entry_from_discovery: discovery: %s", diff --git a/homeassistant/components/uptime/config_flow.py b/homeassistant/components/uptime/config_flow.py index edbe6d86f38..d5215cc6c06 100644 --- a/homeassistant/components/uptime/config_flow.py +++ b/homeassistant/components/uptime/config_flow.py @@ -5,8 +5,7 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -18,7 +17,7 @@ class UptimeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/uptimerobot/config_flow.py b/homeassistant/components/uptimerobot/config_flow.py index 14ec1ae6cdc..1303f2cc6e3 100644 --- a/homeassistant/components/uptimerobot/config_flow.py +++ b/homeassistant/components/uptimerobot/config_flow.py @@ -14,9 +14,8 @@ from pyuptimerobot import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import API_ATTR_OK, DOMAIN, LOGGER @@ -24,7 +23,7 @@ from .const import API_ATTR_OK, DOMAIN, LOGGER STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str}) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for UptimeRobot.""" VERSION = 1 @@ -68,7 +67,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -85,13 +84,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Return the reauth confirm step.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/v2c/config_flow.py b/homeassistant/components/v2c/config_flow.py index 382b41d3994..4bb36f3191a 100644 --- a/homeassistant/components/v2c/config_flow.py +++ b/homeassistant/components/v2c/config_flow.py @@ -8,9 +8,8 @@ from pytrydan import Trydan from pytrydan.exceptions import TrydanError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.httpx_client import get_async_client from .const import DOMAIN @@ -24,14 +23,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class V2CConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for V2C.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/vallox/config_flow.py b/homeassistant/components/vallox/config_flow.py index 6c6e3630023..d3fbaf8b7f2 100644 --- a/homeassistant/components/vallox/config_flow.py +++ b/homeassistant/components/vallox/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from vallox_websocket_api import Vallox, ValloxApiException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.util.network import is_ip_address @@ -35,14 +34,14 @@ async def validate_host(hass: HomeAssistant, host: str) -> None: await client.fetch_metric_data() -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ValloxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for the Vallox integration.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 1888a177895..306e69c24ed 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -7,16 +7,15 @@ import velbusaio.controller from velbusaio.exceptions import VelbusConnectionFailed import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import usb +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.util import slugify from .const import DOMAIN -class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VelbusConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 2 @@ -27,7 +26,7 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._device: str = "" self._title: str = "" - def _create_device(self, name: str, prt: str) -> FlowResult: + def _create_device(self, name: str, prt: str) -> ConfigFlowResult: """Create an entry async.""" return self.async_create_entry(title=name, data={CONF_PORT: prt}) @@ -44,7 +43,7 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" self._errors = {} if user_input is not None: @@ -69,7 +68,9 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=self._errors, ) - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle USB Discovery.""" await self.async_set_unique_id( f"{discovery_info.vid}:{discovery_info.pid}_{discovery_info.serial_number}_{discovery_info.manufacturer}_{discovery_info.description}" @@ -89,7 +90,7 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Discovery confirmation.""" if user_input is not None: return self._create_device(self._title, self._device) diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 57791ea01dd..882a6673d2e 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -7,7 +7,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PASSWORD from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -24,7 +23,9 @@ DATA_SCHEMA = vol.Schema( class VeluxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for velux.""" - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, config: dict[str, Any] + ) -> config_entries.ConfigFlowResult: """Import a config entry.""" def create_repair(error: str | None = None) -> None: @@ -79,7 +80,7 @@ class VeluxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/venstar/config_flow.py b/homeassistant/components/venstar/config_flow.py index 66ce22cb00b..a88c048a611 100644 --- a/homeassistant/components/venstar/config_flow.py +++ b/homeassistant/components/venstar/config_flow.py @@ -4,7 +4,7 @@ from typing import Any from venstarcolortouch import VenstarColorTouch import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -13,7 +13,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType @@ -54,7 +53,7 @@ class VenstarConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create config entry. Show the setup form to the user.""" errors = {} @@ -85,7 +84,7 @@ class VenstarConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, import_data: ConfigType) -> FlowResult: + async def async_step_import(self, import_data: ConfigType) -> ConfigFlowResult: """Import entry from configuration.yaml.""" self._async_abort_entries_match({CONF_HOST: import_data[CONF_HOST]}) return await self.async_step_user( diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 00b45e00b11..5106b50bf67 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -10,11 +10,16 @@ import pyvera as pv from requests.exceptions import RequestException import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + SOURCE_IMPORT, + SOURCE_USER, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import entity_registry as er from .const import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN @@ -68,7 +73,7 @@ def options_data(user_input: dict[str, str]) -> dict[str, list[int]]: ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Options for the component.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -78,7 +83,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry( @@ -92,7 +97,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class VeraFlowHandler(ConfigFlow, domain=DOMAIN): """Vera config flow.""" @staticmethod @@ -103,14 +108,14 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user initiated flow.""" if user_input is not None: return await self.async_step_finish( { **user_input, **options_data(user_input), - **{CONF_SOURCE: config_entries.SOURCE_USER}, + **{CONF_SOURCE: SOURCE_USER}, **{CONF_LEGACY_UNIQUE_ID: False}, } ) @@ -122,7 +127,7 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ), ) - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + async def async_step_import(self, config: dict[str, Any]) -> ConfigFlowResult: """Handle a flow initialized by import.""" # If there are entities with the legacy unique_id, then this imported config @@ -142,12 +147,12 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_finish( { **config, - **{CONF_SOURCE: config_entries.SOURCE_IMPORT}, + **{CONF_SOURCE: SOURCE_IMPORT}, **{CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id}, } ) - async def async_step_finish(self, config: dict[str, Any]) -> FlowResult: + async def async_step_finish(self, config: dict[str, Any]) -> ConfigFlowResult: """Validate and create config entry.""" base_url = config[CONF_CONTROLLER] = config[CONF_CONTROLLER].rstrip("/") controller = pv.VeraController(base_url) diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py index d945463fa5e..a7ba3bc6822 100644 --- a/homeassistant/components/verisure/config_flow.py +++ b/homeassistant/components/verisure/config_flow.py @@ -12,10 +12,14 @@ from verisure import ( ) import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_CODE, CONF_EMAIL, CONF_PASSWORD from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.storage import STORAGE_DIR from .const import ( @@ -45,7 +49,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -102,7 +106,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_mfa( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle multifactor authentication step.""" errors: dict[str, str] = {} @@ -134,7 +138,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_installation( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select Verisure installation to add.""" installations_data = await self.hass.async_add_executor_job( self.verisure.get_installations @@ -170,7 +174,9 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with Verisure.""" self.entry = cast( ConfigEntry, @@ -180,7 +186,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with Verisure.""" errors: dict[str, str] = {} @@ -250,7 +256,7 @@ class VerisureConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_mfa( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle multifactor authentication step during re-authentication.""" errors: dict[str, str] = {} @@ -303,7 +309,7 @@ class VerisureOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Verisure options.""" errors: dict[str, Any] = {} diff --git a/homeassistant/components/version/config_flow.py b/homeassistant/components/version/config_flow.py index 2fd670a7342..d08d741a241 100644 --- a/homeassistant/components/version/config_flow.py +++ b/homeassistant/components/version/config_flow.py @@ -5,9 +5,8 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_SOURCE -from homeassistant.data_entry_flow import FlowResult from .const import ( ATTR_VERSION_SOURCE, @@ -33,7 +32,7 @@ from .const import ( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VersionConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Version.""" VERSION = 1 @@ -45,7 +44,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial user step.""" if user_input is None: self._entry_data = DEFAULT_CONFIGURATION.copy() @@ -78,7 +77,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_version_source( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the version_source step.""" if user_input is None: if self._entry_data[CONF_SOURCE] in ( diff --git a/homeassistant/components/vesync/config_flow.py b/homeassistant/components/vesync/config_flow.py index 3f469d5eb81..5e71c434449 100644 --- a/homeassistant/components/vesync/config_flow.py +++ b/homeassistant/components/vesync/config_flow.py @@ -4,14 +4,14 @@ from collections import OrderedDict from pyvesync import VeSync import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from .const import DOMAIN -class VeSyncFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class VeSyncFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 diff --git a/homeassistant/components/vicare/config_flow.py b/homeassistant/components/vicare/config_flow.py index 32ae4af0fe7..6d9f72a426f 100644 --- a/homeassistant/components/vicare/config_flow.py +++ b/homeassistant/components/vicare/config_flow.py @@ -11,10 +11,9 @@ from PyViCare.PyViCareUtils import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -46,15 +45,15 @@ USER_SCHEMA = REAUTH_SCHEMA.extend( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ViCareConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for ViCare.""" VERSION = 1 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Invoke when a user initiates a flow via the user interface.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -77,14 +76,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with ViCare.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with ViCare.""" errors: dict[str, str] = {} assert self.entry is not None @@ -115,7 +116,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Invoke when a Viessmann MAC address is discovered on the network.""" formatted_mac = format_mac(discovery_info.macaddress) _LOGGER.debug("Found device with mac %s", formatted_mac) diff --git a/homeassistant/components/vilfo/config_flow.py b/homeassistant/components/vilfo/config_flow.py index f174a7697d3..4fa7fd4ae9b 100644 --- a/homeassistant/components/vilfo/config_flow.py +++ b/homeassistant/components/vilfo/config_flow.py @@ -8,8 +8,10 @@ from vilfo.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.util.network import is_host_valid from .const import DOMAIN, ROUTER_DEFAULT_HOST @@ -62,7 +64,7 @@ def _try_connect_and_fetch_basic_info(host, token): return result -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -91,7 +93,7 @@ async def validate_input(hass: core.HomeAssistant, data): return config -class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class DomainConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Vilfo Router.""" VERSION = 1 @@ -122,13 +124,13 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class InvalidHost(exceptions.HomeAssistantError): +class InvalidHost(HomeAssistantError): """Error to indicate that hostname/IP address is invalid.""" diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 792407d2545..b0936b0e81b 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -10,7 +10,6 @@ from pyvizio import VizioAsync, async_guess_device_type from pyvizio.const import APP_HOME import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.components.media_player import MediaPlayerDeviceClass from homeassistant.config_entries import ( @@ -18,6 +17,9 @@ from homeassistant.config_entries import ( SOURCE_IMPORT, SOURCE_ZEROCONF, ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, ) from homeassistant.const import ( CONF_ACCESS_TOKEN, @@ -29,7 +31,6 @@ from homeassistant.const import ( CONF_PIN, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util.network import is_ip_address @@ -103,7 +104,7 @@ def _host_is_same(host1: str, host2: str) -> bool: return host1 == host2 -class VizioOptionsConfigFlow(config_entries.OptionsFlow): +class VizioOptionsConfigFlow(OptionsFlow): """Handle Vizio options.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -112,7 +113,7 @@ class VizioOptionsConfigFlow(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the vizio options.""" if user_input is not None: if user_input.get(CONF_APPS_TO_INCLUDE_OR_EXCLUDE): @@ -173,7 +174,7 @@ class VizioOptionsConfigFlow(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=options) -class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VizioConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a Vizio config flow.""" VERSION = 1 @@ -193,7 +194,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._data: dict[str, Any] | None = None self._apps: dict[str, list] = {} - async def _create_entry(self, input_dict: dict[str, Any]) -> FlowResult: + async def _create_entry(self, input_dict: dict[str, Any]) -> ConfigFlowResult: """Create vizio config entry.""" # Remove extra keys that will not be used by entry setup input_dict.pop(CONF_APPS_TO_INCLUDE_OR_EXCLUDE, None) @@ -206,7 +207,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} @@ -283,7 +284,9 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import a config entry from configuration.yaml.""" # Check if new config entry matches any existing config entries for entry in self._async_current_entries(): @@ -347,7 +350,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" host = discovery_info.host # If host already has port, no need to add it again @@ -387,7 +390,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pair_tv( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start pairing process for TV. Ask user for PIN to complete pairing process. @@ -452,7 +455,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def _pairing_complete(self, step_id: str) -> FlowResult: + async def _pairing_complete(self, step_id: str) -> ConfigFlowResult: """Handle config flow completion.""" assert self._data if not self._must_show_form: @@ -466,7 +469,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pairing_complete( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Complete non-import sourced config flow. Display final message to user confirming pairing. @@ -475,7 +478,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pairing_complete_import( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Complete import sourced config flow. Display final message to user confirming pairing and displaying diff --git a/homeassistant/components/vlc_telnet/config_flow.py b/homeassistant/components/vlc_telnet/config_flow.py index 6995a16c3ab..48f8cc3f077 100644 --- a/homeassistant/components/vlc_telnet/config_flow.py +++ b/homeassistant/components/vlc_telnet/config_flow.py @@ -9,11 +9,11 @@ from aiovlc.client import Client from aiovlc.exceptions import AuthError, ConnectError import voluptuous as vol -from homeassistant import core, exceptions from homeassistant.components.hassio import HassioServiceInfo -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import DEFAULT_PORT, DOMAIN @@ -46,9 +46,7 @@ async def vlc_connect(vlc: Client) -> None: await vlc.disconnect() -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect.""" vlc = Client( password=data[CONF_PASSWORD], @@ -76,7 +74,7 @@ class VLCTelnetConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -105,7 +103,9 @@ class VLCTelnetConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=user_form_schema(user_input), errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth flow.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert self.entry @@ -114,7 +114,7 @@ class VLCTelnetConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth confirm.""" assert self.entry errors = {} @@ -149,7 +149,9 @@ class VLCTelnetConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Handle the discovery step via hassio.""" await self.async_set_unique_id("hassio") self._abort_if_unique_id_configured(discovery_info.config) @@ -160,7 +162,7 @@ class VLCTelnetConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm Supervisor discovery.""" assert self.hassio_discovery if user_input is None: @@ -184,9 +186,9 @@ class VLCTelnetConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=info["title"], data=self.hassio_discovery) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/vodafone_station/config_flow.py b/homeassistant/components/vodafone_station/config_flow.py index 987d4d71f41..b8b08463999 100644 --- a/homeassistant/components/vodafone_station/config_flow.py +++ b/homeassistant/components/vodafone_station/config_flow.py @@ -14,12 +14,12 @@ from homeassistant.components.device_tracker import ( from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlow, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from .const import _LOGGER, DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN @@ -69,7 +69,7 @@ class VodafoneStationConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -101,7 +101,9 @@ class VodafoneStationConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=user_form_schema(user_input), errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth flow.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert self.entry @@ -110,7 +112,7 @@ class VodafoneStationConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth confirm.""" assert self.entry errors = {} @@ -153,7 +155,7 @@ class VodafoneStationOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: diff --git a/homeassistant/components/voip/config_flow.py b/homeassistant/components/voip/config_flow.py index 3af15bd2c0b..44709d2b674 100644 --- a/homeassistant/components/voip/config_flow.py +++ b/homeassistant/components/voip/config_flow.py @@ -6,22 +6,26 @@ from typing import Any from voip_utils import SIP_PORT import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import CONF_SIP_PORT, DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VoIPConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for VoIP integration.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -39,22 +43,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create the options flow.""" return VoipOptionsFlowHandler(config_entry) -class VoipOptionsFlowHandler(config_entries.OptionsFlow): +class VoipOptionsFlowHandler(OptionsFlow): """Handle VoIP options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/volumio/config_flow.py b/homeassistant/components/volumio/config_flow.py index f2b5c7e796c..9a19130113d 100644 --- a/homeassistant/components/volumio/config_flow.py +++ b/homeassistant/components/volumio/config_flow.py @@ -6,11 +6,11 @@ import logging from pyvolumio import CannotConnectError, Volumio import voluptuous as vol -from homeassistant import config_entries, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_ID, CONF_NAME, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -33,7 +33,7 @@ async def validate_input(hass, host, port): raise CannotConnect from error -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VolumioConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Volumio.""" VERSION = 1 @@ -96,7 +96,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self._host = discovery_info.host self._port = discovery_info.port @@ -121,5 +121,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/volvooncall/config_flow.py b/homeassistant/components/volvooncall/config_flow.py index d56d10ded5a..f9188ac79c1 100644 --- a/homeassistant/components/volvooncall/config_flow.py +++ b/homeassistant/components/volvooncall/config_flow.py @@ -8,14 +8,13 @@ from typing import Any import voluptuous as vol from volvooncall import Connection -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_PASSWORD, CONF_REGION, CONF_UNIT_SYSTEM, CONF_USERNAME, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import VolvoData @@ -31,15 +30,15 @@ from .errors import InvalidAuth _LOGGER = logging.getLogger(__name__) -class VolvoOnCallConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class VolvoOnCallConfigFlow(ConfigFlow, domain=DOMAIN): """VolvoOnCall config flow.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user step.""" errors = {} defaults = { @@ -106,7 +105,9 @@ class VolvoOnCallConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=user_schema, errors=errors ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/vulcan/config_flow.py b/homeassistant/components/vulcan/config_flow.py index 4547e6dd31e..ae4226162e5 100644 --- a/homeassistant/components/vulcan/config_flow.py +++ b/homeassistant/components/vulcan/config_flow.py @@ -16,9 +16,8 @@ from vulcan import ( Vulcan, ) -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PIN, CONF_REGION, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import DOMAIN @@ -33,7 +32,7 @@ LOGIN_SCHEMA = { } -class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class VulcanFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Uonet+ Vulcan config flow.""" VERSION = 1 @@ -241,7 +240,9 @@ class VulcanFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() diff --git a/homeassistant/components/wallbox/config_flow.py b/homeassistant/components/wallbox/config_flow.py index 0f3782958d3..17b3f02a61f 100644 --- a/homeassistant/components/wallbox/config_flow.py +++ b/homeassistant/components/wallbox/config_flow.py @@ -7,9 +7,9 @@ from typing import Any import voluptuous as vol from wallbox import Wallbox -from homeassistant import config_entries, core +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant from .const import CONF_STATION, DOMAIN from .coordinator import InvalidAuth, WallboxCoordinator @@ -25,9 +25,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. @@ -41,14 +39,16 @@ async def validate_input( return {"title": "Wallbox Portal"} -class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN): +class WallboxConfigFlow(ConfigFlow, domain=COMPONENT_DOMAIN): """Handle a config flow for Wallbox.""" def __init__(self) -> None: """Start the Wallbox config flow.""" - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -58,7 +58,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=COMPONENT_DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/waqi/config_flow.py b/homeassistant/components/waqi/config_flow.py index 55740913487..7a0d4eda546 100644 --- a/homeassistant/components/waqi/config_flow.py +++ b/homeassistant/components/waqi/config_flow.py @@ -12,7 +12,7 @@ from aiowaqi import ( ) import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, @@ -22,7 +22,7 @@ from homeassistant.const import ( CONF_NAME, ) from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.selector import ( @@ -66,7 +66,7 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -107,7 +107,7 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_map( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add measuring station via map.""" errors: dict[str, str] = {} if user_input is not None: @@ -149,7 +149,7 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_station_number( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add measuring station via station number.""" errors: dict[str, str] = {} if user_input is not None: @@ -182,7 +182,7 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN): async def _async_create_entry( self, measuring_station: WAQIAirQuality - ) -> FlowResult: + ) -> ConfigFlowResult: await self.async_set_unique_id(str(measuring_station.station_id)) self._abort_if_unique_id_configured() return self.async_create_entry( @@ -193,7 +193,7 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_import(self, import_config: ConfigType) -> FlowResult: + async def async_step_import(self, import_config: ConfigType) -> ConfigFlowResult: """Handle importing from yaml.""" await self.async_set_unique_id(str(import_config[CONF_STATION_NUMBER])) try: diff --git a/homeassistant/components/watttime/config_flow.py b/homeassistant/components/watttime/config_flow.py index 12601c0af83..776fd130824 100644 --- a/homeassistant/components/watttime/config_flow.py +++ b/homeassistant/components/watttime/config_flow.py @@ -8,8 +8,12 @@ from aiowatttime import Client from aiowatttime.errors import CoordinatesNotFoundError, InvalidCredentialsError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, @@ -18,7 +22,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import ( @@ -68,7 +71,7 @@ def get_unique_id(data: dict[str, Any]) -> str: return f"{data[CONF_LATITUDE]}, {data[CONF_LONGITUDE]}" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WattTimeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for WattTime.""" VERSION = 1 @@ -80,7 +83,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_validate_credentials( self, username: str, password: str, error_step_id: str, error_schema: vol.Schema - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate input credentials and proceed accordingly.""" session = aiohttp_client.async_get_clientsession(self.hass) @@ -128,7 +131,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_coordinates( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the coordinates step.""" if not user_input: return self.async_show_form( @@ -174,7 +177,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_location( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the "pick a location" step.""" if not user_input: return self.async_show_form( @@ -190,14 +193,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self.async_step_coordinates() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._data = {**entry_data} return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" if not user_input: return self.async_show_form( @@ -217,7 +222,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if not user_input: return self.async_show_form( @@ -232,7 +237,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class WattTimeOptionsFlowHandler(config_entries.OptionsFlow): +class WattTimeOptionsFlowHandler(OptionsFlow): """Handle a WattTime options flow.""" def __init__(self, entry: ConfigEntry) -> None: @@ -241,7 +246,7 @@ class WattTimeOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(data=user_input) diff --git a/homeassistant/components/waze_travel_time/config_flow.py b/homeassistant/components/waze_travel_time/config_flow.py index 60134452025..2b863d74f7c 100644 --- a/homeassistant/components/waze_travel_time/config_flow.py +++ b/homeassistant/components/waze_travel_time/config_flow.py @@ -3,10 +3,14 @@ from __future__ import annotations import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_NAME, CONF_REGION from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( BooleanSelector, SelectSelector, @@ -86,14 +90,14 @@ def default_options(hass: HomeAssistant) -> dict[str, str | bool]: return defaults -class WazeOptionsFlow(config_entries.OptionsFlow): +class WazeOptionsFlow(OptionsFlow): """Handle an options flow for Waze Travel Time.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize waze options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: return self.async_create_entry( @@ -109,7 +113,7 @@ class WazeOptionsFlow(config_entries.OptionsFlow): ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WazeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Waze Travel Time.""" VERSION = 1 @@ -117,12 +121,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> WazeOptionsFlow: """Get the options flow for this handler.""" return WazeOptionsFlow(config_entry) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle the initial step.""" errors = {} user_input = user_input or {} diff --git a/homeassistant/components/weatherflow/config_flow.py b/homeassistant/components/weatherflow/config_flow.py index d4ee319e70b..e96972437fc 100644 --- a/homeassistant/components/weatherflow/config_flow.py +++ b/homeassistant/components/weatherflow/config_flow.py @@ -9,9 +9,8 @@ from typing import Any from pyweatherflowudp.client import EVENT_DEVICE_DISCOVERED, WeatherFlowListener from pyweatherflowudp.errors import AddressInUseError, EndpointError, ListenerError -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( DOMAIN, @@ -42,14 +41,14 @@ async def _async_can_discover_devices() -> bool: return True -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WeatherFlowConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for WeatherFlow.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # Only allow a single instance of integration since the listener diff --git a/homeassistant/components/weatherflow_cloud/config_flow.py b/homeassistant/components/weatherflow_cloud/config_flow.py index 85c1acbb807..889b7e81d52 100644 --- a/homeassistant/components/weatherflow_cloud/config_flow.py +++ b/homeassistant/components/weatherflow_cloud/config_flow.py @@ -10,7 +10,6 @@ from weatherflow4py.api import WeatherFlowRestAPI from homeassistant import config_entries from homeassistant.const import CONF_API_TOKEN -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -32,7 +31,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> config_entries.ConfigFlowResult: """Handle a flow for reauth.""" errors = {} @@ -58,7 +59,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/weatherkit/config_flow.py b/homeassistant/components/weatherkit/config_flow.py index 657a80547ab..1fbb0c1a36f 100644 --- a/homeassistant/components/weatherkit/config_flow.py +++ b/homeassistant/components/weatherkit/config_flow.py @@ -12,7 +12,7 @@ from apple_weatherkit.client import ( ) import voluptuous as vol -from homeassistant import config_entries, data_entry_flow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( @@ -53,7 +53,7 @@ class WeatherKitUnsupportedLocationError(Exception): """Error to indicate a location is unsupported.""" -class WeatherKitFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class WeatherKitFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for WeatherKit.""" VERSION = 1 @@ -61,7 +61,7 @@ class WeatherKitFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 1669e5a4c89..52c17a8b411 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -10,10 +10,15 @@ from aiowebostv import WebOsTvPairError import voluptuous as vol from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import config_validation as cv from . import async_control_connect, update_client_key @@ -51,7 +56,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} if user_input is not None: @@ -82,7 +87,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_pairing( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Display pairing form.""" self._async_check_configured_entry() @@ -107,7 +112,9 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="pairing", errors=errors) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" assert discovery_info.ssdp_location host = urlparse(discovery_info.ssdp_location).hostname @@ -129,7 +136,9 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): self._uuid = uuid return await self.async_step_pairing() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an WebOsTvPairError.""" self._host = entry_data[CONF_HOST] self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -137,7 +146,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" assert self._entry is not None @@ -168,7 +177,7 @@ class OptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/wemo/config_flow.py b/homeassistant/components/wemo/config_flow.py index e4626b4deaf..97a9eb34057 100644 --- a/homeassistant/components/wemo/config_flow.py +++ b/homeassistant/components/wemo/config_flow.py @@ -8,9 +8,8 @@ from typing import Any, get_type_hints import pywemo import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, OptionsFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult, OptionsFlow from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.config_entry_flow import DiscoveryFlowHandler from .const import DOMAIN @@ -45,7 +44,7 @@ class WemoOptionsFlow(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage options for the WeMo component.""" errors: dict[str, str] | None = None if user_input is not None: diff --git a/homeassistant/components/whirlpool/config_flow.py b/homeassistant/components/whirlpool/config_flow.py index dbd3f9b6fd4..aa6d7875865 100644 --- a/homeassistant/components/whirlpool/config_flow.py +++ b/homeassistant/components/whirlpool/config_flow.py @@ -11,9 +11,10 @@ from whirlpool.appliancesmanager import AppliancesManager from whirlpool.auth import Auth from whirlpool.backendselector import BackendSelector -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_REGIONS_MAP, DOMAIN @@ -33,9 +34,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, str] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. @@ -61,13 +60,15 @@ async def validate_input( return {"title": data[CONF_USERNAME]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WhirlpoolConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Whirlpool Sixth Sense.""" VERSION = 1 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Whirlpool Sixth Sense.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -75,7 +76,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Whirlpool Sixth Sense.""" errors: dict[str, str] = {} @@ -110,7 +111,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -142,13 +143,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class NoAppliances(exceptions.HomeAssistantError): +class NoAppliances(HomeAssistantError): """Error to indicate no supported appliances in the user account.""" diff --git a/homeassistant/components/whois/config_flow.py b/homeassistant/components/whois/config_flow.py index 640daaa314c..1a35ca9749e 100644 --- a/homeassistant/components/whois/config_flow.py +++ b/homeassistant/components/whois/config_flow.py @@ -12,9 +12,8 @@ from whois.exceptions import ( WhoisCommandFailed, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DOMAIN -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -28,7 +27,7 @@ class WhoisFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/wiffi/config_flow.py b/homeassistant/components/wiffi/config_flow.py index d6da03c2134..a186b7fe4da 100644 --- a/homeassistant/components/wiffi/config_flow.py +++ b/homeassistant/components/wiffi/config_flow.py @@ -9,14 +9,14 @@ import errno import voluptuous as vol from wiffi import WiffiTcpServer -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.core import callback from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN -class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class WiffiFlowHandler(ConfigFlow, domain=DOMAIN): """Wiffi server setup config flow.""" VERSION = 1 @@ -24,7 +24,7 @@ class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Create Wiffi server setup option flow.""" return OptionsFlowHandler(config_entry) @@ -67,10 +67,10 @@ class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Wiffi server setup option flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/wilight/config_flow.py b/homeassistant/components/wilight/config_flow.py index b7df1932cab..15c7a4d2c3b 100644 --- a/homeassistant/components/wilight/config_flow.py +++ b/homeassistant/components/wilight/config_flow.py @@ -4,9 +4,8 @@ from urllib.parse import urlparse import pywilight from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from . import DOMAIN @@ -50,7 +49,9 @@ class WiLightFlowHandler(ConfigFlow, domain=DOMAIN): } return self.async_create_entry(title=self._title, data=data) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered WiLight.""" # Filter out basic information if ( diff --git a/homeassistant/components/withings/config_flow.py b/homeassistant/components/withings/config_flow.py index 31c40bf9791..e7baf2714a2 100644 --- a/homeassistant/components/withings/config_flow.py +++ b/homeassistant/components/withings/config_flow.py @@ -8,9 +8,8 @@ from typing import Any from aiowithings import AuthScope from homeassistant.components.webhook import async_generate_id -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_TOKEN, CONF_WEBHOOK_ID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import CONF_USE_WEBHOOK, DEFAULT_TITLE, DOMAIN @@ -44,7 +43,9 @@ class WithingsFlowHandler( ) } - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -53,13 +54,13 @@ class WithingsFlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" user_id = str(data[CONF_TOKEN]["userid"]) if not self.reauth_entry: diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index f2d109bd6bb..a5a9d2e5ed4 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -10,9 +10,9 @@ from pywizlight.exceptions import WizLightConnectionError, WizLightTimeOutError import voluptuous as vol from homeassistant.components import dhcp, onboarding -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.util.network import is_ip_address from .const import DEFAULT_NAME, DISCOVER_SCAN_TIMEOUT, DOMAIN, WIZ_CONNECT_EXCEPTIONS @@ -36,7 +36,9 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN): """Initialize the config flow.""" self._discovered_devices: dict[str, DiscoveredBulb] = {} - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" self._discovered_device = DiscoveredBulb( discovery_info.ip, discovery_info.macaddress @@ -45,14 +47,14 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: dict[str, str] - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle integration discovery.""" self._discovered_device = DiscoveredBulb( discovery_info["ip_address"], discovery_info["mac_address"] ) return await self._async_handle_discovery() - async def _async_handle_discovery(self) -> FlowResult: + async def _async_handle_discovery(self) -> ConfigFlowResult: """Handle any discovery.""" device = self._discovered_device _LOGGER.debug("Discovered device: %s", device) @@ -81,7 +83,7 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" ip_address = self._discovered_device.ip_address if user_input is not None or not onboarding.async_is_onboarded(self.hass): @@ -104,7 +106,7 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step to pick discovered device.""" if user_input is not None: device = self._discovered_devices[user_input[CONF_DEVICE]] @@ -146,7 +148,7 @@ class WizConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index cbb78545e2b..f6da1f73bb2 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -7,10 +7,14 @@ import voluptuous as vol from wled import WLED, Device, WLEDConnectionError from homeassistant.components import onboarding, zeroconf -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_KEEP_MAIN_LIGHT, DEFAULT_KEEP_MAIN_LIGHT, DOMAIN @@ -31,7 +35,7 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -64,7 +68,7 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" # Abort quick if the mac address is provided by discovery info if mac := discovery_info.properties.get(CONF_MAC): @@ -95,7 +99,7 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): return self.async_create_entry( @@ -126,7 +130,7 @@ class WLEDOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage WLED options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/wolflink/config_flow.py b/homeassistant/components/wolflink/config_flow.py index bffc742f202..f8b528f431a 100644 --- a/homeassistant/components/wolflink/config_flow.py +++ b/homeassistant/components/wolflink/config_flow.py @@ -6,7 +6,7 @@ import voluptuous as vol from wolf_comm.token_auth import InvalidAuth from wolf_comm.wolf_client import WolfClient -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from .const import DEVICE_GATEWAY, DEVICE_ID, DEVICE_NAME, DOMAIN @@ -18,7 +18,7 @@ USER_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WolfLinkConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Wolf SmartSet Service.""" VERSION = 1 diff --git a/homeassistant/components/workday/config_flow.py b/homeassistant/components/workday/config_flow.py index 9f7e829a244..d65bc71c6e8 100644 --- a/homeassistant/components/workday/config_flow.py +++ b/homeassistant/components/workday/config_flow.py @@ -10,11 +10,12 @@ import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_COUNTRY, CONF_LANGUAGE, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.selector import ( CountrySelector, @@ -200,7 +201,7 @@ class WorkdayConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user initial step.""" errors: dict[str, str] = {} @@ -228,7 +229,7 @@ class WorkdayConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_options( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle remaining flow.""" errors: dict[str, str] = {} if user_input is not None: @@ -290,7 +291,7 @@ class WorkdayOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Workday options.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/ws66i/config_flow.py b/homeassistant/components/ws66i/config_flow.py index a7deb74eb3e..8f9e1f20682 100644 --- a/homeassistant/components/ws66i/config_flow.py +++ b/homeassistant/components/ws66i/config_flow.py @@ -7,8 +7,10 @@ from typing import Any from pyws66i import WS66i, get_ws66i import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_IP_ADDRESS +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from .const import ( CONF_SOURCE_1, @@ -40,7 +42,7 @@ DATA_SCHEMA = vol.Schema({vol.Required(CONF_IP_ADDRESS): str}) FIRST_ZONE = 11 -@core.callback +@callback def _sources_from_config(data): sources_config = { str(idx + 1): data.get(source) for idx, source in enumerate(SOURCES) @@ -70,7 +72,7 @@ def _verify_connection(ws66i: WS66i) -> bool: async def validate_input( - hass: core.HomeAssistant, input_data: dict[str, Any] + hass: HomeAssistant, input_data: dict[str, Any] ) -> dict[str, Any]: """Validate the user input. @@ -86,7 +88,7 @@ async def validate_input( return {CONF_IP_ADDRESS: input_data[CONF_IP_ADDRESS]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WS66iConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for WS66i 6-Zone Amplifier.""" VERSION = 1 @@ -115,15 +117,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) @staticmethod - @core.callback + @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> Ws66iOptionsFlowHandler: """Define the config flow to handle options.""" return Ws66iOptionsFlowHandler(config_entry) -@core.callback +@callback def _key_for_source(index, source, previous_sources): key = vol.Required( source, description={"suggested_value": previous_sources[str(index)]} @@ -132,10 +134,10 @@ def _key_for_source(index, source, previous_sources): return key -class Ws66iOptionsFlowHandler(config_entries.OptionsFlow): +class Ws66iOptionsFlowHandler(OptionsFlow): """Handle a WS66i options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry @@ -160,5 +162,5 @@ class Ws66iOptionsFlowHandler(config_entries.OptionsFlow): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/wyoming/config_flow.py b/homeassistant/components/wyoming/config_flow.py index 528165712b5..d1767c558a5 100644 --- a/homeassistant/components/wyoming/config_flow.py +++ b/homeassistant/components/wyoming/config_flow.py @@ -7,10 +7,9 @@ from urllib.parse import urlparse import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import hassio, zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN from .data import WyomingService @@ -25,7 +24,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class WyomingConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Wyoming integration.""" VERSION = 1 @@ -36,7 +35,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -62,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_hassio( self, discovery_info: hassio.HassioServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Supervisor add-on discovery.""" _LOGGER.debug("Supervisor discovery info: %s", discovery_info) await self.async_set_unique_id(discovery_info.uuid) @@ -79,7 +78,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm Supervisor discovery.""" errors: dict[str, str] = {} @@ -104,7 +103,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" _LOGGER.debug("Zeroconf discovery info: %s", discovery_info) if discovery_info.port is None: @@ -131,7 +130,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" assert self._service is not None assert self._name is not None diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index 2fae1796e45..d5a0332cf00 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -5,11 +5,10 @@ from socket import gaierror import voluptuous as vol from xiaomi_gateway import MULTICAST_PORT, XiaomiGateway, XiaomiGatewayDiscovery -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import ( @@ -44,7 +43,7 @@ GATEWAY_SETTINGS = vol.Schema( ) -class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class XiaomiAqaraFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Xiaomi Aqara config flow.""" VERSION = 1 @@ -148,7 +147,7 @@ class XiaomiAqaraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" name = discovery_info.name self.host = discovery_info.host diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index 576d49296e9..2c8be4954eb 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -16,9 +16,8 @@ from homeassistant.components.bluetooth import ( async_discovered_service_info, async_process_advertisements, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -76,7 +75,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -109,7 +108,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_get_encryption_key_legacy( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Enter a legacy bindkey for a v2/v3 MiBeacon device.""" assert self._discovery_info assert self._discovered_device @@ -143,7 +142,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_get_encryption_key_4_5( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Enter a bindkey for a v4/v5 MiBeacon device.""" assert self._discovery_info assert self._discovered_device @@ -177,7 +176,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): return self._async_get_or_create_entry() @@ -190,7 +189,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm_slow( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Ack that device is slow.""" if user_input is not None: return self._async_get_or_create_entry() @@ -203,7 +202,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] @@ -260,7 +259,9 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({vol.Required(CONF_ADDRESS): vol.In(titles)}), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a flow initialized by a reauth event.""" entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry is not None @@ -279,7 +280,9 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): # Otherwise there wasn't actually encryption so abort return self.async_abort(reason="reauth_successful") - def _async_get_or_create_entry(self, bindkey: str | None = None) -> FlowResult: + def _async_get_or_create_entry( + self, bindkey: str | None = None + ) -> ConfigFlowResult: data: dict[str, Any] = {} if bindkey: diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 379db82042b..f47a14ec89c 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -10,12 +10,16 @@ from micloud import MiCloud from micloud.micloudexception import MiCloudAccessDenied import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf -from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MAC, CONF_MODEL, CONF_TOKEN from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import ( @@ -56,16 +60,16 @@ DEVICE_CLOUD_CONFIG = vol.Schema( ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Options for the component.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init object.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors = {} if user_input is not None: @@ -104,7 +108,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class XiaomiMiioFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Xiaomi Miio config flow.""" VERSION = 1 @@ -127,7 +131,9 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Get the options flow.""" return OptionsFlowHandler(config_entry) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an authentication error or missing cloud credentials.""" self.host = entry_data[CONF_HOST] self.token = entry_data[CONF_TOKEN] @@ -137,7 +143,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is not None: return await self.async_step_cloud() @@ -145,13 +151,13 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self.async_step_cloud() async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" name = discovery_info.name self.host = discovery_info.host @@ -213,7 +219,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_cloud( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure a xiaomi miio device through the Miio Cloud.""" errors = {} if user_input is not None: @@ -290,7 +296,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle multiple cloud devices found.""" errors: dict[str, str] = {} if user_input is not None: @@ -308,7 +314,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_manual( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure a xiaomi miio device Manually.""" errors: dict[str, str] = {} if user_input is not None: @@ -327,7 +333,7 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_connect( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Connect to a xiaomi miio device.""" errors: dict[str, str] = {} if self.host is None or self.token is None: diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index ff813d43d78..6cdf7a298f3 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -8,10 +8,14 @@ import voluptuous as vol from yalesmartalarmclient.client import YaleSmartAlarmClient from yalesmartalarmclient.exceptions import AuthenticationError -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import ( @@ -54,14 +58,16 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): """Get the options flow for this handler.""" return YaleOptionsFlowHandler(config_entry) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with Yale.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors = {} @@ -102,7 +108,7 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -153,7 +159,7 @@ class YaleOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Yale options.""" errors: dict[str, Any] = {} diff --git a/homeassistant/components/yalexs_ble/config_flow.py b/homeassistant/components/yalexs_ble/config_flow.py index ecfbd45f36e..01e55c66ab1 100644 --- a/homeassistant/components/yalexs_ble/config_flow.py +++ b/homeassistant/components/yalexs_ble/config_flow.py @@ -16,15 +16,20 @@ from yalexs_ble import ( ) from yalexs_ble.const import YALE_MFR_ID -from homeassistant import config_entries, data_entry_flow from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_ble_device_from_address, async_discovered_service_info, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_ADDRESS from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.typing import DiscoveryInfoType from .const import CONF_ALWAYS_CONNECTED, CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN @@ -57,7 +62,7 @@ async def async_validate_lock_or_error( return {} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class YalexsConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Yale Access Bluetooth.""" VERSION = 1 @@ -67,11 +72,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_devices: dict[str, BluetoothServiceInfoBleak] = {} self._lock_cfg: ValidatedLockConfig | None = None - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -86,7 +91,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered integration.""" lock_cfg = ValidatedLockConfig( discovery_info["name"], @@ -137,7 +142,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # and entered the keys. We abort the discovery flow since # we assume they do not want to use the discovered keys for # some reason. - raise data_entry_flow.AbortFlow("already_in_progress") + raise AbortFlow("already_in_progress") hass.config_entries.flow.async_abort(progress["flow_id"]) self._lock_cfg = lock_cfg @@ -150,7 +155,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a confirmation of discovered integration.""" assert self._discovery_info is not None assert self._lock_cfg is not None @@ -174,7 +179,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -183,7 +190,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_validate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth and validation.""" errors = {} reauth_entry = self._reauth_entry @@ -221,7 +228,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} @@ -296,28 +303,28 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> YaleXSBLEOptionsFlowHandler: """Get the options flow for this handler.""" return YaleXSBLEOptionsFlowHandler(config_entry) -class YaleXSBLEOptionsFlowHandler(config_entries.OptionsFlow): +class YaleXSBLEOptionsFlowHandler(OptionsFlow): """Handle YaleXSBLE options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize YaleXSBLE options flow.""" self.entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the YaleXSBLE options.""" return await self.async_step_device_options() async def async_step_device_options( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the YaleXSBLE devices options.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/yamaha_musiccast/config_flow.py b/homeassistant/components/yamaha_musiccast/config_flow.py index b64f5aba6b7..078efdc9471 100644 --- a/homeassistant/components/yamaha_musiccast/config_flow.py +++ b/homeassistant/components/yamaha_musiccast/config_flow.py @@ -32,7 +32,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> data_entry_flow.ConfigFlowResult: """Handle a flow initiated by the user.""" # Request user input, unless we are preparing discovery flow if user_input is None: @@ -74,7 +74,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN): def _show_setup_form( self, errors: dict | None = None - ) -> data_entry_flow.FlowResult: + ) -> data_entry_flow.ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -84,7 +84,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_ssdp( self, discovery_info: ssdp.SsdpServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> data_entry_flow.ConfigFlowResult: """Handle ssdp discoveries.""" if not await MusicCastDevice.check_yamaha_ssdp( discovery_info.ssdp_location, async_get_clientsession(self.hass) @@ -116,7 +116,9 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_confirm() - async def async_step_confirm(self, user_input=None) -> data_entry_flow.FlowResult: + async def async_step_confirm( + self, user_input=None + ) -> data_entry_flow.ConfigFlowResult: """Allow the user to confirm adding the device.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/yardian/config_flow.py b/homeassistant/components/yardian/config_flow.py index 99258965f21..83ff1ef5c81 100644 --- a/homeassistant/components/yardian/config_flow.py +++ b/homeassistant/components/yardian/config_flow.py @@ -12,9 +12,8 @@ from pyyardian import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, PRODUCT_NAME @@ -29,7 +28,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class YardianConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Yardian.""" VERSION = 1 @@ -45,7 +44,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index 43f90511893..afa2eff64c4 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -9,12 +9,17 @@ import yeelight from yeelight.aio import AsyncBulb from yeelight.main import get_known_models -from homeassistant import config_entries, exceptions from homeassistant.components import dhcp, onboarding, ssdp, zeroconf -from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from .const import ( @@ -40,7 +45,7 @@ MODEL_UNKNOWN = "unknown" _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class YeelightConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Yeelight.""" VERSION = 1 @@ -59,19 +64,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery from homekit.""" self._discovered_ip = discovery_info.host return await self._async_handle_discovery() - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery from dhcp.""" self._discovered_ip = discovery_info.ip return await self._async_handle_discovery() async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery from zeroconf.""" self._discovered_ip = discovery_info.host await self.async_set_unique_id( @@ -79,7 +86,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self._async_handle_discovery_with_unique_id() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle discovery from ssdp.""" self._discovered_ip = urlparse(discovery_info.ssdp_headers["location"]).hostname await self.async_set_unique_id(discovery_info.ssdp_headers["id"]) @@ -272,7 +281,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return MODEL_UNKNOWN -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for Yeelight.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -323,5 +332,5 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/yolink/config_flow.py b/homeassistant/components/yolink/config_flow.py index 128cd6cb35c..f22c50c3345 100644 --- a/homeassistant/components/yolink/config_flow.py +++ b/homeassistant/components/yolink/config_flow.py @@ -5,8 +5,7 @@ from collections.abc import Mapping import logging from typing import Any -from homeassistant.config_entries import ConfigEntry -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN @@ -31,20 +30,22 @@ class OAuth2FlowHandler( scopes = ["create"] return {"scope": " ".join(scopes)} - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm(self, user_input=None) -> FlowResult: + async def async_step_reauth_confirm(self, user_input=None) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict) -> FlowResult: + async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: """Create an oauth config entry or update existing entry for reauth.""" if existing_entry := self._reauth_entry: self.hass.config_entries.async_update_entry( @@ -56,7 +57,7 @@ class OAuth2FlowHandler( async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" existing_entry = await self.async_set_unique_id(DOMAIN) if existing_entry and not self._reauth_entry: diff --git a/homeassistant/components/youless/config_flow.py b/homeassistant/components/youless/config_flow.py index 2cf79ae64e0..a6a993aa72a 100644 --- a/homeassistant/components/youless/config_flow.py +++ b/homeassistant/components/youless/config_flow.py @@ -8,9 +8,8 @@ from urllib.error import HTTPError, URLError import voluptuous as vol from youless_api import YoulessAPI -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE, CONF_HOST -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -26,7 +25,7 @@ class YoulessConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/youtube/config_flow.py b/homeassistant/components/youtube/config_flow.py index cf0d61b5d38..89ee02f9c90 100644 --- a/homeassistant/components/youtube/config_flow.py +++ b/homeassistant/components/youtube/config_flow.py @@ -10,10 +10,13 @@ from youtubeaio.helper import first from youtubeaio.types import AuthScope, ForbiddenError from youtubeaio.youtube import YouTube -from homeassistant.config_entries import ConfigEntry, OptionsFlowWithConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( @@ -67,7 +70,9 @@ class OAuth2FlowHandler( "prompt": "consent", } - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -76,7 +81,7 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") @@ -89,7 +94,7 @@ class OAuth2FlowHandler( await self._youtube.set_user_authentication(token, [AuthScope.READ_ONLY]) return self._youtube - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow, or update existing entry.""" try: youtube = await self.get_resource(data[CONF_TOKEN][CONF_ACCESS_TOKEN]) @@ -129,7 +134,7 @@ class OAuth2FlowHandler( async def async_step_channels( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select which channels to track.""" if user_input: return self.async_create_entry( @@ -164,7 +169,7 @@ class YouTubeOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Initialize form.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/zamg/config_flow.py b/homeassistant/components/zamg/config_flow.py index d20af9b274b..630c9e747c3 100644 --- a/homeassistant/components/zamg/config_flow.py +++ b/homeassistant/components/zamg/config_flow.py @@ -7,14 +7,13 @@ import voluptuous as vol from zamg import ZamgData from zamg.exceptions import ZamgApiError, ZamgNoDataError -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_STATION_ID, DOMAIN, LOGGER -class ZamgConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ZamgConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for zamg integration.""" VERSION = 1 @@ -23,7 +22,7 @@ class ZamgConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if self._client is None: self._client = ZamgData() diff --git a/homeassistant/components/zeversolar/config_flow.py b/homeassistant/components/zeversolar/config_flow.py index f749b9d471c..376fc43bcec 100644 --- a/homeassistant/components/zeversolar/config_flow.py +++ b/homeassistant/components/zeversolar/config_flow.py @@ -7,9 +7,8 @@ from typing import Any import voluptuous as vol import zeversolar -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import DOMAIN @@ -23,14 +22,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ZeverSolarConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for zeversolar.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index 60cf917d9f6..1fbe7b689ca 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -12,15 +12,24 @@ import voluptuous as vol import zigpy.backups from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH -from homeassistant import config_entries from homeassistant.components import onboarding, usb, zeroconf from homeassistant.components.file_upload import process_uploaded_file from homeassistant.components.hassio import AddonError, AddonState from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon from homeassistant.components.homeassistant_yellow import hardware as yellow_hardware +from homeassistant.config_entries import ( + SOURCE_IGNORE, + SOURCE_ZEROCONF, + ConfigEntry, + ConfigEntryBaseFlow, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OperationNotAllowed, + OptionsFlow, +) from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowHandler, FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.selector import FileSelector, FileSelectorConfig from homeassistant.util import dt as dt_util @@ -122,7 +131,7 @@ async def list_serial_ports(hass: HomeAssistant) -> list[ListPortInfo]: return ports -class BaseZhaFlow(FlowHandler): +class BaseZhaFlow(ConfigEntryBaseFlow): """Mixin for common ZHA flow steps and forms.""" _hass: HomeAssistant @@ -146,7 +155,7 @@ class BaseZhaFlow(FlowHandler): self._hass = hass self._radio_mgr.hass = hass - async def _async_create_radio_entry(self) -> FlowResult: + async def _async_create_radio_entry(self) -> ConfigFlowResult: """Create a config entry with the current flow state.""" assert self._title is not None assert self._radio_mgr.radio_type is not None @@ -168,7 +177,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_choose_serial_port( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose a serial port.""" ports = await list_serial_ports(self.hass) list_of_ports = [ @@ -232,7 +241,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_manual_pick_radio_type( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manually select the radio type.""" if user_input is not None: self._radio_mgr.radio_type = RadioType.get_by_description( @@ -257,7 +266,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_manual_port_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Enter port settings specific for this type of radio.""" assert self._radio_mgr.radio_type is not None errors = {} @@ -286,7 +295,7 @@ class BaseZhaFlow(FlowHandler): if param not in SUPPORTED_PORT_SETTINGS: continue - if source == config_entries.SOURCE_ZEROCONF and param == CONF_BAUDRATE: + if source == SOURCE_ZEROCONF and param == CONF_BAUDRATE: value = 115200 param = vol.Required(CONF_BAUDRATE, default=value) elif ( @@ -307,7 +316,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_verify_radio( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add a warning step to dissuade the use of deprecated radios.""" assert self._radio_mgr.radio_type is not None @@ -327,7 +336,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_choose_formation_strategy( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose how to deal with the current radio's settings.""" await self._radio_mgr.async_load_network_settings() @@ -370,20 +379,20 @@ class BaseZhaFlow(FlowHandler): async def async_step_reuse_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Reuse the existing network settings on the stick.""" return await self._async_create_radio_entry() async def async_step_form_initial_network( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Form an initial network.""" # This step exists only for translations, it does nothing new return await self.async_step_form_new_network(user_input) async def async_step_form_new_network( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Form a brand-new network.""" await self._radio_mgr.async_form_network() return await self._async_create_radio_entry() @@ -399,7 +408,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_upload_manual_backup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Upload and restore a coordinator backup JSON file.""" errors = {} @@ -427,7 +436,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_choose_automatic_backup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose an automatic backup.""" if self.show_advanced_options: # Always show the PAN IDs when in advanced mode @@ -466,7 +475,7 @@ class BaseZhaFlow(FlowHandler): async def async_step_maybe_confirm_ezsp_restore( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm restore for EZSP radios that require permanent IEEE writes.""" call_step_2 = await self._radio_mgr.async_restore_backup_step_1() if not call_step_2: @@ -486,7 +495,7 @@ class BaseZhaFlow(FlowHandler): ) -class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN): +class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 4 @@ -512,14 +521,14 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create the options flow.""" return ZhaOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a ZHA config flow start.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -528,7 +537,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm a discovery.""" self._set_confirm_only() @@ -566,7 +575,9 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN description_placeholders={CONF_NAME: self._title}, ) - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle usb discovery.""" vid = discovery_info.vid pid = discovery_info.pid @@ -585,7 +596,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN if self.hass.config_entries.flow.async_progress_by_handler(DECONZ_DOMAIN): return self.async_abort(reason="not_zha_device") for entry in self.hass.config_entries.async_entries(DECONZ_DOMAIN): - if entry.source != config_entries.SOURCE_IGNORE: + if entry.source != SOURCE_IGNORE: return self.async_abort(reason="not_zha_device") self._radio_mgr.device_path = dev_path @@ -602,7 +613,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" # Hostname is format: livingroom.local. @@ -638,7 +649,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN async def async_step_hardware( self, data: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle hardware flow.""" try: discovery_data = HARDWARE_DISCOVERY_SCHEMA(data) @@ -664,10 +675,10 @@ class ZhaConfigFlowHandler(BaseZhaFlow, config_entries.ConfigFlow, domain=DOMAIN return await self.async_step_confirm() -class ZhaOptionsFlowHandler(BaseZhaFlow, config_entries.OptionsFlow): +class ZhaOptionsFlowHandler(BaseZhaFlow, OptionsFlow): """Handle an options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" super().__init__() self.config_entry = config_entry @@ -679,11 +690,11 @@ class ZhaOptionsFlowHandler(BaseZhaFlow, config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Launch the options flow.""" if user_input is not None: # OperationNotAllowed: ZHA is not running - with suppress(config_entries.OperationNotAllowed): + with suppress(OperationNotAllowed): await self.hass.config_entries.async_unload(self.config_entry.entry_id) return await self.async_step_prompt_migrate_or_reconfigure() @@ -692,7 +703,7 @@ class ZhaOptionsFlowHandler(BaseZhaFlow, config_entries.OptionsFlow): async def async_step_prompt_migrate_or_reconfigure( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm if we are migrating adapters or just re-configuring.""" return self.async_show_menu( @@ -705,13 +716,13 @@ class ZhaOptionsFlowHandler(BaseZhaFlow, config_entries.OptionsFlow): async def async_step_intent_reconfigure( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Virtual step for when the user is reconfiguring the integration.""" return await self.async_step_choose_serial_port() async def async_step_intent_migrate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the user wants to reset their current radio.""" if user_input is not None: @@ -723,7 +734,7 @@ class ZhaOptionsFlowHandler(BaseZhaFlow, config_entries.OptionsFlow): async def async_step_instruct_unplug( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Instruct the user to unplug the current radio, if possible.""" if user_input is not None: @@ -758,8 +769,8 @@ class ZhaOptionsFlowHandler(BaseZhaFlow, config_entries.OptionsFlow): def async_remove(self): """Maybe reload ZHA if the flow is aborted.""" if self.config_entry.state not in ( - config_entries.ConfigEntryState.SETUP_ERROR, - config_entries.ConfigEntryState.NOT_LOADED, + ConfigEntryState.SETUP_ERROR, + ConfigEntryState.NOT_LOADED, ): return diff --git a/homeassistant/components/zodiac/config_flow.py b/homeassistant/components/zodiac/config_flow.py index 4acb3873031..d599c9747f9 100644 --- a/homeassistant/components/zodiac/config_flow.py +++ b/homeassistant/components/zodiac/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DEFAULT_NAME, DOMAIN @@ -16,7 +15,7 @@ class ZodiacConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/zone/config_flow.py b/homeassistant/components/zone/config_flow.py index de163146ab7..c176054bd34 100644 --- a/homeassistant/components/zone/config_flow.py +++ b/homeassistant/components/zone/config_flow.py @@ -4,10 +4,10 @@ This is no longer in use. This file is around so that existing config entries will remain to be loaded and then automatically migrated to the storage collection. """ -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from .const import DOMAIN -class ZoneConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ZoneConfigFlow(ConfigFlow, domain=DOMAIN): """Stub zone config flow class.""" diff --git a/homeassistant/components/zwave_me/config_flow.py b/homeassistant/components/zwave_me/config_flow.py index 0c7d77b0153..5fbfc1a475b 100644 --- a/homeassistant/components/zwave_me/config_flow.py +++ b/homeassistant/components/zwave_me/config_flow.py @@ -6,10 +6,9 @@ import logging from url_normalize import url_normalize import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.zeroconf import ZeroconfServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_TOKEN, CONF_URL -from homeassistant.data_entry_flow import FlowResult from . import helpers from .const import DOMAIN @@ -17,7 +16,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class ZWaveMeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ZWaveMeConfigFlow(ConfigFlow, domain=DOMAIN): """ZWaveMe integration config flow.""" def __init__(self) -> None: @@ -28,7 +27,7 @@ class ZWaveMeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user or started with zeroconf.""" errors = {} placeholders = { @@ -88,7 +87,7 @@ class ZWaveMeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered Z-Wave accessory - get url to pass into user step. This flow is triggered by the discovery component. diff --git a/tests/components/wallbox/test_config_flow.py b/tests/components/wallbox/test_config_flow.py index 20ad693c696..6b1592a3c4f 100644 --- a/tests/components/wallbox/test_config_flow.py +++ b/tests/components/wallbox/test_config_flow.py @@ -42,7 +42,7 @@ test_response = json.loads( async def test_show_set_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" - flow = config_flow.ConfigFlow() + flow = config_flow.WallboxConfigFlow() flow.hass = hass result = await flow.async_step_user(user_input=None) From 52e7912caf3edfae21cf4341487dfe9e019dcf73 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 20:08:46 +0100 Subject: [PATCH 0062/1691] Migrate integrations i-m to generic flowhandler (#111863) --- .../components/ialarm/config_flow.py | 7 ++- .../components/iaqualink/config_flow.py | 7 +-- .../components/ibeacon/config_flow.py | 22 +++++--- .../components/icloud/config_flow.py | 11 ++-- .../components/idasen_desk/config_flow.py | 9 ++- homeassistant/components/imap/config_flow.py | 27 +++++---- .../components/improv_ble/config_flow.py | 30 +++++----- .../components/inkbird/config_flow.py | 9 ++- .../components/insteon/config_flow.py | 43 +++++++++------ .../components/intellifire/config_flow.py | 21 ++++--- .../components/iotawatt/config_flow.py | 14 ++--- homeassistant/components/ipma/config_flow.py | 5 +- homeassistant/components/ipp/config_flow.py | 11 ++-- homeassistant/components/iqvia/config_flow.py | 7 +-- .../islamic_prayer_times/config_flow.py | 20 ++++--- homeassistant/components/iss/config_flow.py | 22 +++++--- .../components/isy994/config_flow.py | 55 +++++++++++-------- .../components/jellyfin/config_flow.py | 15 ++--- .../components/juicenet/config_flow.py | 4 +- .../components/justnimbus/config_flow.py | 13 +++-- .../components/jvc_projector/config_flow.py | 11 ++-- .../components/kaleidescape/config_flow.py | 15 +++-- .../components/keenetic_ndms2/config_flow.py | 23 +++++--- .../components/kegtron/config_flow.py | 9 ++- .../components/keymitt_ble/config_flow.py | 11 ++-- .../components/kitchen_sink/config_flow.py | 7 +-- .../components/kmtronic/config_flow.py | 19 ++++--- homeassistant/components/knx/config_flow.py | 47 +++++++++------- homeassistant/components/kodi/config_flow.py | 20 +++---- .../components/konnected/config_flow.py | 20 ++++--- .../kostal_plenticore/config_flow.py | 4 +- .../components/kraken/config_flow.py | 20 ++++--- .../components/lacrosse_view/config_flow.py | 15 ++--- .../components/lamarzocco/config_flow.py | 13 +++-- .../components/lametric/config_flow.py | 32 +++++++---- .../landisgyr_heat_meter/config_flow.py | 9 ++- .../components/lastfm/config_flow.py | 8 +-- .../components/launch_library/config_flow.py | 7 +-- .../components/laundrify/config_flow.py | 13 +++-- homeassistant/components/lcn/config_flow.py | 18 +++--- .../components/ld2410_ble/config_flow.py | 9 ++- .../components/leaone/config_flow.py | 5 +- .../components/led_ble/config_flow.py | 9 ++- .../components/lg_soundbar/config_flow.py | 7 +-- .../components/lidarr/config_flow.py | 11 ++-- homeassistant/components/lifx/config_flow.py | 23 ++++---- .../linear_garage_door/config_flow.py | 15 ++--- .../components/litejet/config_flow.py | 20 ++++--- .../components/litterrobot/config_flow.py | 13 +++-- .../components/livisi/config_flow.py | 9 ++- .../components/local_calendar/config_flow.py | 7 +-- .../components/local_ip/config_flow.py | 5 +- .../components/local_todo/config_flow.py | 7 +-- .../components/logi_circle/config_flow.py | 4 +- .../components/lookin/config_flow.py | 11 ++-- homeassistant/components/loqed/config_flow.py | 9 ++- .../components/luftdaten/config_flow.py | 9 ++- .../components/lupusec/config_flow.py | 7 ++- .../components/lutron/config_flow.py | 9 +-- .../components/lutron_caseta/config_flow.py | 9 ++- homeassistant/components/lyric/config_flow.py | 10 ++-- .../components/matter/config_flow.py | 30 +++++----- .../components/meater/config_flow.py | 15 ++--- .../components/medcom_ble/config_flow.py | 12 ++-- .../components/melcloud/config_flow.py | 19 ++++--- .../components/melnor/config_flow.py | 15 +++-- homeassistant/components/met/config_flow.py | 24 ++++---- .../components/met_eireann/config_flow.py | 7 +-- .../components/meteo_france/config_flow.py | 12 ++-- .../components/meteoclimatic/config_flow.py | 4 +- .../components/metoffice/config_flow.py | 15 +++-- .../components/microbees/config_flow.py | 11 ++-- .../components/mikrotik/config_flow.py | 28 ++++++---- homeassistant/components/mill/config_flow.py | 4 +- .../minecraft_server/config_flow.py | 7 +-- homeassistant/components/mjpeg/config_flow.py | 12 ++-- homeassistant/components/moat/config_flow.py | 9 ++- .../components/mobile_app/config_flow.py | 4 +- .../components/modem_callerid/config_flow.py | 13 +++-- .../components/modern_forms/config_flow.py | 15 +++-- .../moehlenhoff_alpha2/config_flow.py | 5 +- .../components/monoprice/config_flow.py | 24 ++++---- homeassistant/components/moon/config_flow.py | 5 +- .../components/mopeka/config_flow.py | 9 ++- .../components/motion_blinds/config_flow.py | 28 ++++++---- .../components/motioneye/config_flow.py | 18 +++--- .../components/motionmount/config_flow.py | 23 +++++--- homeassistant/components/mqtt/config_flow.py | 24 +++++--- .../components/mullvad/config_flow.py | 7 +-- .../components/mutesync/config_flow.py | 7 +-- homeassistant/components/myq/config_flow.py | 4 +- .../components/mysensors/config_flow.py | 18 +++--- .../components/mystrom/config_flow.py | 7 +-- .../components/myuplink/config_flow.py | 11 ++-- 94 files changed, 715 insertions(+), 612 deletions(-) diff --git a/homeassistant/components/ialarm/config_flow.py b/homeassistant/components/ialarm/config_flow.py index a894a6f4e11..0681160c741 100644 --- a/homeassistant/components/ialarm/config_flow.py +++ b/homeassistant/components/ialarm/config_flow.py @@ -4,8 +4,9 @@ import logging from pyialarm import IAlarm import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant from .const import DEFAULT_PORT, DOMAIN @@ -19,12 +20,12 @@ DATA_SCHEMA = vol.Schema( ) -async def _get_device_mac(hass: core.HomeAssistant, host, port): +async def _get_device_mac(hass: HomeAssistant, host, port): ialarm = IAlarm(host, port) return await hass.async_add_executor_job(ialarm.get_mac) -class IAlarmConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IAlarmConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Antifurto365 iAlarm.""" VERSION = 1 diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index 0dfc60f2fee..abacdf7a42f 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -11,21 +11,20 @@ from iaqualink.exception import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN -class AqualinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AqualinkFlowHandler(ConfigFlow, domain=DOMAIN): """Aqualink config flow.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow start.""" # Supporting a single account. entries = self._async_current_entries() diff --git a/homeassistant/components/ibeacon/config_flow.py b/homeassistant/components/ibeacon/config_flow.py index c7d6c358a29..c315090ef3d 100644 --- a/homeassistant/components/ibeacon/config_flow.py +++ b/homeassistant/components/ibeacon/config_flow.py @@ -6,23 +6,27 @@ from uuid import UUID import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import bluetooth +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import CONF_ALLOW_NAMELESS_UUIDS, DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IBeaconConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for iBeacon Tracker.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -38,20 +42,20 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlow: """Get the options flow for this handler.""" - return OptionsFlow(config_entry) + return IBeaconOptionsFlow(config_entry) -class OptionsFlow(config_entries.OptionsFlow): +class IBeaconOptionsFlow(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input: dict | None = None) -> FlowResult: + async def async_step_init(self, user_input: dict | None = None) -> ConfigFlowResult: """Manage the options.""" errors = {} diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index 4be5487f755..5a46ab3b4f3 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -15,9 +15,8 @@ from pyicloud.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.storage import Store from .const import ( @@ -38,7 +37,7 @@ CONF_VERIFICATION_CODE = "verification_code" _LOGGER = logging.getLogger(__name__) -class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class IcloudFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a iCloud config flow.""" VERSION = 1 @@ -178,7 +177,9 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._validate_and_create_entry(user_input, "user") - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Initialise re-authentication.""" # Store existing entry data so it can be used later and set unique ID # so existing config entry can be updated @@ -189,7 +190,7 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Update password for a config entry that can't authenticate.""" if user_input is None: return self._show_setup_form(step_id="reauth_confirm") diff --git a/homeassistant/components/idasen_desk/config_flow.py b/homeassistant/components/idasen_desk/config_flow.py index 80282ce0271..8fec40cde80 100644 --- a/homeassistant/components/idasen_desk/config_flow.py +++ b/homeassistant/components/idasen_desk/config_flow.py @@ -10,20 +10,19 @@ from idasen_ha import Desk from idasen_ha.errors import AuthFailedError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, EXPECTED_SERVICE_UUID _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IdasenDeskConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Idasen Desk integration.""" VERSION = 1 @@ -35,7 +34,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -49,7 +48,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/imap/config_flow.py b/homeassistant/components/imap/config_flow.py index 15b52ce6333..c4df769384e 100644 --- a/homeassistant/components/imap/config_flow.py +++ b/homeassistant/components/imap/config_flow.py @@ -8,10 +8,15 @@ from typing import Any from aioimaplib import AioImapException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import ( BooleanSelector, @@ -119,15 +124,15 @@ async def validate_input( return errors -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IMAPConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for imap.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None + _reauth_entry: ConfigEntry | None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" schema = CONFIG_SCHEMA @@ -152,7 +157,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): schema = self.add_suggested_values_to_schema(schema, user_input) return self.async_show_form(step_id="user", data_schema=schema, errors=errors) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -161,7 +168,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} assert self._reauth_entry @@ -188,18 +195,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlow: """Get the options flow for this handler.""" return OptionsFlow(config_entry) -class OptionsFlow(config_entries.OptionsFlowWithConfigEntry): +class OptionsFlow(OptionsFlowWithConfigEntry): """Option flow handler.""" async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors: dict[str, str] | None = None entry_data: dict[str, Any] = dict(self._config_entry.data) diff --git a/homeassistant/components/improv_ble/config_flow.py b/homeassistant/components/improv_ble/config_flow.py index 6f940f91946..a7d3c2cf000 100644 --- a/homeassistant/components/improv_ble/config_flow.py +++ b/homeassistant/components/improv_ble/config_flow.py @@ -19,11 +19,11 @@ from improv_ble_client import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import bluetooth +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from .const import DOMAIN @@ -47,7 +47,7 @@ class Credentials: ssid: str -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ImprovBLEConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Improv via BLE.""" VERSION = 1 @@ -55,9 +55,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _authorize_task: asyncio.Task | None = None _can_identify: bool | None = None _credentials: Credentials | None = None - _provision_result: FlowResult | None = None + _provision_result: ConfigFlowResult | None = None _provision_task: asyncio.Task | None = None - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None _remove_bluetooth_callback: Callable[[], None] | None = None _unsub: Callable[[], None] | None = None @@ -71,7 +71,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} @@ -158,7 +158,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: bluetooth.BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the Bluetooth discovery step.""" self._discovery_info = discovery_info @@ -181,7 +181,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle bluetooth confirm step.""" # mypy is not aware that we can't get here without having these set already assert self._discovery_info is not None @@ -198,7 +198,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_start_improv( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start improv flow. If the device supports identification, show a menu, if it does not, @@ -220,7 +220,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_main_menu() return await self.async_step_provision() - async def async_step_main_menu(self, _: None = None) -> FlowResult: + async def async_step_main_menu(self, _: None = None) -> ConfigFlowResult: """Show the main menu.""" return self.async_show_menu( step_id="main_menu", @@ -232,7 +232,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_identify( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle identify step.""" # mypy is not aware that we can't get here without having these set already assert self._device is not None @@ -247,7 +247,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_provision( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle provision step.""" # mypy is not aware that we can't get here without having these set already assert self._device is not None @@ -272,7 +272,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_do_provision( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Execute provisioning.""" async def _do_provision() -> None: @@ -339,7 +339,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_provision_done( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the result of the provision step.""" # mypy is not aware that we can't get here without having these set already assert self._provision_result is not None @@ -350,7 +350,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_authorize( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle authorize step.""" # mypy is not aware that we can't get here without having these set already assert self._device is not None diff --git a/homeassistant/components/inkbird/config_flow.py b/homeassistant/components/inkbird/config_flow.py index c63ad7e09d8..19c27dd5757 100644 --- a/homeassistant/components/inkbird/config_flow.py +++ b/homeassistant/components/inkbird/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class INKBIRDConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class INKBIRDConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class INKBIRDConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/insteon/config_flow.py b/homeassistant/components/insteon/config_flow.py index 36e977f6db0..66a131207bf 100644 --- a/homeassistant/components/insteon/config_flow.py +++ b/homeassistant/components/insteon/config_flow.py @@ -5,8 +5,14 @@ import logging from pyinsteon import async_close, async_connect, devices -from homeassistant import config_entries from homeassistant.components import dhcp, usb +from homeassistant.config_entries import ( + DEFAULT_DISCOVERY_UNIQUE_ID, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE, @@ -17,7 +23,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -106,7 +111,7 @@ def _remove_x10(device, options): return new_options, housecode, unitcode -class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class InsteonFlowHandler(ConfigFlow, domain=DOMAIN): """Insteon config flow handler.""" _device_path: str | None = None @@ -116,7 +121,7 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> InsteonOptionsFlowHandler: """Define the config flow to handle options.""" return InsteonOptionsFlowHandler(config_entry) @@ -185,7 +190,9 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id=step_id, data_schema=data_schema, errors=errors ) - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle USB discovery.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -203,10 +210,10 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = { CONF_NAME: f"Insteon PLM {self._device_name}" } - await self.async_set_unique_id(config_entries.DEFAULT_DISCOVERY_UNIQUE_ID) + await self.async_set_unique_id(DEFAULT_DISCOVERY_UNIQUE_ID) return await self.async_step_confirm_usb() - async def async_step_confirm_usb(self, user_input=None) -> FlowResult: + async def async_step_confirm_usb(self, user_input=None) -> ConfigFlowResult: """Confirm a USB discovery.""" if user_input is not None: return await self.async_step_plm({CONF_DEVICE: self._device_path}) @@ -216,7 +223,9 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={CONF_NAME: self._device_name}, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle a DHCP discovery.""" self.discovered_conf = {CONF_HOST: discovery_info.ip} self.context["title_placeholders"] = { @@ -226,14 +235,14 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() -class InsteonOptionsFlowHandler(config_entries.OptionsFlow): +class InsteonOptionsFlowHandler(OptionsFlow): """Handle an Insteon options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init the InsteonOptionsFlowHandler class.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Init the options config flow.""" menu_options = [STEP_ADD_OVERRIDE, STEP_ADD_X10] @@ -250,7 +259,7 @@ class InsteonOptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_menu(step_id="init", menu_options=menu_options) - async def async_step_change_hub_config(self, user_input=None) -> FlowResult: + async def async_step_change_hub_config(self, user_input=None) -> ConfigFlowResult: """Change the Hub configuration.""" errors = {} if user_input is not None: @@ -276,7 +285,7 @@ class InsteonOptionsFlowHandler(config_entries.OptionsFlow): step_id=STEP_CHANGE_HUB_CONFIG, data_schema=data_schema, errors=errors ) - async def async_step_change_plm_config(self, user_input=None) -> FlowResult: + async def async_step_change_plm_config(self, user_input=None) -> ConfigFlowResult: """Change the PLM configuration.""" errors = {} if user_input is not None: @@ -299,7 +308,7 @@ class InsteonOptionsFlowHandler(config_entries.OptionsFlow): step_id=STEP_CHANGE_PLM_CONFIG, data_schema=data_schema, errors=errors ) - async def async_step_add_override(self, user_input=None) -> FlowResult: + async def async_step_add_override(self, user_input=None) -> ConfigFlowResult: """Add a device override.""" errors = {} if user_input is not None: @@ -315,7 +324,7 @@ class InsteonOptionsFlowHandler(config_entries.OptionsFlow): step_id=STEP_ADD_OVERRIDE, data_schema=data_schema, errors=errors ) - async def async_step_add_x10(self, user_input=None) -> FlowResult: + async def async_step_add_x10(self, user_input=None) -> ConfigFlowResult: """Add an X10 device.""" errors: dict[str, str] = {} if user_input is not None: @@ -328,7 +337,7 @@ class InsteonOptionsFlowHandler(config_entries.OptionsFlow): step_id=STEP_ADD_X10, data_schema=data_schema, errors=errors ) - async def async_step_remove_override(self, user_input=None) -> FlowResult: + async def async_step_remove_override(self, user_input=None) -> ConfigFlowResult: """Remove a device override.""" errors: dict[str, str] = {} options = self.config_entry.options @@ -346,7 +355,7 @@ class InsteonOptionsFlowHandler(config_entries.OptionsFlow): step_id=STEP_REMOVE_OVERRIDE, data_schema=data_schema, errors=errors ) - async def async_step_remove_x10(self, user_input=None) -> FlowResult: + async def async_step_remove_x10(self, user_input=None) -> ConfigFlowResult: """Remove an X10 device.""" errors: dict[str, str] = {} options = self.config_entry.options diff --git a/homeassistant/components/intellifire/config_flow.py b/homeassistant/components/intellifire/config_flow.py index 6a061b40bcc..17d442428e7 100644 --- a/homeassistant/components/intellifire/config_flow.py +++ b/homeassistant/components/intellifire/config_flow.py @@ -11,10 +11,9 @@ from intellifire4py.exceptions import LoginException from intellifire4py.intellifire import IntellifireAPICloud, IntellifireAPILocal import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.dhcp import DhcpServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_USER_ID, DOMAIN, LOGGER @@ -46,7 +45,7 @@ async def validate_host_input(host: str, dhcp_mode: bool = False) -> str: return serial -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IntelliFireConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for IntelliFire.""" VERSION = 1 @@ -107,7 +106,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_api_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure API access.""" errors = {} @@ -151,7 +150,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="api_config", errors=errors, data_schema=control_schema ) - async def _async_validate_ip_and_continue(self, host: str) -> FlowResult: + async def _async_validate_ip_and_continue(self, host: str) -> ConfigFlowResult: """Validate local config and continue.""" self._async_abort_entries_match({CONF_HOST: host}) self._serial = await validate_host_input(host) @@ -181,7 +180,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Pick which device to configure.""" errors = {} LOGGER.debug("STEP: pick_device") @@ -210,7 +209,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start the user flow.""" # Launch fireplaces discovery @@ -222,7 +221,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): LOGGER.debug("Running Step: manual_device_entry") return await self.async_step_manual_device_entry() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" LOGGER.debug("STEP: reauth") entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -237,7 +238,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = placeholders return await self.async_step_api_config() - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: """Handle DHCP Discovery.""" # Run validation logic on ip diff --git a/homeassistant/components/iotawatt/config_flow.py b/homeassistant/components/iotawatt/config_flow.py index 9ec860ea76a..6b7b86b73af 100644 --- a/homeassistant/components/iotawatt/config_flow.py +++ b/homeassistant/components/iotawatt/config_flow.py @@ -6,8 +6,10 @@ import logging from iotawattpy.iotawatt import Iotawatt import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import httpx_client from .const import CONNECTION_ERRORS, DOMAIN @@ -15,9 +17,7 @@ from .const import CONNECTION_ERRORS, DOMAIN _LOGGER = logging.getLogger(__name__) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, str] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect.""" iotawatt = Iotawatt( "", @@ -40,7 +40,7 @@ async def validate_input( return {} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IOTaWattConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for iotawatt.""" VERSION = 1 @@ -99,9 +99,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=data[CONF_HOST], data=data) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/ipma/config_flow.py b/homeassistant/components/ipma/config_flow.py index cdea88bdbc0..cf468c9013a 100644 --- a/homeassistant/components/ipma/config_flow.py +++ b/homeassistant/components/ipma/config_flow.py @@ -7,9 +7,8 @@ from pyipma.api import IPMA_API from pyipma.location import Location import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -25,7 +24,7 @@ class IpmaFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index dfe6c0b2127..334fb5d7690 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -16,7 +16,7 @@ from pyipp import ( import voluptuous as vol from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -26,7 +26,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_BASE_PATH, CONF_SERIAL, DOMAIN @@ -65,7 +64,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -104,7 +103,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" host = discovery_info.host @@ -190,7 +189,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a confirmation flow initiated by zeroconf.""" if user_input is None: return self.async_show_form( @@ -204,7 +203,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): data=self.discovery_info, ) - def _show_setup_form(self, errors: dict | None = None) -> FlowResult: + def _show_setup_form(self, errors: dict | None = None) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/iqvia/config_flow.py b/homeassistant/components/iqvia/config_flow.py index 32ce64014d7..d293aedd7b2 100644 --- a/homeassistant/components/iqvia/config_flow.py +++ b/homeassistant/components/iqvia/config_flow.py @@ -7,14 +7,13 @@ from pyiqvia import Client from pyiqvia.errors import InvalidZipError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers import aiohttp_client from .const import CONF_ZIP_CODE, DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class IqviaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle an IQVIA config flow.""" VERSION = 1 @@ -25,7 +24,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return self.async_show_form(step_id="user", data_schema=self.data_schema) diff --git a/homeassistant/components/islamic_prayer_times/config_flow.py b/homeassistant/components/islamic_prayer_times/config_flow.py index 73696572593..766163a9b0d 100644 --- a/homeassistant/components/islamic_prayer_times/config_flow.py +++ b/homeassistant/components/islamic_prayer_times/config_flow.py @@ -7,10 +7,14 @@ from prayer_times_calculator import InvalidResponseError, PrayerTimesCalculator from requests.exceptions import ConnectionError as ConnError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( LocationSelector, SelectSelector, @@ -58,7 +62,7 @@ async def async_validate_location( return errors -class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class IslamicPrayerFlowHandler(ConfigFlow, domain=DOMAIN): """Handle the Islamic Prayer config flow.""" VERSION = 1 @@ -67,14 +71,14 @@ class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> IslamicPrayerOptionsFlowHandler: """Get the options flow for this handler.""" return IslamicPrayerOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -111,16 +115,16 @@ class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class IslamicPrayerOptionsFlowHandler(config_entries.OptionsFlow): +class IslamicPrayerOptionsFlowHandler(OptionsFlow): """Handle Islamic Prayer client options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/iss/config_flow.py b/homeassistant/components/iss/config_flow.py index f8ebd9db723..80644698239 100644 --- a/homeassistant/components/iss/config_flow.py +++ b/homeassistant/components/iss/config_flow.py @@ -2,15 +2,19 @@ import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_SHOW_ON_MAP from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ISSConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for iss component.""" VERSION = 1 @@ -18,12 +22,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # Check if already configured if self._async_current_entries(): @@ -39,15 +43,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user") -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Config flow options handler for iss.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: self.options.update(user_input) diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 2cdfd1df16d..658f7204d41 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -13,11 +13,18 @@ from pyisy.configuration import Configuration from pyisy.connection import Connection import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import dhcp, ssdp +from homeassistant.config_entries import ( + SOURCE_IGNORE, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import AbortFlow +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from .const import ( @@ -58,9 +65,7 @@ def _data_schema(schema_input: dict[str, str]) -> vol.Schema: ) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -118,7 +123,7 @@ async def validate_input( } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class Isy994ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Universal Devices ISY/IoX.""" VERSION = 1 @@ -126,19 +131,19 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the ISY/IoX config flow.""" self.discovered_conf: dict[str, str] = {} - self._existing_entry: config_entries.ConfigEntry | None = None + self._existing_entry: ConfigEntry | None = None @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} info: dict[str, str] = {} @@ -175,7 +180,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): existing_entry = await self.async_set_unique_id(isy_mac) if not existing_entry: return - if existing_entry.source == config_entries.SOURCE_IGNORE: + if existing_entry.source == SOURCE_IGNORE: raise AbortFlow("already_configured") parsed_url = urlparse(existing_entry.data[CONF_HOST]) if parsed_url.hostname != ip_address: @@ -202,7 +207,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) raise AbortFlow("already_configured") - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered ISY/IoX device via dhcp.""" friendly_name = discovery_info.hostname if friendly_name.startswith("polisy") or friendly_name.startswith("eisy"): @@ -223,7 +230,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = self.discovered_conf return await self.async_step_user() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered ISY/IoX Device.""" friendly_name = discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME] url = discovery_info.ssdp_location @@ -250,14 +259,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = self.discovered_conf return await self.async_step_user() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth.""" self._existing_entry = await self.async_set_unique_id(self.context["unique_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth input.""" errors = {} assert self._existing_entry is not None @@ -299,16 +310,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for ISY/IoX.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -337,13 +348,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=options_schema) -class InvalidHost(exceptions.HomeAssistantError): +class InvalidHost(HomeAssistantError): """Error to indicate the host value is invalid.""" -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/jellyfin/config_flow.py b/homeassistant/components/jellyfin/config_flow.py index 84360ed053e..b6702722006 100644 --- a/homeassistant/components/jellyfin/config_flow.py +++ b/homeassistant/components/jellyfin/config_flow.py @@ -7,9 +7,8 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.util.uuid import random_uuid_hex from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input @@ -37,7 +36,7 @@ def _generate_client_device_id() -> str: return random_uuid_hex() -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class JellyfinConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Jellyfin.""" VERSION = 1 @@ -45,11 +44,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the Jellyfin config flow.""" self.client_device_id: str | None = None - self.entry: config_entries.ConfigEntry | None = None + self.entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a user defined configuration.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -92,14 +91,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/juicenet/config_flow.py b/homeassistant/components/juicenet/config_flow.py index 7fdc024df47..1385aa2eca8 100644 --- a/homeassistant/components/juicenet/config_flow.py +++ b/homeassistant/components/juicenet/config_flow.py @@ -1,11 +1,11 @@ """Config flow for JuiceNet integration.""" -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from . import DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class JuiceNetConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for JuiceNet.""" VERSION = 1 diff --git a/homeassistant/components/justnimbus/config_flow.py b/homeassistant/components/justnimbus/config_flow.py index 536943ef607..08c02d40f94 100644 --- a/homeassistant/components/justnimbus/config_flow.py +++ b/homeassistant/components/justnimbus/config_flow.py @@ -8,9 +8,8 @@ from typing import Any import justnimbus import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CLIENT_ID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import CONF_ZIP_CODE, DOMAIN @@ -25,15 +24,15 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class JustNimbusConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for JustNimbus.""" VERSION = 1 - reauth_entry: config_entries.ConfigEntry | None = None + reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -76,7 +75,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/jvc_projector/config_flow.py b/homeassistant/components/jvc_projector/config_flow.py index 181d11e1f56..7564d571d3b 100644 --- a/homeassistant/components/jvc_projector/config_flow.py +++ b/homeassistant/components/jvc_projector/config_flow.py @@ -9,9 +9,8 @@ from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnec from jvcprojector.projector import DEFAULT_PORT import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from homeassistant.util.network import is_host_valid @@ -27,7 +26,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user initiated device additions.""" errors = {} @@ -74,7 +73,9 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth on password authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -83,7 +84,7 @@ class JvcProjectorConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" assert self._reauth_entry diff --git a/homeassistant/components/kaleidescape/config_flow.py b/homeassistant/components/kaleidescape/config_flow.py index 5454f29f5cb..bb9f47ec1e8 100644 --- a/homeassistant/components/kaleidescape/config_flow.py +++ b/homeassistant/components/kaleidescape/config_flow.py @@ -2,21 +2,18 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, cast +from typing import Any, cast from urllib.parse import urlparse import voluptuous as vol from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from . import KaleidescapeDeviceInfo, UnsupportedError, validate_host from .const import DEFAULT_HOST, DOMAIN, NAME as KALEIDESCAPE_NAME -if TYPE_CHECKING: - from homeassistant.data_entry_flow import FlowResult - ERROR_CANNOT_CONNECT = "cannot_connect" ERROR_UNSUPPORTED = "unsupported" @@ -30,7 +27,7 @@ class KaleidescapeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user initiated device additions.""" errors = {} host = DEFAULT_HOST @@ -63,7 +60,9 @@ class KaleidescapeConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle discovered device.""" host = cast(str, urlparse(discovery_info.ssdp_location).hostname) serial_number = discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL] @@ -93,7 +92,7 @@ class KaleidescapeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle addition of discovered device.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/keenetic_ndms2/config_flow.py b/homeassistant/components/keenetic_ndms2/config_flow.py index 535c83c927e..553204512e5 100644 --- a/homeassistant/components/keenetic_ndms2/config_flow.py +++ b/homeassistant/components/keenetic_ndms2/config_flow.py @@ -7,9 +7,13 @@ from urllib.parse import urlparse from ndms2_client import Client, ConnectionException, InterfaceInfo, TelnetConnection import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -18,7 +22,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import ( @@ -37,7 +40,7 @@ from .const import ( from .router import KeeneticRouter -class KeeneticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class KeeneticFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -52,7 +55,7 @@ class KeeneticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -97,7 +100,9 @@ class KeeneticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered device.""" friendly_name = discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME, "") @@ -124,7 +129,7 @@ class KeeneticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() -class KeeneticOptionsFlowHandler(config_entries.OptionsFlow): +class KeeneticOptionsFlowHandler(OptionsFlow): """Handle options.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -134,7 +139,7 @@ class KeeneticOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" router: KeeneticRouter = self.hass.data[DOMAIN][self.config_entry.entry_id][ ROUTER @@ -153,7 +158,7 @@ class KeeneticOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the device tracker options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/kegtron/config_flow.py b/homeassistant/components/kegtron/config_flow.py index cc0457af87b..8e657de800d 100644 --- a/homeassistant/components/kegtron/config_flow.py +++ b/homeassistant/components/kegtron/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class KegtronConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class KegtronConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class KegtronConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/keymitt_ble/config_flow.py b/homeassistant/components/keymitt_ble/config_flow.py index 5665dc27d17..d0fb6ff0317 100644 --- a/homeassistant/components/keymitt_ble/config_flow.py +++ b/homeassistant/components/keymitt_ble/config_flow.py @@ -17,9 +17,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -54,7 +53,7 @@ class MicroBotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" _LOGGER.debug("Discovered bluetooth device: %s", discovery_info) await self.async_set_unique_id(discovery_info.address) @@ -71,14 +70,14 @@ class MicroBotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # This is for backwards compatibility. return await self.async_step_init(user_input) async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Check if paired.""" errors: dict[str, str] = {} @@ -125,7 +124,7 @@ class MicroBotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Given a configured host, will ask the user to press the button to pair.""" errors: dict[str, str] = {} token = randomid(32) diff --git a/homeassistant/components/kitchen_sink/config_flow.py b/homeassistant/components/kitchen_sink/config_flow.py index 54104784c50..56df82047ad 100644 --- a/homeassistant/components/kitchen_sink/config_flow.py +++ b/homeassistant/components/kitchen_sink/config_flow.py @@ -3,18 +3,17 @@ from __future__ import annotations from typing import Any -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from . import DOMAIN -class KitchenSinkConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class KitchenSinkConfigFlow(ConfigFlow, domain=DOMAIN): """Kitchen Sink configuration flow.""" VERSION = 1 - async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult: """Set the config entry up from yaml.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py index 8a00a03e673..e1d0bf95d09 100644 --- a/homeassistant/components/kmtronic/config_flow.py +++ b/homeassistant/components/kmtronic/config_flow.py @@ -8,9 +8,10 @@ from pykmtronic.auth import Auth from pykmtronic.hub import KMTronicHubAPI import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from .const import CONF_REVERSE, DOMAIN @@ -26,7 +27,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect.""" session = aiohttp_client.async_get_clientsession(hass) auth = Auth( @@ -47,7 +48,7 @@ async def validate_input(hass: core.HomeAssistant, data): return data -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class KmtronicConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for kmtronic.""" VERSION = 1 @@ -55,7 +56,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> KMTronicOptionsFlow: """Get the options flow for this handler.""" return KMTronicOptionsFlow(config_entry) @@ -81,18 +82,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class KMTronicOptionsFlow(config_entries.OptionsFlow): +class KMTronicOptionsFlow(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 5338a5fddca..e095f71f924 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -18,10 +18,15 @@ from xknx.io.self_description import request_description from xknx.io.util import validate_ip as xknx_validate_ip from xknx.secure.keyring import Keyring, XMLInterface -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryBaseFlow, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowHandler, FlowResult from homeassistant.helpers import selector from homeassistant.helpers.typing import UNDEFINED @@ -96,7 +101,7 @@ _PORT_SELECTOR = vol.All( ) -class KNXCommonFlow(ABC, FlowHandler): +class KNXCommonFlow(ABC, ConfigEntryBaseFlow): """Base class for KNX flows.""" def __init__(self, initial_data: KNXConfigEntryData) -> None: @@ -115,7 +120,7 @@ class KNXCommonFlow(ABC, FlowHandler): self._async_scan_gen: AsyncGenerator[GatewayDescriptor, None] | None = None @abstractmethod - def finish_flow(self) -> FlowResult: + def finish_flow(self) -> ConfigFlowResult: """Finish the flow.""" @property @@ -136,7 +141,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_connection_type( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle connection type configuration.""" if user_input is not None: if self._async_scan_gen: @@ -202,7 +207,9 @@ class KNXCommonFlow(ABC, FlowHandler): step_id="connection_type", data_schema=vol.Schema(fields) ) - async def async_step_tunnel(self, user_input: dict | None = None) -> FlowResult: + async def async_step_tunnel( + self, user_input: dict | None = None + ) -> ConfigFlowResult: """Select a tunnel from a list. Will be skipped if the gateway scan was unsuccessful @@ -258,7 +265,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_manual_tunnel( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manually configure tunnel connection parameters. Fields default to preselected gateway if one was found.""" errors: dict = {} @@ -380,7 +387,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_secure_tunnel_manual( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure ip secure tunnelling manually.""" errors: dict = {} @@ -428,7 +435,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_secure_routing_manual( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure ip secure routing manually.""" errors: dict = {} @@ -481,7 +488,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_secure_knxkeys( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage upload of new KNX Keyring file.""" errors: dict[str, str] = {} @@ -533,7 +540,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_knxkeys_tunnel_select( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select if a specific tunnel should be used from knxkeys file.""" errors = {} description_placeholders = {} @@ -618,7 +625,9 @@ class KNXCommonFlow(ABC, FlowHandler): description_placeholders=description_placeholders, ) - async def async_step_routing(self, user_input: dict | None = None) -> FlowResult: + async def async_step_routing( + self, user_input: dict | None = None + ) -> ConfigFlowResult: """Routing setup.""" errors: dict = {} _individual_address = ( @@ -703,7 +712,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_secure_key_source_menu_tunnel( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the key source menu.""" return self.async_show_menu( step_id="secure_key_source_menu_tunnel", @@ -712,7 +721,7 @@ class KNXCommonFlow(ABC, FlowHandler): async def async_step_secure_key_source_menu_routing( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the key source menu.""" return self.async_show_menu( step_id="secure_key_source_menu_routing", @@ -736,7 +745,7 @@ class KNXConfigFlow(KNXCommonFlow, ConfigFlow, domain=DOMAIN): return KNXOptionsFlow(config_entry) @callback - def finish_flow(self) -> FlowResult: + def finish_flow(self) -> ConfigFlowResult: """Create the ConfigEntry.""" title = self.new_title or f"KNX {self.new_entry_data[CONF_KNX_CONNECTION_TYPE]}" return self.async_create_entry( @@ -744,7 +753,7 @@ class KNXConfigFlow(KNXCommonFlow, ConfigFlow, domain=DOMAIN): data=DEFAULT_ENTRY_DATA | self.new_entry_data, ) - async def async_step_user(self, user_input: dict | None = None) -> FlowResult: + async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -762,7 +771,7 @@ class KNXOptionsFlow(KNXCommonFlow, OptionsFlow): super().__init__(initial_data=config_entry.data) # type: ignore[arg-type] @callback - def finish_flow(self) -> FlowResult: + def finish_flow(self) -> ConfigFlowResult: """Update the ConfigEntry and finish the flow.""" new_data = DEFAULT_ENTRY_DATA | self.initial_data | self.new_entry_data self.hass.config_entries.async_update_entry( @@ -774,7 +783,7 @@ class KNXOptionsFlow(KNXCommonFlow, OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage KNX options.""" return self.async_show_menu( step_id="init", @@ -787,7 +796,7 @@ class KNXOptionsFlow(KNXCommonFlow, OptionsFlow): async def async_step_communication_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage KNX communication settings.""" if user_input is not None: self.new_entry_data = KNXConfigEntryData( diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index bd6d23e0f8e..74fc5e0cb09 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -6,8 +6,8 @@ import logging from pykodi import CannotConnectError, InvalidAuthError, Kodi, get_kodi_connection import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -17,8 +17,8 @@ from homeassistant.const import ( CONF_TIMEOUT, CONF_USERNAME, ) -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -33,7 +33,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -async def validate_http(hass: core.HomeAssistant, data): +async def validate_http(hass: HomeAssistant, data): """Validate the user input allows us to connect over HTTP.""" host = data[CONF_HOST] @@ -56,7 +56,7 @@ async def validate_http(hass: core.HomeAssistant, data): raise InvalidAuth from error -async def validate_ws(hass: core.HomeAssistant, data): +async def validate_ws(hass: HomeAssistant, data): """Validate the user input allows us to connect over WS.""" if not (ws_port := data.get(CONF_WS_PORT)): return @@ -84,7 +84,7 @@ async def validate_ws(hass: core.HomeAssistant, data): raise WSCannotConnect from error -class KodiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class KodiConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Kodi.""" VERSION = 1 @@ -102,7 +102,7 @@ class KodiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self._host = discovery_info.host self._port = discovery_info.port or DEFAULT_PORT @@ -313,13 +313,13 @@ class KodiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return data -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class WSCannotConnect(exceptions.HomeAssistantError): +class WSCannotConnect(HomeAssistantError): """Error to indicate we cannot connect to websocket.""" diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index c9889dd6464..f6225760feb 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -11,12 +11,17 @@ from urllib.parse import urlparse import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, BinarySensorDeviceClass, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_BINARY_SENSORS, @@ -33,7 +38,6 @@ from homeassistant.const import ( CONF_ZONE, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import ( @@ -166,7 +170,7 @@ CONFIG_ENTRY_SCHEMA = vol.Schema( ) -class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class KonnectedFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Konnected Panels.""" VERSION = 1 @@ -244,7 +248,9 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return await self.async_step_user() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered konnected panel. This flow is triggered by the SSDP component. It will check if the @@ -376,16 +382,16 @@ class KonnectedFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Return the Options Flow.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for a Konnected Panel.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.entry = config_entry self.model = self.entry.data[CONF_MODEL] diff --git a/homeassistant/components/kostal_plenticore/config_flow.py b/homeassistant/components/kostal_plenticore/config_flow.py index 8dd3a823570..c88006e59b6 100644 --- a/homeassistant/components/kostal_plenticore/config_flow.py +++ b/homeassistant/components/kostal_plenticore/config_flow.py @@ -5,7 +5,7 @@ from aiohttp.client_exceptions import ClientError from pykoplenti import ApiClient, AuthenticationException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_BASE, CONF_HOST, CONF_PASSWORD from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -38,7 +38,7 @@ async def test_connection(hass: HomeAssistant, data) -> str: return values["scb:network"][hostname_id] -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class KostalPlenticoreConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Kostal Plenticore Solar Inverter.""" VERSION = 1 diff --git a/homeassistant/components/kraken/config_flow.py b/homeassistant/components/kraken/config_flow.py index ff4c882504c..fba08b63071 100644 --- a/homeassistant/components/kraken/config_flow.py +++ b/homeassistant/components/kraken/config_flow.py @@ -7,17 +7,21 @@ import krakenex from pykrakenapi.pykrakenapi import KrakenAPI import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import CONF_TRACKED_ASSET_PAIRS, DEFAULT_SCAN_INTERVAL, DOMAIN from .utils import get_tradable_asset_pairs -class KrakenConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class KrakenConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for kraken.""" VERSION = 1 @@ -25,14 +29,14 @@ class KrakenConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> KrakenOptionsFlowHandler: """Get the options flow for this handler.""" return KrakenOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="already_configured") @@ -45,16 +49,16 @@ class KrakenConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class KrakenOptionsFlowHandler(config_entries.OptionsFlow): +class KrakenOptionsFlowHandler(OptionsFlow): """Handle Kraken client options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Kraken options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Kraken options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/lacrosse_view/config_flow.py b/homeassistant/components/lacrosse_view/config_flow.py index 67d294de179..db6def15835 100644 --- a/homeassistant/components/lacrosse_view/config_flow.py +++ b/homeassistant/components/lacrosse_view/config_flow.py @@ -8,9 +8,8 @@ from typing import Any from lacrosse_view import LaCrosse, Location, LoginError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -45,7 +44,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> list[Loca return locations -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LaCrosseViewConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for LaCrosse View.""" VERSION = 1 @@ -54,11 +53,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the config flow.""" self.data: dict[str, str] = {} self.locations: list[Location] = [] - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: _LOGGER.debug("Showing initial form") @@ -100,7 +99,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_location( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the location step.""" if not user_input: @@ -135,7 +134,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Reauth in case of a password change or other error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/lamarzocco/config_flow.py b/homeassistant/components/lamarzocco/config_flow.py index 7c63532104f..d9fa21c5419 100644 --- a/homeassistant/components/lamarzocco/config_flow.py +++ b/homeassistant/components/lamarzocco/config_flow.py @@ -7,9 +7,8 @@ from lmcloud import LMCloud as LaMarzoccoClient from lmcloud.exceptions import AuthFail, RequestNotSuccessful import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import ( SelectOptionDict, @@ -35,7 +34,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -89,7 +88,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_machine_selection( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Let user select machine to connect to.""" errors: dict[str, str] = {} if user_input: @@ -141,7 +140,9 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -150,7 +151,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if not user_input: return self.async_show_form( diff --git a/homeassistant/components/lametric/config_flow.py b/homeassistant/components/lametric/config_flow.py index 1dad190d706..0fdd566fada 100644 --- a/homeassistant/components/lametric/config_flow.py +++ b/homeassistant/components/lametric/config_flow.py @@ -28,9 +28,9 @@ from homeassistant.components.ssdp import ( ATTR_UPNP_SERIAL, SsdpServiceInfo, ) -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_DEVICE, CONF_HOST, CONF_MAC -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler from homeassistant.helpers.device_registry import format_mac @@ -72,11 +72,13 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" return await self.async_step_choice_enter_manual_or_fetch_cloud() - async def async_step_ssdp(self, discovery_info: SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initiated by SSDP discovery.""" url = URL(discovery_info.ssdp_location or "") if url.host is None or not ( @@ -106,7 +108,9 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): self.discovered_serial = serial return await self.async_step_choice_enter_manual_or_fetch_cloud() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with LaMetric.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -115,7 +119,7 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): async def async_step_choice_enter_manual_or_fetch_cloud( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user's choice. Either enter the manual credentials or fetch the cloud credentials. @@ -127,7 +131,7 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): async def async_step_manual_entry( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user's choice of entering the device manually.""" errors: dict[str, str] = {} if user_input is not None: @@ -166,7 +170,9 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): errors=errors, ) - async def async_step_cloud_fetch_devices(self, data: dict[str, Any]) -> FlowResult: + async def async_step_cloud_fetch_devices( + self, data: dict[str, Any] + ) -> ConfigFlowResult: """Fetch information about devices from the cloud.""" lametric = LaMetricCloud( token=data["token"]["access_token"], @@ -184,7 +190,7 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): async def async_step_cloud_select_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle device selection from devices offered by the cloud.""" if self.discovered: user_input = {CONF_DEVICE: self.discovered_serial} @@ -232,7 +238,9 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): errors=errors, ) - async def _async_step_create_entry(self, host: str, api_key: str) -> FlowResult: + async def _async_step_create_entry( + self, host: str, api_key: str + ) -> ConfigFlowResult: """Create entry.""" lametric = LaMetricDevice( host=host, @@ -287,7 +295,9 @@ class LaMetricFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): }, ) - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery to update existing entries.""" mac = format_mac(discovery_info.macaddress) for entry in self._async_current_entries(): diff --git a/homeassistant/components/landisgyr_heat_meter/config_flow.py b/homeassistant/components/landisgyr_heat_meter/config_flow.py index 479e7107025..64f4555c8fb 100644 --- a/homeassistant/components/landisgyr_heat_meter/config_flow.py +++ b/homeassistant/components/landisgyr_heat_meter/config_flow.py @@ -10,11 +10,10 @@ from serial.tools import list_ports import ultraheat_api import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import usb +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN, ULTRAHEAT_TIMEOUT @@ -30,14 +29,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LandisgyrConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Ultraheat Heat Meter.""" VERSION = 2 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when setting up serial configuration.""" errors = {} @@ -63,7 +62,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_serial_manual_path( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Set path manually.""" errors = {} diff --git a/homeassistant/components/lastfm/config_flow.py b/homeassistant/components/lastfm/config_flow.py index 4ff809b56d0..51791de54fb 100644 --- a/homeassistant/components/lastfm/config_flow.py +++ b/homeassistant/components/lastfm/config_flow.py @@ -9,11 +9,11 @@ import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_API_KEY from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( SelectOptionDict, SelectSelector, @@ -83,7 +83,7 @@ class LastFmConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Initialize user input.""" errors: dict[str, str] = {} if user_input is not None: @@ -102,7 +102,7 @@ class LastFmConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_friends( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Form to select other users and friends.""" errors: dict[str, str] = {} if user_input is not None: @@ -159,7 +159,7 @@ class LastFmOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Initialize form.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/launch_library/config_flow.py b/homeassistant/components/launch_library/config_flow.py index d57bc3b7d01..3349efb9c28 100644 --- a/homeassistant/components/launch_library/config_flow.py +++ b/homeassistant/components/launch_library/config_flow.py @@ -3,20 +3,19 @@ from __future__ import annotations from typing import Any -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN -class LaunchLibraryFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class LaunchLibraryFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Launch Library component.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # Check if already configured if self._async_current_entries(): diff --git a/homeassistant/components/laundrify/config_flow.py b/homeassistant/components/laundrify/config_flow.py index 55a29fec2e7..e11ef88f2df 100644 --- a/homeassistant/components/laundrify/config_flow.py +++ b/homeassistant/components/laundrify/config_flow.py @@ -13,9 +13,8 @@ from laundrify_aio.exceptions import ( ) from voluptuous import Required, Schema -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CODE -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -32,13 +31,13 @@ class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self.async_step_init(user_input) async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="init", data_schema=CONFIG_SCHEMA) @@ -77,13 +76,15 @@ class LaundrifyConfigFlow(ConfigFlow, domain=DOMAIN): step_id="init", data_schema=CONFIG_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py index 09f588b6953..87dc9c2b602 100644 --- a/homeassistant/components/lcn/config_flow.py +++ b/homeassistant/components/lcn/config_flow.py @@ -5,7 +5,12 @@ import logging import pypck -from homeassistant import config_entries +from homeassistant.config_entries import ( + SOURCE_IMPORT, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import ( CONF_HOST, CONF_IP_ADDRESS, @@ -14,7 +19,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.typing import ConfigType from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN @@ -23,9 +27,7 @@ from .helpers import purge_device_registry, purge_entity_registry _LOGGER = logging.getLogger(__name__) -def get_config_entry( - hass: HomeAssistant, data: ConfigType -) -> config_entries.ConfigEntry | None: +def get_config_entry(hass: HomeAssistant, data: ConfigType) -> ConfigEntry | None: """Check config entries for already configured entries based on the ip address/port.""" return next( ( @@ -65,12 +67,12 @@ async def validate_connection(host_name: str, data: ConfigType) -> ConfigType: return data -class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class LcnFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a LCN config flow.""" VERSION = 1 - async def async_step_import(self, data: ConfigType) -> FlowResult: + async def async_step_import(self, data: ConfigType) -> ConfigFlowResult: """Import existing configuration from LCN.""" host_name = data[CONF_HOST] # validate the imported connection parameters @@ -94,7 +96,7 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # check if we already have a host with the same address configured if entry := get_config_entry(self.hass, data): - entry.source = config_entries.SOURCE_IMPORT + entry.source = SOURCE_IMPORT # Cleanup entity and device registry, if we imported from configuration.yaml to # remove orphans when entities were removed from configuration purge_entity_registry(self.hass, entry.entry_id, data) diff --git a/homeassistant/components/ld2410_ble/config_flow.py b/homeassistant/components/ld2410_ble/config_flow.py index d56610f87a8..b0286b299a3 100644 --- a/homeassistant/components/ld2410_ble/config_flow.py +++ b/homeassistant/components/ld2410_ble/config_flow.py @@ -8,20 +8,19 @@ from bluetooth_data_tools import human_readable_name from ld2410_ble import BLEAK_EXCEPTIONS, LD2410BLE import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, LOCAL_NAMES _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class Ld2410BleConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for LD2410 BLE.""" VERSION = 1 @@ -33,7 +32,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -47,7 +46,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/leaone/config_flow.py b/homeassistant/components/leaone/config_flow.py index 5bbf2917332..fba9f103446 100644 --- a/homeassistant/components/leaone/config_flow.py +++ b/homeassistant/components/leaone/config_flow.py @@ -7,9 +7,8 @@ from leaone_ble import LeaoneBluetoothDeviceData as DeviceData import voluptuous as vol from homeassistant.components.bluetooth import async_discovered_service_info -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -25,7 +24,7 @@ class LeaoneConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/led_ble/config_flow.py b/homeassistant/components/led_ble/config_flow.py index 44436180ea8..70b4f78e840 100644 --- a/homeassistant/components/led_ble/config_flow.py +++ b/homeassistant/components/led_ble/config_flow.py @@ -8,20 +8,19 @@ from bluetooth_data_tools import human_readable_name from led_ble import BLEAK_EXCEPTIONS, LEDBLE import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, LOCAL_NAMES, UNSUPPORTED_SUB_MODEL _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LedBleConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Yale Access Bluetooth.""" VERSION = 1 @@ -33,7 +32,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" if discovery_info.name.startswith(UNSUPPORTED_SUB_MODEL): # These versions speak a different protocol @@ -51,7 +50,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/lg_soundbar/config_flow.py b/homeassistant/components/lg_soundbar/config_flow.py index fde5c20ebd7..c28c3e458af 100644 --- a/homeassistant/components/lg_soundbar/config_flow.py +++ b/homeassistant/components/lg_soundbar/config_flow.py @@ -5,9 +5,8 @@ from queue import Empty, Full, Queue import temescal import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_PORT, DOMAIN @@ -67,12 +66,12 @@ def test_connect(host, port): return details -class LGSoundbarConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LGSoundbarConfigFlow(ConfigFlow, domain=DOMAIN): """LG Soundbar config flow.""" VERSION = 1 - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._show_form() diff --git a/homeassistant/components/lidarr/config_flow.py b/homeassistant/components/lidarr/config_flow.py index a0b73950766..b1b04f6e778 100644 --- a/homeassistant/components/lidarr/config_flow.py +++ b/homeassistant/components/lidarr/config_flow.py @@ -9,10 +9,9 @@ from aiopyarr import exceptions from aiopyarr.lidarr_client import LidarrClient import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DEFAULT_NAME, DOMAIN @@ -27,7 +26,9 @@ class LidarrConfigFlow(ConfigFlow, domain=DOMAIN): """Initialize the flow.""" self.entry: ConfigEntry | None = None - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -35,7 +36,7 @@ class LidarrConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is not None: return await self.async_step_user() @@ -45,7 +46,7 @@ class LidarrConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} diff --git a/homeassistant/components/lifx/config_flow.py b/homeassistant/components/lifx/config_flow.py index b6fd67c0356..dc0a0ab2d36 100644 --- a/homeassistant/components/lifx/config_flow.py +++ b/homeassistant/components/lifx/config_flow.py @@ -8,12 +8,11 @@ from aiolifx.aiolifx import Light from aiolifx.connection import LIFXConnection import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.components.dhcp import DhcpServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE, CONF_HOST from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import DiscoveryInfoType @@ -36,7 +35,7 @@ from .util import ( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LifXConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for LIFX.""" VERSION = 1 @@ -46,7 +45,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_devices: dict[str, Light] = {} self._discovered_device: Light | None = None - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via DHCP.""" mac = discovery_info.macaddress host = discovery_info.ip @@ -69,13 +70,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle HomeKit discovery.""" return await self._async_handle_discovery(host=discovery_info.host) async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle LIFX UDP broadcast discovery.""" serial = discovery_info[CONF_SERIAL] host = discovery_info[CONF_HOST] @@ -85,7 +86,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_handle_discovery( self, host: str, serial: str | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle any discovery.""" self._async_abort_entries_match({CONF_HOST: host}) self.context[CONF_HOST] = host @@ -120,7 +121,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None discovered = self._discovered_device @@ -146,7 +147,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -168,7 +169,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step to pick discovered device.""" if user_input is not None: serial = user_input[CONF_DEVICE] @@ -207,7 +208,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry_from_device(self, device: Light) -> FlowResult: + def _async_create_entry_from_device(self, device: Light) -> ConfigFlowResult: """Create a config entry from a smart device.""" self._abort_if_unique_id_configured(updates={CONF_HOST: device.ip_addr}) return self.async_create_entry( diff --git a/homeassistant/components/linear_garage_door/config_flow.py b/homeassistant/components/linear_garage_door/config_flow.py index 6bca49adb4c..c8892a8f99c 100644 --- a/homeassistant/components/linear_garage_door/config_flow.py +++ b/homeassistant/components/linear_garage_door/config_flow.py @@ -10,10 +10,9 @@ from linear_garage_door import Linear from linear_garage_door.errors import InvalidLoginError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -63,7 +62,7 @@ async def validate_input( return info -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LinearGarageDoorConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Linear Garage Door.""" VERSION = 1 @@ -71,11 +70,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" self.data: dict[str, Sequence[Collection[str]]] = {} - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" data_schema = STEP_USER_DATA_SCHEMA @@ -115,7 +114,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_site( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the site step.""" if isinstance(self.data["sites"], list): @@ -150,7 +149,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Reauth in case of a password change or other error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/litejet/config_flow.py b/homeassistant/components/litejet/config_flow.py index a7b5a6f000e..3c452f7b4d7 100644 --- a/homeassistant/components/litejet/config_flow.py +++ b/homeassistant/components/litejet/config_flow.py @@ -7,25 +7,29 @@ import pylitejet from serial import SerialException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import CONF_DEFAULT_TRANSITION, DOMAIN -class LiteJetOptionsFlow(config_entries.OptionsFlow): +class LiteJetOptionsFlow(OptionsFlow): """Handle LiteJet options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize LiteJet options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage LiteJet options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -45,12 +49,12 @@ class LiteJetOptionsFlow(config_entries.OptionsFlow): ) -class LiteJetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LiteJetConfigFlow(ConfigFlow, domain=DOMAIN): """LiteJet config flow.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create a LiteJet config entry based upon user input.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -79,7 +83,7 @@ class LiteJetConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> LiteJetOptionsFlow: """Get the options flow for this handler.""" return LiteJetOptionsFlow(config_entry) diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index 558945ca1db..54377d43980 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -9,9 +9,8 @@ from pylitterbot import Account from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -23,21 +22,23 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LitterRobotConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Litter-Robot.""" VERSION = 1 username: str - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" self.username = entry_data[CONF_USERNAME] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user's reauth credentials.""" errors = {} if user_input: @@ -62,7 +63,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index c8685eb2390..1b5c4892eee 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -8,15 +8,14 @@ from aiohttp import ClientConnectorError from aiolivisi import AioLivisi, errors as livisi_errors import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN, LOGGER -class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class LivisiFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Livisi Smart Home config flow.""" def __init__(self) -> None: @@ -31,7 +30,7 @@ class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=self.data_schema) @@ -70,7 +69,7 @@ class LivisiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def create_entity( self, user_input: dict[str, str], controller_info: dict[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: """Create LIVISI entity.""" if (controller_data := controller_info.get("gateway")) is None: controller_data = controller_info diff --git a/homeassistant/components/local_calendar/config_flow.py b/homeassistant/components/local_calendar/config_flow.py index a5a75fee58b..8703def7304 100644 --- a/homeassistant/components/local_calendar/config_flow.py +++ b/homeassistant/components/local_calendar/config_flow.py @@ -5,8 +5,7 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.util import slugify from .const import CONF_CALENDAR_NAME, CONF_STORAGE_KEY, DOMAIN @@ -18,14 +17,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LocalCalendarConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Local Calendar.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/local_ip/config_flow.py b/homeassistant/components/local_ip/config_flow.py index be708f5d8b9..09e9835e65e 100644 --- a/homeassistant/components/local_ip/config_flow.py +++ b/homeassistant/components/local_ip/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -14,7 +13,7 @@ class SimpleConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/local_todo/config_flow.py b/homeassistant/components/local_todo/config_flow.py index 73328358a3c..dc596c91d9c 100644 --- a/homeassistant/components/local_todo/config_flow.py +++ b/homeassistant/components/local_todo/config_flow.py @@ -6,8 +6,7 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.util import slugify from .const import CONF_STORAGE_KEY, CONF_TODO_LIST_NAME, DOMAIN @@ -21,14 +20,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LocalTodoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Local To-do.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index be22a9a5d30..6b7d6881c19 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -7,8 +7,8 @@ from logi_circle import LogiCircle from logi_circle.exception import AuthorizationFailed import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_API_KEY, CONF_CLIENT_ID, @@ -53,7 +53,7 @@ def register_flow_implementation( } -class LogiCircleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class LogiCircleFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Logi Circle component.""" VERSION = 1 diff --git a/homeassistant/components/lookin/config_flow.py b/homeassistant/components/lookin/config_flow.py index 895d071ab4e..27ba96e9eb0 100644 --- a/homeassistant/components/lookin/config_flow.py +++ b/homeassistant/components/lookin/config_flow.py @@ -8,10 +8,9 @@ import aiohttp from aiolookin import Device, LookInHttpProtocol, NoUsableService import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -19,7 +18,7 @@ from .const import DOMAIN LOGGER = logging.getLogger(__name__) -class LookinFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class LookinFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for lookin.""" def __init__(self) -> None: @@ -29,7 +28,7 @@ class LookinFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Start a discovery flow from zeroconf.""" uid: str = discovery_info.hostname.removesuffix(".local.") host: str = discovery_info.host @@ -52,7 +51,7 @@ class LookinFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """User initiated discover flow.""" errors: dict[str, str] = {} @@ -88,7 +87,7 @@ class LookinFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the discover flow.""" assert self._host is not None if user_input is None: diff --git a/homeassistant/components/loqed/config_flow.py b/homeassistant/components/loqed/config_flow.py index 1c76f480529..a0f23b4359d 100644 --- a/homeassistant/components/loqed/config_flow.py +++ b/homeassistant/components/loqed/config_flow.py @@ -9,12 +9,11 @@ import aiohttp from loqedAPI import cloud_loqed, loqed import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import webhook from homeassistant.components.zeroconf import ZeroconfServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_TOKEN, CONF_NAME, CONF_WEBHOOK_ID from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -23,7 +22,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class LoqedConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Loqed.""" VERSION = 1 @@ -83,7 +82,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" host = discovery_info.host self._host = host @@ -101,7 +100,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show userform to user.""" user_data_schema = ( vol.Schema( diff --git a/homeassistant/components/luftdaten/config_flow.py b/homeassistant/components/luftdaten/config_flow.py index 0fa800f1d8b..f2f8a9366cd 100644 --- a/homeassistant/components/luftdaten/config_flow.py +++ b/homeassistant/components/luftdaten/config_flow.py @@ -7,22 +7,21 @@ from luftdaten import Luftdaten from luftdaten.exceptions import LuftdatenConnectionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_SHOW_ON_MAP from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import CONF_SENSOR_ID, DOMAIN -class SensorCommunityFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SensorCommunityFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Sensor.Community config flow.""" VERSION = 1 @callback - def _show_form(self, errors: dict[str, str] | None = None) -> FlowResult: + def _show_form(self, errors: dict[str, str] | None = None) -> ConfigFlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", @@ -37,7 +36,7 @@ class SensorCommunityFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if user_input is None: return self._show_form() diff --git a/homeassistant/components/lupusec/config_flow.py b/homeassistant/components/lupusec/config_flow.py index 1fae687cbdb..4129c33eabf 100644 --- a/homeassistant/components/lupusec/config_flow.py +++ b/homeassistant/components/lupusec/config_flow.py @@ -16,7 +16,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -37,7 +36,7 @@ class LupusecConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -67,7 +66,9 @@ class LupusecConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, user_input: dict[str, Any] + ) -> config_entries.ConfigFlowResult: """Import the yaml config.""" self._async_abort_entries_match( { diff --git a/homeassistant/components/lutron/config_flow.py b/homeassistant/components/lutron/config_flow.py index 04628849230..1a21ab37469 100644 --- a/homeassistant/components/lutron/config_flow.py +++ b/homeassistant/components/lutron/config_flow.py @@ -8,9 +8,8 @@ from urllib.error import HTTPError from pylutron import Lutron import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -24,7 +23,7 @@ class LutronConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """First step in the config flow.""" # Check if a configuration entry already exists @@ -74,7 +73,9 @@ class LutronConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Attempt to import the existing configuration.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 21f7cbd9683..7323acc3e4c 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -10,11 +10,10 @@ from pylutron_caseta.pairing import PAIR_CA, PAIR_CERT, PAIR_KEY, async_pair from pylutron_caseta.smartbridge import Smartbridge import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( ABORT_REASON_CANNOT_CONNECT, @@ -45,7 +44,7 @@ DATA_SCHEMA_USER = vol.Schema({vol.Required(CONF_HOST): str}) TLS_ASSET_TEMPLATE = "lutron_caseta-{}-{}.pem" -class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class LutronCasetaFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Lutron Caseta config flow.""" VERSION = 1 @@ -67,7 +66,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf discovery.""" hostname = discovery_info.hostname if hostname is None or not hostname.lower().startswith("lutron-"): @@ -88,7 +87,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by homekit discovery.""" return await self.async_step_zeroconf(discovery_info) diff --git a/homeassistant/components/lyric/config_flow.py b/homeassistant/components/lyric/config_flow.py index de6808dd0de..16c5c40cbea 100644 --- a/homeassistant/components/lyric/config_flow.py +++ b/homeassistant/components/lyric/config_flow.py @@ -5,7 +5,7 @@ from collections.abc import Mapping import logging from typing import Any -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN @@ -23,19 +23,21 @@ class OAuth2FlowHandler( """Return logger.""" return logging.getLogger(__name__) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an oauth config entry or update existing entry for reauth.""" existing_entry = await self.async_set_unique_id(DOMAIN) if existing_entry: diff --git a/homeassistant/components/matter/config_flow.py b/homeassistant/components/matter/config_flow.py index 1636790c4cb..8982067207e 100644 --- a/homeassistant/components/matter/config_flow.py +++ b/homeassistant/components/matter/config_flow.py @@ -8,7 +8,6 @@ from matter_server.client import MatterClient from matter_server.client.exceptions import CannotConnect, InvalidServerVersion import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.hassio import ( AddonError, AddonInfo, @@ -17,9 +16,10 @@ from homeassistant.components.hassio import ( HassioServiceInfo, is_hassio, ) +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client @@ -56,7 +56,7 @@ def build_ws_address(host: str, port: int) -> str: return f"ws://{host}:{port}/ws" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MatterConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Matter.""" VERSION = 1 @@ -72,7 +72,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_install_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Install Matter Server add-on.""" if not self.install_task: self.install_task = self.hass.async_create_task(self._async_install_addon()) @@ -98,7 +98,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_install_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on installation failed.""" return self.async_abort(reason="addon_install_failed") @@ -120,7 +120,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_start_addon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start Matter Server add-on.""" if not self.start_task: self.start_task = self.hass.async_create_task(self._async_start_addon()) @@ -143,7 +143,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_start_failed( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add-on start failed.""" return self.async_abort(reason="addon_start_failed") @@ -186,7 +186,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if is_hassio(self.hass): return await self.async_step_on_supervisor() @@ -195,7 +195,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_manual( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a manual configuration.""" if user_input is None: return self.async_show_form( @@ -222,7 +222,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="manual", data_schema=get_manual_schema(user_input), errors=errors ) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Receive configuration from add-on discovery info. This flow is triggered by the Matter Server add-on. @@ -240,7 +242,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the add-on discovery.""" if user_input is not None: return await self.async_step_on_supervisor( @@ -251,7 +253,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_on_supervisor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle logic when on Supervisor host.""" if user_input is None: return self.async_show_form( @@ -274,7 +276,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_finish_addon_setup( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare info needed to complete the config entry.""" if not self.ws_address: discovery_info = await self._async_get_addon_discovery_info() @@ -289,7 +291,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._async_create_entry_or_abort() - async def _async_create_entry_or_abort(self) -> FlowResult: + async def _async_create_entry_or_abort(self) -> ConfigFlowResult: """Return a config entry for the flow or abort if already configured.""" assert self.ws_address is not None diff --git a/homeassistant/components/meater/config_flow.py b/homeassistant/components/meater/config_flow.py index 91a927a5fb2..d325bcd4e5a 100644 --- a/homeassistant/components/meater/config_flow.py +++ b/homeassistant/components/meater/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from meater import AuthenticationError, MeaterApi, ServiceUnavailableError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -20,7 +19,7 @@ USER_SCHEMA = vol.Schema( ) -class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MeaterConfigFlow(ConfigFlow, domain=DOMAIN): """Meater Config Flow.""" _data_schema = USER_SCHEMA @@ -28,7 +27,7 @@ class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Define the login user step.""" if user_input is None: return self.async_show_form( @@ -45,7 +44,9 @@ class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._try_connect_meater("user", None, username, password) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._data_schema = REAUTH_SCHEMA self._username = entry_data[CONF_USERNAME] @@ -53,7 +54,7 @@ class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" placeholders = {"username": self._username} if not user_input: @@ -70,7 +71,7 @@ class MeaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _try_connect_meater( self, step_id, placeholders: dict[str, str] | None, username: str, password: str - ) -> FlowResult: + ) -> ConfigFlowResult: session = aiohttp_client.async_get_clientsession(self.hass) api = MeaterApi(session) diff --git a/homeassistant/components/medcom_ble/config_flow.py b/homeassistant/components/medcom_ble/config_flow.py index 30a87afbb72..faf482ca1f9 100644 --- a/homeassistant/components/medcom_ble/config_flow.py +++ b/homeassistant/components/medcom_ble/config_flow.py @@ -16,9 +16,9 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfo, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from .const import DOMAIN @@ -51,7 +51,7 @@ class InspectorBLEConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" _LOGGER.debug("Discovered BLE device: %s", discovery_info.name) await self.async_set_unique_id(discovery_info.address) @@ -67,7 +67,7 @@ class InspectorBLEConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" # We always will have self._discovery_info be a BluetoothServiceInfo at this point # and this helps mypy not complain @@ -84,7 +84,7 @@ class InspectorBLEConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] @@ -123,7 +123,7 @@ class InspectorBLEConfigFlow(ConfigFlow, domain=DOMAIN): ), ) - async def async_step_check_connection(self) -> FlowResult: + async def async_step_check_connection(self) -> ConfigFlowResult: """Check we can connect to the device before considering the configuration is successful.""" # We always will have self._discovery_info be a BluetoothServiceInfo at this point # and this helps mypy not complain diff --git a/homeassistant/components/melcloud/config_flow.py b/homeassistant/components/melcloud/config_flow.py index 88f658a0615..f848846409d 100644 --- a/homeassistant/components/melcloud/config_flow.py +++ b/homeassistant/components/melcloud/config_flow.py @@ -11,9 +11,8 @@ from aiohttp import ClientError, ClientResponseError import pymelcloud import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -21,14 +20,14 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 - entry: config_entries.ConfigEntry | None = None + entry: ConfigEntry | None = None - async def _create_entry(self, username: str, token: str) -> FlowResult: + async def _create_entry(self, username: str, token: str) -> ConfigFlowResult: """Register new entry.""" await self.async_set_unique_id(username) self._abort_if_unique_id_configured({CONF_TOKEN: token}) @@ -42,7 +41,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): *, password: str | None = None, token: str | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Create client.""" try: async with asyncio.timeout(10): @@ -67,7 +66,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """User initiated config flow.""" if user_input is None: return self.async_show_form( @@ -79,14 +78,16 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): username = user_input[CONF_USERNAME] return await self._create_client(username, password=user_input[CONF_PASSWORD]) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with MELCloud.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with MELCloud.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/melnor/config_flow.py b/homeassistant/components/melnor/config_flow.py index 7aad59acf7b..223b30c7bb3 100644 --- a/homeassistant/components/melnor/config_flow.py +++ b/homeassistant/components/melnor/config_flow.py @@ -6,16 +6,15 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.bluetooth import async_discovered_service_info from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, MANUFACTURER_DATA_START, MANUFACTURER_ID -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MelnorConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for melnor.""" VERSION = 1 @@ -25,7 +24,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_address: str self._discovered_addresses: list[str] = [] - def _create_entry(self, address: str) -> FlowResult: + def _create_entry(self, address: str) -> ConfigFlowResult: """Create an entry for a discovered device.""" return self.async_create_entry( @@ -37,7 +36,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered device.""" if user_input is not None: @@ -50,7 +49,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by Bluetooth discovery.""" address = discovery_info.address @@ -65,7 +64,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step to pick discovered device.""" if user_input is not None: @@ -108,7 +107,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self.async_step_pick_device() diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index ac614e4691b..2afe6f8cd30 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -5,8 +5,13 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import OptionsFlowWithConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, + OptionsFlowWithConfigEntry, +) from homeassistant.const import ( CONF_ELEVATION, CONF_LATITUDE, @@ -15,7 +20,6 @@ from homeassistant.const import ( UnitOfLength, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( NumberSelector, @@ -47,7 +51,7 @@ def configured_instances(hass: HomeAssistant) -> set[str]: def _get_data_schema( - hass: HomeAssistant, config_entry: config_entries.ConfigEntry | None = None + hass: HomeAssistant, config_entry: ConfigEntry | None = None ) -> vol.Schema: """Get a schema with default values.""" # If tracking home or no config entry is passed in, default value come from Home location @@ -91,14 +95,14 @@ def _get_data_schema( ) -class MetConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MetConfigFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Met component.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -120,7 +124,7 @@ class MetConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_onboarding( self, data: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by onboarding.""" # Don't create entry if latitude or longitude isn't set. # Also, filters out our onboarding default location. @@ -137,8 +141,8 @@ class MetConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for Met.""" return MetOptionsFlowHandler(config_entry) @@ -148,7 +152,7 @@ class MetOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure options for Met.""" if user_input is not None: diff --git a/homeassistant/components/met_eireann/config_flow.py b/homeassistant/components/met_eireann/config_flow.py index b4c0102b97e..d1e49e4961e 100644 --- a/homeassistant/components/met_eireann/config_flow.py +++ b/homeassistant/components/met_eireann/config_flow.py @@ -3,22 +3,21 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import DOMAIN, HOME_LOCATION_NAME -class MetEireannFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MetEireannFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Met Eireann component.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: # Check if an identical entity is already configured diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index a3001ee25c0..695b0c9b353 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -8,18 +8,16 @@ from meteofrance_api.client import MeteoFranceClient from meteofrance_api.model import Place import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import CONF_CITY, DOMAIN _LOGGER = logging.getLogger(__name__) -class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MeteoFranceFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Meteo-France config flow.""" VERSION = 1 @@ -33,7 +31,7 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" if user_input is None: @@ -49,7 +47,7 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors: dict[str, str] = {} @@ -83,7 +81,7 @@ class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_cities( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step where the user choose the city from the API search results.""" if not user_input: if len(self.places) > 1 and self.source != SOURCE_IMPORT: diff --git a/homeassistant/components/meteoclimatic/config_flow.py b/homeassistant/components/meteoclimatic/config_flow.py index 49be3889ead..3067368319a 100644 --- a/homeassistant/components/meteoclimatic/config_flow.py +++ b/homeassistant/components/meteoclimatic/config_flow.py @@ -5,14 +5,14 @@ from meteoclimatic import MeteoclimaticClient from meteoclimatic.exceptions import MeteoclimaticError, StationNotFound import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from .const import CONF_STATION_CODE, DOMAIN _LOGGER = logging.getLogger(__name__) -class MeteoclimaticFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MeteoclimaticFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Meteoclimatic config flow.""" VERSION = 1 diff --git a/homeassistant/components/metoffice/config_flow.py b/homeassistant/components/metoffice/config_flow.py index 3cf3b0fcda0..0677ae5e44c 100644 --- a/homeassistant/components/metoffice/config_flow.py +++ b/homeassistant/components/metoffice/config_flow.py @@ -7,9 +7,10 @@ from typing import Any import datapoint import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from .const import DOMAIN @@ -18,9 +19,7 @@ from .helpers import fetch_site _LOGGER = logging.getLogger(__name__) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate that the user input allows us to connect to DataPoint. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -41,14 +40,14 @@ async def validate_input( return {"site_name": site.name} -class MetOfficeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MetOfficeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Met Office weather integration.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -87,5 +86,5 @@ class MetOfficeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/microbees/config_flow.py b/homeassistant/components/microbees/config_flow.py index fb0b5faa020..2eaed8dbafc 100644 --- a/homeassistant/components/microbees/config_flow.py +++ b/homeassistant/components/microbees/config_flow.py @@ -7,7 +7,6 @@ from microBeesPy import MicroBees, MicroBeesException from homeassistant import config_entries from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow from .const import DOMAIN @@ -32,7 +31,9 @@ class OAuth2FlowHandler( scopes = ["read", "write"] return {"scope": " ".join(scopes)} - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry( + self, data: dict[str, Any] + ) -> config_entries.ConfigFlowResult: """Create an oauth config entry or update existing entry for reauth.""" microbees = MicroBees( @@ -61,7 +62,9 @@ class OAuth2FlowHandler( return self.async_abort(reason="reauth_successful") return self.async_abort(reason="wrong_account") - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> config_entries.ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -70,7 +73,7 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") diff --git a/homeassistant/components/mikrotik/config_flow.py b/homeassistant/components/mikrotik/config_flow.py index 84b334c5f8f..d2a7c5673b0 100644 --- a/homeassistant/components/mikrotik/config_flow.py +++ b/homeassistant/components/mikrotik/config_flow.py @@ -6,7 +6,12 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -15,7 +20,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_ARP_PING, @@ -30,23 +34,23 @@ from .errors import CannotConnect, LoginError from .hub import get_api -class MikrotikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MikrotikFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Mikrotik config flow.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None + _reauth_entry: ConfigEntry | None @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> MikrotikOptionsFlowHandler: """Get the options flow for this handler.""" return MikrotikOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -78,7 +82,7 @@ class MikrotikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, data: Mapping[str, Any]) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -87,7 +91,7 @@ class MikrotikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} assert self._reauth_entry @@ -122,22 +126,22 @@ class MikrotikFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class MikrotikOptionsFlowHandler(config_entries.OptionsFlow): +class MikrotikOptionsFlowHandler(OptionsFlow): """Handle Mikrotik options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Mikrotik options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Mikrotik options.""" return await self.async_step_device_tracker() async def async_step_device_tracker( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the device tracker options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/mill/config_flow.py b/homeassistant/components/mill/config_flow.py index 9f7dd5d5cdb..ce99f6cab65 100644 --- a/homeassistant/components/mill/config_flow.py +++ b/homeassistant/components/mill/config_flow.py @@ -3,14 +3,14 @@ from mill import Mill from mill_local import Mill as MillLocal import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CLOUD, CONNECTION_TYPE, DOMAIN, LOCAL -class MillConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MillConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Mill integration.""" VERSION = 1 diff --git a/homeassistant/components/minecraft_server/config_flow.py b/homeassistant/components/minecraft_server/config_flow.py index 022b7ed3991..b870c032b1e 100644 --- a/homeassistant/components/minecraft_server/config_flow.py +++ b/homeassistant/components/minecraft_server/config_flow.py @@ -6,9 +6,8 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_TYPE -from homeassistant.data_entry_flow import FlowResult from .api import MinecraftServer, MinecraftServerAddressError, MinecraftServerType from .const import DEFAULT_NAME, DOMAIN @@ -25,7 +24,7 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -66,7 +65,7 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" if user_input is None: user_input = {} diff --git a/homeassistant/components/mjpeg/config_flow.py b/homeassistant/components/mjpeg/config_flow.py index 024766f4c63..c5c3938aff4 100644 --- a/homeassistant/components/mjpeg/config_flow.py +++ b/homeassistant/components/mjpeg/config_flow.py @@ -10,7 +10,12 @@ from requests.auth import HTTPBasicAuth, HTTPDigestAuth from requests.exceptions import HTTPError, Timeout import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_AUTHENTICATION, CONF_NAME, @@ -21,7 +26,6 @@ from homeassistant.const import ( HTTP_DIGEST_AUTHENTICATION, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, DOMAIN, LOGGER @@ -140,7 +144,7 @@ class MJPEGFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} @@ -184,7 +188,7 @@ class MJPEGOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage MJPEG IP Camera options.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/moat/config_flow.py b/homeassistant/components/moat/config_flow.py index 4e522a81c73..ce400b6851d 100644 --- a/homeassistant/components/moat/config_flow.py +++ b/homeassistant/components/moat/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class MoatConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class MoatConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class MoatConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/mobile_app/config_flow.py b/homeassistant/components/mobile_app/config_flow.py index 246c433672b..46b0c9ba09f 100644 --- a/homeassistant/components/mobile_app/config_flow.py +++ b/homeassistant/components/mobile_app/config_flow.py @@ -1,15 +1,15 @@ """Config flow for Mobile App.""" import uuid -from homeassistant import config_entries from homeassistant.components import person +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ATTR_DEVICE_ID from homeassistant.helpers import entity_registry as er from .const import ATTR_APP_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN -class MobileAppFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MobileAppFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Mobile App config flow.""" VERSION = 1 diff --git a/homeassistant/components/modem_callerid/config_flow.py b/homeassistant/components/modem_callerid/config_flow.py index fac20073fe9..52bbf51fc61 100644 --- a/homeassistant/components/modem_callerid/config_flow.py +++ b/homeassistant/components/modem_callerid/config_flow.py @@ -8,10 +8,9 @@ import serial.tools.list_ports from serial.tools.list_ports_common import ListPortInfo import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import usb +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DOMAIN, EXCEPTIONS @@ -23,14 +22,16 @@ def _generate_unique_id(port: ListPortInfo) -> str: return f"{port.vid}:{port.pid}_{port.serial_number}_{port.manufacturer}_{port.description}" -class PhoneModemFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class PhoneModemFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Phone Modem.""" def __init__(self) -> None: """Set up flow instance.""" self._device: str | None = None - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle USB Discovery.""" dev_path = discovery_info.device unique_id = f"{discovery_info.vid}:{discovery_info.pid}_{discovery_info.serial_number}_{discovery_info.manufacturer}_{discovery_info.description}" @@ -44,7 +45,7 @@ class PhoneModemFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_usb_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle USB Discovery confirmation.""" if user_input is not None: return self.async_create_entry( @@ -56,7 +57,7 @@ class PhoneModemFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors: dict[str, str] | None = {} if self._async_in_progress(): diff --git a/homeassistant/components/modern_forms/config_flow.py b/homeassistant/components/modern_forms/config_flow.py index f8f3e2f1dc1..80e8bf5cf77 100644 --- a/homeassistant/components/modern_forms/config_flow.py +++ b/homeassistant/components/modern_forms/config_flow.py @@ -7,9 +7,8 @@ from aiomodernforms import ModernFormsConnectionError, ModernFormsDevice import voluptuous as vol from homeassistant.components import zeroconf -from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow +from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -22,13 +21,13 @@ class ModernFormsFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle setup by user for Modern Forms integration.""" return await self._handle_config_flow(user_input) async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" host = discovery_info.hostname.rstrip(".") name, _ = host.rsplit(".") @@ -47,13 +46,13 @@ class ModernFormsFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" return await self._handle_config_flow(user_input) async def _handle_config_flow( self, user_input: dict[str, Any] | None = None, prepare: bool = False - ) -> FlowResult: + ) -> ConfigFlowResult: """Config flow handler for ModernForms.""" source = self.context.get("source") @@ -97,7 +96,7 @@ class ModernFormsFlowHandler(ConfigFlow, domain=DOMAIN): data={CONF_HOST: user_input[CONF_HOST], CONF_MAC: user_input[CONF_MAC]}, ) - def _show_setup_form(self, errors: dict | None = None) -> FlowResult: + def _show_setup_form(self, errors: dict | None = None) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -105,7 +104,7 @@ class ModernFormsFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors or {}, ) - def _show_confirm_dialog(self, errors: dict | None = None) -> FlowResult: + def _show_confirm_dialog(self, errors: dict | None = None) -> ConfigFlowResult: """Show the confirm dialog to the user.""" name = self.context.get(CONF_NAME) return self.async_show_form( diff --git a/homeassistant/components/moehlenhoff_alpha2/config_flow.py b/homeassistant/components/moehlenhoff_alpha2/config_flow.py index a4bdfd71cce..64cad25f461 100644 --- a/homeassistant/components/moehlenhoff_alpha2/config_flow.py +++ b/homeassistant/components/moehlenhoff_alpha2/config_flow.py @@ -6,9 +6,8 @@ import aiohttp from moehlenhoff_alpha2 import Alpha2Base import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -43,7 +42,7 @@ class Alpha2BaseConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/monoprice/config_flow.py b/homeassistant/components/monoprice/config_flow.py index 9c659d4f733..43286946b7d 100644 --- a/homeassistant/components/monoprice/config_flow.py +++ b/homeassistant/components/monoprice/config_flow.py @@ -7,8 +7,10 @@ from pymonoprice import get_monoprice from serial import SerialException import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_PORT +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from .const import ( CONF_SOURCE_1, @@ -37,7 +39,7 @@ OPTIONS_FOR_DATA = {vol.Optional(source): str for source in SOURCES} DATA_SCHEMA = vol.Schema({vol.Required(CONF_PORT): str, **OPTIONS_FOR_DATA}) -@core.callback +@callback def _sources_from_config(data): sources_config = { str(idx + 1): data.get(source) for idx, source in enumerate(SOURCES) @@ -50,7 +52,7 @@ def _sources_from_config(data): } -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -67,7 +69,7 @@ async def validate_input(hass: core.HomeAssistant, data): return {CONF_PORT: data[CONF_PORT], CONF_SOURCES: sources} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MonoPriceConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Monoprice 6-Zone Amplifier.""" VERSION = 1 @@ -91,15 +93,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) @staticmethod - @core.callback + @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> MonopriceOptionsFlowHandler: """Define the config flow to handle options.""" return MonopriceOptionsFlowHandler(config_entry) -@core.callback +@callback def _key_for_source(index, source, previous_sources): if str(index) in previous_sources: key = vol.Optional( @@ -111,14 +113,14 @@ def _key_for_source(index, source, previous_sources): return key -class MonopriceOptionsFlowHandler(config_entries.OptionsFlow): +class MonopriceOptionsFlowHandler(OptionsFlow): """Handle a Monoprice options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry - @core.callback + @callback def _previous_sources(self): if CONF_SOURCES in self.config_entry.options: previous = self.config_entry.options[CONF_SOURCES] @@ -147,5 +149,5 @@ class MonopriceOptionsFlowHandler(config_entries.OptionsFlow): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/moon/config_flow.py b/homeassistant/components/moon/config_flow.py index 08b2a4995f1..76f1850d6e7 100644 --- a/homeassistant/components/moon/config_flow.py +++ b/homeassistant/components/moon/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DEFAULT_NAME, DOMAIN @@ -16,7 +15,7 @@ class MoonConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/mopeka/config_flow.py b/homeassistant/components/mopeka/config_flow.py index 54a2e7bcaf3..1bd79c1a20d 100644 --- a/homeassistant/components/mopeka/config_flow.py +++ b/homeassistant/components/mopeka/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class MopekaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class MopekaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class MopekaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/motion_blinds/config_flow.py b/homeassistant/components/motion_blinds/config_flow.py index 588d470bb6c..0d63a2e413d 100644 --- a/homeassistant/components/motion_blinds/config_flow.py +++ b/homeassistant/components/motion_blinds/config_flow.py @@ -6,11 +6,15 @@ from typing import Any from motionblinds import MotionDiscovery, MotionGateway import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_HOST from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import ( @@ -30,16 +34,16 @@ CONFIG_SCHEMA = vol.Schema( ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Options for the component.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init object.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors: dict[str, str] = {} if user_input is not None: @@ -61,7 +65,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MotionBlindsFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Motionblinds config flow.""" VERSION = 1 @@ -75,12 +79,14 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow.""" return OptionsFlowHandler(config_entry) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" mac_address = format_mac(discovery_info.macaddress).replace(":", "") await self.async_set_unique_id(mac_address) @@ -107,7 +113,7 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -136,7 +142,7 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle multiple motion gateways found.""" if user_input is not None: self._host = user_input["select_ip"] @@ -148,7 +154,7 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_connect( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Connect to the Motion Gateway.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/motioneye/config_flow.py b/homeassistant/components/motioneye/config_flow.py index 4ab4761fe0b..fba66c45b8b 100644 --- a/homeassistant/components/motioneye/config_flow.py +++ b/homeassistant/components/motioneye/config_flow.py @@ -16,11 +16,11 @@ from homeassistant.config_entries import ( SOURCE_REAUTH, ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlow, ) from homeassistant.const import CONF_SOURCE, CONF_URL, CONF_WEBHOOK_ID from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -47,12 +47,12 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" def _get_form( user_input: dict[str, Any], errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the form to the user.""" url_schema: dict[vol.Required, type[str]] = {} if not self._hassio_discovery: @@ -157,11 +157,15 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN): data=user_input, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthentication flow.""" return await self.async_step_user() - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Handle Supervisor discovery.""" self._hassio_discovery = discovery_info.config await self._async_handle_discovery_without_unique_id() @@ -170,7 +174,7 @@ class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm Supervisor discovery.""" if user_input is None and self._hassio_discovery is not None: return self.async_show_form( @@ -196,7 +200,7 @@ class MotionEyeOptionsFlow(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/motionmount/config_flow.py b/homeassistant/components/motionmount/config_flow.py index a593b30201e..3b0c8ffd830 100644 --- a/homeassistant/components/motionmount/config_flow.py +++ b/homeassistant/components/motionmount/config_flow.py @@ -6,10 +6,13 @@ from typing import Any import motionmount import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + DEFAULT_DISCOVERY_UNIQUE_ID, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_UUID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import DOMAIN, EMPTY_MAC @@ -23,7 +26,7 @@ _LOGGER = logging.getLogger(__name__) # 3. New CE but old Pro FW -> It doesn't supply the mac using DNS-SD but we can read it (returning the EMPTY_MAC) # 4. New CE and new Pro FW -> Both DNS-SD and a read gives us the mac # If we can't get the mac, we use DEFAULT_DISCOVERY_UNIQUE_ID as an ID, so we can always configure a single MotionMount. Most households will only have a single MotionMount -class MotionMountFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MotionMountFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Vogel's MotionMount config flow.""" VERSION = 1 @@ -34,7 +37,7 @@ class MotionMountFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return self._show_setup_form() @@ -55,13 +58,13 @@ class MotionMountFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_configured") # Otherwise we try to continue with the generic uid - info[CONF_UUID] = config_entries.DEFAULT_DISCOVERY_UNIQUE_ID + info[CONF_UUID] = DEFAULT_DISCOVERY_UNIQUE_ID # If the device mac is valid we use it, otherwise we use the default id if info.get(CONF_UUID, EMPTY_MAC) != EMPTY_MAC: unique_id = info[CONF_UUID] else: - unique_id = config_entries.DEFAULT_DISCOVERY_UNIQUE_ID + unique_id = DEFAULT_DISCOVERY_UNIQUE_ID name = info.get(CONF_NAME, user_input[CONF_HOST]) @@ -77,7 +80,7 @@ class MotionMountFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" # Extract information from discovery @@ -137,7 +140,7 @@ class MotionMountFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a confirmation flow initiated by zeroconf.""" if user_input is None: return self.async_show_form( @@ -162,7 +165,9 @@ class MotionMountFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return {CONF_UUID: format_mac(mm.mac.hex()), CONF_NAME: mm.name} - def _show_setup_form(self, errors: dict[str, str] | None = None) -> FlowResult: + def _show_setup_form( + self, errors: dict[str, str] | None = None + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 4f46dffec11..96ee6596f5a 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -14,7 +14,12 @@ import voluptuous as vol from homeassistant.components.file_upload import process_uploaded_file from homeassistant.components.hassio import HassioServiceInfo -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_CLIENT_ID, CONF_DISCOVERY, @@ -26,7 +31,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.json import json_dumps from homeassistant.helpers.selector import ( @@ -171,7 +175,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -180,7 +184,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_broker( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the setup.""" errors: dict[str, str] = {} fields: OrderedDict[Any, Any] = OrderedDict() @@ -211,7 +215,9 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): step_id="broker", data_schema=vol.Schema(fields), errors=errors ) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Receive a Hass.io discovery.""" await self._async_handle_discovery_without_unique_id() @@ -221,7 +227,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm a Hass.io discovery.""" errors: dict[str, str] = {} if TYPE_CHECKING: @@ -265,13 +271,13 @@ class MQTTOptionsFlowHandler(OptionsFlow): self.broker_config: dict[str, str | int] = {} self.options = config_entry.options - async def async_step_init(self, user_input: None = None) -> FlowResult: + async def async_step_init(self, user_input: None = None) -> ConfigFlowResult: """Manage the MQTT options.""" return await self.async_step_broker() async def async_step_broker( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the MQTT broker configuration.""" errors: dict[str, str] = {} fields: OrderedDict[Any, Any] = OrderedDict() @@ -304,7 +310,7 @@ class MQTTOptionsFlowHandler(OptionsFlow): async def async_step_options( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the MQTT options.""" errors = {} current_config = self.config_entry.data diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py index ad045dbb54c..f576dbc08af 100644 --- a/homeassistant/components/mullvad/config_flow.py +++ b/homeassistant/components/mullvad/config_flow.py @@ -1,18 +1,17 @@ """Config flow for Mullvad VPN integration.""" from mullvad_api import MullvadAPI, MullvadAPIError -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MullvadConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Mullvad VPN.""" VERSION = 1 - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle the initial step.""" self._async_abort_entries_match() diff --git a/homeassistant/components/mutesync/config_flow.py b/homeassistant/components/mutesync/config_flow.py index 21bbcfe69bb..75c15ff2f50 100644 --- a/homeassistant/components/mutesync/config_flow.py +++ b/homeassistant/components/mutesync/config_flow.py @@ -8,9 +8,8 @@ import aiohttp import mutesync import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -38,14 +37,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return token -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MuteSyncConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for mütesync.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/myq/config_flow.py b/homeassistant/components/myq/config_flow.py index 27bb1c4b9e5..46b3d7afcc5 100644 --- a/homeassistant/components/myq/config_flow.py +++ b/homeassistant/components/myq/config_flow.py @@ -1,11 +1,11 @@ """Config flow for MyQ integration.""" -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from . import DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MyQConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for MyQ.""" VERSION = 1 diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index fdf056c6c06..c4926696643 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -11,16 +11,14 @@ from awesomeversion import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.mqtt import ( DOMAIN as MQTT_DOMAIN, valid_publish_topic, valid_subscribe_topic, ) -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector import homeassistant.helpers.config_validation as cv @@ -120,7 +118,7 @@ def _is_same_device( return True -class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class MySensorsConfigFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" def __init__(self) -> None: @@ -129,13 +127,13 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create a config entry from frontend user input.""" return await self.async_step_select_gateway_type() async def async_step_select_gateway_type( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the select gateway type menu.""" return self.async_show_menu( step_id="select_gateway_type", @@ -144,7 +142,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_gw_serial( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create config entry for a serial gateway.""" gw_type = self._gw_type = CONF_GATEWAY_TYPE_SERIAL errors: dict[str, str] = {} @@ -173,7 +171,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_gw_tcp( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create a config entry for a tcp gateway.""" gw_type = self._gw_type = CONF_GATEWAY_TYPE_TCP errors: dict[str, str] = {} @@ -207,7 +205,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_gw_mqtt( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create a config entry for a mqtt gateway.""" # Naive check that doesn't consider config entry state. if MQTT_DOMAIN not in self.hass.config.components: @@ -262,7 +260,7 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry(self, user_input: dict[str, Any]) -> FlowResult: + def _async_create_entry(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Create the config entry.""" return self.async_create_entry( title=f"{user_input[CONF_DEVICE]}", diff --git a/homeassistant/components/mystrom/config_flow.py b/homeassistant/components/mystrom/config_flow.py index 6b2fe85bfe8..dc603a16c34 100644 --- a/homeassistant/components/mystrom/config_flow.py +++ b/homeassistant/components/mystrom/config_flow.py @@ -8,9 +8,8 @@ import pymystrom from pymystrom.exceptions import MyStromConnectionError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -26,14 +25,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class MyStromConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for myStrom.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/myuplink/config_flow.py b/homeassistant/components/myuplink/config_flow.py index c108aa00ebe..fee571effa6 100644 --- a/homeassistant/components/myuplink/config_flow.py +++ b/homeassistant/components/myuplink/config_flow.py @@ -3,8 +3,7 @@ from collections.abc import Mapping import logging from typing import Any -from homeassistant.config_entries import ConfigEntry -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN, OAUTH2_SCOPES @@ -29,7 +28,9 @@ class OAuth2FlowHandler( """Extra data that needs to be appended to the authorize url.""" return {"scope": " ".join(OAUTH2_SCOPES)} - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.config_entry_reauth = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -38,7 +39,7 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form( @@ -47,7 +48,7 @@ class OAuth2FlowHandler( return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict) -> FlowResult: + async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: """Create or update the config entry.""" if self.config_entry_reauth: return self.async_update_reload_and_abort( From e0c1feb22c35ae130d54ea9e5e779432d7fe4a61 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 20:09:01 +0100 Subject: [PATCH 0063/1691] Migrate integrations n-r to generic flowhandler (#111864) --- homeassistant/components/nam/config_flow.py | 21 +++++----- .../components/nanoleaf/config_flow.py | 31 +++++++------- homeassistant/components/neato/config_flow.py | 13 +++--- homeassistant/components/nest/config_flow.py | 25 ++++++----- .../components/netatmo/config_flow.py | 39 +++++++++-------- .../components/netgear/config_flow.py | 20 +++++---- .../components/netgear_lte/config_flow.py | 12 +++--- homeassistant/components/nexia/config_flow.py | 12 +++--- .../components/nextbus/config_flow.py | 15 ++++--- .../components/nextcloud/config_flow.py | 11 ++--- .../components/nextdns/config_flow.py | 9 ++-- .../components/nfandroidtv/config_flow.py | 7 ++-- .../components/nibe_heatpump/config_flow.py | 11 +++-- .../components/nightscout/config_flow.py | 10 ++--- homeassistant/components/nina/config_flow.py | 20 +++++---- .../components/nmap_tracker/config_flow.py | 19 +++++---- .../components/nobo_hub/config_flow.py | 28 +++++++------ .../components/notion/config_flow.py | 14 +++---- .../components/nuheat/config_flow.py | 14 ++++--- homeassistant/components/nuki/config_flow.py | 13 +++--- homeassistant/components/nut/config_flow.py | 20 +++++---- homeassistant/components/nws/config_flow.py | 15 ++++--- .../components/nzbget/config_flow.py | 5 +-- .../components/obihai/config_flow.py | 11 ++--- .../components/octoprint/config_flow.py | 19 +++++---- .../components/omnilogic/config_flow.py | 10 ++--- homeassistant/components/oncue/config_flow.py | 7 ++-- .../components/onewire/config_flow.py | 10 ++--- homeassistant/components/onvif/config_flow.py | 34 +++++++++------ .../components/open_meteo/config_flow.py | 5 +-- .../openai_conversation/config_flow.py | 24 ++++++----- .../openexchangerates/config_flow.py | 14 ++++--- .../components/opengarage/config_flow.py | 7 ++-- .../components/openhome/config_flow.py | 9 ++-- .../components/opensky/config_flow.py | 6 +-- .../components/opentherm_gw/config_flow.py | 10 ++--- .../components/openuv/config_flow.py | 16 +++---- .../components/openweathermap/config_flow.py | 10 ++--- .../components/opower/config_flow.py | 19 +++++---- homeassistant/components/oralb/config_flow.py | 9 ++-- .../components/osoenergy/config_flow.py | 22 +++++----- homeassistant/components/otbr/config_flow.py | 9 ++-- .../components/ourgroceries/config_flow.py | 7 ++-- .../components/overkiz/config_flow.py | 26 ++++++------ .../components/ovo_energy/config_flow.py | 7 ++-- .../components/owntracks/config_flow.py | 4 +- .../components/p1_monitor/config_flow.py | 5 +-- .../components/panasonic_viera/config_flow.py | 4 +- homeassistant/components/peco/config_flow.py | 9 ++-- .../components/pegel_online/config_flow.py | 11 +++-- .../components/permobil/config_flow.py | 15 +++---- .../components/philips_js/config_flow.py | 24 ++++++----- .../components/pi_hole/config_flow.py | 15 +++---- .../components/picnic/config_flow.py | 18 ++++---- homeassistant/components/ping/config_flow.py | 26 +++++++----- .../components/plaato/config_flow.py | 7 ++-- homeassistant/components/plex/config_flow.py | 26 ++++++++---- .../components/plugwise/config_flow.py | 7 ++-- .../components/plum_lightpad/config_flow.py | 7 ++-- homeassistant/components/point/config_flow.py | 4 +- .../components/poolsense/config_flow.py | 7 ++-- .../components/powerwall/config_flow.py | 42 +++++++++++-------- .../private_ble_device/config_flow.py | 5 +-- .../components/profiler/config_flow.py | 4 +- .../components/progettihwsw/config_flow.py | 14 ++++--- .../components/prosegur/config_flow.py | 20 +++++---- .../components/proximity/config_flow.py | 14 ++++--- .../components/prusalink/config_flow.py | 7 ++-- homeassistant/components/ps4/config_flow.py | 4 +- .../components/pure_energie/config_flow.py | 9 ++-- .../components/purpleair/config_flow.py | 35 +++++++++------- .../components/pushbullet/config_flow.py | 7 ++-- .../components/pushover/config_flow.py | 15 +++---- .../components/pvoutput/config_flow.py | 11 ++--- .../pvpc_hourly_pricing/config_flow.py | 34 +++++++++------ .../components/qbittorrent/config_flow.py | 5 +-- .../components/qingping/config_flow.py | 9 ++-- homeassistant/components/qnap/config_flow.py | 7 ++-- .../components/qnap_qsw/config_flow.py | 14 ++++--- .../components/rabbitair/config_flow.py | 5 +-- .../components/rachio/config_flow.py | 27 +++++++----- .../components/radarr/config_flow.py | 9 ++-- .../components/radio_browser/config_flow.py | 7 ++-- .../components/radiotherm/config_flow.py | 11 ++--- .../components/rainbird/config_flow.py | 19 +++++---- .../rainforest_eagle/config_flow.py | 7 ++-- .../rainforest_raven/config_flow.py | 13 +++--- .../components/rainmachine/config_flow.py | 23 +++++----- .../components/rapt_ble/config_flow.py | 9 ++-- .../components/raspberry_pi/config_flow.py | 7 ++-- homeassistant/components/rdw/config_flow.py | 5 +-- .../components/recollect_waste/config_flow.py | 22 ++++++---- .../components/renault/config_flow.py | 19 +++++---- .../components/renson/config_flow.py | 7 ++-- .../components/reolink/config_flow.py | 29 ++++++++----- homeassistant/components/repairs/models.py | 2 +- .../components/rfxtrx/config_flow.py | 31 ++++++++------ .../components/rhasspy/config_flow.py | 7 ++-- .../components/ridwell/config_flow.py | 17 ++++---- homeassistant/components/ring/config_flow.py | 20 +++++---- homeassistant/components/risco/config_flow.py | 41 ++++++++++-------- .../rituals_perfume_genie/config_flow.py | 7 ++-- .../components/roborock/config_flow.py | 18 ++++---- homeassistant/components/roku/config_flow.py | 15 +++---- homeassistant/components/romy/config_flow.py | 11 +++-- .../components/roomba/config_flow.py | 30 ++++++++----- homeassistant/components/roon/config_flow.py | 10 +++-- .../components/rpi_power/config_flow.py | 4 +- .../components/rtsp_to_webrtc/config_flow.py | 28 ++++++++----- .../ruckus_unleashed/config_flow.py | 21 ++++++---- .../components/ruuvi_gateway/config_flow.py | 13 +++--- .../components/ruuvitag_ble/config_flow.py | 9 ++-- .../components/rympro/config_flow.py | 13 +++--- 113 files changed, 890 insertions(+), 746 deletions(-) diff --git a/homeassistant/components/nam/config_flow.py b/homeassistant/components/nam/config_flow.py index 8f44c28df3a..c91f08b743a 100644 --- a/homeassistant/components/nam/config_flow.py +++ b/homeassistant/components/nam/config_flow.py @@ -17,11 +17,10 @@ from nettigo_air_monitor import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac @@ -70,7 +69,7 @@ async def async_check_credentials( await nam.async_check_credentials() -class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NAMFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for Nettigo Air Monitor.""" VERSION = 1 @@ -78,12 +77,12 @@ class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize flow.""" self.host: str - self.entry: config_entries.ConfigEntry + self.entry: ConfigEntry self._config: NamConfig async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} @@ -119,7 +118,7 @@ class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_credentials( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the credentials step.""" errors: dict[str, str] = {} @@ -145,7 +144,7 @@ class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self.host = discovery_info.host self.context["title_placeholders"] = {"host": self.host} @@ -167,7 +166,7 @@ class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery confirm.""" errors: dict[str, str] = {} @@ -188,7 +187,9 @@ class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" if entry := self.hass.config_entries.async_get_entry(self.context["entry_id"]): self.entry = entry @@ -198,7 +199,7 @@ class NAMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/nanoleaf/config_flow.py b/homeassistant/components/nanoleaf/config_flow.py index 87239f5fd80..d4948507a42 100644 --- a/homeassistant/components/nanoleaf/config_flow.py +++ b/homeassistant/components/nanoleaf/config_flow.py @@ -9,10 +9,9 @@ from typing import Any, Final, cast from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp, zeroconf +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.json import save_json from homeassistant.util.json import JsonObjectType, JsonValueType, load_json_object @@ -31,10 +30,10 @@ USER_SCHEMA: Final = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NanoleafConfigFlow(ConfigFlow, domain=DOMAIN): """Nanoleaf config flow.""" - reauth_entry: config_entries.ConfigEntry | None = None + reauth_entry: ConfigEntry | None = None nanoleaf: Nanoleaf @@ -46,7 +45,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Nanoleaf flow initiated by the user.""" if user_input is None: return self.async_show_form( @@ -77,10 +76,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self.async_step_link() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle Nanoleaf reauth flow if token is invalid.""" self.reauth_entry = cast( - config_entries.ConfigEntry, + ConfigEntry, self.hass.config_entries.async_get_entry(self.context["entry_id"]), ) self.nanoleaf = Nanoleaf( @@ -91,21 +92,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Nanoleaf Zeroconf discovery.""" _LOGGER.debug("Zeroconf discovered: %s", discovery_info) return await self._async_homekit_zeroconf_discovery_handler(discovery_info) async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Nanoleaf Homekit discovery.""" _LOGGER.debug("Homekit discovered: %s", discovery_info) return await self._async_homekit_zeroconf_discovery_handler(discovery_info) async def _async_homekit_zeroconf_discovery_handler( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Nanoleaf Homekit and Zeroconf discovery.""" return await self._async_discovery_handler( discovery_info.host, @@ -113,7 +114,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): discovery_info.properties[zeroconf.ATTR_PROPERTIES_ID], ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle Nanoleaf SSDP discovery.""" _LOGGER.debug("SSDP discovered: %s", discovery_info) return await self._async_discovery_handler( @@ -124,7 +127,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_discovery_handler( self, host: str, name: str, device_id: str - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Nanoleaf discovery.""" # The name is unique and printed on the device and cannot be changed. await self.async_set_unique_id(name) @@ -156,7 +159,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle Nanoleaf link step.""" if user_input is None: return self.async_show_form(step_id="link") @@ -188,7 +191,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_setup_finish( self, discovery_integration_import: bool = False - ) -> FlowResult: + ) -> ConfigFlowResult: """Finish Nanoleaf config flow.""" try: await self.nanoleaf.get_info() diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 6b31cf9c05d..89a8639009f 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -5,8 +5,7 @@ from collections.abc import Mapping import logging from typing import Any -from homeassistant.config_entries import SOURCE_REAUTH -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import NEATO_DOMAIN @@ -26,7 +25,7 @@ class OAuth2FlowHandler( async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create an entry for the flow.""" current_entries = self._async_current_entries() if self.source != SOURCE_REAUTH and current_entries: @@ -35,19 +34,21 @@ class OAuth2FlowHandler( return await super().async_step_user(user_input=user_input) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon migration of old entries.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth upon migration of old entries.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow. Update an entry if one already exist.""" current_entries = self._async_current_entries() if self.source == SOURCE_REAUTH and current_entries: diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index 381cc36449d..8e05bc611c9 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -22,8 +22,7 @@ from google_nest_sdm.exceptions import ( from google_nest_sdm.structure import InfoTrait, Structure import voluptuous as vol -from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry, ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.util import get_random_string @@ -133,7 +132,7 @@ class NestFlowHandler( authorize_url = OAUTH2_AUTHORIZE.format(project_id=project_id) return f"{authorize_url}{query}" - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Complete OAuth setup and finish pubsub or finish.""" _LOGGER.debug("Finishing post-oauth configuration") self._data.update(data) @@ -142,7 +141,9 @@ class NestFlowHandler( return await self.async_step_finish() return await self.async_step_pubsub() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._data.update(entry_data) @@ -150,7 +151,7 @@ class NestFlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") @@ -158,7 +159,7 @@ class NestFlowHandler( async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" self._data[DATA_SDM] = {} if self.source == SOURCE_REAUTH: @@ -169,7 +170,7 @@ class NestFlowHandler( async def async_step_create_cloud_project( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle initial step in app credentails flow.""" implementations = await config_entry_oauth2_flow.async_get_implementations( self.hass, self.DOMAIN @@ -196,7 +197,7 @@ class NestFlowHandler( async def async_step_cloud_project( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle cloud project in user input.""" if user_input is not None: self._data.update(user_input) @@ -216,7 +217,7 @@ class NestFlowHandler( async def async_step_device_project( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Collect device access project from user input.""" errors = {} if user_input is not None: @@ -249,7 +250,7 @@ class NestFlowHandler( async def async_step_pubsub( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure and create Pub/Sub subscriber.""" data = { **self._data, @@ -313,7 +314,9 @@ class NestFlowHandler( errors=errors, ) - async def async_step_finish(self, data: dict[str, Any] | None = None) -> FlowResult: + async def async_step_finish( + self, data: dict[str, Any] | None = None + ) -> ConfigFlowResult: """Create an entry for the SDM flow.""" _LOGGER.debug("Creating/updating configuration entry") # Update existing config entry when in the reauth flow. diff --git a/homeassistant/components/netatmo/config_flow.py b/homeassistant/components/netatmo/config_flow.py index bae81a7762f..b2e24420d11 100644 --- a/homeassistant/components/netatmo/config_flow.py +++ b/homeassistant/components/netatmo/config_flow.py @@ -8,10 +8,14 @@ import uuid import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_SHOW_ON_MAP, CONF_UUID from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from .api import get_api_scopes @@ -40,8 +44,8 @@ class NetatmoFlowHandler( @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" return NetatmoOptionsFlowHandler(config_entry) @@ -56,32 +60,31 @@ class NetatmoFlowHandler( scopes = get_api_scopes(self.flow_impl.domain) return {"scope": " ".join(scopes)} - async def async_step_user(self, user_input: dict | None = None) -> FlowResult: + async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult: """Handle a flow start.""" await self.async_set_unique_id(DOMAIN) - if ( - self.source != config_entries.SOURCE_REAUTH - and self._async_current_entries() - ): + if self.source != SOURCE_REAUTH and self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") return await super().async_step_user(user_input) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict) -> FlowResult: + async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult: """Create an oauth config entry or update existing entry for reauth.""" existing_entry = await self.async_set_unique_id(DOMAIN) if existing_entry: @@ -92,22 +95,22 @@ class NetatmoFlowHandler( return await super().async_oauth_create_entry(data) -class NetatmoOptionsFlowHandler(config_entries.OptionsFlow): +class NetatmoOptionsFlowHandler(OptionsFlow): """Handle Netatmo options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Netatmo options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) self.options.setdefault(CONF_WEATHER_AREAS, {}) - async def async_step_init(self, user_input: dict | None = None) -> FlowResult: + async def async_step_init(self, user_input: dict | None = None) -> ConfigFlowResult: """Manage the Netatmo options.""" return await self.async_step_public_weather_areas() async def async_step_public_weather_areas( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage configuration of Netatmo public weather areas.""" errors: dict = {} @@ -142,7 +145,7 @@ class NetatmoOptionsFlowHandler(config_entries.OptionsFlow): errors=errors, ) - async def async_step_public_weather(self, user_input: dict) -> FlowResult: + async def async_step_public_weather(self, user_input: dict) -> ConfigFlowResult: """Manage configuration of Netatmo public weather sensors.""" if user_input is not None and CONF_NEW_AREA not in user_input: self.options[CONF_WEATHER_AREAS][ @@ -203,7 +206,7 @@ class NetatmoOptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="public_weather", data_schema=data_schema) - def _create_options_entry(self) -> FlowResult: + def _create_options_entry(self) -> ConfigFlowResult: """Update config entry options.""" return self.async_create_entry( title="Netatmo Public Weather", data=self.options diff --git a/homeassistant/components/netgear/config_flow.py b/homeassistant/components/netgear/config_flow.py index 7b74880d011..85982196cf2 100644 --- a/homeassistant/components/netgear/config_flow.py +++ b/homeassistant/components/netgear/config_flow.py @@ -8,8 +8,13 @@ from urllib.parse import urlparse from pynetgear import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_USER import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -18,7 +23,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.util.network import is_ipv4_address from .const import ( @@ -55,10 +59,10 @@ def _ordered_shared_schema(schema_input): } -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Options for the component.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init object.""" self.config_entry = config_entry @@ -81,7 +85,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=settings_schema) -class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NetgearFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -99,7 +103,7 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow.""" return OptionsFlowHandler(config_entry) @@ -121,7 +125,9 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=self.placeholders, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Initialize flow from ssdp.""" updated_data: dict[str, str | int | bool] = {} diff --git a/homeassistant/components/netgear_lte/config_flow.py b/homeassistant/components/netgear_lte/config_flow.py index a3a56bab03b..f0c731599f5 100644 --- a/homeassistant/components/netgear_lte/config_flow.py +++ b/homeassistant/components/netgear_lte/config_flow.py @@ -8,18 +8,18 @@ from eternalegypt import Error, Modem from eternalegypt.eternalegypt import Information import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_create_clientsession from .const import DEFAULT_HOST, DOMAIN, LOGGER, MANUFACTURER -class NetgearLTEFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NetgearLTEFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Netgear LTE.""" - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + async def async_step_import(self, config: dict[str, Any]) -> ConfigFlowResult: """Import a configuration from config.yaml.""" host = config[CONF_HOST] password = config[CONF_PASSWORD] @@ -37,7 +37,7 @@ class NetgearLTEFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -94,7 +94,7 @@ class NetgearLTEFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return info -class InputValidationError(exceptions.HomeAssistantError): +class InputValidationError(HomeAssistantError): """Error to indicate we cannot proceed due to invalid input.""" def __init__(self, base: str) -> None: diff --git a/homeassistant/components/nexia/config_flow.py b/homeassistant/components/nexia/config_flow.py index 46dc1454a2a..8112e019f0d 100644 --- a/homeassistant/components/nexia/config_flow.py +++ b/homeassistant/components/nexia/config_flow.py @@ -6,8 +6,10 @@ from nexia.const import BRAND_ASAIR, BRAND_NEXIA, BRAND_TRANE from nexia.home import NexiaHome import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -36,7 +38,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -73,7 +75,7 @@ async def validate_input(hass: core.HomeAssistant, data): return info -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NexiaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Nexia.""" VERSION = 1 @@ -102,9 +104,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/nextbus/config_flow.py b/homeassistant/components/nextbus/config_flow.py index a4045ada372..67bc8581e86 100644 --- a/homeassistant/components/nextbus/config_flow.py +++ b/homeassistant/components/nextbus/config_flow.py @@ -5,9 +5,8 @@ import logging from py_nextbus import NextBusClient import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME, CONF_STOP -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( SelectOptionDict, SelectSelector, @@ -89,7 +88,7 @@ def _unique_id_from_data(data: dict[str, str]) -> str: return f"{data[CONF_AGENCY]}_{data[CONF_ROUTE]}_{data[CONF_STOP]}" -class NextBusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NextBusFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Nextbus configuration.""" VERSION = 1 @@ -104,7 +103,7 @@ class NextBusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._client = NextBusClient(output_format="json") _LOGGER.info("Init new config flow") - async def async_step_import(self, config_input: dict[str, str]) -> FlowResult: + async def async_step_import(self, config_input: dict[str, str]) -> ConfigFlowResult: """Handle import of config.""" agency_tag = config_input[CONF_AGENCY] route_tag = config_input[CONF_ROUTE] @@ -141,14 +140,14 @@ class NextBusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" return await self.async_step_agency(user_input) async def async_step_agency( self, user_input: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Select agency.""" if user_input is not None: self.data[CONF_AGENCY] = user_input[CONF_AGENCY] @@ -173,7 +172,7 @@ class NextBusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_route( self, user_input: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Select route.""" if user_input is not None: self.data[CONF_ROUTE] = user_input[CONF_ROUTE] @@ -198,7 +197,7 @@ class NextBusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_stop( self, user_input: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Select stop.""" if user_input is not None: diff --git a/homeassistant/components/nextcloud/config_flow.py b/homeassistant/components/nextcloud/config_flow.py index ec56307aad7..f843a7d609e 100644 --- a/homeassistant/components/nextcloud/config_flow.py +++ b/homeassistant/components/nextcloud/config_flow.py @@ -12,9 +12,8 @@ from nextcloudmonitor import ( ) import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_VERIFY_SSL, DOMAIN @@ -52,7 +51,7 @@ class NextcloudConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -75,14 +74,16 @@ class NextcloudConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle flow upon an API authentication error.""" self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" errors = {} assert self._entry is not None diff --git a/homeassistant/components/nextdns/config_flow.py b/homeassistant/components/nextdns/config_flow.py index b0a1d936752..2a676f7a3a1 100644 --- a/homeassistant/components/nextdns/config_flow.py +++ b/homeassistant/components/nextdns/config_flow.py @@ -8,15 +8,14 @@ from aiohttp.client_exceptions import ClientConnectorError from nextdns import ApiError, InvalidApiKeyError, NextDns import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_PROFILE_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_PROFILE_ID, DOMAIN -class NextDnsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NextDnsFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for NextDNS.""" VERSION = 1 @@ -28,7 +27,7 @@ class NextDnsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} @@ -58,7 +57,7 @@ class NextDnsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_profiles( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the profiles step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/nfandroidtv/config_flow.py b/homeassistant/components/nfandroidtv/config_flow.py index d4491cee48e..3d0b07a2ea3 100644 --- a/homeassistant/components/nfandroidtv/config_flow.py +++ b/homeassistant/components/nfandroidtv/config_flow.py @@ -7,21 +7,20 @@ from typing import Any from notifications_android_tv.notifications import ConnectError, Notifications import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DOMAIN _LOGGER = logging.getLogger(__name__) -class NFAndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NFAndroidTVFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for NFAndroidTV.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} diff --git a/homeassistant/components/nibe_heatpump/config_flow.py b/homeassistant/components/nibe_heatpump/config_flow.py index 6680ca6e325..0ba80ad6754 100644 --- a/homeassistant/components/nibe_heatpump/config_flow.py +++ b/homeassistant/components/nibe_heatpump/config_flow.py @@ -17,10 +17,9 @@ from nibe.heatpump import HeatPump, Model import voluptuous as vol import yarl -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_MODEL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .const import ( @@ -166,20 +165,20 @@ async def validate_modbus_input( } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NibeHeatPumpConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Nibe Heat Pump.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" return self.async_show_menu(step_id="user", menu_options=["modbus", "nibegw"]) async def async_step_modbus( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the modbus step.""" if user_input is None: return self.async_show_form( @@ -205,7 +204,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_nibegw( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the nibegw step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/nightscout/config_flow.py b/homeassistant/components/nightscout/config_flow.py index 6249979c83d..032af8975eb 100644 --- a/homeassistant/components/nightscout/config_flow.py +++ b/homeassistant/components/nightscout/config_flow.py @@ -6,9 +6,9 @@ from aiohttp import ClientError, ClientResponseError from py_nightscout import Api as NightscoutAPI import voluptuous as vol -from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_URL -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN from .utils import hash_from_url @@ -36,14 +36,14 @@ async def _validate_input(data: dict[str, Any]) -> dict[str, str]: return {"title": status.name} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NightscoutConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Nightscout.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -66,7 +66,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class InputValidationError(exceptions.HomeAssistantError): +class InputValidationError(HomeAssistantError): """Error to indicate we cannot proceed due to invalid input.""" def __init__(self, base: str) -> None: diff --git a/homeassistant/components/nina/config_flow.py b/homeassistant/components/nina/config_flow.py index 9c6de40ac6b..0954ba26874 100644 --- a/homeassistant/components/nina/config_flow.py +++ b/homeassistant/components/nina/config_flow.py @@ -6,9 +6,13 @@ from typing import Any from pynina import ApiError, Nina import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_registry import ( @@ -81,7 +85,7 @@ def prepare_user_input( return user_input -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NinaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for NINA.""" VERSION: int = 1 @@ -96,9 +100,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.regions[name] = {} async def async_step_user( - self: ConfigFlow, + self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, Any] = {} @@ -158,16 +162,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for nut.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self.data = dict(self.config_entry.data) diff --git a/homeassistant/components/nmap_tracker/config_flow.py b/homeassistant/components/nmap_tracker/config_flow.py index a1afa1b1bba..40ad698b948 100644 --- a/homeassistant/components/nmap_tracker/config_flow.py +++ b/homeassistant/components/nmap_tracker/config_flow.py @@ -6,7 +6,6 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import network from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, @@ -14,10 +13,14 @@ from homeassistant.components.device_tracker import ( DEFAULT_CONSIDER_HOME, ) from homeassistant.components.network import MDNS_TARGET_IP -from homeassistant.config_entries import ConfigEntry, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_EXCLUDE, CONF_HOSTS from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import ( @@ -133,16 +136,16 @@ async def _async_build_schema_with_user_input( return vol.Schema(schema) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for homekit.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.options = dict(config_entry.options) async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" errors = {} if user_input is not None: @@ -163,7 +166,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NmapTrackerConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Nmap Tracker.""" VERSION = 1 @@ -174,7 +177,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/nobo_hub/config_flow.py b/homeassistant/components/nobo_hub/config_flow.py index f1e2dd7d9d2..20b61010ba9 100644 --- a/homeassistant/components/nobo_hub/config_flow.py +++ b/homeassistant/components/nobo_hub/config_flow.py @@ -7,10 +7,14 @@ from typing import Any from pynobo import nobo import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import ( @@ -26,7 +30,7 @@ DATA_NOBO_HUB_IMPL = "nobo_hub_flow_implementation" DEVICE_INPUT = "device_input" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NoboHubConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Nobø Ecohub.""" VERSION = 1 @@ -38,7 +42,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._discovered_hubs is None: self._discovered_hubs = dict(await nobo.async_discover_hubs()) @@ -67,7 +71,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_selected( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle configuration of a selected discovered device.""" errors = {} if user_input is not None: @@ -97,7 +101,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_manual( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle configuration of an undiscovered device.""" errors = {} if user_input is not None: @@ -124,7 +128,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _create_configuration( self, serial: str, ip_address: str, auto_discovered: bool - ) -> FlowResult: + ) -> ConfigFlowResult: await self.async_set_unique_id(serial) self._abort_if_unique_id_configured() name = await self._test_connection(serial, ip_address) @@ -164,8 +168,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) @@ -179,14 +183,14 @@ class NoboHubConnectError(HomeAssistantError): self.msg = msg -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handles options flow for the component.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize the options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: diff --git a/homeassistant/components/notion/config_flow.py b/homeassistant/components/notion/config_flow.py index f43c87b5085..d9464d50a83 100644 --- a/homeassistant/components/notion/config_flow.py +++ b/homeassistant/components/notion/config_flow.py @@ -8,11 +8,9 @@ from typing import Any from aionotion.errors import InvalidCredentialsError, NotionError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from .const import CONF_REFRESH_TOKEN, CONF_USER_UUID, DOMAIN, LOGGER from .util import async_get_client_with_credentials @@ -64,7 +62,7 @@ async def async_validate_credentials( ) -class NotionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class NotionFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Notion config flow.""" VERSION = 1 @@ -73,7 +71,9 @@ class NotionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize.""" self._reauth_entry: ConfigEntry | None = None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -82,7 +82,7 @@ class NotionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" assert self._reauth_entry @@ -121,7 +121,7 @@ class NotionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return self.async_show_form(step_id="user", data_schema=AUTH_SCHEMA) diff --git a/homeassistant/components/nuheat/config_flow.py b/homeassistant/components/nuheat/config_flow.py index 0959466244d..96610139a85 100644 --- a/homeassistant/components/nuheat/config_flow.py +++ b/homeassistant/components/nuheat/config_flow.py @@ -6,8 +6,10 @@ import nuheat import requests.exceptions import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import CONF_SERIAL_NUMBER, DOMAIN @@ -22,7 +24,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -56,7 +58,7 @@ async def validate_input(hass: core.HomeAssistant, data): return {"title": thermostat.room, "serial_number": thermostat.serial_number} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NuHeatConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for NuHeat.""" VERSION = 1 @@ -87,13 +89,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class InvalidThermostat(exceptions.HomeAssistantError): +class InvalidThermostat(HomeAssistantError): """Error to indicate there is invalid thermostat.""" diff --git a/homeassistant/components/nuki/config_flow.py b/homeassistant/components/nuki/config_flow.py index 4acfecf492b..a29ff75580a 100644 --- a/homeassistant/components/nuki/config_flow.py +++ b/homeassistant/components/nuki/config_flow.py @@ -8,10 +8,9 @@ from pynuki.bridge import InvalidCredentialsException from requests.exceptions import RequestException import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from .const import CONF_ENCRYPT_TOKEN, DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN from .helpers import CannotConnect, InvalidAuth, parse_id @@ -59,7 +58,7 @@ async def validate_input(hass, data): return info -class NukiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NukiConfigFlow(ConfigFlow, domain=DOMAIN): """Nuki config flow.""" def __init__(self): @@ -71,7 +70,9 @@ class NukiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a flow initiated by the user.""" return await self.async_step_validate(user_input) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a DHCP discovered Nuki bridge.""" await self.async_set_unique_id(discovery_info.hostname[12:].upper()) @@ -87,7 +88,9 @@ class NukiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_validate() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._data = entry_data diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 917f004ce32..782266a460a 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -7,9 +7,13 @@ from typing import Any import voluptuous as vol -from homeassistant import exceptions from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_ALIAS, CONF_BASE, @@ -20,7 +24,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from . import PyNUTData from .const import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN @@ -94,7 +98,7 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare configuration for a discovered nut device.""" self.discovery_info = discovery_info await self._async_handle_discovery_without_unique_id() @@ -106,7 +110,7 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user input.""" errors: dict[str, str] = {} if user_input is not None: @@ -136,7 +140,7 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_ups( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the picking the ups.""" errors: dict[str, str] = {} @@ -194,7 +198,7 @@ class OptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -212,5 +216,5 @@ class OptionsFlowHandler(OptionsFlow): return self.async_show_form(step_id="init", data_schema=vol.Schema(base_schema)) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/nws/config_flow.py b/homeassistant/components/nws/config_flow.py index 10eab390917..a6e14500ed2 100644 --- a/homeassistant/components/nws/config_flow.py +++ b/homeassistant/components/nws/config_flow.py @@ -8,9 +8,10 @@ import aiohttp from pynws import SimpleNWS import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -20,9 +21,7 @@ from .const import CONF_STATION, DOMAIN _LOGGER = logging.getLogger(__name__) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -45,14 +44,14 @@ async def validate_input( return {"title": nws.station} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class NWSConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for National Weather Service (NWS).""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -88,5 +87,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index 782ec791eeb..f528712d6a0 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -6,7 +6,7 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -16,7 +16,6 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN from .coordinator import NZBGetAPI, NZBGetAPIException @@ -48,7 +47,7 @@ class NZBGetConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/obihai/config_flow.py b/homeassistant/components/obihai/config_flow.py index 1790add84f0..559900db5d0 100644 --- a/homeassistant/components/obihai/config_flow.py +++ b/homeassistant/components/obihai/config_flow.py @@ -9,10 +9,9 @@ from pyobihai import PyObihai import voluptuous as vol from homeassistant.components import dhcp -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .connectivity import validate_auth @@ -59,7 +58,7 @@ class ObihaiFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} @@ -94,7 +93,9 @@ class ObihaiFlowHandler(ConfigFlow, domain=DOMAIN): data_schema=self.add_suggested_values_to_schema(data_schema, user_input), ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a DHCP discovered Obihai.""" self._dhcp_discovery_info = discovery_info @@ -102,7 +103,7 @@ class ObihaiFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_dhcp_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to confirm.""" assert self._dhcp_discovery_info await self.async_set_unique_id(format_mac(self._dhcp_discovery_info.macaddress)) diff --git a/homeassistant/components/octoprint/config_flow.py b/homeassistant/components/octoprint/config_flow.py index 01a3e9518c0..94c9e940fd3 100644 --- a/homeassistant/components/octoprint/config_flow.py +++ b/homeassistant/components/octoprint/config_flow.py @@ -11,8 +11,8 @@ from pyoctoprintapi import ApiError, OctoprintClient, OctoprintException import voluptuous as vol from yarl import URL -from homeassistant import config_entries, data_entry_flow, exceptions from homeassistant.components import ssdp, zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -22,7 +22,8 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.data_entry_flow import AbortFlow +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.util.ssl import get_default_context, get_default_no_verify_context @@ -47,7 +48,7 @@ def _schema_with_defaults( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OctoPrintConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for OctoPrint.""" VERSION = 1 @@ -76,7 +77,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} try: return await self._finish_config(user_input) - except data_entry_flow.AbortFlow as err: + except AbortFlow as err: raise err from None except CannotConnect: errors["base"] = "cannot_connect" @@ -160,7 +161,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle discovery flow.""" uuid = discovery_info.properties["uuid"] await self.async_set_unique_id(uuid) @@ -186,7 +187,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_ssdp( self, discovery_info: ssdp.SsdpServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle ssdp discovery flow.""" uuid = discovery_info.upnp["UDN"][5:] await self.async_set_unique_id(uuid) @@ -209,7 +210,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() - async def async_step_reauth(self, config: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, config: Mapping[str, Any]) -> ConfigFlowResult: """Handle reauthorization request from Octoprint.""" self._reauth_data = dict(config) @@ -223,7 +224,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauthorization flow.""" assert self._reauth_data is not None @@ -279,5 +280,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): session.detach() -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/omnilogic/config_flow.py b/homeassistant/components/omnilogic/config_flow.py index 1635eaa7558..76c42b19866 100644 --- a/homeassistant/components/omnilogic/config_flow.py +++ b/homeassistant/components/omnilogic/config_flow.py @@ -6,7 +6,7 @@ import logging from omnilogic import LoginException, OmniLogic, OmniLogicException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client @@ -16,7 +16,7 @@ from .const import CONF_SCAN_INTERVAL, DEFAULT_PH_OFFSET, DEFAULT_SCAN_INTERVAL, _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OmniLogicConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Omnilogic.""" VERSION = 1 @@ -24,7 +24,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) @@ -72,10 +72,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle Omnilogic client options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/oncue/config_flow.py b/homeassistant/components/oncue/config_flow.py index cedb4feb7a4..e56266202bd 100644 --- a/homeassistant/components/oncue/config_flow.py +++ b/homeassistant/components/oncue/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from aiooncue import LoginFailedException, Oncue import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONNECTION_EXCEPTIONS, DOMAIN @@ -17,14 +16,14 @@ from .const import CONNECTION_EXCEPTIONS, DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OncueConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Oncue.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 4764e3b2a55..11a57fdaab5 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -8,11 +8,11 @@ import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.device_registry import DeviceEntry @@ -65,7 +65,7 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle 1-Wire config flow start. Let user manually input configuration. @@ -124,7 +124,7 @@ class OnewireOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" device_registry = dr.async_get(self.hass) self.configurable_devices = { @@ -142,7 +142,7 @@ class OnewireOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_device_selection( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select what devices to configure.""" errors = {} if user_input is not None: @@ -187,7 +187,7 @@ class OnewireOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_configure_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Config precision option for device.""" if user_input is not None: self._update_device_options(user_input) diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 9688a78bf3f..ba1fc1c3098 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -14,7 +14,6 @@ from wsdiscovery.scope import Scope from wsdiscovery.service import Service from zeep.exceptions import Fault -from homeassistant import config_entries from homeassistant.components import dhcp from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS from homeassistant.components.stream import ( @@ -22,6 +21,13 @@ from homeassistant.components.stream import ( CONF_USE_WALLCLOCK_AS_TIMESTAMPS, RTSP_TRANSPORTS, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -30,7 +36,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import device_registry as dr from .const import ( @@ -91,16 +97,16 @@ async def async_discovery(hass: HomeAssistant) -> list[dict[str, Any]]: return devices -class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class OnvifFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a ONVIF config flow.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry + _reauth_entry: ConfigEntry @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OnvifOptionsFlowHandler: """Get the options flow for this handler.""" return OnvifOptionsFlowHandler(config_entry) @@ -123,7 +129,9 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({vol.Required("auto", default=True): bool}), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication of an existing config entry.""" reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -134,7 +142,7 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth.""" entry = self._reauth_entry errors: dict[str, str] | None = {} @@ -161,7 +169,9 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=description_placeholders, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" hass = self.hass mac = discovery_info.macaddress @@ -176,7 +186,7 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if ( not (entry := hass.config_entries.async_get_entry(entry_id)) or entry.domain != DOMAIN - or entry.state is config_entries.ConfigEntryState.LOADED + or entry.state is ConfigEntryState.LOADED ): continue if hass.config_entries.async_update_entry( @@ -235,7 +245,7 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_configure( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Device configuration.""" errors: dict[str, str] = {} description_placeholders: dict[str, str] = {} @@ -374,10 +384,10 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await device.close() -class OnvifOptionsFlowHandler(config_entries.OptionsFlow): +class OnvifOptionsFlowHandler(OptionsFlow): """Handle ONVIF options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize ONVIF options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) diff --git a/homeassistant/components/open_meteo/config_flow.py b/homeassistant/components/open_meteo/config_flow.py index 7a603f887f0..1c5c76500b1 100644 --- a/homeassistant/components/open_meteo/config_flow.py +++ b/homeassistant/components/open_meteo/config_flow.py @@ -6,9 +6,8 @@ from typing import Any import voluptuous as vol from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ZONE -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import EntitySelector, EntitySelectorConfig from .const import DOMAIN @@ -21,7 +20,7 @@ class OpenMeteoFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: await self.async_set_unique_id(user_input[CONF_ZONE]) diff --git a/homeassistant/components/openai_conversation/config_flow.py b/homeassistant/components/openai_conversation/config_flow.py index ef1e498d061..a971e34ccf5 100644 --- a/homeassistant/components/openai_conversation/config_flow.py +++ b/homeassistant/components/openai_conversation/config_flow.py @@ -9,10 +9,14 @@ from typing import Any import openai import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( NumberSelector, NumberSelectorConfig, @@ -61,14 +65,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None: await hass.async_add_executor_job(client.with_options(timeout=10.0).models.list) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OpenAIConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for OpenAI Conversation.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -95,22 +99,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create the options flow.""" - return OptionsFlow(config_entry) + return OpenAIOptionsFlow(config_entry) -class OptionsFlow(config_entries.OptionsFlow): +class OpenAIOptionsFlow(OptionsFlow): """OpenAI config flow options handler.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="OpenAI Conversation", data=user_input) diff --git a/homeassistant/components/openexchangerates/config_flow.py b/homeassistant/components/openexchangerates/config_flow.py index 0425b44d9e6..4a15ed33fd8 100644 --- a/homeassistant/components/openexchangerates/config_flow.py +++ b/homeassistant/components/openexchangerates/config_flow.py @@ -12,10 +12,10 @@ from aioopenexchangerates import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_BASE from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CLIENT_TIMEOUT, DEFAULT_BASE, DOMAIN, LOGGER @@ -45,7 +45,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, return {"title": data[CONF_BASE]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OpenExchangeRatesConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Open Exchange Rates.""" VERSION = 1 @@ -53,11 +53,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" self.currencies: dict[str, str] = {} - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" currencies = await self.async_get_currencies() @@ -110,7 +110,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/opengarage/config_flow.py b/homeassistant/components/opengarage/config_flow.py index c1b0ff7105e..6b9a84a6a77 100644 --- a/homeassistant/components/opengarage/config_flow.py +++ b/homeassistant/components/opengarage/config_flow.py @@ -8,10 +8,9 @@ import aiohttp import opengarage import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac @@ -53,14 +52,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return {"title": status.get("name"), "unique_id": format_mac(status["mac"])} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OpenGarageConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for OpenGarage.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/openhome/config_flow.py b/homeassistant/components/openhome/config_flow.py index c8a13a3c7aa..5b26b63922b 100644 --- a/homeassistant/components/openhome/config_flow.py +++ b/homeassistant/components/openhome/config_flow.py @@ -8,9 +8,8 @@ from homeassistant.components.ssdp import ( ATTR_UPNP_UDN, SsdpServiceInfo, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -25,7 +24,9 @@ def _is_complete_discovery(discovery_info: SsdpServiceInfo) -> bool: class OpenhomeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle an Openhome config flow.""" - async def async_step_ssdp(self, discovery_info: SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" _LOGGER.debug("async_step_ssdp: started") @@ -51,7 +52,7 @@ class OpenhomeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" if user_input is not None: diff --git a/homeassistant/components/opensky/config_flow.py b/homeassistant/components/opensky/config_flow.py index 87621ea3508..568082b67fd 100644 --- a/homeassistant/components/opensky/config_flow.py +++ b/homeassistant/components/opensky/config_flow.py @@ -11,6 +11,7 @@ import voluptuous as vol from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, + ConfigFlowResult, OptionsFlowWithConfigEntry, ) from homeassistant.const import ( @@ -22,7 +23,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -48,7 +48,7 @@ class OpenSkyConfigFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Initialize user input.""" if user_input is not None: return self.async_create_entry( @@ -87,7 +87,7 @@ class OpenSkyOptionsFlowHandler(OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Initialize form.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 70bed0d1665..9e2b4d76dd3 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -8,7 +8,7 @@ from pyotgw import vars as gw_vars from serial import SerialException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_DEVICE, CONF_ID, @@ -30,7 +30,7 @@ from .const import ( ) -class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OpenThermGwConfigFlow(ConfigFlow, domain=DOMAIN): """OpenTherm Gateway Config Flow.""" VERSION = 1 @@ -38,7 +38,7 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OpenThermGwOptionsFlow: """Get the options flow for this handler.""" return OpenThermGwOptionsFlow(config_entry) @@ -116,10 +116,10 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class OpenThermGwOptionsFlow(config_entries.OptionsFlow): +class OpenThermGwOptionsFlow(OptionsFlow): """Handle opentherm_gw options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize the options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index d78fa84c8c5..0e6dfcdad1e 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -9,8 +9,7 @@ from pyopenuv import Client from pyopenuv.errors import OpenUvError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_ELEVATION, @@ -18,7 +17,6 @@ from homeassistant.const import ( CONF_LONGITUDE, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -70,7 +68,7 @@ class OpenUvData: return f"{self.latitude}, {self.longitude}" -class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class OpenUvFlowHandler(ConfigFlow, domain=DOMAIN): """Handle an OpenUV config flow.""" VERSION = 2 @@ -99,7 +97,7 @@ class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def _async_verify( self, data: OpenUvData, error_step_id: str, error_schema: vol.Schema - ) -> FlowResult: + ) -> ConfigFlowResult: """Verify the credentials and create/re-auth the entry.""" websession = aiohttp_client.async_get_clientsession(self.hass) client = Client(data.api_key, 0, 0, session=websession) @@ -138,14 +136,16 @@ class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Define the config flow to handle options.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_data = entry_data return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" if not user_input: return self.async_show_form( @@ -168,7 +168,7 @@ class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return self.async_show_form( diff --git a/homeassistant/components/openweathermap/config_flow.py b/homeassistant/components/openweathermap/config_flow.py index 799be35fb42..717a5148398 100644 --- a/homeassistant/components/openweathermap/config_flow.py +++ b/homeassistant/components/openweathermap/config_flow.py @@ -5,7 +5,7 @@ from pyowm import OWM from pyowm.commons.exceptions import APIRequestError, UnauthorizedError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_API_KEY, CONF_LANGUAGE, @@ -28,7 +28,7 @@ from .const import ( ) -class OpenWeatherMapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OpenWeatherMapConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for OpenWeatherMap.""" VERSION = CONFIG_FLOW_VERSION @@ -36,7 +36,7 @@ class OpenWeatherMapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OpenWeatherMapOptionsFlow: """Get the options flow for this handler.""" return OpenWeatherMapOptionsFlow(config_entry) @@ -90,10 +90,10 @@ class OpenWeatherMapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema, errors=errors) -class OpenWeatherMapOptionsFlow(config_entries.OptionsFlow): +class OpenWeatherMapOptionsFlow(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/opower/config_flow.py b/homeassistant/components/opower/config_flow.py index ab1fbbe36e3..b21ad72d688 100644 --- a/homeassistant/components/opower/config_flow.py +++ b/homeassistant/components/opower/config_flow.py @@ -15,10 +15,9 @@ from opower import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_create_clientsession from .const import CONF_TOTP_SECRET, CONF_UTILITY, DOMAIN @@ -55,19 +54,19 @@ async def _validate_login( return errors -class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OpowerConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Opower.""" VERSION = 1 def __init__(self) -> None: """Initialize a new OpowerConfigFlow.""" - self.reauth_entry: config_entries.ConfigEntry | None = None + self.reauth_entry: ConfigEntry | None = None self.utility_info: dict[str, Any] | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -91,7 +90,7 @@ class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_mfa( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle MFA step.""" assert self.utility_info is not None errors: dict[str, str] = {} @@ -120,14 +119,16 @@ class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_opower_entry(self, data: dict[str, Any]) -> FlowResult: + def _async_create_opower_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create the config entry.""" return self.async_create_entry( title=f"{data[CONF_UTILITY]} ({data[CONF_USERNAME]})", data=data, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -136,7 +137,7 @@ class OpowerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" assert self.reauth_entry errors: dict[str, str] = {} diff --git a/homeassistant/components/oralb/config_flow.py b/homeassistant/components/oralb/config_flow.py index 28e16c7f8a7..41938cc0c49 100644 --- a/homeassistant/components/oralb/config_flow.py +++ b/homeassistant/components/oralb/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class OralBConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class OralBConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class OralBConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/osoenergy/config_flow.py b/homeassistant/components/osoenergy/config_flow.py index 28b037f9cc5..0591630c660 100644 --- a/homeassistant/components/osoenergy/config_flow.py +++ b/homeassistant/components/osoenergy/config_flow.py @@ -6,10 +6,13 @@ from typing import Any from apyosoenergyapi import OSOEnergy import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -18,7 +21,7 @@ _LOGGER = logging.getLogger(__name__) _SCHEMA_STEP_USER = vol.Schema({vol.Required(CONF_API_KEY): str}) -class OSOEnergyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class OSOEnergyFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a OSO Energy config flow.""" VERSION = 1 @@ -27,7 +30,7 @@ class OSOEnergyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize.""" self.entry: ConfigEntry | None = None - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -36,10 +39,7 @@ class OSOEnergyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_email := await self.get_user_email(user_input[CONF_API_KEY]): await self.async_set_unique_id(user_email) - if ( - self.context["source"] == config_entries.SOURCE_REAUTH - and self.entry - ): + if self.context["source"] == SOURCE_REAUTH and self.entry: self.hass.config_entries.async_update_entry( self.entry, title=user_email, data=user_input ) @@ -67,7 +67,9 @@ class OSOEnergyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.exception("Unknown error occurred") return None - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Re Authenticate a user.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) data = {CONF_API_KEY: user_input[CONF_API_KEY]} diff --git a/homeassistant/components/otbr/config_flow.py b/homeassistant/components/otbr/config_flow.py index d0d3f1c1060..98e6c18741f 100644 --- a/homeassistant/components/otbr/config_flow.py +++ b/homeassistant/components/otbr/config_flow.py @@ -19,10 +19,9 @@ from homeassistant.components.hassio import ( ) from homeassistant.components.homeassistant_yellow import hardware as yellow_hardware from homeassistant.components.thread import async_get_preferred_dataset -from homeassistant.config_entries import SOURCE_HASSIO, ConfigFlow +from homeassistant.config_entries import SOURCE_HASSIO, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_URL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -100,7 +99,7 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Set up by user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -129,7 +128,9 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Handle hassio discovery.""" config = discovery_info.config url = f"http://{config['host']}:{config['port']}" diff --git a/homeassistant/components/ourgroceries/config_flow.py b/homeassistant/components/ourgroceries/config_flow.py index 65670dd7f92..2d95428ee59 100644 --- a/homeassistant/components/ourgroceries/config_flow.py +++ b/homeassistant/components/ourgroceries/config_flow.py @@ -9,9 +9,8 @@ from ourgroceries import OurGroceries from ourgroceries.exceptions import InvalidLoginException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -25,14 +24,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OurGroceriesConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for OurGroceries.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/overkiz/config_flow.py b/homeassistant/components/overkiz/config_flow.py index 5ded6de86f3..e9abef5897a 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -22,9 +22,8 @@ from pyoverkiz.obfuscate import obfuscate_id from pyoverkiz.utils import generate_local_server, is_overkiz_gateway import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp, zeroconf -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -32,7 +31,6 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_create_clientsession @@ -43,7 +41,7 @@ class DeveloperModeDisabled(HomeAssistantError): """Error to indicate Somfy Developer Mode is disabled.""" -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OverkizConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Overkiz (by Somfy).""" VERSION = 1 @@ -84,7 +82,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step via config flow.""" if user_input: self._server = user_input[CONF_HUB] @@ -109,7 +107,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_local_or_cloud( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Users can choose between local API or cloud API via config flow.""" if user_input: self._api_type = user_input[CONF_API_TYPE] @@ -135,7 +133,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_cloud( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the cloud authentication step via config flow.""" errors: dict[str, str] = {} description_placeholders = {} @@ -217,7 +215,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_local( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the local authentication step via config flow.""" errors = {} description_placeholders = {} @@ -300,7 +298,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle DHCP discovery.""" hostname = discovery_info.hostname gateway_id = hostname[8:22] @@ -311,7 +311,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle ZeroConf discovery.""" properties = discovery_info.properties gateway_id = properties["gateway_pin"] @@ -333,7 +333,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._process_discovery(gateway_id) - async def _process_discovery(self, gateway_id: str) -> FlowResult: + async def _process_discovery(self, gateway_id: str) -> ConfigFlowResult: """Handle discovery of a gateway.""" await self.async_set_unique_id(gateway_id) self._abort_if_unique_id_configured() @@ -341,7 +341,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth.""" self._reauth_entry = cast( ConfigEntry, diff --git a/homeassistant/components/ovo_energy/config_flow.py b/homeassistant/components/ovo_energy/config_flow.py index 69fab07829f..8070dfaac6e 100644 --- a/homeassistant/components/ovo_energy/config_flow.py +++ b/homeassistant/components/ovo_energy/config_flow.py @@ -8,9 +8,8 @@ import aiohttp from ovoenergy.ovoenergy import OVOEnergy import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_ACCOUNT, DOMAIN @@ -37,7 +36,7 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: Mapping[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: @@ -73,7 +72,7 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth( self, user_input: Mapping[str, Any], - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" errors = {} diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index 13b2051ffa2..3c1f6708342 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -1,8 +1,8 @@ """Config flow for OwnTracks.""" import secrets -from homeassistant import config_entries from homeassistant.components import cloud, webhook +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_WEBHOOK_ID from .const import DOMAIN @@ -12,7 +12,7 @@ CONF_SECRET = "secret" CONF_CLOUDHOOK = "cloudhook" -class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN): +class OwnTracksFlow(ConfigFlow, domain=DOMAIN): """Set up OwnTracks.""" VERSION = 1 diff --git a/homeassistant/components/p1_monitor/config_flow.py b/homeassistant/components/p1_monitor/config_flow.py index 00b035aba7f..230cd57d614 100644 --- a/homeassistant/components/p1_monitor/config_flow.py +++ b/homeassistant/components/p1_monitor/config_flow.py @@ -6,9 +6,8 @@ from typing import Any from p1monitor import P1Monitor, P1MonitorError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import TextSelector @@ -22,7 +21,7 @@ class P1MonitorFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/panasonic_viera/config_flow.py b/homeassistant/components/panasonic_viera/config_flow.py index 80ab929231e..35719f4cbfd 100644 --- a/homeassistant/components/panasonic_viera/config_flow.py +++ b/homeassistant/components/panasonic_viera/config_flow.py @@ -6,7 +6,7 @@ from urllib.error import URLError from panasonic_viera import TV_TYPE_ENCRYPTED, RemoteControl, SOAPError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT from .const import ( @@ -25,7 +25,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PanasonicVieraConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for Panasonic Viera.""" VERSION = 1 diff --git a/homeassistant/components/peco/config_flow.py b/homeassistant/components/peco/config_flow.py index 144495ec066..6fd4bfc0fe4 100644 --- a/homeassistant/components/peco/config_flow.py +++ b/homeassistant/components/peco/config_flow.py @@ -12,8 +12,7 @@ from peco import ( ) import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers import config_validation as cv from .const import CONF_COUNTY, CONF_PHONE_NUMBER, COUNTY_LIST, DOMAIN @@ -28,7 +27,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PecoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for PECO Outage Counter.""" VERSION = 1 @@ -54,7 +53,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -90,7 +89,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_finish_smart_meter( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the finish smart meter step.""" if "phone_number" in self.meter_error: if self.meter_error["type"] == "error": diff --git a/homeassistant/components/pegel_online/config_flow.py b/homeassistant/components/pegel_online/config_flow.py index a72e450e2e5..b0ce4cc3f38 100644 --- a/homeassistant/components/pegel_online/config_flow.py +++ b/homeassistant/components/pegel_online/config_flow.py @@ -6,7 +6,7 @@ from typing import Any from aiopegelonline import CONNECT_ERRORS, PegelOnline import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_LATITUDE, CONF_LOCATION, @@ -14,7 +14,6 @@ from homeassistant.const import ( CONF_RADIUS, UnitOfLength, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( LocationSelector, @@ -29,7 +28,7 @@ from homeassistant.helpers.selector import ( from .const import CONF_STATION, DEFAULT_RADIUS, DOMAIN -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -42,7 +41,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if not user_input: return self._show_form_user() @@ -69,7 +68,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select_station( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step select_station of a flow initialized by the user.""" if not user_input: stations = [ @@ -101,7 +100,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: if user_input is None: user_input = {} return self.async_show_form( diff --git a/homeassistant/components/permobil/config_flow.py b/homeassistant/components/permobil/config_flow.py index 2e3e228d512..d70365a9ca1 100644 --- a/homeassistant/components/permobil/config_flow.py +++ b/homeassistant/components/permobil/config_flow.py @@ -13,10 +13,9 @@ from mypermobil import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CODE, CONF_EMAIL, CONF_REGION, CONF_TOKEN, CONF_TTL from homeassistant.core import HomeAssistant, async_get_hass -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -41,7 +40,7 @@ GET_EMAIL_SCHEMA = vol.Schema( GET_TOKEN_SCHEMA = vol.Schema({vol.Required(CONF_CODE): cv.string}) -class PermobilConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PermobilConfigFlow(ConfigFlow, domain=DOMAIN): """Permobil config flow.""" VERSION = 1 @@ -56,7 +55,7 @@ class PermobilConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Invoke when a user initiates a flow via the user interface.""" errors: dict[str, str] = {} @@ -80,7 +79,7 @@ class PermobilConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_region( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Invoke when a user initiates a flow via the user interface.""" errors: dict[str, str] = {} if not user_input: @@ -130,7 +129,7 @@ class PermobilConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_email_code( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Second step in config flow to enter the email code.""" errors: dict[str, str] = {} @@ -160,7 +159,9 @@ class PermobilConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=self.data[CONF_EMAIL], data=self.data) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index d1cd3e7b1a5..fd308427ef4 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -8,7 +8,7 @@ from typing import Any from haphilipsjs import ConnectionFailure, PairingFailure, PhilipsTV import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_VERSION, CONF_HOST, @@ -16,7 +16,7 @@ from homeassistant.const import ( CONF_PIN, CONF_USERNAME, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import selector from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -49,7 +49,7 @@ OPTIONS_FLOW = { async def _validate_input( - hass: core.HomeAssistant, host: str, api_version: int + hass: HomeAssistant, host: str, api_version: int ) -> PhilipsTV: """Validate the user input allows us to connect.""" hub = PhilipsTV(host, api_version) @@ -63,7 +63,7 @@ async def _validate_input( return hub -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PhilipsJSConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Philips TV.""" VERSION = 1 @@ -74,9 +74,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._current: dict[str, Any] = {} self._hub: PhilipsTV | None = None self._pair_state: Any = None - self._entry: config_entries.ConfigEntry | None = None + self._entry: ConfigEntry | None = None - async def _async_create_current(self) -> FlowResult: + async def _async_create_current(self) -> ConfigFlowResult: system = self._current[CONF_SYSTEM] if self._entry: self.hass.config_entries.async_update_entry( @@ -94,7 +94,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pair( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Attempt to pair with device.""" assert self._hub @@ -145,7 +145,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._current[CONF_PASSWORD] = password return await self._async_create_current() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) self._current[CONF_HOST] = entry_data[CONF_HOST] @@ -154,7 +156,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input: @@ -187,9 +189,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user", data_schema=schema, errors=errors) @staticmethod - @core.callback + @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SchemaOptionsFlowHandler: """Get the options flow for this handler.""" return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) diff --git a/homeassistant/components/pi_hole/config_flow.py b/homeassistant/components/pi_hole/config_flow.py index 136e851429d..446191ed0ae 100644 --- a/homeassistant/components/pi_hole/config_flow.py +++ b/homeassistant/components/pi_hole/config_flow.py @@ -9,7 +9,7 @@ from hole import Hole from hole.exceptions import HoleError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -19,7 +19,6 @@ from homeassistant.const import ( CONF_SSL, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -33,7 +32,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class PiHoleFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Pi-hole config flow.""" VERSION = 1 @@ -44,7 +43,7 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -103,7 +102,7 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_api_key( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle step to setup API key.""" errors = {} if user_input is not None: @@ -120,7 +119,9 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._config = dict(entry_data) return await self.async_step_reauth_confirm() @@ -128,7 +129,7 @@ class PiHoleFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Perform reauth confirm upon an API authentication error.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/picnic/config_flow.py b/homeassistant/components/picnic/config_flow.py index b02c0a74bfc..f28b4dfed4d 100644 --- a/homeassistant/components/picnic/config_flow.py +++ b/homeassistant/components/picnic/config_flow.py @@ -10,15 +10,15 @@ from python_picnic_api.session import PicnicAuthError import requests import voluptuous as vol -from homeassistant import config_entries, core, exceptions -from homeassistant.config_entries import SOURCE_REAUTH +from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_COUNTRY_CODE, CONF_PASSWORD, CONF_USERNAME, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import COUNTRY_CODES, DOMAIN @@ -45,7 +45,7 @@ class PicnicHub: return picnic.session.auth_token, picnic.get_user() -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. @@ -75,12 +75,14 @@ async def validate_input(hass: core.HomeAssistant, data): } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PicnicConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Picnic.""" VERSION = 1 - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform the re-auth step upon an API authentication error.""" return await self.async_step_user() @@ -128,9 +130,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/ping/config_flow.py b/homeassistant/components/ping/config_flow.py index 29b8a8ba2a5..4163f710f2e 100644 --- a/homeassistant/components/ping/config_flow.py +++ b/homeassistant/components/ping/config_flow.py @@ -7,14 +7,18 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.device_tracker import ( CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from homeassistant.util.network import is_ip_address @@ -23,14 +27,14 @@ from .const import CONF_IMPORTED_BY, CONF_PING_COUNT, DEFAULT_PING_COUNT, DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PingConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Ping.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -56,7 +60,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def async_step_import(self, import_info: Mapping[str, Any]) -> FlowResult: + async def async_step_import( + self, import_info: Mapping[str, Any] + ) -> ConfigFlowResult: """Import an entry.""" to_import = { @@ -78,22 +84,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create the options flow.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle an options flow for Ping.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index 654150ffa48..df99f604146 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -4,9 +4,8 @@ from __future__ import annotations from pyplaato.plaato import PlaatoDeviceType import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import cloud, webhook -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_WEBHOOK_ID from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -26,7 +25,7 @@ from .const import ( ) -class PlaatoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PlaatoConfigFlow(ConfigFlow, domain=DOMAIN): """Handles a Plaato config flow.""" VERSION = 1 @@ -168,7 +167,7 @@ class PlaatoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return PlaatoOptionsFlowHandler(config_entry) -class PlaatoOptionsFlowHandler(config_entries.OptionsFlow): +class PlaatoOptionsFlowHandler(OptionsFlow): """Handle Plaato options.""" def __init__(self, config_entry: ConfigEntry) -> None: diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 10ae380a08a..93947614955 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -13,10 +13,17 @@ from plexauth import PlexAuth import requests.exceptions import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import http from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.config_entries import ( + SOURCE_INTEGRATION_DISCOVERY, + SOURCE_REAUTH, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_CLIENT_ID, CONF_HOST, @@ -28,7 +35,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import discovery_flow from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -80,12 +86,12 @@ async def async_discover(hass): discovery_flow.async_create_flow( hass, DOMAIN, - context={CONF_SOURCE: config_entries.SOURCE_INTEGRATION_DISCOVERY}, + context={CONF_SOURCE: SOURCE_INTEGRATION_DISCOVERY}, data=server_data, ) -class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class PlexFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Plex config flow.""" VERSION = 1 @@ -93,7 +99,7 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> PlexOptionsFlowHandler: """Get the options flow for this handler.""" return PlexOptionsFlowHandler(config_entry) @@ -241,7 +247,7 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } entry = await self.async_set_unique_id(server_id) - if self.context[CONF_SOURCE] == config_entries.SOURCE_REAUTH: + if self.context[CONF_SOURCE] == SOURCE_REAUTH: self.hass.config_entries.async_update_entry(entry, data=data) _LOGGER.debug("Updated config entry for %s", plex_server.friendly_name) await self.hass.config_entries.async_reload(entry.entry_id) @@ -338,7 +344,9 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): server_config = {CONF_TOKEN: self.token} return await self.async_step_server_validate(server_config) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" self._reauth_config = { CONF_SERVER_IDENTIFIER: entry_data[CONF_SERVER_IDENTIFIER] @@ -346,10 +354,10 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() -class PlexOptionsFlowHandler(config_entries.OptionsFlow): +class PlexOptionsFlowHandler(OptionsFlow): """Handle Plex options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Plex options flow.""" self.options = copy.deepcopy(dict(config_entry.options)) self.server_id = config_entry.data[CONF_SERVER_IDENTIFIER] diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 89c1b6eab52..48a932ad466 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -15,7 +15,7 @@ from plugwise.exceptions import ( import voluptuous as vol from homeassistant.components.zeroconf import ZeroconfServiceInfo -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_BASE, CONF_HOST, @@ -25,7 +25,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -89,7 +88,7 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Prepare configuration for a discovered Plugwise Smile.""" self.discovery_info = discovery_info _properties = discovery_info.properties @@ -166,7 +165,7 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step when using network/gateway setups.""" errors = {} diff --git a/homeassistant/components/plum_lightpad/config_flow.py b/homeassistant/components/plum_lightpad/config_flow.py index 9f81a57d42e..546398f5fa6 100644 --- a/homeassistant/components/plum_lightpad/config_flow.py +++ b/homeassistant/components/plum_lightpad/config_flow.py @@ -8,9 +8,8 @@ from aiohttp import ContentTypeError from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN from .utils import load_plum @@ -18,7 +17,7 @@ from .utils import load_plum _LOGGER = logging.getLogger(__name__) -class PlumLightpadConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PlumLightpadConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for Plum Lightpad integration.""" VERSION = 1 @@ -37,7 +36,7 @@ class PlumLightpadConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user or redirected to by import.""" if not user_input: return self._show_form() diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index 718e4a831c9..b9254aa7d4d 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -6,8 +6,8 @@ import logging from pypoint import PointSession import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -40,7 +40,7 @@ def register_flow_implementation(hass, domain, client_id, client_secret): } -class PointFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class PointFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 diff --git a/homeassistant/components/poolsense/config_flow.py b/homeassistant/components/poolsense/config_flow.py index 64685d67035..13ccfa4abc0 100644 --- a/homeassistant/components/poolsense/config_flow.py +++ b/homeassistant/components/poolsense/config_flow.py @@ -5,9 +5,8 @@ from typing import Any from poolsense import PoolSense import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -15,7 +14,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class PoolSenseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PoolSenseConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for PoolSense.""" VERSION = 1 @@ -25,7 +24,7 @@ class PoolSenseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 8b347ef49c1..767c4f1120d 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -16,10 +16,16 @@ from tesla_powerwall import ( ) import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import dhcp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.util.network import is_ip_address @@ -30,8 +36,8 @@ _LOGGER = logging.getLogger(__name__) ENTRY_FAILURE_STATES = { - config_entries.ConfigEntryState.SETUP_ERROR, - config_entries.ConfigEntryState.SETUP_RETRY, + ConfigEntryState.SETUP_ERROR, + ConfigEntryState.SETUP_RETRY, } @@ -59,9 +65,7 @@ async def _powerwall_is_reachable(ip_address: str, password: str) -> bool: return True -async def validate_input( - hass: core.HomeAssistant, data: dict[str, str] -) -> dict[str, str]: +async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from schema with values provided by the user. @@ -85,7 +89,7 @@ async def validate_input( return {"title": site_info.site_name, "unique_id": gateway_din.upper()} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PowerwallConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tesla Powerwall.""" VERSION = 1 @@ -94,11 +98,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the powerwall flow.""" self.ip_address: str | None = None self.title: str | None = None - self.reauth_entry: config_entries.ConfigEntry | None = None + self.reauth_entry: ConfigEntry | None = None - async def _async_powerwall_is_offline( - self, entry: config_entries.ConfigEntry - ) -> bool: + async def _async_powerwall_is_offline(self, entry: ConfigEntry) -> bool: """Check if the power wall is offline. We define offline by the config entry @@ -113,7 +115,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): or not async_last_update_was_successful(self.hass, entry) ) and not await _powerwall_is_reachable(ip_address, password) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" self.ip_address = discovery_info.ip gateway_din = discovery_info.hostname.upper() @@ -180,7 +184,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm a discovered powerwall.""" assert self.ip_address is not None assert self.unique_id is not None @@ -209,7 +213,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] | None = {} description_placeholders: dict[str, str] = {} @@ -243,7 +247,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle reauth confirmation.""" assert self.reauth_entry is not None errors: dict[str, str] | None = {} @@ -265,7 +269,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=description_placeholders, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -273,5 +279,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_reauth_confirm() -class WrongVersion(exceptions.HomeAssistantError): +class WrongVersion(HomeAssistantError): """Error indicating we cannot interact with the powerwall software version.""" diff --git a/homeassistant/components/private_ble_device/config_flow.py b/homeassistant/components/private_ble_device/config_flow.py index 4fec68e507e..ff6fade1141 100644 --- a/homeassistant/components/private_ble_device/config_flow.py +++ b/homeassistant/components/private_ble_device/config_flow.py @@ -8,8 +8,7 @@ import logging import voluptuous as vol from homeassistant.components import bluetooth -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN from .coordinator import async_last_service_info @@ -50,7 +49,7 @@ class BLEDeviceTrackerConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Set up by user.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/profiler/config_flow.py b/homeassistant/components/profiler/config_flow.py index db3241bf1d3..bb25e7f7e57 100644 --- a/homeassistant/components/profiler/config_flow.py +++ b/homeassistant/components/profiler/config_flow.py @@ -1,10 +1,10 @@ """Config flow for Profiler integration.""" -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from .const import DEFAULT_NAME, DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ProfilerConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Profiler.""" VERSION = 1 diff --git a/homeassistant/components/progettihwsw/config_flow.py b/homeassistant/components/progettihwsw/config_flow.py index 9b4fc16c8c4..91fe41cda52 100644 --- a/homeassistant/components/progettihwsw/config_flow.py +++ b/homeassistant/components/progettihwsw/config_flow.py @@ -3,7 +3,9 @@ from ProgettiHWSW.ProgettiHWSWAPI import ProgettiHWSWAPI import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -12,7 +14,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user host input.""" api_instance = ProgettiHWSWAPI(f'{data["host"]}:{data["port"]}') @@ -29,7 +31,7 @@ async def validate_input(hass: core.HomeAssistant, data): } -class ProgettiHWSWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ProgettiHWSWConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for ProgettiHWSW Automation.""" VERSION = 1 @@ -88,13 +90,13 @@ class ProgettiHWSWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot identify host.""" -class WrongInfo(exceptions.HomeAssistantError): +class WrongInfo(HomeAssistantError): """Error to indicate we cannot validate relay modes input.""" -class ExistingEntry(exceptions.HomeAssistantError): +class ExistingEntry(HomeAssistantError): """Error to indicate we cannot validate relay modes input.""" diff --git a/homeassistant/components/prosegur/config_flow.py b/homeassistant/components/prosegur/config_flow.py index c28245a09ff..a1f3dac832b 100644 --- a/homeassistant/components/prosegur/config_flow.py +++ b/homeassistant/components/prosegur/config_flow.py @@ -7,10 +7,10 @@ from pyprosegur.auth import COUNTRY, Auth from pyprosegur.installation import Installation import voluptuous as vol -from homeassistant import config_entries, core, exceptions -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client, selector from .const import CONF_CONTRACT, DOMAIN @@ -28,7 +28,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect.""" session = aiohttp_client.async_get_clientsession(hass) auth = Auth(session, data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_COUNTRY]) @@ -41,7 +41,7 @@ async def validate_input(hass: core.HomeAssistant, data): raise CannotConnect from ConnectionError -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ProsegurConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Prosegur Alarm.""" VERSION = 1 @@ -74,7 +74,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_choose_contract( self, user_input: Any | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Let user decide which contract is being setup.""" if user_input: @@ -103,7 +103,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with Prosegur.""" self.entry = cast( ConfigEntry, @@ -155,9 +157,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/proximity/config_flow.py b/homeassistant/components/proximity/config_flow.py index f3306bebf39..cc2e6eb5a39 100644 --- a/homeassistant/components/proximity/config_flow.py +++ b/homeassistant/components/proximity/config_flow.py @@ -8,10 +8,14 @@ import voluptuous as vol from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN from homeassistant.components.person import DOMAIN as PERSON_DOMAIN from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_ZONE from homeassistant.core import State, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( EntitySelector, EntitySelectorConfig, @@ -85,7 +89,7 @@ class ProximityConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: self._async_abort_entries_match(user_input) @@ -111,7 +115,7 @@ class ProximityConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_import( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Import a yaml config entry.""" return await self.async_step_user(user_input) @@ -128,7 +132,7 @@ class ProximityOptionsFlow(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: self.hass.config_entries.async_update_entry( diff --git a/homeassistant/components/prusalink/config_flow.py b/homeassistant/components/prusalink/config_flow.py index e4e9b9d719c..ac4dba0d20c 100644 --- a/homeassistant/components/prusalink/config_flow.py +++ b/homeassistant/components/prusalink/config_flow.py @@ -11,10 +11,9 @@ from pyprusalink import PrusaLink from pyprusalink.types import InvalidAuth import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -63,7 +62,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, return {"title": version["hostname"] or version["text"]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PrusaLinkConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for PrusaLink.""" VERSION = 1 @@ -71,7 +70,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/ps4/config_flow.py b/homeassistant/components/ps4/config_flow.py index 0865d09a460..ef74b3e8712 100644 --- a/homeassistant/components/ps4/config_flow.py +++ b/homeassistant/components/ps4/config_flow.py @@ -6,7 +6,7 @@ from pyps4_2ndscreen.helpers import Helper from pyps4_2ndscreen.media_art import COUNTRIES import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_CODE, CONF_HOST, @@ -38,7 +38,7 @@ PORT_MSG = {UDP_PORT: "port_987_bind_error", TCP_PORT: "port_997_bind_error"} PIN_LENGTH = 8 -class PlayStation4FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class PlayStation4FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a PlayStation 4 config flow.""" VERSION = CONFIG_ENTRY_VERSION diff --git a/homeassistant/components/pure_energie/config_flow.py b/homeassistant/components/pure_energie/config_flow.py index 9e6c510c8b6..b7dc2552231 100644 --- a/homeassistant/components/pure_energie/config_flow.py +++ b/homeassistant/components/pure_energie/config_flow.py @@ -7,9 +7,8 @@ from gridnet import Device, GridNet, GridNetConnectionError import voluptuous as vol from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import TextSelector @@ -25,7 +24,7 @@ class PureEnergieFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -59,7 +58,7 @@ class PureEnergieFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" self.discovered_host = discovery_info.host try: @@ -83,7 +82,7 @@ class PureEnergieFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/purpleair/config_flow.py b/homeassistant/components/purpleair/config_flow.py index e2b43726dc4..90a250a57b0 100644 --- a/homeassistant/components/purpleair/config_flow.py +++ b/homeassistant/components/purpleair/config_flow.py @@ -12,8 +12,12 @@ from aiopurpleair.endpoints.sensors import NearbySensorResult from aiopurpleair.errors import InvalidApiKeyError, PurpleAirError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, @@ -21,7 +25,6 @@ from homeassistant.const import ( CONF_SHOW_ON_MAP, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import ( aiohttp_client, config_validation as cv, @@ -193,7 +196,7 @@ async def async_validate_coordinates( return ValidationResult(data=nearby_sensor_results) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PurpleAirConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for PurpleAir.""" VERSION = 1 @@ -213,7 +216,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_by_coordinates( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the discovery of sensors near a latitude/longitude.""" if user_input is None: return self.async_show_form( @@ -243,7 +246,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_choose_sensor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the selection of a sensor.""" if user_input is None: options = self._flow_data.pop(CONF_NEARBY_SENSOR_OPTIONS) @@ -260,7 +263,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): options={CONF_SENSOR_INDICES: [int(user_input[CONF_SENSOR_INDEX])]}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -269,7 +274,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the re-auth step.""" if user_input is None: return self.async_show_form( @@ -298,7 +303,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=API_KEY_SCHEMA) @@ -319,7 +324,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_by_coordinates() -class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow): +class PurpleAirOptionsFlowHandler(OptionsFlow): """Handle a PurpleAir options flow.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -345,7 +350,7 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_add_sensor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Add a sensor.""" if user_input is None: return self.async_show_form( @@ -376,7 +381,7 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_choose_sensor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Choose a sensor.""" if user_input is None: options = self._flow_data.pop(CONF_NEARBY_SENSOR_OPTIONS) @@ -396,7 +401,7 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" return self.async_show_menu( step_id="init", @@ -405,7 +410,7 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_remove_sensor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Remove a sensor.""" if user_input is None: return self.async_show_form( @@ -467,7 +472,7 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_settings( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage settings.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/pushbullet/config_flow.py b/homeassistant/components/pushbullet/config_flow.py index 1eca2bd890b..1b2ca3b6040 100644 --- a/homeassistant/components/pushbullet/config_flow.py +++ b/homeassistant/components/pushbullet/config_flow.py @@ -6,9 +6,8 @@ from typing import Any from pushbullet import InvalidKeyError, PushBullet, PushbulletError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .const import DEFAULT_NAME, DOMAIN @@ -21,12 +20,12 @@ CONFIG_SCHEMA = vol.Schema( ) -class PushBulletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PushBulletConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for pushbullet integration.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/pushover/config_flow.py b/homeassistant/components/pushover/config_flow.py index 32f91031351..5f67db08073 100644 --- a/homeassistant/components/pushover/config_flow.py +++ b/homeassistant/components/pushover/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from pushover_complete import BadAPIRequestError, PushoverAPI import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from .const import CONF_USER_KEY, DEFAULT_NAME, DOMAIN @@ -39,12 +38,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return errors -class PushBulletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class PushBulletConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for pushover integration.""" - _reauth_entry: config_entries.ConfigEntry | None + _reauth_entry: ConfigEntry | None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -53,7 +54,7 @@ class PushBulletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} if user_input is not None and self._reauth_entry: @@ -84,7 +85,7 @@ class PushBulletConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/pvoutput/config_flow.py b/homeassistant/components/pvoutput/config_flow.py index 2016f87e611..7056e0f00b2 100644 --- a/homeassistant/components/pvoutput/config_flow.py +++ b/homeassistant/components/pvoutput/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from pvo import PVOutput, PVOutputAuthenticationError, PVOutputError import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_SYSTEM_ID, DOMAIN, LOGGER @@ -37,7 +36,7 @@ class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -84,7 +83,9 @@ class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with PVOutput.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -93,7 +94,7 @@ class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with PVOutput.""" errors = {} diff --git a/homeassistant/components/pvpc_hourly_pricing/config_flow.py b/homeassistant/components/pvpc_hourly_pricing/config_flow.py index 66092cb9211..23c6241462c 100644 --- a/homeassistant/components/pvpc_hourly_pricing/config_flow.py +++ b/homeassistant/components/pvpc_hourly_pricing/config_flow.py @@ -7,10 +7,14 @@ from typing import Any from aiopvpc import DEFAULT_POWER_KW, PVPCData import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import CONF_API_TOKEN, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util @@ -32,7 +36,7 @@ _MAIL_TO_LINK = ( ) -class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TariffSelectorConfigFlow(ConfigFlow, domain=DOMAIN): """Handle config flow for `pvpc_hourly_pricing`.""" VERSION = 1 @@ -43,19 +47,19 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _use_api_token: bool = False _api_token: str | None = None _api: PVPCData | None = None - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> PVPCOptionsFlowHandler: """Get the options flow for this handler.""" return PVPCOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is not None: await self.async_set_unique_id(user_input[ATTR_TARIFF]) @@ -92,7 +96,7 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_api_token( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle optional step to define API token for extra sensors.""" if user_input is not None: self._api_token = user_input[CONF_API_TOKEN] @@ -110,7 +114,9 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={"mail_to_link": _MAIL_TO_LINK}, ) - async def _async_verify(self, step_id: str, data_schema: vol.Schema) -> FlowResult: + async def _async_verify( + self, step_id: str, data_schema: vol.Schema + ) -> ConfigFlowResult: """Attempt to verify the provided configuration.""" errors: dict[str, str] = {} auth_ok = True @@ -144,7 +150,9 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): assert self._name is not None return self.async_create_entry(title=self._name, data=data) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with ESIOS Token.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -159,7 +167,7 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" data_schema = vol.Schema( { @@ -174,7 +182,7 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="reauth_confirm", data_schema=data_schema) -class PVPCOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): +class PVPCOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle PVPC options.""" _power: float | None = None @@ -182,7 +190,7 @@ class PVPCOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): async def async_step_api_token( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle optional step to define API token for extra sensors.""" if user_input is not None and user_input.get(CONF_API_TOKEN): return self.async_create_entry( @@ -208,7 +216,7 @@ class PVPCOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: if user_input[CONF_USE_API_TOKEN]: diff --git a/homeassistant/components/qbittorrent/config_flow.py b/homeassistant/components/qbittorrent/config_flow.py index 54215fb4563..148e9765c48 100644 --- a/homeassistant/components/qbittorrent/config_flow.py +++ b/homeassistant/components/qbittorrent/config_flow.py @@ -8,9 +8,8 @@ from qbittorrent.client import LoginRequired from requests.exceptions import RequestException import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DEFAULT_URL, DOMAIN from .helpers import setup_client @@ -32,7 +31,7 @@ class QbittorrentConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a user-initiated config flow.""" errors = {} diff --git a/homeassistant/components/qingping/config_flow.py b/homeassistant/components/qingping/config_flow.py index a90085afb4f..d5fd45e4614 100644 --- a/homeassistant/components/qingping/config_flow.py +++ b/homeassistant/components/qingping/config_flow.py @@ -12,9 +12,8 @@ from homeassistant.components.bluetooth import ( async_discovered_service_info, async_process_advertisements, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -52,7 +51,7 @@ class QingpingConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -69,7 +68,7 @@ class QingpingConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -88,7 +87,7 @@ class QingpingConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/qnap/config_flow.py b/homeassistant/components/qnap/config_flow.py index 04b5340fa8a..d8646c722d7 100644 --- a/homeassistant/components/qnap/config_flow.py +++ b/homeassistant/components/qnap/config_flow.py @@ -8,7 +8,7 @@ from qnapstats import QNAPStats from requests.exceptions import ConnectTimeout import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -17,7 +17,6 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import ( @@ -42,7 +41,7 @@ DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class QnapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class QnapConfigFlow(ConfigFlow, domain=DOMAIN): """Qnap configuration flow.""" VERSION = 1 @@ -50,7 +49,7 @@ class QnapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/qnap_qsw/config_flow.py b/homeassistant/components/qnap_qsw/config_flow.py index e0d4a1f78cd..8ade8169e57 100644 --- a/homeassistant/components/qnap_qsw/config_flow.py +++ b/homeassistant/components/qnap_qsw/config_flow.py @@ -8,10 +8,10 @@ from aioqsw.exceptions import LoginError, QswError from aioqsw.localapi import ConnectionOptions, QnapQswApi import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import aiohttp_client from homeassistant.helpers.device_registry import format_mac @@ -20,7 +20,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class QNapQSWConfigFlow(ConfigFlow, domain=DOMAIN): """Handle config flow for a QNAP QSW device.""" _discovered_mac: str | None = None @@ -28,7 +28,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -71,7 +71,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle DHCP discovery.""" self._discovered_url = f"http://{discovery_info.ip}" self._discovered_mac = discovery_info.macaddress @@ -97,7 +99,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovered_connection( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" errors = {} assert self._discovered_url is not None diff --git a/homeassistant/components/rabbitair/config_flow.py b/homeassistant/components/rabbitair/config_flow.py index e265740179d..f13517a1d56 100644 --- a/homeassistant/components/rabbitair/config_flow.py +++ b/homeassistant/components/rabbitair/config_flow.py @@ -11,7 +11,6 @@ from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_MAC from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr @@ -58,7 +57,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -100,7 +99,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle zeroconf discovery.""" mac = dr.format_mac(discovery_info.properties["id"]) await self.async_set_unique_id(mac) diff --git a/homeassistant/components/rachio/config_flow.py b/homeassistant/components/rachio/config_flow.py index 477abcb3694..0aca6426f65 100644 --- a/homeassistant/components/rachio/config_flow.py +++ b/homeassistant/components/rachio/config_flow.py @@ -8,11 +8,16 @@ from rachiopy import Rachio from requests.exceptions import ConnectTimeout import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from .const import ( CONF_MANUAL_RUN_MINS, @@ -28,7 +33,7 @@ _LOGGER = logging.getLogger(__name__) DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str}, extra=vol.ALLOW_EXTRA) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -56,7 +61,7 @@ async def validate_input(hass: core.HomeAssistant, data): return {"title": username} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RachioConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Rachio.""" VERSION = 1 @@ -84,7 +89,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle HomeKit discovery.""" self._async_abort_entries_match() await self.async_set_unique_id( @@ -96,16 +101,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for Rachio.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry @@ -127,9 +132,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=data_schema) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/radarr/config_flow.py b/homeassistant/components/radarr/config_flow.py index 3feb9a01bea..367e15e098a 100644 --- a/homeassistant/components/radarr/config_flow.py +++ b/homeassistant/components/radarr/config_flow.py @@ -10,10 +10,9 @@ from aiopyarr.models.host_configuration import PyArrHostConfiguration from aiopyarr.radarr_client import RadarrClient import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DEFAULT_NAME, DEFAULT_URL, DOMAIN @@ -28,7 +27,7 @@ class RadarrConfigFlow(ConfigFlow, domain=DOMAIN): """Initialize the flow.""" self.entry: ConfigEntry | None = None - async def async_step_reauth(self, _: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, _: Mapping[str, Any]) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -36,7 +35,7 @@ class RadarrConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is not None: return await self.async_step_user() @@ -46,7 +45,7 @@ class RadarrConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} diff --git a/homeassistant/components/radio_browser/config_flow.py b/homeassistant/components/radio_browser/config_flow.py index 1c6964d0715..ad7e5b71a8e 100644 --- a/homeassistant/components/radio_browser/config_flow.py +++ b/homeassistant/components/radio_browser/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -16,7 +15,7 @@ class RadioBrowserConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -28,6 +27,6 @@ class RadioBrowserConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_onboarding( self, data: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by onboarding.""" return self.async_create_entry(title="Radio Browser", data={}) diff --git a/homeassistant/components/radiotherm/config_flow.py b/homeassistant/components/radiotherm/config_flow.py index c370cc86484..d19fa198d0f 100644 --- a/homeassistant/components/radiotherm/config_flow.py +++ b/homeassistant/components/radiotherm/config_flow.py @@ -8,11 +8,10 @@ from urllib.error import URLError from radiotherm.validate import RadiothermTstatError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -33,7 +32,7 @@ async def validate_connection(hass: HomeAssistant, host: str) -> RadioThermInitD raise CannotConnect(f"Failed to connect to {host}: {ex}") from ex -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RadioThermConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Radio Thermostat.""" VERSION = 1 @@ -43,7 +42,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.discovered_ip: str | None = None self.discovered_init_data: RadioThermInitData | None = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Discover via DHCP.""" self._async_abort_entries_match({CONF_HOST: discovery_info.ip}) try: @@ -84,7 +85,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/rainbird/config_flow.py b/homeassistant/components/rainbird/config_flow.py index d9cf7b565a7..c4f76c5c9c5 100644 --- a/homeassistant/components/rainbird/config_flow.py +++ b/homeassistant/components/rainbird/config_flow.py @@ -14,11 +14,14 @@ from pyrainbird.async_client import ( from pyrainbird.data import WifiParams import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv, selector from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac @@ -53,7 +56,7 @@ class ConfigFlowError(Exception): self.error_code = error_code -class RainbirdConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class RainbirdConfigFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Rain Bird.""" @staticmethod @@ -66,7 +69,7 @@ class RainbirdConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure the Rain Bird device.""" error_code: str | None = None if user_input: @@ -129,7 +132,7 @@ class RainbirdConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, data: dict[str, Any], options: dict[str, Any], - ) -> FlowResult: + ) -> ConfigFlowResult: """Create the config entry.""" # The integration has historically used a serial number, but not all devices # historically had a valid one. Now the mac address is used as a unique id @@ -156,7 +159,7 @@ class RainbirdConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class RainBirdOptionsFlowHandler(config_entries.OptionsFlow): +class RainBirdOptionsFlowHandler(OptionsFlow): """Handle a RainBird options flow.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -165,7 +168,7 @@ class RainBirdOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(data=user_input) diff --git a/homeassistant/components/rainforest_eagle/config_flow.py b/homeassistant/components/rainforest_eagle/config_flow.py index f1e01a9d77a..1a034f098b7 100644 --- a/homeassistant/components/rainforest_eagle/config_flow.py +++ b/homeassistant/components/rainforest_eagle/config_flow.py @@ -6,9 +6,8 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_TYPE -from homeassistant.data_entry_flow import FlowResult from . import data from .const import CONF_CLOUD_ID, CONF_HARDWARE_ADDRESS, CONF_INSTALL_CODE, DOMAIN @@ -31,14 +30,14 @@ def create_schema(user_input: dict[str, Any] | None) -> vol.Schema: ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RainforestEagleConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Rainforest Eagle.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/rainforest_raven/config_flow.py b/homeassistant/components/rainforest_raven/config_flow.py index 2f0234efb7a..744a36d9993 100644 --- a/homeassistant/components/rainforest_raven/config_flow.py +++ b/homeassistant/components/rainforest_raven/config_flow.py @@ -11,10 +11,9 @@ import serial.tools.list_ports from serial.tools.list_ports_common import ListPortInfo import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import usb +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE, CONF_MAC, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( SelectSelector, SelectSelectorConfig, @@ -38,7 +37,7 @@ def _generate_unique_id(info: ListPortInfo | usb.UsbServiceInfo) -> str: ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RainforestRavenConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Rainforest RAVEn devices.""" def __init__(self) -> None: @@ -66,7 +65,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_meters( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Connect to device and discover meters.""" errors: dict[str, str] = {} if user_input is not None: @@ -98,7 +97,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_show_form(step_id="meters", data_schema=schema, errors=errors) - async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult: + async def async_step_usb( + self, discovery_info: usb.UsbServiceInfo + ) -> ConfigFlowResult: """Handle USB Discovery.""" device = discovery_info.device dev_path = await self.hass.async_add_executor_job(usb.get_serial_by_id, device) @@ -114,7 +115,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if self._async_in_progress(): return self.async_abort(reason="already_in_progress") diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index 1d73ef3dd88..eb11e6276af 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -8,12 +8,15 @@ from regenmaschine.controller import Controller from regenmaschine.errors import RainMachineError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import ( @@ -46,7 +49,7 @@ async def async_get_controller( return get_client_controller(client) -class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class RainMachineFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a RainMachine config flow.""" VERSION = 2 @@ -63,19 +66,19 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by homekit discovery.""" return await self.async_step_homekit_zeroconf(discovery_info) async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery via zeroconf.""" return await self.async_step_homekit_zeroconf(discovery_info) async def async_step_homekit_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery via zeroconf.""" ip_address = discovery_info.host @@ -117,7 +120,7 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" errors = {} if user_input: @@ -161,7 +164,7 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class RainMachineOptionsFlowHandler(config_entries.OptionsFlow): +class RainMachineOptionsFlowHandler(OptionsFlow): """Handle a RainMachine options flow.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -170,7 +173,7 @@ class RainMachineOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(data=user_input) diff --git a/homeassistant/components/rapt_ble/config_flow.py b/homeassistant/components/rapt_ble/config_flow.py index 9323ed4eb76..11971f68c54 100644 --- a/homeassistant/components/rapt_ble/config_flow.py +++ b/homeassistant/components/rapt_ble/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class RAPTPillConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class RAPTPillConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class RAPTPillConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/raspberry_pi/config_flow.py b/homeassistant/components/raspberry_pi/config_flow.py index db0f8643e5c..c84f3b38670 100644 --- a/homeassistant/components/raspberry_pi/config_flow.py +++ b/homeassistant/components/raspberry_pi/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -14,7 +13,9 @@ class RaspberryPiConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 - async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult: + async def async_step_system( + self, data: dict[str, Any] | None = None + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/rdw/config_flow.py b/homeassistant/components/rdw/config_flow.py index a9fedc88dac..fb42beb2174 100644 --- a/homeassistant/components/rdw/config_flow.py +++ b/homeassistant/components/rdw/config_flow.py @@ -6,8 +6,7 @@ from typing import Any from vehicle import RDW, RDWError, RDWUnknownLicensePlateError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_LICENSE_PLATE, DOMAIN @@ -20,7 +19,7 @@ class RDWFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/recollect_waste/config_flow.py b/homeassistant/components/recollect_waste/config_flow.py index c3e770cc458..3d79c895354 100644 --- a/homeassistant/components/recollect_waste/config_flow.py +++ b/homeassistant/components/recollect_waste/config_flow.py @@ -7,10 +7,14 @@ from aiorecollect.client import Client from aiorecollect.errors import RecollectError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_FRIENDLY_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DOMAIN, LOGGER @@ -20,7 +24,7 @@ DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RecollectWasteConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for ReCollect Waste.""" VERSION = 2 @@ -28,14 +32,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Define the config flow to handle options.""" return RecollectWasteOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle configuration via the UI.""" if user_input is None: return self.async_show_form( @@ -71,16 +75,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class RecollectWasteOptionsFlowHandler(config_entries.OptionsFlow): +class RecollectWasteOptionsFlowHandler(OptionsFlow): """Handle a Recollect Waste options flow.""" - def __init__(self, entry: config_entries.ConfigEntry) -> None: + def __init__(self, entry: ConfigEntry) -> None: """Initialize.""" self._entry = entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/renault/config_flow.py b/homeassistant/components/renault/config_flow.py index 8f5b99972d1..cc1b76d4a5f 100644 --- a/homeassistant/components/renault/config_flow.py +++ b/homeassistant/components/renault/config_flow.py @@ -7,15 +7,14 @@ from typing import TYPE_CHECKING, Any from renault_api.const import AVAILABLE_LOCALES import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_KAMEREON_ACCOUNT_ID, CONF_LOCALE, DOMAIN from .renault_hub import RenaultHub -class RenaultFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class RenaultFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Renault config flow.""" VERSION = 1 @@ -28,7 +27,7 @@ class RenaultFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a Renault config flow start. Ask the user for API keys. @@ -45,7 +44,7 @@ class RenaultFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_kamereon() return self._show_user_form() - def _show_user_form(self, errors: dict[str, Any] | None = None) -> FlowResult: + def _show_user_form(self, errors: dict[str, Any] | None = None) -> ConfigFlowResult: """Show the API keys form.""" return self.async_show_form( step_id="user", @@ -61,7 +60,7 @@ class RenaultFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_kamereon( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select Kamereon account.""" if user_input: await self.async_set_unique_id(user_input[CONF_KAMEREON_ACCOUNT_ID]) @@ -93,14 +92,16 @@ class RenaultFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._original_data = entry_data return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if not user_input: return self._show_reauth_confirm_form() @@ -128,7 +129,7 @@ class RenaultFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def _show_reauth_confirm_form( self, errors: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the API keys form.""" if TYPE_CHECKING: assert self._original_data diff --git a/homeassistant/components/renson/config_flow.py b/homeassistant/components/renson/config_flow.py index 9883772ce02..614abdb3665 100644 --- a/homeassistant/components/renson/config_flow.py +++ b/homeassistant/components/renson/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from renson_endura_delta import renson import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -24,7 +23,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RensonConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Renson.""" VERSION = 1 @@ -42,7 +41,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/reolink/config_flow.py b/homeassistant/components/reolink/config_flow.py index fc9b717f89b..627a41c9511 100644 --- a/homeassistant/components/reolink/config_flow.py +++ b/homeassistant/components/reolink/config_flow.py @@ -8,8 +8,13 @@ from typing import Any from reolink_aio.exceptions import ApiError, CredentialsInvalidError, ReolinkError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -18,7 +23,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -33,7 +38,7 @@ DEFAULT_PROTOCOL = "rtsp" DEFAULT_OPTIONS = {CONF_PROTOCOL: DEFAULT_PROTOCOL} -class ReolinkOptionsFlowHandler(config_entries.OptionsFlow): +class ReolinkOptionsFlowHandler(OptionsFlow): """Handle Reolink options.""" def __init__(self, config_entry): @@ -42,7 +47,7 @@ class ReolinkOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Reolink options.""" if user_input is not None: return self.async_create_entry(data=user_input) @@ -60,7 +65,7 @@ class ReolinkOptionsFlowHandler(config_entries.OptionsFlow): ) -class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Reolink device.""" VERSION = 1 @@ -75,12 +80,14 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> ReolinkOptionsFlowHandler: """Options callback for Reolink.""" return ReolinkOptionsFlowHandler(config_entry) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an authentication error or no admin privileges.""" self._host = entry_data[CONF_HOST] self._username = entry_data[CONF_USERNAME] @@ -94,13 +101,15 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" if user_input is not None: return await self.async_step_user() return self.async_show_form(step_id="reauth_confirm") - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" mac_address = format_mac(discovery_info.macaddress) existing_entry = await self.async_set_unique_id(mac_address) @@ -157,7 +166,7 @@ class ReolinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} placeholders = { diff --git a/homeassistant/components/repairs/models.py b/homeassistant/components/repairs/models.py index 63b3199141b..94c4d4731b9 100644 --- a/homeassistant/components/repairs/models.py +++ b/homeassistant/components/repairs/models.py @@ -7,7 +7,7 @@ from homeassistant import data_entry_flow from homeassistant.core import HomeAssistant -class RepairsFlow(data_entry_flow.BaseFlowHandler[data_entry_flow.FlowResult]): +class RepairsFlow(data_entry_flow.FlowHandler): """Handle a flow for fixing an issue.""" _flow_result = data_entry_flow.FlowResult diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 8f6ff45840c..e79303a4e23 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -13,8 +13,12 @@ import serial import serial.tools.list_ports import voluptuous as vol -from homeassistant import config_entries, data_entry_flow, exceptions -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, @@ -26,6 +30,7 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import State, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -74,7 +79,7 @@ def none_or_int(value: str | None, base: int) -> int | None: return int(value, base) -class OptionsFlow(config_entries.OptionsFlow): +class RfxtrxOptionsFlow(OptionsFlow): """Handle Rfxtrx options.""" _device_registry: dr.DeviceRegistry @@ -91,13 +96,13 @@ class OptionsFlow(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" return await self.async_step_prompt_options() async def async_step_prompt_options( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Prompt for options.""" errors = {} @@ -171,7 +176,7 @@ class OptionsFlow(config_entries.OptionsFlow): async def async_step_set_device_options( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Manage device options.""" errors = {} assert self._selected_device_object @@ -489,14 +494,14 @@ class OptionsFlow(config_entries.OptionsFlow): ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RfxtrxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for RFXCOM RFXtrx.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" await self.async_set_unique_id(DOMAIN) self._abort_if_unique_id_configured() @@ -515,7 +520,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_network( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Step when setting up network configuration.""" errors: dict[str, str] = {} @@ -542,7 +547,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_serial( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Step when setting up serial configuration.""" errors: dict[str, str] = {} @@ -581,7 +586,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_setup_serial_manual_path( self, user_input: dict[str, Any] | None = None - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Select path manually.""" errors: dict[str, str] = {} @@ -628,7 +633,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: """Get the options flow for this handler.""" - return OptionsFlow(config_entry) + return RfxtrxOptionsFlow(config_entry) def _test_transport(host: str | None, port: int | None, device: str | None) -> bool: @@ -658,5 +663,5 @@ def get_serial_by_id(dev_path: str) -> str: return dev_path -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/rhasspy/config_flow.py b/homeassistant/components/rhasspy/config_flow.py index 69ed802f817..4253e09a94d 100644 --- a/homeassistant/components/rhasspy/config_flow.py +++ b/homeassistant/components/rhasspy/config_flow.py @@ -5,20 +5,19 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RhasspyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Rhasspy.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/ridwell/config_flow.py b/homeassistant/components/ridwell/config_flow.py index 722c20336d4..d8d4e2b7381 100644 --- a/homeassistant/components/ridwell/config_flow.py +++ b/homeassistant/components/ridwell/config_flow.py @@ -8,9 +8,8 @@ from aioridwell import async_get_client from aioridwell.errors import InvalidCredentialsError, RidwellError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import DOMAIN, LOGGER @@ -29,8 +28,8 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for WattTime.""" +class RidwellConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Ridwell.""" VERSION = 2 @@ -41,7 +40,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_validate( self, error_step_id: str, error_schema: vol.Schema - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate input credentials and proceed accordingly.""" errors = {} session = aiohttp_client.async_get_clientsession(self.hass) @@ -81,14 +80,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data={CONF_USERNAME: self._username, CONF_PASSWORD: self._password}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._username = entry_data[CONF_USERNAME] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" if not user_input: return self.async_show_form( @@ -105,7 +106,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if not user_input: return self.async_show_form( diff --git a/homeassistant/components/ring/config_flow.py b/homeassistant/components/ring/config_flow.py index 5c735a3ee8c..6fc22e407f3 100644 --- a/homeassistant/components/ring/config_flow.py +++ b/homeassistant/components/ring/config_flow.py @@ -6,8 +6,7 @@ from typing import Any import ring_doorbell import voluptuous as vol -from homeassistant import config_entries, core, exceptions -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import ( APPLICATION_NAME, CONF_PASSWORD, @@ -15,7 +14,8 @@ from homeassistant.const import ( CONF_USERNAME, __version__ as ha_version, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import CONF_2FA, DOMAIN @@ -27,7 +27,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect.""" auth = ring_doorbell.Auth(f"{APPLICATION_NAME}/{ha_version}") @@ -47,7 +47,7 @@ async def validate_input(hass: core.HomeAssistant, data): return token -class RingConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RingConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Ring.""" VERSION = 1 @@ -96,7 +96,9 @@ class RingConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({vol.Required(CONF_2FA): str}), ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -105,7 +107,7 @@ class RingConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors = {} assert self.reauth_entry is not None @@ -143,9 +145,9 @@ class RingConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class Require2FA(exceptions.HomeAssistantError): +class Require2FA(HomeAssistantError): """Error to indicate we require 2FA.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/risco/config_flow.py b/homeassistant/components/risco/config_flow.py index 61a452a7ecb..d18fb91e0c8 100644 --- a/homeassistant/components/risco/config_flow.py +++ b/homeassistant/components/risco/config_flow.py @@ -8,7 +8,12 @@ from typing import Any from pyrisco import CannotConnectError, RiscoCloud, RiscoLocal, UnauthorizedError import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -22,7 +27,7 @@ from homeassistant.const import ( STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, ) -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -64,7 +69,7 @@ HA_STATES = [ async def validate_cloud_input( - hass: core.HomeAssistant, data: dict[str, Any] + hass: HomeAssistant, data: dict[str, Any] ) -> dict[str, str]: """Validate the user input allows us to connect to Risco Cloud. @@ -81,7 +86,7 @@ async def validate_cloud_input( async def validate_local_input( - hass: core.HomeAssistant, data: Mapping[str, str] + hass: HomeAssistant, data: Mapping[str, str] ) -> dict[str, Any]: """Validate the user input allows us to connect to a local panel. @@ -109,26 +114,26 @@ async def validate_local_input( return {"title": site_id, "comm_delay": comm_delay} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RiscoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Risco.""" VERSION = 1 def __init__(self) -> None: """Init the config flow.""" - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None @staticmethod - @core.callback + @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> RiscoOptionsFlowHandler: """Define the config flow to handle options.""" return RiscoOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" return self.async_show_menu( step_id="user", @@ -137,7 +142,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_cloud( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure a cloud based alarm.""" errors: dict[str, str] = {} if user_input is not None: @@ -169,14 +174,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="cloud", data_schema=CLOUD_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = await self.async_set_unique_id(entry_data[CONF_USERNAME]) return await self.async_step_cloud() async def async_step_local( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure a local based alarm.""" errors: dict[str, str] = {} if user_input is not None: @@ -208,10 +215,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class RiscoOptionsFlowHandler(config_entries.OptionsFlow): +class RiscoOptionsFlowHandler(OptionsFlow): """Handle a Risco options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize.""" self.config_entry = config_entry self._data = {**DEFAULT_OPTIONS, **config_entry.options} @@ -234,7 +241,7 @@ class RiscoOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: self._data = {**self._data, **user_input} @@ -244,7 +251,7 @@ class RiscoOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_risco_to_ha( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Map Risco states to HA states.""" if user_input is not None: self._data[CONF_RISCO_STATES_TO_HA] = user_input @@ -264,7 +271,7 @@ class RiscoOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_ha_to_risco( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Map HA states to Risco states.""" if user_input is not None: self._data[CONF_HA_STATES_TO_RISCO] = user_input diff --git a/homeassistant/components/rituals_perfume_genie/config_flow.py b/homeassistant/components/rituals_perfume_genie/config_flow.py index f9a7f1cb6b8..069d8008945 100644 --- a/homeassistant/components/rituals_perfume_genie/config_flow.py +++ b/homeassistant/components/rituals_perfume_genie/config_flow.py @@ -8,9 +8,8 @@ from aiohttp import ClientResponseError from pyrituals import Account, AuthenticationException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ACCOUNT_HASH, DOMAIN @@ -25,14 +24,14 @@ DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RitualsPerfumeGenieConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Rituals Perfume Genie.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA) diff --git a/homeassistant/components/roborock/config_flow.py b/homeassistant/components/roborock/config_flow.py index 82c513a1b97..597798af2ea 100644 --- a/homeassistant/components/roborock/config_flow.py +++ b/homeassistant/components/roborock/config_flow.py @@ -16,17 +16,15 @@ from roborock.exceptions import ( from roborock.web_api import RoborockApiClient import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_BASE_URL, CONF_ENTRY_CODE, CONF_USER_DATA, DOMAIN _LOGGER = logging.getLogger(__name__) -class RoborockFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class RoborockFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Roborock.""" VERSION = 1 @@ -39,7 +37,7 @@ class RoborockFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} @@ -81,7 +79,7 @@ class RoborockFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_code( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} assert self._client @@ -120,7 +118,9 @@ class RoborockFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._username = entry_data[CONF_USERNAME] assert self._username @@ -132,7 +132,7 @@ class RoborockFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors: dict[str, str] = {} if user_input is not None: @@ -143,7 +143,7 @@ class RoborockFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def _create_entry( self, client: RoborockApiClient, username: str, user_data: UserData - ) -> FlowResult: + ) -> ConfigFlowResult: """Finished config flow and create entry.""" return self.async_create_entry( title=username, diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index e3b8b97aa8f..2e49dcb2f30 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -9,10 +9,9 @@ from rokuecp import Roku, RokuError import voluptuous as vol from homeassistant.components import ssdp, zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -52,7 +51,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): self.discovery_info = {} @callback - def _show_form(self, errors: dict[str, Any] | None = None) -> FlowResult: + def _show_form(self, errors: dict[str, Any] | None = None) -> ConfigFlowResult: """Show the form to the user.""" return self.async_show_form( step_id="user", @@ -62,7 +61,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if not user_input: return self._show_form() @@ -86,7 +85,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by homekit discovery.""" # If we already have the host configured do @@ -114,7 +113,9 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_discovery_confirm() - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" host = urlparse(discovery_info.ssdp_location).hostname name = discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME] @@ -140,7 +141,7 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered device.""" if user_input is None: return self.async_show_form( diff --git a/homeassistant/components/romy/config_flow.py b/homeassistant/components/romy/config_flow.py index 6bc96c9878c..055c5b172bb 100644 --- a/homeassistant/components/romy/config_flow.py +++ b/homeassistant/components/romy/config_flow.py @@ -7,7 +7,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.const import CONF_HOST, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import DOMAIN, LOGGER @@ -26,7 +25,7 @@ class RomyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the user step.""" errors: dict[str, str] = {} @@ -59,7 +58,7 @@ class RomyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_password( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Unlock the robots local http interface with password.""" errors: dict[str, str] = {} @@ -85,7 +84,7 @@ class RomyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle zeroconf discovery.""" LOGGER.debug("Zeroconf discovery_info: %s", discovery_info) @@ -125,7 +124,7 @@ class RomyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle a confirmation flow initiated by zeroconf.""" if user_input is None: return self.async_show_form( @@ -137,7 +136,7 @@ class RomyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self._async_step_finish_config() - async def _async_step_finish_config(self) -> FlowResult: + async def _async_step_finish_config(self) -> config_entries.ConfigFlowResult: """Finish the configuration setup.""" return self.async_create_entry( title=self.robot_name_given_by_user, diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index e4fb45865a2..57ccd02fae3 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -9,11 +9,15 @@ from roombapy.discovery import RoombaDiscovery from roombapy.getpassword import RoombaPassword import voluptuous as vol -from homeassistant import config_entries, core from homeassistant.components import dhcp, zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_DELAY, CONF_HOST, CONF_NAME, CONF_PASSWORD -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback from . import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout from .const import ( @@ -38,7 +42,7 @@ AUTH_HELP_URL_KEY = "auth_help_url" AUTH_HELP_URL_VALUE = "https://www.home-assistant.io/integrations/roomba/#manually-retrieving-your-credentials" -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -65,7 +69,7 @@ async def validate_input(hass: core.HomeAssistant, data): } -class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RoombaConfigFlow(ConfigFlow, domain=DOMAIN): """Roomba configuration flow.""" VERSION = 1 @@ -80,26 +84,30 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" return await self._async_step_discovery( discovery_info.host, discovery_info.hostname.lower().rstrip(".local.") ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" return await self._async_step_discovery( discovery_info.ip, discovery_info.hostname ) - async def _async_step_discovery(self, ip_address: str, hostname: str) -> FlowResult: + async def _async_step_discovery( + self, ip_address: str, hostname: str + ) -> ConfigFlowResult: """Handle any discovery.""" self._async_abort_entries_match({CONF_HOST: ip_address}) @@ -281,10 +289,10 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/roon/config_flow.py b/homeassistant/components/roon/config_flow.py index 6ccf97155c4..e7f9bfb6860 100644 --- a/homeassistant/components/roon/config_flow.py +++ b/homeassistant/components/roon/config_flow.py @@ -5,8 +5,10 @@ import logging from roonapi import RoonApi, RoonDiscovery import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from .const import ( @@ -99,7 +101,7 @@ async def discover(hass): return servers -async def authenticate(hass: core.HomeAssistant, host, port, servers): +async def authenticate(hass: HomeAssistant, host, port, servers): """Connect and authenticate home assistant.""" hub = RoonHub(hass) @@ -116,7 +118,7 @@ async def authenticate(hass: core.HomeAssistant, host, port, servers): } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RoonConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for roon.""" VERSION = 1 @@ -174,5 +176,5 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="link", errors=errors) -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py index 97814c2a866..9f02bdc46ea 100644 --- a/homeassistant/components/rpi_power/config_flow.py +++ b/homeassistant/components/rpi_power/config_flow.py @@ -6,8 +6,8 @@ from typing import Any from rpi_bad_power import new_under_voltage +from homeassistant.config_entries import ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.config_entry_flow import DiscoveryFlowHandler from .const import DOMAIN @@ -34,7 +34,7 @@ class RPiPowerFlow(DiscoveryFlowHandler[Awaitable[bool]], domain=DOMAIN): async def async_step_onboarding( self, data: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by onboarding.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/rtsp_to_webrtc/config_flow.py b/homeassistant/components/rtsp_to_webrtc/config_flow.py index f66a0a30d8c..7593c11d6f3 100644 --- a/homeassistant/components/rtsp_to_webrtc/config_flow.py +++ b/homeassistant/components/rtsp_to_webrtc/config_flow.py @@ -8,11 +8,15 @@ from urllib.parse import urlparse import rtsp_to_webrtc import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.hassio import HassioServiceInfo +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from . import CONF_STUN_SERVER, DATA_SERVER_URL, DOMAIN @@ -22,14 +26,14 @@ _LOGGER = logging.getLogger(__name__) DATA_SCHEMA = vol.Schema({vol.Required(DATA_SERVER_URL): str}) -class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RTSPToWebRTCConfigFlow(ConfigFlow, domain=DOMAIN): """RTSPtoWebRTC config flow.""" _hassio_discovery: dict[str, Any] async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Configure the RTSPtoWebRTC server url.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -72,7 +76,9 @@ class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return "server_unreachable" return None - async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult: + async def async_step_hassio( + self, discovery_info: HassioServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for the RTSPtoWebRTC server add-on discovery.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -82,7 +88,7 @@ class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_hassio_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm Add-on discovery.""" errors = None if user_input is not None: @@ -109,22 +115,22 @@ class RTSPToWebRTCConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Create an options flow.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """RTSPtoWeb Options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/ruckus_unleashed/config_flow.py b/homeassistant/components/ruckus_unleashed/config_flow.py index c11e9cbe89f..745fb04b702 100644 --- a/homeassistant/components/ruckus_unleashed/config_flow.py +++ b/homeassistant/components/ruckus_unleashed/config_flow.py @@ -7,9 +7,10 @@ from aioruckus import AjaxSession, SystemStat from aioruckus.exceptions import AuthenticationError, SchemaError import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import ( API_MESH_NAME, @@ -31,7 +32,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -57,16 +58,16 @@ async def validate_input(hass: core.HomeAssistant, data): } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RuckusUnleashedConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Ruckus Unleashed.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None = None + _reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -105,7 +106,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -113,9 +116,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user() -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/ruuvi_gateway/config_flow.py b/homeassistant/components/ruuvi_gateway/config_flow.py index 178c55a53e4..2b892161f8d 100644 --- a/homeassistant/components/ruuvi_gateway/config_flow.py +++ b/homeassistant/components/ruuvi_gateway/config_flow.py @@ -7,10 +7,9 @@ from typing import Any import aioruuvigateway.api as gw_api from aioruuvigateway.excs import CannotConnect, InvalidAuth -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.httpx_client import get_async_client @@ -20,7 +19,7 @@ from .schemata import CONFIG_SCHEMA, get_config_schema_with_default_host _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RuuviConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Ruuvi Gateway.""" VERSION = 1 @@ -33,7 +32,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_validate( self, user_input: dict[str, Any], - ) -> tuple[FlowResult | None, dict[str, str]]: + ) -> tuple[ConfigFlowResult | None, dict[str, str]]: """Validate configuration (either discovered or user input).""" errors: dict[str, str] = {} @@ -67,7 +66,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle requesting or validating user input.""" if user_input is not None: result, errors = await self._async_validate(user_input) @@ -81,7 +80,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=(errors or None), ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Prepare configuration for a DHCP discovered Ruuvi Gateway.""" await self.async_set_unique_id(format_mac(discovery_info.macaddress)) self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip}) diff --git a/homeassistant/components/ruuvitag_ble/config_flow.py b/homeassistant/components/ruuvitag_ble/config_flow.py index 620b901f4fe..4f61c548038 100644 --- a/homeassistant/components/ruuvitag_ble/config_flow.py +++ b/homeassistant/components/ruuvitag_ble/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class RuuvitagConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class RuuvitagConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class RuuvitagConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/rympro/config_flow.py b/homeassistant/components/rympro/config_flow.py index b954bb10c57..c501ccfa994 100644 --- a/homeassistant/components/rympro/config_flow.py +++ b/homeassistant/components/rympro/config_flow.py @@ -8,10 +8,9 @@ from typing import Any from pyrympro import CannotConnectError, RymPro, UnauthorizedError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN, CONF_UNIQUE_ID from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -41,18 +40,18 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return {CONF_TOKEN: token, CONF_UNIQUE_ID: info["accountNumber"]} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class RymproConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Read Your Meter Pro.""" VERSION = 1 def __init__(self) -> None: """Init the config flow.""" - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -92,7 +91,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] From b0ed8c49615597b97dab24a08f9beed789741e4d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 29 Feb 2024 22:16:14 +0100 Subject: [PATCH 0064/1691] Migrate integrations s-t to generic flowhandler (#111865) --- .../components/sabnzbd/config_flow.py | 7 ++- .../components/samsungtv/config_flow.py | 51 +++++++++++-------- .../components/schlage/config_flow.py | 18 +++---- .../components/screenlogic/config_flow.py | 28 ++++++---- .../components/season/config_flow.py | 5 +- homeassistant/components/sense/config_flow.py | 9 ++-- .../components/sensibo/config_flow.py | 15 +++--- .../components/sensirion_ble/config_flow.py | 9 ++-- .../components/sensorpro/config_flow.py | 9 ++-- .../components/sensorpush/config_flow.py | 9 ++-- .../components/sentry/config_flow.py | 20 +++++--- .../components/sfr_box/config_flow.py | 15 +++--- .../components/sharkiq/config_flow.py | 21 ++++---- .../components/shelly/config_flow.py | 24 +++++---- .../components/shopping_list/config_flow.py | 7 ++- homeassistant/components/sia/config_flow.py | 26 ++++++---- .../components/simplepush/config_flow.py | 7 ++- .../components/simplisafe/config_flow.py | 19 ++++--- .../components/skybell/config_flow.py | 13 ++--- homeassistant/components/slack/config_flow.py | 7 ++- .../components/sleepiq/config_flow.py | 15 +++--- .../components/slimproto/config_flow.py | 5 +- homeassistant/components/sma/config_flow.py | 12 ++--- .../components/smappee/config_flow.py | 4 +- .../smart_meter_texas/config_flow.py | 12 +++-- .../components/smartthings/config_flow.py | 4 +- .../components/smarttub/config_flow.py | 11 ++-- homeassistant/components/smhi/config_flow.py | 7 ++- homeassistant/components/sms/config_flow.py | 10 ++-- .../components/snapcast/config_flow.py | 5 +- homeassistant/components/snooz/config_flow.py | 17 +++---- .../components/solaredge/config_flow.py | 7 ++- .../components/solarlog/config_flow.py | 4 +- homeassistant/components/solax/config_flow.py | 7 ++- homeassistant/components/soma/config_flow.py | 4 +- .../components/somfy_mylink/config_flow.py | 32 +++++++----- .../components/sonarr/config_flow.py | 22 +++++--- .../components/songpal/config_flow.py | 9 ++-- homeassistant/components/sonos/config_flow.py | 4 +- .../components/soundtouch/config_flow.py | 7 ++- .../components/speedtestdotnet/config_flow.py | 20 +++++--- .../components/spider/config_flow.py | 4 +- .../components/spotify/config_flow.py | 11 ++-- homeassistant/components/sql/config_flow.py | 18 ++++--- .../components/squeezebox/config_flow.py | 9 ++-- .../components/srp_energy/config_flow.py | 9 ++-- .../components/starline/config_flow.py | 13 ++--- .../components/starlink/config_flow.py | 5 +- .../components/steam_online/config_flow.py | 33 +++++++----- .../components/steamist/config_flow.py | 21 ++++---- .../components/stookalert/config_flow.py | 5 +- .../components/stookwijzer/config_flow.py | 5 +- .../components/streamlabswater/config_flow.py | 7 ++- .../components/subaru/config_flow.py | 26 ++++++---- .../components/suez_water/config_flow.py | 7 ++- homeassistant/components/sun/config_flow.py | 7 ++- .../components/sunweg/config_flow.py | 11 ++-- .../components/surepetcare/config_flow.py | 13 ++--- .../swiss_public_transport/config_flow.py | 9 ++-- .../components/switchbee/config_flow.py | 7 ++- .../components/switchbot/config_flow.py | 27 ++++++---- .../components/switchbot_cloud/config_flow.py | 7 ++- .../components/switcher_kis/config_flow.py | 13 ++--- .../components/syncthing/config_flow.py | 12 +++-- .../components/syncthru/config_flow.py | 9 ++-- .../components/synology_dsm/config_flow.py | 38 ++++++++------ .../components/system_bridge/config_flow.py | 22 ++++---- .../components/systemmonitor/config_flow.py | 6 ++- homeassistant/components/tado/config_flow.py | 41 ++++++++------- .../components/tailscale/config_flow.py | 11 ++-- .../components/tailwind/config_flow.py | 22 ++++---- homeassistant/components/tami4/config_flow.py | 9 ++-- .../components/tankerkoenig/config_flow.py | 34 ++++++++----- .../components/tasmota/config_flow.py | 15 +++--- .../components/tautulli/config_flow.py | 11 ++-- .../components/technove/config_flow.py | 9 ++-- homeassistant/components/tedee/config_flow.py | 11 ++-- .../components/tellduslive/config_flow.py | 4 +- .../tesla_wall_connector/config_flow.py | 11 ++-- .../components/teslemetry/config_flow.py | 3 +- .../components/tessie/config_flow.py | 14 ++--- .../components/thermobeacon/config_flow.py | 9 ++-- .../components/thermopro/config_flow.py | 9 ++-- .../components/thread/config_flow.py | 11 ++-- .../components/tibber/config_flow.py | 7 ++- homeassistant/components/tile/config_flow.py | 19 ++++--- .../components/tilt_ble/config_flow.py | 9 ++-- .../components/todoist/config_flow.py | 7 ++- homeassistant/components/tolo/config_flow.py | 11 ++-- .../components/tomorrowio/config_flow.py | 22 ++++---- homeassistant/components/toon/config_flow.py | 10 ++-- .../components/totalconnect/config_flow.py | 20 +++++--- .../components/tplink/config_flow.py | 40 +++++++++------ .../components/tplink_omada/config_flow.py | 15 +++--- .../components/traccar_server/config_flow.py | 7 +-- .../components/tractive/config_flow.py | 13 ++--- .../components/tradfri/config_flow.py | 13 +++-- .../trafikverket_camera/config_flow.py | 17 ++++--- .../trafikverket_ferry/config_flow.py | 15 +++--- .../trafikverket_train/config_flow.py | 26 ++++++---- .../config_flow.py | 15 +++--- .../components/transmission/config_flow.py | 28 ++++++---- homeassistant/components/tuya/config_flow.py | 11 ++-- .../components/twentemilieu/config_flow.py | 7 ++- .../components/twinkly/config_flow.py | 12 ++--- .../components/twitch/config_flow.py | 14 ++--- 106 files changed, 792 insertions(+), 661 deletions(-) diff --git a/homeassistant/components/sabnzbd/config_flow.py b/homeassistant/components/sabnzbd/config_flow.py index d433b562183..f3f974eef71 100644 --- a/homeassistant/components/sabnzbd/config_flow.py +++ b/homeassistant/components/sabnzbd/config_flow.py @@ -6,7 +6,7 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import ( CONF_API_KEY, CONF_HOST, @@ -15,7 +15,6 @@ from homeassistant.const import ( CONF_SSL, CONF_URL, ) -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_NAME, DOMAIN from .sab import get_client @@ -31,7 +30,7 @@ USER_SCHEMA = vol.Schema( ) -class SABnzbdConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SABnzbdConfigFlow(ConfigFlow, domain=DOMAIN): """Sabnzbd config flow.""" VERSION = 1 @@ -47,7 +46,7 @@ class SABnzbdConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index e7f71210dfe..87c795e2952 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -11,8 +11,13 @@ import getmac from samsungtvws.encrypted.authenticator import SamsungTVEncryptedWSAsyncAuthenticator import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp, ssdp, zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -23,7 +28,7 @@ from homeassistant.const import ( CONF_TOKEN, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac @@ -57,7 +62,7 @@ def _strip_uuid(udn: str) -> str: def _entry_is_complete( - entry: config_entries.ConfigEntry, + entry: ConfigEntry, ssdp_rendering_control_location: str | None, ssdp_main_tv_agent_location: str | None, ) -> bool: @@ -91,14 +96,14 @@ def _mac_is_same_with_incorrect_formatting( ) -class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a Samsung TV config flow.""" VERSION = 2 def __init__(self) -> None: """Initialize flow.""" - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None self._host: str = "" self._mac: str | None = None self._udn: str | None = None @@ -131,7 +136,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_SSDP_MAIN_TV_AGENT_LOCATION: self._ssdp_main_tv_agent_location, } - def _get_entry_from_bridge(self) -> FlowResult: + def _get_entry_from_bridge(self) -> ConfigFlowResult: """Get device entry.""" assert self._bridge data = self._base_config_entry() @@ -252,7 +257,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: await self._async_set_name_host_from_input(user_input) @@ -270,7 +275,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pairing( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a pairing by accepting the message on the TV.""" assert self._bridge is not None errors: dict[str, str] = {} @@ -292,7 +297,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_encrypted_pairing( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a encrypted pairing.""" assert self._host is not None await self._async_start_encrypted_pairing(self._host) @@ -326,9 +331,9 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @callback def _async_get_existing_matching_entry( self, - ) -> tuple[config_entries.ConfigEntry | None, bool]: + ) -> tuple[ConfigEntry | None, bool]: """Get first existing matching entry (prefer unique id).""" - matching_host_entry: config_entries.ConfigEntry | None = None + matching_host_entry: ConfigEntry | None = None for entry in self._async_current_entries(include_ignore=False): if (self._mac and self._mac == entry.data.get(CONF_MAC)) or ( self._upnp_udn and self._upnp_udn == entry.unique_id @@ -345,7 +350,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @callback def _async_update_existing_matching_entry( self, - ) -> config_entries.ConfigEntry | None: + ) -> ConfigEntry | None: """Check existing entries and update them. Returns the existing entry if it was updated. @@ -398,7 +403,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return None LOGGER.debug("Updating existing config entry with %s", entry_kw_args) self.hass.config_entries.async_update_entry(entry, **entry_kw_args) - if entry.state != config_entries.ConfigEntryState.LOADED: + if entry.state != ConfigEntryState.LOADED: # If its loaded it already has a reload listener in place # and we do not want to trigger multiple reloads self.hass.async_create_task( @@ -430,7 +435,9 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ): raise AbortFlow(RESULT_NOT_SUPPORTED) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by ssdp discovery.""" LOGGER.debug("Samsung device found via SSDP: %s", discovery_info) model_name: str = discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NAME) or "" @@ -475,7 +482,9 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = {"device": self._title} return await self.async_step_confirm() - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by dhcp discovery.""" LOGGER.debug("Samsung device found via DHCP: %s", discovery_info) self._mac = format_mac(discovery_info.macaddress) @@ -487,7 +496,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf discovery.""" LOGGER.debug("Samsung device found via ZEROCONF: %s", discovery_info) self._mac = format_mac(discovery_info.properties["deviceid"]) @@ -499,7 +508,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" if user_input is not None: await self._async_create_bridge() @@ -512,7 +521,9 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="confirm", description_placeholders={"device": self._title} ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -525,7 +536,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth.""" errors = {} assert self._reauth_entry @@ -569,7 +580,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm_encrypted( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth (encrypted method).""" errors = {} assert self._reauth_entry diff --git a/homeassistant/components/schlage/config_flow.py b/homeassistant/components/schlage/config_flow.py index 84bc3ef8ef6..80693013af7 100644 --- a/homeassistant/components/schlage/config_flow.py +++ b/homeassistant/components/schlage/config_flow.py @@ -8,10 +8,8 @@ import pyschlage from pyschlage.exceptions import NotAuthorizedError import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, LOGGER @@ -21,7 +19,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema( STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SchlageConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Schlage.""" VERSION = 1 @@ -30,7 +28,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self._show_user_form({}) @@ -45,13 +43,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(user_id) return self.async_create_entry(title=username, data=user_input) - def _show_user_form(self, errors: dict[str, str]) -> FlowResult: + def _show_user_form(self, errors: dict[str, str]) -> ConfigFlowResult: """Show the user form.""" return self.async_show_form( step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -60,7 +60,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" assert self.reauth_entry is not None if user_input is None: @@ -85,7 +85,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) return self.async_abort(reason="reauth_successful") - def _show_reauth_form(self, errors: dict[str, str]) -> FlowResult: + def _show_reauth_form(self, errors: dict[str, str]) -> ConfigFlowResult: """Show the reauth form.""" return self.async_show_form( step_id="reauth_confirm", diff --git a/homeassistant/components/screenlogic/config_flow.py b/homeassistant/components/screenlogic/config_flow.py index 25d00e3a2ce..424bddb5c8c 100644 --- a/homeassistant/components/screenlogic/config_flow.py +++ b/homeassistant/components/screenlogic/config_flow.py @@ -9,11 +9,15 @@ from screenlogicpy.const.common import SL_GATEWAY_IP, SL_GATEWAY_NAME, SL_GATEWA from screenlogicpy.requests import login import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, CONF_SCAN_INTERVAL from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import format_mac @@ -60,7 +64,7 @@ def name_for_mac(mac): return f"Pentair: {short_mac(mac)}" -class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class ScreenlogicConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow to setup screen logic devices.""" VERSION = 1 @@ -73,17 +77,19 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> ScreenLogicOptionsFlowHandler: """Get the options flow for ScreenLogic.""" return ScreenLogicOptionsFlowHandler(config_entry) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle the start of the config flow.""" self.discovered_gateways = await async_discover_gateways_by_unique_id(self.hass) return await self.async_step_gateway_select() - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" mac = format_mac(discovery_info.macaddress) await self.async_set_unique_id(mac) @@ -94,7 +100,7 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.context["title_placeholders"] = {"name": discovery_info.hostname} return await self.async_step_gateway_entry() - async def async_step_gateway_select(self, user_input=None) -> FlowResult: + async def async_step_gateway_select(self, user_input=None) -> ConfigFlowResult: """Handle the selection of a discovered ScreenLogic gateway.""" existing = self._async_current_ids() unconfigured_gateways = { @@ -141,7 +147,7 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={}, ) - async def async_step_gateway_entry(self, user_input=None) -> FlowResult: + async def async_step_gateway_entry(self, user_input=None) -> ConfigFlowResult: """Handle the manual entry of a ScreenLogic gateway.""" errors: dict[str, str] = {} ip_address = self.discovered_ip @@ -180,14 +186,14 @@ class ScreenlogicConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class ScreenLogicOptionsFlowHandler(config_entries.OptionsFlow): +class ScreenLogicOptionsFlowHandler(OptionsFlow): """Handles the options for the ScreenLogic integration.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Init the screen logic options flow.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None) -> FlowResult: + async def async_step_init(self, user_input=None) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry( diff --git a/homeassistant/components/season/config_flow.py b/homeassistant/components/season/config_flow.py index 069037e53a0..6c461404781 100644 --- a/homeassistant/components/season/config_flow.py +++ b/homeassistant/components/season/config_flow.py @@ -5,9 +5,8 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_TYPE -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import ( SelectSelector, SelectSelectorConfig, @@ -24,7 +23,7 @@ class SeasonConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: await self.async_set_unique_id(user_input[CONF_TYPE]) diff --git a/homeassistant/components/sense/config_flow.py b/homeassistant/components/sense/config_flow.py index 86b68db1e32..ddf28fb1cef 100644 --- a/homeassistant/components/sense/config_flow.py +++ b/homeassistant/components/sense/config_flow.py @@ -10,9 +10,8 @@ from sense_energy import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CODE, CONF_EMAIL, CONF_PASSWORD, CONF_TIMEOUT -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ACTIVE_UPDATE_RATE, DEFAULT_TIMEOUT, DOMAIN, SENSE_CONNECT_EXCEPTIONS @@ -28,7 +27,7 @@ DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SenseConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Sense.""" VERSION = 1 @@ -121,7 +120,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._auth_data = dict(entry_data) return await self.async_step_reauth_validate(entry_data) diff --git a/homeassistant/components/sensibo/config_flow.py b/homeassistant/components/sensibo/config_flow.py index d826e854fa0..75d41b80d8a 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from pysensibo.exceptions import AuthenticationError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import TextSelector from .const import DEFAULT_NAME, DOMAIN @@ -22,14 +21,16 @@ DATA_SCHEMA = vol.Schema( ) -class SensiboConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SensiboConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Sensibo integration.""" VERSION = 2 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Sensibo.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -37,7 +38,7 @@ class SensiboConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Sensibo.""" errors: dict[str, str] = {} @@ -74,7 +75,7 @@ class SensiboConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/sensirion_ble/config_flow.py b/homeassistant/components/sensirion_ble/config_flow.py index 0442b6d16c3..dabe4e323ed 100644 --- a/homeassistant/components/sensirion_ble/config_flow.py +++ b/homeassistant/components/sensirion_ble/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class SensirionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class SensirionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class SensirionConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/sensorpro/config_flow.py b/homeassistant/components/sensorpro/config_flow.py index 182a35880ab..c5899291f7c 100644 --- a/homeassistant/components/sensorpro/config_flow.py +++ b/homeassistant/components/sensorpro/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class SensorProConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class SensorProConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class SensorProConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/sensorpush/config_flow.py b/homeassistant/components/sensorpush/config_flow.py index 9913b7f7b09..5fd29776c32 100644 --- a/homeassistant/components/sensorpush/config_flow.py +++ b/homeassistant/components/sensorpush/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class SensorPushConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class SensorPushConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class SensorPushConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/sentry/config_flow.py b/homeassistant/components/sentry/config_flow.py index 4fe2b0cc503..10d17030d9f 100644 --- a/homeassistant/components/sentry/config_flow.py +++ b/homeassistant/components/sentry/config_flow.py @@ -7,9 +7,13 @@ from typing import Any from sentry_sdk.utils import BadDsn, Dsn import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_DSN, @@ -33,7 +37,7 @@ _LOGGER = logging.getLogger(__name__) DATA_SCHEMA = vol.Schema({vol.Required(CONF_DSN): str}) -class SentryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SentryConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a Sentry config flow.""" VERSION = 1 @@ -41,14 +45,14 @@ class SentryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SentryOptionsFlow: """Get the options flow for this handler.""" return SentryOptionsFlow(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a user config flow.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -70,16 +74,16 @@ class SentryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class SentryOptionsFlow(config_entries.OptionsFlow): +class SentryOptionsFlow(OptionsFlow): """Handle Sentry options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Sentry options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Sentry options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/sfr_box/config_flow.py b/homeassistant/components/sfr_box/config_flow.py index 836ed708743..aa8e48f9422 100644 --- a/homeassistant/components/sfr_box/config_flow.py +++ b/homeassistant/components/sfr_box/config_flow.py @@ -8,9 +8,8 @@ from sfrbox_api.bridge import SFRBox from sfrbox_api.exceptions import SFRBoxAuthenticationError, SFRBoxError import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from homeassistant.helpers.httpx_client import get_async_client @@ -41,7 +40,7 @@ class SFRBoxFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: @@ -65,7 +64,7 @@ class SFRBoxFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_choose_auth( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return self.async_show_menu( step_id="choose_auth", @@ -74,7 +73,7 @@ class SFRBoxFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_auth( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Check authentication.""" errors = {} if user_input is not None: @@ -107,11 +106,13 @@ class SFRBoxFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_skip_auth( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Skip authentication.""" return self.async_create_entry(title="SFR Box", data=self._config) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle failed credentials.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] diff --git a/homeassistant/components/sharkiq/config_flow.py b/homeassistant/components/sharkiq/config_flow.py index c0ca5e1b9e5..8bbad1f7c68 100644 --- a/homeassistant/components/sharkiq/config_flow.py +++ b/homeassistant/components/sharkiq/config_flow.py @@ -9,9 +9,10 @@ import aiohttp from sharkiq import SharkIqAuthError, get_ayla_api import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -39,7 +40,7 @@ SHARKIQ_SCHEMA = vol.Schema( async def _validate_input( - hass: core.HomeAssistant, data: Mapping[str, Any] + hass: HomeAssistant, data: Mapping[str, Any] ) -> dict[str, str]: """Validate the user input allows us to connect.""" ayla_api = get_ayla_api( @@ -74,7 +75,7 @@ async def _validate_input( return {"title": data[CONF_USERNAME]} -class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SharkIqConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Shark IQ.""" VERSION = 1 @@ -99,7 +100,7 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -113,7 +114,9 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=SHARKIQ_SCHEMA, errors=errors ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-auth if login is invalid.""" errors: dict[str, str] = {} @@ -136,13 +139,13 @@ class SharkIqConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class UnknownAuth(exceptions.HomeAssistantError): +class UnknownAuth(HomeAssistantError): """Error to indicate there is an uncaught auth error.""" diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 2ae5a74bb42..21d8c58f0fe 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -16,10 +16,14 @@ from aioshelly.rpc_device import RpcDevice import voluptuous as vol from homeassistant.components.zeroconf import ZeroconfServiceInfo -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig @@ -117,7 +121,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -166,7 +170,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_credentials( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the credentials step.""" errors: dict[str, str] = {} if user_input is not None: @@ -239,7 +243,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" host = discovery_info.host # First try to get the mac address from the name @@ -280,7 +284,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm_discovery( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle discovery confirm.""" errors: dict[str, str] = {} @@ -310,14 +314,16 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors: dict[str, str] = {} assert self.entry is not None @@ -384,7 +390,7 @@ class OptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/shopping_list/config_flow.py b/homeassistant/components/shopping_list/config_flow.py index 0637dcea390..24d4ca92ebc 100644 --- a/homeassistant/components/shopping_list/config_flow.py +++ b/homeassistant/components/shopping_list/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -16,7 +15,7 @@ class ShoppingListFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" # Check if already configured await self.async_set_unique_id(DOMAIN) @@ -31,6 +30,6 @@ class ShoppingListFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_onboarding( self, _: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by onboarding.""" return await self.async_step_user(user_input={}) diff --git a/homeassistant/components/sia/config_flow.py b/homeassistant/components/sia/config_flow.py index cbcb60a4565..8ebe89c24ea 100644 --- a/homeassistant/components/sia/config_flow.py +++ b/homeassistant/components/sia/config_flow.py @@ -15,10 +15,14 @@ from pysiaalarm import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PORT, CONF_PROTOCOL from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_ACCOUNT, @@ -87,7 +91,7 @@ def validate_zones(data: dict[str, Any]) -> dict[str, str] | None: return None -class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SIAConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for sia.""" VERSION: int = 1 @@ -95,7 +99,7 @@ class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SIAOptionsFlowHandler: """Get the options flow for this handler.""" return SIAOptionsFlowHandler(config_entry) @@ -107,7 +111,7 @@ class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial user step.""" errors: dict[str, str] | None = None if user_input is not None: @@ -120,7 +124,7 @@ class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_add_account( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the additional accounts steps.""" errors: dict[str, str] | None = None if user_input is not None: @@ -133,7 +137,7 @@ class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_handle_data_and_route( self, user_input: dict[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user_input, check if configured and route to the right next step or create entry.""" self._update_data(user_input) @@ -171,10 +175,10 @@ class SIAConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._options[CONF_ACCOUNTS][account][CONF_ZONES] = user_input[CONF_ZONES] -class SIAOptionsFlowHandler(config_entries.OptionsFlow): +class SIAOptionsFlowHandler(OptionsFlow): """Handle SIA options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize SIA options flow.""" self.config_entry = config_entry self.options = deepcopy(dict(config_entry.options)) @@ -183,7 +187,7 @@ class SIAOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the SIA options.""" self.hub = self.hass.data[DOMAIN][self.config_entry.entry_id] assert self.hub is not None @@ -193,7 +197,7 @@ class SIAOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_options( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create the options step for a account.""" errors: dict[str, str] | None = None if user_input is not None: diff --git a/homeassistant/components/simplepush/config_flow.py b/homeassistant/components/simplepush/config_flow.py index d87f6fa1913..2b039a33951 100644 --- a/homeassistant/components/simplepush/config_flow.py +++ b/homeassistant/components/simplepush/config_flow.py @@ -6,9 +6,8 @@ from typing import Any from simplepush import UnknownError, send import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from .const import ATTR_ENCRYPTED, CONF_DEVICE_KEY, CONF_SALT, DEFAULT_NAME, DOMAIN @@ -36,12 +35,12 @@ def validate_input(entry: dict[str, str]) -> dict[str, str] | None: return None -class SimplePushFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SimplePushFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for simplepush.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors: dict[str, str] | None = None if user_input is not None: diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index dcfcd6cd9d3..b093e0d216f 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -13,11 +13,14 @@ from simplipy.util.auth import ( ) import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_CODE, CONF_TOKEN, CONF_URL, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import DOMAIN, LOGGER @@ -47,7 +50,7 @@ def async_get_simplisafe_oauth_values() -> SimpliSafeOAuthValues: return SimpliSafeOAuthValues(auth_url, code_verifier) -class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SimpliSafeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a SimpliSafe config flow.""" VERSION = 1 @@ -65,14 +68,14 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Define the config flow to handle options.""" return SimpliSafeOptionsFlowHandler(config_entry) - async def async_step_reauth(self, config: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, config: Mapping[str, Any]) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._reauth = True return await self.async_step_user() async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if user_input is None: return self.async_show_form( @@ -144,7 +147,7 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=simplisafe_user_id, data=data) -class SimpliSafeOptionsFlowHandler(config_entries.OptionsFlow): +class SimpliSafeOptionsFlowHandler(OptionsFlow): """Handle a SimpliSafe options flow.""" def __init__(self, config_entry: ConfigEntry) -> None: @@ -153,7 +156,7 @@ class SimpliSafeOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" if user_input is not None: return self.async_create_entry(data=user_input) diff --git a/homeassistant/components/skybell/config_flow.py b/homeassistant/components/skybell/config_flow.py index 5e63ae4f929..29cd723bff0 100644 --- a/homeassistant/components/skybell/config_flow.py +++ b/homeassistant/components/skybell/config_flow.py @@ -7,27 +7,28 @@ from typing import Any from aioskybell import Skybell, exceptions import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN -class SkybellFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SkybellFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Skybell.""" reauth_email: str - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" self.reauth_email = entry_data[CONF_EMAIL] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user's reauth credentials.""" errors = {} if user_input: @@ -53,7 +54,7 @@ class SkybellFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} diff --git a/homeassistant/components/slack/config_flow.py b/homeassistant/components/slack/config_flow.py index 187cef057a0..10b6183a321 100644 --- a/homeassistant/components/slack/config_flow.py +++ b/homeassistant/components/slack/config_flow.py @@ -7,9 +7,8 @@ from slack import WebClient from slack.errors import SlackApiError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_NAME, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import CONF_DEFAULT_CHANNEL, DOMAIN @@ -26,12 +25,12 @@ CONFIG_SCHEMA = vol.Schema( ) -class SlackFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SlackFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Slack.""" async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} diff --git a/homeassistant/components/sleepiq/config_flow.py b/homeassistant/components/sleepiq/config_flow.py index 77806a1f977..60559921fcb 100644 --- a/homeassistant/components/sleepiq/config_flow.py +++ b/homeassistant/components/sleepiq/config_flow.py @@ -8,10 +8,9 @@ from typing import Any from asyncsleepiq import AsyncSleepIQ, SleepIQLoginException, SleepIQTimeoutException import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -28,7 +27,9 @@ class SleepIQFlowHandler(ConfigFlow, domain=DOMAIN): """Initialize the config flow.""" self._reauth_entry: ConfigEntry | None = None - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import a SleepIQ account as a config entry. This flow is triggered by 'async_setup' for configured accounts. @@ -46,7 +47,7 @@ class SleepIQFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -80,7 +81,9 @@ class SleepIQFlowHandler(ConfigFlow, domain=DOMAIN): last_step=True, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -89,7 +92,7 @@ class SleepIQFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth.""" errors: dict[str, str] = {} assert self._reauth_entry is not None diff --git a/homeassistant/components/slimproto/config_flow.py b/homeassistant/components/slimproto/config_flow.py index 7e2e96f74dc..24457493f9b 100644 --- a/homeassistant/components/slimproto/config_flow.py +++ b/homeassistant/components/slimproto/config_flow.py @@ -4,8 +4,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DEFAULT_NAME, DOMAIN @@ -17,7 +16,7 @@ class SlimProtoConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/sma/config_flow.py b/homeassistant/components/sma/config_flow.py index 070610d6ae2..8824b4242fa 100644 --- a/homeassistant/components/sma/config_flow.py +++ b/homeassistant/components/sma/config_flow.py @@ -7,9 +7,9 @@ from typing import Any import pysma import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_VERIFY_SSL -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -18,9 +18,7 @@ from .const import CONF_GROUP, DOMAIN, GROUPS _LOGGER = logging.getLogger(__name__) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, Any]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect.""" session = async_get_clientsession(hass, verify_ssl=data[CONF_VERIFY_SSL]) @@ -37,7 +35,7 @@ async def validate_input( return device_info -class SmaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SmaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for SMA.""" VERSION = 1 @@ -54,7 +52,7 @@ class SmaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """First step in config flow.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/smappee/config_flow.py b/homeassistant/components/smappee/config_flow.py index e57071b4938..1b79d4ff622 100644 --- a/homeassistant/components/smappee/config_flow.py +++ b/homeassistant/components/smappee/config_flow.py @@ -5,8 +5,8 @@ from pysmappee import helper, mqtt import voluptuous as vol from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from . import api @@ -39,7 +39,7 @@ class SmappeeFlowHandler( async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" if not discovery_info.hostname.startswith(SUPPORTED_LOCAL_DEVICES): diff --git a/homeassistant/components/smart_meter_texas/config_flow.py b/homeassistant/components/smart_meter_texas/config_flow.py index dc0e4e93eff..fef3b579414 100644 --- a/homeassistant/components/smart_meter_texas/config_flow.py +++ b/homeassistant/components/smart_meter_texas/config_flow.py @@ -9,8 +9,10 @@ from smart_meter_texas.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from .const import DOMAIN @@ -22,7 +24,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -44,7 +46,7 @@ async def validate_input(hass: core.HomeAssistant, data): return {"title": account.username} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SMTConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Smart Meter Texas.""" VERSION = 1 @@ -76,9 +78,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/smartthings/config_flow.py b/homeassistant/components/smartthings/config_flow.py index 5e3451dfbce..212f8add07d 100644 --- a/homeassistant/components/smartthings/config_flow.py +++ b/homeassistant/components/smartthings/config_flow.py @@ -7,7 +7,7 @@ from pysmartthings import APIResponseError, AppOAuth, SmartThings from pysmartthings.installedapp import format_install_url import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -35,7 +35,7 @@ from .smartapp import ( _LOGGER = logging.getLogger(__name__) -class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SmartThingsFlowHandler(ConfigFlow, domain=DOMAIN): """Handle configuration of SmartThings integrations.""" VERSION = 2 diff --git a/homeassistant/components/smarttub/config_flow.py b/homeassistant/components/smarttub/config_flow.py index e1637b86d84..5d9767e5fe3 100644 --- a/homeassistant/components/smarttub/config_flow.py +++ b/homeassistant/components/smarttub/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from smarttub import LoginFailed import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN from .controller import SmartTubController @@ -19,7 +18,7 @@ DATA_SCHEMA = vol.Schema( ) -class SmartTubConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SmartTubConfigFlow(ConfigFlow, domain=DOMAIN): """SmartTub configuration flow.""" VERSION = 1 @@ -28,7 +27,7 @@ class SmartTubConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Instantiate config flow.""" super().__init__() self._reauth_input: Mapping[str, Any] | None = None - self._reauth_entry: config_entries.ConfigEntry | None = None + self._reauth_entry: ConfigEntry | None = None async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" @@ -67,7 +66,9 @@ class SmartTubConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Get new credentials if the current ones don't work anymore.""" self._reauth_input = entry_data self._reauth_entry = self.hass.config_entries.async_get_entry( diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index bfa38f317a9..dbb804f0c8a 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -6,10 +6,9 @@ from typing import Any from smhi.smhi_lib import Smhi, SmhiForecastException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from homeassistant.helpers.selector import LocationSelector @@ -30,14 +29,14 @@ async def async_check_location( return True -class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SmhiFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for SMHI component.""" VERSION = 2 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/sms/config_flow.py b/homeassistant/components/sms/config_flow.py index df3530764cb..7b507bac9b2 100644 --- a/homeassistant/components/sms/config_flow.py +++ b/homeassistant/components/sms/config_flow.py @@ -4,8 +4,10 @@ import logging import gammu import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_DEVICE +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import selector from .const import CONF_BAUD_SPEED, DEFAULT_BAUD_SPEED, DEFAULT_BAUD_SPEEDS, DOMAIN @@ -23,7 +25,7 @@ DATA_SCHEMA = vol.Schema( ) -async def get_imei_from_config(hass: core.HomeAssistant, data): +async def get_imei_from_config(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -48,7 +50,7 @@ async def get_imei_from_config(hass: core.HomeAssistant, data): return imei -class SMSFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SMSFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for SMS integration.""" VERSION = 1 @@ -81,5 +83,5 @@ class SMSFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user(user_input) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/snapcast/config_flow.py b/homeassistant/components/snapcast/config_flow.py index 479d1d648b8..c9f69c48ab5 100644 --- a/homeassistant/components/snapcast/config_flow.py +++ b/homeassistant/components/snapcast/config_flow.py @@ -9,9 +9,8 @@ import snapcast.control from snapcast.control.server import CONTROL_PORT import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.data_entry_flow import FlowResult from .const import DEFAULT_TITLE, DOMAIN @@ -28,7 +27,7 @@ SNAPCAST_SCHEMA = vol.Schema( class SnapcastConfigFlow(ConfigFlow, domain=DOMAIN): """Snapcast config flow.""" - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle first step.""" errors = {} if user_input: diff --git a/homeassistant/components/snooz/config_flow.py b/homeassistant/components/snooz/config_flow.py index 7174fbc358c..be0fcbf20de 100644 --- a/homeassistant/components/snooz/config_flow.py +++ b/homeassistant/components/snooz/config_flow.py @@ -14,9 +14,8 @@ from homeassistant.components.bluetooth import ( async_discovered_service_info, async_process_advertisements, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -45,7 +44,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -57,7 +56,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovery is not None @@ -77,7 +76,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: name = user_input[CONF_NAME] @@ -128,7 +127,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_wait_for_pairing_mode( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Wait for device to enter pairing mode.""" if not self._pairing_task: self._pairing_task = self.hass.async_create_task( @@ -153,7 +152,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_pairing_complete( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Create a configuration entry for a device that entered pairing mode.""" assert self._discovery @@ -166,7 +165,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_pairing_timeout( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Inform the user that the device never entered pairing mode.""" if user_input is not None: return await self.async_step_wait_for_pairing_mode() @@ -174,7 +173,7 @@ class SnoozConfigFlow(ConfigFlow, domain=DOMAIN): self._set_confirm_only() return self.async_show_form(step_id="pairing_timeout") - def _create_snooz_entry(self, discovery: DiscoveredSnooz) -> FlowResult: + def _create_snooz_entry(self, discovery: DiscoveredSnooz) -> ConfigFlowResult: assert discovery.device.display_name return self.async_create_entry( title=discovery.device.display_name, diff --git a/homeassistant/components/solaredge/config_flow.py b/homeassistant/components/solaredge/config_flow.py index 8bdf6a4b4aa..6c80cd2496b 100644 --- a/homeassistant/components/solaredge/config_flow.py +++ b/homeassistant/components/solaredge/config_flow.py @@ -7,16 +7,15 @@ from requests.exceptions import ConnectTimeout, HTTPError import solaredge import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.util import slugify from .const import CONF_SITE_ID, DEFAULT_NAME, DOMAIN -class SolarEdgeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SolarEdgeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -56,7 +55,7 @@ class SolarEdgeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step when user initializes a integration.""" self._errors = {} if user_input is not None: diff --git a/homeassistant/components/solarlog/config_flow.py b/homeassistant/components/solarlog/config_flow.py index 86fc0607bf3..021bc24364a 100644 --- a/homeassistant/components/solarlog/config_flow.py +++ b/homeassistant/components/solarlog/config_flow.py @@ -6,7 +6,7 @@ from requests.exceptions import HTTPError, Timeout from sunwatcher.solarlog.solarlog import SolarLog import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify @@ -24,7 +24,7 @@ def solarlog_entries(hass: HomeAssistant): } -class SolarLogConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SolarLogConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for solarlog.""" VERSION = 1 diff --git a/homeassistant/components/solax/config_flow.py b/homeassistant/components/solax/config_flow.py index 2334fd0def2..8b3566e80eb 100644 --- a/homeassistant/components/solax/config_flow.py +++ b/homeassistant/components/solax/config_flow.py @@ -8,9 +8,8 @@ from solax import real_time_api from solax.discovery import DiscoveryError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT -from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv from .const import DOMAIN @@ -39,12 +38,12 @@ async def validate_api(data) -> str: return response.serial_number -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SolaxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Solax.""" async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, Any] = {} if user_input is None: diff --git a/homeassistant/components/soma/config_flow.py b/homeassistant/components/soma/config_flow.py index ca6f1fabf30..db8b75e1422 100644 --- a/homeassistant/components/soma/config_flow.py +++ b/homeassistant/components/soma/config_flow.py @@ -5,7 +5,7 @@ from api.soma_api import SomaApi from requests import RequestException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST, CONF_PORT from .const import DOMAIN @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_PORT = 3000 -class SomaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SomaFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index e42191c1230..db8dcb85383 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -7,11 +7,17 @@ import logging from somfy_mylink_synergy import SomfyMyLinkSynergy import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import dhcp +from homeassistant.config_entries import ( + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PORT -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import format_mac from .const import ( @@ -28,7 +34,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from schema with values provided by the user. @@ -49,7 +55,7 @@ async def validate_input(hass: core.HomeAssistant, data): return {"title": f"MyLink {data[CONF_HOST]}"} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SomfyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Somfy MyLink.""" VERSION = 1 @@ -60,7 +66,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.mac = None self.ip_address = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" self._async_abort_entries_match({CONF_HOST: discovery_info.ip}) @@ -112,16 +120,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for somfy_mylink.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self.options = deepcopy(dict(config_entry.options)) @@ -146,7 +154,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Handle options flow.""" - if self.config_entry.state is not config_entries.ConfigEntryState.LOADED: + if self.config_entry.state is not ConfigEntryState.LOADED: _LOGGER.error("MyLink must be connected to manage device options") return self.async_abort(reason="cannot_connect") @@ -194,9 +202,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index 3ea386faa78..5631c3a70e0 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -11,10 +11,14 @@ from aiopyarr.sonarr_client import SonarrClient import voluptuous as vol import yarl -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( @@ -63,7 +67,9 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): """Get the options flow for this handler.""" return SonarrOptionsFlowHandler(config_entry) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -71,7 +77,7 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: assert self.entry is not None @@ -85,7 +91,7 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -122,7 +128,9 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def _async_reauth_update_entry(self, data: dict[str, Any]) -> FlowResult: + async def _async_reauth_update_entry( + self, data: dict[str, Any] + ) -> ConfigFlowResult: """Update existing config entry.""" assert self.entry is not None self.hass.config_entries.async_update_entry(self.entry, data=data) @@ -157,7 +165,7 @@ class SonarrOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, int] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Sonarr options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index a1e7938f3f8..03c1aa4bdf2 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -7,10 +7,9 @@ from urllib.parse import urlparse from songpal import Device, SongpalException import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.data_entry_flow import FlowResult from .const import CONF_ENDPOINT, DOMAIN @@ -27,7 +26,7 @@ class SongpalConfig: self.endpoint = endpoint -class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SongpalConfigFlow(ConfigFlow, domain=DOMAIN): """Songpal configuration flow.""" VERSION = 1 @@ -93,7 +92,9 @@ class SongpalConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): data={CONF_NAME: self.conf.name, CONF_ENDPOINT: self.conf.endpoint}, ) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered Songpal device.""" await self.async_set_unique_id(discovery_info.upnp[ssdp.ATTR_UPNP_UDN]) self._abort_if_unique_id_configured() diff --git a/homeassistant/components/sonos/config_flow.py b/homeassistant/components/sonos/config_flow.py index cc453f14691..daf9e5c7405 100644 --- a/homeassistant/components/sonos/config_flow.py +++ b/homeassistant/components/sonos/config_flow.py @@ -3,8 +3,8 @@ from collections.abc import Awaitable import dataclasses from homeassistant.components import ssdp, zeroconf +from homeassistant.config_entries import ConfigFlowResult from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.config_entry_flow import DiscoveryFlowHandler from .const import DATA_SONOS_DISCOVERY_MANAGER, DOMAIN, UPNP_ST @@ -25,7 +25,7 @@ class SonosDiscoveryFlowHandler(DiscoveryFlowHandler[Awaitable[bool]], domain=DO async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by zeroconf.""" hostname = discovery_info.hostname if hostname is None or not hostname.lower().startswith("sonos"): diff --git a/homeassistant/components/soundtouch/config_flow.py b/homeassistant/components/soundtouch/config_flow.py index 8f9b993d0d8..757e14438e9 100644 --- a/homeassistant/components/soundtouch/config_flow.py +++ b/homeassistant/components/soundtouch/config_flow.py @@ -5,10 +5,9 @@ from libsoundtouch import soundtouch_device from requests import RequestException import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.zeroconf import ZeroconfServiceInfo +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from .const import DOMAIN @@ -16,7 +15,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -class SoundtouchConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SoundtouchConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Bose SoundTouch.""" VERSION = 1 @@ -53,7 +52,7 @@ class SoundtouchConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by a zeroconf discovery.""" self.host = discovery_info.host diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 58ef53b2c9c..43b4bb64a9d 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -5,9 +5,13 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_SERVER_ID, @@ -18,7 +22,7 @@ from .const import ( ) -class SpeedTestFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SpeedTestFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Speedtest.net config flow.""" VERSION = 1 @@ -26,14 +30,14 @@ class SpeedTestFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SpeedTestOptionsFlowHandler: """Get the options flow for this handler.""" return SpeedTestOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -44,17 +48,17 @@ class SpeedTestFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=DEFAULT_NAME, data=user_input) -class SpeedTestOptionsFlowHandler(config_entries.OptionsFlow): +class SpeedTestOptionsFlowHandler(OptionsFlow): """Handle SpeedTest options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self._servers: dict = {} async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the options.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/spider/config_flow.py b/homeassistant/components/spider/config_flow.py index eaf30bc541f..a8466041dde 100644 --- a/homeassistant/components/spider/config_flow.py +++ b/homeassistant/components/spider/config_flow.py @@ -4,7 +4,7 @@ import logging from spiderpy.spiderapi import SpiderApi, SpiderApiException, UnauthorizedException import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME from .const import DEFAULT_SCAN_INTERVAL, DOMAIN @@ -20,7 +20,7 @@ RESULT_CONN_ERROR = "conn_error" RESULT_SUCCESS = "success" -class SpiderConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SpiderConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a Spider config flow.""" VERSION = 1 diff --git a/homeassistant/components/spotify/config_flow.py b/homeassistant/components/spotify/config_flow.py index bbfb92db091..8629e0864ff 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -7,8 +7,7 @@ from typing import Any from spotipy import Spotify -from homeassistant.config_entries import ConfigEntry -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN, SPOTIFY_SCOPES @@ -34,7 +33,7 @@ class SpotifyFlowHandler( """Extra data that needs to be appended to the authorize url.""" return {"scope": ",".join(SPOTIFY_SCOPES)} - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for Spotify.""" spotify = Spotify(auth=data["token"]["access_token"]) @@ -56,7 +55,9 @@ class SpotifyFlowHandler( return self.async_create_entry(title=name, data=data) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon migration of old entries.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -66,7 +67,7 @@ class SpotifyFlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if self.reauth_entry is None: return self.async_abort(reason="reauth_account_mismatch") diff --git a/homeassistant/components/sql/config_flow.py b/homeassistant/components/sql/config_flow.py index a697bdc51a7..7b03a843941 100644 --- a/homeassistant/components/sql/config_flow.py +++ b/homeassistant/components/sql/config_flow.py @@ -12,13 +12,18 @@ import sqlparse from sqlparse.exceptions import SQLParseError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.recorder import CONF_DB_URL, get_instance from homeassistant.components.sensor import ( CONF_STATE_CLASS, SensorDeviceClass, SensorStateClass, ) +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_NAME, @@ -26,7 +31,6 @@ from homeassistant.const import ( CONF_VALUE_TEMPLATE, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .const import CONF_COLUMN_NAME, CONF_QUERY, DOMAIN @@ -128,7 +132,7 @@ def validate_query(db_url: str, query: str, column: str) -> bool: return True -class SQLConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SQLConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for SQL integration.""" VERSION = 1 @@ -136,14 +140,14 @@ class SQLConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> SQLOptionsFlowHandler: """Get the options flow for this handler.""" return SQLOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step.""" errors = {} description_placeholders = {} @@ -204,12 +208,12 @@ class SQLConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class SQLOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): +class SQLOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle SQL options.""" async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage SQL options.""" errors = {} description_placeholders = {} diff --git a/homeassistant/components/squeezebox/config_flow.py b/homeassistant/components/squeezebox/config_flow.py index d2786bf213b..c05f4ac9fef 100644 --- a/homeassistant/components/squeezebox/config_flow.py +++ b/homeassistant/components/squeezebox/config_flow.py @@ -7,10 +7,11 @@ from typing import TYPE_CHECKING from pysqueezebox import Server, async_discover import voluptuous as vol -from homeassistant import config_entries, data_entry_flow from homeassistant.components import dhcp from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.entity_registry import async_get @@ -61,7 +62,7 @@ def _base_schema(discovery_info=None): return vol.Schema(base_schema) -class SqueezeboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SqueezeboxConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Logitech Squeezebox.""" VERSION = 1 @@ -187,7 +188,7 @@ class SqueezeboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_dhcp( self, discovery_info: dhcp.DhcpServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle dhcp discovery of a Squeezebox player.""" _LOGGER.debug( "Reached dhcp discovery of a player with info: %s", discovery_info @@ -204,7 +205,7 @@ class SqueezeboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # if we have detected this player, do nothing. if not, there must be a server out there for us to configure, so start the normal user flow (which tries to autodetect server) if registry.async_get_entity_id(MP_DOMAIN, DOMAIN, self.unique_id) is not None: # this player is already known, so do nothing other than mark as configured - raise data_entry_flow.AbortFlow("already_configured") + raise AbortFlow("already_configured") # if the player is unknown, then we likely need to configure its server return await self.async_step_user() diff --git a/homeassistant/components/srp_energy/config_flow.py b/homeassistant/components/srp_energy/config_flow.py index ac32e005e06..089b036fea7 100644 --- a/homeassistant/components/srp_energy/config_flow.py +++ b/homeassistant/components/srp_energy/config_flow.py @@ -6,10 +6,9 @@ from typing import Any from srpenergy.client import SrpEnergyClient import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ID, CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import CONF_IS_TOU, DOMAIN, LOGGER @@ -35,13 +34,13 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return is_valid -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SRPEnergyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle an SRP Energy config flow.""" VERSION = 1 @callback - def _show_form(self, errors: dict[str, Any]) -> FlowResult: + def _show_form(self, errors: dict[str, Any]) -> ConfigFlowResult: """Show the form to the user.""" LOGGER.debug("Show Form") return self.async_show_form( @@ -62,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" LOGGER.debug("Config entry") errors: dict[str, str] = {} diff --git a/homeassistant/components/starline/config_flow.py b/homeassistant/components/starline/config_flow.py index a00e24ca2a6..30a95ed6869 100644 --- a/homeassistant/components/starline/config_flow.py +++ b/homeassistant/components/starline/config_flow.py @@ -4,8 +4,9 @@ from __future__ import annotations from starline import StarlineAuth import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback from .const import ( _LOGGER, @@ -24,7 +25,7 @@ from .const import ( ) -class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class StarlineFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a StarLine config flow.""" VERSION = 1 @@ -84,7 +85,7 @@ class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._async_authenticate_user(error) return self._async_form_auth_captcha(error) - @core.callback + @callback def _async_form_auth_app(self, error=None): """Authenticate application form.""" errors = {} @@ -106,7 +107,7 @@ class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - @core.callback + @callback def _async_form_auth_user(self, error=None): """Authenticate user form.""" errors = {} @@ -128,7 +129,7 @@ class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - @core.callback + @callback def _async_form_auth_mfa(self, error=None): """Authenticate mfa form.""" errors = {} @@ -148,7 +149,7 @@ class StarlineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={"phone_number": self._phone_number}, ) - @core.callback + @callback def _async_form_auth_captcha(self, error=None): """Captcha verification form.""" errors = {} diff --git a/homeassistant/components/starlink/config_flow.py b/homeassistant/components/starlink/config_flow.py index 987a84796f1..14840ccf46d 100644 --- a/homeassistant/components/starlink/config_flow.py +++ b/homeassistant/components/starlink/config_flow.py @@ -6,9 +6,8 @@ from typing import Any from starlink_grpc import ChannelContext, GrpcError, get_id import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_IP_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -22,7 +21,7 @@ class StarlinkConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Ask the user for a server address and a name for the system.""" errors = {} if user_input: diff --git a/homeassistant/components/steam_online/config_flow.py b/homeassistant/components/steam_online/config_flow.py index 094db9ba207..e3a239c7181 100644 --- a/homeassistant/components/steam_online/config_flow.py +++ b/homeassistant/components/steam_online/config_flow.py @@ -7,10 +7,15 @@ from typing import Any import steam import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_API_KEY, Platform from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv, entity_registry as er from .const import CONF_ACCOUNT, CONF_ACCOUNTS, DOMAIN, LOGGER, PLACEHOLDERS @@ -27,24 +32,24 @@ def validate_input(user_input: dict[str, str]) -> dict[str, str | int]: return names["response"]["players"]["player"][0] -class SteamFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SteamFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow for Steam.""" def __init__(self) -> None: """Initialize the flow.""" - self.entry: config_entries.ConfigEntry | None = None + self.entry: ConfigEntry | None = None @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, - ) -> config_entries.OptionsFlow: + config_entry: ConfigEntry, + ) -> OptionsFlow: """Get the options flow for this handler.""" return SteamOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is None and self.entry: @@ -65,7 +70,7 @@ class SteamFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors["base"] = "unknown" if not errors: entry = await self.async_set_unique_id(user_input[CONF_ACCOUNT]) - if entry and self.source == config_entries.SOURCE_REAUTH: + if entry and self.source == SOURCE_REAUTH: self.hass.config_entries.async_update_entry(entry, data=user_input) await self.hass.config_entries.async_reload(entry.entry_id) return self.async_abort(reason="reauth_successful") @@ -92,7 +97,9 @@ class SteamFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=PLACEHOLDERS, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -100,7 +107,7 @@ class SteamFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is not None: return await self.async_step_user() @@ -116,17 +123,17 @@ def _batch_ids(ids: list[str]) -> Iterator[list[str]]: yield ids[i : i + MAX_IDS_TO_REQUEST] -class SteamOptionsFlowHandler(config_entries.OptionsFlow): +class SteamOptionsFlowHandler(OptionsFlow): """Handle Steam client options.""" - def __init__(self, entry: config_entries.ConfigEntry) -> None: + def __init__(self, entry: ConfigEntry) -> None: """Initialize options flow.""" self.entry = entry self.options = dict(entry.options) async def async_step_init( self, user_input: dict[str, dict[str, str]] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Steam options.""" if user_input is not None: await self.hass.config_entries.async_unload(self.entry.entry_id) diff --git a/homeassistant/components/steamist/config_flow.py b/homeassistant/components/steamist/config_flow.py index 3d5fe000f35..2a5a4cf5d07 100644 --- a/homeassistant/components/steamist/config_flow.py +++ b/homeassistant/components/steamist/config_flow.py @@ -8,11 +8,10 @@ from aiosteamist import Steamist from discovery30303 import Device30303, normalize_mac import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MODEL, CONF_NAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import DiscoveryInfoType @@ -28,7 +27,7 @@ from .discovery import ( _LOGGER = logging.getLogger(__name__) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SteamistConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Steamist.""" VERSION = 1 @@ -38,7 +37,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_devices: dict[str, Device30303] = {} self._discovered_device: Device30303 | None = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" self._discovered_device = Device30303( ipaddress=discovery_info.ip, @@ -50,7 +51,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle integration discovery.""" self._discovered_device = Device30303( ipaddress=discovery_info["ipaddress"], @@ -60,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return await self._async_handle_discovery() - async def _async_handle_discovery(self) -> FlowResult: + async def _async_handle_discovery(self) -> ConfigFlowResult: """Handle any discovery.""" device = self._discovered_device assert device is not None @@ -91,7 +92,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -108,7 +109,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) @callback - def _async_create_entry_from_device(self, device: Device30303) -> FlowResult: + def _async_create_entry_from_device(self, device: Device30303) -> ConfigFlowResult: """Create a config entry from a device.""" self._async_abort_entries_match({CONF_HOST: device.ipaddress}) data = {CONF_HOST: device.ipaddress, CONF_NAME: device.name} @@ -121,7 +122,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step to pick discovered device.""" if user_input is not None: mac = user_input[CONF_DEVICE] @@ -153,7 +154,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} diff --git a/homeassistant/components/stookalert/config_flow.py b/homeassistant/components/stookalert/config_flow.py index 02189bc161f..bc37fbded72 100644 --- a/homeassistant/components/stookalert/config_flow.py +++ b/homeassistant/components/stookalert/config_flow.py @@ -5,8 +5,7 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import CONF_PROVINCE, DOMAIN, PROVINCES @@ -18,7 +17,7 @@ class StookalertFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: await self.async_set_unique_id(user_input[CONF_PROVINCE]) diff --git a/homeassistant/components/stookwijzer/config_flow.py b/homeassistant/components/stookwijzer/config_flow.py index fdf4cc06f37..7a4ddae91c8 100644 --- a/homeassistant/components/stookwijzer/config_flow.py +++ b/homeassistant/components/stookwijzer/config_flow.py @@ -5,9 +5,8 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.selector import LocationSelector from .const import DOMAIN @@ -20,7 +19,7 @@ class StookwijzerFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if user_input is not None: diff --git a/homeassistant/components/streamlabswater/config_flow.py b/homeassistant/components/streamlabswater/config_flow.py index 5cede037d5a..ebe163c201f 100644 --- a/homeassistant/components/streamlabswater/config_flow.py +++ b/homeassistant/components/streamlabswater/config_flow.py @@ -6,10 +6,9 @@ from typing import Any from streamlabswater.streamlabswater import StreamlabsClient import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN, LOGGER @@ -32,7 +31,7 @@ class StreamlabsConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -57,7 +56,7 @@ class StreamlabsConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Import the yaml config.""" self._async_abort_entries_match(user_input) try: diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index b21feab7843..aeee2cb7675 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -14,7 +14,12 @@ from subarulink import ( from subarulink.const import COUNTRY_CAN, COUNTRY_USA import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_COUNTRY, CONF_DEVICE_ID, @@ -23,7 +28,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client, config_validation as cv from .const import CONF_UPDATE_ENABLED, DOMAIN @@ -34,7 +38,7 @@ CONF_VALIDATION_CODE = "validation_code" PIN_SCHEMA = vol.Schema({vol.Required(CONF_PIN): str}) -class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SubaruConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Subaru.""" VERSION = 1 @@ -46,7 +50,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" error = None @@ -96,7 +100,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) @@ -129,7 +133,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_two_factor( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select contact method and request 2FA code from Subaru.""" error = None if user_input: @@ -157,7 +161,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_two_factor_validate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Validate received 2FA code with Subaru.""" error = None if user_input: @@ -182,7 +186,7 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pin( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle second part of config flow, if required.""" error = None if user_input and self.controller.update_saved_pin(user_input[CONF_PIN]): @@ -202,16 +206,16 @@ class SubaruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="pin", data_schema=PIN_SCHEMA, errors=error) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle a option flow for Subaru.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/suez_water/config_flow.py b/homeassistant/components/suez_water/config_flow.py index d01b8035a0c..667e1b7abaa 100644 --- a/homeassistant/components/suez_water/config_flow.py +++ b/homeassistant/components/suez_water/config_flow.py @@ -8,9 +8,8 @@ from pysuez import SuezClient from pysuez.client import PySuezError import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import CONF_COUNTER_ID, DOMAIN @@ -51,7 +50,7 @@ class SuezWaterConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -75,7 +74,7 @@ class SuezWaterConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Import the yaml config.""" await self.async_set_unique_id(user_input[CONF_USERNAME]) self._abort_if_unique_id_configured() diff --git a/homeassistant/components/sun/config_flow.py b/homeassistant/components/sun/config_flow.py index ae2e5a42efc..fbeeaaea176 100644 --- a/homeassistant/components/sun/config_flow.py +++ b/homeassistant/components/sun/config_flow.py @@ -3,8 +3,7 @@ from __future__ import annotations from typing import Any -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DEFAULT_NAME, DOMAIN @@ -16,7 +15,7 @@ class SunConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -26,6 +25,6 @@ class SunConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="user") - async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Handle import from configuration.yaml.""" return await self.async_step_user(user_input) diff --git a/homeassistant/components/sunweg/config_flow.py b/homeassistant/components/sunweg/config_flow.py index cd24a4722e9..c8a8c4e8b2f 100644 --- a/homeassistant/components/sunweg/config_flow.py +++ b/homeassistant/components/sunweg/config_flow.py @@ -2,15 +2,14 @@ from sunweg.api import APIHelper import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import CONF_PLANT_ID, DOMAIN -class SunWEGConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SunWEGConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow class.""" VERSION = 1 @@ -21,7 +20,7 @@ class SunWEGConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.data: dict = {} @callback - def _async_show_user_form(self, errors=None) -> FlowResult: + def _async_show_user_form(self, errors=None) -> ConfigFlowResult: """Show the form to the user.""" data_schema = vol.Schema( { @@ -34,7 +33,7 @@ class SunWEGConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=data_schema, errors=errors ) - async def async_step_user(self, user_input=None) -> FlowResult: + async def async_step_user(self, user_input=None) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return self._async_show_user_form() @@ -50,7 +49,7 @@ class SunWEGConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.data = user_input return await self.async_step_plant() - async def async_step_plant(self, user_input=None) -> FlowResult: + async def async_step_plant(self, user_input=None) -> ConfigFlowResult: """Handle adding a "plant" to Home Assistant.""" plant_list = await self.hass.async_add_executor_job(self.api.listPlants) diff --git a/homeassistant/components/surepetcare/config_flow.py b/homeassistant/components/surepetcare/config_flow.py index 81607b582c1..53f820af3d9 100644 --- a/homeassistant/components/surepetcare/config_flow.py +++ b/homeassistant/components/surepetcare/config_flow.py @@ -9,10 +9,9 @@ import surepy from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, SURE_API_TIMEOUT @@ -42,7 +41,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return {CONF_TOKEN: token} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SurePetCareConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Sure Petcare.""" VERSION = 1 @@ -53,7 +52,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=USER_DATA_SCHEMA) @@ -83,14 +82,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._username = entry_data[CONF_USERNAME] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/swiss_public_transport/config_flow.py b/homeassistant/components/swiss_public_transport/config_flow.py index e864f31cd6c..59d01743de4 100644 --- a/homeassistant/components/swiss_public_transport/config_flow.py +++ b/homeassistant/components/swiss_public_transport/config_flow.py @@ -9,9 +9,8 @@ from opendata_transport.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -27,7 +26,7 @@ DATA_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -class SwissPublicTransportConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SwissPublicTransportConfigFlow(ConfigFlow, domain=DOMAIN): """Swiss public transport config flow.""" VERSION = 1 @@ -35,7 +34,7 @@ class SwissPublicTransportConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Async user step to set up the connection.""" errors: dict[str, str] = {} if user_input is not None: @@ -70,7 +69,7 @@ class SwissPublicTransportConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders=PLACEHOLDERS, ) - async def async_step_import(self, import_input: dict[str, Any]) -> FlowResult: + async def async_step_import(self, import_input: dict[str, Any]) -> ConfigFlowResult: """Async import step to set up the connection.""" await self.async_set_unique_id( f"{import_input[CONF_START]} {import_input[CONF_DESTINATION]}" diff --git a/homeassistant/components/switchbee/config_flow.py b/homeassistant/components/switchbee/config_flow.py index 8f109c7bf26..956142961aa 100644 --- a/homeassistant/components/switchbee/config_flow.py +++ b/homeassistant/components/switchbee/config_flow.py @@ -8,10 +8,9 @@ from switchbee.api.central_unit import SwitchBeeError from switchbee.api.polling import CentralUnitPolling import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -53,14 +52,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> str: return format_mac(api.mac) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SwitchBeeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for SwitchBee Smart Home.""" VERSION = 2 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index 35e76d8bbb3..94261a1faae 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -18,7 +18,12 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_ADDRESS, CONF_PASSWORD, @@ -26,7 +31,7 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from .const import ( CONF_ENCRYPTION_KEY, @@ -78,7 +83,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" _LOGGER.debug("Discovered bluetooth device: %s", discovery_info.as_dict()) await self.async_set_unique_id(format_unique_id(discovery_info.address)) @@ -109,7 +114,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def _async_create_entry_from_discovery( self, user_input: dict[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: """Create an entry from a discovery.""" assert self._discovered_adv is not None discovery = self._discovered_adv @@ -126,7 +131,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm a single device.""" assert self._discovered_adv is not None if user_input is not None: @@ -143,7 +148,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_password( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the password step.""" assert self._discovered_adv is not None if user_input is not None: @@ -162,7 +167,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_lock_auth( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the SwitchBot API auth step.""" errors = {} assert self._discovered_adv is not None @@ -204,7 +209,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_lock_choose_method( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the SwitchBot API chose method step.""" assert self._discovered_adv is not None @@ -218,7 +223,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_lock_key( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the encryption key step.""" errors = {} assert self._discovered_adv is not None @@ -285,7 +290,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" errors: dict[str, str] = {} device_adv: SwitchBotAdvertisement | None = None @@ -335,7 +340,7 @@ class SwitchbotOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Switchbot options.""" if user_input is not None: # Update common entity options for all other entities. diff --git a/homeassistant/components/switchbot_cloud/config_flow.py b/homeassistant/components/switchbot_cloud/config_flow.py index 5c99567968c..c01699b8c5d 100644 --- a/homeassistant/components/switchbot_cloud/config_flow.py +++ b/homeassistant/components/switchbot_cloud/config_flow.py @@ -6,9 +6,8 @@ from typing import Any from switchbot_api import CannotConnect, InvalidAuth, SwitchBotAPI import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN, ENTRY_TITLE @@ -22,14 +21,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class SwitchBotCloudConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SwitchBotCloudConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for SwitchBot via API.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/switcher_kis/config_flow.py b/homeassistant/components/switcher_kis/config_flow.py index d196bae8568..f459e6e8d76 100644 --- a/homeassistant/components/switcher_kis/config_flow.py +++ b/homeassistant/components/switcher_kis/config_flow.py @@ -3,17 +3,18 @@ from __future__ import annotations from typing import Any -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DATA_DISCOVERY, DOMAIN from .utils import async_discover_devices -class SwitcherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class SwitcherFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Switcher config flow.""" - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Handle a flow initiated by import.""" if self._async_current_entries(True): return self.async_abort(reason="single_instance_allowed") @@ -22,7 +23,7 @@ class SwitcherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if self._async_current_entries(True): return self.async_abort(reason="single_instance_allowed") @@ -37,7 +38,7 @@ class SwitcherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of the config flow.""" discovered_devices = await self.hass.data[DOMAIN][DATA_DISCOVERY] diff --git a/homeassistant/components/syncthing/config_flow.py b/homeassistant/components/syncthing/config_flow.py index 7421a385f08..e936cc23183 100644 --- a/homeassistant/components/syncthing/config_flow.py +++ b/homeassistant/components/syncthing/config_flow.py @@ -2,8 +2,10 @@ import aiosyncthing import voluptuous as vol -from homeassistant import config_entries, core, exceptions +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from .const import DEFAULT_URL, DEFAULT_VERIFY_SSL, DOMAIN @@ -16,7 +18,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): +async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect.""" try: @@ -34,7 +36,7 @@ async def validate_input(hass: core.HomeAssistant, data): raise CannotConnect from error -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SyncThingConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for syncthing.""" VERSION = 1 @@ -60,9 +62,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/syncthru/config_flow.py b/homeassistant/components/syncthru/config_flow.py index 664de1f6d96..8cd1c2c7b3b 100644 --- a/homeassistant/components/syncthru/config_flow.py +++ b/homeassistant/components/syncthru/config_flow.py @@ -7,16 +7,15 @@ from pysyncthru import ConnectionMode, SyncThru, SyncThruAPINotSupported from url_normalize import url_normalize import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import ssdp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME, CONF_URL -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DEFAULT_MODEL, DEFAULT_NAME_TEMPLATE, DOMAIN -class SyncThruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class SyncThruConfigFlow(ConfigFlow, domain=DOMAIN): """Samsung SyncThru config flow.""" VERSION = 1 @@ -30,7 +29,9 @@ class SyncThruConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return await self._async_show_form(step_id="user") return await self._async_check_and_create("user", user_input) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle SSDP initiated flow.""" await self.async_set_unique_id(discovery_info.upnp[ssdp.ATTR_UPNP_UDN]) self._abort_if_unique_id_configured() diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index f49eb7feed1..4da188732ad 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -17,9 +17,13 @@ from synology_dsm.exceptions import ( ) import voluptuous as vol -from homeassistant import exceptions from homeassistant.components import ssdp, zeroconf -from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_DISKS, CONF_HOST, @@ -34,7 +38,7 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import DiscoveryInfoType @@ -130,7 +134,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): step_id: str, user_input: dict[str, Any] | None = None, errors: dict[str, str] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" if not user_input: user_input = {} @@ -156,7 +160,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_validate_input_create_entry( self, user_input: dict[str, Any], step_id: str - ) -> FlowResult: + ) -> ConfigFlowResult: """Process user input and create new or update existing config entry.""" host = user_input[CONF_HOST] port = user_input.get(CONF_PORT) @@ -231,7 +235,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" step = "user" if not user_input: @@ -240,7 +244,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered synology_dsm via zeroconf.""" discovered_macs = [ format_synology_mac(mac) @@ -253,7 +257,9 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): friendly_name = discovery_info.name.removesuffix(HTTP_SUFFIX) return await self._async_from_discovery(host, friendly_name, discovered_macs) - async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: + async def async_step_ssdp( + self, discovery_info: ssdp.SsdpServiceInfo + ) -> ConfigFlowResult: """Handle a discovered synology_dsm via ssdp.""" parsed_url = urlparse(discovery_info.ssdp_location) upnp_friendly_name: str = discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME] @@ -267,7 +273,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def _async_from_discovery( self, host: str, friendly_name: str, discovered_macs: list[str] - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a discovered synology_dsm via zeroconf or ssdp.""" existing_entry = None for discovered_mac in discovered_macs: @@ -307,7 +313,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_link( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Link a config entry from discovery.""" step = "link" if not user_input: @@ -315,7 +321,9 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): user_input = {**self.discovered_conf, **user_input} return await self.async_validate_input_create_entry(user_input, step_id=step) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_conf = entry_data self.context["title_placeholders"][CONF_HOST] = entry_data[CONF_HOST] @@ -324,7 +332,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Perform reauth confirm upon an API authentication error.""" step = "reauth_confirm" if not user_input: @@ -334,7 +342,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_2sa( self, user_input: dict[str, Any], errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Enter 2SA code to anthenticate.""" if not self.saved_user_input: self.saved_user_input = user_input @@ -370,7 +378,7 @@ class SynologyDSMOptionsFlowHandler(OptionsFlow): async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -419,5 +427,5 @@ async def _login_and_fetch_syno_info(api: SynologyDSM, otp_code: str | None) -> return api.information.serial # type: ignore[no-any-return] -class InvalidData(exceptions.HomeAssistantError): +class InvalidData(HomeAssistantError): """Error to indicate we get invalid data from the nas.""" diff --git a/homeassistant/components/system_bridge/config_flow.py b/homeassistant/components/system_bridge/config_flow.py index 0b6a8b4622b..9fcecdc63c4 100644 --- a/homeassistant/components/system_bridge/config_flow.py +++ b/homeassistant/components/system_bridge/config_flow.py @@ -16,11 +16,11 @@ from systembridgemodels.get_data import GetData from systembridgemodels.system import System import voluptuous as vol -from homeassistant import config_entries, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -107,8 +107,8 @@ async def _async_get_info( return errors, None -class ConfigFlow( - config_entries.ConfigFlow, +class SystemBridgeConfigFlow( + ConfigFlow, domain=DOMAIN, ): """Handle a config flow for System Bridge.""" @@ -123,7 +123,7 @@ class ConfigFlow( async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form( @@ -144,7 +144,7 @@ class ConfigFlow( async def async_step_authenticate( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle getting the api-key for authentication.""" errors: dict[str, str] = {} @@ -177,7 +177,7 @@ class ConfigFlow( async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" properties = discovery_info.properties host = properties.get("ip") @@ -198,7 +198,9 @@ class ConfigFlow( return await self.async_step_authenticate() - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._name = entry_data[CONF_HOST] self._input = { @@ -209,9 +211,9 @@ class ConfigFlow( return await self.async_step_authenticate() -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/systemmonitor/config_flow.py b/homeassistant/components/systemmonitor/config_flow.py index b9b95a4a094..8dcc074a0d0 100644 --- a/homeassistant/components/systemmonitor/config_flow.py +++ b/homeassistant/components/systemmonitor/config_flow.py @@ -8,8 +8,8 @@ import voluptuous as vol from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.homeassistant import DOMAIN as HOMEASSISTANT_DOMAIN +from homeassistant.config_entries import ConfigFlowResult from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import entity_registry as er from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.schema_config_entry_flow import ( @@ -138,7 +138,9 @@ class SystemMonitorConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN): return "System Monitor" @callback - def async_create_entry(self, data: Mapping[str, Any], **kwargs: Any) -> FlowResult: + def async_create_entry( + self, data: Mapping[str, Any], **kwargs: Any + ) -> ConfigFlowResult: """Finish config flow and create a config entry.""" if self._async_current_entries(): return self.async_abort(reason="already_configured") diff --git a/homeassistant/components/tado/config_flow.py b/homeassistant/components/tado/config_flow.py index f9f4f80bde1..911cf6a7aac 100644 --- a/homeassistant/components/tado/config_flow.py +++ b/homeassistant/components/tado/config_flow.py @@ -9,11 +9,16 @@ from PyTado.interface import Tado import requests.exceptions import voluptuous as vol -from homeassistant import config_entries, core, exceptions from homeassistant.components import zeroconf +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from .const import ( CONF_FALLBACK, @@ -34,9 +39,7 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input( - hass: core.HomeAssistant, data: dict[str, Any] -) -> dict[str, Any]: +async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. @@ -66,14 +69,14 @@ async def validate_input( return {"title": name, UNIQUE_ID: unique_id} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TadoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tado.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: @@ -102,7 +105,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle HomeKit discovery.""" self._async_abort_entries_match() properties = { @@ -112,7 +115,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured() return await self.async_step_user() - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import a config entry from configuration.yaml.""" _LOGGER.debug("Importing Tado from configuration.yaml") username = import_config[CONF_USERNAME] @@ -135,7 +140,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_PASSWORD: password, }, ) - except exceptions.HomeAssistantError: + except HomeAssistantError: return self.async_abort(reason="import_failed") except PyTado.exceptions.TadoWrongCredentialsException: return self.async_abort(reason="import_failed_invalid_auth") @@ -156,22 +161,22 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle an option flow for Tado.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: return self.async_create_entry(data=user_input) @@ -189,13 +194,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow): return self.async_show_form(step_id="init", data_schema=data_schema) -class CannotConnect(exceptions.HomeAssistantError): +class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" -class InvalidAuth(exceptions.HomeAssistantError): +class InvalidAuth(HomeAssistantError): """Error to indicate there is invalid auth.""" -class NoHomes(exceptions.HomeAssistantError): +class NoHomes(HomeAssistantError): """Error to indicate the account has no homes.""" diff --git a/homeassistant/components/tailscale/config_flow.py b/homeassistant/components/tailscale/config_flow.py index 5f28c566801..ca8d91acbfd 100644 --- a/homeassistant/components/tailscale/config_flow.py +++ b/homeassistant/components/tailscale/config_flow.py @@ -7,10 +7,9 @@ from typing import Any from tailscale import Tailscale, TailscaleAuthenticationError, TailscaleError import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_TAILNET, DOMAIN @@ -36,7 +35,7 @@ class TailscaleFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -82,7 +81,9 @@ class TailscaleFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle initiation of re-authentication with Tailscale.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -91,7 +92,7 @@ class TailscaleFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with Tailscale.""" errors = {} diff --git a/homeassistant/components/tailwind/config_flow.py b/homeassistant/components/tailwind/config_flow.py index 97515f17f3f..af8a2699190 100644 --- a/homeassistant/components/tailwind/config_flow.py +++ b/homeassistant/components/tailwind/config_flow.py @@ -16,9 +16,9 @@ import voluptuous as vol from homeassistant.components import zeroconf from homeassistant.components.dhcp import DhcpServiceInfo -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_TOKEN -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.selector import ( @@ -44,7 +44,7 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} @@ -84,7 +84,7 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery of a Tailwind device.""" if not (device_id := discovery_info.properties.get("device_id")): return self.async_abort(reason="no_device_id") @@ -112,7 +112,7 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" errors = {} @@ -143,7 +143,7 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, _: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, _: Mapping[str, Any]) -> ConfigFlowResult: """Handle initiation of re-authentication with a Tailwind device.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -152,7 +152,7 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with a Tailwind device.""" errors = {} @@ -183,7 +183,9 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery to update existing entries. This flow is triggered only by DHCP discovery of known devices. @@ -196,7 +198,9 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): # abort the flow with an unknown error. return self.async_abort(reason="unknown") - async def _async_step_create_entry(self, *, host: str, token: str) -> FlowResult: + async def _async_step_create_entry( + self, *, host: str, token: str + ) -> ConfigFlowResult: """Create entry.""" tailwind = Tailwind( host=host, token=token, session=async_get_clientsession(self.hass) diff --git a/homeassistant/components/tami4/config_flow.py b/homeassistant/components/tami4/config_flow.py index b36ba9c46c0..cf158cfa166 100644 --- a/homeassistant/components/tami4/config_flow.py +++ b/homeassistant/components/tami4/config_flow.py @@ -8,8 +8,7 @@ from typing import Any from Tami4EdgeAPI import Tami4EdgeAPI, exceptions import voluptuous as vol -from homeassistant import config_entries -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv @@ -23,7 +22,7 @@ _STEP_OTP_CODE_SCHEMA = vol.Schema({vol.Required("otp"): cv.string}) _PHONE_MATCHER = re.compile(r"^(\+?972)?0?(?P\d{8,9})$") -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class Tami4ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tami4Edge.""" VERSION = 1 @@ -32,7 +31,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the otp request step.""" errors = {} if user_input is not None: @@ -62,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_otp( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the otp submission step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/tankerkoenig/config_flow.py b/homeassistant/components/tankerkoenig/config_flow.py index 9bdf5ef0fe0..2c2906132d4 100644 --- a/homeassistant/components/tankerkoenig/config_flow.py +++ b/homeassistant/components/tankerkoenig/config_flow.py @@ -13,7 +13,12 @@ from aiotankerkoenig import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, @@ -25,7 +30,6 @@ from homeassistant.const import ( UnitOfLength, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( @@ -52,7 +56,7 @@ async def async_get_nearby_stations( ) -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -66,14 +70,14 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> OptionsFlowHandler: """Get the options flow for this handler.""" return OptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if not user_input: return self._show_form_user() @@ -110,7 +114,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_select_station( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step select_station of a flow initialized by the user.""" if not user_input: return self.async_show_form( @@ -126,13 +130,15 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): options={CONF_SHOW_ON_MAP: True}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Perform reauth confirm upon an API authentication error.""" if not user_input: return self._show_form_reauth() @@ -158,7 +164,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: if user_input is None: user_input = {} return self.async_show_form( @@ -204,7 +210,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None, errors: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> ConfigFlowResult: if user_input is None: user_input = {} return self.async_show_form( @@ -221,7 +227,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def _create_entry( self, data: dict[str, Any], options: dict[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: return self.async_create_entry( title=data[CONF_NAME], data=data, @@ -229,17 +235,17 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class OptionsFlowHandler(config_entries.OptionsFlow): +class OptionsFlowHandler(OptionsFlow): """Handle an options flow.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry self._stations: dict[str, str] = {} async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle options flow.""" if user_input is not None: self.hass.config_entries.async_update_entry( diff --git a/homeassistant/components/tasmota/config_flow.py b/homeassistant/components/tasmota/config_flow.py index d8981090d58..c1740961228 100644 --- a/homeassistant/components/tasmota/config_flow.py +++ b/homeassistant/components/tasmota/config_flow.py @@ -5,15 +5,14 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries from homeassistant.components.mqtt import valid_subscribe_topic -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from .const import CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX, DOMAIN -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -22,7 +21,9 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Initialize flow.""" self._prefix = DEFAULT_PREFIX - async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: + async def async_step_mqtt( + self, discovery_info: MqttServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by MQTT discovery.""" if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -45,7 +46,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") @@ -56,7 +57,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_config( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the setup.""" errors = {} data = {CONF_DISCOVERY_PREFIX: self._prefix} @@ -85,7 +86,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the setup.""" data = {CONF_DISCOVERY_PREFIX: self._prefix} diff --git a/homeassistant/components/tautulli/config_flow.py b/homeassistant/components/tautulli/config_flow.py index 532852687da..987f3816f15 100644 --- a/homeassistant/components/tautulli/config_flow.py +++ b/homeassistant/components/tautulli/config_flow.py @@ -7,9 +7,8 @@ from typing import Any from pytautulli import PyTautulli, PyTautulliException, exceptions import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DEFAULT_NAME, DOMAIN @@ -22,7 +21,7 @@ class TautulliConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: @@ -49,13 +48,15 @@ class TautulliConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors or {}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle a reauthorization flow request.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} if user_input is not None and ( diff --git a/homeassistant/components/technove/config_flow.py b/homeassistant/components/technove/config_flow.py index d85fd0ad152..0e4f026ba5c 100644 --- a/homeassistant/components/technove/config_flow.py +++ b/homeassistant/components/technove/config_flow.py @@ -6,9 +6,8 @@ from technove import Station as TechnoVEStation, TechnoVE, TechnoVEConnectionErr import voluptuous as vol from homeassistant.components import onboarding, zeroconf -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_MAC -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -23,7 +22,7 @@ class TechnoVEConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" errors = {} if user_input is not None: @@ -51,7 +50,7 @@ class TechnoVEConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle zeroconf discovery.""" # Abort quick if the device with provided mac is already configured if mac := discovery_info.properties.get(CONF_MAC): @@ -78,7 +77,7 @@ class TechnoVEConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by zeroconf.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): return self.async_create_entry( diff --git a/homeassistant/components/tedee/config_flow.py b/homeassistant/components/tedee/config_flow.py index 7c8c7b4c3ab..fe8681a7fe4 100644 --- a/homeassistant/components/tedee/config_flow.py +++ b/homeassistant/components/tedee/config_flow.py @@ -12,9 +12,8 @@ from pytedee_async import ( ) import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN, NAME @@ -27,7 +26,7 @@ class TedeeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -79,7 +78,9 @@ class TedeeConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -88,7 +89,7 @@ class TedeeConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" assert self.reauth_entry diff --git a/homeassistant/components/tellduslive/config_flow.py b/homeassistant/components/tellduslive/config_flow.py index 33910f6ead1..a5b9607b221 100644 --- a/homeassistant/components/tellduslive/config_flow.py +++ b/homeassistant/components/tellduslive/config_flow.py @@ -6,7 +6,7 @@ import os from tellduslive import Session, supports_local_api import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_HOST from homeassistant.util.json import load_json_object @@ -28,7 +28,7 @@ KEY_TOKEN_SECRET = "token_secret" _LOGGER = logging.getLogger(__name__) -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 diff --git a/homeassistant/components/tesla_wall_connector/config_flow.py b/homeassistant/components/tesla_wall_connector/config_flow.py index 5b3cf9bd835..ad8112be4cb 100644 --- a/homeassistant/components/tesla_wall_connector/config_flow.py +++ b/homeassistant/components/tesla_wall_connector/config_flow.py @@ -8,11 +8,10 @@ from tesla_wall_connector import WallConnector from tesla_wall_connector.exceptions import WallConnectorError import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, WALLCONNECTOR_DEVICE_NAME, WALLCONNECTOR_SERIAL_NUMBER @@ -37,7 +36,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, } -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TeslaWallConnectorConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tesla Wall Connector.""" VERSION = 1 @@ -48,7 +47,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self.ip_address: str | None = None self.serial_number = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle dhcp discovery.""" self.ip_address = discovery_info.ip _LOGGER.debug("Discovered Tesla Wall Connector at [%s]", self.ip_address) @@ -89,7 +90,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" data_schema = vol.Schema( {vol.Required(CONF_HOST, default=self.ip_address): str} diff --git a/homeassistant/components/teslemetry/config_flow.py b/homeassistant/components/teslemetry/config_flow.py index 64a279132ad..6c709a9d757 100644 --- a/homeassistant/components/teslemetry/config_flow.py +++ b/homeassistant/components/teslemetry/config_flow.py @@ -11,7 +11,6 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, LOGGER @@ -30,7 +29,7 @@ class TeslemetryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Get configuration from the user.""" errors: dict[str, str] = {} if user_input: diff --git a/homeassistant/components/tessie/config_flow.py b/homeassistant/components/tessie/config_flow.py index 97d9d44af70..428cff5d727 100644 --- a/homeassistant/components/tessie/config_flow.py +++ b/homeassistant/components/tessie/config_flow.py @@ -9,10 +9,8 @@ from aiohttp import ClientConnectionError, ClientResponseError from tessie_api import get_state_of_all_vehicles import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -23,7 +21,7 @@ DESCRIPTION_PLACEHOLDERS = { } -class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TessieConfigFlow(ConfigFlow, domain=DOMAIN): """Config Tessie API connection.""" VERSION = 1 @@ -34,7 +32,7 @@ class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Get configuration from the user.""" errors: dict[str, str] = {} if user_input: @@ -64,7 +62,9 @@ class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-auth.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -73,7 +73,7 @@ class TessieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: Mapping[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Get update API Key from the user.""" errors: dict[str, str] = {} assert self._reauth_entry diff --git a/homeassistant/components/thermobeacon/config_flow.py b/homeassistant/components/thermobeacon/config_flow.py index 864e9532c0e..b635a28dc8c 100644 --- a/homeassistant/components/thermobeacon/config_flow.py +++ b/homeassistant/components/thermobeacon/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class ThermoBeaconConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class ThermoBeaconConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class ThermoBeaconConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/thermopro/config_flow.py b/homeassistant/components/thermopro/config_flow.py index f7e03aff685..02335ef712e 100644 --- a/homeassistant/components/thermopro/config_flow.py +++ b/homeassistant/components/thermopro/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class ThermoProConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class ThermoProConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class ThermoProConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/thread/config_flow.py b/homeassistant/components/thread/config_flow.py index b294dfa51e7..2a2062419f7 100644 --- a/homeassistant/components/thread/config_flow.py +++ b/homeassistant/components/thread/config_flow.py @@ -4,8 +4,7 @@ from __future__ import annotations from typing import Any from homeassistant.components import onboarding, zeroconf -from homeassistant.config_entries import ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from .const import DOMAIN @@ -17,28 +16,28 @@ class ThreadConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_import( self, import_data: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Set up by import from async_setup.""" await self._async_handle_discovery_without_unique_id() return self.async_create_entry(title="Thread", data={}) async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Set up by import from async_setup.""" await self._async_handle_discovery_without_unique_id() return self.async_create_entry(title="Thread", data={}) async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Set up because the user has border routers.""" await self._async_handle_discovery_without_unique_id() return await self.async_step_confirm() async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm the setup.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): return self.async_create_entry(title="Thread", data={}) diff --git a/homeassistant/components/tibber/config_flow.py b/homeassistant/components/tibber/config_flow.py index 8c926c5cc81..10b0d899de2 100644 --- a/homeassistant/components/tibber/config_flow.py +++ b/homeassistant/components/tibber/config_flow.py @@ -7,9 +7,8 @@ import aiohttp import tibber import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -21,14 +20,14 @@ ERR_TOKEN = "invalid_access_token" TOKEN_URL = "https://developer.tibber.com/settings/access-token" -class TibberConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TibberConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tibber integration.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" self._async_abort_entries_match() diff --git a/homeassistant/components/tile/config_flow.py b/homeassistant/components/tile/config_flow.py index 3ba1dc411ae..10bc51ae459 100644 --- a/homeassistant/components/tile/config_flow.py +++ b/homeassistant/components/tile/config_flow.py @@ -8,9 +8,8 @@ from pytile import async_login from pytile.errors import InvalidAuthError, TileError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import aiohttp_client from .const import DOMAIN, LOGGER @@ -29,7 +28,7 @@ STEP_USER_SCHEMA = vol.Schema( ) -class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class TileFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a Tile config flow.""" VERSION = 1 @@ -39,7 +38,7 @@ class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._password: str | None = None self._username: str | None = None - async def _async_verify(self, step_id: str, schema: vol.Schema) -> FlowResult: + async def _async_verify(self, step_id: str, schema: vol.Schema) -> ConfigFlowResult: """Attempt to authenticate the provided credentials.""" assert self._username assert self._password @@ -71,18 +70,22 @@ class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=self._username, data=data) - async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + async def async_step_import( + self, import_config: dict[str, Any] + ) -> ConfigFlowResult: """Import a config entry from configuration.yaml.""" return await self.async_step_user(import_config) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" self._username = entry_data[CONF_USERNAME] return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-auth completion.""" if not user_input: return self.async_show_form( @@ -95,7 +98,7 @@ class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the start of the config flow.""" if not user_input: return self.async_show_form(step_id="user", data_schema=STEP_USER_SCHEMA) diff --git a/homeassistant/components/tilt_ble/config_flow.py b/homeassistant/components/tilt_ble/config_flow.py index d534622eb7b..810a4226117 100644 --- a/homeassistant/components/tilt_ble/config_flow.py +++ b/homeassistant/components/tilt_ble/config_flow.py @@ -10,9 +10,8 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ADDRESS -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -30,7 +29,7 @@ class TiltConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfoBleak - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() @@ -43,7 +42,7 @@ class TiltConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_bluetooth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device = self._discovered_device @@ -62,7 +61,7 @@ class TiltConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step to pick discovered device.""" if user_input is not None: address = user_input[CONF_ADDRESS] diff --git a/homeassistant/components/todoist/config_flow.py b/homeassistant/components/todoist/config_flow.py index 94b4ad31826..745f1775e87 100644 --- a/homeassistant/components/todoist/config_flow.py +++ b/homeassistant/components/todoist/config_flow.py @@ -8,9 +8,8 @@ from requests.exceptions import HTTPError from todoist_api_python.api_async import TodoistAPIAsync import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_TOKEN -from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN @@ -25,14 +24,14 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TodoistConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for todoist.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") diff --git a/homeassistant/components/tolo/config_flow.py b/homeassistant/components/tolo/config_flow.py index 14304f6653e..ed15030b372 100644 --- a/homeassistant/components/tolo/config_flow.py +++ b/homeassistant/components/tolo/config_flow.py @@ -10,9 +10,8 @@ from tololib.errors import ResponseTimedOutError import voluptuous as vol from homeassistant.components import dhcp -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.device_registry import format_mac from .const import DEFAULT_NAME, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_TIMEOUT, DOMAIN @@ -40,7 +39,7 @@ class ToloSaunaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -64,7 +63,9 @@ class ToloSaunaConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle a flow initialized by discovery.""" await self.async_set_unique_id(format_mac(discovery_info.macaddress)) self._abort_if_unique_id_configured({CONF_HOST: discovery_info.ip}) @@ -81,7 +82,7 @@ class ToloSaunaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle user-confirmation of discovered node.""" if user_input is not None: self._async_abort_entries_match({CONF_HOST: self._discovered_host}) diff --git a/homeassistant/components/tomorrowio/config_flow.py b/homeassistant/components/tomorrowio/config_flow.py index aece537c867..e23f90b2e3b 100644 --- a/homeassistant/components/tomorrowio/config_flow.py +++ b/homeassistant/components/tomorrowio/config_flow.py @@ -13,8 +13,13 @@ from pytomorrowio.exceptions import ( from pytomorrowio.pytomorrowio import TomorrowioV4 import voluptuous as vol -from homeassistant import config_entries, core from homeassistant.components.zone import async_active_zone +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import ( CONF_API_KEY, CONF_FRIENDLY_NAME, @@ -24,7 +29,6 @@ from homeassistant.const import ( CONF_NAME, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import LocationSelector, LocationSelectorConfig @@ -40,7 +44,7 @@ _LOGGER = logging.getLogger(__name__) def _get_config_schema( - hass: core.HomeAssistant, + hass: HomeAssistant, source: str | None, input_dict: dict[str, Any] | None = None, ) -> vol.Schema: @@ -83,16 +87,16 @@ def _get_unique_id(hass: HomeAssistant, input_dict: dict[str, Any]): ) -class TomorrowioOptionsConfigFlow(config_entries.OptionsFlow): +class TomorrowioOptionsConfigFlow(OptionsFlow): """Handle Tomorrow.io options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Tomorrow.io options flow.""" self._config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Tomorrow.io options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) @@ -109,7 +113,7 @@ class TomorrowioOptionsConfigFlow(config_entries.OptionsFlow): ) -class TomorrowioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TomorrowioConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tomorrow.io Weather API.""" VERSION = 1 @@ -117,14 +121,14 @@ class TomorrowioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> TomorrowioOptionsConfigFlow: """Get the options flow for this handler.""" return TomorrowioOptionsConfigFlow(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} if user_input is not None: diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index 5c98e35bead..4077d352db9 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -7,7 +7,7 @@ from typing import Any from toonapi import Agreement, Toon, ToonError import voluptuous as vol -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigFlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler @@ -28,7 +28,7 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): """Return logger.""" return logging.getLogger(__name__) - async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Test connection and load up agreements.""" self.data = data @@ -48,7 +48,7 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): async def async_step_import( self, config: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Start a configuration flow based on imported data. This step is merely here to trigger "discovery" when the `toon` @@ -65,7 +65,7 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): async def async_step_agreement( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Select Toon agreement to add.""" if len(self.agreements) == 1: return await self._create_entry(self.agreements[0]) @@ -86,7 +86,7 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN): agreement_index = agreements_list.index(user_input[CONF_AGREEMENT]) return await self._create_entry(self.agreements[agreement_index]) - async def _create_entry(self, agreement: Agreement) -> FlowResult: + async def _create_entry(self, agreement: Agreement) -> ConfigFlowResult: if CONF_MIGRATE in self.context: await self.hass.config_entries.async_remove(self.context[CONF_MIGRATE]) diff --git a/homeassistant/components/totalconnect/config_flow.py b/homeassistant/components/totalconnect/config_flow.py index 8d35506af0f..d7cc3c237a3 100644 --- a/homeassistant/components/totalconnect/config_flow.py +++ b/homeassistant/components/totalconnect/config_flow.py @@ -8,17 +8,21 @@ from total_connect_client.client import TotalConnectClient from total_connect_client.exceptions import AuthenticationError import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_LOCATION, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import AUTO_BYPASS, CONF_USERCODES, DOMAIN PASSWORD_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) -class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TotalConnectConfigFlow(ConfigFlow, domain=DOMAIN): """Total Connect config flow.""" VERSION = 1 @@ -125,7 +129,9 @@ class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): description_placeholders={"location_id": location_for_user}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an authentication error or no usercode.""" self.username = entry_data[CONF_USERNAME] self.usercodes = entry_data[CONF_USERCODES] @@ -173,16 +179,16 @@ class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> TotalConnectOptionsFlowHandler: """Get options flow.""" return TotalConnectOptionsFlowHandler(config_entry) -class TotalConnectOptionsFlowHandler(config_entries.OptionsFlow): +class TotalConnectOptionsFlowHandler(OptionsFlow): """TotalConnect options flow handler.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/homeassistant/components/tplink/config_flow.py b/homeassistant/components/tplink/config_flow.py index 643748f175e..1d818da868b 100644 --- a/homeassistant/components/tplink/config_flow.py +++ b/homeassistant/components/tplink/config_flow.py @@ -15,9 +15,14 @@ from kasa import ( ) import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import dhcp -from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry, ConfigEntryState +from homeassistant.config_entries import ( + SOURCE_REAUTH, + ConfigEntry, + ConfigEntryState, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import ( CONF_ALIAS, CONF_DEVICE, @@ -28,7 +33,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import device_registry as dr from homeassistant.helpers.typing import DiscoveryInfoType @@ -46,7 +50,7 @@ STEP_AUTH_DATA_SCHEMA = vol.Schema( ) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for tplink.""" VERSION = 1 @@ -58,7 +62,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_devices: dict[str, SmartDevice] = {} self._discovered_device: SmartDevice | None = None - async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: + async def async_step_dhcp( + self, discovery_info: dhcp.DhcpServiceInfo + ) -> ConfigFlowResult: """Handle discovery via dhcp.""" return await self._async_handle_discovery( discovery_info.ip, dr.format_mac(discovery_info.macaddress) @@ -66,7 +72,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle integration discovery.""" return await self._async_handle_discovery( discovery_info[CONF_HOST], @@ -77,7 +83,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @callback def _update_config_if_entry_in_setup_error( self, entry: ConfigEntry, host: str, config: dict - ) -> FlowResult | None: + ) -> ConfigFlowResult | None: """If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config.""" if entry.state not in ( ConfigEntryState.SETUP_ERROR, @@ -96,7 +102,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_handle_discovery( self, host: str, formatted_mac: str, config: dict | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle any discovery.""" current_entry = await self.async_set_unique_id( formatted_mac, raise_on_progress=False @@ -131,7 +137,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_auth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that auth is required.""" assert self._discovered_device is not None errors = {} @@ -190,7 +196,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None if user_input is not None: @@ -205,7 +211,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} placeholders: dict[str, str] = {} @@ -237,7 +243,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user_auth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that auth is required.""" errors: dict[str, str] = {} host = self.context[CONF_HOST] @@ -272,7 +278,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_pick_device( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the step to pick discovered device.""" if user_input is not None: mac = user_input[CONF_DEVICE] @@ -332,7 +338,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _config_entries.flow.async_abort(flow["flow_id"]) @callback - def _async_create_entry_from_device(self, device: SmartDevice) -> FlowResult: + def _async_create_entry_from_device(self, device: SmartDevice) -> ConfigFlowResult: """Create a config entry from a smart device.""" self._abort_if_unique_id_configured(updates={CONF_HOST: device.host}) return self.async_create_entry( @@ -401,7 +407,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return self._discovered_device - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Start the reauthentication flow if the device needs updated credentials.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -410,7 +418,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors: dict[str, str] = {} placeholders: dict[str, str] = {} diff --git a/homeassistant/components/tplink_omada/config_flow.py b/homeassistant/components/tplink_omada/config_flow.py index e49e8ccf657..0071e5cda17 100644 --- a/homeassistant/components/tplink_omada/config_flow.py +++ b/homeassistant/components/tplink_omada/config_flow.py @@ -18,10 +18,9 @@ from tplink_omada_client.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import ( async_create_clientsession, @@ -92,7 +91,7 @@ async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> HubInfo: return HubInfo(controller_id, name, sites) -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TpLinkOmadaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for TP-Link Omada.""" VERSION = 1 @@ -105,7 +104,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -130,7 +129,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_site( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle step to select site to manage.""" if user_input is None: @@ -159,14 +158,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=display_name, data=self._omada_opts) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._omada_opts = dict(entry_data) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/traccar_server/config_flow.py b/homeassistant/components/traccar_server/config_flow.py index a2a7daaaa98..86b0cfa8779 100644 --- a/homeassistant/components/traccar_server/config_flow.py +++ b/homeassistant/components/traccar_server/config_flow.py @@ -17,7 +17,6 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.schema_config_entry_flow import ( SchemaFlowFormStep, @@ -130,7 +129,7 @@ class TraccarServerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None, - ) -> FlowResult: + ) -> config_entries.ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: @@ -160,7 +159,9 @@ class TraccarServerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_import(self, import_info: Mapping[str, Any]) -> FlowResult: + async def async_step_import( + self, import_info: Mapping[str, Any] + ) -> config_entries.ConfigFlowResult: """Import an entry.""" configured_port = str(import_info[CONF_PORT]) self._async_abort_entries_match( diff --git a/homeassistant/components/tractive/config_flow.py b/homeassistant/components/tractive/config_flow.py index ba42aeb600d..3299b2981a8 100644 --- a/homeassistant/components/tractive/config_flow.py +++ b/homeassistant/components/tractive/config_flow.py @@ -8,10 +8,9 @@ from typing import Any import aiotractive import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -40,14 +39,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, return {"title": data[CONF_EMAIL], "user_id": user_id} -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TractiveConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for tractive.""" VERSION = 1 async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" if user_input is None: return self.async_show_form(step_id="user", data_schema=USER_DATA_SCHEMA) @@ -70,13 +69,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=USER_DATA_SCHEMA, errors=errors ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle configuration by re-auth.""" return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Dialog that informs the user that reauth is required.""" errors = {} diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 9acdfb36a5d..0a2b6eaef2c 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -9,11 +9,10 @@ from pytradfri import Gateway, RequestError from pytradfri.api.aiocoap_api import APIFactory import voluptuous as vol -from homeassistant import config_entries from homeassistant.components import zeroconf +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult from .const import CONF_GATEWAY_ID, CONF_IDENTITY, CONF_KEY, DOMAIN @@ -29,7 +28,7 @@ class AuthError(Exception): self.code = code -class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class FlowHandler(ConfigFlow, domain=DOMAIN): """Handle a config flow.""" VERSION = 1 @@ -40,13 +39,13 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" return await self.async_step_auth() async def async_step_auth( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the authentication with a gateway.""" errors: dict[str, str] = {} @@ -82,7 +81,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_homekit( self, discovery_info: zeroconf.ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle homekit discovery.""" await self.async_set_unique_id( discovery_info.properties[zeroconf.ATTR_PROPERTIES_ID] @@ -107,7 +106,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._host = host return await self.async_step_auth() - async def _entry_from_data(self, data: dict[str, Any]) -> FlowResult: + async def _entry_from_data(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry from data.""" host = data[CONF_HOST] gateway_id = data[CONF_GATEWAY_ID] diff --git a/homeassistant/components/trafikverket_camera/config_flow.py b/homeassistant/components/trafikverket_camera/config_flow.py index 9db27eda622..566041ec790 100644 --- a/homeassistant/components/trafikverket_camera/config_flow.py +++ b/homeassistant/components/trafikverket_camera/config_flow.py @@ -8,9 +8,8 @@ from pytrafikverket.exceptions import InvalidAuthentication, NoCameraFound, Unkn from pytrafikverket.trafikverket_camera import CameraInfo, TrafikverketCamera import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_ID, CONF_LOCATION -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( SelectOptionDict, @@ -23,12 +22,12 @@ from homeassistant.helpers.selector import ( from .const import DOMAIN -class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TVCameraConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Trafikverket Camera integration.""" VERSION = 3 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None cameras: list[CameraInfo] api_key: str @@ -52,7 +51,9 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return (errors, cameras) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Trafikverket.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -60,7 +61,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Trafikverket.""" errors: dict[str, str] = {} @@ -93,7 +94,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors: dict[str, str] = {} @@ -128,7 +129,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_multiple_cameras( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle when multiple cameras.""" if user_input: diff --git a/homeassistant/components/trafikverket_ferry/config_flow.py b/homeassistant/components/trafikverket_ferry/config_flow.py index 2fb6cfb642a..007e1cd72c1 100644 --- a/homeassistant/components/trafikverket_ferry/config_flow.py +++ b/homeassistant/components/trafikverket_ferry/config_flow.py @@ -8,9 +8,8 @@ from pytrafikverket import TrafikverketFerry from pytrafikverket.exceptions import InvalidAuthentication, NoFerryFound import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, WEEKDAYS -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -44,12 +43,12 @@ DATA_SCHEMA_REAUTH = vol.Schema( ) -class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TVFerryConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Trafikverket Ferry integration.""" VERSION = 1 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None async def validate_input( self, api_key: str, ferry_from: str, ferry_to: str @@ -59,7 +58,9 @@ class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ferry_api = TrafikverketFerry(web_session, api_key) await ferry_api.async_get_next_ferry_stop(ferry_from, ferry_to) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Trafikverket.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -67,7 +68,7 @@ class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Trafikverket.""" errors: dict[str, str] = {} @@ -104,7 +105,7 @@ class TVFerryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/trafikverket_train/config_flow.py b/homeassistant/components/trafikverket_train/config_flow.py index df05942add1..5022921c310 100644 --- a/homeassistant/components/trafikverket_train/config_flow.py +++ b/homeassistant/components/trafikverket_train/config_flow.py @@ -16,10 +16,14 @@ from pytrafikverket.exceptions import ( ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlowWithConfigEntry, +) from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_WEEKDAY, WEEKDAYS from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.selector import ( @@ -116,22 +120,24 @@ async def validate_input( return errors -class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TVTrainConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Trafikverket Train integration.""" VERSION = 1 - entry: config_entries.ConfigEntry | None + entry: ConfigEntry | None @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> TVTrainOptionsFlowHandler: """Get the options flow for this handler.""" return TVTrainOptionsFlowHandler(config_entry) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Trafikverket.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -139,7 +145,7 @@ class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Trafikverket.""" errors: dict[str, str] = {} @@ -175,7 +181,7 @@ class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the user step.""" errors: dict[str, str] = {} @@ -231,12 +237,12 @@ class TVTrainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -class TVTrainOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): +class TVTrainOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle Trafikverket Train options.""" async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage Trafikverket Train options.""" errors: dict[str, Any] = {} diff --git a/homeassistant/components/trafikverket_weatherstation/config_flow.py b/homeassistant/components/trafikverket_weatherstation/config_flow.py index 89cbd373665..9de085bcddd 100644 --- a/homeassistant/components/trafikverket_weatherstation/config_flow.py +++ b/homeassistant/components/trafikverket_weatherstation/config_flow.py @@ -12,21 +12,20 @@ from pytrafikverket.exceptions import ( from pytrafikverket.trafikverket_weather import TrafikverketWeather import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from .const import CONF_STATION, DOMAIN -class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TVWeatherConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Trafikverket Weatherstation integration.""" VERSION = 1 - entry: config_entries.ConfigEntry | None = None + entry: ConfigEntry | None = None async def validate_input(self, sensor_api: str, station: str) -> None: """Validate input from user input.""" @@ -36,7 +35,7 @@ class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" errors = {} @@ -75,7 +74,9 @@ class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Handle re-authentication with Trafikverket.""" self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) @@ -83,7 +84,7 @@ class TVWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm re-authentication with Trafikverket.""" errors: dict[str, str] = {} diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index a987233fef0..9aed54c3e71 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -6,10 +6,14 @@ from typing import Any import voluptuous as vol -from homeassistant import config_entries +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from . import get_api from .const import ( @@ -34,23 +38,23 @@ DATA_SCHEMA = vol.Schema( ) -class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class TransmissionFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Tansmission config flow.""" VERSION = 1 - _reauth_entry: config_entries.ConfigEntry | None + _reauth_entry: ConfigEntry | None @staticmethod @callback def async_get_options_flow( - config_entry: config_entries.ConfigEntry, + config_entry: ConfigEntry, ) -> TransmissionOptionsFlowHandler: """Get the options flow for this handler.""" return TransmissionOptionsFlowHandler(config_entry) async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors = {} @@ -79,7 +83,9 @@ class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors=errors, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -88,7 +94,7 @@ class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_reauth_confirm( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" errors = {} assert self._reauth_entry @@ -122,16 +128,16 @@ class TransmissionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -class TransmissionOptionsFlowHandler(config_entries.OptionsFlow): +class TransmissionOptionsFlowHandler(OptionsFlow): """Handle Transmission client options.""" - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + def __init__(self, config_entry: ConfigEntry) -> None: """Initialize Transmission options flow.""" self.config_entry = config_entry async def async_step_init( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Manage the Transmission options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index e0ac5375b00..397da0618fb 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -7,8 +7,7 @@ from typing import Any from tuya_sharing import LoginControl import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow -from homeassistant.data_entry_flow import FlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.helpers import selector from .const import ( @@ -40,7 +39,7 @@ class TuyaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step user.""" errors = {} placeholders = {} @@ -75,7 +74,7 @@ class TuyaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_scan( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Step scan.""" if user_input is None: return self.async_show_form( @@ -146,7 +145,7 @@ class TuyaConfigFlow(ConfigFlow, domain=DOMAIN): data=entry_data, ) - async def async_step_reauth(self, _: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth(self, _: Mapping[str, Any]) -> ConfigFlowResult: """Handle initiation of re-authentication with Tuya.""" self.__reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -163,7 +162,7 @@ class TuyaConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_reauth_user_code( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle re-authentication with a Tuya.""" errors = {} placeholders = {} diff --git a/homeassistant/components/twentemilieu/config_flow.py b/homeassistant/components/twentemilieu/config_flow.py index 7a00222fe9b..160aecef947 100644 --- a/homeassistant/components/twentemilieu/config_flow.py +++ b/homeassistant/components/twentemilieu/config_flow.py @@ -10,9 +10,8 @@ from twentemilieu import ( ) import voluptuous as vol -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_ID -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_HOUSE_LETTER, CONF_HOUSE_NUMBER, CONF_POST_CODE, DOMAIN @@ -25,7 +24,7 @@ class TwenteMilieuFlowHandler(ConfigFlow, domain=DOMAIN): async def _show_setup_form( self, errors: dict[str, str] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Show the setup form to the user.""" return self.async_show_form( step_id="user", @@ -41,7 +40,7 @@ class TwenteMilieuFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle a flow initiated by the user.""" if user_input is None: return await self._show_setup_form(user_input) diff --git a/homeassistant/components/twinkly/config_flow.py b/homeassistant/components/twinkly/config_flow.py index 6d0785f648e..17acb4f165e 100644 --- a/homeassistant/components/twinkly/config_flow.py +++ b/homeassistant/components/twinkly/config_flow.py @@ -8,8 +8,8 @@ from aiohttp import ClientError from ttls.client import Twinkly from voluptuous import Required, Schema -from homeassistant import config_entries, data_entry_flow from homeassistant.components import dhcp +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -18,7 +18,7 @@ from .const import DEV_ID, DEV_MODEL, DEV_NAME, DOMAIN _LOGGER = logging.getLogger(__name__) -class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): +class TwinklyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle twinkly config flow.""" VERSION = 1 @@ -53,7 +53,7 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_dhcp( self, discovery_info: dhcp.DhcpServiceInfo - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Handle dhcp discovery for twinkly.""" self._async_abort_entries_match({CONF_HOST: discovery_info.ip}) device_info = await Twinkly( @@ -65,9 +65,7 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self._discovered_device = (device_info, discovery_info.ip) return await self.async_step_discovery_confirm() - async def async_step_discovery_confirm( - self, user_input=None - ) -> data_entry_flow.FlowResult: + async def async_step_discovery_confirm(self, user_input=None) -> ConfigFlowResult: """Confirm discovery.""" assert self._discovered_device is not None device_info, host = self._discovered_device @@ -87,7 +85,7 @@ class TwinklyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def _create_entry_from_device( self, device_info: dict[str, Any], host: str - ) -> data_entry_flow.FlowResult: + ) -> ConfigFlowResult: """Create entry from device data.""" return self.async_create_entry( title=device_info[DEV_NAME], diff --git a/homeassistant/components/twitch/config_flow.py b/homeassistant/components/twitch/config_flow.py index 128abf756fa..b8be2ac6e5a 100644 --- a/homeassistant/components/twitch/config_flow.py +++ b/homeassistant/components/twitch/config_flow.py @@ -9,10 +9,10 @@ from twitchAPI.helper import first from twitchAPI.twitch import Twitch from twitchAPI.type import AuthScope, InvalidTokenException -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_TOKEN from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import AbortFlow, FlowResult +from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers.config_entry_oauth2_flow import LocalOAuth2Implementation from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -46,7 +46,7 @@ class OAuth2FlowHandler( async def async_oauth_create_entry( self, data: dict[str, Any], - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the initial step.""" implementation = cast( LocalOAuth2Implementation, @@ -104,7 +104,9 @@ class OAuth2FlowHandler( description_placeholders={"title": self.reauth_entry.title}, ) - async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] @@ -113,13 +115,13 @@ class OAuth2FlowHandler( async def async_step_reauth_confirm( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Confirm reauth dialog.""" if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() - async def async_step_import(self, config: dict[str, Any]) -> FlowResult: + async def async_step_import(self, config: dict[str, Any]) -> ConfigFlowResult: """Import from yaml.""" client = await Twitch( app_id=config[CONF_CLIENT_ID], From 2a135b64b67a5f56c8be300e8c7bd18629cf6d94 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Thu, 29 Feb 2024 22:16:27 +0100 Subject: [PATCH 0065/1691] Add missing unit of measurement for tolerance option in proximity (#111876) --- homeassistant/components/proximity/config_flow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/proximity/config_flow.py b/homeassistant/components/proximity/config_flow.py index cc2e6eb5a39..77ebf26f52a 100644 --- a/homeassistant/components/proximity/config_flow.py +++ b/homeassistant/components/proximity/config_flow.py @@ -14,7 +14,7 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.const import CONF_ZONE +from homeassistant.const import CONF_ZONE, UnitOfLength from homeassistant.core import State, callback from homeassistant.helpers.selector import ( EntitySelector, @@ -54,7 +54,9 @@ def _base_schema(user_input: dict[str, Any]) -> vol.Schema: CONF_TOLERANCE, default=user_input.get(CONF_TOLERANCE, DEFAULT_TOLERANCE), ): NumberSelector( - NumberSelectorConfig(min=1, max=100, step=1), + NumberSelectorConfig( + min=1, max=100, step=1, unit_of_measurement=UnitOfLength.METERS + ), ), } From 3b1688f6bd82eccd32d0357c925466647c8933e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Thu, 29 Feb 2024 22:41:33 +0100 Subject: [PATCH 0066/1691] Update aioairzone to v0.7.5 (#111879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/airzone/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airzone/manifest.json b/homeassistant/components/airzone/manifest.json index 6987b3213c1..59b8645d26c 100644 --- a/homeassistant/components/airzone/manifest.json +++ b/homeassistant/components/airzone/manifest.json @@ -11,5 +11,5 @@ "documentation": "https://www.home-assistant.io/integrations/airzone", "iot_class": "local_polling", "loggers": ["aioairzone"], - "requirements": ["aioairzone==0.7.4"] + "requirements": ["aioairzone==0.7.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1257caa2ec9..1e017a317b0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aioairq==0.3.2 aioairzone-cloud==0.3.8 # homeassistant.components.airzone -aioairzone==0.7.4 +aioairzone==0.7.5 # homeassistant.components.ambient_station aioambient==2024.01.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d93e5dcdd27..7517e025c91 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -170,7 +170,7 @@ aioairq==0.3.2 aioairzone-cloud==0.3.8 # homeassistant.components.airzone -aioairzone==0.7.4 +aioairzone==0.7.5 # homeassistant.components.ambient_station aioambient==2024.01.0 From b39b2d161c76f487ee660d857970f58b4fbb3fb6 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 29 Feb 2024 22:43:47 +0100 Subject: [PATCH 0067/1691] Deconz fix gradient color mode (#111890) * Fix deconz gradient colormode * Fix gradient light not reporting color mode in deCONZ --- homeassistant/components/deconz/light.py | 1 + tests/components/deconz/test_light.py | 111 +++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 27038a07ac3..086db2058c9 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -63,6 +63,7 @@ FLASH_TO_DECONZ = {FLASH_SHORT: LightAlert.SHORT, FLASH_LONG: LightAlert.LONG} DECONZ_TO_COLOR_MODE = { LightColorMode.CT: ColorMode.COLOR_TEMP, + LightColorMode.GRADIENT: ColorMode.XY, LightColorMode.HS: ColorMode.HS, LightColorMode.XY: ColorMode.XY, } diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index d1d5b983956..63c544ff189 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -308,6 +308,117 @@ async def test_no_lights_or_groups( }, }, ), + ( # Gradient light + { + "capabilities": { + "alerts": [ + "none", + "select", + "lselect", + "blink", + "breathe", + "okay", + "channelchange", + "finish", + "stop", + ], + "bri": {"min_dim_level": 0.01}, + "color": { + "ct": {"computes_xy": True, "max": 500, "min": 153}, + "effects": [ + "none", + "colorloop", + "candle", + "fireplace", + "prism", + "sunrise", + ], + "gamut_type": "C", + "gradient": { + "max_segments": 9, + "pixel_count": 16, + "pixel_length": 1250, + "styles": ["linear", "mirrored"], + }, + "modes": ["ct", "effect", "gradient", "hs", "xy"], + "xy": { + "blue": [0.1532, 0.0475], + "green": [0.17, 0.7], + "red": [0.6915, 0.3083], + }, + }, + }, + "colorcapabilities": 31, + "config": { + "bri": { + "couple_ct": False, + "execute_if_off": True, + "startup": "previous", + }, + "color": { + "ct": {"startup": "previous"}, + "execute_if_off": True, + "gradient": {"reversed": False}, + "xy": {"startup": "previous"}, + }, + "groups": ["36", "39", "45", "46", "47", "51", "57", "59"], + "on": {"startup": "previous"}, + }, + "ctmax": 500, + "ctmin": 153, + "etag": "077fb97dd6145f10a3c190f0a1ade499", + "hascolor": True, + "lastannounced": None, + "lastseen": "2024-02-29T18:36Z", + "manufacturername": "Signify Netherlands B.V.", + "modelid": "LCX004", + "name": "Gradient light", + "productid": "Philips-LCX004-1-GALSECLv1", + "productname": "Hue gradient lightstrip", + "state": { + "alert": "none", + "bri": 184, + "colormode": "gradient", + "ct": 396, + "effect": "none", + "gradient": { + "color_adjustment": 0, + "offset": 0, + "offset_adjustment": 0, + "points": [ + [0.2728, 0.6226], + [0.163, 0.4262], + [0.1563, 0.1699], + [0.1551, 0.1147], + [0.1534, 0.0579], + ], + "segments": 5, + "style": "linear", + }, + "hue": 20566, + "on": True, + "reachable": True, + "sat": 254, + "xy": [0.2727, 0.6226], + }, + "swconfigid": "F03CAF4D", + "swversion": "1.104.2", + "type": "Extended color light", + "uniqueid": "00:17:88:01:0b:0c:0d:0e-0f", + }, + { + "entity_id": "light.gradient_light", + "state": STATE_ON, + "attributes": { + ATTR_SUPPORTED_COLOR_MODES: [ + ColorMode.COLOR_TEMP, + ColorMode.HS, + ColorMode.XY, + ], + ATTR_COLOR_MODE: ColorMode.XY, + }, + }, + ), ], ) async def test_lights( From 0b0036fb12f1a87e539e3e77e7a809d9fc31c363 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 12:49:28 -1000 Subject: [PATCH 0068/1691] Bump habluetooth to 2.4.2 (#111885) --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 08e9b8bda2c..b8158a06f7e 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -21,6 +21,6 @@ "bluetooth-auto-recovery==1.3.0", "bluetooth-data-tools==1.19.0", "dbus-fast==2.21.1", - "habluetooth==2.4.1" + "habluetooth==2.4.2" ] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c1f80804b5c..e8efc513fb0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -26,7 +26,7 @@ dbus-fast==2.21.1 fnv-hash-fast==0.5.0 ha-av==10.1.1 ha-ffmpeg==3.1.0 -habluetooth==2.4.1 +habluetooth==2.4.2 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 diff --git a/requirements_all.txt b/requirements_all.txt index 1e017a317b0..2b0d3ec2240 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1031,7 +1031,7 @@ ha-philipsjs==3.1.1 habitipy==0.2.0 # homeassistant.components.bluetooth -habluetooth==2.4.1 +habluetooth==2.4.2 # homeassistant.components.cloud hass-nabucasa==0.78.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7517e025c91..0b1ca167f1c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -842,7 +842,7 @@ ha-philipsjs==3.1.1 habitipy==0.2.0 # homeassistant.components.bluetooth -habluetooth==2.4.1 +habluetooth==2.4.2 # homeassistant.components.cloud hass-nabucasa==0.78.0 From 3b93c21d9d557623ffd8f8964a2759b0c8e2d2e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 12:49:43 -1000 Subject: [PATCH 0069/1691] Switch influxdb to use a SimpleQueue (#111798) --- homeassistant/components/influxdb/__init__.py | 24 ++++++---- tests/components/influxdb/test_init.py | 48 +++++++++++-------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 24c80dc1d54..cd7e6a7ed88 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -512,7 +512,9 @@ class InfluxThread(threading.Thread): def __init__(self, hass, influx, event_to_json, max_tries): """Initialize the listener.""" threading.Thread.__init__(self, name=DOMAIN) - self.queue = queue.Queue() + self.queue: queue.SimpleQueue[ + threading.Event | tuple[float, Event] | None + ] = queue.SimpleQueue() self.influx = influx self.event_to_json = event_to_json self.max_tries = max_tries @@ -548,16 +550,17 @@ class InfluxThread(threading.Thread): if item is None: self.shutdown = True - else: + elif type(item) is tuple: # noqa: E721 timestamp, event = item age = time.monotonic() - timestamp if age < queue_seconds: - event_json = self.event_to_json(event) - if event_json: + if event_json := self.event_to_json(event): json.append(event_json) else: dropped += 1 + elif isinstance(item, threading.Event): + item.set() if dropped: _LOGGER.warning(CATCHING_UP_MESSAGE, dropped) @@ -590,12 +593,15 @@ class InfluxThread(threading.Thread): def run(self): """Process incoming events.""" while not self.shutdown: - count, json = self.get_events_json() + _, json = self.get_events_json() if json: self.write_to_influxdb(json) - for _ in range(count): - self.queue.task_done() def block_till_done(self): - """Block till all events processed.""" - self.queue.join() + """Block till all events processed. + + Currently only used for testing. + """ + event = threading.Event() + self.queue.put(event) + event.wait() diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index b6d68714af5..aa73e12a611 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -22,6 +22,15 @@ BASE_V2_CONFIG = { } +async def async_wait_for_queue_to_process(hass: HomeAssistant) -> None: + """Wait for the queue to be processed. + + In the future we should refactor this away to not have + to access hass.data directly. + """ + await hass.async_add_executor_job(hass.data[influxdb.DOMAIN].block_till_done) + + @dataclass class FilterTest: """Class for capturing a filter test.""" @@ -407,7 +416,7 @@ async def test_event_listener( hass.states.async_set("fake.entity_id", in_, attrs) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -454,7 +463,7 @@ async def test_event_listener_no_units( ] hass.states.async_set("fake.entity_id", 1, attrs) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -497,7 +506,7 @@ async def test_event_listener_inf( ] hass.states.async_set("fake.entity_id", 8, attrs) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -539,7 +548,7 @@ async def test_event_listener_states( ] hass.states.async_set("fake.entity_id", state_state) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) if state_state == 1: @@ -564,7 +573,7 @@ async def execute_filter_test(hass: HomeAssistant, tests, write_api, get_mock_ca ] hass.states.async_set(test.id, 1) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) if test.should_pass: write_api.assert_called_once() @@ -927,7 +936,7 @@ async def test_event_listener_invalid_type( hass.states.async_set("fake.entity_id", in_, attrs) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -970,7 +979,7 @@ async def test_event_listener_default_measurement( ] hass.states.async_set("fake.ok", 1) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1014,7 +1023,7 @@ async def test_event_listener_unit_of_measurement_field( ] hass.states.async_set("fake.entity_id", "foo", attrs) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1062,7 +1071,7 @@ async def test_event_listener_tags_attributes( ] hass.states.async_set("fake.something", 1, attrs) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1120,7 +1129,7 @@ async def test_event_listener_component_override_measurement( ] hass.states.async_set(f"{comp['domain']}.{comp['id']}", 1) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1186,7 +1195,7 @@ async def test_event_listener_component_measurement_attr( ] hass.states.async_set(f"{comp['domain']}.{comp['id']}", 1, comp["attrs"]) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1271,7 +1280,7 @@ async def test_event_listener_ignore_attributes( }, ) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1317,7 +1326,7 @@ async def test_event_listener_ignore_attributes_overlapping_entities( ] hass.states.async_set("sensor.fake", 1, {"ignore": 1}) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1357,7 +1366,7 @@ async def test_event_listener_scheduled_write( with patch.object(influxdb.time, "sleep") as mock_sleep: hass.states.async_set("entity.entity_id", 1) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) assert mock_sleep.called assert write_api.call_count == 2 @@ -1366,7 +1375,7 @@ async def test_event_listener_scheduled_write( with patch.object(influxdb.time, "sleep") as mock_sleep: hass.states.async_set("entity.entity_id", "2") await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) assert not mock_sleep.called assert write_api.call_count == 3 @@ -1406,7 +1415,7 @@ async def test_event_listener_backlog_full( with patch("homeassistant.components.influxdb.time.monotonic", new=fast_monotonic): hass.states.async_set("entity.id", 1) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) assert get_write_api(mock_client).call_count == 0 @@ -1444,7 +1453,7 @@ async def test_event_listener_attribute_name_conflict( ] hass.states.async_set("fake.something", 1, {"value": "value_str"}) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 @@ -1566,7 +1575,8 @@ async def test_invalid_inputs_error( with patch(f"{INFLUX_PATH}.time.sleep") as sleep: hass.states.async_set("fake.something", 1) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) + await hass.async_block_till_done() write_api.assert_called_once() assert ( @@ -1670,7 +1680,7 @@ async def test_precision( }, ) await hass.async_block_till_done() - hass.data[influxdb.DOMAIN].block_till_done() + await async_wait_for_queue_to_process(hass) write_api = get_write_api(mock_client) assert write_api.call_count == 1 From 25510fc13c95f0f1a9c24f54fe81cf929c60f8a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 16:02:13 -1000 Subject: [PATCH 0070/1691] Limit executor imports to a single thread (#111898) * Limit executor imports to a single thread * test for import executor * test for import executor * test for import executor * fixes * better fix --- homeassistant/core.py | 15 +++++++++++++++ homeassistant/loader.py | 4 ++-- tests/components/zwave_js/test_update.py | 4 ++-- tests/test_core.py | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 47a21f3325b..0f038149d63 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -95,6 +95,7 @@ from .util.async_ import ( run_callback_threadsafe, shutdown_run_callback_threadsafe, ) +from .util.executor import InterruptibleThreadPoolExecutor from .util.json import JsonObjectType from .util.read_only_dict import ReadOnlyDict from .util.timeout import TimeoutManager @@ -394,6 +395,9 @@ class HomeAssistant: self.timeout: TimeoutManager = TimeoutManager() self._stop_future: concurrent.futures.Future[None] | None = None self._shutdown_jobs: list[HassJobWithArgs] = [] + self.import_executor = InterruptibleThreadPoolExecutor( + max_workers=1, thread_name_prefix="ImportExecutor" + ) @cached_property def is_running(self) -> bool: @@ -678,6 +682,16 @@ class HomeAssistant: return task + @callback + def async_add_import_executor_job( + self, target: Callable[..., _T], *args: Any + ) -> asyncio.Future[_T]: + """Add an import executor job from within the event loop.""" + task = self.loop.run_in_executor(self.import_executor, target, *args) + self._tasks.add(task) + task.add_done_callback(self._tasks.remove) + return task + @overload @callback def async_run_hass_job( @@ -992,6 +1006,7 @@ class HomeAssistant: self._async_log_running_tasks("close") self.set_state(CoreState.stopped) + self.import_executor.shutdown() if self._stopped is not None: self._stopped.set() diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 1ff98ff6ff2..6c736bf8c4d 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -853,7 +853,7 @@ class Integration: # So we do it before validating config to catch these errors. if load_executor: try: - comp = await self.hass.async_add_executor_job(self.get_component) + comp = await self.hass.async_add_import_executor_job(self.get_component) except ImportError as ex: load_executor = False _LOGGER.debug("Failed to import %s in executor", domain, exc_info=ex) @@ -924,7 +924,7 @@ class Integration: try: if load_executor: try: - platform = await self.hass.async_add_executor_job( + platform = await self.hass.async_add_import_executor_job( self._load_platform, platform_name ) except ImportError as ex: diff --git a/tests/components/zwave_js/test_update.py b/tests/components/zwave_js/test_update.py index ed42363ca41..1774254a3c5 100644 --- a/tests/components/zwave_js/test_update.py +++ b/tests/components/zwave_js/test_update.py @@ -310,7 +310,7 @@ async def test_update_entity_ha_not_running( hass_ws_client: WebSocketGenerator, ) -> None: """Test update occurs only after HA is running.""" - await hass.async_stop() + hass.set_state(CoreState.not_running) client.async_send_command.return_value = {"updates": []} @@ -632,7 +632,7 @@ async def test_update_entity_delay( """Test update occurs on a delay after HA starts.""" client.async_send_command.reset_mock() client.async_send_command.return_value = {"updates": []} - await hass.async_stop() + hass.set_state(CoreState.not_running) entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) entry.add_to_hass(hass) diff --git a/tests/test_core.py b/tests/test_core.py index 3a2c34c5e1c..987f228bea8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2869,3 +2869,19 @@ def test_one_time_listener_repr(hass: HomeAssistant) -> None: assert "OneTimeListener" in repr_str assert "test_core" in repr_str assert "_listener" in repr_str + + +async def test_async_add_import_executor_job(hass: HomeAssistant) -> None: + """Test async_add_import_executor_job works and is limited to one thread.""" + evt = threading.Event() + loop = asyncio.get_running_loop() + + def executor_func() -> None: + evt.set() + return evt + + future = hass.async_add_import_executor_job(executor_func) + await loop.run_in_executor(None, evt.wait) + assert await future is evt + + assert hass.import_executor._max_workers == 1 From c1750f7c3a485714a4e2488cb4a58cb58e6ffa47 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 16:04:41 -1000 Subject: [PATCH 0071/1691] Fix circular imports in core integrations (#111875) * Fix circular imports in core integrations * fix circular import * fix more circular imports * fix more circular imports * fix more circular imports * fix more circular imports * fix more circular imports * fix more circular imports * fix more circular imports * adjust * fix * increase timeout * remove unused logger * keep up to date * make sure its reprod --- homeassistant/components/http/__init__.py | 15 +- homeassistant/components/http/auth.py | 2 +- homeassistant/components/http/ban.py | 5 +- homeassistant/components/http/const.py | 3 +- .../components/http/request_context.py | 5 +- homeassistant/components/http/view.py | 179 +---------------- .../components/websocket_api/connection.py | 2 +- homeassistant/helpers/http.py | 184 ++++++++++++++++++ tests/test_circular_imports.py | 39 ++++ 9 files changed, 242 insertions(+), 192 deletions(-) create mode 100644 homeassistant/helpers/http.py create mode 100644 tests/test_circular_imports.py diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 6bb0c154540..ab228e32a52 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -32,6 +32,11 @@ from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.http import ( + KEY_AUTHENTICATED, # noqa: F401 + HomeAssistantView, + current_request, +) from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass @@ -41,20 +46,14 @@ from homeassistant.util.json import json_loads from .auth import async_setup_auth from .ban import setup_bans -from .const import ( # noqa: F401 - KEY_AUTHENTICATED, - KEY_HASS, - KEY_HASS_REFRESH_TOKEN_ID, - KEY_HASS_USER, -) +from .const import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID, KEY_HASS_USER # noqa: F401 from .cors import setup_cors from .decorators import require_admin # noqa: F401 from .forwarded import async_setup_forwarded from .headers import setup_headers -from .request_context import current_request, setup_request_context +from .request_context import setup_request_context from .security_filter import setup_security_filter from .static import CACHE_HEADERS, CachingStaticResource -from .view import HomeAssistantView from .web_runner import HomeAssistantTCPSite DOMAIN: Final = "http" diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 99d38bf582e..640d899924e 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -20,13 +20,13 @@ from homeassistant.auth.const import GROUP_ID_READ_ONLY from homeassistant.auth.models import User from homeassistant.components import websocket_api 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 .const import KEY_AUTHENTICATED, KEY_HASS_REFRESH_TOKEN_ID, KEY_HASS_USER -from .request_context import current_request _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 62569495ba7..0b720b078b9 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -15,7 +15,6 @@ from aiohttp.web import Application, Request, Response, StreamResponse, middlewa from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized import voluptuous as vol -from homeassistant.components import persistent_notification from homeassistant.config import load_yaml_config_file from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -128,6 +127,10 @@ async def process_wrong_login(request: Request) -> None: _LOGGER.warning(log_msg) + # Circular import with websocket_api + # pylint: disable=import-outside-toplevel + from homeassistant.components import persistent_notification + persistent_notification.async_create( hass, notification_msg, "Login attempt failed", NOTIFICATION_ID_LOGIN ) diff --git a/homeassistant/components/http/const.py b/homeassistant/components/http/const.py index df27122b64a..090e5234aeb 100644 --- a/homeassistant/components/http/const.py +++ b/homeassistant/components/http/const.py @@ -1,7 +1,8 @@ """HTTP specific constants.""" from typing import Final -KEY_AUTHENTICATED: Final = "ha_authenticated" +from homeassistant.helpers.http import KEY_AUTHENTICATED # noqa: F401 + KEY_HASS: Final = "hass" KEY_HASS_USER: Final = "hass_user" KEY_HASS_REFRESH_TOKEN_ID: Final = "hass_refresh_token_id" diff --git a/homeassistant/components/http/request_context.py b/homeassistant/components/http/request_context.py index 6e036b9cdc8..b516b63dc5c 100644 --- a/homeassistant/components/http/request_context.py +++ b/homeassistant/components/http/request_context.py @@ -7,10 +7,7 @@ from contextvars import ContextVar from aiohttp.web import Application, Request, StreamResponse, middleware from homeassistant.core import callback - -current_request: ContextVar[Request | None] = ContextVar( - "current_request", default=None -) +from homeassistant.helpers.http import current_request # noqa: F401 @callback diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 1be3d761a3b..ce02879dbb3 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -1,180 +1,7 @@ """Support for views.""" from __future__ import annotations -import asyncio -from collections.abc import Awaitable, Callable -from http import HTTPStatus -import logging -from typing import Any - -from aiohttp import web -from aiohttp.typedefs import LooseHeaders -from aiohttp.web_exceptions import ( - HTTPBadRequest, - HTTPInternalServerError, - HTTPUnauthorized, +from homeassistant.helpers.http import ( # noqa: F401 + HomeAssistantView, + request_handler_factory, ) -from aiohttp.web_urldispatcher import AbstractRoute -import voluptuous as vol - -from homeassistant import exceptions -from homeassistant.const import CONTENT_TYPE_JSON -from homeassistant.core import Context, HomeAssistant, is_callback -from homeassistant.helpers.json import ( - find_paths_unserializable_data, - json_bytes, - json_dumps, -) -from homeassistant.util.json import JSON_ENCODE_EXCEPTIONS, format_unserializable_data - -from .const import KEY_AUTHENTICATED - -_LOGGER = logging.getLogger(__name__) - - -class HomeAssistantView: - """Base view for all views.""" - - url: str | None = None - extra_urls: list[str] = [] - # Views inheriting from this class can override this - requires_auth = True - cors_allowed = False - - @staticmethod - def context(request: web.Request) -> Context: - """Generate a context from a request.""" - if (user := request.get("hass_user")) is None: - return Context() - - return Context(user_id=user.id) - - @staticmethod - def json( - result: Any, - status_code: HTTPStatus | int = HTTPStatus.OK, - headers: LooseHeaders | None = None, - ) -> web.Response: - """Return a JSON response.""" - try: - msg = json_bytes(result) - except JSON_ENCODE_EXCEPTIONS as err: - _LOGGER.error( - "Unable to serialize to JSON. Bad data found at %s", - format_unserializable_data( - find_paths_unserializable_data(result, dump=json_dumps) - ), - ) - raise HTTPInternalServerError from err - response = web.Response( - body=msg, - content_type=CONTENT_TYPE_JSON, - status=int(status_code), - headers=headers, - zlib_executor_size=32768, - ) - response.enable_compression() - return response - - def json_message( - self, - message: str, - status_code: HTTPStatus | int = HTTPStatus.OK, - message_code: str | None = None, - headers: LooseHeaders | None = None, - ) -> web.Response: - """Return a JSON message response.""" - data = {"message": message} - if message_code is not None: - data["code"] = message_code - return self.json(data, status_code, headers=headers) - - def register( - self, hass: HomeAssistant, app: web.Application, router: web.UrlDispatcher - ) -> None: - """Register the view with a router.""" - assert self.url is not None, "No url set for view" - urls = [self.url] + self.extra_urls - routes: list[AbstractRoute] = [] - - for method in ("get", "post", "delete", "put", "patch", "head", "options"): - if not (handler := getattr(self, method, None)): - continue - - handler = request_handler_factory(hass, self, handler) - - for url in urls: - routes.append(router.add_route(method, url, handler)) - - # Use `get` because CORS middleware is not be loaded in emulated_hue - if self.cors_allowed: - allow_cors = app.get("allow_all_cors") - else: - allow_cors = app.get("allow_configured_cors") - - if allow_cors: - for route in routes: - allow_cors(route) - - -def request_handler_factory( - hass: HomeAssistant, view: HomeAssistantView, handler: Callable -) -> Callable[[web.Request], Awaitable[web.StreamResponse]]: - """Wrap the handler classes.""" - is_coroutinefunction = asyncio.iscoroutinefunction(handler) - assert is_coroutinefunction or is_callback( - handler - ), "Handler should be a coroutine or a callback." - - async def handle(request: web.Request) -> web.StreamResponse: - """Handle incoming request.""" - if hass.is_stopping: - return web.Response(status=HTTPStatus.SERVICE_UNAVAILABLE) - - authenticated = request.get(KEY_AUTHENTICATED, False) - - if view.requires_auth and not authenticated: - raise HTTPUnauthorized() - - if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug( - "Serving %s to %s (auth: %s)", - request.path, - request.remote, - authenticated, - ) - - try: - if is_coroutinefunction: - result = await handler(request, **request.match_info) - else: - result = handler(request, **request.match_info) - except vol.Invalid as err: - raise HTTPBadRequest() from err - except exceptions.ServiceNotFound as err: - raise HTTPInternalServerError() from err - except exceptions.Unauthorized as err: - raise HTTPUnauthorized() from err - - if isinstance(result, web.StreamResponse): - # The method handler returned a ready-made Response, how nice of it - return result - - status_code = HTTPStatus.OK - if isinstance(result, tuple): - result, status_code = result - - if isinstance(result, bytes): - return web.Response(body=result, status=status_code) - - if isinstance(result, str): - return web.Response(text=result, status=status_code) - - if result is None: - return web.Response(body=b"", status=status_code) - - raise TypeError( - f"Result should be None, string, bytes or StreamResponse. Got: {result}" - ) - - return handle diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index 280ff41c56e..aa7bcefadae 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -9,9 +9,9 @@ from aiohttp import web import voluptuous as vol from homeassistant.auth.models import RefreshToken, User -from homeassistant.components.http import current_request from homeassistant.core import Context, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError, Unauthorized +from homeassistant.helpers.http import current_request from homeassistant.util.json import JsonValueType from . import const, messages diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py new file mode 100644 index 00000000000..63ff173a3a0 --- /dev/null +++ b/homeassistant/helpers/http.py @@ -0,0 +1,184 @@ +"""Helper to track the current http request.""" +from __future__ import annotations + +import asyncio +from collections.abc import Awaitable, Callable +from contextvars import ContextVar +from http import HTTPStatus +import logging +from typing import Any, Final + +from aiohttp import web +from aiohttp.typedefs import LooseHeaders +from aiohttp.web import Request +from aiohttp.web_exceptions import ( + HTTPBadRequest, + HTTPInternalServerError, + HTTPUnauthorized, +) +from aiohttp.web_urldispatcher import AbstractRoute +import voluptuous as vol + +from homeassistant import exceptions +from homeassistant.const import CONTENT_TYPE_JSON +from homeassistant.core import Context, HomeAssistant, is_callback +from homeassistant.util.json import JSON_ENCODE_EXCEPTIONS, format_unserializable_data + +from .json import find_paths_unserializable_data, json_bytes, json_dumps + +_LOGGER = logging.getLogger(__name__) + + +KEY_AUTHENTICATED: Final = "ha_authenticated" + +current_request: ContextVar[Request | None] = ContextVar( + "current_request", default=None +) + + +def request_handler_factory( + hass: HomeAssistant, view: HomeAssistantView, handler: Callable +) -> Callable[[web.Request], Awaitable[web.StreamResponse]]: + """Wrap the handler classes.""" + is_coroutinefunction = asyncio.iscoroutinefunction(handler) + assert is_coroutinefunction or is_callback( + handler + ), "Handler should be a coroutine or a callback." + + async def handle(request: web.Request) -> web.StreamResponse: + """Handle incoming request.""" + if hass.is_stopping: + return web.Response(status=HTTPStatus.SERVICE_UNAVAILABLE) + + authenticated = request.get(KEY_AUTHENTICATED, False) + + if view.requires_auth and not authenticated: + raise HTTPUnauthorized() + + if _LOGGER.isEnabledFor(logging.DEBUG): + _LOGGER.debug( + "Serving %s to %s (auth: %s)", + request.path, + request.remote, + authenticated, + ) + + try: + if is_coroutinefunction: + result = await handler(request, **request.match_info) + else: + result = handler(request, **request.match_info) + except vol.Invalid as err: + raise HTTPBadRequest() from err + except exceptions.ServiceNotFound as err: + raise HTTPInternalServerError() from err + except exceptions.Unauthorized as err: + raise HTTPUnauthorized() from err + + if isinstance(result, web.StreamResponse): + # The method handler returned a ready-made Response, how nice of it + return result + + status_code = HTTPStatus.OK + if isinstance(result, tuple): + result, status_code = result + + if isinstance(result, bytes): + return web.Response(body=result, status=status_code) + + if isinstance(result, str): + return web.Response(text=result, status=status_code) + + if result is None: + return web.Response(body=b"", status=status_code) + + raise TypeError( + f"Result should be None, string, bytes or StreamResponse. Got: {result}" + ) + + return handle + + +class HomeAssistantView: + """Base view for all views.""" + + url: str | None = None + extra_urls: list[str] = [] + # Views inheriting from this class can override this + requires_auth = True + cors_allowed = False + + @staticmethod + def context(request: web.Request) -> Context: + """Generate a context from a request.""" + if (user := request.get("hass_user")) is None: + return Context() + + return Context(user_id=user.id) + + @staticmethod + def json( + result: Any, + status_code: HTTPStatus | int = HTTPStatus.OK, + headers: LooseHeaders | None = None, + ) -> web.Response: + """Return a JSON response.""" + try: + msg = json_bytes(result) + except JSON_ENCODE_EXCEPTIONS as err: + _LOGGER.error( + "Unable to serialize to JSON. Bad data found at %s", + format_unserializable_data( + find_paths_unserializable_data(result, dump=json_dumps) + ), + ) + raise HTTPInternalServerError from err + response = web.Response( + body=msg, + content_type=CONTENT_TYPE_JSON, + status=int(status_code), + headers=headers, + zlib_executor_size=32768, + ) + response.enable_compression() + return response + + def json_message( + self, + message: str, + status_code: HTTPStatus | int = HTTPStatus.OK, + message_code: str | None = None, + headers: LooseHeaders | None = None, + ) -> web.Response: + """Return a JSON message response.""" + data = {"message": message} + if message_code is not None: + data["code"] = message_code + return self.json(data, status_code, headers=headers) + + def register( + self, hass: HomeAssistant, app: web.Application, router: web.UrlDispatcher + ) -> None: + """Register the view with a router.""" + assert self.url is not None, "No url set for view" + urls = [self.url] + self.extra_urls + routes: list[AbstractRoute] = [] + + for method in ("get", "post", "delete", "put", "patch", "head", "options"): + if not (handler := getattr(self, method, None)): + continue + + handler = request_handler_factory(hass, self, handler) + + for url in urls: + routes.append(router.add_route(method, url, handler)) + + # Use `get` because CORS middleware is not be loaded in emulated_hue + if self.cors_allowed: + allow_cors = app.get("allow_all_cors") + else: + allow_cors = app.get("allow_configured_cors") + + if allow_cors: + for route in routes: + allow_cors(route) diff --git a/tests/test_circular_imports.py b/tests/test_circular_imports.py new file mode 100644 index 00000000000..1c5157b74e1 --- /dev/null +++ b/tests/test_circular_imports.py @@ -0,0 +1,39 @@ +"""Test to check for circular imports in core components.""" +import asyncio +import sys + +import pytest + +from homeassistant.bootstrap import ( + CORE_INTEGRATIONS, + DEBUGGER_INTEGRATIONS, + DEFAULT_INTEGRATIONS, + FRONTEND_INTEGRATIONS, + LOGGING_INTEGRATIONS, + RECORDER_INTEGRATIONS, + STAGE_1_INTEGRATIONS, +) + + +@pytest.mark.timeout(30) # cloud can take > 9s +@pytest.mark.parametrize( + "component", + sorted( + { + *DEBUGGER_INTEGRATIONS, + *CORE_INTEGRATIONS, + *LOGGING_INTEGRATIONS, + *FRONTEND_INTEGRATIONS, + *RECORDER_INTEGRATIONS, + *STAGE_1_INTEGRATIONS, + *DEFAULT_INTEGRATIONS, + } + ), +) +async def test_circular_imports(component: str) -> None: + """Check that components can be imported without circular imports.""" + process = await asyncio.create_subprocess_exec( + sys.executable, "-c", f"import homeassistant.components.{component}" + ) + await process.communicate() + assert process.returncode == 0 From 914abcec3292dd68a8ce15b28da31fcf831de2df Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 1 Mar 2024 03:05:33 +0100 Subject: [PATCH 0072/1691] Change `hass.components` removal version in log to 2024.9 (#111903) --- homeassistant/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 6c736bf8c4d..02696d6beb5 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1277,7 +1277,7 @@ class Components: report( ( f"accesses hass.components.{comp_name}." - " This is deprecated and will stop working in Home Assistant 2024.6, it" + " This is deprecated and will stop working in Home Assistant 2024.9, it" f" should be updated to import functions used from {comp_name} directly" ), error_if_core=False, From d81ed37501139e218041a375b49c1059cc186fed Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 1 Mar 2024 03:05:43 +0100 Subject: [PATCH 0073/1691] Fix unsupported device type in deCONZ integration (#111892) --- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index af1824e441c..ef2f4a73c1b 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["pydeconz"], "quality_scale": "platinum", - "requirements": ["pydeconz==114"], + "requirements": ["pydeconz==115"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 2b0d3ec2240..ac518aa0299 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1758,7 +1758,7 @@ pydaikin==2.11.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==114 +pydeconz==115 # homeassistant.components.delijn pydelijn==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0b1ca167f1c..adf9b994467 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1366,7 +1366,7 @@ pycsspeechtts==1.0.8 pydaikin==2.11.1 # homeassistant.components.deconz -pydeconz==114 +pydeconz==115 # homeassistant.components.dexcom pydexcom==0.2.3 From 72fe170dc81c0ff38a48a8a607345a2a321e6fc3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Feb 2024 16:23:33 -1000 Subject: [PATCH 0074/1691] Remove unused variable in stats _sorted_statistics_to_dict (#111912) --- homeassistant/components/recorder/statistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 5abe395a8d7..771d85e3569 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -2056,7 +2056,7 @@ def _sorted_statistics_to_dict( # noqa: C901 seen_statistic_ids: set[str] = set() key_func = itemgetter(metadata_id_idx) for meta_id, group in groupby(stats, key_func): - stats_list = stats_by_meta_id[meta_id] = list(group) + stats_by_meta_id[meta_id] = list(group) seen_statistic_ids.add(metadata[meta_id]["statistic_id"]) # Set all statistic IDs to empty lists in result set to maintain the order From 5890a7d38c3a7c5a74a455a47ad352b640928a2c Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 29 Feb 2024 21:26:24 -0500 Subject: [PATCH 0075/1691] Add person tracking for UniFi Protect (#111289) --- homeassistant/components/unifiprotect/binary_sensor.py | 9 +++++++++ homeassistant/components/unifiprotect/switch.py | 10 ++++++++++ tests/components/unifiprotect/test_switch.py | 1 + 3 files changed, 20 insertions(+) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 4408075468f..e82a0ef5553 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -273,6 +273,15 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( ufp_value="is_glass_break_detection_on", ufp_perm=PermRequired.NO_WRITE, ), + ProtectBinaryEntityDescription( + key="track_person", + name="Tracking: Person", + icon="mdi:walk", + entity_category=EntityCategory.DIAGNOSTIC, + ufp_required_field="is_ptz", + ufp_value="is_person_tracking_enabled", + ufp_perm=PermRequired.NO_WRITE, + ), ) LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 64890e17d4d..2090e8baef8 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -298,6 +298,16 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( ufp_set_method="set_glass_break_detection", ufp_perm=PermRequired.WRITE, ), + ProtectSwitchEntityDescription( + key="track_person", + name="Tracking: Person", + icon="mdi:walk", + entity_category=EntityCategory.CONFIG, + ufp_required_field="is_ptz", + ufp_value="is_person_tracking_enabled", + ufp_set_method="set_person_track", + ufp_perm=PermRequired.WRITE, + ), ) PRIVACY_MODE_SWITCH = ProtectSwitchEntityDescription[Camera]( diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index 70a21a324d0..1ad3baf5db1 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -37,6 +37,7 @@ CAMERA_SWITCHES_BASIC = [ not d.name.startswith("Detections:") and d.name != "SSH Enabled" and d.name != "Color Night Vision" + and d.name != "Tracking: Person" ) or d.name == "Detections: Motion" or d.name == "Detections: Person" From 0d0b64d3515344706ea18f4e71da292abd085c9f Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 29 Feb 2024 20:53:52 -0600 Subject: [PATCH 0076/1691] Move HassSetPosition to homeassistant domain (#111867) * Move HassSetPosition to homeassistant domain * Add test for unsupported domain with HassSetPosition * Split service intent handler * cleanup --------- Co-authored-by: Paulus Schoutsen --- homeassistant/components/cover/intent.py | 18 +----- homeassistant/components/intent/__init__.py | 43 +++++++++++--- homeassistant/components/valve/intent.py | 22 ------- homeassistant/helpers/intent.py | 59 +++++++++++++++---- .../test_default_agent_intents.py | 3 - tests/components/cover/test_intent.py | 3 +- tests/components/intent/test_init.py | 17 ++++++ tests/components/valve/test_intent.py | 3 +- 8 files changed, 105 insertions(+), 63 deletions(-) delete mode 100644 homeassistant/components/valve/intent.py diff --git a/homeassistant/components/cover/intent.py b/homeassistant/components/cover/intent.py index 633b09f987c..dc8f722c7ed 100644 --- a/homeassistant/components/cover/intent.py +++ b/homeassistant/components/cover/intent.py @@ -1,16 +1,11 @@ """Intents for the cover integration.""" -import voluptuous as vol -from homeassistant.const import ( - SERVICE_CLOSE_COVER, - SERVICE_OPEN_COVER, - SERVICE_SET_COVER_POSITION, -) +from homeassistant.const import SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER from homeassistant.core import HomeAssistant from homeassistant.helpers import intent -from . import ATTR_POSITION, DOMAIN +from . import DOMAIN INTENT_OPEN_COVER = "HassOpenCover" INTENT_CLOSE_COVER = "HassCloseCover" @@ -30,12 +25,3 @@ async def async_setup_intents(hass: HomeAssistant) -> None: INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}" ), ) - intent.async_register( - hass, - intent.ServiceIntentHandler( - intent.INTENT_SET_POSITION, - DOMAIN, - SERVICE_SET_COVER_POSITION, - extra_slots={ATTR_POSITION: vol.All(vol.Range(min=0, max=100))}, - ), - ) diff --git a/homeassistant/components/intent/__init__.py b/homeassistant/components/intent/__init__.py index 3c8e1d57d7c..f307208e537 100644 --- a/homeassistant/components/intent/__init__.py +++ b/homeassistant/components/intent/__init__.py @@ -10,9 +10,11 @@ import voluptuous as vol from homeassistant.components import http from homeassistant.components.cover import ( + ATTR_POSITION, DOMAIN as COVER_DOMAIN, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER, + SERVICE_SET_COVER_POSITION, ) from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.lock import ( @@ -24,6 +26,7 @@ from homeassistant.components.valve import ( DOMAIN as VALVE_DOMAIN, SERVICE_CLOSE_VALVE, SERVICE_OPEN_VALVE, + SERVICE_SET_VALVE_POSITION, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -75,6 +78,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass, NevermindIntentHandler(), ) + intent.async_register(hass, SetPositionIntentHandler()) return True @@ -89,14 +93,16 @@ class IntentPlatformProtocol(Protocol): class OnOffIntentHandler(intent.ServiceIntentHandler): """Intent handler for on/off that also supports covers, valves, locks, etc.""" - async def async_call_service(self, intent_obj: intent.Intent, state: State) -> None: + async def async_call_service( + self, domain: str, service: str, intent_obj: intent.Intent, state: State + ) -> None: """Call service on entity with handling for special cases.""" hass = intent_obj.hass if state.domain == COVER_DOMAIN: # on = open # off = close - if self.service == SERVICE_TURN_ON: + if service == SERVICE_TURN_ON: service_name = SERVICE_OPEN_COVER else: service_name = SERVICE_CLOSE_COVER @@ -117,7 +123,7 @@ class OnOffIntentHandler(intent.ServiceIntentHandler): if state.domain == LOCK_DOMAIN: # on = lock # off = unlock - if self.service == SERVICE_TURN_ON: + if service == SERVICE_TURN_ON: service_name = SERVICE_LOCK else: service_name = SERVICE_UNLOCK @@ -138,7 +144,7 @@ class OnOffIntentHandler(intent.ServiceIntentHandler): if state.domain == VALVE_DOMAIN: # on = opened # off = closed - if self.service == SERVICE_TURN_ON: + if service == SERVICE_TURN_ON: service_name = SERVICE_OPEN_VALVE else: service_name = SERVICE_CLOSE_VALVE @@ -156,13 +162,13 @@ class OnOffIntentHandler(intent.ServiceIntentHandler): ) return - if not hass.services.has_service(state.domain, self.service): + if not hass.services.has_service(state.domain, service): raise intent.IntentHandleError( - f"Service {self.service} does not support entity {state.entity_id}" + f"Service {service} does not support entity {state.entity_id}" ) # Fall back to homeassistant.turn_on/off - await super().async_call_service(intent_obj, state) + await super().async_call_service(domain, service, intent_obj, state) class GetStateIntentHandler(intent.IntentHandler): @@ -296,6 +302,29 @@ class NevermindIntentHandler(intent.IntentHandler): return intent_obj.create_response() +class SetPositionIntentHandler(intent.DynamicServiceIntentHandler): + """Intent handler for setting positions.""" + + def __init__(self) -> None: + """Create set position handler.""" + super().__init__( + intent.INTENT_SET_POSITION, + extra_slots={ATTR_POSITION: vol.All(vol.Range(min=0, max=100))}, + ) + + def get_domain_and_service( + self, intent_obj: intent.Intent, state: State + ) -> tuple[str, str]: + """Get the domain and service name to call.""" + if state.domain == COVER_DOMAIN: + return (COVER_DOMAIN, SERVICE_SET_COVER_POSITION) + + if state.domain == VALVE_DOMAIN: + return (VALVE_DOMAIN, SERVICE_SET_VALVE_POSITION) + + raise intent.IntentHandleError(f"Domain not supported: {state.domain}") + + async def _async_process_intent( hass: HomeAssistant, domain: str, platform: IntentPlatformProtocol ) -> None: diff --git a/homeassistant/components/valve/intent.py b/homeassistant/components/valve/intent.py deleted file mode 100644 index 1b77bdce343..00000000000 --- a/homeassistant/components/valve/intent.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Intents for the valve integration.""" - -import voluptuous as vol - -from homeassistant.const import SERVICE_SET_VALVE_POSITION -from homeassistant.core import HomeAssistant -from homeassistant.helpers import intent - -from . import ATTR_POSITION, DOMAIN - - -async def async_setup_intents(hass: HomeAssistant) -> None: - """Set up the valve intents.""" - intent.async_register( - hass, - intent.ServiceIntentHandler( - intent.INTENT_SET_POSITION, - DOMAIN, - SERVICE_SET_VALVE_POSITION, - extra_slots={ATTR_POSITION: vol.All(vol.Range(min=0, max=100))}, - ), - ) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 2fd745c35fa..82385f0cda8 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import abstractmethod import asyncio from collections.abc import Collection, Coroutine, Iterable import dataclasses @@ -385,8 +386,8 @@ class IntentHandler: return f"<{self.__class__.__name__} - {self.intent_type}>" -class ServiceIntentHandler(IntentHandler): - """Service Intent handler registration. +class DynamicServiceIntentHandler(IntentHandler): + """Service Intent handler registration (dynamic). Service specific intent handler that calls a service by name/entity_id. """ @@ -404,15 +405,11 @@ class ServiceIntentHandler(IntentHandler): def __init__( self, intent_type: str, - domain: str, - service: str, speech: str | None = None, extra_slots: dict[str, vol.Schema] | None = None, ) -> None: """Create Service Intent Handler.""" self.intent_type = intent_type - self.domain = domain - self.service = service self.speech = speech self.extra_slots = extra_slots @@ -441,6 +438,13 @@ class ServiceIntentHandler(IntentHandler): extra=vol.ALLOW_EXTRA, ) + @abstractmethod + def get_domain_and_service( + self, intent_obj: Intent, state: State + ) -> tuple[str, str]: + """Get the domain and service name to call.""" + raise NotImplementedError() + async def async_handle(self, intent_obj: Intent) -> IntentResponse: """Handle the hass intent.""" hass = intent_obj.hass @@ -536,7 +540,10 @@ class ServiceIntentHandler(IntentHandler): service_coros: list[Coroutine[Any, Any, None]] = [] for state in states: - service_coros.append(self.async_call_service(intent_obj, state)) + domain, service = self.get_domain_and_service(intent_obj, state) + service_coros.append( + self.async_call_service(domain, service, intent_obj, state) + ) # Handle service calls in parallel, noting failures as they occur. failed_results: list[IntentResponseTarget] = [] @@ -558,7 +565,7 @@ class ServiceIntentHandler(IntentHandler): # If no entities succeeded, raise an error. failed_entity_ids = [target.id for target in failed_results] raise IntentHandleError( - f"Failed to call {self.service} for: {failed_entity_ids}" + f"Failed to call {service} for: {failed_entity_ids}" ) response.async_set_results( @@ -574,7 +581,9 @@ class ServiceIntentHandler(IntentHandler): return response - async def async_call_service(self, intent_obj: Intent, state: State) -> None: + async def async_call_service( + self, domain: str, service: str, intent_obj: Intent, state: State + ) -> None: """Call service on entity.""" hass = intent_obj.hass @@ -587,13 +596,13 @@ class ServiceIntentHandler(IntentHandler): await self._run_then_background( hass.async_create_task( hass.services.async_call( - self.domain, - self.service, + domain, + service, service_data, context=intent_obj.context, blocking=True, ), - f"intent_call_service_{self.domain}_{self.service}", + f"intent_call_service_{domain}_{service}", ) ) @@ -615,6 +624,32 @@ class ServiceIntentHandler(IntentHandler): raise +class ServiceIntentHandler(DynamicServiceIntentHandler): + """Service Intent handler registration. + + Service specific intent handler that calls a service by name/entity_id. + """ + + def __init__( + self, + intent_type: str, + domain: str, + service: str, + speech: str | None = None, + extra_slots: dict[str, vol.Schema] | None = None, + ) -> None: + """Create service handler.""" + super().__init__(intent_type, speech=speech, extra_slots=extra_slots) + self.domain = domain + self.service = service + + def get_domain_and_service( + self, intent_obj: Intent, state: State + ) -> tuple[str, str]: + """Get the domain and service name to call.""" + return (self.domain, self.service) + + class IntentCategory(Enum): """Category of an intent.""" diff --git a/tests/components/conversation/test_default_agent_intents.py b/tests/components/conversation/test_default_agent_intents.py index e796a6893a8..edf7e17682e 100644 --- a/tests/components/conversation/test_default_agent_intents.py +++ b/tests/components/conversation/test_default_agent_intents.py @@ -8,7 +8,6 @@ from homeassistant.components.cover import intent as cover_intent from homeassistant.components.homeassistant.exposed_entities import async_expose_entity from homeassistant.components.media_player import intent as media_player_intent from homeassistant.components.vacuum import intent as vaccum_intent -from homeassistant.components.valve import intent as valve_intent from homeassistant.const import STATE_CLOSED from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import intent @@ -84,8 +83,6 @@ async def test_valve_intents( init_components, ) -> None: """Test open/close/set position for valves.""" - await valve_intent.async_setup_intents(hass) - entity_id = f"{valve.DOMAIN}.main_valve" hass.states.async_set(entity_id, STATE_CLOSED) async_expose_entity(hass, conversation.DOMAIN, entity_id, True) diff --git a/tests/components/cover/test_intent.py b/tests/components/cover/test_intent.py index 7705dc1c5a9..b1dbe786065 100644 --- a/tests/components/cover/test_intent.py +++ b/tests/components/cover/test_intent.py @@ -11,6 +11,7 @@ from homeassistant.components.cover import ( from homeassistant.const import STATE_CLOSED, STATE_OPEN from homeassistant.core import HomeAssistant from homeassistant.helpers import intent +from homeassistant.setup import async_setup_component from tests.common import async_mock_service @@ -60,7 +61,7 @@ async def test_close_cover_intent(hass: HomeAssistant) -> None: async def test_set_cover_position(hass: HomeAssistant) -> None: """Test HassSetPosition intent for covers.""" - await cover_intent.async_setup_intents(hass) + assert await async_setup_component(hass, "intent", {}) entity_id = f"{DOMAIN}.test_cover" hass.states.async_set( diff --git a/tests/components/intent/test_init.py b/tests/components/intent/test_init.py index 4c327a237c7..77a6a368c01 100644 --- a/tests/components/intent/test_init.py +++ b/tests/components/intent/test_init.py @@ -432,3 +432,20 @@ async def test_get_state_intent( "domain": {"value": "light"}, }, ) + + +async def test_set_position_intent_unsupported_domain(hass: HomeAssistant) -> None: + """Test that HassSetPosition intent fails with unsupported domain.""" + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, "intent", {}) + + # Can't set position of lights + hass.states.async_set("light.test_light", "off") + + with pytest.raises(intent.IntentHandleError): + await intent.async_handle( + hass, + "test", + "HassSetPosition", + {"name": {"value": "test light"}, "position": {"value": 100}}, + ) diff --git a/tests/components/valve/test_intent.py b/tests/components/valve/test_intent.py index 049bb21c722..a8f4054602b 100644 --- a/tests/components/valve/test_intent.py +++ b/tests/components/valve/test_intent.py @@ -6,7 +6,6 @@ from homeassistant.components.valve import ( SERVICE_CLOSE_VALVE, SERVICE_OPEN_VALVE, SERVICE_SET_VALVE_POSITION, - intent as valve_intent, ) from homeassistant.const import STATE_CLOSED, STATE_OPEN from homeassistant.core import HomeAssistant @@ -60,7 +59,7 @@ async def test_close_valve_intent(hass: HomeAssistant) -> None: async def test_set_valve_position(hass: HomeAssistant) -> None: """Test HassSetPosition intent for valves.""" - await valve_intent.async_setup_intents(hass) + assert await async_setup_component(hass, "intent", {}) entity_id = f"{DOMAIN}.test_valve" hass.states.async_set( From ce3d774222f62470b9c7f8e25f2194a92cc07b49 Mon Sep 17 00:00:00 2001 From: Jevgeni Kiski Date: Fri, 1 Mar 2024 11:02:50 +0200 Subject: [PATCH 0077/1691] Add Vallox filter replacement date (#111391) * Vallox: reset filter button * Better names * Change from button to date platform * Review * Fix * Drop ValloxDateEntityDescription * Stale docstrings * Stale docstring --- homeassistant/components/vallox/__init__.py | 1 + homeassistant/components/vallox/date.py | 65 ++++++++++++++++++++ homeassistant/components/vallox/strings.json | 5 ++ tests/components/vallox/conftest.py | 5 ++ tests/components/vallox/test_date.py | 50 +++++++++++++++ 5 files changed, 126 insertions(+) create mode 100644 homeassistant/components/vallox/date.py create mode 100644 tests/components/vallox/test_date.py diff --git a/homeassistant/components/vallox/__init__.py b/homeassistant/components/vallox/__init__.py index d12f7b4ffa1..ec2904385df 100644 --- a/homeassistant/components/vallox/__init__.py +++ b/homeassistant/components/vallox/__init__.py @@ -47,6 +47,7 @@ CONFIG_SCHEMA = vol.Schema( PLATFORMS: list[str] = [ Platform.BINARY_SENSOR, + Platform.DATE, Platform.FAN, Platform.NUMBER, Platform.SENSOR, diff --git a/homeassistant/components/vallox/date.py b/homeassistant/components/vallox/date.py new file mode 100644 index 00000000000..43297006599 --- /dev/null +++ b/homeassistant/components/vallox/date.py @@ -0,0 +1,65 @@ +"""Support for Vallox date platform.""" +from __future__ import annotations + +from datetime import date + +from vallox_websocket_api import Vallox + +from homeassistant.components.date import DateEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ValloxDataUpdateCoordinator, ValloxEntity +from .const import DOMAIN + + +class ValloxFilterChangeDateEntity(ValloxEntity, DateEntity): + """Representation of a Vallox filter change date entity.""" + + _attr_entity_category = EntityCategory.CONFIG + _attr_translation_key = "filter_change_date" + _attr_icon = "mdi:air-filter" + + def __init__( + self, + name: str, + coordinator: ValloxDataUpdateCoordinator, + client: Vallox, + ) -> None: + """Initialize the Vallox date.""" + super().__init__(name, coordinator) + + self._attr_unique_id = f"{self._device_uuid}-filter_change_date" + self._client = client + + @property + def native_value(self) -> date | None: + """Return the latest value.""" + + return self.coordinator.data.filter_change_date + + async def async_set_value(self, value: date) -> None: + """Change the date.""" + + await self._client.set_filter_change_date(value) + await self.coordinator.async_request_refresh() + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Vallox filter change date entity.""" + + data = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + [ + ValloxFilterChangeDateEntity( + data["name"], data["coordinator"], data["client"] + ) + ] + ) diff --git a/homeassistant/components/vallox/strings.json b/homeassistant/components/vallox/strings.json index e3ade9a55c4..d23d54c75cb 100644 --- a/homeassistant/components/vallox/strings.json +++ b/homeassistant/components/vallox/strings.json @@ -84,6 +84,11 @@ "bypass_locked": { "name": "Bypass locked" } + }, + "date": { + "filter_change_date": { + "name": "Filter change date" + } } }, "services": { diff --git a/tests/components/vallox/conftest.py b/tests/components/vallox/conftest.py index 25d631b4351..08c020c1982 100644 --- a/tests/components/vallox/conftest.py +++ b/tests/components/vallox/conftest.py @@ -104,3 +104,8 @@ def patch_set_fan_speed(): def patch_set_values(): """Patch the Vallox metrics set values.""" return patch("homeassistant.components.vallox.Vallox.set_values") + + +def patch_set_filter_change_date(): + """Patch the Vallox metrics set filter change date.""" + return patch("homeassistant.components.vallox.Vallox.set_filter_change_date") diff --git a/tests/components/vallox/test_date.py b/tests/components/vallox/test_date.py new file mode 100644 index 00000000000..1572e9b205c --- /dev/null +++ b/tests/components/vallox/test_date.py @@ -0,0 +1,50 @@ +"""Tests for Vallox date platform.""" + +from datetime import date + +from vallox_websocket_api import MetricData + +from homeassistant.components.date.const import DOMAIN as DATE_DOMAIN, SERVICE_SET_VALUE +from homeassistant.const import ATTR_DATE, ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant + +from .conftest import patch_set_filter_change_date + +from tests.common import MockConfigEntry + + +async def test_set_filter_change_date( + mock_entry: MockConfigEntry, + hass: HomeAssistant, + setup_fetch_metric_data_mock, +) -> None: + """Test set filter change date.""" + + entity_id = "date.vallox_filter_change_date" + + class MockMetricData(MetricData): + @property + def filter_change_date(self): + return date(2024, 1, 1) + + setup_fetch_metric_data_mock(metric_data_class=MockMetricData) + + with patch_set_filter_change_date() as set_filter_change_date: + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + + assert state.state == "2024-01-01" + + await hass.services.async_call( + DATE_DOMAIN, + SERVICE_SET_VALUE, + service_data={ + ATTR_ENTITY_ID: entity_id, + ATTR_DATE: "2024-02-25", + }, + ) + await hass.async_block_till_done() + set_filter_change_date.assert_called_once_with(date(2024, 2, 25)) From 8a236077b06cd17586aabb34dbf8f70c729dd05b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:57:24 +0100 Subject: [PATCH 0078/1691] Bump actions/cache from 4.0.0 to 4.0.1 (#111916) --- .github/workflows/ci.yaml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e6a17d1090c..013fe7edfa3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -231,7 +231,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: venv key: >- @@ -246,7 +246,7 @@ jobs: pip install "$(cat requirements_test.txt | grep pre-commit)" - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ${{ env.PRE_COMMIT_CACHE }} lookup-only: true @@ -276,7 +276,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -285,7 +285,7 @@ jobs: needs.info.outputs.pre-commit_cache_key }} - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -316,7 +316,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -325,7 +325,7 @@ jobs: needs.info.outputs.pre-commit_cache_key }} - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -355,7 +355,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -364,7 +364,7 @@ jobs: needs.info.outputs.pre-commit_cache_key }} - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -454,7 +454,7 @@ jobs: env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: venv lookup-only: true @@ -463,7 +463,7 @@ jobs: needs.info.outputs.python_cache_key }} - name: Restore pip wheel cache if: steps.cache-venv.outputs.cache-hit != 'true' - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: ${{ env.PIP_CACHE }} key: >- @@ -517,7 +517,7 @@ jobs: check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -549,7 +549,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -582,7 +582,7 @@ jobs: check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -633,7 +633,7 @@ jobs: env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -641,7 +641,7 @@ jobs: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Restore mypy cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4.0.1 with: path: .mypy_cache key: >- @@ -708,7 +708,7 @@ jobs: check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -861,7 +861,7 @@ jobs: check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true @@ -988,7 +988,7 @@ jobs: check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.0 + uses: actions/cache/restore@v4.0.1 with: path: venv fail-on-cache-miss: true From 93f2d2bd197e376cc286bea551cee8cb7c39fcbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:57:39 +0100 Subject: [PATCH 0079/1691] Bump github/codeql-action from 3.24.5 to 3.24.6 (#111917) --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f7d97de0022..a2e04d98754 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@v4.1.1 - name: Initialize CodeQL - uses: github/codeql-action/init@v3.24.5 + uses: github/codeql-action/init@v3.24.6 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.24.5 + uses: github/codeql-action/analyze@v3.24.6 with: category: "/language:python" From ae930215d42e120712e278e0915d373c8e16cc9a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:11:52 +0100 Subject: [PATCH 0080/1691] Add icon translations to Kodi (#111853) --- homeassistant/components/kodi/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/kodi/icons.json diff --git a/homeassistant/components/kodi/icons.json b/homeassistant/components/kodi/icons.json new file mode 100644 index 00000000000..07bd246e92d --- /dev/null +++ b/homeassistant/components/kodi/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "add_to_playlist": "mdi:playlist-plus", + "call_method": "mdi:console" + } +} From 18e1b3bbbccda2b6b071a113601235683382028c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:21:31 +0100 Subject: [PATCH 0081/1691] Add icon translations to Enocean (#111514) --- homeassistant/components/enocean/icons.json | 9 +++++++++ homeassistant/components/enocean/sensor.py | 5 +---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/enocean/icons.json diff --git a/homeassistant/components/enocean/icons.json b/homeassistant/components/enocean/icons.json new file mode 100644 index 00000000000..81cd49ac670 --- /dev/null +++ b/homeassistant/components/enocean/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "window_handle": { + "default": "mdi:window-open-variant" + } + } + } +} diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 83c801d598e..e4dd9c7cfb1 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -62,7 +62,6 @@ SENSOR_DESC_TEMPERATURE = EnOceanSensorEntityDescription( key=SENSOR_TYPE_TEMPERATURE, name="Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - icon="mdi:thermometer", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_TEMPERATURE}", @@ -72,7 +71,6 @@ SENSOR_DESC_HUMIDITY = EnOceanSensorEntityDescription( key=SENSOR_TYPE_HUMIDITY, name="Humidity", native_unit_of_measurement=PERCENTAGE, - icon="mdi:water-percent", device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_HUMIDITY}", @@ -82,7 +80,6 @@ SENSOR_DESC_POWER = EnOceanSensorEntityDescription( key=SENSOR_TYPE_POWER, name="Power", native_unit_of_measurement=UnitOfPower.WATT, - icon="mdi:power-plug", device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_POWER}", @@ -91,7 +88,7 @@ SENSOR_DESC_POWER = EnOceanSensorEntityDescription( SENSOR_DESC_WINDOWHANDLE = EnOceanSensorEntityDescription( key=SENSOR_TYPE_WINDOWHANDLE, name="WindowHandle", - icon="mdi:window-open-variant", + translation_key="window_handle", unique_id=lambda dev_id: f"{combine_hex(dev_id)}-{SENSOR_TYPE_WINDOWHANDLE}", ) From 7b79c218353a767a8ebdc5fe9994e98756000491 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:23:08 +0100 Subject: [PATCH 0082/1691] Add icon translations to EZVIZ (#111532) --- homeassistant/components/ezviz/button.py | 4 --- homeassistant/components/ezviz/icons.json | 32 +++++++++++++++++++++++ homeassistant/components/ezviz/number.py | 5 ++-- homeassistant/components/ezviz/select.py | 1 - 4 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/ezviz/icons.json diff --git a/homeassistant/components/ezviz/button.py b/homeassistant/components/ezviz/button.py index abc44419075..98608a15149 100644 --- a/homeassistant/components/ezviz/button.py +++ b/homeassistant/components/ezviz/button.py @@ -41,7 +41,6 @@ BUTTON_ENTITIES = ( EzvizButtonEntityDescription( key="ptz_up", translation_key="ptz_up", - icon="mdi:pan", method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( "UP", serial, run ), @@ -50,7 +49,6 @@ BUTTON_ENTITIES = ( EzvizButtonEntityDescription( key="ptz_down", translation_key="ptz_down", - icon="mdi:pan", method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( "DOWN", serial, run ), @@ -59,7 +57,6 @@ BUTTON_ENTITIES = ( EzvizButtonEntityDescription( key="ptz_left", translation_key="ptz_left", - icon="mdi:pan", method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( "LEFT", serial, run ), @@ -68,7 +65,6 @@ BUTTON_ENTITIES = ( EzvizButtonEntityDescription( key="ptz_right", translation_key="ptz_right", - icon="mdi:pan", method=lambda pyezviz_client, serial, run: pyezviz_client.ptz_control( "RIGHT", serial, run ), diff --git a/homeassistant/components/ezviz/icons.json b/homeassistant/components/ezviz/icons.json new file mode 100644 index 00000000000..89b4747ed69 --- /dev/null +++ b/homeassistant/components/ezviz/icons.json @@ -0,0 +1,32 @@ +{ + "entity": { + "button": { + "ptz_up": { + "default": "mdi:pan" + }, + "ptz_down": { + "default": "mdi:pan" + }, + "ptz_left": { + "default": "mdi:pan" + }, + "ptz_right": { + "default": "mdi:pan" + } + }, + "number": { + "detection_sensibility": { + "default": "mdi:eye" + } + }, + "select": { + "alarm_sound_mode": { + "default": "mdi:alarm" + } + } + }, + "services": { + "set_alarm_detection_sensibility": "mdi:motion-sensor", + "wake_device": "mdi:sleep-off" + } +} diff --git a/homeassistant/components/ezviz/number.py b/homeassistant/components/ezviz/number.py index c922173aa87..856d7fe392b 100644 --- a/homeassistant/components/ezviz/number.py +++ b/homeassistant/components/ezviz/number.py @@ -48,7 +48,6 @@ class EzvizNumberEntityDescription( NUMBER_TYPE = EzvizNumberEntityDescription( key="detection_sensibility", translation_key="detection_sensibility", - icon="mdi:eye", entity_category=EntityCategory.CONFIG, native_min_value=0, native_step=1, @@ -68,8 +67,8 @@ async def async_setup_entry( async_add_entities( EzvizNumber(coordinator, camera, value, entry.entry_id) for camera in coordinator.data - for capibility, value in coordinator.data[camera]["supportExt"].items() - if capibility == NUMBER_TYPE.supported_ext + for capability, value in coordinator.data[camera]["supportExt"].items() + if capability == NUMBER_TYPE.supported_ext if value in NUMBER_TYPE.supported_ext_value ) diff --git a/homeassistant/components/ezviz/select.py b/homeassistant/components/ezviz/select.py index 8110cf61a5c..d0e86cb026c 100644 --- a/homeassistant/components/ezviz/select.py +++ b/homeassistant/components/ezviz/select.py @@ -37,7 +37,6 @@ class EzvizSelectEntityDescription( SELECT_TYPE = EzvizSelectEntityDescription( key="alarm_sound_mod", translation_key="alarm_sound_mode", - icon="mdi:alarm", entity_category=EntityCategory.CONFIG, options=["soft", "intensive", "silent"], supported_switch=DeviceSwitchType.ALARM_TONE.value, From cc2ce4f4a6f64abe0dc75fb54568682196c9a9fb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:25:34 +0100 Subject: [PATCH 0083/1691] Add icon translations to Flipr (#111540) --- homeassistant/components/flipr/icons.json | 12 ++++++++++++ homeassistant/components/flipr/sensor.py | 3 --- tests/components/flipr/test_sensor.py | 7 ------- 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/flipr/icons.json diff --git a/homeassistant/components/flipr/icons.json b/homeassistant/components/flipr/icons.json new file mode 100644 index 00000000000..2e55e81e562 --- /dev/null +++ b/homeassistant/components/flipr/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "chlorine": { + "default": "mdi:pool" + }, + "red_ox": { + "default": "mdi:pool" + } + } + } +} diff --git a/homeassistant/components/flipr/sensor.py b/homeassistant/components/flipr/sensor.py index 66078c50c1a..452e5b097e9 100644 --- a/homeassistant/components/flipr/sensor.py +++ b/homeassistant/components/flipr/sensor.py @@ -20,12 +20,10 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="chlorine", translation_key="chlorine", native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, - icon="mdi:pool", state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="ph", - icon="mdi:pool", device_class=SensorDeviceClass.PH, state_class=SensorStateClass.MEASUREMENT, ), @@ -45,7 +43,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="red_ox", translation_key="red_ox", native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, - icon="mdi:pool", state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( diff --git a/tests/components/flipr/test_sensor.py b/tests/components/flipr/test_sensor.py index 54684722802..339986dd54b 100644 --- a/tests/components/flipr/test_sensor.py +++ b/tests/components/flipr/test_sensor.py @@ -7,7 +7,6 @@ from flipr_api.exceptions import FliprError from homeassistant.components.flipr.const import CONF_FLIPR_ID, DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass from homeassistant.const import ( - ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, CONF_EMAIL, CONF_PASSWORD, @@ -61,42 +60,36 @@ async def test_sensors(hass: HomeAssistant, entity_registry: er.EntityRegistry) state = hass.states.get("sensor.flipr_myfliprid_ph") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:pool" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.state == "7.03" state = hass.states.get("sensor.flipr_myfliprid_water_temperature") assert state - assert state.attributes.get(ATTR_ICON) is None assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.CELSIUS assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.state == "10.5" state = hass.states.get("sensor.flipr_myfliprid_last_measured") assert state - assert state.attributes.get(ATTR_ICON) is None assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None assert state.attributes.get(ATTR_STATE_CLASS) is None assert state.state == "2021-02-15T09:10:32+00:00" state = hass.states.get("sensor.flipr_myfliprid_red_ox") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:pool" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "mV" assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.state == "657.58" state = hass.states.get("sensor.flipr_myfliprid_chlorine") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:pool" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "mV" assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.state == "0.23654886" state = hass.states.get("sensor.flipr_myfliprid_battery") assert state - assert state.attributes.get(ATTR_ICON) is None assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.state == "95.0" From 3d987a91908041180b4074a1826637da03990405 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:29:02 +0100 Subject: [PATCH 0084/1691] Add icon translations to Flux LED (#111545) --- homeassistant/components/flux_led/button.py | 1 - homeassistant/components/flux_led/icons.json | 61 ++++++++++++++++++++ homeassistant/components/flux_led/number.py | 5 -- homeassistant/components/flux_led/select.py | 3 - homeassistant/components/flux_led/sensor.py | 1 - homeassistant/components/flux_led/switch.py | 10 ---- 6 files changed, 61 insertions(+), 20 deletions(-) create mode 100644 homeassistant/components/flux_led/icons.json diff --git a/homeassistant/components/flux_led/button.py b/homeassistant/components/flux_led/button.py index eb3a7341d4d..5bd3fbbbdbe 100644 --- a/homeassistant/components/flux_led/button.py +++ b/homeassistant/components/flux_led/button.py @@ -28,7 +28,6 @@ RESTART_BUTTON_DESCRIPTION = ButtonEntityDescription( UNPAIR_REMOTES_DESCRIPTION = ButtonEntityDescription( key=_UNPAIR_REMOTES_KEY, translation_key="unpair_remotes", - icon="mdi:remote-off", ) diff --git a/homeassistant/components/flux_led/icons.json b/homeassistant/components/flux_led/icons.json new file mode 100644 index 00000000000..873fcd7c441 --- /dev/null +++ b/homeassistant/components/flux_led/icons.json @@ -0,0 +1,61 @@ +{ + "entity": { + "button": { + "unpair_remotes": { + "default": "mdi:remote-off" + } + }, + "number": { + "effect_speed": { + "default": "mdi:speedometer" + }, + "pixels_per_segment": { + "default": "mdi:dots-grid" + }, + "segments": { + "default": "mdi:segment" + }, + "music_pixels_per_segment": { + "default": "mdi:dots-grid" + }, + "music_segments": { + "default": "mdi:segment" + } + }, + "select": { + "power_restored": { + "default": "mdi:transmission-tower-off" + }, + "ic_type": { + "default": "mdi:chip" + }, + "wiring": { + "default": "mdi:led-strip-variant" + } + }, + "sensor": { + "paired_remotes": { + "default": "mdi:remote" + } + }, + "switch": { + "remote_access": { + "default": "mdi:cloud-off-outline", + "state": { + "on": "mdi:cloud-outline" + } + }, + "music": { + "default": "mdi:microphone-off", + "state": { + "on": "mdi:microphone" + } + } + } + }, + "services": { + "set_custom_effect": "mdi:creation", + "set_zones": "mdi:texture-box", + "set_music_mode": "mdi:music" + } +} diff --git a/homeassistant/components/flux_led/number.py b/homeassistant/components/flux_led/number.py index ac23fbe64b5..5cd95c19328 100644 --- a/homeassistant/components/flux_led/number.py +++ b/homeassistant/components/flux_led/number.py @@ -89,7 +89,6 @@ class FluxSpeedNumber( _attr_native_max_value = 100 _attr_native_step = 1 _attr_mode = NumberMode.SLIDER - _attr_icon = "mdi:speedometer" _attr_translation_key = "effect_speed" @property @@ -175,7 +174,6 @@ class FluxPixelsPerSegmentNumber(FluxConfigNumber): """Defines a flux_led pixels per segment number.""" _attr_translation_key = "pixels_per_segment" - _attr_icon = "mdi:dots-grid" @property def native_max_value(self) -> int: @@ -202,7 +200,6 @@ class FluxSegmentsNumber(FluxConfigNumber): """Defines a flux_led segments number.""" _attr_translation_key = "segments" - _attr_icon = "mdi:segment" @property def native_max_value(self) -> int: @@ -237,7 +234,6 @@ class FluxMusicPixelsPerSegmentNumber(FluxMusicNumber): """Defines a flux_led music pixels per segment number.""" _attr_translation_key = "music_pixels_per_segment" - _attr_icon = "mdi:dots-grid" @property def native_max_value(self) -> int: @@ -266,7 +262,6 @@ class FluxMusicSegmentsNumber(FluxMusicNumber): """Defines a flux_led music segments number.""" _attr_translation_key = "music_segments" - _attr_icon = "mdi:segment" @property def native_max_value(self) -> int: diff --git a/homeassistant/components/flux_led/select.py b/homeassistant/components/flux_led/select.py index cca86e5a216..e920eefc467 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -91,7 +91,6 @@ class FluxPowerStateSelect(FluxConfigAtStartSelect, SelectEntity): """Representation of a Flux power restore state option.""" _attr_translation_key = "power_restored" - _attr_icon = "mdi:transmission-tower-off" _attr_options = list(NAME_TO_POWER_RESTORE_STATE) def __init__( @@ -125,7 +124,6 @@ class FluxPowerStateSelect(FluxConfigAtStartSelect, SelectEntity): class FluxICTypeSelect(FluxConfigSelect): """Representation of Flux ic type.""" - _attr_icon = "mdi:chip" _attr_translation_key = "ic_type" @property @@ -148,7 +146,6 @@ class FluxICTypeSelect(FluxConfigSelect): class FluxWiringsSelect(FluxConfigSelect): """Representation of Flux wirings.""" - _attr_icon = "mdi:led-strip-variant" _attr_translation_key = "wiring" @property diff --git a/homeassistant/components/flux_led/sensor.py b/homeassistant/components/flux_led/sensor.py index 9a19c629383..42590715caf 100644 --- a/homeassistant/components/flux_led/sensor.py +++ b/homeassistant/components/flux_led/sensor.py @@ -34,7 +34,6 @@ async def async_setup_entry( class FluxPairedRemotes(FluxEntity, SensorEntity): """Representation of a Magic Home paired remotes sensor.""" - _attr_icon = "mdi:remote" _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_translation_key = "paired_remotes" diff --git a/homeassistant/components/flux_led/switch.py b/homeassistant/components/flux_led/switch.py index 58aee132216..09f80640f71 100644 --- a/homeassistant/components/flux_led/switch.py +++ b/homeassistant/components/flux_led/switch.py @@ -103,11 +103,6 @@ class FluxRemoteAccessSwitch(FluxBaseEntity, SwitchEntity): """Return true if remote access is enabled.""" return bool(self.entry.data[CONF_REMOTE_ACCESS_ENABLED]) - @property - def icon(self) -> str: - """Return icon based on state.""" - return "mdi:cloud-outline" if self.is_on else "mdi:cloud-off-outline" - class FluxMusicSwitch(FluxEntity, SwitchEntity): """Representation of a Flux music switch.""" @@ -131,8 +126,3 @@ class FluxMusicSwitch(FluxEntity, SwitchEntity): def is_on(self) -> bool: """Return true if microphone is is on.""" return self._device.is_on and self._device.effect == MODE_MUSIC - - @property - def icon(self) -> str: - """Return icon based on state.""" - return "mdi:microphone" if self.is_on else "mdi:microphone-off" From fd9e9ebf5020048983464a273f08e3b42bd8adcc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:29:23 +0100 Subject: [PATCH 0085/1691] Add icon translations to Hydrawise (#111810) --- homeassistant/components/hydrawise/icons.json | 9 +++++++++ homeassistant/components/hydrawise/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/hydrawise/icons.json diff --git a/homeassistant/components/hydrawise/icons.json b/homeassistant/components/hydrawise/icons.json new file mode 100644 index 00000000000..717b5c48357 --- /dev/null +++ b/homeassistant/components/hydrawise/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "watering_time": { + "default": "mdi:water-pump" + } + } + } +} diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index f8490ad00e1..9e3445b2c5d 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -33,7 +33,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="watering_time", translation_key="watering_time", - icon="mdi:water-pump", native_unit_of_measurement=UnitOfTime.MINUTES, ), ) From 9989a63cdfc9520be328dd273c422f7f1292daff Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 1 Mar 2024 12:29:35 +0100 Subject: [PATCH 0086/1691] Add reconfigure step to config flow (#108794) * Initial commit reconfigure * test config config_entries * Fix reconfigure * test_config_entries * review comment * No reconfigure if reauth ongoing * Fix tests * Fix tests * handle source creating flows * combine * No black * Also check reconfigure in reauth flow * Fix support * Add entry id * reset data entry flow * Mods * context data * reset formatting * Fix config flow platforms * Fix tests * Fix step message * Handling reconfigure step * Fix more tests * Config entries tests * entry_id always means reconfigure * Mods * Remove no longer valid exception * Fixes * reset silabs test * dev reg * resets * assist pipeline * Adjust config_entries * Fix * Fixes * docstrings * Review comment * docstring --- .../components/config/config_entries.py | 26 ++- homeassistant/config_entries.py | 77 ++++++++- homeassistant/helpers/data_entry_flow.py | 16 +- .../components/config/test_config_entries.py | 158 +++++++++++++++++- tests/test_config_entries.py | 94 +++++++++++ 5 files changed, 358 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 52904cb8d35..dc684a45770 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -13,6 +13,7 @@ from homeassistant import config_entries, data_entry_flow from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT from homeassistant.components import websocket_api from homeassistant.components.http import HomeAssistantView, require_admin +from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import DependencyError, Unauthorized import homeassistant.helpers.config_validation as cv @@ -153,10 +154,26 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): @require_admin( error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") ) - async def post(self, request: web.Request) -> web.Response: - """Handle a POST request.""" + @RequestDataValidator( + vol.Schema( + { + vol.Required("handler"): vol.Any(str, list), + vol.Optional("show_advanced_options", default=False): cv.boolean, + vol.Optional("entry_id"): cv.string, + }, + extra=vol.ALLOW_EXTRA, + ) + ) + async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: + """Initialize a POST request for a config entry flow.""" + return await self._post_impl(request, data) + + async def _post_impl( + self, request: web.Request, data: dict[str, Any] + ) -> web.Response: + """Handle a POST request for a config entry flow.""" try: - return await super().post(request) + return await super()._post_impl(request, data) except DependencyError as exc: return web.Response( text=f"Failed dependencies {', '.join(exc.failed_dependencies)}", @@ -167,6 +184,9 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): """Return context.""" context = super().get_context(data) context["source"] = config_entries.SOURCE_USER + if entry_id := data.get("entry_id"): + context["source"] = config_entries.SOURCE_RECONFIGURE + context["entry_id"] = entry_id return context def _prepare_result_json( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 2200831e576..a96716ff84b 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -104,6 +104,9 @@ SOURCE_UNIGNORE = "unignore" # This is used to signal that re-authentication is required by the user. SOURCE_REAUTH = "reauth" +# This is used to initiate a reconfigure flow by the user. +SOURCE_RECONFIGURE = "reconfigure" + HANDLERS: Registry[str, type[ConfigFlow]] = Registry() STORAGE_KEY = "core.config_entries" @@ -343,6 +346,9 @@ class ConfigEntry: # Supports options self._supports_options: bool | None = None + # Supports reconfigure + self._supports_reconfigure: bool | None = None + # Listeners to call on update self.update_listeners: list[UpdateListenerType] = [] @@ -361,6 +367,8 @@ class ConfigEntry: self.reload_lock = asyncio.Lock() # Reauth lock to prevent concurrent reauth flows self._reauth_lock = asyncio.Lock() + # Reconfigure lock to prevent concurrent reconfigure flows + self._reconfigure_lock = asyncio.Lock() self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() @@ -413,6 +421,20 @@ class ConfigEntry: ) return self._supports_options or False + @property + def supports_reconfigure(self) -> bool: + """Return if entry supports config options.""" + if self._supports_reconfigure is None and ( + handler := HANDLERS.get(self.domain) + ): + # work out if handler has support for reconfigure step + object.__setattr__( + self, + "_supports_reconfigure", + hasattr(handler, "async_step_reconfigure"), + ) + return self._supports_reconfigure or False + def clear_cache(self) -> None: """Clear cached properties.""" with contextlib.suppress(AttributeError): @@ -430,6 +452,7 @@ class ConfigEntry: "supports_options": self.supports_options, "supports_remove_device": self.supports_remove_device or False, "supports_unload": self.supports_unload or False, + "supports_reconfigure": self.supports_reconfigure or False, "pref_disable_new_entities": self.pref_disable_new_entities, "pref_disable_polling": self.pref_disable_polling, "disabled_by": self.disabled_by, @@ -462,7 +485,6 @@ class ConfigEntry: self.supports_remove_device = await support_remove_from_device( hass, self.domain ) - try: component = integration.get_component() except ImportError as err: @@ -856,8 +878,8 @@ class ConfigEntry: """Start a reauth flow.""" # We will check this again in the task when we hold the lock, # but we also check it now to try to avoid creating the task. - if any(self.async_get_active_flows(hass, {SOURCE_REAUTH})): - # Reauth flow already in progress for this entry + if any(self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH})): + # Reauth or Reconfigure flow already in progress for this entry return hass.async_create_task( self._async_init_reauth(hass, context, data), @@ -872,8 +894,10 @@ class ConfigEntry: ) -> None: """Start a reauth flow.""" async with self._reauth_lock: - if any(self.async_get_active_flows(hass, {SOURCE_REAUTH})): - # Reauth flow already in progress for this entry + if any( + self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH}) + ): + # Reauth or Reconfigure flow already in progress for this entry return result = await hass.config_entries.flow.async_init( self.domain, @@ -903,6 +927,49 @@ class ConfigEntry: translation_placeholders={"name": self.title}, ) + @callback + def async_start_reconfigure( + self, + hass: HomeAssistant, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, + ) -> None: + """Start a reconfigure flow.""" + # We will check this again in the task when we hold the lock, + # but we also check it now to try to avoid creating the task. + if any(self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH})): + # Reconfigure or reauth flow already in progress for this entry + return + hass.async_create_task( + self._async_init_reconfigure(hass, context, data), + f"config entry reconfigure {self.title} {self.domain} {self.entry_id}", + ) + + async def _async_init_reconfigure( + self, + hass: HomeAssistant, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, + ) -> None: + """Start a reconfigure flow.""" + async with self._reconfigure_lock: + if any( + self.async_get_active_flows(hass, {SOURCE_RECONFIGURE, SOURCE_REAUTH}) + ): + # Reconfigure or reauth flow already in progress for this entry + return + await hass.config_entries.flow.async_init( + self.domain, + context={ + "source": SOURCE_RECONFIGURE, + "entry_id": self.entry_id, + "title_placeholders": {"name": self.title}, + "unique_id": self.unique_id, + } + | (context or {}), + data=self.data | (data or {}), + ) + @callback def async_get_active_flows( self, hass: HomeAssistant, sources: set[str] diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 6a6e48caa7e..9a3e3a0f5e0 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -61,6 +61,16 @@ class FlowManagerIndexView(_BaseFlowManagerView): ) ) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: + """Initialize a POST request. + + Override `_post_impl` in subclasses which need + to implement their own `RequestDataValidator` + """ + return await self._post_impl(request, data) + + async def _post_impl( + self, request: web.Request, data: dict[str, Any] + ) -> web.Response: """Handle a POST request.""" if isinstance(data["handler"], list): handler = tuple(data["handler"]) @@ -74,10 +84,8 @@ class FlowManagerIndexView(_BaseFlowManagerView): ) except data_entry_flow.UnknownHandler: return self.json_message("Invalid handler specified", HTTPStatus.NOT_FOUND) - except data_entry_flow.UnknownStep: - return self.json_message( - "Handler does not support user", HTTPStatus.BAD_REQUEST - ) + except data_entry_flow.UnknownStep as err: + return self.json_message(str(err), HTTPStatus.BAD_REQUEST) result = self._prepare_result_json(result) diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 6573a83b061..a55657d792c 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -3,6 +3,7 @@ from collections import OrderedDict from http import HTTPStatus from unittest.mock import ANY, AsyncMock, patch +from aiohttp.test_utils import TestClient import pytest import voluptuous as vol @@ -39,7 +40,7 @@ def mock_test_component(hass): @pytest.fixture -async def client(hass, hass_client): +async def client(hass, hass_client) -> TestClient: """Fixture that can interact with the config manager API.""" await async_setup_component(hass, "http", {}) config_entries.async_setup(hass) @@ -121,6 +122,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "title": "Test 1", "source": "bla", "state": core_ce.ConfigEntryState.NOT_LOADED.value, + "supports_reconfigure": False, "supports_options": True, "supports_remove_device": False, "supports_unload": True, @@ -134,6 +136,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "title": "Test 2", "source": "bla2", "state": core_ce.ConfigEntryState.SETUP_ERROR.value, + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -147,6 +150,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "title": "Test 3", "source": "bla3", "state": core_ce.ConfigEntryState.NOT_LOADED.value, + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -160,6 +164,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "title": "Test 4", "source": "bla4", "state": core_ce.ConfigEntryState.NOT_LOADED.value, + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -173,6 +178,7 @@ async def test_get_entries(hass: HomeAssistant, client, clear_handlers) -> None: "title": "Test 5", "source": "bla5", "state": core_ce.ConfigEntryState.NOT_LOADED.value, + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -521,6 +527,7 @@ async def test_create_account( "entry_id": entries[0].entry_id, "source": core_ce.SOURCE_USER, "state": core_ce.ConfigEntryState.LOADED.value, + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -599,6 +606,7 @@ async def test_two_step_flow( "entry_id": entries[0].entry_id, "source": core_ce.SOURCE_USER, "state": core_ce.ConfigEntryState.LOADED.value, + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1051,6 +1059,7 @@ async def test_get_single( "reason": None, "source": "user", "state": "loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1385,6 +1394,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1399,6 +1409,7 @@ async def test_get_matching_entries_ws( "reason": "Unsupported API", "source": "bla2", "state": "setup_error", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1413,6 +1424,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla3", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1427,6 +1439,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla4", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1441,6 +1454,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla5", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1466,6 +1480,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1490,6 +1505,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla4", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1504,6 +1520,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla5", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1528,6 +1545,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1542,6 +1560,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla3", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1572,6 +1591,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1586,6 +1606,7 @@ async def test_get_matching_entries_ws( "reason": "Unsupported API", "source": "bla2", "state": "setup_error", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1600,6 +1621,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla3", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1614,6 +1636,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla4", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1628,6 +1651,7 @@ async def test_get_matching_entries_ws( "reason": None, "source": "bla5", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1726,6 +1750,7 @@ async def test_subscribe_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1743,6 +1768,7 @@ async def test_subscribe_entries_ws( "reason": "Unsupported API", "source": "bla2", "state": "setup_error", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1760,6 +1786,7 @@ async def test_subscribe_entries_ws( "reason": None, "source": "bla3", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1781,6 +1808,7 @@ async def test_subscribe_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1803,6 +1831,7 @@ async def test_subscribe_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1825,6 +1854,7 @@ async def test_subscribe_entries_ws( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1906,6 +1936,7 @@ async def test_subscribe_entries_ws_filtered( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1923,6 +1954,7 @@ async def test_subscribe_entries_ws_filtered( "reason": None, "source": "bla3", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1946,6 +1978,7 @@ async def test_subscribe_entries_ws_filtered( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1967,6 +2000,7 @@ async def test_subscribe_entries_ws_filtered( "reason": None, "source": "bla3", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -1990,6 +2024,7 @@ async def test_subscribe_entries_ws_filtered( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -2012,6 +2047,7 @@ async def test_subscribe_entries_ws_filtered( "reason": None, "source": "bla", "state": "not_loaded", + "supports_reconfigure": False, "supports_options": False, "supports_remove_device": False, "supports_unload": False, @@ -2106,3 +2142,123 @@ async def test_flow_with_multiple_schema_errors_base( "latitude": "required key not provided", } } + + +async def test_supports_reconfigure( + hass: HomeAssistant, client, enable_custom_integrations: None +) -> None: + """Test a flow that support reconfigure step.""" + mock_platform(hass, "test.config_flow", None) + + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) + + class TestFlow(core_ce.ConfigFlow): + VERSION = 1 + + async def async_step_user(self, user_input=None): + return self.async_create_entry( + title="Test Entry", data={"secret": "account_token"} + ) + + async def async_step_reconfigure(self, user_input=None): + if user_input is None: + return self.async_show_form( + step_id="reconfigure", data_schema=vol.Schema({}) + ) + return self.async_create_entry( + title="Test Entry", data={"secret": "account_token"} + ) + + with patch.dict(HANDLERS, {"test": TestFlow}): + resp = await client.post( + "/api/config/config_entries/flow", + json={"handler": "test", "entry_id": "1"}, + ) + + assert resp.status == HTTPStatus.OK + + data = await resp.json() + flow_id = data.pop("flow_id") + + assert data == { + "type": "form", + "handler": "test", + "step_id": "reconfigure", + "data_schema": [], + "last_step": None, + "preview": None, + "description_placeholders": None, + "errors": None, + } + + with patch.dict(HANDLERS, {"test": TestFlow}): + resp = await client.post( + f"/api/config/config_entries/flow/{flow_id}", + json={}, + ) + assert resp.status == HTTPStatus.OK + + entries = hass.config_entries.async_entries("test") + assert len(entries) == 1 + + data = await resp.json() + data.pop("flow_id") + assert data == { + "handler": "test", + "title": "Test Entry", + "type": "create_entry", + "version": 1, + "result": { + "disabled_by": None, + "domain": "test", + "entry_id": entries[0].entry_id, + "source": core_ce.SOURCE_RECONFIGURE, + "state": core_ce.ConfigEntryState.LOADED.value, + "supports_reconfigure": True, + "supports_options": False, + "supports_remove_device": False, + "supports_unload": False, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "title": "Test Entry", + "reason": None, + }, + "description": None, + "description_placeholders": None, + "options": {}, + "minor_version": 1, + } + + +async def test_does_not_support_reconfigure( + hass: HomeAssistant, client: TestClient, enable_custom_integrations: None +) -> None: + """Test a flow that does not support reconfigure step.""" + mock_platform(hass, "test.config_flow", None) + + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) + + class TestFlow(core_ce.ConfigFlow): + VERSION = 1 + + async def async_step_user(self, user_input=None): + return self.async_create_entry( + title="Test Entry", data={"secret": "account_token"} + ) + + with patch.dict(HANDLERS, {"test": TestFlow}): + resp = await client.post( + "/api/config/config_entries/flow", + json={"handler": "test", "entry_id": "1"}, + ) + + assert resp.status == HTTPStatus.BAD_REQUEST + response = await resp.text() + assert ( + response + == '{"message":"Handler ConfigEntriesFlowManager doesn\'t support step reconfigure"}' + ) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 672dbb9ae64..247a34c078b 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -70,6 +70,10 @@ def mock_handlers() -> Generator[None, None, None]: return self.async_show_form(step_id="reauth_confirm") return self.async_abort(reason="test") + async def async_step_reconfigure(self, data): + """Mock Reauth.""" + return await self.async_step_reauth_confirm() + with patch.dict( config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler} ): @@ -826,6 +830,8 @@ async def test_as_dict(snapshot: SnapshotAssertion) -> None: "_tries", "_setup_again_job", "_supports_options", + "_reconfigure_lock", + "supports_reconfigure", } entry = MockConfigEntry(entry_id="mock-entry") @@ -4062,6 +4068,94 @@ async def test_reauth(hass: HomeAssistant) -> None: assert len(hass.config_entries.flow.async_progress()) == 1 +async def test_reconfigure(hass: HomeAssistant) -> None: + """Test the async_reconfigure_helper.""" + entry = MockConfigEntry(title="test_title", domain="test") + entry2 = MockConfigEntry(title="test_title", domain="test") + + mock_setup_entry = AsyncMock(return_value=True) + mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry)) + mock_platform(hass, "test.config_flow", None) + + await entry.async_setup(hass) + await hass.async_block_till_done() + + flow = hass.config_entries.flow + with patch.object(flow, "async_init", wraps=flow.async_init) as mock_init: + entry.async_start_reconfigure( + hass, + context={"extra_context": "some_extra_context"}, + data={"extra_data": 1234}, + ) + await hass.async_block_till_done() + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["context"]["entry_id"] == entry.entry_id + assert flows[0]["context"]["source"] == config_entries.SOURCE_RECONFIGURE + assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"} + assert flows[0]["context"]["extra_context"] == "some_extra_context" + + assert mock_init.call_args.kwargs["data"]["extra_data"] == 1234 + + assert entry.entry_id != entry2.entry_id + + # Check that we can't start duplicate reconfigure flows + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + + # Check that we can't start duplicate reconfigure flows when the context is different + entry.async_start_reconfigure(hass, {"diff": "diff"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + + # Check that we can start a reconfigure flow for a different entry + entry2.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 2 + + # Abort all existing flows + for flow in hass.config_entries.flow.async_progress(): + hass.config_entries.flow.async_abort(flow["flow_id"]) + await hass.async_block_till_done() + + # Check that we can't start duplicate reconfigure flows + # without blocking between flows + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + + # Abort all existing flows + for flow in hass.config_entries.flow.async_progress(): + hass.config_entries.flow.async_abort(flow["flow_id"]) + await hass.async_block_till_done() + + # Check that we can't start reconfigure flows with active reauth flow + entry.async_start_reauth(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + + # Abort all existing flows + for flow in hass.config_entries.flow.async_progress(): + hass.config_entries.flow.async_abort(flow["flow_id"]) + await hass.async_block_till_done() + + # Check that we can't start reauth flows with active reconfigure flow + entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + entry.async_start_reauth(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(hass.config_entries.flow.async_progress()) == 1 + + async def test_get_active_flows(hass: HomeAssistant) -> None: """Test the async_get_active_flows helper.""" entry = MockConfigEntry(title="test_title", domain="test") From 05b0518854553c5340fc16f2cc397b33128bdf74 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:33:24 +0100 Subject: [PATCH 0087/1691] Add icon translations to Medcom BLE (#111904) Use default icon for Medcom BLE --- homeassistant/components/medcom_ble/icons.json | 9 +++++++++ homeassistant/components/medcom_ble/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/medcom_ble/icons.json diff --git a/homeassistant/components/medcom_ble/icons.json b/homeassistant/components/medcom_ble/icons.json new file mode 100644 index 00000000000..682d028fa02 --- /dev/null +++ b/homeassistant/components/medcom_ble/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "cpm": { + "default": "mdi:radioactive" + } + } + } +} diff --git a/homeassistant/components/medcom_ble/sensor.py b/homeassistant/components/medcom_ble/sensor.py index 4c7488ddc12..74c78a3cf9e 100644 --- a/homeassistant/components/medcom_ble/sensor.py +++ b/homeassistant/components/medcom_ble/sensor.py @@ -29,7 +29,6 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { translation_key="cpm", native_unit_of_measurement=UNIT_CPM, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:radioactive", ), } From d606e96d136bbc7095102655830c8c27bb329b80 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:34:28 +0100 Subject: [PATCH 0088/1691] Add icon translations to Keymitt BLE (#111851) --- homeassistant/components/keymitt_ble/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/keymitt_ble/icons.json diff --git a/homeassistant/components/keymitt_ble/icons.json b/homeassistant/components/keymitt_ble/icons.json new file mode 100644 index 00000000000..77450fbf026 --- /dev/null +++ b/homeassistant/components/keymitt_ble/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "calibrate": "mdi:wrench" + } +} From 677e12b0330942ee184137858470c40debfd77d9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:41:30 +0100 Subject: [PATCH 0089/1691] Add icon translations to IFTTT (#111842) --- homeassistant/components/ifttt/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/ifttt/icons.json diff --git a/homeassistant/components/ifttt/icons.json b/homeassistant/components/ifttt/icons.json new file mode 100644 index 00000000000..b943478a70b --- /dev/null +++ b/homeassistant/components/ifttt/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "push_alarm_state": "mdi:security", + "trigger": "mdi:play" + } +} From 689651ee2cbe6c31d7343ae1d21d55c669cf5fe1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:42:26 +0100 Subject: [PATCH 0090/1691] Add icon translations to Idasen desk (#111813) --- .../components/idasen_desk/button.py | 16 ++++---------- homeassistant/components/idasen_desk/cover.py | 6 ++--- .../components/idasen_desk/icons.json | 22 +++++++++++++++++++ .../components/idasen_desk/sensor.py | 1 - .../components/idasen_desk/strings.json | 8 +++++++ 5 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/idasen_desk/icons.json diff --git a/homeassistant/components/idasen_desk/button.py b/homeassistant/components/idasen_desk/button.py index d11738c6bcd..1887ffe438b 100644 --- a/homeassistant/components/idasen_desk/button.py +++ b/homeassistant/components/idasen_desk/button.py @@ -4,11 +4,7 @@ from dataclasses import dataclass import logging from typing import Any, Final -from homeassistant.components.button import ( - ButtonDeviceClass, - ButtonEntity, - ButtonEntityDescription, -) +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant @@ -40,17 +36,13 @@ class IdasenDeskButtonDescription( BUTTONS: Final = [ IdasenDeskButtonDescription( key="connect", - name="Connect", - icon="mdi:bluetooth-connect", - device_class=ButtonDeviceClass.RESTART, + translation_key="connect", entity_category=EntityCategory.CONFIG, press_action=lambda coordinator: coordinator.async_connect, ), IdasenDeskButtonDescription( key="disconnect", - name="Disconnect", - icon="mdi:bluetooth-off", - device_class=ButtonDeviceClass.RESTART, + translation_key="disconnect", entity_category=EntityCategory.CONFIG, press_action=lambda coordinator: coordinator.async_disconnect, ), @@ -86,7 +78,7 @@ class IdasenDeskButton(ButtonEntity): """Initialize the IdasenDesk button entity.""" self.entity_description = description - self._attr_unique_id = f"{self.entity_description.key}-{address}" + self._attr_unique_id = f"{description.key}-{address}" self._attr_device_info = device_info self._address = address self._coordinator = coordinator diff --git a/homeassistant/components/idasen_desk/cover.py b/homeassistant/components/idasen_desk/cover.py index 1daebe52420..a29d2557f93 100644 --- a/homeassistant/components/idasen_desk/cover.py +++ b/homeassistant/components/idasen_desk/cover.py @@ -12,7 +12,6 @@ from homeassistant.components.cover import ( CoverEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceInfo @@ -39,13 +38,15 @@ class IdasenDeskCover(CoordinatorEntity[IdasenDeskCoordinator], CoverEntity): """Representation of Idasen Desk device.""" _attr_device_class = CoverDeviceClass.DAMPER - _attr_icon = "mdi:desk" _attr_supported_features = ( CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP | CoverEntityFeature.SET_POSITION ) + _attr_has_entity_name = True + _attr_name = None + _attr_translation_key = "desk" def __init__( self, @@ -56,7 +57,6 @@ class IdasenDeskCover(CoordinatorEntity[IdasenDeskCoordinator], CoverEntity): """Initialize an Idasen Desk cover.""" super().__init__(coordinator) self._desk = coordinator.desk - self._attr_name = device_info[ATTR_NAME] self._attr_unique_id = address self._attr_device_info = device_info diff --git a/homeassistant/components/idasen_desk/icons.json b/homeassistant/components/idasen_desk/icons.json new file mode 100644 index 00000000000..8feca3de25d --- /dev/null +++ b/homeassistant/components/idasen_desk/icons.json @@ -0,0 +1,22 @@ +{ + "entity": { + "button": { + "connect": { + "default": "mdi:bluetooth-connect" + }, + "disconnect": { + "default": "mdi:bluetooth-off" + } + }, + "cover": { + "desk": { + "default": "mdi:desk" + } + }, + "sensor": { + "height": { + "default": "mdi:arrow-up-down" + } + } + } +} diff --git a/homeassistant/components/idasen_desk/sensor.py b/homeassistant/components/idasen_desk/sensor.py index 0fb3523a461..44a001960c8 100644 --- a/homeassistant/components/idasen_desk/sensor.py +++ b/homeassistant/components/idasen_desk/sensor.py @@ -41,7 +41,6 @@ SENSORS = ( IdasenDeskSensorDescription( key="height", translation_key="height", - icon="mdi:arrow-up-down", native_unit_of_measurement=UnitOfLength.METERS, device_class=SensorDeviceClass.DISTANCE, state_class=SensorStateClass.MEASUREMENT, diff --git a/homeassistant/components/idasen_desk/strings.json b/homeassistant/components/idasen_desk/strings.json index 446ef93e542..70e08976925 100644 --- a/homeassistant/components/idasen_desk/strings.json +++ b/homeassistant/components/idasen_desk/strings.json @@ -21,6 +21,14 @@ } }, "entity": { + "button": { + "connect": { + "name": "Connect" + }, + "disconnect": { + "name": "Disconnect" + } + }, "sensor": { "height": { "name": "Height" From b29ddfb9be061bfe9ce35d5c56d41a0acb707c4f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:44:27 +0100 Subject: [PATCH 0091/1691] Add icon translations to Lidarr (#111896) --- homeassistant/components/lidarr/icons.json | 15 +++++++++++++++ homeassistant/components/lidarr/sensor.py | 3 --- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/lidarr/icons.json diff --git a/homeassistant/components/lidarr/icons.json b/homeassistant/components/lidarr/icons.json new file mode 100644 index 00000000000..fcda789a8a3 --- /dev/null +++ b/homeassistant/components/lidarr/icons.json @@ -0,0 +1,15 @@ +{ + "entity": { + "sensor": { + "disk_space": { + "default": "mdi:harddisk" + }, + "queue": { + "default": "mdi:download" + }, + "wanted": { + "default": "mdi:music" + } + } + } +} diff --git a/homeassistant/components/lidarr/sensor.py b/homeassistant/components/lidarr/sensor.py index 027779f93fe..82717210dc2 100644 --- a/homeassistant/components/lidarr/sensor.py +++ b/homeassistant/components/lidarr/sensor.py @@ -74,7 +74,6 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = { translation_key="disk_space", native_unit_of_measurement=UnitOfInformation.GIGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:harddisk", value_fn=get_space, state_class=SensorStateClass.TOTAL, description_fn=get_modified_description, @@ -83,7 +82,6 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = { key="queue", translation_key="queue", native_unit_of_measurement="Albums", - icon="mdi:download", value_fn=lambda data, _: data.totalRecords, state_class=SensorStateClass.TOTAL, attributes_fn=lambda data: {i.title: queue_str(i) for i in data.records}, @@ -92,7 +90,6 @@ SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = { key="wanted", translation_key="wanted", native_unit_of_measurement="Albums", - icon="mdi:music", value_fn=lambda data, _: data.totalRecords, state_class=SensorStateClass.TOTAL, entity_registry_enabled_default=False, From e209ae3d4e174e29a818a94a25d149d4df0f671b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 12:49:16 +0100 Subject: [PATCH 0092/1691] Add icon translations to LCN (#111895) --- homeassistant/components/lcn/icons.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 homeassistant/components/lcn/icons.json diff --git a/homeassistant/components/lcn/icons.json b/homeassistant/components/lcn/icons.json new file mode 100644 index 00000000000..c8b451a79ea --- /dev/null +++ b/homeassistant/components/lcn/icons.json @@ -0,0 +1,17 @@ +{ + "services": { + "output_abs": "mdi:brightness-auto", + "output_rel": "mdi:brightness-7", + "output_toggle": "mdi:toggle-switch", + "relays": "mdi:light-switch-off", + "led": "mdi:led-on", + "var_abs": "mdi:wrench", + "var_reset": "mdi:reload", + "var_rel": "mdi:wrench", + "lock_regulator": "mdi:lock", + "send_keys": "mdi:alarm-panel", + "lock_keys": "mdi:lock", + "dyn_text": "mdi:form-textbox", + "pck": "mdi:package-variant-closed" + } +} From 3a5e0c14bfc78496c2d0ae9b94ae55dc8186b15a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 1 Mar 2024 13:07:13 +0100 Subject: [PATCH 0093/1691] Remove config flow specifics from FlowResult (#111932) * Remove config flow specifics from FlowResult * Improve docstring * Update pylint rules --- homeassistant/config_entries.py | 6 +++- homeassistant/data_entry_flow.py | 2 -- pylint/plugins/hass_enforce_type_hints.py | 28 +++++++-------- tests/pylint/test_enforce_type_hints.py | 42 ++++++++++++++++++++++- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index a96716ff84b..b1a4a8ce6cc 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -245,7 +245,11 @@ UPDATE_ENTRY_CONFIG_ENTRY_ATTRS = { } -ConfigFlowResult = FlowResult +class ConfigFlowResult(FlowResult, total=False): + """Typed result dict for config flow.""" + + minor_version: int + version: int class ConfigEntry: diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index b573f528945..526b859d29c 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -151,7 +151,6 @@ class FlowResult(TypedDict, total=False): handler: Required[str] last_step: bool | None menu_options: list[str] | dict[str, str] - minor_version: int options: Mapping[str, Any] preview: str | None progress_action: str @@ -164,7 +163,6 @@ class FlowResult(TypedDict, total=False): translation_domain: str type: FlowResultType url: str - version: int def _map_error_to_schema_errors( diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 602bd8a443d..c3c74c95c84 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -494,11 +494,6 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = { ClassTypeHintMatch( base_class="ConfigFlow", matches=[ - TypeHintMatch( - function_name="async_step123_*", - arg_types={}, - return_type=["ConfigFlowResult", "FlowResult"], - ), TypeHintMatch( function_name="async_get_options_flow", arg_types={ @@ -511,56 +506,61 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = { arg_types={ 1: "DhcpServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_hassio", arg_types={ 1: "HassioServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_homekit", arg_types={ 1: "ZeroconfServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_mqtt", arg_types={ 1: "MqttServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_reauth", arg_types={ 1: "Mapping[str, Any]", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_ssdp", arg_types={ 1: "SsdpServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_usb", arg_types={ 1: "UsbServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), TypeHintMatch( function_name="async_step_zeroconf", arg_types={ 1: "ZeroconfServiceInfo", }, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", + ), + TypeHintMatch( + function_name="async_step_*", + arg_types={}, + return_type="ConfigFlowResult", ), ], ), @@ -570,7 +570,7 @@ _CLASS_MATCH: dict[str, list[ClassTypeHintMatch]] = { TypeHintMatch( function_name="async_step_*", arg_types={}, - return_type=["ConfigFlowResult", "FlowResult"], + return_type="ConfigFlowResult", ), ], ), diff --git a/tests/pylint/test_enforce_type_hints.py b/tests/pylint/test_enforce_type_hints.py index 2a03343cb82..d3b7efae19b 100644 --- a/tests/pylint/test_enforce_type_hints.py +++ b/tests/pylint/test_enforce_type_hints.py @@ -346,7 +346,7 @@ def test_invalid_config_flow_step( pylint.testutils.MessageTest( msg_id="hass-return-type", node=func_node, - args=(["ConfigFlowResult", "FlowResult"], "async_step_zeroconf"), + args=("ConfigFlowResult", "async_step_zeroconf"), line=11, col_offset=4, end_line=11, @@ -356,6 +356,46 @@ def test_invalid_config_flow_step( type_hint_checker.visit_classdef(class_node) +def test_invalid_custom_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_axis_specific( #@ + 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-return-type", + node=func_node, + args=("ConfigFlowResult", "async_step_axis_specific"), + line=11, + col_offset=4, + end_line=11, + end_col_offset=38, + ), + ): + type_hint_checker.visit_classdef(class_node) + + def test_valid_config_flow_step( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: From d757e5ebe95dafae29ee7fe7427ed450666051c1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:16:00 +0100 Subject: [PATCH 0094/1691] Add icon translations to LifX (#111897) --- homeassistant/components/lifx/icons.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 homeassistant/components/lifx/icons.json diff --git a/homeassistant/components/lifx/icons.json b/homeassistant/components/lifx/icons.json new file mode 100644 index 00000000000..bf9e5e732d5 --- /dev/null +++ b/homeassistant/components/lifx/icons.json @@ -0,0 +1,12 @@ +{ + "services": { + "set_hev_cycle_state": "mdi:led-on", + "set_state": "mdi:led-on", + "effect_pulse": "mdi:pulse", + "effect_colorloop": "mdi:looks", + "effect_move": "mdi:cube-send", + "effect_flame": "mdi:fire", + "effect_morph": "mdi:shape-outline", + "effect_stop": "mdi:stop" + } +} From 59d605c82c8660ba3d5aeada1887f09d77ba3d4b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:19:06 +0100 Subject: [PATCH 0095/1691] Add icon translations to min max (#111908) --- homeassistant/components/min_max/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/min_max/icons.json diff --git a/homeassistant/components/min_max/icons.json b/homeassistant/components/min_max/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/min_max/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 1f3b0a4371727648984ca68155513fb436a8eb81 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:20:51 +0100 Subject: [PATCH 0096/1691] Add icon translations to Minecraft Server (#111928) * Add icon translations to Minecraft Server * Add icon translations to Minecraft Server * Add icon translations to Minecraft --- .../minecraft_server/binary_sensor.py | 3 -- .../components/minecraft_server/icons.json | 42 +++++++++++++++++++ .../components/minecraft_server/sensor.py | 19 --------- .../snapshots/test_binary_sensor.ambr | 6 +-- .../snapshots/test_sensor.ambr | 30 ------------- 5 files changed, 43 insertions(+), 57 deletions(-) create mode 100644 homeassistant/components/minecraft_server/icons.json diff --git a/homeassistant/components/minecraft_server/binary_sensor.py b/homeassistant/components/minecraft_server/binary_sensor.py index 6c0a2a248f3..974889b7245 100644 --- a/homeassistant/components/minecraft_server/binary_sensor.py +++ b/homeassistant/components/minecraft_server/binary_sensor.py @@ -14,8 +14,6 @@ from .const import DOMAIN from .coordinator import MinecraftServerCoordinator from .entity import MinecraftServerEntity -ICON_STATUS = "mdi:lan" - KEY_STATUS = "status" @@ -29,7 +27,6 @@ BINARY_SENSOR_DESCRIPTIONS = [ key=KEY_STATUS, translation_key=KEY_STATUS, device_class=BinarySensorDeviceClass.CONNECTIVITY, - icon=ICON_STATUS, ), ] diff --git a/homeassistant/components/minecraft_server/icons.json b/homeassistant/components/minecraft_server/icons.json new file mode 100644 index 00000000000..75719b39753 --- /dev/null +++ b/homeassistant/components/minecraft_server/icons.json @@ -0,0 +1,42 @@ +{ + "entity": { + "binary_sensor": { + "status": { + "default": "mdi:lan", + "state": { + "on": "mdi:lan-connect", + "off": "mdi:lan-disconnect" + } + } + }, + "sensor": { + "edition": { + "default": "mdi:minecraft" + }, + "game_mode": { + "default": "mdi:cog" + }, + "latency": { + "default": "mdi:signal" + }, + "players_max": { + "default": "mdi:account-multiple" + }, + "players_online": { + "default": "mdi:account-multiple" + }, + "protocol_version": { + "default": "mdi:numeric" + }, + "version": { + "default": "mdi:numeric" + }, + "motd": { + "default": "mdi:minecraft" + }, + "map_name": { + "default": "mdi:map" + } + } + } +} diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index 606d6085fda..f6e0805e71b 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -19,16 +19,6 @@ from .entity import MinecraftServerEntity ATTR_PLAYERS_LIST = "players_list" -ICON_EDITION = "mdi:minecraft" -ICON_GAME_MODE = "mdi:cog" -ICON_MAP_NAME = "mdi:map" -ICON_LATENCY = "mdi:signal" -ICON_PLAYERS_MAX = "mdi:account-multiple" -ICON_PLAYERS_ONLINE = "mdi:account-multiple" -ICON_PROTOCOL_VERSION = "mdi:numeric" -ICON_VERSION = "mdi:numeric" -ICON_MOTD = "mdi:minecraft" - KEY_EDITION = "edition" KEY_GAME_MODE = "game_mode" KEY_MAP_NAME = "map_name" @@ -74,7 +64,6 @@ SENSOR_DESCRIPTIONS = [ MinecraftServerSensorEntityDescription( key=KEY_VERSION, translation_key=KEY_VERSION, - icon=ICON_VERSION, value_fn=lambda data: data.version, attributes_fn=None, supported_server_types={ @@ -86,7 +75,6 @@ SENSOR_DESCRIPTIONS = [ MinecraftServerSensorEntityDescription( key=KEY_PROTOCOL_VERSION, translation_key=KEY_PROTOCOL_VERSION, - icon=ICON_PROTOCOL_VERSION, value_fn=lambda data: data.protocol_version, attributes_fn=None, supported_server_types={ @@ -100,7 +88,6 @@ SENSOR_DESCRIPTIONS = [ key=KEY_PLAYERS_MAX, translation_key=KEY_PLAYERS_MAX, native_unit_of_measurement=UNIT_PLAYERS_MAX, - icon=ICON_PLAYERS_MAX, value_fn=lambda data: data.players_max, attributes_fn=None, supported_server_types={ @@ -114,7 +101,6 @@ SENSOR_DESCRIPTIONS = [ translation_key=KEY_LATENCY, native_unit_of_measurement=UnitOfTime.MILLISECONDS, suggested_display_precision=0, - icon=ICON_LATENCY, value_fn=lambda data: data.latency, attributes_fn=None, supported_server_types={ @@ -126,7 +112,6 @@ SENSOR_DESCRIPTIONS = [ MinecraftServerSensorEntityDescription( key=KEY_MOTD, translation_key=KEY_MOTD, - icon=ICON_MOTD, value_fn=lambda data: data.motd, attributes_fn=None, supported_server_types={ @@ -138,7 +123,6 @@ SENSOR_DESCRIPTIONS = [ key=KEY_PLAYERS_ONLINE, translation_key=KEY_PLAYERS_ONLINE, native_unit_of_measurement=UNIT_PLAYERS_ONLINE, - icon=ICON_PLAYERS_ONLINE, value_fn=lambda data: data.players_online, attributes_fn=get_extra_state_attributes_players_list, supported_server_types={ @@ -149,7 +133,6 @@ SENSOR_DESCRIPTIONS = [ MinecraftServerSensorEntityDescription( key=KEY_EDITION, translation_key=KEY_EDITION, - icon=ICON_EDITION, value_fn=lambda data: data.edition, attributes_fn=None, supported_server_types={ @@ -161,7 +144,6 @@ SENSOR_DESCRIPTIONS = [ MinecraftServerSensorEntityDescription( key=KEY_GAME_MODE, translation_key=KEY_GAME_MODE, - icon=ICON_GAME_MODE, value_fn=lambda data: data.game_mode, attributes_fn=None, supported_server_types={ @@ -171,7 +153,6 @@ SENSOR_DESCRIPTIONS = [ MinecraftServerSensorEntityDescription( key=KEY_MAP_NAME, translation_key=KEY_MAP_NAME, - icon=ICON_MAP_NAME, value_fn=lambda data: data.map_name, attributes_fn=None, supported_server_types={ diff --git a/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr b/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr index 2a62fea7f35..00eae5a4bdd 100644 --- a/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr +++ b/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr @@ -4,7 +4,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', 'friendly_name': 'Minecraft Server Status', - 'icon': 'mdi:lan', }), 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', @@ -18,7 +17,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', 'friendly_name': 'Minecraft Server Status', - 'icon': 'mdi:lan', }), 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', @@ -32,7 +30,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', 'friendly_name': 'Minecraft Server Status', - 'icon': 'mdi:lan', }), 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', @@ -46,7 +43,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', 'friendly_name': 'Minecraft Server Status', - 'icon': 'mdi:lan', }), 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', @@ -54,4 +50,4 @@ 'last_updated': , 'state': 'on', }) -# --- \ No newline at end of file +# --- diff --git a/tests/components/minecraft_server/snapshots/test_sensor.ambr b/tests/components/minecraft_server/snapshots/test_sensor.ambr index b0f77f27b80..32cf0f1e935 100644 --- a/tests/components/minecraft_server/snapshots/test_sensor.ambr +++ b/tests/components/minecraft_server/snapshots/test_sensor.ambr @@ -3,7 +3,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Latency', - 'icon': 'mdi:signal', 'unit_of_measurement': , }), 'context': , @@ -17,7 +16,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players online', - 'icon': 'mdi:account-multiple', 'unit_of_measurement': 'players', }), 'context': , @@ -31,7 +29,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players max', - 'icon': 'mdi:account-multiple', 'unit_of_measurement': 'players', }), 'context': , @@ -45,7 +42,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server World message', - 'icon': 'mdi:minecraft', }), 'context': , 'entity_id': 'sensor.minecraft_server_world_message', @@ -58,7 +54,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_version', @@ -71,7 +66,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Protocol version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', @@ -84,7 +78,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Map name', - 'icon': 'mdi:map', }), 'context': , 'entity_id': 'sensor.minecraft_server_map_name', @@ -97,7 +90,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Game mode', - 'icon': 'mdi:cog', }), 'context': , 'entity_id': 'sensor.minecraft_server_game_mode', @@ -110,7 +102,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Edition', - 'icon': 'mdi:minecraft', }), 'context': , 'entity_id': 'sensor.minecraft_server_edition', @@ -123,7 +114,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Latency', - 'icon': 'mdi:signal', 'unit_of_measurement': , }), 'context': , @@ -137,7 +127,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players online', - 'icon': 'mdi:account-multiple', 'players_list': list([ 'Player 1', 'Player 2', @@ -156,7 +145,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players max', - 'icon': 'mdi:account-multiple', 'unit_of_measurement': 'players', }), 'context': , @@ -170,7 +158,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server World message', - 'icon': 'mdi:minecraft', }), 'context': , 'entity_id': 'sensor.minecraft_server_world_message', @@ -183,7 +170,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_version', @@ -196,7 +182,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Protocol version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', @@ -209,7 +194,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Latency', - 'icon': 'mdi:signal', 'unit_of_measurement': , }), 'context': , @@ -223,7 +207,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players online', - 'icon': 'mdi:account-multiple', 'unit_of_measurement': 'players', }), 'context': , @@ -237,7 +220,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players max', - 'icon': 'mdi:account-multiple', 'unit_of_measurement': 'players', }), 'context': , @@ -251,7 +233,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server World message', - 'icon': 'mdi:minecraft', }), 'context': , 'entity_id': 'sensor.minecraft_server_world_message', @@ -264,7 +245,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_version', @@ -277,7 +257,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Protocol version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', @@ -290,7 +269,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Map name', - 'icon': 'mdi:map', }), 'context': , 'entity_id': 'sensor.minecraft_server_map_name', @@ -303,7 +281,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Game mode', - 'icon': 'mdi:cog', }), 'context': , 'entity_id': 'sensor.minecraft_server_game_mode', @@ -316,7 +293,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Edition', - 'icon': 'mdi:minecraft', }), 'context': , 'entity_id': 'sensor.minecraft_server_edition', @@ -329,7 +305,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Latency', - 'icon': 'mdi:signal', 'unit_of_measurement': , }), 'context': , @@ -343,7 +318,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players online', - 'icon': 'mdi:account-multiple', 'players_list': list([ 'Player 1', 'Player 2', @@ -362,7 +336,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Players max', - 'icon': 'mdi:account-multiple', 'unit_of_measurement': 'players', }), 'context': , @@ -376,7 +349,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server World message', - 'icon': 'mdi:minecraft', }), 'context': , 'entity_id': 'sensor.minecraft_server_world_message', @@ -389,7 +361,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_version', @@ -402,7 +373,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Minecraft Server Protocol version', - 'icon': 'mdi:numeric', }), 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', From 841d4e320fa8a586803cfd2ace19c36b4eb84257 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:22:57 +0100 Subject: [PATCH 0097/1691] Add icon translations to Mill (#111907) --- homeassistant/components/mill/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/mill/icons.json diff --git a/homeassistant/components/mill/icons.json b/homeassistant/components/mill/icons.json new file mode 100644 index 00000000000..13d6bb650c1 --- /dev/null +++ b/homeassistant/components/mill/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_room_temperature": "mdi:thermometer" + } +} From f0be33fc6a49d02e7e5d61ea147a9fa86b9752d5 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:31:28 +0100 Subject: [PATCH 0098/1691] Update mypy to a custom version (#111258) * Update mypy to a custom version * Fix call_soon_threadsafe typing Result from a recent typeshed change -> better asyncio callback typing with Ts --- homeassistant/core.py | 5 +++++ requirements_test.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 0f038149d63..e1bd5750c16 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -513,6 +513,11 @@ class HomeAssistant: """ if target is None: raise ValueError("Don't call add_job with None") + if asyncio.iscoroutine(target): + self.loop.call_soon_threadsafe(self.async_add_job, target) + return + if TYPE_CHECKING: + target = cast(Callable[..., Any], target) self.loop.call_soon_threadsafe(self.async_add_job, target, *args) @overload diff --git a/requirements_test.txt b/requirements_test.txt index f3f0bfe5b8e..d367e81c0b2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -11,7 +11,7 @@ astroid==3.1.0 coverage==7.4.3 freezegun==1.4.0 mock-open==1.4.0 -mypy==1.8.0 +mypy-dev==1.9.0b1 pre-commit==3.6.2 pydantic==1.10.12 pylint==3.1.0 From e43c62af1ec4e8a8adc4a0a1a1837e58e3e3b143 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:40:37 +0100 Subject: [PATCH 0099/1691] Add icon translations to Litterrobot (#111899) --- .../components/litterrobot/binary_sensor.py | 2 - .../components/litterrobot/button.py | 2 - .../components/litterrobot/icons.json | 45 +++++++++++++++++++ .../components/litterrobot/select.py | 2 - .../components/litterrobot/switch.py | 9 ---- tests/components/litterrobot/test_button.py | 3 +- 6 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 homeassistant/components/litterrobot/icons.json diff --git a/homeassistant/components/litterrobot/binary_sensor.py b/homeassistant/components/litterrobot/binary_sensor.py index 6a588c36d6c..22e8bda8dfa 100644 --- a/homeassistant/components/litterrobot/binary_sensor.py +++ b/homeassistant/components/litterrobot/binary_sensor.py @@ -52,7 +52,6 @@ BINARY_SENSOR_MAP: dict[type[Robot], tuple[RobotBinarySensorEntityDescription, . RobotBinarySensorEntityDescription[LitterRobot]( key="sleeping", translation_key="sleeping", - icon="mdi:sleep", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, is_on_fn=lambda robot: robot.is_sleeping, @@ -60,7 +59,6 @@ BINARY_SENSOR_MAP: dict[type[Robot], tuple[RobotBinarySensorEntityDescription, . RobotBinarySensorEntityDescription[LitterRobot]( key="sleep_mode", translation_key="sleep_mode", - icon="mdi:sleep", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, is_on_fn=lambda robot: robot.sleep_mode_enabled, diff --git a/homeassistant/components/litterrobot/button.py b/homeassistant/components/litterrobot/button.py index de93ead5190..79a866333d8 100644 --- a/homeassistant/components/litterrobot/button.py +++ b/homeassistant/components/litterrobot/button.py @@ -61,14 +61,12 @@ class RobotButtonEntityDescription(ButtonEntityDescription, RequiredKeysMixin[_R LITTER_ROBOT_BUTTON = RobotButtonEntityDescription[LitterRobot3]( key="reset_waste_drawer", translation_key="reset_waste_drawer", - icon="mdi:delete-variant", entity_category=EntityCategory.CONFIG, press_fn=lambda robot: robot.reset_waste_drawer(), ) FEEDER_ROBOT_BUTTON = RobotButtonEntityDescription[FeederRobot]( key="give_snack", translation_key="give_snack", - icon="mdi:candy-outline", press_fn=lambda robot: robot.give_snack(), ) diff --git a/homeassistant/components/litterrobot/icons.json b/homeassistant/components/litterrobot/icons.json new file mode 100644 index 00000000000..333f309e9e8 --- /dev/null +++ b/homeassistant/components/litterrobot/icons.json @@ -0,0 +1,45 @@ +{ + "entity": { + "binary_sensor": { + "sleeping": { + "default": "mdi:sleep" + }, + "sleep_mode": { + "default": "mdi:sleep" + } + }, + "button": { + "reset_waste_drawer": { + "default": "mdi:delete-variant" + }, + "give_snack": { + "default": "mdi:candy-outline" + } + }, + "select": { + "cycle_delay": { + "default": "mdi:timer-outline" + }, + "meal_insert_size": { + "default": "mdi:scale" + } + }, + "switch": { + "night_light_mode": { + "default": "mdi:lightbulb-off", + "state": { + "on": "mdi:lightbulb-on" + } + }, + "panel_lockout": { + "default": "mdi:lock-open", + "state": { + "on": "mdi:lock" + } + } + } + }, + "services": { + "set_sleep_mode": "mdi:sleep" + } +} diff --git a/homeassistant/components/litterrobot/select.py b/homeassistant/components/litterrobot/select.py index 726cfaebaeb..8b5445269bd 100644 --- a/homeassistant/components/litterrobot/select.py +++ b/homeassistant/components/litterrobot/select.py @@ -51,7 +51,6 @@ ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = { LitterRobot: RobotSelectEntityDescription[LitterRobot, int]( # type: ignore[type-abstract] # only used for isinstance check key="cycle_delay", translation_key="cycle_delay", - icon="mdi:timer-outline", unit_of_measurement=UnitOfTime.MINUTES, current_fn=lambda robot: robot.clean_cycle_wait_time_minutes, options_fn=lambda robot: robot.VALID_WAIT_TIMES, @@ -72,7 +71,6 @@ ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = { FeederRobot: RobotSelectEntityDescription[FeederRobot, float]( key="meal_insert_size", translation_key="meal_insert_size", - icon="mdi:scale", unit_of_measurement="cups", current_fn=lambda robot: robot.meal_insert_size, options_fn=lambda robot: robot.VALID_MEAL_INSERT_SIZES, diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py index 84e6fa2be67..aa4a18c1840 100644 --- a/homeassistant/components/litterrobot/switch.py +++ b/homeassistant/components/litterrobot/switch.py @@ -22,7 +22,6 @@ from .hub import LitterRobotHub class RequiredKeysMixin(Generic[_RobotT]): """A class that describes robot switch entity required keys.""" - icons: tuple[str, str] set_fn: Callable[[_RobotT, bool], Coroutine[Any, Any, bool]] @@ -37,13 +36,11 @@ ROBOT_SWITCHES = [ RobotSwitchEntityDescription[LitterRobot | FeederRobot]( key="night_light_mode_enabled", translation_key="night_light_mode", - icons=("mdi:lightbulb-on", "mdi:lightbulb-off"), set_fn=lambda robot, value: robot.set_night_light(value), ), RobotSwitchEntityDescription[LitterRobot | FeederRobot]( key="panel_lock_enabled", translation_key="panel_lockout", - icons=("mdi:lock", "mdi:lock-open"), set_fn=lambda robot, value: robot.set_panel_lockout(value), ), ] @@ -59,12 +56,6 @@ class RobotSwitchEntity(LitterRobotEntity[_RobotT], SwitchEntity): """Return true if switch is on.""" return bool(getattr(self.robot, self.entity_description.key)) - @property - def icon(self) -> str: - """Return the icon.""" - icon_on, icon_off = self.entity_description.icons - return icon_on if self.is_on else icon_off - async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" await self.entity_description.set_fn(self.robot, True) diff --git a/tests/components/litterrobot/test_button.py b/tests/components/litterrobot/test_button.py index 9a4145dd224..17efd21c76e 100644 --- a/tests/components/litterrobot/test_button.py +++ b/tests/components/litterrobot/test_button.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock from freezegun import freeze_time from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS -from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, STATE_UNKNOWN, EntityCategory +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -21,7 +21,6 @@ async def test_button( state = hass.states.get(BUTTON_ENTITY) assert state - assert state.attributes.get(ATTR_ICON) == "mdi:delete-variant" assert state.state == STATE_UNKNOWN entry = entity_registry.async_get(BUTTON_ENTITY) From 65e004dc99a89c7b4cbe8004b2577c44340b38d2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:40:58 +0100 Subject: [PATCH 0100/1691] Add icon translations to Local IP (#111900) --- homeassistant/components/local_ip/icons.json | 9 +++++++++ homeassistant/components/local_ip/sensor.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/local_ip/icons.json diff --git a/homeassistant/components/local_ip/icons.json b/homeassistant/components/local_ip/icons.json new file mode 100644 index 00000000000..34b329aae63 --- /dev/null +++ b/homeassistant/components/local_ip/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "local_ip": { + "default": "mdi:ip" + } + } + } +} diff --git a/homeassistant/components/local_ip/sensor.py b/homeassistant/components/local_ip/sensor.py index 4c502895b3f..7f855220563 100644 --- a/homeassistant/components/local_ip/sensor.py +++ b/homeassistant/components/local_ip/sensor.py @@ -24,7 +24,7 @@ class IPSensor(SensorEntity): """A simple sensor.""" _attr_unique_id = SENSOR - _attr_icon = "mdi:ip" + _attr_translation_key = "local_ip" def __init__(self, name: str) -> None: """Initialize the sensor.""" From 8bbfb6cc5554fd9d0cd92eb013aa27ac143b6747 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:42:42 +0100 Subject: [PATCH 0101/1691] Add icon translations to Lyric (#111902) Use default icon for Lyric --- homeassistant/components/lyric/icons.json | 12 ++++++++++++ homeassistant/components/lyric/sensor.py | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/lyric/icons.json diff --git a/homeassistant/components/lyric/icons.json b/homeassistant/components/lyric/icons.json new file mode 100644 index 00000000000..555215f8685 --- /dev/null +++ b/homeassistant/components/lyric/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "setpoint_status": { + "default": "mdi:thermostat" + } + } + }, + "services": { + "set_hold_time": "mdi:timer-pause" + } +} diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 1b9af351e71..4e2339368f0 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -105,7 +105,6 @@ DEVICE_SENSORS: list[LyricSensorEntityDescription] = [ LyricSensorEntityDescription( key="setpoint_status", translation_key="setpoint_status", - icon="mdi:thermostat", value_fn=lambda device: get_setpoint_status( device.changeableValues.thermostatSetpointStatus, device.changeableValues.nextPeriodTime, From 646c316102cac1f2a88fd8380f6b7c59906c53ec Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:58:04 +0100 Subject: [PATCH 0102/1691] Add icon translations to Motioneye (#111936) --- homeassistant/components/motioneye/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/motioneye/icons.json diff --git a/homeassistant/components/motioneye/icons.json b/homeassistant/components/motioneye/icons.json new file mode 100644 index 00000000000..b0a4ea8dcb1 --- /dev/null +++ b/homeassistant/components/motioneye/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "set_text_overlay": "mdi:text-box-outline", + "action": "mdi:gesture-tap-button", + "snapshot": "mdi:camera" + } +} From c37f1c67f1280b2d7ca969094fd8c469c201c33c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 13:59:11 +0100 Subject: [PATCH 0103/1691] Add icon translations to Monoprice (#111934) --- homeassistant/components/monoprice/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/monoprice/icons.json diff --git a/homeassistant/components/monoprice/icons.json b/homeassistant/components/monoprice/icons.json new file mode 100644 index 00000000000..22610cc2a47 --- /dev/null +++ b/homeassistant/components/monoprice/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "snapshot": "mdi:content-copy", + "restore": "mdi:content-paste" + } +} From 52fe0a5e966aad9b07fed775bee94183b7fd201c Mon Sep 17 00:00:00 2001 From: gibwar Date: Fri, 1 Mar 2024 08:44:24 -0700 Subject: [PATCH 0104/1691] Fix utility meter reset without tarrifs (#102884) * Fix utility meter reset without tarrifs When using a utility_meter helper set up with a "Meter reset cycle" of "No cycle" it is impossible to properly reset the meter to 0 and utilize the "last reset" and "last period" attributes on the helper. With these changes the service call can now directly reset a meter in such a configuration and the UI selector has been updated to include utility meters in the selection list. Fixes #96920 * Undo UI target selection modes As requested to not cause confusion by being able to reset individual meters when using multiple tariffs. * Adjust logic to only reset standalone sensors The original logic would allow resetting meters with multiple tariffs which was an unintended side effect. --- .../components/utility_meter/sensor.py | 2 +- tests/components/utility_meter/test_sensor.py | 92 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index e9ad7a1ba30..d9c5fabd7c8 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -570,7 +570,7 @@ class UtilityMeterSensor(RestoreSensor): async def async_reset_meter(self, entity_id): """Reset meter.""" - if self._tariff_entity != entity_id: + if self._tariff is not None and self._tariff_entity != entity_id: return _LOGGER.debug("Reset utility meter <%s>", self.entity_id) self._last_reset = dt_util.utcnow() diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index fa1e3aa8785..a4b37271a85 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -21,6 +21,7 @@ from homeassistant.components.utility_meter.const import ( HOURLY, QUARTER_HOURLY, SERVICE_CALIBRATE_METER, + SERVICE_RESET, ) from homeassistant.components.utility_meter.sensor import ( ATTR_LAST_RESET, @@ -771,6 +772,97 @@ async def test_restore_state( assert state.attributes.get("status") == PAUSED +@pytest.mark.parametrize( + ("yaml_config", "config_entry_config"), + ( + ( + { + "utility_meter": { + "energy_bill": { + "source": "sensor.energy", + } + } + }, + None, + ), + ( + None, + { + "cycle": "none", + "delta_values": False, + "name": "Energy bill", + "net_consumption": False, + "offset": 0, + "periodically_resetting": True, + "source": "sensor.energy", + "tariffs": [], + }, + ), + ), +) +async def test_service_reset_no_tariffs( + hass: HomeAssistant, yaml_config, config_entry_config +) -> None: + """Test utility sensor service reset for sensor with no tariffs.""" + # Home assistant is not runnit yet + hass.state = CoreState.not_running + last_reset = "2023-10-01T00:00:00+00:00" + + mock_restore_cache_with_extra_data( + hass, + [ + ( + State( + "sensor.energy_bill", + "3", + attributes={ + ATTR_LAST_RESET: last_reset, + }, + ), + {}, + ), + ], + ) + + if yaml_config: + assert await async_setup_component(hass, DOMAIN, yaml_config) + await hass.async_block_till_done() + else: + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options=config_entry_config, + title=config_entry_config["name"], + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.energy_bill") + assert state + assert state.state == "3" + assert state.attributes.get("last_reset") == last_reset + assert state.attributes.get("last_period") == "0" + + now = dt_util.utcnow() + with freeze_time(now): + await hass.services.async_call( + domain=DOMAIN, + service=SERVICE_RESET, + service_data={}, + target={"entity_id": "sensor.energy_bill"}, + blocking=True, + ) + + await hass.async_block_till_done() + + state = hass.states.get("sensor.energy_bill") + assert state + assert state.state == "0" + assert state.attributes.get("last_reset") == now.isoformat() + assert state.attributes.get("last_period") == "3" + + @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), ( From 99f08fb417158d9f3aaacdf9f4fba05f9e8c8b5d Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Fri, 1 Mar 2024 18:12:08 +0100 Subject: [PATCH 0105/1691] Fix LingeringTask after test completion in enphase_envoy (#111940) --- tests/components/enphase_envoy/test_config_flow.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/components/enphase_envoy/test_config_flow.py b/tests/components/enphase_envoy/test_config_flow.py index 6e274d0f7cc..11f0cbeb871 100644 --- a/tests/components/enphase_envoy/test_config_flow.py +++ b/tests/components/enphase_envoy/test_config_flow.py @@ -28,6 +28,7 @@ async def test_form(hass: HomeAssistant, config, setup_enphase_envoy) -> None: "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Envoy 1234" assert result2["data"] == { @@ -57,6 +58,7 @@ async def test_user_no_serial_number( "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Envoy" assert result2["data"] == { @@ -86,6 +88,7 @@ async def test_user_fetching_serial_fails( "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Envoy" assert result2["data"] == { @@ -115,6 +118,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, setup_enphase_envoy) -> No "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "form" assert result2["errors"] == {"base": "invalid_auth"} @@ -136,6 +140,7 @@ async def test_form_cannot_connect(hass: HomeAssistant, setup_enphase_envoy) -> "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} @@ -157,6 +162,7 @@ async def test_form_unknown_error(hass: HomeAssistant, setup_enphase_envoy) -> N "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "form" assert result2["errors"] == {"base": "unknown"} @@ -199,6 +205,7 @@ async def test_zeroconf_pre_token_firmware( "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Envoy 1234" assert result2["result"].unique_id == "1234" @@ -239,6 +246,7 @@ async def test_zeroconf_token_firmware( "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "create_entry" assert result2["title"] == "Envoy 1234" assert result2["result"].unique_id == "1234" @@ -330,6 +338,7 @@ async def test_zeroconf_serial_already_exists( type="mock_type", ), ) + await hass.async_block_till_done() assert result["type"] == "abort" assert result["reason"] == "already_configured" @@ -353,6 +362,7 @@ async def test_zeroconf_serial_already_exists_ignores_ipv6( type="mock_type", ), ) + await hass.async_block_till_done() assert result["type"] == "abort" assert result["reason"] == "not_ipv4_address" @@ -377,6 +387,7 @@ async def test_zeroconf_host_already_exists( type="mock_type", ), ) + await hass.async_block_till_done() assert result["type"] == "abort" assert result["reason"] == "already_configured" @@ -401,6 +412,7 @@ async def test_reauth(hass: HomeAssistant, config_entry, setup_enphase_envoy) -> "password": "test-password", }, ) + await hass.async_block_till_done() assert result2["type"] == "abort" assert result2["reason"] == "reauth_successful" From 1f173aff1728daaab774931a6ad62b2cf3068aee Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 1 Mar 2024 19:26:44 +0100 Subject: [PATCH 0106/1691] Fix loader test warning (#111965) --- tests/test_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_loader.py b/tests/test_loader.py index babe1abcdd2..b9a65438728 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1029,7 +1029,7 @@ async def test_hass_components_use_reported( "homeassistant.components.http.start_http_server_and_save_config", return_value=None, ): - hass.components.http.start_http_server_and_save_config(hass, [], None) + await hass.components.http.start_http_server_and_save_config(hass, [], None) assert ( "Detected that custom integration 'test_integration_frame'" From d592e95ba42970a1d1781322343767e6fdd5ef92 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 1 Mar 2024 19:38:08 +0100 Subject: [PATCH 0107/1691] Update frontend to 20240301.0 (#111961) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 3bbee2eae58..d975daad508 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240228.1"] + "requirements": ["home-assistant-frontend==20240301.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e8efc513fb0..c80da6b5aad 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240228.1 +home-assistant-frontend==20240301.0 home-assistant-intents==2024.2.28 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index ac518aa0299..641e45bffc7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ hole==0.8.0 holidays==0.43 # homeassistant.components.frontend -home-assistant-frontend==20240228.1 +home-assistant-frontend==20240301.0 # homeassistant.components.conversation home-assistant-intents==2024.2.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index adf9b994467..3f0b86377c7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ hole==0.8.0 holidays==0.43 # homeassistant.components.frontend -home-assistant-frontend==20240228.1 +home-assistant-frontend==20240301.0 # homeassistant.components.conversation home-assistant-intents==2024.2.28 From 0daa860e9489d5b6434c09f5df960b6bd1279071 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 20:16:11 +0100 Subject: [PATCH 0108/1691] Add icon translations to MQTT (#111945) --- homeassistant/components/mqtt/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/mqtt/icons.json diff --git a/homeassistant/components/mqtt/icons.json b/homeassistant/components/mqtt/icons.json new file mode 100644 index 00000000000..1979359c5a1 --- /dev/null +++ b/homeassistant/components/mqtt/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "publish": "mdi:publish", + "dump": "mdi:database-export", + "reload": "mdi:reload" + } +} From 28ab4abe00c04d4512c809c5f4b9577e1ee8ab5e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 20:17:55 +0100 Subject: [PATCH 0109/1691] Add icon translations to Neato (#111947) --- homeassistant/components/neato/icons.json | 5 +++++ homeassistant/components/neato/vacuum.py | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/neato/icons.json diff --git a/homeassistant/components/neato/icons.json b/homeassistant/components/neato/icons.json new file mode 100644 index 00000000000..ca50d5a9bc7 --- /dev/null +++ b/homeassistant/components/neato/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "custom_cleaning": "mdi:broom" + } +} diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 891b090d5d3..15127ed7d66 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -94,7 +94,6 @@ async def async_setup_entry( class NeatoConnectedVacuum(NeatoEntity, StateVacuumEntity): """Representation of a Neato Connected Vacuum.""" - _attr_icon = "mdi:robot-vacuum-variant" _attr_supported_features = ( VacuumEntityFeature.BATTERY | VacuumEntityFeature.PAUSE From 4ca8a02771a29c5dd2a64dad95c1653c2598ef8e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 20:18:59 +0100 Subject: [PATCH 0110/1691] Add icon translations to Nanoleaf (#111946) --- homeassistant/components/nanoleaf/icons.json | 9 +++++++++ homeassistant/components/nanoleaf/light.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/nanoleaf/icons.json diff --git a/homeassistant/components/nanoleaf/icons.json b/homeassistant/components/nanoleaf/icons.json new file mode 100644 index 00000000000..3f4ebf9ed9f --- /dev/null +++ b/homeassistant/components/nanoleaf/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "light": { + "light": { + "default": "mdi:triangle-outline" + } + } + } +} diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index dc251ac1e5d..e8d604d9236 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -47,7 +47,7 @@ class NanoleafLight(NanoleafEntity, LightEntity): _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} _attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION _attr_name = None - _attr_icon = "mdi:triangle-outline" + _attr_translation_key = "light" def __init__( self, nanoleaf: Nanoleaf, coordinator: DataUpdateCoordinator[None] From 23a1c559b2ef8f1ec26c854ec0ed58cd93dcb721 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 20:19:59 +0100 Subject: [PATCH 0111/1691] Add icon translations to Motion blinds (#111935) --- homeassistant/components/motion_blinds/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/motion_blinds/icons.json diff --git a/homeassistant/components/motion_blinds/icons.json b/homeassistant/components/motion_blinds/icons.json new file mode 100644 index 00000000000..a61c36e3f00 --- /dev/null +++ b/homeassistant/components/motion_blinds/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_absolute_position": "mdi:set-square" + } +} From 694c391c6bfdd549386b9bf886f3af9f3823ed90 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 20:21:10 +0100 Subject: [PATCH 0112/1691] Add icon translations to Modem callerID (#111929) --- homeassistant/components/modem_callerid/button.py | 1 - homeassistant/components/modem_callerid/const.py | 1 - homeassistant/components/modem_callerid/icons.json | 14 ++++++++++++++ homeassistant/components/modem_callerid/sensor.py | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/modem_callerid/icons.json diff --git a/homeassistant/components/modem_callerid/button.py b/homeassistant/components/modem_callerid/button.py index 5f9e4cf489c..703339f54e9 100644 --- a/homeassistant/components/modem_callerid/button.py +++ b/homeassistant/components/modem_callerid/button.py @@ -32,7 +32,6 @@ async def async_setup_entry( class PhoneModemButton(ButtonEntity): """Implementation of USB modem caller ID button.""" - _attr_icon = "mdi:phone-hangup" _attr_translation_key = "phone_modem_reject" _attr_has_entity_name = True diff --git a/homeassistant/components/modem_callerid/const.py b/homeassistant/components/modem_callerid/const.py index d144e2afd5c..361766f22a7 100644 --- a/homeassistant/components/modem_callerid/const.py +++ b/homeassistant/components/modem_callerid/const.py @@ -7,7 +7,6 @@ from serial import SerialException DATA_KEY_API = "api" DEFAULT_NAME = "Phone Modem" DOMAIN = "modem_callerid" -ICON = "mdi:phone-classic" EXCEPTIONS: Final = ( FileNotFoundError, diff --git a/homeassistant/components/modem_callerid/icons.json b/homeassistant/components/modem_callerid/icons.json new file mode 100644 index 00000000000..956d941f1db --- /dev/null +++ b/homeassistant/components/modem_callerid/icons.json @@ -0,0 +1,14 @@ +{ + "entity": { + "button": { + "phone_modem_reject": { + "default": "mdi:phone-hangup" + } + }, + "sensor": { + "incoming_call": { + "default": "mdi:phone-classic" + } + } + } +} diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index c7c4403300a..aac43b72c0d 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -10,7 +10,7 @@ from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import CID, DATA_KEY_API, DOMAIN, ICON +from .const import CID, DATA_KEY_API, DOMAIN async def async_setup_entry( @@ -40,10 +40,10 @@ async def async_setup_entry( class ModemCalleridSensor(SensorEntity): """Implementation of USB modem caller ID sensor.""" - _attr_icon = ICON _attr_should_poll = False _attr_has_entity_name = True _attr_name = None + _attr_translation_key = "incoming_call" def __init__(self, api: PhoneModem, server_unique_id: str) -> None: """Initialize the sensor.""" From bdfd272dc0868f91b31da78e7b8b85f64ac93dcd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 20:23:11 +0100 Subject: [PATCH 0113/1691] Use default icon for Melcloud (#111905) --- homeassistant/components/melcloud/icons.json | 13 +++++++++++++ homeassistant/components/melcloud/sensor.py | 8 +------- 2 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/melcloud/icons.json diff --git a/homeassistant/components/melcloud/icons.json b/homeassistant/components/melcloud/icons.json new file mode 100644 index 00000000000..de3eb3c0ba2 --- /dev/null +++ b/homeassistant/components/melcloud/icons.json @@ -0,0 +1,13 @@ +{ + "entity": { + "sensor": { + "energy_consumed": { + "default": "mdi:factory" + } + } + }, + "services": { + "set_vane_horizontal": "mdi:arrow-left-right", + "set_vane_vertical": "mdi:arrow-up-down" + } +} diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index d3d1f4976f6..c37c26e722e 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -42,7 +42,6 @@ ATA_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="room_temperature", translation_key="room_temperature", - icon="mdi:thermometer", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -51,7 +50,7 @@ ATA_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( ), MelcloudSensorEntityDescription( key="energy", - icon="mdi:factory", + translation_key="energy_consumed", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, @@ -63,7 +62,6 @@ ATW_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="outside_temperature", translation_key="outside_temperature", - icon="mdi:thermometer", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -73,7 +71,6 @@ ATW_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="tank_temperature", translation_key="tank_temperature", - icon="mdi:thermometer", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -85,7 +82,6 @@ ATW_ZONE_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="room_temperature", translation_key="room_temperature", - icon="mdi:thermometer", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -95,7 +91,6 @@ ATW_ZONE_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="flow_temperature", translation_key="flow_temperature", - icon="mdi:thermometer", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -105,7 +100,6 @@ ATW_ZONE_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="return_temperature", translation_key="return_temperature", - icon="mdi:thermometer", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, From daf8d1939b357bf8b33d3103476e9644d6207ddb Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 1 Mar 2024 20:51:45 +0100 Subject: [PATCH 0114/1691] Fix generic HassJob typing (#111973) --- homeassistant/helpers/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 9feabbb45e2..42d725c3ce4 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -990,7 +990,7 @@ async def _handle_entity_call( async def _async_admin_handler( hass: HomeAssistant, - service_job: HassJob[[None], Callable[[ServiceCall], Awaitable[None] | None]], + service_job: HassJob[[ServiceCall], Awaitable[None] | None], call: ServiceCall, ) -> None: """Run an admin service.""" From 16b162cd0763deeef825e00017fab7aec801863a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:04:45 +0100 Subject: [PATCH 0115/1691] Fix util create_eager_task typing (#111976) --- homeassistant/util/async_.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 36589e01d65..304f2446722 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -2,7 +2,7 @@ from __future__ import annotations from asyncio import AbstractEventLoop, Future, Semaphore, Task, gather, get_running_loop -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Coroutine import concurrent.futures from contextlib import suppress import functools @@ -26,7 +26,7 @@ _Ts = TypeVarTuple("_Ts") if sys.version_info >= (3, 12, 0): def create_eager_task( - coro: Awaitable[_T], + coro: Coroutine[Any, Any, _T], *, name: str | None = None, loop: AbstractEventLoop | None = None, @@ -41,7 +41,7 @@ if sys.version_info >= (3, 12, 0): else: def create_eager_task( - coro: Awaitable[_T], + coro: Coroutine[Any, Any, _T], *, name: str | None = None, loop: AbstractEventLoop | None = None, From 0628c26ea0fa542b5c2d9a18dd60403cb7d8df87 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 Mar 2024 16:18:58 -0500 Subject: [PATCH 0116/1691] Update reporting for media_source.async_resolve_media (#111969) * Update reporting for media_source.async_resolve_media * Don't raise on core * Fix tests --- .../components/media_source/__init__.py | 5 ++++- tests/components/jellyfin/test_media_source.py | 14 +++++++++++--- tests/components/media_source/test_init.py | 18 +++++++----------- tests/components/reolink/test_media_source.py | 6 ++++-- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 62cf7815613..fdb7fa5f1f2 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -148,7 +148,10 @@ async def async_resolve_media( raise Unresolvable("Media Source not loaded") if target_media_player is UNDEFINED: - report("calls media_source.async_resolve_media without passing an entity_id") + report( + "calls media_source.async_resolve_media without passing an entity_id", + {DOMAIN}, + ) target_media_player = None try: diff --git a/tests/components/jellyfin/test_media_source.py b/tests/components/jellyfin/test_media_source.py index 5f8871e6242..e87b3c15b0b 100644 --- a/tests/components/jellyfin/test_media_source.py +++ b/tests/components/jellyfin/test_media_source.py @@ -40,7 +40,9 @@ async def test_resolve( mock_api.get_item.side_effect = None mock_api.get_item.return_value = load_json_fixture("track.json") - play_media = await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/TRACK-UUID") + play_media = await async_resolve_media( + hass, f"{URI_SCHEME}{DOMAIN}/TRACK-UUID", "media_player.jellyfin_device" + ) assert play_media.mime_type == "audio/flac" assert play_media.url == snapshot @@ -49,7 +51,9 @@ async def test_resolve( mock_api.get_item.side_effect = None mock_api.get_item.return_value = load_json_fixture("movie.json") - play_media = await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/MOVIE-UUID") + play_media = await async_resolve_media( + hass, f"{URI_SCHEME}{DOMAIN}/MOVIE-UUID", "media_player.jellyfin_device" + ) assert play_media.mime_type == "video/mp4" assert play_media.url == snapshot @@ -59,7 +63,11 @@ async def test_resolve( mock_api.get_item.return_value = load_json_fixture("unsupported-item.json") with pytest.raises(BrowseError): - await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/UNSUPPORTED-ITEM-UUID") + await async_resolve_media( + hass, + f"{URI_SCHEME}{DOMAIN}/UNSUPPORTED-ITEM-UUID", + "media_player.jellyfin_device", + ) async def test_root( diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 4e512608abf..eecfe6cde6e 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -121,17 +121,13 @@ async def test_async_resolve_media_no_entity( assert await async_setup_component(hass, media_source.DOMAIN, {}) await hass.async_block_till_done() - media = await media_source.async_resolve_media( - hass, - media_source.generate_media_source_id(media_source.DOMAIN, "local/test.mp3"), - ) - assert isinstance(media, media_source.models.PlayMedia) - assert media.url == "/media/local/test.mp3" - assert media.mime_type == "audio/mpeg" - assert ( - "calls media_source.async_resolve_media without passing an entity_id" - in caplog.text - ) + with pytest.raises(RuntimeError): + await media_source.async_resolve_media( + hass, + media_source.generate_media_source_id( + media_source.DOMAIN, "local/test.mp3" + ), + ) async def test_async_unresolve_media(hass: HomeAssistant) -> None: diff --git a/tests/components/reolink/test_media_source.py b/tests/components/reolink/test_media_source.py index ddb66463419..c7abc5b8e0e 100644 --- a/tests/components/reolink/test_media_source.py +++ b/tests/components/reolink/test_media_source.py @@ -81,7 +81,9 @@ async def test_resolve( f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}" ) - play_media = await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/{file_id}") + play_media = await async_resolve_media( + hass, f"{URI_SCHEME}{DOMAIN}/{file_id}", None + ) assert play_media.mime_type == TEST_MIME_TYPE @@ -245,7 +247,7 @@ async def test_browsing_errors( with pytest.raises(Unresolvable): await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/UNKNOWN") with pytest.raises(Unresolvable): - await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/UNKNOWN") + await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/UNKNOWN", None) async def test_browsing_not_loaded( From 273e125859339bb91c9042d43ca227dbb2a1a29a Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Fri, 1 Mar 2024 23:29:47 +0100 Subject: [PATCH 0117/1691] Bump pyOverkiz to 1.13.8 (#111930) Bump pyoverkiz to 1.13.8 --- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index e7e5b87e099..db24a299f2a 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -19,7 +19,7 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"], - "requirements": ["pyoverkiz==1.13.7"], + "requirements": ["pyoverkiz==1.13.8"], "zeroconf": [ { "type": "_kizbox._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 641e45bffc7..73d129cce83 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2036,7 +2036,7 @@ pyotgw==2.1.3 pyotp==2.8.0 # homeassistant.components.overkiz -pyoverkiz==1.13.7 +pyoverkiz==1.13.8 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3f0b86377c7..4f6e77b3aa6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1578,7 +1578,7 @@ pyotgw==2.1.3 pyotp==2.8.0 # homeassistant.components.overkiz -pyoverkiz==1.13.7 +pyoverkiz==1.13.8 # homeassistant.components.openweathermap pyowm==3.2.0 From 23480292c56626be199aaeab4dcfb9ff8ec156cc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 23:55:37 +0100 Subject: [PATCH 0118/1691] Add icon translations to OpenUV (#111996) Co-authored-by: Aaron Bach --- .../components/openuv/binary_sensor.py | 1 - homeassistant/components/openuv/icons.json | 38 +++++++++++++++++++ homeassistant/components/openuv/sensor.py | 9 ----- 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/openuv/icons.json diff --git a/homeassistant/components/openuv/binary_sensor.py b/homeassistant/components/openuv/binary_sensor.py index 9c970f34dc3..7946597f3c3 100644 --- a/homeassistant/components/openuv/binary_sensor.py +++ b/homeassistant/components/openuv/binary_sensor.py @@ -20,7 +20,6 @@ ATTR_PROTECTION_WINDOW_STARTING_UV = "start_uv" BINARY_SENSOR_DESCRIPTION_PROTECTION_WINDOW = BinarySensorEntityDescription( key=TYPE_PROTECTION_WINDOW, translation_key="protection_window", - icon="mdi:sunglasses", ) diff --git a/homeassistant/components/openuv/icons.json b/homeassistant/components/openuv/icons.json new file mode 100644 index 00000000000..49a79c69672 --- /dev/null +++ b/homeassistant/components/openuv/icons.json @@ -0,0 +1,38 @@ +{ + "entity": { + "binary_sensor": { + "protection_window": { + "default": "mdi:sunglasses" + } + }, + "sensor": { + "current_uv_index": { + "default": "mdi:weather-sunny" + }, + "current_uv_level": { + "default": "mdi:weather-sunny" + }, + "max_uv_index": { + "default": "mdi:weather-sunny" + }, + "skin_type_1_safe_exposure_time": { + "default": "mdi:timer-outline" + }, + "skin_type_2_safe_exposure_time": { + "default": "mdi:timer-outline" + }, + "skin_type_3_safe_exposure_time": { + "default": "mdi:timer-outline" + }, + "skin_type_4_safe_exposure_time": { + "default": "mdi:timer-outline" + }, + "skin_type_5_safe_exposure_time": { + "default": "mdi:timer-outline" + }, + "skin_type_6_safe_exposure_time": { + "default": "mdi:timer-outline" + } + } + } +} diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py index 9e337d49ba3..f2eafa0dffb 100644 --- a/homeassistant/components/openuv/sensor.py +++ b/homeassistant/components/openuv/sensor.py @@ -89,7 +89,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_CURRENT_UV_INDEX, translation_key="current_uv_index", - icon="mdi:weather-sunny", native_unit_of_measurement=UV_INDEX, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["uv"], @@ -97,7 +96,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_CURRENT_UV_LEVEL, translation_key="current_uv_level", - icon="mdi:weather-sunny", device_class=SensorDeviceClass.ENUM, options=[label.value for label in UV_LABEL_DEFINITIONS], value_fn=lambda data: get_uv_label(data["uv"]), @@ -105,7 +103,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_MAX_UV_INDEX, translation_key="max_uv_index", - icon="mdi:weather-sunny", native_unit_of_measurement=UV_INDEX, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["uv_max"], @@ -113,7 +110,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_SAFE_EXPOSURE_TIME_1, translation_key="skin_type_1_safe_exposure_time", - icon="mdi:timer-outline", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["safe_exposure_time"][ @@ -123,7 +119,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_SAFE_EXPOSURE_TIME_2, translation_key="skin_type_2_safe_exposure_time", - icon="mdi:timer-outline", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["safe_exposure_time"][ @@ -133,7 +128,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_SAFE_EXPOSURE_TIME_3, translation_key="skin_type_3_safe_exposure_time", - icon="mdi:timer-outline", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["safe_exposure_time"][ @@ -143,7 +137,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_SAFE_EXPOSURE_TIME_4, translation_key="skin_type_4_safe_exposure_time", - icon="mdi:timer-outline", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["safe_exposure_time"][ @@ -153,7 +146,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_SAFE_EXPOSURE_TIME_5, translation_key="skin_type_5_safe_exposure_time", - icon="mdi:timer-outline", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["safe_exposure_time"][ @@ -163,7 +155,6 @@ SENSOR_DESCRIPTIONS = ( OpenUvSensorEntityDescription( key=TYPE_SAFE_EXPOSURE_TIME_6, translation_key="skin_type_6_safe_exposure_time", - icon="mdi:timer-outline", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda data: data["safe_exposure_time"][ From 357840ec53c746e8deea26802a9a0d9d73226a94 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 1 Mar 2024 23:55:49 +0100 Subject: [PATCH 0119/1691] Add icon translations to Notion (#111984) --- homeassistant/components/notion/icons.json | 9 +++++++++ homeassistant/components/notion/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/notion/icons.json diff --git a/homeassistant/components/notion/icons.json b/homeassistant/components/notion/icons.json new file mode 100644 index 00000000000..63ea6ad2c18 --- /dev/null +++ b/homeassistant/components/notion/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "mold_risk": { + "default": "mdi:liquid-spot" + } + } + } +} diff --git a/homeassistant/components/notion/sensor.py b/homeassistant/components/notion/sensor.py index f5439895ac9..d42a7ea194d 100644 --- a/homeassistant/components/notion/sensor.py +++ b/homeassistant/components/notion/sensor.py @@ -28,7 +28,6 @@ SENSOR_DESCRIPTIONS = ( NotionSensorDescription( key=SENSOR_MOLD, translation_key="mold_risk", - icon="mdi:liquid-spot", listener_kind=ListenerKind.MOLD, ), NotionSensorDescription( From fbb982f8cea01d72d5a1e7066ac5a0afb9e4c602 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Fri, 1 Mar 2024 18:13:09 -0500 Subject: [PATCH 0120/1691] Bump Zigpy to 0.63.3 (#112002) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 3a1df4207ac..225837c66fc 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -27,7 +27,7 @@ "pyserial-asyncio==0.6", "zha-quirks==0.0.112", "zigpy-deconz==0.23.1", - "zigpy==0.63.2", + "zigpy==0.63.3", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", diff --git a/requirements_all.txt b/requirements_all.txt index 73d129cce83..4bff6b5d276 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2950,7 +2950,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.63.2 +zigpy==0.63.3 # homeassistant.components.zoneminder zm-py==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f6e77b3aa6..eef8c4d19d1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2270,7 +2270,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.63.2 +zigpy==0.63.3 # homeassistant.components.zwave_js zwave-js-server-python==0.55.3 From 4ae7102f63f35cbd5a13dee5844cc656071b4abb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:20:21 +0100 Subject: [PATCH 0121/1691] Add icon translations to Modern Forms (#111933) * Add icon translations to Modern Forms * Add icon translations to Modern Forms --- .../components/modern_forms/__init__.py | 2 -- .../components/modern_forms/binary_sensor.py | 5 +-- .../components/modern_forms/icons.json | 34 +++++++++++++++++++ .../components/modern_forms/light.py | 1 - .../components/modern_forms/sensor.py | 5 +-- .../components/modern_forms/switch.py | 5 +-- .../modern_forms/test_binary_sensor.py | 3 -- tests/components/modern_forms/test_sensor.py | 6 +--- tests/components/modern_forms/test_switch.py | 3 -- 9 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 homeassistant/components/modern_forms/icons.json diff --git a/homeassistant/components/modern_forms/__init__.py b/homeassistant/components/modern_forms/__init__.py index 5997b2aa846..c7310ea35a3 100644 --- a/homeassistant/components/modern_forms/__init__.py +++ b/homeassistant/components/modern_forms/__init__.py @@ -139,14 +139,12 @@ class ModernFormsDeviceEntity(CoordinatorEntity[ModernFormsDataUpdateCoordinator *, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator, - icon: str | None = None, enabled_default: bool = True, ) -> None: """Initialize the Modern Forms entity.""" super().__init__(coordinator) self._attr_enabled_default = enabled_default self._entry_id = entry_id - self._attr_icon = icon @property def device_info(self) -> DeviceInfo: diff --git a/homeassistant/components/modern_forms/binary_sensor.py b/homeassistant/components/modern_forms/binary_sensor.py index b3361c3f143..148499aad2d 100644 --- a/homeassistant/components/modern_forms/binary_sensor.py +++ b/homeassistant/components/modern_forms/binary_sensor.py @@ -40,11 +40,10 @@ class ModernFormsBinarySensor(ModernFormsDeviceEntity, BinarySensorEntity): *, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator, - icon: str, key: str, ) -> None: """Initialize Modern Forms switch.""" - super().__init__(entry_id=entry_id, coordinator=coordinator, icon=icon) + super().__init__(entry_id=entry_id, coordinator=coordinator) self._attr_unique_id = f"{coordinator.data.info.mac_address}_{key}" @@ -62,7 +61,6 @@ class ModernFormsLightSleepTimerActive(ModernFormsBinarySensor): super().__init__( coordinator=coordinator, entry_id=entry_id, - icon="mdi:av-timer", key="light_sleep_timer_active", ) @@ -94,7 +92,6 @@ class ModernFormsFanSleepTimerActive(ModernFormsBinarySensor): super().__init__( coordinator=coordinator, entry_id=entry_id, - icon="mdi:av-timer", key="fan_sleep_timer_active", ) diff --git a/homeassistant/components/modern_forms/icons.json b/homeassistant/components/modern_forms/icons.json new file mode 100644 index 00000000000..e5df55dc15e --- /dev/null +++ b/homeassistant/components/modern_forms/icons.json @@ -0,0 +1,34 @@ +{ + "entity": { + "binary_sensor": { + "light_sleep_timer_active": { + "default": "mdi:av-timer" + }, + "fan_sleep_timer_active": { + "default": "mdi:av-timer" + } + }, + "sensor": { + "light_timer_remaining_time": { + "default": "mdi:timer-outline" + }, + "fan_timer_remaining_time": { + "default": "mdi:timer-outline" + } + }, + "switch": { + "away_mode": { + "default": "mdi:airplane-takeoff" + }, + "adaptive_learning": { + "default": "mdi:school-outline" + } + } + }, + "services": { + "set_light_sleep_timer": "mdi:timer", + "clear_light_sleep_timer": "mdi:timer-cancel", + "set_fan_sleep_timer": "mdi:timer", + "clear_fan_sleep_timer": "mdi:timer-cancel" + } +} diff --git a/homeassistant/components/modern_forms/light.py b/homeassistant/components/modern_forms/light.py index 013d6a17d6d..74763948b9f 100644 --- a/homeassistant/components/modern_forms/light.py +++ b/homeassistant/components/modern_forms/light.py @@ -90,7 +90,6 @@ class ModernFormsLightEntity(ModernFormsDeviceEntity, LightEntity): super().__init__( entry_id=entry_id, coordinator=coordinator, - icon=None, ) self._attr_unique_id = f"{self.coordinator.data.info.mac_address}" diff --git a/homeassistant/components/modern_forms/sensor.py b/homeassistant/components/modern_forms/sensor.py index efd659f3ae0..f6f2255090a 100644 --- a/homeassistant/components/modern_forms/sensor.py +++ b/homeassistant/components/modern_forms/sensor.py @@ -43,12 +43,11 @@ class ModernFormsSensor(ModernFormsDeviceEntity, SensorEntity): *, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator, - icon: str, key: str, ) -> None: """Initialize Modern Forms switch.""" self._key = key - super().__init__(entry_id=entry_id, coordinator=coordinator, icon=icon) + super().__init__(entry_id=entry_id, coordinator=coordinator) self._attr_unique_id = f"{self.coordinator.data.info.mac_address}_{self._key}" @@ -64,7 +63,6 @@ class ModernFormsLightTimerRemainingTimeSensor(ModernFormsSensor): super().__init__( coordinator=coordinator, entry_id=entry_id, - icon="mdi:timer-outline", key="light_timer_remaining_time", ) self._attr_device_class = SensorDeviceClass.TIMESTAMP @@ -95,7 +93,6 @@ class ModernFormsFanTimerRemainingTimeSensor(ModernFormsSensor): super().__init__( coordinator=coordinator, entry_id=entry_id, - icon="mdi:timer-outline", key="fan_timer_remaining_time", ) self._attr_device_class = SensorDeviceClass.TIMESTAMP diff --git a/homeassistant/components/modern_forms/switch.py b/homeassistant/components/modern_forms/switch.py index 18d8caccbd6..fef03a96651 100644 --- a/homeassistant/components/modern_forms/switch.py +++ b/homeassistant/components/modern_forms/switch.py @@ -39,12 +39,11 @@ class ModernFormsSwitch(ModernFormsDeviceEntity, SwitchEntity): *, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator, - icon: str, key: str, ) -> None: """Initialize Modern Forms switch.""" self._key = key - super().__init__(entry_id=entry_id, coordinator=coordinator, icon=icon) + super().__init__(entry_id=entry_id, coordinator=coordinator) self._attr_unique_id = f"{self.coordinator.data.info.mac_address}_{self._key}" @@ -60,7 +59,6 @@ class ModernFormsAwaySwitch(ModernFormsSwitch): super().__init__( coordinator=coordinator, entry_id=entry_id, - icon="mdi:airplane-takeoff", key="away_mode", ) @@ -92,7 +90,6 @@ class ModernFormsAdaptiveLearningSwitch(ModernFormsSwitch): super().__init__( coordinator=coordinator, entry_id=entry_id, - icon="mdi:school-outline", key="adaptive_learning", ) diff --git a/tests/components/modern_forms/test_binary_sensor.py b/tests/components/modern_forms/test_binary_sensor.py index 3ea0fca99d5..9c2ce6b5345 100644 --- a/tests/components/modern_forms/test_binary_sensor.py +++ b/tests/components/modern_forms/test_binary_sensor.py @@ -1,7 +1,6 @@ """Tests for the Modern Forms sensor platform.""" from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.modern_forms.const import DOMAIN -from homeassistant.const import ATTR_ICON from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -37,11 +36,9 @@ async def test_binary_sensors( # Light timer remaining time state = hass.states.get("binary_sensor.modernformsfan_light_sleep_timer_active") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:av-timer" assert state.state == "off" # Fan timer remaining time state = hass.states.get("binary_sensor.modernformsfan_fan_sleep_timer_active") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:av-timer" assert state.state == "off" diff --git a/tests/components/modern_forms/test_sensor.py b/tests/components/modern_forms/test_sensor.py index 279942f39a9..dc075181860 100644 --- a/tests/components/modern_forms/test_sensor.py +++ b/tests/components/modern_forms/test_sensor.py @@ -2,7 +2,7 @@ from datetime import datetime from homeassistant.components.sensor import SensorDeviceClass -from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ICON +from homeassistant.const import ATTR_DEVICE_CLASS from homeassistant.core import HomeAssistant from . import init_integration, modern_forms_timers_set_mock @@ -21,14 +21,12 @@ async def test_sensors( # Light timer remaining time state = hass.states.get("sensor.modernformsfan_light_sleep_time") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP assert state.state == "unknown" # Fan timer remaining time state = hass.states.get("sensor.modernformsfan_fan_sleep_time") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP assert state.state == "unknown" @@ -44,13 +42,11 @@ async def test_active_sensors( # Light timer remaining time state = hass.states.get("sensor.modernformsfan_light_sleep_time") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP datetime.fromisoformat(state.state) # Fan timer remaining time state = hass.states.get("sensor.modernformsfan_fan_sleep_time") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP datetime.fromisoformat(state.state) diff --git a/tests/components/modern_forms/test_switch.py b/tests/components/modern_forms/test_switch.py index b0ddc31150b..0a1b75f1df9 100644 --- a/tests/components/modern_forms/test_switch.py +++ b/tests/components/modern_forms/test_switch.py @@ -7,7 +7,6 @@ import pytest from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, - ATTR_ICON, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, @@ -31,7 +30,6 @@ async def test_switch_state( state = hass.states.get("switch.modernformsfan_away_mode") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:airplane-takeoff" assert state.state == STATE_OFF entry = entity_registry.async_get("switch.modernformsfan_away_mode") @@ -40,7 +38,6 @@ async def test_switch_state( state = hass.states.get("switch.modernformsfan_adaptive_learning") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:school-outline" assert state.state == STATE_OFF entry = entity_registry.async_get("switch.modernformsfan_adaptive_learning") From 0cc1fd5e99b312fa15c342017e0c93623f975cda Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:28:37 +0100 Subject: [PATCH 0122/1691] Add icon translations to Nextcloud (#111982) --- homeassistant/components/nextcloud/icons.json | 75 +++++++++++++++++++ homeassistant/components/nextcloud/sensor.py | 23 ------ 2 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/nextcloud/icons.json diff --git a/homeassistant/components/nextcloud/icons.json b/homeassistant/components/nextcloud/icons.json new file mode 100644 index 00000000000..7fc2e13cd50 --- /dev/null +++ b/homeassistant/components/nextcloud/icons.json @@ -0,0 +1,75 @@ +{ + "entity": { + "sensor": { + "nextcloud_activeusers_last1hour": { + "default": "mdi:account-multiple" + }, + "nextcloud_activeusers_last24hours": { + "default": "mdi:account-multiple" + }, + "nextcloud_activeusers_last5minutes": { + "default": "mdi:account-multiple" + }, + "nextcloud_database_size": { + "default": "mdi:database" + }, + "nextcloud_database_type": { + "default": "mdi:database" + }, + "nextcloud_database_version": { + "default": "mdi:database" + }, + "nextcloud_server_php_opcache_memory_usage_current_wasted_percentage": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_opcache_memory_usage_free_memory": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_opcache_memory_usage_used_memory": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_opcache_memory_usage_wasted_memory": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_max_execution_time": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_memory_limit": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_upload_max_filesize": { + "default": "mdi:language-php" + }, + "nextcloud_server_php_version": { + "default": "mdi:language-php" + }, + "nextcloud_system_apps_num_updates_available": { + "default": "mdi:update" + }, + "nextcloud_system_cpuload_1": { + "default": "mdi:chip" + }, + "nextcloud_system_cpuload_5": { + "default": "mdi:chip" + }, + "nextcloud_system_cpuload_15": { + "default": "mdi:chip" + }, + "nextcloud_system_freespace": { + "default": "mdi:harddisk" + }, + "nextcloud_system_mem_free": { + "default": "mdi:memory" + }, + "nextcloud_system_mem_total": { + "default": "mdi:memory" + }, + "nextcloud_system_swap_total": { + "default": "mdi:memory" + }, + "nextcloud_system_swap_free": { + "default": "mdi:memory" + } + } + } +} diff --git a/homeassistant/components/nextcloud/sensor.py b/homeassistant/components/nextcloud/sensor.py index 851cb9f3cd3..d80db0f3ea6 100644 --- a/homeassistant/components/nextcloud/sensor.py +++ b/homeassistant/components/nextcloud/sensor.py @@ -45,21 +45,18 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ translation_key="nextcloud_activeusers_last1hour", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:account-multiple", ), NextcloudSensorEntityDescription( key="activeUsers_last24hours", translation_key="nextcloud_activeusers_last24hours", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:account-multiple", ), NextcloudSensorEntityDescription( key="activeUsers_last5minutes", translation_key="nextcloud_activeusers_last5minutes", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:account-multiple", ), NextcloudSensorEntityDescription( key="cache_expunges", @@ -136,7 +133,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="database_size", translation_key="nextcloud_database_size", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:database", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=1, suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, @@ -145,13 +141,11 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="database_type", translation_key="nextcloud_database_type", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:database", ), NextcloudSensorEntityDescription( key="database_version", translation_key="nextcloud_database_version", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:database", ), NextcloudSensorEntityDescription( key="interned_strings_usage_buffer_size", @@ -328,7 +322,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ translation_key="nextcloud_server_php_opcache_memory_usage_current_wasted_percentage", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - icon="mdi:language-php", native_unit_of_measurement=PERCENTAGE, suggested_display_precision=1, ), @@ -338,7 +331,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ device_class=SensorDeviceClass.DATA_SIZE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - icon="mdi:language-php", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=1, suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, @@ -349,7 +341,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ device_class=SensorDeviceClass.DATA_SIZE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - icon="mdi:language-php", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=1, suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, @@ -360,7 +351,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ device_class=SensorDeviceClass.DATA_SIZE, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - icon="mdi:language-php", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=1, suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, @@ -370,7 +360,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ translation_key="nextcloud_server_php_max_execution_time", device_class=SensorDeviceClass.DURATION, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:language-php", native_unit_of_measurement=UnitOfTime.SECONDS, ), NextcloudSensorEntityDescription( @@ -378,7 +367,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ translation_key="nextcloud_server_php_memory_limit", device_class=SensorDeviceClass.DATA_SIZE, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:language-php", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=1, suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, @@ -388,7 +376,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ translation_key="nextcloud_server_php_upload_max_filesize", device_class=SensorDeviceClass.DATA_SIZE, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:language-php", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=1, suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, @@ -397,7 +384,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="server_php_version", translation_key="nextcloud_server_php_version", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:language-php", ), NextcloudSensorEntityDescription( key="server_webserver", @@ -526,34 +512,29 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="system_apps_num_updates_available", translation_key="nextcloud_system_apps_num_updates_available", state_class=SensorStateClass.MEASUREMENT, - icon="mdi:update", ), NextcloudSensorEntityDescription( key="system_cpuload_1", translation_key="nextcloud_system_cpuload_1", native_unit_of_measurement=UNIT_OF_LOAD, - icon="mdi:chip", suggested_display_precision=2, ), NextcloudSensorEntityDescription( key="system_cpuload_5", translation_key="nextcloud_system_cpuload_5", native_unit_of_measurement=UNIT_OF_LOAD, - icon="mdi:chip", suggested_display_precision=2, ), NextcloudSensorEntityDescription( key="system_cpuload_15", translation_key="nextcloud_system_cpuload_15", native_unit_of_measurement=UNIT_OF_LOAD, - icon="mdi:chip", suggested_display_precision=2, ), NextcloudSensorEntityDescription( key="system_freespace", translation_key="nextcloud_system_freespace", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:harddisk", native_unit_of_measurement=UnitOfInformation.BYTES, suggested_display_precision=2, suggested_unit_of_measurement=UnitOfInformation.GIGABYTES, @@ -562,7 +543,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="system_mem_free", translation_key="nextcloud_system_mem_free", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", native_unit_of_measurement=UnitOfInformation.KILOBYTES, suggested_display_precision=2, suggested_unit_of_measurement=UnitOfInformation.GIGABYTES, @@ -571,7 +551,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="system_mem_total", translation_key="nextcloud_system_mem_total", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", native_unit_of_measurement=UnitOfInformation.KILOBYTES, suggested_display_precision=2, suggested_unit_of_measurement=UnitOfInformation.GIGABYTES, @@ -598,7 +577,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="system_swap_total", translation_key="nextcloud_system_swap_total", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", native_unit_of_measurement=UnitOfInformation.KILOBYTES, suggested_display_precision=2, suggested_unit_of_measurement=UnitOfInformation.GIGABYTES, @@ -607,7 +585,6 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [ key="system_swap_free", translation_key="nextcloud_system_swap_free", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", native_unit_of_measurement=UnitOfInformation.KILOBYTES, suggested_display_precision=2, suggested_unit_of_measurement=UnitOfInformation.GIGABYTES, From a78382a3f700767b2d0de110ad4fb09e38f76bd8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:31:10 +0100 Subject: [PATCH 0123/1691] Add icon translations to NZBGet (#111985) --- homeassistant/components/nzbget/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/nzbget/icons.json diff --git a/homeassistant/components/nzbget/icons.json b/homeassistant/components/nzbget/icons.json new file mode 100644 index 00000000000..a693e9fec86 --- /dev/null +++ b/homeassistant/components/nzbget/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "pause": "mdi:pause", + "resume": "mdi:play", + "set_speed": "mdi:speedometer" + } +} From 2e12e5cda182ddf273386efde86db12be9de8c7e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:35:17 +0100 Subject: [PATCH 0124/1691] Add icon translations to Netgear LTE (#111974) * Add icon translations to Netgear LTE * Add icon translations to Netgear LTE * Update homeassistant/components/netgear_lte/icons.json --- .../components/netgear_lte/icons.json | 39 +++++++++++++++++++ .../components/netgear_lte/sensor.py | 9 ----- .../netgear_lte/snapshots/test_sensor.ambr | 9 ----- 3 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 homeassistant/components/netgear_lte/icons.json diff --git a/homeassistant/components/netgear_lte/icons.json b/homeassistant/components/netgear_lte/icons.json new file mode 100644 index 00000000000..543d9bf4690 --- /dev/null +++ b/homeassistant/components/netgear_lte/icons.json @@ -0,0 +1,39 @@ +{ + "entity": { + "sensor": { + "sms": { + "default": "mdi:message-processing" + }, + "sms_total": { + "default": "mdi:message-processing" + }, + "upstream": { + "default": "mdi:ip-network" + }, + "connection_text": { + "default": "mdi:radio-tower" + }, + "connection_type": { + "default": "mdi:ip" + }, + "service_type": { + "default": "mdi:radio-tower" + }, + "register_network_display": { + "default": "mdi:web" + }, + "band": { + "default": "mdi:radio-tower" + }, + "cell_id": { + "default": "mdi:radio-tower" + } + } + }, + "services": { + "delete_sms": "mdi:delete", + "set_option": "mdi:cog", + "connect_lte": "mdi:wifi", + "disconnect_lte": "mdi:wifi-off" + } +} diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index 4e978a2f964..c1e7aaad171 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -36,14 +36,12 @@ SENSORS: tuple[NetgearLTESensorEntityDescription, ...] = ( NetgearLTESensorEntityDescription( key="sms", translation_key="sms", - icon="mdi:message-processing", native_unit_of_measurement="unread", value_fn=lambda modem_data: sum(1 for x in modem_data.data.sms if x.unread), ), NetgearLTESensorEntityDescription( key="sms_total", translation_key="sms_total", - icon="mdi:message-processing", native_unit_of_measurement="messages", value_fn=lambda modem_data: len(modem_data.data.sms), ), @@ -84,49 +82,42 @@ SENSORS: tuple[NetgearLTESensorEntityDescription, ...] = ( key="upstream", translation_key="upstream", entity_registry_enabled_default=False, - icon="mdi:ip-network", entity_category=EntityCategory.DIAGNOSTIC, ), NetgearLTESensorEntityDescription( key="connection_text", translation_key="connection_text", entity_registry_enabled_default=False, - icon="mdi:radio-tower", entity_category=EntityCategory.DIAGNOSTIC, ), NetgearLTESensorEntityDescription( key="connection_type", translation_key="connection_type", entity_registry_enabled_default=False, - icon="mdi:ip", entity_category=EntityCategory.DIAGNOSTIC, ), NetgearLTESensorEntityDescription( key="current_ps_service_type", translation_key="service_type", entity_registry_enabled_default=False, - icon="mdi:radio-tower", entity_category=EntityCategory.DIAGNOSTIC, ), NetgearLTESensorEntityDescription( key="register_network_display", translation_key="register_network_display", entity_registry_enabled_default=False, - icon="mdi:web", entity_category=EntityCategory.DIAGNOSTIC, ), NetgearLTESensorEntityDescription( key="current_band", translation_key="band", entity_registry_enabled_default=False, - icon="mdi:radio-tower", entity_category=EntityCategory.DIAGNOSTIC, ), NetgearLTESensorEntityDescription( key="cell_id", translation_key="cell_id", entity_registry_enabled_default=False, - icon="mdi:radio-tower", entity_category=EntityCategory.DIAGNOSTIC, ), ) diff --git a/tests/components/netgear_lte/snapshots/test_sensor.ambr b/tests/components/netgear_lte/snapshots/test_sensor.ambr index 8d16ff29dfa..e74416895ee 100644 --- a/tests/components/netgear_lte/snapshots/test_sensor.ambr +++ b/tests/components/netgear_lte/snapshots/test_sensor.ambr @@ -3,7 +3,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Cell ID', - 'icon': 'mdi:radio-tower', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_cell_id', @@ -16,7 +15,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Connection text', - 'icon': 'mdi:radio-tower', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_connection_text', @@ -29,7 +27,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Connection type', - 'icon': 'mdi:ip', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_connection_type', @@ -42,7 +39,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Current band', - 'icon': 'mdi:radio-tower', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_current_band', @@ -68,7 +64,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Register network display', - 'icon': 'mdi:web', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_register_network_display', @@ -95,7 +90,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Service type', - 'icon': 'mdi:radio-tower', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_service_type', @@ -108,7 +102,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 SMS', - 'icon': 'mdi:message-processing', 'unit_of_measurement': 'unread', }), 'context': , @@ -122,7 +115,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 SMS total', - 'icon': 'mdi:message-processing', 'unit_of_measurement': 'messages', }), 'context': , @@ -150,7 +142,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Netgear LM1200 Upstream', - 'icon': 'mdi:ip-network', }), 'context': , 'entity_id': 'sensor.netgear_lm1200_upstream', From f3e0060128ced3264daada432dac27b94c9769de Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:35:27 +0100 Subject: [PATCH 0125/1691] Add icon translations to Omnilogic (#111989) --- homeassistant/components/omnilogic/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/omnilogic/icons.json diff --git a/homeassistant/components/omnilogic/icons.json b/homeassistant/components/omnilogic/icons.json new file mode 100644 index 00000000000..ee5b5102177 --- /dev/null +++ b/homeassistant/components/omnilogic/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_pump_speed": "mdi:water-pump" + } +} From 10c06a1553acb6288f6457a44b88e568685fc111 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:35:40 +0100 Subject: [PATCH 0126/1691] Add icon translations to Ondilo ico (#111990) --- .../components/ondilo_ico/icons.json | 21 +++++++++++++++++++ homeassistant/components/ondilo_ico/sensor.py | 5 ----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/ondilo_ico/icons.json diff --git a/homeassistant/components/ondilo_ico/icons.json b/homeassistant/components/ondilo_ico/icons.json new file mode 100644 index 00000000000..9319b747b28 --- /dev/null +++ b/homeassistant/components/ondilo_ico/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "sensor": { + "oxydo_reduction_potential": { + "default": "mdi:pool" + }, + "ph": { + "default": "mdi:pool" + }, + "tds": { + "default": "mdi:pool" + }, + "rssi": { + "default": "mdi:wifi" + }, + "salt": { + "default": "mdi:pool" + } + } + } +} diff --git a/homeassistant/components/ondilo_ico/sensor.py b/homeassistant/components/ondilo_ico/sensor.py index 90c79003b8a..0a39f661b6b 100644 --- a/homeassistant/components/ondilo_ico/sensor.py +++ b/homeassistant/components/ondilo_ico/sensor.py @@ -43,20 +43,17 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="orp", translation_key="oxydo_reduction_potential", native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, - icon="mdi:pool", state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="ph", translation_key="ph", - icon="mdi:pool", state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key="tds", translation_key="tds", native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION, - icon="mdi:pool", state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( @@ -68,7 +65,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="rssi", translation_key="rssi", - icon="mdi:wifi", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), @@ -76,7 +72,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="salt", translation_key="salt", native_unit_of_measurement="mg/L", - icon="mdi:pool", state_class=SensorStateClass.MEASUREMENT, ), ) From 46a86667d33a1c482e0b79e6e112a5fc0acf4f2e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:35:53 +0100 Subject: [PATCH 0127/1691] Add icon translations to Octoprint (#111988) --- homeassistant/components/octoprint/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/octoprint/icons.json diff --git a/homeassistant/components/octoprint/icons.json b/homeassistant/components/octoprint/icons.json new file mode 100644 index 00000000000..972ecabb765 --- /dev/null +++ b/homeassistant/components/octoprint/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "printer_connect": "mdi:lan-connect" + } +} From dd2d7e579d0892859546c8c11c683c936cd2d97c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:56:34 +0100 Subject: [PATCH 0128/1691] Add icon translations to Opensky (#111994) * Add icon translations to Opensky * Add icon translations to Opensky --- homeassistant/components/opensky/icons.json | 9 +++++++++ homeassistant/components/opensky/sensor.py | 2 +- tests/components/opensky/snapshots/test_sensor.ambr | 2 -- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/opensky/icons.json diff --git a/homeassistant/components/opensky/icons.json b/homeassistant/components/opensky/icons.json new file mode 100644 index 00000000000..763e489c4e7 --- /dev/null +++ b/homeassistant/components/opensky/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "flights": { + "default": "mdi:airplane" + } + } + } +} diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index 9cae0366357..449f17cefbb 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -38,7 +38,7 @@ class OpenSkySensor(CoordinatorEntity[OpenSkyDataUpdateCoordinator], SensorEntit ) _attr_has_entity_name = True _attr_name = None - _attr_icon = "mdi:airplane" + _attr_translation_key = "flights" _attr_native_unit_of_measurement = "flights" _attr_state_class = SensorStateClass.MEASUREMENT diff --git a/tests/components/opensky/snapshots/test_sensor.ambr b/tests/components/opensky/snapshots/test_sensor.ambr index a57b438df67..ab39746a93f 100644 --- a/tests/components/opensky/snapshots/test_sensor.ambr +++ b/tests/components/opensky/snapshots/test_sensor.ambr @@ -4,7 +4,6 @@ 'attributes': ReadOnlyDict({ 'attribution': 'Information provided by the OpenSky Network (https://opensky-network.org)', 'friendly_name': 'OpenSky', - 'icon': 'mdi:airplane', 'state_class': , 'unit_of_measurement': 'flights', }), @@ -20,7 +19,6 @@ 'attributes': ReadOnlyDict({ 'attribution': 'Information provided by the OpenSky Network (https://opensky-network.org)', 'friendly_name': 'OpenSky', - 'icon': 'mdi:airplane', 'state_class': , 'unit_of_measurement': 'flights', }), From 17191b5af2db5aba98fa919e47a27ec1fe128088 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:56:50 +0100 Subject: [PATCH 0129/1691] Add icon translations to Openhome (#111993) --- homeassistant/components/openhome/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/openhome/icons.json diff --git a/homeassistant/components/openhome/icons.json b/homeassistant/components/openhome/icons.json new file mode 100644 index 00000000000..081e97c3489 --- /dev/null +++ b/homeassistant/components/openhome/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "invoke_pin": "mdi:alarm-panel" + } +} From ea6913d4365e0e8a1e66b192bde5757fd21beff1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 00:57:25 +0100 Subject: [PATCH 0130/1691] Add icon translations to OpenAI Conversation (#111992) --- homeassistant/components/openai_conversation/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/openai_conversation/icons.json diff --git a/homeassistant/components/openai_conversation/icons.json b/homeassistant/components/openai_conversation/icons.json new file mode 100644 index 00000000000..7f736a5ff3b --- /dev/null +++ b/homeassistant/components/openai_conversation/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "generate_image": "mdi:image-sync" + } +} From 807765400270a88c17c45308dd9b86b56127189b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 01:01:46 +0100 Subject: [PATCH 0131/1691] Add icon translations to ovo energy (#111997) --- homeassistant/components/ovo_energy/icons.json | 12 ++++++++++++ homeassistant/components/ovo_energy/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/ovo_energy/icons.json diff --git a/homeassistant/components/ovo_energy/icons.json b/homeassistant/components/ovo_energy/icons.json new file mode 100644 index 00000000000..083e8ca8c2c --- /dev/null +++ b/homeassistant/components/ovo_energy/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "last_gas_reading": { + "default": "mdi:gas-cylinder" + }, + "last_gas_cost": { + "default": "mdi:cash-multiple" + } + } + } +} diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 761515c9c84..059ced8477e 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -81,7 +81,6 @@ SENSOR_TYPES_GAS: tuple[OVOEnergySensorEntityDescription, ...] = ( device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - icon="mdi:gas-cylinder", value=lambda usage: usage.gas[-1].consumption, ), OVOEnergySensorEntityDescription( @@ -89,7 +88,6 @@ SENSOR_TYPES_GAS: tuple[OVOEnergySensorEntityDescription, ...] = ( translation_key=KEY_LAST_GAS_COST, device_class=SensorDeviceClass.MONETARY, state_class=SensorStateClass.TOTAL_INCREASING, - icon="mdi:cash-multiple", value=lambda usage: usage.gas[-1].cost.amount if usage.gas[-1].cost is not None else None, From 8ff2a392385918c133cdc50a001717b7ac0a0402 Mon Sep 17 00:00:00 2001 From: Chris Helming <7746625+chelming@users.noreply.github.com> Date: Fri, 1 Mar 2024 20:00:03 -0500 Subject: [PATCH 0132/1691] Fix minor language issues in strings.json (#112006) language fix: allow -> allows --- homeassistant/components/random/strings.json | 2 +- homeassistant/components/template/strings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/random/strings.json b/homeassistant/components/random/strings.json index 164f184ae88..0faad1d8093 100644 --- a/homeassistant/components/random/strings.json +++ b/homeassistant/components/random/strings.json @@ -19,7 +19,7 @@ "title": "Random sensor" }, "user": { - "description": "This helper allow you to create a helper that emits a random value.", + "description": "This helper allows you to create a helper that emits a random value.", "menu_options": { "binary_sensor": "Random binary sensor", "sensor": "Random sensor" diff --git a/homeassistant/components/template/strings.json b/homeassistant/components/template/strings.json index 79cd0289724..6122f4c9db5 100644 --- a/homeassistant/components/template/strings.json +++ b/homeassistant/components/template/strings.json @@ -20,7 +20,7 @@ "title": "Template sensor" }, "user": { - "description": "This helper allow you to create helper entities that define their state using a template.", + "description": "This helper allows you to create helper entities that define their state using a template.", "menu_options": { "binary_sensor": "Template a binary sensor", "sensor": "Template a sensor" From 4249d17c1bb8d0ebbeb099deff4e3c6f6cad53d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 Mar 2024 17:16:01 -1000 Subject: [PATCH 0133/1691] Reduce ESPHome reconnect time (#112001) Use eager tasks to request device_info and entities which avoids waiting one event loop to send the requests to the device --- homeassistant/components/esphome/manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index bd01bea8795..2cbb0494ecf 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -50,6 +50,7 @@ from homeassistant.helpers.issue_registry import ( from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.template import Template from homeassistant.helpers.typing import EventType +from homeassistant.util.async_ import create_eager_task from .bluetooth import async_connect_scanner from .const import ( @@ -390,8 +391,8 @@ class ESPHomeManager: stored_device_name = entry.data.get(CONF_DEVICE_NAME) unique_id_is_mac_address = unique_id and ":" in unique_id results = await asyncio.gather( - cli.device_info(), - cli.list_entities_services(), + create_eager_task(cli.device_info()), + create_eager_task(cli.list_entities_services()), ) device_info: EsphomeDeviceInfo = results[0] From 5f65315e86a5e06abcb80e14f80b876e8e9e5777 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 Mar 2024 17:43:08 -1000 Subject: [PATCH 0134/1691] Reduce samsungtv startup time (#112007) Create the startup tasks eagerly This one is a bit high --- homeassistant/components/samsungtv/media_player.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 44fce7f953f..d8f6624dfea 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from collections.abc import Coroutine, Sequence +from collections.abc import Sequence from typing import Any from async_upnp_client.aiohttp import AiohttpNotifyServer, AiohttpSessionRequester @@ -35,6 +35,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.trigger import PluggableAction +from homeassistant.util.async_ import create_eager_task from .bridge import SamsungTVBridge, SamsungTVWSBridge from .const import CONF_SSDP_RENDERING_CONTROL_LOCATION, DOMAIN, LOGGER @@ -171,15 +172,15 @@ class SamsungTVDevice(SamsungTVEntity, MediaPlayerEntity): await self._dmr_device.async_unsubscribe_services() return - startup_tasks: list[Coroutine[Any, Any, Any]] = [] + startup_tasks: list[asyncio.Task[Any]] = [] if not self._app_list_event.is_set(): - startup_tasks.append(self._async_startup_app_list()) + startup_tasks.append(create_eager_task(self._async_startup_app_list())) if self._dmr_device and not self._dmr_device.is_subscribed: - startup_tasks.append(self._async_resubscribe_dmr()) + startup_tasks.append(create_eager_task(self._async_resubscribe_dmr())) if not self._dmr_device and self._ssdp_rendering_control_location: - startup_tasks.append(self._async_startup_dmr()) + startup_tasks.append(create_eager_task(self._async_startup_dmr())) if startup_tasks: await asyncio.gather(*startup_tasks) From c0f7ade92b2bec7625a83a403dc7f79318d9748c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 Mar 2024 18:15:10 -1000 Subject: [PATCH 0135/1691] Convert command_line to use asyncio for subprocesses (#111927) * Convert command_line to use asyncio for subprocesses * fixes * fix * fixes * more test fixes * more fixes * fixes * preen --- .../components/command_line/binary_sensor.py | 2 +- .../components/command_line/cover.py | 18 +++---- .../components/command_line/sensor.py | 11 ++-- .../components/command_line/switch.py | 28 +++++----- .../components/command_line/utils.py | 52 +++++++++---------- tests/components/command_line/__init__.py | 29 +++++++++++ .../command_line/test_binary_sensor.py | 7 ++- tests/components/command_line/test_cover.py | 24 +++------ tests/components/command_line/test_sensor.py | 25 +++------ tests/components/command_line/test_switch.py | 36 +++++-------- 10 files changed, 115 insertions(+), 117 deletions(-) diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py index 20b538fc4d7..d9385714719 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -148,7 +148,7 @@ class CommandBinarySensor(ManualTriggerEntity, BinarySensorEntity): async def _async_update(self) -> None: """Get the latest data and updates the state.""" - await self.hass.async_add_executor_job(self.data.update) + await self.data.async_update() value = self.data.value if self._value_template is not None: diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 845de352d73..8b60c0750aa 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -28,7 +28,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util, slugify from .const import CONF_COMMAND_TIMEOUT, LOGGER -from .utils import call_shell_with_timeout, check_output_or_log +from .utils import async_call_shell_with_timeout, async_check_output_or_log SCAN_INTERVAL = timedelta(seconds=15) @@ -114,11 +114,11 @@ class CommandCover(ManualTriggerEntity, CoverEntity): ), ) - def _move_cover(self, command: str) -> bool: + async def _async_move_cover(self, command: str) -> bool: """Execute the actual commands.""" LOGGER.info("Running command: %s", command) - returncode = call_shell_with_timeout(command, self._timeout) + returncode = await async_call_shell_with_timeout(command, self._timeout) success = returncode == 0 if not success: @@ -143,11 +143,11 @@ class CommandCover(ManualTriggerEntity, CoverEntity): """ return self._state - def _query_state(self) -> str | None: + async def _async_query_state(self) -> str | None: """Query for the state.""" if self._command_state: LOGGER.info("Running state value command: %s", self._command_state) - return check_output_or_log(self._command_state, self._timeout) + return await async_check_output_or_log(self._command_state, self._timeout) if TYPE_CHECKING: return None @@ -169,7 +169,7 @@ class CommandCover(ManualTriggerEntity, CoverEntity): async def _async_update(self) -> None: """Update device state.""" if self._command_state: - payload = str(await self.hass.async_add_executor_job(self._query_state)) + payload = str(await self._async_query_state()) if self._value_template: payload = self._value_template.async_render_with_possible_json_value( payload, None @@ -189,15 +189,15 @@ class CommandCover(ManualTriggerEntity, CoverEntity): async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - await self.hass.async_add_executor_job(self._move_cover, self._command_open) + await self._async_move_cover(self._command_open) await self._update_entity_state() async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" - await self.hass.async_add_executor_job(self._move_cover, self._command_close) + await self._async_move_cover(self._command_close) await self._update_entity_state() async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" - await self.hass.async_add_executor_job(self._move_cover, self._command_stop) + await self._async_move_cover(self._command_stop) await self._update_entity_state() diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index c1d60b9d2fd..abbbf4822d7 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -33,7 +33,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util from .const import CONF_COMMAND_TIMEOUT, LOGGER -from .utils import check_output_or_log +from .utils import async_check_output_or_log CONF_JSON_ATTRIBUTES = "json_attributes" @@ -138,6 +138,7 @@ class CommandSensor(ManualTriggerSensorEntity): """Update the state of the entity.""" if self._process_updates is None: self._process_updates = asyncio.Lock() + if self._process_updates.locked(): LOGGER.warning( "Updating Command Line Sensor %s took longer than the scheduled update interval %s", @@ -151,7 +152,7 @@ class CommandSensor(ManualTriggerSensorEntity): async def _async_update(self) -> None: """Get the latest data and updates the state.""" - await self.hass.async_add_executor_job(self.data.update) + await self.data.async_update() value = self.data.value if self._json_attributes: @@ -216,7 +217,7 @@ class CommandSensorData: self.command = command self.timeout = command_timeout - def update(self) -> None: + async def async_update(self) -> None: """Get the latest data with a shell command.""" command = self.command @@ -231,7 +232,7 @@ class CommandSensorData: if args_compiled: try: args_to_render = {"arguments": args} - rendered_args = args_compiled.render(args_to_render) + rendered_args = args_compiled.async_render(args_to_render) except TemplateError as ex: LOGGER.exception("Error rendering command template: %s", ex) return @@ -246,4 +247,4 @@ class CommandSensorData: command = f"{prog} {rendered_args}" LOGGER.debug("Running command: %s", command) - self.value = check_output_or_log(command, self.timeout) + self.value = await async_check_output_or_log(command, self.timeout) diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index efeded194ce..b354b8e7576 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -28,7 +28,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import dt as dt_util, slugify from .const import CONF_COMMAND_TIMEOUT, LOGGER -from .utils import call_shell_with_timeout, check_output_or_log +from .utils import async_call_shell_with_timeout, async_check_output_or_log SCAN_INTERVAL = timedelta(seconds=30) @@ -121,28 +121,26 @@ class CommandSwitch(ManualTriggerEntity, SwitchEntity): """Execute the actual commands.""" LOGGER.info("Running command: %s", command) - success = ( - await self.hass.async_add_executor_job( - call_shell_with_timeout, command, self._timeout - ) - == 0 - ) + success = await async_call_shell_with_timeout(command, self._timeout) == 0 if not success: LOGGER.error("Command failed: %s", command) return success - def _query_state_value(self, command: str) -> str | None: + async def _async_query_state_value(self, command: str) -> str | None: """Execute state command for return value.""" LOGGER.info("Running state value command: %s", command) - return check_output_or_log(command, self._timeout) + return await async_check_output_or_log(command, self._timeout) - def _query_state_code(self, command: str) -> bool: + async def _async_query_state_code(self, command: str) -> bool: """Execute state command for return code.""" LOGGER.info("Running state code command: %s", command) return ( - call_shell_with_timeout(command, self._timeout, log_return_code=False) == 0 + await async_call_shell_with_timeout( + command, self._timeout, log_return_code=False + ) + == 0 ) @property @@ -150,12 +148,12 @@ class CommandSwitch(ManualTriggerEntity, SwitchEntity): """Return true if we do optimistic updates.""" return self._command_state is None - def _query_state(self) -> str | int | None: + async def _async_query_state(self) -> str | int | None: """Query for state.""" if self._command_state: if self._value_template: - return self._query_state_value(self._command_state) - return self._query_state_code(self._command_state) + return await self._async_query_state_value(self._command_state) + return await self._async_query_state_code(self._command_state) if TYPE_CHECKING: return None @@ -177,7 +175,7 @@ class CommandSwitch(ManualTriggerEntity, SwitchEntity): async def _async_update(self) -> None: """Update device state.""" if self._command_state: - payload = str(await self.hass.async_add_executor_job(self._query_state)) + payload = str(await self._async_query_state()) value = None if self._value_template: value = self._value_template.async_render_with_possible_json_value( diff --git a/homeassistant/components/command_line/utils.py b/homeassistant/components/command_line/utils.py index 66faa3a0bf8..0cd2947d9cd 100644 --- a/homeassistant/components/command_line/utils.py +++ b/homeassistant/components/command_line/utils.py @@ -1,13 +1,14 @@ """The command_line component utils.""" from __future__ import annotations +import asyncio import logging -import subprocess _LOGGER = logging.getLogger(__name__) +_EXEC_FAILED_CODE = 127 -def call_shell_with_timeout( +async def async_call_shell_with_timeout( command: str, timeout: int, *, log_return_code: bool = True ) -> int: """Run a shell command with a timeout. @@ -17,46 +18,45 @@ def call_shell_with_timeout( """ try: _LOGGER.debug("Running command: %s", command) - subprocess.check_output( + proc = await asyncio.create_subprocess_shell( # noqa: S602 # shell by design command, - shell=True, # noqa: S602 # shell by design - timeout=timeout, close_fds=False, # required for posix_spawn ) - return 0 - except subprocess.CalledProcessError as proc_exception: - if log_return_code: + async with asyncio.timeout(timeout): + await proc.communicate() + return_code = proc.returncode + if return_code == _EXEC_FAILED_CODE: + _LOGGER.error("Error trying to exec command: %s", command) + elif log_return_code and return_code != 0: _LOGGER.error( "Command failed (with return code %s): %s", - proc_exception.returncode, + proc.returncode, command, ) - return proc_exception.returncode - except subprocess.TimeoutExpired: + return return_code or 0 + except TimeoutError: _LOGGER.error("Timeout for command: %s", command) return -1 - except subprocess.SubprocessError: - _LOGGER.error("Error trying to exec command: %s", command) - return -1 -def check_output_or_log(command: str, timeout: int) -> str | None: +async def async_check_output_or_log(command: str, timeout: int) -> str | None: """Run a shell command with a timeout and return the output.""" try: - return_value = subprocess.check_output( + proc = await asyncio.create_subprocess_shell( # noqa: S602 # shell by design command, - shell=True, # noqa: S602 # shell by design - timeout=timeout, close_fds=False, # required for posix_spawn + stdout=asyncio.subprocess.PIPE, ) - return return_value.strip().decode("utf-8") - except subprocess.CalledProcessError as err: - _LOGGER.error( - "Command failed (with return code %s): %s", err.returncode, command - ) - except subprocess.TimeoutExpired: + async with asyncio.timeout(timeout): + stdout, _ = await proc.communicate() + + if proc.returncode != 0: + _LOGGER.error( + "Command failed (with return code %s): %s", proc.returncode, command + ) + else: + return stdout.strip().decode("utf-8") + except TimeoutError: _LOGGER.error("Timeout for command: %s", command) - except subprocess.SubprocessError: - _LOGGER.error("Error trying to exec command: %s", command) return None diff --git a/tests/components/command_line/__init__.py b/tests/components/command_line/__init__.py index d79b3e27db3..736ca68b43d 100644 --- a/tests/components/command_line/__init__.py +++ b/tests/components/command_line/__init__.py @@ -1 +1,30 @@ """Tests for command_line component.""" + +import asyncio +from contextlib import contextmanager +from unittest.mock import MagicMock, patch + + +@contextmanager +def mock_asyncio_subprocess_run( + response: bytes = b"", returncode: int = 0, exception: Exception = None +): + """Mock create_subprocess_shell.""" + + class MockProcess(asyncio.subprocess.Process): + @property + def returncode(self): + return returncode + + async def communicate(self): + if exception: + raise exception + return response, b"" + + mock_process = MockProcess(MagicMock(), MagicMock(), MagicMock()) + + with patch( + "homeassistant.components.command_line.utils.asyncio.create_subprocess_shell", + return_value=mock_process, + ) as mock: + yield mock diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index 7975660fda3..46d072fb94f 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -21,6 +21,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util +from . import mock_asyncio_subprocess_run + from tests.common import async_fire_time_changed @@ -329,10 +331,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "off") await hass.async_block_till_done() - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"0", - ): + with mock_asyncio_subprocess_run(b"0"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) await hass.async_block_till_done() diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index 901fc39eb34..2a66acf8787 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -30,16 +30,15 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util +from . import mock_asyncio_subprocess_run + from tests.common import async_fire_time_changed async def test_no_poll_when_cover_has_no_command_state(hass: HomeAssistant) -> None: """Test that the cover does not polls when there's no state command.""" - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"50\n", - ) as check_output: + with mock_asyncio_subprocess_run(b"50\n") as mock_subprocess_run: assert await setup.async_setup_component( hass, COVER_DOMAIN, @@ -51,7 +50,7 @@ async def test_no_poll_when_cover_has_no_command_state(hass: HomeAssistant) -> N ) async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert not check_output.called + assert not mock_subprocess_run.called @pytest.mark.parametrize( @@ -74,17 +73,13 @@ async def test_poll_when_cover_has_command_state( ) -> None: """Test that the cover polls when there's a state command.""" - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"50\n", - ) as check_output: + with mock_asyncio_subprocess_run(b"50\n") as mock_subprocess_run: async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - check_output.assert_called_once_with( + mock_subprocess_run.assert_called_once_with( "echo state", - shell=True, # noqa: S604 # shell by design - timeout=15, close_fds=False, + stdout=-1, ) @@ -379,10 +374,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "off") await hass.async_block_till_done() - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"50\n", - ): + with mock_asyncio_subprocess_run(b"50\n"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) await hass.async_block_till_done() diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 64227116cfe..e169ef2d99b 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio from datetime import timedelta -import subprocess from typing import Any from unittest.mock import patch @@ -22,6 +21,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util +from . import mock_asyncio_subprocess_run + from tests.common import async_fire_time_changed @@ -132,10 +133,7 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"Works\n", - ) as check_output: + with mock_asyncio_subprocess_run(b"Works\n") as mock_subprocess_run: # Give time for template to load async_fire_time_changed( hass, @@ -143,11 +141,10 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - assert len(check_output.mock_calls) == 1 - check_output.assert_called_with( + assert len(mock_subprocess_run.mock_calls) == 1 + mock_subprocess_run.assert_called_with( 'echo "sensor_value" "3 4"', - shell=True, # noqa: S604 # shell by design - timeout=15, + stdout=-1, close_fds=False, ) @@ -679,10 +676,7 @@ async def test_template_not_error_when_data_is_none( ) -> None: """Test command sensor with template not logging error when data is None.""" - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - side_effect=subprocess.CalledProcessError, - ): + with mock_asyncio_subprocess_run(returncode=1): await setup.async_setup_component( hass, DOMAIN, @@ -747,10 +741,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "off") await hass.async_block_till_done() - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"January 17, 2022", - ): + with mock_asyncio_subprocess_run(b"January 17, 2022"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) await hass.async_block_till_done() diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index 47d9184f4f9..370573e4274 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -5,7 +5,6 @@ import asyncio from datetime import timedelta import json import os -import subprocess import tempfile from unittest.mock import patch @@ -32,6 +31,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util +from . import mock_asyncio_subprocess_run + from tests.common import async_fire_time_changed @@ -374,13 +375,7 @@ async def test_switch_command_state_code_exceptions( ) -> None: """Test that switch state code exceptions are handled correctly.""" - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - side_effect=[ - subprocess.TimeoutExpired("cmd", 10), - subprocess.SubprocessError(), - ], - ) as check_output: + with mock_asyncio_subprocess_run(exception=asyncio.TimeoutError) as run: await setup.async_setup_component( hass, DOMAIN, @@ -401,12 +396,13 @@ async def test_switch_command_state_code_exceptions( async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert check_output.called + assert run.called assert "Timeout for command" in caplog.text + with mock_asyncio_subprocess_run(returncode=127) as run: async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) await hass.async_block_till_done() - assert check_output.called + assert run.called assert "Error trying to exec command" in caplog.text @@ -415,13 +411,7 @@ async def test_switch_command_state_value_exceptions( ) -> None: """Test that switch state value exceptions are handled correctly.""" - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - side_effect=[ - subprocess.TimeoutExpired("cmd", 10), - subprocess.SubprocessError(), - ], - ) as check_output: + with mock_asyncio_subprocess_run(exception=asyncio.TimeoutError) as run: await setup.async_setup_component( hass, DOMAIN, @@ -443,13 +433,14 @@ async def test_switch_command_state_value_exceptions( async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert check_output.call_count == 1 + assert run.call_count == 1 assert "Timeout for command" in caplog.text + with mock_asyncio_subprocess_run(returncode=127) as run: async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) await hass.async_block_till_done() - assert check_output.call_count == 2 - assert "Error trying to exec command" in caplog.text + assert run.call_count == 1 + assert "Command failed (with return code 127)" in caplog.text async def test_unique_id( @@ -750,10 +741,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "off") await hass.async_block_till_done() - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"50\n", - ): + with mock_asyncio_subprocess_run(b"50\n"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) await hass.async_block_till_done() From c770c6c78f12133aa61355c455382abf11f8e134 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 2 Mar 2024 07:10:34 +0100 Subject: [PATCH 0136/1691] Apply better names to a couple of dicts in Axis config flow (#111964) Apply better names to a couple of dicts --- homeassistant/components/axis/config_flow.py | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 1a46388dbd5..f2b6ece3c97 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -57,7 +57,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): def __init__(self) -> None: """Initialize the Axis config flow.""" - self.device_config: dict[str, Any] = {} + self.config: dict[str, Any] = {} self.discovery_schema: dict[vol.Required, type[str | int]] | None = None async def async_step_user( @@ -85,7 +85,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): } ) - self.device_config = { + self.config = { CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT], CONF_USERNAME: user_input[CONF_USERNAME], @@ -110,7 +110,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): return self.async_show_form( step_id="user", - description_placeholders=self.device_config, + description_placeholders=self.config, data_schema=vol.Schema(data), errors=errors, ) @@ -120,7 +120,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): Generate a name to be used as a prefix for device entities. """ - model = self.device_config[CONF_MODEL] + model = self.config[CONF_MODEL] same_model = [ entry.data[CONF_NAME] for entry in self.hass.config_entries.async_entries(AXIS_DOMAIN) @@ -133,10 +133,10 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): if name not in same_model: break - self.device_config[CONF_NAME] = name + self.config[CONF_NAME] = name title = f"{model} - {serial}" - return self.async_create_entry(title=title, data=self.device_config) + return self.async_create_entry(title=title, data=self.config) async def async_step_reauth( self, entry_data: Mapping[str, Any] @@ -197,39 +197,39 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): ) async def _process_discovered_device( - self, device: dict[str, Any] + self, discovery_info: dict[str, Any] ) -> ConfigFlowResult: """Prepare configuration for a discovered Axis device.""" - if device[CONF_MAC][:8] not in AXIS_OUI: + if discovery_info[CONF_MAC][:8] not in AXIS_OUI: return self.async_abort(reason="not_axis_device") - if is_link_local(ip_address(device[CONF_HOST])): + if is_link_local(ip_address(discovery_info[CONF_HOST])): return self.async_abort(reason="link_local_address") - await self.async_set_unique_id(device[CONF_MAC]) + await self.async_set_unique_id(discovery_info[CONF_MAC]) self._abort_if_unique_id_configured( updates={ - CONF_HOST: device[CONF_HOST], - CONF_PORT: device[CONF_PORT], + CONF_HOST: discovery_info[CONF_HOST], + CONF_PORT: discovery_info[CONF_PORT], } ) self.context.update( { "title_placeholders": { - CONF_NAME: device[CONF_NAME], - CONF_HOST: device[CONF_HOST], + CONF_NAME: discovery_info[CONF_NAME], + CONF_HOST: discovery_info[CONF_HOST], }, - "configuration_url": f"http://{device[CONF_HOST]}:{device[CONF_PORT]}", + "configuration_url": f"http://{discovery_info[CONF_HOST]}:{discovery_info[CONF_PORT]}", } ) self.discovery_schema = { - vol.Required(CONF_HOST, default=device[CONF_HOST]): str, + vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_PORT, default=device[CONF_PORT]): int, + vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int, } return await self.async_step_user() From fbb894a0ff0358adf5629e891089e54bab40eb36 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 10:30:10 +0100 Subject: [PATCH 0137/1691] Add icon translations to Nmap tracker (#111983) --- .../components/nmap_tracker/device_tracker.py | 6 +----- homeassistant/components/nmap_tracker/icons.json | 12 ++++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/nmap_tracker/icons.json diff --git a/homeassistant/components/nmap_tracker/device_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py index bada45256a8..9b5597b6ba4 100644 --- a/homeassistant/components/nmap_tracker/device_tracker.py +++ b/homeassistant/components/nmap_tracker/device_tracker.py @@ -46,6 +46,7 @@ class NmapTrackerEntity(ScannerEntity): """An Nmap Tracker entity.""" _attr_should_poll = False + _attr_translation_key = "device_tracker" def __init__( self, nmap_tracker: NmapDeviceScanner, mac_address: str, active: bool @@ -98,11 +99,6 @@ class NmapTrackerEntity(ScannerEntity): """Return tracker source type.""" return SourceType.ROUTER - @property - def icon(self) -> str: - """Return device icon.""" - return "mdi:lan-connect" if self._active else "mdi:lan-disconnect" - @callback def async_process_update(self, online: bool) -> None: """Update device.""" diff --git a/homeassistant/components/nmap_tracker/icons.json b/homeassistant/components/nmap_tracker/icons.json new file mode 100644 index 00000000000..02d1d17b92b --- /dev/null +++ b/homeassistant/components/nmap_tracker/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "device_tracker": { + "device_tracker": { + "default": "mdi:lan-disconnect", + "state": { + "home": "mdi:lan-connect" + } + } + } + } +} From 98873402984bd593316c60643bdebea52ded375b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 10:30:21 +0100 Subject: [PATCH 0138/1691] Add icon translations to Opentherm gw (#111995) --- homeassistant/components/opentherm_gw/icons.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 homeassistant/components/opentherm_gw/icons.json diff --git a/homeassistant/components/opentherm_gw/icons.json b/homeassistant/components/opentherm_gw/icons.json new file mode 100644 index 00000000000..9d5d903aabc --- /dev/null +++ b/homeassistant/components/opentherm_gw/icons.json @@ -0,0 +1,15 @@ +{ + "services": { + "reset_gateway": "mdi:reload", + "set_central_heating_ovrd": "mdi:heat-wave", + "set_clock": "mdi:clock", + "set_control_setpoint": "mdi:thermometer-lines", + "set_hot_water_ovrd": "mdi:thermometer-lines", + "set_hot_water_setpoint": "mdi:thermometer-lines", + "set_gpio_mode": "mdi:cable-data", + "set_led_mode": "mdi:led-on", + "set_max_modulation": "mdi:thermometer-lines", + "set_outside_temperature": "mdi:thermometer-lines", + "set_setback_temperature": "mdi:thermometer-lines" + } +} From 0f71e45fe2214fe55a4eb47a0463b0489d52da99 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 10:30:48 +0100 Subject: [PATCH 0139/1691] Add entity and icon translations to Onvif (#111991) * Add icon translations to Onvif * Add icon translations to Onvif * Update homeassistant/components/onvif/strings.json --- homeassistant/components/onvif/icons.json | 18 ++++++++++++++++++ homeassistant/components/onvif/strings.json | 13 +++++++++++++ homeassistant/components/onvif/switch.py | 9 +++------ 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/onvif/icons.json diff --git a/homeassistant/components/onvif/icons.json b/homeassistant/components/onvif/icons.json new file mode 100644 index 00000000000..4db9a9f9e49 --- /dev/null +++ b/homeassistant/components/onvif/icons.json @@ -0,0 +1,18 @@ +{ + "entity": { + "switch": { + "autofocus": { + "default": "mdi:focus-auto" + }, + "ir_lamp": { + "default": "mdi:spotlight-beam" + }, + "wiper": { + "default": "mdi:wiper" + } + } + }, + "services": { + "ptz": "mdi:pan" + } +} diff --git a/homeassistant/components/onvif/strings.json b/homeassistant/components/onvif/strings.json index 5a36b89688a..c3f0b89df3b 100644 --- a/homeassistant/components/onvif/strings.json +++ b/homeassistant/components/onvif/strings.json @@ -71,6 +71,19 @@ } } }, + "entity": { + "switch": { + "autofocus": { + "name": "Autofocus" + }, + "ir_lamp": { + "name": "IR lamp" + }, + "wiper": { + "name": "Wiper" + } + } + }, "services": { "ptz": { "name": "PTZ", diff --git a/homeassistant/components/onvif/switch.py b/homeassistant/components/onvif/switch.py index 673f77f558c..61983ef1ab5 100644 --- a/homeassistant/components/onvif/switch.py +++ b/homeassistant/components/onvif/switch.py @@ -41,8 +41,7 @@ class ONVIFSwitchEntityDescription( SWITCHES: tuple[ONVIFSwitchEntityDescription, ...] = ( ONVIFSwitchEntityDescription( key="autofocus", - name="Autofocus", - icon="mdi:focus-auto", + translation_key="autofocus", turn_on_data={"Focus": {"AutoFocusMode": "AUTO"}}, turn_off_data={"Focus": {"AutoFocusMode": "MANUAL"}}, turn_on_fn=lambda device: device.async_set_imaging_settings, @@ -51,8 +50,7 @@ SWITCHES: tuple[ONVIFSwitchEntityDescription, ...] = ( ), ONVIFSwitchEntityDescription( key="ir_lamp", - name="IR lamp", - icon="mdi:spotlight-beam", + translation_key="ir_lamp", turn_on_data={"IrCutFilter": "OFF"}, turn_off_data={"IrCutFilter": "ON"}, turn_on_fn=lambda device: device.async_set_imaging_settings, @@ -61,8 +59,7 @@ SWITCHES: tuple[ONVIFSwitchEntityDescription, ...] = ( ), ONVIFSwitchEntityDescription( key="wiper", - name="Wiper", - icon="mdi:wiper", + translation_key="wiper", turn_on_data="tt:Wiper|On", turn_off_data="tt:Wiper|Off", turn_on_fn=lambda device: device.async_run_aux_command, From 0f66292d35598be755db423fb9517cac98733f28 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 2 Mar 2024 12:42:00 +0100 Subject: [PATCH 0140/1691] Add icon translations to Netgear (#111972) --- homeassistant/components/netgear/icons.json | 113 ++++++++++++++++++++ homeassistant/components/netgear/sensor.py | 27 ----- homeassistant/components/netgear/switch.py | 8 -- 3 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 homeassistant/components/netgear/icons.json diff --git a/homeassistant/components/netgear/icons.json b/homeassistant/components/netgear/icons.json new file mode 100644 index 00000000000..c1688b56692 --- /dev/null +++ b/homeassistant/components/netgear/icons.json @@ -0,0 +1,113 @@ +{ + "entity": { + "sensor": { + "link_type": { + "default": "mdi:lan" + }, + "link_rate": { + "default": "mdi:speedometer" + }, + "signal_strength": { + "default": "mdi:wifi" + }, + "ssid": { + "default": "mdi:wifi-marker" + }, + "access_point_mac": { + "default": "mdi:router-network" + }, + "upload_today": { + "default": "mdi:upload" + }, + "download_today": { + "default": "mdi:download" + }, + "upload_yesterday": { + "default": "mdi:upload" + }, + "download_yesterday": { + "default": "mdi:download" + }, + "upload_week": { + "default": "mdi:upload" + }, + "upload_week_average": { + "default": "mdi:upload" + }, + "download_week": { + "default": "mdi:download" + }, + "download_week_average": { + "default": "mdi:download" + }, + "upload_month": { + "default": "mdi:upload" + }, + "upload_month_average": { + "default": "mdi:upload" + }, + "download_month": { + "default": "mdi:download" + }, + "download_month_average": { + "default": "mdi:download" + }, + "upload_last_month": { + "default": "mdi:upload" + }, + "upload_last_month_average": { + "default": "mdi:upload" + }, + "download_last_month": { + "default": "mdi:download" + }, + "download_last_month_average": { + "default": "mdi:download" + }, + "uplink_bandwidth": { + "default": "mdi:upload" + }, + "downlink_bandwidth": { + "default": "mdi:download" + }, + "average_ping": { + "default": "mdi:wan" + }, + "cpu_utilization": { + "default": "mdi:cpu-64-bit" + }, + "memory_utilization": { + "default": "mdi:memory" + }, + "ethernet_link_status": { + "default": "mdi:ethernet" + } + }, + "switch": { + "allowed_on_network": { + "default": "mdi:block-helper" + }, + "access_control": { + "default": "mdi:block-helper" + }, + "traffic_meter": { + "default": "mdi:wifi-arrow-up-down" + }, + "parental_control": { + "default": "mdi:account-child-outline" + }, + "quality_of_service": { + "default": "mdi:wifi-star" + }, + "2g_guest_wifi": { + "default": "mdi:wifi" + }, + "5g_guest_wifi": { + "default": "mdi:wifi" + }, + "smart_connect": { + "default": "mdi:wifi" + } + } + } +} diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index 897fe9da30c..00623938b46 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -46,33 +46,28 @@ SENSOR_TYPES = { key="type", translation_key="link_type", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:lan", ), "link_rate": SensorEntityDescription( key="link_rate", translation_key="link_rate", native_unit_of_measurement="Mbps", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:speedometer", ), "signal": SensorEntityDescription( key="signal", translation_key="signal_strength", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:wifi", ), "ssid": SensorEntityDescription( key="ssid", translation_key="ssid", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:wifi-marker", ), "conn_ap_mac": SensorEntityDescription( key="conn_ap_mac", translation_key="access_point_mac", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:router-network", ), } @@ -92,7 +87,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", ), NetgearSensorEntityDescription( key="NewTodayDownload", @@ -100,7 +94,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", ), NetgearSensorEntityDescription( key="NewYesterdayUpload", @@ -108,7 +101,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", ), NetgearSensorEntityDescription( key="NewYesterdayDownload", @@ -116,7 +108,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", ), NetgearSensorEntityDescription( key="NewWeekUpload", @@ -124,7 +115,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", index=0, value=lambda data: data[0], ), @@ -134,7 +124,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", index=1, value=lambda data: data[1], ), @@ -144,7 +133,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", index=0, value=lambda data: data[0], ), @@ -154,7 +142,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", index=1, value=lambda data: data[1], ), @@ -164,7 +151,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", index=0, value=lambda data: data[0], ), @@ -174,7 +160,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", index=1, value=lambda data: data[1], ), @@ -184,7 +169,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", index=0, value=lambda data: data[0], ), @@ -194,7 +178,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", index=1, value=lambda data: data[1], ), @@ -204,7 +187,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", index=0, value=lambda data: data[0], ), @@ -214,7 +196,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload", index=1, value=lambda data: data[1], ), @@ -224,7 +205,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", index=0, value=lambda data: data[0], ), @@ -234,7 +214,6 @@ SENSOR_TRAFFIC_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download", index=1, value=lambda data: data[1], ), @@ -247,7 +226,6 @@ SENSOR_SPEED_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND, device_class=SensorDeviceClass.DATA_RATE, - icon="mdi:upload", ), NetgearSensorEntityDescription( key="NewOOKLADownlinkBandwidth", @@ -255,14 +233,12 @@ SENSOR_SPEED_TYPES = [ entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND, device_class=SensorDeviceClass.DATA_RATE, - icon="mdi:download", ), NetgearSensorEntityDescription( key="AveragePing", translation_key="average_ping", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfTime.MILLISECONDS, - icon="mdi:wan", ), ] @@ -272,7 +248,6 @@ SENSOR_UTILIZATION = [ translation_key="cpu_utilization", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, - icon="mdi:cpu-64-bit", state_class=SensorStateClass.MEASUREMENT, ), NetgearSensorEntityDescription( @@ -280,7 +255,6 @@ SENSOR_UTILIZATION = [ translation_key="memory_utilization", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), ] @@ -290,7 +264,6 @@ SENSOR_LINK_TYPES = [ key="NewEthernetLinkStatus", translation_key="ethernet_link_status", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:ethernet", ), ] diff --git a/homeassistant/components/netgear/switch.py b/homeassistant/components/netgear/switch.py index 4be13a0f32c..9e5aab2c866 100644 --- a/homeassistant/components/netgear/switch.py +++ b/homeassistant/components/netgear/switch.py @@ -26,7 +26,6 @@ SWITCH_TYPES = [ SwitchEntityDescription( key="allow_or_block", translation_key="allowed_on_network", - icon="mdi:block-helper", entity_category=EntityCategory.CONFIG, ) ] @@ -51,7 +50,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="access_control", translation_key="access_control", - icon="mdi:block-helper", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_block_device_enable_status, action=lambda router: router.api.set_block_device_enable, @@ -59,7 +57,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="traffic_meter", translation_key="traffic_meter", - icon="mdi:wifi-arrow-up-down", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_traffic_meter_enabled, action=lambda router: router.api.enable_traffic_meter, @@ -67,7 +64,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="parental_control", translation_key="parental_control", - icon="mdi:account-child-outline", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_parental_control_enable_status, action=lambda router: router.api.enable_parental_control, @@ -75,7 +71,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="qos", translation_key="quality_of_service", - icon="mdi:wifi-star", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_qos_enable_status, action=lambda router: router.api.set_qos_enable_status, @@ -83,7 +78,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="2g_guest_wifi", translation_key="2g_guest_wifi", - icon="mdi:wifi", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_2g_guest_access_enabled, action=lambda router: router.api.set_2g_guest_access_enabled, @@ -91,7 +85,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="5g_guest_wifi", translation_key="5g_guest_wifi", - icon="mdi:wifi", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_5g_guest_access_enabled, action=lambda router: router.api.set_5g_guest_access_enabled, @@ -99,7 +92,6 @@ ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="smart_connect", translation_key="smart_connect", - icon="mdi:wifi", entity_category=EntityCategory.CONFIG, update=lambda router: router.api.get_smart_connect_enabled, action=lambda router: router.api.set_smart_connect_enabled, From 9478b7de29beef3d576a709d95213a20558edd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 2 Mar 2024 12:52:20 +0100 Subject: [PATCH 0141/1691] Fix config schema for velux (#112037) --- homeassistant/components/velux/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 9bc9c93e0d1..4c84eb687ad 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -22,8 +22,8 @@ CONFIG_SCHEMA = vol.Schema( } ) }, - extra=vol.ALLOW_EXTRA, - ) + ), + extra=vol.ALLOW_EXTRA, ) From 196089e8b7ce6fca49dfa6db9f29ae5ce7a4edbf Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sat, 2 Mar 2024 15:38:31 +0100 Subject: [PATCH 0142/1691] Remove deprecated `hass.components.hassio` usage (#111878) --- homeassistant/helpers/network.py | 13 +++++++++--- homeassistant/helpers/system_info.py | 10 +++++++--- tests/components/hassio/test_init.py | 8 +++++--- tests/helpers/test_network.py | 30 +++++++++++++++------------- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 58ca191feb0..df95c0834bc 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -40,7 +40,11 @@ def get_supervisor_network_url( hass: HomeAssistant, *, allow_ssl: bool = False ) -> str | None: """Get URL for home assistant within supervisor network.""" - if hass.config.api is None or not hass.components.hassio.is_hassio(): + # Local import to avoid circular dependencies + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.hassio import is_hassio + + if hass.config.api is None or not is_hassio(hass): return None scheme = "http" @@ -170,14 +174,17 @@ def get_url( and request_host is not None and hass.config.api is not None ): + # Local import to avoid circular dependencies + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.hassio import get_host_info, is_hassio + scheme = "https" if hass.config.api.use_ssl else "http" current_url = yarl.URL.build( scheme=scheme, host=request_host, port=hass.config.api.port ) known_hostnames = ["localhost"] - if hass.components.hassio.is_hassio(): - host_info = hass.components.hassio.get_host_info() + if is_hassio(hass) and (host_info := get_host_info(hass)): known_hostnames.extend( [host_info["hostname"], f"{host_info['hostname']}.local"] ) diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index 8af04c11c18..31097ec923e 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -30,7 +30,11 @@ cached_get_user = cache(getuser) @bind_hass async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]: """Return info about the system.""" - is_hassio = hass.components.hassio.is_hassio() + # Local import to avoid circular dependencies + # pylint: disable-next=import-outside-toplevel + from homeassistant.components import hassio + + is_hassio = hassio.is_hassio(hass) info_object = { "installation_type": "Unknown", @@ -68,11 +72,11 @@ async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]: # Enrich with Supervisor information if is_hassio: - if not (info := hass.components.hassio.get_info()): + if not (info := hassio.get_info(hass)): _LOGGER.warning("No Home Assistant Supervisor info available") info = {} - host = hass.components.hassio.get_host_info() or {} + host = hassio.get_host_info(hass) or {} info_object["supervisor"] = info.get("supervisor") info_object["host_os"] = host.get("operating_system") info_object["docker_version"] = info.get("docker") diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 7d0943b3677..a6f94152af0 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -15,7 +15,9 @@ from homeassistant.components.hassio import ( DOMAIN, STORAGE_KEY, async_get_addon_store_info, + get_core_info, hostname_from_addon_slug, + is_hassio, ) from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY from homeassistant.components.hassio.handler import HassioAPIError @@ -246,8 +248,8 @@ async def test_setup_api_ping( assert result assert aioclient_mock.call_count == 19 - assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0" - assert hass.components.hassio.is_hassio() + assert get_core_info(hass)["version_latest"] == "1.0.0" + assert is_hassio(hass) async def test_setup_api_panel( @@ -465,7 +467,7 @@ async def test_warn_when_cannot_connect( result = await async_setup_component(hass, "hassio", {}) assert result - assert hass.components.hassio.is_hassio() + assert is_hassio(hass) assert "Not connected with the supervisor / system too busy!" in caplog.text diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index b8fbfc9346b..37603a99a8b 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -565,6 +565,11 @@ async def test_get_request_host(hass: HomeAssistant) -> None: assert _get_request_host() == "example.com" +@patch("homeassistant.components.hassio.is_hassio", Mock(return_value=True)) +@patch( + "homeassistant.components.hassio.get_host_info", + Mock(return_value={"hostname": "homeassistant"}), +) async def test_get_current_request_url_with_known_host( hass: HomeAssistant, current_request ) -> None: @@ -595,10 +600,6 @@ async def test_get_current_request_url_with_known_host( # Ensure hostname from Supervisor is accepted transparently mock_component(hass, "hassio") - hass.components.hassio.is_hassio = Mock(return_value=True) - hass.components.hassio.get_host_info = Mock( - return_value={"hostname": "homeassistant"} - ) with patch( "homeassistant.helpers.network._get_request_host", @@ -623,6 +624,14 @@ async def test_get_current_request_url_with_known_host( get_url(hass, require_current_request=True) +@patch( + "homeassistant.components.hassio.is_hassio", + Mock(return_value={"hostname": "homeassistant"}), +) +@patch( + "homeassistant.components.hassio.get_host_info", + Mock(return_value={"hostname": "hellohost"}), +) async def test_is_internal_request(hass: HomeAssistant, mock_current_request) -> None: """Test if accessing an instance on its internal URL.""" # Test with internal URL: http://example.local:8123 @@ -662,16 +671,9 @@ async def test_is_internal_request(hass: HomeAssistant, mock_current_request) -> assert is_internal_request(hass), mock_current_request.return_value.url # Test for matching against HassOS hostname - with patch.object( - hass.components.hassio, "is_hassio", return_value=True - ), patch.object( - hass.components.hassio, - "get_host_info", - return_value={"hostname": "hellohost"}, - ): - for allowed in ("hellohost", "hellohost.local"): - mock_current_request.return_value = Mock(url=f"http://{allowed}:8123") - assert is_internal_request(hass), mock_current_request.return_value.url + for allowed in ("hellohost", "hellohost.local"): + mock_current_request.return_value = Mock(url=f"http://{allowed}:8123") + assert is_internal_request(hass), mock_current_request.return_value.url async def test_is_hass_url(hass: HomeAssistant) -> None: From ece5587e1f57548e7441d907042d7640b3bd23ac Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 2 Mar 2024 17:32:51 +0100 Subject: [PATCH 0143/1691] Streamline naming in the Axis integration (#112044) * Rename device.py to hub.py * Rename AxisNetworkDevice to AxisHub * Move hub.py into hub package * Rename get_axis_device to get_axis_api * Split out get_axis_api to its own module * Rename device object to hub * Rename device to api in config flow * Convenience method to get hub --- homeassistant/components/axis/__init__.py | 20 +++--- .../components/axis/binary_sensor.py | 35 +++++----- homeassistant/components/axis/camera.py | 42 ++++++------ homeassistant/components/axis/config_flow.py | 18 ++--- homeassistant/components/axis/diagnostics.py | 19 +++--- homeassistant/components/axis/entity.py | 22 +++---- homeassistant/components/axis/hub/__init__.py | 4 ++ homeassistant/components/axis/hub/api.py | 53 +++++++++++++++ .../components/axis/{device.py => hub/hub.py} | 65 +++++-------------- homeassistant/components/axis/light.py | 36 ++++------ homeassistant/components/axis/switch.py | 21 +++--- tests/components/axis/test_config_flow.py | 4 +- tests/components/axis/test_device.py | 14 ++-- tests/components/axis/test_init.py | 2 +- 14 files changed, 183 insertions(+), 172 deletions(-) create mode 100644 homeassistant/components/axis/hub/__init__.py create mode 100644 homeassistant/components/axis/hub/api.py rename homeassistant/components/axis/{device.py => hub/hub.py} (82%) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index ae5ffcbdb7a..9fda3aad84d 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -7,8 +7,8 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DOMAIN as AXIS_DOMAIN, PLATFORMS -from .device import AxisNetworkDevice, get_axis_device from .errors import AuthenticationRequired, CannotConnect +from .hub import AxisHub, get_axis_api _LOGGER = logging.getLogger(__name__) @@ -18,21 +18,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b hass.data.setdefault(AXIS_DOMAIN, {}) try: - api = await get_axis_device(hass, config_entry.data) + api = await get_axis_api(hass, config_entry.data) except CannotConnect as err: raise ConfigEntryNotReady from err except AuthenticationRequired as err: raise ConfigEntryAuthFailed from err - device = AxisNetworkDevice(hass, config_entry, api) - hass.data[AXIS_DOMAIN][config_entry.entry_id] = device - await device.async_update_device_registry() + hub = AxisHub(hass, config_entry, api) + hass.data[AXIS_DOMAIN][config_entry.entry_id] = hub + await hub.async_update_device_registry() await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - device.async_setup_events() + hub.async_setup_events() - config_entry.add_update_listener(device.async_new_address_callback) + config_entry.add_update_listener(hub.async_new_address_callback) config_entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.shutdown) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, hub.shutdown) ) return True @@ -40,8 +40,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload Axis device config entry.""" - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN].pop(config_entry.entry_id) - return await device.async_reset() + hub: AxisHub = hass.data[AXIS_DOMAIN].pop(config_entry.entry_id) + return await hub.async_reset() async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 8e7cda335e6..8b39a8b42b5 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -19,9 +19,8 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later -from .const import DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice from .entity import AxisEventEntity +from .hub import AxisHub DEVICE_CLASS = { EventGroup.INPUT: BinarySensorDeviceClass.CONNECTIVITY, @@ -52,14 +51,14 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up a Axis binary sensor.""" - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id] + hub = AxisHub.get_hub(hass, config_entry) @callback def async_create_entity(event: Event) -> None: """Create Axis binary sensor entity.""" - async_add_entities([AxisBinarySensor(event, device)]) + async_add_entities([AxisBinarySensor(event, hub)]) - device.api.event.subscribe( + hub.api.event.subscribe( async_create_entity, topic_filter=EVENT_TOPICS, operation_filter=EventOperation.INITIALIZED, @@ -69,9 +68,9 @@ async def async_setup_entry( class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): """Representation of a binary Axis event.""" - def __init__(self, event: Event, device: AxisNetworkDevice) -> None: + def __init__(self, event: Event, hub: AxisHub) -> None: """Initialize the Axis binary sensor.""" - super().__init__(event, device) + super().__init__(event, hub) self.cancel_scheduled_update: Callable[[], None] | None = None self._attr_device_class = DEVICE_CLASS.get(event.group) @@ -94,13 +93,13 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): self.cancel_scheduled_update() self.cancel_scheduled_update = None - if self.is_on or self.device.option_trigger_time == 0: + if self.is_on or self.hub.option_trigger_time == 0: self.async_write_ha_state() return self.cancel_scheduled_update = async_call_later( self.hass, - timedelta(seconds=self.device.option_trigger_time), + timedelta(seconds=self.hub.option_trigger_time), scheduled_update, ) @@ -109,21 +108,21 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): """Set binary sensor name.""" if ( event.group == EventGroup.INPUT - and event.id in self.device.api.vapix.ports - and self.device.api.vapix.ports[event.id].name + and event.id in self.hub.api.vapix.ports + and self.hub.api.vapix.ports[event.id].name ): - self._attr_name = self.device.api.vapix.ports[event.id].name + self._attr_name = self.hub.api.vapix.ports[event.id].name elif event.group == EventGroup.MOTION: event_data: FenceGuardHandler | LoiteringGuardHandler | MotionGuardHandler | Vmd4Handler | None = None if event.topic_base == EventTopic.FENCE_GUARD: - event_data = self.device.api.vapix.fence_guard + event_data = self.hub.api.vapix.fence_guard elif event.topic_base == EventTopic.LOITERING_GUARD: - event_data = self.device.api.vapix.loitering_guard + event_data = self.hub.api.vapix.loitering_guard elif event.topic_base == EventTopic.MOTION_GUARD: - event_data = self.device.api.vapix.motion_guard + event_data = self.hub.api.vapix.motion_guard elif event.topic_base == EventTopic.MOTION_DETECTION_4: - event_data = self.device.api.vapix.vmd4 + event_data = self.hub.api.vapix.vmd4 if ( event_data and event_data.initialized @@ -137,8 +136,8 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): if ( event.topic_base == EventTopic.OBJECT_ANALYTICS - and self.device.api.vapix.object_analytics.initialized - and (scenarios := self.device.api.vapix.object_analytics["0"].scenarios) + and self.hub.api.vapix.object_analytics.initialized + and (scenarios := self.hub.api.vapix.object_analytics["0"].scenarios) ): for scenario_id, scenario in scenarios.items(): device_id = scenario.devices[0]["id"] diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index a0c71f101ca..7c93449ec0b 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -9,9 +9,9 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DEFAULT_STREAM_PROFILE, DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice +from .const import DEFAULT_STREAM_PROFILE, DEFAULT_VIDEO_SOURCE from .entity import AxisEntity +from .hub import AxisHub async def async_setup_entry( @@ -22,15 +22,15 @@ async def async_setup_entry( """Set up the Axis camera video stream.""" filter_urllib3_logging() - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id] + hub = AxisHub.get_hub(hass, config_entry) if ( - not (prop := device.api.vapix.params.property_handler.get("0")) + not (prop := hub.api.vapix.params.property_handler.get("0")) or not prop.image_format ): return - async_add_entities([AxisCamera(device)]) + async_add_entities([AxisCamera(hub)]) class AxisCamera(AxisEntity, MjpegCamera): @@ -42,27 +42,27 @@ class AxisCamera(AxisEntity, MjpegCamera): _mjpeg_url: str _stream_source: str - def __init__(self, device: AxisNetworkDevice) -> None: + def __init__(self, hub: AxisHub) -> None: """Initialize Axis Communications camera component.""" - AxisEntity.__init__(self, device) + AxisEntity.__init__(self, hub) self._generate_sources() MjpegCamera.__init__( self, - username=device.username, - password=device.password, + username=hub.username, + password=hub.password, mjpeg_url=self.mjpeg_source, still_image_url=self.image_source, authentication=HTTP_DIGEST_AUTHENTICATION, - unique_id=f"{device.unique_id}-camera", + unique_id=f"{hub.unique_id}-camera", ) async def async_added_to_hass(self) -> None: """Subscribe camera events.""" self.async_on_remove( async_dispatcher_connect( - self.hass, self.device.signal_new_address, self._generate_sources + self.hass, self.hub.signal_new_address, self._generate_sources ) ) @@ -75,27 +75,27 @@ class AxisCamera(AxisEntity, MjpegCamera): """ image_options = self.generate_options(skip_stream_profile=True) self._still_image_url = ( - f"http://{self.device.host}:{self.device.port}/axis-cgi" + f"http://{self.hub.host}:{self.hub.port}/axis-cgi" f"/jpg/image.cgi{image_options}" ) mjpeg_options = self.generate_options() self._mjpeg_url = ( - f"http://{self.device.host}:{self.device.port}/axis-cgi" + f"http://{self.hub.host}:{self.hub.port}/axis-cgi" f"/mjpg/video.cgi{mjpeg_options}" ) stream_options = self.generate_options(add_video_codec_h264=True) self._stream_source = ( - f"rtsp://{self.device.username}:{self.device.password}" - f"@{self.device.host}/axis-media/media.amp{stream_options}" + f"rtsp://{self.hub.username}:{self.hub.password}" + f"@{self.hub.host}/axis-media/media.amp{stream_options}" ) - self.device.additional_diagnostics["camera_sources"] = { + self.hub.additional_diagnostics["camera_sources"] = { "Image": self._still_image_url, "MJPEG": self._mjpeg_url, "Stream": ( - f"rtsp://user:pass@{self.device.host}/axis-media" + f"rtsp://user:pass@{self.hub.host}/axis-media" f"/media.amp{stream_options}" ), } @@ -125,12 +125,12 @@ class AxisCamera(AxisEntity, MjpegCamera): if ( not skip_stream_profile - and self.device.option_stream_profile != DEFAULT_STREAM_PROFILE + and self.hub.option_stream_profile != DEFAULT_STREAM_PROFILE ): - options_dict["streamprofile"] = self.device.option_stream_profile + options_dict["streamprofile"] = self.hub.option_stream_profile - if self.device.option_video_source != DEFAULT_VIDEO_SOURCE: - options_dict["camera"] = self.device.option_video_source + if self.hub.option_video_source != DEFAULT_VIDEO_SOURCE: + options_dict["camera"] = self.hub.option_video_source if not options_dict: return "" diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index f2b6ece3c97..f2dd6eac62a 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -37,8 +37,8 @@ from .const import ( DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN, ) -from .device import AxisNetworkDevice, get_axis_device from .errors import AuthenticationRequired, CannotConnect +from .hub import AxisHub, get_axis_api AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"} DEFAULT_PORT = 80 @@ -71,9 +71,9 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): if user_input is not None: try: - device = await get_axis_device(self.hass, MappingProxyType(user_input)) + api = await get_axis_api(self.hass, MappingProxyType(user_input)) - serial = device.vapix.serial_number + serial = api.vapix.serial_number await self.async_set_unique_id(format_mac(serial)) self._abort_if_unique_id_configured( @@ -90,7 +90,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): CONF_PORT: user_input[CONF_PORT], CONF_USERNAME: user_input[CONF_USERNAME], CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_MODEL: device.vapix.product_number, + CONF_MODEL: api.vapix.product_number, } return await self._create_entry(serial) @@ -238,13 +238,13 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle Axis device options.""" - device: AxisNetworkDevice + hub: AxisHub async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Manage the Axis device options.""" - self.device = self.hass.data[AXIS_DOMAIN][self.config_entry.entry_id] + self.hub = AxisHub.get_hub(self.hass, self.config_entry) return await self.async_step_configure_stream() async def async_step_configure_stream( @@ -257,7 +257,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): schema = {} - vapix = self.device.api.vapix + vapix = self.hub.api.vapix # Stream profiles @@ -271,7 +271,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): schema[ vol.Optional( - CONF_STREAM_PROFILE, default=self.device.option_stream_profile + CONF_STREAM_PROFILE, default=self.hub.option_stream_profile ) ] = vol.In(stream_profiles) @@ -290,7 +290,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): video_sources[int(idx) + 1] = video_source.name schema[ - vol.Optional(CONF_VIDEO_SOURCE, default=self.device.option_video_source) + vol.Optional(CONF_VIDEO_SOURCE, default=self.hub.option_video_source) ] = vol.In(video_sources) return self.async_show_form( diff --git a/homeassistant/components/axis/diagnostics.py b/homeassistant/components/axis/diagnostics.py index 948a36a78a0..2c93cac9b11 100644 --- a/homeassistant/components/axis/diagnostics.py +++ b/homeassistant/components/axis/diagnostics.py @@ -8,8 +8,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MAC, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME from homeassistant.core import HomeAssistant -from .const import DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice +from .hub import AxisHub REDACT_CONFIG = {CONF_MAC, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME} REDACT_BASIC_DEVICE_INFO = {"SerialNumber", "SocSerialNumber"} @@ -20,26 +19,26 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id] - diag: dict[str, Any] = device.additional_diagnostics.copy() + hub = AxisHub.get_hub(hass, config_entry) + diag: dict[str, Any] = hub.additional_diagnostics.copy() diag["config"] = async_redact_data(config_entry.as_dict(), REDACT_CONFIG) - if device.api.vapix.api_discovery: + if hub.api.vapix.api_discovery: diag["api_discovery"] = [ {"id": api.id, "name": api.name, "version": api.version} - for api in device.api.vapix.api_discovery.values() + for api in hub.api.vapix.api_discovery.values() ] - if device.api.vapix.basic_device_info: + if hub.api.vapix.basic_device_info: diag["basic_device_info"] = async_redact_data( - device.api.vapix.basic_device_info["0"], + hub.api.vapix.basic_device_info["0"], REDACT_BASIC_DEVICE_INFO, ) - if device.api.vapix.params: + if hub.api.vapix.params: diag["params"] = async_redact_data( - device.api.vapix.params.items(), + hub.api.vapix.params.items(), REDACT_VAPIX_PARAMS, ) diff --git a/homeassistant/components/axis/entity.py b/homeassistant/components/axis/entity.py index 81f0b1678fb..ec827d1bd49 100644 --- a/homeassistant/components/axis/entity.py +++ b/homeassistant/components/axis/entity.py @@ -10,7 +10,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice +from .hub import AxisHub TOPIC_TO_EVENT_TYPE = { EventTopic.DAY_NIGHT_VISION: "DayNight", @@ -37,13 +37,13 @@ class AxisEntity(Entity): _attr_has_entity_name = True - def __init__(self, device: AxisNetworkDevice) -> None: + def __init__(self, hub: AxisHub) -> None: """Initialize the Axis event.""" - self.device = device + self.hub = hub self._attr_device_info = DeviceInfo( - identifiers={(AXIS_DOMAIN, device.unique_id)}, # type: ignore[arg-type] - serial_number=device.unique_id, + identifiers={(AXIS_DOMAIN, hub.unique_id)}, # type: ignore[arg-type] + serial_number=hub.unique_id, ) async def async_added_to_hass(self) -> None: @@ -51,7 +51,7 @@ class AxisEntity(Entity): self.async_on_remove( async_dispatcher_connect( self.hass, - self.device.signal_reachable, + self.hub.signal_reachable, self.async_signal_reachable_callback, ) ) @@ -59,7 +59,7 @@ class AxisEntity(Entity): @callback def async_signal_reachable_callback(self) -> None: """Call when device connection state change.""" - self._attr_available = self.device.available + self._attr_available = self.hub.available self.async_write_ha_state() @@ -68,16 +68,16 @@ class AxisEventEntity(AxisEntity): _attr_should_poll = False - def __init__(self, event: Event, device: AxisNetworkDevice) -> None: + def __init__(self, event: Event, hub: AxisHub) -> None: """Initialize the Axis event.""" - super().__init__(device) + super().__init__(hub) self._event_id = event.id self._event_topic = event.topic_base self._event_type = TOPIC_TO_EVENT_TYPE[event.topic_base] self._attr_name = f"{self._event_type} {event.id}" - self._attr_unique_id = f"{device.unique_id}-{event.topic}-{event.id}" + self._attr_unique_id = f"{hub.unique_id}-{event.topic}-{event.id}" self._attr_device_class = event.group.value @@ -90,7 +90,7 @@ class AxisEventEntity(AxisEntity): """Subscribe sensors events.""" await super().async_added_to_hass() self.async_on_remove( - self.device.api.event.subscribe( + self.hub.api.event.subscribe( self.async_event_callback, id_filter=self._event_id, topic_filter=self._event_topic, diff --git a/homeassistant/components/axis/hub/__init__.py b/homeassistant/components/axis/hub/__init__.py new file mode 100644 index 00000000000..e68f902b628 --- /dev/null +++ b/homeassistant/components/axis/hub/__init__.py @@ -0,0 +1,4 @@ +"""Internal functionality not part of HA infrastructure.""" + +from .api import get_axis_api # noqa: F401 +from .hub import AxisHub # noqa: F401 diff --git a/homeassistant/components/axis/hub/api.py b/homeassistant/components/axis/hub/api.py new file mode 100644 index 00000000000..e29219edbc2 --- /dev/null +++ b/homeassistant/components/axis/hub/api.py @@ -0,0 +1,53 @@ +"""Axis network device abstraction.""" + +from asyncio import timeout +from types import MappingProxyType +from typing import Any + +import axis +from axis.configuration import Configuration + +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.httpx_client import get_async_client + +from ..const import LOGGER +from ..errors import AuthenticationRequired, CannotConnect + + +async def get_axis_api( + hass: HomeAssistant, + config: MappingProxyType[str, Any], +) -> axis.AxisDevice: + """Create a Axis device API.""" + session = get_async_client(hass, verify_ssl=False) + + device = axis.AxisDevice( + Configuration( + session, + config[CONF_HOST], + port=config[CONF_PORT], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + ) + ) + + try: + async with timeout(30): + await device.vapix.initialize() + + return device + + except axis.Unauthorized as err: + LOGGER.warning( + "Connected to device at %s but not registered", config[CONF_HOST] + ) + raise AuthenticationRequired from err + + except (TimeoutError, axis.RequestError) as err: + LOGGER.error("Error connecting to the Axis device at %s", config[CONF_HOST]) + raise CannotConnect from err + + except axis.AxisException as err: + LOGGER.exception("Unknown Axis communication error occurred") + raise AuthenticationRequired from err diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/hub/hub.py similarity index 82% rename from homeassistant/components/axis/device.py rename to homeassistant/components/axis/hub/hub.py index 845487b79d7..b81d3498255 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/hub/hub.py @@ -1,11 +1,10 @@ """Axis network device abstraction.""" -from asyncio import timeout -from types import MappingProxyType +from __future__ import annotations + from typing import Any import axis -from axis.configuration import Configuration from axis.errors import Unauthorized from axis.stream_manager import Signal, State from axis.vapix.interfaces.mqtt import mqtt_json_to_event @@ -28,10 +27,9 @@ from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.httpx_client import get_async_client from homeassistant.setup import async_when_setup -from .const import ( +from ..const import ( ATTR_MANUFACTURER, CONF_EVENTS, CONF_STREAM_PROFILE, @@ -41,13 +39,11 @@ from .const import ( DEFAULT_TRIGGER_TIME, DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN, - LOGGER, PLATFORMS, ) -from .errors import AuthenticationRequired, CannotConnect -class AxisNetworkDevice: +class AxisHub: """Manages a Axis device.""" def __init__( @@ -64,6 +60,13 @@ class AxisNetworkDevice: self.additional_diagnostics: dict[str, Any] = {} + @callback + @staticmethod + def get_hub(hass: HomeAssistant, config_entry: ConfigEntry) -> AxisHub: + """Get Axis hub from config entry.""" + hub: AxisHub = hass.data[AXIS_DOMAIN][config_entry.entry_id] + return hub + @property def host(self) -> str: """Return the host address of this device.""" @@ -157,7 +160,7 @@ class AxisNetworkDevice: @staticmethod async def async_new_address_callback( - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, config_entry: ConfigEntry ) -> None: """Handle signals of device getting new address. @@ -165,9 +168,9 @@ class AxisNetworkDevice: This is a static method because a class method (bound method), cannot be used with weak references. """ - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][entry.entry_id] - device.api.config.host = device.host - async_dispatcher_send(hass, device.signal_new_address) + hub = AxisHub.get_hub(hass, config_entry) + hub.api.config.host = hub.host + async_dispatcher_send(hass, hub.signal_new_address) async def async_update_device_registry(self) -> None: """Update device registry.""" @@ -237,41 +240,3 @@ class AxisNetworkDevice: return await self.hass.config_entries.async_unload_platforms( self.config_entry, PLATFORMS ) - - -async def get_axis_device( - hass: HomeAssistant, - config: MappingProxyType[str, Any], -) -> axis.AxisDevice: - """Create a Axis device.""" - session = get_async_client(hass, verify_ssl=False) - - device = axis.AxisDevice( - Configuration( - session, - config[CONF_HOST], - port=config[CONF_PORT], - username=config[CONF_USERNAME], - password=config[CONF_PASSWORD], - ) - ) - - try: - async with timeout(30): - await device.vapix.initialize() - - return device - - except axis.Unauthorized as err: - LOGGER.warning( - "Connected to device at %s but not registered", config[CONF_HOST] - ) - raise AuthenticationRequired from err - - except (TimeoutError, axis.RequestError) as err: - LOGGER.error("Error connecting to the Axis device at %s", config[CONF_HOST]) - raise CannotConnect from err - - except axis.AxisException as err: - LOGGER.exception("Unknown Axis communication error occurred") - raise AuthenticationRequired from err diff --git a/homeassistant/components/axis/light.py b/homeassistant/components/axis/light.py index cebd2f1206b..8606335a5b4 100644 --- a/homeassistant/components/axis/light.py +++ b/homeassistant/components/axis/light.py @@ -8,9 +8,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice from .entity import AxisEventEntity +from .hub import AxisHub async def async_setup_entry( @@ -19,20 +18,17 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up a Axis light.""" - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id] + hub = AxisHub.get_hub(hass, config_entry) - if ( - device.api.vapix.light_control is None - or len(device.api.vapix.light_control) == 0 - ): + if hub.api.vapix.light_control is None or len(hub.api.vapix.light_control) == 0: return @callback def async_create_entity(event: Event) -> None: """Create Axis light entity.""" - async_add_entities([AxisLight(event, device)]) + async_add_entities([AxisLight(event, hub)]) - device.api.event.subscribe( + hub.api.event.subscribe( async_create_entity, topic_filter=EventTopic.LIGHT_STATUS, operation_filter=EventOperation.INITIALIZED, @@ -44,16 +40,16 @@ class AxisLight(AxisEventEntity, LightEntity): _attr_should_poll = True - def __init__(self, event: Event, device: AxisNetworkDevice) -> None: + def __init__(self, event: Event, hub: AxisHub) -> None: """Initialize the Axis light.""" - super().__init__(event, device) + super().__init__(event, hub) self._light_id = f"led{event.id}" self.current_intensity = 0 self.max_intensity = 0 - light_type = device.api.vapix.light_control[self._light_id].light_type + light_type = hub.api.vapix.light_control[self._light_id].light_type self._attr_name = f"{light_type} {self._event_type} {event.id}" self._attr_is_on = event.is_tripped @@ -65,13 +61,11 @@ class AxisLight(AxisEventEntity, LightEntity): await super().async_added_to_hass() current_intensity = ( - await self.device.api.vapix.light_control.get_current_intensity( - self._light_id - ) + await self.hub.api.vapix.light_control.get_current_intensity(self._light_id) ) self.current_intensity = current_intensity - max_intensity = await self.device.api.vapix.light_control.get_valid_intensity( + max_intensity = await self.hub.api.vapix.light_control.get_valid_intensity( self._light_id ) self.max_intensity = max_intensity.high @@ -90,24 +84,22 @@ class AxisLight(AxisEventEntity, LightEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on light.""" if not self.is_on: - await self.device.api.vapix.light_control.activate_light(self._light_id) + await self.hub.api.vapix.light_control.activate_light(self._light_id) if ATTR_BRIGHTNESS in kwargs: intensity = int((kwargs[ATTR_BRIGHTNESS] / 255) * self.max_intensity) - await self.device.api.vapix.light_control.set_manual_intensity( + await self.hub.api.vapix.light_control.set_manual_intensity( self._light_id, intensity ) async def async_turn_off(self, **kwargs: Any) -> None: """Turn off light.""" if self.is_on: - await self.device.api.vapix.light_control.deactivate_light(self._light_id) + await self.hub.api.vapix.light_control.deactivate_light(self._light_id) async def async_update(self) -> None: """Update brightness.""" current_intensity = ( - await self.device.api.vapix.light_control.get_current_intensity( - self._light_id - ) + await self.hub.api.vapix.light_control.get_current_intensity(self._light_id) ) self.current_intensity = current_intensity diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index c495dfbdc43..6d3448fca67 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -8,9 +8,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice from .entity import AxisEventEntity +from .hub import AxisHub async def async_setup_entry( @@ -19,14 +18,14 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up a Axis switch.""" - device: AxisNetworkDevice = hass.data[AXIS_DOMAIN][config_entry.entry_id] + hub = AxisHub.get_hub(hass, config_entry) @callback def async_create_entity(event: Event) -> None: """Create Axis switch entity.""" - async_add_entities([AxisSwitch(event, device)]) + async_add_entities([AxisSwitch(event, hub)]) - device.api.event.subscribe( + hub.api.event.subscribe( async_create_entity, topic_filter=EventTopic.RELAY, operation_filter=EventOperation.INITIALIZED, @@ -36,11 +35,11 @@ async def async_setup_entry( class AxisSwitch(AxisEventEntity, SwitchEntity): """Representation of a Axis switch.""" - def __init__(self, event: Event, device: AxisNetworkDevice) -> None: + def __init__(self, event: Event, hub: AxisHub) -> None: """Initialize the Axis switch.""" - super().__init__(event, device) - if event.id and device.api.vapix.ports[event.id].name: - self._attr_name = device.api.vapix.ports[event.id].name + super().__init__(event, hub) + if event.id and hub.api.vapix.ports[event.id].name: + self._attr_name = hub.api.vapix.ports[event.id].name self._attr_is_on = event.is_tripped @callback @@ -51,8 +50,8 @@ class AxisSwitch(AxisEventEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on switch.""" - await self.device.api.vapix.ports.close(self._event_id) + await self.hub.api.vapix.ports.close(self._event_id) async def async_turn_off(self, **kwargs: Any) -> None: """Turn off switch.""" - await self.device.api.vapix.ports.open(self._event_id) + await self.hub.api.vapix.ports.open(self._event_id) diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index a37b0ccd12d..e570c1ecee8 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -124,7 +124,7 @@ async def test_flow_fails_faulty_credentials(hass: HomeAssistant) -> None: assert result["step_id"] == "user" with patch( - "homeassistant.components.axis.config_flow.get_axis_device", + "homeassistant.components.axis.config_flow.get_axis_api", side_effect=config_flow.AuthenticationRequired, ): result = await hass.config_entries.flow.async_configure( @@ -150,7 +150,7 @@ async def test_flow_fails_cannot_connect(hass: HomeAssistant) -> None: assert result["step_id"] == "user" with patch( - "homeassistant.components.axis.config_flow.get_axis_device", + "homeassistant.components.axis.config_flow.get_axis_api", side_effect=config_flow.CannotConnect, ): result = await hass.config_entries.flow.async_configure( diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 0672abbb46b..9912b30f9c7 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -182,7 +182,7 @@ async def test_device_not_accessible( hass: HomeAssistant, config_entry, setup_default_vapix_requests ) -> None: """Failed setup schedules a retry of setup.""" - with patch.object(axis, "get_axis_device", side_effect=axis.errors.CannotConnect): + with patch.object(axis, "get_axis_api", side_effect=axis.errors.CannotConnect): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.data[AXIS_DOMAIN] == {} @@ -193,7 +193,7 @@ async def test_device_trigger_reauth_flow( ) -> None: """Failed authentication trigger a reauthentication flow.""" with patch.object( - axis, "get_axis_device", side_effect=axis.errors.AuthenticationRequired + axis, "get_axis_api", side_effect=axis.errors.AuthenticationRequired ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -205,7 +205,7 @@ async def test_device_unknown_error( hass: HomeAssistant, config_entry, setup_default_vapix_requests ) -> None: """Unknown errors are handled.""" - with patch.object(axis, "get_axis_device", side_effect=Exception): + with patch.object(axis, "get_axis_api", side_effect=Exception): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert hass.data[AXIS_DOMAIN] == {} @@ -217,7 +217,7 @@ async def test_shutdown(config) -> None: entry = Mock() entry.data = config - axis_device = axis.device.AxisNetworkDevice(hass, entry, Mock()) + axis_device = axis.hub.AxisHub(hass, entry, Mock()) await axis_device.shutdown(None) @@ -229,7 +229,7 @@ async def test_get_device_fails(hass: HomeAssistant, config) -> None: with patch( "axis.vapix.vapix.Vapix.initialize", side_effect=axislib.Unauthorized ), pytest.raises(axis.errors.AuthenticationRequired): - await axis.device.get_axis_device(hass, config) + await axis.hub.get_axis_api(hass, config) async def test_get_device_device_unavailable(hass: HomeAssistant, config) -> None: @@ -237,7 +237,7 @@ async def test_get_device_device_unavailable(hass: HomeAssistant, config) -> Non with patch( "axis.vapix.vapix.Vapix.request", side_effect=axislib.RequestError ), pytest.raises(axis.errors.CannotConnect): - await axis.device.get_axis_device(hass, config) + await axis.hub.get_axis_api(hass, config) async def test_get_device_unknown_error(hass: HomeAssistant, config) -> None: @@ -245,4 +245,4 @@ async def test_get_device_unknown_error(hass: HomeAssistant, config) -> None: with patch( "axis.vapix.vapix.Vapix.request", side_effect=axislib.AxisException ), pytest.raises(axis.errors.AuthenticationRequired): - await axis.device.get_axis_device(hass, config) + await axis.hub.get_axis_api(hass, config) diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index 28cfff17ed3..5482e3c5223 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -18,7 +18,7 @@ async def test_setup_entry_fails(hass: HomeAssistant, config_entry) -> None: mock_device = Mock() mock_device.async_setup = AsyncMock(return_value=False) - with patch.object(axis, "AxisNetworkDevice") as mock_device_class: + with patch.object(axis, "AxisHub") as mock_device_class: mock_device_class.return_value = mock_device assert not await hass.config_entries.async_setup(config_entry.entry_id) From e691e45017f1a0cf261b9424dc28c97e801a6c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 2 Mar 2024 17:48:27 +0100 Subject: [PATCH 0144/1691] Use description key instead of name for Tibber RT unique ID (#112035) * Use translation key instead of name for Tibber RT unique ID * migration * use decription.key instead --- homeassistant/components/tibber/sensor.py | 77 ++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index c6e1bdc1895..a2bd8d26f75 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -60,6 +60,35 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) PARALLEL_UPDATES = 0 +RT_SENSORS_UNIQUE_ID_MIGRATION = { + "accumulated_consumption_last_hour": "accumulated consumption current hour", + "accumulated_production_last_hour": "accumulated production current hour", + "current_l1": "current L1", + "current_l2": "current L2", + "current_l3": "current L3", + "estimated_hour_consumption": "Estimated consumption current hour", +} + +RT_SENSORS_UNIQUE_ID_MIGRATION_SIMPLE = { + # simple migration can be done by replacing " " with "_" + "accumulated_consumption", + "accumulated_cost", + "accumulated_production", + "accumulated_reward", + "average_power", + "last_meter_consumption", + "last_meter_production", + "max_power", + "min_power", + "power_factor", + "power_production", + "signal_strength", + "voltage_phase1", + "voltage_phase2", + "voltage_phase3", +} + + RT_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="averagePower", @@ -454,7 +483,7 @@ class TibberSensorRT(TibberSensor, CoordinatorEntity["TibberRtDataCoordinator"]) self._device_name = f"{self._model} {self._home_name}" self._attr_native_value = initial_state - self._attr_unique_id = f"{self._tibber_home.home_id}_rt_{description.name}" + self._attr_unique_id = f"{self._tibber_home.home_id}_rt_{description.key}" if description.key in ("accumulatedCost", "accumulatedReward"): self._attr_native_unit_of_measurement = tibber_home.currency @@ -523,6 +552,7 @@ class TibberRtDataCoordinator(DataUpdateCoordinator): # pylint: disable=hass-en self._async_remove_device_updates_handler = self.async_add_listener( self._add_sensors ) + self.entity_registry = async_get_entity_reg(hass) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop) @callback @@ -530,6 +560,49 @@ class TibberRtDataCoordinator(DataUpdateCoordinator): # pylint: disable=hass-en """Handle Home Assistant stopping.""" self._async_remove_device_updates_handler() + @callback + def _migrate_unique_id(self, sensor_description: SensorEntityDescription) -> None: + """Migrate unique id if needed.""" + home_id = self._tibber_home.home_id + translation_key = sensor_description.translation_key + description_key = sensor_description.key + entity_id: str | None = None + if translation_key in RT_SENSORS_UNIQUE_ID_MIGRATION_SIMPLE: + entity_id = self.entity_registry.async_get_entity_id( + "sensor", + TIBBER_DOMAIN, + f"{home_id}_rt_{translation_key.replace('_', ' ')}", + ) + elif translation_key in RT_SENSORS_UNIQUE_ID_MIGRATION: + entity_id = self.entity_registry.async_get_entity_id( + "sensor", + TIBBER_DOMAIN, + f"{home_id}_rt_{RT_SENSORS_UNIQUE_ID_MIGRATION[translation_key]}", + ) + elif translation_key != description_key: + entity_id = self.entity_registry.async_get_entity_id( + "sensor", + TIBBER_DOMAIN, + f"{home_id}_rt_{translation_key}", + ) + + if entity_id is None: + return + + new_unique_id = f"{home_id}_rt_{description_key}" + + _LOGGER.debug( + "Migrating unique id for %s to %s", + entity_id, + new_unique_id, + ) + try: + self.entity_registry.async_update_entity( + entity_id, new_unique_id=new_unique_id + ) + except ValueError as err: + _LOGGER.error(err) + @callback def _add_sensors(self) -> None: """Add sensor.""" @@ -543,6 +616,8 @@ class TibberRtDataCoordinator(DataUpdateCoordinator): # pylint: disable=hass-en state = live_measurement.get(sensor_description.key) if state is None: continue + + self._migrate_unique_id(sensor_description) entity = TibberSensorRT( self._tibber_home, sensor_description, From 61e28291be19d5f46cf14668b5ba14f6e96ac940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Sat, 2 Mar 2024 19:14:00 +0100 Subject: [PATCH 0145/1691] Update aioairzone-cloud to v0.4.5 (#112034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/airzone_cloud/__init__.py | 1 + homeassistant/components/airzone_cloud/config_flow.py | 1 + homeassistant/components/airzone_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/airzone_cloud/test_coordinator.py | 3 +++ 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airzone_cloud/__init__.py b/homeassistant/components/airzone_cloud/__init__.py index 7e787ef4c69..697b80942f2 100644 --- a/homeassistant/components/airzone_cloud/__init__.py +++ b/homeassistant/components/airzone_cloud/__init__.py @@ -24,6 +24,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: options = ConnectionOptions( entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], + True, ) airzone = AirzoneCloudApi(aiohttp_client.async_get_clientsession(hass), options) diff --git a/homeassistant/components/airzone_cloud/config_flow.py b/homeassistant/components/airzone_cloud/config_flow.py index e523ba23cbe..486c1dfa8b6 100644 --- a/homeassistant/components/airzone_cloud/config_flow.py +++ b/homeassistant/components/airzone_cloud/config_flow.py @@ -93,6 +93,7 @@ class AirZoneCloudConfigFlow(ConfigFlow, domain=DOMAIN): ConnectionOptions( user_input[CONF_USERNAME], user_input[CONF_PASSWORD], + False, ), ) diff --git a/homeassistant/components/airzone_cloud/manifest.json b/homeassistant/components/airzone_cloud/manifest.json index f8b740dc04d..3b8247d003c 100644 --- a/homeassistant/components/airzone_cloud/manifest.json +++ b/homeassistant/components/airzone_cloud/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/airzone_cloud", "iot_class": "cloud_polling", "loggers": ["aioairzone_cloud"], - "requirements": ["aioairzone-cloud==0.3.8"] + "requirements": ["aioairzone-cloud==0.4.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 4bff6b5d276..6684dd41ad9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -188,7 +188,7 @@ aio-georss-gdacs==0.9 aioairq==0.3.2 # homeassistant.components.airzone_cloud -aioairzone-cloud==0.3.8 +aioairzone-cloud==0.4.5 # homeassistant.components.airzone aioairzone==0.7.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eef8c4d19d1..8067b2e4806 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -167,7 +167,7 @@ aio-georss-gdacs==0.9 aioairq==0.3.2 # homeassistant.components.airzone_cloud -aioairzone-cloud==0.3.8 +aioairzone-cloud==0.4.5 # homeassistant.components.airzone aioairzone==0.7.5 diff --git a/tests/components/airzone_cloud/test_coordinator.py b/tests/components/airzone_cloud/test_coordinator.py index 40b6c937ed2..a2307b94335 100644 --- a/tests/components/airzone_cloud/test_coordinator.py +++ b/tests/components/airzone_cloud/test_coordinator.py @@ -46,6 +46,9 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None: ) as mock_webserver, patch( "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", return_value=None, + ), patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi._update_websockets", + return_value=False, ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() From 14b737e9b893194baf8ab6a5d6a2f2ba4eb39732 Mon Sep 17 00:00:00 2001 From: Jeef Date: Sat, 2 Mar 2024 11:15:21 -0700 Subject: [PATCH 0146/1691] Bump weatherflow4py to v0.1.12 (#112040) Backing lib bump --- homeassistant/components/weatherflow_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/weatherflow_cloud/manifest.json b/homeassistant/components/weatherflow_cloud/manifest.json index 2dd4e9ddcd1..6abbeef02df 100644 --- a/homeassistant/components/weatherflow_cloud/manifest.json +++ b/homeassistant/components/weatherflow_cloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "iot_class": "cloud_polling", - "requirements": ["weatherflow4py==0.1.11"] + "requirements": ["weatherflow4py==0.1.12"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6684dd41ad9..f95249f61f3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2836,7 +2836,7 @@ watchdog==2.3.1 waterfurnace==1.1.0 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.11 +weatherflow4py==0.1.12 # homeassistant.components.webmin webmin-xmlrpc==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8067b2e4806..942bfeb3b3f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2174,7 +2174,7 @@ wallbox==0.6.0 watchdog==2.3.1 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.11 +weatherflow4py==0.1.12 # homeassistant.components.webmin webmin-xmlrpc==0.0.1 From 567d4d5926c7eeecafdbb4e40e310ae00d68bc9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 09:01:11 -1000 Subject: [PATCH 0147/1691] Simplify load_executor check in loader (#112029) --- homeassistant/loader.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 02696d6beb5..8b3316b1a7f 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -844,13 +844,11 @@ class Integration: if debug := _LOGGER.isEnabledFor(logging.DEBUG): start = time.perf_counter() domain = self.domain - load_executor = ( - self.import_executor - and f"hass.components.{domain}" not in sys.modules - and f"custom_components.{domain}" not in sys.modules - ) # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. + load_executor = ( + self.import_executor and f"{self.pkg_path}.{domain}" not in sys.modules + ) if load_executor: try: comp = await self.hass.async_add_import_executor_job(self.get_component) @@ -918,8 +916,7 @@ class Integration: load_executor = ( self.import_executor and domain not in self.hass.config.components - and f"hass.components.{domain}" not in sys.modules - and f"custom_components.{domain}" not in sys.modules + and f"{self.pkg_path}.{domain}" not in sys.modules ) try: if load_executor: From ee69a3ea8a05afeb69ef96cd8e5f787a056764d8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 10:14:04 -1000 Subject: [PATCH 0148/1691] Import co2signal in the executor to avoid blocking the event loop (#112058) --- homeassistant/components/co2signal/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/co2signal/manifest.json b/homeassistant/components/co2signal/manifest.json index ff6d5bdb18b..980a3c4e20a 100644 --- a/homeassistant/components/co2signal/manifest.json +++ b/homeassistant/components/co2signal/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@jpbede", "@VIKTORVAV99"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/co2signal", + "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["aioelectricitymaps"], From fd07d83c09667948929cbf6c3db02c637784520b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 10:16:57 -1000 Subject: [PATCH 0149/1691] Import ambient_station in the executor to avoid blocking the event loop (#112053) --- homeassistant/components/ambient_station/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/ambient_station/manifest.json b/homeassistant/components/ambient_station/manifest.json index 046ab9f73e9..a656b297a1c 100644 --- a/homeassistant/components/ambient_station/manifest.json +++ b/homeassistant/components/ambient_station/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@bachya"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ambient_station", + "import_executor": true, "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["aioambient"], From dca6104b4bd471530864cba51b8acc1037b40732 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 10:38:45 -1000 Subject: [PATCH 0150/1691] Bump unifi-discovery to 1.1.8 (#112056) --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 9c0c6c5767a..eba2b934e05 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -42,7 +42,7 @@ "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], "quality_scale": "platinum", - "requirements": ["pyunifiprotect==4.23.3", "unifi-discovery==1.1.7"], + "requirements": ["pyunifiprotect==4.23.3", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index f95249f61f3..ff14eea4d87 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2758,7 +2758,7 @@ uasiren==0.0.1 ultraheat-api==0.5.7 # homeassistant.components.unifiprotect -unifi-discovery==1.1.7 +unifi-discovery==1.1.8 # homeassistant.components.unifi_direct unifi_ap==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 942bfeb3b3f..f7ae8a096aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2111,7 +2111,7 @@ uasiren==0.0.1 ultraheat-api==0.5.7 # homeassistant.components.unifiprotect -unifi-discovery==1.1.7 +unifi-discovery==1.1.8 # homeassistant.components.zha universal-silabs-flasher==0.0.18 From 546fc1e2828de1753eae3ad15362ccf915229a72 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 10:58:08 -1000 Subject: [PATCH 0151/1691] Refactor file_upload to avoid janus dep (#112032) --- .../components/file_upload/__init__.py | 54 +++++++++++-------- .../components/file_upload/manifest.json | 3 +- homeassistant/package_constraints.txt | 1 - requirements_all.txt | 3 -- requirements_test_all.txt | 3 -- tests/components/file_upload/test_init.py | 39 ++++++++++++++ tests/test_requirements.py | 2 +- 7 files changed, 72 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/file_upload/__init__.py b/homeassistant/components/file_upload/__init__.py index 8c594f7f85c..3a76bc8ea5e 100644 --- a/homeassistant/components/file_upload/__init__.py +++ b/homeassistant/components/file_upload/__init__.py @@ -6,11 +6,11 @@ from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass from pathlib import Path +from queue import SimpleQueue import shutil import tempfile from aiohttp import BodyPartReader, web -import janus import voluptuous as vol from homeassistant.components.http import HomeAssistantView @@ -131,16 +131,17 @@ class FileUploadView(HomeAssistantView): reader = await request.multipart() file_field_reader = await reader.next() + filename: str | None if ( not isinstance(file_field_reader, BodyPartReader) or file_field_reader.name != "file" - or file_field_reader.filename is None + or (filename := file_field_reader.filename) is None ): raise vol.Invalid("Expected a file") try: - raise_if_invalid_filename(file_field_reader.filename) + raise_if_invalid_filename(filename) except ValueError as err: raise web.HTTPBadRequest from err @@ -152,39 +153,46 @@ class FileUploadView(HomeAssistantView): file_upload_data: FileUploadData = hass.data[DOMAIN] file_dir = file_upload_data.file_dir(file_id) - queue: janus.Queue[bytes | None] = janus.Queue() + queue: SimpleQueue[ + tuple[bytes, asyncio.Future[None] | None] | None + ] = SimpleQueue() - def _sync_queue_consumer( - sync_q: janus.SyncQueue[bytes | None], _file_name: str - ) -> None: + def _sync_queue_consumer() -> None: file_dir.mkdir() - with (file_dir / _file_name).open("wb") as file_handle: + with (file_dir / filename).open("wb") as file_handle: while True: - _chunk = sync_q.get() - if _chunk is None: + if (_chunk_future := queue.get()) is None: break - + _chunk, _future = _chunk_future + if _future is not None: + hass.loop.call_soon_threadsafe(_future.set_result, None) file_handle.write(_chunk) - sync_q.task_done() fut: asyncio.Future[None] | None = None try: - fut = hass.async_add_executor_job( - _sync_queue_consumer, - queue.sync_q, - file_field_reader.filename, - ) - + fut = hass.async_add_executor_job(_sync_queue_consumer) + megabytes_sending = 0 while chunk := await file_field_reader.read_chunk(ONE_MEGABYTE): - queue.async_q.put_nowait(chunk) - if queue.async_q.qsize() > 5: # Allow up to 5 MB buffer size - await queue.async_q.join() - queue.async_q.put_nowait(None) # terminate queue consumer + megabytes_sending += 1 + if megabytes_sending % 5 != 0: + queue.put_nowait((chunk, None)) + continue + + chunk_future = hass.loop.create_future() + queue.put_nowait((chunk, chunk_future)) + await asyncio.wait( + (fut, chunk_future), return_when=asyncio.FIRST_COMPLETED + ) + if fut.done(): + # The executor job failed + break + + queue.put_nowait(None) # terminate queue consumer finally: if fut is not None: await fut - file_upload_data.files[file_id] = file_field_reader.filename + file_upload_data.files[file_id] = filename return self.json({"file_id": file_id}) diff --git a/homeassistant/components/file_upload/manifest.json b/homeassistant/components/file_upload/manifest.json index 4b4af917bd9..7e5be2db980 100644 --- a/homeassistant/components/file_upload/manifest.json +++ b/homeassistant/components/file_upload/manifest.json @@ -5,6 +5,5 @@ "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/file_upload", "integration_type": "system", - "quality_scale": "internal", - "requirements": ["janus==1.0.0"] + "quality_scale": "internal" } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c80da6b5aad..547ed5a129a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -34,7 +34,6 @@ home-assistant-frontend==20240301.0 home-assistant-intents==2024.2.28 httpx==0.27.0 ifaddr==0.2.0 -janus==1.0.0 Jinja2==3.1.3 lru-dict==1.3.0 mutagen==1.47.0 diff --git a/requirements_all.txt b/requirements_all.txt index ff14eea4d87..0cc5eda75bd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1156,9 +1156,6 @@ iperf3==0.1.11 # homeassistant.components.gogogate2 ismartgate==5.0.1 -# homeassistant.components.file_upload -janus==1.0.0 - # homeassistant.components.abode jaraco.abode==3.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f7ae8a096aa..c79313bf77f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -934,9 +934,6 @@ intellifire4py==2.2.2 # homeassistant.components.gogogate2 ismartgate==5.0.1 -# homeassistant.components.file_upload -janus==1.0.0 - # homeassistant.components.abode jaraco.abode==3.3.0 diff --git a/tests/components/file_upload/test_init.py b/tests/components/file_upload/test_init.py index e9dfbdb4bd6..920565cb68f 100644 --- a/tests/components/file_upload/test_init.py +++ b/tests/components/file_upload/test_init.py @@ -1,4 +1,5 @@ """Test the File Upload integration.""" +from contextlib import contextmanager from pathlib import Path from random import getrandbits from unittest.mock import patch @@ -117,3 +118,41 @@ async def test_upload_with_wrong_key_fails( res = await client.post("/api/file_upload", data={"wrong_key": large_file_io}) assert res.status == 400 + + +async def test_upload_large_file_fails( + hass: HomeAssistant, hass_client: ClientSessionGenerator, large_file_io +) -> None: + """Test uploading large file.""" + assert await async_setup_component(hass, "file_upload", {}) + client = await hass_client() + + @contextmanager + def _mock_open(*args, **kwargs): + yield MockPathOpen() + + class MockPathOpen: + def __init__(self, *args, **kwargs) -> None: + pass + + def write(self, data: bytes) -> None: + raise OSError("Boom") + + with patch( + # Patch temp dir name to avoid tests fail running in parallel + "homeassistant.components.file_upload.TEMP_DIR_NAME", + file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", + ), patch( + # Patch one megabyte to 8 bytes to prevent having to use big files in tests + "homeassistant.components.file_upload.ONE_MEGABYTE", + 8, + ), patch( + "homeassistant.components.file_upload.Path.open", return_value=_mock_open() + ): + res = await client.post("/api/file_upload", data={"file": large_file_io}) + + assert res.status == 500 + + response = await res.content.read() + + assert b"Boom" in response diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 383ac9c012c..228ab2df2b4 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -548,7 +548,7 @@ async def test_discovery_requirements_mqtt(hass: HomeAssistant) -> None: ) as mock_process: await async_get_integration_with_requirements(hass, "mqtt_comp") - assert len(mock_process.mock_calls) == 3 # mqtt also depends on http + assert len(mock_process.mock_calls) == 2 # mqtt also depends on http assert mock_process.mock_calls[0][1][1] == mqtt.requirements From 85ec48ff31a06931b48790bcc6f82c0ca5a99e96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 11:08:22 -1000 Subject: [PATCH 0152/1691] Import anonymize_data in unifiprotect init to avoid it being imported in the event loop (#112052) Improve anonymize_data in unifiprotect init to avoid it being imported in the event loop --- homeassistant/components/unifiprotect/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 94bd3722cfa..942c533b59e 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -7,6 +7,11 @@ import logging from aiohttp.client_exceptions import ServerDisconnectedError from pyunifiprotect.exceptions import ClientError, NotAuthorized +# Import the test_util.anonymize module from the pyunifiprotect package +# in __init__ to ensure it gets imported in the executor since the +# diagnostics module will not be imported in the executor. +from pyunifiprotect.test_util.anonymize import anonymize_data # noqa: F401 + from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant From b734a9ffc86c6ec5e4b0d9bc7aae0360d36927ef Mon Sep 17 00:00:00 2001 From: Isak Nyberg <36712644+IsakNyberg@users.noreply.github.com> Date: Sat, 2 Mar 2024 22:50:24 +0100 Subject: [PATCH 0153/1691] Add device class for permobil record distance sensor (#112062) fix record_distance device_class --- homeassistant/components/permobil/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/permobil/sensor.py b/homeassistant/components/permobil/sensor.py index d01be68d775..8814a3de83d 100644 --- a/homeassistant/components/permobil/sensor.py +++ b/homeassistant/components/permobil/sensor.py @@ -177,6 +177,7 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( key="record_distance", translation_key="record_distance", icon="mdi:map-marker-distance", + device_class=SensorDeviceClass.DISTANCE, state_class=SensorStateClass.TOTAL_INCREASING, ), ) From ec4331fc19dd6239ea951d2c276274375ccbd3ce Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sun, 3 Mar 2024 00:11:22 +0200 Subject: [PATCH 0154/1691] Bump bthome-ble to 3.6.0 (#112060) * Bump bthome-ble to 3.6.0 * Fix discovery info typing --- homeassistant/components/bthome/config_flow.py | 10 +++++----- homeassistant/components/bthome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/bthome/config_flow.py b/homeassistant/components/bthome/config_flow.py index 48b24f1229a..c26e1aae4e9 100644 --- a/homeassistant/components/bthome/config_flow.py +++ b/homeassistant/components/bthome/config_flow.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant.components import onboarding from homeassistant.components.bluetooth import ( - BluetoothServiceInfo, + BluetoothServiceInfoBleak, async_discovered_service_info, ) from homeassistant.config_entries import ConfigFlow, ConfigFlowResult @@ -25,11 +25,11 @@ class Discovery: """A discovered bluetooth device.""" title: str - discovery_info: BluetoothServiceInfo + discovery_info: BluetoothServiceInfoBleak device: DeviceData -def _title(discovery_info: BluetoothServiceInfo, device: DeviceData) -> str: +def _title(discovery_info: BluetoothServiceInfoBleak, device: DeviceData) -> str: return device.title or device.get_device_name() or discovery_info.name @@ -40,12 +40,12 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfo | None = None + self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, Discovery] = {} async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> ConfigFlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index 2a7cf84f16b..a3e974bf71e 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -20,5 +20,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/bthome", "iot_class": "local_push", - "requirements": ["bthome-ble==3.5.0"] + "requirements": ["bthome-ble==3.6.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0cc5eda75bd..ff74086b136 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -621,7 +621,7 @@ brunt==1.2.0 bt-proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==3.5.0 +bthome-ble==3.6.0 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c79313bf77f..aeb7b466345 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -529,7 +529,7 @@ brottsplatskartan==1.0.5 brunt==1.2.0 # homeassistant.components.bthome -bthome-ble==3.5.0 +bthome-ble==3.6.0 # homeassistant.components.buienradar buienradar==1.0.5 From dd1ad711667e64ca139046736dca914cb681a618 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 2 Mar 2024 17:18:34 -0500 Subject: [PATCH 0155/1691] Only load camera prefs once (#112064) --- homeassistant/components/camera/__init__.py | 1 + homeassistant/components/camera/prefs.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 5ac981e9d93..ff4687dd493 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -391,6 +391,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) prefs = CameraPreferences(hass) + await prefs.async_load() hass.data[DATA_CAMERA_PREFS] = prefs hass.http.register_view(CameraImageView(component)) diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py index 160f896c86c..7f3f142378a 100644 --- a/homeassistant/components/camera/prefs.py +++ b/homeassistant/components/camera/prefs.py @@ -29,6 +29,8 @@ class DynamicStreamSettings: class CameraPreferences: """Handle camera preferences.""" + _preload_prefs: dict[str, dict[str, bool | Orientation]] + def __init__(self, hass: HomeAssistant) -> None: """Initialize camera prefs.""" self._hass = hass @@ -41,6 +43,10 @@ class CameraPreferences: str, DynamicStreamSettings ] = {} + async def async_load(self) -> None: + """Initialize the camera preferences.""" + self._preload_prefs = await self._store.async_load() or {} + async def async_update( self, entity_id: str, @@ -63,9 +69,8 @@ class CameraPreferences: if preload_stream is not UNDEFINED: if dynamic_stream_settings: dynamic_stream_settings.preload_stream = preload_stream - preload_prefs = await self._store.async_load() or {} - preload_prefs[entity_id] = {PREF_PRELOAD_STREAM: preload_stream} - await self._store.async_save(preload_prefs) + self._preload_prefs[entity_id] = {PREF_PRELOAD_STREAM: preload_stream} + await self._store.async_save(self._preload_prefs) if orientation is not UNDEFINED: if (registry := er.async_get(self._hass)).async_get(entity_id): @@ -91,10 +96,10 @@ class CameraPreferences: # Get orientation setting from entity registry reg_entry = er.async_get(self._hass).async_get(entity_id) er_prefs: Mapping = reg_entry.options.get(DOMAIN, {}) if reg_entry else {} - preload_prefs = await self._store.async_load() or {} settings = DynamicStreamSettings( preload_stream=cast( - bool, preload_prefs.get(entity_id, {}).get(PREF_PRELOAD_STREAM, False) + bool, + self._preload_prefs.get(entity_id, {}).get(PREF_PRELOAD_STREAM, False), ), orientation=er_prefs.get(PREF_ORIENTATION, Orientation.NO_TRANSFORM), ) From 0ff210658962ee7f7bad12a4760044bacdec592d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 12:19:09 -1000 Subject: [PATCH 0156/1691] Import snmp in the executor to avoid blocking the event loop (#112065) Do not mark for backport to 2024.3 as we are no longer backporting new ones --- homeassistant/components/snmp/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index dd9a2f5270a..beacf55dda9 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -3,6 +3,7 @@ "name": "SNMP", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/snmp", + "import_executor": true, "iot_class": "local_polling", "loggers": ["pyasn1", "pysmi", "pysnmp"], "requirements": ["pysnmp-lextudio==6.0.2"] From 9e428c6c5fc393dae97c7cc10fe834e993502b4c Mon Sep 17 00:00:00 2001 From: elmurato <1382097+elmurato@users.noreply.github.com> Date: Sat, 2 Mar 2024 23:28:27 +0100 Subject: [PATCH 0157/1691] Fix setup failure due to temporary DNS issue in Minecraft Server (#112068) Change ConfigEntryError to ConfigEntryNotReady on failed init --- homeassistant/components/minecraft_server/__init__.py | 6 ++---- tests/components/minecraft_server/test_init.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 6c854750baa..2cd6c51546a 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -14,7 +14,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryError +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.device_registry as dr import homeassistant.helpers.entity_registry as er @@ -41,9 +41,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await api.async_initialize() except MinecraftServerAddressError as error: - raise ConfigEntryError( - f"Server address in configuration entry is invalid: {error}" - ) from error + raise ConfigEntryNotReady(f"Initialization failed: {error}") from error # Create coordinator instance. coordinator = MinecraftServerCoordinator(hass, entry.data[CONF_NAME], api) diff --git a/tests/components/minecraft_server/test_init.py b/tests/components/minecraft_server/test_init.py index 5b0d9509d69..3d554bf1a55 100644 --- a/tests/components/minecraft_server/test_init.py +++ b/tests/components/minecraft_server/test_init.py @@ -153,7 +153,7 @@ async def test_setup_entry_lookup_failure( ) await hass.async_block_till_done() - assert java_mock_config_entry.state == ConfigEntryState.SETUP_ERROR + assert java_mock_config_entry.state == ConfigEntryState.SETUP_RETRY async def test_setup_entry_init_failure( From 08897137ff4ec1d27b77444505ef8af5a2b9bf7f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 13:44:06 -1000 Subject: [PATCH 0158/1691] Pre-import more frontend deps to avoid importing when the event loop is running (#112031) --- homeassistant/bootstrap.py | 13 +++++++++-- tests/test_bootstrap.py | 46 +++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 4fc9073b146..db86d24c667 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -22,16 +22,25 @@ import yarl from . import config as conf_util, config_entries, core, loader, requirements -# Pre-import config and lovelace which have no requirements here to avoid +# Pre-import frontend deps which have no requirements here to avoid # loading them at run time and blocking the event loop. We do this ahead -# of time so that we do not have to flag frontends deps with `import_executor` +# of time so that we do not have to flag frontend deps with `import_executor` # as it would create a thundering heard of executor jobs trying to import # frontend deps at the same time. from .components import ( api as api_pre_import, # noqa: F401 + auth as auth_pre_import, # noqa: F401 config as config_pre_import, # noqa: F401 + device_automation as device_automation_pre_import, # noqa: F401 + diagnostics as diagnostics_pre_import, # noqa: F401 + file_upload as file_upload_pre_import, # noqa: F401 http, lovelace as lovelace_pre_import, # noqa: F401 + onboarding as onboarding_pre_import, # noqa: F401 + repairs as repairs_pre_import, # noqa: F401 + search as search_pre_import, # noqa: F401 + system_log as system_log_pre_import, # noqa: F401 + websocket_api as websocket_api_pre_import, # noqa: F401 ) from .const import ( FORMAT_DATETIME, diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 2fbce03c307..17eaa7aef66 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -3,12 +3,13 @@ import asyncio from collections.abc import Generator, Iterable import glob import os +import sys from typing import Any from unittest.mock import AsyncMock, Mock, patch import pytest -from homeassistant import bootstrap, runner +from homeassistant import bootstrap, loader, runner import homeassistant.config as config_util from homeassistant.config_entries import HANDLERS, ConfigEntry from homeassistant.const import SIGNAL_BOOTSTRAP_INTEGRATIONS @@ -1023,3 +1024,46 @@ async def test_bootstrap_dependencies( f"Dependency {integration} will wait for dependencies dict_keys(['mqtt'])" in caplog.text ) + + +async def test_frontend_deps_pre_import_no_requirements(hass: HomeAssistant) -> None: + """Test frontend dependencies are pre-imported and do not have any requirements.""" + pre_imports = [ + name.removesuffix("_pre_import") + for name in dir(bootstrap) + if name.endswith("_pre_import") + ] + + # Make sure future refactoring does not + # accidentally remove the pre-imports + # or change the naming convention without + # updating this test. + assert len(pre_imports) > 3 + + for pre_import in pre_imports: + integration = await loader.async_get_integration(hass, pre_import) + assert not integration.requirements + + +async def test_bootstrap_does_not_preload_stage_1_integrations() -> None: + """Test that the bootstrap does not preload stage 1 integrations. + + If this test fails it means that stage1 integrations are being + loaded too soon and will not get their requirements updated + before they are loaded at runtime. + """ + + process = await asyncio.create_subprocess_exec( + sys.executable, + "-c", + "import homeassistant.bootstrap; import sys; print(sys.modules)", + stdout=asyncio.subprocess.PIPE, + ) + stdout, _ = await process.communicate() + assert process.returncode == 0 + decoded_stdout = stdout.decode() + + # Ensure no stage1 integrations have been imported + # as a side effect of importing the pre-imports + for integration in bootstrap.STAGE_1_INTEGRATIONS: + assert f"homeassistant.components.{integration}" not in decoded_stdout From 3808e8b0bc7051e73c31e472eb1824bb738cb92c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 13:56:25 -1000 Subject: [PATCH 0159/1691] Switch config to use async_get_component/async_get_platform (#112071) --- homeassistant/config.py | 6 +++--- tests/test_config.py | 38 ++++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 3e593a564a2..d52346e9299 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1418,7 +1418,7 @@ async def async_process_component_config( # noqa: C901 config_exceptions: list[ConfigExceptionInfo] = [] try: - component = integration.get_component() + component = await integration.async_get_component() except LOAD_EXCEPTIONS as exc: exc_info = ConfigExceptionInfo( exc, @@ -1433,7 +1433,7 @@ async def async_process_component_config( # noqa: C901 # Check if the integration has a custom config validator config_validator = None try: - config_validator = integration.get_platform("config") + config_validator = await integration.async_get_platform("config") except ImportError as err: # Filter out import error of the config platform. # If the config platform contains bad imports, make sure @@ -1557,7 +1557,7 @@ async def async_process_component_config( # noqa: C901 continue try: - platform = p_integration.get_platform(domain) + platform = await p_integration.async_get_platform(domain) except LOAD_EXCEPTIONS as exc: exc_info = ConfigExceptionInfo( exc, diff --git a/tests/test_config.py b/tests/test_config.py index 36d4351afb4..5726c515dc3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1430,7 +1430,8 @@ async def test_component_config_exceptions( # Config validator test_integration = Mock( domain="test_domain", - get_platform=Mock( + async_get_component=AsyncMock(), + async_get_platform=AsyncMock( return_value=Mock( async_validate_config=AsyncMock(side_effect=ValueError("broken")) ) @@ -1455,14 +1456,14 @@ async def test_component_config_exceptions( test_integration = Mock( domain="test_domain", - get_platform=Mock( + async_get_platform=AsyncMock( return_value=Mock( async_validate_config=AsyncMock( side_effect=HomeAssistantError("broken") ) ) ), - get_component=Mock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), + async_get_component=AsyncMock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), ) caplog.clear() assert ( @@ -1482,8 +1483,8 @@ async def test_component_config_exceptions( caplog.clear() test_integration = Mock( domain="test_domain", - get_platform=Mock(return_value=None), - get_component=Mock( + async_get_platform=AsyncMock(return_value=None), + async_get_component=AsyncMock( return_value=Mock(CONFIG_SCHEMA=Mock(side_effect=ValueError("broken"))) ), ) @@ -1511,8 +1512,8 @@ async def test_component_config_exceptions( caplog.clear() test_integration = Mock( domain="test_domain", - get_platform=Mock(return_value=None), - get_component=Mock( + async_get_platform=AsyncMock(return_value=None), + async_get_component=AsyncMock( return_value=Mock( spec=["PLATFORM_SCHEMA_BASE"], PLATFORM_SCHEMA_BASE=Mock(side_effect=ValueError("broken")), @@ -1551,13 +1552,13 @@ async def test_component_config_exceptions( caplog.clear() test_integration = Mock( domain="test_domain", - get_platform=Mock(return_value=None), - get_component=Mock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), + async_get_platform=AsyncMock(return_value=None), + async_get_component=AsyncMock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), ) with patch( "homeassistant.config.async_get_integration_with_requirements", return_value=Mock( # integration that owns platform - get_platform=Mock( + async_get_platform=AsyncMock( return_value=Mock( # platform PLATFORM_SCHEMA=Mock(side_effect=ValueError("broken")) ) @@ -1640,12 +1641,12 @@ async def test_component_config_exceptions( "for test_domain component with PLATFORM_SCHEMA" ) in caplog.text - # get_platform("domain") raising on ImportError + # async_get_platform("domain") raising on ImportError caplog.clear() test_integration = Mock( domain="test_domain", - get_platform=Mock(return_value=None), - get_component=Mock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), + async_get_platform=AsyncMock(return_value=None), + async_get_component=AsyncMock(return_value=Mock(spec=["PLATFORM_SCHEMA_BASE"])), ) import_error = ImportError( ("ModuleNotFoundError: No module named 'not_installed_something'"), @@ -1654,7 +1655,7 @@ async def test_component_config_exceptions( with patch( "homeassistant.config.async_get_integration_with_requirements", return_value=Mock( # integration that owns platform - get_platform=Mock(side_effect=import_error) + async_get_platform=AsyncMock(side_effect=import_error) ), ): assert await config_util.async_process_component_and_handle_errors( @@ -1688,12 +1689,13 @@ async def test_component_config_exceptions( "No module named 'not_installed_something'" ) in str(ex.value) - # get_platform("config") raising + # async_get_platform("config") raising caplog.clear() test_integration = Mock( pkg_path="homeassistant.components.test_domain", domain="test_domain", - get_platform=Mock( + async_get_component=AsyncMock(), + async_get_platform=AsyncMock( side_effect=ImportError( ("ModuleNotFoundError: No module named 'not_installed_something'"), name="not_installed_something", @@ -1729,12 +1731,12 @@ async def test_component_config_exceptions( "No module named 'not_installed_something'" in str(ex.value) ) - # get_component raising + # async_get_component raising caplog.clear() test_integration = Mock( pkg_path="homeassistant.components.test_domain", domain="test_domain", - get_component=Mock( + async_get_component=AsyncMock( side_effect=FileNotFoundError("No such file or directory: b'liblibc.a'") ), ) From 9353ad069032b2703171deec0acc5e521f1f5e3a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 14:18:03 -1000 Subject: [PATCH 0160/1691] Import switchbot in the executor to avoid blocking the event loop (#112077) --- homeassistant/components/switchbot/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 401d85e7376..2233adf35b7 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -37,6 +37,7 @@ "config_flow": true, "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/switchbot", + "import_executor": true, "iot_class": "local_push", "loggers": ["switchbot"], "requirements": ["PySwitchbot==0.45.0"] From 6421a08ba852cc38d77b923bb93bbbb1a26bfa71 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:50:35 -1000 Subject: [PATCH 0161/1691] Avoid expensive inspect calls in config validators (#112085) * Avoid expensive inspect calls in config validators inspect has a performance problem https://github.com/python/cpython/issues/92041 We now avoid calling inspect unless we are going to log * remove unused * reduce * get_integration_logger --- homeassistant/helpers/config_validation.py | 49 +++++-------------- homeassistant/helpers/frame.py | 20 ++++++++ tests/helpers/test_config_validation.py | 41 ++++++++-------- tests/helpers/test_frame.py | 43 ++++++++++++++++ .../test_integration_frame/__init__.py | 7 +++ 5 files changed, 101 insertions(+), 59 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index b9ff5704246..59e4f09d26f 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -10,7 +10,6 @@ from datetime import ( timedelta, ) from enum import Enum, StrEnum -import inspect import logging from numbers import Number import os @@ -103,6 +102,7 @@ import homeassistant.util.dt as dt_util from homeassistant.util.yaml.objects import NodeStrClass from . import script_variables as script_variables_helper, template as template_helper +from .frame import get_integration_logger TIME_PERIOD_ERROR = "offset {} should be format 'HH:MM', 'HH:MM:SS' or 'HH:MM:SS.F'" @@ -890,24 +890,17 @@ def _deprecated_or_removed( - No warning if neither key nor replacement_key are provided - Adds replacement_key with default value in this case """ - module = inspect.getmodule(inspect.stack(context=0)[2].frame) - if module is not None: - module_name = module.__name__ - else: - # If Python is unable to access the sources files, the call stack frame - # will be missing information, so let's guard. - # https://github.com/home-assistant/core/issues/24982 - module_name = __name__ - if option_removed: - logger_func = logging.getLogger(module_name).error - option_status = "has been removed" - else: - logger_func = logging.getLogger(module_name).warning - option_status = "is deprecated" def validator(config: dict) -> dict: """Check if key is in config and log warning or error.""" if key in config: + if option_removed: + level = logging.ERROR + option_status = "has been removed" + else: + level = logging.WARNING + option_status = "is deprecated" + try: near = ( f"near {config.__config_file__}" # type: ignore[attr-defined] @@ -928,7 +921,7 @@ def _deprecated_or_removed( if raise_if_present: raise vol.Invalid(warning % arguments) - logger_func(warning, *arguments) + get_integration_logger(__name__).log(level, warning, *arguments) value = config[key] if replacement_key or option_removed: config.pop(key) @@ -1112,19 +1105,9 @@ def expand_condition_shorthand(value: Any | None) -> Any: def empty_config_schema(domain: str) -> Callable[[dict], dict]: """Return a config schema which logs if there are configuration parameters.""" - module = inspect.getmodule(inspect.stack(context=0)[2].frame) - if module is not None: - module_name = module.__name__ - else: - # If Python is unable to access the sources files, the call stack frame - # will be missing information, so let's guard. - # https://github.com/home-assistant/core/issues/24982 - module_name = __name__ - logger_func = logging.getLogger(module_name).error - def validator(config: dict) -> dict: if domain in config and config[domain]: - logger_func( + get_integration_logger(__name__).error( ( "The %s integration does not support any configuration parameters, " "got %s. Please remove the configuration parameters from your " @@ -1146,16 +1129,6 @@ def _no_yaml_config_schema( ) -> Callable[[dict], dict]: """Return a config schema which logs if attempted to setup from YAML.""" - module = inspect.getmodule(inspect.stack(context=0)[2].frame) - if module is not None: - module_name = module.__name__ - else: - # If Python is unable to access the sources files, the call stack frame - # will be missing information, so let's guard. - # https://github.com/home-assistant/core/issues/24982 - module_name = __name__ - logger_func = logging.getLogger(module_name).error - def raise_issue() -> None: # pylint: disable-next=import-outside-toplevel from .issue_registry import IssueSeverity, async_create_issue @@ -1176,7 +1149,7 @@ def _no_yaml_config_schema( def validator(config: dict) -> dict: if domain in config: - logger_func( + get_integration_logger(__name__).error( ( "The %s integration does not support YAML setup, please remove it " "from your configuration file" diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 9c17e17a2c6..04f16ebddd0 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -34,6 +34,26 @@ class IntegrationFrame: relative_filename: str +def get_integration_logger(fallback_name: str) -> logging.Logger: + """Return a logger by checking the current integration frame. + + If Python is unable to access the sources files, the call stack frame + will be missing information, so let's guard by requiring a fallback name. + https://github.com/home-assistant/core/issues/24982 + """ + try: + integration_frame = get_integration_frame() + except MissingIntegrationFrame: + return logging.getLogger(fallback_name) + + if integration_frame.custom_integration: + logger_name = f"custom_components.{integration_frame.integration}" + else: + logger_name = f"homeassistant.components.{integration_frame.integration}" + + return logging.getLogger(logger_name) + + def get_integration_frame(exclude_integrations: set | None = None) -> IntegrationFrame: """Return the frame, integration and integration path of the current stack frame.""" found_frame = None diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index c16d7b1ec51..060800be62d 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -2,6 +2,7 @@ from collections import OrderedDict from datetime import date, datetime, timedelta import enum +import logging import os from socket import _GLOBAL_DEFAULT_TIMEOUT from unittest.mock import Mock, patch @@ -986,7 +987,11 @@ def test_deprecated_with_default(caplog: pytest.LogCaptureFixture, schema) -> No deprecated_schema = vol.All(cv.deprecated("mars", default=False), schema) test_data = {"mars": True} - output = deprecated_schema(test_data.copy()) + with patch( + "homeassistant.helpers.config_validation.get_integration_logger", + return_value=logging.getLogger(__name__), + ): + output = deprecated_schema(test_data.copy()) assert len(caplog.records) == 1 assert caplog.records[0].name == __name__ assert ( @@ -1062,21 +1067,19 @@ def test_deprecated_with_replacement_key_and_default( def test_deprecated_cant_find_module() -> None: - """Test if the current module cannot be inspected.""" - with patch("inspect.getmodule", return_value=None): - # This used to raise. - cv.deprecated( - "mars", - replacement_key="jupiter", - default=False, - ) + """Test if the current module cannot be found.""" + # This used to raise. + cv.deprecated( + "mars", + replacement_key="jupiter", + default=False, + ) - with patch("inspect.getmodule", return_value=None): - # This used to raise. - cv.removed( - "mars", - default=False, - ) + # This used to raise. + cv.removed( + "mars", + default=False, + ) def test_deprecated_or_removed_logger_with_config_attributes( @@ -1551,8 +1554,7 @@ def test_empty_schema(caplog: pytest.LogCaptureFixture) -> None: def test_empty_schema_cant_find_module() -> None: """Test if the current module cannot be inspected.""" - with patch("inspect.getmodule", return_value=None): - cv.empty_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) + cv.empty_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) def test_config_entry_only_schema( @@ -1582,10 +1584,7 @@ def test_config_entry_only_schema( def test_config_entry_only_schema_cant_find_module() -> None: """Test if the current module cannot be inspected.""" - with patch("inspect.getmodule", return_value=None): - cv.config_entry_only_config_schema("test_domain")( - {"test_domain": {"foo": "bar"}} - ) + cv.config_entry_only_config_schema("test_domain")({"test_domain": {"foo": "bar"}}) def test_config_entry_only_schema_no_hass( diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index 5010c459345..fa495e9dbc9 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -22,6 +22,14 @@ async def test_extract_frame_integration( ) +async def test_get_integration_logger( + caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock +) -> None: + """Test extracting the current frame to get the logger.""" + logger = frame.get_integration_logger(__name__) + assert logger.name == "homeassistant.components.hue" + + async def test_extract_frame_resolve_module( hass: HomeAssistant, enable_custom_integrations ) -> None: @@ -39,6 +47,17 @@ async def test_extract_frame_resolve_module( ) +async def test_get_integration_logger_resolve_module( + hass: HomeAssistant, enable_custom_integrations +) -> None: + """Test getting the logger from integration context.""" + from custom_components.test_integration_frame import call_get_integration_logger + + logger = call_get_integration_logger(__name__) + + assert logger.name == "custom_components.test_integration_frame" + + async def test_extract_frame_integration_with_excluded_integration( caplog: pytest.LogCaptureFixture, ) -> None: @@ -102,6 +121,30 @@ async def test_extract_frame_no_integration(caplog: pytest.LogCaptureFixture) -> frame.get_integration_frame() +async def test_get_integration_logger_no_integration( + caplog: pytest.LogCaptureFixture, +) -> None: + """Test getting fallback logger without integration context.""" + with patch( + "homeassistant.helpers.frame.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ], + ): + logger = frame.get_integration_logger(__name__) + + assert logger.name == __name__ + + @patch.object(frame, "_REPORTED_INTEGRATIONS", set()) async def test_prevent_flooding( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock diff --git a/tests/testing_config/custom_components/test_integration_frame/__init__.py b/tests/testing_config/custom_components/test_integration_frame/__init__.py index d342509d52e..31e894dddcd 100644 --- a/tests/testing_config/custom_components/test_integration_frame/__init__.py +++ b/tests/testing_config/custom_components/test_integration_frame/__init__.py @@ -1,8 +1,15 @@ """An integration which calls helpers.frame.get_integration_frame.""" +import logging + from homeassistant.helpers import frame +def call_get_integration_logger(fallback_name: str) -> logging.Logger: + """Call get_integration_logger.""" + return frame.get_integration_logger(fallback_name) + + def call_get_integration_frame() -> frame.IntegrationFrame: """Call get_integration_frame.""" return frame.get_integration_frame() From a07f6d9d76a4ac08ebd15356342e44a733c0f3af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:51:45 -1000 Subject: [PATCH 0162/1691] Import smtp in the executor to avoid blocking the loop (#112089) This one has no external requirements, but the email imports from stdlib take ~0.6s on a green --- homeassistant/components/smtp/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/smtp/manifest.json b/homeassistant/components/smtp/manifest.json index 0e0bba707ac..f321c17efa5 100644 --- a/homeassistant/components/smtp/manifest.json +++ b/homeassistant/components/smtp/manifest.json @@ -3,5 +3,6 @@ "name": "SMTP", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/smtp", + "import_executor": true, "iot_class": "cloud_push" } From 5e5da2bf3a8d6d24fe7840007fac5ea1d1f330cc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:52:11 -1000 Subject: [PATCH 0163/1691] Import integrations using sense-energy in the executor to avoid loop blocking (#112087) Import integrations using sense-enegy in the executor to avoid blocking the event loop --- homeassistant/components/emulated_kasa/manifest.json | 1 + homeassistant/components/sense/manifest.json | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index 843aeddde7b..63125bc0f3a 100644 --- a/homeassistant/components/emulated_kasa/manifest.json +++ b/homeassistant/components/emulated_kasa/manifest.json @@ -3,6 +3,7 @@ "name": "Emulated Kasa", "codeowners": ["@kbickar"], "documentation": "https://www.home-assistant.io/integrations/emulated_kasa", + "import_executor": true, "iot_class": "local_push", "loggers": ["sense_energy"], "quality_scale": "internal", diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index 7ef1caefe48..f930b486baa 100644 --- a/homeassistant/components/sense/manifest.json +++ b/homeassistant/components/sense/manifest.json @@ -18,6 +18,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/sense", + "import_executor": true, "iot_class": "cloud_polling", "loggers": ["sense_energy"], "requirements": ["sense-energy==0.12.2"] From 08c96efebe7b36ef7421f53f4fc2bf2244c545f3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:52:40 -1000 Subject: [PATCH 0164/1691] Import baf in the executor to avoid blocking the event loop (#112086) --- homeassistant/components/baf/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/baf/manifest.json b/homeassistant/components/baf/manifest.json index 497b3638fce..869c9706624 100644 --- a/homeassistant/components/baf/manifest.json +++ b/homeassistant/components/baf/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@bdraco", "@jfroy"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/baf", + "import_executor": true, "iot_class": "local_push", "requirements": ["aiobafi6==0.9.0"], "zeroconf": [ From ea9c969d15482ee8ce2b3657be7dd5ac57054611 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:53:51 -1000 Subject: [PATCH 0165/1691] Fix executor being overloaded in caldav (#112084) Migrate to using a single executor job instead of creating one per calendar. If the user had a lot of calendars the executor would get overloaded --- homeassistant/components/caldav/api.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/caldav/api.py b/homeassistant/components/caldav/api.py index fa89d6acc38..3b524e29370 100644 --- a/homeassistant/components/caldav/api.py +++ b/homeassistant/components/caldav/api.py @@ -1,6 +1,5 @@ """Library for working with CalDAV api.""" -import asyncio import caldav @@ -13,20 +12,13 @@ async def async_get_calendars( """Get all calendars that support the specified component.""" def _get_calendars() -> list[caldav.Calendar]: - return client.principal().calendars() - - calendars = await hass.async_add_executor_job(_get_calendars) - components_results = await asyncio.gather( - *[ - hass.async_add_executor_job(calendar.get_supported_components) - for calendar in calendars + return [ + calendar + for calendar in client.principal().calendars() + if component in calendar.get_supported_components() ] - ) - return [ - calendar - for calendar, supported_components in zip(calendars, components_results) - if component in supported_components - ] + + return await hass.async_add_executor_job(_get_calendars) def get_attr_value(obj: caldav.CalendarObjectResource, attribute: str) -> str | None: From 8d2fe73faacce9da41eec60988533efb30f2fd2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:55:04 -1000 Subject: [PATCH 0166/1691] Fix bootstrap being fetched three times during unifiprotect startup (#112082) We always fetch it to check if the device is online. Avoid fetching it again for migration by passing it to the migrators --- .../components/unifiprotect/__init__.py | 10 ++++-- .../components/unifiprotect/migrate.py | 35 ++++++++----------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 942c533b59e..c4a6bc88068 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -5,6 +5,7 @@ from datetime import timedelta import logging from aiohttp.client_exceptions import ServerDisconnectedError +from pyunifiprotect.data import Bootstrap from pyunifiprotect.exceptions import ClientError, NotAuthorized # Import the test_util.anonymize module from the pyunifiprotect package @@ -128,7 +129,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) try: - await _async_setup_entry(hass, entry, data_service) + await _async_setup_entry(hass, entry, data_service, bootstrap) except Exception as err: if await nvr_info.get_is_prerelease(): # If they are running a pre-release, its quite common for setup @@ -156,9 +157,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_setup_entry( - hass: HomeAssistant, entry: ConfigEntry, data_service: ProtectData + hass: HomeAssistant, + entry: ConfigEntry, + data_service: ProtectData, + bootstrap: Bootstrap, ) -> None: - await async_migrate_data(hass, entry, data_service.api) + await async_migrate_data(hass, entry, data_service.api, bootstrap) await data_service.async_setup() if not data_service.last_update_success: diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index db1e82d9914..3a6dde653b4 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -3,47 +3,39 @@ from __future__ import annotations import logging -from aiohttp.client_exceptions import ServerDisconnectedError from pyunifiprotect import ProtectApiClient from pyunifiprotect.data import NVR, Bootstrap, ProtectAdoptableDeviceModel -from pyunifiprotect.exceptions import ClientError from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry as er _LOGGER = logging.getLogger(__name__) async def async_migrate_data( - hass: HomeAssistant, entry: ConfigEntry, protect: ProtectApiClient + hass: HomeAssistant, + entry: ConfigEntry, + protect: ProtectApiClient, + bootstrap: Bootstrap, ) -> None: """Run all valid UniFi Protect data migrations.""" _LOGGER.debug("Start Migrate: async_migrate_buttons") - await async_migrate_buttons(hass, entry, protect) + await async_migrate_buttons(hass, entry, protect, bootstrap) _LOGGER.debug("Completed Migrate: async_migrate_buttons") _LOGGER.debug("Start Migrate: async_migrate_device_ids") - await async_migrate_device_ids(hass, entry, protect) + await async_migrate_device_ids(hass, entry, protect, bootstrap) _LOGGER.debug("Completed Migrate: async_migrate_device_ids") -async def async_get_bootstrap(protect: ProtectApiClient) -> Bootstrap: - """Get UniFi Protect bootstrap or raise appropriate HA error.""" - - try: - bootstrap = await protect.get_bootstrap() - except (TimeoutError, ClientError, ServerDisconnectedError) as err: - raise ConfigEntryNotReady from err - - return bootstrap - - async def async_migrate_buttons( - hass: HomeAssistant, entry: ConfigEntry, protect: ProtectApiClient + hass: HomeAssistant, + entry: ConfigEntry, + protect: ProtectApiClient, + bootstrap: Bootstrap, ) -> None: """Migrate existing Reboot button unique IDs from {device_id} to {deivce_id}_reboot. @@ -63,7 +55,6 @@ async def async_migrate_buttons( _LOGGER.debug("No button entities need migration") return - bootstrap = await async_get_bootstrap(protect) count = 0 for button in to_migrate: device = bootstrap.get_device_from_id(button.unique_id) @@ -94,7 +85,10 @@ async def async_migrate_buttons( async def async_migrate_device_ids( - hass: HomeAssistant, entry: ConfigEntry, protect: ProtectApiClient + hass: HomeAssistant, + entry: ConfigEntry, + protect: ProtectApiClient, + bootstrap: Bootstrap, ) -> None: """Migrate unique IDs from {device_id}_{name} format to {mac}_{name} format. @@ -119,7 +113,6 @@ async def async_migrate_device_ids( _LOGGER.debug("No entities need migration to MAC address ID") return - bootstrap = await async_get_bootstrap(protect) count = 0 for entity in to_migrate: parts = entity.unique_id.split("_") From f415746e2628dc5da6f686ee56aae75a288bd64c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:55:20 -1000 Subject: [PATCH 0167/1691] Import caldav in the executor to avoid blocking the event loop (#112081) 2024-03-02 18:21:51.794 DEBUG (MainThread) [homeassistant.loader] Component caldav import took 0.676 seconds (loaded_executor=False) --- homeassistant/components/caldav/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/caldav/manifest.json b/homeassistant/components/caldav/manifest.json index e0d598e6493..2c77c86696d 100644 --- a/homeassistant/components/caldav/manifest.json +++ b/homeassistant/components/caldav/manifest.json @@ -4,6 +4,7 @@ "codeowners": [], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/caldav", + "import_executor": true, "iot_class": "cloud_polling", "loggers": ["caldav", "vobject"], "requirements": ["caldav==1.3.9"] From 131068358a89baf0fe79f4c289360889d7ca04af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:55:55 -1000 Subject: [PATCH 0168/1691] Import yeelight in the executor to avoid blocking the event loop (#112080) --- homeassistant/components/yeelight/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 20f8ed3ed4d..58d2d6991ee 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -14,6 +14,7 @@ "homekit": { "models": ["YL*"] }, + "import_executor": true, "iot_class": "local_push", "loggers": ["async_upnp_client", "yeelight"], "quality_scale": "platinum", From 2fe12ade4cbe129ad532fb3292105841e17bb494 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:56:33 -1000 Subject: [PATCH 0169/1691] Ensure all homekit_controller controllers are imported in advance (#112079) * Ensure all homekit_controllers are imported in advance We want to avoid importing them in the event loop later * Ensure all homekit_controllers are imported in advance We want to avoid importing them in the event loop later --- .../components/homekit_controller/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 1043164c801..e3ff4d47fcf 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -6,6 +6,11 @@ import contextlib import logging import aiohomekit +from aiohomekit.const import ( + BLE_TRANSPORT_SUPPORTED, + COAP_TRANSPORT_SUPPORTED, + IP_TRANSPORT_SUPPORTED, +) from aiohomekit.exceptions import ( AccessoryDisconnectedError, AccessoryNotFoundError, @@ -24,6 +29,15 @@ from .connection import HKDevice from .const import DOMAIN, KNOWN_DEVICES from .utils import async_get_controller +# Ensure all the controllers get imported in the executor +# since they are loaded late. +if BLE_TRANSPORT_SUPPORTED: + from aiohomekit.controller import ble # noqa: F401 +if COAP_TRANSPORT_SUPPORTED: + from aiohomekit.controller import coap # noqa: F401 +if IP_TRANSPORT_SUPPORTED: + from aiohomekit.controller import ip # noqa: F401 + _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) From aaa2d8745f78a8f74db7f19ff6e7dac7981a1770 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 16:59:34 -1000 Subject: [PATCH 0170/1691] Import template in the executor to avoid blocking the event loop (#112070) Importing template has a very long dep tree --- homeassistant/components/template/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/template/manifest.json b/homeassistant/components/template/manifest.json index 4112ca7a73f..89aa9997d89 100644 --- a/homeassistant/components/template/manifest.json +++ b/homeassistant/components/template/manifest.json @@ -5,6 +5,7 @@ "codeowners": ["@PhracturedBlue", "@tetienne", "@home-assistant/core"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/template", + "import_executor": true, "integration_type": "helper", "iot_class": "local_push", "quality_scale": "internal" From dc3c7c95f7c5c4d45acb13135903df61f5d98487 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 17:00:28 -1000 Subject: [PATCH 0171/1691] Import stream in the executor to avoid blocking the event loop (#112078) * Import stream in the executor to avoid blocking the event loop This one has some large deps * one more place * avoid call if no change * just in case --- homeassistant/components/stream/__init__.py | 30 +++++++++++-------- homeassistant/components/stream/manifest.json | 1 + 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 837d11eca7c..23ff80952bf 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -188,22 +188,26 @@ CONFIG_SCHEMA = vol.Schema( ) -@callback -def update_pyav_logging(_event: Event | None = None) -> None: - """Adjust libav logging to only log when the stream logger is at DEBUG.""" +def set_pyav_logging(enable: bool) -> None: + """Turn PyAV logging on or off.""" + import av # pylint: disable=import-outside-toplevel - def set_pyav_logging(enable: bool) -> None: - """Turn PyAV logging on or off.""" - import av # pylint: disable=import-outside-toplevel - - av.logging.set_level(av.logging.VERBOSE if enable else av.logging.FATAL) - - # enable PyAV logging iff Stream logger is set to debug - set_pyav_logging(logging.getLogger(__name__).isEnabledFor(logging.DEBUG)) + av.logging.set_level(av.logging.VERBOSE if enable else av.logging.FATAL) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up stream.""" + debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG) + + @callback + def update_pyav_logging(_event: Event | None = None) -> None: + """Adjust libav logging to only log when the stream logger is at DEBUG.""" + nonlocal debug_enabled + if (new_debug_enabled := _LOGGER.isEnabledFor(logging.DEBUG)) == debug_enabled: + return + debug_enabled = new_debug_enabled + # enable PyAV logging iff Stream logger is set to debug + set_pyav_logging(new_debug_enabled) # Only pass through PyAV log messages if stream logging is above DEBUG cancel_logging_listener = hass.bus.async_listen( @@ -213,7 +217,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # at logging.WARNING. Set those Logger levels to logging.ERROR for logging_namespace in ("libav.mp4", "libav.swscaler"): logging.getLogger(logging_namespace).setLevel(logging.ERROR) - update_pyav_logging() + + # This will load av so we run it in the executor + await hass.async_add_import_executor_job(set_pyav_logging, debug_enabled) # Keep import here so that we can import stream integration without installing reqs # pylint: disable-next=import-outside-toplevel diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 37158aa5fe3..0a1b60485d8 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/stream", + "import_executor": true, "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", From a253991c6d1f0cea3ecd41f87254e2966e9e9c57 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Sat, 2 Mar 2024 22:00:50 -0500 Subject: [PATCH 0172/1691] Bump pydrawise to 2024.3.0 (#112066) --- homeassistant/components/hydrawise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hydrawise/manifest.json b/homeassistant/components/hydrawise/manifest.json index 707df93251d..5181de7d2a4 100644 --- a/homeassistant/components/hydrawise/manifest.json +++ b/homeassistant/components/hydrawise/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/hydrawise", "iot_class": "cloud_polling", "loggers": ["pydrawise"], - "requirements": ["pydrawise==2024.2.0"] + "requirements": ["pydrawise==2024.3.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index ff74086b136..10d86bb2bc2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1770,7 +1770,7 @@ pydiscovergy==3.0.0 pydoods==1.0.2 # homeassistant.components.hydrawise -pydrawise==2024.2.0 +pydrawise==2024.3.0 # homeassistant.components.android_ip_webcam pydroid-ipcam==2.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aeb7b466345..52fcc6a58fa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1372,7 +1372,7 @@ pydexcom==0.2.3 pydiscovergy==3.0.0 # homeassistant.components.hydrawise -pydrawise==2024.2.0 +pydrawise==2024.3.0 # homeassistant.components.android_ip_webcam pydroid-ipcam==2.0.0 From c8cb0ff61d7fd25d3b1f99424ad5a24b79feb320 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 17:14:28 -1000 Subject: [PATCH 0173/1691] Avoid trying to import platforms that do not exist (#112028) * Avoid trying to import platforms that do not exist * adjust * fixes * cleanup * cleanup * cleanup * Apply suggestions from code review * docs * fixes * fixes * comment * coverage * coverage * coverage * Switch config to use async_get_component This was another path where integrations that were marked to load in the executor would be loaded in the loop * Switch config to use async_get_component/async_get_platform This was another path where integrations that were marked to load in the executor would be loaded in the loop * merge * refactor * refactor * coverage * preen * preen --- homeassistant/config.py | 34 ++++++++------- homeassistant/helpers/integration_platform.py | 4 ++ homeassistant/loader.py | 38 ++++++++++++++++ tests/test_loader.py | 43 +++++++++++++++++++ .../test_integration_platform/__init__.py | 9 ++++ .../test_integration_platform/config_flow.py | 7 +++ .../test_integration_platform/const.py | 2 + .../test_integration_platform/group.py | 3 ++ .../test_integration_platform/manifest.json | 11 +++++ 9 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 tests/testing_config/custom_components/test_integration_platform/__init__.py create mode 100644 tests/testing_config/custom_components/test_integration_platform/config_flow.py create mode 100644 tests/testing_config/custom_components/test_integration_platform/const.py create mode 100644 tests/testing_config/custom_components/test_integration_platform/group.py create mode 100644 tests/testing_config/custom_components/test_integration_platform/manifest.json diff --git a/homeassistant/config.py b/homeassistant/config.py index d52346e9299..36ac3843b3a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1432,22 +1432,24 @@ async def async_process_component_config( # noqa: C901 # Check if the integration has a custom config validator config_validator = None - try: - config_validator = await integration.async_get_platform("config") - except ImportError as err: - # Filter out import error of the config platform. - # If the config platform contains bad imports, make sure - # that still fails. - if err.name != f"{integration.pkg_path}.config": - exc_info = ConfigExceptionInfo( - err, - ConfigErrorTranslationKey.CONFIG_PLATFORM_IMPORT_ERR, - domain, - config, - integration_docs, - ) - config_exceptions.append(exc_info) - return IntegrationConfigInfo(None, config_exceptions) + if integration.platform_exists("config") is not False: + # If the config platform cannot possibly exist, don't try to load it. + try: + config_validator = await integration.async_get_platform("config") + except ImportError as err: + # Filter out import error of the config platform. + # If the config platform contains bad imports, make sure + # that still fails. + if err.name != f"{integration.pkg_path}.config": + exc_info = ConfigExceptionInfo( + err, + ConfigErrorTranslationKey.CONFIG_PLATFORM_IMPORT_ERR, + domain, + config, + integration_docs, + ) + config_exceptions.append(exc_info) + return IntegrationConfigInfo(None, config_exceptions) if config_validator is not None and hasattr( config_validator, "async_validate_config" diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 138722bd455..aebbc854693 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -48,6 +48,10 @@ def _get_platform( ) return None + if integration.platform_exists(platform_name) is False: + # If the platform cannot possibly exist, don't bother trying to load it + return None + try: return integration.get_platform(platform_name) except ImportError as err: diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 8b3316b1a7f..7873fbd4c74 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -12,6 +12,7 @@ from dataclasses import dataclass import functools as ft import importlib import logging +import os import pathlib import sys import time @@ -976,6 +977,43 @@ class Integration: return platform return self._load_platform(platform_name) + def platform_exists(self, platform_name: str) -> bool | None: + """Check if a platform exists for an integration. + + Returns True if the platform exists, False if it does not. + + If it cannot be determined if the platform exists without attempting + to import the component, it returns None. This will only happen + if this function is called before get_component or async_get_component + has been called for the integration or the integration failed to load. + """ + full_name = f"{self.domain}.{platform_name}" + + cache: dict[str, ModuleType] = self.hass.data[DATA_COMPONENTS] + if full_name in cache: + return True + + missing_platforms_cache: dict[str, ImportError] + missing_platforms_cache = self.hass.data[DATA_MISSING_PLATFORMS] + if full_name in missing_platforms_cache: + return False + + if not (component := cache.get(self.domain)) or not ( + file := getattr(component, "__file__", None) + ): + return None + + path: pathlib.Path = pathlib.Path(file).parent.joinpath(platform_name) + if os.path.exists(path.with_suffix(".py")) or os.path.exists(path): + return True + + exc = ModuleNotFoundError( + f"Platform {full_name} not found", + name=f"{self.pkg_path}.{platform_name}", + ) + missing_platforms_cache[full_name] = exc + return False + def _load_platform(self, platform_name: str) -> ModuleType: """Load a platform for an integration. diff --git a/tests/test_loader.py b/tests/test_loader.py index b9a65438728..34104987de4 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,5 +1,6 @@ """Test to verify that we can load components.""" import asyncio +import os import sys from typing import Any from unittest.mock import MagicMock, Mock, patch @@ -1193,3 +1194,45 @@ async def test_async_get_platform_raises_after_import_failure( in caplog.text ) assert "loaded_executor=False" not in caplog.text + + +async def test_platform_exists( + hass: HomeAssistant, enable_custom_integrations: None +) -> None: + """Test platform_exists.""" + integration = await loader.async_get_integration(hass, "test_integration_platform") + assert integration.domain == "test_integration_platform" + + # get_component never called, will return None + assert integration.platform_exists("non_existing") is None + + component = integration.get_component() + assert component.DOMAIN == "test_integration_platform" + + # component is loaded, should now return False + with patch( + "homeassistant.loader.os.path.exists", wraps=os.path.exists + ) as mock_exists: + assert integration.platform_exists("non_existing") is False + + # We should check if the file exists + assert mock_exists.call_count == 2 + + # component is loaded, should now return False + with patch( + "homeassistant.loader.os.path.exists", wraps=os.path.exists + ) as mock_exists: + assert integration.platform_exists("non_existing") is False + + # We should remember the file does not exist + assert mock_exists.call_count == 0 + + assert integration.platform_exists("group") is True + + platform = await integration.async_get_platform("group") + assert platform.MAGIC == 1 + + platform = integration.get_platform("group") + assert platform.MAGIC == 1 + + assert integration.platform_exists("group") is True diff --git a/tests/testing_config/custom_components/test_integration_platform/__init__.py b/tests/testing_config/custom_components/test_integration_platform/__init__.py new file mode 100644 index 00000000000..6b70949231b --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_platform/__init__.py @@ -0,0 +1,9 @@ +"""Provide a mock package component.""" +from .const import TEST # noqa: F401 + +DOMAIN = "test_integration_platform" + + +async def async_setup(hass, config): + """Mock a successful setup.""" + return True diff --git a/tests/testing_config/custom_components/test_integration_platform/config_flow.py b/tests/testing_config/custom_components/test_integration_platform/config_flow.py new file mode 100644 index 00000000000..9153b666828 --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_platform/config_flow.py @@ -0,0 +1,7 @@ +"""Config flow.""" + +from homeassistant.core import HomeAssistant + + +async def _async_has_devices(hass: HomeAssistant) -> bool: + return True diff --git a/tests/testing_config/custom_components/test_integration_platform/const.py b/tests/testing_config/custom_components/test_integration_platform/const.py new file mode 100644 index 00000000000..7e13e04cb47 --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_platform/const.py @@ -0,0 +1,2 @@ +"""Constants for test_package custom component.""" +TEST = 5 diff --git a/tests/testing_config/custom_components/test_integration_platform/group.py b/tests/testing_config/custom_components/test_integration_platform/group.py new file mode 100644 index 00000000000..070cfa0e406 --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_platform/group.py @@ -0,0 +1,3 @@ +"""Group.""" + +MAGIC = 1 diff --git a/tests/testing_config/custom_components/test_integration_platform/manifest.json b/tests/testing_config/custom_components/test_integration_platform/manifest.json new file mode 100644 index 00000000000..74aa8bb379d --- /dev/null +++ b/tests/testing_config/custom_components/test_integration_platform/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "test_integration_platform", + "name": "Test Integration Platform", + "documentation": "http://test-package.io", + "requirements": [], + "dependencies": [], + "codeowners": [], + "config_flow": true, + "import_executor": true, + "version": "1.2.3" +} From 6a243d6705e5a9eae76115e544ae70235c460443 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 Mar 2024 23:03:35 -1000 Subject: [PATCH 0174/1691] Preload platform integrations to better group executor usage (#112010) --- homeassistant/bootstrap.py | 18 +++++++++++++----- homeassistant/config.py | 24 ++++++++++++++++++++++++ tests/test_config.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index db86d24c667..beb04b50021 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -675,6 +675,9 @@ async def _async_resolve_domains_to_setup( base_platforms_loaded = False domains_to_setup = _get_domains(hass, config) needed_requirements: set[str] = set() + platform_integrations = conf_util.extract_platform_integrations( + config, BASE_PLATFORMS + ) # Resolve all dependencies so we know all integrations # that will have to be loaded and start rightaway @@ -691,7 +694,7 @@ async def _async_resolve_domains_to_setup( # to avoid the lock contention when multiple # integrations try to resolve them at once base_platforms_loaded = True - to_get = {*old_to_resolve, *BASE_PLATFORMS} + to_get = {*old_to_resolve, *BASE_PLATFORMS, *platform_integrations} else: to_get = old_to_resolve @@ -700,13 +703,16 @@ async def _async_resolve_domains_to_setup( integrations_to_process: list[loader.Integration] = [] for domain, itg in (await loader.async_get_integrations(hass, to_get)).items(): - if not isinstance(itg, loader.Integration) or domain not in old_to_resolve: + if not isinstance(itg, loader.Integration): continue - integrations_to_process.append(itg) integration_cache[domain] = itg + needed_requirements.update(itg.requirements) + if domain not in old_to_resolve: + continue + + integrations_to_process.append(itg) manifest_deps.update(itg.dependencies) manifest_deps.update(itg.after_dependencies) - needed_requirements.update(itg.requirements) if not itg.all_dependencies_resolved: resolve_dependencies_tasks.append( create_eager_task( @@ -760,7 +766,9 @@ async def _async_resolve_domains_to_setup( # wait for the translation load lock, loading will be done by the # time it gets to it. hass.async_create_background_task( - translation.async_load_integrations(hass, {*BASE_PLATFORMS, *domains_to_setup}), + translation.async_load_integrations( + hass, {*BASE_PLATFORMS, *platform_integrations, *domains_to_setup} + ), "load translations", eager_start=True, ) diff --git a/homeassistant/config.py b/homeassistant/config.py index 36ac3843b3a..a72bd00ed44 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1388,6 +1388,30 @@ def config_per_platform( yield platform, item +def extract_platform_integrations(config: ConfigType, domains: set[str]) -> set[str]: + """Find all the platforms in a configuration.""" + platform_integrations: set[str] = set() + for key, domain_config in config.items(): + try: + domain = cv.domain_key(key) + except vol.Invalid: + continue + if domain not in domains: + continue + + if not isinstance(domain_config, list): + domain_config = [domain_config] + + for item in domain_config: + try: + platform = item.get(CONF_PLATFORM) + except AttributeError: + continue + if platform: + platform_integrations.add(platform) + return platform_integrations + + def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: """Extract keys from config for given domain name. diff --git a/tests/test_config.py b/tests/test_config.py index 5726c515dc3..73c541bc7e1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2339,3 +2339,34 @@ def test_config_per_platform() -> None: (None, 1), ("hello 2", config["zone Hallo"][1]), ] == list(config_util.config_per_platform(config, "zone")) + + +def test_extract_platform_integrations() -> None: + """Test extract_platform_integrations.""" + config = OrderedDict( + [ + (b"zone", {"platform": "not str"}), + ("zone", {"platform": "hello"}), + ("zonex", []), + ("zoney", ""), + ("notzone", {"platform": "nothello"}), + ("zoner", None), + ("zone Hallo", [1, {"platform": "hello 2"}]), + ("zone 100", None), + ("i n v a-@@", None), + ("i n v a-@@", {"platform": "hello"}), + ("zoneq", "pig"), + ("zoneempty", {"platform": ""}), + ] + ) + assert config_util.extract_platform_integrations(config, {"zone"}) == { + "hello", + "hello 2", + } + assert config_util.extract_platform_integrations(config, {"zonex"}) == set() + assert config_util.extract_platform_integrations(config, {"zoney"}) == set() + assert config_util.extract_platform_integrations( + config, {"zone", "not_valid", "notzone"} + ) == {"hello", "hello 2", "nothello"} + assert config_util.extract_platform_integrations(config, {"zoneq"}) == set() + assert config_util.extract_platform_integrations(config, {"zoneempty"}) == set() From 25551fa9383494657dfe63916a808fa9cc0a2700 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 3 Mar 2024 11:08:28 +0100 Subject: [PATCH 0175/1691] Move Ping binary sensor attributes to sensor entities (#112004) * Move Ping binary sensor attributes to sensor entities * Process code review * Update snapshot --- homeassistant/components/ping/__init__.py | 2 +- .../components/ping/binary_sensor.py | 20 +-- homeassistant/components/ping/coordinator.py | 4 +- homeassistant/components/ping/entity.py | 26 +++ homeassistant/components/ping/helpers.py | 3 +- homeassistant/components/ping/sensor.py | 116 +++++++++++++ homeassistant/components/ping/strings.json | 16 ++ tests/components/ping/conftest.py | 2 +- .../ping/snapshots/test_binary_sensor.ambr | 12 +- .../ping/snapshots/test_sensor.ambr | 154 ++++++++++++++++++ tests/components/ping/test_sensor.py | 33 ++++ 11 files changed, 366 insertions(+), 22 deletions(-) create mode 100644 homeassistant/components/ping/entity.py create mode 100644 homeassistant/components/ping/sensor.py create mode 100644 tests/components/ping/snapshots/test_sensor.ambr create mode 100644 tests/components/ping/test_sensor.py diff --git a/homeassistant/components/ping/__init__.py b/homeassistant/components/ping/__init__.py index 81df1401f91..9aaf8ffbd99 100644 --- a/homeassistant/components/ping/__init__.py +++ b/homeassistant/components/ping/__init__.py @@ -19,7 +19,7 @@ from .helpers import PingDataICMPLib, PingDataSubProcess _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = cv.platform_only_config_schema(DOMAIN) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.SENSOR] @dataclass(slots=True) diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 97636111586..eacf718b6a2 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -18,11 +18,11 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import PingDomainData from .const import CONF_IMPORTED_BY, CONF_PING_COUNT, DEFAULT_PING_COUNT, DOMAIN from .coordinator import PingUpdateCoordinator +from .entity import PingEntity _LOGGER = logging.getLogger(__name__) @@ -84,20 +84,18 @@ async def async_setup_entry( async_add_entities([PingBinarySensor(entry, data.coordinators[entry.entry_id])]) -class PingBinarySensor(CoordinatorEntity[PingUpdateCoordinator], BinarySensorEntity): +class PingBinarySensor(PingEntity, BinarySensorEntity): """Representation of a Ping Binary sensor.""" _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY _attr_available = False + _attr_name = None def __init__( self, config_entry: ConfigEntry, coordinator: PingUpdateCoordinator ) -> None: """Initialize the Ping Binary sensor.""" - super().__init__(coordinator) - - self._attr_name = config_entry.title - self._attr_unique_id = config_entry.entry_id + super().__init__(coordinator, config_entry.entry_id) # if this was imported just enable it when it was enabled before if CONF_IMPORTED_BY in config_entry.data: @@ -113,11 +111,9 @@ class PingBinarySensor(CoordinatorEntity[PingUpdateCoordinator], BinarySensorEnt @property def extra_state_attributes(self) -> dict[str, Any] | None: """Return the state attributes of the ICMP checo request.""" - if self.coordinator.data.data is None: - return None return { - ATTR_ROUND_TRIP_TIME_AVG: self.coordinator.data.data["avg"], - ATTR_ROUND_TRIP_TIME_MAX: self.coordinator.data.data["max"], - ATTR_ROUND_TRIP_TIME_MDEV: self.coordinator.data.data["mdev"], - ATTR_ROUND_TRIP_TIME_MIN: self.coordinator.data.data["min"], + ATTR_ROUND_TRIP_TIME_AVG: self.coordinator.data.data.get("avg"), + ATTR_ROUND_TRIP_TIME_MAX: self.coordinator.data.data.get("max"), + ATTR_ROUND_TRIP_TIME_MDEV: self.coordinator.data.data.get("mdev"), + ATTR_ROUND_TRIP_TIME_MIN: self.coordinator.data.data.get("min"), } diff --git a/homeassistant/components/ping/coordinator.py b/homeassistant/components/ping/coordinator.py index f6bda9693b8..57e212ed69d 100644 --- a/homeassistant/components/ping/coordinator.py +++ b/homeassistant/components/ping/coordinator.py @@ -20,7 +20,7 @@ class PingResult: ip_address: str is_alive: bool - data: dict[str, Any] | None + data: dict[str, Any] class PingUpdateCoordinator(DataUpdateCoordinator[PingResult]): @@ -49,5 +49,5 @@ class PingUpdateCoordinator(DataUpdateCoordinator[PingResult]): return PingResult( ip_address=self.ping.ip_address, is_alive=self.ping.is_alive, - data=self.ping.data, + data=self.ping.data or {}, ) diff --git a/homeassistant/components/ping/entity.py b/homeassistant/components/ping/entity.py new file mode 100644 index 00000000000..1332b82e9a8 --- /dev/null +++ b/homeassistant/components/ping/entity.py @@ -0,0 +1,26 @@ +"""Base entity for the Ping component.""" +from homeassistant.core import DOMAIN +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .coordinator import PingUpdateCoordinator + + +class PingEntity(CoordinatorEntity[PingUpdateCoordinator]): + """Represents a Ping base entity.""" + + _attr_has_entity_name = True + + def __init__( + self, + coordinator: PingUpdateCoordinator, + unique_id: str, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + + self._attr_unique_id = unique_id + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.coordinator.data.ip_address)}, + manufacturer="Ping", + ) diff --git a/homeassistant/components/ping/helpers.py b/homeassistant/components/ping/helpers.py index e3ebaffec12..16d36b61ab7 100644 --- a/homeassistant/components/ping/helpers.py +++ b/homeassistant/components/ping/helpers.py @@ -70,7 +70,6 @@ class PingDataICMPLib(PingData): "min": data.min_rtt, "max": data.max_rtt, "avg": data.avg_rtt, - "mdev": "", } @@ -135,7 +134,7 @@ class PingDataSubProcess(PingData): if TYPE_CHECKING: assert match is not None rtt_min, rtt_avg, rtt_max = match.groups() - return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": ""} + return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max} match = PING_MATCHER.search(str(out_data).rsplit("\n", maxsplit=1)[-1]) if TYPE_CHECKING: assert match is not None diff --git a/homeassistant/components/ping/sensor.py b/homeassistant/components/ping/sensor.py new file mode 100644 index 00000000000..5010c93f314 --- /dev/null +++ b/homeassistant/components/ping/sensor.py @@ -0,0 +1,116 @@ +"""Sensor platform that for Ping integration.""" +from collections.abc import Callable +from dataclasses import dataclass + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory, UnitOfTime +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import PingDomainData +from .const import DOMAIN +from .coordinator import PingResult, PingUpdateCoordinator +from .entity import PingEntity + + +@dataclass(frozen=True, kw_only=True) +class PingSensorEntityDescription(SensorEntityDescription): + """Class to describe a Ping sensor entity.""" + + value_fn: Callable[[PingResult], float | None] + has_fn: Callable[[PingResult], bool] + + +SENSORS: tuple[PingSensorEntityDescription, ...] = ( + PingSensorEntityDescription( + key="round_trip_time_avg", + translation_key="round_trip_time_avg", + native_unit_of_measurement=UnitOfTime.MILLISECONDS, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda result: result.data.get("avg"), + has_fn=lambda result: "avg" in result.data, + ), + PingSensorEntityDescription( + key="round_trip_time_max", + translation_key="round_trip_time_max", + native_unit_of_measurement=UnitOfTime.MILLISECONDS, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda result: result.data.get("max"), + has_fn=lambda result: "max" in result.data, + ), + PingSensorEntityDescription( + key="round_trip_time_mdev", + translation_key="round_trip_time_mdev", + native_unit_of_measurement=UnitOfTime.MILLISECONDS, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda result: result.data.get("mdev"), + has_fn=lambda result: "mdev" in result.data, + ), + PingSensorEntityDescription( + key="round_trip_time_min", + translation_key="round_trip_time_min", + native_unit_of_measurement=UnitOfTime.MILLISECONDS, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.DURATION, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda result: result.data.get("min"), + has_fn=lambda result: "min" in result.data, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Ping sensors from config entry.""" + data: PingDomainData = hass.data[DOMAIN] + coordinator = data.coordinators[entry.entry_id] + + async_add_entities( + PingSensor(entry, description, coordinator) + for description in SENSORS + if description.has_fn(coordinator.data) + ) + + +class PingSensor(PingEntity, SensorEntity): + """Represents a Ping sensor.""" + + entity_description: PingSensorEntityDescription + + def __init__( + self, + config_entry: ConfigEntry, + description: PingSensorEntityDescription, + coordinator: PingUpdateCoordinator, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator, f"{config_entry.entry_id}-{description.key}") + + self.entity_description = description + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return super().available and self.coordinator.data.is_alive + + @property + def native_value(self) -> float | None: + """Return the sensor state.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/ping/strings.json b/homeassistant/components/ping/strings.json index 421d9079c62..ef9f74b4207 100644 --- a/homeassistant/components/ping/strings.json +++ b/homeassistant/components/ping/strings.json @@ -1,4 +1,20 @@ { + "entity": { + "sensor": { + "round_trip_time_avg": { + "name": "Round Trip Time Average" + }, + "round_trip_time_max": { + "name": "Round Trip Time Maximum" + }, + "round_trip_time_mdev": { + "name": "Round Trip Time Mean Deviation" + }, + "round_trip_time_min": { + "name": "Round Trip Time Minimum" + } + } + }, "config": { "step": { "user": { diff --git a/tests/components/ping/conftest.py b/tests/components/ping/conftest.py index 24dd3314e3c..b0f772e603f 100644 --- a/tests/components/ping/conftest.py +++ b/tests/components/ping/conftest.py @@ -26,7 +26,7 @@ def patch_setup(*args, **kwargs): @pytest.fixture(autouse=True) async def patch_ping(): """Patch icmplib async_ping.""" - mock = Host("10.10.10.10", 5, [10, 1, 2]) + mock = Host("10.10.10.10", 5, [10, 1, 2, 5, 6]) with patch( "homeassistant.components.ping.helpers.async_ping", return_value=mock diff --git a/tests/components/ping/snapshots/test_binary_sensor.ambr b/tests/components/ping/snapshots/test_binary_sensor.ambr index 0924c383fc2..e4d26fe873e 100644 --- a/tests/components/ping/snapshots/test_binary_sensor.ambr +++ b/tests/components/ping/snapshots/test_binary_sensor.ambr @@ -72,7 +72,7 @@ 'domain': 'binary_sensor', 'entity_category': None, 'entity_id': 'binary_sensor.10_10_10_10', - 'has_entity_name': False, + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -83,7 +83,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': '10.10.10.10', + 'original_name': None, 'platform': 'ping', 'previous_unique_id': None, 'supported_features': 0, @@ -96,9 +96,9 @@ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', 'friendly_name': '10.10.10.10', - 'round_trip_time_avg': 4.333, + 'round_trip_time_avg': 4.8, 'round_trip_time_max': 10, - 'round_trip_time_mdev': '', + 'round_trip_time_mdev': None, 'round_trip_time_min': 1, }), 'context': , @@ -113,6 +113,10 @@ 'attributes': ReadOnlyDict({ 'device_class': 'connectivity', 'friendly_name': '10.10.10.10', + 'round_trip_time_avg': None, + 'round_trip_time_max': None, + 'round_trip_time_mdev': None, + 'round_trip_time_min': None, }), 'context': , 'entity_id': 'binary_sensor.10_10_10_10', diff --git a/tests/components/ping/snapshots/test_sensor.ambr b/tests/components/ping/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..c91dcff687a --- /dev/null +++ b/tests/components/ping/snapshots/test_sensor.ambr @@ -0,0 +1,154 @@ +# serializer version: 1 +# name: test_setup_and_update[round_trip_time_average] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.10_10_10_10_round_trip_time_average', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Round Trip Time Average', + 'platform': 'ping', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'round_trip_time_avg', + 'unit_of_measurement': , + }) +# --- +# name: test_setup_and_update[round_trip_time_average].1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '10.10.10.10 Round Trip Time Average', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.10_10_10_10_round_trip_time_average', + 'last_changed': , + 'last_updated': , + 'state': '4.8', + }) +# --- +# name: test_setup_and_update[round_trip_time_maximum] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.10_10_10_10_round_trip_time_maximum', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Round Trip Time Maximum', + 'platform': 'ping', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'round_trip_time_max', + 'unit_of_measurement': , + }) +# --- +# name: test_setup_and_update[round_trip_time_maximum].1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '10.10.10.10 Round Trip Time Maximum', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.10_10_10_10_round_trip_time_maximum', + 'last_changed': , + 'last_updated': , + 'state': '10', + }) +# --- +# name: test_setup_and_update[round_trip_time_mean_deviation] + None +# --- +# name: test_setup_and_update[round_trip_time_mean_deviation].1 + None +# --- +# name: test_setup_and_update[round_trip_time_minimum] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.10_10_10_10_round_trip_time_minimum', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Round Trip Time Minimum', + 'platform': 'ping', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'round_trip_time_min', + 'unit_of_measurement': , + }) +# --- +# name: test_setup_and_update[round_trip_time_minimum].1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': '10.10.10.10 Round Trip Time Minimum', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.10_10_10_10_round_trip_time_minimum', + 'last_changed': , + 'last_updated': , + 'state': '1', + }) +# --- diff --git a/tests/components/ping/test_sensor.py b/tests/components/ping/test_sensor.py new file mode 100644 index 00000000000..5c4833aaf06 --- /dev/null +++ b/tests/components/ping/test_sensor.py @@ -0,0 +1,33 @@ +"""Test sensor platform of Ping.""" + +import pytest +from syrupy import SnapshotAssertion +from syrupy.filters import props + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default", "setup_integration") +@pytest.mark.parametrize( + "sensor_name", + [ + "round_trip_time_average", + "round_trip_time_maximum", + "round_trip_time_mean_deviation", # should be None in the snapshot + "round_trip_time_minimum", + ], +) +async def test_setup_and_update( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + sensor_name: str, +) -> None: + """Test sensor setup and update.""" + + entry = entity_registry.async_get(f"sensor.10_10_10_10_{sensor_name}") + assert entry == snapshot(exclude=props("unique_id")) + + state = hass.states.get(f"sensor.10_10_10_10_{sensor_name}") + assert state == snapshot From 2f223ae377e02b2df9553ed8e3568972987ef925 Mon Sep 17 00:00:00 2001 From: Matrix Date: Sun, 3 Mar 2024 18:11:45 +0800 Subject: [PATCH 0176/1691] Add YoLInk YS7905-UC Support (#111709) * Add YS7905-UC Support * Fix as suggestion --- homeassistant/components/yolink/manifest.json | 2 +- homeassistant/components/yolink/sensor.py | 10 ++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yolink/manifest.json b/homeassistant/components/yolink/manifest.json index aae5be3f9d3..fcdab39cb8c 100644 --- a/homeassistant/components/yolink/manifest.json +++ b/homeassistant/components/yolink/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["auth", "application_credentials"], "documentation": "https://www.home-assistant.io/integrations/yolink", "iot_class": "cloud_push", - "requirements": ["yolink-api==0.3.7"] + "requirements": ["yolink-api==0.3.9"] } diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index ace13353341..4401ac1aab5 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -22,6 +22,7 @@ from yolink.const import ( ATTR_DEVICE_TH_SENSOR, ATTR_DEVICE_THERMOSTAT, ATTR_DEVICE_VIBRATION_SENSOR, + ATTR_DEVICE_WATER_DEPTH_SENSOR, ATTR_GARAGE_DOOR_CONTROLLER, ) from yolink.device import YoLinkDevice @@ -37,6 +38,7 @@ from homeassistant.const import ( PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, EntityCategory, + UnitOfLength, UnitOfTemperature, ) from homeassistant.core import HomeAssistant, callback @@ -72,6 +74,7 @@ SENSOR_DEVICE_TYPE = [ ATTR_DEVICE_TH_SENSOR, ATTR_DEVICE_THERMOSTAT, ATTR_DEVICE_VIBRATION_SENSOR, + ATTR_DEVICE_WATER_DEPTH_SENSOR, ATTR_DEVICE_LOCK, ATTR_DEVICE_MANIPULATOR, ATTR_DEVICE_CO_SMOKE_SENSOR, @@ -91,6 +94,7 @@ BATTERY_POWER_SENSOR = [ ATTR_DEVICE_LOCK, ATTR_DEVICE_MANIPULATOR, ATTR_DEVICE_CO_SMOKE_SENSOR, + ATTR_DEVICE_WATER_DEPTH_SENSOR, ] MCU_DEV_TEMPERATURE_SENSOR = [ @@ -195,6 +199,12 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( exists_fn=lambda device: device.device_type in ATTR_DEVICE_POWER_FAILURE_ALARM, value=lambda value: "enabled" if value is True else "disabled", ), + YoLinkSensorEntityDescription( + key="waterDepth", + device_class=SensorDeviceClass.DISTANCE, + native_unit_of_measurement=UnitOfLength.METERS, + exists_fn=lambda device: device.device_type in ATTR_DEVICE_WATER_DEPTH_SENSOR, + ), ) diff --git a/requirements_all.txt b/requirements_all.txt index 10d86bb2bc2..4700e9a93f6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2902,7 +2902,7 @@ yeelight==0.7.14 yeelightsunflower==0.0.10 # homeassistant.components.yolink -yolink-api==0.3.7 +yolink-api==0.3.9 # homeassistant.components.youless youless-api==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 52fcc6a58fa..2779cff43d8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2231,7 +2231,7 @@ yalexs==1.11.4 yeelight==0.7.14 # homeassistant.components.yolink -yolink-api==0.3.7 +yolink-api==0.3.9 # homeassistant.components.youless youless-api==1.0.1 From 25ba046ff1e0538955da15943fd6e2854aca8bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Sun, 3 Mar 2024 10:56:14 +0000 Subject: [PATCH 0177/1691] Fix flakey airzone_cloud tests by avoiding creation of the websocket (#112102) --- tests/components/airzone_cloud/conftest.py | 15 +++++++++++++++ .../components/airzone_cloud/test_coordinator.py | 3 --- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 tests/components/airzone_cloud/conftest.py diff --git a/tests/components/airzone_cloud/conftest.py b/tests/components/airzone_cloud/conftest.py new file mode 100644 index 00000000000..d810c808fde --- /dev/null +++ b/tests/components/airzone_cloud/conftest.py @@ -0,0 +1,15 @@ +"""Tests for the Airzone integration.""" + +from unittest.mock import patch + +import pytest + + +@pytest.fixture(autouse=True) +def airzone_cloud_no_websockets(): + """Fixture to completely disable Airzone Cloud WebSockets.""" + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi._update_websockets", + return_value=False, + ), patch("aioairzone_cloud.websockets.AirzoneCloudIWS.connect", return_value=True): + yield diff --git a/tests/components/airzone_cloud/test_coordinator.py b/tests/components/airzone_cloud/test_coordinator.py index a2307b94335..40b6c937ed2 100644 --- a/tests/components/airzone_cloud/test_coordinator.py +++ b/tests/components/airzone_cloud/test_coordinator.py @@ -46,9 +46,6 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None: ) as mock_webserver, patch( "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", return_value=None, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi._update_websockets", - return_value=False, ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() From 895dc6fce1209f57f09b4d0e982196cfe1c51950 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 3 Mar 2024 14:08:19 +0100 Subject: [PATCH 0178/1691] Add icon translations to Nextbus (#111981) --- homeassistant/components/nextbus/icons.json | 9 +++++++++ homeassistant/components/nextbus/sensor.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/nextbus/icons.json diff --git a/homeassistant/components/nextbus/icons.json b/homeassistant/components/nextbus/icons.json new file mode 100644 index 00000000000..7176a937e83 --- /dev/null +++ b/homeassistant/components/nextbus/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "nextbus": { + "default": "mdi:bus" + } + } + } +} diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index f62bf07eeef..eaedc50cf3b 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -105,7 +105,7 @@ class NextBusDepartureSensor( """ _attr_device_class = SensorDeviceClass.TIMESTAMP - _attr_icon = "mdi:bus" + _attr_translation_key = "nextbus" def __init__( self, From f02e60533d77bd110fadec93af70ff5c4bfd7b83 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 3 Mar 2024 14:08:33 +0100 Subject: [PATCH 0179/1691] Add icon translations to Nexia (#111980) * Add icon translations to Nexia * Add icon translations to Nexia --- homeassistant/components/nexia/icons.json | 27 +++++++++++++++++++++++ homeassistant/components/nexia/number.py | 1 - homeassistant/components/nexia/scene.py | 2 +- homeassistant/components/nexia/switch.py | 5 ----- tests/components/nexia/test_scene.py | 3 --- 5 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/nexia/icons.json diff --git a/homeassistant/components/nexia/icons.json b/homeassistant/components/nexia/icons.json new file mode 100644 index 00000000000..620d1a42c03 --- /dev/null +++ b/homeassistant/components/nexia/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "number": { + "fan_speed": { + "default": "mdi:fan" + } + }, + "scene": { + "automation": { + "default": "mdi:script-text-outline" + } + }, + "switch": { + "hold": { + "default": "mdi:timer", + "state": { + "on": "mdi:timer-off" + } + } + } + }, + "services": { + "set_aircleaner_mode": "mdi:air-filter", + "set_humidify_setpoint": "mdi:water-percent", + "set_hvac_run_mode": "mdi:hvac" + } +} diff --git a/homeassistant/components/nexia/number.py b/homeassistant/components/nexia/number.py index b44c6a4c48f..019d425d1ad 100644 --- a/homeassistant/components/nexia/number.py +++ b/homeassistant/components/nexia/number.py @@ -41,7 +41,6 @@ class NexiaFanSpeedEntity(NexiaThermostatEntity, NumberEntity): """Provides Nexia Fan Speed support.""" _attr_native_unit_of_measurement = PERCENTAGE - _attr_icon = "mdi:fan" _attr_translation_key = "fan_speed" def __init__( diff --git a/homeassistant/components/nexia/scene.py b/homeassistant/components/nexia/scene.py index 3a21c61badd..ba9e8b12dde 100644 --- a/homeassistant/components/nexia/scene.py +++ b/homeassistant/components/nexia/scene.py @@ -35,7 +35,7 @@ async def async_setup_entry( class NexiaAutomationScene(NexiaEntity, Scene): """Provides Nexia automation support.""" - _attr_icon = "mdi:script-text-outline" + _attr_translation_key = "automation" def __init__( self, coordinator: NexiaDataUpdateCoordinator, automation: NexiaAutomation diff --git a/homeassistant/components/nexia/switch.py b/homeassistant/components/nexia/switch.py index 7f191d39c73..87c3f6dc938 100644 --- a/homeassistant/components/nexia/switch.py +++ b/homeassistant/components/nexia/switch.py @@ -53,11 +53,6 @@ class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity): """Return if the zone is in hold mode.""" return self._zone.is_in_permanent_hold() - @property - def icon(self) -> str: - """Return the icon for the switch.""" - return "mdi:timer-off" if self._zone.is_in_permanent_hold() else "mdi:timer" - async def async_turn_on(self, **kwargs: Any) -> None: """Enable permanent hold.""" if self._zone.get_current_mode() == OPERATION_MODE_OFF: diff --git a/tests/components/nexia/test_scene.py b/tests/components/nexia/test_scene.py index 0678921f7ea..e01d843c298 100644 --- a/tests/components/nexia/test_scene.py +++ b/tests/components/nexia/test_scene.py @@ -30,7 +30,6 @@ async def test_automation_scenes(hass: HomeAssistant) -> None: "change Fan Mode to Auto" ), "friendly_name": "Away Short", - "icon": "mdi:script-text-outline", } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears @@ -51,7 +50,6 @@ async def test_automation_scenes(hass: HomeAssistant) -> None: "Activate the mode named 'Power Outage'" ), "friendly_name": "Power Outage", - "icon": "mdi:script-text-outline", } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears @@ -70,7 +68,6 @@ async def test_automation_scenes(hass: HomeAssistant) -> None: "'Home'" ), "friendly_name": "Power Restored", - "icon": "mdi:script-text-outline", } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears From 073fcfcd6f54f8da8fc150af48f17c361635f4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Strandberg?= Date: Sun, 3 Mar 2024 14:27:24 +0100 Subject: [PATCH 0180/1691] Add tests for number of devices created in myuplink (#111816) * Add tests for number of devices created * Change default fixture to 2 devices in one system * Update snapshot * Change deviuce to test * Merge tests to one module --- tests/components/myuplink/conftest.py | 2 +- .../myuplink/fixtures/device-2dev.json | 40 + .../myuplink/fixtures/systems-2dev.json | 34 + .../myuplink/snapshots/test_diagnostics.ambr | 1014 +++++++++++++++++ tests/components/myuplink/test_init.py | 33 +- tests/components/myuplink/test_number.py | 2 +- tests/components/myuplink/test_switch.py | 2 +- 7 files changed, 1123 insertions(+), 4 deletions(-) create mode 100644 tests/components/myuplink/fixtures/device-2dev.json create mode 100644 tests/components/myuplink/fixtures/systems-2dev.json diff --git a/tests/components/myuplink/conftest.py b/tests/components/myuplink/conftest.py index c1937a8ce3c..e6380b101cb 100644 --- a/tests/components/myuplink/conftest.py +++ b/tests/components/myuplink/conftest.py @@ -93,7 +93,7 @@ def load_systems_jv_file(load_systems_file: str) -> dict[str, Any]: @pytest.fixture(scope="session") def load_systems_file() -> str: """Load fixture file for systems.""" - return load_fixture("systems.json", DOMAIN) + return load_fixture("systems-2dev.json", DOMAIN) @pytest.fixture diff --git a/tests/components/myuplink/fixtures/device-2dev.json b/tests/components/myuplink/fixtures/device-2dev.json new file mode 100644 index 00000000000..96360f87ce7 --- /dev/null +++ b/tests/components/myuplink/fixtures/device-2dev.json @@ -0,0 +1,40 @@ +{ + "id": "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff", + "connectionState": "Connected", + "firmware": { + "currentFwVersion": "9682R7", + "desiredFwVersion": "9682R7" + }, + "product": { + "serialNumber": "222222", + "name": "F730 CU 3x400V" + }, + "availableFeatures": { + "settings": true, + "reboot": true, + "forcesync": true, + "forceUpdate": false, + "requestUpdate": false, + "resetAlarm": true, + "triggerEvent": true, + "getMenu": false, + "getMenuChain": false, + "getGuideQuestion": false, + "sendHaystack": true, + "setSmartMode": false, + "setAidMode": true, + "getZones": false, + "processIntent": false, + "boostHotWater": true, + "boostVentilation": true, + "getScheduleConfig": false, + "getScheduleModes": false, + "getScheduleWeekly": false, + "getScheduleVacation": false, + "setScheduleModes": false, + "setScheduleWeekly": false, + "setScheduleOverride": false, + "setScheduleVacation": false, + "setVentilationMode": false + } +} diff --git a/tests/components/myuplink/fixtures/systems-2dev.json b/tests/components/myuplink/fixtures/systems-2dev.json new file mode 100644 index 00000000000..0718fc2301d --- /dev/null +++ b/tests/components/myuplink/fixtures/systems-2dev.json @@ -0,0 +1,34 @@ +{ + "page": 1, + "itemsPerPage": 10, + "numItems": 1, + "systems": [ + { + "systemId": "123456-7890-1234", + "name": "Gotham City", + "securityLevel": "admin", + "hasAlarm": false, + "country": "Sweden", + "devices": [ + { + "id": "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff", + "connectionState": "Connected", + "currentFwVersion": "9682R7", + "product": { + "serialNumber": "222222", + "name": "F730 CU 3x400V" + } + }, + { + "id": "batman-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff", + "connectionState": "Connected", + "currentFwVersion": "9682R7", + "product": { + "serialNumber": "123456", + "name": "F730 CU 3x400V" + } + } + ] + } + ] +} diff --git a/tests/components/myuplink/snapshots/test_diagnostics.ambr b/tests/components/myuplink/snapshots/test_diagnostics.ambr index 49bdfeba93e..53664820364 100644 --- a/tests/components/myuplink/snapshots/test_diagnostics.ambr +++ b/tests/components/myuplink/snapshots/test_diagnostics.ambr @@ -1018,6 +1018,1011 @@ ''', }), }), + dict({ + '123456-7890-1234': dict({ + 'device_data': ''' + { + "id": "batman-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff", + "connectionState": "Connected", + "firmware": { + "currentFwVersion": "9682R7", + "desiredFwVersion": "9682R7" + }, + "product": { + "serialNumber": "123456", + "name": "F730 CU 3x400V" + }, + "availableFeatures": { + "settings": true, + "reboot": true, + "forcesync": true, + "forceUpdate": false, + "requestUpdate": false, + "resetAlarm": true, + "triggerEvent": true, + "getMenu": false, + "getMenuChain": false, + "getGuideQuestion": false, + "sendHaystack": true, + "setSmartMode": false, + "setAidMode": true, + "getZones": false, + "processIntent": false, + "boostHotWater": true, + "boostVentilation": true, + "getScheduleConfig": false, + "getScheduleModes": false, + "getScheduleWeekly": false, + "getScheduleVacation": false, + "setScheduleModes": false, + "setScheduleWeekly": false, + "setScheduleOverride": false, + "setScheduleVacation": false, + "setVentilationMode": false + } + } + + ''', + 'points': ''' + [ + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40004", + "parameterName": "Current outd temp (BT1)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:56:12+00:00", + "value": -9.3, + "strVal": "-9.3°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40008", + "parameterName": "Supply line (BT2)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:58:51+00:00", + "value": 39.7, + "strVal": "39.7°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40012", + "parameterName": "Return line (BT3)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:08:40+00:00", + "value": 34.4, + "strVal": "34.4°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40013", + "parameterName": "Hot water top (BT7)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T08:39:32+00:00", + "value": 46, + "strVal": "46°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40014", + "parameterName": "Hot water char­ging (BT6)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:58:42+00:00", + "value": 44.4, + "strVal": "44.4°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40017", + "parameterName": "Con­denser (BT12)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:41:49+00:00", + "value": 37.7, + "strVal": "37.7°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40018", + "parameterName": "Dis­charge (BT14)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:19:24+00:00", + "value": 89.1, + "strVal": "89.1°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40019", + "parameterName": "Liquid line (BT15)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:42:11+00:00", + "value": 34.4, + "strVal": "34.4°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40020", + "parameterName": "Evap­orator (BT16)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T11:11:00+00:00", + "value": -14.7, + "strVal": "-14.7°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40022", + "parameterName": "Suction gas (BT17)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T11:11:28+00:00", + "value": -1.1, + "strVal": "-1.1°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40025", + "parameterName": "Exhaust air (BT20)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T09:48:50+00:00", + "value": 22.5, + "strVal": "22.5°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40026", + "parameterName": "Extract air (BT21)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T11:11:15+00:00", + "value": -12.1, + "strVal": "-12.1°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40033", + "parameterName": "Room temp­erature (BT50)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T00:21:48+00:00", + "value": 21.2, + "strVal": "21.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40047", + "parameterName": "Supply line (BT61)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": -32768, + "strVal": "-32768°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40048", + "parameterName": "Return line (BT62)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": -32768, + "strVal": "-32768°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40050", + "parameterName": "Value, air veloc­ity sensor (BS1)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-09T11:10:42+00:00", + "value": 101.5, + "strVal": "101.5", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40067", + "parameterName": "Average outdoor temp (BT1)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:56:45+00:00", + "value": -12.2, + "strVal": "-12.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40079", + "parameterName": "Current (BE1)", + "parameterUnit": "A", + "writable": false, + "timestamp": "2024-02-09T09:05:50+00:00", + "value": 3.1, + "strVal": "3.1A", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40081", + "parameterName": "Current (BE2)", + "parameterUnit": "A", + "writable": false, + "timestamp": "2024-02-09T11:11:19+00:00", + "value": 0.3, + "strVal": "0.3A", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40083", + "parameterName": "Current (BE3)", + "parameterUnit": "A", + "writable": false, + "timestamp": "2024-02-09T09:46:11+00:00", + "value": 5.7, + "strVal": "5.7A", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40145", + "parameterName": "Oil temp­erature (EP15-BT29)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40146", + "parameterName": "Oil temp­erature (BT29)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-09T11:09:39+00:00", + "value": -875, + "strVal": "-875", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-09T11:09:39+00:00", + "value": -875, + "strVal": "-875", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "41778", + "parameterName": "Current com­pressor fre­quency", + "parameterUnit": "Hz", + "writable": false, + "timestamp": "2024-02-09T10:07:47+00:00", + "value": 57, + "strVal": "57Hz", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "42770", + "parameterName": "Desired humid­ity", + "parameterUnit": "%", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 0, + "strVal": "0%", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43009", + "parameterName": "Calcu­lated supply climate system 1", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T11:04:53+00:00", + "value": 37.9, + "strVal": "37.9°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43066", + "parameterName": "Defrost­ing time", + "parameterUnit": "s", + "writable": false, + "timestamp": "2024-02-09T09:45:41+00:00", + "value": 0, + "strVal": "0s", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-09T10:59:11+00:00", + "value": 1686.9, + "strVal": "1686.9", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43108", + "parameterName": "Current fan mode", + "parameterUnit": "%", + "writable": false, + "timestamp": "2024-02-08T16:27:27+00:00", + "value": 0, + "strVal": "0%", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43109", + "parameterName": "Current hot water mode", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-06T21:14:34+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43115", + "parameterName": "Hot water: charge set point value", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43116", + "parameterName": "Hot water: charge current value ((BT12 | BT63))", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43122", + "parameterName": "Min com­pressor fre­quency", + "parameterUnit": "Hz", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 20, + "strVal": "20Hz", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43123", + "parameterName": "Max com­pressor fre­quency", + "parameterUnit": "Hz", + "writable": false, + "timestamp": "2024-02-09T10:07:44+00:00", + "value": 57, + "strVal": "57Hz", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43124", + "parameterName": "Refer­ence, air speed sensor", + "parameterUnit": "m3/h", + "writable": false, + "timestamp": "2024-02-09T09:51:03+00:00", + "value": 127.6, + "strVal": "127.6m3/h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43125", + "parameterName": "De­crease from refer­ence value", + "parameterUnit": "%", + "writable": false, + "timestamp": "2024-02-09T11:08:28+00:00", + "value": -1.1, + "strVal": "-1.1%", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43140", + "parameterName": "Invert­er temp­erature", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:19:11+00:00", + "value": 37.2, + "strVal": "37.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43146", + "parameterName": "dT Invert­er - exh air (BT20)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-09T10:37:53+00:00", + "value": 14.9, + "strVal": "14.9°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43161", + "parameterName": "Extern. adjust­ment climate system 1", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 0, + "strVal": "Off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Off", + "icon": "" + }, + { + "value": "1", + "text": "On", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43427", + "parameterName": "Status com­pressor", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-09T09:46:01+00:00", + "value": 60, + "strVal": "runs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "20", + "text": "off", + "icon": "" + }, + { + "value": "40", + "text": "starts", + "icon": "" + }, + { + "value": "60", + "text": "runs", + "icon": "" + }, + { + "value": "100", + "text": "stops", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "43437", + "parameterName": "Heating medium pump speed (GP1)", + "parameterUnit": "%", + "writable": false, + "timestamp": "2024-02-09T10:34:44+00:00", + "value": 79, + "strVal": "79%", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "49633", + "parameterName": "Desired humid­ity", + "parameterUnit": "%", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 50, + "strVal": "50%", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "49993", + "parameterName": "Int elec add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-08T18:57:28+00:00", + "value": 6, + "strVal": "Active", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Alarm", + "icon": "" + }, + { + "value": "1", + "text": "Alarm", + "icon": "" + }, + { + "value": "2", + "text": "Active", + "icon": "" + }, + { + "value": "3", + "text": "Off", + "icon": "" + }, + { + "value": "4", + "text": "Blocked", + "icon": "" + }, + { + "value": "5", + "text": "Off", + "icon": "" + }, + { + "value": "6", + "text": "Active", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "49994", + "parameterName": "Prior­ity", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-08T19:13:05+00:00", + "value": 30, + "strVal": "Heating", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "10", + "text": "Off", + "icon": "" + }, + { + "value": "20", + "text": "Hot water", + "icon": "" + }, + { + "value": "30", + "text": "Heating", + "icon": "" + }, + { + "value": "40", + "text": "Pool", + "icon": "" + }, + { + "value": "41", + "text": "Pool 2", + "icon": "" + }, + { + "value": "50", + "text": "Trans­fer", + "icon": "" + }, + { + "value": "60", + "text": "Cooling", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "49995", + "parameterName": "Pump: Heating medium (GP1)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-01T14:30:32+00:00", + "value": 1, + "strVal": "On", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Off", + "icon": "" + }, + { + "value": "1", + "text": "On", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "50004", + "parameterName": "Tempo­rary lux", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-04T21:06:26+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + }, + { + "value": "1", + "text": "on", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "NIBEF F730 CU 3x400V", + "parameterId": "50005", + "parameterName": "In­creased venti­lation", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-08T16:27:26+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + }, + { + "value": "1", + "text": "on", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + } + ] + + ''', + }), + }), ]), 'itemsPerPage': 10, 'numItems': 1, @@ -1026,6 +2031,15 @@ dict({ 'country': 'Sweden', 'devices': list([ + dict({ + 'connectionState': 'Connected', + 'currentFwVersion': '9682R7', + 'id': 'robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff', + 'product': dict({ + 'name': 'F730 CU 3x400V', + 'serialNumber': '**REDACTED**', + }), + }), dict({ 'connectionState': 'Connected', 'currentFwVersion': '9682R7', diff --git a/tests/components/myuplink/test_init.py b/tests/components/myuplink/test_init.py index 728bcbc702f..17b899aa662 100644 --- a/tests/components/myuplink/test_init.py +++ b/tests/components/myuplink/test_init.py @@ -8,10 +8,11 @@ import pytest from homeassistant.components.myuplink.const import DOMAIN, OAUTH2_TOKEN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from . import setup_integration -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture from tests.test_util.aiohttp import AiohttpClientMocker @@ -66,3 +67,33 @@ async def test_expired_token_refresh_failure( await setup_integration(hass, mock_config_entry) assert mock_config_entry.state is expected_state + + +@pytest.mark.parametrize( + "load_systems_file", + [load_fixture("systems.json", DOMAIN)], +) +async def test_devices_created_count( + hass: HomeAssistant, + mock_myuplink_client: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that one device is created.""" + await setup_integration(hass, mock_config_entry) + + device_registry = dr.async_get(hass) + + assert len(device_registry.devices) == 1 + + +async def test_devices_multiple_created_count( + hass: HomeAssistant, + mock_myuplink_client: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that multiple device are created.""" + await setup_integration(hass, mock_config_entry) + + device_registry = dr.async_get(hass) + + assert len(device_registry.devices) == 2 diff --git a/tests/components/myuplink/test_number.py b/tests/components/myuplink/test_number.py index 158ef35dc77..09f361f0ec6 100644 --- a/tests/components/myuplink/test_number.py +++ b/tests/components/myuplink/test_number.py @@ -16,7 +16,7 @@ pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) ENTITY_ID = "number.f730_cu_3x400v_degree_minutes" ENTITY_FRIENDLY_NAME = "F730 CU 3x400V Degree minutes" -ENTITY_UID = "batman-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-40940" +ENTITY_UID = "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-40940" async def test_entity_registry( diff --git a/tests/components/myuplink/test_switch.py b/tests/components/myuplink/test_switch.py index cbc60cbfc0a..06855fd91da 100644 --- a/tests/components/myuplink/test_switch.py +++ b/tests/components/myuplink/test_switch.py @@ -21,7 +21,7 @@ pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) ENTITY_ID = "switch.f730_cu_3x400v_temporary_lux" ENTITY_FRIENDLY_NAME = "F730 CU 3x400V Tempo­rary lux" -ENTITY_UID = "batman-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-50004" +ENTITY_UID = "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-50004" async def test_entity_registry( From 9fff638311c1a441a4c3e74483c3ed7ce801547d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 04:56:08 -1000 Subject: [PATCH 0181/1691] Load wyoming in the executor to avoid blocking the event loop (#112107) --- homeassistant/components/wyoming/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/wyoming/manifest.json b/homeassistant/components/wyoming/manifest.json index 830ba5a3435..0300f420c35 100644 --- a/homeassistant/components/wyoming/manifest.json +++ b/homeassistant/components/wyoming/manifest.json @@ -5,6 +5,7 @@ "config_flow": true, "dependencies": ["assist_pipeline"], "documentation": "https://www.home-assistant.io/integrations/wyoming", + "import_executor": true, "iot_class": "local_push", "requirements": ["wyoming==1.5.3"], "zeroconf": ["_wyoming._tcp.local."] From 13653be09b4bac1d7165ac1138563e4c37ad16c7 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sun, 3 Mar 2024 17:15:54 +0100 Subject: [PATCH 0182/1691] Add event platform to rfxtrx (#111526) --- homeassistant/components/rfxtrx/__init__.py | 1 + homeassistant/components/rfxtrx/event.py | 94 ++++++++++++++ homeassistant/components/rfxtrx/strings.json | 88 +++++++++++++ .../rfxtrx/snapshots/test_event.ambr | 121 ++++++++++++++++++ tests/components/rfxtrx/test_event.py | 102 +++++++++++++++ 5 files changed, 406 insertions(+) create mode 100644 homeassistant/components/rfxtrx/event.py create mode 100644 tests/components/rfxtrx/snapshots/test_event.ambr create mode 100644 tests/components/rfxtrx/test_event.py diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 0f3988442c7..ce17316e6c7 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -79,6 +79,7 @@ SERVICE_SEND_SCHEMA = vol.Schema({ATTR_EVENT: _bytearray_string}) PLATFORMS = [ Platform.BINARY_SENSOR, Platform.COVER, + Platform.EVENT, Platform.LIGHT, Platform.SENSOR, Platform.SIREN, diff --git a/homeassistant/components/rfxtrx/event.py b/homeassistant/components/rfxtrx/event.py new file mode 100644 index 00000000000..de4b32c5475 --- /dev/null +++ b/homeassistant/components/rfxtrx/event.py @@ -0,0 +1,94 @@ +"""Support for RFXtrx sensors.""" +from __future__ import annotations + +import logging +from typing import Any + +from RFXtrx import ControlEvent, RFXtrxDevice, RFXtrxEvent, SensorEvent + +from homeassistant.components.event import EventEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import slugify + +from . import DeviceTuple, RfxtrxEntity, async_setup_platform_entry + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up config entry.""" + + def _supported(event: RFXtrxEvent) -> bool: + return isinstance(event, (ControlEvent, SensorEvent)) + + def _constructor( + event: RFXtrxEvent, + auto: RFXtrxEvent | None, + device_id: DeviceTuple, + entity_info: dict[str, Any], + ) -> list[Entity]: + entities: list[Entity] = [] + + if hasattr(event.device, "COMMANDS"): + entities.append( + RfxtrxEventEntity( + event.device, device_id, "COMMANDS", "Command", "command" + ) + ) + + if hasattr(event.device, "STATUS"): + entities.append( + RfxtrxEventEntity( + event.device, device_id, "STATUS", "Sensor Status", "status" + ) + ) + + return entities + + await async_setup_platform_entry( + hass, config_entry, async_add_entities, _supported, _constructor + ) + + +class RfxtrxEventEntity(RfxtrxEntity, EventEntity): + """Representation of a RFXtrx event.""" + + def __init__( + self, + device: RFXtrxDevice, + device_id: DeviceTuple, + device_attribute: str, + value_attribute: str, + translation_key: str, + ) -> None: + """Initialize the sensor.""" + super().__init__(device, device_id) + commands: dict[int, str] = getattr(device, device_attribute) + self._attr_name = None + self._attr_unique_id = "_".join(x for x in device_id) + self._attr_event_types = [slugify(command) for command in commands.values()] + self._attr_translation_key = translation_key + self._value_attribute = value_attribute + + @callback + def _handle_event(self, event: RFXtrxEvent, device_id: DeviceTuple) -> None: + """Check if event applies to me and update.""" + if not self._event_applies(event, device_id): + return + + assert isinstance(event, (ControlEvent, SensorEvent)) + + event_type = slugify(event.values[self._value_attribute]) + if event_type not in self._attr_event_types: + _LOGGER.warning("Event type %s is not known", event_type) + return + + self._trigger_event(event_type, event.values) + self.async_write_ha_state() diff --git a/homeassistant/components/rfxtrx/strings.json b/homeassistant/components/rfxtrx/strings.json index 9b99553d3f0..aeb4b2395d3 100644 --- a/homeassistant/components/rfxtrx/strings.json +++ b/homeassistant/components/rfxtrx/strings.json @@ -83,6 +83,94 @@ } }, "entity": { + "event": { + "command": { + "state_attributes": { + "event_type": { + "state": { + "sound_0": "Sound 0", + "sound_1": "Sound 1", + "sound_2": "Sound 2", + "sound_3": "Sound 3", + "sound_4": "Sound 4", + "sound_5": "Sound 5", + "sound_6": "Sound 6", + "sound_7": "Sound 7", + "sound_8": "Sound 8", + "sound_9": "Sound 9", + "sound_10": "Sound 10", + "sound_11": "Sound 11", + "sound_12": "Sound 12", + "sound_13": "Sound 13", + "sound_14": "Sound 14", + "sound_15": "Sound 15", + "down": "Down", + "up": "Up", + "all_off": "All Off", + "all_on": "All On", + "scene": "Scene", + "off": "Off", + "on": "On", + "dim": "Dim", + "bright": "Bright", + "all_group_off": "All/group Off", + "all_group_on": "All/group On", + "chime": "Chime", + "illegal_command": "Illegal command", + "set_level": "Set level", + "group_off": "Group off", + "group_on": "Group on", + "set_group_level": "Set group level", + "level_1": "Level 1", + "level_2": "Level 2", + "level_3": "Level 3", + "level_4": "Level 4", + "level_5": "Level 5", + "level_6": "Level 6", + "level_7": "Level 7", + "level_8": "Level 8", + "level_9": "Level 9", + "program": "Program", + "stop": "Stop", + "0_5_seconds_up": "0.5 Seconds Up", + "0_5_seconds_down": "0.5 Seconds Down", + "2_seconds_up": "2 Seconds Up", + "2_seconds_down": "2 Seconds Down", + "enable_sun_automation": "Enable sun automation", + "disable_sun_automation": "Disable sun automation", + "normal": "Normal", + "normal_delayed": "Normal Delayed", + "alarm": "Alarm", + "alarm_delayed": "Alarm Delayed", + "motion": "Motion", + "no_motion": "No Motion", + "panic": "Panic", + "end_panic": "End Panic", + "ir": "IR", + "arm_away": "Arm Away", + "arm_away_delayed": "Arm Away Delayed", + "arm_home": "Arm Home", + "arm_home_delayed": "Arm Home Delayed", + "disarm": "Disarm", + "light_1_off": "Light 1 Off", + "light_1_on": "Light 1 On", + "light_2_off": "Light 2 Off", + "light_2_on": "Light 2 On", + "dark_detected": "Dark Detected", + "light_detected": "Light Detected", + "battery_low": "Battery low", + "pairing_kd101": "Pairing KD101", + "normal_tamper": "Normal Tamper", + "normal_delayed_tamper": "Normal Delayed Tamper", + "alarm_tamper": "Alarm Tamper", + "alarm_delayed_tamper": "Alarm Delayed Tamper", + "motion_tamper": "Motion Tamper", + "no_motion_tamper": "No Motion Tamper" + } + } + } + } + }, "sensor": { "current_ch_1": { "name": "Current Ch. 1" diff --git a/tests/components/rfxtrx/snapshots/test_event.ambr b/tests/components/rfxtrx/snapshots/test_event.ambr new file mode 100644 index 00000000000..99bb0195c65 --- /dev/null +++ b/tests/components/rfxtrx/snapshots/test_event.ambr @@ -0,0 +1,121 @@ +# serializer version: 1 +# name: test_control_event.2 + ... +# --- +# name: test_control_event.3 + ... + + 'Command': 'On', + + 'Rssi numeric': 5, + ... + - 'event_type': None, + + 'event_type': 'on', + ... + - 'state': 'unknown', + + 'state': '2021-01-09T12:00:00.000+00:00', + ... +# --- +# name: test_control_event[1] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'assumed_state': True, + 'event_type': None, + 'event_types': list([ + 'off', + 'on', + 'dim', + 'bright', + 'all_group_off', + 'all_group_on', + 'chime', + 'illegal_command', + ]), + 'friendly_name': 'ARC C1', + }), + 'context': , + 'entity_id': 'event.arc_c1', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_control_event[2] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'assumed_state': True, + 'event_type': None, + 'event_types': list([ + 'off', + 'on', + 'dim', + 'bright', + 'all_group_off', + 'all_group_on', + 'chime', + 'illegal_command', + ]), + 'friendly_name': 'ARC D1', + }), + 'context': , + 'entity_id': 'event.arc_d1', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_status_event.1 + ... + + 'Battery numeric': 9, + + 'Rssi numeric': 8, + + 'Sensor Status': 'Normal', + ... + - 'event_type': None, + + 'event_type': 'normal', + ... + - 'state': 'unknown', + + 'state': '2021-01-09T12:00:00.000+00:00', + ... +# --- +# name: test_status_event[1] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'assumed_state': True, + 'event_type': None, + 'event_types': list([ + 'normal', + 'normal_delayed', + 'alarm', + 'alarm_delayed', + 'motion', + 'no_motion', + 'panic', + 'end_panic', + 'ir', + 'arm_away', + 'arm_away_delayed', + 'arm_home', + 'arm_home_delayed', + 'disarm', + 'light_1_off', + 'light_1_on', + 'light_2_off', + 'light_2_on', + 'dark_detected', + 'light_detected', + 'battery_low', + 'pairing_kd101', + 'normal_tamper', + 'normal_delayed_tamper', + 'alarm_tamper', + 'alarm_delayed_tamper', + 'motion_tamper', + 'no_motion_tamper', + ]), + 'friendly_name': 'X10 Security d3dc54:32', + }), + 'context': , + 'entity_id': 'event.x10_security_d3dc54_32', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/rfxtrx/test_event.py b/tests/components/rfxtrx/test_event.py new file mode 100644 index 00000000000..d0322c3ed82 --- /dev/null +++ b/tests/components/rfxtrx/test_event.py @@ -0,0 +1,102 @@ +"""The tests for the Rfxtrx sensor platform.""" +from unittest.mock import patch + +from freezegun.api import FrozenDateTimeFactory +import pytest +from RFXtrx import ControlEvent +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.rfxtrx import get_rfx_object +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from .conftest import setup_rfx_test_cfg + + +@pytest.fixture(autouse=True) +def required_platforms_only(): + """Only set up the required platform and required base platforms to speed up tests.""" + with patch( + "homeassistant.components.rfxtrx.PLATFORMS", + (Platform.EVENT,), + ): + yield + + +async def test_control_event( + hass: HomeAssistant, + rfxtrx, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, +) -> None: + """Test event update updates correct event object.""" + hass.config.set_time_zone("UTC") + freezer.move_to("2021-01-09 12:00:00+00:00") + + await setup_rfx_test_cfg( + hass, + devices={ + "0710013d43010150": {}, + "0710013d44010150": {}, + }, + ) + + assert hass.states.get("event.arc_c1") == snapshot(name="1") + assert hass.states.get("event.arc_d1") == snapshot(name="2") + + # only signal one, to make sure we have no overhearing + await rfxtrx.signal("0710013d44010150") + + assert hass.states.get("event.arc_c1") == snapshot(diff="1") + assert hass.states.get("event.arc_d1") == snapshot(diff="2") + + +async def test_status_event( + hass: HomeAssistant, + rfxtrx, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, +) -> None: + """Test event update updates correct event object.""" + hass.config.set_time_zone("UTC") + freezer.move_to("2021-01-09 12:00:00+00:00") + + await setup_rfx_test_cfg( + hass, + devices={ + "0820004dd3dc540089": {}, + }, + ) + + assert hass.states.get("event.x10_security_d3dc54_32") == snapshot(name="1") + + await rfxtrx.signal("0820004dd3dc540089") + + assert hass.states.get("event.x10_security_d3dc54_32") == snapshot(diff="1") + + +async def test_invalid_event_type( + hass: HomeAssistant, + rfxtrx, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, +) -> None: + """Test with 1 sensor.""" + await setup_rfx_test_cfg( + hass, + devices={ + "0710013d43010150": {}, + }, + ) + + state = hass.states.get("event.arc_c1") + + # Invalid event type should not trigger change + event = get_rfx_object("0710013d43010150") + assert isinstance(event, ControlEvent) + event.values["Command"] = "invalid_command" + + rfxtrx.event_callback(event) + await hass.async_block_till_done() + + assert hass.states.get("event.arc_c1") == state From 7af82161a05dbda27b8b8ef3479f7cad900f87cf Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 3 Mar 2024 11:31:23 -0500 Subject: [PATCH 0183/1691] Bump Zigpy to 0.63.4 (#112117) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 225837c66fc..fc050c9b2d1 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -27,7 +27,7 @@ "pyserial-asyncio==0.6", "zha-quirks==0.0.112", "zigpy-deconz==0.23.1", - "zigpy==0.63.3", + "zigpy==0.63.4", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", diff --git a/requirements_all.txt b/requirements_all.txt index 4700e9a93f6..a55f8d567bb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2947,7 +2947,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.63.3 +zigpy==0.63.4 # homeassistant.components.zoneminder zm-py==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2779cff43d8..b15960bf476 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2267,7 +2267,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.63.3 +zigpy==0.63.4 # homeassistant.components.zwave_js zwave-js-server-python==0.55.3 From a37c82e34cbf19ea4d14a3686c2437fefbaa9284 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 07:00:35 -1000 Subject: [PATCH 0184/1691] Add comment to integration platform helper about blocking I/O (#112103) --- homeassistant/helpers/integration_platform.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index aebbc854693..8ba0790a874 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -48,6 +48,16 @@ def _get_platform( ) return None + # + # Loading the platform may do quite a bit of blocking I/O + # and CPU work. (https://github.com/python/cpython/issues/92041) + # + # We don't want to block the event loop for too + # long so we check if the platform exists with `platform_exists` + # before trying to load it. `platform_exists` will do two + # `stat()` system calls which is far cheaper than calling + # `integration.get_platform` + # if integration.platform_exists(platform_name) is False: # If the platform cannot possibly exist, don't bother trying to load it return None From 911cf606782713f3e1ae064bafb9de5e586d62b1 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 3 Mar 2024 18:10:22 +0100 Subject: [PATCH 0185/1691] Address late review for Ping (#112123) * Address late review for Ping * Fix import --- homeassistant/components/ping/binary_sensor.py | 2 +- homeassistant/components/ping/entity.py | 4 +++- homeassistant/components/ping/sensor.py | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index eacf718b6a2..5c1435cc3b1 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -95,7 +95,7 @@ class PingBinarySensor(PingEntity, BinarySensorEntity): self, config_entry: ConfigEntry, coordinator: PingUpdateCoordinator ) -> None: """Initialize the Ping Binary sensor.""" - super().__init__(coordinator, config_entry.entry_id) + super().__init__(config_entry, coordinator, config_entry.entry_id) # if this was imported just enable it when it was enabled before if CONF_IMPORTED_BY in config_entry.data: diff --git a/homeassistant/components/ping/entity.py b/homeassistant/components/ping/entity.py index 1332b82e9a8..8854634e898 100644 --- a/homeassistant/components/ping/entity.py +++ b/homeassistant/components/ping/entity.py @@ -1,4 +1,5 @@ """Base entity for the Ping component.""" +from homeassistant.config_entries import ConfigEntry from homeassistant.core import DOMAIN from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -13,6 +14,7 @@ class PingEntity(CoordinatorEntity[PingUpdateCoordinator]): def __init__( self, + config_entry: ConfigEntry, coordinator: PingUpdateCoordinator, unique_id: str, ) -> None: @@ -21,6 +23,6 @@ class PingEntity(CoordinatorEntity[PingUpdateCoordinator]): self._attr_unique_id = unique_id self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, self.coordinator.data.ip_address)}, + identifiers={(DOMAIN, config_entry.entry_id)}, manufacturer="Ping", ) diff --git a/homeassistant/components/ping/sensor.py b/homeassistant/components/ping/sensor.py index 5010c93f314..f5e5de81aac 100644 --- a/homeassistant/components/ping/sensor.py +++ b/homeassistant/components/ping/sensor.py @@ -101,7 +101,9 @@ class PingSensor(PingEntity, SensorEntity): coordinator: PingUpdateCoordinator, ) -> None: """Initialize the sensor.""" - super().__init__(coordinator, f"{config_entry.entry_id}-{description.key}") + super().__init__( + config_entry, coordinator, f"{config_entry.entry_id}-{description.key}" + ) self.entity_description = description From faee9d996d13b5026ff6a889ea746e0e5162ac83 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 07:21:55 -1000 Subject: [PATCH 0186/1691] Import steamist in the executor to avoid blocking the loop (#112111) --- homeassistant/components/steamist/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/steamist/manifest.json b/homeassistant/components/steamist/manifest.json index 91ebc7f6a21..a703ed588bc 100644 --- a/homeassistant/components/steamist/manifest.json +++ b/homeassistant/components/steamist/manifest.json @@ -14,6 +14,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/steamist", + "import_executor": true, "iot_class": "local_polling", "loggers": ["aiosteamist", "discovery30303"], "requirements": ["aiosteamist==0.3.2", "discovery30303==0.2.1"] From cdd7b94a9571f4ee0e082858ac0cd9f34229a891 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 3 Mar 2024 18:24:04 +0100 Subject: [PATCH 0187/1691] Move all System Monitor updates into single Data Update Coordinator (#112055) --- .../components/systemmonitor/__init__.py | 23 +- .../components/systemmonitor/binary_sensor.py | 56 ++-- .../components/systemmonitor/const.py | 2 +- .../components/systemmonitor/coordinator.py | 315 +++++++++--------- .../components/systemmonitor/diagnostics.py | 18 +- .../components/systemmonitor/sensor.py | 297 ++++++++--------- .../snapshots/test_diagnostics.ambr | 73 ++-- tests/components/systemmonitor/test_sensor.py | 10 +- 8 files changed, 395 insertions(+), 399 deletions(-) diff --git a/homeassistant/components/systemmonitor/__init__.py b/homeassistant/components/systemmonitor/__init__.py index 9fc5c91f085..25c131e547c 100644 --- a/homeassistant/components/systemmonitor/__init__.py +++ b/homeassistant/components/systemmonitor/__init__.py @@ -10,7 +10,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .const import DOMAIN +from .const import DOMAIN, DOMAIN_COORDINATOR +from .coordinator import SystemMonitorCoordinator +from .util import get_all_disk_mounts _LOGGER = logging.getLogger(__name__) @@ -21,6 +23,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up System Monitor from a config entry.""" psutil_wrapper = await hass.async_add_executor_job(ha_psutil.PsutilWrapper) hass.data[DOMAIN] = psutil_wrapper + + disk_arguments = list(await hass.async_add_executor_job(get_all_disk_mounts, hass)) + legacy_resources: set[str] = set(entry.options.get("resources", [])) + for resource in legacy_resources: + if resource.startswith("disk_"): + split_index = resource.rfind("_") + _type = resource[:split_index] + argument = resource[split_index + 1 :] + _LOGGER.debug("Loading legacy %s with argument %s", _type, argument) + disk_arguments.append(argument) + + _LOGGER.debug("disk arguments to be added: %s", disk_arguments) + + coordinator: SystemMonitorCoordinator = SystemMonitorCoordinator( + hass, psutil_wrapper, disk_arguments + ) + await coordinator.async_config_entry_first_refresh() + hass.data[DOMAIN_COORDINATOR] = coordinator + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(update_listener)) return True diff --git a/homeassistant/components/systemmonitor/binary_sensor.py b/homeassistant/components/systemmonitor/binary_sensor.py index 89c2e9d854e..97dd0c97f93 100644 --- a/homeassistant/components/systemmonitor/binary_sensor.py +++ b/homeassistant/components/systemmonitor/binary_sensor.py @@ -7,10 +7,9 @@ from dataclasses import dataclass from functools import lru_cache import logging import sys -from typing import Generic, Literal +from typing import Literal -from psutil import NoSuchProcess, Process -import psutil_home_assistant as ha_psutil +from psutil import NoSuchProcess from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, @@ -26,8 +25,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import slugify -from .const import CONF_PROCESS, DOMAIN -from .coordinator import MonitorCoordinator, SystemMonitorProcessCoordinator, dataT +from .const import CONF_PROCESS, DOMAIN, DOMAIN_COORDINATOR +from .coordinator import SystemMonitorCoordinator _LOGGER = logging.getLogger(__name__) @@ -51,10 +50,10 @@ def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]: return "mdi:cpu-32-bit" -def get_process(entity: SystemMonitorSensor[list[Process]]) -> bool: +def get_process(entity: SystemMonitorSensor) -> bool: """Return process.""" state = False - for proc in entity.coordinator.data: + for proc in entity.coordinator.data.processes: try: _LOGGER.debug("process %s for argument %s", proc.name(), entity.argument) if entity.argument == proc.name(): @@ -70,21 +69,21 @@ def get_process(entity: SystemMonitorSensor[list[Process]]) -> bool: @dataclass(frozen=True, kw_only=True) -class SysMonitorBinarySensorEntityDescription( - BinarySensorEntityDescription, Generic[dataT] -): +class SysMonitorBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes System Monitor binary sensor entities.""" - value_fn: Callable[[SystemMonitorSensor[dataT]], bool] + value_fn: Callable[[SystemMonitorSensor], bool] + add_to_update: Callable[[SystemMonitorSensor], tuple[str, str]] -SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription[list[Process]], ...] = ( - SysMonitorBinarySensorEntityDescription[list[Process]]( +SENSOR_TYPES: tuple[SysMonitorBinarySensorEntityDescription, ...] = ( + SysMonitorBinarySensorEntityDescription( key="binary_process", translation_key="process", icon=get_cpu_icon(), value_fn=get_process, device_class=BinarySensorDeviceClass.RUNNING, + add_to_update=lambda entity: ("processes", ""), ), ) @@ -93,20 +92,15 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up System Montor binary sensors based on a config entry.""" - psutil_wrapper: ha_psutil.PsutilWrapper = hass.data[DOMAIN] - entities: list[SystemMonitorSensor] = [] - process_coordinator = SystemMonitorProcessCoordinator( - hass, psutil_wrapper, "Process coordinator" - ) - await process_coordinator.async_request_refresh() + coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR] for sensor_description in SENSOR_TYPES: _entry = entry.options.get(BINARY_SENSOR_DOMAIN, {}) for argument in _entry.get(CONF_PROCESS, []): entities.append( SystemMonitorSensor( - process_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -116,18 +110,18 @@ async def async_setup_entry( class SystemMonitorSensor( - CoordinatorEntity[MonitorCoordinator[dataT]], BinarySensorEntity + CoordinatorEntity[SystemMonitorCoordinator], BinarySensorEntity ): """Implementation of a system monitor binary sensor.""" _attr_has_entity_name = True _attr_entity_category = EntityCategory.DIAGNOSTIC - entity_description: SysMonitorBinarySensorEntityDescription[dataT] + entity_description: SysMonitorBinarySensorEntityDescription def __init__( self, - coordinator: MonitorCoordinator[dataT], - sensor_description: SysMonitorBinarySensorEntityDescription[dataT], + coordinator: SystemMonitorCoordinator, + sensor_description: SysMonitorBinarySensorEntityDescription, entry_id: str, argument: str, ) -> None: @@ -144,6 +138,20 @@ class SystemMonitorSensor( ) self.argument = argument + async def async_added_to_hass(self) -> None: + """When added to hass.""" + self.coordinator.update_subscribers[ + self.entity_description.add_to_update(self) + ].add(self.entity_id) + return await super().async_added_to_hass() + + async def async_will_remove_from_hass(self) -> None: + """When removed from hass.""" + self.coordinator.update_subscribers[ + self.entity_description.add_to_update(self) + ].remove(self.entity_id) + return await super().async_will_remove_from_hass() + @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/systemmonitor/const.py b/homeassistant/components/systemmonitor/const.py index 1f254ca92d6..4a6000323d5 100644 --- a/homeassistant/components/systemmonitor/const.py +++ b/homeassistant/components/systemmonitor/const.py @@ -1,7 +1,7 @@ """Constants for System Monitor.""" DOMAIN = "systemmonitor" -DOMAIN_COORDINATORS = "systemmonitor_coordinators" +DOMAIN_COORDINATOR = "systemmonitor_coordinator" CONF_INDEX = "index" CONF_PROCESS = "process" diff --git a/homeassistant/components/systemmonitor/coordinator.py b/homeassistant/components/systemmonitor/coordinator.py index 6f93b9ddce8..6ce2836fcde 100644 --- a/homeassistant/components/systemmonitor/coordinator.py +++ b/homeassistant/components/systemmonitor/coordinator.py @@ -2,11 +2,11 @@ from __future__ import annotations -from abc import abstractmethod +from dataclasses import dataclass from datetime import datetime import logging import os -from typing import NamedTuple, TypeVar +from typing import Any, NamedTuple from psutil import Process from psutil._common import sdiskusage, shwtemp, snetio, snicaddr, sswap @@ -14,15 +14,43 @@ import psutil_home_assistant as ha_psutil from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL -from homeassistant.helpers.update_coordinator import ( - TimestampDataUpdateCoordinator, - UpdateFailed, -) +from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) +@dataclass(frozen=True, kw_only=True, slots=True) +class SensorData: + """Sensor data.""" + + disk_usage: dict[str, sdiskusage] + swap: sswap + memory: VirtualMemory + io_counters: dict[str, snetio] + addresses: dict[str, list[snicaddr]] + load: tuple[float, float, float] + cpu_percent: float | None + boot_time: datetime + processes: list[Process] + temperatures: dict[str, list[shwtemp]] + + def as_dict(self) -> dict[str, Any]: + """Return as dict.""" + return { + "disk_usage": {k: str(v) for k, v in self.disk_usage.items()}, + "swap": str(self.swap), + "memory": str(self.memory), + "io_counters": {k: str(v) for k, v in self.io_counters.items()}, + "addresses": {k: str(v) for k, v in self.addresses.items()}, + "load": str(self.load), + "cpu_percent": str(self.cpu_percent), + "boot_time": str(self.boot_time), + "processes": str(self.processes), + "temperatures": {k: str(v) for k, v in self.temperatures.items()}, + } + + class VirtualMemory(NamedTuple): """Represents virtual memory. @@ -37,177 +65,148 @@ class VirtualMemory(NamedTuple): free: float -dataT = TypeVar( - "dataT", - bound=datetime - | dict[str, list[shwtemp]] - | dict[str, list[snicaddr]] - | dict[str, snetio] - | float - | list[Process] - | sswap - | VirtualMemory - | tuple[float, float, float] - | sdiskusage - | None, -) - - -class MonitorCoordinator(TimestampDataUpdateCoordinator[dataT]): - """A System monitor Base Data Update Coordinator.""" - - def __init__( - self, hass: HomeAssistant, psutil_wrapper: ha_psutil.PsutilWrapper, name: str - ) -> None: - """Initialize the coordinator.""" - super().__init__( - hass, - _LOGGER, - name=f"System Monitor {name}", - update_interval=DEFAULT_SCAN_INTERVAL, - always_update=False, - ) - self._psutil = psutil_wrapper.psutil - - async def _async_update_data(self) -> dataT: - """Fetch data.""" - return await self.hass.async_add_executor_job(self.update_data) - - @abstractmethod - def update_data(self) -> dataT: - """To be extended by data update coordinators.""" - - -class SystemMonitorDiskCoordinator(MonitorCoordinator[sdiskusage]): - """A System monitor Disk Data Update Coordinator.""" +class SystemMonitorCoordinator(TimestampDataUpdateCoordinator[SensorData]): + """A System monitor Data Update Coordinator.""" def __init__( self, hass: HomeAssistant, psutil_wrapper: ha_psutil.PsutilWrapper, - name: str, - argument: str, + arguments: list[str], ) -> None: - """Initialize the disk coordinator.""" - super().__init__(hass, psutil_wrapper, name) - self._argument = argument + """Initialize the coordinator.""" + super().__init__( + hass, + _LOGGER, + name="System Monitor update coordinator", + update_interval=DEFAULT_SCAN_INTERVAL, + always_update=False, + ) + self._psutil = psutil_wrapper.psutil + self._arguments = arguments + self.boot_time: datetime | None = None - def update_data(self) -> sdiskusage: + self._initial_update: bool = True + self.update_subscribers: dict[ + tuple[str, str], set[str] + ] = self.set_subscribers_tuples(arguments) + + def set_subscribers_tuples( + self, arguments: list[str] + ) -> dict[tuple[str, str], set[str]]: + """Set tuples in subscribers dictionary.""" + _disk_defaults: dict[tuple[str, str], set[str]] = {} + for argument in arguments: + _disk_defaults[("disks", argument)] = set() + return { + **_disk_defaults, + ("swap", ""): set(), + ("memory", ""): set(), + ("io_counters", ""): set(), + ("addresses", ""): set(), + ("load", ""): set(), + ("cpu_percent", ""): set(), + ("boot", ""): set(), + ("processes", ""): set(), + ("temperatures", ""): set(), + } + + async def _async_update_data(self) -> SensorData: """Fetch data.""" - try: - usage: sdiskusage = self._psutil.disk_usage(self._argument) - _LOGGER.debug("sdiskusage: %s", usage) - return usage - except PermissionError as err: - raise UpdateFailed(f"No permission to access {self._argument}") from err - except OSError as err: - raise UpdateFailed(f"OS error for {self._argument}") from err + _LOGGER.debug("Update list is: %s", self.update_subscribers) + _data = await self.hass.async_add_executor_job(self.update_data) -class SystemMonitorSwapCoordinator(MonitorCoordinator[sswap]): - """A System monitor Swap Data Update Coordinator.""" + load: tuple = (None, None, None) + if self.update_subscribers[("load", "")] or self._initial_update: + load = os.getloadavg() + _LOGGER.debug("Load: %s", load) - def update_data(self) -> sswap: - """Fetch data.""" - swap: sswap = self._psutil.swap_memory() - _LOGGER.debug("sswap: %s", swap) - return swap + cpu_percent: float | None = None + if self.update_subscribers[("cpu_percent", "")] or self._initial_update: + cpu_percent = self._psutil.cpu_percent(interval=None) + _LOGGER.debug("cpu_percent: %s", cpu_percent) - -class SystemMonitorMemoryCoordinator(MonitorCoordinator[VirtualMemory]): - """A System monitor Memory Data Update Coordinator.""" - - def update_data(self) -> VirtualMemory: - """Fetch data.""" - memory = self._psutil.virtual_memory() - _LOGGER.debug("memory: %s", memory) - return VirtualMemory( - memory.total, memory.available, memory.percent, memory.used, memory.free + self._initial_update = False + return SensorData( + disk_usage=_data["disks"], + swap=_data["swap"], + memory=_data["memory"], + io_counters=_data["io_counters"], + addresses=_data["addresses"], + load=load, + cpu_percent=cpu_percent, + boot_time=_data["boot_time"], + processes=_data["processes"], + temperatures=_data["temperatures"], ) + def update_data(self) -> dict[str, Any]: + """To be extended by data update coordinators.""" + disks: dict[str, sdiskusage] = {} + for argument in self._arguments: + if self.update_subscribers[("disks", argument)] or self._initial_update: + try: + usage: sdiskusage = self._psutil.disk_usage(argument) + _LOGGER.debug("sdiskusagefor %s: %s", argument, usage) + except PermissionError as err: + _LOGGER.warning( + "No permission to access %s, error %s", argument, err + ) + except OSError as err: + _LOGGER.warning("OS error for %s, error %s", argument, err) + else: + disks[argument] = usage -class SystemMonitorNetIOCoordinator(MonitorCoordinator[dict[str, snetio]]): - """A System monitor Network IO Data Update Coordinator.""" + swap: sswap | None = None + if self.update_subscribers[("swap", "")] or self._initial_update: + swap = self._psutil.swap_memory() + _LOGGER.debug("sswap: %s", swap) - def update_data(self) -> dict[str, snetio]: - """Fetch data.""" - io_counters: dict[str, snetio] = self._psutil.net_io_counters(pernic=True) - _LOGGER.debug("io_counters: %s", io_counters) - return io_counters + memory = None + if self.update_subscribers[("memory", "")] or self._initial_update: + memory = self._psutil.virtual_memory() + _LOGGER.debug("memory: %s", memory) + memory = VirtualMemory( + memory.total, memory.available, memory.percent, memory.used, memory.free + ) + io_counters: dict[str, snetio] | None = None + if self.update_subscribers[("io_counters", "")] or self._initial_update: + io_counters = self._psutil.net_io_counters(pernic=True) + _LOGGER.debug("io_counters: %s", io_counters) -class SystemMonitorNetAddrCoordinator(MonitorCoordinator[dict[str, list[snicaddr]]]): - """A System monitor Network Address Data Update Coordinator.""" + addresses: dict[str, list[snicaddr]] | None = None + if self.update_subscribers[("addresses", "")] or self._initial_update: + addresses = self._psutil.net_if_addrs() + _LOGGER.debug("ip_addresses: %s", addresses) - def update_data(self) -> dict[str, list[snicaddr]]: - """Fetch data.""" - addresses: dict[str, list[snicaddr]] = self._psutil.net_if_addrs() - _LOGGER.debug("ip_addresses: %s", addresses) - return addresses + if self._initial_update: + # Boot time only needs to refresh on first pass + self.boot_time = dt_util.utc_from_timestamp(self._psutil.boot_time()) + _LOGGER.debug("boot time: %s", self.boot_time) + processes = None + if self.update_subscribers[("processes", "")] or self._initial_update: + processes = self._psutil.process_iter() + _LOGGER.debug("processes: %s", processes) + processes = list(processes) -class SystemMonitorLoadCoordinator( - MonitorCoordinator[tuple[float, float, float] | None] -): - """A System monitor Load Data Update Coordinator.""" + temps: dict[str, list[shwtemp]] = {} + if self.update_subscribers[("temperatures", "")] or self._initial_update: + try: + temps = self._psutil.sensors_temperatures() + _LOGGER.debug("temps: %s", temps) + except AttributeError: + _LOGGER.debug("OS does not provide temperature sensors") - def update_data(self) -> tuple[float, float, float] | None: - """Coordinator is not async.""" - - async def _async_update_data(self) -> tuple[float, float, float] | None: - """Fetch data.""" - return os.getloadavg() - - -class SystemMonitorProcessorCoordinator(MonitorCoordinator[float | None]): - """A System monitor Processor Data Update Coordinator.""" - - def update_data(self) -> float | None: - """Coordinator is not async.""" - - async def _async_update_data(self) -> float | None: - """Get cpu usage. - - Unlikely the rest of the coordinators, this one is async - since it does not block and we need to make sure it runs - in the same thread every time as psutil checks the thread - tid and compares it against the previous one. - """ - cpu_percent: float = self._psutil.cpu_percent(interval=None) - _LOGGER.debug("cpu_percent: %s", cpu_percent) - if cpu_percent > 0.0: - return cpu_percent - return None - - -class SystemMonitorBootTimeCoordinator(MonitorCoordinator[datetime]): - """A System monitor Processor Data Update Coordinator.""" - - def update_data(self) -> datetime: - """Fetch data.""" - boot_time = dt_util.utc_from_timestamp(self._psutil.boot_time()) - _LOGGER.debug("boot time: %s", boot_time) - return boot_time - - -class SystemMonitorProcessCoordinator(MonitorCoordinator[list[Process]]): - """A System monitor Process Data Update Coordinator.""" - - def update_data(self) -> list[Process]: - """Fetch data.""" - processes = self._psutil.process_iter() - _LOGGER.debug("processes: %s", processes) - return list(processes) - - -class SystemMonitorCPUtempCoordinator(MonitorCoordinator[dict[str, list[shwtemp]]]): - """A System monitor CPU Temperature Data Update Coordinator.""" - - def update_data(self) -> dict[str, list[shwtemp]]: - """Fetch data.""" - try: - temps: dict[str, list[shwtemp]] = self._psutil.sensors_temperatures() - _LOGGER.debug("temps: %s", temps) - return temps - except AttributeError as err: - raise UpdateFailed("OS does not provide temperature sensors") from err + return { + "disks": disks, + "swap": swap, + "memory": memory, + "io_counters": io_counters, + "addresses": addresses, + "boot_time": self.boot_time, + "processes": processes, + "temperatures": temps, + } diff --git a/homeassistant/components/systemmonitor/diagnostics.py b/homeassistant/components/systemmonitor/diagnostics.py index d48097e936c..c55869eac82 100644 --- a/homeassistant/components/systemmonitor/diagnostics.py +++ b/homeassistant/components/systemmonitor/diagnostics.py @@ -6,23 +6,21 @@ from typing import Any from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN_COORDINATORS -from .coordinator import MonitorCoordinator +from .const import DOMAIN_COORDINATOR +from .coordinator import SystemMonitorCoordinator async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for Sensibo config entry.""" - coordinators: dict[str, MonitorCoordinator] = hass.data[DOMAIN_COORDINATORS] + coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR] - diag_data = {} - for _type, coordinator in coordinators.items(): - diag_data[_type] = { - "last_update_success": coordinator.last_update_success, - "last_update": str(coordinator.last_update_success_time), - "data": str(coordinator.data), - } + diag_data = { + "last_update_success": coordinator.last_update_success, + "last_update": str(coordinator.last_update_success_time), + "data": coordinator.data.as_dict(), + } return { "entry": entry.as_dict(), diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 1ebf2ba44e4..f32e089a1d9 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -10,11 +10,9 @@ import logging import socket import sys import time -from typing import Any, Generic, Literal +from typing import Any, Literal -from psutil import NoSuchProcess, Process -from psutil._common import sdiskusage, shwtemp, snetio, snicaddr, sswap -import psutil_home_assistant as ha_psutil +from psutil import NoSuchProcess import voluptuous as vol from homeassistant.components.sensor import ( @@ -47,22 +45,8 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateTyp from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import slugify -from .const import CONF_PROCESS, DOMAIN, DOMAIN_COORDINATORS, NET_IO_TYPES -from .coordinator import ( - MonitorCoordinator, - SystemMonitorBootTimeCoordinator, - SystemMonitorCPUtempCoordinator, - SystemMonitorDiskCoordinator, - SystemMonitorLoadCoordinator, - SystemMonitorMemoryCoordinator, - SystemMonitorNetAddrCoordinator, - SystemMonitorNetIOCoordinator, - SystemMonitorProcessCoordinator, - SystemMonitorProcessorCoordinator, - SystemMonitorSwapCoordinator, - VirtualMemory, - dataT, -) +from .const import CONF_PROCESS, DOMAIN, DOMAIN_COORDINATOR, NET_IO_TYPES +from .coordinator import SystemMonitorCoordinator from .util import get_all_disk_mounts, get_all_network_interfaces, read_cpu_temperature _LOGGER = logging.getLogger(__name__) @@ -88,16 +72,16 @@ def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]: def get_processor_temperature( - entity: SystemMonitorSensor[dict[str, list[shwtemp]]], + entity: SystemMonitorSensor, ) -> float | None: """Return processor temperature.""" - return read_cpu_temperature(entity.hass, entity.coordinator.data) + return read_cpu_temperature(entity.hass, entity.coordinator.data.temperatures) -def get_process(entity: SystemMonitorSensor[list[Process]]) -> str: +def get_process(entity: SystemMonitorSensor) -> str: """Return process.""" state = STATE_OFF - for proc in entity.coordinator.data: + for proc in entity.coordinator.data.processes: try: _LOGGER.debug("process %s for argument %s", proc.name(), entity.argument) if entity.argument == proc.name(): @@ -112,26 +96,26 @@ def get_process(entity: SystemMonitorSensor[list[Process]]) -> str: return state -def get_network(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | None: +def get_network(entity: SystemMonitorSensor) -> float | None: """Return network in and out.""" - counters = entity.coordinator.data + counters = entity.coordinator.data.io_counters if entity.argument in counters: counter = counters[entity.argument][IO_COUNTER[entity.entity_description.key]] return round(counter / 1024**2, 1) return None -def get_packets(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | None: +def get_packets(entity: SystemMonitorSensor) -> float | None: """Return packets in and out.""" - counters = entity.coordinator.data + counters = entity.coordinator.data.io_counters if entity.argument in counters: return counters[entity.argument][IO_COUNTER[entity.entity_description.key]] return None -def get_throughput(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | None: +def get_throughput(entity: SystemMonitorSensor) -> float | None: """Return network throughput in and out.""" - counters = entity.coordinator.data + counters = entity.coordinator.data.io_counters state = None if entity.argument in counters: counter = counters[entity.argument][IO_COUNTER[entity.entity_description.key]] @@ -151,10 +135,10 @@ def get_throughput(entity: SystemMonitorSensor[dict[str, snetio]]) -> float | No def get_ip_address( - entity: SystemMonitorSensor[dict[str, list[snicaddr]]], + entity: SystemMonitorSensor, ) -> str | None: """Return network ip address.""" - addresses = entity.coordinator.data + addresses = entity.coordinator.data.addresses if entity.argument in addresses: for addr in addresses[entity.argument]: if addr.family == IF_ADDRS_FAMILY[entity.entity_description.key]: @@ -163,16 +147,18 @@ def get_ip_address( @dataclass(frozen=True, kw_only=True) -class SysMonitorSensorEntityDescription(SensorEntityDescription, Generic[dataT]): +class SysMonitorSensorEntityDescription(SensorEntityDescription): """Describes System Monitor sensor entities.""" - value_fn: Callable[[SystemMonitorSensor[dataT]], StateType | datetime] + value_fn: Callable[[SystemMonitorSensor], StateType | datetime] + add_to_update: Callable[[SystemMonitorSensor], tuple[str, str]] + none_is_unavailable: bool = False mandatory_arg: bool = False placeholder: str | None = None -SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { - "disk_free": SysMonitorSensorEntityDescription[sdiskusage]( +SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { + "disk_free": SysMonitorSensorEntityDescription( key="disk_free", translation_key="disk_free", placeholder="mount_point", @@ -180,9 +166,15 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data.free / 1024**3, 1), + value_fn=lambda entity: round( + entity.coordinator.data.disk_usage[entity.argument].free / 1024**3, 1 + ) + if entity.argument in entity.coordinator.data.disk_usage + else None, + none_is_unavailable=True, + add_to_update=lambda entity: ("disks", entity.argument), ), - "disk_use": SysMonitorSensorEntityDescription[sdiskusage]( + "disk_use": SysMonitorSensorEntityDescription( key="disk_use", translation_key="disk_use", placeholder="mount_point", @@ -190,70 +182,91 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data.used / 1024**3, 1), + value_fn=lambda entity: round( + entity.coordinator.data.disk_usage[entity.argument].used / 1024**3, 1 + ) + if entity.argument in entity.coordinator.data.disk_usage + else None, + none_is_unavailable=True, + add_to_update=lambda entity: ("disks", entity.argument), ), - "disk_use_percent": SysMonitorSensorEntityDescription[sdiskusage]( + "disk_use_percent": SysMonitorSensorEntityDescription( key="disk_use_percent", translation_key="disk_use_percent", placeholder="mount_point", native_unit_of_measurement=PERCENTAGE, icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: entity.coordinator.data.percent, + value_fn=lambda entity: entity.coordinator.data.disk_usage[ + entity.argument + ].percent + if entity.argument in entity.coordinator.data.disk_usage + else None, + none_is_unavailable=True, + add_to_update=lambda entity: ("disks", entity.argument), ), - "ipv4_address": SysMonitorSensorEntityDescription[dict[str, list[snicaddr]]]( + "ipv4_address": SysMonitorSensorEntityDescription( key="ipv4_address", translation_key="ipv4_address", placeholder="ip_address", icon="mdi:ip-network", mandatory_arg=True, value_fn=get_ip_address, + add_to_update=lambda entity: ("addresses", ""), ), - "ipv6_address": SysMonitorSensorEntityDescription[dict[str, list[snicaddr]]]( + "ipv6_address": SysMonitorSensorEntityDescription( key="ipv6_address", translation_key="ipv6_address", placeholder="ip_address", icon="mdi:ip-network", mandatory_arg=True, value_fn=get_ip_address, + add_to_update=lambda entity: ("addresses", ""), ), - "last_boot": SysMonitorSensorEntityDescription[datetime]( + "last_boot": SysMonitorSensorEntityDescription( key="last_boot", translation_key="last_boot", device_class=SensorDeviceClass.TIMESTAMP, - value_fn=lambda entity: entity.coordinator.data, + value_fn=lambda entity: entity.coordinator.data.boot_time, + add_to_update=lambda entity: ("boot", ""), ), - "load_15m": SysMonitorSensorEntityDescription[tuple[float, float, float]]( + "load_15m": SysMonitorSensorEntityDescription( key="load_15m", translation_key="load_15m", icon=get_cpu_icon(), state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data[2], 2), + value_fn=lambda entity: round(entity.coordinator.data.load[2], 2), + add_to_update=lambda entity: ("load", ""), ), - "load_1m": SysMonitorSensorEntityDescription[tuple[float, float, float]]( + "load_1m": SysMonitorSensorEntityDescription( key="load_1m", translation_key="load_1m", icon=get_cpu_icon(), state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data[0], 2), + value_fn=lambda entity: round(entity.coordinator.data.load[0], 2), + add_to_update=lambda entity: ("load", ""), ), - "load_5m": SysMonitorSensorEntityDescription[tuple[float, float, float]]( + "load_5m": SysMonitorSensorEntityDescription( key="load_5m", translation_key="load_5m", icon=get_cpu_icon(), state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data[1], 2), + value_fn=lambda entity: round(entity.coordinator.data.load[1], 2), + add_to_update=lambda entity: ("load", ""), ), - "memory_free": SysMonitorSensorEntityDescription[VirtualMemory]( + "memory_free": SysMonitorSensorEntityDescription( key="memory_free", translation_key="memory_free", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data.available / 1024**2, 1), + value_fn=lambda entity: round( + entity.coordinator.data.memory.available / 1024**2, 1 + ), + add_to_update=lambda entity: ("memory", ""), ), - "memory_use": SysMonitorSensorEntityDescription[VirtualMemory]( + "memory_use": SysMonitorSensorEntityDescription( key="memory_use", translation_key="memory_use", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, @@ -261,20 +274,25 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round( - (entity.coordinator.data.total - entity.coordinator.data.available) + ( + entity.coordinator.data.memory.total + - entity.coordinator.data.memory.available + ) / 1024**2, 1, ), + add_to_update=lambda entity: ("memory", ""), ), - "memory_use_percent": SysMonitorSensorEntityDescription[VirtualMemory]( + "memory_use_percent": SysMonitorSensorEntityDescription( key="memory_use_percent", translation_key="memory_use_percent", native_unit_of_measurement=PERCENTAGE, icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: entity.coordinator.data.percent, + value_fn=lambda entity: entity.coordinator.data.memory.percent, + add_to_update=lambda entity: ("memory", ""), ), - "network_in": SysMonitorSensorEntityDescription[dict[str, snetio]]( + "network_in": SysMonitorSensorEntityDescription( key="network_in", translation_key="network_in", placeholder="interface", @@ -284,8 +302,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_network, + add_to_update=lambda entity: ("io_counters", ""), ), - "network_out": SysMonitorSensorEntityDescription[dict[str, snetio]]( + "network_out": SysMonitorSensorEntityDescription( key="network_out", translation_key="network_out", placeholder="interface", @@ -295,8 +314,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_network, + add_to_update=lambda entity: ("io_counters", ""), ), - "packets_in": SysMonitorSensorEntityDescription[dict[str, snetio]]( + "packets_in": SysMonitorSensorEntityDescription( key="packets_in", translation_key="packets_in", placeholder="interface", @@ -304,8 +324,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_packets, + add_to_update=lambda entity: ("io_counters", ""), ), - "packets_out": SysMonitorSensorEntityDescription[dict[str, snetio]]( + "packets_out": SysMonitorSensorEntityDescription( key="packets_out", translation_key="packets_out", placeholder="interface", @@ -313,8 +334,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_packets, + add_to_update=lambda entity: ("io_counters", ""), ), - "throughput_network_in": SysMonitorSensorEntityDescription[dict[str, snetio]]( + "throughput_network_in": SysMonitorSensorEntityDescription( key="throughput_network_in", translation_key="throughput_network_in", placeholder="interface", @@ -323,8 +345,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { state_class=SensorStateClass.MEASUREMENT, mandatory_arg=True, value_fn=get_throughput, + add_to_update=lambda entity: ("io_counters", ""), ), - "throughput_network_out": SysMonitorSensorEntityDescription[dict[str, snetio]]( + "throughput_network_out": SysMonitorSensorEntityDescription( key="throughput_network_out", translation_key="throughput_network_out", placeholder="interface", @@ -333,60 +356,68 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { state_class=SensorStateClass.MEASUREMENT, mandatory_arg=True, value_fn=get_throughput, + add_to_update=lambda entity: ("io_counters", ""), ), - "process": SysMonitorSensorEntityDescription[list[Process]]( + "process": SysMonitorSensorEntityDescription( key="process", translation_key="process", placeholder="process", icon=get_cpu_icon(), mandatory_arg=True, value_fn=get_process, + add_to_update=lambda entity: ("processes", ""), ), - "processor_use": SysMonitorSensorEntityDescription[float]( + "processor_use": SysMonitorSensorEntityDescription( key="processor_use", translation_key="processor_use", native_unit_of_measurement=PERCENTAGE, icon=get_cpu_icon(), state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: ( - round(entity.coordinator.data) if entity.coordinator.data else None + round(entity.coordinator.data.cpu_percent) + if entity.coordinator.data.cpu_percent + else None ), + add_to_update=lambda entity: ("cpu_percent", ""), ), - "processor_temperature": SysMonitorSensorEntityDescription[ - dict[str, list[shwtemp]] - ]( + "processor_temperature": SysMonitorSensorEntityDescription( key="processor_temperature", translation_key="processor_temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, value_fn=get_processor_temperature, + none_is_unavailable=True, + add_to_update=lambda entity: ("temperatures", ""), ), - "swap_free": SysMonitorSensorEntityDescription[sswap]( + "swap_free": SysMonitorSensorEntityDescription( key="swap_free", translation_key="swap_free", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data.free / 1024**2, 1), + value_fn=lambda entity: round(entity.coordinator.data.swap.free / 1024**2, 1), + add_to_update=lambda entity: ("swap", ""), ), - "swap_use": SysMonitorSensorEntityDescription[sswap]( + "swap_use": SysMonitorSensorEntityDescription( key="swap_use", translation_key="swap_use", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data.used / 1024**2, 1), + value_fn=lambda entity: round(entity.coordinator.data.swap.used / 1024**2, 1), + add_to_update=lambda entity: ("swap", ""), ), - "swap_use_percent": SysMonitorSensorEntityDescription[sswap]( + "swap_use_percent": SysMonitorSensorEntityDescription( key="swap_use_percent", translation_key="swap_use_percent", native_unit_of_measurement=PERCENTAGE, icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: entity.coordinator.data.percent, + value_fn=lambda entity: entity.coordinator.data.swap.percent, + add_to_update=lambda entity: ("swap", ""), ), } @@ -489,7 +520,7 @@ async def async_setup_entry( # noqa: C901 entities: list[SystemMonitorSensor] = [] legacy_resources: set[str] = set(entry.options.get("resources", [])) loaded_resources: set[str] = set() - psutil_wrapper: ha_psutil.PsutilWrapper = hass.data[DOMAIN] + coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR] def get_arguments() -> dict[str, Any]: """Return startup information.""" @@ -507,44 +538,6 @@ async def async_setup_entry( # noqa: C901 startup_arguments = await hass.async_add_executor_job(get_arguments) - disk_coordinators: dict[str, SystemMonitorDiskCoordinator] = {} - for argument in startup_arguments["disk_arguments"]: - disk_coordinators[argument] = SystemMonitorDiskCoordinator( - hass, psutil_wrapper, f"Disk {argument} coordinator", argument - ) - swap_coordinator = SystemMonitorSwapCoordinator( - hass, psutil_wrapper, "Swap coordinator" - ) - memory_coordinator = SystemMonitorMemoryCoordinator( - hass, psutil_wrapper, "Memory coordinator" - ) - net_io_coordinator = SystemMonitorNetIOCoordinator( - hass, psutil_wrapper, "Net IO coordnator" - ) - net_addr_coordinator = SystemMonitorNetAddrCoordinator( - hass, psutil_wrapper, "Net address coordinator" - ) - system_load_coordinator = SystemMonitorLoadCoordinator( - hass, psutil_wrapper, "System load coordinator" - ) - processor_coordinator = SystemMonitorProcessorCoordinator( - hass, psutil_wrapper, "Processor coordinator" - ) - boot_time_coordinator = SystemMonitorBootTimeCoordinator( - hass, psutil_wrapper, "Boot time coordinator" - ) - process_coordinator = SystemMonitorProcessCoordinator( - hass, psutil_wrapper, "Process coordinator" - ) - cpu_temp_coordinator = SystemMonitorCPUtempCoordinator( - hass, psutil_wrapper, "CPU temperature coordinator" - ) - - for argument in startup_arguments["disk_arguments"]: - disk_coordinators[argument] = SystemMonitorDiskCoordinator( - hass, psutil_wrapper, f"Disk {argument} coordinator", argument - ) - _LOGGER.debug("Setup from options %s", entry.options) for _type, sensor_description in SENSOR_TYPES.items(): @@ -556,7 +549,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - disk_coordinators[argument], + coordinator, sensor_description, entry.entry_id, argument, @@ -573,7 +566,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - net_addr_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -588,7 +581,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - boot_time_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -603,7 +596,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - system_load_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -618,7 +611,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - memory_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -634,7 +627,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - net_io_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -649,7 +642,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - process_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -678,7 +671,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - processor_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -696,7 +689,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - cpu_temp_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -711,7 +704,7 @@ async def async_setup_entry( # noqa: C901 loaded_resources.add(slugify(f"{_type}_{argument}")) entities.append( SystemMonitorSensor( - swap_coordinator, + coordinator, sensor_description, entry.entry_id, argument, @@ -735,13 +728,9 @@ async def async_setup_entry( # noqa: C901 _type = resource[:split_index] argument = resource[split_index + 1 :] _LOGGER.debug("Loading legacy %s with argument %s", _type, argument) - if not disk_coordinators.get(argument): - disk_coordinators[argument] = SystemMonitorDiskCoordinator( - hass, psutil_wrapper, f"Disk {argument} coordinator", argument - ) entities.append( SystemMonitorSensor( - disk_coordinators[argument], + coordinator, SENSOR_TYPES[_type], entry.entry_id, argument, @@ -749,23 +738,6 @@ async def async_setup_entry( # noqa: C901 ) ) - hass.data[DOMAIN_COORDINATORS] = {} - # No gathering to avoid swamping the executor - for argument, coordinator in disk_coordinators.items(): - hass.data[DOMAIN_COORDINATORS][f"disk_{argument}"] = coordinator - hass.data[DOMAIN_COORDINATORS]["boot_time"] = boot_time_coordinator - hass.data[DOMAIN_COORDINATORS]["cpu_temp"] = cpu_temp_coordinator - hass.data[DOMAIN_COORDINATORS]["memory"] = memory_coordinator - hass.data[DOMAIN_COORDINATORS]["net_addr"] = net_addr_coordinator - hass.data[DOMAIN_COORDINATORS]["net_io"] = net_io_coordinator - hass.data[DOMAIN_COORDINATORS]["process"] = process_coordinator - hass.data[DOMAIN_COORDINATORS]["processor"] = processor_coordinator - hass.data[DOMAIN_COORDINATORS]["swap"] = swap_coordinator - hass.data[DOMAIN_COORDINATORS]["system_load"] = system_load_coordinator - - for coordinator in hass.data[DOMAIN_COORDINATORS].values(): - await coordinator.async_request_refresh() - @callback def clean_obsolete_entities() -> None: """Remove entities which are disabled and not supported from setup.""" @@ -790,17 +762,18 @@ async def async_setup_entry( # noqa: C901 async_add_entities(entities) -class SystemMonitorSensor(CoordinatorEntity[MonitorCoordinator[dataT]], SensorEntity): +class SystemMonitorSensor(CoordinatorEntity[SystemMonitorCoordinator], SensorEntity): """Implementation of a system monitor sensor.""" _attr_has_entity_name = True _attr_entity_category = EntityCategory.DIAGNOSTIC - entity_description: SysMonitorSensorEntityDescription[dataT] + entity_description: SysMonitorSensorEntityDescription + argument: str def __init__( self, - coordinator: MonitorCoordinator[dataT], - sensor_description: SysMonitorSensorEntityDescription[dataT], + coordinator: SystemMonitorCoordinator, + sensor_description: SysMonitorSensorEntityDescription, entry_id: str, argument: str, legacy_enabled: bool = False, @@ -824,7 +797,31 @@ class SystemMonitorSensor(CoordinatorEntity[MonitorCoordinator[dataT]], SensorEn self.value: int | None = None self.update_time: float | None = None + async def async_added_to_hass(self) -> None: + """When added to hass.""" + self.coordinator.update_subscribers[ + self.entity_description.add_to_update(self) + ].add(self.entity_id) + return await super().async_added_to_hass() + + async def async_will_remove_from_hass(self) -> None: + """When removed from hass.""" + self.coordinator.update_subscribers[ + self.entity_description.add_to_update(self) + ].remove(self.entity_id) + return await super().async_will_remove_from_hass() + @property def native_value(self) -> StateType | datetime: """Return the state.""" return self.entity_description.value_fn(self) + + @property + def available(self) -> bool: + """Return if entity is available.""" + if self.entity_description.none_is_unavailable: + return bool( + self.coordinator.last_update_success is True + and self.native_value is not None + ) + return super().available diff --git a/tests/components/systemmonitor/snapshots/test_diagnostics.ambr b/tests/components/systemmonitor/snapshots/test_diagnostics.ambr index 0acb2362134..883c90d9d5b 100644 --- a/tests/components/systemmonitor/snapshots/test_diagnostics.ambr +++ b/tests/components/systemmonitor/snapshots/test_diagnostics.ambr @@ -2,54 +2,33 @@ # name: test_diagnostics dict({ 'coordinators': dict({ - 'boot_time': dict({ - 'data': '2024-02-24 15:00:00+00:00', - 'last_update_success': True, - }), - 'cpu_temp': dict({ - 'data': "{'cpu0-thermal': [shwtemp(label='cpu0-thermal', current=50.0, high=60.0, critical=70.0)]}", - 'last_update_success': True, - }), - 'disk_/': dict({ - 'data': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)', - 'last_update_success': True, - }), - 'disk_/home/notexist/': dict({ - 'data': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)', - 'last_update_success': True, - }), - 'disk_/media/share': dict({ - 'data': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)', - 'last_update_success': True, - }), - 'memory': dict({ - 'data': 'VirtualMemory(total=104857600, available=41943040, percent=40.0, used=62914560, free=31457280)', - 'last_update_success': True, - }), - 'net_addr': dict({ - 'data': "{'eth0': [snicaddr(family=, address='192.168.1.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)], 'eth1': [snicaddr(family=, address='192.168.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)], 'vethxyzxyz': [snicaddr(family=, address='172.16.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]}", - 'last_update_success': True, - }), - 'net_io': dict({ - 'data': "{'eth0': snetio(bytes_sent=104857600, bytes_recv=104857600, packets_sent=50, packets_recv=50, errin=0, errout=0, dropin=0, dropout=0), 'eth1': snetio(bytes_sent=209715200, bytes_recv=209715200, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0), 'vethxyzxyz': snetio(bytes_sent=314572800, bytes_recv=314572800, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)}", - 'last_update_success': True, - }), - 'process': dict({ - 'data': "[tests.components.systemmonitor.conftest.MockProcess(pid=1, name='python3', status='sleeping', started='2024-02-23 15:00:00'), tests.components.systemmonitor.conftest.MockProcess(pid=1, name='pip', status='sleeping', started='2024-02-23 15:00:00')]", - 'last_update_success': True, - }), - 'processor': dict({ - 'data': '10.0', - 'last_update_success': True, - }), - 'swap': dict({ - 'data': 'sswap(total=104857600, used=62914560, free=41943040, percent=60.0, sin=1, sout=1)', - 'last_update_success': True, - }), - 'system_load': dict({ - 'data': '(1, 2, 3)', - 'last_update_success': True, + 'data': dict({ + 'addresses': dict({ + 'eth0': "[snicaddr(family=, address='192.168.1.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]", + 'eth1': "[snicaddr(family=, address='192.168.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]", + 'vethxyzxyz': "[snicaddr(family=, address='172.16.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]", + }), + 'boot_time': '2024-02-24 15:00:00+00:00', + 'cpu_percent': '10.0', + 'disk_usage': dict({ + '/': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)', + '/home/notexist/': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)', + '/media/share': 'sdiskusage(total=536870912000, used=322122547200, free=214748364800, percent=60.0)', + }), + 'io_counters': dict({ + 'eth0': 'snetio(bytes_sent=104857600, bytes_recv=104857600, packets_sent=50, packets_recv=50, errin=0, errout=0, dropin=0, dropout=0)', + 'eth1': 'snetio(bytes_sent=209715200, bytes_recv=209715200, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)', + 'vethxyzxyz': 'snetio(bytes_sent=314572800, bytes_recv=314572800, packets_sent=150, packets_recv=150, errin=0, errout=0, dropin=0, dropout=0)', + }), + 'load': '(1, 2, 3)', + 'memory': 'VirtualMemory(total=104857600, available=41943040, percent=40.0, used=62914560, free=31457280)', + 'processes': "[tests.components.systemmonitor.conftest.MockProcess(pid=1, name='python3', status='sleeping', started='2024-02-23 15:00:00'), tests.components.systemmonitor.conftest.MockProcess(pid=1, name='pip', status='sleeping', started='2024-02-23 15:00:00')]", + 'swap': 'sswap(total=104857600, used=62914560, free=41943040, percent=60.0, sin=1, sout=1)', + 'temperatures': dict({ + 'cpu0-thermal': "[shwtemp(label='cpu0-thermal', current=50.0, high=60.0, critical=70.0)]", + }), }), + 'last_update_success': True, }), 'entry': dict({ 'data': dict({ diff --git a/tests/components/systemmonitor/test_sensor.py b/tests/components/systemmonitor/test_sensor.py index 4c0c5e179b1..e67dd31a1de 100644 --- a/tests/components/systemmonitor/test_sensor.py +++ b/tests/components/systemmonitor/test_sensor.py @@ -473,10 +473,7 @@ async def test_exception_handling_disk_sensor( async_fire_time_changed(hass) await hass.async_block_till_done() - assert ( - "Error fetching System Monitor Disk / coordinator data: OS error for /" - in caplog.text - ) + assert "OS error for /" in caplog.text disk_sensor = hass.states.get("sensor.system_monitor_disk_free") assert disk_sensor is not None @@ -489,10 +486,7 @@ async def test_exception_handling_disk_sensor( async_fire_time_changed(hass) await hass.async_block_till_done() - assert ( - "Error fetching System Monitor Disk / coordinator data: OS error for /" - in caplog.text - ) + assert "OS error for /" in caplog.text disk_sensor = hass.states.get("sensor.system_monitor_disk_free") assert disk_sensor is not None From 409dc02d364b2db121f245446517a6ccc5b7bd6a Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 3 Mar 2024 18:29:02 +0100 Subject: [PATCH 0188/1691] Ignore failing gas stations in Tankerkoening (#112125) --- homeassistant/components/tankerkoenig/coordinator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tankerkoenig/coordinator.py b/homeassistant/components/tankerkoenig/coordinator.py index c28ebf4aab2..c2d91f20b8a 100644 --- a/homeassistant/components/tankerkoenig/coordinator.py +++ b/homeassistant/components/tankerkoenig/coordinator.py @@ -62,8 +62,11 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): station = await self._tankerkoenig.station_details(station_id) except TankerkoenigInvalidKeyError as err: raise ConfigEntryAuthFailed(err) from err - except (TankerkoenigError, TankerkoenigConnectionError) as err: + except TankerkoenigConnectionError as err: raise ConfigEntryNotReady(err) from err + except TankerkoenigError as err: + _LOGGER.error("Error when adding station %s %s", station_id, err) + continue self.stations[station_id] = station From 74f14204101995e5d9dd03bc2b566c78fdaefa7a Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Sun, 3 Mar 2024 12:41:31 -0500 Subject: [PATCH 0189/1691] Add the ability to resume cleaning on start button (#112122) --- homeassistant/components/roborock/vacuum.py | 7 +++- tests/components/roborock/test_vacuum.py | 45 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roborock/vacuum.py b/homeassistant/components/roborock/vacuum.py index dafbb731bd2..3684b584047 100644 --- a/homeassistant/components/roborock/vacuum.py +++ b/homeassistant/components/roborock/vacuum.py @@ -121,7 +121,12 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity): async def async_start(self) -> None: """Start the vacuum.""" - await self.send(RoborockCommand.APP_START) + if self._device_status.in_cleaning == 2: + await self.send(RoborockCommand.RESUME_ZONED_CLEAN) + elif self._device_status.in_cleaning == 3: + await self.send(RoborockCommand.RESUME_SEGMENT_CLEAN) + else: + await self.send(RoborockCommand.APP_START) async def async_pause(self) -> None: """Pause the vacuum.""" diff --git a/tests/components/roborock/test_vacuum.py b/tests/components/roborock/test_vacuum.py index ecc501cc542..61a2ef5d8e2 100644 --- a/tests/components/roborock/test_vacuum.py +++ b/tests/components/roborock/test_vacuum.py @@ -1,6 +1,7 @@ """Tests for Roborock vacuums.""" +import copy from typing import Any from unittest.mock import patch @@ -8,6 +9,7 @@ import pytest from roborock import RoborockException from roborock.roborock_typing import RoborockCommand +from homeassistant.components.roborock import DOMAIN from homeassistant.components.vacuum import ( SERVICE_CLEAN_SPOT, SERVICE_LOCATE, @@ -22,8 +24,10 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry +from tests.components.roborock.mock_data import PROP ENTITY_ID = "vacuum.roborock_s7_maxv" DEVICE_ID = "abc123" @@ -90,6 +94,47 @@ async def test_commands( assert mock_send_command.call_args[0][1] == called_params +@pytest.mark.parametrize( + ("in_cleaning_int", "expected_command"), + [ + (0, RoborockCommand.APP_START), + (1, RoborockCommand.APP_START), + (2, RoborockCommand.RESUME_ZONED_CLEAN), + (3, RoborockCommand.RESUME_SEGMENT_CLEAN), + ], +) +async def test_resume_cleaning( + hass: HomeAssistant, + bypass_api_fixture, + mock_roborock_entry: MockConfigEntry, + in_cleaning_int: int, + expected_command: RoborockCommand, +) -> None: + """Test resuming clean on start button when a clean is paused.""" + prop = copy.deepcopy(PROP) + prop.status.in_cleaning = in_cleaning_int + with patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", + return_value=prop, + ): + await async_setup_component(hass, DOMAIN, {}) + vacuum = hass.states.get(ENTITY_ID) + assert vacuum + + data = {ATTR_ENTITY_ID: ENTITY_ID} + with patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_command" + ) as mock_send_command: + await hass.services.async_call( + Platform.VACUUM, + SERVICE_START, + data, + blocking=True, + ) + assert mock_send_command.call_count == 1 + assert mock_send_command.call_args[0][0] == expected_command + + async def test_failed_user_command( hass: HomeAssistant, bypass_api_fixture, From 4eb24b2db720149bd713e74f2eda0b21f12b4086 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 3 Mar 2024 18:42:44 +0100 Subject: [PATCH 0190/1691] Clean up setup and teardown of Axis integration (#112120) --- homeassistant/components/axis/__init__.py | 7 ++-- homeassistant/components/axis/const.py | 1 - homeassistant/components/axis/hub/hub.py | 35 +++++++------------ tests/components/axis/conftest.py | 4 +-- .../axis/snapshots/test_diagnostics.ambr | 1 - tests/components/axis/test_config_flow.py | 2 -- tests/components/axis/test_device.py | 7 ---- 7 files changed, 18 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index 9fda3aad84d..d37b2ebe5ac 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -28,9 +28,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b hass.data[AXIS_DOMAIN][config_entry.entry_id] = hub await hub.async_update_device_registry() await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - hub.async_setup_events() + hub.setup() config_entry.add_update_listener(hub.async_new_address_callback) + config_entry.async_on_unload(hub.teardown) config_entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, hub.shutdown) ) @@ -40,8 +41,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload Axis device config entry.""" - hub: AxisHub = hass.data[AXIS_DOMAIN].pop(config_entry.entry_id) - return await hub.async_reset() + hass.data[AXIS_DOMAIN].pop(config_entry.entry_id) + return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py index a13cae0dd0a..f4cc35dd7d2 100644 --- a/homeassistant/components/axis/const.py +++ b/homeassistant/components/axis/const.py @@ -9,7 +9,6 @@ DOMAIN = "axis" ATTR_MANUFACTURER = "Axis Communications AB" -CONF_EVENTS = "events" CONF_STREAM_PROFILE = "stream_profile" CONF_VIDEO_SOURCE = "video_source" diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index b81d3498255..5a0f45a1682 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -31,15 +31,12 @@ from homeassistant.setup import async_when_setup from ..const import ( ATTR_MANUFACTURER, - CONF_EVENTS, CONF_STREAM_PROFILE, CONF_VIDEO_SOURCE, - DEFAULT_EVENTS, DEFAULT_STREAM_PROFILE, DEFAULT_TRIGGER_TIME, DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN, - PLATFORMS, ) @@ -110,11 +107,6 @@ class AxisHub: # Options - @property - def option_events(self) -> bool: - """Config entry option defining if platforms based on events should be created.""" - return self.config_entry.options.get(CONF_EVENTS, DEFAULT_EVENTS) - @property def option_stream_profile(self) -> str: """Config entry option defining what stream profile camera platform should use.""" @@ -147,7 +139,7 @@ class AxisHub: # Callbacks @callback - def async_connection_status_callback(self, status: Signal) -> None: + def connection_status_callback(self, status: Signal) -> None: """Handle signals of device connection status. This is called on every RTSP keep-alive message. @@ -210,17 +202,17 @@ class AxisHub: # Setup and teardown methods - def async_setup_events(self) -> None: + @callback + def setup(self) -> None: """Set up the device events.""" - if self.option_events: - self.api.stream.connection_status_callback.append( - self.async_connection_status_callback - ) - self.api.enable_events() - self.api.stream.start() + self.api.stream.connection_status_callback.append( + self.connection_status_callback + ) + self.api.enable_events() + self.api.stream.start() - if self.api.vapix.mqtt.supported: - async_when_setup(self.hass, MQTT_DOMAIN, self.async_use_mqtt) + if self.api.vapix.mqtt.supported: + async_when_setup(self.hass, MQTT_DOMAIN, self.async_use_mqtt) @callback def disconnect_from_stream(self) -> None: @@ -233,10 +225,7 @@ class AxisHub: """Stop the event stream.""" self.disconnect_from_stream() - async def async_reset(self) -> bool: + @callback + def teardown(self) -> None: """Reset this device to default state.""" self.disconnect_from_stream() - - return await self.hass.config_entries.async_unload_platforms( - self.config_entry, PLATFORMS - ) diff --git a/tests/components/axis/conftest.py b/tests/components/axis/conftest.py index 0e83f9007bf..a15abb224a4 100644 --- a/tests/components/axis/conftest.py +++ b/tests/components/axis/conftest.py @@ -9,7 +9,7 @@ from axis.rtsp import Signal, State import pytest import respx -from homeassistant.components.axis.const import CONF_EVENTS, DOMAIN as AXIS_DOMAIN +from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.const import ( CONF_HOST, CONF_MODEL, @@ -93,7 +93,7 @@ def config_fixture(): @pytest.fixture(name="options") def options_fixture(request): """Define a config entry options fixture.""" - return {CONF_EVENTS: True} + return {} # Axis API fixtures diff --git a/tests/components/axis/snapshots/test_diagnostics.ambr b/tests/components/axis/snapshots/test_diagnostics.ambr index b5647a08543..3902cee6a0b 100644 --- a/tests/components/axis/snapshots/test_diagnostics.ambr +++ b/tests/components/axis/snapshots/test_diagnostics.ambr @@ -41,7 +41,6 @@ 'entry_id': '676abe5b73621446e6550a2e86ffe3dd', 'minor_version': 1, 'options': dict({ - 'events': True, }), 'pref_disable_new_entities': False, 'pref_disable_polling': False, diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index e570c1ecee8..41af5a58876 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -7,7 +7,6 @@ import pytest from homeassistant.components import dhcp, ssdp, zeroconf from homeassistant.components.axis import config_flow from homeassistant.components.axis.const import ( - CONF_EVENTS, CONF_STREAM_PROFILE, CONF_VIDEO_SOURCE, DEFAULT_STREAM_PROFILE, @@ -607,7 +606,6 @@ async def test_option_flow(hass: HomeAssistant, setup_config_entry) -> None: assert result["type"] == FlowResultType.CREATE_ENTRY assert result["data"] == { - CONF_EVENTS: True, CONF_STREAM_PROFILE: "profile_1", CONF_VIDEO_SOURCE: 1, } diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 9912b30f9c7..b41155e88b1 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -171,13 +171,6 @@ async def test_device_unavailable( assert hass.states.get(f"{BINARY_SENSOR_DOMAIN}.{NAME}_sound_1").state == STATE_OFF -async def test_device_reset(hass: HomeAssistant, setup_config_entry) -> None: - """Successfully reset device.""" - device = hass.data[AXIS_DOMAIN][setup_config_entry.entry_id] - result = await device.async_reset() - assert result is True - - async def test_device_not_accessible( hass: HomeAssistant, config_entry, setup_default_vapix_requests ) -> None: From f20e1ad560b0a21e1e6d78430c8e656c375be46d Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 3 Mar 2024 18:46:51 +0100 Subject: [PATCH 0191/1691] Exclude tankerkoenig attributes from recording (#112109) --- homeassistant/components/tankerkoenig/sensor.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index c0394bd318f..b36479d4ce5 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -64,6 +64,19 @@ class FuelPriceSensor(TankerkoenigCoordinatorEntity, SensorEntity): _attr_attribution = ATTRIBUTION _attr_state_class = SensorStateClass.MEASUREMENT _attr_native_unit_of_measurement = CURRENCY_EURO + _unrecorded_attributes = frozenset( + { + ATTR_BRAND, + ATTR_CITY, + ATTR_HOUSE_NUMBER, + ATTR_POSTCODE, + ATTR_STATION_NAME, + ATTR_STREET, + ATTRIBUTION, + ATTR_LATITUDE, + ATTR_LONGITUDE, + } + ) def __init__( self, From 72efb3dab579507b31d916af1444bfa63d9f1d0e Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 3 Mar 2024 18:47:09 +0100 Subject: [PATCH 0192/1691] Bump reolink-aio to 0.8.9 (#112124) * Update strings.json * Bump reolink-aio to 0.8.9 --- homeassistant/components/reolink/manifest.json | 2 +- homeassistant/components/reolink/strings.json | 3 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 0f2ef19ba87..81d11e2fd0a 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -18,5 +18,5 @@ "documentation": "https://www.home-assistant.io/integrations/reolink", "iot_class": "local_push", "loggers": ["reolink_aio"], - "requirements": ["reolink-aio==0.8.8"] + "requirements": ["reolink-aio==0.8.9"] } diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index 92e9a6164f8..fb4d42bb97d 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -367,7 +367,8 @@ "state": { "stayoff": "Stay off", "auto": "Auto", - "alwaysonatnight": "Auto & always on at night" + "alwaysonatnight": "Auto & always on at night", + "alwayson": "Always on" } } }, diff --git a/requirements_all.txt b/requirements_all.txt index a55f8d567bb..0f4c95614a0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2430,7 +2430,7 @@ renault-api==0.2.1 renson-endura-delta==1.7.1 # homeassistant.components.reolink -reolink-aio==0.8.8 +reolink-aio==0.8.9 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b15960bf476..8c44f4ad1f8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1870,7 +1870,7 @@ renault-api==0.2.1 renson-endura-delta==1.7.1 # homeassistant.components.reolink -reolink-aio==0.8.8 +reolink-aio==0.8.9 # homeassistant.components.rflink rflink==0.0.66 From 9af12a06392abd8e3b819cc747be307d0b800698 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 07:48:07 -1000 Subject: [PATCH 0193/1691] Avoid calling async_get_component twice for each component being setup (#112096) We already have the component so we can pass it to async_process_component_config to avoid having to look it up again --- homeassistant/config.py | 26 ++++++++++++++------------ homeassistant/setup.py | 2 +- tests/common.py | 4 ++-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index a72bd00ed44..896c9be3653 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1430,6 +1430,7 @@ async def async_process_component_config( # noqa: C901 hass: HomeAssistant, config: ConfigType, integration: Integration, + component: ComponentProtocol | None = None, ) -> IntegrationConfigInfo: """Check component configuration. @@ -1441,18 +1442,19 @@ async def async_process_component_config( # noqa: C901 integration_docs = integration.documentation config_exceptions: list[ConfigExceptionInfo] = [] - try: - component = await integration.async_get_component() - except LOAD_EXCEPTIONS as exc: - exc_info = ConfigExceptionInfo( - exc, - ConfigErrorTranslationKey.COMPONENT_IMPORT_ERR, - domain, - config, - integration_docs, - ) - config_exceptions.append(exc_info) - return IntegrationConfigInfo(None, config_exceptions) + if not component: + try: + component = await integration.async_get_component() + except LOAD_EXCEPTIONS as exc: + exc_info = ConfigExceptionInfo( + exc, + ConfigErrorTranslationKey.COMPONENT_IMPORT_ERR, + domain, + config, + integration_docs, + ) + config_exceptions.append(exc_info) + return IntegrationConfigInfo(None, config_exceptions) # Check if the integration has a custom config validator config_validator = None diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 0cdbcec3ff3..efbaacac43e 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -299,7 +299,7 @@ async def _async_setup_component( # noqa: C901 return False integration_config_info = await conf_util.async_process_component_config( - hass, config, integration + hass, config, integration, component ) conf_util.async_handle_component_errors(hass, integration_config_info, integration) processed_config = conf_util.async_drop_config_annotations( diff --git a/tests/common.py b/tests/common.py index 14cacdf5d68..c5b517f78ac 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1075,11 +1075,11 @@ def assert_setup_component(count, domain=None): """ config = {} - async def mock_psc(hass, config_input, integration): + async def mock_psc(hass, config_input, integration, component=None): """Mock the prepare_setup_component to capture config.""" domain_input = integration.domain integration_config_info = await async_process_component_config( - hass, config_input, integration + hass, config_input, integration, component ) res = integration_config_info.config config[domain_input] = None if res is None else res.get(domain_input) From 0a462071c8dd16e3dd6a9a208b2f56291fabc27b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 07:48:28 -1000 Subject: [PATCH 0194/1691] Import screenlogic in the executor to avoid blocking the loop (#112097) --- homeassistant/components/screenlogic/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json index 434b8921bc2..6e720db29bc 100644 --- a/homeassistant/components/screenlogic/manifest.json +++ b/homeassistant/components/screenlogic/manifest.json @@ -13,6 +13,7 @@ } ], "documentation": "https://www.home-assistant.io/integrations/screenlogic", + "import_executor": true, "iot_class": "local_push", "loggers": ["screenlogicpy"], "requirements": ["screenlogicpy==0.10.0"] From da6eca7b68b9894fc8cffcfb4f43600b1880885f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 07:50:04 -1000 Subject: [PATCH 0195/1691] Avoid compiling entity service schema when passed defaults (#112099) * Avoid compiling entity service schema when passed defaults * dry --- homeassistant/helpers/config_validation.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 59e4f09d26f..7bf760c3136 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1235,9 +1235,7 @@ TARGET_SERVICE_FIELDS = { } -def make_entity_service_schema( - schema: dict, *, extra: int = vol.PREVENT_EXTRA -) -> vol.Schema: +def _make_entity_service_schema(schema: dict, extra: int) -> vol.Schema: """Create an entity service schema.""" return vol.Schema( vol.All( @@ -1255,6 +1253,21 @@ def make_entity_service_schema( ) +BASE_ENTITY_SCHEMA = _make_entity_service_schema({}, vol.PREVENT_EXTRA) + + +def make_entity_service_schema( + schema: dict, *, extra: int = vol.PREVENT_EXTRA +) -> vol.Schema: + """Create an entity service schema.""" + if not schema and extra == vol.PREVENT_EXTRA: + # If the schema is empty and we don't allow extra keys, we can return + # the base schema and avoid compiling a new schema which is the case + # for ~50% of services. + return BASE_ENTITY_SCHEMA + return _make_entity_service_schema(schema, extra) + + SCRIPT_CONVERSATION_RESPONSE_SCHEMA = vol.Any(template, None) From 372886bf6c0990e6f06322b6d2fc176d1f7583e2 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 3 Mar 2024 18:58:28 +0100 Subject: [PATCH 0196/1691] Add package detection to Reolink (#112126) Add package detection --- .../components/reolink/binary_sensor.py | 9 ++++++ homeassistant/components/reolink/number.py | 32 +++++++++++++++++++ homeassistant/components/reolink/strings.json | 15 +++++++++ 3 files changed, 56 insertions(+) diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index 03b30d8195e..04f3f7c6186 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from reolink_aio.api import ( DUAL_LENS_DUAL_MOTION_MODELS, FACE_DETECTION_TYPE, + PACKAGE_DETECTION_TYPE, PERSON_DETECTION_TYPE, PET_DETECTION_TYPE, VEHICLE_DETECTION_TYPE, @@ -86,6 +87,14 @@ BINARY_SENSORS = ( value=lambda api, ch: api.ai_detected(ch, PET_DETECTION_TYPE), supported=lambda api, ch: api.supported(ch, "ai_animal"), ), + ReolinkBinarySensorEntityDescription( + key=PACKAGE_DETECTION_TYPE, + translation_key="package", + icon="mdi:gift-outline", + icon_off="mdi:gift-off-outline", + value=lambda api, ch: api.ai_detected(ch, PACKAGE_DETECTION_TYPE), + supported=lambda api, ch: api.ai_supported(ch, PACKAGE_DETECTION_TYPE), + ), ReolinkBinarySensorEntityDescription( key="visitor", translation_key="visitor", diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index b27976eaa0e..7f3c2cbbb72 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -166,6 +166,21 @@ NUMBER_ENTITIES = ( value=lambda api, ch: api.ai_sensitivity(ch, "vehicle"), method=lambda api, ch, value: api.set_ai_sensitivity(ch, int(value), "vehicle"), ), + ReolinkNumberEntityDescription( + key="ai_package_sensititvity", + cmd_key="GetAiAlarm", + translation_key="ai_package_sensititvity", + icon="mdi:gift-outline", + entity_category=EntityCategory.CONFIG, + native_step=1, + native_min_value=0, + native_max_value=100, + supported=lambda api, ch: ( + api.supported(ch, "ai_sensitivity") and api.ai_supported(ch, "package") + ), + value=lambda api, ch: api.ai_sensitivity(ch, "package"), + method=lambda api, ch, value: api.set_ai_sensitivity(ch, int(value), "package"), + ), ReolinkNumberEntityDescription( key="ai_pet_sensititvity", cmd_key="GetAiAlarm", @@ -249,6 +264,23 @@ NUMBER_ENTITIES = ( value=lambda api, ch: api.ai_delay(ch, "vehicle"), method=lambda api, ch, value: api.set_ai_delay(ch, int(value), "vehicle"), ), + ReolinkNumberEntityDescription( + key="ai_package_delay", + cmd_key="GetAiAlarm", + translation_key="ai_package_delay", + icon="mdi:gift-outline", + entity_category=EntityCategory.CONFIG, + entity_registry_enabled_default=False, + native_step=1, + native_unit_of_measurement=UnitOfTime.SECONDS, + native_min_value=0, + native_max_value=8, + supported=lambda api, ch: ( + api.supported(ch, "ai_delay") and api.ai_supported(ch, "package") + ), + value=lambda api, ch: api.ai_delay(ch, "package"), + method=lambda api, ch, value: api.set_ai_delay(ch, int(value), "package"), + ), ReolinkNumberEntityDescription( key="ai_pet_delay", cmd_key="GetAiAlarm", diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index fb4d42bb97d..c57eec22ce5 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -103,6 +103,9 @@ "visitor": { "name": "Visitor" }, + "package": { + "name": "Package" + }, "motion_lens_0": { "name": "Motion lens 0" }, @@ -124,6 +127,9 @@ "visitor_lens_0": { "name": "Visitor lens 0" }, + "package_lens_0": { + "name": "Package lens 0" + }, "motion_lens_1": { "name": "Motion lens 1" }, @@ -144,6 +150,9 @@ }, "visitor_lens_1": { "name": "Visitor lens 1" + }, + "package_lens_1": { + "name": "Package lens 1" } }, "button": { @@ -270,6 +279,9 @@ "ai_vehicle_sensititvity": { "name": "AI vehicle sensitivity" }, + "ai_package_sensititvity": { + "name": "AI package sensitivity" + }, "ai_pet_sensititvity": { "name": "AI pet sensitivity" }, @@ -285,6 +297,9 @@ "ai_vehicle_delay": { "name": "AI vehicle delay" }, + "ai_package_delay": { + "name": "AI package delay" + }, "ai_pet_delay": { "name": "AI pet delay" }, From ba9733e90b8a734c5cbcc862427f270759c62237 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 10:23:08 -1000 Subject: [PATCH 0197/1691] Try to preload the config platform when loading a component (#112104) --- homeassistant/config.py | 3 +++ homeassistant/loader.py | 8 ++++++++ tests/test_loader.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/homeassistant/config.py b/homeassistant/config.py index 896c9be3653..3fd76c275c2 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1458,6 +1458,9 @@ async def async_process_component_config( # noqa: C901 # Check if the integration has a custom config validator config_validator = None + # A successful call to async_get_component will prime + # the cache for platform_exists to ensure it does no + # blocking I/O if integration.platform_exists("config") is not False: # If the config platform cannot possibly exist, don't try to load it. try: diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 7873fbd4c74..af15a7acabf 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -900,6 +900,14 @@ class Integration: ) raise ImportError(f"Exception importing {self.pkg_path}") from err + if self.platform_exists("config"): + # Setting up a component always checks if the config + # platform exists. Since we may be running in the executor + # we will use this opportunity to cache the config platform + # as well. + with suppress(ImportError): + self.get_platform("config") + return cache[self.domain] async def async_get_platform(self, platform_name: str) -> ModuleType: diff --git a/tests/test_loader.py b/tests/test_loader.py index 34104987de4..9552849ac4a 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1038,6 +1038,37 @@ async def test_hass_components_use_reported( ) in caplog.text +async def test_async_get_component_preloads_config( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Verify async_get_component will try to preload the config platform.""" + executor_import_integration = _get_test_integration( + hass, "executor_import", True, import_executor=True + ) + assert executor_import_integration.import_executor is True + + assert "homeassistant.components.executor_import" not in sys.modules + assert "custom_components.executor_import" not in sys.modules + + with patch( + "homeassistant.loader.importlib.import_module" + ) as mock_import, patch.object( + executor_import_integration, "platform_exists", return_value=True + ) as mock_platform_exists: + await executor_import_integration.async_get_component() + + assert mock_platform_exists.call_count == 1 + assert mock_import.call_count == 2 + assert ( + mock_import.call_args_list[0][0][0] + == "homeassistant.components.executor_import" + ) + assert ( + mock_import.call_args_list[1][0][0] + == "homeassistant.components.executor_import.config" + ) + + async def test_async_get_component_deadlock_fallback( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: From ec1400d39280c499dcab642508fa4ccc11917e57 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 10:29:45 -1000 Subject: [PATCH 0198/1691] Fix flakey tplink test (#112135) The test here was assuming the first slow was the one it wanted, but sometimes the other flow wins the race --- tests/components/tplink/test_config_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index 2834625292c..54a8893ad98 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -1113,7 +1113,6 @@ async def test_reauth_update_other_flows( mock_config_entry: MockConfigEntry, mock_discovery: AsyncMock, mock_connect: AsyncMock, - # mock_init, ) -> None: """Test reauth updates other reauth flows.""" mock_config_entry2 = MockConfigEntry( @@ -1138,10 +1137,10 @@ async def test_reauth_update_other_flows( flows = hass.config_entries.flow.async_progress() assert len(flows) == 2 - result = flows[0] + flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows} + result = flows_by_entry_id[mock_config_entry.entry_id] assert result["step_id"] == "reauth_confirm" assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY - result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ From 3c960b7d4e7a23cedb765226a98e35d1b4ab52e8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 3 Mar 2024 21:33:33 +0100 Subject: [PATCH 0199/1691] Add icon translations to Melnor (#111906) * Use default icon for Melnor * Use default icon for Melnor --- homeassistant/components/melnor/icons.json | 23 ++++++++++++++++++++++ homeassistant/components/melnor/number.py | 3 --- homeassistant/components/melnor/switch.py | 3 +-- tests/components/melnor/test_number.py | 3 --- tests/components/melnor/test_switch.py | 1 - 5 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/melnor/icons.json diff --git a/homeassistant/components/melnor/icons.json b/homeassistant/components/melnor/icons.json new file mode 100644 index 00000000000..72e479a7d5a --- /dev/null +++ b/homeassistant/components/melnor/icons.json @@ -0,0 +1,23 @@ +{ + "entity": { + "number": { + "manual_minutes": { + "default": "mdi:timer-cog-outline" + }, + "frequency_interval_hours": { + "default": "mdi:calendar-refresh-outline" + }, + "frequency_duration_minutes": { + "default": "mdi:timer-outline" + } + }, + "switch": { + "manual": { + "default": "mdi:sprinkler" + }, + "frequency": { + "default": "mdi:calendar-sync-outline" + } + } + } +} diff --git a/homeassistant/components/melnor/number.py b/homeassistant/components/melnor/number.py index caf2d499851..f68cdd1b29e 100644 --- a/homeassistant/components/melnor/number.py +++ b/homeassistant/components/melnor/number.py @@ -46,7 +46,6 @@ ZONE_ENTITY_DESCRIPTIONS: list[MelnorZoneNumberEntityDescription] = [ entity_category=EntityCategory.CONFIG, native_max_value=360, native_min_value=1, - icon="mdi:timer-cog-outline", key="manual_minutes", translation_key="manual_minutes", native_unit_of_measurement=UnitOfTime.MINUTES, @@ -57,7 +56,6 @@ ZONE_ENTITY_DESCRIPTIONS: list[MelnorZoneNumberEntityDescription] = [ entity_category=EntityCategory.CONFIG, native_max_value=168, native_min_value=1, - icon="mdi:calendar-refresh-outline", key="frequency_interval_hours", translation_key="frequency_interval_hours", native_unit_of_measurement=UnitOfTime.HOURS, @@ -68,7 +66,6 @@ ZONE_ENTITY_DESCRIPTIONS: list[MelnorZoneNumberEntityDescription] = [ entity_category=EntityCategory.CONFIG, native_max_value=360, native_min_value=1, - icon="mdi:timer-outline", key="frequency_duration_minutes", translation_key="frequency_duration_minutes", native_unit_of_measurement=UnitOfTime.MINUTES, diff --git a/homeassistant/components/melnor/switch.py b/homeassistant/components/melnor/switch.py index e3c0e0afa15..48b9a65e920 100644 --- a/homeassistant/components/melnor/switch.py +++ b/homeassistant/components/melnor/switch.py @@ -43,15 +43,14 @@ class MelnorSwitchEntityDescription( ZONE_ENTITY_DESCRIPTIONS = [ MelnorSwitchEntityDescription( device_class=SwitchDeviceClass.SWITCH, - icon="mdi:sprinkler", key="manual", + translation_key="manual", name=None, on_off_fn=lambda valve, bool: valve.set_is_watering(bool), state_fn=lambda valve: valve.is_watering, ), MelnorSwitchEntityDescription( device_class=SwitchDeviceClass.SWITCH, - icon="mdi:calendar-sync-outline", key="frequency", translation_key="frequency", on_off_fn=lambda valve, bool: valve.set_frequency_enabled(bool), diff --git a/tests/components/melnor/test_number.py b/tests/components/melnor/test_number.py index a8d358c2ac2..f8fc31f27c3 100644 --- a/tests/components/melnor/test_number.py +++ b/tests/components/melnor/test_number.py @@ -29,7 +29,6 @@ async def test_manual_watering_minutes(hass: HomeAssistant) -> None: assert number.attributes["max"] == 360 assert number.attributes["min"] == 1 assert number.attributes["step"] == 1.0 - assert number.attributes["icon"] == "mdi:timer-cog-outline" assert device.zone1.manual_watering_minutes == 0 @@ -65,7 +64,6 @@ async def test_frequency_interval_hours(hass: HomeAssistant) -> None: assert number.attributes["max"] == 168 assert number.attributes["min"] == 1 assert number.attributes["step"] == 1.0 - assert number.attributes["icon"] == "mdi:calendar-refresh-outline" assert device.zone1.frequency.interval_hours == 0 @@ -101,7 +99,6 @@ async def test_frequency_duration_minutes(hass: HomeAssistant) -> None: assert number.attributes["max"] == 360 assert number.attributes["min"] == 1 assert number.attributes["step"] == 1.0 - assert number.attributes["icon"] == "mdi:timer-outline" assert device.zone1.frequency.duration_minutes == 0 diff --git a/tests/components/melnor/test_switch.py b/tests/components/melnor/test_switch.py index fdd5e8ad33e..ec2d4c25c55 100644 --- a/tests/components/melnor/test_switch.py +++ b/tests/components/melnor/test_switch.py @@ -26,7 +26,6 @@ async def test_manual_watering_switch_metadata(hass: HomeAssistant) -> None: assert switch is not None assert switch.attributes["device_class"] == SwitchDeviceClass.SWITCH - assert switch.attributes["icon"] == "mdi:sprinkler" async def test_manual_watering_switch_on_off(hass: HomeAssistant) -> None: From d6cbadba3e75a88e876b393fd37b69e98cabfd6a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 11:42:16 -1000 Subject: [PATCH 0200/1691] Ensure setup loads top level component before platforms (#112057) --- homeassistant/setup.py | 23 +++++++++++++++-------- tests/test_setup.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index efbaacac43e..7da82156c3f 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -482,8 +482,21 @@ async def async_prepare_setup_platform( log_error(str(err)) return None + # Platforms cannot exist on their own, they are part of their integration. + # If the integration is not set up yet, and can be set up, set it up. + # + # We do this before we import the platform so the platform already knows + # where the top level component is. + # + if load_top_level_component := integration.domain not in hass.config.components: + try: + component = await integration.async_get_component() + except ImportError as exc: + log_error(f"Unable to import the component ({exc}).") + return None + try: - platform = integration.get_platform(domain) + platform = await integration.async_get_platform(domain) except ImportError as exc: log_error(f"Platform not found ({exc}).") return None @@ -494,13 +507,7 @@ async def async_prepare_setup_platform( # Platforms cannot exist on their own, they are part of their integration. # If the integration is not set up yet, and can be set up, set it up. - if integration.domain not in hass.config.components: - try: - component = integration.get_component() - except ImportError as exc: - log_error(f"Unable to import the component ({exc}).") - return None - + if load_top_level_component: if ( hasattr(component, "setup") or hasattr(component, "async_setup") ) and not await async_setup_component(hass, integration.domain, hass_config): diff --git a/tests/test_setup.py b/tests/test_setup.py index 0f6a4302200..29ced934745 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -7,7 +7,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest import voluptuous as vol -from homeassistant import config_entries, setup +from homeassistant import config_entries, loader, setup from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -822,3 +822,30 @@ async def test_importing_integration_in_executor( assert await setup.async_setup_component(hass, "test_package_loaded_executor", {}) assert await setup.async_setup_component(hass, "test_package_loaded_executor", {}) await hass.async_block_till_done() + + +async def test_async_prepare_setup_platform( + hass: HomeAssistant, + enable_custom_integrations: None, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test we can prepare a platform setup.""" + integration = await loader.async_get_integration(hass, "test") + with patch.object( + integration, "async_get_component", side_effect=ImportError("test is broken") + ): + assert ( + await setup.async_prepare_setup_platform(hass, {}, "config", "test") is None + ) + + assert "test is broken" in caplog.text + + caplog.clear() + # There is no actual config platform for this integration + assert await setup.async_prepare_setup_platform(hass, {}, "config", "test") is None + assert "No module named 'custom_components.test.config'" in caplog.text + + button_platform = ( + await setup.async_prepare_setup_platform(hass, {}, "button", "test") is None + ) + assert button_platform is not None From 68f17b5eab88b375ce208d392805b6124d8ac854 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 3 Mar 2024 23:13:40 +0100 Subject: [PATCH 0201/1691] Add Reolink PTZ patrol start/stop (#112129) --- homeassistant/components/reolink/strings.json | 3 +++ homeassistant/components/reolink/switch.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index c57eec22ce5..b8525549b1d 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -419,6 +419,9 @@ "gaurd_return": { "name": "Guard return" }, + "ptz_patrol": { + "name": "PTZ patrol" + }, "email": { "name": "Email on event" }, diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index 7f57b78df1e..73f4cbc9aa4 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -33,7 +33,7 @@ class ReolinkSwitchEntityDescription( """A class that describes switch entities.""" method: Callable[[Host, int, bool], Any] - value: Callable[[Host, int], bool] + value: Callable[[Host, int], bool | None] @dataclass(frozen=True, kw_only=True) @@ -108,6 +108,14 @@ SWITCH_ENTITIES = ( value=lambda api, ch: api.ptz_guard_enabled(ch), method=lambda api, ch, value: api.set_ptz_guard(ch, enable=value), ), + ReolinkSwitchEntityDescription( + key="ptz_patrol", + translation_key="ptz_patrol", + icon="mdi:map-marker-path", + supported=lambda api, ch: api.supported(ch, "ptz_patrol"), + value=lambda api, ch: None, + method=lambda api, ch, value: api.ctrl_ptz_patrol(ch, value), + ), ReolinkSwitchEntityDescription( key="email", cmd_key="GetEmail", @@ -275,7 +283,7 @@ class ReolinkSwitchEntity(ReolinkChannelCoordinatorEntity, SwitchEntity): super().__init__(reolink_data, channel) @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if switch is on.""" return self.entity_description.value(self._host.api, self._channel) From 60f81c834031fe358015e4b4a27b2dbc201f057d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 14:16:44 -1000 Subject: [PATCH 0202/1691] Fix async_prepare_setup_platform test (#112143) --- tests/test_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_setup.py b/tests/test_setup.py index 29ced934745..1a27ed5584b 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -843,7 +843,7 @@ async def test_async_prepare_setup_platform( caplog.clear() # There is no actual config platform for this integration assert await setup.async_prepare_setup_platform(hass, {}, "config", "test") is None - assert "No module named 'custom_components.test.config'" in caplog.text + assert "test.config not found" in caplog.text button_platform = ( await setup.async_prepare_setup_platform(hass, {}, "button", "test") is None From f9e00ed45b25d425f52cf2ed2b661751a8d00994 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 3 Mar 2024 19:17:02 -0500 Subject: [PATCH 0203/1691] Fix ZHA groups page (#112140) * Fix ZHA groups page * test --- homeassistant/components/zha/core/group.py | 3 ++ tests/components/zha/test_switch.py | 62 +++++++++++----------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index fc7f1f8758f..a62c00e7106 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -87,6 +87,9 @@ class ZHAGroupMember(LogMixin): entity_info = [] for entity_ref in zha_device_registry.get(self.device.ieee): + # We have device entities now that don't leverage cluster handlers + if not entity_ref.cluster_handlers: + continue entity = entity_registry.async_get(entity_ref.reference_id) handler = list(entity_ref.cluster_handlers.values())[0] diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index cb1d87210a7..9a9fbc2b50e 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -25,6 +25,7 @@ from homeassistant.components.zha.core.helpers import get_zha_gateway from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from homeassistant.setup import async_setup_component @@ -39,6 +40,8 @@ from .common import ( ) from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE +from tests.common import MockConfigEntry + ON = 1 OFF = 0 IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8" @@ -91,27 +94,6 @@ def zigpy_cover_device(zigpy_device_mock): return zigpy_device_mock(endpoints) -@pytest.fixture -async def coordinator(hass, zigpy_device_mock, zha_device_joined): - """Test ZHA light platform.""" - - zigpy_device = zigpy_device_mock( - { - 1: { - SIG_EP_INPUT: [], - SIG_EP_OUTPUT: [], - SIG_EP_TYPE: zha.DeviceType.COLOR_DIMMABLE_LIGHT, - } - }, - ieee="00:15:8d:00:02:32:4f:32", - nwk=0x0000, - node_descriptor=b"\xf8\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", - ) - zha_device = await zha_device_joined(zigpy_device) - zha_device.available = True - return zha_device - - @pytest.fixture async def device_switch_1(hass, zigpy_device_mock, zha_device_joined): """Test ZHA switch platform.""" @@ -300,19 +282,41 @@ async def zigpy_device_tuya(hass, zigpy_device_mock, zha_device_joined): new=0, ) async def test_zha_group_switch_entity( - hass: HomeAssistant, device_switch_1, device_switch_2, coordinator + hass: HomeAssistant, + device_switch_1, + device_switch_2, + entity_registry: er.EntityRegistry, + config_entry: MockConfigEntry, ) -> None: """Test the switch entity for a ZHA group.""" + + # make sure we can still get groups when counter entities exist + entity_id = "sensor.coordinator_manufacturer_coordinator_model_counter_1" + state = hass.states.get(entity_id) + assert state is None + + # Enable the entity. + entity_registry.async_update_entity(entity_id, disabled_by=None) + await hass.config_entries.async_reload(config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state is not None + assert state.state == "1" + zha_gateway = get_zha_gateway(hass) assert zha_gateway is not None - zha_gateway.coordinator_zha_device = coordinator - coordinator._zha_gateway = zha_gateway device_switch_1._zha_gateway = zha_gateway device_switch_2._zha_gateway = zha_gateway - member_ieee_addresses = [device_switch_1.ieee, device_switch_2.ieee] + member_ieee_addresses = [ + device_switch_1.ieee, + device_switch_2.ieee, + zha_gateway.coordinator_zha_device.ieee, + ] members = [ GroupMember(device_switch_1.ieee, 1), GroupMember(device_switch_2.ieee, 1), + GroupMember(zha_gateway.coordinator_zha_device.ieee, 1), ] # test creating a group with 2 members @@ -320,7 +324,7 @@ async def test_zha_group_switch_entity( await hass.async_block_till_done() assert zha_group is not None - assert len(zha_group.members) == 2 + assert len(zha_group.members) == 3 for member in zha_group.members: assert member.device.ieee in member_ieee_addresses assert member.group == zha_group @@ -333,12 +337,6 @@ async def test_zha_group_switch_entity( dev1_cluster_on_off = device_switch_1.device.endpoints[1].on_off dev2_cluster_on_off = device_switch_2.device.endpoints[1].on_off - await async_enable_traffic(hass, [device_switch_1, device_switch_2], enabled=False) - await async_wait_for_updates(hass) - - # test that the switches were created and that they are off - assert hass.states.get(entity_id).state == STATE_UNAVAILABLE - # allow traffic to flow through the gateway and device await async_enable_traffic(hass, [device_switch_1, device_switch_2]) await async_wait_for_updates(hass) From 5cb5a1141f6fb4911b2972cecd30bf217a18a105 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 3 Mar 2024 16:54:05 -0800 Subject: [PATCH 0204/1691] Limit rainbird aiohttp client session to a single connection (#112146) Limit rainbird to a single open http connection --- homeassistant/components/rainbird/__init__.py | 7 +++-- .../components/rainbird/config_flow.py | 7 +++-- .../components/rainbird/coordinator.py | 11 ++++++++ tests/components/rainbird/conftest.py | 28 ++++++++++++++++++- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py index f7eab3bc2f2..2a660435e17 100644 --- a/homeassistant/components/rainbird/__init__.py +++ b/homeassistant/components/rainbird/__init__.py @@ -11,11 +11,10 @@ from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr, entity_registry as er -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac from .const import CONF_SERIAL_NUMBER -from .coordinator import RainbirdData +from .coordinator import RainbirdData, async_create_clientsession _LOGGER = logging.getLogger(__name__) @@ -36,9 +35,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) + clientsession = async_create_clientsession() + entry.async_on_unload(clientsession.close) controller = AsyncRainbirdController( AsyncRainbirdClient( - async_get_clientsession(hass), + clientsession, entry.data[CONF_HOST], entry.data[CONF_PASSWORD], ) diff --git a/homeassistant/components/rainbird/config_flow.py b/homeassistant/components/rainbird/config_flow.py index c4f76c5c9c5..44576db8a33 100644 --- a/homeassistant/components/rainbird/config_flow.py +++ b/homeassistant/components/rainbird/config_flow.py @@ -23,7 +23,6 @@ from homeassistant.config_entries import ( from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, selector -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import format_mac from .const import ( @@ -33,6 +32,7 @@ from .const import ( DOMAIN, TIMEOUT_SECONDS, ) +from .coordinator import async_create_clientsession _LOGGER = logging.getLogger(__name__) @@ -104,9 +104,10 @@ class RainbirdConfigFlowHandler(ConfigFlow, domain=DOMAIN): Raises a ConfigFlowError on failure. """ + clientsession = async_create_clientsession() controller = AsyncRainbirdController( AsyncRainbirdClient( - async_get_clientsession(self.hass), + clientsession, host, password, ) @@ -127,6 +128,8 @@ class RainbirdConfigFlowHandler(ConfigFlow, domain=DOMAIN): f"Error connecting to Rain Bird controller: {str(err)}", "cannot_connect", ) from err + finally: + await clientsession.close() async def async_finish( self, diff --git a/homeassistant/components/rainbird/coordinator.py b/homeassistant/components/rainbird/coordinator.py index 9f1ea95b333..22aaf2d11a0 100644 --- a/homeassistant/components/rainbird/coordinator.py +++ b/homeassistant/components/rainbird/coordinator.py @@ -9,6 +9,7 @@ from functools import cached_property import logging from typing import TypeVar +import aiohttp from pyrainbird.async_client import ( AsyncRainbirdController, RainbirdApiException, @@ -28,6 +29,9 @@ UPDATE_INTERVAL = datetime.timedelta(minutes=1) # changes, so we refresh it less often. CALENDAR_UPDATE_INTERVAL = datetime.timedelta(minutes=15) +# Rainbird devices can only accept a single request at a time +CONECTION_LIMIT = 1 + _LOGGER = logging.getLogger(__name__) _T = TypeVar("_T") @@ -43,6 +47,13 @@ class RainbirdDeviceState: rain_delay: int +def async_create_clientsession() -> aiohttp.ClientSession: + """Create a rainbird async_create_clientsession with a connection limit.""" + return aiohttp.ClientSession( + connector=aiohttp.TCPConnector(limit=CONECTION_LIMIT), + ) + + class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]): """Coordinator for rainbird API calls.""" diff --git a/tests/components/rainbird/conftest.py b/tests/components/rainbird/conftest.py index 52b98e5c6b6..53df24264df 100644 --- a/tests/components/rainbird/conftest.py +++ b/tests/components/rainbird/conftest.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Generator from http import HTTPStatus import json from typing import Any @@ -15,7 +16,7 @@ from homeassistant.components.rainbird.const import ( ATTR_DURATION, DEFAULT_TRIGGER_TIME_MINUTES, ) -from homeassistant.const import Platform +from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, Platform from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -155,6 +156,31 @@ def setup_platforms( yield +@pytest.fixture(autouse=True) +def aioclient_mock(hass: HomeAssistant) -> Generator[AiohttpClientMocker, None, None]: + """Context manager to mock aiohttp client.""" + mocker = AiohttpClientMocker() + + def create_session(): + session = mocker.create_session(hass.loop) + + async def close_session(event): + """Close session.""" + await session.close() + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, close_session) + return session + + with patch( + "homeassistant.components.rainbird.async_create_clientsession", + side_effect=create_session, + ), patch( + "homeassistant.components.rainbird.config_flow.async_create_clientsession", + side_effect=create_session, + ): + yield mocker + + def rainbird_json_response(result: dict[str, str]) -> bytes: """Create a fake API response.""" return encryption.encrypt( From ec8d23d0af20292b008b2af1e8671723303695e9 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 4 Mar 2024 02:45:06 +0100 Subject: [PATCH 0205/1691] Fix places not changed to hub in Axis tests (#112128) --- .../axis/{test_device.py => test_hub.py} | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) rename tests/components/axis/{test_device.py => test_hub.py} (87%) diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_hub.py similarity index 87% rename from tests/components/axis/test_device.py rename to tests/components/axis/test_hub.py index b41155e88b1..136ef924c8f 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_hub.py @@ -8,6 +8,7 @@ import pytest from homeassistant.components import axis, zeroconf from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN +from homeassistant.components.axis.hub import AxisHub from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.config_entries import SOURCE_ZEROCONF from homeassistant.const import ( @@ -48,12 +49,12 @@ async def test_device_setup( device_registry: dr.DeviceRegistry, ) -> None: """Successful setup.""" - device = hass.data[AXIS_DOMAIN][setup_config_entry.entry_id] + hub = AxisHub.get_hub(hass, setup_config_entry) - assert device.api.vapix.firmware_version == "9.10.1" - assert device.api.vapix.product_number == "M1065-LW" - assert device.api.vapix.product_type == "Network Camera" - assert device.api.vapix.serial_number == "00408C123456" + assert hub.api.vapix.firmware_version == "9.10.1" + assert hub.api.vapix.product_number == "M1065-LW" + assert hub.api.vapix.product_type == "Network Camera" + assert hub.api.vapix.serial_number == "00408C123456" assert len(forward_entry_setup.mock_calls) == 4 assert forward_entry_setup.mock_calls[0][1][1] == "binary_sensor" @@ -61,27 +62,27 @@ async def test_device_setup( assert forward_entry_setup.mock_calls[2][1][1] == "light" assert forward_entry_setup.mock_calls[3][1][1] == "switch" - assert device.host == config[CONF_HOST] - assert device.model == config[CONF_MODEL] - assert device.name == config[CONF_NAME] - assert device.unique_id == FORMATTED_MAC + assert hub.host == config[CONF_HOST] + assert hub.model == config[CONF_MODEL] + assert hub.name == config[CONF_NAME] + assert hub.unique_id == FORMATTED_MAC device_entry = device_registry.async_get_device( - identifiers={(AXIS_DOMAIN, device.unique_id)} + identifiers={(AXIS_DOMAIN, hub.unique_id)} ) - assert device_entry.configuration_url == device.api.config.url + assert device_entry.configuration_url == hub.api.config.url @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_BASIC_DEVICE_INFO]) async def test_device_info(hass: HomeAssistant, setup_config_entry) -> None: """Verify other path of device information works.""" - device = hass.data[AXIS_DOMAIN][setup_config_entry.entry_id] + hub = AxisHub.get_hub(hass, setup_config_entry) - assert device.api.vapix.firmware_version == "9.80.1" - assert device.api.vapix.product_number == "M1065-LW" - assert device.api.vapix.product_type == "Network Camera" - assert device.api.vapix.serial_number == "00408C123456" + assert hub.api.vapix.firmware_version == "9.80.1" + assert hub.api.vapix.product_number == "M1065-LW" + assert hub.api.vapix.product_type == "Network Camera" + assert hub.api.vapix.serial_number == "00408C123456" @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_MQTT]) @@ -111,8 +112,8 @@ async def test_update_address( hass: HomeAssistant, setup_config_entry, mock_vapix_requests ) -> None: """Test update address works.""" - device = hass.data[AXIS_DOMAIN][setup_config_entry.entry_id] - assert device.api.config.host == "1.2.3.4" + hub = AxisHub.get_hub(hass, setup_config_entry) + assert hub.api.config.host == "1.2.3.4" with patch( "homeassistant.components.axis.async_setup_entry", return_value=True @@ -133,7 +134,7 @@ async def test_update_address( ) await hass.async_block_till_done() - assert device.api.config.host == "2.3.4.5" + assert hub.api.config.host == "2.3.4.5" assert len(mock_setup_entry.mock_calls) == 1 From d50b4ccd625b262d303a97a2e31243e050d82edb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 16:16:02 -1000 Subject: [PATCH 0206/1691] Split up hassio coordinator and data into new file to allow preload (#112147) * Split up hassio coordinator and data into new file to allow preload Since we cannot mark hassio as having a config_flow, it will not get preloaded and since cloud will almost always load right after it and block the import executor, we want to preload the hassio config_flow and platform modules so the other dependants can continue on while cloud is being imported to not delay startup * tweak * tweak --- homeassistant/components/hassio/__init__.py | 562 +----------------- .../components/hassio/binary_sensor.py | 3 +- .../components/hassio/config_flow.py | 5 +- homeassistant/components/hassio/const.py | 17 + homeassistant/components/hassio/data.py | 546 +++++++++++++++++ homeassistant/components/hassio/entity.py | 3 +- homeassistant/components/hassio/sensor.py | 2 +- homeassistant/components/hassio/update.py | 16 +- tests/components/hassio/test_update.py | 6 +- 9 files changed, 610 insertions(+), 550 deletions(-) create mode 100644 homeassistant/components/hassio/data.py diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index e367a935ace..7a87e5026c1 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -2,9 +2,8 @@ from __future__ import annotations import asyncio -from collections import defaultdict from contextlib import suppress -from datetime import datetime, timedelta +from datetime import datetime import logging import os import re @@ -17,14 +16,12 @@ from homeassistant.components import panel_custom from homeassistant.components.homeassistant import async_set_stop_handler from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ATTR_MANUFACTURER, ATTR_NAME, EVENT_CORE_CONFIG_UPDATE, HASSIO_USER_NAME, Platform, ) from homeassistant.core import ( - CALLBACK_TYPE, Event, HassJob, HomeAssistant, @@ -35,24 +32,24 @@ from homeassistant.core import ( from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.event import async_call_later from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.loader import bind_hass from homeassistant.util.async_ import create_eager_task from homeassistant.util.dt import now +# config_flow, and entity platforms are imported to ensure +# other dependencies that wait for hassio are not waiting +# for hassio to import its platforms +from . import binary_sensor, config_flow, sensor, update # noqa: F401 from .addon_manager import AddonError, AddonInfo, AddonManager, AddonState # noqa: F401 from .addon_panel import async_setup_addon_panel from .auth import async_setup_auth_view from .const import ( + ADDONS_COORDINATOR, ATTR_ADDON, ATTR_ADDONS, - ATTR_AUTO_UPDATE, - ATTR_CHANGELOG, ATTR_COMPRESSED, ATTR_FOLDERS, ATTR_HOMEASSISTANT, @@ -60,26 +57,31 @@ from .const import ( ATTR_INPUT, ATTR_LOCATION, ATTR_PASSWORD, - ATTR_REPOSITORY, ATTR_SLUG, - ATTR_STARTED, - ATTR_STATE, - ATTR_URL, - ATTR_VERSION, - CONTAINER_CHANGELOG, - CONTAINER_INFO, - CONTAINER_STATS, - CORE_CONTAINER, - DATA_KEY_ADDONS, - DATA_KEY_CORE, - DATA_KEY_HOST, - DATA_KEY_OS, - DATA_KEY_SUPERVISOR, + DATA_CORE_INFO, + DATA_HOST_INFO, + DATA_INFO, DATA_KEY_SUPERVISOR_ISSUES, + DATA_OS_INFO, + DATA_STORE, + DATA_SUPERVISOR_INFO, DOMAIN, - REQUEST_REFRESH_DELAY, - SUPERVISOR_CONTAINER, - SupervisorEntityModel, + HASSIO_UPDATE_INTERVAL, +) +from .data import ( + HassioDataUpdateCoordinator, + get_addons_changelogs, # noqa: F401 + get_addons_info, # noqa: F401 + get_addons_stats, # noqa: F401 + get_core_info, # noqa: F401 + get_core_stats, # noqa: F401 + get_host_info, # noqa: F401 + get_info, # noqa: F401 + get_issues_info, # noqa: F401 + get_os_info, # noqa: F401 + get_store, # noqa: F401 + get_supervisor_info, # noqa: F401 + get_supervisor_stats, # noqa: F401 ) from .discovery import HassioServiceInfo, async_setup_discovery_view # noqa: F401 from .handler import ( # noqa: F401 @@ -116,6 +118,9 @@ _LOGGER = logging.getLogger(__name__) STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 +# If new platforms are added, be sure to import them above +# so we do not make other components that depend on hassio +# wait for the import of the platforms PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.UPDATE] CONF_FRONTEND_REPO = "development_repo" @@ -125,22 +130,6 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) - -DATA_CORE_INFO = "hassio_core_info" -DATA_CORE_STATS = "hassio_core_stats" -DATA_HOST_INFO = "hassio_host_info" -DATA_STORE = "hassio_store" -DATA_INFO = "hassio_info" -DATA_OS_INFO = "hassio_os_info" -DATA_SUPERVISOR_INFO = "hassio_supervisor_info" -DATA_SUPERVISOR_STATS = "hassio_supervisor_stats" -DATA_ADDONS_CHANGELOGS = "hassio_addons_changelogs" -DATA_ADDONS_INFO = "hassio_addons_info" -DATA_ADDONS_STATS = "hassio_addons_stats" -HASSIO_UPDATE_INTERVAL = timedelta(minutes=5) - -ADDONS_COORDINATOR = "hassio_addons_coordinator" - SERVICE_ADDON_START = "addon_start" SERVICE_ADDON_STOP = "addon_stop" SERVICE_ADDON_RESTART = "addon_restart" @@ -283,126 +272,6 @@ def hostname_from_addon_slug(addon_slug: str) -> str: return addon_slug.replace("_", "-") -@callback -@bind_hass -def get_info(hass: HomeAssistant) -> dict[str, Any] | None: - """Return generic information from Supervisor. - - Async friendly. - """ - return hass.data.get(DATA_INFO) - - -@callback -@bind_hass -def get_host_info(hass: HomeAssistant) -> dict[str, Any] | None: - """Return generic host information. - - Async friendly. - """ - return hass.data.get(DATA_HOST_INFO) - - -@callback -@bind_hass -def get_store(hass: HomeAssistant) -> dict[str, Any] | None: - """Return store information. - - Async friendly. - """ - return hass.data.get(DATA_STORE) - - -@callback -@bind_hass -def get_supervisor_info(hass: HomeAssistant) -> dict[str, Any] | None: - """Return Supervisor information. - - Async friendly. - """ - return hass.data.get(DATA_SUPERVISOR_INFO) - - -@callback -@bind_hass -def get_addons_info(hass: HomeAssistant) -> dict[str, dict[str, Any]] | None: - """Return Addons info. - - Async friendly. - """ - return hass.data.get(DATA_ADDONS_INFO) - - -@callback -@bind_hass -def get_addons_stats(hass: HomeAssistant) -> dict[str, Any]: - """Return Addons stats. - - Async friendly. - """ - return hass.data.get(DATA_ADDONS_STATS) or {} - - -@callback -@bind_hass -def get_core_stats(hass: HomeAssistant) -> dict[str, Any]: - """Return core stats. - - Async friendly. - """ - return hass.data.get(DATA_CORE_STATS) or {} - - -@callback -@bind_hass -def get_supervisor_stats(hass: HomeAssistant) -> dict[str, Any]: - """Return supervisor stats. - - Async friendly. - """ - return hass.data.get(DATA_SUPERVISOR_STATS) or {} - - -@callback -@bind_hass -def get_addons_changelogs(hass: HomeAssistant): - """Return Addons changelogs. - - Async friendly. - """ - return hass.data.get(DATA_ADDONS_CHANGELOGS) - - -@callback -@bind_hass -def get_os_info(hass: HomeAssistant) -> dict[str, Any] | None: - """Return OS information. - - Async friendly. - """ - return hass.data.get(DATA_OS_INFO) - - -@callback -@bind_hass -def get_core_info(hass: HomeAssistant) -> dict[str, Any] | None: - """Return Home Assistant Core information from Supervisor. - - Async friendly. - """ - return hass.data.get(DATA_CORE_INFO) - - -@callback -@bind_hass -def get_issues_info(hass: HomeAssistant) -> SupervisorIssues | None: - """Return Supervisor issues info. - - Async friendly. - """ - return hass.data.get(DATA_KEY_SUPERVISOR_ISSUES) - - @callback @bind_hass def is_hassio(hass: HomeAssistant) -> bool: @@ -652,372 +521,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.pop(ADDONS_COORDINATOR, None) return unload_ok - - -@callback -def async_register_addons_in_dev_reg( - entry_id: str, dev_reg: dr.DeviceRegistry, addons: list[dict[str, Any]] -) -> None: - """Register addons in the device registry.""" - for addon in addons: - params = DeviceInfo( - identifiers={(DOMAIN, addon[ATTR_SLUG])}, - model=SupervisorEntityModel.ADDON, - sw_version=addon[ATTR_VERSION], - name=addon[ATTR_NAME], - entry_type=dr.DeviceEntryType.SERVICE, - configuration_url=f"homeassistant://hassio/addon/{addon[ATTR_SLUG]}", - ) - if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): - params[ATTR_MANUFACTURER] = manufacturer - dev_reg.async_get_or_create(config_entry_id=entry_id, **params) - - -@callback -def async_register_os_in_dev_reg( - entry_id: str, dev_reg: dr.DeviceRegistry, os_dict: dict[str, Any] -) -> None: - """Register OS in the device registry.""" - params = DeviceInfo( - identifiers={(DOMAIN, "OS")}, - manufacturer="Home Assistant", - model=SupervisorEntityModel.OS, - sw_version=os_dict[ATTR_VERSION], - name="Home Assistant Operating System", - entry_type=dr.DeviceEntryType.SERVICE, - ) - dev_reg.async_get_or_create(config_entry_id=entry_id, **params) - - -@callback -def async_register_host_in_dev_reg( - entry_id: str, - dev_reg: dr.DeviceRegistry, -) -> None: - """Register host in the device registry.""" - params = DeviceInfo( - identifiers={(DOMAIN, "host")}, - manufacturer="Home Assistant", - model=SupervisorEntityModel.HOST, - name="Home Assistant Host", - entry_type=dr.DeviceEntryType.SERVICE, - ) - dev_reg.async_get_or_create(config_entry_id=entry_id, **params) - - -@callback -def async_register_core_in_dev_reg( - entry_id: str, - dev_reg: dr.DeviceRegistry, - core_dict: dict[str, Any], -) -> None: - """Register OS in the device registry.""" - params = DeviceInfo( - identifiers={(DOMAIN, "core")}, - manufacturer="Home Assistant", - model=SupervisorEntityModel.CORE, - sw_version=core_dict[ATTR_VERSION], - name="Home Assistant Core", - entry_type=dr.DeviceEntryType.SERVICE, - ) - dev_reg.async_get_or_create(config_entry_id=entry_id, **params) - - -@callback -def async_register_supervisor_in_dev_reg( - entry_id: str, - dev_reg: dr.DeviceRegistry, - supervisor_dict: dict[str, Any], -) -> None: - """Register OS in the device registry.""" - params = DeviceInfo( - identifiers={(DOMAIN, "supervisor")}, - manufacturer="Home Assistant", - model=SupervisorEntityModel.SUPERVIOSR, - sw_version=supervisor_dict[ATTR_VERSION], - name="Home Assistant Supervisor", - entry_type=dr.DeviceEntryType.SERVICE, - ) - dev_reg.async_get_or_create(config_entry_id=entry_id, **params) - - -@callback -def async_remove_addons_from_dev_reg( - dev_reg: dr.DeviceRegistry, addons: set[str] -) -> None: - """Remove addons from the device registry.""" - for addon_slug in addons: - if dev := dev_reg.async_get_device(identifiers={(DOMAIN, addon_slug)}): - dev_reg.async_remove_device(dev.id) - - -class HassioDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=hass-enforce-coordinator-module - """Class to retrieve Hass.io status.""" - - def __init__( - self, hass: HomeAssistant, config_entry: ConfigEntry, dev_reg: dr.DeviceRegistry - ) -> None: - """Initialize coordinator.""" - super().__init__( - hass, - _LOGGER, - name=DOMAIN, - update_interval=HASSIO_UPDATE_INTERVAL, - # We don't want an immediate refresh since we want to avoid - # fetching the container stats right away and avoid hammering - # the Supervisor API on startup - request_refresh_debouncer=Debouncer( - hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False - ), - ) - self.hassio: HassIO = hass.data[DOMAIN] - self.data = {} - self.entry_id = config_entry.entry_id - self.dev_reg = dev_reg - self.is_hass_os = (get_info(self.hass) or {}).get("hassos") is not None - self._container_updates: defaultdict[str, dict[str, set[str]]] = defaultdict( - lambda: defaultdict(set) - ) - - async def _async_update_data(self) -> dict[str, Any]: - """Update data via library.""" - is_first_update = not self.data - - try: - await self.force_data_refresh(is_first_update) - except HassioAPIError as err: - raise UpdateFailed(f"Error on Supervisor API: {err}") from err - - new_data: dict[str, Any] = {} - supervisor_info = get_supervisor_info(self.hass) or {} - addons_info = get_addons_info(self.hass) or {} - addons_stats = get_addons_stats(self.hass) - addons_changelogs = get_addons_changelogs(self.hass) - store_data = get_store(self.hass) or {} - - repositories = { - repo[ATTR_SLUG]: repo[ATTR_NAME] - for repo in store_data.get("repositories", []) - } - - new_data[DATA_KEY_ADDONS] = { - addon[ATTR_SLUG]: { - **addon, - **((addons_stats or {}).get(addon[ATTR_SLUG]) or {}), - ATTR_AUTO_UPDATE: (addons_info.get(addon[ATTR_SLUG]) or {}).get( - ATTR_AUTO_UPDATE, False - ), - ATTR_CHANGELOG: (addons_changelogs or {}).get(addon[ATTR_SLUG]), - ATTR_REPOSITORY: repositories.get( - addon.get(ATTR_REPOSITORY), addon.get(ATTR_REPOSITORY, "") - ), - } - for addon in supervisor_info.get("addons", []) - } - if self.is_hass_os: - new_data[DATA_KEY_OS] = get_os_info(self.hass) - - new_data[DATA_KEY_CORE] = { - **(get_core_info(self.hass) or {}), - **get_core_stats(self.hass), - } - new_data[DATA_KEY_SUPERVISOR] = { - **supervisor_info, - **get_supervisor_stats(self.hass), - } - new_data[DATA_KEY_HOST] = get_host_info(self.hass) or {} - - # If this is the initial refresh, register all addons and return the dict - if is_first_update: - async_register_addons_in_dev_reg( - self.entry_id, self.dev_reg, new_data[DATA_KEY_ADDONS].values() - ) - async_register_core_in_dev_reg( - self.entry_id, self.dev_reg, new_data[DATA_KEY_CORE] - ) - async_register_supervisor_in_dev_reg( - self.entry_id, self.dev_reg, new_data[DATA_KEY_SUPERVISOR] - ) - async_register_host_in_dev_reg(self.entry_id, self.dev_reg) - if self.is_hass_os: - async_register_os_in_dev_reg( - self.entry_id, self.dev_reg, new_data[DATA_KEY_OS] - ) - - # Remove add-ons that are no longer installed from device registry - supervisor_addon_devices = { - list(device.identifiers)[0][1] - for device in self.dev_reg.devices.values() - if self.entry_id in device.config_entries - and device.model == SupervisorEntityModel.ADDON - } - if stale_addons := supervisor_addon_devices - set(new_data[DATA_KEY_ADDONS]): - async_remove_addons_from_dev_reg(self.dev_reg, stale_addons) - - if not self.is_hass_os and ( - dev := self.dev_reg.async_get_device(identifiers={(DOMAIN, "OS")}) - ): - # Remove the OS device if it exists and the installation is not hassos - self.dev_reg.async_remove_device(dev.id) - - # If there are new add-ons, we should reload the config entry so we can - # create new devices and entities. We can return an empty dict because - # coordinator will be recreated. - if self.data and set(new_data[DATA_KEY_ADDONS]) - set( - self.data[DATA_KEY_ADDONS] - ): - self.hass.async_create_task( - self.hass.config_entries.async_reload(self.entry_id) - ) - return {} - - return new_data - - async def force_info_update_supervisor(self) -> None: - """Force update of the supervisor info.""" - self.hass.data[DATA_SUPERVISOR_INFO] = await self.hassio.get_supervisor_info() - await self.async_refresh() - - async def force_data_refresh(self, first_update: bool) -> None: - """Force update of the addon info.""" - container_updates = self._container_updates - - data = self.hass.data - hassio = self.hassio - updates = { - DATA_INFO: hassio.get_info(), - DATA_CORE_INFO: hassio.get_core_info(), - DATA_SUPERVISOR_INFO: hassio.get_supervisor_info(), - DATA_OS_INFO: hassio.get_os_info(), - } - if CONTAINER_STATS in container_updates[CORE_CONTAINER]: - updates[DATA_CORE_STATS] = hassio.get_core_stats() - if CONTAINER_STATS in container_updates[SUPERVISOR_CONTAINER]: - updates[DATA_SUPERVISOR_STATS] = hassio.get_supervisor_stats() - - results = await asyncio.gather(*updates.values()) - for key, result in zip(updates, results): - data[key] = result - - _addon_data = data[DATA_SUPERVISOR_INFO].get("addons", []) - all_addons: list[str] = [] - started_addons: list[str] = [] - for addon in _addon_data: - slug = addon[ATTR_SLUG] - all_addons.append(slug) - if addon[ATTR_STATE] == ATTR_STARTED: - started_addons.append(slug) - # - # Update add-on info if its the first update or - # there is at least one entity that needs the data. - # - # When entities are added they call async_enable_container_updates - # to enable updates for the endpoints they need via - # async_added_to_hass. This ensures that we only update - # the data for the endpoints that are needed to avoid unnecessary - # API calls since otherwise we would fetch stats for all containers - # and throw them away. - # - for data_key, update_func, enabled_key, wanted_addons, needs_first_update in ( - ( - DATA_ADDONS_STATS, - self._update_addon_stats, - CONTAINER_STATS, - started_addons, - False, - ), - ( - DATA_ADDONS_CHANGELOGS, - self._update_addon_changelog, - CONTAINER_CHANGELOG, - all_addons, - True, - ), - ( - DATA_ADDONS_INFO, - self._update_addon_info, - CONTAINER_INFO, - all_addons, - True, - ), - ): - container_data: dict[str, Any] = data.setdefault(data_key, {}) - container_data.update( - dict( - await asyncio.gather( - *[ - update_func(slug) - for slug in wanted_addons - if (first_update and needs_first_update) - or enabled_key in container_updates[slug] - ] - ) - ) - ) - - async def _update_addon_stats(self, slug: str) -> tuple[str, dict[str, Any] | None]: - """Update single addon stats.""" - try: - stats = await self.hassio.get_addon_stats(slug) - return (slug, stats) - except HassioAPIError as err: - _LOGGER.warning("Could not fetch stats for %s: %s", slug, err) - return (slug, None) - - async def _update_addon_changelog(self, slug: str) -> tuple[str, str | None]: - """Return the changelog for an add-on.""" - try: - changelog = await self.hassio.get_addon_changelog(slug) - return (slug, changelog) - except HassioAPIError as err: - _LOGGER.warning("Could not fetch changelog for %s: %s", slug, err) - return (slug, None) - - async def _update_addon_info(self, slug: str) -> tuple[str, dict[str, Any] | None]: - """Return the info for an add-on.""" - try: - info = await self.hassio.get_addon_info(slug) - return (slug, info) - except HassioAPIError as err: - _LOGGER.warning("Could not fetch info for %s: %s", slug, err) - return (slug, None) - - @callback - def async_enable_container_updates( - self, slug: str, entity_id: str, types: set[str] - ) -> CALLBACK_TYPE: - """Enable updates for an add-on.""" - enabled_updates = self._container_updates[slug] - for key in types: - enabled_updates[key].add(entity_id) - - @callback - def _remove() -> None: - for key in types: - enabled_updates[key].remove(entity_id) - - return _remove - - async def _async_refresh( - self, - log_failures: bool = True, - raise_on_auth_failed: bool = False, - scheduled: bool = False, - raise_on_entry_error: bool = False, - ) -> None: - """Refresh data.""" - if not scheduled and not raise_on_auth_failed: - # Force refreshing updates for non-scheduled updates - # If `raise_on_auth_failed` is set, it means this is - # the first refresh and we do not want to delay - # startup or cause a timeout so we only refresh the - # updates if this is not a scheduled refresh and - # we are not doing the first refresh. - try: - await self.hassio.refresh_updates() - except HassioAPIError as err: - _LOGGER.warning("Error on Supervisor API: %s", err) - - await super()._async_refresh( - log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error - ) diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py index f57cfa472c4..b5b4bef1bd8 100644 --- a/homeassistant/components/hassio/binary_sensor.py +++ b/homeassistant/components/hassio/binary_sensor.py @@ -12,8 +12,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ADDONS_COORDINATOR -from .const import ATTR_STARTED, ATTR_STATE, DATA_KEY_ADDONS +from .const import ADDONS_COORDINATOR, ATTR_STARTED, ATTR_STATE, DATA_KEY_ADDONS from .entity import HassioAddonEntity diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py index af4e778f91f..a4c79a97675 100644 --- a/homeassistant/components/hassio/config_flow.py +++ b/homeassistant/components/hassio/config_flow.py @@ -1,14 +1,11 @@ """Config flow for Home Assistant Supervisor integration.""" from __future__ import annotations -import logging from typing import Any from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from . import DOMAIN - -_LOGGER = logging.getLogger(__name__) +from .const import DOMAIN class HassIoConfigFlow(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index b495745e87d..4559326b521 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -1,4 +1,5 @@ """Hass.io const variables.""" +from datetime import timedelta from enum import StrEnum DOMAIN = "hassio" @@ -58,6 +59,22 @@ EVENT_ISSUE_REMOVED = "issue_removed" UPDATE_KEY_SUPERVISOR = "supervisor" +ADDONS_COORDINATOR = "hassio_addons_coordinator" + + +DATA_CORE_INFO = "hassio_core_info" +DATA_CORE_STATS = "hassio_core_stats" +DATA_HOST_INFO = "hassio_host_info" +DATA_STORE = "hassio_store" +DATA_INFO = "hassio_info" +DATA_OS_INFO = "hassio_os_info" +DATA_SUPERVISOR_INFO = "hassio_supervisor_info" +DATA_SUPERVISOR_STATS = "hassio_supervisor_stats" +DATA_ADDONS_CHANGELOGS = "hassio_addons_changelogs" +DATA_ADDONS_INFO = "hassio_addons_info" +DATA_ADDONS_STATS = "hassio_addons_stats" +HASSIO_UPDATE_INTERVAL = timedelta(minutes=5) + ATTR_AUTO_UPDATE = "auto_update" ATTR_VERSION = "version" ATTR_VERSION_LATEST = "version_latest" diff --git a/homeassistant/components/hassio/data.py b/homeassistant/components/hassio/data.py new file mode 100644 index 00000000000..cb496bd0e5a --- /dev/null +++ b/homeassistant/components/hassio/data.py @@ -0,0 +1,546 @@ +"""Data for Hass.io.""" +from __future__ import annotations + +import asyncio +from collections import defaultdict +import logging +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_MANUFACTURER, ATTR_NAME +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.loader import bind_hass + +from .const import ( + ATTR_AUTO_UPDATE, + ATTR_CHANGELOG, + ATTR_REPOSITORY, + ATTR_SLUG, + ATTR_STARTED, + ATTR_STATE, + ATTR_URL, + ATTR_VERSION, + CONTAINER_CHANGELOG, + CONTAINER_INFO, + CONTAINER_STATS, + CORE_CONTAINER, + DATA_ADDONS_CHANGELOGS, + DATA_ADDONS_INFO, + DATA_ADDONS_STATS, + DATA_CORE_INFO, + DATA_CORE_STATS, + DATA_HOST_INFO, + DATA_INFO, + DATA_KEY_ADDONS, + DATA_KEY_CORE, + DATA_KEY_HOST, + DATA_KEY_OS, + DATA_KEY_SUPERVISOR, + DATA_KEY_SUPERVISOR_ISSUES, + DATA_OS_INFO, + DATA_STORE, + DATA_SUPERVISOR_INFO, + DATA_SUPERVISOR_STATS, + DOMAIN, + HASSIO_UPDATE_INTERVAL, + REQUEST_REFRESH_DELAY, + SUPERVISOR_CONTAINER, + SupervisorEntityModel, +) +from .handler import HassIO, HassioAPIError +from .issues import SupervisorIssues + +_LOGGER = logging.getLogger(__name__) + + +@callback +@bind_hass +def get_info(hass: HomeAssistant) -> dict[str, Any] | None: + """Return generic information from Supervisor. + + Async friendly. + """ + return hass.data.get(DATA_INFO) + + +@callback +@bind_hass +def get_host_info(hass: HomeAssistant) -> dict[str, Any] | None: + """Return generic host information. + + Async friendly. + """ + return hass.data.get(DATA_HOST_INFO) + + +@callback +@bind_hass +def get_store(hass: HomeAssistant) -> dict[str, Any] | None: + """Return store information. + + Async friendly. + """ + return hass.data.get(DATA_STORE) + + +@callback +@bind_hass +def get_supervisor_info(hass: HomeAssistant) -> dict[str, Any] | None: + """Return Supervisor information. + + Async friendly. + """ + return hass.data.get(DATA_SUPERVISOR_INFO) + + +@callback +@bind_hass +def get_addons_info(hass: HomeAssistant) -> dict[str, dict[str, Any]] | None: + """Return Addons info. + + Async friendly. + """ + return hass.data.get(DATA_ADDONS_INFO) + + +@callback +@bind_hass +def get_addons_stats(hass: HomeAssistant) -> dict[str, Any]: + """Return Addons stats. + + Async friendly. + """ + return hass.data.get(DATA_ADDONS_STATS) or {} + + +@callback +@bind_hass +def get_core_stats(hass: HomeAssistant) -> dict[str, Any]: + """Return core stats. + + Async friendly. + """ + return hass.data.get(DATA_CORE_STATS) or {} + + +@callback +@bind_hass +def get_supervisor_stats(hass: HomeAssistant) -> dict[str, Any]: + """Return supervisor stats. + + Async friendly. + """ + return hass.data.get(DATA_SUPERVISOR_STATS) or {} + + +@callback +@bind_hass +def get_addons_changelogs(hass: HomeAssistant): + """Return Addons changelogs. + + Async friendly. + """ + return hass.data.get(DATA_ADDONS_CHANGELOGS) + + +@callback +@bind_hass +def get_os_info(hass: HomeAssistant) -> dict[str, Any] | None: + """Return OS information. + + Async friendly. + """ + return hass.data.get(DATA_OS_INFO) + + +@callback +@bind_hass +def get_core_info(hass: HomeAssistant) -> dict[str, Any] | None: + """Return Home Assistant Core information from Supervisor. + + Async friendly. + """ + return hass.data.get(DATA_CORE_INFO) + + +@callback +@bind_hass +def get_issues_info(hass: HomeAssistant) -> SupervisorIssues | None: + """Return Supervisor issues info. + + Async friendly. + """ + return hass.data.get(DATA_KEY_SUPERVISOR_ISSUES) + + +@callback +def async_register_addons_in_dev_reg( + entry_id: str, dev_reg: dr.DeviceRegistry, addons: list[dict[str, Any]] +) -> None: + """Register addons in the device registry.""" + for addon in addons: + params = DeviceInfo( + identifiers={(DOMAIN, addon[ATTR_SLUG])}, + model=SupervisorEntityModel.ADDON, + sw_version=addon[ATTR_VERSION], + name=addon[ATTR_NAME], + entry_type=dr.DeviceEntryType.SERVICE, + configuration_url=f"homeassistant://hassio/addon/{addon[ATTR_SLUG]}", + ) + if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL): + params[ATTR_MANUFACTURER] = manufacturer + dev_reg.async_get_or_create(config_entry_id=entry_id, **params) + + +@callback +def async_register_os_in_dev_reg( + entry_id: str, dev_reg: dr.DeviceRegistry, os_dict: dict[str, Any] +) -> None: + """Register OS in the device registry.""" + params = DeviceInfo( + identifiers={(DOMAIN, "OS")}, + manufacturer="Home Assistant", + model=SupervisorEntityModel.OS, + sw_version=os_dict[ATTR_VERSION], + name="Home Assistant Operating System", + entry_type=dr.DeviceEntryType.SERVICE, + ) + dev_reg.async_get_or_create(config_entry_id=entry_id, **params) + + +@callback +def async_register_host_in_dev_reg( + entry_id: str, + dev_reg: dr.DeviceRegistry, +) -> None: + """Register host in the device registry.""" + params = DeviceInfo( + identifiers={(DOMAIN, "host")}, + manufacturer="Home Assistant", + model=SupervisorEntityModel.HOST, + name="Home Assistant Host", + entry_type=dr.DeviceEntryType.SERVICE, + ) + dev_reg.async_get_or_create(config_entry_id=entry_id, **params) + + +@callback +def async_register_core_in_dev_reg( + entry_id: str, + dev_reg: dr.DeviceRegistry, + core_dict: dict[str, Any], +) -> None: + """Register OS in the device registry.""" + params = DeviceInfo( + identifiers={(DOMAIN, "core")}, + manufacturer="Home Assistant", + model=SupervisorEntityModel.CORE, + sw_version=core_dict[ATTR_VERSION], + name="Home Assistant Core", + entry_type=dr.DeviceEntryType.SERVICE, + ) + dev_reg.async_get_or_create(config_entry_id=entry_id, **params) + + +@callback +def async_register_supervisor_in_dev_reg( + entry_id: str, + dev_reg: dr.DeviceRegistry, + supervisor_dict: dict[str, Any], +) -> None: + """Register OS in the device registry.""" + params = DeviceInfo( + identifiers={(DOMAIN, "supervisor")}, + manufacturer="Home Assistant", + model=SupervisorEntityModel.SUPERVIOSR, + sw_version=supervisor_dict[ATTR_VERSION], + name="Home Assistant Supervisor", + entry_type=dr.DeviceEntryType.SERVICE, + ) + dev_reg.async_get_or_create(config_entry_id=entry_id, **params) + + +@callback +def async_remove_addons_from_dev_reg( + dev_reg: dr.DeviceRegistry, addons: set[str] +) -> None: + """Remove addons from the device registry.""" + for addon_slug in addons: + if dev := dev_reg.async_get_device(identifiers={(DOMAIN, addon_slug)}): + dev_reg.async_remove_device(dev.id) + + +class HassioDataUpdateCoordinator(DataUpdateCoordinator): # pylint: disable=hass-enforce-coordinator-module + """Class to retrieve Hass.io status.""" + + def __init__( + self, hass: HomeAssistant, config_entry: ConfigEntry, dev_reg: dr.DeviceRegistry + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=HASSIO_UPDATE_INTERVAL, + # We don't want an immediate refresh since we want to avoid + # fetching the container stats right away and avoid hammering + # the Supervisor API on startup + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False + ), + ) + self.hassio: HassIO = hass.data[DOMAIN] + self.data = {} + self.entry_id = config_entry.entry_id + self.dev_reg = dev_reg + self.is_hass_os = (get_info(self.hass) or {}).get("hassos") is not None + self._container_updates: defaultdict[str, dict[str, set[str]]] = defaultdict( + lambda: defaultdict(set) + ) + + async def _async_update_data(self) -> dict[str, Any]: + """Update data via library.""" + is_first_update = not self.data + + try: + await self.force_data_refresh(is_first_update) + except HassioAPIError as err: + raise UpdateFailed(f"Error on Supervisor API: {err}") from err + + new_data: dict[str, Any] = {} + supervisor_info = get_supervisor_info(self.hass) or {} + addons_info = get_addons_info(self.hass) or {} + addons_stats = get_addons_stats(self.hass) + addons_changelogs = get_addons_changelogs(self.hass) + store_data = get_store(self.hass) or {} + + repositories = { + repo[ATTR_SLUG]: repo[ATTR_NAME] + for repo in store_data.get("repositories", []) + } + + new_data[DATA_KEY_ADDONS] = { + addon[ATTR_SLUG]: { + **addon, + **((addons_stats or {}).get(addon[ATTR_SLUG]) or {}), + ATTR_AUTO_UPDATE: (addons_info.get(addon[ATTR_SLUG]) or {}).get( + ATTR_AUTO_UPDATE, False + ), + ATTR_CHANGELOG: (addons_changelogs or {}).get(addon[ATTR_SLUG]), + ATTR_REPOSITORY: repositories.get( + addon.get(ATTR_REPOSITORY), addon.get(ATTR_REPOSITORY, "") + ), + } + for addon in supervisor_info.get("addons", []) + } + if self.is_hass_os: + new_data[DATA_KEY_OS] = get_os_info(self.hass) + + new_data[DATA_KEY_CORE] = { + **(get_core_info(self.hass) or {}), + **get_core_stats(self.hass), + } + new_data[DATA_KEY_SUPERVISOR] = { + **supervisor_info, + **get_supervisor_stats(self.hass), + } + new_data[DATA_KEY_HOST] = get_host_info(self.hass) or {} + + # If this is the initial refresh, register all addons and return the dict + if is_first_update: + async_register_addons_in_dev_reg( + self.entry_id, self.dev_reg, new_data[DATA_KEY_ADDONS].values() + ) + async_register_core_in_dev_reg( + self.entry_id, self.dev_reg, new_data[DATA_KEY_CORE] + ) + async_register_supervisor_in_dev_reg( + self.entry_id, self.dev_reg, new_data[DATA_KEY_SUPERVISOR] + ) + async_register_host_in_dev_reg(self.entry_id, self.dev_reg) + if self.is_hass_os: + async_register_os_in_dev_reg( + self.entry_id, self.dev_reg, new_data[DATA_KEY_OS] + ) + + # Remove add-ons that are no longer installed from device registry + supervisor_addon_devices = { + list(device.identifiers)[0][1] + for device in self.dev_reg.devices.values() + if self.entry_id in device.config_entries + and device.model == SupervisorEntityModel.ADDON + } + if stale_addons := supervisor_addon_devices - set(new_data[DATA_KEY_ADDONS]): + async_remove_addons_from_dev_reg(self.dev_reg, stale_addons) + + if not self.is_hass_os and ( + dev := self.dev_reg.async_get_device(identifiers={(DOMAIN, "OS")}) + ): + # Remove the OS device if it exists and the installation is not hassos + self.dev_reg.async_remove_device(dev.id) + + # If there are new add-ons, we should reload the config entry so we can + # create new devices and entities. We can return an empty dict because + # coordinator will be recreated. + if self.data and set(new_data[DATA_KEY_ADDONS]) - set( + self.data[DATA_KEY_ADDONS] + ): + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry_id) + ) + return {} + + return new_data + + async def force_info_update_supervisor(self) -> None: + """Force update of the supervisor info.""" + self.hass.data[DATA_SUPERVISOR_INFO] = await self.hassio.get_supervisor_info() + await self.async_refresh() + + async def force_data_refresh(self, first_update: bool) -> None: + """Force update of the addon info.""" + container_updates = self._container_updates + + data = self.hass.data + hassio = self.hassio + updates = { + DATA_INFO: hassio.get_info(), + DATA_CORE_INFO: hassio.get_core_info(), + DATA_SUPERVISOR_INFO: hassio.get_supervisor_info(), + DATA_OS_INFO: hassio.get_os_info(), + } + if CONTAINER_STATS in container_updates[CORE_CONTAINER]: + updates[DATA_CORE_STATS] = hassio.get_core_stats() + if CONTAINER_STATS in container_updates[SUPERVISOR_CONTAINER]: + updates[DATA_SUPERVISOR_STATS] = hassio.get_supervisor_stats() + + results = await asyncio.gather(*updates.values()) + for key, result in zip(updates, results): + data[key] = result + + _addon_data = data[DATA_SUPERVISOR_INFO].get("addons", []) + all_addons: list[str] = [] + started_addons: list[str] = [] + for addon in _addon_data: + slug = addon[ATTR_SLUG] + all_addons.append(slug) + if addon[ATTR_STATE] == ATTR_STARTED: + started_addons.append(slug) + # + # Update add-on info if its the first update or + # there is at least one entity that needs the data. + # + # When entities are added they call async_enable_container_updates + # to enable updates for the endpoints they need via + # async_added_to_hass. This ensures that we only update + # the data for the endpoints that are needed to avoid unnecessary + # API calls since otherwise we would fetch stats for all containers + # and throw them away. + # + for data_key, update_func, enabled_key, wanted_addons, needs_first_update in ( + ( + DATA_ADDONS_STATS, + self._update_addon_stats, + CONTAINER_STATS, + started_addons, + False, + ), + ( + DATA_ADDONS_CHANGELOGS, + self._update_addon_changelog, + CONTAINER_CHANGELOG, + all_addons, + True, + ), + ( + DATA_ADDONS_INFO, + self._update_addon_info, + CONTAINER_INFO, + all_addons, + True, + ), + ): + container_data: dict[str, Any] = data.setdefault(data_key, {}) + container_data.update( + dict( + await asyncio.gather( + *[ + update_func(slug) + for slug in wanted_addons + if (first_update and needs_first_update) + or enabled_key in container_updates[slug] + ] + ) + ) + ) + + async def _update_addon_stats(self, slug: str) -> tuple[str, dict[str, Any] | None]: + """Update single addon stats.""" + try: + stats = await self.hassio.get_addon_stats(slug) + return (slug, stats) + except HassioAPIError as err: + _LOGGER.warning("Could not fetch stats for %s: %s", slug, err) + return (slug, None) + + async def _update_addon_changelog(self, slug: str) -> tuple[str, str | None]: + """Return the changelog for an add-on.""" + try: + changelog = await self.hassio.get_addon_changelog(slug) + return (slug, changelog) + except HassioAPIError as err: + _LOGGER.warning("Could not fetch changelog for %s: %s", slug, err) + return (slug, None) + + async def _update_addon_info(self, slug: str) -> tuple[str, dict[str, Any] | None]: + """Return the info for an add-on.""" + try: + info = await self.hassio.get_addon_info(slug) + return (slug, info) + except HassioAPIError as err: + _LOGGER.warning("Could not fetch info for %s: %s", slug, err) + return (slug, None) + + @callback + def async_enable_container_updates( + self, slug: str, entity_id: str, types: set[str] + ) -> CALLBACK_TYPE: + """Enable updates for an add-on.""" + enabled_updates = self._container_updates[slug] + for key in types: + enabled_updates[key].add(entity_id) + + @callback + def _remove() -> None: + for key in types: + enabled_updates[key].remove(entity_id) + + return _remove + + async def _async_refresh( + self, + log_failures: bool = True, + raise_on_auth_failed: bool = False, + scheduled: bool = False, + raise_on_entry_error: bool = False, + ) -> None: + """Refresh data.""" + if not scheduled and not raise_on_auth_failed: + # Force refreshing updates for non-scheduled updates + # If `raise_on_auth_failed` is set, it means this is + # the first refresh and we do not want to delay + # startup or cause a timeout so we only refresh the + # updates if this is not a scheduled refresh and + # we are not doing the first refresh. + try: + await self.hassio.refresh_updates() + except HassioAPIError as err: + _LOGGER.warning("Error on Supervisor API: %s", err) + + await super()._async_refresh( + log_failures, raise_on_auth_failed, scheduled, raise_on_entry_error + ) diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py index 63e0314dd05..8ffe4a37bb0 100644 --- a/homeassistant/components/hassio/entity.py +++ b/homeassistant/components/hassio/entity.py @@ -7,7 +7,6 @@ from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DOMAIN, HassioDataUpdateCoordinator from .const import ( ATTR_SLUG, CONTAINER_STATS, @@ -17,9 +16,11 @@ from .const import ( DATA_KEY_HOST, DATA_KEY_OS, DATA_KEY_SUPERVISOR, + DOMAIN, KEY_TO_UPDATE_TYPES, SUPERVISOR_CONTAINER, ) +from .data import HassioDataUpdateCoordinator class HassioAddonEntity(CoordinatorEntity[HassioDataUpdateCoordinator]): diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index 0214f28011d..aeb0a8fc056 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -12,8 +12,8 @@ from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfInformation from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ADDONS_COORDINATOR from .const import ( + ADDONS_COORDINATOR, ATTR_CPU_PERCENT, ATTR_MEMORY_PERCENT, ATTR_VERSION, diff --git a/homeassistant/components/hassio/update.py b/homeassistant/components/hassio/update.py index 8a3199a1121..41a505bb043 100644 --- a/homeassistant/components/hassio/update.py +++ b/homeassistant/components/hassio/update.py @@ -16,14 +16,8 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import ( - ADDONS_COORDINATOR, - async_update_addon, - async_update_core, - async_update_os, - async_update_supervisor, -) from .const import ( + ADDONS_COORDINATOR, ATTR_AUTO_UPDATE, ATTR_CHANGELOG, ATTR_VERSION, @@ -39,7 +33,13 @@ from .entity import ( HassioOSEntity, HassioSupervisorEntity, ) -from .handler import HassioAPIError +from .handler import ( + HassioAPIError, + async_update_addon, + async_update_core, + async_update_os, + async_update_supervisor, +) ENTITY_DESCRIPTION = UpdateEntityDescription( name="Update", diff --git a/tests/components/hassio/test_update.py b/tests/components/hassio/test_update.py index 42918b02266..48f2896cbf9 100644 --- a/tests/components/hassio/test_update.py +++ b/tests/components/hassio/test_update.py @@ -470,7 +470,7 @@ async def test_release_notes_between_versions( config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.get_addons_changelogs", + "homeassistant.components.hassio.data.get_addons_changelogs", return_value={"test": "# 2.0.1\nNew updates\n# 2.0.0\nOld updates"}, ): result = await async_setup_component( @@ -506,7 +506,7 @@ async def test_release_notes_full( config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.get_addons_changelogs", + "homeassistant.components.hassio.data.get_addons_changelogs", return_value={"test": "# 2.0.0\nNew updates\n# 2.0.0\nOld updates"}, ): result = await async_setup_component( @@ -542,7 +542,7 @@ async def test_not_release_notes( config_entry.add_to_hass(hass) with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.get_addons_changelogs", + "homeassistant.components.hassio.data.get_addons_changelogs", return_value={"test": None}, ): result = await async_setup_component( From f1eab3f11fc861408f6848306f8a9fc67e837776 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 16:16:50 -1000 Subject: [PATCH 0207/1691] Preload config flow if it exists when loading a component (#112145) Since config_entries always requires the config_flow to be loaded to check for migrations, load it if we know it exists when loading the underlying integration --- homeassistant/loader.py | 9 +++++++++ tests/test_loader.py | 10 +++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index af15a7acabf..140153c33c2 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -908,6 +908,15 @@ class Integration: with suppress(ImportError): self.get_platform("config") + if self.config_flow: + # If there is a config flow, we will cache it as well since + # config entry setup always has to load the flow to get the + # major/minor version for migrations. Since we may be running + # in the executor we will use this opportunity to cache the + # config_flow as well. + with suppress(ImportError): + self.get_platform("config_flow") + return cache[self.domain] async def async_get_platform(self, platform_name: str) -> ModuleType: diff --git a/tests/test_loader.py b/tests/test_loader.py index 9552849ac4a..d627d950f30 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1038,10 +1038,10 @@ async def test_hass_components_use_reported( ) in caplog.text -async def test_async_get_component_preloads_config( +async def test_async_get_component_preloads_config_and_config_flow( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: - """Verify async_get_component will try to preload the config platform.""" + """Verify async_get_component will try to preload the config and config_flow platform.""" executor_import_integration = _get_test_integration( hass, "executor_import", True, import_executor=True ) @@ -1058,7 +1058,7 @@ async def test_async_get_component_preloads_config( await executor_import_integration.async_get_component() assert mock_platform_exists.call_count == 1 - assert mock_import.call_count == 2 + assert mock_import.call_count == 3 assert ( mock_import.call_args_list[0][0][0] == "homeassistant.components.executor_import" @@ -1067,6 +1067,10 @@ async def test_async_get_component_preloads_config( mock_import.call_args_list[1][0][0] == "homeassistant.components.executor_import.config" ) + assert ( + mock_import.call_args_list[2][0][0] + == "homeassistant.components.executor_import.config_flow" + ) async def test_async_get_component_deadlock_fallback( From 331989de4c1fc9b453d748bd70a48b6cd6bfa7c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 16:20:47 -1000 Subject: [PATCH 0208/1691] Migrate condition/state/trigger helper to use async_get_platform (#112144) Currently these would always load the platform in the loop if it was not already loaded --- homeassistant/helpers/condition.py | 2 +- homeassistant/helpers/state.py | 4 +++- homeassistant/helpers/trigger.py | 2 +- tests/helpers/test_trigger.py | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index adbaa7e3efa..18894893b88 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -197,7 +197,7 @@ async def _async_get_condition_platform( f'Invalid condition "{platform}" specified {config}' ) from None try: - return integration.get_platform("condition") + return await integration.async_get_platform("condition") except ImportError: raise HomeAssistantError( f"Integration '{platform}' does not provide condition support" diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index dae63b4ead1..673cdf48bc5 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -53,7 +53,9 @@ async def async_reproduce_state( return try: - platform: ModuleType = integration.get_platform("reproduce_state") + platform: ModuleType = await integration.async_get_platform( + "reproduce_state" + ) except ImportError: _LOGGER.warning("Integration %s does not support reproduce state", domain) return diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index c9ca76cdf72..2483806abc1 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -222,7 +222,7 @@ async def _async_get_trigger_platform( except IntegrationNotFound: raise vol.Invalid(f"Invalid platform '{platform}' specified") from None try: - return integration.get_platform("trigger") + return await integration.async_get_platform("trigger") except ImportError: raise vol.Invalid( f"Integration '{platform}' does not provide trigger support" diff --git a/tests/helpers/test_trigger.py b/tests/helpers/test_trigger.py index 0a102c4ce42..75394cf0e7a 100644 --- a/tests/helpers/test_trigger.py +++ b/tests/helpers/test_trigger.py @@ -33,7 +33,8 @@ async def test_bad_trigger_platform(hass: HomeAssistant) -> None: async def test_trigger_subtype(hass: HomeAssistant) -> None: """Test trigger subtypes.""" with patch( - "homeassistant.helpers.trigger.async_get_integration", return_value=MagicMock() + "homeassistant.helpers.trigger.async_get_integration", + return_value=MagicMock(async_get_platform=AsyncMock()), ) as integration_mock: await _async_get_trigger_platform(hass, {"platform": "test.subtype"}) assert integration_mock.call_args == call(hass, "test") From bef8376f83e8c9b3ba54a4a9982707acc15ce6b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 16:21:33 -1000 Subject: [PATCH 0209/1691] Use MockConfigEntry in hue tests (#112149) needed for https://github.com/home-assistant/core/pull/112141 --- tests/components/hue/test_bridge.py | 51 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 28aa8626c42..5c27b532570 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -1,6 +1,6 @@ """Test Hue bridge.""" import asyncio -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import patch from aiohttp import client_exceptions from aiohue.errors import Unauthorized @@ -12,16 +12,21 @@ from homeassistant.components.hue import bridge from homeassistant.components.hue.const import ( CONF_ALLOW_HUE_GROUPS, CONF_ALLOW_UNREACHABLE, + DOMAIN, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from tests.common import MockConfigEntry + async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None: """Test a successful setup for V1 bridge.""" - config_entry = Mock() - config_entry.data = {"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1} - config_entry.options = {CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False} + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1}, + options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, + ) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( hass.config_entries, "async_forward_entry_setup" @@ -39,8 +44,10 @@ async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None: async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None: """Test a successful setup for V2 bridge.""" - config_entry = Mock() - config_entry.data = {"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 2} + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 2}, + ) with patch.object(bridge, "HueBridgeV2", return_value=mock_api_v2), patch.object( hass.config_entries, "async_forward_entry_setup" @@ -65,9 +72,11 @@ async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None: async def test_bridge_setup_invalid_api_key(hass: HomeAssistant) -> None: """Test we start config flow if username is no longer whitelisted.""" - entry = Mock() - entry.data = {"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1} - entry.options = {CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False} + entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1}, + options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, + ) hue_bridge = bridge.HueBridge(hass, entry) with patch.object( @@ -81,9 +90,11 @@ async def test_bridge_setup_invalid_api_key(hass: HomeAssistant) -> None: async def test_bridge_setup_timeout(hass: HomeAssistant) -> None: """Test we retry to connect if we cannot connect.""" - entry = Mock() - entry.data = {"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1} - entry.options = {CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False} + entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1}, + options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, + ) hue_bridge = bridge.HueBridge(hass, entry) with patch.object( @@ -96,9 +107,11 @@ async def test_bridge_setup_timeout(hass: HomeAssistant) -> None: async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) -> None: """Test calling reset while the entry has been setup.""" - config_entry = Mock() - config_entry.data = {"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1} - config_entry.options = {CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False} + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1}, + options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, + ) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( hass.config_entries, "async_forward_entry_setup" @@ -122,9 +135,11 @@ async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) -> async def test_handle_unauthorized(hass: HomeAssistant, mock_api_v1) -> None: """Test handling an unauthorized error on update.""" - config_entry = Mock(async_setup=AsyncMock()) - config_entry.data = {"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1} - config_entry.options = {CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False} + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 1}, + options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, + ) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1): hue_bridge = bridge.HueBridge(hass, config_entry) From f4b2c9b5696e0152265fddb3530c50022a1d407e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 17:56:50 -1000 Subject: [PATCH 0210/1691] Fix async_get_component loading in the executor when the module is already loaded (#112153) --- homeassistant/loader.py | 5 ++-- tests/test_loader.py | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 140153c33c2..0d03c4f81eb 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -847,8 +847,9 @@ class Integration: domain = self.domain # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. - load_executor = ( - self.import_executor and f"{self.pkg_path}.{domain}" not in sys.modules + load_executor = self.import_executor and ( + self.pkg_path not in sys.modules + or (self.config_flow and f"{self.pkg_path}.config_flow" not in sys.modules) ) if load_executor: try: diff --git a/tests/test_loader.py b/tests/test_loader.py index d627d950f30..a70bf7d4e3f 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1073,6 +1073,66 @@ async def test_async_get_component_preloads_config_and_config_flow( ) +async def test_async_get_component_loads_loop_if_already_in_sys_modules( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Verify async_get_component does not create an executor job if the module is already in sys.modules.""" + integration = await loader.async_get_integration( + hass, "test_package_loaded_executor" + ) + assert integration.pkg_path == "custom_components.test_package_loaded_executor" + assert integration.import_executor is True + assert integration.config_flow is True + + assert "executor_import" not in hass.config.components + assert "executor_import.config_flow" not in hass.config.components + + config_flow_module_name = f"{integration.pkg_path}.config_flow" + module_mock = MagicMock() + config_flow_module_mock = MagicMock() + + def import_module(name: str) -> Any: + if name == integration.pkg_path: + return module_mock + if name == config_flow_module_name: + return config_flow_module_mock + raise ImportError + + modules_without_config_flow = { + k: v for k, v in sys.modules.items() if k != config_flow_module_name + } + with patch.dict( + "sys.modules", + {**modules_without_config_flow, integration.pkg_path: module_mock}, + clear=True, + ), patch("homeassistant.loader.importlib.import_module", import_module): + module = await integration.async_get_component() + + # The config flow is missing so we should load + # in the executor + assert "loaded_executor=True" in caplog.text + assert "loaded_executor=False" not in caplog.text + assert module is module_mock + caplog.clear() + + with patch.dict( + "sys.modules", + { + integration.pkg_path: module_mock, + config_flow_module_name: config_flow_module_mock, + }, + ), patch("homeassistant.loader.importlib.import_module", import_module): + module = await integration.async_get_component() + + # Everything is there so we should load in the event loop + # since it will all be cached + assert "loaded_executor=False" in caplog.text + assert "loaded_executor=True" not in caplog.text + assert module is module_mock + + async def test_async_get_component_deadlock_fallback( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: From a049d0e8464e08f4087d8680bb8f30bf7d3be20b Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Sun, 3 Mar 2024 23:13:35 -0500 Subject: [PATCH 0211/1691] Add types throughout the prometheus tests (#112156) --- tests/components/prometheus/test_init.py | 132 ++++++++++++++++------- 1 file changed, 93 insertions(+), 39 deletions(-) diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index 7ee534f91ce..812a88db16e 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -65,6 +65,8 @@ from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util +from tests.typing import ClientSessionGenerator + PROMETHEUS_PATH = "homeassistant.components.prometheus" @@ -77,7 +79,11 @@ class FilterTest: @pytest.fixture(name="client") -async def setup_prometheus_client(hass, hass_client, namespace): +async def setup_prometheus_client( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + namespace: str, +): """Initialize an hass_client with Prometheus component.""" # Reset registry prometheus_client.REGISTRY = prometheus_client.CollectorRegistry(auto_describe=True) @@ -110,7 +116,12 @@ async def generate_latest_metrics(client): @pytest.mark.parametrize("namespace", [""]) -async def test_setup_enumeration(hass, hass_client, entity_registry, namespace): +async def test_setup_enumeration( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + entity_registry: er.EntityRegistry, + namespace: str, +) -> None: """Test that setup enumerates existing states/entities.""" # The order of when things are created must be carefully controlled in @@ -138,7 +149,9 @@ async def test_setup_enumeration(hass, hass_client, entity_registry, namespace): @pytest.mark.parametrize("namespace", [""]) -async def test_view_empty_namespace(client, sensor_entities) -> None: +async def test_view_empty_namespace( + client: ClientSessionGenerator, sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics view.""" body = await generate_latest_metrics(client) @@ -162,7 +175,9 @@ async def test_view_empty_namespace(client, sensor_entities) -> None: @pytest.mark.parametrize("namespace", [None]) -async def test_view_default_namespace(client, sensor_entities) -> None: +async def test_view_default_namespace( + client: ClientSessionGenerator, sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics view.""" body = await generate_latest_metrics(client) @@ -180,7 +195,9 @@ async def test_view_default_namespace(client, sensor_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_sensor_unit(client, sensor_entities) -> None: +async def test_sensor_unit( + client: ClientSessionGenerator, sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for sensors with a unit.""" body = await generate_latest_metrics(client) @@ -210,7 +227,9 @@ async def test_sensor_unit(client, sensor_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_sensor_without_unit(client, sensor_entities) -> None: +async def test_sensor_without_unit( + client: ClientSessionGenerator, sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for sensors without a unit.""" body = await generate_latest_metrics(client) @@ -234,7 +253,9 @@ async def test_sensor_without_unit(client, sensor_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_sensor_device_class(client, sensor_entities) -> None: +async def test_sensor_device_class( + client: ClientSessionGenerator, sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for sensor with a device_class.""" body = await generate_latest_metrics(client) @@ -270,7 +291,9 @@ async def test_sensor_device_class(client, sensor_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_input_number(client, input_number_entities) -> None: +async def test_input_number( + client: ClientSessionGenerator, input_number_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for input_number.""" body = await generate_latest_metrics(client) @@ -294,7 +317,9 @@ async def test_input_number(client, input_number_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_number(client, number_entities) -> None: +async def test_number( + client: ClientSessionGenerator, number_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for number.""" body = await generate_latest_metrics(client) @@ -318,7 +343,9 @@ async def test_number(client, number_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_battery(client, sensor_entities) -> None: +async def test_battery( + client: ClientSessionGenerator, sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for battery.""" body = await generate_latest_metrics(client) @@ -330,7 +357,10 @@ async def test_battery(client, sensor_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_climate(client, climate_entities) -> None: +async def test_climate( + client: ClientSessionGenerator, + climate_entities: dict[str, er.RegistryEntry | dict[str, Any]], +) -> None: """Test prometheus metrics for climate entities.""" body = await generate_latest_metrics(client) @@ -366,7 +396,10 @@ async def test_climate(client, climate_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_humidifier(client, humidifier_entities) -> None: +async def test_humidifier( + client: ClientSessionGenerator, + humidifier_entities: dict[str, er.RegistryEntry | dict[str, Any]], +) -> None: """Test prometheus metrics for humidifier entities.""" body = await generate_latest_metrics(client) @@ -397,7 +430,10 @@ async def test_humidifier(client, humidifier_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_attributes(client, switch_entities) -> None: +async def test_attributes( + client: ClientSessionGenerator, + switch_entities: dict[str, er.RegistryEntry | dict[str, Any]], +) -> None: """Test prometheus metrics for entity attributes.""" body = await generate_latest_metrics(client) @@ -427,7 +463,9 @@ async def test_attributes(client, switch_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_binary_sensor(client, binary_sensor_entities) -> None: +async def test_binary_sensor( + client: ClientSessionGenerator, binary_sensor_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for binary_sensor.""" body = await generate_latest_metrics(client) @@ -445,7 +483,9 @@ async def test_binary_sensor(client, binary_sensor_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_input_boolean(client, input_boolean_entities) -> None: +async def test_input_boolean( + client: ClientSessionGenerator, input_boolean_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for input_boolean.""" body = await generate_latest_metrics(client) @@ -463,7 +503,9 @@ async def test_input_boolean(client, input_boolean_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_light(client, light_entities) -> None: +async def test_light( + client: ClientSessionGenerator, light_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for lights.""" body = await generate_latest_metrics(client) @@ -499,7 +541,9 @@ async def test_light(client, light_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_lock(client, lock_entities) -> None: +async def test_lock( + client: ClientSessionGenerator, lock_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for lock.""" body = await generate_latest_metrics(client) @@ -517,7 +561,9 @@ async def test_lock(client, lock_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_cover(client, cover_entities) -> None: +async def test_cover( + client: ClientSessionGenerator, cover_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for cover.""" data = {**cover_entities} body = await generate_latest_metrics(client) @@ -576,7 +622,9 @@ async def test_cover(client, cover_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_device_tracker(client, device_tracker_entities) -> None: +async def test_device_tracker( + client: ClientSessionGenerator, device_tracker_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for device_tracker.""" body = await generate_latest_metrics(client) @@ -593,7 +641,9 @@ async def test_device_tracker(client, device_tracker_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_counter(client, counter_entities) -> None: +async def test_counter( + client: ClientSessionGenerator, counter_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for counter.""" body = await generate_latest_metrics(client) @@ -605,7 +655,9 @@ async def test_counter(client, counter_entities) -> None: @pytest.mark.parametrize("namespace", [""]) -async def test_update(client, update_entities) -> None: +async def test_update( + client: ClientSessionGenerator, update_entities: dict[str, er.RegistryEntry] +) -> None: """Test prometheus metrics for update.""" body = await generate_latest_metrics(client) @@ -625,9 +677,9 @@ async def test_update(client, update_entities) -> None: async def test_renaming_entity_name( hass: HomeAssistant, entity_registry: er.EntityRegistry, - client, - sensor_entities, - climate_entities, + client: ClientSessionGenerator, + sensor_entities: dict[str, er.RegistryEntry], + climate_entities: dict[str, er.RegistryEntry | dict[str, Any]], ) -> None: """Test renaming entity name.""" data = {**sensor_entities, **climate_entities} @@ -751,9 +803,9 @@ async def test_renaming_entity_name( async def test_renaming_entity_id( hass: HomeAssistant, entity_registry: er.EntityRegistry, - client, - sensor_entities, - climate_entities, + client: ClientSessionGenerator, + sensor_entities: dict[str, er.RegistryEntry], + climate_entities: dict[str, er.RegistryEntry | dict[str, Any]], ) -> None: """Test renaming entity id.""" data = {**sensor_entities, **climate_entities} @@ -831,9 +883,9 @@ async def test_renaming_entity_id( async def test_deleting_entity( hass: HomeAssistant, entity_registry: er.EntityRegistry, - client, - sensor_entities, - climate_entities, + client: ClientSessionGenerator, + sensor_entities: dict[str, er.RegistryEntry], + climate_entities: dict[str, er.RegistryEntry | dict[str, Any]], ) -> None: """Test deleting a entity.""" data = {**sensor_entities, **climate_entities} @@ -910,9 +962,9 @@ async def test_deleting_entity( async def test_disabling_entity( hass: HomeAssistant, entity_registry: er.EntityRegistry, - client, - sensor_entities, - climate_entities, + client: ClientSessionGenerator, + sensor_entities: dict[str, er.RegistryEntry], + climate_entities: dict[str, er.RegistryEntry | dict[str, Any]], ) -> None: """Test disabling a entity.""" data = {**sensor_entities, **climate_entities} @@ -1760,14 +1812,14 @@ def mock_client_fixture(): yield counter_client -async def test_minimal_config(hass: HomeAssistant, mock_client) -> None: +async def test_minimal_config(hass: HomeAssistant, mock_client: mock.MagicMock) -> None: """Test the minimal config and defaults of component.""" config = {prometheus.DOMAIN: {}} assert await async_setup_component(hass, prometheus.DOMAIN, config) await hass.async_block_till_done() -async def test_full_config(hass: HomeAssistant, mock_client) -> None: +async def test_full_config(hass: HomeAssistant, mock_client: mock.MagicMock) -> None: """Test the full config of component.""" config = { prometheus.DOMAIN: { @@ -1792,14 +1844,14 @@ async def test_full_config(hass: HomeAssistant, mock_client) -> None: await hass.async_block_till_done() -async def _setup(hass, filter_config): +async def _setup(hass: HomeAssistant, filter_config): """Shared set up for filtering tests.""" config = {prometheus.DOMAIN: {"filter": filter_config}} assert await async_setup_component(hass, prometheus.DOMAIN, config) await hass.async_block_till_done() -async def test_allowlist(hass: HomeAssistant, mock_client) -> None: +async def test_allowlist(hass: HomeAssistant, mock_client: mock.MagicMock) -> None: """Test an allowlist only config.""" await _setup( hass, @@ -1828,7 +1880,7 @@ async def test_allowlist(hass: HomeAssistant, mock_client) -> None: mock_client.labels.reset_mock() -async def test_denylist(hass: HomeAssistant, mock_client) -> None: +async def test_denylist(hass: HomeAssistant, mock_client: mock.MagicMock) -> None: """Test a denylist only config.""" await _setup( hass, @@ -1857,7 +1909,9 @@ async def test_denylist(hass: HomeAssistant, mock_client) -> None: mock_client.labels.reset_mock() -async def test_filtered_denylist(hass: HomeAssistant, mock_client) -> None: +async def test_filtered_denylist( + hass: HomeAssistant, mock_client: mock.MagicMock +) -> None: """Test a denylist config with a filtering allowlist.""" await _setup( hass, From d7507fd8a3d8ca1dcd37d732f148d86d908144af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 18:57:01 -1000 Subject: [PATCH 0212/1691] Run more of hassio setup in in tasks (#112151) * Run more of hassio setup in in tasks There were a few more places were we waited in sequence where we have to make remote api calls that could be moved to tasks * tweak * tweak --- homeassistant/components/hassio/__init__.py | 26 ++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 7a87e5026c1..8e8d077c838 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -375,6 +375,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, push_config) push_config_task = hass.async_create_task(push_config(None), eager_start=True) + # Start listening for problems with supervisor and making issues + hass.data[DATA_KEY_SUPERVISOR_ISSUES] = issues = SupervisorIssues(hass, hassio) + issues_task = hass.async_create_task(issues.setup(), eager_start=True) async def async_service_handler(service: ServiceCall) -> None: """Handle service calls for Hass.io.""" @@ -435,8 +438,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: ) # Fetch data - await update_info_data() - await push_config_task + update_info_task = hass.async_create_task(update_info_data(), eager_start=True) async def _async_stop(hass: HomeAssistant, restart: bool) -> None: """Stop or restart home assistant.""" @@ -459,7 +461,18 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: async_setup_ingress_view(hass, host) # Init add-on ingress panels - await async_setup_addon_panel(hass, hassio) + panels_task = hass.async_create_task( + async_setup_addon_panel(hass, hassio), eager_start=True + ) + + # Make sure to await the update_info task before + # _async_setup_hardware_integration is called + # so the hardware integration can be set up + # and does not fallback to calling later + await panels_task + await update_info_task + await push_config_task + await issues_task # Setup hardware integration for the detected board type @callback @@ -480,7 +493,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: hass.async_create_task( hass.config_entries.flow.async_init( hw_integration, context={"source": "system"} - ) + ), + eager_start=True, ) async_setup_hardware_integration_job = HassJob( @@ -494,10 +508,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: eager_start=True, ) - # Start listening for problems with supervisor and making issues - hass.data[DATA_KEY_SUPERVISOR_ISSUES] = issues = SupervisorIssues(hass, hassio) - await issues.setup() - return True From 99414d8b851c88187b8228c31573b5ea5c725b4b Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 4 Mar 2024 06:00:17 +0100 Subject: [PATCH 0213/1691] Streamline UniFi entity descriptions (#112136) * Use kw_only=True to get rid of Mixins * Clarify which inputs are optional and make them have default values Add doc strings to optional inputs --- homeassistant/components/unifi/button.py | 21 ++------ .../components/unifi/device_tracker.py | 20 ++------ homeassistant/components/unifi/entity.py | 20 ++++---- homeassistant/components/unifi/image.py | 21 ++------ homeassistant/components/unifi/sensor.py | 51 +++---------------- homeassistant/components/unifi/switch.py | 50 +++++------------- homeassistant/components/unifi/update.py | 22 +++----- 7 files changed, 51 insertions(+), 154 deletions(-) diff --git a/homeassistant/components/unifi/button.py b/homeassistant/components/unifi/button.py index f03971267bb..85cd6c94c74 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -6,7 +6,7 @@ from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass -from typing import Any, Generic +from typing import Any import aiounifi from aiounifi.interfaces.api_handlers import ItemEvent @@ -57,21 +57,14 @@ async def async_power_cycle_port_control_fn( await api.request(DevicePowerCyclePortRequest.create(mac, int(index))) -@dataclass(frozen=True) -class UnifiButtonEntityDescriptionMixin(Generic[HandlerT, ApiItemT]): - """Validate and load entities from different UniFi handlers.""" - - control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class UnifiButtonEntityDescription( - ButtonEntityDescription, - UnifiEntityDescription[HandlerT, ApiItemT], - UnifiButtonEntityDescriptionMixin[HandlerT, ApiItemT], + ButtonEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT] ): """Class describing UniFi button entity.""" + control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]] + ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = ( UnifiButtonEntityDescription[Devices, Device]( @@ -84,11 +77,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = ( available_fn=async_device_available_fn, control_fn=async_restart_device_control_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda _: "Restart", object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_restart-{obj_id}", ), @@ -106,7 +96,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = ( event_to_subscribe=None, name_fn=lambda port: f"{port.name} Power Cycle", object_fn=lambda api, obj_id: api.ports[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe, unique_id_fn=lambda hub, obj_id: f"power_cycle-{obj_id}", ), diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 87bc0b6c59b..81ba99c4ebe 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -6,7 +6,7 @@ from collections.abc import Callable, Mapping from dataclasses import dataclass from datetime import timedelta import logging -from typing import Any, Generic +from typing import Any import aiounifi from aiounifi.interfaces.api_handlers import ItemEvent @@ -136,9 +136,9 @@ def async_device_heartbeat_timedelta_fn(hub: UnifiHub, obj_id: str) -> timedelta return timedelta(seconds=device.next_interval + 60) -@dataclass(frozen=True) -class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]): - """Device tracker local functions.""" +@dataclass(frozen=True, kw_only=True) +class UnifiTrackerEntityDescription(UnifiEntityDescription[HandlerT, ApiItemT]): + """Class describing UniFi device tracker entity.""" heartbeat_timedelta_fn: Callable[[UnifiHub, str], timedelta] ip_address_fn: Callable[[aiounifi.Controller, str], str | None] @@ -146,14 +146,6 @@ class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]): hostname_fn: Callable[[aiounifi.Controller, str], str | None] -@dataclass(frozen=True) -class UnifiTrackerEntityDescription( - UnifiEntityDescription[HandlerT, ApiItemT], - UnifiEntityTrackerDescriptionMixin[HandlerT, ApiItemT], -): - """Class describing UniFi device tracker entity.""" - - ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( UnifiTrackerEntityDescription[Clients, Client]( key="Client device scanner", @@ -173,7 +165,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( is_connected_fn=async_client_is_connected_fn, name_fn=lambda client: client.name or client.hostname, object_fn=lambda api, obj_id: api.clients[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"{hub.site}-{obj_id}", ip_address_fn=lambda api, obj_id: api.clients[obj_id].ip, @@ -186,13 +177,10 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=lambda api, obj_id: None, - event_is_on=None, - event_to_subscribe=None, heartbeat_timedelta_fn=async_device_heartbeat_timedelta_fn, is_connected_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].state == 1, name_fn=lambda device: device.name or device.model, object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: obj_id, ip_address_fn=lambda api, obj_id: api.devices[obj_id].ip, diff --git a/homeassistant/components/unifi/entity.py b/homeassistant/components/unifi/entity.py index a88f4c9b657..d3dc1f51873 100644 --- a/homeassistant/components/unifi/entity.py +++ b/homeassistant/components/unifi/entity.py @@ -93,26 +93,26 @@ def async_client_device_info_fn(hub: UnifiHub, obj_id: str) -> DeviceInfo: ) -@dataclass(frozen=True) -class UnifiDescription(Generic[HandlerT, ApiItemT]): - """Validate and load entities from different UniFi handlers.""" +@dataclass(frozen=True, kw_only=True) +class UnifiEntityDescription(EntityDescription, Generic[HandlerT, ApiItemT]): + """UniFi Entity Description.""" allowed_fn: Callable[[UnifiHub, str], bool] api_handler_fn: Callable[[aiounifi.Controller], HandlerT] available_fn: Callable[[UnifiHub, str], bool] device_info_fn: Callable[[UnifiHub, str], DeviceInfo | None] - event_is_on: tuple[EventKey, ...] | None - event_to_subscribe: tuple[EventKey, ...] | None name_fn: Callable[[ApiItemT], str | None] object_fn: Callable[[aiounifi.Controller, str], ApiItemT] - should_poll: bool supported_fn: Callable[[UnifiHub, str], bool | None] unique_id_fn: Callable[[UnifiHub, str], str] - -@dataclass(frozen=True) -class UnifiEntityDescription(EntityDescription, UnifiDescription[HandlerT, ApiItemT]): - """UniFi Entity Description.""" + # Optional + event_is_on: tuple[EventKey, ...] | None = None + """Which UniFi events should be used to consider state 'on'.""" + event_to_subscribe: tuple[EventKey, ...] | None = None + """Which UniFi events to listen on.""" + should_poll: bool = False + """If entity needs to do regular checks on state.""" class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]): diff --git a/homeassistant/components/unifi/image.py b/homeassistant/components/unifi/image.py index a070c158772..fb3cdf0b39e 100644 --- a/homeassistant/components/unifi/image.py +++ b/homeassistant/components/unifi/image.py @@ -6,7 +6,6 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from typing import Generic from aiounifi.interfaces.api_handlers import ItemEvent from aiounifi.interfaces.wlans import Wlans @@ -36,23 +35,16 @@ def async_wlan_qr_code_image_fn(hub: UnifiHub, wlan: Wlan) -> bytes: return hub.api.wlans.generate_wlan_qr_code(wlan) -@dataclass(frozen=True) -class UnifiImageEntityDescriptionMixin(Generic[HandlerT, ApiItemT]): - """Validate and load entities from different UniFi handlers.""" +@dataclass(frozen=True, kw_only=True) +class UnifiImageEntityDescription( + ImageEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT] +): + """Class describing UniFi image entity.""" image_fn: Callable[[UnifiHub, ApiItemT], bytes] value_fn: Callable[[ApiItemT], str | None] -@dataclass(frozen=True) -class UnifiImageEntityDescription( - ImageEntityDescription, - UnifiEntityDescription[HandlerT, ApiItemT], - UnifiImageEntityDescriptionMixin[HandlerT, ApiItemT], -): - """Class describing UniFi image entity.""" - - ENTITY_DESCRIPTIONS: tuple[UnifiImageEntityDescription, ...] = ( UnifiImageEntityDescription[Wlans, Wlan]( key="WLAN QR Code", @@ -63,11 +55,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiImageEntityDescription, ...] = ( api_handler_fn=lambda api: api.wlans, available_fn=async_wlan_available_fn, device_info_fn=async_wlan_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda wlan: "QR Code", object_fn=lambda api, obj_id: api.wlans[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"qr_code-{obj_id}", image_fn=async_wlan_qr_code_image_fn, diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index ab76e662859..bf803b2c0ab 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -9,7 +9,6 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import date, datetime, timedelta from decimal import Decimal -from typing import Generic from aiounifi.interfaces.api_handlers import ItemEvent from aiounifi.interfaces.clients import Clients @@ -154,33 +153,28 @@ def async_client_is_connected_fn(hub: UnifiHub, obj_id: str) -> bool: return True -@dataclass(frozen=True) -class UnifiSensorEntityDescriptionMixin(Generic[HandlerT, ApiItemT]): - """Validate and load entities from different UniFi handlers.""" - - value_fn: Callable[[UnifiHub, ApiItemT], datetime | float | str | None] - - @callback def async_device_state_value_fn(hub: UnifiHub, device: Device) -> str: """Retrieve the state of the device.""" return DEVICE_STATES[device.state] -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class UnifiSensorEntityDescription( - SensorEntityDescription, - UnifiEntityDescription[HandlerT, ApiItemT], - UnifiSensorEntityDescriptionMixin[HandlerT, ApiItemT], + SensorEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT] ): """Class describing UniFi sensor entity.""" + value_fn: Callable[[UnifiHub, ApiItemT], datetime | float | str | None] + + # Optional is_connected_fn: Callable[[UnifiHub, str], bool] | None = None - # Custom function to determine whether a state change should be recorded + """Calculate if source is connected.""" value_changed_fn: Callable[ [StateType | date | datetime | Decimal, datetime | float | str | None], bool, ] = lambda old, new: old != new + """Calculate whether a state change should be recorded.""" ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( @@ -196,12 +190,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.clients, available_fn=lambda hub, _: hub.available, device_info_fn=async_client_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_connected_fn=async_client_is_connected_fn, name_fn=lambda _: "RX", object_fn=lambda api, obj_id: api.clients[obj_id], - should_poll=False, supported_fn=lambda hub, _: hub.option_allow_bandwidth_sensors, unique_id_fn=lambda hub, obj_id: f"rx-{obj_id}", value_fn=async_client_rx_value_fn, @@ -218,12 +209,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.clients, available_fn=lambda hub, _: hub.available, device_info_fn=async_client_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_connected_fn=async_client_is_connected_fn, name_fn=lambda _: "TX", object_fn=lambda api, obj_id: api.clients[obj_id], - should_poll=False, supported_fn=lambda hub, _: hub.option_allow_bandwidth_sensors, unique_id_fn=lambda hub, obj_id: f"tx-{obj_id}", value_fn=async_client_tx_value_fn, @@ -239,11 +227,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.ports, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda port: f"{port.name} PoE Power", object_fn=lambda api, obj_id: api.ports[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe, unique_id_fn=lambda hub, obj_id: f"poe_power-{obj_id}", value_fn=lambda _, obj: obj.poe_power if obj.poe_mode != "off" else "0", @@ -258,11 +243,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.clients, available_fn=lambda hub, obj_id: hub.available, device_info_fn=async_client_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda client: "Uptime", object_fn=lambda api, obj_id: api.clients[obj_id], - should_poll=False, supported_fn=lambda hub, _: hub.option_allow_uptime_sensors, unique_id_fn=lambda hub, obj_id: f"uptime-{obj_id}", value_fn=async_client_uptime_value_fn, @@ -276,8 +258,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.wlans, available_fn=async_wlan_available_fn, device_info_fn=async_wlan_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda wlan: None, object_fn=lambda api, obj_id: api.wlans[obj_id], should_poll=True, @@ -295,8 +275,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.outlets, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda outlet: f"{outlet.name} Outlet Power", object_fn=lambda api, obj_id: api.outlets[obj_id], should_poll=True, @@ -315,11 +293,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda device: "AC Power Budget", object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=async_device_outlet_supported_fn, unique_id_fn=lambda hub, obj_id: f"ac_power_budget-{obj_id}", value_fn=lambda hub, device: device.outlet_ac_power_budget, @@ -335,11 +310,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda device: "AC Power Consumption", object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=async_device_outlet_supported_fn, unique_id_fn=lambda hub, obj_id: f"ac_power_conumption-{obj_id}", value_fn=lambda hub, device: device.outlet_ac_power_consumption, @@ -353,11 +325,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda device: "Uptime", object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_uptime-{obj_id}", value_fn=async_device_uptime_value_fn, @@ -373,11 +342,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda device: "Temperature", object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].has_temperature, unique_id_fn=lambda hub, obj_id: f"device_temperature-{obj_id}", value_fn=lambda ctrlr, device: device.general_temperature, @@ -391,11 +357,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda device: "State", object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_state-{obj_id}", value_fn=async_device_state_value_fn, diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 4a2785f0c17..6b2ce3c8b34 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -10,7 +10,7 @@ from __future__ import annotations import asyncio from collections.abc import Callable, Coroutine from dataclasses import dataclass -from typing import Any, Generic +from typing import Any import aiounifi from aiounifi.interfaces.api_handlers import ItemEvent @@ -162,24 +162,20 @@ async def async_wlan_control_fn(hub: UnifiHub, obj_id: str, target: bool) -> Non await hub.api.request(WlanEnableRequest.create(obj_id, target)) -@dataclass(frozen=True) -class UnifiSwitchEntityDescriptionMixin(Generic[HandlerT, ApiItemT]): - """Validate and load entities from different UniFi handlers.""" +@dataclass(frozen=True, kw_only=True) +class UnifiSwitchEntityDescription( + SwitchEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT] +): + """Class describing UniFi switch entity.""" control_fn: Callable[[UnifiHub, str, bool], Coroutine[Any, Any, None]] is_on_fn: Callable[[UnifiHub, ApiItemT], bool] - -@dataclass(frozen=True) -class UnifiSwitchEntityDescription( - SwitchEntityDescription, - UnifiEntityDescription[HandlerT, ApiItemT], - UnifiSwitchEntityDescriptionMixin[HandlerT, ApiItemT], -): - """Class describing UniFi switch entity.""" - + # Optional custom_subscribe: Callable[[aiounifi.Controller], SubscriptionT] | None = None + """Callback for additional subscriptions to any UniFi handler.""" only_event_for_state_change: bool = False + """Use only UniFi events to trigger state changes.""" ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( @@ -200,7 +196,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( name_fn=lambda client: None, object_fn=lambda api, obj_id: api.clients[obj_id], only_event_for_state_change=True, - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"block-{obj_id}", ), @@ -214,12 +209,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( control_fn=async_dpi_group_control_fn, custom_subscribe=lambda api: api.dpi_apps.subscribe, device_info_fn=async_dpi_group_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_on_fn=async_dpi_group_is_on_fn, name_fn=lambda group: group.name, object_fn=lambda api, obj_id: api.dpi_groups[obj_id], - should_poll=False, supported_fn=lambda c, obj_id: bool(c.api.dpi_groups[obj_id].dpiapp_ids), unique_id_fn=lambda hub, obj_id: obj_id, ), @@ -232,12 +224,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( available_fn=async_device_available_fn, control_fn=async_outlet_control_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_on_fn=lambda hub, outlet: outlet.relay_state, name_fn=lambda outlet: outlet.name, object_fn=lambda api, obj_id: api.outlets[obj_id], - should_poll=False, supported_fn=async_outlet_supports_switching_fn, unique_id_fn=lambda hub, obj_id: f"outlet-{obj_id}", ), @@ -252,12 +241,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( available_fn=lambda hub, obj_id: hub.available, control_fn=async_port_forward_control_fn, device_info_fn=async_port_forward_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_on_fn=lambda hub, port_forward: port_forward.enabled, name_fn=lambda port_forward: f"{port_forward.name}", object_fn=lambda api, obj_id: api.port_forwarding[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"port_forward-{obj_id}", ), @@ -273,12 +259,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( available_fn=async_device_available_fn, control_fn=async_poe_port_control_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_on_fn=lambda hub, port: port.poe_mode != "off", name_fn=lambda port: f"{port.name} PoE", object_fn=lambda api, obj_id: api.ports[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe, unique_id_fn=lambda hub, obj_id: f"poe-{obj_id}", ), @@ -293,12 +276,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( available_fn=lambda hub, _: hub.available, control_fn=async_wlan_control_fn, device_info_fn=async_wlan_device_info_fn, - event_is_on=None, - event_to_subscribe=None, is_on_fn=lambda hub, wlan: wlan.enabled, name_fn=lambda wlan: None, object_fn=lambda api, obj_id: api.wlans[obj_id], - should_poll=False, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"wlan-{obj_id}", ), @@ -354,15 +334,11 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity): """Base representation of a UniFi switch.""" entity_description: UnifiSwitchEntityDescription[HandlerT, ApiItemT] - only_event_for_state_change = False @callback def async_initiate_state(self) -> None: """Initiate entity state.""" - self.async_update_state(ItemEvent.ADDED, self._obj_id) - self.only_event_for_state_change = ( - self.entity_description.only_event_for_state_change - ) + self.async_update_state(ItemEvent.ADDED, self._obj_id, first_update=True) async def async_turn_on(self, **kwargs: Any) -> None: """Turn on switch.""" @@ -373,12 +349,14 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity): await self.entity_description.control_fn(self.hub, self._obj_id, False) @callback - def async_update_state(self, event: ItemEvent, obj_id: str) -> None: + def async_update_state( + self, event: ItemEvent, obj_id: str, first_update: bool = False + ) -> None: """Update entity state. Update attr_is_on. """ - if self.only_event_for_state_change: + if not first_update and self.entity_description.only_event_for_state_change: return description = self.entity_description diff --git a/homeassistant/components/unifi/update.py b/homeassistant/components/unifi/update.py index b7f33b632b3..bb9771c170a 100644 --- a/homeassistant/components/unifi/update.py +++ b/homeassistant/components/unifi/update.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass import logging -from typing import Any, Generic, TypeVar +from typing import Any, TypeVar import aiounifi from aiounifi.interfaces.api_handlers import ItemEvent @@ -40,23 +40,16 @@ async def async_device_control_fn(api: aiounifi.Controller, obj_id: str) -> None await api.request(DeviceUpgradeRequest.create(obj_id)) -@dataclass(frozen=True) -class UnifiUpdateEntityDescriptionMixin(Generic[_HandlerT, _DataT]): - """Validate and load entities from different UniFi handlers.""" +@dataclass(frozen=True, kw_only=True) +class UnifiUpdateEntityDescription( + UpdateEntityDescription, UnifiEntityDescription[_HandlerT, _DataT] +): + """Class describing UniFi update entity.""" control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]] state_fn: Callable[[aiounifi.Controller, _DataT], bool] -@dataclass(frozen=True) -class UnifiUpdateEntityDescription( - UpdateEntityDescription, - UnifiEntityDescription[_HandlerT, _DataT], - UnifiUpdateEntityDescriptionMixin[_HandlerT, _DataT], -): - """Class describing UniFi update entity.""" - - ENTITY_DESCRIPTIONS: tuple[UnifiUpdateEntityDescription, ...] = ( UnifiUpdateEntityDescription[Devices, Device]( key="Upgrade device", @@ -67,11 +60,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiUpdateEntityDescription, ...] = ( available_fn=async_device_available_fn, control_fn=async_device_control_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda device: None, object_fn=lambda api, obj_id: api.devices[obj_id], - should_poll=False, state_fn=lambda api, device: device.state == 4, supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_update-{obj_id}", From 943996b60b6dd992bcb36f2b9aeca566ed9bd0d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 20:22:31 -1000 Subject: [PATCH 0214/1691] Avoid multiple executor jobs with concurrent calls to async_get_component (#112155) --- homeassistant/loader.py | 39 +++++++++++++++++++++---- tests/test_loader.py | 63 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 0d03c4f81eb..33ccd5615a8 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -661,6 +661,7 @@ class Integration: self._all_dependencies_resolved = True self._all_dependencies = set() + self._component_future: asyncio.Future[ComponentProtocol] | None = None self._import_futures: dict[str, asyncio.Future[ModuleType]] = {} _LOGGER.info("Loaded %s from %s", self.domain, pkg_path) @@ -842,34 +843,60 @@ class Integration: and will check if import_executor is set and load it in the executor, otherwise it will load it in the event loop. """ + if self._component_future: + return await self._component_future + if debug := _LOGGER.isEnabledFor(logging.DEBUG): start = time.perf_counter() - domain = self.domain + # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. load_executor = self.import_executor and ( self.pkg_path not in sys.modules or (self.config_flow and f"{self.pkg_path}.config_flow" not in sys.modules) ) - if load_executor: + if not load_executor: + comp = self.get_component() + if debug: + _LOGGER.debug( + "Component %s import took %.3f seconds (loaded_executor=False)", + self.domain, + time.perf_counter() - start, + ) + return comp + + self._component_future = self.hass.loop.create_future() + try: try: comp = await self.hass.async_add_import_executor_job(self.get_component) except ImportError as ex: load_executor = False - _LOGGER.debug("Failed to import %s in executor", domain, exc_info=ex) + _LOGGER.debug( + "Failed to import %s in executor", self.domain, exc_info=ex + ) # If importing in the executor deadlocks because there is a circular # dependency, we fall back to the event loop. comp = self.get_component() - else: - comp = self.get_component() + self._component_future.set_result(comp) + except BaseException as ex: + self._component_future.set_exception(ex) + with suppress(BaseException): + # Set the exception retrieved flag on the future since + # it will never be retrieved unless there + # are concurrent calls to async_get_component + self._component_future.result() + raise + finally: + self._component_future = None if debug: _LOGGER.debug( "Component %s import took %.3f seconds (loaded_executor=%s)", - domain, + self.domain, time.perf_counter() - start, load_executor, ) + return comp def get_component(self) -> ComponentProtocol: diff --git a/tests/test_loader.py b/tests/test_loader.py index a70bf7d4e3f..8400adca5c4 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -2,6 +2,7 @@ import asyncio import os import sys +import threading from typing import Any from unittest.mock import MagicMock, Mock, patch @@ -1086,8 +1087,8 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert integration.import_executor is True assert integration.config_flow is True - assert "executor_import" not in hass.config.components - assert "executor_import.config_flow" not in hass.config.components + assert "test_package_loaded_executor" not in hass.config.components + assert "test_package_loaded_executor.config_flow" not in hass.config.components config_flow_module_name = f"{integration.pkg_path}.config_flow" module_mock = MagicMock() @@ -1133,6 +1134,64 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert module is module_mock +async def test_async_get_component_concurrent_loads( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Verify async_get_component waits if the first load if called again when still in progress.""" + integration = await loader.async_get_integration( + hass, "test_package_loaded_executor" + ) + assert integration.pkg_path == "custom_components.test_package_loaded_executor" + assert integration.import_executor is True + assert integration.config_flow is True + + assert "test_package_loaded_executor" not in hass.config.components + assert "test_package_loaded_executor.config_flow" not in hass.config.components + + config_flow_module_name = f"{integration.pkg_path}.config_flow" + module_mock = MagicMock() + config_flow_module_mock = MagicMock() + imports = [] + start_event = threading.Event() + import_event = asyncio.Event() + + def import_module(name: str) -> Any: + hass.loop.call_soon_threadsafe(import_event.set) + imports.append(name) + start_event.wait() + if name == integration.pkg_path: + return module_mock + if name == config_flow_module_name: + return config_flow_module_mock + raise ImportError + + modules_without_integration = { + k: v + for k, v in sys.modules.items() + if k != config_flow_module_name and k != integration.pkg_path + } + with patch.dict( + "sys.modules", + {**modules_without_integration}, + clear=True, + ), patch("homeassistant.loader.importlib.import_module", import_module): + load_task1 = asyncio.create_task(integration.async_get_component()) + load_task2 = asyncio.create_task(integration.async_get_component()) + await import_event.wait() # make sure the import is started + assert not integration._component_future.done() + start_event.set() + comp1 = await load_task1 + comp2 = await load_task2 + assert integration._component_future is None + + assert comp1 is module_mock + assert comp2 is module_mock + + assert imports == [integration.pkg_path, config_flow_module_name] + + async def test_async_get_component_deadlock_fallback( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: From e0a8a9d551a3a8e0f728ecfe93e51cf4d685b3e4 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <9592452+CFenner@users.noreply.github.com> Date: Mon, 4 Mar 2024 07:56:45 +0100 Subject: [PATCH 0215/1691] Ignore unsupported devices (room sensors, floor heating) in ViCare integration (#112106) * ignore unsupported devices * Update __init__.py * move unsupported device to const * fix ruff --- homeassistant/components/vicare/__init__.py | 10 ++++++++-- homeassistant/components/vicare/const.py | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index eec5f097535..74ebffa53cd 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -20,7 +20,13 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.storage import STORAGE_DIR -from .const import DEFAULT_CACHE_DURATION, DEVICE_LIST, DOMAIN, PLATFORMS +from .const import ( + DEFAULT_CACHE_DURATION, + DEVICE_LIST, + DOMAIN, + PLATFORMS, + UNSUPPORTED_DEVICES, +) from .types import ViCareDevice from .utils import get_device @@ -109,5 +115,5 @@ def get_supported_devices( return [ device_config for device_config in devices - if device_config.getModel() not in ["Heatbox1", "Heatbox2_SRC"] + if device_config.getModel() not in UNSUPPORTED_DEVICES ] diff --git a/homeassistant/components/vicare/const.py b/homeassistant/components/vicare/const.py index 8b76344843a..9f57bb5e5e8 100644 --- a/homeassistant/components/vicare/const.py +++ b/homeassistant/components/vicare/const.py @@ -14,6 +14,15 @@ PLATFORMS = [ Platform.WATER_HEATER, ] +UNSUPPORTED_DEVICES = [ + "Heatbox1", + "Heatbox2_SRC", + "E3_FloorHeatingCircuitChannel", + "E3_FloorHeatingCircuitDistributorBox", + "E3_RoomControl_One_522", + "E3_RoomSensor", +] + DEVICE_LIST = "device_list" VICARE_NAME = "ViCare" From 5227976aa24bfb8dbe024f073e0356aef6a475c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 21:32:19 -1000 Subject: [PATCH 0216/1691] Group loading of platforms in the import executor (#112141) Co-authored-by: Paulus Schoutsen --- homeassistant/config_entries.py | 6 +- homeassistant/loader.py | 196 +++++++++++------- tests/test_loader.py | 191 ++++++++++++++++- .../test_package_loaded_executor/button.py | 1 + .../test_package_loaded_executor/light.py | 1 + .../test_package_loaded_executor/switch.py | 1 + 6 files changed, 320 insertions(+), 76 deletions(-) create mode 100644 tests/testing_config/custom_components/test_package_loaded_executor/button.py create mode 100644 tests/testing_config/custom_components/test_package_loaded_executor/light.py create mode 100644 tests/testing_config/custom_components/test_package_loaded_executor/switch.py diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b1a4a8ce6cc..219f4ff1709 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -506,7 +506,7 @@ class ConfigEntry: if domain_is_integration: try: - await integration.async_get_platform("config_flow") + await integration.async_get_platforms(("config_flow",)) except ImportError as err: _LOGGER.error( ( @@ -1814,6 +1814,8 @@ class ConfigEntries: self, entry: ConfigEntry, platforms: Iterable[Platform | str] ) -> None: """Forward the setup of an entry to platforms.""" + integration = await loader.async_get_integration(self.hass, entry.domain) + await integration.async_get_platforms(platforms) await asyncio.gather( *( create_eager_task( @@ -2519,7 +2521,7 @@ async def _load_integration( # Make sure requirements and dependencies of component are resolved await async_process_deps_reqs(hass, hass_config, integration) try: - await integration.async_get_platform("config_flow") + await integration.async_get_platforms(("config_flow",)) except ImportError as err: _LOGGER.error( "Error occurred loading flow for integration %s: %s", diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 33ccd5615a8..f19577ac10a 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -27,6 +27,7 @@ from awesomeversion import ( import voluptuous as vol from . import generated +from .const import Platform from .core import HomeAssistant, callback from .generated.application_credentials import APPLICATION_CREDENTIALS from .generated.bluetooth import BLUETOOTH @@ -663,6 +664,12 @@ class Integration: self._component_future: asyncio.Future[ComponentProtocol] | None = None self._import_futures: dict[str, asyncio.Future[ModuleType]] = {} + cache: dict[str, ModuleType | ComponentProtocol] = hass.data[DATA_COMPONENTS] + self._cache = cache + missing_platforms_cache: dict[str, ImportError] = hass.data[ + DATA_MISSING_PLATFORMS + ] + self._missing_platforms_cache = missing_platforms_cache _LOGGER.info("Loaded %s from %s", self.domain, pkg_path) @cached_property @@ -909,12 +916,14 @@ class Integration: with a dict cache which is thread-safe since importlib has appropriate locks. """ - cache: dict[str, ComponentProtocol] = self.hass.data[DATA_COMPONENTS] - if self.domain in cache: - return cache[self.domain] + cache = self._cache + domain = self.domain + + if domain in cache: + return cache[domain] try: - cache[self.domain] = cast( + cache[domain] = cast( ComponentProtocol, importlib.import_module(self.pkg_path) ) except ImportError: @@ -945,75 +954,122 @@ class Integration: with suppress(ImportError): self.get_platform("config_flow") - return cache[self.domain] + return cache[domain] + + def _load_platforms(self, platform_names: Iterable[str]) -> dict[str, ModuleType]: + """Load platforms for an integration.""" + return { + platform_name: self._load_platform(platform_name) + for platform_name in platform_names + } async def async_get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" + platforms = await self.async_get_platforms([platform_name]) + return platforms[platform_name] + + async def async_get_platforms( + self, platform_names: Iterable[Platform | str] + ) -> dict[str, ModuleType]: + """Return a platforms for an integration.""" domain = self.domain - full_name = f"{self.domain}.{platform_name}" - if platform := self._get_platform_cached(full_name): - return platform - if future := self._import_futures.get(full_name): - return await future - if debug := _LOGGER.isEnabledFor(logging.DEBUG): - start = time.perf_counter() - import_future = self.hass.loop.create_future() - self._import_futures[full_name] = import_future - load_executor = ( - self.import_executor - and domain not in self.hass.config.components - and f"{self.pkg_path}.{domain}" not in sys.modules - ) - try: - if load_executor: - try: - platform = await self.hass.async_add_import_executor_job( - self._load_platform, platform_name - ) - except ImportError as ex: - _LOGGER.debug( - "Failed to import %s in executor", domain, exc_info=ex - ) - load_executor = False - # If importing in the executor deadlocks because there is a circular - # dependency, we fall back to the event loop. - platform = self._load_platform(platform_name) + platforms: dict[str, ModuleType] = {} + + load_executor_platforms: list[str] = [] + load_event_loop_platforms: list[str] = [] + in_progress_imports: dict[str, asyncio.Future[ModuleType]] = {} + import_futures: list[tuple[str, asyncio.Future[ModuleType]]] = [] + + for platform_name in platform_names: + full_name = f"{domain}.{platform_name}" + if platform := self._get_platform_cached(full_name): + platforms[platform_name] = platform + continue + + # Another call to async_get_platforms is already importing this platform + if future := self._import_futures.get(platform_name): + in_progress_imports[platform_name] = future + continue + + if ( + self.import_executor + and full_name not in self.hass.config.components + and f"{self.pkg_path}.{platform_name}" not in sys.modules + ): + load_executor_platforms.append(platform_name) else: - platform = self._load_platform(platform_name) - import_future.set_result(platform) - except BaseException as ex: - import_future.set_exception(ex) - with suppress(BaseException): - # Clear the exception retrieved flag on the future since - # it will never be retrieved unless there - # are concurrent calls to async_get_platform - import_future.result() - raise - finally: - self._import_futures.pop(full_name) + load_event_loop_platforms.append(platform_name) - if debug: - _LOGGER.debug( - "Importing platform %s took %.2fs (loaded_executor=%s)", - full_name, - time.perf_counter() - start, - load_executor, - ) + import_future = self.hass.loop.create_future() + self._import_futures[platform_name] = import_future + import_futures.append((platform_name, import_future)) - return platform + if load_executor_platforms or load_event_loop_platforms: + if debug := _LOGGER.isEnabledFor(logging.DEBUG): + start = time.perf_counter() + + try: + if load_executor_platforms: + try: + platforms.update( + await self.hass.async_add_import_executor_job( + self._load_platforms, platform_names + ) + ) + except ImportError as ex: + _LOGGER.debug( + "Failed to import %s platforms %s in executor", + domain, + load_executor_platforms, + exc_info=ex, + ) + # If importing in the executor deadlocks because there is a circular + # dependency, we fall back to the event loop. + load_event_loop_platforms.extend(load_executor_platforms) + + if load_event_loop_platforms: + platforms.update(self._load_platforms(platform_names)) + + for platform_name, import_future in import_futures: + import_future.set_result(platforms[platform_name]) + + except BaseException as ex: + for _, import_future in import_futures: + import_future.set_exception(ex) + with suppress(BaseException): + # Set the exception retrieved flag on the future since + # it will never be retrieved unless there + # are concurrent calls to async_get_platforms + import_future.result() + raise + + finally: + for platform_name, _ in import_futures: + self._import_futures.pop(platform_name) + + if debug: + _LOGGER.debug( + "Importing platforms for %s executor=%s loop=%s took %.2fs", + domain, + load_executor_platforms, + load_event_loop_platforms, + time.perf_counter() - start, + ) + + if in_progress_imports: + for platform_name, future in in_progress_imports.items(): + platforms[platform_name] = await future + + return platforms def _get_platform_cached(self, full_name: str) -> ModuleType | None: """Return a platform for an integration from cache.""" - cache: dict[str, ModuleType] = self.hass.data[DATA_COMPONENTS] - if full_name in cache: - return cache[full_name] - - missing_platforms_cache: dict[str, ImportError] = self.hass.data[ - DATA_MISSING_PLATFORMS - ] - if full_name in missing_platforms_cache: - raise missing_platforms_cache[full_name] - + if full_name in self._cache: + # the cache is either a ModuleType or a ComponentProtocol + # but we only care about the ModuleType here + return self._cache[full_name] # type: ignore[return-value] + if full_name in self._missing_platforms_cache: + raise self._missing_platforms_cache[full_name] return None def get_platform(self, platform_name: str) -> ModuleType: @@ -1033,14 +1089,11 @@ class Integration: has been called for the integration or the integration failed to load. """ full_name = f"{self.domain}.{platform_name}" - - cache: dict[str, ModuleType] = self.hass.data[DATA_COMPONENTS] + cache = self._cache if full_name in cache: return True - missing_platforms_cache: dict[str, ImportError] - missing_platforms_cache = self.hass.data[DATA_MISSING_PLATFORMS] - if full_name in missing_platforms_cache: + if full_name in self._missing_platforms_cache: return False if not (component := cache.get(self.domain)) or not ( @@ -1056,7 +1109,7 @@ class Integration: f"Platform {full_name} not found", name=f"{self.pkg_path}.{platform_name}", ) - missing_platforms_cache[full_name] = exc + self._missing_platforms_cache[full_name] = exc return False def _load_platform(self, platform_name: str) -> ModuleType: @@ -1077,10 +1130,7 @@ class Integration: if self.domain in cache: # If the domain is loaded, cache that the platform # does not exist so we do not try to load it again - missing_platforms_cache: dict[str, ImportError] = self.hass.data[ - DATA_MISSING_PLATFORMS - ] - missing_platforms_cache[full_name] = ex + self._missing_platforms_cache[full_name] = ex raise except RuntimeError as err: # _DeadlockError inherits from RuntimeError diff --git a/tests/test_loader.py b/tests/test_loader.py index 8400adca5c4..fdbc457dfe0 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -278,6 +278,41 @@ async def test_async_get_platform_caches_failures_when_component_loaded( assert await integration.async_get_platform("light") == hue_light +async def test_async_get_platforms_caches_failures_when_component_loaded( + hass: HomeAssistant, +) -> None: + """Test async_get_platforms cache failures only when the component is loaded.""" + integration = await loader.async_get_integration(hass, "hue") + + with pytest.raises(ImportError), patch( + "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + ): + assert integration.get_component() == hue + + with pytest.raises(ImportError), patch( + "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + ): + assert await integration.async_get_platforms(["light"]) == {"light": hue_light} + + # Hue is not loaded so we should still hit the import_module path + with pytest.raises(ImportError), patch( + "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + ): + assert await integration.async_get_platforms(["light"]) == {"light": hue_light} + + assert integration.get_component() == hue + + # Hue is loaded so we should cache the import_module failure now + with pytest.raises(ImportError), patch( + "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + ): + assert await integration.async_get_platforms(["light"]) == {"light": hue_light} + + # Hue is loaded and the last call should have cached the import_module failure + with pytest.raises(ImportError): + assert await integration.async_get_platforms(["light"]) == {"light": hue_light} + + async def test_get_integration_legacy( hass: HomeAssistant, enable_custom_integrations: None ) -> None: @@ -1302,7 +1337,9 @@ async def test_async_get_platform_deadlock_fallback( "Detected deadlock trying to import homeassistant.components.executor_import" in caplog.text ) - assert "loaded_executor=False" in caplog.text + # We should have tried both the executor and loop + assert "executor=['config_flow']" in caplog.text + assert "loop=['config_flow']" in caplog.text assert module is module_mock @@ -1390,3 +1427,155 @@ async def test_platform_exists( assert platform.MAGIC == 1 assert integration.platform_exists("group") is True + + +async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Verify async_get_platforms does not create an executor job. + + Case is for when the module is already in sys.modules. + """ + integration = await loader.async_get_integration( + hass, "test_package_loaded_executor" + ) + assert integration.pkg_path == "custom_components.test_package_loaded_executor" + assert integration.import_executor is True + assert integration.config_flow is True + + assert "test_package_loaded_executor" not in hass.config.components + assert "test_package_loaded_executor.config_flow" not in hass.config.components + await integration.async_get_component() + + button_module_name = f"{integration.pkg_path}.button" + switch_module_name = f"{integration.pkg_path}.switch" + light_module_name = f"{integration.pkg_path}.light" + button_module_mock = MagicMock() + switch_module_mock = MagicMock() + light_module_mock = MagicMock() + + def import_module(name: str) -> Any: + if name == button_module_name: + return button_module_mock + if name == switch_module_name: + return switch_module_mock + if name == light_module_name: + return light_module_mock + raise ImportError + + modules_without_button = { + k: v for k, v in sys.modules.items() if k != button_module_name + } + with patch.dict( + "sys.modules", + modules_without_button, + clear=True, + ), patch("homeassistant.loader.importlib.import_module", import_module): + module = (await integration.async_get_platforms(["button"]))["button"] + + # The button module is missing so we should load + # in the executor + assert "executor=['button']" in caplog.text + assert "loop=[]" in caplog.text + assert module is button_module_mock + caplog.clear() + + with patch.dict( + "sys.modules", + { + **modules_without_button, + button_module_name: button_module_mock, + }, + ), patch("homeassistant.loader.importlib.import_module", import_module): + module = (await integration.async_get_platforms(["button"]))["button"] + + # Everything is cached so there should be no logging + assert "loop=" not in caplog.text + assert "executor=" not in caplog.text + assert module is button_module_mock + caplog.clear() + + modules_without_switch = { + k: v for k, v in sys.modules.items() if k not in switch_module_name + } + with patch.dict( + "sys.modules", + {**modules_without_switch, light_module_name: light_module_mock}, + clear=True, + ), patch("homeassistant.loader.importlib.import_module", import_module): + modules = await integration.async_get_platforms(["button", "switch", "light"]) + + # The button module is already in the cache so nothing happens + # The switch module is loaded in the executor since its not in the cache + # The light module is in memory but not in the cache so its loaded in the loop + assert "['button']" not in caplog.text + assert "executor=['switch']" in caplog.text + assert "loop=['light']" in caplog.text + assert modules == { + "button": button_module_mock, + "switch": switch_module_mock, + "light": light_module_mock, + } + + +async def test_async_get_platforms_concurrent_loads( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Verify async_get_platforms waits if the first load if called again. + + Case is for when when a second load is called + and the first is still in progress. + """ + integration = await loader.async_get_integration( + hass, "test_package_loaded_executor" + ) + assert integration.pkg_path == "custom_components.test_package_loaded_executor" + assert integration.import_executor is True + assert integration.config_flow is True + + assert "test_package_loaded_executor" not in hass.config.components + assert "test_package_loaded_executor.config_flow" not in hass.config.components + await integration.async_get_component() + + button_module_name = f"{integration.pkg_path}.button" + button_module_mock = MagicMock() + + imports = [] + start_event = threading.Event() + import_event = asyncio.Event() + + def import_module(name: str) -> Any: + hass.loop.call_soon_threadsafe(import_event.set) + imports.append(name) + start_event.wait() + if name == button_module_name: + return button_module_mock + raise ImportError + + modules_without_button = { + k: v + for k, v in sys.modules.items() + if k != button_module_name and k != integration.pkg_path + } + with patch.dict( + "sys.modules", + modules_without_button, + clear=True, + ), patch("homeassistant.loader.importlib.import_module", import_module): + load_task1 = asyncio.create_task(integration.async_get_platforms(["button"])) + load_task2 = asyncio.create_task(integration.async_get_platforms(["button"])) + await import_event.wait() # make sure the import is started + assert not integration._import_futures["button"].done() + start_event.set() + load_result1 = await load_task1 + load_result2 = await load_task2 + assert integration._import_futures is not None + + assert load_result1 == {"button": button_module_mock} + assert load_result2 == {"button": button_module_mock} + + assert imports == [button_module_name] diff --git a/tests/testing_config/custom_components/test_package_loaded_executor/button.py b/tests/testing_config/custom_components/test_package_loaded_executor/button.py new file mode 100644 index 00000000000..0157551af84 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_executor/button.py @@ -0,0 +1 @@ +"""Provide a mock button platform.""" diff --git a/tests/testing_config/custom_components/test_package_loaded_executor/light.py b/tests/testing_config/custom_components/test_package_loaded_executor/light.py new file mode 100644 index 00000000000..0f1e5f1a631 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_executor/light.py @@ -0,0 +1 @@ +"""Provide a mock light platform.""" diff --git a/tests/testing_config/custom_components/test_package_loaded_executor/switch.py b/tests/testing_config/custom_components/test_package_loaded_executor/switch.py new file mode 100644 index 00000000000..134235622f3 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_executor/switch.py @@ -0,0 +1 @@ +"""Provide a mock switch platform.""" From c13231fc00e93b9de4d6cf16cdff0e497ba92bc8 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 4 Mar 2024 08:49:12 +0100 Subject: [PATCH 0217/1691] Improve mqtt value template error logging (#110492) * Refactor mqtt value template error logging * Remove import --- homeassistant/components/mqtt/event.py | 5 +- homeassistant/components/mqtt/image.py | 12 +++-- homeassistant/components/mqtt/mixins.py | 5 +- homeassistant/components/mqtt/models.py | 63 +++++++++++++++++-------- homeassistant/components/mqtt/tag.py | 15 ++++-- tests/components/mqtt/test_init.py | 26 ++++------ 6 files changed, 81 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/mqtt/event.py b/homeassistant/components/mqtt/event.py index c245b66fdb1..165d273f46e 100644 --- a/homeassistant/components/mqtt/event.py +++ b/homeassistant/components/mqtt/event.py @@ -29,7 +29,6 @@ from .const import ( CONF_STATE_TOPIC, PAYLOAD_EMPTY_JSON, PAYLOAD_NONE, - TEMPLATE_ERRORS, ) from .debug_info import log_messages from .mixins import ( @@ -39,6 +38,7 @@ from .mixins import ( ) from .models import ( MqttValueTemplate, + MqttValueTemplateException, PayloadSentinel, ReceiveMessage, ReceivePayloadType, @@ -134,7 +134,8 @@ class MqttEvent(MqttEntity, EventEntity): event_type: str try: payload = self._template(msg.payload, PayloadSentinel.DEFAULT) - except TEMPLATE_ERRORS: + except MqttValueTemplateException as exc: + _LOGGER.warning(exc) return if ( not payload diff --git a/homeassistant/components/mqtt/image.py b/homeassistant/components/mqtt/image.py index e91a8c5c259..91e11d06371 100644 --- a/homeassistant/components/mqtt/image.py +++ b/homeassistant/components/mqtt/image.py @@ -24,14 +24,19 @@ from homeassistant.util import dt as dt_util from . import subscription from .config import MQTT_BASE_SCHEMA -from .const import CONF_ENCODING, CONF_QOS, TEMPLATE_ERRORS +from .const import CONF_ENCODING, CONF_QOS from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entity_entry_helper, ) -from .models import MessageCallbackType, MqttValueTemplate, ReceiveMessage +from .models import ( + MessageCallbackType, + MqttValueTemplate, + MqttValueTemplateException, + ReceiveMessage, +) from .util import get_mqtt_data, valid_subscribe_topic _LOGGER = logging.getLogger(__name__) @@ -191,7 +196,8 @@ class MqttImage(MqttEntity, ImageEntity): try: url = cv.url(self._url_template(msg.payload)) self._attr_image_url = url - except TEMPLATE_ERRORS: + except MqttValueTemplateException as exc: + _LOGGER.warning(exc) return except vol.Invalid: _LOGGER.error( diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 5736f821f69..554e83204dd 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -94,7 +94,6 @@ from .const import ( DOMAIN, MQTT_CONNECTED, MQTT_DISCONNECTED, - TEMPLATE_ERRORS, ) from .debug_info import log_message, log_messages from .discovery import ( @@ -109,6 +108,7 @@ from .discovery import ( from .models import ( MessageCallbackType, MqttValueTemplate, + MqttValueTemplateException, PublishPayloadType, ReceiveMessage, ) @@ -482,7 +482,8 @@ def write_state_on_attr_change( } try: msg_callback(msg) - except TEMPLATE_ERRORS: + except MqttValueTemplateException as exc: + _LOGGER.warning(exc) return if not _attrs_have_changed(tracked_attrs): return diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 1295bfb8ff3..9fcea353299 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -223,6 +223,36 @@ class MqttCommandTemplate: ) from exc +class MqttValueTemplateException(TemplateError): + """Handle MqttValueTemplate exceptions.""" + + def __init__( + self, + *args: object, + base_exception: Exception, + value_template: str, + default: ReceivePayloadType | PayloadSentinel, + payload: ReceivePayloadType, + entity_id: str | None = None, + ) -> None: + """Initialize exception.""" + super().__init__(base_exception, *args) + entity_id_log = "" if entity_id is None else f" for entity '{entity_id}'" + default_log = str(default) + default_payload_log = ( + "" if default is PayloadSentinel.NONE else f", default value: {default_log}" + ) + payload_log = str(payload) + self._message = ( + f"{type(base_exception).__name__}: {base_exception} rendering template{entity_id_log}" + f", template: '{value_template}'{default_payload_log} and payload: {payload_log}" + ) + + def __str__(self) -> str: + """Return exception message string.""" + return self._message + + class MqttValueTemplate: """Class for rendering MQTT value template with possible json values.""" @@ -291,14 +321,13 @@ class MqttValueTemplate: ) ) except TEMPLATE_ERRORS as exc: - _LOGGER.error( - "%s: %s rendering template for entity '%s', template: '%s'", - type(exc).__name__, - exc, - self._entity.entity_id if self._entity else "n/a", - self._value_template.template, - ) - raise + raise MqttValueTemplateException( + base_exception=exc, + value_template=self._value_template.template, + default=default, + payload=payload, + entity_id=self._entity.entity_id if self._entity else None, + ) from exc return rendered_payload _LOGGER.debug( @@ -318,17 +347,13 @@ class MqttValueTemplate: ) ) except TEMPLATE_ERRORS as exc: - _LOGGER.error( - "%s: %s rendering template for entity '%s', template: " - "'%s', default value: %s and payload: %s", - type(exc).__name__, - exc, - self._entity.entity_id if self._entity else "n/a", - self._value_template.template, - default, - payload, - ) - raise + raise MqttValueTemplateException( + base_exception=exc, + value_template=self._value_template.template, + default=default, + payload=payload, + entity_id=self._entity.entity_id if self._entity else None, + ) from exc return rendered_payload diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index 0eda584e95a..42c8760c302 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable import functools +import logging import voluptuous as vol @@ -15,7 +16,7 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import subscription from .config import MQTT_BASE_SCHEMA -from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC, TEMPLATE_ERRORS +from .const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_TOPIC from .discovery import MQTTDiscoveryPayload from .mixins import ( MQTT_ENTITY_DEVICE_INFO_SCHEMA, @@ -25,10 +26,17 @@ from .mixins import ( send_discovery_done, update_device, ) -from .models import MqttValueTemplate, ReceiveMessage, ReceivePayloadType +from .models import ( + MqttValueTemplate, + MqttValueTemplateException, + ReceiveMessage, + ReceivePayloadType, +) from .subscription import EntitySubscription from .util import get_mqtt_data, valid_subscribe_topic +_LOGGER = logging.getLogger(__name__) + LOG_NAME = "Tag" TAG = "tag" @@ -138,7 +146,8 @@ class MQTTTagScanner(MqttDiscoveryDeviceUpdate): async def tag_scanned(msg: ReceiveMessage) -> None: try: tag_id = str(self._value_template(msg.payload, "")).strip() - except TEMPLATE_ERRORS: + except MqttValueTemplateException as exc: + _LOGGER.warning(exc) return if not tag_id: # No output from template, ignore return diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 9fe394bd797..16fa06ccd27 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -19,6 +19,7 @@ from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA from homeassistant.components.mqtt.models import ( MessageCallbackType, MqttCommandTemplateException, + MqttValueTemplateException, ReceiveMessage, ) from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState @@ -433,37 +434,30 @@ async def test_value_template_value(hass: HomeAssistant) -> None: assert template_state_calls.call_count == 1 -async def test_value_template_fails( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture -) -> None: +async def test_value_template_fails(hass: HomeAssistant) -> None: """Test the rendering of MQTT value template fails.""" - - # test rendering a value fails entity = MockEntity(entity_id="sensor.test") entity.hass = hass tpl = template.Template("{{ value_json.some_var * 2 }}") val_tpl = mqtt.MqttValueTemplate(tpl, hass=hass, entity=entity) - with pytest.raises(TypeError) as exc: + with pytest.raises(MqttValueTemplateException) as exc: val_tpl.async_render_with_possible_json_value('{"some_var": null }') - assert str(exc.value) == "unsupported operand type(s) for *: 'NoneType' and 'int'" - assert ( + assert str(exc.value) == ( "TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' " "rendering template for entity 'sensor.test', " - "template: '{{ value_json.some_var * 2 }}'" - ) in caplog.text - caplog.clear() - with pytest.raises(TypeError) as exc: + "template: '{{ value_json.some_var * 2 }}' " + 'and payload: {"some_var": null }' + ) + with pytest.raises(MqttValueTemplateException) as exc: val_tpl.async_render_with_possible_json_value( '{"some_var": null }', default=100 ) - assert str(exc.value) == "unsupported operand type(s) for *: 'NoneType' and 'int'" - assert ( + assert str(exc.value) == ( "TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' " "rendering template for entity 'sensor.test', " "template: '{{ value_json.some_var * 2 }}', default value: 100 and payload: " '{"some_var": null }' - ) in caplog.text - await hass.async_block_till_done() + ) async def test_service_call_without_topic_does_not_publish( From 40c0b4caf0a356c06113362baac220b15c97b949 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 22:03:30 -1000 Subject: [PATCH 0218/1691] Import recorder and common recorder platforms before asyncio starts (#112131) --- homeassistant/bootstrap.py | 5 ++++- pyproject.toml | 3 +++ requirements.txt | 3 +++ tests/test_bootstrap.py | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index beb04b50021..a6aadcc67a6 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -34,11 +34,14 @@ from .components import ( device_automation as device_automation_pre_import, # noqa: F401 diagnostics as diagnostics_pre_import, # noqa: F401 file_upload as file_upload_pre_import, # noqa: F401 - http, + history as history_pre_import, # noqa: F401 + http, # not named pre_import since it has requirements lovelace as lovelace_pre_import, # noqa: F401 onboarding as onboarding_pre_import, # noqa: F401 + recorder as recorder_import, # noqa: F401 - not named pre_import since it has requirements repairs as repairs_pre_import, # noqa: F401 search as search_pre_import, # noqa: F401 + sensor as sensor_pre_import, # noqa: F401 system_log as system_log_pre_import, # noqa: F401 websocket_api as websocket_api_pre_import, # noqa: F401 ) diff --git a/pyproject.toml b/pyproject.toml index c8959fb97ff..c2825830481 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "bcrypt==4.1.2", "certifi>=2021.5.30", "ciso8601==2.3.1", + "fnv-hash-fast==0.5.0", # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all "httpx==0.27.0", @@ -50,9 +51,11 @@ dependencies = [ "orjson==3.9.15", "packaging>=23.1", "pip>=21.3.1", + "psutil-home-assistant==0.0.1", "python-slugify==8.0.4", "PyYAML==6.0.1", "requests==2.31.0", + "SQLAlchemy==2.0.27", "typing-extensions>=4.10.0,<5.0", "ulid-transform==0.9.0", # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 diff --git a/requirements.txt b/requirements.txt index 8ded95427c2..61c531e16c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ awesomeversion==24.2.0 bcrypt==4.1.2 certifi>=2021.5.30 ciso8601==2.3.1 +fnv-hash-fast==0.5.0 httpx==0.27.0 home-assistant-bluetooth==1.12.0 ifaddr==0.2.0 @@ -26,9 +27,11 @@ pyOpenSSL==24.0.0 orjson==3.9.15 packaging>=23.1 pip>=21.3.1 +psutil-home-assistant==0.0.1 python-slugify==8.0.4 PyYAML==6.0.1 requests==2.31.0 +SQLAlchemy==2.0.27 typing-extensions>=4.10.0,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 17eaa7aef66..f9c8647bd6e 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1026,8 +1026,8 @@ async def test_bootstrap_dependencies( ) -async def test_frontend_deps_pre_import_no_requirements(hass: HomeAssistant) -> None: - """Test frontend dependencies are pre-imported and do not have any requirements.""" +async def test_pre_import_no_requirements(hass: HomeAssistant) -> None: + """Test pre-imported and do not have any requirements.""" pre_imports = [ name.removesuffix("_pre_import") for name in dir(bootstrap) From 38f9285bd6f7d261bb52cba04dfb93813389cd52 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 Mar 2024 22:39:07 -1000 Subject: [PATCH 0219/1691] Group platform loads in homekit_controller to minimize executor use (#112148) --- .../homekit_controller/connection.py | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 0dabc814a7e..ab5bbc10440 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -30,7 +30,6 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.event import async_call_later, async_track_time_interval -from homeassistant.util.async_ import create_eager_task from .config_flow import normalize_hkid from .const import ( @@ -320,7 +319,7 @@ class HKDevice: ) # BLE devices always get an RSSI sensor as well if "sensor" not in self.platforms: - await self.async_load_platform("sensor") + await self._async_load_platforms({"sensor"}) @callback def _async_start_polling(self) -> None: @@ -791,19 +790,14 @@ class HKDevice: self.entities.add(entity_key) break - async def async_load_platform(self, platform: str) -> None: - """Load a single platform idempotently.""" - if platform in self.platforms: + async def _async_load_platforms(self, platforms: set[str]) -> None: + """Load a group of platforms.""" + if not (to_load := platforms - self.platforms): return - - self.platforms.add(platform) - try: - await self.hass.config_entries.async_forward_entry_setup( - self.config_entry, platform - ) - except Exception: - self.platforms.remove(platform) - raise + self.platforms.update(to_load) + await self.hass.config_entries.async_forward_entry_setups( + self.config_entry, platforms + ) async def async_load_platforms(self) -> None: """Load any platforms needed by this HomeKit device.""" @@ -822,12 +816,7 @@ class HKDevice: to_load.add(platform) if to_load: - await asyncio.gather( - *( - create_eager_task(self.async_load_platform(platform)) - for platform in to_load - ) - ) + await self._async_load_platforms(to_load) @callback def async_update_available_state(self, *_: Any) -> None: From 2c5510df3071391c3e723ddfa6ea002bd2981dd1 Mon Sep 17 00:00:00 2001 From: Yuxin Wang Date: Mon, 4 Mar 2024 03:40:59 -0500 Subject: [PATCH 0220/1691] Avoid using coordinator in config flow of APCUPSD (#112121) * Separate data class out of coordinator * Further fix the imports * Update homeassistant/components/apcupsd/coordinator.py Co-authored-by: J. Nick Koston * Use `or` to make it a bit cleaner when trying to find the UPS model Co-authored-by: Robert Svensson * Use or to make it a bit cleaner when trying to find the UPS model Co-authored-by: Robert Svensson * Use plain dict instead of `OrderedDict` --------- Co-authored-by: J. Nick Koston Co-authored-by: Robert Svensson --- .../components/apcupsd/binary_sensor.py | 5 +- .../components/apcupsd/config_flow.py | 32 ++++------ homeassistant/components/apcupsd/const.py | 1 + .../components/apcupsd/coordinator.py | 59 ++++++++++--------- homeassistant/components/apcupsd/sensor.py | 5 +- 5 files changed, 49 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index d200df743f1..89de03fced2 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -13,7 +13,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DOMAIN, APCUPSdCoordinator +from .const import DOMAIN +from .coordinator import APCUPSdCoordinator _LOGGER = logging.getLogger(__name__) _DESCRIPTION = BinarySensorEntityDescription( @@ -53,7 +54,7 @@ class OnlineStatus(CoordinatorEntity[APCUPSdCoordinator], BinarySensorEntity): super().__init__(coordinator, context=description.key.upper()) # Set up unique id and device info if serial number is available. - if (serial_no := coordinator.ups_serial_no) is not None: + if (serial_no := coordinator.data.serial_no) is not None: self._attr_unique_id = f"{serial_no}_{description.key}" self.entity_description = description self._attr_device_info = coordinator.device_info diff --git a/homeassistant/components/apcupsd/config_flow.py b/homeassistant/components/apcupsd/config_flow.py index 0e4c5ecb413..3e37cb209c3 100644 --- a/homeassistant/components/apcupsd/config_flow.py +++ b/homeassistant/components/apcupsd/config_flow.py @@ -1,17 +1,19 @@ """Config flow for APCUPSd integration.""" from __future__ import annotations +import asyncio from typing import Any +import aioapcaccess import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.helpers import selector import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.update_coordinator import UpdateFailed -from . import DOMAIN, APCUPSdCoordinator +from .const import CONNECTION_TIMEOUT, DOMAIN +from .coordinator import APCUPSdData _PORT_SELECTOR = vol.All( selector.NumberSelector( @@ -49,32 +51,22 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN): self._async_abort_entries_match({CONF_HOST: host, CONF_PORT: port}) # Test the connection to the host and get the current status for serial number. - coordinator = APCUPSdCoordinator(self.hass, host, port) - await coordinator.async_request_refresh() - - if isinstance(coordinator.last_exception, (UpdateFailed, TimeoutError)): + try: + async with asyncio.timeout(CONNECTION_TIMEOUT): + data = APCUPSdData(await aioapcaccess.request_status(host, port)) + except (OSError, asyncio.IncompleteReadError, TimeoutError): errors = {"base": "cannot_connect"} return self.async_show_form( step_id="user", data_schema=_SCHEMA, errors=errors ) - if not coordinator.data: + if not data: return self.async_abort(reason="no_status") # We _try_ to use the serial number of the UPS as the unique id since this field # is not guaranteed to exist on all APC UPS models. - await self.async_set_unique_id(coordinator.ups_serial_no) + await self.async_set_unique_id(data.serial_no) self._abort_if_unique_id_configured() - title = "APC UPS" - if coordinator.ups_name is not None: - title = coordinator.ups_name - elif coordinator.ups_model is not None: - title = coordinator.ups_model - elif coordinator.ups_serial_no is not None: - title = coordinator.ups_serial_no - - return self.async_create_entry( - title=title, - data=user_input, - ) + title = data.name or data.model or data.serial_no or "APC UPS" + return self.async_create_entry(title=title, data=user_input) diff --git a/homeassistant/components/apcupsd/const.py b/homeassistant/components/apcupsd/const.py index cacc9e29369..1bdf87bc57b 100644 --- a/homeassistant/components/apcupsd/const.py +++ b/homeassistant/components/apcupsd/const.py @@ -2,3 +2,4 @@ from typing import Final DOMAIN: Final = "apcupsd" +CONNECTION_TIMEOUT: int = 10 diff --git a/homeassistant/components/apcupsd/coordinator.py b/homeassistant/components/apcupsd/coordinator.py index 98d464ec526..5d71fd3e219 100644 --- a/homeassistant/components/apcupsd/coordinator.py +++ b/homeassistant/components/apcupsd/coordinator.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -from collections import OrderedDict from datetime import timedelta import logging from typing import Final @@ -19,14 +18,35 @@ from homeassistant.helpers.update_coordinator import ( UpdateFailed, ) -from .const import DOMAIN +from .const import CONNECTION_TIMEOUT, DOMAIN _LOGGER = logging.getLogger(__name__) UPDATE_INTERVAL: Final = timedelta(seconds=60) REQUEST_REFRESH_COOLDOWN: Final = 5 -class APCUPSdCoordinator(DataUpdateCoordinator[OrderedDict[str, str]]): +class APCUPSdData(dict[str, str]): + """Store data about an APCUPSd and provide a few helper methods for easier accesses.""" + + @property + def name(self) -> str | None: + """Return the name of the UPS, if available.""" + return self.get("UPSNAME") + + @property + def model(self) -> str | None: + """Return the model of the UPS, if available.""" + # Different UPS models may report slightly different keys for model, here we + # try them all. + return self.get("APCMODEL") or self.get("MODEL") + + @property + def serial_no(self) -> str | None: + """Return the unique serial number of the UPS, if available.""" + return self.get("SERIALNO") + + +class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]): """Store and coordinate the data retrieved from APCUPSd for all sensors. For each entity to use, acts as the single point responsible for fetching @@ -52,46 +72,27 @@ class APCUPSdCoordinator(DataUpdateCoordinator[OrderedDict[str, str]]): self._host = host self._port = port - @property - def ups_name(self) -> str | None: - """Return the name of the UPS, if available.""" - return self.data.get("UPSNAME") - - @property - def ups_model(self) -> str | None: - """Return the model of the UPS, if available.""" - # Different UPS models may report slightly different keys for model, here we - # try them all. - for model_key in ("APCMODEL", "MODEL"): - if model_key in self.data: - return self.data[model_key] - return None - - @property - def ups_serial_no(self) -> str | None: - """Return the unique serial number of the UPS, if available.""" - return self.data.get("SERIALNO") - @property def device_info(self) -> DeviceInfo: """Return the DeviceInfo of this APC UPS, if serial number is available.""" return DeviceInfo( - identifiers={(DOMAIN, self.ups_serial_no or self.config_entry.entry_id)}, - model=self.ups_model, + identifiers={(DOMAIN, self.data.serial_no or self.config_entry.entry_id)}, + model=self.data.model, manufacturer="APC", - name=self.ups_name if self.ups_name else "APC UPS", + name=self.data.name or "APC UPS", hw_version=self.data.get("FIRMWARE"), sw_version=self.data.get("VERSION"), ) - async def _async_update_data(self) -> OrderedDict[str, str]: + async def _async_update_data(self) -> APCUPSdData: """Fetch the latest status from APCUPSd. Note that the result dict uses upper case for each resource, where our integration uses lower cases as keys internally. """ - async with asyncio.timeout(10): + async with asyncio.timeout(CONNECTION_TIMEOUT): try: - return await aioapcaccess.request_status(self._host, self._port) + data = await aioapcaccess.request_status(self._host, self._port) + return APCUPSdData(data) except (OSError, asyncio.IncompleteReadError) as error: raise UpdateFailed(error) from error diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 4a2261f0b30..e27071c75c3 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -24,7 +24,8 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DOMAIN, APCUPSdCoordinator +from .const import DOMAIN +from .coordinator import APCUPSdCoordinator _LOGGER = logging.getLogger(__name__) @@ -493,7 +494,7 @@ class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity): super().__init__(coordinator=coordinator, context=description.key.upper()) # Set up unique id and device info if serial number is available. - if (serial_no := coordinator.ups_serial_no) is not None: + if (serial_no := coordinator.data.serial_no) is not None: self._attr_unique_id = f"{serial_no}_{description.key}" self.entity_description = description From ac416f7e07a0434d9a17abfd9c276ebeecf69bfc Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 4 Mar 2024 01:05:28 -0800 Subject: [PATCH 0221/1691] Add rainbird request debouncer and immediately update entity switch state (#112152) --- homeassistant/components/rainbird/coordinator.py | 8 ++++++++ homeassistant/components/rainbird/switch.py | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/homeassistant/components/rainbird/coordinator.py b/homeassistant/components/rainbird/coordinator.py index 22aaf2d11a0..70365c2f095 100644 --- a/homeassistant/components/rainbird/coordinator.py +++ b/homeassistant/components/rainbird/coordinator.py @@ -19,6 +19,7 @@ from pyrainbird.data import ModelAndVersion, Schedule from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -29,6 +30,10 @@ UPDATE_INTERVAL = datetime.timedelta(minutes=1) # changes, so we refresh it less often. CALENDAR_UPDATE_INTERVAL = datetime.timedelta(minutes=15) +# The valves state are not immediately reflected after issuing a command. We add +# small delay to give additional time to reflect the new state. +DEBOUNCER_COOLDOWN = 5 + # Rainbird devices can only accept a single request at a time CONECTION_LIMIT = 1 @@ -71,6 +76,9 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]): _LOGGER, name=name, update_interval=UPDATE_INTERVAL, + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=DEBOUNCER_COOLDOWN, immediate=False + ), ) self._controller = controller self._unique_id = unique_id diff --git a/homeassistant/components/rainbird/switch.py b/homeassistant/components/rainbird/switch.py index da3979a27fd..810a6fbb721 100644 --- a/homeassistant/components/rainbird/switch.py +++ b/homeassistant/components/rainbird/switch.py @@ -103,6 +103,10 @@ class RainBirdSwitch(CoordinatorEntity[RainbirdUpdateCoordinator], SwitchEntity) except RainbirdApiException as err: raise HomeAssistantError("Rain Bird device failure") from err + # The device reflects the old state for a few moments. Update the + # state manually and trigger a refresh after a short debounced delay. + self.coordinator.data.active_zones.add(self._zone) + self.async_write_ha_state() await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs): @@ -115,6 +119,11 @@ class RainBirdSwitch(CoordinatorEntity[RainbirdUpdateCoordinator], SwitchEntity) ) from err except RainbirdApiException as err: raise HomeAssistantError("Rain Bird device failure") from err + + # The device reflects the old state for a few moments. Update the + # state manually and trigger a refresh after a short debounced delay. + self.coordinator.data.active_zones.remove(self._zone) + self.async_write_ha_state() await self.coordinator.async_request_refresh() @property From faef5da1c58ed45a1fc249a4c58d2237f13421c6 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <9592452+CFenner@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:10:59 +0100 Subject: [PATCH 0222/1691] Handle exception in ViCare integration (#111128) --- homeassistant/components/vicare/climate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 1b8c1edcb8c..10cc1a15c9e 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -205,7 +205,8 @@ class ViCareClimate(ViCareEntity, ClimateEntity): "heating_curve_shift" ] = self._circuit.getHeatingCurveShift() - self._attributes["vicare_modes"] = self._circuit.getModes() + with suppress(PyViCareNotSupportedFeatureError): + self._attributes["vicare_modes"] = self._circuit.getModes() self._current_action = False # Update the specific device attributes From 4c6767056668b039d99bd5dc5a215dfd727726b7 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 4 Mar 2024 10:14:46 +0000 Subject: [PATCH 0223/1691] Update System Bridge to support version 4.x.x and above (#107957) * Update System Bridge to support version 4.x.x and above Update systembridgeconnector to version 4.0.0.dev4 Update system_bridgeconnector version to 4.0.0.dev6 Refactor WebSocket client handling in config_flow.py Update strings Update data handling Add default field values to SystemBridgeCoordinatorData Add version check and issue creation for unsupported System Bridge versions Update coordinator.py to set disks and memory to None Update system bridge coordinator to use token instead of API key Update systembridgeconnector version to 4.0.0.dev7 Update systembridgeconnector version to 4.0.0.dev8 Update systembridgeconnector version to 4.0.0.dev9 Changes Update units Fix GPU memory calculation in sensor.py Update GPU memory unit of measurement Add translation keys for binary sensor names Cleanup Add async_migrate_entry function for entry migration Update systembridgeconnector version to 4.0.0.dev10 Update systembridgeconnector version to 4.0.0.dev11 Add version check and authentication handling Update token description in strings.json Fix skipping partitions without data in system_bridge sensor Update systembridgeconnector version to 4.0.0.dev12 Update systembridgeconnector version to 4.0.0 Add check for unsupported version of System Bridge Update systembridgeconnector version to 4.0.1 Update debug log message in async_setup_entry function Remove debug log statement Fixes Update key to token Update tests Update tests Remove unused import in test_config_flow.py Remove added missing translations for another PR Refactor CPU power per CPU calculation Make one liner into lambda Refactors Fix exception type in async_setup_entry function Move checks to class and set minor version Remove unnecessary comment in gpu_memory_free function Remove translation_key for memory_used_percentage sensor Reverse string change Update token placeholder in strings.json Remove suggested_display_precision from sensor descriptions Remove suggested_display_precision from GPU sensor setup Refactor sensor code * Update migrate entry * Refactor GPU-related functions to use a decorator * Move per cpu functions to use decorator * Refactor functions to use decorators for data availability * Remove CONF_API_KEY from config entry data * Add test for migration * Refactor import statement in test_config_flow.py --- .../components/system_bridge/__init__.py | 64 ++- .../components/system_bridge/config_flow.py | 101 +++- .../components/system_bridge/const.py | 6 +- .../components/system_bridge/coordinator.py | 83 +-- .../components/system_bridge/manifest.json | 2 +- .../components/system_bridge/media_player.py | 16 +- .../components/system_bridge/media_source.py | 8 +- .../components/system_bridge/notify.py | 2 +- .../components/system_bridge/sensor.py | 490 ++++++++++-------- .../components/system_bridge/strings.json | 15 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../system_bridge/test_config_flow.py | 127 +++-- 13 files changed, 569 insertions(+), 349 deletions(-) diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index d2f5c795b7f..e927a05b96f 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -9,7 +9,7 @@ from systembridgeconnector.exceptions import ( ConnectionClosedException, ConnectionErrorException, ) -from systembridgeconnector.version import SUPPORTED_VERSION, Version +from systembridgeconnector.version import Version from systembridgemodels.keyboard_key import KeyboardKey from systembridgemodels.keyboard_text import KeyboardText from systembridgemodels.open_path import OpenPath @@ -25,6 +25,7 @@ from homeassistant.const import ( CONF_NAME, CONF_PATH, CONF_PORT, + CONF_TOKEN, CONF_URL, Platform, ) @@ -36,6 +37,7 @@ from homeassistant.helpers import ( discovery, ) from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import DOMAIN, MODULES from .coordinator import SystemBridgeDataUpdateCoordinator @@ -80,16 +82,13 @@ async def async_setup_entry( version = Version( entry.data[CONF_HOST], entry.data[CONF_PORT], - entry.data[CONF_API_KEY], + entry.data[CONF_TOKEN], session=async_get_clientsession(hass), ) + supported = False try: async with asyncio.timeout(10): - if not await version.check_supported(): - raise ConfigEntryNotReady( - "You are not running a supported version of System Bridge. Please" - f" update to {SUPPORTED_VERSION} or higher." - ) + supported = await version.check_supported() except AuthenticationException as exception: _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) raise ConfigEntryAuthFailed from exception @@ -102,6 +101,21 @@ async def async_setup_entry( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception + # If not supported, create an issue and raise ConfigEntryNotReady + if not supported: + async_create_issue( + hass=hass, + domain=DOMAIN, + issue_id=f"system_bridge_{entry.entry_id}_unsupported_version", + translation_key="unsupported_version", + translation_placeholders={"host": entry.data[CONF_HOST]}, + severity=IssueSeverity.ERROR, + is_fixable=False, + ) + raise ConfigEntryNotReady( + "You are not running a supported version of System Bridge. Please update to the latest version." + ) + coordinator = SystemBridgeDataUpdateCoordinator( hass, _LOGGER, @@ -122,6 +136,7 @@ async def async_setup_entry( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception + # Fetch initial data so we have data when entities subscribe await coordinator.async_config_entry_first_refresh() try: @@ -139,13 +154,6 @@ async def async_setup_entry( f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})." ) from exception - _LOGGER.debug( - "Initial coordinator data for %s (%s):\n%s", - entry.title, - entry.data[CONF_HOST], - coordinator.data.json(), - ) - hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator @@ -183,7 +191,7 @@ async def async_setup_entry( if entry.entry_id in device_entry.config_entries ) except StopIteration as exception: - raise vol.Invalid from exception + raise vol.Invalid(f"Could not find device {device}") from exception raise vol.Invalid(f"Device {device} does not exist") async def handle_open_path(call: ServiceCall) -> None: @@ -328,3 +336,29 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Reload the config entry when it changed.""" await hass.config_entries.async_reload(entry.entry_id) + + +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Migrate old entry.""" + _LOGGER.debug("Migrating from version %s", config_entry.version) + + if config_entry.version == 1 and config_entry.minor_version == 1: + # Migrate to CONF_TOKEN, which was added in 1.2 + new_data = dict(config_entry.data) + new_data.setdefault(CONF_TOKEN, config_entry.data.get(CONF_API_KEY)) + new_data.pop(CONF_API_KEY, None) + + hass.config_entries.async_update_entry( + config_entry, + data=new_data, + minor_version=2, + ) + + _LOGGER.debug( + "Migration to version %s.%s successful", + config_entry.version, + config_entry.minor_version, + ) + + # User is trying to downgrade from a future version + return False diff --git a/homeassistant/components/system_bridge/config_flow.py b/homeassistant/components/system_bridge/config_flow.py index 9fcecdc63c4..1d4f73799a1 100644 --- a/homeassistant/components/system_bridge/config_flow.py +++ b/homeassistant/components/system_bridge/config_flow.py @@ -12,13 +12,12 @@ from systembridgeconnector.exceptions import ( ConnectionErrorException, ) from systembridgeconnector.websocket_client import WebSocketClient -from systembridgemodels.get_data import GetData -from systembridgemodels.system import System +from systembridgemodels.modules import GetData, System import voluptuous as vol from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv @@ -28,12 +27,12 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -STEP_AUTHENTICATE_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): cv.string}) +STEP_AUTHENTICATE_DATA_SCHEMA = vol.Schema({vol.Required(CONF_TOKEN): cv.string}) STEP_USER_DATA_SCHEMA = vol.Schema( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT, default=9170): cv.string, - vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_TOKEN): cv.string, } ) @@ -114,12 +113,100 @@ class SystemBridgeConfigFlow( """Handle a config flow for System Bridge.""" VERSION = 1 + MINOR_VERSION = 2 def __init__(self) -> None: """Initialize flow.""" self._name: str | None = None self._input: dict[str, Any] = {} self._reauth = False + self._system_data: System | None = None + + async def _validate_input( + self, + data: dict[str, Any], + ) -> dict[str, str]: + """Validate the user input allows us to connect. + + Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. + """ + host = data[CONF_HOST] + + websocket_client = WebSocketClient( + host, + data[CONF_PORT], + data[CONF_TOKEN], + ) + + async def async_handle_module( + module_name: str, + module: Any, + ) -> None: + """Handle data from the WebSocket client.""" + _LOGGER.debug("Set new data for: %s", module_name) + if module_name == "system": + self._system_data = module + + try: + async with asyncio.timeout(15): + await websocket_client.connect( + session=async_get_clientsession(self.hass) + ) + self.hass.async_create_task( + websocket_client.listen(callback=async_handle_module) + ) + response = await websocket_client.get_data(GetData(modules=["system"])) + _LOGGER.debug("Got response: %s", response) + if response is None: + raise CannotConnect("No data received") + while self._system_data is None: + await asyncio.sleep(0.2) + except AuthenticationException as exception: + _LOGGER.warning( + "Authentication error when connecting to %s: %s", + data[CONF_HOST], + exception, + ) + raise InvalidAuth from exception + except ( + ConnectionClosedException, + ConnectionErrorException, + ) as exception: + _LOGGER.warning( + "Connection error when connecting to %s: %s", data[CONF_HOST], exception + ) + raise CannotConnect from exception + except TimeoutError as exception: + _LOGGER.warning( + "Timed out connecting to %s: %s", data[CONF_HOST], exception + ) + raise CannotConnect from exception + except ValueError as exception: + raise CannotConnect from exception + + _LOGGER.debug("Got System data: %s", self._system_data) + + return {"hostname": host, "uuid": self._system_data.uuid} + + async def _async_get_info( + self, + user_input: dict[str, Any], + ) -> tuple[dict[str, str], dict[str, str] | None]: + errors = {} + + try: + info = await self._validate_input(user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return errors, info + + return errors, None async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -130,7 +217,7 @@ class SystemBridgeConfigFlow( step_id="user", data_schema=STEP_USER_DATA_SCHEMA ) - errors, info = await _async_get_info(self.hass, user_input) + errors, info = await self._async_get_info(user_input) if not errors and info is not None: # Check if already configured await self.async_set_unique_id(info["uuid"], raise_on_progress=False) @@ -150,7 +237,7 @@ class SystemBridgeConfigFlow( if user_input is not None: user_input = {**self._input, **user_input} - errors, info = await _async_get_info(self.hass, user_input) + errors, info = await self._async_get_info(user_input) if not errors and info is not None: # Check if already configured existing_entry = await self.async_set_unique_id(info["uuid"]) diff --git a/homeassistant/components/system_bridge/const.py b/homeassistant/components/system_bridge/const.py index fc87b609b78..e58cdf5f72d 100644 --- a/homeassistant/components/system_bridge/const.py +++ b/homeassistant/components/system_bridge/const.py @@ -5,9 +5,9 @@ DOMAIN = "system_bridge" MODULES = [ "battery", "cpu", - "disk", - "display", - "gpu", + "disks", + "displays", + "gpus", "media", "memory", "processes", diff --git a/homeassistant/components/system_bridge/coordinator.py b/homeassistant/components/system_bridge/coordinator.py index 532092ab133..459caa975cc 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -3,58 +3,63 @@ from __future__ import annotations import asyncio from collections.abc import Callable +from dataclasses import dataclass, field from datetime import timedelta import logging from typing import Any -from pydantic import BaseModel # pylint: disable=no-name-in-module from systembridgeconnector.exceptions import ( AuthenticationException, ConnectionClosedException, ConnectionErrorException, ) from systembridgeconnector.websocket_client import WebSocketClient -from systembridgemodels.battery import Battery -from systembridgemodels.cpu import Cpu -from systembridgemodels.disk import Disk -from systembridgemodels.display import Display -from systembridgemodels.get_data import GetData -from systembridgemodels.gpu import Gpu -from systembridgemodels.media import Media -from systembridgemodels.media_directories import MediaDirectories -from systembridgemodels.media_files import File as MediaFile, MediaFiles +from systembridgemodels.media_directories import MediaDirectory +from systembridgemodels.media_files import MediaFile, MediaFiles from systembridgemodels.media_get_file import MediaGetFile from systembridgemodels.media_get_files import MediaGetFiles -from systembridgemodels.memory import Memory -from systembridgemodels.processes import Processes -from systembridgemodels.register_data_listener import RegisterDataListener -from systembridgemodels.system import System +from systembridgemodels.modules import ( + CPU, + GPU, + Battery, + Disks, + Display, + GetData, + Media, + Memory, + Process, + RegisterDataListener, + System, +) +from systembridgemodels.response import Response from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - CONF_API_KEY, CONF_HOST, CONF_PORT, + CONF_TOKEN, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN, MODULES -class SystemBridgeCoordinatorData(BaseModel): +@dataclass +class SystemBridgeCoordinatorData: """System Bridge Coordianator Data.""" - battery: Battery = None - cpu: Cpu = None - disk: Disk = None - display: Display = None - gpu: Gpu = None - media: Media = None + battery: Battery = field(default_factory=Battery) + cpu: CPU = field(default_factory=CPU) + disks: Disks = None + displays: list[Display] = field(default_factory=list[Display]) + gpus: list[GPU] = field(default_factory=list[GPU]) + media: Media = field(default_factory=Media) memory: Memory = None - processes: Processes = None + processes: list[Process] = field(default_factory=list[Process]) system: System = None @@ -78,11 +83,14 @@ class SystemBridgeDataUpdateCoordinator( self.websocket_client = WebSocketClient( entry.data[CONF_HOST], entry.data[CONF_PORT], - entry.data[CONF_API_KEY], + entry.data[CONF_TOKEN], ) super().__init__( - hass, LOGGER, name=DOMAIN, update_interval=timedelta(seconds=30) + hass, + LOGGER, + name=DOMAIN, + update_interval=timedelta(seconds=30), ) @property @@ -99,16 +107,14 @@ class SystemBridgeDataUpdateCoordinator( async def async_get_data( self, modules: list[str], - ) -> None: + ) -> Response: """Get data from WebSocket.""" if not self.websocket_client.connected: await self._setup_websocket() - self.hass.async_create_task( - self.websocket_client.get_data(GetData(modules=modules)) - ) + return await self.websocket_client.get_data(GetData(modules=modules)) - async def async_get_media_directories(self) -> MediaDirectories: + async def async_get_media_directories(self) -> list[MediaDirectory]: """Get media directories.""" return await self.websocket_client.get_directories() @@ -154,7 +160,11 @@ class SystemBridgeDataUpdateCoordinator( await self.websocket_client.listen(callback=self.async_handle_module) except AuthenticationException as exception: self.last_update_success = False - self.logger.error("Authentication failed for %s: %s", self.title, exception) + self.logger.error( + "Authentication failed while listening for %s: %s", + self.title, + exception, + ) if self.unsub: self.unsub() self.unsub = None @@ -199,14 +209,18 @@ class SystemBridgeDataUpdateCoordinator( await self.websocket_client.register_data_listener( RegisterDataListener(modules=MODULES) ) + self.last_update_success = True + self.async_update_listeners() except AuthenticationException as exception: - self.last_update_success = False - self.logger.error("Authentication failed for %s: %s", self.title, exception) + self.logger.error( + "Authentication failed at setup for %s: %s", self.title, exception + ) if self.unsub: self.unsub() self.unsub = None self.last_update_success = False self.async_update_listeners() + raise ConfigEntryAuthFailed from exception except ConnectionErrorException as exception: self.logger.warning( "Connection error occurred for %s. Will retry: %s", @@ -224,9 +238,6 @@ class SystemBridgeDataUpdateCoordinator( self.last_update_success = False self.async_update_listeners() - self.last_update_success = True - self.async_update_listeners() - async def close_websocket(_) -> None: """Close WebSocket connection.""" await self.websocket_client.close() diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index 17c43fa4d24..dc2b645d5b7 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -10,6 +10,6 @@ "iot_class": "local_push", "loggers": ["systembridgeconnector"], "quality_scale": "silver", - "requirements": ["systembridgeconnector==3.10.0"], + "requirements": ["systembridgeconnector==4.0.1"], "zeroconf": ["_system-bridge._tcp.local."] } diff --git a/homeassistant/components/system_bridge/media_player.py b/homeassistant/components/system_bridge/media_player.py index 02670d36fe3..a9252a739c9 100644 --- a/homeassistant/components/system_bridge/media_player.py +++ b/homeassistant/components/system_bridge/media_player.py @@ -4,7 +4,7 @@ from __future__ import annotations import datetime as dt from typing import Final -from systembridgemodels.media_control import Action as MediaAction, MediaControl +from systembridgemodels.media_control import MediaAction, MediaControl from homeassistant.components.media_player import ( MediaPlayerDeviceClass, @@ -202,7 +202,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Send play command.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.play, + action=MediaAction.PLAY.value, ) ) @@ -210,7 +210,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Send pause command.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.pause, + action=MediaAction.PAUSE.value, ) ) @@ -218,7 +218,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Send stop command.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.stop, + action=MediaAction.STOP.value, ) ) @@ -226,7 +226,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Send previous track command.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.previous, + action=MediaAction.PREVIOUS.value, ) ) @@ -234,7 +234,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Send next track command.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.next, + action=MediaAction.NEXT.value, ) ) @@ -245,7 +245,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Enable/disable shuffle mode.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.shuffle, + action=MediaAction.SHUFFLE.value, value=shuffle, ) ) @@ -257,7 +257,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): """Set repeat mode.""" await self.coordinator.websocket_client.media_control( MediaControl( - action=MediaAction.repeat, + action=MediaAction.REPEAT.value, value=MEDIA_SET_REPEAT_MAP.get(repeat), ) ) diff --git a/homeassistant/components/system_bridge/media_source.py b/homeassistant/components/system_bridge/media_source.py index 3423946f637..be15542bcc3 100644 --- a/homeassistant/components/system_bridge/media_source.py +++ b/homeassistant/components/system_bridge/media_source.py @@ -1,8 +1,8 @@ """System Bridge Media Source Implementation.""" from __future__ import annotations -from systembridgemodels.media_directories import MediaDirectories -from systembridgemodels.media_files import File as MediaFile, MediaFiles +from systembridgemodels.media_directories import MediaDirectory +from systembridgemodels.media_files import MediaFile, MediaFiles from homeassistant.components.media_player import MediaClass from homeassistant.components.media_source import MEDIA_CLASS_MAP, MEDIA_MIME_TYPES @@ -129,7 +129,7 @@ def _build_base_url( def _build_root_paths( entry: ConfigEntry, - media_directories: MediaDirectories, + media_directories: list[MediaDirectory], ) -> BrowseMediaSource: """Build base categories for System Bridge media.""" return BrowseMediaSource( @@ -152,7 +152,7 @@ def _build_root_paths( children=[], children_media_class=MediaClass.DIRECTORY, ) - for directory in media_directories.directories + for directory in media_directories ], children_media_class=MediaClass.DIRECTORY, ) diff --git a/homeassistant/components/system_bridge/notify.py b/homeassistant/components/system_bridge/notify.py index f8c00789ae5..03665cf3575 100644 --- a/homeassistant/components/system_bridge/notify.py +++ b/homeassistant/components/system_bridge/notify.py @@ -71,6 +71,6 @@ class SystemBridgeNotificationService(BaseNotificationService): title=kwargs.get(ATTR_TITLE, data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)), ) - _LOGGER.debug("Sending notification: %s", notification.json()) + _LOGGER.debug("Sending notification: %s", notification) await self._coordinator.websocket_client.send_notification(notification) diff --git a/homeassistant/components/system_bridge/sensor.py b/homeassistant/components/system_bridge/sensor.py index 35cc0e00809..b4e840643f1 100644 --- a/homeassistant/components/system_bridge/sensor.py +++ b/homeassistant/components/system_bridge/sensor.py @@ -6,6 +6,10 @@ from dataclasses import dataclass from datetime import UTC, datetime, timedelta from typing import Final, cast +from systembridgemodels.modules.cpu import PerCPU +from systembridgemodels.modules.displays import Display +from systembridgemodels.modules.gpus import GPU + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -26,7 +30,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import UNDEFINED, StateType -from homeassistant.util.dt import utcnow +from homeassistant.util import dt as dt_util from .const import DOMAIN from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator @@ -51,88 +55,174 @@ class SystemBridgeSensorEntityDescription(SensorEntityDescription): def battery_time_remaining(data: SystemBridgeCoordinatorData) -> datetime | None: """Return the battery time remaining.""" - if (value := getattr(data.battery, "sensors_secsleft", None)) is not None: - return utcnow() + timedelta(seconds=value) - return None - - -def cpu_power_package(data: SystemBridgeCoordinatorData) -> float | None: - """Return the CPU package power.""" - if data.cpu.power_package is not None: - return data.cpu.power_package - return None - - -def cpu_power_per_cpu( - data: SystemBridgeCoordinatorData, - cpu: int, -) -> float | None: - """Return CPU power per CPU.""" - if (value := getattr(data.cpu, f"power_per_cpu_{cpu}", None)) is not None: - return value + if (battery_time := data.battery.time_remaining) is not None: + return dt_util.utcnow() + timedelta(seconds=battery_time) return None def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None: """Return the CPU speed.""" - if data.cpu.frequency_current is not None: - return round(data.cpu.frequency_current / 1000, 2) + if (cpu_frequency := data.cpu.frequency) is not None and ( + cpu_frequency.current + ) is not None: + return round(cpu_frequency.current / 1000, 2) return None -def gpu_core_clock_speed(data: SystemBridgeCoordinatorData, key: str) -> float | None: +def with_per_cpu(func) -> Callable: + """Wrap a function to ensure per CPU data is available.""" + + def wrapper(data: SystemBridgeCoordinatorData, index: int) -> float | None: + """Wrap a function to ensure per CPU data is available.""" + if data.cpu.per_cpu is not None and index < len(data.cpu.per_cpu): + return func(data.cpu.per_cpu[index]) + return None + + return wrapper + + +@with_per_cpu +def cpu_power_per_cpu(per_cpu: PerCPU) -> float | None: + """Return CPU power per CPU.""" + return per_cpu.power + + +@with_per_cpu +def cpu_usage_per_cpu(per_cpu: PerCPU) -> float | None: + """Return CPU usage per CPU.""" + return per_cpu.usage + + +def with_display(func) -> Callable: + """Wrap a function to ensure a Display is available.""" + + def wrapper(data: SystemBridgeCoordinatorData, index: int) -> Display | None: + """Wrap a function to ensure a Display is available.""" + if index < len(data.displays): + return func(data.displays[index]) + return None + + return wrapper + + +@with_display +def display_resolution_horizontal(display: Display) -> int | None: + """Return the Display resolution horizontal.""" + return display.resolution_horizontal + + +@with_display +def display_resolution_vertical(display: Display) -> int | None: + """Return the Display resolution vertical.""" + return display.resolution_vertical + + +@with_display +def display_refresh_rate(display: Display) -> float | None: + """Return the Display refresh rate.""" + return display.refresh_rate + + +def with_gpu(func) -> Callable: + """Wrap a function to ensure a GPU is available.""" + + def wrapper(data: SystemBridgeCoordinatorData, index: int) -> GPU | None: + """Wrap a function to ensure a GPU is available.""" + if index < len(data.gpus): + return func(data.gpus[index]) + return None + + return wrapper + + +@with_gpu +def gpu_core_clock_speed(gpu: GPU) -> float | None: """Return the GPU core clock speed.""" - if (value := getattr(data.gpu, f"{key}_core_clock", None)) is not None: - return round(value) - return None + return gpu.core_clock -def gpu_memory_clock_speed(data: SystemBridgeCoordinatorData, key: str) -> float | None: +@with_gpu +def gpu_fan_speed(gpu: GPU) -> float | None: + """Return the GPU fan speed.""" + return gpu.fan_speed + + +@with_gpu +def gpu_memory_clock_speed(gpu: GPU) -> float | None: """Return the GPU memory clock speed.""" - if (value := getattr(data.gpu, f"{key}_memory_clock", None)) is not None: - return round(value) - return None + return gpu.memory_clock -def gpu_memory_free(data: SystemBridgeCoordinatorData, key: str) -> float | None: +@with_gpu +def gpu_memory_free(gpu: GPU) -> float | None: """Return the free GPU memory.""" - if (value := getattr(data.gpu, f"{key}_memory_free", None)) is not None: - return round(value) - return None + return gpu.memory_free -def gpu_memory_used(data: SystemBridgeCoordinatorData, key: str) -> float | None: +@with_gpu +def gpu_memory_used(gpu: GPU) -> float | None: """Return the used GPU memory.""" - if (value := getattr(data.gpu, f"{key}_memory_used", None)) is not None: - return round(value) - return None + return gpu.memory_used -def gpu_memory_used_percentage( - data: SystemBridgeCoordinatorData, key: str -) -> float | None: +@with_gpu +def gpu_memory_used_percentage(gpu: GPU) -> float | None: """Return the used GPU memory percentage.""" - if ((used := getattr(data.gpu, f"{key}_memory_used", None)) is not None) and ( - (total := getattr(data.gpu, f"{key}_memory_total", None)) is not None - ): - return round( - used / total * 100, - 2, - ) + if (gpu.memory_used) is not None and (gpu.memory_total) is not None: + return round(gpu.memory_used / gpu.memory_total * 100, 2) return None +@with_gpu +def gpu_power_usage(gpu: GPU) -> float | None: + """Return the GPU power usage.""" + return gpu.power_usage + + +@with_gpu +def gpu_temperature(gpu: GPU) -> float | None: + """Return the GPU temperature.""" + return gpu.temperature + + +@with_gpu +def gpu_usage_percentage(gpu: GPU) -> float | None: + """Return the GPU usage percentage.""" + return gpu.core_load + + def memory_free(data: SystemBridgeCoordinatorData) -> float | None: """Return the free memory.""" - if data.memory.virtual_free is not None: - return round(data.memory.virtual_free / 1000**3, 2) + if (virtual := data.memory.virtual) is not None and ( + free := virtual.free + ) is not None: + return round(free / 1000**3, 2) return None def memory_used(data: SystemBridgeCoordinatorData) -> float | None: """Return the used memory.""" - if data.memory.virtual_used is not None: - return round(data.memory.virtual_used / 1000**3, 2) + if (virtual := data.memory.virtual) is not None and ( + used := virtual.used + ) is not None: + return round(used / 1000**3, 2) + return None + + +def partition_usage( + data: SystemBridgeCoordinatorData, + device_index: int, + partition_index: int, +) -> float | None: + """Return the used memory.""" + if ( + (devices := data.disks.devices) is not None + and device_index < len(devices) + and (partitions := devices[device_index].partitions) is not None + and partition_index < len(partitions) + and (usage := partitions[partition_index].usage) is not None + ): + return usage.percent return None @@ -151,7 +241,7 @@ BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=2, icon="mdi:chip", - value=cpu_power_package, + value=lambda data: data.cpu.power, ), SystemBridgeSensorEntityDescription( key="cpu_speed", @@ -197,15 +287,14 @@ BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = ( ), SystemBridgeSensorEntityDescription( key="memory_used_percentage", - translation_key="memory_used", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, icon="mdi:memory", - value=lambda data: data.memory.virtual_percent, + value=lambda data: data.memory.virtual.percent, ), SystemBridgeSensorEntityDescription( key="memory_used", - translation_key="amount_memory_used", + translation_key="memory_used", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfInformation.GIGABYTES, @@ -224,7 +313,7 @@ BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = ( translation_key="processes", state_class=SensorStateClass.MEASUREMENT, icon="mdi:counter", - value=lambda data: int(data.processes.count), + value=lambda data: len(data.processes), ), SystemBridgeSensorEntityDescription( key="processes_load", @@ -279,23 +368,27 @@ async def async_setup_entry( SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT]) ) - for partition in coordinator.data.disk.partitions: - entities.append( - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"filesystem_{partition.replace(':', '')}", - name=f"{partition} space used", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PERCENTAGE, - icon="mdi:harddisk", - value=lambda data, p=partition: getattr( - data.disk, f"usage_{p}_percent", None + for index_device, device in enumerate(coordinator.data.disks.devices): + if device.partitions is None: + continue + + for index_partition, partition in enumerate(device.partitions): + entities.append( + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"filesystem_{partition.mount_point.replace(':', '')}", + name=f"{partition.mount_point} space used", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + icon="mdi:harddisk", + value=lambda data, + dk=index_device, + pk=index_partition: partition_usage(data, dk, pk), ), - ), - entry.data[CONF_PORT], + entry.data[CONF_PORT], + ) ) - ) if ( coordinator.data.battery @@ -307,20 +400,6 @@ async def async_setup_entry( SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT]) ) - displays: list[dict[str, str]] = [] - if coordinator.data.display.displays is not None: - displays.extend( - { - "key": display, - "name": getattr(coordinator.data.display, f"{display}_name").replace( - "Display ", "" - ), - } - for display in coordinator.data.display.displays - if hasattr(coordinator.data.display, f"{display}_name") - ) - display_count = len(displays) - entities.append( SystemBridgeSensor( coordinator, @@ -329,236 +408,213 @@ async def async_setup_entry( translation_key="displays_connected", state_class=SensorStateClass.MEASUREMENT, icon="mdi:monitor", - value=lambda _, count=display_count: count, + value=lambda data: len(data.displays) if data.displays else None, ), entry.data[CONF_PORT], ) ) - for _, display in enumerate(displays): + if coordinator.data.displays is not None: + for index, display in enumerate(coordinator.data.displays): + entities = [ + *entities, + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"display_{display.id}_resolution_x", + name=f"Display {display.id} resolution x", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PIXELS, + icon="mdi:monitor", + value=lambda data, k=index: display_resolution_horizontal( + data, k + ), + ), + entry.data[CONF_PORT], + ), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"display_{display.id}_resolution_y", + name=f"Display {display.id} resolution y", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PIXELS, + icon="mdi:monitor", + value=lambda data, k=index: display_resolution_vertical( + data, k + ), + ), + entry.data[CONF_PORT], + ), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"display_{display.id}_refresh_rate", + name=f"Display {display.id} refresh rate", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfFrequency.HERTZ, + device_class=SensorDeviceClass.FREQUENCY, + icon="mdi:monitor", + value=lambda data, k=index: display_refresh_rate(data, k), + ), + entry.data[CONF_PORT], + ), + ] + + for index, gpu in enumerate(coordinator.data.gpus): entities = [ *entities, SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"display_{display['name']}_resolution_x", - name=f"Display {display['name']} resolution x", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PIXELS, - icon="mdi:monitor", - value=lambda data, k=display["key"]: getattr( - data.display, f"{k}_resolution_horizontal", None - ), - ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"display_{display['name']}_resolution_y", - name=f"Display {display['name']} resolution y", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PIXELS, - icon="mdi:monitor", - value=lambda data, k=display["key"]: getattr( - data.display, f"{k}_resolution_vertical", None - ), - ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"display_{display['name']}_refresh_rate", - name=f"Display {display['name']} refresh rate", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfFrequency.HERTZ, - device_class=SensorDeviceClass.FREQUENCY, - icon="mdi:monitor", - value=lambda data, k=display["key"]: getattr( - data.display, f"{k}_refresh_rate", None - ), - ), - entry.data[CONF_PORT], - ), - ] - - gpus: list[dict[str, str]] = [] - if coordinator.data.gpu.gpus is not None: - gpus.extend( - { - "key": gpu, - "name": getattr(coordinator.data.gpu, f"{gpu}_name"), - } - for gpu in coordinator.data.gpu.gpus - if hasattr(coordinator.data.gpu, f"{gpu}_name") - ) - - for index, gpu in enumerate(gpus): - entities = [ - *entities, - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_core_clock_speed", - name=f"{gpu['name']} clock speed", + key=f"gpu_{gpu.id}_core_clock_speed", + name=f"{gpu.name} clock speed", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ, device_class=SensorDeviceClass.FREQUENCY, icon="mdi:speedometer", - value=lambda data, k=gpu["key"]: gpu_core_clock_speed(data, k), + value=lambda data, k=index: gpu_core_clock_speed(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_memory_clock_speed", - name=f"{gpu['name']} memory clock speed", + key=f"gpu_{gpu.id}_memory_clock_speed", + name=f"{gpu.name} memory clock speed", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ, device_class=SensorDeviceClass.FREQUENCY, icon="mdi:speedometer", - value=lambda data, k=gpu["key"]: gpu_memory_clock_speed(data, k), + value=lambda data, k=index: gpu_memory_clock_speed(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_memory_free", - name=f"{gpu['name']} memory free", + key=f"gpu_{gpu.id}_memory_free", + name=f"{gpu.name} memory free", state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfInformation.GIGABYTES, + native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:memory", - value=lambda data, k=gpu["key"]: gpu_memory_free(data, k), + value=lambda data, k=index: gpu_memory_free(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_memory_used_percentage", - name=f"{gpu['name']} memory used %", + key=f"gpu_{gpu.id}_memory_used_percentage", + name=f"{gpu.name} memory used %", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, icon="mdi:memory", - value=lambda data, k=gpu["key"]: gpu_memory_used_percentage( - data, k - ), + value=lambda data, k=index: gpu_memory_used_percentage(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_memory_used", - name=f"{gpu['name']} memory used", + key=f"gpu_{gpu.id}_memory_used", + name=f"{gpu.name} memory used", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfInformation.GIGABYTES, + native_unit_of_measurement=UnitOfInformation.MEGABYTES, device_class=SensorDeviceClass.DATA_SIZE, icon="mdi:memory", - value=lambda data, k=gpu["key"]: gpu_memory_used(data, k), + value=lambda data, k=index: gpu_memory_used(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_fan_speed", - name=f"{gpu['name']} fan speed", + key=f"gpu_{gpu.id}_fan_speed", + name=f"{gpu.name} fan speed", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=REVOLUTIONS_PER_MINUTE, icon="mdi:fan", - value=lambda data, k=gpu["key"]: getattr( - data.gpu, f"{k}_fan_speed", None - ), + value=lambda data, k=index: gpu_fan_speed(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_power_usage", - name=f"{gpu['name']} power usage", + key=f"gpu_{gpu.id}_power_usage", + name=f"{gpu.name} power usage", entity_registry_enabled_default=False, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfPower.WATT, - value=lambda data, k=gpu["key"]: getattr( - data.gpu, f"{k}_power", None - ), + value=lambda data, k=index: gpu_power_usage(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_temperature", - name=f"{gpu['name']} temperature", + key=f"gpu_{gpu.id}_temperature", + name=f"{gpu.name} temperature", entity_registry_enabled_default=False, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda data, k=gpu["key"]: getattr( - data.gpu, f"{k}_temperature", None - ), + value=lambda data, k=index: gpu_temperature(data, k), ), entry.data[CONF_PORT], ), SystemBridgeSensor( coordinator, SystemBridgeSensorEntityDescription( - key=f"gpu_{index}_usage_percentage", - name=f"{gpu['name']} usage %", + key=f"gpu_{gpu.id}_usage_percentage", + name=f"{gpu.name} usage %", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, icon="mdi:percent", - value=lambda data, k=gpu["key"]: getattr( - data.gpu, f"{k}_core_load", None - ), + value=lambda data, k=index: gpu_usage_percentage(data, k), ), entry.data[CONF_PORT], ), ] - for index in range(coordinator.data.cpu.count): - entities.append( - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"processes_load_cpu_{index}", - name=f"Load CPU {index}", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PERCENTAGE, - icon="mdi:percent", - value=lambda data, k=index: getattr(data.cpu, f"usage_{k}", None), - ), - entry.data[CONF_PORT], - ) - ) - if hasattr(coordinator.data.cpu, f"power_per_cpu_{index}"): - entities.append( - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"cpu_power_core_{index}", - name=f"CPU Core {index} Power", - entity_registry_enabled_default=False, - native_unit_of_measurement=UnitOfPower.WATT, - state_class=SensorStateClass.MEASUREMENT, - suggested_display_precision=2, - icon="mdi:chip", - value=lambda data, k=index: cpu_power_per_cpu(data, k), + if coordinator.data.cpu.per_cpu is not None: + for cpu in coordinator.data.cpu.per_cpu: + entities.extend( + [ + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"processes_load_cpu_{cpu.id}", + name=f"Load CPU {cpu.id}", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + icon="mdi:percent", + value=lambda data, k=cpu.id: cpu_usage_per_cpu(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ) + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"cpu_power_core_{cpu.id}", + name=f"CPU Core {cpu.id} Power", + entity_registry_enabled_default=False, + native_unit_of_measurement=UnitOfPower.WATT, + state_class=SensorStateClass.MEASUREMENT, + icon="mdi:chip", + value=lambda data, k=cpu.id: cpu_power_per_cpu(data, k), + ), + entry.data[CONF_PORT], + ), + ] ) async_add_entities(entities) diff --git a/homeassistant/components/system_bridge/strings.json b/homeassistant/components/system_bridge/strings.json index d99a2cf4588..16425da88c4 100644 --- a/homeassistant/components/system_bridge/strings.json +++ b/homeassistant/components/system_bridge/strings.json @@ -3,21 +3,22 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "unsupported_version": "Your version of System Bridge is not supported. Please upgrade to the latest version.", "unknown": "[%key:common::config_flow::error::unknown%]" }, "flow_title": "{name}", "step": { "authenticate": { "data": { - "api_key": "[%key:common::config_flow::data::api_key%]" + "token": "[%key:common::config_flow::data::api_token%]" }, - "description": "Please enter the API Key you set in your configuration for {name}." + "description": "Please enter the token set in your configuration for {name}." }, "user": { "data": { - "api_key": "[%key:common::config_flow::data::api_key%]", "host": "[%key:common::config_flow::data::host%]", - "port": "[%key:common::config_flow::data::port%]" + "port": "[%key:common::config_flow::data::port%]", + "token": "Token" }, "description": "Please enter your connection details." } @@ -85,6 +86,12 @@ } } }, + "issues": { + "unsupported_version": { + "title": "System Bridge Upgrade Required", + "description": "Your version of System Bridge for host {host} is not supported.\n\nPlease upgrade to the latest version." + } + }, "services": { "open_path": { "name": "Open path", diff --git a/requirements_all.txt b/requirements_all.txt index 0f4c95614a0..8a72a2849c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2647,7 +2647,7 @@ switchbot-api==2.0.0 synology-srm==0.2.0 # homeassistant.components.system_bridge -systembridgeconnector==3.10.0 +systembridgeconnector==4.0.1 # homeassistant.components.tailscale tailscale==0.6.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8c44f4ad1f8..0078a890143 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2033,7 +2033,7 @@ surepy==0.9.0 switchbot-api==2.0.0 # homeassistant.components.system_bridge -systembridgeconnector==3.10.0 +systembridgeconnector==4.0.1 # homeassistant.components.tailscale tailscale==0.6.0 diff --git a/tests/components/system_bridge/test_config_flow.py b/tests/components/system_bridge/test_config_flow.py index 53c8ecf88bd..21194db42fa 100644 --- a/tests/components/system_bridge/test_config_flow.py +++ b/tests/components/system_bridge/test_config_flow.py @@ -1,5 +1,8 @@ """Test the System Bridge config flow.""" +from collections.abc import Awaitable, Callable +from dataclasses import asdict from ipaddress import ip_address +from typing import Any from unittest.mock import patch from systembridgeconnector.const import TYPE_DATA_UPDATE @@ -9,13 +12,14 @@ from systembridgeconnector.exceptions import ( ConnectionErrorException, ) from systembridgemodels.const import MODEL_SYSTEM +from systembridgemodels.modules.system import System from systembridgemodels.response import Response -from systembridgemodels.system import LastUpdated, System from homeassistant import config_entries, data_entry_flow from homeassistant.components import zeroconf +from homeassistant.components.system_bridge.config_flow import SystemBridgeConfigFlow from homeassistant.components.system_bridge.const import DOMAIN -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry @@ -23,16 +27,16 @@ from tests.common import MockConfigEntry FIXTURE_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" FIXTURE_UUID = "e91bf575-56f3-4c83-8f42-70ac17adcd33" -FIXTURE_AUTH_INPUT = {CONF_API_KEY: "abc-123-def-456-ghi"} +FIXTURE_AUTH_INPUT = {CONF_TOKEN: "abc-123-def-456-ghi"} FIXTURE_USER_INPUT = { - CONF_API_KEY: "abc-123-def-456-ghi", + CONF_TOKEN: "abc-123-def-456-ghi", CONF_HOST: "test-bridge", CONF_PORT: "9170", } FIXTURE_ZEROCONF_INPUT = { - CONF_API_KEY: "abc-123-def-456-ghi", + CONF_TOKEN: "abc-123-def-456-ghi", CONF_HOST: "1.1.1.1", CONF_PORT: "9170", } @@ -69,7 +73,6 @@ FIXTURE_ZEROCONF_BAD = zeroconf.ZeroconfServiceInfo( FIXTURE_SYSTEM = System( - id=FIXTURE_UUID, boot_time=1, fqdn="", hostname="1.1.1.1", @@ -82,20 +85,7 @@ FIXTURE_SYSTEM = System( version="", version_latest="", version_newer_available=False, - last_updated=LastUpdated( - boot_time=1, - fqdn=1, - hostname=1, - ip_address_4=1, - mac_address=1, - platform=1, - platform_version=1, - uptime=1, - uuid=1, - version=1, - version_latest=1, - version_newer_available=1, - ), + users=[], ) FIXTURE_DATA_RESPONSE = Response( @@ -104,7 +94,7 @@ FIXTURE_DATA_RESPONSE = Response( subtype=None, message="Data received", module=MODEL_SYSTEM, - data=FIXTURE_SYSTEM, + data=asdict(FIXTURE_SYSTEM), ) FIXTURE_DATA_RESPONSE_BAD = Response( @@ -126,6 +116,17 @@ FIXTURE_DATA_RESPONSE_BAD = Response( ) +async def mock_data_listener( + self, + callback: Callable[[str, Any], Awaitable[None]] | None = None, + _: bool = False, +): + """Mock websocket data listener.""" + if callback is not None: + # Simulate data received from the websocket + await callback(MODEL_SYSTEM, FIXTURE_SYSTEM) + + async def test_show_user_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" result = await hass.config_entries.flow.async_init( @@ -152,6 +153,7 @@ async def test_user_flow(hass: HomeAssistant) -> None: return_value=FIXTURE_DATA_RESPONSE, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ), patch( "homeassistant.components.system_bridge.async_setup_entry", return_value=True, @@ -206,6 +208,7 @@ async def test_form_connection_closed_cannot_connect(hass: HomeAssistant) -> Non side_effect=ConnectionClosedException, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -233,6 +236,7 @@ async def test_form_timeout_cannot_connect(hass: HomeAssistant) -> None: side_effect=TimeoutError, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -260,6 +264,7 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: side_effect=AuthenticationException, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -287,33 +292,7 @@ async def test_form_uuid_error(hass: HomeAssistant) -> None: side_effect=ValueError, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], FIXTURE_USER_INPUT - ) - await hass.async_block_till_done() - - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["step_id"] == "user" - assert result2["errors"] == {"base": "cannot_connect"} - - -async def test_form_value_error(hass: HomeAssistant) -> None: - """Test we handle error from bad value.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["errors"] is None - - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - return_value=FIXTURE_DATA_RESPONSE_BAD, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -341,6 +320,7 @@ async def test_form_unknown_error(hass: HomeAssistant) -> None: side_effect=Exception, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -368,6 +348,7 @@ async def test_reauth_authorization_error(hass: HomeAssistant) -> None: side_effect=AuthenticationException, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT @@ -401,6 +382,24 @@ async def test_reauth_connection_error(hass: HomeAssistant) -> None: assert result2["step_id"] == "authenticate" assert result2["errors"] == {"base": "cannot_connect"} + with patch( + "systembridgeconnector.websocket_client.WebSocketClient.connect", + ), patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + return_value=None, + ), patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], FIXTURE_AUTH_INPUT + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.FlowResultType.FORM + assert result3["step_id"] == "authenticate" + assert result3["errors"] == {"base": "cannot_connect"} + async def test_reauth_connection_closed_error(hass: HomeAssistant) -> None: """Test we show user form on connection error.""" @@ -418,6 +417,7 @@ async def test_reauth_connection_closed_error(hass: HomeAssistant) -> None: side_effect=ConnectionClosedException, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT @@ -450,10 +450,11 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: return_value=FIXTURE_DATA_RESPONSE, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ), patch( "homeassistant.components.system_bridge.async_setup_entry", return_value=True, - ) as mock_setup_entry: + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT ) @@ -462,8 +463,6 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.ABORT assert result2["reason"] == "reauth_successful" - assert len(mock_setup_entry.mock_calls) == 1 - async def test_zeroconf_flow(hass: HomeAssistant) -> None: """Test zeroconf flow.""" @@ -484,6 +483,7 @@ async def test_zeroconf_flow(hass: HomeAssistant) -> None: return_value=FIXTURE_DATA_RESPONSE, ), patch( "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, ), patch( "homeassistant.components.system_bridge.async_setup_entry", return_value=True, @@ -536,3 +536,28 @@ async def test_zeroconf_bad_zeroconf_info(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "unknown" + + +async def test_migration(hass: HomeAssistant) -> None: + """Test migration from system_bridge to system_bridge.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=FIXTURE_UUID, + data={ + CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN], + CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST], + CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT], + }, + version=1, + minor_version=1, + ) + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + # Check that the version has been updated and the api_key has been moved to token + assert config_entry.version == SystemBridgeConfigFlow.VERSION + assert config_entry.minor_version == SystemBridgeConfigFlow.MINOR_VERSION + assert CONF_API_KEY not in config_entry.data + assert config_entry.data[CONF_TOKEN] == FIXTURE_USER_INPUT[CONF_TOKEN] + assert config_entry.data == FIXTURE_USER_INPUT From 86039de3cdba99b44867f777ec51399f964a3d3c Mon Sep 17 00:00:00 2001 From: Alberto Geniola Date: Mon, 4 Mar 2024 11:39:13 +0100 Subject: [PATCH 0224/1691] Add local API support to elmax (#94392) * Add support for local (lan) panel integration * Fix merge conflicts * Remove executable flag from non-executable files * Fix tests * Update homeassistant/components/elmax/__init__.py Shorten comment Co-authored-by: Erik Montnemery * Fix typehint * Rename DummyPanel into DirectPanel * Update homeassistant/components/elmax/__init__.py Rewording Co-authored-by: Erik Montnemery * Update homeassistant/components/elmax/__init__.py Rewording Co-authored-by: Erik Montnemery * Refactor option step into menu step * Change requirement statement * Refactor dictionary key entries to use existing constants * Align step names to new constants * Align step names to new constants amd align tests * Align step names to new constants amd align tests * Align step names to new constants * Simplify logic to handle entire entry instead of a portion of the state * Simplify working mode checks * Add data_description dictionary to better explain SSL and FOLLOW_MDSN options * Add support for local (lan) panel integration * Fix merge conflicts * Remove executable flag from non-executable files * Fix tests * Update homeassistant/components/elmax/__init__.py Shorten comment Co-authored-by: Erik Montnemery * Fix typehint * Rename DummyPanel into DirectPanel * Update homeassistant/components/elmax/__init__.py Rewording Co-authored-by: Erik Montnemery * Update homeassistant/components/elmax/__init__.py Rewording Co-authored-by: Erik Montnemery * Refactor option step into menu step * Change requirement statement * Refactor dictionary key entries to use existing constants * Align step names to new constants * Align step names to new constants amd align tests * Align step names to new constants amd align tests * Align step names to new constants * Simplify logic to handle entire entry instead of a portion of the state * Simplify working mode checks * Add data_description dictionary to better explain SSL and FOLLOW_MDSN options * Add newline at end of file * Remove CONF_ELMAX_MODE_DIRECT_FOLLOW_MDNS option * Fix Ruff pre-check --------- Co-authored-by: Erik Montnemery --- homeassistant/components/elmax/__init__.py | 88 ++++- .../components/elmax/alarm_control_panel.py | 1 - .../components/elmax/binary_sensor.py | 1 - homeassistant/components/elmax/common.py | 130 ++++--- homeassistant/components/elmax/config_flow.py | 353 ++++++++++++++++-- homeassistant/components/elmax/const.py | 15 + homeassistant/components/elmax/cover.py | 1 - homeassistant/components/elmax/manifest.json | 7 +- homeassistant/components/elmax/strings.json | 32 +- homeassistant/components/elmax/switch.py | 1 - homeassistant/generated/zeroconf.py | 5 + tests/components/elmax/__init__.py | 8 + tests/components/elmax/conftest.py | 56 ++- .../elmax/fixtures/cloud/get_panel.json | 126 +++++++ .../elmax/fixtures/cloud/list_devices.json | 12 + .../elmax/fixtures/cloud/login.json | 8 + .../components/elmax/fixtures/direct/cert.pem | 22 ++ .../fixtures/direct/discovery_panel.json | 148 ++++++++ .../elmax/fixtures/direct/login.json | 3 + tests/components/elmax/test_config_flow.py | 331 +++++++++++++++- 20 files changed, 1242 insertions(+), 106 deletions(-) create mode 100644 tests/components/elmax/fixtures/cloud/get_panel.json create mode 100644 tests/components/elmax/fixtures/cloud/list_devices.json create mode 100644 tests/components/elmax/fixtures/cloud/login.json create mode 100644 tests/components/elmax/fixtures/direct/cert.pem create mode 100644 tests/components/elmax/fixtures/direct/discovery_panel.json create mode 100644 tests/components/elmax/fixtures/direct/login.json diff --git a/homeassistant/components/elmax/__init__.py b/homeassistant/components/elmax/__init__.py index 0c0a80b4958..95b0588e332 100644 --- a/homeassistant/components/elmax/__init__.py +++ b/homeassistant/components/elmax/__init__.py @@ -4,11 +4,28 @@ from __future__ import annotations from datetime import timedelta import logging +from elmax_api.exceptions import ElmaxBadLoginError +from elmax_api.http import Elmax, ElmaxLocal, GenericElmax +from elmax_api.model.panel import PanelEntry + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryAuthFailed -from .common import ElmaxCoordinator +from .common import ( + DirectPanel, + ElmaxCoordinator, + build_direct_ssl_context, + get_direct_api_url, +) from .const import ( + CONF_ELMAX_MODE, + CONF_ELMAX_MODE_CLOUD, + CONF_ELMAX_MODE_DIRECT, + CONF_ELMAX_MODE_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL, + CONF_ELMAX_MODE_DIRECT_SSL_CERT, CONF_ELMAX_PANEL_ID, CONF_ELMAX_PANEL_PIN, CONF_ELMAX_PASSWORD, @@ -21,17 +38,71 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +async def _load_elmax_panel_client( + entry: ConfigEntry, +) -> tuple[GenericElmax, PanelEntry]: + # Connection mode was not present in initial version, default to cloud if not set + mode = entry.data.get(CONF_ELMAX_MODE, CONF_ELMAX_MODE_CLOUD) + if mode == CONF_ELMAX_MODE_DIRECT: + client_api_url = get_direct_api_url( + host=entry.data[CONF_ELMAX_MODE_DIRECT_HOST], + port=entry.data[CONF_ELMAX_MODE_DIRECT_PORT], + use_ssl=entry.data[CONF_ELMAX_MODE_DIRECT_SSL], + ) + custom_ssl_context = None + custom_ssl_cert = entry.data.get(CONF_ELMAX_MODE_DIRECT_SSL_CERT) + if custom_ssl_cert: + custom_ssl_context = build_direct_ssl_context(cadata=custom_ssl_cert) + + client = ElmaxLocal( + panel_api_url=client_api_url, + panel_code=entry.data[CONF_ELMAX_PANEL_PIN], + ssl_context=custom_ssl_context, + ) + panel = DirectPanel(panel_uri=client_api_url) + else: + client = Elmax( + username=entry.data[CONF_ELMAX_USERNAME], + password=entry.data[CONF_ELMAX_PASSWORD], + ) + client.set_current_panel( + entry.data[CONF_ELMAX_PANEL_ID], entry.data[CONF_ELMAX_PANEL_PIN] + ) + # Make sure the panel is online and assigned to the current user + panel = await _check_cloud_panel_status(client, entry.data[CONF_ELMAX_PANEL_ID]) + + return client, panel + + +async def _check_cloud_panel_status(client: Elmax, panel_id: str) -> PanelEntry: + """Perform integrity checks against the cloud for panel-user association.""" + # Retrieve the panel online status first + panels = await client.list_control_panels() + panel = next((panel for panel in panels if panel.hash == panel_id), None) + + # If the panel is no longer available within the ones associated to that client, raise + # a config error as the user must reconfigure it in order to make it work again + if not panel: + raise ConfigEntryAuthFailed( + f"Panel ID {panel_id} is no longer linked to this user account" + ) + return panel + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up elmax-cloud from a config entry.""" + try: + client, panel = await _load_elmax_panel_client(entry) + except ElmaxBadLoginError as err: + raise ConfigEntryAuthFailed() from err + # Create the API client object and attempt a login, so that we immediately know # if there is something wrong with user credentials coordinator = ElmaxCoordinator( hass=hass, logger=_LOGGER, - username=entry.data[CONF_ELMAX_USERNAME], - password=entry.data[CONF_ELMAX_PASSWORD], - panel_id=entry.data[CONF_ELMAX_PANEL_ID], - panel_pin=entry.data[CONF_ELMAX_PANEL_PIN], + elmax_api_client=client, + panel=panel, name=f"Elmax Cloud {entry.entry_id}", update_interval=timedelta(seconds=POLLING_SECONDS), ) @@ -42,11 +113,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Store a global reference to the coordinator for later use hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + # Perform platform initialization. await hass.config_entries.async_forward_entry_setups(entry, ELMAX_PLATFORMS) return True +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle an options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, ELMAX_PLATFORMS) diff --git a/homeassistant/components/elmax/alarm_control_panel.py b/homeassistant/components/elmax/alarm_control_panel.py index 40c84efc60e..269cc989b51 100644 --- a/homeassistant/components/elmax/alarm_control_panel.py +++ b/homeassistant/components/elmax/alarm_control_panel.py @@ -39,7 +39,6 @@ async def async_setup_entry( # Otherwise, add all the entities we found entities = [ ElmaxArea( - panel=coordinator.panel_entry, elmax_device=area, panel_version=panel_status.release, coordinator=coordinator, diff --git a/homeassistant/components/elmax/binary_sensor.py b/homeassistant/components/elmax/binary_sensor.py index 0defbe464f9..5798b7ec59e 100644 --- a/homeassistant/components/elmax/binary_sensor.py +++ b/homeassistant/components/elmax/binary_sensor.py @@ -38,7 +38,6 @@ async def async_setup_entry( if zone.endpoint_id in known_devices: continue entity = ElmaxSensor( - panel=coordinator.panel_entry, elmax_device=zone, panel_version=panel_status.release, coordinator=coordinator, diff --git a/homeassistant/components/elmax/common.py b/homeassistant/components/elmax/common.py index 7cbc6f63596..6f91dae048d 100644 --- a/homeassistant/components/elmax/common.py +++ b/homeassistant/components/elmax/common.py @@ -1,10 +1,11 @@ """Elmax integration common classes and utilities.""" from __future__ import annotations -import asyncio +from asyncio import timeout from datetime import timedelta import logging from logging import Logger +import ssl from elmax_api.exceptions import ( ElmaxApiError, @@ -13,12 +14,14 @@ from elmax_api.exceptions import ( ElmaxNetworkError, ElmaxPanelBusyError, ) -from elmax_api.http import Elmax +from elmax_api.http import Elmax, GenericElmax from elmax_api.model.actuator import Actuator from elmax_api.model.area import Area from elmax_api.model.cover import Cover from elmax_api.model.endpoint import DeviceEndpoint from elmax_api.model.panel import PanelEntry, PanelStatus +from httpx import ConnectError, ConnectTimeout +from packaging import version from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError @@ -29,11 +32,50 @@ from homeassistant.helpers.update_coordinator import ( UpdateFailed, ) -from .const import DEFAULT_TIMEOUT, DOMAIN +from .const import ( + DEFAULT_TIMEOUT, + DOMAIN, + ELMAX_LOCAL_API_PATH, + MIN_APIV2_SUPPORTED_VERSION, +) _LOGGER = logging.getLogger(__name__) +def get_direct_api_url(host: str, port: int, use_ssl: bool) -> str: + """Return the direct API url given the base URI.""" + schema = "https" if use_ssl else "http" + return f"{schema}://{host}:{port}/{ELMAX_LOCAL_API_PATH}" + + +def build_direct_ssl_context(cadata: str) -> ssl.SSLContext: + """Create a custom SSL context for direct-api verification.""" + context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(cadata=cadata) + return context + + +def check_local_version_supported(api_version: str | None) -> bool: + """Check whether the given API version is supported.""" + if api_version is None: + return False + return version.parse(api_version) >= version.parse(MIN_APIV2_SUPPORTED_VERSION) + + +class DirectPanel(PanelEntry): + """Helper class for wrapping a directly accessed Elmax Panel.""" + + def __init__(self, panel_uri): + """Construct the object.""" + super().__init__(panel_uri, True, {}) + + def get_name_by_user(self, username: str) -> str: + """Return the panel name.""" + return f"Direct Panel {self.hash}" + + class ElmaxCoordinator(DataUpdateCoordinator[PanelStatus]): # pylint: disable=hass-enforce-coordinator-module """Coordinator helper to handle Elmax API polling.""" @@ -41,25 +83,21 @@ class ElmaxCoordinator(DataUpdateCoordinator[PanelStatus]): # pylint: disable=h self, hass: HomeAssistant, logger: Logger, - username: str, - password: str, - panel_id: str, - panel_pin: str, + elmax_api_client: GenericElmax, + panel: PanelEntry, name: str, update_interval: timedelta, ) -> None: """Instantiate the object.""" - self._client = Elmax(username=username, password=password) - self._panel_id = panel_id - self._panel_pin = panel_pin - self._panel_entry = None + self._client = elmax_api_client + self._panel_entry = panel self._state_by_endpoint = None super().__init__( hass=hass, logger=logger, name=name, update_interval=update_interval ) @property - def panel_entry(self) -> PanelEntry | None: + def panel_entry(self) -> PanelEntry: """Return the panel entry.""" return self._panel_entry @@ -92,54 +130,46 @@ class ElmaxCoordinator(DataUpdateCoordinator[PanelStatus]): # pylint: disable=h """Return the current http client being used by this instance.""" return self._client + @http_client.setter + def http_client(self, client: GenericElmax): + """Set the client library instance for Elmax API.""" + self._client = client + async def _async_update_data(self): try: - async with asyncio.timeout(DEFAULT_TIMEOUT): - # Retrieve the panel online status first - panels = await self._client.list_control_panels() - panel = next( - (panel for panel in panels if panel.hash == self._panel_id), None - ) + async with timeout(DEFAULT_TIMEOUT): + # The following command might fail in case of the panel is offline. + # We handle this case in the following exception blocks. + status = await self._client.get_current_panel_status() - # If the panel is no more available within the given. Raise config error as the user must - # reconfigure it in order to make it work again - if not panel: - raise ConfigEntryAuthFailed( - f"Panel ID {self._panel_id} is no more linked to this user" - " account" - ) - - self._panel_entry = panel - - # If the panel is online, proceed with fetching its state - # and return it right away - if panel.online: - status = await self._client.get_panel_status( - control_panel_id=panel.hash, pin=self._panel_pin - ) # type: PanelStatus - - # Store a dictionary for fast endpoint state access - self._state_by_endpoint = { - k.endpoint_id: k for k in status.all_endpoints - } - return status - - # Otherwise, return None. Listeners will know that this means the device is offline - return None + # Store a dictionary for fast endpoint state access + self._state_by_endpoint = { + k.endpoint_id: k for k in status.all_endpoints + } + return status except ElmaxBadPinError as err: raise ConfigEntryAuthFailed("Control panel pin was refused") from err except ElmaxBadLoginError as err: - raise ConfigEntryAuthFailed("Refused username/password") from err + raise ConfigEntryAuthFailed("Refused username/password/pin") from err except ElmaxApiError as err: raise UpdateFailed(f"Error communicating with ELMAX API: {err}") from err except ElmaxPanelBusyError as err: raise UpdateFailed( "Communication with the panel failed, as it is currently busy" ) from err - except ElmaxNetworkError as err: + except (ConnectError, ConnectTimeout, ElmaxNetworkError) as err: + if isinstance(self._client, Elmax): + raise UpdateFailed( + "A communication error has occurred. " + "Make sure HA can reach the internet and that " + "your firewall allows communication with the Meross Cloud." + ) from err + raise UpdateFailed( - "A network error occurred while communicating with Elmax cloud." + "A communication error has occurred. " + "Make sure the panel is online and that " + "your firewall allows communication with it." ) from err @@ -148,20 +178,18 @@ class ElmaxEntity(CoordinatorEntity[ElmaxCoordinator]): def __init__( self, - panel: PanelEntry, elmax_device: DeviceEndpoint, panel_version: str, coordinator: ElmaxCoordinator, ) -> None: """Construct the object.""" super().__init__(coordinator=coordinator) - self._panel = panel self._device = elmax_device self._attr_unique_id = elmax_device.endpoint_id self._attr_name = elmax_device.name self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, panel.hash)}, - name=panel.get_name_by_user( + identifiers={(DOMAIN, coordinator.panel_entry.hash)}, + name=coordinator.panel_entry.get_name_by_user( coordinator.http_client.get_authenticated_username() ), manufacturer="Elmax", @@ -172,4 +200,4 @@ class ElmaxEntity(CoordinatorEntity[ElmaxCoordinator]): @property def available(self) -> bool: """Return if entity is available.""" - return super().available and self._panel.online + return super().available and self.coordinator.panel_entry.online diff --git a/homeassistant/components/elmax/config_flow.py b/homeassistant/components/elmax/config_flow.py index ec3dece500d..da74e7138bd 100644 --- a/homeassistant/components/elmax/config_flow.py +++ b/homeassistant/components/elmax/config_flow.py @@ -6,20 +6,37 @@ import logging from typing import Any from elmax_api.exceptions import ElmaxBadLoginError, ElmaxBadPinError, ElmaxNetworkError -from elmax_api.http import Elmax -from elmax_api.model.panel import PanelEntry +from elmax_api.http import Elmax, ElmaxLocal, GenericElmax +from elmax_api.model.panel import PanelEntry, PanelStatus +import httpx import voluptuous as vol +from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult +from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError +from .common import ( + build_direct_ssl_context, + check_local_version_supported, + get_direct_api_url, +) from .const import ( + CONF_ELMAX_MODE, + CONF_ELMAX_MODE_CLOUD, + CONF_ELMAX_MODE_DIRECT, + CONF_ELMAX_MODE_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL, + CONF_ELMAX_MODE_DIRECT_SSL_CERT, CONF_ELMAX_PANEL_ID, CONF_ELMAX_PANEL_NAME, CONF_ELMAX_PANEL_PIN, CONF_ELMAX_PASSWORD, CONF_ELMAX_USERNAME, DOMAIN, + ELMAX_MODE_DIRECT_DEFAULT_HTTP_PORT, + ELMAX_MODE_DIRECT_DEFAULT_HTTPS_PORT, ) _LOGGER = logging.getLogger(__name__) @@ -39,6 +56,22 @@ REAUTH_FORM_SCHEMA = vol.Schema( } ) +DIRECT_SETUP_SCHEMA = vol.Schema( + { + vol.Required(CONF_ELMAX_MODE_DIRECT_HOST): str, + vol.Required(CONF_ELMAX_MODE_DIRECT_PORT, default=443): int, + vol.Required(CONF_ELMAX_MODE_DIRECT_SSL, default=True): bool, + vol.Required(CONF_ELMAX_PANEL_PIN): str, + } +) + +ZEROCONF_SETUP_SCHEMA = vol.Schema( + { + vol.Required(CONF_ELMAX_PANEL_PIN): str, + vol.Required(CONF_ELMAX_MODE_DIRECT_SSL, default=True): bool, + } +) + def _store_panel_by_name( panel: PanelEntry, username: str, panel_names: dict[str, str] @@ -59,38 +92,216 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 _client: Elmax - _username: str - _password: str + _selected_mode: str + _panel_pin: str + _panel_id: str + + # Direct API variables + _panel_direct_use_ssl: bool + _panel_direct_hostname: str + _panel_direct_port: int + _panel_direct_follow_mdns: bool + _panel_direct_ssl_cert: str | None + _panel_direct_http_port: int + _panel_direct_https_port: int + + # Cloud API variables + _cloud_username: str + _cloud_password: str + _reauth_cloud_username: str | None + _reauth_cloud_panelid: str | None + + # Panel selection variables _panels_schema: vol.Schema _panel_names: dict _entry: ConfigEntry | None async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Handle a flow initialized by the user.""" + ) -> FlowResult: + """Handle the flow initiated by the user.""" + return await self.async_step_choose_mode(user_input=user_input) + + async def async_step_choose_mode( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle local vs cloud mode selection step.""" + return self.async_show_menu( + step_id="choose_mode", + menu_options={ + CONF_ELMAX_MODE_CLOUD: "Connect to Elmax Panel via Elmax Cloud APIs", + CONF_ELMAX_MODE_DIRECT: "Connect to Elmax Panel via local/direct IP", + }, + ) + + async def _handle_direct_and_create_entry( + self, fallback_step_id: str, schema: vol.Schema + ) -> FlowResult: + return await self._test_direct_and_create_entry() + + async def _test_direct_and_create_entry(self): + """Test the direct connection to the Elmax panel and create and entry if successful.""" + ssl_context = None + self._panel_direct_ssl_cert = None + if self._panel_direct_use_ssl: + # Fetch the remote certificate. + # Local API is exposed via a self-signed SSL that we must add to our trust store. + self._panel_direct_ssl_cert = ( + await GenericElmax.retrieve_server_certificate( + hostname=self._panel_direct_hostname, + port=self._panel_direct_port, + ) + ) + ssl_context = build_direct_ssl_context(cadata=self._panel_direct_ssl_cert) + + # Attempt the connection to make sure the pin works. Also, take the chance to retrieve the panel ID via APIs. + client_api_url = get_direct_api_url( + host=self._panel_direct_hostname, + port=self._panel_direct_port, + use_ssl=self._panel_direct_use_ssl, + ) + client = ElmaxLocal( + panel_api_url=client_api_url, + panel_code=self._panel_pin, + ssl_context=ssl_context, + ) + try: + await client.login() + except (ElmaxNetworkError, httpx.ConnectError, httpx.ConnectTimeout): + return self.async_show_form( + step_id=CONF_ELMAX_MODE_DIRECT, + data_schema=DIRECT_SETUP_SCHEMA, + errors={"base": "network_error"}, + ) + except ElmaxBadLoginError: + return self.async_show_form( + step_id=CONF_ELMAX_MODE_DIRECT, + data_schema=DIRECT_SETUP_SCHEMA, + errors={"base": "invalid_auth"}, + ) + + # Retrieve the current panel status. If this succeeds, it means the + # setup did complete successfully. + panel_status: PanelStatus = await client.get_current_panel_status() + + # Make sure this is the only Elmax integration for this specific panel id. + await self.async_set_unique_id(panel_status.panel_id) + self._abort_if_unique_id_configured() + + return await self._check_unique_and_create_entry( + unique_id=panel_status.panel_id, + title=f"Elmax Direct {panel_status.panel_id}", + data={ + CONF_ELMAX_MODE: self._selected_mode, + CONF_ELMAX_MODE_DIRECT_HOST: self._panel_direct_hostname, + CONF_ELMAX_MODE_DIRECT_PORT: self._panel_direct_port, + CONF_ELMAX_MODE_DIRECT_SSL: self._panel_direct_use_ssl, + CONF_ELMAX_PANEL_PIN: self._panel_pin, + CONF_ELMAX_PANEL_ID: panel_status.panel_id, + CONF_ELMAX_MODE_DIRECT_SSL_CERT: self._panel_direct_ssl_cert, + }, + ) + + async def async_step_direct(self, user_input: dict[str, Any]) -> FlowResult: + """Handle the direct setup step.""" + self._selected_mode = CONF_ELMAX_MODE_CLOUD + if user_input is None: + return self.async_show_form( + step_id=CONF_ELMAX_MODE_DIRECT, + data_schema=DIRECT_SETUP_SCHEMA, + errors=None, + ) + + self._panel_direct_hostname = user_input[CONF_ELMAX_MODE_DIRECT_HOST] + self._panel_direct_port = user_input[CONF_ELMAX_MODE_DIRECT_PORT] + self._panel_direct_use_ssl = user_input[CONF_ELMAX_MODE_DIRECT_SSL] + self._panel_pin = user_input[CONF_ELMAX_PANEL_PIN] + self._panel_direct_follow_mdns = True + + tmp_schema = vol.Schema( + { + vol.Required( + CONF_ELMAX_MODE_DIRECT_HOST, default=self._panel_direct_hostname + ): str, + vol.Required( + CONF_ELMAX_MODE_DIRECT_PORT, default=self._panel_direct_port + ): int, + vol.Required( + CONF_ELMAX_MODE_DIRECT_SSL, default=self._panel_direct_use_ssl + ): bool, + vol.Required(CONF_ELMAX_PANEL_PIN, default=self._panel_pin): str, + } + ) + return await self._handle_direct_and_create_entry( + fallback_step_id=CONF_ELMAX_MODE_DIRECT, schema=tmp_schema + ) + + async def async_step_zeroconf_setup(self, user_input: dict[str, Any]) -> FlowResult: + """Handle the direct setup step triggered via zeroconf.""" + if user_input is None: + return self.async_show_form( + step_id="zeroconf_setup", + data_schema=ZEROCONF_SETUP_SCHEMA, + errors=None, + ) + self._panel_direct_use_ssl = user_input[CONF_ELMAX_MODE_DIRECT_SSL] + self._panel_direct_port = ( + self._panel_direct_https_port + if self._panel_direct_use_ssl + else self._panel_direct_http_port + ) + self._panel_pin = user_input[CONF_ELMAX_PANEL_PIN] + tmp_schema = vol.Schema( + { + vol.Required(CONF_ELMAX_PANEL_PIN, default=self._panel_pin): str, + vol.Required( + CONF_ELMAX_MODE_DIRECT_SSL, default=self._panel_direct_use_ssl + ): bool, + } + ) + return await self._handle_direct_and_create_entry( + fallback_step_id="zeroconf_setup", schema=tmp_schema + ) + + async def _check_unique_and_create_entry( + self, unique_id: str, title: str, data: Mapping[str, Any] + ) -> FlowResult: + # Make sure this is the only Elmax integration for this specific panel id. + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=title, + data=data, + ) + + async def async_step_cloud(self, user_input: dict[str, Any]) -> FlowResult: + """Handle the cloud setup flow.""" + self._selected_mode = CONF_ELMAX_MODE_CLOUD + # When invokes without parameters, show the login form. if user_input is None: - return self.async_show_form(step_id="user", data_schema=LOGIN_FORM_SCHEMA) - - username = user_input[CONF_ELMAX_USERNAME] - password = user_input[CONF_ELMAX_PASSWORD] + return self.async_show_form( + step_id=CONF_ELMAX_MODE_CLOUD, data_schema=LOGIN_FORM_SCHEMA, errors={} + ) # Otherwise, it means we are handling now the "submission" of the user form. # In this case, let's try to log in to the Elmax cloud and retrieve the available panels. + username = user_input[CONF_ELMAX_USERNAME] + password = user_input[CONF_ELMAX_PASSWORD] try: client = await self._async_login(username=username, password=password) except ElmaxBadLoginError: return self.async_show_form( - step_id="user", + step_id=CONF_ELMAX_MODE_CLOUD, data_schema=LOGIN_FORM_SCHEMA, errors={"base": "invalid_auth"}, ) except ElmaxNetworkError: _LOGGER.exception("A network error occurred") return self.async_show_form( - step_id="user", + step_id=CONF_ELMAX_MODE_CLOUD, data_schema=LOGIN_FORM_SCHEMA, errors={"base": "network_error"}, ) @@ -101,7 +312,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): # If no online panel was found, we display an error in the next UI. if not online_panels: return self.async_show_form( - step_id="user", + step_id=CONF_ELMAX_MODE_CLOUD, data_schema=LOGIN_FORM_SCHEMA, errors={"base": "no_panel_online"}, ) @@ -125,8 +336,8 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): } ) self._panels_schema = schema - self._username = username - self._password = password + self._cloud_username = username + self._cloud_password = password # If everything went OK, proceed to panel selection. return await self.async_step_panels(user_input=None) @@ -155,23 +366,27 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): await self._client.get_panel_status( control_panel_id=panel_id, pin=panel_pin ) - return self.async_create_entry( - title=f"Elmax {panel_name}", - data={ - CONF_ELMAX_PANEL_ID: panel_id, - CONF_ELMAX_PANEL_PIN: panel_pin, - CONF_ELMAX_USERNAME: self._username, - CONF_ELMAX_PASSWORD: self._password, - }, - ) except ElmaxBadPinError: errors["base"] = "invalid_pin" except Exception: # pylint: disable=broad-except _LOGGER.exception("Error occurred") errors["base"] = "unknown" - return self.async_show_form( - step_id="panels", data_schema=self._panels_schema, errors=errors + if errors: + return self.async_show_form( + step_id="panels", data_schema=self._panels_schema, errors=errors + ) + + return await self._check_unique_and_create_entry( + unique_id=panel_id, + title=f"Elmax cloud {panel_name}", + data={ + CONF_ELMAX_MODE: CONF_ELMAX_MODE_CLOUD, + CONF_ELMAX_PANEL_ID: panel_id, + CONF_ELMAX_PANEL_PIN: panel_pin, + CONF_ELMAX_USERNAME: self._cloud_username, + CONF_ELMAX_PASSWORD: self._cloud_password, + }, ) async def async_step_reauth( @@ -179,6 +394,8 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + self._reauth_cloud_username = entry_data.get(CONF_ELMAX_USERNAME) + self._reauth_cloud_panelid = entry_data.get(CONF_ELMAX_PANEL_ID) return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( @@ -190,6 +407,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): username = user_input[CONF_ELMAX_USERNAME] password = user_input[CONF_ELMAX_PASSWORD] panel_pin = user_input[CONF_ELMAX_PANEL_PIN] + await self.async_set_unique_id(self._reauth_cloud_panelid) # Handle authentication, make sure the panel we are re-authenticating against is listed among results # and verify its pin is correct. @@ -238,6 +456,89 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): step_id="reauth_confirm", data_schema=REAUTH_FORM_SCHEMA, errors=errors ) + async def _async_handle_entry_match( + self, + local_id: str, + remote_id: str | None, + host: str, + https_port: int, + http_port: int, + ) -> FlowResult | None: + # Look for another entry with the same PANEL_ID (local or remote). + # If there already is a matching panel, take the change to notify the Coordinator + # so that it uses the newly discovered IP address. This mitigates the issues + # arising with DHCP and IP changes of the panels. + for entry in self._async_current_entries(include_ignore=False): + if entry.data[CONF_ELMAX_PANEL_ID] in (local_id, remote_id): + # If the discovery finds another entry with the same ID, skip the notification. + # However, if the discovery finds a new host for a panel that was already registered + # for a given host (leave PORT comparison aside as we don't want to get notified twice + # for HTTP and HTTPS), update the entry so that the integration "follows" the DHCP IP. + if ( + entry.data.get(CONF_ELMAX_MODE, CONF_ELMAX_MODE_CLOUD) + == CONF_ELMAX_MODE_DIRECT + and entry.data[CONF_ELMAX_MODE_DIRECT_HOST] != host + ): + new_data: dict[str, Any] = {} + new_data.update(entry.data) + new_data[CONF_ELMAX_MODE_DIRECT_HOST] = host + new_data[CONF_ELMAX_MODE_DIRECT_PORT] = ( + https_port + if entry.data[CONF_ELMAX_MODE_DIRECT_SSL] + else http_port + ) + self.hass.config_entries.async_update_entry( + entry, unique_id=entry.unique_id, data=new_data + ) + # Abort the configuration, as there already is an entry for this PANEL-ID. + return self.async_abort(reason="already_configured") + return None + + async def async_step_zeroconf( + self, discovery_info: ZeroconfServiceInfo + ) -> FlowResult: + """Handle device found via zeroconf.""" + host = discovery_info.host + https_port = ( + int(discovery_info.port) + if discovery_info.port is not None + else ELMAX_MODE_DIRECT_DEFAULT_HTTPS_PORT + ) + plain_http_port = discovery_info.properties.get( + "http_port", ELMAX_MODE_DIRECT_DEFAULT_HTTP_PORT + ) + plain_http_port = int(plain_http_port) + local_id = discovery_info.properties.get("idl") + remote_id = discovery_info.properties.get("idr") + v2api_version = discovery_info.properties.get("v2") + + # Only deal with panels exposing v2 version + if not check_local_version_supported(v2api_version): + return self.async_abort(reason="not_supported") + + # Handle the discovered panel info. This is useful especially if the panel + # changes its IP address while remaining perfectly configured. + if ( + local_id is not None + and ( + abort_result := await self._async_handle_entry_match( + local_id, remote_id, host, https_port, plain_http_port + ) + ) + is not None + ): + return abort_result + + self._selected_mode = CONF_ELMAX_MODE_DIRECT + self._panel_direct_hostname = host + self._panel_direct_https_port = https_port + self._panel_direct_http_port = plain_http_port + self._panel_direct_follow_mdns = True + + return self.async_show_form( + step_id="zeroconf_setup", data_schema=ZEROCONF_SETUP_SCHEMA + ) + @staticmethod async def _async_login(username: str, password: str) -> Elmax: """Log in to the Elmax cloud and return the http client.""" diff --git a/homeassistant/components/elmax/const.py b/homeassistant/components/elmax/const.py index cd2c73002a4..8ac5fbdad51 100644 --- a/homeassistant/components/elmax/const.py +++ b/homeassistant/components/elmax/const.py @@ -5,9 +5,21 @@ DOMAIN = "elmax" CONF_ELMAX_USERNAME = "username" CONF_ELMAX_PASSWORD = "password" CONF_ELMAX_PANEL_ID = "panel_id" +CONF_ELMAX_PANEL_LOCAL_ID = "panel_local_id" +CONF_ELMAX_PANEL_REMOTE_ID = "panel_remote_id" CONF_ELMAX_PANEL_PIN = "panel_pin" CONF_ELMAX_PANEL_NAME = "panel_name" +CONF_ELMAX_MODE = "mode" +CONF_ELMAX_MODE_CLOUD = "cloud" +CONF_ELMAX_MODE_DIRECT = "direct" +CONF_ELMAX_MODE_DIRECT_HOST = "panel_api_host" +CONF_ELMAX_MODE_DIRECT_PORT = "panel_api_port" +CONF_ELMAX_MODE_DIRECT_SSL = "use_ssl" +CONF_ELMAX_MODE_DIRECT_SSL_CERT = "ssl_cert" + +ELMAX_LOCAL_API_PATH = "api/v2" + CONF_CONFIG_ENTRY_ID = "config_entry_id" CONF_ENDPOINT_ID = "endpoint_id" @@ -18,5 +30,8 @@ ELMAX_PLATFORMS = [ Platform.COVER, ] +ELMAX_MODE_DIRECT_DEFAULT_HTTPS_PORT = 443 +ELMAX_MODE_DIRECT_DEFAULT_HTTP_PORT = 80 POLLING_SECONDS = 30 DEFAULT_TIMEOUT = 10.0 +MIN_APIV2_SUPPORTED_VERSION = "4.9.13" diff --git a/homeassistant/components/elmax/cover.py b/homeassistant/components/elmax/cover.py index e05b17b9171..5f161c0b279 100644 --- a/homeassistant/components/elmax/cover.py +++ b/homeassistant/components/elmax/cover.py @@ -49,7 +49,6 @@ async def async_setup_entry( if cover.endpoint_id in known_devices: continue entity = ElmaxCover( - panel=coordinator.panel_entry, elmax_device=cover, panel_version=panel_status.release, coordinator=coordinator, diff --git a/homeassistant/components/elmax/manifest.json b/homeassistant/components/elmax/manifest.json index dfb90763c83..181b1c8a882 100644 --- a/homeassistant/components/elmax/manifest.json +++ b/homeassistant/components/elmax/manifest.json @@ -6,5 +6,10 @@ "documentation": "https://www.home-assistant.io/integrations/elmax", "iot_class": "cloud_polling", "loggers": ["elmax_api"], - "requirements": ["elmax-api==0.0.4"] + "requirements": ["elmax-api==0.0.4"], + "zeroconf": [ + { + "type": "_elmax-ssl._tcp.local." + } + ] } diff --git a/homeassistant/components/elmax/strings.json b/homeassistant/components/elmax/strings.json index 4bc705adfbe..17cdaac0bb8 100644 --- a/homeassistant/components/elmax/strings.json +++ b/homeassistant/components/elmax/strings.json @@ -1,13 +1,42 @@ { "config": { "step": { - "user": { + "choose_mode": { + "description": "Please choose the connection mode to Elmax panels.", + "menu_options": { + "cloud": "Connect to Elmax Panel via Elmax Cloud APIs", + "direct": "Connect to Elmax Panel via local/direct IP" + } + }, + "cloud": { "description": "Please login to the Elmax cloud using your credentials", "data": { "password": "[%key:common::config_flow::data::password%]", "username": "[%key:common::config_flow::data::username%]" } }, + "zeroconf_setup": { + "description": "Configure discovered local Elmax panel", + "data": { + "panel_pin": "Panel PIN code", + "use_ssl": "Use SSL" + }, + "data_description": { + "use_ssl": "Whether or not using strict SSL checks. Disable if the panel does not expose a valid SSL certificate or if SSL communication is unsupported by the panel you are connecting to." + } + }, + "direct": { + "description": "Specify the Elmax panel connection parameters below.", + "data": { + "panel_api_host": "Panel API Hostname or IP", + "panel_api_port": "Panel API port", + "use_ssl": "Use SSL", + "panel_pin": "Panel PIN code" + }, + "data_description": { + "use_ssl": "Whether or not using strict SSL checks. Disable if the panel does not expose a valid SSL certificate or if SSL communication is unsupported by the panel you are connecting to." + } + }, "panels": { "description": "Select which panel you would like to control with this integration. Please note that the panel must be ON in order to be configured.", "data": { @@ -30,6 +59,7 @@ "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "network_error": "A network error occurred", "invalid_pin": "The provided pin is invalid", + "invalid_mode": "Invalid or unsupported mode", "reauth_panel_disappeared": "The given panel is no longer associated to this user. Please log in using an account associated to this panel.", "unknown": "[%key:common::config_flow::error::unknown%]" }, diff --git a/homeassistant/components/elmax/switch.py b/homeassistant/components/elmax/switch.py index 877330892e5..5f3ca4aea7c 100644 --- a/homeassistant/components/elmax/switch.py +++ b/homeassistant/components/elmax/switch.py @@ -40,7 +40,6 @@ async def async_setup_entry( if actuator.endpoint_id in known_devices: continue entity = ElmaxSwitch( - panel=coordinator.panel_entry, elmax_device=actuator, panel_version=panel_status.release, coordinator=coordinator, diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 0f16977097d..baf922cdc99 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -411,6 +411,11 @@ ZEROCONF = { "domain": "elgato", }, ], + "_elmax-ssl._tcp.local.": [ + { + "domain": "elmax", + }, + ], "_enphase-envoy._tcp.local.": [ { "domain": "enphase_envoy", diff --git a/tests/components/elmax/__init__.py b/tests/components/elmax/__init__.py index cf1bce356c7..6d2e2333560 100644 --- a/tests/components/elmax/__init__.py +++ b/tests/components/elmax/__init__.py @@ -1,4 +1,5 @@ """Tests for the Elmax component.""" +from tests.common import load_fixture MOCK_USER_JWT = ( "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" @@ -12,4 +13,11 @@ MOCK_USER_ID = "1b11bb11bbb11111b1b11b1b" MOCK_PANEL_ID = "2db3dae30b9102de4d078706f94d0708" MOCK_PANEL_NAME = "Test Panel Name" MOCK_PANEL_PIN = "000000" +MOCK_WRONG_PANEL_PIN = "000000" MOCK_PASSWORD = "password" +MOCK_DIRECT_HOST = "1.1.1.1" +MOCK_DIRECT_HOST_CHANGED = "2.2.2.2" +MOCK_DIRECT_PORT = 443 +MOCK_DIRECT_SSL = True +MOCK_DIRECT_CERT = load_fixture("direct/cert.pem", "elmax") +MOCK_DIRECT_FOLLOW_MDNS = True diff --git a/tests/components/elmax/conftest.py b/tests/components/elmax/conftest.py index 70e3af76702..85e4902b010 100644 --- a/tests/components/elmax/conftest.py +++ b/tests/components/elmax/conftest.py @@ -1,5 +1,6 @@ """Configuration for Elmax tests.""" import json +from unittest.mock import patch from elmax_api.constants import ( BASE_URL, @@ -11,25 +12,35 @@ from httpx import Response import pytest import respx -from . import MOCK_PANEL_ID, MOCK_PANEL_PIN +from . import ( + MOCK_DIRECT_HOST, + MOCK_DIRECT_PORT, + MOCK_DIRECT_SSL, + MOCK_PANEL_ID, + MOCK_PANEL_PIN, +) from tests.common import load_fixture +MOCK_DIRECT_BASE_URI = ( + f"{'https' if MOCK_DIRECT_SSL else 'http'}://{MOCK_DIRECT_HOST}:{MOCK_DIRECT_PORT}" +) + @pytest.fixture(autouse=True) -def httpx_mock_fixture(requests_mock): - """Configure httpx fixture.""" +def httpx_mock_cloud_fixture(requests_mock): + """Configure httpx fixture for cloud API communication.""" with respx.mock(base_url=BASE_URL, assert_all_called=False) as respx_mock: # Mock Login POST. login_route = respx_mock.post(f"/{ENDPOINT_LOGIN}", name="login") login_route.return_value = Response( - 200, json=json.loads(load_fixture("login.json", "elmax")) + 200, json=json.loads(load_fixture("cloud/login.json", "elmax")) ) # Mock Device list GET. list_devices_route = respx_mock.get(f"/{ENDPOINT_DEVICES}", name="list_devices") list_devices_route.return_value = Response( - 200, json=json.loads(load_fixture("list_devices.json", "elmax")) + 200, json=json.loads(load_fixture("cloud/list_devices.json", "elmax")) ) # Mock Panel GET. @@ -37,7 +48,40 @@ def httpx_mock_fixture(requests_mock): f"/{ENDPOINT_DISCOVERY}/{MOCK_PANEL_ID}/{MOCK_PANEL_PIN}", name="get_panel" ) get_panel_route.return_value = Response( - 200, json=json.loads(load_fixture("get_panel.json", "elmax")) + 200, json=json.loads(load_fixture("cloud/get_panel.json", "elmax")) ) yield respx_mock + + +@pytest.fixture(autouse=True) +def httpx_mock_direct_fixture(requests_mock): + """Configure httpx fixture for direct Panel-API communication.""" + with respx.mock( + base_url=MOCK_DIRECT_BASE_URI, assert_all_called=False + ) as respx_mock: + # Mock Login POST. + login_route = respx_mock.post(f"/api/v2/{ENDPOINT_LOGIN}", name="login") + login_route.return_value = Response( + 200, json=json.loads(load_fixture("direct/login.json", "elmax")) + ) + + # Mock Device list GET. + list_devices_route = respx_mock.get( + f"/api/v2/{ENDPOINT_DISCOVERY}", name="discovery_panel" + ) + list_devices_route.return_value = Response( + 200, json=json.loads(load_fixture("direct/discovery_panel.json", "elmax")) + ) + + yield respx_mock + + +@pytest.fixture(autouse=True) +def elmax_mock_direct_cert(requests_mock): + """Patch elmax library to return a specific PEM for SSL communication.""" + with patch( + "elmax_api.http.GenericElmax.retrieve_server_certificate", + return_value=load_fixture("direct/cert.pem", "elmax"), + ) as patched_ssl_get_cert: + yield patched_ssl_get_cert diff --git a/tests/components/elmax/fixtures/cloud/get_panel.json b/tests/components/elmax/fixtures/cloud/get_panel.json new file mode 100644 index 00000000000..b97ab3b6c30 --- /dev/null +++ b/tests/components/elmax/fixtures/cloud/get_panel.json @@ -0,0 +1,126 @@ +{ + "release": 11.7, + "tappFeature": true, + "sceneFeature": true, + "zone": [ + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-zona-0", + "visibile": true, + "indice": 0, + "nome": "Feed zone 0", + "aperta": false, + "esclusa": false + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-zona-1", + "visibile": true, + "indice": 1, + "nome": "Feed Zone 1", + "aperta": false, + "esclusa": false + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-zona-2", + "visibile": true, + "indice": 2, + "nome": "Feed Zone 2", + "aperta": false, + "esclusa": false + } + ], + "uscite": [ + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-uscita-0", + "visibile": true, + "indice": 0, + "nome": "Actuator 0", + "aperta": false + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-uscita-1", + "visibile": true, + "indice": 1, + "nome": "Actuator 1", + "aperta": false + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-uscita-2", + "visibile": true, + "indice": 2, + "nome": "Actuator 2", + "aperta": true + } + ], + "aree": [ + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-area-0", + "visibile": true, + "indice": 0, + "nome": "AREA 0", + "statiDisponibili": [0, 1, 2, 3, 4], + "statiSessioneDisponibili": [0, 1, 2, 3], + "stato": 0, + "statoSessione": 0 + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-area-1", + "visibile": true, + "indice": 1, + "nome": "AREA 1", + "statiDisponibili": [0, 1, 2, 3, 4], + "statiSessioneDisponibili": [0, 1, 2, 3], + "stato": 0, + "statoSessione": 0 + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-area-2", + "visibile": false, + "indice": 2, + "nome": "AREA 2", + "statiDisponibili": [0, 1, 2, 3, 4], + "statiSessioneDisponibili": [0, 1, 2, 3], + "stato": 0, + "statoSessione": 0 + } + ], + "tapparelle": [ + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-tapparella-0", + "visibile": true, + "indice": 0, + "stato": "stop", + "posizione": 100, + "nome": "Cover 0" + } + ], + "gruppi": [ + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-gruppo-0", + "visibile": true, + "indice": 0, + "nome": "Group 0" + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-gruppo-1", + "visibile": false, + "indice": 1, + "nome": "Group 1" + } + ], + "scenari": [ + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-scenario-0", + "visibile": true, + "indice": 0, + "nome": "Automation 0" + }, + { + "endpointId": "2db3dae30b9102de4d078706f94d0708-scenario-2", + "visibile": true, + "indice": 2, + "nome": "Automation 2" + } + ], + "utente": "this.is@test.com", + "centrale": "2db3dae30b9102de4d078706f94d0708" +} diff --git a/tests/components/elmax/fixtures/cloud/list_devices.json b/tests/components/elmax/fixtures/cloud/list_devices.json new file mode 100644 index 00000000000..9a3091f371d --- /dev/null +++ b/tests/components/elmax/fixtures/cloud/list_devices.json @@ -0,0 +1,12 @@ +[ + { + "centrale_online": true, + "hash": "2db3dae30b9102de4d078706f94d0708", + "username": [{ "name": "this.is@test.com", "label": "Test Panel Name" }] + }, + { + "centrale_online": true, + "hash": "d8e8fca2dc0f896fd7cb4cb0031ba249", + "username": [{ "name": "this.is@test.com", "label": "Test Panel Name" }] + } +] diff --git a/tests/components/elmax/fixtures/cloud/login.json b/tests/components/elmax/fixtures/cloud/login.json new file mode 100644 index 00000000000..87b1af3f295 --- /dev/null +++ b/tests/components/elmax/fixtures/cloud/login.json @@ -0,0 +1,8 @@ +{ + "token": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIxYjExYmIxMWJiYjExMTExYjFiMTFiMWIiLCJlbWFpbCI6InRoaXMuaXNAdGVzdC5jb20iLCJyb2xlIjoidXNlciIsImlhdCI6MTYzNjE5OTk5OCwiZXhwIjoxNjM2MjM1OTk4fQ.1C7lXuKyX1HEGOfMxNwxJ2n-CjoW4rwvNRITQxLICv0", + "user": { + "_id": "1b11bb11bbb11111b1b11b1b", + "email": "this.is@test.com", + "role": "user" + } +} diff --git a/tests/components/elmax/fixtures/direct/cert.pem b/tests/components/elmax/fixtures/direct/cert.pem new file mode 100644 index 00000000000..f91abbf791c --- /dev/null +++ b/tests/components/elmax/fixtures/direct/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDozCCAougAwIBAgIUQw+nCBfAnisI86E/KS24OpJl6oQwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCSVQxDzANBgNVBAgMBk1pbGFubzEPMA0GA1UEBwwGTWls +YW5vMRcwFQYDVQQKDA5BbGJlcnRvR2VuaW9sYTEXMBUGA1UEAwwOYWxiZXJ0b2dl +bmlvbGEwHhcNMjIxMTEyMTc1MzE0WhcNMjMxMTEyMTc1MzE0WjBhMQswCQYDVQQG +EwJJVDEPMA0GA1UECAwGTWlsYW5vMQ8wDQYDVQQHDAZNaWxhbm8xFzAVBgNVBAoM +DkFsYmVydG9HZW5pb2xhMRcwFQYDVQQDDA5hbGJlcnRvZ2VuaW9sYTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKlyvbpb3IqrKsmNe3WLscXWOm1zWuZ +OECukWl9NFep1v7H2VgH8Vc5/Lz8GyFIK6f4auq3E8Cv3AuDH3Z9q8sxN4E4vh6Q +zkFqyBa4yAXyaN6AT/ZgmZTbd4KUg+AHecGeDdedbDRc7s8bPDILcQM/S49RSnnS +IYivDnf3uByCPjvU2+/JRUvrB+rlL3tvUt/H+In8uRd01cBAx3GQLN/eqmkgUhiy +/eI3r7g5goxyCZpy6uyQEfN1CYWOpoIdL9rAEwwvrT+zK7iPqxCN3N0xQNvVpNzE +ifTONUPq+JPxO0SIP3Ro7rSeNSoe1O309qb7kpi5G/Zt7u3nRoiL1zUCAwEAAaNT +MFEwHQYDVR0OBBYEFIceFAyZ62kgZZqVJ4cLQCMbproRMB8GA1UdIwQYMBaAFIce +FAyZ62kgZZqVJ4cLQCMbproRMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACHt8GY2FAruQ4Xa1JIMFHzGsqW8OMg6M1Ntrclu7MmST7T35VyCzBfB +QUwasn8bp/XHTZSmJB8RndYRKslATHSMyWhqjGCBNoSI8ljJgn9YLopRnAwEKQ6P +sqRl6/uMhwC587nIG1GJGWx2SbxrqoTcy39QmK0vHZfCVctzREZLaWGI2rtiQkVZ +mt3A0aQck+KQPlCIac14Z6lZJLJ4wSq6x7gjQAFA9uQfbnTaerOGgDTnhwsIrON3 +TkSZ0dFgvt3lnbpO7fa8nPhdKFgzWZzymAjv3vsAxiboDE3LPn9BulxEkC+IFf5Q +6n+BK6Ogu16GZX4zUKeVdF089opux64= +-----END CERTIFICATE----- diff --git a/tests/components/elmax/fixtures/direct/discovery_panel.json b/tests/components/elmax/fixtures/direct/discovery_panel.json new file mode 100644 index 00000000000..423cb64052a --- /dev/null +++ b/tests/components/elmax/fixtures/direct/discovery_panel.json @@ -0,0 +1,148 @@ +{ + "release": "PHANTOM64PRO_GSM 11.9.844", + "tappFeature": true, + "sceneFeature": true, + "zone": [ + { + "endpointId": "13762559c53cd093171-zona-0", + "visibile": true, + "indice": 0, + "aperta": true, + "esclusa": false, + "nome": "ZONA 01" + }, + { + "endpointId": "13762559c53cd093171-zona-1", + "visibile": true, + "indice": 1, + "aperta": true, + "esclusa": false, + "nome": "ZONA 02e" + }, + { + "endpointId": "13762559c53cd093171-zona-2", + "visibile": true, + "indice": 2, + "aperta": true, + "esclusa": false, + "nome": "ZONA 03a" + }, + { + "endpointId": "13762559c53cd093171-zona-3", + "visibile": true, + "indice": 3, + "aperta": true, + "esclusa": false, + "nome": "ZONA 04" + }, + { + "endpointId": "13762559c53cd093171-zona-4", + "visibile": true, + "indice": 4, + "aperta": true, + "esclusa": false, + "nome": "ZONA 05" + }, + { + "endpointId": "13762559c53cd093171-zona-5", + "visibile": true, + "indice": 5, + "aperta": true, + "esclusa": false, + "nome": "ZONA 06" + }, + { + "endpointId": "13762559c53cd093171-zona-6", + "visibile": true, + "indice": 6, + "aperta": true, + "esclusa": false, + "nome": "ZONA 07" + }, + { + "endpointId": "13762559c53cd093171-zona-7", + "visibile": true, + "indice": 7, + "aperta": true, + "esclusa": false, + "nome": "ZONA 08" + } + ], + "uscite": [ + { + "endpointId": "13762559c53cd093171-uscita-1", + "visibile": true, + "indice": 1, + "aperta": true, + "nome": "USCITA 02" + } + ], + "aree": [ + { + "endpointId": "13762559c53cd093171-area-0", + "visibile": true, + "indice": 0, + "statiDisponibili": [0, 1, 2, 3, 4], + "statiSessioneDisponibili": [0, 1, 2, 3], + "stato": 0, + "statoSessione": 1, + "zoneBmask": "0700000000000000", + "nome": "AREA 1" + }, + { + "endpointId": "13762559c53cd093171-area-1", + "visibile": true, + "indice": 1, + "statiDisponibili": [0, 1, 2, 3, 4], + "statiSessioneDisponibili": [0, 1, 2, 3], + "stato": 0, + "statoSessione": 1, + "zoneBmask": "3800000000000000", + "nome": "AREA 2" + }, + { + "endpointId": "13762559c53cd093171-area-2", + "visibile": true, + "indice": 2, + "statiDisponibili": [0, 1, 2, 3, 4], + "statiSessioneDisponibili": [0, 1, 2, 3], + "stato": 0, + "statoSessione": 1, + "zoneBmask": "C000000000000000", + "nome": "AREA 3" + } + ], + "tapparelle": [ + { + "endpointId": "13762559c53cd093171-tapparella-0", + "visibile": true, + "indice": 0, + "stato": "stop", + "posizione": 0, + "nome": "ESPAN.DOM.01" + } + ], + "gruppi": [ + { + "endpointId": "13762559c53cd093171-gruppo-1", + "visibile": true, + "indice": 1, + "nome": "GRUPPOUSC02" + } + ], + "scenari": [ + { + "endpointId": "13762559c53cd093171-scenario-1", + "visibile": true, + "indice": 1, + "nome": "SCENARIO02" + }, + { + "endpointId": "13762559c53cd093171-scenario-2", + "visibile": true, + "indice": 2, + "nome": "SCENARIO03" + } + ], + "datetime": "19:16:44 23/10/2022" +} diff --git a/tests/components/elmax/fixtures/direct/login.json b/tests/components/elmax/fixtures/direct/login.json new file mode 100644 index 00000000000..5ca1e8cb1b8 --- /dev/null +++ b/tests/components/elmax/fixtures/direct/login.json @@ -0,0 +1,3 @@ +{ + "token": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImNhcGFiaWxpdGllcyI6eyJ6b25lIjoiMTExMTExMTEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInVzYyI6IjAxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwiaW5kaWNlIjowLCJhcmVlIjo3LCJjYW0iOjAsInRhcHAiOjEsImdydXBwaSI6Miwic2NlbmFyaSI6Nn0sImlhdCI6MTY2NjU0NDYzMywiZXhwIjoxNTY2NTQ4MjM0fQ.0N50aK8VrCBvVZuLf2AzLxH96PFES7gql69URKb50cA" +} diff --git a/tests/components/elmax/test_config_flow.py b/tests/components/elmax/test_config_flow.py index d2f8d9841d4..ed23d3b8fb5 100644 --- a/tests/components/elmax/test_config_flow.py +++ b/tests/components/elmax/test_config_flow.py @@ -4,7 +4,15 @@ from unittest.mock import patch from elmax_api.exceptions import ElmaxBadLoginError, ElmaxBadPinError, ElmaxNetworkError from homeassistant import config_entries, data_entry_flow +from homeassistant.components import zeroconf from homeassistant.components.elmax.const import ( + CONF_ELMAX_MODE, + CONF_ELMAX_MODE_CLOUD, + CONF_ELMAX_MODE_DIRECT, + CONF_ELMAX_MODE_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL, + CONF_ELMAX_MODE_DIRECT_SSL_CERT, CONF_ELMAX_PANEL_ID, CONF_ELMAX_PANEL_NAME, CONF_ELMAX_PANEL_PIN, @@ -16,29 +24,122 @@ from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.core import HomeAssistant from . import ( + MOCK_DIRECT_CERT, + MOCK_DIRECT_HOST, + MOCK_DIRECT_HOST_CHANGED, + MOCK_DIRECT_PORT, + MOCK_DIRECT_SSL, MOCK_PANEL_ID, MOCK_PANEL_NAME, MOCK_PANEL_PIN, MOCK_PASSWORD, MOCK_USERNAME, + MOCK_WRONG_PANEL_PIN, ) from tests.common import MockConfigEntry +MOCK_ZEROCONF_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( + ip_address=MOCK_DIRECT_HOST, + ip_addresses=[MOCK_DIRECT_HOST], + hostname="VideoBox.local", + name="VideoBox", + port=443, + properties={ + "idl": MOCK_PANEL_ID, + "idr": MOCK_PANEL_ID, + "v1": "PHANTOM64PRO_GSM 11.9.844", + "v2": "4.9.13", + }, + type="_elmax-ssl._tcp", +) +MOCK_ZEROCONF_DISCOVERY_CHANGED_INFO = zeroconf.ZeroconfServiceInfo( + ip_address=MOCK_DIRECT_HOST_CHANGED, + ip_addresses=[MOCK_DIRECT_HOST_CHANGED], + hostname="VideoBox.local", + name="VideoBox", + port=443, + properties={ + "idl": MOCK_PANEL_ID, + "idr": MOCK_PANEL_ID, + "v1": "PHANTOM64PRO_GSM 11.9.844", + "v2": "4.9.13", + }, + type="_elmax-ssl._tcp", +) +MOCK_ZEROCONF_DISCOVERY_INFO_NOT_SUPPORTED = zeroconf.ZeroconfServiceInfo( + ip_address=MOCK_DIRECT_HOST, + ip_addresses=[MOCK_DIRECT_HOST], + hostname="VideoBox.local", + name="VideoBox", + port=443, + properties={ + "idl": MOCK_PANEL_ID, + "idr": MOCK_PANEL_ID, + "v1": "PHANTOM64PRO_GSM 11.9.844", + }, + type="_elmax-ssl._tcp", +) CONF_POLLING = "polling" -async def test_show_form(hass: HomeAssistant) -> None: +async def test_show_menu(hass: HomeAssistant) -> None: """Test that the form is served with no input.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "user" + assert result["type"] == data_entry_flow.FlowResultType.MENU + assert result["step_id"] == "choose_mode" -async def test_standard_setup(hass: HomeAssistant) -> None: - """Test the standard setup case.""" +async def test_direct_setup(hass: HomeAssistant) -> None: + """Test the standard direct setup case.""" + show_form_result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch( + "homeassistant.components.elmax.async_setup_entry", + return_value=True, + ): + set_mode_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_DIRECT}, + ) + result = await hass.config_entries.flow.async_configure( + set_mode_result["flow_id"], + { + CONF_ELMAX_MODE_DIRECT_HOST: MOCK_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT: MOCK_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL: MOCK_DIRECT_SSL, + CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN, + }, + ) + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + + +async def test_direct_show_form(hass: HomeAssistant) -> None: + """Test the standard direct show form case.""" + show_form_result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch( + "homeassistant.components.elmax.async_setup_entry", + return_value=True, + ): + set_mode_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + ) + result = await hass.config_entries.flow.async_configure( + set_mode_result["flow_id"], {"next_step_id": CONF_ELMAX_MODE_DIRECT} + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == CONF_ELMAX_MODE_DIRECT + assert result["errors"] is None + + +async def test_cloud_setup(hass: HomeAssistant) -> None: + """Test the standard cloud setup case.""" # Setup once. show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -47,6 +148,10 @@ async def test_standard_setup(hass: HomeAssistant) -> None: "homeassistant.components.elmax.async_setup_entry", return_value=True, ): + show_form_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) login_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], { @@ -65,7 +170,131 @@ async def test_standard_setup(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY -async def test_one_config_allowed(hass: HomeAssistant) -> None: +async def test_zeroconf_form_setup_api_not_supported(hass): + """Test the zeroconf setup case.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DISCOVERY_INFO_NOT_SUPPORTED, + ) + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "not_supported" + + +async def test_zeroconf_discovery(hass): + """Test discovery of Elmax local api panel.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DISCOVERY_INFO, + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "zeroconf_setup" + assert result["errors"] is None + + +async def test_zeroconf_setup_show_form(hass): + """Test discovery shows a form when activated.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DISCOVERY_INFO, + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + ) + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "zeroconf_setup" + + +async def test_zeroconf_setup(hass): + """Test the successful creation of config entry via discovery flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DISCOVERY_INFO, + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN, + CONF_ELMAX_MODE_DIRECT_SSL: MOCK_DIRECT_SSL, + }, + ) + + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + + +async def test_zeroconf_already_configured(hass): + """Ensure local discovery aborts when same panel is already added to ha.""" + MockConfigEntry( + domain=DOMAIN, + title=f"Elmax Direct ({MOCK_PANEL_ID})", + data={ + CONF_ELMAX_MODE: CONF_ELMAX_MODE_DIRECT, + CONF_ELMAX_MODE_DIRECT_HOST: MOCK_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT: MOCK_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL: MOCK_DIRECT_SSL, + CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN, + CONF_ELMAX_PANEL_ID: MOCK_PANEL_ID, + CONF_ELMAX_MODE_DIRECT_SSL_CERT: MOCK_DIRECT_CERT, + }, + unique_id=MOCK_PANEL_ID, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DISCOVERY_INFO, + ) + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +async def test_zeroconf_panel_changed_ip(hass): + """Ensure local discovery updates the panel data when a the panel changes its IP.""" + # Simulate an entry already exists for ip MOCK_DIRECT_HOST. + config_entry = MockConfigEntry( + domain=DOMAIN, + title=f"Elmax Direct ({MOCK_PANEL_ID})", + data={ + CONF_ELMAX_MODE: CONF_ELMAX_MODE_DIRECT, + CONF_ELMAX_MODE_DIRECT_HOST: MOCK_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT: MOCK_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL: MOCK_DIRECT_SSL, + CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN, + CONF_ELMAX_PANEL_ID: MOCK_PANEL_ID, + CONF_ELMAX_MODE_DIRECT_SSL_CERT: MOCK_DIRECT_CERT, + }, + unique_id=MOCK_PANEL_ID, + ) + config_entry.add_to_hass(hass) + + # Simulate a MDNS discovery finds the same panel with a different IP (MOCK_ZEROCONF_DISCOVERY_CHANGED_INFO). + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data=MOCK_ZEROCONF_DISCOVERY_CHANGED_INFO, + ) + + # Expect we abort the configuration as "already configured" + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "already_configured" + + # Expect the panel ip has been updated. + assert ( + hass.config_entries.async_get_entry(config_entry.entry_id).data[ + CONF_ELMAX_MODE_DIRECT_HOST + ] + == MOCK_ZEROCONF_DISCOVERY_CHANGED_INFO.host + ) + + +async def test_one_config_allowed_cloud(hass: HomeAssistant) -> None: """Test that only one Elmax configuration is allowed for each panel.""" MockConfigEntry( domain=DOMAIN, @@ -82,8 +311,12 @@ async def test_one_config_allowed(hass: HomeAssistant) -> None: show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - login_result = await hass.config_entries.flow.async_configure( + user_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) + login_result = await hass.config_entries.flow.async_configure( + user_result["flow_id"], { CONF_ELMAX_USERNAME: MOCK_USERNAME, CONF_ELMAX_PASSWORD: MOCK_PASSWORD, @@ -100,7 +333,7 @@ async def test_one_config_allowed(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" -async def test_invalid_credentials(hass: HomeAssistant) -> None: +async def test_cloud_invalid_credentials(hass: HomeAssistant) -> None: """Test that invalid credentials throws an error.""" with patch( "elmax_api.http.Elmax.login", @@ -109,6 +342,10 @@ async def test_invalid_credentials(hass: HomeAssistant) -> None: show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) + show_form_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) login_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], { @@ -116,12 +353,12 @@ async def test_invalid_credentials(hass: HomeAssistant) -> None: CONF_ELMAX_PASSWORD: "incorrect_password", }, ) - assert login_result["step_id"] == "user" + assert login_result["step_id"] == CONF_ELMAX_MODE_CLOUD assert login_result["type"] == data_entry_flow.FlowResultType.FORM assert login_result["errors"] == {"base": "invalid_auth"} -async def test_connection_error(hass: HomeAssistant) -> None: +async def test_cloud_connection_error(hass: HomeAssistant) -> None: """Test other than invalid credentials throws an error.""" with patch( "elmax_api.http.Elmax.login", @@ -130,6 +367,10 @@ async def test_connection_error(hass: HomeAssistant) -> None: show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) + show_form_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) login_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], { @@ -137,11 +378,65 @@ async def test_connection_error(hass: HomeAssistant) -> None: CONF_ELMAX_PASSWORD: MOCK_PASSWORD, }, ) - assert login_result["step_id"] == "user" + assert login_result["step_id"] == CONF_ELMAX_MODE_CLOUD assert login_result["type"] == data_entry_flow.FlowResultType.FORM assert login_result["errors"] == {"base": "network_error"} +async def test_direct_connection_error(hass: HomeAssistant) -> None: + """Test network error while dealing with direct panel APIs.""" + with patch( + "elmax_api.http.ElmaxLocal.login", + side_effect=ElmaxNetworkError(), + ): + show_form_result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + set_mode_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_DIRECT}, + ) + result = await hass.config_entries.flow.async_configure( + set_mode_result["flow_id"], + { + CONF_ELMAX_MODE_DIRECT_HOST: MOCK_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT: MOCK_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL: MOCK_DIRECT_SSL, + CONF_ELMAX_PANEL_PIN: MOCK_PANEL_PIN, + }, + ) + assert result["step_id"] == CONF_ELMAX_MODE_DIRECT + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["errors"] == {"base": "network_error"} + + +async def test_direct_wrong_panel_code(hass: HomeAssistant) -> None: + """Test wrong code being specified while dealing with direct panel APIs.""" + with patch( + "elmax_api.http.ElmaxLocal.login", + side_effect=ElmaxBadLoginError(), + ): + show_form_result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + set_mode_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_DIRECT}, + ) + result = await hass.config_entries.flow.async_configure( + set_mode_result["flow_id"], + { + CONF_ELMAX_MODE_DIRECT_HOST: MOCK_DIRECT_HOST, + CONF_ELMAX_MODE_DIRECT_PORT: MOCK_DIRECT_PORT, + CONF_ELMAX_MODE_DIRECT_SSL: MOCK_DIRECT_SSL, + CONF_ELMAX_PANEL_PIN: MOCK_WRONG_PANEL_PIN, + }, + ) + assert result["step_id"] == CONF_ELMAX_MODE_DIRECT + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["errors"] == {"base": "invalid_auth"} + + async def test_unhandled_error(hass: HomeAssistant) -> None: """Test unhandled exceptions.""" with patch( @@ -151,6 +446,10 @@ async def test_unhandled_error(hass: HomeAssistant) -> None: show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) + show_form_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) login_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], { @@ -180,6 +479,10 @@ async def test_invalid_pin(hass: HomeAssistant) -> None: show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) + show_form_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) login_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], { @@ -209,6 +512,10 @@ async def test_no_online_panel(hass: HomeAssistant) -> None: show_form_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) + show_form_result = await hass.config_entries.flow.async_configure( + show_form_result["flow_id"], + {"next_step_id": CONF_ELMAX_MODE_CLOUD}, + ) login_result = await hass.config_entries.flow.async_configure( show_form_result["flow_id"], { @@ -216,7 +523,7 @@ async def test_no_online_panel(hass: HomeAssistant) -> None: CONF_ELMAX_PASSWORD: MOCK_PASSWORD, }, ) - assert login_result["step_id"] == "user" + assert login_result["step_id"] == CONF_ELMAX_MODE_CLOUD assert login_result["type"] == data_entry_flow.FlowResultType.FORM assert login_result["errors"] == {"base": "no_panel_online"} From 1b7c3061f338e1336bca0072155aa35fe429a557 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:50:31 +0100 Subject: [PATCH 0225/1691] Bump actions/download-artifact from 4.1.3 to 4.1.4 (#112159) --- .github/workflows/ci.yaml | 2 +- .github/workflows/wheels.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 013fe7edfa3..0f97206690b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1079,7 +1079,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v4.1.1 - name: Download all coverage artifacts - uses: actions/download-artifact@v4.1.3 + uses: actions/download-artifact@v4.1.4 with: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index bae60e8e945..238ed15f33f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -91,12 +91,12 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download env_file - uses: actions/download-artifact@v4.1.3 + uses: actions/download-artifact@v4.1.4 with: name: env_file - name: Download requirements_diff - uses: actions/download-artifact@v4.1.3 + uses: actions/download-artifact@v4.1.4 with: name: requirements_diff @@ -129,12 +129,12 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download env_file - uses: actions/download-artifact@v4.1.3 + uses: actions/download-artifact@v4.1.4 with: name: env_file - name: Download requirements_diff - uses: actions/download-artifact@v4.1.3 + uses: actions/download-artifact@v4.1.4 with: name: requirements_diff From c58828aac04e89251f80ae39cd76c9dfae724e6d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 4 Mar 2024 12:09:27 +0100 Subject: [PATCH 0226/1691] Remove unused test helper mock_area_registry (#112172) --- tests/common.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/common.py b/tests/common.py index c5b517f78ac..738183a3f87 100644 --- a/tests/common.py +++ b/tests/common.py @@ -2,7 +2,6 @@ from __future__ import annotations import asyncio -from collections import OrderedDict from collections.abc import AsyncGenerator, Generator, Mapping, Sequence from contextlib import asynccontextmanager, contextmanager from datetime import UTC, datetime, timedelta @@ -618,27 +617,6 @@ def mock_registry( return registry -def mock_area_registry( - hass: HomeAssistant, mock_entries: dict[str, ar.AreaEntry] | None = None -) -> ar.AreaRegistry: - """Mock the Area Registry. - - This should only be used if you need to mock/re-stage a clean mocked - area registry in your current hass object. It can be useful to, - for example, pre-load the registry with items. - - This mock will thus replace the existing registry in the running hass. - - If you just need to access the existing registry, use the `area_registry` - fixture instead. - """ - registry = ar.AreaRegistry(hass) - registry.areas = mock_entries or OrderedDict() - - hass.data[ar.DATA_REGISTRY] = registry - return registry - - def mock_device_registry( hass: HomeAssistant, mock_entries: dict[str, dr.DeviceEntry] | None = None, From 9d7c947d199d70ee0b2ad660bd8e9c59cb5e3e87 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 01:10:44 -1000 Subject: [PATCH 0227/1691] Migrate config to use async_get_component (#112160) The component should always be loaded here but in the rare case it might not be, we should not block the loop when it loads --- homeassistant/components/config/device_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 7bd76310929..8444d407030 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -124,7 +124,7 @@ async def websocket_remove_config_entry_from_device( try: integration = await loader.async_get_integration(hass, config_entry.domain) - component = integration.get_component() + component = await integration.async_get_component() except (ImportError, loader.IntegrationNotFound) as exc: raise HomeAssistantError("Integration not found") from exc From 613bf1c22690e3817fc970f18ed7fbf9d28c3f22 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 01:11:34 -1000 Subject: [PATCH 0228/1691] Migrate device_automation to use async_get_platform (#112162) This ensures that if device_automation loads an integration's platform it will get loaded in the executor if enabled --- homeassistant/components/device_automation/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 2bf87343c72..91a223217b3 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -172,7 +172,7 @@ async def async_get_device_automation_platform( platform_name = automation_type.value.section try: integration = await async_get_integration_with_requirements(hass, domain) - platform = integration.get_platform(platform_name) + platform = await integration.async_get_platform(platform_name) except IntegrationNotFound as err: raise InvalidDeviceAutomationConfig( f"Integration '{domain}' not found" From a698bd58002fa78a4c9c96e2a36705af51a2c8d1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 01:26:55 -1000 Subject: [PATCH 0229/1691] Migrate rest to use eager tasks for setup (#112166) The refresh tasks will avoid one iteration of the event loop to start fetching data The load tasks will likely never suspend and avoid being scheduled on the event loop --- homeassistant/components/rest/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index ee79c45921c..d8610f148bd 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -40,6 +40,7 @@ from homeassistant.helpers.reload import ( ) from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util.async_ import create_eager_task from .const import ( CONF_ENCODING, @@ -129,10 +130,10 @@ async def _async_process_config(hass: HomeAssistant, config: ConfigType) -> bool load_coroutines.append(load_coroutine) if refresh_coroutines: - await asyncio.gather(*refresh_coroutines) + await asyncio.gather(*(create_eager_task(coro) for coro in refresh_coroutines)) if load_coroutines: - await asyncio.gather(*load_coroutines) + await asyncio.gather(*(create_eager_task(coro) for coro in load_coroutines)) return True From dd00a87ac5f787a1de70ac3e2e196668ca0c49a2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 01:27:52 -1000 Subject: [PATCH 0230/1691] Migrate application_credentials to use async_get_platform (#112161) This ensures that if application_credentials loads the integration's application_credentials platform it will get loaded in the executor if enabled --- homeassistant/components/application_credentials/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/application_credentials/__init__.py b/homeassistant/components/application_credentials/__init__.py index 679ff9bfac4..cd6e85a427a 100644 --- a/homeassistant/components/application_credentials/__init__.py +++ b/homeassistant/components/application_credentials/__init__.py @@ -291,7 +291,7 @@ async def _get_platform( _LOGGER.debug("Integration '%s' does not exist: %s", integration_domain, err) return None try: - platform = integration.get_platform("application_credentials") + platform = await integration.async_get_platform("application_credentials") except ImportError as err: _LOGGER.debug( "Integration '%s' does not provide application_credentials: %s", From 917f0f849db36c5b8644f8d600299d4284e56a0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 01:29:32 -1000 Subject: [PATCH 0231/1691] Import in the executor by default for core integrations (#112127) * Import in the executor by default for core integration * merge correct branch in * Group loading of platforms in the import executor * adjust test * remove other pr * Fix async_prepare_setup_platform test The message changed because the order changed but was not caught before merge because it required the combination of PRs to change the error message * fix * tweak * fix * self review * review * fix hue mocking * Update homeassistant/loader.py Co-authored-by: Paulus Schoutsen * lint * Fix async_get_component loading in the executor when the module is already loaded The sys.modules check was incorrect (only on dev) * fix * Avoid multiple executor jobs with concurrent calls to async_get_component Return a future that can be awaited if the component is curently being loaded * adjust * coverage * coverage * concurrent platforms load test * doc strings * coverage --------- Co-authored-by: Paulus Schoutsen --- homeassistant/loader.py | 5 ++++- tests/test_loader.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index f19577ac10a..46f3e352596 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -742,7 +742,10 @@ class Integration: @cached_property def import_executor(self) -> bool: """Import integration in the executor.""" - return self.manifest.get("import_executor") or False + # If the integration does not explicitly set import_executor, we default to + # True if it's a built-in integration and False if it's a custom integration. + # In the future, we want to default to True for all integrations. + return self.manifest.get("import_executor", self.is_built_in) @property def mqtt(self) -> list[str] | None: diff --git a/tests/test_loader.py b/tests/test_loader.py index fdbc457dfe0..0c0431ed227 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1033,6 +1033,14 @@ async def test_async_suggest_report_issue( ) +def test_import_executor_default(hass: HomeAssistant) -> None: + """Test that import_executor defaults.""" + custom_comp = mock_integration(hass, MockModule("any_random"), built_in=False) + assert custom_comp.import_executor is False + built_in_comp = mock_integration(hass, MockModule("other_random"), built_in=True) + assert built_in_comp.import_executor is True + + async def test_config_folder_not_in_path(hass): """Test that config folder is not in path.""" From c2a991625d0925f420b0374a162f369991ecfa65 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 4 Mar 2024 12:16:22 +0000 Subject: [PATCH 0232/1691] Update systembridgeconnector to 4.0.2 (#112174) --- homeassistant/components/system_bridge/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index dc2b645d5b7..8355935162d 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -10,6 +10,6 @@ "iot_class": "local_push", "loggers": ["systembridgeconnector"], "quality_scale": "silver", - "requirements": ["systembridgeconnector==4.0.1"], + "requirements": ["systembridgeconnector==4.0.2"], "zeroconf": ["_system-bridge._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 8a72a2849c8..8c13e868478 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2647,7 +2647,7 @@ switchbot-api==2.0.0 synology-srm==0.2.0 # homeassistant.components.system_bridge -systembridgeconnector==4.0.1 +systembridgeconnector==4.0.2 # homeassistant.components.tailscale tailscale==0.6.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0078a890143..ed3b1da72d4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2033,7 +2033,7 @@ surepy==0.9.0 switchbot-api==2.0.0 # homeassistant.components.system_bridge -systembridgeconnector==4.0.1 +systembridgeconnector==4.0.2 # homeassistant.components.tailscale tailscale==0.6.0 From b381922a20d50284f77a37df9e07a79af5834e38 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:22:14 +0100 Subject: [PATCH 0233/1691] Issues template function (#95206) * Add 'issues' template function for listing active issues. * Add issue template function test * Add 'issue' template function for getting specific issue by domain and issue_id * Remove comment * Fix function description * Remove reduntant function, Fix tests * remove pass_context * remove issues filter Co-authored-by: Erik Montnemery --------- Co-authored-by: Erik Montnemery --- homeassistant/helpers/template.py | 28 +++++++++++++- tests/helpers/test_template.py | 62 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index f7f12c59710..426ff3bed6f 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -78,7 +78,13 @@ from homeassistant.util.json import JSON_DECODE_EXCEPTIONS, json_loads from homeassistant.util.read_only_dict import ReadOnlyDict from homeassistant.util.thread import ThreadWithException -from . import area_registry, device_registry, entity_registry, location as loc_helper +from . import ( + area_registry, + device_registry, + entity_registry, + issue_registry, + location as loc_helper, +) from .singleton import singleton from .translation import async_translate_state from .typing import TemplateVarsType @@ -1357,6 +1363,21 @@ def is_device_attr( return bool(device_attr(hass, device_or_entity_id, attr_name) == attr_value) +def issues(hass: HomeAssistant) -> dict[tuple[str, str], dict[str, Any]]: + """Return all open issues.""" + current_issues = issue_registry.async_get(hass).issues + # Use JSON for safe representation + return {k: v.to_json() for (k, v) in current_issues.items()} + + +def issue(hass: HomeAssistant, domain: str, issue_id: str) -> dict[str, Any] | None: + """Get issue by domain and issue_id.""" + result = issue_registry.async_get(hass).async_get_issue(domain, issue_id) + if result: + return result.to_json() + return None + + def areas(hass: HomeAssistant) -> Iterable[str | None]: """Return all areas.""" area_reg = area_registry.async_get(hass) @@ -2619,6 +2640,11 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["device_id"] = hassfunction(device_id) self.filters["device_id"] = self.globals["device_id"] + self.globals["issues"] = hassfunction(issues) + + self.globals["issue"] = hassfunction(issue) + self.filters["issue"] = self.globals["issue"] + self.globals["areas"] = hassfunction(areas) self.globals["area_id"] = hassfunction(area_id) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index ab6d61bfe6c..6279ea0089a 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -37,6 +37,7 @@ from homeassistant.helpers import ( device_registry as dr, entity, entity_registry as er, + issue_registry as ir, template, translation, ) @@ -3512,6 +3513,67 @@ async def test_device_attr( assert info.rate_limit is None +async def test_issues(hass: HomeAssistant, issue_registry: ir.IssueRegistry) -> None: + """Test issues function.""" + # Test no issues + info = render_to_info(hass, "{{ issues() }}") + assert_result_info(info, {}) + assert info.rate_limit is None + + # Test persistent issue + ir.async_create_issue( + hass, + "test", + "issue 1", + breaks_in_ha_version="2023.7", + is_fixable=True, + is_persistent=True, + learn_more_url="https://theuselessweb.com", + severity="error", + translation_key="abc_1234", + translation_placeholders={"abc": "123"}, + ) + await hass.async_block_till_done() + created_issue = issue_registry.async_get_issue("test", "issue 1") + info = render_to_info(hass, "{{ issues()['test', 'issue 1'] }}") + assert_result_info(info, created_issue.to_json()) + assert info.rate_limit is None + + # Test fixed issue + ir.async_delete_issue(hass, "test", "issue 1") + await hass.async_block_till_done() + info = render_to_info(hass, "{{ issues() }}") + assert_result_info(info, {}) + assert info.rate_limit is None + + +async def test_issue(hass: HomeAssistant, issue_registry: ir.IssueRegistry) -> None: + """Test issue function.""" + # Test non existent issue + info = render_to_info(hass, "{{ issue('non_existent', 'issue') }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test existing issue + ir.async_create_issue( + hass, + "test", + "issue 1", + breaks_in_ha_version="2023.7", + is_fixable=True, + is_persistent=True, + learn_more_url="https://theuselessweb.com", + severity="error", + translation_key="abc_1234", + translation_placeholders={"abc": "123"}, + ) + await hass.async_block_till_done() + created_issue = issue_registry.async_get_issue("test", "issue 1") + info = render_to_info(hass, "{{ issue('test', 'issue 1') }}") + assert_result_info(info, created_issue.to_json()) + assert info.rate_limit is None + + async def test_areas(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None: """Test areas function.""" # Test no areas From a55fb1184a4ef9a2a6c3dced9c465205821bb7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Mon, 4 Mar 2024 13:36:37 +0000 Subject: [PATCH 0234/1691] Fix pylint/mypy on elmax integration (#112211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit elmax: config_flow: fix mypy/pylint Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/elmax/config_flow.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/elmax/config_flow.py b/homeassistant/components/elmax/config_flow.py index da74e7138bd..2e0c91d7785 100644 --- a/homeassistant/components/elmax/config_flow.py +++ b/homeassistant/components/elmax/config_flow.py @@ -13,7 +13,6 @@ import voluptuous as vol from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from .common import ( @@ -118,13 +117,13 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle the flow initiated by the user.""" return await self.async_step_choose_mode(user_input=user_input) async def async_step_choose_mode( self, user_input: dict[str, Any] | None = None - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle local vs cloud mode selection step.""" return self.async_show_menu( step_id="choose_mode", @@ -136,7 +135,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): async def _handle_direct_and_create_entry( self, fallback_step_id: str, schema: vol.Schema - ) -> FlowResult: + ) -> ConfigFlowResult: return await self._test_direct_and_create_entry() async def _test_direct_and_create_entry(self): @@ -202,7 +201,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): }, ) - async def async_step_direct(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_direct(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Handle the direct setup step.""" self._selected_mode = CONF_ELMAX_MODE_CLOUD if user_input is None: @@ -236,7 +235,9 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): fallback_step_id=CONF_ELMAX_MODE_DIRECT, schema=tmp_schema ) - async def async_step_zeroconf_setup(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_zeroconf_setup( + self, user_input: dict[str, Any] + ) -> ConfigFlowResult: """Handle the direct setup step triggered via zeroconf.""" if user_input is None: return self.async_show_form( @@ -265,7 +266,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): async def _check_unique_and_create_entry( self, unique_id: str, title: str, data: Mapping[str, Any] - ) -> FlowResult: + ) -> ConfigFlowResult: # Make sure this is the only Elmax integration for this specific panel id. await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured() @@ -275,7 +276,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): data=data, ) - async def async_step_cloud(self, user_input: dict[str, Any]) -> FlowResult: + async def async_step_cloud(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Handle the cloud setup flow.""" self._selected_mode = CONF_ELMAX_MODE_CLOUD @@ -463,7 +464,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): host: str, https_port: int, http_port: int, - ) -> FlowResult | None: + ) -> ConfigFlowResult | None: # Look for another entry with the same PANEL_ID (local or remote). # If there already is a matching panel, take the change to notify the Coordinator # so that it uses the newly discovered IP address. This mitigates the issues @@ -496,7 +497,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_zeroconf( self, discovery_info: ZeroconfServiceInfo - ) -> FlowResult: + ) -> ConfigFlowResult: """Handle device found via zeroconf.""" host = discovery_info.host https_port = ( From f7ac3912ec710a8ff2a383b119613d9bba4e1674 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:26:33 +0100 Subject: [PATCH 0235/1691] Add icon translations to Snapcast (#112229) --- homeassistant/components/snapcast/icons.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 homeassistant/components/snapcast/icons.json diff --git a/homeassistant/components/snapcast/icons.json b/homeassistant/components/snapcast/icons.json new file mode 100644 index 00000000000..bdc20665282 --- /dev/null +++ b/homeassistant/components/snapcast/icons.json @@ -0,0 +1,9 @@ +{ + "services": { + "join": "mdi:music-note-plus", + "unjoin": "mdi:music-note-minus", + "snapshot": "mdi:camera", + "restore": "mdi:camera-retake", + "set_latency": "mdi:camera-timer" + } +} From d811125eb7474209bc01f922183cd8c658fbe94c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:26:41 +0100 Subject: [PATCH 0236/1691] Add icon translations to Rainbird (#112200) * Add icon translations to Rainbird * Add icon translations to Rainbird --- .../components/rainbird/binary_sensor.py | 1 - homeassistant/components/rainbird/calendar.py | 2 +- homeassistant/components/rainbird/icons.json | 28 +++++++++++++++++++ homeassistant/components/rainbird/number.py | 1 - homeassistant/components/rainbird/sensor.py | 1 - .../components/rainbird/test_binary_sensor.py | 1 - tests/components/rainbird/test_calendar.py | 3 -- tests/components/rainbird/test_number.py | 1 - tests/components/rainbird/test_sensor.py | 1 - 9 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/rainbird/icons.json diff --git a/homeassistant/components/rainbird/binary_sensor.py b/homeassistant/components/rainbird/binary_sensor.py index 142e8ecc4b8..279b8625f20 100644 --- a/homeassistant/components/rainbird/binary_sensor.py +++ b/homeassistant/components/rainbird/binary_sensor.py @@ -21,7 +21,6 @@ _LOGGER = logging.getLogger(__name__) RAIN_SENSOR_ENTITY_DESCRIPTION = BinarySensorEntityDescription( key="rainsensor", translation_key="rainsensor", - icon="mdi:water", ) diff --git a/homeassistant/components/rainbird/calendar.py b/homeassistant/components/rainbird/calendar.py index 2001a14ac93..85906fa3fe3 100644 --- a/homeassistant/components/rainbird/calendar.py +++ b/homeassistant/components/rainbird/calendar.py @@ -49,7 +49,7 @@ class RainBirdCalendarEntity( _attr_has_entity_name = True _attr_name: str | None = None - _attr_icon = "mdi:sprinkler" + _attr_translation_key = "calendar" def __init__( self, diff --git a/homeassistant/components/rainbird/icons.json b/homeassistant/components/rainbird/icons.json new file mode 100644 index 00000000000..79d2256f184 --- /dev/null +++ b/homeassistant/components/rainbird/icons.json @@ -0,0 +1,28 @@ +{ + "entity": { + "binary_sensor": { + "rainsensor": { + "default": "mdi:water" + } + }, + "calendar": { + "calendar": { + "default": "mdi:sprinkler" + } + }, + "number": { + "rain_delay": { + "default": "mdi:water-off" + } + }, + "sensor": { + "raindelay": { + "default": "mdi:water-off" + } + } + }, + "services": { + "start_irrigation": "mdi:water", + "set_rain_delay": "mdi:water-sync" + } +} diff --git a/homeassistant/components/rainbird/number.py b/homeassistant/components/rainbird/number.py index dd9664222b2..9b7bdd481cb 100644 --- a/homeassistant/components/rainbird/number.py +++ b/homeassistant/components/rainbird/number.py @@ -41,7 +41,6 @@ class RainDelayNumber(CoordinatorEntity[RainbirdUpdateCoordinator], NumberEntity _attr_native_max_value = 14 _attr_native_step = 1 _attr_native_unit_of_measurement = UnitOfTime.DAYS - _attr_icon = "mdi:water-off" _attr_translation_key = "rain_delay" _attr_has_entity_name = True diff --git a/homeassistant/components/rainbird/sensor.py b/homeassistant/components/rainbird/sensor.py index 84bf8cadb7b..9daf0958327 100644 --- a/homeassistant/components/rainbird/sensor.py +++ b/homeassistant/components/rainbird/sensor.py @@ -19,7 +19,6 @@ _LOGGER = logging.getLogger(__name__) RAIN_DELAY_ENTITY_DESCRIPTION = SensorEntityDescription( key="raindelay", translation_key="raindelay", - icon="mdi:water-off", ) diff --git a/tests/components/rainbird/test_binary_sensor.py b/tests/components/rainbird/test_binary_sensor.py index 826a7635c53..77ad640e790 100644 --- a/tests/components/rainbird/test_binary_sensor.py +++ b/tests/components/rainbird/test_binary_sensor.py @@ -53,7 +53,6 @@ async def test_rainsensor( assert rainsensor.state == expected_state assert rainsensor.attributes == { "friendly_name": "Rain Bird Controller Rainsensor", - "icon": "mdi:water", } diff --git a/tests/components/rainbird/test_calendar.py b/tests/components/rainbird/test_calendar.py index 673d32998d5..497a88e4c3c 100644 --- a/tests/components/rainbird/test_calendar.py +++ b/tests/components/rainbird/test_calendar.py @@ -204,7 +204,6 @@ async def test_event_state( "description": "", "location": "", "friendly_name": "Rain Bird Controller", - "icon": "mdi:sprinkler", } assert state.state == expected_state @@ -248,7 +247,6 @@ async def test_no_schedule( assert state.state == "unavailable" assert state.attributes == { "friendly_name": "Rain Bird Controller", - "icon": "mdi:sprinkler", } client = await hass_client() @@ -276,7 +274,6 @@ async def test_program_schedule_disabled( assert state.state == "off" assert state.attributes == { "friendly_name": "Rain Bird Controller", - "icon": "mdi:sprinkler", } diff --git a/tests/components/rainbird/test_number.py b/tests/components/rainbird/test_number.py index b0c1856819e..0830a238fd7 100644 --- a/tests/components/rainbird/test_number.py +++ b/tests/components/rainbird/test_number.py @@ -57,7 +57,6 @@ async def test_number_values( assert raindelay.state == expected_state assert raindelay.attributes == { "friendly_name": "Rain Bird Controller Rain delay", - "icon": "mdi:water-off", "min": 0, "max": 14, "mode": "auto", diff --git a/tests/components/rainbird/test_sensor.py b/tests/components/rainbird/test_sensor.py index ebe852ccf46..730e1d50809 100644 --- a/tests/components/rainbird/test_sensor.py +++ b/tests/components/rainbird/test_sensor.py @@ -51,7 +51,6 @@ async def test_sensors( assert raindelay.state == expected_state assert raindelay.attributes == { "friendly_name": "Rain Bird Controller Raindelay", - "icon": "mdi:water-off", } entity_entry = entity_registry.async_get("sensor.rain_bird_controller_raindelay") From f5367e002b8358c205e99ff246638ff84ea35f1b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:27:00 +0100 Subject: [PATCH 0237/1691] Add icon translations to RFXCOM RFXtrx (#112209) --- homeassistant/components/rfxtrx/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/rfxtrx/icons.json diff --git a/homeassistant/components/rfxtrx/icons.json b/homeassistant/components/rfxtrx/icons.json new file mode 100644 index 00000000000..c1b8e741e45 --- /dev/null +++ b/homeassistant/components/rfxtrx/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "send": "mdi:send" + } +} From 4cfca55099d17f6a274f8cd9289851f02bf9876f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:36:41 +0100 Subject: [PATCH 0238/1691] Add icon translations to Simplisafe (#112224) --- homeassistant/components/simplisafe/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/simplisafe/icons.json diff --git a/homeassistant/components/simplisafe/icons.json b/homeassistant/components/simplisafe/icons.json new file mode 100644 index 00000000000..60ddb7f0982 --- /dev/null +++ b/homeassistant/components/simplisafe/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "remove_pin": "mdi:alarm-panel-outline", + "set_pin": "mdi:alarm-panel", + "set_system_properties": "mdi:cog" + } +} From 35599046acc6e2ede28a8899e723545134434390 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:36:50 +0100 Subject: [PATCH 0239/1691] Add icon translations to Ridwell (#112210) --- homeassistant/components/ridwell/calendar.py | 2 +- homeassistant/components/ridwell/icons.json | 14 ++++++++++++++ homeassistant/components/ridwell/switch.py | 1 - 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/ridwell/icons.json diff --git a/homeassistant/components/ridwell/calendar.py b/homeassistant/components/ridwell/calendar.py index 3ef3bbdc5ae..bf9c83914ac 100644 --- a/homeassistant/components/ridwell/calendar.py +++ b/homeassistant/components/ridwell/calendar.py @@ -49,8 +49,8 @@ async def async_setup_entry( class RidwellCalendar(RidwellEntity, CalendarEntity): """Define a Ridwell calendar.""" - _attr_icon = "mdi:delete-empty" _attr_name = None + _attr_translation_key = "calendar" def __init__( self, coordinator: RidwellDataUpdateCoordinator, account: RidwellAccount diff --git a/homeassistant/components/ridwell/icons.json b/homeassistant/components/ridwell/icons.json new file mode 100644 index 00000000000..dd9680ae687 --- /dev/null +++ b/homeassistant/components/ridwell/icons.json @@ -0,0 +1,14 @@ +{ + "entity": { + "calendar": { + "calendar": { + "default": "mdi:delete-empty" + } + }, + "switch": { + "opt_in": { + "default": "mdi:calendar-check" + } + } + } +} diff --git a/homeassistant/components/ridwell/switch.py b/homeassistant/components/ridwell/switch.py index f47fc1ca0af..c18e1d44b07 100644 --- a/homeassistant/components/ridwell/switch.py +++ b/homeassistant/components/ridwell/switch.py @@ -19,7 +19,6 @@ from .entity import RidwellEntity SWITCH_DESCRIPTION = SwitchEntityDescription( key="opt_in", translation_key="opt_in", - icon="mdi:calendar-check", ) From 15fa302c9a1f6d8752058091a2696bb1c32114c5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:37:03 +0100 Subject: [PATCH 0240/1691] Add icon translations to Recollect Waste (#112204) --- homeassistant/components/recollect_waste/calendar.py | 2 +- homeassistant/components/recollect_waste/icons.json | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/recollect_waste/icons.json diff --git a/homeassistant/components/recollect_waste/calendar.py b/homeassistant/components/recollect_waste/calendar.py index c439f647da5..b39ff430610 100644 --- a/homeassistant/components/recollect_waste/calendar.py +++ b/homeassistant/components/recollect_waste/calendar.py @@ -47,8 +47,8 @@ async def async_setup_entry( class ReCollectWasteCalendar(ReCollectWasteEntity, CalendarEntity): """Define a ReCollect Waste calendar.""" - _attr_icon = "mdi:delete-empty" _attr_name = None + _attr_translation_key = "calendar" def __init__( self, diff --git a/homeassistant/components/recollect_waste/icons.json b/homeassistant/components/recollect_waste/icons.json new file mode 100644 index 00000000000..59a2e742a1d --- /dev/null +++ b/homeassistant/components/recollect_waste/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "calendar": { + "calendar": { + "default": "mdi:delete-empty" + } + } + } +} From a2b9f59b8c704ed6703d771a1a11190d60163838 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:44:58 +0100 Subject: [PATCH 0241/1691] Add icon translations to Private BLE Device (#112185) --- .../private_ble_device/device_tracker.py | 6 +----- .../components/private_ble_device/icons.json | 20 +++++++++++++++++++ .../components/private_ble_device/sensor.py | 2 -- 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/private_ble_device/icons.json diff --git a/homeassistant/components/private_ble_device/device_tracker.py b/homeassistant/components/private_ble_device/device_tracker.py index 64e23b25ebe..4f22c9b275c 100644 --- a/homeassistant/components/private_ble_device/device_tracker.py +++ b/homeassistant/components/private_ble_device/device_tracker.py @@ -31,6 +31,7 @@ class BasePrivateDeviceTracker(BasePrivateDeviceEntity, BaseTrackerEntity): _attr_should_poll = False _attr_has_entity_name = True + _attr_translation_key = "device_tracker" _attr_name = None @property @@ -68,8 +69,3 @@ class BasePrivateDeviceTracker(BasePrivateDeviceEntity, BaseTrackerEntity): def source_type(self) -> SourceType: """Return the source type, eg gps or router, of the device.""" return SourceType.BLUETOOTH_LE - - @property - def icon(self) -> str: - """Return device icon.""" - return "mdi:bluetooth-connect" if self._last_info else "mdi:bluetooth-off" diff --git a/homeassistant/components/private_ble_device/icons.json b/homeassistant/components/private_ble_device/icons.json new file mode 100644 index 00000000000..0bab138182d --- /dev/null +++ b/homeassistant/components/private_ble_device/icons.json @@ -0,0 +1,20 @@ +{ + "entity": { + "device_tracker": { + "device_tracker": { + "default": "mdi:bluetooth-off", + "state": { + "home": "mdi:bluetooth-connect" + } + } + }, + "sensor": { + "estimated_distance": { + "default": "mdi:signal-distance-variant" + }, + "estimated_broadcast_interval": { + "default": "mdi:timer-sync-outline" + } + } + } +} diff --git a/homeassistant/components/private_ble_device/sensor.py b/homeassistant/components/private_ble_device/sensor.py index fb094de3d58..1cff006794c 100644 --- a/homeassistant/components/private_ble_device/sensor.py +++ b/homeassistant/components/private_ble_device/sensor.py @@ -65,7 +65,6 @@ SENSOR_DESCRIPTIONS = ( PrivateDeviceSensorEntityDescription( key="estimated_distance", translation_key="estimated_distance", - icon="mdi:signal-distance-variant", native_unit_of_measurement=UnitOfLength.METERS, value_fn=lambda _, service_info: service_info.advertisement and service_info.advertisement.tx_power @@ -79,7 +78,6 @@ SENSOR_DESCRIPTIONS = ( PrivateDeviceSensorEntityDescription( key="estimated_broadcast_interval", translation_key="estimated_broadcast_interval", - icon="mdi:timer-sync-outline", native_unit_of_measurement=UnitOfTime.SECONDS, entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, From 7eb61b748e9d56d80cd7e85086318f854bb655e6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:45:36 +0100 Subject: [PATCH 0242/1691] Add icon translations to PurpleAir (#112192) --- homeassistant/components/purpleair/icons.json | 24 +++++++++++++++++++ homeassistant/components/purpleair/sensor.py | 6 ----- 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/purpleair/icons.json diff --git a/homeassistant/components/purpleair/icons.json b/homeassistant/components/purpleair/icons.json new file mode 100644 index 00000000000..683d5b31b14 --- /dev/null +++ b/homeassistant/components/purpleair/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "sensor": { + "pm0_3_count_concentration": { + "default": "mdi:blur" + }, + "pm0_5_count_concentration": { + "default": "mdi:blur" + }, + "pm1_0_count_concentration": { + "default": "mdi:blur" + }, + "pm10_0_count_concentration": { + "default": "mdi:blur" + }, + "pm5_0_count_concentration": { + "default": "mdi:blur" + }, + "pm2_5_count_concentration": { + "default": "mdi:blur" + } + } + } +} diff --git a/homeassistant/components/purpleair/sensor.py b/homeassistant/components/purpleair/sensor.py index 50dbb47a285..eee4583ca11 100644 --- a/homeassistant/components/purpleair/sensor.py +++ b/homeassistant/components/purpleair/sensor.py @@ -52,7 +52,6 @@ SENSOR_DESCRIPTIONS = [ key="pm0.3_count_concentration", translation_key="pm0_3_count_concentration", entity_registry_enabled_default=False, - icon="mdi:blur", native_unit_of_measurement=CONCENTRATION_PARTICLES_PER_100_MILLILITERS, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda sensor: sensor.pm0_3_um_count, @@ -61,7 +60,6 @@ SENSOR_DESCRIPTIONS = [ key="pm0.5_count_concentration", translation_key="pm0_5_count_concentration", entity_registry_enabled_default=False, - icon="mdi:blur", native_unit_of_measurement=CONCENTRATION_PARTICLES_PER_100_MILLILITERS, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda sensor: sensor.pm0_5_um_count, @@ -70,7 +68,6 @@ SENSOR_DESCRIPTIONS = [ key="pm1.0_count_concentration", translation_key="pm1_0_count_concentration", entity_registry_enabled_default=False, - icon="mdi:blur", native_unit_of_measurement=CONCENTRATION_PARTICLES_PER_100_MILLILITERS, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda sensor: sensor.pm1_0_um_count, @@ -86,7 +83,6 @@ SENSOR_DESCRIPTIONS = [ key="pm10.0_count_concentration", translation_key="pm10_0_count_concentration", entity_registry_enabled_default=False, - icon="mdi:blur", native_unit_of_measurement=CONCENTRATION_PARTICLES_PER_100_MILLILITERS, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda sensor: sensor.pm10_0_um_count, @@ -102,7 +98,6 @@ SENSOR_DESCRIPTIONS = [ key="pm2.5_count_concentration", translation_key="pm2_5_count_concentration", entity_registry_enabled_default=False, - icon="mdi:blur", native_unit_of_measurement=CONCENTRATION_PARTICLES_PER_100_MILLILITERS, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda sensor: sensor.pm2_5_um_count, @@ -118,7 +113,6 @@ SENSOR_DESCRIPTIONS = [ key="pm5.0_count_concentration", translation_key="pm5_0_count_concentration", entity_registry_enabled_default=False, - icon="mdi:blur", native_unit_of_measurement=CONCENTRATION_PARTICLES_PER_100_MILLILITERS, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda sensor: sensor.pm5_0_um_count, From b8629028da728f232d60cdffc536bd7a65b7f9e8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:45:48 +0100 Subject: [PATCH 0243/1691] Add icon translations to Rainmachine (#112202) --- .../components/rainmachine/binary_sensor.py | 7 -- .../components/rainmachine/icons.json | 85 +++++++++++++++++++ .../components/rainmachine/select.py | 1 - .../components/rainmachine/sensor.py | 4 - 4 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/rainmachine/icons.json diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py index 930139acf60..7434577405e 100644 --- a/homeassistant/components/rainmachine/binary_sensor.py +++ b/homeassistant/components/rainmachine/binary_sensor.py @@ -42,14 +42,12 @@ BINARY_SENSOR_DESCRIPTIONS = ( RainMachineBinarySensorDescription( key=TYPE_FLOW_SENSOR, translation_key=TYPE_FLOW_SENSOR, - icon="mdi:water-pump", api_category=DATA_PROVISION_SETTINGS, data_key="useFlowSensor", ), RainMachineBinarySensorDescription( key=TYPE_FREEZE, translation_key=TYPE_FREEZE, - icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_CURRENT, data_key="freeze", @@ -57,7 +55,6 @@ BINARY_SENSOR_DESCRIPTIONS = ( RainMachineBinarySensorDescription( key=TYPE_HOURLY, translation_key=TYPE_HOURLY, - icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_CURRENT, data_key="hourly", @@ -65,7 +62,6 @@ BINARY_SENSOR_DESCRIPTIONS = ( RainMachineBinarySensorDescription( key=TYPE_MONTH, translation_key=TYPE_MONTH, - icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_CURRENT, data_key="month", @@ -73,7 +69,6 @@ BINARY_SENSOR_DESCRIPTIONS = ( RainMachineBinarySensorDescription( key=TYPE_RAINDELAY, translation_key=TYPE_RAINDELAY, - icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_CURRENT, data_key="rainDelay", @@ -81,7 +76,6 @@ BINARY_SENSOR_DESCRIPTIONS = ( RainMachineBinarySensorDescription( key=TYPE_RAINSENSOR, translation_key=TYPE_RAINSENSOR, - icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, api_category=DATA_RESTRICTIONS_CURRENT, @@ -90,7 +84,6 @@ BINARY_SENSOR_DESCRIPTIONS = ( RainMachineBinarySensorDescription( key=TYPE_WEEKDAY, translation_key=TYPE_WEEKDAY, - icon="mdi:cancel", entity_category=EntityCategory.DIAGNOSTIC, api_category=DATA_RESTRICTIONS_CURRENT, data_key="weekDay", diff --git a/homeassistant/components/rainmachine/icons.json b/homeassistant/components/rainmachine/icons.json new file mode 100644 index 00000000000..32988081a18 --- /dev/null +++ b/homeassistant/components/rainmachine/icons.json @@ -0,0 +1,85 @@ +{ + "entity": { + "binary_sensor": { + "flow_sensor": { + "default": "mdi:water-pump" + }, + "freeze": { + "default": "mdi:cancel" + }, + "hourly": { + "default": "mdi:cancel" + }, + "month": { + "default": "mdi:cancel" + }, + "raindelay": { + "default": "mdi:cancel" + }, + "rainsensor": { + "default": "mdi:cancel" + }, + "weekday": { + "default": "mdi:cancel" + } + }, + "select": { + "freeze_protection_temperature": { + "default": "mdi:thermometer" + } + }, + "sensor": { + "translation_key_0": { + "default": "mdi:abc" + }, + "translation_key_1": { + "default": "mdi:abc" + }, + "translation_key_2": { + "default": "mdi:abc" + }, + "translation_key_3": { + "default": "mdi:abc" + }, + "translation_key_4": { + "default": "mdi:abc" + }, + "translation_key_5": { + "default": "mdi:abc" + }, + "translation_key_6": { + "default": "mdi:abc" + }, + "translation_key_7": { + "default": "mdi:abc" + } + }, + "switch": { + "flow_sensor_clicks_cubic_meter": { + "default": "mdi:water-pump" + }, + "flow_sensor_consumed_liters": { + "default": "mdi:water-pump" + }, + "flow_sensor_leak_clicks": { + "default": "mdi:pipe-leak" + }, + "flow_sensor_leak_volume": { + "default": "mdi:pipe-leak" + } + } + }, + "services": { + "pause_watering": "mdi:pause", + "restrict_watering": "mdi:cancel", + "start_program": "mdi:play", + "start_zone": "mdi:play", + "stop_all": "mdi:stop", + "stop_program": "mdi:stop", + "stop_zone": "mdi:stop", + "unpause_watering": "mdi:play-pause", + "push_flow_meter_data": "mdi:database-arrow-up", + "push_weather_data": "mdi:database-arrow-up", + "unrestrict_watering": "mdi:check" + } +} diff --git a/homeassistant/components/rainmachine/select.py b/homeassistant/components/rainmachine/select.py index 893c1afa8da..5c68e4b573e 100644 --- a/homeassistant/components/rainmachine/select.py +++ b/homeassistant/components/rainmachine/select.py @@ -50,7 +50,6 @@ SELECT_DESCRIPTIONS = ( FreezeProtectionSelectDescription( key=TYPE_FREEZE_PROTECTION_TEMPERATURE, translation_key=TYPE_FREEZE_PROTECTION_TEMPERATURE, - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, api_category=DATA_RESTRICTIONS_UNIVERSAL, data_key="freezeProtectTemp", diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index ed9b8cc0142..c9e87ad0d07 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -66,7 +66,6 @@ SENSOR_DESCRIPTIONS = ( RainMachineSensorDataDescription( key=TYPE_FLOW_SENSOR_CLICK_M3, translation_key=TYPE_FLOW_SENSOR_CLICK_M3, - icon="mdi:water-pump", native_unit_of_measurement=f"clicks/{UnitOfVolume.CUBIC_METERS}", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, @@ -77,7 +76,6 @@ SENSOR_DESCRIPTIONS = ( RainMachineSensorDataDescription( key=TYPE_FLOW_SENSOR_CONSUMED_LITERS, translation_key=TYPE_FLOW_SENSOR_CONSUMED_LITERS, - icon="mdi:water-pump", device_class=SensorDeviceClass.WATER, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfVolume.LITERS, @@ -89,7 +87,6 @@ SENSOR_DESCRIPTIONS = ( RainMachineSensorDataDescription( key=TYPE_FLOW_SENSOR_LEAK_CLICKS, translation_key=TYPE_FLOW_SENSOR_LEAK_CLICKS, - icon="mdi:pipe-leak", entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement="clicks", entity_registry_enabled_default=False, @@ -100,7 +97,6 @@ SENSOR_DESCRIPTIONS = ( RainMachineSensorDataDescription( key=TYPE_FLOW_SENSOR_LEAK_VOLUME, translation_key=TYPE_FLOW_SENSOR_LEAK_VOLUME, - icon="mdi:pipe-leak", device_class=SensorDeviceClass.WATER, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfVolume.LITERS, From 2db0da39151738a3b4b3d5c7d4a9e41e77792f7a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:46:14 +0100 Subject: [PATCH 0244/1691] Add icon translations to Renault (#112205) * Add icon translations to Renault * Add icon translations to Renault --- .../components/renault/binary_sensor.py | 23 +--- homeassistant/components/renault/button.py | 3 - .../components/renault/device_tracker.py | 1 - homeassistant/components/renault/icons.json | 70 ++++++++++ homeassistant/components/renault/select.py | 15 --- homeassistant/components/renault/sensor.py | 32 +---- tests/components/renault/__init__.py | 4 - .../renault/snapshots/test_binary_sensor.ambr | 12 +- .../renault/snapshots/test_button.ambr | 60 +++------ .../snapshots/test_device_tracker.ambr | 18 +-- .../renault/snapshots/test_select.ambr | 18 +-- .../renault/snapshots/test_sensor.ambr | 120 ++++++------------ 12 files changed, 150 insertions(+), 226 deletions(-) create mode 100644 homeassistant/components/renault/icons.json diff --git a/homeassistant/components/renault/binary_sensor.py b/homeassistant/components/renault/binary_sensor.py index 0d66e5444e7..f77aaeb7893 100644 --- a/homeassistant/components/renault/binary_sensor.py +++ b/homeassistant/components/renault/binary_sensor.py @@ -1,7 +1,6 @@ """Support for Renault binary sensors.""" from __future__ import annotations -from collections.abc import Callable from dataclasses import dataclass from renault_api.kamereon.enums import ChargeState, PlugState @@ -22,23 +21,15 @@ from .entity import RenaultDataEntity, RenaultDataEntityDescription from .renault_hub import RenaultHub -@dataclass(frozen=True) -class RenaultBinarySensorRequiredKeysMixin: - """Mixin for required keys.""" - - on_key: str - on_value: StateType - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RenaultBinarySensorEntityDescription( BinarySensorEntityDescription, RenaultDataEntityDescription, - RenaultBinarySensorRequiredKeysMixin, ): """Class describing Renault binary sensor entities.""" - icon_fn: Callable[[RenaultBinarySensor], str] | None = None + on_key: str + on_value: StateType async def async_setup_entry( @@ -71,13 +62,6 @@ class RenaultBinarySensor( return None return data == self.entity_description.on_value - @property - def icon(self) -> str | None: - """Icon handling.""" - if self.entity_description.icon_fn: - return self.entity_description.icon_fn(self) - return None - BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = tuple( [ @@ -98,7 +82,6 @@ BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = tuple( RenaultBinarySensorEntityDescription( key="hvac_status", coordinator="hvac_status", - icon_fn=lambda e: "mdi:fan" if e.is_on else "mdi:fan-off", on_key="hvacStatus", on_value="on", translation_key="hvac_status", diff --git a/homeassistant/components/renault/button.py b/homeassistant/components/renault/button.py index 87883204890..9f259594bfa 100644 --- a/homeassistant/components/renault/button.py +++ b/homeassistant/components/renault/button.py @@ -61,20 +61,17 @@ BUTTON_TYPES: tuple[RenaultButtonEntityDescription, ...] = ( RenaultButtonEntityDescription( async_press=lambda x: x.vehicle.set_ac_start(21, None), key="start_air_conditioner", - icon="mdi:air-conditioner", translation_key="start_air_conditioner", ), RenaultButtonEntityDescription( async_press=lambda x: x.vehicle.set_charge_start(), key="start_charge", - icon="mdi:ev-station", requires_electricity=True, translation_key="start_charge", ), RenaultButtonEntityDescription( async_press=lambda x: x.vehicle.set_charge_stop(), key="stop_charge", - icon="mdi:ev-station", requires_electricity=True, translation_key="stop_charge", ), diff --git a/homeassistant/components/renault/device_tracker.py b/homeassistant/components/renault/device_tracker.py index a27c59cecfb..d5171bc954a 100644 --- a/homeassistant/components/renault/device_tracker.py +++ b/homeassistant/components/renault/device_tracker.py @@ -54,7 +54,6 @@ DEVICE_TRACKER_TYPES: tuple[RenaultDataEntityDescription, ...] = ( RenaultDataEntityDescription( key="location", coordinator="location", - icon="mdi:car", translation_key="location", ), ) diff --git a/homeassistant/components/renault/icons.json b/homeassistant/components/renault/icons.json new file mode 100644 index 00000000000..b8bbcdc91cc --- /dev/null +++ b/homeassistant/components/renault/icons.json @@ -0,0 +1,70 @@ +{ + "entity": { + "binary_sensor": { + "hvac_status": { + "default": "mdi:fan-off", + "state": { + "on": "mdi:fan" + } + } + }, + "button": { + "start_air_conditioner": { + "default": "mdi:air-conditioner" + }, + "start_charge": { + "default": "mdi:ev-station" + }, + "stop_charge": { + "default": "mdi:ev-station" + } + }, + "device_tracker": { + "location": { + "default": "mdi:car" + } + }, + "select": { + "charge_mode": { + "default": "mdi:calendar-remove", + "state": { + "schedule_mode": "mdi:calendar-clock" + } + } + }, + "sensor": { + "charge_state": { + "default": "mdi:mdi:flash-off", + "state": { + "charge_in_progress": "mdi:flash" + } + }, + "charging_remaining_time": { + "default": "mdi:timer" + }, + "plug_state": { + "default": "mdi:power-plug-off", + "state": { + "plugged": "mdi:power-plug" + } + }, + "battery_autonomy": { + "default": "mdi:ev-station" + }, + "mileage": { + "default": "mdi:sign-direction" + }, + "fuel_autonomy": { + "default": "mdi:gas-station" + }, + "fuel_quantity": { + "default": "mdi:fuel" + } + } + }, + "services": { + "ac_start": "mdi:hvac", + "ac_cancel": "mdi:hvac-off", + "charge_set_schedules": "mdi:calendar-clock" + } +} diff --git a/homeassistant/components/renault/select.py b/homeassistant/components/renault/select.py index 9dcc52abc87..398ccf14fb7 100644 --- a/homeassistant/components/renault/select.py +++ b/homeassistant/components/renault/select.py @@ -1,7 +1,6 @@ """Support for Renault sensors.""" from __future__ import annotations -from collections.abc import Callable from dataclasses import dataclass from typing import cast @@ -23,7 +22,6 @@ class RenaultSelectRequiredKeysMixin: """Mixin for required keys.""" data_key: str - icon_lambda: Callable[[RenaultSelectEntity], str] @dataclass(frozen=True) @@ -68,30 +66,17 @@ class RenaultSelectEntity( """Return the state of this entity.""" return self._get_data_attr(self.entity_description.data_key) - @property - def icon(self) -> str | None: - """Icon handling.""" - return self.entity_description.icon_lambda(self) - async def async_select_option(self, option: str) -> None: """Change the selected option.""" await self.vehicle.set_charge_mode(option) -def _get_charge_mode_icon(entity: RenaultSelectEntity) -> str: - """Return the icon of this entity.""" - if entity.data == "schedule_mode": - return "mdi:calendar-clock" - return "mdi:calendar-remove" - - SENSOR_TYPES: tuple[RenaultSelectEntityDescription, ...] = ( RenaultSelectEntityDescription( key="charge_mode", coordinator="charge_mode", data_key="chargeMode", translation_key="charge_mode", - icon_lambda=_get_charge_mode_icon, options=["always", "always_charging", "schedule_mode"], ), ) diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index d30b8d01fb3..d864bd4936c 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from datetime import datetime from typing import TYPE_CHECKING, Any, Generic, cast -from renault_api.kamereon.enums import ChargeState, PlugState from renault_api.kamereon.models import ( KamereonVehicleBatteryStatusData, KamereonVehicleCockpitData, @@ -59,7 +58,6 @@ class RenaultSensorEntityDescription( ): """Class describing Renault sensor entities.""" - icon_lambda: Callable[[RenaultSensor[T]], str] | None = None condition_lambda: Callable[[RenaultVehicleProxy], bool] | None = None requires_fuel: bool = False value_lambda: Callable[[RenaultSensor[T]], StateType | datetime] | None = None @@ -93,13 +91,6 @@ class RenaultSensor(RenaultDataEntity[T], SensorEntity): """Return the state of this entity.""" return self._get_data_attr(self.entity_description.data_key) - @property - def icon(self) -> str | None: - """Icon handling.""" - if self.entity_description.icon_lambda is None: - return super().icon - return self.entity_description.icon_lambda(self) - @property def native_value(self) -> StateType | datetime: """Return the state of this entity.""" @@ -122,13 +113,6 @@ def _get_charge_state_formatted(entity: RenaultSensor[T]) -> str | None: return charging_status.name.lower() if charging_status else None -def _get_charge_state_icon(entity: RenaultSensor[T]) -> str: - """Return the icon of this entity.""" - if entity.data == ChargeState.CHARGE_IN_PROGRESS.value: - return "mdi:flash" - return "mdi:flash-off" - - def _get_plug_state_formatted(entity: RenaultSensor[T]) -> str | None: """Return the plug_status of this entity.""" data = cast(KamereonVehicleBatteryStatusData, entity.coordinator.data) @@ -136,15 +120,8 @@ def _get_plug_state_formatted(entity: RenaultSensor[T]) -> str | None: return plug_status.name.lower() if plug_status else None -def _get_plug_state_icon(entity: RenaultSensor[T]) -> str: - """Return the icon of this entity.""" - if entity.data == PlugState.PLUGGED.value: - return "mdi:power-plug" - return "mdi:power-plug-off" - - def _get_rounded_value(entity: RenaultSensor[T]) -> float: - """Return the icon of this entity.""" + """Return the rounded value of this entity.""" return round(cast(float, entity.data)) @@ -173,7 +150,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( translation_key="charge_state", device_class=SensorDeviceClass.ENUM, entity_class=RenaultSensor[KamereonVehicleBatteryStatusData], - icon_lambda=_get_charge_state_icon, options=[ "not_in_charge", "waiting_for_a_planned_charge", @@ -192,7 +168,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( data_key="chargingRemainingTime", device_class=SensorDeviceClass.DURATION, entity_class=RenaultSensor[KamereonVehicleBatteryStatusData], - icon="mdi:timer", native_unit_of_measurement=UnitOfTime.MINUTES, state_class=SensorStateClass.MEASUREMENT, translation_key="charging_remaining_time", @@ -232,7 +207,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( translation_key="plug_state", device_class=SensorDeviceClass.ENUM, entity_class=RenaultSensor[KamereonVehicleBatteryStatusData], - icon_lambda=_get_plug_state_icon, options=["unplugged", "plugged", "plug_error", "plug_unknown"], value_lambda=_get_plug_state_formatted, ), @@ -242,7 +216,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( data_key="batteryAutonomy", device_class=SensorDeviceClass.DISTANCE, entity_class=RenaultSensor[KamereonVehicleBatteryStatusData], - icon="mdi:ev-station", native_unit_of_measurement=UnitOfLength.KILOMETERS, state_class=SensorStateClass.MEASUREMENT, translation_key="battery_autonomy", @@ -283,7 +256,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( data_key="totalMileage", device_class=SensorDeviceClass.DISTANCE, entity_class=RenaultSensor[KamereonVehicleCockpitData], - icon="mdi:sign-direction", native_unit_of_measurement=UnitOfLength.KILOMETERS, state_class=SensorStateClass.TOTAL_INCREASING, value_lambda=_get_rounded_value, @@ -295,7 +267,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( data_key="fuelAutonomy", device_class=SensorDeviceClass.DISTANCE, entity_class=RenaultSensor[KamereonVehicleCockpitData], - icon="mdi:gas-station", native_unit_of_measurement=UnitOfLength.KILOMETERS, state_class=SensorStateClass.MEASUREMENT, requires_fuel=True, @@ -308,7 +279,6 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = ( data_key="fuelQuantity", device_class=SensorDeviceClass.VOLUME, entity_class=RenaultSensor[KamereonVehicleCockpitData], - icon="mdi:fuel", native_unit_of_measurement=UnitOfVolume.LITERS, state_class=SensorStateClass.TOTAL, requires_fuel=True, diff --git a/tests/components/renault/__init__.py b/tests/components/renault/__init__.py index 8c47410ce40..7053bf8df2d 100644 --- a/tests/components/renault/__init__.py +++ b/tests/components/renault/__init__.py @@ -81,8 +81,6 @@ def check_entities_no_data( assert state.state == expected_state for attr in FIXED_ATTRIBUTES: assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) def check_entities_unavailable( @@ -100,5 +98,3 @@ def check_entities_unavailable( assert state.state == STATE_UNAVAILABLE for attr in FIXED_ATTRIBUTES: assert state.attributes.get(attr) == expected_entity.get(attr) - # Check dynamic attributes: - assert state.attributes.get(ATTR_ICON) == get_no_data_icon(expected_entity) diff --git a/tests/components/renault/snapshots/test_binary_sensor.ambr b/tests/components/renault/snapshots/test_binary_sensor.ambr index 8adbf1e9d02..da0916cfae0 100644 --- a/tests/components/renault/snapshots/test_binary_sensor.ambr +++ b/tests/components/renault/snapshots/test_binary_sensor.ambr @@ -785,7 +785,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:fan-off', + 'original_icon': None, 'original_name': 'HVAC', 'platform': 'renault', 'previous_unique_id': None, @@ -823,7 +823,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER HVAC', - 'icon': 'mdi:fan-off', }), 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', @@ -951,7 +950,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:fan-off', + 'original_icon': None, 'original_name': 'HVAC', 'platform': 'renault', 'previous_unique_id': None, @@ -1175,7 +1174,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER HVAC', - 'icon': 'mdi:fan-off', }), 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', @@ -2037,7 +2035,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:fan-off', + 'original_icon': None, 'original_name': 'HVAC', 'platform': 'renault', 'previous_unique_id': None, @@ -2075,7 +2073,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER HVAC', - 'icon': 'mdi:fan-off', }), 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', @@ -2203,7 +2200,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:fan-off', + 'original_icon': None, 'original_name': 'HVAC', 'platform': 'renault', 'previous_unique_id': None, @@ -2427,7 +2424,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER HVAC', - 'icon': 'mdi:fan-off', }), 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', diff --git a/tests/components/renault/snapshots/test_button.ambr b/tests/components/renault/snapshots/test_button.ambr index 58903962a2e..38f0d0fa106 100644 --- a/tests/components/renault/snapshots/test_button.ambr +++ b/tests/components/renault/snapshots/test_button.ambr @@ -55,7 +55,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -71,7 +71,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -137,7 +136,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -168,7 +167,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Start charge', 'platform': 'renault', 'previous_unique_id': None, @@ -199,7 +198,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Stop charge', 'platform': 'renault', 'previous_unique_id': None, @@ -215,7 +214,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -226,7 +224,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_start_charge', @@ -237,7 +234,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Stop charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_stop_charge', @@ -303,7 +299,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -334,7 +330,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Start charge', 'platform': 'renault', 'previous_unique_id': None, @@ -365,7 +361,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Stop charge', 'platform': 'renault', 'previous_unique_id': None, @@ -381,7 +377,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -392,7 +387,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_start_charge', @@ -403,7 +397,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Stop charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_stop_charge', @@ -469,7 +462,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -500,7 +493,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Start charge', 'platform': 'renault', 'previous_unique_id': None, @@ -531,7 +524,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Stop charge', 'platform': 'renault', 'previous_unique_id': None, @@ -547,7 +540,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -558,7 +550,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_start_charge', @@ -569,7 +560,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Stop charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_stop_charge', @@ -635,7 +625,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -651,7 +641,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -717,7 +706,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -748,7 +737,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Start charge', 'platform': 'renault', 'previous_unique_id': None, @@ -779,7 +768,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Stop charge', 'platform': 'renault', 'previous_unique_id': None, @@ -795,7 +784,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -806,7 +794,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_start_charge', @@ -817,7 +804,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Stop charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_stop_charge', @@ -883,7 +869,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -914,7 +900,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Start charge', 'platform': 'renault', 'previous_unique_id': None, @@ -945,7 +931,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Stop charge', 'platform': 'renault', 'previous_unique_id': None, @@ -961,7 +947,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -972,7 +957,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_start_charge', @@ -983,7 +967,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Stop charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_stop_charge', @@ -1049,7 +1032,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:air-conditioner', + 'original_icon': None, 'original_name': 'Start air conditioner', 'platform': 'renault', 'previous_unique_id': None, @@ -1080,7 +1063,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Start charge', 'platform': 'renault', 'previous_unique_id': None, @@ -1111,7 +1094,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Stop charge', 'platform': 'renault', 'previous_unique_id': None, @@ -1127,7 +1110,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start air conditioner', - 'icon': 'mdi:air-conditioner', }), 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', @@ -1138,7 +1120,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Start charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_start_charge', @@ -1149,7 +1130,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Stop charge', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'button.reg_number_stop_charge', diff --git a/tests/components/renault/snapshots/test_device_tracker.ambr b/tests/components/renault/snapshots/test_device_tracker.ambr index 2dd61ce6ace..3a029617ab3 100644 --- a/tests/components/renault/snapshots/test_device_tracker.ambr +++ b/tests/components/renault/snapshots/test_device_tracker.ambr @@ -55,7 +55,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Location', 'platform': 'renault', 'previous_unique_id': None, @@ -71,7 +71,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Location', - 'icon': 'mdi:car', 'source_type': , }), 'context': , @@ -138,7 +137,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Location', 'platform': 'renault', 'previous_unique_id': None, @@ -154,7 +153,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Location', - 'icon': 'mdi:car', 'source_type': , }), 'context': , @@ -261,7 +259,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Location', 'platform': 'renault', 'previous_unique_id': None, @@ -277,7 +275,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Location', - 'icon': 'mdi:car', 'source_type': , }), 'context': , @@ -344,7 +341,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Location', 'platform': 'renault', 'previous_unique_id': None, @@ -361,7 +358,6 @@ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Location', 'gps_accuracy': 0, - 'icon': 'mdi:car', 'latitude': 48.1234567, 'longitude': 11.1234567, 'source_type': , @@ -430,7 +426,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Location', 'platform': 'renault', 'previous_unique_id': None, @@ -447,7 +443,6 @@ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Location', 'gps_accuracy': 0, - 'icon': 'mdi:car', 'latitude': 48.1234567, 'longitude': 11.1234567, 'source_type': , @@ -556,7 +551,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car', + 'original_icon': None, 'original_name': 'Location', 'platform': 'renault', 'previous_unique_id': None, @@ -573,7 +568,6 @@ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Location', 'gps_accuracy': 0, - 'icon': 'mdi:car', 'latitude': 48.1234567, 'longitude': 11.1234567, 'source_type': , diff --git a/tests/components/renault/snapshots/test_select.ambr b/tests/components/renault/snapshots/test_select.ambr index 173afa6bdb9..053cbf48217 100644 --- a/tests/components/renault/snapshots/test_select.ambr +++ b/tests/components/renault/snapshots/test_select.ambr @@ -101,7 +101,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-remove', + 'original_icon': None, 'original_name': 'Charge mode', 'platform': 'renault', 'previous_unique_id': None, @@ -117,7 +117,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Charge mode', - 'icon': 'mdi:calendar-remove', 'options': list([ 'always', 'always_charging', @@ -194,7 +193,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-remove', + 'original_icon': None, 'original_name': 'Charge mode', 'platform': 'renault', 'previous_unique_id': None, @@ -210,7 +209,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Charge mode', - 'icon': 'mdi:calendar-remove', 'options': list([ 'always', 'always_charging', @@ -287,7 +285,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-remove', + 'original_icon': None, 'original_name': 'Charge mode', 'platform': 'renault', 'previous_unique_id': None, @@ -303,7 +301,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Charge mode', - 'icon': 'mdi:calendar-remove', 'options': list([ 'always', 'always_charging', @@ -420,7 +417,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-remove', + 'original_icon': None, 'original_name': 'Charge mode', 'platform': 'renault', 'previous_unique_id': None, @@ -436,7 +433,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Charge mode', - 'icon': 'mdi:calendar-remove', 'options': list([ 'always', 'always_charging', @@ -513,7 +509,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-remove', + 'original_icon': None, 'original_name': 'Charge mode', 'platform': 'renault', 'previous_unique_id': None, @@ -529,7 +525,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Charge mode', - 'icon': 'mdi:calendar-remove', 'options': list([ 'always', 'always_charging', @@ -606,7 +601,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-clock', + 'original_icon': None, 'original_name': 'Charge mode', 'platform': 'renault', 'previous_unique_id': None, @@ -622,7 +617,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'REG-NUMBER Charge mode', - 'icon': 'mdi:calendar-clock', 'options': list([ 'always', 'always_charging', diff --git a/tests/components/renault/snapshots/test_sensor.ambr b/tests/components/renault/snapshots/test_sensor.ambr index 866728eb09b..a13b194c5b8 100644 --- a/tests/components/renault/snapshots/test_sensor.ambr +++ b/tests/components/renault/snapshots/test_sensor.ambr @@ -57,7 +57,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -90,7 +90,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:gas-station', + 'original_icon': None, 'original_name': 'Fuel autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -123,7 +123,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:fuel', + 'original_icon': None, 'original_name': 'Fuel quantity', 'platform': 'renault', 'previous_unique_id': None, @@ -233,7 +233,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -247,7 +246,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Fuel autonomy', - 'icon': 'mdi:gas-station', 'state_class': , 'unit_of_measurement': , }), @@ -261,7 +259,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'volume', 'friendly_name': 'REG-NUMBER Fuel quantity', - 'icon': 'mdi:fuel', 'state_class': , 'unit_of_measurement': , }), @@ -404,7 +401,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:flash-off', + 'original_icon': None, 'original_name': 'Charge state', 'platform': 'renault', 'previous_unique_id': None, @@ -437,7 +434,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer', + 'original_icon': None, 'original_name': 'Charging remaining time', 'platform': 'renault', 'previous_unique_id': None, @@ -508,7 +505,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:power-plug-off', + 'original_icon': None, 'original_name': 'Plug state', 'platform': 'renault', 'previous_unique_id': None, @@ -541,7 +538,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Battery autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -671,7 +668,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -704,7 +701,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:gas-station', + 'original_icon': None, 'original_name': 'Fuel autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -737,7 +734,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:fuel', + 'original_icon': None, 'original_name': 'Fuel quantity', 'platform': 'renault', 'previous_unique_id': None, @@ -860,7 +857,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Charge state', - 'icon': 'mdi:flash-off', 'options': list([ 'not_in_charge', 'waiting_for_a_planned_charge', @@ -882,7 +878,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'REG-NUMBER Charging remaining time', - 'icon': 'mdi:timer', 'state_class': , 'unit_of_measurement': , }), @@ -909,7 +904,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Plug state', - 'icon': 'mdi:power-plug-off', 'options': list([ 'unplugged', 'plugged', @@ -927,7 +921,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Battery autonomy', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -978,7 +971,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -992,7 +984,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Fuel autonomy', - 'icon': 'mdi:gas-station', 'state_class': , 'unit_of_measurement': , }), @@ -1006,7 +997,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'volume', 'friendly_name': 'REG-NUMBER Fuel quantity', - 'icon': 'mdi:fuel', 'state_class': , 'unit_of_measurement': , }), @@ -1149,7 +1139,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:flash-off', + 'original_icon': None, 'original_name': 'Charge state', 'platform': 'renault', 'previous_unique_id': None, @@ -1182,7 +1172,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer', + 'original_icon': None, 'original_name': 'Charging remaining time', 'platform': 'renault', 'previous_unique_id': None, @@ -1253,7 +1243,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:power-plug-off', + 'original_icon': None, 'original_name': 'Plug state', 'platform': 'renault', 'previous_unique_id': None, @@ -1286,7 +1276,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Battery autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -1416,7 +1406,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -1603,7 +1593,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Charge state', - 'icon': 'mdi:flash-off', 'options': list([ 'not_in_charge', 'waiting_for_a_planned_charge', @@ -1625,7 +1614,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'REG-NUMBER Charging remaining time', - 'icon': 'mdi:timer', 'state_class': , 'unit_of_measurement': , }), @@ -1652,7 +1640,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Plug state', - 'icon': 'mdi:power-plug-off', 'options': list([ 'unplugged', 'plugged', @@ -1670,7 +1657,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Battery autonomy', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -1721,7 +1707,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -1888,7 +1873,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:flash-off', + 'original_icon': None, 'original_name': 'Charge state', 'platform': 'renault', 'previous_unique_id': None, @@ -1921,7 +1906,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer', + 'original_icon': None, 'original_name': 'Charging remaining time', 'platform': 'renault', 'previous_unique_id': None, @@ -1992,7 +1977,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:power-plug-off', + 'original_icon': None, 'original_name': 'Plug state', 'platform': 'renault', 'previous_unique_id': None, @@ -2025,7 +2010,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Battery autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -2155,7 +2140,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -2373,7 +2358,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Charge state', - 'icon': 'mdi:flash-off', 'options': list([ 'not_in_charge', 'waiting_for_a_planned_charge', @@ -2395,7 +2379,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'REG-NUMBER Charging remaining time', - 'icon': 'mdi:timer', 'state_class': , 'unit_of_measurement': , }), @@ -2422,7 +2405,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Plug state', - 'icon': 'mdi:power-plug-off', 'options': list([ 'unplugged', 'plugged', @@ -2440,7 +2422,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Battery autonomy', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -2491,7 +2472,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -2627,7 +2607,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -2660,7 +2640,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:gas-station', + 'original_icon': None, 'original_name': 'Fuel autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -2693,7 +2673,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:fuel', + 'original_icon': None, 'original_name': 'Fuel quantity', 'platform': 'renault', 'previous_unique_id': None, @@ -2803,7 +2783,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -2817,7 +2796,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Fuel autonomy', - 'icon': 'mdi:gas-station', 'state_class': , 'unit_of_measurement': , }), @@ -2831,7 +2809,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'volume', 'friendly_name': 'REG-NUMBER Fuel quantity', - 'icon': 'mdi:fuel', 'state_class': , 'unit_of_measurement': , }), @@ -2974,7 +2951,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:flash', + 'original_icon': None, 'original_name': 'Charge state', 'platform': 'renault', 'previous_unique_id': None, @@ -3007,7 +2984,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer', + 'original_icon': None, 'original_name': 'Charging remaining time', 'platform': 'renault', 'previous_unique_id': None, @@ -3078,7 +3055,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:power-plug', + 'original_icon': None, 'original_name': 'Plug state', 'platform': 'renault', 'previous_unique_id': None, @@ -3111,7 +3088,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Battery autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -3241,7 +3218,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -3274,7 +3251,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:gas-station', + 'original_icon': None, 'original_name': 'Fuel autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -3307,7 +3284,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:fuel', + 'original_icon': None, 'original_name': 'Fuel quantity', 'platform': 'renault', 'previous_unique_id': None, @@ -3430,7 +3407,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Charge state', - 'icon': 'mdi:flash', 'options': list([ 'not_in_charge', 'waiting_for_a_planned_charge', @@ -3452,7 +3428,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'REG-NUMBER Charging remaining time', - 'icon': 'mdi:timer', 'state_class': , 'unit_of_measurement': , }), @@ -3479,7 +3454,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Plug state', - 'icon': 'mdi:power-plug', 'options': list([ 'unplugged', 'plugged', @@ -3497,7 +3471,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Battery autonomy', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -3548,7 +3521,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -3562,7 +3534,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Fuel autonomy', - 'icon': 'mdi:gas-station', 'state_class': , 'unit_of_measurement': , }), @@ -3576,7 +3547,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'volume', 'friendly_name': 'REG-NUMBER Fuel quantity', - 'icon': 'mdi:fuel', 'state_class': , 'unit_of_measurement': , }), @@ -3719,7 +3689,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:flash', + 'original_icon': None, 'original_name': 'Charge state', 'platform': 'renault', 'previous_unique_id': None, @@ -3752,7 +3722,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer', + 'original_icon': None, 'original_name': 'Charging remaining time', 'platform': 'renault', 'previous_unique_id': None, @@ -3823,7 +3793,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:power-plug', + 'original_icon': None, 'original_name': 'Plug state', 'platform': 'renault', 'previous_unique_id': None, @@ -3856,7 +3826,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Battery autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -3986,7 +3956,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -4173,7 +4143,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Charge state', - 'icon': 'mdi:flash', 'options': list([ 'not_in_charge', 'waiting_for_a_planned_charge', @@ -4195,7 +4164,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'REG-NUMBER Charging remaining time', - 'icon': 'mdi:timer', 'state_class': , 'unit_of_measurement': , }), @@ -4222,7 +4190,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Plug state', - 'icon': 'mdi:power-plug', 'options': list([ 'unplugged', 'plugged', @@ -4240,7 +4207,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Battery autonomy', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -4291,7 +4257,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), @@ -4458,7 +4423,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:flash-off', + 'original_icon': None, 'original_name': 'Charge state', 'platform': 'renault', 'previous_unique_id': None, @@ -4491,7 +4456,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer', + 'original_icon': None, 'original_name': 'Charging remaining time', 'platform': 'renault', 'previous_unique_id': None, @@ -4562,7 +4527,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:power-plug-off', + 'original_icon': None, 'original_name': 'Plug state', 'platform': 'renault', 'previous_unique_id': None, @@ -4595,7 +4560,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Battery autonomy', 'platform': 'renault', 'previous_unique_id': None, @@ -4725,7 +4690,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:sign-direction', + 'original_icon': None, 'original_name': 'Mileage', 'platform': 'renault', 'previous_unique_id': None, @@ -4943,7 +4908,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Charge state', - 'icon': 'mdi:flash-off', 'options': list([ 'not_in_charge', 'waiting_for_a_planned_charge', @@ -4965,7 +4929,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'REG-NUMBER Charging remaining time', - 'icon': 'mdi:timer', 'state_class': , 'unit_of_measurement': , }), @@ -4992,7 +4955,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'REG-NUMBER Plug state', - 'icon': 'mdi:power-plug-off', 'options': list([ 'unplugged', 'plugged', @@ -5010,7 +4972,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Battery autonomy', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -5061,7 +5022,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'distance', 'friendly_name': 'REG-NUMBER Mileage', - 'icon': 'mdi:sign-direction', 'state_class': , 'unit_of_measurement': , }), From 7089ace89429485aafca68648e64564f79057198 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:46:34 +0100 Subject: [PATCH 0245/1691] Add icon translations to Solarlog (#112232) --- homeassistant/components/solarlog/icons.json | 39 ++++++++++++++++++++ homeassistant/components/solarlog/sensor.py | 11 ------ 2 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/solarlog/icons.json diff --git a/homeassistant/components/solarlog/icons.json b/homeassistant/components/solarlog/icons.json new file mode 100644 index 00000000000..487f11acbe4 --- /dev/null +++ b/homeassistant/components/solarlog/icons.json @@ -0,0 +1,39 @@ +{ + "entity": { + "sensor": { + "power_ac": { + "default": "mdi:solar-power" + }, + "power_dc": { + "default": "mdi:solar-power" + }, + "yield_day": { + "default": "mdi:solar-power" + }, + "yield_yesterday": { + "default": "mdi:solar-power" + }, + "yield_month": { + "default": "mdi:solar-power" + }, + "yield_year": { + "default": "mdi:solar-power" + }, + "yield_total": { + "default": "mdi:solar-power" + }, + "total_power": { + "default": "mdi:solar-power" + }, + "alternator_loss": { + "default": "mdi:solar-power" + }, + "capacity": { + "default": "mdi:solar-power" + }, + "power_available": { + "default": "mdi:solar-power" + } + } + } +} diff --git a/homeassistant/components/solarlog/sensor.py b/homeassistant/components/solarlog/sensor.py index a8025c7fc0f..68ccf5c9c88 100644 --- a/homeassistant/components/solarlog/sensor.py +++ b/homeassistant/components/solarlog/sensor.py @@ -43,7 +43,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="power_ac", translation_key="power_ac", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -51,7 +50,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="power_dc", translation_key="power_dc", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -73,7 +71,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="yield_day", translation_key="yield_day", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, value=lambda value: round(value / 1000, 3), @@ -81,7 +78,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="yield_yesterday", translation_key="yield_yesterday", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, value=lambda value: round(value / 1000, 3), @@ -89,7 +85,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="yield_month", translation_key="yield_month", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, value=lambda value: round(value / 1000, 3), @@ -97,7 +92,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="yield_year", translation_key="yield_year", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, value=lambda value: round(value / 1000, 3), @@ -105,7 +99,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="yield_total", translation_key="yield_total", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL, @@ -157,14 +150,12 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="total_power", translation_key="total_power", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, ), SolarLogSensorEntityDescription( key="alternator_loss", translation_key="alternator_loss", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -172,7 +163,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="capacity", translation_key="capacity", - icon="mdi:solar-power", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, @@ -189,7 +179,6 @@ SENSOR_TYPES: tuple[SolarLogSensorEntityDescription, ...] = ( SolarLogSensorEntityDescription( key="power_available", translation_key="power_available", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, From 511810a4f511a3b4fcc2c076b9d0654a5da7897d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:46:49 +0100 Subject: [PATCH 0246/1691] Add icon translations to Songpal (#112234) --- homeassistant/components/songpal/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/songpal/icons.json diff --git a/homeassistant/components/songpal/icons.json b/homeassistant/components/songpal/icons.json new file mode 100644 index 00000000000..1c831fbbd00 --- /dev/null +++ b/homeassistant/components/songpal/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_sound_setting": "mdi:volume-high" + } +} From 6aae44dbb3efd8f323ace5075b59e703cef24fe3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 15:47:15 +0100 Subject: [PATCH 0247/1691] Add icon translations to Philips TV (#112179) --- .../components/philips_js/binary_sensor.py | 2 -- .../components/philips_js/icons.json | 25 +++++++++++++++++++ homeassistant/components/philips_js/light.py | 1 - homeassistant/components/philips_js/switch.py | 2 -- 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/philips_js/icons.json diff --git a/homeassistant/components/philips_js/binary_sensor.py b/homeassistant/components/philips_js/binary_sensor.py index 74fe41bf722..678ba9d98f1 100644 --- a/homeassistant/components/philips_js/binary_sensor.py +++ b/homeassistant/components/philips_js/binary_sensor.py @@ -29,13 +29,11 @@ DESCRIPTIONS = ( PhilipsTVBinarySensorEntityDescription( key="recording_ongoing", translation_key="recording_ongoing", - icon="mdi:record-rec", recording_value="RECORDING_ONGOING", ), PhilipsTVBinarySensorEntityDescription( key="recording_new", translation_key="recording_new", - icon="mdi:new-box", recording_value="RECORDING_NEW", ), ) diff --git a/homeassistant/components/philips_js/icons.json b/homeassistant/components/philips_js/icons.json new file mode 100644 index 00000000000..ea9cbd3114e --- /dev/null +++ b/homeassistant/components/philips_js/icons.json @@ -0,0 +1,25 @@ +{ + "entity": { + "binary_sensor": { + "recording_ongoing": { + "default": "mdi:record-rec" + }, + "recording_new": { + "default": "mdi:new-box" + } + }, + "light": { + "ambilight": { + "default": "mdi:television-ambient-light" + } + }, + "switch": { + "screen_state": { + "default": "mdi:television-shimmer" + }, + "ambilight_hue": { + "default": "mdi:television-ambient-light" + } + } + } +} diff --git a/homeassistant/components/philips_js/light.py b/homeassistant/components/philips_js/light.py index 75f43039de8..7803938cf7e 100644 --- a/homeassistant/components/philips_js/light.py +++ b/homeassistant/components/philips_js/light.py @@ -153,7 +153,6 @@ class PhilipsTVLightEntity(PhilipsJsEntity, LightEntity): self._attr_supported_color_modes = {ColorMode.HS, ColorMode.ONOFF} self._attr_supported_features = LightEntityFeature.EFFECT self._attr_unique_id = coordinator.unique_id - self._attr_icon = "mdi:television-ambient-light" self._update_from_coordinator() diff --git a/homeassistant/components/philips_js/switch.py b/homeassistant/components/philips_js/switch.py index 29cfa10a230..71548c8238e 100644 --- a/homeassistant/components/philips_js/switch.py +++ b/homeassistant/components/philips_js/switch.py @@ -45,7 +45,6 @@ class PhilipsTVScreenSwitch(PhilipsJsEntity, SwitchEntity): super().__init__(coordinator) - self._attr_icon = "mdi:television-shimmer" self._attr_unique_id = f"{coordinator.unique_id}_screenstate" @property @@ -84,7 +83,6 @@ class PhilipsTVAmbilightHueSwitch(PhilipsJsEntity, SwitchEntity): super().__init__(coordinator) - self._attr_icon = "mdi:television-ambient-light" self._attr_unique_id = f"{coordinator.unique_id}_ambi_hue" @property From 34d25cf9e6711f44910bda0480890c28b268bdaf Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Mon, 4 Mar 2024 15:57:37 +0100 Subject: [PATCH 0248/1691] Bump bring-api to 0.5.4 (#111654) --- homeassistant/components/bring/coordinator.py | 6 +- homeassistant/components/bring/manifest.json | 2 +- homeassistant/components/bring/todo.py | 103 +++++++++++------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 69 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/bring/coordinator.py b/homeassistant/components/bring/coordinator.py index dbb6905473d..550c589aa4e 100644 --- a/homeassistant/components/bring/coordinator.py +++ b/homeassistant/components/bring/coordinator.py @@ -6,7 +6,7 @@ import logging from bring_api.bring import Bring from bring_api.exceptions import BringParseException, BringRequestException -from bring_api.types import BringItemsResponse, BringList +from bring_api.types import BringList, BringPurchase from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -20,8 +20,8 @@ _LOGGER = logging.getLogger(__name__) class BringData(BringList): """Coordinator data class.""" - purchase_items: list[BringItemsResponse] - recently_items: list[BringItemsResponse] + purchase_items: list[BringPurchase] + recently_items: list[BringPurchase] class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]): diff --git a/homeassistant/components/bring/manifest.json b/homeassistant/components/bring/manifest.json index 604b9d9c750..0e425ec3eee 100644 --- a/homeassistant/components/bring/manifest.json +++ b/homeassistant/components/bring/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/bring", "integration_type": "service", "iot_class": "cloud_polling", - "requirements": ["bring-api==0.4.1"] + "requirements": ["bring-api==0.5.4"] } diff --git a/homeassistant/components/bring/todo.py b/homeassistant/components/bring/todo.py index 3e4e3137aaa..5d3fc5bbf68 100644 --- a/homeassistant/components/bring/todo.py +++ b/homeassistant/components/bring/todo.py @@ -2,8 +2,10 @@ from __future__ import annotations from typing import TYPE_CHECKING +import uuid from bring_api.exceptions import BringRequestException +from bring_api.types import BringItem, BringItemOperation from homeassistant.components.todo import ( TodoItem, @@ -76,7 +78,7 @@ class BringTodoListEntity( return [ *( TodoItem( - uid=item["itemId"], + uid=item["uuid"], summary=item["itemId"], description=item["specification"] or "", status=TodoItemStatus.NEEDS_ACTION, @@ -85,7 +87,7 @@ class BringTodoListEntity( ), *( TodoItem( - uid=item["itemId"], + uid=item["uuid"], summary=item["itemId"], description=item["specification"] or "", status=TodoItemStatus.COMPLETED, @@ -103,7 +105,10 @@ class BringTodoListEntity( """Add an item to the To-do list.""" try: await self.coordinator.bring.save_item( - self.bring_list["listUuid"], item.summary, item.description or "" + self.bring_list["listUuid"], + item.summary, + item.description or "", + str(uuid.uuid4()), ) except BringRequestException as e: raise HomeAssistantError("Unable to save todo item for bring") from e @@ -121,60 +126,69 @@ class BringTodoListEntity( - Completed items will move to the "completed" section in home assistant todo list and get moved to the recently list in bring - - Bring items do not have unique identifiers and are using the - name/summery/title. Therefore the name is not to be changed! Should a name - be changed anyway, a new item will be created instead and no update for - this item is performed and on the next cloud pull update, it will get - cleared and replaced seamlessly + - Bring shows some odd behaviour when renaming items. This is because Bring + did not have unique identifiers for items in the past and this is still + a relic from it. Therefore the name is not to be changed! Should a name + be changed anyway, the item will be deleted and a new item will be created + instead and no update for this item is performed and on the next cloud pull + update, it will get cleared and replaced seamlessly. """ bring_list = self.bring_list bring_purchase_item = next( - (i for i in bring_list["purchase_items"] if i["itemId"] == item.uid), + (i for i in bring_list["purchase_items"] if i["uuid"] == item.uid), None, ) bring_recently_item = next( - (i for i in bring_list["recently_items"] if i["itemId"] == item.uid), + (i for i in bring_list["recently_items"] if i["uuid"] == item.uid), None, ) + current_item = bring_purchase_item or bring_recently_item + if TYPE_CHECKING: assert item.uid + assert current_item - if item.status == TodoItemStatus.COMPLETED and bring_purchase_item: - await self.coordinator.bring.complete_item( - bring_list["listUuid"], - item.uid, - ) - - elif item.status == TodoItemStatus.NEEDS_ACTION and bring_recently_item: - await self.coordinator.bring.save_item( - bring_list["listUuid"], - item.uid, - ) - - elif item.summary == item.uid: + if item.summary == current_item["itemId"]: try: - await self.coordinator.bring.update_item( + await self.coordinator.bring.batch_update_list( bring_list["listUuid"], - item.uid, - item.description or "", + BringItem( + itemId=item.summary, + spec=item.description, + uuid=item.uid, + ), + BringItemOperation.ADD + if item.status == TodoItemStatus.NEEDS_ACTION + else BringItemOperation.COMPLETE, ) except BringRequestException as e: raise HomeAssistantError("Unable to update todo item for bring") from e else: try: - await self.coordinator.bring.remove_item( + await self.coordinator.bring.batch_update_list( bring_list["listUuid"], - item.uid, - ) - await self.coordinator.bring.save_tem( - bring_list["listUuid"], - item.summary, - item.description or "", + [ + BringItem( + itemId=current_item["itemId"], + spec=item.description, + uuid=item.uid, + operation=BringItemOperation.REMOVE, + ), + BringItem( + itemId=item.summary, + spec=item.description, + uuid=str(uuid.uuid4()), + operation=BringItemOperation.ADD + if item.status == TodoItemStatus.NEEDS_ACTION + else BringItemOperation.COMPLETE, + ), + ], ) + except BringRequestException as e: raise HomeAssistantError("Unable to replace todo item for bring") from e @@ -182,12 +196,21 @@ class BringTodoListEntity( async def async_delete_todo_items(self, uids: list[str]) -> None: """Delete an item from the To-do list.""" - for uid in uids: - try: - await self.coordinator.bring.remove_item( - self.bring_list["listUuid"], uid - ) - except BringRequestException as e: - raise HomeAssistantError("Unable to delete todo item for bring") from e + + try: + await self.coordinator.bring.batch_update_list( + self.bring_list["listUuid"], + [ + BringItem( + itemId=uid, + spec="", + uuid=uid, + ) + for uid in uids + ], + BringItemOperation.REMOVE, + ) + except BringRequestException as e: + raise HomeAssistantError("Unable to delete todo item for bring") from e await self.coordinator.async_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index 8c13e868478..47e4dcf5971 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -603,7 +603,7 @@ boschshcpy==0.2.75 boto3==1.33.13 # homeassistant.components.bring -bring-api==0.4.1 +bring-api==0.5.4 # homeassistant.components.broadlink broadlink==0.18.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ed3b1da72d4..18f8208ae26 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -514,7 +514,7 @@ bond-async==0.2.1 boschshcpy==0.2.75 # homeassistant.components.bring -bring-api==0.4.1 +bring-api==0.5.4 # homeassistant.components.broadlink broadlink==0.18.3 From 2563db9a0ccea35a05ef00232e197112d7acdce6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:21:39 +0100 Subject: [PATCH 0249/1691] Add icon translations to Smarttub (#112227) --- homeassistant/components/smarttub/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/smarttub/icons.json diff --git a/homeassistant/components/smarttub/icons.json b/homeassistant/components/smarttub/icons.json new file mode 100644 index 00000000000..7ae96d03383 --- /dev/null +++ b/homeassistant/components/smarttub/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "set_primary_filtration": "mdi:filter", + "set_secondary_filtration": "mdi:filter-multiple", + "snooze_reminder": "mdi:timer-pause", + "reset_reminder": "mdi:timer-sync" + } +} From 863950ab12f046d5ef49aca3a40905370d9a5e9a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:50:40 +0100 Subject: [PATCH 0250/1691] Add icon translations to sabNZBd (#112219) --- homeassistant/components/sabnzbd/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/sabnzbd/icons.json diff --git a/homeassistant/components/sabnzbd/icons.json b/homeassistant/components/sabnzbd/icons.json new file mode 100644 index 00000000000..a693e9fec86 --- /dev/null +++ b/homeassistant/components/sabnzbd/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "pause": "mdi:pause", + "resume": "mdi:play", + "set_speed": "mdi:speedometer" + } +} From 47b7333d99be6001928952c28dca82cc0f4153c5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:51:17 +0100 Subject: [PATCH 0251/1691] Add icon translations to Slack (#112226) --- homeassistant/components/slack/icons.json | 9 +++++++++ homeassistant/components/slack/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/slack/icons.json diff --git a/homeassistant/components/slack/icons.json b/homeassistant/components/slack/icons.json new file mode 100644 index 00000000000..5f09ed12fcc --- /dev/null +++ b/homeassistant/components/slack/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "do_not_disturb_until": { + "default": "mdi:clock" + } + } + } +} diff --git a/homeassistant/components/slack/sensor.py b/homeassistant/components/slack/sensor.py index 4e65fdfc26d..0c99cdeedec 100644 --- a/homeassistant/components/slack/sensor.py +++ b/homeassistant/components/slack/sensor.py @@ -30,7 +30,6 @@ async def async_setup_entry( SensorEntityDescription( key="do_not_disturb_until", translation_key="do_not_disturb_until", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, ), entry, From 3c12f6339e48ee73bb86d97c3b35c7524a5ea884 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:53:10 +0100 Subject: [PATCH 0252/1691] Add icon translations to Season (#112221) --- homeassistant/components/season/icons.json | 15 +++++++++++++++ homeassistant/components/season/sensor.py | 11 ----------- 2 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/season/icons.json diff --git a/homeassistant/components/season/icons.json b/homeassistant/components/season/icons.json new file mode 100644 index 00000000000..160ab257338 --- /dev/null +++ b/homeassistant/components/season/icons.json @@ -0,0 +1,15 @@ +{ + "entity": { + "sensor": { + "season": { + "default": "mdi:cloud", + "state": { + "spring": "mdi:flower", + "summer": "mdi:sunglasses", + "autumn": "mdi:leaf", + "winter": "mdi:snowflake" + } + } + } + } +} diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py index cfca3c1f9ea..aa0f6c80b19 100644 --- a/homeassistant/components/season/sensor.py +++ b/homeassistant/components/season/sensor.py @@ -32,13 +32,6 @@ HEMISPHERE_SEASON_SWAP = { STATE_SUMMER: STATE_WINTER, } -SEASON_ICONS = { - STATE_SPRING: "mdi:flower", - STATE_SUMMER: "mdi:sunglasses", - STATE_AUTUMN: "mdi:leaf", - STATE_WINTER: "mdi:snowflake", -} - async def async_setup_entry( hass: HomeAssistant, @@ -113,7 +106,3 @@ class SeasonSensorEntity(SensorEntity): self._attr_native_value = get_season( utcnow().replace(tzinfo=None), self.hemisphere, self.type ) - - self._attr_icon = "mdi:cloud" - if self._attr_native_value: - self._attr_icon = SEASON_ICONS[self._attr_native_value] From 03793d75f82dab18bae619fc1c13c42dd8ed3427 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:53:30 +0100 Subject: [PATCH 0253/1691] Add icon translations to Screenlogic (#112220) --- homeassistant/components/screenlogic/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/screenlogic/icons.json diff --git a/homeassistant/components/screenlogic/icons.json b/homeassistant/components/screenlogic/icons.json new file mode 100644 index 00000000000..d8d021c20e6 --- /dev/null +++ b/homeassistant/components/screenlogic/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "set_color_mode": "mdi:palette", + "start_super_chlorination": "mdi:pool", + "stop_super_chlorination": "mdi:pool" + } +} From ce897b864508270dc94dc672e5c5759fe01c51ba Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 4 Mar 2024 15:54:58 +0000 Subject: [PATCH 0254/1691] Change log level for System Bridge service handlers (#112175) Change log level from info to debug for System Bridge service handlers --- homeassistant/components/system_bridge/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index e927a05b96f..d999ed8a9f3 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -196,7 +196,7 @@ async def async_setup_entry( async def handle_open_path(call: ServiceCall) -> None: """Handle the open path service call.""" - _LOGGER.info("Open: %s", call.data) + _LOGGER.debug("Open: %s", call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE] ] @@ -206,7 +206,7 @@ async def async_setup_entry( async def handle_power_command(call: ServiceCall) -> None: """Handle the power command service call.""" - _LOGGER.info("Power command: %s", call.data) + _LOGGER.debug("Power command: %s", call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE] ] @@ -217,7 +217,7 @@ async def async_setup_entry( async def handle_open_url(call: ServiceCall) -> None: """Handle the open url service call.""" - _LOGGER.info("Open: %s", call.data) + _LOGGER.debug("Open: %s", call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ call.data[CONF_BRIDGE] ] From d7c91a4195d7cfb29e5401435e8c627759e24431 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:56:19 +0100 Subject: [PATCH 0255/1691] Add icon translations to Roon (#112218) --- homeassistant/components/roon/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/roon/icons.json diff --git a/homeassistant/components/roon/icons.json b/homeassistant/components/roon/icons.json new file mode 100644 index 00000000000..571ca3f45a2 --- /dev/null +++ b/homeassistant/components/roon/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "transfer": "mdi:monitor-multiple" + } +} From f0487da6f68cbcf6d114240bf1696b6946d19ca5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 16:58:38 +0100 Subject: [PATCH 0256/1691] Add icon translations to Swiss public transport (#112254) --- .../components/swiss_public_transport/icons.json | 9 +++++++++ .../components/swiss_public_transport/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/swiss_public_transport/icons.json diff --git a/homeassistant/components/swiss_public_transport/icons.json b/homeassistant/components/swiss_public_transport/icons.json new file mode 100644 index 00000000000..fac54b10809 --- /dev/null +++ b/homeassistant/components/swiss_public_transport/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "departure": { + "default": "mdi:bus" + } + } + } +} diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index ede2798f675..9b351af6ed4 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -108,7 +108,6 @@ class SwissPublicTransportSensor( """Implementation of a Swiss public transport sensor.""" _attr_attribution = "Data provided by transport.opendata.ch" - _attr_icon = "mdi:bus" _attr_has_entity_name = True _attr_translation_key = "departure" _attr_device_class = SensorDeviceClass.TIMESTAMP From 0134715e2ba4c6d64476f417c714791b467d2abf Mon Sep 17 00:00:00 2001 From: Brig Lamoreaux Date: Mon, 4 Mar 2024 09:19:03 -0700 Subject: [PATCH 0257/1691] Address srp_energy late review comment (#105756) Change the reference to already_configured_service. --- homeassistant/components/srp_energy/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/srp_energy/strings.json b/homeassistant/components/srp_energy/strings.json index 35195ddb4f2..191d10a70dd 100644 --- a/homeassistant/components/srp_energy/strings.json +++ b/homeassistant/components/srp_energy/strings.json @@ -17,7 +17,7 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } }, "entity": { From 58d0420a6b5f4b71a25e806f1629334dfdf085c4 Mon Sep 17 00:00:00 2001 From: belangp <583452+belangp@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:20:20 -0400 Subject: [PATCH 0258/1691] Add Hyperion sensor to report active priority on each instance (#102333) * Implement code review comments * Update homeassistant/components/hyperion/sensor.py --------- Co-authored-by: Erik Montnemery --- homeassistant/components/hyperion/__init__.py | 2 +- homeassistant/components/hyperion/const.py | 3 + homeassistant/components/hyperion/sensor.py | 210 ++++++++++++++++++ .../components/hyperion/strings.json | 5 + tests/components/hyperion/test_light.py | 11 + tests/components/hyperion/test_sensor.py | 178 +++++++++++++++ 6 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/hyperion/sensor.py create mode 100644 tests/components/hyperion/test_sensor.py diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index 42d9770656b..58eaedb3ff9 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -32,7 +32,7 @@ from .const import ( SIGNAL_INSTANCE_REMOVE, ) -PLATFORMS = [Platform.CAMERA, Platform.LIGHT, Platform.SWITCH] +PLATFORMS = [Platform.CAMERA, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hyperion/const.py b/homeassistant/components/hyperion/const.py index 77e16df4d72..3d44dd35e08 100644 --- a/homeassistant/components/hyperion/const.py +++ b/homeassistant/components/hyperion/const.py @@ -28,3 +28,6 @@ SIGNAL_ENTITY_REMOVE = f"{DOMAIN}_entity_remove_signal.{{}}" TYPE_HYPERION_CAMERA = "hyperion_camera" TYPE_HYPERION_LIGHT = "hyperion_light" TYPE_HYPERION_COMPONENT_SWITCH_BASE = "hyperion_component_switch" + +TYPE_HYPERION_SENSOR_BASE = "hyperion_sensor" +TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY = "visible_priority" diff --git a/homeassistant/components/hyperion/sensor.py b/homeassistant/components/hyperion/sensor.py new file mode 100644 index 00000000000..f0d1c6b1314 --- /dev/null +++ b/homeassistant/components/hyperion/sensor.py @@ -0,0 +1,210 @@ +"""Sensor platform for Hyperion.""" +from __future__ import annotations + +import functools +from typing import Any + +from hyperion import client +from hyperion.const import ( + KEY_COMPONENTID, + KEY_ORIGIN, + KEY_OWNER, + KEY_PRIORITIES, + KEY_PRIORITY, + KEY_RGB, + KEY_UPDATE, + KEY_VALUE, + KEY_VISIBLE, +) + +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ( + get_hyperion_device_id, + get_hyperion_unique_id, + listen_for_instance_updates, +) +from .const import ( + CONF_INSTANCE_CLIENTS, + DOMAIN, + HYPERION_MANUFACTURER_NAME, + HYPERION_MODEL_NAME, + SIGNAL_ENTITY_REMOVE, + TYPE_HYPERION_SENSOR_BASE, + TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY, +) + +SENSORS = [TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY] +PRIORITY_SENSOR_DESCRIPTION = SensorEntityDescription( + key="visible_priority", + translation_key="visible_priority", + icon="mdi:lava-lamp", +) + + +def _sensor_unique_id(server_id: str, instance_num: int, suffix: str) -> str: + """Calculate a sensor's unique_id.""" + return get_hyperion_unique_id( + server_id, + instance_num, + f"{TYPE_HYPERION_SENSOR_BASE}_{suffix}", + ) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up a Hyperion platform from config entry.""" + entry_data = hass.data[DOMAIN][config_entry.entry_id] + server_id = config_entry.unique_id + + @callback + def instance_add(instance_num: int, instance_name: str) -> None: + """Add entities for a new Hyperion instance.""" + assert server_id + sensors = [ + HyperionVisiblePrioritySensor( + server_id, + instance_num, + instance_name, + entry_data[CONF_INSTANCE_CLIENTS][instance_num], + PRIORITY_SENSOR_DESCRIPTION, + ) + ] + + async_add_entities(sensors) + + @callback + def instance_remove(instance_num: int) -> None: + """Remove entities for an old Hyperion instance.""" + assert server_id + + for sensor in SENSORS: + async_dispatcher_send( + hass, + SIGNAL_ENTITY_REMOVE.format( + _sensor_unique_id(server_id, instance_num, sensor), + ), + ) + + listen_for_instance_updates(hass, config_entry, instance_add, instance_remove) + + +class HyperionSensor(SensorEntity): + """Sensor class.""" + + _attr_has_entity_name = True + _attr_should_poll = False + + def __init__( + self, + server_id: str, + instance_num: int, + instance_name: str, + hyperion_client: client.HyperionClient, + entity_description: SensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + self.entity_description = entity_description + self._client = hyperion_client + self._attr_native_value = None + self._client_callbacks: dict[str, Any] = {} + + device_id = get_hyperion_device_id(server_id, instance_num) + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, device_id)}, + manufacturer=HYPERION_MANUFACTURER_NAME, + model=HYPERION_MODEL_NAME, + name=instance_name, + configuration_url=self._client.remote_url, + ) + + @property + def available(self) -> bool: + """Return server availability.""" + return bool(self._client.has_loaded_state) + + async def async_added_to_hass(self) -> None: + """Register callbacks when entity added to hass.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_ENTITY_REMOVE.format(self._attr_unique_id), + functools.partial(self.async_remove, force_remove=True), + ) + ) + + self._client.add_callbacks(self._client_callbacks) + + async def async_will_remove_from_hass(self) -> None: + """Cleanup prior to hass removal.""" + self._client.remove_callbacks(self._client_callbacks) + + +class HyperionVisiblePrioritySensor(HyperionSensor): + """Class that displays the visible priority of a Hyperion instance.""" + + def __init__( + self, + server_id: str, + instance_num: int, + instance_name: str, + hyperion_client: client.HyperionClient, + entity_description: SensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + + super().__init__( + server_id, instance_num, instance_name, hyperion_client, entity_description + ) + + self._attr_unique_id = _sensor_unique_id( + server_id, instance_num, TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY + ) + + self._client_callbacks = { + f"{KEY_PRIORITIES}-{KEY_UPDATE}": self._update_priorities + } + + @callback + def _update_priorities(self, _: dict[str, Any] | None = None) -> None: + """Update Hyperion priorities.""" + state_value = None + attrs = {} + + for priority in self._client.priorities or []: + if not (KEY_VISIBLE in priority and priority[KEY_VISIBLE] is True): + continue + + if priority[KEY_COMPONENTID] == "COLOR": + state_value = priority[KEY_VALUE][KEY_RGB] + else: + state_value = priority[KEY_OWNER] + + attrs = { + "component_id": priority[KEY_COMPONENTID], + "origin": priority[KEY_ORIGIN], + "priority": priority[KEY_PRIORITY], + "owner": priority[KEY_OWNER], + } + + if priority[KEY_COMPONENTID] == "COLOR": + attrs["color"] = priority[KEY_VALUE] + else: + attrs["color"] = None + + self._attr_native_value = state_value + self._attr_extra_state_attributes = attrs + + self.async_write_ha_state() diff --git a/homeassistant/components/hyperion/strings.json b/homeassistant/components/hyperion/strings.json index 8d7e3751c4c..79c226b71eb 100644 --- a/homeassistant/components/hyperion/strings.json +++ b/homeassistant/components/hyperion/strings.json @@ -80,6 +80,11 @@ "usb_capture": { "name": "Component USB capture" } + }, + "sensor": { + "visible_priority": { + "name": "Visible priority" + } } } } diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 4715441a5de..0dd2ad9fc94 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -338,6 +338,7 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None: const.KEY_PRIORITY: TEST_PRIORITY, const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR, const.KEY_VALUE: {const.KEY_RGB: (0, 255, 255)}, + const.KEY_OWNER: "System", } ] @@ -432,6 +433,7 @@ async def test_light_async_turn_on(hass: HomeAssistant) -> None: const.KEY_PRIORITY: TEST_PRIORITY, const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR, const.KEY_VALUE: {const.KEY_RGB: (0, 0, 255)}, + const.KEY_OWNER: "System", } ] call_registered_callback(client, "priorities-update") @@ -564,6 +566,8 @@ async def test_light_async_updates_from_hyperion_client( const.KEY_PRIORITY: TEST_PRIORITY, const.KEY_COMPONENTID: const.KEY_COMPONENTID_EFFECT, const.KEY_OWNER: effect, + const.KEY_VISIBLE: True, + const.KEY_ORIGIN: "System", } ] @@ -581,6 +585,9 @@ async def test_light_async_updates_from_hyperion_client( const.KEY_PRIORITY: TEST_PRIORITY, const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR, const.KEY_VALUE: {const.KEY_RGB: rgb}, + const.KEY_VISIBLE: True, + const.KEY_ORIGIN: "System", + const.KEY_OWNER: "System", } ] @@ -625,6 +632,9 @@ async def test_light_async_updates_from_hyperion_client( const.KEY_PRIORITY: TEST_PRIORITY, const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR, const.KEY_VALUE: {const.KEY_RGB: rgb}, + const.KEY_VISIBLE: True, + const.KEY_ORIGIN: "System", + const.KEY_OWNER: "System", } ] call_registered_callback(client, "client-update", {"loaded-state": True}) @@ -645,6 +655,7 @@ async def test_full_state_loaded_on_start(hass: HomeAssistant) -> None: const.KEY_PRIORITY: TEST_PRIORITY, const.KEY_COMPONENTID: const.KEY_COMPONENTID_COLOR, const.KEY_VALUE: {const.KEY_RGB: (0, 100, 100)}, + const.KEY_OWNER: "System", } ] client.effects = [{const.KEY_NAME: "One"}, {const.KEY_NAME: "Two"}] diff --git a/tests/components/hyperion/test_sensor.py b/tests/components/hyperion/test_sensor.py new file mode 100644 index 00000000000..65991b4b7e1 --- /dev/null +++ b/tests/components/hyperion/test_sensor.py @@ -0,0 +1,178 @@ +"""Tests for the Hyperion integration.""" + +from hyperion.const import ( + KEY_ACTIVE, + KEY_COMPONENTID, + KEY_ORIGIN, + KEY_OWNER, + KEY_PRIORITY, + KEY_RGB, + KEY_VALUE, + KEY_VISIBLE, +) + +from homeassistant.components.hyperion import get_hyperion_device_id +from homeassistant.components.hyperion.const import ( + DOMAIN, + HYPERION_MANUFACTURER_NAME, + HYPERION_MODEL_NAME, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.util import slugify + +from . import ( + TEST_CONFIG_ENTRY_ID, + TEST_INSTANCE, + TEST_INSTANCE_1, + TEST_SYSINFO_ID, + call_registered_callback, + create_mock_client, + setup_test_config_entry, +) + +TEST_COMPONENTS = [ + {"enabled": True, "name": "VISIBLE_PRIORITY"}, +] + +TEST_SENSOR_BASE_ENTITY_ID = "sensor.test_instance_1" +TEST_VISIBLE_EFFECT_SENSOR_ID = "sensor.test_instance_1_visible_priority" + + +async def test_sensor_has_correct_entities(hass: HomeAssistant) -> None: + """Test that the correct sensor entities are created.""" + client = create_mock_client() + client.components = TEST_COMPONENTS + await setup_test_config_entry(hass, hyperion_client=client) + + for component in TEST_COMPONENTS: + name = slugify(component["name"]) + entity_id = f"{TEST_SENSOR_BASE_ENTITY_ID}_{name}" + entity_state = hass.states.get(entity_id) + assert entity_state, f"Couldn't find entity: {entity_id}" + + +async def test_device_info(hass: HomeAssistant) -> None: + """Verify device information includes expected details.""" + client = create_mock_client() + client.components = TEST_COMPONENTS + await setup_test_config_entry(hass, hyperion_client=client) + + device_identifer = get_hyperion_device_id(TEST_SYSINFO_ID, TEST_INSTANCE) + device_registry = dr.async_get(hass) + + device = device_registry.async_get_device(identifiers={(DOMAIN, device_identifer)}) + assert device + assert device.config_entries == {TEST_CONFIG_ENTRY_ID} + assert device.identifiers == {(DOMAIN, device_identifer)} + assert device.manufacturer == HYPERION_MANUFACTURER_NAME + assert device.model == HYPERION_MODEL_NAME + assert device.name == TEST_INSTANCE_1["friendly_name"] + + entity_registry = er.async_get(hass) + entities_from_device = [ + entry.entity_id + for entry in er.async_entries_for_device(entity_registry, device.id) + ] + + for component in TEST_COMPONENTS: + name = slugify(component["name"]) + entity_id = TEST_SENSOR_BASE_ENTITY_ID + "_" + name + assert entity_id in entities_from_device + + +async def test_visible_effect_state_changes(hass: HomeAssistant) -> None: + """Verify that state changes are processed as expected for visible effect sensor.""" + client = create_mock_client() + client.components = TEST_COMPONENTS + await setup_test_config_entry(hass, hyperion_client=client) + + # Simulate a platform grabber effect state callback from Hyperion. + client.priorities = [ + { + KEY_ACTIVE: True, + KEY_COMPONENTID: "GRABBER", + KEY_ORIGIN: "System", + KEY_OWNER: "X11", + KEY_PRIORITY: 250, + KEY_VISIBLE: True, + } + ] + + call_registered_callback(client, "priorities-update") + entity_state = hass.states.get(TEST_VISIBLE_EFFECT_SENSOR_ID) + assert entity_state + assert entity_state.state == client.priorities[0][KEY_OWNER] + assert ( + entity_state.attributes["component_id"] == client.priorities[0][KEY_COMPONENTID] + ) + assert entity_state.attributes["origin"] == client.priorities[0][KEY_ORIGIN] + assert entity_state.attributes["priority"] == client.priorities[0][KEY_PRIORITY] + + # Simulate an effect state callback from Hyperion. + client.priorities = [ + { + KEY_ACTIVE: True, + KEY_COMPONENTID: "EFFECT", + KEY_ORIGIN: "System", + KEY_OWNER: "Warm mood blobs", + KEY_PRIORITY: 250, + KEY_VISIBLE: True, + } + ] + + call_registered_callback(client, "priorities-update") + entity_state = hass.states.get(TEST_VISIBLE_EFFECT_SENSOR_ID) + assert entity_state + assert entity_state.state == client.priorities[0][KEY_OWNER] + assert ( + entity_state.attributes["component_id"] == client.priorities[0][KEY_COMPONENTID] + ) + assert entity_state.attributes["origin"] == client.priorities[0][KEY_ORIGIN] + assert entity_state.attributes["priority"] == client.priorities[0][KEY_PRIORITY] + + # Simulate a USB Capture state callback from Hyperion. + client.priorities = [ + { + KEY_ACTIVE: True, + KEY_COMPONENTID: "V4L", + KEY_ORIGIN: "System", + KEY_OWNER: "V4L2", + KEY_PRIORITY: 250, + KEY_VISIBLE: True, + } + ] + + call_registered_callback(client, "priorities-update") + entity_state = hass.states.get(TEST_VISIBLE_EFFECT_SENSOR_ID) + assert entity_state + assert entity_state.state == client.priorities[0][KEY_OWNER] + assert ( + entity_state.attributes["component_id"] == client.priorities[0][KEY_COMPONENTID] + ) + assert entity_state.attributes["origin"] == client.priorities[0][KEY_ORIGIN] + assert entity_state.attributes["priority"] == client.priorities[0][KEY_PRIORITY] + + # Simulate a color effect state callback from Hyperion. + client.priorities = [ + { + KEY_ACTIVE: True, + KEY_COMPONENTID: "COLOR", + KEY_ORIGIN: "System", + KEY_OWNER: "System", + KEY_PRIORITY: 250, + KEY_VALUE: {KEY_RGB: [0, 0, 0]}, + KEY_VISIBLE: True, + } + ] + + call_registered_callback(client, "priorities-update") + entity_state = hass.states.get(TEST_VISIBLE_EFFECT_SENSOR_ID) + assert entity_state + assert entity_state.state == str(client.priorities[0][KEY_VALUE][KEY_RGB]) + assert ( + entity_state.attributes["component_id"] == client.priorities[0][KEY_COMPONENTID] + ) + assert entity_state.attributes["origin"] == client.priorities[0][KEY_ORIGIN] + assert entity_state.attributes["priority"] == client.priorities[0][KEY_PRIORITY] + assert entity_state.attributes["color"] == client.priorities[0][KEY_VALUE] From 91b2dd4b83b7bede3c9bb1f98f93ade62309f99c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 17:20:27 +0100 Subject: [PATCH 0259/1691] Add icon translations to Sensibo (#112222) * Add icon translations to Sensibo * Add icon translations to Sensibo * Add icon translations to Sensibo --- .../components/sensibo/binary_sensor.py | 3 -- homeassistant/components/sensibo/button.py | 1 - homeassistant/components/sensibo/icons.json | 54 +++++++++++++++++++ homeassistant/components/sensibo/select.py | 2 - homeassistant/components/sensibo/sensor.py | 9 ---- homeassistant/components/sensibo/switch.py | 2 - homeassistant/components/sensibo/update.py | 1 - .../sensibo/snapshots/test_sensor.ambr | 1 - 8 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/sensibo/icons.json diff --git a/homeassistant/components/sensibo/binary_sensor.py b/homeassistant/components/sensibo/binary_sensor.py index 5cd71a2b0e4..c619bf8a9be 100644 --- a/homeassistant/components/sensibo/binary_sensor.py +++ b/homeassistant/components/sensibo/binary_sensor.py @@ -70,13 +70,11 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionBinarySensorEntityDescription, ...] = ( key="is_main_sensor", translation_key="is_main_sensor", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:connection", value_fn=lambda data: data.is_main_sensor, ), SensiboMotionBinarySensorEntityDescription( key="motion", device_class=BinarySensorDeviceClass.MOTION, - icon="mdi:motion-sensor", value_fn=lambda data: data.motion, ), ) @@ -86,7 +84,6 @@ MOTION_DEVICE_SENSOR_TYPES: tuple[SensiboDeviceBinarySensorEntityDescription, .. key="room_occupied", translation_key="room_occupied", device_class=BinarySensorDeviceClass.MOTION, - icon="mdi:motion-sensor", value_fn=lambda data: data.room_occupied, ), ) diff --git a/homeassistant/components/sensibo/button.py b/homeassistant/components/sensibo/button.py index 942f7eaeb00..5f7c7a88f41 100644 --- a/homeassistant/components/sensibo/button.py +++ b/homeassistant/components/sensibo/button.py @@ -34,7 +34,6 @@ class SensiboButtonEntityDescription( DEVICE_BUTTON_TYPES = SensiboButtonEntityDescription( key="reset_filter", translation_key="reset_filter", - icon="mdi:air-filter", entity_category=EntityCategory.CONFIG, data_key="filter_clean", ) diff --git a/homeassistant/components/sensibo/icons.json b/homeassistant/components/sensibo/icons.json new file mode 100644 index 00000000000..e26840e48eb --- /dev/null +++ b/homeassistant/components/sensibo/icons.json @@ -0,0 +1,54 @@ +{ + "entity": { + "binary_sensor": { + "is_main_sensor": { + "default": "mdi:connection" + } + }, + "button": { + "reset_filter": { + "default": "mdi:air-filter" + } + }, + "select": { + "horizontalswing": { + "default": "mdi:air-conditioner" + }, + "light": { + "default": "mdi:flashlight" + } + }, + "sensor": { + "filter_last_reset": { + "default": "mdi:timer" + }, + "battery_voltage": { + "default": "mdi:battery" + }, + "sensitivity": { + "default": "mdi:air-filter" + }, + "timer_time": { + "default": "mdi:timer" + }, + "airq_tvoc": { + "default": "mdi:air-filter" + } + }, + "switch": { + "timer_on_switch": { + "default": "mdi:timer" + }, + "climate_react_switch": { + "default": "mdi:wizard-hat" + } + } + }, + "services": { + "assume_state": "mdi:shape-outline", + "enable_timer": "mdi:timer-play", + "enable_pure_boost": "mdi:air-filter", + "full_state": "mdi:shape", + "enable_climate_react": "mdi:wizard-hat" + } +} diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index bbac3fbdbd0..9e6179dc1c4 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -41,7 +41,6 @@ DEVICE_SELECT_TYPES = ( SensiboSelectEntityDescription( key="horizontalSwing", data_key="horizontal_swing_mode", - icon="mdi:air-conditioner", value_fn=lambda data: data.horizontal_swing_mode, options_fn=lambda data: data.horizontal_swing_modes, translation_key="horizontalswing", @@ -50,7 +49,6 @@ DEVICE_SELECT_TYPES = ( SensiboSelectEntityDescription( key="light", data_key="light_mode", - icon="mdi:flashlight", value_fn=lambda data: data.light_mode, options_fn=lambda data: data.light_modes, translation_key="light", diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index 805b888204b..ddbc0561d2f 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -69,7 +69,6 @@ FILTER_LAST_RESET_DESCRIPTION = SensiboDeviceSensorEntityDescription( key="filter_last_reset", translation_key="filter_last_reset", device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:timer", value_fn=lambda data: data.filter_last_reset, extra_fn=None, ) @@ -82,7 +81,6 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionSensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:wifi", value_fn=lambda data: data.rssi, entity_registry_enabled_default=False, ), @@ -93,7 +91,6 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionSensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:battery", value_fn=lambda data: data.battery_voltage, ), SensiboMotionSensorEntityDescription( @@ -101,7 +98,6 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:water", value_fn=lambda data: data.humidity, ), SensiboMotionSensorEntityDescription( @@ -109,7 +105,6 @@ MOTION_SENSOR_TYPES: tuple[SensiboMotionSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:thermometer", value_fn=lambda data: data.temperature, ), ) @@ -119,14 +114,12 @@ PURE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.PM25, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:air-filter", value_fn=lambda data: data.pm25, extra_fn=None, ), SensiboDeviceSensorEntityDescription( key="pure_sensitivity", translation_key="sensitivity", - icon="mdi:air-filter", value_fn=lambda data: data.pure_sensitivity, extra_fn=None, ), @@ -138,7 +131,6 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( key="timer_time", translation_key="timer_time", device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:timer", value_fn=lambda data: data.timer_time, extra_fn=lambda data: {"id": data.timer_id, "turn_on": data.timer_state_on}, ), @@ -188,7 +180,6 @@ AIRQ_SENSOR_TYPES: tuple[SensiboDeviceSensorEntityDescription, ...] = ( translation_key="airq_tvoc", native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:air-filter", value_fn=lambda data: data.tvoc, extra_fn=None, ), diff --git a/homeassistant/components/sensibo/switch.py b/homeassistant/components/sensibo/switch.py index 0911985ed7d..be56759fb95 100644 --- a/homeassistant/components/sensibo/switch.py +++ b/homeassistant/components/sensibo/switch.py @@ -47,7 +47,6 @@ DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( key="timer_on_switch", translation_key="timer_on_switch", device_class=SwitchDeviceClass.SWITCH, - icon="mdi:timer", value_fn=lambda data: data.timer_on, extra_fn=lambda data: {"id": data.timer_id, "turn_on": data.timer_state_on}, command_on="async_turn_on_timer", @@ -58,7 +57,6 @@ DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( key="climate_react_switch", translation_key="climate_react_switch", device_class=SwitchDeviceClass.SWITCH, - icon="mdi:wizard-hat", value_fn=lambda data: data.smart_on, extra_fn=lambda data: {"type": data.smart_type}, command_on="async_turn_on_off_smart", diff --git a/homeassistant/components/sensibo/update.py b/homeassistant/components/sensibo/update.py index c51d57dd9d1..b2dc31aa068 100644 --- a/homeassistant/components/sensibo/update.py +++ b/homeassistant/components/sensibo/update.py @@ -43,7 +43,6 @@ DEVICE_SENSOR_TYPES: tuple[SensiboDeviceUpdateEntityDescription, ...] = ( key="fw_ver_available", device_class=UpdateDeviceClass.FIRMWARE, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:rocket-launch", value_version=lambda data: data.fw_ver, value_available=lambda data: data.fw_ver_available, ), diff --git a/tests/components/sensibo/snapshots/test_sensor.ambr b/tests/components/sensibo/snapshots/test_sensor.ambr index 4522071049d..d645bdbd383 100644 --- a/tests/components/sensibo/snapshots/test_sensor.ambr +++ b/tests/components/sensibo/snapshots/test_sensor.ambr @@ -3,7 +3,6 @@ ReadOnlyDict({ 'device_class': 'pm25', 'friendly_name': 'Kitchen PM2.5', - 'icon': 'mdi:air-filter', 'state_class': , 'unit_of_measurement': 'µg/m³', }) From 3d1fbe444e62f2b14aa8f82ea8fd17f655345ebb Mon Sep 17 00:00:00 2001 From: hopkins-tk Date: Mon, 4 Mar 2024 17:20:46 +0100 Subject: [PATCH 0260/1691] Fix authentication issues for asekopool (#99495) * fix: handle authentication issues for asekopool * fix: handle authentication issues for asekopool * feat: add config migration * feat: add re-authentication step * fix: add reauth message * fix: add tests for config flow * fix: tests clean up * Update homeassistant/components/aseko_pool_live/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/aseko_pool_live/__init__.py Co-authored-by: Erik Montnemery * fix: Reformat code * Fix bad merge * Really fix bad merge * Update config_flow.py --------- Co-authored-by: Erik Montnemery --- .../components/aseko_pool_live/__init__.py | 31 +++- .../components/aseko_pool_live/config_flow.py | 116 +++++++++---- .../components/aseko_pool_live/manifest.json | 2 +- .../components/aseko_pool_live/strings.json | 9 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../aseko_pool_live/test_config_flow.py | 158 +++++++++++++++--- 7 files changed, 261 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/aseko_pool_live/__init__.py b/homeassistant/components/aseko_pool_live/__init__.py index b09682fcaf9..8f973af7cf7 100644 --- a/homeassistant/components/aseko_pool_live/__init__.py +++ b/homeassistant/components/aseko_pool_live/__init__.py @@ -3,12 +3,12 @@ from __future__ import annotations import logging -from aioaseko import APIUnavailable, MobileAccount +from aioaseko import APIUnavailable, InvalidAuthCredentials, MobileAccount from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ACCESS_TOKEN, Platform +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -22,11 +22,15 @@ PLATFORMS: list[str] = [Platform.BINARY_SENSOR, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Aseko Pool Live from a config entry.""" account = MobileAccount( - async_get_clientsession(hass), access_token=entry.data[CONF_ACCESS_TOKEN] + async_get_clientsession(hass), + username=entry.data[CONF_EMAIL], + password=entry.data[CONF_PASSWORD], ) try: units = await account.get_units() + except InvalidAuthCredentials as err: + raise ConfigEntryAuthFailed from err except APIUnavailable as err: raise ConfigEntryNotReady from err @@ -48,3 +52,22 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok + + +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Migrate old entry.""" + _LOGGER.debug("Migrating from version %s", config_entry.version) + + if config_entry.version == 1: + new = { + CONF_EMAIL: config_entry.title, + CONF_PASSWORD: "", + } + + hass.config_entries.async_update_entry(config_entry, data=new, version=2) + + _LOGGER.debug("Migration to version %s successful", config_entry.version) + return True + + _LOGGER.error("Attempt to migrate from unknown version %s", config_entry.version) + return False diff --git a/homeassistant/components/aseko_pool_live/config_flow.py b/homeassistant/components/aseko_pool_live/config_flow.py index ef9ea9ef29e..49143652fa9 100644 --- a/homeassistant/components/aseko_pool_live/config_flow.py +++ b/homeassistant/components/aseko_pool_live/config_flow.py @@ -1,19 +1,15 @@ """Config flow for Aseko Pool Live integration.""" from __future__ import annotations +from collections.abc import Mapping import logging from typing import Any -from aioaseko import APIUnavailable, InvalidAuthCredentials, MobileAccount, WebAccount +from aioaseko import APIUnavailable, InvalidAuthCredentials, WebAccount import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import ( - CONF_ACCESS_TOKEN, - CONF_EMAIL, - CONF_PASSWORD, - CONF_UNIQUE_ID, -) +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_UNIQUE_ID from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN @@ -24,7 +20,16 @@ _LOGGER = logging.getLogger(__name__) class AsekoConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Aseko Pool Live.""" - VERSION = 1 + VERSION = 2 + + data_schema = vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + } + ) + + reauth_entry: ConfigEntry | None = None async def get_account_info(self, email: str, password: str) -> dict: """Get account info from the mobile API and the web API.""" @@ -33,19 +38,83 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN): web_account = WebAccount(session, email, password) web_account_info = await web_account.login() - mobile_account = MobileAccount(session, email, password) - await mobile_account.login() - return { - CONF_ACCESS_TOKEN: mobile_account.access_token, - CONF_EMAIL: web_account_info.email, + CONF_EMAIL: email, + CONF_PASSWORD: password, CONF_UNIQUE_ID: web_account_info.user_id, } async def async_step_user( - self, user_input: dict[str, Any] | None = None + self, user_input: Mapping[str, Any] | None = None ) -> ConfigFlowResult: """Handle the initial step.""" + + self.reauth_entry = None + errors = {} + + if user_input is not None: + try: + info = await self.get_account_info( + user_input[CONF_EMAIL], user_input[CONF_PASSWORD] + ) + except APIUnavailable: + errors["base"] = "cannot_connect" + except InvalidAuthCredentials: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return await self.async_store_credentials(info) + + return self.async_show_form( + step_id="user", + data_schema=self.data_schema, + errors=errors, + ) + + async def async_store_credentials(self, info: dict[str, Any]) -> ConfigFlowResult: + """Store validated credentials.""" + + if self.reauth_entry: + self.hass.config_entries.async_update_entry( + self.reauth_entry, + title=info[CONF_EMAIL], + data={ + CONF_EMAIL: info[CONF_EMAIL], + CONF_PASSWORD: info[CONF_PASSWORD], + }, + ) + await self.hass.config_entries.async_reload(self.reauth_entry.entry_id) + return self.async_abort(reason="reauth_successful") + + await self.async_set_unique_id(info[CONF_UNIQUE_ID]) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=info[CONF_EMAIL], + data={ + CONF_EMAIL: info[CONF_EMAIL], + CONF_PASSWORD: info[CONF_PASSWORD], + }, + ) + + async def async_step_reauth( + self, user_input: Mapping[str, Any] + ) -> ConfigFlowResult: + """Perform reauth upon an API authentication error.""" + + self.reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + + return await self.async_step_reauth_confirm(user_input) + + async def async_step_reauth_confirm( + self, user_input: Mapping | None = None + ) -> ConfigFlowResult: + """Dialog that informs the user that reauth is required.""" + errors = {} if user_input is not None: try: @@ -60,21 +129,10 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(info[CONF_UNIQUE_ID]) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=info[CONF_EMAIL], - data={CONF_ACCESS_TOKEN: info[CONF_ACCESS_TOKEN]}, - ) + return await self.async_store_credentials(info) return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_EMAIL): str, - vol.Required(CONF_PASSWORD): str, - } - ), + step_id="reauth_confirm", + data_schema=self.data_schema, errors=errors, ) diff --git a/homeassistant/components/aseko_pool_live/manifest.json b/homeassistant/components/aseko_pool_live/manifest.json index 487032bb09d..f7c29277977 100644 --- a/homeassistant/components/aseko_pool_live/manifest.json +++ b/homeassistant/components/aseko_pool_live/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/aseko_pool_live", "iot_class": "cloud_polling", "loggers": ["aioaseko"], - "requirements": ["aioaseko==0.0.2"] + "requirements": ["aioaseko==0.1.1"] } diff --git a/homeassistant/components/aseko_pool_live/strings.json b/homeassistant/components/aseko_pool_live/strings.json index 2a6df30b148..7f77b9ec69b 100644 --- a/homeassistant/components/aseko_pool_live/strings.json +++ b/homeassistant/components/aseko_pool_live/strings.json @@ -6,6 +6,12 @@ "email": "[%key:common::config_flow::data::email%]", "password": "[%key:common::config_flow::data::password%]" } + }, + "reauth_confirm": { + "data": { + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { @@ -14,7 +20,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "entity": { diff --git a/requirements_all.txt b/requirements_all.txt index 47e4dcf5971..6bf84d90c45 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -200,7 +200,7 @@ aioambient==2024.01.0 aioapcaccess==0.4.2 # homeassistant.components.aseko_pool_live -aioaseko==0.0.2 +aioaseko==0.1.1 # homeassistant.components.asuswrt aioasuswrt==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18f8208ae26..16284a1f096 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -179,7 +179,7 @@ aioambient==2024.01.0 aioapcaccess==0.4.2 # homeassistant.components.aseko_pool_live -aioaseko==0.0.2 +aioaseko==0.1.1 # homeassistant.components.asuswrt aioasuswrt==1.4.0 diff --git a/tests/components/aseko_pool_live/test_config_flow.py b/tests/components/aseko_pool_live/test_config_flow.py index 9d7a5b83d78..fc109f8ac4e 100644 --- a/tests/components/aseko_pool_live/test_config_flow.py +++ b/tests/components/aseko_pool_live/test_config_flow.py @@ -1,37 +1,40 @@ """Test the Aseko Pool Live config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import patch from aioaseko import AccountInfo, APIUnavailable, InvalidAuthCredentials import pytest -from homeassistant import config_entries, setup +from homeassistant import config_entries from homeassistant.components.aseko_pool_live.const import DOMAIN -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_EMAIL, CONF_PASSWORD +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from tests.common import MockConfigEntry -async def test_form(hass: HomeAssistant) -> None: + +async def test_async_step_user_form(hass: HomeAssistant) -> None: """Test we get the form.""" - await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == FlowResultType.FORM assert result["errors"] == {} + +async def test_async_step_user_success(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + with patch( "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), ), patch( - "homeassistant.components.aseko_pool_live.config_flow.MobileAccount", - ) as mock_mobile_account, patch( "homeassistant.components.aseko_pool_live.async_setup_entry", return_value=True, ) as mock_setup_entry: - mobile_account = mock_mobile_account.return_value - mobile_account.login = AsyncMock() - mobile_account.access_token = "any_access_token" result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -43,23 +46,56 @@ async def test_form(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.CREATE_ENTRY assert result2["title"] == "aseko@example.com" - assert result2["data"] == {CONF_ACCESS_TOKEN: "any_access_token"} + assert result2["data"] == { + CONF_EMAIL: "aseko@example.com", + CONF_PASSWORD: "passw0rd", + } assert len(mock_setup_entry.mock_calls) == 1 @pytest.mark.parametrize( - ("error_web", "error_mobile", "reason"), + ("error_web", "reason"), [ - (APIUnavailable, None, "cannot_connect"), - (InvalidAuthCredentials, None, "invalid_auth"), - (Exception, None, "unknown"), - (None, APIUnavailable, "cannot_connect"), - (None, InvalidAuthCredentials, "invalid_auth"), - (None, Exception, "unknown"), + (APIUnavailable, "cannot_connect"), + (InvalidAuthCredentials, "invalid_auth"), + (Exception, "unknown"), + ], +) +async def test_async_step_user_exception( + hass: HomeAssistant, error_web: Exception, reason: str +) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", + return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), + side_effect=error_web, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "aseko@example.com", + CONF_PASSWORD: "passw0rd", + }, + ) + + assert result2["type"] == FlowResultType.FORM + assert result2["errors"] == {"base": reason} + + +@pytest.mark.parametrize( + ("error_web", "reason"), + [ + (APIUnavailable, "cannot_connect"), + (InvalidAuthCredentials, "invalid_auth"), + (Exception, "unknown"), ], ) async def test_get_account_info_exceptions( - hass: HomeAssistant, error_web: Exception, error_mobile: Exception, reason: str + hass: HomeAssistant, error_web: Exception, reason: str ) -> None: """Test we handle config flow exceptions.""" result = await hass.config_entries.flow.async_init( @@ -70,9 +106,6 @@ async def test_get_account_info_exceptions( "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), side_effect=error_web, - ), patch( - "homeassistant.components.aseko_pool_live.config_flow.MobileAccount.login", - side_effect=error_mobile, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -84,3 +117,84 @@ async def test_get_account_info_exceptions( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": reason} + + +async def test_async_step_reauth_success(hass: HomeAssistant) -> None: + """Test successful reauthentication.""" + + mock_entry = MockConfigEntry( + domain=DOMAIN, + unique_id="UID", + data={CONF_EMAIL: "aseko@example.com"}, + ) + mock_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": mock_entry.entry_id, + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "reauth_confirm" + assert result["errors"] == {} + + with patch( + "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", + return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_EMAIL: "aseko@example.com", CONF_PASSWORD: "passw0rd"}, + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reauth_successful" + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + ("error_web", "reason"), + [ + (APIUnavailable, "cannot_connect"), + (InvalidAuthCredentials, "invalid_auth"), + (Exception, "unknown"), + ], +) +async def test_async_step_reauth_exception( + hass: HomeAssistant, error_web: Exception, reason: str +) -> None: + """Test we get the form.""" + + mock_entry = MockConfigEntry( + domain=DOMAIN, + unique_id="UID", + data={CONF_EMAIL: "aseko@example.com"}, + ) + mock_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": mock_entry.entry_id, + }, + ) + + with patch( + "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", + return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), + side_effect=error_web, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_EMAIL: "aseko@example.com", + CONF_PASSWORD: "passw0rd", + }, + ) + + assert result2["type"] == FlowResultType.FORM + assert result2["errors"] == {"base": reason} From 4bc9d806e2fdd92c78a8b81272585b412ecc61ae Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 4 Mar 2024 18:09:49 +0100 Subject: [PATCH 0261/1691] Update frontend to 20240304.0 (#112263) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index d975daad508..0606312aaea 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240301.0"] + "requirements": ["home-assistant-frontend==20240304.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 547ed5a129a..99f3eb074bd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240301.0 +home-assistant-frontend==20240304.0 home-assistant-intents==2024.2.28 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 6bf84d90c45..8226b2f4422 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ hole==0.8.0 holidays==0.43 # homeassistant.components.frontend -home-assistant-frontend==20240301.0 +home-assistant-frontend==20240304.0 # homeassistant.components.conversation home-assistant-intents==2024.2.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 16284a1f096..f17011f6244 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ hole==0.8.0 holidays==0.43 # homeassistant.components.frontend -home-assistant-frontend==20240301.0 +home-assistant-frontend==20240304.0 # homeassistant.components.conversation home-assistant-intents==2024.2.28 From b195c3fa7b6669a633232614e6c869bcf0aa4820 Mon Sep 17 00:00:00 2001 From: Xiretza Date: Mon, 4 Mar 2024 17:22:18 +0000 Subject: [PATCH 0262/1691] Fix spaceapi attribute for closed icon (#108596) spaceapi: fix attribute for closed icon The JSON field is "state.icon.closed", not "state.icon.close". --- homeassistant/components/spaceapi/__init__.py | 4 ++-- tests/components/spaceapi/test_init.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index c18f150a925..1a25986a243 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -36,7 +36,7 @@ ATTR_RADIO_SHOW = "radio_show" ATTR_LAT = "lat" ATTR_LON = "lon" ATTR_API = "api" -ATTR_CLOSE = "close" +ATTR_CLOSED = "closed" ATTR_CONTACT = "contact" ATTR_ISSUE_REPORT_CHANNELS = "issue_report_channels" ATTR_LASTCHANGE = "lastchange" @@ -292,7 +292,7 @@ class APISpaceApiView(HomeAssistantView): with suppress(KeyError): state[ATTR_ICON] = { ATTR_OPEN: spaceapi["state"][CONF_ICON_OPEN], - ATTR_CLOSE: spaceapi["state"][CONF_ICON_CLOSED], + ATTR_CLOSED: spaceapi["state"][CONF_ICON_CLOSED], } data = { diff --git a/tests/components/spaceapi/test_init.py b/tests/components/spaceapi/test_init.py index ac892eeb2d8..0ab91d48fcc 100644 --- a/tests/components/spaceapi/test_init.py +++ b/tests/components/spaceapi/test_init.py @@ -116,7 +116,7 @@ async def test_spaceapi_get(hass: HomeAssistant, mock_client) -> None: assert data["location"]["lon"] == -117.22743 assert data["state"]["open"] == "null" assert data["state"]["icon"]["open"] == "https://home-assistant.io/open.png" - assert data["state"]["icon"]["close"] == "https://home-assistant.io/close.png" + assert data["state"]["icon"]["closed"] == "https://home-assistant.io/close.png" assert data["spacefed"]["spacenet"] == bool(1) assert data["spacefed"]["spacesaml"] == bool(0) assert data["spacefed"]["spacephone"] == bool(1) From b5528de80783430ac176513d4011805e98657622 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Tue, 5 Mar 2024 03:42:56 +1000 Subject: [PATCH 0263/1691] Add sensor platform to Teslemetry (#109088) * Adding Energy * Adding Energy * Work in progress * Add fixtures * Add product info * Add sensors * Add icons * Update metadata * Use SensorEntityDescription for Energy * Use ENERGY_STORAGE * Add tests * Fix coverage * Update wall connector precision and units * Change devices * Fix serial number * Add icons and VIN to wall connector * Fix serial number again * Update snapshots * Use timestamp for minutes to arrival * Cleanup snapshot * Improvements * Update fixture * Add "code" to translations * Add "code" to snapshot * Use async_add_entities once * Disable a bunch of sensors * Ruff * Improve fixture and test coverage * Regen Snapshots * Add init to coordinator --- .../components/teslemetry/__init__.py | 64 +- .../components/teslemetry/climate.py | 2 +- .../components/teslemetry/coordinator.py | 43 +- homeassistant/components/teslemetry/entity.py | 71 +- homeassistant/components/teslemetry/models.py | 25 +- homeassistant/components/teslemetry/sensor.py | 461 ++++ .../components/teslemetry/strings.json | 122 + tests/components/teslemetry/conftest.py | 13 +- tests/components/teslemetry/const.py | 1 + .../teslemetry/fixtures/live_status.json | 33 + .../teslemetry/fixtures/products.json | 52 +- .../teslemetry/fixtures/site_info.json | 87 + .../teslemetry/fixtures/vehicle_data.json | 16 +- .../teslemetry/snapshots/test_sensor.ambr | 2208 +++++++++++++++++ tests/components/teslemetry/test_init.py | 20 +- tests/components/teslemetry/test_sensor.py | 26 + 16 files changed, 3186 insertions(+), 58 deletions(-) create mode 100644 homeassistant/components/teslemetry/sensor.py create mode 100644 tests/components/teslemetry/fixtures/live_status.json create mode 100644 tests/components/teslemetry/fixtures/site_info.json create mode 100644 tests/components/teslemetry/snapshots/test_sensor.ambr create mode 100644 tests/components/teslemetry/test_sensor.py diff --git a/homeassistant/components/teslemetry/__init__.py b/homeassistant/components/teslemetry/__init__.py index fb74e905181..f2532fcaf5b 100644 --- a/homeassistant/components/teslemetry/__init__.py +++ b/homeassistant/components/teslemetry/__init__.py @@ -2,7 +2,7 @@ import asyncio from typing import Final -from tesla_fleet_api import Teslemetry, VehicleSpecific +from tesla_fleet_api import EnergySpecific, Teslemetry, VehicleSpecific from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError from homeassistant.config_entries import ConfigEntry @@ -12,12 +12,13 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN, LOGGER -from .coordinator import TeslemetryVehicleDataCoordinator -from .models import TeslemetryVehicleData +from .coordinator import ( + TeslemetryEnergyDataCoordinator, + TeslemetryVehicleDataCoordinator, +) +from .models import TeslemetryData, TeslemetryEnergyData, TeslemetryVehicleData -PLATFORMS: Final = [ - Platform.CLIMATE, -] +PLATFORMS: Final = [Platform.CLIMATE, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -42,29 +43,48 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady from e # Create array of classes - data = [] + vehicles: list[TeslemetryVehicleData] = [] + energysites: list[TeslemetryEnergyData] = [] for product in products: - if "vin" not in product: - continue - vin = product["vin"] - - api = VehicleSpecific(teslemetry.vehicle, vin) - coordinator = TeslemetryVehicleDataCoordinator(hass, api) - data.append( - TeslemetryVehicleData( - api=api, - coordinator=coordinator, - vin=vin, + if "vin" in product: + vin = product["vin"] + api = VehicleSpecific(teslemetry.vehicle, vin) + coordinator = TeslemetryVehicleDataCoordinator(hass, api) + vehicles.append( + TeslemetryVehicleData( + api=api, + coordinator=coordinator, + vin=vin, + ) + ) + elif "energy_site_id" in product: + site_id = product["energy_site_id"] + api = EnergySpecific(teslemetry.energy, site_id) + energysites.append( + TeslemetryEnergyData( + api=api, + coordinator=TeslemetryEnergyDataCoordinator(hass, api), + id=site_id, + info=product, + ) ) - ) - # Do all coordinator first refresh simultaneously + # Do all coordinator first refreshes simultaneously await asyncio.gather( - *(vehicle.coordinator.async_config_entry_first_refresh() for vehicle in data) + *( + vehicle.coordinator.async_config_entry_first_refresh() + for vehicle in vehicles + ), + *( + energysite.coordinator.async_config_entry_first_refresh() + for energysite in energysites + ), ) # Setup Platforms - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = TeslemetryData( + vehicles, energysites + ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/teslemetry/climate.py b/homeassistant/components/teslemetry/climate.py index 748acbb8552..31556356caf 100644 --- a/homeassistant/components/teslemetry/climate.py +++ b/homeassistant/components/teslemetry/climate.py @@ -26,7 +26,7 @@ async def async_setup_entry( async_add_entities( TeslemetryClimateEntity(vehicle, TeslemetryClimateSide.DRIVER) - for vehicle in data + for vehicle in data.vehicles ) diff --git a/homeassistant/components/teslemetry/coordinator.py b/homeassistant/components/teslemetry/coordinator.py index 35e8ccd3bcf..b4dfdc3839a 100644 --- a/homeassistant/components/teslemetry/coordinator.py +++ b/homeassistant/components/teslemetry/coordinator.py @@ -2,7 +2,7 @@ from datetime import timedelta from typing import Any -from tesla_fleet_api import VehicleSpecific +from tesla_fleet_api import EnergySpecific, VehicleSpecific from tesla_fleet_api.exceptions import TeslaFleetError, VehicleOffline from homeassistant.core import HomeAssistant @@ -14,19 +14,29 @@ from .const import LOGGER, TeslemetryState SYNC_INTERVAL = 60 -class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): - """Class to manage fetching data from the Teslemetry API.""" +class TeslemetryDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): + """Base class for Teslemetry Data Coordinators.""" - def __init__(self, hass: HomeAssistant, api: VehicleSpecific) -> None: - """Initialize Teslemetry Data Update Coordinator.""" + name: str + + def __init__( + self, hass: HomeAssistant, api: VehicleSpecific | EnergySpecific + ) -> None: + """Initialize Teslemetry Vehicle Update Coordinator.""" super().__init__( hass, LOGGER, - name="Teslemetry Vehicle", + name=self.name, update_interval=timedelta(seconds=SYNC_INTERVAL), ) self.api = api + +class TeslemetryVehicleDataCoordinator(TeslemetryDataCoordinator): + """Class to manage fetching data from the Teslemetry API.""" + + name = "Teslemetry Vehicle" + async def async_config_entry_first_refresh(self) -> None: """Perform first refresh.""" try: @@ -65,3 +75,24 @@ class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): else: result[key] = value return result + + +class TeslemetryEnergyDataCoordinator(TeslemetryDataCoordinator): + """Class to manage fetching data from the Teslemetry API.""" + + name = "Teslemetry Energy Site" + + async def _async_update_data(self) -> dict[str, Any]: + """Update energy site data using Teslemetry API.""" + + try: + data = await self.api.live_status() + except TeslaFleetError as e: + raise UpdateFailed(e.message) from e + + # Convert Wall Connectors from array to dict + data["response"]["wall_connectors"] = { + wc["din"]: wc for wc in data["response"].get("wall_connectors", []) + } + + return data["response"] diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index 024d0603e7e..c8f650c01aa 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -10,12 +10,15 @@ from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, MODELS, TeslemetryState -from .coordinator import TeslemetryVehicleDataCoordinator -from .models import TeslemetryVehicleData +from .coordinator import ( + TeslemetryEnergyDataCoordinator, + TeslemetryVehicleDataCoordinator, +) +from .models import TeslemetryEnergyData, TeslemetryVehicleData class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator]): - """Parent class for Teslemetry Entities.""" + """Parent class for Teslemetry Vehicle Entities.""" _attr_has_entity_name = True @@ -74,3 +77,65 @@ class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator for key, value in args: self.coordinator.data[key] = value self.async_write_ha_state() + + +class TeslemetryEnergyEntity(CoordinatorEntity[TeslemetryEnergyDataCoordinator]): + """Parent class for Teslemetry Energy Entities.""" + + _attr_has_entity_name = True + + def __init__( + self, + energysite: TeslemetryEnergyData, + key: str, + ) -> None: + """Initialize common aspects of a Teslemetry entity.""" + super().__init__(energysite.coordinator) + self.key = key + self.api = energysite.api + + self._attr_translation_key = key + self._attr_unique_id = f"{energysite.id}-{key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, str(energysite.id))}, + manufacturer="Tesla", + configuration_url="https://teslemetry.com/console", + name=self.coordinator.data.get("site_name", "Energy Site"), + ) + + def get(self, key: str | None = None, default: Any | None = None) -> Any: + """Return a specific value from coordinator data.""" + return self.coordinator.data.get(key or self.key, default) + + +class TeslemetryWallConnectorEntity(CoordinatorEntity[TeslemetryEnergyDataCoordinator]): + """Parent class for Teslemetry Wall Connector Entities.""" + + _attr_has_entity_name = True + + def __init__( + self, + energysite: TeslemetryEnergyData, + din: str, + key: str, + ) -> None: + """Initialize common aspects of a Teslemetry entity.""" + super().__init__(energysite.coordinator) + self.din = din + self.key = key + + self._attr_translation_key = key + self._attr_unique_id = f"{energysite.id}-{din}-{key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, din)}, + manufacturer="Tesla", + configuration_url="https://teslemetry.com/console", + name="Wall Connector", + via_device=(DOMAIN, str(energysite.id)), + serial_number=din.split("-")[-1], + ) + + @property + def _value(self) -> int: + """Return a specific wall connector value from coordinator data.""" + return self.coordinator.data["wall_connectors"][self.din].get(self.key) diff --git a/homeassistant/components/teslemetry/models.py b/homeassistant/components/teslemetry/models.py index e5b27fa9279..2b41adf7979 100644 --- a/homeassistant/components/teslemetry/models.py +++ b/homeassistant/components/teslemetry/models.py @@ -4,9 +4,20 @@ from __future__ import annotations import asyncio from dataclasses import dataclass -from tesla_fleet_api import VehicleSpecific +from tesla_fleet_api import EnergySpecific, VehicleSpecific -from .coordinator import TeslemetryVehicleDataCoordinator +from .coordinator import ( + TeslemetryEnergyDataCoordinator, + TeslemetryVehicleDataCoordinator, +) + + +@dataclass +class TeslemetryData: + """Data for the Teslemetry integration.""" + + vehicles: list[TeslemetryVehicleData] + energysites: list[TeslemetryEnergyData] @dataclass @@ -17,3 +28,13 @@ class TeslemetryVehicleData: coordinator: TeslemetryVehicleDataCoordinator vin: str wakelock = asyncio.Lock() + + +@dataclass +class TeslemetryEnergyData: + """Data for a vehicle in the Teslemetry integration.""" + + api: EnergySpecific + coordinator: TeslemetryEnergyDataCoordinator + id: int + info: dict[str, str] diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py new file mode 100644 index 00000000000..693b2e0b22a --- /dev/null +++ b/homeassistant/components/teslemetry/sensor.py @@ -0,0 +1,461 @@ +"""Sensor platform for Teslemetry integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timedelta +from itertools import chain + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + PERCENTAGE, + EntityCategory, + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfEnergy, + UnitOfLength, + UnitOfPower, + UnitOfPressure, + UnitOfSpeed, + UnitOfTemperature, + UnitOfTime, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType +from homeassistant.util import dt as dt_util + +from .const import DOMAIN +from .entity import ( + TeslemetryEnergyEntity, + TeslemetryVehicleEntity, + TeslemetryWallConnectorEntity, +) +from .models import TeslemetryEnergyData, TeslemetryVehicleData + + +@callback +def minutes_to_datetime(value: StateType) -> datetime | None: + """Convert relative minutes into absolute datetime.""" + if isinstance(value, (int, float)) and value > 0: + return dt_util.now() + timedelta(minutes=value) + return None + + +@dataclass(frozen=True, kw_only=True) +class TeslemetrySensorEntityDescription(SensorEntityDescription): + """Describes Teslemetry Sensor entity.""" + + value_fn: Callable[[StateType], StateType | datetime] = lambda x: x + + +VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( + TeslemetrySensorEntityDescription( + key="charge_state_usable_battery_level", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + ), + TeslemetrySensorEntityDescription( + key="charge_state_charge_energy_added", + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + suggested_display_precision=1, + ), + TeslemetrySensorEntityDescription( + key="charge_state_charger_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.KILO_WATT, + device_class=SensorDeviceClass.POWER, + ), + TeslemetrySensorEntityDescription( + key="charge_state_charger_voltage", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + device_class=SensorDeviceClass.VOLTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + ), + TeslemetrySensorEntityDescription( + key="charge_state_charger_actual_current", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + device_class=SensorDeviceClass.CURRENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + TeslemetrySensorEntityDescription( + key="charge_state_charge_rate", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR, + device_class=SensorDeviceClass.SPEED, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="charge_state_minutes_to_full_charge", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=minutes_to_datetime, + ), + TeslemetrySensorEntityDescription( + key="charge_state_battery_range", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfLength.MILES, + device_class=SensorDeviceClass.DISTANCE, + suggested_display_precision=1, + ), + TeslemetrySensorEntityDescription( + key="drive_state_speed", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR, + device_class=SensorDeviceClass.SPEED, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="drive_state_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.KILO_WATT, + device_class=SensorDeviceClass.POWER, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="drive_state_shift_state", + icon="mdi:car-shift-pattern", + options=["p", "d", "r", "n"], + device_class=SensorDeviceClass.ENUM, + value_fn=lambda x: x.lower() if isinstance(x, str) else x, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="vehicle_state_odometer", + state_class=SensorStateClass.TOTAL_INCREASING, + native_unit_of_measurement=UnitOfLength.MILES, + device_class=SensorDeviceClass.DISTANCE, + suggested_display_precision=0, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="vehicle_state_tpms_pressure_fl", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.BAR, + suggested_unit_of_measurement=UnitOfPressure.PSI, + device_class=SensorDeviceClass.PRESSURE, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="vehicle_state_tpms_pressure_fr", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.BAR, + suggested_unit_of_measurement=UnitOfPressure.PSI, + device_class=SensorDeviceClass.PRESSURE, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="vehicle_state_tpms_pressure_rl", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.BAR, + suggested_unit_of_measurement=UnitOfPressure.PSI, + device_class=SensorDeviceClass.PRESSURE, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="vehicle_state_tpms_pressure_rr", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.BAR, + suggested_unit_of_measurement=UnitOfPressure.PSI, + device_class=SensorDeviceClass.PRESSURE, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="climate_state_inside_temp", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + suggested_display_precision=1, + ), + TeslemetrySensorEntityDescription( + key="climate_state_outside_temp", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + suggested_display_precision=1, + ), + TeslemetrySensorEntityDescription( + key="climate_state_driver_temp_setting", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="climate_state_passenger_temp_setting", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + suggested_display_precision=1, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="drive_state_active_route_traffic_minutes_delay", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTime.MINUTES, + device_class=SensorDeviceClass.DURATION, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="drive_state_active_route_energy_at_arrival", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="drive_state_active_route_miles_to_arrival", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfLength.MILES, + device_class=SensorDeviceClass.DISTANCE, + ), + TeslemetrySensorEntityDescription( + key="drive_state_active_route_minutes_to_arrival", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=minutes_to_datetime, + ), + TeslemetrySensorEntityDescription( + key="drive_state_active_route_destination", + icon="mdi:map-marker", + entity_category=EntityCategory.DIAGNOSTIC, + ), +) + +ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="solar_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:solar-power", + ), + SensorEntityDescription( + key="energy_left", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY_STORAGE, + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:battery", + ), + SensorEntityDescription( + key="total_pack_energy", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY_STORAGE, + entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:battery-high", + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="percentage_charged", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + suggested_display_precision=2, + ), + SensorEntityDescription( + key="battery_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:home-battery", + ), + SensorEntityDescription( + key="load_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:power-plug", + ), + SensorEntityDescription( + key="grid_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:transmission-tower", + ), + SensorEntityDescription( + key="grid_services_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:transmission-tower", + ), + SensorEntityDescription( + key="generator_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:generator-stationary", + entity_registry_enabled_default=False, + ), +) + +WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="wall_connector_state", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + icon="mdi:ev-station", + ), + SensorEntityDescription( + key="wall_connector_fault_state", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + icon="mdi:ev-station", + ), + SensorEntityDescription( + key="wall_connector_power", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=2, + device_class=SensorDeviceClass.POWER, + icon="mdi:ev-station", + ), + SensorEntityDescription( + key="vin", + icon="mdi:car-electric", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the Teslemetry sensor platform from a config entry.""" + data = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + chain( + ( # Add vehicles + TeslemetryVehicleSensorEntity(vehicle, description) + for vehicle in data.vehicles + for description in VEHICLE_DESCRIPTIONS + ), + ( # Add energy sites + TeslemetryEnergySensorEntity(energysite, description) + for energysite in data.energysites + for description in ENERGY_DESCRIPTIONS + if description.key in energysite.coordinator.data + ), + ( # Add wall connectors + TeslemetryWallConnectorSensorEntity(energysite, din, description) + for energysite in data.energysites + for din in energysite.coordinator.data.get("wall_connectors", {}) + for description in WALL_CONNECTOR_DESCRIPTIONS + ), + ) + ) + + +class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity): + """Base class for Teslemetry vehicle metric sensors.""" + + entity_description: TeslemetrySensorEntityDescription + + def __init__( + self, + vehicle: TeslemetryVehicleData, + description: TeslemetrySensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + super().__init__(vehicle, description.key) + self.entity_description = description + + @property + def native_value(self) -> StateType | datetime: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self.get()) + + @property + def available(self) -> bool: + """Return if sensor is available.""" + return super().available and self.get() is not None + + +class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity): + """Base class for Teslemetry energy site metric sensors.""" + + entity_description: SensorEntityDescription + + def __init__( + self, + energysite: TeslemetryEnergyData, + description: SensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + super().__init__(energysite, description.key) + self.entity_description = description + + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + return self.get() + + +class TeslemetryWallConnectorSensorEntity(TeslemetryWallConnectorEntity, SensorEntity): + """Base class for Teslemetry energy site metric sensors.""" + + entity_description: SensorEntityDescription + + def __init__( + self, + energysite: TeslemetryEnergyData, + din: str, + description: SensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + super().__init__( + energysite, + din, + description.key, + ) + self.entity_description = description + + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + return self._value diff --git a/homeassistant/components/teslemetry/strings.json b/homeassistant/components/teslemetry/strings.json index 95b2266b2dd..2e89503bbe9 100644 --- a/homeassistant/components/teslemetry/strings.json +++ b/homeassistant/components/teslemetry/strings.json @@ -30,6 +30,128 @@ } } } + }, + "sensor": { + "charge_state_usable_battery_level": { + "name": "Battery level" + }, + "charge_state_charge_energy_added": { + "name": "Charge energy added" + }, + "charge_state_charger_power": { + "name": "Charger power" + }, + "charge_state_charger_voltage": { + "name": "Charger voltage" + }, + "charge_state_charger_actual_current": { + "name": "Charger current" + }, + "charge_state_charge_rate": { + "name": "Charge rate" + }, + "charge_state_battery_range": { + "name": "Battery range" + }, + "charge_state_minutes_to_full_charge": { + "name": "Time to full charge" + }, + "drive_state_speed": { + "name": "Speed" + }, + "drive_state_power": { + "name": "Power" + }, + "drive_state_shift_state": { + "name": "Shift state", + "state": { + "p": "Park", + "d": "Drive", + "r": "Reverse", + "n": "Neutral" + } + }, + "vehicle_state_odometer": { + "name": "Odometer" + }, + "vehicle_state_tpms_pressure_fl": { + "name": "Tire pressure front left" + }, + "vehicle_state_tpms_pressure_fr": { + "name": "Tire pressure front right" + }, + "vehicle_state_tpms_pressure_rl": { + "name": "Tire pressure rear left" + }, + "vehicle_state_tpms_pressure_rr": { + "name": "Tire pressure rear right" + }, + "climate_state_inside_temp": { + "name": "Inside temperature" + }, + "climate_state_outside_temp": { + "name": "Outside temperature" + }, + "climate_state_driver_temp_setting": { + "name": "Driver temperature setting" + }, + "climate_state_passenger_temp_setting": { + "name": "Passenger temperature setting" + }, + "drive_state_active_route_traffic_minutes_delay": { + "name": "Traffic delay" + }, + "drive_state_active_route_energy_at_arrival": { + "name": "State of charge at arrival" + }, + "drive_state_active_route_miles_to_arrival": { + "name": "Distance to arrival" + }, + "drive_state_active_route_minutes_to_arrival": { + "name": "Time to arrival" + }, + "drive_state_active_route_destination": { + "name": "Destination" + }, + "solar_power": { + "name": "Solar power" + }, + "energy_left": { + "name": "Energy left" + }, + "total_pack_energy": { + "name": "Total pack energy" + }, + "percentage_charged": { + "name": "Percentage charged" + }, + "battery_power": { + "name": "Battery power" + }, + "load_power": { + "name": "Load power" + }, + "grid_power": { + "name": "Grid power" + }, + "grid_services_power": { + "name": "Grid services power" + }, + "generator_power": { + "name": "Generator power" + }, + "wall_connector_state": { + "name": "State code" + }, + "wall_connector_fault_state": { + "name": "Fault state code" + }, + "wall_connector_power": { + "name": "Power" + }, + "vin": { + "name": "Vehicle" + } } } } diff --git a/tests/components/teslemetry/conftest.py b/tests/components/teslemetry/conftest.py index 8c1fe070dde..692d97dc4d8 100644 --- a/tests/components/teslemetry/conftest.py +++ b/tests/components/teslemetry/conftest.py @@ -1,11 +1,12 @@ """Fixtures for Tessie.""" from __future__ import annotations +from copy import deepcopy from unittest.mock import patch import pytest -from .const import PRODUCTS, RESPONSE_OK, VEHICLE_DATA, WAKE_UP_ONLINE +from .const import LIVE_STATUS, PRODUCTS, RESPONSE_OK, VEHICLE_DATA, WAKE_UP_ONLINE @pytest.fixture(autouse=True) @@ -55,3 +56,13 @@ def mock_request(): return_value=RESPONSE_OK, ) as mock_request: yield mock_request + + +@pytest.fixture(autouse=True) +def mock_live_status(): + """Mock Teslemetry Energy Specific live_status method.""" + with patch( + "homeassistant.components.teslemetry.EnergySpecific.live_status", + side_effect=lambda: deepcopy(LIVE_STATUS), + ) as mock_live_status: + yield mock_live_status diff --git a/tests/components/teslemetry/const.py b/tests/components/teslemetry/const.py index 0feb056fa72..90419d43bbb 100644 --- a/tests/components/teslemetry/const.py +++ b/tests/components/teslemetry/const.py @@ -12,5 +12,6 @@ WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None} PRODUCTS = load_json_object_fixture("products.json", DOMAIN) VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN) +LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN) RESPONSE_OK = {"response": {}, "error": None} diff --git a/tests/components/teslemetry/fixtures/live_status.json b/tests/components/teslemetry/fixtures/live_status.json new file mode 100644 index 00000000000..486f9f4fadd --- /dev/null +++ b/tests/components/teslemetry/fixtures/live_status.json @@ -0,0 +1,33 @@ +{ + "response": { + "solar_power": 1185, + "energy_left": 38896.47368421053, + "total_pack_energy": 40727, + "percentage_charged": 95.50537403739663, + "backup_capable": true, + "battery_power": 5060, + "load_power": 6245, + "grid_status": "Active", + "grid_services_active": false, + "grid_power": 0, + "grid_services_power": 0, + "generator_power": 0, + "island_status": "on_grid", + "storm_mode_active": false, + "timestamp": "2024-01-01T00:00:00+00:00", + "wall_connectors": [ + { + "din": "abd-123", + "wall_connector_state": 2, + "wall_connector_fault_state": 2, + "wall_connector_power": 0 + }, + { + "din": "bcd-234", + "wall_connector_state": 2, + "wall_connector_fault_state": 2, + "wall_connector_power": 0 + } + ] + } +} diff --git a/tests/components/teslemetry/fixtures/products.json b/tests/components/teslemetry/fixtures/products.json index 430c3b39dc8..aa59062e8d4 100644 --- a/tests/components/teslemetry/fixtures/products.json +++ b/tests/components/teslemetry/fixtures/products.json @@ -71,28 +71,50 @@ "release_notes_supported": true }, { - "energy_site_id": 2345, - "resource_type": "wall_connector", - "id": "ID1234", - "asset_site_id": "abcdef", - "warp_site_number": "ID1234", + "energy_site_id": 123456, + "resource_type": "battery", + "site_name": "Energy Site", + "id": "ABC123", + "gateway_id": "ABC123", + "asset_site_id": "c0ffee", + "warp_site_number": "GA123456", + "energy_left": 23286.105263157893, + "total_pack_energy": 40804, + "percentage_charged": 57.068192488868476, + "battery_type": "ac_powerwall", + "backup_capable": true, + "battery_power": 14990, "go_off_grid_test_banner_enabled": null, - "storm_mode_enabled": null, - "powerwall_onboarding_settings_set": null, + "storm_mode_enabled": true, + "powerwall_onboarding_settings_set": true, "powerwall_tesla_electric_interested_in": null, "vpp_tour_enabled": null, - "sync_grid_alert_enabled": false, - "breaker_alert_enabled": false, + "sync_grid_alert_enabled": true, + "breaker_alert_enabled": true, "components": { - "battery": false, - "solar": false, - "grid": false, - "load_meter": false, + "battery": true, + "battery_type": "ac_powerwall", + "solar": true, + "solar_type": "pv_panel", + "grid": true, + "load_meter": true, + "market_type": "residential", "wall_connectors": [ - { "device_id": "abcdef", "din": "12345", "is_active": true } + { + "device_id": "abc-123", + "din": "123-abc", + "is_active": true + }, + { + "device_id": "bcd-234", + "din": "234-bcd", + "is_active": true + } ] }, - "features": {} + "features": { + "rate_plan_manager_no_pricing_constraint": true + } } ], "count": 2 diff --git a/tests/components/teslemetry/fixtures/site_info.json b/tests/components/teslemetry/fixtures/site_info.json new file mode 100644 index 00000000000..d39fc1f68aa --- /dev/null +++ b/tests/components/teslemetry/fixtures/site_info.json @@ -0,0 +1,87 @@ +{ + "response": { + "id": "1233-abcd", + "site_name": "Site", + "backup_reserve_percent": 0, + "default_real_mode": "self_consumption", + "installation_date": "2022-01-01T00:00:00+00:00", + "user_settings": { + "go_off_grid_test_banner_enabled": false, + "storm_mode_enabled": true, + "powerwall_onboarding_settings_set": true, + "powerwall_tesla_electric_interested_in": false, + "vpp_tour_enabled": true, + "sync_grid_alert_enabled": true, + "breaker_alert_enabled": false + }, + "components": { + "solar": true, + "solar_type": "pv_panel", + "battery": true, + "grid": true, + "backup": true, + "gateway": "teg", + "load_meter": true, + "tou_capable": true, + "storm_mode_capable": true, + "flex_energy_request_capable": false, + "car_charging_data_supported": false, + "off_grid_vehicle_charging_reserve_supported": false, + "vehicle_charging_performance_view_enabled": false, + "vehicle_charging_solar_offset_view_enabled": false, + "battery_solar_offset_view_enabled": true, + "solar_value_enabled": true, + "energy_value_header": "Energy Value", + "energy_value_subheader": "Estimated Value", + "energy_service_self_scheduling_enabled": true, + "show_grid_import_battery_source_cards": true, + "set_islanding_mode_enabled": true, + "wifi_commissioning_enabled": true, + "backup_time_remaining_enabled": true, + "battery_type": "ac_powerwall", + "configurable": true, + "grid_services_enabled": false, + "wall_connectors": [ + { + "device_id": "123abc", + "din": "abc123", + "is_active": true + }, + { + "device_id": "234bcd", + "din": "bcd234", + "is_active": true + } + ], + "disallow_charge_from_grid_with_solar_installed": true, + "customer_preferred_export_rule": "pv_only", + "net_meter_mode": "battery_ok", + "system_alerts_enabled": true + }, + "version": "23.44.0 eb113390", + "battery_count": 3, + "tou_settings": { + "optimization_strategy": "economics", + "schedule": [ + { + "target": "off_peak", + "week_days": [1, 0], + "start_seconds": 0, + "end_seconds": 3600 + }, + { + "target": "peak", + "week_days": [1, 0], + "start_seconds": 3600, + "end_seconds": 0 + } + ] + }, + "nameplate_power": 15000, + "nameplate_energy": 40500, + "installation_time_zone": "", + "max_site_meter_power_ac": 1000000000, + "min_site_meter_power_ac": -1000000000, + "vpp_backup_reserve_percent": 0 + } +} diff --git a/tests/components/teslemetry/fixtures/vehicle_data.json b/tests/components/teslemetry/fixtures/vehicle_data.json index 44556c1c8df..ba73fe3c4e6 100644 --- a/tests/components/teslemetry/fixtures/vehicle_data.json +++ b/tests/components/teslemetry/fixtures/vehicle_data.json @@ -112,10 +112,20 @@ "wiper_blade_heater": false }, "drive_state": { - "active_route_latitude": -27.855946, - "active_route_longitude": 153.345056, + "active_route_latitude": 30.2226265, + "active_route_longitude": -97.6236871, + "active_route_miles_to_arrival": 0.039491, + "active_route_minutes_to_arrival": 0.103577, "active_route_traffic_minutes_delay": 0, - "power": 0, + "gps_as_of": 1701129612, + "heading": 185, + "latitude": -30.222626, + "longitude": -97.6236871, + "native_latitude": -30.222626, + "native_location_supported": 1, + "native_longitude": -97.6236871, + "native_type": "wgs", + "power": -7, "shift_state": null, "speed": null, "timestamp": 1705707520649 diff --git a/tests/components/teslemetry/snapshots/test_sensor.ambr b/tests/components/teslemetry/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..a384f91fc67 --- /dev/null +++ b/tests/components/teslemetry/snapshots/test_sensor.ambr @@ -0,0 +1,2208 @@ +# serializer version: 1 +# name: test_sensors[sensor.energy_site_battery_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_battery_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:home-battery', + 'original_name': 'Battery power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_power', + 'unique_id': '123456-battery_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_battery_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Battery power', + 'icon': 'mdi:home-battery', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_battery_power', + 'last_changed': , + 'last_updated': , + 'state': '5.06', + }) +# --- +# name: test_sensors[sensor.energy_site_energy_left-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.energy_site_energy_left', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:battery', + 'original_name': 'Energy left', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'energy_left', + 'unique_id': '123456-energy_left', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_energy_left-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy_storage', + 'friendly_name': 'Energy Site Energy left', + 'icon': 'mdi:battery', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_energy_left', + 'last_changed': , + 'last_updated': , + 'state': '38.8964736842105', + }) +# --- +# name: test_sensors[sensor.energy_site_generator_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_generator_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:generator-stationary', + 'original_name': 'Generator power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'generator_power', + 'unique_id': '123456-generator_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_generator_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Generator power', + 'icon': 'mdi:generator-stationary', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_generator_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_sensors[sensor.energy_site_grid_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_grid_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:transmission-tower', + 'original_name': 'Grid power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'grid_power', + 'unique_id': '123456-grid_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_grid_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Grid power', + 'icon': 'mdi:transmission-tower', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_grid_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_sensors[sensor.energy_site_grid_services_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_grid_services_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:transmission-tower', + 'original_name': 'Grid services power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'grid_services_power', + 'unique_id': '123456-grid_services_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_grid_services_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Grid services power', + 'icon': 'mdi:transmission-tower', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_grid_services_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_sensors[sensor.energy_site_load_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_load_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:power-plug', + 'original_name': 'Load power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'load_power', + 'unique_id': '123456-load_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_load_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Load power', + 'icon': 'mdi:power-plug', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_load_power', + 'last_changed': , + 'last_updated': , + 'state': '6.245', + }) +# --- +# name: test_sensors[sensor.energy_site_percentage_charged-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_percentage_charged', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Percentage charged', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'percentage_charged', + 'unique_id': '123456-percentage_charged', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[sensor.energy_site_percentage_charged-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Energy Site Percentage charged', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.energy_site_percentage_charged', + 'last_changed': , + 'last_updated': , + 'state': '95.5053740373966', + }) +# --- +# name: test_sensors[sensor.energy_site_solar_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_solar_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:solar-power', + 'original_name': 'Solar power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'solar_power', + 'unique_id': '123456-solar_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_solar_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Solar power', + 'icon': 'mdi:solar-power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_solar_power', + 'last_changed': , + 'last_updated': , + 'state': '1.185', + }) +# --- +# name: test_sensors[sensor.energy_site_total_pack_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.energy_site_total_pack_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:battery-high', + 'original_name': 'Total pack energy', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_pack_energy', + 'unique_id': '123456-total_pack_energy', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.energy_site_total_pack_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy_storage', + 'friendly_name': 'Energy Site Total pack energy', + 'icon': 'mdi:battery-high', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_total_pack_energy', + 'last_changed': , + 'last_updated': , + 'state': '40.727', + }) +# --- +# name: test_sensors[sensor.test_battery_level-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_battery_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery level', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_usable_battery_level', + 'unique_id': 'VINVINVIN-charge_state_usable_battery_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[sensor.test_battery_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Test Battery level', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.test_battery_level', + 'last_changed': , + 'last_updated': , + 'state': '77', + }) +# --- +# name: test_sensors[sensor.test_battery_range-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_battery_range', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery range', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_battery_range', + 'unique_id': 'VINVINVIN-charge_state_battery_range', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_battery_range-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'distance', + 'friendly_name': 'Test Battery range', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_battery_range', + 'last_changed': , + 'last_updated': , + 'state': '429.48563328', + }) +# --- +# name: test_sensors[sensor.test_charge_energy_added-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charge_energy_added', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charge energy added', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charge_energy_added', + 'unique_id': 'VINVINVIN-charge_state_charge_energy_added', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_charge_energy_added-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test Charge energy added', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_charge_energy_added', + 'last_changed': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[sensor.test_charge_rate-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_charge_rate', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charge rate', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charge_rate', + 'unique_id': 'VINVINVIN-charge_state_charge_rate', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_charge_rate-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'speed', + 'friendly_name': 'Test Charge rate', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_charge_rate', + 'last_changed': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[sensor.test_charger_current-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_charger_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charger current', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charger_actual_current', + 'unique_id': 'VINVINVIN-charge_state_charger_actual_current', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_charger_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Test Charger current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_charger_current', + 'last_changed': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[sensor.test_charger_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charger_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charger power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charger_power', + 'unique_id': 'VINVINVIN-charge_state_charger_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_charger_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Test Charger power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_charger_power', + 'last_changed': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[sensor.test_charger_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_charger_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charger voltage', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charger_voltage', + 'unique_id': 'VINVINVIN-charge_state_charger_voltage', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_charger_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'Test Charger voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_charger_voltage', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_sensors[sensor.test_destination-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_destination', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:map-marker', + 'original_name': 'Destination', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_active_route_destination', + 'unique_id': 'VINVINVIN-drive_state_active_route_destination', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_destination-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Destination', + 'icon': 'mdi:map-marker', + }), + 'context': , + 'entity_id': 'sensor.test_destination', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_sensors[sensor.test_distance_to_arrival-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_distance_to_arrival', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Distance to arrival', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_active_route_miles_to_arrival', + 'unique_id': 'VINVINVIN-drive_state_active_route_miles_to_arrival', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_distance_to_arrival-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'distance', + 'friendly_name': 'Test Distance to arrival', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_distance_to_arrival', + 'last_changed': , + 'last_updated': , + 'state': '0.063555', + }) +# --- +# name: test_sensors[sensor.test_driver_temperature_setting-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_driver_temperature_setting', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Driver temperature setting', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'climate_state_driver_temp_setting', + 'unique_id': 'VINVINVIN-climate_state_driver_temp_setting', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_driver_temperature_setting-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Test Driver temperature setting', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_driver_temperature_setting', + 'last_changed': , + 'last_updated': , + 'state': '22', + }) +# --- +# name: test_sensors[sensor.test_inside_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_inside_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Inside temperature', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'climate_state_inside_temp', + 'unique_id': 'VINVINVIN-climate_state_inside_temp', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_inside_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Test Inside temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_inside_temperature', + 'last_changed': , + 'last_updated': , + 'state': '29.8', + }) +# --- +# name: test_sensors[sensor.test_odometer-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_odometer', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Odometer', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vehicle_state_odometer', + 'unique_id': 'VINVINVIN-vehicle_state_odometer', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_odometer-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'distance', + 'friendly_name': 'Test Odometer', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_odometer', + 'last_changed': , + 'last_updated': , + 'state': '10430.189495371', + }) +# --- +# name: test_sensors[sensor.test_outside_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_outside_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Outside temperature', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'climate_state_outside_temp', + 'unique_id': 'VINVINVIN-climate_state_outside_temp', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_outside_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Test Outside temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_outside_temperature', + 'last_changed': , + 'last_updated': , + 'state': '30', + }) +# --- +# name: test_sensors[sensor.test_passenger_temperature_setting-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_passenger_temperature_setting', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Passenger temperature setting', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'climate_state_passenger_temp_setting', + 'unique_id': 'VINVINVIN-climate_state_passenger_temp_setting', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_passenger_temperature_setting-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Test Passenger temperature setting', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_passenger_temperature_setting', + 'last_changed': , + 'last_updated': , + 'state': '22', + }) +# --- +# name: test_sensors[sensor.test_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_power', + 'unique_id': 'VINVINVIN-drive_state_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Test Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_power', + 'last_changed': , + 'last_updated': , + 'state': '-7', + }) +# --- +# name: test_sensors[sensor.test_shift_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'p', + 'd', + 'r', + 'n', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_shift_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:car-shift-pattern', + 'original_name': 'Shift state', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_shift_state', + 'unique_id': 'VINVINVIN-drive_state_shift_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_shift_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Test Shift state', + 'icon': 'mdi:car-shift-pattern', + 'options': list([ + 'p', + 'd', + 'r', + 'n', + ]), + }), + 'context': , + 'entity_id': 'sensor.test_shift_state', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_sensors[sensor.test_speed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Speed', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_speed', + 'unique_id': 'VINVINVIN-drive_state_speed', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_speed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'speed', + 'friendly_name': 'Test Speed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_speed', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_sensors[sensor.test_state_of_charge_at_arrival-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_state_of_charge_at_arrival', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'State of charge at arrival', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_active_route_energy_at_arrival', + 'unique_id': 'VINVINVIN-drive_state_active_route_energy_at_arrival', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[sensor.test_state_of_charge_at_arrival-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Test State of charge at arrival', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.test_state_of_charge_at_arrival', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_sensors[sensor.test_time_to_arrival-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_time_to_arrival', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Time to arrival', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_active_route_minutes_to_arrival', + 'unique_id': 'VINVINVIN-drive_state_active_route_minutes_to_arrival', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_time_to_arrival-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Test Time to arrival', + }), + 'context': , + 'entity_id': 'sensor.test_time_to_arrival', + 'last_changed': , + 'last_updated': , + 'state': '2024-01-01T00:00:06+00:00', + }) +# --- +# name: test_sensors[sensor.test_time_to_full_charge-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_time_to_full_charge', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Time to full charge', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_minutes_to_full_charge', + 'unique_id': 'VINVINVIN-charge_state_minutes_to_full_charge', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_time_to_full_charge-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Test Time to full charge', + }), + 'context': , + 'entity_id': 'sensor.test_time_to_full_charge', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_front_left-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_tire_pressure_front_left', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tire pressure front left', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vehicle_state_tpms_pressure_fl', + 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_fl', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_front_left-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'pressure', + 'friendly_name': 'Test Tire pressure front left', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_front_left', + 'last_changed': , + 'last_updated': , + 'state': '40.2479739314961', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_front_right-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_tire_pressure_front_right', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tire pressure front right', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vehicle_state_tpms_pressure_fr', + 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_fr', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_front_right-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'pressure', + 'friendly_name': 'Test Tire pressure front right', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_front_right', + 'last_changed': , + 'last_updated': , + 'state': '40.6105682912393', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_rear_left-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_tire_pressure_rear_left', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tire pressure rear left', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vehicle_state_tpms_pressure_rl', + 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_rl', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_rear_left-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'pressure', + 'friendly_name': 'Test Tire pressure rear left', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_rear_left', + 'last_changed': , + 'last_updated': , + 'state': '40.2479739314961', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_rear_right-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_tire_pressure_rear_right', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tire pressure rear right', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vehicle_state_tpms_pressure_rr', + 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_rr', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_rear_right-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'pressure', + 'friendly_name': 'Test Tire pressure rear right', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_rear_right', + 'last_changed': , + 'last_updated': , + 'state': '40.2479739314961', + }) +# --- +# name: test_sensors[sensor.test_traffic_delay-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_traffic_delay', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Traffic delay', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'drive_state_active_route_traffic_minutes_delay', + 'unique_id': 'VINVINVIN-drive_state_active_route_traffic_minutes_delay', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.test_traffic_delay-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Test Traffic delay', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_traffic_delay', + 'last_changed': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[sensor.wall_connector_fault_state_code-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.wall_connector_fault_state_code', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:ev-station', + 'original_name': 'Fault state code', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wall_connector_fault_state', + 'unique_id': '123456-abd-123-wall_connector_fault_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.wall_connector_fault_state_code-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Fault state code', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_fault_state_code', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_sensors[sensor.wall_connector_fault_state_code_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.wall_connector_fault_state_code_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:ev-station', + 'original_name': 'Fault state code', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wall_connector_fault_state', + 'unique_id': '123456-bcd-234-wall_connector_fault_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.wall_connector_fault_state_code_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Fault state code', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_fault_state_code_2', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_sensors[sensor.wall_connector_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.wall_connector_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:ev-station', + 'original_name': 'Power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wall_connector_power', + 'unique_id': '123456-abd-123-wall_connector_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.wall_connector_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Wall Connector Power', + 'icon': 'mdi:ev-station', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.wall_connector_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_sensors[sensor.wall_connector_power_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.wall_connector_power_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:ev-station', + 'original_name': 'Power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wall_connector_power', + 'unique_id': '123456-bcd-234-wall_connector_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.wall_connector_power_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Wall Connector Power', + 'icon': 'mdi:ev-station', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.wall_connector_power_2', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_sensors[sensor.wall_connector_state_code-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.wall_connector_state_code', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:ev-station', + 'original_name': 'State code', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wall_connector_state', + 'unique_id': '123456-abd-123-wall_connector_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.wall_connector_state_code-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector State code', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_state_code', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_sensors[sensor.wall_connector_state_code_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.wall_connector_state_code_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:ev-station', + 'original_name': 'State code', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wall_connector_state', + 'unique_id': '123456-bcd-234-wall_connector_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.wall_connector_state_code_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector State code', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_state_code_2', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_sensors[sensor.wall_connector_vehicle-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.wall_connector_vehicle', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:car-electric', + 'original_name': 'Vehicle', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vin', + 'unique_id': '123456-abd-123-vin', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.wall_connector_vehicle-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Vehicle', + 'icon': 'mdi:car-electric', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_vehicle', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.wall_connector_vehicle_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.wall_connector_vehicle_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:car-electric', + 'original_name': 'Vehicle', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'vin', + 'unique_id': '123456-bcd-234-vin', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.wall_connector_vehicle_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Vehicle', + 'icon': 'mdi:car-electric', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_vehicle_2', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/teslemetry/test_init.py b/tests/components/teslemetry/test_init.py index 28440094bec..ba3ecd2f0e7 100644 --- a/tests/components/teslemetry/test_init.py +++ b/tests/components/teslemetry/test_init.py @@ -55,10 +55,10 @@ async def test_other_failure(hass: HomeAssistant, mock_products) -> None: assert entry.state is ConfigEntryState.SETUP_RETRY -# Coordinator +# Vehicle Coordinator -async def test_first_refresh( +async def test_vehicle_first_refresh( hass: HomeAssistant, mock_wake_up, mock_vehicle_data, @@ -88,14 +88,14 @@ async def test_first_refresh( mock_vehicle_data.assert_called_once() -async def test_first_refresh_error(hass: HomeAssistant, mock_wake_up) -> None: +async def test_vehicle_first_refresh_error(hass: HomeAssistant, mock_wake_up) -> None: """Test first coordinator refresh with an error.""" mock_wake_up.side_effect = TeslaFleetError entry = await setup_platform(hass) assert entry.state is ConfigEntryState.SETUP_RETRY -async def test_refresh_offline( +async def test_vehicle_refresh_offline( hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory ) -> None: """Test coordinator refresh with an error.""" @@ -111,8 +111,18 @@ async def test_refresh_offline( mock_vehicle_data.assert_called_once() -async def test_refresh_error(hass: HomeAssistant, mock_vehicle_data) -> None: +async def test_vehicle_refresh_error(hass: HomeAssistant, mock_vehicle_data) -> None: """Test coordinator refresh with an error.""" mock_vehicle_data.side_effect = TeslaFleetError entry = await setup_platform(hass) assert entry.state is ConfigEntryState.SETUP_RETRY + + +# Test Energy Coordinator + + +async def test_energy_refresh_error(hass: HomeAssistant, mock_live_status) -> None: + """Test coordinator refresh with an error.""" + mock_live_status.side_effect = TeslaFleetError + entry = await setup_platform(hass) + assert entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py new file mode 100644 index 00000000000..f417df23357 --- /dev/null +++ b/tests/components/teslemetry/test_sensor.py @@ -0,0 +1,26 @@ +"""Test the Teslemetry sensor platform.""" +from freezegun.api import FrozenDateTimeFactory +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import assert_entities, setup_platform + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensors( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, +) -> None: + """Tests that the sensor entities are correct.""" + + freezer.move_to("2024-01-01 00:00:00+00:00") + + entry = await setup_platform(hass, [Platform.SENSOR]) + + assert_entities(hass, entry.entry_id, entity_registry, snapshot) From 7e7f25c859b52797c9ffa997f98bbfe31e654b26 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 4 Mar 2024 19:09:39 +0100 Subject: [PATCH 0264/1691] Add config flow to homeworks (#112042) --- .coveragerc | 3 +- .../components/homeworks/__init__.py | 164 +++-- .../components/homeworks/config_flow.py | 465 ++++++++++++++ homeassistant/components/homeworks/const.py | 15 + homeassistant/components/homeworks/light.py | 44 +- .../components/homeworks/manifest.json | 1 + .../components/homeworks/strings.json | 35 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 2 +- requirements_test_all.txt | 3 + tests/components/homeworks/__init__.py | 1 + tests/components/homeworks/conftest.py | 82 +++ .../components/homeworks/test_config_flow.py | 588 ++++++++++++++++++ 13 files changed, 1343 insertions(+), 61 deletions(-) create mode 100644 homeassistant/components/homeworks/config_flow.py create mode 100644 homeassistant/components/homeworks/const.py create mode 100644 homeassistant/components/homeworks/strings.json create mode 100644 tests/components/homeworks/__init__.py create mode 100644 tests/components/homeworks/conftest.py create mode 100644 tests/components/homeworks/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 626c2122d6f..b020819d3be 100644 --- a/.coveragerc +++ b/.coveragerc @@ -545,7 +545,8 @@ omit = homeassistant/components/homematic/notify.py homeassistant/components/homematic/sensor.py homeassistant/components/homematic/switch.py - homeassistant/components/homeworks/* + homeassistant/components/homeworks/__init__.py + homeassistant/components/homeworks/light.py homeassistant/components/horizon/media_player.py homeassistant/components/hp_ilo/sensor.py homeassistant/components/huawei_lte/__init__.py diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 3d9023d16cb..05ba4d02454 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -1,9 +1,14 @@ """Support for Lutron Homeworks Series 4 and 8 systems.""" +from __future__ import annotations + +from dataclasses import dataclass import logging +from typing import Any from pyhomeworks.pyhomeworks import HW_BUTTON_PRESSED, HW_BUTTON_RELEASED, Homeworks import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_ID, @@ -13,27 +18,31 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType from homeassistant.util import slugify +from .const import ( + CONF_ADDR, + CONF_CONTROLLER_ID, + CONF_DIMMERS, + CONF_KEYPADS, + CONF_RATE, + DOMAIN, +) + _LOGGER = logging.getLogger(__name__) -DOMAIN = "homeworks" +PLATFORMS: list[Platform] = [Platform.LIGHT] -HOMEWORKS_CONTROLLER = "homeworks" EVENT_BUTTON_PRESS = "homeworks_button_press" EVENT_BUTTON_RELEASE = "homeworks_button_release" -CONF_DIMMERS = "dimmers" -CONF_KEYPADS = "keypads" -CONF_ADDR = "addr" -CONF_RATE = "rate" +DEFAULT_FADE_RATE = 1.0 -FADE_RATE = 1.0 CV_FADE_RATE = vol.All(vol.Coerce(float), vol.Range(min=0, max=20)) @@ -41,7 +50,7 @@ DIMMER_SCHEMA = vol.Schema( { vol.Required(CONF_ADDR): cv.string, vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_RATE, default=FADE_RATE): CV_FADE_RATE, + vol.Optional(CONF_RATE, default=DEFAULT_FADE_RATE): CV_FADE_RATE, } ) @@ -66,64 +75,137 @@ CONFIG_SCHEMA = vol.Schema( ) -def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: +@dataclass +class HomeworksData: + """Container for config entry data.""" + + controller: Homeworks + controller_id: str + keypads: dict[str, HomeworksKeypad] + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Start Homeworks controller.""" - def hw_callback(msg_type, values): - """Dispatch state changes.""" - _LOGGER.debug("callback: %s, %s", msg_type, values) - addr = values[0] - signal = f"homeworks_entity_{addr}" - dispatcher_send(hass, signal, msg_type, values) - - config = base_config[DOMAIN] - controller = Homeworks(config[CONF_HOST], config[CONF_PORT], hw_callback) - hass.data[HOMEWORKS_CONTROLLER] = controller - - def cleanup(event): - controller.close() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup) - - dimmers = config[CONF_DIMMERS] - load_platform(hass, Platform.LIGHT, DOMAIN, {CONF_DIMMERS: dimmers}, base_config) - - for key_config in config[CONF_KEYPADS]: - addr = key_config[CONF_ADDR] - name = key_config[CONF_NAME] - HomeworksKeypadEvent(hass, addr, name) + if DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) return True -class HomeworksDevice(Entity): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Homeworks from a config entry.""" + + hass.data.setdefault(DOMAIN, {}) + controller_id = entry.options[CONF_CONTROLLER_ID] + + def hw_callback(msg_type: Any, values: Any) -> None: + """Dispatch state changes.""" + _LOGGER.debug("callback: %s, %s", msg_type, values) + addr = values[0] + signal = f"homeworks_entity_{controller_id}_{addr}" + dispatcher_send(hass, signal, msg_type, values) + + config = entry.options + try: + controller = await hass.async_add_executor_job( + Homeworks, config[CONF_HOST], config[CONF_PORT], hw_callback + ) + except (ConnectionError, OSError) as err: + raise ConfigEntryNotReady from err + + def cleanup(event): + controller.close() + + entry.async_on_unload(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)) + + keypads: dict[str, HomeworksKeypad] = {} + for key_config in config.get(CONF_KEYPADS, []): + addr = key_config[CONF_ADDR] + name = key_config[CONF_NAME] + keypads[addr] = HomeworksKeypad(hass, controller, controller_id, addr, name) + + hass.data[DOMAIN][entry.entry_id] = HomeworksData( + controller, controller_id, keypads + ) + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + entry.async_on_unload(entry.add_update_listener(update_listener)) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if not await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + return False + + data: HomeworksData = hass.data[DOMAIN].pop(entry.entry_id) + for keypad in data.keypads.values(): + keypad.unsubscribe() + + await hass.async_add_executor_job(data.controller.close) + + return True + + +async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + +def calculate_unique_id(controller_id, addr, idx): + """Calculate entity unique id.""" + return f"homeworks.{controller_id}.{addr}.{idx}" + + +class HomeworksEntity(Entity): """Base class of a Homeworks device.""" _attr_should_poll = False - def __init__(self, controller, addr, name): + def __init__( + self, + controller: Homeworks, + controller_id: str, + addr: str, + idx: int, + name: str | None, + ) -> None: """Initialize Homeworks device.""" self._addr = addr + self._idx = idx + self._controller_id = controller_id self._attr_name = name - self._attr_unique_id = f"homeworks.{self._addr}" + self._attr_unique_id = calculate_unique_id( + self._controller_id, self._addr, self._idx + ) self._controller = controller -class HomeworksKeypadEvent: +class HomeworksKeypad: """When you want signals instead of entities. Stateless sensors such as keypads are expected to generate an event instead of a sensor entity in hass. """ - def __init__(self, hass, addr, name): + def __init__(self, hass, controller, controller_id, addr, name): """Register callback that will be used for signals.""" - self._hass = hass self._addr = addr + self._controller = controller + self._hass = hass self._name = name self._id = slugify(self._name) - signal = f"homeworks_entity_{self._addr}" - async_dispatcher_connect(self._hass, signal, self._update_callback) + signal = f"homeworks_entity_{controller_id}_{self._addr}" + _LOGGER.debug("connecting %s", signal) + self.unsubscribe = async_dispatcher_connect( + self._hass, signal, self._update_callback + ) @callback def _update_callback(self, msg_type, values): diff --git a/homeassistant/components/homeworks/config_flow.py b/homeassistant/components/homeworks/config_flow.py new file mode 100644 index 00000000000..e7095e4f57f --- /dev/null +++ b/homeassistant/components/homeworks/config_flow.py @@ -0,0 +1,465 @@ +"""Lutron Homeworks Series 4 and 8 config flow.""" +from __future__ import annotations + +from functools import partial +import logging +from typing import Any + +from pyhomeworks.pyhomeworks import Homeworks +import voluptuous as vol + +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.core import ( + DOMAIN as HOMEASSISTANT_DOMAIN, + HomeAssistant, + async_get_hass, + callback, +) +from homeassistant.data_entry_flow import AbortFlow +from homeassistant.helpers import ( + config_validation as cv, + entity_registry as er, + issue_registry as ir, + selector, +) +from homeassistant.helpers.schema_config_entry_flow import ( + SchemaCommonFlowHandler, + SchemaFlowError, + SchemaFlowFormStep, + SchemaFlowMenuStep, + SchemaOptionsFlowHandler, +) +from homeassistant.helpers.selector import TextSelector +from homeassistant.util import slugify + +from . import DEFAULT_FADE_RATE, calculate_unique_id +from .const import ( + CONF_ADDR, + CONF_CONTROLLER_ID, + CONF_DIMMERS, + CONF_INDEX, + CONF_KEYPADS, + CONF_RATE, + DEFAULT_KEYPAD_NAME, + DEFAULT_LIGHT_NAME, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +CONTROLLER_EDIT = { + vol.Required(CONF_HOST): selector.TextSelector(), + vol.Required(CONF_PORT): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, + max=65535, + mode=selector.NumberSelectorMode.BOX, + ) + ), +} + +LIGHT_EDIT = { + vol.Optional(CONF_RATE, default=DEFAULT_FADE_RATE): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=20, + mode=selector.NumberSelectorMode.SLIDER, + step=0.1, + ) + ), +} + + +validate_addr = cv.matches_regex(r"\[\d\d:\d\d:\d\d:\d\d\]") + + +async def validate_add_controller( + handler: ConfigFlow | SchemaOptionsFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Validate controller setup.""" + user_input[CONF_CONTROLLER_ID] = slugify(user_input[CONF_NAME]) + user_input[CONF_PORT] = int(user_input[CONF_PORT]) + try: + handler._async_abort_entries_match( # pylint: disable=protected-access + {CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]} + ) + except AbortFlow as err: + raise SchemaFlowError("duplicated_host_port") from err + + try: + handler._async_abort_entries_match( # pylint: disable=protected-access + {CONF_CONTROLLER_ID: user_input[CONF_CONTROLLER_ID]} + ) + except AbortFlow as err: + raise SchemaFlowError("duplicated_controller_id") from err + + await _try_connection(user_input) + + return user_input + + +async def _try_connection(user_input: dict[str, Any]) -> None: + """Try connecting to the controller.""" + + def _try_connect(host: str, port: int) -> None: + """Try connecting to the controller. + + Raises ConnectionError if the connection fails. + """ + _LOGGER.debug( + "Trying to connect to %s:%s", user_input[CONF_HOST], user_input[CONF_PORT] + ) + controller = Homeworks(host, port, lambda msg_types, values: None) + controller.close() + controller.join() + + hass = async_get_hass() + try: + await hass.async_add_executor_job( + _try_connect, user_input[CONF_HOST], user_input[CONF_PORT] + ) + except ConnectionError as err: + raise SchemaFlowError("connection_error") from err + except Exception as err: + _LOGGER.exception("Caught unexpected exception") + raise SchemaFlowError("unknown_error") from err + + +def _create_import_issue(hass: HomeAssistant) -> None: + """Create a repair issue asking the user to remove YAML.""" + ir.async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2024.6.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=ir.IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Lutron Homeworks", + }, + ) + + +def _validate_address(handler: SchemaCommonFlowHandler, addr: str) -> None: + """Validate address.""" + try: + validate_addr(addr) + except vol.Invalid as err: + raise SchemaFlowError("invalid_addr") from err + + for _key in (CONF_DIMMERS, CONF_KEYPADS): + items: list[dict[str, Any]] = handler.options[_key] + + for item in items: + if item[CONF_ADDR] == addr: + raise SchemaFlowError("duplicated_addr") + + +async def validate_add_keypad( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Validate keypad or light input.""" + _validate_address(handler, user_input[CONF_ADDR]) + + # Standard behavior is to merge the result with the options. + # In this case, we want to add a sub-item so we update the options directly. + items = handler.options[CONF_KEYPADS] + items.append(user_input) + return {} + + +async def validate_add_light( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Validate light input.""" + _validate_address(handler, user_input[CONF_ADDR]) + + # Standard behavior is to merge the result with the options. + # In this case, we want to add a sub-item so we update the options directly. + items = handler.options[CONF_DIMMERS] + items.append(user_input) + return {} + + +async def get_select_light_schema(handler: SchemaCommonFlowHandler) -> vol.Schema: + """Return schema for selecting a light.""" + return vol.Schema( + { + vol.Required(CONF_INDEX): vol.In( + { + str(index): f"{config[CONF_NAME]} ({config[CONF_ADDR]})" + for index, config in enumerate(handler.options[CONF_DIMMERS]) + }, + ) + } + ) + + +async def validate_select_keypad_light( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Store keypad or light index in flow state.""" + handler.flow_state["_idx"] = int(user_input[CONF_INDEX]) + return {} + + +async def get_edit_light_suggested_values( + handler: SchemaCommonFlowHandler, +) -> dict[str, Any]: + """Return suggested values for light editing.""" + idx: int = handler.flow_state["_idx"] + return dict(handler.options[CONF_DIMMERS][idx]) + + +async def validate_light_edit( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Update edited keypad or light.""" + # Standard behavior is to merge the result with the options. + # In this case, we want to add a sub-item so we update the options directly. + idx: int = handler.flow_state["_idx"] + handler.options[CONF_DIMMERS][idx].update(user_input) + return {} + + +async def get_remove_keypad_light_schema( + handler: SchemaCommonFlowHandler, *, key: str +) -> vol.Schema: + """Return schema for keypad or light removal.""" + return vol.Schema( + { + vol.Required(CONF_INDEX): cv.multi_select( + { + str(index): f"{config[CONF_NAME]} ({config[CONF_ADDR]})" + for index, config in enumerate(handler.options[key]) + }, + ) + } + ) + + +async def validate_remove_keypad_light( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any], *, key: str +) -> dict[str, Any]: + """Validate remove keypad or light.""" + removed_indexes: set[str] = set(user_input[CONF_INDEX]) + + # Standard behavior is to merge the result with the options. + # In this case, we want to remove sub-items so we update the options directly. + entity_registry = er.async_get(handler.parent_handler.hass) + items: list[dict[str, Any]] = [] + item: dict[str, Any] + for index, item in enumerate(handler.options[key]): + if str(index) not in removed_indexes: + items.append(item) + elif key != CONF_DIMMERS: + continue + if entity_id := entity_registry.async_get_entity_id( + LIGHT_DOMAIN, + DOMAIN, + calculate_unique_id( + handler.options[CONF_CONTROLLER_ID], item[CONF_ADDR], 0 + ), + ): + entity_registry.async_remove(entity_id) + handler.options[key] = items + return {} + + +DATA_SCHEMA_ADD_CONTROLLER = vol.Schema( + { + vol.Required( + CONF_NAME, description={"suggested_value": "Lutron Homeworks"} + ): selector.TextSelector(), + **CONTROLLER_EDIT, + } +) +DATA_SCHEMA_ADD_LIGHT = vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_LIGHT_NAME): TextSelector(), + vol.Required(CONF_ADDR): TextSelector(), + **LIGHT_EDIT, + } +) +DATA_SCHEMA_ADD_KEYPAD = vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_KEYPAD_NAME): TextSelector(), + vol.Required(CONF_ADDR): TextSelector(), + } +) +DATA_SCHEMA_EDIT_LIGHT = vol.Schema(LIGHT_EDIT) + +OPTIONS_FLOW = { + "init": SchemaFlowMenuStep( + [ + "add_keypad", + "remove_keypad", + "add_light", + "select_edit_light", + "remove_light", + ] + ), + "add_keypad": SchemaFlowFormStep( + DATA_SCHEMA_ADD_KEYPAD, + suggested_values=None, + validate_user_input=validate_add_keypad, + ), + "remove_keypad": SchemaFlowFormStep( + partial(get_remove_keypad_light_schema, key=CONF_KEYPADS), + suggested_values=None, + validate_user_input=partial(validate_remove_keypad_light, key=CONF_KEYPADS), + ), + "add_light": SchemaFlowFormStep( + DATA_SCHEMA_ADD_LIGHT, + suggested_values=None, + validate_user_input=validate_add_light, + ), + "select_edit_light": SchemaFlowFormStep( + get_select_light_schema, + suggested_values=None, + validate_user_input=validate_select_keypad_light, + next_step="edit_light", + ), + "edit_light": SchemaFlowFormStep( + DATA_SCHEMA_EDIT_LIGHT, + suggested_values=get_edit_light_suggested_values, + validate_user_input=validate_light_edit, + ), + "remove_light": SchemaFlowFormStep( + partial(get_remove_keypad_light_schema, key=CONF_DIMMERS), + suggested_values=None, + validate_user_input=partial(validate_remove_keypad_light, key=CONF_DIMMERS), + ), +} + + +class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN): + """Config flow for Lutron Homeworks.""" + + import_config: dict[str, Any] + + async def async_step_import(self, config: dict[str, Any]) -> ConfigFlowResult: + """Start importing configuration from yaml.""" + self.import_config = { + CONF_HOST: config[CONF_HOST], + CONF_PORT: config[CONF_PORT], + CONF_DIMMERS: [ + { + CONF_ADDR: light[CONF_ADDR], + CONF_NAME: light[CONF_NAME], + CONF_RATE: light[CONF_RATE], + } + for light in config[CONF_DIMMERS] + ], + CONF_KEYPADS: [ + { + CONF_ADDR: keypad[CONF_ADDR], + CONF_NAME: keypad[CONF_NAME], + } + for keypad in config[CONF_KEYPADS] + ], + } + return await self.async_step_import_controller_name() + + async def async_step_import_controller_name( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Ask user to set a name of the controller.""" + errors = {} + try: + self._async_abort_entries_match( + { + CONF_HOST: self.import_config[CONF_HOST], + CONF_PORT: self.import_config[CONF_PORT], + } + ) + except AbortFlow: + _create_import_issue(self.hass) + raise + + if user_input: + try: + user_input[CONF_CONTROLLER_ID] = slugify(user_input[CONF_NAME]) + self._async_abort_entries_match( + {CONF_CONTROLLER_ID: user_input[CONF_CONTROLLER_ID]} + ) + except AbortFlow: + errors["base"] = "duplicated_controller_id" + else: + self.import_config |= user_input + return await self.async_step_import_finish() + + return self.async_show_form( + step_id="import_controller_name", + data_schema=vol.Schema( + { + vol.Required( + CONF_NAME, description={"suggested_value": "Lutron Homeworks"} + ): selector.TextSelector(), + } + ), + errors=errors, + ) + + async def async_step_import_finish( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Ask user to remove YAML configuration.""" + + if user_input is not None: + entity_registry = er.async_get(self.hass) + config = self.import_config + for light in config[CONF_DIMMERS]: + addr = light[CONF_ADDR] + if entity_id := entity_registry.async_get_entity_id( + LIGHT_DOMAIN, DOMAIN, f"homeworks.{addr}" + ): + entity_registry.async_update_entity( + entity_id, + new_unique_id=calculate_unique_id( + config[CONF_CONTROLLER_ID], addr, 0 + ), + ) + name = config.pop(CONF_NAME) + return self.async_create_entry( + title=name, + data={}, + options=config, + ) + + return self.async_show_form(step_id="import_finish", data_schema=vol.Schema({})) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a flow initialized by the user.""" + errors = {} + if user_input: + try: + await validate_add_controller(self, user_input) + except SchemaFlowError as err: + errors["base"] = str(err) + else: + self._async_abort_entries_match( + {CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]} + ) + name = user_input.pop(CONF_NAME) + user_input |= {CONF_DIMMERS: [], CONF_KEYPADS: []} + return self.async_create_entry(title=name, data={}, options=user_input) + + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA_ADD_CONTROLLER, + errors=errors, + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> SchemaOptionsFlowHandler: + """Options flow handler for Lutron Homeworks.""" + return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) diff --git a/homeassistant/components/homeworks/const.py b/homeassistant/components/homeworks/const.py new file mode 100644 index 00000000000..df0e5294d4b --- /dev/null +++ b/homeassistant/components/homeworks/const.py @@ -0,0 +1,15 @@ +"""Constants for the Lutron Homeworks integration.""" +from __future__ import annotations + +DOMAIN = "homeworks" + +CONF_ADDR = "addr" +CONF_CONTROLLER_ID = "controller_id" +CONF_DIMMERS = "dimmers" +CONF_INDEX = "index" +CONF_KEYPADS = "keypads" +CONF_RATE = "rate" + +DEFAULT_BUTTON_NAME = "Homeworks button" +DEFAULT_KEYPAD_NAME = "Homeworks keypad" +DEFAULT_LIGHT_NAME = "Homeworks light" diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index 35a9e5665d1..98f327bdfce 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -7,53 +7,61 @@ from typing import Any from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import CONF_ADDR, CONF_DIMMERS, CONF_RATE, HOMEWORKS_CONTROLLER, HomeworksDevice +from . import HomeworksData, HomeworksEntity +from .const import CONF_ADDR, CONF_CONTROLLER_ID, CONF_DIMMERS, CONF_RATE, DOMAIN _LOGGER = logging.getLogger(__name__) -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discover_info: DiscoveryInfoType | None = None, +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Homeworks lights.""" - if discover_info is None: - return - - controller = hass.data[HOMEWORKS_CONTROLLER] + data: HomeworksData = hass.data[DOMAIN][entry.entry_id] + controller = data.controller + controller_id = entry.options[CONF_CONTROLLER_ID] devs = [] - for dimmer in discover_info[CONF_DIMMERS]: + for dimmer in entry.options.get(CONF_DIMMERS, []): dev = HomeworksLight( - controller, dimmer[CONF_ADDR], dimmer[CONF_NAME], dimmer[CONF_RATE] + controller, + controller_id, + dimmer[CONF_ADDR], + dimmer[CONF_NAME], + dimmer[CONF_RATE], ) devs.append(dev) - add_entities(devs, True) + async_add_entities(devs, True) -class HomeworksLight(HomeworksDevice, LightEntity): +class HomeworksLight(HomeworksEntity, LightEntity): """Homeworks Light.""" _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} - def __init__(self, controller, addr, name, rate): + def __init__( + self, + controller, + controller_id, + addr, + name, + rate, + ): """Create device with Addr, name, and rate.""" - super().__init__(controller, addr, name) + super().__init__(controller, controller_id, addr, 0, name) self._rate = rate self._level = 0 self._prev_level = 0 async def async_added_to_hass(self) -> None: """Call when entity is added to hass.""" - signal = f"homeworks_entity_{self._addr}" + signal = f"homeworks_entity_{self._controller_id}_{self._addr}" _LOGGER.debug("connecting %s", signal) self.async_on_remove( async_dispatcher_connect(self.hass, signal, self._update_callback) diff --git a/homeassistant/components/homeworks/manifest.json b/homeassistant/components/homeworks/manifest.json index 4a3f132e14d..c2520b910d9 100644 --- a/homeassistant/components/homeworks/manifest.json +++ b/homeassistant/components/homeworks/manifest.json @@ -2,6 +2,7 @@ "domain": "homeworks", "name": "Lutron Homeworks", "codeowners": [], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homeworks", "iot_class": "local_push", "loggers": ["pyhomeworks"], diff --git a/homeassistant/components/homeworks/strings.json b/homeassistant/components/homeworks/strings.json new file mode 100644 index 00000000000..3154d3e145e --- /dev/null +++ b/homeassistant/components/homeworks/strings.json @@ -0,0 +1,35 @@ +{ + "config": { + "error": { + "connection_error": "Could not connect to the controller.", + "duplicated_controller_id": "The controller name is already in use.", + "duplicated_host_port": "The specified host and port is already configured.", + "unknown_error": "[%key:common::config_flow::error::unknown%]" + }, + "step": { + "import_finish": { + "description": "The existing YAML configuration has succesfully been imported.\n\nYou can now remove the `homeworks` configuration from your configuration.yaml file." + }, + "import_controller_name": { + "description": "Lutron Homeworks is no longer configured through configuration.yaml.\n\nPlease fill in the form to import the existing configuration to the UI.", + "data": { + "name": "[%key:component::homeworks::config::step::user::data::name%]" + }, + "data_description": { + "name": "[%key:component::homeworks::config::step::user::data_description::name%]" + } + }, + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "name": "Controller name", + "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "name": "A unique name identifying the Lutron Homeworks controller" + }, + "description": "Add a Lutron Homeworks controller" + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 55d77e26336..b657433b20b 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -224,6 +224,7 @@ FLOWS = { "homekit_controller", "homematicip_cloud", "homewizard", + "homeworks", "honeywell", "huawei_lte", "hue", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 6b6c41e412c..17115a55435 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3370,7 +3370,7 @@ }, "homeworks": { "integration_type": "hub", - "config_flow": false, + "config_flow": true, "iot_class": "local_push", "name": "Lutron Homeworks" } diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f17011f6244..a10f92e32d2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1437,6 +1437,9 @@ pyhiveapi==0.5.16 # homeassistant.components.homematic pyhomematic==0.1.77 +# homeassistant.components.homeworks +pyhomeworks==0.0.6 + # homeassistant.components.ialarm pyialarm==2.2.0 diff --git a/tests/components/homeworks/__init__.py b/tests/components/homeworks/__init__.py new file mode 100644 index 00000000000..6cb38e6ff81 --- /dev/null +++ b/tests/components/homeworks/__init__.py @@ -0,0 +1 @@ +"""Tests for the Lutron Homeworks Series 4 and 8 integration.""" diff --git a/tests/components/homeworks/conftest.py b/tests/components/homeworks/conftest.py new file mode 100644 index 00000000000..32b77781097 --- /dev/null +++ b/tests/components/homeworks/conftest.py @@ -0,0 +1,82 @@ +"""Common fixtures for the Lutron Homeworks Series 4 and 8 tests.""" +from collections.abc import Generator +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from homeassistant.components.homeworks.const import ( + CONF_ADDR, + CONF_CONTROLLER_ID, + CONF_DIMMERS, + CONF_KEYPADS, + CONF_RATE, + DOMAIN, +) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + title="Lutron Homeworks", + domain=DOMAIN, + data={}, + options={ + CONF_CONTROLLER_ID: "main_controller", + CONF_HOST: "192.168.0.1", + CONF_PORT: 1234, + CONF_DIMMERS: [ + { + CONF_ADDR: "[02:08:01:01]", + CONF_NAME: "Foyer Sconces", + CONF_RATE: 1.0, + } + ], + CONF_KEYPADS: [ + { + CONF_ADDR: "[02:08:02:01]", + CONF_NAME: "Foyer Keypad", + } + ], + }, + ) + + +@pytest.fixture +def mock_empty_config_entry() -> MockConfigEntry: + """Return a mocked config entry with no keypads or dimmers.""" + return MockConfigEntry( + title="Lutron Homeworks", + domain=DOMAIN, + data={}, + options={ + CONF_CONTROLLER_ID: "main_controller", + CONF_HOST: "192.168.0.1", + CONF_PORT: 1234, + CONF_DIMMERS: [], + CONF_KEYPADS: [], + }, + ) + + +@pytest.fixture +def mock_homeworks() -> Generator[None, MagicMock, None]: + """Return a mocked Homeworks client.""" + with patch( + "homeassistant.components.homeworks.Homeworks", autospec=True + ) as homeworks_mock, patch( + "homeassistant.components.homeworks.config_flow.Homeworks", new=homeworks_mock + ): + yield homeworks_mock + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.homeworks.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry diff --git a/tests/components/homeworks/test_config_flow.py b/tests/components/homeworks/test_config_flow.py new file mode 100644 index 00000000000..0975abf1f82 --- /dev/null +++ b/tests/components/homeworks/test_config_flow.py @@ -0,0 +1,588 @@ +"""Test Lutron Homeworks Series 4 and 8 config flow.""" +from unittest.mock import ANY, MagicMock + +import pytest +from pytest_unordered import unordered + +from homeassistant.components.homeworks.const import ( + CONF_ADDR, + CONF_DIMMERS, + CONF_INDEX, + CONF_KEYPADS, + CONF_RATE, + DOMAIN, +) +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import entity_registry as er, issue_registry as ir + +from tests.common import MockConfigEntry + + +async def test_user_flow( + hass: HomeAssistant, mock_homeworks: MagicMock, mock_setup_entry +) -> None: + """Test the user configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.1", + CONF_NAME: "Main controller", + CONF_PORT: 1234, + }, + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Main controller" + assert result["data"] == {} + assert result["options"] == { + "controller_id": "main_controller", + "dimmers": [], + "host": "192.168.0.1", + "keypads": [], + "port": 1234, + } + mock_homeworks.assert_called_once_with("192.168.0.1", 1234, ANY) + mock_controller.close.assert_called_once_with() + mock_controller.join.assert_called_once_with() + + +async def test_user_flow_already_exists( + hass: HomeAssistant, mock_empty_config_entry: MockConfigEntry, mock_setup_entry +) -> None: + """Test the user configuration flow.""" + mock_empty_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.1", + CONF_NAME: "Main controller", + CONF_PORT: 1234, + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "duplicated_host_port"} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.2", + CONF_NAME: "Main controller", + CONF_PORT: 1234, + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "duplicated_controller_id"} + + +@pytest.mark.parametrize( + ("side_effect", "error"), + [(ConnectionError, "connection_error"), (Exception, "unknown_error")], +) +async def test_user_flow_cannot_connect( + hass: HomeAssistant, + mock_homeworks: MagicMock, + mock_setup_entry, + side_effect: type[Exception], + error: str, +) -> None: + """Test handling invalid connection.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + + mock_homeworks.side_effect = side_effect + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.1", + CONF_NAME: "Main controller", + CONF_PORT: 1234, + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": error} + assert result["step_id"] == "user" + + +async def test_import_flow( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + issue_registry: ir.IssueRegistry, + mock_homeworks: MagicMock, + mock_setup_entry, +) -> None: + """Test importing yaml config.""" + entry = entity_registry.async_get_or_create( + LIGHT_DOMAIN, DOMAIN, "homeworks.[02:08:01:01]" + ) + + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_HOST: "192.168.0.1", + CONF_PORT: 1234, + CONF_DIMMERS: [ + { + CONF_ADDR: "[02:08:01:01]", + CONF_NAME: "Foyer Sconces", + CONF_RATE: 1.0, + } + ], + CONF_KEYPADS: [ + { + CONF_ADDR: "[02:08:02:01]", + CONF_NAME: "Foyer Keypad", + } + ], + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "import_controller_name" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_NAME: "Main controller"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "import_finish" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Main controller" + assert result["data"] == {} + assert result["options"] == { + "controller_id": "main_controller", + "dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + assert len(issue_registry.issues) == 0 + + # Check unique ID is updated in entity registry + entry = entity_registry.async_get(entry.id) + assert entry.unique_id == "homeworks.main_controller.[02:08:01:01].0" + + +async def test_import_flow_already_exists( + hass: HomeAssistant, + issue_registry: ir.IssueRegistry, + mock_empty_config_entry: MockConfigEntry, +) -> None: + """Test importing yaml config where entry already exists.""" + mock_empty_config_entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={"host": "192.168.0.1", "port": 1234, CONF_DIMMERS: [], CONF_KEYPADS: []}, + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + assert len(issue_registry.issues) == 1 + + +async def test_import_flow_controller_id_exists( + hass: HomeAssistant, mock_empty_config_entry: MockConfigEntry +) -> None: + """Test importing yaml config where entry already exists.""" + mock_empty_config_entry.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={"host": "192.168.0.2", "port": 1234, CONF_DIMMERS: [], CONF_KEYPADS: []}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "import_controller_name" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_NAME: "Main controller"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "import_controller_name" + assert result["errors"] == {"base": "duplicated_controller_id"} + + +async def test_options_add_light_flow( + hass: HomeAssistant, + mock_empty_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test options flow to add a light.""" + mock_empty_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_empty_config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.async_entity_ids("light") == unordered([]) + + result = await hass.config_entries.options.async_init( + mock_empty_config_entry.entry_id + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "add_light"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_light" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_ADDR: "[02:08:01:02]", + CONF_NAME: "Foyer Downlights", + CONF_RATE: 2.0, + }, + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:02]", "name": "Foyer Downlights", "rate": 2.0}, + ], + "host": "192.168.0.1", + "keypads": [], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the entry was updated with the new entity + assert hass.states.async_entity_ids("light") == unordered( + ["light.foyer_downlights"] + ) + + +async def test_options_add_remove_light_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to add and remove a light.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.async_entity_ids("light") == unordered(["light.foyer_sconces"]) + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "add_light"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_light" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_ADDR: "[02:08:01:02]", + CONF_NAME: "Foyer Downlights", + CONF_RATE: 2.0, + }, + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}, + {"addr": "[02:08:01:02]", "name": "Foyer Downlights", "rate": 2.0}, + ], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the entry was updated with the new entity + assert hass.states.async_entity_ids("light") == unordered( + ["light.foyer_sconces", "light.foyer_downlights"] + ) + + # Now remove the original light + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "remove_light"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "remove_light" + assert result["data_schema"].schema["index"].options == { + "0": "Foyer Sconces ([02:08:01:01])", + "1": "Foyer Downlights ([02:08:01:02])", + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_INDEX: ["0"]} + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:02]", "name": "Foyer Downlights", "rate": 2.0}, + ], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the original entity was removed, with only the new entity left + assert hass.states.async_entity_ids("light") == unordered( + ["light.foyer_downlights"] + ) + + +async def test_options_add_remove_keypad_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to add and remove a keypad.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "add_keypad"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_keypad" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_ADDR: "[02:08:03:01]", + CONF_NAME: "Hall Keypad", + }, + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}, + ], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "name": "Foyer Keypad", + }, + {"addr": "[02:08:03:01]", "name": "Hall Keypad"}, + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Now remove the original keypad + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "remove_keypad"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "remove_keypad" + assert result["data_schema"].schema["index"].options == { + "0": "Foyer Keypad ([02:08:02:01])", + "1": "Hall Keypad ([02:08:03:01])", + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_INDEX: ["0"]} + ) + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}, + ], + "host": "192.168.0.1", + "keypads": [{"addr": "[02:08:03:01]", "name": "Hall Keypad"}], + "port": 1234, + } + await hass.async_block_till_done() + + +async def test_options_add_keypad_with_error( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to add and remove a keypad.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "add_keypad"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_keypad" + + # Try an invalid address + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_ADDR: "[02:08:03:01", + CONF_NAME: "Hall Keypad", + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_keypad" + assert result["errors"] == {"base": "invalid_addr"} + + # Try an address claimed by another keypad + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_ADDR: "[02:08:02:01]", + CONF_NAME: "Hall Keypad", + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_keypad" + assert result["errors"] == {"base": "duplicated_addr"} + + # Try an address claimed by a light + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_ADDR: "[02:08:01:01]", + CONF_NAME: "Hall Keypad", + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_keypad" + assert result["errors"] == {"base": "duplicated_addr"} + + +async def test_options_edit_light_no_lights_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to edit a light.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.async_entity_ids("light") == unordered(["light.foyer_sconces"]) + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_light"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_light" + assert result["data_schema"].schema["index"].container == { + "0": "Foyer Sconces ([02:08:01:01])" + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"index": "0"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "edit_light" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_RATE: 3.0} + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 3.0}], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the entity was updated + assert len(hass.states.async_entity_ids("light")) == 1 + + +async def test_options_edit_light_flow_empty( + hass: HomeAssistant, + mock_empty_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test options flow to edit a light.""" + mock_empty_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_empty_config_entry.entry_id) + await hass.async_block_till_done() + assert hass.states.async_entity_ids("light") == unordered([]) + + result = await hass.config_entries.options.async_init( + mock_empty_config_entry.entry_id + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_light"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_light" + assert result["data_schema"].schema["index"].container == {} From 6544e91f628f6e24c098916fa96d264ecc52db53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Mon, 4 Mar 2024 18:34:53 +0000 Subject: [PATCH 0265/1691] Update aioairzone to v0.7.6 (#112264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/airzone/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airzone/manifest.json b/homeassistant/components/airzone/manifest.json index 59b8645d26c..a14215fea6b 100644 --- a/homeassistant/components/airzone/manifest.json +++ b/homeassistant/components/airzone/manifest.json @@ -11,5 +11,5 @@ "documentation": "https://www.home-assistant.io/integrations/airzone", "iot_class": "local_polling", "loggers": ["aioairzone"], - "requirements": ["aioairzone==0.7.5"] + "requirements": ["aioairzone==0.7.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8226b2f4422..ee9d310734c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aioairq==0.3.2 aioairzone-cloud==0.4.5 # homeassistant.components.airzone -aioairzone==0.7.5 +aioairzone==0.7.6 # homeassistant.components.ambient_station aioambient==2024.01.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a10f92e32d2..eb44d9800a4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -170,7 +170,7 @@ aioairq==0.3.2 aioairzone-cloud==0.4.5 # homeassistant.components.airzone -aioairzone==0.7.5 +aioairzone==0.7.6 # homeassistant.components.ambient_station aioambient==2024.01.0 From 7406ae31f67d20251028de50a7043e22404b0ffc Mon Sep 17 00:00:00 2001 From: Jeef Date: Mon, 4 Mar 2024 11:35:50 -0700 Subject: [PATCH 0266/1691] Weatherflow_cloud backing lib bump (#112262) Backing lib bump --- homeassistant/components/weatherflow_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/weatherflow_cloud/manifest.json b/homeassistant/components/weatherflow_cloud/manifest.json index 6abbeef02df..15943dde32a 100644 --- a/homeassistant/components/weatherflow_cloud/manifest.json +++ b/homeassistant/components/weatherflow_cloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "iot_class": "cloud_polling", - "requirements": ["weatherflow4py==0.1.12"] + "requirements": ["weatherflow4py==0.1.13"] } diff --git a/requirements_all.txt b/requirements_all.txt index ee9d310734c..68cad7a4c7c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2833,7 +2833,7 @@ watchdog==2.3.1 waterfurnace==1.1.0 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.12 +weatherflow4py==0.1.13 # homeassistant.components.webmin webmin-xmlrpc==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eb44d9800a4..20fb6616b61 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2174,7 +2174,7 @@ wallbox==0.6.0 watchdog==2.3.1 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.12 +weatherflow4py==0.1.13 # homeassistant.components.webmin webmin-xmlrpc==0.0.1 From 68141873cdad82f1edcfd13781e947cc09fc0d9f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 4 Mar 2024 19:50:33 +0100 Subject: [PATCH 0267/1691] Enable strict typing of homeworks (#112267) --- .strict-typing | 1 + .../components/homeworks/__init__.py | 17 +++++++++---- homeassistant/components/homeworks/light.py | 24 +++++++++---------- mypy.ini | 10 ++++++++ 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.strict-typing b/.strict-typing index 74535719bb3..fb621d3e53a 100644 --- a/.strict-typing +++ b/.strict-typing @@ -228,6 +228,7 @@ homeassistant.components.homekit_controller.select homeassistant.components.homekit_controller.storage homeassistant.components.homekit_controller.utils homeassistant.components.homewizard.* +homeassistant.components.homeworks.* homeassistant.components.http.* homeassistant.components.huawei_lte.* homeassistant.components.humidifier.* diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 05ba4d02454..4e620dac27f 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -17,7 +17,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, Platform, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send @@ -118,7 +118,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except (ConnectionError, OSError) as err: raise ConfigEntryNotReady from err - def cleanup(event): + def cleanup(event: Event) -> None: controller.close() entry.async_on_unload(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)) @@ -158,7 +158,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: await hass.config_entries.async_reload(entry.entry_id) -def calculate_unique_id(controller_id, addr, idx): +def calculate_unique_id(controller_id: str, addr: str, idx: int) -> str: """Calculate entity unique id.""" return f"homeworks.{controller_id}.{addr}.{idx}" @@ -194,7 +194,14 @@ class HomeworksKeypad: instead of a sensor entity in hass. """ - def __init__(self, hass, controller, controller_id, addr, name): + def __init__( + self, + hass: HomeAssistant, + controller: Homeworks, + controller_id: str, + addr: str, + name: str, + ) -> None: """Register callback that will be used for signals.""" self._addr = addr self._controller = controller @@ -208,7 +215,7 @@ class HomeworksKeypad: ) @callback - def _update_callback(self, msg_type, values): + def _update_callback(self, msg_type: str, values: list[Any]) -> None: """Fire events if button is pressed or released.""" if msg_type == HW_BUTTON_PRESSED: diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index 98f327bdfce..3e6836c75b8 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -4,7 +4,7 @@ from __future__ import annotations import logging from typing import Any -from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED +from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED, Homeworks from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry @@ -47,12 +47,12 @@ class HomeworksLight(HomeworksEntity, LightEntity): def __init__( self, - controller, - controller_id, - addr, - name, - rate, - ): + controller: Homeworks, + controller_id: str, + addr: str, + name: str, + rate: float, + ) -> None: """Create device with Addr, name, and rate.""" super().__init__(controller, controller_id, addr, 0, name) self._rate = rate @@ -83,28 +83,28 @@ class HomeworksLight(HomeworksEntity, LightEntity): self._set_brightness(0) @property - def brightness(self): + def brightness(self) -> int: """Control the brightness.""" return self._level - def _set_brightness(self, level): + def _set_brightness(self, level: int) -> None: """Send the brightness level to the device.""" self._controller.fade_dim( float((level * 100.0) / 255.0), self._rate, 0, self._addr ) @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, str]: """Supported attributes.""" return {"homeworks_address": self._addr} @property - def is_on(self): + def is_on(self) -> bool: """Is the light on/off.""" return self._level != 0 @callback - def _update_callback(self, msg_type, values): + def _update_callback(self, msg_type: str, values: list[Any]) -> None: """Process device specific messages.""" if msg_type == HW_LIGHT_CHANGED: diff --git a/mypy.ini b/mypy.ini index 224508fb6bc..a8b146059fc 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2041,6 +2041,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.homeworks.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.http.*] check_untyped_defs = true disallow_incomplete_defs = true From 32964a2e9029c821a4bc532250ce49ac978e16bd Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 4 Mar 2024 19:50:49 +0100 Subject: [PATCH 0268/1691] Add device and enable entity name for Lutron homeworks (#112268) --- homeassistant/components/homeworks/__init__.py | 1 + homeassistant/components/homeworks/light.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 4e620dac27f..0abb22ad2e8 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -166,6 +166,7 @@ def calculate_unique_id(controller_id: str, addr: str, idx: int) -> str: class HomeworksEntity(Entity): """Base class of a Homeworks device.""" + _attr_has_entity_name = True _attr_should_poll = False def __init__( diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index 3e6836c75b8..4c5a657df36 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -10,6 +10,7 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEnti from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -54,7 +55,10 @@ class HomeworksLight(HomeworksEntity, LightEntity): rate: float, ) -> None: """Create device with Addr, name, and rate.""" - super().__init__(controller, controller_id, addr, 0, name) + super().__init__(controller, controller_id, addr, 0, None) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{controller_id}.{addr}")}, name=name + ) self._rate = rate self._level = 0 self._prev_level = 0 From 55d429926910536cddf6c9750b1fc2f36fb3b0c6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 20:04:13 +0100 Subject: [PATCH 0269/1691] Add icon translations to Steamist (#112246) --- homeassistant/components/steamist/icons.json | 9 +++++++++ homeassistant/components/steamist/switch.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/steamist/icons.json diff --git a/homeassistant/components/steamist/icons.json b/homeassistant/components/steamist/icons.json new file mode 100644 index 00000000000..7baeade8bcd --- /dev/null +++ b/homeassistant/components/steamist/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "switch": { + "steam_active": { + "default": "mdi:pot-steam" + } + } + } +} diff --git a/homeassistant/components/steamist/switch.py b/homeassistant/components/steamist/switch.py index a9a7526c560..2161af4df92 100644 --- a/homeassistant/components/steamist/switch.py +++ b/homeassistant/components/steamist/switch.py @@ -14,7 +14,6 @@ from .entity import SteamistEntity ACTIVE_SWITCH = SwitchEntityDescription( key="active", - icon="mdi:pot-steam", translation_key="steam_active", ) From c5e11a00badac842594e85e0d437da5dfa1a4c2a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 20:05:15 +0100 Subject: [PATCH 0270/1691] Add icon translations to Starlink (#112244) --- homeassistant/components/starlink/icons.json | 24 ++++++++++++++++++++ homeassistant/components/starlink/sensor.py | 6 ----- 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/starlink/icons.json diff --git a/homeassistant/components/starlink/icons.json b/homeassistant/components/starlink/icons.json new file mode 100644 index 00000000000..65cb273e24b --- /dev/null +++ b/homeassistant/components/starlink/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "sensor": { + "ping": { + "default": "mdi:speedometer" + }, + "azimuth": { + "default": "mdi:compass" + }, + "elevation": { + "default": "mdi:compass" + }, + "uplink_throughput": { + "default": "mdi:upload" + }, + "downlink_throughput": { + "default": "mdi:download" + }, + "last_boot_time": { + "default": "mdi:clock" + } + } + } +} diff --git a/homeassistant/components/starlink/sensor.py b/homeassistant/components/starlink/sensor.py index d5116d49305..555990a4b18 100644 --- a/homeassistant/components/starlink/sensor.py +++ b/homeassistant/components/starlink/sensor.py @@ -69,7 +69,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( StarlinkSensorEntityDescription( key="ping", translation_key="ping", - icon="mdi:speedometer", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTime.MILLISECONDS, suggested_display_precision=0, @@ -78,7 +77,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( StarlinkSensorEntityDescription( key="azimuth", translation_key="azimuth", - icon="mdi:compass", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=DEGREE, @@ -89,7 +87,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( StarlinkSensorEntityDescription( key="elevation", translation_key="elevation", - icon="mdi:compass", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=DEGREE, @@ -100,7 +97,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( StarlinkSensorEntityDescription( key="uplink_throughput", translation_key="uplink_throughput", - icon="mdi:upload", state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DATA_RATE, native_unit_of_measurement=UnitOfDataRate.BITS_PER_SECOND, @@ -110,7 +106,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( StarlinkSensorEntityDescription( key="downlink_throughput", translation_key="downlink_throughput", - icon="mdi:download", state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DATA_RATE, native_unit_of_measurement=UnitOfDataRate.BITS_PER_SECOND, @@ -120,7 +115,6 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = ( StarlinkSensorEntityDescription( key="last_boot_time", translation_key="last_boot_time", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda data: now() - timedelta(seconds=data.status["uptime"]), From fdd264194ba84584841490fa199949fab846ee93 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 20:05:59 +0100 Subject: [PATCH 0271/1691] Add icon translations to Squeezebox (#112240) --- homeassistant/components/squeezebox/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/squeezebox/icons.json diff --git a/homeassistant/components/squeezebox/icons.json b/homeassistant/components/squeezebox/icons.json new file mode 100644 index 00000000000..d90e5dad84d --- /dev/null +++ b/homeassistant/components/squeezebox/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "call_method": "mdi:terminal", + "call_query": "mdi:database", + "sync": "mdi:sync", + "unsync": "mdi:sync-off" + } +} From 119df48aac82ef9189703949d8c35cab1ad05c4f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 20:08:37 +0100 Subject: [PATCH 0272/1691] Add icon translations to Streamlabswater (#112247) --- homeassistant/components/streamlabswater/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/streamlabswater/icons.json diff --git a/homeassistant/components/streamlabswater/icons.json b/homeassistant/components/streamlabswater/icons.json new file mode 100644 index 00000000000..aebe224b35e --- /dev/null +++ b/homeassistant/components/streamlabswater/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_away_mode": "mdi:home" + } +} From 0663a4be3be01027246eb0c72a5b1a108b101d89 Mon Sep 17 00:00:00 2001 From: Isak Nyberg <36712644+IsakNyberg@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:11:14 +0100 Subject: [PATCH 0273/1691] Add permobil binary sensor (#112130) * add binary sensor * remove _LOGGER and mixin --- .coveragerc | 1 + homeassistant/components/permobil/__init__.py | 2 +- .../components/permobil/binary_sensor.py | 71 +++++++++++++++++++ .../components/permobil/coordinator.py | 2 +- .../components/permobil/strings.json | 5 ++ 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/permobil/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index b020819d3be..d95c636e22c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -983,6 +983,7 @@ omit = homeassistant/components/pandora/media_player.py homeassistant/components/pencom/switch.py homeassistant/components/permobil/__init__.py + homeassistant/components/permobil/binary_sensor.py homeassistant/components/permobil/coordinator.py homeassistant/components/permobil/entity.py homeassistant/components/permobil/sensor.py diff --git a/homeassistant/components/permobil/__init__.py b/homeassistant/components/permobil/__init__.py index 0213fb6a4b6..18025220852 100644 --- a/homeassistant/components/permobil/__init__.py +++ b/homeassistant/components/permobil/__init__.py @@ -21,7 +21,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import APPLICATION, DOMAIN from .coordinator import MyPermobilCoordinator -PLATFORMS: list[Platform] = [Platform.SENSOR] +PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/permobil/binary_sensor.py b/homeassistant/components/permobil/binary_sensor.py new file mode 100644 index 00000000000..afa17bde04f --- /dev/null +++ b/homeassistant/components/permobil/binary_sensor.py @@ -0,0 +1,71 @@ +"""Platform for binary sensor integration.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from mypermobil import BATTERY_CHARGING + +from homeassistant import config_entries +from homeassistant.components.binary_sensor import ( + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import MyPermobilCoordinator +from .entity import PermobilEntity + + +@dataclass(frozen=True, kw_only=True) +class PermobilBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Permobil binary sensor entity.""" + + is_on_fn: Callable[[Any], bool] + available_fn: Callable[[Any], bool] + + +BINARY_SENSOR_DESCRIPTIONS: tuple[PermobilBinarySensorEntityDescription, ...] = ( + PermobilBinarySensorEntityDescription( + is_on_fn=lambda data: data.battery[BATTERY_CHARGING[0]], + available_fn=lambda data: BATTERY_CHARGING[0] in data.battery, + key="is_charging", + translation_key="is_charging", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Create and setup the binary sensor.""" + + coordinator: MyPermobilCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities( + PermobilbinarySensor(coordinator=coordinator, description=description) + for description in BINARY_SENSOR_DESCRIPTIONS + ) + + +class PermobilbinarySensor(PermobilEntity, BinarySensorEntity): + """Representation of a Binary Sensor.""" + + entity_description: PermobilBinarySensorEntityDescription + + @property + def is_on(self) -> bool: + """Return True if the wheelchair is charging.""" + return self.entity_description.is_on_fn(self.coordinator.data) + + @property + def available(self) -> bool: + """Return True if the sensor has value.""" + return super().available and self.entity_description.available_fn( + self.coordinator.data + ) diff --git a/homeassistant/components/permobil/coordinator.py b/homeassistant/components/permobil/coordinator.py index 3695236cdf0..f505e73fa23 100644 --- a/homeassistant/components/permobil/coordinator.py +++ b/homeassistant/components/permobil/coordinator.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) class MyPermobilData: """MyPermobil data stored in the DataUpdateCoordinator.""" - battery: dict[str, str | float | int | list | dict] + battery: dict[str, str | float | int | bool | list | dict] daily_usage: dict[str, str | float | int | list | dict] records: dict[str, str | float | int | list | dict] diff --git a/homeassistant/components/permobil/strings.json b/homeassistant/components/permobil/strings.json index 5070c13d9e5..d3a9290854e 100644 --- a/homeassistant/components/permobil/strings.json +++ b/homeassistant/components/permobil/strings.json @@ -69,6 +69,11 @@ "record_distance": { "name": "Record distance" } + }, + "binary_sensor": { + "is_charging": { + "name": "Is charging" + } } } } From cfd88d004b9faf01520f06808eafaced4fc6f8cc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 20:26:42 +0100 Subject: [PATCH 0274/1691] Add icon translations to Sure Petcare (#112253) --- homeassistant/components/surepetcare/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/surepetcare/icons.json diff --git a/homeassistant/components/surepetcare/icons.json b/homeassistant/components/surepetcare/icons.json new file mode 100644 index 00000000000..1db15b599df --- /dev/null +++ b/homeassistant/components/surepetcare/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_lock_state": "mdi:lock", + "set_pet_location": "mdi:dog" + } +} From f7acb7c91be5fc49bfc59423bc959cd2e584d427 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 4 Mar 2024 20:27:08 +0100 Subject: [PATCH 0275/1691] Add icon translations to Spotify (#112239) --- homeassistant/components/spotify/icons.json | 9 +++++++++ homeassistant/components/spotify/media_player.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/spotify/icons.json diff --git a/homeassistant/components/spotify/icons.json b/homeassistant/components/spotify/icons.json new file mode 100644 index 00000000000..00c63141eae --- /dev/null +++ b/homeassistant/components/spotify/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "media_player": { + "spotify": { + "default": "mdi:spotify" + } + } + } +} diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 03b703220c3..caa796f9b59 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -118,9 +118,9 @@ class SpotifyMediaPlayer(MediaPlayerEntity): """Representation of a Spotify controller.""" _attr_has_entity_name = True - _attr_icon = "mdi:spotify" _attr_media_image_remotely_accessible = False _attr_name = None + _attr_translation_key = "spotify" def __init__( self, From 59df20f1d48f5bf5b9cde62042516914c0d43f95 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Mon, 4 Mar 2024 14:28:53 -0500 Subject: [PATCH 0276/1691] Bump python_roborock to 0.40.0 (#112238) * bump to python_roborock 0.40.0 * manifest went away in merge? --- homeassistant/components/roborock/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/roborock/snapshots/test_diagnostics.ambr | 4 ++++ tests/components/roborock/test_diagnostics.py | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/roborock/manifest.json b/homeassistant/components/roborock/manifest.json index 946ba77b163..a7a7fe01d23 100644 --- a/homeassistant/components/roborock/manifest.json +++ b/homeassistant/components/roborock/manifest.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "loggers": ["roborock"], "requirements": [ - "python-roborock==0.39.2", + "python-roborock==0.40.0", "vacuum-map-parser-roborock==0.1.1" ] } diff --git a/requirements_all.txt b/requirements_all.txt index 68cad7a4c7c..cd87eb9f34b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2282,7 +2282,7 @@ python-rabbitair==0.0.8 python-ripple-api==0.0.3 # homeassistant.components.roborock -python-roborock==0.39.2 +python-roborock==0.40.0 # homeassistant.components.smarttub python-smarttub==0.0.36 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 20fb6616b61..ac958da3ae6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1755,7 +1755,7 @@ python-qbittorrent==0.4.3 python-rabbitair==0.0.8 # homeassistant.components.roborock -python-roborock==0.39.2 +python-roborock==0.40.0 # homeassistant.components.smarttub python-smarttub==0.0.36 diff --git a/tests/components/roborock/snapshots/test_diagnostics.ambr b/tests/components/roborock/snapshots/test_diagnostics.ambr index 6bcd2152a95..3d78e5fd638 100644 --- a/tests/components/roborock/snapshots/test_diagnostics.ambr +++ b/tests/components/roborock/snapshots/test_diagnostics.ambr @@ -32,6 +32,8 @@ 'coordinators': dict({ '**REDACTED-0**': dict({ 'api': dict({ + 'misc_info': dict({ + }), }), 'roborock_device_info': dict({ 'device': dict({ @@ -309,6 +311,8 @@ }), '**REDACTED-1**': dict({ 'api': dict({ + 'misc_info': dict({ + }), }), 'roborock_device_info': dict({ 'device': dict({ diff --git a/tests/components/roborock/test_diagnostics.py b/tests/components/roborock/test_diagnostics.py index a10cbcf057e..cc02fff3edc 100644 --- a/tests/components/roborock/test_diagnostics.py +++ b/tests/components/roborock/test_diagnostics.py @@ -1,6 +1,7 @@ """Tests for the diagnostics data provided by the Roborock integration.""" from syrupy.assertion import SnapshotAssertion +from syrupy.filters import props from homeassistant.core import HomeAssistant @@ -20,4 +21,4 @@ async def test_diagnostics( result = await get_diagnostics_for_config_entry(hass, hass_client, setup_entry) assert isinstance(result, dict) - assert result == snapshot + assert result == snapshot(exclude=props("Nonce")) From 8777606c6f9a53b6de874c16f3f82834b1f625bb Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 4 Mar 2024 21:40:13 +0100 Subject: [PATCH 0277/1691] Remove callback decorator from coroutine (#112276) --- homeassistant/components/unifi/button.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/unifi/button.py b/homeassistant/components/unifi/button.py index 85cd6c94c74..545bb08a85a 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -40,7 +40,6 @@ from .entity import ( from .hub import UnifiHub -@callback async def async_restart_device_control_fn( api: aiounifi.Controller, obj_id: str ) -> None: @@ -48,7 +47,6 @@ async def async_restart_device_control_fn( await api.request(DeviceRestartRequest.create(obj_id)) -@callback async def async_power_cycle_port_control_fn( api: aiounifi.Controller, obj_id: str ) -> None: From e357c4d5e584e23a52a8aaaece2c2037b8f09285 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 4 Mar 2024 23:58:20 +0100 Subject: [PATCH 0278/1691] Update pytest to 8.1.0 (#112173) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index d367e81c0b2..36e625e5993 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -28,7 +28,7 @@ pytest-timeout==2.2.0 pytest-unordered==0.5.2 pytest-picked==0.5.0 pytest-xdist==3.3.1 -pytest==8.0.2 +pytest==8.1.0 requests-mock==1.11.0 respx==0.20.2 syrupy==4.6.1 From 2c179dc5fbb06dda5cc4af6b3e01222d8d96ed90 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 15:59:12 -1000 Subject: [PATCH 0279/1691] Reduce some linear searches to cleanup the device registry (#112277) Some of the data we had to search for was already available in a dict or underlying data structure. Make it available instead of having to build it every time. There are more places these can be used, but I only did the device registry cleanup for now --- homeassistant/config_entries.py | 5 +++++ homeassistant/helpers/device_registry.py | 10 +++++++--- homeassistant/helpers/entity_registry.py | 20 +++++++++++++++++++- tests/helpers/test_entity_registry.py | 15 +++++++++++++++ tests/test_config_entries.py | 20 ++++++++++---------- 5 files changed, 56 insertions(+), 14 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 219f4ff1709..0dae0359d8a 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1462,6 +1462,11 @@ class ConfigEntries: """Return entry with matching entry_id.""" return self._entries.data.get(entry_id) + @callback + def async_entry_ids(self) -> list[str]: + """Return entry ids.""" + return list(self._entries.data) + @callback def async_entries( self, diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 826a4cc200e..5cbc40e209f 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1090,7 +1090,7 @@ def async_cleanup( ) -> None: """Clean up device registry.""" # Find all devices that are referenced by a config_entry. - config_entry_ids = {entry.entry_id for entry in hass.config_entries.async_entries()} + config_entry_ids = set(hass.config_entries.async_entry_ids()) references_config_entries = { device.id for device in dev_reg.devices.values() @@ -1099,9 +1099,13 @@ def async_cleanup( } # Find all devices that are referenced in the entity registry. - references_entities = {entry.device_id for entry in ent_reg.entities.values()} + device_ids_referenced_by_entities = set(ent_reg.entities.get_device_ids()) - orphan = set(dev_reg.devices) - references_entities - references_config_entries + orphan = ( + set(dev_reg.devices) + - device_ids_referenced_by_entities + - references_config_entries + ) for dev_id in orphan: dev_reg.async_remove_device(dev_id) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 50ecbc1fb59..51afe4fc740 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -10,7 +10,7 @@ timer. from __future__ import annotations from collections import UserDict -from collections.abc import Callable, Iterable, Mapping, ValuesView +from collections.abc import Callable, Iterable, KeysView, Mapping, ValuesView from datetime import datetime, timedelta from enum import StrEnum import logging @@ -511,6 +511,14 @@ class EntityRegistryItems(UserDict[str, RegistryEntry]): self._unindex_entry(key) super().__delitem__(key) + def get_entity_ids(self) -> ValuesView[str]: + """Return entity ids.""" + return self._index.values() + + def get_device_ids(self) -> KeysView[str]: + """Return device ids.""" + return self._device_id_index.keys() + def get_entity_id(self, key: tuple[str, str, str]) -> str | None: """Get entity_id from (domain, platform, unique_id).""" return self._index.get(key) @@ -612,6 +620,16 @@ class EntityRegistry: """Check if an entity_id is currently registered.""" return self.entities.get_entity_id((domain, platform, unique_id)) + @callback + def async_entity_ids(self) -> list[str]: + """Return entity ids.""" + return list(self.entities.get_entity_ids()) + + @callback + def async_device_ids(self) -> list[str]: + """Return known device ids.""" + return list(self.entities.get_device_ids()) + def _entity_id_available( self, entity_id: str, known_object_ids: Iterable[str] | None ) -> bool: diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 0178e4fcd11..1b0fbe51147 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -90,6 +90,9 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: unit_of_measurement="initial-unit_of_measurement", ) + assert set(entity_registry.async_device_ids()) == {"mock-dev-id"} + assert set(entity_registry.async_entity_ids()) == {"light.hue_5678"} + assert orig_entry == er.RegistryEntry( "light.hue_5678", "5678", @@ -159,6 +162,9 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: unit_of_measurement="updated-unit_of_measurement", ) + assert set(entity_registry.async_device_ids()) == {"new-mock-dev-id"} + assert set(entity_registry.async_entity_ids()) == {"light.hue_5678"} + new_entry = entity_registry.async_get_or_create( "light", "hue", @@ -203,6 +209,9 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: unit_of_measurement=None, ) + assert set(entity_registry.async_device_ids()) == set() + assert set(entity_registry.async_entity_ids()) == {"light.hue_5678"} + def test_get_or_create_suggested_object_id_conflict_register( entity_registry: er.EntityRegistry, @@ -446,6 +455,8 @@ def test_async_get_entity_id(entity_registry: er.EntityRegistry) -> None: ) assert entity_registry.async_get_entity_id("light", "hue", "123") is None + assert set(entity_registry.async_entity_ids()) == {"light.hue_1234"} + async def test_updating_config_entry_id( hass: HomeAssistant, entity_registry: er.EntityRegistry @@ -1469,6 +1480,7 @@ def test_entity_registry_items() -> None: entities = er.EntityRegistryItems() assert entities.get_entity_id(("a", "b", "c")) is None assert entities.get_entry("abc") is None + assert set(entities.get_entity_ids()) == set() entry1 = er.RegistryEntry("test.entity1", "1234", "hue") entry2 = er.RegistryEntry("test.entity2", "2345", "hue") @@ -1482,6 +1494,7 @@ def test_entity_registry_items() -> None: assert entities.get_entry(entry1.id) is entry1 assert entities.get_entity_id(("test", "hue", "2345")) is entry2.entity_id assert entities.get_entry(entry2.id) is entry2 + assert set(entities.get_entity_ids()) == {"test.entity2", "test.entity1"} entities.pop("test.entity1") del entities["test.entity2"] @@ -1491,6 +1504,8 @@ def test_entity_registry_items() -> None: assert entities.get_entity_id(("test", "hue", "2345")) is None assert entities.get_entry(entry2.id) is None + assert set(entities.get_entity_ids()) == set() + async def test_disabled_by_str_not_allowed( hass: HomeAssistant, entity_registry: er.EntityRegistry diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 247a34c078b..d6c5d8bdc5c 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -378,7 +378,7 @@ async def test_remove_entry( MockConfigEntry(domain="test_other", entry_id="test3").add_to_manager(manager) # Check all config entries exist - assert [item.entry_id for item in manager.async_entries()] == [ + assert manager.async_entry_ids() == [ "test1", "test2", "test3", @@ -408,7 +408,7 @@ async def test_remove_entry( assert mock_remove_entry.call_count == 1 # Check that config entry was removed. - assert [item.entry_id for item in manager.async_entries()] == ["test1", "test3"] + assert manager.async_entry_ids() == ["test1", "test3"] # Check that entity state has been removed assert hass.states.get("light.test_entity") is None @@ -469,7 +469,7 @@ async def test_remove_entry_handles_callback_error( entry = MockConfigEntry(domain="test", entry_id="test1") entry.add_to_manager(manager) # Check all config entries exist - assert [item.entry_id for item in manager.async_entries()] == ["test1"] + assert manager.async_entry_ids() == ["test1"] # Setup entry await entry.async_setup(hass) await hass.async_block_till_done() @@ -482,7 +482,7 @@ async def test_remove_entry_handles_callback_error( # Check the remove callback was invoked. assert mock_remove_entry.call_count == 1 # Check that config entry was removed. - assert [item.entry_id for item in manager.async_entries()] == [] + assert manager.async_entry_ids() == [] async def test_remove_entry_raises( @@ -502,7 +502,7 @@ async def test_remove_entry_raises( ).add_to_manager(manager) MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager) - assert [item.entry_id for item in manager.async_entries()] == [ + assert manager.async_entry_ids() == [ "test1", "test2", "test3", @@ -511,7 +511,7 @@ async def test_remove_entry_raises( result = await manager.async_remove("test2") assert result == {"require_restart": True} - assert [item.entry_id for item in manager.async_entries()] == ["test1", "test3"] + assert manager.async_entry_ids() == ["test1", "test3"] async def test_remove_entry_if_not_loaded( @@ -526,7 +526,7 @@ async def test_remove_entry_if_not_loaded( MockConfigEntry(domain="comp", entry_id="test2").add_to_manager(manager) MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager) - assert [item.entry_id for item in manager.async_entries()] == [ + assert manager.async_entry_ids() == [ "test1", "test2", "test3", @@ -535,7 +535,7 @@ async def test_remove_entry_if_not_loaded( result = await manager.async_remove("test2") assert result == {"require_restart": False} - assert [item.entry_id for item in manager.async_entries()] == ["test1", "test3"] + assert manager.async_entry_ids() == ["test1", "test3"] assert len(mock_unload_entry.mock_calls) == 0 @@ -550,7 +550,7 @@ async def test_remove_entry_if_integration_deleted( MockConfigEntry(domain="comp", entry_id="test2").add_to_manager(manager) MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager) - assert [item.entry_id for item in manager.async_entries()] == [ + assert manager.async_entry_ids() == [ "test1", "test2", "test3", @@ -559,7 +559,7 @@ async def test_remove_entry_if_integration_deleted( result = await manager.async_remove("test2") assert result == {"require_restart": False} - assert [item.entry_id for item in manager.async_entries()] == ["test1", "test3"] + assert manager.async_entry_ids() == ["test1", "test3"] assert len(mock_unload_entry.mock_calls) == 0 From e26c5f5d299318b2eff9e2939616b1eb0daf9cf9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 16:07:07 -1000 Subject: [PATCH 0280/1691] Ensure hassio diagnostics get imported with the integration (#112286) --- homeassistant/components/hassio/__init__.py | 4 ++-- homeassistant/components/hassio/diagnostics.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 8e8d077c838..5355c5bb16c 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -39,10 +39,10 @@ from homeassistant.loader import bind_hass from homeassistant.util.async_ import create_eager_task from homeassistant.util.dt import now -# config_flow, and entity platforms are imported to ensure +# config_flow, diagnostics, and entity platforms are imported to ensure # other dependencies that wait for hassio are not waiting # for hassio to import its platforms -from . import binary_sensor, config_flow, sensor, update # noqa: F401 +from . import binary_sensor, config_flow, diagnostics, sensor, update # noqa: F401 from .addon_manager import AddonError, AddonInfo, AddonManager, AddonState # noqa: F401 from .addon_panel import async_setup_addon_panel from .auth import async_setup_auth_view diff --git a/homeassistant/components/hassio/diagnostics.py b/homeassistant/components/hassio/diagnostics.py index 41b3fc1c5c3..e4f98e87fc5 100644 --- a/homeassistant/components/hassio/diagnostics.py +++ b/homeassistant/components/hassio/diagnostics.py @@ -9,7 +9,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er -from . import ADDONS_COORDINATOR, HassioDataUpdateCoordinator +from .const import ADDONS_COORDINATOR +from .data import HassioDataUpdateCoordinator async def async_get_config_entry_diagnostics( From d0c81f7d0022f8794c724cc48d9d56a4ef462309 Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Tue, 5 Mar 2024 03:19:26 +0100 Subject: [PATCH 0281/1691] Bump bring-api to 0.5.5 (#112266) Fix KeyError listArticleLanguage --- homeassistant/components/bring/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bring/manifest.json b/homeassistant/components/bring/manifest.json index 0e425ec3eee..d8bfc6c7ebd 100644 --- a/homeassistant/components/bring/manifest.json +++ b/homeassistant/components/bring/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/bring", "integration_type": "service", "iot_class": "cloud_polling", - "requirements": ["bring-api==0.5.4"] + "requirements": ["bring-api==0.5.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index cd87eb9f34b..e160e1be026 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -603,7 +603,7 @@ boschshcpy==0.2.75 boto3==1.33.13 # homeassistant.components.bring -bring-api==0.5.4 +bring-api==0.5.5 # homeassistant.components.broadlink broadlink==0.18.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ac958da3ae6..c280c61f176 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -514,7 +514,7 @@ bond-async==0.2.1 boschshcpy==0.2.75 # homeassistant.components.bring -bring-api==0.5.4 +bring-api==0.5.5 # homeassistant.components.broadlink broadlink==0.18.3 From 1e173e82d094bcf05131e4e0a79d29d0546dd873 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 16:33:12 -1000 Subject: [PATCH 0282/1691] Add support for preloading platforms in the loader (#112282) Co-authored-by: Paulus Schoutsen --- homeassistant/config.py | 4 +- homeassistant/helpers/integration_platform.py | 4 +- homeassistant/loader.py | 145 +++++++++++------- tests/common.py | 4 +- tests/test_loader.py | 101 +++++++----- tests/test_setup.py | 2 +- 6 files changed, 163 insertions(+), 97 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 3fd76c275c2..8962ff8e7a5 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1459,9 +1459,9 @@ async def async_process_component_config( # noqa: C901 # Check if the integration has a custom config validator config_validator = None # A successful call to async_get_component will prime - # the cache for platform_exists to ensure it does no + # the cache for platforms_exists to ensure it does no # blocking I/O - if integration.platform_exists("config") is not False: + if integration.platforms_exists(("config",)): # If the config platform cannot possibly exist, don't try to load it. try: config_validator = await integration.async_get_platform("config") diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 8ba0790a874..26a5e837a2e 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -15,6 +15,7 @@ from homeassistant.loader import ( Integration, async_get_integrations, async_get_loaded_integration, + async_register_preload_platform, bind_hass, ) from homeassistant.setup import ATTR_COMPONENT, EventComponentLoaded @@ -58,7 +59,7 @@ def _get_platform( # `stat()` system calls which is far cheaper than calling # `integration.get_platform` # - if integration.platform_exists(platform_name) is False: + if not integration.platforms_exists((platform_name,)): # If the platform cannot possibly exist, don't bother trying to load it return None @@ -127,6 +128,7 @@ async def async_process_integration_platforms( else: integration_platforms = hass.data[DATA_INTEGRATION_PLATFORMS] + async_register_preload_platform(hass, platform_name) top_level_components = {comp for comp in hass.config.components if "." not in comp} process_job = HassJob( catch_log_exception( diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 46f3e352596..2cee4318e9e 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -54,10 +54,38 @@ _CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) _LOGGER = logging.getLogger(__name__) +# +# Integration.get_component will check preload platforms and +# try to import the code to avoid a thundering heard of import +# executor jobs later in the startup process. +# +# default platforms are prepopulated in this list to ensure that +# by the time the component is loaded, we check if the platform is +# available. +# +# This list can be extended by calling async_register_preload_platform +# +BASE_PRELOAD_PLATFORMS = [ + "config", + "diagnostics", + "energy", + "group", + "logbook", + "hardware", + "intent", + "media_source", + "recorder", + "repairs", + "system_health", + "trigger", +] + + DATA_COMPONENTS = "components" DATA_INTEGRATIONS = "integrations" DATA_MISSING_PLATFORMS = "missing_platforms" DATA_CUSTOM_COMPONENTS = "custom_components" +DATA_PRELOAD_PLATFORMS = "preload_platforms" PACKAGE_CUSTOM_COMPONENTS = "custom_components" PACKAGE_BUILTIN = "homeassistant.components" CUSTOM_WARNING = ( @@ -161,7 +189,7 @@ class Manifest(TypedDict, total=False): disabled: str domain: str integration_type: Literal[ - "entity", "device", "hardware", "helper", "hub", "service", "system" + "entity", "device", "hardware", "helper", "hub", "service", "system", "virtual" ] dependencies: list[str] after_dependencies: list[str] @@ -192,6 +220,7 @@ def async_setup(hass: HomeAssistant) -> None: hass.data[DATA_COMPONENTS] = {} hass.data[DATA_INTEGRATIONS] = {} hass.data[DATA_MISSING_PLATFORMS] = {} + hass.data[DATA_PRELOAD_PLATFORMS] = BASE_PRELOAD_PLATFORMS.copy() def manifest_from_legacy_module(domain: str, module: ModuleType) -> Manifest: @@ -568,6 +597,14 @@ async def async_get_mqtt(hass: HomeAssistant) -> dict[str, list[str]]: return mqtt +@callback +def async_register_preload_platform(hass: HomeAssistant, platform_name: str) -> None: + """Register a platform to be preloaded.""" + preload_platforms: list[str] = hass.data[DATA_PRELOAD_PLATFORMS] + if platform_name not in preload_platforms: + preload_platforms.append(platform_name) + + class Integration: """An integration in Home Assistant.""" @@ -590,11 +627,16 @@ class Integration: ) continue + file_path = manifest_path.parent + # Avoid the listdir for virtual integrations + # as they cannot have any platforms + is_virtual = manifest.get("integration_type") == "virtual" integration = cls( hass, f"{root_module.__name__}.{domain}", - manifest_path.parent, + file_path, manifest, + None if is_virtual else set(os.listdir(file_path)), ) if integration.is_built_in: @@ -647,6 +689,7 @@ class Integration: pkg_path: str, file_path: pathlib.Path, manifest: Manifest, + top_level_files: set[str] | None = None, ) -> None: """Initialize an integration.""" self.hass = hass @@ -662,6 +705,8 @@ class Integration: self._all_dependencies_resolved = True self._all_dependencies = set() + platforms_to_preload: list[str] = hass.data[DATA_PRELOAD_PLATFORMS] + self._platforms_to_preload = platforms_to_preload self._component_future: asyncio.Future[ComponentProtocol] | None = None self._import_futures: dict[str, asyncio.Future[ModuleType]] = {} cache: dict[str, ModuleType | ComponentProtocol] = hass.data[DATA_COMPONENTS] @@ -670,6 +715,7 @@ class Integration: DATA_MISSING_PLATFORMS ] self._missing_platforms_cache = missing_platforms_cache + self._top_level_files = top_level_files or set() _LOGGER.info("Loaded %s from %s", self.domain, pkg_path) @cached_property @@ -735,7 +781,9 @@ class Integration: @cached_property def integration_type( self, - ) -> Literal["entity", "device", "hardware", "helper", "hub", "service", "system"]: + ) -> Literal[ + "entity", "device", "hardware", "helper", "hub", "service", "system", "virtual" + ]: """Return the integration type.""" return self.manifest.get("integration_type", "hub") @@ -878,7 +926,9 @@ class Integration: self._component_future = self.hass.loop.create_future() try: try: - comp = await self.hass.async_add_import_executor_job(self.get_component) + comp = await self.hass.async_add_import_executor_job( + self.get_component, True + ) except ImportError as ex: load_executor = False _LOGGER.debug( @@ -909,7 +959,7 @@ class Integration: return comp - def get_component(self) -> ComponentProtocol: + def get_component(self, preload_platforms: bool = False) -> ComponentProtocol: """Return the component. This method must be thread-safe as it's called from the executor @@ -940,22 +990,19 @@ class Integration: ) raise ImportError(f"Exception importing {self.pkg_path}") from err - if self.platform_exists("config"): - # Setting up a component always checks if the config - # platform exists. Since we may be running in the executor - # we will use this opportunity to cache the config platform - # as well. - with suppress(ImportError): - self.get_platform("config") + if preload_platforms: + for platform_name in self.platforms_exists(self._platforms_to_preload): + with suppress(ImportError): + self.get_platform(platform_name) - if self.config_flow: - # If there is a config flow, we will cache it as well since - # config entry setup always has to load the flow to get the - # major/minor version for migrations. Since we may be running - # in the executor we will use this opportunity to cache the - # config_flow as well. - with suppress(ImportError): - self.get_platform("config_flow") + if self.config_flow: + # If there is a config flow, we will cache it as well since + # config entry setup always has to load the flow to get the + # major/minor version for migrations. Since we may be running + # in the executor we will use this opportunity to cache the + # config_flow as well. + with suppress(ImportError): + self.get_platform("config_flow") return cache[domain] @@ -985,7 +1032,7 @@ class Integration: for platform_name in platform_names: full_name = f"{domain}.{platform_name}" - if platform := self._get_platform_cached(full_name): + if platform := self._get_platform_cached_or_raise(full_name): platforms[platform_name] = platform continue @@ -1065,7 +1112,7 @@ class Integration: return platforms - def _get_platform_cached(self, full_name: str) -> ModuleType | None: + def _get_platform_cached_or_raise(self, full_name: str) -> ModuleType | None: """Return a platform for an integration from cache.""" if full_name in self._cache: # the cache is either a ModuleType or a ComponentProtocol @@ -1077,43 +1124,35 @@ class Integration: def get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" - if platform := self._get_platform_cached(f"{self.domain}.{platform_name}"): + if platform := self._get_platform_cached_or_raise( + f"{self.domain}.{platform_name}" + ): return platform return self._load_platform(platform_name) - def platform_exists(self, platform_name: str) -> bool | None: - """Check if a platform exists for an integration. + def platforms_exists(self, platform_names: Iterable[str]) -> list[str]: + """Check if a platforms exists for an integration. - Returns True if the platform exists, False if it does not. - - If it cannot be determined if the platform exists without attempting - to import the component, it returns None. This will only happen - if this function is called before get_component or async_get_component - has been called for the integration or the integration failed to load. + This method is thread-safe and can be called from the executor + or event loop without doing blocking I/O. """ - full_name = f"{self.domain}.{platform_name}" - cache = self._cache - if full_name in cache: - return True + files = self._top_level_files + domain = self.domain + existing_platforms: list[str] = [] + missing_platforms = self._missing_platforms_cache + for platform_name in platform_names: + full_name = f"{domain}.{platform_name}" + if full_name not in missing_platforms and ( + f"{platform_name}.py" in files or platform_name in files + ): + existing_platforms.append(platform_name) + continue + missing_platforms[full_name] = ModuleNotFoundError( + f"Platform {full_name} not found", + name=f"{self.pkg_path}.{platform_name}", + ) - if full_name in self._missing_platforms_cache: - return False - - if not (component := cache.get(self.domain)) or not ( - file := getattr(component, "__file__", None) - ): - return None - - path: pathlib.Path = pathlib.Path(file).parent.joinpath(platform_name) - if os.path.exists(path.with_suffix(".py")) or os.path.exists(path): - return True - - exc = ModuleNotFoundError( - f"Platform {full_name} not found", - name=f"{self.pkg_path}.{platform_name}", - ) - self._missing_platforms_cache[full_name] = exc - return False + return existing_platforms def _load_platform(self, platform_name: str) -> ModuleType: """Load a platform for an integration. diff --git a/tests/common.py b/tests/common.py index 738183a3f87..829368b309d 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1396,6 +1396,7 @@ def mock_integration( else f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{module.DOMAIN}", pathlib.Path(""), module.mock_manifest(), + set(), ) def mock_import_platform(platform_name: str) -> NoReturn: @@ -1423,13 +1424,14 @@ def mock_platform( platform_path is in form hue.config_flow. """ - domain = platform_path.split(".")[0] + domain, _, platform_name = platform_path.partition(".") integration_cache = hass.data[loader.DATA_INTEGRATIONS] module_cache = hass.data[loader.DATA_COMPONENTS] if domain not in integration_cache: mock_integration(hass, MockModule(domain)) + integration_cache[domain]._top_level_files.add(f"{platform_name}.py") _LOGGER.info("Adding mock integration platform: %s", platform_path) module_cache[platform_path] = module or Mock() diff --git a/tests/test_loader.py b/tests/test_loader.py index 0c0431ed227..7c4a10c4b36 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1094,27 +1094,36 @@ async def test_async_get_component_preloads_config_and_config_flow( assert "homeassistant.components.executor_import" not in sys.modules assert "custom_components.executor_import" not in sys.modules + platform_exists_calls = [] + + def mock_platforms_exists(platforms: list[str]) -> bool: + platform_exists_calls.append(platforms) + return platforms + with patch( "homeassistant.loader.importlib.import_module" ) as mock_import, patch.object( - executor_import_integration, "platform_exists", return_value=True - ) as mock_platform_exists: + executor_import_integration, "platforms_exists", mock_platforms_exists + ): await executor_import_integration.async_get_component() - assert mock_platform_exists.call_count == 1 - assert mock_import.call_count == 3 + assert len(platform_exists_calls[0]) == len(loader.BASE_PRELOAD_PLATFORMS) + assert mock_import.call_count == 2 + len(loader.BASE_PRELOAD_PLATFORMS) assert ( mock_import.call_args_list[0][0][0] == "homeassistant.components.executor_import" ) - assert ( - mock_import.call_args_list[1][0][0] - == "homeassistant.components.executor_import.config" - ) - assert ( - mock_import.call_args_list[2][0][0] - == "homeassistant.components.executor_import.config_flow" - ) + checked_platforms = { + mock_import.call_args_list[i][0][0] + for i in range(1, len(mock_import.call_args_list)) + } + assert checked_platforms == { + "homeassistant.components.executor_import.config_flow", + *( + f"homeassistant.components.executor_import.{platform}" + for platform in loader.BASE_PRELOAD_PLATFORMS + ), + } async def test_async_get_component_loads_loop_if_already_in_sys_modules( @@ -1134,8 +1143,8 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert "test_package_loaded_executor.config_flow" not in hass.config.components config_flow_module_name = f"{integration.pkg_path}.config_flow" - module_mock = MagicMock() - config_flow_module_mock = MagicMock() + module_mock = MagicMock(__file__="__init__.py") + config_flow_module_mock = MagicMock(__file__="config_flow.py") def import_module(name: str) -> Any: if name == integration.pkg_path: @@ -1194,8 +1203,8 @@ async def test_async_get_component_concurrent_loads( assert "test_package_loaded_executor.config_flow" not in hass.config.components config_flow_module_name = f"{integration.pkg_path}.config_flow" - module_mock = MagicMock() - config_flow_module_mock = MagicMock() + module_mock = MagicMock(__file__="__init__.py") + config_flow_module_mock = MagicMock(__file__="config_flow.py") imports = [] start_event = threading.Event() import_event = asyncio.Event() @@ -1232,7 +1241,8 @@ async def test_async_get_component_concurrent_loads( assert comp1 is module_mock assert comp2 is module_mock - assert imports == [integration.pkg_path, config_flow_module_name] + assert integration.pkg_path in imports + assert config_flow_module_name in imports async def test_async_get_component_deadlock_fallback( @@ -1243,7 +1253,7 @@ async def test_async_get_component_deadlock_fallback( hass, "executor_import", True, import_executor=True ) assert executor_import_integration.import_executor is True - module_mock = MagicMock() + module_mock = MagicMock(__file__="__init__.py") import_attempts = 0 def mock_import(module: str, *args: Any, **kwargs: Any) -> Any: @@ -1395,38 +1405,51 @@ async def test_async_get_platform_raises_after_import_failure( assert "loaded_executor=False" not in caplog.text -async def test_platform_exists( +async def test_platforms_exists( hass: HomeAssistant, enable_custom_integrations: None ) -> None: - """Test platform_exists.""" - integration = await loader.async_get_integration(hass, "test_integration_platform") - assert integration.domain == "test_integration_platform" + """Test platforms_exists.""" + original_os_listdir = os.listdir - # get_component never called, will return None - assert integration.platform_exists("non_existing") is None + paths: list[str] = [] - component = integration.get_component() + def mock_list_dir(path: str) -> list[str]: + paths.append(path) + return original_os_listdir(path) + + with patch("homeassistant.loader.os.listdir", mock_list_dir): + integration = await loader.async_get_integration( + hass, "test_integration_platform" + ) + assert integration.domain == "test_integration_platform" + + # Verify the files cache is primed + assert integration.file_path in paths + + # component is loaded, should now return False + with patch("homeassistant.loader.os.listdir", wraps=os.listdir) as mock_exists: + component = integration.get_component() assert component.DOMAIN == "test_integration_platform" - # component is loaded, should now return False - with patch( - "homeassistant.loader.os.path.exists", wraps=os.path.exists - ) as mock_exists: - assert integration.platform_exists("non_existing") is False - - # We should check if the file exists - assert mock_exists.call_count == 2 + # The files cache should be primed when + # the integration is resolved + assert mock_exists.call_count == 0 # component is loaded, should now return False - with patch( - "homeassistant.loader.os.path.exists", wraps=os.path.exists - ) as mock_exists: - assert integration.platform_exists("non_existing") is False + with patch("homeassistant.loader.os.listdir", wraps=os.listdir) as mock_exists: + assert integration.platforms_exists(("non_existing",)) == [] + + # We should remember which files exist + assert mock_exists.call_count == 0 + + # component is loaded, should now return False + with patch("homeassistant.loader.os.listdir", wraps=os.listdir) as mock_exists: + assert integration.platforms_exists(("non_existing",)) == [] # We should remember the file does not exist assert mock_exists.call_count == 0 - assert integration.platform_exists("group") is True + assert integration.platforms_exists(["group"]) == ["group"] platform = await integration.async_get_platform("group") assert platform.MAGIC == 1 @@ -1434,7 +1457,7 @@ async def test_platform_exists( platform = integration.get_platform("group") assert platform.MAGIC == 1 - assert integration.platform_exists("group") is True + assert integration.platforms_exists(["group"]) == ["group"] async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( diff --git a/tests/test_setup.py b/tests/test_setup.py index 1a27ed5584b..29ced934745 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -843,7 +843,7 @@ async def test_async_prepare_setup_platform( caplog.clear() # There is no actual config platform for this integration assert await setup.async_prepare_setup_platform(hass, {}, "config", "test") is None - assert "test.config not found" in caplog.text + assert "No module named 'custom_components.test.config'" in caplog.text button_platform = ( await setup.async_prepare_setup_platform(hass, {}, "button", "test") is None From a9caa3e582ac4a1e58d287b57656f2b8e6d3fafc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 05:45:03 +0100 Subject: [PATCH 0283/1691] Add icon translations to Elkm1 (#111512) --- homeassistant/components/elkm1/icons.json | 27 +++++++++++++++++++++++ homeassistant/components/elkm1/sensor.py | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/elkm1/icons.json diff --git a/homeassistant/components/elkm1/icons.json b/homeassistant/components/elkm1/icons.json new file mode 100644 index 00000000000..3bb9ea8c87d --- /dev/null +++ b/homeassistant/components/elkm1/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "sensor": { + "panel": { + "default": "mdi:home" + }, + "setting": { + "default": "mdi:numeric" + } + } + }, + "services": { + "alarm_bypass": "mdi:shield-off", + "alarm_clear_bypass": "mdi:shield", + "alarm_arm_home_instant": "mdi:shield-lock", + "alarm_arm_night_instant": "mdi:shield-moon", + "alarm_arm_vacation": "mdi:beach", + "alarm_display_message": "mdi:message-alert", + "set_time": "mdi:clock-edit", + "speak_phrase": "mdi:message-processing", + "speak_word": "mdi:message-minus", + "sensor_counter_refresh": "mdi:refresh", + "sensor_counter_set": "mdi:counter", + "sensor_zone_bypass": "mdi:shield-off", + "sensor_zone_trigger": "mdi:shield" + } +} diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 9bd78f61673..59b52ff2b60 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -161,7 +161,7 @@ class ElkKeypad(ElkSensor): class ElkPanel(ElkSensor): """Representation of an Elk-M1 Panel.""" - _attr_icon = "mdi:home" + _attr_translation_key = "panel" _attr_entity_category = EntityCategory.DIAGNOSTIC _element: Panel @@ -184,7 +184,7 @@ class ElkPanel(ElkSensor): class ElkSetting(ElkSensor): """Representation of an Elk-M1 Setting.""" - _attr_icon = "mdi:numeric" + _attr_translation_key = "setting" _element: Setting def _element_changed(self, _: Element, changeset: Any) -> None: From 8b017016b09b7657d4f9f8fdf894b66e9e1b1b7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 4 Mar 2024 19:21:18 -1000 Subject: [PATCH 0284/1691] Refactor integration platforms to import in the executor (#112168) Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/integration_platform.py | 188 ++++++++++++------ homeassistant/loader.py | 4 + .../discovergy/test_system_health.py | 7 + tests/components/gios/test_system_health.py | 5 + tests/components/group/test_init.py | 1 + tests/helpers/test_integration_platform.py | 84 +++++++- tests/test_loader.py | 13 ++ 7 files changed, 242 insertions(+), 60 deletions(-) diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 26a5e837a2e..ef9c97504ae 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -37,68 +37,113 @@ class IntegrationPlatform: @callback -def _get_platform( - integration: Integration | Exception, component_name: str, platform_name: str -) -> ModuleType | None: - """Get a platform from an integration.""" - if isinstance(integration, Exception): - _LOGGER.exception( - "Error importing integration %s for %s", - component_name, - platform_name, - ) - return None - - # - # Loading the platform may do quite a bit of blocking I/O - # and CPU work. (https://github.com/python/cpython/issues/92041) - # - # We don't want to block the event loop for too - # long so we check if the platform exists with `platform_exists` - # before trying to load it. `platform_exists` will do two - # `stat()` system calls which is far cheaper than calling - # `integration.get_platform` - # - if not integration.platforms_exists((platform_name,)): - # If the platform cannot possibly exist, don't bother trying to load it - return None - - try: - return integration.get_platform(platform_name) - except ImportError as err: - if f"{component_name}.{platform_name}" not in str(err): - _LOGGER.exception( - "Unexpected error importing %s/%s.py", - component_name, - platform_name, - ) - - return None - - -@callback -def _async_process_integration_platforms_for_component( +def _async_integration_platform_component_loaded( hass: HomeAssistant, integration_platforms: list[IntegrationPlatform], event: EventType[EventComponentLoaded], ) -> None: """Process integration platforms for a component.""" - component_name = event.data[ATTR_COMPONENT] - if "." in component_name: + if "." in (component_name := event.data[ATTR_COMPONENT]): return integration = async_get_loaded_integration(hass, component_name) + # First filter out platforms that the integration already processed. + integration_platforms_by_name: dict[str, IntegrationPlatform] = {} for integration_platform in integration_platforms: - if component_name in integration_platform.seen_components or not ( - platform := _get_platform( - integration, component_name, integration_platform.platform_name - ) - ): + if component_name in integration_platform.seen_components: continue integration_platform.seen_components.add(component_name) - hass.async_run_hass_job( - integration_platform.process_job, hass, component_name, platform + integration_platforms_by_name[ + integration_platform.platform_name + ] = integration_platform + + if not integration_platforms_by_name: + return + + # Next, check which platforms exist for this integration. + platforms_that_exist = integration.platforms_exists(integration_platforms_by_name) + if not platforms_that_exist: + return + + # If everything is already loaded, we can avoid creating a task. + can_use_cache = True + platforms: dict[str, ModuleType] = {} + for platform_name in platforms_that_exist: + if platform := integration.get_platform_cached(platform_name): + platforms[platform_name] = platform + else: + can_use_cache = False + break + + if can_use_cache: + _process_integration_platforms( + hass, + integration, + platforms, + integration_platforms_by_name, ) + return + + # At least one of the platforms is not loaded, we need to load them + # so we have to fall back to creating a task. + hass.async_create_task( + _async_process_integration_platforms_for_component( + hass, integration, platforms_that_exist, integration_platforms_by_name + ), + eager_start=True, + ) + + +async def _async_process_integration_platforms_for_component( + hass: HomeAssistant, + integration: Integration, + platforms_that_exist: list[str], + integration_platforms_by_name: dict[str, IntegrationPlatform], +) -> None: + """Process integration platforms for a component.""" + # Now we know which platforms to load, let's load them. + try: + platforms = await integration.async_get_platforms(platforms_that_exist) + except ImportError: + _LOGGER.debug( + "Unexpected error importing integration platforms for %s", + integration.domain, + ) + return + + if futures := _process_integration_platforms( + hass, + integration, + platforms, + integration_platforms_by_name, + ): + await asyncio.gather(*futures) + + +@callback +def _process_integration_platforms( + hass: HomeAssistant, + integration: Integration, + platforms: dict[str, ModuleType], + integration_platforms_by_name: dict[str, IntegrationPlatform], +) -> list[asyncio.Future[Awaitable[None] | None]]: + """Process integration platforms for a component. + + Only the platforms that are passed in will be processed. + """ + return [ + future + for platform_name, platform in platforms.items() + if (integration_platform := integration_platforms_by_name[platform_name]) + and ( + future := hass.async_run_hass_job( + integration_platform.process_job, + hass, + integration.domain, + platform, + ) + ) + ] def _format_err(name: str, platform_name: str, *args: Any) -> str: @@ -120,10 +165,11 @@ async def async_process_integration_platforms( hass.bus.async_listen( EVENT_COMPONENT_LOADED, partial( - _async_process_integration_platforms_for_component, + _async_integration_platform_component_loaded, hass, integration_platforms, ), + run_immediately=True, ) else: integration_platforms = hass.data[DATA_INTEGRATION_PLATFORMS] @@ -140,16 +186,42 @@ async def async_process_integration_platforms( integration_platform = IntegrationPlatform( platform_name, process_job, top_level_components ) + # Tell the loader that it should try to pre-load the integration + # for any future components that are loaded so we can reduce the + # amount of import executor usage. + async_register_preload_platform(hass, platform_name) integration_platforms.append(integration_platform) - if not top_level_components: return integrations = await async_get_integrations(hass, top_level_components) - if futures := [ - future - for comp in top_level_components - if (platform := _get_platform(integrations[comp], comp, platform_name)) - and (future := hass.async_run_hass_job(process_job, hass, comp, platform)) - ]: + loaded_integrations: list[Integration] = [ + integration + for integration in integrations.values() + if not isinstance(integration, Exception) + ] + # Finally, fetch the platforms for each integration and process them. + # This uses the import executor in a loop. If there are a lot + # of integration with the integration platform to process, + # this could be a bottleneck. + futures: list[asyncio.Future[None]] = [] + for integration in loaded_integrations: + if not integration.platforms_exists((platform_name,)): + continue + try: + platform = await integration.async_get_platform(platform_name) + except ImportError: + _LOGGER.debug( + "Unexpected error importing %s for %s", + platform_name, + integration.domain, + ) + continue + + if future := hass.async_run_hass_job( + process_job, hass, integration.domain, platform + ): + futures.append(future) + + if futures: await asyncio.gather(*futures) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 2cee4318e9e..a7798f7b5e2 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1122,6 +1122,10 @@ class Integration: raise self._missing_platforms_cache[full_name] return None + def get_platform_cached(self, platform_name: str) -> ModuleType | None: + """Return a platform for an integration from cache.""" + return self._cache.get(f"{self.domain}.{platform_name}") # type: ignore[return-value] + def get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" if platform := self._get_platform_cached_or_raise( diff --git a/tests/components/discovergy/test_system_health.py b/tests/components/discovergy/test_system_health.py index 91025b06dd7..e64072f1023 100644 --- a/tests/components/discovergy/test_system_health.py +++ b/tests/components/discovergy/test_system_health.py @@ -6,6 +6,7 @@ from pydiscovergy.const import API_BASE from homeassistant.components.discovergy.const import DOMAIN from homeassistant.core import HomeAssistant +from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component from tests.common import get_system_health_info @@ -17,8 +18,11 @@ async def test_discovergy_system_health( ) -> None: """Test Discovergy system health.""" aioclient_mock.get(API_BASE, text="") + integration = await async_get_integration(hass, DOMAIN) + await integration.async_get_component() hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, DOMAIN) @@ -34,8 +38,11 @@ async def test_discovergy_system_health_fail( ) -> None: """Test Discovergy system health.""" aioclient_mock.get(API_BASE, exc=ClientError) + integration = await async_get_integration(hass, DOMAIN) + await integration.async_get_component() hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, DOMAIN) diff --git a/tests/components/gios/test_system_health.py b/tests/components/gios/test_system_health.py index 11af7bad8f3..00e2fe50dd4 100644 --- a/tests/components/gios/test_system_health.py +++ b/tests/components/gios/test_system_health.py @@ -5,6 +5,7 @@ from aiohttp import ClientError from homeassistant.components.gios.const import DOMAIN from homeassistant.core import HomeAssistant +from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component from tests.common import get_system_health_info @@ -16,6 +17,8 @@ async def test_gios_system_health( ) -> None: """Test GIOS system health.""" aioclient_mock.get("http://api.gios.gov.pl/", text="") + integration = await async_get_integration(hass, DOMAIN) + await integration.async_get_component() hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) @@ -33,6 +36,8 @@ async def test_gios_system_health_fail( ) -> None: """Test GIOS system health.""" aioclient_mock.get("http://api.gios.gov.pl/", exc=ClientError) + integration = await async_get_integration(hass, DOMAIN) + await integration.async_get_component() hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index cb5143d5a12..a9a2145798b 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -553,6 +553,7 @@ async def test_group_updated_after_device_tracker_zone_change( assert await async_setup_component(hass, "group", {}) assert await async_setup_component(hass, "device_tracker", {}) + await hass.async_block_till_done() await group.Group.async_create_group( hass, diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index 29bda99c9c6..9b1919b35a0 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -1,10 +1,11 @@ """Test integration platform helpers.""" from collections.abc import Callable from types import ModuleType -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest +from homeassistant import loader from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.integration_platform import ( @@ -52,6 +53,84 @@ async def test_process_integration_platforms(hass: HomeAssistant) -> None: assert len(processed) == 2 +async def test_process_integration_platforms_import_fails( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test processing integrations when one fails to import.""" + loaded_platform = Mock() + mock_platform(hass, "loaded.platform_to_check", loaded_platform) + hass.config.components.add("loaded") + + event_platform = Mock() + mock_platform(hass, "event.platform_to_check", event_platform) + + processed = [] + + async def _process_platform(hass, domain, platform): + """Process platform.""" + processed.append((domain, platform)) + + loaded_integration = await loader.async_get_integration(hass, "loaded") + with patch.object( + loaded_integration, "async_get_platform", side_effect=ImportError + ): + await async_process_integration_platforms( + hass, "platform_to_check", _process_platform + ) + + assert len(processed) == 0 + assert "Unexpected error importing platform_to_check for loaded" in caplog.text + + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) + await hass.async_block_till_done() + + assert len(processed) == 1 + assert processed[0][0] == "event" + assert processed[0][1] == event_platform + + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) + await hass.async_block_till_done() + + # Firing again should not check again + assert len(processed) == 1 + + +async def test_process_integration_platforms_import_fails_after_registered( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test processing integrations when one fails to import.""" + loaded_platform = Mock() + mock_platform(hass, "loaded.platform_to_check", loaded_platform) + hass.config.components.add("loaded") + + event_platform = Mock() + mock_platform(hass, "event.platform_to_check", event_platform) + + processed = [] + + async def _process_platform(hass, domain, platform): + """Process platform.""" + processed.append((domain, platform)) + + await async_process_integration_platforms( + hass, "platform_to_check", _process_platform + ) + + assert len(processed) == 1 + assert processed[0][0] == "loaded" + assert processed[0][1] == loaded_platform + + event_integration = await loader.async_get_integration(hass, "event") + with patch.object( + event_integration, "async_get_platforms", side_effect=ImportError + ), patch.object(event_integration, "get_platform_cached", return_value=None): + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) + await hass.async_block_till_done() + + assert len(processed) == 1 + assert "Unexpected error importing integration platforms for event" in caplog.text + + @callback def _process_platform_callback( hass: HomeAssistant, domain: str, platform: ModuleType @@ -126,8 +205,9 @@ async def test_broken_integration( hass, "platform_to_check", _process_platform ) + # This should never actually happen as the component cannot be + # in hass.config.components without a loaded manifest assert len(processed) == 0 - assert "Error importing integration loaded for platform_to_check" in caplog.text async def test_process_integration_platforms_no_integrations( diff --git a/tests/test_loader.py b/tests/test_loader.py index 7c4a10c4b36..74eb4c6ed69 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -120,6 +120,8 @@ async def test_custom_component_name( integration = await loader.async_get_integration(hass, "test") platform = integration.get_platform("light") + assert integration.get_platform_cached("light") is platform + assert platform.__name__ == "custom_components.test.light" assert platform.__package__ == "custom_components.test" @@ -277,6 +279,9 @@ async def test_async_get_platform_caches_failures_when_component_loaded( with pytest.raises(ImportError): assert await integration.async_get_platform("light") == hue_light + # The cache should never be filled because the import error is remembered + assert integration.get_platform_cached("light") is None + async def test_async_get_platforms_caches_failures_when_component_loaded( hass: HomeAssistant, @@ -312,6 +317,9 @@ async def test_async_get_platforms_caches_failures_when_component_loaded( with pytest.raises(ImportError): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} + # The cache should never be filled because the import error is remembered + assert integration.get_platform_cached("light") is None + async def test_get_integration_legacy( hass: HomeAssistant, enable_custom_integrations: None @@ -320,6 +328,7 @@ async def test_get_integration_legacy( integration = await loader.async_get_integration(hass, "test_embedded") assert integration.get_component().DOMAIN == "test_embedded" assert integration.get_platform("switch") is not None + assert integration.get_platform_cached("switch") is not None async def test_get_integration_custom_component( @@ -1549,6 +1558,9 @@ async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( "switch": switch_module_mock, "light": light_module_mock, } + assert integration.get_platform_cached("button") is button_module_mock + assert integration.get_platform_cached("switch") is switch_module_mock + assert integration.get_platform_cached("light") is light_module_mock async def test_async_get_platforms_concurrent_loads( @@ -1610,3 +1622,4 @@ async def test_async_get_platforms_concurrent_loads( assert load_result2 == {"button": button_module_mock} assert imports == [button_module_name] + assert integration.get_platform_cached("button") is button_module_mock From ab4750c2ead35891788cbde63a2792d14693a3a9 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 5 Mar 2024 08:23:17 +0100 Subject: [PATCH 0285/1691] Remove unnecessary access to UniFi hub object in tests (#112275) * Remove unnecessary access to UniFi hub object * Split strings * Skip + on concatenating f-strings * Use single quotes inside double quotes --- tests/components/unifi/test_button.py | 19 ++++--- tests/components/unifi/test_device_tracker.py | 55 ++++++++++++++----- tests/components/unifi/test_sensor.py | 11 ++-- tests/components/unifi/test_services.py | 15 ++--- tests/components/unifi/test_switch.py | 30 +++++----- tests/components/unifi/test_update.py | 5 +- 6 files changed, 87 insertions(+), 48 deletions(-) diff --git a/tests/components/unifi/test_button.py b/tests/components/unifi/test_button.py index 4518393226c..d5be861139b 100644 --- a/tests/components/unifi/test_button.py +++ b/tests/components/unifi/test_button.py @@ -1,8 +1,13 @@ """UniFi Network button platform tests.""" from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass -from homeassistant.components.unifi.const import DOMAIN as UNIFI_DOMAIN -from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, EntityCategory +from homeassistant.components.unifi.const import CONF_SITE_ID +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + CONF_HOST, + STATE_UNAVAILABLE, + EntityCategory, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -36,8 +41,6 @@ async def test_restart_device_button( } ], ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 1 ent_reg_entry = entity_registry.async_get("button.switch_restart") @@ -52,7 +55,8 @@ async def test_restart_device_button( # Send restart device command aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/devmgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/devmgr", ) await hass.services.async_call( @@ -120,8 +124,6 @@ async def test_power_cycle_poe( } ], ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2 ent_reg_entry = entity_registry.async_get("button.switch_port_1_power_cycle") @@ -136,7 +138,8 @@ async def test_power_cycle_poe( # Send restart device command aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/devmgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/devmgr", ) await hass.services.async_call( diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 9a4b406c364..ebf9823fb6d 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -9,11 +9,13 @@ from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.unifi.const import ( CONF_BLOCK_CLIENT, CONF_CLIENT_SOURCE, + CONF_DETECTION_TIME, CONF_IGNORE_WIRED_BUG, CONF_SSID_FILTER, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, + DEFAULT_DETECTION_TIME, DOMAIN as UNIFI_DOMAIN, ) from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNAVAILABLE @@ -55,7 +57,6 @@ async def test_tracked_wireless_clients( config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[client] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME @@ -70,7 +71,9 @@ async def test_tracked_wireless_clients( # Change time to mark client as away - new_time = dt_util.utcnow() + hub.option_detection_time + new_time = dt_util.utcnow() + timedelta( + seconds=config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() @@ -194,7 +197,7 @@ async def test_tracked_wireless_clients_event_source( config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[client] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 assert hass.states.get("device_tracker.client").state == STATE_NOT_HOME @@ -243,7 +246,14 @@ async def test_tracked_wireless_clients_event_source( assert hass.states.get("device_tracker.client").state == STATE_HOME # Change time to mark client as away - freezer.tick(hub.option_detection_time + timedelta(seconds=1)) + freezer.tick( + timedelta( + seconds=( + config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + + 1 + ) + ) + ) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -282,7 +292,14 @@ async def test_tracked_wireless_clients_event_source( assert hass.states.get("device_tracker.client").state == STATE_HOME # Change time to mark client as away - freezer.tick(hub.option_detection_time + timedelta(seconds=1)) + freezer.tick( + timedelta( + seconds=( + config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + + 1 + ) + ) + ) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -682,10 +699,8 @@ async def test_option_ssid_filter( config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[client, client_on_ssid2] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 - assert hass.states.get("device_tracker.client").state == STATE_HOME assert hass.states.get("device_tracker.client_on_ssid2").state == STATE_NOT_HOME @@ -711,7 +726,11 @@ async def test_option_ssid_filter( mock_unifi_websocket(message=MessageKey.CLIENT, data=client_on_ssid2) await hass.async_block_till_done() - new_time = dt_util.utcnow() + hub.option_detection_time + new_time = dt_util.utcnow() + timedelta( + seconds=( + config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + 1 + ) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() @@ -739,7 +758,11 @@ async def test_option_ssid_filter( # Time pass to mark client as away - new_time += hub.option_detection_time + new_time += timedelta( + seconds=( + config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + 1 + ) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() @@ -758,7 +781,9 @@ async def test_option_ssid_filter( mock_unifi_websocket(message=MessageKey.CLIENT, data=client_on_ssid2) await hass.async_block_till_done() - new_time += hub.option_detection_time + new_time += timedelta( + seconds=(config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME)) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() @@ -788,7 +813,6 @@ async def test_wireless_client_go_wired_issue( config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=[client] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 @@ -807,7 +831,9 @@ async def test_wireless_client_go_wired_issue( assert client_state.state == STATE_HOME # Pass time - new_time = dt_util.utcnow() + hub.option_detection_time + new_time = dt_util.utcnow() + timedelta( + seconds=(config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME)) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() @@ -859,7 +885,6 @@ async def test_option_ignore_wired_bug( options={CONF_IGNORE_WIRED_BUG: True}, clients_response=[client], ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 # Client is wireless @@ -876,7 +901,9 @@ async def test_option_ignore_wired_bug( assert client_state.state == STATE_HOME # pass time - new_time = dt_util.utcnow() + hub.option_detection_time + new_time = dt_util.utcnow() + timedelta( + seconds=config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index 8adc379dde8..99cc12eb3f1 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -18,10 +18,11 @@ from homeassistant.components.sensor import ( from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, CONF_ALLOW_UPTIME_SENSORS, + CONF_DETECTION_TIME, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, + DEFAULT_DETECTION_TIME, DEVICE_STATES, - DOMAIN as UNIFI_DOMAIN, ) from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ATTR_DEVICE_CLASS, STATE_UNAVAILABLE, EntityCategory @@ -395,7 +396,6 @@ async def test_bandwidth_sensors( # Verify reset sensor after heartbeat expires - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] new_time = dt_util.utcnow() wireless_client["last_seen"] = dt_util.as_timestamp(new_time) @@ -409,8 +409,11 @@ async def test_bandwidth_sensors( assert hass.states.get("sensor.wireless_client_rx").state == "3456.0" assert hass.states.get("sensor.wireless_client_tx").state == "7891.0" - new_time = new_time + hub.option_detection_time + timedelta(seconds=1) - + new_time += timedelta( + seconds=( + config_entry.options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + 1 + ) + ) with freeze_time(new_time): async_fire_time_changed(hass, new_time) await hass.async_block_till_done() diff --git a/tests/components/unifi/test_services.py b/tests/components/unifi/test_services.py index 613c492a490..0de7d35608f 100644 --- a/tests/components/unifi/test_services.py +++ b/tests/components/unifi/test_services.py @@ -1,13 +1,13 @@ """deCONZ service tests.""" from unittest.mock import patch -from homeassistant.components.unifi.const import DOMAIN as UNIFI_DOMAIN +from homeassistant.components.unifi.const import CONF_SITE_ID, DOMAIN as UNIFI_DOMAIN from homeassistant.components.unifi.services import ( SERVICE_RECONNECT_CLIENT, SERVICE_REMOVE_CLIENTS, SUPPORTED_SERVICES, ) -from homeassistant.const import ATTR_DEVICE_ID +from homeassistant.const import ATTR_DEVICE_ID, CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr @@ -66,11 +66,11 @@ async def test_reconnect_client( config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_response=clients ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/stamgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/stamgr", ) device_entry = device_registry.async_get_or_create( @@ -148,7 +148,8 @@ async def test_reconnect_client_hub_unavailable( aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/stamgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/stamgr", ) device_entry = device_registry.async_get_or_create( @@ -261,11 +262,11 @@ async def test_remove_clients( config_entry = await setup_unifi_integration( hass, aioclient_mock, clients_all_response=clients ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/stamgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/stamgr", ) await hass.services.async_call(UNIFI_DOMAIN, SERVICE_REMOVE_CLIENTS, blocking=True) diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 35b2327bcdc..c4fb18018f1 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -15,6 +15,7 @@ from homeassistant.components.switch import ( from homeassistant.components.unifi.const import ( CONF_BLOCK_CLIENT, CONF_DPI_RESTRICTIONS, + CONF_SITE_ID, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, DOMAIN as UNIFI_DOMAIN, @@ -23,6 +24,7 @@ from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, + CONF_HOST, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, @@ -829,7 +831,6 @@ async def test_switches( dpigroup_response=DPI_GROUPS, dpiapp_response=DPI_APPS, ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3 @@ -857,7 +858,8 @@ async def test_switches( # Block and unblock client aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/stamgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/stamgr", ) await hass.services.async_call( @@ -881,7 +883,8 @@ async def test_switches( # Enable and disable DPI aioclient_mock.clear_requests() aioclient_mock.put( - f"https://{hub.host}:1234/api/s/{hub.site}/rest/dpiapp/5f976f62e3c58f018ec7e17d", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/rest/dpiapp/{DPI_APPS[0]['_id']}", ) await hass.services.async_call( @@ -951,7 +954,6 @@ async def test_block_switches( clients_response=[UNBLOCKED], clients_all_response=[BLOCKED], ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 @@ -981,7 +983,8 @@ async def test_block_switches( aioclient_mock.clear_requests() aioclient_mock.post( - f"https://{hub.host}:1234/api/s/{hub.site}/cmd/stamgr", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/stamgr", ) await hass.services.async_call( @@ -1142,7 +1145,6 @@ async def test_outlet_switches( config_entry = await setup_unifi_integration( hass, aioclient_mock, devices_response=[test_data] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == expected_switches # Validate state object switch_1 = hass.states.get(f"switch.{entity_id}") @@ -1161,7 +1163,8 @@ async def test_outlet_switches( device_id = test_data["device_id"] aioclient_mock.clear_requests() aioclient_mock.put( - f"https://{hub.host}:1234/api/s/{hub.site}/rest/device/{device_id}", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/rest/device/{device_id}", ) await hass.services.async_call( @@ -1333,7 +1336,6 @@ async def test_poe_port_switches( config_entry = await setup_unifi_integration( hass, aioclient_mock, devices_response=[DEVICE_1] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 @@ -1372,7 +1374,8 @@ async def test_poe_port_switches( # Turn off PoE aioclient_mock.clear_requests() aioclient_mock.put( - f"https://{hub.host}:1234/api/s/{hub.site}/rest/device/mock-id", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/rest/device/mock-id", ) await hass.services.async_call( @@ -1445,7 +1448,6 @@ async def test_wlan_switches( config_entry = await setup_unifi_integration( hass, aioclient_mock, wlans_response=[WLAN] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 @@ -1469,7 +1471,8 @@ async def test_wlan_switches( # Disable WLAN aioclient_mock.clear_requests() aioclient_mock.put( - f"https://{hub.host}:1234/api/s/{hub.site}" + f"/rest/wlanconf/{WLAN['_id']}", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/rest/wlanconf/{WLAN['_id']}", ) await hass.services.async_call( @@ -1525,7 +1528,6 @@ async def test_port_forwarding_switches( config_entry = await setup_unifi_integration( hass, aioclient_mock, port_forward_response=[_data.copy()] ) - hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 @@ -1549,8 +1551,8 @@ async def test_port_forwarding_switches( # Disable port forward aioclient_mock.clear_requests() aioclient_mock.put( - f"https://{hub.host}:1234/api/s/{hub.site}" - + f"/rest/portforward/{data['_id']}", + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/rest/portforward/{data['_id']}", ) await hass.services.async_call( diff --git a/tests/components/unifi/test_update.py b/tests/components/unifi/test_update.py index a9440e20339..2c6852629ea 100644 --- a/tests/components/unifi/test_update.py +++ b/tests/components/unifi/test_update.py @@ -162,7 +162,10 @@ async def test_install( device_state = hass.states.get("update.device_1") assert device_state.state == STATE_ON - url = f"https://{config_entry.data[CONF_HOST]}:1234/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/devmgr" + url = ( + f"https://{config_entry.data[CONF_HOST]}:1234" + f"/api/s/{config_entry.data[CONF_SITE_ID]}/cmd/devmgr" + ) aioclient_mock.clear_requests() aioclient_mock.post(url) From 4cb8df0951a25861f2ae1905cf9ce0d9574d0fb3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:42:25 +0100 Subject: [PATCH 0286/1691] Add icon translations to SMS (#112228) --- homeassistant/components/sms/icons.json | 12 ++++++++++++ homeassistant/components/sms/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/sms/icons.json diff --git a/homeassistant/components/sms/icons.json b/homeassistant/components/sms/icons.json new file mode 100644 index 00000000000..f863d7a35a4 --- /dev/null +++ b/homeassistant/components/sms/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "signal_percent": { + "default": "mdi:signal-cellular-3" + }, + "cid": { + "default": "mdi:radio-tower" + } + } + } +} diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index d4c45b83d82..2f5ce7b7da3 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -25,7 +25,6 @@ SIGNAL_SENSORS = ( ), SensorEntityDescription( key="SignalPercent", - icon="mdi:signal-cellular-3", translation_key="signal_percent", native_unit_of_measurement=PERCENTAGE, entity_registry_enabled_default=True, @@ -62,7 +61,6 @@ NETWORK_SENSORS = ( SensorEntityDescription( key="CID", translation_key="cid", - icon="mdi:radio-tower", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), From cb3c7f6b0a1975b8b5dfd61fbbcaf553b5465f5d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:42:45 +0100 Subject: [PATCH 0287/1691] Add icon translations to Rainforest Raven (#112201) --- homeassistant/components/rainforest_raven/icons.json | 12 ++++++++++++ homeassistant/components/rainforest_raven/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/rainforest_raven/icons.json diff --git a/homeassistant/components/rainforest_raven/icons.json b/homeassistant/components/rainforest_raven/icons.json new file mode 100644 index 00000000000..964ecf92c74 --- /dev/null +++ b/homeassistant/components/rainforest_raven/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "signal_strength": { + "default": "mdi:wifi" + }, + "meter_price": { + "default": "mdi:cash" + } + } + } +} diff --git a/homeassistant/components/rainforest_raven/sensor.py b/homeassistant/components/rainforest_raven/sensor.py index d1f1aebb0f3..fd7df25c603 100644 --- a/homeassistant/components/rainforest_raven/sensor.py +++ b/homeassistant/components/rainforest_raven/sensor.py @@ -70,7 +70,6 @@ DIAGNOSTICS = ( key="link_strength", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:wifi", entity_category=EntityCategory.DIAGNOSTIC, attribute_keys=[ "channel", @@ -104,7 +103,6 @@ async def async_setup_entry( translation_key="meter_price", key="price", native_unit_of_measurement=f"{meter_data['PriceCluster']['currency'].value}/{UnitOfEnergy.KILO_WATT_HOUR}", - icon="mdi:cash", state_class=SensorStateClass.MEASUREMENT, attribute_keys=[ "tier", From fb5e5221ae5686d857ca2109a116489018afb362 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:42:59 +0100 Subject: [PATCH 0288/1691] Add icon translations to Plum lightpad (#112183) --- homeassistant/components/plum_lightpad/icons.json | 9 +++++++++ homeassistant/components/plum_lightpad/light.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/plum_lightpad/icons.json diff --git a/homeassistant/components/plum_lightpad/icons.json b/homeassistant/components/plum_lightpad/icons.json new file mode 100644 index 00000000000..dd65160e474 --- /dev/null +++ b/homeassistant/components/plum_lightpad/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "light": { + "glow_ring": { + "default": "mdi:crop-portrait" + } + } + } +} diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index 9464e66e3a9..6d591d29fe4 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -130,8 +130,8 @@ class GlowRing(LightEntity): _attr_color_mode = ColorMode.HS _attr_should_poll = False + _attr_translation_key = "glow_ring" _attr_supported_color_modes = {ColorMode.HS} - _attr_icon = "mdi:crop-portrait" def __init__(self, lightpad): """Initialize the light.""" From ab06f545442ca23c72b55555de377eaa12107738 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:43:10 +0100 Subject: [PATCH 0289/1691] Add icon translations to Profiler (#112186) --- homeassistant/components/profiler/icons.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 homeassistant/components/profiler/icons.json diff --git a/homeassistant/components/profiler/icons.json b/homeassistant/components/profiler/icons.json new file mode 100644 index 00000000000..d6c7ee21257 --- /dev/null +++ b/homeassistant/components/profiler/icons.json @@ -0,0 +1,14 @@ +{ + "services": { + "start": "mdi:play", + "memory": "mdi:memory", + "start_log_objects": "mdi:invoice-text-plus", + "stop_log_objects": "mdi:invoice-text-remove", + "dump_log_objects": "mdi:invoice-export-outline", + "start_log_object_sources": "mdi:play", + "stop_log_object_sources": "mdi:stop", + "lru_stats": "mdi:chart-areaspline", + "log_thread_frames": "mdi:format-list-bulleted", + "log_event_loop_scheduled": "mdi:calendar-clock" + } +} From 85aadb2a20b04bf272abf86b4b588797fafabaad Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:43:23 +0100 Subject: [PATCH 0290/1691] Add icon translations to Prosegur (#112187) --- homeassistant/components/prosegur/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/prosegur/icons.json diff --git a/homeassistant/components/prosegur/icons.json b/homeassistant/components/prosegur/icons.json new file mode 100644 index 00000000000..33cddefdaea --- /dev/null +++ b/homeassistant/components/prosegur/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "request_image": "mdi:image-sync" + } +} From 2585b240d33ed244e72717a539d7d50f9e63470d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:43:37 +0100 Subject: [PATCH 0291/1691] Add icon translations to Prusalink (#112188) --- homeassistant/components/prusalink/button.py | 3 -- homeassistant/components/prusalink/icons.json | 35 +++++++++++++++++++ homeassistant/components/prusalink/sensor.py | 6 ---- 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/prusalink/icons.json diff --git a/homeassistant/components/prusalink/button.py b/homeassistant/components/prusalink/button.py index 8f8a62794a9..46d10fc9703 100644 --- a/homeassistant/components/prusalink/button.py +++ b/homeassistant/components/prusalink/button.py @@ -40,7 +40,6 @@ BUTTONS: dict[str, tuple[PrusaLinkButtonEntityDescription, ...]] = { PrusaLinkButtonEntityDescription[PrinterStatus]( key="printer.cancel_job", translation_key="cancel_job", - icon="mdi:cancel", press_fn=lambda api: api.cancel_job, available_fn=lambda data: ( data["printer"]["state"] @@ -50,7 +49,6 @@ BUTTONS: dict[str, tuple[PrusaLinkButtonEntityDescription, ...]] = { PrusaLinkButtonEntityDescription[PrinterStatus]( key="job.pause_job", translation_key="pause_job", - icon="mdi:pause", press_fn=lambda api: api.pause_job, available_fn=lambda data: cast( bool, data["printer"]["state"] == PrinterState.PRINTING.value @@ -59,7 +57,6 @@ BUTTONS: dict[str, tuple[PrusaLinkButtonEntityDescription, ...]] = { PrusaLinkButtonEntityDescription[PrinterStatus]( key="job.resume_job", translation_key="resume_job", - icon="mdi:play", press_fn=lambda api: api.resume_job, available_fn=lambda data: cast( bool, data["printer"]["state"] == PrinterState.PAUSED.value diff --git a/homeassistant/components/prusalink/icons.json b/homeassistant/components/prusalink/icons.json new file mode 100644 index 00000000000..4d97ea76ddd --- /dev/null +++ b/homeassistant/components/prusalink/icons.json @@ -0,0 +1,35 @@ +{ + "entity": { + "button": { + "cancel_job": { + "default": "mdi:cancel" + }, + "pause_job": { + "default": "mdi:pause" + }, + "resume_job": { + "default": "mdi:play" + } + }, + "sensor": { + "printer_state": { + "default": "mdi:printer-3d" + }, + "material": { + "default": "mdi:palette-swatch-variant" + }, + "progress": { + "default": "mdi:progress-clock" + }, + "filename": { + "default": "mdi:file-image-outline" + }, + "print_start": { + "default": "mdi:clock-start" + }, + "print_finish": { + "default": "mdi:clock-end" + } + } + } +} diff --git a/homeassistant/components/prusalink/sensor.py b/homeassistant/components/prusalink/sensor.py index 29e1d5c9757..efdd044399a 100644 --- a/homeassistant/components/prusalink/sensor.py +++ b/homeassistant/components/prusalink/sensor.py @@ -54,7 +54,6 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { PrusaLinkSensorEntityDescription[PrinterStatus]( key="printer.state", name=None, - icon="mdi:printer-3d", value_fn=lambda data: (cast(str, data["printer"]["state"].lower())), device_class=SensorDeviceClass.ENUM, options=[state.value.lower() for state in PrinterState], @@ -137,7 +136,6 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { PrusaLinkSensorEntityDescription[LegacyPrinterStatus]( key="printer.telemetry.material", translation_key="material", - icon="mdi:palette-swatch-variant", value_fn=lambda data: cast(str, data["telemetry"]["material"]), ), ), @@ -145,7 +143,6 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { PrusaLinkSensorEntityDescription[JobInfo]( key="job.progress", translation_key="progress", - icon="mdi:progress-clock", native_unit_of_measurement=PERCENTAGE, value_fn=lambda data: cast(float, data["progress"]), available_fn=lambda data: data.get("progress") is not None, @@ -153,7 +150,6 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { PrusaLinkSensorEntityDescription[JobInfo]( key="job.filename", translation_key="filename", - icon="mdi:file-image-outline", value_fn=lambda data: cast(str, data["file"]["display_name"]), available_fn=lambda data: data.get("file") is not None, ), @@ -161,7 +157,6 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { key="job.start", translation_key="print_start", device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-start", value_fn=ignore_variance( lambda data: (utcnow() - timedelta(seconds=data["time_printing"])), timedelta(minutes=2), @@ -171,7 +166,6 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { PrusaLinkSensorEntityDescription[JobInfo]( key="job.finish", translation_key="print_finish", - icon="mdi:clock-end", device_class=SensorDeviceClass.TIMESTAMP, value_fn=ignore_variance( lambda data: (utcnow() + timedelta(seconds=data["time_remaining"])), From 30a8c1739f24aab837e2efbadb6b8c106b20b08c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:43:50 +0100 Subject: [PATCH 0292/1691] Add icon translations to PS4 (#112190) --- homeassistant/components/ps4/icons.json | 12 ++++++++++++ homeassistant/components/ps4/media_player.py | 4 +--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/ps4/icons.json diff --git a/homeassistant/components/ps4/icons.json b/homeassistant/components/ps4/icons.json new file mode 100644 index 00000000000..165099070a9 --- /dev/null +++ b/homeassistant/components/ps4/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "media_player": { + "media_player": { + "default": "mdi:sony-playstation" + } + } + }, + "services": { + "send_command": "mdi:terminal" + } +} diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 42a1021afe4..732d86bef37 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -41,8 +41,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -ICON = "mdi:sony-playstation" - DEFAULT_RETRIES = 2 @@ -67,7 +65,6 @@ async def async_setup_entry( class PS4Device(MediaPlayerEntity): """Representation of a PS4.""" - _attr_icon = ICON _attr_supported_features = ( MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.TURN_ON @@ -75,6 +72,7 @@ class PS4Device(MediaPlayerEntity): | MediaPlayerEntityFeature.STOP | MediaPlayerEntityFeature.SELECT_SOURCE ) + _attr_translation_key = "media_player" def __init__( self, From 223be9c788a6264f11bac45cb5a3ef87e4416353 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:44:04 +0100 Subject: [PATCH 0293/1691] Add icon translations to QBittorrent (#112193) --- homeassistant/components/qbittorrent/icons.json | 12 ++++++++++++ homeassistant/components/qbittorrent/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/qbittorrent/icons.json diff --git a/homeassistant/components/qbittorrent/icons.json b/homeassistant/components/qbittorrent/icons.json new file mode 100644 index 00000000000..bb458c751e1 --- /dev/null +++ b/homeassistant/components/qbittorrent/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "download_speed": { + "default": "mdi:cloud-download" + }, + "upload_speed": { + "default": "mdi:cloud-upload" + } + } + } +} diff --git a/homeassistant/components/qbittorrent/sensor.py b/homeassistant/components/qbittorrent/sensor.py index 78e8ba59d44..9e9f7626857 100644 --- a/homeassistant/components/qbittorrent/sensor.py +++ b/homeassistant/components/qbittorrent/sensor.py @@ -64,7 +64,6 @@ SENSOR_TYPES: tuple[QBittorrentSensorEntityDescription, ...] = ( QBittorrentSensorEntityDescription( key=SENSOR_TYPE_DOWNLOAD_SPEED, translation_key="download_speed", - icon="mdi:cloud-download", device_class=SensorDeviceClass.DATA_RATE, native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND, suggested_display_precision=2, @@ -76,7 +75,6 @@ SENSOR_TYPES: tuple[QBittorrentSensorEntityDescription, ...] = ( QBittorrentSensorEntityDescription( key=SENSOR_TYPE_UPLOAD_SPEED, translation_key="upload_speed", - icon="mdi:cloud-upload", device_class=SensorDeviceClass.DATA_RATE, native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND, suggested_display_precision=2, From 982dab3849eafb5015b8a64610f18bef3905ee90 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:44:17 +0100 Subject: [PATCH 0294/1691] Add icon translations to QNap QSW (#112194) --- homeassistant/components/qnap_qsw/icons.json | 33 ++++++++++++++++++++ homeassistant/components/qnap_qsw/sensor.py | 9 ------ 2 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/qnap_qsw/icons.json diff --git a/homeassistant/components/qnap_qsw/icons.json b/homeassistant/components/qnap_qsw/icons.json new file mode 100644 index 00000000000..4cdae8e6718 --- /dev/null +++ b/homeassistant/components/qnap_qsw/icons.json @@ -0,0 +1,33 @@ +{ + "entity": { + "sensor": { + "fan_1_speed": { + "default": "mdi:fan-speed-1" + }, + "fan_2_speed": { + "default": "mdi:fan-speed-2" + }, + "ports": { + "default": "mdi:ethernet" + }, + "rx": { + "default": "mdi:download-network" + }, + "rx_errors": { + "default": "mdi:close-network" + }, + "rx_speed": { + "default": "mdi:download-network" + }, + "tx": { + "default": "mdi:upload-network" + }, + "tx_speed": { + "default": "mdi:upload-network" + }, + "uptime": { + "default": "mdi:timer-outline" + } + } + } +} diff --git a/homeassistant/components/qnap_qsw/sensor.py b/homeassistant/components/qnap_qsw/sensor.py index 3168e4511d2..0fcb74174b5 100644 --- a/homeassistant/components/qnap_qsw/sensor.py +++ b/homeassistant/components/qnap_qsw/sensor.py @@ -62,7 +62,6 @@ class QswSensorEntityDescription(SensorEntityDescription, QswEntityDescription): SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( QswSensorEntityDescription( translation_key="fan_1_speed", - icon="mdi:fan-speed-1", key=QSD_SYSTEM_SENSOR, native_unit_of_measurement=RPM, state_class=SensorStateClass.MEASUREMENT, @@ -70,7 +69,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( ), QswSensorEntityDescription( translation_key="fan_2_speed", - icon="mdi:fan-speed-2", key=QSD_SYSTEM_SENSOR, native_unit_of_measurement=RPM, state_class=SensorStateClass.MEASUREMENT, @@ -82,7 +80,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( ATTR_MAX: [QSD_SYSTEM_BOARD, QSD_PORT_NUM], }, entity_registry_enabled_default=False, - icon="mdi:ethernet", key=QSD_PORTS_STATUS, state_class=SensorStateClass.MEASUREMENT, subkey=QSD_LINK, @@ -91,7 +88,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( entity_registry_enabled_default=False, translation_key="rx", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:download-network", key=QSD_PORTS_STATISTICS, native_unit_of_measurement=UnitOfInformation.BYTES, state_class=SensorStateClass.TOTAL_INCREASING, @@ -100,7 +96,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( QswSensorEntityDescription( entity_registry_enabled_default=False, translation_key="rx_errors", - icon="mdi:close-network", key=QSD_PORTS_STATISTICS, entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL_INCREASING, @@ -110,7 +105,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( entity_registry_enabled_default=False, translation_key="rx_speed", device_class=SensorDeviceClass.DATA_RATE, - icon="mdi:download-network", key=QSD_PORTS_STATISTICS, native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -130,7 +124,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( entity_registry_enabled_default=False, translation_key="tx", device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:upload-network", key=QSD_PORTS_STATISTICS, native_unit_of_measurement=UnitOfInformation.BYTES, state_class=SensorStateClass.TOTAL_INCREASING, @@ -140,7 +133,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( entity_registry_enabled_default=False, translation_key="tx_speed", device_class=SensorDeviceClass.DATA_RATE, - icon="mdi:upload-network", key=QSD_PORTS_STATISTICS, native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -148,7 +140,6 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( ), QswSensorEntityDescription( translation_key="uptime", - icon="mdi:timer-outline", key=QSD_SYSTEM_TIME, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfTime.SECONDS, From 2ae90d5eed2407bb60f13daeee872a53372e2dc4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:44:30 +0100 Subject: [PATCH 0295/1691] Add icon translations to Rachio (#112195) --- homeassistant/components/rachio/icons.json | 19 +++++++++++++++++++ homeassistant/components/rachio/switch.py | 2 -- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/rachio/icons.json diff --git a/homeassistant/components/rachio/icons.json b/homeassistant/components/rachio/icons.json new file mode 100644 index 00000000000..3b3ec860514 --- /dev/null +++ b/homeassistant/components/rachio/icons.json @@ -0,0 +1,19 @@ +{ + "entity": { + "switch": { + "standby": { + "default": "mdi:power" + }, + "rain_delay": { + "default": "mdi:camera-timer" + } + } + }, + "services": { + "set_zone_moisture_percent": "mdi:water-percent", + "start_multiple_zone_schedule": "mdi:play", + "pause_watering": "mdi:pause", + "resume_watering": "mdi:play", + "stop_watering": "mdi:stop" + } +} diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index bbb08f6d46f..c69773009be 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -198,7 +198,6 @@ class RachioStandbySwitch(RachioSwitch): _attr_has_entity_name = True _attr_translation_key = "standby" - _attr_icon = "mdi:power" @property def unique_id(self) -> str: @@ -242,7 +241,6 @@ class RachioRainDelay(RachioSwitch): _attr_has_entity_name = True _attr_translation_key = "rain_delay" - _attr_icon = "mdi:camera-timer" def __init__(self, controller): """Set up a Rachio rain delay switch.""" From 053649faa55af92265252bbea69d9933adea30ae Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:44:42 +0100 Subject: [PATCH 0296/1691] Add icon translations to Radarr (#112196) --- homeassistant/components/radarr/icons.json | 12 ++++++++++++ homeassistant/components/radarr/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/radarr/icons.json diff --git a/homeassistant/components/radarr/icons.json b/homeassistant/components/radarr/icons.json new file mode 100644 index 00000000000..ff31d936ae5 --- /dev/null +++ b/homeassistant/components/radarr/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "movies": { + "default": "mdi:television" + }, + "queue": { + "default": "mdi:download" + } + } + } +} diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index ad9dd4e1ae0..feb4c7964ee 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -80,7 +80,6 @@ SENSOR_TYPES: dict[str, RadarrSensorEntityDescription[Any]] = { key="movies", translation_key="movies", native_unit_of_measurement="Movies", - icon="mdi:television", entity_registry_enabled_default=False, value_fn=lambda data, _: data, ), @@ -88,7 +87,6 @@ SENSOR_TYPES: dict[str, RadarrSensorEntityDescription[Any]] = { key="queue", translation_key="queue", native_unit_of_measurement="Movies", - icon="mdi:download", entity_registry_enabled_default=False, state_class=SensorStateClass.TOTAL, value_fn=lambda data, _: data, From 161d31d789645979a844b55eed5d4d5b1302b78b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:44:57 +0100 Subject: [PATCH 0297/1691] Add icon translations to Radiotherm (#112197) --- homeassistant/components/radiotherm/icons.json | 12 ++++++++++++ homeassistant/components/radiotherm/switch.py | 5 ----- 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/radiotherm/icons.json diff --git a/homeassistant/components/radiotherm/icons.json b/homeassistant/components/radiotherm/icons.json new file mode 100644 index 00000000000..be955c5dcf0 --- /dev/null +++ b/homeassistant/components/radiotherm/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "switch": { + "hold": { + "default": "mdi:timer-off", + "state": { + "on": "mdi:timer" + } + } + } + } +} diff --git a/homeassistant/components/radiotherm/switch.py b/homeassistant/components/radiotherm/switch.py index 3b71baffec6..1f047a76201 100644 --- a/homeassistant/components/radiotherm/switch.py +++ b/homeassistant/components/radiotherm/switch.py @@ -35,11 +35,6 @@ class RadioThermHoldSwitch(RadioThermostatEntity, SwitchEntity): super().__init__(coordinator) self._attr_unique_id = f"{coordinator.init_data.mac}_hold" - @property - def icon(self) -> str: - """Return the icon for the switch.""" - return "mdi:timer-off" if self.is_on else "mdi:timer" - @callback def _process_data(self) -> None: """Update and validate the data from the thermostat.""" From 926159ab9ab699ab29ee559cea2052227c32fbee Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:45:10 +0100 Subject: [PATCH 0298/1691] Add icon translations to RDW (#112203) * Add icon translations to RDW * Add icon translations to RDW --- homeassistant/components/rdw/binary_sensor.py | 1 - homeassistant/components/rdw/icons.json | 9 +++++++++ tests/components/rdw/test_binary_sensor.py | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/rdw/icons.json diff --git a/homeassistant/components/rdw/binary_sensor.py b/homeassistant/components/rdw/binary_sensor.py index ce8e2908251..c971c3e68b9 100644 --- a/homeassistant/components/rdw/binary_sensor.py +++ b/homeassistant/components/rdw/binary_sensor.py @@ -34,7 +34,6 @@ BINARY_SENSORS: tuple[RDWBinarySensorEntityDescription, ...] = ( RDWBinarySensorEntityDescription( key="liability_insured", translation_key="liability_insured", - icon="mdi:shield-car", is_on_fn=lambda vehicle: vehicle.liability_insured, ), RDWBinarySensorEntityDescription( diff --git a/homeassistant/components/rdw/icons.json b/homeassistant/components/rdw/icons.json new file mode 100644 index 00000000000..5e1dffcad9c --- /dev/null +++ b/homeassistant/components/rdw/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "binary_sensor": { + "liability_insured": { + "default": "mdi:shield-car" + } + } + } +} diff --git a/tests/components/rdw/test_binary_sensor.py b/tests/components/rdw/test_binary_sensor.py index aea188db773..a81c723a7d7 100644 --- a/tests/components/rdw/test_binary_sensor.py +++ b/tests/components/rdw/test_binary_sensor.py @@ -23,7 +23,6 @@ async def test_vehicle_binary_sensors( assert entry.unique_id == "11ZKZ3_liability_insured" assert state.state == "off" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Skoda 11ZKZ3 Liability insured" - assert state.attributes.get(ATTR_ICON) == "mdi:shield-car" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.skoda_11zkz3_pending_recall") From 85aedbc200619017d41f39029b089b768287539b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:45:22 +0100 Subject: [PATCH 0299/1691] Add icon translations to Renson (#112207) --- homeassistant/components/renson/fan.py | 2 +- homeassistant/components/renson/icons.json | 24 ++++++++++++++++++++++ homeassistant/components/renson/number.py | 1 - homeassistant/components/renson/switch.py | 1 - 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/renson/icons.json diff --git a/homeassistant/components/renson/fan.py b/homeassistant/components/renson/fan.py index e6bd2717981..d15e14aa628 100644 --- a/homeassistant/components/renson/fan.py +++ b/homeassistant/components/renson/fan.py @@ -117,9 +117,9 @@ async def async_setup_entry( class RensonFan(RensonEntity, FanEntity): """Representation of the Renson fan platform.""" - _attr_icon = "mdi:air-conditioner" _attr_has_entity_name = True _attr_name = None + _attr_translation_key = "fan" _attr_supported_features = FanEntityFeature.SET_SPEED def __init__(self, api: RensonVentilation, coordinator: RensonCoordinator) -> None: diff --git a/homeassistant/components/renson/icons.json b/homeassistant/components/renson/icons.json new file mode 100644 index 00000000000..b7b1fdfdd8c --- /dev/null +++ b/homeassistant/components/renson/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "fan": { + "fan": { + "default": "mdi:air-conditioner" + } + }, + "number": { + "filter_change": { + "default": "mdi:filter" + } + }, + "switch": { + "breeze": { + "default": "mdi:weather-dust" + } + } + }, + "services": { + "set_timer_level": "mdi:timer", + "set_breeze": "mdi:weather-windy", + "set_pollution_settings": "mdi:air-filter" + } +} diff --git a/homeassistant/components/renson/number.py b/homeassistant/components/renson/number.py index 344fa3ff0bd..972368298a6 100644 --- a/homeassistant/components/renson/number.py +++ b/homeassistant/components/renson/number.py @@ -26,7 +26,6 @@ _LOGGER = logging.getLogger(__name__) RENSON_NUMBER_DESCRIPTION = NumberEntityDescription( key="filter_change", translation_key="filter_change", - icon="mdi:filter", native_step=1, native_min_value=0, native_max_value=360, diff --git a/homeassistant/components/renson/switch.py b/homeassistant/components/renson/switch.py index a724dcc5530..8d639a64d48 100644 --- a/homeassistant/components/renson/switch.py +++ b/homeassistant/components/renson/switch.py @@ -22,7 +22,6 @@ _LOGGER = logging.getLogger(__name__) class RensonBreezeSwitch(RensonEntity, SwitchEntity): """Provide the breeze switch.""" - _attr_icon = "mdi:weather-dust" _attr_device_class = SwitchDeviceClass.SWITCH _attr_has_entity_name = True _attr_translation_key = "breeze" From a3a758bd175ff6f9b4ef19a5be18ac8cfa2adc32 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:45:33 +0100 Subject: [PATCH 0300/1691] Add icon translations to Ring (#112212) * Add icon translations to Ring * Add icon translations to Ring --- homeassistant/components/ring/icons.json | 32 ++++++++++++++++++++++++ homeassistant/components/ring/sensor.py | 6 ----- homeassistant/components/ring/switch.py | 3 --- tests/components/ring/test_switch.py | 1 - 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/ring/icons.json diff --git a/homeassistant/components/ring/icons.json b/homeassistant/components/ring/icons.json new file mode 100644 index 00000000000..07b42db1516 --- /dev/null +++ b/homeassistant/components/ring/icons.json @@ -0,0 +1,32 @@ +{ + "entity": { + "sensor": { + "last_activity": { + "default": "mdi:history" + }, + "last_ding": { + "default": "mdi:history" + }, + "last_motion": { + "default": "mdi:history" + }, + "volume": { + "default": "mdi:bell-ring" + }, + "wifi_signal_category": { + "default": "mdi:wifi" + }, + "wifi_signal_strength": { + "default": "mdi:wifi" + } + }, + "switch": { + "siren": { + "default": "mdi:alarm-bell" + } + } + }, + "services": { + "update": "mdi:refresh" + } +} diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 32382a2f929..ab77e0461bb 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -171,7 +171,6 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( key="last_activity", translation_key="last_activity", category=["doorbots", "authorized_doorbots", "stickup_cams"], - icon="mdi:history", device_class=SensorDeviceClass.TIMESTAMP, cls=HistoryRingSensor, ), @@ -179,7 +178,6 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( key="last_ding", translation_key="last_ding", category=["doorbots", "authorized_doorbots"], - icon="mdi:history", kind="ding", device_class=SensorDeviceClass.TIMESTAMP, cls=HistoryRingSensor, @@ -188,7 +186,6 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( key="last_motion", translation_key="last_motion", category=["doorbots", "authorized_doorbots", "stickup_cams"], - icon="mdi:history", kind="motion", device_class=SensorDeviceClass.TIMESTAMP, cls=HistoryRingSensor, @@ -197,14 +194,12 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( key="volume", translation_key="volume", category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], - icon="mdi:bell-ring", cls=RingSensor, ), RingSensorEntityDescription( key="wifi_signal_category", translation_key="wifi_signal_category", category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], - icon="mdi:wifi", entity_category=EntityCategory.DIAGNOSTIC, cls=HealthDataRingSensor, ), @@ -213,7 +208,6 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( translation_key="wifi_signal_strength", category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, - icon="mdi:wifi", device_class=SensorDeviceClass.SIGNAL_STRENGTH, entity_category=EntityCategory.DIAGNOSTIC, cls=HealthDataRingSensor, diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index 1f06f06e32e..b0a1e236df5 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -19,8 +19,6 @@ from .entity import RingEntity _LOGGER = logging.getLogger(__name__) -SIREN_ICON = "mdi:alarm-bell" - # It takes a few seconds for the API to correctly return an update indicating # that the changes have been made. Once we request a change (i.e. a light @@ -65,7 +63,6 @@ class SirenSwitch(BaseRingSwitch): """Creates a switch to turn the ring cameras siren on and off.""" _attr_translation_key = "siren" - _attr_icon = SIREN_ICON def __init__(self, device: RingGeneric, coordinator: RingDataCoordinator) -> None: """Initialize the switch for a device with a siren.""" diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index b856a2f850c..fa3da8b683e 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -45,7 +45,6 @@ async def test_siren_on_reports_correctly( state = hass.states.get("switch.internal_siren") assert state.state == "on" assert state.attributes.get("friendly_name") == "Internal Siren" - assert state.attributes.get("icon") == "mdi:alarm-bell" async def test_siren_can_be_turned_on( From b8be90efa895cc33ee86b16fa14589d150afa1b5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:45:47 +0100 Subject: [PATCH 0301/1691] Add icon translations to Rituals perfume genie (#112213) * Add icon translations to Rituals perfume genie * Add icon translations to Rituals perfume genie --- .../rituals_perfume_genie/icons.json | 30 +++++++++++++++++++ .../rituals_perfume_genie/number.py | 1 - .../rituals_perfume_genie/select.py | 1 - .../rituals_perfume_genie/sensor.py | 3 -- .../rituals_perfume_genie/switch.py | 2 +- .../rituals_perfume_genie/test_number.py | 3 +- .../rituals_perfume_genie/test_select.py | 2 -- .../rituals_perfume_genie/test_sensor.py | 3 -- .../rituals_perfume_genie/test_switch.py | 2 -- 9 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/rituals_perfume_genie/icons.json diff --git a/homeassistant/components/rituals_perfume_genie/icons.json b/homeassistant/components/rituals_perfume_genie/icons.json new file mode 100644 index 00000000000..0d66e206356 --- /dev/null +++ b/homeassistant/components/rituals_perfume_genie/icons.json @@ -0,0 +1,30 @@ +{ + "entity": { + "number": { + "perfume_amount": { + "default": "mdi:gauge" + } + }, + "select": { + "room_size_square_meter": { + "default": "mdi:ruler-square" + } + }, + "sensor": { + "fill": { + "default": "mdi:beaker" + }, + "perfume": { + "default": "mdi:tag" + }, + "wifi_percentage": { + "default": "mdi:wifi" + } + }, + "switch": { + "fan": { + "default": "mdi:fan" + } + } + } +} diff --git a/homeassistant/components/rituals_perfume_genie/number.py b/homeassistant/components/rituals_perfume_genie/number.py index 164b6de52c9..b21245645cf 100644 --- a/homeassistant/components/rituals_perfume_genie/number.py +++ b/homeassistant/components/rituals_perfume_genie/number.py @@ -29,7 +29,6 @@ ENTITY_DESCRIPTIONS = ( RitualsNumberEntityDescription( key="perfume_amount", translation_key="perfume_amount", - icon="mdi:gauge", native_min_value=1, native_max_value=3, value_fn=lambda diffuser: diffuser.perfume_amount, diff --git a/homeassistant/components/rituals_perfume_genie/select.py b/homeassistant/components/rituals_perfume_genie/select.py index b9f0c29b267..0f4deba6e42 100644 --- a/homeassistant/components/rituals_perfume_genie/select.py +++ b/homeassistant/components/rituals_perfume_genie/select.py @@ -29,7 +29,6 @@ ENTITY_DESCRIPTIONS = ( RitualsSelectEntityDescription( key="room_size_square_meter", translation_key="room_size_square_meter", - icon="mdi:ruler-square", unit_of_measurement=AREA_SQUARE_METERS, entity_category=EntityCategory.CONFIG, options=["15", "30", "60", "100"], diff --git a/homeassistant/components/rituals_perfume_genie/sensor.py b/homeassistant/components/rituals_perfume_genie/sensor.py index cd139c94f1c..4de3bb9235c 100644 --- a/homeassistant/components/rituals_perfume_genie/sensor.py +++ b/homeassistant/components/rituals_perfume_genie/sensor.py @@ -40,19 +40,16 @@ ENTITY_DESCRIPTIONS = ( RitualsSensorEntityDescription( key="fill", translation_key="fill", - icon="mdi:beaker", value_fn=lambda diffuser: diffuser.fill, ), RitualsSensorEntityDescription( key="perfume", translation_key="perfume", - icon="mdi:tag", value_fn=lambda diffuser: diffuser.perfume, ), RitualsSensorEntityDescription( key="wifi_percentage", translation_key="wifi_percentage", - icon="mdi:wifi", native_unit_of_measurement=PERCENTAGE, value_fn=lambda diffuser: diffuser.wifi_percentage, ), diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py index 9c9a5f73d16..8f93f4fe14d 100644 --- a/homeassistant/components/rituals_perfume_genie/switch.py +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -37,7 +37,7 @@ ENTITY_DESCRIPTIONS = ( RitualsSwitchEntityDescription( key="is_on", name=None, - icon="mdi:fan", + translation_key="fan", is_on_fn=lambda diffuser: diffuser.is_on, turn_on_fn=lambda diffuser: diffuser.turn_on(), turn_off_fn=lambda diffuser: diffuser.turn_off(), diff --git a/tests/components/rituals_perfume_genie/test_number.py b/tests/components/rituals_perfume_genie/test_number.py index 87d81aa8ec0..8e87ec74b77 100644 --- a/tests/components/rituals_perfume_genie/test_number.py +++ b/tests/components/rituals_perfume_genie/test_number.py @@ -11,7 +11,7 @@ from homeassistant.components.number import ( DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -35,7 +35,6 @@ async def test_number_entity( state = hass.states.get("number.genie_perfume_amount") assert state assert state.state == str(diffuser.perfume_amount) - assert state.attributes[ATTR_ICON] == "mdi:gauge" assert state.attributes[ATTR_MIN] == 1 assert state.attributes[ATTR_MAX] == 3 diff --git a/tests/components/rituals_perfume_genie/test_select.py b/tests/components/rituals_perfume_genie/test_select.py index a055e8fed05..be602552d76 100644 --- a/tests/components/rituals_perfume_genie/test_select.py +++ b/tests/components/rituals_perfume_genie/test_select.py @@ -10,7 +10,6 @@ from homeassistant.components.select import ( from homeassistant.const import ( AREA_SQUARE_METERS, ATTR_ENTITY_ID, - ATTR_ICON, SERVICE_SELECT_OPTION, EntityCategory, ) @@ -33,7 +32,6 @@ async def test_select_entity( state = hass.states.get("select.genie_room_size") assert state assert state.state == str(diffuser.room_size_square_meter) - assert state.attributes[ATTR_ICON] == "mdi:ruler-square" assert state.attributes[ATTR_OPTIONS] == ["15", "30", "60", "100"] entry = entity_registry.async_get("select.genie_room_size") diff --git a/tests/components/rituals_perfume_genie/test_sensor.py b/tests/components/rituals_perfume_genie/test_sensor.py index eb4211f1a20..6502cd8ee4f 100644 --- a/tests/components/rituals_perfume_genie/test_sensor.py +++ b/tests/components/rituals_perfume_genie/test_sensor.py @@ -2,7 +2,6 @@ from homeassistant.components.rituals_perfume_genie.sensor import SensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, - ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, EntityCategory, @@ -29,7 +28,6 @@ async def test_sensors_diffuser_v1_battery_cartridge( state = hass.states.get("sensor.genie_perfume") assert state assert state.state == diffuser.perfume - assert state.attributes.get(ATTR_ICON) == "mdi:tag" entry = entity_registry.async_get("sensor.genie_perfume") assert entry @@ -38,7 +36,6 @@ async def test_sensors_diffuser_v1_battery_cartridge( state = hass.states.get("sensor.genie_fill") assert state assert state.state == diffuser.fill - assert state.attributes.get(ATTR_ICON) == "mdi:beaker" entry = entity_registry.async_get("sensor.genie_fill") assert entry diff --git a/tests/components/rituals_perfume_genie/test_switch.py b/tests/components/rituals_perfume_genie/test_switch.py index 69c2dc01923..70250615446 100644 --- a/tests/components/rituals_perfume_genie/test_switch.py +++ b/tests/components/rituals_perfume_genie/test_switch.py @@ -6,7 +6,6 @@ from homeassistant.components.rituals_perfume_genie.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, - ATTR_ICON, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, @@ -34,7 +33,6 @@ async def test_switch_entity( state = hass.states.get("switch.genie") assert state assert state.state == STATE_ON - assert state.attributes.get(ATTR_ICON) == "mdi:fan" entry = entity_registry.async_get("switch.genie") assert entry From ddcb38289e41b59c8a936e186faeeafccfafa44e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:46:25 +0100 Subject: [PATCH 0302/1691] Add icon translations to Roomba (#112217) --- .../components/roomba/binary_sensor.py | 1 - homeassistant/components/roomba/icons.json | 38 +++++++++++++++++++ homeassistant/components/roomba/sensor.py | 9 ----- 3 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/roomba/icons.json diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 007d803fbf4..4e99fa7d8d0 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -28,7 +28,6 @@ async def async_setup_entry( class RoombaBinStatus(IRobotEntity, BinarySensorEntity): """Class to hold Roomba Sensor basic info.""" - _attr_icon = "mdi:delete-variant" _attr_translation_key = "bin_full" @property diff --git a/homeassistant/components/roomba/icons.json b/homeassistant/components/roomba/icons.json new file mode 100644 index 00000000000..cdb36ef97e5 --- /dev/null +++ b/homeassistant/components/roomba/icons.json @@ -0,0 +1,38 @@ +{ + "entity": { + "binary_sensor": { + "bin_full": { + "default": "mdi:delete-variant" + } + }, + "sensor": { + "battery_cycles": { + "default": "mdi:counter" + }, + "total_cleaning_time": { + "default": "mdi:clock" + }, + "average_mission_time": { + "default": "mdi:clock" + }, + "total_missions": { + "default": "mdi:counter" + }, + "successful_missions": { + "default": "mdi:counter" + }, + "canceled_missions": { + "default": "mdi:counter" + }, + "failed_missions": { + "default": "mdi:counter" + }, + "scrubs_count": { + "default": "mdi:counter" + }, + "total_cleaned_area": { + "default": "mdi:texture-box" + } + } + } +} diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index ad2894ebb11..465e87cde6b 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -52,7 +52,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ key="battery_cycles", translation_key="battery_cycles", state_class=SensorStateClass.MEASUREMENT, - icon="mdi:counter", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda self: self.battery_stats.get("nLithChrg") or self.battery_stats.get("nNimhChrg"), @@ -60,7 +59,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="total_cleaning_time", translation_key="total_cleaning_time", - icon="mdi:clock", native_unit_of_measurement=UnitOfTime.HOURS, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda self: self.run_stats.get("hr"), @@ -68,7 +66,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="average_mission_time", translation_key="average_mission_time", - icon="mdi:clock", native_unit_of_measurement=UnitOfTime.MINUTES, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda self: self.mission_stats.get("aMssnM"), @@ -76,7 +73,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="total_missions", translation_key="total_missions", - icon="mdi:counter", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement="Missions", entity_category=EntityCategory.DIAGNOSTIC, @@ -85,7 +81,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="successful_missions", translation_key="successful_missions", - icon="mdi:counter", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement="Missions", entity_category=EntityCategory.DIAGNOSTIC, @@ -94,7 +89,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="canceled_missions", translation_key="canceled_missions", - icon="mdi:counter", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement="Missions", entity_category=EntityCategory.DIAGNOSTIC, @@ -103,7 +97,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="failed_missions", translation_key="failed_missions", - icon="mdi:counter", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement="Missions", entity_category=EntityCategory.DIAGNOSTIC, @@ -112,7 +105,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="scrubs_count", translation_key="scrubs_count", - icon="mdi:counter", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement="Scrubs", entity_category=EntityCategory.DIAGNOSTIC, @@ -122,7 +114,6 @@ SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="total_cleaned_area", translation_key="total_cleaned_area", - icon="mdi:texture-box", native_unit_of_measurement=AREA_SQUARE_METERS, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda self: ( From 79fcedef1530ccefa97afe018b1c4357154e2bdb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:46:38 +0100 Subject: [PATCH 0303/1691] Add icon translations to Skybell (#112225) --- homeassistant/components/skybell/icons.json | 27 +++++++++++++++++++++ homeassistant/components/skybell/sensor.py | 7 ------ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/skybell/icons.json diff --git a/homeassistant/components/skybell/icons.json b/homeassistant/components/skybell/icons.json new file mode 100644 index 00000000000..a2084cd2971 --- /dev/null +++ b/homeassistant/components/skybell/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "sensor": { + "chime_level": { + "default": "mdi:bell-ring" + }, + "last_button_event": { + "default": "mdi:clock" + }, + "last_motion_event": { + "default": "mdi:clock" + }, + "last_check_in": { + "default": "mdi:clock" + }, + "motion_threshold": { + "default": "mdi:walk" + }, + "wifi_ssid": { + "default": "mdi:wifi-settings" + }, + "wifi_status": { + "default": "mdi:wifi-strength-3" + } + } + } +} diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index 7093c5cad20..37e58bd7f3c 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -40,27 +40,23 @@ SENSOR_TYPES: tuple[SkybellSensorEntityDescription, ...] = ( SkybellSensorEntityDescription( key="chime_level", translation_key="chime_level", - icon="mdi:bell-ring", value_fn=lambda device: device.outdoor_chime_level, ), SkybellSensorEntityDescription( key="last_button_event", translation_key="last_button_event", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda device: device.latest("button").get(CONST.CREATED_AT), ), SkybellSensorEntityDescription( key="last_motion_event", translation_key="last_motion_event", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda device: device.latest("motion").get(CONST.CREATED_AT), ), SkybellSensorEntityDescription( key=CONST.ATTR_LAST_CHECK_IN, translation_key="last_check_in", - icon="mdi:clock", entity_registry_enabled_default=False, device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, @@ -69,7 +65,6 @@ SENSOR_TYPES: tuple[SkybellSensorEntityDescription, ...] = ( SkybellSensorEntityDescription( key="motion_threshold", translation_key="motion_threshold", - icon="mdi:walk", entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.motion_threshold, @@ -84,7 +79,6 @@ SENSOR_TYPES: tuple[SkybellSensorEntityDescription, ...] = ( SkybellSensorEntityDescription( key=CONST.ATTR_WIFI_SSID, translation_key="wifi_ssid", - icon="mdi:wifi-settings", entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.wifi_ssid, @@ -92,7 +86,6 @@ SENSOR_TYPES: tuple[SkybellSensorEntityDescription, ...] = ( SkybellSensorEntityDescription( key=CONST.ATTR_WIFI_STATUS, translation_key="wifi_status", - icon="mdi:wifi-strength-3", entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.wifi_status, From 0724a06d3a68ac3dfd6bed24ba98e841539e6331 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:46:49 +0100 Subject: [PATCH 0304/1691] Add icon translations to Snooz (#112230) --- homeassistant/components/snooz/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/snooz/icons.json diff --git a/homeassistant/components/snooz/icons.json b/homeassistant/components/snooz/icons.json new file mode 100644 index 00000000000..d9cccfff4ea --- /dev/null +++ b/homeassistant/components/snooz/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "transition_on": "mdi:blur", + "transition_off": "mdi:blur-off" + } +} From ee9802534aefa8046999a72fa6945c0a96bd1c0d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:47:00 +0100 Subject: [PATCH 0305/1691] Add icon translations to Solaredge (#112231) --- homeassistant/components/solaredge/icons.json | 33 +++++++++++++++++++ homeassistant/components/solaredge/sensor.py | 9 ----- 2 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/solaredge/icons.json diff --git a/homeassistant/components/solaredge/icons.json b/homeassistant/components/solaredge/icons.json new file mode 100644 index 00000000000..d190c2abddd --- /dev/null +++ b/homeassistant/components/solaredge/icons.json @@ -0,0 +1,33 @@ +{ + "entity": { + "sensor": { + "lifetime_energy": { + "default": "mdi:solar-power" + }, + "energy_this_year": { + "default": "mdi:solar-power" + }, + "energy_this_month": { + "default": "mdi:solar-power" + }, + "energy_today": { + "default": "mdi:solar-power" + }, + "current_power": { + "default": "mdi:solar-power" + }, + "power_consumption": { + "default": "mdi:flash" + }, + "solar_power": { + "default": "mdi:solar-power" + }, + "grid_power": { + "default": "mdi:power-plug" + }, + "storage_power": { + "default": "mdi:car-battery" + } + } + } +} diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index bb82da5fc89..1b6fc0add2f 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -52,7 +52,6 @@ SENSOR_TYPES = [ key="lifetime_energy", json_key="lifeTimeData", translation_key="lifetime_energy", - icon="mdi:solar-power", state_class=SensorStateClass.TOTAL, native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, @@ -62,7 +61,6 @@ SENSOR_TYPES = [ json_key="lastYearData", translation_key="energy_this_year", entity_registry_enabled_default=False, - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, ), @@ -71,7 +69,6 @@ SENSOR_TYPES = [ json_key="lastMonthData", translation_key="energy_this_month", entity_registry_enabled_default=False, - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, ), @@ -80,7 +77,6 @@ SENSOR_TYPES = [ json_key="lastDayData", translation_key="energy_today", entity_registry_enabled_default=False, - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, ), @@ -88,7 +84,6 @@ SENSOR_TYPES = [ key="current_power", json_key="currentPower", translation_key="current_power", - icon="mdi:solar-power", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, @@ -134,28 +129,24 @@ SENSOR_TYPES = [ json_key="LOAD", translation_key="power_consumption", entity_registry_enabled_default=False, - icon="mdi:flash", ), SolarEdgeSensorEntityDescription( key="solar_power", json_key="PV", translation_key="solar_power", entity_registry_enabled_default=False, - icon="mdi:solar-power", ), SolarEdgeSensorEntityDescription( key="grid_power", json_key="GRID", translation_key="grid_power", entity_registry_enabled_default=False, - icon="mdi:power-plug", ), SolarEdgeSensorEntityDescription( key="storage_power", json_key="STORAGE", translation_key="storage_power", entity_registry_enabled_default=False, - icon="mdi:car-battery", ), SolarEdgeSensorEntityDescription( key="purchased_energy", From 3005c92585699f806c5527d40f0cec5802567b97 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:47:17 +0100 Subject: [PATCH 0306/1691] Add icon translations to Soundtouch (#112236) --- homeassistant/components/soundtouch/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/soundtouch/icons.json diff --git a/homeassistant/components/soundtouch/icons.json b/homeassistant/components/soundtouch/icons.json new file mode 100644 index 00000000000..0dd41f4f881 --- /dev/null +++ b/homeassistant/components/soundtouch/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "play_everywhere": "mdi:play", + "create_zone": "mdi:plus", + "add_zone_slave": "mdi:plus", + "remove_zone_slave": "mdi:minus" + } +} From 4f9d8d3048620dfa1f0a0fdc993b2b6f24154dac Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:47:28 +0100 Subject: [PATCH 0307/1691] Add icon translations to Subaru (#112252) --- .../components/subaru/device_tracker.py | 2 +- homeassistant/components/subaru/icons.json | 29 +++++++++++++++++++ homeassistant/components/subaru/sensor.py | 5 ---- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/subaru/icons.json diff --git a/homeassistant/components/subaru/device_tracker.py b/homeassistant/components/subaru/device_tracker.py index 4a8cb8ad5ee..c11a85eca66 100644 --- a/homeassistant/components/subaru/device_tracker.py +++ b/homeassistant/components/subaru/device_tracker.py @@ -47,7 +47,7 @@ class SubaruDeviceTracker( ): """Class for Subaru device tracker.""" - _attr_icon = "mdi:car" + _attr_translation_key = "location" _attr_has_entity_name = True _attr_name = None diff --git a/homeassistant/components/subaru/icons.json b/homeassistant/components/subaru/icons.json new file mode 100644 index 00000000000..f6c3597c3c3 --- /dev/null +++ b/homeassistant/components/subaru/icons.json @@ -0,0 +1,29 @@ +{ + "entity": { + "device_tracker": { + "location": { + "default": "mdi:car" + } + }, + "sensor": { + "odometer": { + "default": "mdi:road-variant" + }, + "average_fuel_consumption": { + "default": "mdi:leaf" + }, + "range": { + "default": "mdi:gas-station" + }, + "fuel_level": { + "default": "mdi:gas-station" + }, + "ev_range": { + "default": "mdi:ev-station" + } + } + }, + "services": { + "unlock_specific_door": "mdi:lock-open-variant" + } +} diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index eda8c20b10e..bdc1677ed8b 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -57,7 +57,6 @@ SAFETY_SENSORS = [ key=sc.ODOMETER, translation_key="odometer", device_class=SensorDeviceClass.DISTANCE, - icon="mdi:road-variant", native_unit_of_measurement=UnitOfLength.KILOMETERS, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -68,7 +67,6 @@ API_GEN_2_SENSORS = [ SensorEntityDescription( key=sc.AVG_FUEL_CONSUMPTION, translation_key="average_fuel_consumption", - icon="mdi:leaf", native_unit_of_measurement=FUEL_CONSUMPTION_LITERS_PER_HUNDRED_KILOMETERS, state_class=SensorStateClass.MEASUREMENT, ), @@ -76,7 +74,6 @@ API_GEN_2_SENSORS = [ key=sc.DIST_TO_EMPTY, translation_key="range", device_class=SensorDeviceClass.DISTANCE, - icon="mdi:gas-station", native_unit_of_measurement=UnitOfLength.KILOMETERS, state_class=SensorStateClass.MEASUREMENT, ), @@ -115,7 +112,6 @@ API_GEN_3_SENSORS = [ SensorEntityDescription( key=sc.REMAINING_FUEL_PERCENT, translation_key="fuel_level", - icon="mdi:gas-station", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), @@ -127,7 +123,6 @@ EV_SENSORS = [ key=sc.EV_DISTANCE_TO_EMPTY, translation_key="ev_range", device_class=SensorDeviceClass.DISTANCE, - icon="mdi:ev-station", native_unit_of_measurement=UnitOfLength.MILES, state_class=SensorStateClass.MEASUREMENT, ), From 0c2cf881acc8a4d3202a6e81d178639184c8953f Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 5 Mar 2024 08:49:05 +0100 Subject: [PATCH 0308/1691] Deprecate old config options for MQTT json light (#111676) * add deprecaction * Deprecate old config options for mqtt json light * Do not deprecate brightness flag * Enable brightness support by default * Keep `false` as default for brightness flag * Add warnings and register issue * log warning and register on use of color_mode flag * Remove redundant cv.deprecated logging + follow up comments --- .../components/mqtt/light/schema_json.py | 159 ++++++++++++++---- homeassistant/components/mqtt/strings.json | 8 + tests/components/mqtt/test_light_json.py | 154 ++++++++++++++++- 3 files changed, 287 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index b1e5c1c18d4..8f54edc15f4 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -1,6 +1,7 @@ """Support for MQTT JSON lights.""" from __future__ import annotations +from collections.abc import Callable from contextlib import suppress import logging from typing import TYPE_CHECKING, Any, cast @@ -20,6 +21,7 @@ from homeassistant.components.light import ( ATTR_TRANSITION, ATTR_WHITE, ATTR_XY_COLOR, + DOMAIN as LIGHT_DOMAIN, ENTITY_ID_FORMAT, FLASH_LONG, FLASH_SHORT, @@ -43,13 +45,15 @@ from homeassistant.const import ( CONF_XY, STATE_ON, ) -from homeassistant.core import callback +from homeassistant.core import async_get_hass, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.json import json_dumps from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType import homeassistant.util.color as color_util from homeassistant.util.json import json_loads_object +from homeassistant.util.yaml import dump as yaml_dump from .. import subscription from ..config import DEFAULT_QOS, DEFAULT_RETAIN, MQTT_RW_SCHEMA @@ -59,6 +63,7 @@ from ..const import ( CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + DOMAIN as MQTT_DOMAIN, ) from ..debug_info import log_messages from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, write_state_on_attr_change @@ -100,12 +105,87 @@ CONF_MAX_MIREDS = "max_mireds" CONF_MIN_MIREDS = "min_mireds" -def valid_color_configuration(config: ConfigType) -> ConfigType: +def valid_color_configuration( + setup_from_yaml: bool, +) -> Callable[[dict[str, Any]], dict[str, Any]]: """Test color_mode is not combined with deprecated config.""" - deprecated = {CONF_COLOR_TEMP, CONF_HS, CONF_RGB, CONF_XY} - if config[CONF_COLOR_MODE] and any(config.get(key) for key in deprecated): - raise vol.Invalid(f"color_mode must not be combined with any of {deprecated}") - return config + + def _valid_color_configuration(config: ConfigType) -> ConfigType: + deprecated = {CONF_COLOR_TEMP, CONF_HS, CONF_RGB, CONF_XY} + deprecated_flags_used = any(config.get(key) for key in deprecated) + if config.get(CONF_SUPPORTED_COLOR_MODES): + if deprecated_flags_used: + raise vol.Invalid( + "supported_color_modes must not " + f"be combined with any of {deprecated}" + ) + elif deprecated_flags_used: + deprecated_flags = ", ".join(key for key in deprecated if key in config) + _LOGGER.warning( + "Deprecated flags [%s] used in MQTT JSON light config " + "for handling color mode, please use `supported_color_modes` instead. " + "Got: %s. This will stop working in Home Assistant Core 2025.3", + deprecated_flags, + config, + ) + if not setup_from_yaml: + return config + issue_id = hex(hash(frozenset(config))) + yaml_config_str = yaml_dump(config) + learn_more_url = ( + "https://www.home-assistant.io/integrations/" + f"{LIGHT_DOMAIN}.mqtt/#json-schema" + ) + hass = async_get_hass() + async_create_issue( + hass, + MQTT_DOMAIN, + issue_id, + issue_domain=LIGHT_DOMAIN, + is_fixable=False, + severity=IssueSeverity.WARNING, + learn_more_url=learn_more_url, + translation_placeholders={ + "deprecated_flags": deprecated_flags, + "config": yaml_config_str, + }, + translation_key="deprecated_color_handling", + ) + + if CONF_COLOR_MODE in config: + _LOGGER.warning( + "Deprecated flag `color_mode` used in MQTT JSON light config " + ", the `color_mode` flag is not used anymore and should be removed. " + "Got: %s. This will stop working in Home Assistant Core 2025.3", + config, + ) + if not setup_from_yaml: + return config + issue_id = hex(hash(frozenset(config))) + yaml_config_str = yaml_dump(config) + learn_more_url = ( + "https://www.home-assistant.io/integrations/" + f"{LIGHT_DOMAIN}.mqtt/#json-schema" + ) + hass = async_get_hass() + async_create_issue( + hass, + MQTT_DOMAIN, + issue_id, + breaks_in_ha_version="2025.3.0", + issue_domain=LIGHT_DOMAIN, + is_fixable=False, + severity=IssueSeverity.WARNING, + learn_more_url=learn_more_url, + translation_placeholders={ + "config": yaml_config_str, + }, + translation_key="deprecated_color_mode_flag", + ) + + return config + + return _valid_color_configuration _PLATFORM_SCHEMA_BASE = ( @@ -115,9 +195,11 @@ _PLATFORM_SCHEMA_BASE = ( vol.Optional( CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE ): vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Inclusive( - CONF_COLOR_MODE, "color_mode", default=DEFAULT_COLOR_MODE - ): cv.boolean, + # CONF_COLOR_MODE was deprecated with HA Core 2024.4 and will be + # removed with HA Core 2025.3 + vol.Optional(CONF_COLOR_MODE): cv.boolean, + # CONF_COLOR_TEMP was deprecated with HA Core 2024.4 and will be + # removed with HA Core 2025.3 vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean, vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean, vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), @@ -127,6 +209,8 @@ _PLATFORM_SCHEMA_BASE = ( vol.Optional( CONF_FLASH_TIME_SHORT, default=DEFAULT_FLASH_TIME_SHORT ): cv.positive_int, + # CONF_HS was deprecated with HA Core 2024.4 and will be + # removed with HA Core 2025.3 vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, @@ -135,9 +219,11 @@ _PLATFORM_SCHEMA_BASE = ( vol.Coerce(int), vol.In([0, 1, 2]) ), vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + # CONF_RGB was deprecated with HA Core 2024.4 and will be + # removed with HA Core 2025.3 vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean, vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, - vol.Inclusive(CONF_SUPPORTED_COLOR_MODES, "color_mode"): vol.All( + vol.Optional(CONF_SUPPORTED_COLOR_MODES): vol.All( cv.ensure_list, [vol.In(VALID_COLOR_MODES)], vol.Unique(), @@ -146,6 +232,8 @@ _PLATFORM_SCHEMA_BASE = ( vol.Optional(CONF_WHITE_SCALE, default=DEFAULT_WHITE_SCALE): vol.All( vol.Coerce(int), vol.Range(min=1) ), + # CONF_XY was deprecated with HA Core 2024.4 and will be + # removed with HA Core 2025.3 vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean, }, ) @@ -154,13 +242,13 @@ _PLATFORM_SCHEMA_BASE = ( ) DISCOVERY_SCHEMA_JSON = vol.All( + valid_color_configuration(False), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), - valid_color_configuration, ) PLATFORM_SCHEMA_MODERN_JSON = vol.All( + valid_color_configuration(True), _PLATFORM_SCHEMA_BASE, - valid_color_configuration, ) @@ -176,6 +264,8 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): _topic: dict[str, str | None] _optimistic: bool + _deprecated_color_handling: bool = False + @staticmethod def config_schema() -> vol.Schema: """Return the config schema.""" @@ -205,7 +295,14 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self._attr_supported_features |= ( config[CONF_EFFECT] and LightEntityFeature.EFFECT ) - if not self._config[CONF_COLOR_MODE]: + if supported_color_modes := self._config.get(CONF_SUPPORTED_COLOR_MODES): + self._attr_supported_color_modes = supported_color_modes + if self.supported_color_modes and len(self.supported_color_modes) == 1: + self._attr_color_mode = next(iter(self.supported_color_modes)) + else: + self._attr_color_mode = ColorMode.UNKNOWN + else: + self._deprecated_color_handling = True color_modes = {ColorMode.ONOFF} if config[CONF_BRIGHTNESS]: color_modes.add(ColorMode.BRIGHTNESS) @@ -216,15 +313,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self._attr_supported_color_modes = filter_supported_color_modes(color_modes) if self.supported_color_modes and len(self.supported_color_modes) == 1: self._fixed_color_mode = next(iter(self.supported_color_modes)) - else: - self._attr_supported_color_modes = self._config[CONF_SUPPORTED_COLOR_MODES] - if self.supported_color_modes and len(self.supported_color_modes) == 1: - self._attr_color_mode = next(iter(self.supported_color_modes)) - else: - self._attr_color_mode = ColorMode.UNKNOWN def _update_color(self, values: dict[str, Any]) -> None: - if not self._config[CONF_COLOR_MODE]: + if self._deprecated_color_handling: # Deprecated color handling try: red = int(values["color"]["r"]) @@ -353,7 +444,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self._attr_is_on = None if ( - not self._config[CONF_COLOR_MODE] + self._deprecated_color_handling and color_supported(self.supported_color_modes) and "color" in values ): @@ -363,7 +454,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): else: self._update_color(values) - if self._config[CONF_COLOR_MODE] and "color_mode" in values: + if not self._deprecated_color_handling and "color_mode" in values: self._update_color(values) if brightness_supported(self.supported_color_modes): @@ -390,9 +481,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): ) if ( - self.supported_color_modes + self._deprecated_color_handling + and self.supported_color_modes and ColorMode.COLOR_TEMP in self.supported_color_modes - and not self._config[CONF_COLOR_MODE] ): # Deprecated color handling try: @@ -461,7 +552,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): @property def color_mode(self) -> ColorMode | str | None: """Return current color mode.""" - if self._config[CONF_COLOR_MODE]: + if not self._deprecated_color_handling: return self._attr_color_mode if self._fixed_color_mode: # Legacy light with support for a single color mode @@ -484,19 +575,20 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT] def _scale_rgbxx(self, rgbxx: tuple[int, ...], kwargs: Any) -> tuple[int, ...]: - # If there's a brightness topic set, we don't want to scale the - # RGBxx values given using the brightness. + # If brightness is supported, we don't want to scale the + # RGBxx values given using the brightness and + # we pop the brightness, to omit it from the payload brightness: int if self._config[CONF_BRIGHTNESS]: brightness = 255 else: - brightness = kwargs.get(ATTR_BRIGHTNESS, 255) + brightness = kwargs.pop(ATTR_BRIGHTNESS, 255) return tuple(round(i / 255 * brightness) for i in rgbxx) def _supports_color_mode(self, color_mode: ColorMode | str) -> bool: """Return True if the light natively supports a color mode.""" return ( - self._config[CONF_COLOR_MODE] + not self._deprecated_color_handling and self.supported_color_modes is not None and color_mode in self.supported_color_modes ) @@ -522,12 +614,13 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): hs_color = kwargs[ATTR_HS_COLOR] message["color"] = {} if self._config[CONF_RGB]: - # If there's a brightness topic set, we don't want to scale the + # If brightness is supported, we don't want to scale the # RGB values given using the brightness. if self._config[CONF_BRIGHTNESS]: brightness = 255 else: - brightness = kwargs.get(ATTR_BRIGHTNESS, 255) + # We pop the brightness, to omit it from the payload + brightness = kwargs.pop(ATTR_BRIGHTNESS, 255) rgb = color_util.color_hsv_to_RGB( hs_color[0], hs_color[1], brightness / 255 * 100 ) @@ -595,7 +688,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self._set_flash_and_transition(message, **kwargs) - if ATTR_BRIGHTNESS in kwargs and self._config[CONF_BRIGHTNESS]: + if ATTR_BRIGHTNESS in kwargs and brightness_supported( + self.supported_color_modes + ): device_brightness = color_util.brightness_to_value( (1, self._config[CONF_BRIGHTNESS_SCALE]), kwargs[ATTR_BRIGHTNESS], diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index 4c37de8204c..87fe0bd033a 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -4,6 +4,14 @@ "title": "MQTT vacuum entities with deprecated `schema` config settings found in your configuration.yaml", "description": "The `schema` setting for MQTT vacuum entities is deprecated and should be removed. Please adjust your configuration.yaml and restart Home Assistant to fix this issue." }, + "deprecated_color_handling": { + "title": "Deprecated color handling used for MQTT light", + "description": "An MQTT light config (with `json` schema) found in `configuration.yaml` uses deprecated color handling flags.\n\nConfiguration found:\n```yaml\n{config}\n```\nDeprecated flags: **{deprecated_flags}**.\n\nUse the `supported_color_modes` option instead and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue." + }, + "deprecated_color_mode_flag": { + "title": "Deprecated color_mode option flag used for MQTT light", + "description": "An MQTT light config (with `json` schema) found in `configuration.yaml` uses a deprecated `color_mode` flag.\n\nConfiguration found:\n```yaml\n{config}\n```\n\nRemove the option from your config and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue." + }, "invalid_platform_config": { "title": "Invalid config found for mqtt {domain} item", "description": "Home Assistant detected an invalid config for a manually configured item.\n\nPlatform domain: **{domain}**\nConfiguration file: **{config_file}**\nNear line: **{line}**\nConfiguration found:\n```yaml\n{config}\n```\nError: **{error}**.\n\nMake sure the configuration is valid and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue." diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index d1fa2b72a31..1d154f7eb26 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -98,6 +98,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, State +from homeassistant.helpers.json import json_dumps from homeassistant.util.json import JsonValueType, json_loads from .test_common import ( @@ -149,7 +150,6 @@ COLOR_MODES_CONFIG = { mqtt.DOMAIN: { light.DOMAIN: { "brightness": True, - "color_mode": True, "effect": True, "command_topic": "test_light_rgb/set", "name": "test", @@ -217,7 +217,157 @@ async def test_fail_setup_if_color_mode_deprecated( ) -> None: """Test if setup fails if color mode is combined with deprecated config keys.""" assert await mqtt_mock_entry() - assert "color_mode must not be combined with any of" in caplog.text + assert "supported_color_modes must not be combined with any of" in caplog.text + + +@pytest.mark.parametrize( + ("hass_config", "color_modes"), + [ + ( + help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"color_temp": True},)), + ("color_temp",), + ), + (help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"hs": True},)), ("hs",)), + (help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"rgb": True},)), ("rgb",)), + (help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"xy": True},)), ("xy",)), + ( + help_custom_config( + light.DOMAIN, DEFAULT_CONFIG, ({"color_temp": True, "rgb": True},) + ), + ("color_temp, rgb", "rgb, color_temp"), + ), + ], + ids=["color_temp", "hs", "rgb", "xy", "color_temp, rgb"], +) +async def test_warning_if_color_mode_flags_are_used( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + color_modes: tuple[str,], +) -> None: + """Test warnings deprecated config keys without supported color modes defined.""" + with patch( + "homeassistant.components.mqtt.light.schema_json.async_create_issue" + ) as mock_async_create_issue: + assert await mqtt_mock_entry() + assert any( + ( + f"Deprecated flags [{color_modes_case}] used in MQTT JSON light config " + "for handling color mode, please use `supported_color_modes` instead." + in caplog.text + ) + for color_modes_case in color_modes + ) + mock_async_create_issue.assert_called_once() + + +@pytest.mark.parametrize( + ("config", "color_modes"), + [ + ( + help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"color_temp": True},)), + ("color_temp",), + ), + (help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"hs": True},)), ("hs",)), + (help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"rgb": True},)), ("rgb",)), + (help_custom_config(light.DOMAIN, DEFAULT_CONFIG, ({"xy": True},)), ("xy",)), + ( + help_custom_config( + light.DOMAIN, DEFAULT_CONFIG, ({"color_temp": True, "rgb": True},) + ), + ("color_temp, rgb", "rgb, color_temp"), + ), + ], + ids=["color_temp", "hs", "rgb", "xy", "color_temp, rgb"], +) +async def test_warning_on_discovery_if_color_mode_flags_are_used( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + config: dict[str, Any], + color_modes: tuple[str,], +) -> None: + """Test warnings deprecated config keys with discovery.""" + with patch( + "homeassistant.components.mqtt.light.schema_json.async_create_issue" + ) as mock_async_create_issue: + assert await mqtt_mock_entry() + + config_payload = json_dumps(config[mqtt.DOMAIN][light.DOMAIN][0]) + async_fire_mqtt_message( + hass, + "homeassistant/light/bla/config", + config_payload, + ) + await hass.async_block_till_done() + assert any( + ( + f"Deprecated flags [{color_modes_case}] used in MQTT JSON light config " + "for handling color mode, please " + "use `supported_color_modes` instead" in caplog.text + ) + for color_modes_case in color_modes + ) + mock_async_create_issue.assert_not_called() + + +@pytest.mark.parametrize( + "hass_config", + [ + help_custom_config( + light.DOMAIN, + DEFAULT_CONFIG, + ({"color_mode": True, "supported_color_modes": ["color_temp"]},), + ), + ], + ids=["color_temp"], +) +async def test_warning_if_color_mode_option_flag_is_used( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test warning deprecated color_mode option flag is used.""" + with patch( + "homeassistant.components.mqtt.light.schema_json.async_create_issue" + ) as mock_async_create_issue: + assert await mqtt_mock_entry() + assert "Deprecated flag `color_mode` used in MQTT JSON light config" in caplog.text + mock_async_create_issue.assert_called_once() + + +@pytest.mark.parametrize( + "config", + [ + help_custom_config( + light.DOMAIN, + DEFAULT_CONFIG, + ({"color_mode": True, "supported_color_modes": ["color_temp"]},), + ), + ], + ids=["color_temp"], +) +async def test_warning_on_discovery_if_color_mode_option_flag_is_used( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + config: dict[str, Any], +) -> None: + """Test warning deprecated color_mode option flag is used.""" + with patch( + "homeassistant.components.mqtt.light.schema_json.async_create_issue" + ) as mock_async_create_issue: + assert await mqtt_mock_entry() + + config_payload = json_dumps(config[mqtt.DOMAIN][light.DOMAIN][0]) + async_fire_mqtt_message( + hass, + "homeassistant/light/bla/config", + config_payload, + ) + await hass.async_block_till_done() + assert "Deprecated flag `color_mode` used in MQTT JSON light config" in caplog.text + mock_async_create_issue.assert_not_called() @pytest.mark.parametrize( From 0e0c1d337ff6767253723e1e7e0f90744cf5c72f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:50:46 +0100 Subject: [PATCH 0309/1691] Add icon translations to P1 monitor (#111998) * Add icon translations to P1 monitor * Add icon translations to P1 monitor --- homeassistant/components/p1_monitor/icons.json | 9 +++++++++ homeassistant/components/p1_monitor/sensor.py | 1 - tests/components/p1_monitor/test_sensor.py | 7 ------- 3 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/p1_monitor/icons.json diff --git a/homeassistant/components/p1_monitor/icons.json b/homeassistant/components/p1_monitor/icons.json new file mode 100644 index 00000000000..d95084ca0e6 --- /dev/null +++ b/homeassistant/components/p1_monitor/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "energy_tariff_period": { + "default": "mdi:calendar-clock" + } + } + } +} diff --git a/homeassistant/components/p1_monitor/sensor.py b/homeassistant/components/p1_monitor/sensor.py index 587dc980e41..4108194aced 100644 --- a/homeassistant/components/p1_monitor/sensor.py +++ b/homeassistant/components/p1_monitor/sensor.py @@ -88,7 +88,6 @@ SENSORS_SMARTMETER: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="energy_tariff_period", translation_key="energy_tariff_period", - icon="mdi:calendar-clock", ), ) diff --git a/tests/components/p1_monitor/test_sensor.py b/tests/components/p1_monitor/test_sensor.py index f84df458d4b..4e6b7580319 100644 --- a/tests/components/p1_monitor/test_sensor.py +++ b/tests/components/p1_monitor/test_sensor.py @@ -13,7 +13,6 @@ from homeassistant.components.sensor import ( from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, - ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, CURRENCY_EURO, UnitOfElectricCurrent, @@ -47,7 +46,6 @@ async def test_smartmeter( assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER - assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.smartmeter_energy_consumption_high_tariff") entry = entity_registry.async_get( @@ -64,7 +62,6 @@ async def test_smartmeter( assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL_INCREASING assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.KILO_WATT_HOUR assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY - assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.smartmeter_energy_tariff_period") entry = entity_registry.async_get("sensor.smartmeter_energy_tariff_period") @@ -73,7 +70,6 @@ async def test_smartmeter( assert entry.unique_id == f"{entry_id}_smartmeter_energy_tariff_period" assert state.state == "high" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SmartMeter Energy tariff period" - assert state.attributes.get(ATTR_ICON) == "mdi:calendar-clock" assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes assert ATTR_DEVICE_CLASS not in state.attributes @@ -109,7 +105,6 @@ async def test_phases( state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricPotential.VOLT ) assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE - assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.phases_current_phase_l1") entry = entity_registry.async_get("sensor.phases_current_phase_l1") @@ -123,7 +118,6 @@ async def test_phases( state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfElectricCurrent.AMPERE ) assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.CURRENT - assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.phases_power_consumed_phase_l1") entry = entity_registry.async_get("sensor.phases_power_consumed_phase_l1") @@ -135,7 +129,6 @@ async def test_phases( assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER - assert ATTR_ICON not in state.attributes assert entry.device_id device_entry = device_registry.async_get(entry.device_id) From 14eba0e7ad691bbf79ee7ba936d26a3bc69d7ca0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:50:59 +0100 Subject: [PATCH 0310/1691] Add icon translations to Speedtestdotnet (#112237) --- homeassistant/components/speedtestdotnet/const.py | 2 -- .../components/speedtestdotnet/icons.json | 15 +++++++++++++++ .../components/speedtestdotnet/sensor.py | 2 -- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/speedtestdotnet/icons.json diff --git a/homeassistant/components/speedtestdotnet/const.py b/homeassistant/components/speedtestdotnet/const.py index 0d2625bfe33..75cd78ca8cf 100644 --- a/homeassistant/components/speedtestdotnet/const.py +++ b/homeassistant/components/speedtestdotnet/const.py @@ -20,5 +20,3 @@ DEFAULT_SCAN_INTERVAL: Final = 60 DEFAULT_SERVER: Final = "*Auto Detect" ATTRIBUTION: Final = "Data retrieved from Speedtest.net by Ookla" - -ICON: Final = "mdi:speedometer" diff --git a/homeassistant/components/speedtestdotnet/icons.json b/homeassistant/components/speedtestdotnet/icons.json new file mode 100644 index 00000000000..9f3beebdf21 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/icons.json @@ -0,0 +1,15 @@ +{ + "entity": { + "sensor": { + "ping": { + "default": "mdi:speedometer" + }, + "download": { + "default": "mdi:speedometer" + }, + "upload": { + "default": "mdi:speedometer" + } + } + } +} diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 53e80be0cc0..1d23e0e516e 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -28,7 +28,6 @@ from .const import ( ATTRIBUTION, DEFAULT_NAME, DOMAIN, - ICON, ) from .coordinator import SpeedTestDataCoordinator @@ -86,7 +85,6 @@ class SpeedtestSensor(CoordinatorEntity[SpeedTestDataCoordinator], SensorEntity) entity_description: SpeedtestSensorEntityDescription _attr_attribution = ATTRIBUTION _attr_has_entity_name = True - _attr_icon = ICON def __init__( self, From fe60cdd821c9474491965abf0d5d0ca2ea53326b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 08:51:11 +0100 Subject: [PATCH 0311/1691] Use default icon in Romy (#112215) --- homeassistant/components/romy/vacuum.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/romy/vacuum.py b/homeassistant/components/romy/vacuum.py index 0670c2a49f6..f352f528c58 100644 --- a/homeassistant/components/romy/vacuum.py +++ b/homeassistant/components/romy/vacuum.py @@ -19,8 +19,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, LOGGER from .coordinator import RomyVacuumCoordinator -ICON = "mdi:robot-vacuum" - FAN_SPEED_NONE = "default" FAN_SPEED_NORMAL = "normal" FAN_SPEED_SILENT = "silent" @@ -68,7 +66,6 @@ class RomyVacuumEntity(CoordinatorEntity[RomyVacuumCoordinator], StateVacuumEnti _attr_name = None _attr_supported_features = SUPPORT_ROMY_ROBOT _attr_fan_speed_list = FAN_SPEEDS - _attr_icon = ICON def __init__( self, From 1d936e9bbf905602f5ecd325b5cc0f83800857cb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 09:19:19 +0100 Subject: [PATCH 0312/1691] Add icon translations to Shopping list (#112223) --- .../components/shopping_list/icons.json | 19 +++++++++++++++++++ .../components/shopping_list/todo.py | 1 - 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/shopping_list/icons.json diff --git a/homeassistant/components/shopping_list/icons.json b/homeassistant/components/shopping_list/icons.json new file mode 100644 index 00000000000..7de3eb1b948 --- /dev/null +++ b/homeassistant/components/shopping_list/icons.json @@ -0,0 +1,19 @@ +{ + "entity": { + "todo": { + "shopping_list": { + "default": "mdi:cart" + } + } + }, + "services": { + "add_item": "mdi:cart-plus", + "remove_item": "mdi:cart-remove", + "complete_item": "mdi:cart-check", + "incomplete_item": "mdi:cart-off", + "complete_all": "mdi:cart-check", + "incomplete_all": "mdi:cart-off", + "clear_completed_items": "mdi:cart-remove", + "sort": "mdi:sort" + } +} diff --git a/homeassistant/components/shopping_list/todo.py b/homeassistant/components/shopping_list/todo.py index 2d959858067..82b6cbfc7f5 100644 --- a/homeassistant/components/shopping_list/todo.py +++ b/homeassistant/components/shopping_list/todo.py @@ -32,7 +32,6 @@ class ShoppingTodoListEntity(TodoListEntity): """A To-do List representation of the Shopping List.""" _attr_has_entity_name = True - _attr_icon = "mdi:cart" _attr_translation_key = "shopping_list" _attr_should_poll = False _attr_supported_features = ( From 23975017767d9c635b22e113c9058683fa83c568 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 09:19:27 +0100 Subject: [PATCH 0313/1691] Add icon translations to Pi-hole (#112180) --- .../components/pi_hole/binary_sensor.py | 1 - homeassistant/components/pi_hole/icons.json | 41 +++++++++++++++++++ homeassistant/components/pi_hole/sensor.py | 9 ---- 3 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/pi_hole/icons.json diff --git a/homeassistant/components/pi_hole/binary_sensor.py b/homeassistant/components/pi_hole/binary_sensor.py index 2f3a5a4801c..42a52d07656 100644 --- a/homeassistant/components/pi_hole/binary_sensor.py +++ b/homeassistant/components/pi_hole/binary_sensor.py @@ -41,7 +41,6 @@ BINARY_SENSOR_TYPES: tuple[PiHoleBinarySensorEntityDescription, ...] = ( PiHoleBinarySensorEntityDescription( key="status", translation_key="status", - icon="mdi:pi-hole", state_value=lambda api: bool(api.data.get("status") == "enabled"), ), ) diff --git a/homeassistant/components/pi_hole/icons.json b/homeassistant/components/pi_hole/icons.json new file mode 100644 index 00000000000..58f20da5a2d --- /dev/null +++ b/homeassistant/components/pi_hole/icons.json @@ -0,0 +1,41 @@ +{ + "entity": { + "binary_sensor": { + "status": { + "default": "mdi:pi-hole" + } + }, + "sensor": { + "ads_blocked_today": { + "default": "mdi:close-octagon-outline" + }, + "ads_percentage_today": { + "default": "mdi:close-octagon-outline" + }, + "clients_ever_seen": { + "default": "mdi:account-outline" + }, + "dns_queries_today": { + "default": "mdi:comment-question-outline" + }, + "domains_being_blocked": { + "default": "mdi:block-helper" + }, + "queries_cached": { + "default": "mdi:comment-question-outline" + }, + "queries_forwarded": { + "default": "mdi:comment-question-outline" + }, + "unique_clients": { + "default": "mdi:account-outline" + }, + "unique_domains": { + "default": "mdi:domain" + } + } + }, + "services": { + "disable": "mdi:server-off" + } +} diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index c6a8d5da83d..9584c23af38 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -19,55 +19,46 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="ads_blocked_today", translation_key="ads_blocked_today", native_unit_of_measurement="ads", - icon="mdi:close-octagon-outline", ), SensorEntityDescription( key="ads_percentage_today", translation_key="ads_percentage_today", native_unit_of_measurement=PERCENTAGE, - icon="mdi:close-octagon-outline", ), SensorEntityDescription( key="clients_ever_seen", translation_key="clients_ever_seen", native_unit_of_measurement="clients", - icon="mdi:account-outline", ), SensorEntityDescription( key="dns_queries_today", translation_key="dns_queries_today", native_unit_of_measurement="queries", - icon="mdi:comment-question-outline", ), SensorEntityDescription( key="domains_being_blocked", translation_key="domains_being_blocked", native_unit_of_measurement="domains", - icon="mdi:block-helper", ), SensorEntityDescription( key="queries_cached", translation_key="queries_cached", native_unit_of_measurement="queries", - icon="mdi:comment-question-outline", ), SensorEntityDescription( key="queries_forwarded", translation_key="queries_forwarded", native_unit_of_measurement="queries", - icon="mdi:comment-question-outline", ), SensorEntityDescription( key="unique_clients", translation_key="unique_clients", native_unit_of_measurement="clients", - icon="mdi:account-outline", ), SensorEntityDescription( key="unique_domains", translation_key="unique_domains", native_unit_of_measurement="domains", - icon="mdi:domain", ), ) From b0136ee2440894b245f41f68d0da3202a9642134 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 09:19:37 +0100 Subject: [PATCH 0314/1691] Add icon translations to PECO (#112169) --- homeassistant/components/peco/icons.json | 21 +++++++++++++++++++++ homeassistant/components/peco/sensor.py | 5 ----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/peco/icons.json diff --git a/homeassistant/components/peco/icons.json b/homeassistant/components/peco/icons.json new file mode 100644 index 00000000000..734c5262bbc --- /dev/null +++ b/homeassistant/components/peco/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "sensor": { + "customers_out": { + "default": "mdi:power-plug-off" + }, + "percent_customers_out": { + "default": "mdi:power-plug-off" + }, + "outage_count": { + "default": "mdi:power-plug-off" + }, + "customers_served": { + "default": "mdi:power-plug-off" + }, + "map_alert": { + "default": "mdi:alert" + } + } + } +} diff --git a/homeassistant/components/peco/sensor.py b/homeassistant/components/peco/sensor.py index f9ad35fd251..ed6540b9eb1 100644 --- a/homeassistant/components/peco/sensor.py +++ b/homeassistant/components/peco/sensor.py @@ -46,7 +46,6 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( translation_key="customers_out", value_fn=lambda data: int(data.outages.customers_out), attribute_fn=lambda data: {}, - icon="mdi:power-plug-off", state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( @@ -55,7 +54,6 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( native_unit_of_measurement=PERCENTAGE, value_fn=lambda data: int(data.outages.percent_customers_out), attribute_fn=lambda data: {}, - icon="mdi:power-plug-off", state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( @@ -63,7 +61,6 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( translation_key="outage_count", value_fn=lambda data: int(data.outages.outage_count), attribute_fn=lambda data: {}, - icon="mdi:power-plug-off", state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( @@ -71,7 +68,6 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( translation_key="customers_served", value_fn=lambda data: int(data.outages.customers_served), attribute_fn=lambda data: {}, - icon="mdi:power-plug-off", state_class=SensorStateClass.MEASUREMENT, ), PECOSensorEntityDescription( @@ -79,7 +75,6 @@ SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( translation_key="map_alert", value_fn=lambda data: str(data.alerts.alert_title), attribute_fn=lambda data: {ATTR_CONTENT: data.alerts.alert_content}, - icon="mdi:alert", ), ) From 48de30446542f5202e3a66bcbb1acdcdbae07fa8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 09:19:52 +0100 Subject: [PATCH 0315/1691] Add icon translations to Permobil (#112170) --- homeassistant/components/permobil/icons.json | 36 ++++++++++++++++++++ homeassistant/components/permobil/sensor.py | 10 ------ 2 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/permobil/icons.json diff --git a/homeassistant/components/permobil/icons.json b/homeassistant/components/permobil/icons.json new file mode 100644 index 00000000000..ba3c612b756 --- /dev/null +++ b/homeassistant/components/permobil/icons.json @@ -0,0 +1,36 @@ +{ + "entity": { + "sensor": { + "state_of_health": { + "default": "mdi:battery-heart-variant" + }, + "charge_time_left": { + "default": "mdi:battery-clock" + }, + "distance_left": { + "default": "mdi:map-marker-distance" + }, + "max_watt_hours": { + "default": "mdi:lightning-bolt" + }, + "watt_hours_left": { + "default": "mdi:lightning-bolt" + }, + "max_distance_left": { + "default": "mdi:map-marker-distance" + }, + "usage_distance": { + "default": "mdi:map-marker-distance" + }, + "usage_adjustments": { + "default": "mdi:seat-recline-extra" + }, + "record_adjustments": { + "default": "mdi:seat-recline-extra" + }, + "record_distance": { + "default": "mdi:map-marker-distance" + } + } + } +} diff --git a/homeassistant/components/permobil/sensor.py b/homeassistant/components/permobil/sensor.py index 8814a3de83d..6b783f3a385 100644 --- a/homeassistant/components/permobil/sensor.py +++ b/homeassistant/components/permobil/sensor.py @@ -72,7 +72,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: BATTERY_STATE_OF_HEALTH[0] in data.battery, key="state_of_health", translation_key="state_of_health", - icon="mdi:battery-heart-variant", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), @@ -82,7 +81,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: BATTERY_CHARGE_TIME_LEFT[0] in data.battery, key="charge_time_left", translation_key="charge_time_left", - icon="mdi:battery-clock", native_unit_of_measurement=UnitOfTime.HOURS, device_class=SensorDeviceClass.DURATION, ), @@ -92,7 +90,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: BATTERY_DISTANCE_LEFT[0] in data.battery, key="distance_left", translation_key="distance_left", - icon="mdi:map-marker-distance", native_unit_of_measurement=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, ), @@ -112,7 +109,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: BATTERY_MAX_AMPERE_HOURS[0] in data.battery, key="max_watt_hours", translation_key="max_watt_hours", - icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY_STORAGE, state_class=SensorStateClass.MEASUREMENT, @@ -124,7 +120,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: BATTERY_AMPERE_HOURS_LEFT[0] in data.battery, key="watt_hours_left", translation_key="watt_hours_left", - icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY_STORAGE, state_class=SensorStateClass.MEASUREMENT, @@ -135,7 +130,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: BATTERY_MAX_DISTANCE_LEFT[0] in data.battery, key="max_distance_left", translation_key="max_distance_left", - icon="mdi:map-marker-distance", native_unit_of_measurement=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, ), @@ -145,7 +139,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: USAGE_DISTANCE[0] in data.daily_usage, key="usage_distance", translation_key="usage_distance", - icon="mdi:map-marker-distance", native_unit_of_measurement=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, state_class=SensorStateClass.TOTAL_INCREASING, @@ -156,7 +149,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: USAGE_ADJUSTMENTS[0] in data.daily_usage, key="usage_adjustments", translation_key="usage_adjustments", - icon="mdi:seat-recline-extra", native_unit_of_measurement="adjustments", state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -166,7 +158,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: RECORDS_SEATING[0] in data.records, key="record_adjustments", translation_key="record_adjustments", - icon="mdi:seat-recline-extra", native_unit_of_measurement="adjustments", state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -176,7 +167,6 @@ SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( available_fn=lambda data: RECORDS_DISTANCE[0] in data.records, key="record_distance", translation_key="record_distance", - icon="mdi:map-marker-distance", device_class=SensorDeviceClass.DISTANCE, state_class=SensorStateClass.TOTAL_INCREASING, ), From 1feeeb86081319b73762dd55e8843154d3889ff9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 09:20:00 +0100 Subject: [PATCH 0316/1691] Add icon translations to Plex (#112182) --- homeassistant/components/plex/icons.json | 13 +++++++++++++ homeassistant/components/plex/sensor.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/plex/icons.json diff --git a/homeassistant/components/plex/icons.json b/homeassistant/components/plex/icons.json new file mode 100644 index 00000000000..03bc835d2f6 --- /dev/null +++ b/homeassistant/components/plex/icons.json @@ -0,0 +1,13 @@ +{ + "entity": { + "sensor": { + "plex": { + "default": "mdi:plex" + } + } + }, + "services": { + "refresh_library": "mdi:refresh", + "scan_for_clients": "mdi:database-refresh" + } +} diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index acc309ab14c..350e0a2dce2 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -72,7 +72,7 @@ class PlexSensor(SensorEntity): _attr_has_entity_name = True _attr_name = None - _attr_icon = "mdi:plex" + _attr_translation_key = "plex" _attr_should_poll = False _attr_native_unit_of_measurement = "watching" From 2e65952eae33e336ed9be13864711aa3201c8faf Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 5 Mar 2024 00:35:00 -0800 Subject: [PATCH 0317/1691] Cleanup rainbird aiohttp client cleanup on EVENT_HOMEASSISTANT_CLOSE (#112292) * Add rainbird aiohttp client cleanup for EVENT_HOMEASSISTANT_CLOSE * Apply suggestions from code review Co-authored-by: Martin Hjelmare --------- Co-authored-by: Martin Hjelmare --- homeassistant/components/rainbird/__init__.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py index 2a660435e17..945c06943d3 100644 --- a/homeassistant/components/rainbird/__init__.py +++ b/homeassistant/components/rainbird/__init__.py @@ -2,12 +2,20 @@ from __future__ import annotations import logging +from typing import Any +import aiohttp from pyrainbird.async_client import AsyncRainbirdClient, AsyncRainbirdController from pyrainbird.exceptions import RainbirdApiException from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, Platform +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_PASSWORD, + EVENT_HOMEASSISTANT_CLOSE, + Platform, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -30,13 +38,30 @@ PLATFORMS = [ DOMAIN = "rainbird" +def _async_register_clientsession_shutdown( + hass: HomeAssistant, entry: ConfigEntry, clientsession: aiohttp.ClientSession +) -> None: + """Register cleanup hooks for the clientsession.""" + + async def _async_close_websession(*_: Any) -> None: + """Close websession.""" + await clientsession.close() + + unsub = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_CLOSE, _async_close_websession + ) + entry.async_on_unload(unsub) + entry.async_on_unload(_async_close_websession) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the config entry for Rain Bird.""" hass.data.setdefault(DOMAIN, {}) clientsession = async_create_clientsession() - entry.async_on_unload(clientsession.close) + _async_register_clientsession_shutdown(hass, entry, clientsession) + controller = AsyncRainbirdController( AsyncRainbirdClient( clientsession, From 33c1e7d45d982fcdead86155e48910b10e7720b8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 11:34:18 +0100 Subject: [PATCH 0318/1691] Add icon translations to Switcher kis (#112296) --- .../components/switcher_kis/button.py | 4 --- .../components/switcher_kis/icons.json | 30 +++++++++++++++++++ .../components/switcher_kis/sensor.py | 2 -- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/switcher_kis/icons.json diff --git a/homeassistant/components/switcher_kis/button.py b/homeassistant/components/switcher_kis/button.py index 64571f15af0..e9a1d1a2a89 100644 --- a/homeassistant/components/switcher_kis/button.py +++ b/homeassistant/components/switcher_kis/button.py @@ -48,7 +48,6 @@ THERMOSTAT_BUTTONS = [ SwitcherThermostatButtonEntityDescription( key="assume_on", translation_key="assume_on", - icon="mdi:fan", entity_category=EntityCategory.CONFIG, press_fn=lambda api, remote: api.control_breeze_device( remote, state=DeviceState.ON, update_state=True @@ -58,7 +57,6 @@ THERMOSTAT_BUTTONS = [ SwitcherThermostatButtonEntityDescription( key="assume_off", translation_key="assume_off", - icon="mdi:fan-off", entity_category=EntityCategory.CONFIG, press_fn=lambda api, remote: api.control_breeze_device( remote, state=DeviceState.OFF, update_state=True @@ -68,7 +66,6 @@ THERMOSTAT_BUTTONS = [ SwitcherThermostatButtonEntityDescription( key="vertical_swing_on", translation_key="vertical_swing_on", - icon="mdi:autorenew", press_fn=lambda api, remote: api.control_breeze_device( remote, swing=ThermostatSwing.ON ), @@ -77,7 +74,6 @@ THERMOSTAT_BUTTONS = [ SwitcherThermostatButtonEntityDescription( key="vertical_swing_off", translation_key="vertical_swing_off", - icon="mdi:autorenew-off", press_fn=lambda api, remote: api.control_breeze_device( remote, swing=ThermostatSwing.OFF ), diff --git a/homeassistant/components/switcher_kis/icons.json b/homeassistant/components/switcher_kis/icons.json new file mode 100644 index 00000000000..4d3576f1a99 --- /dev/null +++ b/homeassistant/components/switcher_kis/icons.json @@ -0,0 +1,30 @@ +{ + "entity": { + "button": { + "assume_on": { + "default": "mdi:fan" + }, + "assume_off": { + "default": "mdi:fan-off" + }, + "vertical_swing_on": { + "default": "mdi:autorenew" + }, + "vertical_swing_off": { + "default": "mdi:autorenew-off" + } + }, + "sensor": { + "remaining_time": { + "default": "mdi:av-timer" + }, + "auto_shutdown": { + "default": "mdi:progress-clock" + } + } + }, + "services": { + "set_auto_off": "mdi:progress-clock", + "turn_on_with_timer": "mdi:timer" + } +} diff --git a/homeassistant/components/switcher_kis/sensor.py b/homeassistant/components/switcher_kis/sensor.py index e9fa13fca8a..09a8383f5c8 100644 --- a/homeassistant/components/switcher_kis/sensor.py +++ b/homeassistant/components/switcher_kis/sensor.py @@ -40,12 +40,10 @@ TIME_SENSORS: list[SensorEntityDescription] = [ SensorEntityDescription( key="remaining_time", translation_key="remaining_time", - icon="mdi:av-timer", ), SensorEntityDescription( key="auto_off_set", translation_key="auto_shutdown", - icon="mdi:progress-clock", entity_registry_enabled_default=False, ), ] From 0b4fafddbccae04a4f290dabdba1546005145e13 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 11:52:50 +0100 Subject: [PATCH 0319/1691] Add icon translations to Systemmonitor (#112300) * Add icon translations to Systemmonitor * Add icon translations to Systemmonitor --- .../components/systemmonitor/icons.json | 51 +++++++++++++++++++ .../components/systemmonitor/sensor.py | 15 ------ .../systemmonitor/snapshots/test_sensor.ambr | 25 --------- tests/components/systemmonitor/test_sensor.py | 1 - 4 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 homeassistant/components/systemmonitor/icons.json diff --git a/homeassistant/components/systemmonitor/icons.json b/homeassistant/components/systemmonitor/icons.json new file mode 100644 index 00000000000..b0ea54acc98 --- /dev/null +++ b/homeassistant/components/systemmonitor/icons.json @@ -0,0 +1,51 @@ +{ + "entity": { + "sensor": { + "disk_free": { + "default": "mdi:harddisk" + }, + "disk_use": { + "default": "mdi:harddisk" + }, + "disk_use_percent": { + "default": "mdi:harddisk" + }, + "ipv4_address": { + "default": "mdi:ip-network" + }, + "ipv6_address": { + "default": "mdi:ip-network" + }, + "memory_free": { + "default": "mdi:memory" + }, + "memory_use": { + "default": "mdi:memory" + }, + "memory_use_percent": { + "default": "mdi:memory" + }, + "network_in": { + "default": "mdi:server-network" + }, + "network_out": { + "default": "mdi:server-network" + }, + "packets_in": { + "default": "mdi:server-network" + }, + "packets_out": { + "default": "mdi:server-network" + }, + "swap_free": { + "default": "mdi:harddisk" + }, + "swap_use": { + "default": "mdi:harddisk" + }, + "swap_use_percent": { + "default": "mdi:harddisk" + } + } + } +} diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index f32e089a1d9..94fc1719c4a 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -164,7 +164,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { placeholder="mount_point", native_unit_of_measurement=UnitOfInformation.GIBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round( entity.coordinator.data.disk_usage[entity.argument].free / 1024**3, 1 @@ -180,7 +179,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { placeholder="mount_point", native_unit_of_measurement=UnitOfInformation.GIBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round( entity.coordinator.data.disk_usage[entity.argument].used / 1024**3, 1 @@ -195,7 +193,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { translation_key="disk_use_percent", placeholder="mount_point", native_unit_of_measurement=PERCENTAGE, - icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: entity.coordinator.data.disk_usage[ entity.argument @@ -209,7 +206,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { key="ipv4_address", translation_key="ipv4_address", placeholder="ip_address", - icon="mdi:ip-network", mandatory_arg=True, value_fn=get_ip_address, add_to_update=lambda entity: ("addresses", ""), @@ -218,7 +214,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { key="ipv6_address", translation_key="ipv6_address", placeholder="ip_address", - icon="mdi:ip-network", mandatory_arg=True, value_fn=get_ip_address, add_to_update=lambda entity: ("addresses", ""), @@ -259,7 +254,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { translation_key="memory_free", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round( entity.coordinator.data.memory.available / 1024**2, 1 @@ -271,7 +265,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { translation_key="memory_use", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round( ( @@ -287,7 +280,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { key="memory_use_percent", translation_key="memory_use_percent", native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: entity.coordinator.data.memory.percent, add_to_update=lambda entity: ("memory", ""), @@ -298,7 +290,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { placeholder="interface", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:server-network", state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_network, @@ -310,7 +301,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { placeholder="interface", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:server-network", state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_network, @@ -320,7 +310,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { key="packets_in", translation_key="packets_in", placeholder="interface", - icon="mdi:server-network", state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_packets, @@ -330,7 +319,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { key="packets_out", translation_key="packets_out", placeholder="interface", - icon="mdi:server-network", state_class=SensorStateClass.TOTAL_INCREASING, mandatory_arg=True, value_fn=get_packets, @@ -395,7 +383,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { translation_key="swap_free", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round(entity.coordinator.data.swap.free / 1024**2, 1), add_to_update=lambda entity: ("swap", ""), @@ -405,7 +392,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { translation_key="swap_use", native_unit_of_measurement=UnitOfInformation.MEBIBYTES, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: round(entity.coordinator.data.swap.used / 1024**2, 1), add_to_update=lambda entity: ("swap", ""), @@ -414,7 +400,6 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { key="swap_use_percent", translation_key="swap_use_percent", native_unit_of_measurement=PERCENTAGE, - icon="mdi:harddisk", state_class=SensorStateClass.MEASUREMENT, value_fn=lambda entity: entity.coordinator.data.swap.percent, add_to_update=lambda entity: ("swap", ""), diff --git a/tests/components/systemmonitor/snapshots/test_sensor.ambr b/tests/components/systemmonitor/snapshots/test_sensor.ambr index 952aaaa7ec2..5374ea886d3 100644 --- a/tests/components/systemmonitor/snapshots/test_sensor.ambr +++ b/tests/components/systemmonitor/snapshots/test_sensor.ambr @@ -3,7 +3,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Disk free /', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': , }) @@ -15,7 +14,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Disk free /media/share', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': , }) @@ -26,7 +24,6 @@ # name: test_sensor[System Monitor Disk usage / - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Disk usage /', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': '%', }) @@ -37,7 +34,6 @@ # name: test_sensor[System Monitor Disk usage /home/notexist/ - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Disk usage /home/notexist/', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': '%', }) @@ -48,7 +44,6 @@ # name: test_sensor[System Monitor Disk usage /media/share - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Disk usage /media/share', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': '%', }) @@ -60,7 +55,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Disk use /', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': , }) @@ -72,7 +66,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Disk use /media/share', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': , }) @@ -83,7 +76,6 @@ # name: test_sensor[System Monitor IPv4 address eth0 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor IPv4 address eth0', - 'icon': 'mdi:ip-network', }) # --- # name: test_sensor[System Monitor IPv4 address eth0 - state] @@ -92,7 +84,6 @@ # name: test_sensor[System Monitor IPv4 address eth1 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor IPv4 address eth1', - 'icon': 'mdi:ip-network', }) # --- # name: test_sensor[System Monitor IPv4 address eth1 - state] @@ -101,7 +92,6 @@ # name: test_sensor[System Monitor IPv6 address eth0 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor IPv6 address eth0', - 'icon': 'mdi:ip-network', }) # --- # name: test_sensor[System Monitor IPv6 address eth0 - state] @@ -110,7 +100,6 @@ # name: test_sensor[System Monitor IPv6 address eth1 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor IPv6 address eth1', - 'icon': 'mdi:ip-network', }) # --- # name: test_sensor[System Monitor IPv6 address eth1 - state] @@ -159,7 +148,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Memory free', - 'icon': 'mdi:memory', 'state_class': , 'unit_of_measurement': , }) @@ -170,7 +158,6 @@ # name: test_sensor[System Monitor Memory usage - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Memory usage', - 'icon': 'mdi:memory', 'state_class': , 'unit_of_measurement': '%', }) @@ -182,7 +169,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Memory use', - 'icon': 'mdi:memory', 'state_class': , 'unit_of_measurement': , }) @@ -194,7 +180,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Network in eth0', - 'icon': 'mdi:server-network', 'state_class': , 'unit_of_measurement': , }) @@ -206,7 +191,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Network in eth1', - 'icon': 'mdi:server-network', 'state_class': , 'unit_of_measurement': , }) @@ -218,7 +202,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Network out eth0', - 'icon': 'mdi:server-network', 'state_class': , 'unit_of_measurement': , }) @@ -230,7 +213,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Network out eth1', - 'icon': 'mdi:server-network', 'state_class': , 'unit_of_measurement': , }) @@ -285,7 +267,6 @@ # name: test_sensor[System Monitor Packets in eth0 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Packets in eth0', - 'icon': 'mdi:server-network', 'state_class': , }) # --- @@ -295,7 +276,6 @@ # name: test_sensor[System Monitor Packets in eth1 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Packets in eth1', - 'icon': 'mdi:server-network', 'state_class': , }) # --- @@ -305,7 +285,6 @@ # name: test_sensor[System Monitor Packets out eth0 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Packets out eth0', - 'icon': 'mdi:server-network', 'state_class': , }) # --- @@ -315,7 +294,6 @@ # name: test_sensor[System Monitor Packets out eth1 - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Packets out eth1', - 'icon': 'mdi:server-network', 'state_class': , }) # --- @@ -366,7 +344,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Swap free', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': , }) @@ -377,7 +354,6 @@ # name: test_sensor[System Monitor Swap usage - attributes] ReadOnlyDict({ 'friendly_name': 'System Monitor Swap usage', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': '%', }) @@ -389,7 +365,6 @@ ReadOnlyDict({ 'device_class': 'data_size', 'friendly_name': 'System Monitor Swap use', - 'icon': 'mdi:harddisk', 'state_class': , 'unit_of_measurement': , }) diff --git a/tests/components/systemmonitor/test_sensor.py b/tests/components/systemmonitor/test_sensor.py index e67dd31a1de..11dd002c2f7 100644 --- a/tests/components/systemmonitor/test_sensor.py +++ b/tests/components/systemmonitor/test_sensor.py @@ -60,7 +60,6 @@ async def test_sensor( "state_class": "measurement", "unit_of_measurement": "MiB", "device_class": "data_size", - "icon": "mdi:memory", "friendly_name": "System Monitor Memory free", } From 4437b3bc9751f1f52348f328afbbcd4e898535f9 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 5 Mar 2024 11:55:22 +0100 Subject: [PATCH 0320/1691] Fix deCONZ light entity might not report a supported color mode (#112116) * Handle case where deCONZ light entity might not report a supported color mode * If in an unknown color mode set ColorMode.UNKNOWN * Fix comment from external discussion --- homeassistant/components/deconz/light.py | 1 + tests/components/deconz/test_light.py | 141 ++++++++++++++++++++++- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 086db2058c9..d618edc93f8 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -165,6 +165,7 @@ class DeconzBaseLight(DeconzDevice[_LightDeviceT], LightEntity): """Representation of a deCONZ light.""" TYPE = DOMAIN + _attr_color_mode = ColorMode.UNKNOWN def __init__(self, device: _LightDeviceT, gateway: DeconzGateway) -> None: """Set up light.""" diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 63c544ff189..07e284d65f2 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1380,10 +1380,147 @@ async def test_verify_group_supported_features( assert len(hass.states.async_all()) == 4 - assert hass.states.get("light.group").state == STATE_ON + group_state = hass.states.get("light.group") + assert group_state.state == STATE_ON + assert group_state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP assert ( - hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES] + group_state.attributes[ATTR_SUPPORTED_FEATURES] == LightEntityFeature.TRANSITION | LightEntityFeature.FLASH | LightEntityFeature.EFFECT ) + + +async def test_verify_group_color_mode_fallback( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_deconz_websocket +) -> None: + """Test that group supported features reflect what included lights support.""" + data = { + "groups": { + "43": { + "action": { + "alert": "none", + "bri": 127, + "colormode": "hs", + "ct": 0, + "effect": "none", + "hue": 0, + "on": True, + "sat": 127, + "scene": "4", + "xy": [0, 0], + }, + "devicemembership": [], + "etag": "4548e982c4cfff942f7af80958abb2a0", + "id": "43", + "lights": ["13"], + "name": "Opbergruimte", + "scenes": [ + { + "id": "1", + "lightcount": 1, + "name": "Scene Normaal deCONZ", + "transitiontime": 10, + }, + { + "id": "2", + "lightcount": 1, + "name": "Scene Fel deCONZ", + "transitiontime": 10, + }, + { + "id": "3", + "lightcount": 1, + "name": "Scene Gedimd deCONZ", + "transitiontime": 10, + }, + { + "id": "4", + "lightcount": 1, + "name": "Scene Uit deCONZ", + "transitiontime": 10, + }, + ], + "state": {"all_on": False, "any_on": False}, + "type": "LightGroup", + }, + }, + "lights": { + "13": { + "capabilities": { + "alerts": [ + "none", + "select", + "lselect", + "blink", + "breathe", + "okay", + "channelchange", + "finish", + "stop", + ], + "bri": {"min_dim_level": 5}, + }, + "config": { + "bri": {"execute_if_off": True, "startup": "previous"}, + "groups": ["43"], + "on": {"startup": "previous"}, + }, + "etag": "ca0ed7763eca37f5e6b24f6d46f8a518", + "hascolor": False, + "lastannounced": None, + "lastseen": "2024-03-02T20:08Z", + "manufacturername": "Signify Netherlands B.V.", + "modelid": "LWA001", + "name": "Opbergruimte Lamp Plafond", + "productid": "Philips-LWA001-1-A19DLv5", + "productname": "Hue white lamp", + "state": { + "alert": "none", + "bri": 76, + "effect": "none", + "on": False, + "reachable": True, + }, + "swconfigid": "87169548", + "swversion": "1.104.2", + "type": "Dimmable light", + "uniqueid": "00:17:88:01:08:11:22:33-01", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) + + group_state = hass.states.get("light.opbergruimte") + assert group_state.state == STATE_OFF + assert group_state.attributes[ATTR_COLOR_MODE] is None + + await mock_deconz_websocket( + data={ + "e": "changed", + "id": "13", + "r": "lights", + "state": { + "alert": "none", + "bri": 76, + "effect": "none", + "on": True, + "reachable": True, + }, + "t": "event", + "uniqueid": "00:17:88:01:08:11:22:33-01", + } + ) + await mock_deconz_websocket( + data={ + "e": "changed", + "id": "43", + "r": "groups", + "state": {"all_on": True, "any_on": True}, + "t": "event", + } + ) + group_state = hass.states.get("light.opbergruimte") + assert group_state.state == STATE_ON + assert group_state.attributes[ATTR_COLOR_MODE] is ColorMode.UNKNOWN From bd3add2cad4398677a9afe27c98384f264e423c4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 12:26:10 +0100 Subject: [PATCH 0321/1691] Add icon translations to Tautulli (#112305) * Add icon translations to Tautulli * Add icon translations to Tautulli --- homeassistant/components/tautulli/icons.json | 36 ++++++++++++++++++++ homeassistant/components/tautulli/sensor.py | 10 ------ 2 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/tautulli/icons.json diff --git a/homeassistant/components/tautulli/icons.json b/homeassistant/components/tautulli/icons.json new file mode 100644 index 00000000000..487f11e77f0 --- /dev/null +++ b/homeassistant/components/tautulli/icons.json @@ -0,0 +1,36 @@ +{ + "entity": { + "sensor": { + "watching_count": { + "default": "mdi:plex" + }, + "stream_count_direct_play": { + "default": "mdi:plex" + }, + "stream_count_direct_stream": { + "default": "mdi:plex" + }, + "stream_count_transcode": { + "default": "mdi:plex" + }, + "top_movies": { + "default": "mdi:movie-open" + }, + "top_tv": { + "default": "mdi:television" + }, + "top_user": { + "default": "mdi:walk" + }, + "state": { + "default": "mdi:plex" + }, + "progress": { + "default": "mdi:progress-clock" + }, + "transcode_decision": { + "default": "mdi:plex" + } + } + } +} diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index ca9de9df8de..d2d0c3595ac 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -59,14 +59,12 @@ class TautulliSensorEntityDescription( SENSOR_TYPES: tuple[TautulliSensorEntityDescription, ...] = ( TautulliSensorEntityDescription( - icon="mdi:plex", key="watching_count", translation_key="watching_count", native_unit_of_measurement="Watching", value_fn=lambda home_stats, activity, _: cast(int, activity.stream_count), ), TautulliSensorEntityDescription( - icon="mdi:plex", key="stream_count_direct_play", translation_key="stream_count_direct_play", entity_category=EntityCategory.DIAGNOSTIC, @@ -77,7 +75,6 @@ SENSOR_TYPES: tuple[TautulliSensorEntityDescription, ...] = ( ), ), TautulliSensorEntityDescription( - icon="mdi:plex", key="stream_count_direct_stream", translation_key="stream_count_direct_stream", entity_category=EntityCategory.DIAGNOSTIC, @@ -88,7 +85,6 @@ SENSOR_TYPES: tuple[TautulliSensorEntityDescription, ...] = ( ), ), TautulliSensorEntityDescription( - icon="mdi:plex", key="stream_count_transcode", translation_key="stream_count_transcode", entity_category=EntityCategory.DIAGNOSTIC, @@ -128,21 +124,18 @@ SENSOR_TYPES: tuple[TautulliSensorEntityDescription, ...] = ( value_fn=lambda home_stats, activity, _: cast(int, activity.wan_bandwidth), ), TautulliSensorEntityDescription( - icon="mdi:movie-open", key="top_movies", translation_key="top_movies", entity_registry_enabled_default=False, value_fn=get_top_stats, ), TautulliSensorEntityDescription( - icon="mdi:television", key="top_tv", translation_key="top_tv", entity_registry_enabled_default=False, value_fn=get_top_stats, ), TautulliSensorEntityDescription( - icon="mdi:walk", key=ATTR_TOP_USER, translation_key="top_user", entity_registry_enabled_default=False, @@ -167,7 +160,6 @@ class TautulliSessionSensorEntityDescription( SESSION_SENSOR_TYPES: tuple[TautulliSessionSensorEntityDescription, ...] = ( TautulliSessionSensorEntityDescription( - icon="mdi:plex", key="state", translation_key="state", value_fn=lambda session: cast(str, session.state), @@ -179,7 +171,6 @@ SESSION_SENSOR_TYPES: tuple[TautulliSessionSensorEntityDescription, ...] = ( value_fn=lambda session: cast(str, session.full_title), ), TautulliSessionSensorEntityDescription( - icon="mdi:progress-clock", key="progress", translation_key="progress", native_unit_of_measurement=PERCENTAGE, @@ -194,7 +185,6 @@ SESSION_SENSOR_TYPES: tuple[TautulliSessionSensorEntityDescription, ...] = ( value_fn=lambda session: cast(str, session.stream_video_resolution), ), TautulliSessionSensorEntityDescription( - icon="mdi:plex", key="transcode_decision", translation_key="transcode_decision", entity_category=EntityCategory.DIAGNOSTIC, From 6e99ca0d8a358d20aa9bbc292d38833174659d76 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 12:58:19 +0100 Subject: [PATCH 0322/1691] Add icon translations to Trafikverket Train (#112322) * Add icon translations to Trafikverket Train * Add icon translations to Trafikverket Train --- .../components/trafikverket_train/icons.json | 39 +++++++++++++++++++ .../components/trafikverket_train/sensor.py | 11 ------ .../snapshots/test_sensor.ambr | 13 ------- 3 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 homeassistant/components/trafikverket_train/icons.json diff --git a/homeassistant/components/trafikverket_train/icons.json b/homeassistant/components/trafikverket_train/icons.json new file mode 100644 index 00000000000..982e3f70b9c --- /dev/null +++ b/homeassistant/components/trafikverket_train/icons.json @@ -0,0 +1,39 @@ +{ + "entity": { + "sensor": { + "departure_time": { + "default": "mdi:clock" + }, + "departure_state": { + "default": "mdi:clock" + }, + "cancelled": { + "default": "mdi:alert" + }, + "delayed_time": { + "default": "mdi:clock" + }, + "planned_time": { + "default": "mdi:clock" + }, + "estimated_time": { + "default": "mdi:clock" + }, + "actual_time": { + "default": "mdi:clock" + }, + "other_info": { + "default": "mdi:information-variant" + }, + "deviation": { + "default": "mdi:alert" + }, + "departure_time_next": { + "default": "mdi:clock" + }, + "departure_time_next_next": { + "default": "mdi:clock" + } + } + } +} diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 68865a64cb5..3dd513e6650 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -43,14 +43,12 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_time", translation_key="departure_time", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.departure_time, ), TrafikverketSensorEntityDescription( key="departure_state", translation_key="departure_state", - icon="mdi:clock", value_fn=lambda data: data.departure_state, device_class=SensorDeviceClass.ENUM, options=["on_time", "delayed", "canceled"], @@ -58,13 +56,11 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="cancelled", translation_key="cancelled", - icon="mdi:alert", value_fn=lambda data: data.cancelled, ), TrafikverketSensorEntityDescription( key="delayed_time", translation_key="delayed_time", - icon="mdi:clock", device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, value_fn=lambda data: data.delayed_time, @@ -72,7 +68,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="planned_time", translation_key="planned_time", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.planned_time, entity_registry_enabled_default=False, @@ -80,7 +75,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="estimated_time", translation_key="estimated_time", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.estimated_time, entity_registry_enabled_default=False, @@ -88,7 +82,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="actual_time", translation_key="actual_time", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.actual_time, entity_registry_enabled_default=False, @@ -96,26 +89,22 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="other_info", translation_key="other_info", - icon="mdi:information-variant", value_fn=lambda data: data.other_info, ), TrafikverketSensorEntityDescription( key="deviation", translation_key="deviation", - icon="mdi:alert", value_fn=lambda data: data.deviation, ), TrafikverketSensorEntityDescription( key="departure_time_next", translation_key="departure_time_next", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.departure_time_next, ), TrafikverketSensorEntityDescription( key="departure_time_next_next", translation_key="departure_time_next_next", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.departure_time_next_next, ), diff --git a/tests/components/trafikverket_train/snapshots/test_sensor.ambr b/tests/components/trafikverket_train/snapshots/test_sensor.ambr index 6ea0168926e..96a38b828f4 100644 --- a/tests/components/trafikverket_train/snapshots/test_sensor.ambr +++ b/tests/components/trafikverket_train/snapshots/test_sensor.ambr @@ -5,7 +5,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -21,7 +20,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'enum', 'friendly_name': 'Stockholm C to Uppsala C Departure state', - 'icon': 'mdi:clock', 'options': list([ 'on_time', 'delayed', @@ -42,7 +40,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time next', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -58,7 +55,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time next after', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -74,7 +70,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Actual time', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -89,7 +84,6 @@ 'attributes': ReadOnlyDict({ 'attribution': 'Data provided by Trafikverket', 'friendly_name': 'Stockholm C to Uppsala C Other information', - 'icon': 'mdi:information-variant', 'product_filter': 'Regionaltåg', }), 'context': , @@ -105,7 +99,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time next', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -121,7 +114,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time next after', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -137,7 +129,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -153,7 +144,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'enum', 'friendly_name': 'Stockholm C to Uppsala C Departure state', - 'icon': 'mdi:clock', 'options': list([ 'on_time', 'delayed', @@ -174,7 +164,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Actual time', - 'icon': 'mdi:clock', 'product_filter': 'Regionaltåg', }), 'context': , @@ -189,7 +178,6 @@ 'attributes': ReadOnlyDict({ 'attribution': 'Data provided by Trafikverket', 'friendly_name': 'Stockholm C to Uppsala C Other information', - 'icon': 'mdi:information-variant', 'product_filter': 'Regionaltåg', }), 'context': , @@ -205,7 +193,6 @@ 'attribution': 'Data provided by Trafikverket', 'device_class': 'timestamp', 'friendly_name': 'Stockholm C to Uppsala C Departure time', - 'icon': 'mdi:clock', }), 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time_2', From d9e9b55a4bef1ab4003467d15989006958574ac8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 12:58:51 +0100 Subject: [PATCH 0323/1691] Add icon translations to Trafikverket Ferry (#112321) * Add icon translations to Trafikverket Ferry * Add icon translations to Trafikverket Ferry --- .../components/trafikverket_ferry/icons.json | 24 +++++++++++++++++++ .../components/trafikverket_ferry/sensor.py | 7 ------ .../trafikverket_ferry/test_sensor.py | 2 -- 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/trafikverket_ferry/icons.json diff --git a/homeassistant/components/trafikverket_ferry/icons.json b/homeassistant/components/trafikverket_ferry/icons.json new file mode 100644 index 00000000000..ca2536efcc5 --- /dev/null +++ b/homeassistant/components/trafikverket_ferry/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "sensor": { + "departure_time": { + "default": "mdi:clock" + }, + "departure_from": { + "default": "mdi:ferry" + }, + "departure_to": { + "default": "mdi:ferry" + }, + "departure_modified": { + "default": "mdi:clock" + }, + "departure_time_next": { + "default": "mdi:clock" + }, + "departure_time_next_next": { + "default": "mdi:clock" + } + } + } +} diff --git a/homeassistant/components/trafikverket_ferry/sensor.py b/homeassistant/components/trafikverket_ferry/sensor.py index cd0682c12bc..f21e9e494b1 100644 --- a/homeassistant/components/trafikverket_ferry/sensor.py +++ b/homeassistant/components/trafikverket_ferry/sensor.py @@ -28,7 +28,6 @@ ATTR_TO = "to_harbour" ATTR_MODIFIED_TIME = "modified_time" ATTR_OTHER_INFO = "other_info" -ICON = "mdi:ferry" SCAN_INTERVAL = timedelta(minutes=5) @@ -51,7 +50,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_time", translation_key="departure_time", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: as_utc(data["departure_time"]), info_fn=lambda data: cast(list[str], data["departure_information"]), @@ -59,21 +57,18 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_from", translation_key="departure_from", - icon="mdi:ferry", value_fn=lambda data: cast(str, data["departure_from"]), info_fn=lambda data: cast(list[str], data["departure_information"]), ), TrafikverketSensorEntityDescription( key="departure_to", translation_key="departure_to", - icon="mdi:ferry", value_fn=lambda data: cast(str, data["departure_to"]), info_fn=lambda data: cast(list[str], data["departure_information"]), ), TrafikverketSensorEntityDescription( key="departure_modified", translation_key="departure_modified", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: as_utc(data["departure_modified"]), info_fn=lambda data: cast(list[str], data["departure_information"]), @@ -82,7 +77,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_time_next", translation_key="departure_time_next", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: as_utc(data["departure_time_next"]), info_fn=None, @@ -91,7 +85,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_time_next_next", translation_key="departure_time_next_next", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: as_utc(data["departure_time_next_next"]), info_fn=None, diff --git a/tests/components/trafikverket_ferry/test_sensor.py b/tests/components/trafikverket_ferry/test_sensor.py index 84cb856a82d..7b3e0b30819 100644 --- a/tests/components/trafikverket_ferry/test_sensor.py +++ b/tests/components/trafikverket_ferry/test_sensor.py @@ -27,9 +27,7 @@ async def test_sensor( assert state1.state == "Harbor 1" assert state2.state == "Harbor 2" assert state3.state == str(dt_util.now().year + 1) + "-05-01T12:00:00+00:00" - assert state1.attributes["icon"] == "mdi:ferry" assert state1.attributes["other_information"] == [""] - assert state2.attributes["icon"] == "mdi:ferry" monkeypatch.setattr(get_ferries[0], "other_information", ["Nothing exiting"]) From 54a5820c3ee90c64e00d864ee49c564a940ec2eb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 12:59:30 +0100 Subject: [PATCH 0324/1691] Add icon translations to Trafikverket Camera (#112320) --- .../trafikverket_camera/binary_sensor.py | 1 - .../components/trafikverket_camera/icons.json | 29 +++++++++++++++++++ .../components/trafikverket_camera/sensor.py | 6 ---- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/trafikverket_camera/icons.json diff --git a/homeassistant/components/trafikverket_camera/binary_sensor.py b/homeassistant/components/trafikverket_camera/binary_sensor.py index b725f6d2f95..0927dc87718 100644 --- a/homeassistant/components/trafikverket_camera/binary_sensor.py +++ b/homeassistant/components/trafikverket_camera/binary_sensor.py @@ -36,7 +36,6 @@ class TVCameraSensorEntityDescription( BINARY_SENSOR_TYPE = TVCameraSensorEntityDescription( key="active", translation_key="active", - icon="mdi:camera-outline", value_fn=lambda data: data.data.active, ) diff --git a/homeassistant/components/trafikverket_camera/icons.json b/homeassistant/components/trafikverket_camera/icons.json new file mode 100644 index 00000000000..46b006ff48b --- /dev/null +++ b/homeassistant/components/trafikverket_camera/icons.json @@ -0,0 +1,29 @@ +{ + "entity": { + "binary_sensor": { + "active": { + "default": "mdi:camera-outline" + } + }, + "sensor": { + "direction": { + "default": "mdi:sign-direction" + }, + "modified": { + "default": "mdi:camera-retake-outline" + }, + "photo_time": { + "default": "mdi:camera-timer" + }, + "photo_url": { + "default": "mdi:camera-outline" + }, + "status": { + "default": "mdi:camera-outline" + }, + "camera_type": { + "default": "mdi:camera-iris" + } + } + } +} diff --git a/homeassistant/components/trafikverket_camera/sensor.py b/homeassistant/components/trafikverket_camera/sensor.py index 678c703307c..f6d136ef8e7 100644 --- a/homeassistant/components/trafikverket_camera/sensor.py +++ b/homeassistant/components/trafikverket_camera/sensor.py @@ -42,13 +42,11 @@ SENSOR_TYPES: tuple[TVCameraSensorEntityDescription, ...] = ( key="direction", translation_key="direction", native_unit_of_measurement=DEGREE, - icon="mdi:sign-direction", value_fn=lambda data: data.data.direction, ), TVCameraSensorEntityDescription( key="modified", translation_key="modified", - icon="mdi:camera-retake-outline", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.data.modified, entity_registry_enabled_default=False, @@ -56,28 +54,24 @@ SENSOR_TYPES: tuple[TVCameraSensorEntityDescription, ...] = ( TVCameraSensorEntityDescription( key="photo_time", translation_key="photo_time", - icon="mdi:camera-timer", device_class=SensorDeviceClass.TIMESTAMP, value_fn=lambda data: data.data.phototime, ), TVCameraSensorEntityDescription( key="photo_url", translation_key="photo_url", - icon="mdi:camera-outline", value_fn=lambda data: data.data.photourl, entity_registry_enabled_default=False, ), TVCameraSensorEntityDescription( key="status", translation_key="status", - icon="mdi:camera-outline", value_fn=lambda data: data.data.status, entity_registry_enabled_default=False, ), TVCameraSensorEntityDescription( key="camera_type", translation_key="camera_type", - icon="mdi:camera-iris", value_fn=lambda data: data.data.camera_type, entity_registry_enabled_default=False, ), From cd967c1af4832445d97e86cd3a0535573cc0d4d0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 13:00:10 +0100 Subject: [PATCH 0325/1691] Add icon translations to Trafikverket Weather station (#112323) --- .../trafikverket_weatherstation/icons.json | 21 +++++++++++++++++++ .../trafikverket_weatherstation/sensor.py | 5 ----- 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/trafikverket_weatherstation/icons.json diff --git a/homeassistant/components/trafikverket_weatherstation/icons.json b/homeassistant/components/trafikverket_weatherstation/icons.json new file mode 100644 index 00000000000..555d79ee084 --- /dev/null +++ b/homeassistant/components/trafikverket_weatherstation/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "sensor": { + "precipitation": { + "default": "mdi:weather-snowy-rainy" + }, + "wind_direction": { + "default": "mdi:flag-triangle" + }, + "wind_speed_max": { + "default": "mdi:weather-windy-variant" + }, + "measure_time": { + "default": "mdi:clock" + }, + "modified_time": { + "default": "mdi:clock" + } + } + } +} diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index 9c025237187..0553213862e 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -84,7 +84,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( key="precipitation", translation_key="precipitation", value_fn=lambda data: data.precipitationtype, - icon="mdi:weather-snowy-rainy", entity_registry_enabled_default=False, options=PRECIPITATION_TYPE, device_class=SensorDeviceClass.ENUM, @@ -94,7 +93,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( translation_key="wind_direction", value_fn=lambda data: data.winddirection, native_unit_of_measurement=DEGREE, - icon="mdi:flag-triangle", state_class=SensorStateClass.MEASUREMENT, ), TrafikverketSensorEntityDescription( @@ -110,7 +108,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( value_fn=lambda data: data.windforcemax or 0, native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND, device_class=SensorDeviceClass.WIND_SPEED, - icon="mdi:weather-windy-variant", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), @@ -133,7 +130,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( key="measure_time", translation_key="measure_time", value_fn=lambda data: data.measure_time, - icon="mdi:clock", entity_registry_enabled_default=False, device_class=SensorDeviceClass.TIMESTAMP, ), @@ -203,7 +199,6 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( key="modified_time", translation_key="modified_time", value_fn=lambda data: add_utc_timezone(data.modified_time), - icon="mdi:clock", entity_registry_enabled_default=False, device_class=SensorDeviceClass.TIMESTAMP, ), From de803349fba6c3fc63ce0bc67435a4e669283416 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 13:57:27 +0100 Subject: [PATCH 0326/1691] Add icon translations to Workday (#112359) --- homeassistant/components/workday/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/workday/icons.json diff --git a/homeassistant/components/workday/icons.json b/homeassistant/components/workday/icons.json new file mode 100644 index 00000000000..10d3c93a288 --- /dev/null +++ b/homeassistant/components/workday/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "check_date": "mdi:calendar-check" + } +} From a26e52aa0cc3d8d5c504ef309deebe0a5db545a6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 13:58:02 +0100 Subject: [PATCH 0327/1691] Add icon translations to Yale (#112361) --- homeassistant/components/yale_smart_alarm/button.py | 1 - homeassistant/components/yale_smart_alarm/icons.json | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/yale_smart_alarm/icons.json diff --git a/homeassistant/components/yale_smart_alarm/button.py b/homeassistant/components/yale_smart_alarm/button.py index 901cd1863ee..4521c4925b1 100644 --- a/homeassistant/components/yale_smart_alarm/button.py +++ b/homeassistant/components/yale_smart_alarm/button.py @@ -16,7 +16,6 @@ BUTTON_TYPES = ( ButtonEntityDescription( key="panic", translation_key="panic", - icon="mdi:alarm-light", ), ) diff --git a/homeassistant/components/yale_smart_alarm/icons.json b/homeassistant/components/yale_smart_alarm/icons.json new file mode 100644 index 00000000000..4cb5888a406 --- /dev/null +++ b/homeassistant/components/yale_smart_alarm/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "button": { + "panic": { + "default": "mdi:alarm-light" + } + } + } +} From afdb7d15cab385a32e63d896221cdceb0973af46 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 13:59:08 +0100 Subject: [PATCH 0328/1691] Add icon translations to YouTube (#112365) * Add icon translations to YouTube * Add icon translations to YouTube --- homeassistant/components/youtube/icons.json | 12 ++++++++++++ homeassistant/components/youtube/sensor.py | 2 -- tests/components/youtube/snapshots/test_sensor.ambr | 4 ---- 3 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/youtube/icons.json diff --git a/homeassistant/components/youtube/icons.json b/homeassistant/components/youtube/icons.json new file mode 100644 index 00000000000..34867058633 --- /dev/null +++ b/homeassistant/components/youtube/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "latest_upload": { + "default": "mdi:youtube" + }, + "subscribers": { + "default": "mdi:youtube-subscription" + } + } + } +} diff --git a/homeassistant/components/youtube/sensor.py b/homeassistant/components/youtube/sensor.py index d037a8c3c4b..e6205ad1646 100644 --- a/homeassistant/components/youtube/sensor.py +++ b/homeassistant/components/youtube/sensor.py @@ -45,7 +45,6 @@ SENSOR_TYPES = [ YouTubeSensorEntityDescription( key="latest_upload", translation_key="latest_upload", - icon="mdi:youtube", available_fn=lambda channel: channel[ATTR_LATEST_VIDEO] is not None, value_fn=lambda channel: channel[ATTR_LATEST_VIDEO][ATTR_TITLE], entity_picture_fn=lambda channel: channel[ATTR_LATEST_VIDEO][ATTR_THUMBNAIL], @@ -57,7 +56,6 @@ SENSOR_TYPES = [ YouTubeSensorEntityDescription( key="subscribers", translation_key="subscribers", - icon="mdi:youtube-subscription", native_unit_of_measurement="subscribers", available_fn=lambda _: True, value_fn=lambda channel: channel[ATTR_SUBSCRIBER_COUNT], diff --git a/tests/components/youtube/snapshots/test_sensor.ambr b/tests/components/youtube/snapshots/test_sensor.ambr index e3bfa4ec4bd..bea70279dd3 100644 --- a/tests/components/youtube/snapshots/test_sensor.ambr +++ b/tests/components/youtube/snapshots/test_sensor.ambr @@ -4,7 +4,6 @@ 'attributes': ReadOnlyDict({ 'entity_picture': 'https://i.ytimg.com/vi/wysukDrMdqU/maxresdefault.jpg', 'friendly_name': 'Google for Developers Latest upload', - 'icon': 'mdi:youtube', 'published_at': datetime.datetime(2023, 5, 11, 0, 20, 46, tzinfo=datetime.timezone.utc), 'video_id': 'wysukDrMdqU', }), @@ -20,7 +19,6 @@ 'attributes': ReadOnlyDict({ 'entity_picture': 'https://yt3.ggpht.com/fca_HuJ99xUxflWdex0XViC3NfctBFreIl8y4i9z411asnGTWY-Ql3MeH_ybA4kNaOjY7kyA=s800-c-k-c0x00ffffff-no-rj', 'friendly_name': 'Google for Developers Subscribers', - 'icon': 'mdi:youtube-subscription', 'unit_of_measurement': 'subscribers', }), 'context': , @@ -34,7 +32,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Google for Developers Latest upload', - 'icon': 'mdi:youtube', }), 'context': , 'entity_id': 'sensor.google_for_developers_latest_upload', @@ -48,7 +45,6 @@ 'attributes': ReadOnlyDict({ 'entity_picture': 'https://yt3.ggpht.com/fca_HuJ99xUxflWdex0XViC3NfctBFreIl8y4i9z411asnGTWY-Ql3MeH_ybA4kNaOjY7kyA=s800-c-k-c0x00ffffff-no-rj', 'friendly_name': 'Google for Developers Subscribers', - 'icon': 'mdi:youtube-subscription', 'unit_of_measurement': 'subscribers', }), 'context': , From f81b1819b0a21d31109c5100435757eb16d77c28 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:00:31 +0100 Subject: [PATCH 0329/1691] Add icon translations to YoLink (#112364) --- .../components/yolink/binary_sensor.py | 1 - homeassistant/components/yolink/icons.json | 31 +++++++++++++++++++ homeassistant/components/yolink/number.py | 7 ++--- homeassistant/components/yolink/sensor.py | 4 --- homeassistant/components/yolink/switch.py | 2 +- 5 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/yolink/icons.json diff --git a/homeassistant/components/yolink/binary_sensor.py b/homeassistant/components/yolink/binary_sensor.py index 0762a3b5c60..51c66be3e5f 100644 --- a/homeassistant/components/yolink/binary_sensor.py +++ b/homeassistant/components/yolink/binary_sensor.py @@ -49,7 +49,6 @@ SENSOR_DEVICE_TYPE = [ SENSOR_TYPES: tuple[YoLinkBinarySensorEntityDescription, ...] = ( YoLinkBinarySensorEntityDescription( key="door_state", - icon="mdi:door", device_class=BinarySensorDeviceClass.DOOR, value=lambda value: value == "open" if value is not None else None, exists_fn=lambda device: device.device_type == ATTR_DEVICE_DOOR_SENSOR, diff --git a/homeassistant/components/yolink/icons.json b/homeassistant/components/yolink/icons.json new file mode 100644 index 00000000000..ee9037c864a --- /dev/null +++ b/homeassistant/components/yolink/icons.json @@ -0,0 +1,31 @@ +{ + "entity": { + "number": { + "config_volume": { + "default": "mdi:volume-high" + } + }, + "sensor": { + "power_failure_alarm": { + "default": "mdi:flash" + }, + "power_failure_alarm_mute": { + "default": "mdi:volume-mute" + }, + "power_failure_alarm_volume": { + "default": "mdi:volume-high" + }, + "power_failure_alarm_beep": { + "default": "mdi:bullhorn" + } + }, + "switch": { + "manipulator_state": { + "default": "mdi:pipe" + } + } + }, + "services": { + "play_on_speaker_hub": "mdi:speaker" + } +} diff --git a/homeassistant/components/yolink/number.py b/homeassistant/components/yolink/number.py index a7ba89e1f6c..d1a52e6e46b 100644 --- a/homeassistant/components/yolink/number.py +++ b/homeassistant/components/yolink/number.py @@ -23,7 +23,7 @@ from .const import DOMAIN from .coordinator import YoLinkCoordinator from .entity import YoLinkEntity -OPTIONS_VALUME = "options_volume" +OPTIONS_VOLUME = "options_volume" @dataclass(frozen=True, kw_only=True) @@ -49,14 +49,13 @@ def get_volume_value(state: dict[str, Any]) -> int | None: DEVICE_CONFIG_DESCRIPTIONS: tuple[YoLinkNumberTypeConfigEntityDescription, ...] = ( YoLinkNumberTypeConfigEntityDescription( - key=OPTIONS_VALUME, + key=OPTIONS_VOLUME, translation_key="config_volume", native_min_value=1, native_max_value=16, mode=NumberMode.SLIDER, native_step=1.0, native_unit_of_measurement=None, - icon="mdi:volume-high", exists_fn=lambda device: device.device_type in SUPPORT_SET_VOLUME_DEVICES, should_update_entity=lambda value: value is not None, value=get_volume_value, @@ -124,7 +123,7 @@ class YoLinkNumberTypeConfigEntity(YoLinkEntity, NumberEntity): """Update the current value.""" if ( self.coordinator.device.device_type == ATTR_DEVICE_SPEAKER_HUB - and self.entity_description.key == OPTIONS_VALUME + and self.entity_description.key == OPTIONS_VOLUME ): await self.update_speaker_hub_volume(value) self._attr_native_value = value diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index 4401ac1aab5..80cbaf27bb3 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -168,7 +168,6 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( key="state", translation_key="power_failure_alarm", device_class=SensorDeviceClass.ENUM, - icon="mdi:flash", options=["normal", "alert", "off"], exists_fn=lambda device: device.device_type in ATTR_DEVICE_POWER_FAILURE_ALARM, ), @@ -176,7 +175,6 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( key="mute", translation_key="power_failure_alarm_mute", device_class=SensorDeviceClass.ENUM, - icon="mdi:volume-mute", options=["muted", "unmuted"], exists_fn=lambda device: device.device_type in ATTR_DEVICE_POWER_FAILURE_ALARM, value=lambda value: "muted" if value is True else "unmuted", @@ -185,7 +183,6 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( key="sound", translation_key="power_failure_alarm_volume", device_class=SensorDeviceClass.ENUM, - icon="mdi:volume-high", options=["low", "medium", "high"], exists_fn=lambda device: device.device_type in ATTR_DEVICE_POWER_FAILURE_ALARM, value=cvt_volume, @@ -194,7 +191,6 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( key="beep", translation_key="power_failure_alarm_beep", device_class=SensorDeviceClass.ENUM, - icon="mdi:bullhorn", options=["enabled", "disabled"], exists_fn=lambda device: device.device_type in ATTR_DEVICE_POWER_FAILURE_ALARM, value=lambda value: "enabled" if value is True else "disabled", diff --git a/homeassistant/components/yolink/switch.py b/homeassistant/components/yolink/switch.py index 69a958ba6d1..920ebd245cf 100644 --- a/homeassistant/components/yolink/switch.py +++ b/homeassistant/components/yolink/switch.py @@ -46,8 +46,8 @@ DEVICE_TYPES: tuple[YoLinkSwitchEntityDescription, ...] = ( ), YoLinkSwitchEntityDescription( key="manipulator_state", + translation_key="manipulator_state", name=None, - icon="mdi:pipe", exists_fn=lambda device: device.device_type == ATTR_DEVICE_MANIPULATOR, ), YoLinkSwitchEntityDescription( From 810dd0d643a592ad018e38603645d7a657c0f344 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:01:09 +0100 Subject: [PATCH 0330/1691] Add icon translations to Yardian (#112362) --- homeassistant/components/yardian/icons.json | 12 ++++++++++++ homeassistant/components/yardian/switch.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/yardian/icons.json diff --git a/homeassistant/components/yardian/icons.json b/homeassistant/components/yardian/icons.json new file mode 100644 index 00000000000..79bcc32adf2 --- /dev/null +++ b/homeassistant/components/yardian/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "switch": { + "switch": { + "default": "mdi:water" + } + } + }, + "services": { + "start_irrigation": "mdi:water" + } +} diff --git a/homeassistant/components/yardian/switch.py b/homeassistant/components/yardian/switch.py index 8598e4a8732..5c4d0b93e74 100644 --- a/homeassistant/components/yardian/switch.py +++ b/homeassistant/components/yardian/switch.py @@ -47,8 +47,8 @@ async def async_setup_entry( class YardianSwitch(CoordinatorEntity[YardianUpdateCoordinator], SwitchEntity): """Representation of a Yardian switch.""" - _attr_icon = "mdi:water" _attr_has_entity_name = True + _attr_translation_key = "switch" def __init__(self, coordinator: YardianUpdateCoordinator, zone_id) -> None: """Initialize a Yardian Switch Device.""" From 835cce4de9f6c4316688c0216a8ea935415b0272 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:03:51 +0100 Subject: [PATCH 0331/1691] Add icon translations to Xiaomi Aqara (#112360) --- homeassistant/components/xiaomi_aqara/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/xiaomi_aqara/icons.json diff --git a/homeassistant/components/xiaomi_aqara/icons.json b/homeassistant/components/xiaomi_aqara/icons.json new file mode 100644 index 00000000000..4975414833d --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "add_device": "mdi:cellphone-link", + "play_ringtone": "mdi:music", + "remove_device": "mdi:cellphone-link", + "stop_ringtone": "mdi:music-off" + } +} From 0b6d004b9c9ee8d3107a7d78bbe5cb08b56ef7c7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:05:13 +0100 Subject: [PATCH 0332/1691] Add icon translations to Weatherkit (#112354) --- homeassistant/components/weatherkit/icons.json | 9 +++++++++ homeassistant/components/weatherkit/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/weatherkit/icons.json diff --git a/homeassistant/components/weatherkit/icons.json b/homeassistant/components/weatherkit/icons.json new file mode 100644 index 00000000000..c555c58f420 --- /dev/null +++ b/homeassistant/components/weatherkit/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "pressure_trend": { + "default": "mdi:gauge" + } + } + } +} diff --git a/homeassistant/components/weatherkit/sensor.py b/homeassistant/components/weatherkit/sensor.py index 38b4a60cba5..28e828eee94 100644 --- a/homeassistant/components/weatherkit/sensor.py +++ b/homeassistant/components/weatherkit/sensor.py @@ -28,7 +28,6 @@ SENSORS = ( SensorEntityDescription( key="pressureTrend", device_class=SensorDeviceClass.ENUM, - icon="mdi:gauge", options=["rising", "falling", "steady"], translation_key="pressure_trend", ), From f0679f668961267b1b81b0860f3ec22f36a52a03 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:17:24 +0100 Subject: [PATCH 0333/1691] Add icon translations to Synology DSM (#112299) --- .../components/synology_dsm/icons.json | 81 +++++++++++++++++++ .../components/synology_dsm/sensor.py | 22 ----- .../components/synology_dsm/switch.py | 1 - 3 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/synology_dsm/icons.json diff --git a/homeassistant/components/synology_dsm/icons.json b/homeassistant/components/synology_dsm/icons.json new file mode 100644 index 00000000000..bbdbc9d2c96 --- /dev/null +++ b/homeassistant/components/synology_dsm/icons.json @@ -0,0 +1,81 @@ +{ + "entity": { + "sensor": { + "cpu_other_load": { + "default": "mdi:chip" + }, + "cpu_user_load": { + "default": "mdi:chip" + }, + "cpu_system_load": { + "default": "mdi:chip" + }, + "cpu_total_load": { + "default": "mdi:chip" + }, + "cpu_1min_load": { + "default": "mdi:chip" + }, + "cpu_5min_load": { + "default": "mdi:chip" + }, + "cpu_15min_load": { + "default": "mdi:chip" + }, + "memory_real_usage": { + "default": "mdi:memory" + }, + "memory_size": { + "default": "mdi:memory" + }, + "memory_cached": { + "default": "mdi:memory" + }, + "memory_available_swap": { + "default": "mdi:memory" + }, + "memory_available_real": { + "default": "mdi:memory" + }, + "memory_total_swap": { + "default": "mdi:memory" + }, + "memory_total_real": { + "default": "mdi:memory" + }, + "network_up": { + "default": "mdi:upload" + }, + "network_down": { + "default": "mdi:download" + }, + "volume_status": { + "default": "mdi:checkbox-marked-circle-outline" + }, + "volume_size_total": { + "default": "mdi:chart-pie" + }, + "volume_size_used": { + "default": "mdi:chart-pie" + }, + "volume_percentage_used": { + "default": "mdi:chart-pie" + }, + "disk_smart_status": { + "default": "mdi:checkbox-marked-circle-outline" + }, + "disk_status": { + "default": "mdi:checkbox-marked-circle-outline" + } + }, + "switch": { + "home_mode": { + "default": "mdi:home-account" + } + } + }, + "services": { + "reboot": "mdi:reboot", + "shutdown": "mdi:power" + } +} diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 76606303c93..2e88690fdc6 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -52,7 +52,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="cpu_other_load", translation_key="cpu_other_load", native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), @@ -61,7 +60,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="cpu_user_load", translation_key="cpu_user_load", native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -69,7 +67,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="cpu_system_load", translation_key="cpu_system_load", native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), @@ -78,7 +75,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="cpu_total_load", translation_key="cpu_total_load", native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -86,7 +82,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="cpu_1min_load", translation_key="cpu_1min_load", native_unit_of_measurement=ENTITY_UNIT_LOAD, - icon="mdi:chip", entity_registry_enabled_default=False, ), SynologyDSMSensorEntityDescription( @@ -94,21 +89,18 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="cpu_5min_load", translation_key="cpu_5min_load", native_unit_of_measurement=ENTITY_UNIT_LOAD, - icon="mdi:chip", ), SynologyDSMSensorEntityDescription( api_key=SynoCoreUtilization.API_KEY, key="cpu_15min_load", translation_key="cpu_15min_load", native_unit_of_measurement=ENTITY_UNIT_LOAD, - icon="mdi:chip", ), SynologyDSMSensorEntityDescription( api_key=SynoCoreUtilization.API_KEY, key="memory_real_usage", translation_key="memory_real_usage", native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -119,7 +111,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), @@ -131,7 +122,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), @@ -143,7 +133,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -154,7 +143,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -165,7 +153,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -176,7 +163,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.MEGABYTES, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -187,7 +173,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_RATE, - icon="mdi:upload", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -198,7 +183,6 @@ UTILISATION_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND, suggested_display_precision=1, device_class=SensorDeviceClass.DATA_RATE, - icon="mdi:download", state_class=SensorStateClass.MEASUREMENT, ), ) @@ -207,7 +191,6 @@ STORAGE_VOL_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( api_key=SynoStorage.API_KEY, key="volume_status", translation_key="volume_status", - icon="mdi:checkbox-marked-circle-outline", ), SynologyDSMSensorEntityDescription( api_key=SynoStorage.API_KEY, @@ -217,7 +200,6 @@ STORAGE_VOL_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.TERABYTES, suggested_display_precision=2, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:chart-pie", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), @@ -229,7 +211,6 @@ STORAGE_VOL_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfInformation.TERABYTES, suggested_display_precision=2, device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:chart-pie", state_class=SensorStateClass.MEASUREMENT, ), SynologyDSMSensorEntityDescription( @@ -237,7 +218,6 @@ STORAGE_VOL_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( key="volume_percentage_used", translation_key="volume_percentage_used", native_unit_of_measurement=PERCENTAGE, - icon="mdi:chart-pie", ), SynologyDSMSensorEntityDescription( api_key=SynoStorage.API_KEY, @@ -262,7 +242,6 @@ STORAGE_DISK_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( api_key=SynoStorage.API_KEY, key="disk_smart_status", translation_key="disk_smart_status", - icon="mdi:checkbox-marked-circle-outline", entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), @@ -270,7 +249,6 @@ STORAGE_DISK_SENSORS: tuple[SynologyDSMSensorEntityDescription, ...] = ( api_key=SynoStorage.API_KEY, key="disk_status", translation_key="disk_status", - icon="mdi:checkbox-marked-circle-outline", entity_category=EntityCategory.DIAGNOSTIC, ), SynologyDSMSensorEntityDescription( diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 77dc854fa3a..87700e9300d 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -34,7 +34,6 @@ SURVEILLANCE_SWITCH: tuple[SynologyDSMSwitchEntityDescription, ...] = ( api_key=SynoSurveillanceStation.HOME_MODE_API_KEY, key="home_mode", translation_key="home_mode", - icon="mdi:home-account", ), ) From 98fa4b3d3cbd8286e4bc221a86689088d0490b3c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:19:01 +0100 Subject: [PATCH 0334/1691] Add icon translations to Trend (#112325) --- homeassistant/components/trend/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/trend/icons.json diff --git a/homeassistant/components/trend/icons.json b/homeassistant/components/trend/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/trend/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From c8a30cfda75020d46fb9bba7da574f2d28ffad22 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:19:08 +0100 Subject: [PATCH 0335/1691] Add icon translations to TP-Link (#112317) --- homeassistant/components/tplink/icons.json | 16 ++++++++++++++++ homeassistant/components/tplink/switch.py | 6 ++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/tplink/icons.json diff --git a/homeassistant/components/tplink/icons.json b/homeassistant/components/tplink/icons.json new file mode 100644 index 00000000000..9b83b3abc85 --- /dev/null +++ b/homeassistant/components/tplink/icons.json @@ -0,0 +1,16 @@ +{ + "entity": { + "switch": { + "led": { + "default": "mdi:led-off", + "state": { + "on": "mdi:led-on" + } + } + } + }, + "services": { + "sequence_effect": "mdi:playlist-play", + "random_effect": "mdi:shuffle-variant" + } +} diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 3e81870d80f..8d64fc73147 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -61,7 +61,7 @@ class SmartPlugLedSwitch(CoordinatedTPLinkEntity, SwitchEntity): ) -> None: """Initialize the LED switch.""" super().__init__(device, coordinator) - self._attr_unique_id = f"{self.device.mac}_led" + self._attr_unique_id = f"{device.mac}_led" self._async_update_attrs() @async_refresh_after @@ -77,9 +77,7 @@ class SmartPlugLedSwitch(CoordinatedTPLinkEntity, SwitchEntity): @callback def _async_update_attrs(self) -> None: """Update the entity's attributes.""" - is_on = self.device.led - self._attr_is_on = is_on - self._attr_icon = "mdi:led-on" if is_on else "mdi:led-off" + self._attr_is_on = self.device.led @callback def _handle_coordinator_update(self) -> None: From d923b56f9f3838013f10f9c4531a5ade07fcb14f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:03 +0100 Subject: [PATCH 0336/1691] Add icon translations to Tesla Wall Connector (#112309) --- homeassistant/components/tesla_wall_connector/icons.json | 9 +++++++++ homeassistant/components/tesla_wall_connector/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/tesla_wall_connector/icons.json diff --git a/homeassistant/components/tesla_wall_connector/icons.json b/homeassistant/components/tesla_wall_connector/icons.json new file mode 100644 index 00000000000..995edd74b04 --- /dev/null +++ b/homeassistant/components/tesla_wall_connector/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "status": { + "default": "mdi:ev-station" + } + } + } +} diff --git a/homeassistant/components/tesla_wall_connector/sensor.py b/homeassistant/components/tesla_wall_connector/sensor.py index bba01f8692d..3681d448172 100644 --- a/homeassistant/components/tesla_wall_connector/sensor.py +++ b/homeassistant/components/tesla_wall_connector/sensor.py @@ -66,7 +66,6 @@ WALL_CONNECTOR_SENSORS = [ data[WALLCONNECTOR_DATA_VITALS].evse_state ), options=list(EVSE_STATE.values()), - icon="mdi:ev-station", ), WallConnectorSensorDescription( key="handle_temp_c", From a13304be5c8c36cfcee307ef4f08fdcdc18c245e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:12 +0100 Subject: [PATCH 0337/1691] Add icon translations to Tolo (#112313) --- .../components/tolo/binary_sensor.py | 2 - homeassistant/components/tolo/button.py | 1 - homeassistant/components/tolo/icons.json | 47 +++++++++++++++++++ homeassistant/components/tolo/number.py | 3 -- homeassistant/components/tolo/select.py | 1 - homeassistant/components/tolo/sensor.py | 4 -- 6 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/tolo/icons.json diff --git a/homeassistant/components/tolo/binary_sensor.py b/homeassistant/components/tolo/binary_sensor.py index 124cd45d78b..f8cb442c92f 100644 --- a/homeassistant/components/tolo/binary_sensor.py +++ b/homeassistant/components/tolo/binary_sensor.py @@ -34,7 +34,6 @@ class ToloFlowInBinarySensor(ToloSaunaCoordinatorEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_translation_key = "water_in_valve" _attr_device_class = BinarySensorDeviceClass.OPENING - _attr_icon = "mdi:water-plus-outline" def __init__( self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry @@ -56,7 +55,6 @@ class ToloFlowOutBinarySensor(ToloSaunaCoordinatorEntity, BinarySensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_translation_key = "water_out_valve" _attr_device_class = BinarySensorDeviceClass.OPENING - _attr_icon = "mdi:water-minus-outline" def __init__( self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry diff --git a/homeassistant/components/tolo/button.py b/homeassistant/components/tolo/button.py index 3b81477ab37..82daa79d1c1 100644 --- a/homeassistant/components/tolo/button.py +++ b/homeassistant/components/tolo/button.py @@ -30,7 +30,6 @@ class ToloLampNextColorButton(ToloSaunaCoordinatorEntity, ButtonEntity): """Button for switching to the next lamp color.""" _attr_entity_category = EntityCategory.CONFIG - _attr_icon = "mdi:palette" _attr_translation_key = "next_color" def __init__( diff --git a/homeassistant/components/tolo/icons.json b/homeassistant/components/tolo/icons.json new file mode 100644 index 00000000000..011df5788b2 --- /dev/null +++ b/homeassistant/components/tolo/icons.json @@ -0,0 +1,47 @@ +{ + "entity": { + "binary_sensor": { + "water_in_valve": { + "default": "mdi:water-plus-outline" + }, + "water_out_valve": { + "default": "mdi:water-minus-outline" + } + }, + "button": { + "next_color": { + "default": "mdi:palette" + } + }, + "number": { + "power_timer": { + "default": "mdi:power-settings" + }, + "salt_bath_timer": { + "default": "mdi:shaker-outline" + }, + "fan_timer": { + "default": "mdi:fan-auto" + } + }, + "select": { + "lamp_mode": { + "default": "mdi:lightbulb-multiple-outline" + } + }, + "sensor": { + "water_level": { + "default": "mdi:waves-arrow-up" + }, + "power_timer_remaining": { + "default": "mdi:power-settings" + }, + "salt_bath_timer_remaining": { + "default": "mdi:shaker-outline" + }, + "fan_timer_remaining": { + "default": "mdi:fan-auto" + } + } + } +} diff --git a/homeassistant/components/tolo/number.py b/homeassistant/components/tolo/number.py index b31b5102394..7cc5d6c0591 100644 --- a/homeassistant/components/tolo/number.py +++ b/homeassistant/components/tolo/number.py @@ -41,7 +41,6 @@ NUMBERS = ( ToloNumberEntityDescription( key="power_timer", translation_key="power_timer", - icon="mdi:power-settings", native_unit_of_measurement=UnitOfTime.MINUTES, native_max_value=POWER_TIMER_MAX, getter=lambda settings: settings.power_timer, @@ -50,7 +49,6 @@ NUMBERS = ( ToloNumberEntityDescription( key="salt_bath_timer", translation_key="salt_bath_timer", - icon="mdi:shaker-outline", native_unit_of_measurement=UnitOfTime.MINUTES, native_max_value=SALT_BATH_TIMER_MAX, getter=lambda settings: settings.salt_bath_timer, @@ -59,7 +57,6 @@ NUMBERS = ( ToloNumberEntityDescription( key="fan_timer", translation_key="fan_timer", - icon="mdi:fan-auto", native_unit_of_measurement=UnitOfTime.MINUTES, native_max_value=FAN_TIMER_MAX, getter=lambda settings: settings.fan_timer, diff --git a/homeassistant/components/tolo/select.py b/homeassistant/components/tolo/select.py index 8e4ecb47f48..26480aa3527 100644 --- a/homeassistant/components/tolo/select.py +++ b/homeassistant/components/tolo/select.py @@ -28,7 +28,6 @@ class ToloLampModeSelect(ToloSaunaCoordinatorEntity, SelectEntity): """TOLO Sauna lamp mode select.""" _attr_entity_category = EntityCategory.CONFIG - _attr_icon = "mdi:lightbulb-multiple-outline" _attr_options = [lamp_mode.name.lower() for lamp_mode in LampMode] _attr_translation_key = "lamp_mode" diff --git a/homeassistant/components/tolo/sensor.py b/homeassistant/components/tolo/sensor.py index ec57612a99f..f9ee5e34307 100644 --- a/homeassistant/components/tolo/sensor.py +++ b/homeassistant/components/tolo/sensor.py @@ -48,7 +48,6 @@ SENSORS = ( key="water_level", translation_key="water_level", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:waves-arrow-up", native_unit_of_measurement=PERCENTAGE, getter=lambda status: status.water_level_percent, availability_checker=None, @@ -66,7 +65,6 @@ SENSORS = ( key="power_timer_remaining", translation_key="power_timer_remaining", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:power-settings", native_unit_of_measurement=UnitOfTime.MINUTES, getter=lambda status: status.power_timer, availability_checker=lambda settings, status: status.power_on @@ -76,7 +74,6 @@ SENSORS = ( key="salt_bath_timer_remaining", translation_key="salt_bath_timer_remaining", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:shaker-outline", native_unit_of_measurement=UnitOfTime.MINUTES, getter=lambda status: status.salt_bath_timer, availability_checker=lambda settings, status: status.salt_bath_on @@ -86,7 +83,6 @@ SENSORS = ( key="fan_timer_remaining", translation_key="fan_timer_remaining", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:fan-auto", native_unit_of_measurement=UnitOfTime.MINUTES, getter=lambda status: status.fan_timer, availability_checker=lambda settings, status: status.fan_on From 7aca347db50c8b649b6b54177741d3fe03ecd498 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:23 +0100 Subject: [PATCH 0338/1691] Add icon translations to Tile (#112310) --- homeassistant/components/tile/device_tracker.py | 4 +--- homeassistant/components/tile/icons.json | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/tile/icons.json diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index df166d17db5..e4adf808029 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -37,8 +37,6 @@ ATTR_RING_STATE = "ring_state" ATTR_TILE_NAME = "tile_name" ATTR_VOIP_STATE = "voip_state" -DEFAULT_ICON = "mdi:view-grid" - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -83,9 +81,9 @@ async def async_setup_scanner( class TileDeviceTracker(CoordinatorEntity[DataUpdateCoordinator[None]], TrackerEntity): """Representation of a network infrastructure device.""" - _attr_icon = DEFAULT_ICON _attr_has_entity_name = True _attr_name = None + _attr_translation_key = "tile" def __init__( self, entry: ConfigEntry, coordinator: DataUpdateCoordinator[None], tile: Tile diff --git a/homeassistant/components/tile/icons.json b/homeassistant/components/tile/icons.json new file mode 100644 index 00000000000..f6f38fe8cef --- /dev/null +++ b/homeassistant/components/tile/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "device_tracker": { + "tile": { + "default": "mdi:view-grid" + } + } + } +} From 96fbaa40009b59240976a66fa3f6953ed3a6794c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:32 +0100 Subject: [PATCH 0339/1691] Add icon translations to Todoist (#112312) --- homeassistant/components/todoist/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/todoist/icons.json diff --git a/homeassistant/components/todoist/icons.json b/homeassistant/components/todoist/icons.json new file mode 100644 index 00000000000..d3b881d480c --- /dev/null +++ b/homeassistant/components/todoist/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "new_task": "mdi:checkbox-marked-circle-plus-outline" + } +} From 2c34c918fcbf963122471c7addf184849f47380b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:41 +0100 Subject: [PATCH 0340/1691] Add icon translations to Template (#112308) --- homeassistant/components/template/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/template/icons.json diff --git a/homeassistant/components/template/icons.json b/homeassistant/components/template/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/template/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 446314bb27d5f6578d0d5c84d2fb43eca11774fb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:50 +0100 Subject: [PATCH 0341/1691] Add icon translations to Syncthing (#112297) --- homeassistant/components/syncthing/const.py | 11 ----------- homeassistant/components/syncthing/icons.json | 16 ++++++++++++++++ homeassistant/components/syncthing/sensor.py | 13 +------------ 3 files changed, 17 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/syncthing/icons.json diff --git a/homeassistant/components/syncthing/const.py b/homeassistant/components/syncthing/const.py index a9ec0ad0375..0e5b2714be8 100644 --- a/homeassistant/components/syncthing/const.py +++ b/homeassistant/components/syncthing/const.py @@ -20,14 +20,3 @@ EVENTS = { "StateChanged": STATE_CHANGED_RECEIVED, "FolderPaused": FOLDER_PAUSED_RECEIVED, } - - -FOLDER_SENSOR_ICONS = { - "paused": "mdi:folder-clock", - "scanning": "mdi:folder-search", - "syncing": "mdi:folder-sync", - "idle": "mdi:folder", -} - -FOLDER_SENSOR_ALERT_ICON = "mdi:folder-alert" -FOLDER_SENSOR_DEFAULT_ICON = "mdi:folder" diff --git a/homeassistant/components/syncthing/icons.json b/homeassistant/components/syncthing/icons.json new file mode 100644 index 00000000000..0e86b6e4fa3 --- /dev/null +++ b/homeassistant/components/syncthing/icons.json @@ -0,0 +1,16 @@ +{ + "entity": { + "sensor": { + "syncthing": { + "default": "mdi:folder-alert", + "state": { + "unknown": "mdi:folder", + "idle": "mdi:folder", + "scanning": "mdi:folder-search", + "syncing": "mdi:folder-sync", + "paused": "mdi:folder-clock" + } + } + } + } +} diff --git a/homeassistant/components/syncthing/sensor.py b/homeassistant/components/syncthing/sensor.py index c88de91cae0..7b22017c9c1 100644 --- a/homeassistant/components/syncthing/sensor.py +++ b/homeassistant/components/syncthing/sensor.py @@ -13,9 +13,6 @@ from homeassistant.helpers.event import async_track_time_interval from .const import ( DOMAIN, FOLDER_PAUSED_RECEIVED, - FOLDER_SENSOR_ALERT_ICON, - FOLDER_SENSOR_DEFAULT_ICON, - FOLDER_SENSOR_ICONS, FOLDER_SUMMARY_RECEIVED, SCAN_INTERVAL, SERVER_AVAILABLE, @@ -57,6 +54,7 @@ class FolderSensor(SensorEntity): """A Syncthing folder sensor.""" _attr_should_poll = False + _attr_translation_key = "syncthing" STATE_ATTRIBUTES = { "errors": "errors", @@ -116,15 +114,6 @@ class FolderSensor(SensorEntity): """Could the device be accessed during the last update call.""" return self._state is not None - @property - def icon(self): - """Return the icon for this sensor.""" - if self._state is None: - return FOLDER_SENSOR_DEFAULT_ICON - if self.state in FOLDER_SENSOR_ICONS: - return FOLDER_SENSOR_ICONS[self.state] - return FOLDER_SENSOR_ALERT_ICON - @property def extra_state_attributes(self): """Return the state attributes.""" From d3f28718ecb82459b76b76f27e314e9b7627408e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:20:59 +0100 Subject: [PATCH 0342/1691] Add icon translations to Tedee (#112307) * Add icon translations to Tedee * Add icon translations to Tedee --- homeassistant/components/tedee/icons.json | 9 +++++++++ homeassistant/components/tedee/sensor.py | 1 - tests/components/tedee/snapshots/test_sensor.ambr | 3 +-- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/tedee/icons.json diff --git a/homeassistant/components/tedee/icons.json b/homeassistant/components/tedee/icons.json new file mode 100644 index 00000000000..3f98462b22f --- /dev/null +++ b/homeassistant/components/tedee/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "pullspring_duration": { + "default": "mdi:timer-lock-open" + } + } + } +} diff --git a/homeassistant/components/tedee/sensor.py b/homeassistant/components/tedee/sensor.py index 225686f6b18..cd01e9d04be 100644 --- a/homeassistant/components/tedee/sensor.py +++ b/homeassistant/components/tedee/sensor.py @@ -42,7 +42,6 @@ ENTITIES: tuple[TedeeSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:timer-lock-open", value_fn=lambda lock: lock.duration_pullspring, entity_category=EntityCategory.DIAGNOSTIC, ), diff --git a/tests/components/tedee/snapshots/test_sensor.ambr b/tests/components/tedee/snapshots/test_sensor.ambr index d1be2839826..5a05a7c7d22 100644 --- a/tests/components/tedee/snapshots/test_sensor.ambr +++ b/tests/components/tedee/snapshots/test_sensor.ambr @@ -59,7 +59,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:timer-lock-open', + 'original_icon': None, 'original_name': 'Pullspring duration', 'platform': 'tedee', 'previous_unique_id': None, @@ -89,7 +89,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'duration', 'friendly_name': 'Lock-1A2B Pullspring duration', - 'icon': 'mdi:timer-lock-open', 'state_class': , 'unit_of_measurement': , }), From bf596562bf30fa42996a2c377cad0e018fe3e866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Tue, 5 Mar 2024 14:25:15 +0100 Subject: [PATCH 0343/1691] Update aioairzone-cloud to v0.4.6 (#112198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds Cloud Push related functionality. Signed-off-by: Álvaro Fernández Rojas --- homeassistant/components/airzone_cloud/coordinator.py | 1 + homeassistant/components/airzone_cloud/manifest.json | 4 ++-- homeassistant/generated/integrations.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/airzone_cloud/conftest.py | 5 ++++- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/airzone_cloud/coordinator.py b/homeassistant/components/airzone_cloud/coordinator.py index 37b31c68ee7..5d15edffdf9 100644 --- a/homeassistant/components/airzone_cloud/coordinator.py +++ b/homeassistant/components/airzone_cloud/coordinator.py @@ -25,6 +25,7 @@ class AirzoneUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): def __init__(self, hass: HomeAssistant, airzone: AirzoneCloudApi) -> None: """Initialize.""" self.airzone = airzone + self.airzone.set_update_callback(self.async_set_updated_data) super().__init__( hass, diff --git a/homeassistant/components/airzone_cloud/manifest.json b/homeassistant/components/airzone_cloud/manifest.json index 3b8247d003c..14f02620c91 100644 --- a/homeassistant/components/airzone_cloud/manifest.json +++ b/homeassistant/components/airzone_cloud/manifest.json @@ -4,7 +4,7 @@ "codeowners": ["@Noltari"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airzone_cloud", - "iot_class": "cloud_polling", + "iot_class": "cloud_push", "loggers": ["aioairzone_cloud"], - "requirements": ["aioairzone-cloud==0.4.5"] + "requirements": ["aioairzone-cloud==0.4.6"] } diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 17115a55435..9c7a2001787 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -168,7 +168,7 @@ "airzone_cloud": { "integration_type": "hub", "config_flow": true, - "iot_class": "cloud_polling", + "iot_class": "cloud_push", "name": "Airzone Cloud" } } diff --git a/requirements_all.txt b/requirements_all.txt index e160e1be026..921987ad392 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -188,7 +188,7 @@ aio-georss-gdacs==0.9 aioairq==0.3.2 # homeassistant.components.airzone_cloud -aioairzone-cloud==0.4.5 +aioairzone-cloud==0.4.6 # homeassistant.components.airzone aioairzone==0.7.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c280c61f176..0e2024e0ccc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -167,7 +167,7 @@ aio-georss-gdacs==0.9 aioairq==0.3.2 # homeassistant.components.airzone_cloud -aioairzone-cloud==0.4.5 +aioairzone-cloud==0.4.6 # homeassistant.components.airzone aioairzone==0.7.6 diff --git a/tests/components/airzone_cloud/conftest.py b/tests/components/airzone_cloud/conftest.py index d810c808fde..a63ab18d7bc 100644 --- a/tests/components/airzone_cloud/conftest.py +++ b/tests/components/airzone_cloud/conftest.py @@ -11,5 +11,8 @@ def airzone_cloud_no_websockets(): with patch( "homeassistant.components.airzone_cloud.AirzoneCloudApi._update_websockets", return_value=False, - ), patch("aioairzone_cloud.websockets.AirzoneCloudIWS.connect", return_value=True): + ), patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.connect_installation_websockets", + return_value=None, + ): yield From b070bb25a68552598af4521295cca277407a6bdd Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 5 Mar 2024 13:30:53 +0000 Subject: [PATCH 0344/1691] Add support for System Bridge service responses (#100055) * Add support for System Bridge service responses * Update conversion to use dataclass * Update debug statements * Update debug message --- .../components/system_bridge/__init__.py | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index d999ed8a9f3..a7541021e0d 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from dataclasses import asdict import logging from systembridgeconnector.exceptions import ( @@ -29,7 +30,12 @@ from homeassistant.const import ( CONF_URL, Platform, ) -from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.core import ( + HomeAssistant, + ServiceCall, + ServiceResponse, + SupportsResponse, +) from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import ( config_validation as cv, @@ -194,52 +200,59 @@ async def async_setup_entry( raise vol.Invalid(f"Could not find device {device}") from exception raise vol.Invalid(f"Device {device} does not exist") - async def handle_open_path(call: ServiceCall) -> None: + async def handle_open_path(service_call: ServiceCall) -> ServiceResponse: """Handle the open path service call.""" - _LOGGER.debug("Open: %s", call.data) + _LOGGER.debug("Open path: %s", service_call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ - call.data[CONF_BRIDGE] + service_call.data[CONF_BRIDGE] ] - await coordinator.websocket_client.open_path( - OpenPath(path=call.data[CONF_PATH]) + response = await coordinator.websocket_client.open_path( + OpenPath(path=service_call.data[CONF_PATH]) ) + return asdict(response) - async def handle_power_command(call: ServiceCall) -> None: + async def handle_power_command(service_call: ServiceCall) -> ServiceResponse: """Handle the power command service call.""" - _LOGGER.debug("Power command: %s", call.data) + _LOGGER.debug("Power command: %s", service_call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ - call.data[CONF_BRIDGE] + service_call.data[CONF_BRIDGE] ] - await getattr( + response = await getattr( coordinator.websocket_client, - POWER_COMMAND_MAP[call.data[CONF_COMMAND]], + POWER_COMMAND_MAP[service_call.data[CONF_COMMAND]], )() + return asdict(response) - async def handle_open_url(call: ServiceCall) -> None: + async def handle_open_url(service_call: ServiceCall) -> ServiceResponse: """Handle the open url service call.""" - _LOGGER.debug("Open: %s", call.data) + _LOGGER.debug("Open URL: %s", service_call.data) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ - call.data[CONF_BRIDGE] + service_call.data[CONF_BRIDGE] ] - await coordinator.websocket_client.open_url(OpenUrl(url=call.data[CONF_URL])) + response = await coordinator.websocket_client.open_url( + OpenUrl(url=service_call.data[CONF_URL]) + ) + return asdict(response) - async def handle_send_keypress(call: ServiceCall) -> None: + async def handle_send_keypress(service_call: ServiceCall) -> ServiceResponse: """Handle the send_keypress service call.""" coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ - call.data[CONF_BRIDGE] + service_call.data[CONF_BRIDGE] ] - await coordinator.websocket_client.keyboard_keypress( - KeyboardKey(key=call.data[CONF_KEY]) + response = await coordinator.websocket_client.keyboard_keypress( + KeyboardKey(key=service_call.data[CONF_KEY]) ) + return asdict(response) - async def handle_send_text(call: ServiceCall) -> None: + async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: """Handle the send_keypress service call.""" coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ - call.data[CONF_BRIDGE] + service_call.data[CONF_BRIDGE] ] - await coordinator.websocket_client.keyboard_text( - KeyboardText(text=call.data[CONF_TEXT]) + response = await coordinator.websocket_client.keyboard_text( + KeyboardText(text=service_call.data[CONF_TEXT]) ) + return asdict(response) hass.services.async_register( DOMAIN, @@ -251,6 +264,7 @@ async def async_setup_entry( vol.Required(CONF_PATH): cv.string, }, ), + supports_response=SupportsResponse.ONLY, ) hass.services.async_register( @@ -263,6 +277,7 @@ async def async_setup_entry( vol.Required(CONF_COMMAND): vol.In(POWER_COMMAND_MAP), }, ), + supports_response=SupportsResponse.ONLY, ) hass.services.async_register( @@ -275,6 +290,7 @@ async def async_setup_entry( vol.Required(CONF_URL): cv.string, }, ), + supports_response=SupportsResponse.ONLY, ) hass.services.async_register( @@ -287,6 +303,7 @@ async def async_setup_entry( vol.Required(CONF_KEY): cv.string, }, ), + supports_response=SupportsResponse.ONLY, ) hass.services.async_register( @@ -299,6 +316,7 @@ async def async_setup_entry( vol.Required(CONF_TEXT): cv.string, }, ), + supports_response=SupportsResponse.ONLY, ) # Reload entry when its updated. From f3eb292c2def05cc0c2b9f3fc8b0f382d8a9f717 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 03:50:41 -1000 Subject: [PATCH 0345/1691] Remove async_entity_ids and get_entity_ids from entity registry (#112311) added in #112277 but not used yet. --- homeassistant/helpers/entity_registry.py | 9 --------- tests/helpers/test_entity_registry.py | 9 --------- 2 files changed, 18 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 51afe4fc740..eb0aace4265 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -511,10 +511,6 @@ class EntityRegistryItems(UserDict[str, RegistryEntry]): self._unindex_entry(key) super().__delitem__(key) - def get_entity_ids(self) -> ValuesView[str]: - """Return entity ids.""" - return self._index.values() - def get_device_ids(self) -> KeysView[str]: """Return device ids.""" return self._device_id_index.keys() @@ -620,11 +616,6 @@ class EntityRegistry: """Check if an entity_id is currently registered.""" return self.entities.get_entity_id((domain, platform, unique_id)) - @callback - def async_entity_ids(self) -> list[str]: - """Return entity ids.""" - return list(self.entities.get_entity_ids()) - @callback def async_device_ids(self) -> list[str]: """Return known device ids.""" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 1b0fbe51147..271494b60a6 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -91,7 +91,6 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: ) assert set(entity_registry.async_device_ids()) == {"mock-dev-id"} - assert set(entity_registry.async_entity_ids()) == {"light.hue_5678"} assert orig_entry == er.RegistryEntry( "light.hue_5678", @@ -163,7 +162,6 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: ) assert set(entity_registry.async_device_ids()) == {"new-mock-dev-id"} - assert set(entity_registry.async_entity_ids()) == {"light.hue_5678"} new_entry = entity_registry.async_get_or_create( "light", @@ -210,7 +208,6 @@ def test_get_or_create_updates_data(entity_registry: er.EntityRegistry) -> None: ) assert set(entity_registry.async_device_ids()) == set() - assert set(entity_registry.async_entity_ids()) == {"light.hue_5678"} def test_get_or_create_suggested_object_id_conflict_register( @@ -455,8 +452,6 @@ def test_async_get_entity_id(entity_registry: er.EntityRegistry) -> None: ) assert entity_registry.async_get_entity_id("light", "hue", "123") is None - assert set(entity_registry.async_entity_ids()) == {"light.hue_1234"} - async def test_updating_config_entry_id( hass: HomeAssistant, entity_registry: er.EntityRegistry @@ -1480,7 +1475,6 @@ def test_entity_registry_items() -> None: entities = er.EntityRegistryItems() assert entities.get_entity_id(("a", "b", "c")) is None assert entities.get_entry("abc") is None - assert set(entities.get_entity_ids()) == set() entry1 = er.RegistryEntry("test.entity1", "1234", "hue") entry2 = er.RegistryEntry("test.entity2", "2345", "hue") @@ -1494,7 +1488,6 @@ def test_entity_registry_items() -> None: assert entities.get_entry(entry1.id) is entry1 assert entities.get_entity_id(("test", "hue", "2345")) is entry2.entity_id assert entities.get_entry(entry2.id) is entry2 - assert set(entities.get_entity_ids()) == {"test.entity2", "test.entity1"} entities.pop("test.entity1") del entities["test.entity2"] @@ -1504,8 +1497,6 @@ def test_entity_registry_items() -> None: assert entities.get_entity_id(("test", "hue", "2345")) is None assert entities.get_entry(entry2.id) is None - assert set(entities.get_entity_ids()) == set() - async def test_disabled_by_str_not_allowed( hass: HomeAssistant, entity_registry: er.EntityRegistry From 385b29bdf5023ec9ffdb503e26a8afc953c9cdb4 Mon Sep 17 00:00:00 2001 From: fwestenberg <47930023+fwestenberg@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:55:59 +0100 Subject: [PATCH 0346/1691] Add Beaufort to wind_speed (#105795) * Add Beaufort to wind_speed * Add Bft to UnitOfSpeed * Update tests with Bft * Remove check for unit * Fix test_deprecated_constants * Test depricated constant Beaufort * Fix test_unit_system.py for Beaufort * Remove _DEPRECATED_SPEED_FEET_BEAUFORT * Remove maxsize from lru_cache * Update test_deprecated_constants * Update comment * Add missing docstring * Apply suggestions from code review --------- Co-authored-by: Erik Montnemery --- homeassistant/components/sensor/const.py | 2 + homeassistant/const.py | 1 + homeassistant/util/unit_conversion.py | 66 +++++++++++++++++++ tests/components/sensor/test_websocket_api.py | 13 +++- tests/test_const.py | 11 +++- tests/util/test_unit_conversion.py | 2 + tests/util/test_unit_system.py | 2 + 7 files changed, 95 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index 3dc8f878791..b5aab53e684 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -344,6 +344,7 @@ class SensorDeviceClass(StrEnum): - SI /metric: `mm/d`, `mm/h`, `m/s`, `km/h` - USCS / imperial: `in/d`, `in/h`, `ft/s`, `mph` - Nautical: `kn` + - Beaufort: `Beaufort` """ SULPHUR_DIOXIDE = "sulphur_dioxide" @@ -431,6 +432,7 @@ class SensorDeviceClass(StrEnum): - SI /metric: `m/s`, `km/h` - USCS / imperial: `ft/s`, `mph` - Nautical: `kn` + - Beaufort: `Beaufort` """ diff --git a/homeassistant/const.py b/homeassistant/const.py index 31b08becee6..3a8d1a09e85 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1214,6 +1214,7 @@ CONCENTRATION_PARTS_PER_BILLION: Final = "ppb" class UnitOfSpeed(StrEnum): """Speed units.""" + BEAUFORT = "Beaufort" FEET_PER_SECOND = "ft/s" METERS_PER_SECOND = "m/s" KILOMETERS_PER_HOUR = "km/h" diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index fe1974f2bee..18318f89da4 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -334,6 +334,7 @@ class SpeedConverter(BaseUnitConverter): UnitOfSpeed.KNOTS: _HRS_TO_SECS / _NAUTICAL_MILE_TO_M, UnitOfSpeed.METERS_PER_SECOND: 1, UnitOfSpeed.MILES_PER_HOUR: _HRS_TO_SECS / _MILE_TO_M, + UnitOfSpeed.BEAUFORT: 1, } VALID_UNITS = { UnitOfVolumetricFlux.INCHES_PER_DAY, @@ -345,8 +346,73 @@ class SpeedConverter(BaseUnitConverter): UnitOfSpeed.KNOTS, UnitOfSpeed.METERS_PER_SECOND, UnitOfSpeed.MILES_PER_HOUR, + UnitOfSpeed.BEAUFORT, } + @classmethod + @lru_cache + def converter_factory( + cls, from_unit: str | None, to_unit: str | None + ) -> Callable[[float], float]: + """Return a function to convert a speed from one unit to another.""" + if from_unit == to_unit: + # Return a function that does nothing. This is not + # in _converter_factory because we do not want to wrap + # it with the None check in converter_factory_allow_none. + return lambda value: value + + return cls._converter_factory(from_unit, to_unit) + + @classmethod + @lru_cache + def converter_factory_allow_none( + cls, from_unit: str | None, to_unit: str | None + ) -> Callable[[float | None], float | None]: + """Return a function to convert a speed from one unit to another which allows None.""" + if from_unit == to_unit: + # Return a function that does nothing. This is not + # in _converter_factory because we do not want to wrap + # it with the None check in this case. + return lambda value: value + + convert = cls._converter_factory(from_unit, to_unit) + return lambda value: None if value is None else convert(value) + + @classmethod + def _converter_factory( + cls, from_unit: str | None, to_unit: str | None + ) -> Callable[[float], float]: + """Convert a speed from one unit to another, eg. 14m/s will return 7Bft.""" + # We cannot use the implementation from BaseUnitConverter here because the + # Beaufort scale is not a constant value to divide or multiply with. + if ( + from_unit not in SpeedConverter.VALID_UNITS + or to_unit not in SpeedConverter.VALID_UNITS + ): + raise HomeAssistantError( + UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, cls.UNIT_CLASS) + ) + + if from_unit == UnitOfSpeed.BEAUFORT: + to_ratio = cls._UNIT_CONVERSION[to_unit] + return lambda val: cls._beaufort_to_ms(val) * to_ratio + if to_unit == UnitOfSpeed.BEAUFORT: + from_ratio = cls._UNIT_CONVERSION[from_unit] + return lambda val: cls._ms_to_beaufort(val / from_ratio) + + from_ratio, to_ratio = cls._get_from_to_ratio(from_unit, to_unit) + return lambda val: (val / from_ratio) * to_ratio + + @classmethod + def _ms_to_beaufort(cls, ms: float) -> float: + """Convert a speed in m/s to Beaufort.""" + return float(round(((ms / 0.836) ** 2) ** (1 / 3))) + + @classmethod + def _beaufort_to_ms(cls, beaufort: float) -> float: + """Convert a speed in Beaufort to m/s.""" + return float(0.836 * beaufort ** (3 / 2)) + class TemperatureConverter(BaseUnitConverter): """Utility to convert temperature values.""" diff --git a/tests/components/sensor/test_websocket_api.py b/tests/components/sensor/test_websocket_api.py index bd0a68598e1..98d07b599fe 100644 --- a/tests/components/sensor/test_websocket_api.py +++ b/tests/components/sensor/test_websocket_api.py @@ -30,7 +30,18 @@ async def test_device_class_units( msg = await client.receive_json() assert msg["success"] assert msg["result"] == { - "units": ["ft/s", "in/d", "in/h", "km/h", "kn", "m/s", "mm/d", "mm/h", "mph"] + "units": [ + "Beaufort", + "ft/s", + "in/d", + "in/h", + "km/h", + "kn", + "m/s", + "mm/d", + "mm/h", + "mph", + ] } # Device class with units which include `None` diff --git a/tests/test_const.py b/tests/test_const.py index 7ca4812ca8e..b43f677ba8f 100644 --- a/tests/test_const.py +++ b/tests/test_const.py @@ -131,7 +131,16 @@ def test_all() -> None: ], "PRECIPITATION_", ) - + _create_tuples(const.UnitOfSpeed, "SPEED_") + + _create_tuples( + [ + const.UnitOfSpeed.FEET_PER_SECOND, + const.UnitOfSpeed.METERS_PER_SECOND, + const.UnitOfSpeed.KILOMETERS_PER_HOUR, + const.UnitOfSpeed.KNOTS, + const.UnitOfSpeed.MILES_PER_HOUR, + ], + "SPEED_", + ) + _create_tuples( [ const.UnitOfVolumetricFlux.MILLIMETERS_PER_DAY, diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index d4649671f47..be8f51af6f9 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -413,6 +413,8 @@ _CONVERTED_VALUE: dict[ (5, UnitOfSpeed.KNOTS, 2.57222, UnitOfSpeed.METERS_PER_SECOND), # 5 ft/s * 0.3048 m/ft = 1.524 m/s (5, UnitOfSpeed.FEET_PER_SECOND, 1.524, UnitOfSpeed.METERS_PER_SECOND), + # float(round(((20.7 m/s / 0.836) ** 2) ** (1 / 3))) = 8.0Bft + (20.7, UnitOfSpeed.METERS_PER_SECOND, 8.0, UnitOfSpeed.BEAUFORT), ], TemperatureConverter: [ (100, UnitOfTemperature.CELSIUS, 212, UnitOfTemperature.FAHRENHEIT), diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 44b287bd05d..5a199783346 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -515,6 +515,7 @@ UNCONVERTED_UNITS_METRIC_SYSTEM = { UnitOfPressure.PA, ), SensorDeviceClass.SPEED: ( + UnitOfSpeed.BEAUFORT, UnitOfSpeed.KILOMETERS_PER_HOUR, UnitOfSpeed.KNOTS, UnitOfSpeed.METERS_PER_SECOND, @@ -723,6 +724,7 @@ UNCONVERTED_UNITS_US_SYSTEM = { ), SensorDeviceClass.PRESSURE: (UnitOfPressure.INHG, UnitOfPressure.PSI), SensorDeviceClass.SPEED: ( + UnitOfSpeed.BEAUFORT, UnitOfSpeed.FEET_PER_SECOND, UnitOfSpeed.KNOTS, UnitOfSpeed.MILES_PER_HOUR, From d0a66b326592788dc0d9ac7bf4f1721aa3c6b2cf Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 14:59:09 +0100 Subject: [PATCH 0347/1691] Add icon translations to WebOS TV (#112355) * Add icon translations to WebOS TV * Update homeassistant/components/webostv/icons.json Co-authored-by: Shay Levy * Update homeassistant/components/webostv/icons.json Co-authored-by: Shay Levy --------- Co-authored-by: Shay Levy --- homeassistant/components/webostv/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/webostv/icons.json diff --git a/homeassistant/components/webostv/icons.json b/homeassistant/components/webostv/icons.json new file mode 100644 index 00000000000..deb9729a99f --- /dev/null +++ b/homeassistant/components/webostv/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "button": "mdi:button-pointer", + "command": "mdi:console", + "select_sound_output": "mdi:volume-source" + } +} From 4fcc446255d682e6121073b5c4f50f85c7da973c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 15:03:22 +0100 Subject: [PATCH 0348/1691] Add icon translations to Watttime (#112351) --- homeassistant/components/watttime/icons.json | 12 ++++++++++++ homeassistant/components/watttime/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/watttime/icons.json diff --git a/homeassistant/components/watttime/icons.json b/homeassistant/components/watttime/icons.json new file mode 100644 index 00000000000..e4c9cfa4f5b --- /dev/null +++ b/homeassistant/components/watttime/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "marginal_operating_emissions_rate": { + "default": "mdi:blur" + }, + "relative_marginal_emissions_intensity": { + "default": "mdi:blur" + } + } + } +} diff --git a/homeassistant/components/watttime/sensor.py b/homeassistant/components/watttime/sensor.py index ca5b0d06fa2..93560cef808 100644 --- a/homeassistant/components/watttime/sensor.py +++ b/homeassistant/components/watttime/sensor.py @@ -38,14 +38,12 @@ REALTIME_EMISSIONS_SENSOR_DESCRIPTIONS = ( SensorEntityDescription( key=SENSOR_TYPE_REALTIME_EMISSIONS_MOER, translation_key="marginal_operating_emissions_rate", - icon="mdi:blur", native_unit_of_measurement=f"{UnitOfMass.POUNDS} CO2/MWh", state_class=SensorStateClass.MEASUREMENT, ), SensorEntityDescription( key=SENSOR_TYPE_REALTIME_EMISSIONS_PERCENT, translation_key="relative_marginal_emissions_intensity", - icon="mdi:blur", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), From dca7083026f7a653d57a26fceb609ec1f95962ba Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 15:32:12 +0100 Subject: [PATCH 0349/1691] Add icon translations to Uptimerobot (#112336) * Add icon translations to Uptimerobot * Add icon translations to Uptimerobot --- .../components/uptimerobot/icons.json | 20 ++++++++++++++ .../components/uptimerobot/sensor.py | 27 +++++-------------- .../components/uptimerobot/switch.py | 2 +- tests/components/uptimerobot/test_sensor.py | 9 +++---- tests/components/uptimerobot/test_switch.py | 5 ++-- 5 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 homeassistant/components/uptimerobot/icons.json diff --git a/homeassistant/components/uptimerobot/icons.json b/homeassistant/components/uptimerobot/icons.json new file mode 100644 index 00000000000..36bd3732f41 --- /dev/null +++ b/homeassistant/components/uptimerobot/icons.json @@ -0,0 +1,20 @@ +{ + "entity": { + "sensor": { + "monitor_status": { + "default": "mdi:television", + "state": { + "pause": "mdi:television-pause", + "up": "mdi:television-shimmer", + "seems_down": "mdi:television-off", + "down": "mdi:television-off" + } + } + }, + "switch": { + "monitor_status": { + "default": "mdi:cog" + } + } + } +} diff --git a/homeassistant/components/uptimerobot/sensor.py b/homeassistant/components/uptimerobot/sensor.py index 4ae40bf4134..c1e4db0fef7 100644 --- a/homeassistant/components/uptimerobot/sensor.py +++ b/homeassistant/components/uptimerobot/sensor.py @@ -1,8 +1,6 @@ """UptimeRobot sensor platform.""" from __future__ import annotations -from typing import TypedDict - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -17,20 +15,12 @@ from .const import DOMAIN from .coordinator import UptimeRobotDataUpdateCoordinator from .entity import UptimeRobotEntity - -class StatusValue(TypedDict): - """Sensor details.""" - - value: str - icon: str - - SENSORS_INFO = { - 0: StatusValue(value="pause", icon="mdi:television-pause"), - 1: StatusValue(value="not_checked_yet", icon="mdi:television"), - 2: StatusValue(value="up", icon="mdi:television-shimmer"), - 8: StatusValue(value="seems_down", icon="mdi:television-off"), - 9: StatusValue(value="down", icon="mdi:television-off"), + 0: "pause", + 1: "not_checked_yet", + 2: "up", + 8: "seems_down", + 9: "down", } @@ -63,9 +53,4 @@ class UptimeRobotSensor(UptimeRobotEntity, SensorEntity): @property def native_value(self) -> str: """Return the status of the monitor.""" - return SENSORS_INFO[self.monitor.status]["value"] - - @property - def icon(self) -> str: - """Return the status of the monitor.""" - return SENSORS_INFO[self.monitor.status]["icon"] + return SENSORS_INFO[self.monitor.status] diff --git a/homeassistant/components/uptimerobot/switch.py b/homeassistant/components/uptimerobot/switch.py index 3406c9fe21a..e76418e3300 100644 --- a/homeassistant/components/uptimerobot/switch.py +++ b/homeassistant/components/uptimerobot/switch.py @@ -40,7 +40,7 @@ async def async_setup_entry( class UptimeRobotSwitch(UptimeRobotEntity, SwitchEntity): """Representation of a UptimeRobot switch.""" - _attr_icon = "mdi:cog" + _attr_translation_key = "monitor_status" @property def is_on(self) -> bool: diff --git a/tests/components/uptimerobot/test_sensor.py b/tests/components/uptimerobot/test_sensor.py index 110ea07c202..8cee33c1052 100644 --- a/tests/components/uptimerobot/test_sensor.py +++ b/tests/components/uptimerobot/test_sensor.py @@ -19,17 +19,14 @@ from .common import ( from tests.common import async_fire_time_changed -SENSOR_ICON = "mdi:television-shimmer" - async def test_presentation(hass: HomeAssistant) -> None: - """Test the presenstation of UptimeRobot sensors.""" + """Test the presentation of UptimeRobot sensors.""" await setup_uptimerobot_integration(hass) entity = hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY) assert entity.state == STATE_UP - assert entity.attributes["icon"] == SENSOR_ICON assert entity.attributes["target"] == MOCK_UPTIMEROBOT_MONITOR["url"] assert entity.attributes["device_class"] == SensorDeviceClass.ENUM assert entity.attributes["options"] == [ @@ -41,8 +38,8 @@ async def test_presentation(hass: HomeAssistant) -> None: ] -async def test_unaviable_on_update_failure(hass: HomeAssistant) -> None: - """Test entity unaviable on update failure.""" +async def test_unavailable_on_update_failure(hass: HomeAssistant) -> None: + """Test entity unavailable on update failure.""" await setup_uptimerobot_integration(hass) entity = hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY) diff --git a/tests/components/uptimerobot/test_switch.py b/tests/components/uptimerobot/test_switch.py index f10a202f208..7929879db19 100644 --- a/tests/components/uptimerobot/test_switch.py +++ b/tests/components/uptimerobot/test_switch.py @@ -28,18 +28,17 @@ from tests.common import MockConfigEntry async def test_presentation(hass: HomeAssistant) -> None: - """Test the presenstation of UptimeRobot sensors.""" + """Test the presentation of UptimeRobot switches.""" await setup_uptimerobot_integration(hass) entity = hass.states.get(UPTIMEROBOT_SWITCH_TEST_ENTITY) assert entity.state == STATE_ON - assert entity.attributes["icon"] == "mdi:cog" assert entity.attributes["target"] == MOCK_UPTIMEROBOT_MONITOR["url"] async def test_switch_off(hass: HomeAssistant) -> None: - """Test entity unaviable on update failure.""" + """Test entity unavailable on update failure.""" mock_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) mock_entry.add_to_hass(hass) From 390f5822fea1e4b99aaf8328e40db682fcbb55bd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 04:58:43 -1000 Subject: [PATCH 0350/1691] Initialize triggers eagerly (#112294) Most of these will never suspend and do not need to be scheduled as tasks on the event loop --- homeassistant/helpers/trigger.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index 2483806abc1..d98b24f9afd 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -28,6 +28,7 @@ from homeassistant.core import ( ) from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import IntegrationNotFound, async_get_integration +from homeassistant.util.async_ import create_eager_task from .typing import ConfigType, TemplateVarsType @@ -305,7 +306,7 @@ async def async_initialize_triggers( variables: TemplateVarsType = None, ) -> CALLBACK_TYPE | None: """Initialize triggers.""" - triggers: list[Coroutine[Any, Any, CALLBACK_TYPE]] = [] + triggers: list[asyncio.Task[CALLBACK_TYPE]] = [] for idx, conf in enumerate(trigger_config): # Skip triggers that are not enabled if not conf.get(CONF_ENABLED, True): @@ -325,8 +326,10 @@ async def async_initialize_triggers( ) triggers.append( - platform.async_attach_trigger( - hass, conf, _trigger_action_wrapper(hass, action, conf), info + create_eager_task( + platform.async_attach_trigger( + hass, conf, _trigger_action_wrapper(hass, action, conf), info + ) ) ) From 7cb8a8bbc9c1ce438a907d9f29ec80bcb84dea29 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 04:59:52 -1000 Subject: [PATCH 0351/1691] Migrate remaining calls in config modules to async_get_component (#112293) * Migrate remaining calls in config modules to async_get_component There were a few cases that were still using get_component that could have done blocking I/O in the event loop, although it was unlikely. The caching check in async_get_component has been moved up to avoid creating the future if the module is already in the cache * fix one more --- homeassistant/config.py | 2 +- homeassistant/config_entries.py | 12 ++++++------ homeassistant/loader.py | 27 +++++++++++++++++++-------- tests/test_loader.py | 7 +++---- tests/test_setup.py | 2 +- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 8962ff8e7a5..bd6d14f8c10 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1083,7 +1083,7 @@ async def merge_packages_config( integration = await async_get_integration_with_requirements( hass, domain ) - component = integration.get_component() + component = await integration.async_get_component() except LOAD_EXCEPTIONS as exc: _log_pkg_error( hass, diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 0dae0359d8a..e6fe5d5d70b 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -490,7 +490,7 @@ class ConfigEntry: hass, self.domain ) try: - component = integration.get_component() + component = await integration.async_get_component() except ImportError as err: _LOGGER.error( "Error importing integration %s to set up %s configuration entry: %s", @@ -677,7 +677,7 @@ class ConfigEntry: self._async_set_state(hass, ConfigEntryState.NOT_LOADED, None) return True - component = integration.get_component() + component = await integration.async_get_component() if integration.domain == self.domain: if not self.state.recoverable: @@ -734,7 +734,7 @@ class ConfigEntry: # entry. return - component = integration.get_component() + component = await integration.async_get_component() if not hasattr(component, "async_remove_entry"): return try: @@ -783,7 +783,7 @@ class ConfigEntry: if not (integration := self._integration_for_domain): integration = await loader.async_get_integration(hass, self.domain) - component = integration.get_component() + component = await integration.async_get_component() supports_migrate = hasattr(component, "async_migrate_entry") if not supports_migrate: if same_major_version: @@ -2497,14 +2497,14 @@ def _handle_entry_updated_filter(event: Event) -> bool: async def support_entry_unload(hass: HomeAssistant, domain: str) -> bool: """Test if a domain supports entry unloading.""" integration = await loader.async_get_integration(hass, domain) - component = integration.get_component() + component = await integration.async_get_component() return hasattr(component, "async_unload_entry") async def support_remove_from_device(hass: HomeAssistant, domain: str) -> bool: """Test if a domain supports being removed from a device.""" integration = await loader.async_get_integration(hass, domain) - component = integration.get_component() + component = await integration.async_get_component() return hasattr(component, "async_remove_config_entry_device") diff --git a/homeassistant/loader.py b/homeassistant/loader.py index a7798f7b5e2..95b00547fb8 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -901,6 +901,10 @@ class Integration: and will check if import_executor is set and load it in the executor, otherwise it will load it in the event loop. """ + domain = self.domain + if domain in (cache := self._cache): + return cache[domain] + if self._component_future: return await self._component_future @@ -914,7 +918,7 @@ class Integration: or (self.config_flow and f"{self.pkg_path}.config_flow" not in sys.modules) ) if not load_executor: - comp = self.get_component() + comp = self._get_component() if debug: _LOGGER.debug( "Component %s import took %.3f seconds (loaded_executor=False)", @@ -927,7 +931,7 @@ class Integration: try: try: comp = await self.hass.async_add_import_executor_job( - self.get_component, True + self._get_component, True ) except ImportError as ex: load_executor = False @@ -936,7 +940,7 @@ class Integration: ) # If importing in the executor deadlocks because there is a circular # dependency, we fall back to the event loop. - comp = self.get_component() + comp = self._get_component() self._component_future.set_result(comp) except BaseException as ex: self._component_future.set_exception(ex) @@ -959,22 +963,29 @@ class Integration: return comp - def get_component(self, preload_platforms: bool = False) -> ComponentProtocol: + def get_component(self) -> ComponentProtocol: """Return the component. This method must be thread-safe as it's called from the executor and the event loop. + This method checks the cache and if the component is not loaded + it will load it in the executor if import_executor is set, otherwise + it will load it in the event loop. + This is mostly a thin wrapper around importlib.import_module with a dict cache which is thread-safe since importlib has appropriate locks. """ + domain = self.domain + if domain in (cache := self._cache): + return cache[domain] + return self._get_component() + + def _get_component(self, preload_platforms: bool = False) -> ComponentProtocol: + """Return the component.""" cache = self._cache domain = self.domain - - if domain in cache: - return cache[domain] - try: cache[domain] = cast( ComponentProtocol, importlib.import_module(self.pkg_path) diff --git a/tests/test_loader.py b/tests/test_loader.py index 74eb4c6ed69..8be7cf09b2c 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1188,10 +1188,9 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( ), patch("homeassistant.loader.importlib.import_module", import_module): module = await integration.async_get_component() - # Everything is there so we should load in the event loop - # since it will all be cached - assert "loaded_executor=False" in caplog.text - assert "loaded_executor=True" not in caplog.text + # Everything is already in the integration cache + # so it should not have to call the load + assert "loaded_executor" not in caplog.text assert module is module_mock diff --git a/tests/test_setup.py b/tests/test_setup.py index 29ced934745..c54c02fd880 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -617,7 +617,7 @@ async def test_async_when_setup_or_start_already_loaded(hass: HomeAssistant) -> async def test_setup_import_blows_up(hass: HomeAssistant) -> None: """Test that we handle it correctly when importing integration blows up.""" with patch( - "homeassistant.loader.Integration.get_component", side_effect=ImportError + "homeassistant.loader.Integration.async_get_component", side_effect=ImportError ): assert not await setup.async_setup_component(hass, "sun", {}) From a277d0c4b518a14b48625c6b76c1b684daf692c0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 05:01:31 -1000 Subject: [PATCH 0352/1691] Migrate system flows to use the discovery helper (#112291) Ensures we are not creating new flows or loading their platforms until the started event once the import executor has clamed down --- homeassistant/components/cloud/__init__.py | 6 ++++-- homeassistant/components/hassio/__init__.py | 22 ++++++++++----------- homeassistant/config_entries.py | 1 + 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 888e99e3a34..5a393b35961 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -11,7 +11,7 @@ from hass_nabucasa import Cloud import voluptuous as vol from homeassistant.components import alexa, google_assistant -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry from homeassistant.const import ( CONF_DESCRIPTION, CONF_MODE, @@ -304,7 +304,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return loaded = True - await hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"}) + await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_SYSTEM} + ) async def _on_connect() -> None: """Handle cloud connect.""" diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 5355c5bb16c..cd1c6e735ef 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -14,7 +14,7 @@ import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import panel_custom from homeassistant.components.homeassistant import async_set_stop_handler -from homeassistant.config_entries import ConfigEntry +from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry from homeassistant.const import ( ATTR_NAME, EVENT_CORE_CONFIG_UPDATE, @@ -30,7 +30,11 @@ from homeassistant.core import ( callback, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, device_registry as dr +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + discovery_flow, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_call_later from homeassistant.helpers.storage import Store @@ -490,11 +494,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: return if (hw_integration := HARDWARE_INTEGRATIONS.get(board)) is None: return - hass.async_create_task( - hass.config_entries.flow.async_init( - hw_integration, context={"source": "system"} - ), - eager_start=True, + discovery_flow.async_create_flow( + hass, hw_integration, context={"source": SOURCE_SYSTEM}, data={} ) async_setup_hardware_integration_job = HassJob( @@ -502,12 +503,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: ) _async_setup_hardware_integration() - - hass.async_create_task( - hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"}), - eager_start=True, + discovery_flow.async_create_flow( + hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={} ) - return True diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index e6fe5d5d70b..e6402696010 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -87,6 +87,7 @@ SOURCE_IMPORT = "import" SOURCE_INTEGRATION_DISCOVERY = "integration_discovery" SOURCE_MQTT = "mqtt" SOURCE_SSDP = "ssdp" +SOURCE_SYSTEM = "system" SOURCE_USB = "usb" SOURCE_USER = "user" SOURCE_ZEROCONF = "zeroconf" From 946572e382fe3f10646bd7949982329bfef17a85 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 05:02:23 -1000 Subject: [PATCH 0353/1691] Simplify loader preload logic for config_flows (#112290) We previously checked Integration.config_flow to see if we should pre-import the config flow, but this is now always set for some integration like `homeassistant_green`, `hassio`, etc. Instead we can add it to the rest of the platforms since we already know which files exist. This simplifies the logic and ensures the pre-import still happens if the file is there even if its not listed in the manifest `2024-03-04 22:54:31.906 DEBUG (MainThread) [homeassistant.loader] Importing platforms for homeassistant_green executor=[config_flow] loop=[] took 2.74s` --- homeassistant/loader.py | 10 +--------- tests/test_loader.py | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 95b00547fb8..ac1d71a632c 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -67,6 +67,7 @@ _LOGGER = logging.getLogger(__name__) # BASE_PRELOAD_PLATFORMS = [ "config", + "config_flow", "diagnostics", "energy", "group", @@ -1006,15 +1007,6 @@ class Integration: with suppress(ImportError): self.get_platform(platform_name) - if self.config_flow: - # If there is a config flow, we will cache it as well since - # config entry setup always has to load the flow to get the - # major/minor version for migrations. Since we may be running - # in the executor we will use this opportunity to cache the - # config_flow as well. - with suppress(ImportError): - self.get_platform("config_flow") - return cache[domain] def _load_platforms(self, platform_names: Iterable[str]) -> dict[str, ModuleType]: diff --git a/tests/test_loader.py b/tests/test_loader.py index 8be7cf09b2c..22379b340d4 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1117,7 +1117,7 @@ async def test_async_get_component_preloads_config_and_config_flow( await executor_import_integration.async_get_component() assert len(platform_exists_calls[0]) == len(loader.BASE_PRELOAD_PLATFORMS) - assert mock_import.call_count == 2 + len(loader.BASE_PRELOAD_PLATFORMS) + assert mock_import.call_count == 1 + len(loader.BASE_PRELOAD_PLATFORMS) assert ( mock_import.call_args_list[0][0][0] == "homeassistant.components.executor_import" From 0b113b6b714cfa9f3c7421081763dbf8d145b546 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 16:20:29 +0100 Subject: [PATCH 0354/1691] Remove entity description mixin in Accuweather (#112375) --- homeassistant/components/accuweather/sensor.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 2219c5de4b6..0ac0e0d9296 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -45,19 +45,11 @@ from .const import ( PARALLEL_UPDATES = 1 -@dataclass(frozen=True) -class AccuWeatherSensorDescriptionMixin: - """Mixin for AccuWeather sensor.""" - - value_fn: Callable[[dict[str, Any]], str | int | float | None] - - -@dataclass(frozen=True) -class AccuWeatherSensorDescription( - SensorEntityDescription, AccuWeatherSensorDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class AccuWeatherSensorDescription(SensorEntityDescription): """Class describing AccuWeather sensor entities.""" + value_fn: Callable[[dict[str, Any]], str | int | float | None] attr_fn: Callable[[dict[str, Any]], dict[str, Any]] = lambda _: {} day: int | None = None From e160d036d953baaf0dca7ffb6cc394c0e2efce85 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 17:29:28 +0100 Subject: [PATCH 0355/1691] Remove entity description mixin in Bravia TV (#112395) * Remove entity description mixin in Bravia TV * Remove entity description mixin in Bravia TV --- homeassistant/components/braviatv/button.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/braviatv/button.py b/homeassistant/components/braviatv/button.py index eb3d2d8797f..02f66167c61 100644 --- a/homeassistant/components/braviatv/button.py +++ b/homeassistant/components/braviatv/button.py @@ -19,20 +19,13 @@ from .coordinator import BraviaTVCoordinator from .entity import BraviaTVEntity -@dataclass(frozen=True) -class BraviaTVButtonDescriptionMixin: - """Mixin to describe a Bravia TV Button entity.""" +@dataclass(frozen=True, kw_only=True) +class BraviaTVButtonDescription(ButtonEntityDescription): + """Bravia TV Button description.""" press_action: Callable[[BraviaTVCoordinator], Coroutine] -@dataclass(frozen=True) -class BraviaTVButtonDescription( - ButtonEntityDescription, BraviaTVButtonDescriptionMixin -): - """Bravia TV Button description.""" - - BUTTONS: tuple[BraviaTVButtonDescription, ...] = ( BraviaTVButtonDescription( key="reboot", From 38a6b45f600d961daa70154faa474edeef4fb6d1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 17:29:40 +0100 Subject: [PATCH 0356/1691] Remove entity description mixin in Brother (#112396) --- homeassistant/components/brother/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 198fe621246..c940e03bc0a 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -35,20 +35,13 @@ UNIT_PAGES = "p" _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class BrotherSensorRequiredKeysMixin: - """Class for Brother entity required keys.""" +@dataclass(frozen=True, kw_only=True) +class BrotherSensorEntityDescription(SensorEntityDescription): + """A class that describes sensor entities.""" value: Callable[[BrotherSensors], StateType | datetime] -@dataclass(frozen=True) -class BrotherSensorEntityDescription( - SensorEntityDescription, BrotherSensorRequiredKeysMixin -): - """A class that describes sensor entities.""" - - SENSOR_TYPES: tuple[BrotherSensorEntityDescription, ...] = ( BrotherSensorEntityDescription( key="status", From b0011d903e71d84fb3059efa172cae1a38e0a1c4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 17:30:26 +0100 Subject: [PATCH 0357/1691] Add icon translations to Vizio (#112348) --- homeassistant/components/vizio/const.py | 4 ---- homeassistant/components/vizio/icons.json | 5 +++++ homeassistant/components/vizio/media_player.py | 2 -- 3 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/vizio/icons.json diff --git a/homeassistant/components/vizio/const.py b/homeassistant/components/vizio/const.py index 9615032c097..954d819b1ac 100644 --- a/homeassistant/components/vizio/const.py +++ b/homeassistant/components/vizio/const.py @@ -49,10 +49,6 @@ DEFAULT_VOLUME_STEP = 1 DEVICE_ID = "pyvizio" DOMAIN = "vizio" -ICON = { - MediaPlayerDeviceClass.TV: "mdi:television", - MediaPlayerDeviceClass.SPEAKER: "mdi:speaker", -} COMMON_SUPPORTED_COMMANDS = ( MediaPlayerEntityFeature.SELECT_SOURCE diff --git a/homeassistant/components/vizio/icons.json b/homeassistant/components/vizio/icons.json new file mode 100644 index 00000000000..ccdaf816bb0 --- /dev/null +++ b/homeassistant/components/vizio/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "update_setting": "mdi:cog" + } +} diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index db3995772d4..ba9cd509499 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -42,7 +42,6 @@ from .const import ( DEFAULT_VOLUME_STEP, DEVICE_ID, DOMAIN, - ICON, SERVICE_UPDATE_SETTING, SUPPORTED_COMMANDS, UPDATE_SETTING_SCHEMA, @@ -166,7 +165,6 @@ class VizioDevice(MediaPlayerEntity): self._attr_supported_features = SUPPORTED_COMMANDS[device_class] # Entity class attributes that will not change - self._attr_icon = ICON[device_class] unique_id = config_entry.unique_id assert unique_id self._attr_unique_id = unique_id From 7cef704220be7cc18b88453d39fa9d5785f7ed2c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:10:00 +0100 Subject: [PATCH 0358/1691] Remove entity description mixin in Balboa (#112389) --- homeassistant/components/balboa/binary_sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/balboa/binary_sensor.py b/homeassistant/components/balboa/binary_sensor.py index 8584ed2783c..053b94007c2 100644 --- a/homeassistant/components/balboa/binary_sensor.py +++ b/homeassistant/components/balboa/binary_sensor.py @@ -33,20 +33,13 @@ async def async_setup_entry( async_add_entities(entities) -@dataclass(frozen=True) -class BalboaBinarySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class BalboaBinarySensorEntityDescription(BinarySensorEntityDescription): + """A class that describes Balboa binary sensor entities.""" is_on_fn: Callable[[SpaClient], bool] -@dataclass(frozen=True) -class BalboaBinarySensorEntityDescription( - BinarySensorEntityDescription, BalboaBinarySensorEntityDescriptionMixin -): - """A class that describes Balboa binary sensor entities.""" - - BINARY_SENSOR_DESCRIPTIONS = ( BalboaBinarySensorEntityDescription( key="Filter1", From cb397cecb1c18e84f4fb81352f387cad9ee02a7d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:11:30 +0100 Subject: [PATCH 0359/1691] Remove entity description mixin in BMW (#112391) --- .../bmw_connected_drive/binary_sensor.py | 14 +++----------- .../components/bmw_connected_drive/button.py | 12 +++--------- .../components/bmw_connected_drive/number.py | 12 +++--------- .../components/bmw_connected_drive/select.py | 12 +++--------- .../components/bmw_connected_drive/switch.py | 12 +++--------- 5 files changed, 15 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index 7ff9ad2d8ab..e7886d79cad 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -109,19 +109,11 @@ def _format_cbs_report( return result -@dataclass(frozen=True) -class BMWRequiredKeysMixin: - """Mixin for required keys.""" - - value_fn: Callable[[MyBMWVehicle], bool] - - -@dataclass(frozen=True) -class BMWBinarySensorEntityDescription( - BinarySensorEntityDescription, BMWRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class BMWBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes BMW binary_sensor entity.""" + value_fn: Callable[[MyBMWVehicle], bool] attr_fn: Callable[[MyBMWVehicle, UnitSystem], dict[str, Any]] | None = None diff --git a/homeassistant/components/bmw_connected_drive/button.py b/homeassistant/components/bmw_connected_drive/button.py index 74f12c9c721..8ef0ac3d3ef 100644 --- a/homeassistant/components/bmw_connected_drive/button.py +++ b/homeassistant/components/bmw_connected_drive/button.py @@ -25,17 +25,11 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class BMWRequiredKeysMixin: - """Mixin for required keys.""" - - remote_function: Callable[[MyBMWVehicle], Coroutine[Any, Any, RemoteServiceStatus]] - - -@dataclass(frozen=True) -class BMWButtonEntityDescription(ButtonEntityDescription, BMWRequiredKeysMixin): +@dataclass(frozen=True, kw_only=True) +class BMWButtonEntityDescription(ButtonEntityDescription): """Class describing BMW button entities.""" + remote_function: Callable[[MyBMWVehicle], Coroutine[Any, Any, RemoteServiceStatus]] enabled_when_read_only: bool = False is_available: Callable[[MyBMWVehicle], bool] = lambda _: True diff --git a/homeassistant/components/bmw_connected_drive/number.py b/homeassistant/components/bmw_connected_drive/number.py index 21326a59118..defeb3f0f56 100644 --- a/homeassistant/components/bmw_connected_drive/number.py +++ b/homeassistant/components/bmw_connected_drive/number.py @@ -26,18 +26,12 @@ from .coordinator import BMWDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class BMWRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class BMWNumberEntityDescription(NumberEntityDescription): + """Describes BMW number entity.""" value_fn: Callable[[MyBMWVehicle], float | int | None] remote_service: Callable[[MyBMWVehicle, float | int], Coroutine[Any, Any, Any]] - - -@dataclass(frozen=True) -class BMWNumberEntityDescription(NumberEntityDescription, BMWRequiredKeysMixin): - """Describes BMW number entity.""" - is_available: Callable[[MyBMWVehicle], bool] = lambda _: False dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None diff --git a/homeassistant/components/bmw_connected_drive/select.py b/homeassistant/components/bmw_connected_drive/select.py index db426b89487..24172857e70 100644 --- a/homeassistant/components/bmw_connected_drive/select.py +++ b/homeassistant/components/bmw_connected_drive/select.py @@ -22,18 +22,12 @@ from .coordinator import BMWDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class BMWRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class BMWSelectEntityDescription(SelectEntityDescription): + """Describes BMW sensor entity.""" current_option: Callable[[MyBMWVehicle], str] remote_service: Callable[[MyBMWVehicle, str], Coroutine[Any, Any, Any]] - - -@dataclass(frozen=True) -class BMWSelectEntityDescription(SelectEntityDescription, BMWRequiredKeysMixin): - """Describes BMW sensor entity.""" - is_available: Callable[[MyBMWVehicle], bool] = lambda _: False dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None diff --git a/homeassistant/components/bmw_connected_drive/switch.py b/homeassistant/components/bmw_connected_drive/switch.py index 7c8952f4ecc..5ee31d2efd7 100644 --- a/homeassistant/components/bmw_connected_drive/switch.py +++ b/homeassistant/components/bmw_connected_drive/switch.py @@ -22,19 +22,13 @@ from .coordinator import BMWDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class BMWRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class BMWSwitchEntityDescription(SwitchEntityDescription): + """Describes BMW switch entity.""" value_fn: Callable[[MyBMWVehicle], bool] remote_service_on: Callable[[MyBMWVehicle], Coroutine[Any, Any, Any]] remote_service_off: Callable[[MyBMWVehicle], Coroutine[Any, Any, Any]] - - -@dataclass(frozen=True) -class BMWSwitchEntityDescription(SwitchEntityDescription, BMWRequiredKeysMixin): - """Describes BMW switch entity.""" - is_available: Callable[[MyBMWVehicle], bool] = lambda _: False dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None From b9ef27799b1404e3a8b1622a8e11b77876b46295 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:12:10 +0100 Subject: [PATCH 0360/1691] Remove entity description mixin in Bond (#112393) --- homeassistant/components/bond/button.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/bond/button.py b/homeassistant/components/bond/button.py index 273ef837f6e..b90727100bd 100644 --- a/homeassistant/components/bond/button.py +++ b/homeassistant/components/bond/button.py @@ -21,23 +21,15 @@ from .utils import BondDevice, BondHub STEP_SIZE = 10 -@dataclass(frozen=True) -class BondButtonEntityDescriptionMixin: - """Mixin to describe a Bond Button entity.""" - - mutually_exclusive: Action | None - argument: int | None - - -@dataclass(frozen=True) -class BondButtonEntityDescription( - ButtonEntityDescription, BondButtonEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class BondButtonEntityDescription(ButtonEntityDescription): """Class to describe a Bond Button entity.""" # BondEntity does not support UNDEFINED, # restrict the type to str | None name: str | None = None + mutually_exclusive: Action | None + argument: int | None STOP_BUTTON = BondButtonEntityDescription( From 59eac48e33f1c3e938044078df2e2a08010cf73e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:14:41 +0100 Subject: [PATCH 0361/1691] Remove entity description mixin in Bosch SHC (#112394) --- homeassistant/components/bosch_shc/switch.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index 8e542c860d4..a6eee973206 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -29,23 +29,15 @@ from .const import DATA_SESSION, DOMAIN from .entity import SHCEntity -@dataclass(frozen=True) -class SHCSwitchRequiredKeysMixin: - """Mixin for SHC switch required keys.""" +@dataclass(frozen=True, kw_only=True) +class SHCSwitchEntityDescription(SwitchEntityDescription): + """Class describing SHC switch entities.""" on_key: str on_value: StateType should_poll: bool -@dataclass(frozen=True) -class SHCSwitchEntityDescription( - SwitchEntityDescription, - SHCSwitchRequiredKeysMixin, -): - """Class describing SHC switch entities.""" - - SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = { "smartplug": SHCSwitchEntityDescription( key="smartplug", From a626a698f05c1f34316cfe44d59dd57cc86ebaea Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:26:20 +0100 Subject: [PATCH 0362/1691] Remove entity description mixin in Dovado (#112402) --- homeassistant/components/dovado/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 4de20bf86e8..5da248c414a 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -30,18 +30,13 @@ SENSOR_NETWORK = "network" SENSOR_SMS_UNREAD = "sms" -@dataclass(frozen=True) -class DovadoRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DovadoSensorEntityDescription(SensorEntityDescription): + """Describes Dovado sensor entity.""" identifier: str -@dataclass(frozen=True) -class DovadoSensorEntityDescription(SensorEntityDescription, DovadoRequiredKeysMixin): - """Describes Dovado sensor entity.""" - - SENSOR_TYPES: tuple[DovadoSensorEntityDescription, ...] = ( DovadoSensorEntityDescription( identifier=SENSOR_NETWORK, From 4a7eab92bb5ce2543b79e14071c42d08f0854a51 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:27:18 +0100 Subject: [PATCH 0363/1691] Remove entity description mixin in Ecobee (#112408) --- homeassistant/components/ecobee/number.py | 13 +++---------- homeassistant/components/ecobee/sensor.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/ecobee/number.py b/homeassistant/components/ecobee/number.py index 345ca7b705f..a4106f196a1 100644 --- a/homeassistant/components/ecobee/number.py +++ b/homeassistant/components/ecobee/number.py @@ -18,21 +18,14 @@ from .entity import EcobeeBaseEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class EcobeeNumberEntityDescriptionBase: - """Required values when describing Ecobee number entities.""" +@dataclass(frozen=True, kw_only=True) +class EcobeeNumberEntityDescription(NumberEntityDescription): + """Class describing Ecobee number entities.""" ecobee_setting_key: str set_fn: Callable[[EcobeeData, int, int], Awaitable] -@dataclass(frozen=True) -class EcobeeNumberEntityDescription( - NumberEntityDescription, EcobeeNumberEntityDescriptionBase -): - """Class describing Ecobee number entities.""" - - VENTILATOR_NUMBERS = ( EcobeeNumberEntityDescription( key="home", diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index 7f0e7b808a8..ce2f0f7beb8 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -25,20 +25,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER -@dataclass(frozen=True) -class EcobeeSensorEntityDescriptionMixin: - """Represent the required ecobee entity description attributes.""" +@dataclass(frozen=True, kw_only=True) +class EcobeeSensorEntityDescription(SensorEntityDescription): + """Represent the ecobee sensor entity description.""" runtime_key: str | None -@dataclass(frozen=True) -class EcobeeSensorEntityDescription( - SensorEntityDescription, EcobeeSensorEntityDescriptionMixin -): - """Represent the ecobee sensor entity description.""" - - SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = ( EcobeeSensorEntityDescription( key="temperature", From e076b6aacfa0ac00366bec7f322a3922b067d24b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:27:31 +0100 Subject: [PATCH 0364/1691] Add icon translations to Vodafone Station (#112349) --- .../vodafone_station/device_tracker.py | 7 +-- .../components/vodafone_station/icons.json | 44 +++++++++++++++++++ .../components/vodafone_station/sensor.py | 10 ----- 3 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/vodafone_station/icons.json diff --git a/homeassistant/components/vodafone_station/device_tracker.py b/homeassistant/components/vodafone_station/device_tracker.py index 9f98da88d22..ebe985cb744 100644 --- a/homeassistant/components/vodafone_station/device_tracker.py +++ b/homeassistant/components/vodafone_station/device_tracker.py @@ -61,6 +61,8 @@ def async_add_new_tracked_entities( class VodafoneStationTracker(CoordinatorEntity[VodafoneStationRouter], ScannerEntity): """Representation of a Vodafone Station device.""" + _attr_translation_key = "device_tracker" + def __init__( self, coordinator: VodafoneStationRouter, device_info: VodafoneStationDeviceInfo ) -> None: @@ -98,11 +100,6 @@ class VodafoneStationTracker(CoordinatorEntity[VodafoneStationRouter], ScannerEn """Return the hostname of device.""" return self._attr_name - @property - def icon(self) -> str: - """Return device icon.""" - return "mdi:lan-connect" if self._device.connected else "mdi:lan-disconnect" - @property def ip_address(self) -> str | None: """Return the primary ip address of the device.""" diff --git a/homeassistant/components/vodafone_station/icons.json b/homeassistant/components/vodafone_station/icons.json new file mode 100644 index 00000000000..b504fc5cc5f --- /dev/null +++ b/homeassistant/components/vodafone_station/icons.json @@ -0,0 +1,44 @@ +{ + "entity": { + "device_tracker": { + "device_tracker": { + "default": "mdi:lan-disconnect", + "state": { + "home": "mdi:lan-connect" + } + } + }, + "sensor": { + "external_ipv4": { + "default": "mdi:earth" + }, + "external_ipv6": { + "default": "mdi:earth" + }, + "external_ip_key": { + "default": "mdi:earth" + }, + "active_connection": { + "default": "mdi:wan" + }, + "fw_version": { + "default": "mdi:new-box" + }, + "phone_num1": { + "default": "mdi:phone" + }, + "phone_num2": { + "default": "mdi:phone" + }, + "sys_cpu_usage": { + "default": "mdi:chip" + }, + "sys_memory_usage": { + "default": "mdi:memory" + }, + "sys_reboot_cause": { + "default": "mdi:restart-alert" + } + } + } +} diff --git a/homeassistant/components/vodafone_station/sensor.py b/homeassistant/components/vodafone_station/sensor.py index b383c2d193a..c5fff05a164 100644 --- a/homeassistant/components/vodafone_station/sensor.py +++ b/homeassistant/components/vodafone_station/sensor.py @@ -72,26 +72,22 @@ SENSOR_TYPES: Final = ( VodafoneStationEntityDescription( key="wan_ip4_addr", translation_key="external_ipv4", - icon="mdi:earth", is_suitable=lambda info: info["wan_ip4_addr"] not in NOT_AVAILABLE, ), VodafoneStationEntityDescription( key="wan_ip6_addr", translation_key="external_ipv6", - icon="mdi:earth", is_suitable=lambda info: info["wan_ip6_addr"] not in NOT_AVAILABLE, ), VodafoneStationEntityDescription( key="vf_internet_key_ip_addr", translation_key="external_ip_key", - icon="mdi:earth", is_suitable=lambda info: info["vf_internet_key_ip_addr"] not in NOT_AVAILABLE, ), VodafoneStationEntityDescription( key="inter_ip_address", translation_key="active_connection", device_class=SensorDeviceClass.ENUM, - icon="mdi:wan", options=LINE_TYPES, value=_line_connection, ), @@ -112,19 +108,16 @@ SENSOR_TYPES: Final = ( VodafoneStationEntityDescription( key="fw_version", translation_key="fw_version", - icon="mdi:new-box", entity_category=EntityCategory.DIAGNOSTIC, ), VodafoneStationEntityDescription( key="phone_num1", translation_key="phone_num1", - icon="mdi:phone", is_suitable=lambda info: info["phone_unavailable1"] == "0", ), VodafoneStationEntityDescription( key="phone_num2", translation_key="phone_num2", - icon="mdi:phone", is_suitable=lambda info: info["phone_unavailable2"] == "0", ), VodafoneStationEntityDescription( @@ -137,7 +130,6 @@ SENSOR_TYPES: Final = ( VodafoneStationEntityDescription( key="sys_cpu_usage", translation_key="sys_cpu_usage", - icon="mdi:chip", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, value=lambda coordinator, key: float(coordinator.data.sensors[key][:-1]), @@ -145,7 +137,6 @@ SENSOR_TYPES: Final = ( VodafoneStationEntityDescription( key="sys_memory_usage", translation_key="sys_memory_usage", - icon="mdi:memory", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, value=lambda coordinator, key: float(coordinator.data.sensors[key][:-1]), @@ -153,7 +144,6 @@ SENSOR_TYPES: Final = ( VodafoneStationEntityDescription( key="sys_reboot_cause", translation_key="sys_reboot_cause", - icon="mdi:restart-alert", entity_category=EntityCategory.DIAGNOSTIC, ), ) From 900112b1e840c4b039450a376c1b2a9507c744dd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:30:42 +0100 Subject: [PATCH 0365/1691] Remove entity description mixin in AirQ (#112377) --- homeassistant/components/airq/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/airq/sensor.py b/homeassistant/components/airq/sensor.py index ad05202943f..660487fef53 100644 --- a/homeassistant/components/airq/sensor.py +++ b/homeassistant/components/airq/sensor.py @@ -37,18 +37,13 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class AirQEntityDescriptionMixin: - """Class for keys required by AirQ entity.""" +@dataclass(frozen=True, kw_only=True) +class AirQEntityDescription(SensorEntityDescription): + """Describes AirQ sensor entity.""" value: Callable[[dict], float | int | None] -@dataclass(frozen=True) -class AirQEntityDescription(SensorEntityDescription, AirQEntityDescriptionMixin): - """Describes AirQ sensor entity.""" - - # Keys must match those in the data dictionary SENSOR_TYPES: list[AirQEntityDescription] = [ AirQEntityDescription( From c06ab0bddf8c8be6e00030b46680eb4b99a6a8b6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:32:35 +0100 Subject: [PATCH 0366/1691] Remove entity description mixin in Aladdin Connect (#112379) * Remove entity description mixin in Aladdin Connect * Remove entity description mixin in Aladdin Connect --- homeassistant/components/aladdin_connect/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/aladdin_connect/sensor.py b/homeassistant/components/aladdin_connect/sensor.py index 0a264edc8c2..5ea1c13fc27 100644 --- a/homeassistant/components/aladdin_connect/sensor.py +++ b/homeassistant/components/aladdin_connect/sensor.py @@ -23,20 +23,13 @@ from .const import DOMAIN from .model import DoorDevice -@dataclass(frozen=True) -class AccSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class AccSensorEntityDescription(SensorEntityDescription): + """Describes AladdinConnect sensor entity.""" value_fn: Callable -@dataclass(frozen=True) -class AccSensorEntityDescription( - SensorEntityDescription, AccSensorEntityDescriptionMixin -): - """Describes AladdinConnect sensor entity.""" - - SENSORS: tuple[AccSensorEntityDescription, ...] = ( AccSensorEntityDescription( key="battery_level", From 017b2623e8ff741276902578f32aa896c89eed44 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 18:36:58 +0100 Subject: [PATCH 0367/1691] Remove entity description mixin in Azure DevOps (#112385) --- homeassistant/components/azure_devops/sensor.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 6daf9b434df..71881ba2a16 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -17,23 +17,16 @@ from . import AzureDevOpsDeviceEntity, AzureDevOpsEntityDescription from .const import CONF_ORG, DOMAIN -@dataclass(frozen=True) -class AzureDevOpsSensorEntityDescriptionMixin: - """Mixin class for required Azure DevOps sensor description keys.""" - - build_key: int - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class AzureDevOpsSensorEntityDescription( AzureDevOpsEntityDescription, SensorEntityDescription, - AzureDevOpsSensorEntityDescriptionMixin, ): """Class describing Azure DevOps sensor entities.""" - attrs: Callable[[DevOpsBuild], Any] = round - value: Callable[[DevOpsBuild], StateType] = round + build_key: int + attrs: Callable[[DevOpsBuild], Any] + value: Callable[[DevOpsBuild], StateType] async def async_setup_entry( From 4d82ea516a55deb64fc8fecca94c6b4231df440e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 5 Mar 2024 18:47:35 +0100 Subject: [PATCH 0368/1691] Add comments why we use loop.create_task in core (#112372) --- homeassistant/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/core.py b/homeassistant/core.py index e1bd5750c16..1fbdb91cfb6 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -605,6 +605,8 @@ class HomeAssistant: hassjob.target = cast( Callable[..., Coroutine[Any, Any, _R]], hassjob.target ) + # Use loop.create_task + # to avoid the extra function call in asyncio.create_task. task = self.loop.create_task(hassjob.target(*args), name=hassjob.name) elif hassjob.job_type is HassJobType.Callback: if TYPE_CHECKING: @@ -649,6 +651,8 @@ class HomeAssistant: if task.done(): return task else: + # Use loop.create_task + # to avoid the extra function call in asyncio.create_task. task = self.loop.create_task(target, name=name) self._tasks.add(task) task.add_done_callback(self._tasks.remove) @@ -671,6 +675,8 @@ class HomeAssistant: if task.done(): return task else: + # Use loop.create_task + # to avoid the extra function call in asyncio.create_task. task = self.loop.create_task(target, name=name) self._background_tasks.add(task) task.add_done_callback(self._background_tasks.remove) From 8e2de517100f2db930b5b40953c8aa20f15c6ff4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 19:07:45 +0100 Subject: [PATCH 0369/1691] Remove entity description mixin in Dormakaba dKey (#112401) --- .../components/dormakaba_dkey/binary_sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/dormakaba_dkey/binary_sensor.py b/homeassistant/components/dormakaba_dkey/binary_sensor.py index 2ec2b0a1c91..1c2205c01d3 100644 --- a/homeassistant/components/dormakaba_dkey/binary_sensor.py +++ b/homeassistant/components/dormakaba_dkey/binary_sensor.py @@ -22,20 +22,13 @@ from .entity import DormakabaDkeyEntity from .models import DormakabaDkeyData -@dataclass(frozen=True) -class DormakabaDkeyBinarySensorDescriptionMixin: - """Class for keys required by Dormakaba dKey binary sensor entity.""" +@dataclass(frozen=True, kw_only=True) +class DormakabaDkeyBinarySensorDescription(BinarySensorEntityDescription): + """Describes Dormakaba dKey binary sensor entity.""" is_on: Callable[[Notifications], bool] -@dataclass(frozen=True) -class DormakabaDkeyBinarySensorDescription( - BinarySensorEntityDescription, DormakabaDkeyBinarySensorDescriptionMixin -): - """Describes Dormakaba dKey binary sensor entity.""" - - BINARY_SENSOR_DESCRIPTIONS = ( DormakabaDkeyBinarySensorDescription( key="door_position", From a515603aaf85cba67952d6de925316f645a77793 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 5 Mar 2024 19:10:13 +0100 Subject: [PATCH 0370/1691] Add button to homeworks (#112269) --- .coveragerc | 1 + .../components/homeworks/__init__.py | 3 +- homeassistant/components/homeworks/button.py | 78 ++++ .../components/homeworks/config_flow.py | 210 ++++++++++- homeassistant/components/homeworks/const.py | 3 + homeassistant/components/homeworks/light.py | 5 - .../components/homeworks/strings.json | 100 +++++ tests/components/homeworks/conftest.py | 20 + .../components/homeworks/test_config_flow.py | 352 +++++++++++++++++- 9 files changed, 763 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/homeworks/button.py diff --git a/.coveragerc b/.coveragerc index d95c636e22c..55179b740cf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -546,6 +546,7 @@ omit = homeassistant/components/homematic/sensor.py homeassistant/components/homematic/switch.py homeassistant/components/homeworks/__init__.py + homeassistant/components/homeworks/button.py homeassistant/components/homeworks/light.py homeassistant/components/horizon/media_player.py homeassistant/components/hp_ilo/sensor.py diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 0abb22ad2e8..3d9de889ae7 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -36,7 +36,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.LIGHT] +PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.LIGHT] EVENT_BUTTON_PRESS = "homeworks_button_press" EVENT_BUTTON_RELEASE = "homeworks_button_release" @@ -186,6 +186,7 @@ class HomeworksEntity(Entity): self._controller_id, self._addr, self._idx ) self._controller = controller + self._attr_extra_state_attributes = {"homeworks_address": self._addr} class HomeworksKeypad: diff --git a/homeassistant/components/homeworks/button.py b/homeassistant/components/homeworks/button.py new file mode 100644 index 00000000000..04cf3594e0e --- /dev/null +++ b/homeassistant/components/homeworks/button.py @@ -0,0 +1,78 @@ +"""Support for Lutron Homeworks buttons.""" +from __future__ import annotations + +from time import sleep + +from pyhomeworks.pyhomeworks import Homeworks + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeworksData, HomeworksEntity +from .const import ( + CONF_ADDR, + CONF_BUTTONS, + CONF_CONTROLLER_ID, + CONF_KEYPADS, + CONF_NUMBER, + CONF_RELEASE_DELAY, + DOMAIN, +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Homeworks buttons.""" + data: HomeworksData = hass.data[DOMAIN][entry.entry_id] + controller = data.controller + controller_id = entry.options[CONF_CONTROLLER_ID] + devs = [] + for keypad in entry.options.get(CONF_KEYPADS, []): + for button in keypad[CONF_BUTTONS]: + dev = HomeworksButton( + controller, + controller_id, + keypad[CONF_ADDR], + keypad[CONF_NAME], + button[CONF_NAME], + button[CONF_NUMBER], + button[CONF_RELEASE_DELAY], + ) + devs.append(dev) + async_add_entities(devs, True) + + +class HomeworksButton(HomeworksEntity, ButtonEntity): + """Homeworks Button.""" + + def __init__( + self, + controller: Homeworks, + controller_id: str, + addr: str, + keypad_name: str, + button_name: str, + button_number: int, + release_delay: float, + ) -> None: + """Create device with Addr, name, and rate.""" + super().__init__(controller, controller_id, addr, button_number, button_name) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{controller_id}.{addr}")}, name=keypad_name + ) + self._release_delay = release_delay + + def press(self) -> None: + """Press the button.""" + # pylint: disable-next=protected-access + self._controller._send(f"KBP, {self._addr}, {self._idx}") + if not self._release_delay: + return + sleep(self._release_delay) + # pylint: disable-next=protected-access + self._controller._send(f"KBR, {self._addr}, {self._idx}") diff --git a/homeassistant/components/homeworks/config_flow.py b/homeassistant/components/homeworks/config_flow.py index e7095e4f57f..269abd70535 100644 --- a/homeassistant/components/homeworks/config_flow.py +++ b/homeassistant/components/homeworks/config_flow.py @@ -8,6 +8,7 @@ from typing import Any from pyhomeworks.pyhomeworks import Homeworks import voluptuous as vol +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT @@ -37,11 +38,15 @@ from homeassistant.util import slugify from . import DEFAULT_FADE_RATE, calculate_unique_id from .const import ( CONF_ADDR, + CONF_BUTTONS, CONF_CONTROLLER_ID, CONF_DIMMERS, CONF_INDEX, CONF_KEYPADS, + CONF_NUMBER, CONF_RATE, + CONF_RELEASE_DELAY, + DEFAULT_BUTTON_NAME, DEFAULT_KEYPAD_NAME, DEFAULT_LIGHT_NAME, DOMAIN, @@ -71,6 +76,18 @@ LIGHT_EDIT = { ), } +BUTTON_EDIT = { + vol.Optional(CONF_RELEASE_DELAY, default=0): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=5, + step=0.01, + mode=selector.NumberSelectorMode.BOX, + unit_of_measurement="s", + ), + ), +} + validate_addr = cv.matches_regex(r"\[\d\d:\d\d:\d\d:\d\d\]") @@ -160,6 +177,31 @@ def _validate_address(handler: SchemaCommonFlowHandler, addr: str) -> None: raise SchemaFlowError("duplicated_addr") +def _validate_button_number(handler: SchemaCommonFlowHandler, number: int) -> None: + """Validate button number.""" + keypad = handler.flow_state["_idx"] + buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS] + + for button in buttons: + if button[CONF_NUMBER] == number: + raise SchemaFlowError("duplicated_number") + + +async def validate_add_button( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Validate button input.""" + user_input[CONF_NUMBER] = int(user_input[CONF_NUMBER]) + _validate_button_number(handler, user_input[CONF_NUMBER]) + + # Standard behavior is to merge the result with the options. + # In this case, we want to add a sub-item so we update the options directly. + keypad = handler.flow_state["_idx"] + buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS] + buttons.append(user_input) + return {} + + async def validate_add_keypad( handler: SchemaCommonFlowHandler, user_input: dict[str, Any] ) -> dict[str, Any]: @@ -169,7 +211,7 @@ async def validate_add_keypad( # Standard behavior is to merge the result with the options. # In this case, we want to add a sub-item so we update the options directly. items = handler.options[CONF_KEYPADS] - items.append(user_input) + items.append(user_input | {CONF_BUTTONS: []}) return {} @@ -186,6 +228,37 @@ async def validate_add_light( return {} +async def get_select_button_schema(handler: SchemaCommonFlowHandler) -> vol.Schema: + """Return schema for selecting a button.""" + keypad = handler.flow_state["_idx"] + buttons: list[dict[str, Any]] = handler.options[CONF_KEYPADS][keypad][CONF_BUTTONS] + + return vol.Schema( + { + vol.Required(CONF_INDEX): vol.In( + { + str(index): f"{config[CONF_NAME]} ({config[CONF_NUMBER]})" + for index, config in enumerate(buttons) + }, + ) + } + ) + + +async def get_select_keypad_schema(handler: SchemaCommonFlowHandler) -> vol.Schema: + """Return schema for selecting a keypad.""" + return vol.Schema( + { + vol.Required(CONF_INDEX): vol.In( + { + str(index): f"{config[CONF_NAME]} ({config[CONF_ADDR]})" + for index, config in enumerate(handler.options[CONF_KEYPADS]) + }, + ) + } + ) + + async def get_select_light_schema(handler: SchemaCommonFlowHandler) -> vol.Schema: """Return schema for selecting a light.""" return vol.Schema( @@ -200,6 +273,14 @@ async def get_select_light_schema(handler: SchemaCommonFlowHandler) -> vol.Schem ) +async def validate_select_button( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Store button index in flow state.""" + handler.flow_state["_button_idx"] = int(user_input[CONF_INDEX]) + return {} + + async def validate_select_keypad_light( handler: SchemaCommonFlowHandler, user_input: dict[str, Any] ) -> dict[str, Any]: @@ -208,6 +289,15 @@ async def validate_select_keypad_light( return {} +async def get_edit_button_suggested_values( + handler: SchemaCommonFlowHandler, +) -> dict[str, Any]: + """Return suggested values for button editing.""" + keypad_idx: int = handler.flow_state["_idx"] + button_idx: int = handler.flow_state["_button_idx"] + return dict(handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS][button_idx]) + + async def get_edit_light_suggested_values( handler: SchemaCommonFlowHandler, ) -> dict[str, Any]: @@ -216,6 +306,19 @@ async def get_edit_light_suggested_values( return dict(handler.options[CONF_DIMMERS][idx]) +async def validate_button_edit( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Update edited keypad or light.""" + # Standard behavior is to merge the result with the options. + # In this case, we want to add a sub-item so we update the options directly. + keypad_idx: int = handler.flow_state["_idx"] + button_idx: int = handler.flow_state["_button_idx"] + buttons: list[dict] = handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS] + buttons[button_idx].update(user_input) + return {} + + async def validate_light_edit( handler: SchemaCommonFlowHandler, user_input: dict[str, Any] ) -> dict[str, Any]: @@ -227,6 +330,22 @@ async def validate_light_edit( return {} +async def get_remove_button_schema(handler: SchemaCommonFlowHandler) -> vol.Schema: + """Return schema for button removal.""" + keypad_idx: int = handler.flow_state["_idx"] + buttons: list[dict] = handler.options[CONF_KEYPADS][keypad_idx][CONF_BUTTONS] + return vol.Schema( + { + vol.Required(CONF_INDEX): cv.multi_select( + { + str(index): f"{config[CONF_NAME]} ({config[CONF_NUMBER]})" + for index, config in enumerate(buttons) + }, + ) + } + ) + + async def get_remove_keypad_light_schema( handler: SchemaCommonFlowHandler, *, key: str ) -> vol.Schema: @@ -243,6 +362,37 @@ async def get_remove_keypad_light_schema( ) +async def validate_remove_button( + handler: SchemaCommonFlowHandler, user_input: dict[str, Any] +) -> dict[str, Any]: + """Validate remove keypad or light.""" + removed_indexes: set[str] = set(user_input[CONF_INDEX]) + + # Standard behavior is to merge the result with the options. + # In this case, we want to remove sub-items so we update the options directly. + entity_registry = er.async_get(handler.parent_handler.hass) + keypad_idx: int = handler.flow_state["_idx"] + keypad: dict = handler.options[CONF_KEYPADS][keypad_idx] + items: list[dict[str, Any]] = [] + item: dict[str, Any] + for index, item in enumerate(keypad[CONF_BUTTONS]): + if str(index) not in removed_indexes: + items.append(item) + button_number = keypad[CONF_BUTTONS][index][CONF_NUMBER] + if entity_id := entity_registry.async_get_entity_id( + BUTTON_DOMAIN, + DOMAIN, + calculate_unique_id( + handler.options[CONF_CONTROLLER_ID], + keypad[CONF_ADDR], + button_number, + ), + ): + entity_registry.async_remove(entity_id) + keypad[CONF_BUTTONS] = items + return {} + + async def validate_remove_keypad_light( handler: SchemaCommonFlowHandler, user_input: dict[str, Any], *, key: str ) -> dict[str, Any]: @@ -292,12 +442,28 @@ DATA_SCHEMA_ADD_KEYPAD = vol.Schema( vol.Required(CONF_ADDR): TextSelector(), } ) +DATA_SCHEMA_ADD_BUTTON = vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_BUTTON_NAME): TextSelector(), + vol.Required(CONF_NUMBER): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, + max=24, + step=1, + mode=selector.NumberSelectorMode.BOX, + ), + ), + **BUTTON_EDIT, + } +) +DATA_SCHEMA_EDIT_BUTTON = vol.Schema(BUTTON_EDIT) DATA_SCHEMA_EDIT_LIGHT = vol.Schema(LIGHT_EDIT) OPTIONS_FLOW = { "init": SchemaFlowMenuStep( [ "add_keypad", + "select_edit_keypad", "remove_keypad", "add_light", "select_edit_light", @@ -309,6 +475,40 @@ OPTIONS_FLOW = { suggested_values=None, validate_user_input=validate_add_keypad, ), + "select_edit_keypad": SchemaFlowFormStep( + get_select_keypad_schema, + suggested_values=None, + validate_user_input=validate_select_keypad_light, + next_step="edit_keypad", + ), + "edit_keypad": SchemaFlowMenuStep( + [ + "add_button", + "select_edit_button", + "remove_button", + ] + ), + "add_button": SchemaFlowFormStep( + DATA_SCHEMA_ADD_BUTTON, + suggested_values=None, + validate_user_input=validate_add_button, + ), + "select_edit_button": SchemaFlowFormStep( + get_select_button_schema, + suggested_values=None, + validate_user_input=validate_select_button, + next_step="edit_button", + ), + "edit_button": SchemaFlowFormStep( + DATA_SCHEMA_EDIT_BUTTON, + suggested_values=get_edit_button_suggested_values, + validate_user_input=validate_button_edit, + ), + "remove_button": SchemaFlowFormStep( + get_remove_button_schema, + suggested_values=None, + validate_user_input=validate_remove_button, + ), "remove_keypad": SchemaFlowFormStep( partial(get_remove_keypad_light_schema, key=CONF_KEYPADS), suggested_values=None, @@ -359,6 +559,14 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN): CONF_KEYPADS: [ { CONF_ADDR: keypad[CONF_ADDR], + CONF_BUTTONS: [ + { + CONF_NAME: button[CONF_NAME], + CONF_NUMBER: button[CONF_NUMBER], + CONF_RELEASE_DELAY: button[CONF_RELEASE_DELAY], + } + for button in keypad[CONF_BUTTONS] + ], CONF_NAME: keypad[CONF_NAME], } for keypad in config[CONF_KEYPADS] diff --git a/homeassistant/components/homeworks/const.py b/homeassistant/components/homeworks/const.py index df0e5294d4b..c0d4439ff93 100644 --- a/homeassistant/components/homeworks/const.py +++ b/homeassistant/components/homeworks/const.py @@ -4,11 +4,14 @@ from __future__ import annotations DOMAIN = "homeworks" CONF_ADDR = "addr" +CONF_BUTTONS = "buttons" CONF_CONTROLLER_ID = "controller_id" CONF_DIMMERS = "dimmers" CONF_INDEX = "index" CONF_KEYPADS = "keypads" +CONF_NUMBER = "number" CONF_RATE = "rate" +CONF_RELEASE_DELAY = "release_delay" DEFAULT_BUTTON_NAME = "Homeworks button" DEFAULT_KEYPAD_NAME = "Homeworks keypad" diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index 4c5a657df36..a9b0e2587df 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -97,11 +97,6 @@ class HomeworksLight(HomeworksEntity, LightEntity): float((level * 100.0) / 255.0), self._rate, 0, self._addr ) - @property - def extra_state_attributes(self) -> dict[str, str]: - """Supported attributes.""" - return {"homeworks_address": self._addr} - @property def is_on(self) -> bool: """Is the light on/off.""" diff --git a/homeassistant/components/homeworks/strings.json b/homeassistant/components/homeworks/strings.json index 3154d3e145e..2a1ddb44b44 100644 --- a/homeassistant/components/homeworks/strings.json +++ b/homeassistant/components/homeworks/strings.json @@ -31,5 +31,105 @@ "description": "Add a Lutron Homeworks controller" } } + }, + "options": { + "error": { + "duplicated_addr": "The specified address is already in use", + "duplicated_number": "The specified number is already in use", + "invalid_addr": "Invalid address" + }, + "step": { + "init": { + "menu_options": { + "add_keypad": "Add keypad", + "add_light": "Add light", + "remove_keypad": "Remove keypad", + "remove_light": "Remove light", + "select_edit_keypad": "Configure keypad", + "select_edit_light": "Configure light" + } + }, + "add_button": { + "data": { + "name": "[%key:common::config_flow::data::name%]", + "number": "Number", + "release_delay": "Release delay" + }, + "data_description": { + "number": "Button number in the range 1 to 24", + "release_delay": "Time between press and release, set to 0 to only press" + }, + "title": "[%key:component::homeworks::options::step::init::menu_options::add_keypad%]" + }, + "add_keypad": { + "data": { + "name": "[%key:common::config_flow::data::name%]", + "addr": "Address" + }, + "data_description": { + "addr": "Keypad address, must be formatted as `[##:##:##:##]`" + }, + "title": "[%key:component::homeworks::options::step::init::menu_options::add_keypad%]" + }, + "add_light": { + "data": { + "name": "[%key:common::config_flow::data::name%]", + "addr": "[%key:component::homeworks::options::step::add_keypad::data::addr%]", + "rate": "Fade rate" + }, + "data_description": { + "addr": "Keypad address, must be formatted as `[##:##:##:##]`", + "rate": "Time in seconds for the light to transition to a new brightness level" + }, + "title": "[%key:component::homeworks::options::step::init::menu_options::add_light%]" + }, + "edit_button": { + "data": { + "release_delay": "[%key:component::homeworks::options::step::add_button::data::release_delay%]" + }, + "data_description": { + "release_delay": "[%key:component::homeworks::options::step::add_button::data_description::release_delay%]" + }, + "title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::select_edit_button%]" + }, + "edit_keypad": { + "menu_options": { + "add_button": "Add button", + "remove_button": "Remove button", + "select_edit_button": "Configure button" + } + }, + "edit_light": { + "data": { + "rate": "[%key:component::homeworks::options::step::add_light::data::rate%]" + }, + "data_description": { + "rate": "[%key:component::homeworks::options::step::add_light::data_description::rate%]" + }, + "description": "Select a light to configure", + "title": "[%key:component::homeworks::options::step::init::menu_options::select_edit_light%]" + }, + "remove_button": { + "description": "Select buttons to remove", + "title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::remove_button%]" + }, + "remove_keypad": { + "description": "Select keypads to remove", + "title": "[%key:component::homeworks::options::step::init::menu_options::remove_keypad%]" + }, + "remove_light": { + "description": "Select lights to remove", + "title": "[%key:component::homeworks::options::step::init::menu_options::remove_light%]" + }, + "select_edit_button": { + "title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::select_edit_button%]" + }, + "select_edit_keypad": { + "title": "[%key:component::homeworks::options::step::init::menu_options::select_edit_keypad%]" + }, + "select_edit_light": { + "title": "[%key:component::homeworks::options::step::init::menu_options::select_edit_light%]" + } + } } } diff --git a/tests/components/homeworks/conftest.py b/tests/components/homeworks/conftest.py index 32b77781097..273b8f1ae4b 100644 --- a/tests/components/homeworks/conftest.py +++ b/tests/components/homeworks/conftest.py @@ -6,10 +6,13 @@ import pytest from homeassistant.components.homeworks.const import ( CONF_ADDR, + CONF_BUTTONS, CONF_CONTROLLER_ID, CONF_DIMMERS, CONF_KEYPADS, + CONF_NUMBER, CONF_RATE, + CONF_RELEASE_DELAY, DOMAIN, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT @@ -39,6 +42,23 @@ def mock_config_entry() -> MockConfigEntry: { CONF_ADDR: "[02:08:02:01]", CONF_NAME: "Foyer Keypad", + CONF_BUTTONS: [ + { + CONF_NAME: "Morning", + CONF_NUMBER: 1, + CONF_RELEASE_DELAY: None, + }, + { + CONF_NAME: "Relax", + CONF_NUMBER: 2, + CONF_RELEASE_DELAY: None, + }, + { + CONF_NAME: "Dim up", + CONF_NUMBER: 3, + CONF_RELEASE_DELAY: 0.2, + }, + ], } ], }, diff --git a/tests/components/homeworks/test_config_flow.py b/tests/components/homeworks/test_config_flow.py index 0975abf1f82..22a8127b135 100644 --- a/tests/components/homeworks/test_config_flow.py +++ b/tests/components/homeworks/test_config_flow.py @@ -4,12 +4,16 @@ from unittest.mock import ANY, MagicMock import pytest from pytest_unordered import unordered +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.homeworks.const import ( CONF_ADDR, + CONF_BUTTONS, CONF_DIMMERS, CONF_INDEX, CONF_KEYPADS, + CONF_NUMBER, CONF_RATE, + CONF_RELEASE_DELAY, DOMAIN, ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN @@ -154,6 +158,23 @@ async def test_import_flow( { CONF_ADDR: "[02:08:02:01]", CONF_NAME: "Foyer Keypad", + CONF_BUTTONS: [ + { + CONF_NAME: "Morning", + CONF_NUMBER: 1, + CONF_RELEASE_DELAY: None, + }, + { + CONF_NAME: "Relax", + CONF_NUMBER: 2, + CONF_RELEASE_DELAY: None, + }, + { + CONF_NAME: "Dim up", + CONF_NUMBER: 3, + CONF_RELEASE_DELAY: 0.2, + }, + ], } ], }, @@ -180,6 +201,15 @@ async def test_import_flow( "keypads": [ { "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], "name": "Foyer Keypad", } ], @@ -320,6 +350,15 @@ async def test_options_add_remove_light_flow( "keypads": [ { "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], "name": "Foyer Keypad", } ], @@ -362,6 +401,15 @@ async def test_options_add_remove_light_flow( "keypads": [ { "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], "name": "Foyer Keypad", } ], @@ -412,9 +460,18 @@ async def test_options_add_remove_keypad_flow( "keypads": [ { "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], "name": "Foyer Keypad", }, - {"addr": "[02:08:03:01]", "name": "Hall Keypad"}, + {"addr": "[02:08:03:01]", "buttons": [], "name": "Hall Keypad"}, ], "port": 1234, } @@ -447,7 +504,7 @@ async def test_options_add_remove_keypad_flow( {"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}, ], "host": "192.168.0.1", - "keypads": [{"addr": "[02:08:03:01]", "name": "Hall Keypad"}], + "keypads": [{"addr": "[02:08:03:01]", "buttons": [], "name": "Hall Keypad"}], "port": 1234, } await hass.async_block_till_done() @@ -551,6 +608,15 @@ async def test_options_edit_light_no_lights_flow( "keypads": [ { "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], "name": "Foyer Keypad", } ], @@ -586,3 +652,285 @@ async def test_options_edit_light_flow_empty( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "select_edit_light" assert result["data_schema"].schema["index"].container == {} + + +async def test_options_add_button_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to add a button.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_keypad"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_keypad" + assert result["data_schema"].schema["index"].container == { + "0": "Foyer Keypad ([02:08:02:01])" + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"index": "0"}, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "edit_keypad" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "add_button"} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_button" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "Dim down", + CONF_NUMBER: 4, + CONF_RELEASE_DELAY: 0.2, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + { + "name": "Dim down", + "number": 4, + "release_delay": 0.2, + }, + ], + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the new entities were added + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 4 + + +async def test_options_add_button_flow_duplicate( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to add a button.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_keypad"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_keypad" + assert result["data_schema"].schema["index"].container == { + "0": "Foyer Keypad ([02:08:02:01])" + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"index": "0"}, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "edit_keypad" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "add_button"} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "add_button" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_NAME: "Dim down", + CONF_NUMBER: 1, + CONF_RELEASE_DELAY: 0.2, + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "duplicated_number"} + + +async def test_options_edit_button_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to add a button.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_keypad"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_keypad" + assert result["data_schema"].schema["index"].container == { + "0": "Foyer Keypad ([02:08:02:01])" + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"index": "0"}, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "edit_keypad" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_button"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_button" + assert result["data_schema"].schema["index"].container == { + "0": "Morning (1)", + "1": "Relax (2)", + "2": "Dim up (3)", + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"index": "0"}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "edit_button" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_RELEASE_DELAY: 0, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": 0.0, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the new entities were added + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 + + +async def test_options_remove_button_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test options flow to remove a button.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "select_edit_keypad"} + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "select_edit_keypad" + assert result["data_schema"].schema["index"].container == { + "0": "Foyer Keypad ([02:08:02:01])" + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"index": "0"}, + ) + assert result["type"] == FlowResultType.MENU + assert result["step_id"] == "edit_keypad" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], {"next_step_id": "remove_button"} + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "remove_button" + assert result["data_schema"].schema["index"].options == { + "0": "Morning (1)", + "1": "Relax (2)", + "2": "Dim up (3)", + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_INDEX: ["0"]} + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "controller_id": "main_controller", + "dimmers": [{"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "buttons": [ + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + await hass.async_block_till_done() + + # Check the entities were removed + assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2 From 1824ff643889158800e6690a17681dcbc3ead203 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 19:21:11 +0100 Subject: [PATCH 0371/1691] Remove entity description mixin in Airzone (#112378) --- homeassistant/components/airzone/select.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/airzone/select.py b/homeassistant/components/airzone/select.py index 6f69d4454ee..5d60b777f2e 100644 --- a/homeassistant/components/airzone/select.py +++ b/homeassistant/components/airzone/select.py @@ -26,19 +26,14 @@ from .coordinator import AirzoneUpdateCoordinator from .entity import AirzoneEntity, AirzoneZoneEntity -@dataclass(frozen=True) -class AirzoneSelectDescriptionMixin: - """Define an entity description mixin for select entities.""" +@dataclass(frozen=True, kw_only=True) +class AirzoneSelectDescription(SelectEntityDescription): + """Class to describe an Airzone select entity.""" api_param: str options_dict: dict[str, int] -@dataclass(frozen=True) -class AirzoneSelectDescription(SelectEntityDescription, AirzoneSelectDescriptionMixin): - """Class to describe an Airzone select entity.""" - - GRILLE_ANGLE_DICT: Final[dict[str, int]] = { "90deg": GrilleAngle.DEG_90, "50deg": GrilleAngle.DEG_50, From 3cfe34665671ce475dbb77c578167439431f6611 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 5 Mar 2024 20:44:50 +0100 Subject: [PATCH 0372/1691] Remove list comprehension when adding entities in Nextcloud (#112429) --- homeassistant/components/nextcloud/binary_sensor.py | 8 +++----- homeassistant/components/nextcloud/sensor.py | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/nextcloud/binary_sensor.py b/homeassistant/components/nextcloud/binary_sensor.py index 313d555a3d7..e4068695086 100644 --- a/homeassistant/components/nextcloud/binary_sensor.py +++ b/homeassistant/components/nextcloud/binary_sensor.py @@ -58,11 +58,9 @@ async def async_setup_entry( """Set up the Nextcloud binary sensors.""" coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - NextcloudBinarySensor(coordinator, entry, sensor) - for sensor in BINARY_SENSORS - if sensor.key in coordinator.data - ] + NextcloudBinarySensor(coordinator, entry, sensor) + for sensor in BINARY_SENSORS + if sensor.key in coordinator.data ) diff --git a/homeassistant/components/nextcloud/sensor.py b/homeassistant/components/nextcloud/sensor.py index d80db0f3ea6..7d3f14e3b7f 100644 --- a/homeassistant/components/nextcloud/sensor.py +++ b/homeassistant/components/nextcloud/sensor.py @@ -606,11 +606,9 @@ async def async_setup_entry( """Set up the Nextcloud sensors.""" coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - NextcloudSensor(coordinator, entry, sensor) - for sensor in SENSORS - if sensor.key in coordinator.data - ] + NextcloudSensor(coordinator, entry, sensor) + for sensor in SENSORS + if sensor.key in coordinator.data ) From 85edafa88742f102a2b27231a4241058e6e7fb7a Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 5 Mar 2024 20:46:43 +0100 Subject: [PATCH 0373/1691] Remove list comprehension when adding entities in opengarage (#112430) --- .../components/opengarage/binary_sensor.py | 14 ++++++-------- homeassistant/components/opengarage/sensor.py | 16 +++++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/opengarage/binary_sensor.py b/homeassistant/components/opengarage/binary_sensor.py index 22f118ca804..1b40aacffe5 100644 --- a/homeassistant/components/opengarage/binary_sensor.py +++ b/homeassistant/components/opengarage/binary_sensor.py @@ -35,14 +35,12 @@ async def async_setup_entry( entry.entry_id ] async_add_entities( - [ - OpenGarageBinarySensor( - open_garage_data_coordinator, - cast(str, entry.unique_id), - description, - ) - for description in SENSOR_TYPES - ], + OpenGarageBinarySensor( + open_garage_data_coordinator, + cast(str, entry.unique_id), + description, + ) + for description in SENSOR_TYPES ) diff --git a/homeassistant/components/opengarage/sensor.py b/homeassistant/components/opengarage/sensor.py index b1d6cb921fa..c3fa3decc82 100644 --- a/homeassistant/components/opengarage/sensor.py +++ b/homeassistant/components/opengarage/sensor.py @@ -65,15 +65,13 @@ async def async_setup_entry( entry.entry_id ] async_add_entities( - [ - OpenGarageSensor( - open_garage_data_coordinator, - cast(str, entry.unique_id), - description, - ) - for description in SENSOR_TYPES - if description.key in open_garage_data_coordinator.data - ], + OpenGarageSensor( + open_garage_data_coordinator, + cast(str, entry.unique_id), + description, + ) + for description in SENSOR_TYPES + if description.key in open_garage_data_coordinator.data ) From ff21a2fcca0cc8ea4e947727bdc70d973be7f7e6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 20:47:01 +0100 Subject: [PATCH 0374/1691] Remove entity description mixin in Electric Kiwi (#112411) --- .../components/electric_kiwi/sensor.py | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/electric_kiwi/sensor.py b/homeassistant/components/electric_kiwi/sensor.py index 83431dfd925..5f0351fd39b 100644 --- a/homeassistant/components/electric_kiwi/sensor.py +++ b/homeassistant/components/electric_kiwi/sensor.py @@ -34,20 +34,13 @@ ATTR_NEXT_BILLING_DATE = "next_billing_date" ATTR_HOP_PERCENTAGE = "hop_percentage" -@dataclass(frozen=True) -class ElectricKiwiAccountRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class ElectricKiwiAccountSensorEntityDescription(SensorEntityDescription): + """Describes Electric Kiwi sensor entity.""" value_func: Callable[[AccountBalance], float | datetime] -@dataclass(frozen=True) -class ElectricKiwiAccountSensorEntityDescription( - SensorEntityDescription, ElectricKiwiAccountRequiredKeysMixin -): - """Describes Electric Kiwi sensor entity.""" - - ACCOUNT_SENSOR_TYPES: tuple[ElectricKiwiAccountSensorEntityDescription, ...] = ( ElectricKiwiAccountSensorEntityDescription( key=ATTR_TOTAL_RUNNING_BALANCE, @@ -85,21 +78,13 @@ ACCOUNT_SENSOR_TYPES: tuple[ElectricKiwiAccountSensorEntityDescription, ...] = ( ) -@dataclass(frozen=True) -class ElectricKiwiHOPRequiredKeysMixin: - """Mixin for required HOP keys.""" +@dataclass(frozen=True, kw_only=True) +class ElectricKiwiHOPSensorEntityDescription(SensorEntityDescription): + """Describes Electric Kiwi HOP sensor entity.""" value_func: Callable[[Hop], datetime] -@dataclass(frozen=True) -class ElectricKiwiHOPSensorEntityDescription( - SensorEntityDescription, - ElectricKiwiHOPRequiredKeysMixin, -): - """Describes Electric Kiwi HOP sensor entity.""" - - def _check_and_move_time(hop: Hop, time: str) -> datetime: """Return the time a day forward if HOP end_time is in the past.""" date_time = datetime.combine( From 32bb33c55e0db39e7b6150c128c62a8905fb5ca0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:06:13 +0100 Subject: [PATCH 0375/1691] Migrate Azure DevOps to has entity name (#112420) --- homeassistant/components/azure_devops/__init__.py | 2 ++ homeassistant/components/azure_devops/sensor.py | 6 +++--- homeassistant/components/azure_devops/strings.json | 7 +++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index edd06d69d2e..5a447d485d2 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -98,6 +98,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class AzureDevOpsEntity(CoordinatorEntity[DataUpdateCoordinator[list[DevOpsBuild]]]): """Defines a base Azure DevOps entity.""" + _attr_has_entity_name = True + entity_description: AzureDevOpsEntityDescription def __init__( diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 71881ba2a16..7eb5a452ace 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -19,8 +19,7 @@ from .const import CONF_ORG, DOMAIN @dataclass(frozen=True, kw_only=True) class AzureDevOpsSensorEntityDescription( - AzureDevOpsEntityDescription, - SensorEntityDescription, + AzureDevOpsEntityDescription, SensorEntityDescription ): """Class describing Azure DevOps sensor entities.""" @@ -40,7 +39,8 @@ async def async_setup_entry( coordinator, AzureDevOpsSensorEntityDescription( key=f"{build.project.id}_{build.definition.id}_latest_build", - name=f"{build.project.name} {build.definition.name} Latest Build", + translation_key="latest_build", + translation_placeholders={"definition_name": build.definition.name}, icon="mdi:pipe", attrs=lambda build: { "definition_id": build.definition.id, diff --git a/homeassistant/components/azure_devops/strings.json b/homeassistant/components/azure_devops/strings.json index ad8ebaa016e..c163aee5b7f 100644 --- a/homeassistant/components/azure_devops/strings.json +++ b/homeassistant/components/azure_devops/strings.json @@ -28,5 +28,12 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } + }, + "entity": { + "sensor": { + "latest_build": { + "name": "{definition_name} latest build" + } + } } } From 9a24e97ecbd83844868cda6a6fd3940b25d92395 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:08:57 +0100 Subject: [PATCH 0376/1691] Remove entity description mixin in Devolo Home Network (#112399) * Remove entity description mixin in Devolo Home Network * Fix --- .../components/devolo_home_network/binary_sensor.py | 13 +++---------- .../components/devolo_home_network/button.py | 13 +++---------- .../components/devolo_home_network/image.py | 13 +++---------- .../components/devolo_home_network/sensor.py | 13 ++++--------- .../components/devolo_home_network/switch.py | 13 +++---------- .../components/devolo_home_network/update.py | 13 +++---------- 6 files changed, 19 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/devolo_home_network/binary_sensor.py b/homeassistant/components/devolo_home_network/binary_sensor.py index cf8358b69a3..8d7f578651a 100644 --- a/homeassistant/components/devolo_home_network/binary_sensor.py +++ b/homeassistant/components/devolo_home_network/binary_sensor.py @@ -32,20 +32,13 @@ def _is_connected_to_router(entity: DevoloBinarySensorEntity) -> bool: ) -@dataclass(frozen=True) -class DevoloBinarySensorRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DevoloBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes devolo sensor entity.""" value_func: Callable[[DevoloBinarySensorEntity], bool] -@dataclass(frozen=True) -class DevoloBinarySensorEntityDescription( - BinarySensorEntityDescription, DevoloBinarySensorRequiredKeysMixin -): - """Describes devolo sensor entity.""" - - SENSOR_TYPES: dict[str, DevoloBinarySensorEntityDescription] = { CONNECTED_TO_ROUTER: DevoloBinarySensorEntityDescription( key=CONNECTED_TO_ROUTER, diff --git a/homeassistant/components/devolo_home_network/button.py b/homeassistant/components/devolo_home_network/button.py index eba1ad05157..3bcdf6c4610 100644 --- a/homeassistant/components/devolo_home_network/button.py +++ b/homeassistant/components/devolo_home_network/button.py @@ -22,20 +22,13 @@ from .const import DOMAIN, IDENTIFY, PAIRING, RESTART, START_WPS from .entity import DevoloEntity -@dataclass(frozen=True) -class DevoloButtonRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DevoloButtonEntityDescription(ButtonEntityDescription): + """Describes devolo button entity.""" press_func: Callable[[Device], Awaitable[bool]] -@dataclass(frozen=True) -class DevoloButtonEntityDescription( - ButtonEntityDescription, DevoloButtonRequiredKeysMixin -): - """Describes devolo button entity.""" - - BUTTON_TYPES: dict[str, DevoloButtonEntityDescription] = { IDENTIFY: DevoloButtonEntityDescription( key=IDENTIFY, diff --git a/homeassistant/components/devolo_home_network/image.py b/homeassistant/components/devolo_home_network/image.py index 72cf4f57c1d..16b89bb1180 100644 --- a/homeassistant/components/devolo_home_network/image.py +++ b/homeassistant/components/devolo_home_network/image.py @@ -21,20 +21,13 @@ from .const import DOMAIN, IMAGE_GUEST_WIFI, SWITCH_GUEST_WIFI from .entity import DevoloCoordinatorEntity -@dataclass(frozen=True) -class DevoloImageRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DevoloImageEntityDescription(ImageEntityDescription): + """Describes devolo image entity.""" image_func: Callable[[WifiGuestAccessGet], bytes] -@dataclass(frozen=True) -class DevoloImageEntityDescription( - ImageEntityDescription, DevoloImageRequiredKeysMixin -): - """Describes devolo image entity.""" - - IMAGE_TYPES: dict[str, DevoloImageEntityDescription] = { IMAGE_GUEST_WIFI: DevoloImageEntityDescription( key=IMAGE_GUEST_WIFI, diff --git a/homeassistant/components/devolo_home_network/sensor.py b/homeassistant/components/devolo_home_network/sensor.py index 750bb9ad13d..8a7676ceb26 100644 --- a/homeassistant/components/devolo_home_network/sensor.py +++ b/homeassistant/components/devolo_home_network/sensor.py @@ -49,19 +49,14 @@ class DataRateDirection(StrEnum): TX = "tx_rate" -@dataclass(frozen=True) -class DevoloSensorRequiredKeysMixin(Generic[_CoordinatorDataT]): - """Mixin for required keys.""" - - value_func: Callable[[_CoordinatorDataT], float] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class DevoloSensorEntityDescription( - SensorEntityDescription, DevoloSensorRequiredKeysMixin[_CoordinatorDataT] + SensorEntityDescription, Generic[_CoordinatorDataT] ): """Describes devolo sensor entity.""" + value_func: Callable[[_CoordinatorDataT], float] + SENSOR_TYPES: dict[str, DevoloSensorEntityDescription[Any]] = { CONNECTED_PLC_DEVICES: DevoloSensorEntityDescription[LogicalNetwork]( diff --git a/homeassistant/components/devolo_home_network/switch.py b/homeassistant/components/devolo_home_network/switch.py index af0569a016f..05169073369 100644 --- a/homeassistant/components/devolo_home_network/switch.py +++ b/homeassistant/components/devolo_home_network/switch.py @@ -23,22 +23,15 @@ from .entity import DevoloCoordinatorEntity _DataT = TypeVar("_DataT", bound=WifiGuestAccessGet | bool) -@dataclass(frozen=True) -class DevoloSwitchRequiredKeysMixin(Generic[_DataT]): - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DevoloSwitchEntityDescription(SwitchEntityDescription, Generic[_DataT]): + """Describes devolo switch entity.""" is_on_func: Callable[[_DataT], bool] turn_on_func: Callable[[Device], Awaitable[bool]] turn_off_func: Callable[[Device], Awaitable[bool]] -@dataclass(frozen=True) -class DevoloSwitchEntityDescription( - SwitchEntityDescription, DevoloSwitchRequiredKeysMixin[_DataT] -): - """Describes devolo switch entity.""" - - SWITCH_TYPES: dict[str, DevoloSwitchEntityDescription[Any]] = { SWITCH_GUEST_WIFI: DevoloSwitchEntityDescription[WifiGuestAccessGet]( key=SWITCH_GUEST_WIFI, diff --git a/homeassistant/components/devolo_home_network/update.py b/homeassistant/components/devolo_home_network/update.py index 03f86381307..88c10b61cfc 100644 --- a/homeassistant/components/devolo_home_network/update.py +++ b/homeassistant/components/devolo_home_network/update.py @@ -26,21 +26,14 @@ from .const import DOMAIN, REGULAR_FIRMWARE from .entity import DevoloCoordinatorEntity -@dataclass(frozen=True) -class DevoloUpdateRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DevoloUpdateEntityDescription(UpdateEntityDescription): + """Describes devolo update entity.""" latest_version: Callable[[UpdateFirmwareCheck], str] update_func: Callable[[Device], Awaitable[bool]] -@dataclass(frozen=True) -class DevoloUpdateEntityDescription( - UpdateEntityDescription, DevoloUpdateRequiredKeysMixin -): - """Describes devolo update entity.""" - - UPDATE_TYPES: dict[str, DevoloUpdateEntityDescription] = { REGULAR_FIRMWARE: DevoloUpdateEntityDescription( key=REGULAR_FIRMWARE, From b025d6c6f271212a3e7bf08f3999ad3e8487ed64 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 5 Mar 2024 21:10:02 +0100 Subject: [PATCH 0377/1691] Remove list comprehension when adding entities in Smartthings (#112432) --- homeassistant/components/smartthings/fan.py | 8 +++----- homeassistant/components/smartthings/lock.py | 8 +++----- homeassistant/components/smartthings/scene.py | 2 +- homeassistant/components/smartthings/switch.py | 8 +++----- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 0e15ea7800a..37c19eecd6b 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -31,11 +31,9 @@ async def async_setup_entry( """Add fans for a config entry.""" broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( - [ - SmartThingsFan(device) - for device in broker.devices.values() - if broker.any_assigned(device.device_id, "fan") - ] + SmartThingsFan(device) + for device in broker.devices.values() + if broker.any_assigned(device.device_id, "fan") ) diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index c0fbc32fa19..4e726ddc991 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -33,11 +33,9 @@ async def async_setup_entry( """Add locks for a config entry.""" broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( - [ - SmartThingsLock(device) - for device in broker.devices.values() - if broker.any_assigned(device.device_id, "lock") - ] + SmartThingsLock(device) + for device in broker.devices.values() + if broker.any_assigned(device.device_id, "lock") ) diff --git a/homeassistant/components/smartthings/scene.py b/homeassistant/components/smartthings/scene.py index ffdb900237e..faf58ede014 100644 --- a/homeassistant/components/smartthings/scene.py +++ b/homeassistant/components/smartthings/scene.py @@ -16,7 +16,7 @@ async def async_setup_entry( ) -> None: """Add switches for a config entry.""" broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] - async_add_entities([SmartThingsScene(scene) for scene in broker.scenes.values()]) + async_add_entities(SmartThingsScene(scene) for scene in broker.scenes.values()) class SmartThingsScene(Scene): diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index e6432dcb50c..b1a859847a3 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -23,11 +23,9 @@ async def async_setup_entry( """Add switches for a config entry.""" broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] async_add_entities( - [ - SmartThingsSwitch(device) - for device in broker.devices.values() - if broker.any_assigned(device.device_id, "switch") - ] + SmartThingsSwitch(device) + for device in broker.devices.values() + if broker.any_assigned(device.device_id, "switch") ) From 968f5f1a34dee035392ba132a46594b9ae3615b9 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 5 Mar 2024 21:10:30 +0100 Subject: [PATCH 0378/1691] Remove list comprehension when adding entities in Wallbox (#112433) --- homeassistant/components/wallbox/lock.py | 8 +++----- homeassistant/components/wallbox/number.py | 8 +++----- homeassistant/components/wallbox/sensor.py | 8 +++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/wallbox/lock.py b/homeassistant/components/wallbox/lock.py index 11a66a4814c..195d76e9238 100644 --- a/homeassistant/components/wallbox/lock.py +++ b/homeassistant/components/wallbox/lock.py @@ -42,11 +42,9 @@ async def async_setup_entry( raise PlatformNotReady from exc async_add_entities( - [ - WallboxLock(coordinator, description) - for ent in coordinator.data - if (description := LOCK_TYPES.get(ent)) - ] + WallboxLock(coordinator, description) + for ent in coordinator.data + if (description := LOCK_TYPES.get(ent)) ) diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index 76cf8316959..695a7960e51 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -92,11 +92,9 @@ async def async_setup_entry( raise PlatformNotReady from exc async_add_entities( - [ - WallboxNumber(coordinator, entry, description) - for ent in coordinator.data - if (description := NUMBER_TYPES.get(ent)) - ] + WallboxNumber(coordinator, entry, description) + for ent in coordinator.data + if (description := NUMBER_TYPES.get(ent)) ) diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index 5a825722d53..50312487e15 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -161,11 +161,9 @@ async def async_setup_entry( coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - WallboxSensor(coordinator, description) - for ent in coordinator.data - if (description := SENSOR_TYPES.get(ent)) - ] + WallboxSensor(coordinator, description) + for ent in coordinator.data + if (description := SENSOR_TYPES.get(ent)) ) From 1bac51142dc80228e3a1e1bc0b085ce3b40162ff Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 5 Mar 2024 21:13:14 +0100 Subject: [PATCH 0379/1691] Do not use list comprehension in async_add_entities in Unifi (#112435) Do not use list comprehension in async_add_entities --- homeassistant/components/unifi/hub/hub.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index c9054152abb..0188adf5c3f 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -196,12 +196,10 @@ class UnifiHub: def async_add_unifi_entities() -> None: """Add UniFi entity.""" async_add_entities( - [ - unifi_platform_entity(obj_id, self, description) - for description in descriptions - for obj_id in description.api_handler_fn(self.api) - if self._async_should_add_entity(description, obj_id) - ] + unifi_platform_entity(obj_id, self, description) + for description in descriptions + for obj_id in description.api_handler_fn(self.api) + if self._async_should_add_entity(description, obj_id) ) async_add_unifi_entities() From dda0f0d9aa72427e43b99357a2df01a0f67013bf Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 5 Mar 2024 21:28:50 +0100 Subject: [PATCH 0380/1691] Remove the deprecated YAML support for Aftership (#112390) --- .../components/aftership/config_flow.py | 27 +-------- homeassistant/components/aftership/sensor.py | 55 +------------------ .../components/aftership/test_config_flow.py | 42 +------------- 3 files changed, 5 insertions(+), 119 deletions(-) diff --git a/homeassistant/components/aftership/config_flow.py b/homeassistant/components/aftership/config_flow.py index 9d377ae8ce7..f48221840ba 100644 --- a/homeassistant/components/aftership/config_flow.py +++ b/homeassistant/components/aftership/config_flow.py @@ -8,10 +8,8 @@ from pyaftership import AfterShip, AfterShipException import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_API_KEY, CONF_NAME -from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN +from homeassistant.const import CONF_API_KEY from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import DOMAIN @@ -47,26 +45,3 @@ class AfterShipConfigFlow(ConfigFlow, domain=DOMAIN): data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}), errors=errors, ) - - async def async_step_import(self, config: dict[str, Any]) -> ConfigFlowResult: - """Import configuration from yaml.""" - async_create_issue( - self.hass, - HOMEASSISTANT_DOMAIN, - f"deprecated_yaml_{DOMAIN}", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml", - translation_placeholders={ - "domain": DOMAIN, - "integration_title": "AfterShip", - }, - ) - - self._async_abort_entries_match({CONF_API_KEY: config[CONF_API_KEY]}) - return self.async_create_entry( - title=config.get(CONF_NAME, "AfterShip"), - data={CONF_API_KEY: config[CONF_API_KEY]}, - ) diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index 055d31fc16d..f96281fce8f 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -5,24 +5,16 @@ import logging from typing import Any, Final from pyaftership import AfterShip, AfterShipException -import voluptuous as vol -from homeassistant.components.sensor import ( - PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA, - SensorEntity, -) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_API_KEY, CONF_NAME +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, ServiceCall -from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import Throttle from .const import ( @@ -33,7 +25,6 @@ from .const import ( CONF_SLUG, CONF_TITLE, CONF_TRACKING_NUMBER, - DEFAULT_NAME, DOMAIN, MIN_TIME_BETWEEN_UPDATES, REMOVE_TRACKING_SERVICE_SCHEMA, @@ -44,47 +35,7 @@ from .const import ( _LOGGER: Final = logging.getLogger(__name__) -PLATFORM_SCHEMA: Final = BASE_PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the AfterShip sensor platform.""" - aftership = AfterShip( - api_key=config[CONF_API_KEY], session=async_get_clientsession(hass) - ) - try: - await aftership.trackings.list() - except AfterShipException: - async_create_issue( - hass, - DOMAIN, - "deprecated_yaml_import_issue_cannot_connect", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml_import_issue_cannot_connect", - translation_placeholders={ - "integration_title": "AfterShip", - "url": "/config/integrations/dashboard/add?domain=aftership", - }, - ) - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=config - ) - ) +PLATFORM_SCHEMA: Final = cv.removed(DOMAIN, raise_if_present=False) async def async_setup_entry( diff --git a/tests/components/aftership/test_config_flow.py b/tests/components/aftership/test_config_flow.py index 4668e7a61e4..8e9f1a34d31 100644 --- a/tests/components/aftership/test_config_flow.py +++ b/tests/components/aftership/test_config_flow.py @@ -4,13 +4,10 @@ from unittest.mock import AsyncMock, patch from pyaftership import AfterShipException from homeassistant.components.aftership.const import DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from homeassistant.helpers import issue_registry as ir - -from tests.common import MockConfigEntry async def test_full_user_flow(hass: HomeAssistant, mock_setup_entry) -> None: @@ -75,40 +72,3 @@ async def test_flow_cannot_connect(hass: HomeAssistant, mock_setup_entry) -> Non assert result["data"] == { CONF_API_KEY: "mock-api-key", } - - -async def test_import_flow( - hass: HomeAssistant, issue_registry: ir.IssueRegistry, mock_setup_entry -) -> None: - """Test importing yaml config.""" - - with patch( - "homeassistant.components.aftership.config_flow.AfterShip", - return_value=AsyncMock(), - ) as mock_aftership: - mock_aftership.return_value.trackings.return_value.list.return_value = {} - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_API_KEY: "yaml-api-key"}, - ) - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "AfterShip" - assert result["data"] == { - CONF_API_KEY: "yaml-api-key", - } - assert len(issue_registry.issues) == 1 - - -async def test_import_flow_already_exists( - hass: HomeAssistant, issue_registry: ir.IssueRegistry -) -> None: - """Test importing yaml config where entry already exists.""" - entry = MockConfigEntry(domain=DOMAIN, data={CONF_API_KEY: "yaml-api-key"}) - entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_API_KEY: "yaml-api-key"} - ) - assert result["type"] == FlowResultType.ABORT - assert result["reason"] == "already_configured" - assert len(issue_registry.issues) == 1 From 5dea902a9c5ef460bce79696a1c5bd99c0806439 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:34:50 +0100 Subject: [PATCH 0381/1691] Add icon translations to Azure DevOps (#112436) --- homeassistant/components/azure_devops/icons.json | 9 +++++++++ homeassistant/components/azure_devops/sensor.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/azure_devops/icons.json diff --git a/homeassistant/components/azure_devops/icons.json b/homeassistant/components/azure_devops/icons.json new file mode 100644 index 00000000000..de720b46106 --- /dev/null +++ b/homeassistant/components/azure_devops/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "latest_build": { + "default": "mdi:pipe" + } + } + } +} diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 7eb5a452ace..94161d4ccbf 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -41,7 +41,6 @@ async def async_setup_entry( key=f"{build.project.id}_{build.definition.id}_latest_build", translation_key="latest_build", translation_placeholders={"definition_name": build.definition.name}, - icon="mdi:pipe", attrs=lambda build: { "definition_id": build.definition.id, "definition_name": build.definition.name, From ed23bb7c042a18005eb48b0e6b1159c3d437be60 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:36:11 +0100 Subject: [PATCH 0382/1691] Small cleanup in Dexcom (#112425) --- homeassistant/components/dexcom/__init__.py | 23 +++++---------------- homeassistant/components/dexcom/const.py | 3 --- homeassistant/components/dexcom/sensor.py | 5 ++--- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/dexcom/__init__.py b/homeassistant/components/dexcom/__init__.py index 2d7c2120758..3ef6f7fd72e 100644 --- a/homeassistant/components/dexcom/__init__.py +++ b/homeassistant/components/dexcom/__init__.py @@ -10,15 +10,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import ( - CONF_SERVER, - COORDINATOR, - DOMAIN, - MG_DL, - PLATFORMS, - SERVER_OUS, - UNDO_UPDATE_LISTENER, -) +from .const import CONF_SERVER, DOMAIN, MG_DL, PLATFORMS, SERVER_OUS _LOGGER = logging.getLogger(__name__) @@ -59,11 +51,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) await coordinator.async_config_entry_first_refresh() - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = { - COORDINATOR: coordinator, - UNDO_UPDATE_LISTENER: entry.add_update_listener(update_listener), - } + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + + entry.async_on_unload(entry.add_update_listener(update_listener)) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -72,10 +62,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() - - if unload_ok: + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/homeassistant/components/dexcom/const.py b/homeassistant/components/dexcom/const.py index 8712eeb10ad..1a0eeeba090 100644 --- a/homeassistant/components/dexcom/const.py +++ b/homeassistant/components/dexcom/const.py @@ -24,6 +24,3 @@ CONF_SERVER = "server" SERVER_OUS = "EU" SERVER_US = "US" - -COORDINATOR = "coordinator" -UNDO_UPDATE_LISTENER = "undo_update_listener" diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index 592419abc1b..9a7a8631092 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -12,7 +12,7 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, ) -from .const import COORDINATOR, DOMAIN, GLUCOSE_TREND_ICON, MG_DL +from .const import DOMAIN, GLUCOSE_TREND_ICON, MG_DL async def async_setup_entry( @@ -21,7 +21,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Dexcom sensors.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + coordinator = hass.data[DOMAIN][config_entry.entry_id] username = config_entry.data[CONF_USERNAME] unit_of_measurement = config_entry.options[CONF_UNIT_OF_MEASUREMENT] async_add_entities( @@ -31,7 +31,6 @@ async def async_setup_entry( coordinator, username, config_entry.entry_id, unit_of_measurement ), ], - False, ) From 7c9891fff9f2ed00b730e9b561fb3a153dda96ba Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 5 Mar 2024 21:36:26 +0100 Subject: [PATCH 0383/1691] Break out UniFi config handling to own class (#111325) --- homeassistant/components/unifi/config_flow.py | 24 ++-- .../components/unifi/device_tracker.py | 22 ++-- homeassistant/components/unifi/hub/config.py | 123 ++++++++++++++++++ homeassistant/components/unifi/hub/hub.py | 114 +++------------- homeassistant/components/unifi/sensor.py | 20 +-- homeassistant/components/unifi/switch.py | 8 +- tests/components/unifi/conftest.py | 4 +- tests/components/unifi/test_hub.py | 20 +-- 8 files changed, 193 insertions(+), 142 deletions(-) create mode 100644 homeassistant/components/unifi/hub/config.py diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 0342707c657..ae55d7ffcc9 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -260,7 +260,7 @@ class UnifiOptionsFlowHandler(OptionsFlow): if self.config_entry.entry_id not in self.hass.data[UNIFI_DOMAIN]: return self.async_abort(reason="integration_not_setup") self.hub = self.hass.data[UNIFI_DOMAIN][self.config_entry.entry_id] - self.options[CONF_BLOCK_CLIENT] = self.hub.option_block_clients + self.options[CONF_BLOCK_CLIENT] = self.hub.config.option_block_clients if self.show_advanced_options: return await self.async_step_configure_entity_sources() @@ -288,11 +288,11 @@ class UnifiOptionsFlowHandler(OptionsFlow): { vol.Optional( CONF_TRACK_CLIENTS, - default=self.hub.option_track_clients, + default=self.hub.config.option_track_clients, ): bool, vol.Optional( CONF_TRACK_DEVICES, - default=self.hub.option_track_devices, + default=self.hub.config.option_track_devices, ): bool, vol.Optional( CONF_BLOCK_CLIENT, default=self.options[CONF_BLOCK_CLIENT] @@ -361,7 +361,7 @@ class UnifiOptionsFlowHandler(OptionsFlow): ssid_filter = {ssid: ssid for ssid in sorted(ssids)} selected_ssids_to_filter = [ - ssid for ssid in self.hub.option_ssid_filter if ssid in ssid_filter + ssid for ssid in self.hub.config.option_ssid_filter if ssid in ssid_filter ] return self.async_show_form( @@ -370,26 +370,28 @@ class UnifiOptionsFlowHandler(OptionsFlow): { vol.Optional( CONF_TRACK_CLIENTS, - default=self.hub.option_track_clients, + default=self.hub.config.option_track_clients, ): bool, vol.Optional( CONF_TRACK_WIRED_CLIENTS, - default=self.hub.option_track_wired_clients, + default=self.hub.config.option_track_wired_clients, ): bool, vol.Optional( CONF_TRACK_DEVICES, - default=self.hub.option_track_devices, + default=self.hub.config.option_track_devices, ): bool, vol.Optional( CONF_SSID_FILTER, default=selected_ssids_to_filter ): cv.multi_select(ssid_filter), vol.Optional( CONF_DETECTION_TIME, - default=int(self.hub.option_detection_time.total_seconds()), + default=int( + self.hub.config.option_detection_time.total_seconds() + ), ): int, vol.Optional( CONF_IGNORE_WIRED_BUG, - default=self.hub.option_ignore_wired_bug, + default=self.hub.config.option_ignore_wired_bug, ): bool, } ), @@ -449,11 +451,11 @@ class UnifiOptionsFlowHandler(OptionsFlow): { vol.Optional( CONF_ALLOW_BANDWIDTH_SENSORS, - default=self.hub.option_allow_bandwidth_sensors, + default=self.hub.config.option_allow_bandwidth_sensors, ): bool, vol.Optional( CONF_ALLOW_UPTIME_SENSORS, - default=self.hub.option_allow_uptime_sensors, + default=self.hub.config.option_allow_uptime_sensors, ): bool, } ), diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 81ba99c4ebe..cce41135e4c 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -82,21 +82,21 @@ WIRELESS_DISCONNECTION = ( @callback def async_client_allowed_fn(hub: UnifiHub, obj_id: str) -> bool: """Check if client is allowed.""" - if obj_id in hub.option_supported_clients: + if obj_id in hub.config.option_supported_clients: return True - if not hub.option_track_clients: + if not hub.config.option_track_clients: return False client = hub.api.clients[obj_id] if client.mac not in hub.wireless_clients: - if not hub.option_track_wired_clients: + if not hub.config.option_track_wired_clients: return False elif ( client.essid - and hub.option_ssid_filter - and client.essid not in hub.option_ssid_filter + and hub.config.option_ssid_filter + and client.essid not in hub.config.option_ssid_filter ): return False @@ -109,20 +109,20 @@ def async_client_is_connected_fn(hub: UnifiHub, obj_id: str) -> bool: client = hub.api.clients[obj_id] if hub.wireless_clients.is_wireless(client) and client.is_wired: - if not hub.option_ignore_wired_bug: + if not hub.config.option_ignore_wired_bug: return False # Wired bug in action if ( not client.is_wired and client.essid - and hub.option_ssid_filter - and client.essid not in hub.option_ssid_filter + and hub.config.option_ssid_filter + and client.essid not in hub.config.option_ssid_filter ): return False if ( dt_util.utcnow() - dt_util.utc_from_timestamp(client.last_seen or 0) - > hub.option_detection_time + > hub.config.option_detection_time ): return False @@ -161,7 +161,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( + WIRELESS_CONNECTION + WIRELESS_DISCONNECTION ), - heartbeat_timedelta_fn=lambda hub, _: hub.option_detection_time, + heartbeat_timedelta_fn=lambda hub, _: hub.config.option_detection_time, is_connected_fn=async_client_is_connected_fn, name_fn=lambda client: client.name or client.hostname, object_fn=lambda api, obj_id: api.clients[obj_id], @@ -173,7 +173,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( UnifiTrackerEntityDescription[Devices, Device]( key="Device scanner", has_entity_name=True, - allowed_fn=lambda hub, obj_id: hub.option_track_devices, + allowed_fn=lambda hub, obj_id: hub.config.option_track_devices, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=lambda api, obj_id: None, diff --git a/homeassistant/components/unifi/hub/config.py b/homeassistant/components/unifi/hub/config.py new file mode 100644 index 00000000000..be2da7e47c9 --- /dev/null +++ b/homeassistant/components/unifi/hub/config.py @@ -0,0 +1,123 @@ +"""UniFi Network config entry abstraction.""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import timedelta +import ssl +from typing import Literal, Self + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + CONF_VERIFY_SSL, +) + +from ..const import ( + CONF_ALLOW_BANDWIDTH_SENSORS, + CONF_ALLOW_UPTIME_SENSORS, + CONF_BLOCK_CLIENT, + CONF_CLIENT_SOURCE, + CONF_DETECTION_TIME, + CONF_DPI_RESTRICTIONS, + CONF_IGNORE_WIRED_BUG, + CONF_SITE_ID, + CONF_SSID_FILTER, + CONF_TRACK_CLIENTS, + CONF_TRACK_DEVICES, + CONF_TRACK_WIRED_CLIENTS, + DEFAULT_ALLOW_BANDWIDTH_SENSORS, + DEFAULT_ALLOW_UPTIME_SENSORS, + DEFAULT_DETECTION_TIME, + DEFAULT_DPI_RESTRICTIONS, + DEFAULT_IGNORE_WIRED_BUG, + DEFAULT_TRACK_CLIENTS, + DEFAULT_TRACK_DEVICES, + DEFAULT_TRACK_WIRED_CLIENTS, +) + + +@dataclass +class UnifiConfig: + """Represent a UniFi config entry.""" + + entry: ConfigEntry + + host: str + port: int + username: str + password: str + site: str + ssl_context: ssl.SSLContext | Literal[False] + + option_supported_clients: list[str] + """Allow creating entities from clients.""" + + # Device tracker options + + option_track_clients: list[str] + """Config entry option to not track clients.""" + option_track_wired_clients: list[str] + """Config entry option to not track wired clients.""" + option_track_devices: bool + """Config entry option to not track devices.""" + option_ssid_filter: set[str] + """Config entry option listing what SSIDs are being used to track clients.""" + option_detection_time: timedelta + """Config entry option defining number of seconds from last seen to away""" + option_ignore_wired_bug: bool + """Config entry option to ignore wired bug.""" + + # Client control options + + option_block_clients: list[str] + """Config entry option with list of clients to control network access.""" + option_dpi_restrictions: bool + """Config entry option to control DPI restriction groups.""" + + # Statistics sensor options + + option_allow_bandwidth_sensors: bool + """Config entry option to allow bandwidth sensors.""" + option_allow_uptime_sensors: bool + """Config entry option to allow uptime sensors.""" + + @classmethod + def from_config_entry(cls, config_entry: ConfigEntry) -> Self: + """Create object from config entry.""" + config = config_entry.data + options = config_entry.options + return cls( + entry=config_entry, + host=config[CONF_HOST], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + port=config[CONF_PORT], + site=config[CONF_SITE_ID], + ssl_context=config.get(CONF_VERIFY_SSL, False), + option_supported_clients=options.get(CONF_CLIENT_SOURCE, []), + option_track_clients=options.get(CONF_TRACK_CLIENTS, DEFAULT_TRACK_CLIENTS), + option_track_wired_clients=options.get( + CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS + ), + option_track_devices=options.get(CONF_TRACK_DEVICES, DEFAULT_TRACK_DEVICES), + option_ssid_filter=set(options.get(CONF_SSID_FILTER, [])), + option_detection_time=timedelta( + seconds=options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) + ), + option_ignore_wired_bug=options.get( + CONF_IGNORE_WIRED_BUG, DEFAULT_IGNORE_WIRED_BUG + ), + option_block_clients=options.get(CONF_BLOCK_CLIENT, []), + option_dpi_restrictions=options.get( + CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS + ), + option_allow_bandwidth_sensors=options.get( + CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS + ), + option_allow_uptime_sensors=options.get( + CONF_ALLOW_UPTIME_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS + ), + ) diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index 0188adf5c3f..6acfd1759f6 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -10,7 +10,7 @@ from aiounifi.interfaces.api_handlers import ItemEvent from aiounifi.models.device import DeviceSetPoePortModeRequest from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, Platform +from homeassistant.const import Platform from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import ( @@ -29,31 +29,13 @@ import homeassistant.util.dt as dt_util from ..const import ( ATTR_MANUFACTURER, - CONF_ALLOW_BANDWIDTH_SENSORS, - CONF_ALLOW_UPTIME_SENSORS, - CONF_BLOCK_CLIENT, - CONF_CLIENT_SOURCE, - CONF_DETECTION_TIME, - CONF_DPI_RESTRICTIONS, - CONF_IGNORE_WIRED_BUG, CONF_SITE_ID, - CONF_SSID_FILTER, - CONF_TRACK_CLIENTS, - CONF_TRACK_DEVICES, - CONF_TRACK_WIRED_CLIENTS, - DEFAULT_ALLOW_BANDWIDTH_SENSORS, - DEFAULT_ALLOW_UPTIME_SENSORS, - DEFAULT_DETECTION_TIME, - DEFAULT_DPI_RESTRICTIONS, - DEFAULT_IGNORE_WIRED_BUG, - DEFAULT_TRACK_CLIENTS, - DEFAULT_TRACK_DEVICES, - DEFAULT_TRACK_WIRED_CLIENTS, DOMAIN as UNIFI_DOMAIN, PLATFORMS, UNIFI_WIRELESS_CLIENTS, ) from ..entity import UnifiEntity, UnifiEntityDescription +from .config import UnifiConfig from .websocket import UnifiWebsocket CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) @@ -67,8 +49,8 @@ class UnifiHub: ) -> None: """Initialize the system.""" self.hass = hass - self.config_entry = config_entry self.api = api + self.config = UnifiConfig.from_config_entry(config_entry) self.websocket = UnifiWebsocket(hass, api, self.signal_reachable) self.wireless_clients = hass.data[UNIFI_WIRELESS_CLIENTS] @@ -79,72 +61,12 @@ class UnifiHub: self._cancel_heartbeat_check: CALLBACK_TYPE | None = None self._heartbeat_time: dict[str, datetime] = {} - self.load_config_entry_options() - self.entities: dict[str, str] = {} self.known_objects: set[tuple[str, str]] = set() self.poe_command_queue: dict[str, dict[int, str]] = {} self._cancel_poe_command: CALLBACK_TYPE | None = None - def load_config_entry_options(self) -> None: - """Store attributes to avoid property call overhead since they are called frequently.""" - options = self.config_entry.options - - # Allow creating entities from clients. - self.option_supported_clients: list[str] = options.get(CONF_CLIENT_SOURCE, []) - - # Device tracker options - - # Config entry option to not track clients. - self.option_track_clients = options.get( - CONF_TRACK_CLIENTS, DEFAULT_TRACK_CLIENTS - ) - # Config entry option to not track wired clients. - self.option_track_wired_clients = options.get( - CONF_TRACK_WIRED_CLIENTS, DEFAULT_TRACK_WIRED_CLIENTS - ) - # Config entry option to not track devices. - self.option_track_devices: bool = options.get( - CONF_TRACK_DEVICES, DEFAULT_TRACK_DEVICES - ) - # Config entry option listing what SSIDs are being used to track clients. - self.option_ssid_filter = set(options.get(CONF_SSID_FILTER, [])) - # Config entry option defining number of seconds from last seen to away - self.option_detection_time = timedelta( - seconds=options.get(CONF_DETECTION_TIME, DEFAULT_DETECTION_TIME) - ) - # Config entry option to ignore wired bug. - self.option_ignore_wired_bug = options.get( - CONF_IGNORE_WIRED_BUG, DEFAULT_IGNORE_WIRED_BUG - ) - - # Client control options - - # Config entry option with list of clients to control network access. - self.option_block_clients: list[str] = options.get(CONF_BLOCK_CLIENT, []) - # Config entry option to control DPI restriction groups. - self.option_dpi_restrictions: bool = options.get( - CONF_DPI_RESTRICTIONS, DEFAULT_DPI_RESTRICTIONS - ) - - # Statistics sensor options - - # Config entry option to allow bandwidth sensors. - self.option_allow_bandwidth_sensors: bool = options.get( - CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS - ) - # Config entry option to allow uptime sensors. - self.option_allow_uptime_sensors: bool = options.get( - CONF_ALLOW_UPTIME_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS - ) - - @property - def host(self) -> str: - """Return the host of this hub.""" - host: str = self.config_entry.data[CONF_HOST] - return host - @property def available(self) -> bool: """Websocket connection state.""" @@ -219,7 +141,7 @@ class UnifiHub: partial(async_create_entity, description), ItemEvent.ADDED ) - self.config_entry.async_on_unload( + self.config.entry.async_on_unload( async_dispatcher_connect( self.hass, self.signal_options_update, @@ -232,12 +154,12 @@ class UnifiHub: @property def signal_reachable(self) -> str: """Integration specific event to signal a change in connection status.""" - return f"unifi-reachable-{self.config_entry.entry_id}" + return f"unifi-reachable-{self.config.entry.entry_id}" @property def signal_options_update(self) -> str: """Event specific per UniFi entry to signal new options.""" - return f"unifi-options-{self.config_entry.entry_id}" + return f"unifi-options-{self.config.entry.entry_id}" @property def signal_heartbeat_missed(self) -> str: @@ -248,25 +170,29 @@ class UnifiHub: """Set up a UniFi Network instance.""" await self.api.initialize() - assert self.config_entry.unique_id is not None - self.is_admin = self.api.sites[self.config_entry.unique_id].role == "admin" + assert self.config.entry.unique_id is not None + self.is_admin = self.api.sites[self.config.entry.unique_id].role == "admin" # Restore device tracker clients that are not a part of active clients list. macs: list[str] = [] entity_registry = er.async_get(self.hass) for entry in async_entries_for_config_entry( - entity_registry, self.config_entry.entry_id + entity_registry, self.config.entry.entry_id ): if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id: macs.append(entry.unique_id.split("-", 1)[1]) - for mac in self.option_supported_clients + self.option_block_clients + macs: + for mac in ( + self.config.option_supported_clients + + self.config.option_block_clients + + macs + ): if mac not in self.api.clients and mac in self.api.clients_all: self.api.clients.process_raw([dict(self.api.clients_all[mac].raw)]) self.wireless_clients.update_clients(set(self.api.clients.values())) - self.config_entry.add_update_listener(self.async_config_entry_updated) + self.config.entry.add_update_listener(self.async_config_entry_updated) self._cancel_heartbeat_check = async_track_time_interval( self.hass, self._async_check_for_stale, CHECK_HEARTBEAT_INTERVAL @@ -328,7 +254,7 @@ class UnifiHub: @property def device_info(self) -> DeviceInfo: """UniFi Network device info.""" - assert self.config_entry.unique_id is not None + assert self.config.entry.unique_id is not None version: str | None = None if sysinfo := next(iter(self.api.system_information.values()), None): @@ -336,7 +262,7 @@ class UnifiHub: return DeviceInfo( entry_type=DeviceEntryType.SERVICE, - identifiers={(UNIFI_DOMAIN, self.config_entry.unique_id)}, + identifiers={(UNIFI_DOMAIN, self.config.entry.unique_id)}, manufacturer=ATTR_MANUFACTURER, model="UniFi Network Application", name="UniFi Network", @@ -348,7 +274,7 @@ class UnifiHub: """Update device registry.""" device_registry = dr.async_get(self.hass) return device_registry.async_get_or_create( - config_entry_id=self.config_entry.entry_id, **self.device_info + config_entry_id=self.config.entry.entry_id, **self.device_info ) @staticmethod @@ -362,7 +288,7 @@ class UnifiHub: """ if not (hub := hass.data[UNIFI_DOMAIN].get(config_entry.entry_id)): return - hub.load_config_entry_options() + hub.config = UnifiConfig.from_config_entry(config_entry) async_dispatcher_send(hass, hub.signal_options_update) @callback @@ -382,7 +308,7 @@ class UnifiHub: await self.websocket.stop_and_wait() unload_ok = await self.hass.config_entries.async_unload_platforms( - self.config_entry, PLATFORMS + self.config.entry, PLATFORMS ) if not unload_ok: diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index bf803b2c0ab..e83add10040 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -55,17 +55,17 @@ from .hub import UnifiHub @callback def async_bandwidth_sensor_allowed_fn(hub: UnifiHub, obj_id: str) -> bool: """Check if client is allowed.""" - if obj_id in hub.option_supported_clients: + if obj_id in hub.config.option_supported_clients: return True - return hub.option_allow_bandwidth_sensors + return hub.config.option_allow_bandwidth_sensors @callback def async_uptime_sensor_allowed_fn(hub: UnifiHub, obj_id: str) -> bool: """Check if client is allowed.""" - if obj_id in hub.option_supported_clients: + if obj_id in hub.config.option_supported_clients: return True - return hub.option_allow_uptime_sensors + return hub.config.option_allow_uptime_sensors @callback @@ -101,7 +101,7 @@ def async_wlan_client_value_fn(hub: UnifiHub, wlan: Wlan) -> int: for client in hub.api.clients.values() if client.essid == wlan.name and dt_util.utcnow() - dt_util.utc_from_timestamp(client.last_seen or 0) - < hub.option_detection_time + < hub.config.option_detection_time ] ) @@ -146,7 +146,7 @@ def async_client_is_connected_fn(hub: UnifiHub, obj_id: str) -> bool: if ( dt_util.utcnow() - dt_util.utc_from_timestamp(client.last_seen or 0) - > hub.option_detection_time + > hub.config.option_detection_time ): return False @@ -193,7 +193,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( is_connected_fn=async_client_is_connected_fn, name_fn=lambda _: "RX", object_fn=lambda api, obj_id: api.clients[obj_id], - supported_fn=lambda hub, _: hub.option_allow_bandwidth_sensors, + supported_fn=lambda hub, _: hub.config.option_allow_bandwidth_sensors, unique_id_fn=lambda hub, obj_id: f"rx-{obj_id}", value_fn=async_client_rx_value_fn, ), @@ -212,7 +212,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( is_connected_fn=async_client_is_connected_fn, name_fn=lambda _: "TX", object_fn=lambda api, obj_id: api.clients[obj_id], - supported_fn=lambda hub, _: hub.option_allow_bandwidth_sensors, + supported_fn=lambda hub, _: hub.config.option_allow_bandwidth_sensors, unique_id_fn=lambda hub, obj_id: f"tx-{obj_id}", value_fn=async_client_tx_value_fn, ), @@ -245,7 +245,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( device_info_fn=async_client_device_info_fn, name_fn=lambda client: "Uptime", object_fn=lambda api, obj_id: api.clients[obj_id], - supported_fn=lambda hub, _: hub.option_allow_uptime_sensors, + supported_fn=lambda hub, _: hub.config.option_allow_uptime_sensors, unique_id_fn=lambda hub, obj_id: f"uptime-{obj_id}", value_fn=async_client_uptime_value_fn, ), @@ -412,7 +412,7 @@ class UnifiSensorEntity(UnifiEntity[HandlerT, ApiItemT], SensorEntity): if description.is_connected_fn(self.hub, self._obj_id): self.hub.async_heartbeat( self._attr_unique_id, - dt_util.utcnow() + self.hub.option_detection_time, + dt_util.utcnow() + self.hub.config.option_detection_time, ) async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 6b2ce3c8b34..7e8a422ef23 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -64,9 +64,9 @@ CLIENT_UNBLOCKED = (EventKey.WIRED_CLIENT_UNBLOCKED, EventKey.WIRELESS_CLIENT_UN @callback def async_block_client_allowed_fn(hub: UnifiHub, obj_id: str) -> bool: """Check if client is allowed.""" - if obj_id in hub.option_supported_clients: + if obj_id in hub.config.option_supported_clients: return True - return obj_id in hub.option_block_clients + return obj_id in hub.config.option_block_clients @callback @@ -95,7 +95,7 @@ def async_dpi_group_device_info_fn(hub: UnifiHub, obj_id: str) -> DeviceInfo: @callback def async_port_forward_device_info_fn(hub: UnifiHub, obj_id: str) -> DeviceInfo: """Create device registry entry for port forward.""" - unique_id = hub.config_entry.unique_id + unique_id = hub.config.entry.unique_id assert unique_id is not None return DeviceInfo( entry_type=DeviceEntryType.SERVICE, @@ -203,7 +203,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( key="DPI restriction", entity_category=EntityCategory.CONFIG, icon="mdi:network", - allowed_fn=lambda hub, obj_id: hub.option_dpi_restrictions, + allowed_fn=lambda hub, obj_id: hub.config.option_dpi_restrictions, api_handler_fn=lambda api: api.dpi_groups, available_fn=lambda hub, obj_id: hub.available, control_fn=async_dpi_group_control_fn, diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index 162f2b4d3aa..abc1592c4f5 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -45,10 +45,10 @@ class WebsocketStateManager(asyncio.Event): """ hub = self.hass.data[UNIFI_DOMAIN][DEFAULT_CONFIG_ENTRY_ID] self.aioclient_mock.get( - f"https://{hub.host}:1234", status=302 + f"https://{hub.config.host}:1234", status=302 ) # Check UniFi OS self.aioclient_mock.post( - f"https://{hub.host}:1234/api/login", + f"https://{hub.config.host}:1234/api/login", json={"data": "login successful", "meta": {"rc": "ok"}}, headers={"content-type": CONTENT_TYPE_JSON}, ) diff --git a/tests/components/unifi/test_hub.py b/tests/components/unifi/test_hub.py index 35b6e50cfd4..c1ea4f4ba0b 100644 --- a/tests/components/unifi/test_hub.py +++ b/tests/components/unifi/test_hub.py @@ -255,7 +255,7 @@ async def test_hub_setup( ) hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - entry = hub.config_entry + entry = hub.config.entry assert len(forward_entry_setup.mock_calls) == len(PLATFORMS) assert forward_entry_setup.mock_calls[0][1] == (entry, BUTTON_DOMAIN) assert forward_entry_setup.mock_calls[1][1] == (entry, TRACKER_DOMAIN) @@ -263,17 +263,17 @@ async def test_hub_setup( assert forward_entry_setup.mock_calls[3][1] == (entry, SENSOR_DOMAIN) assert forward_entry_setup.mock_calls[4][1] == (entry, SWITCH_DOMAIN) - assert hub.host == ENTRY_CONFIG[CONF_HOST] + assert hub.config.host == ENTRY_CONFIG[CONF_HOST] assert hub.is_admin == (SITE[0]["role"] == "admin") - assert hub.option_allow_bandwidth_sensors == DEFAULT_ALLOW_BANDWIDTH_SENSORS - assert hub.option_allow_uptime_sensors == DEFAULT_ALLOW_UPTIME_SENSORS - assert isinstance(hub.option_block_clients, list) - assert hub.option_track_clients == DEFAULT_TRACK_CLIENTS - assert hub.option_track_devices == DEFAULT_TRACK_DEVICES - assert hub.option_track_wired_clients == DEFAULT_TRACK_WIRED_CLIENTS - assert hub.option_detection_time == timedelta(seconds=DEFAULT_DETECTION_TIME) - assert isinstance(hub.option_ssid_filter, set) + assert hub.config.option_allow_bandwidth_sensors == DEFAULT_ALLOW_BANDWIDTH_SENSORS + assert hub.config.option_allow_uptime_sensors == DEFAULT_ALLOW_UPTIME_SENSORS + assert isinstance(hub.config.option_block_clients, list) + assert hub.config.option_track_clients == DEFAULT_TRACK_CLIENTS + assert hub.config.option_track_devices == DEFAULT_TRACK_DEVICES + assert hub.config.option_track_wired_clients == DEFAULT_TRACK_WIRED_CLIENTS + assert hub.config.option_detection_time == timedelta(seconds=DEFAULT_DETECTION_TIME) + assert isinstance(hub.config.option_ssid_filter, set) assert hub.signal_reachable == "unifi-reachable-1" assert hub.signal_options_update == "unifi-options-1" From 4bdcab7cc2ea963f077f4ab81d46152e6d67ee46 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 5 Mar 2024 21:36:46 +0100 Subject: [PATCH 0384/1691] Rename reconfigure step in apple_tv (#112438) --- homeassistant/components/apple_tv/config_flow.py | 6 +++--- homeassistant/components/apple_tv/strings.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index bd95aa6ca39..02117424f80 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -157,9 +157,9 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN): } self.scan_filter = self.unique_id self.context["identifier"] = self.unique_id - return await self.async_step_reconfigure() + return await self.async_step_restore_device() - async def async_step_reconfigure( + async def async_step_restore_device( self, user_input: dict[str, str] | None = None ) -> ConfigFlowResult: """Inform user that reconfiguration is about to start.""" @@ -168,7 +168,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN): self.async_pair_next_protocol, allow_exist=True ) - return self.async_show_form(step_id="reconfigure") + return self.async_show_form(step_id="restore_device") async def async_step_user( self, user_input: dict[str, str] | None = None diff --git a/homeassistant/components/apple_tv/strings.json b/homeassistant/components/apple_tv/strings.json index 8730ffe01d5..4efe80f7bef 100644 --- a/homeassistant/components/apple_tv/strings.json +++ b/homeassistant/components/apple_tv/strings.json @@ -9,7 +9,7 @@ "device_input": "[%key:common::config_flow::data::device%]" } }, - "reconfigure": { + "restore_device": { "title": "Device reconfiguration", "description": "Reconfigure this device to restore its functionality." }, From 7e5a59756e055ce88a2f85145f9e683f1cf89424 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Tue, 5 Mar 2024 12:40:51 -0800 Subject: [PATCH 0385/1691] Remove myself as a codeowner for neato (#112410) * Remove myself as a codeowner for neato * Update CODEOWNERS from hassfest --- CODEOWNERS | 4 ++-- homeassistant/components/neato/manifest.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 1424469a94b..31da9f521ba 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -858,8 +858,8 @@ build.json @home-assistant/supervisor /tests/components/nam/ @bieniu /homeassistant/components/nanoleaf/ @milanmeu /tests/components/nanoleaf/ @milanmeu -/homeassistant/components/neato/ @dshokouhi @Santobert -/tests/components/neato/ @dshokouhi @Santobert +/homeassistant/components/neato/ @Santobert +/tests/components/neato/ @Santobert /homeassistant/components/nederlandse_spoorwegen/ @YarmoM /homeassistant/components/ness_alarm/ @nickw444 /tests/components/ness_alarm/ @nickw444 diff --git a/homeassistant/components/neato/manifest.json b/homeassistant/components/neato/manifest.json index 5222ec938c8..1d5edb7ca44 100644 --- a/homeassistant/components/neato/manifest.json +++ b/homeassistant/components/neato/manifest.json @@ -1,7 +1,7 @@ { "domain": "neato", "name": "Neato Botvac", - "codeowners": ["@dshokouhi", "@Santobert"], + "codeowners": ["@Santobert"], "config_flow": true, "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/neato", From 153b1947fa9b144fb3b296c2e6d667a8b8d6e4d4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:43:48 +0100 Subject: [PATCH 0386/1691] Remove entity description mixin in AirNow (#112376) --- homeassistant/components/airnow/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index bfe9e92c4a3..258a0c680a7 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -51,19 +51,14 @@ ATTR_LEVEL = "level" ATTR_STATION = "reporting_station" -@dataclass(frozen=True) -class AirNowEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class AirNowEntityDescription(SensorEntityDescription): + """Describes Airnow sensor entity.""" value_fn: Callable[[Any], StateType] extra_state_attributes_fn: Callable[[Any], dict[str, str]] | None -@dataclass(frozen=True) -class AirNowEntityDescription(SensorEntityDescription, AirNowEntityDescriptionMixin): - """Describes Airnow sensor entity.""" - - def station_extra_attrs(data: dict[str, Any]) -> dict[str, Any]: """Process extra attributes for station location (if available).""" if ATTR_API_STATION in data: From f1e564fb47346a4b4ab36fe2e570aa92f2498993 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:45:19 +0100 Subject: [PATCH 0387/1691] Add icon translations to Vilfo (#112347) --- homeassistant/components/vilfo/icons.json | 12 ++++++++++++ homeassistant/components/vilfo/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/vilfo/icons.json diff --git a/homeassistant/components/vilfo/icons.json b/homeassistant/components/vilfo/icons.json new file mode 100644 index 00000000000..0b2e2a45a16 --- /dev/null +++ b/homeassistant/components/vilfo/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "load": { + "default": "mdi:memory" + }, + "boot_time": { + "default": "mdi:timer-outline" + } + } + } +} diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index c72edf1b7db..5f62b62a976 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -41,13 +41,11 @@ SENSOR_TYPES: tuple[VilfoSensorEntityDescription, ...] = ( key=ATTR_LOAD, translation_key=ATTR_LOAD, native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", api_key=ATTR_API_DATA_FIELD_LOAD, ), VilfoSensorEntityDescription( key=ATTR_BOOT_TIME, translation_key=ATTR_BOOT_TIME, - icon="mdi:timer-outline", api_key=ATTR_API_DATA_FIELD_BOOT_TIME, device_class=SensorDeviceClass.TIMESTAMP, ), From aab2c91c87421cd286f298d57d742aefb6e2e142 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:45:48 +0100 Subject: [PATCH 0388/1691] Add icon translations to Verisure (#112342) --- homeassistant/components/verisure/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/verisure/icons.json diff --git a/homeassistant/components/verisure/icons.json b/homeassistant/components/verisure/icons.json new file mode 100644 index 00000000000..35f6960b1e8 --- /dev/null +++ b/homeassistant/components/verisure/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "capture_smartcam": "mdi:camera", + "enable_autolock": "mdi:lock", + "disable_autolock": "mdi:lock-off" + } +} From 75df17a8bbd0712379d28d378d76b6fc309a2c86 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:46:59 +0100 Subject: [PATCH 0389/1691] Add icon translations to Vallox (#112339) --- .../components/vallox/binary_sensor.py | 1 - homeassistant/components/vallox/date.py | 1 - homeassistant/components/vallox/icons.json | 44 +++++++++++++++++++ homeassistant/components/vallox/number.py | 3 -- homeassistant/components/vallox/sensor.py | 6 --- homeassistant/components/vallox/switch.py | 1 - 6 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/vallox/icons.json diff --git a/homeassistant/components/vallox/binary_sensor.py b/homeassistant/components/vallox/binary_sensor.py index f919e67fa14..04d78766f40 100644 --- a/homeassistant/components/vallox/binary_sensor.py +++ b/homeassistant/components/vallox/binary_sensor.py @@ -59,7 +59,6 @@ BINARY_SENSOR_ENTITIES: tuple[ValloxBinarySensorEntityDescription, ...] = ( ValloxBinarySensorEntityDescription( key="post_heater", translation_key="post_heater", - icon="mdi:radiator", metric_key="A_CYC_IO_HEATER", ), ) diff --git a/homeassistant/components/vallox/date.py b/homeassistant/components/vallox/date.py index 43297006599..b9ea6d66015 100644 --- a/homeassistant/components/vallox/date.py +++ b/homeassistant/components/vallox/date.py @@ -20,7 +20,6 @@ class ValloxFilterChangeDateEntity(ValloxEntity, DateEntity): _attr_entity_category = EntityCategory.CONFIG _attr_translation_key = "filter_change_date" - _attr_icon = "mdi:air-filter" def __init__( self, diff --git a/homeassistant/components/vallox/icons.json b/homeassistant/components/vallox/icons.json new file mode 100644 index 00000000000..67b41d216d2 --- /dev/null +++ b/homeassistant/components/vallox/icons.json @@ -0,0 +1,44 @@ +{ + "entity": { + "binary_sensor": { + "post_heater": { + "default": "mdi:radiator" + } + }, + "date": { + "filter_change_date": { + "default": "mdi:air-filter" + } + }, + "sensor": { + "current_profile": { + "default": "mdi:gauge" + }, + "fan_speed": { + "default": "mdi:fan" + }, + "extract_fan_speed": { + "default": "mdi:fan" + }, + "supply_fan_speed": { + "default": "mdi:fan" + }, + "cell_state": { + "default": "mdi:swap-horizontal-bold" + }, + "efficiency": { + "default": "mdi:gauge" + } + }, + "switch": { + "bypass_locked": { + "default": "mdi:arrow-horizontal-lock" + } + } + }, + "services": { + "set_profile_fan_speed_home": "mdi:home", + "set_profile_fan_speed_away": "mdi:walk", + "set_profile_fan_speed_boost": "mdi:speedometer" + } +} diff --git a/homeassistant/components/vallox/number.py b/homeassistant/components/vallox/number.py index 044bc7e0a43..c9bbdda8a6a 100644 --- a/homeassistant/components/vallox/number.py +++ b/homeassistant/components/vallox/number.py @@ -77,7 +77,6 @@ NUMBER_ENTITIES: tuple[ValloxNumberEntityDescription, ...] = ( metric_key="A_CYC_HOME_AIR_TEMP_TARGET", device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - icon="mdi:thermometer", native_min_value=5.0, native_max_value=25.0, native_step=1.0, @@ -88,7 +87,6 @@ NUMBER_ENTITIES: tuple[ValloxNumberEntityDescription, ...] = ( metric_key="A_CYC_AWAY_AIR_TEMP_TARGET", device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - icon="mdi:thermometer", native_min_value=5.0, native_max_value=25.0, native_step=1.0, @@ -99,7 +97,6 @@ NUMBER_ENTITIES: tuple[ValloxNumberEntityDescription, ...] = ( metric_key="A_CYC_BOOST_AIR_TEMP_TARGET", device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - icon="mdi:thermometer", native_min_value=5.0, native_max_value=25.0, native_step=1.0, diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index 79dfeae8412..45118111f58 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -138,14 +138,12 @@ SENSOR_ENTITIES: tuple[ValloxSensorEntityDescription, ...] = ( ValloxSensorEntityDescription( key="current_profile", translation_key="current_profile", - icon="mdi:gauge", entity_type=ValloxProfileSensor, ), ValloxSensorEntityDescription( key="fan_speed", translation_key="fan_speed", metric_key="A_CYC_FAN_SPEED", - icon="mdi:fan", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, entity_type=ValloxFanSpeedSensor, @@ -154,7 +152,6 @@ SENSOR_ENTITIES: tuple[ValloxSensorEntityDescription, ...] = ( key="extract_fan_speed", translation_key="extract_fan_speed", metric_key="A_CYC_EXTR_FAN_SPEED", - icon="mdi:fan", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=REVOLUTIONS_PER_MINUTE, entity_type=ValloxFanSpeedSensor, @@ -164,7 +161,6 @@ SENSOR_ENTITIES: tuple[ValloxSensorEntityDescription, ...] = ( key="supply_fan_speed", translation_key="supply_fan_speed", metric_key="A_CYC_SUPP_FAN_SPEED", - icon="mdi:fan", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=REVOLUTIONS_PER_MINUTE, entity_type=ValloxFanSpeedSensor, @@ -179,7 +175,6 @@ SENSOR_ENTITIES: tuple[ValloxSensorEntityDescription, ...] = ( ValloxSensorEntityDescription( key="cell_state", translation_key="cell_state", - icon="mdi:swap-horizontal-bold", metric_key="A_CYC_CELL_STATE", entity_type=ValloxCellStateSensor, ), @@ -243,7 +238,6 @@ SENSOR_ENTITIES: tuple[ValloxSensorEntityDescription, ...] = ( key="efficiency", translation_key="efficiency", metric_key="A_CYC_EXTRACT_EFFICIENCY", - icon="mdi:gauge", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, entity_registry_enabled_default=False, diff --git a/homeassistant/components/vallox/switch.py b/homeassistant/components/vallox/switch.py index fcc468c0fb2..06d791430e1 100644 --- a/homeassistant/components/vallox/switch.py +++ b/homeassistant/components/vallox/switch.py @@ -77,7 +77,6 @@ SWITCH_ENTITIES: tuple[ValloxSwitchEntityDescription, ...] = ( ValloxSwitchEntityDescription( key="bypass_locked", translation_key="bypass_locked", - icon="mdi:arrow-horizontal-lock", metric_key="A_CYC_BYPASS_LOCKED", ), ) From d7d2a28f5ef8ee43aa92e6288fedefb3066864a6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:47:43 +0100 Subject: [PATCH 0390/1691] Remove entity description mixin in Abode (#112374) --- homeassistant/components/abode/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 1b1dbe8b30a..873953952bd 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -27,19 +27,14 @@ ABODE_TEMPERATURE_UNIT_HA_UNIT = { } -@dataclass(frozen=True) -class AbodeSensorDescriptionMixin: - """Mixin for Abode sensor.""" +@dataclass(frozen=True, kw_only=True) +class AbodeSensorDescription(SensorEntityDescription): + """Class describing Abode sensor entities.""" value_fn: Callable[[AbodeSense], float] native_unit_of_measurement_fn: Callable[[AbodeSense], str] -@dataclass(frozen=True) -class AbodeSensorDescription(SensorEntityDescription, AbodeSensorDescriptionMixin): - """Class describing Abode sensor entities.""" - - SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = ( AbodeSensorDescription( key=CONST.TEMP_STATUS_KEY, From 928e95781e816c86ed0417439bb3759de518df7f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:47:55 +0100 Subject: [PATCH 0391/1691] Add icon translations to Velbus (#112340) --- homeassistant/components/velbus/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/velbus/icons.json diff --git a/homeassistant/components/velbus/icons.json b/homeassistant/components/velbus/icons.json new file mode 100644 index 00000000000..a806782d189 --- /dev/null +++ b/homeassistant/components/velbus/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "sync_clock": "mdi:clock", + "scan": "mdi:magnify", + "clear_cache": "mdi:delete", + "set_memo_text": "mdi:note-text" + } +} From cde1273399332db435d0d0ac67abdd9d8b799b98 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:49:39 +0100 Subject: [PATCH 0392/1691] Add icon translations to Ukraine Alarm (#112330) --- .../components/ukraine_alarm/binary_sensor.py | 5 ----- .../components/ukraine_alarm/icons.json | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/ukraine_alarm/icons.json diff --git a/homeassistant/components/ukraine_alarm/binary_sensor.py b/homeassistant/components/ukraine_alarm/binary_sensor.py index f5479917064..cc816e17704 100644 --- a/homeassistant/components/ukraine_alarm/binary_sensor.py +++ b/homeassistant/components/ukraine_alarm/binary_sensor.py @@ -36,31 +36,26 @@ BINARY_SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = ( key=ALERT_TYPE_AIR, translation_key="air", device_class=BinarySensorDeviceClass.SAFETY, - icon="mdi:cloud", ), BinarySensorEntityDescription( key=ALERT_TYPE_URBAN_FIGHTS, translation_key="urban_fights", device_class=BinarySensorDeviceClass.SAFETY, - icon="mdi:pistol", ), BinarySensorEntityDescription( key=ALERT_TYPE_ARTILLERY, translation_key="artillery", device_class=BinarySensorDeviceClass.SAFETY, - icon="mdi:tank", ), BinarySensorEntityDescription( key=ALERT_TYPE_CHEMICAL, translation_key="chemical", device_class=BinarySensorDeviceClass.SAFETY, - icon="mdi:chemical-weapon", ), BinarySensorEntityDescription( key=ALERT_TYPE_NUCLEAR, translation_key="nuclear", device_class=BinarySensorDeviceClass.SAFETY, - icon="mdi:nuke", ), ) diff --git a/homeassistant/components/ukraine_alarm/icons.json b/homeassistant/components/ukraine_alarm/icons.json new file mode 100644 index 00000000000..a5c198ec9d3 --- /dev/null +++ b/homeassistant/components/ukraine_alarm/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "binary_sensor": { + "air": { + "default": "mdi:cloud" + }, + "urban_fights": { + "default": "mdi:pistol" + }, + "artillery": { + "default": "mdi:tank" + }, + "chemical": { + "default": "mdi:chemical-weapon" + }, + "nuclear": { + "default": "mdi:nuke" + } + } + } +} From d369447961ea52ab1da180ac99867fcb7a629c65 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:50:32 +0100 Subject: [PATCH 0393/1691] Add icon translations to Twitch (#112329) --- homeassistant/components/twitch/icons.json | 9 +++++++++ homeassistant/components/twitch/sensor.py | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/twitch/icons.json diff --git a/homeassistant/components/twitch/icons.json b/homeassistant/components/twitch/icons.json new file mode 100644 index 00000000000..54b07caf5f8 --- /dev/null +++ b/homeassistant/components/twitch/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "channel": { + "default": "mdi:twitch" + } + } + } +} diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 05fd3fa3e71..c1af382ff86 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -46,8 +46,6 @@ ATTR_FOLLOW_SINCE = "following_since" ATTR_FOLLOWING = "followers" ATTR_VIEWS = "views" -ICON = "mdi:twitch" - STATE_OFFLINE = "offline" STATE_STREAMING = "streaming" @@ -118,7 +116,7 @@ async def async_setup_entry( class TwitchSensor(SensorEntity): """Representation of a Twitch channel.""" - _attr_icon = ICON + _attr_translation_key = "channel" def __init__(self, channel: TwitchUser, client: Twitch) -> None: """Initialize the sensor.""" From 71be56e1fd7bd7626347f6918e26e75b5ae43bd0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:51:12 +0100 Subject: [PATCH 0394/1691] Remove entity description mixin in Android IP Webcam (#112380) --- .../components/android_ip_webcam/sensor.py | 14 +++----------- .../components/android_ip_webcam/switch.py | 13 +++---------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index e55112b7259..bd057bac7c7 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -23,19 +23,11 @@ from .coordinator import AndroidIPCamDataUpdateCoordinator from .entity import AndroidIPCamBaseEntity -@dataclass(frozen=True) -class AndroidIPWebcamSensorEntityDescriptionMixin: - """Mixin for required keys.""" - - value_fn: Callable[[PyDroidIPCam], StateType] - - -@dataclass(frozen=True) -class AndroidIPWebcamSensorEntityDescription( - SensorEntityDescription, AndroidIPWebcamSensorEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class AndroidIPWebcamSensorEntityDescription(SensorEntityDescription): """Entity description class for Android IP Webcam sensors.""" + value_fn: Callable[[PyDroidIPCam], StateType] unit_fn: Callable[[PyDroidIPCam], str | None] = lambda _: None diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py index d2a40cb619a..d3edd4a0439 100644 --- a/homeassistant/components/android_ip_webcam/switch.py +++ b/homeassistant/components/android_ip_webcam/switch.py @@ -18,21 +18,14 @@ from .coordinator import AndroidIPCamDataUpdateCoordinator from .entity import AndroidIPCamBaseEntity -@dataclass(frozen=True) -class AndroidIPWebcamSwitchEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class AndroidIPWebcamSwitchEntityDescription(SwitchEntityDescription): + """Entity description class for Android IP Webcam switches.""" on_func: Callable[[PyDroidIPCam], Coroutine[Any, Any, bool]] off_func: Callable[[PyDroidIPCam], Coroutine[Any, Any, bool]] -@dataclass(frozen=True) -class AndroidIPWebcamSwitchEntityDescription( - SwitchEntityDescription, AndroidIPWebcamSwitchEntityDescriptionMixin -): - """Entity description class for Android IP Webcam switches.""" - - SWITCH_TYPES: tuple[AndroidIPWebcamSwitchEntityDescription, ...] = ( AndroidIPWebcamSwitchEntityDescription( key="exposure_lock", From 066f227476b86f9d3961464bd246a2febf3bcb3f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:52:06 +0100 Subject: [PATCH 0395/1691] Remove entity description mixin in Aseko (#112382) --- .../components/aseko_pool_live/binary_sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/aseko_pool_live/binary_sensor.py b/homeassistant/components/aseko_pool_live/binary_sensor.py index e0b45ee6d4f..a84ac0799d4 100644 --- a/homeassistant/components/aseko_pool_live/binary_sensor.py +++ b/homeassistant/components/aseko_pool_live/binary_sensor.py @@ -20,20 +20,13 @@ from .coordinator import AsekoDataUpdateCoordinator from .entity import AsekoEntity -@dataclass(frozen=True) -class AsekoBinarySensorDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class AsekoBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes an Aseko binary sensor entity.""" value_fn: Callable[[Unit], bool] -@dataclass(frozen=True) -class AsekoBinarySensorEntityDescription( - BinarySensorEntityDescription, AsekoBinarySensorDescriptionMixin -): - """Describes an Aseko binary sensor entity.""" - - UNIT_BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = ( AsekoBinarySensorEntityDescription( key="water_flow", From 96e582c1b7f6b48fb93ce3e34ec06cda0ceacf99 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:54:52 +0100 Subject: [PATCH 0396/1691] Remove entity description mixin in Awair (#112384) --- homeassistant/components/awair/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 03243e51b7c..45b40d70399 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -50,18 +50,13 @@ from .coordinator import AwairDataUpdateCoordinator, AwairResult DUST_ALIASES = [API_PM25, API_PM10] -@dataclass(frozen=True) -class AwairRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class AwairSensorEntityDescription(SensorEntityDescription): + """Describes Awair sensor entity.""" unique_id_tag: str -@dataclass(frozen=True) -class AwairSensorEntityDescription(SensorEntityDescription, AwairRequiredKeysMixin): - """Describes Awair sensor entity.""" - - SENSOR_TYPE_SCORE = AwairSensorEntityDescription( key=API_SCORE, native_unit_of_measurement=PERCENTAGE, From 05628ecb2f3faee421097763b952c5047874cc35 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:55:45 +0100 Subject: [PATCH 0397/1691] Remove entity description mixin in Comfoconnect (#112397) --- homeassistant/components/comfoconnect/sensor.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 421643f5ced..be0616468a1 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -79,19 +79,11 @@ ATTR_SUPPLY_TEMPERATURE = "supply_temperature" _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class ComfoconnectRequiredKeysMixin: - """Mixin for required keys.""" - - sensor_id: int - - -@dataclass(frozen=True) -class ComfoconnectSensorEntityDescription( - SensorEntityDescription, ComfoconnectRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class ComfoconnectSensorEntityDescription(SensorEntityDescription): """Describes Comfoconnect sensor entity.""" + sensor_id: int multiplier: float = 1 From 4cf900911b41d71763fa94a0603487efa040cd2c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:59:11 +0100 Subject: [PATCH 0398/1691] Remove entity description mixin in Daikin (#112398) --- homeassistant/components/daikin/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index b890ad823f7..8076cd52b56 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -39,18 +39,13 @@ from .const import ( ) -@dataclass(frozen=True) -class DaikinRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class DaikinSensorEntityDescription(SensorEntityDescription): + """Describes Daikin sensor entity.""" value_func: Callable[[Appliance], float | None] -@dataclass(frozen=True) -class DaikinSensorEntityDescription(SensorEntityDescription, DaikinRequiredKeysMixin): - """Describes Daikin sensor entity.""" - - SENSOR_TYPES: tuple[DaikinSensorEntityDescription, ...] = ( DaikinSensorEntityDescription( key=ATTR_INSIDE_TEMPERATURE, From 362a10e82e44f9c5e7afc4d0248986168c5e49f5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 21:59:48 +0100 Subject: [PATCH 0399/1691] Remove entity description mixin in Doorbird (#112400) --- homeassistant/components/doorbird/button.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/doorbird/button.py b/homeassistant/components/doorbird/button.py index d77ac7a378e..48709ceb9c1 100644 --- a/homeassistant/components/doorbird/button.py +++ b/homeassistant/components/doorbird/button.py @@ -17,20 +17,13 @@ from .models import DoorBirdData IR_RELAY = "__ir_light__" -@dataclass(frozen=True) -class DoorbirdButtonEntityDescriptionMixin: - """Mixin to describe a Doorbird Button entity.""" +@dataclass(frozen=True, kw_only=True) +class DoorbirdButtonEntityDescription(ButtonEntityDescription): + """Class to describe a Doorbird Button entity.""" press_action: Callable[[DoorBird, str], None] -@dataclass(frozen=True) -class DoorbirdButtonEntityDescription( - ButtonEntityDescription, DoorbirdButtonEntityDescriptionMixin -): - """Class to describe a Doorbird Button entity.""" - - RELAY_ENTITY_DESCRIPTION = DoorbirdButtonEntityDescription( key="relay", translation_key="relay", From dd3c11f179151d8fd1be13bc07f2c10b4b5f42e6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 22:05:14 +0100 Subject: [PATCH 0400/1691] Remove entity description mixin in Ecoforest (#112409) --- homeassistant/components/ecoforest/number.py | 13 +++---------- homeassistant/components/ecoforest/sensor.py | 13 +++---------- homeassistant/components/ecoforest/switch.py | 13 +++---------- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/ecoforest/number.py b/homeassistant/components/ecoforest/number.py index 79d62b6a2d2..046e780dc2b 100644 --- a/homeassistant/components/ecoforest/number.py +++ b/homeassistant/components/ecoforest/number.py @@ -16,20 +16,13 @@ from .coordinator import EcoforestCoordinator from .entity import EcoforestEntity -@dataclass(frozen=True) -class EcoforestRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EcoforestNumberEntityDescription(NumberEntityDescription): + """Describes an ecoforest number entity.""" value_fn: Callable[[Device], float | None] -@dataclass(frozen=True) -class EcoforestNumberEntityDescription( - NumberEntityDescription, EcoforestRequiredKeysMixin -): - """Describes an ecoforest number entity.""" - - NUMBER_ENTITIES = ( EcoforestNumberEntityDescription( key="power_level", diff --git a/homeassistant/components/ecoforest/sensor.py b/homeassistant/components/ecoforest/sensor.py index 90904d274ac..2b698602cff 100644 --- a/homeassistant/components/ecoforest/sensor.py +++ b/homeassistant/components/ecoforest/sensor.py @@ -33,20 +33,13 @@ STATUS_TYPE = [s.value for s in State] ALARM_TYPE = [a.value for a in Alarm] + ["none"] -@dataclass(frozen=True) -class EcoforestRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EcoforestSensorEntityDescription(SensorEntityDescription): + """Describes Ecoforest sensor entity.""" value_fn: Callable[[Device], StateType] -@dataclass(frozen=True) -class EcoforestSensorEntityDescription( - SensorEntityDescription, EcoforestRequiredKeysMixin -): - """Describes Ecoforest sensor entity.""" - - SENSOR_TYPES: tuple[EcoforestSensorEntityDescription, ...] = ( EcoforestSensorEntityDescription( key="temperature", diff --git a/homeassistant/components/ecoforest/switch.py b/homeassistant/components/ecoforest/switch.py index 1e70068cde8..378c27924ae 100644 --- a/homeassistant/components/ecoforest/switch.py +++ b/homeassistant/components/ecoforest/switch.py @@ -17,21 +17,14 @@ from .coordinator import EcoforestCoordinator from .entity import EcoforestEntity -@dataclass(frozen=True) -class EcoforestSwitchRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EcoforestSwitchEntityDescription(SwitchEntityDescription): + """Describes an Ecoforest switch entity.""" value_fn: Callable[[Device], bool] switch_fn: Callable[[EcoforestApi, bool], Awaitable[Device]] -@dataclass(frozen=True) -class EcoforestSwitchEntityDescription( - SwitchEntityDescription, EcoforestSwitchRequiredKeysMixin -): - """Describes an Ecoforest switch entity.""" - - SWITCH_TYPES: tuple[EcoforestSwitchEntityDescription, ...] = ( EcoforestSwitchEntityDescription( key="status", From 4a1681bb4b8802fdaba94a3290211616d860c25f Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 5 Mar 2024 22:11:04 +0100 Subject: [PATCH 0401/1691] Allow duplicate modbus addresses on different devices (#112434) --- homeassistant/components/modbus/validators.py | 12 +- tests/components/modbus/test_init.py | 127 ++++++++++++++++++ 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index bdf472e4f76..765ce4d8be3 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -308,7 +308,7 @@ def check_config(config: dict) -> dict: ) -> bool: """Validate entity.""" name = entity[CONF_NAME] - addr = str(entity[CONF_ADDRESS]) + addr = f"{hub_name}{entity[CONF_ADDRESS]}" scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) if scan_interval < 5: _LOGGER.warning( @@ -335,11 +335,15 @@ def check_config(config: dict) -> dict: loc_addr: set[str] = {addr} if CONF_TARGET_TEMP in entity: - loc_addr.add(f"{entity[CONF_TARGET_TEMP]}_{inx}") + loc_addr.add(f"{hub_name}{entity[CONF_TARGET_TEMP]}_{inx}") if CONF_HVAC_MODE_REGISTER in entity: - loc_addr.add(f"{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}") + loc_addr.add( + f"{hub_name}{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}" + ) if CONF_FAN_MODE_REGISTER in entity: - loc_addr.add(f"{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}") + loc_addr.add( + f"{hub_name}{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}" + ) dup_addrs = ent_addr.intersection(loc_addr) if len(dup_addrs) > 0: diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 5738268a593..4de9a439a01 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -740,6 +740,133 @@ async def test_duplicate_fan_mode_validator(do_config) -> None: assert len(do_config[CONF_FAN_MODE_VALUES]) == 2 +@pytest.mark.parametrize( + ("do_config", "sensor_cnt"), + [ + ( + [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + { + CONF_NAME: TEST_ENTITY_NAME + "1", + CONF_ADDRESS: 119, + CONF_SLAVE: 0, + }, + ], + }, + ], + 2, + ), + ( + [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + { + CONF_NAME: TEST_ENTITY_NAME + "1", + CONF_ADDRESS: 117, + CONF_SLAVE: 1, + }, + ], + }, + ], + 2, + ), + ( + [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + { + CONF_NAME: TEST_ENTITY_NAME + "1", + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + ], + }, + ], + 1, + ), + ( + [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + { + CONF_NAME: TEST_ENTITY_NAME + "1", + CONF_ADDRESS: 119, + CONF_SLAVE: 0, + }, + ], + }, + { + CONF_NAME: TEST_MODBUS_NAME + "1", + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 119, + CONF_SLAVE: 0, + }, + ], + }, + ], + 2, + ), + ], +) +async def test_duplicate_addresses(do_config, sensor_cnt) -> None: + """Test duplicate entity validator.""" + check_config(do_config) + use_inx = len(do_config) - 1 + assert len(do_config[use_inx][CONF_SENSORS]) == sensor_cnt + + @pytest.mark.parametrize( "do_config", [ From 33fe6ad647ef96108dc860cb8e822a79e1cdb92c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 5 Mar 2024 22:37:53 +0100 Subject: [PATCH 0402/1691] Add icon translations to Tomorrowio (#112315) --- .../components/tomorrowio/icons.json | 48 +++++++++++++++++++ homeassistant/components/tomorrowio/sensor.py | 14 ------ 2 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 homeassistant/components/tomorrowio/icons.json diff --git a/homeassistant/components/tomorrowio/icons.json b/homeassistant/components/tomorrowio/icons.json new file mode 100644 index 00000000000..aa1d3782546 --- /dev/null +++ b/homeassistant/components/tomorrowio/icons.json @@ -0,0 +1,48 @@ +{ + "entity": { + "sensor": { + "dew_point": { + "default": "mdi:thermometer-water" + }, + "cloud_base": { + "default": "mdi:cloud-arrow-down" + }, + "cloud_ceiling": { + "default": "mdi:cloud-arrow-up" + }, + "cloud_cover": { + "default": "mdi:cloud-percent" + }, + "wind_gust": { + "default": "mdi:weather-windy" + }, + "precipitation_type": { + "default": "mdi:weather-snowy-rainy" + }, + "health_concern": { + "default": "mdi:hospital" + }, + "china_mep_health_concern": { + "default": "mdi:hospital" + }, + "pollen_index": { + "default": "mdi:tree" + }, + "weed_pollen_index": { + "default": "mdi:flower-pollen" + }, + "grass_pollen_index": { + "default": "mdi:grass" + }, + "fire_index": { + "default": "mdi:fire" + }, + "uv_index": { + "default": "mdi:sun-wireless" + }, + "uv_radiation_health_concern": { + "default": "mdi:weather-sunny-alert" + } + } + } +} diff --git a/homeassistant/components/tomorrowio/sensor.py b/homeassistant/components/tomorrowio/sensor.py index 6b285378e7e..d45be4aafdc 100644 --- a/homeassistant/components/tomorrowio/sensor.py +++ b/homeassistant/components/tomorrowio/sensor.py @@ -117,7 +117,6 @@ SENSOR_TYPES = ( key="dew_point", translation_key="dew_point", attribute=TMRW_ATTR_DEW_POINT, - icon="mdi:thermometer-water", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -146,7 +145,6 @@ SENSOR_TYPES = ( key="cloud_base", translation_key="cloud_base", attribute=TMRW_ATTR_CLOUD_BASE, - icon="mdi:cloud-arrow-down", unit_imperial=UnitOfLength.MILES, unit_metric=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, @@ -162,7 +160,6 @@ SENSOR_TYPES = ( key="cloud_ceiling", translation_key="cloud_ceiling", attribute=TMRW_ATTR_CLOUD_CEILING, - icon="mdi:cloud-arrow-up", unit_imperial=UnitOfLength.MILES, unit_metric=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, @@ -177,7 +174,6 @@ SENSOR_TYPES = ( key="cloud_cover", translation_key="cloud_cover", attribute=TMRW_ATTR_CLOUD_COVER, - icon="mdi:cloud-percent", native_unit_of_measurement=PERCENTAGE, ), # Data comes in as m/s, convert to mi/h for imperial @@ -185,7 +181,6 @@ SENSOR_TYPES = ( key="wind_gust", translation_key="wind_gust", attribute=TMRW_ATTR_WIND_GUST, - icon="mdi:weather-windy", unit_imperial=UnitOfSpeed.MILES_PER_HOUR, unit_metric=UnitOfSpeed.METERS_PER_SECOND, device_class=SensorDeviceClass.SPEED, @@ -199,7 +194,6 @@ SENSOR_TYPES = ( translation_key="precipitation_type", attribute=TMRW_ATTR_PRECIPITATION_TYPE, value_map=PrecipitationType, - icon="mdi:weather-snowy-rainy", ), # Data comes in as ppb, convert to µg/m^3 # Molecular weight of Ozone is 48 @@ -272,7 +266,6 @@ SENSOR_TYPES = ( translation_key="health_concern", attribute=TMRW_ATTR_EPA_HEALTH_CONCERN, value_map=HealthConcernType, - icon="mdi:hospital", ), TomorrowioSensorEntityDescription( key="china_mep_air_quality_index", @@ -291,13 +284,11 @@ SENSOR_TYPES = ( translation_key="china_mep_health_concern", attribute=TMRW_ATTR_CHINA_HEALTH_CONCERN, value_map=HealthConcernType, - icon="mdi:hospital", ), TomorrowioSensorEntityDescription( key="tree_pollen_index", translation_key="pollen_index", attribute=TMRW_ATTR_POLLEN_TREE, - icon="mdi:tree", value_map=PollenIndex, ), TomorrowioSensorEntityDescription( @@ -305,34 +296,29 @@ SENSOR_TYPES = ( translation_key="weed_pollen_index", attribute=TMRW_ATTR_POLLEN_WEED, value_map=PollenIndex, - icon="mdi:flower-pollen", ), TomorrowioSensorEntityDescription( key="grass_pollen_index", translation_key="grass_pollen_index", attribute=TMRW_ATTR_POLLEN_GRASS, - icon="mdi:grass", value_map=PollenIndex, ), TomorrowioSensorEntityDescription( key="fire_index", translation_key="fire_index", attribute=TMRW_ATTR_FIRE_INDEX, - icon="mdi:fire", ), TomorrowioSensorEntityDescription( key="uv_index", translation_key="uv_index", attribute=TMRW_ATTR_UV_INDEX, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:sun-wireless", ), TomorrowioSensorEntityDescription( key="uv_radiation_health_concern", translation_key="uv_radiation_health_concern", attribute=TMRW_ATTR_UV_HEALTH_CONCERN, value_map=UVDescription, - icon="mdi:weather-sunny-alert", ), ) From 3d3e9900c37cf89c95c57f95bc56fb6d99a01077 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 5 Mar 2024 22:52:11 +0100 Subject: [PATCH 0403/1691] Add TypeVar default for FlowResult (#112345) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- homeassistant/auth/__init__.py | 4 +- homeassistant/auth/mfa_modules/__init__.py | 2 - homeassistant/auth/providers/__init__.py | 2 - .../components/auth/mfa_setup_flow.py | 4 +- .../components/config/config_entries.py | 16 ++++-- .../components/repairs/issue_handler.py | 6 +- homeassistant/components/repairs/models.py | 2 - .../components/zwave_js/config_flow.py | 4 +- homeassistant/config_entries.py | 22 +++++--- homeassistant/data_entry_flow.py | 55 +++++++------------ homeassistant/helpers/data_entry_flow.py | 17 ++++-- tests/helpers/test_discovery_flow.py | 2 +- .../helpers/test_schema_config_entry_flow.py | 10 ++-- tests/test_data_entry_flow.py | 12 ++-- 14 files changed, 77 insertions(+), 81 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index a68f8bc95eb..f99e90dbc05 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -91,8 +91,6 @@ async def auth_manager_from_config( class AuthManagerFlowManager(data_entry_flow.FlowManager): """Manage authentication flows.""" - _flow_result = FlowResult - def __init__(self, hass: HomeAssistant, auth_manager: AuthManager) -> None: """Init auth manager flows.""" super().__init__(hass) @@ -112,7 +110,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager): return await auth_provider.async_login_flow(context) async def async_finish_flow( - self, flow: data_entry_flow.BaseFlowHandler, result: FlowResult + self, flow: data_entry_flow.FlowHandler, result: FlowResult ) -> FlowResult: """Return a user as result of login flow.""" flow = cast(LoginFlow, flow) diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 3c8c0e3a096..aa28710d8c6 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -96,8 +96,6 @@ class MultiFactorAuthModule: class SetupFlow(data_entry_flow.FlowHandler): """Handler for the setup flow.""" - _flow_result = FlowResult - def __init__( self, auth_module: MultiFactorAuthModule, setup_schema: vol.Schema, user_id: str ) -> None: diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 577955d7c75..7d74dd2dc26 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -184,8 +184,6 @@ async def load_auth_provider_module( class LoginFlow(data_entry_flow.FlowHandler): """Handler for the login flow.""" - _flow_result = FlowResult - def __init__(self, auth_provider: AuthProvider) -> None: """Initialize the login flow.""" self._auth_provider = auth_provider diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index 58c45c56b85..a7999af666a 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -38,8 +38,6 @@ _LOGGER = logging.getLogger(__name__) class MfaFlowManager(data_entry_flow.FlowManager): """Manage multi factor authentication flows.""" - _flow_result = data_entry_flow.FlowResult - async def async_create_flow( # type: ignore[override] self, handler_key: str, @@ -56,7 +54,7 @@ class MfaFlowManager(data_entry_flow.FlowManager): return await mfa_module.async_setup_flow(user_id) async def async_finish_flow( - self, flow: data_entry_flow.BaseFlowHandler, result: data_entry_flow.FlowResult + self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult ) -> data_entry_flow.FlowResult: """Complete an mfs setup flow.""" _LOGGER.debug("flow_result: %s", result) diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index dc684a45770..48665645c6f 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -141,7 +141,9 @@ def _prepare_config_flow_result_json( return data -class ConfigManagerFlowIndexView(FlowManagerIndexView): +class ConfigManagerFlowIndexView( + FlowManagerIndexView[config_entries.ConfigEntriesFlowManager] +): """View to create config flows.""" url = "/api/config/config_entries/flow" @@ -196,7 +198,9 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): return _prepare_config_flow_result_json(result, super()._prepare_result_json) -class ConfigManagerFlowResourceView(FlowManagerResourceView): +class ConfigManagerFlowResourceView( + FlowManagerResourceView[config_entries.ConfigEntriesFlowManager] +): """View to interact with the flow manager.""" url = "/api/config/config_entries/flow/{flow_id}" @@ -238,7 +242,9 @@ class ConfigManagerAvailableFlowView(HomeAssistantView): return self.json(await async_get_config_flows(hass, **kwargs)) -class OptionManagerFlowIndexView(FlowManagerIndexView): +class OptionManagerFlowIndexView( + FlowManagerIndexView[config_entries.OptionsFlowManager] +): """View to create option flows.""" url = "/api/config/config_entries/options/flow" @@ -255,7 +261,9 @@ class OptionManagerFlowIndexView(FlowManagerIndexView): return await super().post(request) -class OptionManagerFlowResourceView(FlowManagerResourceView): +class OptionManagerFlowResourceView( + FlowManagerResourceView[config_entries.OptionsFlowManager] +): """View to interact with the option flow manager.""" url = "/api/config/config_entries/options/flow/{flow_id}" diff --git a/homeassistant/components/repairs/issue_handler.py b/homeassistant/components/repairs/issue_handler.py index 388edc56254..f2ce3bac84e 100644 --- a/homeassistant/components/repairs/issue_handler.py +++ b/homeassistant/components/repairs/issue_handler.py @@ -48,11 +48,9 @@ class ConfirmRepairFlow(RepairsFlow): ) -class RepairsFlowManager(data_entry_flow.BaseFlowManager[data_entry_flow.FlowResult]): +class RepairsFlowManager(data_entry_flow.FlowManager): """Manage repairs flows.""" - _flow_result = data_entry_flow.FlowResult - async def async_create_flow( self, handler_key: str, @@ -84,7 +82,7 @@ class RepairsFlowManager(data_entry_flow.BaseFlowManager[data_entry_flow.FlowRes return flow async def async_finish_flow( - self, flow: data_entry_flow.BaseFlowHandler, result: data_entry_flow.FlowResult + self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult ) -> data_entry_flow.FlowResult: """Complete a fix flow.""" if result.get("type") != data_entry_flow.FlowResultType.ABORT: diff --git a/homeassistant/components/repairs/models.py b/homeassistant/components/repairs/models.py index 94c4d4731b9..6ae175b29e9 100644 --- a/homeassistant/components/repairs/models.py +++ b/homeassistant/components/repairs/models.py @@ -10,8 +10,6 @@ from homeassistant.core import HomeAssistant class RepairsFlow(data_entry_flow.FlowHandler): """Handle a flow for fixing an issue.""" - _flow_result = data_entry_flow.FlowResult - issue_id: str data: dict[str, str | int | float | None] | None diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 9eccb032120..cb564de924c 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -34,7 +34,7 @@ from homeassistant.config_entries import ( ) from homeassistant.const import CONF_NAME, CONF_URL from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import AbortFlow, BaseFlowManager +from homeassistant.data_entry_flow import AbortFlow, FlowManager from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -182,7 +182,7 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC): @property @abstractmethod - def flow_manager(self) -> BaseFlowManager: + def flow_manager(self) -> FlowManager[ConfigFlowResult]: """Return the flow manager of the flow.""" async def async_step_install_addon( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index e6402696010..164825c4dec 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1045,7 +1045,7 @@ class FlowCancelledError(Exception): """Error to indicate that a flow has been cancelled.""" -class ConfigEntriesFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult]): +class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): """Manage all the config entry flows that are in progress.""" _flow_result = ConfigFlowResult @@ -1170,7 +1170,9 @@ class ConfigEntriesFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult] self._discovery_debouncer.async_shutdown() async def async_finish_flow( - self, flow: data_entry_flow.BaseFlowHandler, result: ConfigFlowResult + self, + flow: data_entry_flow.FlowHandler[ConfigFlowResult], + result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish a config flow and add an entry.""" flow = cast(ConfigFlow, flow) @@ -1290,7 +1292,9 @@ class ConfigEntriesFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult] return flow async def async_post_init( - self, flow: data_entry_flow.BaseFlowHandler, result: ConfigFlowResult + self, + flow: data_entry_flow.FlowHandler[ConfigFlowResult], + result: ConfigFlowResult, ) -> None: """After a flow is initialised trigger new flow notifications.""" source = flow.context["source"] @@ -1936,7 +1940,7 @@ def _async_abort_entries_match( raise data_entry_flow.AbortFlow("already_configured") -class ConfigEntryBaseFlow(data_entry_flow.BaseFlowHandler[ConfigFlowResult]): +class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult]): """Base class for config and option flows.""" _flow_result = ConfigFlowResult @@ -2288,7 +2292,7 @@ class ConfigFlow(ConfigEntryBaseFlow): return self.async_abort(reason=reason) -class OptionsFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult]): +class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): """Flow to set options for a configuration entry.""" _flow_result = ConfigFlowResult @@ -2317,7 +2321,9 @@ class OptionsFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult]): return handler.async_get_options_flow(entry) async def async_finish_flow( - self, flow: data_entry_flow.BaseFlowHandler, result: ConfigFlowResult + self, + flow: data_entry_flow.FlowHandler[ConfigFlowResult], + result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish an options flow and update options for configuration entry. @@ -2337,7 +2343,9 @@ class OptionsFlowManager(data_entry_flow.BaseFlowManager[ConfigFlowResult]): result["result"] = True return result - async def _async_setup_preview(self, flow: data_entry_flow.BaseFlowHandler) -> None: + async def _async_setup_preview( + self, flow: data_entry_flow.FlowHandler[ConfigFlowResult] + ) -> None: """Set up preview for an option flow handler.""" entry = self._async_get_config_entry(flow.handler) await _load_integration(self.hass, entry.domain, {}) diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 526b859d29c..3005c21c272 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -11,8 +11,9 @@ from enum import StrEnum from functools import partial import logging from types import MappingProxyType -from typing import Any, Generic, Required, TypedDict, TypeVar +from typing import Any, Generic, Required, TypedDict +from typing_extensions import TypeVar import voluptuous as vol from .core import HomeAssistant, callback @@ -84,7 +85,7 @@ STEP_ID_OPTIONAL_STEPS = { } -_FlowResultT = TypeVar("_FlowResultT", bound="FlowResult") +_FlowResultT = TypeVar("_FlowResultT", bound="FlowResult", default="FlowResult") @dataclass(slots=True) @@ -188,10 +189,10 @@ def _map_error_to_schema_errors( schema_errors[path_part_str] = error.error_message -class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): +class FlowManager(abc.ABC, Generic[_FlowResultT]): """Manage all the flows that are in progress.""" - _flow_result: Callable[..., _FlowResultT] + _flow_result: Callable[..., _FlowResultT] = FlowResult # type: ignore[assignment] def __init__( self, @@ -200,9 +201,9 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): """Initialize the flow manager.""" self.hass = hass self._preview: set[str] = set() - self._progress: dict[str, BaseFlowHandler] = {} - self._handler_progress_index: dict[str, set[BaseFlowHandler]] = {} - self._init_data_process_index: dict[type, set[BaseFlowHandler]] = {} + self._progress: dict[str, FlowHandler[_FlowResultT]] = {} + self._handler_progress_index: dict[str, set[FlowHandler[_FlowResultT]]] = {} + self._init_data_process_index: dict[type, set[FlowHandler[_FlowResultT]]] = {} @abc.abstractmethod async def async_create_flow( @@ -211,7 +212,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): *, context: dict[str, Any] | None = None, data: dict[str, Any] | None = None, - ) -> BaseFlowHandler[_FlowResultT]: + ) -> FlowHandler[_FlowResultT]: """Create a flow for specified handler. Handler key is the domain of the component that we want to set up. @@ -219,12 +220,12 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): @abc.abstractmethod async def async_finish_flow( - self, flow: BaseFlowHandler, result: _FlowResultT + self, flow: FlowHandler[_FlowResultT], result: _FlowResultT ) -> _FlowResultT: """Finish a data entry flow.""" async def async_post_init( - self, flow: BaseFlowHandler, result: _FlowResultT + self, flow: FlowHandler[_FlowResultT], result: _FlowResultT ) -> None: """Entry has finished executing its first step asynchronously.""" @@ -298,7 +299,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): @callback def _async_progress_by_handler( self, handler: str, match_context: dict[str, Any] | None - ) -> list[BaseFlowHandler[_FlowResultT]]: + ) -> list[FlowHandler[_FlowResultT]]: """Return the flows in progress by handler. If match_context is specified, only return flows with a context that @@ -362,7 +363,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): data_schema := cur_step.get("data_schema") ) is not None and user_input is not None: try: - user_input = data_schema(user_input) + user_input = data_schema(user_input) # type: ignore[operator] except vol.Invalid as ex: raised_errors = [ex] if isinstance(ex, vol.MultipleInvalid): @@ -444,7 +445,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): self._async_remove_flow_progress(flow_id) @callback - def _async_add_flow_progress(self, flow: BaseFlowHandler[_FlowResultT]) -> None: + def _async_add_flow_progress(self, flow: FlowHandler[_FlowResultT]) -> None: """Add a flow to in progress.""" if flow.init_data is not None: init_data_type = type(flow.init_data) @@ -453,9 +454,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): self._handler_progress_index.setdefault(flow.handler, set()).add(flow) @callback - def _async_remove_flow_from_index( - self, flow: BaseFlowHandler[_FlowResultT] - ) -> None: + def _async_remove_flow_from_index(self, flow: FlowHandler[_FlowResultT]) -> None: """Remove a flow from in progress.""" if flow.init_data is not None: init_data_type = type(flow.init_data) @@ -481,7 +480,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): async def _async_handle_step( self, - flow: BaseFlowHandler[_FlowResultT], + flow: FlowHandler[_FlowResultT], step_id: str, user_input: dict | BaseServiceInfo | None, ) -> _FlowResultT: @@ -558,7 +557,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): return result def _raise_if_step_does_not_exist( - self, flow: BaseFlowHandler, step_id: str + self, flow: FlowHandler[_FlowResultT], step_id: str ) -> None: """Raise if the step does not exist.""" method = f"async_step_{step_id}" @@ -569,7 +568,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): f"Handler {self.__class__.__name__} doesn't support step {step_id}" ) - async def _async_setup_preview(self, flow: BaseFlowHandler) -> None: + async def _async_setup_preview(self, flow: FlowHandler[_FlowResultT]) -> None: """Set up preview for a flow handler.""" if flow.handler not in self._preview: self._preview.add(flow.handler) @@ -577,7 +576,7 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): @callback def _async_flow_handler_to_flow_result( - self, flows: Iterable[BaseFlowHandler], include_uninitialized: bool + self, flows: Iterable[FlowHandler[_FlowResultT]], include_uninitialized: bool ) -> list[_FlowResultT]: """Convert a list of FlowHandler to a partial FlowResult that can be serialized.""" results = [] @@ -595,16 +594,10 @@ class BaseFlowManager(abc.ABC, Generic[_FlowResultT]): return results -class FlowManager(BaseFlowManager[FlowResult]): - """Manage all the flows that are in progress.""" - - _flow_result = FlowResult - - -class BaseFlowHandler(Generic[_FlowResultT]): +class FlowHandler(Generic[_FlowResultT]): """Handle a data entry flow.""" - _flow_result: Callable[..., _FlowResultT] + _flow_result: Callable[..., _FlowResultT] = FlowResult # type: ignore[assignment] # Set by flow manager cur_step: _FlowResultT | None = None @@ -881,12 +874,6 @@ class BaseFlowHandler(Generic[_FlowResultT]): self.__progress_task = progress_task -class FlowHandler(BaseFlowHandler[FlowResult]): - """Handle a data entry flow.""" - - _flow_result = FlowResult - - # These can be removed if no deprecated constant are in this module anymore __getattr__ = partial(check_if_deprecated_constant, module_globals=globals()) __dir__ = partial( diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 9a3e3a0f5e0..21ebab7b4eb 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -2,9 +2,10 @@ from __future__ import annotations from http import HTTPStatus -from typing import Any +from typing import Any, Generic from aiohttp import web +from typing_extensions import TypeVar import voluptuous as vol import voluptuous_serialize @@ -14,11 +15,17 @@ from homeassistant.components.http.data_validator import RequestDataValidator from . import config_validation as cv +_FlowManagerT = TypeVar( + "_FlowManagerT", + bound=data_entry_flow.FlowManager[Any], + default=data_entry_flow.FlowManager, +) -class _BaseFlowManagerView(HomeAssistantView): + +class _BaseFlowManagerView(HomeAssistantView, Generic[_FlowManagerT]): """Foundation for flow manager views.""" - def __init__(self, flow_mgr: data_entry_flow.BaseFlowManager) -> None: + def __init__(self, flow_mgr: _FlowManagerT) -> None: """Initialize the flow manager index view.""" self._flow_mgr = flow_mgr @@ -48,7 +55,7 @@ class _BaseFlowManagerView(HomeAssistantView): return data -class FlowManagerIndexView(_BaseFlowManagerView): +class FlowManagerIndexView(_BaseFlowManagerView[_FlowManagerT]): """View to create config flows.""" @RequestDataValidator( @@ -96,7 +103,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): return {"show_advanced_options": data["show_advanced_options"]} -class FlowManagerResourceView(_BaseFlowManagerView): +class FlowManagerResourceView(_BaseFlowManagerView[_FlowManagerT]): """View to interact with the flow manager.""" async def get(self, request: web.Request, /, flow_id: str) -> web.Response: diff --git a/tests/helpers/test_discovery_flow.py b/tests/helpers/test_discovery_flow.py index 7dcf6256a59..0b3386f8e04 100644 --- a/tests/helpers/test_discovery_flow.py +++ b/tests/helpers/test_discovery_flow.py @@ -63,7 +63,7 @@ async def test_async_create_flow_checks_existing_flows_after_startup( """Test existing flows prevent an identical ones from being after startup.""" hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) with patch( - "homeassistant.data_entry_flow.BaseFlowManager.async_has_matching_flow", + "homeassistant.data_entry_flow.FlowManager.async_has_matching_flow", return_value=True, ): discovery_flow.async_create_flow( diff --git a/tests/helpers/test_schema_config_entry_flow.py b/tests/helpers/test_schema_config_entry_flow.py index 6778a168dd7..58f6a261aef 100644 --- a/tests/helpers/test_schema_config_entry_flow.py +++ b/tests/helpers/test_schema_config_entry_flow.py @@ -45,7 +45,7 @@ def manager_fixture(): handlers = Registry() entries = [] - class FlowManager(data_entry_flow.BaseFlowManager): + class FlowManager(data_entry_flow.FlowManager): """Test flow manager.""" async def async_create_flow(self, handler_key, *, context, data): @@ -105,7 +105,7 @@ async def test_name(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> @pytest.mark.parametrize("marker", (vol.Required, vol.Optional)) async def test_config_flow_advanced_option( - hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager, marker + hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker ) -> None: """Test handling of advanced options in config flow.""" manager.hass = hass @@ -200,7 +200,7 @@ async def test_config_flow_advanced_option( @pytest.mark.parametrize("marker", (vol.Required, vol.Optional)) async def test_options_flow_advanced_option( - hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager, marker + hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker ) -> None: """Test handling of advanced options in options flow.""" manager.hass = hass @@ -475,7 +475,7 @@ async def test_next_step_function(hass: HomeAssistant) -> None: async def test_suggested_values( - hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager + hass: HomeAssistant, manager: data_entry_flow.FlowManager ) -> None: """Test suggested_values handling in SchemaFlowFormStep.""" manager.hass = hass @@ -667,7 +667,7 @@ async def test_options_flow_state(hass: HomeAssistant) -> None: async def test_options_flow_omit_optional_keys( - hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager + hass: HomeAssistant, manager: data_entry_flow.FlowManager ) -> None: """Test handling of advanced options in options flow.""" manager.hass = hass diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index 96bd45d4e36..c6a5f65be92 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -24,11 +24,9 @@ def manager(): handlers = Registry() entries = [] - class FlowManager(data_entry_flow.BaseFlowManager): + class FlowManager(data_entry_flow.FlowManager): """Test flow manager.""" - _flow_result = data_entry_flow.FlowResult - async def async_create_flow(self, handler_key, *, context, data): """Test create flow.""" handler = handlers.get(handler_key) @@ -81,7 +79,7 @@ async def test_configure_reuses_handler_instance(manager) -> None: assert len(manager.mock_created_entries) == 0 -async def test_configure_two_steps(manager: data_entry_flow.BaseFlowManager) -> None: +async def test_configure_two_steps(manager: data_entry_flow.FlowManager) -> None: """Test that we reuse instances.""" @manager.mock_reg_handler("test") @@ -258,7 +256,7 @@ async def test_finish_callback_change_result_type(hass: HomeAssistant) -> None: step_id="init", data_schema=vol.Schema({"count": int}) ) - class FlowManager(data_entry_flow.BaseFlowManager): + class FlowManager(data_entry_flow.FlowManager): async def async_create_flow(self, handler_name, *, context, data): """Create a test flow.""" return TestFlow() @@ -775,7 +773,7 @@ async def test_async_get_unknown_flow(manager) -> None: async def test_async_has_matching_flow( - hass: HomeAssistant, manager: data_entry_flow.BaseFlowManager + hass: HomeAssistant, manager: data_entry_flow.FlowManager ) -> None: """Test we can check for matching flows.""" manager.hass = hass @@ -951,7 +949,7 @@ async def test_show_menu(hass: HomeAssistant, manager, menu_options) -> None: async def test_find_flows_by_init_data_type( - manager: data_entry_flow.BaseFlowManager, + manager: data_entry_flow.FlowManager, ) -> None: """Test we can find flows by init data type.""" From a7f84c577d4690c059c8331d681d13545a875fdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 23:05:24 +0100 Subject: [PATCH 0404/1691] Bump dorny/paths-filter from 3.0.1 to 3.0.2 (#112158) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0f97206690b..72e303ac1c0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -103,7 +103,7 @@ jobs: echo "key=pre-commit-${{ env.CACHE_VERSION }}-${{ hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT - name: Filter for core changes - uses: dorny/paths-filter@v3.0.1 + uses: dorny/paths-filter@v3.0.2 id: core with: filters: .core_files.yaml @@ -118,7 +118,7 @@ jobs: echo "Result:" cat .integration_paths.yaml - name: Filter for integration changes - uses: dorny/paths-filter@v3.0.1 + uses: dorny/paths-filter@v3.0.2 id: integrations with: filters: .integration_paths.yaml From 235771aaa21ec3cafc98007bfc1bfe2f2db0e803 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 12:14:30 -1000 Subject: [PATCH 0405/1691] Bump SQLAlchemy to 2.0.28 (#112314) --- homeassistant/components/recorder/manifest.json | 2 +- homeassistant/components/sql/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/recorder/test_init.py | 4 ++++ 8 files changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index 98b6d15facb..feeccbab612 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -7,7 +7,7 @@ "iot_class": "local_push", "quality_scale": "internal", "requirements": [ - "SQLAlchemy==2.0.27", + "SQLAlchemy==2.0.28", "fnv-hash-fast==0.5.0", "psutil-home-assistant==0.0.1" ] diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index b440b795e0e..0935b9ae5e5 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sql", "iot_class": "local_polling", - "requirements": ["SQLAlchemy==2.0.27", "sqlparse==0.4.4"] + "requirements": ["SQLAlchemy==2.0.28", "sqlparse==0.4.4"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 99f3eb074bd..030aafc4df7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -52,7 +52,7 @@ PyTurboJPEG==1.7.1 pyudev==0.23.2 PyYAML==6.0.1 requests==2.31.0 -SQLAlchemy==2.0.27 +SQLAlchemy==2.0.28 typing-extensions>=4.10.0,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 diff --git a/pyproject.toml b/pyproject.toml index c2825830481..19b8f810899 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ "python-slugify==8.0.4", "PyYAML==6.0.1", "requests==2.31.0", - "SQLAlchemy==2.0.27", + "SQLAlchemy==2.0.28", "typing-extensions>=4.10.0,<5.0", "ulid-transform==0.9.0", # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 diff --git a/requirements.txt b/requirements.txt index 61c531e16c1..b7a970bc967 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ psutil-home-assistant==0.0.1 python-slugify==8.0.4 PyYAML==6.0.1 requests==2.31.0 -SQLAlchemy==2.0.27 +SQLAlchemy==2.0.28 typing-extensions>=4.10.0,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 diff --git a/requirements_all.txt b/requirements_all.txt index 921987ad392..1385545dbc3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -128,7 +128,7 @@ RtmAPI==0.7.2 # homeassistant.components.recorder # homeassistant.components.sql -SQLAlchemy==2.0.27 +SQLAlchemy==2.0.28 # homeassistant.components.tami4 Tami4EdgeAPI==2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0e2024e0ccc..11bead1f992 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -113,7 +113,7 @@ RtmAPI==0.7.2 # homeassistant.components.recorder # homeassistant.components.sql -SQLAlchemy==2.0.27 +SQLAlchemy==2.0.28 # homeassistant.components.tami4 Tami4EdgeAPI==2.1 diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 1c8e9da551e..b84b672f3d4 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -2228,6 +2228,10 @@ async def test_connect_args_priority(hass: HomeAssistant, config_url) -> None: def __init__(*args, **kwargs): ... + @property + def is_async(self): + return False + def connect(self, *args, **params): nonlocal connect_params connect_params.append(params) From 73e1f8a7e53137757ea4a5eda3ee9c08ce33050f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 12:22:59 -1000 Subject: [PATCH 0406/1691] Pre import image_upload to avoid loading it after asyncio has started (#112444) image_upload will always be setup because its a dep of person and since person is a dep of onboarding which is a dep of frontend its already a base requirement for homeassistant. Pillow is now listed as a requirement for homeassistant so we can be sure it installed by the time bootstrap is loaded image_upload loading is currently a bottleneck to get the frontend loaded because it has to load in the import executor when everything is busy early in startup --- homeassistant/bootstrap.py | 1 + pyproject.toml | 1 + requirements.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index a6aadcc67a6..84581131edc 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -36,6 +36,7 @@ from .components import ( file_upload as file_upload_pre_import, # noqa: F401 history as history_pre_import, # noqa: F401 http, # not named pre_import since it has requirements + image_upload as image_upload_import, # noqa: F401 - not named pre_import since it has requirements lovelace as lovelace_pre_import, # noqa: F401 onboarding as onboarding_pre_import, # noqa: F401 recorder as recorder_import, # noqa: F401 - not named pre_import since it has requirements diff --git a/pyproject.toml b/pyproject.toml index 19b8f810899..765adc656d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ dependencies = [ "PyJWT==2.8.0", # PyJWT has loose dependency. We want the latest one. "cryptography==42.0.5", + "Pillow==10.2.0", # pyOpenSSL 23.2.0 is required to work with cryptography 41+ "pyOpenSSL==24.0.0", "orjson==3.9.15", diff --git a/requirements.txt b/requirements.txt index b7a970bc967..2a9358c911f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,6 +23,7 @@ Jinja2==3.1.3 lru-dict==1.3.0 PyJWT==2.8.0 cryptography==42.0.5 +Pillow==10.2.0 pyOpenSSL==24.0.0 orjson==3.9.15 packaging>=23.1 From d34e2c1f1219dc7d7311fdb208670053bf34a29b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 12:23:18 -1000 Subject: [PATCH 0407/1691] Pre import webhook before asyncio starts (#112441) This one ends in stage 1 and other components have to wait for it to be imported. Its cheap to import but it ends up at the end of the line which means other end up waiting for it which is time we could be doing startup work `2024-03-04 23:13:04.347 INFO (MainThread) [homeassistant.bootstrap] Setting up stage 1: {usb, websocket_api, webhook, zeroconf, bluetooth, ssdp, dhcp, cloud, network, api, http, hassio}` It currently always has a wait time for the import executor `2024-03-04 23:13:04.496 DEBUG (MainThread) [homeassistant.loader] Component webhook import took 0.146 seconds (loaded_executor=True)` --- homeassistant/bootstrap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 84581131edc..030099a9e2c 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -44,6 +44,7 @@ from .components import ( search as search_pre_import, # noqa: F401 sensor as sensor_pre_import, # noqa: F401 system_log as system_log_pre_import, # noqa: F401 + webhook as webhook_pre_import, # noqa: F401 websocket_api as websocket_api_pre_import, # noqa: F401 ) from .const import ( From fbabbc8f92bd1929e99c3bd19766295ecf33cc8e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 12:27:45 -1000 Subject: [PATCH 0408/1691] Limit legacy state translations to custom components (#112295) * Limit legacy state translations to custom components We were trying to load **thousands** of `*.light.json`, `*.switch.json` files at run time that did not exist. There have been replaced with entity translations: https://github.com/home-assistant/developers.home-assistant/pull/1557 https://github.com/home-assistant/core/pull/82701 https://github.com/home-assistant/core/pull/112023 will completely remove them, but for now we will only load them for custom components to reduce the number of files having to be examined * reduce * reduce * reduce * reduce * comment * coverage * try to remove empty dict in loaded_translations fallback when missing --- homeassistant/helpers/translation.py | 19 ++-- tests/helpers/test_translation.py | 97 +++++++++++++++---- .../__init__.py | 1 + .../manifest.json | 7 ++ .../__init__.py | 1 + .../manifest.json | 7 ++ 6 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 tests/testing_config/custom_components/test_legacy_state_translations/__init__.py create mode 100644 tests/testing_config/custom_components/test_legacy_state_translations/manifest.json create mode 100644 tests/testing_config/custom_components/test_legacy_state_translations_bad_data/__init__.py create mode 100644 tests/testing_config/custom_components/test_legacy_state_translations_bad_data/manifest.json diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index c2caecaa184..ca79ec443ab 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -165,20 +165,21 @@ async def _async_get_component_strings( for language in languages: files_to_load: dict[str, str] = {} files_to_load_by_language[language] = files_to_load - - loaded_translations: dict[str, Any] = {} - translations_by_language[language] = loaded_translations + translations_by_language[language] = {} for loaded in components: - domain = loaded.partition(".")[0] + domain, _, platform = loaded.partition(".") if not (integration := integrations.get(domain)): continue - path = component_translation_path(loaded, language, integration) - # No translation available - if path is None: - loaded_translations[loaded] = {} - else: + if platform and integration.is_built_in: + # Legacy state translations are no longer used for built-in integrations + # and we avoid trying to load them. This is a temporary measure to allow + # them to keep working for custom integrations until we can fully remove + # them. + continue + + if path := component_translation_path(loaded, language, integration): files_to_load[loaded] = path if not files_to_load: diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 210378c5812..69d7ef274ae 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -335,20 +335,57 @@ async def test_get_translation_categories(hass: HomeAssistant) -> None: assert "component.light.device_automation.action_type.turn_on" in translations -async def test_translation_merging( +async def test_legacy_platform_translations_not_used_built_in_integrations( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: - """Test we merge translations of two integrations.""" + """Test legacy platform translations are not used for built-in integrations.""" hass.config.components.add("moon.sensor") hass.config.components.add("sensor") + load_requests = [] + + def mock_load_translations_files_by_language(files): + load_requests.append(files) + return {} + + with patch( + "homeassistant.helpers.translation._load_translations_files_by_language", + mock_load_translations_files_by_language, + ): + await translation.async_get_translations(hass, "en", "state") + + assert len(load_requests) == 1 + to_load = load_requests[0] + assert len(to_load) == 1 + en_load = to_load["en"] + assert len(en_load) == 1 + assert "sensor" in en_load + assert "moon.sensor" not in en_load + + +async def test_translation_merging_custom_components( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Test we merge translations of two integrations. + + Legacy state translations only used for custom integrations. + """ + hass.config.components.add("test_legacy_state_translations.sensor") + hass.config.components.add("sensor") + orig_load_translations = translation._load_translations_files_by_language def mock_load_translations_files(files): """Mock loading.""" result = orig_load_translations(files) - result["en"]["moon.sensor"] = { - "state": {"moon__phase": {"first_quarter": "First Quarter"}} + result["en"]["test_legacy_state_translations.sensor"] = { + "state": { + "test_legacy_state_translations__phase": { + "first_quarter": "First Quarter" + } + } } return result @@ -358,15 +395,20 @@ async def test_translation_merging( ): translations = await translation.async_get_translations(hass, "en", "state") - assert "component.sensor.state.moon__phase.first_quarter" in translations + assert ( + "component.sensor.state.test_legacy_state_translations__phase.first_quarter" + in translations + ) - hass.config.components.add("season.sensor") + hass.config.components.add("test_legacy_state_translations_bad_data.sensor") # Patch in some bad translation data def mock_load_bad_translations_files(files): """Mock loading.""" result = orig_load_translations(files) - result["en"]["season.sensor"] = {"state": "bad data"} + result["en"]["test_legacy_state_translations_bad_data.sensor"] = { + "state": "bad data" + } return result with patch( @@ -375,7 +417,10 @@ async def test_translation_merging( ): translations = await translation.async_get_translations(hass, "en", "state") - assert "component.sensor.state.moon__phase.first_quarter" in translations + assert ( + "component.sensor.state.test_legacy_state_translations__phase.first_quarter" + in translations + ) assert ( "An integration providing translations for sensor provided invalid data:" @@ -383,17 +428,26 @@ async def test_translation_merging( ) in caplog.text -async def test_translation_merging_loaded_apart( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture +async def test_translation_merging_loaded_apart_custom_integrations( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, ) -> None: - """Test we merge translations of two integrations when they are not loaded at the same time.""" + """Test we merge translations of two integrations when they are not loaded at the same time. + + Legacy state translations only used for custom integrations. + """ orig_load_translations = translation._load_translations_files_by_language def mock_load_translations_files(files): """Mock loading.""" result = orig_load_translations(files) - result["en"]["moon.sensor"] = { - "state": {"moon__phase": {"first_quarter": "First Quarter"}} + result["en"]["test_legacy_state_translations.sensor"] = { + "state": { + "test_legacy_state_translations__phase": { + "first_quarter": "First Quarter" + } + } } return result @@ -405,9 +459,12 @@ async def test_translation_merging_loaded_apart( ): translations = await translation.async_get_translations(hass, "en", "state") - assert "component.sensor.state.moon__phase.first_quarter" not in translations + assert ( + "component.sensor.state.test_legacy_state_translations__phase.first_quarter" + not in translations + ) - hass.config.components.add("moon.sensor") + hass.config.components.add("test_legacy_state_translations.sensor") with patch( "homeassistant.helpers.translation._load_translations_files_by_language", @@ -415,7 +472,10 @@ async def test_translation_merging_loaded_apart( ): translations = await translation.async_get_translations(hass, "en", "state") - assert "component.sensor.state.moon__phase.first_quarter" in translations + assert ( + "component.sensor.state.test_legacy_state_translations__phase.first_quarter" + in translations + ) with patch( "homeassistant.helpers.translation._load_translations_files_by_language", @@ -425,7 +485,10 @@ async def test_translation_merging_loaded_apart( hass, "en", "state", integrations={"sensor"} ) - assert "component.sensor.state.moon__phase.first_quarter" in translations + assert ( + "component.sensor.state.test_legacy_state_translations__phase.first_quarter" + in translations + ) async def test_translation_merging_loaded_together( diff --git a/tests/testing_config/custom_components/test_legacy_state_translations/__init__.py b/tests/testing_config/custom_components/test_legacy_state_translations/__init__.py new file mode 100644 index 00000000000..e3bf8f02952 --- /dev/null +++ b/tests/testing_config/custom_components/test_legacy_state_translations/__init__.py @@ -0,0 +1 @@ +"""Provide a mock package component.""" diff --git a/tests/testing_config/custom_components/test_legacy_state_translations/manifest.json b/tests/testing_config/custom_components/test_legacy_state_translations/manifest.json new file mode 100644 index 00000000000..6f5c8837eb7 --- /dev/null +++ b/tests/testing_config/custom_components/test_legacy_state_translations/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "test_legacy_state_translations", + "name": "Test package for legacy state translations", + "documentation": "http://test-package.io", + "config_flow": true, + "version": "1.2.3" +} diff --git a/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/__init__.py b/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/__init__.py new file mode 100644 index 00000000000..e3bf8f02952 --- /dev/null +++ b/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/__init__.py @@ -0,0 +1 @@ +"""Provide a mock package component.""" diff --git a/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/manifest.json b/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/manifest.json new file mode 100644 index 00000000000..d5d7fc7b1b1 --- /dev/null +++ b/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "test_legacy_state_translations_bad_data", + "name": "Test package for legacy state translations", + "documentation": "http://test-package.io", + "config_flow": true, + "version": "1.2.3" +} From 862d04c0358d814c3f8dd6caa36f347bd9fee0b5 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Tue, 5 Mar 2024 23:35:22 +0100 Subject: [PATCH 0409/1691] Update xknx to 2.12.2 - Fix thread leak on unsuccessful connections (#112450) Update xknx to 2.12.2 --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 906b072c0be..290b560dad5 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -11,7 +11,7 @@ "loggers": ["xknx", "xknxproject"], "quality_scale": "platinum", "requirements": [ - "xknx==2.12.1", + "xknx==2.12.2", "xknxproject==3.7.0", "knx-frontend==2024.1.20.105944" ] diff --git a/requirements_all.txt b/requirements_all.txt index 1385545dbc3..2f733ac71d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2869,7 +2869,7 @@ xbox-webapi==2.0.11 xiaomi-ble==0.25.2 # homeassistant.components.knx -xknx==2.12.1 +xknx==2.12.2 # homeassistant.components.knx xknxproject==3.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 11bead1f992..702a0a936b4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2207,7 +2207,7 @@ xbox-webapi==2.0.11 xiaomi-ble==0.25.2 # homeassistant.components.knx -xknx==2.12.1 +xknx==2.12.2 # homeassistant.components.knx xknxproject==3.7.0 From fc732ecf1773299b6d8a2ac966e9e488b54d9b60 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 5 Mar 2024 23:42:47 +0100 Subject: [PATCH 0410/1691] Remove list comprehension when adding entities in Vallox (#112446) --- homeassistant/components/vallox/binary_sensor.py | 6 ++---- homeassistant/components/vallox/number.py | 10 ++++------ homeassistant/components/vallox/sensor.py | 6 ++---- homeassistant/components/vallox/switch.py | 10 ++++------ 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/vallox/binary_sensor.py b/homeassistant/components/vallox/binary_sensor.py index 04d78766f40..4d1778a81e1 100644 --- a/homeassistant/components/vallox/binary_sensor.py +++ b/homeassistant/components/vallox/binary_sensor.py @@ -74,8 +74,6 @@ async def async_setup_entry( data = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - ValloxBinarySensorEntity(data["name"], data["coordinator"], description) - for description in BINARY_SENSOR_ENTITIES - ] + ValloxBinarySensorEntity(data["name"], data["coordinator"], description) + for description in BINARY_SENSOR_ENTITIES ) diff --git a/homeassistant/components/vallox/number.py b/homeassistant/components/vallox/number.py index c9bbdda8a6a..8f198ea8947 100644 --- a/homeassistant/components/vallox/number.py +++ b/homeassistant/components/vallox/number.py @@ -111,10 +111,8 @@ async def async_setup_entry( data = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - ValloxNumberEntity( - data["name"], data["coordinator"], description, data["client"] - ) - for description in NUMBER_ENTITIES - ] + ValloxNumberEntity( + data["name"], data["coordinator"], description, data["client"] + ) + for description in NUMBER_ENTITIES ) diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index 45118111f58..decfec9d52c 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -262,8 +262,6 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][entry.entry_id]["coordinator"] async_add_entities( - [ - description.entity_type(name, coordinator, description) - for description in SENSOR_ENTITIES - ] + description.entity_type(name, coordinator, description) + for description in SENSOR_ENTITIES ) diff --git a/homeassistant/components/vallox/switch.py b/homeassistant/components/vallox/switch.py index 06d791430e1..c2358e1022f 100644 --- a/homeassistant/components/vallox/switch.py +++ b/homeassistant/components/vallox/switch.py @@ -92,10 +92,8 @@ async def async_setup_entry( data = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ - ValloxSwitchEntity( - data["name"], data["coordinator"], description, data["client"] - ) - for description in SWITCH_ENTITIES - ] + ValloxSwitchEntity( + data["name"], data["coordinator"], description, data["client"] + ) + for description in SWITCH_ENTITIES ) From f03be2fd9e869dd79c5023d1b74c8d2d5880689b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 01:59:07 +0100 Subject: [PATCH 0411/1691] Remove entity description mixin in BAF (#112386) --- homeassistant/components/baf/binary_sensor.py | 12 +++--------- homeassistant/components/baf/number.py | 11 +++-------- homeassistant/components/baf/sensor.py | 12 +++--------- homeassistant/components/baf/switch.py | 12 +++--------- 4 files changed, 12 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/baf/binary_sensor.py b/homeassistant/components/baf/binary_sensor.py index 50e8cd78629..a4f501df5c1 100644 --- a/homeassistant/components/baf/binary_sensor.py +++ b/homeassistant/components/baf/binary_sensor.py @@ -21,20 +21,14 @@ from .entity import BAFEntity from .models import BAFData -@dataclass(frozen=True) -class BAFBinarySensorDescriptionMixin: - """Required values for BAF binary sensors.""" - - value_fn: Callable[[Device], bool | None] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BAFBinarySensorDescription( BinarySensorEntityDescription, - BAFBinarySensorDescriptionMixin, ): """Class describing BAF binary sensor entities.""" + value_fn: Callable[[Device], bool | None] + OCCUPANCY_SENSORS = ( BAFBinarySensorDescription( diff --git a/homeassistant/components/baf/number.py b/homeassistant/components/baf/number.py index 9dd4180c7e1..438dbbb689d 100644 --- a/homeassistant/components/baf/number.py +++ b/homeassistant/components/baf/number.py @@ -22,18 +22,13 @@ from .entity import BAFEntity from .models import BAFData -@dataclass(frozen=True) -class BAFNumberDescriptionMixin: - """Required values for BAF sensors.""" +@dataclass(frozen=True, kw_only=True) +class BAFNumberDescription(NumberEntityDescription): + """Class describing BAF sensor entities.""" value_fn: Callable[[Device], int | None] -@dataclass(frozen=True) -class BAFNumberDescription(NumberEntityDescription, BAFNumberDescriptionMixin): - """Class describing BAF sensor entities.""" - - AUTO_COMFORT_NUMBER_DESCRIPTIONS = ( BAFNumberDescription( key="comfort_min_speed", diff --git a/homeassistant/components/baf/sensor.py b/homeassistant/components/baf/sensor.py index 5c8d8f2979b..33cee901594 100644 --- a/homeassistant/components/baf/sensor.py +++ b/homeassistant/components/baf/sensor.py @@ -28,20 +28,14 @@ from .entity import BAFEntity from .models import BAFData -@dataclass(frozen=True) -class BAFSensorDescriptionMixin: - """Required values for BAF sensors.""" - - value_fn: Callable[[Device], int | float | str | None] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BAFSensorDescription( SensorEntityDescription, - BAFSensorDescriptionMixin, ): """Class describing BAF sensor entities.""" + value_fn: Callable[[Device], int | float | str | None] + AUTO_COMFORT_SENSORS = ( BAFSensorDescription( diff --git a/homeassistant/components/baf/switch.py b/homeassistant/components/baf/switch.py index ccb8aee36e5..406fcacadde 100644 --- a/homeassistant/components/baf/switch.py +++ b/homeassistant/components/baf/switch.py @@ -18,20 +18,14 @@ from .entity import BAFEntity from .models import BAFData -@dataclass(frozen=True) -class BAFSwitchDescriptionMixin: - """Required values for BAF sensors.""" - - value_fn: Callable[[Device], bool | None] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BAFSwitchDescription( SwitchEntityDescription, - BAFSwitchDescriptionMixin, ): """Class describing BAF switch entities.""" + value_fn: Callable[[Device], bool | None] + BASE_SWITCHES = [ BAFSwitchDescription( From 3f9dbd3e25165121934f57c7a712306489197ed5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 16:18:57 -1000 Subject: [PATCH 0412/1691] Fix config modules being imported in the event loop (#112462) * Fix config modules being imported in the event loop There was a late import in this integration because of the circular import. The code has been rearranged to avoid the circular imports * fixes * fixes * fix patching * make eager * remove unrelated change from this branch --- homeassistant/components/config/__init__.py | 289 ++---------------- homeassistant/components/config/automation.py | 3 +- homeassistant/components/config/const.py | 5 + homeassistant/components/config/scene.py | 3 +- homeassistant/components/config/script.py | 3 +- homeassistant/components/config/view.py | 243 +++++++++++++++ tests/components/config/conftest.py | 4 +- tests/components/config/test_automation.py | 17 +- tests/components/config/test_core.py | 9 +- tests/components/config/test_scene.py | 11 +- tests/components/config/test_script.py | 17 +- 11 files changed, 317 insertions(+), 287 deletions(-) create mode 100644 homeassistant/components/config/const.py create mode 100644 homeassistant/components/config/view.py diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index fbeb5904a1a..77c49c4b412 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -1,48 +1,44 @@ """Component to configure Home Assistant via an API.""" from __future__ import annotations -import asyncio -from collections.abc import Callable, Coroutine -from http import HTTPStatus -import importlib -import os -from typing import Any, Generic, TypeVar, cast - -from aiohttp import web -import voluptuous as vol - from homeassistant.components import frontend -from homeassistant.components.http import HomeAssistantView, require_admin -from homeassistant.const import CONF_ID, EVENT_COMPONENT_LOADED +from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType from homeassistant.setup import ATTR_COMPONENT -from homeassistant.util.file import write_utf8_file_atomic -from homeassistant.util.yaml import dump, load_yaml -from homeassistant.util.yaml.loader import JSON_TYPE -_DataT = TypeVar("_DataT", dict[str, dict[str, Any]], list[dict[str, Any]]) - -DOMAIN = "config" +from . import ( + area_registry, + auth, + auth_provider_homeassistant, + automation, + config_entries, + core, + device_registry, + entity_registry, + floor_registry, + label_registry, + scene, + script, +) +from .const import DOMAIN SECTIONS = ( - "area_registry", - "auth", - "auth_provider_homeassistant", - "automation", - "config_entries", - "core", - "device_registry", - "entity_registry", - "floor_registry", - "label_registry", - "script", - "scene", + area_registry, + auth, + auth_provider_homeassistant, + automation, + config_entries, + core, + device_registry, + entity_registry, + floor_registry, + label_registry, + script, + scene, ) -ACTION_CREATE_UPDATE = "create_update" -ACTION_DELETE = "delete" + CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) @@ -53,231 +49,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass, "config", "config", "hass:cog", require_admin=True ) - for panel_name in SECTIONS: - panel = importlib.import_module(f".{panel_name}", __name__) - + for panel in SECTIONS: if panel.async_setup(hass): - key = f"{DOMAIN}.{panel_name}" + name = panel.__name__.split(".")[-1] + key = f"{DOMAIN}.{name}" hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key}) return True - - -class BaseEditConfigView(HomeAssistantView, Generic[_DataT]): - """Configure a Group endpoint.""" - - def __init__( - self, - component: str, - config_type: str, - path: str, - key_schema: Callable[[Any], str], - data_schema: Callable[[dict[str, Any]], Any], - *, - post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None, - data_validator: Callable[ - [HomeAssistant, str, dict[str, Any]], - Coroutine[Any, Any, dict[str, Any] | None], - ] - | None = None, - ) -> None: - """Initialize a config view.""" - self.url = f"/api/config/{component}/{config_type}/{{config_key}}" - self.name = f"api:config:{component}:{config_type}" - self.path = path - self.key_schema = key_schema - self.data_schema = data_schema - self.post_write_hook = post_write_hook - self.data_validator = data_validator - self.mutation_lock = asyncio.Lock() - - def _empty_config(self) -> _DataT: - """Empty config if file not found.""" - raise NotImplementedError - - def _get_value( - self, hass: HomeAssistant, data: _DataT, config_key: str - ) -> dict[str, Any] | None: - """Get value.""" - raise NotImplementedError - - def _write_value( - self, - hass: HomeAssistant, - data: _DataT, - config_key: str, - new_value: dict[str, Any], - ) -> None: - """Set value.""" - raise NotImplementedError - - def _delete_value( - self, hass: HomeAssistant, data: _DataT, config_key: str - ) -> dict[str, Any] | None: - """Delete value.""" - raise NotImplementedError - - @require_admin - async def get(self, request: web.Request, config_key: str) -> web.Response: - """Fetch device specific config.""" - hass: HomeAssistant = request.app["hass"] - async with self.mutation_lock: - current = await self.read_config(hass) - value = self._get_value(hass, current, config_key) - - if value is None: - return self.json_message("Resource not found", HTTPStatus.NOT_FOUND) - - return self.json(value) - - @require_admin - async def post(self, request: web.Request, config_key: str) -> web.Response: - """Validate config and return results.""" - try: - data = await request.json() - except ValueError: - return self.json_message("Invalid JSON specified", HTTPStatus.BAD_REQUEST) - - try: - self.key_schema(config_key) - except vol.Invalid as err: - return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST) - - hass: HomeAssistant = request.app["hass"] - - try: - # We just validate, we don't store that data because - # we don't want to store the defaults. - if self.data_validator: - await self.data_validator(hass, config_key, data) - else: - self.data_schema(data) - except (vol.Invalid, HomeAssistantError) as err: - return self.json_message( - f"Message malformed: {err}", HTTPStatus.BAD_REQUEST - ) - - path = hass.config.path(self.path) - - async with self.mutation_lock: - current = await self.read_config(hass) - self._write_value(hass, current, config_key, data) - - await hass.async_add_executor_job(_write, path, current) - - if self.post_write_hook is not None: - hass.async_create_task( - self.post_write_hook(ACTION_CREATE_UPDATE, config_key) - ) - - return self.json({"result": "ok"}) - - @require_admin - async def delete(self, request: web.Request, config_key: str) -> web.Response: - """Remove an entry.""" - hass: HomeAssistant = request.app["hass"] - async with self.mutation_lock: - current = await self.read_config(hass) - value = self._get_value(hass, current, config_key) - path = hass.config.path(self.path) - - if value is None: - return self.json_message("Resource not found", HTTPStatus.BAD_REQUEST) - - self._delete_value(hass, current, config_key) - await hass.async_add_executor_job(_write, path, current) - - if self.post_write_hook is not None: - hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key)) - - return self.json({"result": "ok"}) - - async def read_config(self, hass: HomeAssistant) -> _DataT: - """Read the config.""" - current = await hass.async_add_executor_job(_read, hass.config.path(self.path)) - if not current: - current = self._empty_config() - return cast(_DataT, current) - - -class EditKeyBasedConfigView(BaseEditConfigView[dict[str, dict[str, Any]]]): - """Configure a list of entries.""" - - def _empty_config(self) -> dict[str, Any]: - """Return an empty config.""" - return {} - - def _get_value( - self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str - ) -> dict[str, Any] | None: - """Get value.""" - return data.get(config_key) - - def _write_value( - self, - hass: HomeAssistant, - data: dict[str, dict[str, Any]], - config_key: str, - new_value: dict[str, Any], - ) -> None: - """Set value.""" - data.setdefault(config_key, {}).update(new_value) - - def _delete_value( - self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str - ) -> dict[str, Any]: - """Delete value.""" - return data.pop(config_key) - - -class EditIdBasedConfigView(BaseEditConfigView[list[dict[str, Any]]]): - """Configure key based config entries.""" - - def _empty_config(self) -> list[Any]: - """Return an empty config.""" - return [] - - def _get_value( - self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str - ) -> dict[str, Any] | None: - """Get value.""" - return next((val for val in data if val.get(CONF_ID) == config_key), None) - - def _write_value( - self, - hass: HomeAssistant, - data: list[dict[str, Any]], - config_key: str, - new_value: dict[str, Any], - ) -> None: - """Set value.""" - if (value := self._get_value(hass, data, config_key)) is None: - value = {CONF_ID: config_key} - data.append(value) - - value.update(new_value) - - def _delete_value( - self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str - ) -> None: - """Delete value.""" - index = next( - idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key - ) - data.pop(index) - - -def _read(path: str) -> JSON_TYPE | None: - """Read YAML helper.""" - if not os.path.isfile(path): - return None - - return load_yaml(path) - - -def _write(path: str, data: dict | list) -> None: - """Write YAML helper.""" - # Do it before opening file. If dump causes error it will now not - # truncate the file. - contents = dump(data) - write_utf8_file_atomic(path, contents) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index cf637b0aa23..59d4854533d 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -14,7 +14,8 @@ from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv, entity_registry as er -from . import ACTION_DELETE, EditIdBasedConfigView +from .const import ACTION_DELETE +from .view import EditIdBasedConfigView @callback diff --git a/homeassistant/components/config/const.py b/homeassistant/components/config/const.py new file mode 100644 index 00000000000..f48d00a7ca9 --- /dev/null +++ b/homeassistant/components/config/const.py @@ -0,0 +1,5 @@ +"""Constants for config.""" + +ACTION_CREATE_UPDATE = "create_update" +ACTION_DELETE = "delete" +DOMAIN = "config" diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index 01bdce0c8bc..dbcce222a4f 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -10,7 +10,8 @@ from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, entity_registry as er -from . import ACTION_DELETE, EditIdBasedConfigView +from .const import ACTION_DELETE +from .view import EditIdBasedConfigView @callback diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index d181ad94286..35596c7a84f 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -13,7 +13,8 @@ from homeassistant.const import SERVICE_RELOAD from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv, entity_registry as er -from . import ACTION_DELETE, EditKeyBasedConfigView +from .const import ACTION_DELETE +from .view import EditKeyBasedConfigView @callback diff --git a/homeassistant/components/config/view.py b/homeassistant/components/config/view.py new file mode 100644 index 00000000000..cf24074bda9 --- /dev/null +++ b/homeassistant/components/config/view.py @@ -0,0 +1,243 @@ +"""Component to configure Home Assistant via an API.""" +from __future__ import annotations + +import asyncio +from collections.abc import Callable, Coroutine +from http import HTTPStatus +import os +from typing import Any, Generic, TypeVar, cast + +from aiohttp import web +import voluptuous as vol + +from homeassistant.components.http import HomeAssistantView, require_admin +from homeassistant.const import CONF_ID +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.util.file import write_utf8_file_atomic +from homeassistant.util.yaml import dump, load_yaml +from homeassistant.util.yaml.loader import JSON_TYPE + +from .const import ACTION_CREATE_UPDATE, ACTION_DELETE + +_DataT = TypeVar("_DataT", dict[str, dict[str, Any]], list[dict[str, Any]]) + + +class BaseEditConfigView(HomeAssistantView, Generic[_DataT]): + """Configure a Group endpoint.""" + + def __init__( + self, + component: str, + config_type: str, + path: str, + key_schema: Callable[[Any], str], + data_schema: Callable[[dict[str, Any]], Any], + *, + post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None, + data_validator: Callable[ + [HomeAssistant, str, dict[str, Any]], + Coroutine[Any, Any, dict[str, Any] | None], + ] + | None = None, + ) -> None: + """Initialize a config view.""" + self.url = f"/api/config/{component}/{config_type}/{{config_key}}" + self.name = f"api:config:{component}:{config_type}" + self.path = path + self.key_schema = key_schema + self.data_schema = data_schema + self.post_write_hook = post_write_hook + self.data_validator = data_validator + self.mutation_lock = asyncio.Lock() + + def _empty_config(self) -> _DataT: + """Empty config if file not found.""" + raise NotImplementedError + + def _get_value( + self, hass: HomeAssistant, data: _DataT, config_key: str + ) -> dict[str, Any] | None: + """Get value.""" + raise NotImplementedError + + def _write_value( + self, + hass: HomeAssistant, + data: _DataT, + config_key: str, + new_value: dict[str, Any], + ) -> None: + """Set value.""" + raise NotImplementedError + + def _delete_value( + self, hass: HomeAssistant, data: _DataT, config_key: str + ) -> dict[str, Any] | None: + """Delete value.""" + raise NotImplementedError + + @require_admin + async def get(self, request: web.Request, config_key: str) -> web.Response: + """Fetch device specific config.""" + hass: HomeAssistant = request.app["hass"] + async with self.mutation_lock: + current = await self.read_config(hass) + value = self._get_value(hass, current, config_key) + + if value is None: + return self.json_message("Resource not found", HTTPStatus.NOT_FOUND) + + return self.json(value) + + @require_admin + async def post(self, request: web.Request, config_key: str) -> web.Response: + """Validate config and return results.""" + try: + data = await request.json() + except ValueError: + return self.json_message("Invalid JSON specified", HTTPStatus.BAD_REQUEST) + + try: + self.key_schema(config_key) + except vol.Invalid as err: + return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST) + + hass: HomeAssistant = request.app["hass"] + + try: + # We just validate, we don't store that data because + # we don't want to store the defaults. + if self.data_validator: + await self.data_validator(hass, config_key, data) + else: + self.data_schema(data) + except (vol.Invalid, HomeAssistantError) as err: + return self.json_message( + f"Message malformed: {err}", HTTPStatus.BAD_REQUEST + ) + + path = hass.config.path(self.path) + + async with self.mutation_lock: + current = await self.read_config(hass) + self._write_value(hass, current, config_key, data) + + await hass.async_add_executor_job(_write, path, current) + + if self.post_write_hook is not None: + hass.async_create_task( + self.post_write_hook(ACTION_CREATE_UPDATE, config_key) + ) + + return self.json({"result": "ok"}) + + @require_admin + async def delete(self, request: web.Request, config_key: str) -> web.Response: + """Remove an entry.""" + hass: HomeAssistant = request.app["hass"] + async with self.mutation_lock: + current = await self.read_config(hass) + value = self._get_value(hass, current, config_key) + path = hass.config.path(self.path) + + if value is None: + return self.json_message("Resource not found", HTTPStatus.BAD_REQUEST) + + self._delete_value(hass, current, config_key) + await hass.async_add_executor_job(_write, path, current) + + if self.post_write_hook is not None: + hass.async_create_task(self.post_write_hook(ACTION_DELETE, config_key)) + + return self.json({"result": "ok"}) + + async def read_config(self, hass: HomeAssistant) -> _DataT: + """Read the config.""" + current = await hass.async_add_executor_job(_read, hass.config.path(self.path)) + if not current: + current = self._empty_config() + return cast(_DataT, current) + + +class EditKeyBasedConfigView(BaseEditConfigView[dict[str, dict[str, Any]]]): + """Configure a list of entries.""" + + def _empty_config(self) -> dict[str, Any]: + """Return an empty config.""" + return {} + + def _get_value( + self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str + ) -> dict[str, Any] | None: + """Get value.""" + return data.get(config_key) + + def _write_value( + self, + hass: HomeAssistant, + data: dict[str, dict[str, Any]], + config_key: str, + new_value: dict[str, Any], + ) -> None: + """Set value.""" + data.setdefault(config_key, {}).update(new_value) + + def _delete_value( + self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str + ) -> dict[str, Any]: + """Delete value.""" + return data.pop(config_key) + + +class EditIdBasedConfigView(BaseEditConfigView[list[dict[str, Any]]]): + """Configure key based config entries.""" + + def _empty_config(self) -> list[Any]: + """Return an empty config.""" + return [] + + def _get_value( + self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str + ) -> dict[str, Any] | None: + """Get value.""" + return next((val for val in data if val.get(CONF_ID) == config_key), None) + + def _write_value( + self, + hass: HomeAssistant, + data: list[dict[str, Any]], + config_key: str, + new_value: dict[str, Any], + ) -> None: + """Set value.""" + if (value := self._get_value(hass, data, config_key)) is None: + value = {CONF_ID: config_key} + data.append(value) + + value.update(new_value) + + def _delete_value( + self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str + ) -> None: + """Delete value.""" + index = next( + idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key + ) + data.pop(index) + + +def _read(path: str) -> JSON_TYPE | None: + """Read YAML helper.""" + if not os.path.isfile(path): + return None + + return load_yaml(path) + + +def _write(path: str, data: dict | list) -> None: + """Write YAML helper.""" + # Do it before opening file. If dump causes error it will now not + # truncate the file. + contents = dump(data) + write_utf8_file_atomic(path, contents) diff --git a/tests/components/config/conftest.py b/tests/components/config/conftest.py index e6f1532428e..83c8cd014f3 100644 --- a/tests/components/config/conftest.py +++ b/tests/components/config/conftest.py @@ -51,11 +51,11 @@ def mock_config_store(data=None): return result with patch( - "homeassistant.components.config._read", + "homeassistant.components.config.view._read", side_effect=mock_read, autospec=True, ), patch( - "homeassistant.components.config._write", + "homeassistant.components.config.view._write", side_effect=mock_write, autospec=True, ), patch( diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 1a099c05b16..9ba5762e1d0 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -8,6 +8,7 @@ import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.components.config import automation from homeassistant.const import STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -41,7 +42,7 @@ async def test_get_automation_config( setup_automation, ) -> None: """Test getting automation config.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) client = await hass_client() @@ -64,7 +65,7 @@ async def test_update_automation_config( setup_automation, ) -> None: """Test updating automation config.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("automation")) == [] @@ -153,7 +154,7 @@ async def test_update_automation_config_with_error( validation_error: str, ) -> None: """Test updating automation config with errors.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("automation")) == [] @@ -206,7 +207,7 @@ async def test_update_automation_config_with_blueprint_substitution_error( validation_error: str, ) -> None: """Test updating automation config with errors.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("automation")) == [] @@ -242,7 +243,7 @@ async def test_update_remove_key_automation_config( setup_automation, ) -> None: """Test updating automation config while removing a key.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("automation")) == [] @@ -281,7 +282,7 @@ async def test_bad_formatted_automations( setup_automation, ) -> None: """Test that we handle automations without ID.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("automation")) == [] @@ -347,7 +348,7 @@ async def test_delete_automation( assert len(entity_registry.entities) == 2 - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): assert await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("automation")) == [ @@ -385,7 +386,7 @@ async def test_api_calls_require_admin( setup_automation, ) -> None: """Test cloud APIs endpoints do not work as a normal user.""" - with patch.object(config, "SECTIONS", ["automation"]): + with patch.object(config, "SECTIONS", [automation]): await async_setup_component(hass, "config", {}) hass_config_store["automations.yaml"] = [{"id": "sun"}, {"id": "moon"}] diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index bd21e5e7d30..e50ec97e1b1 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -6,6 +6,7 @@ import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.components.config import core from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import ( CONF_UNIT_SYSTEM, @@ -23,7 +24,7 @@ from tests.typing import ClientSessionGenerator, WebSocketGenerator @pytest.fixture async def client(hass, hass_ws_client): """Fixture that can interact with the config manager API.""" - with patch.object(config, "SECTIONS", ["core"]): + with patch.object(config, "SECTIONS", [core]): assert await async_setup_component(hass, "config", {}) return await hass_ws_client(hass) @@ -32,7 +33,7 @@ async def test_validate_config_ok( hass: HomeAssistant, hass_client: ClientSessionGenerator ) -> None: """Test checking config.""" - with patch.object(config, "SECTIONS", ["core"]): + with patch.object(config, "SECTIONS", [core]): await async_setup_component(hass, "config", {}) client = await hass_client() @@ -95,7 +96,7 @@ async def test_validate_config_requires_admin( hass_read_only_access_token: str, ) -> None: """Test checking configuration does not work as a normal user.""" - with patch.object(config, "SECTIONS", ["core"]): + with patch.object(config, "SECTIONS", [core]): await async_setup_component(hass, "config", {}) client = await hass_client(hass_read_only_access_token) @@ -180,7 +181,7 @@ async def test_websocket_core_update_not_admin( ) -> None: """Test core config fails for non admin.""" hass_admin_user.groups = [] - with patch.object(config, "SECTIONS", ["core"]): + with patch.object(config, "SECTIONS", [core]): await async_setup_component(hass, "config", {}) client = await hass_ws_client(hass) diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index 9fd596f7f91..2f2ca9b1c37 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -7,6 +7,7 @@ import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.components.config import scene from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -27,7 +28,7 @@ async def test_create_scene( setup_scene, ) -> None: """Test creating a scene.""" - with patch.object(config, "SECTIONS", ["scene"]): + with patch.object(config, "SECTIONS", [scene]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("scene")) == [] @@ -74,7 +75,7 @@ async def test_update_scene( setup_scene, ) -> None: """Test updating a scene.""" - with patch.object(config, "SECTIONS", ["scene"]): + with patch.object(config, "SECTIONS", [scene]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("scene")) == [] @@ -122,7 +123,7 @@ async def test_bad_formatted_scene( setup_scene, ) -> None: """Test that we handle scene without ID.""" - with patch.object(config, "SECTIONS", ["scene"]): + with patch.object(config, "SECTIONS", [scene]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("scene")) == [] @@ -192,7 +193,7 @@ async def test_delete_scene( assert len(entity_registry.entities) == 2 - with patch.object(config, "SECTIONS", ["scene"]): + with patch.object(config, "SECTIONS", [scene]): assert await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("scene")) == [ @@ -232,7 +233,7 @@ async def test_api_calls_require_admin( setup_scene, ) -> None: """Test scene APIs endpoints do not work as a normal user.""" - with patch.object(config, "SECTIONS", ["scene"]): + with patch.object(config, "SECTIONS", [scene]): await async_setup_component(hass, "config", {}) hass_config_store["scenes.yaml"] = [ diff --git a/tests/components/config/test_script.py b/tests/components/config/test_script.py index 7cf8cf5833e..bebec0aedba 100644 --- a/tests/components/config/test_script.py +++ b/tests/components/config/test_script.py @@ -8,6 +8,7 @@ import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from homeassistant.components.config import script from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -32,7 +33,7 @@ async def test_get_script_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: """Test getting script config.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) client = await hass_client() @@ -55,7 +56,7 @@ async def test_update_script_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: """Test updating script config.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("script")) == [] @@ -91,7 +92,7 @@ async def test_invalid_object_id( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: """Test creating a script with an invalid object_id.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("script")) == [] @@ -156,7 +157,7 @@ async def test_update_script_config_with_error( validation_error: str, ) -> None: """Test updating script config with errors.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("script")) == [] @@ -207,7 +208,7 @@ async def test_update_script_config_with_blueprint_substitution_error( validation_error: str, ) -> None: """Test updating script config with errors.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("script")) == [] @@ -240,7 +241,7 @@ async def test_update_remove_key_script_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: """Test updating script config while removing a key.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("script")) == [] @@ -287,7 +288,7 @@ async def test_delete_script( hass_config_store, ) -> None: """Test deleting a script.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) assert sorted(hass.states.async_entity_ids("script")) == [ @@ -326,7 +327,7 @@ async def test_api_calls_require_admin( hass_config_store, ) -> None: """Test script APIs endpoints do not work as a normal user.""" - with patch.object(config, "SECTIONS", ["script"]): + with patch.object(config, "SECTIONS", [script]): await async_setup_component(hass, "config", {}) hass_config_store["scripts.yaml"] = { From e568f867d2a509646d20f47fda123d0938060ebe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 16:31:10 -1000 Subject: [PATCH 0413/1691] Adjust MAX_LOAD_CONCURRENTLY constant to allow 6 storage loaders (#112468) --- homeassistant/bootstrap.py | 1 - homeassistant/helpers/storage.py | 3 ++- homeassistant/loader.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 030099a9e2c..0800586eb85 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -101,7 +101,6 @@ STAGE_2_TIMEOUT = 300 WRAP_UP_TIMEOUT = 300 COOLDOWN_TIME = 60 -MAX_LOAD_CONCURRENTLY = 6 DEBUGGER_INTEGRATIONS = {"debugpy"} CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"} diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 44460ffa601..39cfac885cc 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -21,7 +21,7 @@ from homeassistant.core import ( callback, ) from homeassistant.exceptions import HomeAssistantError -from homeassistant.loader import MAX_LOAD_CONCURRENTLY, bind_hass +from homeassistant.loader import bind_hass from homeassistant.util import json as json_util import homeassistant.util.dt as dt_util from homeassistant.util.file import WriteError @@ -36,6 +36,7 @@ else: # mypy: allow-untyped-calls, allow-untyped-defs, no-warn-return-any # mypy: no-check-untyped-defs +MAX_LOAD_CONCURRENTLY = 6 STORAGE_DIR = ".storage" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index ac1d71a632c..06bac608aec 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -98,7 +98,6 @@ CUSTOM_WARNING = ( _UNDEF = object() # Internal; not helpers.typing.UNDEFINED due to circular dependency -MAX_LOAD_CONCURRENTLY = 4 MOVED_ZEROCONF_PROPS = ("macaddress", "model", "manufacturer") From aa216f0298d5a1398f9ed7472bc52f898393df58 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 16:31:38 -1000 Subject: [PATCH 0414/1691] Add system_health to the hassio pre-imports to avoid a late executor job (#112466) * Add system_health to the hassio pre-imports to avoid a late executor job `2024-03-05 17:01:33.034 DEBUG (MainThread) [homeassistant.loader] Importing platforms for hassio executor=[system_health] loop=[] took 0.12s` This one does not take that much time but it happens at a time where the import executor is the most busy during startup * key * move patch as its too early now --- homeassistant/components/hassio/__init__.py | 13 ++++++++++--- homeassistant/components/hassio/system_health.py | 11 +++++++---- tests/components/hassio/test_system_health.py | 12 ++++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index cd1c6e735ef..1a666fcc42d 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -43,10 +43,17 @@ from homeassistant.loader import bind_hass from homeassistant.util.async_ import create_eager_task from homeassistant.util.dt import now -# config_flow, diagnostics, and entity platforms are imported to ensure -# other dependencies that wait for hassio are not waiting +# config_flow, diagnostics, system_health, and entity platforms are imported to +# ensure other dependencies that wait for hassio are not waiting # for hassio to import its platforms -from . import binary_sensor, config_flow, diagnostics, sensor, update # noqa: F401 +from . import ( # noqa: F401 + binary_sensor, + config_flow, + diagnostics, + sensor, + system_health, + update, +) from .addon_manager import AddonError, AddonInfo, AddonManager, AddonState # noqa: F401 from .addon_panel import async_setup_addon_panel from .auth import async_setup_auth_view diff --git a/homeassistant/components/hassio/system_health.py b/homeassistant/components/hassio/system_health.py index d89224a2476..e46db2f8e75 100644 --- a/homeassistant/components/hassio/system_health.py +++ b/homeassistant/components/hassio/system_health.py @@ -7,10 +7,10 @@ from typing import Any from homeassistant.components import system_health from homeassistant.core import HomeAssistant, callback -from . import get_host_info, get_info, get_os_info, get_supervisor_info +from .data import get_host_info, get_info, get_os_info, get_supervisor_info -SUPERVISOR_PING = f"http://{os.environ['SUPERVISOR']}/supervisor/ping" -OBSERVER_URL = f"http://{os.environ['SUPERVISOR']}:4357" +SUPERVISOR_PING = "http://{ip_address}/supervisor/ping" +OBSERVER_URL = "http://{ip_address}:4357" @callback @@ -23,6 +23,7 @@ def async_register( async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: """Get info for the info page.""" + ip_address = os.environ["SUPERVISOR"] info = get_info(hass) or {} host_info = get_host_info(hass) or {} supervisor_info = get_supervisor_info(hass) @@ -62,7 +63,9 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: information["board"] = os_info.get("board") information["supervisor_api"] = system_health.async_check_can_reach_url( - hass, SUPERVISOR_PING, OBSERVER_URL + hass, + SUPERVISOR_PING.format(ip_address=ip_address), + OBSERVER_URL.format(ip_address=ip_address), ) information["version_api"] = system_health.async_check_can_reach_url( hass, diff --git a/tests/components/hassio/test_system_health.py b/tests/components/hassio/test_system_health.py index 7a01c9444ab..715bf3bab91 100644 --- a/tests/components/hassio/test_system_health.py +++ b/tests/components/hassio/test_system_health.py @@ -28,8 +28,7 @@ async def test_hassio_system_health( ) hass.config.components.add("hassio") - with patch.dict(os.environ, MOCK_ENVIRON): - assert await async_setup_component(hass, "system_health", {}) + assert await async_setup_component(hass, "system_health", {}) hass.data["hassio_info"] = { "channel": "stable", @@ -50,7 +49,8 @@ async def test_hassio_system_health( "addons": [{"name": "Awesome Addon", "version": "1.0.0"}], } - info = await get_system_health_info(hass, "hassio") + with patch.dict(os.environ, MOCK_ENVIRON): + info = await get_system_health_info(hass, "hassio") for key, val in info.items(): if asyncio.iscoroutine(val): @@ -87,8 +87,7 @@ async def test_hassio_system_health_with_issues( ) hass.config.components.add("hassio") - with patch.dict(os.environ, MOCK_ENVIRON): - assert await async_setup_component(hass, "system_health", {}) + assert await async_setup_component(hass, "system_health", {}) hass.data["hassio_info"] = {"channel": "stable"} hass.data["hassio_host_info"] = {} @@ -98,7 +97,8 @@ async def test_hassio_system_health_with_issues( "supported": False, } - info = await get_system_health_info(hass, "hassio") + with patch.dict(os.environ, MOCK_ENVIRON): + info = await get_system_health_info(hass, "hassio") for key, val in info.items(): if asyncio.iscoroutine(val): From 9be4fb057491456b9340a01998588591b3ba0471 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 16:32:02 -1000 Subject: [PATCH 0415/1691] Start fetching the source ip sooner in http to reduce setup time (#112461) --- homeassistant/components/http/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index ab228e32a52..6f60112c9ba 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -42,6 +42,7 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.setup import async_start_setup, async_when_setup_or_start from homeassistant.util import dt as dt_util, ssl as ssl_util +from homeassistant.util.async_ import create_eager_task from homeassistant.util.json import json_loads from .auth import async_setup_auth @@ -188,6 +189,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: login_threshold = conf[CONF_LOGIN_ATTEMPTS_THRESHOLD] ssl_profile = conf[CONF_SSL_PROFILE] + source_ip_task = create_eager_task(async_get_source_ip(hass)) + server = HomeAssistantHTTP( hass, server_host=server_host, @@ -222,7 +225,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.http = server - local_ip = await async_get_source_ip(hass) + local_ip = await source_ip_task host = local_ip if server_host is not None: From 2c13a810864691eed6858f61b3e740e253b59175 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 16:32:23 -1000 Subject: [PATCH 0416/1691] Load network storage in a task to reduce startup time (#112460) We waited for these in series but they are not dependant on each other --- homeassistant/components/network/network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/network/network.py b/homeassistant/components/network/network.py index 0b90023bfd4..4613cb91cc9 100644 --- a/homeassistant/components/network/network.py +++ b/homeassistant/components/network/network.py @@ -7,6 +7,7 @@ from typing import Any from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.singleton import singleton from homeassistant.helpers.storage import Store +from homeassistant.util.async_ import create_eager_task from .const import ( ATTR_CONFIGURED_ADAPTERS, @@ -50,8 +51,9 @@ class Network: async def async_setup(self) -> None: """Set up the network config.""" - await self.async_load() + storage_load_task = create_eager_task(self.async_load()) self.adapters = await async_load_adapters() + await storage_load_task @callback def async_configure(self) -> None: From fe0ba1141dd9b0d28907ce8d0a9be19f746a7cc2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 6 Mar 2024 03:41:20 +0100 Subject: [PATCH 0417/1691] Fix handling missing parameter by bumping axis library to v50 (#112437) Fix handling missing parameter --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index bd6faf8b149..5311d18f991 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==49"], + "requirements": ["axis==50"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index 2f733ac71d0..5fbd14cd5a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==49 +axis==50 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 702a0a936b4..395efe6dbbb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -454,7 +454,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==49 +axis==50 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From f5700aa3184ffa63c4016760e04095b7c6daf4a8 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 6 Mar 2024 03:43:58 +0100 Subject: [PATCH 0418/1691] Bump holidays to 0.44 (#112442) --- homeassistant/components/holiday/manifest.json | 2 +- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/holiday/manifest.json b/homeassistant/components/holiday/manifest.json index 234df998035..5f78d961810 100644 --- a/homeassistant/components/holiday/manifest.json +++ b/homeassistant/components/holiday/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/holiday", "iot_class": "local_polling", - "requirements": ["holidays==0.43", "babel==2.13.1"] + "requirements": ["holidays==0.44", "babel==2.13.1"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 62819f74c2a..96a3b53797c 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_polling", "loggers": ["holidays"], "quality_scale": "internal", - "requirements": ["holidays==0.43"] + "requirements": ["holidays==0.44"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5fbd14cd5a1..f3a46575980 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1071,7 +1071,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.43 +holidays==0.44 # homeassistant.components.frontend home-assistant-frontend==20240304.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 395efe6dbbb..1fe5b97e36a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -870,7 +870,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.43 +holidays==0.44 # homeassistant.components.frontend home-assistant-frontend==20240304.0 From 982c8f8f4a08fa1f473b236bfca120ee1941ef96 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 18:46:24 -1000 Subject: [PATCH 0419/1691] Fix incorrect scope on checking files to load in translations (#112457) discovered in https://github.com/home-assistant/core/pull/112295#discussion_r1513505710 We only checked if the last language had files to load instead of all of them. The checks for each language are the same because the only reason we would skip a language is a missing/broken integration or the integration is a single file. Both of these loop conditions are always the same reguardless of the language so the check worked --- homeassistant/helpers/translation.py | 12 ++++++----- tests/helpers/test_translation.py | 31 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index ca79ec443ab..b799469e2d3 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -162,13 +162,14 @@ async def _async_get_component_strings( translations_by_language: dict[str, dict[str, Any]] = {} # Determine paths of missing components/platforms files_to_load_by_language: dict[str, dict[str, str]] = {} + has_files_to_load = False for language in languages: files_to_load: dict[str, str] = {} files_to_load_by_language[language] = files_to_load translations_by_language[language] = {} - for loaded in components: - domain, _, platform = loaded.partition(".") + for comp in components: + domain, _, platform = comp.partition(".") if not (integration := integrations.get(domain)): continue @@ -179,10 +180,11 @@ async def _async_get_component_strings( # them. continue - if path := component_translation_path(loaded, language, integration): - files_to_load[loaded] = path + if path := component_translation_path(comp, language, integration): + files_to_load[comp] = path + has_files_to_load = True - if not files_to_load: + if not has_files_to_load: return translations_by_language # Load files diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 69d7ef274ae..839607cc8b8 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -541,6 +541,37 @@ async def test_ensure_translations_still_load_if_one_integration_fails( assert translations == sensor_translations +async def test_load_translations_all_integrations_broken( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Ensure we do not try to load translations again if the integration is broken.""" + hass.config.components.add("broken") + hass.config.components.add("broken2") + + with patch( + "homeassistant.helpers.translation.async_get_integrations", + return_value={ + "broken2": Exception("unhandled failure"), + "broken": Exception("unhandled failure"), + }, + ): + translations = await translation.async_get_translations( + hass, "en", "entity_component", integrations={"broken", "broken2"} + ) + assert "Failed to load integration for translation" in caplog.text + assert "broken" in caplog.text + assert "broken2" in caplog.text + assert not translations + caplog.clear() + + translations = await translation.async_get_translations( + hass, "en", "entity_component", integrations={"broken", "broken2"} + ) + assert not translations + # Ensure we do not try again + assert "Failed to load integration for translation" not in caplog.text + + async def test_caching(hass: HomeAssistant) -> None: """Test we cache data.""" hass.config.components.add("sensor") From 0f69a0647cd1176561767571600581684ab2061d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 18:47:11 -1000 Subject: [PATCH 0420/1691] Migrate remaining get_platform in config to async_get_platform (#112469) This was the only remaining case where blocking I/O might have happened in config. It was unlikely though as async_get_component should have pre-imported the config platform --- homeassistant/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index bd6d14f8c10..354eb0d858f 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1098,7 +1098,9 @@ async def merge_packages_config( continue try: - config_platform: ModuleType | None = integration.get_platform("config") + config_platform: ModuleType | None = ( + await integration.async_get_platform("config") + ) # Test if config platform has a config validator if not hasattr(config_platform, "async_validate_config"): config_platform = None From 8fe80a4766aa7cc55ab911bfd78f85671fac4c3c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 18:47:41 -1000 Subject: [PATCH 0421/1691] Migrate remaining get_platform in check_config to async_get_platform (#112470) These were very likely to be cached so they were low on the list to migrate, but since they are called in the event loop its best to be sure we do no blocking I/O --- homeassistant/helpers/check_config.py | 4 ++-- tests/helpers/test_check_config.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index b362d68ad55..0de11303cf9 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -199,7 +199,7 @@ async def async_check_ha_config_file( # noqa: C901 # Check if the integration has a custom config validator config_validator = None try: - config_validator = integration.get_platform("config") + config_validator = await integration.async_get_platform("config") except ImportError as err: # Filter out import error of the config platform. # If the config platform contains bad imports, make sure @@ -270,7 +270,7 @@ async def async_check_ha_config_file( # noqa: C901 p_integration = await async_get_integration_with_requirements( hass, p_name ) - platform = p_integration.get_platform(domain) + platform = await p_integration.async_get_platform(domain) except loader.IntegrationNotFound as ex: # We get this error if an integration is not found. In recovery mode and # safe mode, this currently happens for all custom integrations. Don't diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index edf1066f744..a7c5fe36cff 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -338,7 +338,7 @@ async def test_config_platform_import_error(hass: HomeAssistant) -> None: # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"} with patch( - "homeassistant.loader.Integration.get_platform", + "homeassistant.loader.Integration.async_get_platform", side_effect=ImportError("blablabla"), ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): res = await async_check_ha_config_file(hass) @@ -358,7 +358,7 @@ async def test_platform_import_error(hass: HomeAssistant) -> None: # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"} with patch( - "homeassistant.loader.Integration.get_platform", + "homeassistant.loader.Integration.async_get_platform", side_effect=[None, ImportError("blablabla")], ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): res = await async_check_ha_config_file(hass) From 48f1b08e4b4a3d13e21b3e8c8f6d4ad85e7fc3ba Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:27:15 +0100 Subject: [PATCH 0422/1691] Add icon translations to Zeversolar (#112367) --- homeassistant/components/zeversolar/icons.json | 12 ++++++++++++ homeassistant/components/zeversolar/sensor.py | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/zeversolar/icons.json diff --git a/homeassistant/components/zeversolar/icons.json b/homeassistant/components/zeversolar/icons.json new file mode 100644 index 00000000000..8e30a4df86b --- /dev/null +++ b/homeassistant/components/zeversolar/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "pac": { + "default": "mdi:solar-power-variant" + }, + "energy_today": { + "default": "mdi:home-battery" + } + } + } +} diff --git a/homeassistant/components/zeversolar/sensor.py b/homeassistant/components/zeversolar/sensor.py index 9e2333a1e24..ca09e5773b0 100644 --- a/homeassistant/components/zeversolar/sensor.py +++ b/homeassistant/components/zeversolar/sensor.py @@ -39,7 +39,7 @@ class ZeversolarEntityDescription( SENSOR_TYPES = ( ZeversolarEntityDescription( key="pac", - icon="mdi:solar-power-variant", + translation_key="pac", native_unit_of_measurement=UnitOfPower.WATT, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, @@ -49,7 +49,6 @@ SENSOR_TYPES = ( ZeversolarEntityDescription( key="energy_today", translation_key="energy_today", - icon="mdi:home-battery", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, device_class=SensorDeviceClass.ENERGY, From f12d5ba368fc925c2a42c9d5c3ec80d04c34bf46 Mon Sep 17 00:00:00 2001 From: Martin Eberhardt Date: Wed, 6 Mar 2024 06:30:10 +0100 Subject: [PATCH 0423/1691] Remove myself as maintainer of the Rejseplanen integration (#112426) * Remove myself as maintainer of the Rejseplanen integration Unfortunately I don't have the time or energy to maintain the integration. I have made a post on the Danish HA Facebook group, encouraging someone to take over. https://www.facebook.com/groups/209025039666209/posts/1515990205636346/ * Update CODEOWNERS --- CODEOWNERS | 1 - homeassistant/components/rejseplanen/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 31da9f521ba..d4d1e70388f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1093,7 +1093,6 @@ build.json @home-assistant/supervisor /tests/components/recovery_mode/ @home-assistant/core /homeassistant/components/refoss/ @ashionky /tests/components/refoss/ @ashionky -/homeassistant/components/rejseplanen/ @DarkFox /homeassistant/components/remote/ @home-assistant/core /tests/components/remote/ @home-assistant/core /homeassistant/components/renault/ @epenet diff --git a/homeassistant/components/rejseplanen/manifest.json b/homeassistant/components/rejseplanen/manifest.json index a54bc1f075b..72da7a65f45 100644 --- a/homeassistant/components/rejseplanen/manifest.json +++ b/homeassistant/components/rejseplanen/manifest.json @@ -1,7 +1,7 @@ { "domain": "rejseplanen", "name": "Rejseplanen", - "codeowners": ["@DarkFox"], + "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/rejseplanen", "iot_class": "cloud_polling", "loggers": ["rjpl"], From 661209111c2a5db2ef579a4c797f08ef66ff3ae6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:30:21 +0100 Subject: [PATCH 0424/1691] Add icon translations to Tado (#112301) --- homeassistant/components/tado/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/tado/icons.json diff --git a/homeassistant/components/tado/icons.json b/homeassistant/components/tado/icons.json new file mode 100644 index 00000000000..83ef6d4b332 --- /dev/null +++ b/homeassistant/components/tado/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "set_climate_timer": "mdi:timer", + "set_water_heater_timer": "mdi:timer", + "set_climate_temperature_offset": "mdi:thermometer", + "add_meter_reading": "mdi:counter" + } +} From 9d89adb3d0b44ab28a7e6abc2a9b1f0e0ea9dfe4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:30:34 +0100 Subject: [PATCH 0425/1691] Add icon translations to iCloud (#111812) --- homeassistant/components/icloud/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/icloud/icons.json diff --git a/homeassistant/components/icloud/icons.json b/homeassistant/components/icloud/icons.json new file mode 100644 index 00000000000..4ed856aabc1 --- /dev/null +++ b/homeassistant/components/icloud/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "update": "mdi:update", + "play_sound": "mdi:speaker-wireless", + "display_message": "mdi:message-alert", + "lost_device": "mdi:devices" + } +} From 102dbdabb5490a38556569f837d1e6c3418ed8ec Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:30:46 +0100 Subject: [PATCH 0426/1691] Add icon translations to Weatherflow (#112353) --- .../components/weatherflow/icons.json | 21 +++++++++++++++++++ .../components/weatherflow/sensor.py | 11 ---------- 2 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/weatherflow/icons.json diff --git a/homeassistant/components/weatherflow/icons.json b/homeassistant/components/weatherflow/icons.json new file mode 100644 index 00000000000..71a8b48415d --- /dev/null +++ b/homeassistant/components/weatherflow/icons.json @@ -0,0 +1,21 @@ +{ + "entity": { + "sensor": { + "lightning_average_distance": { + "default": "mdi:lightning-bolt" + }, + "lightning_count": { + "default": "mdi:lightning-bolt" + }, + "precipitation_type": { + "default": "mdi:weather-rainy" + }, + "wind_direction": { + "default": "mdi:compass-outline" + }, + "wind_direction_average": { + "default": "mdi:compass-outline" + } + } + } +} diff --git a/homeassistant/components/weatherflow/sensor.py b/homeassistant/components/weatherflow/sensor.py index bbdd79e1533..74642ae04af 100644 --- a/homeassistant/components/weatherflow/sensor.py +++ b/homeassistant/components/weatherflow/sensor.py @@ -138,7 +138,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( ), WeatherFlowSensorEntityDescription( key="lightning_strike_average_distance", - icon="mdi:lightning-bolt", state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=UnitOfLength.KILOMETERS, @@ -149,7 +148,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="lightning_strike_count", translation_key="lightning_count", - icon="mdi:lightning-bolt", state_class=SensorStateClass.TOTAL, raw_data_conv_fn=lambda raw_data: raw_data, ), @@ -158,12 +156,10 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( translation_key="precipitation_type", device_class=SensorDeviceClass.ENUM, options=["none", "rain", "hail", "rain_hail", "unknown"], - icon="mdi:weather-rainy", raw_data_conv_fn=precipitation_raw_conversion_fn, ), WeatherFlowSensorEntityDescription( key="rain_accumulation_previous_minute", - icon="mdi:weather-rainy", native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.PRECIPITATION, @@ -174,7 +170,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( key="rain_rate", state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.PRECIPITATION_INTENSITY, - icon="mdi:weather-rainy", native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR, raw_data_conv_fn=lambda raw_data: raw_data.magnitude, ), @@ -242,7 +237,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="wind_gust", translation_key="wind_gust", - icon="mdi:weather-windy", device_class=SensorDeviceClass.WIND_SPEED, native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -252,7 +246,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="wind_lull", translation_key="wind_lull", - icon="mdi:weather-windy", device_class=SensorDeviceClass.WIND_SPEED, native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -262,7 +255,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="wind_speed", device_class=SensorDeviceClass.WIND_SPEED, - icon="mdi:weather-windy", event_subscriptions=[EVENT_RAPID_WIND, EVENT_OBSERVATION], native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -272,7 +264,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="wind_average", translation_key="wind_speed_average", - icon="mdi:weather-windy", device_class=SensorDeviceClass.WIND_SPEED, native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -282,7 +273,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="wind_direction", translation_key="wind_direction", - icon="mdi:compass-outline", native_unit_of_measurement=DEGREE, state_class=SensorStateClass.MEASUREMENT, event_subscriptions=[EVENT_RAPID_WIND, EVENT_OBSERVATION], @@ -291,7 +281,6 @@ SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = ( WeatherFlowSensorEntityDescription( key="wind_direction_average", translation_key="wind_direction_average", - icon="mdi:compass-outline", native_unit_of_measurement=DEGREE, state_class=SensorStateClass.MEASUREMENT, raw_data_conv_fn=lambda raw_data: raw_data.magnitude, From af95d9058f5be5597436067dbfc0cfc8cd92d24a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:30:58 +0100 Subject: [PATCH 0427/1691] Add icon translations to Vesync (#112344) --- homeassistant/components/vesync/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/vesync/icons.json diff --git a/homeassistant/components/vesync/icons.json b/homeassistant/components/vesync/icons.json new file mode 100644 index 00000000000..a4bf4afd410 --- /dev/null +++ b/homeassistant/components/vesync/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "update_devices": "mdi:update" + } +} From 7f6c6e39c0dc1e36584e1f159cf43d237bf6db6f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:31:08 +0100 Subject: [PATCH 0428/1691] Add icon translations to Velux (#112341) --- homeassistant/components/velux/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/velux/icons.json diff --git a/homeassistant/components/velux/icons.json b/homeassistant/components/velux/icons.json new file mode 100644 index 00000000000..a16e7b50093 --- /dev/null +++ b/homeassistant/components/velux/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reboot_gateway": "mdi:restart" + } +} From 421014bf8dbb80dec553cb0655b226d188a95800 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:31:22 +0100 Subject: [PATCH 0429/1691] Add icon translations to Tradfri (#112319) --- homeassistant/components/tradfri/icons.json | 12 ++++++++++++ homeassistant/components/tradfri/sensor.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/tradfri/icons.json diff --git a/homeassistant/components/tradfri/icons.json b/homeassistant/components/tradfri/icons.json new file mode 100644 index 00000000000..5bacebb3595 --- /dev/null +++ b/homeassistant/components/tradfri/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "aqi": { + "default": "mdi:air-filter" + }, + "filter_life_remaining": { + "default": "mdi:clock-outline" + } + } + } +} diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 7f04b8aff03..da7e5336ba5 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -91,7 +91,6 @@ SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = ( translation_key="aqi", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, - icon="mdi:air-filter", value=_get_air_quality, ), TradfriSensorEntityDescription( @@ -99,7 +98,6 @@ SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = ( translation_key="filter_life_remaining", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTime.HOURS, - icon="mdi:clock-outline", value=_get_filter_time_left, ), ) From f357e13e3d55984cdfb4b88dd887ad69dfc54915 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:31:37 +0100 Subject: [PATCH 0430/1691] Add icon translations to UPnP (#112335) * Add icon translations to UPnP * Add icon translations to UPnP --- homeassistant/components/upnp/icons.json | 39 ++++++++++++++++++++++++ homeassistant/components/upnp/sensor.py | 11 ------- 2 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/upnp/icons.json diff --git a/homeassistant/components/upnp/icons.json b/homeassistant/components/upnp/icons.json new file mode 100644 index 00000000000..1d4ebaf183d --- /dev/null +++ b/homeassistant/components/upnp/icons.json @@ -0,0 +1,39 @@ +{ + "entity": { + "sensor": { + "data_received": { + "default": "mdi:server-network" + }, + "data_sent": { + "default": "mdi:server-network" + }, + "packets_received": { + "default": "mdi:server-network" + }, + "packets_sent": { + "default": "mdi:server-network" + }, + "external_ip": { + "default": "mdi:server-network" + }, + "uptime": { + "default": "mdi:server-network" + }, + "wan_status": { + "default": "mdi:server-network" + }, + "download_speed": { + "default": "mdi:server-network" + }, + "upload_speed": { + "default": "mdi:server-network" + }, + "packet_download_speed": { + "default": "mdi:server-network" + }, + "packet_upload_speed": { + "default": "mdi:server-network" + } + } + } +} diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index e493118f58e..ce7b95bbbf3 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -50,7 +50,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( UpnpSensorEntityDescription( key=BYTES_RECEIVED, translation_key="data_received", - icon="mdi:server-network", device_class=SensorDeviceClass.DATA_SIZE, native_unit_of_measurement=UnitOfInformation.BYTES, entity_registry_enabled_default=False, @@ -60,7 +59,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( UpnpSensorEntityDescription( key=BYTES_SENT, translation_key="data_sent", - icon="mdi:server-network", device_class=SensorDeviceClass.DATA_SIZE, native_unit_of_measurement=UnitOfInformation.BYTES, entity_registry_enabled_default=False, @@ -70,7 +68,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( UpnpSensorEntityDescription( key=PACKETS_RECEIVED, translation_key="packets_received", - icon="mdi:server-network", native_unit_of_measurement=DATA_PACKETS, entity_registry_enabled_default=False, state_class=SensorStateClass.TOTAL_INCREASING, @@ -79,7 +76,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( UpnpSensorEntityDescription( key=PACKETS_SENT, translation_key="packets_sent", - icon="mdi:server-network", native_unit_of_measurement=DATA_PACKETS, entity_registry_enabled_default=False, state_class=SensorStateClass.TOTAL_INCREASING, @@ -88,13 +84,11 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( UpnpSensorEntityDescription( key=ROUTER_IP, translation_key="external_ip", - icon="mdi:server-network", entity_category=EntityCategory.DIAGNOSTIC, ), UpnpSensorEntityDescription( key=ROUTER_UPTIME, translation_key="uptime", - icon="mdi:server-network", native_unit_of_measurement=UnitOfTime.SECONDS, entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, @@ -103,7 +97,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( UpnpSensorEntityDescription( key=WAN_STATUS, translation_key="wan_status", - icon="mdi:server-network", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), @@ -112,7 +105,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( translation_key="download_speed", value_key=KIBIBYTES_PER_SEC_RECEIVED, unique_id="KiB/sec_received", - icon="mdi:server-network", device_class=SensorDeviceClass.DATA_RATE, native_unit_of_measurement=UnitOfDataRate.KIBIBYTES_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -123,7 +115,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( translation_key="upload_speed", value_key=KIBIBYTES_PER_SEC_SENT, unique_id="KiB/sec_sent", - icon="mdi:server-network", device_class=SensorDeviceClass.DATA_RATE, native_unit_of_measurement=UnitOfDataRate.KIBIBYTES_PER_SECOND, state_class=SensorStateClass.MEASUREMENT, @@ -134,7 +125,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( translation_key="packet_download_speed", value_key=PACKETS_PER_SEC_RECEIVED, unique_id="packets/sec_received", - icon="mdi:server-network", native_unit_of_measurement=DATA_RATE_PACKETS_PER_SECOND, entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -145,7 +135,6 @@ SENSOR_DESCRIPTIONS: tuple[UpnpSensorEntityDescription, ...] = ( translation_key="packet_upload_speed", value_key=PACKETS_PER_SEC_SENT, unique_id="packets/sec_sent", - icon="mdi:server-network", native_unit_of_measurement=DATA_RATE_PACKETS_PER_SECOND, entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, From 70c0f3e2072d6da097f5de6f77edf766f05a3813 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:31:46 +0100 Subject: [PATCH 0431/1691] Add icon translations to Totalconnect (#112316) --- homeassistant/components/totalconnect/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/totalconnect/icons.json diff --git a/homeassistant/components/totalconnect/icons.json b/homeassistant/components/totalconnect/icons.json new file mode 100644 index 00000000000..356ce0a929b --- /dev/null +++ b/homeassistant/components/totalconnect/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "arm_away_instant": "mdi:shield-lock", + "arm_home_instant": "mdi:shield-home" + } +} From fdb85f9e9e01624535db3f74a23fb9083c3ca886 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:31:58 +0100 Subject: [PATCH 0432/1691] Add icon translations to TechnoVE (#112306) * Add icon translations to TechnoVE * Add icon translations to TechnoVE --- homeassistant/components/technove/icons.json | 9 +++++++++ homeassistant/components/technove/sensor.py | 1 - tests/components/technove/snapshots/test_sensor.ambr | 3 +-- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/technove/icons.json diff --git a/homeassistant/components/technove/icons.json b/homeassistant/components/technove/icons.json new file mode 100644 index 00000000000..ff47d3c32bc --- /dev/null +++ b/homeassistant/components/technove/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "ssid": { + "default": "mdi:wifi" + } + } + } +} diff --git a/homeassistant/components/technove/sensor.py b/homeassistant/components/technove/sensor.py index e4d3822ee1b..576ba88cec6 100644 --- a/homeassistant/components/technove/sensor.py +++ b/homeassistant/components/technove/sensor.py @@ -104,7 +104,6 @@ SENSORS: tuple[TechnoVESensorEntityDescription, ...] = ( TechnoVESensorEntityDescription( key="ssid", translation_key="ssid", - icon="mdi:wifi", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, value_fn=lambda station: station.info.network_ssid, diff --git a/tests/components/technove/snapshots/test_sensor.ambr b/tests/components/technove/snapshots/test_sensor.ambr index 941d93107df..ca676ffdca0 100644 --- a/tests/components/technove/snapshots/test_sensor.ambr +++ b/tests/components/technove/snapshots/test_sensor.ambr @@ -433,7 +433,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:wifi', + 'original_icon': None, 'original_name': 'Wi-Fi network name', 'platform': 'technove', 'previous_unique_id': None, @@ -447,7 +447,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'TechnoVE Station Wi-Fi network name', - 'icon': 'mdi:wifi', }), 'context': , 'entity_id': 'sensor.technove_station_wi_fi_network_name', From 2ed9c26c22bbd53f9d43481a23fec44e233f008f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:32:09 +0100 Subject: [PATCH 0433/1691] Add icon translations to Tami4 (#112304) --- homeassistant/components/tami4/button.py | 1 - homeassistant/components/tami4/icons.json | 32 +++++++++++++++++++++++ homeassistant/components/tami4/sensor.py | 7 ----- 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/tami4/icons.json diff --git a/homeassistant/components/tami4/button.py b/homeassistant/components/tami4/button.py index c17a296e219..8b0dd6a5e11 100644 --- a/homeassistant/components/tami4/button.py +++ b/homeassistant/components/tami4/button.py @@ -27,7 +27,6 @@ BUTTONS: tuple[Tami4EdgeButtonEntityDescription] = ( Tami4EdgeButtonEntityDescription( key="boil_water", translation_key="boil_water", - icon="mdi:kettle-steam", press_fn=lambda api: api.boil_water(), ), ) diff --git a/homeassistant/components/tami4/icons.json b/homeassistant/components/tami4/icons.json new file mode 100644 index 00000000000..d623bdc6007 --- /dev/null +++ b/homeassistant/components/tami4/icons.json @@ -0,0 +1,32 @@ +{ + "entity": { + "button": { + "boil_water": { + "default": "mdi:kettle-steam" + } + }, + "sensor": { + "uv_last_replacement": { + "default": "mdi:calendar" + }, + "uv_upcoming_replacement": { + "default": "mdi:calendar" + }, + "uv_status": { + "default": "mdi:clipboard-check-multiple" + }, + "filter_last_replacement": { + "default": "mdi:calendar" + }, + "filter_upcoming_replacement": { + "default": "mdi:calendar" + }, + "filter_status": { + "default": "mdi:clipboard-check-multiple" + }, + "filter_litters_passed": { + "default": "mdi:water" + } + } + } +} diff --git a/homeassistant/components/tami4/sensor.py b/homeassistant/components/tami4/sensor.py index df271da7309..177e4cc784d 100644 --- a/homeassistant/components/tami4/sensor.py +++ b/homeassistant/components/tami4/sensor.py @@ -25,41 +25,34 @@ ENTITY_DESCRIPTIONS = [ SensorEntityDescription( key="uv_last_replacement", translation_key="uv_last_replacement", - icon="mdi:calendar", device_class=SensorDeviceClass.DATE, ), SensorEntityDescription( key="uv_upcoming_replacement", translation_key="uv_upcoming_replacement", - icon="mdi:calendar", device_class=SensorDeviceClass.DATE, ), SensorEntityDescription( key="uv_status", translation_key="uv_status", - icon="mdi:clipboard-check-multiple", ), SensorEntityDescription( key="filter_last_replacement", translation_key="filter_last_replacement", - icon="mdi:calendar", device_class=SensorDeviceClass.DATE, ), SensorEntityDescription( key="filter_upcoming_replacement", translation_key="filter_upcoming_replacement", - icon="mdi:calendar", device_class=SensorDeviceClass.DATE, ), SensorEntityDescription( key="filter_status", translation_key="filter_status", - icon="mdi:clipboard-check-multiple", ), SensorEntityDescription( key="filter_litters_passed", translation_key="filter_litters_passed", - icon="mdi:water", state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.WATER, native_unit_of_measurement=UnitOfVolume.LITERS, From addd957091eef34bbb9d3924d3465cdea3e75305 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:32:19 +0100 Subject: [PATCH 0434/1691] Use default icon for Lupusec (#111901) --- homeassistant/components/lupusec/alarm_control_panel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py index cd4e433bd5d..a13e3684294 100644 --- a/homeassistant/components/lupusec/alarm_control_panel.py +++ b/homeassistant/components/lupusec/alarm_control_panel.py @@ -43,7 +43,6 @@ class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity): """An alarm_control_panel implementation for Lupusec.""" _attr_name = None - _attr_icon = "mdi:security" _attr_supported_features = ( AlarmControlPanelEntityFeature.ARM_HOME | AlarmControlPanelEntityFeature.ARM_AWAY From 8d6205cedd82f9a4a6fabf1487120ed630057dd9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:32:31 +0100 Subject: [PATCH 0435/1691] Add icon translations to iBeacon (#111811) * Add icon translations to iBeacon * Add icon translations to iBeacon --- .../components/ibeacon/device_tracker.py | 6 +----- homeassistant/components/ibeacon/icons.json | 17 +++++++++++++++++ homeassistant/components/ibeacon/sensor.py | 1 - 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/ibeacon/icons.json diff --git a/homeassistant/components/ibeacon/device_tracker.py b/homeassistant/components/ibeacon/device_tracker.py index 8e194ac27b1..80d5733eccf 100644 --- a/homeassistant/components/ibeacon/device_tracker.py +++ b/homeassistant/components/ibeacon/device_tracker.py @@ -49,6 +49,7 @@ class IBeaconTrackerEntity(IBeaconEntity, BaseTrackerEntity): """An iBeacon Tracker entity.""" _attr_name = None + _attr_translation_key = "device_tracker" def __init__( self, @@ -74,11 +75,6 @@ class IBeaconTrackerEntity(IBeaconEntity, BaseTrackerEntity): """Return tracker source type.""" return SourceType.BLUETOOTH_LE - @property - def icon(self) -> str: - """Return device icon.""" - return "mdi:bluetooth-connect" if self._active else "mdi:bluetooth-off" - @callback def _async_seen( self, diff --git a/homeassistant/components/ibeacon/icons.json b/homeassistant/components/ibeacon/icons.json new file mode 100644 index 00000000000..5f9b89e6568 --- /dev/null +++ b/homeassistant/components/ibeacon/icons.json @@ -0,0 +1,17 @@ +{ + "entity": { + "device_tracker": { + "device_tracker": { + "default": "mdi:bluetooth-off", + "state": { + "home": "mdi:bluetooth-connect" + } + } + }, + "sensor": { + "estimated_distance": { + "default": "mdi:signal-distance-variant" + } + } + } +} diff --git a/homeassistant/components/ibeacon/sensor.py b/homeassistant/components/ibeacon/sensor.py index 3ce145fc3b9..500b0de93f6 100644 --- a/homeassistant/components/ibeacon/sensor.py +++ b/homeassistant/components/ibeacon/sensor.py @@ -56,7 +56,6 @@ SENSOR_DESCRIPTIONS = ( IBeaconSensorEntityDescription( key="estimated_distance", translation_key="estimated_distance", - icon="mdi:signal-distance-variant", native_unit_of_measurement=UnitOfLength.METERS, value_fn=lambda ibeacon_advertisement: ibeacon_advertisement.distance, state_class=SensorStateClass.MEASUREMENT, From eef661c9175235aa20d87d8c0c37190a9b93fde7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 06:32:47 +0100 Subject: [PATCH 0436/1691] Add icon translations to V2C (#112338) --- homeassistant/components/v2c/icons.json | 38 +++++++++++++++++++++++++ homeassistant/components/v2c/sensor.py | 4 --- homeassistant/components/v2c/switch.py | 4 --- 3 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/v2c/icons.json diff --git a/homeassistant/components/v2c/icons.json b/homeassistant/components/v2c/icons.json new file mode 100644 index 00000000000..0c0609de347 --- /dev/null +++ b/homeassistant/components/v2c/icons.json @@ -0,0 +1,38 @@ +{ + "entity": { + "sensor": { + "charge_power": { + "default": "mdi:ev-station" + }, + "charge_energy": { + "default": "mdi:ev-station" + }, + "charge_time": { + "default": "mdi:timer" + }, + "house_power": { + "default": "mdi:home-lightning-bolt" + }, + "fv_power": { + "default": "mdi:solar-power-variant" + } + }, + "switch": { + "paused": { + "default": "mdi:pause" + }, + "locked": { + "default": "mdi:lock" + }, + "timer": { + "default": "mdi:timer" + }, + "dynamic": { + "default": "mdi:gauge" + }, + "pause_dynamic": { + "default": "mdi:pause" + } + } + } +} diff --git a/homeassistant/components/v2c/sensor.py b/homeassistant/components/v2c/sensor.py index 0aa727fa408..29697bd7fe6 100644 --- a/homeassistant/components/v2c/sensor.py +++ b/homeassistant/components/v2c/sensor.py @@ -50,7 +50,6 @@ TRYDAN_SENSORS = ( V2CSensorEntityDescription( key="charge_energy", translation_key="charge_energy", - icon="mdi:ev-station", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, device_class=SensorDeviceClass.ENERGY, @@ -59,7 +58,6 @@ TRYDAN_SENSORS = ( V2CSensorEntityDescription( key="charge_time", translation_key="charge_time", - icon="mdi:timer", native_unit_of_measurement=UnitOfTime.SECONDS, state_class=SensorStateClass.TOTAL_INCREASING, device_class=SensorDeviceClass.DURATION, @@ -68,7 +66,6 @@ TRYDAN_SENSORS = ( V2CSensorEntityDescription( key="house_power", translation_key="house_power", - icon="mdi:home-lightning-bolt", native_unit_of_measurement=UnitOfPower.WATT, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.POWER, @@ -77,7 +74,6 @@ TRYDAN_SENSORS = ( V2CSensorEntityDescription( key="fv_power", translation_key="fv_power", - icon="mdi:solar-power-variant", native_unit_of_measurement=UnitOfPower.WATT, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.POWER, diff --git a/homeassistant/components/v2c/switch.py b/homeassistant/components/v2c/switch.py index a8b4728c66d..4e02f810a7a 100644 --- a/homeassistant/components/v2c/switch.py +++ b/homeassistant/components/v2c/switch.py @@ -45,7 +45,6 @@ TRYDAN_SWITCHES = ( V2CSwitchEntityDescription( key="paused", translation_key="paused", - icon="mdi:pause", value_fn=lambda evse_data: evse_data.paused == PauseState.PAUSED, turn_on_fn=lambda evse: evse.pause(), turn_off_fn=lambda evse: evse.resume(), @@ -53,7 +52,6 @@ TRYDAN_SWITCHES = ( V2CSwitchEntityDescription( key="locked", translation_key="locked", - icon="mdi:lock", value_fn=lambda evse_data: evse_data.locked == LockState.ENABLED, turn_on_fn=lambda evse: evse.lock(), turn_off_fn=lambda evse: evse.unlock(), @@ -61,7 +59,6 @@ TRYDAN_SWITCHES = ( V2CSwitchEntityDescription( key="timer", translation_key="timer", - icon="mdi:timer", value_fn=lambda evse_data: evse_data.timer == ChargePointTimerState.TIMER_ON, turn_on_fn=lambda evse: evse.timer(), turn_off_fn=lambda evse: evse.timer_disable(), @@ -69,7 +66,6 @@ TRYDAN_SWITCHES = ( V2CSwitchEntityDescription( key="dynamic", translation_key="dynamic", - icon="mdi:gauge", value_fn=lambda evse_data: evse_data.dynamic == DynamicState.ENABLED, turn_on_fn=lambda evse: evse.dynamic(), turn_off_fn=lambda evse: evse.dynamic_disable(), From 87739bc072c4ac75e8dd3851f32f06e368a798a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 19:34:16 -1000 Subject: [PATCH 0437/1691] Add test to ensure bootstrap continues if an integraton raises CancelledError (#112472) --- tests/test_bootstrap.py | 45 +++++++++++++++++++ .../__init__.py | 8 ++++ .../manifest.json | 10 +++++ .../__init__.py | 13 ++++++ .../config_flow.py | 17 +++++++ .../manifest.json | 10 +++++ 6 files changed, 103 insertions(+) create mode 100644 tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py create mode 100644 tests/testing_config/custom_components/test_package_raises_cancelled_error/manifest.json create mode 100644 tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py create mode 100644 tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py create mode 100644 tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/manifest.json diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index f9c8647bd6e..9599e249f40 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1067,3 +1067,48 @@ async def test_bootstrap_does_not_preload_stage_1_integrations() -> None: # as a side effect of importing the pre-imports for integration in bootstrap.STAGE_1_INTEGRATIONS: assert f"homeassistant.components.{integration}" not in decoded_stdout + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_cancellation_does_not_leak_upward_from_async_setup( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Test setting up an integration that raises asyncio.CancelledError.""" + await bootstrap.async_setup_multi_components( + hass, {"test_package_raises_cancelled_error"}, {} + ) + await hass.async_block_till_done() + + assert ( + "Error during setup of component test_package_raises_cancelled_error" + in caplog.text + ) + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_cancellation_does_not_leak_upward_from_async_setup_entry( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, +) -> None: + """Test setting up an integration that raises asyncio.CancelledError.""" + entry = MockConfigEntry( + domain="test_package_raises_cancelled_error_config_entry", data={} + ) + entry.add_to_hass(hass) + await bootstrap.async_setup_multi_components( + hass, {"test_package_raises_cancelled_error_config_entry"}, {} + ) + await hass.async_block_till_done() + + await bootstrap.async_setup_multi_components(hass, {"test_package"}, {}) + await hass.async_block_till_done() + assert ( + "Error setting up entry Mock Title for test_package_raises_cancelled_error_config_entry" + in caplog.text + ) + + assert "test_package" in hass.config.components + assert "test_package_raises_cancelled_error_config_entry" in hass.config.components diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py b/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py new file mode 100644 index 00000000000..e77df90a00b --- /dev/null +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py @@ -0,0 +1,8 @@ +"""Provide a mock package component.""" +import asyncio + + +async def async_setup(hass, config): + """Mock a successful setup.""" + asyncio.current_task().cancel() + await asyncio.sleep(0) diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error/manifest.json b/tests/testing_config/custom_components/test_package_raises_cancelled_error/manifest.json new file mode 100644 index 00000000000..930c4d2ae9e --- /dev/null +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "test_package_raises_cancelled_error", + "name": "Test Package that raises asyncio.CancelledError", + "documentation": "http://test-package.io", + "requirements": [], + "dependencies": [], + "codeowners": [], + "config_flow": false, + "version": "1.2.3" +} diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py new file mode 100644 index 00000000000..1283e79f21b --- /dev/null +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py @@ -0,0 +1,13 @@ +"""Provide a mock package component.""" +import asyncio + + +async def async_setup(hass, config): + """Mock a successful setup.""" + return True + + +async def async_setup_entry(hass, entry): + """Mock an unsuccessful entry setup.""" + asyncio.current_task().cancel() + await asyncio.sleep(0) diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py new file mode 100644 index 00000000000..7277bac343d --- /dev/null +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py @@ -0,0 +1,17 @@ +"""Config flow.""" + +from homeassistant.config_entries import ConfigFlow +from homeassistant.core import HomeAssistant + + +class MockConfigFlow( + ConfigFlow, domain="test_package_raises_cancelled_error_config_entry" +): + """Mock config flow.""" + + pass + + +async def _async_has_devices(hass: HomeAssistant) -> bool: + """Return if there are devices that can be discovered.""" + return True diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/manifest.json b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/manifest.json new file mode 100644 index 00000000000..2ce303ca687 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "test_package_raises_cancelled_error_config_entry", + "name": "Test Package that raises asyncio.CancelledError in async_setup_entry", + "documentation": "http://test-package.io", + "requirements": [], + "dependencies": [], + "codeowners": [], + "config_flow": true, + "version": "1.2.3" +} From f3a9756f819bcee792166704c28dc6e288ede41c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 21:16:42 -1000 Subject: [PATCH 0438/1691] Avoid waiting for integration platforms in the parent integration (#112467) --- homeassistant/components/backup/manager.py | 2 +- .../components/energy/websocket_api.py | 4 +- homeassistant/components/hardware/hardware.py | 4 +- .../silabs_multiprotocol_addon.py | 5 ++- .../components/repairs/issue_handler.py | 4 +- homeassistant/helpers/integration_platform.py | 31 +++++++++++++ .../accuweather/test_system_health.py | 2 + tests/components/airly/test_system_health.py | 2 + tests/components/alexa/test_init.py | 1 + .../assist_pipeline/test_logbook.py | 1 + tests/components/automation/test_init.py | 1 + tests/components/automation/test_logbook.py | 1 + tests/components/backup/test_manager.py | 2 + tests/components/deconz/test_logbook.py | 2 + tests/components/energy/test_websocket_api.py | 3 ++ tests/components/gios/test_system_health.py | 2 + .../google_assistant/test_logbook.py | 1 + tests/components/group/test_init.py | 1 + tests/components/hassio/test_system_health.py | 2 + tests/components/homekit/test_init.py | 1 + tests/components/ipma/test_system_health.py | 1 + tests/components/isy994/test_system_health.py | 2 + .../components/nextdns/test_system_health.py | 2 + .../components/raspberry_pi/test_hardware.py | 2 + tests/components/script/test_init.py | 1 + tests/components/shelly/test_logbook.py | 2 + tests/components/zha/test_logbook.py | 3 ++ tests/components/zwave_js/test_logbook.py | 2 + tests/helpers/test_integration_platform.py | 44 +++++++++++++++++++ 29 files changed, 126 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/backup/manager.py b/homeassistant/components/backup/manager.py index 4c06f2171b6..ba5d8c8733c 100644 --- a/homeassistant/components/backup/manager.py +++ b/homeassistant/components/backup/manager.py @@ -125,7 +125,7 @@ class BackupManager: async def load_platforms(self) -> None: """Load backup platforms.""" await integration_platform.async_process_integration_platforms( - self.hass, DOMAIN, self._add_platform + self.hass, DOMAIN, self._add_platform, wait_for_platforms=True ) LOGGER.debug("Loaded %s platforms", len(self.platforms)) self.loaded_platforms = True diff --git a/homeassistant/components/energy/websocket_api.py b/homeassistant/components/energy/websocket_api.py index 5d9cd81013d..73aa8330bfe 100644 --- a/homeassistant/components/energy/websocket_api.py +++ b/homeassistant/components/energy/websocket_api.py @@ -71,7 +71,9 @@ async def async_get_energy_platforms( platforms[domain] = cast(EnergyPlatform, platform).async_get_solar_forecast - await async_process_integration_platforms(hass, DOMAIN, _process_energy_platform) + await async_process_integration_platforms( + hass, DOMAIN, _process_energy_platform, wait_for_platforms=True + ) return platforms diff --git a/homeassistant/components/hardware/hardware.py b/homeassistant/components/hardware/hardware.py index d44a232c232..800f15137c6 100644 --- a/homeassistant/components/hardware/hardware.py +++ b/homeassistant/components/hardware/hardware.py @@ -15,7 +15,9 @@ async def async_process_hardware_platforms(hass: HomeAssistant) -> None: """Start processing hardware platforms.""" hass.data[DOMAIN]["hardware_platform"] = {} - await async_process_integration_platforms(hass, DOMAIN, _register_hardware_platform) + await async_process_integration_platforms( + hass, DOMAIN, _register_hardware_platform, wait_for_platforms=True + ) @callback diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index 2a2d617c5b4..5a1cf1d5421 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -143,7 +143,10 @@ class MultiprotocolAddonManager(WaitingAddonManager): async def async_setup(self) -> None: """Set up the manager.""" await async_process_integration_platforms( - self._hass, "silabs_multiprotocol", self._register_multipan_platform + self._hass, + "silabs_multiprotocol", + self._register_multipan_platform, + wait_for_platforms=True, ) await self.async_load() diff --git a/homeassistant/components/repairs/issue_handler.py b/homeassistant/components/repairs/issue_handler.py index f2ce3bac84e..be7a6310464 100644 --- a/homeassistant/components/repairs/issue_handler.py +++ b/homeassistant/components/repairs/issue_handler.py @@ -102,7 +102,9 @@ async def async_process_repairs_platforms(hass: HomeAssistant) -> None: """Start processing repairs platforms.""" hass.data[DOMAIN]["platforms"] = {} - await async_process_integration_platforms(hass, DOMAIN, _register_repairs_platform) + await async_process_integration_platforms( + hass, DOMAIN, _register_repairs_platform, wait_for_platforms=True + ) @callback diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index ef9c97504ae..7debae6072f 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -157,6 +157,7 @@ async def async_process_integration_platforms( platform_name: str, # Any = platform. process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None] | None], + wait_for_platforms: bool = False, ) -> None: """Process a specific platform for all current and future loaded integrations.""" if DATA_INTEGRATION_PLATFORMS not in hass.data: @@ -194,6 +195,36 @@ async def async_process_integration_platforms( if not top_level_components: return + # We create a task here for two reasons: + # + # 1. We want the integration that provides the integration platform to + # not be delayed by waiting on each individual platform to be processed + # since the import or the integration platforms themselves may have to + # schedule I/O or executor jobs. + # + # 2. We want the behavior to be the same as if the integration that has + # the integration platform is loaded after the platform is processed. + # + # We use hass.async_create_task instead of asyncio.create_task because + # we want to make sure that startup waits for the task to complete. + # + future = hass.async_create_task( + _async_process_integration_platforms( + hass, platform_name, top_level_components.copy(), process_job + ), + eager_start=True, + ) + if wait_for_platforms: + await future + + +async def _async_process_integration_platforms( + hass: HomeAssistant, + platform_name: str, + top_level_components: set[str], + process_job: HassJob, +) -> None: + """Process integration platforms for a component.""" integrations = await async_get_integrations(hass, top_level_components) loaded_integrations: list[Integration] = [ integration diff --git a/tests/components/accuweather/test_system_health.py b/tests/components/accuweather/test_system_health.py index b7acc318883..34f5b761ccb 100644 --- a/tests/components/accuweather/test_system_health.py +++ b/tests/components/accuweather/test_system_health.py @@ -19,6 +19,7 @@ async def test_accuweather_system_health( aioclient_mock.get("https://dataservice.accuweather.com/", text="") hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = {} @@ -43,6 +44,7 @@ async def test_accuweather_system_health_fail( aioclient_mock.get("https://dataservice.accuweather.com/", exc=ClientError) hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = {} diff --git a/tests/components/airly/test_system_health.py b/tests/components/airly/test_system_health.py index 38f4378a2e3..fa26f6ccd0c 100644 --- a/tests/components/airly/test_system_health.py +++ b/tests/components/airly/test_system_health.py @@ -19,6 +19,7 @@ async def test_airly_system_health( aioclient_mock.get("https://airapi.airly.eu/v2/", text="") hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = Mock( @@ -47,6 +48,7 @@ async def test_airly_system_health_fail( aioclient_mock.get("https://airapi.airly.eu/v2/", exc=ClientError) hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() hass.data[DOMAIN] = {} hass.data[DOMAIN]["0123xyz"] = Mock( diff --git a/tests/components/alexa/test_init.py b/tests/components/alexa/test_init.py index 279fbfc4cef..3ddf33776bb 100644 --- a/tests/components/alexa/test_init.py +++ b/tests/components/alexa/test_init.py @@ -11,6 +11,7 @@ async def test_humanify_alexa_event(hass: HomeAssistant) -> None: hass.config.components.add("recorder") await async_setup_component(hass, "alexa", {}) await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() hass.states.async_set("light.kitchen", "on", {"friendly_name": "Kitchen Light"}) results = mock_humanify( diff --git a/tests/components/assist_pipeline/test_logbook.py b/tests/components/assist_pipeline/test_logbook.py index c1e0633ed57..aaa36b730fe 100644 --- a/tests/components/assist_pipeline/test_logbook.py +++ b/tests/components/assist_pipeline/test_logbook.py @@ -15,6 +15,7 @@ async def test_recording_event( """Test recording event.""" hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() entry = MockConfigEntry() entry.add_to_hass(hass) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 462f748c15e..e613a997939 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1862,6 +1862,7 @@ async def test_logbook_humanify_automation_triggered_event(hass: HomeAssistant) hass.config.components.add("recorder") await async_setup_component(hass, automation.DOMAIN, {}) await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() event1, event2 = mock_humanify( hass, diff --git a/tests/components/automation/test_logbook.py b/tests/components/automation/test_logbook.py index 802977a63be..7063f2bfb3e 100644 --- a/tests/components/automation/test_logbook.py +++ b/tests/components/automation/test_logbook.py @@ -11,6 +11,7 @@ async def test_humanify_automation_trigger_event(hass: HomeAssistant) -> None: hass.config.components.add("recorder") assert await async_setup_component(hass, "automation", {}) assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() context = Context() event1, event2 = mock_humanify( diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index f7ecab0efa1..129607e7304 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -199,6 +199,7 @@ async def test_loading_platforms( ), ) await manager.load_platforms() + await hass.async_block_till_done() assert manager.loaded_platforms assert len(manager.platforms) == 1 @@ -218,6 +219,7 @@ async def test_not_loading_bad_platforms( await _setup_mock_domain(hass) await manager.load_platforms() + await hass.async_block_till_done() assert manager.loaded_platforms assert len(manager.platforms) == 0 diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 4d2043923bd..83f2e463992 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -74,6 +74,7 @@ async def test_humanifying_deconz_alarm_event( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, @@ -183,6 +184,7 @@ async def test_humanifying_deconz_event( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, diff --git a/tests/components/energy/test_websocket_api.py b/tests/components/energy/test_websocket_api.py index f953d0e3a03..f136c37b544 100644 --- a/tests/components/energy/test_websocket_api.py +++ b/tests/components/energy/test_websocket_api.py @@ -89,6 +89,7 @@ async def test_save_preferences( mock_energy_platform, ) -> None: """Test we can save preferences.""" + await hass.async_block_till_done() client = await hass_ws_client(hass) # Test saving default prefs is also valid. @@ -283,6 +284,7 @@ async def test_get_solar_forecast( entry.add_to_hass(hass) manager = await data.async_get_manager(hass) + manager.data = data.EnergyManager.default_preferences() manager.data["energy_sources"].append( { @@ -292,6 +294,7 @@ async def test_get_solar_forecast( } ) client = await hass_ws_client(hass) + await hass.async_block_till_done() await client.send_json({"id": 5, "type": "energy/solar_forecast"}) diff --git a/tests/components/gios/test_system_health.py b/tests/components/gios/test_system_health.py index 00e2fe50dd4..571614ed1de 100644 --- a/tests/components/gios/test_system_health.py +++ b/tests/components/gios/test_system_health.py @@ -21,6 +21,7 @@ async def test_gios_system_health( await integration.async_get_component() hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, DOMAIN) @@ -40,6 +41,7 @@ async def test_gios_system_health_fail( await integration.async_get_component() hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, DOMAIN) diff --git a/tests/components/google_assistant/test_logbook.py b/tests/components/google_assistant/test_logbook.py index 58b747a6f07..e8132d822e8 100644 --- a/tests/components/google_assistant/test_logbook.py +++ b/tests/components/google_assistant/test_logbook.py @@ -18,6 +18,7 @@ async def test_humanify_command_received(hass: HomeAssistant) -> None: hass.config.components.add("frontend") hass.config.components.add("google_assistant") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() hass.states.async_set( "light.kitchen", "on", {ATTR_FRIENDLY_NAME: "The Kitchen Lights"} diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index a9a2145798b..6dd1ca1a6ed 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -759,6 +759,7 @@ async def test_service_group_services_add_remove_entities(hass: HomeAssistant) - assert await async_setup_component(hass, "person", {}) with assert_setup_component(0, "group"): await async_setup_component(hass, "group", {"group": {}}) + await hass.async_block_till_done() assert hass.services.has_service("group", group.SERVICE_SET) diff --git a/tests/components/hassio/test_system_health.py b/tests/components/hassio/test_system_health.py index 715bf3bab91..bce3e5991fb 100644 --- a/tests/components/hassio/test_system_health.py +++ b/tests/components/hassio/test_system_health.py @@ -29,6 +29,7 @@ async def test_hassio_system_health( hass.config.components.add("hassio") assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() hass.data["hassio_info"] = { "channel": "stable", @@ -88,6 +89,7 @@ async def test_hassio_system_health_with_issues( hass.config.components.add("hassio") assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() hass.data["hassio_info"] = {"channel": "stable"} hass.data["hassio_host_info"] = {} diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 5a1d42352fe..1f0d9846fd1 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -33,6 +33,7 @@ async def test_humanify_homekit_changed_event( with patch("homeassistant.components.homekit.HomeKit"): assert await async_setup_component(hass, "homekit", {"homekit": {}}) assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() event1, event2 = mock_humanify( hass, diff --git a/tests/components/ipma/test_system_health.py b/tests/components/ipma/test_system_health.py index 665ed193da1..b4cefad80c0 100644 --- a/tests/components/ipma/test_system_health.py +++ b/tests/components/ipma/test_system_health.py @@ -17,6 +17,7 @@ async def test_ipma_system_health( hass.config.components.add("ipma") assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, "ipma") diff --git a/tests/components/isy994/test_system_health.py b/tests/components/isy994/test_system_health.py index fdc0c634f8a..fa5bab0025c 100644 --- a/tests/components/isy994/test_system_health.py +++ b/tests/components/isy994/test_system_health.py @@ -27,6 +27,7 @@ async def test_system_health( hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() MockConfigEntry( domain=DOMAIN, @@ -66,6 +67,7 @@ async def test_system_health_failed_connect( hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/nextdns/test_system_health.py b/tests/components/nextdns/test_system_health.py index 14d447947c1..4d4299621e8 100644 --- a/tests/components/nextdns/test_system_health.py +++ b/tests/components/nextdns/test_system_health.py @@ -19,6 +19,7 @@ async def test_nextdns_system_health( aioclient_mock.get(API_ENDPOINT, text="") hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, DOMAIN) @@ -36,6 +37,7 @@ async def test_nextdns_system_health_fail( aioclient_mock.get(API_ENDPOINT, exc=ClientError) hass.config.components.add(DOMAIN) assert await async_setup_component(hass, "system_health", {}) + await hass.async_block_till_done() info = await get_system_health_info(hass, DOMAIN) diff --git a/tests/components/raspberry_pi/test_hardware.py b/tests/components/raspberry_pi/test_hardware.py index d41fbf2f5e1..430611d1576 100644 --- a/tests/components/raspberry_pi/test_hardware.py +++ b/tests/components/raspberry_pi/test_hardware.py @@ -18,6 +18,7 @@ async def test_hardware_info( """Test we can get the board info.""" mock_integration(hass, MockModule("hassio")) await async_setup_component(hass, HASSIO_DOMAIN, {}) + await hass.async_block_till_done() # Setup the config entry config_entry = MockConfigEntry( @@ -70,6 +71,7 @@ async def test_hardware_info_fail( """Test async_info raises if os_info is not as expected.""" mock_integration(hass, MockModule("hassio")) await async_setup_component(hass, HASSIO_DOMAIN, {}) + await hass.async_block_till_done() # Setup the config entry config_entry = MockConfigEntry( diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 2d21dc924dd..5a5c2c4c9ce 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -898,6 +898,7 @@ async def test_logbook_humanify_script_started_event(hass: HomeAssistant) -> Non hass.config.components.add("recorder") await async_setup_component(hass, DOMAIN, {}) await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() event1, event2 = mock_humanify( hass, diff --git a/tests/components/shelly/test_logbook.py b/tests/components/shelly/test_logbook.py index 35c0d5a17de..7cf73fd343f 100644 --- a/tests/components/shelly/test_logbook.py +++ b/tests/components/shelly/test_logbook.py @@ -31,6 +31,7 @@ async def test_humanify_shelly_click_event_block_device( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() event1, event2 = mock_humanify( hass, @@ -81,6 +82,7 @@ async def test_humanify_shelly_click_event_rpc_device( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() event1, event2 = mock_humanify( hass, diff --git a/tests/components/zha/test_logbook.py b/tests/components/zha/test_logbook.py index 44495cf0e15..b0d7d7579f3 100644 --- a/tests/components/zha/test_logbook.py +++ b/tests/components/zha/test_logbook.py @@ -84,6 +84,7 @@ async def test_zha_logbook_event_device_with_triggers( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, @@ -162,6 +163,7 @@ async def test_zha_logbook_event_device_no_triggers( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, @@ -246,6 +248,7 @@ async def test_zha_logbook_event_device_no_device( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, diff --git a/tests/components/zwave_js/test_logbook.py b/tests/components/zwave_js/test_logbook.py index 98062612309..c4e601da2fc 100644 --- a/tests/components/zwave_js/test_logbook.py +++ b/tests/components/zwave_js/test_logbook.py @@ -25,6 +25,7 @@ async def test_humanifying_zwave_js_notification_event( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, @@ -108,6 +109,7 @@ async def test_humanifying_zwave_js_value_notification_event( hass.config.components.add("recorder") assert await async_setup_component(hass, "logbook", {}) + await hass.async_block_till_done() events = mock_humanify( hass, diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index 9b1919b35a0..70874a7a58c 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -16,6 +16,44 @@ from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED from tests.common import mock_platform +async def test_process_integration_platforms_with_wait(hass: HomeAssistant) -> None: + """Test processing integrations.""" + loaded_platform = Mock() + mock_platform(hass, "loaded.platform_to_check", loaded_platform) + hass.config.components.add("loaded") + + event_platform = Mock() + mock_platform(hass, "event.platform_to_check", event_platform) + + processed = [] + + async def _process_platform(hass, domain, platform): + """Process platform.""" + processed.append((domain, platform)) + + await async_process_integration_platforms( + hass, "platform_to_check", _process_platform, wait_for_platforms=True + ) + # No block till done here, we want to make sure it waits for the platform + + assert len(processed) == 1 + assert processed[0][0] == "loaded" + assert processed[0][1] == loaded_platform + + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) + await hass.async_block_till_done() + + assert len(processed) == 2 + assert processed[1][0] == "event" + assert processed[1][1] == event_platform + + hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) + await hass.async_block_till_done() + + # Firing again should not check again + assert len(processed) == 2 + + async def test_process_integration_platforms(hass: HomeAssistant) -> None: """Test processing integrations.""" loaded_platform = Mock() @@ -34,6 +72,7 @@ async def test_process_integration_platforms(hass: HomeAssistant) -> None: await async_process_integration_platforms( hass, "platform_to_check", _process_platform ) + await hass.async_block_till_done() assert len(processed) == 1 assert processed[0][0] == "loaded" @@ -77,6 +116,7 @@ async def test_process_integration_platforms_import_fails( await async_process_integration_platforms( hass, "platform_to_check", _process_platform ) + await hass.async_block_till_done() assert len(processed) == 0 assert "Unexpected error importing platform_to_check for loaded" in caplog.text @@ -115,6 +155,7 @@ async def test_process_integration_platforms_import_fails_after_registered( await async_process_integration_platforms( hass, "platform_to_check", _process_platform ) + await hass.async_block_till_done() assert len(processed) == 1 assert processed[0][0] == "loaded" @@ -166,6 +207,7 @@ async def test_process_integration_platforms_non_compliant( await async_process_integration_platforms( hass, "platform_to_check", process_platform ) + await hass.async_block_till_done() assert len(processed) == 0 assert "Exception in " in caplog.text @@ -204,6 +246,7 @@ async def test_broken_integration( await async_process_integration_platforms( hass, "platform_to_check", _process_platform ) + await hass.async_block_till_done() # This should never actually happen as the component cannot be # in hass.config.components without a loaded manifest @@ -226,5 +269,6 @@ async def test_process_integration_platforms_no_integrations( await async_process_integration_platforms( hass, "platform_to_check", _process_platform ) + await hass.async_block_till_done() assert len(processed) == 0 From 403b47f7122072b4a95afbd5ea324d3fb545c475 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 6 Mar 2024 08:55:49 +0100 Subject: [PATCH 0439/1691] Add Reolink play quick reply message (#112134) --- homeassistant/components/reolink/select.py | 19 +++++++++++++++++-- homeassistant/components/reolink/strings.json | 3 +++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/reolink/select.py b/homeassistant/components/reolink/select.py index 769ccdf7e01..5be0bdd6c26 100644 --- a/homeassistant/components/reolink/select.py +++ b/homeassistant/components/reolink/select.py @@ -41,6 +41,11 @@ class ReolinkSelectEntityDescription( value: Callable[[Host, int], str] | None = None +def _get_quick_reply_id(api: Host, ch: int, mess: str) -> int: + """Get the quick reply file id from the message string.""" + return [k for k, v in api.quick_reply_dict(ch).items() if v == mess][0] + + SELECT_ENTITIES = ( ReolinkSelectEntityDescription( key="floodlight_mode", @@ -72,6 +77,16 @@ SELECT_ENTITIES = ( supported=lambda api, ch: api.supported(ch, "ptz_presets"), method=lambda api, ch, name: api.set_ptz_command(ch, preset=name), ), + ReolinkSelectEntityDescription( + key="play_quick_reply_message", + translation_key="play_quick_reply_message", + icon="mdi:message-reply-text-outline", + get_options=lambda api, ch: list(api.quick_reply_dict(ch).values())[1:], + supported=lambda api, ch: api.supported(ch, "play_quick_reply"), + method=lambda api, ch, mess: ( + api.play_quick_reply(ch, file_id=_get_quick_reply_id(api, ch, mess)) + ), + ), ReolinkSelectEntityDescription( key="auto_quick_reply_message", cmd_key="GetAutoReply", @@ -81,8 +96,8 @@ SELECT_ENTITIES = ( get_options=lambda api, ch: list(api.quick_reply_dict(ch).values()), supported=lambda api, ch: api.supported(ch, "quick_reply"), value=lambda api, ch: api.quick_reply_dict(ch)[api.quick_reply_file(ch)], - method=lambda api, ch, mess: api.set_quick_reply( - ch, file_id=[k for k, v in api.quick_reply_dict(ch).items() if v == mess][0] + method=lambda api, ch, mess: ( + api.set_quick_reply(ch, file_id=_get_quick_reply_id(api, ch, mess)) ), ), ReolinkSelectEntityDescription( diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index b8525549b1d..d8505e7edb9 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -363,6 +363,9 @@ "ptz_preset": { "name": "PTZ preset" }, + "play_quick_reply_message": { + "name": "Play quick reply message" + }, "auto_quick_reply_message": { "name": "Auto quick reply message", "state": { From 64dcc4606fcaeb93e49737ecb87daf931c1fd59f Mon Sep 17 00:00:00 2001 From: steffenrapp <88974099+steffenrapp@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:01:14 +0100 Subject: [PATCH 0440/1691] Deprecate attributes of Nuki entities (#111419) * Remove attributes from Nuki entities * Comment about deprecation * Update homeassistant/components/nuki/binary_sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/nuki/binary_sensor.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/nuki/lock.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/nuki/sensor.py Co-authored-by: Martin Hjelmare --------- Co-authored-by: Martin Hjelmare --- homeassistant/components/nuki/binary_sensor.py | 2 ++ homeassistant/components/nuki/lock.py | 1 + homeassistant/components/nuki/sensor.py | 1 + 3 files changed, 4 insertions(+) diff --git a/homeassistant/components/nuki/binary_sensor.py b/homeassistant/components/nuki/binary_sensor.py index c01c1c50237..ac2ce24f2b4 100644 --- a/homeassistant/components/nuki/binary_sensor.py +++ b/homeassistant/components/nuki/binary_sensor.py @@ -50,6 +50,7 @@ class NukiDoorsensorEntity(NukiEntity[NukiDevice], BinarySensorEntity): """Return a unique ID.""" return f"{self._nuki_device.nuki_id}_doorsensor" + # Deprecated, can be removed in 2024.10 @property def extra_state_attributes(self): """Return the device specific state attributes.""" @@ -90,6 +91,7 @@ class NukiRingactionEntity(NukiEntity[NukiDevice], BinarySensorEntity): """Return a unique ID.""" return f"{self._nuki_device.nuki_id}_ringaction" + # Deprecated, can be removed in 2024.10 @property def extra_state_attributes(self): """Return the device specific state attributes.""" diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index f1e553e6668..974ffbd4d85 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -76,6 +76,7 @@ class NukiDeviceEntity(NukiEntity[_NukiDeviceT], LockEntity): """Return a unique ID.""" return self._nuki_device.nuki_id + # Deprecated, can be removed in 2024.10 @property def extra_state_attributes(self) -> dict[str, Any]: """Return the device specific state attributes.""" diff --git a/homeassistant/components/nuki/sensor.py b/homeassistant/components/nuki/sensor.py index 3c6775cd171..69d166e4ee5 100644 --- a/homeassistant/components/nuki/sensor.py +++ b/homeassistant/components/nuki/sensor.py @@ -37,6 +37,7 @@ class NukiBatterySensor(NukiEntity[NukiDevice], SensorEntity): """Return a unique ID.""" return f"{self._nuki_device.nuki_id}_battery_level" + # Deprecated, can be removed in 2024.10 @property def extra_state_attributes(self): """Return the device specific state attributes.""" From 995d93dd33a944d421e84e438ef373a5b6c68851 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 6 Mar 2024 09:07:09 +0100 Subject: [PATCH 0441/1691] Remove deprecated `hass.components` usage in config entry flow (#111880) * Remove deprecated `hass.components` usage in config entry flow * Do local import * Also use local import for webhook --- homeassistant/helpers/config_entry_flow.py | 36 ++++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index b645fdb06bd..23638dc3549 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -220,21 +220,33 @@ class WebhookFlowHandler(config_entries.ConfigFlow): if user_input is None: return self.async_show_form(step_id="user") - webhook_id = self.hass.components.webhook.async_generate_id() + # Local import to be sure cloud is loaded and setup + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.cloud import ( + async_active_subscription, + async_create_cloudhook, + async_is_connected, + ) - if ( - "cloud" in self.hass.config.components - and self.hass.components.cloud.async_active_subscription() + # Local import to be sure webhook is loaded and setup + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.webhook import ( + async_generate_id, + async_generate_url, + ) + + webhook_id = async_generate_id() + + if "cloud" in self.hass.config.components and async_active_subscription( + self.hass ): - if not self.hass.components.cloud.async_is_connected(): + if not async_is_connected(self.hass): return self.async_abort(reason="cloud_not_connected") - webhook_url = await self.hass.components.cloud.async_create_cloudhook( - webhook_id - ) + webhook_url = await async_create_cloudhook(self.hass, webhook_id) cloudhook = True else: - webhook_url = self.hass.components.webhook.async_generate_url(webhook_id) + webhook_url = async_generate_url(self.hass, webhook_id) cloudhook = False self._description_placeholder["webhook_url"] = webhook_url @@ -267,4 +279,8 @@ async def webhook_async_remove_entry( if not entry.data.get("cloudhook") or "cloud" not in hass.config.components: return - await hass.components.cloud.async_delete_cloudhook(entry.data["webhook_id"]) + # Local import to be sure cloud is loaded and setup + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.cloud import async_delete_cloudhook + + await async_delete_cloudhook(hass, entry.data["webhook_id"]) From f801ec45ce4041c568b84696acbb087b1fc86a53 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Wed, 6 Mar 2024 18:09:15 +1000 Subject: [PATCH 0442/1691] Bump tesla-fleet-api to 0.4.9 (#112288) * Bump to 0.4.9 * Use SubscriptionRequired instead of PaymentRequired --- homeassistant/components/teslemetry/__init__.py | 8 ++++++-- homeassistant/components/teslemetry/config_flow.py | 8 ++++++-- homeassistant/components/teslemetry/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/teslemetry/test_config_flow.py | 8 ++++++-- tests/components/teslemetry/test_init.py | 4 ++-- 7 files changed, 23 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/teslemetry/__init__.py b/homeassistant/components/teslemetry/__init__.py index f2532fcaf5b..b1083473858 100644 --- a/homeassistant/components/teslemetry/__init__.py +++ b/homeassistant/components/teslemetry/__init__.py @@ -3,7 +3,11 @@ import asyncio from typing import Final from tesla_fleet_api import EnergySpecific, Teslemetry, VehicleSpecific -from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError +from tesla_fleet_api.exceptions import ( + InvalidToken, + SubscriptionRequired, + TeslaFleetError, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ACCESS_TOKEN, Platform @@ -36,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except InvalidToken: LOGGER.error("Access token is invalid, unable to connect to Teslemetry") return False - except PaymentRequired: + except SubscriptionRequired: LOGGER.error("Subscription required, unable to connect to Telemetry") return False except TeslaFleetError as e: diff --git a/homeassistant/components/teslemetry/config_flow.py b/homeassistant/components/teslemetry/config_flow.py index 6c709a9d757..72ae712f994 100644 --- a/homeassistant/components/teslemetry/config_flow.py +++ b/homeassistant/components/teslemetry/config_flow.py @@ -6,7 +6,11 @@ from typing import Any from aiohttp import ClientConnectionError from tesla_fleet_api import Teslemetry -from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError +from tesla_fleet_api.exceptions import ( + InvalidToken, + SubscriptionRequired, + TeslaFleetError, +) import voluptuous as vol from homeassistant import config_entries @@ -41,7 +45,7 @@ class TeslemetryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await teslemetry.test() except InvalidToken: errors[CONF_ACCESS_TOKEN] = "invalid_access_token" - except PaymentRequired: + except SubscriptionRequired: errors["base"] = "subscription_required" except ClientConnectionError: errors["base"] = "cannot_connect" diff --git a/homeassistant/components/teslemetry/manifest.json b/homeassistant/components/teslemetry/manifest.json index ab2d52f329d..7f3f1704f2d 100644 --- a/homeassistant/components/teslemetry/manifest.json +++ b/homeassistant/components/teslemetry/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/teslemetry", "iot_class": "cloud_polling", "loggers": ["tesla-fleet-api"], - "requirements": ["tesla-fleet-api==0.4.6"] + "requirements": ["tesla-fleet-api==0.4.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index f3a46575980..0ef673aa9bd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2677,7 +2677,7 @@ temperusb==1.6.1 # tensorflow==2.5.0 # homeassistant.components.teslemetry -tesla-fleet-api==0.4.6 +tesla-fleet-api==0.4.9 # homeassistant.components.powerwall tesla-powerwall==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1fe5b97e36a..a25035620b7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2051,7 +2051,7 @@ temescal==0.5 temperusb==1.6.1 # homeassistant.components.teslemetry -tesla-fleet-api==0.4.6 +tesla-fleet-api==0.4.9 # homeassistant.components.powerwall tesla-powerwall==0.5.1 diff --git a/tests/components/teslemetry/test_config_flow.py b/tests/components/teslemetry/test_config_flow.py index b89967bfa35..3757c331996 100644 --- a/tests/components/teslemetry/test_config_flow.py +++ b/tests/components/teslemetry/test_config_flow.py @@ -4,7 +4,11 @@ from unittest.mock import patch from aiohttp import ClientConnectionError import pytest -from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError +from tesla_fleet_api.exceptions import ( + InvalidToken, + SubscriptionRequired, + TeslaFleetError, +) from homeassistant import config_entries from homeassistant.components.teslemetry.const import DOMAIN @@ -54,7 +58,7 @@ async def test_form( ("side_effect", "error"), [ (InvalidToken, {CONF_ACCESS_TOKEN: "invalid_access_token"}), - (PaymentRequired, {"base": "subscription_required"}), + (SubscriptionRequired, {"base": "subscription_required"}), (ClientConnectionError, {"base": "cannot_connect"}), (TeslaFleetError, {"base": "unknown"}), ], diff --git a/tests/components/teslemetry/test_init.py b/tests/components/teslemetry/test_init.py index ba3ecd2f0e7..9742338f27a 100644 --- a/tests/components/teslemetry/test_init.py +++ b/tests/components/teslemetry/test_init.py @@ -5,7 +5,7 @@ from datetime import timedelta from freezegun.api import FrozenDateTimeFactory from tesla_fleet_api.exceptions import ( InvalidToken, - PaymentRequired, + SubscriptionRequired, TeslaFleetError, VehicleOffline, ) @@ -42,7 +42,7 @@ async def test_auth_failure(hass: HomeAssistant, mock_products) -> None: async def test_subscription_failure(hass: HomeAssistant, mock_products) -> None: """Test init with an client response error.""" - mock_products.side_effect = PaymentRequired + mock_products.side_effect = SubscriptionRequired entry = await setup_platform(hass) assert entry.state is ConfigEntryState.SETUP_ERROR From 8770a508590a49ec470bf3ebd01b95ed39e6f4d8 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 6 Mar 2024 09:11:31 +0100 Subject: [PATCH 0443/1691] Remove deprecated `hass.components` usage in mobile_app (#111888) --- homeassistant/components/mobile_app/http_api.py | 2 +- homeassistant/components/mobile_app/webhook.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 92bb473d51a..129a6a8b8d1 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -93,7 +93,7 @@ class RegistrationsView(HomeAssistantView): remote_ui_url = None if cloud.async_active_subscription(hass): - with suppress(hass.components.cloud.CloudNotAvailable): + with suppress(cloud.CloudNotAvailable): remote_ui_url = cloud.async_remote_ui_url(hass) return self.json( diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 1a56b13ddc5..19335507b72 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -743,7 +743,7 @@ async def webhook_get_config( resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] if cloud.async_active_subscription(hass): - with suppress(hass.components.cloud.CloudNotAvailable): + with suppress(cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) webhook_id = config_entry.data[CONF_WEBHOOK_ID] From 72ac2f127f955ba8ebbf86e8e47848197f51126e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 22:25:11 -1000 Subject: [PATCH 0444/1691] Wait to start emulated_hue until the started event (#112477) --- homeassistant/components/emulated_hue/__init__.py | 4 ++-- tests/components/emulated_hue/test_init.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 1ba93da716c..b5b51eb4361 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -10,7 +10,7 @@ from homeassistant.components.network import async_get_source_ip from homeassistant.const import ( CONF_ENTITIES, CONF_TYPE, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import Event, HomeAssistant @@ -153,6 +153,6 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool: """Start the bridge.""" await start_emulated_hue_bridge(hass, config, app) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _start) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _start) return True diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 9a872d66946..e8e79e78833 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -12,7 +12,7 @@ from homeassistant.components.emulated_hue.config import ( Config, ) from homeassistant.components.emulated_hue.upnp import UPNPResponderProtocol -from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from homeassistant.util import utcnow @@ -145,7 +145,7 @@ async def test_setup_works(hass: HomeAssistant) -> None: spec=UPNPResponderProtocol ) assert await async_setup_component(hass, "emulated_hue", {}) - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert len(mock_create_upnp_datagram_endpoint.mock_calls) == 1 From 87a82e0562834be682061b4c60f34936f5fb3955 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 5 Mar 2024 23:34:18 -1000 Subject: [PATCH 0445/1691] Fix detection of DLI sockets in wemo (#112485) --- homeassistant/components/wemo/wemo_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/wemo/wemo_device.py b/homeassistant/components/wemo/wemo_device.py index a54610e9a8b..f9b111d7eb1 100644 --- a/homeassistant/components/wemo/wemo_device.py +++ b/homeassistant/components/wemo/wemo_device.py @@ -244,7 +244,7 @@ class DeviceCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-en def _create_device_info(wemo: WeMoDevice) -> DeviceInfo: """Create device information. Modify if special device.""" _dev_info = _device_info(wemo) - if wemo.model_name == "DLI emulated Belkin Socket": + if wemo.model_name.lower() == "dli emulated belkin socket": _dev_info[ATTR_CONFIGURATION_URL] = f"http://{wemo.host}" _dev_info[ATTR_IDENTIFIERS] = {(DOMAIN, wemo.serial_number[:-1])} return _dev_info From 8e26e36033320db29725b152a952f91713e5926d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:34:48 +0100 Subject: [PATCH 0446/1691] Bump Wandalen/wretry.action from 1.4.4 to 1.4.5 (#111700) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 72e303ac1c0..a5f49533147 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1084,7 +1084,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.4.4 + uses: Wandalen/wretry.action@v1.4.5 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1095,7 +1095,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.4.4 + uses: Wandalen/wretry.action@v1.4.5 with: action: codecov/codecov-action@v3.1.3 with: | From 39cad5f1ee3d1adc5c0db51abc197d57778f4ebd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 10:42:07 +0100 Subject: [PATCH 0447/1691] Remove entity description mixin in Enphase Envoy (#112486) --- .../components/enphase_envoy/binary_sensor.py | 26 ++--- .../components/enphase_envoy/number.py | 26 ++--- .../components/enphase_envoy/select.py | 26 ++--- .../components/enphase_envoy/sensor.py | 96 +++++-------------- .../components/enphase_envoy/switch.py | 39 ++------ 5 files changed, 53 insertions(+), 160 deletions(-) diff --git a/homeassistant/components/enphase_envoy/binary_sensor.py b/homeassistant/components/enphase_envoy/binary_sensor.py index 5eb2e621e47..7d7e06dadb5 100644 --- a/homeassistant/components/enphase_envoy/binary_sensor.py +++ b/homeassistant/components/enphase_envoy/binary_sensor.py @@ -22,20 +22,13 @@ from .coordinator import EnphaseUpdateCoordinator from .entity import EnvoyBaseEntity -@dataclass(frozen=True) -class EnvoyEnchargeRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyEnchargeBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes an Envoy Encharge binary sensor entity.""" value_fn: Callable[[EnvoyEncharge], bool] -@dataclass(frozen=True) -class EnvoyEnchargeBinarySensorEntityDescription( - BinarySensorEntityDescription, EnvoyEnchargeRequiredKeysMixin -): - """Describes an Envoy Encharge binary sensor entity.""" - - ENCHARGE_SENSORS = ( EnvoyEnchargeBinarySensorEntityDescription( key="communicating", @@ -53,20 +46,13 @@ ENCHARGE_SENSORS = ( ) -@dataclass(frozen=True) -class EnvoyEnpowerRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyEnpowerBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes an Envoy Enpower binary sensor entity.""" value_fn: Callable[[EnvoyEnpower], bool] -@dataclass(frozen=True) -class EnvoyEnpowerBinarySensorEntityDescription( - BinarySensorEntityDescription, EnvoyEnpowerRequiredKeysMixin -): - """Describes an Envoy Enpower binary sensor entity.""" - - ENPOWER_SENSORS = ( EnvoyEnpowerBinarySensorEntityDescription( key="communicating", diff --git a/homeassistant/components/enphase_envoy/number.py b/homeassistant/components/enphase_envoy/number.py index bf54c91f45b..85f99299c71 100644 --- a/homeassistant/components/enphase_envoy/number.py +++ b/homeassistant/components/enphase_envoy/number.py @@ -25,35 +25,21 @@ from .coordinator import EnphaseUpdateCoordinator from .entity import EnvoyBaseEntity -@dataclass(frozen=True) -class EnvoyRelayRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyRelayNumberEntityDescription(NumberEntityDescription): + """Describes an Envoy Dry Contact Relay number entity.""" value_fn: Callable[[EnvoyDryContactSettings], float] -@dataclass(frozen=True) -class EnvoyRelayNumberEntityDescription( - NumberEntityDescription, EnvoyRelayRequiredKeysMixin -): - """Describes an Envoy Dry Contact Relay number entity.""" - - -@dataclass(frozen=True) -class EnvoyStorageSettingsRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyStorageSettingsNumberEntityDescription(NumberEntityDescription): + """Describes an Envoy storage mode number entity.""" value_fn: Callable[[EnvoyStorageSettings], float] update_fn: Callable[[Envoy, float], Awaitable[dict[str, Any]]] -@dataclass(frozen=True) -class EnvoyStorageSettingsNumberEntityDescription( - NumberEntityDescription, EnvoyStorageSettingsRequiredKeysMixin -): - """Describes an Envoy storage mode number entity.""" - - RELAY_ENTITIES = ( EnvoyRelayNumberEntityDescription( key="soc_low", diff --git a/homeassistant/components/enphase_envoy/select.py b/homeassistant/components/enphase_envoy/select.py index 5d2edf91d9a..b133c6268d6 100644 --- a/homeassistant/components/enphase_envoy/select.py +++ b/homeassistant/components/enphase_envoy/select.py @@ -21,9 +21,9 @@ from .coordinator import EnphaseUpdateCoordinator from .entity import EnvoyBaseEntity -@dataclass(frozen=True) -class EnvoyRelayRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyRelaySelectEntityDescription(SelectEntityDescription): + """Describes an Envoy Dry Contact Relay select entity.""" value_fn: Callable[[EnvoyDryContactSettings], str] update_fn: Callable[ @@ -31,28 +31,14 @@ class EnvoyRelayRequiredKeysMixin: ] -@dataclass(frozen=True) -class EnvoyRelaySelectEntityDescription( - SelectEntityDescription, EnvoyRelayRequiredKeysMixin -): - """Describes an Envoy Dry Contact Relay select entity.""" - - -@dataclass(frozen=True) -class EnvoyStorageSettingsRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyStorageSettingsSelectEntityDescription(SelectEntityDescription): + """Describes an Envoy storage settings select entity.""" value_fn: Callable[[EnvoyStorageSettings], str] update_fn: Callable[[Envoy, str], Awaitable[dict[str, Any]]] -@dataclass(frozen=True) -class EnvoyStorageSettingsSelectEntityDescription( - SelectEntityDescription, EnvoyStorageSettingsRequiredKeysMixin -): - """Describes an Envoy storage settings select entity.""" - - RELAY_MODE_MAP = { DryContactMode.MANUAL: "standard", DryContactMode.STATE_OF_CHARGE: "battery", diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index c0d1c66deae..5ec71eee645 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -58,20 +58,13 @@ INVERTERS_KEY = "inverters" LAST_REPORTED_KEY = "last_reported" -@dataclass(frozen=True) -class EnvoyInverterRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyInverterSensorEntityDescription(SensorEntityDescription): + """Describes an Envoy inverter sensor entity.""" value_fn: Callable[[EnvoyInverter], datetime.datetime | float] -@dataclass(frozen=True) -class EnvoyInverterSensorEntityDescription( - SensorEntityDescription, EnvoyInverterRequiredKeysMixin -): - """Describes an Envoy inverter sensor entity.""" - - INVERTER_SENSORS = ( EnvoyInverterSensorEntityDescription( key=INVERTERS_KEY, @@ -91,21 +84,14 @@ INVERTER_SENSORS = ( ) -@dataclass(frozen=True) -class EnvoyProductionRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyProductionSensorEntityDescription(SensorEntityDescription): + """Describes an Envoy production sensor entity.""" value_fn: Callable[[EnvoySystemProduction], int] on_phase: str | None -@dataclass(frozen=True) -class EnvoyProductionSensorEntityDescription( - SensorEntityDescription, EnvoyProductionRequiredKeysMixin -): - """Describes an Envoy production sensor entity.""" - - PRODUCTION_SENSORS = ( EnvoyProductionSensorEntityDescription( key="production", @@ -168,21 +154,14 @@ PRODUCTION_PHASE_SENSORS = { } -@dataclass(frozen=True) -class EnvoyConsumptionRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyConsumptionSensorEntityDescription(SensorEntityDescription): + """Describes an Envoy consumption sensor entity.""" value_fn: Callable[[EnvoySystemConsumption], int] on_phase: str | None -@dataclass(frozen=True) -class EnvoyConsumptionSensorEntityDescription( - SensorEntityDescription, EnvoyConsumptionRequiredKeysMixin -): - """Describes an Envoy consumption sensor entity.""" - - CONSUMPTION_SENSORS = ( EnvoyConsumptionSensorEntityDescription( key="consumption", @@ -245,9 +224,9 @@ CONSUMPTION_PHASE_SENSORS = { } -@dataclass(frozen=True) -class EnvoyCTRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyCTSensorEntityDescription(SensorEntityDescription): + """Describes an Envoy CT sensor entity.""" value_fn: Callable[ [EnvoyMeterData], @@ -256,11 +235,6 @@ class EnvoyCTRequiredKeysMixin: on_phase: str | None -@dataclass(frozen=True) -class EnvoyCTSensorEntityDescription(SensorEntityDescription, EnvoyCTRequiredKeysMixin): - """Describes an Envoy CT sensor entity.""" - - CT_NET_CONSUMPTION_SENSORS = ( EnvoyCTSensorEntityDescription( key="lifetime_net_consumption", @@ -390,33 +364,24 @@ CT_PRODUCTION_PHASE_SENSORS = { } -@dataclass(frozen=True) -class EnvoyEnchargeRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyEnchargeSensorEntityDescription(SensorEntityDescription): + """Describes an Envoy Encharge sensor entity.""" value_fn: Callable[[EnvoyEncharge], datetime.datetime | int | float] -@dataclass(frozen=True) -class EnvoyEnchargeSensorEntityDescription( - SensorEntityDescription, EnvoyEnchargeRequiredKeysMixin -): - """Describes an Envoy Encharge sensor entity.""" - - @dataclass(frozen=True) class EnvoyEnchargePowerRequiredKeysMixin: """Mixin for required keys.""" - value_fn: Callable[[EnvoyEnchargePower], int | float] - -@dataclass(frozen=True) -class EnvoyEnchargePowerSensorEntityDescription( - SensorEntityDescription, EnvoyEnchargePowerRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class EnvoyEnchargePowerSensorEntityDescription(SensorEntityDescription): """Describes an Envoy Encharge sensor entity.""" + value_fn: Callable[[EnvoyEnchargePower], int | float] + ENCHARGE_INVENTORY_SENSORS = ( EnvoyEnchargeSensorEntityDescription( @@ -455,20 +420,13 @@ ENCHARGE_POWER_SENSORS = ( ) -@dataclass(frozen=True) -class EnvoyEnpowerRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyEnpowerSensorEntityDescription(SensorEntityDescription): + """Describes an Envoy Encharge sensor entity.""" value_fn: Callable[[EnvoyEnpower], datetime.datetime | int | float] -@dataclass(frozen=True) -class EnvoyEnpowerSensorEntityDescription( - SensorEntityDescription, EnvoyEnpowerRequiredKeysMixin -): - """Describes an Envoy Encharge sensor entity.""" - - ENPOWER_SENSORS = ( EnvoyEnpowerSensorEntityDescription( key="temperature", @@ -489,15 +447,13 @@ ENPOWER_SENSORS = ( class EnvoyEnchargeAggregateRequiredKeysMixin: """Mixin for required keys.""" - value_fn: Callable[[EnvoyEnchargeAggregate], int] - -@dataclass(frozen=True) -class EnvoyEnchargeAggregateSensorEntityDescription( - SensorEntityDescription, EnvoyEnchargeAggregateRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class EnvoyEnchargeAggregateSensorEntityDescription(SensorEntityDescription): """Describes an Envoy Encharge sensor entity.""" + value_fn: Callable[[EnvoyEnchargeAggregate], int] + ENCHARGE_AGGREGATE_SENSORS = ( EnvoyEnchargeAggregateSensorEntityDescription( diff --git a/homeassistant/components/enphase_envoy/switch.py b/homeassistant/components/enphase_envoy/switch.py index 921c5601dac..0f9d47ca6df 100644 --- a/homeassistant/components/enphase_envoy/switch.py +++ b/homeassistant/components/enphase_envoy/switch.py @@ -24,54 +24,33 @@ from .entity import EnvoyBaseEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class EnvoyEnpowerRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyEnpowerSwitchEntityDescription(SwitchEntityDescription): + """Describes an Envoy Enpower switch entity.""" value_fn: Callable[[EnvoyEnpower], bool] turn_on_fn: Callable[[Envoy], Coroutine[Any, Any, dict[str, Any]]] turn_off_fn: Callable[[Envoy], Coroutine[Any, Any, dict[str, Any]]] -@dataclass(frozen=True) -class EnvoyEnpowerSwitchEntityDescription( - SwitchEntityDescription, EnvoyEnpowerRequiredKeysMixin -): - """Describes an Envoy Enpower switch entity.""" - - -@dataclass(frozen=True) -class EnvoyDryContactRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyDryContactSwitchEntityDescription(SwitchEntityDescription): + """Describes an Envoy Enpower dry contact switch entity.""" value_fn: Callable[[EnvoyDryContactStatus], bool] turn_on_fn: Callable[[Envoy, str], Coroutine[Any, Any, dict[str, Any]]] turn_off_fn: Callable[[Envoy, str], Coroutine[Any, Any, dict[str, Any]]] -@dataclass(frozen=True) -class EnvoyDryContactSwitchEntityDescription( - SwitchEntityDescription, EnvoyDryContactRequiredKeysMixin -): - """Describes an Envoy Enpower dry contact switch entity.""" - - -@dataclass(frozen=True) -class EnvoyStorageSettingsRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnvoyStorageSettingsSwitchEntityDescription(SwitchEntityDescription): + """Describes an Envoy storage settings switch entity.""" value_fn: Callable[[EnvoyStorageSettings], bool] turn_on_fn: Callable[[Envoy], Awaitable[dict[str, Any]]] turn_off_fn: Callable[[Envoy], Awaitable[dict[str, Any]]] -@dataclass(frozen=True) -class EnvoyStorageSettingsSwitchEntityDescription( - SwitchEntityDescription, EnvoyStorageSettingsRequiredKeysMixin -): - """Describes an Envoy storage settings switch entity.""" - - ENPOWER_GRID_SWITCH = EnvoyEnpowerSwitchEntityDescription( key="mains_admin_state", translation_key="grid_enabled", From 25992526002edefdca8efdf18690fdc5b435a31a Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Wed, 6 Mar 2024 09:47:21 +0000 Subject: [PATCH 0448/1691] Post System Bridge 4.x.x integration improvements (#112189) * Dont remove api key during migration * Fix return * Fix test * Make lambda more readable * Move fixtures to init, move migration test to test_init.py * Refactor config_entry data assignment * Refactor system_bridge migration tests * Fix type for debug message * Fix type for debug message * Remove duplicated unused code (rebase error) * Refactor test_migration_minor_2_to_1 to test_migration_minor_future_to_2 * Fix version check in async_migrate_entry * Update migration logic to handle future minor version * Add ConfigEntryState assertion in test_init.py * Change condition to minor_version < 2 Co-authored-by: Martin Hjelmare * Refactor system bridge migration tests * Remove minor downgrade code * Update tests/components/system_bridge/test_init.py Co-authored-by: Martin Hjelmare * Update tests/components/system_bridge/test_init.py Co-authored-by: Martin Hjelmare * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Move dataclass to store requested data to data.py * Use dataclass in config flow * Move media player and sensor onto data.py dataclass * Move data and handler inside validate --------- Co-authored-by: Martin Hjelmare --- .../components/system_bridge/__init__.py | 16 +- .../components/system_bridge/config_flow.py | 131 ++++----------- .../components/system_bridge/coordinator.py | 39 +---- .../components/system_bridge/data.py | 29 ++++ .../components/system_bridge/media_player.py | 5 +- .../components/system_bridge/sensor.py | 27 ++-- tests/components/system_bridge/__init__.py | 115 ++++++++++++++ .../system_bridge/test_config_flow.py | 149 ++---------------- tests/components/system_bridge/test_init.py | 83 ++++++++++ 9 files changed, 302 insertions(+), 292 deletions(-) create mode 100644 homeassistant/components/system_bridge/data.py create mode 100644 tests/components/system_bridge/test_init.py diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index a7541021e0d..3683834f184 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -45,6 +45,7 @@ from homeassistant.helpers import ( from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from .config_flow import SystemBridgeConfigFlow from .const import DOMAIN, MODULES from .coordinator import SystemBridgeDataUpdateCoordinator @@ -358,13 +359,19 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry.""" - _LOGGER.debug("Migrating from version %s", config_entry.version) + _LOGGER.debug( + "Migrating from version %s.%s", + config_entry.version, + config_entry.minor_version, + ) - if config_entry.version == 1 and config_entry.minor_version == 1: + if config_entry.version > SystemBridgeConfigFlow.VERSION: + return False + + if config_entry.minor_version < 2: # Migrate to CONF_TOKEN, which was added in 1.2 new_data = dict(config_entry.data) new_data.setdefault(CONF_TOKEN, config_entry.data.get(CONF_API_KEY)) - new_data.pop(CONF_API_KEY, None) hass.config_entries.async_update_entry( config_entry, @@ -378,5 +385,4 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> config_entry.minor_version, ) - # User is trying to downgrade from a future version - return False + return True diff --git a/homeassistant/components/system_bridge/config_flow.py b/homeassistant/components/system_bridge/config_flow.py index 1d4f73799a1..0042d9c647e 100644 --- a/homeassistant/components/system_bridge/config_flow.py +++ b/homeassistant/components/system_bridge/config_flow.py @@ -12,18 +12,19 @@ from systembridgeconnector.exceptions import ( ConnectionErrorException, ) from systembridgeconnector.websocket_client import WebSocketClient -from systembridgemodels.modules import GetData, System +from systembridgemodels.modules import GetData import voluptuous as vol from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN +from .data import SystemBridgeData _LOGGER = logging.getLogger(__name__) @@ -45,25 +46,40 @@ async def _validate_input( Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ - host = data[CONF_HOST] + + system_bridge_data = SystemBridgeData() + + async def _async_handle_module( + module_name: str, + module: Any, + ) -> None: + """Handle data from the WebSocket client.""" + _LOGGER.debug("Set new data for: %s", module_name) + setattr(system_bridge_data, module_name, module) websocket_client = WebSocketClient( - host, + data[CONF_HOST], data[CONF_PORT], - data[CONF_API_KEY], + data[CONF_TOKEN], ) + try: async with asyncio.timeout(15): await websocket_client.connect(session=async_get_clientsession(hass)) - hass.async_create_task(websocket_client.listen()) + hass.async_create_task( + websocket_client.listen(callback=_async_handle_module) + ) response = await websocket_client.get_data(GetData(modules=["system"])) - _LOGGER.debug("Got response: %s", response.json()) - if response.data is None or not isinstance(response.data, System): + _LOGGER.debug("Got response: %s", response) + if response is None: raise CannotConnect("No data received") - system: System = response.data + while system_bridge_data.system is None: + await asyncio.sleep(0.2) except AuthenticationException as exception: _LOGGER.warning( - "Authentication error when connecting to %s: %s", data[CONF_HOST], exception + "Authentication error when connecting to %s: %s", + data[CONF_HOST], + exception, ) raise InvalidAuth from exception except ( @@ -80,9 +96,9 @@ async def _validate_input( except ValueError as exception: raise CannotConnect from exception - _LOGGER.debug("Got System data: %s", system.json()) + _LOGGER.debug("Got System data: %s", system_bridge_data.system) - return {"hostname": host, "uuid": system.uuid} + return {"hostname": data[CONF_HOST], "uuid": system_bridge_data.system.uuid} async def _async_get_info( @@ -120,93 +136,6 @@ class SystemBridgeConfigFlow( self._name: str | None = None self._input: dict[str, Any] = {} self._reauth = False - self._system_data: System | None = None - - async def _validate_input( - self, - data: dict[str, Any], - ) -> dict[str, str]: - """Validate the user input allows us to connect. - - Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. - """ - host = data[CONF_HOST] - - websocket_client = WebSocketClient( - host, - data[CONF_PORT], - data[CONF_TOKEN], - ) - - async def async_handle_module( - module_name: str, - module: Any, - ) -> None: - """Handle data from the WebSocket client.""" - _LOGGER.debug("Set new data for: %s", module_name) - if module_name == "system": - self._system_data = module - - try: - async with asyncio.timeout(15): - await websocket_client.connect( - session=async_get_clientsession(self.hass) - ) - self.hass.async_create_task( - websocket_client.listen(callback=async_handle_module) - ) - response = await websocket_client.get_data(GetData(modules=["system"])) - _LOGGER.debug("Got response: %s", response) - if response is None: - raise CannotConnect("No data received") - while self._system_data is None: - await asyncio.sleep(0.2) - except AuthenticationException as exception: - _LOGGER.warning( - "Authentication error when connecting to %s: %s", - data[CONF_HOST], - exception, - ) - raise InvalidAuth from exception - except ( - ConnectionClosedException, - ConnectionErrorException, - ) as exception: - _LOGGER.warning( - "Connection error when connecting to %s: %s", data[CONF_HOST], exception - ) - raise CannotConnect from exception - except TimeoutError as exception: - _LOGGER.warning( - "Timed out connecting to %s: %s", data[CONF_HOST], exception - ) - raise CannotConnect from exception - except ValueError as exception: - raise CannotConnect from exception - - _LOGGER.debug("Got System data: %s", self._system_data) - - return {"hostname": host, "uuid": self._system_data.uuid} - - async def _async_get_info( - self, - user_input: dict[str, Any], - ) -> tuple[dict[str, str], dict[str, str] | None]: - errors = {} - - try: - info = await self._validate_input(user_input) - except CannotConnect: - errors["base"] = "cannot_connect" - except InvalidAuth: - errors["base"] = "invalid_auth" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: - return errors, info - - return errors, None async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -217,7 +146,7 @@ class SystemBridgeConfigFlow( step_id="user", data_schema=STEP_USER_DATA_SCHEMA ) - errors, info = await self._async_get_info(user_input) + errors, info = await _async_get_info(self.hass, user_input) if not errors and info is not None: # Check if already configured await self.async_set_unique_id(info["uuid"], raise_on_progress=False) @@ -237,7 +166,7 @@ class SystemBridgeConfigFlow( if user_input is not None: user_input = {**self._input, **user_input} - errors, info = await self._async_get_info(user_input) + errors, info = await _async_get_info(self.hass, user_input) if not errors and info is not None: # Check if already configured existing_entry = await self.async_set_unique_id(info["uuid"]) diff --git a/homeassistant/components/system_bridge/coordinator.py b/homeassistant/components/system_bridge/coordinator.py index 459caa975cc..ca475dc5863 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio from collections.abc import Callable -from dataclasses import dataclass, field from datetime import timedelta import logging from typing import Any @@ -18,19 +17,7 @@ from systembridgemodels.media_directories import MediaDirectory from systembridgemodels.media_files import MediaFile, MediaFiles from systembridgemodels.media_get_file import MediaGetFile from systembridgemodels.media_get_files import MediaGetFiles -from systembridgemodels.modules import ( - CPU, - GPU, - Battery, - Disks, - Display, - GetData, - Media, - Memory, - Process, - RegisterDataListener, - System, -) +from systembridgemodels.modules import GetData, RegisterDataListener from systembridgemodels.response import Response from homeassistant.config_entries import ConfigEntry @@ -46,26 +33,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN, MODULES +from .data import SystemBridgeData -@dataclass -class SystemBridgeCoordinatorData: - """System Bridge Coordianator Data.""" - - battery: Battery = field(default_factory=Battery) - cpu: CPU = field(default_factory=CPU) - disks: Disks = None - displays: list[Display] = field(default_factory=list[Display]) - gpus: list[GPU] = field(default_factory=list[GPU]) - media: Media = field(default_factory=Media) - memory: Memory = None - processes: list[Process] = field(default_factory=list[Process]) - system: System = None - - -class SystemBridgeDataUpdateCoordinator( - DataUpdateCoordinator[SystemBridgeCoordinatorData] -): +class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[SystemBridgeData]): """Class to manage fetching System Bridge data from single endpoint.""" def __init__( @@ -79,7 +50,7 @@ class SystemBridgeDataUpdateCoordinator( self.title = entry.title self.unsub: Callable | None = None - self.systembridge_data = SystemBridgeCoordinatorData() + self.systembridge_data = SystemBridgeData() self.websocket_client = WebSocketClient( entry.data[CONF_HOST], entry.data[CONF_PORT], @@ -247,7 +218,7 @@ class SystemBridgeDataUpdateCoordinator( EVENT_HOMEASSISTANT_STOP, close_websocket ) - async def _async_update_data(self) -> SystemBridgeCoordinatorData: + async def _async_update_data(self) -> SystemBridgeData: """Update System Bridge data from WebSocket.""" self.logger.debug( "_async_update_data - WebSocket Connected: %s", diff --git a/homeassistant/components/system_bridge/data.py b/homeassistant/components/system_bridge/data.py new file mode 100644 index 00000000000..fc7d119a324 --- /dev/null +++ b/homeassistant/components/system_bridge/data.py @@ -0,0 +1,29 @@ +"""System Bridge integration data.""" +from dataclasses import dataclass, field + +from systembridgemodels.modules import ( + CPU, + GPU, + Battery, + Disks, + Display, + Media, + Memory, + Process, + System, +) + + +@dataclass +class SystemBridgeData: + """System Bridge Data.""" + + battery: Battery = field(default_factory=Battery) + cpu: CPU = field(default_factory=CPU) + disks: Disks = None + displays: list[Display] = field(default_factory=list[Display]) + gpus: list[GPU] = field(default_factory=list[GPU]) + media: Media = field(default_factory=Media) + memory: Memory = None + processes: list[Process] = field(default_factory=list[Process]) + system: System = None diff --git a/homeassistant/components/system_bridge/media_player.py b/homeassistant/components/system_bridge/media_player.py index a9252a739c9..79fa3dc8c93 100644 --- a/homeassistant/components/system_bridge/media_player.py +++ b/homeassistant/components/system_bridge/media_player.py @@ -20,7 +20,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator +from .coordinator import SystemBridgeDataUpdateCoordinator +from .data import SystemBridgeData from .entity import SystemBridgeEntity STATUS_CHANGING: Final[str] = "CHANGING" @@ -126,7 +127,7 @@ class SystemBridgeMediaPlayer(SystemBridgeEntity, MediaPlayerEntity): return features @property - def _systembridge_data(self) -> SystemBridgeCoordinatorData: + def _systembridge_data(self) -> SystemBridgeData: """Return data for the entity.""" return self.coordinator.data diff --git a/homeassistant/components/system_bridge/sensor.py b/homeassistant/components/system_bridge/sensor.py index b4e840643f1..c7a5eac391c 100644 --- a/homeassistant/components/system_bridge/sensor.py +++ b/homeassistant/components/system_bridge/sensor.py @@ -33,7 +33,8 @@ from homeassistant.helpers.typing import UNDEFINED, StateType from homeassistant.util import dt as dt_util from .const import DOMAIN -from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator +from .coordinator import SystemBridgeDataUpdateCoordinator +from .data import SystemBridgeData from .entity import SystemBridgeEntity ATTR_AVAILABLE: Final = "available" @@ -53,14 +54,14 @@ class SystemBridgeSensorEntityDescription(SensorEntityDescription): value: Callable = round -def battery_time_remaining(data: SystemBridgeCoordinatorData) -> datetime | None: +def battery_time_remaining(data: SystemBridgeData) -> datetime | None: """Return the battery time remaining.""" if (battery_time := data.battery.time_remaining) is not None: return dt_util.utcnow() + timedelta(seconds=battery_time) return None -def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None: +def cpu_speed(data: SystemBridgeData) -> float | None: """Return the CPU speed.""" if (cpu_frequency := data.cpu.frequency) is not None and ( cpu_frequency.current @@ -72,7 +73,7 @@ def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None: def with_per_cpu(func) -> Callable: """Wrap a function to ensure per CPU data is available.""" - def wrapper(data: SystemBridgeCoordinatorData, index: int) -> float | None: + def wrapper(data: SystemBridgeData, index: int) -> float | None: """Wrap a function to ensure per CPU data is available.""" if data.cpu.per_cpu is not None and index < len(data.cpu.per_cpu): return func(data.cpu.per_cpu[index]) @@ -96,7 +97,7 @@ def cpu_usage_per_cpu(per_cpu: PerCPU) -> float | None: def with_display(func) -> Callable: """Wrap a function to ensure a Display is available.""" - def wrapper(data: SystemBridgeCoordinatorData, index: int) -> Display | None: + def wrapper(data: SystemBridgeData, index: int) -> Display | None: """Wrap a function to ensure a Display is available.""" if index < len(data.displays): return func(data.displays[index]) @@ -126,7 +127,7 @@ def display_refresh_rate(display: Display) -> float | None: def with_gpu(func) -> Callable: """Wrap a function to ensure a GPU is available.""" - def wrapper(data: SystemBridgeCoordinatorData, index: int) -> GPU | None: + def wrapper(data: SystemBridgeData, index: int) -> GPU | None: """Wrap a function to ensure a GPU is available.""" if index < len(data.gpus): return func(data.gpus[index]) @@ -191,7 +192,7 @@ def gpu_usage_percentage(gpu: GPU) -> float | None: return gpu.core_load -def memory_free(data: SystemBridgeCoordinatorData) -> float | None: +def memory_free(data: SystemBridgeData) -> float | None: """Return the free memory.""" if (virtual := data.memory.virtual) is not None and ( free := virtual.free @@ -200,7 +201,7 @@ def memory_free(data: SystemBridgeCoordinatorData) -> float | None: return None -def memory_used(data: SystemBridgeCoordinatorData) -> float | None: +def memory_used(data: SystemBridgeData) -> float | None: """Return the used memory.""" if (virtual := data.memory.virtual) is not None and ( used := virtual.used @@ -210,7 +211,7 @@ def memory_used(data: SystemBridgeCoordinatorData) -> float | None: def partition_usage( - data: SystemBridgeCoordinatorData, + data: SystemBridgeData, device_index: int, partition_index: int, ) -> float | None: @@ -382,9 +383,11 @@ async def async_setup_entry( state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, icon="mdi:harddisk", - value=lambda data, - dk=index_device, - pk=index_partition: partition_usage(data, dk, pk), + value=( + lambda data, + dk=index_device, + pk=index_partition: partition_usage(data, dk, pk) + ), ), entry.data[CONF_PORT], ) diff --git a/tests/components/system_bridge/__init__.py b/tests/components/system_bridge/__init__.py index f049f887584..edbe5469705 100644 --- a/tests/components/system_bridge/__init__.py +++ b/tests/components/system_bridge/__init__.py @@ -1 +1,116 @@ """Tests for the System Bridge integration.""" + +from collections.abc import Awaitable, Callable +from dataclasses import asdict +from ipaddress import ip_address +from typing import Any + +from systembridgeconnector.const import TYPE_DATA_UPDATE +from systembridgemodels.const import MODEL_SYSTEM +from systembridgemodels.modules import System +from systembridgemodels.response import Response + +from homeassistant.components import zeroconf +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN + +FIXTURE_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" +FIXTURE_UUID = "e91bf575-56f3-4c83-8f42-70ac17adcd33" + +FIXTURE_AUTH_INPUT = {CONF_TOKEN: "abc-123-def-456-ghi"} + +FIXTURE_USER_INPUT = { + CONF_TOKEN: "abc-123-def-456-ghi", + CONF_HOST: "test-bridge", + CONF_PORT: "9170", +} + +FIXTURE_ZEROCONF_INPUT = { + CONF_TOKEN: "abc-123-def-456-ghi", + CONF_HOST: "1.1.1.1", + CONF_PORT: "9170", +} + +FIXTURE_ZEROCONF = zeroconf.ZeroconfServiceInfo( + ip_address=ip_address("1.1.1.1"), + ip_addresses=[ip_address("1.1.1.1")], + port=9170, + hostname="test-bridge.local.", + type="_system-bridge._tcp.local.", + name="System Bridge - test-bridge._system-bridge._tcp.local.", + properties={ + "address": "http://test-bridge:9170", + "fqdn": "test-bridge", + "host": "test-bridge", + "ip": "1.1.1.1", + "mac": FIXTURE_MAC_ADDRESS, + "port": "9170", + "uuid": FIXTURE_UUID, + }, +) + +FIXTURE_ZEROCONF_BAD = zeroconf.ZeroconfServiceInfo( + ip_address=ip_address("1.1.1.1"), + ip_addresses=[ip_address("1.1.1.1")], + port=9170, + hostname="test-bridge.local.", + type="_system-bridge._tcp.local.", + name="System Bridge - test-bridge._system-bridge._tcp.local.", + properties={ + "something": "bad", + }, +) + + +FIXTURE_SYSTEM = System( + boot_time=1, + fqdn="", + hostname="1.1.1.1", + ip_address_4="1.1.1.1", + mac_address=FIXTURE_MAC_ADDRESS, + platform="", + platform_version="", + uptime=1, + uuid=FIXTURE_UUID, + version="", + version_latest="", + version_newer_available=False, + users=[], +) + +FIXTURE_DATA_RESPONSE = Response( + id="1234", + type=TYPE_DATA_UPDATE, + subtype=None, + message="Data received", + module=MODEL_SYSTEM, + data=asdict(FIXTURE_SYSTEM), +) + +FIXTURE_DATA_RESPONSE_BAD = Response( + id="1234", + type=TYPE_DATA_UPDATE, + subtype=None, + message="Data received", + module=MODEL_SYSTEM, + data={}, +) + +FIXTURE_DATA_RESPONSE_BAD = Response( + id="1234", + type=TYPE_DATA_UPDATE, + subtype=None, + message="Data received", + module=MODEL_SYSTEM, + data={}, +) + + +async def mock_data_listener( + self, + callback: Callable[[str, Any], Awaitable[None]] | None = None, + _: bool = False, +): + """Mock websocket data listener.""" + if callback is not None: + # Simulate data received from the websocket + await callback(MODEL_SYSTEM, FIXTURE_SYSTEM) diff --git a/tests/components/system_bridge/test_config_flow.py b/tests/components/system_bridge/test_config_flow.py index 21194db42fa..e30d389cc1b 100644 --- a/tests/components/system_bridge/test_config_flow.py +++ b/tests/components/system_bridge/test_config_flow.py @@ -1,131 +1,29 @@ """Test the System Bridge config flow.""" -from collections.abc import Awaitable, Callable -from dataclasses import asdict -from ipaddress import ip_address -from typing import Any from unittest.mock import patch -from systembridgeconnector.const import TYPE_DATA_UPDATE from systembridgeconnector.exceptions import ( AuthenticationException, ConnectionClosedException, ConnectionErrorException, ) -from systembridgemodels.const import MODEL_SYSTEM -from systembridgemodels.modules.system import System -from systembridgemodels.response import Response from homeassistant import config_entries, data_entry_flow -from homeassistant.components import zeroconf -from homeassistant.components.system_bridge.config_flow import SystemBridgeConfigFlow from homeassistant.components.system_bridge.const import DOMAIN -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN from homeassistant.core import HomeAssistant +from . import ( + FIXTURE_AUTH_INPUT, + FIXTURE_DATA_RESPONSE, + FIXTURE_USER_INPUT, + FIXTURE_UUID, + FIXTURE_ZEROCONF, + FIXTURE_ZEROCONF_BAD, + FIXTURE_ZEROCONF_INPUT, + mock_data_listener, +) + from tests.common import MockConfigEntry -FIXTURE_MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" -FIXTURE_UUID = "e91bf575-56f3-4c83-8f42-70ac17adcd33" - -FIXTURE_AUTH_INPUT = {CONF_TOKEN: "abc-123-def-456-ghi"} - -FIXTURE_USER_INPUT = { - CONF_TOKEN: "abc-123-def-456-ghi", - CONF_HOST: "test-bridge", - CONF_PORT: "9170", -} - -FIXTURE_ZEROCONF_INPUT = { - CONF_TOKEN: "abc-123-def-456-ghi", - CONF_HOST: "1.1.1.1", - CONF_PORT: "9170", -} - -FIXTURE_ZEROCONF = zeroconf.ZeroconfServiceInfo( - ip_address=ip_address("1.1.1.1"), - ip_addresses=[ip_address("1.1.1.1")], - port=9170, - hostname="test-bridge.local.", - type="_system-bridge._tcp.local.", - name="System Bridge - test-bridge._system-bridge._tcp.local.", - properties={ - "address": "http://test-bridge:9170", - "fqdn": "test-bridge", - "host": "test-bridge", - "ip": "1.1.1.1", - "mac": FIXTURE_MAC_ADDRESS, - "port": "9170", - "uuid": FIXTURE_UUID, - }, -) - -FIXTURE_ZEROCONF_BAD = zeroconf.ZeroconfServiceInfo( - ip_address=ip_address("1.1.1.1"), - ip_addresses=[ip_address("1.1.1.1")], - port=9170, - hostname="test-bridge.local.", - type="_system-bridge._tcp.local.", - name="System Bridge - test-bridge._system-bridge._tcp.local.", - properties={ - "something": "bad", - }, -) - - -FIXTURE_SYSTEM = System( - boot_time=1, - fqdn="", - hostname="1.1.1.1", - ip_address_4="1.1.1.1", - mac_address=FIXTURE_MAC_ADDRESS, - platform="", - platform_version="", - uptime=1, - uuid=FIXTURE_UUID, - version="", - version_latest="", - version_newer_available=False, - users=[], -) - -FIXTURE_DATA_RESPONSE = Response( - id="1234", - type=TYPE_DATA_UPDATE, - subtype=None, - message="Data received", - module=MODEL_SYSTEM, - data=asdict(FIXTURE_SYSTEM), -) - -FIXTURE_DATA_RESPONSE_BAD = Response( - id="1234", - type=TYPE_DATA_UPDATE, - subtype=None, - message="Data received", - module=MODEL_SYSTEM, - data={}, -) - -FIXTURE_DATA_RESPONSE_BAD = Response( - id="1234", - type=TYPE_DATA_UPDATE, - subtype=None, - message="Data received", - module=MODEL_SYSTEM, - data={}, -) - - -async def mock_data_listener( - self, - callback: Callable[[str, Any], Awaitable[None]] | None = None, - _: bool = False, -): - """Mock websocket data listener.""" - if callback is not None: - # Simulate data received from the websocket - await callback(MODEL_SYSTEM, FIXTURE_SYSTEM) - async def test_show_user_form(hass: HomeAssistant) -> None: """Test that the setup form is served.""" @@ -536,28 +434,3 @@ async def test_zeroconf_bad_zeroconf_info(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "unknown" - - -async def test_migration(hass: HomeAssistant) -> None: - """Test migration from system_bridge to system_bridge.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - unique_id=FIXTURE_UUID, - data={ - CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN], - CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST], - CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT], - }, - version=1, - minor_version=1, - ) - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - # Check that the version has been updated and the api_key has been moved to token - assert config_entry.version == SystemBridgeConfigFlow.VERSION - assert config_entry.minor_version == SystemBridgeConfigFlow.MINOR_VERSION - assert CONF_API_KEY not in config_entry.data - assert config_entry.data[CONF_TOKEN] == FIXTURE_USER_INPUT[CONF_TOKEN] - assert config_entry.data == FIXTURE_USER_INPUT diff --git a/tests/components/system_bridge/test_init.py b/tests/components/system_bridge/test_init.py new file mode 100644 index 00000000000..67d8595ba4c --- /dev/null +++ b/tests/components/system_bridge/test_init.py @@ -0,0 +1,83 @@ +"""Test the System Bridge integration.""" + +from unittest.mock import patch + +from homeassistant.components.system_bridge.config_flow import SystemBridgeConfigFlow +from homeassistant.components.system_bridge.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONF_TOKEN +from homeassistant.core import HomeAssistant + +from . import FIXTURE_USER_INPUT, FIXTURE_UUID + +from tests.common import MockConfigEntry + + +async def test_migration_minor_1_to_2(hass: HomeAssistant) -> None: + """Test migration.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=FIXTURE_UUID, + data={ + CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN], + CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST], + CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT], + }, + version=SystemBridgeConfigFlow.VERSION, + minor_version=1, + ) + + with patch( + "homeassistant.components.system_bridge.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 1 + + # Check that the version has been updated and the api_key has been moved to token + assert config_entry.version == SystemBridgeConfigFlow.VERSION + assert config_entry.minor_version == SystemBridgeConfigFlow.MINOR_VERSION + assert config_entry.data == { + CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN], + CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST], + CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT], + CONF_TOKEN: FIXTURE_USER_INPUT[CONF_TOKEN], + } + assert config_entry.state == ConfigEntryState.LOADED + + +async def test_migration_minor_future_version(hass: HomeAssistant) -> None: + """Test migration.""" + config_entry_data = { + CONF_API_KEY: FIXTURE_USER_INPUT[CONF_TOKEN], + CONF_HOST: FIXTURE_USER_INPUT[CONF_HOST], + CONF_PORT: FIXTURE_USER_INPUT[CONF_PORT], + CONF_TOKEN: FIXTURE_USER_INPUT[CONF_TOKEN], + } + config_entry_version = SystemBridgeConfigFlow.VERSION + config_entry_minor_version = SystemBridgeConfigFlow.MINOR_VERSION + 1 + config_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=FIXTURE_UUID, + data=config_entry_data, + version=config_entry_version, + minor_version=config_entry_minor_version, + ) + + with patch( + "homeassistant.components.system_bridge.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert len(mock_setup_entry.mock_calls) == 1 + + assert config_entry.version == config_entry_version + assert config_entry.minor_version == config_entry_minor_version + assert config_entry.data == config_entry_data + assert config_entry.state == ConfigEntryState.LOADED From 09b1b40833843b6cbe7f966b52e526d30bb2b64d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 11:02:13 +0100 Subject: [PATCH 0449/1691] Add icon translations to Reolink (#112208) * Add icon translations to Reolink * Fix * Update homeassistant/components/reolink/icons.json Co-authored-by: starkillerOG * Update homeassistant/components/reolink/icons.json --------- Co-authored-by: starkillerOG --- .../components/reolink/binary_sensor.py | 20 -- homeassistant/components/reolink/button.py | 10 - homeassistant/components/reolink/icons.json | 260 ++++++++++++++++++ homeassistant/components/reolink/light.py | 2 - homeassistant/components/reolink/number.py | 29 -- homeassistant/components/reolink/select.py | 7 - homeassistant/components/reolink/sensor.py | 2 - homeassistant/components/reolink/siren.py | 1 - homeassistant/components/reolink/switch.py | 18 -- 9 files changed, 260 insertions(+), 89 deletions(-) create mode 100644 homeassistant/components/reolink/icons.json diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index 04f3f7c6186..87991709e8b 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -36,8 +36,6 @@ class ReolinkBinarySensorEntityDescription( ): """A class that describes binary sensor entities.""" - icon_off: str = "mdi:motion-sensor-off" - icon: str = "mdi:motion-sensor" value: Callable[[Host, int], bool] @@ -50,7 +48,6 @@ BINARY_SENSORS = ( ReolinkBinarySensorEntityDescription( key=FACE_DETECTION_TYPE, translation_key="face", - icon="mdi:face-recognition", value=lambda api, ch: api.ai_detected(ch, FACE_DETECTION_TYPE), supported=lambda api, ch: api.ai_supported(ch, FACE_DETECTION_TYPE), ), @@ -63,16 +60,12 @@ BINARY_SENSORS = ( ReolinkBinarySensorEntityDescription( key=VEHICLE_DETECTION_TYPE, translation_key="vehicle", - icon="mdi:car", - icon_off="mdi:car-off", value=lambda api, ch: api.ai_detected(ch, VEHICLE_DETECTION_TYPE), supported=lambda api, ch: api.ai_supported(ch, VEHICLE_DETECTION_TYPE), ), ReolinkBinarySensorEntityDescription( key=PET_DETECTION_TYPE, translation_key="pet", - icon="mdi:dog-side", - icon_off="mdi:dog-side-off", value=lambda api, ch: api.ai_detected(ch, PET_DETECTION_TYPE), supported=lambda api, ch: ( api.ai_supported(ch, PET_DETECTION_TYPE) @@ -82,24 +75,18 @@ BINARY_SENSORS = ( ReolinkBinarySensorEntityDescription( key=PET_DETECTION_TYPE, translation_key="animal", - icon="mdi:paw", - icon_off="mdi:paw-off", value=lambda api, ch: api.ai_detected(ch, PET_DETECTION_TYPE), supported=lambda api, ch: api.supported(ch, "ai_animal"), ), ReolinkBinarySensorEntityDescription( key=PACKAGE_DETECTION_TYPE, translation_key="package", - icon="mdi:gift-outline", - icon_off="mdi:gift-off-outline", value=lambda api, ch: api.ai_detected(ch, PACKAGE_DETECTION_TYPE), supported=lambda api, ch: api.ai_supported(ch, PACKAGE_DETECTION_TYPE), ), ReolinkBinarySensorEntityDescription( key="visitor", translation_key="visitor", - icon="mdi:bell-ring-outline", - icon_off="mdi:doorbell", value=lambda api, ch: api.visitor_detected(ch), supported=lambda api, ch: api.is_doorbell(ch), ), @@ -149,13 +136,6 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt key = entity_description.key self._attr_translation_key = f"{key}_lens_{self._channel}" - @property - def icon(self) -> str | None: - """Icon of the sensor.""" - if self.is_on is False: - return self.entity_description.icon_off - return super().icon - @property def is_on(self) -> bool: """State of the sensor.""" diff --git a/homeassistant/components/reolink/button.py b/homeassistant/components/reolink/button.py index 5656f178db6..b878505ed84 100644 --- a/homeassistant/components/reolink/button.py +++ b/homeassistant/components/reolink/button.py @@ -64,7 +64,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_stop", translation_key="ptz_stop", - icon="mdi:pan", enabled_default=lambda api, ch: api.supported(ch, "pan_tilt"), supported=lambda api, ch: ( api.supported(ch, "pan_tilt") or api.supported(ch, "zoom_basic") @@ -74,7 +73,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_left", translation_key="ptz_left", - icon="mdi:pan", supported=lambda api, ch: api.supported(ch, "pan"), method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.left.value), ptz_cmd=PtzEnum.left.value, @@ -82,7 +80,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_right", translation_key="ptz_right", - icon="mdi:pan", supported=lambda api, ch: api.supported(ch, "pan"), method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.right.value), ptz_cmd=PtzEnum.right.value, @@ -90,7 +87,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_up", translation_key="ptz_up", - icon="mdi:pan", supported=lambda api, ch: api.supported(ch, "tilt"), method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.up.value), ptz_cmd=PtzEnum.up.value, @@ -98,7 +94,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_down", translation_key="ptz_down", - icon="mdi:pan", supported=lambda api, ch: api.supported(ch, "tilt"), method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.down.value), ptz_cmd=PtzEnum.down.value, @@ -106,7 +101,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_zoom_in", translation_key="ptz_zoom_in", - icon="mdi:magnify", entity_registry_enabled_default=False, supported=lambda api, ch: api.supported(ch, "zoom_basic"), method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.zoomin.value), @@ -115,7 +109,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_zoom_out", translation_key="ptz_zoom_out", - icon="mdi:magnify", entity_registry_enabled_default=False, supported=lambda api, ch: api.supported(ch, "zoom_basic"), method=lambda api, ch: api.set_ptz_command(ch, command=PtzEnum.zoomout.value), @@ -124,7 +117,6 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="ptz_calibrate", translation_key="ptz_calibrate", - icon="mdi:pan", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "ptz_callibrate"), method=lambda api, ch: api.ptz_callibrate(ch), @@ -132,14 +124,12 @@ BUTTON_ENTITIES = ( ReolinkButtonEntityDescription( key="guard_go_to", translation_key="guard_go_to", - icon="mdi:crosshairs-gps", supported=lambda api, ch: api.supported(ch, "ptz_guard"), method=lambda api, ch: api.set_ptz_guard(ch, command=GuardEnum.goto.value), ), ReolinkButtonEntityDescription( key="guard_set", translation_key="guard_set", - icon="mdi:crosshairs-gps", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "ptz_guard"), method=lambda api, ch: api.set_ptz_guard(ch, command=GuardEnum.set.value), diff --git a/homeassistant/components/reolink/icons.json b/homeassistant/components/reolink/icons.json new file mode 100644 index 00000000000..ff2dece95ea --- /dev/null +++ b/homeassistant/components/reolink/icons.json @@ -0,0 +1,260 @@ +{ + "entity": { + "binary_sensor": { + "face": { + "default": "mdi:motion-sensor-off", + "state": { + "on": "mdi:face-recognition" + } + }, + "vehicle": { + "default": "mdi:car-off", + "state": { + "on": "mdi:car" + } + }, + "pet": { + "default": "mdi:dog-side-off", + "state": { + "on": "mdi:dog-side" + } + }, + "animal": { + "default": "mdi:paw-off", + "state": { + "on": "mdi:paw" + } + }, + "package": { + "default": "mdi:gift-off-outline", + "state": { + "on": "mdi:gift-outline" + } + }, + "visitor": { + "default": "mdi:doorbell", + "state": { + "on": "mdi:bell-ring-outline" + } + }, + "person": { + "default": "mdi:motion-sensor-off", + "state": { + "on": "mdi:motion-sensor" + } + } + }, + "button": { + "ptz_stop": { + "default": "mdi:pan" + }, + "ptz_left": { + "default": "mdi:pan" + }, + "ptz_right": { + "default": "mdi:pan" + }, + "ptz_up": { + "default": "mdi:pan" + }, + "ptz_down": { + "default": "mdi:pan" + }, + "ptz_zoom_in": { + "default": "mdi:magnify" + }, + "ptz_zoom_out": { + "default": "mdi:magnify" + }, + "ptz_calibrate": { + "default": "mdi:pan" + }, + "guard_go_to": { + "default": "mdi:crosshairs-gps" + }, + "guard_set": { + "default": "mdi:crosshairs-gps" + } + }, + "light": { + "floodlight": { + "default": "mdi:spotlight-beam" + }, + "status_led": { + "default": "mdi:lightning-bolt-circle" + } + }, + "number": { + "zoom": { + "default": "mdi:magnify" + }, + "focus": { + "default": "mdi:focus-field" + }, + "floodlight_brightness": { + "default": "mdi:spotlight-beam" + }, + "volume": { + "default": "mdi:volume-high" + }, + "guard_return_time": { + "default": "mdi:crosshairs-gps" + }, + "motion_sensitivity": { + "default": "mdi:motion-sensor" + }, + "ai_face_sensititvity": { + "default": "mdi:face-recognition" + }, + "ai_person_sensititvity": { + "default": "mdi:account" + }, + "ai_vehicle_sensititvity": { + "default": "mdi:car" + }, + "ai_package_sensititvity": { + "default": "mdi:gift-outline" + }, + "ai_pet_sensititvity": { + "default": "mdi:dog-side" + }, + "ai_animal_sensititvity": { + "default": "mdi:paw" + }, + "ai_face_delay": { + "default": "mdi:face-recognition" + }, + "ai_person_delay": { + "default": "mdi:account" + }, + "ai_vehicle_delay": { + "default": "mdi:car" + }, + "ai_package_delay": { + "default": "mdi:gift-outline" + }, + "ai_pet_delay": { + "default": "mdi:dog-side" + }, + "ai_animal_delay": { + "default": "mdi:paw" + }, + "auto_quick_reply_time": { + "default": "mdi:message-reply-text-outline" + }, + "auto_track_limit_left": { + "default": "mdi:angle-acute" + }, + "auto_track_limit_right": { + "default": "mdi:angle-acute" + }, + "auto_track_disappear_time": { + "default": "mdi:target-account" + }, + "auto_track_stop_time": { + "default": "mdi:target-account" + }, + "day_night_switch_threshold": { + "default": "mdi:theme-light-dark" + }, + "image_brightness": { + "default": "mdi:image-edit" + }, + "image_contrast": { + "default": "mdi:image-edit" + }, + "image_saturation": { + "default": "mdi:image-edit" + }, + "image_sharpness": { + "default": "mdi:image-edit" + }, + "image_hue": { + "default": "mdi:image-edit" + } + }, + "select": { + "floodlight_mode": { + "default": "mdi:spotlight-beam" + }, + "day_night_mode": { + "default": "mdi:theme-light-dark" + }, + "ptz_preset": { + "default": "mdi:pan" + }, + "play_quick_reply_message": { + "default": "mdi:message-reply-text-outline" + }, + "auto_quick_reply_message": { + "default": "mdi:message-reply-text-outline" + }, + "auto_track_method": { + "default": "mdi:target-account" + }, + "status_led": { + "default": "mdi:lightning-bolt-circle" + } + }, + "sensor": { + "ptz_pan_position": { + "default": "mdi:pan" + }, + "wifi_signal": { + "default": "mdi:wifi" + } + }, + "siren": { + "siren": { + "default": "mdi:alarm-light" + } + }, + "switch": { + "ir_lights": { + "default": "mdi:led-off" + }, + "record_audio": { + "default": "mdi:microphone" + }, + "siren_on_event": { + "default": "mdi:alarm-light" + }, + "auto_tracking": { + "default": "mdi:target-account" + }, + "auto_focus": { + "default": "mdi:focus-field" + }, + "gaurd_return": { + "default": "mdi:crosshairs-gps" + }, + "ptz_patrol": { + "default": "mdi:map-marker-path" + }, + "email": { + "default": "mdi:email" + }, + "ftp_upload": { + "default": "mdi:swap-horizontal" + }, + "push_notifications": { + "default": "mdi:message-badge" + }, + "record": { + "default": "mdi:record-rec" + }, + "buzzer": { + "default": "mdi:room-service" + }, + "doorbell_button_sound": { + "default": "mdi:volume-high" + }, + "hdr": { + "default": "mdi:hdr" + } + } + }, + "services": { + "ptz_move": "mdi:pan" + } +} diff --git a/homeassistant/components/reolink/light.py b/homeassistant/components/reolink/light.py index 222ab984e3f..108e8fb477b 100644 --- a/homeassistant/components/reolink/light.py +++ b/homeassistant/components/reolink/light.py @@ -43,7 +43,6 @@ LIGHT_ENTITIES = ( key="floodlight", cmd_key="GetWhiteLed", translation_key="floodlight", - icon="mdi:spotlight-beam", supported=lambda api, ch: api.supported(ch, "floodLight"), is_on_fn=lambda api, ch: api.whiteled_state(ch), turn_on_off_fn=lambda api, ch, value: api.set_whiteled(ch, state=value), @@ -54,7 +53,6 @@ LIGHT_ENTITIES = ( key="status_led", cmd_key="GetPowerLed", translation_key="status_led", - icon="mdi:lightning-bolt-circle", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "power_led"), is_on_fn=lambda api, ch: api.status_led_enabled(ch), diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index 7f3c2cbbb72..2ed841a081a 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -43,7 +43,6 @@ NUMBER_ENTITIES = ( key="zoom", cmd_key="GetZoomFocus", translation_key="zoom", - icon="mdi:magnify", mode=NumberMode.SLIDER, native_step=1, get_min_value=lambda api, ch: api.zoom_range(ch)["zoom"]["pos"]["min"], @@ -56,7 +55,6 @@ NUMBER_ENTITIES = ( key="focus", cmd_key="GetZoomFocus", translation_key="focus", - icon="mdi:focus-field", mode=NumberMode.SLIDER, native_step=1, get_min_value=lambda api, ch: api.zoom_range(ch)["focus"]["pos"]["min"], @@ -72,7 +70,6 @@ NUMBER_ENTITIES = ( key="floodlight_brightness", cmd_key="GetWhiteLed", translation_key="floodlight_brightness", - icon="mdi:spotlight-beam", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=1, @@ -85,7 +82,6 @@ NUMBER_ENTITIES = ( key="volume", cmd_key="GetAudioCfg", translation_key="volume", - icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -98,7 +94,6 @@ NUMBER_ENTITIES = ( key="guard_return_time", cmd_key="GetPtzGuard", translation_key="guard_return_time", - icon="mdi:crosshairs-gps", entity_category=EntityCategory.CONFIG, native_step=1, native_unit_of_measurement=UnitOfTime.SECONDS, @@ -112,7 +107,6 @@ NUMBER_ENTITIES = ( key="motion_sensitivity", cmd_key="GetMdAlarm", translation_key="motion_sensitivity", - icon="mdi:motion-sensor", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=1, @@ -125,7 +119,6 @@ NUMBER_ENTITIES = ( key="ai_face_sensititvity", cmd_key="GetAiAlarm", translation_key="ai_face_sensititvity", - icon="mdi:face-recognition", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -140,7 +133,6 @@ NUMBER_ENTITIES = ( key="ai_person_sensititvity", cmd_key="GetAiAlarm", translation_key="ai_person_sensititvity", - icon="mdi:account", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -155,7 +147,6 @@ NUMBER_ENTITIES = ( key="ai_vehicle_sensititvity", cmd_key="GetAiAlarm", translation_key="ai_vehicle_sensititvity", - icon="mdi:car", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -170,7 +161,6 @@ NUMBER_ENTITIES = ( key="ai_package_sensititvity", cmd_key="GetAiAlarm", translation_key="ai_package_sensititvity", - icon="mdi:gift-outline", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -185,7 +175,6 @@ NUMBER_ENTITIES = ( key="ai_pet_sensititvity", cmd_key="GetAiAlarm", translation_key="ai_pet_sensititvity", - icon="mdi:dog-side", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -202,7 +191,6 @@ NUMBER_ENTITIES = ( key="ai_pet_sensititvity", cmd_key="GetAiAlarm", translation_key="ai_animal_sensititvity", - icon="mdi:paw", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -217,7 +205,6 @@ NUMBER_ENTITIES = ( key="ai_face_delay", cmd_key="GetAiAlarm", translation_key="ai_face_delay", - icon="mdi:face-recognition", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -234,7 +221,6 @@ NUMBER_ENTITIES = ( key="ai_person_delay", cmd_key="GetAiAlarm", translation_key="ai_person_delay", - icon="mdi:account", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -251,7 +237,6 @@ NUMBER_ENTITIES = ( key="ai_vehicle_delay", cmd_key="GetAiAlarm", translation_key="ai_vehicle_delay", - icon="mdi:car", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -268,7 +253,6 @@ NUMBER_ENTITIES = ( key="ai_package_delay", cmd_key="GetAiAlarm", translation_key="ai_package_delay", - icon="mdi:gift-outline", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -285,7 +269,6 @@ NUMBER_ENTITIES = ( key="ai_pet_delay", cmd_key="GetAiAlarm", translation_key="ai_pet_delay", - icon="mdi:dog-side", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -304,7 +287,6 @@ NUMBER_ENTITIES = ( key="ai_pet_delay", cmd_key="GetAiAlarm", translation_key="ai_animal_delay", - icon="mdi:paw", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -321,7 +303,6 @@ NUMBER_ENTITIES = ( key="auto_quick_reply_time", cmd_key="GetAutoReply", translation_key="auto_quick_reply_time", - icon="mdi:message-reply-text-outline", entity_category=EntityCategory.CONFIG, native_step=1, native_unit_of_measurement=UnitOfTime.SECONDS, @@ -335,7 +316,6 @@ NUMBER_ENTITIES = ( key="auto_track_limit_left", cmd_key="GetPtzTraceSection", translation_key="auto_track_limit_left", - icon="mdi:angle-acute", mode=NumberMode.SLIDER, entity_category=EntityCategory.CONFIG, native_step=1, @@ -349,7 +329,6 @@ NUMBER_ENTITIES = ( key="auto_track_limit_right", cmd_key="GetPtzTraceSection", translation_key="auto_track_limit_right", - icon="mdi:angle-acute", mode=NumberMode.SLIDER, entity_category=EntityCategory.CONFIG, native_step=1, @@ -363,7 +342,6 @@ NUMBER_ENTITIES = ( key="auto_track_disappear_time", cmd_key="GetAiCfg", translation_key="auto_track_disappear_time", - icon="mdi:target-account", entity_category=EntityCategory.CONFIG, native_step=1, native_unit_of_measurement=UnitOfTime.SECONDS, @@ -379,7 +357,6 @@ NUMBER_ENTITIES = ( key="auto_track_stop_time", cmd_key="GetAiCfg", translation_key="auto_track_stop_time", - icon="mdi:target-account", entity_category=EntityCategory.CONFIG, native_step=1, native_unit_of_measurement=UnitOfTime.SECONDS, @@ -393,7 +370,6 @@ NUMBER_ENTITIES = ( key="day_night_switch_threshold", cmd_key="GetIsp", translation_key="day_night_switch_threshold", - icon="mdi:theme-light-dark", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -407,7 +383,6 @@ NUMBER_ENTITIES = ( key="image_brightness", cmd_key="GetImage", translation_key="image_brightness", - icon="mdi:image-edit", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -421,7 +396,6 @@ NUMBER_ENTITIES = ( key="image_contrast", cmd_key="GetImage", translation_key="image_contrast", - icon="mdi:image-edit", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -435,7 +409,6 @@ NUMBER_ENTITIES = ( key="image_saturation", cmd_key="GetImage", translation_key="image_saturation", - icon="mdi:image-edit", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -449,7 +422,6 @@ NUMBER_ENTITIES = ( key="image_sharpness", cmd_key="GetImage", translation_key="image_sharpness", - icon="mdi:image-edit", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, @@ -463,7 +435,6 @@ NUMBER_ENTITIES = ( key="image_hue", cmd_key="GetImage", translation_key="image_hue", - icon="mdi:image-edit", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, native_step=1, diff --git a/homeassistant/components/reolink/select.py b/homeassistant/components/reolink/select.py index 5be0bdd6c26..24c149d2dee 100644 --- a/homeassistant/components/reolink/select.py +++ b/homeassistant/components/reolink/select.py @@ -51,7 +51,6 @@ SELECT_ENTITIES = ( key="floodlight_mode", cmd_key="GetWhiteLed", translation_key="floodlight_mode", - icon="mdi:spotlight-beam", entity_category=EntityCategory.CONFIG, get_options=lambda api, ch: api.whiteled_mode_list(ch), supported=lambda api, ch: api.supported(ch, "floodLight"), @@ -62,7 +61,6 @@ SELECT_ENTITIES = ( key="day_night_mode", cmd_key="GetIsp", translation_key="day_night_mode", - icon="mdi:theme-light-dark", entity_category=EntityCategory.CONFIG, get_options=[mode.name for mode in DayNightEnum], supported=lambda api, ch: api.supported(ch, "dayNight"), @@ -72,7 +70,6 @@ SELECT_ENTITIES = ( ReolinkSelectEntityDescription( key="ptz_preset", translation_key="ptz_preset", - icon="mdi:pan", get_options=lambda api, ch: list(api.ptz_presets(ch)), supported=lambda api, ch: api.supported(ch, "ptz_presets"), method=lambda api, ch, name: api.set_ptz_command(ch, preset=name), @@ -80,7 +77,6 @@ SELECT_ENTITIES = ( ReolinkSelectEntityDescription( key="play_quick_reply_message", translation_key="play_quick_reply_message", - icon="mdi:message-reply-text-outline", get_options=lambda api, ch: list(api.quick_reply_dict(ch).values())[1:], supported=lambda api, ch: api.supported(ch, "play_quick_reply"), method=lambda api, ch, mess: ( @@ -91,7 +87,6 @@ SELECT_ENTITIES = ( key="auto_quick_reply_message", cmd_key="GetAutoReply", translation_key="auto_quick_reply_message", - icon="mdi:message-reply-text-outline", entity_category=EntityCategory.CONFIG, get_options=lambda api, ch: list(api.quick_reply_dict(ch).values()), supported=lambda api, ch: api.supported(ch, "quick_reply"), @@ -104,7 +99,6 @@ SELECT_ENTITIES = ( key="auto_track_method", cmd_key="GetAiCfg", translation_key="auto_track_method", - icon="mdi:target-account", entity_category=EntityCategory.CONFIG, get_options=[method.name for method in TrackMethodEnum], supported=lambda api, ch: api.supported(ch, "auto_track_method"), @@ -115,7 +109,6 @@ SELECT_ENTITIES = ( key="status_led", cmd_key="GetPowerLed", translation_key="status_led", - icon="mdi:lightning-bolt-circle", entity_category=EntityCategory.CONFIG, get_options=[state.name for state in StatusLedEnum], supported=lambda api, ch: api.supported(ch, "doorbell_led"), diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py index 6f4af489fe5..6706e49fcf4 100644 --- a/homeassistant/components/reolink/sensor.py +++ b/homeassistant/components/reolink/sensor.py @@ -54,7 +54,6 @@ SENSORS = ( key="ptz_pan_position", cmd_key="GetPtzCurPos", translation_key="ptz_pan_position", - icon="mdi:pan", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, value=lambda api, ch: api.ptz_pan_position(ch), @@ -67,7 +66,6 @@ HOST_SENSORS = ( key="wifi_signal", cmd_key="GetWifiSignal", translation_key="wifi_signal", - icon="mdi:wifi", state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, diff --git a/homeassistant/components/reolink/siren.py b/homeassistant/components/reolink/siren.py index 90590acb4e4..e174be0304f 100644 --- a/homeassistant/components/reolink/siren.py +++ b/homeassistant/components/reolink/siren.py @@ -34,7 +34,6 @@ SIREN_ENTITIES = ( ReolinkSirenEntityDescription( key="siren", translation_key="siren", - icon="mdi:alarm-light", supported=lambda api, ch: api.supported(ch, "siren_play"), ), ) diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index 73f4cbc9aa4..4d09e4ce490 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -52,7 +52,6 @@ SWITCH_ENTITIES = ( key="ir_lights", cmd_key="GetIrLights", translation_key="ir_lights", - icon="mdi:led-off", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "ir_lights"), value=lambda api, ch: api.ir_enabled(ch), @@ -62,7 +61,6 @@ SWITCH_ENTITIES = ( key="record_audio", cmd_key="GetEnc", translation_key="record_audio", - icon="mdi:microphone", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "audio"), value=lambda api, ch: api.audio_record(ch), @@ -72,7 +70,6 @@ SWITCH_ENTITIES = ( key="siren_on_event", cmd_key="GetAudioAlarm", translation_key="siren_on_event", - icon="mdi:alarm-light", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "siren"), value=lambda api, ch: api.audio_alarm_enabled(ch), @@ -82,7 +79,6 @@ SWITCH_ENTITIES = ( key="auto_tracking", cmd_key="GetAiCfg", translation_key="auto_tracking", - icon="mdi:target-account", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "auto_track"), value=lambda api, ch: api.auto_track_enabled(ch), @@ -92,7 +88,6 @@ SWITCH_ENTITIES = ( key="auto_focus", cmd_key="GetAutoFocus", translation_key="auto_focus", - icon="mdi:focus-field", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "auto_focus"), value=lambda api, ch: api.autofocus_enabled(ch), @@ -102,7 +97,6 @@ SWITCH_ENTITIES = ( key="gaurd_return", cmd_key="GetPtzGuard", translation_key="gaurd_return", - icon="mdi:crosshairs-gps", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "ptz_guard"), value=lambda api, ch: api.ptz_guard_enabled(ch), @@ -111,7 +105,6 @@ SWITCH_ENTITIES = ( ReolinkSwitchEntityDescription( key="ptz_patrol", translation_key="ptz_patrol", - icon="mdi:map-marker-path", supported=lambda api, ch: api.supported(ch, "ptz_patrol"), value=lambda api, ch: None, method=lambda api, ch, value: api.ctrl_ptz_patrol(ch, value), @@ -120,7 +113,6 @@ SWITCH_ENTITIES = ( key="email", cmd_key="GetEmail", translation_key="email", - icon="mdi:email", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "email") and api.is_nvr, value=lambda api, ch: api.email_enabled(ch), @@ -130,7 +122,6 @@ SWITCH_ENTITIES = ( key="ftp_upload", cmd_key="GetFtp", translation_key="ftp_upload", - icon="mdi:swap-horizontal", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "ftp") and api.is_nvr, value=lambda api, ch: api.ftp_enabled(ch), @@ -140,7 +131,6 @@ SWITCH_ENTITIES = ( key="push_notifications", cmd_key="GetPush", translation_key="push_notifications", - icon="mdi:message-badge", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "push") and api.is_nvr, value=lambda api, ch: api.push_enabled(ch), @@ -150,7 +140,6 @@ SWITCH_ENTITIES = ( key="record", cmd_key="GetRec", translation_key="record", - icon="mdi:record-rec", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "recording") and api.is_nvr, value=lambda api, ch: api.recording_enabled(ch), @@ -160,7 +149,6 @@ SWITCH_ENTITIES = ( key="buzzer", cmd_key="GetBuzzerAlarmV20", translation_key="buzzer", - icon="mdi:room-service", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "buzzer") and api.is_nvr, value=lambda api, ch: api.buzzer_enabled(ch), @@ -170,7 +158,6 @@ SWITCH_ENTITIES = ( key="doorbell_button_sound", cmd_key="GetAudioCfg", translation_key="doorbell_button_sound", - icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "doorbell_button_sound"), value=lambda api, ch: api.doorbell_button_sound(ch), @@ -180,7 +167,6 @@ SWITCH_ENTITIES = ( key="hdr", cmd_key="GetIsp", translation_key="hdr", - icon="mdi:hdr", entity_category=EntityCategory.CONFIG, entity_registry_enabled_default=False, supported=lambda api, ch: api.supported(ch, "HDR"), @@ -194,7 +180,6 @@ NVR_SWITCH_ENTITIES = ( key="email", cmd_key="GetEmail", translation_key="email", - icon="mdi:email", entity_category=EntityCategory.CONFIG, supported=lambda api: api.supported(None, "email"), value=lambda api: api.email_enabled(), @@ -204,7 +189,6 @@ NVR_SWITCH_ENTITIES = ( key="ftp_upload", cmd_key="GetFtp", translation_key="ftp_upload", - icon="mdi:swap-horizontal", entity_category=EntityCategory.CONFIG, supported=lambda api: api.supported(None, "ftp"), value=lambda api: api.ftp_enabled(), @@ -214,7 +198,6 @@ NVR_SWITCH_ENTITIES = ( key="push_notifications", cmd_key="GetPush", translation_key="push_notifications", - icon="mdi:message-badge", entity_category=EntityCategory.CONFIG, supported=lambda api: api.supported(None, "push"), value=lambda api: api.push_enabled(), @@ -224,7 +207,6 @@ NVR_SWITCH_ENTITIES = ( key="record", cmd_key="GetRec", translation_key="record", - icon="mdi:record-rec", entity_category=EntityCategory.CONFIG, supported=lambda api: api.supported(None, "recording"), value=lambda api: api.recording_enabled(), From 8c2c3e08393a4023cd68510f8ba5cb41029e038c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 6 Mar 2024 11:05:06 +0100 Subject: [PATCH 0450/1691] Improve integration_entities template function (#111943) --- homeassistant/helpers/template.py | 30 +++++++++++++++++------------- tests/helpers/test_template.py | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 426ff3bed6f..287e8b065b9 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1286,19 +1286,23 @@ def integration_entities(hass: HomeAssistant, entry_name: str) -> Iterable[str]: or provide a config entry title for filtering between instances of the same integration. """ - # first try if this is a config entry match - conf_entry = next( - ( - entry.entry_id - for entry in hass.config_entries.async_entries() - if entry.title == entry_name - ), - None, - ) - if conf_entry is not None: - ent_reg = entity_registry.async_get(hass) - entries = entity_registry.async_entries_for_config_entry(ent_reg, conf_entry) - return [entry.entity_id for entry in entries] + + # Don't allow searching for config entries without title + if not entry_name: + return [] + + # first try if there are any config entries with a matching title + entities: list[str] = [] + ent_reg = entity_registry.async_get(hass) + for entry in hass.config_entries.async_entries(): + if entry.title != entry_name: + continue + entries = entity_registry.async_entries_for_config_entry( + ent_reg, entry.entry_id + ) + entities.extend(entry.entity_id for entry in entries) + if entities: + return entities # fallback to just returning all entities for a domain # pylint: disable-next=import-outside-toplevel diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 6279ea0089a..6ca93e79d5f 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -3280,6 +3280,16 @@ async def test_integration_entities( hass: HomeAssistant, entity_registry: er.EntityRegistry ) -> None: """Test integration_entities function.""" + # test entities for untitled config entry + config_entry = MockConfigEntry(domain="mock", title="") + config_entry.add_to_hass(hass) + entity_registry.async_get_or_create( + "sensor", "mock", "untitled", config_entry=config_entry + ) + info = render_to_info(hass, "{{ integration_entities('') }}") + assert_result_info(info, []) + assert info.rate_limit is None + # test entities for given config entry title config_entry = MockConfigEntry(domain="mock", title="Mock bridge 2") config_entry.add_to_hass(hass) @@ -3290,6 +3300,23 @@ async def test_integration_entities( assert_result_info(info, [entity_entry.entity_id]) assert info.rate_limit is None + # test entities for given non unique config entry title + config_entry = MockConfigEntry(domain="mock", title="Not unique") + config_entry.add_to_hass(hass) + entity_entry_not_unique_1 = entity_registry.async_get_or_create( + "sensor", "mock", "not_unique_1", config_entry=config_entry + ) + config_entry = MockConfigEntry(domain="mock", title="Not unique") + config_entry.add_to_hass(hass) + entity_entry_not_unique_2 = entity_registry.async_get_or_create( + "sensor", "mock", "not_unique_2", config_entry=config_entry + ) + info = render_to_info(hass, "{{ integration_entities('Not unique') }}") + assert_result_info( + info, [entity_entry_not_unique_1.entity_id, entity_entry_not_unique_2.entity_id] + ) + assert info.rate_limit is None + # test integration entities not in entity registry mock_entity = entity.Entity() mock_entity.hass = hass From 0a11cb538225cad3ca147a8de6121077fdd816c1 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:25:56 +0100 Subject: [PATCH 0451/1691] Avoid errors when there is no internet connection in Husqvarna Automower (#111101) * Avoid errors when no internet connection * Add error * Create task in HA * change from matter to automower * tests * Update homeassistant/components/husqvarna_automower/coordinator.py Co-authored-by: Martin Hjelmare * address review * Make websocket optional * fix aioautomower version * Fix tests * Use stored websocket * reset reconnect time after sucessful connection * Typo * Remove comment * Add test * Address review --------- Co-authored-by: Martin Hjelmare --- .../husqvarna_automower/__init__.py | 15 +++--- .../husqvarna_automower/coordinator.py | 49 +++++++++++++++---- .../husqvarna_automower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../husqvarna_automower/conftest.py | 8 +++ .../husqvarna_automower/test_init.py | 44 ++++++++++++++++- 7 files changed, 101 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index 7ed8a6b23e8..20218229385 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -6,7 +6,7 @@ from aioautomower.session import AutomowerSession from aiohttp import ClientError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow @@ -17,7 +17,6 @@ from .coordinator import AutomowerDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) - PLATFORMS: list[Platform] = [Platform.LAWN_MOWER, Platform.SENSOR, Platform.SWITCH] @@ -38,13 +37,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await api_api.async_get_access_token() except ClientError as err: raise ConfigEntryNotReady from err - coordinator = AutomowerDataUpdateCoordinator(hass, automower_api) + coordinator = AutomowerDataUpdateCoordinator(hass, automower_api, entry) await coordinator.async_config_entry_first_refresh() + entry.async_create_background_task( + hass, + coordinator.client_listen(hass, entry, automower_api), + "websocket_task", + ) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.shutdown) - ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -52,8 +53,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle unload of an entry.""" - coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - await coordinator.shutdown() unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/husqvarna_automower/coordinator.py b/homeassistant/components/husqvarna_automower/coordinator.py index 70d69f90549..2840823415a 100644 --- a/homeassistant/components/husqvarna_automower/coordinator.py +++ b/homeassistant/components/husqvarna_automower/coordinator.py @@ -1,23 +1,28 @@ """Data UpdateCoordinator for the Husqvarna Automower integration.""" +import asyncio from datetime import timedelta import logging -from typing import Any +from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError from aioautomower.model import MowerAttributes +from aioautomower.session import AutomowerSession +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .api import AsyncConfigEntryAuth from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +MAX_WS_RECONNECT_TIME = 600 class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttributes]]): """Class to manage fetching Husqvarna data.""" - def __init__(self, hass: HomeAssistant, api: AsyncConfigEntryAuth) -> None: + def __init__( + self, hass: HomeAssistant, api: AutomowerSession, entry: ConfigEntry + ) -> None: """Initialize data updater.""" super().__init__( hass, @@ -35,13 +40,39 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttrib await self.api.connect() self.api.register_data_callback(self.callback) self.ws_connected = True - return await self.api.get_status() - - async def shutdown(self, *_: Any) -> None: - """Close resources.""" - await self.api.close() + try: + return await self.api.get_status() + except ApiException as err: + raise UpdateFailed(err) from err @callback def callback(self, ws_data: dict[str, MowerAttributes]) -> None: """Process websocket callbacks and write them to the DataUpdateCoordinator.""" self.async_set_updated_data(ws_data) + + async def client_listen( + self, + hass: HomeAssistant, + entry: ConfigEntry, + automower_client: AutomowerSession, + reconnect_time: int = 2, + ) -> None: + """Listen with the client.""" + try: + await automower_client.auth.websocket_connect() + reconnect_time = 2 + await automower_client.start_listening() + except HusqvarnaWSServerHandshakeError as err: + _LOGGER.debug( + "Failed to connect to websocket. Trying to reconnect: %s", err + ) + + if not hass.is_stopping: + await asyncio.sleep(reconnect_time) + reconnect_time = min(reconnect_time * 2, MAX_WS_RECONNECT_TIME) + await self.client_listen( + hass=hass, + entry=entry, + automower_client=automower_client, + reconnect_time=reconnect_time, + ) diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json index 1eb40bfad33..dc40116f31e 100644 --- a/homeassistant/components/husqvarna_automower/manifest.json +++ b/homeassistant/components/husqvarna_automower/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "iot_class": "cloud_push", - "requirements": ["aioautomower==2024.2.7"] + "requirements": ["aioautomower==2024.2.10"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0ef673aa9bd..1be2107cc08 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -206,7 +206,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.2.7 +aioautomower==2024.2.10 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a25035620b7..e1ff81a97dc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -185,7 +185,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.2.7 +aioautomower==2024.2.10 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/tests/components/husqvarna_automower/conftest.py b/tests/components/husqvarna_automower/conftest.py index 89c0133cd0b..3194f1b3188 100644 --- a/tests/components/husqvarna_automower/conftest.py +++ b/tests/components/husqvarna_automower/conftest.py @@ -4,6 +4,7 @@ import time from unittest.mock import AsyncMock, patch from aioautomower.utils import mower_list_to_dictionary_dataclass +from aiohttp import ClientWebSocketResponse import pytest from homeassistant.components.application_credentials import ( @@ -82,4 +83,11 @@ def mock_automower_client() -> Generator[AsyncMock, None, None]: client.get_status.return_value = mower_list_to_dictionary_dataclass( load_json_value_fixture("mower.json", DOMAIN) ) + + async def websocket_connect() -> ClientWebSocketResponse: + """Mock listen.""" + return ClientWebSocketResponse + + client.auth = AsyncMock(side_effect=websocket_connect) + yield client diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index 14460ad5d21..c11e4ac4cc7 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -1,8 +1,11 @@ """Tests for init module.""" +from datetime import timedelta import http import time from unittest.mock import AsyncMock +from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.components.husqvarna_automower.const import DOMAIN, OAUTH2_TOKEN @@ -11,7 +14,7 @@ from homeassistant.core import HomeAssistant from . import setup_integration -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed from tests.test_util.aiohttp import AiohttpClientMocker @@ -66,3 +69,42 @@ async def test_expired_token_refresh_failure( await setup_integration(hass, mock_config_entry) assert mock_config_entry.state is expected_state + + +async def test_update_failed( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test load and unload entry.""" + getattr(mock_automower_client, "get_status").side_effect = ApiException( + "Test error" + ) + await setup_integration(hass, mock_config_entry) + entry = hass.config_entries.async_entries(DOMAIN)[0] + + assert entry.state == ConfigEntryState.SETUP_RETRY + + +async def test_websocket_not_available( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, +) -> None: + """Test trying reload the websocket.""" + mock_automower_client.start_listening.side_effect = HusqvarnaWSServerHandshakeError( + "Boom" + ) + await setup_integration(hass, mock_config_entry) + assert "Failed to connect to websocket. Trying to reconnect: Boom" in caplog.text + assert mock_automower_client.auth.websocket_connect.call_count == 1 + assert mock_automower_client.start_listening.call_count == 1 + assert mock_config_entry.state == ConfigEntryState.LOADED + freezer.tick(timedelta(seconds=2)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert mock_automower_client.auth.websocket_connect.call_count == 2 + assert mock_automower_client.start_listening.call_count == 2 + assert mock_config_entry.state == ConfigEntryState.LOADED From 4d6a910e645da17bcd59c8ef9d185dab4112747d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 11:33:02 +0100 Subject: [PATCH 0452/1691] Remove constructor in WLED Options flow (#112456) --- homeassistant/components/wled/config_flow.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index f6da1f73bb2..2c8edf4066c 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.core import callback @@ -121,13 +121,9 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): return await wled.update() -class WLEDOptionsFlowHandler(OptionsFlow): +class WLEDOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle WLED options.""" - def __init__(self, config_entry: ConfigEntry) -> None: - """Initialize WLED options flow.""" - self.config_entry = config_entry - async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: @@ -141,7 +137,7 @@ class WLEDOptionsFlowHandler(OptionsFlow): { vol.Optional( CONF_KEEP_MAIN_LIGHT, - default=self.config_entry.options.get( + default=self.options.get( CONF_KEEP_MAIN_LIGHT, DEFAULT_KEEP_MAIN_LIGHT ), ): bool, From 2c42517ac4fc69abfc6e4071530aa8cce778fd15 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 11:37:05 +0100 Subject: [PATCH 0453/1691] Add icon translations to Tailwind (#112303) --- homeassistant/components/tailwind/binary_sensor.py | 1 - homeassistant/components/tailwind/icons.json | 14 ++++++++++++++ homeassistant/components/tailwind/number.py | 1 - .../tailwind/snapshots/test_binary_sensor.ambr | 6 ++---- .../components/tailwind/snapshots/test_number.ambr | 3 +-- 5 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/tailwind/icons.json diff --git a/homeassistant/components/tailwind/binary_sensor.py b/homeassistant/components/tailwind/binary_sensor.py index eaa0cbd1a08..75262427d9f 100644 --- a/homeassistant/components/tailwind/binary_sensor.py +++ b/homeassistant/components/tailwind/binary_sensor.py @@ -34,7 +34,6 @@ DESCRIPTIONS: tuple[TailwindDoorBinarySensorEntityDescription, ...] = ( translation_key="operational_problem", entity_category=EntityCategory.DIAGNOSTIC, device_class=BinarySensorDeviceClass.PROBLEM, - icon="mdi:garage-alert", is_on_fn=lambda door: door.locked_out, ), ) diff --git a/homeassistant/components/tailwind/icons.json b/homeassistant/components/tailwind/icons.json new file mode 100644 index 00000000000..ed2c6af16b3 --- /dev/null +++ b/homeassistant/components/tailwind/icons.json @@ -0,0 +1,14 @@ +{ + "entity": { + "binary_sensor": { + "operational_problem": { + "default": "mdi:garage-alert" + } + }, + "number": { + "brightness": { + "default": "mdi:led-on" + } + } + } +} diff --git a/homeassistant/components/tailwind/number.py b/homeassistant/components/tailwind/number.py index 5853e5c2d30..b44e98b27f5 100644 --- a/homeassistant/components/tailwind/number.py +++ b/homeassistant/components/tailwind/number.py @@ -30,7 +30,6 @@ class TailwindNumberEntityDescription(NumberEntityDescription): DESCRIPTIONS = [ TailwindNumberEntityDescription( key="brightness", - icon="mdi:led-on", translation_key="brightness", entity_category=EntityCategory.CONFIG, native_step=1, diff --git a/tests/components/tailwind/snapshots/test_binary_sensor.ambr b/tests/components/tailwind/snapshots/test_binary_sensor.ambr index 3f6f2baf26b..6b09714a27e 100644 --- a/tests/components/tailwind/snapshots/test_binary_sensor.ambr +++ b/tests/components/tailwind/snapshots/test_binary_sensor.ambr @@ -4,7 +4,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'problem', 'friendly_name': 'Door 1 Operational problem', - 'icon': 'mdi:garage-alert', }), 'context': , 'entity_id': 'binary_sensor.door_1_operational_problem', @@ -36,7 +35,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:garage-alert', + 'original_icon': None, 'original_name': 'Operational problem', 'platform': 'tailwind', 'previous_unique_id': None, @@ -81,7 +80,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'problem', 'friendly_name': 'Door 2 Operational problem', - 'icon': 'mdi:garage-alert', }), 'context': , 'entity_id': 'binary_sensor.door_2_operational_problem', @@ -113,7 +111,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:garage-alert', + 'original_icon': None, 'original_name': 'Operational problem', 'platform': 'tailwind', 'previous_unique_id': None, diff --git a/tests/components/tailwind/snapshots/test_number.ambr b/tests/components/tailwind/snapshots/test_number.ambr index 95237f82522..e00827ed761 100644 --- a/tests/components/tailwind/snapshots/test_number.ambr +++ b/tests/components/tailwind/snapshots/test_number.ambr @@ -3,7 +3,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Tailwind iQ3 Status LED brightness', - 'icon': 'mdi:led-on', 'max': 100, 'min': 0, 'mode': , @@ -45,7 +44,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:led-on', + 'original_icon': None, 'original_name': 'Status LED brightness', 'platform': 'tailwind', 'previous_unique_id': None, From c8f340966ee6e43fb1a32c2626034b2c9641aac3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 11:49:36 +0100 Subject: [PATCH 0454/1691] Add icon translations to Elgato (#111423) --- homeassistant/components/elgato/icons.json | 15 +++++++++++++++ homeassistant/components/elgato/switch.py | 2 -- .../components/elgato/snapshots/test_switch.ambr | 6 ++---- 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/elgato/icons.json diff --git a/homeassistant/components/elgato/icons.json b/homeassistant/components/elgato/icons.json new file mode 100644 index 00000000000..1b5eaf3763a --- /dev/null +++ b/homeassistant/components/elgato/icons.json @@ -0,0 +1,15 @@ +{ + "entity": { + "switch": { + "bypass": { + "default": "mdi:battery-off-outline" + }, + "energy_saving": { + "default": "mdi:leaf" + } + } + }, + "services": { + "identify": "mdi:crosshairs-question" + } +} diff --git a/homeassistant/components/elgato/switch.py b/homeassistant/components/elgato/switch.py index d1f370547a4..1396d74e0ce 100644 --- a/homeassistant/components/elgato/switch.py +++ b/homeassistant/components/elgato/switch.py @@ -32,7 +32,6 @@ SWITCHES = [ ElgatoSwitchEntityDescription( key="bypass", translation_key="bypass", - icon="mdi:battery-off-outline", entity_category=EntityCategory.CONFIG, has_fn=lambda x: x.battery is not None, is_on_fn=lambda x: x.settings.battery.bypass if x.settings.battery else None, @@ -41,7 +40,6 @@ SWITCHES = [ ElgatoSwitchEntityDescription( key="energy_saving", translation_key="energy_saving", - icon="mdi:leaf", entity_category=EntityCategory.CONFIG, has_fn=lambda x: x.battery is not None, is_on_fn=lambda x: ( diff --git a/tests/components/elgato/snapshots/test_switch.ambr b/tests/components/elgato/snapshots/test_switch.ambr index 962563599f6..3b31b7bca14 100644 --- a/tests/components/elgato/snapshots/test_switch.ambr +++ b/tests/components/elgato/snapshots/test_switch.ambr @@ -3,7 +3,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Frenck Energy saving', - 'icon': 'mdi:leaf', }), 'context': , 'entity_id': 'switch.frenck_energy_saving', @@ -35,7 +34,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:leaf', + 'original_icon': None, 'original_name': 'Energy saving', 'platform': 'elgato', 'previous_unique_id': None, @@ -83,7 +82,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Frenck Studio mode', - 'icon': 'mdi:battery-off-outline', }), 'context': , 'entity_id': 'switch.frenck_studio_mode', @@ -115,7 +113,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:battery-off-outline', + 'original_icon': None, 'original_name': 'Studio mode', 'platform': 'elgato', 'previous_unique_id': None, From c00426293dcf459abd05f208332e880d6d0473f7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 11:51:23 +0100 Subject: [PATCH 0455/1691] Add icon translations to Twentemilieu (#112327) --- .../components/twentemilieu/calendar.py | 2 +- .../components/twentemilieu/icons.json | 26 +++++++++++++++++++ .../components/twentemilieu/sensor.py | 5 ---- .../twentemilieu/snapshots/test_calendar.ambr | 5 ++-- .../twentemilieu/snapshots/test_sensor.ambr | 15 ++++------- 5 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/twentemilieu/icons.json diff --git a/homeassistant/components/twentemilieu/calendar.py b/homeassistant/components/twentemilieu/calendar.py index f4d1e51b171..e17e61e727a 100644 --- a/homeassistant/components/twentemilieu/calendar.py +++ b/homeassistant/components/twentemilieu/calendar.py @@ -31,8 +31,8 @@ class TwenteMilieuCalendar(TwenteMilieuEntity, CalendarEntity): """Defines a Twente Milieu calendar.""" _attr_has_entity_name = True - _attr_icon = "mdi:delete-empty" _attr_name = None + _attr_translation_key = "calendar" def __init__( self, diff --git a/homeassistant/components/twentemilieu/icons.json b/homeassistant/components/twentemilieu/icons.json new file mode 100644 index 00000000000..b033178fbb8 --- /dev/null +++ b/homeassistant/components/twentemilieu/icons.json @@ -0,0 +1,26 @@ +{ + "entity": { + "calendar": { + "calendar": { + "default": "mdi:delete-empty" + } + }, + "sensor": { + "christmas_tree_pickup": { + "default": "mdi:pine-tree" + }, + "non_recyclable_waste_pickup": { + "default": "mdi:delete-empty" + }, + "organic_waste_pickup": { + "default": "mdi:delete-empty" + }, + "paper_waste_pickup": { + "default": "mdi:delete-empty" + }, + "packages_waste_pickup": { + "default": "mdi:delete-empty" + } + } + } +} diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index 32b4de47de4..f47d4100a6a 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -33,35 +33,30 @@ SENSORS: tuple[TwenteMilieuSensorDescription, ...] = ( key="tree", translation_key="christmas_tree_pickup", waste_type=WasteType.TREE, - icon="mdi:pine-tree", device_class=SensorDeviceClass.DATE, ), TwenteMilieuSensorDescription( key="Non-recyclable", translation_key="non_recyclable_waste_pickup", waste_type=WasteType.NON_RECYCLABLE, - icon="mdi:delete-empty", device_class=SensorDeviceClass.DATE, ), TwenteMilieuSensorDescription( key="Organic", translation_key="organic_waste_pickup", waste_type=WasteType.ORGANIC, - icon="mdi:delete-empty", device_class=SensorDeviceClass.DATE, ), TwenteMilieuSensorDescription( key="Paper", translation_key="paper_waste_pickup", waste_type=WasteType.PAPER, - icon="mdi:delete-empty", device_class=SensorDeviceClass.DATE, ), TwenteMilieuSensorDescription( key="Plastic", translation_key="packages_waste_pickup", waste_type=WasteType.PACKAGES, - icon="mdi:delete-empty", device_class=SensorDeviceClass.DATE, ), ) diff --git a/tests/components/twentemilieu/snapshots/test_calendar.ambr b/tests/components/twentemilieu/snapshots/test_calendar.ambr index 5399e6f547a..d04cec1a1de 100644 --- a/tests/components/twentemilieu/snapshots/test_calendar.ambr +++ b/tests/components/twentemilieu/snapshots/test_calendar.ambr @@ -32,7 +32,6 @@ 'description': '', 'end_time': '2022-01-07 00:00:00', 'friendly_name': 'Twente Milieu', - 'icon': 'mdi:delete-empty', 'location': '', 'message': 'Christmas tree pickup', 'start_time': '2022-01-06 00:00:00', @@ -67,12 +66,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:delete-empty', + 'original_icon': None, 'original_name': None, 'platform': 'twentemilieu', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'calendar', 'unique_id': '12345', 'unit_of_measurement': None, }) diff --git a/tests/components/twentemilieu/snapshots/test_sensor.ambr b/tests/components/twentemilieu/snapshots/test_sensor.ambr index 0a1be9f4455..bedad263251 100644 --- a/tests/components/twentemilieu/snapshots/test_sensor.ambr +++ b/tests/components/twentemilieu/snapshots/test_sensor.ambr @@ -4,7 +4,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'date', 'friendly_name': 'Twente Milieu Christmas tree pickup', - 'icon': 'mdi:pine-tree', }), 'context': , 'entity_id': 'sensor.twente_milieu_christmas_tree_pickup', @@ -36,7 +35,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:pine-tree', + 'original_icon': None, 'original_name': 'Christmas tree pickup', 'platform': 'twentemilieu', 'previous_unique_id': None, @@ -81,7 +80,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'date', 'friendly_name': 'Twente Milieu Non-recyclable waste pickup', - 'icon': 'mdi:delete-empty', }), 'context': , 'entity_id': 'sensor.twente_milieu_non_recyclable_waste_pickup', @@ -113,7 +111,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:delete-empty', + 'original_icon': None, 'original_name': 'Non-recyclable waste pickup', 'platform': 'twentemilieu', 'previous_unique_id': None, @@ -158,7 +156,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'date', 'friendly_name': 'Twente Milieu Organic waste pickup', - 'icon': 'mdi:delete-empty', }), 'context': , 'entity_id': 'sensor.twente_milieu_organic_waste_pickup', @@ -190,7 +187,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:delete-empty', + 'original_icon': None, 'original_name': 'Organic waste pickup', 'platform': 'twentemilieu', 'previous_unique_id': None, @@ -235,7 +232,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'date', 'friendly_name': 'Twente Milieu Packages waste pickup', - 'icon': 'mdi:delete-empty', }), 'context': , 'entity_id': 'sensor.twente_milieu_packages_waste_pickup', @@ -267,7 +263,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:delete-empty', + 'original_icon': None, 'original_name': 'Packages waste pickup', 'platform': 'twentemilieu', 'previous_unique_id': None, @@ -312,7 +308,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'date', 'friendly_name': 'Twente Milieu Paper waste pickup', - 'icon': 'mdi:delete-empty', }), 'context': , 'entity_id': 'sensor.twente_milieu_paper_waste_pickup', @@ -344,7 +339,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:delete-empty', + 'original_icon': None, 'original_name': 'Paper waste pickup', 'platform': 'twentemilieu', 'previous_unique_id': None, From 50a33c5a1864da7dbe772a7bfcf042354f327a82 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:14:21 +0100 Subject: [PATCH 0456/1691] Fix typo in Reolink translation keys (#112489) * Fix typo in Reolink translation keys * Fix typo in Reolink translation keys --- homeassistant/components/reolink/icons.json | 14 +++++++------- homeassistant/components/reolink/number.py | 12 ++++++------ homeassistant/components/reolink/strings.json | 14 +++++++------- homeassistant/components/reolink/switch.py | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/reolink/icons.json b/homeassistant/components/reolink/icons.json index ff2dece95ea..15c3c00ecf8 100644 --- a/homeassistant/components/reolink/icons.json +++ b/homeassistant/components/reolink/icons.json @@ -103,22 +103,22 @@ "motion_sensitivity": { "default": "mdi:motion-sensor" }, - "ai_face_sensititvity": { + "ai_face_sensitivity": { "default": "mdi:face-recognition" }, - "ai_person_sensititvity": { + "ai_person_sensitivity": { "default": "mdi:account" }, - "ai_vehicle_sensititvity": { + "ai_vehicle_sensitivity": { "default": "mdi:car" }, - "ai_package_sensititvity": { + "ai_package_sensitivity": { "default": "mdi:gift-outline" }, - "ai_pet_sensititvity": { + "ai_pet_sensitivity": { "default": "mdi:dog-side" }, - "ai_animal_sensititvity": { + "ai_animal_sensitivity": { "default": "mdi:paw" }, "ai_face_delay": { @@ -225,7 +225,7 @@ "auto_focus": { "default": "mdi:focus-field" }, - "gaurd_return": { + "guard_return": { "default": "mdi:crosshairs-gps" }, "ptz_patrol": { diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index 2ed841a081a..ec6354aa320 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -118,7 +118,7 @@ NUMBER_ENTITIES = ( ReolinkNumberEntityDescription( key="ai_face_sensititvity", cmd_key="GetAiAlarm", - translation_key="ai_face_sensititvity", + translation_key="ai_face_sensitivity", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -132,7 +132,7 @@ NUMBER_ENTITIES = ( ReolinkNumberEntityDescription( key="ai_person_sensititvity", cmd_key="GetAiAlarm", - translation_key="ai_person_sensititvity", + translation_key="ai_person_sensitivity", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -146,7 +146,7 @@ NUMBER_ENTITIES = ( ReolinkNumberEntityDescription( key="ai_vehicle_sensititvity", cmd_key="GetAiAlarm", - translation_key="ai_vehicle_sensititvity", + translation_key="ai_vehicle_sensitivity", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -160,7 +160,7 @@ NUMBER_ENTITIES = ( ReolinkNumberEntityDescription( key="ai_package_sensititvity", cmd_key="GetAiAlarm", - translation_key="ai_package_sensititvity", + translation_key="ai_package_sensitivity", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -174,7 +174,7 @@ NUMBER_ENTITIES = ( ReolinkNumberEntityDescription( key="ai_pet_sensititvity", cmd_key="GetAiAlarm", - translation_key="ai_pet_sensititvity", + translation_key="ai_pet_sensitivity", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -190,7 +190,7 @@ NUMBER_ENTITIES = ( ReolinkNumberEntityDescription( key="ai_pet_sensititvity", cmd_key="GetAiAlarm", - translation_key="ai_animal_sensititvity", + translation_key="ai_animal_sensitivity", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index d8505e7edb9..b795ba16f64 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -270,22 +270,22 @@ "motion_sensitivity": { "name": "Motion sensitivity" }, - "ai_face_sensititvity": { + "ai_face_sensitivity": { "name": "AI face sensitivity" }, - "ai_person_sensititvity": { + "ai_person_sensitivity": { "name": "AI person sensitivity" }, - "ai_vehicle_sensititvity": { + "ai_vehicle_sensitivity": { "name": "AI vehicle sensitivity" }, - "ai_package_sensititvity": { + "ai_package_sensitivity": { "name": "AI package sensitivity" }, - "ai_pet_sensititvity": { + "ai_pet_sensitivity": { "name": "AI pet sensitivity" }, - "ai_animal_sensititvity": { + "ai_animal_sensitivity": { "name": "AI animal sensitivity" }, "ai_face_delay": { @@ -419,7 +419,7 @@ "auto_focus": { "name": "Auto focus" }, - "gaurd_return": { + "guard_return": { "name": "Guard return" }, "ptz_patrol": { diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index 4d09e4ce490..3c429185556 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -96,7 +96,7 @@ SWITCH_ENTITIES = ( ReolinkSwitchEntityDescription( key="gaurd_return", cmd_key="GetPtzGuard", - translation_key="gaurd_return", + translation_key="guard_return", entity_category=EntityCategory.CONFIG, supported=lambda api, ch: api.supported(ch, "ptz_guard"), value=lambda api, ch: api.ptz_guard_enabled(ch), From 3e7d42a83b16984e8efc71af2de2ffeb116dd8ff Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 6 Mar 2024 12:16:29 +0100 Subject: [PATCH 0457/1691] Update frontend to 20240306.0 (#112492) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 0606312aaea..cea376fa8ff 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240304.0"] + "requirements": ["home-assistant-frontend==20240306.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 030aafc4df7..b2563831301 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240304.0 +home-assistant-frontend==20240306.0 home-assistant-intents==2024.2.28 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 1be2107cc08..5e30dabff84 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ hole==0.8.0 holidays==0.44 # homeassistant.components.frontend -home-assistant-frontend==20240304.0 +home-assistant-frontend==20240306.0 # homeassistant.components.conversation home-assistant-intents==2024.2.28 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e1ff81a97dc..9f7aaf1ab55 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ hole==0.8.0 holidays==0.44 # homeassistant.components.frontend -home-assistant-frontend==20240304.0 +home-assistant-frontend==20240306.0 # homeassistant.components.conversation home-assistant-intents==2024.2.28 From e041c3aa0f724311b0a5a83c1c0fa88acd2b4f72 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:17:35 +0100 Subject: [PATCH 0458/1691] Remove entity description mixin in EnOcean (#112483) Remove entity description mixin in Enocean --- homeassistant/components/enocean/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index e4dd9c7cfb1..01847769a73 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -44,20 +44,13 @@ SENSOR_TYPE_TEMPERATURE = "temperature" SENSOR_TYPE_WINDOWHANDLE = "windowhandle" -@dataclass(frozen=True) -class EnOceanSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnOceanSensorEntityDescription(SensorEntityDescription): + """Describes EnOcean sensor entity.""" unique_id: Callable[[list[int]], str | None] -@dataclass(frozen=True) -class EnOceanSensorEntityDescription( - SensorEntityDescription, EnOceanSensorEntityDescriptionMixin -): - """Describes EnOcean sensor entity.""" - - SENSOR_DESC_TEMPERATURE = EnOceanSensorEntityDescription( key=SENSOR_TYPE_TEMPERATURE, name="Temperature", From 4a22415c2d112de933af72e02c6cc60c273da497 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:26:37 +0100 Subject: [PATCH 0459/1691] Add icon translations to Zone (#112370) --- homeassistant/components/zone/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/zone/icons.json diff --git a/homeassistant/components/zone/icons.json b/homeassistant/components/zone/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/zone/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 1cc58f217b9d60ca98183db391fbd1d91d019b22 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:26:45 +0100 Subject: [PATCH 0460/1691] Add icon translations to Zodiac (#112369) --- homeassistant/components/zodiac/icons.json | 22 ++++++++++++++++++++++ homeassistant/components/zodiac/sensor.py | 16 ---------------- 2 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/zodiac/icons.json diff --git a/homeassistant/components/zodiac/icons.json b/homeassistant/components/zodiac/icons.json new file mode 100644 index 00000000000..2bb2f614f85 --- /dev/null +++ b/homeassistant/components/zodiac/icons.json @@ -0,0 +1,22 @@ +{ + "entity": { + "sensor": { + "sign": { + "default": "mdi:zodiac-aries", + "state": { + "taurus": "mdi:zodiac-taurus", + "gemini": "mdi:zodiac-gemini", + "cancer": "mdi:zodiac-cancer", + "leo": "mdi:zodiac-leo", + "virgo": "mdi:zodiac-virgo", + "libra": "mdi:zodiac-libra", + "scorpio": "mdi:zodiac-scorpio", + "sagittarius": "mdi:zodiac-sagittarius", + "capricorn": "mdi:zodiac-capricorn", + "aquarius": "mdi:zodiac-aquarius", + "pisces": "mdi:zodiac-pisces" + } + } + } + } +} diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py index 2e79f3804ab..71d995bb73e 100644 --- a/homeassistant/components/zodiac/sensor.py +++ b/homeassistant/components/zodiac/sensor.py @@ -145,21 +145,6 @@ ZODIAC_BY_DATE = ( ), ) -ZODIAC_ICONS = { - SIGN_ARIES: "mdi:zodiac-aries", - SIGN_TAURUS: "mdi:zodiac-taurus", - SIGN_GEMINI: "mdi:zodiac-gemini", - SIGN_CANCER: "mdi:zodiac-cancer", - SIGN_LEO: "mdi:zodiac-leo", - SIGN_VIRGO: "mdi:zodiac-virgo", - SIGN_LIBRA: "mdi:zodiac-libra", - SIGN_SCORPIO: "mdi:zodiac-scorpio", - SIGN_SAGITTARIUS: "mdi:zodiac-sagittarius", - SIGN_CAPRICORN: "mdi:zodiac-capricorn", - SIGN_AQUARIUS: "mdi:zodiac-aquarius", - SIGN_PISCES: "mdi:zodiac-pisces", -} - async def async_setup_entry( hass: HomeAssistant, @@ -211,6 +196,5 @@ class ZodiacSensor(SensorEntity): today.month == sign[1][1] and today.day <= sign[1][0] ): self._attr_native_value = sign[2] - self._attr_icon = ZODIAC_ICONS.get(sign[2]) self._attr_extra_state_attributes = sign[3] break From 66fae7ff9dd40bac7c7b721b406a5b9507974716 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:27:05 +0100 Subject: [PATCH 0461/1691] Add icon translations to Wilight (#112357) --- homeassistant/components/wilight/fan.py | 1 - homeassistant/components/wilight/icons.json | 17 +++++++++++++++++ homeassistant/components/wilight/switch.py | 6 ------ 3 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/wilight/icons.json diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index ba9a108f636..4c34bd16bd0 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -55,7 +55,6 @@ class WiLightFan(WiLightDevice, FanEntity): """Representation of a WiLights fan.""" _attr_name = None - _attr_icon = "mdi:fan" _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) _attr_supported_features = FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION diff --git a/homeassistant/components/wilight/icons.json b/homeassistant/components/wilight/icons.json new file mode 100644 index 00000000000..3c5d0112de1 --- /dev/null +++ b/homeassistant/components/wilight/icons.json @@ -0,0 +1,17 @@ +{ + "entity": { + "switch": { + "watering": { + "default": "mdi:water" + }, + "pause": { + "default": "mdi:pause-circle-outline" + } + } + }, + "services": { + "set_watering_time": "mdi:timer", + "set_pause_time": "mdi:timer-pause", + "set_trigger": "mdi:gesture-tap-button" + } +} diff --git a/homeassistant/components/wilight/switch.py b/homeassistant/components/wilight/switch.py index 334d750b1e1..ac4d65b041b 100644 --- a/homeassistant/components/wilight/switch.py +++ b/homeassistant/components/wilight/switch.py @@ -56,10 +56,6 @@ VALID_TRIGGER_INDEX = vol.All( DESC_WATERING = "watering" DESC_PAUSE = "pause" -# Icons of the valve switch entities -ICON_WATERING = "mdi:water" -ICON_PAUSE = "mdi:pause-circle-outline" - def entities_from_discovered_wilight(api_device: PyWiLightDevice) -> tuple[Any]: """Parse configuration and add WiLight switch entities.""" @@ -149,7 +145,6 @@ class WiLightValveSwitch(WiLightDevice, SwitchEntity): """Representation of a WiLights Valve switch.""" _attr_translation_key = "watering" - _attr_icon = ICON_WATERING @property def is_on(self) -> bool: @@ -266,7 +261,6 @@ class WiLightValvePauseSwitch(WiLightDevice, SwitchEntity): """Representation of a WiLights Valve Pause switch.""" _attr_translation_key = "pause" - _attr_icon = ICON_PAUSE @property def is_on(self) -> bool: From dc2d83f131b5ef078027bbfcdb0ae74e9d0c3dc9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:35:30 +0100 Subject: [PATCH 0462/1691] Add icon translations to ViCare (#112346) * Add icon translations to ViCare * Add icon translations to ViCare --- .../components/vicare/binary_sensor.py | 6 -- homeassistant/components/vicare/button.py | 1 - homeassistant/components/vicare/icons.json | 93 +++++++++++++++++++ homeassistant/components/vicare/number.py | 2 - homeassistant/components/vicare/sensor.py | 18 +--- .../vicare/snapshots/test_binary_sensor.ambr | 3 - 6 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 homeassistant/components/vicare/icons.json diff --git a/homeassistant/components/vicare/binary_sensor.py b/homeassistant/components/vicare/binary_sensor.py index a78b1fe5dab..a67a7096e0b 100644 --- a/homeassistant/components/vicare/binary_sensor.py +++ b/homeassistant/components/vicare/binary_sensor.py @@ -48,14 +48,12 @@ CIRCUIT_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = ( ViCareBinarySensorEntityDescription( key="circulationpump_active", translation_key="circulation_pump", - icon="mdi:pump", device_class=BinarySensorDeviceClass.RUNNING, value_getter=lambda api: api.getCirculationPumpActive(), ), ViCareBinarySensorEntityDescription( key="frost_protection_active", translation_key="frost_protection", - icon="mdi:snowflake", value_getter=lambda api: api.getFrostProtectionActive(), ), ) @@ -64,7 +62,6 @@ BURNER_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = ( ViCareBinarySensorEntityDescription( key="burner_active", translation_key="burner", - icon="mdi:gas-burner", device_class=BinarySensorDeviceClass.RUNNING, value_getter=lambda api: api.getActive(), ), @@ -83,7 +80,6 @@ GLOBAL_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = ( ViCareBinarySensorEntityDescription( key="solar_pump_active", translation_key="solar_pump", - icon="mdi:pump", device_class=BinarySensorDeviceClass.RUNNING, value_getter=lambda api: api.getSolarPumpActive(), ), @@ -96,14 +92,12 @@ GLOBAL_SENSORS: tuple[ViCareBinarySensorEntityDescription, ...] = ( ViCareBinarySensorEntityDescription( key="dhw_circulationpump_active", translation_key="domestic_hot_water_circulation_pump", - icon="mdi:pump", device_class=BinarySensorDeviceClass.RUNNING, value_getter=lambda api: api.getDomesticHotWaterCirculationPumpActive(), ), ViCareBinarySensorEntityDescription( key="dhw_pump_active", translation_key="domestic_hot_water_pump", - icon="mdi:pump", device_class=BinarySensorDeviceClass.RUNNING, value_getter=lambda api: api.getDomesticHotWaterPumpActive(), ), diff --git a/homeassistant/components/vicare/button.py b/homeassistant/components/vicare/button.py index ae32e66dff3..f0eaac25724 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -39,7 +39,6 @@ BUTTON_DESCRIPTIONS: tuple[ViCareButtonEntityDescription, ...] = ( ViCareButtonEntityDescription( key="activate_onetimecharge", translation_key="activate_onetimecharge", - icon="mdi:shower-head", entity_category=EntityCategory.CONFIG, value_getter=lambda api: api.getOneTimeCharge(), value_setter=lambda api: api.activateOneTimeCharge(), diff --git a/homeassistant/components/vicare/icons.json b/homeassistant/components/vicare/icons.json new file mode 100644 index 00000000000..2f40d8a8822 --- /dev/null +++ b/homeassistant/components/vicare/icons.json @@ -0,0 +1,93 @@ +{ + "entity": { + "binary_sensor": { + "circulation_pump": { + "default": "mdi:pump" + }, + "frost_protection": { + "default": "mdi:snowflake" + }, + "burner": { + "default": "mdi:gas-burner" + }, + "solar_pump": { + "default": "mdi:pump" + }, + "domestic_hot_water_circulation_pump": { + "default": "mdi:pump" + }, + "domestic_hot_water_pump": { + "default": "mdi:pump" + } + }, + "button": { + "activate_onetimecharge": { + "default": "mdi:shower-head" + } + }, + "number": { + "heating_curve_shift": { + "default": "mdi:plus-minus-variant" + }, + "heating_curve_slope": { + "default": "mdi:slope-uphill" + } + }, + "sensor": { + "volumetric_flow": { + "default": "mdi:gauge" + }, + "ess_state_of_charge": { + "default": "mdi:home-battery" + }, + "pcc_transfer_power_exchange": { + "default": "mdi:transmission-tower" + }, + "pcc_energy_consumption": { + "default": "mdi:transmission-tower-export" + }, + "pcc_energy_feed_in": { + "default": "mdi:transmission-tower-import" + }, + "photovoltaic_energy_production_today": { + "default": "mdi:solar-power" + }, + "burner_starts": { + "default": "mdi:counter" + }, + "burner_hours": { + "default": "mdi:counter" + }, + "burner_modulation": { + "default": "mdi:percent" + }, + "compressor_starts": { + "default": "mdi:counter" + }, + "compressor_hours": { + "default": "mdi:counter" + }, + "compressor_hours_loadclass1": { + "default": "mdi:counter" + }, + "compressor_hours_loadclass2": { + "default": "mdi:counter" + }, + "compressor_hours_loadclass3": { + "default": "mdi:counter" + }, + "compressor_hours_loadclass4": { + "default": "mdi:counter" + }, + "compressor_hours_loadclass5": { + "default": "mdi:counter" + }, + "compressor_phase": { + "default": "mdi:information" + } + } + }, + "services": { + "set_vicare_mode": "mdi:cog" + } +} diff --git a/homeassistant/components/vicare/number.py b/homeassistant/components/vicare/number.py index 70fefb6e8db..a0ae8f3c3e7 100644 --- a/homeassistant/components/vicare/number.py +++ b/homeassistant/components/vicare/number.py @@ -52,7 +52,6 @@ CIRCUIT_ENTITY_DESCRIPTIONS: tuple[ViCareNumberEntityDescription, ...] = ( ViCareNumberEntityDescription( key="heating curve shift", translation_key="heating_curve_shift", - icon="mdi:plus-minus-variant", entity_category=EntityCategory.CONFIG, device_class=NumberDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, @@ -70,7 +69,6 @@ CIRCUIT_ENTITY_DESCRIPTIONS: tuple[ViCareNumberEntityDescription, ...] = ( ViCareNumberEntityDescription( key="heating curve slope", translation_key="heating_curve_slope", - icon="mdi:slope-uphill", entity_category=EntityCategory.CONFIG, value_getter=lambda api: api.getHeatingCurveSlope(), value_setter=lambda api, slope: ( diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index b36b363fc15..fe0171bd476 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -590,7 +590,6 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="volumetric_flow", translation_key="volumetric_flow", - icon="mdi:gauge", native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, value_getter=lambda api: api.getVolumetricFlowReturn() / 1000, entity_category=EntityCategory.DIAGNOSTIC, @@ -598,7 +597,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ), ViCareSensorEntityDescription( key="ess_state_of_charge", - icon="mdi:home-battery", + translation_key="ess_state_of_charge", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, @@ -623,7 +622,6 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="pcc_transfer_power_exchange", translation_key="pcc_transfer_power_exchange", - icon="mdi:transmission-tower", native_unit_of_measurement=UnitOfPower.WATT, state_class=SensorStateClass.MEASUREMENT, value_getter=lambda api: api.getPointOfCommonCouplingTransferPowerExchange(), @@ -631,7 +629,6 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="pcc_energy_consumption", translation_key="pcc_energy_consumption", - icon="mdi:transmission-tower-export", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, value_getter=lambda api: api.getPointOfCommonCouplingTransferConsumptionTotal(), @@ -640,7 +637,6 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="pcc_energy_feed_in", translation_key="pcc_energy_feed_in", - icon="mdi:transmission-tower-import", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, value_getter=lambda api: api.getPointOfCommonCouplingTransferFeedInTotal(), @@ -657,7 +653,6 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="photovoltaic_energy_production_today", translation_key="photovoltaic_energy_production_today", - icon="mdi:solar-power", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL_INCREASING, value_getter=lambda api: api.getPhotovoltaicProductionCumulatedCurrentDay(), @@ -688,7 +683,6 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="burner_starts", translation_key="burner_starts", - icon="mdi:counter", value_getter=lambda api: api.getStarts(), entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL_INCREASING, @@ -696,7 +690,6 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="burner_hours", translation_key="burner_hours", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHours(), entity_category=EntityCategory.DIAGNOSTIC, @@ -705,7 +698,6 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="burner_modulation", translation_key="burner_modulation", - icon="mdi:percent", native_unit_of_measurement=PERCENTAGE, value_getter=lambda api: api.getModulation(), state_class=SensorStateClass.MEASUREMENT, @@ -716,7 +708,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_starts", translation_key="compressor_starts", - icon="mdi:counter", value_getter=lambda api: api.getStarts(), entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL_INCREASING, @@ -724,7 +715,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_hours", translation_key="compressor_hours", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHours(), entity_category=EntityCategory.DIAGNOSTIC, @@ -733,7 +723,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_hours_loadclass1", translation_key="compressor_hours_loadclass1", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHoursLoadClass1(), entity_category=EntityCategory.DIAGNOSTIC, @@ -743,7 +732,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_hours_loadclass2", translation_key="compressor_hours_loadclass2", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHoursLoadClass2(), entity_category=EntityCategory.DIAGNOSTIC, @@ -753,7 +741,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_hours_loadclass3", translation_key="compressor_hours_loadclass3", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHoursLoadClass3(), entity_category=EntityCategory.DIAGNOSTIC, @@ -763,7 +750,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_hours_loadclass4", translation_key="compressor_hours_loadclass4", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHoursLoadClass4(), entity_category=EntityCategory.DIAGNOSTIC, @@ -773,7 +759,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_hours_loadclass5", translation_key="compressor_hours_loadclass5", - icon="mdi:counter", native_unit_of_measurement=UnitOfTime.HOURS, value_getter=lambda api: api.getHoursLoadClass5(), entity_category=EntityCategory.DIAGNOSTIC, @@ -783,7 +768,6 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( ViCareSensorEntityDescription( key="compressor_phase", translation_key="compressor_phase", - icon="mdi:information", value_getter=lambda api: api.getPhase(), entity_category=EntityCategory.DIAGNOSTIC, ), diff --git a/tests/components/vicare/snapshots/test_binary_sensor.ambr b/tests/components/vicare/snapshots/test_binary_sensor.ambr index 2d08a50bf3f..002031a7491 100644 --- a/tests/components/vicare/snapshots/test_binary_sensor.ambr +++ b/tests/components/vicare/snapshots/test_binary_sensor.ambr @@ -4,7 +4,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'running', 'friendly_name': 'model0 Burner', - 'icon': 'mdi:gas-burner', }), 'context': , 'entity_id': 'binary_sensor.model0_burner', @@ -18,7 +17,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'running', 'friendly_name': 'model0 Circulation pump', - 'icon': 'mdi:pump', }), 'context': , 'entity_id': 'binary_sensor.model0_circulation_pump', @@ -31,7 +29,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'model0 Frost protection', - 'icon': 'mdi:snowflake', }), 'context': , 'entity_id': 'binary_sensor.model0_frost_protection', From abf75c4faf9897cd0a7637ac20096cfa555d9078 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:35:38 +0100 Subject: [PATCH 0463/1691] Add icon translations to UPB (#112333) --- homeassistant/components/upb/icons.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 homeassistant/components/upb/icons.json diff --git a/homeassistant/components/upb/icons.json b/homeassistant/components/upb/icons.json new file mode 100644 index 00000000000..187f0f60970 --- /dev/null +++ b/homeassistant/components/upb/icons.json @@ -0,0 +1,12 @@ +{ + "services": { + "light_fade_start": "mdi:transition", + "light_fade_stop": "mdi:transition-masked", + "light_blink": "mdi:eye", + "link_deactivate": "mdi:link-off", + "link_goto": "mdi:link-variant", + "link_fade_start": "mdi:transition", + "link_fade_stop": "mdi:transition-masked", + "link_blink": "mdi:eye" + } +} From 8946cfc1e947d7f53a4e01005bc5e1e42b008597 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:35:47 +0100 Subject: [PATCH 0464/1691] Add icon translations to Tuya (#112326) --- .../components/tuya/alarm_control_panel.py | 1 - .../components/tuya/binary_sensor.py | 7 - homeassistant/components/tuya/button.py | 6 - homeassistant/components/tuya/icons.json | 373 ++++++++++++++++++ homeassistant/components/tuya/number.py | 36 -- homeassistant/components/tuya/select.py | 27 -- homeassistant/components/tuya/sensor.py | 34 +- homeassistant/components/tuya/switch.py | 61 --- 8 files changed, 374 insertions(+), 171 deletions(-) create mode 100644 homeassistant/components/tuya/icons.json diff --git a/homeassistant/components/tuya/alarm_control_panel.py b/homeassistant/components/tuya/alarm_control_panel.py index 681f025f57b..3cf9cd718ae 100644 --- a/homeassistant/components/tuya/alarm_control_panel.py +++ b/homeassistant/components/tuya/alarm_control_panel.py @@ -87,7 +87,6 @@ async def async_setup_entry( class TuyaAlarmEntity(TuyaEntity, AlarmControlPanelEntity): """Tuya Alarm Entity.""" - _attr_icon = "mdi:security" _attr_name = None def __init__( diff --git a/homeassistant/components/tuya/binary_sensor.py b/homeassistant/components/tuya/binary_sensor.py index 5664801d76e..c66581ef46c 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -51,7 +51,6 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { "dgnbj": ( TuyaBinarySensorEntityDescription( key=DPCode.GAS_SENSOR_STATE, - icon="mdi:gas-cylinder", device_class=BinarySensorDeviceClass.GAS, on_value="alarm", ), @@ -76,14 +75,12 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { TuyaBinarySensorEntityDescription( key=DPCode.CO_STATE, translation_key="carbon_monoxide", - icon="mdi:molecule-co", device_class=BinarySensorDeviceClass.SAFETY, on_value="alarm", ), TuyaBinarySensorEntityDescription( key=DPCode.CO2_STATE, translation_key="carbon_dioxide", - icon="mdi:molecule-co2", device_class=BinarySensorDeviceClass.SAFETY, on_value="alarm", ), @@ -109,7 +106,6 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { ), TuyaBinarySensorEntityDescription( key=DPCode.SMOKE_SENSOR_STATE, - icon="mdi:smoke-detector", device_class=BinarySensorDeviceClass.SMOKE, on_value="alarm", ), @@ -146,7 +142,6 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { TuyaBinarySensorEntityDescription( key=DPCode.FEED_STATE, translation_key="feeding", - icon="mdi:information", on_value="feeding", ), ), @@ -329,14 +324,12 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { key=f"{DPCode.SHOCK_STATE}_drop", dpcode=DPCode.SHOCK_STATE, translation_key="drop", - icon="mdi:icon=package-down", on_value="drop", ), TuyaBinarySensorEntityDescription( key=f"{DPCode.SHOCK_STATE}_tilt", dpcode=DPCode.SHOCK_STATE, translation_key="tilt", - icon="mdi:spirit-level", on_value="tilt", ), ), diff --git a/homeassistant/components/tuya/button.py b/homeassistant/components/tuya/button.py index 5b936b305fb..41957fe7e73 100644 --- a/homeassistant/components/tuya/button.py +++ b/homeassistant/components/tuya/button.py @@ -23,31 +23,26 @@ BUTTONS: dict[str, tuple[ButtonEntityDescription, ...]] = { ButtonEntityDescription( key=DPCode.RESET_DUSTER_CLOTH, translation_key="reset_duster_cloth", - icon="mdi:restart", entity_category=EntityCategory.CONFIG, ), ButtonEntityDescription( key=DPCode.RESET_EDGE_BRUSH, translation_key="reset_edge_brush", - icon="mdi:restart", entity_category=EntityCategory.CONFIG, ), ButtonEntityDescription( key=DPCode.RESET_FILTER, translation_key="reset_filter", - icon="mdi:air-filter", entity_category=EntityCategory.CONFIG, ), ButtonEntityDescription( key=DPCode.RESET_MAP, translation_key="reset_map", - icon="mdi:map-marker-remove", entity_category=EntityCategory.CONFIG, ), ButtonEntityDescription( key=DPCode.RESET_ROLL_BRUSH, translation_key="reset_roll_brush", - icon="mdi:restart", entity_category=EntityCategory.CONFIG, ), ), @@ -57,7 +52,6 @@ BUTTONS: dict[str, tuple[ButtonEntityDescription, ...]] = { ButtonEntityDescription( key=DPCode.SWITCH_USB6, translation_key="snooze", - icon="mdi:sleep", ), ), } diff --git a/homeassistant/components/tuya/icons.json b/homeassistant/components/tuya/icons.json new file mode 100644 index 00000000000..48ae61f36fd --- /dev/null +++ b/homeassistant/components/tuya/icons.json @@ -0,0 +1,373 @@ +{ + "entity": { + "binary_sensor": { + "carbon_monoxide": { + "default": "mdi:molecule-co" + }, + "carbon_dioxide": { + "default": "mdi:molecule-co2" + }, + "feeding": { + "default": "mdi:information" + }, + "drop": { + "default": "mdi:package-down" + }, + "tilt": { + "default": "mdi:spirit-level" + } + }, + "button": { + "reset_duster_cloth": { + "default": "mdi:restart" + }, + "reset_edge_brush": { + "default": "mdi:restart" + }, + "reset_filter": { + "default": "mdi:air-filter" + }, + "reset_map": { + "default": "mdi:map-marker-remove" + }, + "reset_roll_brush": { + "default": "mdi:restart" + }, + "snooze": { + "default": "mdi:sleep" + } + }, + "number": { + "heat_preservation_time": { + "default": "mdi:timer" + }, + "feed": { + "default": "mdi:bowl" + }, + "voice_times": { + "default": "mdi:microphone" + }, + "near_detection": { + "default": "mdi:signal-distance-variant" + }, + "far_detection": { + "default": "mdi:signal-distance-variant" + }, + "water_level": { + "default": "mdi:cup-water" + }, + "cook_time": { + "default": "mdi:timer" + }, + "volume": { + "default": "mdi:volume-high" + }, + "minimum_brightness": { + "default": "mdi:lightbulb-outline" + }, + "maximum_brightness": { + "default": "mdi:lightbulb-on-outline" + }, + "minimum_brightness_2": { + "default": "mdi:lightbulb-outline" + }, + "maximum_brightness_2": { + "default": "mdi:lightbulb-on-outline" + }, + "minimum_brightness_3": { + "default": "mdi:lightbulb-outline" + }, + "maximum_brightness_3": { + "default": "mdi:lightbulb-on-outline" + }, + "move_down": { + "default": "mdi:arrow-down-bold" + }, + "move_up": { + "default": "mdi:arrow-up-bold" + }, + "down_delay": { + "default": "mdi:timer" + }, + "temperature": { + "default": "mdi:thermometer" + } + }, + "select": { + "cups": { + "default": "mdi:numeric" + }, + "concentration": { + "default": "mdi:altimeter" + }, + "mode": { + "default": "mdi:coffee" + }, + "temperature_level": { + "default": "mdi:thermometer-lines" + }, + "weather_delay": { + "default": "mdi:weather-cloudy-clock" + }, + "decibel_sensitivity": { + "default": "mdi:volume-vibrate" + }, + "record_mode": { + "default": "mdi:record-rec" + }, + "basic_nightvision": { + "default": "mdi:theme-light-dark" + }, + "basic_anti_flicker": { + "default": "mdi:image-outline" + }, + "motion_sensitivity": { + "default": "mdi:motion-sensor" + }, + "vacuum_cistern": { + "default": "mdi:water-opacity" + }, + "vacuum_collection": { + "default": "mdi:air-filter" + }, + "vacuum_mode": { + "default": "mdi:layers-outline" + }, + "vertical_fan_angle": { + "default": "mdi:format-vertical-align-center" + }, + "horizontal_fan_angle": { + "default": "mdi:format-horizontal-align-center" + }, + "countdown": { + "default": "mdi:timer-cog-outline" + }, + "curtain_motor_mode": { + "default": "mdi:swap-horizontal" + }, + "humidifier_spray_mode": { + "default": "mdi:spray" + }, + "humidifier_level": { + "default": "mdi:spray" + }, + "humidifier_moodlighting": { + "default": "mdi:lightbulb-multiple" + }, + "target_humidity": { + "default": "mdi:water-percent" + } + }, + "sensor": { + "battery_state": { + "default": "mdi:battery" + }, + "gas": { + "default": "mdi:gas-cylinder" + }, + "carbon_monoxide": { + "default": "mdi:molecule-co" + }, + "carbon_dioxide": { + "default": "mdi:molecule-co2" + }, + "luminosity": { + "default": "mdi:brightness-6" + }, + "illuminance": { + "default": "mdi:brightness-6" + }, + "smoke_amount": { + "default": "mdi:smoke-detector" + }, + "last_amount": { + "default": "mdi:counter" + }, + "remaining_time": { + "default": "mdi:timer" + }, + "total_watering_time": { + "default": "mdi:history" + }, + "cleaning_area": { + "default": "mdi:texture-box" + }, + "cleaning_time": { + "default": "mdi:progress-clock" + }, + "total_cleaning_area": { + "default": "mdi:texture-box" + }, + "total_cleaning_time": { + "default": "mdi:history" + }, + "total_cleaning_times": { + "default": "mdi:counter" + }, + "duster_cloth_life": { + "default": "mdi:ticket-percent-outline" + }, + "side_brush_life": { + "default": "mdi:ticket-percent-outline" + }, + "filter_life": { + "default": "mdi:ticket-percent-outline" + }, + "rolling_brush_life": { + "default": "mdi:ticket-percent-outline" + }, + "last_operation_duration": { + "default": "mdi:progress-clock" + }, + "water_level": { + "default": "mdi:waves-arrow-up" + }, + "filter_utilization": { + "default": "mdi:ticket-percent-outline" + }, + "pm25": { + "default": "mdi:molecule" + }, + "total_operating_time": { + "default": "mdi:history" + }, + "total_absorption_particles": { + "default": "mdi:texture-box" + }, + "air_quality": { + "default": "mdi:air-filter" + } + }, + "switch": { + "start": { + "default": "mdi:kettle-steam" + }, + "disinfection": { + "default": "mdi:bacteria" + }, + "water": { + "default": "mdi:water" + }, + "slow_feed": { + "default": "mdi:speedometer-slow" + }, + "filter_reset": { + "default": "mdi:filter" + }, + "water_pump_reset": { + "default": "mdi:pump" + }, + "reset_of_water_usage_days": { + "default": "mdi:water-sync" + }, + "uv_sterilization": { + "default": "mdi:lightbulb" + }, + "child_lock": { + "default": "mdi:account-lock" + }, + "radio": { + "default": "mdi:radio" + }, + "alarm_1": { + "default": "mdi:alarm" + }, + "alarm_2": { + "default": "mdi:alarm" + }, + "alarm_3": { + "default": "mdi:alarm" + }, + "alarm_4": { + "default": "mdi:alarm" + }, + "sleep_aid": { + "default": "mdi:power-sleep" + }, + "ionizer": { + "default": "mdi:minus-circle-outline" + }, + "filter_cartridge_reset": { + "default": "mdi:filter" + }, + "humidification": { + "default": "mdi:water-percent" + }, + "switch": { + "default": "mdi:power" + }, + "do_not_disturb": { + "default": "mdi:minus-circle" + }, + "mute_voice": { + "default": "mdi:account-voice" + }, + "battery_lock": { + "default": "mdi:battery-lock" + }, + "cry_detection": { + "default": "mdi:emoticon-cry" + }, + "sound_detection": { + "default": "mdi:microphone-outline" + }, + "video_recording": { + "default": "mdi:record-rec" + }, + "motion_recording": { + "default": "mdi:record-rec" + }, + "privacy_mode": { + "default": "mdi:eye-off" + }, + "flip": { + "default": "mdi:flip-horizontal" + }, + "time_watermark": { + "default": "mdi:watermark" + }, + "wide_dynamic_range": { + "default": "mdi:watermark" + }, + "motion_tracking": { + "default": "mdi:motion-sensor" + }, + "motion_alarm": { + "default": "mdi:motion-sensor" + }, + "energy_saving": { + "default": "mdi:leaf" + }, + "open_window_detection": { + "default": "mdi:window-open" + }, + "spray": { + "default": "mdi:spray" + }, + "voice": { + "default": "mdi:account-voice" + }, + "anion": { + "default": "mdi:atom" + }, + "oxygen_bar": { + "default": "mdi:molecule" + }, + "natural_wind": { + "default": "mdi:weather-windy" + }, + "sound": { + "default": "mdi:minus-circle" + }, + "reverse": { + "default": "mdi:swap-horizontal" + }, + "sleep": { + "default": "mdi:power-sleep" + }, + "sterilization": { + "default": "mdi:minus-circle-outline" + } + } + } +} diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index 8fc55d2c230..fdc578bc53e 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -38,34 +38,29 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { key=DPCode.TEMP_SET, translation_key="temperature", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.TEMP_SET_F, translation_key="temperature", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.TEMP_BOILING_C, translation_key="temperature_after_boiling", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.TEMP_BOILING_F, translation_key="temperature_after_boiling", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.WARM_TIME, translation_key="heat_preservation_time", - icon="mdi:timer", entity_category=EntityCategory.CONFIG, ), ), @@ -75,12 +70,10 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.MANUAL_FEED, translation_key="feed", - icon="mdi:bowl", ), NumberEntityDescription( key=DPCode.VOICE_TIMES, translation_key="voice_times", - icon="mdi:microphone", ), ), # Human Presence Sensor @@ -94,13 +87,11 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.NEAR_DETECTION, translation_key="near_detection", - icon="mdi:signal-distance-variant", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.FAR_DETECTION, translation_key="far_detection", - icon="mdi:signal-distance-variant", entity_category=EntityCategory.CONFIG, ), ), @@ -110,20 +101,17 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.WATER_SET, translation_key="water_level", - icon="mdi:cup-water", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.TEMP_SET, translation_key="temperature", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.WARM_TIME, translation_key="heat_preservation_time", - icon="mdi:timer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( @@ -138,13 +126,11 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.COOK_TEMPERATURE, translation_key="cook_temperature", - icon="mdi:thermometer", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.COOK_TIME, translation_key="cook_time", - icon="mdi:timer", native_unit_of_measurement=UnitOfTime.MINUTES, entity_category=EntityCategory.CONFIG, ), @@ -160,7 +146,6 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.VOLUME_SET, translation_key="volume", - icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, ), ), @@ -179,7 +164,6 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.BASIC_DEVICE_VOLUME, translation_key="volume", - icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, ), ), @@ -189,37 +173,31 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.BRIGHTNESS_MIN_1, translation_key="minimum_brightness", - icon="mdi:lightbulb-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MAX_1, translation_key="maximum_brightness", - icon="mdi:lightbulb-on-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MIN_2, translation_key="minimum_brightness_2", - icon="mdi:lightbulb-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MAX_2, translation_key="maximum_brightness_2", - icon="mdi:lightbulb-on-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MIN_3, translation_key="minimum_brightness_3", - icon="mdi:lightbulb-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MAX_3, translation_key="maximum_brightness_3", - icon="mdi:lightbulb-on-outline", entity_category=EntityCategory.CONFIG, ), ), @@ -229,25 +207,21 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.BRIGHTNESS_MIN_1, translation_key="minimum_brightness", - icon="mdi:lightbulb-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MAX_1, translation_key="maximum_brightness", - icon="mdi:lightbulb-on-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MIN_2, translation_key="minimum_brightness_2", - icon="mdi:lightbulb-outline", entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.BRIGHTNESS_MAX_2, translation_key="maximum_brightness_2", - icon="mdi:lightbulb-on-outline", entity_category=EntityCategory.CONFIG, ), ), @@ -265,21 +239,18 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NumberEntityDescription( key=DPCode.ARM_DOWN_PERCENT, translation_key="move_down", - icon="mdi:arrow-down-bold", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.ARM_UP_PERCENT, translation_key="move_up", - icon="mdi:arrow-up-bold", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.CONFIG, ), NumberEntityDescription( key=DPCode.CLICK_SUSTAIN_TIME, translation_key="down_delay", - icon="mdi:timer", entity_category=EntityCategory.CONFIG, ), ), @@ -290,7 +261,6 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { key=DPCode.TEMP, translation_key="temperature", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer-lines", ), ), # Humidifier @@ -300,13 +270,11 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { key=DPCode.TEMP_SET, translation_key="temperature", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer-lines", ), NumberEntityDescription( key=DPCode.TEMP_SET_F, translation_key="temperature", device_class=NumberDeviceClass.TEMPERATURE, - icon="mdi:thermometer-lines", ), ), } @@ -390,10 +358,6 @@ class TuyaNumberEntity(TuyaEntity, NumberEntity): self._attr_device_class = None return - # If we still have a device class, we should not use an icon - if self.device_class: - self._attr_icon = None - # Found unit of measurement, use the standardized Unit # Use the target conversion unit (if set) self._attr_native_unit_of_measurement = ( diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index 5d712767697..ecc0571962b 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -33,12 +33,10 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.CUP_NUMBER, translation_key="cups", - icon="mdi:numeric", ), SelectEntityDescription( key=DPCode.CONCENTRATION_SET, translation_key="concentration", - icon="mdi:altimeter", entity_category=EntityCategory.CONFIG, ), SelectEntityDescription( @@ -49,7 +47,6 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.MODE, translation_key="mode", - icon="mdi:coffee", ), ), # Switch @@ -72,7 +69,6 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.LEVEL, translation_key="temperature_level", - icon="mdi:thermometer-lines", ), ), # Smart Water Timer @@ -81,7 +77,6 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.WEATHER_DELAY, translation_key="weather_delay", - icon="mdi:weather-cloudy-clock", entity_category=EntityCategory.CONFIG, ), ), @@ -109,31 +104,26 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { ), SelectEntityDescription( key=DPCode.DECIBEL_SENSITIVITY, - icon="mdi:volume-vibrate", entity_category=EntityCategory.CONFIG, translation_key="decibel_sensitivity", ), SelectEntityDescription( key=DPCode.RECORD_MODE, - icon="mdi:record-rec", entity_category=EntityCategory.CONFIG, translation_key="record_mode", ), SelectEntityDescription( key=DPCode.BASIC_NIGHTVISION, - icon="mdi:theme-light-dark", entity_category=EntityCategory.CONFIG, translation_key="basic_nightvision", ), SelectEntityDescription( key=DPCode.BASIC_ANTI_FLICKER, - icon="mdi:image-outline", entity_category=EntityCategory.CONFIG, translation_key="basic_anti_flicker", ), SelectEntityDescription( key=DPCode.MOTION_SENSITIVITY, - icon="mdi:motion-sensor", entity_category=EntityCategory.CONFIG, translation_key="motion_sensitivity", ), @@ -209,19 +199,16 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.CISTERN, entity_category=EntityCategory.CONFIG, - icon="mdi:water-opacity", translation_key="vacuum_cistern", ), SelectEntityDescription( key=DPCode.COLLECTION_MODE, entity_category=EntityCategory.CONFIG, - icon="mdi:air-filter", translation_key="vacuum_collection", ), SelectEntityDescription( key=DPCode.MODE, entity_category=EntityCategory.CONFIG, - icon="mdi:layers-outline", translation_key="vacuum_mode", ), ), @@ -231,25 +218,21 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.FAN_VERTICAL, entity_category=EntityCategory.CONFIG, - icon="mdi:format-vertical-align-center", translation_key="vertical_fan_angle", ), SelectEntityDescription( key=DPCode.FAN_HORIZONTAL, entity_category=EntityCategory.CONFIG, - icon="mdi:format-horizontal-align-center", translation_key="horizontal_fan_angle", ), SelectEntityDescription( key=DPCode.COUNTDOWN, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), SelectEntityDescription( key=DPCode.COUNTDOWN_SET, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), ), @@ -259,7 +242,6 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.CONTROL_BACK_MODE, entity_category=EntityCategory.CONFIG, - icon="mdi:swap-horizontal", translation_key="curtain_motor_mode", ), SelectEntityDescription( @@ -274,31 +256,26 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.SPRAY_MODE, entity_category=EntityCategory.CONFIG, - icon="mdi:spray", translation_key="humidifier_spray_mode", ), SelectEntityDescription( key=DPCode.LEVEL, entity_category=EntityCategory.CONFIG, - icon="mdi:spray", translation_key="humidifier_level", ), SelectEntityDescription( key=DPCode.MOODLIGHTING, entity_category=EntityCategory.CONFIG, - icon="mdi:lightbulb-multiple", translation_key="humidifier_moodlighting", ), SelectEntityDescription( key=DPCode.COUNTDOWN, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), SelectEntityDescription( key=DPCode.COUNTDOWN_SET, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), ), @@ -308,13 +285,11 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.COUNTDOWN, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), SelectEntityDescription( key=DPCode.COUNTDOWN_SET, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), ), @@ -324,14 +299,12 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { SelectEntityDescription( key=DPCode.COUNTDOWN_SET, entity_category=EntityCategory.CONFIG, - icon="mdi:timer-cog-outline", translation_key="countdown", ), SelectEntityDescription( key=DPCode.DEHUMIDITY_SET_ENUM, translation_key="target_humidity", entity_category=EntityCategory.CONFIG, - icon="mdi:water-percent", ), ), } diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 80c76a0c253..00ee4ea5902 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -58,7 +58,6 @@ BATTERY_SENSORS: tuple[TuyaSensorEntityDescription, ...] = ( TuyaSensorEntityDescription( key=DPCode.BATTERY_STATE, translation_key="battery_state", - icon="mdi:battery", entity_category=EntityCategory.DIAGNOSTIC, ), TuyaSensorEntityDescription( @@ -88,7 +87,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.GAS_SENSOR_VALUE, translation_key="gas", - icon="mdi:gas-cylinder", state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( @@ -112,14 +110,12 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.CO_VALUE, translation_key="carbon_monoxide", - icon="mdi:molecule-co", device_class=SensorDeviceClass.CO, state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.CO2_VALUE, translation_key="carbon_dioxide", - icon="mdi:molecule-co2", device_class=SensorDeviceClass.CO2, state_class=SensorStateClass.MEASUREMENT, ), @@ -131,12 +127,10 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.BRIGHT_STATE, translation_key="luminosity", - icon="mdi:brightness-6", ), TuyaSensorEntityDescription( key=DPCode.BRIGHT_VALUE, translation_key="illuminance", - icon="mdi:brightness-6", device_class=SensorDeviceClass.ILLUMINANCE, state_class=SensorStateClass.MEASUREMENT, ), @@ -155,7 +149,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.SMOKE_SENSOR_VALUE, translation_key="smoke_amount", - icon="mdi:smoke-detector", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, ), @@ -238,7 +231,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.FEED_REPORT, translation_key="last_amount", - icon="mdi:counter", state_class=SensorStateClass.MEASUREMENT, ), ), @@ -387,7 +379,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.BRIGHT_STATE, translation_key="luminosity", - icon="mdi:brightness-6", ), TuyaSensorEntityDescription( key=DPCode.BRIGHT_VALUE, @@ -438,7 +429,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { key=DPCode.REMAIN_TIME, translation_key="remaining_time", native_unit_of_measurement=UnitOfTime.MINUTES, - icon="mdi:timer", ), ), # PIR Detector @@ -512,7 +502,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.GAS_SENSOR_VALUE, name=None, - icon="mdi:gas-cylinder", + translation_key="gas", state_class=SensorStateClass.MEASUREMENT, ), *BATTERY_SENSORS, @@ -523,7 +513,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.TIME_USE, translation_key="total_watering_time", - icon="mdi:history", state_class=SensorStateClass.TOTAL_INCREASING, entity_category=EntityCategory.DIAGNOSTIC, ), @@ -658,7 +647,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.SMOKE_SENSOR_VALUE, translation_key="smoke_amount", - icon="mdi:smoke-detector", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, ), @@ -858,55 +846,46 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.CLEAN_AREA, translation_key="cleaning_area", - icon="mdi:texture-box", state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.CLEAN_TIME, translation_key="cleaning_time", - icon="mdi:progress-clock", state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.TOTAL_CLEAN_AREA, translation_key="total_cleaning_area", - icon="mdi:texture-box", state_class=SensorStateClass.TOTAL_INCREASING, ), TuyaSensorEntityDescription( key=DPCode.TOTAL_CLEAN_TIME, translation_key="total_cleaning_time", - icon="mdi:history", state_class=SensorStateClass.TOTAL_INCREASING, ), TuyaSensorEntityDescription( key=DPCode.TOTAL_CLEAN_COUNT, translation_key="total_cleaning_times", - icon="mdi:counter", state_class=SensorStateClass.TOTAL_INCREASING, ), TuyaSensorEntityDescription( key=DPCode.DUSTER_CLOTH, translation_key="duster_cloth_life", - icon="mdi:ticket-percent-outline", state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.EDGE_BRUSH, translation_key="side_brush_life", - icon="mdi:ticket-percent-outline", state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.FILTER_LIFE, translation_key="filter_life", - icon="mdi:ticket-percent-outline", state_class=SensorStateClass.MEASUREMENT, ), TuyaSensorEntityDescription( key=DPCode.ROLL_BRUSH, translation_key="rolling_brush_life", - icon="mdi:ticket-percent-outline", state_class=SensorStateClass.MEASUREMENT, ), ), @@ -917,7 +896,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { key=DPCode.TIME_TOTAL, translation_key="last_operation_duration", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:progress-clock", ), ), # Humidifier @@ -945,7 +923,6 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { key=DPCode.LEVEL_CURRENT, translation_key="water_level", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:waves-arrow-up", ), ), # Air Purifier @@ -955,14 +932,12 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { key=DPCode.FILTER, translation_key="filter_utilization", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:ticket-percent-outline", ), TuyaSensorEntityDescription( key=DPCode.PM25, translation_key="pm25", device_class=SensorDeviceClass.PM25, state_class=SensorStateClass.MEASUREMENT, - icon="mdi:molecule", ), TuyaSensorEntityDescription( key=DPCode.TEMP, @@ -991,21 +966,18 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { TuyaSensorEntityDescription( key=DPCode.TOTAL_TIME, translation_key="total_operating_time", - icon="mdi:history", state_class=SensorStateClass.TOTAL_INCREASING, entity_category=EntityCategory.DIAGNOSTIC, ), TuyaSensorEntityDescription( key=DPCode.TOTAL_PM, translation_key="total_absorption_particles", - icon="mdi:texture-box", state_class=SensorStateClass.TOTAL_INCREASING, entity_category=EntityCategory.DIAGNOSTIC, ), TuyaSensorEntityDescription( key=DPCode.AIR_QUALITY, translation_key="air_quality", - icon="mdi:air-filter", ), ), # Fan @@ -1191,10 +1163,6 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity): self._attr_device_class = None return - # If we still have a device class, we should not use an icon - if self.device_class: - self._attr_icon = None - # Found unit of measurement, use the standardized Unit # Use the target conversion unit (if set) self._attr_native_unit_of_measurement = ( diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index a89dbbd7132..c2797be0adc 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -30,7 +30,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.START, translation_key="start", - icon="mdi:kettle-steam", ), SwitchEntityDescription( key=DPCode.WARM, @@ -44,12 +43,10 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.DISINFECTION, translation_key="disinfection", - icon="mdi:bacteria", ), SwitchEntityDescription( key=DPCode.WATER, translation_key="water", - icon="mdi:water", ), ), # Smart Pet Feeder @@ -58,7 +55,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SLOW_FEED, translation_key="slow_feed", - icon="mdi:speedometer-slow", entity_category=EntityCategory.CONFIG, ), ), @@ -68,13 +64,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.FILTER_RESET, translation_key="filter_reset", - icon="mdi:filter", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.PUMP_RESET, translation_key="water_pump_reset", - icon="mdi:pump", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( @@ -84,13 +78,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.WATER_RESET, translation_key="reset_of_water_usage_days", - icon="mdi:water-sync", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.UV, translation_key="uv_sterilization", - icon="mdi:lightbulb", entity_category=EntityCategory.CONFIG, ), ), @@ -110,7 +102,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.CHILD_LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( @@ -124,36 +115,30 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH_1, translation_key="radio", - icon="mdi:radio", ), SwitchEntityDescription( key=DPCode.SWITCH_2, translation_key="alarm_1", - icon="mdi:alarm", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.SWITCH_3, translation_key="alarm_2", - icon="mdi:alarm", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.SWITCH_4, translation_key="alarm_3", - icon="mdi:alarm", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.SWITCH_5, translation_key="alarm_4", - icon="mdi:alarm", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.SWITCH_6, translation_key="sleep_aid", - icon="mdi:power-sleep", ), ), # Two-way temperature and humidity switch @@ -177,7 +162,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.CHILD_LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( @@ -256,19 +240,16 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.ANION, translation_key="ionizer", - icon="mdi:minus-circle-outline", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.FILTER_RESET, translation_key="filter_cartridge_reset", - icon="mdi:filter", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( @@ -278,13 +259,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.WET, translation_key="humidification", - icon="mdi:water-percent", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.UV, translation_key="uv_sterilization", - icon="mdi:minus-circle-outline", entity_category=EntityCategory.CONFIG, ), ), @@ -294,13 +273,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.ANION, translation_key="ionizer", - icon="mdi:minus-circle-outline", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), ), @@ -310,13 +287,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH, translation_key="switch", - icon="mdi:power", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.START, translation_key="start", - icon="mdi:pot-steam", entity_category=EntityCategory.CONFIG, ), ), @@ -326,7 +301,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.CHILD_LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( @@ -404,13 +378,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.ANION, translation_key="ionizer", - icon="mdi:minus-circle-outline", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), ), @@ -420,13 +392,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH_DISTURB, translation_key="do_not_disturb", - icon="mdi:minus-circle", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.VOICE_SWITCH, translation_key="mute_voice", - icon="mdi:account-voice", entity_category=EntityCategory.CONFIG, ), ), @@ -435,7 +405,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH, translation_key="switch", - icon="mdi:sprinkler-variant", ), ), # Siren Alarm @@ -453,67 +422,56 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.WIRELESS_BATTERYLOCK, translation_key="battery_lock", - icon="mdi:battery-lock", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.CRY_DETECTION_SWITCH, translation_key="cry_detection", - icon="mdi:emoticon-cry", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.DECIBEL_SWITCH, translation_key="sound_detection", - icon="mdi:microphone-outline", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.RECORD_SWITCH, translation_key="video_recording", - icon="mdi:record-rec", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.MOTION_RECORD, translation_key="motion_recording", - icon="mdi:record-rec", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.BASIC_PRIVATE, translation_key="privacy_mode", - icon="mdi:eye-off", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.BASIC_FLIP, translation_key="flip", - icon="mdi:flip-horizontal", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.BASIC_OSD, translation_key="time_watermark", - icon="mdi:watermark", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.BASIC_WDR, translation_key="wide_dynamic_range", - icon="mdi:watermark", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.MOTION_TRACKING, translation_key="motion_tracking", - icon="mdi:motion-sensor", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.MOTION_SWITCH, translation_key="motion_alarm", - icon="mdi:motion-sensor", entity_category=EntityCategory.CONFIG, ), ), @@ -522,7 +480,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH, translation_key="switch", - icon="mdi:cursor-pointer", ), ), # IoT Switch? @@ -551,7 +508,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.CHILD_LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), ), @@ -561,7 +517,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH_SAVE_ENERGY, translation_key="energy_saving", - icon="mdi:leaf", entity_category=EntityCategory.CONFIG, ), ), @@ -571,13 +526,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.CHILD_LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.WINDOW_CHECK, translation_key="open_window_detection", - icon="mdi:window-open", entity_category=EntityCategory.CONFIG, ), ), @@ -603,7 +556,6 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.DO_NOT_DISTURB, translation_key="do_not_disturb", - icon="mdi:minus-circle-outline", entity_category=EntityCategory.CONFIG, ), ), @@ -617,12 +569,10 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH_SPRAY, translation_key="spray", - icon="mdi:spray", ), SwitchEntityDescription( key=DPCode.SWITCH_VOICE, translation_key="voice", - icon="mdi:account-voice", entity_category=EntityCategory.CONFIG, ), ), @@ -640,37 +590,31 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.ANION, translation_key="anion", - icon="mdi:atom", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.HUMIDIFIER, translation_key="humidification", - icon="mdi:air-humidifier", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.OXYGEN, translation_key="oxygen_bar", - icon="mdi:molecule", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.FAN_COOL, translation_key="natural_wind", - icon="mdi:weather-windy", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.FAN_BEEP, translation_key="sound", - icon="mdi:minus-circle", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.CHILD_LOCK, translation_key="child_lock", - icon="mdi:account-lock", entity_category=EntityCategory.CONFIG, ), ), @@ -680,13 +624,11 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.CONTROL_BACK, translation_key="reverse", - icon="mdi:swap-horizontal", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.OPPOSITE, translation_key="reverse", - icon="mdi:swap-horizontal", entity_category=EntityCategory.CONFIG, ), ), @@ -696,19 +638,16 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { SwitchEntityDescription( key=DPCode.SWITCH_SOUND, translation_key="voice", - icon="mdi:account-voice", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.SLEEP, translation_key="sleep", - icon="mdi:power-sleep", entity_category=EntityCategory.CONFIG, ), SwitchEntityDescription( key=DPCode.STERILIZATION, translation_key="sterilization", - icon="mdi:minus-circle-outline", entity_category=EntityCategory.CONFIG, ), ), From a4444925c8087ed9d880f51365762b318e10ff0c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:36:01 +0100 Subject: [PATCH 0465/1691] Add icon translations to Transmission (#112324) --- homeassistant/components/transmission/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/transmission/icons.json diff --git a/homeassistant/components/transmission/icons.json b/homeassistant/components/transmission/icons.json new file mode 100644 index 00000000000..56ae46f933d --- /dev/null +++ b/homeassistant/components/transmission/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "add_torrent": "mdi:download", + "remove_torrent": "mdi:download-off", + "start_torrent": "mdi:play", + "stop_torrent": "mdi:stop" + } +} From 503d39ef0d73c7abb87d1335357afe88445a5994 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:36:20 +0100 Subject: [PATCH 0466/1691] Add icon translations to TP-Link Omada (#112318) * Add icon translations to TP-Link Omada * Add icon translations to TP-Link Omada --- .../components/tplink_omada/icons.json | 9 +++++ .../components/tplink_omada/switch.py | 8 ++-- .../tplink_omada/snapshots/test_switch.ambr | 40 ++++++++----------- 3 files changed, 28 insertions(+), 29 deletions(-) create mode 100644 homeassistant/components/tplink_omada/icons.json diff --git a/homeassistant/components/tplink_omada/icons.json b/homeassistant/components/tplink_omada/icons.json new file mode 100644 index 00000000000..38efc9068be --- /dev/null +++ b/homeassistant/components/tplink_omada/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "switch": { + "poe_control": { + "default": "mdi:ethernet" + } + } + } +} diff --git a/homeassistant/components/tplink_omada/switch.py b/homeassistant/components/tplink_omada/switch.py index f8a124b94fc..618b49556d2 100644 --- a/homeassistant/components/tplink_omada/switch.py +++ b/homeassistant/components/tplink_omada/switch.py @@ -17,8 +17,6 @@ from .const import DOMAIN from .controller import OmadaSiteController, OmadaSwitchPortCoordinator from .entity import OmadaDeviceEntity -POE_SWITCH_ICON = "mdi:ethernet" - async def async_setup_entry( hass: HomeAssistant, @@ -62,8 +60,8 @@ class OmadaNetworkSwitchPortPoEControl( """Representation of a PoE control toggle on a single network port on a switch.""" _attr_has_entity_name = True + _attr_translation_key = "poe_control" _attr_entity_category = EntityCategory.CONFIG - _attr_icon = POE_SWITCH_ICON def __init__( self, @@ -74,8 +72,8 @@ class OmadaNetworkSwitchPortPoEControl( """Initialize the PoE switch.""" super().__init__(coordinator, device) self.port_id = port_id - self.port_details = self.coordinator.data[port_id] - self.omada_client = self.coordinator.omada_client + self.port_details = coordinator.data[port_id] + self.omada_client = coordinator.omada_client self._attr_unique_id = f"{device.mac}_{port_id}_poe" self._attr_name = f"{get_port_base_name(self.port_details)} PoE" diff --git a/tests/components/tplink_omada/snapshots/test_switch.ambr b/tests/components/tplink_omada/snapshots/test_switch.ambr index ee87a061a3c..78698e7ef87 100644 --- a/tests/components/tplink_omada/snapshots/test_switch.ambr +++ b/tests/components/tplink_omada/snapshots/test_switch.ambr @@ -3,7 +3,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 1 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_1_poe', @@ -35,12 +34,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 1 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000001_poe', 'unit_of_measurement': None, }) @@ -49,7 +48,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 6 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_6_poe', @@ -81,12 +79,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 6 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000006_poe', 'unit_of_measurement': None, }) @@ -95,7 +93,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 7 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_7_poe', @@ -127,12 +124,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 7 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000007_poe', 'unit_of_measurement': None, }) @@ -141,7 +138,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 8 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_8_poe', @@ -173,12 +169,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 8 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000008_poe', 'unit_of_measurement': None, }) @@ -187,7 +183,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 2 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_2_poe', @@ -219,12 +214,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 2 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000002_poe', 'unit_of_measurement': None, }) @@ -233,7 +228,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 3 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_3_poe', @@ -265,12 +259,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 3 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000003_poe', 'unit_of_measurement': None, }) @@ -279,7 +273,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 4 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_4_poe', @@ -311,12 +304,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 4 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000004_poe', 'unit_of_measurement': None, }) @@ -325,7 +318,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test PoE Switch Port 5 PoE', - 'icon': 'mdi:ethernet', }), 'context': , 'entity_id': 'switch.test_poe_switch_port_5_poe', @@ -357,12 +349,12 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ethernet', + 'original_icon': None, 'original_name': 'Port 5 PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'poe_control', 'unique_id': '54-AF-97-00-00-01_000000000000000000000005_poe', 'unit_of_measurement': None, }) From e53dcea007d3dddc45868ab88bf2a2afa0f2a812 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:36:35 +0100 Subject: [PATCH 0467/1691] Add icon translations to Poolsense (#112184) --- homeassistant/components/poolsense/icons.json | 24 +++++++++++++++++++ homeassistant/components/poolsense/sensor.py | 9 +------ 2 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/poolsense/icons.json diff --git a/homeassistant/components/poolsense/icons.json b/homeassistant/components/poolsense/icons.json new file mode 100644 index 00000000000..fb36897af78 --- /dev/null +++ b/homeassistant/components/poolsense/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "sensor": { + "chlorine": { + "default": "mdi:pool" + }, + "water_temp": { + "default": "mdi:coolant-temperature" + }, + "chlorine_high": { + "default": "mdi:pool" + }, + "chlorine_low": { + "default": "mdi:pool" + }, + "ph_high": { + "default": "mdi:pool" + }, + "ph_low": { + "default": "mdi:pool" + } + } + } +} diff --git a/homeassistant/components/poolsense/sensor.py b/homeassistant/components/poolsense/sensor.py index c61196d9931..23235c09a84 100644 --- a/homeassistant/components/poolsense/sensor.py +++ b/homeassistant/components/poolsense/sensor.py @@ -25,11 +25,9 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( key="Chlorine", translation_key="chlorine", native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, - icon="mdi:pool", ), SensorEntityDescription( key="pH", - icon="mdi:pool", device_class=SensorDeviceClass.PH, ), SensorEntityDescription( @@ -40,36 +38,31 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="Water Temp", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - icon="mdi:coolant-temperature", + translation_key="water_temp", device_class=SensorDeviceClass.TEMPERATURE, ), SensorEntityDescription( key="Last Seen", translation_key="last_seen", - icon="mdi:clock", device_class=SensorDeviceClass.TIMESTAMP, ), SensorEntityDescription( key="Chlorine High", translation_key="chlorine_high", native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, - icon="mdi:pool", ), SensorEntityDescription( key="Chlorine Low", translation_key="chlorine_low", native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT, - icon="mdi:pool", ), SensorEntityDescription( key="pH High", translation_key="ph_high", - icon="mdi:pool", ), SensorEntityDescription( key="pH Low", translation_key="ph_low", - icon="mdi:pool", ), ) From b07e9df308a2fec26d76ee0ae046c5699d0546d1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:36:44 +0100 Subject: [PATCH 0468/1691] Add icon translations to Starline (#112241) --- .../components/starline/binary_sensor.py | 4 - homeassistant/components/starline/button.py | 1 - .../components/starline/device_tracker.py | 5 -- homeassistant/components/starline/icons.json | 79 +++++++++++++++++++ homeassistant/components/starline/lock.py | 7 -- homeassistant/components/starline/sensor.py | 5 -- homeassistant/components/starline/switch.py | 52 ++---------- 7 files changed, 86 insertions(+), 67 deletions(-) create mode 100644 homeassistant/components/starline/icons.json diff --git a/homeassistant/components/starline/binary_sensor.py b/homeassistant/components/starline/binary_sensor.py index c0fe56df71e..958435bfcfd 100644 --- a/homeassistant/components/starline/binary_sensor.py +++ b/homeassistant/components/starline/binary_sensor.py @@ -19,7 +19,6 @@ BINARY_SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = ( BinarySensorEntityDescription( key="hbrake", translation_key="hand_brake", - icon="mdi:car-brake-parking", ), BinarySensorEntityDescription( key="hood", @@ -45,19 +44,16 @@ BINARY_SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = ( key="hfree", translation_key="handsfree", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:hand-back-right", ), BinarySensorEntityDescription( key="neutral", translation_key="neutral", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:car-shift-pattern", ), BinarySensorEntityDescription( key="arm_moving_pb", translation_key="moving_ban", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:car-off", ), ) diff --git a/homeassistant/components/starline/button.py b/homeassistant/components/starline/button.py index af6a05206e0..b46faedcff0 100644 --- a/homeassistant/components/starline/button.py +++ b/homeassistant/components/starline/button.py @@ -14,7 +14,6 @@ BUTTON_TYPES: tuple[ButtonEntityDescription, ...] = ( ButtonEntityDescription( key="poke", translation_key="horn", - icon="mdi:bullhorn-outline", ), ) diff --git a/homeassistant/components/starline/device_tracker.py b/homeassistant/components/starline/device_tracker.py index 1ddcbc9373b..06703c30482 100644 --- a/homeassistant/components/starline/device_tracker.py +++ b/homeassistant/components/starline/device_tracker.py @@ -60,8 +60,3 @@ class StarlineDeviceTracker(StarlineEntity, TrackerEntity, RestoreEntity): def source_type(self) -> SourceType: """Return the source type, eg gps or router, of the device.""" return SourceType.GPS - - @property - def icon(self): - """Return the icon to use in the frontend, if any.""" - return "mdi:map-marker-outline" diff --git a/homeassistant/components/starline/icons.json b/homeassistant/components/starline/icons.json new file mode 100644 index 00000000000..b98c4178af1 --- /dev/null +++ b/homeassistant/components/starline/icons.json @@ -0,0 +1,79 @@ +{ + "entity": { + "binary_sensor": { + "hand_brake": { + "default": "mdi:car-brake-parking" + }, + "handsfree": { + "default": "mdi:hand-back-right" + }, + "neutral": { + "default": "mdi:car-shift-pattern" + }, + "moving_ban": { + "default": "mdi:car-off" + } + }, + "button": { + "horn": { + "default": "mdi:bullhorn-outline" + } + }, + "device_tracker": { + "location": { + "default": "mdi:map-marker-outline" + } + }, + "sensor": { + "balance": { + "default": "mdi:cash-multiple" + }, + "fuel": { + "default": "mdi:fuel" + }, + "errors": { + "default": "mdi:alert-octagon" + }, + "mileage": { + "default": "mdi:counter" + }, + "gps_count": { + "default": "mdi:satellite-variant" + } + }, + "switch": { + "engine": { + "default": "mdi:engine-off-outline", + "state": { + "on": "mdi:engine-outline" + } + }, + "webasto": { + "default": "mdi:radiator-off", + "state": { + "on": "mdi:radiator" + } + }, + "additional_channel": { + "default": "mdi:access-point-network-off", + "state": { + "on": "mdi:access-point-network" + } + }, + "horn": { + "default": "mdi:bullhorn-outline" + }, + "service_mode": { + "default": "mdi:car-wrench", + "state": { + "on": "mdi:wrench-clock" + } + } + } + }, + "services": { + "update_state": "mdi:reload", + "set_scan_interval": "mdi:timer", + "set_scan_obd_interval": "mdi:timer" + } +} diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index f663c472a78..104e807aff0 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -62,13 +62,6 @@ class StarlineLock(StarlineEntity, LockEntity): """ return self._device.alarm_state - @property - def icon(self) -> str: - """Icon to use in the frontend, if any.""" - return ( - "mdi:shield-check-outline" if self.is_locked else "mdi:shield-alert-outline" - ) - @property def is_locked(self) -> bool | None: """Return true if lock is locked.""" diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 4f02ee1a1f6..f9e6f7b1e0a 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -33,7 +33,6 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="balance", translation_key="balance", - icon="mdi:cash-multiple", ), SensorEntityDescription( key="ctemp", @@ -55,12 +54,10 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="fuel", translation_key="fuel", - icon="mdi:fuel", ), SensorEntityDescription( key="errors", translation_key="errors", - icon="mdi:alert-octagon", entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( @@ -68,12 +65,10 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( translation_key="mileage", native_unit_of_measurement=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, - icon="mdi:counter", ), SensorEntityDescription( key="gps_count", translation_key="gps_count", - icon="mdi:satellite-variant", native_unit_of_measurement="satellites", ), ) diff --git a/homeassistant/components/starline/switch.py b/homeassistant/components/starline/switch.py index ef24dd52c02..13de0272284 100644 --- a/homeassistant/components/starline/switch.py +++ b/homeassistant/components/starline/switch.py @@ -1,7 +1,6 @@ """Support for StarLine switch.""" from __future__ import annotations -from dataclasses import dataclass from typing import Any from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription @@ -14,53 +13,27 @@ from .account import StarlineAccount, StarlineDevice from .const import DOMAIN from .entity import StarlineEntity - -@dataclass(frozen=True) -class StarlineRequiredKeysMixin: - """Mixin for required keys.""" - - icon_on: str - icon_off: str - - -@dataclass(frozen=True) -class StarlineSwitchEntityDescription( - SwitchEntityDescription, StarlineRequiredKeysMixin -): - """Describes Starline switch entity.""" - - -SWITCH_TYPES: tuple[StarlineSwitchEntityDescription, ...] = ( - StarlineSwitchEntityDescription( +SWITCH_TYPES: tuple[SwitchEntityDescription, ...] = ( + SwitchEntityDescription( key="ign", translation_key="engine", - icon_on="mdi:engine-outline", - icon_off="mdi:engine-off-outline", ), - StarlineSwitchEntityDescription( + SwitchEntityDescription( key="webasto", translation_key="webasto", - icon_on="mdi:radiator", - icon_off="mdi:radiator-off", ), - StarlineSwitchEntityDescription( + SwitchEntityDescription( key="out", translation_key="additional_channel", - icon_on="mdi:access-point-network", - icon_off="mdi:access-point-network-off", ), # Deprecated and should be removed in 2024.8 - StarlineSwitchEntityDescription( + SwitchEntityDescription( key="poke", translation_key="horn", - icon_on="mdi:bullhorn-outline", - icon_off="mdi:bullhorn-outline", ), - StarlineSwitchEntityDescription( + SwitchEntityDescription( key="valet", translation_key="service_mode", - icon_on="mdi:wrench-clock", - icon_off="mdi:car-wrench", ), ) @@ -83,15 +56,13 @@ async def async_setup_entry( class StarlineSwitch(StarlineEntity, SwitchEntity): """Representation of a StarLine switch.""" - entity_description: StarlineSwitchEntityDescription - _attr_assumed_state = True def __init__( self, account: StarlineAccount, device: StarlineDevice, - description: StarlineSwitchEntityDescription, + description: SwitchEntityDescription, ) -> None: """Initialize the switch.""" super().__init__(account, device, description.key) @@ -109,15 +80,6 @@ class StarlineSwitch(StarlineEntity, SwitchEntity): return self._account.engine_attrs(self._device) return None - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return ( - self.entity_description.icon_on - if self.is_on - else self.entity_description.icon_off - ) - @property def is_on(self): """Return True if entity is on.""" From 8557d0326539adad662a585508171b872d7d7248 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:36:51 +0100 Subject: [PATCH 0469/1691] Add icon translations to iZone (#111848) --- homeassistant/components/izone/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/izone/icons.json diff --git a/homeassistant/components/izone/icons.json b/homeassistant/components/izone/icons.json new file mode 100644 index 00000000000..e02cd57c141 --- /dev/null +++ b/homeassistant/components/izone/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "airflow_min": "mdi:fan-minus", + "airflow_max": "mdi:fan-plus" + } +} From 3e5fdfb5705947f3669d962b5df97450688efb02 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:36:59 +0100 Subject: [PATCH 0470/1691] Add icon translations to isy994 (#111847) --- homeassistant/components/isy994/icons.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 homeassistant/components/isy994/icons.json diff --git a/homeassistant/components/isy994/icons.json b/homeassistant/components/isy994/icons.json new file mode 100644 index 00000000000..27b2ea6954e --- /dev/null +++ b/homeassistant/components/isy994/icons.json @@ -0,0 +1,12 @@ +{ + "services": { + "send_raw_node_command": "mdi:console-line", + "send_node_command": "mdi:console", + "get_zwave_parameter": "mdi:download", + "set_zwave_parameter": "mdi:upload", + "set_zwave_lock_user_code": "mdi:upload-lock", + "delete_zwave_lock_user_code": "mdi:lock-remove", + "rename_node": "mdi:pencil", + "send_program_command": "mdi:console" + } +} From 2684b1f7724fbae5466972348f2e981c4efc443c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:37:06 +0100 Subject: [PATCH 0471/1691] Add icon translations to Insteon (#111844) --- homeassistant/components/insteon/icons.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 homeassistant/components/insteon/icons.json diff --git a/homeassistant/components/insteon/icons.json b/homeassistant/components/insteon/icons.json new file mode 100644 index 00000000000..4d015e13b0d --- /dev/null +++ b/homeassistant/components/insteon/icons.json @@ -0,0 +1,15 @@ +{ + "services": { + "add_all_link": "mdi:link-variant", + "delete_all_link": "mdi:link-variant-remove", + "load_all_link_database": "mdi:database", + "print_all_link_database": "mdi:database-export", + "print_im_all_link_database": "mdi:database-export", + "x10_all_units_off": "mdi:power-off", + "x10_all_lights_on": "mdi:lightbulb-on", + "x10_all_lights_off": "mdi:lightbulb-off", + "scene_on": "mdi:palette", + "scene_off": "mdi:palette-outline", + "add_default_links": "mdi:link-variant-plus" + } +} From 8cd98a53197672e8040f5db540a32c3ef394ecf5 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 6 Mar 2024 12:37:15 +0100 Subject: [PATCH 0472/1691] Add Shelly restart required binary sensor (#112493) --- homeassistant/components/shelly/binary_sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index e9c8e909e87..6378bb4d7c8 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -206,6 +206,13 @@ RPC_SENSORS: Final = { name="Smoke", device_class=BinarySensorDeviceClass.SMOKE, ), + "restart": RpcBinarySensorDescription( + key="sys", + sub_key="restart_required", + name="Restart required", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + ), } From 8440b57349989ac9b82e070b6926bd4573c0aea6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 12:50:59 +0100 Subject: [PATCH 0473/1691] Remove entity description mixin in August (#112383) --- .../components/august/binary_sensor.py | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index 3c2ea5b3faa..c24228006cb 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -105,27 +105,15 @@ def _native_datetime() -> datetime: return datetime.now() -@dataclass(frozen=True) -class AugustBinarySensorEntityDescription(BinarySensorEntityDescription): +@dataclass(frozen=True, kw_only=True) +class AugustDoorbellBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes August binary_sensor entity.""" - -@dataclass(frozen=True) -class AugustDoorbellRequiredKeysMixin: - """Mixin for required keys.""" - value_fn: Callable[[AugustData, DoorbellDetail], bool] is_time_based: bool -@dataclass(frozen=True) -class AugustDoorbellBinarySensorEntityDescription( - BinarySensorEntityDescription, AugustDoorbellRequiredKeysMixin -): - """Describes August binary_sensor entity.""" - - -SENSOR_TYPE_DOOR = AugustBinarySensorEntityDescription( +SENSOR_TYPE_DOOR = BinarySensorEntityDescription( key="open", device_class=BinarySensorDeviceClass.DOOR, ) @@ -217,7 +205,7 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity): self, data: AugustData, device: Lock, - description: AugustBinarySensorEntityDescription, + description: BinarySensorEntityDescription, ) -> None: """Initialize the sensor.""" super().__init__(data, device) From 6be2fa1293e431dddd4ec67a4b1e2abf3fa1ae52 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 13:35:08 +0100 Subject: [PATCH 0474/1691] Add icon translations to Zerproc (#112366) --- homeassistant/components/zerproc/icons.json | 9 +++++++++ homeassistant/components/zerproc/light.py | 4 ++-- tests/components/zerproc/test_light.py | 7 ------- 3 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/zerproc/icons.json diff --git a/homeassistant/components/zerproc/icons.json b/homeassistant/components/zerproc/icons.json new file mode 100644 index 00000000000..82c95aebce6 --- /dev/null +++ b/homeassistant/components/zerproc/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "light": { + "light": { + "default": "mdi:string-lights" + } + } + } +} diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index c6be3c70e65..94a99743cdc 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -77,13 +77,13 @@ async def async_setup_entry( class ZerprocLight(LightEntity): - """Representation of an Zerproc Light.""" + """Representation of a Zerproc Light.""" _attr_color_mode = ColorMode.HS - _attr_icon = "mdi:string-lights" _attr_supported_color_modes = {ColorMode.HS} _attr_has_entity_name = True _attr_name = None + _attr_translation_key = "light" def __init__(self, light) -> None: """Initialize a Zerproc light.""" diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 662a75fb7c8..2046afca278 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -22,7 +22,6 @@ from homeassistant.components.zerproc.const import ( from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, - ATTR_ICON, ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON, @@ -101,7 +100,6 @@ async def test_init(hass: HomeAssistant, mock_entry) -> None: ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_SUPPORTED_FEATURES: 0, - ATTR_ICON: "mdi:string-lights", ATTR_COLOR_MODE: None, ATTR_BRIGHTNESS: None, ATTR_HS_COLOR: None, @@ -115,7 +113,6 @@ async def test_init(hass: HomeAssistant, mock_entry) -> None: ATTR_FRIENDLY_NAME: "LEDBlue-33445566", ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_SUPPORTED_FEATURES: 0, - ATTR_ICON: "mdi:string-lights", ATTR_COLOR_MODE: ColorMode.HS, ATTR_BRIGHTNESS: 255, ATTR_HS_COLOR: (221.176, 100.0), @@ -287,7 +284,6 @@ async def test_light_update(hass: HomeAssistant, mock_light) -> None: ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_SUPPORTED_FEATURES: 0, - ATTR_ICON: "mdi:string-lights", ATTR_COLOR_MODE: None, ATTR_BRIGHTNESS: None, ATTR_HS_COLOR: None, @@ -311,7 +307,6 @@ async def test_light_update(hass: HomeAssistant, mock_light) -> None: ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_SUPPORTED_FEATURES: 0, - ATTR_ICON: "mdi:string-lights", } with patch.object( @@ -329,7 +324,6 @@ async def test_light_update(hass: HomeAssistant, mock_light) -> None: ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_SUPPORTED_FEATURES: 0, - ATTR_ICON: "mdi:string-lights", ATTR_COLOR_MODE: None, ATTR_BRIGHTNESS: None, ATTR_HS_COLOR: None, @@ -352,7 +346,6 @@ async def test_light_update(hass: HomeAssistant, mock_light) -> None: ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF", ATTR_SUPPORTED_COLOR_MODES: [ColorMode.HS], ATTR_SUPPORTED_FEATURES: 0, - ATTR_ICON: "mdi:string-lights", ATTR_COLOR_MODE: ColorMode.HS, ATTR_BRIGHTNESS: 220, ATTR_HS_COLOR: (261.429, 31.818), From a23dbe47276b29c1d1638b8a26e50d6120f2d8fc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 13:36:01 +0100 Subject: [PATCH 0475/1691] Add icon translations to Whois (#112356) --- homeassistant/components/whois/icons.json | 24 +++++++++++++++++++ homeassistant/components/whois/sensor.py | 6 ----- .../whois/snapshots/test_sensor.ambr | 18 +++++--------- 3 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 homeassistant/components/whois/icons.json diff --git a/homeassistant/components/whois/icons.json b/homeassistant/components/whois/icons.json new file mode 100644 index 00000000000..459ae252138 --- /dev/null +++ b/homeassistant/components/whois/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "sensor": { + "admin": { + "default": "mdi:account-star" + }, + "days_until_expiration": { + "default": "mdi:calendar-clock" + }, + "owner": { + "default": "mdi:account" + }, + "registrant": { + "default": "mdi:account-edit" + }, + "registrar": { + "default": "mdi:store" + }, + "reseller": { + "default": "mdi:store" + } + } + } +} diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 7118701a868..2de6509698f 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -61,7 +61,6 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="admin", translation_key="admin", - icon="mdi:account-star", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, value_fn=lambda domain: getattr(domain, "admin", None), @@ -76,7 +75,6 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="days_until_expiration", translation_key="days_until_expiration", - icon="mdi:calendar-clock", native_unit_of_measurement=UnitOfTime.DAYS, value_fn=_days_until_expiration, ), @@ -97,7 +95,6 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="owner", translation_key="owner", - icon="mdi:account", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, value_fn=lambda domain: getattr(domain, "owner", None), @@ -105,7 +102,6 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="registrant", translation_key="registrant", - icon="mdi:account-edit", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, value_fn=lambda domain: getattr(domain, "registrant", None), @@ -113,7 +109,6 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="registrar", translation_key="registrar", - icon="mdi:store", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, value_fn=lambda domain: domain.registrar if domain.registrar else None, @@ -121,7 +116,6 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( WhoisSensorEntityDescription( key="reseller", translation_key="reseller", - icon="mdi:store", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, value_fn=lambda domain: getattr(domain, "reseller", None), diff --git a/tests/components/whois/snapshots/test_sensor.ambr b/tests/components/whois/snapshots/test_sensor.ambr index 99299ae36da..52909edf0b1 100644 --- a/tests/components/whois/snapshots/test_sensor.ambr +++ b/tests/components/whois/snapshots/test_sensor.ambr @@ -3,7 +3,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'home-assistant.io Admin', - 'icon': 'mdi:account-star', }), 'context': , 'entity_id': 'sensor.home_assistant_io_admin', @@ -35,7 +34,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:account-star', + 'original_icon': None, 'original_name': 'Admin', 'platform': 'whois', 'previous_unique_id': None, @@ -156,7 +155,6 @@ 'attributes': ReadOnlyDict({ 'expires': '2023-01-01T00:00:00', 'friendly_name': 'home-assistant.io Days until expiration', - 'icon': 'mdi:calendar-clock', 'name_servers': 'ns1.example.com ns2.example.com', 'registrar': 'My Registrar', 'unit_of_measurement': , @@ -192,7 +190,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:calendar-clock', + 'original_icon': None, 'original_name': 'Days until expiration', 'platform': 'whois', 'previous_unique_id': None, @@ -388,7 +386,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'home-assistant.io Owner', - 'icon': 'mdi:account', }), 'context': , 'entity_id': 'sensor.home_assistant_io_owner', @@ -420,7 +417,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:account', + 'original_icon': None, 'original_name': 'Owner', 'platform': 'whois', 'previous_unique_id': None, @@ -464,7 +461,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'home-assistant.io Registrant', - 'icon': 'mdi:account-edit', }), 'context': , 'entity_id': 'sensor.home_assistant_io_registrant', @@ -496,7 +492,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:account-edit', + 'original_icon': None, 'original_name': 'Registrant', 'platform': 'whois', 'previous_unique_id': None, @@ -540,7 +536,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'home-assistant.io Registrar', - 'icon': 'mdi:store', }), 'context': , 'entity_id': 'sensor.home_assistant_io_registrar', @@ -572,7 +567,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:store', + 'original_icon': None, 'original_name': 'Registrar', 'platform': 'whois', 'previous_unique_id': None, @@ -616,7 +611,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'home-assistant.io Reseller', - 'icon': 'mdi:store', }), 'context': , 'entity_id': 'sensor.home_assistant_io_reseller', @@ -648,7 +642,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:store', + 'original_icon': None, 'original_name': 'Reseller', 'platform': 'whois', 'previous_unique_id': None, From 0cdc90468e57b704e5e396b0c53d7b1a2087d543 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 13:38:32 +0100 Subject: [PATCH 0476/1691] Add icon translations to Tailscale (#112302) --- .../components/tailscale/binary_sensor.py | 6 ---- homeassistant/components/tailscale/icons.json | 29 +++++++++++++++++++ homeassistant/components/tailscale/sensor.py | 1 - .../tailscale/test_binary_sensor.py | 14 +-------- tests/components/tailscale/test_sensor.py | 10 +------ 5 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 homeassistant/components/tailscale/icons.json diff --git a/homeassistant/components/tailscale/binary_sensor.py b/homeassistant/components/tailscale/binary_sensor.py index 00fa21279ea..2424837354d 100644 --- a/homeassistant/components/tailscale/binary_sensor.py +++ b/homeassistant/components/tailscale/binary_sensor.py @@ -38,42 +38,36 @@ BINARY_SENSORS: tuple[TailscaleBinarySensorEntityDescription, ...] = ( TailscaleBinarySensorEntityDescription( key="client_supports_hair_pinning", translation_key="client_supports_hair_pinning", - icon="mdi:wan", entity_category=EntityCategory.DIAGNOSTIC, is_on_fn=lambda device: device.client_connectivity.client_supports.hair_pinning, ), TailscaleBinarySensorEntityDescription( key="client_supports_ipv6", translation_key="client_supports_ipv6", - icon="mdi:wan", entity_category=EntityCategory.DIAGNOSTIC, is_on_fn=lambda device: device.client_connectivity.client_supports.ipv6, ), TailscaleBinarySensorEntityDescription( key="client_supports_pcp", translation_key="client_supports_pcp", - icon="mdi:wan", entity_category=EntityCategory.DIAGNOSTIC, is_on_fn=lambda device: device.client_connectivity.client_supports.pcp, ), TailscaleBinarySensorEntityDescription( key="client_supports_pmp", translation_key="client_supports_pmp", - icon="mdi:wan", entity_category=EntityCategory.DIAGNOSTIC, is_on_fn=lambda device: device.client_connectivity.client_supports.pmp, ), TailscaleBinarySensorEntityDescription( key="client_supports_udp", translation_key="client_supports_udp", - icon="mdi:wan", entity_category=EntityCategory.DIAGNOSTIC, is_on_fn=lambda device: device.client_connectivity.client_supports.udp, ), TailscaleBinarySensorEntityDescription( key="client_supports_upnp", translation_key="client_supports_upnp", - icon="mdi:wan", entity_category=EntityCategory.DIAGNOSTIC, is_on_fn=lambda device: device.client_connectivity.client_supports.upnp, ), diff --git a/homeassistant/components/tailscale/icons.json b/homeassistant/components/tailscale/icons.json new file mode 100644 index 00000000000..a5d3bec1884 --- /dev/null +++ b/homeassistant/components/tailscale/icons.json @@ -0,0 +1,29 @@ +{ + "entity": { + "binary_sensor": { + "client_supports_hair_pinning": { + "default": "mdi:wan" + }, + "client_supports_ipv6": { + "default": "mdi:wan" + }, + "client_supports_pcp": { + "default": "mdi:wan" + }, + "client_supports_pmp": { + "default": "mdi:wan" + }, + "client_supports_udp": { + "default": "mdi:wan" + }, + "client_supports_upnp": { + "default": "mdi:wan" + } + }, + "sensor": { + "ip": { + "default": "mdi:ip-network" + } + } + } +} diff --git a/homeassistant/components/tailscale/sensor.py b/homeassistant/components/tailscale/sensor.py index 5d2e615945b..61044eda56c 100644 --- a/homeassistant/components/tailscale/sensor.py +++ b/homeassistant/components/tailscale/sensor.py @@ -39,7 +39,6 @@ SENSORS: tuple[TailscaleSensorEntityDescription, ...] = ( TailscaleSensorEntityDescription( key="ip", translation_key="ip", - icon="mdi:ip-network", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.addresses[0] if device.addresses else None, ), diff --git a/tests/components/tailscale/test_binary_sensor.py b/tests/components/tailscale/test_binary_sensor.py index b258abd1ed4..bd7ca7087bb 100644 --- a/tests/components/tailscale/test_binary_sensor.py +++ b/tests/components/tailscale/test_binary_sensor.py @@ -5,12 +5,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, ) from homeassistant.components.tailscale.const import DOMAIN -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_FRIENDLY_NAME, - ATTR_ICON, - EntityCategory, -) +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -34,7 +29,6 @@ async def test_tailscale_binary_sensors( assert state.state == STATE_ON assert state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Client" assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.UPDATE - assert ATTR_ICON not in state.attributes state = hass.states.get("binary_sensor.frencks_iphone_supports_hairpinning") entry = entity_registry.async_get( @@ -49,7 +43,6 @@ async def test_tailscale_binary_sensors( state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Supports hairpinning" ) - assert state.attributes.get(ATTR_ICON) == "mdi:wan" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.frencks_iphone_supports_ipv6") @@ -60,7 +53,6 @@ async def test_tailscale_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Supports IPv6" - assert state.attributes.get(ATTR_ICON) == "mdi:wan" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.frencks_iphone_supports_pcp") @@ -71,7 +63,6 @@ async def test_tailscale_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Supports PCP" - assert state.attributes.get(ATTR_ICON) == "mdi:wan" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.frencks_iphone_supports_nat_pmp") @@ -82,7 +73,6 @@ async def test_tailscale_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Supports NAT-PMP" - assert state.attributes.get(ATTR_ICON) == "mdi:wan" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.frencks_iphone_supports_udp") @@ -93,7 +83,6 @@ async def test_tailscale_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_ON assert state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Supports UDP" - assert state.attributes.get(ATTR_ICON) == "mdi:wan" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.frencks_iphone_supports_upnp") @@ -104,7 +93,6 @@ async def test_tailscale_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "frencks-iphone Supports UPnP" - assert state.attributes.get(ATTR_ICON) == "mdi:wan" assert ATTR_DEVICE_CLASS not in state.attributes assert entry.device_id diff --git a/tests/components/tailscale/test_sensor.py b/tests/components/tailscale/test_sensor.py index 1ea91d1b4b6..7fce057d3e7 100644 --- a/tests/components/tailscale/test_sensor.py +++ b/tests/components/tailscale/test_sensor.py @@ -1,12 +1,7 @@ """Tests for the sensors provided by the Tailscale integration.""" from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.tailscale.const import DOMAIN -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_FRIENDLY_NAME, - ATTR_ICON, - EntityCategory, -) +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -30,7 +25,6 @@ async def test_tailscale_sensors( assert state.state == "2022-02-25T09:49:06+00:00" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "router Expires" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP - assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.router_last_seen") entry = entity_registry.async_get("sensor.router_last_seen") @@ -41,7 +35,6 @@ async def test_tailscale_sensors( assert state.state == "2021-11-15T20:37:03+00:00" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "router Last seen" assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.TIMESTAMP - assert ATTR_ICON not in state.attributes state = hass.states.get("sensor.router_ip_address") entry = entity_registry.async_get("sensor.router_ip_address") @@ -51,7 +44,6 @@ async def test_tailscale_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == "100.11.11.112" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "router IP address" - assert state.attributes.get(ATTR_ICON) == "mdi:ip-network" assert ATTR_DEVICE_CLASS not in state.attributes assert entry.device_id From 780428fde60c2765c4bfeb546460af8be1fdc4ed Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 6 Mar 2024 13:47:16 +0100 Subject: [PATCH 0477/1691] Disable Shelly diagnostic binary sensor (#112497) --- homeassistant/components/shelly/binary_sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 6378bb4d7c8..41029e2de24 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -211,6 +211,7 @@ RPC_SENSORS: Final = { sub_key="restart_required", name="Restart required", device_class=BinarySensorDeviceClass.PROBLEM, + entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), } From 807c3ca76b424f71697f085562db00dc4e82c955 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 6 Mar 2024 13:56:47 +0100 Subject: [PATCH 0478/1691] Add custom integration block list (#112481) * Add custom integration block list * Fix typo * Add version condition * Add block reason, simplify blocked versions, add tests * Change logic for OK versions * Add link to custom integration's issue tracker * Add missing file --------- Co-authored-by: Martin Hjelmare --- homeassistant/loader.py | 64 +++++++++++++++++-- tests/test_loader.py | 52 +++++++++++++++ .../test_blocked_version/manifest.json | 4 ++ 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 tests/testing_config/custom_components/test_blocked_version/manifest.json diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 06bac608aec..ebea95eaa75 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -82,6 +82,19 @@ BASE_PRELOAD_PLATFORMS = [ ] +@dataclass +class BlockedIntegration: + """Blocked custom integration details.""" + + lowest_good_version: AwesomeVersion | None + reason: str + + +BLOCKED_CUSTOM_INTEGRATIONS: dict[str, BlockedIntegration] = { + # Added in 2024.3.0 because of https://github.com/home-assistant/core/issues/112464 + "start_time": BlockedIntegration(None, "breaks Home Assistant") +} + DATA_COMPONENTS = "components" DATA_INTEGRATIONS = "integrations" DATA_MISSING_PLATFORMS = "missing_platforms" @@ -643,6 +656,7 @@ class Integration: return integration _LOGGER.warning(CUSTOM_WARNING, integration.domain) + if integration.version is None: _LOGGER.error( ( @@ -679,6 +693,21 @@ class Integration: integration.version, ) return None + + if blocked := BLOCKED_CUSTOM_INTEGRATIONS.get(integration.domain): + if _version_blocked(integration.version, blocked): + _LOGGER.error( + ( + "Version %s of custom integration '%s' %s and was blocked " + "from loading, please %s" + ), + integration.version, + integration.domain, + blocked.reason, + async_suggest_report_issue(None, integration=integration), + ) + return None + return integration return None @@ -1210,6 +1239,20 @@ class Integration: return f"" +def _version_blocked( + integration_version: AwesomeVersion, + blocked_integration: BlockedIntegration, +) -> bool: + """Return True if the integration version is blocked.""" + if blocked_integration.lowest_good_version is None: + return True + + if integration_version >= blocked_integration.lowest_good_version: + return False + + return True + + def _resolve_integrations_from_root( hass: HomeAssistant, root_module: ModuleType, domains: Iterable[str] ) -> dict[str, Integration]: @@ -1565,6 +1608,7 @@ def is_component_module_loaded(hass: HomeAssistant, module: str) -> bool: def async_get_issue_tracker( hass: HomeAssistant | None, *, + integration: Integration | None = None, integration_domain: str | None = None, module: str | None = None, ) -> str | None: @@ -1572,19 +1616,23 @@ def async_get_issue_tracker( issue_tracker = ( "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue" ) - if not integration_domain and not module: + if not integration and not integration_domain and not module: # If we know nothing about the entity, suggest opening an issue on HA core return issue_tracker - if hass and integration_domain: + if not integration and (hass and integration_domain): with suppress(IntegrationNotLoaded): integration = async_get_loaded_integration(hass, integration_domain) - if not integration.is_built_in: - return integration.issue_tracker + + if integration and not integration.is_built_in: + return integration.issue_tracker if module and "custom_components" in module: return None + if integration: + integration_domain = integration.domain + if integration_domain: issue_tracker += f"+label%3A%22integration%3A+{integration_domain}%22" return issue_tracker @@ -1594,15 +1642,21 @@ def async_get_issue_tracker( def async_suggest_report_issue( hass: HomeAssistant | None, *, + integration: Integration | None = None, integration_domain: str | None = None, module: str | None = None, ) -> str: """Generate a blurb asking the user to file a bug report.""" issue_tracker = async_get_issue_tracker( - hass, integration_domain=integration_domain, module=module + hass, + integration=integration, + integration_domain=integration_domain, + module=module, ) if not issue_tracker: + if integration: + integration_domain = integration.domain if not integration_domain: return "report it to the custom integration author" return ( diff --git a/tests/test_loader.py b/tests/test_loader.py index 22379b340d4..6fa4de1da9c 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -6,6 +6,7 @@ import threading from typing import Any from unittest.mock import MagicMock, Mock, patch +from awesomeversion import AwesomeVersion import pytest from homeassistant import loader @@ -167,6 +168,57 @@ async def test_custom_integration_version_not_valid( ) in caplog.text +@pytest.mark.parametrize( + "blocked_versions", + [ + loader.BlockedIntegration(None, "breaks Home Assistant"), + loader.BlockedIntegration(AwesomeVersion("2.0.0"), "breaks Home Assistant"), + ], +) +async def test_custom_integration_version_blocked( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, + blocked_versions, +) -> None: + """Test that we log a warning when custom integrations have a blocked version.""" + with patch.dict( + loader.BLOCKED_CUSTOM_INTEGRATIONS, {"test_blocked_version": blocked_versions} + ): + with pytest.raises(loader.IntegrationNotFound): + await loader.async_get_integration(hass, "test_blocked_version") + + assert ( + "Version 1.0.0 of custom integration 'test_blocked_version' breaks" + " Home Assistant and was blocked from loading, please report it to the" + " author of the 'test_blocked_version' custom integration" + ) in caplog.text + + +@pytest.mark.parametrize( + "blocked_versions", + [ + loader.BlockedIntegration(AwesomeVersion("0.9.9"), "breaks Home Assistant"), + loader.BlockedIntegration(AwesomeVersion("1.0.0"), "breaks Home Assistant"), + ], +) +async def test_custom_integration_version_not_blocked( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + enable_custom_integrations: None, + blocked_versions, +) -> None: + """Test that we log a warning when custom integrations have a blocked version.""" + with patch.dict( + loader.BLOCKED_CUSTOM_INTEGRATIONS, {"test_blocked_version": blocked_versions} + ): + await loader.async_get_integration(hass, "test_blocked_version") + + assert ( + "Version 1.0.0 of custom integration 'test_blocked_version'" + ) not in caplog.text + + async def test_get_integration(hass: HomeAssistant) -> None: """Test resolving integration.""" with pytest.raises(loader.IntegrationNotLoaded): diff --git a/tests/testing_config/custom_components/test_blocked_version/manifest.json b/tests/testing_config/custom_components/test_blocked_version/manifest.json new file mode 100644 index 00000000000..8359c4fe510 --- /dev/null +++ b/tests/testing_config/custom_components/test_blocked_version/manifest.json @@ -0,0 +1,4 @@ +{ + "domain": "test_blocked_version", + "version": "1.0.0" +} From 0153c1840f03b986380d96cc68bf48876fb03287 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 14:22:50 +0100 Subject: [PATCH 0479/1691] Remove entity description mixin in EasyEnergy (#112407) --- homeassistant/components/easyenergy/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/easyenergy/sensor.py b/homeassistant/components/easyenergy/sensor.py index d719eac17af..04bdeb0f747 100644 --- a/homeassistant/components/easyenergy/sensor.py +++ b/homeassistant/components/easyenergy/sensor.py @@ -29,21 +29,14 @@ from .const import DOMAIN, SERVICE_TYPE_DEVICE_NAMES from .coordinator import EasyEnergyData, EasyEnergyDataUpdateCoordinator -@dataclass(frozen=True) -class EasyEnergySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EasyEnergySensorEntityDescription(SensorEntityDescription): + """Describes easyEnergy sensor entity.""" value_fn: Callable[[EasyEnergyData], float | datetime | None] service_type: str -@dataclass(frozen=True) -class EasyEnergySensorEntityDescription( - SensorEntityDescription, EasyEnergySensorEntityDescriptionMixin -): - """Describes easyEnergy sensor entity.""" - - SENSORS: tuple[EasyEnergySensorEntityDescription, ...] = ( EasyEnergySensorEntityDescription( key="current_hour_price", From 86503526eed9cb19ca243b7ebc8894c253cce426 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 14:23:00 +0100 Subject: [PATCH 0480/1691] Remove entity description mixin in EnergyZero (#112412) --- homeassistant/components/energyzero/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/energyzero/sensor.py b/homeassistant/components/energyzero/sensor.py index 005abb62e91..6438dc6475a 100644 --- a/homeassistant/components/energyzero/sensor.py +++ b/homeassistant/components/energyzero/sensor.py @@ -29,21 +29,14 @@ from .const import DOMAIN, SERVICE_TYPE_DEVICE_NAMES from .coordinator import EnergyZeroData, EnergyZeroDataUpdateCoordinator -@dataclass(frozen=True) -class EnergyZeroSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class EnergyZeroSensorEntityDescription(SensorEntityDescription): + """Describes an EnergyZero sensor entity.""" value_fn: Callable[[EnergyZeroData], float | datetime | None] service_type: str -@dataclass(frozen=True) -class EnergyZeroSensorEntityDescription( - SensorEntityDescription, EnergyZeroSensorEntityDescriptionMixin -): - """Describes a Pure Energie sensor entity.""" - - SENSORS: tuple[EnergyZeroSensorEntityDescription, ...] = ( EnergyZeroSensorEntityDescription( key="current_hour_price", From 5400b4055a066ee0979b413b080e4b77cd3f3523 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 14:24:05 +0100 Subject: [PATCH 0481/1691] Remove entity description mixin in Dremel 3D Printer (#112405) --- .../components/dremel_3d_printer/binary_sensor.py | 13 +++---------- .../components/dremel_3d_printer/button.py | 13 +++---------- .../components/dremel_3d_printer/sensor.py | 14 +++----------- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/dremel_3d_printer/binary_sensor.py b/homeassistant/components/dremel_3d_printer/binary_sensor.py index 22c2a1a9557..f1fc31eac60 100644 --- a/homeassistant/components/dremel_3d_printer/binary_sensor.py +++ b/homeassistant/components/dremel_3d_printer/binary_sensor.py @@ -19,20 +19,13 @@ from .const import DOMAIN from .entity import Dremel3DPrinterEntity -@dataclass(frozen=True) -class Dremel3DPrinterBinarySensorEntityMixin: - """Mixin for Dremel 3D Printer binary sensor.""" +@dataclass(frozen=True, kw_only=True) +class Dremel3DPrinterBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes a Dremel 3D Printer binary sensor.""" value_fn: Callable[[Dremel3DPrinter], bool] -@dataclass(frozen=True) -class Dremel3DPrinterBinarySensorEntityDescription( - BinarySensorEntityDescription, Dremel3DPrinterBinarySensorEntityMixin -): - """Describes a Dremel 3D Printer binary sensor.""" - - BINARY_SENSOR_TYPES: tuple[Dremel3DPrinterBinarySensorEntityDescription, ...] = ( Dremel3DPrinterBinarySensorEntityDescription( key="door", diff --git a/homeassistant/components/dremel_3d_printer/button.py b/homeassistant/components/dremel_3d_printer/button.py index b2ea103f78b..0a312de1468 100644 --- a/homeassistant/components/dremel_3d_printer/button.py +++ b/homeassistant/components/dremel_3d_printer/button.py @@ -16,20 +16,13 @@ from .const import DOMAIN from .entity import Dremel3DPrinterEntity -@dataclass(frozen=True) -class Dremel3DPrinterButtonEntityMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class Dremel3DPrinterButtonEntityDescription(ButtonEntityDescription): + """Describes a Dremel 3D Printer button entity.""" press_fn: Callable[[Dremel3DPrinter], None] -@dataclass(frozen=True) -class Dremel3DPrinterButtonEntityDescription( - ButtonEntityDescription, Dremel3DPrinterButtonEntityMixin -): - """Describes a Dremel 3D Printer button entity.""" - - BUTTON_TYPES: tuple[Dremel3DPrinterButtonEntityDescription, ...] = ( Dremel3DPrinterButtonEntityDescription( key="cancel_job", diff --git a/homeassistant/components/dremel_3d_printer/sensor.py b/homeassistant/components/dremel_3d_printer/sensor.py index 98e4cd0e85d..0bcdfa904e8 100644 --- a/homeassistant/components/dremel_3d_printer/sensor.py +++ b/homeassistant/components/dremel_3d_printer/sensor.py @@ -31,19 +31,11 @@ from .const import ATTR_EXTRUDER, ATTR_PLATFORM, DOMAIN from .entity import Dremel3DPrinterEntity -@dataclass(frozen=True) -class Dremel3DPrinterSensorEntityMixin: - """Mixin for Dremel 3D Printer sensor.""" - - value_fn: Callable[[Dremel3DPrinter, str], StateType | datetime] - - -@dataclass(frozen=True) -class Dremel3DPrinterSensorEntityDescription( - SensorEntityDescription, Dremel3DPrinterSensorEntityMixin -): +@dataclass(frozen=True, kw_only=True) +class Dremel3DPrinterSensorEntityDescription(SensorEntityDescription): """Describes a Dremel 3D Printer sensor.""" + value_fn: Callable[[Dremel3DPrinter, str], StateType | datetime] available_fn: Callable[[Dremel3DPrinter, str], bool] = lambda api, _: True From 1daaffc01f03f5d8a93d708c4faaba4b645f9d5b Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:24:53 +0100 Subject: [PATCH 0482/1691] Bump pytedee_async to 0.2.15 (#112495) --- homeassistant/components/tedee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json index 1776e3b7ab2..a3e29e1b40f 100644 --- a/homeassistant/components/tedee/manifest.json +++ b/homeassistant/components/tedee/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/tedee", "iot_class": "local_push", - "requirements": ["pytedee-async==0.2.13"] + "requirements": ["pytedee-async==0.2.15"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5e30dabff84..4470a8dec34 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2179,7 +2179,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.13 +pytedee-async==0.2.15 # homeassistant.components.tfiac pytfiac==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f7aaf1ab55..fc704fff6bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1697,7 +1697,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.13 +pytedee-async==0.2.15 # homeassistant.components.motionmount python-MotionMount==0.3.1 From ffcb06beb92363208fb9e9efd536ba2055b4c61b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 6 Mar 2024 14:56:50 +0100 Subject: [PATCH 0483/1691] Allow start_time >= 1.1.7 (#112500) --- homeassistant/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index ebea95eaa75..cba7df278f2 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -92,7 +92,7 @@ class BlockedIntegration: BLOCKED_CUSTOM_INTEGRATIONS: dict[str, BlockedIntegration] = { # Added in 2024.3.0 because of https://github.com/home-assistant/core/issues/112464 - "start_time": BlockedIntegration(None, "breaks Home Assistant") + "start_time": BlockedIntegration(AwesomeVersion("1.1.7"), "breaks Home Assistant") } DATA_COMPONENTS = "components" From f3684575442c26498a1b985c6d2eeb35411b35bf Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 16:05:35 +0100 Subject: [PATCH 0484/1691] Add icon translations to Sonarr (#112233) --- homeassistant/components/sonarr/icons.json | 24 ++++++++++++++++++++++ homeassistant/components/sonarr/sensor.py | 6 ------ tests/components/sonarr/test_sensor.py | 7 ------- 3 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/sonarr/icons.json diff --git a/homeassistant/components/sonarr/icons.json b/homeassistant/components/sonarr/icons.json new file mode 100644 index 00000000000..7980db52b29 --- /dev/null +++ b/homeassistant/components/sonarr/icons.json @@ -0,0 +1,24 @@ +{ + "entity": { + "sensor": { + "commands": { + "default": "mdi:code-braces" + }, + "diskspace": { + "default": "mdi:harddisk" + }, + "queue": { + "default": "mdi:download" + }, + "series": { + "default": "mdi:television" + }, + "upcoming": { + "default": "mdi:television" + }, + "wanted": { + "default": "mdi:television" + } + } + } +} diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 5753d0d23ea..4aea2a73ede 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -89,7 +89,6 @@ SENSOR_TYPES: dict[str, SonarrSensorEntityDescription[Any]] = { "commands": SonarrSensorEntityDescription[list[Command]]( key="commands", translation_key="commands", - icon="mdi:code-braces", native_unit_of_measurement="Commands", entity_registry_enabled_default=False, value_fn=len, @@ -98,7 +97,6 @@ SENSOR_TYPES: dict[str, SonarrSensorEntityDescription[Any]] = { "diskspace": SonarrSensorEntityDescription[list[Diskspace]]( key="diskspace", translation_key="diskspace", - icon="mdi:harddisk", native_unit_of_measurement=UnitOfInformation.GIGABYTES, device_class=SensorDeviceClass.DATA_SIZE, entity_registry_enabled_default=False, @@ -108,7 +106,6 @@ SENSOR_TYPES: dict[str, SonarrSensorEntityDescription[Any]] = { "queue": SonarrSensorEntityDescription[SonarrQueue]( key="queue", translation_key="queue", - icon="mdi:download", native_unit_of_measurement="Episodes", entity_registry_enabled_default=False, value_fn=lambda data: data.totalRecords, @@ -117,7 +114,6 @@ SENSOR_TYPES: dict[str, SonarrSensorEntityDescription[Any]] = { "series": SonarrSensorEntityDescription[list[SonarrSeries]]( key="series", translation_key="series", - icon="mdi:television", native_unit_of_measurement="Series", entity_registry_enabled_default=False, value_fn=len, @@ -131,7 +127,6 @@ SENSOR_TYPES: dict[str, SonarrSensorEntityDescription[Any]] = { "upcoming": SonarrSensorEntityDescription[list[SonarrCalendar]]( key="upcoming", translation_key="upcoming", - icon="mdi:television", native_unit_of_measurement="Episodes", value_fn=len, attributes_fn=lambda data: { @@ -141,7 +136,6 @@ SENSOR_TYPES: dict[str, SonarrSensorEntityDescription[Any]] = { "wanted": SonarrSensorEntityDescription[SonarrWantedMissing]( key="wanted", translation_key="wanted", - icon="mdi:television", native_unit_of_measurement="Episodes", entity_registry_enabled_default=False, value_fn=lambda data: data.totalRecords, diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index e44081f94bf..eccc535ff66 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -8,7 +8,6 @@ import pytest from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import ( - ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, STATE_UNAVAILABLE, UnitOfInformation, @@ -50,41 +49,35 @@ async def test_sensors( state = hass.states.get("sensor.sonarr_commands") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:code-braces" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Commands" assert state.state == "2" state = hass.states.get("sensor.sonarr_disk_space") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:harddisk" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfInformation.GIGABYTES assert state.attributes.get("C:\\") == "263.10/465.42GB (56.53%)" assert state.state == "263.10" state = hass.states.get("sensor.sonarr_queue") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:download" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" assert state.attributes.get("The Andy Griffith Show S01E01") == "100.00%" assert state.state == "1" state = hass.states.get("sensor.sonarr_shows") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Series" assert state.attributes.get("The Andy Griffith Show") == "0/0 Episodes" assert state.state == "1" state = hass.states.get("sensor.sonarr_upcoming") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" assert state.attributes.get("Bob's Burgers") == "S04E11" assert state.state == "1" state = hass.states.get("sensor.sonarr_wanted") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" assert state.attributes.get("Bob's Burgers S04E11") == "2014-01-26T17:30:00-08:00" assert ( From 656ef143daf5184a8aa340d320dca7d36473566e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 16:27:13 +0100 Subject: [PATCH 0485/1691] Add icon translations to Wallbox (#112350) * Add icon translations to Wallbox * fix --- homeassistant/components/wallbox/icons.json | 27 +++++++++++++++++++++ homeassistant/components/wallbox/sensor.py | 7 ------ tests/components/wallbox/test_sensor.py | 3 +-- 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/wallbox/icons.json diff --git a/homeassistant/components/wallbox/icons.json b/homeassistant/components/wallbox/icons.json new file mode 100644 index 00000000000..359e05cb441 --- /dev/null +++ b/homeassistant/components/wallbox/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "sensor": { + "charging_speed": { + "default": "mdi:speedometer" + }, + "added_range": { + "default": "mdi:map-marker-distance" + }, + "cost": { + "default": "mdi:ev-station" + }, + "current_mode": { + "default": "mdi:ev-station" + }, + "depot_price": { + "default": "mdi:ev-station" + }, + "energy_price": { + "default": "mdi:ev-station" + }, + "status_description": { + "default": "mdi:ev-station" + } + } + } +} diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index 50312487e15..7fb9a488244 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -78,14 +78,12 @@ SENSOR_TYPES: dict[str, WallboxSensorEntityDescription] = { CHARGER_CHARGING_SPEED_KEY: WallboxSensorEntityDescription( key=CHARGER_CHARGING_SPEED_KEY, translation_key=CHARGER_CHARGING_SPEED_KEY, - icon="mdi:speedometer", precision=0, state_class=SensorStateClass.MEASUREMENT, ), CHARGER_ADDED_RANGE_KEY: WallboxSensorEntityDescription( key=CHARGER_ADDED_RANGE_KEY, translation_key=CHARGER_ADDED_RANGE_KEY, - icon="mdi:map-marker-distance", precision=0, native_unit_of_measurement=UnitOfLength.KILOMETERS, device_class=SensorDeviceClass.DISTANCE, @@ -110,7 +108,6 @@ SENSOR_TYPES: dict[str, WallboxSensorEntityDescription] = { CHARGER_COST_KEY: WallboxSensorEntityDescription( key=CHARGER_COST_KEY, translation_key=CHARGER_COST_KEY, - icon="mdi:ev-station", state_class=SensorStateClass.TOTAL_INCREASING, ), CHARGER_STATE_OF_CHARGE_KEY: WallboxSensorEntityDescription( @@ -123,26 +120,22 @@ SENSOR_TYPES: dict[str, WallboxSensorEntityDescription] = { CHARGER_CURRENT_MODE_KEY: WallboxSensorEntityDescription( key=CHARGER_CURRENT_MODE_KEY, translation_key=CHARGER_CURRENT_MODE_KEY, - icon="mdi:ev-station", ), CHARGER_DEPOT_PRICE_KEY: WallboxSensorEntityDescription( key=CHARGER_DEPOT_PRICE_KEY, translation_key=CHARGER_DEPOT_PRICE_KEY, - icon="mdi:ev-station", precision=2, state_class=SensorStateClass.MEASUREMENT, ), CHARGER_ENERGY_PRICE_KEY: WallboxSensorEntityDescription( key=CHARGER_ENERGY_PRICE_KEY, translation_key=CHARGER_ENERGY_PRICE_KEY, - icon="mdi:ev-station", precision=2, state_class=SensorStateClass.MEASUREMENT, ), CHARGER_STATUS_DESCRIPTION_KEY: WallboxSensorEntityDescription( key=CHARGER_STATUS_DESCRIPTION_KEY, translation_key=CHARGER_STATUS_DESCRIPTION_KEY, - icon="mdi:ev-station", ), CHARGER_MAX_CHARGING_CURRENT_KEY: WallboxSensorEntityDescription( key=CHARGER_MAX_CHARGING_CURRENT_KEY, diff --git a/tests/components/wallbox/test_sensor.py b/tests/components/wallbox/test_sensor.py index ca12e1d9ac3..ad9ed6c0706 100644 --- a/tests/components/wallbox/test_sensor.py +++ b/tests/components/wallbox/test_sensor.py @@ -1,5 +1,5 @@ """Test Wallbox Switch component.""" -from homeassistant.const import CONF_ICON, CONF_UNIT_OF_MEASUREMENT, UnitOfPower +from homeassistant.const import CONF_UNIT_OF_MEASUREMENT, UnitOfPower from homeassistant.core import HomeAssistant from . import setup_integration @@ -24,7 +24,6 @@ async def test_wallbox_sensor_class( assert state.name == "Wallbox WallboxName Charging power" state = hass.states.get(MOCK_SENSOR_CHARGING_SPEED_ID) - assert state.attributes[CONF_ICON] == "mdi:speedometer" assert state.name == "Wallbox WallboxName Charging speed" # Test round with precision '0' works From c8f39911cc716c6e5b304ba72c5683e7cc55c478 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 17:50:41 +0100 Subject: [PATCH 0486/1691] Enable some PERF rules (#112498) Co-authored-by: Franck Nijhof --- homeassistant/components/insteon/utils.py | 2 +- homeassistant/components/matter/diagnostics.py | 2 +- homeassistant/components/mqtt/__init__.py | 2 +- homeassistant/components/prometheus/__init__.py | 2 +- homeassistant/components/systemmonitor/util.py | 2 +- homeassistant/components/velbus/cover.py | 5 +---- pyproject.toml | 3 +++ tests/components/bayesian/test_binary_sensor.py | 6 +++--- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index d7cbe676eee..1438b6c9c52 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -145,7 +145,7 @@ def add_insteon_events(hass: HomeAssistant, device: Device) -> None: for name_or_group, event in device.events.items(): if isinstance(name_or_group, int): - for _, event in device.events[name_or_group].items(): + for event in device.events[name_or_group].values(): _register_event(event, async_fire_insteon_event) else: _register_event(event, async_fire_insteon_event) diff --git a/homeassistant/components/matter/diagnostics.py b/homeassistant/components/matter/diagnostics.py index 8846a75b42a..d875f37dcae 100644 --- a/homeassistant/components/matter/diagnostics.py +++ b/homeassistant/components/matter/diagnostics.py @@ -21,7 +21,7 @@ def redact_matter_attributes(node_data: dict[str, Any]) -> dict[str, Any]: """Redact Matter cluster attribute.""" redacted = deepcopy(node_data) for attribute_to_redact in ATTRIBUTES_TO_REDACT: - for attribute_path, _value in redacted["attributes"].items(): + for attribute_path in redacted["attributes"]: _, cluster_id, attribute_id = parse_attribute_path(attribute_path) if cluster_id != attribute_to_redact.cluster_id: continue diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 1412ad63e68..7da8636b19b 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -429,7 +429,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ] await asyncio.gather(*tasks) - for _, component in mqtt_data.reload_handlers.items(): + for component in mqtt_data.reload_handlers.values(): component() # Fire event diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 86163704797..317b5c569d4 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -259,7 +259,7 @@ class PrometheusMetrics: self, entity_id: str, friendly_name: str | None = None ) -> None: """Remove labelsets matching the given entity id from all metrics.""" - for _, metric in self._metrics.items(): + for metric in self._metrics.values(): for sample in cast(list[prometheus_client.Metric], metric.collect())[ 0 ].samples: diff --git a/homeassistant/components/systemmonitor/util.py b/homeassistant/components/systemmonitor/util.py index c67d4771ff4..75f8d2b2df1 100644 --- a/homeassistant/components/systemmonitor/util.py +++ b/homeassistant/components/systemmonitor/util.py @@ -57,7 +57,7 @@ def get_all_network_interfaces(hass: HomeAssistant) -> set[str]: """Return all network interfaces on system.""" psutil_wrapper: ha_psutil = hass.data[DOMAIN] interfaces: set[str] = set() - for interface, _ in psutil_wrapper.psutil.net_if_addrs().items(): + for interface in psutil_wrapper.psutil.net_if_addrs(): if interface.startswith("veth"): # Don't load docker virtual network interfaces continue diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 46881fcdcaf..7c6f15a808d 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -26,10 +26,7 @@ async def async_setup_entry( """Set up Velbus switch based on config_entry.""" await hass.data[DOMAIN][entry.entry_id]["tsk"] cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - entities = [] - for channel in cntrl.get_all("cover"): - entities.append(VelbusCover(channel)) - async_add_entities(entities) + async_add_entities(VelbusCover(channel) for channel in cntrl.get_all("cover")) class VelbusCover(VelbusEntity, CoverEntity): diff --git a/pyproject.toml b/pyproject.toml index 765adc656d9..e50f633485b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -600,6 +600,9 @@ select = [ "N804", # First argument of a class method should be named cls "N805", # First argument of a method should be named self "N815", # Variable {name} in class scope should not be mixedCase + "PERF101", # Do not cast an iterable to list before iterating over it + "PERF102", # When using only the {subset} of a dict use the {subset}() method + "PERF203", # try-except within a loop incurs performance overhead "PGH004", # Use specific rule codes when using noqa "PLC0414", # Useless import alias. Import alias does not rename original package. "PLC", # pylint diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index e4c3e38695a..dba0769bc82 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -402,7 +402,7 @@ async def test_multiple_observations(hass: HomeAssistant) -> None: state = hass.states.get("binary_sensor.test_binary") - for _, attrs in state.attributes.items(): + for attrs in state.attributes.values(): json.dumps(attrs) assert state.attributes.get("occurred_observation_entities") == [] assert state.attributes.get("probability") == 0.2 @@ -474,7 +474,7 @@ async def test_multiple_numeric_observations(hass: HomeAssistant) -> None: state = hass.states.get("binary_sensor.test_binary") - for _, attrs in state.attributes.items(): + for attrs in state.attributes.values(): json.dumps(attrs) assert state.attributes.get("occurred_observation_entities") == [] assert state.attributes.get("probability") == 0.1 @@ -782,7 +782,7 @@ async def test_state_attributes_are_serializable(hass: HomeAssistant) -> None: state.attributes.get("occurred_observation_entities") ) - for _, attrs in state.attributes.items(): + for attrs in state.attributes.values(): json.dumps(attrs) From 770e48d51265d9c613cadd066d7f53d9b0e2498d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 6 Mar 2024 18:28:12 +0100 Subject: [PATCH 0487/1691] Simplify color mode logic in Tuya light (#110327) * Simplify color mode logic in Tuya light * Remove exclusion from LightEntity.__should_report_light_issue * Fix test --- homeassistant/components/light/__init__.py | 4 +-- homeassistant/components/tuya/light.py | 29 +++++++++++++--------- tests/components/light/test_init.py | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 795975b5c3e..93a3b23f504 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1336,5 +1336,5 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """Return if light color mode issues should be reported.""" if not self.platform: return True - # philips_js and tuya have known issues, we don't need users to open issues - return self.platform.platform_name not in {"philips_js", "tuya"} + # philips_js has known issues, we don't need users to open issues + return self.platform.platform_name not in {"philips_js"} diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 98d704326ae..55833d50bdd 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -14,6 +14,7 @@ from homeassistant.components.light import ( ColorMode, LightEntity, LightEntityDescription, + filter_supported_color_modes, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory @@ -442,6 +443,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): _color_data_type: ColorTypeData | None = None _color_mode: DPCode | None = None _color_temp: IntegerTypeData | None = None + _fixed_color_mode: ColorMode | None = None def __init__( self, @@ -453,7 +455,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): super().__init__(device, device_manager) self.entity_description = description self._attr_unique_id = f"{super().unique_id}{description.key}" - self._attr_supported_color_modes: set[ColorMode] = set() + color_modes: set[ColorMode] = {ColorMode.ONOFF} # Determine DPCodes self._color_mode_dpcode = self.find_dpcode( @@ -464,7 +466,7 @@ class TuyaLightEntity(TuyaEntity, LightEntity): description.brightness, dptype=DPType.INTEGER, prefer_function=True ): self._brightness = int_type - self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS) + color_modes.add(ColorMode.BRIGHTNESS) self._brightness_max = self.find_dpcode( description.brightness_max, dptype=DPType.INTEGER ) @@ -476,13 +478,13 @@ class TuyaLightEntity(TuyaEntity, LightEntity): description.color_temp, dptype=DPType.INTEGER, prefer_function=True ): self._color_temp = int_type - self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP) + color_modes.add(ColorMode.COLOR_TEMP) if ( dpcode := self.find_dpcode(description.color_data, prefer_function=True) ) and self.get_dptype(dpcode) == DPType.JSON: self._color_data_dpcode = dpcode - self._attr_supported_color_modes.add(ColorMode.HS) + color_modes.add(ColorMode.HS) if dpcode in self.device.function: values = cast(str, self.device.function[dpcode].values) else: @@ -503,8 +505,10 @@ class TuyaLightEntity(TuyaEntity, LightEntity): ): self._color_data_type = DEFAULT_COLOR_TYPE_DATA_V2 - if not self._attr_supported_color_modes: - self._attr_supported_color_modes = {ColorMode.ONOFF} + self._attr_supported_color_modes = filter_supported_color_modes(color_modes) + if len(self._attr_supported_color_modes) == 1: + # If the light supports only a single color mode, set it now + self._fixed_color_mode = next(iter(self._attr_supported_color_modes)) @property def is_on(self) -> bool: @@ -698,18 +702,19 @@ class TuyaLightEntity(TuyaEntity, LightEntity): @property def color_mode(self) -> ColorMode: """Return the color_mode of the light.""" - # We consider it to be in HS color mode, when work mode is anything + if self._fixed_color_mode: + # The light supports only a single color mode, return it + return self._fixed_color_mode + + # The light supports both color temperature and HS, determine which mode the + # light is in. We consider it to be in HS color mode, when work mode is anything # else than "white". if ( self._color_mode_dpcode and self.device.status.get(self._color_mode_dpcode) != WorkMode.WHITE ): return ColorMode.HS - if self._color_temp: - return ColorMode.COLOR_TEMP - if self._brightness: - return ColorMode.BRIGHTNESS - return ColorMode.ONOFF + return ColorMode.COLOR_TEMP def _get_color_data(self) -> ColorData | None: """Get current color data from device.""" diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index ca25611f890..b2292f59bb1 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -2791,7 +2791,7 @@ def test_report_invalid_color_mode( ( light.ColorMode.ONOFF, {light.ColorMode.ONOFF, light.ColorMode.BRIGHTNESS}, - "tuya", # We don't log issues for tuya + "philips_js", # We don't log issues for philips_js False, ), ], From 7096701cab833ce8c95c4b068b5906349ae15fef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 08:10:26 -1000 Subject: [PATCH 0488/1691] Use an eager task for Sonos async_update_device_properties (#112488) If the device does not need to be polled for the battery state which is only present on portable speakers, the task will never suspend and never need to be scheduled on the event loop --- homeassistant/components/sonos/speaker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 3c9e4692fdc..d541da40e15 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -456,7 +456,9 @@ class SonosSpeaker: def async_dispatch_device_properties(self, event: SonosEvent) -> None: """Update device properties from an event.""" self.event_stats.process(event) - self.hass.async_create_task(self.async_update_device_properties(event)) + self.hass.async_create_task( + self.async_update_device_properties(event), eager_start=True + ) async def async_update_device_properties(self, event: SonosEvent) -> None: """Update device properties from an event.""" From 0f3838e7a81348a6eda735625118df999d8d2128 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 08:55:47 -1000 Subject: [PATCH 0489/1691] Fix sonos overloading the executor when there are many devices (#112482) --- homeassistant/components/sonos/speaker.py | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index d541da40e15..e32f595a13a 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -8,7 +8,7 @@ import datetime from functools import partial import logging import time -from typing import Any, cast +from typing import TYPE_CHECKING, Any, cast import defusedxml.ElementTree as ET from soco.core import SoCo @@ -64,6 +64,9 @@ from .helpers import soco_error from .media import SonosMedia from .statistics import ActivityStatistics, EventStatistics +if TYPE_CHECKING: + from . import SonosData + NEVER_TIME = -1200.0 RESUB_COOLDOWN_SECONDS = 10.0 EVENT_CHARGING = { @@ -97,6 +100,7 @@ class SonosSpeaker: ) -> None: """Initialize a SonosSpeaker.""" self.hass = hass + self.data: SonosData = hass.data[DATA_SONOS] self.soco = soco self.websocket: SonosWebsocket | None = None self.household_id: str = soco.household_id @@ -183,7 +187,7 @@ class SonosSpeaker: ) dispatch_pairs: tuple[tuple[str, Callable[..., Any]], ...] = ( (SONOS_CHECK_ACTIVITY, self.async_check_activity), - (SONOS_SPEAKER_ADDED, self.update_group_for_uid), + (SONOS_SPEAKER_ADDED, self.async_update_group_for_uid), (f"{SONOS_REBOOTED}-{self.soco.uid}", self.async_rebooted), (f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", self.speaker_activity), (f"{SONOS_VANISHED}-{self.soco.uid}", self.async_vanished), @@ -272,12 +276,12 @@ class SonosSpeaker: @property def alarms(self) -> SonosAlarms: """Return the SonosAlarms instance for this household.""" - return self.hass.data[DATA_SONOS].alarms[self.household_id] + return self.data.alarms[self.household_id] @property def favorites(self) -> SonosFavorites: """Return the SonosFavorites instance for this household.""" - return self.hass.data[DATA_SONOS].favorites[self.household_id] + return self.data.favorites[self.household_id] @property def is_coordinator(self) -> bool: @@ -496,9 +500,7 @@ class SonosSpeaker: "x-rincon:" ): new_coordinator_uid = av_transport_uri.split(":")[-1] - if new_coordinator_speaker := self.hass.data[DATA_SONOS].discovered.get( - new_coordinator_uid - ): + if new_coordinator_speaker := self.data.discovered.get(new_coordinator_uid): _LOGGER.debug( "Media update coordinator (%s) received for %s", new_coordinator_speaker.zone_name, @@ -657,7 +659,7 @@ class SonosSpeaker: await self.async_unsubscribe() - self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid) + self.data.discovery_known.discard(self.soco.uid) async def async_vanished(self, reason: str) -> None: """Handle removal of speaker when marked as vanished.""" @@ -784,15 +786,16 @@ class SonosSpeaker: """Update group topology when polling.""" self.hass.add_job(self.create_update_groups_coro()) - def update_group_for_uid(self, uid: str) -> None: + @callback + def async_update_group_for_uid(self, uid: str) -> None: """Update group topology if uid is missing.""" if uid not in self._group_members_missing: return - missing_zone = self.hass.data[DATA_SONOS].discovered[uid].zone_name + missing_zone = self.data.discovered[uid].zone_name _LOGGER.debug( "%s was missing, adding to %s group", missing_zone, self.zone_name ) - self.update_groups() + self.hass.async_create_task(self.create_update_groups_coro(), eager_start=True) @callback def async_update_groups(self, event: SonosEvent) -> None: @@ -866,7 +869,7 @@ class SonosSpeaker: sonos_group_entities = [] for uid in group: - speaker = self.hass.data[DATA_SONOS].discovered.get(uid) + speaker = self.data.discovered.get(uid) if speaker: self._group_members_missing.discard(uid) sonos_group.append(speaker) @@ -894,10 +897,7 @@ class SonosSpeaker: self.async_write_entity_states() for joined_uid in group[1:]: - joined_speaker: SonosSpeaker = self.hass.data[ - DATA_SONOS - ].discovered.get(joined_uid) - if joined_speaker: + if joined_speaker := self.data.discovered.get(joined_uid): joined_speaker.coordinator = self joined_speaker.sonos_group = sonos_group joined_speaker.sonos_group_entities = sonos_group_entities @@ -908,13 +908,13 @@ class SonosSpeaker: async def _async_handle_group_event(event: SonosEvent | None) -> None: """Get async lock and handle event.""" - async with self.hass.data[DATA_SONOS].topology_condition: + async with self.data.topology_condition: group = await _async_extract_group(event) if self.soco.uid == group[0]: _async_regroup(group) - self.hass.data[DATA_SONOS].topology_condition.notify_all() + self.data.topology_condition.notify_all() return _async_handle_group_event(event) From d50e4f6645aa1781d067f4ad8cbd44c5c2d59c4f Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:22:08 +0100 Subject: [PATCH 0490/1691] Add snapshot tests to webmin (#112518) add snapshot tests to webmin --- .coveragerc | 1 - tests/components/webmin/conftest.py | 19 +- .../webmin/snapshots/test_sensor.ambr | 369 ++++++++++++++++++ tests/components/webmin/test_init.py | 16 +- tests/components/webmin/test_sensor.py | 28 ++ 5 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 tests/components/webmin/snapshots/test_sensor.ambr create mode 100644 tests/components/webmin/test_sensor.py diff --git a/.coveragerc b/.coveragerc index 55179b740cf..72021c60ac1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1592,7 +1592,6 @@ omit = homeassistant/components/weatherflow_cloud/const.py homeassistant/components/weatherflow_cloud/coordinator.py homeassistant/components/weatherflow_cloud/weather.py - homeassistant/components/webmin/sensor.py homeassistant/components/wiffi/__init__.py homeassistant/components/wiffi/binary_sensor.py homeassistant/components/wiffi/sensor.py diff --git a/tests/components/webmin/conftest.py b/tests/components/webmin/conftest.py index 196ce40408d..b41e31be574 100644 --- a/tests/components/webmin/conftest.py +++ b/tests/components/webmin/conftest.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, patch import pytest -from homeassistant.components.webmin.const import DEFAULT_PORT +from homeassistant.components.webmin.const import DEFAULT_PORT, DOMAIN from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -13,6 +13,9 @@ from homeassistant.const import ( CONF_USERNAME, CONF_VERIFY_SSL, ) +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, load_json_object_fixture TEST_USER_INPUT = { CONF_HOST: "192.168.1.1", @@ -31,3 +34,17 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: "homeassistant.components.webmin.async_setup_entry", return_value=True ) as mock_setup: yield mock_setup + + +async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry: + """Set up the Webmin integration in Home Assistant.""" + entry = MockConfigEntry(domain=DOMAIN, options=TEST_USER_INPUT, title="name") + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.webmin.helpers.WebminInstance.update", + return_value=load_json_object_fixture("webmin_update.json", DOMAIN), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + return entry diff --git a/tests/components/webmin/snapshots/test_sensor.ambr b/tests/components/webmin/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..285bcdf1f9b --- /dev/null +++ b/tests/components/webmin/snapshots/test_sensor.ambr @@ -0,0 +1,369 @@ +# serializer version: 1 +# name: test_sensor[sensor.192_168_1_1_load_15m-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_load_15m', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Load (15m)', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'load_15m', + 'unique_id': '12:34:56:78:9a:bc_load_15m', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.192_168_1_1_load_15m-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': '192.168.1.1 Load (15m)', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_load_15m', + 'last_changed': , + 'last_updated': , + 'state': '1.0', + }) +# --- +# name: test_sensor[sensor.192_168_1_1_load_1m-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_load_1m', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Load (1m)', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'load_1m', + 'unique_id': '12:34:56:78:9a:bc_load_1m', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.192_168_1_1_load_1m-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': '192.168.1.1 Load (1m)', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_load_1m', + 'last_changed': , + 'last_updated': , + 'state': '0.98', + }) +# --- +# name: test_sensor[sensor.192_168_1_1_load_5m-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_load_5m', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Load (5m)', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'load_5m', + 'unique_id': '12:34:56:78:9a:bc_load_5m', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.192_168_1_1_load_5m-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': '192.168.1.1 Load (5m)', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_load_5m', + 'last_changed': , + 'last_updated': , + 'state': '1.02', + }) +# --- +# name: test_sensor[sensor.192_168_1_1_memory_free-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_memory_free', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Memory free', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'mem_free', + 'unique_id': '12:34:56:78:9a:bc_mem_free', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.192_168_1_1_memory_free-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': '192.168.1.1 Memory free', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_memory_free', + 'last_changed': , + 'last_updated': , + 'state': '24.9505462646484', + }) +# --- +# name: test_sensor[sensor.192_168_1_1_memory_total-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_memory_total', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Memory total', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'mem_total', + 'unique_id': '12:34:56:78:9a:bc_mem_total', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.192_168_1_1_memory_total-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': '192.168.1.1 Memory total', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_memory_total', + 'last_changed': , + 'last_updated': , + 'state': '31.2490539550781', + }) +# --- +# name: test_sensor[sensor.192_168_1_1_swap_free-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_swap_free', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Swap free', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'swap_free', + 'unique_id': '12:34:56:78:9a:bc_swap_free', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.192_168_1_1_swap_free-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': '192.168.1.1 Swap free', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_swap_free', + 'last_changed': , + 'last_updated': , + 'state': '1.86260986328125', + }) +# --- +# name: test_sensor[sensor.192_168_1_1_swap_total-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.192_168_1_1_swap_total', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Swap total', + 'platform': 'webmin', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'swap_total', + 'unique_id': '12:34:56:78:9a:bc_swap_total', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.192_168_1_1_swap_total-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': '192.168.1.1 Swap total', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.192_168_1_1_swap_total', + 'last_changed': , + 'last_updated': , + 'state': '1.86260986328125', + }) +# --- diff --git a/tests/components/webmin/test_init.py b/tests/components/webmin/test_init.py index 21963a2120c..4a6e7ce4994 100644 --- a/tests/components/webmin/test_init.py +++ b/tests/components/webmin/test_init.py @@ -1,28 +1,16 @@ """Tests for the Webmin integration.""" -from unittest.mock import patch - from homeassistant.components.webmin.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from .conftest import TEST_USER_INPUT - -from tests.common import MockConfigEntry, load_json_object_fixture +from .conftest import async_init_integration async def test_unload_entry(hass: HomeAssistant) -> None: """Test successful unload of entry.""" - entry = MockConfigEntry(domain=DOMAIN, options=TEST_USER_INPUT, title="name") - entry.add_to_hass(hass) + entry = await async_init_integration(hass) - with patch( - "homeassistant.components.webmin.helpers.WebminInstance.update", - return_value=load_json_object_fixture("webmin_update.json", DOMAIN), - ): - await hass.config_entries.async_setup(entry.entry_id) - - await hass.async_block_till_done() assert entry.state is ConfigEntryState.LOADED assert await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/webmin/test_sensor.py b/tests/components/webmin/test_sensor.py new file mode 100644 index 00000000000..528aebca990 --- /dev/null +++ b/tests/components/webmin/test_sensor.py @@ -0,0 +1,28 @@ +"""Test cases for the Webmin sensors.""" +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .conftest import async_init_integration + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test the sensor entities and states.""" + + entry = await async_init_integration(hass) + + entity_entries = er.async_entries_for_config_entry(entity_registry, entry.entry_id) + + assert entity_entries + + for entity_entry in entity_entries: + assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") + assert (state := hass.states.get(entity_entry.entity_id)) + assert state == snapshot(name=f"{entity_entry.entity_id}-state") From 457e27ecfdbe5b3f202e9518c14b47d9b77e4a1e Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 6 Mar 2024 13:47:58 -0600 Subject: [PATCH 0491/1691] Bump intents to 2024.3.6 (#112515) --- homeassistant/components/conversation/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index 6f484941a3d..4e3339d227b 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.2.28"] + "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.6"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b2563831301..dfb9b8a8c57 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -31,7 +31,7 @@ hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240306.0 -home-assistant-intents==2024.2.28 +home-assistant-intents==2024.3.6 httpx==0.27.0 ifaddr==0.2.0 Jinja2==3.1.3 diff --git a/requirements_all.txt b/requirements_all.txt index 4470a8dec34..35f4d96a65d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ holidays==0.44 home-assistant-frontend==20240306.0 # homeassistant.components.conversation -home-assistant-intents==2024.2.28 +home-assistant-intents==2024.3.6 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc704fff6bf..cff88c23f22 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ holidays==0.44 home-assistant-frontend==20240306.0 # homeassistant.components.conversation -home-assistant-intents==2024.2.28 +home-assistant-intents==2024.3.6 # homeassistant.components.home_connect homeconnect==0.7.2 From 248f2ac2fb522af938ea1d6832d7fe8c821e9e6d Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 6 Mar 2024 20:53:44 +0100 Subject: [PATCH 0492/1691] Remove hourly weather entity from Environment Canada (#112447) * Remove hourly weather entity from Environment Canada * Remove from strings --- .../environment_canada/strings.json | 3 --- .../components/environment_canada/weather.py | 20 ++++++++----------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/environment_canada/strings.json b/homeassistant/components/environment_canada/strings.json index eb9ec24dad0..fc03550b64e 100644 --- a/homeassistant/components/environment_canada/strings.json +++ b/homeassistant/components/environment_canada/strings.json @@ -110,9 +110,6 @@ } }, "weather": { - "hourly_forecast": { - "name": "Hourly forecast" - }, "forecast": { "name": "Forecast" } diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index b4b5d27f45f..1af05287192 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -68,17 +68,15 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][config_entry.entry_id]["weather_coordinator"] entity_registry = er.async_get(hass) - entities = [ECWeather(coordinator, False)] - - # Add hourly entity to legacy config entries - if entity_registry.async_get_entity_id( + # Remove hourly entity from legacy config entries + if hourly_entity_id := entity_registry.async_get_entity_id( WEATHER_DOMAIN, DOMAIN, _calculate_unique_id(config_entry.unique_id, True), ): - entities.append(ECWeather(coordinator, True)) + entity_registry.async_remove(hourly_entity_id) - async_add_entities(entities) + async_add_entities([ECWeather(coordinator)]) def _calculate_unique_id(config_entry_unique_id: str | None, hourly: bool) -> str: @@ -98,17 +96,15 @@ class ECWeather(SingleCoordinatorWeatherEntity): WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY ) - def __init__(self, coordinator, hourly): + def __init__(self, coordinator): """Initialize Environment Canada weather.""" super().__init__(coordinator) self.ec_data = coordinator.ec_data self._attr_attribution = self.ec_data.metadata["attribution"] - self._attr_translation_key = "hourly_forecast" if hourly else "forecast" + self._attr_translation_key = "forecast" self._attr_unique_id = _calculate_unique_id( - coordinator.config_entry.unique_id, hourly + coordinator.config_entry.unique_id, False ) - self._attr_entity_registry_enabled_default = not hourly - self._hourly = hourly self._attr_device_info = device_info(coordinator.config_entry) @property @@ -180,7 +176,7 @@ class ECWeather(SingleCoordinatorWeatherEntity): @property def forecast(self) -> list[Forecast] | None: """Return the forecast array.""" - return get_forecast(self.ec_data, self._hourly) + return get_forecast(self.ec_data, False) @callback def _async_forecast_daily(self) -> list[Forecast] | None: From 96b2d4f9f0db053baf676f795e8efd17520dd67d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 09:54:01 -1000 Subject: [PATCH 0493/1691] Reduce executor jobs needed to setup filesize (#112490) * Reduce executor jobs needed to setup filesize Move the _get_full_path check into the coordinator so everything can happen in the executor at setup time * Reduce executor jobs needed to setup filesize Move the _get_full_path check into the coordinator so everything can happen in the executor at setup time * Update homeassistant/components/filesize/coordinator.py Co-authored-by: Paulus Schoutsen --------- Co-authored-by: Paulus Schoutsen --- homeassistant/components/filesize/__init__.py | 20 +------------ .../components/filesize/coordinator.py | 28 ++++++++++++++++--- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/filesize/__init__.py b/homeassistant/components/filesize/__init__.py index 9d7cc99421f..4a3e35b4e29 100644 --- a/homeassistant/components/filesize/__init__.py +++ b/homeassistant/components/filesize/__init__.py @@ -1,35 +1,17 @@ """The filesize component.""" from __future__ import annotations -import pathlib - from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_FILE_PATH from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from .const import PLATFORMS from .coordinator import FileSizeCoordinator -def _get_full_path(hass: HomeAssistant, path: str) -> str: - """Check if path is valid, allowed and return full path.""" - get_path = pathlib.Path(path) - if not get_path.exists() or not get_path.is_file(): - raise ConfigEntryNotReady(f"Can not access file {path}") - - if not hass.config.is_allowed_path(path): - raise ConfigEntryNotReady(f"Filepath {path} is not valid or allowed") - - return str(get_path.absolute()) - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up from a config entry.""" - full_path = await hass.async_add_executor_job( - _get_full_path, hass, entry.data[CONF_FILE_PATH] - ) - coordinator = FileSizeCoordinator(hass, full_path) + coordinator = FileSizeCoordinator(hass, entry.data[CONF_FILE_PATH]) await coordinator.async_config_entry_first_refresh() await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/filesize/coordinator.py b/homeassistant/components/filesize/coordinator.py index 75411f84975..87e866b89d9 100644 --- a/homeassistant/components/filesize/coordinator.py +++ b/homeassistant/components/filesize/coordinator.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import datetime, timedelta import logging import os +import pathlib from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -17,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) class FileSizeCoordinator(DataUpdateCoordinator[dict[str, int | float | datetime]]): """Filesize coordinator.""" - def __init__(self, hass: HomeAssistant, path: str) -> None: + def __init__(self, hass: HomeAssistant, unresolved_path: str) -> None: """Initialize filesize coordinator.""" super().__init__( hass, @@ -26,15 +27,34 @@ class FileSizeCoordinator(DataUpdateCoordinator[dict[str, int | float | datetime update_interval=timedelta(seconds=60), always_update=False, ) - self._path = path + self._unresolved_path = unresolved_path + self._path: pathlib.Path | None = None - async def _async_update_data(self) -> dict[str, float | int | datetime]: + def _get_full_path(self) -> pathlib.Path: + """Check if path is valid, allowed and return full path.""" + path = self._unresolved_path + get_path = pathlib.Path(path) + if not self.hass.config.is_allowed_path(path): + raise UpdateFailed(f"Filepath {path} is not valid or allowed") + + if not get_path.exists() or not get_path.is_file(): + raise UpdateFailed(f"Can not access file {path}") + + return get_path.absolute() + + def _update(self) -> os.stat_result: """Fetch file information.""" + if not self._path: + self._path = self._get_full_path() + try: - statinfo = await self.hass.async_add_executor_job(os.stat, self._path) + return self._path.stat() except OSError as error: raise UpdateFailed(f"Can not retrieve file statistics {error}") from error + async def _async_update_data(self) -> dict[str, float | int | datetime]: + """Fetch file information.""" + statinfo = await self.hass.async_add_executor_job(self._update) size = statinfo.st_size last_updated = dt_util.utc_from_timestamp(statinfo.st_mtime) From b8e39bd9689f1599bd93f9d1607ca53dc23317ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 11:06:55 -1000 Subject: [PATCH 0494/1691] Increase bootstrap preload test timeout (#112520) --- tests/test_bootstrap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 9599e249f40..550eb4bffee 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1045,6 +1045,7 @@ async def test_pre_import_no_requirements(hass: HomeAssistant) -> None: assert not integration.requirements +@pytest.mark.timeout(20) async def test_bootstrap_does_not_preload_stage_1_integrations() -> None: """Test that the bootstrap does not preload stage 1 integrations. From 675b1a392bd4a8f81fa4fdf66c24eb31dc568378 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 11:46:23 -1000 Subject: [PATCH 0495/1691] Fix homekit creating executor jobs to resolve default listen ips (#112522) --- homeassistant/components/homekit/__init__.py | 5 ++++- tests/components/homekit/test_homekit.py | 16 +++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index a60f55e8bb0..afbfc784b19 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -7,6 +7,7 @@ from copy import deepcopy import ipaddress import logging import os +import socket from typing import Any, cast from aiohttp import web @@ -149,6 +150,8 @@ PORT_CLEANUP_CHECK_INTERVAL_SECS = 1 _HOMEKIT_CONFIG_UPDATE_TIME = ( 10 # number of seconds to wait for homekit to see the c# change ) +_HAS_IPV6 = hasattr(socket, "AF_INET6") +_DEFAULT_BIND = ["0.0.0.0", "::"] if _HAS_IPV6 else ["0.0.0.0"] def _has_all_unique_names_and_ports( @@ -308,7 +311,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: _LOGGER.debug("Begin setup HomeKit for %s", name) # ip_address and advertise_ip are yaml only - ip_address = conf.get(CONF_IP_ADDRESS, [None]) + ip_address = conf.get(CONF_IP_ADDRESS, _DEFAULT_BIND) advertise_ips: list[str] = conf.get( CONF_ADVERTISE_IP ) or await network.async_get_announce_addresses(hass) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 158cb29239c..6c01dd9f681 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -80,6 +80,8 @@ from tests.common import MockConfigEntry, get_fixture_path IP_ADDRESS = "127.0.0.1" +DEFAULT_LISTEN = ["0.0.0.0", "::"] + def generate_filter( include_domains, @@ -173,7 +175,7 @@ async def test_setup_min(hass: HomeAssistant, mock_async_zeroconf: None) -> None hass, BRIDGE_NAME, DEFAULT_PORT, - [None], + DEFAULT_LISTEN, ANY, ANY, {}, @@ -215,7 +217,7 @@ async def test_removing_entry( hass, BRIDGE_NAME, DEFAULT_PORT, - [None], + DEFAULT_LISTEN, ANY, ANY, {}, @@ -354,7 +356,7 @@ async def test_homekit_with_single_advertise_ips( ANY, entry.title, loop=hass.loop, - address=[None], + address=DEFAULT_LISTEN, port=ANY, persist_file=ANY, advertised_address="1.3.4.4", @@ -393,7 +395,7 @@ async def test_homekit_with_many_advertise_ips( ANY, entry.title, loop=hass.loop, - address=[None], + address=DEFAULT_LISTEN, port=ANY, persist_file=ANY, advertised_address=["1.3.4.4", "4.3.2.2"], @@ -1678,7 +1680,7 @@ async def test_yaml_updates_update_config_entry_for_name( hass, BRIDGE_NAME, 12345, - [None], + DEFAULT_LISTEN, ANY, ANY, {}, @@ -2049,7 +2051,7 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: hass, "reloadable", 12345, - [None], + DEFAULT_LISTEN, ANY, False, {}, @@ -2084,7 +2086,7 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: hass, "reloadable", 45678, - [None], + DEFAULT_LISTEN, ANY, False, {}, From 581a4f82b29a0b2d70a99c12d1aee274ff209708 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 11:46:38 -1000 Subject: [PATCH 0496/1691] Avoid importing counter and proximity integrations in logbook (#112528) --- homeassistant/components/logbook/const.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/logbook/const.py b/homeassistant/components/logbook/const.py index 2d9911117f9..3d4c0b3615f 100644 --- a/homeassistant/components/logbook/const.py +++ b/homeassistant/components/logbook/const.py @@ -2,14 +2,17 @@ from __future__ import annotations from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED -from homeassistant.components.counter import DOMAIN as COUNTER_DOMAIN -from homeassistant.components.proximity import DOMAIN as PROXIMITY_DOMAIN from homeassistant.components.script import EVENT_SCRIPT_STARTED from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import EVENT_CALL_SERVICE, EVENT_LOGBOOK_ENTRY +# # Domains that are always continuous -ALWAYS_CONTINUOUS_DOMAINS = {COUNTER_DOMAIN, PROXIMITY_DOMAIN} +# +# These are hard coded here to avoid importing +# the entire counter and proximity integrations +# to get the name of the domain. +ALWAYS_CONTINUOUS_DOMAINS = {"counter", "proximity"} # Domains that are continuous if there is a UOM set on the entity CONDITIONALLY_CONTINUOUS_DOMAINS = {SENSOR_DOMAIN} From 74dabff4a7cfbbfbf540e91464503d68d654d171 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 6 Mar 2024 23:06:41 +0100 Subject: [PATCH 0497/1691] Add icon translations to Waze Travel Time (#112352) * Add icon translations to Waze Travel Time * Fix tests --- homeassistant/components/waze_travel_time/icons.json | 9 +++++++++ homeassistant/components/waze_travel_time/sensor.py | 2 +- tests/components/waze_travel_time/test_sensor.py | 1 - 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/waze_travel_time/icons.json diff --git a/homeassistant/components/waze_travel_time/icons.json b/homeassistant/components/waze_travel_time/icons.json new file mode 100644 index 00000000000..54d3183363e --- /dev/null +++ b/homeassistant/components/waze_travel_time/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "waze_travel_time": { + "default": "mdi:car" + } + } + } +} diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index ef372e5fd33..5204c0b1075 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -90,6 +90,7 @@ class WazeTravelTime(SensorEntity): identifiers={(DOMAIN, DOMAIN)}, configuration_url="https://www.waze.com", ) + _attr_translation_key = "waze_travel_time" def __init__( self, @@ -105,7 +106,6 @@ class WazeTravelTime(SensorEntity): self._attr_name = name self._origin = origin self._destination = destination - self._attr_icon = "mdi:car" self._state = None async def async_added_to_hass(self) -> None: diff --git a/tests/components/waze_travel_time/test_sensor.py b/tests/components/waze_travel_time/test_sensor.py index adcc334889d..60e5535fdba 100644 --- a/tests/components/waze_travel_time/test_sensor.py +++ b/tests/components/waze_travel_time/test_sensor.py @@ -74,7 +74,6 @@ async def test_sensor(hass: HomeAssistant) -> None: hass.states.get("sensor.waze_travel_time").attributes["unit_of_measurement"] == "min" ) - assert hass.states.get("sensor.waze_travel_time").attributes["icon"] == "mdi:car" @pytest.mark.parametrize( From 67a177679e2839aff586a943404446e9eb2078bf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 13:33:44 -1000 Subject: [PATCH 0498/1691] Log tasks that are being waited on when startup is blocked (#112542) --- homeassistant/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/core.py b/homeassistant/core.py index 1fbdb91cfb6..9a373f7a159 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -482,8 +482,10 @@ class HomeAssistant: " phase. We're going to continue anyway. Please report the" " following info at" " https://github.com/home-assistant/core/issues: %s" + " The system is waiting for tasks: %s" ), ", ".join(self.config.components), + self._tasks, ) # Allow automations to set up the start triggers before changing state From 1772e5257c616b6837fa13fb709110a4ffbcddd0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 15:37:41 -1000 Subject: [PATCH 0499/1691] Move analytics setup to later stage to avoid delaying frontend startup (#112535) Move analytics setup to stage 1 to avoid delaying frontend startup analytics was only needed in the frontend startup phase for onboarding. Its very unlikely the user will be able to complete the onboarding steps and get to the analytics screen before analytics is done loading so we can delay loading it until stage 1. To be absolutely sure that it is ready, the core_config step in onboarding will wait to proceed if it is some how still being setup --- homeassistant/bootstrap.py | 1 + .../components/onboarding/manifest.json | 2 +- homeassistant/components/onboarding/views.py | 26 +++++++++++++------ tests/components/onboarding/test_views.py | 22 ++++++++++++++++ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0800586eb85..68f1266317a 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -141,6 +141,7 @@ DEFAULT_INTEGRATIONS = { # These integrations are set up unless recovery mode is activated. # # Integrations providing core functionality: + "analytics", # Needed for onboarding "application_credentials", "backup", "frontend", diff --git a/homeassistant/components/onboarding/manifest.json b/homeassistant/components/onboarding/manifest.json index 466292eb2e0..918d845993a 100644 --- a/homeassistant/components/onboarding/manifest.json +++ b/homeassistant/components/onboarding/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Onboarding", "after_dependencies": ["hassio"], "codeowners": ["@home-assistant/core"], - "dependencies": ["analytics", "auth", "http", "person"], + "dependencies": ["auth", "http", "person"], "documentation": "https://www.home-assistant.io/integrations/onboarding", "integration_type": "system", "quality_scale": "internal" diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index e1edfa82a62..9bef34150a2 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from collections.abc import Coroutine from http import HTTPStatus from typing import TYPE_CHECKING, Any, cast @@ -20,6 +21,8 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import area_registry as ar from homeassistant.helpers.system_info import async_get_system_info from homeassistant.helpers.translation import async_get_translations +from homeassistant.setup import async_setup_component +from homeassistant.util.async_ import create_eager_task if TYPE_CHECKING: from . import OnboadingStorage @@ -215,15 +218,22 @@ class CoreConfigOnboardingView(_BaseOnboardingView): ): onboard_integrations.append("rpi_power") - # Set up integrations after onboarding - await asyncio.gather( - *( - hass.config_entries.flow.async_init( - domain, context={"source": "onboarding"} - ) - for domain in onboard_integrations + coros: list[Coroutine[Any, Any, Any]] = [ + hass.config_entries.flow.async_init( + domain, context={"source": "onboarding"} ) - ) + for domain in onboard_integrations + ] + + if "analytics" not in hass.config.components: + # If by some chance that analytics has not finished + # setting up, wait for it here so its ready for the + # next step. + coros.append(async_setup_component(hass, "analytics", {})) + + # Set up integrations after onboarding and ensure + # analytics is ready for the next step. + await asyncio.gather(*(create_eager_task(coro) for coro in coros)) return self.json({}) diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index b23f693b230..af9e6f9ebf0 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -567,6 +567,28 @@ async def test_onboarding_core_no_rpi_power( assert not rpi_power_state +async def test_onboarding_core_ensures_analytics_loaded( + hass: HomeAssistant, + hass_storage: dict[str, Any], + hass_client: ClientSessionGenerator, + mock_default_integrations, +) -> None: + """Test finishing the core step ensures analytics is ready.""" + mock_storage(hass_storage, {"done": [const.STEP_USER]}) + assert "analytics" not in hass.config.components + + assert await async_setup_component(hass, "onboarding", {}) + await hass.async_block_till_done() + + client = await hass_client() + resp = await client.post("/api/onboarding/core_config") + + assert resp.status == 200 + + await hass.async_block_till_done() + assert "analytics" in hass.config.components + + async def test_onboarding_analytics( hass: HomeAssistant, hass_storage: dict[str, Any], From 869128e951dd4fb93392032ef39660d1d36e1e4b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 15:38:52 -1000 Subject: [PATCH 0500/1691] Pre import the rest of the recorder platforms before asyncio starts (#112289) * Pre import the rest of the recorder platforms before asyncio starts I removed these from #112131 since I had trouble with the weather tests passing due to a race that I could not figure out. The race seems to have gone away now (at least locally) so hopefully the CI will pass now * Avoid importing counter and proximity integrations in logbook * Avoid importing counter and proximity integrations in logbook --- homeassistant/bootstrap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 68f1266317a..446fe73f926 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -37,6 +37,7 @@ from .components import ( history as history_pre_import, # noqa: F401 http, # not named pre_import since it has requirements image_upload as image_upload_import, # noqa: F401 - not named pre_import since it has requirements + logbook as logbook_pre_import, # noqa: F401 lovelace as lovelace_pre_import, # noqa: F401 onboarding as onboarding_pre_import, # noqa: F401 recorder as recorder_import, # noqa: F401 - not named pre_import since it has requirements @@ -47,6 +48,7 @@ from .components import ( webhook as webhook_pre_import, # noqa: F401 websocket_api as websocket_api_pre_import, # noqa: F401 ) +from .components.sensor import recorder as sensor_recorder # noqa: F401 from .const import ( FORMAT_DATETIME, KEY_DATA_LOGGING as DATA_LOGGING, From 84455dbe1d31e5ffd703890c275ba12c93e21c7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 16:09:55 -1000 Subject: [PATCH 0501/1691] Avoid having to work out the job type for each entity service at startup (#112557) --- homeassistant/core.py | 11 +++++++++-- homeassistant/helpers/entity_component.py | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 9a373f7a159..aa15872c29b 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -2014,9 +2014,10 @@ class Service: service: str, context: Context | None = None, supports_response: SupportsResponse = SupportsResponse.NONE, + job_type: HassJobType | None = None, ) -> None: """Initialize a service.""" - self.job = HassJob(func, f"service {domain}.{service}") + self.job = HassJob(func, f"service {domain}.{service}", job_type=job_type) self.schema = schema self.supports_response = supports_response @@ -2158,6 +2159,7 @@ class ServiceRegistry: ], schema: vol.Schema | None = None, supports_response: SupportsResponse = SupportsResponse.NONE, + job_type: HassJobType | None = None, ) -> None: """Register a service. @@ -2168,7 +2170,12 @@ class ServiceRegistry: domain = domain.lower() service = service.lower() service_obj = Service( - service_func, schema, domain, service, supports_response=supports_response + service_func, + schema, + domain, + service, + supports_response=supports_response, + job_type=job_type, ) if domain in self._services: diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 389dd69900a..40b665ed373 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -22,6 +22,7 @@ from homeassistant.const import ( from homeassistant.core import ( Event, HassJob, + HassJobType, HomeAssistant, ServiceCall, ServiceResponse, @@ -278,6 +279,7 @@ class EntityComponent(Generic[_EntityT]): ), schema, supports_response, + job_type=HassJobType.Coroutinefunction, ) async def async_setup_platform( From a7b4cd3512fc80a65c96dd222553f186484e7113 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 16:10:07 -1000 Subject: [PATCH 0502/1691] Pass job type to event listeners when creating entities (#112551) --- homeassistant/helpers/entity.py | 7 +++++- homeassistant/helpers/event.py | 40 ++++++++++++++++++++------------- tests/helpers/test_event.py | 19 +++++++++++----- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 3517d41314b..9fe4af2c6a6 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -46,6 +46,7 @@ from homeassistant.const import ( from homeassistant.core import ( CALLBACK_TYPE, Context, + HassJobType, HomeAssistant, callback, get_release_channel, @@ -1430,7 +1431,10 @@ class Entity( self.async_on_remove( async_track_entity_registry_updated_event( - self.hass, self.entity_id, self._async_registry_updated + self.hass, + self.entity_id, + self._async_registry_updated, + job_type=HassJobType.Callback, ) ) self._async_subscribe_device_updates() @@ -1545,6 +1549,7 @@ class Entity( self.hass, device_id, self._async_device_registry_updated, + job_type=HassJobType.Callback, ) if ( not self._on_remove diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 0dc3115466a..3c0aa4b9e34 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -299,6 +299,7 @@ def async_track_state_change_event( hass: HomeAssistant, entity_ids: str | Iterable[str], action: Callable[[EventType[EventStateChangedData]], Any], + job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track specific state change events indexed by entity_id. @@ -313,7 +314,7 @@ def async_track_state_change_event( """ if not (entity_ids := _async_string_to_lower_list(entity_ids)): return _remove_empty_listener - return _async_track_state_change_event(hass, entity_ids, action) + return _async_track_state_change_event(hass, entity_ids, action, job_type) @callback @@ -361,9 +362,12 @@ def _async_track_state_change_event( hass: HomeAssistant, entity_ids: str | Iterable[str], action: Callable[[EventType[EventStateChangedData]], Any], + job_type: HassJobType | None, ) -> CALLBACK_TYPE: """async_track_state_change_event without lowercasing.""" - return _async_track_event(_KEYED_TRACK_STATE_CHANGE, hass, entity_ids, action) + return _async_track_event( + _KEYED_TRACK_STATE_CHANGE, hass, entity_ids, action, job_type + ) @callback @@ -397,6 +401,7 @@ def _async_track_event( hass: HomeAssistant, keys: str | Iterable[str], action: Callable[[EventType[_TypedDictT]], None], + job_type: HassJobType | None, ) -> CALLBACK_TYPE: """Track an event by a specific key. @@ -429,7 +434,7 @@ def _async_track_event( run_immediately=tracker.run_immediately, ) - job = HassJob(action, f"track {tracker.event_type} event {keys}") + job = HassJob(action, f"track {tracker.event_type} event {keys}", job_type=job_type) for key in keys: if callback_list := callbacks.get(key): @@ -494,6 +499,7 @@ def async_track_entity_registry_updated_event( hass: HomeAssistant, entity_ids: str | Iterable[str], action: Callable[[EventType[EventEntityRegistryUpdatedData]], Any], + job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track specific entity registry updated events indexed by entity_id. @@ -502,10 +508,7 @@ def async_track_entity_registry_updated_event( Similar to async_track_state_change_event. """ return _async_track_event( - _KEYED_TRACK_ENTITY_REGISTRY_UPDATED, - hass, - entity_ids, - action, + _KEYED_TRACK_ENTITY_REGISTRY_UPDATED, hass, entity_ids, action, job_type ) @@ -558,16 +561,14 @@ def async_track_device_registry_updated_event( hass: HomeAssistant, device_ids: str | Iterable[str], action: Callable[[EventType[EventDeviceRegistryUpdatedData]], Any], + job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track specific device registry updated events indexed by device_id. Similar to async_track_entity_registry_updated_event. """ return _async_track_event( - _KEYED_TRACK_DEVICE_REGISTRY_UPDATED, - hass, - device_ids, - action, + _KEYED_TRACK_DEVICE_REGISTRY_UPDATED, hass, device_ids, action, job_type ) @@ -606,11 +607,12 @@ def async_track_state_added_domain( hass: HomeAssistant, domains: str | Iterable[str], action: Callable[[EventType[EventStateChangedData]], Any], + job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track state change events when an entity is added to domains.""" if not (domains := _async_string_to_lower_list(domains)): return _remove_empty_listener - return _async_track_state_added_domain(hass, domains, action) + return _async_track_state_added_domain(hass, domains, action, job_type) _KEYED_TRACK_STATE_ADDED_DOMAIN = _KeyedEventTracker( @@ -628,9 +630,12 @@ def _async_track_state_added_domain( hass: HomeAssistant, domains: str | Iterable[str], action: Callable[[EventType[EventStateChangedData]], Any], + job_type: HassJobType | None, ) -> CALLBACK_TYPE: """Track state change events when an entity is added to domains.""" - return _async_track_event(_KEYED_TRACK_STATE_ADDED_DOMAIN, hass, domains, action) + return _async_track_event( + _KEYED_TRACK_STATE_ADDED_DOMAIN, hass, domains, action, job_type + ) @callback @@ -661,9 +666,12 @@ def async_track_state_removed_domain( hass: HomeAssistant, domains: str | Iterable[str], action: Callable[[EventType[EventStateChangedData]], Any], + job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track state change events when an entity is removed from domains.""" - return _async_track_event(_KEYED_TRACK_STATE_REMOVED_DOMAIN, hass, domains, action) + return _async_track_event( + _KEYED_TRACK_STATE_REMOVED_DOMAIN, hass, domains, action, job_type + ) @callback @@ -781,7 +789,7 @@ class _TrackStateChangeFiltered: return self._listeners[_ENTITIES_LISTENER] = _async_track_state_change_event( - self.hass, entities, self._action + self.hass, entities, self._action, self._action_as_hassjob.job_type ) @callback @@ -798,7 +806,7 @@ class _TrackStateChangeFiltered: return self._listeners[_DOMAINS_LISTENER] = _async_track_state_added_domain( - self.hass, domains, self._state_added + self.hass, domains, self._state_added, HassJobType.Callback ) @callback diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 0c2c530eb9f..d18014b9f6f 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -453,7 +453,7 @@ async def test_async_track_state_change_event(hass: HomeAssistant) -> None: raise ValueError unsub_single = async_track_state_change_event( - hass, ["light.Bowl"], single_run_callback + hass, ["light.Bowl"], single_run_callback, job_type=ha.HassJobType.Callback ) unsub_multi = async_track_state_change_event( hass, ["light.Bowl", "switch.kitchen"], multiple_run_callback @@ -560,7 +560,9 @@ async def test_async_track_state_added_domain(hass: HomeAssistant) -> None: def callback_that_throws(event): raise ValueError - unsub_single = async_track_state_added_domain(hass, "light", single_run_callback) + unsub_single = async_track_state_added_domain( + hass, "light", single_run_callback, job_type=ha.HassJobType.Callback + ) unsub_multi = async_track_state_added_domain( hass, ["light", "switch"], multiple_run_callback ) @@ -672,7 +674,9 @@ async def test_async_track_state_removed_domain(hass: HomeAssistant) -> None: def callback_that_throws(event): raise ValueError - unsub_single = async_track_state_removed_domain(hass, "light", single_run_callback) + unsub_single = async_track_state_removed_domain( + hass, "light", single_run_callback, job_type=ha.HassJobType.Callback + ) unsub_multi = async_track_state_removed_domain( hass, ["light", "switch"], multiple_run_callback ) @@ -4602,7 +4606,9 @@ async def test_async_track_entity_registry_updated_event(hass: HomeAssistant) -> def run_callback(event): event_data.append(event.data) - unsub1 = async_track_entity_registry_updated_event(hass, entity_id, run_callback) + unsub1 = async_track_entity_registry_updated_event( + hass, entity_id, run_callback, job_type=ha.HassJobType.Callback + ) unsub2 = async_track_entity_registry_updated_event( hass, new_entity_id, run_callback ) @@ -4721,7 +4727,10 @@ async def test_async_track_device_registry_updated_event(hass: HomeAssistant) -> hass, device_id, single_device_id_callback ) unsub2 = async_track_device_registry_updated_event( - hass, [device_id, device_id2], multiple_device_id_callback + hass, + [device_id, device_id2], + multiple_device_id_callback, + job_type=ha.HassJobType.Callback, ) hass.bus.async_fire( EVENT_DEVICE_REGISTRY_UPDATED, {"action": "create", "device_id": device_id} From e12e1290658afad1e73398f6a97aaf6dad3770ae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 17:03:27 -1000 Subject: [PATCH 0503/1691] Make HassJob job_type lookup lazy (#112563) --- homeassistant/core.py | 9 ++++++--- tests/test_core.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index aa15872c29b..f0b10760228 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -289,8 +289,6 @@ class HassJob(Generic[_P, _R_co]): we run the job. """ - __slots__ = ("job_type", "target", "name", "_cancel_on_shutdown") - def __init__( self, target: Callable[_P, _R_co], @@ -302,8 +300,13 @@ class HassJob(Generic[_P, _R_co]): """Create a job object.""" self.target = target self.name = name - self.job_type = job_type or _get_hassjob_callable_job_type(target) self._cancel_on_shutdown = cancel_on_shutdown + self._job_type = job_type + + @cached_property + def job_type(self) -> HassJobType: + """Return the job type.""" + return self._job_type or _get_hassjob_callable_job_type(self.target) @property def cancel_on_shutdown(self) -> bool | None: diff --git a/tests/test_core.py b/tests/test_core.py index 987f228bea8..75d06a7c61f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2190,7 +2190,7 @@ async def test_hassjob_forbid_coroutine() -> None: coro = bla() with pytest.raises(ValueError): - ha.HassJob(coro) + ha.HassJob(coro).job_type # To avoid warning about unawaited coro await coro From 3ccbb2c87ab95dc080da347b4d037fa29d694914 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:44:58 +1300 Subject: [PATCH 0504/1691] Bump aioesphomeapi to 23.1.0 (#112560) --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index a1841306f0c..de26fd7aeba 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -16,7 +16,7 @@ "iot_class": "local_push", "loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"], "requirements": [ - "aioesphomeapi==23.0.0", + "aioesphomeapi==23.1.0", "esphome-dashboard-api==1.2.3", "bleak-esphome==1.0.0" ], diff --git a/requirements_all.txt b/requirements_all.txt index 35f4d96a65d..91c366fb4a1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -245,7 +245,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==23.0.0 +aioesphomeapi==23.1.0 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cff88c23f22..ece612fc662 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -224,7 +224,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==23.0.0 +aioesphomeapi==23.1.0 # homeassistant.components.flo aioflo==2021.11.0 From 1fb9cfe37e4531a6c4a0c63f5159956a0c7248e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 6 Mar 2024 20:54:09 -1000 Subject: [PATCH 0505/1691] Speed up the frame helper (#112562) --- homeassistant/helpers/frame.py | 59 +++++++--- homeassistant/util/async_.py | 52 ++++---- tests/common.py | 19 ++- tests/components/zeroconf/test_usage.py | 44 ++++--- tests/conftest.py | 33 +++--- tests/helpers/test_aiohttp_client.py | 89 ++++++++------ tests/helpers/test_deprecation.py | 128 +++++++++++--------- tests/helpers/test_frame.py | 104 ++++++++-------- tests/helpers/test_httpx_client.py | 84 +++++++------ tests/test_loader.py | 2 +- tests/util/test_async.py | 150 ++++++++++++------------ tests/util/yaml/test_init.py | 34 +++--- 12 files changed, 460 insertions(+), 338 deletions(-) diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 04f16ebddd0..3b9c556966a 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -6,15 +6,21 @@ from collections.abc import Callable from contextlib import suppress from dataclasses import dataclass import functools +import linecache import logging import sys -from traceback import FrameSummary, extract_stack -from typing import Any, TypeVar, cast +from types import FrameType +from typing import TYPE_CHECKING, Any, TypeVar, cast from homeassistant.core import HomeAssistant, async_get_hass from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import async_suggest_report_issue +if TYPE_CHECKING: + from functools import cached_property +else: + from homeassistant.backports.functools import cached_property + _LOGGER = logging.getLogger(__name__) # Keep track of integrations already reported to prevent flooding @@ -28,10 +34,25 @@ class IntegrationFrame: """Integration frame container.""" custom_integration: bool - frame: FrameSummary integration: str module: str | None relative_filename: str + _frame: FrameType + + @cached_property + def line_number(self) -> int: + """Return the line number of the frame.""" + return self._frame.f_lineno + + @cached_property + def filename(self) -> str: + """Return the filename of the frame.""" + return self._frame.f_code.co_filename + + @cached_property + def line(self) -> str: + """Return the line of the frame.""" + return (linecache.getline(self.filename, self.line_number) or "?").strip() def get_integration_logger(fallback_name: str) -> logging.Logger: @@ -54,19 +75,28 @@ def get_integration_logger(fallback_name: str) -> logging.Logger: return logging.getLogger(logger_name) +def get_current_frame(depth: int = 0) -> FrameType: + """Return the current frame.""" + # Add one to depth since get_current_frame is included + return sys._getframe(depth + 1) # pylint: disable=protected-access + + def get_integration_frame(exclude_integrations: set | None = None) -> IntegrationFrame: """Return the frame, integration and integration path of the current stack frame.""" found_frame = None if not exclude_integrations: exclude_integrations = set() - for frame in reversed(extract_stack()): + frame: FrameType | None = get_current_frame() + while frame is not None: + filename = frame.f_code.co_filename + for path in ("custom_components/", "homeassistant/components/"): try: - index = frame.filename.index(path) + index = filename.index(path) start = index + len(path) - end = frame.filename.index("/", start) - integration = frame.filename[start:end] + end = filename.index("/", start) + integration = filename[start:end] if integration not in exclude_integrations: found_frame = frame @@ -77,6 +107,8 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio if found_frame is not None: break + frame = frame.f_back + if found_frame is None: raise MissingIntegrationFrame @@ -84,16 +116,16 @@ def get_integration_frame(exclude_integrations: set | None = None) -> Integratio for module, module_obj in dict(sys.modules).items(): if not hasattr(module_obj, "__file__"): continue - if module_obj.__file__ == found_frame.filename: + if module_obj.__file__ == found_frame.f_code.co_filename: found_module = module break return IntegrationFrame( custom_integration=path == "custom_components/", - frame=found_frame, integration=integration, module=found_module, - relative_filename=found_frame.filename[index:], + relative_filename=found_frame.f_code.co_filename[index:], + _frame=found_frame, ) @@ -137,9 +169,8 @@ def _report_integration( Async friendly. """ - found_frame = integration_frame.frame # Keep track of integrations already reported to prevent flooding - key = f"{found_frame.filename}:{found_frame.lineno}" + key = f"{integration_frame.filename}:{integration_frame.line_number}" if key in _REPORTED_INTEGRATIONS: return _REPORTED_INTEGRATIONS.add(key) @@ -160,8 +191,8 @@ def _report_integration( integration_frame.integration, what, integration_frame.relative_filename, - found_frame.lineno, - (found_frame.line or "?").strip(), + integration_frame.line_number, + integration_frame.line, report_issue, ) diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 304f2446722..4ec5028252e 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -9,7 +9,6 @@ import functools import logging import sys import threading -from traceback import extract_stack from typing import Any, ParamSpec, TypeVar, TypeVarTuple from homeassistant.exceptions import HomeAssistantError @@ -116,14 +115,6 @@ def check_loop( The default advisory message is 'Use `await hass.async_add_executor_job()' Set `advise_msg` to an alternate message if the solution differs. """ - # pylint: disable=import-outside-toplevel - from homeassistant.core import HomeAssistant, async_get_hass - from homeassistant.helpers.frame import ( - MissingIntegrationFrame, - get_integration_frame, - ) - from homeassistant.loader import async_suggest_report_issue - try: get_running_loop() in_loop = True @@ -133,18 +124,32 @@ def check_loop( if not in_loop: return + # Import only after we know we are running in the event loop + # so threads do not have to pay the late import cost. + # pylint: disable=import-outside-toplevel + from homeassistant.core import HomeAssistant, async_get_hass + from homeassistant.helpers.frame import ( + MissingIntegrationFrame, + get_current_frame, + get_integration_frame, + ) + from homeassistant.loader import async_suggest_report_issue + found_frame = None - stack = extract_stack() - - if ( - func.__name__ == "sleep" - and len(stack) >= 3 - and stack[-3].filename.endswith("pydevd.py") - ): - # Don't report `time.sleep` injected by the debugger (pydevd.py) - # stack[-1] is us, stack[-2] is protected_loop_func, stack[-3] is the offender - return + if func.__name__ == "sleep": + # + # Avoid extracting the stack unless we need to since it + # will have to access the linecache which can do blocking + # I/O and we are trying to avoid blocking calls. + # + # frame[1] is us + # frame[2] is protected_loop_func + # frame[3] is the offender + with suppress(ValueError): + offender_frame = get_current_frame(3) + if offender_frame.f_code.co_filename.endswith("pydevd.py"): + return try: integration_frame = get_integration_frame() @@ -167,7 +172,6 @@ def check_loop( module=integration_frame.module, ) - found_frame = integration_frame.frame _LOGGER.warning( ( "Detected blocking call to %s inside the event loop by %sintegration '%s' " @@ -177,8 +181,8 @@ def check_loop( "custom " if integration_frame.custom_integration else "", integration_frame.integration, integration_frame.relative_filename, - found_frame.lineno, - (found_frame.line or "?").strip(), + integration_frame.line_number, + integration_frame.line, report_issue, ) @@ -186,8 +190,8 @@ def check_loop( raise RuntimeError( "Blocking calls must be done in the executor or a separate thread;" f" {advise_msg or 'Use `await hass.async_add_executor_job()`'}; at" - f" {integration_frame.relative_filename}, line {found_frame.lineno}:" - f" {(found_frame.line or '?').strip()}" + f" {integration_frame.relative_filename}, line {integration_frame.line_number}:" + f" {integration_frame.line}" ) diff --git a/tests/common.py b/tests/common.py index 829368b309d..cc2b4fa2ad6 100644 --- a/tests/common.py +++ b/tests/common.py @@ -15,7 +15,7 @@ import os import pathlib import threading import time -from types import ModuleType +from types import FrameType, ModuleType from typing import Any, NoReturn, TypeVar from unittest.mock import AsyncMock, Mock, patch @@ -1596,3 +1596,20 @@ def help_test_all(module: ModuleType) -> None: assert set(module.__all__) == { itm for itm in module.__dir__() if not itm.startswith("_") } + + +def extract_stack_to_frame(extract_stack: list[Mock]) -> FrameType: + """Convert an extract stack to a frame list.""" + stack = list(extract_stack) + for frame in stack: + frame.f_back = None + frame.f_code.co_filename = frame.filename + frame.f_lineno = int(frame.lineno) + + top_frame = stack.pop() + current_frame = top_frame + while stack and (next_frame := stack.pop()): + current_frame.f_back = next_frame + current_frame = next_frame + + return top_frame diff --git a/tests/components/zeroconf/test_usage.py b/tests/components/zeroconf/test_usage.py index 3aaea1c50ee..81743cb5e93 100644 --- a/tests/components/zeroconf/test_usage.py +++ b/tests/components/zeroconf/test_usage.py @@ -9,6 +9,8 @@ from homeassistant.components.zeroconf.usage import install_multiple_zeroconf_ca from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.common import extract_stack_to_frame + DOMAIN = "zeroconf" @@ -50,25 +52,29 @@ async def test_multiple_zeroconf_instances_gives_shared( line="self.light.is_on", ) with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/dev/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/dev/homeassistant/components/zeroconf/usage.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/dev/mdns/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/dev/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/dev/homeassistant/components/zeroconf/usage.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/dev/mdns/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): assert zeroconf.Zeroconf() == zeroconf_instance diff --git a/tests/conftest.py b/tests/conftest.py index 3be03e1e3ca..5753729213f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -96,6 +96,7 @@ from .common import ( # noqa: E402, isort:skip init_recorder_component, mock_storage, patch_yaml_files, + extract_stack_to_frame, ) from .test_util.aiohttp import ( # noqa: E402, isort:skip AiohttpClientMocker, @@ -1588,20 +1589,24 @@ def mock_integration_frame() -> Generator[Mock, None, None]: line="self.light.is_on", ) with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): yield correct_frame diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 8488a5f15e3..1a9c2b0212e 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -20,7 +20,12 @@ from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE, HomeAssistant import homeassistant.helpers.aiohttp_client as client from homeassistant.util.color import RGBColor -from tests.common import MockConfigEntry, MockModule, mock_integration +from tests.common import ( + MockConfigEntry, + MockModule, + extract_stack_to_frame, + mock_integration, +) from tests.test_util.aiohttp import AiohttpClientMocker @@ -166,24 +171,29 @@ async def test_warning_close_session_integration( ) -> None: """Test log warning message when closing the session from integration context.""" with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): session = client.async_get_clientsession(hass) await session.close() @@ -202,24 +212,29 @@ async def test_warning_close_session_custom( """Test log warning message when closing the session from custom context.""" mock_integration(hass, MockModule("hue"), built_in=False) with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): session = client.async_get_clientsession(hass) await session.close() diff --git a/tests/helpers/test_deprecation.py b/tests/helpers/test_deprecation.py index 25b37e2073f..f5089ecbaec 100644 --- a/tests/helpers/test_deprecation.py +++ b/tests/helpers/test_deprecation.py @@ -20,7 +20,7 @@ from homeassistant.helpers.deprecation import ( ) from homeassistant.helpers.frame import MissingIntegrationFrame -from tests.common import MockModule, mock_integration +from tests.common import MockModule, extract_stack_to_frame, mock_integration class MockBaseClassDeprecatedProperty: @@ -178,24 +178,29 @@ def test_deprecated_function_called_from_built_in_integration( pass with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): mock_deprecated_function() assert ( @@ -230,24 +235,29 @@ def test_deprecated_function_called_from_custom_integration( pass with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): mock_deprecated_function() assert ( @@ -327,24 +337,29 @@ def test_check_if_deprecated_constant( # mock sys.modules for homeassistant/helpers/frame.py#get_integration_frame with patch.dict(sys.modules, {module_name: Mock(__file__=filename)}), patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename=filename, - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename=filename, + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): value = check_if_deprecated_constant("TEST_CONSTANT", module_globals) assert value == _get_value(deprecated_constant) @@ -397,7 +412,8 @@ def test_check_if_deprecated_constant_integration_not_found( } with patch( - "homeassistant.helpers.frame.extract_stack", side_effect=MissingIntegrationFrame + "homeassistant.helpers.frame.get_current_frame", + side_effect=MissingIntegrationFrame, ): value = check_if_deprecated_constant("TEST_CONSTANT", module_globals) assert value == _get_value(deprecated_constant) diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index fa495e9dbc9..7881c845a77 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -7,6 +7,8 @@ import pytest from homeassistant.core import HomeAssistant from homeassistant.helpers import frame +from tests.common import extract_stack_to_frame + async def test_extract_frame_integration( caplog: pytest.LogCaptureFixture, mock_integration_frame: Mock @@ -15,7 +17,7 @@ async def test_extract_frame_integration( integration_frame = frame.get_integration_frame() assert integration_frame == frame.IntegrationFrame( custom_integration=False, - frame=mock_integration_frame, + _frame=mock_integration_frame, integration="hue", module=None, relative_filename="homeassistant/components/hue/light.py", @@ -40,7 +42,7 @@ async def test_extract_frame_resolve_module( assert integration_frame == frame.IntegrationFrame( custom_integration=True, - frame=ANY, + _frame=ANY, integration="test_integration_frame", module="custom_components.test_integration_frame", relative_filename="custom_components/test_integration_frame/__init__.py", @@ -68,25 +70,27 @@ async def test_extract_frame_integration_with_excluded_integration( line="self.light.is_on", ) with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/dev/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/dev/homeassistant/components/zeroconf/usage.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/dev/mdns/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/dev/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/dev/homeassistant/components/zeroconf/usage.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/dev/mdns/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): integration_frame = frame.get_integration_frame( exclude_integrations={"zeroconf"} @@ -94,7 +98,7 @@ async def test_extract_frame_integration_with_excluded_integration( assert integration_frame == frame.IntegrationFrame( custom_integration=False, - frame=correct_frame, + _frame=correct_frame, integration="mdns", module=None, relative_filename="homeassistant/components/mdns/light.py", @@ -104,19 +108,21 @@ async def test_extract_frame_integration_with_excluded_integration( async def test_extract_frame_no_integration(caplog: pytest.LogCaptureFixture) -> None: """Test extracting the current frame without integration context.""" with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), pytest.raises(frame.MissingIntegrationFrame): frame.get_integration_frame() @@ -126,19 +132,21 @@ async def test_get_integration_logger_no_integration( ) -> None: """Test getting fallback logger without integration context.""" with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): logger = frame.get_integration_logger(__name__) diff --git a/tests/helpers/test_httpx_client.py b/tests/helpers/test_httpx_client.py index 693c45cc73a..52a6226c307 100644 --- a/tests/helpers/test_httpx_client.py +++ b/tests/helpers/test_httpx_client.py @@ -7,7 +7,7 @@ import pytest from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE, HomeAssistant import homeassistant.helpers.httpx_client as client -from tests.common import MockModule, mock_integration +from tests.common import MockModule, extract_stack_to_frame, mock_integration async def test_get_async_client_with_ssl(hass: HomeAssistant) -> None: @@ -104,24 +104,29 @@ async def test_warning_close_session_integration( ) -> None: """Test log warning message when closing the session from integration context.""" with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="await session.aclose()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.aclose()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="await session.aclose()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): httpx_session = client.get_async_client(hass) await httpx_session.aclose() @@ -141,24 +146,29 @@ async def test_warning_close_session_custom( """Test log warning message when closing the session from custom context.""" mock_integration(hass, MockModule("hue"), built_in=False) with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="await session.aclose()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.aclose()", + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="await session.aclose()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): httpx_session = client.get_async_client(hass) await httpx_session.aclose() diff --git a/tests/test_loader.py b/tests/test_loader.py index 6fa4de1da9c..d73ae161041 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1122,7 +1122,7 @@ async def test_hass_components_use_reported( ) integration_frame = frame.IntegrationFrame( custom_integration=True, - frame=mock_integration_frame, + _frame=mock_integration_frame, integration="test_integration_frame", module="custom_components.test_integration_frame", relative_filename="custom_components/test_integration_frame/__init__.py", diff --git a/tests/util/test_async.py b/tests/util/test_async.py index ad2c9329fb7..199e21960e4 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -10,6 +10,8 @@ from homeassistant import block_async_io from homeassistant.core import HomeAssistant from homeassistant.util import async_ as hasync +from tests.common import extract_stack_to_frame + @patch("concurrent.futures.Future") @patch("threading.get_ident") @@ -49,24 +51,28 @@ async def test_check_loop_async() -> None: async def test_check_loop_async_integration(caplog: pytest.LogCaptureFixture) -> None: """Test check_loop detects and raises when called from event loop from integration context.""" with pytest.raises(RuntimeError), patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on" + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): hasync.check_loop(banned_function) assert ( @@ -82,24 +88,28 @@ async def test_check_loop_async_integration_non_strict( ) -> None: """Test check_loop detects when called from event loop from integration context.""" with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on" + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): hasync.check_loop(banned_function, strict=False) assert ( @@ -113,24 +123,28 @@ async def test_check_loop_async_integration_non_strict( async def test_check_loop_async_custom(caplog: pytest.LogCaptureFixture) -> None: """Test check_loop detects when called from event loop with custom component context.""" with pytest.raises(RuntimeError), patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on" + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): hasync.check_loop(banned_function) assert ( @@ -161,24 +175,16 @@ async def test_protect_loop_debugger_sleep(caplog: pytest.LogCaptureFixture) -> block_async_io.enable() with patch( - "homeassistant.util.async_.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/.venv/blah/pydevd.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/util/async.py", - lineno="123", - line="protected_loop_func", - ), - Mock( - filename="/home/paulus/homeassistant/util/async.py", - lineno="123", - line="check_loop()", - ), - ], + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/.venv/blah/pydevd.py", + lineno="23", + line="do_something()", + ), + ] + ), ): time.sleep(0) assert "Detected blocking call inside the event loop" not in caplog.text diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index 93c8ed50498..9fda5be73a8 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -18,7 +18,7 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.util.yaml as yaml from homeassistant.util.yaml import loader as yaml_loader -from tests.common import get_test_config_dir, patch_yaml_files +from tests.common import extract_stack_to_frame, get_test_config_dir, patch_yaml_files @pytest.fixture(params=["enable_c_loader", "disable_c_loader"]) @@ -611,20 +611,24 @@ def mock_integration_frame() -> Generator[Mock, None, None]: line="self.light.is_on", ) with patch( - "homeassistant.helpers.frame.extract_stack", - return_value=[ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ], + "homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line + ), patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ): yield correct_frame From a92e65bc54a4ce2126bf703a97266cf688ec1599 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 09:33:31 +0100 Subject: [PATCH 0506/1691] Bump Wandalen/wretry.action from 1.4.5 to 1.4.7 (#112575) Bumps [Wandalen/wretry.action](https://github.com/wandalen/wretry.action) from 1.4.5 to 1.4.7. - [Release notes](https://github.com/wandalen/wretry.action/releases) - [Commits](https://github.com/wandalen/wretry.action/compare/v1.4.5...v1.4.7) --- updated-dependencies: - dependency-name: Wandalen/wretry.action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a5f49533147..45628d00d33 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1084,7 +1084,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.4.5 + uses: Wandalen/wretry.action@v1.4.7 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1095,7 +1095,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.4.5 + uses: Wandalen/wretry.action@v1.4.7 with: action: codecov/codecov-action@v3.1.3 with: | From d42dd0114d2c077b5d8c393e87d9ac8bdb4329a6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 7 Mar 2024 12:33:28 +0100 Subject: [PATCH 0507/1691] Add icon translations to Twinkly (#112328) --- homeassistant/components/twinkly/icons.json | 9 +++++++++ homeassistant/components/twinkly/light.py | 2 +- tests/components/twinkly/snapshots/test_diagnostics.ambr | 1 - tests/components/twinkly/test_light.py | 3 --- 4 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/twinkly/icons.json diff --git a/homeassistant/components/twinkly/icons.json b/homeassistant/components/twinkly/icons.json new file mode 100644 index 00000000000..82c95aebce6 --- /dev/null +++ b/homeassistant/components/twinkly/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "light": { + "light": { + "default": "mdi:string-lights" + } + } + } +} diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 453ba900706..35b5f8bd1d3 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -66,7 +66,7 @@ class TwinklyLight(LightEntity): _attr_has_entity_name = True _attr_name = None - _attr_icon = "mdi:string-lights" + _attr_translation_key = "light" def __init__( self, diff --git a/tests/components/twinkly/snapshots/test_diagnostics.ambr b/tests/components/twinkly/snapshots/test_diagnostics.ambr index 2a10154c3da..0601159ca4c 100644 --- a/tests/components/twinkly/snapshots/test_diagnostics.ambr +++ b/tests/components/twinkly/snapshots/test_diagnostics.ambr @@ -8,7 +8,6 @@ 'effect_list': list([ ]), 'friendly_name': 'twinkly_test_device_name', - 'icon': 'mdi:string-lights', 'supported_color_modes': list([ 'brightness', ]), diff --git a/tests/components/twinkly/test_light.py b/tests/components/twinkly/test_light.py index 452467bb160..976744ff5db 100644 --- a/tests/components/twinkly/test_light.py +++ b/tests/components/twinkly/test_light.py @@ -30,9 +30,6 @@ async def test_initial_state(hass: HomeAssistant) -> None: assert state.state == "on" assert state.attributes[ATTR_BRIGHTNESS] == 26 assert state.attributes["friendly_name"] == TEST_NAME - assert state.attributes["icon"] == "mdi:string-lights" - - assert entity.original_icon == "mdi:string-lights" assert device.name == TEST_NAME assert device.model == TEST_MODEL From 008e025d5c3a61f89b6ce1e5f64d861dfed6ce96 Mon Sep 17 00:00:00 2001 From: Jeef Date: Thu, 7 Mar 2024 04:39:38 -0700 Subject: [PATCH 0508/1691] Bump weatherflow4py to 0.1.14 (#112554) adding missing rain states --- homeassistant/components/weatherflow_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/weatherflow_cloud/manifest.json b/homeassistant/components/weatherflow_cloud/manifest.json index 15943dde32a..4cb0f5296e5 100644 --- a/homeassistant/components/weatherflow_cloud/manifest.json +++ b/homeassistant/components/weatherflow_cloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "iot_class": "cloud_polling", - "requirements": ["weatherflow4py==0.1.13"] + "requirements": ["weatherflow4py==0.1.14"] } diff --git a/requirements_all.txt b/requirements_all.txt index 91c366fb4a1..fd453d2b93e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2833,7 +2833,7 @@ watchdog==2.3.1 waterfurnace==1.1.0 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.13 +weatherflow4py==0.1.14 # homeassistant.components.webmin webmin-xmlrpc==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ece612fc662..e6442ed4a72 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2174,7 +2174,7 @@ wallbox==0.6.0 watchdog==2.3.1 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.13 +weatherflow4py==0.1.14 # homeassistant.components.webmin webmin-xmlrpc==0.0.1 From 82efb3d35b8dfaafe4ac4f9d74c84ad9df590f0a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 7 Mar 2024 12:41:14 +0100 Subject: [PATCH 0509/1691] Make FlowResult a generic type (#111952) --- homeassistant/auth/__init__.py | 18 +++-- homeassistant/auth/models.py | 3 + homeassistant/auth/providers/__init__.py | 15 +++-- homeassistant/auth/providers/command_line.py | 5 +- homeassistant/auth/providers/homeassistant.py | 5 +- .../auth/providers/insecure_example.py | 5 +- .../auth/providers/legacy_api_password.py | 5 +- .../auth/providers/trusted_networks.py | 5 +- homeassistant/components/auth/login_flow.py | 20 +++--- .../components/zwave_js/config_flow.py | 2 +- homeassistant/config_entries.py | 14 ++-- homeassistant/data_entry_flow.py | 67 ++++++++++++------- homeassistant/helpers/data_entry_flow.py | 11 +-- 13 files changed, 95 insertions(+), 80 deletions(-) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index f99e90dbc05..fa89134305e 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -19,13 +19,13 @@ from homeassistant.core import ( HomeAssistant, callback, ) -from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util import dt as dt_util from . import auth_store, jwt_wrapper, models from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN, REFRESH_TOKEN_EXPIRATION from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config +from .models import AuthFlowResult from .providers import AuthProvider, LoginFlow, auth_provider_from_config EVENT_USER_ADDED = "user_added" @@ -88,9 +88,13 @@ async def auth_manager_from_config( return manager -class AuthManagerFlowManager(data_entry_flow.FlowManager): +class AuthManagerFlowManager( + data_entry_flow.FlowManager[AuthFlowResult, tuple[str, str]] +): """Manage authentication flows.""" + _flow_result = AuthFlowResult + def __init__(self, hass: HomeAssistant, auth_manager: AuthManager) -> None: """Init auth manager flows.""" super().__init__(hass) @@ -98,11 +102,11 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager): async def async_create_flow( self, - handler_key: str, + handler_key: tuple[str, str], *, context: dict[str, Any] | None = None, data: dict[str, Any] | None = None, - ) -> data_entry_flow.FlowHandler: + ) -> LoginFlow: """Create a login flow.""" auth_provider = self.auth_manager.get_auth_provider(*handler_key) if not auth_provider: @@ -110,8 +114,10 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager): return await auth_provider.async_login_flow(context) async def async_finish_flow( - self, flow: data_entry_flow.FlowHandler, result: FlowResult - ) -> FlowResult: + self, + flow: data_entry_flow.FlowHandler[AuthFlowResult, tuple[str, str]], + result: AuthFlowResult, + ) -> AuthFlowResult: """Return a user as result of login flow.""" flow = cast(LoginFlow, flow) diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index 4cf94401478..d71cd682086 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -11,6 +11,7 @@ from attr import Attribute from attr.setters import validate from homeassistant.const import __version__ +from homeassistant.data_entry_flow import FlowResult from homeassistant.util import dt as dt_util from . import permissions as perm_mdl @@ -26,6 +27,8 @@ TOKEN_TYPE_NORMAL = "normal" TOKEN_TYPE_SYSTEM = "system" TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token" +AuthFlowResult = FlowResult[tuple[str, str]] + @attr.s(slots=True) class Group: diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 7d74dd2dc26..2036178b225 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -13,14 +13,13 @@ from voluptuous.humanize import humanize_error from homeassistant import data_entry_flow, requirements from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.util import dt as dt_util from homeassistant.util.decorator import Registry from ..auth_store import AuthStore from ..const import MFA_SESSION_EXPIRATION -from ..models import Credentials, RefreshToken, User, UserMeta +from ..models import AuthFlowResult, Credentials, RefreshToken, User, UserMeta _LOGGER = logging.getLogger(__name__) DATA_REQS = "auth_prov_reqs_processed" @@ -181,9 +180,11 @@ async def load_auth_provider_module( return module -class LoginFlow(data_entry_flow.FlowHandler): +class LoginFlow(data_entry_flow.FlowHandler[AuthFlowResult, tuple[str, str]]): """Handler for the login flow.""" + _flow_result = AuthFlowResult + def __init__(self, auth_provider: AuthProvider) -> None: """Initialize the login flow.""" self._auth_provider = auth_provider @@ -197,7 +198,7 @@ class LoginFlow(data_entry_flow.FlowHandler): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the first step of login flow. Return self.async_show_form(step_id='init') if user_input is None. @@ -207,7 +208,7 @@ class LoginFlow(data_entry_flow.FlowHandler): async def async_step_select_mfa_module( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of select mfa module.""" errors = {} @@ -232,7 +233,7 @@ class LoginFlow(data_entry_flow.FlowHandler): async def async_step_mfa( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of mfa validation.""" assert self.credential assert self.user @@ -282,6 +283,6 @@ class LoginFlow(data_entry_flow.FlowHandler): errors=errors, ) - async def async_finish(self, flow_result: Any) -> FlowResult: + async def async_finish(self, flow_result: Any) -> AuthFlowResult: """Handle the pass of login flow.""" return self.async_create_entry(data=flow_result) diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index 4ec2ca18611..346d2cc503f 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -10,10 +10,9 @@ from typing import Any, cast import voluptuous as vol from homeassistant.const import CONF_COMMAND -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError -from ..models import Credentials, UserMeta +from ..models import AuthFlowResult, Credentials, UserMeta from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow CONF_ARGS = "args" @@ -138,7 +137,7 @@ class CommandLineLoginFlow(LoginFlow): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of the form.""" errors = {} diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 6f621b93a6a..2c4fb034dc9 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -12,11 +12,10 @@ import voluptuous as vol from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.storage import Store -from ..models import Credentials, UserMeta +from ..models import AuthFlowResult, Credentials, UserMeta from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow STORAGE_VERSION = 1 @@ -321,7 +320,7 @@ class HassLoginFlow(LoginFlow): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of the form.""" errors = {} diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index f7f01e74c27..851bed6638c 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -8,10 +8,9 @@ from typing import Any, cast import voluptuous as vol from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError -from ..models import Credentials, UserMeta +from ..models import AuthFlowResult, Credentials, UserMeta from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow USER_SCHEMA = vol.Schema( @@ -98,7 +97,7 @@ class ExampleLoginFlow(LoginFlow): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of the form.""" errors = None diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index 98c246d74e4..4fcfd4f7b12 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -11,12 +11,11 @@ from typing import Any, cast import voluptuous as vol from homeassistant.core import async_get_hass, callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from ..models import Credentials, UserMeta +from ..models import AuthFlowResult, Credentials, UserMeta from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow AUTH_PROVIDER_TYPE = "legacy_api_password" @@ -101,7 +100,7 @@ class LegacyLoginFlow(LoginFlow): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of the form.""" errors = {} diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index cc195c14c23..54633744bd9 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -19,13 +19,12 @@ from typing import Any, cast import voluptuous as vol from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.network import is_cloud_connection from .. import InvalidAuthError -from ..models import Credentials, RefreshToken, UserMeta +from ..models import AuthFlowResult, Credentials, RefreshToken, UserMeta from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow IPAddress = IPv4Address | IPv6Address @@ -226,7 +225,7 @@ class TrustedNetworksLoginFlow(LoginFlow): async def async_step_init( self, user_input: dict[str, str] | None = None - ) -> FlowResult: + ) -> AuthFlowResult: """Handle the step of the form.""" try: cast( diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index cc6cb5fc47a..12b1893bc9d 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -79,7 +79,7 @@ import voluptuous_serialize from homeassistant import data_entry_flow from homeassistant.auth import AuthManagerFlowManager, InvalidAuthError -from homeassistant.auth.models import Credentials +from homeassistant.auth.models import AuthFlowResult, Credentials from homeassistant.components import onboarding from homeassistant.components.http.auth import async_user_not_allowed_do_auth from homeassistant.components.http.ban import ( @@ -197,8 +197,8 @@ class AuthProvidersView(HomeAssistantView): def _prepare_result_json( - result: data_entry_flow.FlowResult, -) -> data_entry_flow.FlowResult: + result: AuthFlowResult, +) -> AuthFlowResult: """Convert result to JSON.""" if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY: data = result.copy() @@ -237,7 +237,7 @@ class LoginFlowBaseView(HomeAssistantView): self, request: web.Request, client_id: str, - result: data_entry_flow.FlowResult, + result: AuthFlowResult, ) -> web.Response: """Convert the flow result to a response.""" if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY: @@ -297,7 +297,9 @@ class LoginFlowIndexView(LoginFlowBaseView): vol.Schema( { vol.Required("client_id"): str, - vol.Required("handler"): vol.Any(str, list), + vol.Required("handler"): vol.All( + [vol.Any(str, None)], vol.Length(2, 2), vol.Coerce(tuple) + ), vol.Required("redirect_uri"): str, vol.Optional("type", default="authorize"): str, } @@ -312,15 +314,11 @@ class LoginFlowIndexView(LoginFlowBaseView): if not indieauth.verify_client_id(client_id): return self.json_message("Invalid client id", HTTPStatus.BAD_REQUEST) - handler: tuple[str, ...] | str - if isinstance(data["handler"], list): - handler = tuple(data["handler"]) - else: - handler = data["handler"] + handler: tuple[str, str] = tuple(data["handler"]) try: result = await self._flow_mgr.async_init( - handler, # type: ignore[arg-type] + handler, context={ "ip_address": ip_address(request.remote), # type: ignore[arg-type] "credential_only": data.get("type") == "link_user", diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index cb564de924c..8c5aa55713a 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -182,7 +182,7 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC): @property @abstractmethod - def flow_manager(self) -> FlowManager[ConfigFlowResult]: + def flow_manager(self) -> FlowManager[ConfigFlowResult, str]: """Return the flow manager of the flow.""" async def async_step_install_addon( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 164825c4dec..d9023e5e11a 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1045,7 +1045,7 @@ class FlowCancelledError(Exception): """Error to indicate that a flow has been cancelled.""" -class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): +class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): """Manage all the config entry flows that are in progress.""" _flow_result = ConfigFlowResult @@ -1171,7 +1171,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): async def async_finish_flow( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult], + flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish a config flow and add an entry.""" @@ -1293,7 +1293,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): async def async_post_init( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult], + flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], result: ConfigFlowResult, ) -> None: """After a flow is initialised trigger new flow notifications.""" @@ -1940,7 +1940,7 @@ def _async_abort_entries_match( raise data_entry_flow.AbortFlow("already_configured") -class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult]): +class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult, str]): """Base class for config and option flows.""" _flow_result = ConfigFlowResult @@ -2292,7 +2292,7 @@ class ConfigFlow(ConfigEntryBaseFlow): return self.async_abort(reason=reason) -class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): +class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): """Flow to set options for a configuration entry.""" _flow_result = ConfigFlowResult @@ -2322,7 +2322,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): async def async_finish_flow( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult], + flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish an options flow and update options for configuration entry. @@ -2344,7 +2344,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): return result async def _async_setup_preview( - self, flow: data_entry_flow.FlowHandler[ConfigFlowResult] + self, flow: data_entry_flow.FlowHandler[ConfigFlowResult, str] ) -> None: """Set up preview for an option flow handler.""" entry = self._async_get_config_entry(flow.handler) diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 3005c21c272..4334ec2b274 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -85,7 +85,8 @@ STEP_ID_OPTIONAL_STEPS = { } -_FlowResultT = TypeVar("_FlowResultT", bound="FlowResult", default="FlowResult") +_FlowResultT = TypeVar("_FlowResultT", bound="FlowResult[Any]", default="FlowResult") +_HandlerT = TypeVar("_HandlerT", default=str) @dataclass(slots=True) @@ -138,7 +139,7 @@ class AbortFlow(FlowError): self.description_placeholders = description_placeholders -class FlowResult(TypedDict, total=False): +class FlowResult(TypedDict, Generic[_HandlerT], total=False): """Typed result dict.""" context: dict[str, Any] @@ -149,7 +150,7 @@ class FlowResult(TypedDict, total=False): errors: dict[str, str] | None extra: str flow_id: Required[str] - handler: Required[str] + handler: Required[_HandlerT] last_step: bool | None menu_options: list[str] | dict[str, str] options: Mapping[str, Any] @@ -189,7 +190,7 @@ def _map_error_to_schema_errors( schema_errors[path_part_str] = error.error_message -class FlowManager(abc.ABC, Generic[_FlowResultT]): +class FlowManager(abc.ABC, Generic[_FlowResultT, _HandlerT]): """Manage all the flows that are in progress.""" _flow_result: Callable[..., _FlowResultT] = FlowResult # type: ignore[assignment] @@ -200,19 +201,23 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): ) -> None: """Initialize the flow manager.""" self.hass = hass - self._preview: set[str] = set() - self._progress: dict[str, FlowHandler[_FlowResultT]] = {} - self._handler_progress_index: dict[str, set[FlowHandler[_FlowResultT]]] = {} - self._init_data_process_index: dict[type, set[FlowHandler[_FlowResultT]]] = {} + self._preview: set[_HandlerT] = set() + self._progress: dict[str, FlowHandler[_FlowResultT, _HandlerT]] = {} + self._handler_progress_index: dict[ + _HandlerT, set[FlowHandler[_FlowResultT, _HandlerT]] + ] = {} + self._init_data_process_index: dict[ + type, set[FlowHandler[_FlowResultT, _HandlerT]] + ] = {} @abc.abstractmethod async def async_create_flow( self, - handler_key: str, + handler_key: _HandlerT, *, context: dict[str, Any] | None = None, data: dict[str, Any] | None = None, - ) -> FlowHandler[_FlowResultT]: + ) -> FlowHandler[_FlowResultT, _HandlerT]: """Create a flow for specified handler. Handler key is the domain of the component that we want to set up. @@ -220,18 +225,18 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): @abc.abstractmethod async def async_finish_flow( - self, flow: FlowHandler[_FlowResultT], result: _FlowResultT + self, flow: FlowHandler[_FlowResultT, _HandlerT], result: _FlowResultT ) -> _FlowResultT: """Finish a data entry flow.""" async def async_post_init( - self, flow: FlowHandler[_FlowResultT], result: _FlowResultT + self, flow: FlowHandler[_FlowResultT, _HandlerT], result: _FlowResultT ) -> None: """Entry has finished executing its first step asynchronously.""" @callback def async_has_matching_flow( - self, handler: str, match_context: dict[str, Any], data: Any + self, handler: _HandlerT, match_context: dict[str, Any], data: Any ) -> bool: """Check if an existing matching flow is in progress. @@ -265,7 +270,7 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): @callback def async_progress_by_handler( self, - handler: str, + handler: _HandlerT, include_uninitialized: bool = False, match_context: dict[str, Any] | None = None, ) -> list[_FlowResultT]: @@ -298,8 +303,8 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): @callback def _async_progress_by_handler( - self, handler: str, match_context: dict[str, Any] | None - ) -> list[FlowHandler[_FlowResultT]]: + self, handler: _HandlerT, match_context: dict[str, Any] | None + ) -> list[FlowHandler[_FlowResultT, _HandlerT]]: """Return the flows in progress by handler. If match_context is specified, only return flows with a context that @@ -315,7 +320,11 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): ] async def async_init( - self, handler: str, *, context: dict[str, Any] | None = None, data: Any = None + self, + handler: _HandlerT, + *, + context: dict[str, Any] | None = None, + data: Any = None, ) -> _FlowResultT: """Start a data entry flow.""" if context is None: @@ -445,7 +454,9 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): self._async_remove_flow_progress(flow_id) @callback - def _async_add_flow_progress(self, flow: FlowHandler[_FlowResultT]) -> None: + def _async_add_flow_progress( + self, flow: FlowHandler[_FlowResultT, _HandlerT] + ) -> None: """Add a flow to in progress.""" if flow.init_data is not None: init_data_type = type(flow.init_data) @@ -454,7 +465,9 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): self._handler_progress_index.setdefault(flow.handler, set()).add(flow) @callback - def _async_remove_flow_from_index(self, flow: FlowHandler[_FlowResultT]) -> None: + def _async_remove_flow_from_index( + self, flow: FlowHandler[_FlowResultT, _HandlerT] + ) -> None: """Remove a flow from in progress.""" if flow.init_data is not None: init_data_type = type(flow.init_data) @@ -480,7 +493,7 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): async def _async_handle_step( self, - flow: FlowHandler[_FlowResultT], + flow: FlowHandler[_FlowResultT, _HandlerT], step_id: str, user_input: dict | BaseServiceInfo | None, ) -> _FlowResultT: @@ -557,7 +570,7 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): return result def _raise_if_step_does_not_exist( - self, flow: FlowHandler[_FlowResultT], step_id: str + self, flow: FlowHandler[_FlowResultT, _HandlerT], step_id: str ) -> None: """Raise if the step does not exist.""" method = f"async_step_{step_id}" @@ -568,7 +581,9 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): f"Handler {self.__class__.__name__} doesn't support step {step_id}" ) - async def _async_setup_preview(self, flow: FlowHandler[_FlowResultT]) -> None: + async def _async_setup_preview( + self, flow: FlowHandler[_FlowResultT, _HandlerT] + ) -> None: """Set up preview for a flow handler.""" if flow.handler not in self._preview: self._preview.add(flow.handler) @@ -576,7 +591,9 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): @callback def _async_flow_handler_to_flow_result( - self, flows: Iterable[FlowHandler[_FlowResultT]], include_uninitialized: bool + self, + flows: Iterable[FlowHandler[_FlowResultT, _HandlerT]], + include_uninitialized: bool, ) -> list[_FlowResultT]: """Convert a list of FlowHandler to a partial FlowResult that can be serialized.""" results = [] @@ -594,7 +611,7 @@ class FlowManager(abc.ABC, Generic[_FlowResultT]): return results -class FlowHandler(Generic[_FlowResultT]): +class FlowHandler(Generic[_FlowResultT, _HandlerT]): """Handle a data entry flow.""" _flow_result: Callable[..., _FlowResultT] = FlowResult # type: ignore[assignment] @@ -606,7 +623,7 @@ class FlowHandler(Generic[_FlowResultT]): # and removes the need for constant None checks or asserts. flow_id: str = None # type: ignore[assignment] hass: HomeAssistant = None # type: ignore[assignment] - handler: str = None # type: ignore[assignment] + handler: _HandlerT = None # type: ignore[assignment] # Ensure the attribute has a subscriptable, but immutable, default value. context: dict[str, Any] = MappingProxyType({}) # type: ignore[assignment] diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 21ebab7b4eb..524fd4ebf7d 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -17,7 +17,7 @@ from . import config_validation as cv _FlowManagerT = TypeVar( "_FlowManagerT", - bound=data_entry_flow.FlowManager[Any], + bound="data_entry_flow.FlowManager[Any]", default=data_entry_flow.FlowManager, ) @@ -61,7 +61,7 @@ class FlowManagerIndexView(_BaseFlowManagerView[_FlowManagerT]): @RequestDataValidator( vol.Schema( { - vol.Required("handler"): vol.Any(str, list), + vol.Required("handler"): str, vol.Optional("show_advanced_options", default=False): cv.boolean, }, extra=vol.ALLOW_EXTRA, @@ -79,14 +79,9 @@ class FlowManagerIndexView(_BaseFlowManagerView[_FlowManagerT]): self, request: web.Request, data: dict[str, Any] ) -> web.Response: """Handle a POST request.""" - if isinstance(data["handler"], list): - handler = tuple(data["handler"]) - else: - handler = data["handler"] - try: result = await self._flow_mgr.async_init( - handler, # type: ignore[arg-type] + data["handler"], context=self.get_context(data), ) except data_entry_flow.UnknownHandler: From 531e25cbc6a2a68af7faf20861854c12402d6abb Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:37:48 +0100 Subject: [PATCH 0510/1691] Change KEY_HASS to be an aiohttp AppKey (#111954) --- homeassistant/components/auth/__init__.py | 7 ++++--- homeassistant/components/auth/login_flow.py | 5 +++-- homeassistant/components/http/__init__.py | 4 +++- homeassistant/components/http/ban.py | 3 ++- homeassistant/components/http/const.py | 3 +-- homeassistant/components/http/static.py | 4 +--- homeassistant/helpers/http.py | 3 ++- tests/components/http/test_auth.py | 5 +++-- tests/components/http/test_ban.py | 20 ++++++++++---------- tests/components/http/test_init.py | 10 ++++++++++ 10 files changed, 39 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index dd07e137e5e..5f3836252df 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -143,6 +143,7 @@ from homeassistant.auth.models import ( User, ) from homeassistant.components import websocket_api +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import ( async_sign_path, async_user_not_allowed_do_auth, @@ -209,7 +210,7 @@ class RevokeTokenView(HomeAssistantView): async def post(self, request: web.Request) -> web.Response: """Revoke a token.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] data = cast(MultiDictProxy[str], await request.post()) # OAuth 2.0 Token Revocation [RFC7009] @@ -243,7 +244,7 @@ class TokenView(HomeAssistantView): @log_invalid_auth async def post(self, request: web.Request) -> web.Response: """Grant a token.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] data = cast(MultiDictProxy[str], await request.post()) grant_type = data.get("grant_type") @@ -415,7 +416,7 @@ class LinkUserView(HomeAssistantView): @RequestDataValidator(vol.Schema({"code": str, "client_id": str})) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Link a user.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] user: User = request["hass_user"] credentials = self._retrieve_credentials(data["client_id"], data["code"]) diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 12b1893bc9d..8051e871776 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -81,6 +81,7 @@ from homeassistant import data_entry_flow from homeassistant.auth import AuthManagerFlowManager, InvalidAuthError from homeassistant.auth.models import AuthFlowResult, Credentials from homeassistant.components import onboarding +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import async_user_not_allowed_do_auth from homeassistant.components.http.ban import ( log_invalid_auth, @@ -144,7 +145,7 @@ class AuthProvidersView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Get available auth providers.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if not onboarding.async_is_user_onboarded(hass): return self.json_message( message="Onboarding not finished", @@ -255,7 +256,7 @@ class LoginFlowBaseView(HomeAssistantView): await process_wrong_login(request) return self.json(_prepare_result_json(result)) - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if not await indieauth.verify_redirect_uri( hass, client_id, result["context"]["redirect_uri"] diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 6f60112c9ba..d053e238e6e 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -34,6 +34,7 @@ from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.helpers.http import ( KEY_AUTHENTICATED, # noqa: F401 + KEY_HASS, HomeAssistantView, current_request, ) @@ -47,7 +48,7 @@ from homeassistant.util.json import json_loads from .auth import async_setup_auth from .ban import setup_bans -from .const import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID, KEY_HASS_USER # noqa: F401 +from .const import KEY_HASS_REFRESH_TOKEN_ID, KEY_HASS_USER # noqa: F401 from .cors import setup_cors from .decorators import require_admin # noqa: F401 from .forwarded import async_setup_forwarded @@ -323,6 +324,7 @@ class HomeAssistantHTTP: ) -> None: """Initialize the server.""" self.app[KEY_HASS] = self.hass + self.app["hass"] = self.hass # For backwards compatibility # Order matters, security filters middleware needs to go first, # forwarded middleware needs to go second. diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 0b720b078b9..bec11d6e5ff 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -21,6 +21,7 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as dt_util, yaml +from .const import KEY_HASS from .view import HomeAssistantView _HassViewT = TypeVar("_HassViewT", bound=HomeAssistantView) @@ -105,7 +106,7 @@ async def process_wrong_login(request: Request) -> None: Increase failed login attempts counter for remote IP address. Add ip ban entry if failed login attempts exceeds threshold. """ - hass = request.app["hass"] + hass = request.app[KEY_HASS] remote_addr = ip_address(request.remote) # type: ignore[arg-type] remote_host = request.remote diff --git a/homeassistant/components/http/const.py b/homeassistant/components/http/const.py index 090e5234aeb..3181b043b88 100644 --- a/homeassistant/components/http/const.py +++ b/homeassistant/components/http/const.py @@ -1,8 +1,7 @@ """HTTP specific constants.""" from typing import Final -from homeassistant.helpers.http import KEY_AUTHENTICATED # noqa: F401 +from homeassistant.helpers.http import KEY_AUTHENTICATED, KEY_HASS # noqa: F401 -KEY_HASS: Final = "hass" KEY_HASS_USER: Final = "hass_user" KEY_HASS_REFRESH_TOKEN_ID: Final = "hass_refresh_token_id" diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index e6e773d4c0c..57f3f22866e 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -12,8 +12,6 @@ from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound from aiohttp.web_urldispatcher import StaticResource from lru import LRU -from homeassistant.core import HomeAssistant - from .const import KEY_HASS CACHE_TIME: Final = 31 * 86400 # = 1 month @@ -48,7 +46,7 @@ class CachingStaticResource(StaticResource): rel_url = request.match_info["filename"] key = (rel_url, self._directory) if (filepath_content_type := PATH_CACHE.get(key)) is None: - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] try: filepath = await hass.async_add_executor_job(_get_file_path, *key) except (ValueError, FileNotFoundError) as error: diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index 63ff173a3a0..6c92917f0bc 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -10,7 +10,7 @@ from typing import Any, Final from aiohttp import web from aiohttp.typedefs import LooseHeaders -from aiohttp.web import Request +from aiohttp.web import AppKey, Request from aiohttp.web_exceptions import ( HTTPBadRequest, HTTPInternalServerError, @@ -30,6 +30,7 @@ _LOGGER = logging.getLogger(__name__) KEY_AUTHENTICATED: Final = "ha_authenticated" +KEY_HASS: AppKey[HomeAssistant] = AppKey("hass") current_request: ContextVar[Request | None] = ContextVar( "current_request", default=None diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index ab56dca5580..a54a697240a 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -17,6 +17,7 @@ from homeassistant.auth.providers.legacy_api_password import ( LegacyApiPasswordAuthProvider, ) from homeassistant.components import websocket_api +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.auth import ( CONTENT_USER_NAME, DATA_SIGN_SECRET, @@ -78,7 +79,7 @@ async def get_legacy_user(auth): def app(hass): """Fixture to set up a web.Application.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass app.router.add_get("/", mock_handler) async_setup_forwarded(app, True, []) return app @@ -88,7 +89,7 @@ def app(hass): def app2(hass): """Fixture to set up a web.Application without real_ip middleware.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass app.router.add_get("/", mock_handler) return app diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index e38a9c97071..26301cf5b79 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -10,7 +10,7 @@ from aiohttp.web_middlewares import middleware import pytest import homeassistant.components.http as http -from homeassistant.components.http import KEY_AUTHENTICATED +from homeassistant.components.http import KEY_AUTHENTICATED, KEY_HASS from homeassistant.components.http.ban import ( IP_BANS_FILE, KEY_BAN_MANAGER, @@ -58,7 +58,7 @@ async def test_access_from_banned_ip( ) -> None: """Test accessing to server from banned IP. Both trusted and not.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -87,7 +87,7 @@ async def test_access_from_banned_ip_with_partially_broken_yaml_file( still load the bans. """ app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -118,7 +118,7 @@ async def test_no_ip_bans_file( ) -> None: """Test no ip bans file.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -138,7 +138,7 @@ async def test_failure_loading_ip_bans_file( ) -> None: """Test failure loading ip bans file.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -160,7 +160,7 @@ async def test_ip_ban_manager_never_started( ) -> None: """Test we handle the ip ban manager not being started.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass setup_bans(hass, app, 5) set_real_ip = mock_real_ip(app) @@ -199,7 +199,7 @@ async def test_access_from_supervisor_ip( ) -> None: """Test accessing to server from supervisor IP.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def unauth_handler(request): """Return a mock web response.""" @@ -270,7 +270,7 @@ async def test_ip_bans_file_creation( ) -> None: """Testing if banned IP file created.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def unauth_handler(request): """Return a mock web response.""" @@ -326,7 +326,7 @@ async def test_failed_login_attempts_counter( ) -> None: """Testing if failed login attempts counter increased.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def auth_handler(request): """Return 200 status code.""" @@ -398,7 +398,7 @@ async def test_single_ban_file_entry( ) -> None: """Test that only one item is added to ban file.""" app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass async def unauth_handler(request): """Return a mock web response.""" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 97e39811cd8..a1be00e3a2b 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -14,6 +14,7 @@ from homeassistant.auth.providers.legacy_api_password import ( ) import homeassistant.components.http as http from homeassistant.core import HomeAssistant +from homeassistant.helpers.http import KEY_HASS from homeassistant.helpers.network import NoURLAvailableError from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -97,6 +98,15 @@ async def test_registering_view_while_running( hass.http.register_view(TestView) +async def test_homeassistant_assigned_to_app(hass: HomeAssistant) -> None: + """Test HomeAssistant instance is assigned to HomeAssistantApp.""" + assert await async_setup_component(hass, "api", {"http": {}}) + await hass.async_start() + assert hass.http.app[KEY_HASS] == hass + assert hass.http.app["hass"] == hass # For backwards compatibility + await hass.async_stop() + + async def test_not_log_password( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, From fa4c0d78e98cc24f228a09db2a5dce7ba938def6 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 7 Mar 2024 16:41:54 +0100 Subject: [PATCH 0511/1691] Restore the juicenet integration (#112578) --- .coveragerc | 6 + CODEOWNERS | 2 + homeassistant/components/juicenet/__init__.py | 112 +++++++++++++--- .../components/juicenet/config_flow.py | 69 +++++++++- homeassistant/components/juicenet/const.py | 6 + homeassistant/components/juicenet/device.py | 19 +++ homeassistant/components/juicenet/entity.py | 34 +++++ .../components/juicenet/manifest.json | 7 +- homeassistant/components/juicenet/number.py | 99 ++++++++++++++ homeassistant/components/juicenet/sensor.py | 116 ++++++++++++++++ .../components/juicenet/strings.json | 41 +++++- homeassistant/components/juicenet/switch.py | 49 +++++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + script/hassfest/requirements.py | 1 + tests/components/juicenet/test_config_flow.py | 124 ++++++++++++++++++ tests/components/juicenet/test_init.py | 50 ------- 19 files changed, 669 insertions(+), 79 deletions(-) create mode 100644 homeassistant/components/juicenet/const.py create mode 100644 homeassistant/components/juicenet/device.py create mode 100644 homeassistant/components/juicenet/entity.py create mode 100644 homeassistant/components/juicenet/number.py create mode 100644 homeassistant/components/juicenet/sensor.py create mode 100644 homeassistant/components/juicenet/switch.py create mode 100644 tests/components/juicenet/test_config_flow.py delete mode 100644 tests/components/juicenet/test_init.py diff --git a/.coveragerc b/.coveragerc index 72021c60ac1..f5f22096115 100644 --- a/.coveragerc +++ b/.coveragerc @@ -641,6 +641,12 @@ omit = homeassistant/components/izone/climate.py homeassistant/components/izone/discovery.py homeassistant/components/joaoapps_join/* + homeassistant/components/juicenet/__init__.py + homeassistant/components/juicenet/device.py + homeassistant/components/juicenet/entity.py + homeassistant/components/juicenet/number.py + homeassistant/components/juicenet/sensor.py + homeassistant/components/juicenet/switch.py homeassistant/components/justnimbus/coordinator.py homeassistant/components/justnimbus/entity.py homeassistant/components/justnimbus/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index d4d1e70388f..15af941984b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -669,6 +669,8 @@ build.json @home-assistant/supervisor /tests/components/jellyfin/ @j-stienstra @ctalkington /homeassistant/components/jewish_calendar/ @tsvi /tests/components/jewish_calendar/ @tsvi +/homeassistant/components/juicenet/ @jesserockz +/tests/components/juicenet/ @jesserockz /homeassistant/components/justnimbus/ @kvanzuijlen /tests/components/justnimbus/ @kvanzuijlen /homeassistant/components/jvc_projector/ @SteveEasley @msavazzi diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index 820f0d1fcc0..bcefe763e15 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -1,37 +1,107 @@ """The JuiceNet integration.""" -from __future__ import annotations +from datetime import timedelta +import logging -from homeassistant.config_entries import ConfigEntry, ConfigEntryState +import aiohttp +from pyjuicenet import Api, TokenError +import voluptuous as vol + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_ACCESS_TOKEN, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import issue_registry as ir +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -DOMAIN = "juicenet" +from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR +from .device import JuiceNetApi + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = [Platform.NUMBER, Platform.SENSOR, Platform.SWITCH] + +CONFIG_SCHEMA = vol.Schema( + vol.All( + cv.deprecated(DOMAIN), + {DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})}, + ), + extra=vol.ALLOW_EXTRA, +) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the JuiceNet component.""" + conf = config.get(DOMAIN) + hass.data.setdefault(DOMAIN, {}) + + if not conf: + return True + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf + ) + ) + return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up JuiceNet from a config entry.""" - ir.async_create_issue( + + config = entry.data + + session = async_get_clientsession(hass) + + access_token = config[CONF_ACCESS_TOKEN] + api = Api(access_token, session) + + juicenet = JuiceNetApi(api) + + try: + await juicenet.setup() + except TokenError as error: + _LOGGER.error("JuiceNet Error %s", error) + return False + except aiohttp.ClientError as error: + _LOGGER.error("Could not reach the JuiceNet API %s", error) + raise ConfigEntryNotReady from error + + if not juicenet.devices: + _LOGGER.error("No JuiceNet devices found for this account") + return False + _LOGGER.info("%d JuiceNet device(s) found", len(juicenet.devices)) + + async def async_update_data(): + """Update all device states from the JuiceNet API.""" + for device in juicenet.devices: + await device.update_state(True) + return True + + coordinator = DataUpdateCoordinator( hass, - DOMAIN, - DOMAIN, - is_fixable=False, - severity=ir.IssueSeverity.ERROR, - translation_key="integration_removed", - translation_placeholders={ - "entries": "/config/integrations/integration/juicenet", - }, + _LOGGER, + name="JuiceNet", + update_method=async_update_data, + update_interval=timedelta(seconds=30), ) + await coordinator.async_config_entry_first_refresh() + + hass.data[DOMAIN][entry.entry_id] = { + JUICENET_API: juicenet, + JUICENET_COORDINATOR: coordinator, + } + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - if all( - config_entry.state is ConfigEntryState.NOT_LOADED - for config_entry in hass.config_entries.async_entries(DOMAIN) - if config_entry.entry_id != entry.entry_id - ): - ir.async_delete_issue(hass, DOMAIN, DOMAIN) - - return True + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/juicenet/config_flow.py b/homeassistant/components/juicenet/config_flow.py index 1385aa2eca8..743e4098a9a 100644 --- a/homeassistant/components/juicenet/config_flow.py +++ b/homeassistant/components/juicenet/config_flow.py @@ -1,11 +1,78 @@ """Config flow for JuiceNet integration.""" +import logging +import aiohttp +from pyjuicenet import Api, TokenError +import voluptuous as vol + +from homeassistant import core, exceptions from homeassistant.config_entries import ConfigFlow +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.helpers.aiohttp_client import async_get_clientsession -from . import DOMAIN +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) + + +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + session = async_get_clientsession(hass) + juicenet = Api(data[CONF_ACCESS_TOKEN], session) + + try: + await juicenet.get_devices() + except TokenError as error: + _LOGGER.error("Token Error %s", error) + raise InvalidAuth from error + except aiohttp.ClientError as error: + _LOGGER.error("Error connecting %s", error) + raise CannotConnect from error + + # Return info that you want to store in the config entry. + return {"title": "JuiceNet"} class JuiceNetConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for JuiceNet.""" VERSION = 1 + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + await self.async_set_unique_id(user_input[CONF_ACCESS_TOKEN]) + self._abort_if_unique_id_configured() + + try: + info = await validate_input(self.hass, user_input) + return self.async_create_entry(title=info["title"], data=user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + async def async_step_import(self, user_input): + """Handle import.""" + return await self.async_step_user(user_input) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/juicenet/const.py b/homeassistant/components/juicenet/const.py new file mode 100644 index 00000000000..5dc3e5c3e27 --- /dev/null +++ b/homeassistant/components/juicenet/const.py @@ -0,0 +1,6 @@ +"""Constants used by the JuiceNet component.""" + +DOMAIN = "juicenet" + +JUICENET_API = "juicenet_api" +JUICENET_COORDINATOR = "juicenet_coordinator" diff --git a/homeassistant/components/juicenet/device.py b/homeassistant/components/juicenet/device.py new file mode 100644 index 00000000000..86e1c92e4da --- /dev/null +++ b/homeassistant/components/juicenet/device.py @@ -0,0 +1,19 @@ +"""Adapter to wrap the pyjuicenet api for home assistant.""" + + +class JuiceNetApi: + """Represent a connection to JuiceNet.""" + + def __init__(self, api): + """Create an object from the provided API instance.""" + self.api = api + self._devices = [] + + async def setup(self): + """JuiceNet device setup.""" # noqa: D403 + self._devices = await self.api.get_devices() + + @property + def devices(self) -> list: + """Get a list of devices managed by this account.""" + return self._devices diff --git a/homeassistant/components/juicenet/entity.py b/homeassistant/components/juicenet/entity.py new file mode 100644 index 00000000000..b3433948582 --- /dev/null +++ b/homeassistant/components/juicenet/entity.py @@ -0,0 +1,34 @@ +"""Adapter to wrap the pyjuicenet api for home assistant.""" + +from pyjuicenet import Charger + +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +from .const import DOMAIN + + +class JuiceNetDevice(CoordinatorEntity): + """Represent a base JuiceNet device.""" + + _attr_has_entity_name = True + + def __init__( + self, device: Charger, key: str, coordinator: DataUpdateCoordinator + ) -> None: + """Initialise the sensor.""" + super().__init__(coordinator) + self.device = device + self.key = key + self._attr_unique_id = f"{device.id}-{key}" + self._attr_device_info = DeviceInfo( + configuration_url=( + f"https://home.juice.net/Portal/Details?unitID={device.id}" + ), + identifiers={(DOMAIN, device.id)}, + manufacturer="JuiceNet", + name=device.name, + ) diff --git a/homeassistant/components/juicenet/manifest.json b/homeassistant/components/juicenet/manifest.json index 5bdad83ac1e..979e540af01 100644 --- a/homeassistant/components/juicenet/manifest.json +++ b/homeassistant/components/juicenet/manifest.json @@ -1,9 +1,10 @@ { "domain": "juicenet", "name": "JuiceNet", - "codeowners": [], + "codeowners": ["@jesserockz"], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/juicenet", - "integration_type": "system", "iot_class": "cloud_polling", - "requirements": [] + "loggers": ["pyjuicenet"], + "requirements": ["python-juicenet==1.1.0"] } diff --git a/homeassistant/components/juicenet/number.py b/homeassistant/components/juicenet/number.py new file mode 100644 index 00000000000..fd2535c5bf3 --- /dev/null +++ b/homeassistant/components/juicenet/number.py @@ -0,0 +1,99 @@ +"""Support for controlling juicenet/juicepoint/juicebox based EVSE numbers.""" +from __future__ import annotations + +from dataclasses import dataclass + +from pyjuicenet import Api, Charger + +from homeassistant.components.number import ( + DEFAULT_MAX_VALUE, + NumberEntity, + NumberEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR +from .entity import JuiceNetDevice + + +@dataclass(frozen=True) +class JuiceNetNumberEntityDescriptionMixin: + """Mixin for required keys.""" + + setter_key: str + + +@dataclass(frozen=True) +class JuiceNetNumberEntityDescription( + NumberEntityDescription, JuiceNetNumberEntityDescriptionMixin +): + """An entity description for a JuiceNetNumber.""" + + native_max_value_key: str | None = None + + +NUMBER_TYPES: tuple[JuiceNetNumberEntityDescription, ...] = ( + JuiceNetNumberEntityDescription( + translation_key="amperage_limit", + key="current_charging_amperage_limit", + native_min_value=6, + native_max_value_key="max_charging_amperage", + native_step=1, + setter_key="set_charging_amperage_limit", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the JuiceNet Numbers.""" + juicenet_data = hass.data[DOMAIN][config_entry.entry_id] + api: Api = juicenet_data[JUICENET_API] + coordinator = juicenet_data[JUICENET_COORDINATOR] + + entities = [ + JuiceNetNumber(device, description, coordinator) + for device in api.devices + for description in NUMBER_TYPES + ] + async_add_entities(entities) + + +class JuiceNetNumber(JuiceNetDevice, NumberEntity): + """Implementation of a JuiceNet number.""" + + entity_description: JuiceNetNumberEntityDescription + + def __init__( + self, + device: Charger, + description: JuiceNetNumberEntityDescription, + coordinator: DataUpdateCoordinator, + ) -> None: + """Initialise the number.""" + super().__init__(device, description.key, coordinator) + self.entity_description = description + + @property + def native_value(self) -> float | None: + """Return the value of the entity.""" + return getattr(self.device, self.entity_description.key, None) + + @property + def native_max_value(self) -> float: + """Return the maximum value.""" + if self.entity_description.native_max_value_key is not None: + return getattr(self.device, self.entity_description.native_max_value_key) + if self.entity_description.native_max_value is not None: + return self.entity_description.native_max_value + return DEFAULT_MAX_VALUE + + async def async_set_native_value(self, value: float) -> None: + """Update the current value.""" + await getattr(self.device, self.entity_description.setter_key)(value) diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py new file mode 100644 index 00000000000..5f71e066b9c --- /dev/null +++ b/homeassistant/components/juicenet/sensor.py @@ -0,0 +1,116 @@ +"""Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" +from __future__ import annotations + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfEnergy, + UnitOfPower, + UnitOfTemperature, + UnitOfTime, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR +from .entity import JuiceNetDevice + +SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="status", + name="Charging Status", + ), + SensorEntityDescription( + key="temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="voltage", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + device_class=SensorDeviceClass.VOLTAGE, + ), + SensorEntityDescription( + key="amps", + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + device_class=SensorDeviceClass.CURRENT, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="watts", + native_unit_of_measurement=UnitOfPower.WATT, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + ), + SensorEntityDescription( + key="charge_time", + translation_key="charge_time", + native_unit_of_measurement=UnitOfTime.SECONDS, + icon="mdi:timer-outline", + ), + SensorEntityDescription( + key="energy_added", + translation_key="energy_added", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the JuiceNet Sensors.""" + juicenet_data = hass.data[DOMAIN][config_entry.entry_id] + api = juicenet_data[JUICENET_API] + coordinator = juicenet_data[JUICENET_COORDINATOR] + + entities = [ + JuiceNetSensorDevice(device, coordinator, description) + for device in api.devices + for description in SENSOR_TYPES + ] + async_add_entities(entities) + + +class JuiceNetSensorDevice(JuiceNetDevice, SensorEntity): + """Implementation of a JuiceNet sensor.""" + + def __init__( + self, device, coordinator, description: SensorEntityDescription + ) -> None: + """Initialise the sensor.""" + super().__init__(device, description.key, coordinator) + self.entity_description = description + + @property + def icon(self): + """Return the icon of the sensor.""" + icon = None + if self.entity_description.key == "status": + status = self.device.status + if status == "standby": + icon = "mdi:power-plug-off" + elif status == "plugged": + icon = "mdi:power-plug" + elif status == "charging": + icon = "mdi:battery-positive" + else: + icon = self.entity_description.icon + return icon + + @property + def native_value(self): + """Return the state.""" + return getattr(self.device, self.entity_description.key, None) diff --git a/homeassistant/components/juicenet/strings.json b/homeassistant/components/juicenet/strings.json index 6e25130955b..0e3732c66d2 100644 --- a/homeassistant/components/juicenet/strings.json +++ b/homeassistant/components/juicenet/strings.json @@ -1,8 +1,41 @@ { - "issues": { - "integration_removed": { - "title": "The JuiceNet integration has been removed", - "description": "Enel X has dropped support for JuiceNet in favor of JuicePass, and the JuiceNet integration has been removed from Home Assistant as it was no longer working.\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing JuiceNet integration entries]({entries})." + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "step": { + "user": { + "data": { + "api_token": "[%key:common::config_flow::data::api_token%]" + }, + "description": "You will need the API Token from https://home.juice.net/Manage.", + "title": "Connect to JuiceNet" + } + } + }, + "entity": { + "number": { + "amperage_limit": { + "name": "Amperage limit" + } + }, + "sensor": { + "charge_time": { + "name": "Charge time" + }, + "energy_added": { + "name": "Energy added" + } + }, + "switch": { + "charge_now": { + "name": "Charge now" + } } } } diff --git a/homeassistant/components/juicenet/switch.py b/homeassistant/components/juicenet/switch.py new file mode 100644 index 00000000000..7c373eeeb24 --- /dev/null +++ b/homeassistant/components/juicenet/switch.py @@ -0,0 +1,49 @@ +"""Support for monitoring juicenet/juicepoint/juicebox based EVSE switches.""" +from typing import Any + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR +from .entity import JuiceNetDevice + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the JuiceNet switches.""" + entities = [] + juicenet_data = hass.data[DOMAIN][config_entry.entry_id] + api = juicenet_data[JUICENET_API] + coordinator = juicenet_data[JUICENET_COORDINATOR] + + for device in api.devices: + entities.append(JuiceNetChargeNowSwitch(device, coordinator)) + async_add_entities(entities) + + +class JuiceNetChargeNowSwitch(JuiceNetDevice, SwitchEntity): + """Implementation of a JuiceNet switch.""" + + _attr_translation_key = "charge_now" + + def __init__(self, device, coordinator): + """Initialise the switch.""" + super().__init__(device, "charge_now", coordinator) + + @property + def is_on(self): + """Return true if switch is on.""" + return self.device.override_time != 0 + + async def async_turn_on(self, **kwargs: Any) -> None: + """Charge now.""" + await self.device.set_override(True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Don't charge now.""" + await self.device.set_override(False) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index b657433b20b..6fc9d4dee27 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -256,6 +256,7 @@ FLOWS = { "isy994", "izone", "jellyfin", + "juicenet", "justnimbus", "jvc_projector", "kaleidescape", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 9c7a2001787..99b400119c6 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2911,6 +2911,12 @@ "config_flow": false, "iot_class": "cloud_push" }, + "juicenet": { + "name": "JuiceNet", + "integration_type": "hub", + "config_flow": true, + "iot_class": "cloud_polling" + }, "justnimbus": { "name": "JustNimbus", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index fd453d2b93e..1105c612e1e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2241,6 +2241,9 @@ python-izone==1.2.9 # homeassistant.components.joaoapps_join python-join-api==0.0.9 +# homeassistant.components.juicenet +python-juicenet==1.1.0 + # homeassistant.components.tplink python-kasa[speedups]==0.6.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e6442ed4a72..6fec7801dd6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1723,6 +1723,9 @@ python-homewizard-energy==4.3.1 # homeassistant.components.izone python-izone==1.2.9 +# homeassistant.components.juicenet +python-juicenet==1.1.0 + # homeassistant.components.tplink python-kasa[speedups]==0.6.2.1 diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index 9ad9c3676b8..8b9f73336fe 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -33,6 +33,7 @@ IGNORE_VIOLATIONS = { "blink", "ezviz", "hdmi_cec", + "juicenet", "lupusec", "rainbird", "slide", diff --git a/tests/components/juicenet/test_config_flow.py b/tests/components/juicenet/test_config_flow.py new file mode 100644 index 00000000000..6adc841862e --- /dev/null +++ b/tests/components/juicenet/test_config_flow.py @@ -0,0 +1,124 @@ +"""Test the JuiceNet config flow.""" +from unittest.mock import MagicMock, patch + +import aiohttp +from pyjuicenet import TokenError + +from homeassistant import config_entries +from homeassistant.components.juicenet.const import DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.core import HomeAssistant + + +def _mock_juicenet_return_value(get_devices=None): + juicenet_mock = MagicMock() + type(juicenet_mock).get_devices = MagicMock(return_value=get_devices) + return juicenet_mock + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + return_value=MagicMock(), + ), patch( + "homeassistant.components.juicenet.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.juicenet.async_setup_entry", return_value=True + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == "JuiceNet" + assert result2["data"] == {CONF_ACCESS_TOKEN: "access_token"} + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_invalid_auth(hass: HomeAssistant) -> None: + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + side_effect=TokenError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect(hass: HomeAssistant) -> None: + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + side_effect=aiohttp.ClientError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_catch_unknown_errors(hass: HomeAssistant) -> None: + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + side_effect=Exception, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +async def test_import(hass: HomeAssistant) -> None: + """Test that import works as expected.""" + + with patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + return_value=MagicMock(), + ), patch( + "homeassistant.components.juicenet.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.juicenet.async_setup_entry", return_value=True + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_ACCESS_TOKEN: "access_token"}, + ) + await hass.async_block_till_done() + + assert result["type"] == "create_entry" + assert result["title"] == "JuiceNet" + assert result["data"] == {CONF_ACCESS_TOKEN: "access_token"} + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/juicenet/test_init.py b/tests/components/juicenet/test_init.py deleted file mode 100644 index 8896798abe3..00000000000 --- a/tests/components/juicenet/test_init.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Tests for the JuiceNet component.""" - -from homeassistant.components.juicenet import DOMAIN -from homeassistant.config_entries import ConfigEntryState -from homeassistant.core import HomeAssistant -from homeassistant.helpers import issue_registry as ir - -from tests.common import MockConfigEntry - - -async def test_juicenet_repair_issue( - hass: HomeAssistant, issue_registry: ir.IssueRegistry -) -> None: - """Test the JuiceNet configuration entry loading/unloading handles the repair.""" - config_entry_1 = MockConfigEntry( - title="Example 1", - domain=DOMAIN, - ) - config_entry_1.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry_1.entry_id) - await hass.async_block_till_done() - assert config_entry_1.state is ConfigEntryState.LOADED - - # Add a second one - config_entry_2 = MockConfigEntry( - title="Example 2", - domain=DOMAIN, - ) - config_entry_2.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry_2.entry_id) - await hass.async_block_till_done() - - assert config_entry_2.state is ConfigEntryState.LOADED - assert issue_registry.async_get_issue(DOMAIN, DOMAIN) - - # Remove the first one - await hass.config_entries.async_remove(config_entry_1.entry_id) - await hass.async_block_till_done() - - assert config_entry_1.state is ConfigEntryState.NOT_LOADED - assert config_entry_2.state is ConfigEntryState.LOADED - assert issue_registry.async_get_issue(DOMAIN, DOMAIN) - - # Remove the second one - await hass.config_entries.async_remove(config_entry_2.entry_id) - await hass.async_block_till_done() - - assert config_entry_1.state is ConfigEntryState.NOT_LOADED - assert config_entry_2.state is ConfigEntryState.NOT_LOADED - assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None From 1b824a4fea19c607eacad6737f88c154335d6bbf Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 7 Mar 2024 16:58:49 +0100 Subject: [PATCH 0512/1691] Update frontend to 20240307.0 (#112620) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index cea376fa8ff..f49a632cae2 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240306.0"] + "requirements": ["home-assistant-frontend==20240307.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index dfb9b8a8c57..e0dac4e9193 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240306.0 +home-assistant-frontend==20240307.0 home-assistant-intents==2024.3.6 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 1105c612e1e..8fab01f80da 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ hole==0.8.0 holidays==0.44 # homeassistant.components.frontend -home-assistant-frontend==20240306.0 +home-assistant-frontend==20240307.0 # homeassistant.components.conversation home-assistant-intents==2024.3.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6fec7801dd6..b3235cd3d4f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ hole==0.8.0 holidays==0.44 # homeassistant.components.frontend -home-assistant-frontend==20240306.0 +home-assistant-frontend==20240307.0 # homeassistant.components.conversation home-assistant-intents==2024.3.6 From 70a3f67e0327fd2cb2d94617331b3a164bbbda34 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 06:00:05 -1000 Subject: [PATCH 0513/1691] Avoid extracting the stack twice in system_log (#112572) --- homeassistant/components/system_log/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 3ede14a2ad6..8b158041cdc 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -180,8 +180,8 @@ class LogEntry: self.exception = "".join(traceback.format_exception(*record.exc_info)) _, _, tb = record.exc_info # Last line of traceback contains the root cause of the exception - if traceback.extract_tb(tb): - self.root_cause = str(traceback.extract_tb(tb)[-1]) + if extracted := traceback.extract_tb(tb): + self.root_cause = str(extracted[-1]) self.source = source self.count = 1 self.key = (self.name, source, self.root_cause) From 177d8f32097841204be5a80e6f6240ab2785f056 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 7 Mar 2024 17:11:45 +0100 Subject: [PATCH 0514/1691] Make hass-nabucasa a core requirement (#112623) --- pyproject.toml | 3 +++ requirements.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e50f633485b..e03e0fda88e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,9 @@ dependencies = [ "certifi>=2021.5.30", "ciso8601==2.3.1", "fnv-hash-fast==0.5.0", + # hass-nabucasa is imported by helpers which don't depend on the cloud + # integration + "hass-nabucasa==0.78.0", # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all "httpx==0.27.0", diff --git a/requirements.txt b/requirements.txt index 2a9358c911f..03f3ec987fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ bcrypt==4.1.2 certifi>=2021.5.30 ciso8601==2.3.1 fnv-hash-fast==0.5.0 +hass-nabucasa==0.78.0 httpx==0.27.0 home-assistant-bluetooth==1.12.0 ifaddr==0.2.0 From 714777e853de53d42a12fbf695375858b8bfcfc8 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 7 Mar 2024 17:55:06 +0100 Subject: [PATCH 0515/1691] modbus scan_interval: 0 is correct configuration (#112619) --- homeassistant/components/modbus/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 765ce4d8be3..1819bc649cb 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -310,7 +310,7 @@ def check_config(config: dict) -> dict: name = entity[CONF_NAME] addr = f"{hub_name}{entity[CONF_ADDRESS]}" scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) - if scan_interval < 5: + if 0 < scan_interval < 5: _LOGGER.warning( ( "%s %s scan_interval(%d) is lower than 5 seconds, " From 8ca127df2a0a9196fe05f6a835e6004390a77e64 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:03:44 +0100 Subject: [PATCH 0516/1691] Use KEY_HASS [h-z] (#112610) --- homeassistant/components/hassio/auth.py | 6 +++--- homeassistant/components/hassio/http.py | 4 ++-- homeassistant/components/history/__init__.py | 4 ++-- homeassistant/components/homekit/__init__.py | 4 ++-- homeassistant/components/html5/notify.py | 8 ++++---- homeassistant/components/image/__init__.py | 4 ++-- homeassistant/components/image_upload/__init__.py | 6 +++--- homeassistant/components/intent/__init__.py | 2 +- homeassistant/components/ios/__init__.py | 4 ++-- homeassistant/components/konnected/__init__.py | 9 +++++---- homeassistant/components/logbook/rest_api.py | 4 ++-- .../components/logi_circle/config_flow.py | 4 ++-- homeassistant/components/meraki/device_tracker.py | 4 ++-- homeassistant/components/mobile_app/http_api.py | 4 ++-- homeassistant/components/mystrom/binary_sensor.py | 4 ++-- homeassistant/components/onboarding/views.py | 14 +++++++------- homeassistant/components/plex/config_flow.py | 4 ++-- homeassistant/components/plex/view.py | 4 ++-- homeassistant/components/point/config_flow.py | 4 ++-- .../components/shopping_list/__init__.py | 8 ++++---- homeassistant/components/spaceapi/__init__.py | 4 ++-- homeassistant/components/stream/core.py | 4 ++-- homeassistant/components/stt/__init__.py | 6 +++--- homeassistant/components/torque/sensor.py | 4 ++-- homeassistant/components/webhook/__init__.py | 4 ++-- homeassistant/components/websocket_api/http.py | 4 ++-- homeassistant/components/zwave_js/api.py | 5 ++--- homeassistant/helpers/config_entry_oauth2_flow.py | 2 +- tests/components/http/test_data_validator.py | 6 +++--- tests/components/http/test_view.py | 15 ++++++++------- tests/components/logi_circle/test_config_flow.py | 3 ++- 31 files changed, 82 insertions(+), 80 deletions(-) diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py index 1e20b3da8e5..7ee9f04120a 100644 --- a/homeassistant/components/hassio/auth.py +++ b/homeassistant/components/hassio/auth.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.auth.models import User from homeassistant.auth.providers import homeassistant as auth_ha -from homeassistant.components.http import KEY_HASS_USER, HomeAssistantView +from homeassistant.components.http import KEY_HASS, KEY_HASS_USER, HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv @@ -74,7 +74,7 @@ class HassIOAuth(HassIOBaseAuth): async def post(self, request: web.Request, data: dict[str, str]) -> web.Response: """Handle auth requests.""" self._check_access(request) - provider = auth_ha.async_get_provider(request.app["hass"]) + provider = auth_ha.async_get_provider(request.app[KEY_HASS]) try: await provider.async_validate_login( @@ -104,7 +104,7 @@ class HassIOPasswordReset(HassIOBaseAuth): async def post(self, request: web.Request, data: dict[str, str]) -> web.Response: """Handle password reset requests.""" self._check_access(request) - provider = auth_ha.async_get_provider(request.app["hass"]) + provider = auth_ha.async_get_provider(request.app[KEY_HASS]) try: await provider.async_change_password( diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index d86f1b7dc5c..afaa57dda9a 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -23,11 +23,11 @@ from aiohttp.web_exceptions import HTTPBadGateway from homeassistant.components.http import ( KEY_AUTHENTICATED, + KEY_HASS, KEY_HASS_USER, HomeAssistantView, ) from homeassistant.components.onboarding import async_is_onboarded -from homeassistant.core import HomeAssistant from .const import X_HASS_SOURCE @@ -116,7 +116,7 @@ class HassIOView(HomeAssistantView): if path != unquote(path): return web.Response(status=HTTPStatus.BAD_REQUEST) - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] is_admin = request[KEY_AUTHENTICATED] and request[KEY_HASS_USER].is_admin authorized = is_admin diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 9eab92dce5c..de5f994f8c7 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -9,7 +9,7 @@ from aiohttp import web import voluptuous as vol from homeassistant.components import frontend -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.recorder import get_instance, history from homeassistant.components.recorder.util import session_scope from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE @@ -74,7 +74,7 @@ class HistoryPeriodView(HomeAssistantView): "filter_entity_id is missing", HTTPStatus.BAD_REQUEST ) - hass = request.app["hass"] + hass = request.app[KEY_HASS] for entity_id in entity_ids: if not hass.states.get(entity_id) and not valid_entity_id(entity_id): diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index afbfc784b19..76256ea1a7e 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -27,7 +27,7 @@ from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.device_automation.trigger import ( async_validate_trigger_config, ) -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.humidifier import DOMAIN as HUMIDIFIER_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -1163,7 +1163,7 @@ class HomeKitPairingQRView(HomeAssistantView): if not request.query_string: raise Unauthorized() entry_id, secret = request.query_string.split("-") - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] domain_data: dict[str, HomeKitEntryData] = hass.data[DOMAIN] if ( not (entry_data := domain_data.get(entry_id)) diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index d65a4c42488..a9ecabb37a8 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -19,7 +19,7 @@ import voluptuous as vol from voluptuous.humanize import humanize_error from homeassistant.components import websocket_api -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, @@ -231,7 +231,7 @@ class HTML5PushRegistrationView(HomeAssistantView): self.registrations[name] = data try: - hass = request.app["hass"] + hass = request.app[KEY_HASS] await hass.async_add_executor_job( save_json, self.json_path, self.registrations @@ -279,7 +279,7 @@ class HTML5PushRegistrationView(HomeAssistantView): reg = self.registrations.pop(found) try: - hass = request.app["hass"] + hass = request.app[KEY_HASS] await hass.async_add_executor_job( save_json, self.json_path, self.registrations @@ -388,7 +388,7 @@ class HTML5PushCallbackView(HomeAssistantView): ) event_name = f"{NOTIFY_CALLBACK_EVENT}.{event_payload[ATTR_TYPE]}" - request.app["hass"].bus.fire(event_name, event_payload) + request.app[KEY_HASS].bus.fire(event_name, event_payload) return self.json({"status": "ok", "event": event_payload[ATTR_TYPE]}) diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index 164b7048da8..9b05dcddffb 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Final, final from aiohttp import hdrs, web import httpx -from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView +from homeassistant.components.http import KEY_AUTHENTICATED, KEY_HASS, HomeAssistantView from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONTENT_TYPE_MULTIPART, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -345,7 +345,7 @@ async def async_get_still_stream( """Write image to stream.""" event.set() - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] remove = async_track_state_change_event( hass, image_entity.entity_id, diff --git a/homeassistant/components/image_upload/__init__.py b/homeassistant/components/image_upload/__init__.py index 6faa690b4cb..d7c5052af30 100644 --- a/homeassistant/components/image_upload/__init__.py +++ b/homeassistant/components/image_upload/__init__.py @@ -13,8 +13,8 @@ from aiohttp.web_request import FileField from PIL import Image, ImageOps, UnidentifiedImageError import voluptuous as vol +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.http.static import CACHE_HEADERS -from homeassistant.components.http.view import HomeAssistantView from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import collection, config_validation as cv @@ -162,7 +162,7 @@ class ImageUploadView(HomeAssistantView): request._client_max_size = MAX_SIZE # pylint: disable=protected-access data = await request.post() - item = await request.app["hass"].data[DOMAIN].async_create_item(data) + item = await request.app[KEY_HASS].data[DOMAIN].async_create_item(data) return self.json(item) @@ -200,7 +200,7 @@ class ImageServeView(HomeAssistantView): if image_info is None: raise web.HTTPNotFound() - hass = request.app["hass"] + hass = request.app[KEY_HASS] target_file = self.image_folder / image_id / f"{width}x{height}" if not target_file.is_file(): diff --git a/homeassistant/components/intent/__init__.py b/homeassistant/components/intent/__init__.py index f307208e537..7fd9fd4b712 100644 --- a/homeassistant/components/intent/__init__.py +++ b/homeassistant/components/intent/__init__.py @@ -348,7 +348,7 @@ class IntentHandleView(http.HomeAssistantView): ) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle intent with name/data.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[http.KEY_HASS] language = hass.config.language try: diff --git a/homeassistant/components/ios/__init__.py b/homeassistant/components/ios/__init__.py index 291f08425fa..5dcec01e138 100644 --- a/homeassistant/components/ios/__init__.py +++ b/homeassistant/components/ios/__init__.py @@ -7,7 +7,7 @@ from aiohttp import web import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -348,7 +348,7 @@ class iOSIdentifyDeviceView(HomeAssistantView): except ValueError: return self.json_message("Invalid JSON", HTTPStatus.BAD_REQUEST) - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] data[ATTR_LAST_SEEN_AT] = datetime.datetime.now().isoformat() diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index fa8a35d7a64..f23f9110a74 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, @@ -304,7 +304,7 @@ class KonnectedView(HomeAssistantView): async def update_sensor(self, request: Request, device_id) -> Response: """Process a put or post.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] data = hass.data[DOMAIN] auth = request.headers.get(AUTHORIZATION) @@ -376,7 +376,7 @@ class KonnectedView(HomeAssistantView): async def get(self, request: Request, device_id) -> Response: """Return the current binary state of a switch.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] data = hass.data[DOMAIN] if not (device := data[CONF_DEVICES].get(device_id)): @@ -424,7 +424,8 @@ class KonnectedView(HomeAssistantView): # Make sure entity is setup if zone_entity_id := zone.get(ATTR_ENTITY_ID): resp["state"] = self.binary_value( - hass.states.get(zone_entity_id).state, zone[CONF_ACTIVATION] + hass.states.get(zone_entity_id).state, # type: ignore[union-attr] + zone[CONF_ACTIVATION], ) return self.json(resp) diff --git a/homeassistant/components/logbook/rest_api.py b/homeassistant/components/logbook/rest_api.py index 57d0a6695c7..5ffd057670f 100644 --- a/homeassistant/components/logbook/rest_api.py +++ b/homeassistant/components/logbook/rest_api.py @@ -9,7 +9,7 @@ from typing import Any, cast from aiohttp import web import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.recorder import get_instance from homeassistant.components.recorder.filters import Filters from homeassistant.core import HomeAssistant, callback @@ -86,7 +86,7 @@ class LogbookView(HomeAssistantView): return self.json_message("Invalid end_time", HTTPStatus.BAD_REQUEST) end_day = end_day_dt - hass = request.app["hass"] + hass = request.app[KEY_HASS] context_id = request.query.get("context_id") diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index 6b7d6881c19..86ef530d0d3 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -7,7 +7,7 @@ from logi_circle import LogiCircle from logi_circle.exception import AuthorizationFailed import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.config_entries import ConfigFlow from homeassistant.const import ( CONF_API_KEY, @@ -191,7 +191,7 @@ class LogiCircleAuthCallbackView(HomeAssistantView): async def get(self, request): """Receive authorization code.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] if "code" in request.query: hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/meraki/device_tracker.py b/homeassistant/components/meraki/device_tracker.py index dae98734ae7..4149c5ac8ae 100644 --- a/homeassistant/components/meraki/device_tracker.py +++ b/homeassistant/components/meraki/device_tracker.py @@ -12,7 +12,7 @@ from homeassistant.components.device_tracker import ( AsyncSeeCallback, SourceType, ) -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -86,7 +86,7 @@ class MerakiView(HomeAssistantView): if not data["data"]["observations"]: _LOGGER.debug("No observations found") return - self._handle(request.app["hass"], data) + self._handle(request.app[KEY_HASS], data) @callback def _handle(self, hass, data): diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 129a6a8b8d1..b48221c3c56 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -10,7 +10,7 @@ from nacl.secret import SecretBox import voluptuous as vol from homeassistant.components import cloud -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import ATTR_DEVICE_ID, CONF_WEBHOOK_ID from homeassistant.helpers import config_validation as cv @@ -65,7 +65,7 @@ class RegistrationsView(HomeAssistantView): ) async def post(self, request: Request, data: dict) -> Response: """Handle the POST request for registration.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] webhook_id = secrets.token_hex() diff --git a/homeassistant/components/mystrom/binary_sensor.py b/homeassistant/components/mystrom/binary_sensor.py index 91a7814df2d..33e78ca3afe 100644 --- a/homeassistant/components/mystrom/binary_sensor.py +++ b/homeassistant/components/mystrom/binary_sensor.py @@ -5,7 +5,7 @@ from http import HTTPStatus import logging from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -37,7 +37,7 @@ class MyStromView(HomeAssistantView): async def get(self, request): """Handle the GET request received from a myStrom button.""" - res = await self._handle(request.app["hass"], request.query) + res = await self._handle(request.app[KEY_HASS], request.query) return res async def _handle(self, hass, data): diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 9bef34150a2..37ace1e1bab 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -14,7 +14,7 @@ from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.providers.homeassistant import HassAuthProvider from homeassistant.components import person from homeassistant.components.auth import indieauth -from homeassistant.components.http import KEY_HASS_REFRESH_TOKEN_ID +from homeassistant.components.http import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import HomeAssistant, callback @@ -85,7 +85,7 @@ class InstallationTypeOnboardingView(HomeAssistantView): if self._data["done"]: raise HTTPUnauthorized() - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] info = await async_get_system_info(hass) return self.json({"installation_type": info["installation_type"]}) @@ -136,7 +136,7 @@ class UserOnboardingView(_BaseOnboardingView): ) async def post(self, request: web.Request, data: dict[str, str]) -> web.Response: """Handle user creation, area creation.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] async with self._lock: if self._async_is_done(): @@ -190,7 +190,7 @@ class CoreConfigOnboardingView(_BaseOnboardingView): async def post(self, request: web.Request) -> web.Response: """Handle finishing core config step.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] async with self._lock: if self._async_is_done(): @@ -250,7 +250,7 @@ class IntegrationOnboardingView(_BaseOnboardingView): ) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle token creation.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] refresh_token_id = request[KEY_HASS_REFRESH_TOKEN_ID] async with self._lock: @@ -263,7 +263,7 @@ class IntegrationOnboardingView(_BaseOnboardingView): # Validate client ID and redirect uri if not await indieauth.verify_redirect_uri( - request.app["hass"], data["client_id"], data["redirect_uri"] + request.app[KEY_HASS], data["client_id"], data["redirect_uri"] ): return self.json_message( "invalid client id or redirect uri", HTTPStatus.BAD_REQUEST @@ -294,7 +294,7 @@ class AnalyticsOnboardingView(_BaseOnboardingView): async def post(self, request: web.Request) -> web.Response: """Handle finishing analytics step.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] async with self._lock: if self._async_is_done(): diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 93947614955..1bc72b9e639 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -14,7 +14,7 @@ import requests.exceptions import voluptuous as vol from homeassistant.components import http -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.config_entries import ( SOURCE_INTEGRATION_DISCOVERY, @@ -443,7 +443,7 @@ class PlexAuthorizationCallbackView(HomeAssistantView): async def get(self, request): """Receive authorization confirmation.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] await hass.config_entries.flow.async_configure( flow_id=request.query["flow_id"], user_input=None ) diff --git a/homeassistant/components/plex/view.py b/homeassistant/components/plex/view.py index ba883883ddc..49c0af1c48e 100644 --- a/homeassistant/components/plex/view.py +++ b/homeassistant/components/plex/view.py @@ -8,7 +8,7 @@ from aiohttp import web from aiohttp.hdrs import CACHE_CONTROL from aiohttp.typedefs import LooseHeaders -from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView +from homeassistant.components.http import KEY_AUTHENTICATED, KEY_HASS, HomeAssistantView from homeassistant.components.media_player import async_fetch_image from .const import SERVERS @@ -33,7 +33,7 @@ class PlexImageView(HomeAssistantView): if not request[KEY_AUTHENTICATED]: return web.Response(status=HTTPStatus.UNAUTHORIZED) - hass = request.app["hass"] + hass = request.app[KEY_HASS] if (server := get_plex_data(hass)[SERVERS].get(server_id)) is None: return web.Response(status=HTTPStatus.NOT_FOUND) diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index b9254aa7d4d..b88862758af 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -6,7 +6,7 @@ import logging from pypoint import PointSession import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import callback @@ -179,7 +179,7 @@ class MinutAuthCallbackView(HomeAssistantView): @staticmethod async def get(request): """Receive authorization code.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] if "code" in request.query: hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index e030f15d26e..a250adcf5f2 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -414,7 +414,7 @@ class ShoppingListView(http.HomeAssistantView): @callback def get(self, request: web.Request) -> web.Response: """Retrieve shopping list items.""" - return self.json(request.app["hass"].data[DOMAIN].items) + return self.json(request.app[http.KEY_HASS].data[DOMAIN].items) class UpdateShoppingListItemView(http.HomeAssistantView): @@ -426,7 +426,7 @@ class UpdateShoppingListItemView(http.HomeAssistantView): async def post(self, request: web.Request, item_id: str) -> web.Response: """Update a shopping list item.""" data = await request.json() - hass: HomeAssistant = request.app["hass"] + hass = request.app[http.KEY_HASS] try: item = await hass.data[DOMAIN].async_update(item_id, data) @@ -446,7 +446,7 @@ class CreateShoppingListItemView(http.HomeAssistantView): @RequestDataValidator(vol.Schema({vol.Required("name"): str})) async def post(self, request: web.Request, data: dict[str, str]) -> web.Response: """Create a new shopping list item.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[http.KEY_HASS] item = await hass.data[DOMAIN].async_add(data["name"]) return self.json(item) @@ -459,7 +459,7 @@ class ClearCompletedItemsView(http.HomeAssistantView): async def post(self, request: web.Request) -> web.Response: """Retrieve if API is running.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[http.KEY_HASS] await hass.data[DOMAIN].async_clear_completed() return self.json_message("Cleared completed items.") diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index 1a25986a243..bcbd2a258ac 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -3,7 +3,7 @@ from contextlib import suppress import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ICON, @@ -266,7 +266,7 @@ class APISpaceApiView(HomeAssistantView): @ha.callback def get(self, request): """Get SpaceAPI data.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] spaceapi = dict(hass.data[DATA_SPACEAPI]) is_sensors = spaceapi.get("sensors") diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 1d2957b35a3..b7f27367d77 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any from aiohttp import web import numpy as np -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.event import async_call_later from homeassistant.util.decorator import Registry @@ -380,7 +380,7 @@ class StreamView(HomeAssistantView): self, request: web.Request, token: str, sequence: str = "", part_num: str = "" ) -> web.StreamResponse: """Start a GET request.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] stream = next( (s for s in hass.data[DOMAIN][ATTR_STREAMS] if s.access_token == token), diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index c856a4817c9..45bdc3a63c7 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -18,7 +18,7 @@ from aiohttp.web_exceptions import ( import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant, callback @@ -250,7 +250,7 @@ class SpeechToTextView(HomeAssistantView): async def post(self, request: web.Request, provider: str) -> web.Response: """Convert Speech (audio) to text.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] provider_entity: SpeechToTextEntity | None = None if ( not (provider_entity := async_get_speech_to_text_entity(hass, provider)) @@ -290,7 +290,7 @@ class SpeechToTextView(HomeAssistantView): async def get(self, request: web.Request, provider: str) -> web.Response: """Return provider specific audio information.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if ( not (provider_entity := async_get_speech_to_text_entity(hass, provider)) and provider not in self.providers diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py index a2e1e35d2ae..0ac91589c35 100644 --- a/homeassistant/components/torque/sensor.py +++ b/homeassistant/components/torque/sensor.py @@ -5,7 +5,7 @@ import re import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_EMAIL, CONF_NAME, DEGREE from homeassistant.core import HomeAssistant, callback @@ -74,7 +74,7 @@ class TorqueReceiveDataView(HomeAssistantView): @callback def get(self, request): """Handle Torque data request.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] data = request.query if self.email is not None and self.email != data[SENSOR_EMAIL_FIELD]: diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 00b27fdb647..8d73e444279 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -14,7 +14,7 @@ from aiohttp.web import Request, Response import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.network import get_url, is_cloud_connection @@ -202,7 +202,7 @@ class WebhookView(HomeAssistantView): async def _handle(self, request: Request, webhook_id: str) -> Response: """Handle webhook call.""" _LOGGER.debug("Handling webhook %s payload for %s", request.method, webhook_id) - hass = request.app["hass"] + hass = request.app[KEY_HASS] return await async_handle_webhook(hass, webhook_id, request) get = _handle diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 82c54a08136..06b9c0c4bfa 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any, Final from aiohttp import WSMsgType, web -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -49,7 +49,7 @@ class WebsocketAPIView(HomeAssistantView): async def get(self, request: web.Request) -> web.WebSocketResponse: """Handle an incoming websocket connection.""" - return await WebSocketHandler(request.app["hass"], request).async_handle() + return await WebSocketHandler(request.app[KEY_HASS], request).async_handle() class WebSocketAdapter(logging.LoggerAdapter): diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 5aa27ada977..068ce348caf 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -55,8 +55,7 @@ from zwave_js_server.model.utils import ( from zwave_js_server.util.node import async_set_config_parameter from homeassistant.components import websocket_api -from homeassistant.components.http import require_admin -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin from homeassistant.components.websocket_api import ( ERR_INVALID_FORMAT, ERR_NOT_FOUND, @@ -2196,7 +2195,7 @@ class FirmwareUploadView(HomeAssistantView): @require_admin async def post(self, request: web.Request, device_id: str) -> web.Response: """Handle upload.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] try: node = async_get_node_from_device_id(hass, device_id, self._dev_reg) diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 337e6ca92b6..dcc0265ab61 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -455,7 +455,7 @@ class OAuth2AuthorizeCallbackView(http.HomeAssistantView): if "state" not in request.query: return web.Response(text="Missing state parameter") - hass = request.app["hass"] + hass = request.app[http.KEY_HASS] state = _decode_jwt(hass, request.query["state"]) diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index ecff4370999..c9f1ed46157 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -5,7 +5,7 @@ from unittest.mock import Mock from aiohttp import web import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from tests.typing import ClientSessionGenerator @@ -14,7 +14,7 @@ from tests.typing import ClientSessionGenerator async def get_client(aiohttp_client, validator): """Generate a client that hits a view decorated with validator.""" app = web.Application() - app["hass"] = Mock(is_stopping=False) + app[KEY_HASS] = Mock(is_stopping=False) app["allow_configured_cors"] = lambda _: None class TestView(HomeAssistantView): @@ -27,7 +27,7 @@ async def get_client(aiohttp_client, validator): """Test method.""" return b"" - TestView().register(app["hass"], app, app.router) + TestView().register(app[KEY_HASS], app, app.router) client = await aiohttp_client(app) return client diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index e52413d5225..6f8386ae36f 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -12,6 +12,7 @@ from aiohttp.web_exceptions import ( import pytest import voluptuous as vol +from homeassistant.components.http import KEY_HASS from homeassistant.components.http.view import ( HomeAssistantView, request_handler_factory, @@ -22,13 +23,13 @@ from homeassistant.exceptions import ServiceNotFound, Unauthorized @pytest.fixture def mock_request() -> Mock: """Mock a request.""" - return Mock(app={"hass": Mock(is_stopping=False)}, match_info={}) + return Mock(app={KEY_HASS: Mock(is_stopping=False)}, match_info={}) @pytest.fixture def mock_request_with_stopping() -> Mock: """Mock a request.""" - return Mock(app={"hass": Mock(is_stopping=True)}, match_info={}) + return Mock(app={KEY_HASS: Mock(is_stopping=True)}, match_info={}) async def test_invalid_json(caplog: pytest.LogCaptureFixture) -> None: @@ -52,7 +53,7 @@ async def test_handling_unauthorized(mock_request: Mock) -> None: """Test handling unauth exceptions.""" with pytest.raises(HTTPUnauthorized): await request_handler_factory( - mock_request.app["hass"], + mock_request.app[KEY_HASS], Mock(requires_auth=False), AsyncMock(side_effect=Unauthorized), )(mock_request) @@ -62,7 +63,7 @@ async def test_handling_invalid_data(mock_request: Mock) -> None: """Test handling unauth exceptions.""" with pytest.raises(HTTPBadRequest): await request_handler_factory( - mock_request.app["hass"], + mock_request.app[KEY_HASS], Mock(requires_auth=False), AsyncMock(side_effect=vol.Invalid("yo")), )(mock_request) @@ -72,7 +73,7 @@ async def test_handling_service_not_found(mock_request: Mock) -> None: """Test handling unauth exceptions.""" with pytest.raises(HTTPInternalServerError): await request_handler_factory( - mock_request.app["hass"], + mock_request.app[KEY_HASS], Mock(requires_auth=False), AsyncMock(side_effect=ServiceNotFound("test", "test")), )(mock_request) @@ -81,7 +82,7 @@ async def test_handling_service_not_found(mock_request: Mock) -> None: async def test_not_running(mock_request_with_stopping: Mock) -> None: """Test we get a 503 when not running.""" response = await request_handler_factory( - mock_request_with_stopping.app["hass"], + mock_request_with_stopping.app[KEY_HASS], Mock(requires_auth=False), AsyncMock(side_effect=Unauthorized), )(mock_request_with_stopping) @@ -92,7 +93,7 @@ async def test_invalid_handler(mock_request: Mock) -> None: """Test an invalid handler.""" with pytest.raises(TypeError): await request_handler_factory( - mock_request.app["hass"], + mock_request.app[KEY_HASS], Mock(requires_auth=False), AsyncMock(return_value=["not valid"]), )(mock_request) diff --git a/tests/components/logi_circle/test_config_flow.py b/tests/components/logi_circle/test_config_flow.py index 830760040e0..2c1642aefba 100644 --- a/tests/components/logi_circle/test_config_flow.py +++ b/tests/components/logi_circle/test_config_flow.py @@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest from homeassistant import config_entries, data_entry_flow +from homeassistant.components.http import KEY_HASS from homeassistant.components.logi_circle import config_flow from homeassistant.components.logi_circle.config_flow import ( DOMAIN, @@ -23,7 +24,7 @@ class MockRequest: def __init__(self, hass, query): """Init request object.""" - self.app = {"hass": hass} + self.app = {KEY_HASS: hass} self.query = query From f3594c543d2a923b57218c13a3f0b5bb9927588f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:09:20 +0100 Subject: [PATCH 0517/1691] Use KEY_HASS [a-g] (#112609) --- homeassistant/components/alexa/intent.py | 2 +- homeassistant/components/alexa/smart_home.py | 9 ++++-- .../components/ambiclimate/config_flow.py | 4 +-- homeassistant/components/api/__init__.py | 32 ++++++++----------- homeassistant/components/backup/http.py | 4 +-- homeassistant/components/calendar/__init__.py | 5 +-- homeassistant/components/cloud/http_api.py | 14 ++++---- .../components/config/config_entries.py | 10 +++--- homeassistant/components/config/core.py | 4 +-- homeassistant/components/config/view.py | 8 ++--- .../components/conversation/__init__.py | 2 +- .../components/diagnostics/__init__.py | 2 +- homeassistant/components/doorbird/view.py | 5 ++- .../components/emulated_hue/__init__.py | 3 +- .../components/emulated_hue/hue_api.py | 8 ++--- .../components/file_upload/__init__.py | 6 ++-- .../components/foursquare/__init__.py | 4 +-- homeassistant/components/frontend/__init__.py | 4 +-- .../components/google_assistant/http.py | 4 +-- .../ambiclimate/test_config_flow.py | 5 +-- 20 files changed, 67 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index 58319dd44b5..a55ff7b774f 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -64,7 +64,7 @@ class AlexaIntentsView(http.HomeAssistantView): async def post(self, request: http.HomeAssistantRequest) -> Response | bytes: """Handle Alexa.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[http.KEY_HASS] message: dict[str, Any] = await request.json() _LOGGER.debug("Received Alexa request: %s", message) diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index 88f66e93fc1..dc93ec56810 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -7,8 +7,11 @@ from yarl import URL from homeassistant import core from homeassistant.auth.models import User -from homeassistant.components.http import HomeAssistantRequest -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import ( + KEY_HASS, + HomeAssistantRequest, + HomeAssistantView, +) from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import entity_registry as er @@ -146,7 +149,7 @@ class SmartHomeView(HomeAssistantView): Lambda, which will need to forward the requests to here and pass back the response. """ - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] user: User = request["hass_user"] message: dict[str, Any] = await request.json() diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 598a8748412..1087f29643c 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -5,7 +5,7 @@ from typing import Any from aiohttp import web import ambiclimate -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import HomeAssistant, callback @@ -150,7 +150,7 @@ class AmbiclimateAuthCallbackView(HomeAssistantView): """Receive authorization token.""" if (code := request.query.get("code")) is None: return "No code" - hass = request.app["hass"] + hass = request.app[KEY_HASS] hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": "code"}, data=code diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 01a84cf606a..a6b899d01ec 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -117,7 +117,7 @@ class APICoreStateView(HomeAssistantView): Home Assistant core is running. Its primary use case is for supervisor to check if Home Assistant is running. """ - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] return self.json({"state": hass.state.value}) @@ -130,7 +130,7 @@ class APIEventStream(HomeAssistantView): @require_admin async def get(self, request: web.Request) -> web.StreamResponse: """Provide a streaming interface for the event bus.""" - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] stop_obj = object() to_write: asyncio.Queue[object | str] = asyncio.Queue() @@ -197,8 +197,7 @@ class APIConfigView(HomeAssistantView): @ha.callback def get(self, request: web.Request) -> web.Response: """Get current configuration.""" - hass: HomeAssistant = request.app[KEY_HASS] - return self.json(hass.config.as_dict()) + return self.json(request.app[KEY_HASS].config.as_dict()) class APIStatesView(HomeAssistantView): @@ -211,7 +210,7 @@ class APIStatesView(HomeAssistantView): def get(self, request: web.Request) -> web.Response: """Get current states.""" user: User = request[KEY_HASS_USER] - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] if user.is_admin: states = (state.as_dict_json for state in hass.states.async_all()) else: @@ -240,7 +239,7 @@ class APIEntityStateView(HomeAssistantView): def get(self, request: web.Request, entity_id: str) -> web.Response: """Retrieve state of entity.""" user: User = request[KEY_HASS_USER] - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] if not user.permissions.check_entity(entity_id, POLICY_READ): raise Unauthorized(entity_id=entity_id) @@ -256,7 +255,7 @@ class APIEntityStateView(HomeAssistantView): user: User = request[KEY_HASS_USER] if not user.is_admin: raise Unauthorized(entity_id=entity_id) - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] try: data = await request.json() except ValueError: @@ -296,8 +295,7 @@ class APIEntityStateView(HomeAssistantView): """Remove entity.""" if not request[KEY_HASS_USER].is_admin: raise Unauthorized(entity_id=entity_id) - hass: HomeAssistant = request.app[KEY_HASS] - if hass.states.async_remove(entity_id): + if request.app[KEY_HASS].states.async_remove(entity_id): return self.json_message("Entity removed.") return self.json_message("Entity not found.", HTTPStatus.NOT_FOUND) @@ -311,8 +309,7 @@ class APIEventListenersView(HomeAssistantView): @ha.callback def get(self, request: web.Request) -> web.Response: """Get event listeners.""" - hass: HomeAssistant = request.app[KEY_HASS] - return self.json(async_events_json(hass)) + return self.json(async_events_json(request.app[KEY_HASS])) class APIEventView(HomeAssistantView): @@ -346,8 +343,7 @@ class APIEventView(HomeAssistantView): if state: event_data[key] = state - hass: HomeAssistant = request.app[KEY_HASS] - hass.bus.async_fire( + request.app[KEY_HASS].bus.async_fire( event_type, event_data, ha.EventOrigin.remote, self.context(request) ) @@ -362,8 +358,7 @@ class APIServicesView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Get registered services.""" - hass: HomeAssistant = request.app[KEY_HASS] - services = await async_services_json(hass) + services = await async_services_json(request.app[KEY_HASS]) return self.json(services) @@ -380,7 +375,7 @@ class APIDomainServicesView(HomeAssistantView): Returns a list of changed states. """ - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] body = await request.text() try: data = json_loads(body) if body else None @@ -433,8 +428,7 @@ class APIComponentsView(HomeAssistantView): @ha.callback def get(self, request: web.Request) -> web.Response: """Get current loaded components.""" - hass: HomeAssistant = request.app[KEY_HASS] - return self.json(list(hass.config.components)) + return self.json(request.app[KEY_HASS].config.components) @lru_cache @@ -471,7 +465,7 @@ class APIErrorLog(HomeAssistantView): @require_admin async def get(self, request: web.Request) -> web.FileResponse: """Retrieve API error log.""" - hass: HomeAssistant = request.app[KEY_HASS] + hass = request.app[KEY_HASS] response = web.FileResponse(hass.data[DATA_LOGGING]) response.enable_compression() return response diff --git a/homeassistant/components/backup/http.py b/homeassistant/components/backup/http.py index a6a7a2346e4..a2aa7026445 100644 --- a/homeassistant/components/backup/http.py +++ b/homeassistant/components/backup/http.py @@ -6,7 +6,7 @@ from http import HTTPStatus from aiohttp.hdrs import CONTENT_DISPOSITION from aiohttp.web import FileResponse, Request, Response -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.core import HomeAssistant, callback from homeassistant.util import slugify @@ -35,7 +35,7 @@ class DownloadBackupView(HomeAssistantView): if not request["hass_user"].is_admin: return Response(status=HTTPStatus.UNAUTHORIZED) - manager: BackupManager = request.app["hass"].data[DOMAIN] + manager: BackupManager = request.app[KEY_HASS].data[DOMAIN] backup = await manager.get_backup(slug) if backup is None or not backup.path.exists(): diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index bef0e2fc09f..aff33dd9c31 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -652,7 +652,7 @@ class CalendarEventView(http.HomeAssistantView): try: calendar_event_list = await entity.async_get_events( - request.app["hass"], + request.app[http.KEY_HASS], dt_util.as_local(start_date), dt_util.as_local(end_date), ) @@ -682,11 +682,12 @@ class CalendarListView(http.HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Retrieve calendar list.""" - hass = request.app["hass"] + hass = request.app[http.KEY_HASS] calendar_list: list[dict[str, str]] = [] for entity in self.component.entities: state = hass.states.get(entity.entity_id) + assert state calendar_list.append({"name": state.name, "entity_id": entity.entity_id}) return self.json(sorted(calendar_list, key=lambda x: cast(str, x["name"]))) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 4fd9d5c0301..f5cbb287ef1 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -25,7 +25,7 @@ from homeassistant.components.alexa import ( ) from homeassistant.components.google_assistant import helpers as google_helpers from homeassistant.components.homeassistant import exposed_entities -from homeassistant.components.http import HomeAssistantView, require_admin +from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.core import HomeAssistant, callback @@ -197,7 +197,7 @@ class GoogleActionsSyncView(HomeAssistantView): @_handle_cloud_errors async def post(self, request: web.Request) -> web.Response: """Trigger a Google Actions sync.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] cloud: Cloud[CloudClient] = hass.data[DOMAIN] gconf = await cloud.client.get_google_config() status = await gconf.async_sync_entities(gconf.agent_user_id) @@ -217,7 +217,7 @@ class CloudLoginView(HomeAssistantView): ) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle login request.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] cloud: Cloud[CloudClient] = hass.data[DOMAIN] await cloud.login(data["email"], data["password"]) @@ -235,7 +235,7 @@ class CloudLogoutView(HomeAssistantView): @_handle_cloud_errors async def post(self, request: web.Request) -> web.Response: """Handle logout request.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] cloud: Cloud[CloudClient] = hass.data[DOMAIN] async with asyncio.timeout(REQUEST_TIMEOUT): @@ -262,7 +262,7 @@ class CloudRegisterView(HomeAssistantView): ) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle registration request.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] cloud: Cloud[CloudClient] = hass.data[DOMAIN] client_metadata = None @@ -299,7 +299,7 @@ class CloudResendConfirmView(HomeAssistantView): @RequestDataValidator(vol.Schema({vol.Required("email"): str})) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle resending confirm email code request.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] cloud: Cloud[CloudClient] = hass.data[DOMAIN] async with asyncio.timeout(REQUEST_TIMEOUT): @@ -319,7 +319,7 @@ class CloudForgotPasswordView(HomeAssistantView): @RequestDataValidator(vol.Schema({vol.Required("email"): str})) async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response: """Handle forgot password request.""" - hass = request.app["hass"] + hass = request.app[KEY_HASS] cloud: Cloud[CloudClient] = hass.data[DOMAIN] async with asyncio.timeout(REQUEST_TIMEOUT): diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index 48665645c6f..eb7b1786671 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant import config_entries, data_entry_flow from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT from homeassistant.components import websocket_api -from homeassistant.components.http import HomeAssistantView, require_admin +from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import DependencyError, Unauthorized @@ -64,7 +64,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """List available config entries.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] domain = None if "domain" in request.query: domain = request.query["domain"] @@ -88,7 +88,7 @@ class ConfigManagerEntryResourceView(HomeAssistantView): if not request["hass_user"].is_admin: raise Unauthorized(config_entry_id=entry_id, permission="remove") - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] try: result = await hass.config_entries.async_remove(entry_id) @@ -109,7 +109,7 @@ class ConfigManagerEntryResourceReloadView(HomeAssistantView): if not request["hass_user"].is_admin: raise Unauthorized(config_entry_id=entry_id, permission="remove") - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] entry = hass.config_entries.async_get_entry(entry_id) if not entry: return self.json_message("Invalid entry specified", HTTPStatus.NOT_FOUND) @@ -235,7 +235,7 @@ class ConfigManagerAvailableFlowView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """List available flow handlers.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] kwargs: dict[str, Any] = {} if "type" in request.query: kwargs["type_filter"] = request.query["type"] diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index c3e070a3751..d35b00654b7 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -7,7 +7,7 @@ from aiohttp import web import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.components.http import HomeAssistantView, require_admin +from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin from homeassistant.components.sensor import async_update_suggested_units from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import check_config, config_validation as cv @@ -34,7 +34,7 @@ class CheckConfigView(HomeAssistantView): async def post(self, request: web.Request) -> web.Response: """Validate configuration and return results.""" - res = await check_config.async_check_ha_config_file(request.app["hass"]) + res = await check_config.async_check_ha_config_file(request.app[KEY_HASS]) state = "invalid" if res.errors else "valid" diff --git a/homeassistant/components/config/view.py b/homeassistant/components/config/view.py index cf24074bda9..e3497ab91f1 100644 --- a/homeassistant/components/config/view.py +++ b/homeassistant/components/config/view.py @@ -10,7 +10,7 @@ from typing import Any, Generic, TypeVar, cast from aiohttp import web import voluptuous as vol -from homeassistant.components.http import HomeAssistantView, require_admin +from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -80,7 +80,7 @@ class BaseEditConfigView(HomeAssistantView, Generic[_DataT]): @require_admin async def get(self, request: web.Request, config_key: str) -> web.Response: """Fetch device specific config.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] async with self.mutation_lock: current = await self.read_config(hass) value = self._get_value(hass, current, config_key) @@ -103,7 +103,7 @@ class BaseEditConfigView(HomeAssistantView, Generic[_DataT]): except vol.Invalid as err: return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST) - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] try: # We just validate, we don't store that data because @@ -135,7 +135,7 @@ class BaseEditConfigView(HomeAssistantView, Generic[_DataT]): @require_admin async def delete(self, request: web.Request, config_key: str) -> web.Response: """Remove an entry.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] async with self.mutation_lock: current = await self.read_config(hass) value = self._get_value(hass, current, config_key) diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index 09b0e8e2310..38327e0108f 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -484,7 +484,7 @@ class ConversationProcessView(http.HomeAssistantView): ) async def post(self, request: web.Request, data: dict[str, str]) -> web.Response: """Send a request for processing.""" - hass = request.app["hass"] + hass = request.app[http.KEY_HASS] result = await async_converse( hass, diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index 679efd137ce..565fc2112b7 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -232,7 +232,7 @@ class DownloadDiagnosticsView(http.HomeAssistantView): device_diagnostics = sub_type is not None - hass: HomeAssistant = request.app["hass"] + hass = request.app[http.KEY_HASS] if (config_entry := hass.config_entries.async_get_entry(d_id)) is None: return web.Response(status=HTTPStatus.NOT_FOUND) diff --git a/homeassistant/components/doorbird/view.py b/homeassistant/components/doorbird/view.py index 396db79bf4c..b9d03288e26 100644 --- a/homeassistant/components/doorbird/view.py +++ b/homeassistant/components/doorbird/view.py @@ -5,8 +5,7 @@ from http import HTTPStatus from aiohttp import web -from homeassistant.components.http import HomeAssistantView -from homeassistant.core import HomeAssistant +from homeassistant.components.http import KEY_HASS, HomeAssistantView from .const import API_URL, DOMAIN from .device import async_reset_device_favorites @@ -23,7 +22,7 @@ class DoorBirdRequestView(HomeAssistantView): async def get(self, request: web.Request, event: str) -> web.Response: """Respond to requests from the device.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] token: str | None = request.query.get("token") if ( token is None diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index b5b51eb4361..ed24fe29c60 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -6,6 +6,7 @@ import logging from aiohttp import web import voluptuous as vol +from homeassistant.components.http import KEY_HASS from homeassistant.components.network import async_get_source_ip from homeassistant.const import ( CONF_ENTITIES, @@ -130,7 +131,7 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool: await config.async_setup() app = web.Application() - app["hass"] = hass + app[KEY_HASS] = hass # We misunderstood the startup signal. You're not allowed to change # anything during startup. Temp workaround. diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 873f446aad8..c1f853be80d 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -34,7 +34,7 @@ from homeassistant.components.cover import ( CoverEntityFeature, ) from homeassistant.components.fan import ATTR_PERCENTAGE, FanEntityFeature -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.humidifier import ATTR_HUMIDITY, SERVICE_SET_HUMIDITY from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -319,7 +319,7 @@ class HueOneLightStateView(HomeAssistantView): if not _remote_is_allowed(request.remote): return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) - hass: core.HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] hass_entity_id = self.config.number_to_entity_id(entity_id) if hass_entity_id is None: @@ -362,7 +362,7 @@ class HueOneLightChangeView(HomeAssistantView): return self.json_message("Only local IPs allowed", HTTPStatus.UNAUTHORIZED) config = self.config - hass: core.HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] entity_id = config.number_to_entity_id(entity_number) if entity_id is None: @@ -885,7 +885,7 @@ def create_config_model(config: Config, request: web.Request) -> dict[str, Any]: def create_list_of_entities(config: Config, request: web.Request) -> dict[str, Any]: """Create a list of all entities.""" - hass: core.HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] return { config.entity_id_to_number(entity_id): state_to_json(config, state) for entity_id in config.get_exposed_entity_ids() diff --git a/homeassistant/components/file_upload/__init__.py b/homeassistant/components/file_upload/__init__.py index 3a76bc8ea5e..85dacb388c0 100644 --- a/homeassistant/components/file_upload/__init__.py +++ b/homeassistant/components/file_upload/__init__.py @@ -13,7 +13,7 @@ import tempfile from aiohttp import BodyPartReader, web import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback @@ -145,7 +145,7 @@ class FileUploadView(HomeAssistantView): except ValueError as err: raise web.HTTPBadRequest from err - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] file_id = ulid_hex() if DOMAIN not in hass.data: @@ -199,7 +199,7 @@ class FileUploadView(HomeAssistantView): @RequestDataValidator({vol.Required("file_id"): str}) async def delete(self, request: web.Request, data: dict[str, str]) -> web.Response: """Delete a file.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if DOMAIN not in hass.data: raise web.HTTPNotFound() diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index fcf1434c2c2..6511dbf4478 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.core import HomeAssistant, ServiceCall import homeassistant.helpers.config_validation as cv @@ -105,4 +105,4 @@ class FoursquarePushReceiver(HomeAssistantView): ) return self.json_message("Incorrect secret", HTTPStatus.BAD_REQUEST) - request.app["hass"].bus.async_fire(EVENT_PUSH, data) + request.app[KEY_HASS].bus.async_fire(EVENT_PUSH, data) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 48d5bcb0b05..eff69e9d6f0 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -14,7 +14,7 @@ import voluptuous as vol from yarl import URL from homeassistant.components import onboarding, websocket_api -from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.config import async_hass_config_yaml from homeassistant.const import ( @@ -595,7 +595,7 @@ class IndexView(web_urldispatcher.AbstractResource): async def get(self, request: web.Request) -> web.Response: """Serve the index page for panel pages.""" - hass: HomeAssistant = request.app["hass"] + hass = request.app[KEY_HASS] if not onboarding.async_is_onboarded(hass): return web.Response(status=302, headers={"location": "/onboarding.html"}) diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 0d75a1bede7..20e9c3bf842 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -12,7 +12,7 @@ from aiohttp.web import Request, Response import jwt from homeassistant.components import webhook -from homeassistant.components.http import HomeAssistantView +from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -380,7 +380,7 @@ class GoogleAssistantView(HomeAssistantView): """Handle Google Assistant requests.""" message: dict = await request.json() result = await async_handle_message( - request.app["hass"], + request.app[KEY_HASS], self.config, request["hass_user"].id, request["hass_user"].id, diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index 162bc43c6c0..9de287ea369 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -6,6 +6,7 @@ import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components.ambiclimate import config_flow +from homeassistant.components.http import KEY_HASS from homeassistant.config import async_process_ha_core_config from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import HomeAssistant @@ -127,11 +128,11 @@ async def test_view(hass: HomeAssistant) -> None: request = aiohttp.MockRequest( b"", query_string="code=test_code", mock_source="test" ) - request.app = {"hass": hass} + request.app = {KEY_HASS: hass} view = config_flow.AmbiclimateAuthCallbackView() assert await view.get(request) == "OK!" request = aiohttp.MockRequest(b"", query_string="", mock_source="test") - request.app = {"hass": hass} + request.app = {KEY_HASS: hass} view = config_flow.AmbiclimateAuthCallbackView() assert await view.get(request) == "No code" From c1d45f63f569e6d55ee655085b868ad8c256449b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 7 Mar 2024 20:15:52 +0100 Subject: [PATCH 0518/1691] Fix Bang and olufsen naming (#111344) * Fix Bang and olufsen naming * Fix * Fix tests * Fix tests --- .../components/bang_olufsen/entity.py | 5 +-- .../components/bang_olufsen/media_player.py | 8 ++-- .../components/bang_olufsen/websocket.py | 4 +- tests/components/bang_olufsen/conftest.py | 40 ++++++------------- .../bang_olufsen/test_config_flow.py | 31 +++++++------- 5 files changed, 34 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/bang_olufsen/entity.py b/homeassistant/components/bang_olufsen/entity.py index 76d93ca0635..bf6966f83bf 100644 --- a/homeassistant/components/bang_olufsen/entity.py +++ b/homeassistant/components/bang_olufsen/entity.py @@ -36,7 +36,6 @@ class BangOlufsenBase: # Set the configuration variables. self._host: str = self.entry.data[CONF_HOST] - self._name: str = self.entry.title self._unique_id: str = cast(str, self.entry.unique_id) # Objects that get directly updated by notifications. @@ -54,15 +53,13 @@ class BangOlufsenEntity(Entity, BangOlufsenBase): """Base Entity for BangOlufsen entities.""" _attr_has_entity_name = True + _attr_should_poll = False def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: """Initialize the object.""" super().__init__(entry, client) self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, self._unique_id)}) - self._attr_device_class = None - self._attr_entity_category = None - self._attr_should_poll = False async def _update_connection_state(self, connection_state: bool) -> None: """Update entity connection state.""" diff --git a/homeassistant/components/bang_olufsen/media_player.py b/homeassistant/components/bang_olufsen/media_player.py index eaafddcabd6..3209c676af7 100644 --- a/homeassistant/components/bang_olufsen/media_player.py +++ b/homeassistant/components/bang_olufsen/media_player.py @@ -94,11 +94,12 @@ async def async_setup_entry( async_add_entities(new_entities=[BangOlufsenMediaPlayer(config_entry, data.client)]) -class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity): +class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): """Representation of a media player.""" - _attr_has_entity_name = False _attr_icon = "mdi:speaker-wireless" + _attr_name = None + _attr_device_class = MediaPlayerDeviceClass.SPEAKER _attr_supported_features = BANG_OLUFSEN_FEATURES def __init__(self, entry: ConfigEntry, client: MozartClient) -> None: @@ -113,12 +114,9 @@ class BangOlufsenMediaPlayer(MediaPlayerEntity, BangOlufsenEntity): identifiers={(DOMAIN, self._unique_id)}, manufacturer="Bang & Olufsen", model=self._model, - name=cast(str, self.name), serial_number=self._unique_id, ) - self._attr_name = self._name self._attr_unique_id = self._unique_id - self._attr_device_class = MediaPlayerDeviceClass.SPEAKER # Misc. variables. self._audio_sources: dict[str, str] = {} diff --git a/homeassistant/components/bang_olufsen/websocket.py b/homeassistant/components/bang_olufsen/websocket.py index fd378a40bd3..81de5596e31 100644 --- a/homeassistant/components/bang_olufsen/websocket.py +++ b/homeassistant/components/bang_olufsen/websocket.py @@ -80,12 +80,12 @@ class BangOlufsenWebsocket(BangOlufsenBase): def on_connection(self) -> None: """Handle WebSocket connection made.""" - _LOGGER.debug("Connected to the %s notification channel", self._name) + _LOGGER.debug("Connected to the %s notification channel", self.entry.title) self._update_connection_status() def on_connection_lost(self) -> None: """Handle WebSocket connection lost.""" - _LOGGER.error("Lost connection to the %s", self._name) + _LOGGER.error("Lost connection to the %s", self.entry.title) self._update_connection_status() def on_notification_notification( diff --git a/tests/components/bang_olufsen/conftest.py b/tests/components/bang_olufsen/conftest.py index 8c212ef16be..e714f55de4a 100644 --- a/tests/components/bang_olufsen/conftest.py +++ b/tests/components/bang_olufsen/conftest.py @@ -1,5 +1,5 @@ """Test fixtures for bang_olufsen.""" - +from collections.abc import Generator from unittest.mock import AsyncMock, patch from mozart_api.models import BeolinkPeer @@ -18,25 +18,6 @@ from .const import ( from tests.common import MockConfigEntry -class MockMozartClient: - """Class for mocking MozartClient objects and methods.""" - - async def __aenter__(self): - """Mock async context entry.""" - - async def __aexit__(self, exc_type, exc, tb): - """Mock async context exit.""" - - # API call results - get_beolink_self_result = BeolinkPeer( - friendly_name=TEST_FRIENDLY_NAME, jid=TEST_JID_1 - ) - - # API endpoints - get_beolink_self = AsyncMock() - get_beolink_self.return_value = get_beolink_self_result - - @pytest.fixture def mock_config_entry(): """Mock config entry.""" @@ -49,17 +30,22 @@ def mock_config_entry(): @pytest.fixture -def mock_client(): +def mock_mozart_client() -> Generator[AsyncMock, None, None]: """Mock MozartClient.""" - client = MockMozartClient() - - with patch("mozart_api.mozart_client.MozartClient", return_value=client): + with patch( + "homeassistant.components.bang_olufsen.MozartClient", autospec=True + ) as mock_client, patch( + "homeassistant.components.bang_olufsen.config_flow.MozartClient", + new=mock_client, + ): + client = mock_client.return_value + client.get_beolink_self = AsyncMock() + client.get_beolink_self.return_value = BeolinkPeer( + friendly_name=TEST_FRIENDLY_NAME, jid=TEST_JID_1 + ) yield client - # Reset mocked API call counts and side effects - client.get_beolink_self.reset_mock(side_effect=True) - @pytest.fixture def mock_setup_entry(): diff --git a/tests/components/bang_olufsen/test_config_flow.py b/tests/components/bang_olufsen/test_config_flow.py index dd42c4c5c8c..793ef0c2c9b 100644 --- a/tests/components/bang_olufsen/test_config_flow.py +++ b/tests/components/bang_olufsen/test_config_flow.py @@ -13,7 +13,6 @@ from homeassistant.const import CONF_SOURCE from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from .conftest import MockMozartClient from .const import ( TEST_DATA_CREATE_ENTRY, TEST_DATA_USER, @@ -27,10 +26,10 @@ pytestmark = pytest.mark.usefixtures("mock_setup_entry") async def test_config_flow_timeout_error( - hass: HomeAssistant, mock_client: MockMozartClient + hass: HomeAssistant, mock_mozart_client ) -> None: """Test we handle timeout_error.""" - mock_client.get_beolink_self.side_effect = TimeoutError() + mock_mozart_client.get_beolink_self.side_effect = TimeoutError() result_user = await hass.config_entries.flow.async_init( handler=DOMAIN, @@ -40,14 +39,16 @@ async def test_config_flow_timeout_error( assert result_user["type"] == FlowResultType.FORM assert result_user["errors"] == {"base": "timeout_error"} - assert mock_client.get_beolink_self.call_count == 1 + assert mock_mozart_client.get_beolink_self.call_count == 1 async def test_config_flow_client_connector_error( - hass: HomeAssistant, mock_client: MockMozartClient + hass: HomeAssistant, mock_mozart_client ) -> None: """Test we handle client_connector_error.""" - mock_client.get_beolink_self.side_effect = ClientConnectorError(Mock(), Mock()) + mock_mozart_client.get_beolink_self.side_effect = ClientConnectorError( + Mock(), Mock() + ) result_user = await hass.config_entries.flow.async_init( handler=DOMAIN, @@ -57,7 +58,7 @@ async def test_config_flow_client_connector_error( assert result_user["type"] == FlowResultType.FORM assert result_user["errors"] == {"base": "client_connector_error"} - assert mock_client.get_beolink_self.call_count == 1 + assert mock_mozart_client.get_beolink_self.call_count == 1 async def test_config_flow_invalid_ip(hass: HomeAssistant) -> None: @@ -73,10 +74,10 @@ async def test_config_flow_invalid_ip(hass: HomeAssistant) -> None: async def test_config_flow_api_exception( - hass: HomeAssistant, mock_client: MockMozartClient + hass: HomeAssistant, mock_mozart_client ) -> None: """Test we handle api_exception.""" - mock_client.get_beolink_self.side_effect = ApiException() + mock_mozart_client.get_beolink_self.side_effect = ApiException() result_user = await hass.config_entries.flow.async_init( handler=DOMAIN, @@ -86,10 +87,10 @@ async def test_config_flow_api_exception( assert result_user["type"] == FlowResultType.FORM assert result_user["errors"] == {"base": "api_exception"} - assert mock_client.get_beolink_self.call_count == 1 + assert mock_mozart_client.get_beolink_self.call_count == 1 -async def test_config_flow(hass: HomeAssistant, mock_client: MockMozartClient) -> None: +async def test_config_flow(hass: HomeAssistant, mock_mozart_client) -> None: """Test config flow.""" result_init = await hass.config_entries.flow.async_init( @@ -109,12 +110,10 @@ async def test_config_flow(hass: HomeAssistant, mock_client: MockMozartClient) - assert result_user["type"] == FlowResultType.CREATE_ENTRY assert result_user["data"] == TEST_DATA_CREATE_ENTRY - assert mock_client.get_beolink_self.call_count == 1 + assert mock_mozart_client.get_beolink_self.call_count == 1 -async def test_config_flow_zeroconf( - hass: HomeAssistant, mock_client: MockMozartClient -) -> None: +async def test_config_flow_zeroconf(hass: HomeAssistant, mock_mozart_client) -> None: """Test zeroconf discovery.""" result_zeroconf = await hass.config_entries.flow.async_init( @@ -134,7 +133,7 @@ async def test_config_flow_zeroconf( assert result_confirm["type"] == FlowResultType.CREATE_ENTRY assert result_confirm["data"] == TEST_DATA_CREATE_ENTRY - assert mock_client.get_beolink_self.call_count == 0 + assert mock_mozart_client.get_beolink_self.call_count == 0 async def test_config_flow_zeroconf_not_mozart_device(hass: HomeAssistant) -> None: From 49d20eedd4572840ba364258dc54d64b13d39c9a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 7 Mar 2024 20:37:35 +0100 Subject: [PATCH 0519/1691] Remove local imports of hass-nabucasa (#112634) --- homeassistant/components/http/forwarded.py | 22 ++-------------------- homeassistant/helpers/network.py | 3 +-- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/http/forwarded.py b/homeassistant/components/http/forwarded.py index 5d122436af2..c5698ebe919 100644 --- a/homeassistant/components/http/forwarded.py +++ b/homeassistant/components/http/forwarded.py @@ -4,11 +4,10 @@ from __future__ import annotations from collections.abc import Awaitable, Callable from ipaddress import IPv4Network, IPv6Network, ip_address import logging -from types import ModuleType -from typing import Literal from aiohttp.hdrs import X_FORWARDED_FOR, X_FORWARDED_HOST, X_FORWARDED_PROTO from aiohttp.web import Application, HTTPBadRequest, Request, StreamResponse, middleware +from hass_nabucasa import remote from homeassistant.core import callback @@ -67,30 +66,13 @@ def async_setup_forwarded( an HTTP 400 status code is thrown. """ - remote: Literal[False] | None | ModuleType = None - @middleware async def forwarded_middleware( request: Request, handler: Callable[[Request], Awaitable[StreamResponse]] ) -> StreamResponse: """Process forwarded data by a reverse proxy.""" - nonlocal remote - - if remote is None: - # Initialize remote method - try: - from hass_nabucasa import ( # pylint: disable=import-outside-toplevel - remote, - ) - - # venv users might have an old version installed if they don't have cloud around anymore - if not hasattr(remote, "is_cloud_request"): - remote = False - except ImportError: - remote = False - # Skip requests from Remote UI - if remote and remote.is_cloud_request.get(): + if remote.is_cloud_request.get(): return await handler(request) # Handle X-Forwarded-For diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index df95c0834bc..46d71ccb390 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -6,6 +6,7 @@ from contextlib import suppress from ipaddress import ip_address from typing import cast +from hass_nabucasa import remote import yarl from homeassistant.components import http @@ -314,6 +315,4 @@ def is_cloud_connection(hass: HomeAssistant) -> bool: if "cloud" not in hass.config.components: return False - from hass_nabucasa import remote # pylint: disable=import-outside-toplevel - return remote.is_cloud_request.get() From 27af6f146613e741973e01c7cbffe48e89fe8f6e Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 7 Mar 2024 22:38:51 +0100 Subject: [PATCH 0520/1691] Bump pymodbus to v3.6.5 (#112629) --- homeassistant/components/modbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index b90f5663643..6b072457144 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -6,5 +6,5 @@ "iot_class": "local_polling", "loggers": ["pymodbus"], "quality_scale": "gold", - "requirements": ["pymodbus==3.6.4"] + "requirements": ["pymodbus==3.6.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8fab01f80da..fda096426e0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1968,7 +1968,7 @@ pymitv==1.4.3 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.6.4 +pymodbus==3.6.5 # homeassistant.components.monoprice pymonoprice==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b3235cd3d4f..f4a4057ebbe 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1525,7 +1525,7 @@ pymeteoclimatic==0.1.0 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.6.4 +pymodbus==3.6.5 # homeassistant.components.monoprice pymonoprice==0.4 From 5da629b3e59bca2cf7ca1236ffe82c49e89b85a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 13:18:18 -1000 Subject: [PATCH 0521/1691] Log waiting tasks in bootstrap that are delaying startup (#112637) --- homeassistant/bootstrap.py | 20 +++++++-- homeassistant/core.py | 12 ++++++ tests/test_bootstrap.py | 85 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 446fe73f926..28eb45e5273 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -606,7 +606,10 @@ class _WatchPendingSetups: domain: (now - start_time) for domain, start_time in self._setup_started.items() } - _LOGGER.debug("Integration remaining: %s", remaining_with_setup_started) + if remaining_with_setup_started: + _LOGGER.debug("Integration remaining: %s", remaining_with_setup_started) + elif waiting_tasks := self._hass._active_tasks: # pylint: disable=protected-access + _LOGGER.debug("Waiting on tasks: %s", waiting_tasks) self._async_dispatch(remaining_with_setup_started) if ( self._setup_started @@ -849,7 +852,10 @@ async def _async_set_up_integrations( ): await async_setup_multi_components(hass, stage_1_domains, config) except TimeoutError: - _LOGGER.warning("Setup timed out for stage 1 - moving forward") + _LOGGER.warning( + "Setup timed out for stage 1 waiting on %s - moving forward", + hass._active_tasks, # pylint: disable=protected-access + ) # Add after dependencies when setting up stage 2 domains async_set_domains_to_be_loaded(hass, stage_2_domains) @@ -862,7 +868,10 @@ async def _async_set_up_integrations( ): await async_setup_multi_components(hass, stage_2_domains, config) except TimeoutError: - _LOGGER.warning("Setup timed out for stage 2 - moving forward") + _LOGGER.warning( + "Setup timed out for stage 2 waiting on %s - moving forward", + hass._active_tasks, # pylint: disable=protected-access + ) # Wrap up startup _LOGGER.debug("Waiting for startup to wrap up") @@ -870,7 +879,10 @@ async def _async_set_up_integrations( async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME): await hass.async_block_till_done() except TimeoutError: - _LOGGER.warning("Setup timed out for bootstrap - moving forward") + _LOGGER.warning( + "Setup timed out for bootstrap waiting on %s - moving forward", + hass._active_tasks, # pylint: disable=protected-access + ) watcher.async_stop() diff --git a/homeassistant/core.py b/homeassistant/core.py index f0b10760228..3a60e6f1170 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -402,6 +402,18 @@ class HomeAssistant: max_workers=1, thread_name_prefix="ImportExecutor" ) + @property + def _active_tasks(self) -> set[asyncio.Future[Any]]: + """Return all active tasks. + + This property is used in bootstrap to log all active tasks + so we can identify what is blocking startup. + + This property is marked as private to avoid accidental use + as it is not guaranteed to be present in future versions. + """ + return self._tasks + @cached_property def is_running(self) -> bool: """Return if Home Assistant is running.""" diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 550eb4bffee..83722566590 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -844,10 +844,10 @@ async def test_warning_logged_on_wrap_up_timeout( def gen_domain_setup(domain): async def async_setup(hass, config): - async def _background_task(): + async def _not_marked_background_task(): await asyncio.sleep(0.2) - hass.async_create_task(_background_task()) + hass.async_create_task(_not_marked_background_task()) return True return async_setup @@ -865,7 +865,86 @@ async def test_warning_logged_on_wrap_up_timeout( await bootstrap._async_set_up_integrations(hass, {"normal_integration": {}}) await hass.async_block_till_done() - assert "Setup timed out for bootstrap - moving forward" in caplog.text + assert "Setup timed out for bootstrap" in caplog.text + assert "waiting on" in caplog.text + assert "_not_marked_background_task" in caplog.text + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_tasks_logged_that_block_stage_1( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test we log tasks that delay stage 1 startup.""" + + def gen_domain_setup(domain): + async def async_setup(hass, config): + async def _not_marked_background_task(): + await asyncio.sleep(0.2) + + hass.async_create_task(_not_marked_background_task()) + await asyncio.sleep(0.1) + return True + + return async_setup + + mock_integration( + hass, + MockModule( + domain="normal_integration", + async_setup=gen_domain_setup("normal_integration"), + partial_manifest={}, + ), + ) + + original_stage_1 = bootstrap.STAGE_1_INTEGRATIONS + with patch.object(bootstrap, "STAGE_1_TIMEOUT", 0), patch.object( + bootstrap, "COOLDOWN_TIME", 0 + ), patch.object( + bootstrap, "STAGE_1_INTEGRATIONS", [*original_stage_1, "normal_integration"] + ): + await bootstrap._async_set_up_integrations(hass, {"normal_integration": {}}) + await hass.async_block_till_done() + + assert "Setup timed out for stage 1 waiting on" in caplog.text + assert "waiting on" in caplog.text + assert "_not_marked_background_task" in caplog.text + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_tasks_logged_that_block_stage_2( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test we log tasks that delay stage 2 startup.""" + + def gen_domain_setup(domain): + async def async_setup(hass, config): + async def _not_marked_background_task(): + await asyncio.sleep(0.2) + + hass.async_create_task(_not_marked_background_task()) + await asyncio.sleep(0.1) + return True + + return async_setup + + mock_integration( + hass, + MockModule( + domain="normal_integration", + async_setup=gen_domain_setup("normal_integration"), + partial_manifest={}, + ), + ) + + with patch.object(bootstrap, "STAGE_2_TIMEOUT", 0), patch.object( + bootstrap, "COOLDOWN_TIME", 0 + ): + await bootstrap._async_set_up_integrations(hass, {"normal_integration": {}}) + await hass.async_block_till_done() + + assert "Setup timed out for stage 2 waiting on" in caplog.text + assert "waiting on" in caplog.text + assert "_not_marked_background_task" in caplog.text @pytest.mark.parametrize("load_registries", [False]) From a6b17dbe688cd1f08798a1ee3ea6a4e45303d869 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 18:32:26 -1000 Subject: [PATCH 0522/1691] Schedule polling as periodic tasks (#112640) * Schedule periodic coordinator updates as background tasks. Currently, the coordinator's periodic refreshes delay startup because they are not scheduled as background tasks. We will wait if the startup takes long enough for the first planned refresh. Another coordinator's scheduled refresh will be fired on busy systems, further delaying the startup. This chain of events results in the startup taking a long time and hitting the safety timeout because too many coordinators are refreshing. This case can also happen with scheduled entity refreshes, but it's less common. A future PR will address that case. * periodic_tasks * periodic_tasks * periodic_tasks * merge * merge * merge * merge * merge * fix test that call the sync api from async * one more place * cannot chain * async_run_periodic_hass_job * sun and pattern time changes from automations also block startup * Revert "sun and pattern time changes from automations also block startup" This reverts commit 6de2defa0586165c2918ce006cf57aa3acaae730. * make sure polling is cancelled when config entry is unloaded * Revert "Revert "sun and pattern time changes from automations also block startup"" This reverts commit e8f12aad5579a1e5e3930957a46ea6f3dd9beecf. * remove DisabledError from homewizard test as it relies on a race * fix race * direct coverage --- homeassistant/bootstrap.py | 2 +- homeassistant/config_entries.py | 45 +++++++- homeassistant/core.py | 113 ++++++++++++++++++-- homeassistant/helpers/entity_platform.py | 14 ++- homeassistant/helpers/event.py | 6 +- homeassistant/helpers/update_coordinator.py | 14 ++- tests/components/hassio/test_sensor.py | 58 ++++++---- tests/components/homewizard/test_sensor.py | 4 +- tests/components/homewizard/test_switch.py | 2 +- tests/helpers/test_entity_platform.py | 2 +- tests/test_config_entries.py | 15 ++- tests/test_core.py | 68 +++++++++++- 12 files changed, 292 insertions(+), 51 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 28eb45e5273..3d4e79c1ca2 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -877,7 +877,7 @@ async def _async_set_up_integrations( _LOGGER.debug("Waiting for startup to wrap up") try: async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME): - await hass.async_block_till_done() + await hass.async_block_till_done(wait_periodic_tasks=False) except TimeoutError: _LOGGER.warning( "Setup timed out for bootstrap waiting on %s - moving forward", diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d9023e5e11a..6976d6d36a8 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -17,6 +17,7 @@ from contextvars import ContextVar from copy import deepcopy from enum import Enum, StrEnum import functools +from itertools import chain import logging from random import randint from types import MappingProxyType @@ -377,6 +378,7 @@ class ConfigEntry: self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() + self._periodic_tasks: set[asyncio.Future[Any]] = set() self._integration_for_domain: loader.Integration | None = None self._tries = 0 @@ -854,15 +856,15 @@ class ConfigEntry: if job := self._on_unload.pop()(): self.async_create_task(hass, job) - if not self._tasks and not self._background_tasks: + if not self._tasks and not self._background_tasks and not self._periodic_tasks: return cancel_message = f"Config entry {self.title} with {self.domain} unloading" - for task in self._background_tasks: + for task in chain(self._background_tasks, self._periodic_tasks): task.cancel(cancel_message) _, pending = await asyncio.wait( - [*self._tasks, *self._background_tasks], timeout=10 + [*self._tasks, *self._background_tasks, *self._periodic_tasks], timeout=10 ) for task in pending: @@ -1026,7 +1028,13 @@ class ConfigEntry: Background tasks are automatically canceled when config entry is unloaded. - target: target to call. + A background task is different from a normal task: + + - Will not block startup + - Will be automatically cancelled on shutdown + - Calls to async_block_till_done will not wait for completion + + This method must be run in the event loop. """ task = hass.async_create_background_task(target, name, eager_start) if task.done(): @@ -1035,6 +1043,35 @@ class ConfigEntry: task.add_done_callback(self._background_tasks.remove) return task + @callback + def async_create_periodic_task( + self, + hass: HomeAssistant, + target: Coroutine[Any, Any, _R], + name: str, + eager_start: bool = False, + ) -> asyncio.Task[_R]: + """Create a periodic task tied to the config entry lifecycle. + + Periodic tasks are automatically canceled when config entry is unloaded. + + This type of task is typically used for polling. + + A periodic task is different from a normal task: + + - Will not block startup + - Will be automatically cancelled on shutdown + - Calls to async_block_till_done will wait for completion by default + + This method must be run in the event loop. + """ + task = hass.async_create_periodic_task(target, name, eager_start) + if task.done(): + return task + self._periodic_tasks.add(task) + task.add_done_callback(self._periodic_tasks.remove) + return task + current_entry: ContextVar[ConfigEntry | None] = ContextVar( "current_entry", default=None diff --git a/homeassistant/core.py b/homeassistant/core.py index 3a60e6f1170..b78be4ff3ad 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -382,6 +382,7 @@ class HomeAssistant: self.loop = asyncio.get_running_loop() self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() + self._periodic_tasks: set[asyncio.Future[Any]] = set() self.bus = EventBus(self) self.services = ServiceRegistry(self) self.states = StateMachine(self.bus, self.loop) @@ -640,6 +641,56 @@ class HomeAssistant: return task + @overload + @callback + def async_run_periodic_hass_job( + self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any + ) -> asyncio.Future[_R] | None: + ... + + @overload + @callback + def async_run_periodic_hass_job( + self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any + ) -> asyncio.Future[_R] | None: + ... + + @callback + def async_run_periodic_hass_job( + self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any + ) -> asyncio.Future[_R] | None: + """Add a periodic HassJob from within the event loop. + + This method must be run in the event loop. + hassjob: HassJob to call. + args: parameters for method to call. + """ + task: asyncio.Future[_R] + # This code path is performance sensitive and uses + # if TYPE_CHECKING to avoid the overhead of constructing + # the type used for the cast. For history see: + # https://github.com/home-assistant/core/pull/71960 + if hassjob.job_type is HassJobType.Coroutinefunction: + if TYPE_CHECKING: + hassjob.target = cast( + Callable[..., Coroutine[Any, Any, _R]], hassjob.target + ) + task = create_eager_task(hassjob.target(*args), name=hassjob.name) + elif hassjob.job_type is HassJobType.Callback: + if TYPE_CHECKING: + hassjob.target = cast(Callable[..., _R], hassjob.target) + hassjob.target(*args) + return None + else: + if TYPE_CHECKING: + hassjob.target = cast(Callable[..., _R], hassjob.target) + task = self.loop.run_in_executor(None, hassjob.target, *args) + + self._periodic_tasks.add(task) + task.add_done_callback(self._periodic_tasks.remove) + + return task + def create_task( self, target: Coroutine[Any, Any, Any], name: str | None = None ) -> None: @@ -681,9 +732,17 @@ class HomeAssistant: ) -> asyncio.Task[_R]: """Create a task from within the event loop. - This is a background task which will not block startup and will be - automatically cancelled on shutdown. If you are using this in your - integration, use the create task methods on the config entry instead. + This type of task is for background tasks that usually run for + the lifetime of Home Assistant or an integration's setup. + + A background task is different from a normal task: + + - Will not block startup + - Will be automatically cancelled on shutdown + - Calls to async_block_till_done will not wait for completion + + If you are using this in your integration, use the create task + methods on the config entry instead. This method must be run in the event loop. """ @@ -699,6 +758,37 @@ class HomeAssistant: task.add_done_callback(self._background_tasks.remove) return task + @callback + def async_create_periodic_task( + self, target: Coroutine[Any, Any, _R], name: str, eager_start: bool = False + ) -> asyncio.Task[_R]: + """Create a task from within the event loop. + + This type of task is typically used for polling. + + A periodic task is different from a normal task: + + - Will not block startup + - Will be automatically cancelled on shutdown + - Calls to async_block_till_done will wait for completion by default + + If you are using this in your integration, use the create task + methods on the config entry instead. + + This method must be run in the event loop. + """ + if eager_start: + task = create_eager_task(target, name=name, loop=self.loop) + if task.done(): + return task + else: + # Use loop.create_task + # to avoid the extra function call in asyncio.create_task. + task = self.loop.create_task(target, name=name) + self._periodic_tasks.add(task) + task.add_done_callback(self._periodic_tasks.remove) + return task + @callback def async_add_executor_job( self, target: Callable[..., _T], *args: Any @@ -808,16 +898,19 @@ class HomeAssistant: self.async_block_till_done(), self.loop ).result() - async def async_block_till_done(self) -> None: + async def async_block_till_done(self, wait_periodic_tasks: bool = True) -> None: """Block until all pending work is done.""" # To flush out any call_soon_threadsafe await asyncio.sleep(0) start_time: float | None = None current_task = asyncio.current_task() - while tasks := [ task - for task in self._tasks + for task in ( + self._tasks | self._periodic_tasks + if wait_periodic_tasks + else self._tasks + ) if task is not current_task and not cancelling(task) ]: await self._await_and_log_pending(tasks) @@ -948,7 +1041,7 @@ class HomeAssistant: self._tasks = set() # Cancel all background tasks - for task in self._background_tasks: + for task in self._background_tasks | self._periodic_tasks: self._tasks.add(task) task.add_done_callback(self._tasks.remove) task.cancel("Home Assistant is stopping") @@ -960,7 +1053,7 @@ class HomeAssistant: self.bus.async_fire(EVENT_HOMEASSISTANT_STOP) try: async with self.timeout.async_timeout(STOP_STAGE_SHUTDOWN_TIMEOUT): - await self.async_block_till_done() + await self.async_block_till_done(wait_periodic_tasks=False) except TimeoutError: _LOGGER.warning( "Timed out waiting for integrations to stop, the shutdown will" @@ -973,7 +1066,7 @@ class HomeAssistant: self.bus.async_fire(EVENT_HOMEASSISTANT_FINAL_WRITE) try: async with self.timeout.async_timeout(FINAL_WRITE_STAGE_SHUTDOWN_TIMEOUT): - await self.async_block_till_done() + await self.async_block_till_done(wait_periodic_tasks=False) except TimeoutError: _LOGGER.warning( "Timed out waiting for final writes to complete, the shutdown will" @@ -1025,7 +1118,7 @@ class HomeAssistant: try: async with self.timeout.async_timeout(CLOSE_STAGE_SHUTDOWN_TIMEOUT): - await self.async_block_till_done() + await self.async_block_till_done(wait_periodic_tasks=False) except TimeoutError: _LOGGER.warning( "Timed out waiting for close event to be processed, the shutdown will" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 3a441e75e84..e9258f8d1c7 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -641,7 +641,19 @@ class EntityPlatform: @callback def _async_handle_interval_callback(self, now: datetime) -> None: """Update all the entity states in a single platform.""" - self.hass.async_create_task(self._update_entity_states(now), eager_start=True) + if self.config_entry: + self.config_entry.async_create_periodic_task( + self.hass, + self._update_entity_states(now), + name=f"EntityPlatform poll {self.domain}.{self.platform_name}", + eager_start=True, + ) + else: + self.hass.async_create_periodic_task( + self._update_entity_states(now), + name=f"EntityPlatform poll {self.domain}.{self.platform_name}", + eager_start=True, + ) def _entity_id_already_exists(self, entity_id: str) -> tuple[bool, bool]: """Check if an entity_id already exists. diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 3c0aa4b9e34..0233eea37d6 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1609,7 +1609,7 @@ class _TrackTimeInterval: self._track_job, hass.loop.time() + self.seconds, ) - hass.async_run_hass_job(self._run_job, now) + hass.async_run_periodic_hass_job(self._run_job, now) @callback def async_cancel(self) -> None: @@ -1694,7 +1694,7 @@ class SunListener: """Handle solar event.""" self._unsub_sun = None self._listen_next_sun_event() - self.hass.async_run_hass_job(self.job) + self.hass.async_run_periodic_hass_job(self.job) @callback def _handle_config_event(self, _event: Any) -> None: @@ -1780,7 +1780,7 @@ class _TrackUTCTimeChange: # time when the timer was scheduled utc_now = time_tracker_utcnow() localized_now = dt_util.as_local(utc_now) if self.local else utc_now - hass.async_run_hass_job(self.job, localized_now) + hass.async_run_periodic_hass_job(self.job, localized_now) if TYPE_CHECKING: assert self._pattern_time_change_listener_job is not None self._cancel_callback = async_track_point_in_utc_time( diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 018ba1d13e4..5fe2071e853 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -253,7 +253,19 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): @callback def __wrap_handle_refresh_interval(self) -> None: """Handle a refresh interval occurrence.""" - self.hass.async_create_task(self._handle_refresh_interval(), eager_start=True) + if self.config_entry: + self.config_entry.async_create_periodic_task( + self.hass, + self._handle_refresh_interval(), + name=f"{self.name} - {self.config_entry.title} - refresh", + eager_start=True, + ) + else: + self.hass.async_create_periodic_task( + self._handle_refresh_interval(), + name=f"{self.name} - refresh", + eager_start=True, + ) async def _handle_refresh_interval(self, _now: datetime | None = None) -> None: """Handle a refresh interval occurrence.""" diff --git a/tests/components/hassio/test_sensor.py b/tests/components/hassio/test_sensor.py index fbc6f08a1f5..9dbcb5d0e5d 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -3,14 +3,17 @@ from datetime import timedelta import os from unittest.mock import patch +from freezegun.api import FrozenDateTimeFactory import pytest +from homeassistant import config_entries from homeassistant.components.hassio import ( DOMAIN, HASSIO_UPDATE_INTERVAL, HassioAPIError, ) from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -264,6 +267,7 @@ async def test_sensor( ("sensor.test_memory_percent", "4.59"), ], ) +@patch.dict(os.environ, MOCK_ENVIRON) async def test_stats_addon_sensor( hass: HomeAssistant, entity_id, @@ -271,18 +275,17 @@ async def test_stats_addon_sensor( aioclient_mock: AiohttpClientMocker, entity_registry: er.EntityRegistry, caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, ) -> None: """Test stats addons sensor.""" config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) - with patch.dict(os.environ, MOCK_ENVIRON): - result = await async_setup_component( - hass, - "hassio", - {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, - ) - assert result + assert await async_setup_component( + hass, + "hassio", + {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}}, + ) await hass.async_block_till_done() # Verify that the entity is disabled by default. @@ -292,9 +295,8 @@ async def test_stats_addon_sensor( _install_default_mocks(aioclient_mock) _install_test_addon_stats_failure_mock(aioclient_mock) - async_fire_time_changed( - hass, dt_util.utcnow() + HASSIO_UPDATE_INTERVAL + timedelta(seconds=1) - ) + freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) + async_fire_time_changed(hass) await hass.async_block_till_done() assert "Could not fetch stats" not in caplog.text @@ -303,22 +305,31 @@ async def test_stats_addon_sensor( _install_default_mocks(aioclient_mock) _install_test_addon_stats_mock(aioclient_mock) - async_fire_time_changed( - hass, dt_util.utcnow() + HASSIO_UPDATE_INTERVAL + timedelta(seconds=1) - ) + freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) + async_fire_time_changed(hass) await hass.async_block_till_done() - # Enable the entity. + assert "Could not fetch stats" not in caplog.text + + # Enable the entity and wait for the reload to complete. entity_registry.async_update_entity(entity_id, disabled_by=None) - await hass.config_entries.async_reload(config_entry.entry_id) + freezer.tick(config_entries.RELOAD_AFTER_UPDATE_DELAY) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert config_entry.state is config_entries.ConfigEntryState.LOADED + # Verify the entity is still enabled + assert entity_registry.async_get(entity_id).disabled_by is None + + # The config entry just reloaded, so we need to wait for the next update + freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) + async_fire_time_changed(hass) await hass.async_block_till_done() - # There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer - async_fire_time_changed( - hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY) - ) - await hass.async_block_till_done() + assert hass.states.get(entity_id) is not None + freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() # Verify that the entity have the expected state. state = hass.states.get(entity_id) assert state.state == expected @@ -327,9 +338,10 @@ async def test_stats_addon_sensor( _install_default_mocks(aioclient_mock) _install_test_addon_stats_failure_mock(aioclient_mock) - async_fire_time_changed( - hass, dt_util.utcnow() + HASSIO_UPDATE_INTERVAL + timedelta(seconds=1) - ) + freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) + async_fire_time_changed(hass) await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_UNAVAILABLE assert "Could not fetch stats" in caplog.text diff --git a/tests/components/homewizard/test_sensor.py b/tests/components/homewizard/test_sensor.py index 243e8f542e2..a7d018ea35f 100644 --- a/tests/components/homewizard/test_sensor.py +++ b/tests/components/homewizard/test_sensor.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock -from homewizard_energy.errors import DisabledError, RequestError +from homewizard_energy.errors import RequestError from homewizard_energy.models import Data import pytest from syrupy.assertion import SnapshotAssertion @@ -375,7 +375,7 @@ async def test_disabled_by_default_sensors( assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION -@pytest.mark.parametrize("exception", [RequestError, DisabledError]) +@pytest.mark.parametrize("exception", [RequestError]) async def test_sensors_unreachable( hass: HomeAssistant, mock_homewizardenergy: MagicMock, diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index bfc23264340..85c2bee709c 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -192,7 +192,7 @@ async def test_switch_entities( @pytest.mark.parametrize("device_fixture", ["HWE-SKT"]) -@pytest.mark.parametrize("exception", [RequestError, DisabledError, UnsupportedError]) +@pytest.mark.parametrize("exception", [RequestError, UnsupportedError]) @pytest.mark.parametrize( ("entity_id", "method"), [ diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 07ecd7844da..f6fc5888c1c 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -200,7 +200,7 @@ async def test_set_scan_interval_via_platform( component = EntityComponent(_LOGGER, DOMAIN, hass) - component.setup({DOMAIN: {"platform": "platform"}}) + await component.async_setup({DOMAIN: {"platform": "platform"}}) await hass.async_block_till_done() assert mock_track.called diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index d6c5d8bdc5c..089d0f6b21b 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -4329,10 +4329,23 @@ async def test_task_tracking(hass: HomeAssistant) -> None: entry.async_create_background_task( hass, test_task(), "background-task-name", eager_start=False ) + entry.async_create_periodic_task( + hass, test_task(), "periodic-task-name", eager_start=False + ) + entry.async_create_periodic_task( + hass, test_task(), "periodic-task-name", eager_start=True + ) await asyncio.sleep(0) hass.loop.call_soon(event.set) await entry._async_process_on_unload(hass) - assert results == ["on_unload", "background", "background", "normal"] + assert results == [ + "on_unload", + "background", + "background", + "background", + "background", + "normal", + ] async def test_preview_supported( diff --git a/tests/test_core.py b/tests/test_core.py index 75d06a7c61f..9960e8a1671 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -223,6 +223,47 @@ async def test_async_create_task_schedule_coroutine_with_name() -> None: assert "named task" in str(task) +async def test_async_run_periodic_hass_job_calls_callback() -> None: + """Test that the callback annotation is respected.""" + hass = MagicMock() + calls = [] + + def job(): + asyncio.get_running_loop() # ensure we are in the event loop + calls.append(1) + + ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(ha.callback(job))) + assert len(calls) == 1 + + +async def test_async_run_periodic_hass_job_calls_coro_function() -> None: + """Test running coros from async_run_periodic_hass_job.""" + hass = MagicMock() + calls = [] + + async def job(): + calls.append(1) + + await ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(job)) + assert len(calls) == 1 + + +async def test_async_run_periodic_hass_job_calls_executor_function() -> None: + """Test running in the executor from async_run_periodic_hass_job.""" + hass = MagicMock() + hass.loop = asyncio.get_running_loop() + calls = [] + + def job(): + try: + asyncio.get_running_loop() # ensure we are not in the event loop + except RuntimeError: + calls.append(1) + + await ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(job)) + assert len(calls) == 1 + + async def test_async_run_hass_job_calls_callback() -> None: """Test that the callback annotation is respected.""" hass = MagicMock() @@ -514,7 +555,7 @@ async def test_shutdown_calls_block_till_done_after_shutdown_run_callback_thread """Ensure shutdown_run_callback_threadsafe is called before the final async_block_till_done.""" stop_calls = [] - async def _record_block_till_done(): + async def _record_block_till_done(wait_periodic_tasks: bool = True): nonlocal stop_calls stop_calls.append("async_block_till_done") @@ -2098,9 +2139,9 @@ async def test_chained_logging_hits_log_timeout( return hass.async_create_task(_task_chain_1()) - with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0001): + with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0): hass.async_create_task(_task_chain_1()) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_periodic_tasks=False) assert "_task_chain_" in caplog.text @@ -2654,6 +2695,27 @@ async def test_background_task(hass: HomeAssistant, eager_start: bool) -> None: assert result.result() == ha.CoreState.stopping +@pytest.mark.parametrize("eager_start", (True, False)) +async def test_periodic_task(hass: HomeAssistant, eager_start: bool) -> None: + """Test periodic tasks being quit.""" + result = asyncio.Future() + + async def test_task(): + try: + await asyncio.sleep(1) + except asyncio.CancelledError: + result.set_result(hass.state) + raise + + task = hass.async_create_periodic_task( + test_task(), "happy task", eager_start=eager_start + ) + assert "happy task" in str(task) + await asyncio.sleep(0) + await hass.async_stop() + assert result.result() == ha.CoreState.stopping + + async def test_shutdown_does_not_block_on_normal_tasks( hass: HomeAssistant, ) -> None: From c101eb50017d0fdefe4f14114c9b69de972413c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 18:37:22 -1000 Subject: [PATCH 0523/1691] Fix homekit_controller delaying startup and shutdown (#112671) The alive poll was scheduled as a tracked task but should have been a background task so it was cancelled. --- .../components/homekit_controller/connection.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index ab5bbc10440..a3c68fffe64 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -339,8 +339,11 @@ class HKDevice: @callback def _async_schedule_update(self, now: datetime) -> None: """Schedule an update.""" - self.hass.async_create_task( - self._debounced_update.async_call(), eager_start=True + self.config_entry.async_create_background_task( + self.hass, + self._debounced_update.async_call(), + name=f"hkc {self.unique_id} alive poll", + eager_start=True, ) async def async_add_new_entities(self) -> None: @@ -692,8 +695,8 @@ class HKDevice: def process_config_changed(self, config_num: int) -> None: """Handle a config change notification from the pairing.""" - self.hass.async_create_task( - self.async_update_new_accessories_state(), eager_start=True + self.config_entry.async_create_task( + self.hass, self.async_update_new_accessories_state(), eager_start=True ) async def async_update_new_accessories_state(self) -> None: From d40bd0f6c8e6039075e892f93a1b7b18cf89b6e6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 18:37:43 -1000 Subject: [PATCH 0524/1691] Fix dhcp delaying shutdown (#112670) --- homeassistant/components/dhcp/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index ebd0629950e..de3e48c468b 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -316,7 +316,9 @@ class NetworkWatcher(WatcherBase): """Start a new discovery task if one is not running.""" if self._discover_task and not self._discover_task.done(): return - self._discover_task = self.hass.async_create_task(self.async_discover()) + self._discover_task = self.hass.async_create_background_task( + self.async_discover(), name="dhcp discovery", eager_start=True + ) async def async_discover(self) -> None: """Process discovery.""" From a12fa0383b666ae2ebfc073392485cef85799881 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 18:37:56 -1000 Subject: [PATCH 0525/1691] Make zeroconf lookups background tasks (#112669) * Make zeroconf lookups background tasks There were blocking startup and shutdown * disable for now --- homeassistant/components/zeroconf/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 344c174242a..d580f097179 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -414,10 +414,11 @@ class ZeroconfDiscovery: if async_service_info.load_from_cache(zeroconf): self._async_process_service_update(async_service_info, service_type, name) else: - self.hass.async_create_task( + self.hass.async_create_background_task( self._async_lookup_and_process_service_update( zeroconf, async_service_info, service_type, name - ) + ), + name=f"zeroconf lookup {name}.{service_type}", ) async def _async_lookup_and_process_service_update( From 0382d628a3b470f3c07bd8dd200448fc3ae392de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 18:39:44 -1000 Subject: [PATCH 0526/1691] Close hue api if setup fails (#112164) fixes #109722 --- homeassistant/components/hue/bridge.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index abf91cf4577..86db9c4021c 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -71,10 +71,11 @@ class HueBridge: async def async_initialize_bridge(self) -> bool: """Initialize Connection with the Hue API.""" + setup_ok = False try: async with asyncio.timeout(10): await self.api.initialize() - + setup_ok = True except (LinkButtonNotPressed, Unauthorized): # Usernames can become invalid if hub is reset or user removed. # We are going to fail the config entry setup and initiate a new @@ -95,6 +96,9 @@ class HueBridge: except Exception: # pylint: disable=broad-except self.logger.exception("Unknown error connecting to Hue bridge") return False + finally: + if not setup_ok: + await self.api.close() # v1 specific initialization/setup code here if self.api_version == 1: From caefdc619245b821dac9b21cdb99f7847150709a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 19:14:42 -1000 Subject: [PATCH 0527/1691] Avoid writing registries to disk during startup (#112662) --- homeassistant/helpers/area_registry.py | 9 +---- homeassistant/helpers/device_registry.py | 10 ++--- homeassistant/helpers/entity_registry.py | 10 ++--- homeassistant/helpers/floor_registry.py | 9 +---- homeassistant/helpers/issue_registry.py | 9 +---- homeassistant/helpers/label_registry.py | 9 +---- homeassistant/helpers/registry.py | 35 ++++++++++++++++ tests/helpers/test_registry.py | 51 ++++++++++++++++++++++++ 8 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 homeassistant/helpers/registry.py create mode 100644 tests/helpers/test_registry.py diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index f1731f43473..7565c87b6b2 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -14,6 +14,7 @@ from .normalized_name_base_registry import ( NormalizedNameBaseRegistryItems, normalize_name, ) +from .registry import BaseRegistry from .storage import Store from .typing import UNDEFINED, UndefinedType @@ -22,7 +23,6 @@ EVENT_AREA_REGISTRY_UPDATED = "area_registry_updated" STORAGE_KEY = "core.area_registry" STORAGE_VERSION_MAJOR = 1 STORAGE_VERSION_MINOR = 6 -SAVE_DELAY = 10 class EventAreaRegistryUpdatedData(TypedDict): @@ -86,7 +86,7 @@ class AreaRegistryStore(Store[dict[str, list[dict[str, Any]]]]): return old_data -class AreaRegistry: +class AreaRegistry(BaseRegistry): """Class to hold a registry of areas.""" areas: NormalizedNameBaseRegistryItems[AreaEntry] @@ -273,11 +273,6 @@ class AreaRegistry: self.areas = areas self._area_data = areas.data - @callback - def async_schedule_save(self) -> None: - """Schedule saving the area registry.""" - self._store.async_delay_save(self._data_to_save, SAVE_DELAY) - @callback def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data of area registry to store in a file.""" diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 5cbc40e209f..253b7eaa65c 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -31,6 +31,7 @@ from .deprecation import ( ) from .frame import report from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes +from .registry import BaseRegistry from .typing import UNDEFINED, UndefinedType if TYPE_CHECKING: @@ -45,7 +46,7 @@ EVENT_DEVICE_REGISTRY_UPDATED = "device_registry_updated" STORAGE_KEY = "core.device_registry" STORAGE_VERSION_MAJOR = 1 STORAGE_VERSION_MINOR = 5 -SAVE_DELAY = 10 + CLEANUP_DELAY = 10 CONNECTION_BLUETOOTH = "bluetooth" @@ -456,7 +457,7 @@ class DeviceRegistryItems(UserDict[str, _EntryTypeT]): return None -class DeviceRegistry: +class DeviceRegistry(BaseRegistry): """Class to hold a registry of devices.""" devices: DeviceRegistryItems[DeviceEntry] @@ -898,11 +899,6 @@ class DeviceRegistry: self.deleted_devices = deleted_devices self._device_data = devices.data - @callback - def async_schedule_save(self) -> None: - """Schedule saving the device registry.""" - self._store.async_delay_save(self._data_to_save, SAVE_DELAY) - @callback def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: """Return data of device registry to store in a file.""" diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index eb0aace4265..df923c15c37 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -52,6 +52,7 @@ from homeassistant.util.read_only_dict import ReadOnlyDict from . import device_registry as dr, storage from .device_registry import EVENT_DEVICE_REGISTRY_UPDATED from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes +from .registry import BaseRegistry from .typing import UNDEFINED, UndefinedType if TYPE_CHECKING: @@ -61,7 +62,7 @@ T = TypeVar("T") DATA_REGISTRY = "entity_registry" EVENT_ENTITY_REGISTRY_UPDATED = "entity_registry_updated" -SAVE_DELAY = 10 + _LOGGER = logging.getLogger(__name__) STORAGE_VERSION_MAJOR = 1 @@ -549,7 +550,7 @@ class EntityRegistryItems(UserDict[str, RegistryEntry]): return [data[key] for key in self._area_id_index.get(area_id, ())] -class EntityRegistry: +class EntityRegistry(BaseRegistry): """Class to hold a registry of entities.""" deleted_entities: dict[tuple[str, str, str], DeletedRegistryEntry] @@ -1182,11 +1183,6 @@ class EntityRegistry: self.entities = entities self._entities_data = entities.data - @callback - def async_schedule_save(self) -> None: - """Schedule saving the entity registry.""" - self._store.async_delay_save(self._data_to_save, SAVE_DELAY) - @callback def _data_to_save(self) -> dict[str, Any]: """Return data of entity registry to store in a file.""" diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index 978471d7cd2..d966f6045b6 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -14,6 +14,7 @@ from .normalized_name_base_registry import ( NormalizedNameBaseRegistryItems, normalize_name, ) +from .registry import BaseRegistry from .storage import Store from .typing import UNDEFINED, EventType, UndefinedType @@ -21,7 +22,6 @@ DATA_REGISTRY = "floor_registry" EVENT_FLOOR_REGISTRY_UPDATED = "floor_registry_updated" STORAGE_KEY = "core.floor_registry" STORAGE_VERSION_MAJOR = 1 -SAVE_DELAY = 10 class EventFloorRegistryUpdatedData(TypedDict): @@ -44,7 +44,7 @@ class FloorEntry(NormalizedNameBaseRegistryEntry): level: int = 0 -class FloorRegistry: +class FloorRegistry(BaseRegistry): """Class to hold a registry of floors.""" floors: NormalizedNameBaseRegistryItems[FloorEntry] @@ -209,11 +209,6 @@ class FloorRegistry: self.floors = floors self._floor_data = floors.data - @callback - def async_schedule_save(self) -> None: - """Schedule saving the floor registry.""" - self._store.async_delay_save(self._data_to_save, SAVE_DELAY) - @callback def _data_to_save(self) -> dict[str, list[dict[str, str | int | list[str] | None]]]: """Return data of floor registry to store in a file.""" diff --git a/homeassistant/helpers/issue_registry.py b/homeassistant/helpers/issue_registry.py index 27d568a13de..fc8c547404d 100644 --- a/homeassistant/helpers/issue_registry.py +++ b/homeassistant/helpers/issue_registry.py @@ -14,6 +14,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as dt_util +from .registry import BaseRegistry from .storage import Store DATA_REGISTRY = "issue_registry" @@ -21,7 +22,6 @@ EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED = "repairs_issue_registry_updated" STORAGE_KEY = "repairs.issue_registry" STORAGE_VERSION_MAJOR = 1 STORAGE_VERSION_MINOR = 2 -SAVE_DELAY = 10 class IssueSeverity(StrEnum): @@ -92,7 +92,7 @@ class IssueRegistryStore(Store[dict[str, list[dict[str, Any]]]]): return old_data -class IssueRegistry: +class IssueRegistry(BaseRegistry): """Class to hold a registry of issues.""" def __init__(self, hass: HomeAssistant, *, read_only: bool = False) -> None: @@ -259,11 +259,6 @@ class IssueRegistry: self.issues = issues - @callback - def async_schedule_save(self) -> None: - """Schedule saving the issue registry.""" - self._store.async_delay_save(self._data_to_save, SAVE_DELAY) - @callback def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]: """Return data of issue registry to store in a file.""" diff --git a/homeassistant/helpers/label_registry.py b/homeassistant/helpers/label_registry.py index ef3abc19d8c..34c23c98f2a 100644 --- a/homeassistant/helpers/label_registry.py +++ b/homeassistant/helpers/label_registry.py @@ -14,6 +14,7 @@ from .normalized_name_base_registry import ( NormalizedNameBaseRegistryItems, normalize_name, ) +from .registry import BaseRegistry from .storage import Store from .typing import UNDEFINED, EventType, UndefinedType @@ -21,7 +22,6 @@ DATA_REGISTRY = "label_registry" EVENT_LABEL_REGISTRY_UPDATED = "label_registry_updated" STORAGE_KEY = "core.label_registry" STORAGE_VERSION_MAJOR = 1 -SAVE_DELAY = 10 class EventLabelRegistryUpdatedData(TypedDict): @@ -44,7 +44,7 @@ class LabelEntry(NormalizedNameBaseRegistryEntry): icon: str | None = None -class LabelRegistry: +class LabelRegistry(BaseRegistry): """Class to hold a registry of labels.""" labels: NormalizedNameBaseRegistryItems[LabelEntry] @@ -205,11 +205,6 @@ class LabelRegistry: self.labels = labels self._label_data = labels.data - @callback - def async_schedule_save(self) -> None: - """Schedule saving the label registry.""" - self._store.async_delay_save(self._data_to_save, SAVE_DELAY) - @callback def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]: """Return data of label registry to store in a file.""" diff --git a/homeassistant/helpers/registry.py b/homeassistant/helpers/registry.py new file mode 100644 index 00000000000..7b13941e5d2 --- /dev/null +++ b/homeassistant/helpers/registry.py @@ -0,0 +1,35 @@ +"""Provide a base implementation for registries.""" +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any + +from homeassistant.core import CoreState, HomeAssistant, callback + +if TYPE_CHECKING: + from .storage import Store + +SAVE_DELAY = 10 +SAVE_DELAY_STARTING = 300 + + +class BaseRegistry(ABC): + """Class to implement a registry.""" + + hass: HomeAssistant + _store: Store + + @callback + def async_schedule_save(self) -> None: + """Schedule saving the registry.""" + # Schedule the save past startup to avoid writing + # the file while the system is starting. + delay = ( + SAVE_DELAY_STARTING if self.hass.state is CoreState.starting else SAVE_DELAY + ) + self._store.async_delay_save(self._data_to_save, delay) + + @callback + @abstractmethod + def _data_to_save(self) -> dict[str, Any]: + """Return data of registry to store in a file.""" diff --git a/tests/helpers/test_registry.py b/tests/helpers/test_registry.py new file mode 100644 index 00000000000..2782d3140f7 --- /dev/null +++ b/tests/helpers/test_registry.py @@ -0,0 +1,51 @@ +"""Tests for the registry.""" +from typing import Any + +from freezegun.api import FrozenDateTimeFactory + +from homeassistant.core import CoreState, HomeAssistant +from homeassistant.helpers import storage +from homeassistant.helpers.registry import SAVE_DELAY, SAVE_DELAY_STARTING, BaseRegistry + +from tests.common import async_fire_time_changed + + +class SampleRegistry(BaseRegistry): + """Class to hold a registry of X.""" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the registry.""" + self.hass = hass + self._store = storage.Store(hass, 1, "test") + self.save_calls = 0 + + def _data_to_save(self) -> None: + """Return data of registry to save.""" + self.save_calls += 1 + return None + + +async def test_async_schedule_save( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, hass_storage: dict[str, Any] +) -> None: + """Test saving the registry.""" + registry = SampleRegistry(hass) + hass.set_state(CoreState.starting) + + registry.async_schedule_save() + freezer.tick(SAVE_DELAY) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert registry.save_calls == 0 + + freezer.tick(SAVE_DELAY_STARTING) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert registry.save_calls == 1 + + hass.set_state(CoreState.running) + registry.async_schedule_save() + freezer.tick(SAVE_DELAY) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert registry.save_calls == 2 From 15b59d310a30e485555aee055f6fc3c0802b695d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 8 Mar 2024 07:42:37 +0100 Subject: [PATCH 0528/1691] Bump axis to v52 (#112632) * Bump axis to v51 * Bump to v52 --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 5311d18f991..9bfe37da9da 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==50"], + "requirements": ["axis==52"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index fda096426e0..91404852ece 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==50 +axis==52 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f4a4057ebbe..0cb0228e201 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -454,7 +454,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==50 +axis==52 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From d2effd869343b924e2d25e12c050d4359444287c Mon Sep 17 00:00:00 2001 From: Jim Date: Fri, 8 Mar 2024 07:56:26 +0000 Subject: [PATCH 0529/1691] Bump python-telegram-bot package to 21.0.1 (#110297) * Bump python-telegram-bot package version to the latest. * PySocks is no longer required as python-telegram-bot doesn't use urllib3 anymore. * Fix moved ParseMode import * Update filters import to new structure. * Refactor removed Request objects to HTTPXRequest objects. * Update to support asyncc functions * Update timeout to new kwarg connect_timeout is the most obvious option based on current param description, but this may need changing. * Compatibility typo. * Make methods async and use Bot client async natively * Type needs to be Optional That's what the source types are from the library Also handle new possibility of None value * Add socks support version of the library * Refactor load_data function Update to be async friendly Refactor to use httpx instead of requests. * Refactor Dispatcher references to Application This is the newer model of the same class. * Make more stuff async-friendly. * Update tests to refactor Dispatcher usage out. * Remove import and reference directly * Refactor typing method * Use async_fire now we have async support * Fix some over complicate inheritance. * Add the polling test telegram_text event fired back in. * Add extra context to comment * Handler should also be async * Use underscores instead of camelCase Co-authored-by: Martin Hjelmare * Renamed kwarg. * Refactor current timeout param to be read timeout Reading the old version of the library code I believe this matches the existing functionality best * Combine unload methods into one listener * Fix test by stopping HA as part of fixture * Add new fixture to mock stop_polling call Use this in all polling tests. * No longer need to check if application is running It was to stop a test failing. * Make sure the updater is started in tests Mock external call methods Remove stop_polling mock. * Use cleaner references to patched methods * Improve test by letting the library create the Update object * Mock component tear down methods to be async * Bump mypy cache version * Update dependency to install from git Allows use as a custom component in 2024.3 Allows us to track mypy issue resolution. * Update manifest and requirements for new python-telegram-bot release. * Remove pytest filterwarnings entry for old version of python-telegram-bot library. --------- Co-authored-by: Martin Hjelmare --- .github/workflows/ci.yaml | 2 +- .../components/telegram_bot/__init__.py | 214 +++++++++--------- .../components/telegram_bot/manifest.json | 2 +- .../components/telegram_bot/polling.py | 31 +-- .../components/telegram_bot/strings.json | 36 +-- .../components/telegram_bot/webhooks.py | 48 ++-- pyproject.toml | 2 - requirements_all.txt | 5 +- requirements_test_all.txt | 5 +- tests/components/telegram_bot/conftest.py | 37 ++- .../telegram_bot/test_telegram_bot.py | 50 ++-- 11 files changed, 240 insertions(+), 192 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 45628d00d33..4042319cd0b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,7 +35,7 @@ on: env: CACHE_VERSION: 5 PIP_CACHE_VERSION: 4 - MYPY_CACHE_VERSION: 7 + MYPY_CACHE_VERSION: 8 HA_SHORT_VERSION: "2024.4" DEFAULT_PYTHON: "3.11" ALL_PYTHON_VERSIONS: "['3.11', '3.12']" diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 2ba7752a85f..1aeed6a25bb 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -1,15 +1,14 @@ """Support to send and receive Telegram messages.""" from __future__ import annotations -from functools import partial +import asyncio import importlib import io from ipaddress import ip_network import logging from typing import Any -import requests -from requests.auth import HTTPBasicAuth, HTTPDigestAuth +import httpx from telegram import ( Bot, CallbackQuery, @@ -21,10 +20,10 @@ from telegram import ( Update, User, ) +from telegram.constants import ParseMode from telegram.error import TelegramError -from telegram.ext import CallbackContext, Filters -from telegram.parsemode import ParseMode -from telegram.utils.request import Request +from telegram.ext import CallbackContext, filters +from telegram.request import HTTPXRequest import voluptuous as vol from homeassistant.const import ( @@ -283,7 +282,7 @@ SERVICE_MAP = { } -def load_data( +async def load_data( hass, url=None, filepath=None, @@ -297,35 +296,48 @@ def load_data( try: if url is not None: # Load data from URL - params = {"timeout": 15} + params = {} + headers = {} if authentication == HTTP_BEARER_AUTHENTICATION and password is not None: - params["headers"] = {"Authorization": f"Bearer {password}"} + headers = {"Authorization": f"Bearer {password}"} elif username is not None and password is not None: if authentication == HTTP_DIGEST_AUTHENTICATION: - params["auth"] = HTTPDigestAuth(username, password) + params["auth"] = httpx.DigestAuth(username, password) else: - params["auth"] = HTTPBasicAuth(username, password) + params["auth"] = httpx.BasicAuth(username, password) if verify_ssl is not None: params["verify"] = verify_ssl + retry_num = 0 - while retry_num < num_retries: - req = requests.get(url, **params) - if not req.ok: - _LOGGER.warning( - "Status code %s (retry #%s) loading %s", - req.status_code, - retry_num + 1, - url, - ) - else: - data = io.BytesIO(req.content) - if data.read(): - data.seek(0) - data.name = url - return data - _LOGGER.warning("Empty data (retry #%s) in %s)", retry_num + 1, url) - retry_num += 1 - _LOGGER.warning("Can't load data in %s after %s retries", url, retry_num) + async with httpx.AsyncClient( + timeout=15, headers=headers, **params + ) as client: + while retry_num < num_retries: + req = await client.get(url) + if req.status_code != 200: + _LOGGER.warning( + "Status code %s (retry #%s) loading %s", + req.status_code, + retry_num + 1, + url, + ) + else: + data = io.BytesIO(req.content) + if data.read(): + data.seek(0) + data.name = url + return data + _LOGGER.warning( + "Empty data (retry #%s) in %s)", retry_num + 1, url + ) + retry_num += 1 + if retry_num < num_retries: + await asyncio.sleep( + 1 + ) # Add a sleep to allow other async operations to proceed + _LOGGER.warning( + "Can't load data in %s after %s retries", url, retry_num + ) elif filepath is not None: if hass.config.is_allowed_path(filepath): return open(filepath, "rb") @@ -406,9 +418,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.debug("New telegram message %s: %s", msgtype, kwargs) if msgtype == SERVICE_SEND_MESSAGE: - await hass.async_add_executor_job( - partial(notify_service.send_message, **kwargs) - ) + await notify_service.send_message(**kwargs) elif msgtype in [ SERVICE_SEND_PHOTO, SERVICE_SEND_ANIMATION, @@ -416,33 +426,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: SERVICE_SEND_VOICE, SERVICE_SEND_DOCUMENT, ]: - await hass.async_add_executor_job( - partial(notify_service.send_file, msgtype, **kwargs) - ) + await notify_service.send_file(msgtype, **kwargs) elif msgtype == SERVICE_SEND_STICKER: - await hass.async_add_executor_job( - partial(notify_service.send_sticker, **kwargs) - ) + await notify_service.send_sticker(**kwargs) elif msgtype == SERVICE_SEND_LOCATION: - await hass.async_add_executor_job( - partial(notify_service.send_location, **kwargs) - ) + await notify_service.send_location(**kwargs) elif msgtype == SERVICE_SEND_POLL: - await hass.async_add_executor_job( - partial(notify_service.send_poll, **kwargs) - ) + await notify_service.send_poll(**kwargs) elif msgtype == SERVICE_ANSWER_CALLBACK_QUERY: - await hass.async_add_executor_job( - partial(notify_service.answer_callback_query, **kwargs) - ) + await notify_service.answer_callback_query(**kwargs) elif msgtype == SERVICE_DELETE_MESSAGE: - await hass.async_add_executor_job( - partial(notify_service.delete_message, **kwargs) - ) + await notify_service.delete_message(**kwargs) else: - await hass.async_add_executor_job( - partial(notify_service.edit_message, msgtype, **kwargs) - ) + await notify_service.edit_message(msgtype, **kwargs) # Register notification services for service_notif, schema in SERVICE_MAP.items(): @@ -460,11 +456,13 @@ def initialize_bot(p_config): proxy_params = p_config.get(CONF_PROXY_PARAMS) if proxy_url is not None: - request = Request( - con_pool_size=8, proxy_url=proxy_url, urllib3_proxy_kwargs=proxy_params - ) + # These have been kept for backwards compatibility, they can actually be stuffed into the URL. + # Side note: In the future we should deprecate these and raise a repair issue if we find them here. + auth = proxy_params.pop("username"), proxy_params.pop("password") + proxy = httpx.Proxy(proxy_url, auth=auth, **proxy_params) + request = HTTPXRequest(connection_pool_size=8, proxy=proxy) else: - request = Request(con_pool_size=8) + request = HTTPXRequest(connection_pool_size=8) return Bot(token=api_key, request=request) @@ -616,10 +614,12 @@ class TelegramNotificationService: ) return params - def _send_msg(self, func_send, msg_error, message_tag, *args_msg, **kwargs_msg): + async def _send_msg( + self, func_send, msg_error, message_tag, *args_msg, **kwargs_msg + ): """Send one message.""" try: - out = func_send(*args_msg, **kwargs_msg) + out = await func_send(*args_msg, **kwargs_msg) if not isinstance(out, bool) and hasattr(out, ATTR_MESSAGEID): chat_id = out.chat_id message_id = out[ATTR_MESSAGEID] @@ -636,7 +636,7 @@ class TelegramNotificationService: } if message_tag is not None: event_data[ATTR_MESSAGE_TAG] = message_tag - self.hass.bus.fire(EVENT_TELEGRAM_SENT, event_data) + self.hass.bus.async_fire(EVENT_TELEGRAM_SENT, event_data) elif not isinstance(out, bool): _LOGGER.warning( "Update last message: out_type:%s, out=%s", type(out), out @@ -647,14 +647,14 @@ class TelegramNotificationService: "%s: %s. Args: %s, kwargs: %s", msg_error, exc, args_msg, kwargs_msg ) - def send_message(self, message="", target=None, **kwargs): + async def send_message(self, message="", target=None, **kwargs): """Send a message to one or multiple pre-allowed chat IDs.""" title = kwargs.get(ATTR_TITLE) text = f"{title}\n{message}" if title else message params = self._get_msg_kwargs(kwargs) for chat_id in self._get_target_chat_ids(target): _LOGGER.debug("Send message in chat ID %s with params: %s", chat_id, params) - self._send_msg( + await self._send_msg( self.bot.send_message, "Error sending message", params[ATTR_MESSAGE_TAG], @@ -665,15 +665,15 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) - def delete_message(self, chat_id=None, **kwargs): + async def delete_message(self, chat_id=None, **kwargs): """Delete a previously sent message.""" chat_id = self._get_target_chat_ids(chat_id)[0] message_id, _ = self._get_msg_ids(kwargs, chat_id) _LOGGER.debug("Delete message %s in chat ID %s", message_id, chat_id) - deleted = self._send_msg( + deleted = await self._send_msg( self.bot.delete_message, "Error deleting message", None, chat_id, message_id ) # reduce message_id anyway: @@ -682,7 +682,7 @@ class TelegramNotificationService: self._last_message_id[chat_id] -= 1 return deleted - def edit_message(self, type_edit, chat_id=None, **kwargs): + async def edit_message(self, type_edit, chat_id=None, **kwargs): """Edit a previously sent message.""" chat_id = self._get_target_chat_ids(chat_id)[0] message_id, inline_message_id = self._get_msg_ids(kwargs, chat_id) @@ -698,7 +698,7 @@ class TelegramNotificationService: title = kwargs.get(ATTR_TITLE) text = f"{title}\n{message}" if title else message _LOGGER.debug("Editing message with ID %s", message_id or inline_message_id) - return self._send_msg( + return await self._send_msg( self.bot.edit_message_text, "Error editing text message", params[ATTR_MESSAGE_TAG], @@ -709,10 +709,10 @@ class TelegramNotificationService: parse_mode=params[ATTR_PARSER], disable_web_page_preview=params[ATTR_DISABLE_WEB_PREV], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) if type_edit == SERVICE_EDIT_CAPTION: - return self._send_msg( + return await self._send_msg( self.bot.edit_message_caption, "Error editing message attributes", params[ATTR_MESSAGE_TAG], @@ -721,11 +721,11 @@ class TelegramNotificationService: inline_message_id=inline_message_id, caption=kwargs.get(ATTR_CAPTION), reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], ) - return self._send_msg( + return await self._send_msg( self.bot.edit_message_reply_markup, "Error editing message attributes", params[ATTR_MESSAGE_TAG], @@ -733,10 +733,10 @@ class TelegramNotificationService: message_id=message_id, inline_message_id=inline_message_id, reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) - def answer_callback_query( + async def answer_callback_query( self, message, callback_query_id, show_alert=False, **kwargs ): """Answer a callback originated with a press in an inline keyboard.""" @@ -747,20 +747,20 @@ class TelegramNotificationService: message, show_alert, ) - self._send_msg( + await self._send_msg( self.bot.answer_callback_query, "Error sending answer callback query", params[ATTR_MESSAGE_TAG], callback_query_id, text=message, show_alert=show_alert, - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) - def send_file(self, file_type=SERVICE_SEND_PHOTO, target=None, **kwargs): + async def send_file(self, file_type=SERVICE_SEND_PHOTO, target=None, **kwargs): """Send a photo, sticker, video, or document.""" params = self._get_msg_kwargs(kwargs) - file_content = load_data( + file_content = await load_data( self.hass, url=kwargs.get(ATTR_URL), filepath=kwargs.get(ATTR_FILE), @@ -775,7 +775,7 @@ class TelegramNotificationService: _LOGGER.debug("Sending file to chat ID %s", chat_id) if file_type == SERVICE_SEND_PHOTO: - self._send_msg( + await self._send_msg( self.bot.send_photo, "Error sending photo", params[ATTR_MESSAGE_TAG], @@ -785,12 +785,12 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], ) elif file_type == SERVICE_SEND_STICKER: - self._send_msg( + await self._send_msg( self.bot.send_sticker, "Error sending sticker", params[ATTR_MESSAGE_TAG], @@ -799,11 +799,11 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) elif file_type == SERVICE_SEND_VIDEO: - self._send_msg( + await self._send_msg( self.bot.send_video, "Error sending video", params[ATTR_MESSAGE_TAG], @@ -813,11 +813,11 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], ) elif file_type == SERVICE_SEND_DOCUMENT: - self._send_msg( + await self._send_msg( self.bot.send_document, "Error sending document", params[ATTR_MESSAGE_TAG], @@ -827,11 +827,11 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], ) elif file_type == SERVICE_SEND_VOICE: - self._send_msg( + await self._send_msg( self.bot.send_voice, "Error sending voice", params[ATTR_MESSAGE_TAG], @@ -841,10 +841,10 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) elif file_type == SERVICE_SEND_ANIMATION: - self._send_msg( + await self._send_msg( self.bot.send_animation, "Error sending animation", params[ATTR_MESSAGE_TAG], @@ -854,7 +854,7 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], ) @@ -862,13 +862,13 @@ class TelegramNotificationService: else: _LOGGER.error("Can't send file with kwargs: %s", kwargs) - def send_sticker(self, target=None, **kwargs): + async def send_sticker(self, target=None, **kwargs): """Send a sticker from a telegram sticker pack.""" params = self._get_msg_kwargs(kwargs) stickerid = kwargs.get(ATTR_STICKER_ID) if stickerid: for chat_id in self._get_target_chat_ids(target): - self._send_msg( + await self._send_msg( self.bot.send_sticker, "Error sending sticker", params[ATTR_MESSAGE_TAG], @@ -877,12 +877,12 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) else: - self.send_file(SERVICE_SEND_STICKER, target, **kwargs) + await self.send_file(SERVICE_SEND_STICKER, target, **kwargs) - def send_location(self, latitude, longitude, target=None, **kwargs): + async def send_location(self, latitude, longitude, target=None, **kwargs): """Send a location.""" latitude = float(latitude) longitude = float(longitude) @@ -891,7 +891,7 @@ class TelegramNotificationService: _LOGGER.debug( "Send location %s/%s to chat ID %s", latitude, longitude, chat_id ) - self._send_msg( + await self._send_msg( self.bot.send_location, "Error sending location", params[ATTR_MESSAGE_TAG], @@ -900,10 +900,10 @@ class TelegramNotificationService: longitude=longitude, disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) - def send_poll( + async def send_poll( self, question, options, @@ -917,7 +917,7 @@ class TelegramNotificationService: openperiod = kwargs.get(ATTR_OPEN_PERIOD) for chat_id in self._get_target_chat_ids(target): _LOGGER.debug("Send poll '%s' to chat ID %s", question, chat_id) - self._send_msg( + await self._send_msg( self.bot.send_poll, "Error sending poll", params[ATTR_MESSAGE_TAG], @@ -929,14 +929,14 @@ class TelegramNotificationService: open_period=openperiod, disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], - timeout=params[ATTR_TIMEOUT], + read_timeout=params[ATTR_TIMEOUT], ) - def leave_chat(self, chat_id=None): + async def leave_chat(self, chat_id=None): """Remove bot from chat.""" chat_id = self._get_target_chat_ids(chat_id)[0] _LOGGER.debug("Leave from chat ID %s", chat_id) - leaved = self._send_msg( + leaved = await self._send_msg( self.bot.leave_chat, "Error leaving chat", None, chat_id ) return leaved @@ -950,8 +950,8 @@ class BaseTelegramBotEntity: self.allowed_chat_ids = config[CONF_ALLOWED_CHAT_IDS] self.hass = hass - def handle_update(self, update: Update, context: CallbackContext) -> bool: - """Handle updates from bot dispatcher set up by the respective platform.""" + async def handle_update(self, update: Update, context: CallbackContext) -> bool: + """Handle updates from bot application set up by the respective platform.""" _LOGGER.debug("Handling update %s", update) if not self.authorize_update(update): return False @@ -972,12 +972,12 @@ class BaseTelegramBotEntity: return True _LOGGER.debug("Firing event %s: %s", event_type, event_data) - self.hass.bus.fire(event_type, event_data) + self.hass.bus.async_fire(event_type, event_data) return True @staticmethod - def _get_command_event_data(command_text: str) -> dict[str, str | list]: - if not command_text.startswith("/"): + def _get_command_event_data(command_text: str | None) -> dict[str, str | list]: + if not command_text or not command_text.startswith("/"): return {} command_parts = command_text.split() command = command_parts[0] @@ -990,7 +990,7 @@ class BaseTelegramBotEntity: ATTR_CHAT_ID: message.chat.id, ATTR_DATE: message.date, } - if Filters.command.filter(message): + if filters.COMMAND.filter(message): # This is a command message - set event type to command and split data into command and args event_type = EVENT_TELEGRAM_COMMAND event_data.update(self._get_command_event_data(message.text)) diff --git a/homeassistant/components/telegram_bot/manifest.json b/homeassistant/components/telegram_bot/manifest.json index a4964638242..c176e6c2cdf 100644 --- a/homeassistant/components/telegram_bot/manifest.json +++ b/homeassistant/components/telegram_bot/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/telegram_bot", "iot_class": "cloud_push", "loggers": ["telegram"], - "requirements": ["python-telegram-bot==13.1", "PySocks==1.7.1"] + "requirements": ["python-telegram-bot[socks]==21.0.1"] } diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index 69bb4dc4963..bac6262cc6a 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -3,7 +3,7 @@ import logging from telegram import Update from telegram.error import NetworkError, RetryAfter, TelegramError, TimedOut -from telegram.ext import CallbackContext, TypeHandler, Updater +from telegram.ext import ApplicationBuilder, CallbackContext, TypeHandler from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP @@ -22,7 +22,7 @@ async def async_setup_platform(hass, bot, config): return True -def process_error(update: Update, context: CallbackContext) -> None: +async def process_error(update: Update, context: CallbackContext) -> None: """Telegram bot error handler.""" try: if context.error: @@ -35,26 +35,29 @@ def process_error(update: Update, context: CallbackContext) -> None: class PollBot(BaseTelegramBotEntity): - """Controls the Updater object that holds the bot and a dispatcher. + """Controls the Application object that holds the bot and an updater. - The dispatcher is set up by the super class to pass telegram updates to `self.handle_update` + The application is set up to pass telegram updates to `self.handle_update` """ def __init__(self, hass, bot, config): - """Create Updater and Dispatcher before calling super().""" - self.bot = bot - self.updater = Updater(bot=bot, workers=4) - self.dispatcher = self.updater.dispatcher - self.dispatcher.add_handler(TypeHandler(Update, self.handle_update)) - self.dispatcher.add_error_handler(process_error) + """Create Application to poll for updates.""" super().__init__(hass, config) + self.bot = bot + self.application = ApplicationBuilder().bot(self.bot).build() + self.application.add_handler(TypeHandler(Update, self.handle_update)) + self.application.add_error_handler(process_error) - def start_polling(self, event=None): + async def start_polling(self, event=None): """Start the polling task.""" _LOGGER.debug("Starting polling") - self.updater.start_polling() + await self.application.initialize() + await self.application.updater.start_polling() + await self.application.start() - def stop_polling(self, event=None): + async def stop_polling(self, event=None): """Stop the polling task.""" _LOGGER.debug("Stopping polling") - self.updater.stop() + await self.application.updater.stop() + await self.application.stop() + await self.application.shutdown() diff --git a/homeassistant/components/telegram_bot/strings.json b/homeassistant/components/telegram_bot/strings.json index de5de685409..2e1d51f31e8 100644 --- a/homeassistant/components/telegram_bot/strings.json +++ b/homeassistant/components/telegram_bot/strings.json @@ -29,8 +29,8 @@ "description": "Disables link previews for links in the message." }, "timeout": { - "name": "Timeout", - "description": "Timeout for send message. Will help with timeout errors (poor internet connection, etc)s." + "name": "Read timeout", + "description": "Read timeout for send message. Will help with timeout errors (poor internet connection, etc)s." }, "keyboard": { "name": "Keyboard", @@ -95,8 +95,8 @@ "description": "Enable or disable SSL certificate verification. Set to false if you're downloading the file from a URL and you don't want to validate the SSL certificate of the server." }, "timeout": { - "name": "Timeout", - "description": "Timeout for send photo. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for send photo." }, "keyboard": { "name": "[%key:component::telegram_bot::services::send_message::fields::keyboard::name%]", @@ -157,8 +157,8 @@ "description": "[%key:component::telegram_bot::services::send_photo::fields::verify_ssl::description%]" }, "timeout": { - "name": "Timeout", - "description": "Timeout for send sticker. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for send sticker." }, "keyboard": { "name": "[%key:component::telegram_bot::services::send_message::fields::keyboard::name%]", @@ -223,7 +223,7 @@ "description": "[%key:component::telegram_bot::services::send_photo::fields::verify_ssl::description%]" }, "timeout": { - "name": "Timeout", + "name": "Read timeout", "description": "[%key:component::telegram_bot::services::send_sticker::fields::timeout::description%]" }, "keyboard": { @@ -289,8 +289,8 @@ "description": "[%key:component::telegram_bot::services::send_photo::fields::verify_ssl::description%]" }, "timeout": { - "name": "Timeout", - "description": "Timeout for send video. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for send video." }, "keyboard": { "name": "[%key:component::telegram_bot::services::send_message::fields::keyboard::name%]", @@ -351,8 +351,8 @@ "description": "[%key:component::telegram_bot::services::send_photo::fields::verify_ssl::description%]" }, "timeout": { - "name": "Timeout", - "description": "Timeout for send voice. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for send voice." }, "keyboard": { "name": "[%key:component::telegram_bot::services::send_message::fields::keyboard::name%]", @@ -417,8 +417,8 @@ "description": "[%key:component::telegram_bot::services::send_photo::fields::verify_ssl::description%]" }, "timeout": { - "name": "Timeout", - "description": "Timeout for send document. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for send document." }, "keyboard": { "name": "[%key:component::telegram_bot::services::send_message::fields::keyboard::name%]", @@ -459,7 +459,7 @@ "description": "[%key:component::telegram_bot::services::send_message::fields::disable_notification::description%]" }, "timeout": { - "name": "Timeout", + "name": "Read timeout", "description": "[%key:component::telegram_bot::services::send_photo::fields::timeout::description%]" }, "keyboard": { @@ -513,8 +513,8 @@ "description": "[%key:component::telegram_bot::services::send_message::fields::disable_notification::description%]" }, "timeout": { - "name": "Timeout", - "description": "Timeout for send poll. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for send poll." }, "message_tag": { "name": "[%key:component::telegram_bot::services::send_message::fields::message_tag::name%]", @@ -617,8 +617,8 @@ "description": "Show a permanent notification." }, "timeout": { - "name": "Timeout", - "description": "Timeout for sending the answer. Will help with timeout errors (poor internet connection, etc)." + "name": "Read timeout", + "description": "Read timeout for sending the answer." } } }, diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index c21cffa84b1..50fd7bc8427 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -8,7 +8,7 @@ import string from telegram import Update from telegram.error import TimedOut -from telegram.ext import Dispatcher, TypeHandler +from telegram.ext import Application, TypeHandler from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -36,16 +36,17 @@ async def async_setup_platform(hass, bot, config): _LOGGER.error("Invalid telegram webhook %s must be https", pushbot.webhook_url) return False + await pushbot.start_application() webhook_registered = await pushbot.register_webhook() if not webhook_registered: return False - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, pushbot.deregister_webhook) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, pushbot.stop_application) hass.http.register_view( PushBotView( hass, bot, - pushbot.dispatcher, + pushbot.application, config[CONF_TRUSTED_NETWORKS], secret_token, ) @@ -57,13 +58,13 @@ class PushBot(BaseTelegramBotEntity): """Handles all the push/webhook logic and passes telegram updates to `self.handle_update`.""" def __init__(self, hass, bot, config, secret_token): - """Create Dispatcher before calling super().""" + """Create Application before calling super().""" self.bot = bot self.trusted_networks = config[CONF_TRUSTED_NETWORKS] self.secret_token = secret_token - # Dumb dispatcher that just gets our updates to our handler callback (self.handle_update) - self.dispatcher = Dispatcher(bot, None) - self.dispatcher.add_handler(TypeHandler(Update, self.handle_update)) + # Dumb Application that just gets our updates to our handler callback (self.handle_update) + self.application = Application.builder().bot(bot).updater(None).build() + self.application.add_handler(TypeHandler(Update, self.handle_update)) super().__init__(hass, config) self.base_url = config.get(CONF_URL) or get_url( @@ -71,15 +72,15 @@ class PushBot(BaseTelegramBotEntity): ) self.webhook_url = f"{self.base_url}{TELEGRAM_WEBHOOK_URL}" - def _try_to_set_webhook(self): + async def _try_to_set_webhook(self): _LOGGER.debug("Registering webhook URL: %s", self.webhook_url) retry_num = 0 while retry_num < 3: try: - return self.bot.set_webhook( + return await self.bot.set_webhook( self.webhook_url, api_kwargs={"secret_token": self.secret_token}, - timeout=5, + connect_timeout=5, ) except TimedOut: retry_num += 1 @@ -87,11 +88,14 @@ class PushBot(BaseTelegramBotEntity): return False + async def start_application(self): + """Handle starting the Application object.""" + await self.application.initialize() + await self.application.start() + async def register_webhook(self): """Query telegram and register the URL for our webhook.""" - current_status = await self.hass.async_add_executor_job( - self.bot.get_webhook_info - ) + current_status = await self.bot.get_webhook_info() # Some logging of Bot current status: last_error_date = getattr(current_status, "last_error_date", None) if (last_error_date is not None) and (isinstance(last_error_date, int)): @@ -105,7 +109,7 @@ class PushBot(BaseTelegramBotEntity): _LOGGER.debug("telegram webhook status: %s", current_status) if current_status and current_status["url"] != self.webhook_url: - result = await self.hass.async_add_executor_job(self._try_to_set_webhook) + result = await self._try_to_set_webhook() if result: _LOGGER.info("Set new telegram webhook %s", self.webhook_url) else: @@ -114,10 +118,16 @@ class PushBot(BaseTelegramBotEntity): return True - def deregister_webhook(self, event=None): + async def stop_application(self, event=None): + """Handle gracefully stopping the Application object.""" + await self.deregister_webhook() + await self.application.stop() + await self.application.shutdown() + + async def deregister_webhook(self): """Query telegram and deregister the URL for our webhook.""" _LOGGER.debug("Deregistering webhook URL") - return self.bot.delete_webhook() + await self.bot.delete_webhook() class PushBotView(HomeAssistantView): @@ -127,11 +137,11 @@ class PushBotView(HomeAssistantView): url = TELEGRAM_WEBHOOK_URL name = "telegram_webhooks" - def __init__(self, hass, bot, dispatcher, trusted_networks, secret_token): + def __init__(self, hass, bot, application, trusted_networks, secret_token): """Initialize by storing stuff needed for setting up our webhook endpoint.""" self.hass = hass self.bot = bot - self.dispatcher = dispatcher + self.application = application self.trusted_networks = trusted_networks self.secret_token = secret_token @@ -153,6 +163,6 @@ class PushBotView(HomeAssistantView): update = Update.de_json(update_data, self.bot) _LOGGER.debug("Received Update on %s: %s", self.url, update) - await self.hass.async_add_executor_job(self.dispatcher.process_update, update) + await self.application.process_update(update) return None diff --git a/pyproject.toml b/pyproject.toml index e03e0fda88e..e27562d1a8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -508,8 +508,6 @@ filterwarnings = [ "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.miioprotocol", # https://github.com/hunterjm/python-onvif-zeep-async/pull/51 - >3.1.12 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:onvif.client", - # Fixed upstream in python-telegram-bot - >=20.0 - "ignore:python-telegram-bot is using upstream urllib3:UserWarning:telegram.utils.request", # https://github.com/xeniter/romy/pull/1 - >0.0.7 "ignore:with timeout\\(\\) is deprecated, use async with timeout\\(\\) instead:DeprecationWarning:romy.utils", # https://github.com/grahamwetzler/smart-meter-texas/pull/143 - >0.5.3 diff --git a/requirements_all.txt b/requirements_all.txt index 91404852ece..bc00afbfeab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -92,9 +92,6 @@ PyQRCode==1.2.1 # homeassistant.components.rmvtransport PyRMVtransport==0.3.3 -# homeassistant.components.telegram_bot -PySocks==1.7.1 - # homeassistant.components.switchbot PySwitchbot==0.45.0 @@ -2300,7 +2297,7 @@ python-tado==0.17.4 python-technove==1.2.2 # homeassistant.components.telegram_bot -python-telegram-bot==13.1 +python-telegram-bot[socks]==21.0.1 # homeassistant.components.vlc python-vlc==3.0.18122 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0cb0228e201..63b063badd9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -80,9 +80,6 @@ PyQRCode==1.2.1 # homeassistant.components.rmvtransport PyRMVtransport==0.3.3 -# homeassistant.components.telegram_bot -PySocks==1.7.1 - # homeassistant.components.switchbot PySwitchbot==0.45.0 @@ -1773,7 +1770,7 @@ python-tado==0.17.4 python-technove==1.2.2 # homeassistant.components.telegram_bot -python-telegram-bot==13.1 +python-telegram-bot[socks]==21.0.1 # homeassistant.components.tile pytile==2023.04.0 diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index af23efc1afc..f607ed468c5 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -2,13 +2,19 @@ from unittest.mock import patch import pytest +from telegram import User from homeassistant.components.telegram_bot import ( CONF_ALLOWED_CHAT_IDS, CONF_TRUSTED_NETWORKS, DOMAIN, ) -from homeassistant.const import CONF_API_KEY, CONF_PLATFORM, CONF_URL +from homeassistant.const import ( + CONF_API_KEY, + CONF_PLATFORM, + CONF_URL, + EVENT_HOMEASSISTANT_START, +) from homeassistant.setup import async_setup_component @@ -65,6 +71,23 @@ def mock_register_webhook(): yield +@pytest.fixture +def mock_external_calls(): + """Mock calls that make calls to the live Telegram API.""" + test_user = User(123456, "Testbot", True) + with patch( + "telegram.Bot.get_me", + return_value=test_user, + ), patch( + "telegram.Bot._bot_user", + test_user, + ), patch( + "telegram.Bot.bot", + test_user, + ), patch("telegram.ext.Updater._bootstrap"): + yield + + @pytest.fixture def mock_generate_secret_token(): """Mock secret token generated for webhook.""" @@ -174,7 +197,11 @@ def update_callback_query(): @pytest.fixture async def webhook_platform( - hass, config_webhooks, mock_register_webhook, mock_generate_secret_token + hass, + config_webhooks, + mock_register_webhook, + mock_external_calls, + mock_generate_secret_token, ): """Fixture for setting up the webhooks platform using appropriate config and mocks.""" await async_setup_component( @@ -183,14 +210,18 @@ async def webhook_platform( config_webhooks, ) await hass.async_block_till_done() + yield + await hass.async_stop() @pytest.fixture -async def polling_platform(hass, config_polling): +async def polling_platform(hass, config_polling, mock_external_calls): """Fixture for setting up the polling platform using appropriate config and mocks.""" await async_setup_component( hass, DOMAIN, config_polling, ) + # Fire this event to start polling + hass.bus.fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index be28f7be636..f86f70e77c1 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -1,25 +1,17 @@ """Tests for the telegram_bot component.""" -import pytest +from unittest.mock import AsyncMock, patch + from telegram import Update -from telegram.ext.dispatcher import Dispatcher from homeassistant.components.telegram_bot import DOMAIN, SERVICE_SEND_MESSAGE from homeassistant.components.telegram_bot.webhooks import TELEGRAM_WEBHOOK_URL from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component from tests.common import async_capture_events from tests.typing import ClientSessionGenerator -@pytest.fixture(autouse=True) -def clear_dispatcher(): - """Clear the singleton that telegram.ext.dispatcher.Dispatcher sets on itself.""" - yield - Dispatcher._set_singleton(None) - # This is how python-telegram-bot resets the dispatcher in their test suite - Dispatcher._Dispatcher__singleton_semaphore.release() - - async def test_webhook_platform_init(hass: HomeAssistant, webhook_platform) -> None: """Test initialization of the webhooks platform.""" assert hass.services.has_service(DOMAIN, SERVICE_SEND_MESSAGE) is True @@ -109,18 +101,38 @@ async def test_webhook_endpoint_generates_telegram_callback_event( async def test_polling_platform_message_text_update( - hass: HomeAssistant, polling_platform, update_message_text + hass: HomeAssistant, config_polling, update_message_text ) -> None: - """Provide the `PollBot`s `Dispatcher` with an `Update` and assert fired `telegram_text` event.""" + """Provide the `BaseTelegramBotEntity.update_handler` with an `Update` and assert fired `telegram_text` event.""" events = async_capture_events(hass, "telegram_text") - def telegram_dispatcher_callback(): - dispatcher = Dispatcher.get_instance() - update = Update.de_json(update_message_text, dispatcher.bot) - dispatcher.process_update(update) + with patch( + "homeassistant.components.telegram_bot.polling.ApplicationBuilder" + ) as application_builder_class: + await async_setup_component( + hass, + DOMAIN, + config_polling, + ) + await hass.async_block_till_done() + # Set up the integration with the polling platform inside the patch context manager. + application = ( + application_builder_class.return_value.bot.return_value.build.return_value + ) + # Then call the callback and assert events fired. + handler = application.add_handler.call_args[0][0] + handle_update_callback = handler.callback - # python-telegram-bots `Updater` uses threading, so we need to schedule its callback in a sync context. - await hass.async_add_executor_job(telegram_dispatcher_callback) + # Create Update object using library API. + application.bot.defaults.tzinfo = None + update = Update.de_json(update_message_text, application.bot) + + # handle_update_callback == BaseTelegramBotEntity.update_handler + await handle_update_callback(update, None) + + application.updater.stop = AsyncMock() + application.stop = AsyncMock() + application.shutdown = AsyncMock() # Make sure event has fired await hass.async_block_till_done() From 7dcf27596670f8a341082bef5a19a85cc467a71a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 7 Mar 2024 23:30:31 -1000 Subject: [PATCH 0530/1691] Speed up importing mqtt platforms (#112682) Use async_forward_entry_setups so platforms can be loaded in a single executor job instead of many: Currently they all have to create a new job because it did not use async_forward_entry_setups ``` 2024-03-08 08:29:29.819 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[light] loop=[] took 12.12s 2024-03-08 08:29:29.822 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[number] loop=[] took 12.12s 2024-03-08 08:29:29.826 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[scene] loop=[] took 12.13s 2024-03-08 08:29:29.829 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[select] loop=[] took 12.13s 2024-03-08 08:29:29.833 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[sensor] loop=[] took 12.14s 2024-03-08 08:29:30.882 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[update] loop=[] took 13.18s 2024-03-08 08:29:30.948 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[button] loop=[] took 13.18s 2024-03-08 08:29:30.949 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[climate] loop=[] took 13.19s 2024-03-08 08:29:31.012 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[fan] loop=[] took 13.25s 2024-03-08 08:29:31.019 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[humidifier] loop=[] took 13.25s 2024-03-08 08:29:31.024 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[image] loop=[] took 13.26s 2024-03-08 08:29:31.034 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[lock] loop=[] took 13.27s 2024-03-08 08:29:31.045 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[vacuum] loop=[] took 13.28s 2024-03-08 08:29:31.050 DEBUG (MainThread) [homeassistant.loader] Importing platforms for mqtt executor=[valve] loop=[] took 13.28s ``` --- homeassistant/components/mqtt/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 7da8636b19b..279dbc57c84 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -40,6 +40,7 @@ from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration +from homeassistant.util.async_ import create_eager_task # Loading the config flow file will register the flow from . import debug_info, discovery @@ -449,14 +450,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Forward the entry setup to the MQTT platforms await asyncio.gather( *( - [ - device_automation.async_setup_entry(hass, config_entry), - tag.async_setup_entry(hass, config_entry), - ] - + [ - hass.config_entries.async_forward_entry_setup(entry, component) - for component in PLATFORMS - ] + create_eager_task( + device_automation.async_setup_entry(hass, config_entry) + ), + create_eager_task(tag.async_setup_entry(hass, config_entry)), + create_eager_task( + hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + ), ) ) # Setup discovery From eb8f8e1ae46c5eb02f1f117dd3bc134a2cade1d7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:13:24 +0100 Subject: [PATCH 0531/1691] Use aiohttp.AppKey for http ban keys (#112657) --- homeassistant/components/http/ban.py | 31 ++++++++++++++++------------ tests/components/http/test_ban.py | 7 +++---- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index bec11d6e5ff..e5a65c2fe72 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -11,7 +11,14 @@ import logging from socket import gethostbyaddr, herror from typing import Any, Concatenate, Final, ParamSpec, TypeVar -from aiohttp.web import Application, Request, Response, StreamResponse, middleware +from aiohttp.web import ( + AppKey, + Application, + Request, + Response, + StreamResponse, + middleware, +) from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized import voluptuous as vol @@ -29,9 +36,11 @@ _P = ParamSpec("_P") _LOGGER: Final = logging.getLogger(__name__) -KEY_BAN_MANAGER: Final = "ha_banned_ips_manager" -KEY_FAILED_LOGIN_ATTEMPTS: Final = "ha_failed_login_attempts" -KEY_LOGIN_THRESHOLD: Final = "ha_login_threshold" +KEY_BAN_MANAGER = AppKey["IpBanManager"]("ha_banned_ips_manager") +KEY_FAILED_LOGIN_ATTEMPTS = AppKey[defaultdict[IPv4Address | IPv6Address, int]]( + "ha_failed_login_attempts" +) +KEY_LOGIN_THRESHOLD = AppKey[int]("ban_manager.ip_bans_lookup") NOTIFICATION_ID_BAN: Final = "ip-ban" NOTIFICATION_ID_LOGIN: Final = "http-login" @@ -48,7 +57,7 @@ SCHEMA_IP_BAN_ENTRY: Final = vol.Schema( def setup_bans(hass: HomeAssistant, app: Application, login_threshold: int) -> None: """Create IP Ban middleware for the app.""" app.middlewares.append(ban_middleware) - app[KEY_FAILED_LOGIN_ATTEMPTS] = defaultdict(int) + app[KEY_FAILED_LOGIN_ATTEMPTS] = defaultdict[IPv4Address | IPv6Address, int](int) app[KEY_LOGIN_THRESHOLD] = login_threshold app[KEY_BAN_MANAGER] = IpBanManager(hass) @@ -64,13 +73,11 @@ async def ban_middleware( request: Request, handler: Callable[[Request], Awaitable[StreamResponse]] ) -> StreamResponse: """IP Ban middleware.""" - ban_manager: IpBanManager | None = request.app.get(KEY_BAN_MANAGER) - if ban_manager is None: + if (ban_manager := request.app.get(KEY_BAN_MANAGER)) is None: _LOGGER.error("IP Ban middleware loaded but banned IPs not loaded") return await handler(request) - ip_bans_lookup = ban_manager.ip_bans_lookup - if ip_bans_lookup: + if ip_bans_lookup := ban_manager.ip_bans_lookup: # Verify if IP is not banned ip_address_ = ip_address(request.remote) # type: ignore[arg-type] if ip_address_ in ip_bans_lookup: @@ -154,7 +161,7 @@ async def process_wrong_login(request: Request) -> None: request.app[KEY_FAILED_LOGIN_ATTEMPTS][remote_addr] >= request.app[KEY_LOGIN_THRESHOLD] ): - ban_manager: IpBanManager = request.app[KEY_BAN_MANAGER] + ban_manager = request.app[KEY_BAN_MANAGER] _LOGGER.warning("Banned IP %s for too many login attempts", remote_addr) await ban_manager.async_add_ban(remote_addr) @@ -180,9 +187,7 @@ def process_success_login(request: Request) -> None: return remote_addr = ip_address(request.remote) # type: ignore[arg-type] - login_attempt_history: defaultdict[IPv4Address | IPv6Address, int] = app[ - KEY_FAILED_LOGIN_ATTEMPTS - ] + login_attempt_history = app[KEY_FAILED_LOGIN_ATTEMPTS] if remote_addr in login_attempt_history and login_attempt_history[remote_addr] > 0: _LOGGER.debug( "Login success, reset failed login attempts counter from %s", remote_addr diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index 26301cf5b79..5ab9db4e64e 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -15,7 +15,6 @@ from homeassistant.components.http.ban import ( IP_BANS_FILE, KEY_BAN_MANAGER, KEY_FAILED_LOGIN_ATTEMPTS, - IpBanManager, process_success_login, setup_bans, ) @@ -215,7 +214,7 @@ async def test_access_from_supervisor_ip( ): client = await aiohttp_client(app) - manager: IpBanManager = app[KEY_BAN_MANAGER] + manager = app[KEY_BAN_MANAGER] with patch( "homeassistant.components.hassio.HassIO.get_resolution_info", @@ -288,7 +287,7 @@ async def test_ip_bans_file_creation( ): client = await aiohttp_client(app) - manager: IpBanManager = app[KEY_BAN_MANAGER] + manager = app[KEY_BAN_MANAGER] m_open = mock_open() with patch("homeassistant.components.http.ban.open", m_open, create=True): @@ -408,7 +407,7 @@ async def test_single_ban_file_entry( setup_bans(hass, app, 2) mock_real_ip(app)("200.201.202.204") - manager: IpBanManager = app[KEY_BAN_MANAGER] + manager = app[KEY_BAN_MANAGER] m_open = mock_open() with patch("homeassistant.components.http.ban.open", m_open, create=True): From 4893087a7eb5ecdfd810cf8a23a05596f8b9396a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:14:16 +0100 Subject: [PATCH 0532/1691] Add TypeVar defaults for DataUpdateCoordinator (#111949) --- .../components/bluetooth/passive_update_coordinator.py | 5 ++++- homeassistant/components/zha/update.py | 4 +++- homeassistant/helpers/update_coordinator.py | 9 ++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/bluetooth/passive_update_coordinator.py b/homeassistant/components/bluetooth/passive_update_coordinator.py index fcf6fcdf255..0de1fadce30 100644 --- a/homeassistant/components/bluetooth/passive_update_coordinator.py +++ b/homeassistant/components/bluetooth/passive_update_coordinator.py @@ -1,7 +1,9 @@ """Passive update coordinator for the Bluetooth integration.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any + +from typing_extensions import TypeVar from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.update_coordinator import ( @@ -20,6 +22,7 @@ if TYPE_CHECKING: _PassiveBluetoothDataUpdateCoordinatorT = TypeVar( "_PassiveBluetoothDataUpdateCoordinatorT", bound="PassiveBluetoothDataUpdateCoordinator", + default="PassiveBluetoothDataUpdateCoordinator", ) diff --git a/homeassistant/components/zha/update.py b/homeassistant/components/zha/update.py index 12d89972380..b7b721766b8 100644 --- a/homeassistant/components/zha/update.py +++ b/homeassistant/components/zha/update.py @@ -93,7 +93,9 @@ class ZHAFirmwareUpdateCoordinator(DataUpdateCoordinator[None]): # pylint: disa @CONFIG_DIAGNOSTIC_MATCH(cluster_handler_names=CLUSTER_HANDLER_OTA) -class ZHAFirmwareUpdateEntity(ZhaEntity, CoordinatorEntity, UpdateEntity): +class ZHAFirmwareUpdateEntity( + ZhaEntity, CoordinatorEntity[ZHAFirmwareUpdateCoordinator], UpdateEntity +): """Representation of a ZHA firmware update entity.""" _unique_id_suffix = "firmware_update" diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 5fe2071e853..c3f2c60c8c3 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -8,11 +8,12 @@ from datetime import datetime, timedelta import logging from random import randint from time import monotonic -from typing import Any, Generic, Protocol, TypeVar +from typing import Any, Generic, Protocol import urllib.error import aiohttp import requests +from typing_extensions import TypeVar from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -37,12 +38,14 @@ from .debounce import Debouncer REQUEST_REFRESH_DEFAULT_COOLDOWN = 10 REQUEST_REFRESH_DEFAULT_IMMEDIATE = True -_DataT = TypeVar("_DataT") +_DataT = TypeVar("_DataT", default=dict[str, Any]) _BaseDataUpdateCoordinatorT = TypeVar( "_BaseDataUpdateCoordinatorT", bound="BaseDataUpdateCoordinatorProtocol" ) _DataUpdateCoordinatorT = TypeVar( - "_DataUpdateCoordinatorT", bound="DataUpdateCoordinator[Any]" + "_DataUpdateCoordinatorT", + bound="DataUpdateCoordinator[Any]", + default="DataUpdateCoordinator[dict[str, Any]]", ) From 9ba5159ae27e40def6e2a893b8281a46410913ae Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:30:39 +0100 Subject: [PATCH 0533/1691] Add reauthentication for husqvarna_automower (#109930) * Add reauthentication for husqvarna_automower * Remove unneded lines * Don't extract token on reauth * Update homeassistant/components/husqvarna_automower/config_flow.py Co-authored-by: Joost Lekkerkerker * Update tests/components/husqvarna_automower/conftest.py Co-authored-by: Joost Lekkerkerker * Use helper * Test if authentication is done with the right account * switch to ConfigFlowResult --------- Co-authored-by: Joost Lekkerkerker --- .../husqvarna_automower/__init__.py | 8 +- .../husqvarna_automower/config_flow.py | 25 ++- .../husqvarna_automower/strings.json | 7 +- .../husqvarna_automower/test_config_flow.py | 145 ++++++++++++++++++ .../husqvarna_automower/test_init.py | 2 +- 5 files changed, 181 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index 20218229385..473c67c4ea8 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -3,12 +3,12 @@ import logging from aioautomower.session import AutomowerSession -from aiohttp import ClientError +from aiohttp import ClientResponseError from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow from . import api @@ -35,7 +35,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: automower_api = AutomowerSession(api_api) try: await api_api.async_get_access_token() - except ClientError as err: + except ClientResponseError as err: + if 400 <= err.status < 500: + raise ConfigEntryAuthFailed from err raise ConfigEntryNotReady from err coordinator = AutomowerDataUpdateCoordinator(hass, automower_api, entry) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/husqvarna_automower/config_flow.py b/homeassistant/components/husqvarna_automower/config_flow.py index 11d3abb29a0..5ba0aeae154 100644 --- a/homeassistant/components/husqvarna_automower/config_flow.py +++ b/homeassistant/components/husqvarna_automower/config_flow.py @@ -1,10 +1,11 @@ """Config flow to add the integration via the UI.""" +from collections.abc import Mapping import logging from typing import Any from aioautomower.utils import async_structure_token -from homeassistant.config_entries import ConfigFlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN from homeassistant.helpers import config_entry_oauth2_flow @@ -22,11 +23,16 @@ class HusqvarnaConfigFlowHandler( VERSION = 1 DOMAIN = DOMAIN + reauth_entry: ConfigEntry | None = None async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow.""" token = data[CONF_TOKEN] user_id = token[CONF_USER_ID] + if self.reauth_entry: + if self.reauth_entry.unique_id != user_id: + return self.async_abort(reason="wrong_account") + return self.async_update_reload_and_abort(self.reauth_entry, data=data) structured_token = await async_structure_token(token[CONF_ACCESS_TOKEN]) first_name = structured_token.user.first_name last_name = structured_token.user.last_name @@ -41,3 +47,20 @@ class HusqvarnaConfigFlowHandler( def logger(self) -> logging.Logger: """Return logger.""" return logging.getLogger(__name__) + + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: + """Perform reauth upon an API authentication error.""" + self.reauth_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Confirm reauth dialog.""" + if user_input is None: + return self.async_show_form(step_id="reauth_confirm") + return await self.async_step_user() diff --git a/homeassistant/components/husqvarna_automower/strings.json b/homeassistant/components/husqvarna_automower/strings.json index d6017de2bd7..2d42172506d 100644 --- a/homeassistant/components/husqvarna_automower/strings.json +++ b/homeassistant/components/husqvarna_automower/strings.json @@ -1,6 +1,10 @@ { "config": { "step": { + "reauth_confirm": { + "title": "[%key:common::config_flow::title::reauth%]", + "description": "The Husqvarna Automower integration needs to re-authenticate your account" + }, "pick_implementation": { "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" } @@ -17,7 +21,8 @@ "user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]", "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]", - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "wrong_account": "You can only reauthenticate this entry with the same Husqvarna account." }, "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" diff --git a/tests/components/husqvarna_automower/test_config_flow.py b/tests/components/husqvarna_automower/test_config_flow.py index fcf9fbffa0c..e22ab7718ec 100644 --- a/tests/components/husqvarna_automower/test_config_flow.py +++ b/tests/components/husqvarna_automower/test_config_flow.py @@ -127,3 +127,148 @@ async def test_config_non_unique_profile( result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == FlowResultType.ABORT assert result["reason"] == "already_configured" + + +async def test_reauth( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + mock_config_entry: MockConfigEntry, + current_request_with_host: None, + mock_automower_client: AsyncMock, + jwt, +) -> None: + """Test the reauthentication case updates the existing config entry.""" + + mock_config_entry.add_to_hass(hass) + + mock_config_entry.async_start_reauth(hass) + await hass.async_block_till_done() + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + result = flows[0] + assert result["step_id"] == "reauth_confirm" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "access_token": "mock-updated-token", + "scope": "iam:read amc:api", + "expires_in": 86399, + "refresh_token": "mock-refresh-token", + "provider": "husqvarna", + "user_id": USER_ID, + "token_type": "Bearer", + "expires_at": 1697753347, + }, + ) + + with patch( + "homeassistant.components.husqvarna_automower.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + assert result.get("type") == "abort" + assert result.get("reason") == "reauth_successful" + + assert mock_config_entry.unique_id == USER_ID + assert "token" in mock_config_entry.data + # Verify access token is refreshed + assert mock_config_entry.data["token"].get("access_token") == "mock-updated-token" + assert mock_config_entry.data["token"].get("refresh_token") == "mock-refresh-token" + + +async def test_reauth_wrong_account( + hass: HomeAssistant, + hass_client_no_auth: ClientSessionGenerator, + aioclient_mock: AiohttpClientMocker, + mock_config_entry: MockConfigEntry, + current_request_with_host: None, + mock_automower_client: AsyncMock, + jwt, +) -> None: + """Test the reauthentication aborts, if user tries to reauthenticate with another account.""" + + mock_config_entry.add_to_hass(hass) + + mock_config_entry.async_start_reauth(hass) + await hass.async_block_till_done() + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + result = flows[0] + assert result["step_id"] == "reauth_confirm" + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + state = config_entry_oauth2_flow._encode_jwt( + hass, + { + "flow_id": result["flow_id"], + "redirect_uri": "https://example.com/auth/external/callback", + }, + ) + assert result["url"] == ( + f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}" + "&redirect_uri=https://example.com/auth/external/callback" + f"&state={state}" + ) + client = await hass_client_no_auth() + resp = await client.get(f"/auth/external/callback?code=abcd&state={state}") + assert resp.status == 200 + assert resp.headers["content-type"] == "text/html; charset=utf-8" + + aioclient_mock.post( + OAUTH2_TOKEN, + json={ + "access_token": "mock-updated-token", + "scope": "iam:read amc:api", + "expires_in": 86399, + "refresh_token": "mock-refresh-token", + "provider": "husqvarna", + "user_id": "wrong-user-id", + "token_type": "Bearer", + "expires_at": 1697753347, + }, + ) + + with patch( + "homeassistant.components.husqvarna_automower.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + assert result.get("type") == "abort" + assert result.get("reason") == "wrong_account" + + assert mock_config_entry.unique_id == USER_ID + assert "token" in mock_config_entry.data + # Verify access token is like before + assert mock_config_entry.data["token"].get("access_token") == jwt + assert ( + mock_config_entry.data["token"].get("refresh_token") + == "3012bc9f-7a65-4240-b817-9154ffdcc30f" + ) diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index c11e4ac4cc7..3fba90d7032 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -41,7 +41,7 @@ async def test_load_unload_entry( ( time.time() - 3600, http.HTTPStatus.UNAUTHORIZED, - ConfigEntryState.SETUP_RETRY, # Will trigger reauth in the future + ConfigEntryState.SETUP_ERROR, ), ( time.time() - 3600, From 9555e8764a1b819fe769e05d500dd480ac6bf1ad Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 8 Mar 2024 03:51:05 -0700 Subject: [PATCH 0534/1691] Bump `aionotion` to 2024.03.0 (#112675) --- homeassistant/components/notion/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/notion/manifest.json b/homeassistant/components/notion/manifest.json index 5fc94b5e646..d4d2cb579a3 100644 --- a/homeassistant/components/notion/manifest.json +++ b/homeassistant/components/notion/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "iot_class": "cloud_polling", "loggers": ["aionotion"], - "requirements": ["aionotion==2024.02.2"] + "requirements": ["aionotion==2024.03.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index bc00afbfeab..53757e2cf8c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -312,7 +312,7 @@ aiomusiccast==0.14.8 aionanoleaf==0.2.1 # homeassistant.components.notion -aionotion==2024.02.2 +aionotion==2024.03.0 # homeassistant.components.oncue aiooncue==0.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 63b063badd9..9c9039f26e5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -285,7 +285,7 @@ aiomusiccast==0.14.8 aionanoleaf==0.2.1 # homeassistant.components.notion -aionotion==2024.02.2 +aionotion==2024.03.0 # homeassistant.components.oncue aiooncue==0.3.5 From 2d701d5a7dcd6d1d29345d574af728277de0a158 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:51:59 +0100 Subject: [PATCH 0535/1691] Use aiohttp.AppKey for http cors keys (#112658) --- homeassistant/components/http/__init__.py | 7 ++++--- homeassistant/components/http/cors.py | 13 +++++++++---- homeassistant/helpers/http.py | 9 ++++++--- tests/components/http/test_cors.py | 3 ++- tests/components/http/test_data_validator.py | 3 ++- tests/components/http/test_static.py | 3 ++- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index d053e238e6e..51c98b080f0 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -33,6 +33,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.helpers.http import ( + KEY_ALLOW_CONFIGRED_CORS, KEY_AUTHENTICATED, # noqa: F401 KEY_HASS, HomeAssistantView, @@ -389,7 +390,7 @@ class HomeAssistantHTTP: # Should be instance of aiohttp.web_exceptions._HTTPMove. raise redirect_exc(redirect_to) # type: ignore[arg-type,misc] - self.app["allow_configured_cors"]( + self.app[KEY_ALLOW_CONFIGRED_CORS]( self.app.router.add_route("GET", url, redirect) ) @@ -405,7 +406,7 @@ class HomeAssistantHTTP: else: resource = web.StaticResource(url_path, path) self.app.router.register_resource(resource) - self.app["allow_configured_cors"](resource) + self.app[KEY_ALLOW_CONFIGRED_CORS](resource) return async def serve_file(request: web.Request) -> web.FileResponse: @@ -414,7 +415,7 @@ class HomeAssistantHTTP: return web.FileResponse(path, headers=CACHE_HEADERS) return web.FileResponse(path) - self.app["allow_configured_cors"]( + self.app[KEY_ALLOW_CONFIGRED_CORS]( self.app.router.add_route("GET", url_path, serve_file) ) diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index 7eb1a5f84fe..1bb5d2b5a6b 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -1,7 +1,7 @@ """Provide CORS support for the HTTP component.""" from __future__ import annotations -from typing import Final +from typing import Final, cast from aiohttp.hdrs import ACCEPT, AUTHORIZATION, CONTENT_TYPE, ORIGIN from aiohttp.web import Application @@ -15,6 +15,11 @@ from aiohttp.web_urldispatcher import ( from homeassistant.const import HTTP_HEADER_X_REQUESTED_WITH from homeassistant.core import callback +from homeassistant.helpers.http import ( + KEY_ALLOW_ALL_CORS, + KEY_ALLOW_CONFIGRED_CORS, + AllowCorsType, +) ALLOWED_CORS_HEADERS: Final[list[str]] = [ ORIGIN, @@ -70,7 +75,7 @@ def setup_cors(app: Application, origins: list[str]) -> None: cors.add(route, config) cors_added.add(path_str) - app["allow_all_cors"] = lambda route: _allow_cors( + app[KEY_ALLOW_ALL_CORS] = lambda route: _allow_cors( route, { "*": aiohttp_cors.ResourceOptions( @@ -80,6 +85,6 @@ def setup_cors(app: Application, origins: list[str]) -> None: ) if origins: - app["allow_configured_cors"] = _allow_cors + app[KEY_ALLOW_CONFIGRED_CORS] = cast(AllowCorsType, _allow_cors) else: - app["allow_configured_cors"] = lambda _: None + app[KEY_ALLOW_CONFIGRED_CORS] = lambda _: None diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index 6c92917f0bc..e0f7d59ae9a 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -16,7 +16,7 @@ from aiohttp.web_exceptions import ( HTTPInternalServerError, HTTPUnauthorized, ) -from aiohttp.web_urldispatcher import AbstractRoute +from aiohttp.web_urldispatcher import AbstractResource, AbstractRoute import voluptuous as vol from homeassistant import exceptions @@ -29,7 +29,10 @@ from .json import find_paths_unserializable_data, json_bytes, json_dumps _LOGGER = logging.getLogger(__name__) +AllowCorsType = Callable[[AbstractRoute | AbstractResource], None] KEY_AUTHENTICATED: Final = "ha_authenticated" +KEY_ALLOW_ALL_CORS = AppKey[AllowCorsType]("allow_all_cors") +KEY_ALLOW_CONFIGRED_CORS = AppKey[AllowCorsType]("allow_configured_cors") KEY_HASS: AppKey[HomeAssistant] = AppKey("hass") current_request: ContextVar[Request | None] = ContextVar( @@ -176,9 +179,9 @@ class HomeAssistantView: # Use `get` because CORS middleware is not be loaded in emulated_hue if self.cors_allowed: - allow_cors = app.get("allow_all_cors") + allow_cors = app.get(KEY_ALLOW_ALL_CORS) else: - allow_cors = app.get("allow_configured_cors") + allow_cors = app.get(KEY_ALLOW_CONFIGRED_CORS) if allow_cors: for route in routes: diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py index 738579ea190..f280c151b7d 100644 --- a/tests/components/http/test_cors.py +++ b/tests/components/http/test_cors.py @@ -17,6 +17,7 @@ import pytest from homeassistant.components.http.cors import setup_cors from homeassistant.components.http.view import HomeAssistantView from homeassistant.core import HomeAssistant +from homeassistant.helpers.http import KEY_ALLOW_CONFIGRED_CORS from homeassistant.setup import async_setup_component from . import HTTP_HEADER_HA_AUTH @@ -56,7 +57,7 @@ def client(event_loop, aiohttp_client): """Fixture to set up a web.Application.""" app = web.Application() setup_cors(app, [TRUSTED_ORIGIN]) - app["allow_configured_cors"](app.router.add_get("/", mock_handler)) + app[KEY_ALLOW_CONFIGRED_CORS](app.router.add_get("/", mock_handler)) return event_loop.run_until_complete(aiohttp_client(app)) diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index c9f1ed46157..3e18bdb4783 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.helpers.http import KEY_ALLOW_CONFIGRED_CORS from tests.typing import ClientSessionGenerator @@ -15,7 +16,7 @@ async def get_client(aiohttp_client, validator): """Generate a client that hits a view decorated with validator.""" app = web.Application() app[KEY_HASS] = Mock(is_stopping=False) - app["allow_configured_cors"] = lambda _: None + app[KEY_ALLOW_CONFIGRED_CORS] = lambda _: None class TestView(HomeAssistantView): url = "/" diff --git a/tests/components/http/test_static.py b/tests/components/http/test_static.py index b11d54defd6..7f6504bdb7c 100644 --- a/tests/components/http/test_static.py +++ b/tests/components/http/test_static.py @@ -9,6 +9,7 @@ import pytest from homeassistant.components.http.static import CachingStaticResource, _get_file_path from homeassistant.core import EVENT_HOMEASSISTANT_START, HomeAssistant +from homeassistant.helpers.http import KEY_ALLOW_CONFIGRED_CORS from homeassistant.setup import async_setup_component from tests.typing import ClientSessionGenerator @@ -49,7 +50,7 @@ async def test_static_path_blocks_anchors( resource = CachingStaticResource(url, str(tmp_path)) assert resource.canonical == canonical_url app.router.register_resource(resource) - app["allow_configured_cors"](resource) + app[KEY_ALLOW_CONFIGRED_CORS](resource) resp = await mock_http_client.get(canonical_url, allow_redirects=False) assert resp.status == 403 From 6e807df22e64f2a5b0dee7452b73a57938681a30 Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Fri, 8 Mar 2024 11:55:18 +0100 Subject: [PATCH 0536/1691] Fix incorrect filtering of unsupported locales in bring-api (#112589) --- homeassistant/components/bring/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bring/manifest.json b/homeassistant/components/bring/manifest.json index d8bfc6c7ebd..6b905a61b7d 100644 --- a/homeassistant/components/bring/manifest.json +++ b/homeassistant/components/bring/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/bring", "integration_type": "service", "iot_class": "cloud_polling", - "requirements": ["bring-api==0.5.5"] + "requirements": ["bring-api==0.5.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 53757e2cf8c..07e276f7c2f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -600,7 +600,7 @@ boschshcpy==0.2.75 boto3==1.33.13 # homeassistant.components.bring -bring-api==0.5.5 +bring-api==0.5.6 # homeassistant.components.broadlink broadlink==0.18.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c9039f26e5..5646c80699f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -511,7 +511,7 @@ bond-async==0.2.1 boschshcpy==0.2.75 # homeassistant.components.bring -bring-api==0.5.5 +bring-api==0.5.6 # homeassistant.components.broadlink broadlink==0.18.3 From 7926a76da577009321f3b588b245ed02a90918b8 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 8 Mar 2024 12:00:48 +0100 Subject: [PATCH 0537/1691] Bump devcontainer to 1-3.12 (#108709) --- Dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 453b922cd0b..e60456f7b1f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11 +FROM mcr.microsoft.com/devcontainers/python:1-3.12 SHELL ["/bin/bash", "-o", "pipefail", "-c"] From f8b05a0a81b802dc91ec8da60d70d8b32cf03c7e Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Fri, 8 Mar 2024 11:11:17 +0000 Subject: [PATCH 0538/1691] Update systembridgeconnector to version 4.0.3 (#112608) --- homeassistant/components/system_bridge/config_flow.py | 3 ++- homeassistant/components/system_bridge/coordinator.py | 7 +++---- homeassistant/components/system_bridge/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/system_bridge/config_flow.py b/homeassistant/components/system_bridge/config_flow.py index 0042d9c647e..1406088f3d1 100644 --- a/homeassistant/components/system_bridge/config_flow.py +++ b/homeassistant/components/system_bridge/config_flow.py @@ -61,11 +61,12 @@ async def _validate_input( data[CONF_HOST], data[CONF_PORT], data[CONF_TOKEN], + session=async_get_clientsession(hass), ) try: async with asyncio.timeout(15): - await websocket_client.connect(session=async_get_clientsession(hass)) + await websocket_client.connect() hass.async_create_task( websocket_client.listen(callback=_async_handle_module) ) diff --git a/homeassistant/components/system_bridge/coordinator.py b/homeassistant/components/system_bridge/coordinator.py index ca475dc5863..df0a39a877d 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -55,6 +55,7 @@ class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[SystemBridgeData]) entry.data[CONF_HOST], entry.data[CONF_PORT], entry.data[CONF_TOKEN], + session=async_get_clientsession(hass), ) super().__init__( @@ -106,7 +107,7 @@ class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[SystemBridgeData]) self, base: str, path: str, - ) -> MediaFile: + ) -> MediaFile | None: """Get media file.""" return await self.websocket_client.get_file( MediaGetFile( @@ -168,9 +169,7 @@ class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[SystemBridgeData]) """Use WebSocket for updates.""" try: async with asyncio.timeout(20): - await self.websocket_client.connect( - session=async_get_clientsession(self.hass), - ) + await self.websocket_client.connect() self.hass.async_create_background_task( self._listen_for_data(), diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index 8355935162d..b4365fda778 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -10,6 +10,6 @@ "iot_class": "local_push", "loggers": ["systembridgeconnector"], "quality_scale": "silver", - "requirements": ["systembridgeconnector==4.0.2"], + "requirements": ["systembridgeconnector==4.0.3"], "zeroconf": ["_system-bridge._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 07e276f7c2f..f38f474b1cd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2647,7 +2647,7 @@ switchbot-api==2.0.0 synology-srm==0.2.0 # homeassistant.components.system_bridge -systembridgeconnector==4.0.2 +systembridgeconnector==4.0.3 # homeassistant.components.tailscale tailscale==0.6.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5646c80699f..c4a038e88f5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2036,7 +2036,7 @@ surepy==0.9.0 switchbot-api==2.0.0 # homeassistant.components.system_bridge -systembridgeconnector==4.0.2 +systembridgeconnector==4.0.3 # homeassistant.components.tailscale tailscale==0.6.0 From d7e7dc96cc7f79d15095704f78869ba860a5042a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:46:16 +0100 Subject: [PATCH 0539/1691] Make Event data generic (#111955) --- .../components/history/websocket_api.py | 2 +- homeassistant/components/logbook/helpers.py | 2 +- homeassistant/core.py | 30 +++++++------------ homeassistant/helpers/typing.py | 9 ++---- 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/history/websocket_api.py b/homeassistant/components/history/websocket_api.py index f903a9904a9..dc249ddb035 100644 --- a/homeassistant/components/history/websocket_api.py +++ b/homeassistant/components/history/websocket_api.py @@ -359,7 +359,7 @@ async def _async_events_consumer( def _async_subscribe_events( hass: HomeAssistant, subscriptions: list[CALLBACK_TYPE], - target: Callable[[Event], None], + target: Callable[[Event[Any]], None], entity_ids: list[str], significant_changes_only: bool, minimal_response: bool, diff --git a/homeassistant/components/logbook/helpers.py b/homeassistant/components/logbook/helpers.py index b7293087e7e..68fa5794618 100644 --- a/homeassistant/components/logbook/helpers.py +++ b/homeassistant/components/logbook/helpers.py @@ -161,7 +161,7 @@ def event_forwarder_filtered( def async_subscribe_events( hass: HomeAssistant, subscriptions: list[CALLBACK_TYPE], - target: Callable[[Event], None], + target: Callable[[Event[Any]], None], event_types: tuple[str, ...], entities_filter: Callable[[str], bool] | None, entity_ids: list[str] | None, diff --git a/homeassistant/core.py b/homeassistant/core.py index b78be4ff3ad..4771213e0d3 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -30,19 +30,10 @@ import re import threading import time from time import monotonic -from typing import ( - TYPE_CHECKING, - Any, - Generic, - Literal, - ParamSpec, - Self, - TypeVar, - cast, - overload, -) +from typing import TYPE_CHECKING, Any, Generic, Literal, ParamSpec, Self, cast, overload from urllib.parse import urlparse +from typing_extensions import TypeVar import voluptuous as vol import yarl @@ -133,6 +124,7 @@ _P = ParamSpec("_P") # Internal; not helpers.typing.UNDEFINED due to circular dependency _UNDEF: dict[Any, Any] = {} _CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) +_DataT = TypeVar("_DataT", bound=Mapping[str, Any], default=dict[str, Any]) CALLBACK_TYPE = Callable[[], None] CORE_STORAGE_KEY = "core.config" @@ -1164,7 +1156,7 @@ class Context: self.id = id or ulid_now() self.user_id = user_id self.parent_id = parent_id - self.origin_event: Event | None = None + self.origin_event: Event[Any] | None = None def __eq__(self, other: Any) -> bool: """Compare contexts.""" @@ -1209,20 +1201,20 @@ class EventOrigin(enum.Enum): return self.value -class Event: +class Event(Generic[_DataT]): """Representation of an event within the bus.""" def __init__( self, event_type: str, - data: Mapping[str, Any] | None = None, + data: _DataT | None = None, origin: EventOrigin = EventOrigin.local, time_fired: datetime.datetime | None = None, context: Context | None = None, ) -> None: """Initialize a new event.""" self.event_type = event_type - self.data = data or {} + self.data: _DataT = data or {} # type: ignore[assignment] self.origin = origin self.time_fired = time_fired or dt_util.utcnow() if not context: @@ -1294,8 +1286,8 @@ class Event: _FilterableJobType = tuple[ - HassJob[[Event], Coroutine[Any, Any, None] | None], # job - Callable[[Event], bool] | None, # event_filter + HassJob[[Event[_DataT]], Coroutine[Any, Any, None] | None], # job + Callable[[Event[_DataT]], bool] | None, # event_filter bool, # run_immediately ] @@ -1331,8 +1323,8 @@ class EventBus: def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" - self._listeners: dict[str, list[_FilterableJobType]] = {} - self._match_all_listeners: list[_FilterableJobType] = [] + self._listeners: dict[str, list[_FilterableJobType[Any]]] = {} + self._match_all_listeners: list[_FilterableJobType[Any]] = [] self._listeners[MATCH_ALL] = self._match_all_listeners self._hass = hass diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index 9e3f9de34fa..4b94e153aef 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -1,7 +1,7 @@ """Typing Helpers for Home Assistant.""" from collections.abc import Mapping from enum import Enum -from typing import Any, Generic, TypeVar +from typing import Any, TypeVar import homeassistant.core @@ -33,11 +33,6 @@ UNDEFINED = UndefinedType._singleton # pylint: disable=protected-access # They are kept in order not to break custom integrations # that may rely on them. # In due time they will be removed. +EventType = homeassistant.core.Event HomeAssistantType = homeassistant.core.HomeAssistant ServiceCallType = homeassistant.core.ServiceCall - - -class EventType(homeassistant.core.Event, Generic[_DataT]): - """Generic Event class to better type data.""" - - data: _DataT # type: ignore[assignment] From 0e3945ca6c4c28c7c20901141f44ee83eb3a54c7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:15:26 +0100 Subject: [PATCH 0540/1691] Add empty line after module docstring [d-f] (#112698) --- homeassistant/components/daikin/__init__.py | 1 + homeassistant/components/daikin/climate.py | 1 + homeassistant/components/daikin/config_flow.py | 1 + homeassistant/components/daikin/sensor.py | 1 + homeassistant/components/daikin/switch.py | 1 + homeassistant/components/danfoss_air/__init__.py | 1 + homeassistant/components/danfoss_air/binary_sensor.py | 1 + homeassistant/components/danfoss_air/sensor.py | 1 + homeassistant/components/danfoss_air/switch.py | 1 + homeassistant/components/date/__init__.py | 1 + homeassistant/components/datetime/__init__.py | 1 + homeassistant/components/ddwrt/device_tracker.py | 1 + homeassistant/components/debugpy/__init__.py | 1 + homeassistant/components/deconz/alarm_control_panel.py | 1 + homeassistant/components/deconz/binary_sensor.py | 1 + homeassistant/components/deconz/climate.py | 1 + homeassistant/components/deconz/cover.py | 1 + homeassistant/components/deconz/device_trigger.py | 1 + homeassistant/components/deconz/diagnostics.py | 1 + homeassistant/components/deconz/errors.py | 1 + homeassistant/components/deconz/fan.py | 1 + homeassistant/components/deconz/light.py | 1 + homeassistant/components/deconz/logbook.py | 1 + homeassistant/components/deconz/siren.py | 1 + homeassistant/components/deconz/util.py | 1 + homeassistant/components/decora/light.py | 1 + homeassistant/components/decora_wifi/light.py | 1 + homeassistant/components/default_config/__init__.py | 1 + homeassistant/components/delijn/sensor.py | 1 + homeassistant/components/deluge/__init__.py | 1 + homeassistant/components/deluge/config_flow.py | 1 + homeassistant/components/deluge/coordinator.py | 1 + homeassistant/components/deluge/sensor.py | 1 + homeassistant/components/deluge/switch.py | 1 + homeassistant/components/demo/__init__.py | 1 + homeassistant/components/demo/air_quality.py | 1 + homeassistant/components/demo/alarm_control_panel.py | 1 + homeassistant/components/demo/binary_sensor.py | 1 + homeassistant/components/demo/button.py | 1 + homeassistant/components/demo/calendar.py | 1 + homeassistant/components/demo/camera.py | 1 + homeassistant/components/demo/climate.py | 1 + homeassistant/components/demo/config_flow.py | 1 + homeassistant/components/demo/cover.py | 1 + homeassistant/components/demo/date.py | 1 + homeassistant/components/demo/datetime.py | 1 + homeassistant/components/demo/device_tracker.py | 1 + homeassistant/components/demo/event.py | 1 + homeassistant/components/demo/fan.py | 1 + homeassistant/components/demo/geo_location.py | 1 + homeassistant/components/demo/humidifier.py | 1 + homeassistant/components/demo/image_processing.py | 1 + homeassistant/components/demo/light.py | 1 + homeassistant/components/demo/lock.py | 1 + homeassistant/components/demo/mailbox.py | 1 + homeassistant/components/demo/media_player.py | 1 + homeassistant/components/demo/notify.py | 1 + homeassistant/components/demo/number.py | 1 + homeassistant/components/demo/remote.py | 1 + homeassistant/components/demo/select.py | 1 + homeassistant/components/demo/sensor.py | 1 + homeassistant/components/demo/siren.py | 1 + homeassistant/components/demo/stt.py | 1 + homeassistant/components/demo/switch.py | 1 + homeassistant/components/demo/text.py | 1 + homeassistant/components/demo/time.py | 1 + homeassistant/components/demo/tts.py | 1 + homeassistant/components/demo/update.py | 1 + homeassistant/components/demo/vacuum.py | 1 + homeassistant/components/demo/water_heater.py | 1 + homeassistant/components/demo/weather.py | 1 + homeassistant/components/denon/media_player.py | 1 + homeassistant/components/denonavr/config_flow.py | 1 + homeassistant/components/denonavr/media_player.py | 1 + homeassistant/components/denonavr/receiver.py | 1 + homeassistant/components/derivative/__init__.py | 1 + homeassistant/components/derivative/config_flow.py | 1 + homeassistant/components/derivative/sensor.py | 1 + homeassistant/components/devialet/__init__.py | 1 + homeassistant/components/devialet/config_flow.py | 1 + homeassistant/components/devialet/const.py | 1 + homeassistant/components/devialet/coordinator.py | 1 + homeassistant/components/devialet/diagnostics.py | 1 + homeassistant/components/devialet/media_player.py | 1 + homeassistant/components/device_automation/__init__.py | 1 + homeassistant/components/device_automation/action.py | 1 + homeassistant/components/device_automation/condition.py | 1 + homeassistant/components/device_automation/entity.py | 1 + homeassistant/components/device_automation/exceptions.py | 1 + homeassistant/components/device_automation/helpers.py | 1 + homeassistant/components/device_automation/toggle_entity.py | 1 + homeassistant/components/device_automation/trigger.py | 1 + homeassistant/components/device_sun_light_trigger/__init__.py | 1 + homeassistant/components/device_tracker/__init__.py | 1 + homeassistant/components/device_tracker/config_entry.py | 1 + homeassistant/components/device_tracker/const.py | 1 + homeassistant/components/device_tracker/device_condition.py | 1 + homeassistant/components/device_tracker/device_trigger.py | 1 + homeassistant/components/device_tracker/legacy.py | 1 + homeassistant/components/devolo_home_control/__init__.py | 1 + homeassistant/components/devolo_home_control/binary_sensor.py | 1 + homeassistant/components/devolo_home_control/climate.py | 1 + homeassistant/components/devolo_home_control/config_flow.py | 1 + homeassistant/components/devolo_home_control/cover.py | 1 + homeassistant/components/devolo_home_control/devolo_device.py | 1 + .../components/devolo_home_control/devolo_multi_level_switch.py | 1 + homeassistant/components/devolo_home_control/diagnostics.py | 1 + homeassistant/components/devolo_home_control/exceptions.py | 1 + homeassistant/components/devolo_home_control/light.py | 1 + homeassistant/components/devolo_home_control/sensor.py | 1 + homeassistant/components/devolo_home_control/siren.py | 1 + homeassistant/components/devolo_home_control/subscriber.py | 1 + homeassistant/components/devolo_home_control/switch.py | 1 + homeassistant/components/devolo_home_network/__init__.py | 1 + homeassistant/components/devolo_home_network/binary_sensor.py | 1 + homeassistant/components/devolo_home_network/button.py | 1 + homeassistant/components/devolo_home_network/config_flow.py | 1 + homeassistant/components/devolo_home_network/device_tracker.py | 1 + homeassistant/components/devolo_home_network/diagnostics.py | 1 + homeassistant/components/devolo_home_network/entity.py | 1 + homeassistant/components/devolo_home_network/image.py | 1 + homeassistant/components/devolo_home_network/sensor.py | 1 + homeassistant/components/devolo_home_network/switch.py | 1 + homeassistant/components/devolo_home_network/update.py | 1 + homeassistant/components/dexcom/__init__.py | 1 + homeassistant/components/dexcom/config_flow.py | 1 + homeassistant/components/dexcom/const.py | 1 + homeassistant/components/dexcom/sensor.py | 1 + homeassistant/components/dhcp/__init__.py | 1 + homeassistant/components/diagnostics/__init__.py | 1 + homeassistant/components/diagnostics/const.py | 1 + homeassistant/components/diagnostics/util.py | 1 + homeassistant/components/dialogflow/config_flow.py | 1 + homeassistant/components/digital_ocean/__init__.py | 1 + homeassistant/components/digital_ocean/binary_sensor.py | 1 + homeassistant/components/digital_ocean/switch.py | 1 + homeassistant/components/directv/__init__.py | 1 + homeassistant/components/directv/config_flow.py | 1 + homeassistant/components/directv/entity.py | 1 + homeassistant/components/directv/media_player.py | 1 + homeassistant/components/directv/remote.py | 1 + homeassistant/components/discogs/sensor.py | 1 + homeassistant/components/discord/__init__.py | 1 + homeassistant/components/discord/config_flow.py | 1 + homeassistant/components/discord/notify.py | 1 + homeassistant/components/discovergy/__init__.py | 1 + homeassistant/components/discovergy/config_flow.py | 1 + homeassistant/components/discovergy/const.py | 1 + homeassistant/components/discovergy/coordinator.py | 1 + homeassistant/components/discovergy/diagnostics.py | 1 + homeassistant/components/discovergy/sensor.py | 1 + homeassistant/components/discovergy/system_health.py | 1 + homeassistant/components/dlib_face_detect/image_processing.py | 1 + homeassistant/components/dlib_face_identify/image_processing.py | 1 + homeassistant/components/dlink/__init__.py | 1 + homeassistant/components/dlink/config_flow.py | 1 + homeassistant/components/dlink/data.py | 1 + homeassistant/components/dlink/entity.py | 1 + homeassistant/components/dlink/switch.py | 1 + homeassistant/components/dlna_dmr/__init__.py | 1 + homeassistant/components/dlna_dmr/config_flow.py | 1 + homeassistant/components/dlna_dmr/const.py | 1 + homeassistant/components/dlna_dmr/data.py | 1 + homeassistant/components/dlna_dmr/media_player.py | 1 + homeassistant/components/dlna_dms/__init__.py | 1 + homeassistant/components/dlna_dms/config_flow.py | 1 + homeassistant/components/dlna_dms/const.py | 1 + homeassistant/components/dlna_dms/dms.py | 1 + homeassistant/components/dlna_dms/util.py | 1 + homeassistant/components/dnsip/__init__.py | 1 + homeassistant/components/dnsip/config_flow.py | 1 + homeassistant/components/dnsip/const.py | 1 + homeassistant/components/dnsip/sensor.py | 1 + homeassistant/components/dominos/__init__.py | 1 + homeassistant/components/doods/image_processing.py | 1 + homeassistant/components/doorbird/__init__.py | 1 + homeassistant/components/doorbird/camera.py | 1 + homeassistant/components/doorbird/config_flow.py | 1 + homeassistant/components/doorbird/const.py | 1 + homeassistant/components/doorbird/device.py | 1 + homeassistant/components/doorbird/logbook.py | 1 + homeassistant/components/doorbird/models.py | 1 + homeassistant/components/doorbird/view.py | 1 + homeassistant/components/dormakaba_dkey/__init__.py | 1 + homeassistant/components/dormakaba_dkey/binary_sensor.py | 1 + homeassistant/components/dormakaba_dkey/config_flow.py | 1 + homeassistant/components/dormakaba_dkey/entity.py | 1 + homeassistant/components/dormakaba_dkey/lock.py | 1 + homeassistant/components/dormakaba_dkey/models.py | 1 + homeassistant/components/dormakaba_dkey/sensor.py | 1 + homeassistant/components/dovado/__init__.py | 1 + homeassistant/components/dovado/notify.py | 1 + homeassistant/components/dovado/sensor.py | 1 + homeassistant/components/downloader/__init__.py | 1 + homeassistant/components/dremel_3d_printer/__init__.py | 1 + homeassistant/components/dremel_3d_printer/binary_sensor.py | 1 + homeassistant/components/dremel_3d_printer/button.py | 1 + homeassistant/components/dremel_3d_printer/camera.py | 1 + homeassistant/components/dremel_3d_printer/config_flow.py | 1 + homeassistant/components/dremel_3d_printer/const.py | 1 + homeassistant/components/dremel_3d_printer/sensor.py | 1 + homeassistant/components/drop_connect/__init__.py | 1 + homeassistant/components/drop_connect/config_flow.py | 1 + homeassistant/components/drop_connect/coordinator.py | 1 + homeassistant/components/drop_connect/entity.py | 1 + homeassistant/components/drop_connect/sensor.py | 1 + homeassistant/components/dsmr/__init__.py | 1 + homeassistant/components/dsmr/config_flow.py | 1 + homeassistant/components/dsmr/const.py | 1 + homeassistant/components/dsmr/sensor.py | 1 + homeassistant/components/dsmr_reader/config_flow.py | 1 + homeassistant/components/dsmr_reader/definitions.py | 1 + homeassistant/components/dsmr_reader/sensor.py | 1 + homeassistant/components/dte_energy_bridge/sensor.py | 1 + homeassistant/components/dublin_bus_transport/sensor.py | 1 + homeassistant/components/duckdns/__init__.py | 1 + homeassistant/components/dunehd/__init__.py | 1 + homeassistant/components/dunehd/config_flow.py | 1 + homeassistant/components/dunehd/const.py | 1 + homeassistant/components/dunehd/media_player.py | 1 + homeassistant/components/duotecno/__init__.py | 1 + homeassistant/components/duotecno/binary_sensor.py | 1 + homeassistant/components/duotecno/climate.py | 1 + homeassistant/components/duotecno/config_flow.py | 1 + homeassistant/components/duotecno/const.py | 1 + homeassistant/components/duotecno/cover.py | 1 + homeassistant/components/duotecno/entity.py | 1 + homeassistant/components/duotecno/light.py | 1 + homeassistant/components/duotecno/switch.py | 1 + homeassistant/components/dweet/__init__.py | 1 + homeassistant/components/dweet/sensor.py | 1 + homeassistant/components/dynalite/__init__.py | 1 + homeassistant/components/dynalite/bridge.py | 1 + homeassistant/components/dynalite/config_flow.py | 1 + homeassistant/components/dynalite/convert_config.py | 1 + homeassistant/components/dynalite/dynalitebase.py | 1 + homeassistant/components/eafm/config_flow.py | 1 + homeassistant/components/easyenergy/__init__.py | 1 + homeassistant/components/easyenergy/config_flow.py | 1 + homeassistant/components/easyenergy/const.py | 1 + homeassistant/components/easyenergy/coordinator.py | 1 + homeassistant/components/easyenergy/diagnostics.py | 1 + homeassistant/components/easyenergy/sensor.py | 1 + homeassistant/components/easyenergy/services.py | 1 + homeassistant/components/ebox/sensor.py | 1 + homeassistant/components/ebusd/const.py | 1 + homeassistant/components/ebusd/sensor.py | 1 + homeassistant/components/ecoal_boiler/sensor.py | 1 + homeassistant/components/ecoal_boiler/switch.py | 1 + homeassistant/components/ecobee/__init__.py | 1 + homeassistant/components/ecobee/binary_sensor.py | 1 + homeassistant/components/ecobee/climate.py | 1 + homeassistant/components/ecobee/config_flow.py | 1 + homeassistant/components/ecobee/entity.py | 1 + homeassistant/components/ecobee/humidifier.py | 1 + homeassistant/components/ecobee/notify.py | 1 + homeassistant/components/ecobee/number.py | 1 + homeassistant/components/ecobee/sensor.py | 1 + homeassistant/components/ecobee/util.py | 1 + homeassistant/components/ecobee/weather.py | 1 + homeassistant/components/ecoforest/__init__.py | 1 + homeassistant/components/ecoforest/config_flow.py | 1 + homeassistant/components/ecoforest/entity.py | 1 + homeassistant/components/ecoforest/number.py | 1 + homeassistant/components/ecoforest/sensor.py | 1 + homeassistant/components/ecoforest/switch.py | 1 + homeassistant/components/econet/binary_sensor.py | 1 + homeassistant/components/econet/climate.py | 1 + homeassistant/components/econet/config_flow.py | 1 + homeassistant/components/econet/sensor.py | 1 + homeassistant/components/econet/water_heater.py | 1 + homeassistant/components/ecovacs/binary_sensor.py | 1 + homeassistant/components/ecovacs/button.py | 1 + homeassistant/components/ecovacs/config_flow.py | 1 + homeassistant/components/ecovacs/const.py | 1 + homeassistant/components/ecovacs/controller.py | 1 + homeassistant/components/ecovacs/diagnostics.py | 1 + homeassistant/components/ecovacs/entity.py | 1 + homeassistant/components/ecovacs/number.py | 1 + homeassistant/components/ecovacs/select.py | 1 + homeassistant/components/ecovacs/sensor.py | 1 + homeassistant/components/ecovacs/switch.py | 1 + homeassistant/components/ecovacs/util.py | 1 + homeassistant/components/ecovacs/vacuum.py | 1 + homeassistant/components/ecowitt/__init__.py | 1 + homeassistant/components/ecowitt/config_flow.py | 1 + homeassistant/components/ecowitt/diagnostics.py | 1 + homeassistant/components/ecowitt/entity.py | 1 + homeassistant/components/ecowitt/sensor.py | 1 + homeassistant/components/eddystone_temperature/sensor.py | 1 + homeassistant/components/edimax/switch.py | 1 + homeassistant/components/edl21/sensor.py | 1 + homeassistant/components/efergy/__init__.py | 1 + homeassistant/components/efergy/config_flow.py | 1 + homeassistant/components/efergy/const.py | 1 + homeassistant/components/efergy/sensor.py | 1 + homeassistant/components/egardia/alarm_control_panel.py | 1 + homeassistant/components/egardia/binary_sensor.py | 1 + homeassistant/components/eight_sleep/__init__.py | 1 + homeassistant/components/electrasmart/__init__.py | 1 + homeassistant/components/electrasmart/config_flow.py | 1 + homeassistant/components/electric_kiwi/__init__.py | 1 + homeassistant/components/electric_kiwi/config_flow.py | 1 + homeassistant/components/electric_kiwi/oauth2.py | 1 + homeassistant/components/electric_kiwi/select.py | 1 + homeassistant/components/electric_kiwi/sensor.py | 1 + homeassistant/components/elgato/__init__.py | 1 + homeassistant/components/elgato/button.py | 1 + homeassistant/components/elgato/config_flow.py | 1 + homeassistant/components/elgato/const.py | 1 + homeassistant/components/elgato/coordinator.py | 1 + homeassistant/components/elgato/diagnostics.py | 1 + homeassistant/components/elgato/entity.py | 1 + homeassistant/components/elgato/light.py | 1 + homeassistant/components/elgato/sensor.py | 1 + homeassistant/components/elgato/switch.py | 1 + homeassistant/components/eliqonline/sensor.py | 1 + homeassistant/components/elkm1/__init__.py | 1 + homeassistant/components/elkm1/alarm_control_panel.py | 1 + homeassistant/components/elkm1/binary_sensor.py | 1 + homeassistant/components/elkm1/climate.py | 1 + homeassistant/components/elkm1/config_flow.py | 1 + homeassistant/components/elkm1/discovery.py | 1 + homeassistant/components/elkm1/light.py | 1 + homeassistant/components/elkm1/logbook.py | 1 + homeassistant/components/elkm1/models.py | 1 + homeassistant/components/elkm1/scene.py | 1 + homeassistant/components/elkm1/sensor.py | 1 + homeassistant/components/elkm1/switch.py | 1 + homeassistant/components/elmax/__init__.py | 1 + homeassistant/components/elmax/alarm_control_panel.py | 1 + homeassistant/components/elmax/binary_sensor.py | 1 + homeassistant/components/elmax/common.py | 1 + homeassistant/components/elmax/config_flow.py | 1 + homeassistant/components/elmax/const.py | 1 + homeassistant/components/elmax/cover.py | 1 + homeassistant/components/elv/switch.py | 1 + homeassistant/components/elvia/__init__.py | 1 + homeassistant/components/elvia/config_flow.py | 1 + homeassistant/components/elvia/const.py | 1 + homeassistant/components/elvia/importer.py | 1 + homeassistant/components/emby/media_player.py | 1 + homeassistant/components/emoncms/sensor.py | 1 + homeassistant/components/emoncms_history/__init__.py | 1 + homeassistant/components/emonitor/__init__.py | 1 + homeassistant/components/emonitor/sensor.py | 1 + homeassistant/components/emulated_hue/__init__.py | 1 + homeassistant/components/emulated_hue/config.py | 1 + homeassistant/components/emulated_hue/hue_api.py | 1 + homeassistant/components/emulated_hue/upnp.py | 1 + homeassistant/components/energy/__init__.py | 1 + homeassistant/components/energy/data.py | 1 + homeassistant/components/energy/sensor.py | 1 + homeassistant/components/energy/types.py | 1 + homeassistant/components/energy/validate.py | 1 + homeassistant/components/energy/websocket_api.py | 1 + homeassistant/components/energyzero/__init__.py | 1 + homeassistant/components/energyzero/config_flow.py | 1 + homeassistant/components/energyzero/const.py | 1 + homeassistant/components/energyzero/coordinator.py | 1 + homeassistant/components/energyzero/diagnostics.py | 1 + homeassistant/components/energyzero/sensor.py | 1 + homeassistant/components/energyzero/services.py | 1 + homeassistant/components/enigma2/media_player.py | 1 + homeassistant/components/enocean/binary_sensor.py | 1 + homeassistant/components/enocean/device.py | 1 + homeassistant/components/enocean/light.py | 1 + homeassistant/components/enocean/sensor.py | 1 + homeassistant/components/enocean/switch.py | 1 + homeassistant/components/enphase_envoy/__init__.py | 1 + homeassistant/components/enphase_envoy/binary_sensor.py | 1 + homeassistant/components/enphase_envoy/config_flow.py | 1 + homeassistant/components/enphase_envoy/const.py | 1 + homeassistant/components/enphase_envoy/coordinator.py | 1 + homeassistant/components/enphase_envoy/diagnostics.py | 1 + homeassistant/components/enphase_envoy/entity.py | 1 + homeassistant/components/enphase_envoy/number.py | 1 + homeassistant/components/enphase_envoy/select.py | 1 + homeassistant/components/enphase_envoy/sensor.py | 1 + homeassistant/components/enphase_envoy/switch.py | 1 + homeassistant/components/entur_public_transport/sensor.py | 1 + homeassistant/components/environment_canada/__init__.py | 1 + homeassistant/components/environment_canada/camera.py | 1 + homeassistant/components/environment_canada/diagnostics.py | 1 + homeassistant/components/environment_canada/sensor.py | 1 + homeassistant/components/environment_canada/weather.py | 1 + homeassistant/components/envisalink/alarm_control_panel.py | 1 + homeassistant/components/envisalink/binary_sensor.py | 1 + homeassistant/components/envisalink/sensor.py | 1 + homeassistant/components/envisalink/switch.py | 1 + homeassistant/components/ephember/climate.py | 1 + homeassistant/components/epion/__init__.py | 1 + homeassistant/components/epion/config_flow.py | 1 + homeassistant/components/epion/const.py | 1 + homeassistant/components/epion/sensor.py | 1 + homeassistant/components/epson/exceptions.py | 1 + homeassistant/components/epson/media_player.py | 1 + homeassistant/components/epsonworkforce/sensor.py | 1 + homeassistant/components/escea/climate.py | 1 + homeassistant/components/escea/discovery.py | 1 + homeassistant/components/esphome/__init__.py | 1 + homeassistant/components/esphome/alarm_control_panel.py | 1 + homeassistant/components/esphome/binary_sensor.py | 1 + homeassistant/components/esphome/bluetooth.py | 1 + homeassistant/components/esphome/button.py | 1 + homeassistant/components/esphome/camera.py | 1 + homeassistant/components/esphome/climate.py | 1 + homeassistant/components/esphome/config_flow.py | 1 + homeassistant/components/esphome/const.py | 1 + homeassistant/components/esphome/cover.py | 1 + homeassistant/components/esphome/dashboard.py | 1 + homeassistant/components/esphome/diagnostics.py | 1 + homeassistant/components/esphome/domain_data.py | 1 + homeassistant/components/esphome/entity.py | 1 + homeassistant/components/esphome/entry_data.py | 1 + homeassistant/components/esphome/fan.py | 1 + homeassistant/components/esphome/light.py | 1 + homeassistant/components/esphome/lock.py | 1 + homeassistant/components/esphome/manager.py | 1 + homeassistant/components/esphome/media_player.py | 1 + homeassistant/components/esphome/number.py | 1 + homeassistant/components/esphome/select.py | 1 + homeassistant/components/esphome/sensor.py | 1 + homeassistant/components/esphome/switch.py | 1 + homeassistant/components/esphome/text.py | 1 + homeassistant/components/esphome/update.py | 1 + homeassistant/components/etherscan/sensor.py | 1 + homeassistant/components/eufy/light.py | 1 + homeassistant/components/eufy/switch.py | 1 + homeassistant/components/eufylife_ble/__init__.py | 1 + homeassistant/components/eufylife_ble/config_flow.py | 1 + homeassistant/components/eufylife_ble/models.py | 1 + homeassistant/components/eufylife_ble/sensor.py | 1 + homeassistant/components/event/__init__.py | 1 + homeassistant/components/everlights/light.py | 1 + homeassistant/components/evil_genius_labs/__init__.py | 1 + homeassistant/components/evil_genius_labs/config_flow.py | 1 + homeassistant/components/evil_genius_labs/diagnostics.py | 1 + homeassistant/components/evil_genius_labs/light.py | 1 + homeassistant/components/evil_genius_labs/util.py | 1 + homeassistant/components/evohome/__init__.py | 1 + homeassistant/components/evohome/climate.py | 1 + homeassistant/components/evohome/water_heater.py | 1 + homeassistant/components/ezviz/alarm_control_panel.py | 1 + homeassistant/components/ezviz/binary_sensor.py | 1 + homeassistant/components/ezviz/button.py | 1 + homeassistant/components/ezviz/camera.py | 1 + homeassistant/components/ezviz/config_flow.py | 1 + homeassistant/components/ezviz/entity.py | 1 + homeassistant/components/ezviz/image.py | 1 + homeassistant/components/ezviz/light.py | 1 + homeassistant/components/ezviz/number.py | 1 + homeassistant/components/ezviz/select.py | 1 + homeassistant/components/ezviz/sensor.py | 1 + homeassistant/components/ezviz/siren.py | 1 + homeassistant/components/ezviz/switch.py | 1 + homeassistant/components/ezviz/update.py | 1 + homeassistant/components/faa_delays/__init__.py | 1 + homeassistant/components/faa_delays/binary_sensor.py | 1 + homeassistant/components/faa_delays/const.py | 1 + homeassistant/components/facebook/notify.py | 1 + homeassistant/components/fail2ban/sensor.py | 1 + homeassistant/components/familyhub/camera.py | 1 + homeassistant/components/fan/__init__.py | 1 + homeassistant/components/fan/device_action.py | 1 + homeassistant/components/fan/device_condition.py | 1 + homeassistant/components/fan/device_trigger.py | 1 + homeassistant/components/fan/reproduce_state.py | 1 + homeassistant/components/fan/significant_change.py | 1 + homeassistant/components/fastdotcom/__init__.py | 1 + homeassistant/components/fastdotcom/config_flow.py | 1 + homeassistant/components/fastdotcom/coordinator.py | 1 + homeassistant/components/fastdotcom/sensor.py | 1 + homeassistant/components/fastdotcom/services.py | 1 + homeassistant/components/feedreader/__init__.py | 1 + homeassistant/components/ffmpeg/__init__.py | 1 + homeassistant/components/ffmpeg/camera.py | 1 + homeassistant/components/ffmpeg_motion/binary_sensor.py | 1 + homeassistant/components/ffmpeg_noise/binary_sensor.py | 1 + homeassistant/components/fibaro/__init__.py | 1 + homeassistant/components/fibaro/binary_sensor.py | 1 + homeassistant/components/fibaro/climate.py | 1 + homeassistant/components/fibaro/config_flow.py | 1 + homeassistant/components/fibaro/cover.py | 1 + homeassistant/components/fibaro/event.py | 1 + homeassistant/components/fibaro/light.py | 1 + homeassistant/components/fibaro/lock.py | 1 + homeassistant/components/fibaro/scene.py | 1 + homeassistant/components/fibaro/sensor.py | 1 + homeassistant/components/fibaro/switch.py | 1 + homeassistant/components/fido/sensor.py | 1 + homeassistant/components/file/notify.py | 1 + homeassistant/components/file/sensor.py | 1 + homeassistant/components/file_upload/__init__.py | 1 + homeassistant/components/filesize/__init__.py | 1 + homeassistant/components/filesize/config_flow.py | 1 + homeassistant/components/filesize/coordinator.py | 1 + homeassistant/components/filesize/sensor.py | 1 + homeassistant/components/filter/sensor.py | 1 + homeassistant/components/fints/sensor.py | 1 + homeassistant/components/fireservicerota/__init__.py | 1 + homeassistant/components/fireservicerota/binary_sensor.py | 1 + homeassistant/components/fireservicerota/config_flow.py | 1 + homeassistant/components/firmata/board.py | 1 + homeassistant/components/firmata/const.py | 1 + homeassistant/components/firmata/entity.py | 1 + homeassistant/components/firmata/light.py | 1 + homeassistant/components/firmata/pin.py | 1 + homeassistant/components/fitbit/const.py | 1 + homeassistant/components/fitbit/sensor.py | 1 + homeassistant/components/fivem/__init__.py | 1 + homeassistant/components/fivem/binary_sensor.py | 1 + homeassistant/components/fivem/config_flow.py | 1 + homeassistant/components/fivem/coordinator.py | 1 + homeassistant/components/fivem/entity.py | 1 + homeassistant/components/fivem/sensor.py | 1 + homeassistant/components/fixer/sensor.py | 1 + homeassistant/components/fjaraskupan/__init__.py | 1 + homeassistant/components/fjaraskupan/binary_sensor.py | 1 + homeassistant/components/fjaraskupan/config_flow.py | 1 + homeassistant/components/fjaraskupan/coordinator.py | 1 + homeassistant/components/fjaraskupan/fan.py | 1 + homeassistant/components/fjaraskupan/light.py | 1 + homeassistant/components/fjaraskupan/number.py | 1 + homeassistant/components/fjaraskupan/sensor.py | 1 + homeassistant/components/fleetgo/device_tracker.py | 1 + homeassistant/components/flexit/climate.py | 1 + homeassistant/components/flexit_bacnet/__init__.py | 1 + homeassistant/components/flexit_bacnet/binary_sensor.py | 1 + homeassistant/components/flexit_bacnet/config_flow.py | 1 + homeassistant/components/flexit_bacnet/const.py | 1 + homeassistant/components/flexit_bacnet/entity.py | 1 + homeassistant/components/flexit_bacnet/sensor.py | 1 + homeassistant/components/flic/binary_sensor.py | 1 + homeassistant/components/flipr/__init__.py | 1 + homeassistant/components/flipr/binary_sensor.py | 1 + homeassistant/components/flipr/config_flow.py | 1 + homeassistant/components/flipr/coordinator.py | 1 + homeassistant/components/flipr/entity.py | 1 + homeassistant/components/flipr/sensor.py | 1 + homeassistant/components/flo/binary_sensor.py | 1 + homeassistant/components/flo/config_flow.py | 1 + homeassistant/components/flo/device.py | 1 + homeassistant/components/flo/entity.py | 1 + homeassistant/components/flo/sensor.py | 1 + homeassistant/components/flo/switch.py | 1 + homeassistant/components/flock/notify.py | 1 + homeassistant/components/flume/__init__.py | 1 + homeassistant/components/flume/binary_sensor.py | 1 + homeassistant/components/flume/config_flow.py | 1 + homeassistant/components/flume/const.py | 1 + homeassistant/components/flume/coordinator.py | 1 + homeassistant/components/flume/entity.py | 1 + homeassistant/components/flux/switch.py | 1 + homeassistant/components/flux_led/__init__.py | 1 + homeassistant/components/flux_led/button.py | 1 + homeassistant/components/flux_led/config_flow.py | 1 + homeassistant/components/flux_led/coordinator.py | 1 + homeassistant/components/flux_led/diagnostics.py | 1 + homeassistant/components/flux_led/discovery.py | 1 + homeassistant/components/flux_led/entity.py | 1 + homeassistant/components/flux_led/light.py | 1 + homeassistant/components/flux_led/number.py | 1 + homeassistant/components/flux_led/select.py | 1 + homeassistant/components/flux_led/sensor.py | 1 + homeassistant/components/flux_led/switch.py | 1 + homeassistant/components/flux_led/util.py | 1 + homeassistant/components/folder/sensor.py | 1 + homeassistant/components/folder_watcher/__init__.py | 1 + homeassistant/components/foobot/sensor.py | 1 + homeassistant/components/forecast_solar/__init__.py | 1 + homeassistant/components/forecast_solar/config_flow.py | 1 + homeassistant/components/forecast_solar/const.py | 1 + homeassistant/components/forecast_solar/coordinator.py | 1 + homeassistant/components/forecast_solar/diagnostics.py | 1 + homeassistant/components/forecast_solar/energy.py | 1 + homeassistant/components/forecast_solar/sensor.py | 1 + homeassistant/components/forked_daapd/__init__.py | 1 + homeassistant/components/forked_daapd/browse_media.py | 1 + homeassistant/components/forked_daapd/config_flow.py | 1 + homeassistant/components/forked_daapd/const.py | 1 + homeassistant/components/forked_daapd/media_player.py | 1 + homeassistant/components/fortios/device_tracker.py | 1 + homeassistant/components/foscam/camera.py | 1 + homeassistant/components/foscam/config_flow.py | 1 + homeassistant/components/foscam/entity.py | 1 + homeassistant/components/foursquare/__init__.py | 1 + homeassistant/components/free_mobile/notify.py | 1 + homeassistant/components/freebox/__init__.py | 1 + homeassistant/components/freebox/alarm_control_panel.py | 1 + homeassistant/components/freebox/binary_sensor.py | 1 + homeassistant/components/freebox/button.py | 1 + homeassistant/components/freebox/camera.py | 1 + homeassistant/components/freebox/const.py | 1 + homeassistant/components/freebox/device_tracker.py | 1 + homeassistant/components/freebox/home_base.py | 1 + homeassistant/components/freebox/router.py | 1 + homeassistant/components/freebox/sensor.py | 1 + homeassistant/components/freebox/switch.py | 1 + homeassistant/components/freedompro/__init__.py | 1 + homeassistant/components/freedompro/binary_sensor.py | 1 + homeassistant/components/freedompro/climate.py | 1 + homeassistant/components/freedompro/config_flow.py | 1 + homeassistant/components/freedompro/coordinator.py | 1 + homeassistant/components/freedompro/fan.py | 1 + homeassistant/components/freedompro/light.py | 1 + homeassistant/components/freedompro/sensor.py | 1 + homeassistant/components/fritz/binary_sensor.py | 1 + homeassistant/components/fritz/button.py | 1 + homeassistant/components/fritz/common.py | 1 + homeassistant/components/fritz/config_flow.py | 1 + homeassistant/components/fritz/device_tracker.py | 1 + homeassistant/components/fritz/diagnostics.py | 1 + homeassistant/components/fritz/sensor.py | 1 + homeassistant/components/fritz/services.py | 1 + homeassistant/components/fritz/switch.py | 1 + homeassistant/components/fritz/update.py | 1 + homeassistant/components/fritzbox/__init__.py | 1 + homeassistant/components/fritzbox/binary_sensor.py | 1 + homeassistant/components/fritzbox/button.py | 1 + homeassistant/components/fritzbox/climate.py | 1 + homeassistant/components/fritzbox/config_flow.py | 1 + homeassistant/components/fritzbox/const.py | 1 + homeassistant/components/fritzbox/coordinator.py | 1 + homeassistant/components/fritzbox/cover.py | 1 + homeassistant/components/fritzbox/diagnostics.py | 1 + homeassistant/components/fritzbox/light.py | 1 + homeassistant/components/fritzbox/model.py | 1 + homeassistant/components/fritzbox/sensor.py | 1 + homeassistant/components/fritzbox/switch.py | 1 + homeassistant/components/fritzbox_callmonitor/base.py | 1 + homeassistant/components/fritzbox_callmonitor/config_flow.py | 1 + homeassistant/components/fritzbox_callmonitor/const.py | 1 + homeassistant/components/fritzbox_callmonitor/sensor.py | 1 + homeassistant/components/fronius/__init__.py | 1 + homeassistant/components/fronius/config_flow.py | 1 + homeassistant/components/fronius/const.py | 1 + homeassistant/components/fronius/coordinator.py | 1 + homeassistant/components/fronius/sensor.py | 1 + homeassistant/components/frontend/__init__.py | 1 + homeassistant/components/frontend/storage.py | 1 + homeassistant/components/frontier_silicon/__init__.py | 1 + homeassistant/components/frontier_silicon/config_flow.py | 1 + homeassistant/components/frontier_silicon/media_player.py | 1 + homeassistant/components/fully_kiosk/__init__.py | 1 + homeassistant/components/fully_kiosk/binary_sensor.py | 1 + homeassistant/components/fully_kiosk/button.py | 1 + homeassistant/components/fully_kiosk/config_flow.py | 1 + homeassistant/components/fully_kiosk/const.py | 1 + homeassistant/components/fully_kiosk/diagnostics.py | 1 + homeassistant/components/fully_kiosk/entity.py | 1 + homeassistant/components/fully_kiosk/media_player.py | 1 + homeassistant/components/fully_kiosk/number.py | 1 + homeassistant/components/fully_kiosk/sensor.py | 1 + homeassistant/components/fully_kiosk/services.py | 1 + homeassistant/components/fully_kiosk/switch.py | 1 + homeassistant/components/futurenow/light.py | 1 + 657 files changed, 657 insertions(+) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index b8e87d2b200..f0b62e95b1f 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -1,4 +1,5 @@ """Platform for the Daikin AC.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index c6bab19aa8a..f48bedad4ec 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -1,4 +1,5 @@ """Support for the Daikin HVAC.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index 25e504f915a..2acbe42264d 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Daikin platform.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index 8076cd52b56..a17a80f2065 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -1,4 +1,5 @@ """Support for Daikin AC sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/daikin/switch.py b/homeassistant/components/daikin/switch.py index dd157774d6e..af94e98a337 100644 --- a/homeassistant/components/daikin/switch.py +++ b/homeassistant/components/daikin/switch.py @@ -1,4 +1,5 @@ """Support for Daikin AirBase zones.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/danfoss_air/__init__.py b/homeassistant/components/danfoss_air/__init__.py index 5069a62bcdf..5e4880705d5 100644 --- a/homeassistant/components/danfoss_air/__init__.py +++ b/homeassistant/components/danfoss_air/__init__.py @@ -1,4 +1,5 @@ """Support for Danfoss Air HRV.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/danfoss_air/binary_sensor.py b/homeassistant/components/danfoss_air/binary_sensor.py index 3764345a7b8..c8da17bfddc 100644 --- a/homeassistant/components/danfoss_air/binary_sensor.py +++ b/homeassistant/components/danfoss_air/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the for Danfoss Air HRV binary sensors.""" + from __future__ import annotations from pydanfossair.commands import ReadCommand diff --git a/homeassistant/components/danfoss_air/sensor.py b/homeassistant/components/danfoss_air/sensor.py index 024bb50ba34..3f1e2b577a6 100644 --- a/homeassistant/components/danfoss_air/sensor.py +++ b/homeassistant/components/danfoss_air/sensor.py @@ -1,4 +1,5 @@ """Support for the for Danfoss Air HRV sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/danfoss_air/switch.py b/homeassistant/components/danfoss_air/switch.py index b1ee7dce44a..c014f226384 100644 --- a/homeassistant/components/danfoss_air/switch.py +++ b/homeassistant/components/danfoss_air/switch.py @@ -1,4 +1,5 @@ """Support for the for Danfoss Air HRV sswitches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/date/__init__.py b/homeassistant/components/date/__init__.py index 00ec09043c9..5f569500c19 100644 --- a/homeassistant/components/date/__init__.py +++ b/homeassistant/components/date/__init__.py @@ -1,4 +1,5 @@ """Component to allow setting date as platforms.""" + from __future__ import annotations from datetime import date, timedelta diff --git a/homeassistant/components/datetime/__init__.py b/homeassistant/components/datetime/__init__.py index 9a509aadc70..15829e14de5 100644 --- a/homeassistant/components/datetime/__init__.py +++ b/homeassistant/components/datetime/__init__.py @@ -1,4 +1,5 @@ """Component to allow setting date/time as platforms.""" + from __future__ import annotations from datetime import UTC, datetime, timedelta diff --git a/homeassistant/components/ddwrt/device_tracker.py b/homeassistant/components/ddwrt/device_tracker.py index 7874786adba..39a7d1ebc0c 100644 --- a/homeassistant/components/ddwrt/device_tracker.py +++ b/homeassistant/components/ddwrt/device_tracker.py @@ -1,4 +1,5 @@ """Support for DD-WRT routers.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/debugpy/__init__.py b/homeassistant/components/debugpy/__init__.py index 1dc0f525c4d..4dfad108136 100644 --- a/homeassistant/components/debugpy/__init__.py +++ b/homeassistant/components/debugpy/__init__.py @@ -1,4 +1,5 @@ """The Remote Python Debugger integration.""" + from __future__ import annotations from asyncio import Event, get_running_loop diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index 179fa2320df..862ecece40f 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for deCONZ alarm control panel devices.""" + from __future__ import annotations from pydeconz.models.alarm_system import AlarmSystemArmAction diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index c0a4e2585a3..5dd56a4c5b1 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -1,4 +1,5 @@ """Support for deCONZ binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 35a0e810c9e..2fddedb00d5 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -1,4 +1,5 @@ """Support for deCONZ climate devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 012f064dd07..bac1ea404b9 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -1,4 +1,5 @@ """Support for deCONZ covers.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index 70d03f808c1..a4c24b7b7f4 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for deconz events.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/deconz/diagnostics.py b/homeassistant/components/deconz/diagnostics.py index 5b7986fc4c9..aa40c314802 100644 --- a/homeassistant/components/deconz/diagnostics.py +++ b/homeassistant/components/deconz/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for deCONZ.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/deconz/errors.py b/homeassistant/components/deconz/errors.py index be13e579ce0..55558123034 100644 --- a/homeassistant/components/deconz/errors.py +++ b/homeassistant/components/deconz/errors.py @@ -1,4 +1,5 @@ """Errors for the deCONZ component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 278d702d63b..65cc2f81ee2 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -1,4 +1,5 @@ """Support for deCONZ fans.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index d618edc93f8..1f9bc953448 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -1,4 +1,5 @@ """Support for deCONZ lights.""" + from __future__ import annotations from typing import Any, TypedDict, TypeVar diff --git a/homeassistant/components/deconz/logbook.py b/homeassistant/components/deconz/logbook.py index 39fe7e98a56..3ef14eca657 100644 --- a/homeassistant/components/deconz/logbook.py +++ b/homeassistant/components/deconz/logbook.py @@ -1,4 +1,5 @@ """Describe deCONZ logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/deconz/siren.py b/homeassistant/components/deconz/siren.py index 45c81c9e31c..a6c4a441e1f 100644 --- a/homeassistant/components/deconz/siren.py +++ b/homeassistant/components/deconz/siren.py @@ -1,4 +1,5 @@ """Support for deCONZ siren.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/deconz/util.py b/homeassistant/components/deconz/util.py index 4e7b1e7739f..7c44280200d 100644 --- a/homeassistant/components/deconz/util.py +++ b/homeassistant/components/deconz/util.py @@ -1,4 +1,5 @@ """Utilities for deCONZ integration.""" + from __future__ import annotations diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 4a56b72ec66..237577872c9 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -1,4 +1,5 @@ """Support for Decora dimmers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index a9d43736743..56488f0712b 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -1,4 +1,5 @@ """Interfaces with the myLeviton API for Decora Smart WiFi products.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/default_config/__init__.py b/homeassistant/components/default_config/__init__.py index 2221bbbef61..e7302528b2e 100644 --- a/homeassistant/components/default_config/__init__.py +++ b/homeassistant/components/default_config/__init__.py @@ -1,4 +1,5 @@ """Component providing default configuration for new users.""" + from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 2cf85b91abd..5239d6fbd6b 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -1,4 +1,5 @@ """Support for De Lijn (Flemish public transport) information.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/deluge/__init__.py b/homeassistant/components/deluge/__init__.py index 40f4d772670..6a313db2669 100644 --- a/homeassistant/components/deluge/__init__.py +++ b/homeassistant/components/deluge/__init__.py @@ -1,4 +1,5 @@ """The Deluge integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/deluge/config_flow.py b/homeassistant/components/deluge/config_flow.py index ae28f32767d..8ebf56ceb5b 100644 --- a/homeassistant/components/deluge/config_flow.py +++ b/homeassistant/components/deluge/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Deluge integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/deluge/coordinator.py b/homeassistant/components/deluge/coordinator.py index 7a3e840ff95..6b3c177b90d 100644 --- a/homeassistant/components/deluge/coordinator.py +++ b/homeassistant/components/deluge/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Deluge integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/deluge/sensor.py b/homeassistant/components/deluge/sensor.py index eeb947663bf..1b96c60ec45 100644 --- a/homeassistant/components/deluge/sensor.py +++ b/homeassistant/components/deluge/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the Deluge BitTorrent client API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/deluge/switch.py b/homeassistant/components/deluge/switch.py index 483b02844d6..866f7b4f25b 100644 --- a/homeassistant/components/deluge/switch.py +++ b/homeassistant/components/deluge/switch.py @@ -1,4 +1,5 @@ """Support for setting the Deluge BitTorrent client in Pause.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 98226d68030..6fa7e0d973b 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -1,4 +1,5 @@ """Set up the demo environment that mimics interaction with devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/demo/air_quality.py b/homeassistant/components/demo/air_quality.py index d1a56112497..551f2c8e88a 100644 --- a/homeassistant/components/demo/air_quality.py +++ b/homeassistant/components/demo/air_quality.py @@ -1,4 +1,5 @@ """Demo platform that offers fake air quality data.""" + from __future__ import annotations from homeassistant.components.air_quality import AirQualityEntity diff --git a/homeassistant/components/demo/alarm_control_panel.py b/homeassistant/components/demo/alarm_control_panel.py index 1c15e9d5b7e..0b152f87c29 100644 --- a/homeassistant/components/demo/alarm_control_panel.py +++ b/homeassistant/components/demo/alarm_control_panel.py @@ -1,4 +1,5 @@ """Demo platform that has two fake alarm control panels.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py index 21f4054b241..bc1d7b9daf2 100644 --- a/homeassistant/components/demo/binary_sensor.py +++ b/homeassistant/components/demo/binary_sensor.py @@ -1,4 +1,5 @@ """Demo platform that has two fake binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/demo/button.py b/homeassistant/components/demo/button.py index 4fefd75bb8c..a3b8dd9ff0c 100644 --- a/homeassistant/components/demo/button.py +++ b/homeassistant/components/demo/button.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake button entity.""" + from __future__ import annotations from homeassistant.components import persistent_notification diff --git a/homeassistant/components/demo/calendar.py b/homeassistant/components/demo/calendar.py index b4200f1be89..d513bc38250 100644 --- a/homeassistant/components/demo/calendar.py +++ b/homeassistant/components/demo/calendar.py @@ -1,4 +1,5 @@ """Demo platform that has two fake calendars.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/demo/camera.py b/homeassistant/components/demo/camera.py index 502129b5c9d..9fae6468207 100644 --- a/homeassistant/components/demo/camera.py +++ b/homeassistant/components/demo/camera.py @@ -1,4 +1,5 @@ """Demo camera platform that has a fake camera.""" + from __future__ import annotations from pathlib import Path diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index 745a2473939..666323d80d9 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake climate device.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/config_flow.py b/homeassistant/components/demo/config_flow.py index 5178b04b527..cc57ed9a460 100644 --- a/homeassistant/components/demo/config_flow.py +++ b/homeassistant/components/demo/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure demo component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/cover.py b/homeassistant/components/demo/cover.py index 93998eb1e8b..adddb6a3a7d 100644 --- a/homeassistant/components/demo/cover.py +++ b/homeassistant/components/demo/cover.py @@ -1,4 +1,5 @@ """Demo platform for the cover component.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/demo/date.py b/homeassistant/components/demo/date.py index 34d1909bebe..b67c4248123 100644 --- a/homeassistant/components/demo/date.py +++ b/homeassistant/components/demo/date.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake Date entity.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/demo/datetime.py b/homeassistant/components/demo/datetime.py index 63c8a5a7873..920bc14cdc5 100644 --- a/homeassistant/components/demo/datetime.py +++ b/homeassistant/components/demo/datetime.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake date/time entity.""" + from __future__ import annotations from datetime import UTC, datetime diff --git a/homeassistant/components/demo/device_tracker.py b/homeassistant/components/demo/device_tracker.py index de387545368..2097f29ea28 100644 --- a/homeassistant/components/demo/device_tracker.py +++ b/homeassistant/components/demo/device_tracker.py @@ -1,4 +1,5 @@ """Demo platform for the Device tracker component.""" + from __future__ import annotations import random diff --git a/homeassistant/components/demo/event.py b/homeassistant/components/demo/event.py index 8bc720e2db7..c58b5f5fc2e 100644 --- a/homeassistant/components/demo/event.py +++ b/homeassistant/components/demo/event.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake event entity.""" + from __future__ import annotations from homeassistant.components.event import EventDeviceClass, EventEntity diff --git a/homeassistant/components/demo/fan.py b/homeassistant/components/demo/fan.py index 644c4cb7860..82b256cd75f 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -1,4 +1,5 @@ """Demo fan platform that has a fake fan.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/geo_location.py b/homeassistant/components/demo/geo_location.py index cd020d1bb8a..ac72a3097b0 100644 --- a/homeassistant/components/demo/geo_location.py +++ b/homeassistant/components/demo/geo_location.py @@ -1,4 +1,5 @@ """Demo platform for the geolocation component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/demo/humidifier.py b/homeassistant/components/demo/humidifier.py index a63e3e1983f..37c4f189f96 100644 --- a/homeassistant/components/demo/humidifier.py +++ b/homeassistant/components/demo/humidifier.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake humidifier device.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/image_processing.py b/homeassistant/components/demo/image_processing.py index 71ea9d97bf6..d109f55f5a2 100644 --- a/homeassistant/components/demo/image_processing.py +++ b/homeassistant/components/demo/image_processing.py @@ -1,4 +1,5 @@ """Support for the demo image processing.""" + from __future__ import annotations from homeassistant.components.image_processing import ( diff --git a/homeassistant/components/demo/light.py b/homeassistant/components/demo/light.py index d8451bdd683..c859fef3b76 100644 --- a/homeassistant/components/demo/light.py +++ b/homeassistant/components/demo/light.py @@ -1,4 +1,5 @@ """Demo light platform that implements lights.""" + from __future__ import annotations import random diff --git a/homeassistant/components/demo/lock.py b/homeassistant/components/demo/lock.py index 3a6780ce30e..8c10877482f 100644 --- a/homeassistant/components/demo/lock.py +++ b/homeassistant/components/demo/lock.py @@ -1,4 +1,5 @@ """Demo lock platform that implements locks.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/demo/mailbox.py b/homeassistant/components/demo/mailbox.py index 8aa3e1ef384..ca9cbf0bd85 100644 --- a/homeassistant/components/demo/mailbox.py +++ b/homeassistant/components/demo/mailbox.py @@ -1,4 +1,5 @@ """Support for a demo mailbox.""" + from __future__ import annotations from hashlib import sha1 diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index b0b2e1a95f5..a31d4a29c0e 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -1,4 +1,5 @@ """Demo implementation of the media player.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/demo/notify.py b/homeassistant/components/demo/notify.py index 3d614d9abf0..c6a9483b328 100644 --- a/homeassistant/components/demo/notify.py +++ b/homeassistant/components/demo/notify.py @@ -1,4 +1,5 @@ """Demo notification service.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/number.py b/homeassistant/components/demo/number.py index db065054804..8c3f5ec3477 100644 --- a/homeassistant/components/demo/number.py +++ b/homeassistant/components/demo/number.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake Number entity.""" + from __future__ import annotations from homeassistant.components.number import NumberDeviceClass, NumberEntity, NumberMode diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py index f4f81a52052..774f375dd27 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -1,4 +1,5 @@ """Demo platform that has two fake remotes.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/demo/select.py b/homeassistant/components/demo/select.py index 58244e063f5..ff664a31d2f 100644 --- a/homeassistant/components/demo/select.py +++ b/homeassistant/components/demo/select.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake select entity.""" + from __future__ import annotations from homeassistant.components.select import SelectEntity diff --git a/homeassistant/components/demo/sensor.py b/homeassistant/components/demo/sensor.py index 41057bc458f..4281ca9cc59 100644 --- a/homeassistant/components/demo/sensor.py +++ b/homeassistant/components/demo/sensor.py @@ -1,4 +1,5 @@ """Demo platform that has a couple of fake sensors.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/demo/siren.py b/homeassistant/components/demo/siren.py index 3b3c3dfc610..235d98f5875 100644 --- a/homeassistant/components/demo/siren.py +++ b/homeassistant/components/demo/siren.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake siren device.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/stt.py b/homeassistant/components/demo/stt.py index 8cbc287b71d..95eebe44588 100644 --- a/homeassistant/components/demo/stt.py +++ b/homeassistant/components/demo/stt.py @@ -1,4 +1,5 @@ """Support for the demo for speech-to-text service.""" + from __future__ import annotations from collections.abc import AsyncIterable diff --git a/homeassistant/components/demo/switch.py b/homeassistant/components/demo/switch.py index ac91b069d8d..5dc05398bf1 100644 --- a/homeassistant/components/demo/switch.py +++ b/homeassistant/components/demo/switch.py @@ -1,4 +1,5 @@ """Demo platform that has two fake switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/text.py b/homeassistant/components/demo/text.py index d7174002055..1730f414fdf 100644 --- a/homeassistant/components/demo/text.py +++ b/homeassistant/components/demo/text.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake text entity.""" + from __future__ import annotations from homeassistant.components.text import TextEntity, TextMode diff --git a/homeassistant/components/demo/time.py b/homeassistant/components/demo/time.py index d0ec87386ef..f5f0322f9be 100644 --- a/homeassistant/components/demo/time.py +++ b/homeassistant/components/demo/time.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake time entity.""" + from __future__ import annotations from datetime import time diff --git a/homeassistant/components/demo/tts.py b/homeassistant/components/demo/tts.py index dfc8d7d7efb..c2fa367da29 100644 --- a/homeassistant/components/demo/tts.py +++ b/homeassistant/components/demo/tts.py @@ -1,4 +1,5 @@ """Support for the demo for text-to-speech service.""" + from __future__ import annotations import os diff --git a/homeassistant/components/demo/update.py b/homeassistant/components/demo/update.py index 747b3c130d9..7e53f5ce8ca 100644 --- a/homeassistant/components/demo/update.py +++ b/homeassistant/components/demo/update.py @@ -1,4 +1,5 @@ """Demo platform that offers fake update entities.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index 6ce67dffb90..d4c3820d29e 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -1,4 +1,5 @@ """Demo platform for the vacuum component.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/demo/water_heater.py b/homeassistant/components/demo/water_heater.py index beb46c5d8ad..f295780b190 100644 --- a/homeassistant/components/demo/water_heater.py +++ b/homeassistant/components/demo/water_heater.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake water heater device.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/demo/weather.py b/homeassistant/components/demo/weather.py index a990e26c658..fbc2b660efb 100644 --- a/homeassistant/components/demo/weather.py +++ b/homeassistant/components/demo/weather.py @@ -1,4 +1,5 @@ """Demo platform that offers fake meteorological data.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index b3b9e1a98ef..1d49323f0cc 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -1,4 +1,5 @@ """Support for Denon Network Receivers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/denonavr/config_flow.py b/homeassistant/components/denonavr/config_flow.py index 9225844285e..9a7d2a30438 100644 --- a/homeassistant/components/denonavr/config_flow.py +++ b/homeassistant/components/denonavr/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Denon AVR receivers using their HTTP interface.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 0002b04bd62..2f9b96d9471 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -1,4 +1,5 @@ """Support for Denon AVR receivers using their HTTP interface.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/denonavr/receiver.py b/homeassistant/components/denonavr/receiver.py index c400ed0bcce..abee5ed74d2 100644 --- a/homeassistant/components/denonavr/receiver.py +++ b/homeassistant/components/denonavr/receiver.py @@ -1,4 +1,5 @@ """Code to handle a DenonAVR receiver.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/derivative/__init__.py b/homeassistant/components/derivative/__init__.py index c5b1c8e31e9..2b365e96244 100644 --- a/homeassistant/components/derivative/__init__.py +++ b/homeassistant/components/derivative/__init__.py @@ -1,4 +1,5 @@ """The Derivative integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/derivative/config_flow.py b/homeassistant/components/derivative/config_flow.py index 3b0b2425aac..e15741ce9cf 100644 --- a/homeassistant/components/derivative/config_flow.py +++ b/homeassistant/components/derivative/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Derivative integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index cd912ceb24e..e46482babc8 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -1,4 +1,5 @@ """Numeric derivative of data coming from a source sensor over time.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/devialet/__init__.py b/homeassistant/components/devialet/__init__.py index 034f93abb68..2eccdb2a4b6 100644 --- a/homeassistant/components/devialet/__init__.py +++ b/homeassistant/components/devialet/__init__.py @@ -1,4 +1,5 @@ """The Devialet integration.""" + from __future__ import annotations from devialet import DevialetApi diff --git a/homeassistant/components/devialet/config_flow.py b/homeassistant/components/devialet/config_flow.py index e244bfeb895..4c097ae6f86 100644 --- a/homeassistant/components/devialet/config_flow.py +++ b/homeassistant/components/devialet/config_flow.py @@ -1,4 +1,5 @@ """Support for Devialet Phantom speakers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/devialet/const.py b/homeassistant/components/devialet/const.py index ccb4fbc7964..58482134527 100644 --- a/homeassistant/components/devialet/const.py +++ b/homeassistant/components/devialet/const.py @@ -1,4 +1,5 @@ """Constants for the Devialet integration.""" + from typing import Final DOMAIN: Final = "devialet" diff --git a/homeassistant/components/devialet/coordinator.py b/homeassistant/components/devialet/coordinator.py index 9e1eada7183..9cfeb797373 100644 --- a/homeassistant/components/devialet/coordinator.py +++ b/homeassistant/components/devialet/coordinator.py @@ -1,4 +1,5 @@ """Class representing a Devialet update coordinator.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/devialet/diagnostics.py b/homeassistant/components/devialet/diagnostics.py index f9824a9cad1..ae887dd1c8c 100644 --- a/homeassistant/components/devialet/diagnostics.py +++ b/homeassistant/components/devialet/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Devialet.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devialet/media_player.py b/homeassistant/components/devialet/media_player.py index a79a82e6f60..d490e348b9c 100644 --- a/homeassistant/components/devialet/media_player.py +++ b/homeassistant/components/devialet/media_player.py @@ -1,4 +1,5 @@ """Support for Devialet speakers.""" + from __future__ import annotations from devialet.const import NORMAL_INPUTS diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 91a223217b3..4eaff935dce 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -1,4 +1,5 @@ """Helpers for device automations.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/device_automation/action.py b/homeassistant/components/device_automation/action.py index 58c124377ff..b1c63ac439b 100644 --- a/homeassistant/components/device_automation/action.py +++ b/homeassistant/components/device_automation/action.py @@ -1,4 +1,5 @@ """Device action validator.""" + from __future__ import annotations from typing import Any, Protocol diff --git a/homeassistant/components/device_automation/condition.py b/homeassistant/components/device_automation/condition.py index f819668f090..13454d416a0 100644 --- a/homeassistant/components/device_automation/condition.py +++ b/homeassistant/components/device_automation/condition.py @@ -1,4 +1,5 @@ """Validate device conditions.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any, Protocol diff --git a/homeassistant/components/device_automation/entity.py b/homeassistant/components/device_automation/entity.py index 87ff5a2cb52..aaa14dbf9b0 100644 --- a/homeassistant/components/device_automation/entity.py +++ b/homeassistant/components/device_automation/entity.py @@ -1,4 +1,5 @@ """Device automation helpers for entity.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/device_automation/exceptions.py b/homeassistant/components/device_automation/exceptions.py index 0b2f2c01be7..8782f8245f3 100644 --- a/homeassistant/components/device_automation/exceptions.py +++ b/homeassistant/components/device_automation/exceptions.py @@ -1,4 +1,5 @@ """Device automation exceptions.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/device_automation/helpers.py b/homeassistant/components/device_automation/helpers.py index a00455293f6..0d935444a59 100644 --- a/homeassistant/components/device_automation/helpers.py +++ b/homeassistant/components/device_automation/helpers.py @@ -1,4 +1,5 @@ """Helpers for device oriented automations.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/device_automation/toggle_entity.py b/homeassistant/components/device_automation/toggle_entity.py index 189fc750e50..d2220836226 100644 --- a/homeassistant/components/device_automation/toggle_entity.py +++ b/homeassistant/components/device_automation/toggle_entity.py @@ -1,4 +1,5 @@ """Device automation helpers for toggle entity.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/device_automation/trigger.py b/homeassistant/components/device_automation/trigger.py index 6bbbd6febce..cc8c4d4d52e 100644 --- a/homeassistant/components/device_automation/trigger.py +++ b/homeassistant/components/device_automation/trigger.py @@ -1,4 +1,5 @@ """Offer device oriented automation.""" + from __future__ import annotations from typing import Any, Protocol diff --git a/homeassistant/components/device_sun_light_trigger/__init__.py b/homeassistant/components/device_sun_light_trigger/__init__.py index ea9205ebdec..f1a3dc7e78b 100644 --- a/homeassistant/components/device_sun_light_trigger/__init__.py +++ b/homeassistant/components/device_sun_light_trigger/__init__.py @@ -1,4 +1,5 @@ """Support to turn on lights based on the states.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index adcc90cccbf..4081e669b00 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -1,4 +1,5 @@ """Provide functionality to keep track of devices.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/device_tracker/config_entry.py b/homeassistant/components/device_tracker/config_entry.py index 20ac365b33b..a1c1961dc43 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -1,4 +1,5 @@ """Code to set up a device tracker platform using a config entry.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/device_tracker/const.py b/homeassistant/components/device_tracker/const.py index 67a90ab0f95..25f82b91dda 100644 --- a/homeassistant/components/device_tracker/const.py +++ b/homeassistant/components/device_tracker/const.py @@ -1,4 +1,5 @@ """Device tracker constants.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/device_tracker/device_condition.py b/homeassistant/components/device_tracker/device_condition.py index b5bf850b4fa..2d6d723dc49 100644 --- a/homeassistant/components/device_tracker/device_condition.py +++ b/homeassistant/components/device_tracker/device_condition.py @@ -1,4 +1,5 @@ """Provides device automations for Device tracker.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/device_tracker/device_trigger.py b/homeassistant/components/device_tracker/device_trigger.py index 404dad0d4d1..bcd2f0f2342 100644 --- a/homeassistant/components/device_tracker/device_trigger.py +++ b/homeassistant/components/device_tracker/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Device Tracker.""" + from __future__ import annotations from operator import attrgetter diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index e1a8058d819..45d971529d1 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -1,4 +1,5 @@ """Legacy device tracker classes.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/devolo_home_control/__init__.py b/homeassistant/components/devolo_home_control/__init__.py index ec0d5a3a666..78e536209d1 100644 --- a/homeassistant/components/devolo_home_control/__init__.py +++ b/homeassistant/components/devolo_home_control/__init__.py @@ -1,4 +1,5 @@ """The devolo_home_control integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/devolo_home_control/binary_sensor.py b/homeassistant/components/devolo_home_control/binary_sensor.py index a0d80926bc8..564d3a583d2 100644 --- a/homeassistant/components/devolo_home_control/binary_sensor.py +++ b/homeassistant/components/devolo_home_control/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for binary sensor integration.""" + from __future__ import annotations from devolo_home_control_api.devices.zwave import Zwave diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index 9f17a653673..702adc06e1f 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -1,4 +1,5 @@ """Platform for climate integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devolo_home_control/config_flow.py b/homeassistant/components/devolo_home_control/config_flow.py index 9ae31432b53..662ce51daaf 100644 --- a/homeassistant/components/devolo_home_control/config_flow.py +++ b/homeassistant/components/devolo_home_control/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the devolo home control integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/devolo_home_control/cover.py b/homeassistant/components/devolo_home_control/cover.py index b76948bcee7..3af0de3a509 100644 --- a/homeassistant/components/devolo_home_control/cover.py +++ b/homeassistant/components/devolo_home_control/cover.py @@ -1,4 +1,5 @@ """Platform for cover integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devolo_home_control/devolo_device.py b/homeassistant/components/devolo_home_control/devolo_device.py index e63e711ea6f..fe8212732a5 100644 --- a/homeassistant/components/devolo_home_control/devolo_device.py +++ b/homeassistant/components/devolo_home_control/devolo_device.py @@ -1,4 +1,5 @@ """Base class for a device entity integrated in devolo Home Control.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py b/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py index d2608ed43c7..3072cb01f2e 100644 --- a/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py +++ b/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py @@ -1,4 +1,5 @@ """Base class for multi level switches in devolo Home Control.""" + from devolo_home_control_api.devices.zwave import Zwave from devolo_home_control_api.homecontrol import HomeControl diff --git a/homeassistant/components/devolo_home_control/diagnostics.py b/homeassistant/components/devolo_home_control/diagnostics.py index 412effcd5ed..2a7e82b0901 100644 --- a/homeassistant/components/devolo_home_control/diagnostics.py +++ b/homeassistant/components/devolo_home_control/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for devolo Home Control.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devolo_home_control/exceptions.py b/homeassistant/components/devolo_home_control/exceptions.py index a89058e6c16..1ae66d47be2 100644 --- a/homeassistant/components/devolo_home_control/exceptions.py +++ b/homeassistant/components/devolo_home_control/exceptions.py @@ -1,4 +1,5 @@ """Custom exceptions for the devolo_home_control integration.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/devolo_home_control/light.py b/homeassistant/components/devolo_home_control/light.py index e91466c7ece..79e3531741f 100644 --- a/homeassistant/components/devolo_home_control/light.py +++ b/homeassistant/components/devolo_home_control/light.py @@ -1,4 +1,5 @@ """Platform for light integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index fa11424ae94..579dc8f38d6 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from devolo_home_control_api.devices.zwave import Zwave diff --git a/homeassistant/components/devolo_home_control/siren.py b/homeassistant/components/devolo_home_control/siren.py index 216ab4ab296..f0a2c228068 100644 --- a/homeassistant/components/devolo_home_control/siren.py +++ b/homeassistant/components/devolo_home_control/siren.py @@ -1,4 +1,5 @@ """Platform for siren integration.""" + from typing import Any from devolo_home_control_api.devices.zwave import Zwave diff --git a/homeassistant/components/devolo_home_control/subscriber.py b/homeassistant/components/devolo_home_control/subscriber.py index 13ffabeaba2..99c21b3fd36 100644 --- a/homeassistant/components/devolo_home_control/subscriber.py +++ b/homeassistant/components/devolo_home_control/subscriber.py @@ -1,4 +1,5 @@ """Subscriber for devolo home control API publisher.""" + from collections.abc import Callable import logging diff --git a/homeassistant/components/devolo_home_control/switch.py b/homeassistant/components/devolo_home_control/switch.py index c442cc55763..1a6f61f64ab 100644 --- a/homeassistant/components/devolo_home_control/switch.py +++ b/homeassistant/components/devolo_home_control/switch.py @@ -1,4 +1,5 @@ """Platform for switch integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devolo_home_network/__init__.py b/homeassistant/components/devolo_home_network/__init__.py index 842d1bee40f..137eee62b22 100644 --- a/homeassistant/components/devolo_home_network/__init__.py +++ b/homeassistant/components/devolo_home_network/__init__.py @@ -1,4 +1,5 @@ """The devolo Home Network integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/devolo_home_network/binary_sensor.py b/homeassistant/components/devolo_home_network/binary_sensor.py index 8d7f578651a..6750fbc50d5 100644 --- a/homeassistant/components/devolo_home_network/binary_sensor.py +++ b/homeassistant/components/devolo_home_network/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for binary sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/devolo_home_network/button.py b/homeassistant/components/devolo_home_network/button.py index 3bcdf6c4610..e9210b7ccc1 100644 --- a/homeassistant/components/devolo_home_network/button.py +++ b/homeassistant/components/devolo_home_network/button.py @@ -1,4 +1,5 @@ """Platform for button integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/devolo_home_network/config_flow.py b/homeassistant/components/devolo_home_network/config_flow.py index 3f0ec0a02b7..a53211aa479 100644 --- a/homeassistant/components/devolo_home_network/config_flow.py +++ b/homeassistant/components/devolo_home_network/config_flow.py @@ -1,4 +1,5 @@ """Config flow for devolo Home Network integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/devolo_home_network/device_tracker.py b/homeassistant/components/devolo_home_network/device_tracker.py index c73e08abed2..92ab56ab922 100644 --- a/homeassistant/components/devolo_home_network/device_tracker.py +++ b/homeassistant/components/devolo_home_network/device_tracker.py @@ -1,4 +1,5 @@ """Platform for device tracker integration.""" + from __future__ import annotations from devolo_plc_api.device import Device diff --git a/homeassistant/components/devolo_home_network/diagnostics.py b/homeassistant/components/devolo_home_network/diagnostics.py index bd4393d73dd..17d65fd26b2 100644 --- a/homeassistant/components/devolo_home_network/diagnostics.py +++ b/homeassistant/components/devolo_home_network/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for devolo Home Network.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/devolo_home_network/entity.py b/homeassistant/components/devolo_home_network/entity.py index d6ddf661494..a6159d7b948 100644 --- a/homeassistant/components/devolo_home_network/entity.py +++ b/homeassistant/components/devolo_home_network/entity.py @@ -1,4 +1,5 @@ """Generic platform.""" + from __future__ import annotations from typing import TypeVar diff --git a/homeassistant/components/devolo_home_network/image.py b/homeassistant/components/devolo_home_network/image.py index 16b89bb1180..71d27b18d0c 100644 --- a/homeassistant/components/devolo_home_network/image.py +++ b/homeassistant/components/devolo_home_network/image.py @@ -1,4 +1,5 @@ """Platform for image integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/devolo_home_network/sensor.py b/homeassistant/components/devolo_home_network/sensor.py index 8a7676ceb26..cc682d8f694 100644 --- a/homeassistant/components/devolo_home_network/sensor.py +++ b/homeassistant/components/devolo_home_network/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/devolo_home_network/switch.py b/homeassistant/components/devolo_home_network/switch.py index 05169073369..ffb6ae8098a 100644 --- a/homeassistant/components/devolo_home_network/switch.py +++ b/homeassistant/components/devolo_home_network/switch.py @@ -1,4 +1,5 @@ """Platform for switch integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/devolo_home_network/update.py b/homeassistant/components/devolo_home_network/update.py index 88c10b61cfc..f94cf13ef5c 100644 --- a/homeassistant/components/devolo_home_network/update.py +++ b/homeassistant/components/devolo_home_network/update.py @@ -1,4 +1,5 @@ """Platform for update integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/dexcom/__init__.py b/homeassistant/components/dexcom/__init__.py index 3ef6f7fd72e..2053f266eb5 100644 --- a/homeassistant/components/dexcom/__init__.py +++ b/homeassistant/components/dexcom/__init__.py @@ -1,4 +1,5 @@ """The Dexcom integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/dexcom/config_flow.py b/homeassistant/components/dexcom/config_flow.py index b099a97e0b2..48cdcd99439 100644 --- a/homeassistant/components/dexcom/config_flow.py +++ b/homeassistant/components/dexcom/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Dexcom integration.""" + from __future__ import annotations from pydexcom import AccountError, Dexcom, SessionError diff --git a/homeassistant/components/dexcom/const.py b/homeassistant/components/dexcom/const.py index 1a0eeeba090..25abb1bed26 100644 --- a/homeassistant/components/dexcom/const.py +++ b/homeassistant/components/dexcom/const.py @@ -1,4 +1,5 @@ """Constants for the Dexcom integration.""" + from homeassistant.const import Platform DOMAIN = "dexcom" diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index 9a7a8631092..4c2e46ba06c 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -1,4 +1,5 @@ """Support for Dexcom sensors.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index de3e48c468b..305e5870663 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -1,4 +1,5 @@ """The dhcp integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index 565fc2112b7..9dec7cc15be 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -1,4 +1,5 @@ """The Diagnostics integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine, Mapping diff --git a/homeassistant/components/diagnostics/const.py b/homeassistant/components/diagnostics/const.py index 20f97be1eb1..11042c3b7dc 100644 --- a/homeassistant/components/diagnostics/const.py +++ b/homeassistant/components/diagnostics/const.py @@ -1,4 +1,5 @@ """Constants for the Diagnostics integration.""" + from enum import StrEnum DOMAIN = "diagnostics" diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index 47a0eac9a0d..5b30692aa9b 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -1,4 +1,5 @@ """Diagnostic utilities.""" + from __future__ import annotations from collections.abc import Iterable, Mapping diff --git a/homeassistant/components/dialogflow/config_flow.py b/homeassistant/components/dialogflow/config_flow.py index 7e62869c3fa..9833e7d1c18 100644 --- a/homeassistant/components/dialogflow/config_flow.py +++ b/homeassistant/components/dialogflow/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DialogFlow.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/digital_ocean/__init__.py b/homeassistant/components/digital_ocean/__init__.py index ed951fa2aa4..e5b62d430b6 100644 --- a/homeassistant/components/digital_ocean/__init__.py +++ b/homeassistant/components/digital_ocean/__init__.py @@ -1,4 +1,5 @@ """Support for Digital Ocean.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index e2bd09ba15e..9218d9bde0e 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the state of Digital Ocean droplets.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index b226dbab0a9..a01965e3667 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -1,4 +1,5 @@ """Support for interacting with Digital Ocean droplets.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/directv/__init__.py b/homeassistant/components/directv/__init__.py index 3dfb5708b98..50eb6bc7959 100644 --- a/homeassistant/components/directv/__init__.py +++ b/homeassistant/components/directv/__init__.py @@ -1,4 +1,5 @@ """The DirecTV integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/directv/config_flow.py b/homeassistant/components/directv/config_flow.py index 6c5654b185e..f1289119f2b 100644 --- a/homeassistant/components/directv/config_flow.py +++ b/homeassistant/components/directv/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DirecTV.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/directv/entity.py b/homeassistant/components/directv/entity.py index 0da2cfcb9d6..45a3c59991d 100644 --- a/homeassistant/components/directv/entity.py +++ b/homeassistant/components/directv/entity.py @@ -1,4 +1,5 @@ """Base DirecTV Entity.""" + from __future__ import annotations from directv import DIRECTV diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index 63d086564ee..24d21c195a6 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -1,4 +1,5 @@ """Support for the DirecTV receivers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/directv/remote.py b/homeassistant/components/directv/remote.py index d100abd3495..724984545d2 100644 --- a/homeassistant/components/directv/remote.py +++ b/homeassistant/components/directv/remote.py @@ -1,4 +1,5 @@ """Support for the DIRECTV remote.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/discogs/sensor.py b/homeassistant/components/discogs/sensor.py index e6a46ac535f..4a732130485 100644 --- a/homeassistant/components/discogs/sensor.py +++ b/homeassistant/components/discogs/sensor.py @@ -1,4 +1,5 @@ """Show the amount of records in a user's Discogs collection.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/discord/__init__.py b/homeassistant/components/discord/__init__.py index 329709e88d2..8c1e80527f8 100644 --- a/homeassistant/components/discord/__init__.py +++ b/homeassistant/components/discord/__init__.py @@ -1,4 +1,5 @@ """The discord integration.""" + from aiohttp.client_exceptions import ClientConnectorError import nextcord diff --git a/homeassistant/components/discord/config_flow.py b/homeassistant/components/discord/config_flow.py index e93f2cc565f..a2747c1d803 100644 --- a/homeassistant/components/discord/config_flow.py +++ b/homeassistant/components/discord/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Discord integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py index ff83d97f8c2..5f1e494c97e 100644 --- a/homeassistant/components/discord/notify.py +++ b/homeassistant/components/discord/notify.py @@ -1,4 +1,5 @@ """Discord platform for notify component.""" + from __future__ import annotations from io import BytesIO diff --git a/homeassistant/components/discovergy/__init__.py b/homeassistant/components/discovergy/__init__.py index 786f589bf7b..0d38182da5d 100644 --- a/homeassistant/components/discovergy/__init__.py +++ b/homeassistant/components/discovergy/__init__.py @@ -1,4 +1,5 @@ """The Discovergy integration.""" + from __future__ import annotations from pydiscovergy import Discovergy diff --git a/homeassistant/components/discovergy/config_flow.py b/homeassistant/components/discovergy/config_flow.py index ab57bb138de..e47935764a8 100644 --- a/homeassistant/components/discovergy/config_flow.py +++ b/homeassistant/components/discovergy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Discovergy integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/discovergy/const.py b/homeassistant/components/discovergy/const.py index f410eb94bcf..39ff7a7cd4b 100644 --- a/homeassistant/components/discovergy/const.py +++ b/homeassistant/components/discovergy/const.py @@ -1,4 +1,5 @@ """Constants for the Discovergy integration.""" + from __future__ import annotations DOMAIN = "discovergy" diff --git a/homeassistant/components/discovergy/coordinator.py b/homeassistant/components/discovergy/coordinator.py index e5e161a5d40..3be4c71c987 100644 --- a/homeassistant/components/discovergy/coordinator.py +++ b/homeassistant/components/discovergy/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Discovergy integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/discovergy/diagnostics.py b/homeassistant/components/discovergy/diagnostics.py index 99d559e94bc..15676da9888 100644 --- a/homeassistant/components/discovergy/diagnostics.py +++ b/homeassistant/components/discovergy/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for discovergy.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/discovergy/sensor.py b/homeassistant/components/discovergy/sensor.py index 365a67fe552..cb9f8c3bf1d 100644 --- a/homeassistant/components/discovergy/sensor.py +++ b/homeassistant/components/discovergy/sensor.py @@ -1,4 +1,5 @@ """Discovergy sensor entity.""" + from collections.abc import Callable from dataclasses import dataclass, field from datetime import datetime diff --git a/homeassistant/components/discovergy/system_health.py b/homeassistant/components/discovergy/system_health.py index 61fe4099596..1d539ac7012 100644 --- a/homeassistant/components/discovergy/system_health.py +++ b/homeassistant/components/discovergy/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from typing import Any from pydiscovergy.const import API_BASE diff --git a/homeassistant/components/dlib_face_detect/image_processing.py b/homeassistant/components/dlib_face_detect/image_processing.py index 42031b28844..d2559c0753e 100644 --- a/homeassistant/components/dlib_face_detect/image_processing.py +++ b/homeassistant/components/dlib_face_detect/image_processing.py @@ -1,4 +1,5 @@ """Component that will help set the Dlib face detect processing.""" + from __future__ import annotations import io diff --git a/homeassistant/components/dlib_face_identify/image_processing.py b/homeassistant/components/dlib_face_identify/image_processing.py index e6aaa6848d0..73cf53db5a2 100644 --- a/homeassistant/components/dlib_face_identify/image_processing.py +++ b/homeassistant/components/dlib_face_identify/image_processing.py @@ -1,4 +1,5 @@ """Component that will help set the Dlib face detect processing.""" + from __future__ import annotations import io diff --git a/homeassistant/components/dlink/__init__.py b/homeassistant/components/dlink/__init__.py index 40fce4acf76..80260643223 100644 --- a/homeassistant/components/dlink/__init__.py +++ b/homeassistant/components/dlink/__init__.py @@ -1,4 +1,5 @@ """The D-Link Power Plug integration.""" + from __future__ import annotations from pyW215.pyW215 import SmartPlug diff --git a/homeassistant/components/dlink/config_flow.py b/homeassistant/components/dlink/config_flow.py index a600c0e148f..52937d26b7d 100644 --- a/homeassistant/components/dlink/config_flow.py +++ b/homeassistant/components/dlink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the D-Link Power Plug integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/dlink/data.py b/homeassistant/components/dlink/data.py index b93cd219166..35cafc18367 100644 --- a/homeassistant/components/dlink/data.py +++ b/homeassistant/components/dlink/data.py @@ -1,4 +1,5 @@ """Data for the D-Link Power Plug integration.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/dlink/entity.py b/homeassistant/components/dlink/entity.py index 238db5f5c57..2a9ac0e6c12 100644 --- a/homeassistant/components/dlink/entity.py +++ b/homeassistant/components/dlink/entity.py @@ -1,4 +1,5 @@ """Entity representing a D-Link Power Plug device.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py index 0814945bc07..a37caa6700c 100644 --- a/homeassistant/components/dlink/switch.py +++ b/homeassistant/components/dlink/switch.py @@ -1,4 +1,5 @@ """Support for D-Link Power Plug Switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/dlna_dmr/__init__.py b/homeassistant/components/dlna_dmr/__init__.py index e9dd60c5896..d22f4eb41d4 100644 --- a/homeassistant/components/dlna_dmr/__init__.py +++ b/homeassistant/components/dlna_dmr/__init__.py @@ -1,4 +1,5 @@ """The dlna_dmr component.""" + from __future__ import annotations from homeassistant import config_entries diff --git a/homeassistant/components/dlna_dmr/config_flow.py b/homeassistant/components/dlna_dmr/config_flow.py index c98ed6e6dce..9d95ba3883e 100644 --- a/homeassistant/components/dlna_dmr/config_flow.py +++ b/homeassistant/components/dlna_dmr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DLNA DMR.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/dlna_dmr/const.py b/homeassistant/components/dlna_dmr/const.py index 4cea664f058..df81cee08e4 100644 --- a/homeassistant/components/dlna_dmr/const.py +++ b/homeassistant/components/dlna_dmr/const.py @@ -1,4 +1,5 @@ """Constants for the DLNA DMR component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/dlna_dmr/data.py b/homeassistant/components/dlna_dmr/data.py index 1a1a28d758c..7af396f7c60 100644 --- a/homeassistant/components/dlna_dmr/data.py +++ b/homeassistant/components/dlna_dmr/data.py @@ -1,4 +1,5 @@ """Data used by this integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index c8c70486854..7bad837d328 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -1,4 +1,5 @@ """Support for DLNA DMR (Device Media Renderer).""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/dlna_dms/__init__.py b/homeassistant/components/dlna_dms/__init__.py index 5cd6321a5df..668a2e9d965 100644 --- a/homeassistant/components/dlna_dms/__init__.py +++ b/homeassistant/components/dlna_dms/__init__.py @@ -3,6 +3,7 @@ A single config entry is used, with SSDP discovery for media servers. Each server is wrapped in a DmsEntity, and the server's USN is used as the unique_id. """ + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/dlna_dms/config_flow.py b/homeassistant/components/dlna_dms/config_flow.py index 0df2068ec71..480f45ee95b 100644 --- a/homeassistant/components/dlna_dms/config_flow.py +++ b/homeassistant/components/dlna_dms/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DLNA DMS.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/dlna_dms/const.py b/homeassistant/components/dlna_dms/const.py index 8d4cb6352ee..686e6c63108 100644 --- a/homeassistant/components/dlna_dms/const.py +++ b/homeassistant/components/dlna_dms/const.py @@ -1,4 +1,5 @@ """Constants for the DLNA MediaServer integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/dlna_dms/dms.py b/homeassistant/components/dlna_dms/dms.py index 54cca744360..aaa55e3ad3e 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -1,4 +1,5 @@ """Wrapper for media_source around async_upnp_client's DmsDevice .""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/dlna_dms/util.py b/homeassistant/components/dlna_dms/util.py index 74c5bd2e01b..78ada3c708a 100644 --- a/homeassistant/components/dlna_dms/util.py +++ b/homeassistant/components/dlna_dms/util.py @@ -1,4 +1,5 @@ """Small utility functions for the dlna_dms integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/dnsip/__init__.py b/homeassistant/components/dnsip/__init__.py index 13783a1b07f..78309b5f2bf 100644 --- a/homeassistant/components/dnsip/__init__.py +++ b/homeassistant/components/dnsip/__init__.py @@ -1,4 +1,5 @@ """The dnsip component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/dnsip/config_flow.py b/homeassistant/components/dnsip/config_flow.py index b9e3c2a2dfe..8dc62a8343d 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for dnsip integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/dnsip/const.py b/homeassistant/components/dnsip/const.py index 56215d3d9a6..41116bde61a 100644 --- a/homeassistant/components/dnsip/const.py +++ b/homeassistant/components/dnsip/const.py @@ -1,4 +1,5 @@ """Constants for dnsip integration.""" + from homeassistant.const import Platform DOMAIN = "dnsip" diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py index 975ec1992ae..529de6f2b1b 100644 --- a/homeassistant/components/dnsip/sensor.py +++ b/homeassistant/components/dnsip/sensor.py @@ -1,4 +1,5 @@ """Get your own public IP address or that of any host.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/dominos/__init__.py b/homeassistant/components/dominos/__init__.py index 37344ed9d4b..48404e6dbee 100644 --- a/homeassistant/components/dominos/__init__.py +++ b/homeassistant/components/dominos/__init__.py @@ -1,4 +1,5 @@ """Support for Dominos Pizza ordering.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index ba97dbe38ec..aa9e9535ea9 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -1,4 +1,5 @@ """Support for the DOODS service.""" + from __future__ import annotations import io diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index d7800a26fc8..71786eac779 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -1,4 +1,5 @@ """Support for DoorBird devices.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index 3da47eb572a..ddc8ae0bdc5 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -1,4 +1,5 @@ """Support for viewing the camera feed from a DoorBird video doorbell.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/doorbird/config_flow.py b/homeassistant/components/doorbird/config_flow.py index b91c072f85b..8bb069bab88 100644 --- a/homeassistant/components/doorbird/config_flow.py +++ b/homeassistant/components/doorbird/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DoorBird integration.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/doorbird/const.py b/homeassistant/components/doorbird/const.py index 416603a312c..1bd13496e3a 100644 --- a/homeassistant/components/doorbird/const.py +++ b/homeassistant/components/doorbird/const.py @@ -1,4 +1,5 @@ """The DoorBird integration constants.""" + from homeassistant.const import Platform DOMAIN = "doorbird" diff --git a/homeassistant/components/doorbird/device.py b/homeassistant/components/doorbird/device.py index 767a80a7857..e0fb02fcb8d 100644 --- a/homeassistant/components/doorbird/device.py +++ b/homeassistant/components/doorbird/device.py @@ -1,4 +1,5 @@ """Support for DoorBird devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/doorbird/logbook.py b/homeassistant/components/doorbird/logbook.py index 84497a312ae..71a63557a2c 100644 --- a/homeassistant/components/doorbird/logbook.py +++ b/homeassistant/components/doorbird/logbook.py @@ -1,4 +1,5 @@ """Describe logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/doorbird/models.py b/homeassistant/components/doorbird/models.py index f8fb8687e59..a8ecbf19d5d 100644 --- a/homeassistant/components/doorbird/models.py +++ b/homeassistant/components/doorbird/models.py @@ -1,4 +1,5 @@ """The doorbird integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/doorbird/view.py b/homeassistant/components/doorbird/view.py index b9d03288e26..e1fa8e7cfbb 100644 --- a/homeassistant/components/doorbird/view.py +++ b/homeassistant/components/doorbird/view.py @@ -1,4 +1,5 @@ """Support for DoorBird devices.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/dormakaba_dkey/__init__.py b/homeassistant/components/dormakaba_dkey/__init__.py index 4903e46b8dc..a8868e8563c 100644 --- a/homeassistant/components/dormakaba_dkey/__init__.py +++ b/homeassistant/components/dormakaba_dkey/__init__.py @@ -1,4 +1,5 @@ """The Dormakaba dKey integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/dormakaba_dkey/binary_sensor.py b/homeassistant/components/dormakaba_dkey/binary_sensor.py index 1c2205c01d3..a8574443e35 100644 --- a/homeassistant/components/dormakaba_dkey/binary_sensor.py +++ b/homeassistant/components/dormakaba_dkey/binary_sensor.py @@ -1,4 +1,5 @@ """Dormakaba dKey integration binary sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/dormakaba_dkey/config_flow.py b/homeassistant/components/dormakaba_dkey/config_flow.py index c2cdf1854e2..d4cd19644c1 100644 --- a/homeassistant/components/dormakaba_dkey/config_flow.py +++ b/homeassistant/components/dormakaba_dkey/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Dormakaba dKey integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/dormakaba_dkey/entity.py b/homeassistant/components/dormakaba_dkey/entity.py index 26a06deed0e..756edccf02f 100644 --- a/homeassistant/components/dormakaba_dkey/entity.py +++ b/homeassistant/components/dormakaba_dkey/entity.py @@ -1,4 +1,5 @@ """Dormakaba dKey integration base entity.""" + from __future__ import annotations import abc diff --git a/homeassistant/components/dormakaba_dkey/lock.py b/homeassistant/components/dormakaba_dkey/lock.py index e238c4e143b..5f475d37152 100644 --- a/homeassistant/components/dormakaba_dkey/lock.py +++ b/homeassistant/components/dormakaba_dkey/lock.py @@ -1,4 +1,5 @@ """Dormakaba dKey integration lock platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/dormakaba_dkey/models.py b/homeassistant/components/dormakaba_dkey/models.py index cd260c15e81..23687e82334 100644 --- a/homeassistant/components/dormakaba_dkey/models.py +++ b/homeassistant/components/dormakaba_dkey/models.py @@ -1,4 +1,5 @@ """The Dormakaba dKey integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/dormakaba_dkey/sensor.py b/homeassistant/components/dormakaba_dkey/sensor.py index 39915563b03..e461ba1e44f 100644 --- a/homeassistant/components/dormakaba_dkey/sensor.py +++ b/homeassistant/components/dormakaba_dkey/sensor.py @@ -1,4 +1,5 @@ """Dormakaba dKey integration sensor platform.""" + from __future__ import annotations from py_dormakaba_dkey import DKEYLock diff --git a/homeassistant/components/dovado/__init__.py b/homeassistant/components/dovado/__init__.py index 40069a769a1..60e8351cc24 100644 --- a/homeassistant/components/dovado/__init__.py +++ b/homeassistant/components/dovado/__init__.py @@ -1,4 +1,5 @@ """Support for Dovado router.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/dovado/notify.py b/homeassistant/components/dovado/notify.py index ba7dcf6d486..556848bf89f 100644 --- a/homeassistant/components/dovado/notify.py +++ b/homeassistant/components/dovado/notify.py @@ -1,4 +1,5 @@ """Support for SMS notifications from the Dovado router.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/dovado/sensor.py b/homeassistant/components/dovado/sensor.py index 5da248c414a..bd53fb22ad2 100644 --- a/homeassistant/components/dovado/sensor.py +++ b/homeassistant/components/dovado/sensor.py @@ -1,4 +1,5 @@ """Support for sensors from the Dovado router.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 1922fde3102..5d7f1594be0 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -1,4 +1,5 @@ """Support for functionality to download files.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/dremel_3d_printer/__init__.py b/homeassistant/components/dremel_3d_printer/__init__.py index 5f9f10dc9c1..76cd63a3a1d 100644 --- a/homeassistant/components/dremel_3d_printer/__init__.py +++ b/homeassistant/components/dremel_3d_printer/__init__.py @@ -1,4 +1,5 @@ """The Dremel 3D Printer (3D20, 3D40, 3D45) integration.""" + from __future__ import annotations from dremel3dpy import Dremel3DPrinter diff --git a/homeassistant/components/dremel_3d_printer/binary_sensor.py b/homeassistant/components/dremel_3d_printer/binary_sensor.py index f1fc31eac60..e6df0ebcf6e 100644 --- a/homeassistant/components/dremel_3d_printer/binary_sensor.py +++ b/homeassistant/components/dremel_3d_printer/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring Dremel 3D Printer binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/dremel_3d_printer/button.py b/homeassistant/components/dremel_3d_printer/button.py index 0a312de1468..d92263b6a15 100644 --- a/homeassistant/components/dremel_3d_printer/button.py +++ b/homeassistant/components/dremel_3d_printer/button.py @@ -1,4 +1,5 @@ """Support for Dremel 3D Printer buttons.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/dremel_3d_printer/camera.py b/homeassistant/components/dremel_3d_printer/camera.py index 7468400ec35..dc663844c9c 100644 --- a/homeassistant/components/dremel_3d_printer/camera.py +++ b/homeassistant/components/dremel_3d_printer/camera.py @@ -1,4 +1,5 @@ """Support for Dremel 3D45 Camera.""" + from __future__ import annotations from homeassistant.components.camera import CameraEntityDescription diff --git a/homeassistant/components/dremel_3d_printer/config_flow.py b/homeassistant/components/dremel_3d_printer/config_flow.py index f9dc96b8287..aa4cdb045e7 100644 --- a/homeassistant/components/dremel_3d_printer/config_flow.py +++ b/homeassistant/components/dremel_3d_printer/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Dremel 3D Printer (3D20, 3D40, 3D45).""" + from __future__ import annotations from json.decoder import JSONDecodeError diff --git a/homeassistant/components/dremel_3d_printer/const.py b/homeassistant/components/dremel_3d_printer/const.py index cccdeb937cb..f060daf0d57 100644 --- a/homeassistant/components/dremel_3d_printer/const.py +++ b/homeassistant/components/dremel_3d_printer/const.py @@ -1,4 +1,5 @@ """Constants for the Dremel 3D Printer (3D20, 3D40, 3D45) integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/dremel_3d_printer/sensor.py b/homeassistant/components/dremel_3d_printer/sensor.py index 0bcdfa904e8..bda2bb537fd 100644 --- a/homeassistant/components/dremel_3d_printer/sensor.py +++ b/homeassistant/components/dremel_3d_printer/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring Dremel 3D Printer sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/drop_connect/__init__.py b/homeassistant/components/drop_connect/__init__.py index 7bfab762f99..bc700456398 100644 --- a/homeassistant/components/drop_connect/__init__.py +++ b/homeassistant/components/drop_connect/__init__.py @@ -1,4 +1,5 @@ """The drop_connect integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/drop_connect/config_flow.py b/homeassistant/components/drop_connect/config_flow.py index c315616ceba..476b244f345 100644 --- a/homeassistant/components/drop_connect/config_flow.py +++ b/homeassistant/components/drop_connect/config_flow.py @@ -1,4 +1,5 @@ """Config flow for drop_connect integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/drop_connect/coordinator.py b/homeassistant/components/drop_connect/coordinator.py index e4937ed5f65..0861e091153 100644 --- a/homeassistant/components/drop_connect/coordinator.py +++ b/homeassistant/components/drop_connect/coordinator.py @@ -1,4 +1,5 @@ """DROP device data update coordinator object.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/drop_connect/entity.py b/homeassistant/components/drop_connect/entity.py index 85c506b19a3..459552e8511 100644 --- a/homeassistant/components/drop_connect/entity.py +++ b/homeassistant/components/drop_connect/entity.py @@ -1,4 +1,5 @@ """Base entity class for DROP entities.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/drop_connect/sensor.py b/homeassistant/components/drop_connect/sensor.py index c9450440473..249922a4b3c 100644 --- a/homeassistant/components/drop_connect/sensor.py +++ b/homeassistant/components/drop_connect/sensor.py @@ -1,4 +1,5 @@ """Support for DROP sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/dsmr/__init__.py b/homeassistant/components/dsmr/__init__.py index f3546fc0a00..00252b98517 100644 --- a/homeassistant/components/dsmr/__init__.py +++ b/homeassistant/components/dsmr/__init__.py @@ -1,4 +1,5 @@ """The dsmr component.""" + from __future__ import annotations from asyncio import CancelledError diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 422ae285b4c..49e1818edcc 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for DSMR integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index 9504929c5a9..d40581bcdee 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -1,4 +1,5 @@ """Constants for the DSMR integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index ad1c4e64c55..766603f740b 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -1,4 +1,5 @@ """Support for Dutch Smart Meter (also known as Smartmeter or P1 port).""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/dsmr_reader/config_flow.py b/homeassistant/components/dsmr_reader/config_flow.py index 87b0e562efd..4f2485ec647 100644 --- a/homeassistant/components/dsmr_reader/config_flow.py +++ b/homeassistant/components/dsmr_reader/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure DSMR Reader.""" + from __future__ import annotations from collections.abc import Awaitable diff --git a/homeassistant/components/dsmr_reader/definitions.py b/homeassistant/components/dsmr_reader/definitions.py index 2b5b995eabd..901dfc047f5 100644 --- a/homeassistant/components/dsmr_reader/definitions.py +++ b/homeassistant/components/dsmr_reader/definitions.py @@ -1,4 +1,5 @@ """Definitions for DSMR Reader sensors added to MQTT.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/dsmr_reader/sensor.py b/homeassistant/components/dsmr_reader/sensor.py index c618995ed45..3c07ad65de6 100644 --- a/homeassistant/components/dsmr_reader/sensor.py +++ b/homeassistant/components/dsmr_reader/sensor.py @@ -1,4 +1,5 @@ """Support for DSMR Reader through MQTT.""" + from __future__ import annotations from homeassistant.components import mqtt diff --git a/homeassistant/components/dte_energy_bridge/sensor.py b/homeassistant/components/dte_energy_bridge/sensor.py index 34c78ed824b..c33bb37e468 100644 --- a/homeassistant/components/dte_energy_bridge/sensor.py +++ b/homeassistant/components/dte_energy_bridge/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring energy usage using the DTE energy bridge.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/dublin_bus_transport/sensor.py b/homeassistant/components/dublin_bus_transport/sensor.py index b50bd604763..3f9c57456f8 100644 --- a/homeassistant/components/dublin_bus_transport/sensor.py +++ b/homeassistant/components/dublin_bus_transport/sensor.py @@ -3,6 +3,7 @@ For more info on the API see : https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus-bus-eireann-luas-and-irish-rail/resource/4b9f2c4f-6bf5-4958-a43a-f12dab04cf61 """ + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index c0c3b14566c..73dd5960631 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -1,4 +1,5 @@ """Integrate with DuckDNS.""" + from __future__ import annotations from collections.abc import Callable, Coroutine, Sequence diff --git a/homeassistant/components/dunehd/__init__.py b/homeassistant/components/dunehd/__init__.py index c97e27f4017..27e9e749472 100644 --- a/homeassistant/components/dunehd/__init__.py +++ b/homeassistant/components/dunehd/__init__.py @@ -1,4 +1,5 @@ """The Dune HD component.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/dunehd/config_flow.py b/homeassistant/components/dunehd/config_flow.py index 718d5b4ee6f..43e919ee42c 100644 --- a/homeassistant/components/dunehd/config_flow.py +++ b/homeassistant/components/dunehd/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Dune HD integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/dunehd/const.py b/homeassistant/components/dunehd/const.py index 1cc89cf2028..b4aa34ee72c 100644 --- a/homeassistant/components/dunehd/const.py +++ b/homeassistant/components/dunehd/const.py @@ -1,4 +1,5 @@ """Constants for Dune HD integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index ff7f78d537b..ded23ea4669 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -1,4 +1,5 @@ """Dune HD implementation of the media player.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/duotecno/__init__.py b/homeassistant/components/duotecno/__init__.py index 288210c7280..1873db45226 100644 --- a/homeassistant/components/duotecno/__init__.py +++ b/homeassistant/components/duotecno/__init__.py @@ -1,4 +1,5 @@ """The duotecno integration.""" + from __future__ import annotations from duotecno.controller import PyDuotecno diff --git a/homeassistant/components/duotecno/binary_sensor.py b/homeassistant/components/duotecno/binary_sensor.py index 60578adf6a7..10c807a8023 100644 --- a/homeassistant/components/duotecno/binary_sensor.py +++ b/homeassistant/components/duotecno/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Duotecno binary sensors.""" + from __future__ import annotations from duotecno.controller import PyDuotecno diff --git a/homeassistant/components/duotecno/climate.py b/homeassistant/components/duotecno/climate.py index 3df80721af4..77b602c8716 100644 --- a/homeassistant/components/duotecno/climate.py +++ b/homeassistant/components/duotecno/climate.py @@ -1,4 +1,5 @@ """Support for Duotecno climate devices.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/duotecno/config_flow.py b/homeassistant/components/duotecno/config_flow.py index 069988dbb17..44675d6bbde 100644 --- a/homeassistant/components/duotecno/config_flow.py +++ b/homeassistant/components/duotecno/config_flow.py @@ -1,4 +1,5 @@ """Config flow for duotecno integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/duotecno/const.py b/homeassistant/components/duotecno/const.py index 6bffe2358e1..964e0e91d1b 100644 --- a/homeassistant/components/duotecno/const.py +++ b/homeassistant/components/duotecno/const.py @@ -1,4 +1,5 @@ """Constants for the duotecno integration.""" + from typing import Final DOMAIN: Final = "duotecno" diff --git a/homeassistant/components/duotecno/cover.py b/homeassistant/components/duotecno/cover.py index b8802c77304..1c4f7d70fc5 100644 --- a/homeassistant/components/duotecno/cover.py +++ b/homeassistant/components/duotecno/cover.py @@ -1,4 +1,5 @@ """Support for Velbus covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/duotecno/entity.py b/homeassistant/components/duotecno/entity.py index 85566b3ebad..86f61c8a73c 100644 --- a/homeassistant/components/duotecno/entity.py +++ b/homeassistant/components/duotecno/entity.py @@ -1,4 +1,5 @@ """Support for Velbus devices.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/duotecno/light.py b/homeassistant/components/duotecno/light.py index 851dd64bfb2..57635ac2bc2 100644 --- a/homeassistant/components/duotecno/light.py +++ b/homeassistant/components/duotecno/light.py @@ -1,4 +1,5 @@ """Support for Duotecno lights.""" + from typing import Any from duotecno.controller import PyDuotecno diff --git a/homeassistant/components/duotecno/switch.py b/homeassistant/components/duotecno/switch.py index d43f82fc657..b3a87786d4e 100644 --- a/homeassistant/components/duotecno/switch.py +++ b/homeassistant/components/duotecno/switch.py @@ -1,4 +1,5 @@ """Support for Duotecno switches.""" + from typing import Any from duotecno.controller import PyDuotecno diff --git a/homeassistant/components/dweet/__init__.py b/homeassistant/components/dweet/__init__.py index eb809ff47df..c1232bab2cf 100644 --- a/homeassistant/components/dweet/__init__.py +++ b/homeassistant/components/dweet/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to Dweet.io.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/dweet/sensor.py b/homeassistant/components/dweet/sensor.py index 8a1b5a1bc6c..79e25bec0c1 100644 --- a/homeassistant/components/dweet/sensor.py +++ b/homeassistant/components/dweet/sensor.py @@ -1,4 +1,5 @@ """Support for showing values from Dweet.io.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 77880fd74cb..e1f0b277945 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -1,4 +1,5 @@ """Support for the Dynalite networks.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index 82666f20a40..2245364b0b7 100644 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -1,4 +1,5 @@ """Code to handle a Dynalite bridge.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index 2bb4327f517..3ae4828b668 100644 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Dynalite hub.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index 25d18dd92e8..00edc26f1ab 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -1,4 +1,5 @@ """Convert the HA config to the dynalite config.""" + from __future__ import annotations from types import MappingProxyType diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py index baf4c12a4c5..869695d43f7 100644 --- a/homeassistant/components/dynalite/dynalitebase.py +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -1,4 +1,5 @@ """Support for the Dynalite devices as entities.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/eafm/config_flow.py b/homeassistant/components/eafm/config_flow.py index a29905a00b0..0345d2acf94 100644 --- a/homeassistant/components/eafm/config_flow.py +++ b/homeassistant/components/eafm/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure flood monitoring gauges.""" + from aioeafm import get_stations import voluptuous as vol diff --git a/homeassistant/components/easyenergy/__init__.py b/homeassistant/components/easyenergy/__init__.py index e941c78b1fb..e520631158a 100644 --- a/homeassistant/components/easyenergy/__init__.py +++ b/homeassistant/components/easyenergy/__init__.py @@ -1,4 +1,5 @@ """The easyEnergy integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/easyenergy/config_flow.py b/homeassistant/components/easyenergy/config_flow.py index 9a196061391..07e94060b74 100644 --- a/homeassistant/components/easyenergy/config_flow.py +++ b/homeassistant/components/easyenergy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for easyEnergy integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/easyenergy/const.py b/homeassistant/components/easyenergy/const.py index 1de7ac0bd58..4670e9c4edd 100644 --- a/homeassistant/components/easyenergy/const.py +++ b/homeassistant/components/easyenergy/const.py @@ -1,4 +1,5 @@ """Constants for the easyEnergy integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/easyenergy/coordinator.py b/homeassistant/components/easyenergy/coordinator.py index 3996fd4d16a..8c1c593af93 100644 --- a/homeassistant/components/easyenergy/coordinator.py +++ b/homeassistant/components/easyenergy/coordinator.py @@ -1,4 +1,5 @@ """The Coordinator for easyEnergy.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/easyenergy/diagnostics.py b/homeassistant/components/easyenergy/diagnostics.py index 0c885174872..d6912e1c926 100644 --- a/homeassistant/components/easyenergy/diagnostics.py +++ b/homeassistant/components/easyenergy/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for easyEnergy.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/easyenergy/sensor.py b/homeassistant/components/easyenergy/sensor.py index 04bdeb0f747..65fe2558d46 100644 --- a/homeassistant/components/easyenergy/sensor.py +++ b/homeassistant/components/easyenergy/sensor.py @@ -1,4 +1,5 @@ """Support for easyEnergy sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/easyenergy/services.py b/homeassistant/components/easyenergy/services.py index 95763e5db25..402f3195dc7 100644 --- a/homeassistant/components/easyenergy/services.py +++ b/homeassistant/components/easyenergy/services.py @@ -1,4 +1,5 @@ """Services for easyEnergy integration.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/ebox/sensor.py b/homeassistant/components/ebox/sensor.py index aaa8c5ceb0c..aff154cca02 100644 --- a/homeassistant/components/ebox/sensor.py +++ b/homeassistant/components/ebox/sensor.py @@ -2,6 +2,7 @@ Get data from 'My Usage Page' page: https://client.ebox.ca/myusage """ + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ebusd/const.py b/homeassistant/components/ebusd/const.py index 9bc489f40f2..4fb3032e19b 100644 --- a/homeassistant/components/ebusd/const.py +++ b/homeassistant/components/ebusd/const.py @@ -1,4 +1,5 @@ """Constants for ebus component.""" + from homeassistant.components.sensor import SensorDeviceClass from homeassistant.const import ( PERCENTAGE, diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index 923f94f705d..7b2120f6799 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -1,4 +1,5 @@ """Support for Ebusd sensors.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/ecoal_boiler/sensor.py b/homeassistant/components/ecoal_boiler/sensor.py index 06dfec9ff01..4ce52d283fc 100644 --- a/homeassistant/components/ecoal_boiler/sensor.py +++ b/homeassistant/components/ecoal_boiler/sensor.py @@ -1,4 +1,5 @@ """Allows reading temperatures from ecoal/esterownik.pl controller.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index b2b46beb26b..7fede88bc2b 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -1,4 +1,5 @@ """Allows to configuration ecoal (esterownik.pl) pumps as switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ecobee/__init__.py b/homeassistant/components/ecobee/__init__.py index 962eebc2a33..8083d0efcb4 100644 --- a/homeassistant/components/ecobee/__init__.py +++ b/homeassistant/components/ecobee/__init__.py @@ -1,4 +1,5 @@ """Support for ecobee.""" + from datetime import timedelta from pyecobee import ECOBEE_API_KEY, ECOBEE_REFRESH_TOKEN, Ecobee, ExpiredTokenError diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py index 4ad0190e01a..18e09178581 100644 --- a/homeassistant/components/ecobee/binary_sensor.py +++ b/homeassistant/components/ecobee/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Ecobee binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 58a3cb09997..ae076d42e1d 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -1,4 +1,5 @@ """Support for Ecobee Thermostats.""" + from __future__ import annotations import collections diff --git a/homeassistant/components/ecobee/config_flow.py b/homeassistant/components/ecobee/config_flow.py index 5f0066b6c10..dd5c2c62c85 100644 --- a/homeassistant/components/ecobee/config_flow.py +++ b/homeassistant/components/ecobee/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure ecobee.""" + from pyecobee import ( ECOBEE_API_KEY, ECOBEE_CONFIG_FILENAME, diff --git a/homeassistant/components/ecobee/entity.py b/homeassistant/components/ecobee/entity.py index 24fe11d17da..08ec1968999 100644 --- a/homeassistant/components/ecobee/entity.py +++ b/homeassistant/components/ecobee/entity.py @@ -1,4 +1,5 @@ """Base classes shared among Ecobee entities.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ecobee/humidifier.py b/homeassistant/components/ecobee/humidifier.py index d8ebd3d77d8..0de7de2e803 100644 --- a/homeassistant/components/ecobee/humidifier.py +++ b/homeassistant/components/ecobee/humidifier.py @@ -1,4 +1,5 @@ """Support for using humidifier with ecobee thermostats.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ecobee/notify.py b/homeassistant/components/ecobee/notify.py index 1372cc9f64d..b2f6ccb05c8 100644 --- a/homeassistant/components/ecobee/notify.py +++ b/homeassistant/components/ecobee/notify.py @@ -1,4 +1,5 @@ """Support for Ecobee Send Message service.""" + from __future__ import annotations from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService diff --git a/homeassistant/components/ecobee/number.py b/homeassistant/components/ecobee/number.py index a4106f196a1..974dc3ca132 100644 --- a/homeassistant/components/ecobee/number.py +++ b/homeassistant/components/ecobee/number.py @@ -1,4 +1,5 @@ """Support for using number with ecobee thermostats.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index ce2f0f7beb8..3e2e984cccb 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -1,4 +1,5 @@ """Support for Ecobee sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ecobee/util.py b/homeassistant/components/ecobee/util.py index ac30b3bb660..1eba02f9795 100644 --- a/homeassistant/components/ecobee/util.py +++ b/homeassistant/components/ecobee/util.py @@ -1,4 +1,5 @@ """Validation utility functions for ecobee services.""" + from datetime import datetime import voluptuous as vol diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 3e71b05af1d..53b6b0bb345 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -1,4 +1,5 @@ """Support for displaying weather info from Ecobee API.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ecoforest/__init__.py b/homeassistant/components/ecoforest/__init__.py index 7b4dd08610a..4d5aaa40576 100644 --- a/homeassistant/components/ecoforest/__init__.py +++ b/homeassistant/components/ecoforest/__init__.py @@ -1,4 +1,5 @@ """The Ecoforest integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ecoforest/config_flow.py b/homeassistant/components/ecoforest/config_flow.py index 1e535870e1c..91260f0811e 100644 --- a/homeassistant/components/ecoforest/config_flow.py +++ b/homeassistant/components/ecoforest/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ecoforest integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ecoforest/entity.py b/homeassistant/components/ecoforest/entity.py index 901ed1bf4bf..539b0e55e19 100644 --- a/homeassistant/components/ecoforest/entity.py +++ b/homeassistant/components/ecoforest/entity.py @@ -1,4 +1,5 @@ """Base Entity for Ecoforest.""" + from __future__ import annotations from pyecoforest.models.device import Device diff --git a/homeassistant/components/ecoforest/number.py b/homeassistant/components/ecoforest/number.py index 046e780dc2b..db3275c1fcc 100644 --- a/homeassistant/components/ecoforest/number.py +++ b/homeassistant/components/ecoforest/number.py @@ -1,4 +1,5 @@ """Support for Ecoforest number platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ecoforest/sensor.py b/homeassistant/components/ecoforest/sensor.py index 2b698602cff..997b02436cc 100644 --- a/homeassistant/components/ecoforest/sensor.py +++ b/homeassistant/components/ecoforest/sensor.py @@ -1,4 +1,5 @@ """Support for Ecoforest sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ecoforest/switch.py b/homeassistant/components/ecoforest/switch.py index 378c27924ae..f59970aa751 100644 --- a/homeassistant/components/ecoforest/switch.py +++ b/homeassistant/components/ecoforest/switch.py @@ -1,4 +1,5 @@ """Switch platform for Ecoforest.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/econet/binary_sensor.py b/homeassistant/components/econet/binary_sensor.py index 2a54a45eba2..3f8e17a5fbe 100644 --- a/homeassistant/components/econet/binary_sensor.py +++ b/homeassistant/components/econet/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Rheem EcoNet water heaters.""" + from __future__ import annotations from pyeconet.equipment import EquipmentType diff --git a/homeassistant/components/econet/climate.py b/homeassistant/components/econet/climate.py index ac812a07566..508ad5bbcb7 100644 --- a/homeassistant/components/econet/climate.py +++ b/homeassistant/components/econet/climate.py @@ -1,4 +1,5 @@ """Support for Rheem EcoNet thermostats.""" + from typing import Any from pyeconet.equipment import EquipmentType diff --git a/homeassistant/components/econet/config_flow.py b/homeassistant/components/econet/config_flow.py index 237047b4974..81a5fdf75f0 100644 --- a/homeassistant/components/econet/config_flow.py +++ b/homeassistant/components/econet/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the EcoNet component.""" + from pyeconet import EcoNetApiInterface from pyeconet.errors import InvalidCredentialsError, PyeconetError import voluptuous as vol diff --git a/homeassistant/components/econet/sensor.py b/homeassistant/components/econet/sensor.py index cfcab158277..f2d4ab304a5 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -1,4 +1,5 @@ """Support for Rheem EcoNet water heaters.""" + from __future__ import annotations from pyeconet.equipment import Equipment, EquipmentType diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py index a99ab087729..5db339b4411 100644 --- a/homeassistant/components/econet/water_heater.py +++ b/homeassistant/components/econet/water_heater.py @@ -1,4 +1,5 @@ """Support for Rheem EcoNet water heaters.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/ecovacs/binary_sensor.py b/homeassistant/components/ecovacs/binary_sensor.py index f04f2110003..cc401cc3ca0 100644 --- a/homeassistant/components/ecovacs/binary_sensor.py +++ b/homeassistant/components/ecovacs/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor module.""" + from collections.abc import Callable from dataclasses import dataclass from typing import Generic diff --git a/homeassistant/components/ecovacs/button.py b/homeassistant/components/ecovacs/button.py index 0e011726010..48636d56834 100644 --- a/homeassistant/components/ecovacs/button.py +++ b/homeassistant/components/ecovacs/button.py @@ -1,4 +1,5 @@ """Ecovacs button module.""" + from dataclasses import dataclass from deebot_client.capabilities import ( diff --git a/homeassistant/components/ecovacs/config_flow.py b/homeassistant/components/ecovacs/config_flow.py index fbf9ac0911a..8cf82f6237c 100644 --- a/homeassistant/components/ecovacs/config_flow.py +++ b/homeassistant/components/ecovacs/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ecovacs mqtt integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ecovacs/const.py b/homeassistant/components/ecovacs/const.py index dc055cee519..e5ef0760182 100644 --- a/homeassistant/components/ecovacs/const.py +++ b/homeassistant/components/ecovacs/const.py @@ -1,4 +1,5 @@ """Ecovacs constants.""" + from enum import StrEnum from deebot_client.events import LifeSpan diff --git a/homeassistant/components/ecovacs/controller.py b/homeassistant/components/ecovacs/controller.py index 6ba5dcdba6c..5defcdf861f 100644 --- a/homeassistant/components/ecovacs/controller.py +++ b/homeassistant/components/ecovacs/controller.py @@ -1,4 +1,5 @@ """Controller module.""" + from __future__ import annotations from collections.abc import Generator, Mapping diff --git a/homeassistant/components/ecovacs/diagnostics.py b/homeassistant/components/ecovacs/diagnostics.py index 6493dce2712..9340841223e 100644 --- a/homeassistant/components/ecovacs/diagnostics.py +++ b/homeassistant/components/ecovacs/diagnostics.py @@ -1,4 +1,5 @@ """Ecovacs diagnostics.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ecovacs/entity.py b/homeassistant/components/ecovacs/entity.py index 817172016bc..4497f82d964 100644 --- a/homeassistant/components/ecovacs/entity.py +++ b/homeassistant/components/ecovacs/entity.py @@ -1,4 +1,5 @@ """Ecovacs mqtt entity module.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/ecovacs/number.py b/homeassistant/components/ecovacs/number.py index 0dc379c68f0..e53f7e6aae0 100644 --- a/homeassistant/components/ecovacs/number.py +++ b/homeassistant/components/ecovacs/number.py @@ -1,4 +1,5 @@ """Ecovacs number module.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ecovacs/select.py b/homeassistant/components/ecovacs/select.py index 00e7134266b..8a3def54e28 100644 --- a/homeassistant/components/ecovacs/select.py +++ b/homeassistant/components/ecovacs/select.py @@ -1,4 +1,5 @@ """Ecovacs select entity module.""" + from collections.abc import Callable from dataclasses import dataclass from typing import Any, Generic diff --git a/homeassistant/components/ecovacs/sensor.py b/homeassistant/components/ecovacs/sensor.py index 6efc9ec0385..b89a4e3ecc4 100644 --- a/homeassistant/components/ecovacs/sensor.py +++ b/homeassistant/components/ecovacs/sensor.py @@ -1,4 +1,5 @@ """Ecovacs sensor module.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ecovacs/switch.py b/homeassistant/components/ecovacs/switch.py index 316ed5427ba..0d2f8f2024f 100644 --- a/homeassistant/components/ecovacs/switch.py +++ b/homeassistant/components/ecovacs/switch.py @@ -1,4 +1,5 @@ """Ecovacs switch module.""" + from dataclasses import dataclass from typing import Any diff --git a/homeassistant/components/ecovacs/util.py b/homeassistant/components/ecovacs/util.py index b3e0d4d96be..ea0c75b748b 100644 --- a/homeassistant/components/ecovacs/util.py +++ b/homeassistant/components/ecovacs/util.py @@ -1,4 +1,5 @@ """Ecovacs util functions.""" + from __future__ import annotations import random diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 0d65d58d84c..523f301db28 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -1,4 +1,5 @@ """Support for Ecovacs Ecovacs Vacuums.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/ecowitt/__init__.py b/homeassistant/components/ecowitt/__init__.py index eaf2441ffac..0c330bc3f33 100644 --- a/homeassistant/components/ecowitt/__init__.py +++ b/homeassistant/components/ecowitt/__init__.py @@ -1,4 +1,5 @@ """The Ecowitt Weather Station Component.""" + from __future__ import annotations from aioecowitt import EcoWittListener diff --git a/homeassistant/components/ecowitt/config_flow.py b/homeassistant/components/ecowitt/config_flow.py index cc32405072c..b131cbea6ae 100644 --- a/homeassistant/components/ecowitt/config_flow.py +++ b/homeassistant/components/ecowitt/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ecowitt.""" + from __future__ import annotations import secrets diff --git a/homeassistant/components/ecowitt/diagnostics.py b/homeassistant/components/ecowitt/diagnostics.py index 96fa020667b..e4aecc1c07b 100644 --- a/homeassistant/components/ecowitt/diagnostics.py +++ b/homeassistant/components/ecowitt/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for EcoWitt.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ecowitt/entity.py b/homeassistant/components/ecowitt/entity.py index cf62cfb2d94..d6e268c3578 100644 --- a/homeassistant/components/ecowitt/entity.py +++ b/homeassistant/components/ecowitt/entity.py @@ -1,4 +1,5 @@ """The Ecowitt Weather Station Entity.""" + from __future__ import annotations import time diff --git a/homeassistant/components/ecowitt/sensor.py b/homeassistant/components/ecowitt/sensor.py index 4bcdd2461cd..5f2f08f2519 100644 --- a/homeassistant/components/ecowitt/sensor.py +++ b/homeassistant/components/ecowitt/sensor.py @@ -1,4 +1,5 @@ """Support for Ecowitt Weather Stations.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/eddystone_temperature/sensor.py b/homeassistant/components/eddystone_temperature/sensor.py index 347ee1b242f..b136b193686 100644 --- a/homeassistant/components/eddystone_temperature/sensor.py +++ b/homeassistant/components/eddystone_temperature/sensor.py @@ -3,6 +3,7 @@ Your beacons must be configured to transmit UID (for identification) and TLM (for temperature) frames. """ + from __future__ import annotations import logging diff --git a/homeassistant/components/edimax/switch.py b/homeassistant/components/edimax/switch.py index 34f6a500917..61f3e6f4538 100644 --- a/homeassistant/components/edimax/switch.py +++ b/homeassistant/components/edimax/switch.py @@ -1,4 +1,5 @@ """Support for Edimax switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index 0126c87b8cd..4474893d9b6 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -1,4 +1,5 @@ """Support for EDL21 Smart Meters.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/efergy/__init__.py b/homeassistant/components/efergy/__init__.py index 0ca5bf1d8f7..3bfd37392ad 100644 --- a/homeassistant/components/efergy/__init__.py +++ b/homeassistant/components/efergy/__init__.py @@ -1,4 +1,5 @@ """The Efergy integration.""" + from __future__ import annotations from pyefergy import Efergy, exceptions diff --git a/homeassistant/components/efergy/config_flow.py b/homeassistant/components/efergy/config_flow.py index 487bd6ea510..8e23925d193 100644 --- a/homeassistant/components/efergy/config_flow.py +++ b/homeassistant/components/efergy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Efergy integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/efergy/const.py b/homeassistant/components/efergy/const.py index 5a0ca11693b..f6d26a3430d 100644 --- a/homeassistant/components/efergy/const.py +++ b/homeassistant/components/efergy/const.py @@ -1,4 +1,5 @@ """Constants for the Efergy integration.""" + from datetime import timedelta import logging from typing import Final diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index dd8752dde7f..27d1da976b8 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -1,4 +1,5 @@ """Support for Efergy sensors.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index 60b80fffd23..c58396ae947 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -1,4 +1,5 @@ """Interfaces with Egardia/Woonveilig alarm control panel.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py index 021111e53b3..53505f58d3b 100644 --- a/homeassistant/components/egardia/binary_sensor.py +++ b/homeassistant/components/egardia/binary_sensor.py @@ -1,4 +1,5 @@ """Interfaces with Egardia/Woonveilig alarm control panel.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/eight_sleep/__init__.py b/homeassistant/components/eight_sleep/__init__.py index ab5eff3b60f..9df39bbe314 100644 --- a/homeassistant/components/eight_sleep/__init__.py +++ b/homeassistant/components/eight_sleep/__init__.py @@ -1,4 +1,5 @@ """The Eight Sleep integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry, ConfigEntryState diff --git a/homeassistant/components/electrasmart/__init__.py b/homeassistant/components/electrasmart/__init__.py index 6fb9c35757f..b8e5eb1bdd8 100644 --- a/homeassistant/components/electrasmart/__init__.py +++ b/homeassistant/components/electrasmart/__init__.py @@ -1,4 +1,5 @@ """The Electra Air Conditioner integration.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/electrasmart/config_flow.py b/homeassistant/components/electrasmart/config_flow.py index 818480080c9..a2e6889c346 100644 --- a/homeassistant/components/electrasmart/config_flow.py +++ b/homeassistant/components/electrasmart/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Electra Air Conditioner integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/electric_kiwi/__init__.py b/homeassistant/components/electric_kiwi/__init__.py index 00ff6749364..8c9a0b3950e 100644 --- a/homeassistant/components/electric_kiwi/__init__.py +++ b/homeassistant/components/electric_kiwi/__init__.py @@ -1,4 +1,5 @@ """The Electric Kiwi integration.""" + from __future__ import annotations import aiohttp diff --git a/homeassistant/components/electric_kiwi/config_flow.py b/homeassistant/components/electric_kiwi/config_flow.py index 4dac4041c1f..5be3edeaa66 100644 --- a/homeassistant/components/electric_kiwi/config_flow.py +++ b/homeassistant/components/electric_kiwi/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Electric Kiwi.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/electric_kiwi/oauth2.py b/homeassistant/components/electric_kiwi/oauth2.py index ce3e473159a..864550991f5 100644 --- a/homeassistant/components/electric_kiwi/oauth2.py +++ b/homeassistant/components/electric_kiwi/oauth2.py @@ -1,4 +1,5 @@ """OAuth2 implementations for Toon.""" + from __future__ import annotations import base64 diff --git a/homeassistant/components/electric_kiwi/select.py b/homeassistant/components/electric_kiwi/select.py index 5905efc1604..90b31aa7511 100644 --- a/homeassistant/components/electric_kiwi/select.py +++ b/homeassistant/components/electric_kiwi/select.py @@ -1,4 +1,5 @@ """Support for Electric Kiwi hour of free power.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/electric_kiwi/sensor.py b/homeassistant/components/electric_kiwi/sensor.py index 5f0351fd39b..308201a9458 100644 --- a/homeassistant/components/electric_kiwi/sensor.py +++ b/homeassistant/components/electric_kiwi/sensor.py @@ -1,4 +1,5 @@ """Support for Electric Kiwi sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/elgato/__init__.py b/homeassistant/components/elgato/__init__.py index 7584695240b..8d6af325213 100644 --- a/homeassistant/components/elgato/__init__.py +++ b/homeassistant/components/elgato/__init__.py @@ -1,4 +1,5 @@ """Support for Elgato Lights.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/elgato/button.py b/homeassistant/components/elgato/button.py index 9747496c126..47e24ca245a 100644 --- a/homeassistant/components/elgato/button.py +++ b/homeassistant/components/elgato/button.py @@ -1,4 +1,5 @@ """Support for Elgato button.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index 36f30ada1ab..5329fcee90a 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Elgato Light integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elgato/const.py b/homeassistant/components/elgato/const.py index e9a93387e63..114bb01583a 100644 --- a/homeassistant/components/elgato/const.py +++ b/homeassistant/components/elgato/const.py @@ -1,4 +1,5 @@ """Constants for the Elgato Light integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/elgato/coordinator.py b/homeassistant/components/elgato/coordinator.py index 0dda6bad292..c2bc79491a1 100644 --- a/homeassistant/components/elgato/coordinator.py +++ b/homeassistant/components/elgato/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for Elgato.""" + from dataclasses import dataclass from elgato import BatteryInfo, Elgato, ElgatoConnectionError, Info, Settings, State diff --git a/homeassistant/components/elgato/diagnostics.py b/homeassistant/components/elgato/diagnostics.py index 46730b8f005..91f5c9a8319 100644 --- a/homeassistant/components/elgato/diagnostics.py +++ b/homeassistant/components/elgato/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Elgato.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elgato/entity.py b/homeassistant/components/elgato/entity.py index 3f46b51d7b7..42920c3d28e 100644 --- a/homeassistant/components/elgato/entity.py +++ b/homeassistant/components/elgato/entity.py @@ -1,4 +1,5 @@ """Base entity for the Elgato integration.""" + from __future__ import annotations from homeassistant.const import ATTR_CONNECTIONS, CONF_MAC diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index f74ec04476f..100a04fb6fb 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -1,4 +1,5 @@ """Support for Elgato lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elgato/sensor.py b/homeassistant/components/elgato/sensor.py index b683b80f5fa..76d88df3fb9 100644 --- a/homeassistant/components/elgato/sensor.py +++ b/homeassistant/components/elgato/sensor.py @@ -1,4 +1,5 @@ """Support for Elgato sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/elgato/switch.py b/homeassistant/components/elgato/switch.py index 1396d74e0ce..0d20ae95e03 100644 --- a/homeassistant/components/elgato/switch.py +++ b/homeassistant/components/elgato/switch.py @@ -1,4 +1,5 @@ """Support for Elgato switches.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/eliqonline/sensor.py b/homeassistant/components/eliqonline/sensor.py index 2a929db4b0a..2aa0ab15746 100644 --- a/homeassistant/components/eliqonline/sensor.py +++ b/homeassistant/components/eliqonline/sensor.py @@ -1,4 +1,5 @@ """Monitors home energy use for the ELIQ Online service.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index 03f1f80b4f9..3b0c5f02f97 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -1,4 +1,5 @@ """Support the ElkM1 Gold and ElkM1 EZ8 alarm/integration panels.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index bfac466caeb..5752bf82436 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -1,4 +1,5 @@ """Each ElkM1 area will be created as a separate alarm_control_panel.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elkm1/binary_sensor.py b/homeassistant/components/elkm1/binary_sensor.py index 95f9162468e..c04a9d17830 100644 --- a/homeassistant/components/elkm1/binary_sensor.py +++ b/homeassistant/components/elkm1/binary_sensor.py @@ -1,4 +1,5 @@ """Support for control of ElkM1 binary sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index 97b16b14954..76ede0bbdf1 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -1,4 +1,5 @@ """Support for control of Elk-M1 connected thermostats.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index 50a1c795b81..6e4874f7366 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Elk-M1 Control integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/elkm1/discovery.py b/homeassistant/components/elkm1/discovery.py index 83b2d3f113b..8a68b6524b7 100644 --- a/homeassistant/components/elkm1/discovery.py +++ b/homeassistant/components/elkm1/discovery.py @@ -1,4 +1,5 @@ """The elkm1 integration discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index 844e4f3dd15..432d6683de4 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -1,4 +1,5 @@ """Support for control of ElkM1 lighting (X10, UPB, etc).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elkm1/logbook.py b/homeassistant/components/elkm1/logbook.py index e86e58d23fd..b31c537d93f 100644 --- a/homeassistant/components/elkm1/logbook.py +++ b/homeassistant/components/elkm1/logbook.py @@ -1,4 +1,5 @@ """Describe elkm1 logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/elkm1/models.py b/homeassistant/components/elkm1/models.py index 9f784951c11..7dd3313782e 100644 --- a/homeassistant/components/elkm1/models.py +++ b/homeassistant/components/elkm1/models.py @@ -1,4 +1,5 @@ """The elkm1 integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/elkm1/scene.py b/homeassistant/components/elkm1/scene.py index 9cb0c62ff77..9658052f3e5 100644 --- a/homeassistant/components/elkm1/scene.py +++ b/homeassistant/components/elkm1/scene.py @@ -1,4 +1,5 @@ """Support for control of ElkM1 tasks ("macros").""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elkm1/sensor.py b/homeassistant/components/elkm1/sensor.py index 59b52ff2b60..27a6c1596eb 100644 --- a/homeassistant/components/elkm1/sensor.py +++ b/homeassistant/components/elkm1/sensor.py @@ -1,4 +1,5 @@ """Support for control of ElkM1 sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index b4080adc698..3224f9affcf 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -1,4 +1,5 @@ """Support for control of ElkM1 outputs (relays).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/elmax/__init__.py b/homeassistant/components/elmax/__init__.py index 95b0588e332..ce8ce68d953 100644 --- a/homeassistant/components/elmax/__init__.py +++ b/homeassistant/components/elmax/__init__.py @@ -1,4 +1,5 @@ """The elmax-cloud integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/elmax/alarm_control_panel.py b/homeassistant/components/elmax/alarm_control_panel.py index 269cc989b51..b9a895f6967 100644 --- a/homeassistant/components/elmax/alarm_control_panel.py +++ b/homeassistant/components/elmax/alarm_control_panel.py @@ -1,4 +1,5 @@ """Elmax sensor platform.""" + from __future__ import annotations from elmax_api.model.alarm_status import AlarmArmStatus, AlarmStatus diff --git a/homeassistant/components/elmax/binary_sensor.py b/homeassistant/components/elmax/binary_sensor.py index 5798b7ec59e..b3bdc174246 100644 --- a/homeassistant/components/elmax/binary_sensor.py +++ b/homeassistant/components/elmax/binary_sensor.py @@ -1,4 +1,5 @@ """Elmax sensor platform.""" + from __future__ import annotations from elmax_api.model.panel import PanelStatus diff --git a/homeassistant/components/elmax/common.py b/homeassistant/components/elmax/common.py index 6f91dae048d..39b6797fc58 100644 --- a/homeassistant/components/elmax/common.py +++ b/homeassistant/components/elmax/common.py @@ -1,4 +1,5 @@ """Elmax integration common classes and utilities.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/elmax/config_flow.py b/homeassistant/components/elmax/config_flow.py index 2e0c91d7785..d7c6e74ed14 100644 --- a/homeassistant/components/elmax/config_flow.py +++ b/homeassistant/components/elmax/config_flow.py @@ -1,4 +1,5 @@ """Config flow for elmax-cloud integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/elmax/const.py b/homeassistant/components/elmax/const.py index 8ac5fbdad51..d87ccbd014e 100644 --- a/homeassistant/components/elmax/const.py +++ b/homeassistant/components/elmax/const.py @@ -1,4 +1,5 @@ """Constants for the elmax-cloud integration.""" + from homeassistant.const import Platform DOMAIN = "elmax" diff --git a/homeassistant/components/elmax/cover.py b/homeassistant/components/elmax/cover.py index 5f161c0b279..6113ccd7997 100644 --- a/homeassistant/components/elmax/cover.py +++ b/homeassistant/components/elmax/cover.py @@ -1,4 +1,5 @@ """Elmax cover platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/elv/switch.py b/homeassistant/components/elv/switch.py index b998e2dd737..e790873e368 100644 --- a/homeassistant/components/elv/switch.py +++ b/homeassistant/components/elv/switch.py @@ -1,4 +1,5 @@ """Support for PCA 301 smart switch.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/elvia/__init__.py b/homeassistant/components/elvia/__init__.py index 1f85fe720a7..f1eafe64079 100644 --- a/homeassistant/components/elvia/__init__.py +++ b/homeassistant/components/elvia/__init__.py @@ -1,4 +1,5 @@ """The Elvia integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/elvia/config_flow.py b/homeassistant/components/elvia/config_flow.py index 9e9b8919e4a..4cf311e780e 100644 --- a/homeassistant/components/elvia/config_flow.py +++ b/homeassistant/components/elvia/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Elvia integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/elvia/const.py b/homeassistant/components/elvia/const.py index c4b8e40e73f..1342bd75d6d 100644 --- a/homeassistant/components/elvia/const.py +++ b/homeassistant/components/elvia/const.py @@ -1,4 +1,5 @@ """Constants for the Elvia integration.""" + from logging import getLogger DOMAIN = "elvia" diff --git a/homeassistant/components/elvia/importer.py b/homeassistant/components/elvia/importer.py index 097db51cab8..4e8b7f716ef 100644 --- a/homeassistant/components/elvia/importer.py +++ b/homeassistant/components/elvia/importer.py @@ -1,4 +1,5 @@ """Importer for the Elvia integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py index 73593ef09a1..22d7939a14e 100644 --- a/homeassistant/components/emby/media_player.py +++ b/homeassistant/components/emby/media_player.py @@ -1,4 +1,5 @@ """Support to interface with the Emby API.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/emoncms/sensor.py b/homeassistant/components/emoncms/sensor.py index f26ed72f44c..746877c4e5f 100644 --- a/homeassistant/components/emoncms/sensor.py +++ b/homeassistant/components/emoncms/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring emoncms feeds.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/emoncms_history/__init__.py b/homeassistant/components/emoncms_history/__init__.py index b4420ec6863..ab3f2671b99 100644 --- a/homeassistant/components/emoncms_history/__init__.py +++ b/homeassistant/components/emoncms_history/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to Emoncms.""" + from datetime import timedelta from http import HTTPStatus import logging diff --git a/homeassistant/components/emonitor/__init__.py b/homeassistant/components/emonitor/__init__.py index 3bc5c7862cb..74d08432f72 100644 --- a/homeassistant/components/emonitor/__init__.py +++ b/homeassistant/components/emonitor/__init__.py @@ -1,4 +1,5 @@ """The SiteSage Emonitor integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/emonitor/sensor.py b/homeassistant/components/emonitor/sensor.py index 1c3011ee28d..551e47a91a4 100644 --- a/homeassistant/components/emonitor/sensor.py +++ b/homeassistant/components/emonitor/sensor.py @@ -1,4 +1,5 @@ """Support for a Emonitor channel sensor.""" + from __future__ import annotations from aioemonitor.monitor import EmonitorChannel, EmonitorStatus diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index ed24fe29c60..9a7ce8369aa 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -1,4 +1,5 @@ """Support for local control of entities by emulating a Philips Hue bridge.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/emulated_hue/config.py b/homeassistant/components/emulated_hue/config.py index 2be89e7214c..cd0617134f6 100644 --- a/homeassistant/components/emulated_hue/config.py +++ b/homeassistant/components/emulated_hue/config.py @@ -1,4 +1,5 @@ """Support for local control of entities by emulating a Philips Hue bridge.""" + from __future__ import annotations from functools import cache diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index c1f853be80d..73242da4b01 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -1,4 +1,5 @@ """Support for a Hue API to control Home Assistant.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index 9f5ca312343..4fb0be81814 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -1,4 +1,5 @@ """Support UPNP discovery method that mimics Hue hubs.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/energy/__init__.py b/homeassistant/components/energy/__init__.py index 7f86b2458cb..63d5df20050 100644 --- a/homeassistant/components/energy/__init__.py +++ b/homeassistant/components/energy/__init__.py @@ -1,4 +1,5 @@ """The Energy integration.""" + from __future__ import annotations from homeassistant.components import frontend diff --git a/homeassistant/components/energy/data.py b/homeassistant/components/energy/data.py index 6f6b481b044..d4533b2fcc8 100644 --- a/homeassistant/components/energy/data.py +++ b/homeassistant/components/energy/data.py @@ -1,4 +1,5 @@ """Energy data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index b684ad5ab8f..638b560a954 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -1,4 +1,5 @@ """Helper sensor for calculating utility costs.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/energy/types.py b/homeassistant/components/energy/types.py index 819ed6ac5a8..d52a15a60c8 100644 --- a/homeassistant/components/energy/types.py +++ b/homeassistant/components/energy/types.py @@ -1,4 +1,5 @@ """Types for the energy platform.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/energy/validate.py b/homeassistant/components/energy/validate.py index f1eb7591e83..2d34f606653 100644 --- a/homeassistant/components/energy/validate.py +++ b/homeassistant/components/energy/validate.py @@ -1,4 +1,5 @@ """Validate the energy preferences provide valid data.""" + from __future__ import annotations from collections.abc import Mapping, Sequence diff --git a/homeassistant/components/energy/websocket_api.py b/homeassistant/components/energy/websocket_api.py index 73aa8330bfe..2dd45a8be4d 100644 --- a/homeassistant/components/energy/websocket_api.py +++ b/homeassistant/components/energy/websocket_api.py @@ -1,4 +1,5 @@ """The Energy websocket API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/energyzero/__init__.py b/homeassistant/components/energyzero/__init__.py index 8878a99e562..3e1bb830cce 100644 --- a/homeassistant/components/energyzero/__init__.py +++ b/homeassistant/components/energyzero/__init__.py @@ -1,4 +1,5 @@ """The EnergyZero integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/energyzero/config_flow.py b/homeassistant/components/energyzero/config_flow.py index af86ef4d216..72a1e376dcf 100644 --- a/homeassistant/components/energyzero/config_flow.py +++ b/homeassistant/components/energyzero/config_flow.py @@ -1,4 +1,5 @@ """Config flow for EnergyZero integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/energyzero/const.py b/homeassistant/components/energyzero/const.py index 03d94facf3b..7079b720f4d 100644 --- a/homeassistant/components/energyzero/const.py +++ b/homeassistant/components/energyzero/const.py @@ -1,4 +1,5 @@ """Constants for the EnergyZero integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/energyzero/coordinator.py b/homeassistant/components/energyzero/coordinator.py index a30509a3840..65955b2ebe6 100644 --- a/homeassistant/components/energyzero/coordinator.py +++ b/homeassistant/components/energyzero/coordinator.py @@ -1,4 +1,5 @@ """The Coordinator for EnergyZero.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/energyzero/diagnostics.py b/homeassistant/components/energyzero/diagnostics.py index b4018a32d3d..35d20fee929 100644 --- a/homeassistant/components/energyzero/diagnostics.py +++ b/homeassistant/components/energyzero/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for EnergyZero.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/energyzero/sensor.py b/homeassistant/components/energyzero/sensor.py index 6438dc6475a..f65f7bd559c 100644 --- a/homeassistant/components/energyzero/sensor.py +++ b/homeassistant/components/energyzero/sensor.py @@ -1,4 +1,5 @@ """Support for EnergyZero sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/energyzero/services.py b/homeassistant/components/energyzero/services.py index 325c443375e..d04912ca99e 100644 --- a/homeassistant/components/energyzero/services.py +++ b/homeassistant/components/energyzero/services.py @@ -1,4 +1,5 @@ """The EnergyZero services.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index b73f8b51c4d..afe8a426c72 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -1,4 +1,5 @@ """Support for Enigma2 media players.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py index 25fc8c4f50a..9ebedc52c00 100644 --- a/homeassistant/components/enocean/binary_sensor.py +++ b/homeassistant/components/enocean/binary_sensor.py @@ -1,4 +1,5 @@ """Support for EnOcean binary sensors.""" + from __future__ import annotations from enocean.utils import combine_hex diff --git a/homeassistant/components/enocean/device.py b/homeassistant/components/enocean/device.py index 220f940f37f..5c12fc12a68 100644 --- a/homeassistant/components/enocean/device.py +++ b/homeassistant/components/enocean/device.py @@ -1,4 +1,5 @@ """Representation of an EnOcean device.""" + from enocean.protocol.packet import Packet from enocean.utils import combine_hex diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py index 2500ad7ce94..937930c4a31 100644 --- a/homeassistant/components/enocean/light.py +++ b/homeassistant/components/enocean/light.py @@ -1,4 +1,5 @@ """Support for EnOcean light sources.""" + from __future__ import annotations import math diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 01847769a73..c22a7d95760 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -1,4 +1,5 @@ """Support for EnOcean sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py index 13920f08e85..4fa75ff9712 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -1,4 +1,5 @@ """Support for EnOcean switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/enphase_envoy/__init__.py b/homeassistant/components/enphase_envoy/__init__.py index 2473c2d9b2f..2407f807eb7 100644 --- a/homeassistant/components/enphase_envoy/__init__.py +++ b/homeassistant/components/enphase_envoy/__init__.py @@ -1,4 +1,5 @@ """The Enphase Envoy integration.""" + from __future__ import annotations from pyenphase import Envoy diff --git a/homeassistant/components/enphase_envoy/binary_sensor.py b/homeassistant/components/enphase_envoy/binary_sensor.py index 7d7e06dadb5..dfa619f07d8 100644 --- a/homeassistant/components/enphase_envoy/binary_sensor.py +++ b/homeassistant/components/enphase_envoy/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Enphase Envoy solar energy monitor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/enphase_envoy/config_flow.py b/homeassistant/components/enphase_envoy/config_flow.py index 743935b906e..13894d423d6 100644 --- a/homeassistant/components/enphase_envoy/config_flow.py +++ b/homeassistant/components/enphase_envoy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Enphase Envoy integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/enphase_envoy/const.py b/homeassistant/components/enphase_envoy/const.py index c5656a65b6f..fe8e7e9ec1f 100644 --- a/homeassistant/components/enphase_envoy/const.py +++ b/homeassistant/components/enphase_envoy/const.py @@ -1,4 +1,5 @@ """The enphase_envoy component.""" + from pyenphase import EnvoyAuthenticationError, EnvoyAuthenticationRequired from homeassistant.const import Platform diff --git a/homeassistant/components/enphase_envoy/coordinator.py b/homeassistant/components/enphase_envoy/coordinator.py index 02a9d2f2491..c8152d44726 100644 --- a/homeassistant/components/enphase_envoy/coordinator.py +++ b/homeassistant/components/enphase_envoy/coordinator.py @@ -1,4 +1,5 @@ """The enphase_envoy component.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/enphase_envoy/diagnostics.py b/homeassistant/components/enphase_envoy/diagnostics.py index 7b8a3e03270..6c1472fedc8 100644 --- a/homeassistant/components/enphase_envoy/diagnostics.py +++ b/homeassistant/components/enphase_envoy/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Enphase Envoy.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/enphase_envoy/entity.py b/homeassistant/components/enphase_envoy/entity.py index 16669bcd098..491951625ee 100644 --- a/homeassistant/components/enphase_envoy/entity.py +++ b/homeassistant/components/enphase_envoy/entity.py @@ -1,4 +1,5 @@ """Support for Enphase Envoy solar energy monitor.""" + from __future__ import annotations from pyenphase import EnvoyData diff --git a/homeassistant/components/enphase_envoy/number.py b/homeassistant/components/enphase_envoy/number.py index 85f99299c71..61d9aabb469 100644 --- a/homeassistant/components/enphase_envoy/number.py +++ b/homeassistant/components/enphase_envoy/number.py @@ -1,4 +1,5 @@ """Number platform for Enphase Envoy solar energy monitor.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/enphase_envoy/select.py b/homeassistant/components/enphase_envoy/select.py index b133c6268d6..98374d16394 100644 --- a/homeassistant/components/enphase_envoy/select.py +++ b/homeassistant/components/enphase_envoy/select.py @@ -1,4 +1,5 @@ """Select platform for Enphase Envoy solar energy monitor.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 5ec71eee645..842c50c6d9d 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -1,4 +1,5 @@ """Support for Enphase Envoy solar energy monitor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/enphase_envoy/switch.py b/homeassistant/components/enphase_envoy/switch.py index 0f9d47ca6df..dbe14ee94ea 100644 --- a/homeassistant/components/enphase_envoy/switch.py +++ b/homeassistant/components/enphase_envoy/switch.py @@ -1,4 +1,5 @@ """Switch platform for Enphase Envoy solar energy monitor.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index e109c25d340..fed556359bd 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -1,4 +1,5 @@ """Real-time information about public transport departures in Norway.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/environment_canada/__init__.py b/homeassistant/components/environment_canada/__init__.py index 925bc42a930..6f47d057e81 100644 --- a/homeassistant/components/environment_canada/__init__.py +++ b/homeassistant/components/environment_canada/__init__.py @@ -1,4 +1,5 @@ """The Environment Canada (EC) component.""" + from datetime import timedelta import logging import xml.etree.ElementTree as et diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 385f973a25a..73032f59ac2 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -1,4 +1,5 @@ """Support for the Environment Canada radar imagery.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/environment_canada/diagnostics.py b/homeassistant/components/environment_canada/diagnostics.py index 297f4664fb0..63f8bb72189 100644 --- a/homeassistant/components/environment_canada/diagnostics.py +++ b/homeassistant/components/environment_canada/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Environment Canada.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index 143090cc227..1c9a6fa1709 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -1,4 +1,5 @@ """Support for the Environment Canada weather service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index 1af05287192..8093603eb2a 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -1,4 +1,5 @@ """Platform for retrieving meteorological data from Environment Canada.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index 273dd4f0d0a..119608bbb2a 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Envisalink-based alarm control panels (Honeywell/DSC).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index f08989c32be..9c0909539bb 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Envisalink zone states- represented as binary sensors.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/envisalink/sensor.py b/homeassistant/components/envisalink/sensor.py index 72a64931070..fcafc23dd37 100644 --- a/homeassistant/components/envisalink/sensor.py +++ b/homeassistant/components/envisalink/sensor.py @@ -1,4 +1,5 @@ """Support for Envisalink sensors (shows panel info).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/envisalink/switch.py b/homeassistant/components/envisalink/switch.py index 0bedc41e55e..36ad3d5bf81 100644 --- a/homeassistant/components/envisalink/switch.py +++ b/homeassistant/components/envisalink/switch.py @@ -1,4 +1,5 @@ """Support for Envisalink zone bypass switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ephember/climate.py b/homeassistant/components/ephember/climate.py index 047b9234b82..89d84a2c6fd 100644 --- a/homeassistant/components/ephember/climate.py +++ b/homeassistant/components/ephember/climate.py @@ -1,4 +1,5 @@ """Support for the EPH Controls Ember themostats.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/epion/__init__.py b/homeassistant/components/epion/__init__.py index ed2f5559f32..fec975c5098 100644 --- a/homeassistant/components/epion/__init__.py +++ b/homeassistant/components/epion/__init__.py @@ -1,4 +1,5 @@ """The Epion integration.""" + from __future__ import annotations from epion import Epion diff --git a/homeassistant/components/epion/config_flow.py b/homeassistant/components/epion/config_flow.py index f0e598a9af4..ce9a733ffbf 100644 --- a/homeassistant/components/epion/config_flow.py +++ b/homeassistant/components/epion/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Epion.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/epion/const.py b/homeassistant/components/epion/const.py index 83f82261583..9156c761346 100644 --- a/homeassistant/components/epion/const.py +++ b/homeassistant/components/epion/const.py @@ -1,4 +1,5 @@ """Constants for the Epion API.""" + from datetime import timedelta DOMAIN = "epion" diff --git a/homeassistant/components/epion/sensor.py b/homeassistant/components/epion/sensor.py index c722e73ac6c..4717c095bfe 100644 --- a/homeassistant/components/epion/sensor.py +++ b/homeassistant/components/epion/sensor.py @@ -1,4 +1,5 @@ """Support for Epion API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/epson/exceptions.py b/homeassistant/components/epson/exceptions.py index 5cc65b32891..9af87391cb1 100644 --- a/homeassistant/components/epson/exceptions.py +++ b/homeassistant/components/epson/exceptions.py @@ -1,4 +1,5 @@ """The errors of Epson integration.""" + from homeassistant import exceptions diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index 1f401ed0a7d..a962b94b5e0 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -1,4 +1,5 @@ """Support for Epson projector.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/epsonworkforce/sensor.py b/homeassistant/components/epsonworkforce/sensor.py index 3b31082f333..f8b11fc31f4 100644 --- a/homeassistant/components/epsonworkforce/sensor.py +++ b/homeassistant/components/epsonworkforce/sensor.py @@ -1,4 +1,5 @@ """Support for Epson Workforce Printer.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/escea/climate.py b/homeassistant/components/escea/climate.py index b204ae196e8..555da1494d7 100644 --- a/homeassistant/components/escea/climate.py +++ b/homeassistant/components/escea/climate.py @@ -1,4 +1,5 @@ """Support for the Escea Fireplace.""" + from __future__ import annotations from collections.abc import Coroutine diff --git a/homeassistant/components/escea/discovery.py b/homeassistant/components/escea/discovery.py index 0d7f3024bfc..cbdc77536d7 100644 --- a/homeassistant/components/escea/discovery.py +++ b/homeassistant/components/escea/discovery.py @@ -1,4 +1,5 @@ """Internal discovery service for Escea Fireplace.""" + from __future__ import annotations from pescea import ( diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index bc22cc13d6f..3de5d48391f 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -1,4 +1,5 @@ """Support for esphome devices.""" + from __future__ import annotations from aioesphomeapi import APIClient diff --git a/homeassistant/components/esphome/alarm_control_panel.py b/homeassistant/components/esphome/alarm_control_panel.py index 58f63446da7..c430934019c 100644 --- a/homeassistant/components/esphome/alarm_control_panel.py +++ b/homeassistant/components/esphome/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for ESPHome Alarm Control Panel.""" + from __future__ import annotations from aioesphomeapi import ( diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py index 4eb29f0c210..ac0676d8d1e 100644 --- a/homeassistant/components/esphome/binary_sensor.py +++ b/homeassistant/components/esphome/binary_sensor.py @@ -1,4 +1,5 @@ """Support for ESPHome binary sensors.""" + from __future__ import annotations from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityInfo diff --git a/homeassistant/components/esphome/bluetooth.py b/homeassistant/components/esphome/bluetooth.py index 37a555f3115..37ae28df0ca 100644 --- a/homeassistant/components/esphome/bluetooth.py +++ b/homeassistant/components/esphome/bluetooth.py @@ -1,4 +1,5 @@ """Bluetooth support for esphome.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/esphome/button.py b/homeassistant/components/esphome/button.py index d59e135d748..406d86e9dc5 100644 --- a/homeassistant/components/esphome/button.py +++ b/homeassistant/components/esphome/button.py @@ -1,4 +1,5 @@ """Support for ESPHome buttons.""" + from __future__ import annotations from aioesphomeapi import ButtonInfo, EntityInfo, EntityState diff --git a/homeassistant/components/esphome/camera.py b/homeassistant/components/esphome/camera.py index 0b9c2995dac..83cf8d03e78 100644 --- a/homeassistant/components/esphome/camera.py +++ b/homeassistant/components/esphome/camera.py @@ -1,4 +1,5 @@ """Support for ESPHome cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index b9952004569..5337f9cf933 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -1,4 +1,5 @@ """Support for ESPHome climate devices.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 98f64818b42..5e166db7092 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure esphome component.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/components/esphome/const.py b/homeassistant/components/esphome/const.py index 575c57c8672..9c09591f6ea 100644 --- a/homeassistant/components/esphome/const.py +++ b/homeassistant/components/esphome/const.py @@ -1,4 +1,5 @@ """ESPHome constants.""" + from awesomeversion import AwesomeVersion DOMAIN = "esphome" diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 77c3fee0afc..3a1767d50f0 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -1,4 +1,5 @@ """Support for ESPHome covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/esphome/dashboard.py b/homeassistant/components/esphome/dashboard.py index 03264291d8f..54a593fe0cc 100644 --- a/homeassistant/components/esphome/dashboard.py +++ b/homeassistant/components/esphome/dashboard.py @@ -1,4 +1,5 @@ """Files to interact with a the ESPHome dashboard.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/esphome/diagnostics.py b/homeassistant/components/esphome/diagnostics.py index f270196db50..44241f5950c 100644 --- a/homeassistant/components/esphome/diagnostics.py +++ b/homeassistant/components/esphome/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for ESPHome.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/esphome/domain_data.py b/homeassistant/components/esphome/domain_data.py index 6dae91c4c24..9ac8fe97614 100644 --- a/homeassistant/components/esphome/domain_data.py +++ b/homeassistant/components/esphome/domain_data.py @@ -1,4 +1,5 @@ """Support for esphome domain data.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index 7b06fadb33f..aa98ccef70c 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -1,4 +1,5 @@ """Support for esphome entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index a15f68fd6cc..2a34089cbe9 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -1,4 +1,5 @@ """Runtime entry data for ESPHome stored in hass.data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 90cda53dee6..25c81fcb8a8 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -1,4 +1,5 @@ """Support for ESPHome fans.""" + from __future__ import annotations import math diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index 4f047bad757..da6d4c7b1fc 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -1,4 +1,5 @@ """Support for ESPHome lights.""" + from __future__ import annotations from functools import lru_cache diff --git a/homeassistant/components/esphome/lock.py b/homeassistant/components/esphome/lock.py index 55177fd9a51..586a8e7af22 100644 --- a/homeassistant/components/esphome/lock.py +++ b/homeassistant/components/esphome/lock.py @@ -1,4 +1,5 @@ """Support for ESPHome locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index 2cbb0494ecf..07c4a39e113 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -1,4 +1,5 @@ """Manager for esphome devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py index 208f1edebeb..60ccc08cad4 100644 --- a/homeassistant/components/esphome/media_player.py +++ b/homeassistant/components/esphome/media_player.py @@ -1,4 +1,5 @@ """Support for ESPHome media players.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index 2619dbad045..c9511ffe5bc 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -1,4 +1,5 @@ """Support for esphome numbers.""" + from __future__ import annotations import math diff --git a/homeassistant/components/esphome/select.py b/homeassistant/components/esphome/select.py index 43965a11df4..8f6fa4af6f0 100644 --- a/homeassistant/components/esphome/select.py +++ b/homeassistant/components/esphome/select.py @@ -1,4 +1,5 @@ """Support for esphome selects.""" + from __future__ import annotations from aioesphomeapi import EntityInfo, SelectInfo, SelectState diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index d2be19a3fb3..e89901604b2 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -1,4 +1,5 @@ """Support for esphome sensors.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index a6ecd86f264..f42e215eeaa 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -1,4 +1,5 @@ """Support for ESPHome switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/esphome/text.py b/homeassistant/components/esphome/text.py index 337cbb26fee..9cd7cb4c008 100644 --- a/homeassistant/components/esphome/text.py +++ b/homeassistant/components/esphome/text.py @@ -1,4 +1,5 @@ """Support for esphome texts.""" + from __future__ import annotations from aioesphomeapi import EntityInfo, TextInfo, TextMode as EsphomeTextMode, TextState diff --git a/homeassistant/components/esphome/update.py b/homeassistant/components/esphome/update.py index a444c98b987..2219900cced 100644 --- a/homeassistant/components/esphome/update.py +++ b/homeassistant/components/esphome/update.py @@ -1,4 +1,5 @@ """Update platform for ESPHome.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/etherscan/sensor.py b/homeassistant/components/etherscan/sensor.py index d98ddcba23c..38219bf659b 100644 --- a/homeassistant/components/etherscan/sensor.py +++ b/homeassistant/components/etherscan/sensor.py @@ -1,4 +1,5 @@ """Support for Etherscan sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py index 5185dcd8818..c1506c00cdc 100644 --- a/homeassistant/components/eufy/light.py +++ b/homeassistant/components/eufy/light.py @@ -1,4 +1,5 @@ """Support for EufyHome lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/eufy/switch.py b/homeassistant/components/eufy/switch.py index 324133354fb..58bcc6ceb21 100644 --- a/homeassistant/components/eufy/switch.py +++ b/homeassistant/components/eufy/switch.py @@ -1,4 +1,5 @@ """Support for EufyHome switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/eufylife_ble/__init__.py b/homeassistant/components/eufylife_ble/__init__.py index f407e86a289..f66cf7df30d 100644 --- a/homeassistant/components/eufylife_ble/__init__.py +++ b/homeassistant/components/eufylife_ble/__init__.py @@ -1,4 +1,5 @@ """The EufyLife integration.""" + from __future__ import annotations from eufylife_ble_client import EufyLifeBLEDevice diff --git a/homeassistant/components/eufylife_ble/config_flow.py b/homeassistant/components/eufylife_ble/config_flow.py index 1f2c851e484..072a025cf2b 100644 --- a/homeassistant/components/eufylife_ble/config_flow.py +++ b/homeassistant/components/eufylife_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the EufyLife integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/eufylife_ble/models.py b/homeassistant/components/eufylife_ble/models.py index 62537f22f23..eb937fc4f3d 100644 --- a/homeassistant/components/eufylife_ble/models.py +++ b/homeassistant/components/eufylife_ble/models.py @@ -1,4 +1,5 @@ """Models for the EufyLife integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/eufylife_ble/sensor.py b/homeassistant/components/eufylife_ble/sensor.py index 69b88bb01f6..5e3ae64aabf 100644 --- a/homeassistant/components/eufylife_ble/sensor.py +++ b/homeassistant/components/eufylife_ble/sensor.py @@ -1,4 +1,5 @@ """Support for EufyLife sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/event/__init__.py b/homeassistant/components/event/__init__.py index b05c3a6f3a5..5a7e8bdda30 100644 --- a/homeassistant/components/event/__init__.py +++ b/homeassistant/components/event/__init__.py @@ -1,4 +1,5 @@ """Component for handling incoming events as a platform.""" + from __future__ import annotations from dataclasses import asdict, dataclass diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py index 1a177cf8909..334e464d67e 100644 --- a/homeassistant/components/everlights/light.py +++ b/homeassistant/components/everlights/light.py @@ -1,4 +1,5 @@ """Support for EverLights lights.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/evil_genius_labs/__init__.py b/homeassistant/components/evil_genius_labs/__init__.py index 44d46d27a9d..fe91e58d839 100644 --- a/homeassistant/components/evil_genius_labs/__init__.py +++ b/homeassistant/components/evil_genius_labs/__init__.py @@ -1,4 +1,5 @@ """The Evil Genius Labs integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/evil_genius_labs/config_flow.py b/homeassistant/components/evil_genius_labs/config_flow.py index 919863a0a4f..283b3d36beb 100644 --- a/homeassistant/components/evil_genius_labs/config_flow.py +++ b/homeassistant/components/evil_genius_labs/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Evil Genius Labs integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/evil_genius_labs/diagnostics.py b/homeassistant/components/evil_genius_labs/diagnostics.py index a6a15165716..2249e1269b0 100644 --- a/homeassistant/components/evil_genius_labs/diagnostics.py +++ b/homeassistant/components/evil_genius_labs/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Evil Genius Labs.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/evil_genius_labs/light.py b/homeassistant/components/evil_genius_labs/light.py index 5612d0e8522..c64a22d28cd 100644 --- a/homeassistant/components/evil_genius_labs/light.py +++ b/homeassistant/components/evil_genius_labs/light.py @@ -1,4 +1,5 @@ """Light platform for Evil Genius Light.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/evil_genius_labs/util.py b/homeassistant/components/evil_genius_labs/util.py index eb2caf59d9d..db07cf46918 100644 --- a/homeassistant/components/evil_genius_labs/util.py +++ b/homeassistant/components/evil_genius_labs/util.py @@ -1,4 +1,5 @@ """Utilities for Evil Genius Labs.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index ddad635ddcf..3017685a307 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -2,6 +2,7 @@ Such systems include evohome, Round Thermostat, and others. """ + from __future__ import annotations from collections.abc import Awaitable diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index 8b74d31cc0d..2d462b5c525 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -1,4 +1,5 @@ """Support for Climate devices of (EMEA/EU-based) Honeywell TCC systems.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 26a60f9ec08..26be4b47a36 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -1,4 +1,5 @@ """Support for WaterHeater devices of (EMEA/EU) Honeywell TCC systems.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ezviz/alarm_control_panel.py b/homeassistant/components/ezviz/alarm_control_panel.py index 1cdda152685..f847b8445f9 100644 --- a/homeassistant/components/ezviz/alarm_control_panel.py +++ b/homeassistant/components/ezviz/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Ezviz alarm.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ezviz/binary_sensor.py b/homeassistant/components/ezviz/binary_sensor.py index 81697e2772c..c13375cb487 100644 --- a/homeassistant/components/ezviz/binary_sensor.py +++ b/homeassistant/components/ezviz/binary_sensor.py @@ -1,4 +1,5 @@ """Support for EZVIZ binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/ezviz/button.py b/homeassistant/components/ezviz/button.py index 98608a15149..deb2ca33355 100644 --- a/homeassistant/components/ezviz/button.py +++ b/homeassistant/components/ezviz/button.py @@ -1,4 +1,5 @@ """Support for EZVIZ button controls.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index 6397d8a27dc..455c41b385f 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -1,4 +1,5 @@ """Support ezviz camera devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ezviz/config_flow.py b/homeassistant/components/ezviz/config_flow.py index 28c80ad7bc0..a453398a17a 100644 --- a/homeassistant/components/ezviz/config_flow.py +++ b/homeassistant/components/ezviz/config_flow.py @@ -1,4 +1,5 @@ """Config flow for EZVIZ.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/ezviz/entity.py b/homeassistant/components/ezviz/entity.py index c8ce3daf074..44de4a0c9c7 100644 --- a/homeassistant/components/ezviz/entity.py +++ b/homeassistant/components/ezviz/entity.py @@ -1,4 +1,5 @@ """An abstract class common to all EZVIZ entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ezviz/image.py b/homeassistant/components/ezviz/image.py index aeb8eafe68f..0c362f8cbe7 100644 --- a/homeassistant/components/ezviz/image.py +++ b/homeassistant/components/ezviz/image.py @@ -1,4 +1,5 @@ """Support EZVIZ last motion image.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ezviz/light.py b/homeassistant/components/ezviz/light.py index 558072658d3..5c33fbee02a 100644 --- a/homeassistant/components/ezviz/light.py +++ b/homeassistant/components/ezviz/light.py @@ -1,4 +1,5 @@ """Support for EZVIZ light entity.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ezviz/number.py b/homeassistant/components/ezviz/number.py index 856d7fe392b..8fe0ba1330f 100644 --- a/homeassistant/components/ezviz/number.py +++ b/homeassistant/components/ezviz/number.py @@ -1,4 +1,5 @@ """Support for EZVIZ number controls.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ezviz/select.py b/homeassistant/components/ezviz/select.py index d0e86cb026c..a63dad5ff7b 100644 --- a/homeassistant/components/ezviz/select.py +++ b/homeassistant/components/ezviz/select.py @@ -1,4 +1,5 @@ """Support for EZVIZ select controls.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ezviz/sensor.py b/homeassistant/components/ezviz/sensor.py index aecf25c2c78..e0750b985fc 100644 --- a/homeassistant/components/ezviz/sensor.py +++ b/homeassistant/components/ezviz/sensor.py @@ -1,4 +1,5 @@ """Support for EZVIZ sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/ezviz/siren.py b/homeassistant/components/ezviz/siren.py index 1f08b389236..8bacceff29f 100644 --- a/homeassistant/components/ezviz/siren.py +++ b/homeassistant/components/ezviz/siren.py @@ -1,4 +1,5 @@ """Support for EZVIZ sirens.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ezviz/switch.py b/homeassistant/components/ezviz/switch.py index f6d19afae0c..7f568654870 100644 --- a/homeassistant/components/ezviz/switch.py +++ b/homeassistant/components/ezviz/switch.py @@ -1,4 +1,5 @@ """Support for EZVIZ Switch sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ezviz/update.py b/homeassistant/components/ezviz/update.py index 003397d8dda..05735d152cf 100644 --- a/homeassistant/components/ezviz/update.py +++ b/homeassistant/components/ezviz/update.py @@ -1,4 +1,5 @@ """Support for EZVIZ sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/faa_delays/__init__.py b/homeassistant/components/faa_delays/__init__.py index 3606da33499..750b1f4a833 100644 --- a/homeassistant/components/faa_delays/__init__.py +++ b/homeassistant/components/faa_delays/__init__.py @@ -1,4 +1,5 @@ """The FAA Delays integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID, Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/faa_delays/binary_sensor.py b/homeassistant/components/faa_delays/binary_sensor.py index 5cc23b5d73c..6a01bf6ebed 100644 --- a/homeassistant/components/faa_delays/binary_sensor.py +++ b/homeassistant/components/faa_delays/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for FAA Delays sensor component.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/faa_delays/const.py b/homeassistant/components/faa_delays/const.py index 3b9bda33bfb..b91b4536267 100644 --- a/homeassistant/components/faa_delays/const.py +++ b/homeassistant/components/faa_delays/const.py @@ -1,4 +1,5 @@ """Constants for the FAA Delays integration.""" + from __future__ import annotations DOMAIN = "faa_delays" diff --git a/homeassistant/components/facebook/notify.py b/homeassistant/components/facebook/notify.py index 2600bbd2d9c..38ed78d125b 100644 --- a/homeassistant/components/facebook/notify.py +++ b/homeassistant/components/facebook/notify.py @@ -1,4 +1,5 @@ """Facebook platform for notify component.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 22b4bfe6ea1..5682c127c41 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -1,4 +1,5 @@ """Support for displaying IPs banned by fail2ban.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/familyhub/camera.py b/homeassistant/components/familyhub/camera.py index 324341dc8cf..da6f82cf56b 100644 --- a/homeassistant/components/familyhub/camera.py +++ b/homeassistant/components/familyhub/camera.py @@ -1,4 +1,5 @@ """Family Hub camera for Samsung Refrigerators.""" + from __future__ import annotations from pyfamilyhublocal import FamilyHubCam diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index c35d828e398..aeb3a6c89df 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -1,4 +1,5 @@ """Provides functionality to interact with fans.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/fan/device_action.py b/homeassistant/components/fan/device_action.py index fc7f1ddce1f..b4164f1d1a6 100644 --- a/homeassistant/components/fan/device_action.py +++ b/homeassistant/components/fan/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Fan.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/fan/device_condition.py b/homeassistant/components/fan/device_condition.py index 920f970185b..39f77b7a128 100644 --- a/homeassistant/components/fan/device_condition.py +++ b/homeassistant/components/fan/device_condition.py @@ -1,4 +1,5 @@ """Provide the device automations for Fan.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/fan/device_trigger.py b/homeassistant/components/fan/device_trigger.py index cc10e9cbeca..8e1c518d7c7 100644 --- a/homeassistant/components/fan/device_trigger.py +++ b/homeassistant/components/fan/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Fan.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/fan/reproduce_state.py b/homeassistant/components/fan/reproduce_state.py index a12b23cb16d..391059a369c 100644 --- a/homeassistant/components/fan/reproduce_state.py +++ b/homeassistant/components/fan/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Fan state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/fan/significant_change.py b/homeassistant/components/fan/significant_change.py index b8038b93f79..d3d346d5f66 100644 --- a/homeassistant/components/fan/significant_change.py +++ b/homeassistant/components/fan/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Fan state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py index ada717a6dac..9cd99c0c58b 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -1,4 +1,5 @@ """Support for testing internet speed via Fast.com.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fastdotcom/config_flow.py b/homeassistant/components/fastdotcom/config_flow.py index a8491a820ab..ec62c86d787 100644 --- a/homeassistant/components/fastdotcom/config_flow.py +++ b/homeassistant/components/fastdotcom/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Fast.com integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fastdotcom/coordinator.py b/homeassistant/components/fastdotcom/coordinator.py index 692a85d2eda..42e60069507 100644 --- a/homeassistant/components/fastdotcom/coordinator.py +++ b/homeassistant/components/fastdotcom/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Fast.com integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index a213898562b..52f9c6f0a2c 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -1,4 +1,5 @@ """Support for Fast.com internet speed testing sensor.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/fastdotcom/services.py b/homeassistant/components/fastdotcom/services.py index d1a9ee2125b..a901915e11f 100644 --- a/homeassistant/components/fastdotcom/services.py +++ b/homeassistant/components/fastdotcom/services.py @@ -1,4 +1,5 @@ """Services for the Fastdotcom integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntryState diff --git a/homeassistant/components/feedreader/__init__.py b/homeassistant/components/feedreader/__init__.py index 04511a1a986..0a16e986d0b 100644 --- a/homeassistant/components/feedreader/__init__.py +++ b/homeassistant/components/feedreader/__init__.py @@ -1,4 +1,5 @@ """Support for RSS/Atom feeds.""" + from __future__ import annotations from calendar import timegm diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index 4ab4ee32a09..f16aed7b607 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -1,4 +1,5 @@ """Support for FFmpeg.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ffmpeg/camera.py b/homeassistant/components/ffmpeg/camera.py index 884629c8ae6..c0ce4ad9746 100644 --- a/homeassistant/components/ffmpeg/camera.py +++ b/homeassistant/components/ffmpeg/camera.py @@ -1,4 +1,5 @@ """Support for Cameras with FFmpeg as decoder.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py index b982d944c6a..d5030d4530e 100644 --- a/homeassistant/components/ffmpeg_motion/binary_sensor.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -1,4 +1,5 @@ """Provides a binary sensor which is a collection of ffmpeg tools.""" + from __future__ import annotations from typing import Any, TypeVar diff --git a/homeassistant/components/ffmpeg_noise/binary_sensor.py b/homeassistant/components/ffmpeg_noise/binary_sensor.py index a802868334d..a434b4a9924 100644 --- a/homeassistant/components/ffmpeg_noise/binary_sensor.py +++ b/homeassistant/components/ffmpeg_noise/binary_sensor.py @@ -1,4 +1,5 @@ """Provides a binary sensor which is a collection of ffmpeg tools.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 159ba62bd24..2c1405130b4 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -1,4 +1,5 @@ """Support for the Fibaro devices.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index 07c0d9a779c..c0980025555 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Fibaro binary sensors.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index cb64acdea14..cf08d52d36e 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -1,4 +1,5 @@ """Support for Fibaro thermostats.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/fibaro/config_flow.py b/homeassistant/components/fibaro/config_flow.py index 5d62d410a87..8c2fb502488 100644 --- a/homeassistant/components/fibaro/config_flow.py +++ b/homeassistant/components/fibaro/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Fibaro integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index d353b352c5c..16be6e98ae1 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -1,4 +1,5 @@ """Support for Fibaro cover - curtains, rollershutters etc.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fibaro/event.py b/homeassistant/components/fibaro/event.py index 020a478db95..676889bbe0d 100644 --- a/homeassistant/components/fibaro/event.py +++ b/homeassistant/components/fibaro/event.py @@ -1,4 +1,5 @@ """Support for Fibaro event entities.""" + from __future__ import annotations from pyfibaro.fibaro_device import DeviceModel, SceneEvent diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index 17de9a6636a..2f2182c53cd 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -1,4 +1,5 @@ """Support for Fibaro lights.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/fibaro/lock.py b/homeassistant/components/fibaro/lock.py index 715116d2843..271e3981b71 100644 --- a/homeassistant/components/fibaro/lock.py +++ b/homeassistant/components/fibaro/lock.py @@ -1,4 +1,5 @@ """Support for Fibaro locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fibaro/scene.py b/homeassistant/components/fibaro/scene.py index 7ae8bff151f..a40a1ef5b57 100644 --- a/homeassistant/components/fibaro/scene.py +++ b/homeassistant/components/fibaro/scene.py @@ -1,4 +1,5 @@ """Support for Fibaro scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index e859a9b1afb..a7c1d93da7b 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -1,4 +1,5 @@ """Support for Fibaro sensors.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py index fdd473ea282..f6ceed972f7 100644 --- a/homeassistant/components/fibaro/switch.py +++ b/homeassistant/components/fibaro/switch.py @@ -1,4 +1,5 @@ """Support for Fibaro switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fido/sensor.py b/homeassistant/components/fido/sensor.py index b7942056a2c..d2169ae32e8 100644 --- a/homeassistant/components/fido/sensor.py +++ b/homeassistant/components/fido/sensor.py @@ -3,6 +3,7 @@ Get data from 'Usage Summary' page: https://www.fido.ca/pages/#/my-account/wireless """ + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/file/notify.py b/homeassistant/components/file/notify.py index ca0deb89c7b..50e6cec09a8 100644 --- a/homeassistant/components/file/notify.py +++ b/homeassistant/components/file/notify.py @@ -1,4 +1,5 @@ """Support for file notification.""" + from __future__ import annotations import os diff --git a/homeassistant/components/file/sensor.py b/homeassistant/components/file/sensor.py index 82eb5880b79..f70b0bce701 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -1,4 +1,5 @@ """Support for sensor value(s) stored in local files.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/file_upload/__init__.py b/homeassistant/components/file_upload/__init__.py index 85dacb388c0..94c3c39ce92 100644 --- a/homeassistant/components/file_upload/__init__.py +++ b/homeassistant/components/file_upload/__init__.py @@ -1,4 +1,5 @@ """The File Upload integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/filesize/__init__.py b/homeassistant/components/filesize/__init__.py index 4a3e35b4e29..90d2af5d52a 100644 --- a/homeassistant/components/filesize/__init__.py +++ b/homeassistant/components/filesize/__init__.py @@ -1,4 +1,5 @@ """The filesize component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/filesize/config_flow.py b/homeassistant/components/filesize/config_flow.py index 6d156aa4c0a..51eff46bdb3 100644 --- a/homeassistant/components/filesize/config_flow.py +++ b/homeassistant/components/filesize/config_flow.py @@ -1,4 +1,5 @@ """The filesize config flow.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/filesize/coordinator.py b/homeassistant/components/filesize/coordinator.py index 87e866b89d9..2e59e922801 100644 --- a/homeassistant/components/filesize/coordinator.py +++ b/homeassistant/components/filesize/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for monitoring the size of a file.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 7d41989cfca..761513b1f48 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -1,4 +1,5 @@ """Sensor for monitoring the size of a file.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index c240d04ec1a..783bbbe740e 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -1,4 +1,5 @@ """Allows the creation of a sensor that filters state property.""" + from __future__ import annotations from collections import Counter, deque diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index c969adfe637..6d3f1f63b84 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -1,4 +1,5 @@ """Read the balance of your bank accounts via FinTS.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/fireservicerota/__init__.py b/homeassistant/components/fireservicerota/__init__.py index cb7a18dfcac..c3ee594e47d 100644 --- a/homeassistant/components/fireservicerota/__init__.py +++ b/homeassistant/components/fireservicerota/__init__.py @@ -1,4 +1,5 @@ """The FireServiceRota integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/fireservicerota/binary_sensor.py b/homeassistant/components/fireservicerota/binary_sensor.py index d2f4e2e11f2..9938f6ab096 100644 --- a/homeassistant/components/fireservicerota/binary_sensor.py +++ b/homeassistant/components/fireservicerota/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor platform for FireServiceRota integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fireservicerota/config_flow.py b/homeassistant/components/fireservicerota/config_flow.py index 9eeb018ca02..afaef17c5a6 100644 --- a/homeassistant/components/fireservicerota/config_flow.py +++ b/homeassistant/components/fireservicerota/config_flow.py @@ -1,4 +1,5 @@ """Config flow for FireServiceRota.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/firmata/board.py b/homeassistant/components/firmata/board.py index 233388d5013..9573627e130 100644 --- a/homeassistant/components/firmata/board.py +++ b/homeassistant/components/firmata/board.py @@ -1,4 +1,5 @@ """Code to handle a Firmata board.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/firmata/const.py b/homeassistant/components/firmata/const.py index da722b51897..541003ec5f4 100644 --- a/homeassistant/components/firmata/const.py +++ b/homeassistant/components/firmata/const.py @@ -1,4 +1,5 @@ """Constants for the Firmata component.""" + from typing import Final from homeassistant.const import ( diff --git a/homeassistant/components/firmata/entity.py b/homeassistant/components/firmata/entity.py index 51d2ad51866..60b7c3879ff 100644 --- a/homeassistant/components/firmata/entity.py +++ b/homeassistant/components/firmata/entity.py @@ -1,4 +1,5 @@ """Entity for Firmata devices.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/firmata/light.py b/homeassistant/components/firmata/light.py index 29504f704bf..00453762c14 100644 --- a/homeassistant/components/firmata/light.py +++ b/homeassistant/components/firmata/light.py @@ -1,4 +1,5 @@ """Support for Firmata light output.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/firmata/pin.py b/homeassistant/components/firmata/pin.py index 190889914b3..c27152a8150 100644 --- a/homeassistant/components/firmata/pin.py +++ b/homeassistant/components/firmata/pin.py @@ -1,4 +1,5 @@ """Code to handle pins on a Firmata board.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fitbit/const.py b/homeassistant/components/fitbit/const.py index 45b81b3919e..c20854e03cf 100644 --- a/homeassistant/components/fitbit/const.py +++ b/homeassistant/components/fitbit/const.py @@ -1,4 +1,5 @@ """Constants for the Fitbit platform.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index eb7d3b02b4d..6df4968739f 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -1,4 +1,5 @@ """Support for the Fitbit API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fivem/__init__.py b/homeassistant/components/fivem/__init__.py index 996aecef261..25d24502846 100644 --- a/homeassistant/components/fivem/__init__.py +++ b/homeassistant/components/fivem/__init__.py @@ -1,4 +1,5 @@ """The FiveM integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fivem/binary_sensor.py b/homeassistant/components/fivem/binary_sensor.py index ee46067f443..de58ea52fb6 100644 --- a/homeassistant/components/fivem/binary_sensor.py +++ b/homeassistant/components/fivem/binary_sensor.py @@ -1,4 +1,5 @@ """The FiveM binary sensor platform.""" + from dataclasses import dataclass from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/fivem/config_flow.py b/homeassistant/components/fivem/config_flow.py index b985b161af4..7cc553a6a72 100644 --- a/homeassistant/components/fivem/config_flow.py +++ b/homeassistant/components/fivem/config_flow.py @@ -1,4 +1,5 @@ """Config flow for FiveM integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fivem/coordinator.py b/homeassistant/components/fivem/coordinator.py index 9da641b0bd9..c96fa42fb3e 100644 --- a/homeassistant/components/fivem/coordinator.py +++ b/homeassistant/components/fivem/coordinator.py @@ -1,4 +1,5 @@ """The FiveM update coordinator.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fivem/entity.py b/homeassistant/components/fivem/entity.py index 69204b559ae..a7459123fa1 100644 --- a/homeassistant/components/fivem/entity.py +++ b/homeassistant/components/fivem/entity.py @@ -1,4 +1,5 @@ """The FiveM entity.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fivem/sensor.py b/homeassistant/components/fivem/sensor.py index c39f67c5503..b63f3b9082f 100644 --- a/homeassistant/components/fivem/sensor.py +++ b/homeassistant/components/fivem/sensor.py @@ -1,4 +1,5 @@ """The FiveM sensor platform.""" + from dataclasses import dataclass from homeassistant.components.sensor import SensorEntity, SensorEntityDescription diff --git a/homeassistant/components/fixer/sensor.py b/homeassistant/components/fixer/sensor.py index 8091f8981e3..10f05ca29f8 100644 --- a/homeassistant/components/fixer/sensor.py +++ b/homeassistant/components/fixer/sensor.py @@ -1,4 +1,5 @@ """Currency exchange rate support that comes from fixer.io.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py index 48d7809b715..d95cb1d1006 100644 --- a/homeassistant/components/fjaraskupan/__init__.py +++ b/homeassistant/components/fjaraskupan/__init__.py @@ -1,4 +1,5 @@ """The Fjäråskupan integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fjaraskupan/binary_sensor.py b/homeassistant/components/fjaraskupan/binary_sensor.py index 03302d490a6..93886a2ac6a 100644 --- a/homeassistant/components/fjaraskupan/binary_sensor.py +++ b/homeassistant/components/fjaraskupan/binary_sensor.py @@ -1,4 +1,5 @@ """Support for sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fjaraskupan/config_flow.py b/homeassistant/components/fjaraskupan/config_flow.py index dd1dc03d3ad..d5c287a0cff 100644 --- a/homeassistant/components/fjaraskupan/config_flow.py +++ b/homeassistant/components/fjaraskupan/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Fjäråskupan integration.""" + from __future__ import annotations from fjaraskupan import device_filter diff --git a/homeassistant/components/fjaraskupan/coordinator.py b/homeassistant/components/fjaraskupan/coordinator.py index f955c7ca024..22811ce534b 100644 --- a/homeassistant/components/fjaraskupan/coordinator.py +++ b/homeassistant/components/fjaraskupan/coordinator.py @@ -1,4 +1,5 @@ """The Fjäråskupan data update coordinator.""" + from __future__ import annotations from collections.abc import AsyncIterator diff --git a/homeassistant/components/fjaraskupan/fan.py b/homeassistant/components/fjaraskupan/fan.py index ee989bb2ee0..194a3c1d251 100644 --- a/homeassistant/components/fjaraskupan/fan.py +++ b/homeassistant/components/fjaraskupan/fan.py @@ -1,4 +1,5 @@ """Support for Fjäråskupan fans.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fjaraskupan/light.py b/homeassistant/components/fjaraskupan/light.py index 396f6b00e3b..7f33d7806ee 100644 --- a/homeassistant/components/fjaraskupan/light.py +++ b/homeassistant/components/fjaraskupan/light.py @@ -1,4 +1,5 @@ """Support for lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fjaraskupan/number.py b/homeassistant/components/fjaraskupan/number.py index d57e10aa561..1828c4cdea5 100644 --- a/homeassistant/components/fjaraskupan/number.py +++ b/homeassistant/components/fjaraskupan/number.py @@ -1,4 +1,5 @@ """Support for sensors.""" + from __future__ import annotations from homeassistant.components.number import NumberEntity diff --git a/homeassistant/components/fjaraskupan/sensor.py b/homeassistant/components/fjaraskupan/sensor.py index 30527d4e29d..36db4d7ed9f 100644 --- a/homeassistant/components/fjaraskupan/sensor.py +++ b/homeassistant/components/fjaraskupan/sensor.py @@ -1,4 +1,5 @@ """Support for sensors.""" + from __future__ import annotations from fjaraskupan import Device diff --git a/homeassistant/components/fleetgo/device_tracker.py b/homeassistant/components/fleetgo/device_tracker.py index e736419ce29..3249e8035b4 100644 --- a/homeassistant/components/fleetgo/device_tracker.py +++ b/homeassistant/components/fleetgo/device_tracker.py @@ -1,4 +1,5 @@ """Support for FleetGO Platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index 85d5e9f4eac..c15c74b4aac 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -1,4 +1,5 @@ """Platform for Flexit AC units with CI66 Modbus adapter.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/flexit_bacnet/__init__.py b/homeassistant/components/flexit_bacnet/__init__.py index 5732fb3822c..6b42310d181 100644 --- a/homeassistant/components/flexit_bacnet/__init__.py +++ b/homeassistant/components/flexit_bacnet/__init__.py @@ -1,4 +1,5 @@ """The Flexit Nordic (BACnet) integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/flexit_bacnet/binary_sensor.py b/homeassistant/components/flexit_bacnet/binary_sensor.py index b014fbca415..901cc52de47 100644 --- a/homeassistant/components/flexit_bacnet/binary_sensor.py +++ b/homeassistant/components/flexit_bacnet/binary_sensor.py @@ -1,4 +1,5 @@ """The Flexit Nordic (BACnet) integration.""" + from collections.abc import Callable from dataclasses import dataclass diff --git a/homeassistant/components/flexit_bacnet/config_flow.py b/homeassistant/components/flexit_bacnet/config_flow.py index 9b0e3188508..087f70869bb 100644 --- a/homeassistant/components/flexit_bacnet/config_flow.py +++ b/homeassistant/components/flexit_bacnet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Flexit Nordic (BACnet) integration.""" + from __future__ import annotations import asyncio.exceptions diff --git a/homeassistant/components/flexit_bacnet/const.py b/homeassistant/components/flexit_bacnet/const.py index ed52b45f05e..3a16e548ece 100644 --- a/homeassistant/components/flexit_bacnet/const.py +++ b/homeassistant/components/flexit_bacnet/const.py @@ -1,4 +1,5 @@ """Constants for the Flexit Nordic (BACnet) integration.""" + from flexit_bacnet import ( VENTILATION_MODE_AWAY, VENTILATION_MODE_HIGH, diff --git a/homeassistant/components/flexit_bacnet/entity.py b/homeassistant/components/flexit_bacnet/entity.py index 3e00fae54af..bd92550db19 100644 --- a/homeassistant/components/flexit_bacnet/entity.py +++ b/homeassistant/components/flexit_bacnet/entity.py @@ -1,4 +1,5 @@ """Base entity for the Flexit Nordic (BACnet) integration.""" + from __future__ import annotations from flexit_bacnet import FlexitBACnet diff --git a/homeassistant/components/flexit_bacnet/sensor.py b/homeassistant/components/flexit_bacnet/sensor.py index 590136ad5f7..2453acb90be 100644 --- a/homeassistant/components/flexit_bacnet/sensor.py +++ b/homeassistant/components/flexit_bacnet/sensor.py @@ -1,4 +1,5 @@ """The Flexit Nordic (BACnet) integration.""" + from collections.abc import Callable from dataclasses import dataclass diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py index 81a23a9eeb5..b7f8bb0c854 100644 --- a/homeassistant/components/flic/binary_sensor.py +++ b/homeassistant/components/flic/binary_sensor.py @@ -1,4 +1,5 @@ """Support to use flic buttons as a binary sensor.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/flipr/__init__.py b/homeassistant/components/flipr/__init__.py index e6d7cb1dd17..28515dd386f 100644 --- a/homeassistant/components/flipr/__init__.py +++ b/homeassistant/components/flipr/__init__.py @@ -1,4 +1,5 @@ """The Flipr integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/flipr/binary_sensor.py b/homeassistant/components/flipr/binary_sensor.py index 677a282e8cb..a3c3e4dc8a1 100644 --- a/homeassistant/components/flipr/binary_sensor.py +++ b/homeassistant/components/flipr/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Flipr binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/flipr/config_flow.py b/homeassistant/components/flipr/config_flow.py index 187e43d7426..9d177e4c2b6 100644 --- a/homeassistant/components/flipr/config_flow.py +++ b/homeassistant/components/flipr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Flipr integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/flipr/coordinator.py b/homeassistant/components/flipr/coordinator.py index d51db645035..afc7465498f 100644 --- a/homeassistant/components/flipr/coordinator.py +++ b/homeassistant/components/flipr/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for flipr integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/flipr/entity.py b/homeassistant/components/flipr/entity.py index 6166d727ac7..859ffc9390b 100644 --- a/homeassistant/components/flipr/entity.py +++ b/homeassistant/components/flipr/entity.py @@ -1,4 +1,5 @@ """Base entity for the flipr entity.""" + from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import ( diff --git a/homeassistant/components/flipr/sensor.py b/homeassistant/components/flipr/sensor.py index 452e5b097e9..7a1c64dc766 100644 --- a/homeassistant/components/flipr/sensor.py +++ b/homeassistant/components/flipr/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the Flipr's pool_sensor.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index d61f67cc623..84ce9d2bb7b 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Flo Water Monitor binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/flo/config_flow.py b/homeassistant/components/flo/config_flow.py index c9bc39024a0..ec92b60c740 100644 --- a/homeassistant/components/flo/config_flow.py +++ b/homeassistant/components/flo/config_flow.py @@ -1,4 +1,5 @@ """Config flow for flo integration.""" + from aioflo import async_get_api from aioflo.errors import RequestError import voluptuous as vol diff --git a/homeassistant/components/flo/device.py b/homeassistant/components/flo/device.py index 27feb15a97e..2d99b8ac7a7 100644 --- a/homeassistant/components/flo/device.py +++ b/homeassistant/components/flo/device.py @@ -1,4 +1,5 @@ """Flo device object.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/flo/entity.py b/homeassistant/components/flo/entity.py index 2745f5f9fb7..62090d67194 100644 --- a/homeassistant/components/flo/entity.py +++ b/homeassistant/components/flo/entity.py @@ -1,4 +1,5 @@ """Base entity class for Flo entities.""" + from __future__ import annotations from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo diff --git a/homeassistant/components/flo/sensor.py b/homeassistant/components/flo/sensor.py index 476898c8ef3..9b85f3a855b 100644 --- a/homeassistant/components/flo/sensor.py +++ b/homeassistant/components/flo/sensor.py @@ -1,4 +1,5 @@ """Support for Flo Water Monitor sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index 62a57c463e2..7b8dfe74c97 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -1,4 +1,5 @@ """Switch representing the shutoff valve for the Flo by Moen integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/flock/notify.py b/homeassistant/components/flock/notify.py index c5926e3158e..61c9a29bd6c 100644 --- a/homeassistant/components/flock/notify.py +++ b/homeassistant/components/flock/notify.py @@ -1,4 +1,5 @@ """Flock platform for notify component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/flume/__init__.py b/homeassistant/components/flume/__init__.py index 3a8718a14e0..d91c6b175cf 100644 --- a/homeassistant/components/flume/__init__.py +++ b/homeassistant/components/flume/__init__.py @@ -1,4 +1,5 @@ """The flume integration.""" + from __future__ import annotations from pyflume import FlumeAuth, FlumeDeviceList diff --git a/homeassistant/components/flume/binary_sensor.py b/homeassistant/components/flume/binary_sensor.py index a31fecf305e..1e0808e7e7d 100644 --- a/homeassistant/components/flume/binary_sensor.py +++ b/homeassistant/components/flume/binary_sensor.py @@ -1,4 +1,5 @@ """Flume binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/flume/config_flow.py b/homeassistant/components/flume/config_flow.py index c5bf6b2a323..cbe3f4983fa 100644 --- a/homeassistant/components/flume/config_flow.py +++ b/homeassistant/components/flume/config_flow.py @@ -1,4 +1,5 @@ """Config flow for flume integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/flume/const.py b/homeassistant/components/flume/const.py index a4e7dba444e..1f9fc10b1b3 100644 --- a/homeassistant/components/flume/const.py +++ b/homeassistant/components/flume/const.py @@ -1,4 +1,5 @@ """The Flume component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/flume/coordinator.py b/homeassistant/components/flume/coordinator.py index b5d37b8027f..30e7962304c 100644 --- a/homeassistant/components/flume/coordinator.py +++ b/homeassistant/components/flume/coordinator.py @@ -1,4 +1,5 @@ """The IntelliFire integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/flume/entity.py b/homeassistant/components/flume/entity.py index a6d13b1f291..139094e9ae3 100644 --- a/homeassistant/components/flume/entity.py +++ b/homeassistant/components/flume/entity.py @@ -1,4 +1,5 @@ """Platform for shared base classes for sensors.""" + from __future__ import annotations from typing import TypeVar diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py index f71ccc87f05..63f58ff64c4 100644 --- a/homeassistant/components/flux/switch.py +++ b/homeassistant/components/flux/switch.py @@ -2,6 +2,7 @@ The idea was taken from https://github.com/KpaBap/hue-flux/ """ + from __future__ import annotations import datetime diff --git a/homeassistant/components/flux_led/__init__.py b/homeassistant/components/flux_led/__init__.py index 2d9dddd3684..b3e17a65a5c 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -1,4 +1,5 @@ """The Flux LED/MagicLight integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/flux_led/button.py b/homeassistant/components/flux_led/button.py index 5bd3fbbbdbe..90918a55bb2 100644 --- a/homeassistant/components/flux_led/button.py +++ b/homeassistant/components/flux_led/button.py @@ -1,4 +1,5 @@ """Support for Magic home button.""" + from __future__ import annotations from flux_led.aio import AIOWifiLedBulb diff --git a/homeassistant/components/flux_led/config_flow.py b/homeassistant/components/flux_led/config_flow.py index 6b22676ee60..469c67deb22 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Flux LED/MagicLight.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/flux_led/coordinator.py b/homeassistant/components/flux_led/coordinator.py index bf3f1dee94a..a473387a513 100644 --- a/homeassistant/components/flux_led/coordinator.py +++ b/homeassistant/components/flux_led/coordinator.py @@ -1,4 +1,5 @@ """The Flux LED/MagicLight integration coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/flux_led/diagnostics.py b/homeassistant/components/flux_led/diagnostics.py index f0c95ffbe56..e24c1aff9a4 100644 --- a/homeassistant/components/flux_led/diagnostics.py +++ b/homeassistant/components/flux_led/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for flux_led.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/flux_led/discovery.py b/homeassistant/components/flux_led/discovery.py index ef0c131993e..9ff1864ec6a 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -1,4 +1,5 @@ """The Flux LED/MagicLight integration discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/flux_led/entity.py b/homeassistant/components/flux_led/entity.py index 1adcd39e22f..bcf7bfff9ed 100644 --- a/homeassistant/components/flux_led/entity.py +++ b/homeassistant/components/flux_led/entity.py @@ -1,4 +1,5 @@ """Support for Magic Home lights.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 1232cb41031..6456eb36dbb 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -1,4 +1,5 @@ """Support for Magic Home lights.""" + from __future__ import annotations import ast diff --git a/homeassistant/components/flux_led/number.py b/homeassistant/components/flux_led/number.py index 5cd95c19328..93687c0c579 100644 --- a/homeassistant/components/flux_led/number.py +++ b/homeassistant/components/flux_led/number.py @@ -1,4 +1,5 @@ """Support for LED numbers.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/flux_led/select.py b/homeassistant/components/flux_led/select.py index e920eefc467..3809e73147a 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -1,4 +1,5 @@ """Support for Magic Home select.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/flux_led/sensor.py b/homeassistant/components/flux_led/sensor.py index 42590715caf..5a6633669ae 100644 --- a/homeassistant/components/flux_led/sensor.py +++ b/homeassistant/components/flux_led/sensor.py @@ -1,4 +1,5 @@ """Support for Magic Home sensors.""" + from __future__ import annotations from homeassistant import config_entries diff --git a/homeassistant/components/flux_led/switch.py b/homeassistant/components/flux_led/switch.py index 09f80640f71..3adcd9a9da9 100644 --- a/homeassistant/components/flux_led/switch.py +++ b/homeassistant/components/flux_led/switch.py @@ -1,4 +1,5 @@ """Support for Magic Home switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/flux_led/util.py b/homeassistant/components/flux_led/util.py index 8db12cb6e32..2691b841952 100644 --- a/homeassistant/components/flux_led/util.py +++ b/homeassistant/components/flux_led/util.py @@ -1,4 +1,5 @@ """Utils for Magic Home.""" + from __future__ import annotations from flux_led.aio import AIOWifiLedBulb diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index 8c71208b745..c4454eba800 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -1,4 +1,5 @@ """Sensor for monitoring the contents of a folder.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/folder_watcher/__init__.py b/homeassistant/components/folder_watcher/__init__.py index 41a20360ff3..d111fe03c5c 100644 --- a/homeassistant/components/folder_watcher/__init__.py +++ b/homeassistant/components/folder_watcher/__init__.py @@ -1,4 +1,5 @@ """Component for monitoring activity on a folder.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/foobot/sensor.py b/homeassistant/components/foobot/sensor.py index 0af1206dbd3..ac8c7e3eec8 100644 --- a/homeassistant/components/foobot/sensor.py +++ b/homeassistant/components/foobot/sensor.py @@ -1,4 +1,5 @@ """Support for the Foobot indoor air quality monitor.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/forecast_solar/__init__.py b/homeassistant/components/forecast_solar/__init__.py index 1d28aad6a92..f4cb1d0a631 100644 --- a/homeassistant/components/forecast_solar/__init__.py +++ b/homeassistant/components/forecast_solar/__init__.py @@ -1,4 +1,5 @@ """The Forecast.Solar integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/forecast_solar/config_flow.py b/homeassistant/components/forecast_solar/config_flow.py index da4f979478e..982f32eb07b 100644 --- a/homeassistant/components/forecast_solar/config_flow.py +++ b/homeassistant/components/forecast_solar/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Forecast.Solar integration.""" + from __future__ import annotations import re diff --git a/homeassistant/components/forecast_solar/const.py b/homeassistant/components/forecast_solar/const.py index 24273f32405..ac80b64b869 100644 --- a/homeassistant/components/forecast_solar/const.py +++ b/homeassistant/components/forecast_solar/const.py @@ -1,4 +1,5 @@ """Constants for the Forecast.Solar integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/forecast_solar/coordinator.py b/homeassistant/components/forecast_solar/coordinator.py index 2ef6912e5a2..1de5edddbef 100644 --- a/homeassistant/components/forecast_solar/coordinator.py +++ b/homeassistant/components/forecast_solar/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Forecast.Solar integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/forecast_solar/diagnostics.py b/homeassistant/components/forecast_solar/diagnostics.py index 970747253df..a9bcebdb3cd 100644 --- a/homeassistant/components/forecast_solar/diagnostics.py +++ b/homeassistant/components/forecast_solar/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Forecast.Solar integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/forecast_solar/energy.py b/homeassistant/components/forecast_solar/energy.py index b2e9b51473b..f4d03f26299 100644 --- a/homeassistant/components/forecast_solar/energy.py +++ b/homeassistant/components/forecast_solar/energy.py @@ -1,4 +1,5 @@ """Energy platform.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/forecast_solar/sensor.py b/homeassistant/components/forecast_solar/sensor.py index 68a3fe81867..8d35b38765a 100644 --- a/homeassistant/components/forecast_solar/sensor.py +++ b/homeassistant/components/forecast_solar/sensor.py @@ -1,4 +1,5 @@ """Support for the Forecast.Solar sensor service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/forked_daapd/__init__.py b/homeassistant/components/forked_daapd/__init__.py index 9dfb92c60c8..6ff9a030a02 100644 --- a/homeassistant/components/forked_daapd/__init__.py +++ b/homeassistant/components/forked_daapd/__init__.py @@ -1,4 +1,5 @@ """The forked_daapd component.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/forked_daapd/browse_media.py b/homeassistant/components/forked_daapd/browse_media.py index 79aa03774b7..f2c62b80234 100644 --- a/homeassistant/components/forked_daapd/browse_media.py +++ b/homeassistant/components/forked_daapd/browse_media.py @@ -1,4 +1,5 @@ """Browse media for forked-daapd.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index fad1de7d1fe..6f7f9f2689a 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure forked-daapd devices.""" + from contextlib import suppress import logging diff --git a/homeassistant/components/forked_daapd/const.py b/homeassistant/components/forked_daapd/const.py index 686a9dbbde9..8d671f2fc07 100644 --- a/homeassistant/components/forked_daapd/const.py +++ b/homeassistant/components/forked_daapd/const.py @@ -1,4 +1,5 @@ """Const for forked-daapd.""" + from homeassistant.components.media_player import MediaPlayerEntityFeature, MediaType CALLBACK_TIMEOUT = 8 # max time between command and callback from forked-daapd server diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index df12de944ae..faaffaa3fd7 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -1,4 +1,5 @@ """Support forked_daapd media player.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/fortios/device_tracker.py b/homeassistant/components/fortios/device_tracker.py index d941375c8a3..3169e9a842f 100644 --- a/homeassistant/components/fortios/device_tracker.py +++ b/homeassistant/components/fortios/device_tracker.py @@ -2,6 +2,7 @@ This component is part of the device_tracker platform. """ + from __future__ import annotations import logging diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index 6674bff81e0..45704515422 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -1,4 +1,5 @@ """Component providing basic support for Foscam IP cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/foscam/config_flow.py b/homeassistant/components/foscam/config_flow.py index c30e3a73754..ab9bc32c6b0 100644 --- a/homeassistant/components/foscam/config_flow.py +++ b/homeassistant/components/foscam/config_flow.py @@ -1,4 +1,5 @@ """Config flow for foscam integration.""" + from libpyfoscam import FoscamCamera from libpyfoscam.foscam import ( ERROR_FOSCAM_AUTH, diff --git a/homeassistant/components/foscam/entity.py b/homeassistant/components/foscam/entity.py index ebcd9574e32..e9d1bbbe176 100644 --- a/homeassistant/components/foscam/entity.py +++ b/homeassistant/components/foscam/entity.py @@ -1,4 +1,5 @@ """Component providing basic support for Foscam IP cameras.""" + from __future__ import annotations from homeassistant.const import ATTR_HW_VERSION, ATTR_MODEL, ATTR_SW_VERSION diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index 6511dbf4478..c0eac33a6a8 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -1,4 +1,5 @@ """Support for the Foursquare (Swarm) API.""" + from http import HTTPStatus import logging diff --git a/homeassistant/components/free_mobile/notify.py b/homeassistant/components/free_mobile/notify.py index 9a1d8c99e19..d888ceadb18 100644 --- a/homeassistant/components/free_mobile/notify.py +++ b/homeassistant/components/free_mobile/notify.py @@ -1,4 +1,5 @@ """Support for Free Mobile SMS platform.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/freebox/__init__.py b/homeassistant/components/freebox/__init__.py index bcfbfdbec28..90ebd53048a 100644 --- a/homeassistant/components/freebox/__init__.py +++ b/homeassistant/components/freebox/__init__.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" + from datetime import timedelta import logging diff --git a/homeassistant/components/freebox/alarm_control_panel.py b/homeassistant/components/freebox/alarm_control_panel.py index be3d88cf5b4..8879963e7f1 100644 --- a/homeassistant/components/freebox/alarm_control_panel.py +++ b/homeassistant/components/freebox/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Freebox alarms.""" + from typing import Any from homeassistant.components.alarm_control_panel import ( diff --git a/homeassistant/components/freebox/binary_sensor.py b/homeassistant/components/freebox/binary_sensor.py index ef7f1ea3899..1d149d3336c 100644 --- a/homeassistant/components/freebox/binary_sensor.py +++ b/homeassistant/components/freebox/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/freebox/button.py b/homeassistant/components/freebox/button.py index d1268fb91d2..046003b209d 100644 --- a/homeassistant/components/freebox/button.py +++ b/homeassistant/components/freebox/button.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/freebox/camera.py b/homeassistant/components/freebox/camera.py index 96b0f63a92e..879941af040 100644 --- a/homeassistant/components/freebox/camera.py +++ b/homeassistant/components/freebox/camera.py @@ -1,4 +1,5 @@ """Support for Freebox cameras.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/freebox/const.py b/homeassistant/components/freebox/const.py index ef5cabda1b6..13be45926b4 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -1,4 +1,5 @@ """Freebox component constants.""" + from __future__ import annotations import enum diff --git a/homeassistant/components/freebox/device_tracker.py b/homeassistant/components/freebox/device_tracker.py index 663acdc1f15..0f5b7eb4837 100644 --- a/homeassistant/components/freebox/device_tracker.py +++ b/homeassistant/components/freebox/device_tracker.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/freebox/home_base.py b/homeassistant/components/freebox/home_base.py index 2d75494e281..ba7cb69fb99 100644 --- a/homeassistant/components/freebox/home_base.py +++ b/homeassistant/components/freebox/home_base.py @@ -1,4 +1,5 @@ """Support for Freebox base features.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 3b13fad0572..26b3e37beb3 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -1,4 +1,5 @@ """Represent the Freebox router and its devices and sensors.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index 4e7c3910c54..e5a0b8223a9 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -1,4 +1,5 @@ """Support for Freebox devices (Freebox v6 and Freebox mini 4K).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index 5b6dd494f0b..3ffa80429e8 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -1,4 +1,5 @@ """Support for Freebox Delta, Revolution and Mini 4K.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/freedompro/__init__.py b/homeassistant/components/freedompro/__init__.py index 78871bc99bf..c14c2f5ae36 100644 --- a/homeassistant/components/freedompro/__init__.py +++ b/homeassistant/components/freedompro/__init__.py @@ -1,4 +1,5 @@ """Support for freedompro.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/freedompro/binary_sensor.py b/homeassistant/components/freedompro/binary_sensor.py index 3bba3439341..ccea5faf41f 100644 --- a/homeassistant/components/freedompro/binary_sensor.py +++ b/homeassistant/components/freedompro/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Freedompro binary_sensor.""" + from typing import Any from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/freedompro/climate.py b/homeassistant/components/freedompro/climate.py index 3bb62cb23fb..d534db7e858 100644 --- a/homeassistant/components/freedompro/climate.py +++ b/homeassistant/components/freedompro/climate.py @@ -1,4 +1,5 @@ """Support for Freedompro climate.""" + from __future__ import annotations import json diff --git a/homeassistant/components/freedompro/config_flow.py b/homeassistant/components/freedompro/config_flow.py index 6b224c87610..f1dd9dbbf14 100644 --- a/homeassistant/components/freedompro/config_flow.py +++ b/homeassistant/components/freedompro/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Freedompro.""" + from pyfreedompro import get_list import voluptuous as vol diff --git a/homeassistant/components/freedompro/coordinator.py b/homeassistant/components/freedompro/coordinator.py index c896f5ec203..c5f8ea990dc 100644 --- a/homeassistant/components/freedompro/coordinator.py +++ b/homeassistant/components/freedompro/coordinator.py @@ -1,4 +1,5 @@ """Freedompro data update coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/freedompro/fan.py b/homeassistant/components/freedompro/fan.py index 59eb50ebe4a..fe77398ece4 100644 --- a/homeassistant/components/freedompro/fan.py +++ b/homeassistant/components/freedompro/fan.py @@ -1,4 +1,5 @@ """Support for Freedompro fan.""" + from __future__ import annotations import json diff --git a/homeassistant/components/freedompro/light.py b/homeassistant/components/freedompro/light.py index 9df3679ad70..ab8df7ec9db 100644 --- a/homeassistant/components/freedompro/light.py +++ b/homeassistant/components/freedompro/light.py @@ -1,4 +1,5 @@ """Support for Freedompro light.""" + from __future__ import annotations import json diff --git a/homeassistant/components/freedompro/sensor.py b/homeassistant/components/freedompro/sensor.py index dc6861a4f0a..3c5101e3634 100644 --- a/homeassistant/components/freedompro/sensor.py +++ b/homeassistant/components/freedompro/sensor.py @@ -1,4 +1,5 @@ """Support for Freedompro sensor.""" + from typing import Any from homeassistant.components.sensor import ( diff --git a/homeassistant/components/fritz/binary_sensor.py b/homeassistant/components/fritz/binary_sensor.py index f703fadb4b8..adca977e179 100644 --- a/homeassistant/components/fritz/binary_sensor.py +++ b/homeassistant/components/fritz/binary_sensor.py @@ -1,4 +1,5 @@ """AVM FRITZ!Box connectivity sensor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fritz/button.py b/homeassistant/components/fritz/button.py index de34056b0d7..7c7dcaba80f 100644 --- a/homeassistant/components/fritz/button.py +++ b/homeassistant/components/fritz/button.py @@ -1,4 +1,5 @@ """Switches for AVM Fritz!Box buttons.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 3d287b57384..f3468a0a161 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!Box classes.""" + from __future__ import annotations from collections.abc import Callable, ValuesView diff --git a/homeassistant/components/fritz/config_flow.py b/homeassistant/components/fritz/config_flow.py index 99a1917e932..a217adf935c 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the FRITZ!Box Tools integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fritz/device_tracker.py b/homeassistant/components/fritz/device_tracker.py index d4ba53aa6a2..89ba6c1cad8 100644 --- a/homeassistant/components/fritz/device_tracker.py +++ b/homeassistant/components/fritz/device_tracker.py @@ -1,4 +1,5 @@ """Support for FRITZ!Box devices.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/fritz/diagnostics.py b/homeassistant/components/fritz/diagnostics.py index 0322e55a9e0..3136f03f95b 100644 --- a/homeassistant/components/fritz/diagnostics.py +++ b/homeassistant/components/fritz/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for AVM FRITZ!Box.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 7fcc4944ec5..aa9c410a545 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -1,4 +1,5 @@ """AVM FRITZ!Box binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fritz/services.py b/homeassistant/components/fritz/services.py index e0b41d0e87e..47fb0ceb1c6 100644 --- a/homeassistant/components/fritz/services.py +++ b/homeassistant/components/fritz/services.py @@ -1,4 +1,5 @@ """Services for Fritz integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index c3da6b5af0b..26926c09223 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -1,4 +1,5 @@ """Switches for AVM Fritz!Box functions.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fritz/update.py b/homeassistant/components/fritz/update.py index fafd9c37ab8..1a24a8dd152 100644 --- a/homeassistant/components/fritz/update.py +++ b/homeassistant/components/fritz/update.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!Box update platform.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/fritzbox/__init__.py b/homeassistant/components/fritzbox/__init__.py index 8cb41ebcbe1..7f4006768c4 100644 --- a/homeassistant/components/fritzbox/__init__.py +++ b/homeassistant/components/fritzbox/__init__.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome devices.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index c6676bb1bbf..08fddc8a0ae 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Fritzbox binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fritzbox/button.py b/homeassistant/components/fritzbox/button.py index 6695c564331..f3ea03f91b2 100644 --- a/homeassistant/components/fritzbox/button.py +++ b/homeassistant/components/fritzbox/button.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome templates.""" + from pyfritzhome.devicetypes import FritzhomeTemplate from homeassistant.components.button import ButtonEntity diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 8dc19c199a3..17accf35819 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome thermostat devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index cf42a41b179..377d46eceff 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -1,4 +1,5 @@ """Config flow for AVM FRITZ!SmartHome.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fritzbox/const.py b/homeassistant/components/fritzbox/const.py index 791da4540a4..d664bd3a8d4 100644 --- a/homeassistant/components/fritzbox/const.py +++ b/homeassistant/components/fritzbox/const.py @@ -1,4 +1,5 @@ """Constants for the AVM FRITZ!SmartHome integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fritzbox/coordinator.py b/homeassistant/components/fritzbox/coordinator.py index f6d210e367a..c58665f2b5d 100644 --- a/homeassistant/components/fritzbox/coordinator.py +++ b/homeassistant/components/fritzbox/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for AVM FRITZ!SmartHome devices.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/fritzbox/cover.py b/homeassistant/components/fritzbox/cover.py index 4c2ba76c377..bd80b5f4af1 100644 --- a/homeassistant/components/fritzbox/cover.py +++ b/homeassistant/components/fritzbox/cover.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome cover devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fritzbox/diagnostics.py b/homeassistant/components/fritzbox/diagnostics.py index 6c50e1311df..93e560e3117 100644 --- a/homeassistant/components/fritzbox/diagnostics.py +++ b/homeassistant/components/fritzbox/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for AVM Fritz!Smarthome.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fritzbox/light.py b/homeassistant/components/fritzbox/light.py index 6c06f2cc699..dbc09beb235 100644 --- a/homeassistant/components/fritzbox/light.py +++ b/homeassistant/components/fritzbox/light.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome lightbulbs.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/fritzbox/model.py b/homeassistant/components/fritzbox/model.py index 74c5bd42927..f0353bc58d6 100644 --- a/homeassistant/components/fritzbox/model.py +++ b/homeassistant/components/fritzbox/model.py @@ -1,4 +1,5 @@ """Models for the AVM FRITZ!SmartHome integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index fd55369d915..29f61d6e466 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome temperature sensor only devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 4d93cddb617..b7ad08785f4 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -1,4 +1,5 @@ """Support for AVM FRITZ!SmartHome switch devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fritzbox_callmonitor/base.py b/homeassistant/components/fritzbox_callmonitor/base.py index df19bca7b13..72d17b57abc 100644 --- a/homeassistant/components/fritzbox_callmonitor/base.py +++ b/homeassistant/components/fritzbox_callmonitor/base.py @@ -1,4 +1,5 @@ """Base class for fritzbox_callmonitor entities.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index 5150abc395c..acb16acbded 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -1,4 +1,5 @@ """Config flow for fritzbox_callmonitor.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/fritzbox_callmonitor/const.py b/homeassistant/components/fritzbox_callmonitor/const.py index a13a86574df..406a1dd6d64 100644 --- a/homeassistant/components/fritzbox_callmonitor/const.py +++ b/homeassistant/components/fritzbox_callmonitor/const.py @@ -1,4 +1,5 @@ """Constants for the AVM Fritz!Box call monitor integration.""" + from enum import StrEnum from typing import Final diff --git a/homeassistant/components/fritzbox_callmonitor/sensor.py b/homeassistant/components/fritzbox_callmonitor/sensor.py index 036c9605d0a..0a127ec36b3 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -1,4 +1,5 @@ """Sensor to monitor incoming/outgoing phone calls on a Fritz!Box router.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/fronius/__init__.py b/homeassistant/components/fronius/__init__.py index d0e13aa7914..1928bb15bc2 100644 --- a/homeassistant/components/fronius/__init__.py +++ b/homeassistant/components/fronius/__init__.py @@ -1,4 +1,5 @@ """The Fronius integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/fronius/config_flow.py b/homeassistant/components/fronius/config_flow.py index 18abac5e38a..22bfc4a67f4 100644 --- a/homeassistant/components/fronius/config_flow.py +++ b/homeassistant/components/fronius/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Fronius integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/fronius/const.py b/homeassistant/components/fronius/const.py index 18f35de8336..8702339ef03 100644 --- a/homeassistant/components/fronius/const.py +++ b/homeassistant/components/fronius/const.py @@ -1,4 +1,5 @@ """Constants for the Fronius integration.""" + from enum import StrEnum from typing import Final, NamedTuple, TypedDict diff --git a/homeassistant/components/fronius/coordinator.py b/homeassistant/components/fronius/coordinator.py index fcf9ce0a389..b36f1a653d9 100644 --- a/homeassistant/components/fronius/coordinator.py +++ b/homeassistant/components/fronius/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinators for the Fronius integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/fronius/sensor.py b/homeassistant/components/fronius/sensor.py index 2fa4e4fd160..2d79086d8ba 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -1,4 +1,5 @@ """Support for Fronius devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index eff69e9d6f0..2f64a019c19 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -1,4 +1,5 @@ """Handle the frontend for Home Assistant.""" + from __future__ import annotations from collections.abc import Iterator diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py index 91646dcb745..d387e14b085 100644 --- a/homeassistant/components/frontend/storage.py +++ b/homeassistant/components/frontend/storage.py @@ -1,4 +1,5 @@ """API for persistent storage for the frontend.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/frontier_silicon/__init__.py b/homeassistant/components/frontier_silicon/__init__.py index f1e0ad48d30..325af100005 100644 --- a/homeassistant/components/frontier_silicon/__init__.py +++ b/homeassistant/components/frontier_silicon/__init__.py @@ -1,4 +1,5 @@ """The Frontier Silicon integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/frontier_silicon/config_flow.py b/homeassistant/components/frontier_silicon/config_flow.py index 7ee3743860d..a9c87cd9d4a 100644 --- a/homeassistant/components/frontier_silicon/config_flow.py +++ b/homeassistant/components/frontier_silicon/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Frontier Silicon Media Player integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index 223abe26e55..ac72df67014 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -1,4 +1,5 @@ """Support for Frontier Silicon Devices (Medion, Hama, Auna,...).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fully_kiosk/__init__.py b/homeassistant/components/fully_kiosk/__init__.py index 8b350433858..a0ed0cb4fa0 100644 --- a/homeassistant/components/fully_kiosk/__init__.py +++ b/homeassistant/components/fully_kiosk/__init__.py @@ -1,4 +1,5 @@ """The Fully Kiosk Browser integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/fully_kiosk/binary_sensor.py b/homeassistant/components/fully_kiosk/binary_sensor.py index 5eebf8a77ab..3cf9adea1d5 100644 --- a/homeassistant/components/fully_kiosk/binary_sensor.py +++ b/homeassistant/components/fully_kiosk/binary_sensor.py @@ -1,4 +1,5 @@ """Fully Kiosk Browser sensor.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/fully_kiosk/button.py b/homeassistant/components/fully_kiosk/button.py index 0a6233937ae..975a225853b 100644 --- a/homeassistant/components/fully_kiosk/button.py +++ b/homeassistant/components/fully_kiosk/button.py @@ -1,4 +1,5 @@ """Fully Kiosk Browser button.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fully_kiosk/config_flow.py b/homeassistant/components/fully_kiosk/config_flow.py index 0261e2ae52b..8fd0d4ee4cc 100644 --- a/homeassistant/components/fully_kiosk/config_flow.py +++ b/homeassistant/components/fully_kiosk/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Fully Kiosk Browser integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/fully_kiosk/const.py b/homeassistant/components/fully_kiosk/const.py index 3db33d21ef0..35fe539a552 100644 --- a/homeassistant/components/fully_kiosk/const.py +++ b/homeassistant/components/fully_kiosk/const.py @@ -1,4 +1,5 @@ """Constants for the Fully Kiosk Browser integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/fully_kiosk/diagnostics.py b/homeassistant/components/fully_kiosk/diagnostics.py index 121621186cd..df03cb4a7bf 100644 --- a/homeassistant/components/fully_kiosk/diagnostics.py +++ b/homeassistant/components/fully_kiosk/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Fully Kiosk Browser.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fully_kiosk/entity.py b/homeassistant/components/fully_kiosk/entity.py index b053508ae41..a1f077d7886 100644 --- a/homeassistant/components/fully_kiosk/entity.py +++ b/homeassistant/components/fully_kiosk/entity.py @@ -1,4 +1,5 @@ """Base entity for the Fully Kiosk Browser integration.""" + from __future__ import annotations import json diff --git a/homeassistant/components/fully_kiosk/media_player.py b/homeassistant/components/fully_kiosk/media_player.py index 8e6d2fad533..1e258c928e7 100644 --- a/homeassistant/components/fully_kiosk/media_player.py +++ b/homeassistant/components/fully_kiosk/media_player.py @@ -1,4 +1,5 @@ """Fully Kiosk Browser media player.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/fully_kiosk/number.py b/homeassistant/components/fully_kiosk/number.py index 4203a64074d..59c249fd1c2 100644 --- a/homeassistant/components/fully_kiosk/number.py +++ b/homeassistant/components/fully_kiosk/number.py @@ -1,4 +1,5 @@ """Fully Kiosk Browser number entity.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/fully_kiosk/sensor.py b/homeassistant/components/fully_kiosk/sensor.py index 8e9029fda73..48fc8e51425 100644 --- a/homeassistant/components/fully_kiosk/sensor.py +++ b/homeassistant/components/fully_kiosk/sensor.py @@ -1,4 +1,5 @@ """Fully Kiosk Browser sensor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/fully_kiosk/services.py b/homeassistant/components/fully_kiosk/services.py index 5106fd2e06e..c1e0d89f7a1 100644 --- a/homeassistant/components/fully_kiosk/services.py +++ b/homeassistant/components/fully_kiosk/services.py @@ -1,4 +1,5 @@ """Services for the Fully Kiosk Browser integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/fully_kiosk/switch.py b/homeassistant/components/fully_kiosk/switch.py index d5480b784c4..601517e50b6 100644 --- a/homeassistant/components/fully_kiosk/switch.py +++ b/homeassistant/components/fully_kiosk/switch.py @@ -1,4 +1,5 @@ """Fully Kiosk Browser switch.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/futurenow/light.py b/homeassistant/components/futurenow/light.py index d070d832052..8474c1073e9 100644 --- a/homeassistant/components/futurenow/light.py +++ b/homeassistant/components/futurenow/light.py @@ -1,4 +1,5 @@ """Support for FutureNow Ethernet unit outputs as Lights.""" + from __future__ import annotations from typing import Any From 930a39671226e400d3d43231211c75d29a4908ab Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:33:51 +0100 Subject: [PATCH 0541/1691] Add empty line after module docstring [s] (#112704) --- homeassistant/components/sabnzbd/__init__.py | 1 + homeassistant/components/sabnzbd/config_flow.py | 1 + homeassistant/components/sabnzbd/const.py | 1 + homeassistant/components/sabnzbd/sab.py | 1 + homeassistant/components/sabnzbd/sensor.py | 1 + homeassistant/components/saj/sensor.py | 1 + homeassistant/components/samsungtv/__init__.py | 1 + homeassistant/components/samsungtv/bridge.py | 1 + homeassistant/components/samsungtv/config_flow.py | 1 + homeassistant/components/samsungtv/device_trigger.py | 1 + homeassistant/components/samsungtv/diagnostics.py | 1 + homeassistant/components/samsungtv/entity.py | 1 + homeassistant/components/samsungtv/helpers.py | 1 + homeassistant/components/samsungtv/media_player.py | 1 + homeassistant/components/samsungtv/remote.py | 1 + homeassistant/components/samsungtv/trigger.py | 1 + homeassistant/components/samsungtv/triggers/turn_on.py | 1 + homeassistant/components/satel_integra/alarm_control_panel.py | 1 + homeassistant/components/satel_integra/binary_sensor.py | 1 + homeassistant/components/satel_integra/switch.py | 1 + homeassistant/components/scene/__init__.py | 1 + homeassistant/components/schedule/__init__.py | 1 + homeassistant/components/schlage/__init__.py | 1 + homeassistant/components/schlage/config_flow.py | 1 + homeassistant/components/schlage/coordinator.py | 1 + homeassistant/components/schlage/lock.py | 1 + homeassistant/components/schluter/climate.py | 1 + homeassistant/components/scrape/__init__.py | 1 + homeassistant/components/scrape/config_flow.py | 1 + homeassistant/components/scrape/const.py | 1 + homeassistant/components/scrape/coordinator.py | 1 + homeassistant/components/scrape/sensor.py | 1 + homeassistant/components/screenlogic/binary_sensor.py | 1 + homeassistant/components/screenlogic/climate.py | 1 + homeassistant/components/screenlogic/config_flow.py | 1 + homeassistant/components/screenlogic/const.py | 1 + homeassistant/components/screenlogic/coordinator.py | 1 + homeassistant/components/screenlogic/data.py | 1 + homeassistant/components/screenlogic/entity.py | 1 + homeassistant/components/screenlogic/light.py | 1 + homeassistant/components/screenlogic/number.py | 1 + homeassistant/components/screenlogic/sensor.py | 1 + homeassistant/components/screenlogic/switch.py | 1 + homeassistant/components/script/__init__.py | 1 + homeassistant/components/script/config.py | 1 + homeassistant/components/script/helpers.py | 1 + homeassistant/components/script/logbook.py | 1 + homeassistant/components/script/trace.py | 1 + homeassistant/components/scsgate/cover.py | 1 + homeassistant/components/scsgate/light.py | 1 + homeassistant/components/scsgate/switch.py | 1 + homeassistant/components/search/__init__.py | 1 + homeassistant/components/season/__init__.py | 1 + homeassistant/components/season/config_flow.py | 1 + homeassistant/components/season/const.py | 1 + homeassistant/components/season/sensor.py | 1 + homeassistant/components/select/__init__.py | 1 + homeassistant/components/select/device_action.py | 1 + homeassistant/components/select/device_condition.py | 1 + homeassistant/components/select/device_trigger.py | 1 + homeassistant/components/select/reproduce_state.py | 1 + homeassistant/components/select/significant_change.py | 1 + homeassistant/components/sendgrid/notify.py | 1 + homeassistant/components/sense/__init__.py | 1 + homeassistant/components/sense/config_flow.py | 1 + homeassistant/components/sensibo/__init__.py | 1 + homeassistant/components/sensibo/binary_sensor.py | 1 + homeassistant/components/sensibo/button.py | 1 + homeassistant/components/sensibo/climate.py | 1 + homeassistant/components/sensibo/config_flow.py | 1 + homeassistant/components/sensibo/coordinator.py | 1 + homeassistant/components/sensibo/diagnostics.py | 1 + homeassistant/components/sensibo/entity.py | 1 + homeassistant/components/sensibo/number.py | 1 + homeassistant/components/sensibo/select.py | 1 + homeassistant/components/sensibo/sensor.py | 1 + homeassistant/components/sensibo/switch.py | 1 + homeassistant/components/sensibo/update.py | 1 + homeassistant/components/sensibo/util.py | 1 + homeassistant/components/sensirion_ble/__init__.py | 1 + homeassistant/components/sensirion_ble/config_flow.py | 1 + homeassistant/components/sensirion_ble/sensor.py | 1 + homeassistant/components/sensor/__init__.py | 1 + homeassistant/components/sensor/const.py | 1 + homeassistant/components/sensor/device_condition.py | 1 + homeassistant/components/sensor/helpers.py | 1 + homeassistant/components/sensor/recorder.py | 1 + homeassistant/components/sensor/significant_change.py | 1 + homeassistant/components/sensor/websocket_api.py | 1 + homeassistant/components/sensorpro/__init__.py | 1 + homeassistant/components/sensorpro/config_flow.py | 1 + homeassistant/components/sensorpro/device.py | 1 + homeassistant/components/sensorpro/sensor.py | 1 + homeassistant/components/sensorpush/__init__.py | 1 + homeassistant/components/sensorpush/config_flow.py | 1 + homeassistant/components/sensorpush/sensor.py | 1 + homeassistant/components/sentry/__init__.py | 1 + homeassistant/components/sentry/config_flow.py | 1 + homeassistant/components/senz/__init__.py | 1 + homeassistant/components/senz/api.py | 1 + homeassistant/components/senz/climate.py | 1 + homeassistant/components/serial/sensor.py | 1 + homeassistant/components/serial_pm/sensor.py | 1 + homeassistant/components/sesame/lock.py | 1 + homeassistant/components/seven_segments/image_processing.py | 1 + homeassistant/components/seventeentrack/sensor.py | 1 + homeassistant/components/sfr_box/__init__.py | 1 + homeassistant/components/sfr_box/binary_sensor.py | 1 + homeassistant/components/sfr_box/button.py | 1 + homeassistant/components/sfr_box/config_flow.py | 1 + homeassistant/components/sfr_box/const.py | 1 + homeassistant/components/sfr_box/coordinator.py | 1 + homeassistant/components/sfr_box/diagnostics.py | 1 + homeassistant/components/sfr_box/models.py | 1 + homeassistant/components/sfr_box/sensor.py | 1 + homeassistant/components/sharkiq/config_flow.py | 1 + homeassistant/components/sharkiq/const.py | 1 + homeassistant/components/sharkiq/update_coordinator.py | 1 + homeassistant/components/sharkiq/vacuum.py | 1 + homeassistant/components/shell_command/__init__.py | 1 + homeassistant/components/shelly/__init__.py | 1 + homeassistant/components/shelly/binary_sensor.py | 1 + homeassistant/components/shelly/bluetooth/__init__.py | 1 + homeassistant/components/shelly/button.py | 1 + homeassistant/components/shelly/climate.py | 1 + homeassistant/components/shelly/config_flow.py | 1 + homeassistant/components/shelly/const.py | 1 + homeassistant/components/shelly/coordinator.py | 1 + homeassistant/components/shelly/cover.py | 1 + homeassistant/components/shelly/device_trigger.py | 1 + homeassistant/components/shelly/diagnostics.py | 1 + homeassistant/components/shelly/entity.py | 1 + homeassistant/components/shelly/event.py | 1 + homeassistant/components/shelly/light.py | 1 + homeassistant/components/shelly/logbook.py | 1 + homeassistant/components/shelly/number.py | 1 + homeassistant/components/shelly/sensor.py | 1 + homeassistant/components/shelly/switch.py | 1 + homeassistant/components/shelly/update.py | 1 + homeassistant/components/shelly/utils.py | 1 + homeassistant/components/shelly/valve.py | 1 + homeassistant/components/shodan/sensor.py | 1 + homeassistant/components/shopping_list/__init__.py | 1 + homeassistant/components/shopping_list/config_flow.py | 1 + homeassistant/components/shopping_list/intent.py | 1 + homeassistant/components/sia/__init__.py | 1 + homeassistant/components/sia/alarm_control_panel.py | 1 + homeassistant/components/sia/binary_sensor.py | 1 + homeassistant/components/sia/config_flow.py | 1 + homeassistant/components/sia/const.py | 1 + homeassistant/components/sia/hub.py | 1 + homeassistant/components/sia/sia_entity_base.py | 1 + homeassistant/components/sia/utils.py | 1 + homeassistant/components/sigfox/sensor.py | 1 + homeassistant/components/sighthound/image_processing.py | 1 + homeassistant/components/signal_messenger/notify.py | 1 + homeassistant/components/simplepush/config_flow.py | 1 + homeassistant/components/simplepush/notify.py | 1 + homeassistant/components/simplisafe/__init__.py | 1 + homeassistant/components/simplisafe/alarm_control_panel.py | 1 + homeassistant/components/simplisafe/binary_sensor.py | 1 + homeassistant/components/simplisafe/button.py | 1 + homeassistant/components/simplisafe/config_flow.py | 1 + homeassistant/components/simplisafe/diagnostics.py | 1 + homeassistant/components/simplisafe/lock.py | 1 + homeassistant/components/simplisafe/sensor.py | 1 + homeassistant/components/simplisafe/typing.py | 1 + homeassistant/components/simulated/sensor.py | 1 + homeassistant/components/sinch/notify.py | 1 + homeassistant/components/siren/__init__.py | 1 + homeassistant/components/sisyphus/light.py | 1 + homeassistant/components/sisyphus/media_player.py | 1 + homeassistant/components/sky_hub/device_tracker.py | 1 + homeassistant/components/skybeacon/sensor.py | 1 + homeassistant/components/skybell/__init__.py | 1 + homeassistant/components/skybell/binary_sensor.py | 1 + homeassistant/components/skybell/camera.py | 1 + homeassistant/components/skybell/config_flow.py | 1 + homeassistant/components/skybell/entity.py | 1 + homeassistant/components/skybell/light.py | 1 + homeassistant/components/skybell/sensor.py | 1 + homeassistant/components/skybell/switch.py | 1 + homeassistant/components/slack/__init__.py | 1 + homeassistant/components/slack/config_flow.py | 1 + homeassistant/components/slack/const.py | 1 + homeassistant/components/slack/notify.py | 1 + homeassistant/components/slack/sensor.py | 1 + homeassistant/components/sleepiq/__init__.py | 1 + homeassistant/components/sleepiq/binary_sensor.py | 1 + homeassistant/components/sleepiq/button.py | 1 + homeassistant/components/sleepiq/config_flow.py | 1 + homeassistant/components/sleepiq/entity.py | 1 + homeassistant/components/sleepiq/number.py | 1 + homeassistant/components/sleepiq/select.py | 1 + homeassistant/components/sleepiq/sensor.py | 1 + homeassistant/components/sleepiq/switch.py | 1 + homeassistant/components/slide/__init__.py | 1 + homeassistant/components/slide/cover.py | 1 + homeassistant/components/slimproto/__init__.py | 1 + homeassistant/components/slimproto/media_player.py | 1 + homeassistant/components/sma/__init__.py | 1 + homeassistant/components/sma/config_flow.py | 1 + homeassistant/components/sma/const.py | 1 + homeassistant/components/sma/sensor.py | 1 + homeassistant/components/smappee/api.py | 1 + homeassistant/components/smappee/binary_sensor.py | 1 + homeassistant/components/smappee/sensor.py | 1 + homeassistant/components/smappee/switch.py | 1 + homeassistant/components/smart_meter_texas/const.py | 1 + homeassistant/components/smart_meter_texas/sensor.py | 1 + homeassistant/components/smartthings/__init__.py | 1 + homeassistant/components/smartthings/binary_sensor.py | 1 + homeassistant/components/smartthings/climate.py | 1 + homeassistant/components/smartthings/config_flow.py | 1 + homeassistant/components/smartthings/const.py | 1 + homeassistant/components/smartthings/cover.py | 1 + homeassistant/components/smartthings/fan.py | 1 + homeassistant/components/smartthings/light.py | 1 + homeassistant/components/smartthings/lock.py | 1 + homeassistant/components/smartthings/scene.py | 1 + homeassistant/components/smartthings/sensor.py | 1 + homeassistant/components/smartthings/switch.py | 1 + homeassistant/components/smarttub/__init__.py | 1 + homeassistant/components/smarttub/binary_sensor.py | 1 + homeassistant/components/smarttub/climate.py | 1 + homeassistant/components/smarttub/config_flow.py | 1 + homeassistant/components/smarttub/light.py | 1 + homeassistant/components/smarttub/sensor.py | 1 + homeassistant/components/smarty/__init__.py | 1 + homeassistant/components/smarty/binary_sensor.py | 1 + homeassistant/components/smarty/fan.py | 1 + homeassistant/components/smarty/sensor.py | 1 + homeassistant/components/smhi/__init__.py | 1 + homeassistant/components/smhi/config_flow.py | 1 + homeassistant/components/smhi/const.py | 1 + homeassistant/components/smhi/weather.py | 1 + homeassistant/components/sms/notify.py | 1 + homeassistant/components/sms/sensor.py | 1 + homeassistant/components/smtp/notify.py | 1 + homeassistant/components/snapcast/const.py | 1 + homeassistant/components/snapcast/media_player.py | 1 + homeassistant/components/snapcast/server.py | 1 + homeassistant/components/snips/__init__.py | 1 + homeassistant/components/snmp/device_tracker.py | 1 + homeassistant/components/snmp/sensor.py | 1 + homeassistant/components/snmp/switch.py | 1 + homeassistant/components/snooz/__init__.py | 1 + homeassistant/components/snooz/config_flow.py | 1 + homeassistant/components/snooz/fan.py | 1 + homeassistant/components/solaredge/__init__.py | 1 + homeassistant/components/solaredge/config_flow.py | 1 + homeassistant/components/solaredge/const.py | 1 + homeassistant/components/solaredge/coordinator.py | 1 + homeassistant/components/solaredge/sensor.py | 1 + homeassistant/components/solaredge_local/sensor.py | 1 + homeassistant/components/solarlog/__init__.py | 1 + homeassistant/components/solarlog/const.py | 1 + homeassistant/components/solarlog/coordinator.py | 1 + homeassistant/components/solarlog/sensor.py | 1 + homeassistant/components/solax/__init__.py | 1 + homeassistant/components/solax/config_flow.py | 1 + homeassistant/components/solax/sensor.py | 1 + homeassistant/components/soma/__init__.py | 1 + homeassistant/components/soma/cover.py | 1 + homeassistant/components/soma/sensor.py | 1 + homeassistant/components/somfy_mylink/config_flow.py | 1 + homeassistant/components/somfy_mylink/const.py | 1 + homeassistant/components/sonarr/__init__.py | 1 + homeassistant/components/sonarr/config_flow.py | 1 + homeassistant/components/sonarr/coordinator.py | 1 + homeassistant/components/sonarr/entity.py | 1 + homeassistant/components/sonarr/sensor.py | 1 + homeassistant/components/songpal/config_flow.py | 1 + homeassistant/components/songpal/media_player.py | 1 + homeassistant/components/sonos/__init__.py | 1 + homeassistant/components/sonos/alarms.py | 1 + homeassistant/components/sonos/binary_sensor.py | 1 + homeassistant/components/sonos/config_flow.py | 1 + homeassistant/components/sonos/const.py | 1 + homeassistant/components/sonos/diagnostics.py | 1 + homeassistant/components/sonos/entity.py | 1 + homeassistant/components/sonos/exception.py | 1 + homeassistant/components/sonos/favorites.py | 1 + homeassistant/components/sonos/helpers.py | 1 + homeassistant/components/sonos/household_coordinator.py | 1 + homeassistant/components/sonos/media.py | 1 + homeassistant/components/sonos/media_browser.py | 1 + homeassistant/components/sonos/media_player.py | 1 + homeassistant/components/sonos/number.py | 1 + homeassistant/components/sonos/sensor.py | 1 + homeassistant/components/sonos/speaker.py | 1 + homeassistant/components/sonos/statistics.py | 1 + homeassistant/components/sonos/switch.py | 1 + homeassistant/components/sony_projector/switch.py | 1 + homeassistant/components/soundtouch/media_player.py | 1 + homeassistant/components/spaceapi/__init__.py | 1 + homeassistant/components/spc/alarm_control_panel.py | 1 + homeassistant/components/spc/binary_sensor.py | 1 + homeassistant/components/speedtestdotnet/__init__.py | 1 + homeassistant/components/speedtestdotnet/config_flow.py | 1 + homeassistant/components/speedtestdotnet/const.py | 1 + homeassistant/components/speedtestdotnet/sensor.py | 1 + homeassistant/components/spider/climate.py | 1 + homeassistant/components/spider/const.py | 1 + homeassistant/components/spider/sensor.py | 1 + homeassistant/components/spider/switch.py | 1 + homeassistant/components/splunk/__init__.py | 1 + homeassistant/components/spotify/__init__.py | 1 + homeassistant/components/spotify/browse_media.py | 1 + homeassistant/components/spotify/config_flow.py | 1 + homeassistant/components/spotify/media_player.py | 1 + homeassistant/components/spotify/system_health.py | 1 + homeassistant/components/spotify/util.py | 1 + homeassistant/components/sql/__init__.py | 1 + homeassistant/components/sql/config_flow.py | 1 + homeassistant/components/sql/models.py | 1 + homeassistant/components/sql/sensor.py | 1 + homeassistant/components/sql/util.py | 1 + homeassistant/components/squeezebox/media_player.py | 1 + homeassistant/components/srp_energy/__init__.py | 1 + homeassistant/components/srp_energy/config_flow.py | 1 + homeassistant/components/srp_energy/const.py | 1 + homeassistant/components/srp_energy/coordinator.py | 1 + homeassistant/components/srp_energy/sensor.py | 1 + homeassistant/components/ssdp/__init__.py | 1 + homeassistant/components/starline/__init__.py | 1 + homeassistant/components/starline/account.py | 1 + homeassistant/components/starline/binary_sensor.py | 1 + homeassistant/components/starline/button.py | 1 + homeassistant/components/starline/config_flow.py | 1 + homeassistant/components/starline/device_tracker.py | 1 + homeassistant/components/starline/entity.py | 1 + homeassistant/components/starline/lock.py | 1 + homeassistant/components/starline/sensor.py | 1 + homeassistant/components/starline/switch.py | 1 + homeassistant/components/starlingbank/sensor.py | 1 + homeassistant/components/starlink/__init__.py | 1 + homeassistant/components/starlink/config_flow.py | 1 + homeassistant/components/starlink/coordinator.py | 1 + homeassistant/components/starlink/entity.py | 1 + homeassistant/components/starlink/sensor.py | 1 + homeassistant/components/startca/sensor.py | 1 + homeassistant/components/statistics/sensor.py | 1 + homeassistant/components/steam_online/__init__.py | 1 + homeassistant/components/steam_online/config_flow.py | 1 + homeassistant/components/steam_online/coordinator.py | 1 + homeassistant/components/steam_online/entity.py | 1 + homeassistant/components/steam_online/sensor.py | 1 + homeassistant/components/steamist/__init__.py | 1 + homeassistant/components/steamist/config_flow.py | 1 + homeassistant/components/steamist/coordinator.py | 1 + homeassistant/components/steamist/discovery.py | 1 + homeassistant/components/steamist/entity.py | 1 + homeassistant/components/steamist/sensor.py | 1 + homeassistant/components/steamist/switch.py | 1 + homeassistant/components/stiebel_eltron/__init__.py | 1 + homeassistant/components/stiebel_eltron/climate.py | 1 + homeassistant/components/stookalert/__init__.py | 1 + homeassistant/components/stookalert/binary_sensor.py | 1 + homeassistant/components/stookalert/config_flow.py | 1 + homeassistant/components/stookalert/diagnostics.py | 1 + homeassistant/components/stookwijzer/__init__.py | 1 + homeassistant/components/stookwijzer/config_flow.py | 1 + homeassistant/components/stookwijzer/const.py | 1 + homeassistant/components/stookwijzer/diagnostics.py | 1 + homeassistant/components/stookwijzer/sensor.py | 1 + homeassistant/components/stream/__init__.py | 1 + homeassistant/components/stream/core.py | 1 + homeassistant/components/stream/fmp4utils.py | 1 + homeassistant/components/stream/hls.py | 1 + homeassistant/components/stream/recorder.py | 1 + homeassistant/components/stream/worker.py | 1 + homeassistant/components/streamlabswater/binary_sensor.py | 1 + homeassistant/components/streamlabswater/config_flow.py | 1 + homeassistant/components/streamlabswater/coordinator.py | 1 + homeassistant/components/streamlabswater/entity.py | 1 + homeassistant/components/streamlabswater/sensor.py | 1 + homeassistant/components/stt/__init__.py | 1 + homeassistant/components/stt/const.py | 1 + homeassistant/components/stt/legacy.py | 1 + homeassistant/components/stt/models.py | 1 + homeassistant/components/subaru/__init__.py | 1 + homeassistant/components/subaru/config_flow.py | 1 + homeassistant/components/subaru/const.py | 1 + homeassistant/components/subaru/device_tracker.py | 1 + homeassistant/components/subaru/diagnostics.py | 1 + homeassistant/components/subaru/sensor.py | 1 + homeassistant/components/suez_water/__init__.py | 1 + homeassistant/components/suez_water/config_flow.py | 1 + homeassistant/components/suez_water/sensor.py | 1 + homeassistant/components/sun/__init__.py | 1 + homeassistant/components/sun/config_flow.py | 1 + homeassistant/components/sun/const.py | 1 + homeassistant/components/sun/sensor.py | 1 + homeassistant/components/sun/trigger.py | 1 + homeassistant/components/sunweg/config_flow.py | 1 + homeassistant/components/sunweg/const.py | 1 + homeassistant/components/sunweg/sensor.py | 1 + homeassistant/components/sunweg/sensor_types/inverter.py | 1 + homeassistant/components/sunweg/sensor_types/phase.py | 1 + .../components/sunweg/sensor_types/sensor_entity_description.py | 1 + homeassistant/components/sunweg/sensor_types/string.py | 1 + homeassistant/components/sunweg/sensor_types/total.py | 1 + homeassistant/components/supervisord/sensor.py | 1 + homeassistant/components/supla/__init__.py | 1 + homeassistant/components/supla/cover.py | 1 + homeassistant/components/supla/entity.py | 1 + homeassistant/components/supla/switch.py | 1 + homeassistant/components/surepetcare/__init__.py | 1 + homeassistant/components/surepetcare/binary_sensor.py | 1 + homeassistant/components/surepetcare/config_flow.py | 1 + homeassistant/components/surepetcare/entity.py | 1 + homeassistant/components/surepetcare/lock.py | 1 + homeassistant/components/surepetcare/sensor.py | 1 + homeassistant/components/swiss_hydrological_data/sensor.py | 1 + homeassistant/components/swiss_public_transport/coordinator.py | 1 + homeassistant/components/swiss_public_transport/sensor.py | 1 + homeassistant/components/swisscom/device_tracker.py | 1 + homeassistant/components/switch/__init__.py | 1 + homeassistant/components/switch/device_action.py | 1 + homeassistant/components/switch/device_condition.py | 1 + homeassistant/components/switch/device_trigger.py | 1 + homeassistant/components/switch/light.py | 1 + homeassistant/components/switch/reproduce_state.py | 1 + homeassistant/components/switch/significant_change.py | 1 + homeassistant/components/switch_as_x/__init__.py | 1 + homeassistant/components/switch_as_x/config_flow.py | 1 + homeassistant/components/switch_as_x/cover.py | 1 + homeassistant/components/switch_as_x/entity.py | 1 + homeassistant/components/switch_as_x/fan.py | 1 + homeassistant/components/switch_as_x/light.py | 1 + homeassistant/components/switch_as_x/lock.py | 1 + homeassistant/components/switch_as_x/siren.py | 1 + homeassistant/components/switch_as_x/valve.py | 1 + homeassistant/components/switchbee/config_flow.py | 1 + homeassistant/components/switchbot/binary_sensor.py | 1 + homeassistant/components/switchbot/config_flow.py | 1 + homeassistant/components/switchbot/const.py | 1 + homeassistant/components/switchbot/coordinator.py | 1 + homeassistant/components/switchbot/cover.py | 1 + homeassistant/components/switchbot/entity.py | 1 + homeassistant/components/switchbot/humidifier.py | 1 + homeassistant/components/switchbot/light.py | 1 + homeassistant/components/switchbot/lock.py | 1 + homeassistant/components/switchbot/sensor.py | 1 + homeassistant/components/switchbot/switch.py | 1 + homeassistant/components/switchbot_cloud/__init__.py | 1 + homeassistant/components/switchbot_cloud/const.py | 1 + homeassistant/components/switchbot_cloud/coordinator.py | 1 + homeassistant/components/switchbot_cloud/entity.py | 1 + homeassistant/components/switchbot_cloud/switch.py | 1 + homeassistant/components/switcher_kis/__init__.py | 1 + homeassistant/components/switcher_kis/button.py | 1 + homeassistant/components/switcher_kis/climate.py | 1 + homeassistant/components/switcher_kis/config_flow.py | 1 + homeassistant/components/switcher_kis/cover.py | 1 + homeassistant/components/switcher_kis/diagnostics.py | 1 + homeassistant/components/switcher_kis/sensor.py | 1 + homeassistant/components/switcher_kis/switch.py | 1 + homeassistant/components/switcher_kis/utils.py | 1 + homeassistant/components/switchmate/switch.py | 1 + homeassistant/components/syncthing/const.py | 1 + homeassistant/components/syncthru/__init__.py | 1 + homeassistant/components/syncthru/binary_sensor.py | 1 + homeassistant/components/syncthru/sensor.py | 1 + homeassistant/components/synology_chat/notify.py | 1 + homeassistant/components/synology_dsm/__init__.py | 1 + homeassistant/components/synology_dsm/binary_sensor.py | 1 + homeassistant/components/synology_dsm/button.py | 1 + homeassistant/components/synology_dsm/camera.py | 1 + homeassistant/components/synology_dsm/common.py | 1 + homeassistant/components/synology_dsm/config_flow.py | 1 + homeassistant/components/synology_dsm/const.py | 1 + homeassistant/components/synology_dsm/coordinator.py | 1 + homeassistant/components/synology_dsm/diagnostics.py | 1 + homeassistant/components/synology_dsm/entity.py | 1 + homeassistant/components/synology_dsm/media_source.py | 1 + homeassistant/components/synology_dsm/models.py | 1 + homeassistant/components/synology_dsm/sensor.py | 1 + homeassistant/components/synology_dsm/service.py | 1 + homeassistant/components/synology_dsm/switch.py | 1 + homeassistant/components/synology_dsm/update.py | 1 + homeassistant/components/synology_srm/device_tracker.py | 1 + homeassistant/components/syslog/notify.py | 1 + homeassistant/components/system_bridge/__init__.py | 1 + homeassistant/components/system_bridge/binary_sensor.py | 1 + homeassistant/components/system_bridge/config_flow.py | 1 + homeassistant/components/system_bridge/coordinator.py | 1 + homeassistant/components/system_bridge/data.py | 1 + homeassistant/components/system_bridge/entity.py | 1 + homeassistant/components/system_bridge/media_player.py | 1 + homeassistant/components/system_bridge/media_source.py | 1 + homeassistant/components/system_bridge/notify.py | 1 + homeassistant/components/system_bridge/sensor.py | 1 + homeassistant/components/system_bridge/update.py | 1 + homeassistant/components/system_health/__init__.py | 1 + homeassistant/components/system_log/__init__.py | 1 + homeassistant/components/systemmonitor/config_flow.py | 1 + homeassistant/components/systemmonitor/diagnostics.py | 1 + 499 files changed, 499 insertions(+) diff --git a/homeassistant/components/sabnzbd/__init__.py b/homeassistant/components/sabnzbd/__init__.py index 7d0437da033..6a68f98203b 100644 --- a/homeassistant/components/sabnzbd/__init__.py +++ b/homeassistant/components/sabnzbd/__init__.py @@ -1,4 +1,5 @@ """Support for monitoring an SABnzbd NZB client.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/sabnzbd/config_flow.py b/homeassistant/components/sabnzbd/config_flow.py index f3f974eef71..12138a8a085 100644 --- a/homeassistant/components/sabnzbd/config_flow.py +++ b/homeassistant/components/sabnzbd/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for SabNzbd.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sabnzbd/const.py b/homeassistant/components/sabnzbd/const.py index 8add1f61493..a9cd80898f7 100644 --- a/homeassistant/components/sabnzbd/const.py +++ b/homeassistant/components/sabnzbd/const.py @@ -1,4 +1,5 @@ """Constants for the Sabnzbd component.""" + from datetime import timedelta DOMAIN = "sabnzbd" diff --git a/homeassistant/components/sabnzbd/sab.py b/homeassistant/components/sabnzbd/sab.py index af70e4b8afc..0114e14e516 100644 --- a/homeassistant/components/sabnzbd/sab.py +++ b/homeassistant/components/sabnzbd/sab.py @@ -1,4 +1,5 @@ """Support for the Sabnzbd service.""" + from pysabnzbd import SabnzbdApi, SabnzbdApiException from homeassistant.const import CONF_API_KEY, CONF_URL diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index ff33c084ffa..330a0a8c8d1 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring an SABnzbd NZB client.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 866279af973..d420d499c4b 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -1,4 +1,5 @@ """SAJ solar inverter interface.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 56fd230fd6f..9dcb2f9f57e 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -1,4 +1,5 @@ """The Samsung TV integration.""" + from __future__ import annotations from collections.abc import Coroutine, Mapping diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index f2767ce693e..817437ef4d6 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -1,4 +1,5 @@ """samsungctl and samsungtvws bridge classes.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index 87c795e2952..fbaab44ddec 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Samsung TV.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/samsungtv/device_trigger.py b/homeassistant/components/samsungtv/device_trigger.py index f3a69e637e6..e47cde785eb 100644 --- a/homeassistant/components/samsungtv/device_trigger.py +++ b/homeassistant/components/samsungtv/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for control of Samsung TV.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/samsungtv/diagnostics.py b/homeassistant/components/samsungtv/diagnostics.py index 319e08827cf..5ce0c0393ca 100644 --- a/homeassistant/components/samsungtv/diagnostics.py +++ b/homeassistant/components/samsungtv/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for SamsungTV.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/samsungtv/entity.py b/homeassistant/components/samsungtv/entity.py index 384a7a21528..ee2f50716eb 100644 --- a/homeassistant/components/samsungtv/entity.py +++ b/homeassistant/components/samsungtv/entity.py @@ -1,4 +1,5 @@ """Base SamsungTV Entity.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/samsungtv/helpers.py b/homeassistant/components/samsungtv/helpers.py index 06a3c3e70e1..b334c60442b 100644 --- a/homeassistant/components/samsungtv/helpers.py +++ b/homeassistant/components/samsungtv/helpers.py @@ -1,4 +1,5 @@ """Helper functions for Samsung TV.""" + from __future__ import annotations from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index d8f6624dfea..36715c44a9b 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -1,4 +1,5 @@ """Support for interface with an Samsung TV.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/samsungtv/remote.py b/homeassistant/components/samsungtv/remote.py index bbe65d2ac82..752c5e2f950 100644 --- a/homeassistant/components/samsungtv/remote.py +++ b/homeassistant/components/samsungtv/remote.py @@ -1,4 +1,5 @@ """Support for the SamsungTV remote.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/samsungtv/trigger.py b/homeassistant/components/samsungtv/trigger.py index cd78ff18be7..dc32617b583 100644 --- a/homeassistant/components/samsungtv/trigger.py +++ b/homeassistant/components/samsungtv/trigger.py @@ -1,4 +1,5 @@ """Samsung TV trigger dispatcher.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/samsungtv/triggers/turn_on.py b/homeassistant/components/samsungtv/triggers/turn_on.py index de0036234ad..6bcb9365b67 100644 --- a/homeassistant/components/samsungtv/triggers/turn_on.py +++ b/homeassistant/components/samsungtv/triggers/turn_on.py @@ -1,4 +1,5 @@ """Samsung TV device turn on trigger.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/satel_integra/alarm_control_panel.py b/homeassistant/components/satel_integra/alarm_control_panel.py index 79ef4c048b3..bce2c2c6a5d 100644 --- a/homeassistant/components/satel_integra/alarm_control_panel.py +++ b/homeassistant/components/satel_integra/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Satel Integra alarm, using ETHM module.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/satel_integra/binary_sensor.py b/homeassistant/components/satel_integra/binary_sensor.py index 5d2ce2c193c..b668ced326c 100644 --- a/homeassistant/components/satel_integra/binary_sensor.py +++ b/homeassistant/components/satel_integra/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Satel Integra zone states- represented as binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/satel_integra/switch.py b/homeassistant/components/satel_integra/switch.py index 469b2280290..6ce82908de7 100644 --- a/homeassistant/components/satel_integra/switch.py +++ b/homeassistant/components/satel_integra/switch.py @@ -1,4 +1,5 @@ """Support for Satel Integra modifiable outputs represented as switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 3c8adbd0502..5a74e64ca53 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -1,4 +1,5 @@ """Allow users to set and activate scenes.""" + from __future__ import annotations import functools as ft diff --git a/homeassistant/components/schedule/__init__.py b/homeassistant/components/schedule/__init__.py index 2f7831fedd4..2dc2ff2d035 100644 --- a/homeassistant/components/schedule/__init__.py +++ b/homeassistant/components/schedule/__init__.py @@ -1,4 +1,5 @@ """Support for schedules in Home Assistant.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/schlage/__init__.py b/homeassistant/components/schlage/__init__.py index 96ff32d3e85..1c3ad547f3d 100644 --- a/homeassistant/components/schlage/__init__.py +++ b/homeassistant/components/schlage/__init__.py @@ -1,4 +1,5 @@ """The Schlage integration.""" + from __future__ import annotations from pycognito.exceptions import WarrantException diff --git a/homeassistant/components/schlage/config_flow.py b/homeassistant/components/schlage/config_flow.py index 80693013af7..217cacedc41 100644 --- a/homeassistant/components/schlage/config_flow.py +++ b/homeassistant/components/schlage/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Schlage integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/schlage/coordinator.py b/homeassistant/components/schlage/coordinator.py index 3d736306d91..959d1e215f8 100644 --- a/homeassistant/components/schlage/coordinator.py +++ b/homeassistant/components/schlage/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Schlage integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/schlage/lock.py b/homeassistant/components/schlage/lock.py index 0b5e35492de..7e6f60211b0 100644 --- a/homeassistant/components/schlage/lock.py +++ b/homeassistant/components/schlage/lock.py @@ -1,4 +1,5 @@ """Platform for Schlage lock integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/schluter/climate.py b/homeassistant/components/schluter/climate.py index 5d747c8f345..74e2d9a0194 100644 --- a/homeassistant/components/schluter/climate.py +++ b/homeassistant/components/schluter/climate.py @@ -1,4 +1,5 @@ """Support for Schluter thermostats.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/scrape/__init__.py b/homeassistant/components/scrape/__init__.py index e96260139da..3906f5cf306 100644 --- a/homeassistant/components/scrape/__init__.py +++ b/homeassistant/components/scrape/__init__.py @@ -1,4 +1,5 @@ """The scrape component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/scrape/config_flow.py b/homeassistant/components/scrape/config_flow.py index b4305b3948e..fc8124010ce 100644 --- a/homeassistant/components/scrape/config_flow.py +++ b/homeassistant/components/scrape/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Scrape integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/scrape/const.py b/homeassistant/components/scrape/const.py index cd64199fa23..292f0d0b247 100644 --- a/homeassistant/components/scrape/const.py +++ b/homeassistant/components/scrape/const.py @@ -1,4 +1,5 @@ """Constants for Scrape integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/scrape/coordinator.py b/homeassistant/components/scrape/coordinator.py index 9fc66db3481..74fd510ac94 100644 --- a/homeassistant/components/scrape/coordinator.py +++ b/homeassistant/components/scrape/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for the scrape component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index bb8c233983d..58d90b2399e 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -1,4 +1,5 @@ """Support for getting data from websites with scraping.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py index 096c2c22918..ac3fce80102 100644 --- a/homeassistant/components/screenlogic/binary_sensor.py +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -1,4 +1,5 @@ """Support for a ScreenLogic Binary Sensor.""" + from copy import copy import dataclasses import logging diff --git a/homeassistant/components/screenlogic/climate.py b/homeassistant/components/screenlogic/climate.py index 6d95f06a49c..b3b83a66935 100644 --- a/homeassistant/components/screenlogic/climate.py +++ b/homeassistant/components/screenlogic/climate.py @@ -1,4 +1,5 @@ """Support for a ScreenLogic heating device.""" + from dataclasses import dataclass import logging from typing import Any diff --git a/homeassistant/components/screenlogic/config_flow.py b/homeassistant/components/screenlogic/config_flow.py index 424bddb5c8c..74a01fdeaa2 100644 --- a/homeassistant/components/screenlogic/config_flow.py +++ b/homeassistant/components/screenlogic/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ScreenLogic.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/screenlogic/const.py b/homeassistant/components/screenlogic/const.py index 104736f300b..31e8468240f 100644 --- a/homeassistant/components/screenlogic/const.py +++ b/homeassistant/components/screenlogic/const.py @@ -1,4 +1,5 @@ """Constants for the ScreenLogic integration.""" + from screenlogicpy.const.common import UNIT from screenlogicpy.device_const.circuit import FUNCTION from screenlogicpy.device_const.system import COLOR_MODE diff --git a/homeassistant/components/screenlogic/coordinator.py b/homeassistant/components/screenlogic/coordinator.py index f16f2b9ff34..281bac86e01 100644 --- a/homeassistant/components/screenlogic/coordinator.py +++ b/homeassistant/components/screenlogic/coordinator.py @@ -1,4 +1,5 @@ """ScreenlogicDataUpdateCoordinator definition.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/screenlogic/data.py b/homeassistant/components/screenlogic/data.py index cda1bc83f81..2df09ab142b 100644 --- a/homeassistant/components/screenlogic/data.py +++ b/homeassistant/components/screenlogic/data.py @@ -1,4 +1,5 @@ """Support for configurable supported data values for the ScreenLogic integration.""" + from screenlogicpy.const.data import DEVICE, VALUE ENTITY_MIGRATIONS = { diff --git a/homeassistant/components/screenlogic/entity.py b/homeassistant/components/screenlogic/entity.py index fc2c855d682..6d898ec825d 100644 --- a/homeassistant/components/screenlogic/entity.py +++ b/homeassistant/components/screenlogic/entity.py @@ -1,4 +1,5 @@ """Base ScreenLogicEntity definitions.""" + from collections.abc import Callable from dataclasses import dataclass from datetime import datetime diff --git a/homeassistant/components/screenlogic/light.py b/homeassistant/components/screenlogic/light.py index 60cf7d52a48..19f27a73115 100644 --- a/homeassistant/components/screenlogic/light.py +++ b/homeassistant/components/screenlogic/light.py @@ -1,4 +1,5 @@ """Support for a ScreenLogic light 'circuit' switch.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/screenlogic/number.py b/homeassistant/components/screenlogic/number.py index 1ff611b2c9f..d3e433b60e0 100644 --- a/homeassistant/components/screenlogic/number.py +++ b/homeassistant/components/screenlogic/number.py @@ -1,4 +1,5 @@ """Support for a ScreenLogic number entity.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index c73ce8be42c..9fe201ba6bb 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -1,4 +1,5 @@ """Support for a ScreenLogic Sensor.""" + from collections.abc import Callable from copy import copy import dataclasses diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py index 43f749db913..f97106fa7bc 100644 --- a/homeassistant/components/screenlogic/switch.py +++ b/homeassistant/components/screenlogic/switch.py @@ -1,4 +1,5 @@ """Support for a ScreenLogic 'circuit' switch.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index f1a86687255..12abf51a925 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -1,4 +1,5 @@ """Support for scripts.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/script/config.py b/homeassistant/components/script/config.py index 1cbab23d843..fc1b49b1823 100644 --- a/homeassistant/components/script/config.py +++ b/homeassistant/components/script/config.py @@ -1,4 +1,5 @@ """Config validation helper for the script integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/script/helpers.py b/homeassistant/components/script/helpers.py index 4504869e270..b070a4d60ce 100644 --- a/homeassistant/components/script/helpers.py +++ b/homeassistant/components/script/helpers.py @@ -1,4 +1,5 @@ """Helpers for automation integration.""" + from homeassistant.components.blueprint import DomainBlueprints from homeassistant.const import SERVICE_RELOAD from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/script/logbook.py b/homeassistant/components/script/logbook.py index ce23f083ee0..ec7d62d7949 100644 --- a/homeassistant/components/script/logbook.py +++ b/homeassistant/components/script/logbook.py @@ -1,4 +1,5 @@ """Describe logbook events.""" + from homeassistant.components.logbook import ( LOGBOOK_ENTRY_CONTEXT_ID, LOGBOOK_ENTRY_ENTITY_ID, diff --git a/homeassistant/components/script/trace.py b/homeassistant/components/script/trace.py index c63d50b1041..a50cda752d0 100644 --- a/homeassistant/components/script/trace.py +++ b/homeassistant/components/script/trace.py @@ -1,4 +1,5 @@ """Trace support for script.""" + from __future__ import annotations from collections.abc import Iterator diff --git a/homeassistant/components/scsgate/cover.py b/homeassistant/components/scsgate/cover.py index f68b089e2d7..8f17ca170a0 100644 --- a/homeassistant/components/scsgate/cover.py +++ b/homeassistant/components/scsgate/cover.py @@ -1,4 +1,5 @@ """Support for SCSGate covers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/scsgate/light.py b/homeassistant/components/scsgate/light.py index df63bad49d4..a4bb78fcd1c 100644 --- a/homeassistant/components/scsgate/light.py +++ b/homeassistant/components/scsgate/light.py @@ -1,4 +1,5 @@ """Support for SCSGate lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/scsgate/switch.py b/homeassistant/components/scsgate/switch.py index 3f215130048..8ad31106cf7 100644 --- a/homeassistant/components/scsgate/switch.py +++ b/homeassistant/components/scsgate/switch.py @@ -1,4 +1,5 @@ """Support for SCSGate switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/search/__init__.py b/homeassistant/components/search/__init__.py index 7dd7d952e95..1eafc137580 100644 --- a/homeassistant/components/search/__init__.py +++ b/homeassistant/components/search/__init__.py @@ -1,4 +1,5 @@ """The Search integration.""" + from __future__ import annotations from collections import defaultdict, deque diff --git a/homeassistant/components/season/__init__.py b/homeassistant/components/season/__init__.py index f67abee3bea..42af9c4459b 100644 --- a/homeassistant/components/season/__init__.py +++ b/homeassistant/components/season/__init__.py @@ -1,4 +1,5 @@ """The Season integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/season/config_flow.py b/homeassistant/components/season/config_flow.py index 6c461404781..77c408f4e3f 100644 --- a/homeassistant/components/season/config_flow.py +++ b/homeassistant/components/season/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Season integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/season/const.py b/homeassistant/components/season/const.py index c27d4f5c40e..d3b1827ac9a 100644 --- a/homeassistant/components/season/const.py +++ b/homeassistant/components/season/const.py @@ -1,4 +1,5 @@ """Constants for the Season integration.""" + from typing import Final from homeassistant.const import Platform diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py index aa0f6c80b19..96744db1d02 100644 --- a/homeassistant/components/season/sensor.py +++ b/homeassistant/components/season/sensor.py @@ -1,4 +1,5 @@ """Support for Season sensors.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/select/__init__.py b/homeassistant/components/select/__init__.py index 8ec08f4606f..00963f4d069 100644 --- a/homeassistant/components/select/__init__.py +++ b/homeassistant/components/select/__init__.py @@ -1,4 +1,5 @@ """Component to allow selecting an option from a list as platforms.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/select/device_action.py b/homeassistant/components/select/device_action.py index a7d47d8c833..a3827a23d41 100644 --- a/homeassistant/components/select/device_action.py +++ b/homeassistant/components/select/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Select.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/select/device_condition.py b/homeassistant/components/select/device_condition.py index 712e7bf78b6..cd99009dd90 100644 --- a/homeassistant/components/select/device_condition.py +++ b/homeassistant/components/select/device_condition.py @@ -1,4 +1,5 @@ """Provide the device conditions for Select.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/select/device_trigger.py b/homeassistant/components/select/device_trigger.py index 2cd2da0e1a6..b09a25ba082 100644 --- a/homeassistant/components/select/device_trigger.py +++ b/homeassistant/components/select/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for Select.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/select/reproduce_state.py b/homeassistant/components/select/reproduce_state.py index 0b68ae12fdc..88ccda6f07d 100644 --- a/homeassistant/components/select/reproduce_state.py +++ b/homeassistant/components/select/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce a Select entity state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/select/significant_change.py b/homeassistant/components/select/significant_change.py index 835db314a38..c9cd6b735d6 100644 --- a/homeassistant/components/select/significant_change.py +++ b/homeassistant/components/select/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Select state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sendgrid/notify.py b/homeassistant/components/sendgrid/notify.py index 25d00fdd3b8..01ceccf781a 100644 --- a/homeassistant/components/sendgrid/notify.py +++ b/homeassistant/components/sendgrid/notify.py @@ -1,4 +1,5 @@ """SendGrid notification service.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 392dd33a031..9d909730f5a 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -1,4 +1,5 @@ """Support for monitoring a Sense energy sensor.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/sense/config_flow.py b/homeassistant/components/sense/config_flow.py index ddf28fb1cef..e5880675d2b 100644 --- a/homeassistant/components/sense/config_flow.py +++ b/homeassistant/components/sense/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Sense integration.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/sensibo/__init__.py b/homeassistant/components/sensibo/__init__.py index 9a278d0c4df..b14d06c5811 100644 --- a/homeassistant/components/sensibo/__init__.py +++ b/homeassistant/components/sensibo/__init__.py @@ -1,4 +1,5 @@ """The Sensibo component.""" + from __future__ import annotations from pysensibo.exceptions import AuthenticationError diff --git a/homeassistant/components/sensibo/binary_sensor.py b/homeassistant/components/sensibo/binary_sensor.py index c619bf8a9be..fa0ea9231f9 100644 --- a/homeassistant/components/sensibo/binary_sensor.py +++ b/homeassistant/components/sensibo/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor platform for Sensibo integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sensibo/button.py b/homeassistant/components/sensibo/button.py index 5f7c7a88f41..f6d540f54d1 100644 --- a/homeassistant/components/sensibo/button.py +++ b/homeassistant/components/sensibo/button.py @@ -1,4 +1,5 @@ """Button platform for Sensibo integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 0ad2a0a714f..f2a2d3f1827 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -1,4 +1,5 @@ """Support for Sensibo wifi-enabled home thermostats.""" + from __future__ import annotations from bisect import bisect_left diff --git a/homeassistant/components/sensibo/config_flow.py b/homeassistant/components/sensibo/config_flow.py index 75d41b80d8a..667f96fe1c2 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Sensibo integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/sensibo/coordinator.py b/homeassistant/components/sensibo/coordinator.py index 1cdcbd79932..4f4f76aba10 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Sensibo integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/sensibo/diagnostics.py b/homeassistant/components/sensibo/diagnostics.py index 32ad07871a3..d00da7e1223 100644 --- a/homeassistant/components/sensibo/diagnostics.py +++ b/homeassistant/components/sensibo/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Sensibo.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py index 5a755a7730c..4c22ef8366a 100644 --- a/homeassistant/components/sensibo/entity.py +++ b/homeassistant/components/sensibo/entity.py @@ -1,4 +1,5 @@ """Base entity for Sensibo integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index ac76277fb20..35500664041 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -1,4 +1,5 @@ """Number platform for Sensibo integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index 9e6179dc1c4..97fce5ae832 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -1,4 +1,5 @@ """Select platform for Sensibo integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index ddbc0561d2f..b58be0e5bdf 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Sensibo integration.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/sensibo/switch.py b/homeassistant/components/sensibo/switch.py index be56759fb95..7edb2d3f72e 100644 --- a/homeassistant/components/sensibo/switch.py +++ b/homeassistant/components/sensibo/switch.py @@ -1,4 +1,5 @@ """Switch platform for Sensibo integration.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/sensibo/update.py b/homeassistant/components/sensibo/update.py index b2dc31aa068..13c89e8c9bf 100644 --- a/homeassistant/components/sensibo/update.py +++ b/homeassistant/components/sensibo/update.py @@ -1,4 +1,5 @@ """Update platform for Sensibo integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sensibo/util.py b/homeassistant/components/sensibo/util.py index 98b843a9dfc..3c750b2f017 100644 --- a/homeassistant/components/sensibo/util.py +++ b/homeassistant/components/sensibo/util.py @@ -1,4 +1,5 @@ """Utils for Sensibo integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sensirion_ble/__init__.py b/homeassistant/components/sensirion_ble/__init__.py index 66e6f7c250b..762086d102d 100644 --- a/homeassistant/components/sensirion_ble/__init__.py +++ b/homeassistant/components/sensirion_ble/__init__.py @@ -1,4 +1,5 @@ """The sensirion_ble integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sensirion_ble/config_flow.py b/homeassistant/components/sensirion_ble/config_flow.py index dabe4e323ed..066a4a69f9a 100644 --- a/homeassistant/components/sensirion_ble/config_flow.py +++ b/homeassistant/components/sensirion_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for sensirion_ble.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sensirion_ble/sensor.py b/homeassistant/components/sensirion_ble/sensor.py index 3d288f92d12..2ca5a524c8f 100644 --- a/homeassistant/components/sensirion_ble/sensor.py +++ b/homeassistant/components/sensirion_ble/sensor.py @@ -1,4 +1,5 @@ """Support for Sensirion sensors.""" + from __future__ import annotations from sensor_state_data import ( diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 9f525c3d498..e56037e3cee 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -1,4 +1,5 @@ """Component to interface with various sensors that can be monitored.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index b5aab53e684..cc89908f00d 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -1,4 +1,5 @@ """Constants for sensor.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/sensor/device_condition.py b/homeassistant/components/sensor/device_condition.py index b7cf533d3da..fb605d9419c 100644 --- a/homeassistant/components/sensor/device_condition.py +++ b/homeassistant/components/sensor/device_condition.py @@ -1,4 +1,5 @@ """Provides device conditions for sensors.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/sensor/helpers.py b/homeassistant/components/sensor/helpers.py index a3f5e3827bf..12a5dcefdf8 100644 --- a/homeassistant/components/sensor/helpers.py +++ b/homeassistant/components/sensor/helpers.py @@ -1,4 +1,5 @@ """Helpers for sensor entities.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/sensor/recorder.py b/homeassistant/components/sensor/recorder.py index a53ae906718..97ad49fb937 100644 --- a/homeassistant/components/sensor/recorder.py +++ b/homeassistant/components/sensor/recorder.py @@ -1,4 +1,5 @@ """Statistics helper for sensor.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/sensor/significant_change.py b/homeassistant/components/sensor/significant_change.py index f426674c32d..f320a7efcdf 100644 --- a/homeassistant/components/sensor/significant_change.py +++ b/homeassistant/components/sensor/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant sensor state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sensor/websocket_api.py b/homeassistant/components/sensor/websocket_api.py index a98c4b25392..2110ccc7253 100644 --- a/homeassistant/components/sensor/websocket_api.py +++ b/homeassistant/components/sensor/websocket_api.py @@ -1,4 +1,5 @@ """The sensor websocket API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sensorpro/__init__.py b/homeassistant/components/sensorpro/__init__.py index 43c87ad32ee..a4b5ae351a1 100644 --- a/homeassistant/components/sensorpro/__init__.py +++ b/homeassistant/components/sensorpro/__init__.py @@ -1,4 +1,5 @@ """The SensorPro integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sensorpro/config_flow.py b/homeassistant/components/sensorpro/config_flow.py index c5899291f7c..ce26d70a659 100644 --- a/homeassistant/components/sensorpro/config_flow.py +++ b/homeassistant/components/sensorpro/config_flow.py @@ -1,4 +1,5 @@ """Config flow for sensorpro ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sensorpro/device.py b/homeassistant/components/sensorpro/device.py index 326eb8b8bbd..38b94a19452 100644 --- a/homeassistant/components/sensorpro/device.py +++ b/homeassistant/components/sensorpro/device.py @@ -1,4 +1,5 @@ """Support for SensorPro devices.""" + from __future__ import annotations from sensorpro_ble import DeviceKey diff --git a/homeassistant/components/sensorpro/sensor.py b/homeassistant/components/sensorpro/sensor.py index 9ac4b10b99e..536a3c6b775 100644 --- a/homeassistant/components/sensorpro/sensor.py +++ b/homeassistant/components/sensorpro/sensor.py @@ -1,4 +1,5 @@ """Support for SensorPro sensors.""" + from __future__ import annotations from sensorpro_ble import ( diff --git a/homeassistant/components/sensorpush/__init__.py b/homeassistant/components/sensorpush/__init__.py index 7828a581d07..177b51d135b 100644 --- a/homeassistant/components/sensorpush/__init__.py +++ b/homeassistant/components/sensorpush/__init__.py @@ -1,4 +1,5 @@ """The SensorPush Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sensorpush/config_flow.py b/homeassistant/components/sensorpush/config_flow.py index 5fd29776c32..d826029276b 100644 --- a/homeassistant/components/sensorpush/config_flow.py +++ b/homeassistant/components/sensorpush/config_flow.py @@ -1,4 +1,5 @@ """Config flow for sensorpush integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sensorpush/sensor.py b/homeassistant/components/sensorpush/sensor.py index e12bf0e48c6..20d97a32415 100644 --- a/homeassistant/components/sensorpush/sensor.py +++ b/homeassistant/components/sensorpush/sensor.py @@ -1,4 +1,5 @@ """Support for sensorpush ble sensors.""" + from __future__ import annotations from sensorpush_ble import DeviceClass, DeviceKey, SensorUpdate, Units diff --git a/homeassistant/components/sentry/__init__.py b/homeassistant/components/sentry/__init__.py index 5e4fb80688d..dcbcc59a749 100644 --- a/homeassistant/components/sentry/__init__.py +++ b/homeassistant/components/sentry/__init__.py @@ -1,4 +1,5 @@ """The sentry integration.""" + from __future__ import annotations import re diff --git a/homeassistant/components/sentry/config_flow.py b/homeassistant/components/sentry/config_flow.py index 10d17030d9f..b10409caf38 100644 --- a/homeassistant/components/sentry/config_flow.py +++ b/homeassistant/components/sentry/config_flow.py @@ -1,4 +1,5 @@ """Config flow for sentry integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/senz/__init__.py b/homeassistant/components/senz/__init__.py index 559760ec52d..d40b485bf89 100644 --- a/homeassistant/components/senz/__init__.py +++ b/homeassistant/components/senz/__init__.py @@ -1,4 +1,5 @@ """The nVent RAYCHEM SENZ integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/senz/api.py b/homeassistant/components/senz/api.py index 1f0ccee3c7c..fa139ac9c64 100644 --- a/homeassistant/components/senz/api.py +++ b/homeassistant/components/senz/api.py @@ -1,4 +1,5 @@ """API for nVent RAYCHEM SENZ bound to Home Assistant OAuth.""" + from typing import cast from aiosenz import AbstractSENZAuth diff --git a/homeassistant/components/senz/climate.py b/homeassistant/components/senz/climate.py index c921e1ac1da..3b834654ca6 100644 --- a/homeassistant/components/senz/climate.py +++ b/homeassistant/components/senz/climate.py @@ -1,4 +1,5 @@ """nVent RAYCHEM SENZ climate platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py index 63253375cc7..7f40279df85 100644 --- a/homeassistant/components/serial/sensor.py +++ b/homeassistant/components/serial/sensor.py @@ -1,4 +1,5 @@ """Support for reading data from a serial port.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/serial_pm/sensor.py b/homeassistant/components/serial_pm/sensor.py index 2c94b9bcf01..00ac4fe8731 100644 --- a/homeassistant/components/serial_pm/sensor.py +++ b/homeassistant/components/serial_pm/sensor.py @@ -1,4 +1,5 @@ """Support for particulate matter sensors connected to a serial port.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index c539e7507eb..050a5978acc 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -1,4 +1,5 @@ """Support for Sesame, by CANDY HOUSE.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/seven_segments/image_processing.py b/homeassistant/components/seven_segments/image_processing.py index 58d532f58f8..deb25da5b09 100644 --- a/homeassistant/components/seven_segments/image_processing.py +++ b/homeassistant/components/seven_segments/image_processing.py @@ -1,4 +1,5 @@ """Optical character recognition processing of seven segments displays.""" + from __future__ import annotations import io diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index dd61e1627b4..7f9bd5f2505 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -1,4 +1,5 @@ """Support for package tracking sensors from 17track.net.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/sfr_box/__init__.py b/homeassistant/components/sfr_box/__init__.py index 564f1970b64..7e8ea102576 100644 --- a/homeassistant/components/sfr_box/__init__.py +++ b/homeassistant/components/sfr_box/__init__.py @@ -1,4 +1,5 @@ """SFR Box.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sfr_box/binary_sensor.py b/homeassistant/components/sfr_box/binary_sensor.py index 9bf053a3897..ca81129f699 100644 --- a/homeassistant/components/sfr_box/binary_sensor.py +++ b/homeassistant/components/sfr_box/binary_sensor.py @@ -1,4 +1,5 @@ """SFR Box sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sfr_box/button.py b/homeassistant/components/sfr_box/button.py index 56c5335e908..5f26bc82f1b 100644 --- a/homeassistant/components/sfr_box/button.py +++ b/homeassistant/components/sfr_box/button.py @@ -1,4 +1,5 @@ """SFR Box button platform.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/sfr_box/config_flow.py b/homeassistant/components/sfr_box/config_flow.py index aa8e48f9422..f7d72c01ccd 100644 --- a/homeassistant/components/sfr_box/config_flow.py +++ b/homeassistant/components/sfr_box/config_flow.py @@ -1,4 +1,5 @@ """SFR Box config flow.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/sfr_box/const.py b/homeassistant/components/sfr_box/const.py index 3700890b957..acc4e8e4941 100644 --- a/homeassistant/components/sfr_box/const.py +++ b/homeassistant/components/sfr_box/const.py @@ -1,4 +1,5 @@ """SFR Box constants.""" + from homeassistant.const import Platform DEFAULT_HOST = "192.168.0.1" diff --git a/homeassistant/components/sfr_box/coordinator.py b/homeassistant/components/sfr_box/coordinator.py index 739fc2a770b..29ab1d7a2c6 100644 --- a/homeassistant/components/sfr_box/coordinator.py +++ b/homeassistant/components/sfr_box/coordinator.py @@ -1,4 +1,5 @@ """SFR Box coordinator.""" + from collections.abc import Callable, Coroutine from datetime import timedelta import logging diff --git a/homeassistant/components/sfr_box/diagnostics.py b/homeassistant/components/sfr_box/diagnostics.py index e0e84a7ec1a..c0c964cd153 100644 --- a/homeassistant/components/sfr_box/diagnostics.py +++ b/homeassistant/components/sfr_box/diagnostics.py @@ -1,4 +1,5 @@ """SFR Box diagnostics platform.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/sfr_box/models.py b/homeassistant/components/sfr_box/models.py index ff723c2c6ef..aa776a6bf60 100644 --- a/homeassistant/components/sfr_box/models.py +++ b/homeassistant/components/sfr_box/models.py @@ -1,4 +1,5 @@ """SFR Box models.""" + from dataclasses import dataclass from sfrbox_api.bridge import SFRBox diff --git a/homeassistant/components/sfr_box/sensor.py b/homeassistant/components/sfr_box/sensor.py index 6f77ca8d285..626b4181486 100644 --- a/homeassistant/components/sfr_box/sensor.py +++ b/homeassistant/components/sfr_box/sensor.py @@ -1,4 +1,5 @@ """SFR Box sensor platform.""" + from collections.abc import Callable from dataclasses import dataclass from typing import Generic, TypeVar diff --git a/homeassistant/components/sharkiq/config_flow.py b/homeassistant/components/sharkiq/config_flow.py index 8bbad1f7c68..492b8f2a365 100644 --- a/homeassistant/components/sharkiq/config_flow.py +++ b/homeassistant/components/sharkiq/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Shark IQ integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sharkiq/const.py b/homeassistant/components/sharkiq/const.py index b12a86dc240..8d5d4708e0e 100644 --- a/homeassistant/components/sharkiq/const.py +++ b/homeassistant/components/sharkiq/const.py @@ -1,4 +1,5 @@ """Shark IQ Constants.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/sharkiq/update_coordinator.py b/homeassistant/components/sharkiq/update_coordinator.py index c378797f56e..01550024e9e 100644 --- a/homeassistant/components/sharkiq/update_coordinator.py +++ b/homeassistant/components/sharkiq/update_coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for shark iq vacuums.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index 9510b7d3f66..f24a21b7f8e 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -1,4 +1,5 @@ """Shark IQ Wrapper.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index 5aa8dadee19..98b08d975ff 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -1,4 +1,5 @@ """Expose regular shell commands as services.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 4895e2a1a2b..29550e841d7 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -1,4 +1,5 @@ """The Shelly integration.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 41029e2de24..bce84a829ed 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor for Shelly.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/shelly/bluetooth/__init__.py b/homeassistant/components/shelly/bluetooth/__init__.py index 5432ceb3a12..fad7ddf4424 100644 --- a/homeassistant/components/shelly/bluetooth/__init__.py +++ b/homeassistant/components/shelly/bluetooth/__init__.py @@ -1,4 +1,5 @@ """Bluetooth support for shelly.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/shelly/button.py b/homeassistant/components/shelly/button.py index f4294dee9ee..1c497231b3e 100644 --- a/homeassistant/components/shelly/button.py +++ b/homeassistant/components/shelly/button.py @@ -1,4 +1,5 @@ """Button for Shelly.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index 3ceb38c84c3..e155edf4f4c 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -1,4 +1,5 @@ """Climate support for Shelly.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 21d8c58f0fe..ca56552d125 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Shelly integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 827a6c00a30..ebe2dc4bc28 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -1,4 +1,5 @@ """Constants for the Shelly integration.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 4afe66199f0..6d2170bf941 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -1,4 +1,5 @@ """Coordinators for the Shelly integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/shelly/cover.py b/homeassistant/components/shelly/cover.py index caff64d7707..2327c5b4779 100644 --- a/homeassistant/components/shelly/cover.py +++ b/homeassistant/components/shelly/cover.py @@ -1,4 +1,5 @@ """Cover for Shelly.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index 1f41483efc0..9aa57fa1d15 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for Shelly.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/shelly/diagnostics.py b/homeassistant/components/shelly/diagnostics.py index 0a1fa0e21fc..473bef21835 100644 --- a/homeassistant/components/shelly/diagnostics.py +++ b/homeassistant/components/shelly/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Shelly.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 513e2c88998..6cb4c7f6542 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -1,4 +1,5 @@ """Shelly entity helper.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/shelly/event.py b/homeassistant/components/shelly/event.py index 5425f71366f..ea7cac2d253 100644 --- a/homeassistant/components/shelly/event.py +++ b/homeassistant/components/shelly/event.py @@ -1,4 +1,5 @@ """Event for Shelly.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 234f376e85f..7c465e43db0 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -1,4 +1,5 @@ """Light for Shelly.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/shelly/logbook.py b/homeassistant/components/shelly/logbook.py index d55ffe0fd28..fbf72e6ebe8 100644 --- a/homeassistant/components/shelly/logbook.py +++ b/homeassistant/components/shelly/logbook.py @@ -1,4 +1,5 @@ """Describe Shelly logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index ef3963c53c3..3dd2550b6ff 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -1,4 +1,5 @@ """Number for Shelly.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index b88b6886b84..3dc9102df70 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -1,4 +1,5 @@ """Sensor for Shelly.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index a45fd9295f2..b690cfab6c8 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -1,4 +1,5 @@ """Switch for Shelly.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index 9e8b1505afe..765e4ee441b 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -1,4 +1,5 @@ """Update entities for Shelly devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 9389f4e1507..4081fe86dd0 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -1,4 +1,5 @@ """Shelly helpers functions.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/shelly/valve.py b/homeassistant/components/shelly/valve.py index 7bc4a9a5329..a17738e3575 100644 --- a/homeassistant/components/shelly/valve.py +++ b/homeassistant/components/shelly/valve.py @@ -1,4 +1,5 @@ """Valve for Shelly.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/shodan/sensor.py b/homeassistant/components/shodan/sensor.py index bdef681fdd2..fd608cbcb45 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -1,4 +1,5 @@ """Sensor for displaying the number of result on Shodan.io.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/shopping_list/__init__.py b/homeassistant/components/shopping_list/__init__.py index a250adcf5f2..1176192bdcd 100644 --- a/homeassistant/components/shopping_list/__init__.py +++ b/homeassistant/components/shopping_list/__init__.py @@ -1,4 +1,5 @@ """Support to manage a shopping list.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/shopping_list/config_flow.py b/homeassistant/components/shopping_list/config_flow.py index 24d4ca92ebc..ffc8a3be21a 100644 --- a/homeassistant/components/shopping_list/config_flow.py +++ b/homeassistant/components/shopping_list/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the shopping list integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/shopping_list/intent.py b/homeassistant/components/shopping_list/intent.py index 180007c2dfb..70a70467cbd 100644 --- a/homeassistant/components/shopping_list/intent.py +++ b/homeassistant/components/shopping_list/intent.py @@ -1,4 +1,5 @@ """Intents for the Shopping List integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/sia/__init__.py b/homeassistant/components/sia/__init__.py index a59d1f1cdad..d1bc3fa9968 100644 --- a/homeassistant/components/sia/__init__.py +++ b/homeassistant/components/sia/__init__.py @@ -1,4 +1,5 @@ """The sia integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PORT from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/sia/alarm_control_panel.py b/homeassistant/components/sia/alarm_control_panel.py index e7850a5f9d2..8c995da542a 100644 --- a/homeassistant/components/sia/alarm_control_panel.py +++ b/homeassistant/components/sia/alarm_control_panel.py @@ -1,4 +1,5 @@ """Module for SIA Alarm Control Panels.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/sia/binary_sensor.py b/homeassistant/components/sia/binary_sensor.py index f6e2533be93..307b5073e90 100644 --- a/homeassistant/components/sia/binary_sensor.py +++ b/homeassistant/components/sia/binary_sensor.py @@ -1,4 +1,5 @@ """Module for SIA Binary Sensors.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/sia/config_flow.py b/homeassistant/components/sia/config_flow.py index 8ebe89c24ea..f7a7a1f06e4 100644 --- a/homeassistant/components/sia/config_flow.py +++ b/homeassistant/components/sia/config_flow.py @@ -1,4 +1,5 @@ """Config flow for sia integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/sia/const.py b/homeassistant/components/sia/const.py index 82783611e07..20a0afa9edf 100644 --- a/homeassistant/components/sia/const.py +++ b/homeassistant/components/sia/const.py @@ -1,4 +1,5 @@ """Constants for the sia integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/sia/hub.py b/homeassistant/components/sia/hub.py index 9ba7a19a9be..591e4aadad7 100644 --- a/homeassistant/components/sia/hub.py +++ b/homeassistant/components/sia/hub.py @@ -1,4 +1,5 @@ """The sia hub.""" + from __future__ import annotations from copy import deepcopy diff --git a/homeassistant/components/sia/sia_entity_base.py b/homeassistant/components/sia/sia_entity_base.py index f6895cc48a9..aecac2b540b 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -1,4 +1,5 @@ """Module for SIA Base Entity.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/sia/utils.py b/homeassistant/components/sia/utils.py index e9db69041d6..2fab4bf39ce 100644 --- a/homeassistant/components/sia/utils.py +++ b/homeassistant/components/sia/utils.py @@ -1,4 +1,5 @@ """Helper functions for the SIA integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 1a1d7bb74b0..87acc78e748 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -1,4 +1,5 @@ """Sensor for SigFox devices.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/sighthound/image_processing.py b/homeassistant/components/sighthound/image_processing.py index 69776f4e2ac..bcfa4bca3c2 100644 --- a/homeassistant/components/sighthound/image_processing.py +++ b/homeassistant/components/sighthound/image_processing.py @@ -1,4 +1,5 @@ """Person detection using Sighthound cloud service.""" + from __future__ import annotations import io diff --git a/homeassistant/components/signal_messenger/notify.py b/homeassistant/components/signal_messenger/notify.py index 25dae1617c3..3659864efd7 100644 --- a/homeassistant/components/signal_messenger/notify.py +++ b/homeassistant/components/signal_messenger/notify.py @@ -1,4 +1,5 @@ """Signal Messenger for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/simplepush/config_flow.py b/homeassistant/components/simplepush/config_flow.py index 2b039a33951..4e954e89938 100644 --- a/homeassistant/components/simplepush/config_flow.py +++ b/homeassistant/components/simplepush/config_flow.py @@ -1,4 +1,5 @@ """Config flow for simplepush integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/simplepush/notify.py b/homeassistant/components/simplepush/notify.py index cc6c61ced03..e21a62a6a12 100644 --- a/homeassistant/components/simplepush/notify.py +++ b/homeassistant/components/simplepush/notify.py @@ -1,4 +1,5 @@ """Simplepush notification service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 1e558356ea3..cdeb6910aa5 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -1,4 +1,5 @@ """Support for SimpliSafe alarm systems.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 71f250b0e02..731400e67d5 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for SimpliSafe alarm control panels.""" + from __future__ import annotations from simplipy.errors import SimplipyError diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index d9384b948ed..b3aa1e3281d 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -1,4 +1,5 @@ """Support for SimpliSafe binary sensors.""" + from __future__ import annotations from simplipy.device import DeviceTypes, DeviceV3 diff --git a/homeassistant/components/simplisafe/button.py b/homeassistant/components/simplisafe/button.py index 220ca89d170..40bf857da2a 100644 --- a/homeassistant/components/simplisafe/button.py +++ b/homeassistant/components/simplisafe/button.py @@ -1,4 +1,5 @@ """Buttons for the SimpliSafe integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index b093e0d216f..c0d98c5644f 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the SimpliSafe component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/simplisafe/diagnostics.py b/homeassistant/components/simplisafe/diagnostics.py index cb983f74202..e63e1551740 100644 --- a/homeassistant/components/simplisafe/diagnostics.py +++ b/homeassistant/components/simplisafe/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for SimpliSafe.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 9ce59eb3b56..b6bb07fd100 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -1,4 +1,5 @@ """Support for SimpliSafe locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index 949d8890398..bd04720f9ba 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -1,4 +1,5 @@ """Support for SimpliSafe freeze sensor.""" + from __future__ import annotations from simplipy.device import DeviceTypes diff --git a/homeassistant/components/simplisafe/typing.py b/homeassistant/components/simplisafe/typing.py index d49d356036a..5651a3072b9 100644 --- a/homeassistant/components/simplisafe/typing.py +++ b/homeassistant/components/simplisafe/typing.py @@ -1,4 +1,5 @@ """Define typing helpers for SimpliSafe.""" + from simplipy.system.v2 import SystemV2 from simplipy.system.v3 import SystemV3 diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index 0f9db48e78c..51ec19ac80b 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -1,4 +1,5 @@ """Adds a simulated sensor.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/sinch/notify.py b/homeassistant/components/sinch/notify.py index cd37f8bf627..77443dd1a84 100644 --- a/homeassistant/components/sinch/notify.py +++ b/homeassistant/components/sinch/notify.py @@ -1,4 +1,5 @@ """Support for Sinch notifications.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/siren/__init__.py b/homeassistant/components/siren/__init__.py index fb41d5f7b48..a083aa9d702 100644 --- a/homeassistant/components/siren/__init__.py +++ b/homeassistant/components/siren/__init__.py @@ -1,4 +1,5 @@ """Component to interface with various sirens/chimes.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py index d0cd7597e58..aad18461b6e 100644 --- a/homeassistant/components/sisyphus/light.py +++ b/homeassistant/components/sisyphus/light.py @@ -1,4 +1,5 @@ """Support for the light on the Sisyphus Kinetic Art Table.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index e13924a51e9..8fb1aa50223 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -1,4 +1,5 @@ """Support for track controls on the Sisyphus Kinetic Art Table.""" + from __future__ import annotations import aiohttp diff --git a/homeassistant/components/sky_hub/device_tracker.py b/homeassistant/components/sky_hub/device_tracker.py index 8741b2ed560..52c56993be0 100644 --- a/homeassistant/components/sky_hub/device_tracker.py +++ b/homeassistant/components/sky_hub/device_tracker.py @@ -1,4 +1,5 @@ """Support for Sky Hub.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/skybeacon/sensor.py b/homeassistant/components/skybeacon/sensor.py index 17bf8a3ab7f..94a3e270cb3 100644 --- a/homeassistant/components/skybeacon/sensor.py +++ b/homeassistant/components/skybeacon/sensor.py @@ -1,4 +1,5 @@ """Support for Skybeacon temperature/humidity Bluetooth LE sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py index ac948408a3f..0282ad40254 100644 --- a/homeassistant/components/skybell/__init__.py +++ b/homeassistant/components/skybell/__init__.py @@ -1,4 +1,5 @@ """Support for the Skybell HD Doorbell.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index fa55b352f61..3c2d90b2630 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor support for the Skybell HD Doorbell.""" + from __future__ import annotations from aioskybell.helpers import const as CONST diff --git a/homeassistant/components/skybell/camera.py b/homeassistant/components/skybell/camera.py index 1e510687a02..683b840debe 100644 --- a/homeassistant/components/skybell/camera.py +++ b/homeassistant/components/skybell/camera.py @@ -1,4 +1,5 @@ """Camera support for the Skybell HD Doorbell.""" + from __future__ import annotations from aiohttp import web diff --git a/homeassistant/components/skybell/config_flow.py b/homeassistant/components/skybell/config_flow.py index 29cd723bff0..26602e81882 100644 --- a/homeassistant/components/skybell/config_flow.py +++ b/homeassistant/components/skybell/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Skybell integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/skybell/entity.py b/homeassistant/components/skybell/entity.py index 2d596ec8aac..f3b0c077212 100644 --- a/homeassistant/components/skybell/entity.py +++ b/homeassistant/components/skybell/entity.py @@ -1,4 +1,5 @@ """Entity representing a Skybell HD Doorbell.""" + from __future__ import annotations from aioskybell import SkybellDevice diff --git a/homeassistant/components/skybell/light.py b/homeassistant/components/skybell/light.py index 70fe01fdb5e..cba9e70c848 100644 --- a/homeassistant/components/skybell/light.py +++ b/homeassistant/components/skybell/light.py @@ -1,4 +1,5 @@ """Light/LED support for the Skybell HD Doorbell.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index 37e58bd7f3c..04fb33d9be3 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -1,4 +1,5 @@ """Sensor support for Skybell Doorbells.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/skybell/switch.py b/homeassistant/components/skybell/switch.py index f67cca41ac9..fa4f723573f 100644 --- a/homeassistant/components/skybell/switch.py +++ b/homeassistant/components/skybell/switch.py @@ -1,4 +1,5 @@ """Switch support for the Skybell HD Doorbell.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/slack/__init__.py b/homeassistant/components/slack/__init__.py index 47ee07a7004..e5f6a50122e 100644 --- a/homeassistant/components/slack/__init__.py +++ b/homeassistant/components/slack/__init__.py @@ -1,4 +1,5 @@ """The slack integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/slack/config_flow.py b/homeassistant/components/slack/config_flow.py index 10b6183a321..b23dc60da60 100644 --- a/homeassistant/components/slack/config_flow.py +++ b/homeassistant/components/slack/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Slack integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/slack/const.py b/homeassistant/components/slack/const.py index ccc1fbb6643..0c26b63c72d 100644 --- a/homeassistant/components/slack/const.py +++ b/homeassistant/components/slack/const.py @@ -1,4 +1,5 @@ """Constants for the Slack integration.""" + from typing import Final ATTR_BLOCKS = "blocks" diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index aae2846503d..06fc76e217a 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -1,4 +1,5 @@ """Slack platform for notify component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/slack/sensor.py b/homeassistant/components/slack/sensor.py index 0c99cdeedec..b4d7fd28bd7 100644 --- a/homeassistant/components/slack/sensor.py +++ b/homeassistant/components/slack/sensor.py @@ -1,4 +1,5 @@ """Slack platform for sensor component.""" + from __future__ import annotations from slack import WebClient diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index f70bed1333e..6506be06e72 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,4 +1,5 @@ """Support for SleepIQ from SleepNumber.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index e137edb29ce..cb56a516b9b 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -1,4 +1,5 @@ """Support for SleepIQ sensors.""" + from asyncsleepiq import SleepIQBed, SleepIQSleeper from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/sleepiq/button.py b/homeassistant/components/sleepiq/button.py index 0d9a118d3c9..f82de232276 100644 --- a/homeassistant/components/sleepiq/button.py +++ b/homeassistant/components/sleepiq/button.py @@ -1,4 +1,5 @@ """Support for SleepIQ buttons.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sleepiq/config_flow.py b/homeassistant/components/sleepiq/config_flow.py index 60559921fcb..4a4813192c3 100644 --- a/homeassistant/components/sleepiq/config_flow.py +++ b/homeassistant/components/sleepiq/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure SleepIQ component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py index 9a0342aa7ac..3ffd736ccda 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -1,4 +1,5 @@ """Entity for the SleepIQ integration.""" + from abc import abstractmethod from typing import TypeVar diff --git a/homeassistant/components/sleepiq/number.py b/homeassistant/components/sleepiq/number.py index 4f90ef7dbdc..f42bed004fc 100644 --- a/homeassistant/components/sleepiq/number.py +++ b/homeassistant/components/sleepiq/number.py @@ -1,4 +1,5 @@ """Support for SleepIQ SleepNumber firmness number entities.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/sleepiq/select.py b/homeassistant/components/sleepiq/select.py index df8d854c9da..74bcd89cc4d 100644 --- a/homeassistant/components/sleepiq/select.py +++ b/homeassistant/components/sleepiq/select.py @@ -1,4 +1,5 @@ """Support for SleepIQ foundation preset selection.""" + from __future__ import annotations from asyncsleepiq import ( diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index c463c80224e..413e8e4d856 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -1,4 +1,5 @@ """Support for SleepIQ Sensor.""" + from __future__ import annotations from asyncsleepiq import SleepIQBed, SleepIQSleeper diff --git a/homeassistant/components/sleepiq/switch.py b/homeassistant/components/sleepiq/switch.py index 62ad72d9db4..9fc8ca9d20e 100644 --- a/homeassistant/components/sleepiq/switch.py +++ b/homeassistant/components/sleepiq/switch.py @@ -1,4 +1,5 @@ """Support for SleepIQ switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/slide/__init__.py b/homeassistant/components/slide/__init__.py index 6ab3528a927..868a0e82f89 100644 --- a/homeassistant/components/slide/__init__.py +++ b/homeassistant/components/slide/__init__.py @@ -1,4 +1,5 @@ """Component for the Slide API.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/slide/cover.py b/homeassistant/components/slide/cover.py index 866d3d40307..5186b3d0fea 100644 --- a/homeassistant/components/slide/cover.py +++ b/homeassistant/components/slide/cover.py @@ -1,4 +1,5 @@ """Support for Slide slides.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/slimproto/__init__.py b/homeassistant/components/slimproto/__init__.py index c22349bb2f2..a5ab10ac32b 100644 --- a/homeassistant/components/slimproto/__init__.py +++ b/homeassistant/components/slimproto/__init__.py @@ -1,4 +1,5 @@ """SlimProto Player integration.""" + from __future__ import annotations from aioslimproto import SlimServer diff --git a/homeassistant/components/slimproto/media_player.py b/homeassistant/components/slimproto/media_player.py index 1bf3c57fee2..81bce3820ed 100644 --- a/homeassistant/components/slimproto/media_player.py +++ b/homeassistant/components/slimproto/media_player.py @@ -1,4 +1,5 @@ """MediaPlayer platform for SlimProto Player integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sma/__init__.py b/homeassistant/components/sma/__init__.py index 419fd6aa8ed..febd4e34aaf 100644 --- a/homeassistant/components/sma/__init__.py +++ b/homeassistant/components/sma/__init__.py @@ -1,4 +1,5 @@ """The sma integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/sma/config_flow.py b/homeassistant/components/sma/config_flow.py index 8824b4242fa..dcf1084f161 100644 --- a/homeassistant/components/sma/config_flow.py +++ b/homeassistant/components/sma/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the sma integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sma/const.py b/homeassistant/components/sma/const.py index d51b3f6d316..dec99c1d1af 100644 --- a/homeassistant/components/sma/const.py +++ b/homeassistant/components/sma/const.py @@ -1,4 +1,5 @@ """Constants for the sma integration.""" + from homeassistant.const import Platform DOMAIN = "sma" diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index abf5c9a878f..0d4aad6050a 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -1,4 +1,5 @@ """SMA Solar Webconnect interface.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/smappee/api.py b/homeassistant/components/smappee/api.py index f12f53ff27f..1a036b1072f 100644 --- a/homeassistant/components/smappee/api.py +++ b/homeassistant/components/smappee/api.py @@ -1,4 +1,5 @@ """API for Smappee bound to Home Assistant OAuth.""" + from asyncio import run_coroutine_threadsafe from pysmappee import api diff --git a/homeassistant/components/smappee/binary_sensor.py b/homeassistant/components/smappee/binary_sensor.py index ed09b51ff25..a653896f1c2 100644 --- a/homeassistant/components/smappee/binary_sensor.py +++ b/homeassistant/components/smappee/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring a Smappee appliance binary sensor.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index ad6e5af963e..d2f94ed9a75 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring a Smappee energy sensor.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/smappee/switch.py b/homeassistant/components/smappee/switch.py index 238e41af8ff..678b7fa8229 100644 --- a/homeassistant/components/smappee/switch.py +++ b/homeassistant/components/smappee/switch.py @@ -1,4 +1,5 @@ """Support for interacting with Smappee Comport Plugs, Switches and Output Modules.""" + from typing import Any from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/smart_meter_texas/const.py b/homeassistant/components/smart_meter_texas/const.py index 3b87454a6a7..defe49f0be4 100644 --- a/homeassistant/components/smart_meter_texas/const.py +++ b/homeassistant/components/smart_meter_texas/const.py @@ -1,4 +1,5 @@ """Constants for the Smart Meter Texas integration.""" + from datetime import timedelta SCAN_INTERVAL = timedelta(hours=1) diff --git a/homeassistant/components/smart_meter_texas/sensor.py b/homeassistant/components/smart_meter_texas/sensor.py index a35a92bf257..80fc79671b5 100644 --- a/homeassistant/components/smart_meter_texas/sensor.py +++ b/homeassistant/components/smart_meter_texas/sensor.py @@ -1,4 +1,5 @@ """Support for Smart Meter Texas sensors.""" + from smart_meter_texas import Meter from homeassistant.components.sensor import ( diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index cdf04be29f3..6f213936447 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -1,4 +1,5 @@ """Support for SmartThings Cloud.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 25f9fa224ff..4bb60217eee 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -1,4 +1,5 @@ """Support for binary sensors through the SmartThings cloud API.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 4c2afa45b7f..4c767cbfa30 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,4 +1,5 @@ """Support for climate devices through the SmartThings cloud API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/smartthings/config_flow.py b/homeassistant/components/smartthings/config_flow.py index 212f8add07d..85f350b8fb3 100644 --- a/homeassistant/components/smartthings/config_flow.py +++ b/homeassistant/components/smartthings/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure SmartThings.""" + from http import HTTPStatus import logging diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 393242a30dd..e50837697e7 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -1,4 +1,5 @@ """Constants used by the SmartThings component and platforms.""" + from datetime import timedelta import re diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index 83522c61794..b1e260b5962 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -1,4 +1,5 @@ """Support for covers through the SmartThings cloud API.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 37c19eecd6b..7430b48dbd3 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -1,4 +1,5 @@ """Support for fans through the SmartThings cloud API.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 58623e08394..24a44a99d94 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -1,4 +1,5 @@ """Support for lights through the SmartThings cloud API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index 4e726ddc991..0cd954e7542 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -1,4 +1,5 @@ """Support for locks through the SmartThings cloud API.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/smartthings/scene.py b/homeassistant/components/smartthings/scene.py index faf58ede014..9756cef9f04 100644 --- a/homeassistant/components/smartthings/scene.py +++ b/homeassistant/components/smartthings/scene.py @@ -1,4 +1,5 @@ """Support for scenes through the SmartThings cloud API.""" + from typing import Any from homeassistant.components.scene import Scene diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 18016a88d29..13315c30031 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -1,4 +1,5 @@ """Support for sensors through the SmartThings cloud API.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index b1a859847a3..bd5f7bc0b68 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -1,4 +1,5 @@ """Support for switches through the SmartThings cloud API.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/smarttub/__init__.py b/homeassistant/components/smarttub/__init__.py index f98dbac86a1..8406fdc4c2f 100644 --- a/homeassistant/components/smarttub/__init__.py +++ b/homeassistant/components/smarttub/__init__.py @@ -1,4 +1,5 @@ """SmartTub integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py index 99037cd623c..cca0c6bc2ce 100644 --- a/homeassistant/components/smarttub/binary_sensor.py +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for binary sensor integration.""" + from __future__ import annotations from smarttub import SpaError, SpaReminder diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 4921fca022d..f0bb84b3390 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -1,4 +1,5 @@ """Platform for climate integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/smarttub/config_flow.py b/homeassistant/components/smarttub/config_flow.py index 5d9767e5fe3..60f14b03e45 100644 --- a/homeassistant/components/smarttub/config_flow.py +++ b/homeassistant/components/smarttub/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the SmartTub integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/smarttub/light.py b/homeassistant/components/smarttub/light.py index d89cdba3367..b5fac0b34f6 100644 --- a/homeassistant/components/smarttub/light.py +++ b/homeassistant/components/smarttub/light.py @@ -1,4 +1,5 @@ """Platform for light integration.""" + from typing import Any from smarttub import SpaLight diff --git a/homeassistant/components/smarttub/sensor.py b/homeassistant/components/smarttub/sensor.py index c362e1ea8f0..3694ca81a6b 100644 --- a/homeassistant/components/smarttub/sensor.py +++ b/homeassistant/components/smarttub/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from enum import Enum import smarttub diff --git a/homeassistant/components/smarty/__init__.py b/homeassistant/components/smarty/__init__.py index e3cf1dcf287..cc2e3850ef9 100644 --- a/homeassistant/components/smarty/__init__.py +++ b/homeassistant/components/smarty/__init__.py @@ -1,4 +1,5 @@ """Support to control a Salda Smarty XP/XV ventilation unit.""" + from datetime import timedelta import ipaddress import logging diff --git a/homeassistant/components/smarty/binary_sensor.py b/homeassistant/components/smarty/binary_sensor.py index d9d757a71b5..cf40dc7b982 100644 --- a/homeassistant/components/smarty/binary_sensor.py +++ b/homeassistant/components/smarty/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Salda Smarty XP/XV Ventilation Unit Binary Sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/smarty/fan.py b/homeassistant/components/smarty/fan.py index d3ba407fa40..6d46e040033 100644 --- a/homeassistant/components/smarty/fan.py +++ b/homeassistant/components/smarty/fan.py @@ -1,4 +1,5 @@ """Platform to control a Salda Smarty XP/XV ventilation unit.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/smarty/sensor.py b/homeassistant/components/smarty/sensor.py index 57d681594cf..a0c15b3825f 100644 --- a/homeassistant/components/smarty/sensor.py +++ b/homeassistant/components/smarty/sensor.py @@ -1,4 +1,5 @@ """Support for Salda Smarty XP/XV Ventilation Unit Sensors.""" + from __future__ import annotations import datetime as dt diff --git a/homeassistant/components/smhi/__init__.py b/homeassistant/components/smhi/__init__.py index 1dbfb5ecedd..94bdfcc4559 100644 --- a/homeassistant/components/smhi/__init__.py +++ b/homeassistant/components/smhi/__init__.py @@ -1,4 +1,5 @@ """Support for the Swedish weather institute weather service.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_LATITUDE, diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index dbb804f0c8a..4a28f475ad6 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure SMHI component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/smhi/const.py b/homeassistant/components/smhi/const.py index cc1c4550723..11401119227 100644 --- a/homeassistant/components/smhi/const.py +++ b/homeassistant/components/smhi/const.py @@ -1,4 +1,5 @@ """Constants in smhi component.""" + from typing import Final from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 5814db8168e..9204816c595 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -1,4 +1,5 @@ """Support for the Swedish weather institute weather service.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sms/notify.py b/homeassistant/components/sms/notify.py index 21d3ab2beb5..3374681c0f3 100644 --- a/homeassistant/components/sms/notify.py +++ b/homeassistant/components/sms/notify.py @@ -1,4 +1,5 @@ """Support for SMS notification services.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index 2f5ce7b7da3..7b1aa540728 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -1,4 +1,5 @@ """Support for SMS dongle sensor.""" + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py index 87600650551..a82675a2f07 100644 --- a/homeassistant/components/smtp/notify.py +++ b/homeassistant/components/smtp/notify.py @@ -1,4 +1,5 @@ """Mail (SMTP) notification service.""" + from __future__ import annotations from email.mime.application import MIMEApplication diff --git a/homeassistant/components/snapcast/const.py b/homeassistant/components/snapcast/const.py index ded57e6fb03..fbd5b903669 100644 --- a/homeassistant/components/snapcast/const.py +++ b/homeassistant/components/snapcast/const.py @@ -1,4 +1,5 @@ """Constants for Snapcast.""" + from homeassistant.const import Platform PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER] diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py index ae2917a106d..0918d6465ad 100644 --- a/homeassistant/components/snapcast/media_player.py +++ b/homeassistant/components/snapcast/media_player.py @@ -1,4 +1,5 @@ """Support for interacting with Snapcast clients.""" + from __future__ import annotations from snapcast.control.server import Snapserver diff --git a/homeassistant/components/snapcast/server.py b/homeassistant/components/snapcast/server.py index bac51150eba..761a8aa4712 100644 --- a/homeassistant/components/snapcast/server.py +++ b/homeassistant/components/snapcast/server.py @@ -1,4 +1,5 @@ """Snapcast Integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/snips/__init__.py b/homeassistant/components/snips/__init__.py index 33500217397..4731a0f324a 100644 --- a/homeassistant/components/snips/__init__.py +++ b/homeassistant/components/snips/__init__.py @@ -1,4 +1,5 @@ """Support for Snips on-device ASR and NLU.""" + from datetime import timedelta import json import logging diff --git a/homeassistant/components/snmp/device_tracker.py b/homeassistant/components/snmp/device_tracker.py index 696b079fd5e..4b8ab073b9c 100644 --- a/homeassistant/components/snmp/device_tracker.py +++ b/homeassistant/components/snmp/device_tracker.py @@ -1,4 +1,5 @@ """Support for fetching WiFi associations through SNMP.""" + from __future__ import annotations import binascii diff --git a/homeassistant/components/snmp/sensor.py b/homeassistant/components/snmp/sensor.py index a5915183ad0..02e466d2bc4 100644 --- a/homeassistant/components/snmp/sensor.py +++ b/homeassistant/components/snmp/sensor.py @@ -1,4 +1,5 @@ """Support for displaying collected data over SNMP.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/snmp/switch.py b/homeassistant/components/snmp/switch.py index a30cf93bcde..11bb4768e38 100644 --- a/homeassistant/components/snmp/switch.py +++ b/homeassistant/components/snmp/switch.py @@ -1,4 +1,5 @@ """Support for SNMP enabled switch.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/snooz/__init__.py b/homeassistant/components/snooz/__init__.py index 8349f781cf8..c97c89c2f4a 100644 --- a/homeassistant/components/snooz/__init__.py +++ b/homeassistant/components/snooz/__init__.py @@ -1,4 +1,5 @@ """The Snooz component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/snooz/config_flow.py b/homeassistant/components/snooz/config_flow.py index be0fcbf20de..3962a44d8b9 100644 --- a/homeassistant/components/snooz/config_flow.py +++ b/homeassistant/components/snooz/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Snooz component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/snooz/fan.py b/homeassistant/components/snooz/fan.py index 5cb80cb4189..fd6e5e69556 100644 --- a/homeassistant/components/snooz/fan.py +++ b/homeassistant/components/snooz/fan.py @@ -1,4 +1,5 @@ """Fan representation of a Snooz device.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/solaredge/__init__.py b/homeassistant/components/solaredge/__init__.py index 0b685661ac3..69e02c1875c 100644 --- a/homeassistant/components/solaredge/__init__.py +++ b/homeassistant/components/solaredge/__init__.py @@ -1,4 +1,5 @@ """The SolarEdge integration.""" + from __future__ import annotations import socket diff --git a/homeassistant/components/solaredge/config_flow.py b/homeassistant/components/solaredge/config_flow.py index 6c80cd2496b..b75af866549 100644 --- a/homeassistant/components/solaredge/config_flow.py +++ b/homeassistant/components/solaredge/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the SolarEdge platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/solaredge/const.py b/homeassistant/components/solaredge/const.py index aa6251ff433..6546b41d0ef 100644 --- a/homeassistant/components/solaredge/const.py +++ b/homeassistant/components/solaredge/const.py @@ -1,4 +1,5 @@ """Constants for the SolarEdge Monitoring API.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/solaredge/coordinator.py b/homeassistant/components/solaredge/coordinator.py index 4938a54ed65..d2da99820d7 100644 --- a/homeassistant/components/solaredge/coordinator.py +++ b/homeassistant/components/solaredge/coordinator.py @@ -1,4 +1,5 @@ """Provides the data update coordinators for SolarEdge.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 1b6fc0add2f..0099812c6fc 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -1,4 +1,5 @@ """Support for SolarEdge Monitoring API.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 0475489a6f4..2799d303a19 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -1,4 +1,5 @@ """Support for SolarEdge-local Monitoring API.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/solarlog/__init__.py b/homeassistant/components/solarlog/__init__.py index 95cf5cc4567..d2a3c50295c 100644 --- a/homeassistant/components/solarlog/__init__.py +++ b/homeassistant/components/solarlog/__init__.py @@ -1,4 +1,5 @@ """Solar-Log integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/solarlog/const.py b/homeassistant/components/solarlog/const.py index d8ba49adbec..31f17af83b5 100644 --- a/homeassistant/components/solarlog/const.py +++ b/homeassistant/components/solarlog/const.py @@ -1,4 +1,5 @@ """Constants for the Solar-Log integration.""" + from __future__ import annotations DOMAIN = "solarlog" diff --git a/homeassistant/components/solarlog/coordinator.py b/homeassistant/components/solarlog/coordinator.py index d363256f355..6af7c96302d 100644 --- a/homeassistant/components/solarlog/coordinator.py +++ b/homeassistant/components/solarlog/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for solarlog integration.""" + from datetime import timedelta import logging from urllib.parse import ParseResult, urlparse diff --git a/homeassistant/components/solarlog/sensor.py b/homeassistant/components/solarlog/sensor.py index 68ccf5c9c88..dcb4afcb863 100644 --- a/homeassistant/components/solarlog/sensor.py +++ b/homeassistant/components/solarlog/sensor.py @@ -1,4 +1,5 @@ """Platform for solarlog sensors.""" + from collections.abc import Callable from dataclasses import dataclass from datetime import datetime diff --git a/homeassistant/components/solax/__init__.py b/homeassistant/components/solax/__init__.py index 6ede5b5df02..b5e15043cec 100644 --- a/homeassistant/components/solax/__init__.py +++ b/homeassistant/components/solax/__init__.py @@ -1,4 +1,5 @@ """The solax component.""" + from solax import real_time_api from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/solax/config_flow.py b/homeassistant/components/solax/config_flow.py index 8b3566e80eb..4055f1c46ae 100644 --- a/homeassistant/components/solax/config_flow.py +++ b/homeassistant/components/solax/config_flow.py @@ -1,4 +1,5 @@ """Config flow for solax integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/solax/sensor.py b/homeassistant/components/solax/sensor.py index eee74c1007f..dc3b7e522a3 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -1,4 +1,5 @@ """Support for Solax inverter via local API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/soma/__init__.py b/homeassistant/components/soma/__init__.py index bbcc29d7853..cd282a9f276 100644 --- a/homeassistant/components/soma/__init__.py +++ b/homeassistant/components/soma/__init__.py @@ -1,4 +1,5 @@ """Support for Soma Smartshades.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/soma/cover.py b/homeassistant/components/soma/cover.py index 4aa2559b140..a5d9507af4a 100644 --- a/homeassistant/components/soma/cover.py +++ b/homeassistant/components/soma/cover.py @@ -1,4 +1,5 @@ """Support for Soma Covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/soma/sensor.py b/homeassistant/components/soma/sensor.py index d1c0de188a0..4992ec5cde4 100644 --- a/homeassistant/components/soma/sensor.py +++ b/homeassistant/components/soma/sensor.py @@ -1,4 +1,5 @@ """Support for Soma sensors.""" + from datetime import timedelta from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/somfy_mylink/config_flow.py b/homeassistant/components/somfy_mylink/config_flow.py index db8dcb85383..6e68be45dff 100644 --- a/homeassistant/components/somfy_mylink/config_flow.py +++ b/homeassistant/components/somfy_mylink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Somfy MyLink integration.""" + from __future__ import annotations from copy import deepcopy diff --git a/homeassistant/components/somfy_mylink/const.py b/homeassistant/components/somfy_mylink/const.py index bb9f2c5fd42..8669c73fb9b 100644 --- a/homeassistant/components/somfy_mylink/const.py +++ b/homeassistant/components/somfy_mylink/const.py @@ -1,4 +1,5 @@ """Component for the Somfy MyLink device supporting the Synergy API.""" + from homeassistant.const import Platform CONF_SYSTEM_ID = "system_id" diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 69d2ba76e22..89c247ebbfb 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -1,4 +1,5 @@ """The Sonarr component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index 5631c3a70e0..7d1db21cd53 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Sonarr.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/sonarr/coordinator.py b/homeassistant/components/sonarr/coordinator.py index 1010c196c21..2d807bcf140 100644 --- a/homeassistant/components/sonarr/coordinator.py +++ b/homeassistant/components/sonarr/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Sonarr integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/sonarr/entity.py b/homeassistant/components/sonarr/entity.py index 6231ca3903a..7dc0d0ca147 100644 --- a/homeassistant/components/sonarr/entity.py +++ b/homeassistant/components/sonarr/entity.py @@ -1,4 +1,5 @@ """Base Entity for Sonarr.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 4aea2a73ede..6e08d6b4886 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -1,4 +1,5 @@ """Support for Sonarr sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/songpal/config_flow.py b/homeassistant/components/songpal/config_flow.py index 03c1aa4bdf2..f8a0db3815d 100644 --- a/homeassistant/components/songpal/config_flow.py +++ b/homeassistant/components/songpal/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure songpal component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index 582e62a67eb..33dc65d5eaa 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -1,4 +1,5 @@ """Support for Songpal-enabled (Sony) media devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 0df6a7422fe..d16fd6cc6ea 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -1,4 +1,5 @@ """Support to embed Sonos.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sonos/alarms.py b/homeassistant/components/sonos/alarms.py index e7cf05a1ff0..a18598fc545 100644 --- a/homeassistant/components/sonos/alarms.py +++ b/homeassistant/components/sonos/alarms.py @@ -1,4 +1,5 @@ """Class representing Sonos alarms.""" + from __future__ import annotations from collections.abc import Iterator diff --git a/homeassistant/components/sonos/binary_sensor.py b/homeassistant/components/sonos/binary_sensor.py index 4a41e572c1a..fbeb196f9fa 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -1,4 +1,5 @@ """Entity representing a Sonos power sensor.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sonos/config_flow.py b/homeassistant/components/sonos/config_flow.py index daf9e5c7405..a8ace6e35c5 100644 --- a/homeassistant/components/sonos/config_flow.py +++ b/homeassistant/components/sonos/config_flow.py @@ -1,4 +1,5 @@ """Config flow for SONOS.""" + from collections.abc import Awaitable import dataclasses diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index e42fb7d67c7..610a68afedf 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -1,4 +1,5 @@ """Const for Sonos.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/sonos/diagnostics.py b/homeassistant/components/sonos/diagnostics.py index 21e440673d6..b97b03b9be2 100644 --- a/homeassistant/components/sonos/diagnostics.py +++ b/homeassistant/components/sonos/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Sonos.""" + from __future__ import annotations import time diff --git a/homeassistant/components/sonos/entity.py b/homeassistant/components/sonos/entity.py index 05b69c54c50..bd7256493e8 100644 --- a/homeassistant/components/sonos/entity.py +++ b/homeassistant/components/sonos/entity.py @@ -1,4 +1,5 @@ """Entity representing a Sonos player.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/sonos/exception.py b/homeassistant/components/sonos/exception.py index 7ff5dacd293..6f7483f4188 100644 --- a/homeassistant/components/sonos/exception.py +++ b/homeassistant/components/sonos/exception.py @@ -1,4 +1,5 @@ """Sonos specific exceptions.""" + from homeassistant.components.media_player.errors import BrowseError from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/sonos/favorites.py b/homeassistant/components/sonos/favorites.py index eeeb210b9ec..5050555a7cb 100644 --- a/homeassistant/components/sonos/favorites.py +++ b/homeassistant/components/sonos/favorites.py @@ -1,4 +1,5 @@ """Class representing Sonos favorites.""" + from __future__ import annotations from collections.abc import Iterator diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index 1005b6c7d6a..b855c014e1d 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -1,4 +1,5 @@ """Helper methods for common tasks.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sonos/household_coordinator.py b/homeassistant/components/sonos/household_coordinator.py index 29b9a005552..b0c0592a642 100644 --- a/homeassistant/components/sonos/household_coordinator.py +++ b/homeassistant/components/sonos/household_coordinator.py @@ -1,4 +1,5 @@ """Class representing a Sonos household storage helper.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sonos/media.py b/homeassistant/components/sonos/media.py index 0a402064fca..1f5432c440b 100644 --- a/homeassistant/components/sonos/media.py +++ b/homeassistant/components/sonos/media.py @@ -1,4 +1,5 @@ """Support for media metadata handling.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 1b32cb2473a..17327bf4be1 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 27059bba180..12e8b44652a 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1,4 +1,5 @@ """Support to interface with Sonos players.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/sonos/number.py b/homeassistant/components/sonos/number.py index c74c5933ecf..f9e9fc8bee0 100644 --- a/homeassistant/components/sonos/number.py +++ b/homeassistant/components/sonos/number.py @@ -1,4 +1,5 @@ """Entity representing a Sonos number control.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sonos/sensor.py b/homeassistant/components/sonos/sensor.py index ca3cc89d750..25cbcf752ea 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -1,4 +1,5 @@ """Entity representing a Sonos battery level.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index e32f595a13a..ebc981790fa 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -1,4 +1,5 @@ """Base class for common speaker tasks.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/sonos/statistics.py b/homeassistant/components/sonos/statistics.py index b761469aea5..ec3486d47e7 100644 --- a/homeassistant/components/sonos/statistics.py +++ b/homeassistant/components/sonos/statistics.py @@ -1,4 +1,5 @@ """Class to track subscription event statistics.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index c551d4a00d3..53e06687615 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -1,4 +1,5 @@ """Entity representing a Sonos Alarm.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/sony_projector/switch.py b/homeassistant/components/sony_projector/switch.py index 259ab8b154c..7ecff46d3bd 100644 --- a/homeassistant/components/sony_projector/switch.py +++ b/homeassistant/components/sony_projector/switch.py @@ -1,4 +1,5 @@ """Support for Sony projectors via SDCP network control.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 831b64f7056..0843cc1a826 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -1,4 +1,5 @@ """Support for interface with a Bose SoundTouch.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/spaceapi/__init__.py b/homeassistant/components/spaceapi/__init__.py index bcbd2a258ac..93d448bd17f 100644 --- a/homeassistant/components/spaceapi/__init__.py +++ b/homeassistant/components/spaceapi/__init__.py @@ -1,4 +1,5 @@ """Support for the SpaceAPI.""" + from contextlib import suppress import voluptuous as vol diff --git a/homeassistant/components/spc/alarm_control_panel.py b/homeassistant/components/spc/alarm_control_panel.py index ace352b2ba0..d7f783b550d 100644 --- a/homeassistant/components/spc/alarm_control_panel.py +++ b/homeassistant/components/spc/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" + from __future__ import annotations from pyspcwebgw import SpcWebGateway diff --git a/homeassistant/components/spc/binary_sensor.py b/homeassistant/components/spc/binary_sensor.py index a43551567e6..c79fa1f0c09 100644 --- a/homeassistant/components/spc/binary_sensor.py +++ b/homeassistant/components/spc/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" + from __future__ import annotations from pyspcwebgw import SpcWebGateway diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 1fb368b13c7..f4f483e7ff8 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -1,4 +1,5 @@ """Support for testing internet speed via Speedtest.net.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 43b4bb64a9d..2ef2a70d745 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Speedtest.net.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/speedtestdotnet/const.py b/homeassistant/components/speedtestdotnet/const.py index 75cd78ca8cf..2002d46c838 100644 --- a/homeassistant/components/speedtestdotnet/const.py +++ b/homeassistant/components/speedtestdotnet/const.py @@ -1,4 +1,5 @@ """Constants used by Speedtest.net.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/speedtestdotnet/sensor.py b/homeassistant/components/speedtestdotnet/sensor.py index 1d23e0e516e..5bf1a6bea91 100644 --- a/homeassistant/components/speedtestdotnet/sensor.py +++ b/homeassistant/components/speedtestdotnet/sensor.py @@ -1,4 +1,5 @@ """Support for Speedtest.net internet speed testing sensor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py index 15ba19e9b3a..11e84a942f4 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -1,4 +1,5 @@ """Support for Spider thermostats.""" + from typing import Any from homeassistant.components.climate import ( diff --git a/homeassistant/components/spider/const.py b/homeassistant/components/spider/const.py index e48e963637a..189763f4e98 100644 --- a/homeassistant/components/spider/const.py +++ b/homeassistant/components/spider/const.py @@ -1,4 +1,5 @@ """Constants for the Spider integration.""" + from homeassistant.const import Platform DOMAIN = "spider" diff --git a/homeassistant/components/spider/sensor.py b/homeassistant/components/spider/sensor.py index bce437437c6..70c38a40e15 100644 --- a/homeassistant/components/spider/sensor.py +++ b/homeassistant/components/spider/sensor.py @@ -1,4 +1,5 @@ """Support for Spider Powerplugs (energy & power).""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/spider/switch.py b/homeassistant/components/spider/switch.py index 508dcee9d73..63f0ec6cb69 100644 --- a/homeassistant/components/spider/switch.py +++ b/homeassistant/components/spider/switch.py @@ -1,4 +1,5 @@ """Support for Spider switches.""" + from typing import Any from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/splunk/__init__.py b/homeassistant/components/splunk/__init__.py index 32b63c42370..4294020eeee 100644 --- a/homeassistant/components/splunk/__init__.py +++ b/homeassistant/components/splunk/__init__.py @@ -1,4 +1,5 @@ """Support to send data to a Splunk instance.""" + from http import HTTPStatus import json import logging diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index ca9f63bbd1c..eb3c57eba21 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -1,4 +1,5 @@ """The spotify integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/spotify/browse_media.py b/homeassistant/components/spotify/browse_media.py index 162369fd27d..cc8f57be1bb 100644 --- a/homeassistant/components/spotify/browse_media.py +++ b/homeassistant/components/spotify/browse_media.py @@ -1,4 +1,5 @@ """Support for Spotify media browsing.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/spotify/config_flow.py b/homeassistant/components/spotify/config_flow.py index 8629e0864ff..0c60959362d 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Spotify.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index caa796f9b59..487e58d8f8b 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -1,4 +1,5 @@ """Support for interacting with Spotify Connect.""" + from __future__ import annotations from asyncio import run_coroutine_threadsafe diff --git a/homeassistant/components/spotify/system_health.py b/homeassistant/components/spotify/system_health.py index a22f7b8a821..963c3bfb0ef 100644 --- a/homeassistant/components/spotify/system_health.py +++ b/homeassistant/components/spotify/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from homeassistant.components import system_health from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/spotify/util.py b/homeassistant/components/spotify/util.py index e9af305a1d0..c1baec0b498 100644 --- a/homeassistant/components/spotify/util.py +++ b/homeassistant/components/spotify/util.py @@ -1,4 +1,5 @@ """Utils for Spotify.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sql/__init__.py b/homeassistant/components/sql/__init__.py index a4768165c25..71e3671ce96 100644 --- a/homeassistant/components/sql/__init__.py +++ b/homeassistant/components/sql/__init__.py @@ -1,4 +1,5 @@ """The sql component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sql/config_flow.py b/homeassistant/components/sql/config_flow.py index 7b03a843941..5537c7ff3b0 100644 --- a/homeassistant/components/sql/config_flow.py +++ b/homeassistant/components/sql/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for SQL integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sql/models.py b/homeassistant/components/sql/models.py index feac9ebf20c..872ceedde71 100644 --- a/homeassistant/components/sql/models.py +++ b/homeassistant/components/sql/models.py @@ -1,4 +1,5 @@ """The sql integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index 063627f9f43..68a6cb71f5b 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -1,4 +1,5 @@ """Sensor from an SQL Query.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/sql/util.py b/homeassistant/components/sql/util.py index 3dd0990b241..48fb53820ff 100644 --- a/homeassistant/components/sql/util.py +++ b/homeassistant/components/sql/util.py @@ -1,4 +1,5 @@ """Utils for sql.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 4e3d71eca24..191981b781d 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing to the Logitech SqueezeBox API.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/srp_energy/__init__.py b/homeassistant/components/srp_energy/__init__.py index 98d1cdd421a..591ba5043e9 100644 --- a/homeassistant/components/srp_energy/__init__.py +++ b/homeassistant/components/srp_energy/__init__.py @@ -1,4 +1,5 @@ """The SRP Energy integration.""" + from srpenergy.client import SrpEnergyClient from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/srp_energy/config_flow.py b/homeassistant/components/srp_energy/config_flow.py index 089b036fea7..8ec53a20cc8 100644 --- a/homeassistant/components/srp_energy/config_flow.py +++ b/homeassistant/components/srp_energy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for SRP Energy.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/srp_energy/const.py b/homeassistant/components/srp_energy/const.py index b2ab05f43d5..00b3b958740 100644 --- a/homeassistant/components/srp_energy/const.py +++ b/homeassistant/components/srp_energy/const.py @@ -1,4 +1,5 @@ """Constants for the SRP Energy integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/srp_energy/coordinator.py b/homeassistant/components/srp_energy/coordinator.py index a72ea4d3334..60f73fc27c6 100644 --- a/homeassistant/components/srp_energy/coordinator.py +++ b/homeassistant/components/srp_energy/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the srp_energy integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/srp_energy/sensor.py b/homeassistant/components/srp_energy/sensor.py index 9e8b8d08de9..a9f5c25d6a5 100644 --- a/homeassistant/components/srp_energy/sensor.py +++ b/homeassistant/components/srp_energy/sensor.py @@ -1,4 +1,5 @@ """Support for SRP Energy Sensor.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index 69647925c47..ce6b7e30a84 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -1,4 +1,5 @@ """The SSDP integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/starline/__init__.py b/homeassistant/components/starline/__init__.py index 2af7b4a75f4..17f3b7dc504 100644 --- a/homeassistant/components/starline/__init__.py +++ b/homeassistant/components/starline/__init__.py @@ -1,4 +1,5 @@ """The StarLine component.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/starline/account.py b/homeassistant/components/starline/account.py index 2940dcf0579..d260ba3503e 100644 --- a/homeassistant/components/starline/account.py +++ b/homeassistant/components/starline/account.py @@ -1,4 +1,5 @@ """StarLine Account.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/starline/binary_sensor.py b/homeassistant/components/starline/binary_sensor.py index 958435bfcfd..0383fc8ade6 100644 --- a/homeassistant/components/starline/binary_sensor.py +++ b/homeassistant/components/starline/binary_sensor.py @@ -1,4 +1,5 @@ """Reads vehicle status from StarLine API.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/starline/button.py b/homeassistant/components/starline/button.py index b46faedcff0..fa2b96ca58c 100644 --- a/homeassistant/components/starline/button.py +++ b/homeassistant/components/starline/button.py @@ -1,4 +1,5 @@ """Support for StarLine button.""" + from __future__ import annotations from homeassistant.components.button import ButtonEntity, ButtonEntityDescription diff --git a/homeassistant/components/starline/config_flow.py b/homeassistant/components/starline/config_flow.py index 30a95ed6869..402a94c46b0 100644 --- a/homeassistant/components/starline/config_flow.py +++ b/homeassistant/components/starline/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure StarLine component.""" + from __future__ import annotations from starline import StarlineAuth diff --git a/homeassistant/components/starline/device_tracker.py b/homeassistant/components/starline/device_tracker.py index 06703c30482..9b57b335d8b 100644 --- a/homeassistant/components/starline/device_tracker.py +++ b/homeassistant/components/starline/device_tracker.py @@ -1,4 +1,5 @@ """StarLine device tracker.""" + from homeassistant.components.device_tracker import SourceType, TrackerEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/starline/entity.py b/homeassistant/components/starline/entity.py index 27be5e2aace..74807996dfb 100644 --- a/homeassistant/components/starline/entity.py +++ b/homeassistant/components/starline/entity.py @@ -1,4 +1,5 @@ """StarLine base entity.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index 104e807aff0..19aad1a19b2 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -1,4 +1,5 @@ """Support for StarLine lock.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index f9e6f7b1e0a..a53751a3b23 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -1,4 +1,5 @@ """Reads vehicle status from StarLine API.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/starline/switch.py b/homeassistant/components/starline/switch.py index 13de0272284..8ca736d2ac5 100644 --- a/homeassistant/components/starline/switch.py +++ b/homeassistant/components/starline/switch.py @@ -1,4 +1,5 @@ """Support for StarLine switch.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/starlingbank/sensor.py b/homeassistant/components/starlingbank/sensor.py index f4a87837878..2950c60a6ff 100644 --- a/homeassistant/components/starlingbank/sensor.py +++ b/homeassistant/components/starlingbank/sensor.py @@ -1,4 +1,5 @@ """Support for balance data via the Starling Bank API.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/starlink/__init__.py b/homeassistant/components/starlink/__init__.py index 3413c4ff595..fc6863e7935 100644 --- a/homeassistant/components/starlink/__init__.py +++ b/homeassistant/components/starlink/__init__.py @@ -1,4 +1,5 @@ """The Starlink integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/starlink/config_flow.py b/homeassistant/components/starlink/config_flow.py index 14840ccf46d..a64d5998556 100644 --- a/homeassistant/components/starlink/config_flow.py +++ b/homeassistant/components/starlink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Starlink.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/starlink/coordinator.py b/homeassistant/components/starlink/coordinator.py index 95a5515ab21..069c9969de7 100644 --- a/homeassistant/components/starlink/coordinator.py +++ b/homeassistant/components/starlink/coordinator.py @@ -1,4 +1,5 @@ """Contains the shared Coordinator for Starlink systems.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/starlink/entity.py b/homeassistant/components/starlink/entity.py index b726beeef0d..e868e4f0645 100644 --- a/homeassistant/components/starlink/entity.py +++ b/homeassistant/components/starlink/entity.py @@ -1,4 +1,5 @@ """Contains base entity classes for Starlink entities.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/starlink/sensor.py b/homeassistant/components/starlink/sensor.py index 555990a4b18..3f3b855ca63 100644 --- a/homeassistant/components/starlink/sensor.py +++ b/homeassistant/components/starlink/sensor.py @@ -1,4 +1,5 @@ """Contains sensors exposed by the Starlink integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/startca/sensor.py b/homeassistant/components/startca/sensor.py index ab53b039756..fad001d6d29 100644 --- a/homeassistant/components/startca/sensor.py +++ b/homeassistant/components/startca/sensor.py @@ -1,4 +1,5 @@ """Support for Start.ca Bandwidth Monitor.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 817780a9282..0a88e9d93af 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -1,4 +1,5 @@ """Support for statistics for sensor values.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/steam_online/__init__.py b/homeassistant/components/steam_online/__init__.py index 72f1cd2abb8..93b4a3eb370 100644 --- a/homeassistant/components/steam_online/__init__.py +++ b/homeassistant/components/steam_online/__init__.py @@ -1,4 +1,5 @@ """The Steam integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/steam_online/config_flow.py b/homeassistant/components/steam_online/config_flow.py index e3a239c7181..bd38e79b133 100644 --- a/homeassistant/components/steam_online/config_flow.py +++ b/homeassistant/components/steam_online/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Steam integration.""" + from __future__ import annotations from collections.abc import Iterator, Mapping diff --git a/homeassistant/components/steam_online/coordinator.py b/homeassistant/components/steam_online/coordinator.py index 719acecd1f2..847fd297247 100644 --- a/homeassistant/components/steam_online/coordinator.py +++ b/homeassistant/components/steam_online/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Steam integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/steam_online/entity.py b/homeassistant/components/steam_online/entity.py index 8ad6bd8c713..3ba23a3e58a 100644 --- a/homeassistant/components/steam_online/entity.py +++ b/homeassistant/components/steam_online/entity.py @@ -1,4 +1,5 @@ """Entity classes for the Steam integration.""" + from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index d3ae69e2517..8e8b70eaeb9 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -1,4 +1,5 @@ """Sensor for Steam account status.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/steamist/__init__.py b/homeassistant/components/steamist/__init__.py index e84615b9352..8d8401ec6fd 100644 --- a/homeassistant/components/steamist/__init__.py +++ b/homeassistant/components/steamist/__init__.py @@ -1,4 +1,5 @@ """The Steamist integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/steamist/config_flow.py b/homeassistant/components/steamist/config_flow.py index 2a5a4cf5d07..aaa2bd9c853 100644 --- a/homeassistant/components/steamist/config_flow.py +++ b/homeassistant/components/steamist/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Steamist integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/steamist/coordinator.py b/homeassistant/components/steamist/coordinator.py index 67aedf0af94..c5aa7be7ddc 100644 --- a/homeassistant/components/steamist/coordinator.py +++ b/homeassistant/components/steamist/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for steamist.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/steamist/discovery.py b/homeassistant/components/steamist/discovery.py index cff97692979..5c3262ce4eb 100644 --- a/homeassistant/components/steamist/discovery.py +++ b/homeassistant/components/steamist/discovery.py @@ -1,4 +1,5 @@ """The Steamist integration discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/steamist/entity.py b/homeassistant/components/steamist/entity.py index 78340dab363..aef2d652058 100644 --- a/homeassistant/components/steamist/entity.py +++ b/homeassistant/components/steamist/entity.py @@ -1,4 +1,5 @@ """Support for Steamist sensors.""" + from __future__ import annotations from aiosteamist import SteamistStatus diff --git a/homeassistant/components/steamist/sensor.py b/homeassistant/components/steamist/sensor.py index dd51c485b4e..557952767ea 100644 --- a/homeassistant/components/steamist/sensor.py +++ b/homeassistant/components/steamist/sensor.py @@ -1,4 +1,5 @@ """Support for Steamist sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/steamist/switch.py b/homeassistant/components/steamist/switch.py index 2161af4df92..91806f4fa0c 100644 --- a/homeassistant/components/steamist/switch.py +++ b/homeassistant/components/steamist/switch.py @@ -1,4 +1,5 @@ """Support for Steamist switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/stiebel_eltron/__init__.py b/homeassistant/components/stiebel_eltron/__init__.py index 13ca12f482e..a5e92312f3d 100644 --- a/homeassistant/components/stiebel_eltron/__init__.py +++ b/homeassistant/components/stiebel_eltron/__init__.py @@ -1,4 +1,5 @@ """The component for STIEBEL ELTRON heat pumps with ISGWeb Modbus module.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/stiebel_eltron/climate.py b/homeassistant/components/stiebel_eltron/climate.py index cedd1b3dd90..41015ac16a4 100644 --- a/homeassistant/components/stiebel_eltron/climate.py +++ b/homeassistant/components/stiebel_eltron/climate.py @@ -1,4 +1,5 @@ """Support for stiebel_eltron climate platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/stookalert/__init__.py b/homeassistant/components/stookalert/__init__.py index 63458a2f78a..0ef9c7fa845 100644 --- a/homeassistant/components/stookalert/__init__.py +++ b/homeassistant/components/stookalert/__init__.py @@ -1,4 +1,5 @@ """The Stookalert integration.""" + from __future__ import annotations import stookalert diff --git a/homeassistant/components/stookalert/binary_sensor.py b/homeassistant/components/stookalert/binary_sensor.py index 0ee087a779e..a2fff52f2a3 100644 --- a/homeassistant/components/stookalert/binary_sensor.py +++ b/homeassistant/components/stookalert/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Stookalert Binary Sensor.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/stookalert/config_flow.py b/homeassistant/components/stookalert/config_flow.py index bc37fbded72..0d3bc0c1761 100644 --- a/homeassistant/components/stookalert/config_flow.py +++ b/homeassistant/components/stookalert/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Stookalert integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/stookalert/diagnostics.py b/homeassistant/components/stookalert/diagnostics.py index cf327174673..c15e808ae19 100644 --- a/homeassistant/components/stookalert/diagnostics.py +++ b/homeassistant/components/stookalert/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Stookalert.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/stookwijzer/__init__.py b/homeassistant/components/stookwijzer/__init__.py index d1950eaf0a3..a714e3bd368 100644 --- a/homeassistant/components/stookwijzer/__init__.py +++ b/homeassistant/components/stookwijzer/__init__.py @@ -1,4 +1,5 @@ """The Stookwijzer integration.""" + from __future__ import annotations from stookwijzer import Stookwijzer diff --git a/homeassistant/components/stookwijzer/config_flow.py b/homeassistant/components/stookwijzer/config_flow.py index 7a4ddae91c8..be53ce56390 100644 --- a/homeassistant/components/stookwijzer/config_flow.py +++ b/homeassistant/components/stookwijzer/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Stookwijzer integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/stookwijzer/const.py b/homeassistant/components/stookwijzer/const.py index 1a125da6a6b..e8cb3d818e6 100644 --- a/homeassistant/components/stookwijzer/const.py +++ b/homeassistant/components/stookwijzer/const.py @@ -1,4 +1,5 @@ """Constants for the Stookwijzer integration.""" + from enum import StrEnum import logging from typing import Final diff --git a/homeassistant/components/stookwijzer/diagnostics.py b/homeassistant/components/stookwijzer/diagnostics.py index e29606cb191..c7bf4fad14d 100644 --- a/homeassistant/components/stookwijzer/diagnostics.py +++ b/homeassistant/components/stookwijzer/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Stookwijzer.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/stookwijzer/sensor.py b/homeassistant/components/stookwijzer/sensor.py index 312f8bdd02d..b8f9a660598 100644 --- a/homeassistant/components/stookwijzer/sensor.py +++ b/homeassistant/components/stookwijzer/sensor.py @@ -1,4 +1,5 @@ """Support for Stookwijzer Sensor.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 23ff80952bf..3c36a78f13c 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -14,6 +14,7 @@ are no active output formats, the background worker is shut down and access tokens are expired. Alternatively, a Stream can be configured with keepalive to always keep workers active. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index b7f27367d77..4ff5f79ebd8 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -1,4 +1,5 @@ """Provides core stream functionality.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/stream/fmp4utils.py b/homeassistant/components/stream/fmp4utils.py index 7276e7a0d9b..d84b98f8b6f 100644 --- a/homeassistant/components/stream/fmp4utils.py +++ b/homeassistant/components/stream/fmp4utils.py @@ -1,4 +1,5 @@ """Utilities to help convert mp4s to fmp4s.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index cddb4413ed8..16694822b01 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -1,4 +1,5 @@ """Provide functionality to stream HLS.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index a3441eb76da..3af581d0e1b 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -1,4 +1,5 @@ """Provide functionality to record stream.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 0badd8ebc42..1bea660c95d 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -1,4 +1,5 @@ """Provides the worker thread needed for processing streams.""" + from __future__ import annotations from collections import defaultdict, deque diff --git a/homeassistant/components/streamlabswater/binary_sensor.py b/homeassistant/components/streamlabswater/binary_sensor.py index efc0eb24dd7..5a0073c25d3 100644 --- a/homeassistant/components/streamlabswater/binary_sensor.py +++ b/homeassistant/components/streamlabswater/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Streamlabs Water Monitor Away Mode.""" + from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/streamlabswater/config_flow.py b/homeassistant/components/streamlabswater/config_flow.py index ebe163c201f..327e5dcdae3 100644 --- a/homeassistant/components/streamlabswater/config_flow.py +++ b/homeassistant/components/streamlabswater/config_flow.py @@ -1,4 +1,5 @@ """Config flow for StreamLabs integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/streamlabswater/coordinator.py b/homeassistant/components/streamlabswater/coordinator.py index bcb2e7790d4..56e67abe222 100644 --- a/homeassistant/components/streamlabswater/coordinator.py +++ b/homeassistant/components/streamlabswater/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for Streamlabs water integration.""" + from dataclasses import dataclass from datetime import timedelta diff --git a/homeassistant/components/streamlabswater/entity.py b/homeassistant/components/streamlabswater/entity.py index 4458523a07f..fb7031a9e76 100644 --- a/homeassistant/components/streamlabswater/entity.py +++ b/homeassistant/components/streamlabswater/entity.py @@ -1,4 +1,5 @@ """Base entity for Streamlabs integration.""" + from homeassistant.core import DOMAIN from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/streamlabswater/sensor.py b/homeassistant/components/streamlabswater/sensor.py index d9bb76814b5..412b2187495 100644 --- a/homeassistant/components/streamlabswater/sensor.py +++ b/homeassistant/components/streamlabswater/sensor.py @@ -1,4 +1,5 @@ """Support for Streamlabs Water Monitor Usage.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index 45bdc3a63c7..811913e1494 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -1,4 +1,5 @@ """Provide functionality to STT.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/stt/const.py b/homeassistant/components/stt/const.py index c9f5eb13d17..2df5bea0316 100644 --- a/homeassistant/components/stt/const.py +++ b/homeassistant/components/stt/const.py @@ -1,4 +1,5 @@ """STT constante.""" + from enum import Enum DOMAIN = "stt" diff --git a/homeassistant/components/stt/legacy.py b/homeassistant/components/stt/legacy.py index bd1cfbca3d2..fc7738418f6 100644 --- a/homeassistant/components/stt/legacy.py +++ b/homeassistant/components/stt/legacy.py @@ -1,4 +1,5 @@ """Handle legacy speech-to-text platforms.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/stt/models.py b/homeassistant/components/stt/models.py index 45322e2da07..9471316dc8e 100644 --- a/homeassistant/components/stt/models.py +++ b/homeassistant/components/stt/models.py @@ -1,4 +1,5 @@ """Speech-to-text data models.""" + from dataclasses import dataclass from .const import ( diff --git a/homeassistant/components/subaru/__init__.py b/homeassistant/components/subaru/__init__.py index 8a22391284f..d7169fc181e 100644 --- a/homeassistant/components/subaru/__init__.py +++ b/homeassistant/components/subaru/__init__.py @@ -1,4 +1,5 @@ """The Subaru integration.""" + from datetime import timedelta import logging import time diff --git a/homeassistant/components/subaru/config_flow.py b/homeassistant/components/subaru/config_flow.py index aeee2cb7675..5ecaf9670d7 100644 --- a/homeassistant/components/subaru/config_flow.py +++ b/homeassistant/components/subaru/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Subaru integration.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/subaru/const.py b/homeassistant/components/subaru/const.py index ab76c363f7e..d8692e6a8bc 100644 --- a/homeassistant/components/subaru/const.py +++ b/homeassistant/components/subaru/const.py @@ -1,4 +1,5 @@ """Constants for the Subaru integration.""" + from subarulink.const import ALL_DOORS, DRIVERS_DOOR, TAILGATE_DOOR from homeassistant.const import Platform diff --git a/homeassistant/components/subaru/device_tracker.py b/homeassistant/components/subaru/device_tracker.py index c11a85eca66..8611c338a8b 100644 --- a/homeassistant/components/subaru/device_tracker.py +++ b/homeassistant/components/subaru/device_tracker.py @@ -1,4 +1,5 @@ """Support for Subaru device tracker.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/subaru/diagnostics.py b/homeassistant/components/subaru/diagnostics.py index 79ffcbe1792..0a26387d1c2 100644 --- a/homeassistant/components/subaru/diagnostics.py +++ b/homeassistant/components/subaru/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics for the Subaru integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/subaru/sensor.py b/homeassistant/components/subaru/sensor.py index bdc1677ed8b..bbb00a758dd 100644 --- a/homeassistant/components/subaru/sensor.py +++ b/homeassistant/components/subaru/sensor.py @@ -1,4 +1,5 @@ """Support for Subaru sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/suez_water/__init__.py b/homeassistant/components/suez_water/__init__.py index 02d78dfee41..07944de2c81 100644 --- a/homeassistant/components/suez_water/__init__.py +++ b/homeassistant/components/suez_water/__init__.py @@ -1,4 +1,5 @@ """The Suez Water integration.""" + from __future__ import annotations from pysuez import SuezClient diff --git a/homeassistant/components/suez_water/config_flow.py b/homeassistant/components/suez_water/config_flow.py index 667e1b7abaa..f3bfda91c3c 100644 --- a/homeassistant/components/suez_water/config_flow.py +++ b/homeassistant/components/suez_water/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Suez Water integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 6df2e3870d7..5f89c28808d 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -1,4 +1,5 @@ """Sensor for Suez Water Consumption data.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index feb68d76f6a..0c494b7a268 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -1,4 +1,5 @@ """Support for functionality to keep track of the sun.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/sun/config_flow.py b/homeassistant/components/sun/config_flow.py index fbeeaaea176..399373c8cb4 100644 --- a/homeassistant/components/sun/config_flow.py +++ b/homeassistant/components/sun/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Sun integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/sun/const.py b/homeassistant/components/sun/const.py index 245f8ca1d58..df7b0d43465 100644 --- a/homeassistant/components/sun/const.py +++ b/homeassistant/components/sun/const.py @@ -1,4 +1,5 @@ """Constants for the Sun integration.""" + from typing import Final DOMAIN: Final = "sun" diff --git a/homeassistant/components/sun/sensor.py b/homeassistant/components/sun/sensor.py index 2a21b9d0246..1abb1a6f23d 100644 --- a/homeassistant/components/sun/sensor.py +++ b/homeassistant/components/sun/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Sun integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/sun/trigger.py b/homeassistant/components/sun/trigger.py index 3cc3cabfbd3..7724816d636 100644 --- a/homeassistant/components/sun/trigger.py +++ b/homeassistant/components/sun/trigger.py @@ -1,4 +1,5 @@ """Offer sun based automation rules.""" + from datetime import timedelta import voluptuous as vol diff --git a/homeassistant/components/sunweg/config_flow.py b/homeassistant/components/sunweg/config_flow.py index c8a8c4e8b2f..c4af05a0cc9 100644 --- a/homeassistant/components/sunweg/config_flow.py +++ b/homeassistant/components/sunweg/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Sun WEG integration.""" + from sunweg.api import APIHelper import voluptuous as vol diff --git a/homeassistant/components/sunweg/const.py b/homeassistant/components/sunweg/const.py index e4b2b242abf..11d24352962 100644 --- a/homeassistant/components/sunweg/const.py +++ b/homeassistant/components/sunweg/const.py @@ -1,4 +1,5 @@ """Define constants for the Sun WEG component.""" + from enum import Enum from homeassistant.const import Platform diff --git a/homeassistant/components/sunweg/sensor.py b/homeassistant/components/sunweg/sensor.py index 42a3dc33d2b..004dd7276a7 100644 --- a/homeassistant/components/sunweg/sensor.py +++ b/homeassistant/components/sunweg/sensor.py @@ -1,4 +1,5 @@ """Read status of SunWEG inverters.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/sunweg/sensor_types/inverter.py b/homeassistant/components/sunweg/sensor_types/inverter.py index f406efb1a83..1010488b38a 100644 --- a/homeassistant/components/sunweg/sensor_types/inverter.py +++ b/homeassistant/components/sunweg/sensor_types/inverter.py @@ -1,4 +1,5 @@ """SunWEG Sensor definitions for the Inverter type.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/sunweg/sensor_types/phase.py b/homeassistant/components/sunweg/sensor_types/phase.py index ca6b9374e0d..d9db6c7c714 100644 --- a/homeassistant/components/sunweg/sensor_types/phase.py +++ b/homeassistant/components/sunweg/sensor_types/phase.py @@ -1,4 +1,5 @@ """SunWEG Sensor definitions for the Phase type.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass diff --git a/homeassistant/components/sunweg/sensor_types/sensor_entity_description.py b/homeassistant/components/sunweg/sensor_types/sensor_entity_description.py index a47818b694b..8c792ab617f 100644 --- a/homeassistant/components/sunweg/sensor_types/sensor_entity_description.py +++ b/homeassistant/components/sunweg/sensor_types/sensor_entity_description.py @@ -1,4 +1,5 @@ """Sensor Entity Description for the SunWEG integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/sunweg/sensor_types/string.py b/homeassistant/components/sunweg/sensor_types/string.py index d3ee0a43c21..ec59da5d20d 100644 --- a/homeassistant/components/sunweg/sensor_types/string.py +++ b/homeassistant/components/sunweg/sensor_types/string.py @@ -1,4 +1,5 @@ """SunWEG Sensor definitions for the String type.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass diff --git a/homeassistant/components/sunweg/sensor_types/total.py b/homeassistant/components/sunweg/sensor_types/total.py index ed9d6171735..5ae8be6dba3 100644 --- a/homeassistant/components/sunweg/sensor_types/total.py +++ b/homeassistant/components/sunweg/sensor_types/total.py @@ -1,4 +1,5 @@ """SunWEG Sensor definitions for Totals.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/supervisord/sensor.py b/homeassistant/components/supervisord/sensor.py index ce7efb11a3a..7939232cd6f 100644 --- a/homeassistant/components/supervisord/sensor.py +++ b/homeassistant/components/supervisord/sensor.py @@ -1,4 +1,5 @@ """Sensor for Supervisord process status.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/supla/__init__.py b/homeassistant/components/supla/__init__.py index 9652cae4aa4..46a3ec2b2c0 100644 --- a/homeassistant/components/supla/__init__.py +++ b/homeassistant/components/supla/__init__.py @@ -1,4 +1,5 @@ """Support for Supla devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/supla/cover.py b/homeassistant/components/supla/cover.py index 7f2857395b8..4cdee04b149 100644 --- a/homeassistant/components/supla/cover.py +++ b/homeassistant/components/supla/cover.py @@ -1,4 +1,5 @@ """Support for SUPLA covers - curtains, rollershutters, entry gate etc.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/supla/entity.py b/homeassistant/components/supla/entity.py index 244048973fa..fa257e39a06 100644 --- a/homeassistant/components/supla/entity.py +++ b/homeassistant/components/supla/entity.py @@ -1,4 +1,5 @@ """Base class for SUPLA channels.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/supla/switch.py b/homeassistant/components/supla/switch.py index d904455a3fe..5afcb9f08f6 100644 --- a/homeassistant/components/supla/switch.py +++ b/homeassistant/components/supla/switch.py @@ -1,4 +1,5 @@ """Support for SUPLA switch.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/surepetcare/__init__.py b/homeassistant/components/surepetcare/__init__.py index d4c337c4096..b9e2bb6a410 100644 --- a/homeassistant/components/surepetcare/__init__.py +++ b/homeassistant/components/surepetcare/__init__.py @@ -1,4 +1,5 @@ """The surepetcare integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 8cb750dc1d1..0c99985d514 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Sure PetCare Flaps/Pets binary sensors.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/surepetcare/config_flow.py b/homeassistant/components/surepetcare/config_flow.py index 53f820af3d9..dc11631de81 100644 --- a/homeassistant/components/surepetcare/config_flow.py +++ b/homeassistant/components/surepetcare/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Sure Petcare integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/surepetcare/entity.py b/homeassistant/components/surepetcare/entity.py index e6a44d5bfa9..400f6a80ac9 100644 --- a/homeassistant/components/surepetcare/entity.py +++ b/homeassistant/components/surepetcare/entity.py @@ -1,4 +1,5 @@ """Entity for Surepetcare.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/surepetcare/lock.py b/homeassistant/components/surepetcare/lock.py index 3161fa6e0bc..1f33457214e 100644 --- a/homeassistant/components/surepetcare/lock.py +++ b/homeassistant/components/surepetcare/lock.py @@ -1,4 +1,5 @@ """Support for Sure PetCare Flaps locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/surepetcare/sensor.py b/homeassistant/components/surepetcare/sensor.py index fbd228619cd..3618ac7d163 100644 --- a/homeassistant/components/surepetcare/sensor.py +++ b/homeassistant/components/surepetcare/sensor.py @@ -1,4 +1,5 @@ """Support for Sure PetCare Flaps/Pets sensors.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index d51ce18ada4..f8346d7368d 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -1,4 +1,5 @@ """Support for hydrological data from the Fed. Office for the Environment.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/swiss_public_transport/coordinator.py b/homeassistant/components/swiss_public_transport/coordinator.py index 97253d5776e..d24dc85e3dc 100644 --- a/homeassistant/components/swiss_public_transport/coordinator.py +++ b/homeassistant/components/swiss_public_transport/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the swiss_public_transport integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 9b351af6ed4..4bca9aade60 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -1,4 +1,5 @@ """Support for transport.opendata.ch.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/swisscom/device_tracker.py b/homeassistant/components/swisscom/device_tracker.py index 900117a54b7..cd393c79e09 100644 --- a/homeassistant/components/swisscom/device_tracker.py +++ b/homeassistant/components/swisscom/device_tracker.py @@ -1,4 +1,5 @@ """Support for Swisscom routers (Internet-Box).""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index ce9b1477ad6..86c67248eea 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -1,4 +1,5 @@ """Component to interface with switches that can be controlled remotely.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/switch/device_action.py b/homeassistant/components/switch/device_action.py index ce9f0a36117..bff4ce6e396 100644 --- a/homeassistant/components/switch/device_action.py +++ b/homeassistant/components/switch/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for switches.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/switch/device_condition.py b/homeassistant/components/switch/device_condition.py index 7f47983ba67..f3a6c299529 100644 --- a/homeassistant/components/switch/device_condition.py +++ b/homeassistant/components/switch/device_condition.py @@ -1,4 +1,5 @@ """Provides device conditions for switches.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/switch/device_trigger.py b/homeassistant/components/switch/device_trigger.py index 499b04bbaf3..6898a9954de 100644 --- a/homeassistant/components/switch/device_trigger.py +++ b/homeassistant/components/switch/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for switches.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index ffd345cea3b..25dc572b136 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -1,4 +1,5 @@ """Light support for switch entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switch/reproduce_state.py b/homeassistant/components/switch/reproduce_state.py index 0a6d0de9602..aaed39d39b8 100644 --- a/homeassistant/components/switch/reproduce_state.py +++ b/homeassistant/components/switch/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Switch state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/switch/significant_change.py b/homeassistant/components/switch/significant_change.py index 231085a3eef..ab7c6bc9281 100644 --- a/homeassistant/components/switch/significant_change.py +++ b/homeassistant/components/switch/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Switch state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switch_as_x/__init__.py b/homeassistant/components/switch_as_x/__init__.py index d94c7c9f098..23de494396e 100644 --- a/homeassistant/components/switch_as_x/__init__.py +++ b/homeassistant/components/switch_as_x/__init__.py @@ -1,4 +1,5 @@ """Component to wrap switch entities in entities of other domains.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switch_as_x/config_flow.py b/homeassistant/components/switch_as_x/config_flow.py index e40e247f105..37df3affbad 100644 --- a/homeassistant/components/switch_as_x/config_flow.py +++ b/homeassistant/components/switch_as_x/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Switch as X integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/switch_as_x/cover.py b/homeassistant/components/switch_as_x/cover.py index 37071ac6771..3db5d6aba81 100644 --- a/homeassistant/components/switch_as_x/cover.py +++ b/homeassistant/components/switch_as_x/cover.py @@ -1,4 +1,5 @@ """Cover support for switch entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switch_as_x/entity.py b/homeassistant/components/switch_as_x/entity.py index 39c2a8cab60..04b1deac7fc 100644 --- a/homeassistant/components/switch_as_x/entity.py +++ b/homeassistant/components/switch_as_x/entity.py @@ -1,4 +1,5 @@ """Base entity for the Switch as X integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switch_as_x/fan.py b/homeassistant/components/switch_as_x/fan.py index d8c43cfe381..fb795b4f54a 100644 --- a/homeassistant/components/switch_as_x/fan.py +++ b/homeassistant/components/switch_as_x/fan.py @@ -1,4 +1,5 @@ """Fan support for switch entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switch_as_x/light.py b/homeassistant/components/switch_as_x/light.py index e6183c95d91..59b816f7935 100644 --- a/homeassistant/components/switch_as_x/light.py +++ b/homeassistant/components/switch_as_x/light.py @@ -1,4 +1,5 @@ """Light support for switch entities.""" + from __future__ import annotations from homeassistant.components.light import ( diff --git a/homeassistant/components/switch_as_x/lock.py b/homeassistant/components/switch_as_x/lock.py index 528825c0300..de836904b66 100644 --- a/homeassistant/components/switch_as_x/lock.py +++ b/homeassistant/components/switch_as_x/lock.py @@ -1,4 +1,5 @@ """Lock support for switch entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switch_as_x/siren.py b/homeassistant/components/switch_as_x/siren.py index c9981b17cfe..7d9a41d9cd9 100644 --- a/homeassistant/components/switch_as_x/siren.py +++ b/homeassistant/components/switch_as_x/siren.py @@ -1,4 +1,5 @@ """Siren support for switch entities.""" + from __future__ import annotations from homeassistant.components.siren import ( diff --git a/homeassistant/components/switch_as_x/valve.py b/homeassistant/components/switch_as_x/valve.py index 971338764a5..cae2b4b2430 100644 --- a/homeassistant/components/switch_as_x/valve.py +++ b/homeassistant/components/switch_as_x/valve.py @@ -1,4 +1,5 @@ """Valve support for switch entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switchbee/config_flow.py b/homeassistant/components/switchbee/config_flow.py index 956142961aa..9b5139340b1 100644 --- a/homeassistant/components/switchbee/config_flow.py +++ b/homeassistant/components/switchbee/config_flow.py @@ -1,4 +1,5 @@ """Config flow for SwitchBee Smart Home integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switchbot/binary_sensor.py b/homeassistant/components/switchbot/binary_sensor.py index 7169f01b38f..92e00a65d8a 100644 --- a/homeassistant/components/switchbot/binary_sensor.py +++ b/homeassistant/components/switchbot/binary_sensor.py @@ -1,4 +1,5 @@ """Support for SwitchBot binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index 94261a1faae..06b95c6f8aa 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Switchbot.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switchbot/const.py b/homeassistant/components/switchbot/const.py index 0f7d1407fc5..9993bd95415 100644 --- a/homeassistant/components/switchbot/const.py +++ b/homeassistant/components/switchbot/const.py @@ -1,4 +1,5 @@ """Constants for the switchbot integration.""" + from enum import StrEnum from switchbot import SwitchbotModel diff --git a/homeassistant/components/switchbot/coordinator.py b/homeassistant/components/switchbot/coordinator.py index 29679605e8b..2c68b126fa5 100644 --- a/homeassistant/components/switchbot/coordinator.py +++ b/homeassistant/components/switchbot/coordinator.py @@ -1,4 +1,5 @@ """Provides the switchbot DataUpdateCoordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/switchbot/cover.py b/homeassistant/components/switchbot/cover.py index 4883bf456c0..8039ff8ec15 100644 --- a/homeassistant/components/switchbot/cover.py +++ b/homeassistant/components/switchbot/cover.py @@ -1,4 +1,5 @@ """Support for SwitchBot curtains.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switchbot/entity.py b/homeassistant/components/switchbot/entity.py index cf7f97a2692..bde69429bc3 100644 --- a/homeassistant/components/switchbot/entity.py +++ b/homeassistant/components/switchbot/entity.py @@ -1,4 +1,5 @@ """An abstract class common to all Switchbot entities.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/switchbot/humidifier.py b/homeassistant/components/switchbot/humidifier.py index 5b53b410208..3871fcb7265 100644 --- a/homeassistant/components/switchbot/humidifier.py +++ b/homeassistant/components/switchbot/humidifier.py @@ -1,4 +1,5 @@ """Support for Switchbot humidifier.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switchbot/light.py b/homeassistant/components/switchbot/light.py index 53b40bbf780..649a8b34c75 100644 --- a/homeassistant/components/switchbot/light.py +++ b/homeassistant/components/switchbot/light.py @@ -1,4 +1,5 @@ """Switchbot integration light platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switchbot/lock.py b/homeassistant/components/switchbot/lock.py index 60f4fe66c26..7b58a2f5ac3 100644 --- a/homeassistant/components/switchbot/lock.py +++ b/homeassistant/components/switchbot/lock.py @@ -1,4 +1,5 @@ """Support for SwitchBot lock platform.""" + from typing import Any import switchbot diff --git a/homeassistant/components/switchbot/sensor.py b/homeassistant/components/switchbot/sensor.py index a408bcb58bc..2a25d84aa8d 100644 --- a/homeassistant/components/switchbot/sensor.py +++ b/homeassistant/components/switchbot/sensor.py @@ -1,4 +1,5 @@ """Support for SwitchBot sensors.""" + from __future__ import annotations from homeassistant.components.bluetooth import async_last_service_info diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index f62e4d3f918..26ceee203aa 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -1,4 +1,5 @@ """Support for Switchbot bot.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switchbot_cloud/__init__.py b/homeassistant/components/switchbot_cloud/__init__.py index 8d3b2443b18..744d513f521 100644 --- a/homeassistant/components/switchbot_cloud/__init__.py +++ b/homeassistant/components/switchbot_cloud/__init__.py @@ -1,4 +1,5 @@ """The SwitchBot via API integration.""" + from asyncio import gather from dataclasses import dataclass, field from logging import getLogger diff --git a/homeassistant/components/switchbot_cloud/const.py b/homeassistant/components/switchbot_cloud/const.py index ef69c9c1d02..b90a2f3a2ec 100644 --- a/homeassistant/components/switchbot_cloud/const.py +++ b/homeassistant/components/switchbot_cloud/const.py @@ -1,4 +1,5 @@ """Constants for the SwitchBot Cloud integration.""" + from datetime import timedelta from typing import Final diff --git a/homeassistant/components/switchbot_cloud/coordinator.py b/homeassistant/components/switchbot_cloud/coordinator.py index 92099ccde43..4c12e03a6f2 100644 --- a/homeassistant/components/switchbot_cloud/coordinator.py +++ b/homeassistant/components/switchbot_cloud/coordinator.py @@ -1,4 +1,5 @@ """SwitchBot Cloud coordinator.""" + from asyncio import timeout from logging import getLogger from typing import Any diff --git a/homeassistant/components/switchbot_cloud/entity.py b/homeassistant/components/switchbot_cloud/entity.py index 5d0e2ff09c3..7bb00cda945 100644 --- a/homeassistant/components/switchbot_cloud/entity.py +++ b/homeassistant/components/switchbot_cloud/entity.py @@ -1,4 +1,5 @@ """Base class for SwitchBot via API entities.""" + from typing import Any from switchbot_api import Commands, Device, Remote, SwitchBotAPI diff --git a/homeassistant/components/switchbot_cloud/switch.py b/homeassistant/components/switchbot_cloud/switch.py index 4f2cdc22ba9..fbcd4430f6e 100644 --- a/homeassistant/components/switchbot_cloud/switch.py +++ b/homeassistant/components/switchbot_cloud/switch.py @@ -1,4 +1,5 @@ """Support for SwitchBot switch.""" + from typing import Any from switchbot_api import CommonCommands, Device, PowerState, Remote, SwitchBotAPI diff --git a/homeassistant/components/switcher_kis/__init__.py b/homeassistant/components/switcher_kis/__init__.py index 79ef201efee..14aeea09626 100644 --- a/homeassistant/components/switcher_kis/__init__.py +++ b/homeassistant/components/switcher_kis/__init__.py @@ -1,4 +1,5 @@ """The Switcher integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/switcher_kis/button.py b/homeassistant/components/switcher_kis/button.py index e9a1d1a2a89..093077ed818 100644 --- a/homeassistant/components/switcher_kis/button.py +++ b/homeassistant/components/switcher_kis/button.py @@ -1,4 +1,5 @@ """Switcher integration Button platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/switcher_kis/climate.py b/homeassistant/components/switcher_kis/climate.py index 180b71b1fe6..caf46ca8975 100644 --- a/homeassistant/components/switcher_kis/climate.py +++ b/homeassistant/components/switcher_kis/climate.py @@ -1,4 +1,5 @@ """Switcher integration Climate platform.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/switcher_kis/config_flow.py b/homeassistant/components/switcher_kis/config_flow.py index f459e6e8d76..bd24481ce3f 100644 --- a/homeassistant/components/switcher_kis/config_flow.py +++ b/homeassistant/components/switcher_kis/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Switcher integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/switcher_kis/cover.py b/homeassistant/components/switcher_kis/cover.py index 4d81480e136..88a3b5050d9 100644 --- a/homeassistant/components/switcher_kis/cover.py +++ b/homeassistant/components/switcher_kis/cover.py @@ -1,4 +1,5 @@ """Switcher integration Cover platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/switcher_kis/diagnostics.py b/homeassistant/components/switcher_kis/diagnostics.py index 765a3dde9e7..441f45198a2 100644 --- a/homeassistant/components/switcher_kis/diagnostics.py +++ b/homeassistant/components/switcher_kis/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Switcher.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/switcher_kis/sensor.py b/homeassistant/components/switcher_kis/sensor.py index 09a8383f5c8..88da03fecea 100644 --- a/homeassistant/components/switcher_kis/sensor.py +++ b/homeassistant/components/switcher_kis/sensor.py @@ -1,4 +1,5 @@ """Switcher integration Sensor platform.""" + from __future__ import annotations from aioswitcher.device import DeviceCategory diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index c24157f70fc..ba5291d6ec0 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -1,4 +1,5 @@ """Switcher integration Switch platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/switcher_kis/utils.py b/homeassistant/components/switcher_kis/utils.py index ad0414ae806..d95c1122732 100644 --- a/homeassistant/components/switcher_kis/utils.py +++ b/homeassistant/components/switcher_kis/utils.py @@ -1,4 +1,5 @@ """Switcher integration helpers functions.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/switchmate/switch.py b/homeassistant/components/switchmate/switch.py index f88da064af6..ee8b65b47e2 100644 --- a/homeassistant/components/switchmate/switch.py +++ b/homeassistant/components/switchmate/switch.py @@ -1,4 +1,5 @@ """Support for Switchmate.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/syncthing/const.py b/homeassistant/components/syncthing/const.py index 0e5b2714be8..e1e78b0e778 100644 --- a/homeassistant/components/syncthing/const.py +++ b/homeassistant/components/syncthing/const.py @@ -1,4 +1,5 @@ """Constants for the syncthing integration.""" + from datetime import timedelta DOMAIN = "syncthing" diff --git a/homeassistant/components/syncthru/__init__.py b/homeassistant/components/syncthru/__init__.py index 8d17f038819..5ad4a85cc09 100644 --- a/homeassistant/components/syncthru/__init__.py +++ b/homeassistant/components/syncthru/__init__.py @@ -1,4 +1,5 @@ """The syncthru component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/syncthru/binary_sensor.py b/homeassistant/components/syncthru/binary_sensor.py index f5e23ea25ad..2b110c2af1d 100644 --- a/homeassistant/components/syncthru/binary_sensor.py +++ b/homeassistant/components/syncthru/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Samsung Printers with SyncThru web interface.""" + from __future__ import annotations from pysyncthru import SyncThru, SyncthruState diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index f651556bddb..703c283182b 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -1,4 +1,5 @@ """Support for Samsung Printers with SyncThru web interface.""" + from __future__ import annotations from pysyncthru import SyncThru, SyncthruState diff --git a/homeassistant/components/synology_chat/notify.py b/homeassistant/components/synology_chat/notify.py index ca8fba53120..a36f073b8bb 100644 --- a/homeassistant/components/synology_chat/notify.py +++ b/homeassistant/components/synology_chat/notify.py @@ -1,4 +1,5 @@ """SynologyChat platform for notify component.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index ecda3addcb5..853459b00aa 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -1,4 +1,5 @@ """The Synology DSM component.""" + from __future__ import annotations from itertools import chain diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 27c6b416cb4..3c60b99c39f 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Synology DSM binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_dsm/button.py b/homeassistant/components/synology_dsm/button.py index 0e737c48eb6..e74aa034aa1 100644 --- a/homeassistant/components/synology_dsm/button.py +++ b/homeassistant/components/synology_dsm/button.py @@ -1,4 +1,5 @@ """Support for Synology DSM buttons.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index 187db9fbba8..6289c816740 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -1,4 +1,5 @@ """Support for Synology DSM cameras.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index b5a2c7bfad5..4bb52383148 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -1,4 +1,5 @@ """The Synology DSM component.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 4da188732ad..c77b8196faf 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Synology DSM integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index c5c9e590684..140e07e975b 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -1,4 +1,5 @@ """Constants for Synology DSM.""" + from __future__ import annotations from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED diff --git a/homeassistant/components/synology_dsm/coordinator.py b/homeassistant/components/synology_dsm/coordinator.py index 9d0ccfd86d2..bc896b1ad45 100644 --- a/homeassistant/components/synology_dsm/coordinator.py +++ b/homeassistant/components/synology_dsm/coordinator.py @@ -1,4 +1,5 @@ """synology_dsm coordinators.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/synology_dsm/diagnostics.py b/homeassistant/components/synology_dsm/diagnostics.py index 30af7f94282..d9b4131b078 100644 --- a/homeassistant/components/synology_dsm/diagnostics.py +++ b/homeassistant/components/synology_dsm/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Synology DSM.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/synology_dsm/entity.py b/homeassistant/components/synology_dsm/entity.py index 8d53284fee7..0c91d7914f3 100644 --- a/homeassistant/components/synology_dsm/entity.py +++ b/homeassistant/components/synology_dsm/entity.py @@ -1,4 +1,5 @@ """Entities for Synology DSM.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_dsm/media_source.py b/homeassistant/components/synology_dsm/media_source.py index 3f30fe9b4e9..69c87f4d0a1 100644 --- a/homeassistant/components/synology_dsm/media_source.py +++ b/homeassistant/components/synology_dsm/media_source.py @@ -1,4 +1,5 @@ """Expose Synology DSM as a media source.""" + from __future__ import annotations import mimetypes diff --git a/homeassistant/components/synology_dsm/models.py b/homeassistant/components/synology_dsm/models.py index 8c4341a2d37..4f51d329ded 100644 --- a/homeassistant/components/synology_dsm/models.py +++ b/homeassistant/components/synology_dsm/models.py @@ -1,4 +1,5 @@ """The synology_dsm integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 2e88690fdc6..7e2f2582afb 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -1,4 +1,5 @@ """Support for Synology DSM sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_dsm/service.py b/homeassistant/components/synology_dsm/service.py index 9797b808617..366f7d4ba3a 100644 --- a/homeassistant/components/synology_dsm/service.py +++ b/homeassistant/components/synology_dsm/service.py @@ -1,4 +1,5 @@ """The Synology DSM component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 87700e9300d..d5845e3affe 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -1,4 +1,5 @@ """Support for Synology DSM switch.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_dsm/update.py b/homeassistant/components/synology_dsm/update.py index c66fc3c3d73..6505b116b73 100644 --- a/homeassistant/components/synology_dsm/update.py +++ b/homeassistant/components/synology_dsm/update.py @@ -1,4 +1,5 @@ """Support for Synology DSM update platform.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/synology_srm/device_tracker.py b/homeassistant/components/synology_srm/device_tracker.py index e67f7ecf34e..7c7343e88f6 100644 --- a/homeassistant/components/synology_srm/device_tracker.py +++ b/homeassistant/components/synology_srm/device_tracker.py @@ -1,4 +1,5 @@ """Device tracker for Synology SRM routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/syslog/notify.py b/homeassistant/components/syslog/notify.py index d4772e95259..b16d44fb504 100644 --- a/homeassistant/components/syslog/notify.py +++ b/homeassistant/components/syslog/notify.py @@ -1,4 +1,5 @@ """Syslog notification service.""" + from __future__ import annotations import syslog diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index 3683834f184..f68bd97732d 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -1,4 +1,5 @@ """The System Bridge integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/system_bridge/binary_sensor.py b/homeassistant/components/system_bridge/binary_sensor.py index 7c2607e3506..36de4c72c3d 100644 --- a/homeassistant/components/system_bridge/binary_sensor.py +++ b/homeassistant/components/system_bridge/binary_sensor.py @@ -1,4 +1,5 @@ """Support for System Bridge binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/system_bridge/config_flow.py b/homeassistant/components/system_bridge/config_flow.py index 1406088f3d1..ff24a2c730f 100644 --- a/homeassistant/components/system_bridge/config_flow.py +++ b/homeassistant/components/system_bridge/config_flow.py @@ -1,4 +1,5 @@ """Config flow for System Bridge integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/system_bridge/coordinator.py b/homeassistant/components/system_bridge/coordinator.py index df0a39a877d..f810c69a873 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for System Bridge.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/system_bridge/data.py b/homeassistant/components/system_bridge/data.py index fc7d119a324..f07e8d75f28 100644 --- a/homeassistant/components/system_bridge/data.py +++ b/homeassistant/components/system_bridge/data.py @@ -1,4 +1,5 @@ """System Bridge integration data.""" + from dataclasses import dataclass, field from systembridgemodels.modules import ( diff --git a/homeassistant/components/system_bridge/entity.py b/homeassistant/components/system_bridge/entity.py index 72a6fc93977..b37e55cf406 100644 --- a/homeassistant/components/system_bridge/entity.py +++ b/homeassistant/components/system_bridge/entity.py @@ -1,4 +1,5 @@ """Base entity for the system bridge integration.""" + from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/system_bridge/media_player.py b/homeassistant/components/system_bridge/media_player.py index 79fa3dc8c93..d467110e06d 100644 --- a/homeassistant/components/system_bridge/media_player.py +++ b/homeassistant/components/system_bridge/media_player.py @@ -1,4 +1,5 @@ """Support for System Bridge media players.""" + from __future__ import annotations import datetime as dt diff --git a/homeassistant/components/system_bridge/media_source.py b/homeassistant/components/system_bridge/media_source.py index be15542bcc3..b9a7da18386 100644 --- a/homeassistant/components/system_bridge/media_source.py +++ b/homeassistant/components/system_bridge/media_source.py @@ -1,4 +1,5 @@ """System Bridge Media Source Implementation.""" + from __future__ import annotations from systembridgemodels.media_directories import MediaDirectory diff --git a/homeassistant/components/system_bridge/notify.py b/homeassistant/components/system_bridge/notify.py index 03665cf3575..0e2f058cc7c 100644 --- a/homeassistant/components/system_bridge/notify.py +++ b/homeassistant/components/system_bridge/notify.py @@ -1,4 +1,5 @@ """Support for System Bridge notification service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/system_bridge/sensor.py b/homeassistant/components/system_bridge/sensor.py index c7a5eac391c..56cd9bcb13f 100644 --- a/homeassistant/components/system_bridge/sensor.py +++ b/homeassistant/components/system_bridge/sensor.py @@ -1,4 +1,5 @@ """Support for System Bridge sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/system_bridge/update.py b/homeassistant/components/system_bridge/update.py index 5f667fad30d..b0d341cee3b 100644 --- a/homeassistant/components/system_bridge/update.py +++ b/homeassistant/components/system_bridge/update.py @@ -1,4 +1,5 @@ """Support for System Bridge updates.""" + from __future__ import annotations from homeassistant.components.update import UpdateEntity diff --git a/homeassistant/components/system_health/__init__.py b/homeassistant/components/system_health/__init__.py index 7c4d0f9ac46..6a1e4830443 100644 --- a/homeassistant/components/system_health/__init__.py +++ b/homeassistant/components/system_health/__init__.py @@ -1,4 +1,5 @@ """Support for System health .""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 8b158041cdc..8a8e8486f93 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -1,4 +1,5 @@ """Support for system log.""" + from __future__ import annotations from collections import OrderedDict, deque diff --git a/homeassistant/components/systemmonitor/config_flow.py b/homeassistant/components/systemmonitor/config_flow.py index 8dcc074a0d0..924f63c8d1c 100644 --- a/homeassistant/components/systemmonitor/config_flow.py +++ b/homeassistant/components/systemmonitor/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for System Monitor.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/systemmonitor/diagnostics.py b/homeassistant/components/systemmonitor/diagnostics.py index c55869eac82..317758651d7 100644 --- a/homeassistant/components/systemmonitor/diagnostics.py +++ b/homeassistant/components/systemmonitor/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Sensibo.""" + from __future__ import annotations from typing import Any From 87165c0d6e7a40fa60233fa778f40c26b1537a01 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:44:56 +0100 Subject: [PATCH 0542/1691] Add empty line after module docstring [tests t-z] (#112712) --- tests/components/tado/test_binary_sensor.py | 1 + tests/components/tado/test_climate.py | 1 + tests/components/tado/test_config_flow.py | 1 + tests/components/tado/test_sensor.py | 1 + tests/components/tado/test_water_heater.py | 1 + tests/components/tailscale/conftest.py | 1 + tests/components/tailscale/test_binary_sensor.py | 1 + tests/components/tailscale/test_diagnostics.py | 1 + tests/components/tailscale/test_init.py | 1 + tests/components/tailscale/test_sensor.py | 1 + tests/components/tailwind/conftest.py | 1 + tests/components/tailwind/test_button.py | 1 + tests/components/tailwind/test_config_flow.py | 1 + tests/components/tailwind/test_cover.py | 1 + tests/components/tailwind/test_init.py | 1 + tests/components/tailwind/test_number.py | 1 + tests/components/tankerkoenig/conftest.py | 1 + tests/components/tankerkoenig/test_config_flow.py | 1 + tests/components/tankerkoenig/test_coordinator.py | 1 + tests/components/tankerkoenig/test_diagnostics.py | 1 + tests/components/tasmota/conftest.py | 1 + tests/components/tasmota/test_config_flow.py | 1 + tests/components/tautulli/test_config_flow.py | 1 + tests/components/tcp/test_binary_sensor.py | 1 + tests/components/tcp/test_sensor.py | 1 + tests/components/technove/conftest.py | 1 + tests/components/technove/test_binary_sensor.py | 1 + tests/components/technove/test_sensor.py | 1 + tests/components/technove/test_switch.py | 1 + tests/components/tedee/conftest.py | 1 + tests/components/tedee/test_config_flow.py | 1 + tests/components/tedee/test_diagnostics.py | 1 + tests/components/tedee/test_init.py | 1 + tests/components/tedee/test_lock.py | 1 + tests/components/telegram/test_notify.py | 1 + tests/components/telegram_bot/conftest.py | 1 + tests/components/telegram_bot/test_broadcast.py | 1 + tests/components/telegram_bot/test_telegram_bot.py | 1 + tests/components/tellduslive/test_config_flow.py | 1 + tests/components/temper/test_sensor.py | 1 + tests/components/template/test_binary_sensor.py | 1 + tests/components/template/test_config_flow.py | 1 + tests/components/template/test_cover.py | 1 + tests/components/template/test_image.py | 1 + tests/components/template/test_init.py | 1 + tests/components/template/test_number.py | 1 + tests/components/template/test_select.py | 1 + tests/components/template/test_sensor.py | 1 + tests/components/template/test_trigger.py | 1 + tests/components/template/test_weather.py | 1 + tests/components/tesla_wall_connector/conftest.py | 1 + tests/components/tesla_wall_connector/test_binary_sensor.py | 1 + tests/components/tesla_wall_connector/test_config_flow.py | 1 + tests/components/tesla_wall_connector/test_init.py | 1 + tests/components/tesla_wall_connector/test_sensor.py | 1 + tests/components/teslemetry/conftest.py | 1 + tests/components/teslemetry/test_sensor.py | 1 + tests/components/tessie/conftest.py | 1 + tests/components/tessie/test_button.py | 1 + tests/components/tessie/test_climate.py | 1 + tests/components/tessie/test_coordinator.py | 1 + tests/components/tessie/test_cover.py | 1 + tests/components/tessie/test_select.py | 1 + tests/components/tessie/test_sensor.py | 1 + tests/components/tessie/test_switch.py | 1 + tests/components/tessie/test_update.py | 1 + tests/components/text/test_init.py | 1 + tests/components/text/test_recorder.py | 1 + tests/components/thermobeacon/test_config_flow.py | 1 + tests/components/thermobeacon/test_sensor.py | 1 + tests/components/thermopro/test_config_flow.py | 1 + tests/components/thermopro/test_sensor.py | 1 + tests/components/thread/test_config_flow.py | 1 + tests/components/threshold/test_config_flow.py | 1 + tests/components/tibber/test_config_flow.py | 1 + tests/components/tibber/test_diagnostics.py | 1 + tests/components/tibber/test_statistics.py | 1 + tests/components/tile/test_config_flow.py | 1 + tests/components/tile/test_diagnostics.py | 1 + tests/components/tilt_ble/test_config_flow.py | 1 + tests/components/time/test_init.py | 1 + tests/components/time_date/conftest.py | 1 + tests/components/time_date/test_config_flow.py | 1 + tests/components/timer/test_init.py | 1 + tests/components/tod/test_binary_sensor.py | 1 + tests/components/tod/test_config_flow.py | 1 + tests/components/todoist/conftest.py | 1 + tests/components/todoist/test_calendar.py | 1 + tests/components/todoist/test_init.py | 1 + tests/components/todoist/test_todo.py | 1 + tests/components/tolo/test_config_flow.py | 1 + tests/components/tomato/test_device_tracker.py | 1 + tests/components/tomorrowio/const.py | 1 + tests/components/tomorrowio/test_config_flow.py | 1 + tests/components/tomorrowio/test_init.py | 1 + tests/components/tomorrowio/test_sensor.py | 1 + tests/components/tomorrowio/test_weather.py | 1 + tests/components/toon/test_config_flow.py | 1 + tests/components/totalconnect/common.py | 1 + tests/components/totalconnect/test_alarm_control_panel.py | 1 + tests/components/totalconnect/test_binary_sensor.py | 1 + tests/components/totalconnect/test_config_flow.py | 1 + tests/components/totalconnect/test_diagnostics.py | 1 + tests/components/totalconnect/test_init.py | 1 + tests/components/tplink/test_config_flow.py | 1 + tests/components/tplink/test_init.py | 1 + tests/components/tplink/test_light.py | 1 + tests/components/tplink_omada/conftest.py | 1 + tests/components/tplink_omada/test_config_flow.py | 1 + tests/components/tplink_omada/test_switch.py | 1 + tests/components/traccar/test_init.py | 1 + tests/components/traccar_server/common.py | 1 + tests/components/traccar_server/conftest.py | 1 + tests/components/traccar_server/test_config_flow.py | 1 + tests/components/traccar_server/test_diagnostics.py | 1 + tests/components/tractive/test_config_flow.py | 1 + tests/components/tradfri/common.py | 1 + tests/components/tradfri/conftest.py | 1 + tests/components/tradfri/test_config_flow.py | 1 + tests/components/tradfri/test_cover.py | 1 + tests/components/tradfri/test_diagnostics.py | 1 + tests/components/tradfri/test_fan.py | 1 + tests/components/tradfri/test_init.py | 1 + tests/components/tradfri/test_light.py | 1 + tests/components/tradfri/test_sensor.py | 1 + tests/components/tradfri/test_switch.py | 1 + tests/components/trafikverket_camera/conftest.py | 1 + tests/components/trafikverket_camera/test_binary_sensor.py | 1 + tests/components/trafikverket_camera/test_camera.py | 1 + tests/components/trafikverket_camera/test_config_flow.py | 1 + tests/components/trafikverket_camera/test_coordinator.py | 1 + tests/components/trafikverket_camera/test_init.py | 1 + tests/components/trafikverket_camera/test_recorder.py | 1 + tests/components/trafikverket_camera/test_sensor.py | 1 + tests/components/trafikverket_ferry/conftest.py | 1 + tests/components/trafikverket_ferry/test_config_flow.py | 1 + tests/components/trafikverket_ferry/test_coordinator.py | 1 + tests/components/trafikverket_ferry/test_init.py | 1 + tests/components/trafikverket_ferry/test_sensor.py | 1 + tests/components/trafikverket_train/conftest.py | 1 + tests/components/trafikverket_train/test_config_flow.py | 1 + tests/components/trafikverket_train/test_init.py | 1 + tests/components/trafikverket_train/test_sensor.py | 1 + tests/components/trafikverket_train/test_util.py | 1 + tests/components/trafikverket_weatherstation/test_config_flow.py | 1 + tests/components/transmission/test_config_flow.py | 1 + tests/components/transport_nsw/test_sensor.py | 1 + tests/components/trend/conftest.py | 1 + tests/components/trend/test_binary_sensor.py | 1 + tests/components/trend/test_config_flow.py | 1 + tests/components/tts/common.py | 1 + tests/components/tts/conftest.py | 1 + tests/components/tts/test_legacy.py | 1 + tests/components/tts/test_media_source.py | 1 + tests/components/tts/test_notify.py | 1 + tests/components/tuya/conftest.py | 1 + tests/components/tuya/test_config_flow.py | 1 + tests/components/twentemilieu/conftest.py | 1 + tests/components/twentemilieu/test_calendar.py | 1 + tests/components/twentemilieu/test_config_flow.py | 1 + tests/components/twentemilieu/test_diagnostics.py | 1 + tests/components/twentemilieu/test_init.py | 1 + tests/components/twilio/test_init.py | 1 + tests/components/twinkly/conftest.py | 1 + tests/components/twinkly/test_config_flow.py | 1 + tests/components/twinkly/test_diagnostics.py | 1 + tests/components/twinkly/test_light.py | 1 + tests/components/twitch/conftest.py | 1 + tests/components/twitch/test_config_flow.py | 1 + tests/components/twitch/test_sensor.py | 1 + tests/components/ukraine_alarm/test_config_flow.py | 1 + tests/components/unifi/conftest.py | 1 + tests/components/unifi/test_device_tracker.py | 1 + tests/components/unifi/test_diagnostics.py | 1 + tests/components/unifi/test_hub.py | 1 + tests/components/unifi/test_init.py | 1 + tests/components/unifi/test_sensor.py | 1 + tests/components/unifi/test_services.py | 1 + tests/components/unifi/test_switch.py | 1 + tests/components/unifi/test_update.py | 1 + tests/components/unifiprotect/test_config_flow.py | 1 + tests/components/unifiprotect/test_diagnostics.py | 1 + tests/components/unifiprotect/test_recorder.py | 1 + tests/components/unifiprotect/test_repairs.py | 1 + tests/components/unifiprotect/test_sensor.py | 1 + tests/components/universal/test_media_player.py | 1 + tests/components/upb/test_config_flow.py | 1 + tests/components/upcloud/test_config_flow.py | 1 + tests/components/update/test_device_trigger.py | 1 + tests/components/update/test_init.py | 1 + tests/components/update/test_recorder.py | 1 + tests/components/update/test_significant_change.py | 1 + tests/components/upnp/conftest.py | 1 + tests/components/upnp/test_init.py | 1 + tests/components/uptime/conftest.py | 1 + tests/components/uptime/test_init.py | 1 + tests/components/uptimerobot/common.py | 1 + tests/components/uptimerobot/test_config_flow.py | 1 + tests/components/uptimerobot/test_init.py | 1 + tests/components/uptimerobot/test_switch.py | 1 + tests/components/utility_meter/test_config_flow.py | 1 + tests/components/utility_meter/test_init.py | 1 + tests/components/utility_meter/test_sensor.py | 1 + tests/components/uvc/test_camera.py | 1 + tests/components/v2c/conftest.py | 1 + tests/components/v2c/test_config_flow.py | 1 + tests/components/vacuum/common.py | 1 + tests/components/vacuum/test_device_trigger.py | 1 + tests/components/vacuum/test_init.py | 1 + tests/components/vacuum/test_recorder.py | 1 + tests/components/vallox/test_binary_sensor.py | 1 + tests/components/vallox/test_config_flow.py | 1 + tests/components/vallox/test_fan.py | 1 + tests/components/valve/test_init.py | 1 + tests/components/velbus/conftest.py | 1 + tests/components/velbus/test_config_flow.py | 1 + tests/components/velbus/test_init.py | 1 + tests/components/velux/conftest.py | 1 + tests/components/velux/test_config_flow.py | 1 + tests/components/venstar/test_climate.py | 1 + tests/components/venstar/test_init.py | 1 + tests/components/vera/common.py | 1 + tests/components/vera/conftest.py | 1 + tests/components/vera/test_binary_sensor.py | 1 + tests/components/vera/test_climate.py | 1 + tests/components/vera/test_common.py | 1 + tests/components/vera/test_config_flow.py | 1 + tests/components/vera/test_cover.py | 1 + tests/components/vera/test_init.py | 1 + tests/components/vera/test_light.py | 1 + tests/components/vera/test_lock.py | 1 + tests/components/vera/test_scene.py | 1 + tests/components/vera/test_sensor.py | 1 + tests/components/vera/test_switch.py | 1 + tests/components/verisure/conftest.py | 1 + tests/components/verisure/test_config_flow.py | 1 + tests/components/version/common.py | 1 + tests/components/version/test_binary_sensor.py | 1 + tests/components/version/test_config_flow.py | 1 + tests/components/version/test_sensor.py | 1 + tests/components/vesync/conftest.py | 1 + tests/components/vesync/test_config_flow.py | 1 + tests/components/vesync/test_diagnostics.py | 1 + tests/components/vesync/test_init.py | 1 + tests/components/vicare/conftest.py | 1 + tests/components/vicare/test_config_flow.py | 1 + tests/components/vilfo/test_config_flow.py | 1 + tests/components/vizio/conftest.py | 1 + tests/components/vizio/const.py | 1 + tests/components/vizio/test_init.py | 1 + tests/components/vizio/test_media_player.py | 1 + tests/components/vlc_telnet/test_config_flow.py | 1 + tests/components/vodafone_station/const.py | 1 + tests/components/vodafone_station/test_config_flow.py | 1 + tests/components/voicerss/test_tts.py | 1 + tests/components/voip/test_binary_sensor.py | 1 + tests/components/voip/test_config_flow.py | 1 + tests/components/voip/test_devices.py | 1 + tests/components/voip/test_init.py | 1 + tests/components/voip/test_select.py | 1 + tests/components/voip/test_switch.py | 1 + tests/components/volumio/test_config_flow.py | 1 + tests/components/volvooncall/test_config_flow.py | 1 + tests/components/vultr/test_init.py | 1 + tests/components/vultr/test_switch.py | 1 + tests/components/wake_on_lan/conftest.py | 1 + tests/components/wake_on_lan/test_init.py | 1 + tests/components/wake_on_lan/test_switch.py | 1 + tests/components/wake_word/common.py | 1 + tests/components/wallbox/test_config_flow.py | 1 + tests/components/wallbox/test_sensor.py | 1 + tests/components/waqi/conftest.py | 1 + tests/components/water_heater/common.py | 1 + tests/components/water_heater/conftest.py | 1 + tests/components/water_heater/test_init.py | 1 + tests/components/water_heater/test_recorder.py | 1 + tests/components/watttime/test_config_flow.py | 1 + tests/components/watttime/test_diagnostics.py | 1 + tests/components/waze_travel_time/conftest.py | 1 + tests/components/weather/conftest.py | 1 + tests/components/weather/test_init.py | 1 + tests/components/weather/test_intent.py | 1 + tests/components/weather/test_recorder.py | 1 + tests/components/weather/test_websocket_api.py | 1 + tests/components/weatherflow_cloud/conftest.py | 1 + tests/components/weatherkit/conftest.py | 1 + tests/components/weatherkit/test_config_flow.py | 1 + tests/components/weatherkit/test_coordinator.py | 1 + tests/components/weatherkit/test_setup.py | 1 + tests/components/webhook/test_init.py | 1 + tests/components/webhook/test_trigger.py | 1 + tests/components/webmin/conftest.py | 1 + tests/components/webmin/test_config_flow.py | 1 + tests/components/webmin/test_init.py | 1 + tests/components/webostv/conftest.py | 1 + tests/components/webostv/const.py | 1 + tests/components/webostv/test_diagnostics.py | 1 + tests/components/webostv/test_init.py | 1 + tests/components/webostv/test_media_player.py | 1 + tests/components/webostv/test_notify.py | 1 + tests/components/webostv/test_trigger.py | 1 + tests/components/websocket_api/test_auth.py | 1 + tests/components/websocket_api/test_decorators.py | 1 + tests/components/websocket_api/test_init.py | 1 + tests/components/websocket_api/test_sensor.py | 1 + tests/components/wemo/test_light_bridge.py | 1 + tests/components/whirlpool/conftest.py | 1 + tests/components/whirlpool/test_climate.py | 1 + tests/components/whirlpool/test_config_flow.py | 1 + tests/components/whirlpool/test_init.py | 1 + tests/components/whirlpool/test_sensor.py | 1 + tests/components/whois/conftest.py | 1 + tests/components/whois/test_config_flow.py | 1 + tests/components/whois/test_diagnostics.py | 1 + tests/components/whois/test_init.py | 1 + tests/components/whois/test_sensor.py | 1 + tests/components/wiffi/conftest.py | 1 + tests/components/wilight/conftest.py | 1 + tests/components/wilight/test_cover.py | 1 + tests/components/wilight/test_fan.py | 1 + tests/components/wilight/test_init.py | 1 + tests/components/wilight/test_light.py | 1 + tests/components/wilight/test_switch.py | 1 + tests/components/withings/conftest.py | 1 + tests/components/withings/test_binary_sensor.py | 1 + tests/components/withings/test_calendar.py | 1 + tests/components/withings/test_config_flow.py | 1 + tests/components/withings/test_diagnostics.py | 1 + tests/components/withings/test_init.py | 1 + tests/components/withings/test_sensor.py | 1 + tests/components/wiz/test_config_flow.py | 1 + tests/components/wiz/test_diagnostics.py | 1 + tests/components/wled/conftest.py | 1 + tests/components/wled/test_button.py | 1 + tests/components/wled/test_config_flow.py | 1 + tests/components/wled/test_diagnostics.py | 1 + tests/components/wled/test_sensor.py | 1 + tests/components/wled/test_update.py | 1 + tests/components/wolflink/test_config_flow.py | 1 + tests/components/workday/conftest.py | 1 + tests/components/workday/test_binary_sensor.py | 1 + tests/components/workday/test_config_flow.py | 1 + tests/components/workday/test_init.py | 1 + tests/components/ws66i/test_config_flow.py | 1 + tests/components/ws66i/test_init.py | 1 + tests/components/ws66i/test_media_player.py | 1 + tests/components/wsdot/test_sensor.py | 1 + tests/components/wyoming/conftest.py | 1 + tests/components/wyoming/test_binary_sensor.py | 1 + tests/components/wyoming/test_config_flow.py | 1 + tests/components/wyoming/test_devices.py | 1 + tests/components/wyoming/test_init.py | 1 + tests/components/wyoming/test_number.py | 1 + tests/components/wyoming/test_select.py | 1 + tests/components/wyoming/test_stt.py | 1 + tests/components/wyoming/test_switch.py | 1 + tests/components/wyoming/test_tts.py | 1 + tests/components/xbox/test_config_flow.py | 1 + tests/components/xiaomi/test_device_tracker.py | 1 + tests/components/xiaomi_aqara/test_config_flow.py | 1 + tests/components/xiaomi_ble/test_config_flow.py | 1 + tests/components/xiaomi_ble/test_sensor.py | 1 + tests/components/xiaomi_miio/test_button.py | 1 + tests/components/xiaomi_miio/test_config_flow.py | 1 + tests/components/xiaomi_miio/test_vacuum.py | 1 + tests/components/yale_smart_alarm/conftest.py | 1 + tests/components/yale_smart_alarm/test_config_flow.py | 1 + tests/components/yale_smart_alarm/test_coordinator.py | 1 + tests/components/yale_smart_alarm/test_diagnostics.py | 1 + tests/components/yamaha/test_media_player.py | 1 + tests/components/yamaha_musiccast/test_config_flow.py | 1 + tests/components/yandextts/test_tts.py | 1 + tests/components/yardian/conftest.py | 1 + tests/components/yardian/test_config_flow.py | 1 + tests/components/yeelight/test_binary_sensor.py | 1 + tests/components/yeelight/test_config_flow.py | 1 + tests/components/yeelight/test_init.py | 1 + tests/components/yeelight/test_light.py | 1 + tests/components/yolink/test_config_flow.py | 1 + tests/components/youless/test_config_flows.py | 1 + tests/components/youtube/conftest.py | 1 + tests/components/youtube/test_config_flow.py | 1 + tests/components/youtube/test_diagnostics.py | 1 + tests/components/youtube/test_sensor.py | 1 + tests/components/zamg/conftest.py | 1 + tests/components/zamg/test_config_flow.py | 1 + tests/components/zamg/test_init.py | 1 + tests/components/zeroconf/test_init.py | 1 + tests/components/zeroconf/test_usage.py | 1 + tests/components/zerproc/conftest.py | 1 + tests/components/zerproc/test_config_flow.py | 1 + tests/components/zerproc/test_light.py | 1 + tests/components/zeversolar/test_config_flow.py | 1 + tests/components/zha/conftest.py | 1 + tests/components/zha/test_alarm_control_panel.py | 1 + tests/components/zha/test_api.py | 1 + tests/components/zha/test_backup.py | 1 + tests/components/zha/test_binary_sensor.py | 1 + tests/components/zha/test_button.py | 1 + tests/components/zha/test_climate.py | 1 + tests/components/zha/test_cluster_handlers.py | 1 + tests/components/zha/test_device.py | 1 + tests/components/zha/test_device_action.py | 1 + tests/components/zha/test_device_tracker.py | 1 + tests/components/zha/test_device_trigger.py | 1 + tests/components/zha/test_diagnostics.py | 1 + tests/components/zha/test_discover.py | 1 + tests/components/zha/test_fan.py | 1 + tests/components/zha/test_light.py | 1 + tests/components/zha/test_lock.py | 1 + tests/components/zha/test_logbook.py | 1 + tests/components/zha/test_number.py | 1 + tests/components/zha/test_registries.py | 1 + tests/components/zha/test_repairs.py | 1 + tests/components/zha/test_select.py | 1 + tests/components/zha/test_sensor.py | 1 + tests/components/zha/test_silabs_multiprotocol.py | 1 + tests/components/zha/test_siren.py | 1 + tests/components/zha/test_switch.py | 1 + tests/components/zha/test_update.py | 1 + tests/components/zha/test_websocket_api.py | 1 + tests/components/zodiac/test_config_flow.py | 1 + tests/components/zodiac/test_sensor.py | 1 + tests/components/zone/test_init.py | 1 + tests/components/zwave_js/common.py | 1 + tests/components/zwave_js/test_api.py | 1 + tests/components/zwave_js/test_binary_sensor.py | 1 + tests/components/zwave_js/test_device_action.py | 1 + tests/components/zwave_js/test_device_condition.py | 1 + tests/components/zwave_js/test_device_trigger.py | 1 + tests/components/zwave_js/test_event.py | 1 + tests/components/zwave_js/test_events.py | 1 + tests/components/zwave_js/test_humidifier.py | 1 + tests/components/zwave_js/test_light.py | 1 + tests/components/zwave_js/test_logbook.py | 1 + tests/components/zwave_js/test_number.py | 1 + tests/components/zwave_js/test_repairs.py | 1 + tests/components/zwave_js/test_select.py | 1 + tests/components/zwave_js/test_services.py | 1 + tests/components/zwave_js/test_siren.py | 1 + tests/components/zwave_js/test_trigger.py | 1 + tests/components/zwave_me/test_config_flow.py | 1 + tests/components/zwave_me/test_remove_stale_devices.py | 1 + 443 files changed, 443 insertions(+) diff --git a/tests/components/tado/test_binary_sensor.py b/tests/components/tado/test_binary_sensor.py index 1e2f53efeb5..78cd91c56c6 100644 --- a/tests/components/tado/test_binary_sensor.py +++ b/tests/components/tado/test_binary_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the tado platform.""" + from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant diff --git a/tests/components/tado/test_climate.py b/tests/components/tado/test_climate.py index 91bc1af191e..98fd2d753a4 100644 --- a/tests/components/tado/test_climate.py +++ b/tests/components/tado/test_climate.py @@ -1,4 +1,5 @@ """The sensor tests for the tado platform.""" + from homeassistant.core import HomeAssistant from .util import async_init_integration diff --git a/tests/components/tado/test_config_flow.py b/tests/components/tado/test_config_flow.py index ac04777dc1c..5f20e0e95b3 100644 --- a/tests/components/tado/test_config_flow.py +++ b/tests/components/tado/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Tado config flow.""" + from http import HTTPStatus from ipaddress import ip_address from unittest.mock import MagicMock, patch diff --git a/tests/components/tado/test_sensor.py b/tests/components/tado/test_sensor.py index 9bdc0614a2b..0fa7a9ca370 100644 --- a/tests/components/tado/test_sensor.py +++ b/tests/components/tado/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the tado platform.""" + from homeassistant.core import HomeAssistant from .util import async_init_integration diff --git a/tests/components/tado/test_water_heater.py b/tests/components/tado/test_water_heater.py index b552118df9d..223a1fda16a 100644 --- a/tests/components/tado/test_water_heater.py +++ b/tests/components/tado/test_water_heater.py @@ -1,4 +1,5 @@ """The sensor tests for the tado platform.""" + from homeassistant.core import HomeAssistant from .util import async_init_integration diff --git a/tests/components/tailscale/conftest.py b/tests/components/tailscale/conftest.py index ec3b6afa139..5cf3f344739 100644 --- a/tests/components/tailscale/conftest.py +++ b/tests/components/tailscale/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Tailscale integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/tailscale/test_binary_sensor.py b/tests/components/tailscale/test_binary_sensor.py index bd7ca7087bb..1d1cda84723 100644 --- a/tests/components/tailscale/test_binary_sensor.py +++ b/tests/components/tailscale/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Tailscale integration.""" + from homeassistant.components.binary_sensor import ( STATE_OFF, STATE_ON, diff --git a/tests/components/tailscale/test_diagnostics.py b/tests/components/tailscale/test_diagnostics.py index 4f900db7401..26ba611438c 100644 --- a/tests/components/tailscale/test_diagnostics.py +++ b/tests/components/tailscale/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Tailscale integration.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/tailscale/test_init.py b/tests/components/tailscale/test_init.py index 11ca8a910a6..eeeaed57ba6 100644 --- a/tests/components/tailscale/test_init.py +++ b/tests/components/tailscale/test_init.py @@ -1,4 +1,5 @@ """Tests for the Tailscale integration.""" + from unittest.mock import MagicMock from tailscale import TailscaleAuthenticationError, TailscaleConnectionError diff --git a/tests/components/tailscale/test_sensor.py b/tests/components/tailscale/test_sensor.py index 7fce057d3e7..aa2bc6c472a 100644 --- a/tests/components/tailscale/test_sensor.py +++ b/tests/components/tailscale/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Tailscale integration.""" + from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.tailscale.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, EntityCategory diff --git a/tests/components/tailwind/conftest.py b/tests/components/tailwind/conftest.py index b39a3598a3e..b03bb0ab0ec 100644 --- a/tests/components/tailwind/conftest.py +++ b/tests/components/tailwind/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Tailwind integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/tailwind/test_button.py b/tests/components/tailwind/test_button.py index a0128d5f498..55c0e3f5a9e 100644 --- a/tests/components/tailwind/test_button.py +++ b/tests/components/tailwind/test_button.py @@ -1,4 +1,5 @@ """Tests for button entities provided by the Tailwind integration.""" + from unittest.mock import MagicMock from gotailwind import TailwindError diff --git a/tests/components/tailwind/test_config_flow.py b/tests/components/tailwind/test_config_flow.py index 341f977d350..efd828dcbde 100644 --- a/tests/components/tailwind/test_config_flow.py +++ b/tests/components/tailwind/test_config_flow.py @@ -1,4 +1,5 @@ """Configuration flow tests for the Tailwind integration.""" + from ipaddress import ip_address from unittest.mock import MagicMock diff --git a/tests/components/tailwind/test_cover.py b/tests/components/tailwind/test_cover.py index 9620d6149b7..5e77ed19af3 100644 --- a/tests/components/tailwind/test_cover.py +++ b/tests/components/tailwind/test_cover.py @@ -1,4 +1,5 @@ """Tests for cover entities provided by the Tailwind integration.""" + from unittest.mock import ANY, MagicMock from gotailwind import ( diff --git a/tests/components/tailwind/test_init.py b/tests/components/tailwind/test_init.py index fb61d155008..8ea5f1108f4 100644 --- a/tests/components/tailwind/test_init.py +++ b/tests/components/tailwind/test_init.py @@ -1,4 +1,5 @@ """Integration tests for the Tailwind integration.""" + from unittest.mock import MagicMock from gotailwind import TailwindAuthenticationError, TailwindConnectionError diff --git a/tests/components/tailwind/test_number.py b/tests/components/tailwind/test_number.py index e16c940b85d..84d22eebd85 100644 --- a/tests/components/tailwind/test_number.py +++ b/tests/components/tailwind/test_number.py @@ -1,4 +1,5 @@ """Tests for number entities provided by the Tailwind integration.""" + from unittest.mock import MagicMock from gotailwind import TailwindError diff --git a/tests/components/tankerkoenig/conftest.py b/tests/components/tankerkoenig/conftest.py index 011dcf5e7bd..7f71ce8cb19 100644 --- a/tests/components/tankerkoenig/conftest.py +++ b/tests/components/tankerkoenig/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Tankerkoenig integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/tankerkoenig/test_config_flow.py b/tests/components/tankerkoenig/test_config_flow.py index db3d0aac222..f7cdc44021e 100644 --- a/tests/components/tankerkoenig/test_config_flow.py +++ b/tests/components/tankerkoenig/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Tankerkoenig config flow.""" + from unittest.mock import patch from aiotankerkoenig.exceptions import TankerkoenigInvalidKeyError diff --git a/tests/components/tankerkoenig/test_coordinator.py b/tests/components/tankerkoenig/test_coordinator.py index 650fa5a18ac..e1604c77819 100644 --- a/tests/components/tankerkoenig/test_coordinator.py +++ b/tests/components/tankerkoenig/test_coordinator.py @@ -1,4 +1,5 @@ """Tests for the Tankerkoening integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/tankerkoenig/test_diagnostics.py b/tests/components/tankerkoenig/test_diagnostics.py index 8d7137c503a..441268659f3 100644 --- a/tests/components/tankerkoenig/test_diagnostics.py +++ b/tests/components/tankerkoenig/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the Tankerkoening integration.""" + from __future__ import annotations import pytest diff --git a/tests/components/tasmota/conftest.py b/tests/components/tasmota/conftest.py index aa73e4d9994..1bb1f085e91 100644 --- a/tests/components/tasmota/conftest.py +++ b/tests/components/tasmota/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for Tasmota component.""" + from unittest.mock import patch from hatasmota.discovery import get_status_sensor_entities diff --git a/tests/components/tasmota/test_config_flow.py b/tests/components/tasmota/test_config_flow.py index 514ddaf72ff..2db4d7c6493 100644 --- a/tests/components/tasmota/test_config_flow.py +++ b/tests/components/tasmota/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from homeassistant import config_entries from homeassistant.core import HomeAssistant from homeassistant.helpers.service_info.mqtt import MqttServiceInfo diff --git a/tests/components/tautulli/test_config_flow.py b/tests/components/tautulli/test_config_flow.py index 0ca2d0438a7..f867b5d0fb1 100644 --- a/tests/components/tautulli/test_config_flow.py +++ b/tests/components/tautulli/test_config_flow.py @@ -1,4 +1,5 @@ """Test Tautulli config flow.""" + from unittest.mock import AsyncMock, patch from pytautulli import exceptions diff --git a/tests/components/tcp/test_binary_sensor.py b/tests/components/tcp/test_binary_sensor.py index 3a4b8c490c7..089e5748cb7 100644 --- a/tests/components/tcp/test_binary_sensor.py +++ b/tests/components/tcp/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the TCP binary sensor platform.""" + from datetime import timedelta from unittest.mock import call, patch diff --git a/tests/components/tcp/test_sensor.py b/tests/components/tcp/test_sensor.py index cade92f3372..04fbb2c667e 100644 --- a/tests/components/tcp/test_sensor.py +++ b/tests/components/tcp/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the TCP sensor platform.""" + from copy import copy from unittest.mock import call, patch diff --git a/tests/components/technove/conftest.py b/tests/components/technove/conftest.py index b3921f865dc..4f4e5e4c978 100644 --- a/tests/components/technove/conftest.py +++ b/tests/components/technove/conftest.py @@ -1,4 +1,5 @@ """Fixtures for TechnoVE integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/technove/test_binary_sensor.py b/tests/components/technove/test_binary_sensor.py index 5e168ce0760..faa29e4e047 100644 --- a/tests/components/technove/test_binary_sensor.py +++ b/tests/components/technove/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the TechnoVE binary sensor platform.""" + from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/technove/test_sensor.py b/tests/components/technove/test_sensor.py index c44aab8ecc4..b1d63933bc1 100644 --- a/tests/components/technove/test_sensor.py +++ b/tests/components/technove/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the TechnoVE sensor platform.""" + from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/technove/test_switch.py b/tests/components/technove/test_switch.py index d0b709a4eda..b1a66607f66 100644 --- a/tests/components/technove/test_switch.py +++ b/tests/components/technove/test_switch.py @@ -1,4 +1,5 @@ """Tests for the TechnoVE switch platform.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/tedee/conftest.py b/tests/components/tedee/conftest.py index 21fb4047ab3..b5597ee2faa 100644 --- a/tests/components/tedee/conftest.py +++ b/tests/components/tedee/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Tedee integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/tedee/test_config_flow.py b/tests/components/tedee/test_config_flow.py index bc5b73aa4a9..6e8f02d04bc 100644 --- a/tests/components/tedee/test_config_flow.py +++ b/tests/components/tedee/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Tedee config flow.""" + from unittest.mock import MagicMock from pytedee_async import ( diff --git a/tests/components/tedee/test_diagnostics.py b/tests/components/tedee/test_diagnostics.py index 9a31e153b6c..1487645572f 100644 --- a/tests/components/tedee/test_diagnostics.py +++ b/tests/components/tedee/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Tedee integration.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/tedee/test_init.py b/tests/components/tedee/test_init.py index ca64c01a983..9388aaf008c 100644 --- a/tests/components/tedee/test_init.py +++ b/tests/components/tedee/test_init.py @@ -1,4 +1,5 @@ """Test initialization of tedee.""" + from unittest.mock import MagicMock from pytedee_async.exception import TedeeAuthException, TedeeClientException diff --git a/tests/components/tedee/test_lock.py b/tests/components/tedee/test_lock.py index fca1ae2b07f..f108c4f09f0 100644 --- a/tests/components/tedee/test_lock.py +++ b/tests/components/tedee/test_lock.py @@ -1,4 +1,5 @@ """Tests for tedee lock.""" + from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/telegram/test_notify.py b/tests/components/telegram/test_notify.py index 0b2943da152..ee13d8dc47c 100644 --- a/tests/components/telegram/test_notify.py +++ b/tests/components/telegram/test_notify.py @@ -1,4 +1,5 @@ """The tests for the telegram.notify platform.""" + from unittest.mock import patch from homeassistant import config as hass_config diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index f607ed468c5..864c9eba9ec 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -1,4 +1,5 @@ """Tests for the telegram_bot integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/telegram_bot/test_broadcast.py b/tests/components/telegram_bot/test_broadcast.py index a369754ae6a..b78054dc087 100644 --- a/tests/components/telegram_bot/test_broadcast.py +++ b/tests/components/telegram_bot/test_broadcast.py @@ -1,4 +1,5 @@ """Test Telegram broadcast.""" + from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index f86f70e77c1..d6588535b4f 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -1,4 +1,5 @@ """Tests for the telegram_bot component.""" + from unittest.mock import AsyncMock, patch from telegram import Update diff --git a/tests/components/tellduslive/test_config_flow.py b/tests/components/tellduslive/test_config_flow.py index 2d0a5fb2110..17d29b383fb 100644 --- a/tests/components/tellduslive/test_config_flow.py +++ b/tests/components/tellduslive/test_config_flow.py @@ -1,5 +1,6 @@ # flake8: noqa pylint: skip-file """Tests for the TelldusLive config flow.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/temper/test_sensor.py b/tests/components/temper/test_sensor.py index d195ff85c82..94c44cc4296 100644 --- a/tests/components/temper/test_sensor.py +++ b/tests/components/temper/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the temper (USB temperature sensor) component.""" + from datetime import timedelta from unittest.mock import Mock, patch diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 708571ce913..452f926dca5 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Template Binary sensor platform.""" + from datetime import UTC, datetime, timedelta import logging from unittest.mock import patch diff --git a/tests/components/template/test_config_flow.py b/tests/components/template/test_config_flow.py index b95a68afd85..4950c4ce819 100644 --- a/tests/components/template/test_config_flow.py +++ b/tests/components/template/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Switch config flow.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index 88f0fc366a3..e9a29fdc2e2 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -1,4 +1,5 @@ """The tests for the Template cover platform.""" + from typing import Any import pytest diff --git a/tests/components/template/test_image.py b/tests/components/template/test_image.py index 7b399e13ec0..6162276fcec 100644 --- a/tests/components/template/test_image.py +++ b/tests/components/template/test_image.py @@ -1,4 +1,5 @@ """The tests for the Template image platform.""" + from http import HTTPStatus from io import BytesIO from typing import Any diff --git a/tests/components/template/test_init.py b/tests/components/template/test_init.py index df1e86eaacd..ed0426b7e0e 100644 --- a/tests/components/template/test_init.py +++ b/tests/components/template/test_init.py @@ -1,4 +1,5 @@ """The test for the Template sensor platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/template/test_number.py b/tests/components/template/test_number.py index 02d5a8451e0..bfaf3b6a0a1 100644 --- a/tests/components/template/test_number.py +++ b/tests/components/template/test_number.py @@ -1,4 +1,5 @@ """The tests for the Template number platform.""" + from homeassistant import setup from homeassistant.components.input_number import ( ATTR_VALUE as INPUT_NUMBER_ATTR_VALUE, diff --git a/tests/components/template/test_select.py b/tests/components/template/test_select.py index 1f9915d4b21..6567926cd01 100644 --- a/tests/components/template/test_select.py +++ b/tests/components/template/test_select.py @@ -1,4 +1,5 @@ """The tests for the Template select platform.""" + from homeassistant import setup from homeassistant.components.input_select import ( ATTR_OPTION as INPUT_SELECT_ATTR_OPTION, diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 314218fc849..8d497a9af73 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -1,4 +1,5 @@ """The test for the Template sensor platform.""" + from asyncio import Event from datetime import datetime, timedelta from unittest.mock import ANY, patch diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index af010c57e2e..db7cd3a2471 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the Template automation.""" + from datetime import timedelta from unittest import mock diff --git a/tests/components/template/test_weather.py b/tests/components/template/test_weather.py index 36071c746da..3941194c586 100644 --- a/tests/components/template/test_weather.py +++ b/tests/components/template/test_weather.py @@ -1,4 +1,5 @@ """The tests for the Template Weather platform.""" + from typing import Any import pytest diff --git a/tests/components/tesla_wall_connector/conftest.py b/tests/components/tesla_wall_connector/conftest.py index e9f26a58eec..7ff5db504d2 100644 --- a/tests/components/tesla_wall_connector/conftest.py +++ b/tests/components/tesla_wall_connector/conftest.py @@ -1,4 +1,5 @@ """Common fixutres with default mocks as well as common test helper methods.""" + from dataclasses import dataclass from datetime import timedelta from typing import Any diff --git a/tests/components/tesla_wall_connector/test_binary_sensor.py b/tests/components/tesla_wall_connector/test_binary_sensor.py index 09283cb5352..22100bbb1c1 100644 --- a/tests/components/tesla_wall_connector/test_binary_sensor.py +++ b/tests/components/tesla_wall_connector/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for binary sensors.""" + from homeassistant.core import HomeAssistant from .conftest import ( diff --git a/tests/components/tesla_wall_connector/test_config_flow.py b/tests/components/tesla_wall_connector/test_config_flow.py index 17920a3bf57..198dcccfe00 100644 --- a/tests/components/tesla_wall_connector/test_config_flow.py +++ b/tests/components/tesla_wall_connector/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Tesla Wall Connector config flow.""" + from unittest.mock import patch from tesla_wall_connector.exceptions import WallConnectorConnectionError diff --git a/tests/components/tesla_wall_connector/test_init.py b/tests/components/tesla_wall_connector/test_init.py index aa7a71948b7..152bf18d57e 100644 --- a/tests/components/tesla_wall_connector/test_init.py +++ b/tests/components/tesla_wall_connector/test_init.py @@ -1,4 +1,5 @@ """Test the Tesla Wall Connector config flow.""" + from tesla_wall_connector.exceptions import WallConnectorConnectionError from homeassistant import config_entries diff --git a/tests/components/tesla_wall_connector/test_sensor.py b/tests/components/tesla_wall_connector/test_sensor.py index 28b50ba72ea..d064b9028b5 100644 --- a/tests/components/tesla_wall_connector/test_sensor.py +++ b/tests/components/tesla_wall_connector/test_sensor.py @@ -1,4 +1,5 @@ """Tests for sensors.""" + from homeassistant.core import HomeAssistant from .conftest import ( diff --git a/tests/components/teslemetry/conftest.py b/tests/components/teslemetry/conftest.py index 692d97dc4d8..f252787b37c 100644 --- a/tests/components/teslemetry/conftest.py +++ b/tests/components/teslemetry/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Tessie.""" + from __future__ import annotations from copy import deepcopy diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py index f417df23357..3380c0086db 100644 --- a/tests/components/teslemetry/test_sensor.py +++ b/tests/components/teslemetry/test_sensor.py @@ -1,4 +1,5 @@ """Test the Teslemetry sensor platform.""" + from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion diff --git a/tests/components/tessie/conftest.py b/tests/components/tessie/conftest.py index 02b3d56691e..f38ef6c7e3f 100644 --- a/tests/components/tessie/conftest.py +++ b/tests/components/tessie/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Tessie.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/tessie/test_button.py b/tests/components/tessie/test_button.py index 674e7a32747..fa6c8358ae6 100644 --- a/tests/components/tessie/test_button.py +++ b/tests/components/tessie/test_button.py @@ -1,4 +1,5 @@ """Test the Tessie button platform.""" + from unittest.mock import patch from syrupy import SnapshotAssertion diff --git a/tests/components/tessie/test_climate.py b/tests/components/tessie/test_climate.py index 6d1c8c220d1..88499a7736c 100644 --- a/tests/components/tessie/test_climate.py +++ b/tests/components/tessie/test_climate.py @@ -1,4 +1,5 @@ """Test the Tessie climate platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/tessie/test_coordinator.py b/tests/components/tessie/test_coordinator.py index 14bb6b7d203..c4c1b6d1e72 100644 --- a/tests/components/tessie/test_coordinator.py +++ b/tests/components/tessie/test_coordinator.py @@ -1,4 +1,5 @@ """Test the Tessie sensor platform.""" + from datetime import timedelta from homeassistant.components.tessie import PLATFORMS diff --git a/tests/components/tessie/test_cover.py b/tests/components/tessie/test_cover.py index c86cce466e1..c4dbd8cb704 100644 --- a/tests/components/tessie/test_cover.py +++ b/tests/components/tessie/test_cover.py @@ -1,4 +1,5 @@ """Test the Tessie cover platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/tessie/test_select.py b/tests/components/tessie/test_select.py index d22f8cccad7..0a6d56621b3 100644 --- a/tests/components/tessie/test_select.py +++ b/tests/components/tessie/test_select.py @@ -1,4 +1,5 @@ """Test the Tessie select platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/tessie/test_sensor.py b/tests/components/tessie/test_sensor.py index efe34df7226..92256d25eb1 100644 --- a/tests/components/tessie/test_sensor.py +++ b/tests/components/tessie/test_sensor.py @@ -1,4 +1,5 @@ """Test the Tessie sensor platform.""" + from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion diff --git a/tests/components/tessie/test_switch.py b/tests/components/tessie/test_switch.py index 60f3fab490c..907be29ddcc 100644 --- a/tests/components/tessie/test_switch.py +++ b/tests/components/tessie/test_switch.py @@ -1,4 +1,5 @@ """Test the Tessie switch platform.""" + from unittest.mock import patch from syrupy import SnapshotAssertion diff --git a/tests/components/tessie/test_update.py b/tests/components/tessie/test_update.py index 54e56c46b50..8d098e9a966 100644 --- a/tests/components/tessie/test_update.py +++ b/tests/components/tessie/test_update.py @@ -1,4 +1,5 @@ """Test the Tessie update platform.""" + from unittest.mock import patch from syrupy import SnapshotAssertion diff --git a/tests/components/text/test_init.py b/tests/components/text/test_init.py index d144cc86c91..7b44903eec3 100644 --- a/tests/components/text/test_init.py +++ b/tests/components/text/test_init.py @@ -1,4 +1,5 @@ """The tests for the text component.""" + from typing import Any import pytest diff --git a/tests/components/text/test_recorder.py b/tests/components/text/test_recorder.py index da9ec810da9..3aa4424b904 100644 --- a/tests/components/text/test_recorder.py +++ b/tests/components/text/test_recorder.py @@ -1,4 +1,5 @@ """The tests for text recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/thermobeacon/test_config_flow.py b/tests/components/thermobeacon/test_config_flow.py index eba12c11177..a63ccf08963 100644 --- a/tests/components/thermobeacon/test_config_flow.py +++ b/tests/components/thermobeacon/test_config_flow.py @@ -1,4 +1,5 @@ """Test the ThermoBeacon config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/thermobeacon/test_sensor.py b/tests/components/thermobeacon/test_sensor.py index e8d77e3a487..199d44821b6 100644 --- a/tests/components/thermobeacon/test_sensor.py +++ b/tests/components/thermobeacon/test_sensor.py @@ -1,4 +1,5 @@ """Test the ThermoBeacon sensors.""" + from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.thermobeacon.const import DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/thermopro/test_config_flow.py b/tests/components/thermopro/test_config_flow.py index dfd0e39777c..0ee86cd5067 100644 --- a/tests/components/thermopro/test_config_flow.py +++ b/tests/components/thermopro/test_config_flow.py @@ -1,4 +1,5 @@ """Test the ThermoPro config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/thermopro/test_sensor.py b/tests/components/thermopro/test_sensor.py index d754991f3d8..e9f234100b4 100644 --- a/tests/components/thermopro/test_sensor.py +++ b/tests/components/thermopro/test_sensor.py @@ -1,4 +1,5 @@ """Test the ThermoPro config flow.""" + from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.thermopro.const import DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/thread/test_config_flow.py b/tests/components/thread/test_config_flow.py index 51ebe3b5976..e46b5b70961 100644 --- a/tests/components/thread/test_config_flow.py +++ b/tests/components/thread/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Thread config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/threshold/test_config_flow.py b/tests/components/threshold/test_config_flow.py index 8229ed1b1ef..ca61be795d6 100644 --- a/tests/components/threshold/test_config_flow.py +++ b/tests/components/threshold/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Threshold config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/tibber/test_config_flow.py b/tests/components/tibber/test_config_flow.py index 545a79ff56f..b6c616c5cf0 100644 --- a/tests/components/tibber/test_config_flow.py +++ b/tests/components/tibber/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Tibber config flow.""" + from asyncio import TimeoutError from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch diff --git a/tests/components/tibber/test_diagnostics.py b/tests/components/tibber/test_diagnostics.py index 9446778637e..7047d995819 100644 --- a/tests/components/tibber/test_diagnostics.py +++ b/tests/components/tibber/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Netatmo diagnostics.""" + from unittest.mock import patch from homeassistant.components.recorder import Recorder diff --git a/tests/components/tibber/test_statistics.py b/tests/components/tibber/test_statistics.py index 566e5a651a5..d6c510a8785 100644 --- a/tests/components/tibber/test_statistics.py +++ b/tests/components/tibber/test_statistics.py @@ -1,4 +1,5 @@ """Test adding external statistics from Tibber.""" + from unittest.mock import AsyncMock from homeassistant.components.recorder import Recorder diff --git a/tests/components/tile/test_config_flow.py b/tests/components/tile/test_config_flow.py index d35f928787c..5d269bfee5d 100644 --- a/tests/components/tile/test_config_flow.py +++ b/tests/components/tile/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Tile config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/tile/test_diagnostics.py b/tests/components/tile/test_diagnostics.py index 8af2c513202..2ac3e06ccd8 100644 --- a/tests/components/tile/test_diagnostics.py +++ b/tests/components/tile/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Tile diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/tilt_ble/test_config_flow.py b/tests/components/tilt_ble/test_config_flow.py index e972aee0bb9..b9623f9700d 100644 --- a/tests/components/tilt_ble/test_config_flow.py +++ b/tests/components/tilt_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Tilt config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/time/test_init.py b/tests/components/time/test_init.py index a3248c96361..20360279217 100644 --- a/tests/components/time/test_init.py +++ b/tests/components/time/test_init.py @@ -1,4 +1,5 @@ """The tests for the time component.""" + from datetime import time from homeassistant.components.time import DOMAIN, SERVICE_SET_VALUE, TimeEntity diff --git a/tests/components/time_date/conftest.py b/tests/components/time_date/conftest.py index af732f978b4..72363dcdf9e 100644 --- a/tests/components/time_date/conftest.py +++ b/tests/components/time_date/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Time & Date integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/time_date/test_config_flow.py b/tests/components/time_date/test_config_flow.py index 228a34b65b4..7402fc529d1 100644 --- a/tests/components/time_date/test_config_flow.py +++ b/tests/components/time_date/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Time & Date config flow.""" + from __future__ import annotations from unittest.mock import AsyncMock diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index 92baa013f14..5aca1625d1f 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -1,4 +1,5 @@ """The tests for the timer component.""" + from datetime import timedelta import logging from unittest.mock import patch diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index c7979b884d4..65c768a2a64 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test Times of the Day Binary Sensor.""" + from datetime import datetime, timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/tod/test_config_flow.py b/tests/components/tod/test_config_flow.py index 6860d401ce2..b18e8e316e3 100644 --- a/tests/components/tod/test_config_flow.py +++ b/tests/components/tod/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Times of the Day config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/todoist/conftest.py b/tests/components/todoist/conftest.py index 42251b0ea18..773fc0eb195 100644 --- a/tests/components/todoist/conftest.py +++ b/tests/components/todoist/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the todoist tests.""" + from collections.abc import Generator from http import HTTPStatus from unittest.mock import AsyncMock, patch diff --git a/tests/components/todoist/test_calendar.py b/tests/components/todoist/test_calendar.py index 761eeb07c61..ddffd879d46 100644 --- a/tests/components/todoist/test_calendar.py +++ b/tests/components/todoist/test_calendar.py @@ -1,4 +1,5 @@ """Unit tests for the Todoist calendar platform.""" + from datetime import timedelta from http import HTTPStatus from typing import Any diff --git a/tests/components/todoist/test_init.py b/tests/components/todoist/test_init.py index 0e80be5410f..62915eb0fdd 100644 --- a/tests/components/todoist/test_init.py +++ b/tests/components/todoist/test_init.py @@ -1,4 +1,5 @@ """Unit tests for the Todoist integration.""" + from http import HTTPStatus from unittest.mock import AsyncMock diff --git a/tests/components/todoist/test_todo.py b/tests/components/todoist/test_todo.py index a227ec858e4..373eb0158ea 100644 --- a/tests/components/todoist/test_todo.py +++ b/tests/components/todoist/test_todo.py @@ -1,4 +1,5 @@ """Unit tests for the Todoist todo platform.""" + from typing import Any from unittest.mock import AsyncMock diff --git a/tests/components/tolo/test_config_flow.py b/tests/components/tolo/test_config_flow.py index 77547557b63..ff704bcd5cd 100644 --- a/tests/components/tolo/test_config_flow.py +++ b/tests/components/tolo/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the TOLO Sauna config flow.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/tomato/test_device_tracker.py b/tests/components/tomato/test_device_tracker.py index 11e73b5695c..099a2c2b40a 100644 --- a/tests/components/tomato/test_device_tracker.py +++ b/tests/components/tomato/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the Tomato device tracker platform.""" + from unittest import mock import pytest diff --git a/tests/components/tomorrowio/const.py b/tests/components/tomorrowio/const.py index cc9fe803f4e..f2851914379 100644 --- a/tests/components/tomorrowio/const.py +++ b/tests/components/tomorrowio/const.py @@ -1,4 +1,5 @@ """Constants for tomorrowio tests.""" + from homeassistant.const import ( CONF_API_KEY, CONF_LATITUDE, diff --git a/tests/components/tomorrowio/test_config_flow.py b/tests/components/tomorrowio/test_config_flow.py index 301b9bef554..5d4d2e3b43b 100644 --- a/tests/components/tomorrowio/test_config_flow.py +++ b/tests/components/tomorrowio/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Tomorrow.io config flow.""" + from unittest.mock import patch from pytomorrowio.exceptions import ( diff --git a/tests/components/tomorrowio/test_init.py b/tests/components/tomorrowio/test_init.py index fe17bbe79b7..576c5ad1e46 100644 --- a/tests/components/tomorrowio/test_init.py +++ b/tests/components/tomorrowio/test_init.py @@ -1,4 +1,5 @@ """Tests for Tomorrow.io init.""" + from datetime import timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/tomorrowio/test_sensor.py b/tests/components/tomorrowio/test_sensor.py index 53e455ffb8d..0b5b989b91a 100644 --- a/tests/components/tomorrowio/test_sensor.py +++ b/tests/components/tomorrowio/test_sensor.py @@ -1,4 +1,5 @@ """Tests for Tomorrow.io sensor entities.""" + from __future__ import annotations from datetime import datetime diff --git a/tests/components/tomorrowio/test_weather.py b/tests/components/tomorrowio/test_weather.py index e715fccea6b..dbb94dcd89b 100644 --- a/tests/components/tomorrowio/test_weather.py +++ b/tests/components/tomorrowio/test_weather.py @@ -1,4 +1,5 @@ """Tests for Tomorrow.io weather entity.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/toon/test_config_flow.py b/tests/components/toon/test_config_flow.py index 2105802e2e6..3e8f7fa2624 100644 --- a/tests/components/toon/test_config_flow.py +++ b/tests/components/toon/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Toon config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py index ccee4c43781..2661c18772d 100644 --- a/tests/components/totalconnect/common.py +++ b/tests/components/totalconnect/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for TotalConnect.""" + from unittest.mock import patch from total_connect_client import ArmingState, ResultCode, ZoneStatus, ZoneType diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index be1a05947cc..7ac6540f1ff 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Tests for the TotalConnect alarm control panel device.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/totalconnect/test_binary_sensor.py b/tests/components/totalconnect/test_binary_sensor.py index 8f9cabe670c..8ff548850d9 100644 --- a/tests/components/totalconnect/test_binary_sensor.py +++ b/tests/components/totalconnect/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the TotalConnect binary sensor.""" + from unittest.mock import patch from homeassistant.components.binary_sensor import ( diff --git a/tests/components/totalconnect/test_config_flow.py b/tests/components/totalconnect/test_config_flow.py index 54259538456..39ba30a93a8 100644 --- a/tests/components/totalconnect/test_config_flow.py +++ b/tests/components/totalconnect/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the TotalConnect config flow.""" + from unittest.mock import patch from total_connect_client.exceptions import AuthenticationError diff --git a/tests/components/totalconnect/test_diagnostics.py b/tests/components/totalconnect/test_diagnostics.py index a632cb81a60..2ad05c60936 100644 --- a/tests/components/totalconnect/test_diagnostics.py +++ b/tests/components/totalconnect/test_diagnostics.py @@ -1,4 +1,5 @@ """Test TotalConnect diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant diff --git a/tests/components/totalconnect/test_init.py b/tests/components/totalconnect/test_init.py index c14014eaaf6..ba533e19798 100644 --- a/tests/components/totalconnect/test_init.py +++ b/tests/components/totalconnect/test_init.py @@ -1,4 +1,5 @@ """Tests for the TotalConnect init process.""" + from unittest.mock import patch from total_connect_client.exceptions import AuthenticationError diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index 54a8893ad98..0f5f2e0d542 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the tplink config flow.""" + from unittest.mock import AsyncMock, patch from kasa import TimeoutException diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 4af4a80c927..4ad8687afe3 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -1,4 +1,5 @@ """Tests for the TP-Link component.""" + from __future__ import annotations import copy diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index bd8a380daa1..193a52c4e2d 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -1,4 +1,5 @@ """Tests for light platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/tplink_omada/conftest.py b/tests/components/tplink_omada/conftest.py index 8f977de588c..ce7fc880c8e 100644 --- a/tests/components/tplink_omada/conftest.py +++ b/tests/components/tplink_omada/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for TP-Link Omada integration.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/tplink_omada/test_config_flow.py b/tests/components/tplink_omada/test_config_flow.py index 5c27c9bde6b..4c1f05deb67 100644 --- a/tests/components/tplink_omada/test_config_flow.py +++ b/tests/components/tplink_omada/test_config_flow.py @@ -1,4 +1,5 @@ """Test the TP-Link Omada config flows.""" + from unittest.mock import patch from tplink_omada_client import OmadaSite diff --git a/tests/components/tplink_omada/test_switch.py b/tests/components/tplink_omada/test_switch.py index 786893f328d..0a7d6840295 100644 --- a/tests/components/tplink_omada/test_switch.py +++ b/tests/components/tplink_omada/test_switch.py @@ -1,4 +1,5 @@ """Tests for TP-Link Omada switch entities.""" + from unittest.mock import MagicMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/traccar/test_init.py b/tests/components/traccar/test_init.py index b85701f9c72..e0ce876a97f 100644 --- a/tests/components/traccar/test_init.py +++ b/tests/components/traccar/test_init.py @@ -1,4 +1,5 @@ """The tests the for Traccar device tracker platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/traccar_server/common.py b/tests/components/traccar_server/common.py index b85f7b672f8..ea5c26f57e2 100644 --- a/tests/components/traccar_server/common.py +++ b/tests/components/traccar_server/common.py @@ -1,4 +1,5 @@ """Common test tools for Traccar Server.""" + from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry diff --git a/tests/components/traccar_server/conftest.py b/tests/components/traccar_server/conftest.py index b313cb6734d..c0836455bd8 100644 --- a/tests/components/traccar_server/conftest.py +++ b/tests/components/traccar_server/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Traccar Server tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/traccar_server/test_config_flow.py b/tests/components/traccar_server/test_config_flow.py index 00a987a4711..055f155a894 100644 --- a/tests/components/traccar_server/test_config_flow.py +++ b/tests/components/traccar_server/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Traccar Server config flow.""" + from collections.abc import Generator from typing import Any from unittest.mock import AsyncMock diff --git a/tests/components/traccar_server/test_diagnostics.py b/tests/components/traccar_server/test_diagnostics.py index ebefaab6df8..38c35178b6f 100644 --- a/tests/components/traccar_server/test_diagnostics.py +++ b/tests/components/traccar_server/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Traccar Server diagnostics.""" + from collections.abc import Generator from unittest.mock import AsyncMock diff --git a/tests/components/tractive/test_config_flow.py b/tests/components/tractive/test_config_flow.py index 6dd6f119d45..014526c9651 100644 --- a/tests/components/tractive/test_config_flow.py +++ b/tests/components/tractive/test_config_flow.py @@ -1,4 +1,5 @@ """Test the tractive config flow.""" + from unittest.mock import patch import aiotractive diff --git a/tests/components/tradfri/common.py b/tests/components/tradfri/common.py index b527d02bae4..ab3f6fb71c1 100644 --- a/tests/components/tradfri/common.py +++ b/tests/components/tradfri/common.py @@ -1,4 +1,5 @@ """Common tools used for the Tradfri test suite.""" + from copy import deepcopy from dataclasses import dataclass from typing import Any diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index 86e66f37d8f..9ddac769c1f 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -1,4 +1,5 @@ """Common tradfri test fixtures.""" + from __future__ import annotations from collections.abc import Callable, Generator diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 3f5c71645c8..fd3d85461b1 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Tradfri config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, patch diff --git a/tests/components/tradfri/test_cover.py b/tests/components/tradfri/test_cover.py index 5b669a3cc60..5aa4e75728d 100644 --- a/tests/components/tradfri/test_cover.py +++ b/tests/components/tradfri/test_cover.py @@ -1,4 +1,5 @@ """Tradfri cover (recognised as blinds in the IKEA ecosystem) platform tests.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/tradfri/test_diagnostics.py b/tests/components/tradfri/test_diagnostics.py index 0da1cb8aadf..ab5aaa45b45 100644 --- a/tests/components/tradfri/test_diagnostics.py +++ b/tests/components/tradfri/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for Tradfri diagnostics.""" + from __future__ import annotations import pytest diff --git a/tests/components/tradfri/test_fan.py b/tests/components/tradfri/test_fan.py index 8bcad92cb96..2abe03d629a 100644 --- a/tests/components/tradfri/test_fan.py +++ b/tests/components/tradfri/test_fan.py @@ -1,4 +1,5 @@ """Tradfri fan (recognised as air purifiers in the IKEA ecosystem) platform tests.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index 2848fcd0f3d..54ce469f3c5 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -1,4 +1,5 @@ """Tests for Tradfri setup.""" + from unittest.mock import MagicMock from homeassistant.components import tradfri diff --git a/tests/components/tradfri/test_light.py b/tests/components/tradfri/test_light.py index 789ebaae840..887b043689f 100644 --- a/tests/components/tradfri/test_light.py +++ b/tests/components/tradfri/test_light.py @@ -1,4 +1,5 @@ """Tradfri lights platform tests.""" + from typing import Any import pytest diff --git a/tests/components/tradfri/test_sensor.py b/tests/components/tradfri/test_sensor.py index ccb4eccd702..9db4bedce12 100644 --- a/tests/components/tradfri/test_sensor.py +++ b/tests/components/tradfri/test_sensor.py @@ -1,4 +1,5 @@ """Tradfri sensor platform tests.""" + from __future__ import annotations import pytest diff --git a/tests/components/tradfri/test_switch.py b/tests/components/tradfri/test_switch.py index 2380824faa8..231e761f88f 100644 --- a/tests/components/tradfri/test_switch.py +++ b/tests/components/tradfri/test_switch.py @@ -1,4 +1,5 @@ """Tradfri switch (recognised as sockets in the IKEA ecosystem) platform tests.""" + from __future__ import annotations import pytest diff --git a/tests/components/trafikverket_camera/conftest.py b/tests/components/trafikverket_camera/conftest.py index 92693ccf3c2..61eebb623b2 100644 --- a/tests/components/trafikverket_camera/conftest.py +++ b/tests/components/trafikverket_camera/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Trafikverket Camera integration tests.""" + from __future__ import annotations from datetime import datetime diff --git a/tests/components/trafikverket_camera/test_binary_sensor.py b/tests/components/trafikverket_camera/test_binary_sensor.py index 87d0e6d58b7..ffdb5b44813 100644 --- a/tests/components/trafikverket_camera/test_binary_sensor.py +++ b/tests/components/trafikverket_camera/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the Trafikverket binary sensor platform.""" + from __future__ import annotations from pytrafikverket.trafikverket_camera import CameraInfo diff --git a/tests/components/trafikverket_camera/test_camera.py b/tests/components/trafikverket_camera/test_camera.py index 182924e9f0e..1bf742b5f08 100644 --- a/tests/components/trafikverket_camera/test_camera.py +++ b/tests/components/trafikverket_camera/test_camera.py @@ -1,4 +1,5 @@ """The test for the Trafikverket camera platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/trafikverket_camera/test_config_flow.py b/tests/components/trafikverket_camera/test_config_flow.py index 005c6006d81..356a0efcfaf 100644 --- a/tests/components/trafikverket_camera/test_config_flow.py +++ b/tests/components/trafikverket_camera/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Trafikverket Camera config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/trafikverket_camera/test_coordinator.py b/tests/components/trafikverket_camera/test_coordinator.py index 0f79307e0b6..4f633cb524d 100644 --- a/tests/components/trafikverket_camera/test_coordinator.py +++ b/tests/components/trafikverket_camera/test_coordinator.py @@ -1,4 +1,5 @@ """The test for the Trafikverket Camera coordinator.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/trafikverket_camera/test_init.py b/tests/components/trafikverket_camera/test_init.py index e10c6c16e33..688af08fec1 100644 --- a/tests/components/trafikverket_camera/test_init.py +++ b/tests/components/trafikverket_camera/test_init.py @@ -1,4 +1,5 @@ """Test for Trafikverket Ferry component Init.""" + from __future__ import annotations from datetime import datetime diff --git a/tests/components/trafikverket_camera/test_recorder.py b/tests/components/trafikverket_camera/test_recorder.py index 777c6ea26b3..83645f141fa 100644 --- a/tests/components/trafikverket_camera/test_recorder.py +++ b/tests/components/trafikverket_camera/test_recorder.py @@ -1,4 +1,5 @@ """The tests for Trafikcerket Camera recorder.""" + from __future__ import annotations import pytest diff --git a/tests/components/trafikverket_camera/test_sensor.py b/tests/components/trafikverket_camera/test_sensor.py index c1c98aed797..9d357bbd0ca 100644 --- a/tests/components/trafikverket_camera/test_sensor.py +++ b/tests/components/trafikverket_camera/test_sensor.py @@ -1,4 +1,5 @@ """The test for the Trafikverket sensor platform.""" + from __future__ import annotations from pytrafikverket.trafikverket_camera import CameraInfo diff --git a/tests/components/trafikverket_ferry/conftest.py b/tests/components/trafikverket_ferry/conftest.py index beeca9bd9f3..3491b8474af 100644 --- a/tests/components/trafikverket_ferry/conftest.py +++ b/tests/components/trafikverket_ferry/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Trafikverket Ferry integration tests.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/trafikverket_ferry/test_config_flow.py b/tests/components/trafikverket_ferry/test_config_flow.py index dbbd1fb09ac..228357e666c 100644 --- a/tests/components/trafikverket_ferry/test_config_flow.py +++ b/tests/components/trafikverket_ferry/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Trafikverket Ferry config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/trafikverket_ferry/test_coordinator.py b/tests/components/trafikverket_ferry/test_coordinator.py index c0fbe7537cc..6ac4eaa3a78 100644 --- a/tests/components/trafikverket_ferry/test_coordinator.py +++ b/tests/components/trafikverket_ferry/test_coordinator.py @@ -1,4 +1,5 @@ """The test for the Trafikverket Ferry coordinator.""" + from __future__ import annotations from datetime import date, datetime, timedelta diff --git a/tests/components/trafikverket_ferry/test_init.py b/tests/components/trafikverket_ferry/test_init.py index d5063ab704c..adfc84d94cb 100644 --- a/tests/components/trafikverket_ferry/test_init.py +++ b/tests/components/trafikverket_ferry/test_init.py @@ -1,4 +1,5 @@ """Test for Trafikverket Ferry component Init.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/trafikverket_ferry/test_sensor.py b/tests/components/trafikverket_ferry/test_sensor.py index 7b3e0b30819..fc8fa557714 100644 --- a/tests/components/trafikverket_ferry/test_sensor.py +++ b/tests/components/trafikverket_ferry/test_sensor.py @@ -1,4 +1,5 @@ """The test for the Trafikverket sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/trafikverket_train/conftest.py b/tests/components/trafikverket_train/conftest.py index 423dee541d2..e299b115014 100644 --- a/tests/components/trafikverket_train/conftest.py +++ b/tests/components/trafikverket_train/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Trafikverket Train integration tests.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/trafikverket_train/test_config_flow.py b/tests/components/trafikverket_train/test_config_flow.py index f56aee163bc..85cbfae61f5 100644 --- a/tests/components/trafikverket_train/test_config_flow.py +++ b/tests/components/trafikverket_train/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Trafikverket Train config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/trafikverket_train/test_init.py b/tests/components/trafikverket_train/test_init.py index 74b6f30ce61..175a71f42df 100644 --- a/tests/components/trafikverket_train/test_init.py +++ b/tests/components/trafikverket_train/test_init.py @@ -1,4 +1,5 @@ """Test for Trafikverket Train component Init.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/trafikverket_train/test_sensor.py b/tests/components/trafikverket_train/test_sensor.py index 819433a6b9c..915dd6a6d57 100644 --- a/tests/components/trafikverket_train/test_sensor.py +++ b/tests/components/trafikverket_train/test_sensor.py @@ -1,4 +1,5 @@ """The test for the Trafikverket train sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/trafikverket_train/test_util.py b/tests/components/trafikverket_train/test_util.py index e978917adca..03abaf26d9c 100644 --- a/tests/components/trafikverket_train/test_util.py +++ b/tests/components/trafikverket_train/test_util.py @@ -1,4 +1,5 @@ """The test for the Trafikverket train utils.""" + from __future__ import annotations from datetime import datetime diff --git a/tests/components/trafikverket_weatherstation/test_config_flow.py b/tests/components/trafikverket_weatherstation/test_config_flow.py index e55e04d8411..8967f6c73aa 100644 --- a/tests/components/trafikverket_weatherstation/test_config_flow.py +++ b/tests/components/trafikverket_weatherstation/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Trafikverket weatherstation config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/transmission/test_config_flow.py b/tests/components/transmission/test_config_flow.py index 04f44d3b7e7..451d3ec8b78 100644 --- a/tests/components/transmission/test_config_flow.py +++ b/tests/components/transmission/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Transmission config flow.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/transport_nsw/test_sensor.py b/tests/components/transport_nsw/test_sensor.py index 5ec28c72fed..9ecff818592 100644 --- a/tests/components/transport_nsw/test_sensor.py +++ b/tests/components/transport_nsw/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Transport NSW (AU) sensor platform.""" + from unittest.mock import patch from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/tests/components/trend/conftest.py b/tests/components/trend/conftest.py index 2555492e4fd..5263b86d268 100644 --- a/tests/components/trend/conftest.py +++ b/tests/components/trend/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the trend component tests.""" + from collections.abc import Awaitable, Callable from typing import Any diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py index 115bac5ed5d..d8d02755044 100644 --- a/tests/components/trend/test_binary_sensor.py +++ b/tests/components/trend/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the Trend sensor platform.""" + from datetime import timedelta import logging from typing import Any diff --git a/tests/components/trend/test_config_flow.py b/tests/components/trend/test_config_flow.py index e81d57ef9e1..baccc396bf1 100644 --- a/tests/components/trend/test_config_flow.py +++ b/tests/components/trend/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Trend config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/tts/common.py b/tests/components/tts/common.py index 0c3642df6fe..5bdc156eacc 100644 --- a/tests/components/tts/common.py +++ b/tests/components/tts/common.py @@ -1,4 +1,5 @@ """Provide common tests tools for tts.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/tts/conftest.py b/tests/components/tts/conftest.py index 753c90e158d..a8bdeea5545 100644 --- a/tests/components/tts/conftest.py +++ b/tests/components/tts/conftest.py @@ -2,6 +2,7 @@ From http://doc.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures """ + from collections.abc import Generator import pytest diff --git a/tests/components/tts/test_legacy.py b/tests/components/tts/test_legacy.py index 5a8321e2ae4..6fe6b0b790a 100644 --- a/tests/components/tts/test_legacy.py +++ b/tests/components/tts/test_legacy.py @@ -1,4 +1,5 @@ """Test the legacy tts setup.""" + from __future__ import annotations import pytest diff --git a/tests/components/tts/test_media_source.py b/tests/components/tts/test_media_source.py index 641c02064ec..4c10d8f0b08 100644 --- a/tests/components/tts/test_media_source.py +++ b/tests/components/tts/test_media_source.py @@ -1,4 +1,5 @@ """Tests for TTS media source.""" + from http import HTTPStatus from unittest.mock import MagicMock diff --git a/tests/components/tts/test_notify.py b/tests/components/tts/test_notify.py index 1a776140457..acefca21f6c 100644 --- a/tests/components/tts/test_notify.py +++ b/tests/components/tts/test_notify.py @@ -1,4 +1,5 @@ """The tests for the TTS component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/tuya/conftest.py b/tests/components/tuya/conftest.py index 6decb7c5f10..541e2f1c9e3 100644 --- a/tests/components/tuya/conftest.py +++ b/tests/components/tuya/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Tuya integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/tuya/test_config_flow.py b/tests/components/tuya/test_config_flow.py index c38d8e5f8b5..646d6a09f12 100644 --- a/tests/components/tuya/test_config_flow.py +++ b/tests/components/tuya/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Tuya config flow.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/twentemilieu/conftest.py b/tests/components/twentemilieu/conftest.py index c42e3a9eb58..5d64f3ad066 100644 --- a/tests/components/twentemilieu/conftest.py +++ b/tests/components/twentemilieu/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Twente Milieu integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/twentemilieu/test_calendar.py b/tests/components/twentemilieu/test_calendar.py index 7610b8b003b..ba95b8c59cd 100644 --- a/tests/components/twentemilieu/test_calendar.py +++ b/tests/components/twentemilieu/test_calendar.py @@ -1,4 +1,5 @@ """Tests for the Twente Milieu calendar.""" + from http import HTTPStatus import pytest diff --git a/tests/components/twentemilieu/test_config_flow.py b/tests/components/twentemilieu/test_config_flow.py index e5875ecaab7..e272ce38bee 100644 --- a/tests/components/twentemilieu/test_config_flow.py +++ b/tests/components/twentemilieu/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Twente Milieu config flow.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/twentemilieu/test_diagnostics.py b/tests/components/twentemilieu/test_diagnostics.py index 0828d35ec51..586b0ca4fde 100644 --- a/tests/components/twentemilieu/test_diagnostics.py +++ b/tests/components/twentemilieu/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the TwenteMilieu integration.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/twentemilieu/test_init.py b/tests/components/twentemilieu/test_init.py index 64377022713..901252f050f 100644 --- a/tests/components/twentemilieu/test_init.py +++ b/tests/components/twentemilieu/test_init.py @@ -1,4 +1,5 @@ """Tests for the Twente Milieu integration.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/twilio/test_init.py b/tests/components/twilio/test_init.py index 0b181a79d0d..77e6afe3a12 100644 --- a/tests/components/twilio/test_init.py +++ b/tests/components/twilio/test_init.py @@ -1,4 +1,5 @@ """Test the init file of Twilio.""" + from homeassistant import config_entries, data_entry_flow from homeassistant.components import twilio from homeassistant.config import async_process_ha_core_config diff --git a/tests/components/twinkly/conftest.py b/tests/components/twinkly/conftest.py index 5a689c31baa..6705d570205 100644 --- a/tests/components/twinkly/conftest.py +++ b/tests/components/twinkly/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the Twinkly integration.""" + from collections.abc import Awaitable, Callable, Coroutine from typing import Any from unittest.mock import patch diff --git a/tests/components/twinkly/test_config_flow.py b/tests/components/twinkly/test_config_flow.py index 4ddc0d4c195..2e8337bc860 100644 --- a/tests/components/twinkly/test_config_flow.py +++ b/tests/components/twinkly/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the config_flow of the twinly component.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/twinkly/test_diagnostics.py b/tests/components/twinkly/test_diagnostics.py index ab07cabef4a..680f82365c0 100644 --- a/tests/components/twinkly/test_diagnostics.py +++ b/tests/components/twinkly/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics of the twinkly component.""" + from collections.abc import Awaitable, Callable from syrupy import SnapshotAssertion diff --git a/tests/components/twinkly/test_light.py b/tests/components/twinkly/test_light.py index 976744ff5db..7a55dbec14a 100644 --- a/tests/components/twinkly/test_light.py +++ b/tests/components/twinkly/test_light.py @@ -1,4 +1,5 @@ """Tests for the integration of a twinly device.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/twitch/conftest.py b/tests/components/twitch/conftest.py index b3894203786..bf997314092 100644 --- a/tests/components/twitch/conftest.py +++ b/tests/components/twitch/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the Twitch integration.""" + from collections.abc import Awaitable, Callable, Generator import time from unittest.mock import AsyncMock, patch diff --git a/tests/components/twitch/test_config_flow.py b/tests/components/twitch/test_config_flow.py index 36312fea83e..4b6834ba544 100644 --- a/tests/components/twitch/test_config_flow.py +++ b/tests/components/twitch/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow for Twitch.""" + from unittest.mock import patch import pytest diff --git a/tests/components/twitch/test_sensor.py b/tests/components/twitch/test_sensor.py index 047c55d3b72..3385cb228fd 100644 --- a/tests/components/twitch/test_sensor.py +++ b/tests/components/twitch/test_sensor.py @@ -1,4 +1,5 @@ """The tests for an update of the Twitch component.""" + from datetime import datetime import pytest diff --git a/tests/components/ukraine_alarm/test_config_flow.py b/tests/components/ukraine_alarm/test_config_flow.py index 3bb776dadb0..6d9ce7b7f72 100644 --- a/tests/components/ukraine_alarm/test_config_flow.py +++ b/tests/components/ukraine_alarm/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ukraine Alarm config flow.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index abc1592c4f5..1ef8948ec51 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -1,4 +1,5 @@ """Fixtures for UniFi Network methods.""" + from __future__ import annotations import asyncio diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index ebf9823fb6d..b22767a2914 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the UniFi Network device tracker platform.""" + from datetime import timedelta from aiounifi.models.message import MessageKey diff --git a/tests/components/unifi/test_diagnostics.py b/tests/components/unifi/test_diagnostics.py index ce5290bccef..792512683d3 100644 --- a/tests/components/unifi/test_diagnostics.py +++ b/tests/components/unifi/test_diagnostics.py @@ -1,4 +1,5 @@ """Test UniFi Network diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, diff --git a/tests/components/unifi/test_hub.py b/tests/components/unifi/test_hub.py index c1ea4f4ba0b..6dce2109771 100644 --- a/tests/components/unifi/test_hub.py +++ b/tests/components/unifi/test_hub.py @@ -1,4 +1,5 @@ """Test UniFi Network.""" + from copy import deepcopy from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index f4d735c1d52..dd0442edb5c 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -1,4 +1,5 @@ """Test UniFi Network integration setup process.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index 99cc12eb3f1..7a58252a6bd 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -1,4 +1,5 @@ """UniFi Network sensor platform tests.""" + from copy import deepcopy from datetime import datetime, timedelta from unittest.mock import patch diff --git a/tests/components/unifi/test_services.py b/tests/components/unifi/test_services.py index 0de7d35608f..3f7da7a63ae 100644 --- a/tests/components/unifi/test_services.py +++ b/tests/components/unifi/test_services.py @@ -1,4 +1,5 @@ """deCONZ service tests.""" + from unittest.mock import patch from homeassistant.components.unifi.const import CONF_SITE_ID, DOMAIN as UNIFI_DOMAIN diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index c4fb18018f1..a6b787045bd 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -1,4 +1,5 @@ """UniFi Network switch platform tests.""" + from copy import deepcopy from datetime import timedelta diff --git a/tests/components/unifi/test_update.py b/tests/components/unifi/test_update.py index 2c6852629ea..4094c544431 100644 --- a/tests/components/unifi/test_update.py +++ b/tests/components/unifi/test_update.py @@ -1,4 +1,5 @@ """The tests for the UniFi Network update platform.""" + from copy import deepcopy from aiounifi.models.message import MessageKey diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index a9ff98fc681..6dfbb907097 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -1,4 +1,5 @@ """Test the UniFi Protect config flow.""" + from __future__ import annotations from dataclasses import asdict diff --git a/tests/components/unifiprotect/test_diagnostics.py b/tests/components/unifiprotect/test_diagnostics.py index 62d3f33ce0b..b13c069b37c 100644 --- a/tests/components/unifiprotect/test_diagnostics.py +++ b/tests/components/unifiprotect/test_diagnostics.py @@ -1,4 +1,5 @@ """Test UniFi Protect diagnostics.""" + from pyunifiprotect.data import NVR, Light from homeassistant.components.unifiprotect.const import CONF_ALLOW_EA diff --git a/tests/components/unifiprotect/test_recorder.py b/tests/components/unifiprotect/test_recorder.py index ab6e3fcb5ae..3e1a8599ea7 100644 --- a/tests/components/unifiprotect/test_recorder.py +++ b/tests/components/unifiprotect/test_recorder.py @@ -1,4 +1,5 @@ """The tests for unifiprotect recorder.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index 12701604306..41307029a0f 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -1,4 +1,5 @@ """Test repairs for unifiprotect.""" + from __future__ import annotations from copy import copy diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index 89a153caed2..52c70719acd 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -1,4 +1,5 @@ """Test the UniFi Protect sensor platform.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 60196e6fe24..c48316e6f22 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the Universal Media player platform.""" + from copy import copy from unittest.mock import Mock, patch diff --git a/tests/components/upb/test_config_flow.py b/tests/components/upb/test_config_flow.py index d2fbe27248d..7641e64fece 100644 --- a/tests/components/upb/test_config_flow.py +++ b/tests/components/upb/test_config_flow.py @@ -1,4 +1,5 @@ """Test the UPB Control config flow.""" + from unittest.mock import MagicMock, PropertyMock, patch from homeassistant import config_entries diff --git a/tests/components/upcloud/test_config_flow.py b/tests/components/upcloud/test_config_flow.py index cc869cdb99b..86f73463fab 100644 --- a/tests/components/upcloud/test_config_flow.py +++ b/tests/components/upcloud/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the UpCloud config flow.""" + from unittest.mock import patch import requests.exceptions diff --git a/tests/components/update/test_device_trigger.py b/tests/components/update/test_device_trigger.py index 16749167c41..ddb98c91ea2 100644 --- a/tests/components/update/test_device_trigger.py +++ b/tests/components/update/test_device_trigger.py @@ -1,4 +1,5 @@ """The test for update device automation.""" + from datetime import timedelta import pytest diff --git a/tests/components/update/test_init.py b/tests/components/update/test_init.py index 67661d6936e..3a923c8ed18 100644 --- a/tests/components/update/test_init.py +++ b/tests/components/update/test_init.py @@ -1,4 +1,5 @@ """The tests for the Update component.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/update/test_recorder.py b/tests/components/update/test_recorder.py index 1c0423bb9ad..d8048316900 100644 --- a/tests/components/update/test_recorder.py +++ b/tests/components/update/test_recorder.py @@ -1,4 +1,5 @@ """The tests for update recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/update/test_significant_change.py b/tests/components/update/test_significant_change.py index 35e601f4789..6c6ffbb10ee 100644 --- a/tests/components/update/test_significant_change.py +++ b/tests/components/update/test_significant_change.py @@ -1,4 +1,5 @@ """Test the update significant change platform.""" + from homeassistant.components.update.const import ( ATTR_IN_PROGRESS, ATTR_INSTALLED_VERSION, diff --git a/tests/components/upnp/conftest.py b/tests/components/upnp/conftest.py index db166144925..c1d1903878e 100644 --- a/tests/components/upnp/conftest.py +++ b/tests/components/upnp/conftest.py @@ -1,4 +1,5 @@ """Configuration for SSDP tests.""" + from __future__ import annotations import copy diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index d1d3dfa6c35..c46f3187b21 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -1,4 +1,5 @@ """Test UPnP/IGD setup process.""" + from __future__ import annotations from unittest.mock import AsyncMock diff --git a/tests/components/uptime/conftest.py b/tests/components/uptime/conftest.py index 7ee34856e63..a681fb40173 100644 --- a/tests/components/uptime/conftest.py +++ b/tests/components/uptime/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Uptime integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/uptime/test_init.py b/tests/components/uptime/test_init.py index 3535f846013..503657eb93e 100644 --- a/tests/components/uptime/test_init.py +++ b/tests/components/uptime/test_init.py @@ -1,4 +1,5 @@ """Tests for the Uptime integration.""" + from homeassistant.components.uptime.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/uptimerobot/common.py b/tests/components/uptimerobot/common.py index 15f6e153b19..23c0d3e1ce7 100644 --- a/tests/components/uptimerobot/common.py +++ b/tests/components/uptimerobot/common.py @@ -1,4 +1,5 @@ """Common constants and functions for UptimeRobot tests.""" + from __future__ import annotations from enum import Enum diff --git a/tests/components/uptimerobot/test_config_flow.py b/tests/components/uptimerobot/test_config_flow.py index ad495b9a55c..721338ee6e7 100644 --- a/tests/components/uptimerobot/test_config_flow.py +++ b/tests/components/uptimerobot/test_config_flow.py @@ -1,4 +1,5 @@ """Test the UptimeRobot config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/uptimerobot/test_init.py b/tests/components/uptimerobot/test_init.py index 67fac2437f0..a45480e50a5 100644 --- a/tests/components/uptimerobot/test_init.py +++ b/tests/components/uptimerobot/test_init.py @@ -1,4 +1,5 @@ """Test the UptimeRobot init.""" + from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/uptimerobot/test_switch.py b/tests/components/uptimerobot/test_switch.py index 7929879db19..82b8ca55831 100644 --- a/tests/components/uptimerobot/test_switch.py +++ b/tests/components/uptimerobot/test_switch.py @@ -1,4 +1,5 @@ """Test UptimeRobot switch.""" + from unittest.mock import patch import pytest diff --git a/tests/components/utility_meter/test_config_flow.py b/tests/components/utility_meter/test_config_flow.py index 75ea6d3a4d2..7a05564b5c4 100644 --- a/tests/components/utility_meter/test_config_flow.py +++ b/tests/components/utility_meter/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Utility Meter config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/utility_meter/test_init.py b/tests/components/utility_meter/test_init.py index ee0290744ce..d39d2fb6ed7 100644 --- a/tests/components/utility_meter/test_init.py +++ b/tests/components/utility_meter/test_init.py @@ -1,4 +1,5 @@ """The tests for the utility_meter component.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index a4b37271a85..3ec1946f3de 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the utility_meter sensor platform.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index dd42cfc2977..ccc8b75021f 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -1,4 +1,5 @@ """The tests for UVC camera module.""" + from datetime import UTC, datetime, timedelta from unittest.mock import call, patch diff --git a/tests/components/v2c/conftest.py b/tests/components/v2c/conftest.py index 85831b607b7..2bdfc405e2d 100644 --- a/tests/components/v2c/conftest.py +++ b/tests/components/v2c/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the V2C tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/v2c/test_config_flow.py b/tests/components/v2c/test_config_flow.py index 50bc4ca91bf..a0657fa0c7c 100644 --- a/tests/components/v2c/test_config_flow.py +++ b/tests/components/v2c/test_config_flow.py @@ -1,4 +1,5 @@ """Test the V2C config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/vacuum/common.py b/tests/components/vacuum/common.py index 6cecbda9968..0e46ebf5e44 100644 --- a/tests/components/vacuum/common.py +++ b/tests/components/vacuum/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.vacuum import ( ATTR_FAN_SPEED, ATTR_PARAMS, diff --git a/tests/components/vacuum/test_device_trigger.py b/tests/components/vacuum/test_device_trigger.py index 605dd6e5b9f..cb84f5697c1 100644 --- a/tests/components/vacuum/test_device_trigger.py +++ b/tests/components/vacuum/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Vacuum device triggers.""" + from datetime import timedelta import pytest diff --git a/tests/components/vacuum/test_init.py b/tests/components/vacuum/test_init.py index 0da4470c762..7a42913afbf 100644 --- a/tests/components/vacuum/test_init.py +++ b/tests/components/vacuum/test_init.py @@ -1,4 +1,5 @@ """The tests for the Vacuum entity integration.""" + from __future__ import annotations from homeassistant.components.vacuum import StateVacuumEntity, VacuumEntityFeature diff --git a/tests/components/vacuum/test_recorder.py b/tests/components/vacuum/test_recorder.py index 3694f0b5803..884e6943e95 100644 --- a/tests/components/vacuum/test_recorder.py +++ b/tests/components/vacuum/test_recorder.py @@ -1,4 +1,5 @@ """The tests for vacuum recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/vallox/test_binary_sensor.py b/tests/components/vallox/test_binary_sensor.py index 4add40f6a45..b8915e50e20 100644 --- a/tests/components/vallox/test_binary_sensor.py +++ b/tests/components/vallox/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for Vallox binary sensor platform.""" + from typing import Any import pytest diff --git a/tests/components/vallox/test_config_flow.py b/tests/components/vallox/test_config_flow.py index 3ded98f2307..6d053dd4e8a 100644 --- a/tests/components/vallox/test_config_flow.py +++ b/tests/components/vallox/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Vallox integration config flow.""" + from unittest.mock import patch from vallox_websocket_api import ValloxApiException, ValloxWebsocketException diff --git a/tests/components/vallox/test_fan.py b/tests/components/vallox/test_fan.py index e817bfbe89a..03ca3bca365 100644 --- a/tests/components/vallox/test_fan.py +++ b/tests/components/vallox/test_fan.py @@ -1,4 +1,5 @@ """Tests for Vallox fan platform.""" + from unittest.mock import call import pytest diff --git a/tests/components/valve/test_init.py b/tests/components/valve/test_init.py index 6f5c49830bb..314819c4cc8 100644 --- a/tests/components/valve/test_init.py +++ b/tests/components/valve/test_init.py @@ -1,4 +1,5 @@ """The tests for Valve.""" + from collections.abc import Generator import pytest diff --git a/tests/components/velbus/conftest.py b/tests/components/velbus/conftest.py index c5db7f0ec73..f393ebb819d 100644 --- a/tests/components/velbus/conftest.py +++ b/tests/components/velbus/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Velbus tests.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index 6b491c93657..4e3eeaf0fb8 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Velbus config flow.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/velbus/test_init.py b/tests/components/velbus/test_init.py index 0a1a727abcf..08efdd0410b 100644 --- a/tests/components/velbus/test_init.py +++ b/tests/components/velbus/test_init.py @@ -1,4 +1,5 @@ """Tests for the Velbus component initialisation.""" + from unittest.mock import patch import pytest diff --git a/tests/components/velux/conftest.py b/tests/components/velux/conftest.py index 60144d7137c..a3ebaf51d7a 100644 --- a/tests/components/velux/conftest.py +++ b/tests/components/velux/conftest.py @@ -1,4 +1,5 @@ """Configuration for Velux tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/velux/test_config_flow.py b/tests/components/velux/test_config_flow.py index f44debcb892..816dbf95420 100644 --- a/tests/components/velux/test_config_flow.py +++ b/tests/components/velux/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Velux config flow.""" + from __future__ import annotations from copy import deepcopy diff --git a/tests/components/venstar/test_climate.py b/tests/components/venstar/test_climate.py index d7c28b953cc..c090fadb445 100644 --- a/tests/components/venstar/test_climate.py +++ b/tests/components/venstar/test_climate.py @@ -1,4 +1,5 @@ """The climate tests for the venstar integration.""" + from unittest.mock import patch from homeassistant.components.climate import ClimateEntityFeature diff --git a/tests/components/venstar/test_init.py b/tests/components/venstar/test_init.py index 2a09f31f399..14064334faf 100644 --- a/tests/components/venstar/test_init.py +++ b/tests/components/venstar/test_init.py @@ -1,4 +1,5 @@ """Tests of the initialization of the venstar integration.""" + from unittest.mock import patch from homeassistant.components.venstar.const import DOMAIN as VENSTAR_DOMAIN diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index df753713a40..b6be60927cf 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -1,4 +1,5 @@ """Common code for tests.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/vera/conftest.py b/tests/components/vera/conftest.py index 2b1c65a9af1..7169765c4f2 100644 --- a/tests/components/vera/conftest.py +++ b/tests/components/vera/conftest.py @@ -1,4 +1,5 @@ """Fixtures for tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/vera/test_binary_sensor.py b/tests/components/vera/test_binary_sensor.py index 58b8ed7f461..76795ef86cf 100644 --- a/tests/components/vera/test_binary_sensor.py +++ b/tests/components/vera/test_binary_sensor.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/vera/test_climate.py b/tests/components/vera/test_climate.py index 9e5c4b607fe..f158dc9eec4 100644 --- a/tests/components/vera/test_climate.py +++ b/tests/components/vera/test_climate.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/vera/test_common.py b/tests/components/vera/test_common.py index 100a788313d..57a220493fd 100644 --- a/tests/components/vera/test_common.py +++ b/tests/components/vera/test_common.py @@ -1,4 +1,5 @@ """Tests for common vera code.""" + from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/vera/test_config_flow.py b/tests/components/vera/test_config_flow.py index ae85019c44c..2262347450d 100644 --- a/tests/components/vera/test_config_flow.py +++ b/tests/components/vera/test_config_flow.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock, patch from requests.exceptions import RequestException diff --git a/tests/components/vera/test_cover.py b/tests/components/vera/test_cover.py index 15b95b55e39..3549add6d38 100644 --- a/tests/components/vera/test_cover.py +++ b/tests/components/vera/test_cover.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index 23fb588d197..585d6d6f60a 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/vera/test_light.py b/tests/components/vera/test_light.py index c96199e5989..6bdc3df9a64 100644 --- a/tests/components/vera/test_light.py +++ b/tests/components/vera/test_light.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/vera/test_lock.py b/tests/components/vera/test_lock.py index 644d58b9fc5..4139a494e1f 100644 --- a/tests/components/vera/test_lock.py +++ b/tests/components/vera/test_lock.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/vera/test_scene.py b/tests/components/vera/test_scene.py index b23d220e74e..d254a9f12aa 100644 --- a/tests/components/vera/test_scene.py +++ b/tests/components/vera/test_scene.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index 9ddf05a5b0a..ebe8beb4e29 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -1,4 +1,5 @@ """Vera tests.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/vera/test_switch.py b/tests/components/vera/test_switch.py index 2ffb472aeeb..8000aeaac9d 100644 --- a/tests/components/vera/test_switch.py +++ b/tests/components/vera/test_switch.py @@ -1,4 +1,5 @@ """Vera tests.""" + from unittest.mock import MagicMock import pyvera as pv diff --git a/tests/components/verisure/conftest.py b/tests/components/verisure/conftest.py index 8e1da712a5c..445b7b95300 100644 --- a/tests/components/verisure/conftest.py +++ b/tests/components/verisure/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Verisure integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/verisure/test_config_flow.py b/tests/components/verisure/test_config_flow.py index 89b295857ff..62ae00b5622 100644 --- a/tests/components/verisure/test_config_flow.py +++ b/tests/components/verisure/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Verisure config flow.""" + from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/version/common.py b/tests/components/version/common.py index c4759604a44..33a1747cf0e 100644 --- a/tests/components/version/common.py +++ b/tests/components/version/common.py @@ -1,4 +1,5 @@ """Fixtures for version integration.""" + from __future__ import annotations from typing import Any, Final diff --git a/tests/components/version/test_binary_sensor.py b/tests/components/version/test_binary_sensor.py index 31ac70cf8b3..38fd78dfbb6 100644 --- a/tests/components/version/test_binary_sensor.py +++ b/tests/components/version/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the version binary sensor platform.""" + from __future__ import annotations from homeassistant.components.version.const import DEFAULT_CONFIGURATION diff --git a/tests/components/version/test_config_flow.py b/tests/components/version/test_config_flow.py index 8de4afff92e..d7edb5526d5 100644 --- a/tests/components/version/test_config_flow.py +++ b/tests/components/version/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Version config flow.""" + from unittest.mock import patch from pyhaversion.consts import HaVersionChannel, HaVersionSource diff --git a/tests/components/version/test_sensor.py b/tests/components/version/test_sensor.py index 0a3e89494f1..3a00c10aadc 100644 --- a/tests/components/version/test_sensor.py +++ b/tests/components/version/test_sensor.py @@ -1,4 +1,5 @@ """The test for the version sensor platform.""" + from __future__ import annotations from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/vesync/conftest.py b/tests/components/vesync/conftest.py index 8815a4b9748..23e0938cce6 100644 --- a/tests/components/vesync/conftest.py +++ b/tests/components/vesync/conftest.py @@ -1,4 +1,5 @@ """Configuration for VeSync tests.""" + from __future__ import annotations from unittest.mock import Mock, patch diff --git a/tests/components/vesync/test_config_flow.py b/tests/components/vesync/test_config_flow.py index acf4414900f..a283b89b841 100644 --- a/tests/components/vesync/test_config_flow.py +++ b/tests/components/vesync/test_config_flow.py @@ -1,4 +1,5 @@ """Test for vesync config flow.""" + from unittest.mock import patch from homeassistant import data_entry_flow diff --git a/tests/components/vesync/test_diagnostics.py b/tests/components/vesync/test_diagnostics.py index 62365189064..22968c79730 100644 --- a/tests/components/vesync/test_diagnostics.py +++ b/tests/components/vesync/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the VeSync integration.""" + from unittest.mock import patch from pyvesync.helpers import Helpers diff --git a/tests/components/vesync/test_init.py b/tests/components/vesync/test_init.py index c643e2bda19..005beab4503 100644 --- a/tests/components/vesync/test_init.py +++ b/tests/components/vesync/test_init.py @@ -1,4 +1,5 @@ """Tests for the init module.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/vicare/conftest.py b/tests/components/vicare/conftest.py index 46d90960f4e..fac85b5052a 100644 --- a/tests/components/vicare/conftest.py +++ b/tests/components/vicare/conftest.py @@ -1,4 +1,5 @@ """Fixtures for ViCare integration tests.""" + from __future__ import annotations from collections.abc import AsyncGenerator, Generator diff --git a/tests/components/vicare/test_config_flow.py b/tests/components/vicare/test_config_flow.py index e24f7ea834b..031fcdff9d3 100644 --- a/tests/components/vicare/test_config_flow.py +++ b/tests/components/vicare/test_config_flow.py @@ -1,4 +1,5 @@ """Test the ViCare config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index b893d2df550..c8bae610e73 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Vilfo Router config flow.""" + from unittest.mock import Mock, patch import vilfo diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index ad67d309cdd..08a6e6715be 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -1,4 +1,5 @@ """Configure py.test.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/vizio/const.py b/tests/components/vizio/const.py index 849c13d4396..0054b47c536 100644 --- a/tests/components/vizio/const.py +++ b/tests/components/vizio/const.py @@ -1,4 +1,5 @@ """Constants for the Vizio integration tests.""" + from ipaddress import ip_address from homeassistant.components import zeroconf diff --git a/tests/components/vizio/test_init.py b/tests/components/vizio/test_init.py index 288dd2c6ac0..edab40444b6 100644 --- a/tests/components/vizio/test_init.py +++ b/tests/components/vizio/test_init.py @@ -1,4 +1,5 @@ """Tests for Vizio init.""" + from datetime import timedelta import pytest diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 142c5f74b84..2f585b9e8be 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -1,4 +1,5 @@ """Tests for Vizio config flow.""" + from __future__ import annotations from contextlib import asynccontextmanager diff --git a/tests/components/vlc_telnet/test_config_flow.py b/tests/components/vlc_telnet/test_config_flow.py index a94f290f7e6..1d75025535a 100644 --- a/tests/components/vlc_telnet/test_config_flow.py +++ b/tests/components/vlc_telnet/test_config_flow.py @@ -1,4 +1,5 @@ """Test the VLC media player Telnet config flow.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/vodafone_station/const.py b/tests/components/vodafone_station/const.py index 40dc305630e..1b3d36def03 100644 --- a/tests/components/vodafone_station/const.py +++ b/tests/components/vodafone_station/const.py @@ -1,4 +1,5 @@ """Common stuff for Vodafone Station tests.""" + from homeassistant.components.vodafone_station.const import DOMAIN from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME diff --git a/tests/components/vodafone_station/test_config_flow.py b/tests/components/vodafone_station/test_config_flow.py index a50bde8de64..6334979b742 100644 --- a/tests/components/vodafone_station/test_config_flow.py +++ b/tests/components/vodafone_station/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Vodafone Station config flow.""" + from unittest.mock import patch from aiovodafone import exceptions as aiovodafone_exceptions diff --git a/tests/components/voicerss/test_tts.py b/tests/components/voicerss/test_tts.py index cb24d45246c..d1e7ba3c62f 100644 --- a/tests/components/voicerss/test_tts.py +++ b/tests/components/voicerss/test_tts.py @@ -1,4 +1,5 @@ """The tests for the VoiceRSS speech platform.""" + from http import HTTPStatus import pytest diff --git a/tests/components/voip/test_binary_sensor.py b/tests/components/voip/test_binary_sensor.py index 794d307ee01..58f1e0ea53b 100644 --- a/tests/components/voip/test_binary_sensor.py +++ b/tests/components/voip/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test VoIP binary sensor devices.""" + from homeassistant.components.voip.devices import VoIPDevice from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/tests/components/voip/test_config_flow.py b/tests/components/voip/test_config_flow.py index f7b3595699c..079177db139 100644 --- a/tests/components/voip/test_config_flow.py +++ b/tests/components/voip/test_config_flow.py @@ -1,4 +1,5 @@ """Test VoIP config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/voip/test_devices.py b/tests/components/voip/test_devices.py index 189dff49839..55359b8407d 100644 --- a/tests/components/voip/test_devices.py +++ b/tests/components/voip/test_devices.py @@ -1,4 +1,5 @@ """Test VoIP devices.""" + from __future__ import annotations from voip_utils import CallInfo diff --git a/tests/components/voip/test_init.py b/tests/components/voip/test_init.py index edc55685597..a0d843a47bd 100644 --- a/tests/components/voip/test_init.py +++ b/tests/components/voip/test_init.py @@ -1,4 +1,5 @@ """Test VoIP init.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/voip/test_select.py b/tests/components/voip/test_select.py index 7dd041a6866..a9741b44081 100644 --- a/tests/components/voip/test_select.py +++ b/tests/components/voip/test_select.py @@ -1,4 +1,5 @@ """Test VoIP select.""" + from homeassistant.components.voip.devices import VoIPDevice from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/tests/components/voip/test_switch.py b/tests/components/voip/test_switch.py index eb8fcfa2220..8b3cd03f2ac 100644 --- a/tests/components/voip/test_switch.py +++ b/tests/components/voip/test_switch.py @@ -1,4 +1,5 @@ """Test VoIP switch devices.""" + from homeassistant.components.voip.devices import VoIPDevice from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/tests/components/volumio/test_config_flow.py b/tests/components/volumio/test_config_flow.py index 841b558eba3..93469a02205 100644 --- a/tests/components/volumio/test_config_flow.py +++ b/tests/components/volumio/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Volumio config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/volvooncall/test_config_flow.py b/tests/components/volvooncall/test_config_flow.py index c8ed92d8ee5..e8b9de9a07b 100644 --- a/tests/components/volvooncall/test_config_flow.py +++ b/tests/components/volvooncall/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Volvo On Call config flow.""" + from unittest.mock import Mock, patch from aiohttp import ClientResponseError diff --git a/tests/components/vultr/test_init.py b/tests/components/vultr/test_init.py index 86034cd3828..8c5ec51f584 100644 --- a/tests/components/vultr/test_init.py +++ b/tests/components/vultr/test_init.py @@ -1,4 +1,5 @@ """The tests for the Vultr component.""" + from copy import deepcopy import json from unittest.mock import patch diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index feee545c365..a8968682aef 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -1,4 +1,5 @@ """Test the Vultr switch platform.""" + from __future__ import annotations import json diff --git a/tests/components/wake_on_lan/conftest.py b/tests/components/wake_on_lan/conftest.py index 5fa44f10c2c..66782531ef1 100644 --- a/tests/components/wake_on_lan/conftest.py +++ b/tests/components/wake_on_lan/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for Wake on Lan.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/wake_on_lan/test_init.py b/tests/components/wake_on_lan/test_init.py index 1cfe2fa7436..8cfb0e6491e 100644 --- a/tests/components/wake_on_lan/test_init.py +++ b/tests/components/wake_on_lan/test_init.py @@ -1,4 +1,5 @@ """Tests for Wake On LAN component.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index b2702ed1815..77e1ba55519 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -1,4 +1,5 @@ """The tests for the wake on lan switch platform.""" + from __future__ import annotations from unittest.mock import AsyncMock, patch diff --git a/tests/components/wake_word/common.py b/tests/components/wake_word/common.py index f732044bc13..9e2c570c6d0 100644 --- a/tests/components/wake_word/common.py +++ b/tests/components/wake_word/common.py @@ -1,4 +1,5 @@ """Provide common test tools for wake-word-detection.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/tests/components/wallbox/test_config_flow.py b/tests/components/wallbox/test_config_flow.py index 6b1592a3c4f..ebb3a2fd693 100644 --- a/tests/components/wallbox/test_config_flow.py +++ b/tests/components/wallbox/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Wallbox config flow.""" + from http import HTTPStatus import json diff --git a/tests/components/wallbox/test_sensor.py b/tests/components/wallbox/test_sensor.py index ad9ed6c0706..5a8b3c290c1 100644 --- a/tests/components/wallbox/test_sensor.py +++ b/tests/components/wallbox/test_sensor.py @@ -1,4 +1,5 @@ """Test Wallbox Switch component.""" + from homeassistant.const import CONF_UNIT_OF_MEASUREMENT, UnitOfPower from homeassistant.core import HomeAssistant diff --git a/tests/components/waqi/conftest.py b/tests/components/waqi/conftest.py index 176c1e27d8f..f42c8be6097 100644 --- a/tests/components/waqi/conftest.py +++ b/tests/components/waqi/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the World Air Quality Index (WAQI) tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/water_heater/common.py b/tests/components/water_heater/common.py index 0d2d73d17fd..9e47af4a19f 100644 --- a/tests/components/water_heater/common.py +++ b/tests/components/water_heater/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.water_heater import ( _LOGGER, ATTR_AWAY_MODE, diff --git a/tests/components/water_heater/conftest.py b/tests/components/water_heater/conftest.py index 0ce869ab724..d6858fe08e1 100644 --- a/tests/components/water_heater/conftest.py +++ b/tests/components/water_heater/conftest.py @@ -1,4 +1,5 @@ """Fixtures for water heater platform tests.""" + from collections.abc import Generator import pytest diff --git a/tests/components/water_heater/test_init.py b/tests/components/water_heater/test_init.py index c6f1e729edd..f6f48e65480 100644 --- a/tests/components/water_heater/test_init.py +++ b/tests/components/water_heater/test_init.py @@ -1,4 +1,5 @@ """The tests for the water heater component.""" + from __future__ import annotations from unittest import mock diff --git a/tests/components/water_heater/test_recorder.py b/tests/components/water_heater/test_recorder.py index c7a2e61ba4c..a3f808818ab 100644 --- a/tests/components/water_heater/test_recorder.py +++ b/tests/components/water_heater/test_recorder.py @@ -1,4 +1,5 @@ """The tests for water_heater recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/watttime/test_config_flow.py b/tests/components/watttime/test_config_flow.py index ce9284924f5..c8e4ed5b06b 100644 --- a/tests/components/watttime/test_config_flow.py +++ b/tests/components/watttime/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WattTime config flow.""" + from unittest.mock import AsyncMock, patch from aiowatttime.errors import CoordinatesNotFoundError, InvalidCredentialsError diff --git a/tests/components/watttime/test_diagnostics.py b/tests/components/watttime/test_diagnostics.py index 1f45ba870fc..0526a64aedc 100644 --- a/tests/components/watttime/test_diagnostics.py +++ b/tests/components/watttime/test_diagnostics.py @@ -1,4 +1,5 @@ """Test WattTime diagnostics.""" + from syrupy import SnapshotAssertion from syrupy.filters import props diff --git a/tests/components/waze_travel_time/conftest.py b/tests/components/waze_travel_time/conftest.py index 64c05a5dcc1..012657bdd8d 100644 --- a/tests/components/waze_travel_time/conftest.py +++ b/tests/components/waze_travel_time/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Waze Travel Time tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/weather/conftest.py b/tests/components/weather/conftest.py index a85b5e85d4b..073af7ab8ef 100644 --- a/tests/components/weather/conftest.py +++ b/tests/components/weather/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Weather platform tests.""" + from collections.abc import Generator import pytest diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index b982ab610ec..cf29c8d62ab 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -1,4 +1,5 @@ """The test for weather entity.""" + from datetime import datetime import pytest diff --git a/tests/components/weather/test_intent.py b/tests/components/weather/test_intent.py index 1a171da7fae..a562838edf3 100644 --- a/tests/components/weather/test_intent.py +++ b/tests/components/weather/test_intent.py @@ -1,4 +1,5 @@ """Test weather intents.""" + from unittest.mock import patch import pytest diff --git a/tests/components/weather/test_recorder.py b/tests/components/weather/test_recorder.py index 6b2ce4b633a..2986ec19a8c 100644 --- a/tests/components/weather/test_recorder.py +++ b/tests/components/weather/test_recorder.py @@ -1,4 +1,5 @@ """The tests for weather recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/weather/test_websocket_api.py b/tests/components/weather/test_websocket_api.py index 4a401d79849..5c8a785771f 100644 --- a/tests/components/weather/test_websocket_api.py +++ b/tests/components/weather/test_websocket_api.py @@ -1,4 +1,5 @@ """Test the weather websocket API.""" + from homeassistant.components.weather import Forecast, WeatherEntityFeature from homeassistant.components.weather.const import DOMAIN from homeassistant.const import UnitOfTemperature diff --git a/tests/components/weatherflow_cloud/conftest.py b/tests/components/weatherflow_cloud/conftest.py index 45ad80541f7..e07abe2b924 100644 --- a/tests/components/weatherflow_cloud/conftest.py +++ b/tests/components/weatherflow_cloud/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the WeatherflowCloud tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/weatherkit/conftest.py b/tests/components/weatherkit/conftest.py index 7cfa2f7eef5..ac1dab76a86 100644 --- a/tests/components/weatherkit/conftest.py +++ b/tests/components/weatherkit/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Apple WeatherKit tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/weatherkit/test_config_flow.py b/tests/components/weatherkit/test_config_flow.py index 9e4d03cbad4..58397ac2ed0 100644 --- a/tests/components/weatherkit/test_config_flow.py +++ b/tests/components/weatherkit/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Apple WeatherKit config flow.""" + from unittest.mock import AsyncMock, patch from apple_weatherkit import DataSetType diff --git a/tests/components/weatherkit/test_coordinator.py b/tests/components/weatherkit/test_coordinator.py index 7113e1d4d51..eff142f3d94 100644 --- a/tests/components/weatherkit/test_coordinator.py +++ b/tests/components/weatherkit/test_coordinator.py @@ -1,4 +1,5 @@ """Test WeatherKit data coordinator.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/weatherkit/test_setup.py b/tests/components/weatherkit/test_setup.py index d71ecbda1b0..479d08be20d 100644 --- a/tests/components/weatherkit/test_setup.py +++ b/tests/components/weatherkit/test_setup.py @@ -1,4 +1,5 @@ """Test the WeatherKit setup process.""" + from unittest.mock import patch from apple_weatherkit.client import ( diff --git a/tests/components/webhook/test_init.py b/tests/components/webhook/test_init.py index fbe0da15853..b92e9795432 100644 --- a/tests/components/webhook/test_init.py +++ b/tests/components/webhook/test_init.py @@ -1,4 +1,5 @@ """Test the webhook component.""" + from http import HTTPStatus from ipaddress import ip_address from unittest.mock import Mock, patch diff --git a/tests/components/webhook/test_trigger.py b/tests/components/webhook/test_trigger.py index 713130b6fb6..37aae47dd14 100644 --- a/tests/components/webhook/test_trigger.py +++ b/tests/components/webhook/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the webhook automation trigger.""" + from ipaddress import ip_address from unittest.mock import Mock, patch diff --git a/tests/components/webmin/conftest.py b/tests/components/webmin/conftest.py index b41e31be574..4fd674c66c8 100644 --- a/tests/components/webmin/conftest.py +++ b/tests/components/webmin/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Webmin integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/webmin/test_config_flow.py b/tests/components/webmin/test_config_flow.py index d61ed5a03d6..e680f0e164a 100644 --- a/tests/components/webmin/test_config_flow.py +++ b/tests/components/webmin/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Webmin config flow.""" + from __future__ import annotations from http import HTTPStatus diff --git a/tests/components/webmin/test_init.py b/tests/components/webmin/test_init.py index 4a6e7ce4994..7b6282edfae 100644 --- a/tests/components/webmin/test_init.py +++ b/tests/components/webmin/test_init.py @@ -1,4 +1,5 @@ """Tests for the Webmin integration.""" + from homeassistant.components.webmin.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/webostv/conftest.py b/tests/components/webostv/conftest.py index b78046c22ec..a21b10d0d9d 100644 --- a/tests/components/webostv/conftest.py +++ b/tests/components/webostv/conftest.py @@ -1,4 +1,5 @@ """Common fixtures and objects for the LG webOS integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/webostv/const.py b/tests/components/webostv/const.py index fbdb9c47c3b..afaed224e83 100644 --- a/tests/components/webostv/const.py +++ b/tests/components/webostv/const.py @@ -1,4 +1,5 @@ """Constants for LG webOS Smart TV tests.""" + from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.webostv.const import LIVE_TV_APP_ID diff --git a/tests/components/webostv/test_diagnostics.py b/tests/components/webostv/test_diagnostics.py index b7d1646c6b6..934b59a7b83 100644 --- a/tests/components/webostv/test_diagnostics.py +++ b/tests/components/webostv/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by LG webOS Smart TV.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant diff --git a/tests/components/webostv/test_init.py b/tests/components/webostv/test_init.py index 9d5b24d0d4e..30af1428701 100644 --- a/tests/components/webostv/test_init.py +++ b/tests/components/webostv/test_init.py @@ -1,4 +1,5 @@ """The tests for the LG webOS TV platform.""" + from unittest.mock import Mock from aiowebostv import WebOsTvPairError diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index cc060064b8b..2dff9477e50 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the LG webOS media player platform.""" + from datetime import timedelta from http import HTTPStatus from unittest.mock import Mock diff --git a/tests/components/webostv/test_notify.py b/tests/components/webostv/test_notify.py index dc150145b60..a1c37b9bf97 100644 --- a/tests/components/webostv/test_notify.py +++ b/tests/components/webostv/test_notify.py @@ -1,4 +1,5 @@ """The tests for the WebOS TV notify platform.""" + from unittest.mock import Mock, call from aiowebostv import WebOsTvPairError diff --git a/tests/components/webostv/test_trigger.py b/tests/components/webostv/test_trigger.py index 74573e2185b..dd119bd0d5a 100644 --- a/tests/components/webostv/test_trigger.py +++ b/tests/components/webostv/test_trigger.py @@ -1,4 +1,5 @@ """The tests for WebOS TV automation triggers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py index 65cf3012e30..35bf2402b6c 100644 --- a/tests/components/websocket_api/test_auth.py +++ b/tests/components/websocket_api/test_auth.py @@ -1,4 +1,5 @@ """Test auth of websocket API.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/websocket_api/test_decorators.py b/tests/components/websocket_api/test_decorators.py index 7ef14ca124f..3e9c13a8b15 100644 --- a/tests/components/websocket_api/test_decorators.py +++ b/tests/components/websocket_api/test_decorators.py @@ -1,4 +1,5 @@ """Test decorators.""" + from homeassistant.components import http, websocket_api from homeassistant.core import HomeAssistant diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py index c4c83925311..9360ff4ef8a 100644 --- a/tests/components/websocket_api/test_init.py +++ b/tests/components/websocket_api/test_init.py @@ -1,4 +1,5 @@ """Tests for the Home Assistant Websocket API.""" + from unittest.mock import Mock, patch from aiohttp import WSMsgType diff --git a/tests/components/websocket_api/test_sensor.py b/tests/components/websocket_api/test_sensor.py index 9f53efb8610..72b39b39354 100644 --- a/tests/components/websocket_api/test_sensor.py +++ b/tests/components/websocket_api/test_sensor.py @@ -1,4 +1,5 @@ """Test cases for the API stream sensor.""" + from homeassistant.auth.providers.legacy_api_password import ( LegacyApiPasswordAuthProvider, ) diff --git a/tests/components/wemo/test_light_bridge.py b/tests/components/wemo/test_light_bridge.py index 6f4180626b2..149fb13d023 100644 --- a/tests/components/wemo/test_light_bridge.py +++ b/tests/components/wemo/test_light_bridge.py @@ -1,4 +1,5 @@ """Tests for the Wemo light entity via the bridge.""" + from unittest.mock import create_autospec import pytest diff --git a/tests/components/whirlpool/conftest.py b/tests/components/whirlpool/conftest.py index 7ab7f013dd5..7af14f9991a 100644 --- a/tests/components/whirlpool/conftest.py +++ b/tests/components/whirlpool/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Whirlpool Sixth Sense integration tests.""" + from unittest import mock from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/whirlpool/test_climate.py b/tests/components/whirlpool/test_climate.py index 0cc58e80f0d..21c4501e6d0 100644 --- a/tests/components/whirlpool/test_climate.py +++ b/tests/components/whirlpool/test_climate.py @@ -1,4 +1,5 @@ """Test the Whirlpool Sixth Sense climate domain.""" + from unittest.mock import MagicMock from attr import dataclass diff --git a/tests/components/whirlpool/test_config_flow.py b/tests/components/whirlpool/test_config_flow.py index 60e64c89929..5d2375d2668 100644 --- a/tests/components/whirlpool/test_config_flow.py +++ b/tests/components/whirlpool/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Whirlpool Sixth Sense config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/whirlpool/test_init.py b/tests/components/whirlpool/test_init.py index 0eda2442b2c..b5c8cf3d481 100644 --- a/tests/components/whirlpool/test_init.py +++ b/tests/components/whirlpool/test_init.py @@ -1,4 +1,5 @@ """Test the Whirlpool Sixth Sense init.""" + from unittest.mock import AsyncMock, MagicMock import aiohttp diff --git a/tests/components/whirlpool/test_sensor.py b/tests/components/whirlpool/test_sensor.py index 20d0c436134..fc509f264c5 100644 --- a/tests/components/whirlpool/test_sensor.py +++ b/tests/components/whirlpool/test_sensor.py @@ -1,4 +1,5 @@ """Test the Whirlpool Sensor domain.""" + from datetime import UTC, datetime from unittest.mock import MagicMock diff --git a/tests/components/whois/conftest.py b/tests/components/whois/conftest.py index 1ea31621e88..c9d25e24b13 100644 --- a/tests/components/whois/conftest.py +++ b/tests/components/whois/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Whois integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/whois/test_config_flow.py b/tests/components/whois/test_config_flow.py index 91aa207d60f..1d3f1a8c6d2 100644 --- a/tests/components/whois/test_config_flow.py +++ b/tests/components/whois/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Whois config flow.""" + from unittest.mock import AsyncMock, MagicMock import pytest diff --git a/tests/components/whois/test_diagnostics.py b/tests/components/whois/test_diagnostics.py index a134c2136a3..49869fb8bf7 100644 --- a/tests/components/whois/test_diagnostics.py +++ b/tests/components/whois/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Whois integration.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/whois/test_init.py b/tests/components/whois/test_init.py index 1cf2fb7b5ea..0765661c574 100644 --- a/tests/components/whois/test_init.py +++ b/tests/components/whois/test_init.py @@ -1,4 +1,5 @@ """Tests for the Whois integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/whois/test_sensor.py b/tests/components/whois/test_sensor.py index a7c1ad82291..b9619d6cfe0 100644 --- a/tests/components/whois/test_sensor.py +++ b/tests/components/whois/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Whois integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/wiffi/conftest.py b/tests/components/wiffi/conftest.py index bceded737c6..644c3c460ed 100644 --- a/tests/components/wiffi/conftest.py +++ b/tests/components/wiffi/conftest.py @@ -1,4 +1,5 @@ """Configuration for Wiffi tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/wilight/conftest.py b/tests/components/wilight/conftest.py index b20a7757e22..dc8bef004ce 100644 --- a/tests/components/wilight/conftest.py +++ b/tests/components/wilight/conftest.py @@ -1,2 +1,3 @@ """wilight conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/wilight/test_cover.py b/tests/components/wilight/test_cover.py index ce0a65ca29a..93da57a7f7f 100644 --- a/tests/components/wilight/test_cover.py +++ b/tests/components/wilight/test_cover.py @@ -1,4 +1,5 @@ """Tests for the WiLight integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/wilight/test_fan.py b/tests/components/wilight/test_fan.py index dc3ad57e11f..7b2e9550c53 100644 --- a/tests/components/wilight/test_fan.py +++ b/tests/components/wilight/test_fan.py @@ -1,4 +1,5 @@ """Tests for the WiLight integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/wilight/test_init.py b/tests/components/wilight/test_init.py index 860fd8ea54d..aed45fcb023 100644 --- a/tests/components/wilight/test_init.py +++ b/tests/components/wilight/test_init.py @@ -1,4 +1,5 @@ """Tests for the WiLight integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/wilight/test_light.py b/tests/components/wilight/test_light.py index f82d493e70c..44c0060c5bb 100644 --- a/tests/components/wilight/test_light.py +++ b/tests/components/wilight/test_light.py @@ -1,4 +1,5 @@ """Tests for the WiLight integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/wilight/test_switch.py b/tests/components/wilight/test_switch.py index 32b54a9756d..6026cec9847 100644 --- a/tests/components/wilight/test_switch.py +++ b/tests/components/wilight/test_switch.py @@ -1,4 +1,5 @@ """Tests for the WiLight integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/withings/conftest.py b/tests/components/withings/conftest.py index 7f15c5e0252..781024c86f7 100644 --- a/tests/components/withings/conftest.py +++ b/tests/components/withings/conftest.py @@ -1,4 +1,5 @@ """Fixtures for tests.""" + from datetime import timedelta import time from unittest.mock import AsyncMock, patch diff --git a/tests/components/withings/test_binary_sensor.py b/tests/components/withings/test_binary_sensor.py index c93c4522684..56fc8bc49de 100644 --- a/tests/components/withings/test_binary_sensor.py +++ b/tests/components/withings/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Withings component.""" + from unittest.mock import AsyncMock from aiohttp.client_exceptions import ClientResponseError diff --git a/tests/components/withings/test_calendar.py b/tests/components/withings/test_calendar.py index 014beb7a233..060a1baa54d 100644 --- a/tests/components/withings/test_calendar.py +++ b/tests/components/withings/test_calendar.py @@ -1,4 +1,5 @@ """Tests for the Withings calendar.""" + from datetime import date, timedelta from http import HTTPStatus from unittest.mock import AsyncMock diff --git a/tests/components/withings/test_config_flow.py b/tests/components/withings/test_config_flow.py index f8f8f62becf..9852461f5e2 100644 --- a/tests/components/withings/test_config_flow.py +++ b/tests/components/withings/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for config flow.""" + from unittest.mock import AsyncMock, patch from homeassistant.components.withings.const import DOMAIN diff --git a/tests/components/withings/test_diagnostics.py b/tests/components/withings/test_diagnostics.py index 928eccdde0f..9d8dc03e52d 100644 --- a/tests/components/withings/test_diagnostics.py +++ b/tests/components/withings/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Withings integration.""" + from unittest.mock import AsyncMock, patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index 390fbc3bbc3..4caf83bef91 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -1,4 +1,5 @@ """Tests for the Withings component.""" + from datetime import timedelta from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index 88018d54877..72da4b9d973 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Withings component.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index b1a56f6c624..6ee77e83fb6 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WiZ Platform config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/wiz/test_diagnostics.py b/tests/components/wiz/test_diagnostics.py index ef26e63069b..07178d5e93b 100644 --- a/tests/components/wiz/test_diagnostics.py +++ b/tests/components/wiz/test_diagnostics.py @@ -1,4 +1,5 @@ """Test WiZ diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/wled/conftest.py b/tests/components/wled/conftest.py index bbbdd4e1cbe..ce0c7c28f6a 100644 --- a/tests/components/wled/conftest.py +++ b/tests/components/wled/conftest.py @@ -1,4 +1,5 @@ """Fixtures for WLED integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/wled/test_button.py b/tests/components/wled/test_button.py index 92a13baf43c..ef662fb4ded 100644 --- a/tests/components/wled/test_button.py +++ b/tests/components/wled/test_button.py @@ -1,4 +1,5 @@ """Tests for the WLED button platform.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/wled/test_config_flow.py b/tests/components/wled/test_config_flow.py index 949916aaccc..fc2a11c3e46 100644 --- a/tests/components/wled/test_config_flow.py +++ b/tests/components/wled/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the WLED config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/wled/test_diagnostics.py b/tests/components/wled/test_diagnostics.py index 38e7ebe3e25..494e420ba77 100644 --- a/tests/components/wled/test_diagnostics.py +++ b/tests/components/wled/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the WLED integration.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/wled/test_sensor.py b/tests/components/wled/test_sensor.py index db68bc2e454..d72e6581549 100644 --- a/tests/components/wled/test_sensor.py +++ b/tests/components/wled/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the WLED sensor platform.""" + from datetime import datetime from unittest.mock import MagicMock, patch diff --git a/tests/components/wled/test_update.py b/tests/components/wled/test_update.py index d35f0712400..c576cdf16f9 100644 --- a/tests/components/wled/test_update.py +++ b/tests/components/wled/test_update.py @@ -1,4 +1,5 @@ """Tests for the WLED update platform.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/wolflink/test_config_flow.py b/tests/components/wolflink/test_config_flow.py index 5be2dbf78f7..5760a54f5ce 100644 --- a/tests/components/wolflink/test_config_flow.py +++ b/tests/components/wolflink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Wolf SmartSet Service config flow.""" + from unittest.mock import patch from httpcore import ConnectError diff --git a/tests/components/workday/conftest.py b/tests/components/workday/conftest.py index a9e4dd2ffd0..1f3d9bcaabc 100644 --- a/tests/components/workday/conftest.py +++ b/tests/components/workday/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Workday integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index a359d83d87d..a3fba852f60 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests the Home Assistant workday binary sensor.""" + from datetime import date, datetime from typing import Any diff --git a/tests/components/workday/test_config_flow.py b/tests/components/workday/test_config_flow.py index fb0d78365e8..75677143ecb 100644 --- a/tests/components/workday/test_config_flow.py +++ b/tests/components/workday/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Workday config flow.""" + from __future__ import annotations from datetime import datetime diff --git a/tests/components/workday/test_init.py b/tests/components/workday/test_init.py index 047409b5078..e9de315e1d1 100644 --- a/tests/components/workday/test_init.py +++ b/tests/components/workday/test_init.py @@ -1,4 +1,5 @@ """Test Workday component setup process.""" + from __future__ import annotations from datetime import datetime diff --git a/tests/components/ws66i/test_config_flow.py b/tests/components/ws66i/test_config_flow.py index aeeedcd89b3..aa5e9005394 100644 --- a/tests/components/ws66i/test_config_flow.py +++ b/tests/components/ws66i/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WS66i 6-Zone Amplifier config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/ws66i/test_init.py b/tests/components/ws66i/test_init.py index 7110a598e3e..9938ed84303 100644 --- a/tests/components/ws66i/test_init.py +++ b/tests/components/ws66i/test_init.py @@ -1,4 +1,5 @@ """Test the WS66i 6-Zone Amplifier init file.""" + from unittest.mock import patch from homeassistant.components.ws66i.const import DOMAIN diff --git a/tests/components/ws66i/test_media_player.py b/tests/components/ws66i/test_media_player.py index a79ff96cfd7..08ab646ce28 100644 --- a/tests/components/ws66i/test_media_player.py +++ b/tests/components/ws66i/test_media_player.py @@ -1,4 +1,5 @@ """The tests for WS66i Media player platform.""" + from collections import defaultdict from unittest.mock import patch diff --git a/tests/components/wsdot/test_sensor.py b/tests/components/wsdot/test_sensor.py index 9f3f2472ba6..2ab5f3dc4b5 100644 --- a/tests/components/wsdot/test_sensor.py +++ b/tests/components/wsdot/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the WSDOT platform.""" + from datetime import datetime, timedelta, timezone import re diff --git a/tests/components/wyoming/conftest.py b/tests/components/wyoming/conftest.py index f22ec7e9e16..b6111eaddd8 100644 --- a/tests/components/wyoming/conftest.py +++ b/tests/components/wyoming/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Wyoming tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/wyoming/test_binary_sensor.py b/tests/components/wyoming/test_binary_sensor.py index fba181a63ca..8d4e3c72c56 100644 --- a/tests/components/wyoming/test_binary_sensor.py +++ b/tests/components/wyoming/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test Wyoming binary sensor devices.""" + from homeassistant.components.wyoming.devices import SatelliteDevice from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON diff --git a/tests/components/wyoming/test_config_flow.py b/tests/components/wyoming/test_config_flow.py index f711b56b3bc..f0713448e54 100644 --- a/tests/components/wyoming/test_config_flow.py +++ b/tests/components/wyoming/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Wyoming config flow.""" + from ipaddress import IPv4Address from unittest.mock import AsyncMock, patch diff --git a/tests/components/wyoming/test_devices.py b/tests/components/wyoming/test_devices.py index 0273a7da275..98efb76ab1d 100644 --- a/tests/components/wyoming/test_devices.py +++ b/tests/components/wyoming/test_devices.py @@ -1,4 +1,5 @@ """Test Wyoming devices.""" + from __future__ import annotations from homeassistant.components.assist_pipeline.select import OPTION_PREFERRED diff --git a/tests/components/wyoming/test_init.py b/tests/components/wyoming/test_init.py index 85539f5a164..ad92a317717 100644 --- a/tests/components/wyoming/test_init.py +++ b/tests/components/wyoming/test_init.py @@ -1,4 +1,5 @@ """Test init.""" + from unittest.mock import patch from homeassistant.config_entries import ConfigEntry diff --git a/tests/components/wyoming/test_number.py b/tests/components/wyoming/test_number.py index 084021d61a7..bc6dbc2c677 100644 --- a/tests/components/wyoming/test_number.py +++ b/tests/components/wyoming/test_number.py @@ -1,4 +1,5 @@ """Test Wyoming number.""" + from unittest.mock import patch from homeassistant.components.wyoming.devices import SatelliteDevice diff --git a/tests/components/wyoming/test_select.py b/tests/components/wyoming/test_select.py index 128aab57a1a..e6ec2c4d432 100644 --- a/tests/components/wyoming/test_select.py +++ b/tests/components/wyoming/test_select.py @@ -1,4 +1,5 @@ """Test Wyoming select.""" + from unittest.mock import Mock, patch from homeassistant.components import assist_pipeline diff --git a/tests/components/wyoming/test_stt.py b/tests/components/wyoming/test_stt.py index 1938d44d310..4d1ab6dc4d9 100644 --- a/tests/components/wyoming/test_stt.py +++ b/tests/components/wyoming/test_stt.py @@ -1,4 +1,5 @@ """Test stt.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/wyoming/test_switch.py b/tests/components/wyoming/test_switch.py index 6246ba95003..160712bf3de 100644 --- a/tests/components/wyoming/test_switch.py +++ b/tests/components/wyoming/test_switch.py @@ -1,4 +1,5 @@ """Test Wyoming switch devices.""" + from homeassistant.components.wyoming.devices import SatelliteDevice from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON diff --git a/tests/components/wyoming/test_tts.py b/tests/components/wyoming/test_tts.py index 301074e8ffb..895774bf92a 100644 --- a/tests/components/wyoming/test_tts.py +++ b/tests/components/wyoming/test_tts.py @@ -1,4 +1,5 @@ """Test tts.""" + from __future__ import annotations import io diff --git a/tests/components/xbox/test_config_flow.py b/tests/components/xbox/test_config_flow.py index 9738dc70148..d4f7a697839 100644 --- a/tests/components/xbox/test_config_flow.py +++ b/tests/components/xbox/test_config_flow.py @@ -1,4 +1,5 @@ """Test the xbox config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/xiaomi/test_device_tracker.py b/tests/components/xiaomi/test_device_tracker.py index 9b302160f30..0a576b70bdf 100644 --- a/tests/components/xiaomi/test_device_tracker.py +++ b/tests/components/xiaomi/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the Xiaomi router device tracker platform.""" + from http import HTTPStatus import logging from unittest.mock import MagicMock, call, patch diff --git a/tests/components/xiaomi_aqara/test_config_flow.py b/tests/components/xiaomi_aqara/test_config_flow.py index d15a442a840..a2cf62297ef 100644 --- a/tests/components/xiaomi_aqara/test_config_flow.py +++ b/tests/components/xiaomi_aqara/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Xiaomi Aqara config flow.""" + from ipaddress import ip_address from socket import gaierror from unittest.mock import Mock, patch diff --git a/tests/components/xiaomi_ble/test_config_flow.py b/tests/components/xiaomi_ble/test_config_flow.py index e821f5ec779..1ae2a75e66b 100644 --- a/tests/components/xiaomi_ble/test_config_flow.py +++ b/tests/components/xiaomi_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Xiaomi config flow.""" + from unittest.mock import patch from xiaomi_ble import XiaomiBluetoothDeviceData as DeviceData diff --git a/tests/components/xiaomi_ble/test_sensor.py b/tests/components/xiaomi_ble/test_sensor.py index ceca08a68ee..96957bb686f 100644 --- a/tests/components/xiaomi_ble/test_sensor.py +++ b/tests/components/xiaomi_ble/test_sensor.py @@ -1,4 +1,5 @@ """Test Xiaomi BLE sensors.""" + from datetime import timedelta import time diff --git a/tests/components/xiaomi_miio/test_button.py b/tests/components/xiaomi_miio/test_button.py index 552b302aafe..098c08d189a 100644 --- a/tests/components/xiaomi_miio/test_button.py +++ b/tests/components/xiaomi_miio/test_button.py @@ -1,4 +1,5 @@ """The tests for the xiaomi_miio button component.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index b36924764fe..06c3bf15a1e 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Xiaomi Miio config flow.""" + from ipaddress import ip_address from unittest.mock import Mock, patch diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index 9e823035dd9..a020b4bf8e9 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -1,4 +1,5 @@ """The tests for the Xiaomi vacuum platform.""" + from datetime import datetime, time, timedelta from unittest import mock from unittest.mock import MagicMock, patch diff --git a/tests/components/yale_smart_alarm/conftest.py b/tests/components/yale_smart_alarm/conftest.py index c3f5fcf74b8..816fc922411 100644 --- a/tests/components/yale_smart_alarm/conftest.py +++ b/tests/components/yale_smart_alarm/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Yale Smart Living integration.""" + from __future__ import annotations import json diff --git a/tests/components/yale_smart_alarm/test_config_flow.py b/tests/components/yale_smart_alarm/test_config_flow.py index 90c0b78baf5..5a7f3bc3d08 100644 --- a/tests/components/yale_smart_alarm/test_config_flow.py +++ b/tests/components/yale_smart_alarm/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Yale Smart Living config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/yale_smart_alarm/test_coordinator.py b/tests/components/yale_smart_alarm/test_coordinator.py index 9ee09e9c0f2..5125c817567 100644 --- a/tests/components/yale_smart_alarm/test_coordinator.py +++ b/tests/components/yale_smart_alarm/test_coordinator.py @@ -1,4 +1,5 @@ """The test for the sensibo coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/yale_smart_alarm/test_diagnostics.py b/tests/components/yale_smart_alarm/test_diagnostics.py index dc4c5e8c8d7..23fc77fedcc 100644 --- a/tests/components/yale_smart_alarm/test_diagnostics.py +++ b/tests/components/yale_smart_alarm/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Yale Smart Living diagnostics.""" + from __future__ import annotations from unittest.mock import Mock diff --git a/tests/components/yamaha/test_media_player.py b/tests/components/yamaha/test_media_player.py index 6fc3259a4c0..73885bc8ac7 100644 --- a/tests/components/yamaha/test_media_player.py +++ b/tests/components/yamaha/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the Yamaha Media player platform.""" + from unittest.mock import MagicMock, PropertyMock, call, patch import pytest diff --git a/tests/components/yamaha_musiccast/test_config_flow.py b/tests/components/yamaha_musiccast/test_config_flow.py index 4ce95e418d0..356dc78ded1 100644 --- a/tests/components/yamaha_musiccast/test_config_flow.py +++ b/tests/components/yamaha_musiccast/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from unittest.mock import patch from aiomusiccast import MusicCastConnectionException diff --git a/tests/components/yandextts/test_tts.py b/tests/components/yandextts/test_tts.py index 79e1f9108c5..6a4b7e11ce6 100644 --- a/tests/components/yandextts/test_tts.py +++ b/tests/components/yandextts/test_tts.py @@ -1,4 +1,5 @@ """The tests for the Yandex SpeechKit speech platform.""" + from http import HTTPStatus import pytest diff --git a/tests/components/yardian/conftest.py b/tests/components/yardian/conftest.py index d4f289c4242..985d2303fdf 100644 --- a/tests/components/yardian/conftest.py +++ b/tests/components/yardian/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Yardian tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/yardian/test_config_flow.py b/tests/components/yardian/test_config_flow.py index 5f1fcc940cc..c93f48d1c48 100644 --- a/tests/components/yardian/test_config_flow.py +++ b/tests/components/yardian/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Yardian config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/yeelight/test_binary_sensor.py b/tests/components/yeelight/test_binary_sensor.py index 1410a3a8cdc..5ada7377b08 100644 --- a/tests/components/yeelight/test_binary_sensor.py +++ b/tests/components/yeelight/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Yeelight binary sensor.""" + from unittest.mock import patch from homeassistant.components.yeelight import DOMAIN diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index 512ad78fca0..e04dad88da4 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Yeelight config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index 12f1dd8e182..f9ccc7edb8b 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -1,4 +1,5 @@ """Test Yeelight.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index e16692de990..d61532e3f21 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -1,4 +1,5 @@ """Test the Yeelight light.""" + from datetime import timedelta import logging import socket diff --git a/tests/components/yolink/test_config_flow.py b/tests/components/yolink/test_config_flow.py index 2b6034fd597..60b190025c6 100644 --- a/tests/components/yolink/test_config_flow.py +++ b/tests/components/yolink/test_config_flow.py @@ -1,4 +1,5 @@ """Test yolink config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/youless/test_config_flows.py b/tests/components/youless/test_config_flows.py index 6512103cde0..bc53c55539b 100644 --- a/tests/components/youless/test_config_flows.py +++ b/tests/components/youless/test_config_flows.py @@ -1,4 +1,5 @@ """Test the youless config flow.""" + from unittest.mock import MagicMock, patch from urllib.error import URLError diff --git a/tests/components/youtube/conftest.py b/tests/components/youtube/conftest.py index 8b6ce5d00a2..a90dbba8aaa 100644 --- a/tests/components/youtube/conftest.py +++ b/tests/components/youtube/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the YouTube integration.""" + from collections.abc import Awaitable, Callable, Coroutine import time from typing import Any diff --git a/tests/components/youtube/test_config_flow.py b/tests/components/youtube/test_config_flow.py index c4aacc9603d..c735add840b 100644 --- a/tests/components/youtube/test_config_flow.py +++ b/tests/components/youtube/test_config_flow.py @@ -1,4 +1,5 @@ """Test the YouTube config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/youtube/test_diagnostics.py b/tests/components/youtube/test_diagnostics.py index 4fe16c3a8b6..3a5765b5890 100644 --- a/tests/components/youtube/test_diagnostics.py +++ b/tests/components/youtube/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the YouTube integration.""" + from syrupy import SnapshotAssertion from homeassistant.components.youtube.const import DOMAIN diff --git a/tests/components/youtube/test_sensor.py b/tests/components/youtube/test_sensor.py index 9f0b63bc062..ae0c38306e4 100644 --- a/tests/components/youtube/test_sensor.py +++ b/tests/components/youtube/test_sensor.py @@ -1,4 +1,5 @@ """Sensor tests for the YouTube integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/zamg/conftest.py b/tests/components/zamg/conftest.py index e3d3d384e85..0598e2adfb4 100644 --- a/tests/components/zamg/conftest.py +++ b/tests/components/zamg/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Zamg integration tests.""" + from collections.abc import Generator import json from unittest.mock import MagicMock, patch diff --git a/tests/components/zamg/test_config_flow.py b/tests/components/zamg/test_config_flow.py index e7df8532e26..bc95afa7936 100644 --- a/tests/components/zamg/test_config_flow.py +++ b/tests/components/zamg/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Zamg config flow.""" + from unittest.mock import MagicMock from zamg.exceptions import ZamgApiError diff --git a/tests/components/zamg/test_init.py b/tests/components/zamg/test_init.py index 2d6bc339de5..cda17268478 100644 --- a/tests/components/zamg/test_init.py +++ b/tests/components/zamg/test_init.py @@ -1,4 +1,5 @@ """Test Zamg component init.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index c94b2d66465..b156690d290 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -1,4 +1,5 @@ """Test Zeroconf component setup process.""" + from typing import Any from unittest.mock import call, patch diff --git a/tests/components/zeroconf/test_usage.py b/tests/components/zeroconf/test_usage.py index 81743cb5e93..9162e3eb5af 100644 --- a/tests/components/zeroconf/test_usage.py +++ b/tests/components/zeroconf/test_usage.py @@ -1,4 +1,5 @@ """Test Zeroconf multiple instance protection.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/zerproc/conftest.py b/tests/components/zerproc/conftest.py index 9d6bd9dea23..e6297d2cb14 100644 --- a/tests/components/zerproc/conftest.py +++ b/tests/components/zerproc/conftest.py @@ -1,2 +1,3 @@ """zerproc conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/zerproc/test_config_flow.py b/tests/components/zerproc/test_config_flow.py index 0a493929b67..2d933158c54 100644 --- a/tests/components/zerproc/test_config_flow.py +++ b/tests/components/zerproc/test_config_flow.py @@ -1,4 +1,5 @@ """Test the zerproc config flow.""" + from unittest.mock import patch import pyzerproc diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 2046afca278..56a5590e293 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -1,4 +1,5 @@ """Test the zerproc lights.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/zeversolar/test_config_flow.py b/tests/components/zeversolar/test_config_flow.py index 9ef1c1d6f83..57b7fefefb4 100644 --- a/tests/components/zeversolar/test_config_flow.py +++ b/tests/components/zeversolar/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Zeversolar config flow.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 36d0cbcff97..4c3c4f82a21 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -1,4 +1,5 @@ """Test configuration for the ZHA component.""" + from collections.abc import Callable, Generator import itertools import time diff --git a/tests/components/zha/test_alarm_control_panel.py b/tests/components/zha/test_alarm_control_panel.py index 49ad1b81e3b..18065420e58 100644 --- a/tests/components/zha/test_alarm_control_panel.py +++ b/tests/components/zha/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Test ZHA alarm control panel.""" + from unittest.mock import AsyncMock, call, patch, sentinel import pytest diff --git a/tests/components/zha/test_api.py b/tests/components/zha/test_api.py index c3dac0ddd8c..9e35e482fcf 100644 --- a/tests/components/zha/test_api.py +++ b/tests/components/zha/test_api.py @@ -1,4 +1,5 @@ """Test ZHA API.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/tests/components/zha/test_backup.py b/tests/components/zha/test_backup.py index bee00c5a587..9cf88df1707 100644 --- a/tests/components/zha/test_backup.py +++ b/tests/components/zha/test_backup.py @@ -1,4 +1,5 @@ """Unit tests for ZHA backup platform.""" + from unittest.mock import AsyncMock from zigpy.application import ControllerApplication diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index 5dd7a5653ec..91a800cafd5 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test ZHA binary sensor.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zha/test_button.py b/tests/components/zha/test_button.py index 9eab72b435b..4c0c6845885 100644 --- a/tests/components/zha/test_button.py +++ b/tests/components/zha/test_button.py @@ -1,4 +1,5 @@ """Test ZHA button.""" + from typing import Final from unittest.mock import call, patch diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index d60b4bd1a49..744bbcb9f12 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -1,4 +1,5 @@ """Test ZHA climate.""" + from typing import Literal from unittest.mock import call, patch diff --git a/tests/components/zha/test_cluster_handlers.py b/tests/components/zha/test_cluster_handlers.py index 252148481a7..30d892fb436 100644 --- a/tests/components/zha/test_cluster_handlers.py +++ b/tests/components/zha/test_cluster_handlers.py @@ -1,4 +1,5 @@ """Test ZHA Core cluster handlers.""" + from collections.abc import Callable import logging import math diff --git a/tests/components/zha/test_device.py b/tests/components/zha/test_device.py index 411d7081577..94994250167 100644 --- a/tests/components/zha/test_device.py +++ b/tests/components/zha/test_device.py @@ -1,4 +1,5 @@ """Test ZHA device switch.""" + from datetime import timedelta import logging import time diff --git a/tests/components/zha/test_device_action.py b/tests/components/zha/test_device_action.py index 229fde89f15..a7b66dea8d7 100644 --- a/tests/components/zha/test_device_action.py +++ b/tests/components/zha/test_device_action.py @@ -1,4 +1,5 @@ """The test for ZHA device automation actions.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/zha/test_device_tracker.py b/tests/components/zha/test_device_tracker.py index aa3c6b7d146..89ea788e5ef 100644 --- a/tests/components/zha/test_device_tracker.py +++ b/tests/components/zha/test_device_tracker.py @@ -1,4 +1,5 @@ """Test ZHA Device Tracker.""" + from datetime import timedelta import time from unittest.mock import patch diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index c3563872873..2698866ebaa 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -1,4 +1,5 @@ """ZHA device automation trigger tests.""" + from datetime import timedelta import time from unittest.mock import patch diff --git a/tests/components/zha/test_diagnostics.py b/tests/components/zha/test_diagnostics.py index c91d9c1ddbc..3493d772a6f 100644 --- a/tests/components/zha/test_diagnostics.py +++ b/tests/components/zha/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the ESPHome integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index c8eba90a372..760f6da5337 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -1,4 +1,5 @@ """Test ZHA device discovery.""" + from collections.abc import Callable import re from typing import Any diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 51b1cd9160c..5da333afdcd 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -1,4 +1,5 @@ """Test ZHA fan.""" + from unittest.mock import AsyncMock, call, patch import pytest diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index bd799187a19..a6473c6007c 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -1,4 +1,5 @@ """Test ZHA light.""" + from datetime import timedelta from unittest.mock import AsyncMock, call, patch, sentinel diff --git a/tests/components/zha/test_lock.py b/tests/components/zha/test_lock.py index 2f1ecb6983d..52b1d891dfd 100644 --- a/tests/components/zha/test_lock.py +++ b/tests/components/zha/test_lock.py @@ -1,4 +1,5 @@ """Test ZHA lock.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zha/test_logbook.py b/tests/components/zha/test_logbook.py index b0d7d7579f3..889c73362ae 100644 --- a/tests/components/zha/test_logbook.py +++ b/tests/components/zha/test_logbook.py @@ -1,4 +1,5 @@ """ZHA logbook describe events tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zha/test_number.py b/tests/components/zha/test_number.py index 3d888a57a28..4a6482183ab 100644 --- a/tests/components/zha/test_number.py +++ b/tests/components/zha/test_number.py @@ -1,4 +1,5 @@ """Test ZHA analog output.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/zha/test_registries.py b/tests/components/zha/test_registries.py index 80845cf9866..317f47f1eea 100644 --- a/tests/components/zha/test_registries.py +++ b/tests/components/zha/test_registries.py @@ -1,4 +1,5 @@ """Test ZHA registries.""" + from __future__ import annotations import typing diff --git a/tests/components/zha/test_repairs.py b/tests/components/zha/test_repairs.py index 0efff5ecb52..5d5dbe8d147 100644 --- a/tests/components/zha/test_repairs.py +++ b/tests/components/zha/test_repairs.py @@ -1,4 +1,5 @@ """Test ZHA repairs.""" + from collections.abc import Callable from http import HTTPStatus import logging diff --git a/tests/components/zha/test_select.py b/tests/components/zha/test_select.py index 549a123aefb..e044281d2a1 100644 --- a/tests/components/zha/test_select.py +++ b/tests/components/zha/test_select.py @@ -1,4 +1,5 @@ """Test ZHA select entities.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index a7047b8dcd4..0ff5e91f4a9 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -1,4 +1,5 @@ """Test ZHA sensor.""" + from datetime import timedelta import math from unittest.mock import MagicMock, patch diff --git a/tests/components/zha/test_silabs_multiprotocol.py b/tests/components/zha/test_silabs_multiprotocol.py index 074484e6d24..03c845269e0 100644 --- a/tests/components/zha/test_silabs_multiprotocol.py +++ b/tests/components/zha/test_silabs_multiprotocol.py @@ -1,4 +1,5 @@ """Test ZHA Silicon Labs Multiprotocol support.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/tests/components/zha/test_siren.py b/tests/components/zha/test_siren.py index b953d833330..dae04c45fef 100644 --- a/tests/components/zha/test_siren.py +++ b/tests/components/zha/test_siren.py @@ -1,4 +1,5 @@ """Test zha siren.""" + from datetime import timedelta from unittest.mock import ANY, call, patch diff --git a/tests/components/zha/test_switch.py b/tests/components/zha/test_switch.py index 9a9fbc2b50e..644062198f9 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -1,4 +1,5 @@ """Test ZHA switch.""" + from unittest.mock import AsyncMock, call, patch import pytest diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index 29be109c673..31c28e31646 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -1,4 +1,5 @@ """Test ZHA firmware updates.""" + from unittest.mock import AsyncMock, call, patch import pytest diff --git a/tests/components/zha/test_websocket_api.py b/tests/components/zha/test_websocket_api.py index bafea7e1965..4bd6540f411 100644 --- a/tests/components/zha/test_websocket_api.py +++ b/tests/components/zha/test_websocket_api.py @@ -1,4 +1,5 @@ """Test ZHA WebSocket API.""" + from __future__ import annotations from binascii import unhexlify diff --git a/tests/components/zodiac/test_config_flow.py b/tests/components/zodiac/test_config_flow.py index 4b5baefb0f2..15e8bb04ef6 100644 --- a/tests/components/zodiac/test_config_flow.py +++ b/tests/components/zodiac/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Zodiac config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zodiac/test_sensor.py b/tests/components/zodiac/test_sensor.py index 9fa151c87d5..3d43fe60a5a 100644 --- a/tests/components/zodiac/test_sensor.py +++ b/tests/components/zodiac/test_sensor.py @@ -1,4 +1,5 @@ """The test for the zodiac sensor platform.""" + from datetime import datetime from unittest.mock import patch diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 2924e6654e2..08e96c104d2 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -1,4 +1,5 @@ """Test zone component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index f4d7ea0a754..64bc981de11 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -1,4 +1,5 @@ """Provide common test tools for Z-Wave JS.""" + from __future__ import annotations from copy import deepcopy diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index bf5ad88447e..ed84d155e1d 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS Websocket API.""" + from copy import deepcopy from http import HTTPStatus import json diff --git a/tests/components/zwave_js/test_binary_sensor.py b/tests/components/zwave_js/test_binary_sensor.py index a3ae9954d2f..61ceb82263e 100644 --- a/tests/components/zwave_js/test_binary_sensor.py +++ b/tests/components/zwave_js/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS binary sensor platform.""" + from zwave_js_server.event import Event from zwave_js_server.model.node import Node diff --git a/tests/components/zwave_js/test_device_action.py b/tests/components/zwave_js/test_device_action.py index ce2b916b7a1..46686be0994 100644 --- a/tests/components/zwave_js/test_device_action.py +++ b/tests/components/zwave_js/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Z-Wave JS device actions.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py index f7aacec36ac..4c3cfd5d8a0 100644 --- a/tests/components/zwave_js/test_device_condition.py +++ b/tests/components/zwave_js/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Z-Wave JS device conditions.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index f9615c84e1d..0835ef01162 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Z-Wave JS device triggers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zwave_js/test_event.py b/tests/components/zwave_js/test_event.py index 12187d3d227..1db02662f4e 100644 --- a/tests/components/zwave_js/test_event.py +++ b/tests/components/zwave_js/test_event.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS event platform.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/zwave_js/test_events.py b/tests/components/zwave_js/test_events.py index 1e91b9338fa..0bb6376a02b 100644 --- a/tests/components/zwave_js/test_events.py +++ b/tests/components/zwave_js/test_events.py @@ -1,4 +1,5 @@ """Test Z-Wave JS events.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/zwave_js/test_humidifier.py b/tests/components/zwave_js/test_humidifier.py index 23e2dc68314..261e09babee 100644 --- a/tests/components/zwave_js/test_humidifier.py +++ b/tests/components/zwave_js/test_humidifier.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS humidifier platform.""" + from zwave_js_server.const import CommandClass from zwave_js_server.const.command_class.humidity_control import HumidityControlMode from zwave_js_server.event import Event diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index f5b53f6a76e..0f41ae7dbaa 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS light platform.""" + from copy import deepcopy from zwave_js_server.event import Event diff --git a/tests/components/zwave_js/test_logbook.py b/tests/components/zwave_js/test_logbook.py index c4e601da2fc..e42a2b2c56e 100644 --- a/tests/components/zwave_js/test_logbook.py +++ b/tests/components/zwave_js/test_logbook.py @@ -1,4 +1,5 @@ """The tests for Z-Wave JS logbook.""" + from zwave_js_server.const import CommandClass from homeassistant.components.zwave_js.const import ( diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py index b05d9e46f73..e05346b442a 100644 --- a/tests/components/zwave_js/test_number.py +++ b/tests/components/zwave_js/test_number.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS number platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/zwave_js/test_repairs.py b/tests/components/zwave_js/test_repairs.py index d2b702089f2..77191982b6e 100644 --- a/tests/components/zwave_js/test_repairs.py +++ b/tests/components/zwave_js/test_repairs.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS repairs module.""" + from copy import deepcopy from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/zwave_js/test_select.py b/tests/components/zwave_js/test_select.py index 1cbdb8799f3..ee2d14cfcba 100644 --- a/tests/components/zwave_js/test_select.py +++ b/tests/components/zwave_js/test_select.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS number platform.""" + from unittest.mock import MagicMock from zwave_js_server.const import CURRENT_VALUE_PROPERTY, CommandClass diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 8697dad2e7b..633a4dbf7de 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS services.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/zwave_js/test_siren.py b/tests/components/zwave_js/test_siren.py index 6df5881107a..4eb872954d1 100644 --- a/tests/components/zwave_js/test_siren.py +++ b/tests/components/zwave_js/test_siren.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS siren platform.""" + from zwave_js_server.event import Event from homeassistant.components.siren import ( diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index 26b9459cfc2..23c97913400 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -1,4 +1,5 @@ """The tests for Z-Wave JS automation triggers.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/zwave_me/test_config_flow.py b/tests/components/zwave_me/test_config_flow.py index 145cecd58c8..f84b74b2f0e 100644 --- a/tests/components/zwave_me/test_config_flow.py +++ b/tests/components/zwave_me/test_config_flow.py @@ -1,4 +1,5 @@ """Test the zwave_me config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/zwave_me/test_remove_stale_devices.py b/tests/components/zwave_me/test_remove_stale_devices.py index d5496255add..8117237ba15 100644 --- a/tests/components/zwave_me/test_remove_stale_devices.py +++ b/tests/components/zwave_me/test_remove_stale_devices.py @@ -1,4 +1,5 @@ """Test the zwave_me removal of stale devices.""" + from unittest.mock import patch import uuid From 7d6251ca080739501e0f9302b5b0b7595ab6e051 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:47:22 +0100 Subject: [PATCH 0543/1691] Add empty line after module docstring [tests q-s] (#112711) --- tests/components/qbittorrent/conftest.py | 1 + tests/components/qingping/test_binary_sensor.py | 1 + tests/components/qingping/test_config_flow.py | 1 + tests/components/qingping/test_sensor.py | 1 + tests/components/qnap/conftest.py | 1 + tests/components/qnap/test_config_flow.py | 1 + tests/components/rabbitair/test_config_flow.py | 1 + tests/components/rachio/test_config_flow.py | 1 + tests/components/radarr/test_calendar.py | 1 + tests/components/radarr/test_config_flow.py | 1 + tests/components/radio_browser/conftest.py | 1 + tests/components/radio_browser/test_config_flow.py | 1 + tests/components/rainforest_eagle/conftest.py | 1 + tests/components/rainforest_eagle/test_config_flow.py | 1 + tests/components/rainforest_eagle/test_diagnostics.py | 1 + tests/components/rainforest_eagle/test_sensor.py | 1 + tests/components/rainforest_raven/test_config_flow.py | 1 + tests/components/rainforest_raven/test_coordinator.py | 1 + tests/components/rainforest_raven/test_diagnostics.py | 1 + tests/components/rainmachine/test_config_flow.py | 1 + tests/components/rainmachine/test_diagnostics.py | 1 + tests/components/random/test_binary_sensor.py | 1 + tests/components/random/test_config_flow.py | 1 + tests/components/random/test_sensor.py | 1 + tests/components/rapt_ble/test_config_flow.py | 1 + tests/components/raspberry_pi/test_config_flow.py | 1 + tests/components/raspberry_pi/test_hardware.py | 1 + tests/components/raspberry_pi/test_init.py | 1 + tests/components/rdw/conftest.py | 1 + tests/components/rdw/test_binary_sensor.py | 1 + tests/components/rdw/test_diagnostics.py | 1 + tests/components/rdw/test_init.py | 1 + tests/components/rdw/test_sensor.py | 1 + tests/components/recollect_waste/conftest.py | 1 + tests/components/recollect_waste/test_config_flow.py | 1 + tests/components/recollect_waste/test_diagnostics.py | 1 + .../recorder/auto_repairs/statistics/test_duplicates.py | 1 + tests/components/recorder/common.py | 1 + tests/components/recorder/db_schema_23.py | 1 + tests/components/recorder/db_schema_23_with_newer_columns.py | 1 + tests/components/recorder/db_schema_25.py | 1 + tests/components/recorder/db_schema_28.py | 1 + tests/components/recorder/db_schema_30.py | 1 + tests/components/recorder/db_schema_32.py | 1 + tests/components/recorder/table_managers/test_recorder_runs.py | 1 + tests/components/recorder/table_managers/test_statistics_meta.py | 1 + tests/components/recorder/test_backup.py | 1 + tests/components/recorder/test_entity_registry.py | 1 + tests/components/recorder/test_history.py | 1 + tests/components/recorder/test_history_db_schema_30.py | 1 + tests/components/recorder/test_history_db_schema_32.py | 1 + tests/components/recorder/test_init.py | 1 + tests/components/recorder/test_models.py | 1 + tests/components/recorder/test_models_legacy.py | 1 + tests/components/recorder/test_purge.py | 1 + tests/components/recorder/test_statistics.py | 1 + tests/components/recorder/test_statistics_v23_migration.py | 1 + tests/components/recorder/test_system_health.py | 1 + tests/components/recorder/test_util.py | 1 + tests/components/recorder/test_v32_migration.py | 1 + tests/components/recovery_mode/test_init.py | 1 + tests/components/refoss/conftest.py | 1 + tests/components/refoss/test_config_flow.py | 1 + tests/components/remember_the_milk/test_init.py | 1 + tests/components/remote/test_device_condition.py | 1 + tests/components/remote/test_device_trigger.py | 1 + tests/components/remote/test_significant_change.py | 1 + tests/components/renault/conftest.py | 1 + tests/components/renault/const.py | 1 + tests/components/renault/test_binary_sensor.py | 1 + tests/components/renault/test_button.py | 1 + tests/components/renault/test_config_flow.py | 1 + tests/components/renault/test_device_tracker.py | 1 + tests/components/renault/test_init.py | 1 + tests/components/renault/test_select.py | 1 + tests/components/renault/test_sensor.py | 1 + tests/components/renault/test_services.py | 1 + tests/components/renson/test_config_flow.py | 1 + tests/components/reolink/conftest.py | 1 + tests/components/reolink/test_config_flow.py | 1 + tests/components/reolink/test_init.py | 1 + tests/components/reolink/test_media_source.py | 1 + tests/components/repairs/test_init.py | 1 + tests/components/repairs/test_websocket_api.py | 1 + tests/components/rest/test_notify.py | 1 + tests/components/rest/test_sensor.py | 1 + tests/components/rest/test_switch.py | 1 + tests/components/rest_command/conftest.py | 1 + tests/components/rflink/conftest.py | 1 + tests/components/rflink/test_binary_sensor.py | 1 + tests/components/rflink/test_cover.py | 1 + tests/components/rflink/test_sensor.py | 1 + tests/components/rflink/test_switch.py | 1 + tests/components/rflink/test_utils.py | 1 + tests/components/rfxtrx/conftest.py | 1 + tests/components/rfxtrx/test_cover.py | 1 + tests/components/rfxtrx/test_device_action.py | 1 + tests/components/rfxtrx/test_device_trigger.py | 1 + tests/components/rfxtrx/test_event.py | 1 + tests/components/rfxtrx/test_init.py | 1 + tests/components/rfxtrx/test_light.py | 1 + tests/components/rfxtrx/test_siren.py | 1 + tests/components/rfxtrx/test_switch.py | 1 + tests/components/rhasspy/test_config_flow.py | 1 + tests/components/rhasspy/test_init.py | 1 + tests/components/ridwell/conftest.py | 1 + tests/components/ridwell/test_config_flow.py | 1 + tests/components/ridwell/test_diagnostics.py | 1 + tests/components/ring/common.py | 1 + tests/components/ring/conftest.py | 1 + tests/components/ring/test_binary_sensor.py | 1 + tests/components/ring/test_config_flow.py | 1 + tests/components/risco/conftest.py | 1 + tests/components/risco/test_alarm_control_panel.py | 1 + tests/components/risco/test_binary_sensor.py | 1 + tests/components/risco/test_config_flow.py | 1 + tests/components/risco/test_sensor.py | 1 + tests/components/risco/test_switch.py | 1 + tests/components/risco/util.py | 1 + tests/components/rituals_perfume_genie/common.py | 1 + tests/components/rituals_perfume_genie/test_binary_sensor.py | 1 + tests/components/rituals_perfume_genie/test_config_flow.py | 1 + tests/components/rituals_perfume_genie/test_diagnostics.py | 1 + tests/components/rituals_perfume_genie/test_init.py | 1 + tests/components/rituals_perfume_genie/test_number.py | 1 + tests/components/rituals_perfume_genie/test_sensor.py | 1 + tests/components/rituals_perfume_genie/test_switch.py | 1 + tests/components/roborock/conftest.py | 1 + tests/components/roborock/mock_data.py | 1 + tests/components/roborock/test_button.py | 1 + tests/components/roborock/test_config_flow.py | 1 + tests/components/roborock/test_init.py | 1 + tests/components/roborock/test_number.py | 1 + tests/components/roborock/test_select.py | 1 + tests/components/roborock/test_sensor.py | 1 + tests/components/roborock/test_switch.py | 1 + tests/components/roborock/test_time.py | 1 + tests/components/roku/conftest.py | 1 + tests/components/roku/test_binary_sensor.py | 1 + tests/components/roku/test_diagnostics.py | 1 + tests/components/roku/test_init.py | 1 + tests/components/roku/test_media_player.py | 1 + tests/components/roku/test_remote.py | 1 + tests/components/roku/test_select.py | 1 + tests/components/roku/test_sensor.py | 1 + tests/components/romy/test_config_flow.py | 1 + tests/components/roomba/test_config_flow.py | 1 + tests/components/roon/test_config_flow.py | 1 + tests/components/rpi_power/test_binary_sensor.py | 1 + tests/components/rpi_power/test_config_flow.py | 1 + tests/components/rss_feed_template/test_init.py | 1 + tests/components/rtsp_to_webrtc/test_diagnostics.py | 1 + tests/components/ruckus_unleashed/test_config_flow.py | 1 + tests/components/ruckus_unleashed/test_device_tracker.py | 1 + tests/components/ruckus_unleashed/test_init.py | 1 + tests/components/ruuvi_gateway/consts.py | 1 + tests/components/ruuvi_gateway/test_config_flow.py | 1 + tests/components/ruuvi_gateway/utils.py | 1 + tests/components/ruuvitag_ble/fixtures.py | 1 + tests/components/ruuvitag_ble/test_config_flow.py | 1 + tests/components/rympro/test_config_flow.py | 1 + tests/components/sabnzbd/conftest.py | 1 + tests/components/sabnzbd/test_config_flow.py | 1 + tests/components/sabnzbd/test_init.py | 1 + tests/components/samsungtv/conftest.py | 1 + tests/components/samsungtv/const.py | 1 + tests/components/samsungtv/test_config_flow.py | 1 + tests/components/samsungtv/test_diagnostics.py | 1 + tests/components/samsungtv/test_init.py | 1 + tests/components/samsungtv/test_media_player.py | 1 + tests/components/samsungtv/test_remote.py | 1 + tests/components/samsungtv/test_trigger.py | 1 + tests/components/scene/common.py | 1 + tests/components/schedule/test_init.py | 1 + tests/components/schedule/test_recorder.py | 1 + tests/components/schlage/conftest.py | 1 + tests/components/schlage/test_config_flow.py | 1 + tests/components/schlage/test_switch.py | 1 + tests/components/scrape/conftest.py | 1 + tests/components/scrape/test_config_flow.py | 1 + tests/components/scrape/test_init.py | 1 + tests/components/scrape/test_sensor.py | 1 + tests/components/screenlogic/test_config_flow.py | 1 + tests/components/screenlogic/test_data.py | 1 + tests/components/screenlogic/test_diagnostics.py | 1 + tests/components/screenlogic/test_init.py | 1 + tests/components/script/test_recorder.py | 1 + tests/components/season/conftest.py | 1 + tests/components/season/test_config_flow.py | 1 + tests/components/season/test_init.py | 1 + tests/components/season/test_sensor.py | 1 + tests/components/select/test_device_condition.py | 1 + tests/components/select/test_device_trigger.py | 1 + tests/components/select/test_init.py | 1 + tests/components/select/test_recorder.py | 1 + tests/components/select/test_significant_change.py | 1 + tests/components/sense/test_config_flow.py | 1 + tests/components/sensibo/conftest.py | 1 + tests/components/sensibo/test_binary_sensor.py | 1 + tests/components/sensibo/test_button.py | 1 + tests/components/sensibo/test_climate.py | 1 + tests/components/sensibo/test_config_flow.py | 1 + tests/components/sensibo/test_coordinator.py | 1 + tests/components/sensibo/test_diagnostics.py | 1 + tests/components/sensibo/test_entity.py | 1 + tests/components/sensibo/test_init.py | 1 + tests/components/sensibo/test_number.py | 1 + tests/components/sensibo/test_select.py | 1 + tests/components/sensibo/test_sensor.py | 1 + tests/components/sensibo/test_switch.py | 1 + tests/components/sensibo/test_update.py | 1 + tests/components/sensirion_ble/fixtures.py | 1 + tests/components/sensirion_ble/test_config_flow.py | 1 + tests/components/sensor/test_device_trigger.py | 1 + tests/components/sensor/test_init.py | 1 + tests/components/sensor/test_recorder.py | 1 + tests/components/sensor/test_recorder_missing_stats.py | 1 + tests/components/sensor/test_websocket_api.py | 1 + tests/components/sensorpro/test_config_flow.py | 1 + tests/components/sensorpro/test_sensor.py | 1 + tests/components/sensorpush/test_config_flow.py | 1 + tests/components/sensorpush/test_sensor.py | 1 + tests/components/sentry/conftest.py | 1 + tests/components/senz/test_config_flow.py | 1 + tests/components/seventeentrack/test_sensor.py | 1 + tests/components/sfr_box/conftest.py | 1 + tests/components/sfr_box/test_binary_sensor.py | 1 + tests/components/sfr_box/test_button.py | 1 + tests/components/sfr_box/test_diagnostics.py | 1 + tests/components/sfr_box/test_init.py | 1 + tests/components/sfr_box/test_sensor.py | 1 + tests/components/sharkiq/test_config_flow.py | 1 + tests/components/sharkiq/test_vacuum.py | 1 + tests/components/shell_command/test_init.py | 1 + tests/components/shelly/bluetooth/test_scanner.py | 1 + tests/components/shelly/conftest.py | 1 + tests/components/shelly/test_binary_sensor.py | 1 + tests/components/shelly/test_button.py | 1 + tests/components/shelly/test_climate.py | 1 + tests/components/shelly/test_config_flow.py | 1 + tests/components/shelly/test_coordinator.py | 1 + tests/components/shelly/test_cover.py | 1 + tests/components/shelly/test_device_trigger.py | 1 + tests/components/shelly/test_diagnostics.py | 1 + tests/components/shelly/test_event.py | 1 + tests/components/shelly/test_init.py | 1 + tests/components/shelly/test_light.py | 1 + tests/components/shelly/test_logbook.py | 1 + tests/components/shelly/test_number.py | 1 + tests/components/shelly/test_sensor.py | 1 + tests/components/shelly/test_switch.py | 1 + tests/components/shelly/test_update.py | 1 + tests/components/shelly/test_utils.py | 1 + tests/components/shelly/test_valve.py | 1 + tests/components/shopping_list/conftest.py | 1 + tests/components/shopping_list/test_config_flow.py | 1 + tests/components/shopping_list/test_init.py | 1 + tests/components/shopping_list/test_intent.py | 1 + tests/components/sia/test_config_flow.py | 1 + tests/components/sigfox/test_sensor.py | 1 + tests/components/sighthound/test_image_processing.py | 1 + tests/components/signal_messenger/conftest.py | 1 + tests/components/simplepush/test_config_flow.py | 1 + tests/components/simplisafe/test_diagnostics.py | 1 + tests/components/simplisafe/test_init.py | 1 + tests/components/simulated/test_sensor.py | 1 + tests/components/siren/test_init.py | 1 + tests/components/siren/test_recorder.py | 1 + tests/components/skybell/conftest.py | 1 + tests/components/skybell/test_binary_sensor.py | 1 + tests/components/skybell/test_config_flow.py | 1 + tests/components/slack/test_config_flow.py | 1 + tests/components/slack/test_init.py | 1 + tests/components/slack/test_notify.py | 1 + tests/components/sleepiq/conftest.py | 1 + tests/components/sleepiq/test_binary_sensor.py | 1 + tests/components/sleepiq/test_button.py | 1 + tests/components/sleepiq/test_config_flow.py | 1 + tests/components/sleepiq/test_init.py | 1 + tests/components/sleepiq/test_light.py | 1 + tests/components/sleepiq/test_number.py | 1 + tests/components/sleepiq/test_select.py | 1 + tests/components/sleepiq/test_sensor.py | 1 + tests/components/sleepiq/test_switch.py | 1 + tests/components/slimproto/conftest.py | 1 + tests/components/slimproto/test_config_flow.py | 1 + tests/components/sma/conftest.py | 1 + tests/components/sma/test_config_flow.py | 1 + tests/components/sma/test_sensor.py | 1 + tests/components/smappee/test_config_flow.py | 1 + tests/components/smappee/test_init.py | 1 + tests/components/smart_meter_texas/conftest.py | 1 + tests/components/smart_meter_texas/test_config_flow.py | 1 + tests/components/smart_meter_texas/test_init.py | 1 + tests/components/smart_meter_texas/test_sensor.py | 1 + tests/components/smartthings/test_binary_sensor.py | 1 + tests/components/smartthings/test_climate.py | 1 + tests/components/smartthings/test_config_flow.py | 1 + tests/components/smartthings/test_cover.py | 1 + tests/components/smartthings/test_fan.py | 1 + tests/components/smartthings/test_init.py | 1 + tests/components/smartthings/test_light.py | 1 + tests/components/smartthings/test_lock.py | 1 + tests/components/smartthings/test_scene.py | 1 + tests/components/smartthings/test_sensor.py | 1 + tests/components/smartthings/test_smartapp.py | 1 + tests/components/smartthings/test_switch.py | 1 + tests/components/smarttub/test_binary_sensor.py | 1 + tests/components/smarttub/test_config_flow.py | 1 + tests/components/smarttub/test_init.py | 1 + tests/components/smhi/common.py | 1 + tests/components/smhi/test_config_flow.py | 1 + tests/components/smhi/test_init.py | 1 + tests/components/smhi/test_weather.py | 1 + tests/components/smtp/test_notify.py | 1 + tests/components/snapcast/conftest.py | 1 + tests/components/snooz/conftest.py | 1 + tests/components/snooz/test_config_flow.py | 1 + tests/components/snooz/test_fan.py | 1 + tests/components/snooz/test_init.py | 1 + tests/components/solaredge/test_config_flow.py | 1 + tests/components/solaredge/test_coordinator.py | 1 + tests/components/solarlog/test_config_flow.py | 1 + tests/components/solax/test_config_flow.py | 1 + tests/components/soma/test_config_flow.py | 1 + tests/components/somfy_mylink/test_config_flow.py | 1 + tests/components/sonarr/conftest.py | 1 + tests/components/sonarr/test_config_flow.py | 1 + tests/components/sonarr/test_init.py | 1 + tests/components/sonarr/test_sensor.py | 1 + tests/components/songpal/test_init.py | 1 + tests/components/songpal/test_media_player.py | 1 + tests/components/sonos/conftest.py | 1 + tests/components/sonos/test_config_flow.py | 1 + tests/components/sonos/test_helpers.py | 1 + tests/components/sonos/test_media_player.py | 1 + tests/components/sonos/test_number.py | 1 + tests/components/sonos/test_repairs.py | 1 + tests/components/sonos/test_sensor.py | 1 + tests/components/sonos/test_services.py | 1 + tests/components/sonos/test_speaker.py | 1 + tests/components/sonos/test_statistics.py | 1 + tests/components/sonos/test_switch.py | 1 + tests/components/soundtouch/test_config_flow.py | 1 + tests/components/soundtouch/test_media_player.py | 1 + tests/components/spaceapi/test_init.py | 1 + tests/components/spc/test_init.py | 1 + tests/components/speedtestdotnet/conftest.py | 1 + tests/components/speedtestdotnet/test_config_flow.py | 1 + tests/components/speedtestdotnet/test_sensor.py | 1 + tests/components/spider/test_config_flow.py | 1 + tests/components/spotify/test_config_flow.py | 1 + tests/components/sql/test_config_flow.py | 1 + tests/components/sql/test_init.py | 1 + tests/components/sql/test_sensor.py | 1 + tests/components/sql/test_util.py | 1 + tests/components/squeezebox/test_config_flow.py | 1 + tests/components/srp_energy/conftest.py | 1 + tests/components/srp_energy/test_config_flow.py | 1 + tests/components/srp_energy/test_init.py | 1 + tests/components/ssdp/conftest.py | 1 + tests/components/ssdp/test_init.py | 1 + tests/components/starlink/test_config_flow.py | 1 + tests/components/starlink/test_diagnostics.py | 1 + tests/components/starlink/test_init.py | 1 + tests/components/startca/test_sensor.py | 1 + tests/components/statistics/test_sensor.py | 1 + tests/components/statsd/test_init.py | 1 + tests/components/steam_online/test_config_flow.py | 1 + tests/components/steamist/test_config_flow.py | 1 + tests/components/steamist/test_init.py | 1 + tests/components/steamist/test_sensor.py | 1 + tests/components/steamist/test_switch.py | 1 + tests/components/stookalert/test_config_flow.py | 1 + tests/components/stookwijzer/test_config_flow.py | 1 + tests/components/stream/common.py | 1 + tests/components/stream/conftest.py | 1 + tests/components/stream/test_hls.py | 1 + tests/components/streamlabswater/conftest.py | 1 + tests/components/streamlabswater/test_binary_sensor.py | 1 + tests/components/streamlabswater/test_config_flow.py | 1 + tests/components/streamlabswater/test_sensor.py | 1 + tests/components/stt/common.py | 1 + tests/components/stt/test_init.py | 1 + tests/components/stt/test_legacy.py | 1 + tests/components/subaru/conftest.py | 1 + tests/components/subaru/test_config_flow.py | 1 + tests/components/subaru/test_device_tracker.py | 1 + tests/components/subaru/test_init.py | 1 + tests/components/subaru/test_lock.py | 1 + tests/components/subaru/test_sensor.py | 1 + tests/components/suez_water/conftest.py | 1 + tests/components/suez_water/test_config_flow.py | 1 + tests/components/sun/test_config_flow.py | 1 + tests/components/sun/test_init.py | 1 + tests/components/sun/test_recorder.py | 1 + tests/components/sun/test_sensor.py | 1 + tests/components/sun/test_trigger.py | 1 + tests/components/sunweg/test_config_flow.py | 1 + tests/components/surepetcare/conftest.py | 1 + tests/components/surepetcare/test_binary_sensor.py | 1 + tests/components/surepetcare/test_config_flow.py | 1 + tests/components/surepetcare/test_sensor.py | 1 + tests/components/swiss_public_transport/conftest.py | 1 + tests/components/swiss_public_transport/test_config_flow.py | 1 + tests/components/swiss_public_transport/test_init.py | 1 + tests/components/switch/common.py | 1 + tests/components/switch/conftest.py | 1 + tests/components/switch/test_device_condition.py | 1 + tests/components/switch/test_device_trigger.py | 1 + tests/components/switch/test_significant_change.py | 1 + tests/components/switch_as_x/conftest.py | 1 + tests/components/switch_as_x/test_config_flow.py | 1 + tests/components/switch_as_x/test_fan.py | 1 + tests/components/switch_as_x/test_init.py | 1 + tests/components/switch_as_x/test_light.py | 1 + tests/components/switch_as_x/test_lock.py | 1 + tests/components/switch_as_x/test_siren.py | 1 + tests/components/switch_as_x/test_valve.py | 1 + tests/components/switchbot/test_config_flow.py | 1 + tests/components/switchbot_cloud/conftest.py | 1 + tests/components/switchbot_cloud/test_config_flow.py | 1 + tests/components/switcher_kis/conftest.py | 1 + tests/components/switcher_kis/test_button.py | 1 + tests/components/switcher_kis/test_climate.py | 1 + tests/components/switcher_kis/test_config_flow.py | 1 + tests/components/switcher_kis/test_cover.py | 1 + tests/components/switcher_kis/test_init.py | 1 + tests/components/switcher_kis/test_services.py | 1 + tests/components/switcher_kis/test_switch.py | 1 + tests/components/syncthing/test_config_flow.py | 1 + tests/components/synology_dsm/conftest.py | 1 + tests/components/synology_dsm/test_config_flow.py | 1 + tests/components/synology_dsm/test_init.py | 1 + tests/components/system_bridge/test_config_flow.py | 1 + tests/components/system_health/test_init.py | 1 + tests/components/system_log/test_init.py | 1 + tests/components/systemmonitor/conftest.py | 1 + tests/components/systemmonitor/test_binary_sensor.py | 1 + tests/components/systemmonitor/test_config_flow.py | 1 + tests/components/systemmonitor/test_diagnostics.py | 1 + tests/components/systemmonitor/test_init.py | 1 + 442 files changed, 442 insertions(+) diff --git a/tests/components/qbittorrent/conftest.py b/tests/components/qbittorrent/conftest.py index 448f68db81e..9a5ead35a05 100644 --- a/tests/components/qbittorrent/conftest.py +++ b/tests/components/qbittorrent/conftest.py @@ -1,4 +1,5 @@ """Fixtures for testing qBittorrent component.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/qingping/test_binary_sensor.py b/tests/components/qingping/test_binary_sensor.py index f201b3b55ff..b05a213ef5c 100644 --- a/tests/components/qingping/test_binary_sensor.py +++ b/tests/components/qingping/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Qingping binary sensors.""" + from datetime import timedelta import time diff --git a/tests/components/qingping/test_config_flow.py b/tests/components/qingping/test_config_flow.py index aed1b45286a..c5b5dd94cc2 100644 --- a/tests/components/qingping/test_config_flow.py +++ b/tests/components/qingping/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Qingping config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/qingping/test_sensor.py b/tests/components/qingping/test_sensor.py index 12e3ec85c52..135eaf296ea 100644 --- a/tests/components/qingping/test_sensor.py +++ b/tests/components/qingping/test_sensor.py @@ -1,4 +1,5 @@ """Test the Qingping sensors.""" + from datetime import timedelta import time diff --git a/tests/components/qnap/conftest.py b/tests/components/qnap/conftest.py index 7f26763d4d7..5c6d5eb65fc 100644 --- a/tests/components/qnap/conftest.py +++ b/tests/components/qnap/conftest.py @@ -1,4 +1,5 @@ """Setup the QNAP tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/qnap/test_config_flow.py b/tests/components/qnap/test_config_flow.py index 75af07cbf8b..881086b9e10 100644 --- a/tests/components/qnap/test_config_flow.py +++ b/tests/components/qnap/test_config_flow.py @@ -1,4 +1,5 @@ """Test the QNAP config flow.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/rabbitair/test_config_flow.py b/tests/components/rabbitair/test_config_flow.py index b0c85fbf402..5f2295fd7f9 100644 --- a/tests/components/rabbitair/test_config_flow.py +++ b/tests/components/rabbitair/test_config_flow.py @@ -1,4 +1,5 @@ """Test the RabbitAir config flow.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/rachio/test_config_flow.py b/tests/components/rachio/test_config_flow.py index 26083f51e63..a5a4def94bb 100644 --- a/tests/components/rachio/test_config_flow.py +++ b/tests/components/rachio/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Rachio config flow.""" + from ipaddress import ip_address from unittest.mock import MagicMock, patch diff --git a/tests/components/radarr/test_calendar.py b/tests/components/radarr/test_calendar.py index 61e9bc27c9b..e82760cadba 100644 --- a/tests/components/radarr/test_calendar.py +++ b/tests/components/radarr/test_calendar.py @@ -1,4 +1,5 @@ """The tests for Radarr calendar platform.""" + from datetime import timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/radarr/test_config_flow.py b/tests/components/radarr/test_config_flow.py index 5eab7c02bb9..9733393836a 100644 --- a/tests/components/radarr/test_config_flow.py +++ b/tests/components/radarr/test_config_flow.py @@ -1,4 +1,5 @@ """Test Radarr config flow.""" + from unittest.mock import patch from aiopyarr import exceptions diff --git a/tests/components/radio_browser/conftest.py b/tests/components/radio_browser/conftest.py index 5a5b888d944..fa732912dc0 100644 --- a/tests/components/radio_browser/conftest.py +++ b/tests/components/radio_browser/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Radio Browser integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/radio_browser/test_config_flow.py b/tests/components/radio_browser/test_config_flow.py index d958efc4e50..0c0f2f479a8 100644 --- a/tests/components/radio_browser/test_config_flow.py +++ b/tests/components/radio_browser/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Radio Browser config flow.""" + from unittest.mock import AsyncMock from homeassistant.components.radio_browser.const import DOMAIN diff --git a/tests/components/rainforest_eagle/conftest.py b/tests/components/rainforest_eagle/conftest.py index d3a8687f724..9ea607b1db4 100644 --- a/tests/components/rainforest_eagle/conftest.py +++ b/tests/components/rainforest_eagle/conftest.py @@ -1,4 +1,5 @@ """Conftest for rainforest_eagle.""" + from unittest.mock import AsyncMock, Mock, patch import pytest diff --git a/tests/components/rainforest_eagle/test_config_flow.py b/tests/components/rainforest_eagle/test_config_flow.py index d9b66b0feec..4eaf26cb767 100644 --- a/tests/components/rainforest_eagle/test_config_flow.py +++ b/tests/components/rainforest_eagle/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Rainforest Eagle config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/rainforest_eagle/test_diagnostics.py b/tests/components/rainforest_eagle/test_diagnostics.py index a3331244209..ed13c33f7b8 100644 --- a/tests/components/rainforest_eagle/test_diagnostics.py +++ b/tests/components/rainforest_eagle/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Rainforest Eagle diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.components.rainforest_eagle.const import ( CONF_CLOUD_ID, diff --git a/tests/components/rainforest_eagle/test_sensor.py b/tests/components/rainforest_eagle/test_sensor.py index 5e76a81932a..31630913a70 100644 --- a/tests/components/rainforest_eagle/test_sensor.py +++ b/tests/components/rainforest_eagle/test_sensor.py @@ -1,4 +1,5 @@ """Tests for rainforest eagle sensors.""" + from homeassistant.components.rainforest_eagle.const import DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/rainforest_raven/test_config_flow.py b/tests/components/rainforest_raven/test_config_flow.py index c7364c9435e..d7b188d6b14 100644 --- a/tests/components/rainforest_raven/test_config_flow.py +++ b/tests/components/rainforest_raven/test_config_flow.py @@ -1,4 +1,5 @@ """Test Rainforest RAVEn config flow.""" + from unittest.mock import patch from aioraven.device import RAVEnConnectionError diff --git a/tests/components/rainforest_raven/test_coordinator.py b/tests/components/rainforest_raven/test_coordinator.py index 6b29c944aeb..0c716aef6fc 100644 --- a/tests/components/rainforest_raven/test_coordinator.py +++ b/tests/components/rainforest_raven/test_coordinator.py @@ -1,4 +1,5 @@ """Tests for the Rainforest RAVEn data coordinator.""" + from aioraven.device import RAVEnConnectionError import pytest diff --git a/tests/components/rainforest_raven/test_diagnostics.py b/tests/components/rainforest_raven/test_diagnostics.py index 639eacadc76..fe01dc1d0f9 100644 --- a/tests/components/rainforest_raven/test_diagnostics.py +++ b/tests/components/rainforest_raven/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Rainforest Eagle diagnostics.""" + from dataclasses import asdict import pytest diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index 631f1d5a3f8..8b8104fe7a7 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the OpenUV config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/rainmachine/test_diagnostics.py b/tests/components/rainmachine/test_diagnostics.py index 2180bf2a20e..6ea50e5b102 100644 --- a/tests/components/rainmachine/test_diagnostics.py +++ b/tests/components/rainmachine/test_diagnostics.py @@ -1,4 +1,5 @@ """Test RainMachine diagnostics.""" + from regenmaschine.errors import RainMachineError from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/random/test_binary_sensor.py b/tests/components/random/test_binary_sensor.py index 3bcf43ae22e..8884b48a1c1 100644 --- a/tests/components/random/test_binary_sensor.py +++ b/tests/components/random/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the Random binary sensor platform.""" + from unittest.mock import patch from homeassistant.core import HomeAssistant diff --git a/tests/components/random/test_config_flow.py b/tests/components/random/test_config_flow.py index 909e866ea92..261843df78e 100644 --- a/tests/components/random/test_config_flow.py +++ b/tests/components/random/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Random config flow.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/random/test_sensor.py b/tests/components/random/test_sensor.py index 4682ae9078e..d2b953aea75 100644 --- a/tests/components/random/test_sensor.py +++ b/tests/components/random/test_sensor.py @@ -1,4 +1,5 @@ """The test for the random number sensor platform.""" + from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/rapt_ble/test_config_flow.py b/tests/components/rapt_ble/test_config_flow.py index 46b7265b238..b71843bd44f 100644 --- a/tests/components/rapt_ble/test_config_flow.py +++ b/tests/components/rapt_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the RAPT config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/raspberry_pi/test_config_flow.py b/tests/components/raspberry_pi/test_config_flow.py index 68306b3ea9a..05fea6ed3d3 100644 --- a/tests/components/raspberry_pi/test_config_flow.py +++ b/tests/components/raspberry_pi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Raspberry Pi config flow.""" + from unittest.mock import patch from homeassistant.components.raspberry_pi.const import DOMAIN diff --git a/tests/components/raspberry_pi/test_hardware.py b/tests/components/raspberry_pi/test_hardware.py index 430611d1576..19d9a65fadd 100644 --- a/tests/components/raspberry_pi/test_hardware.py +++ b/tests/components/raspberry_pi/test_hardware.py @@ -1,4 +1,5 @@ """Test the Raspberry Pi hardware platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/raspberry_pi/test_init.py b/tests/components/raspberry_pi/test_init.py index 88518cc00b0..ebe04ed8384 100644 --- a/tests/components/raspberry_pi/test_init.py +++ b/tests/components/raspberry_pi/test_init.py @@ -1,4 +1,5 @@ """Test the Raspberry Pi integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/rdw/conftest.py b/tests/components/rdw/conftest.py index 5fe40b0b497..7e9f485eaef 100644 --- a/tests/components/rdw/conftest.py +++ b/tests/components/rdw/conftest.py @@ -1,4 +1,5 @@ """Fixtures for RDW integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/rdw/test_binary_sensor.py b/tests/components/rdw/test_binary_sensor.py index a81c723a7d7..4c21f5f881f 100644 --- a/tests/components/rdw/test_binary_sensor.py +++ b/tests/components/rdw/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the RDW integration.""" + from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.rdw.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON diff --git a/tests/components/rdw/test_diagnostics.py b/tests/components/rdw/test_diagnostics.py index 28b7714fcce..a5e8c72dba1 100644 --- a/tests/components/rdw/test_diagnostics.py +++ b/tests/components/rdw/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the RDW integration.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/rdw/test_init.py b/tests/components/rdw/test_init.py index b31b0aa8d81..6f4454325d5 100644 --- a/tests/components/rdw/test_init.py +++ b/tests/components/rdw/test_init.py @@ -1,4 +1,5 @@ """Tests for the RDW integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from homeassistant.components.rdw.const import DOMAIN diff --git a/tests/components/rdw/test_sensor.py b/tests/components/rdw/test_sensor.py index 3e7ad7ab89e..ef8ce48e7ce 100644 --- a/tests/components/rdw/test_sensor.py +++ b/tests/components/rdw/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the RDW integration.""" + from homeassistant.components.rdw.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorDeviceClass from homeassistant.const import ( diff --git a/tests/components/recollect_waste/conftest.py b/tests/components/recollect_waste/conftest.py index 861d4804f85..746a2203992 100644 --- a/tests/components/recollect_waste/conftest.py +++ b/tests/components/recollect_waste/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for ReCollect Waste.""" + from datetime import date from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/recollect_waste/test_config_flow.py b/tests/components/recollect_waste/test_config_flow.py index 9688400a8ea..a65b0d27a74 100644 --- a/tests/components/recollect_waste/test_config_flow.py +++ b/tests/components/recollect_waste/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the ReCollect Waste config flow.""" + from unittest.mock import AsyncMock, patch from aiorecollect.errors import RecollectError diff --git a/tests/components/recollect_waste/test_diagnostics.py b/tests/components/recollect_waste/test_diagnostics.py index 69ff1596d7c..6c8549786e8 100644 --- a/tests/components/recollect_waste/test_diagnostics.py +++ b/tests/components/recollect_waste/test_diagnostics.py @@ -1,4 +1,5 @@ """Test ReCollect Waste diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant diff --git a/tests/components/recorder/auto_repairs/statistics/test_duplicates.py b/tests/components/recorder/auto_repairs/statistics/test_duplicates.py index c2aee941efc..5dea61f0795 100644 --- a/tests/components/recorder/auto_repairs/statistics/test_duplicates.py +++ b/tests/components/recorder/auto_repairs/statistics/test_duplicates.py @@ -1,4 +1,5 @@ """Test removing statistics duplicates.""" + from collections.abc import Callable import importlib from pathlib import Path diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index d0ed6f15d43..19ee449ae0b 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -1,4 +1,5 @@ """Common test utils for working with recorder.""" + from __future__ import annotations import asyncio diff --git a/tests/components/recorder/db_schema_23.py b/tests/components/recorder/db_schema_23.py index a89599520c0..c145767a838 100644 --- a/tests/components/recorder/db_schema_23.py +++ b/tests/components/recorder/db_schema_23.py @@ -5,6 +5,7 @@ used by Home Assistant Core 2021.11.0, which adds the name column to statistics_meta. It is used to test the schema migration logic. """ + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/recorder/db_schema_23_with_newer_columns.py b/tests/components/recorder/db_schema_23_with_newer_columns.py index 160ddc5761c..f3a2dc39859 100644 --- a/tests/components/recorder/db_schema_23_with_newer_columns.py +++ b/tests/components/recorder/db_schema_23_with_newer_columns.py @@ -9,6 +9,7 @@ allow the recorder to startup successfully. It is used to test the schema migration logic. """ + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/recorder/db_schema_25.py b/tests/components/recorder/db_schema_25.py index 24b5b764c65..84c06a95d2e 100644 --- a/tests/components/recorder/db_schema_25.py +++ b/tests/components/recorder/db_schema_25.py @@ -1,4 +1,5 @@ """Models for SQLAlchemy.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/recorder/db_schema_28.py b/tests/components/recorder/db_schema_28.py index 9df32f1b6c1..3593ad37e59 100644 --- a/tests/components/recorder/db_schema_28.py +++ b/tests/components/recorder/db_schema_28.py @@ -3,6 +3,7 @@ This file contains the model definitions for schema version 28. It is used to test the schema migration logic. """ + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/recorder/db_schema_30.py b/tests/components/recorder/db_schema_30.py index c1a61159c98..facb61a5942 100644 --- a/tests/components/recorder/db_schema_30.py +++ b/tests/components/recorder/db_schema_30.py @@ -3,6 +3,7 @@ This file contains the model definitions for schema version 30. It is used to test the schema migration logic. """ + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/recorder/db_schema_32.py b/tests/components/recorder/db_schema_32.py index e092de28eca..bd245f9013f 100644 --- a/tests/components/recorder/db_schema_32.py +++ b/tests/components/recorder/db_schema_32.py @@ -3,6 +3,7 @@ This file contains the model definitions for schema version 30. It is used to test the schema migration logic. """ + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/recorder/table_managers/test_recorder_runs.py b/tests/components/recorder/table_managers/test_recorder_runs.py index 2946850ec11..41f3a8fef4d 100644 --- a/tests/components/recorder/table_managers/test_recorder_runs.py +++ b/tests/components/recorder/table_managers/test_recorder_runs.py @@ -1,4 +1,5 @@ """Test recorder runs table manager.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/recorder/table_managers/test_statistics_meta.py b/tests/components/recorder/table_managers/test_statistics_meta.py index ab6615c6dd0..a1dc777d80d 100644 --- a/tests/components/recorder/table_managers/test_statistics_meta.py +++ b/tests/components/recorder/table_managers/test_statistics_meta.py @@ -1,4 +1,5 @@ """The tests for the Recorder component.""" + from __future__ import annotations import pytest diff --git a/tests/components/recorder/test_backup.py b/tests/components/recorder/test_backup.py index de2bc52d369..4953504786a 100644 --- a/tests/components/recorder/test_backup.py +++ b/tests/components/recorder/test_backup.py @@ -1,4 +1,5 @@ """Test backup platform for the Recorder integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/recorder/test_entity_registry.py b/tests/components/recorder/test_entity_registry.py index 0d675574e12..22989b761dc 100644 --- a/tests/components/recorder/test_entity_registry.py +++ b/tests/components/recorder/test_entity_registry.py @@ -1,4 +1,5 @@ """The tests for sensor recorder platform.""" + from collections.abc import Callable import pytest diff --git a/tests/components/recorder/test_history.py b/tests/components/recorder/test_history.py index 21af6b01182..432239849aa 100644 --- a/tests/components/recorder/test_history.py +++ b/tests/components/recorder/test_history.py @@ -1,4 +1,5 @@ """The tests the History component.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/recorder/test_history_db_schema_30.py b/tests/components/recorder/test_history_db_schema_30.py index 4f75dc15b15..3df1aa6278e 100644 --- a/tests/components/recorder/test_history_db_schema_30.py +++ b/tests/components/recorder/test_history_db_schema_30.py @@ -1,4 +1,5 @@ """The tests the History component.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/recorder/test_history_db_schema_32.py b/tests/components/recorder/test_history_db_schema_32.py index 477c13d6166..904c232d3f7 100644 --- a/tests/components/recorder/test_history_db_schema_32.py +++ b/tests/components/recorder/test_history_db_schema_32.py @@ -1,4 +1,5 @@ """The tests the History component.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index b84b672f3d4..acfe6189af9 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -1,4 +1,5 @@ """The tests for the Recorder component.""" + from __future__ import annotations import asyncio diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index 639efd0678d..26c25bbb71f 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -1,4 +1,5 @@ """The tests for the Recorder component.""" + from datetime import datetime, timedelta from unittest.mock import PropertyMock diff --git a/tests/components/recorder/test_models_legacy.py b/tests/components/recorder/test_models_legacy.py index f830ac53544..f4cdcd7268b 100644 --- a/tests/components/recorder/test_models_legacy.py +++ b/tests/components/recorder/test_models_legacy.py @@ -1,4 +1,5 @@ """The tests for the Recorder component legacy models.""" + from datetime import datetime, timedelta from unittest.mock import PropertyMock diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 2a9260a28a4..8225fae3d5b 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -1,4 +1,5 @@ """Test data purging.""" + from datetime import datetime, timedelta import json import sqlite3 diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 16033188549..548a9d17502 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -1,4 +1,5 @@ """The tests for sensor recorder platform.""" + from collections.abc import Callable from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/recorder/test_statistics_v23_migration.py b/tests/components/recorder/test_statistics_v23_migration.py index 3aa96e18503..c7b68f29b1c 100644 --- a/tests/components/recorder/test_statistics_v23_migration.py +++ b/tests/components/recorder/test_statistics_v23_migration.py @@ -3,6 +3,7 @@ The v23 schema used for these tests has been slightly modified to add the EventData table to allow the recorder to startup successfully. """ + from functools import partial import importlib import json diff --git a/tests/components/recorder/test_system_health.py b/tests/components/recorder/test_system_health.py index 5adacaf0ab6..456a9d7de36 100644 --- a/tests/components/recorder/test_system_health.py +++ b/tests/components/recorder/test_system_health.py @@ -1,4 +1,5 @@ """Test recorder system health.""" + from unittest.mock import ANY, Mock, patch import pytest diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 0d3ec463825..5f8225e8fe6 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -1,4 +1,5 @@ """Test util methods.""" + from collections.abc import Callable from datetime import UTC, datetime, timedelta import os diff --git a/tests/components/recorder/test_v32_migration.py b/tests/components/recorder/test_v32_migration.py index e423c479df4..bab28f0f90c 100644 --- a/tests/components/recorder/test_v32_migration.py +++ b/tests/components/recorder/test_v32_migration.py @@ -1,4 +1,5 @@ """The tests for recorder platform migrating data from v30.""" + from datetime import timedelta import importlib from pathlib import Path diff --git a/tests/components/recovery_mode/test_init.py b/tests/components/recovery_mode/test_init.py index ec8db443ef1..506cd010725 100644 --- a/tests/components/recovery_mode/test_init.py +++ b/tests/components/recovery_mode/test_init.py @@ -1,4 +1,5 @@ """Tests for the Recovery Mode integration.""" + from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/refoss/conftest.py b/tests/components/refoss/conftest.py index 2fc695bbb2e..d627af5b5ab 100644 --- a/tests/components/refoss/conftest.py +++ b/tests/components/refoss/conftest.py @@ -1,4 +1,5 @@ """Pytest module configuration.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/refoss/test_config_flow.py b/tests/components/refoss/test_config_flow.py index 2a5842ffe46..e16974a9d71 100644 --- a/tests/components/refoss/test_config_flow.py +++ b/tests/components/refoss/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the refoss Integration.""" + from unittest.mock import AsyncMock, patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/remember_the_milk/test_init.py b/tests/components/remember_the_milk/test_init.py index 703e1de7b93..bda9f406f4b 100644 --- a/tests/components/remember_the_milk/test_init.py +++ b/tests/components/remember_the_milk/test_init.py @@ -1,4 +1,5 @@ """Tests for the Remember The Milk component.""" + from unittest.mock import Mock, mock_open, patch import homeassistant.components.remember_the_milk as rtm diff --git a/tests/components/remote/test_device_condition.py b/tests/components/remote/test_device_condition.py index 1048aa1b081..2d21e6e25ed 100644 --- a/tests/components/remote/test_device_condition.py +++ b/tests/components/remote/test_device_condition.py @@ -1,4 +1,5 @@ """The test for remote device automation.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/remote/test_device_trigger.py b/tests/components/remote/test_device_trigger.py index 711b9672aa0..f87a5484f4a 100644 --- a/tests/components/remote/test_device_trigger.py +++ b/tests/components/remote/test_device_trigger.py @@ -1,4 +1,5 @@ """The test for remote device automation.""" + from datetime import timedelta import pytest diff --git a/tests/components/remote/test_significant_change.py b/tests/components/remote/test_significant_change.py index dcbfce213d6..050d5a2ffc7 100644 --- a/tests/components/remote/test_significant_change.py +++ b/tests/components/remote/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Remote significant change platform.""" + from homeassistant.components.remote import ATTR_ACTIVITY_LIST, ATTR_CURRENT_ACTIVITY from homeassistant.components.remote.significant_change import ( async_check_significant_change, diff --git a/tests/components/renault/conftest.py b/tests/components/renault/conftest.py index 312ddbf6092..c4855742e5b 100644 --- a/tests/components/renault/conftest.py +++ b/tests/components/renault/conftest.py @@ -1,4 +1,5 @@ """Provide common Renault fixtures.""" + from collections.abc import Generator import contextlib from types import MappingProxyType diff --git a/tests/components/renault/const.py b/tests/components/renault/const.py index 342ab803f33..d849c658149 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -1,4 +1,5 @@ """Constants for the Renault integration tests.""" + from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.renault.const import ( CONF_KAMEREON_ACCOUNT_ID, diff --git a/tests/components/renault/test_binary_sensor.py b/tests/components/renault/test_binary_sensor.py index f1e3511dc2c..7a0d593a4c4 100644 --- a/tests/components/renault/test_binary_sensor.py +++ b/tests/components/renault/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for Renault binary sensors.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/renault/test_button.py b/tests/components/renault/test_button.py index 47a411ce791..d592f040c97 100644 --- a/tests/components/renault/test_button.py +++ b/tests/components/renault/test_button.py @@ -1,4 +1,5 @@ """Tests for Renault sensors.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/renault/test_config_flow.py b/tests/components/renault/test_config_flow.py index 5d933c03c65..48b20071525 100644 --- a/tests/components/renault/test_config_flow.py +++ b/tests/components/renault/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Renault config flow.""" + from unittest.mock import AsyncMock, PropertyMock, patch import pytest diff --git a/tests/components/renault/test_device_tracker.py b/tests/components/renault/test_device_tracker.py index a551d2df986..a809ce82e6e 100644 --- a/tests/components/renault/test_device_tracker.py +++ b/tests/components/renault/test_device_tracker.py @@ -1,4 +1,5 @@ """Tests for Renault sensors.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/renault/test_init.py b/tests/components/renault/test_init.py index 0f26bf6fbdb..6f222c760a7 100644 --- a/tests/components/renault/test_init.py +++ b/tests/components/renault/test_init.py @@ -1,4 +1,5 @@ """Tests for Renault setup process.""" + from collections.abc import Generator from typing import Any from unittest.mock import Mock, patch diff --git a/tests/components/renault/test_select.py b/tests/components/renault/test_select.py index f170dec6c4a..5dcd798def2 100644 --- a/tests/components/renault/test_select.py +++ b/tests/components/renault/test_select.py @@ -1,4 +1,5 @@ """Tests for Renault selects.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/renault/test_sensor.py b/tests/components/renault/test_sensor.py index fb61f31ec44..984e974ce3b 100644 --- a/tests/components/renault/test_sensor.py +++ b/tests/components/renault/test_sensor.py @@ -1,4 +1,5 @@ """Tests for Renault sensors.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/renault/test_services.py b/tests/components/renault/test_services.py index 7f5cb9a8184..0dba6a9adae 100644 --- a/tests/components/renault/test_services.py +++ b/tests/components/renault/test_services.py @@ -1,4 +1,5 @@ """Tests for Renault sensors.""" + from collections.abc import Generator from datetime import datetime from unittest.mock import patch diff --git a/tests/components/renson/test_config_flow.py b/tests/components/renson/test_config_flow.py index 578c6125427..185d21aead8 100644 --- a/tests/components/renson/test_config_flow.py +++ b/tests/components/renson/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Renson config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index 948ec41f551..a340e8cebbe 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -1,4 +1,5 @@ """Setup the Reolink tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/reolink/test_config_flow.py b/tests/components/reolink/test_config_flow.py index 89ab4be9f1e..34f617fb071 100644 --- a/tests/components/reolink/test_config_flow.py +++ b/tests/components/reolink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Reolink config flow.""" + from datetime import timedelta import json from typing import Any diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index 65490129486..abf97ee96ca 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -1,4 +1,5 @@ """Test the Reolink init.""" + from datetime import timedelta from typing import Any from unittest.mock import AsyncMock, MagicMock, Mock, patch diff --git a/tests/components/reolink/test_media_source.py b/tests/components/reolink/test_media_source.py index c7abc5b8e0e..9c5aebed222 100644 --- a/tests/components/reolink/test_media_source.py +++ b/tests/components/reolink/test_media_source.py @@ -1,4 +1,5 @@ """Tests for the Reolink media_source platform.""" + from datetime import datetime, timedelta import logging from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/repairs/test_init.py b/tests/components/repairs/test_init.py index 977bd9b5e55..d4bc2777c8a 100644 --- a/tests/components/repairs/test_init.py +++ b/tests/components/repairs/test_init.py @@ -1,4 +1,5 @@ """Test the repairs websocket API.""" + from unittest.mock import AsyncMock, Mock from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/repairs/test_websocket_api.py b/tests/components/repairs/test_websocket_api.py index ef08095ca79..859bdb15805 100644 --- a/tests/components/repairs/test_websocket_api.py +++ b/tests/components/repairs/test_websocket_api.py @@ -1,4 +1,5 @@ """Test the repairs websocket API.""" + from __future__ import annotations from http import HTTPStatus diff --git a/tests/components/rest/test_notify.py b/tests/components/rest/test_notify.py index f9a2e88c732..9f47e74c535 100644 --- a/tests/components/rest/test_notify.py +++ b/tests/components/rest/test_notify.py @@ -1,4 +1,5 @@ """The tests for the rest.notify platform.""" + from unittest.mock import patch import respx diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 2e4b06ac2d2..81684b6301e 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the REST sensor platform.""" + from http import HTTPStatus import ssl from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index 7be2ce4c63e..551994312d4 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -1,4 +1,5 @@ """The tests for the REST switch platform.""" + from http import HTTPStatus import httpx diff --git a/tests/components/rest_command/conftest.py b/tests/components/rest_command/conftest.py index 1a624b7534f..ec1cfb16ee6 100644 --- a/tests/components/rest_command/conftest.py +++ b/tests/components/rest_command/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the trend component tests.""" + from collections.abc import Awaitable, Callable from typing import Any diff --git a/tests/components/rflink/conftest.py b/tests/components/rflink/conftest.py index dcaeb0a5e01..b7c32bf0f13 100644 --- a/tests/components/rflink/conftest.py +++ b/tests/components/rflink/conftest.py @@ -1,2 +1,3 @@ """rflink conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/rflink/test_binary_sensor.py b/tests/components/rflink/test_binary_sensor.py index 416bd4f71b4..c92eaa30fe8 100644 --- a/tests/components/rflink/test_binary_sensor.py +++ b/tests/components/rflink/test_binary_sensor.py @@ -3,6 +3,7 @@ Test setup of rflink sensor component/platform. Verify manual and automatic sensor creation. """ + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/rflink/test_cover.py b/tests/components/rflink/test_cover.py index 71b3d2067d0..0829fddef51 100644 --- a/tests/components/rflink/test_cover.py +++ b/tests/components/rflink/test_cover.py @@ -4,6 +4,7 @@ Test setup of RFLink covers component/platform. State tracking and control of RFLink cover devices. """ + from homeassistant.components.rflink import EVENT_BUTTON_PRESSED from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/rflink/test_sensor.py b/tests/components/rflink/test_sensor.py index a4330a86b4f..e375f3ae863 100644 --- a/tests/components/rflink/test_sensor.py +++ b/tests/components/rflink/test_sensor.py @@ -4,6 +4,7 @@ Test setup of rflink sensor component/platform. Verify manual and automatic sensor creation. """ + from homeassistant.components.rflink import ( CONF_RECONNECT_INTERVAL, DATA_ENTITY_LOOKUP, diff --git a/tests/components/rflink/test_switch.py b/tests/components/rflink/test_switch.py index 35646d0fd22..705856565ae 100644 --- a/tests/components/rflink/test_switch.py +++ b/tests/components/rflink/test_switch.py @@ -4,6 +4,7 @@ Test setup of rflink switch component/platform. State tracking and control of Rflink switch devices. """ + from homeassistant.components.rflink import EVENT_BUTTON_PRESSED from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/rflink/test_utils.py b/tests/components/rflink/test_utils.py index 9a9caebab17..170a05f8623 100644 --- a/tests/components/rflink/test_utils.py +++ b/tests/components/rflink/test_utils.py @@ -1,4 +1,5 @@ """Test for RFLink utils methods.""" + from homeassistant.components.rflink.utils import ( brightness_to_rflink, rflink_to_brightness, diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index 8208c1138c0..536672b66cd 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -1,4 +1,5 @@ """Common test tools.""" + from __future__ import annotations from unittest.mock import Mock, patch diff --git a/tests/components/rfxtrx/test_cover.py b/tests/components/rfxtrx/test_cover.py index d8eec762ec4..c5a5d29e693 100644 --- a/tests/components/rfxtrx/test_cover.py +++ b/tests/components/rfxtrx/test_cover.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx cover platform.""" + from unittest.mock import call import pytest diff --git a/tests/components/rfxtrx/test_device_action.py b/tests/components/rfxtrx/test_device_action.py index 6b2431fb763..07a972a9148 100644 --- a/tests/components/rfxtrx/test_device_action.py +++ b/tests/components/rfxtrx/test_device_action.py @@ -1,4 +1,5 @@ """The tests for RFXCOM RFXtrx device actions.""" + from __future__ import annotations from typing import Any, NamedTuple diff --git a/tests/components/rfxtrx/test_device_trigger.py b/tests/components/rfxtrx/test_device_trigger.py index a253810c4c8..63ac5b1ab89 100644 --- a/tests/components/rfxtrx/test_device_trigger.py +++ b/tests/components/rfxtrx/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for RFXCOM RFXtrx device triggers.""" + from __future__ import annotations from typing import Any, NamedTuple diff --git a/tests/components/rfxtrx/test_event.py b/tests/components/rfxtrx/test_event.py index d0322c3ed82..1a4305d97f6 100644 --- a/tests/components/rfxtrx/test_event.py +++ b/tests/components/rfxtrx/test_event.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx sensor platform.""" + from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/rfxtrx/test_init.py b/tests/components/rfxtrx/test_init.py index 88a63e47cf1..b969a63a990 100644 --- a/tests/components/rfxtrx/test_init.py +++ b/tests/components/rfxtrx/test_init.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx component.""" + from __future__ import annotations from unittest.mock import ANY, call diff --git a/tests/components/rfxtrx/test_light.py b/tests/components/rfxtrx/test_light.py index 30f54352fb9..05862e47630 100644 --- a/tests/components/rfxtrx/test_light.py +++ b/tests/components/rfxtrx/test_light.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx light platform.""" + from unittest.mock import call import pytest diff --git a/tests/components/rfxtrx/test_siren.py b/tests/components/rfxtrx/test_siren.py index 6e428f45d92..f8db86cff8d 100644 --- a/tests/components/rfxtrx/test_siren.py +++ b/tests/components/rfxtrx/test_siren.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx siren platform.""" + from unittest.mock import call from homeassistant.components.rfxtrx import DOMAIN diff --git a/tests/components/rfxtrx/test_switch.py b/tests/components/rfxtrx/test_switch.py index ec835ebda52..63aacdd5eab 100644 --- a/tests/components/rfxtrx/test_switch.py +++ b/tests/components/rfxtrx/test_switch.py @@ -1,4 +1,5 @@ """The tests for the RFXtrx switch platform.""" + from unittest.mock import call import pytest diff --git a/tests/components/rhasspy/test_config_flow.py b/tests/components/rhasspy/test_config_flow.py index 53c82c0cecd..1a53dd32e04 100644 --- a/tests/components/rhasspy/test_config_flow.py +++ b/tests/components/rhasspy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Rhasspy config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/rhasspy/test_init.py b/tests/components/rhasspy/test_init.py index e4f0b346347..c03083c0b8a 100644 --- a/tests/components/rhasspy/test_init.py +++ b/tests/components/rhasspy/test_init.py @@ -1,4 +1,5 @@ """Tests for the Rhasspy integration.""" + from homeassistant.components.rhasspy.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/ridwell/conftest.py b/tests/components/ridwell/conftest.py index 651c2a96388..aed5a00b1a8 100644 --- a/tests/components/ridwell/conftest.py +++ b/tests/components/ridwell/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for Ridwell.""" + from datetime import date from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/ridwell/test_config_flow.py b/tests/components/ridwell/test_config_flow.py index 990ac656696..15352929b4c 100644 --- a/tests/components/ridwell/test_config_flow.py +++ b/tests/components/ridwell/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ridwell config flow.""" + from unittest.mock import AsyncMock, patch from aioridwell.errors import InvalidCredentialsError, RidwellError diff --git a/tests/components/ridwell/test_diagnostics.py b/tests/components/ridwell/test_diagnostics.py index c87004a8e76..adfbb525283 100644 --- a/tests/components/ridwell/test_diagnostics.py +++ b/tests/components/ridwell/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Ridwell diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/ring/common.py b/tests/components/ring/common.py index 93a6e4f91e0..c6852bf87d6 100644 --- a/tests/components/ring/common.py +++ b/tests/components/ring/common.py @@ -1,4 +1,5 @@ """Common methods used across the tests for ring devices.""" + from unittest.mock import patch from homeassistant.components.ring import DOMAIN diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index e9800393835..42b2184f289 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -1,4 +1,5 @@ """Configuration for Ring tests.""" + from collections.abc import Generator import re from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/ring/test_binary_sensor.py b/tests/components/ring/test_binary_sensor.py index fa211b7e881..8738594fa05 100644 --- a/tests/components/ring/test_binary_sensor.py +++ b/tests/components/ring/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Ring binary sensor platform.""" + from time import time from unittest.mock import patch diff --git a/tests/components/ring/test_config_flow.py b/tests/components/ring/test_config_flow.py index 53c7e139a51..f9c24ad77c5 100644 --- a/tests/components/ring/test_config_flow.py +++ b/tests/components/ring/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ring config flow.""" + from unittest.mock import AsyncMock, Mock import pytest diff --git a/tests/components/risco/conftest.py b/tests/components/risco/conftest.py index a8a764cd502..6e86e04be7d 100644 --- a/tests/components/risco/conftest.py +++ b/tests/components/risco/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Risco tests.""" + from unittest.mock import MagicMock, PropertyMock, patch import pytest diff --git a/tests/components/risco/test_alarm_control_panel.py b/tests/components/risco/test_alarm_control_panel.py index e49817469b4..5fc4f40da99 100644 --- a/tests/components/risco/test_alarm_control_panel.py +++ b/tests/components/risco/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Tests for the Risco alarm control panel device.""" + from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch import pytest diff --git a/tests/components/risco/test_binary_sensor.py b/tests/components/risco/test_binary_sensor.py index ee74dbbedc8..ce49c1696fd 100644 --- a/tests/components/risco/test_binary_sensor.py +++ b/tests/components/risco/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Risco binary sensors.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/risco/test_config_flow.py b/tests/components/risco/test_config_flow.py index cc6cefc1325..4775cfca903 100644 --- a/tests/components/risco/test_config_flow.py +++ b/tests/components/risco/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Risco config flow.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/risco/test_sensor.py b/tests/components/risco/test_sensor.py index e8bae275cc2..a172eb3d650 100644 --- a/tests/components/risco/test_sensor.py +++ b/tests/components/risco/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Risco event sensors.""" + from datetime import timedelta from unittest.mock import MagicMock, PropertyMock, patch diff --git a/tests/components/risco/test_switch.py b/tests/components/risco/test_switch.py index 07058119a62..1d575f4b75b 100644 --- a/tests/components/risco/test_switch.py +++ b/tests/components/risco/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Risco binary sensors.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/risco/util.py b/tests/components/risco/util.py index b2600383f2a..db77c112f75 100644 --- a/tests/components/risco/util.py +++ b/tests/components/risco/util.py @@ -1,4 +1,5 @@ """Utilities for Risco tests.""" + from unittest.mock import AsyncMock, MagicMock TEST_SITE_UUID = "test-site-uuid" diff --git a/tests/components/rituals_perfume_genie/common.py b/tests/components/rituals_perfume_genie/common.py index f8bcc10ca59..f2a54ca5def 100644 --- a/tests/components/rituals_perfume_genie/common.py +++ b/tests/components/rituals_perfume_genie/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for Rituals Perfume Genie.""" + from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/rituals_perfume_genie/test_binary_sensor.py b/tests/components/rituals_perfume_genie/test_binary_sensor.py index dae654d6e16..8f94dcc215a 100644 --- a/tests/components/rituals_perfume_genie/test_binary_sensor.py +++ b/tests/components/rituals_perfume_genie/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Rituals Perfume Genie binary sensor platform.""" + from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON, EntityCategory from homeassistant.core import HomeAssistant diff --git a/tests/components/rituals_perfume_genie/test_config_flow.py b/tests/components/rituals_perfume_genie/test_config_flow.py index f656e71b579..b601c6c7c47 100644 --- a/tests/components/rituals_perfume_genie/test_config_flow.py +++ b/tests/components/rituals_perfume_genie/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Rituals Perfume Genie config flow.""" + from http import HTTPStatus from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/rituals_perfume_genie/test_diagnostics.py b/tests/components/rituals_perfume_genie/test_diagnostics.py index a57f14f9afd..744f064a5da 100644 --- a/tests/components/rituals_perfume_genie/test_diagnostics.py +++ b/tests/components/rituals_perfume_genie/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Rituals Perfume Genie integration.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/rituals_perfume_genie/test_init.py b/tests/components/rituals_perfume_genie/test_init.py index 7f2f06b707c..d1001d1ad93 100644 --- a/tests/components/rituals_perfume_genie/test_init.py +++ b/tests/components/rituals_perfume_genie/test_init.py @@ -1,4 +1,5 @@ """Tests for the Rituals Perfume Genie integration.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/rituals_perfume_genie/test_number.py b/tests/components/rituals_perfume_genie/test_number.py index 8e87ec74b77..f88bcc6d0cb 100644 --- a/tests/components/rituals_perfume_genie/test_number.py +++ b/tests/components/rituals_perfume_genie/test_number.py @@ -1,4 +1,5 @@ """Tests for the Rituals Perfume Genie number platform.""" + from __future__ import annotations import pytest diff --git a/tests/components/rituals_perfume_genie/test_sensor.py b/tests/components/rituals_perfume_genie/test_sensor.py index 6502cd8ee4f..fd273d238fe 100644 --- a/tests/components/rituals_perfume_genie/test_sensor.py +++ b/tests/components/rituals_perfume_genie/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Rituals Perfume Genie sensor platform.""" + from homeassistant.components.rituals_perfume_genie.sensor import SensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, diff --git a/tests/components/rituals_perfume_genie/test_switch.py b/tests/components/rituals_perfume_genie/test_switch.py index 70250615446..7e6a94906e1 100644 --- a/tests/components/rituals_perfume_genie/test_switch.py +++ b/tests/components/rituals_perfume_genie/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Rituals Perfume Genie switch platform.""" + from __future__ import annotations from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY diff --git a/tests/components/roborock/conftest.py b/tests/components/roborock/conftest.py index efbc2ea7f9d..b556804e46a 100644 --- a/tests/components/roborock/conftest.py +++ b/tests/components/roborock/conftest.py @@ -1,4 +1,5 @@ """Global fixtures for Roborock integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/roborock/mock_data.py b/tests/components/roborock/mock_data.py index 8935a77f142..16ebc8806f9 100644 --- a/tests/components/roborock/mock_data.py +++ b/tests/components/roborock/mock_data.py @@ -1,4 +1,5 @@ """Mock data for Roborock tests.""" + from __future__ import annotations from PIL import Image diff --git a/tests/components/roborock/test_button.py b/tests/components/roborock/test_button.py index 3948e0c161a..5654dac9218 100644 --- a/tests/components/roborock/test_button.py +++ b/tests/components/roborock/test_button.py @@ -1,4 +1,5 @@ """Test Roborock Button platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/roborock/test_config_flow.py b/tests/components/roborock/test_config_flow.py index e2454b3ad57..b5cff60cddb 100644 --- a/tests/components/roborock/test_config_flow.py +++ b/tests/components/roborock/test_config_flow.py @@ -1,4 +1,5 @@ """Test Roborock config flow.""" + from copy import deepcopy from unittest.mock import patch diff --git a/tests/components/roborock/test_init.py b/tests/components/roborock/test_init.py index dd85861d53c..8f9bc56d07b 100644 --- a/tests/components/roborock/test_init.py +++ b/tests/components/roborock/test_init.py @@ -1,4 +1,5 @@ """Test for Roborock init.""" + from unittest.mock import patch from roborock import RoborockException, RoborockInvalidCredentials diff --git a/tests/components/roborock/test_number.py b/tests/components/roborock/test_number.py index b660bfc2969..1c20a93cace 100644 --- a/tests/components/roborock/test_number.py +++ b/tests/components/roborock/test_number.py @@ -1,4 +1,5 @@ """Test Roborock Number platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/roborock/test_select.py b/tests/components/roborock/test_select.py index bcea4e6246b..67fefee7afb 100644 --- a/tests/components/roborock/test_select.py +++ b/tests/components/roborock/test_select.py @@ -1,4 +1,5 @@ """Test Roborock Select platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/roborock/test_sensor.py b/tests/components/roborock/test_sensor.py index 4966c8fa3be..a5f4164eee1 100644 --- a/tests/components/roborock/test_sensor.py +++ b/tests/components/roborock/test_sensor.py @@ -1,4 +1,5 @@ """Test Roborock Sensors.""" + from unittest.mock import patch from roborock import DeviceData, HomeDataDevice diff --git a/tests/components/roborock/test_switch.py b/tests/components/roborock/test_switch.py index fb301390fee..42a5e92f32a 100644 --- a/tests/components/roborock/test_switch.py +++ b/tests/components/roborock/test_switch.py @@ -1,4 +1,5 @@ """Test Roborock Switch platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/roborock/test_time.py b/tests/components/roborock/test_time.py index 1cf2fe6bed5..378c642b2f4 100644 --- a/tests/components/roborock/test_time.py +++ b/tests/components/roborock/test_time.py @@ -1,4 +1,5 @@ """Test Roborock Time platform.""" + from datetime import time from unittest.mock import patch diff --git a/tests/components/roku/conftest.py b/tests/components/roku/conftest.py index 2015d01ea68..4cec3e233e6 100644 --- a/tests/components/roku/conftest.py +++ b/tests/components/roku/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Roku integration tests.""" + from collections.abc import Generator import json from unittest.mock import MagicMock, patch diff --git a/tests/components/roku/test_binary_sensor.py b/tests/components/roku/test_binary_sensor.py index 14d7eb392ad..89e559bbabd 100644 --- a/tests/components/roku/test_binary_sensor.py +++ b/tests/components/roku/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Roku integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/roku/test_diagnostics.py b/tests/components/roku/test_diagnostics.py index 708e6d3f5e3..37e0d43a582 100644 --- a/tests/components/roku/test_diagnostics.py +++ b/tests/components/roku/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Roku integration.""" + from rokuecp import Device as RokuDevice from syrupy import SnapshotAssertion diff --git a/tests/components/roku/test_init.py b/tests/components/roku/test_init.py index 7f291f020d9..a4fc8477ac3 100644 --- a/tests/components/roku/test_init.py +++ b/tests/components/roku/test_init.py @@ -1,4 +1,5 @@ """Tests for the Roku integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from rokuecp import RokuConnectionError diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 776962e071c..ec7213d3b3c 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the Roku Media Player platform.""" + from datetime import timedelta from unittest.mock import MagicMock, patch diff --git a/tests/components/roku/test_remote.py b/tests/components/roku/test_remote.py index 5a0f00ab3b6..3d40006a259 100644 --- a/tests/components/roku/test_remote.py +++ b/tests/components/roku/test_remote.py @@ -1,4 +1,5 @@ """The tests for the Roku remote platform.""" + from unittest.mock import MagicMock from homeassistant.components.remote import ( diff --git a/tests/components/roku/test_select.py b/tests/components/roku/test_select.py index 8de1ff5a248..edef070ee21 100644 --- a/tests/components/roku/test_select.py +++ b/tests/components/roku/test_select.py @@ -1,4 +1,5 @@ """Tests for the Roku select platform.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/roku/test_sensor.py b/tests/components/roku/test_sensor.py index ab7b9ac00f5..e0aeb22126c 100644 --- a/tests/components/roku/test_sensor.py +++ b/tests/components/roku/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Roku integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/romy/test_config_flow.py b/tests/components/romy/test_config_flow.py index a24a3f46bfa..480a37fa068 100644 --- a/tests/components/romy/test_config_flow.py +++ b/tests/components/romy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the ROMY config flow.""" + from ipaddress import ip_address from unittest.mock import Mock, PropertyMock, patch diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index 4d44035893d..af82d4a690e 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -1,4 +1,5 @@ """Test the iRobot Roomba config flow.""" + from ipaddress import ip_address from unittest.mock import MagicMock, PropertyMock, patch diff --git a/tests/components/roon/test_config_flow.py b/tests/components/roon/test_config_flow.py index 1ce8d716824..ce7203caf66 100644 --- a/tests/components/roon/test_config_flow.py +++ b/tests/components/roon/test_config_flow.py @@ -1,4 +1,5 @@ """Test the roon config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/rpi_power/test_binary_sensor.py b/tests/components/rpi_power/test_binary_sensor.py index 9ba81a69c72..78b7b9261b9 100644 --- a/tests/components/rpi_power/test_binary_sensor.py +++ b/tests/components/rpi_power/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for rpi_power binary sensor.""" + from datetime import timedelta import logging from unittest.mock import MagicMock diff --git a/tests/components/rpi_power/test_config_flow.py b/tests/components/rpi_power/test_config_flow.py index 5c474fc0821..1cb9f772d70 100644 --- a/tests/components/rpi_power/test_config_flow.py +++ b/tests/components/rpi_power/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for rpi_power config flow.""" + from unittest.mock import MagicMock from homeassistant.components.rpi_power.const import DOMAIN diff --git a/tests/components/rss_feed_template/test_init.py b/tests/components/rss_feed_template/test_init.py index 6dc247ec6b8..351c9e9d1cb 100644 --- a/tests/components/rss_feed_template/test_init.py +++ b/tests/components/rss_feed_template/test_init.py @@ -1,4 +1,5 @@ """The tests for the rss_feed_api component.""" + from http import HTTPStatus from defusedxml import ElementTree diff --git a/tests/components/rtsp_to_webrtc/test_diagnostics.py b/tests/components/rtsp_to_webrtc/test_diagnostics.py index 270af9267df..e020ebfd5f3 100644 --- a/tests/components/rtsp_to_webrtc/test_diagnostics.py +++ b/tests/components/rtsp_to_webrtc/test_diagnostics.py @@ -1,4 +1,5 @@ """Test nest diagnostics.""" + from typing import Any from homeassistant.core import HomeAssistant diff --git a/tests/components/ruckus_unleashed/test_config_flow.py b/tests/components/ruckus_unleashed/test_config_flow.py index cd74395fa66..c9f321a1da6 100644 --- a/tests/components/ruckus_unleashed/test_config_flow.py +++ b/tests/components/ruckus_unleashed/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ruckus Unleashed config flow.""" + from copy import deepcopy from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/ruckus_unleashed/test_device_tracker.py b/tests/components/ruckus_unleashed/test_device_tracker.py index cda3836a0a4..6da0f68b5d8 100644 --- a/tests/components/ruckus_unleashed/test_device_tracker.py +++ b/tests/components/ruckus_unleashed/test_device_tracker.py @@ -1,4 +1,5 @@ """The sensor tests for the Ruckus Unleashed platform.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/ruckus_unleashed/test_init.py b/tests/components/ruckus_unleashed/test_init.py index c8246a5ac1e..48c0a5a270e 100644 --- a/tests/components/ruckus_unleashed/test_init.py +++ b/tests/components/ruckus_unleashed/test_init.py @@ -1,4 +1,5 @@ """Test the Ruckus Unleashed config flow.""" + from unittest.mock import AsyncMock from aioruckus.const import ERROR_CONNECT_TIMEOUT, ERROR_LOGIN_INCORRECT diff --git a/tests/components/ruuvi_gateway/consts.py b/tests/components/ruuvi_gateway/consts.py index bd544fb2098..077c5114acf 100644 --- a/tests/components/ruuvi_gateway/consts.py +++ b/tests/components/ruuvi_gateway/consts.py @@ -1,4 +1,5 @@ """Constants for ruuvi_gateway tests.""" + from __future__ import annotations ASYNC_SETUP_ENTRY = "homeassistant.components.ruuvi_gateway.async_setup_entry" diff --git a/tests/components/ruuvi_gateway/test_config_flow.py b/tests/components/ruuvi_gateway/test_config_flow.py index 42dac479955..e9e8446f8ac 100644 --- a/tests/components/ruuvi_gateway/test_config_flow.py +++ b/tests/components/ruuvi_gateway/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ruuvi Gateway config flow.""" + from unittest.mock import patch from aioruuvigateway.excs import CannotConnect, InvalidAuth diff --git a/tests/components/ruuvi_gateway/utils.py b/tests/components/ruuvi_gateway/utils.py index 76bde687321..0f928036373 100644 --- a/tests/components/ruuvi_gateway/utils.py +++ b/tests/components/ruuvi_gateway/utils.py @@ -1,4 +1,5 @@ """Utilities for ruuvi_gateway tests.""" + from __future__ import annotations import time diff --git a/tests/components/ruuvitag_ble/fixtures.py b/tests/components/ruuvitag_ble/fixtures.py index 26eee1bac5e..5d6ac9ea470 100644 --- a/tests/components/ruuvitag_ble/fixtures.py +++ b/tests/components/ruuvitag_ble/fixtures.py @@ -1,4 +1,5 @@ """Fixtures for testing RuuviTag BLE.""" + from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_RUUVITAG_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/ruuvitag_ble/test_config_flow.py b/tests/components/ruuvitag_ble/test_config_flow.py index dcfda4e30e2..6f668b0168b 100644 --- a/tests/components/ruuvitag_ble/test_config_flow.py +++ b/tests/components/ruuvitag_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ruuvitag config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/rympro/test_config_flow.py b/tests/components/rympro/test_config_flow.py index c20c6c5a699..811234dd559 100644 --- a/tests/components/rympro/test_config_flow.py +++ b/tests/components/rympro/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Read Your Meter Pro config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/sabnzbd/conftest.py b/tests/components/sabnzbd/conftest.py index 01cea606654..d1854017452 100644 --- a/tests/components/sabnzbd/conftest.py +++ b/tests/components/sabnzbd/conftest.py @@ -1,4 +1,5 @@ """Configuration for Sabnzbd tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/sabnzbd/test_config_flow.py b/tests/components/sabnzbd/test_config_flow.py index 05040186bb3..2da1c7c87db 100644 --- a/tests/components/sabnzbd/test_config_flow.py +++ b/tests/components/sabnzbd/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Sabnzbd config flow.""" + from unittest.mock import AsyncMock, patch from pysabnzbd import SabnzbdApiException diff --git a/tests/components/sabnzbd/test_init.py b/tests/components/sabnzbd/test_init.py index e41a23bbfc9..e666f9f1d3e 100644 --- a/tests/components/sabnzbd/test_init.py +++ b/tests/components/sabnzbd/test_init.py @@ -1,4 +1,5 @@ """Tests for the SABnzbd Integration.""" + from unittest.mock import patch from homeassistant.components.sabnzbd import DEFAULT_NAME, DOMAIN, OLD_SENSOR_KEYS diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index 6754faf2da6..ad0c3a21db6 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Samsung TV.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Generator diff --git a/tests/components/samsungtv/const.py b/tests/components/samsungtv/const.py index 347886419f3..43d240ed779 100644 --- a/tests/components/samsungtv/const.py +++ b/tests/components/samsungtv/const.py @@ -1,4 +1,5 @@ """Constants for the samsungtv tests.""" + from samsungtvws.event import ED_INSTALLED_APP_EVENT from homeassistant.components import ssdp diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 60b89766155..78f72dfd299 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Samsung TV config flow.""" + from copy import deepcopy from ipaddress import ip_address from unittest.mock import ANY, AsyncMock, Mock, call, patch diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py index 651b6f27a44..2e590518187 100644 --- a/tests/components/samsungtv/test_diagnostics.py +++ b/tests/components/samsungtv/test_diagnostics.py @@ -1,4 +1,5 @@ """Test samsungtv diagnostics.""" + from unittest.mock import Mock import pytest diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index cde5cebcefe..fa7aec9a421 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -1,4 +1,5 @@ """Tests for the Samsung TV Integration.""" + from unittest.mock import AsyncMock, Mock, patch import pytest diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index c4c6a08b88b..446888a1f54 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -1,4 +1,5 @@ """Tests for samsungtv component.""" + from copy import deepcopy from datetime import datetime, timedelta import logging diff --git a/tests/components/samsungtv/test_remote.py b/tests/components/samsungtv/test_remote.py index 88cf47bf148..1f9115afca5 100644 --- a/tests/components/samsungtv/test_remote.py +++ b/tests/components/samsungtv/test_remote.py @@ -1,4 +1,5 @@ """The tests for the SamsungTV remote platform.""" + from unittest.mock import Mock import pytest diff --git a/tests/components/samsungtv/test_trigger.py b/tests/components/samsungtv/test_trigger.py index 12af639b251..0bf57a899a9 100644 --- a/tests/components/samsungtv/test_trigger.py +++ b/tests/components/samsungtv/test_trigger.py @@ -1,4 +1,5 @@ """The tests for WebOS TV automation triggers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/scene/common.py b/tests/components/scene/common.py index cdf124add29..e20da63c402 100644 --- a/tests/components/scene/common.py +++ b/tests/components/scene/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.scene import DOMAIN from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, SERVICE_TURN_ON from homeassistant.loader import bind_hass diff --git a/tests/components/schedule/test_init.py b/tests/components/schedule/test_init.py index 70ba6dfde3c..9ee16806287 100644 --- a/tests/components/schedule/test_init.py +++ b/tests/components/schedule/test_init.py @@ -1,4 +1,5 @@ """Test for the Schedule integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/tests/components/schedule/test_recorder.py b/tests/components/schedule/test_recorder.py index 58a171f9102..df28730ee79 100644 --- a/tests/components/schedule/test_recorder.py +++ b/tests/components/schedule/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/schlage/conftest.py b/tests/components/schlage/conftest.py index 5f9676b7d09..40d880b73f8 100644 --- a/tests/components/schlage/conftest.py +++ b/tests/components/schlage/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Schlage tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, create_autospec, patch diff --git a/tests/components/schlage/test_config_flow.py b/tests/components/schlage/test_config_flow.py index 14121f5d9ca..118ae44d15b 100644 --- a/tests/components/schlage/test_config_flow.py +++ b/tests/components/schlage/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Schlage config flow.""" + from unittest.mock import AsyncMock, Mock from pyschlage.exceptions import Error as PyschlageError, NotAuthorizedError diff --git a/tests/components/schlage/test_switch.py b/tests/components/schlage/test_switch.py index 30e56b0686f..bf74a79b406 100644 --- a/tests/components/schlage/test_switch.py +++ b/tests/components/schlage/test_switch.py @@ -1,4 +1,5 @@ """Test schlage switch.""" + from unittest.mock import Mock from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN diff --git a/tests/components/scrape/conftest.py b/tests/components/scrape/conftest.py index 026daeea38c..a7181943884 100644 --- a/tests/components/scrape/conftest.py +++ b/tests/components/scrape/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Scrape integration.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/scrape/test_config_flow.py b/tests/components/scrape/test_config_flow.py index 7dd2954f8c3..6baecae50c5 100644 --- a/tests/components/scrape/test_config_flow.py +++ b/tests/components/scrape/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Scrape config flow.""" + from __future__ import annotations from unittest.mock import AsyncMock, patch diff --git a/tests/components/scrape/test_init.py b/tests/components/scrape/test_init.py index 638e25a6e05..8ad766a80bd 100644 --- a/tests/components/scrape/test_init.py +++ b/tests/components/scrape/test_init.py @@ -1,4 +1,5 @@ """Test Scrape component setup process.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/scrape/test_sensor.py b/tests/components/scrape/test_sensor.py index 559c94633cd..03b85a8944f 100644 --- a/tests/components/scrape/test_sensor.py +++ b/tests/components/scrape/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Scrape sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py index 8e40f5f0e5c..81913e76e27 100644 --- a/tests/components/screenlogic/test_config_flow.py +++ b/tests/components/screenlogic/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Pentair ScreenLogic config flow.""" + from unittest.mock import patch from screenlogicpy import ScreenLogicError diff --git a/tests/components/screenlogic/test_data.py b/tests/components/screenlogic/test_data.py index ead064f7d93..a14b2f93169 100644 --- a/tests/components/screenlogic/test_data.py +++ b/tests/components/screenlogic/test_data.py @@ -1,4 +1,5 @@ """Tests for ScreenLogic integration data processing.""" + from unittest.mock import DEFAULT, patch from screenlogicpy import ScreenLogicGateway diff --git a/tests/components/screenlogic/test_diagnostics.py b/tests/components/screenlogic/test_diagnostics.py index dcbca954730..b66aa26a203 100644 --- a/tests/components/screenlogic/test_diagnostics.py +++ b/tests/components/screenlogic/test_diagnostics.py @@ -1,4 +1,5 @@ """Testing for ScreenLogic diagnostics.""" + from unittest.mock import DEFAULT, patch from screenlogicpy import ScreenLogicGateway diff --git a/tests/components/screenlogic/test_init.py b/tests/components/screenlogic/test_init.py index cf0a7ef3f38..5b75517da6e 100644 --- a/tests/components/screenlogic/test_init.py +++ b/tests/components/screenlogic/test_init.py @@ -1,4 +1,5 @@ """Tests for ScreenLogic integration init.""" + from dataclasses import dataclass from unittest.mock import DEFAULT, patch diff --git a/tests/components/script/test_recorder.py b/tests/components/script/test_recorder.py index 4e98ea9e670..465d287318d 100644 --- a/tests/components/script/test_recorder.py +++ b/tests/components/script/test_recorder.py @@ -1,4 +1,5 @@ """The tests for script recorder.""" + from __future__ import annotations import pytest diff --git a/tests/components/season/conftest.py b/tests/components/season/conftest.py index 40d95f3331b..b0b4f1058d9 100644 --- a/tests/components/season/conftest.py +++ b/tests/components/season/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Season integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/season/test_config_flow.py b/tests/components/season/test_config_flow.py index 884c5a3ddc8..e0a140f7136 100644 --- a/tests/components/season/test_config_flow.py +++ b/tests/components/season/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Season config flow.""" + from unittest.mock import MagicMock from homeassistant.components.season.const import DOMAIN, TYPE_ASTRONOMICAL diff --git a/tests/components/season/test_init.py b/tests/components/season/test_init.py index 9d964512160..6c716d5e4a5 100644 --- a/tests/components/season/test_init.py +++ b/tests/components/season/test_init.py @@ -1,4 +1,5 @@ """Tests for the Season integration.""" + from homeassistant.components.season.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/season/test_sensor.py b/tests/components/season/test_sensor.py index 413291c4f75..dd42ad6ce1c 100644 --- a/tests/components/season/test_sensor.py +++ b/tests/components/season/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Season integration.""" + from datetime import datetime from freezegun import freeze_time diff --git a/tests/components/select/test_device_condition.py b/tests/components/select/test_device_condition.py index 3e0ecd6e547..c744a6409bf 100644 --- a/tests/components/select/test_device_condition.py +++ b/tests/components/select/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Select device conditions.""" + from __future__ import annotations import pytest diff --git a/tests/components/select/test_device_trigger.py b/tests/components/select/test_device_trigger.py index 0be5c605dc1..bd40f975d3e 100644 --- a/tests/components/select/test_device_trigger.py +++ b/tests/components/select/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Select device triggers.""" + from __future__ import annotations import pytest diff --git a/tests/components/select/test_init.py b/tests/components/select/test_init.py index 604bf3f0fb9..b135a6e1ab0 100644 --- a/tests/components/select/test_init.py +++ b/tests/components/select/test_init.py @@ -1,4 +1,5 @@ """The tests for the Select component.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/select/test_recorder.py b/tests/components/select/test_recorder.py index 53911578d53..73dea423f6a 100644 --- a/tests/components/select/test_recorder.py +++ b/tests/components/select/test_recorder.py @@ -1,4 +1,5 @@ """The tests for select recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/select/test_significant_change.py b/tests/components/select/test_significant_change.py index 34ae5cad54e..7cba7e4962d 100644 --- a/tests/components/select/test_significant_change.py +++ b/tests/components/select/test_significant_change.py @@ -1,4 +1,5 @@ """Test the select significant change platform.""" + from homeassistant.components.select.significant_change import ( async_check_significant_change, ) diff --git a/tests/components/sense/test_config_flow.py b/tests/components/sense/test_config_flow.py index 40a6189dc74..dc1cee43662 100644 --- a/tests/components/sense/test_config_flow.py +++ b/tests/components/sense/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Sense config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/sensibo/conftest.py b/tests/components/sensibo/conftest.py index 17c295b4c48..01f77d7d40c 100644 --- a/tests/components/sensibo/conftest.py +++ b/tests/components/sensibo/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Sensibo integration.""" + from __future__ import annotations import json diff --git a/tests/components/sensibo/test_binary_sensor.py b/tests/components/sensibo/test_binary_sensor.py index 99bcfac8c9b..24653e6b7c7 100644 --- a/tests/components/sensibo/test_binary_sensor.py +++ b/tests/components/sensibo/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the sensibo binary sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensibo/test_button.py b/tests/components/sensibo/test_button.py index 2277c84d187..e94a3c00fd5 100644 --- a/tests/components/sensibo/test_button.py +++ b/tests/components/sensibo/test_button.py @@ -1,4 +1,5 @@ """The test for the sensibo button platform.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/sensibo/test_climate.py b/tests/components/sensibo/test_climate.py index bf0113cb22b..9634abc66fc 100644 --- a/tests/components/sensibo/test_climate.py +++ b/tests/components/sensibo/test_climate.py @@ -1,4 +1,5 @@ """The test for the sensibo binary sensor platform.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/sensibo/test_config_flow.py b/tests/components/sensibo/test_config_flow.py index feba0e2c39b..12c4e187550 100644 --- a/tests/components/sensibo/test_config_flow.py +++ b/tests/components/sensibo/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Sensibo config flow.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/sensibo/test_coordinator.py b/tests/components/sensibo/test_coordinator.py index 3c02fb0d3a9..ba07cec2eda 100644 --- a/tests/components/sensibo/test_coordinator.py +++ b/tests/components/sensibo/test_coordinator.py @@ -1,4 +1,5 @@ """The test for the sensibo coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensibo/test_diagnostics.py b/tests/components/sensibo/test_diagnostics.py index 320125e6403..1fe72cca0f3 100644 --- a/tests/components/sensibo/test_diagnostics.py +++ b/tests/components/sensibo/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Sensibo diagnostics.""" + from __future__ import annotations from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/sensibo/test_entity.py b/tests/components/sensibo/test_entity.py index aff4ba45eaa..dbd6b76d0bf 100644 --- a/tests/components/sensibo/test_entity.py +++ b/tests/components/sensibo/test_entity.py @@ -1,4 +1,5 @@ """The test for the sensibo entity.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/sensibo/test_init.py b/tests/components/sensibo/test_init.py index 90dbcd86a96..49aa629d159 100644 --- a/tests/components/sensibo/test_init.py +++ b/tests/components/sensibo/test_init.py @@ -1,4 +1,5 @@ """Test for Sensibo component Init.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/sensibo/test_number.py b/tests/components/sensibo/test_number.py index bdf3e5721c7..fc692b8ecc0 100644 --- a/tests/components/sensibo/test_number.py +++ b/tests/components/sensibo/test_number.py @@ -1,4 +1,5 @@ """The test for the sensibo number platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensibo/test_select.py b/tests/components/sensibo/test_select.py index 41a67dfbe79..6ff4afa5d47 100644 --- a/tests/components/sensibo/test_select.py +++ b/tests/components/sensibo/test_select.py @@ -1,4 +1,5 @@ """The test for the sensibo select platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensibo/test_sensor.py b/tests/components/sensibo/test_sensor.py index b3089c37e68..4e254568ac4 100644 --- a/tests/components/sensibo/test_sensor.py +++ b/tests/components/sensibo/test_sensor.py @@ -1,4 +1,5 @@ """The test for the sensibo select platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensibo/test_switch.py b/tests/components/sensibo/test_switch.py index e319be85c73..b0f9a8d2a59 100644 --- a/tests/components/sensibo/test_switch.py +++ b/tests/components/sensibo/test_switch.py @@ -1,4 +1,5 @@ """The test for the sensibo switch platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensibo/test_update.py b/tests/components/sensibo/test_update.py index 72e9ae9f902..23b2719d5b5 100644 --- a/tests/components/sensibo/test_update.py +++ b/tests/components/sensibo/test_update.py @@ -1,4 +1,5 @@ """The test for the sensibo update platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sensirion_ble/fixtures.py b/tests/components/sensirion_ble/fixtures.py index c49ea3c1da2..96101a3e7b6 100644 --- a/tests/components/sensirion_ble/fixtures.py +++ b/tests/components/sensirion_ble/fixtures.py @@ -1,4 +1,5 @@ """Fixtures for testing Sensirion BLE.""" + from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_SENSIRION_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/sensirion_ble/test_config_flow.py b/tests/components/sensirion_ble/test_config_flow.py index 542c49e285b..e93d060fd3e 100644 --- a/tests/components/sensirion_ble/test_config_flow.py +++ b/tests/components/sensirion_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Sensirion config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index bbc59cca322..c97f34c2c39 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -1,4 +1,5 @@ """The test for sensor device automation.""" + from datetime import timedelta import pytest diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 52e1851833e..909552bb2ed 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -1,4 +1,5 @@ """The test for sensor entity.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index b4b535473c1..83cfd8c2e83 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -1,4 +1,5 @@ """The tests for sensor recorder platform.""" + from collections.abc import Callable from datetime import datetime, timedelta import math diff --git a/tests/components/sensor/test_recorder_missing_stats.py b/tests/components/sensor/test_recorder_missing_stats.py index 810eaf6d730..88c98e6589f 100644 --- a/tests/components/sensor/test_recorder_missing_stats.py +++ b/tests/components/sensor/test_recorder_missing_stats.py @@ -1,4 +1,5 @@ """The tests for sensor recorder platform can catch up.""" + from datetime import datetime, timedelta from pathlib import Path from unittest.mock import patch diff --git a/tests/components/sensor/test_websocket_api.py b/tests/components/sensor/test_websocket_api.py index 98d07b599fe..6f4eeb252e2 100644 --- a/tests/components/sensor/test_websocket_api.py +++ b/tests/components/sensor/test_websocket_api.py @@ -1,4 +1,5 @@ """Test the sensor websocket API.""" + from pytest_unordered import unordered from homeassistant.components.sensor.const import ( diff --git a/tests/components/sensorpro/test_config_flow.py b/tests/components/sensorpro/test_config_flow.py index b876fb215c1..1558e774f21 100644 --- a/tests/components/sensorpro/test_config_flow.py +++ b/tests/components/sensorpro/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SensorPro config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/sensorpro/test_sensor.py b/tests/components/sensorpro/test_sensor.py index 5cb4483e92c..b98c629b51a 100644 --- a/tests/components/sensorpro/test_sensor.py +++ b/tests/components/sensorpro/test_sensor.py @@ -1,4 +1,5 @@ """Test the SensorPro sensors.""" + from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.components.sensorpro.const import DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/sensorpush/test_config_flow.py b/tests/components/sensorpush/test_config_flow.py index cf1cc7ea6fc..abbe04178c2 100644 --- a/tests/components/sensorpush/test_config_flow.py +++ b/tests/components/sensorpush/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SensorPush config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/sensorpush/test_sensor.py b/tests/components/sensorpush/test_sensor.py index 2e7a0867309..1054f97fb12 100644 --- a/tests/components/sensorpush/test_sensor.py +++ b/tests/components/sensorpush/test_sensor.py @@ -1,4 +1,5 @@ """Test the SensorPush sensors.""" + from datetime import timedelta import time diff --git a/tests/components/sentry/conftest.py b/tests/components/sentry/conftest.py index a7347d44bab..781250b2753 100644 --- a/tests/components/sentry/conftest.py +++ b/tests/components/sentry/conftest.py @@ -1,4 +1,5 @@ """Configuration for Sentry tests.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/senz/test_config_flow.py b/tests/components/senz/test_config_flow.py index e9179f9ab30..04ef1a6de0c 100644 --- a/tests/components/senz/test_config_flow.py +++ b/tests/components/senz/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SENZ config flow.""" + from unittest.mock import patch from aiosenz import AUTHORIZATION_ENDPOINT, TOKEN_ENDPOINT diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 653e4b956b6..995aaaaac87 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the seventeentrack sensor.""" + from __future__ import annotations import datetime diff --git a/tests/components/sfr_box/conftest.py b/tests/components/sfr_box/conftest.py index a8cd6fd8bd4..dec99738a03 100644 --- a/tests/components/sfr_box/conftest.py +++ b/tests/components/sfr_box/conftest.py @@ -1,4 +1,5 @@ """Provide common SFR Box fixtures.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/sfr_box/test_binary_sensor.py b/tests/components/sfr_box/test_binary_sensor.py index 65f3c8f8c0e..f3d012712ca 100644 --- a/tests/components/sfr_box/test_binary_sensor.py +++ b/tests/components/sfr_box/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the SFR Box binary sensors.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/sfr_box/test_button.py b/tests/components/sfr_box/test_button.py index 5a833056291..a83c46dbd87 100644 --- a/tests/components/sfr_box/test_button.py +++ b/tests/components/sfr_box/test_button.py @@ -1,4 +1,5 @@ """Test the SFR Box buttons.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/sfr_box/test_diagnostics.py b/tests/components/sfr_box/test_diagnostics.py index a433236ab7a..512a737d434 100644 --- a/tests/components/sfr_box/test_diagnostics.py +++ b/tests/components/sfr_box/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the SFR Box diagnostics.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/sfr_box/test_init.py b/tests/components/sfr_box/test_init.py index df4d1242e02..4bcd4ae9208 100644 --- a/tests/components/sfr_box/test_init.py +++ b/tests/components/sfr_box/test_init.py @@ -1,4 +1,5 @@ """Test the SFR Box setup process.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/sfr_box/test_sensor.py b/tests/components/sfr_box/test_sensor.py index c374837c5a7..9d806f39be7 100644 --- a/tests/components/sfr_box/test_sensor.py +++ b/tests/components/sfr_box/test_sensor.py @@ -1,4 +1,5 @@ """Test the SFR Box sensors.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/sharkiq/test_config_flow.py b/tests/components/sharkiq/test_config_flow.py index a98eff6f2bb..da185968127 100644 --- a/tests/components/sharkiq/test_config_flow.py +++ b/tests/components/sharkiq/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Shark IQ config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/sharkiq/test_vacuum.py b/tests/components/sharkiq/test_vacuum.py index 34b49f5d581..4a1671a616f 100644 --- a/tests/components/sharkiq/test_vacuum.py +++ b/tests/components/sharkiq/test_vacuum.py @@ -1,4 +1,5 @@ """Test the Shark IQ vacuum entity.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index b0c0680c905..89fb768db2d 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -1,4 +1,5 @@ """The tests for the Shell command component.""" + from __future__ import annotations import asyncio diff --git a/tests/components/shelly/bluetooth/test_scanner.py b/tests/components/shelly/bluetooth/test_scanner.py index d9ec0064606..c7bbb5cb708 100644 --- a/tests/components/shelly/bluetooth/test_scanner.py +++ b/tests/components/shelly/bluetooth/test_scanner.py @@ -1,4 +1,5 @@ """Test the shelly bluetooth scanner.""" + from __future__ import annotations from aioshelly.ble.const import BLE_SCAN_RESULT_EVENT diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 1d4a00f34ca..df6a5f41306 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -1,4 +1,5 @@ """Test configuration for Shelly.""" + from unittest.mock import AsyncMock, Mock, PropertyMock, patch from aioshelly.block_device import BlockDevice, BlockUpdateType diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index db1f27b0c1a..00a430cd4b1 100644 --- a/tests/components/shelly/test_binary_sensor.py +++ b/tests/components/shelly/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for Shelly binary sensor platform.""" + from unittest.mock import Mock from aioshelly.const import MODEL_MOTION diff --git a/tests/components/shelly/test_button.py b/tests/components/shelly/test_button.py index 33b11d0e045..14349411670 100644 --- a/tests/components/shelly/test_button.py +++ b/tests/components/shelly/test_button.py @@ -1,4 +1,5 @@ """Tests for Shelly button platform.""" + from unittest.mock import Mock import pytest diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py index f435d337537..7e0e2d1ce46 100644 --- a/tests/components/shelly/test_climate.py +++ b/tests/components/shelly/test_climate.py @@ -1,4 +1,5 @@ """Tests for Shelly climate platform.""" + from copy import deepcopy from unittest.mock import AsyncMock, Mock, PropertyMock diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 50e42f71b33..7ce111fdf21 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Shelly config flow.""" + from dataclasses import replace from ipaddress import ip_address from typing import Any diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py index 67df09a5adb..4c169998104 100644 --- a/tests/components/shelly/test_coordinator.py +++ b/tests/components/shelly/test_coordinator.py @@ -1,4 +1,5 @@ """Tests for Shelly coordinator.""" + from datetime import timedelta from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/shelly/test_cover.py b/tests/components/shelly/test_cover.py index 890bdaf8619..cd5efb76cfe 100644 --- a/tests/components/shelly/test_cover.py +++ b/tests/components/shelly/test_cover.py @@ -1,4 +1,5 @@ """Tests for Shelly cover platform.""" + from unittest.mock import Mock import pytest diff --git a/tests/components/shelly/test_device_trigger.py b/tests/components/shelly/test_device_trigger.py index d6f48a03eab..c4db8acaf6d 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Shelly device triggers.""" + from unittest.mock import Mock from aioshelly.const import MODEL_BUTTON1 diff --git a/tests/components/shelly/test_diagnostics.py b/tests/components/shelly/test_diagnostics.py index d65244ee0b7..f7f238f3327 100644 --- a/tests/components/shelly/test_diagnostics.py +++ b/tests/components/shelly/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for Shelly diagnostics platform.""" + from unittest.mock import ANY, Mock from aioshelly.ble.const import BLE_SCAN_RESULT_EVENT diff --git a/tests/components/shelly/test_event.py b/tests/components/shelly/test_event.py index d7319811748..2465b016808 100644 --- a/tests/components/shelly/test_event.py +++ b/tests/components/shelly/test_event.py @@ -1,4 +1,5 @@ """Tests for Shelly button platform.""" + from unittest.mock import Mock from aioshelly.const import MODEL_I3 diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index 79115354299..6a2d5394c80 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -1,4 +1,5 @@ """Test cases for the Shelly component.""" + from unittest.mock import AsyncMock, Mock, patch from aioshelly.exceptions import ( diff --git a/tests/components/shelly/test_light.py b/tests/components/shelly/test_light.py index f484f526e3f..2c7eda3a1e0 100644 --- a/tests/components/shelly/test_light.py +++ b/tests/components/shelly/test_light.py @@ -1,4 +1,5 @@ """Tests for Shelly light platform.""" + from unittest.mock import AsyncMock, Mock from aioshelly.const import ( diff --git a/tests/components/shelly/test_logbook.py b/tests/components/shelly/test_logbook.py index 7cf73fd343f..cd1714d6b26 100644 --- a/tests/components/shelly/test_logbook.py +++ b/tests/components/shelly/test_logbook.py @@ -1,4 +1,5 @@ """The tests for Shelly logbook.""" + from unittest.mock import Mock from homeassistant.components.shelly.const import ( diff --git a/tests/components/shelly/test_number.py b/tests/components/shelly/test_number.py index 855ac263b0b..ecc6d7410bf 100644 --- a/tests/components/shelly/test_number.py +++ b/tests/components/shelly/test_number.py @@ -1,4 +1,5 @@ """Tests for Shelly number platform.""" + from unittest.mock import AsyncMock, Mock from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 03bcc545d15..0a15b78994b 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -1,4 +1,5 @@ """Tests for Shelly sensor platform.""" + from copy import deepcopy from unittest.mock import Mock diff --git a/tests/components/shelly/test_switch.py b/tests/components/shelly/test_switch.py index e3ba9c9da73..a57a9890921 100644 --- a/tests/components/shelly/test_switch.py +++ b/tests/components/shelly/test_switch.py @@ -1,4 +1,5 @@ """Tests for Shelly switch platform.""" + from copy import deepcopy from unittest.mock import AsyncMock, Mock diff --git a/tests/components/shelly/test_update.py b/tests/components/shelly/test_update.py index b3a4ed5f703..387dc93e33e 100644 --- a/tests/components/shelly/test_update.py +++ b/tests/components/shelly/test_update.py @@ -1,4 +1,5 @@ """Tests for Shelly update platform.""" + from unittest.mock import AsyncMock, Mock from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError diff --git a/tests/components/shelly/test_utils.py b/tests/components/shelly/test_utils.py index 73cb7e83fdd..7c4ea8accae 100644 --- a/tests/components/shelly/test_utils.py +++ b/tests/components/shelly/test_utils.py @@ -1,4 +1,5 @@ """Tests for Shelly utils.""" + from typing import Any from unittest.mock import Mock diff --git a/tests/components/shelly/test_valve.py b/tests/components/shelly/test_valve.py index 1f9d2f76399..b588cd28906 100644 --- a/tests/components/shelly/test_valve.py +++ b/tests/components/shelly/test_valve.py @@ -1,4 +1,5 @@ """Tests for Shelly valve platform.""" + from unittest.mock import Mock from aioshelly.const import MODEL_GAS diff --git a/tests/components/shopping_list/conftest.py b/tests/components/shopping_list/conftest.py index aec55362d0b..3af5d30972e 100644 --- a/tests/components/shopping_list/conftest.py +++ b/tests/components/shopping_list/conftest.py @@ -1,4 +1,5 @@ """Shopping list test helpers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/shopping_list/test_config_flow.py b/tests/components/shopping_list/test_config_flow.py index 34d74d18046..1d807e87ca2 100644 --- a/tests/components/shopping_list/test_config_flow.py +++ b/tests/components/shopping_list/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from homeassistant.components.shopping_list.const import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.core import HomeAssistant diff --git a/tests/components/shopping_list/test_init.py b/tests/components/shopping_list/test_init.py index a28b1ee0cfb..c28ea66a32b 100644 --- a/tests/components/shopping_list/test_init.py +++ b/tests/components/shopping_list/test_init.py @@ -1,4 +1,5 @@ """Test shopping list component.""" + from http import HTTPStatus import pytest diff --git a/tests/components/shopping_list/test_intent.py b/tests/components/shopping_list/test_intent.py index 50c698def5d..07128835b6a 100644 --- a/tests/components/shopping_list/test_intent.py +++ b/tests/components/shopping_list/test_intent.py @@ -1,4 +1,5 @@ """Test Shopping List intents.""" + from homeassistant.core import HomeAssistant from homeassistant.helpers import intent diff --git a/tests/components/sia/test_config_flow.py b/tests/components/sia/test_config_flow.py index ef252991d7c..542c06da24f 100644 --- a/tests/components/sia/test_config_flow.py +++ b/tests/components/sia/test_config_flow.py @@ -1,4 +1,5 @@ """Test the sia config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/sigfox/test_sensor.py b/tests/components/sigfox/test_sensor.py index dba30995bc4..c9876f5f3f9 100644 --- a/tests/components/sigfox/test_sensor.py +++ b/tests/components/sigfox/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sigfox sensor.""" + from http import HTTPStatus import re diff --git a/tests/components/sighthound/test_image_processing.py b/tests/components/sighthound/test_image_processing.py index 5961b925a2a..09d6c2a1ca8 100644 --- a/tests/components/sighthound/test_image_processing.py +++ b/tests/components/sighthound/test_image_processing.py @@ -1,4 +1,5 @@ """Tests for the Sighthound integration.""" + from copy import deepcopy import datetime import os diff --git a/tests/components/signal_messenger/conftest.py b/tests/components/signal_messenger/conftest.py index 017f598b93c..ecafff1ef4a 100644 --- a/tests/components/signal_messenger/conftest.py +++ b/tests/components/signal_messenger/conftest.py @@ -1,4 +1,5 @@ """Signal notification test helpers.""" + from http import HTTPStatus from pysignalclirestapi import SignalCliRestApi diff --git a/tests/components/simplepush/test_config_flow.py b/tests/components/simplepush/test_config_flow.py index 02db81ceaa7..3905014747b 100644 --- a/tests/components/simplepush/test_config_flow.py +++ b/tests/components/simplepush/test_config_flow.py @@ -1,4 +1,5 @@ """Test Simplepush config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py index 538165bd769..e6a9d70b164 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -1,4 +1,5 @@ """Test SimpliSafe diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant diff --git a/tests/components/simplisafe/test_init.py b/tests/components/simplisafe/test_init.py index cc7b2b8d2b6..3a89caeb891 100644 --- a/tests/components/simplisafe/test_init.py +++ b/tests/components/simplisafe/test_init.py @@ -1,4 +1,5 @@ """Define tests for SimpliSafe setup.""" + from unittest.mock import patch from homeassistant.components.simplisafe import DOMAIN diff --git a/tests/components/simulated/test_sensor.py b/tests/components/simulated/test_sensor.py index 32c04c7a462..d32eca8c66e 100644 --- a/tests/components/simulated/test_sensor.py +++ b/tests/components/simulated/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the simulated sensor.""" + from homeassistant.components.simulated.sensor import ( CONF_AMP, CONF_FWHM, diff --git a/tests/components/siren/test_init.py b/tests/components/siren/test_init.py index 1cf44d16ea0..168300d0abe 100644 --- a/tests/components/siren/test_init.py +++ b/tests/components/siren/test_init.py @@ -1,4 +1,5 @@ """The tests for the siren component.""" + from types import ModuleType from unittest.mock import MagicMock diff --git a/tests/components/siren/test_recorder.py b/tests/components/siren/test_recorder.py index 76b497e024a..bf9a49a7c76 100644 --- a/tests/components/siren/test_recorder.py +++ b/tests/components/siren/test_recorder.py @@ -1,4 +1,5 @@ """The tests for siren recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/skybell/conftest.py b/tests/components/skybell/conftest.py index beb3fec9b98..9b95cfd79bb 100644 --- a/tests/components/skybell/conftest.py +++ b/tests/components/skybell/conftest.py @@ -1,4 +1,5 @@ """Configure pytest for Skybell tests.""" + from unittest.mock import AsyncMock, patch from aioskybell import Skybell, SkybellDevice diff --git a/tests/components/skybell/test_binary_sensor.py b/tests/components/skybell/test_binary_sensor.py index 8e0bc884730..004f8160366 100644 --- a/tests/components/skybell/test_binary_sensor.py +++ b/tests/components/skybell/test_binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor tests for the Skybell integration.""" + from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.const import ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant diff --git a/tests/components/skybell/test_config_flow.py b/tests/components/skybell/test_config_flow.py index d83f4243d7f..535eb91f01b 100644 --- a/tests/components/skybell/test_config_flow.py +++ b/tests/components/skybell/test_config_flow.py @@ -1,4 +1,5 @@ """Test SkyBell config flow.""" + from unittest.mock import patch from aioskybell import exceptions diff --git a/tests/components/slack/test_config_flow.py b/tests/components/slack/test_config_flow.py index e941e4ba47c..c7b8d927c94 100644 --- a/tests/components/slack/test_config_flow.py +++ b/tests/components/slack/test_config_flow.py @@ -1,4 +1,5 @@ """Test Slack config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/slack/test_init.py b/tests/components/slack/test_init.py index 487a65b8ef0..e206e066c67 100644 --- a/tests/components/slack/test_init.py +++ b/tests/components/slack/test_init.py @@ -1,4 +1,5 @@ """Test Slack integration.""" + from homeassistant.components.slack.const import DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/slack/test_notify.py b/tests/components/slack/test_notify.py index 6c90ad8cd39..e2c8e907185 100644 --- a/tests/components/slack/test_notify.py +++ b/tests/components/slack/test_notify.py @@ -1,4 +1,5 @@ """Test slack notifications.""" + from __future__ import annotations from unittest.mock import AsyncMock, Mock diff --git a/tests/components/sleepiq/conftest.py b/tests/components/sleepiq/conftest.py index 58718edcafb..3a53e8ce684 100644 --- a/tests/components/sleepiq/conftest.py +++ b/tests/components/sleepiq/conftest.py @@ -1,4 +1,5 @@ """Common methods for SleepIQ.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index 25147dd3823..bbb0200dd23 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for SleepIQ binary sensor platform.""" + from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDeviceClass from homeassistant.const import ( ATTR_DEVICE_CLASS, diff --git a/tests/components/sleepiq/test_button.py b/tests/components/sleepiq/test_button.py index c766db6b286..0979d01ba7b 100644 --- a/tests/components/sleepiq/test_button.py +++ b/tests/components/sleepiq/test_button.py @@ -1,4 +1,5 @@ """The tests for SleepIQ binary sensor platform.""" + from homeassistant.components.button import DOMAIN from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME from homeassistant.core import HomeAssistant diff --git a/tests/components/sleepiq/test_config_flow.py b/tests/components/sleepiq/test_config_flow.py index 0f251675892..b623252cec4 100644 --- a/tests/components/sleepiq/test_config_flow.py +++ b/tests/components/sleepiq/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the SleepIQ config flow.""" + from unittest.mock import AsyncMock, patch from asyncsleepiq import SleepIQLoginException, SleepIQTimeoutException diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index 6a02f795805..216d0e49b08 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -1,4 +1,5 @@ """Tests for the SleepIQ integration.""" + from asyncsleepiq import ( SleepIQAPIException, SleepIQLoginException, diff --git a/tests/components/sleepiq/test_light.py b/tests/components/sleepiq/test_light.py index 24193308f46..55961ba989f 100644 --- a/tests/components/sleepiq/test_light.py +++ b/tests/components/sleepiq/test_light.py @@ -1,4 +1,5 @@ """The tests for SleepIQ light platform.""" + from homeassistant.components.light import DOMAIN from homeassistant.components.sleepiq.coordinator import LONGER_UPDATE_INTERVAL from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON diff --git a/tests/components/sleepiq/test_number.py b/tests/components/sleepiq/test_number.py index 4676cf94174..f3a38cc89e5 100644 --- a/tests/components/sleepiq/test_number.py +++ b/tests/components/sleepiq/test_number.py @@ -1,4 +1,5 @@ """The tests for SleepIQ number platform.""" + from homeassistant.components.number import ( ATTR_MAX, ATTR_MIN, diff --git a/tests/components/sleepiq/test_select.py b/tests/components/sleepiq/test_select.py index c4ec3896bd7..cc61494689e 100644 --- a/tests/components/sleepiq/test_select.py +++ b/tests/components/sleepiq/test_select.py @@ -1,4 +1,5 @@ """Tests for the SleepIQ select platform.""" + from unittest.mock import MagicMock from asyncsleepiq import FootWarmingTemps diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index a707a0169af..c027aaee87b 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -1,4 +1,5 @@ """The tests for SleepIQ sensor platform.""" + from homeassistant.components.sensor import DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON from homeassistant.core import HomeAssistant diff --git a/tests/components/sleepiq/test_switch.py b/tests/components/sleepiq/test_switch.py index 04d0b6657d7..3cc9db235b6 100644 --- a/tests/components/sleepiq/test_switch.py +++ b/tests/components/sleepiq/test_switch.py @@ -1,4 +1,5 @@ """The tests for SleepIQ switch platform.""" + from homeassistant.components.sleepiq.coordinator import LONGER_UPDATE_INTERVAL from homeassistant.components.switch import DOMAIN from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON diff --git a/tests/components/slimproto/conftest.py b/tests/components/slimproto/conftest.py index faf91ea27d1..637f5ec0a99 100644 --- a/tests/components/slimproto/conftest.py +++ b/tests/components/slimproto/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the SlimProto Player integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/slimproto/test_config_flow.py b/tests/components/slimproto/test_config_flow.py index 15ea5434fc5..686768c6eb6 100644 --- a/tests/components/slimproto/test_config_flow.py +++ b/tests/components/slimproto/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SlimProto Player config flow.""" + from unittest.mock import AsyncMock from homeassistant.components.slimproto.const import DEFAULT_NAME, DOMAIN diff --git a/tests/components/sma/conftest.py b/tests/components/sma/conftest.py index 2ce5db5e0ca..2da12c249d7 100644 --- a/tests/components/sma/conftest.py +++ b/tests/components/sma/conftest.py @@ -1,4 +1,5 @@ """Fixtures for sma tests.""" + from unittest.mock import patch from pysma.const import GENERIC_SENSORS diff --git a/tests/components/sma/test_config_flow.py b/tests/components/sma/test_config_flow.py index 541376d1536..afb4da9aff7 100644 --- a/tests/components/sma/test_config_flow.py +++ b/tests/components/sma/test_config_flow.py @@ -1,4 +1,5 @@ """Test the sma config flow.""" + from unittest.mock import patch from pysma.exceptions import ( diff --git a/tests/components/sma/test_sensor.py b/tests/components/sma/test_sensor.py index acc26a8bf90..de7e1167f1f 100644 --- a/tests/components/sma/test_sensor.py +++ b/tests/components/sma/test_sensor.py @@ -1,4 +1,5 @@ """Test the sma sensor platform.""" + from pysma.const import ( ENERGY_METER_VIA_INVERTER, GENERIC_SENSORS, diff --git a/tests/components/smappee/test_config_flow.py b/tests/components/smappee/test_config_flow.py index 8d4d7b8c3b2..794510366e2 100644 --- a/tests/components/smappee/test_config_flow.py +++ b/tests/components/smappee/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Smappee component config flow module.""" + from http import HTTPStatus from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/smappee/test_init.py b/tests/components/smappee/test_init.py index 34700c26a5a..489b60572e3 100644 --- a/tests/components/smappee/test_init.py +++ b/tests/components/smappee/test_init.py @@ -1,4 +1,5 @@ """Tests for the Smappee component init module.""" + from unittest.mock import patch from homeassistant.components.smappee.const import DOMAIN diff --git a/tests/components/smart_meter_texas/conftest.py b/tests/components/smart_meter_texas/conftest.py index 782deafbcf3..04a3344b5cc 100644 --- a/tests/components/smart_meter_texas/conftest.py +++ b/tests/components/smart_meter_texas/conftest.py @@ -1,4 +1,5 @@ """Test configuration and mocks for Smart Meter Texas.""" + from http import HTTPStatus import json diff --git a/tests/components/smart_meter_texas/test_config_flow.py b/tests/components/smart_meter_texas/test_config_flow.py index 0fb56937f0a..e63a7f4530b 100644 --- a/tests/components/smart_meter_texas/test_config_flow.py +++ b/tests/components/smart_meter_texas/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Smart Meter Texas config flow.""" + from unittest.mock import patch from aiohttp import ClientError diff --git a/tests/components/smart_meter_texas/test_init.py b/tests/components/smart_meter_texas/test_init.py index f3e8e73a24d..df0d5385c66 100644 --- a/tests/components/smart_meter_texas/test_init.py +++ b/tests/components/smart_meter_texas/test_init.py @@ -1,4 +1,5 @@ """Test the Smart Meter Texas module.""" + from unittest.mock import patch from homeassistant.components.homeassistant import ( diff --git a/tests/components/smart_meter_texas/test_sensor.py b/tests/components/smart_meter_texas/test_sensor.py index 7960b007d74..36142d5f059 100644 --- a/tests/components/smart_meter_texas/test_sensor.py +++ b/tests/components/smart_meter_texas/test_sensor.py @@ -1,4 +1,5 @@ """Test the Smart Meter Texas sensor entity.""" + from unittest.mock import patch from homeassistant.components.homeassistant import ( diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index 1c222b3ca78..9d704cdf8c9 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import ATTRIBUTES, CAPABILITIES, Attribute, Capability from homeassistant.components.binary_sensor import ( diff --git a/tests/components/smartthings/test_climate.py b/tests/components/smartthings/test_climate.py index 475a8f09e03..3fb293e587f 100644 --- a/tests/components/smartthings/test_climate.py +++ b/tests/components/smartthings/test_climate.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import Attribute, Capability from pysmartthings.device import Status import pytest diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 168756b0dfe..b8a850443d5 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the SmartThings config flow module.""" + from http import HTTPStatus from unittest.mock import AsyncMock, Mock, patch from uuid import uuid4 diff --git a/tests/components/smartthings/test_cover.py b/tests/components/smartthings/test_cover.py index 081b40e57a9..c4f6c15a3fe 100644 --- a/tests/components/smartthings/test_cover.py +++ b/tests/components/smartthings/test_cover.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import Attribute, Capability from homeassistant.components.cover import ( diff --git a/tests/components/smartthings/test_fan.py b/tests/components/smartthings/test_fan.py index ca2f97b2909..b8928ef5247 100644 --- a/tests/components/smartthings/test_fan.py +++ b/tests/components/smartthings/test_fan.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import Attribute, Capability from homeassistant.components.fan import ( diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index ec2ec3abe19..f960ba15e85 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -1,4 +1,5 @@ """Tests for the SmartThings component init module.""" + from http import HTTPStatus from unittest.mock import Mock, patch from uuid import uuid4 diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index b6e4e5a107b..5a92023cc6c 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import Attribute, Capability import pytest diff --git a/tests/components/smartthings/test_lock.py b/tests/components/smartthings/test_lock.py index 10981433c1d..2e149df6213 100644 --- a/tests/components/smartthings/test_lock.py +++ b/tests/components/smartthings/test_lock.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import Attribute, Capability from pysmartthings.device import Status diff --git a/tests/components/smartthings/test_scene.py b/tests/components/smartthings/test_scene.py index 489ca87371c..1eaaad55d0f 100644 --- a/tests/components/smartthings/test_scene.py +++ b/tests/components/smartthings/test_scene.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNAVAILABLE diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py index 35d38dd33de..6529a7f25f0 100644 --- a/tests/components/smartthings/test_sensor.py +++ b/tests/components/smartthings/test_sensor.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import ATTRIBUTES, CAPABILITIES, Attribute, Capability from homeassistant.components.sensor import ( diff --git a/tests/components/smartthings/test_smartapp.py b/tests/components/smartthings/test_smartapp.py index 155ed8b3ff1..c7861866fad 100644 --- a/tests/components/smartthings/test_smartapp.py +++ b/tests/components/smartthings/test_smartapp.py @@ -1,4 +1,5 @@ """Tests for the smartapp module.""" + from unittest.mock import AsyncMock, Mock, patch from uuid import uuid4 diff --git a/tests/components/smartthings/test_switch.py b/tests/components/smartthings/test_switch.py index e9dd8ad1b68..d858a9eea5a 100644 --- a/tests/components/smartthings/test_switch.py +++ b/tests/components/smartthings/test_switch.py @@ -3,6 +3,7 @@ The only mocking required is of the underlying SmartThings API object so real HTTP calls are not initiated during testing. """ + from pysmartthings import Attribute, Capability from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py index 97ed23d3d0b..3365b03b041 100644 --- a/tests/components/smarttub/test_binary_sensor.py +++ b/tests/components/smarttub/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the SmartTub binary sensor platform.""" + from datetime import datetime from unittest.mock import create_autospec diff --git a/tests/components/smarttub/test_config_flow.py b/tests/components/smarttub/test_config_flow.py index 137b73480ea..df3695f31af 100644 --- a/tests/components/smarttub/test_config_flow.py +++ b/tests/components/smarttub/test_config_flow.py @@ -1,4 +1,5 @@ """Test the smarttub config flow.""" + from unittest.mock import patch from smarttub import LoginFailed diff --git a/tests/components/smarttub/test_init.py b/tests/components/smarttub/test_init.py index 083e0dc8b46..b1eac3fd98b 100644 --- a/tests/components/smarttub/test_init.py +++ b/tests/components/smarttub/test_init.py @@ -1,4 +1,5 @@ """Test smarttub setup process.""" + from unittest.mock import patch from smarttub import LoginFailed diff --git a/tests/components/smhi/common.py b/tests/components/smhi/common.py index 8a12cf651b7..7339ba76ac1 100644 --- a/tests/components/smhi/common.py +++ b/tests/components/smhi/common.py @@ -1,4 +1,5 @@ """Common test utilities.""" + from unittest.mock import Mock diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index f33849694c8..b8742d0cbf6 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Smhi config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/smhi/test_init.py b/tests/components/smhi/test_init.py index ec6e4c417bb..aefbccb64ec 100644 --- a/tests/components/smhi/test_init.py +++ b/tests/components/smhi/test_init.py @@ -1,4 +1,5 @@ """Test SMHI component setup process.""" + from unittest.mock import patch from smhi.smhi_lib import APIURL_TEMPLATE diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 2ad9153dd41..65d3782c2b4 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -1,4 +1,5 @@ """Test for the smhi weather entity.""" + from datetime import datetime, timedelta from unittest.mock import patch diff --git a/tests/components/smtp/test_notify.py b/tests/components/smtp/test_notify.py index 182b45d9c1b..73911de7ed4 100644 --- a/tests/components/smtp/test_notify.py +++ b/tests/components/smtp/test_notify.py @@ -1,4 +1,5 @@ """The tests for the notify smtp platform.""" + from pathlib import Path import re from unittest.mock import patch diff --git a/tests/components/snapcast/conftest.py b/tests/components/snapcast/conftest.py index 00d031192d8..7d29b098482 100644 --- a/tests/components/snapcast/conftest.py +++ b/tests/components/snapcast/conftest.py @@ -1,4 +1,5 @@ """Test the snapcast config flow.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/snooz/conftest.py b/tests/components/snooz/conftest.py index cb4873e7937..8cdc2ec0982 100644 --- a/tests/components/snooz/conftest.py +++ b/tests/components/snooz/conftest.py @@ -1,4 +1,5 @@ """Snooz test fixtures and configuration.""" + from __future__ import annotations import pytest diff --git a/tests/components/snooz/test_config_flow.py b/tests/components/snooz/test_config_flow.py index 385a47cf578..172ca3cd143 100644 --- a/tests/components/snooz/test_config_flow.py +++ b/tests/components/snooz/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Snooz config flow.""" + from __future__ import annotations from asyncio import Event diff --git a/tests/components/snooz/test_fan.py b/tests/components/snooz/test_fan.py index 795525fdf71..ddc93a4ba1f 100644 --- a/tests/components/snooz/test_fan.py +++ b/tests/components/snooz/test_fan.py @@ -1,4 +1,5 @@ """Test Snooz fan entity.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/snooz/test_init.py b/tests/components/snooz/test_init.py index a7a0566d7c6..b1ab06fcc8e 100644 --- a/tests/components/snooz/test_init.py +++ b/tests/components/snooz/test_init.py @@ -1,4 +1,5 @@ """Test Snooz configuration.""" + from __future__ import annotations import pytest diff --git a/tests/components/solaredge/test_config_flow.py b/tests/components/solaredge/test_config_flow.py index c23d2578d1c..81b97c071fd 100644 --- a/tests/components/solaredge/test_config_flow.py +++ b/tests/components/solaredge/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the SolarEdge config flow.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/solaredge/test_coordinator.py b/tests/components/solaredge/test_coordinator.py index de9aab016ee..4bd9dee930c 100644 --- a/tests/components/solaredge/test_coordinator.py +++ b/tests/components/solaredge/test_coordinator.py @@ -1,4 +1,5 @@ """Tests for the SolarEdge coordinator services.""" + from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/solarlog/test_config_flow.py b/tests/components/solarlog/test_config_flow.py index 9383f517104..e80e49bc9e1 100644 --- a/tests/components/solarlog/test_config_flow.py +++ b/tests/components/solarlog/test_config_flow.py @@ -1,4 +1,5 @@ """Test the solarlog config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/solax/test_config_flow.py b/tests/components/solax/test_config_flow.py index 23be906137b..47e588d34a3 100644 --- a/tests/components/solax/test_config_flow.py +++ b/tests/components/solax/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the solax config flow.""" + from unittest.mock import patch from solax import RealTimeAPI diff --git a/tests/components/soma/test_config_flow.py b/tests/components/soma/test_config_flow.py index 91a0d27b428..04a93bb5a58 100644 --- a/tests/components/soma/test_config_flow.py +++ b/tests/components/soma/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Soma config flow.""" + from unittest.mock import patch from api.soma_api import SomaApi diff --git a/tests/components/somfy_mylink/test_config_flow.py b/tests/components/somfy_mylink/test_config_flow.py index 68f0bc0a04c..c5d1c726a8f 100644 --- a/tests/components/somfy_mylink/test_config_flow.py +++ b/tests/components/somfy_mylink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Somfy MyLink config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/sonarr/conftest.py b/tests/components/sonarr/conftest.py index da8ff75df0f..7c18fb372a1 100644 --- a/tests/components/sonarr/conftest.py +++ b/tests/components/sonarr/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Sonarr integration tests.""" + from collections.abc import Generator import json from unittest.mock import MagicMock, patch diff --git a/tests/components/sonarr/test_config_flow.py b/tests/components/sonarr/test_config_flow.py index 2a078f49190..3e48a4b25a8 100644 --- a/tests/components/sonarr/test_config_flow.py +++ b/tests/components/sonarr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Sonarr config flow.""" + from unittest.mock import MagicMock, patch from aiopyarr import ArrAuthenticationException, ArrException diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py index 5c48f6c8445..9f512c11074 100644 --- a/tests/components/sonarr/test_init.py +++ b/tests/components/sonarr/test_init.py @@ -1,4 +1,5 @@ """Tests for the Sonsrr integration.""" + from unittest.mock import MagicMock, patch from aiopyarr import ArrAuthenticationException, ArrException diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index eccc535ff66..2304a9388ea 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Sonarr sensor platform.""" + from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/songpal/test_init.py b/tests/components/songpal/test_init.py index 813b615acdd..6bc14e06153 100644 --- a/tests/components/songpal/test_init.py +++ b/tests/components/songpal/test_init.py @@ -1,4 +1,5 @@ """Tests songpal setup.""" + from unittest.mock import patch from homeassistant.components import songpal diff --git a/tests/components/songpal/test_media_player.py b/tests/components/songpal/test_media_player.py index 534e2e6e9e6..4b1abf8709e 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -1,4 +1,5 @@ """Test songpal media_player.""" + from datetime import timedelta import logging from unittest.mock import AsyncMock, MagicMock, call, patch diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 8bd8224e726..f46023a05de 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -1,4 +1,5 @@ """Configuration for Sonos tests.""" + from copy import copy from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock, Mock, patch diff --git a/tests/components/sonos/test_config_flow.py b/tests/components/sonos/test_config_flow.py index 2fd8ad110df..2b408f1fa74 100644 --- a/tests/components/sonos/test_config_flow.py +++ b/tests/components/sonos/test_config_flow.py @@ -1,4 +1,5 @@ """Test the sonos config flow.""" + from __future__ import annotations from ipaddress import ip_address diff --git a/tests/components/sonos/test_helpers.py b/tests/components/sonos/test_helpers.py index 48594d65301..fde61527953 100644 --- a/tests/components/sonos/test_helpers.py +++ b/tests/components/sonos/test_helpers.py @@ -1,4 +1,5 @@ """Test the sonos config flow.""" + from __future__ import annotations import pytest diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index ddf550dc376..d89a1076db3 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the Sonos Media Player platform.""" + from homeassistant.const import STATE_IDLE from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import ( diff --git a/tests/components/sonos/test_number.py b/tests/components/sonos/test_number.py index d58b84ab6cb..abfe0b1ff3f 100644 --- a/tests/components/sonos/test_number.py +++ b/tests/components/sonos/test_number.py @@ -1,4 +1,5 @@ """Tests for the Sonos number platform.""" + from unittest.mock import PropertyMock, patch from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE diff --git a/tests/components/sonos/test_repairs.py b/tests/components/sonos/test_repairs.py index b86c6bd5f66..cc1f59c5cd0 100644 --- a/tests/components/sonos/test_repairs.py +++ b/tests/components/sonos/test_repairs.py @@ -1,4 +1,5 @@ """Test repairs handling for Sonos.""" + from unittest.mock import Mock from homeassistant.components.sonos.const import ( diff --git a/tests/components/sonos/test_sensor.py b/tests/components/sonos/test_sensor.py index 40b0c2d21c6..6e4461e5397 100644 --- a/tests/components/sonos/test_sensor.py +++ b/tests/components/sonos/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Sonos battery sensor platform.""" + from datetime import timedelta from unittest.mock import PropertyMock, patch diff --git a/tests/components/sonos/test_services.py b/tests/components/sonos/test_services.py index 029c80835ee..adea75f74ab 100644 --- a/tests/components/sonos/test_services.py +++ b/tests/components/sonos/test_services.py @@ -1,4 +1,5 @@ """Tests for Sonos services.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/sonos/test_speaker.py b/tests/components/sonos/test_speaker.py index e9b85c22eb3..8ae1d0bae03 100644 --- a/tests/components/sonos/test_speaker.py +++ b/tests/components/sonos/test_speaker.py @@ -1,4 +1,5 @@ """Tests for common SonosSpeaker behavior.""" + from unittest.mock import patch import pytest diff --git a/tests/components/sonos/test_statistics.py b/tests/components/sonos/test_statistics.py index 4a32b18f72b..4f28ec31412 100644 --- a/tests/components/sonos/test_statistics.py +++ b/tests/components/sonos/test_statistics.py @@ -1,4 +1,5 @@ """Tests for the Sonos statistics.""" + from homeassistant.components.sonos.const import DATA_SONOS from homeassistant.core import HomeAssistant diff --git a/tests/components/sonos/test_switch.py b/tests/components/sonos/test_switch.py index 301c4a641ea..d8499c50bb1 100644 --- a/tests/components/sonos/test_switch.py +++ b/tests/components/sonos/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Sonos Alarm switch platform.""" + from copy import copy from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/soundtouch/test_config_flow.py b/tests/components/soundtouch/test_config_flow.py index 896202355ac..bc7de5b7fda 100644 --- a/tests/components/soundtouch/test_config_flow.py +++ b/tests/components/soundtouch/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index 0bae58a1c00..94e6965a571 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -1,4 +1,5 @@ """Test the SoundTouch component.""" + from datetime import timedelta from typing import Any diff --git a/tests/components/spaceapi/test_init.py b/tests/components/spaceapi/test_init.py index 0ab91d48fcc..14b4c9177f9 100644 --- a/tests/components/spaceapi/test_init.py +++ b/tests/components/spaceapi/test_init.py @@ -1,4 +1,5 @@ """The tests for the Home Assistant SpaceAPI component.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/spc/test_init.py b/tests/components/spc/test_init.py index 1972b7af5c8..92c3282dd23 100644 --- a/tests/components/spc/test_init.py +++ b/tests/components/spc/test_init.py @@ -1,4 +1,5 @@ """Tests for Vanderbilt SPC component.""" + from unittest.mock import Mock, PropertyMock, patch from homeassistant.bootstrap import async_setup_component diff --git a/tests/components/speedtestdotnet/conftest.py b/tests/components/speedtestdotnet/conftest.py index 0dab08eddef..a48e7a878ff 100644 --- a/tests/components/speedtestdotnet/conftest.py +++ b/tests/components/speedtestdotnet/conftest.py @@ -1,4 +1,5 @@ """Conftest for speedtestdotnet.""" + from unittest.mock import patch import pytest diff --git a/tests/components/speedtestdotnet/test_config_flow.py b/tests/components/speedtestdotnet/test_config_flow.py index 00269c55ec3..f412c71a6ed 100644 --- a/tests/components/speedtestdotnet/test_config_flow.py +++ b/tests/components/speedtestdotnet/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for SpeedTest config flow.""" + from unittest.mock import MagicMock from homeassistant import config_entries diff --git a/tests/components/speedtestdotnet/test_sensor.py b/tests/components/speedtestdotnet/test_sensor.py index d15e9fb92f4..e529d46b537 100644 --- a/tests/components/speedtestdotnet/test_sensor.py +++ b/tests/components/speedtestdotnet/test_sensor.py @@ -1,4 +1,5 @@ """Tests for SpeedTest sensors.""" + from unittest.mock import MagicMock from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN diff --git a/tests/components/spider/test_config_flow.py b/tests/components/spider/test_config_flow.py index d8055538faa..e0a3e16e350 100644 --- a/tests/components/spider/test_config_flow.py +++ b/tests/components/spider/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Spider config flow.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index 7940964d68f..9a36942aafd 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Spotify config flow.""" + from http import HTTPStatus from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/sql/test_config_flow.py b/tests/components/sql/test_config_flow.py index 43608d0d32a..f1dec1871ed 100644 --- a/tests/components/sql/test_config_flow.py +++ b/tests/components/sql/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SQL config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/sql/test_init.py b/tests/components/sql/test_init.py index 2ae6010e0c5..cf5721f52f6 100644 --- a/tests/components/sql/test_init.py +++ b/tests/components/sql/test_init.py @@ -1,4 +1,5 @@ """Test for SQL component Init.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index dd0bd06c008..701c2c363ef 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -1,4 +1,5 @@ """The test for the sql sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sql/test_util.py b/tests/components/sql/test_util.py index 5211a47c4d4..004b511a2f0 100644 --- a/tests/components/sql/test_util.py +++ b/tests/components/sql/test_util.py @@ -1,4 +1,5 @@ """Test the sql utils.""" + from homeassistant.components.recorder import Recorder, get_instance from homeassistant.components.sql.util import resolve_db_url from homeassistant.core import HomeAssistant diff --git a/tests/components/squeezebox/test_config_flow.py b/tests/components/squeezebox/test_config_flow.py index 04d30cdd331..af100e28232 100644 --- a/tests/components/squeezebox/test_config_flow.py +++ b/tests/components/squeezebox/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Logitech Squeezebox config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/srp_energy/conftest.py b/tests/components/srp_energy/conftest.py index e3597081d77..c2cc6d5e1a4 100644 --- a/tests/components/srp_energy/conftest.py +++ b/tests/components/srp_energy/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Srp Energy integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/srp_energy/test_config_flow.py b/tests/components/srp_energy/test_config_flow.py index 572b67259f1..8d4904bf00d 100644 --- a/tests/components/srp_energy/test_config_flow.py +++ b/tests/components/srp_energy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SRP Energy config flow.""" + from unittest.mock import MagicMock, patch from homeassistant.components.srp_energy.const import CONF_IS_TOU, DOMAIN diff --git a/tests/components/srp_energy/test_init.py b/tests/components/srp_energy/test_init.py index a60dd09ea11..e2411fd4688 100644 --- a/tests/components/srp_energy/test_init.py +++ b/tests/components/srp_energy/test_init.py @@ -1,4 +1,5 @@ """Tests for Srp Energy component Init.""" + from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/ssdp/conftest.py b/tests/components/ssdp/conftest.py index 7b6d67895e5..157e6d0daa9 100644 --- a/tests/components/ssdp/conftest.py +++ b/tests/components/ssdp/conftest.py @@ -1,4 +1,5 @@ """Configuration for SSDP tests.""" + from unittest.mock import AsyncMock, patch from async_upnp_client.server import UpnpServer diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 324136c011b..e88214f01f5 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -1,4 +1,5 @@ """Test the SSDP integration.""" + from datetime import datetime from ipaddress import IPv4Address from unittest.mock import ANY, AsyncMock, patch diff --git a/tests/components/starlink/test_config_flow.py b/tests/components/starlink/test_config_flow.py index 3bb3f286638..5b0e122ad5d 100644 --- a/tests/components/starlink/test_config_flow.py +++ b/tests/components/starlink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Starlink config flow.""" + from homeassistant import config_entries, data_entry_flow from homeassistant.components.starlink.const import DOMAIN from homeassistant.config_entries import SOURCE_USER diff --git a/tests/components/starlink/test_diagnostics.py b/tests/components/starlink/test_diagnostics.py index 231b58a2d5e..2c11c19d4c2 100644 --- a/tests/components/starlink/test_diagnostics.py +++ b/tests/components/starlink/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for Starlink diagnostics.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.components.starlink.const import DOMAIN diff --git a/tests/components/starlink/test_init.py b/tests/components/starlink/test_init.py index 94a8a2a341b..53d58a852cd 100644 --- a/tests/components/starlink/test_init.py +++ b/tests/components/starlink/test_init.py @@ -1,4 +1,5 @@ """Tests Starlink integration init/unload.""" + from homeassistant.components.starlink.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_IP_ADDRESS diff --git a/tests/components/startca/test_sensor.py b/tests/components/startca/test_sensor.py index 7b691410907..b0d43af1cae 100644 --- a/tests/components/startca/test_sensor.py +++ b/tests/components/startca/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Start.ca sensor platform.""" + from http import HTTPStatus from homeassistant.bootstrap import async_setup_component diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index 7f3c9881751..c0420e93f70 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -1,4 +1,5 @@ """The test for the statistics sensor platform.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/tests/components/statsd/test_init.py b/tests/components/statsd/test_init.py index 1b48b6195e5..e24909e3f53 100644 --- a/tests/components/statsd/test_init.py +++ b/tests/components/statsd/test_init.py @@ -1,4 +1,5 @@ """The tests for the StatsD feeder.""" + from unittest import mock from unittest.mock import patch diff --git a/tests/components/steam_online/test_config_flow.py b/tests/components/steam_online/test_config_flow.py index a62adb18776..cbe02e1c3b6 100644 --- a/tests/components/steam_online/test_config_flow.py +++ b/tests/components/steam_online/test_config_flow.py @@ -1,4 +1,5 @@ """Test Steam config flow.""" + from unittest.mock import patch import steam diff --git a/tests/components/steamist/test_config_flow.py b/tests/components/steamist/test_config_flow.py index 9664eb7323f..34dc92495a9 100644 --- a/tests/components/steamist/test_config_flow.py +++ b/tests/components/steamist/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Steamist config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/steamist/test_init.py b/tests/components/steamist/test_init.py index 3c4b0084807..911ac790206 100644 --- a/tests/components/steamist/test_init.py +++ b/tests/components/steamist/test_init.py @@ -1,4 +1,5 @@ """Tests for the steamist component.""" + from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/steamist/test_sensor.py b/tests/components/steamist/test_sensor.py index 30dfc419f91..79592f9fc85 100644 --- a/tests/components/steamist/test_sensor.py +++ b/tests/components/steamist/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the steamist sensos.""" + from __future__ import annotations from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, UnitOfTemperature, UnitOfTime diff --git a/tests/components/steamist/test_switch.py b/tests/components/steamist/test_switch.py index 47a9cbf6708..a20bebc4052 100644 --- a/tests/components/steamist/test_switch.py +++ b/tests/components/steamist/test_switch.py @@ -1,4 +1,5 @@ """Tests for the steamist switch.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/stookalert/test_config_flow.py b/tests/components/stookalert/test_config_flow.py index 0014f4e5ad5..9830022203a 100644 --- a/tests/components/stookalert/test_config_flow.py +++ b/tests/components/stookalert/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Stookalert config flow.""" + from unittest.mock import patch from homeassistant.components.stookalert.const import CONF_PROVINCE, DOMAIN diff --git a/tests/components/stookwijzer/test_config_flow.py b/tests/components/stookwijzer/test_config_flow.py index 90786659254..590c93bb3c1 100644 --- a/tests/components/stookwijzer/test_config_flow.py +++ b/tests/components/stookwijzer/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Stookwijzer config flow.""" + from unittest.mock import patch from homeassistant.components.stookwijzer.const import DOMAIN diff --git a/tests/components/stream/common.py b/tests/components/stream/common.py index ae4a4fc2d9d..ab42141c667 100644 --- a/tests/components/stream/common.py +++ b/tests/components/stream/common.py @@ -1,4 +1,5 @@ """Collection of test helpers.""" + from fractions import Fraction import functools from functools import partial diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index 1a7ea3ca7e6..dd8b99f2af0 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -9,6 +9,7 @@ nothing for the test to verify. The solution is the WorkerSync class that allows the tests to pause the worker thread before finalizing the stream so that it can inspect the output. """ + from __future__ import annotations import asyncio diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index c5dfa1fc7ed..8390a1ac049 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -1,4 +1,5 @@ """The tests for hls streams.""" + from datetime import timedelta from http import HTTPStatus import logging diff --git a/tests/components/streamlabswater/conftest.py b/tests/components/streamlabswater/conftest.py index 64fbed63520..c303c1b7ef0 100644 --- a/tests/components/streamlabswater/conftest.py +++ b/tests/components/streamlabswater/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the StreamLabs tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/streamlabswater/test_binary_sensor.py b/tests/components/streamlabswater/test_binary_sensor.py index 4f533d91b55..7c9351c5e69 100644 --- a/tests/components/streamlabswater/test_binary_sensor.py +++ b/tests/components/streamlabswater/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Streamlabs Water binary sensor platform.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/streamlabswater/test_config_flow.py b/tests/components/streamlabswater/test_config_flow.py index 68f671d3b8c..4efe80b31e5 100644 --- a/tests/components/streamlabswater/test_config_flow.py +++ b/tests/components/streamlabswater/test_config_flow.py @@ -1,4 +1,5 @@ """Test the StreamLabs config flow.""" + from unittest.mock import AsyncMock, patch from homeassistant import config_entries diff --git a/tests/components/streamlabswater/test_sensor.py b/tests/components/streamlabswater/test_sensor.py index a78d4129abb..f27b61d724b 100644 --- a/tests/components/streamlabswater/test_sensor.py +++ b/tests/components/streamlabswater/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Streamlabs Water sensor platform.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/stt/common.py b/tests/components/stt/common.py index 79b58531b54..e6c36c5b350 100644 --- a/tests/components/stt/common.py +++ b/tests/components/stt/common.py @@ -1,4 +1,5 @@ """Provide common test tools for STT.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/tests/components/stt/test_init.py b/tests/components/stt/test_init.py index 9764451c5d5..4230b7bf33d 100644 --- a/tests/components/stt/test_init.py +++ b/tests/components/stt/test_init.py @@ -1,4 +1,5 @@ """Test STT component setup.""" + from collections.abc import AsyncIterable, Generator from http import HTTPStatus from pathlib import Path diff --git a/tests/components/stt/test_legacy.py b/tests/components/stt/test_legacy.py index 7176b866b00..04068b012f1 100644 --- a/tests/components/stt/test_legacy.py +++ b/tests/components/stt/test_legacy.py @@ -1,4 +1,5 @@ """Test the legacy stt setup.""" + from __future__ import annotations from pathlib import Path diff --git a/tests/components/subaru/conftest.py b/tests/components/subaru/conftest.py index 4927525d896..3f01ad2da18 100644 --- a/tests/components/subaru/conftest.py +++ b/tests/components/subaru/conftest.py @@ -1,4 +1,5 @@ """Common functions needed to setup tests for Subaru component.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/subaru/test_config_flow.py b/tests/components/subaru/test_config_flow.py index 7e892d2c99a..919c59c6d1b 100644 --- a/tests/components/subaru/test_config_flow.py +++ b/tests/components/subaru/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Subaru component config flow.""" + from copy import deepcopy from unittest import mock from unittest.mock import PropertyMock, patch diff --git a/tests/components/subaru/test_device_tracker.py b/tests/components/subaru/test_device_tracker.py index 616d868016e..b8a970007ab 100644 --- a/tests/components/subaru/test_device_tracker.py +++ b/tests/components/subaru/test_device_tracker.py @@ -1,4 +1,5 @@ """Test Subaru device tracker.""" + from copy import deepcopy from unittest.mock import patch diff --git a/tests/components/subaru/test_init.py b/tests/components/subaru/test_init.py index e82d7a1d72c..da5a754bedc 100644 --- a/tests/components/subaru/test_init.py +++ b/tests/components/subaru/test_init.py @@ -1,4 +1,5 @@ """Test Subaru component setup and updates.""" + from unittest.mock import patch from subarulink import InvalidCredentials, SubaruException diff --git a/tests/components/subaru/test_lock.py b/tests/components/subaru/test_lock.py index 7dab83a4c06..e90a0278ee5 100644 --- a/tests/components/subaru/test_lock.py +++ b/tests/components/subaru/test_lock.py @@ -1,4 +1,5 @@ """Test Subaru locks.""" + from unittest.mock import patch import pytest diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py index fd03ed3044b..faf57e4a5cf 100644 --- a/tests/components/subaru/test_sensor.py +++ b/tests/components/subaru/test_sensor.py @@ -1,4 +1,5 @@ """Test Subaru sensors.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/suez_water/conftest.py b/tests/components/suez_water/conftest.py index 8a67cfe97d7..6c124bec30e 100644 --- a/tests/components/suez_water/conftest.py +++ b/tests/components/suez_water/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Suez Water tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/suez_water/test_config_flow.py b/tests/components/suez_water/test_config_flow.py index c18b8a927e9..73d3c572498 100644 --- a/tests/components/suez_water/test_config_flow.py +++ b/tests/components/suez_water/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Suez Water config flow.""" + from unittest.mock import AsyncMock, patch from pysuez.client import PySuezError diff --git a/tests/components/sun/test_config_flow.py b/tests/components/sun/test_config_flow.py index 2bf577f82b8..ef13595ed59 100644 --- a/tests/components/sun/test_config_flow.py +++ b/tests/components/sun/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Sun config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/sun/test_init.py b/tests/components/sun/test_init.py index fef9bd4e049..b97596f74e8 100644 --- a/tests/components/sun/test_init.py +++ b/tests/components/sun/test_init.py @@ -1,4 +1,5 @@ """The tests for the Sun component.""" + from datetime import datetime, timedelta from unittest.mock import patch diff --git a/tests/components/sun/test_recorder.py b/tests/components/sun/test_recorder.py index e24f404a34b..3392884e20e 100644 --- a/tests/components/sun/test_recorder.py +++ b/tests/components/sun/test_recorder.py @@ -1,4 +1,5 @@ """The tests for sun recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/sun/test_sensor.py b/tests/components/sun/test_sensor.py index b1fb0d2facd..13de0dffbdd 100644 --- a/tests/components/sun/test_sensor.py +++ b/tests/components/sun/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Sun sensor platform.""" + from datetime import datetime, timedelta from astral import LocationInfo diff --git a/tests/components/sun/test_trigger.py b/tests/components/sun/test_trigger.py index 9d8f5d82a51..f7bdb5eb17b 100644 --- a/tests/components/sun/test_trigger.py +++ b/tests/components/sun/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the sun automation.""" + from datetime import datetime from freezegun import freeze_time diff --git a/tests/components/sunweg/test_config_flow.py b/tests/components/sunweg/test_config_flow.py index 1298d7e93fb..e45e671f752 100644 --- a/tests/components/sunweg/test_config_flow.py +++ b/tests/components/sunweg/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Sun WEG server config flow.""" + from unittest.mock import patch from sunweg.api import APIHelper diff --git a/tests/components/surepetcare/conftest.py b/tests/components/surepetcare/conftest.py index 79c1b88d99b..9ae1bfe310a 100644 --- a/tests/components/surepetcare/conftest.py +++ b/tests/components/surepetcare/conftest.py @@ -1,4 +1,5 @@ """Define fixtures available for all tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/surepetcare/test_binary_sensor.py b/tests/components/surepetcare/test_binary_sensor.py index 9f4018b4b65..106cf2f9155 100644 --- a/tests/components/surepetcare/test_binary_sensor.py +++ b/tests/components/surepetcare/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Sure Petcare binary sensor platform.""" + from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/surepetcare/test_config_flow.py b/tests/components/surepetcare/test_config_flow.py index e3521ef3d25..67ee5d81247 100644 --- a/tests/components/surepetcare/test_config_flow.py +++ b/tests/components/surepetcare/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Sure Petcare config flow.""" + from unittest.mock import NonCallableMagicMock, patch from surepy.exceptions import SurePetcareAuthenticationError, SurePetcareError diff --git a/tests/components/surepetcare/test_sensor.py b/tests/components/surepetcare/test_sensor.py index c0491908ca0..f543cdb9d35 100644 --- a/tests/components/surepetcare/test_sensor.py +++ b/tests/components/surepetcare/test_sensor.py @@ -1,4 +1,5 @@ """Test the surepetcare sensor platform.""" + from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/swiss_public_transport/conftest.py b/tests/components/swiss_public_transport/conftest.py index d84446db086..d01fba0f9d0 100644 --- a/tests/components/swiss_public_transport/conftest.py +++ b/tests/components/swiss_public_transport/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the swiss_public_transport tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/swiss_public_transport/test_config_flow.py b/tests/components/swiss_public_transport/test_config_flow.py index 5870f6f0555..9400423ff98 100644 --- a/tests/components/swiss_public_transport/test_config_flow.py +++ b/tests/components/swiss_public_transport/test_config_flow.py @@ -1,4 +1,5 @@ """Test the swiss_public_transport config flow.""" + from unittest.mock import AsyncMock, patch from opendata_transport.exceptions import ( diff --git a/tests/components/swiss_public_transport/test_init.py b/tests/components/swiss_public_transport/test_init.py index 2c8e12e04bf..e1b27cf5fe1 100644 --- a/tests/components/swiss_public_transport/test_init.py +++ b/tests/components/swiss_public_transport/test_init.py @@ -1,4 +1,5 @@ """Test the swiss_public_transport config flow.""" + from unittest.mock import AsyncMock, patch from homeassistant.components.swiss_public_transport.const import ( diff --git a/tests/components/switch/common.py b/tests/components/switch/common.py index 1123b1de6c1..60c79fdf6a8 100644 --- a/tests/components/switch/common.py +++ b/tests/components/switch/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.switch import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/switch/conftest.py b/tests/components/switch/conftest.py index 11f1563b723..c526ef4c4fe 100644 --- a/tests/components/switch/conftest.py +++ b/tests/components/switch/conftest.py @@ -1,2 +1,3 @@ """switch conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index c9521930a73..7127e840aeb 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -1,4 +1,5 @@ """The test for switch device automation.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 03f7e8fbb8e..0e6eff9bd27 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -1,4 +1,5 @@ """The test for switch device automation.""" + from datetime import timedelta import pytest diff --git a/tests/components/switch/test_significant_change.py b/tests/components/switch/test_significant_change.py index 296bb24cd30..76f22922932 100644 --- a/tests/components/switch/test_significant_change.py +++ b/tests/components/switch/test_significant_change.py @@ -1,4 +1,5 @@ """Test the sensor significant change platform.""" + from homeassistant.components.switch.significant_change import ( async_check_significant_change, ) diff --git a/tests/components/switch_as_x/conftest.py b/tests/components/switch_as_x/conftest.py index d324f7a0c54..82827924070 100644 --- a/tests/components/switch_as_x/conftest.py +++ b/tests/components/switch_as_x/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Switch as X integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/switch_as_x/test_config_flow.py b/tests/components/switch_as_x/test_config_flow.py index 09661b0619c..7603e0d1235 100644 --- a/tests/components/switch_as_x/test_config_flow.py +++ b/tests/components/switch_as_x/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Switch as X config flow.""" + from __future__ import annotations from unittest.mock import AsyncMock diff --git a/tests/components/switch_as_x/test_fan.py b/tests/components/switch_as_x/test_fan.py index c459831b3ad..fd4296bd616 100644 --- a/tests/components/switch_as_x/test_fan.py +++ b/tests/components/switch_as_x/test_fan.py @@ -1,4 +1,5 @@ """Tests for the Switch as X Fan platform.""" + from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler diff --git a/tests/components/switch_as_x/test_init.py b/tests/components/switch_as_x/test_init.py index 2b0a67f3984..3fe6ede151f 100644 --- a/tests/components/switch_as_x/test_init.py +++ b/tests/components/switch_as_x/test_init.py @@ -1,4 +1,5 @@ """Tests for the Switch as X.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/switch_as_x/test_light.py b/tests/components/switch_as_x/test_light.py index 5bdec990fd4..5e48b7db965 100644 --- a/tests/components/switch_as_x/test_light.py +++ b/tests/components/switch_as_x/test_light.py @@ -1,4 +1,5 @@ """Tests for the Switch as X Light platform.""" + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_MODE, diff --git a/tests/components/switch_as_x/test_lock.py b/tests/components/switch_as_x/test_lock.py index bdf1b754c5a..f7d61cf6895 100644 --- a/tests/components/switch_as_x/test_lock.py +++ b/tests/components/switch_as_x/test_lock.py @@ -1,4 +1,5 @@ """Tests for the Switch as X Lock platform.""" + from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler diff --git a/tests/components/switch_as_x/test_siren.py b/tests/components/switch_as_x/test_siren.py index 581aa74daff..83daa862830 100644 --- a/tests/components/switch_as_x/test_siren.py +++ b/tests/components/switch_as_x/test_siren.py @@ -1,4 +1,5 @@ """Tests for the Switch as X Siren platform.""" + from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler diff --git a/tests/components/switch_as_x/test_valve.py b/tests/components/switch_as_x/test_valve.py index b76da012bde..854f693404f 100644 --- a/tests/components/switch_as_x/test_valve.py +++ b/tests/components/switch_as_x/test_valve.py @@ -1,4 +1,5 @@ """Tests for the Switch as X Valve platform.""" + from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch_as_x.config_flow import SwitchAsXConfigFlowHandler from homeassistant.components.switch_as_x.const import ( diff --git a/tests/components/switchbot/test_config_flow.py b/tests/components/switchbot/test_config_flow.py index 85174658279..350957db0dd 100644 --- a/tests/components/switchbot/test_config_flow.py +++ b/tests/components/switchbot/test_config_flow.py @@ -1,4 +1,5 @@ """Test the switchbot config flow.""" + from unittest.mock import patch from switchbot import SwitchbotAccountConnectionError, SwitchbotAuthenticationError diff --git a/tests/components/switchbot_cloud/conftest.py b/tests/components/switchbot_cloud/conftest.py index b96d7638797..bfaea2c5a31 100644 --- a/tests/components/switchbot_cloud/conftest.py +++ b/tests/components/switchbot_cloud/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the SwitchBot via API tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/switchbot_cloud/test_config_flow.py b/tests/components/switchbot_cloud/test_config_flow.py index 6fdf8fecdb7..47758d50582 100644 --- a/tests/components/switchbot_cloud/test_config_flow.py +++ b/tests/components/switchbot_cloud/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SwitchBot via API config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/switcher_kis/conftest.py b/tests/components/switcher_kis/conftest.py index 7fff1c476fb..543f6cad008 100644 --- a/tests/components/switcher_kis/conftest.py +++ b/tests/components/switcher_kis/conftest.py @@ -1,4 +1,5 @@ """Common fixtures and objects for the Switcher integration tests.""" + from unittest.mock import AsyncMock, Mock, patch import pytest diff --git a/tests/components/switcher_kis/test_button.py b/tests/components/switcher_kis/test_button.py index d752acca7e5..c1350c0fec2 100644 --- a/tests/components/switcher_kis/test_button.py +++ b/tests/components/switcher_kis/test_button.py @@ -1,4 +1,5 @@ """Tests for Switcher button platform.""" + from unittest.mock import ANY, patch from aioswitcher.api import DeviceState, SwitcherBaseResponse, ThermostatSwing diff --git a/tests/components/switcher_kis/test_climate.py b/tests/components/switcher_kis/test_climate.py index 1919261109e..759f7f1bd98 100644 --- a/tests/components/switcher_kis/test_climate.py +++ b/tests/components/switcher_kis/test_climate.py @@ -1,4 +1,5 @@ """Test the Switcher climate platform.""" + from unittest.mock import ANY, patch from aioswitcher.api import SwitcherBaseResponse diff --git a/tests/components/switcher_kis/test_config_flow.py b/tests/components/switcher_kis/test_config_flow.py index e5859a095ca..e03c8eb645f 100644 --- a/tests/components/switcher_kis/test_config_flow.py +++ b/tests/components/switcher_kis/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Switcher config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/switcher_kis/test_cover.py b/tests/components/switcher_kis/test_cover.py index 2de4497a51e..07f349d1a72 100644 --- a/tests/components/switcher_kis/test_cover.py +++ b/tests/components/switcher_kis/test_cover.py @@ -1,4 +1,5 @@ """Test the Switcher cover platform.""" + from unittest.mock import patch from aioswitcher.api import SwitcherBaseResponse diff --git a/tests/components/switcher_kis/test_init.py b/tests/components/switcher_kis/test_init.py index 04b35956c11..f0484ca2f67 100644 --- a/tests/components/switcher_kis/test_init.py +++ b/tests/components/switcher_kis/test_init.py @@ -1,4 +1,5 @@ """Test cases for the switcher_kis component.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/switcher_kis/test_services.py b/tests/components/switcher_kis/test_services.py index bb16cf2d427..039daec4c97 100644 --- a/tests/components/switcher_kis/test_services.py +++ b/tests/components/switcher_kis/test_services.py @@ -1,4 +1,5 @@ """Test the services for the Switcher integration.""" + from unittest.mock import patch from aioswitcher.api import Command diff --git a/tests/components/switcher_kis/test_switch.py b/tests/components/switcher_kis/test_switch.py index fb45a372d2a..058546ac2ae 100644 --- a/tests/components/switcher_kis/test_switch.py +++ b/tests/components/switcher_kis/test_switch.py @@ -1,4 +1,5 @@ """Test the Switcher switch platform.""" + from unittest.mock import patch from aioswitcher.api import Command, SwitcherBaseResponse diff --git a/tests/components/syncthing/test_config_flow.py b/tests/components/syncthing/test_config_flow.py index 6e631f9b233..c7b3c1a3498 100644 --- a/tests/components/syncthing/test_config_flow.py +++ b/tests/components/syncthing/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for syncthing config flow.""" + from unittest.mock import patch from aiosyncthing.exceptions import UnauthorizedError diff --git a/tests/components/synology_dsm/conftest.py b/tests/components/synology_dsm/conftest.py index 77ef1b61e9e..044a3738543 100644 --- a/tests/components/synology_dsm/conftest.py +++ b/tests/components/synology_dsm/conftest.py @@ -1,4 +1,5 @@ """Configure Synology DSM tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 4d4ba583169..d0fd3d37b13 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Synology DSM config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock, Mock, patch diff --git a/tests/components/synology_dsm/test_init.py b/tests/components/synology_dsm/test_init.py index 7e86b28f0ee..bc8086b8d38 100644 --- a/tests/components/synology_dsm/test_init.py +++ b/tests/components/synology_dsm/test_init.py @@ -1,4 +1,5 @@ """Tests for the Synology DSM component.""" + from unittest.mock import MagicMock, patch from synology_dsm.exceptions import SynologyDSMLoginInvalidException diff --git a/tests/components/system_bridge/test_config_flow.py b/tests/components/system_bridge/test_config_flow.py index e30d389cc1b..d9a30610d65 100644 --- a/tests/components/system_bridge/test_config_flow.py +++ b/tests/components/system_bridge/test_config_flow.py @@ -1,4 +1,5 @@ """Test the System Bridge config flow.""" + from unittest.mock import patch from systembridgeconnector.exceptions import ( diff --git a/tests/components/system_health/test_init.py b/tests/components/system_health/test_init.py index ceb1ec03fe3..0767d9c3ff1 100644 --- a/tests/components/system_health/test_init.py +++ b/tests/components/system_health/test_init.py @@ -1,4 +1,5 @@ """Tests for the system health component init.""" + from unittest.mock import AsyncMock, Mock, patch from aiohttp.client_exceptions import ClientError diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 1357d9e5e9e..133b48ea745 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -1,4 +1,5 @@ """Test system log component.""" + from __future__ import annotations import asyncio diff --git a/tests/components/systemmonitor/conftest.py b/tests/components/systemmonitor/conftest.py index b12c11e73e6..3994b1cdfba 100644 --- a/tests/components/systemmonitor/conftest.py +++ b/tests/components/systemmonitor/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the System Monitor integration.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/systemmonitor/test_binary_sensor.py b/tests/components/systemmonitor/test_binary_sensor.py index 650f89c7566..51c8fc87a3a 100644 --- a/tests/components/systemmonitor/test_binary_sensor.py +++ b/tests/components/systemmonitor/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test System Monitor binary sensor.""" + from datetime import timedelta from unittest.mock import Mock, patch diff --git a/tests/components/systemmonitor/test_config_flow.py b/tests/components/systemmonitor/test_config_flow.py index 2536f847b43..eb6f5778805 100644 --- a/tests/components/systemmonitor/test_config_flow.py +++ b/tests/components/systemmonitor/test_config_flow.py @@ -1,4 +1,5 @@ """Test the System Monitor config flow.""" + from __future__ import annotations from unittest.mock import AsyncMock diff --git a/tests/components/systemmonitor/test_diagnostics.py b/tests/components/systemmonitor/test_diagnostics.py index b50f1aa16b2..78128aad5f4 100644 --- a/tests/components/systemmonitor/test_diagnostics.py +++ b/tests/components/systemmonitor/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the System Monitor integration.""" + from unittest.mock import Mock from syrupy import SnapshotAssertion diff --git a/tests/components/systemmonitor/test_init.py b/tests/components/systemmonitor/test_init.py index 3cba655f6bf..2142eecb8b4 100644 --- a/tests/components/systemmonitor/test_init.py +++ b/tests/components/systemmonitor/test_init.py @@ -1,4 +1,5 @@ """Test for System Monitor init.""" + from __future__ import annotations from unittest.mock import Mock From c88b337600853728cc9f53006407a492ff92ea44 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:50:04 +0100 Subject: [PATCH 0544/1691] Add empty line after module docstring [tests f-k] (#112709) --- tests/components/faa_delays/test_config_flow.py | 1 + tests/components/facebook/test_notify.py | 1 + tests/components/fail2ban/test_sensor.py | 1 + tests/components/fan/common.py | 1 + tests/components/fan/test_device_trigger.py | 1 + tests/components/fan/test_recorder.py | 1 + tests/components/fastdotcom/test_config_flow.py | 1 + tests/components/fastdotcom/test_coordinator.py | 1 + tests/components/fastdotcom/test_init.py | 1 + tests/components/fastdotcom/test_sensor.py | 1 + tests/components/fastdotcom/test_service.py | 1 + tests/components/feedreader/test_init.py | 1 + tests/components/ffmpeg/test_binary_sensor.py | 1 + tests/components/ffmpeg/test_init.py | 1 + tests/components/fibaro/conftest.py | 1 + tests/components/fibaro/test_config_flow.py | 1 + tests/components/fibaro/test_scene.py | 1 + tests/components/file/test_sensor.py | 1 + tests/components/file_upload/conftest.py | 1 + tests/components/file_upload/test_init.py | 1 + tests/components/filesize/conftest.py | 1 + tests/components/filesize/test_config_flow.py | 1 + tests/components/filesize/test_init.py | 1 + tests/components/filter/test_sensor.py | 1 + tests/components/fireservicerota/test_config_flow.py | 1 + tests/components/firmata/test_config_flow.py | 1 + tests/components/fivem/test_config_flow.py | 1 + tests/components/fjaraskupan/conftest.py | 1 + tests/components/fjaraskupan/test_config_flow.py | 1 + tests/components/flexit_bacnet/conftest.py | 1 + tests/components/flexit_bacnet/test_binary_sensor.py | 1 + tests/components/flexit_bacnet/test_climate.py | 1 + tests/components/flexit_bacnet/test_init.py | 1 + tests/components/flexit_bacnet/test_number.py | 1 + tests/components/flexit_bacnet/test_sensor.py | 1 + tests/components/flexit_bacnet/test_switch.py | 1 + tests/components/flic/test_binary_sensor.py | 1 + tests/components/flick_electric/test_config_flow.py | 1 + tests/components/flipr/test_binary_sensor.py | 1 + tests/components/flipr/test_config_flow.py | 1 + tests/components/flipr/test_init.py | 1 + tests/components/flipr/test_sensor.py | 1 + tests/components/flo/conftest.py | 1 + tests/components/flo/test_binary_sensor.py | 1 + tests/components/flo/test_config_flow.py | 1 + tests/components/flo/test_device.py | 1 + tests/components/flo/test_init.py | 1 + tests/components/flo/test_sensor.py | 1 + tests/components/flo/test_switch.py | 1 + tests/components/flume/test_config_flow.py | 1 + tests/components/flux/test_switch.py | 1 + tests/components/flux_led/test_button.py | 1 + tests/components/flux_led/test_config_flow.py | 1 + tests/components/flux_led/test_diagnostics.py | 1 + tests/components/flux_led/test_init.py | 1 + tests/components/flux_led/test_light.py | 1 + tests/components/flux_led/test_select.py | 1 + tests/components/flux_led/test_sensor.py | 1 + tests/components/flux_led/test_switch.py | 1 + tests/components/foobot/test_sensor.py | 1 + tests/components/forecast_solar/test_config_flow.py | 1 + tests/components/forecast_solar/test_diagnostics.py | 1 + tests/components/forecast_solar/test_energy.py | 1 + tests/components/forecast_solar/test_init.py | 1 + tests/components/forecast_solar/test_sensor.py | 1 + tests/components/forked_daapd/test_browse_media.py | 1 + tests/components/forked_daapd/test_config_flow.py | 1 + tests/components/forked_daapd/test_media_player.py | 1 + tests/components/foscam/test_config_flow.py | 1 + tests/components/freebox/common.py | 1 + tests/components/freebox/test_alarm_control_panel.py | 1 + tests/components/freebox/test_binary_sensor.py | 1 + tests/components/freebox/test_button.py | 1 + tests/components/freebox/test_config_flow.py | 1 + tests/components/freebox/test_device_tracker.py | 1 + tests/components/freebox/test_init.py | 1 + tests/components/freebox/test_sensor.py | 1 + tests/components/freedompro/conftest.py | 1 + tests/components/freedompro/test_binary_sensor.py | 1 + tests/components/freedompro/test_climate.py | 1 + tests/components/freedompro/test_config_flow.py | 1 + tests/components/freedompro/test_cover.py | 1 + tests/components/freedompro/test_fan.py | 1 + tests/components/freedompro/test_light.py | 1 + tests/components/freedompro/test_lock.py | 1 + tests/components/freedompro/test_sensor.py | 1 + tests/components/freedompro/test_switch.py | 1 + tests/components/fritz/const.py | 1 + tests/components/fritz/test_button.py | 1 + tests/components/fritz/test_diagnostics.py | 1 + tests/components/fritz/test_image.py | 1 + tests/components/fritz/test_init.py | 1 + tests/components/fritz/test_sensor.py | 1 + tests/components/fritz/test_switch.py | 1 + tests/components/fritzbox/conftest.py | 1 + tests/components/fritzbox/const.py | 1 + tests/components/fritzbox/test_binary_sensor.py | 1 + tests/components/fritzbox/test_button.py | 1 + tests/components/fritzbox/test_climate.py | 1 + tests/components/fritzbox/test_cover.py | 1 + tests/components/fritzbox/test_diagnostics.py | 1 + tests/components/fritzbox/test_init.py | 1 + tests/components/fritzbox/test_light.py | 1 + tests/components/fritzbox/test_sensor.py | 1 + tests/components/fritzbox/test_switch.py | 1 + tests/components/fritzbox_callmonitor/test_config_flow.py | 1 + tests/components/fronius/test_config_flow.py | 1 + tests/components/fronius/test_coordinator.py | 1 + tests/components/fronius/test_init.py | 1 + tests/components/fronius/test_sensor.py | 1 + tests/components/frontend/test_init.py | 1 + tests/components/frontend/test_storage.py | 1 + tests/components/frontier_silicon/conftest.py | 1 + tests/components/frontier_silicon/test_config_flow.py | 1 + tests/components/fully_kiosk/conftest.py | 1 + tests/components/fully_kiosk/test_binary_sensor.py | 1 + tests/components/fully_kiosk/test_button.py | 1 + tests/components/fully_kiosk/test_diagnostics.py | 1 + tests/components/fully_kiosk/test_media_player.py | 1 + tests/components/fully_kiosk/test_number.py | 1 + tests/components/fully_kiosk/test_sensor.py | 1 + tests/components/fully_kiosk/test_services.py | 1 + tests/components/fully_kiosk/test_switch.py | 1 + tests/components/garages_amsterdam/test_config_flow.py | 1 + tests/components/gardena_bluetooth/conftest.py | 1 + tests/components/gardena_bluetooth/test_config_flow.py | 1 + tests/components/gardena_bluetooth/test_sensor.py | 1 + tests/components/gdacs/test_config_flow.py | 1 + tests/components/gdacs/test_init.py | 1 + tests/components/gdacs/test_sensor.py | 1 + tests/components/geo_json_events/conftest.py | 1 + tests/components/geo_json_events/test_geo_location.py | 1 + tests/components/geo_json_events/test_init.py | 1 + tests/components/geo_rss_events/test_sensor.py | 1 + tests/components/geocaching/conftest.py | 1 + tests/components/geocaching/test_config_flow.py | 1 + tests/components/geofency/test_init.py | 1 + tests/components/geonetnz_quakes/test_config_flow.py | 1 + tests/components/geonetnz_quakes/test_init.py | 1 + tests/components/geonetnz_volcano/test_config_flow.py | 1 + tests/components/geonetnz_volcano/test_init.py | 1 + tests/components/geonetnz_volcano/test_sensor.py | 1 + tests/components/gios/test_sensor.py | 1 + tests/components/github/common.py | 1 + tests/components/github/conftest.py | 1 + tests/components/github/test_config_flow.py | 1 + tests/components/glances/conftest.py | 1 + tests/components/glances/test_config_flow.py | 1 + tests/components/glances/test_init.py | 1 + tests/components/glances/test_sensor.py | 1 + tests/components/goalzero/test_binary_sensor.py | 1 + tests/components/goalzero/test_config_flow.py | 1 + tests/components/goalzero/test_init.py | 1 + tests/components/goalzero/test_switch.py | 1 + tests/components/gogogate2/test_config_flow.py | 1 + tests/components/gogogate2/test_cover.py | 1 + tests/components/gogogate2/test_init.py | 1 + tests/components/gogogate2/test_sensor.py | 1 + tests/components/goodwe/conftest.py | 1 + tests/components/goodwe/test_config_flow.py | 1 + tests/components/goodwe/test_diagnostics.py | 1 + tests/components/google/conftest.py | 1 + tests/components/google/test_calendar.py | 1 + tests/components/google/test_diagnostics.py | 1 + tests/components/google/test_init.py | 1 + tests/components/google_assistant/test_button.py | 1 + tests/components/google_assistant/test_diagnostics.py | 1 + tests/components/google_assistant/test_google_assistant.py | 1 + tests/components/google_assistant/test_helpers.py | 1 + tests/components/google_assistant/test_http.py | 1 + tests/components/google_assistant/test_init.py | 1 + tests/components/google_assistant/test_logbook.py | 1 + tests/components/google_assistant/test_report_state.py | 1 + tests/components/google_assistant/test_trait.py | 1 + tests/components/google_assistant_sdk/conftest.py | 1 + tests/components/google_assistant_sdk/test_config_flow.py | 1 + tests/components/google_assistant_sdk/test_helpers.py | 1 + tests/components/google_assistant_sdk/test_init.py | 1 + tests/components/google_assistant_sdk/test_notify.py | 1 + tests/components/google_domains/test_init.py | 1 + tests/components/google_generative_ai_conversation/conftest.py | 1 + .../google_generative_ai_conversation/test_config_flow.py | 1 + tests/components/google_generative_ai_conversation/test_init.py | 1 + tests/components/google_mail/conftest.py | 1 + tests/components/google_mail/test_config_flow.py | 1 + tests/components/google_mail/test_notify.py | 1 + tests/components/google_mail/test_sensor.py | 1 + tests/components/google_mail/test_services.py | 1 + tests/components/google_pubsub/test_init.py | 1 + tests/components/google_sheets/test_config_flow.py | 1 + tests/components/google_tasks/test_init.py | 1 + tests/components/google_translate/conftest.py | 1 + tests/components/google_translate/test_config_flow.py | 1 + tests/components/google_translate/test_tts.py | 1 + tests/components/google_travel_time/conftest.py | 1 + tests/components/google_wifi/test_sensor.py | 1 + tests/components/govee_ble/test_config_flow.py | 1 + tests/components/govee_ble/test_sensor.py | 1 + tests/components/govee_light_local/conftest.py | 1 + tests/components/govee_light_local/test_config_flow.py | 1 + tests/components/gpsd/conftest.py | 1 + tests/components/gpsd/test_config_flow.py | 1 + tests/components/gpslogger/test_init.py | 1 + tests/components/gree/conftest.py | 1 + tests/components/gree/test_bridge.py | 1 + tests/components/gree/test_climate.py | 1 + tests/components/gree/test_config_flow.py | 1 + tests/components/gree/test_init.py | 1 + tests/components/gree/test_switch.py | 1 + tests/components/greeneye_monitor/common.py | 1 + tests/components/greeneye_monitor/conftest.py | 1 + tests/components/greeneye_monitor/test_sensor.py | 1 + tests/components/group/common.py | 1 + tests/components/group/test_binary_sensor.py | 1 + tests/components/group/test_config_flow.py | 1 + tests/components/group/test_init.py | 1 + tests/components/group/test_notify.py | 1 + tests/components/group/test_recorder.py | 1 + tests/components/group/test_reproduce_state.py | 1 + tests/components/growatt_server/test_config_flow.py | 1 + tests/components/guardian/conftest.py | 1 + tests/components/guardian/test_config_flow.py | 1 + tests/components/guardian/test_diagnostics.py | 1 + tests/components/habitica/test_config_flow.py | 1 + tests/components/habitica/test_init.py | 1 + tests/components/hardkernel/test_config_flow.py | 1 + tests/components/hardkernel/test_hardware.py | 1 + tests/components/hardkernel/test_init.py | 1 + tests/components/hardware/test_websocket_api.py | 1 + tests/components/harmony/conftest.py | 1 + tests/components/harmony/test_config_flow.py | 1 + tests/components/harmony/test_init.py | 1 + tests/components/harmony/test_remote.py | 1 + tests/components/harmony/test_select.py | 1 + tests/components/harmony/test_switch.py | 1 + tests/components/hassio/test_addon_manager.py | 1 + tests/components/hassio/test_addon_panel.py | 1 + tests/components/hassio/test_auth.py | 1 + tests/components/hassio/test_config_flow.py | 1 + tests/components/hassio/test_discovery.py | 1 + tests/components/hassio/test_handler.py | 1 + tests/components/hassio/test_http.py | 1 + tests/components/hassio/test_ingress.py | 1 + tests/components/hassio/test_init.py | 1 + tests/components/hassio/test_issues.py | 1 + tests/components/hassio/test_sensor.py | 1 + tests/components/hassio/test_update.py | 1 + tests/components/hdmi_cec/conftest.py | 1 + tests/components/hdmi_cec/test_init.py | 1 + tests/components/hdmi_cec/test_media_player.py | 1 + tests/components/hdmi_cec/test_switch.py | 1 + tests/components/heos/conftest.py | 1 + tests/components/heos/test_config_flow.py | 1 + tests/components/heos/test_services.py | 1 + tests/components/here_travel_time/test_config_flow.py | 1 + tests/components/here_travel_time/test_sensor.py | 1 + tests/components/hisense_aehw4a1/test_init.py | 1 + tests/components/history/test_init.py | 1 + tests/components/history/test_init_db_schema_30.py | 1 + tests/components/history_stats/test_sensor.py | 1 + tests/components/hive/test_config_flow.py | 1 + tests/components/holiday/conftest.py | 1 + tests/components/holiday/test_calendar.py | 1 + tests/components/holiday/test_config_flow.py | 1 + tests/components/home_connect/test_config_flow.py | 1 + tests/components/homeassistant/test_scene.py | 1 + tests/components/homeassistant/triggers/test_homeassistant.py | 1 + tests/components/homeassistant/triggers/test_numeric_state.py | 1 + tests/components/homeassistant/triggers/test_state.py | 1 + tests/components/homeassistant/triggers/test_time.py | 1 + tests/components/homeassistant/triggers/test_time_pattern.py | 1 + tests/components/homeassistant_alerts/test_init.py | 1 + tests/components/homeassistant_green/test_config_flow.py | 1 + tests/components/homeassistant_green/test_hardware.py | 1 + tests/components/homeassistant_green/test_init.py | 1 + tests/components/homeassistant_hardware/conftest.py | 1 + .../homeassistant_hardware/test_silabs_multiprotocol_addon.py | 1 + tests/components/homeassistant_sky_connect/conftest.py | 1 + tests/components/homeassistant_sky_connect/test_config_flow.py | 1 + tests/components/homeassistant_sky_connect/test_hardware.py | 1 + tests/components/homeassistant_sky_connect/test_init.py | 1 + tests/components/homeassistant_yellow/conftest.py | 1 + tests/components/homeassistant_yellow/test_config_flow.py | 1 + tests/components/homeassistant_yellow/test_hardware.py | 1 + tests/components/homeassistant_yellow/test_init.py | 1 + tests/components/homekit/conftest.py | 1 + tests/components/homekit/test_accessories.py | 1 + tests/components/homekit/test_config_flow.py | 1 + tests/components/homekit/test_diagnostics.py | 1 + tests/components/homekit/test_get_accessories.py | 1 + tests/components/homekit/test_homekit.py | 1 + tests/components/homekit/test_iidmanager.py | 1 + tests/components/homekit/test_init.py | 1 + tests/components/homekit/test_type_covers.py | 1 + tests/components/homekit/test_type_fans.py | 1 + tests/components/homekit/test_type_humidifiers.py | 1 + tests/components/homekit/test_type_lights.py | 1 + tests/components/homekit/test_type_remote.py | 1 + tests/components/homekit/test_type_security_systems.py | 1 + tests/components/homekit/test_type_sensors.py | 1 + tests/components/homekit/test_type_switches.py | 1 + tests/components/homekit/test_type_thermostats.py | 1 + tests/components/homekit/test_type_triggers.py | 1 + tests/components/homekit/test_util.py | 1 + tests/components/homekit_controller/common.py | 1 + .../homekit_controller/specific_devices/test_anker_eufycam.py | 1 + .../homekit_controller/specific_devices/test_aqara_switch.py | 1 + .../homekit_controller/specific_devices/test_connectsense.py | 1 + .../homekit_controller/specific_devices/test_ecobee3.py | 1 + .../homekit_controller/specific_devices/test_hue_bridge.py | 1 + .../homekit_controller/specific_devices/test_koogeek_ls1.py | 1 + .../homekit_controller/specific_devices/test_koogeek_sw2.py | 1 + .../homekit_controller/specific_devices/test_netamo_doorbell.py | 1 + .../homekit_controller/specific_devices/test_vocolinc_vp3.py | 1 + tests/components/homekit_controller/test_alarm_control_panel.py | 1 + tests/components/homekit_controller/test_binary_sensor.py | 1 + tests/components/homekit_controller/test_button.py | 1 + tests/components/homekit_controller/test_climate.py | 1 + tests/components/homekit_controller/test_cover.py | 1 + tests/components/homekit_controller/test_device_trigger.py | 1 + tests/components/homekit_controller/test_event.py | 1 + tests/components/homekit_controller/test_fan.py | 1 + tests/components/homekit_controller/test_humidifier.py | 1 + tests/components/homekit_controller/test_init.py | 1 + tests/components/homekit_controller/test_light.py | 1 + tests/components/homekit_controller/test_lock.py | 1 + tests/components/homekit_controller/test_media_player.py | 1 + tests/components/homekit_controller/test_number.py | 1 + tests/components/homekit_controller/test_select.py | 1 + tests/components/homekit_controller/test_sensor.py | 1 + tests/components/homekit_controller/test_storage.py | 1 + tests/components/homekit_controller/test_switch.py | 1 + tests/components/homekit_controller/test_utils.py | 1 + tests/components/homematicip_cloud/conftest.py | 1 + tests/components/homematicip_cloud/test_alarm_control_panel.py | 1 + tests/components/homematicip_cloud/test_binary_sensor.py | 1 + tests/components/homematicip_cloud/test_config_flow.py | 1 + tests/components/homematicip_cloud/test_cover.py | 1 + tests/components/homematicip_cloud/test_device.py | 1 + tests/components/homematicip_cloud/test_hap.py | 1 + tests/components/homematicip_cloud/test_init.py | 1 + tests/components/homematicip_cloud/test_light.py | 1 + tests/components/homematicip_cloud/test_lock.py | 1 + tests/components/homematicip_cloud/test_sensor.py | 1 + tests/components/homematicip_cloud/test_switch.py | 1 + tests/components/homematicip_cloud/test_weather.py | 1 + tests/components/homewizard/conftest.py | 1 + tests/components/homewizard/test_button.py | 1 + tests/components/homewizard/test_config_flow.py | 1 + tests/components/homewizard/test_init.py | 1 + tests/components/homewizard/test_number.py | 1 + tests/components/homewizard/test_switch.py | 1 + tests/components/homeworks/conftest.py | 1 + tests/components/homeworks/test_config_flow.py | 1 + tests/components/honeywell/test_config_flow.py | 1 + tests/components/honeywell/test_diagnostics.py | 1 + tests/components/honeywell/test_init.py | 1 + tests/components/honeywell/test_sensor.py | 1 + tests/components/html5/test_notify.py | 1 + tests/components/http/test_auth.py | 1 + tests/components/http/test_ban.py | 1 + tests/components/http/test_cors.py | 1 + tests/components/http/test_data_validator.py | 1 + tests/components/http/test_forwarded.py | 1 + tests/components/http/test_headers.py | 1 + tests/components/http/test_request_context.py | 1 + tests/components/http/test_view.py | 1 + tests/components/huawei_lte/test_button.py | 1 + tests/components/huawei_lte/test_config_flow.py | 1 + tests/components/huawei_lte/test_select.py | 1 + tests/components/huawei_lte/test_switches.py | 1 + tests/components/hue/test_binary_sensor.py | 1 + tests/components/hue/test_config_flow.py | 1 + tests/components/hue/test_device_trigger_v1.py | 1 + tests/components/hue/test_device_trigger_v2.py | 1 + tests/components/hue/test_diagnostics.py | 1 + tests/components/hue/test_event.py | 1 + tests/components/hue/test_init.py | 1 + tests/components/hue/test_light_v1.py | 1 + tests/components/hue/test_light_v2.py | 1 + tests/components/hue/test_migration.py | 1 + tests/components/hue/test_scene.py | 1 + tests/components/hue/test_sensor_v1.py | 1 + tests/components/hue/test_sensor_v2.py | 1 + tests/components/hue/test_services.py | 1 + tests/components/hue/test_switch.py | 1 + tests/components/huisbaasje/test_config_flow.py | 1 + tests/components/huisbaasje/test_init.py | 1 + tests/components/huisbaasje/test_sensor.py | 1 + tests/components/humidifier/test_init.py | 1 + tests/components/humidifier/test_recorder.py | 1 + tests/components/hunterdouglas_powerview/test_config_flow.py | 1 + tests/components/hunterdouglas_powerview/test_scene.py | 1 + tests/components/husqvarna_automower/conftest.py | 1 + tests/components/husqvarna_automower/test_init.py | 1 + tests/components/husqvarna_automower/test_lawn_mower.py | 1 + tests/components/husqvarna_automower/test_sensor.py | 1 + tests/components/husqvarna_automower/test_switch.py | 1 + tests/components/huum/test_config_flow.py | 1 + tests/components/hyperion/conftest.py | 1 + tests/components/hyperion/test_camera.py | 1 + tests/components/hyperion/test_config_flow.py | 1 + tests/components/hyperion/test_light.py | 1 + tests/components/hyperion/test_switch.py | 1 + tests/components/ialarm/test_config_flow.py | 1 + tests/components/ialarm/test_init.py | 1 + tests/components/iaqualink/test_config_flow.py | 1 + tests/components/iaqualink/test_utils.py | 1 + tests/components/ibeacon/test_config_flow.py | 1 + tests/components/ibeacon/test_coordinator.py | 1 + tests/components/ibeacon/test_device_tracker.py | 1 + tests/components/ibeacon/test_sensor.py | 1 + tests/components/icloud/conftest.py | 1 + tests/components/icloud/const.py | 1 + tests/components/icloud/test_config_flow.py | 1 + tests/components/icloud/test_init.py | 1 + tests/components/idasen_desk/test_buttons.py | 1 + tests/components/idasen_desk/test_config_flow.py | 1 + tests/components/idasen_desk/test_cover.py | 1 + tests/components/idasen_desk/test_init.py | 1 + tests/components/idasen_desk/test_sensors.py | 1 + tests/components/ifttt/test_init.py | 1 + tests/components/image/conftest.py | 1 + tests/components/image/test_recorder.py | 1 + tests/components/image_processing/common.py | 1 + tests/components/image_processing/test_init.py | 1 + tests/components/imap/test_diagnostics.py | 1 + tests/components/improv_ble/test_config_flow.py | 1 + tests/components/influxdb/test_init.py | 1 + tests/components/influxdb/test_sensor.py | 1 + tests/components/inkbird/test_config_flow.py | 1 + tests/components/inkbird/test_sensor.py | 1 + tests/components/input_boolean/test_recorder.py | 1 + tests/components/input_boolean/test_reproduce_state.py | 1 + tests/components/input_button/test_recorder.py | 1 + tests/components/input_datetime/test_recorder.py | 1 + tests/components/input_number/test_init.py | 1 + tests/components/input_number/test_recorder.py | 1 + tests/components/input_select/test_init.py | 1 + tests/components/input_select/test_recorder.py | 1 + tests/components/input_text/test_init.py | 1 + tests/components/input_text/test_recorder.py | 1 + tests/components/insteon/const.py | 1 + tests/components/insteon/test_config_flow.py | 1 + tests/components/insteon/test_lock.py | 1 + tests/components/integration/test_config_flow.py | 1 + tests/components/integration/test_sensor.py | 1 + tests/components/intellifire/conftest.py | 1 + tests/components/intellifire/test_config_flow.py | 1 + tests/components/intent_script/test_init.py | 1 + tests/components/ios/test_init.py | 1 + tests/components/iotawatt/conftest.py | 1 + tests/components/iotawatt/test_config_flow.py | 1 + tests/components/iotawatt/test_sensor.py | 1 + tests/components/ipma/test_config_flow.py | 1 + tests/components/ipma/test_init.py | 1 + tests/components/ipp/conftest.py | 1 + tests/components/ipp/test_init.py | 1 + tests/components/ipp/test_sensor.py | 1 + tests/components/iqvia/test_config_flow.py | 1 + tests/components/iqvia/test_diagnostics.py | 1 + tests/components/islamic_prayer_times/conftest.py | 1 + tests/components/islamic_prayer_times/test_config_flow.py | 1 + tests/components/islamic_prayer_times/test_init.py | 1 + tests/components/islamic_prayer_times/test_sensor.py | 1 + tests/components/iss/test_config_flow.py | 1 + tests/components/izone/test_config_flow.py | 1 + tests/components/jellyfin/conftest.py | 1 + tests/components/jellyfin/test_config_flow.py | 1 + tests/components/jellyfin/test_diagnostics.py | 1 + tests/components/jellyfin/test_init.py | 1 + tests/components/jellyfin/test_media_player.py | 1 + tests/components/jellyfin/test_media_source.py | 1 + tests/components/jellyfin/test_sensor.py | 1 + tests/components/jewish_calendar/test_binary_sensor.py | 1 + tests/components/jewish_calendar/test_sensor.py | 1 + tests/components/juicenet/test_config_flow.py | 1 + tests/components/justnimbus/test_config_flow.py | 1 + tests/components/justnimbus/test_init.py | 1 + tests/components/kegtron/test_config_flow.py | 1 + tests/components/kegtron/test_sensor.py | 1 + tests/components/keymitt_ble/test_config_flow.py | 1 + tests/components/kira/test_remote.py | 1 + tests/components/kira/test_sensor.py | 1 + tests/components/kitchen_sink/test_config_flow.py | 1 + tests/components/kitchen_sink/test_image.py | 1 + tests/components/kitchen_sink/test_lawn_mower.py | 1 + tests/components/kitchen_sink/test_lock.py | 1 + tests/components/kitchen_sink/test_sensor.py | 1 + tests/components/kmtronic/conftest.py | 1 + tests/components/kmtronic/test_config_flow.py | 1 + tests/components/kmtronic/test_switch.py | 1 + tests/components/knx/conftest.py | 1 + tests/components/knx/test_binary_sensor.py | 1 + tests/components/knx/test_button.py | 1 + tests/components/knx/test_config_flow.py | 1 + tests/components/knx/test_date.py | 1 + tests/components/knx/test_datetime.py | 1 + tests/components/knx/test_expose.py | 1 + tests/components/knx/test_fan.py | 1 + tests/components/knx/test_init.py | 1 + tests/components/knx/test_interface_device.py | 1 + tests/components/knx/test_light.py | 1 + tests/components/knx/test_sensor.py | 1 + tests/components/knx/test_services.py | 1 + tests/components/knx/test_switch.py | 1 + tests/components/knx/test_telegrams.py | 1 + tests/components/knx/test_text.py | 1 + tests/components/knx/test_time.py | 1 + tests/components/knx/test_weather.py | 1 + tests/components/knx/test_websocket.py | 1 + tests/components/kodi/test_config_flow.py | 1 + tests/components/kodi/test_init.py | 1 + tests/components/kodi/util.py | 1 + tests/components/konnected/test_config_flow.py | 1 + tests/components/konnected/test_init.py | 1 + tests/components/konnected/test_panel.py | 1 + tests/components/kostal_plenticore/conftest.py | 1 + tests/components/kostal_plenticore/test_config_flow.py | 1 + tests/components/kostal_plenticore/test_diagnostics.py | 1 + tests/components/kostal_plenticore/test_number.py | 1 + tests/components/kostal_plenticore/test_select.py | 1 + tests/components/kraken/conftest.py | 1 + tests/components/kraken/test_config_flow.py | 1 + tests/components/kraken/test_init.py | 1 + tests/components/kraken/test_sensor.py | 1 + tests/components/kulersky/test_config_flow.py | 1 + tests/components/kulersky/test_light.py | 1 + 528 files changed, 528 insertions(+) diff --git a/tests/components/faa_delays/test_config_flow.py b/tests/components/faa_delays/test_config_flow.py index 5fb1b9cfcd2..d164eab918f 100644 --- a/tests/components/faa_delays/test_config_flow.py +++ b/tests/components/faa_delays/test_config_flow.py @@ -1,4 +1,5 @@ """Test the FAA Delays config flow.""" + from unittest.mock import patch from aiohttp import ClientConnectionError diff --git a/tests/components/facebook/test_notify.py b/tests/components/facebook/test_notify.py index 152a516943b..bbaa1f12516 100644 --- a/tests/components/facebook/test_notify.py +++ b/tests/components/facebook/test_notify.py @@ -1,4 +1,5 @@ """The test for the Facebook notify module.""" + from http import HTTPStatus import pytest diff --git a/tests/components/fail2ban/test_sensor.py b/tests/components/fail2ban/test_sensor.py index 5906ac947fe..713edb72444 100644 --- a/tests/components/fail2ban/test_sensor.py +++ b/tests/components/fail2ban/test_sensor.py @@ -1,4 +1,5 @@ """The tests for local file sensor platform.""" + from unittest.mock import Mock, mock_open, patch from homeassistant.components.fail2ban.sensor import ( diff --git a/tests/components/fan/common.py b/tests/components/fan/common.py index 58264d80817..40fab746e8d 100644 --- a/tests/components/fan/common.py +++ b/tests/components/fan/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.fan import ( ATTR_DIRECTION, ATTR_OSCILLATING, diff --git a/tests/components/fan/test_device_trigger.py b/tests/components/fan/test_device_trigger.py index 8ac5e79ba5b..cf0325f133d 100644 --- a/tests/components/fan/test_device_trigger.py +++ b/tests/components/fan/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Fan device triggers.""" + from datetime import timedelta import pytest diff --git a/tests/components/fan/test_recorder.py b/tests/components/fan/test_recorder.py index d75e1e18511..4f1499765b8 100644 --- a/tests/components/fan/test_recorder.py +++ b/tests/components/fan/test_recorder.py @@ -1,4 +1,5 @@ """The tests for fan recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/fastdotcom/test_config_flow.py b/tests/components/fastdotcom/test_config_flow.py index 17e75935dae..d2122f4fe61 100644 --- a/tests/components/fastdotcom/test_config_flow.py +++ b/tests/components/fastdotcom/test_config_flow.py @@ -1,4 +1,5 @@ """Test for the Fast.com config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/fastdotcom/test_coordinator.py b/tests/components/fastdotcom/test_coordinator.py index f51f0254714..5c8cb17c736 100644 --- a/tests/components/fastdotcom/test_coordinator.py +++ b/tests/components/fastdotcom/test_coordinator.py @@ -1,4 +1,5 @@ """Test the FastdotcomDataUpdateCoordindator.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/fastdotcom/test_init.py b/tests/components/fastdotcom/test_init.py index 547d574b25a..12e3902d874 100644 --- a/tests/components/fastdotcom/test_init.py +++ b/tests/components/fastdotcom/test_init.py @@ -1,4 +1,5 @@ """Test for Sensibo component Init.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/fastdotcom/test_sensor.py b/tests/components/fastdotcom/test_sensor.py index 47826bf35cf..8f5a81e5205 100644 --- a/tests/components/fastdotcom/test_sensor.py +++ b/tests/components/fastdotcom/test_sensor.py @@ -1,4 +1,5 @@ """Test the FastdotcomDataUpdateCoordindator.""" + from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/fastdotcom/test_service.py b/tests/components/fastdotcom/test_service.py index 2f919bc8a84..8747beb6245 100644 --- a/tests/components/fastdotcom/test_service.py +++ b/tests/components/fastdotcom/test_service.py @@ -1,4 +1,5 @@ """Test Fastdotcom service.""" + from unittest.mock import patch import pytest diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index cd906940931..25760da0028 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -1,4 +1,5 @@ """The tests for the feedreader component.""" + from collections.abc import Generator from datetime import datetime, timedelta import pickle diff --git a/tests/components/ffmpeg/test_binary_sensor.py b/tests/components/ffmpeg/test_binary_sensor.py index 6eec115d6f0..72d6fee5e7f 100644 --- a/tests/components/ffmpeg/test_binary_sensor.py +++ b/tests/components/ffmpeg/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for Home Assistant ffmpeg binary sensor.""" + from unittest.mock import AsyncMock, patch from homeassistant.const import EVENT_HOMEASSISTANT_START diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 452a8188596..5ead1bcf942 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -1,4 +1,5 @@ """The tests for Home Assistant ffmpeg.""" + from unittest.mock import AsyncMock, MagicMock, Mock, call, patch from homeassistant.components import ffmpeg diff --git a/tests/components/fibaro/conftest.py b/tests/components/fibaro/conftest.py index d86817814b1..345668c23bd 100644 --- a/tests/components/fibaro/conftest.py +++ b/tests/components/fibaro/conftest.py @@ -1,4 +1,5 @@ """Test helpers.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/fibaro/test_config_flow.py b/tests/components/fibaro/test_config_flow.py index 42d20f902c0..2c7d05b87a3 100644 --- a/tests/components/fibaro/test_config_flow.py +++ b/tests/components/fibaro/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Fibaro config flow.""" + from unittest.mock import Mock import pytest diff --git a/tests/components/fibaro/test_scene.py b/tests/components/fibaro/test_scene.py index e07face3ac0..667f8236a31 100644 --- a/tests/components/fibaro/test_scene.py +++ b/tests/components/fibaro/test_scene.py @@ -1,4 +1,5 @@ """Test the Fibaro scene platform.""" + from unittest.mock import Mock from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN diff --git a/tests/components/file/test_sensor.py b/tests/components/file/test_sensor.py index e01d90b8cc3..8acdc324209 100644 --- a/tests/components/file/test_sensor.py +++ b/tests/components/file/test_sensor.py @@ -1,4 +1,5 @@ """The tests for local file sensor platform.""" + from unittest.mock import Mock, patch from homeassistant.const import STATE_UNKNOWN diff --git a/tests/components/file_upload/conftest.py b/tests/components/file_upload/conftest.py index ab9965c1914..00423ad21e6 100644 --- a/tests/components/file_upload/conftest.py +++ b/tests/components/file_upload/conftest.py @@ -1,4 +1,5 @@ """Fixtures for FileUpload integration.""" + from io import StringIO import pytest diff --git a/tests/components/file_upload/test_init.py b/tests/components/file_upload/test_init.py index 920565cb68f..deff64ff073 100644 --- a/tests/components/file_upload/test_init.py +++ b/tests/components/file_upload/test_init.py @@ -1,4 +1,5 @@ """Test the File Upload integration.""" + from contextlib import contextmanager from pathlib import Path from random import getrandbits diff --git a/tests/components/filesize/conftest.py b/tests/components/filesize/conftest.py index ac36ab687f4..81aea2aee54 100644 --- a/tests/components/filesize/conftest.py +++ b/tests/components/filesize/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Filesize integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/filesize/test_config_flow.py b/tests/components/filesize/test_config_flow.py index 27dec438168..38209a3014e 100644 --- a/tests/components/filesize/test_config_flow.py +++ b/tests/components/filesize/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Filesize config flow.""" + from pathlib import Path from unittest.mock import patch diff --git a/tests/components/filesize/test_init.py b/tests/components/filesize/test_init.py index c580bb7da77..7f1672176dc 100644 --- a/tests/components/filesize/test_init.py +++ b/tests/components/filesize/test_init.py @@ -1,4 +1,5 @@ """Tests for the Filesize integration.""" + from pathlib import Path from homeassistant.components.filesize.const import DOMAIN diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index ffb306a23c1..d0588b7ea9a 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -1,4 +1,5 @@ """The test for the data filter sensor platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/fireservicerota/test_config_flow.py b/tests/components/fireservicerota/test_config_flow.py index ace0532f1f7..ffb3ac23f1b 100644 --- a/tests/components/fireservicerota/test_config_flow.py +++ b/tests/components/fireservicerota/test_config_flow.py @@ -1,4 +1,5 @@ """Test the FireServiceRota config flow.""" + from unittest.mock import patch from pyfireservicerota import InvalidAuthError diff --git a/tests/components/firmata/test_config_flow.py b/tests/components/firmata/test_config_flow.py index 474455fc164..4dc0dbc80ca 100644 --- a/tests/components/firmata/test_config_flow.py +++ b/tests/components/firmata/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Firmata config flow.""" + from unittest.mock import patch from pymata_express.pymata_express_serial import serial diff --git a/tests/components/fivem/test_config_flow.py b/tests/components/fivem/test_config_flow.py index 121b416a110..1596da01648 100644 --- a/tests/components/fivem/test_config_flow.py +++ b/tests/components/fivem/test_config_flow.py @@ -1,4 +1,5 @@ """Test the FiveM config flow.""" + from unittest.mock import patch from fivem import FiveMServerOfflineError diff --git a/tests/components/fjaraskupan/conftest.py b/tests/components/fjaraskupan/conftest.py index 46ff5ae167a..85493157a3c 100644 --- a/tests/components/fjaraskupan/conftest.py +++ b/tests/components/fjaraskupan/conftest.py @@ -1,4 +1,5 @@ """Standard fixtures for the Fjäråskupan integration.""" + from __future__ import annotations import pytest diff --git a/tests/components/fjaraskupan/test_config_flow.py b/tests/components/fjaraskupan/test_config_flow.py index 13dc643a2a1..b6da1fcf5b5 100644 --- a/tests/components/fjaraskupan/test_config_flow.py +++ b/tests/components/fjaraskupan/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Fjäråskupan config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/flexit_bacnet/conftest.py b/tests/components/flexit_bacnet/conftest.py index cfcecebd19b..55941dc44a9 100644 --- a/tests/components/flexit_bacnet/conftest.py +++ b/tests/components/flexit_bacnet/conftest.py @@ -1,4 +1,5 @@ """Configuration for Flexit Nordic (BACnet) tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/flexit_bacnet/test_binary_sensor.py b/tests/components/flexit_bacnet/test_binary_sensor.py index df363086f63..649eebaec2c 100644 --- a/tests/components/flexit_bacnet/test_binary_sensor.py +++ b/tests/components/flexit_bacnet/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) binary sensor entities.""" + from unittest.mock import AsyncMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/flexit_bacnet/test_climate.py b/tests/components/flexit_bacnet/test_climate.py index 077aee019e7..6c88e6e69d2 100644 --- a/tests/components/flexit_bacnet/test_climate.py +++ b/tests/components/flexit_bacnet/test_climate.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) climate entity.""" + from unittest.mock import AsyncMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/flexit_bacnet/test_init.py b/tests/components/flexit_bacnet/test_init.py index 71f79f54302..0741120c1ad 100644 --- a/tests/components/flexit_bacnet/test_init.py +++ b/tests/components/flexit_bacnet/test_init.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) __init__.""" + from flexit_bacnet import DecodingError from homeassistant.components.flexit_bacnet.const import DOMAIN diff --git a/tests/components/flexit_bacnet/test_number.py b/tests/components/flexit_bacnet/test_number.py index 6f23ee11866..2aa3c9abcff 100644 --- a/tests/components/flexit_bacnet/test_number.py +++ b/tests/components/flexit_bacnet/test_number.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) number entities.""" + from unittest.mock import AsyncMock from flexit_bacnet import DecodingError diff --git a/tests/components/flexit_bacnet/test_sensor.py b/tests/components/flexit_bacnet/test_sensor.py index 2285b4c8692..460f2cf5728 100644 --- a/tests/components/flexit_bacnet/test_sensor.py +++ b/tests/components/flexit_bacnet/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) sensor entities.""" + from unittest.mock import AsyncMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/flexit_bacnet/test_switch.py b/tests/components/flexit_bacnet/test_switch.py index 7c08fc2a024..19c7dfc804e 100644 --- a/tests/components/flexit_bacnet/test_switch.py +++ b/tests/components/flexit_bacnet/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) switch entities.""" + from unittest.mock import AsyncMock from flexit_bacnet import DecodingError diff --git a/tests/components/flic/test_binary_sensor.py b/tests/components/flic/test_binary_sensor.py index 2fa703348f9..d2584e4f5a2 100644 --- a/tests/components/flic/test_binary_sensor.py +++ b/tests/components/flic/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for Flic button integration.""" + from unittest import mock from homeassistant.core import HomeAssistant diff --git a/tests/components/flick_electric/test_config_flow.py b/tests/components/flick_electric/test_config_flow.py index bd77f1b6002..123232e8c52 100644 --- a/tests/components/flick_electric/test_config_flow.py +++ b/tests/components/flick_electric/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Flick Electric config flow.""" + from unittest.mock import patch from pyflick.authentication import AuthException diff --git a/tests/components/flipr/test_binary_sensor.py b/tests/components/flipr/test_binary_sensor.py index fa938521d3b..971b5b046b3 100644 --- a/tests/components/flipr/test_binary_sensor.py +++ b/tests/components/flipr/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Flipr binary sensor.""" + from datetime import datetime from unittest.mock import patch diff --git a/tests/components/flipr/test_config_flow.py b/tests/components/flipr/test_config_flow.py index 08fb71da1e4..4ee6d85cead 100644 --- a/tests/components/flipr/test_config_flow.py +++ b/tests/components/flipr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Flipr config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/flipr/test_init.py b/tests/components/flipr/test_init.py index c1c5c0086e7..8300ac185ba 100644 --- a/tests/components/flipr/test_init.py +++ b/tests/components/flipr/test_init.py @@ -1,4 +1,5 @@ """Tests for init methods.""" + from unittest.mock import patch from homeassistant.components.flipr.const import CONF_FLIPR_ID, DOMAIN diff --git a/tests/components/flipr/test_sensor.py b/tests/components/flipr/test_sensor.py index 339986dd54b..31eb075469d 100644 --- a/tests/components/flipr/test_sensor.py +++ b/tests/components/flipr/test_sensor.py @@ -1,4 +1,5 @@ """Test the Flipr sensor.""" + from datetime import datetime from unittest.mock import patch diff --git a/tests/components/flo/conftest.py b/tests/components/flo/conftest.py index 1484b10eae2..3cd666b7462 100644 --- a/tests/components/flo/conftest.py +++ b/tests/components/flo/conftest.py @@ -1,4 +1,5 @@ """Define fixtures available for all tests.""" + from http import HTTPStatus import json import time diff --git a/tests/components/flo/test_binary_sensor.py b/tests/components/flo/test_binary_sensor.py index b94f182f3bf..d3032cde1b5 100644 --- a/tests/components/flo/test_binary_sensor.py +++ b/tests/components/flo/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test Flo by Moen binary sensor entities.""" + from homeassistant.components.flo.const import DOMAIN as FLO_DOMAIN from homeassistant.const import ( ATTR_FRIENDLY_NAME, diff --git a/tests/components/flo/test_config_flow.py b/tests/components/flo/test_config_flow.py index 703689e7c36..f5a730a2056 100644 --- a/tests/components/flo/test_config_flow.py +++ b/tests/components/flo/test_config_flow.py @@ -1,4 +1,5 @@ """Test the flo config flow.""" + from http import HTTPStatus import json import time diff --git a/tests/components/flo/test_device.py b/tests/components/flo/test_device.py index 34abfef0e72..c1c9222c723 100644 --- a/tests/components/flo/test_device.py +++ b/tests/components/flo/test_device.py @@ -1,4 +1,5 @@ """Define tests for device-related endpoints.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/flo/test_init.py b/tests/components/flo/test_init.py index 44f31bdb738..599a91b80fb 100644 --- a/tests/components/flo/test_init.py +++ b/tests/components/flo/test_init.py @@ -1,4 +1,5 @@ """Test init.""" + from homeassistant.components.flo.const import DOMAIN as FLO_DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant diff --git a/tests/components/flo/test_sensor.py b/tests/components/flo/test_sensor.py index 14a36b9e032..5fe388c62e1 100644 --- a/tests/components/flo/test_sensor.py +++ b/tests/components/flo/test_sensor.py @@ -1,4 +1,5 @@ """Test Flo by Moen sensor entities.""" + from homeassistant.components.flo.const import DOMAIN as FLO_DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass from homeassistant.const import ATTR_ENTITY_ID, CONF_PASSWORD, CONF_USERNAME diff --git a/tests/components/flo/test_switch.py b/tests/components/flo/test_switch.py index 93996a35a98..85f7ea0f317 100644 --- a/tests/components/flo/test_switch.py +++ b/tests/components/flo/test_switch.py @@ -1,4 +1,5 @@ """Tests for the switch domain for Flo by Moen.""" + from homeassistant.components.flo.const import DOMAIN as FLO_DOMAIN from homeassistant.components.switch import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, STATE_OFF, STATE_ON diff --git a/tests/components/flume/test_config_flow.py b/tests/components/flume/test_config_flow.py index f5315e07043..64c85a0b896 100644 --- a/tests/components/flume/test_config_flow.py +++ b/tests/components/flume/test_config_flow.py @@ -1,4 +1,5 @@ """Test the flume config flow.""" + from unittest.mock import MagicMock, patch import requests.exceptions diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index ed8a4756031..1e783c6b649 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Flux switch platform.""" + from unittest.mock import patch from freezegun import freeze_time diff --git a/tests/components/flux_led/test_button.py b/tests/components/flux_led/test_button.py index 010b7f329ed..105fc57c085 100644 --- a/tests/components/flux_led/test_button.py +++ b/tests/components/flux_led/test_button.py @@ -1,4 +1,5 @@ """Tests for button platform.""" + from homeassistant.components import flux_led from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.flux_led.const import DOMAIN diff --git a/tests/components/flux_led/test_config_flow.py b/tests/components/flux_led/test_config_flow.py index 008661303f1..183164e04a1 100644 --- a/tests/components/flux_led/test_config_flow.py +++ b/tests/components/flux_led/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Flux LED/Magic Home config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/flux_led/test_diagnostics.py b/tests/components/flux_led/test_diagnostics.py index 4bc53440632..b539d5252e5 100644 --- a/tests/components/flux_led/test_diagnostics.py +++ b/tests/components/flux_led/test_diagnostics.py @@ -1,4 +1,5 @@ """Test flux_led diagnostics.""" + from homeassistant.components.flux_led.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/flux_led/test_init.py b/tests/components/flux_led/test_init.py index d75644c7599..7c4917ea4dc 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -1,4 +1,5 @@ """Tests for the flux_led component.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/flux_led/test_light.py b/tests/components/flux_led/test_light.py index 6ddb9e1687f..f5a7b310202 100644 --- a/tests/components/flux_led/test_light.py +++ b/tests/components/flux_led/test_light.py @@ -1,4 +1,5 @@ """Tests for light platform.""" + from datetime import timedelta from unittest.mock import AsyncMock, Mock diff --git a/tests/components/flux_led/test_select.py b/tests/components/flux_led/test_select.py index 1cdbb9369ab..76512d9dc4c 100644 --- a/tests/components/flux_led/test_select.py +++ b/tests/components/flux_led/test_select.py @@ -1,4 +1,5 @@ """Tests for select platform.""" + from unittest.mock import patch from flux_led.const import ( diff --git a/tests/components/flux_led/test_sensor.py b/tests/components/flux_led/test_sensor.py index b06a6330fde..49f291a916a 100644 --- a/tests/components/flux_led/test_sensor.py +++ b/tests/components/flux_led/test_sensor.py @@ -1,4 +1,5 @@ """Tests for flux_led sensor platform.""" + from homeassistant.components import flux_led from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/flux_led/test_switch.py b/tests/components/flux_led/test_switch.py index 5d025a4cab0..ac0aec66c23 100644 --- a/tests/components/flux_led/test_switch.py +++ b/tests/components/flux_led/test_switch.py @@ -1,4 +1,5 @@ """Tests for switch platform.""" + from flux_led.const import MODE_MUSIC from homeassistant.components import flux_led diff --git a/tests/components/foobot/test_sensor.py b/tests/components/foobot/test_sensor.py index 27e56816176..d8beae3b77b 100644 --- a/tests/components/foobot/test_sensor.py +++ b/tests/components/foobot/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Foobot sensor platform.""" + from http import HTTPStatus import re from unittest.mock import MagicMock diff --git a/tests/components/forecast_solar/test_config_flow.py b/tests/components/forecast_solar/test_config_flow.py index 06aeb94542e..015bd809b20 100644 --- a/tests/components/forecast_solar/test_config_flow.py +++ b/tests/components/forecast_solar/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Forecast.Solar config flow.""" + from unittest.mock import AsyncMock from homeassistant.components.forecast_solar.const import ( diff --git a/tests/components/forecast_solar/test_diagnostics.py b/tests/components/forecast_solar/test_diagnostics.py index e72f2d7d9dc..0e80fba7647 100644 --- a/tests/components/forecast_solar/test_diagnostics.py +++ b/tests/components/forecast_solar/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Forecast.Solar integration.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/forecast_solar/test_energy.py b/tests/components/forecast_solar/test_energy.py index 7d3a853b8a7..246ed866506 100644 --- a/tests/components/forecast_solar/test_energy.py +++ b/tests/components/forecast_solar/test_energy.py @@ -1,4 +1,5 @@ """Test forecast solar energy platform.""" + from datetime import UTC, datetime from unittest.mock import MagicMock diff --git a/tests/components/forecast_solar/test_init.py b/tests/components/forecast_solar/test_init.py index 25dcb41c976..b581888547d 100644 --- a/tests/components/forecast_solar/test_init.py +++ b/tests/components/forecast_solar/test_init.py @@ -1,4 +1,5 @@ """Tests for the Forecast.Solar integration.""" + from unittest.mock import MagicMock, patch from forecast_solar import ForecastSolarConnectionError diff --git a/tests/components/forecast_solar/test_sensor.py b/tests/components/forecast_solar/test_sensor.py index 8faec950eb7..a09bb94aa1b 100644 --- a/tests/components/forecast_solar/test_sensor.py +++ b/tests/components/forecast_solar/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Forecast.Solar integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/forked_daapd/test_browse_media.py b/tests/components/forked_daapd/test_browse_media.py index 4d15f083591..29923c9f9e9 100644 --- a/tests/components/forked_daapd/test_browse_media.py +++ b/tests/components/forked_daapd/test_browse_media.py @@ -1,4 +1,5 @@ """Media browsing tests for the forked_daapd media player platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/forked_daapd/test_config_flow.py b/tests/components/forked_daapd/test_config_flow.py index 080e47acc3e..7dc5e1e20f9 100644 --- a/tests/components/forked_daapd/test_config_flow.py +++ b/tests/components/forked_daapd/test_config_flow.py @@ -1,4 +1,5 @@ """The config flow tests for the forked_daapd media player platform.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/forked_daapd/test_media_player.py b/tests/components/forked_daapd/test_media_player.py index ea9cf557570..19488666be7 100644 --- a/tests/components/forked_daapd/test_media_player.py +++ b/tests/components/forked_daapd/test_media_player.py @@ -1,4 +1,5 @@ """The media player tests for the forked_daapd media player platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/foscam/test_config_flow.py b/tests/components/foscam/test_config_flow.py index 49bffe491af..fc3bd1d5ad2 100644 --- a/tests/components/foscam/test_config_flow.py +++ b/tests/components/foscam/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Foscam config flow.""" + from unittest.mock import patch from libpyfoscam.foscam import ( diff --git a/tests/components/freebox/common.py b/tests/components/freebox/common.py index 9f7dfd8f92a..31ee2a474a5 100644 --- a/tests/components/freebox/common.py +++ b/tests/components/freebox/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for Freebox.""" + from unittest.mock import patch from homeassistant.components.freebox.const import DOMAIN diff --git a/tests/components/freebox/test_alarm_control_panel.py b/tests/components/freebox/test_alarm_control_panel.py index 44286f18b87..e4ee8f63b2c 100644 --- a/tests/components/freebox/test_alarm_control_panel.py +++ b/tests/components/freebox/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Tests for the Freebox alarms.""" + from copy import deepcopy from unittest.mock import Mock diff --git a/tests/components/freebox/test_binary_sensor.py b/tests/components/freebox/test_binary_sensor.py index ee07af786be..4950ef27e5f 100644 --- a/tests/components/freebox/test_binary_sensor.py +++ b/tests/components/freebox/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Freebox binary sensors.""" + from copy import deepcopy from unittest.mock import Mock diff --git a/tests/components/freebox/test_button.py b/tests/components/freebox/test_button.py index 209ab1e9fc2..e2e1c9714a5 100644 --- a/tests/components/freebox/test_button.py +++ b/tests/components/freebox/test_button.py @@ -1,4 +1,5 @@ """Tests for the Freebox buttons.""" + from unittest.mock import ANY, AsyncMock, Mock, patch from pytest_unordered import unordered diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index c19b3c3f3b2..a7dff79ecfb 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Freebox config flow.""" + from ipaddress import ip_address from unittest.mock import Mock, patch diff --git a/tests/components/freebox/test_device_tracker.py b/tests/components/freebox/test_device_tracker.py index 6d4ca5fb7ee..405166d6ba2 100644 --- a/tests/components/freebox/test_device_tracker.py +++ b/tests/components/freebox/test_device_tracker.py @@ -1,4 +1,5 @@ """Tests for the Freebox device trackers.""" + from unittest.mock import Mock from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/freebox/test_init.py b/tests/components/freebox/test_init.py index 9064727fb7f..4be58f247cd 100644 --- a/tests/components/freebox/test_init.py +++ b/tests/components/freebox/test_init.py @@ -1,4 +1,5 @@ """Tests for the Freebox init.""" + from unittest.mock import ANY, Mock, patch from pytest_unordered import unordered diff --git a/tests/components/freebox/test_sensor.py b/tests/components/freebox/test_sensor.py index 0abdc55b92c..834bafa0e64 100644 --- a/tests/components/freebox/test_sensor.py +++ b/tests/components/freebox/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Freebox sensors.""" + from copy import deepcopy from unittest.mock import Mock diff --git a/tests/components/freedompro/conftest.py b/tests/components/freedompro/conftest.py index 30e7968e2fe..63efbc31ca5 100644 --- a/tests/components/freedompro/conftest.py +++ b/tests/components/freedompro/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Freedompro integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/freedompro/test_binary_sensor.py b/tests/components/freedompro/test_binary_sensor.py index 84e421a8653..30bb1a58117 100644 --- a/tests/components/freedompro/test_binary_sensor.py +++ b/tests/components/freedompro/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Freedompro binary sensor.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/freedompro/test_climate.py b/tests/components/freedompro/test_climate.py index 581c6d05448..0195f6bde94 100644 --- a/tests/components/freedompro/test_climate.py +++ b/tests/components/freedompro/test_climate.py @@ -1,4 +1,5 @@ """Tests for the Freedompro climate.""" + from datetime import timedelta from unittest.mock import ANY, patch diff --git a/tests/components/freedompro/test_config_flow.py b/tests/components/freedompro/test_config_flow.py index cab8605d865..a0063f72557 100644 --- a/tests/components/freedompro/test_config_flow.py +++ b/tests/components/freedompro/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Freedompro config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/freedompro/test_cover.py b/tests/components/freedompro/test_cover.py index a4c837194fe..9eef2b0b95b 100644 --- a/tests/components/freedompro/test_cover.py +++ b/tests/components/freedompro/test_cover.py @@ -1,4 +1,5 @@ """Tests for the Freedompro cover.""" + from datetime import timedelta from unittest.mock import ANY, patch diff --git a/tests/components/freedompro/test_fan.py b/tests/components/freedompro/test_fan.py index 80b1e5613eb..ea9b94ffc41 100644 --- a/tests/components/freedompro/test_fan.py +++ b/tests/components/freedompro/test_fan.py @@ -1,4 +1,5 @@ """Tests for the Freedompro fan.""" + from datetime import timedelta from unittest.mock import ANY, patch diff --git a/tests/components/freedompro/test_light.py b/tests/components/freedompro/test_light.py index 53cb59d5646..d0270f40843 100644 --- a/tests/components/freedompro/test_light.py +++ b/tests/components/freedompro/test_light.py @@ -1,4 +1,5 @@ """Tests for the Freedompro light.""" + from unittest.mock import patch import pytest diff --git a/tests/components/freedompro/test_lock.py b/tests/components/freedompro/test_lock.py index 37145d6fe95..ad5b53dd51d 100644 --- a/tests/components/freedompro/test_lock.py +++ b/tests/components/freedompro/test_lock.py @@ -1,4 +1,5 @@ """Tests for the Freedompro lock.""" + from datetime import timedelta from unittest.mock import ANY, patch diff --git a/tests/components/freedompro/test_sensor.py b/tests/components/freedompro/test_sensor.py index c06ce5b0794..1deb895ad3d 100644 --- a/tests/components/freedompro/test_sensor.py +++ b/tests/components/freedompro/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Freedompro sensor.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/freedompro/test_switch.py b/tests/components/freedompro/test_switch.py index 7d72a87a7b5..e3ab02de55b 100644 --- a/tests/components/freedompro/test_switch.py +++ b/tests/components/freedompro/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Freedompro switch.""" + from datetime import timedelta from unittest.mock import ANY, patch diff --git a/tests/components/fritz/const.py b/tests/components/fritz/const.py index d39cb21beea..30c9f9be174 100644 --- a/tests/components/fritz/const.py +++ b/tests/components/fritz/const.py @@ -1,4 +1,5 @@ """Common stuff for Fritz!Tools tests.""" + from homeassistant.components import ssdp from homeassistant.components.fritz.const import DOMAIN from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN diff --git a/tests/components/fritz/test_button.py b/tests/components/fritz/test_button.py index e375cd4f047..106fb7f9bef 100644 --- a/tests/components/fritz/test_button.py +++ b/tests/components/fritz/test_button.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools button platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/fritz/test_diagnostics.py b/tests/components/fritz/test_diagnostics.py index 9f50a598039..9dc50cc3378 100644 --- a/tests/components/fritz/test_diagnostics.py +++ b/tests/components/fritz/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools diagnostics platform.""" + from __future__ import annotations from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/fritz/test_image.py b/tests/components/fritz/test_image.py index bd75676da6c..85d02eff153 100644 --- a/tests/components/fritz/test_image.py +++ b/tests/components/fritz/test_image.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools image platform.""" + from datetime import timedelta from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/fritz/test_init.py b/tests/components/fritz/test_init.py index 2e230eb61a6..0a525192778 100644 --- a/tests/components/fritz/test_init.py +++ b/tests/components/fritz/test_init.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools.""" + from unittest.mock import patch import pytest diff --git a/tests/components/fritz/test_sensor.py b/tests/components/fritz/test_sensor.py index 9265d1337f0..8ca7b510412 100644 --- a/tests/components/fritz/test_sensor.py +++ b/tests/components/fritz/test_sensor.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/fritz/test_switch.py b/tests/components/fritz/test_switch.py index c8288f0143f..722f16fa0de 100644 --- a/tests/components/fritz/test_switch.py +++ b/tests/components/fritz/test_switch.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools switch platform.""" + from __future__ import annotations import pytest diff --git a/tests/components/fritzbox/conftest.py b/tests/components/fritzbox/conftest.py index 1fbaf48de4b..02a3eed011e 100644 --- a/tests/components/fritzbox/conftest.py +++ b/tests/components/fritzbox/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the AVM Fritz!Box integration.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/fritzbox/const.py b/tests/components/fritzbox/const.py index 1725d974c6f..c2895de6b8e 100644 --- a/tests/components/fritzbox/const.py +++ b/tests/components/fritzbox/const.py @@ -1,4 +1,5 @@ """Constants for fritzbox tests.""" + from homeassistant.components.fritzbox.const import DOMAIN from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME diff --git a/tests/components/fritzbox/test_binary_sensor.py b/tests/components/fritzbox/test_binary_sensor.py index 983516bb9c0..3828cedc67f 100644 --- a/tests/components/fritzbox/test_binary_sensor.py +++ b/tests/components/fritzbox/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box binary sensor component.""" + from datetime import timedelta from unittest import mock from unittest.mock import Mock diff --git a/tests/components/fritzbox/test_button.py b/tests/components/fritzbox/test_button.py index 8c0bbec573e..f254b2e0710 100644 --- a/tests/components/fritzbox/test_button.py +++ b/tests/components/fritzbox/test_button.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box templates.""" + from datetime import timedelta from unittest.mock import Mock diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index a14c53d6529..a201eab3665 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box climate component.""" + from datetime import timedelta from unittest.mock import Mock, call diff --git a/tests/components/fritzbox/test_cover.py b/tests/components/fritzbox/test_cover.py index e3a6d786abf..b723ac97d06 100644 --- a/tests/components/fritzbox/test_cover.py +++ b/tests/components/fritzbox/test_cover.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box switch component.""" + from datetime import timedelta from unittest.mock import Mock, call diff --git a/tests/components/fritzbox/test_diagnostics.py b/tests/components/fritzbox/test_diagnostics.py index ec1bcce3979..38aaa623080 100644 --- a/tests/components/fritzbox/test_diagnostics.py +++ b/tests/components/fritzbox/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the AVM Fritz!Box integration.""" + from __future__ import annotations from unittest.mock import Mock diff --git a/tests/components/fritzbox/test_init.py b/tests/components/fritzbox/test_init.py index b8273204325..4ee351f7914 100644 --- a/tests/components/fritzbox/test_init.py +++ b/tests/components/fritzbox/test_init.py @@ -1,4 +1,5 @@ """Tests for the AVM Fritz!Box integration.""" + from __future__ import annotations from unittest.mock import Mock, call, patch diff --git a/tests/components/fritzbox/test_light.py b/tests/components/fritzbox/test_light.py index 858b564cd18..b750a2e9275 100644 --- a/tests/components/fritzbox/test_light.py +++ b/tests/components/fritzbox/test_light.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box light component.""" + from datetime import timedelta from unittest.mock import Mock, call diff --git a/tests/components/fritzbox/test_sensor.py b/tests/components/fritzbox/test_sensor.py index 9fe25d02ed0..48b769eaac2 100644 --- a/tests/components/fritzbox/test_sensor.py +++ b/tests/components/fritzbox/test_sensor.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box sensor component.""" + from datetime import timedelta from unittest.mock import Mock diff --git a/tests/components/fritzbox/test_switch.py b/tests/components/fritzbox/test_switch.py index aefe21e3ffc..67393bc09a5 100644 --- a/tests/components/fritzbox/test_switch.py +++ b/tests/components/fritzbox/test_switch.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box switch component.""" + from datetime import timedelta from unittest.mock import Mock diff --git a/tests/components/fritzbox_callmonitor/test_config_flow.py b/tests/components/fritzbox_callmonitor/test_config_flow.py index 386151c31b1..accdad3a70d 100644 --- a/tests/components/fritzbox_callmonitor/test_config_flow.py +++ b/tests/components/fritzbox_callmonitor/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for fritzbox_callmonitor config flow.""" + from __future__ import annotations from unittest.mock import PropertyMock diff --git a/tests/components/fronius/test_config_flow.py b/tests/components/fronius/test_config_flow.py index 928bca0eb94..d0dd32ad801 100644 --- a/tests/components/fronius/test_config_flow.py +++ b/tests/components/fronius/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Fronius config flow.""" + from unittest.mock import patch from pyfronius import FroniusError diff --git a/tests/components/fronius/test_coordinator.py b/tests/components/fronius/test_coordinator.py index d4f42fadb06..13a08bbe70e 100644 --- a/tests/components/fronius/test_coordinator.py +++ b/tests/components/fronius/test_coordinator.py @@ -1,4 +1,5 @@ """Test the Fronius update coordinators.""" + from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/fronius/test_init.py b/tests/components/fronius/test_init.py index f8d86bac26a..282b2c3fa76 100644 --- a/tests/components/fronius/test_init.py +++ b/tests/components/fronius/test_init.py @@ -1,4 +1,5 @@ """Test the Fronius integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/fronius/test_sensor.py b/tests/components/fronius/test_sensor.py index a8f48ce2e88..f5e77660271 100644 --- a/tests/components/fronius/test_sensor.py +++ b/tests/components/fronius/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Fronius sensor platform.""" + from freezegun.api import FrozenDateTimeFactory import pytest diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index f04d4a9bc52..8bc2cf0606a 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -1,4 +1,5 @@ """The tests for Home Assistant frontend.""" + from http import HTTPStatus import re from typing import Any diff --git a/tests/components/frontend/test_storage.py b/tests/components/frontend/test_storage.py index 0260907ab2e..8b97fa9ee04 100644 --- a/tests/components/frontend/test_storage.py +++ b/tests/components/frontend/test_storage.py @@ -1,4 +1,5 @@ """The tests for frontend storage.""" + from typing import Any import pytest diff --git a/tests/components/frontier_silicon/conftest.py b/tests/components/frontier_silicon/conftest.py index 1def9b160b2..65a5ede5b26 100644 --- a/tests/components/frontier_silicon/conftest.py +++ b/tests/components/frontier_silicon/conftest.py @@ -1,4 +1,5 @@ """Configuration for frontier_silicon tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/frontier_silicon/test_config_flow.py b/tests/components/frontier_silicon/test_config_flow.py index bedac792f02..6a5e62f7dce 100644 --- a/tests/components/frontier_silicon/test_config_flow.py +++ b/tests/components/frontier_silicon/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Frontier Silicon config flow.""" + from unittest.mock import AsyncMock, patch from afsapi import ConnectionError, InvalidPinException, NotImplementedException diff --git a/tests/components/fully_kiosk/conftest.py b/tests/components/fully_kiosk/conftest.py index e409a0a3787..ff732d0e223 100644 --- a/tests/components/fully_kiosk/conftest.py +++ b/tests/components/fully_kiosk/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Fully Kiosk Browser integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/fully_kiosk/test_binary_sensor.py b/tests/components/fully_kiosk/test_binary_sensor.py index cc003199f26..23843eef19c 100644 --- a/tests/components/fully_kiosk/test_binary_sensor.py +++ b/tests/components/fully_kiosk/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser binary sensors.""" + from unittest.mock import MagicMock from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/fully_kiosk/test_button.py b/tests/components/fully_kiosk/test_button.py index f04935aed0e..9bd4c3a897c 100644 --- a/tests/components/fully_kiosk/test_button.py +++ b/tests/components/fully_kiosk/test_button.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser buttons.""" + from unittest.mock import MagicMock import homeassistant.components.button as button diff --git a/tests/components/fully_kiosk/test_diagnostics.py b/tests/components/fully_kiosk/test_diagnostics.py index e48867739e8..31050dad1e0 100644 --- a/tests/components/fully_kiosk/test_diagnostics.py +++ b/tests/components/fully_kiosk/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser diagnostics.""" + from unittest.mock import MagicMock from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/fully_kiosk/test_media_player.py b/tests/components/fully_kiosk/test_media_player.py index 4cae64e641e..b8719a578aa 100644 --- a/tests/components/fully_kiosk/test_media_player.py +++ b/tests/components/fully_kiosk/test_media_player.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser media player.""" + from unittest.mock import MagicMock, Mock, patch from homeassistant.components.fully_kiosk.const import DOMAIN, MEDIA_SUPPORT_FULLYKIOSK diff --git a/tests/components/fully_kiosk/test_number.py b/tests/components/fully_kiosk/test_number.py index 286ca7fc0cb..6bdad9af520 100644 --- a/tests/components/fully_kiosk/test_number.py +++ b/tests/components/fully_kiosk/test_number.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser number entities.""" + from unittest.mock import MagicMock from homeassistant.components.fully_kiosk.const import DOMAIN, UPDATE_INTERVAL diff --git a/tests/components/fully_kiosk/test_sensor.py b/tests/components/fully_kiosk/test_sensor.py index 40912f0f568..6342e3216d7 100644 --- a/tests/components/fully_kiosk/test_sensor.py +++ b/tests/components/fully_kiosk/test_sensor.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser sensors.""" + from unittest.mock import MagicMock from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/fully_kiosk/test_services.py b/tests/components/fully_kiosk/test_services.py index 6622b400da1..eaf00d74a91 100644 --- a/tests/components/fully_kiosk/test_services.py +++ b/tests/components/fully_kiosk/test_services.py @@ -1,4 +1,5 @@ """Test Fully Kiosk Browser services.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/fully_kiosk/test_switch.py b/tests/components/fully_kiosk/test_switch.py index 3c0874384c2..03ac00ef677 100644 --- a/tests/components/fully_kiosk/test_switch.py +++ b/tests/components/fully_kiosk/test_switch.py @@ -1,4 +1,5 @@ """Test the Fully Kiosk Browser switches.""" + from unittest.mock import MagicMock from homeassistant.components.fully_kiosk.const import DOMAIN diff --git a/tests/components/garages_amsterdam/test_config_flow.py b/tests/components/garages_amsterdam/test_config_flow.py index ebafe3d566a..ae735d71e55 100644 --- a/tests/components/garages_amsterdam/test_config_flow.py +++ b/tests/components/garages_amsterdam/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Garages Amsterdam config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/gardena_bluetooth/conftest.py b/tests/components/gardena_bluetooth/conftest.py index 9395d8570e6..c840ad9bf77 100644 --- a/tests/components/gardena_bluetooth/conftest.py +++ b/tests/components/gardena_bluetooth/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Gardena Bluetooth tests.""" + from collections.abc import Awaitable, Callable, Generator from typing import Any from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/gardena_bluetooth/test_config_flow.py b/tests/components/gardena_bluetooth/test_config_flow.py index bcbd27e50a7..7707a13180f 100644 --- a/tests/components/gardena_bluetooth/test_config_flow.py +++ b/tests/components/gardena_bluetooth/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Gardena Bluetooth config flow.""" + from unittest.mock import Mock from gardena_bluetooth.exceptions import CharacteristicNotFound diff --git a/tests/components/gardena_bluetooth/test_sensor.py b/tests/components/gardena_bluetooth/test_sensor.py index dc0d0cb4809..e794934d028 100644 --- a/tests/components/gardena_bluetooth/test_sensor.py +++ b/tests/components/gardena_bluetooth/test_sensor.py @@ -1,4 +1,5 @@ """Test Gardena Bluetooth sensor.""" + from collections.abc import Awaitable, Callable from gardena_bluetooth.const import Battery, Sensor, Valve diff --git a/tests/components/gdacs/test_config_flow.py b/tests/components/gdacs/test_config_flow.py index ad673815ace..71e5dfdb5d5 100644 --- a/tests/components/gdacs/test_config_flow.py +++ b/tests/components/gdacs/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the GDACS config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/gdacs/test_init.py b/tests/components/gdacs/test_init.py index 6fc721261a7..1da4b0d9b9f 100644 --- a/tests/components/gdacs/test_init.py +++ b/tests/components/gdacs/test_init.py @@ -1,4 +1,5 @@ """Define tests for the GDACS general setup.""" + from unittest.mock import patch from homeassistant.components.gdacs import DOMAIN, FEED diff --git a/tests/components/gdacs/test_sensor.py b/tests/components/gdacs/test_sensor.py index f40756235e1..a21df176bdd 100644 --- a/tests/components/gdacs/test_sensor.py +++ b/tests/components/gdacs/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the GDACS Feed integration.""" + from unittest.mock import patch from freezegun import freeze_time diff --git a/tests/components/geo_json_events/conftest.py b/tests/components/geo_json_events/conftest.py index db0ac38fe47..80e06f4880c 100644 --- a/tests/components/geo_json_events/conftest.py +++ b/tests/components/geo_json_events/conftest.py @@ -1,4 +1,5 @@ """Configuration for GeoJSON Events tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index 2f3b12ed554..ac9f3f2347f 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the geojson platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/geo_json_events/test_init.py b/tests/components/geo_json_events/test_init.py index bc803b3e8d8..278586ba2e3 100644 --- a/tests/components/geo_json_events/test_init.py +++ b/tests/components/geo_json_events/test_init.py @@ -1,4 +1,5 @@ """Define tests for the GeoJSON Events general setup.""" + from unittest.mock import patch from homeassistant.components.geo_json_events.const import DOMAIN diff --git a/tests/components/geo_rss_events/test_sensor.py b/tests/components/geo_rss_events/test_sensor.py index c86ef393875..76f1709bd75 100644 --- a/tests/components/geo_rss_events/test_sensor.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -1,4 +1,5 @@ """The test for the geo rss events sensor platform.""" + from unittest.mock import MagicMock, patch from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/geocaching/conftest.py b/tests/components/geocaching/conftest.py index f59f428118e..68041672efb 100644 --- a/tests/components/geocaching/conftest.py +++ b/tests/components/geocaching/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Geocaching integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/geocaching/test_config_flow.py b/tests/components/geocaching/test_config_flow.py index 1b0d32278a6..15f7ee0972f 100644 --- a/tests/components/geocaching/test_config_flow.py +++ b/tests/components/geocaching/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Geocaching config flow.""" + from http import HTTPStatus from unittest.mock import MagicMock diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index 2ab2d9cc8bb..d5d77c1387a 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -1,4 +1,5 @@ """The tests for the Geofency device tracker platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/geonetnz_quakes/test_config_flow.py b/tests/components/geonetnz_quakes/test_config_flow.py index 4a59386fc35..75e595c9900 100644 --- a/tests/components/geonetnz_quakes/test_config_flow.py +++ b/tests/components/geonetnz_quakes/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the GeoNet NZ Quakes config flow.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/geonetnz_quakes/test_init.py b/tests/components/geonetnz_quakes/test_init.py index 043d0ff6209..6730fa53ece 100644 --- a/tests/components/geonetnz_quakes/test_init.py +++ b/tests/components/geonetnz_quakes/test_init.py @@ -1,4 +1,5 @@ """Define tests for the GeoNet NZ Quakes general setup.""" + from unittest.mock import patch from homeassistant.components.geonetnz_quakes import DOMAIN, FEED diff --git a/tests/components/geonetnz_volcano/test_config_flow.py b/tests/components/geonetnz_volcano/test_config_flow.py index 7583bc29a43..6bc211e72be 100644 --- a/tests/components/geonetnz_volcano/test_config_flow.py +++ b/tests/components/geonetnz_volcano/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the GeoNet NZ Volcano config flow.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/geonetnz_volcano/test_init.py b/tests/components/geonetnz_volcano/test_init.py index 64e7ddc3eba..fe113434dc6 100644 --- a/tests/components/geonetnz_volcano/test_init.py +++ b/tests/components/geonetnz_volcano/test_init.py @@ -1,4 +1,5 @@ """Define tests for the GeoNet NZ Volcano general setup.""" + from unittest.mock import AsyncMock, patch from homeassistant.components.geonetnz_volcano import DOMAIN, FEED diff --git a/tests/components/geonetnz_volcano/test_sensor.py b/tests/components/geonetnz_volcano/test_sensor.py index 4d11ff0673c..f97b89053fd 100644 --- a/tests/components/geonetnz_volcano/test_sensor.py +++ b/tests/components/geonetnz_volcano/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the GeoNet NZ Volcano Feed integration.""" + from unittest.mock import AsyncMock, patch from freezegun import freeze_time diff --git a/tests/components/gios/test_sensor.py b/tests/components/gios/test_sensor.py index 7a7a735ff42..4f35587d4fe 100644 --- a/tests/components/gios/test_sensor.py +++ b/tests/components/gios/test_sensor.py @@ -1,4 +1,5 @@ """Test sensor of GIOS integration.""" + from datetime import timedelta import json from unittest.mock import patch diff --git a/tests/components/github/common.py b/tests/components/github/common.py index 223722dccd8..d850ce1bba8 100644 --- a/tests/components/github/common.py +++ b/tests/components/github/common.py @@ -1,4 +1,5 @@ """Common helpers for GitHub integration tests.""" + from __future__ import annotations import json diff --git a/tests/components/github/conftest.py b/tests/components/github/conftest.py index b0b6f243fa0..2951a58702a 100644 --- a/tests/components/github/conftest.py +++ b/tests/components/github/conftest.py @@ -1,4 +1,5 @@ """conftest for the GitHub integration.""" + from collections.abc import Generator from unittest.mock import patch diff --git a/tests/components/github/test_config_flow.py b/tests/components/github/test_config_flow.py index 4f805cf43fc..c715889b7dc 100644 --- a/tests/components/github/test_config_flow.py +++ b/tests/components/github/test_config_flow.py @@ -1,4 +1,5 @@ """Test the GitHub config flow.""" + from unittest.mock import AsyncMock, MagicMock, patch from aiogithubapi import GitHubException diff --git a/tests/components/glances/conftest.py b/tests/components/glances/conftest.py index 9f4590ab5e0..339136f44e8 100644 --- a/tests/components/glances/conftest.py +++ b/tests/components/glances/conftest.py @@ -1,4 +1,5 @@ """Conftest for speedtestdotnet.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/glances/test_config_flow.py b/tests/components/glances/test_config_flow.py index 8d590317c61..09dc638bb53 100644 --- a/tests/components/glances/test_config_flow.py +++ b/tests/components/glances/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Glances config flow.""" + from unittest.mock import MagicMock from glances_api.exceptions import ( diff --git a/tests/components/glances/test_init.py b/tests/components/glances/test_init.py index 764426c6276..aa861dc5518 100644 --- a/tests/components/glances/test_init.py +++ b/tests/components/glances/test_init.py @@ -1,4 +1,5 @@ """Tests for Glances integration.""" + from unittest.mock import AsyncMock, MagicMock from glances_api.exceptions import ( diff --git a/tests/components/glances/test_sensor.py b/tests/components/glances/test_sensor.py index aeef1de0b09..ebe8b75b618 100644 --- a/tests/components/glances/test_sensor.py +++ b/tests/components/glances/test_sensor.py @@ -1,4 +1,5 @@ """Tests for glances sensors.""" + from syrupy import SnapshotAssertion from homeassistant.components.glances.const import DOMAIN diff --git a/tests/components/goalzero/test_binary_sensor.py b/tests/components/goalzero/test_binary_sensor.py index 0a26d2adc2f..0e169fc9de0 100644 --- a/tests/components/goalzero/test_binary_sensor.py +++ b/tests/components/goalzero/test_binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor tests for the Goalzero integration.""" + from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.goalzero.const import DEFAULT_NAME from homeassistant.const import ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON diff --git a/tests/components/goalzero/test_config_flow.py b/tests/components/goalzero/test_config_flow.py index 6d02730a572..7e57312c5b6 100644 --- a/tests/components/goalzero/test_config_flow.py +++ b/tests/components/goalzero/test_config_flow.py @@ -1,4 +1,5 @@ """Test Goal Zero Yeti config flow.""" + from unittest.mock import patch from goalzero import exceptions diff --git a/tests/components/goalzero/test_init.py b/tests/components/goalzero/test_init.py index 3a277d4cb53..1390561785e 100644 --- a/tests/components/goalzero/test_init.py +++ b/tests/components/goalzero/test_init.py @@ -1,4 +1,5 @@ """Test Goal Zero integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/goalzero/test_switch.py b/tests/components/goalzero/test_switch.py index d97a4b9a3fd..de2e6035a12 100644 --- a/tests/components/goalzero/test_switch.py +++ b/tests/components/goalzero/test_switch.py @@ -1,4 +1,5 @@ """Switch tests for the Goalzero integration.""" + from homeassistant.components.goalzero.const import DEFAULT_NAME from homeassistant.components.switch import DOMAIN from homeassistant.const import ( diff --git a/tests/components/gogogate2/test_config_flow.py b/tests/components/gogogate2/test_config_flow.py index 6de04125783..a88dbd45116 100644 --- a/tests/components/gogogate2/test_config_flow.py +++ b/tests/components/gogogate2/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the GogoGate2 component.""" + from ipaddress import ip_address from unittest.mock import MagicMock, patch diff --git a/tests/components/gogogate2/test_cover.py b/tests/components/gogogate2/test_cover.py index ca6509d53b9..001212fa17b 100644 --- a/tests/components/gogogate2/test_cover.py +++ b/tests/components/gogogate2/test_cover.py @@ -1,4 +1,5 @@ """Tests for the GogoGate2 component.""" + from datetime import timedelta from unittest.mock import MagicMock, patch diff --git a/tests/components/gogogate2/test_init.py b/tests/components/gogogate2/test_init.py index 5c0755bb91b..abc7e4a2326 100644 --- a/tests/components/gogogate2/test_init.py +++ b/tests/components/gogogate2/test_init.py @@ -1,4 +1,5 @@ """Tests for the GogoGate2 component.""" + from unittest.mock import MagicMock, patch from ismartgate import GogoGate2Api diff --git a/tests/components/gogogate2/test_sensor.py b/tests/components/gogogate2/test_sensor.py index 8df88b2b4b7..610d9eda34f 100644 --- a/tests/components/gogogate2/test_sensor.py +++ b/tests/components/gogogate2/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the GogoGate2 component.""" + from datetime import timedelta from unittest.mock import MagicMock, patch diff --git a/tests/components/goodwe/conftest.py b/tests/components/goodwe/conftest.py index cabb0f6ea10..0b4ce67d121 100644 --- a/tests/components/goodwe/conftest.py +++ b/tests/components/goodwe/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Aladdin Connect integration tests.""" + from unittest.mock import AsyncMock, MagicMock from goodwe import Inverter diff --git a/tests/components/goodwe/test_config_flow.py b/tests/components/goodwe/test_config_flow.py index c9bf5f1e9ff..b1aabe0be09 100644 --- a/tests/components/goodwe/test_config_flow.py +++ b/tests/components/goodwe/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Goodwe config flow.""" + from unittest.mock import AsyncMock, patch from goodwe import InverterError diff --git a/tests/components/goodwe/test_diagnostics.py b/tests/components/goodwe/test_diagnostics.py index edda2ed2cb7..21917265811 100644 --- a/tests/components/goodwe/test_diagnostics.py +++ b/tests/components/goodwe/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the CO2Signal diagnostics.""" + from unittest.mock import MagicMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/google/conftest.py b/tests/components/google/conftest.py index 97d918c2e01..989e6690630 100644 --- a/tests/components/google/conftest.py +++ b/tests/components/google/conftest.py @@ -1,4 +1,5 @@ """Test configuration and mocks for the google integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Generator diff --git a/tests/components/google/test_calendar.py b/tests/components/google/test_calendar.py index 55a9f814a63..3946e432497 100644 --- a/tests/components/google/test_calendar.py +++ b/tests/components/google/test_calendar.py @@ -1,4 +1,5 @@ """The tests for the google calendar platform.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/tests/components/google/test_diagnostics.py b/tests/components/google/test_diagnostics.py index 5ebc683485b..dd25ba152e5 100644 --- a/tests/components/google/test_diagnostics.py +++ b/tests/components/google/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for diagnostics platform of google calendar.""" + from collections.abc import Callable from typing import Any diff --git a/tests/components/google/test_init.py b/tests/components/google/test_init.py index 26a5cb2e192..c51ecf593e3 100644 --- a/tests/components/google/test_init.py +++ b/tests/components/google/test_init.py @@ -1,4 +1,5 @@ """The tests for the Google Calendar component.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/tests/components/google_assistant/test_button.py b/tests/components/google_assistant/test_button.py index d3c5665b945..11ca77bf733 100644 --- a/tests/components/google_assistant/test_button.py +++ b/tests/components/google_assistant/test_button.py @@ -1,4 +1,5 @@ """Test buttons.""" + from unittest.mock import patch import pytest diff --git a/tests/components/google_assistant/test_diagnostics.py b/tests/components/google_assistant/test_diagnostics.py index df8221b5053..27f741e3e49 100644 --- a/tests/components/google_assistant/test_diagnostics.py +++ b/tests/components/google_assistant/test_diagnostics.py @@ -1,4 +1,5 @@ """Test diagnostics.""" + from unittest.mock import patch import pytest diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index 4fb6f50a5e6..94936eaaf3d 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -1,4 +1,5 @@ """The tests for the Google Assistant component.""" + from http import HTTPStatus import json from unittest.mock import patch diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 1de1799358f..2455e1938fd 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -1,4 +1,5 @@ """Test Google Assistant helpers.""" + from datetime import timedelta from http import HTTPStatus from unittest.mock import Mock, call, patch diff --git a/tests/components/google_assistant/test_http.py b/tests/components/google_assistant/test_http.py index 6f2d61d03ae..cc1332a372b 100644 --- a/tests/components/google_assistant/test_http.py +++ b/tests/components/google_assistant/test_http.py @@ -1,4 +1,5 @@ """Test Google http services.""" + from datetime import UTC, datetime, timedelta from http import HTTPStatus import json diff --git a/tests/components/google_assistant/test_init.py b/tests/components/google_assistant/test_init.py index f33cf4354e3..270455d4f76 100644 --- a/tests/components/google_assistant/test_init.py +++ b/tests/components/google_assistant/test_init.py @@ -1,4 +1,5 @@ """The tests for google-assistant init.""" + from http import HTTPStatus from homeassistant.components import google_assistant as ga diff --git a/tests/components/google_assistant/test_logbook.py b/tests/components/google_assistant/test_logbook.py index e8132d822e8..ca5b011dddc 100644 --- a/tests/components/google_assistant/test_logbook.py +++ b/tests/components/google_assistant/test_logbook.py @@ -1,4 +1,5 @@ """The tests for Google Assistant logbook.""" + from homeassistant.components.google_assistant.const import ( DOMAIN, EVENT_COMMAND_RECEIVED, diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index 29ac7c3b48d..a4829e6140f 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -1,4 +1,5 @@ """Test Google report state.""" + from datetime import datetime, timedelta from http import HTTPStatus from unittest.mock import AsyncMock, patch diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 58cbc5dce0e..b9a68b90a26 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,4 +1,5 @@ """Tests for the Google Assistant traits.""" + from datetime import datetime, timedelta from unittest.mock import ANY, patch diff --git a/tests/components/google_assistant_sdk/conftest.py b/tests/components/google_assistant_sdk/conftest.py index c994a8b12e3..6922b078574 100644 --- a/tests/components/google_assistant_sdk/conftest.py +++ b/tests/components/google_assistant_sdk/conftest.py @@ -1,4 +1,5 @@ """PyTest fixtures and test helpers.""" + from collections.abc import Awaitable, Callable, Coroutine import time from typing import Any diff --git a/tests/components/google_assistant_sdk/test_config_flow.py b/tests/components/google_assistant_sdk/test_config_flow.py index c65477b18b1..49e849398af 100644 --- a/tests/components/google_assistant_sdk/test_config_flow.py +++ b/tests/components/google_assistant_sdk/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Assistant SDK config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/google_assistant_sdk/test_helpers.py b/tests/components/google_assistant_sdk/test_helpers.py index 03a04097d67..1090eb9da45 100644 --- a/tests/components/google_assistant_sdk/test_helpers.py +++ b/tests/components/google_assistant_sdk/test_helpers.py @@ -1,4 +1,5 @@ """Test the Google Assistant SDK helpers.""" + from homeassistant.components.google_assistant_sdk.const import SUPPORTED_LANGUAGE_CODES from homeassistant.components.google_assistant_sdk.helpers import ( DEFAULT_LANGUAGE_CODES, diff --git a/tests/components/google_assistant_sdk/test_init.py b/tests/components/google_assistant_sdk/test_init.py index 5aa68093627..2d930599c24 100644 --- a/tests/components/google_assistant_sdk/test_init.py +++ b/tests/components/google_assistant_sdk/test_init.py @@ -1,4 +1,5 @@ """Tests for Google Assistant SDK.""" + from datetime import timedelta import http import time diff --git a/tests/components/google_assistant_sdk/test_notify.py b/tests/components/google_assistant_sdk/test_notify.py index 3320bb944b2..0ffdc3c5660 100644 --- a/tests/components/google_assistant_sdk/test_notify.py +++ b/tests/components/google_assistant_sdk/test_notify.py @@ -1,4 +1,5 @@ """Tests for the Google Assistant notify.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/google_domains/test_init.py b/tests/components/google_domains/test_init.py index 12f5e509736..a682d4ad090 100644 --- a/tests/components/google_domains/test_init.py +++ b/tests/components/google_domains/test_init.py @@ -1,4 +1,5 @@ """Test the Google Domains component.""" + from datetime import timedelta import pytest diff --git a/tests/components/google_generative_ai_conversation/conftest.py b/tests/components/google_generative_ai_conversation/conftest.py index 0a45a991bf8..66dfd980cf3 100644 --- a/tests/components/google_generative_ai_conversation/conftest.py +++ b/tests/components/google_generative_ai_conversation/conftest.py @@ -1,4 +1,5 @@ """Tests helpers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/google_generative_ai_conversation/test_config_flow.py b/tests/components/google_generative_ai_conversation/test_config_flow.py index 4a2478c5a7a..bff560dfb29 100644 --- a/tests/components/google_generative_ai_conversation/test_config_flow.py +++ b/tests/components/google_generative_ai_conversation/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Generative AI Conversation config flow.""" + from unittest.mock import patch from google.api_core.exceptions import ClientError diff --git a/tests/components/google_generative_ai_conversation/test_init.py b/tests/components/google_generative_ai_conversation/test_init.py index eee00fadfac..bcf0600373f 100644 --- a/tests/components/google_generative_ai_conversation/test_init.py +++ b/tests/components/google_generative_ai_conversation/test_init.py @@ -1,4 +1,5 @@ """Tests for the Google Generative AI Conversation integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from google.api_core.exceptions import ClientError diff --git a/tests/components/google_mail/conftest.py b/tests/components/google_mail/conftest.py index c3318b37f0f..947d5fe2fb1 100644 --- a/tests/components/google_mail/conftest.py +++ b/tests/components/google_mail/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the Google Mail integration.""" + from collections.abc import Awaitable, Callable, Coroutine import time from typing import Any diff --git a/tests/components/google_mail/test_config_flow.py b/tests/components/google_mail/test_config_flow.py index dbf06c26205..6456e38b3fc 100644 --- a/tests/components/google_mail/test_config_flow.py +++ b/tests/components/google_mail/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Mail config flow.""" + from unittest.mock import patch from httplib2 import Response diff --git a/tests/components/google_mail/test_notify.py b/tests/components/google_mail/test_notify.py index 1e9a174d81f..7373047b46e 100644 --- a/tests/components/google_mail/test_notify.py +++ b/tests/components/google_mail/test_notify.py @@ -1,4 +1,5 @@ """Notify tests for the Google Mail integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/google_mail/test_sensor.py b/tests/components/google_mail/test_sensor.py index 248622d3157..e0b072d4b7d 100644 --- a/tests/components/google_mail/test_sensor.py +++ b/tests/components/google_mail/test_sensor.py @@ -1,4 +1,5 @@ """Sensor tests for the Google Mail integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/google_mail/test_services.py b/tests/components/google_mail/test_services.py index caa0d887dec..ca82564dae9 100644 --- a/tests/components/google_mail/test_services.py +++ b/tests/components/google_mail/test_services.py @@ -1,4 +1,5 @@ """Services tests for the Google Mail integration.""" + from unittest.mock import patch from aiohttp.client_exceptions import ClientResponseError diff --git a/tests/components/google_pubsub/test_init.py b/tests/components/google_pubsub/test_init.py index 0a1d4741268..e397ab2c403 100644 --- a/tests/components/google_pubsub/test_init.py +++ b/tests/components/google_pubsub/test_init.py @@ -1,4 +1,5 @@ """The tests for the Google Pub/Sub component.""" + from dataclasses import dataclass from datetime import datetime import os diff --git a/tests/components/google_sheets/test_config_flow.py b/tests/components/google_sheets/test_config_flow.py index c2a12d65a97..edf4580485f 100644 --- a/tests/components/google_sheets/test_config_flow.py +++ b/tests/components/google_sheets/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Sheets config flow.""" + from collections.abc import Generator from unittest.mock import Mock, patch diff --git a/tests/components/google_tasks/test_init.py b/tests/components/google_tasks/test_init.py index b486942f70a..061bf549748 100644 --- a/tests/components/google_tasks/test_init.py +++ b/tests/components/google_tasks/test_init.py @@ -1,4 +1,5 @@ """Tests for Google Tasks.""" + from collections.abc import Awaitable, Callable import http import time diff --git a/tests/components/google_translate/conftest.py b/tests/components/google_translate/conftest.py index 34132fc5c1d..3600fae3841 100644 --- a/tests/components/google_translate/conftest.py +++ b/tests/components/google_translate/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Google Translate text-to-speech tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/google_translate/test_config_flow.py b/tests/components/google_translate/test_config_flow.py index e9a41e8eea6..a4104fc0908 100644 --- a/tests/components/google_translate/test_config_flow.py +++ b/tests/components/google_translate/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Translate text-to-speech config flow.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index fd1ddd8a4f2..1df609b0db4 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -1,4 +1,5 @@ """The tests for the Google speech platform.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/google_travel_time/conftest.py b/tests/components/google_travel_time/conftest.py index cef8dfeb65c..d5a1447f767 100644 --- a/tests/components/google_travel_time/conftest.py +++ b/tests/components/google_travel_time/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Google Time Travel tests.""" + from unittest.mock import patch from googlemaps.exceptions import ApiError, Timeout, TransportError diff --git a/tests/components/google_wifi/test_sensor.py b/tests/components/google_wifi/test_sensor.py index 3eceac64904..fcc5603fdc5 100644 --- a/tests/components/google_wifi/test_sensor.py +++ b/tests/components/google_wifi/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Google Wifi platform.""" + from datetime import datetime, timedelta from http import HTTPStatus from unittest.mock import Mock, patch diff --git a/tests/components/govee_ble/test_config_flow.py b/tests/components/govee_ble/test_config_flow.py index fee37a0a886..4b498b2618a 100644 --- a/tests/components/govee_ble/test_config_flow.py +++ b/tests/components/govee_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Govee config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/govee_ble/test_sensor.py b/tests/components/govee_ble/test_sensor.py index 55f3d293096..099fff7d413 100644 --- a/tests/components/govee_ble/test_sensor.py +++ b/tests/components/govee_ble/test_sensor.py @@ -1,4 +1,5 @@ """Test the Govee BLE sensors.""" + from datetime import timedelta import time diff --git a/tests/components/govee_light_local/conftest.py b/tests/components/govee_light_local/conftest.py index 2b3690f7011..5976d3c1b74 100644 --- a/tests/components/govee_light_local/conftest.py +++ b/tests/components/govee_light_local/conftest.py @@ -1,4 +1,5 @@ """Tests configuration for Govee Local API.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/govee_light_local/test_config_flow.py b/tests/components/govee_light_local/test_config_flow.py index 7753b40c29c..2b527c867f9 100644 --- a/tests/components/govee_light_local/test_config_flow.py +++ b/tests/components/govee_light_local/test_config_flow.py @@ -1,4 +1,5 @@ """Test Govee light local config flow.""" + from unittest.mock import AsyncMock, patch from govee_local_api import GoveeDevice diff --git a/tests/components/gpsd/conftest.py b/tests/components/gpsd/conftest.py index c2bd2b8564a..71bb3aa61bf 100644 --- a/tests/components/gpsd/conftest.py +++ b/tests/components/gpsd/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the GPSD tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/gpsd/test_config_flow.py b/tests/components/gpsd/test_config_flow.py index 0b0465b026d..81d6681dabd 100644 --- a/tests/components/gpsd/test_config_flow.py +++ b/tests/components/gpsd/test_config_flow.py @@ -1,4 +1,5 @@ """Test the GPSD config flow.""" + from unittest.mock import AsyncMock, patch from gps3.agps3threaded import GPSD_PORT as DEFAULT_PORT diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py index 3873695033e..cfe9d050c69 100644 --- a/tests/components/gpslogger/test_init.py +++ b/tests/components/gpslogger/test_init.py @@ -1,4 +1,5 @@ """The tests the for GPSLogger device tracker platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/gree/conftest.py b/tests/components/gree/conftest.py index 8ef5f7bb38f..18113e6530c 100644 --- a/tests/components/gree/conftest.py +++ b/tests/components/gree/conftest.py @@ -1,4 +1,5 @@ """Pytest module configuration.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/gree/test_bridge.py b/tests/components/gree/test_bridge.py index f40ab6525d4..37b0b0dc15e 100644 --- a/tests/components/gree/test_bridge.py +++ b/tests/components/gree/test_bridge.py @@ -1,4 +1,5 @@ """Tests for gree component.""" + from datetime import timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/gree/test_climate.py b/tests/components/gree/test_climate.py index 5b261fa266b..2b62be2b16d 100644 --- a/tests/components/gree/test_climate.py +++ b/tests/components/gree/test_climate.py @@ -1,4 +1,5 @@ """Tests for gree component.""" + from datetime import timedelta from unittest.mock import DEFAULT as DEFAULT_MOCK, AsyncMock, patch diff --git a/tests/components/gree/test_config_flow.py b/tests/components/gree/test_config_flow.py index d4a922be449..7127af6b913 100644 --- a/tests/components/gree/test_config_flow.py +++ b/tests/components/gree/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Gree Integration.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/gree/test_init.py b/tests/components/gree/test_init.py index 240b60e30d7..1da965e001b 100644 --- a/tests/components/gree/test_init.py +++ b/tests/components/gree/test_init.py @@ -1,4 +1,5 @@ """Tests for the Gree Integration.""" + from unittest.mock import patch from homeassistant.components.gree.const import DOMAIN as GREE_DOMAIN diff --git a/tests/components/gree/test_switch.py b/tests/components/gree/test_switch.py index d8160d99040..9c465a9f297 100644 --- a/tests/components/gree/test_switch.py +++ b/tests/components/gree/test_switch.py @@ -1,4 +1,5 @@ """Tests for gree component.""" + from unittest.mock import patch from greeclimate.exceptions import DeviceTimeoutError diff --git a/tests/components/greeneye_monitor/common.py b/tests/components/greeneye_monitor/common.py index e9285647f4d..40562096b53 100644 --- a/tests/components/greeneye_monitor/common.py +++ b/tests/components/greeneye_monitor/common.py @@ -1,4 +1,5 @@ """Common helpers for greeneye_monitor tests.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/greeneye_monitor/conftest.py b/tests/components/greeneye_monitor/conftest.py index 70b337430c5..d09d31d1db8 100644 --- a/tests/components/greeneye_monitor/conftest.py +++ b/tests/components/greeneye_monitor/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for testing greeneye_monitor.""" + from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/greeneye_monitor/test_sensor.py b/tests/components/greeneye_monitor/test_sensor.py index f739b8a64ca..35d515a4877 100644 --- a/tests/components/greeneye_monitor/test_sensor.py +++ b/tests/components/greeneye_monitor/test_sensor.py @@ -1,4 +1,5 @@ """Tests for greeneye_monitor sensors.""" + from unittest.mock import AsyncMock from homeassistant.components.greeneye_monitor.sensor import ( diff --git a/tests/components/group/common.py b/tests/components/group/common.py index b2c35703e6c..d24deb2f34f 100644 --- a/tests/components/group/common.py +++ b/tests/components/group/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.group import ( ATTR_ADD_ENTITIES, ATTR_ENTITIES, diff --git a/tests/components/group/test_binary_sensor.py b/tests/components/group/test_binary_sensor.py index 10c1d58d3d2..2e2b5f3bd21 100644 --- a/tests/components/group/test_binary_sensor.py +++ b/tests/components/group/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Group Binary Sensor platform.""" + from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.group import DOMAIN from homeassistant.const import ( diff --git a/tests/components/group/test_config_flow.py b/tests/components/group/test_config_flow.py index 9db70ca80d1..706d1304916 100644 --- a/tests/components/group/test_config_flow.py +++ b/tests/components/group/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Switch config flow.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 6dd1ca1a6ed..c609c705f21 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -1,4 +1,5 @@ """The tests for the Group components.""" + from __future__ import annotations from collections import OrderedDict diff --git a/tests/components/group/test_notify.py b/tests/components/group/test_notify.py index 77569c80f0f..52d049431d8 100644 --- a/tests/components/group/test_notify.py +++ b/tests/components/group/test_notify.py @@ -1,4 +1,5 @@ """The tests for the notify.group platform.""" + from unittest.mock import MagicMock, patch from homeassistant import config as hass_config diff --git a/tests/components/group/test_recorder.py b/tests/components/group/test_recorder.py index 3ca965ec998..2b0cab6c6f7 100644 --- a/tests/components/group/test_recorder.py +++ b/tests/components/group/test_recorder.py @@ -1,4 +1,5 @@ """The tests for group recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py index ea9c6e9d43d..ff834bc87d2 100644 --- a/tests/components/group/test_reproduce_state.py +++ b/tests/components/group/test_reproduce_state.py @@ -1,4 +1,5 @@ """The tests for reproduction of state.""" + from asyncio import Future from unittest.mock import ANY, patch diff --git a/tests/components/growatt_server/test_config_flow.py b/tests/components/growatt_server/test_config_flow.py index 8455495165a..5703ed02a8a 100644 --- a/tests/components/growatt_server/test_config_flow.py +++ b/tests/components/growatt_server/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Growatt server config flow.""" + from copy import deepcopy from unittest.mock import patch diff --git a/tests/components/guardian/conftest.py b/tests/components/guardian/conftest.py index f2cde0a553d..9f9b0f95e0d 100644 --- a/tests/components/guardian/conftest.py +++ b/tests/components/guardian/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for Elexa Guardian tests.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/guardian/test_config_flow.py b/tests/components/guardian/test_config_flow.py index e52d14fb6a0..3922b196e4b 100644 --- a/tests/components/guardian/test_config_flow.py +++ b/tests/components/guardian/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Elexa Guardian config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/guardian/test_diagnostics.py b/tests/components/guardian/test_diagnostics.py index ec288461661..02b620b8e01 100644 --- a/tests/components/guardian/test_diagnostics.py +++ b/tests/components/guardian/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Guardian diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.components.guardian import DOMAIN, GuardianData from homeassistant.core import HomeAssistant diff --git a/tests/components/habitica/test_config_flow.py b/tests/components/habitica/test_config_flow.py index 83202078dfe..1fcddb0f766 100644 --- a/tests/components/habitica/test_config_flow.py +++ b/tests/components/habitica/test_config_flow.py @@ -1,4 +1,5 @@ """Test the habitica config flow.""" + from unittest.mock import AsyncMock, MagicMock, patch from aiohttp import ClientResponseError diff --git a/tests/components/habitica/test_init.py b/tests/components/habitica/test_init.py index 91fa6f90e9f..9168e29f2d5 100644 --- a/tests/components/habitica/test_init.py +++ b/tests/components/habitica/test_init.py @@ -1,4 +1,5 @@ """Test the habitica module.""" + from http import HTTPStatus import pytest diff --git a/tests/components/hardkernel/test_config_flow.py b/tests/components/hardkernel/test_config_flow.py index 309c796fcc3..1965e2af8c7 100644 --- a/tests/components/hardkernel/test_config_flow.py +++ b/tests/components/hardkernel/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Hardkernel config flow.""" + from unittest.mock import patch from homeassistant.components.hardkernel.const import DOMAIN diff --git a/tests/components/hardkernel/test_hardware.py b/tests/components/hardkernel/test_hardware.py index ee2299f383c..8b57fc24d00 100644 --- a/tests/components/hardkernel/test_hardware.py +++ b/tests/components/hardkernel/test_hardware.py @@ -1,4 +1,5 @@ """Test the Hardkernel hardware platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/hardkernel/test_init.py b/tests/components/hardkernel/test_init.py index 98f4c08cc80..90717054ead 100644 --- a/tests/components/hardkernel/test_init.py +++ b/tests/components/hardkernel/test_init.py @@ -1,4 +1,5 @@ """Test the Hardkernel integration.""" + from unittest.mock import patch from homeassistant.components.hardkernel.const import DOMAIN diff --git a/tests/components/hardware/test_websocket_api.py b/tests/components/hardware/test_websocket_api.py index 98fa00486ff..fe33d1d0c09 100644 --- a/tests/components/hardware/test_websocket_api.py +++ b/tests/components/hardware/test_websocket_api.py @@ -1,4 +1,5 @@ """Test the hardware websocket API.""" + from collections import namedtuple import datetime from unittest.mock import patch diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index 9b335d18183..c7594848990 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -1,4 +1,5 @@ """Fixtures for harmony tests.""" + from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from aioharmony.const import ClientCallbackType diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index b1c9d5649bb..2451a59afa3 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Logitech Harmony Hub config flow.""" + from unittest.mock import AsyncMock, MagicMock, patch import aiohttp diff --git a/tests/components/harmony/test_init.py b/tests/components/harmony/test_init.py index f718cee109e..971983fc3b6 100644 --- a/tests/components/harmony/test_init.py +++ b/tests/components/harmony/test_init.py @@ -1,4 +1,5 @@ """Test init of Logitch Harmony Hub integration.""" + from homeassistant.components.harmony.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.core import HomeAssistant diff --git a/tests/components/harmony/test_remote.py b/tests/components/harmony/test_remote.py index b3f0d695c75..14b8bdd63cf 100644 --- a/tests/components/harmony/test_remote.py +++ b/tests/components/harmony/test_remote.py @@ -1,4 +1,5 @@ """Test the Logitech Harmony Hub remote.""" + from datetime import timedelta from aioharmony.const import SendCommandDevice diff --git a/tests/components/harmony/test_select.py b/tests/components/harmony/test_select.py index 0b775a84d08..da699be5372 100644 --- a/tests/components/harmony/test_select.py +++ b/tests/components/harmony/test_select.py @@ -1,4 +1,5 @@ """Test the Logitech Harmony Hub activity select.""" + from datetime import timedelta from homeassistant.components.harmony.const import DOMAIN diff --git a/tests/components/harmony/test_switch.py b/tests/components/harmony/test_switch.py index f843ab4deca..b5603938972 100644 --- a/tests/components/harmony/test_switch.py +++ b/tests/components/harmony/test_switch.py @@ -1,4 +1,5 @@ """Test the Logitech Harmony Hub activity switches.""" + from datetime import timedelta from homeassistant.components import automation, script diff --git a/tests/components/hassio/test_addon_manager.py b/tests/components/hassio/test_addon_manager.py index 57a6949c56d..f846de007ef 100644 --- a/tests/components/hassio/test_addon_manager.py +++ b/tests/components/hassio/test_addon_manager.py @@ -1,4 +1,5 @@ """Test the addon manager.""" + from __future__ import annotations import asyncio diff --git a/tests/components/hassio/test_addon_panel.py b/tests/components/hassio/test_addon_panel.py index 4abc4b16c9f..9b1735287c6 100644 --- a/tests/components/hassio/test_addon_panel.py +++ b/tests/components/hassio/test_addon_panel.py @@ -1,4 +1,5 @@ """Test add-on panel.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/hassio/test_auth.py b/tests/components/hassio/test_auth.py index b58d43f87ed..175d9061d56 100644 --- a/tests/components/hassio/test_auth.py +++ b/tests/components/hassio/test_auth.py @@ -1,4 +1,5 @@ """The tests for the hassio component.""" + from http import HTTPStatus from unittest.mock import Mock, patch diff --git a/tests/components/hassio/test_config_flow.py b/tests/components/hassio/test_config_flow.py index 80b403a333b..4067c9e9afe 100644 --- a/tests/components/hassio/test_config_flow.py +++ b/tests/components/hassio/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Home Assistant Supervisor config flow.""" + from unittest.mock import patch from homeassistant.components.hassio import DOMAIN diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index 0923967a480..7a7d543afb7 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -1,4 +1,5 @@ """Test config flow.""" + from http import HTTPStatus from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/hassio/test_handler.py b/tests/components/hassio/test_handler.py index 06c726360d9..42e0a174952 100644 --- a/tests/components/hassio/test_handler.py +++ b/tests/components/hassio/test_handler.py @@ -1,4 +1,5 @@ """The tests for the hassio component.""" + from __future__ import annotations from typing import Any, Literal diff --git a/tests/components/hassio/test_http.py b/tests/components/hassio/test_http.py index 4e1e7436a58..55d4d8b0365 100644 --- a/tests/components/hassio/test_http.py +++ b/tests/components/hassio/test_http.py @@ -1,4 +1,5 @@ """The tests for the hassio component.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/hassio/test_ingress.py b/tests/components/hassio/test_ingress.py index 791337079f0..f6eeecd5f90 100644 --- a/tests/components/hassio/test_ingress.py +++ b/tests/components/hassio/test_ingress.py @@ -1,4 +1,5 @@ """The tests for the hassio component.""" + from http import HTTPStatus from unittest.mock import MagicMock, patch diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index a6f94152af0..462fc845c44 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,4 +1,5 @@ """The tests for the hassio component.""" + from datetime import timedelta import os from typing import Any diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 21cd249bd53..4daa6ab0f58 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -1,4 +1,5 @@ """Test issues from supervisor issues.""" + from __future__ import annotations import os diff --git a/tests/components/hassio/test_sensor.py b/tests/components/hassio/test_sensor.py index 9dbcb5d0e5d..fbf9a9acc79 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the hassio sensors.""" + from datetime import timedelta import os from unittest.mock import patch diff --git a/tests/components/hassio/test_update.py b/tests/components/hassio/test_update.py index 48f2896cbf9..c89636cb65b 100644 --- a/tests/components/hassio/test_update.py +++ b/tests/components/hassio/test_update.py @@ -1,4 +1,5 @@ """The tests for the hassio update entities.""" + from datetime import timedelta import os from unittest.mock import patch diff --git a/tests/components/hdmi_cec/conftest.py b/tests/components/hdmi_cec/conftest.py index c5f82c04e19..0756ea639b7 100644 --- a/tests/components/hdmi_cec/conftest.py +++ b/tests/components/hdmi_cec/conftest.py @@ -1,4 +1,5 @@ """Tests for the HDMI-CEC component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/hdmi_cec/test_init.py b/tests/components/hdmi_cec/test_init.py index 7fb921225f3..b8cbf1ea8cd 100644 --- a/tests/components/hdmi_cec/test_init.py +++ b/tests/components/hdmi_cec/test_init.py @@ -1,4 +1,5 @@ """Tests for the HDMI-CEC component.""" + from datetime import timedelta from unittest.mock import ANY, PropertyMock, call, patch diff --git a/tests/components/hdmi_cec/test_media_player.py b/tests/components/hdmi_cec/test_media_player.py index 0a2f30c691a..4c2c5f42e6e 100644 --- a/tests/components/hdmi_cec/test_media_player.py +++ b/tests/components/hdmi_cec/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the HDMI-CEC media player platform.""" + from pycec.const import ( DEVICE_TYPE_NAMES, KEY_BACKWARD, diff --git a/tests/components/hdmi_cec/test_switch.py b/tests/components/hdmi_cec/test_switch.py index ccb10a2f492..d54d6cc103b 100644 --- a/tests/components/hdmi_cec/test_switch.py +++ b/tests/components/hdmi_cec/test_switch.py @@ -1,4 +1,5 @@ """Tests for the HDMI-CEC switch platform.""" + from pycec.const import POWER_OFF, POWER_ON, STATUS_PLAY, STATUS_STILL, STATUS_STOP from pycec.network import PhysicalAddress import pytest diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index ee3847604c9..785d7c1d619 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -1,4 +1,5 @@ """Configuration for HEOS tests.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py index bfb6b03c898..7f0cd6cbd5a 100644 --- a/tests/components/heos/test_config_flow.py +++ b/tests/components/heos/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Heos config flow module.""" + from unittest.mock import patch from urllib.parse import urlparse diff --git a/tests/components/heos/test_services.py b/tests/components/heos/test_services.py index 88eda68abb6..2d812eb83ab 100644 --- a/tests/components/heos/test_services.py +++ b/tests/components/heos/test_services.py @@ -1,4 +1,5 @@ """Tests for the services module.""" + from pyheos import CommandFailedError, HeosError, const import pytest diff --git a/tests/components/here_travel_time/test_config_flow.py b/tests/components/here_travel_time/test_config_flow.py index 42add4192e5..4d3797c66a0 100644 --- a/tests/components/here_travel_time/test_config_flow.py +++ b/tests/components/here_travel_time/test_config_flow.py @@ -1,4 +1,5 @@ """Test the HERE Travel Time config flow.""" + from unittest.mock import patch from here_routing import HERERoutingError, HERERoutingUnauthorizedError diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 21580c48f33..6c5cd776ff2 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -1,4 +1,5 @@ """The test for the HERE Travel Time sensor platform.""" + from datetime import timedelta from unittest.mock import MagicMock, patch diff --git a/tests/components/hisense_aehw4a1/test_init.py b/tests/components/hisense_aehw4a1/test_init.py index 3cf89173f20..ff49879d344 100644 --- a/tests/components/hisense_aehw4a1/test_init.py +++ b/tests/components/hisense_aehw4a1/test_init.py @@ -1,4 +1,5 @@ """Tests for the Hisense AEH-W4A1 init file.""" + from unittest.mock import patch from pyaehw4a1 import exceptions diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 356fbb86b01..8e120728a8b 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -1,4 +1,5 @@ """The tests the History component.""" + from datetime import timedelta from http import HTTPStatus import json diff --git a/tests/components/history/test_init_db_schema_30.py b/tests/components/history/test_init_db_schema_30.py index caf151cafe7..b0c07cf25af 100644 --- a/tests/components/history/test_init_db_schema_30.py +++ b/tests/components/history/test_init_db_schema_30.py @@ -1,4 +1,5 @@ """The tests the History component.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index c421a1b8c5c..a6422996726 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -1,4 +1,5 @@ """The test for the History Statistics sensor platform.""" + from datetime import datetime, timedelta from unittest.mock import patch diff --git a/tests/components/hive/test_config_flow.py b/tests/components/hive/test_config_flow.py index 69969b4ac0d..383e4c78ace 100644 --- a/tests/components/hive/test_config_flow.py +++ b/tests/components/hive/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Hive config flow.""" + from unittest.mock import patch from apyhiveapi.helper import hive_exceptions diff --git a/tests/components/holiday/conftest.py b/tests/components/holiday/conftest.py index d9b0d1a5788..92f46c8b238 100644 --- a/tests/components/holiday/conftest.py +++ b/tests/components/holiday/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Holiday tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/holiday/test_calendar.py b/tests/components/holiday/test_calendar.py index df0ce6d50d5..b5067a467ed 100644 --- a/tests/components/holiday/test_calendar.py +++ b/tests/components/holiday/test_calendar.py @@ -1,4 +1,5 @@ """Tests for calendar platform of Holiday integration.""" + from datetime import datetime, timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/holiday/test_config_flow.py b/tests/components/holiday/test_config_flow.py index 7dce6131616..eb21480fbb1 100644 --- a/tests/components/holiday/test_config_flow.py +++ b/tests/components/holiday/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Holiday config flow.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/home_connect/test_config_flow.py b/tests/components/home_connect/test_config_flow.py index 209100c71b2..74ca918889d 100644 --- a/tests/components/home_connect/test_config_flow.py +++ b/tests/components/home_connect/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Home Connect config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index d754c67ad49..033d414d4a9 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -1,4 +1,5 @@ """Test Home Assistant scenes.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homeassistant/triggers/test_homeassistant.py b/tests/components/homeassistant/triggers/test_homeassistant.py index 9a202bc99a1..ebe90415018 100644 --- a/tests/components/homeassistant/triggers/test_homeassistant.py +++ b/tests/components/homeassistant/triggers/test_homeassistant.py @@ -1,4 +1,5 @@ """The tests for the Event automation.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 92c8aac3eba..9357d29ac38 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -1,4 +1,5 @@ """The tests for numeric state automation.""" + from datetime import timedelta import logging from unittest.mock import patch diff --git a/tests/components/homeassistant/triggers/test_state.py b/tests/components/homeassistant/triggers/test_state.py index a8f001ff5e0..0045888f971 100644 --- a/tests/components/homeassistant/triggers/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -1,4 +1,5 @@ """The test for state automation.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/homeassistant/triggers/test_time.py b/tests/components/homeassistant/triggers/test_time.py index ab5eb383f96..340b2839ab1 100644 --- a/tests/components/homeassistant/triggers/test_time.py +++ b/tests/components/homeassistant/triggers/test_time.py @@ -1,4 +1,5 @@ """The tests for the time automation.""" + from datetime import timedelta from unittest.mock import Mock, patch diff --git a/tests/components/homeassistant/triggers/test_time_pattern.py b/tests/components/homeassistant/triggers/test_time_pattern.py index e505dd4f3f5..2d814813ed4 100644 --- a/tests/components/homeassistant/triggers/test_time_pattern.py +++ b/tests/components/homeassistant/triggers/test_time_pattern.py @@ -1,4 +1,5 @@ """The tests for the time_pattern automation.""" + from datetime import timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index c772c088505..d21d5135db5 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -1,4 +1,5 @@ """Test creating repairs from alerts.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/homeassistant_green/test_config_flow.py b/tests/components/homeassistant_green/test_config_flow.py index cfac774f77e..f0bf15aaa53 100644 --- a/tests/components/homeassistant_green/test_config_flow.py +++ b/tests/components/homeassistant_green/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Home Assistant Green config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homeassistant_green/test_hardware.py b/tests/components/homeassistant_green/test_hardware.py index c9f958b882c..ab91514b297 100644 --- a/tests/components/homeassistant_green/test_hardware.py +++ b/tests/components/homeassistant_green/test_hardware.py @@ -1,4 +1,5 @@ """Test the Home Assistant Green hardware platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homeassistant_green/test_init.py b/tests/components/homeassistant_green/test_init.py index b183a332f50..0efb449137a 100644 --- a/tests/components/homeassistant_green/test_init.py +++ b/tests/components/homeassistant_green/test_init.py @@ -1,4 +1,5 @@ """Test the Home Assistant Green integration.""" + from unittest.mock import patch from homeassistant.components.hassio import DOMAIN as HASSIO_DOMAIN diff --git a/tests/components/homeassistant_hardware/conftest.py b/tests/components/homeassistant_hardware/conftest.py index 02b468e558e..9656b3d2e60 100644 --- a/tests/components/homeassistant_hardware/conftest.py +++ b/tests/components/homeassistant_hardware/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the Home Assistant Hardware integration.""" + from collections.abc import Generator from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py index 43fcd69e4db..6d1efc79df4 100644 --- a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py +++ b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py @@ -1,4 +1,5 @@ """Test the Home Assistant Hardware silabs multiprotocol addon manager.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/homeassistant_sky_connect/conftest.py b/tests/components/homeassistant_sky_connect/conftest.py index 90dbe5af384..6ab78fe53ef 100644 --- a/tests/components/homeassistant_sky_connect/conftest.py +++ b/tests/components/homeassistant_sky_connect/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the Home Assistant SkyConnect integration.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/homeassistant_sky_connect/test_config_flow.py b/tests/components/homeassistant_sky_connect/test_config_flow.py index 36f0a259b7f..85a8f8d2668 100644 --- a/tests/components/homeassistant_sky_connect/test_config_flow.py +++ b/tests/components/homeassistant_sky_connect/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Home Assistant SkyConnect config flow.""" + from collections.abc import Generator import copy from unittest.mock import Mock, patch diff --git a/tests/components/homeassistant_sky_connect/test_hardware.py b/tests/components/homeassistant_sky_connect/test_hardware.py index ca9a7887040..079b03bbb92 100644 --- a/tests/components/homeassistant_sky_connect/test_hardware.py +++ b/tests/components/homeassistant_sky_connect/test_hardware.py @@ -1,4 +1,5 @@ """Test the Home Assistant SkyConnect hardware platform.""" + from unittest.mock import patch from homeassistant.components.homeassistant_sky_connect.const import DOMAIN diff --git a/tests/components/homeassistant_sky_connect/test_init.py b/tests/components/homeassistant_sky_connect/test_init.py index 11961c09a2d..a8602b2f65c 100644 --- a/tests/components/homeassistant_sky_connect/test_init.py +++ b/tests/components/homeassistant_sky_connect/test_init.py @@ -1,4 +1,5 @@ """Test the Home Assistant SkyConnect integration.""" + from collections.abc import Generator from typing import Any from unittest.mock import MagicMock, Mock, patch diff --git a/tests/components/homeassistant_yellow/conftest.py b/tests/components/homeassistant_yellow/conftest.py index a7d66d659f0..39ce00c59d4 100644 --- a/tests/components/homeassistant_yellow/conftest.py +++ b/tests/components/homeassistant_yellow/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the Home Assistant Yellow integration.""" + from collections.abc import Generator from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/homeassistant_yellow/test_config_flow.py b/tests/components/homeassistant_yellow/test_config_flow.py index 1b80610953f..821621d5e57 100644 --- a/tests/components/homeassistant_yellow/test_config_flow.py +++ b/tests/components/homeassistant_yellow/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Home Assistant Yellow config flow.""" + from collections.abc import Generator from unittest.mock import Mock, patch diff --git a/tests/components/homeassistant_yellow/test_hardware.py b/tests/components/homeassistant_yellow/test_hardware.py index b7843e75dcf..9d43b341abf 100644 --- a/tests/components/homeassistant_yellow/test_hardware.py +++ b/tests/components/homeassistant_yellow/test_hardware.py @@ -1,4 +1,5 @@ """Test the Home Assistant Yellow hardware platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homeassistant_yellow/test_init.py b/tests/components/homeassistant_yellow/test_init.py index f042a7bf54d..de6696a108e 100644 --- a/tests/components/homeassistant_yellow/test_init.py +++ b/tests/components/homeassistant_yellow/test_init.py @@ -1,4 +1,5 @@ """Test the Home Assistant Yellow integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index 8c6d4328065..39df14a47ef 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -1,4 +1,5 @@ """HomeKit session fixtures.""" + from contextlib import suppress import os from unittest.mock import patch diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 4b1f315c0b6..a148ab9f61c 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -2,6 +2,7 @@ This includes tests for all mock object types. """ + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index 6dff9ef896e..dd389207b8d 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -1,4 +1,5 @@ """Test the HomeKit config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py index 2f18c7a5a89..954e415082f 100644 --- a/tests/components/homekit/test_diagnostics.py +++ b/tests/components/homekit/test_diagnostics.py @@ -1,4 +1,5 @@ """Test homekit diagnostics.""" + from unittest.mock import ANY, MagicMock, patch from homeassistant.components.homekit.const import ( diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 179a0ce467f..02a39ed9258 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -1,4 +1,5 @@ """Package to test the get_accessory method.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 6c01dd9f681..da5f8e4f5e5 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,4 +1,5 @@ """Tests for the HomeKit component.""" + from __future__ import annotations import asyncio diff --git a/tests/components/homekit/test_iidmanager.py b/tests/components/homekit/test_iidmanager.py index 386e8cf8f11..39d2dda8237 100644 --- a/tests/components/homekit/test_iidmanager.py +++ b/tests/components/homekit/test_iidmanager.py @@ -1,4 +1,5 @@ """Tests for the HomeKit IID manager.""" + from typing import Any from uuid import UUID diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 1f0d9846fd1..068f13c0e54 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -1,4 +1,5 @@ """Test HomeKit initialization.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index e0c016264f2..6efd9118092 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -1,4 +1,5 @@ """Test different accessory types: Covers.""" + from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index cb47b320c04..d971b8c06d2 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -1,4 +1,5 @@ """Test different accessory types: Fans.""" + from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE from homeassistant.components.fan import ( diff --git a/tests/components/homekit/test_type_humidifiers.py b/tests/components/homekit/test_type_humidifiers.py index 8ac748db278..fdd01e05a91 100644 --- a/tests/components/homekit/test_type_humidifiers.py +++ b/tests/components/homekit/test_type_humidifiers.py @@ -1,4 +1,5 @@ """Test different accessory types: HumidifierDehumidifier.""" + from pyhap.accessory_driver import AccessoryDriver from pyhap.const import ( CATEGORY_HUMIDIFIER, diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 3bd3f1fb824..fce523262b6 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -1,4 +1,5 @@ """Test different accessory types: Lights.""" + from datetime import timedelta from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE diff --git a/tests/components/homekit/test_type_remote.py b/tests/components/homekit/test_type_remote.py index 7c66c20f17e..988950c64a8 100644 --- a/tests/components/homekit/test_type_remote.py +++ b/tests/components/homekit/test_type_remote.py @@ -1,4 +1,5 @@ """Test different accessory types: Remotes.""" + from unittest.mock import patch import pytest diff --git a/tests/components/homekit/test_type_security_systems.py b/tests/components/homekit/test_type_security_systems.py index b71e01dd280..18434a345ce 100644 --- a/tests/components/homekit/test_type_security_systems.py +++ b/tests/components/homekit/test_type_security_systems.py @@ -1,4 +1,5 @@ """Test different accessory types: Security Systems.""" + from pyhap.loader import get_loader import pytest diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index cba0bc54243..ac086b8100e 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -1,4 +1,5 @@ """Test different accessory types: Sensors.""" + from unittest.mock import patch from homeassistant.components.binary_sensor import BinarySensorDeviceClass diff --git a/tests/components/homekit/test_type_switches.py b/tests/components/homekit/test_type_switches.py index 863515c31d7..27937babc57 100644 --- a/tests/components/homekit/test_type_switches.py +++ b/tests/components/homekit/test_type_switches.py @@ -1,4 +1,5 @@ """Test different accessory types: Switches.""" + from datetime import timedelta import pytest diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index e827573363d..5bcfe1c2038 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -1,4 +1,5 @@ """Test different accessory types: Thermostats.""" + from unittest.mock import patch from pyhap.characteristic import Characteristic diff --git a/tests/components/homekit/test_type_triggers.py b/tests/components/homekit/test_type_triggers.py index 33ce01678a3..7471e0bff1c 100644 --- a/tests/components/homekit/test_type_triggers.py +++ b/tests/components/homekit/test_type_triggers.py @@ -1,4 +1,5 @@ """Test different accessory types: Triggers (Programmable Switches).""" + from unittest.mock import MagicMock from homeassistant.components.device_automation import DeviceAutomationType diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 60ee2a4d8e8..9d8f5c5417e 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -1,4 +1,5 @@ """Test HomeKit util module.""" + from unittest.mock import MagicMock, Mock, patch import pytest diff --git a/tests/components/homekit_controller/common.py b/tests/components/homekit_controller/common.py index a5219fe7018..39466cc51e4 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -1,4 +1,5 @@ """Code to support homekit_controller tests.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py b/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py index 603036c00fd..43945af2fbf 100644 --- a/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py +++ b/tests/components/homekit_controller/specific_devices/test_anker_eufycam.py @@ -1,4 +1,5 @@ """Test against characteristics captured from a eufycam.""" + from homeassistant.core import HomeAssistant from ..common import ( diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py index 338f2bc6e9f..b6848000943 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py @@ -5,6 +5,7 @@ service-label-index despite not being linked to a service-label. https://github.com/home-assistant/core/pull/39090 """ + from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE, EntityCategory from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_connectsense.py b/tests/components/homekit_controller/specific_devices/test_connectsense.py index 94a91bb0417..b3190c510fd 100644 --- a/tests/components/homekit_controller/specific_devices/test_connectsense.py +++ b/tests/components/homekit_controller/specific_devices/test_connectsense.py @@ -1,4 +1,5 @@ """Make sure that ConnectSense Smart Outlet2 / In-Wall Outlet is enumerated properly.""" + from homeassistant.components.sensor import SensorStateClass from homeassistant.const import UnitOfElectricCurrent, UnitOfEnergy, UnitOfPower from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_ecobee3.py b/tests/components/homekit_controller/specific_devices/test_ecobee3.py index 99ece418c7b..3f93ca1a896 100644 --- a/tests/components/homekit_controller/specific_devices/test_ecobee3.py +++ b/tests/components/homekit_controller/specific_devices/test_ecobee3.py @@ -2,6 +2,7 @@ https://github.com/home-assistant/core/issues/15336 """ + from typing import Any from unittest import mock diff --git a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py index f1f8b690384..61c4fd1d1da 100644 --- a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py @@ -1,4 +1,5 @@ """Tests for handling accessories on a Hue bridge via HomeKit.""" + from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE, EntityCategory from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py index baee3082106..9c6e5a6687a 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -1,4 +1,5 @@ """Make sure that existing Koogeek LS1 support isn't broken.""" + from datetime import timedelta from unittest import mock diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py b/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py index 7114d138039..0063bfc7f5b 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_sw2.py @@ -4,6 +4,7 @@ This Koogeek device has a custom power sensor that extra handling. It should have 2 entities - the actual switch and a sensor for power usage. """ + from homeassistant.components.sensor import SensorStateClass from homeassistant.const import UnitOfPower from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_netamo_doorbell.py b/tests/components/homekit_controller/specific_devices/test_netamo_doorbell.py index d65baf93884..9b85a111fc7 100644 --- a/tests/components/homekit_controller/specific_devices/test_netamo_doorbell.py +++ b/tests/components/homekit_controller/specific_devices/test_netamo_doorbell.py @@ -2,6 +2,7 @@ https://github.com/home-assistant/core/issues/44596 """ + from homeassistant.core import HomeAssistant from ..common import ( diff --git a/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py b/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py index b42a7652c1c..9bb06486e18 100644 --- a/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py +++ b/tests/components/homekit_controller/specific_devices/test_vocolinc_vp3.py @@ -1,4 +1,5 @@ """Make sure that existing VOCOlinc VP3 support isn't broken.""" + from homeassistant.components.sensor import SensorStateClass from homeassistant.const import UnitOfPower from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/test_alarm_control_panel.py b/tests/components/homekit_controller/test_alarm_control_panel.py index 19991d7cc13..a660e29ca17 100644 --- a/tests/components/homekit_controller/test_alarm_control_panel.py +++ b/tests/components/homekit_controller/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Basic checks for HomeKitalarm_control_panel.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_binary_sensor.py b/tests/components/homekit_controller/test_binary_sensor.py index 92c303cab45..3d4486bb38d 100644 --- a/tests/components/homekit_controller/test_binary_sensor.py +++ b/tests/components/homekit_controller/test_binary_sensor.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit motion sensors and contact sensors.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_button.py b/tests/components/homekit_controller/test_button.py index 57592fb7a27..0d76ac98fbe 100644 --- a/tests/components/homekit_controller/test_button.py +++ b/tests/components/homekit_controller/test_button.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit button.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_climate.py b/tests/components/homekit_controller/test_climate.py index e4fe754013a..5470c669700 100644 --- a/tests/components/homekit_controller/test_climate.py +++ b/tests/components/homekit_controller/test_climate.py @@ -1,4 +1,5 @@ """Basic checks for HomeKitclimate.""" + from aiohomekit.model.characteristics import ( ActivationStateValues, CharacteristicsTypes, diff --git a/tests/components/homekit_controller/test_cover.py b/tests/components/homekit_controller/test_cover.py index 7d004a8a428..671e9779d30 100644 --- a/tests/components/homekit_controller/test_cover.py +++ b/tests/components/homekit_controller/test_cover.py @@ -1,4 +1,5 @@ """Basic checks for HomeKitalarm_control_panel.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index 2f66a1eea26..0437958edae 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -1,4 +1,5 @@ """Test homekit_controller stateless triggers.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes import pytest diff --git a/tests/components/homekit_controller/test_event.py b/tests/components/homekit_controller/test_event.py index a836fb1c669..e139b49982a 100644 --- a/tests/components/homekit_controller/test_event.py +++ b/tests/components/homekit_controller/test_event.py @@ -1,4 +1,5 @@ """Test homekit_controller stateless triggers.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_fan.py b/tests/components/homekit_controller/test_fan.py index 938f09c453e..428d3ab7d50 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit fans.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_humidifier.py b/tests/components/homekit_controller/test_humidifier.py index 1a1db53d8dd..60c74be3949 100644 --- a/tests/components/homekit_controller/test_humidifier.py +++ b/tests/components/homekit_controller/test_humidifier.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit Humidifier/Dehumidifier.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_init.py b/tests/components/homekit_controller/test_init.py index 57d206a6025..b658b11f2cb 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -1,4 +1,5 @@ """Tests for homekit_controller init.""" + from datetime import timedelta import pathlib from unittest.mock import patch diff --git a/tests/components/homekit_controller/test_light.py b/tests/components/homekit_controller/test_light.py index c7f168b2abe..606a9e75eb1 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -1,4 +1,5 @@ """Basic checks for HomeKitSwitch.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_lock.py b/tests/components/homekit_controller/test_lock.py index 9aacda81683..db248b82b1a 100644 --- a/tests/components/homekit_controller/test_lock.py +++ b/tests/components/homekit_controller/test_lock.py @@ -1,4 +1,5 @@ """Basic checks for HomeKitLock.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_media_player.py b/tests/components/homekit_controller/test_media_player.py index 1573fccea02..62a042ff7b9 100644 --- a/tests/components/homekit_controller/test_media_player.py +++ b/tests/components/homekit_controller/test_media_player.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit motion sensors and contact sensors.""" + from aiohomekit.model.characteristics import ( CharacteristicPermissions, CharacteristicsTypes, diff --git a/tests/components/homekit_controller/test_number.py b/tests/components/homekit_controller/test_number.py index d35df281eab..96e2cbe8d4d 100644 --- a/tests/components/homekit_controller/test_number.py +++ b/tests/components/homekit_controller/test_number.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit sensor.""" + from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_select.py b/tests/components/homekit_controller/test_select.py index baae2cf8219..b00206e1b0d 100644 --- a/tests/components/homekit_controller/test_select.py +++ b/tests/components/homekit_controller/test_select.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit select entities.""" + from aiohomekit.model import Accessory from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.characteristics.const import TemperatureDisplayUnits diff --git a/tests/components/homekit_controller/test_sensor.py b/tests/components/homekit_controller/test_sensor.py index 3134605125e..8634b33fe3b 100644 --- a/tests/components/homekit_controller/test_sensor.py +++ b/tests/components/homekit_controller/test_sensor.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit sensor.""" + from unittest.mock import patch from aiohomekit.model import Transport diff --git a/tests/components/homekit_controller/test_storage.py b/tests/components/homekit_controller/test_storage.py index afab63983e2..9523dc9abb7 100644 --- a/tests/components/homekit_controller/test_storage.py +++ b/tests/components/homekit_controller/test_storage.py @@ -1,4 +1,5 @@ """Basic checks for entity map storage.""" + from typing import Any from aiohomekit.model.characteristics import CharacteristicsTypes diff --git a/tests/components/homekit_controller/test_switch.py b/tests/components/homekit_controller/test_switch.py index 5b6a77b75c9..8a6b2a65e88 100644 --- a/tests/components/homekit_controller/test_switch.py +++ b/tests/components/homekit_controller/test_switch.py @@ -1,4 +1,5 @@ """Basic checks for HomeKitSwitch.""" + from aiohomekit.model.characteristics import ( CharacteristicsTypes, InUseValues, diff --git a/tests/components/homekit_controller/test_utils.py b/tests/components/homekit_controller/test_utils.py index 57dd98669fb..703cf288f63 100644 --- a/tests/components/homekit_controller/test_utils.py +++ b/tests/components/homekit_controller/test_utils.py @@ -1,4 +1,5 @@ """Checks for basic helper utils.""" + from homeassistant.components.homekit_controller.utils import unique_id_to_iids diff --git a/tests/components/homematicip_cloud/conftest.py b/tests/components/homematicip_cloud/conftest.py index c033670efa6..af663a150ac 100644 --- a/tests/components/homematicip_cloud/conftest.py +++ b/tests/components/homematicip_cloud/conftest.py @@ -1,4 +1,5 @@ """Initializer helpers for HomematicIP fake server.""" + from unittest.mock import AsyncMock, MagicMock, Mock, patch from homematicip.aio.auth import AsyncAuth diff --git a/tests/components/homematicip_cloud/test_alarm_control_panel.py b/tests/components/homematicip_cloud/test_alarm_control_panel.py index ddd32b7dc64..05d7963cea8 100644 --- a/tests/components/homematicip_cloud/test_alarm_control_panel.py +++ b/tests/components/homematicip_cloud/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud alarm control panel.""" + from homeassistant.components.alarm_control_panel import ( DOMAIN as ALARM_CONTROL_PANEL_DOMAIN, ) diff --git a/tests/components/homematicip_cloud/test_binary_sensor.py b/tests/components/homematicip_cloud/test_binary_sensor.py index 07fcccc7479..54f8e2141d2 100644 --- a/tests/components/homematicip_cloud/test_binary_sensor.py +++ b/tests/components/homematicip_cloud/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud binary sensor.""" + from homematicip.base.enums import SmokeDetectorAlarmType, WindowState from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN diff --git a/tests/components/homematicip_cloud/test_config_flow.py b/tests/components/homematicip_cloud/test_config_flow.py index 44f923b33df..d560293dd66 100644 --- a/tests/components/homematicip_cloud/test_config_flow.py +++ b/tests/components/homematicip_cloud/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/homematicip_cloud/test_cover.py b/tests/components/homematicip_cloud/test_cover.py index 2c80c4e41a1..ee126dff936 100644 --- a/tests/components/homematicip_cloud/test_cover.py +++ b/tests/components/homematicip_cloud/test_cover.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud cover.""" + from homematicip.base.enums import DoorCommand, DoorState from homeassistant.components.cover import ( diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index a535d0ed6f0..facb12ed8ea 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -1,4 +1,5 @@ """Common tests for HomematicIP devices.""" + from unittest.mock import patch from homematicip.base.enums import EventType diff --git a/tests/components/homematicip_cloud/test_hap.py b/tests/components/homematicip_cloud/test_hap.py index 0d950968191..de47360770f 100644 --- a/tests/components/homematicip_cloud/test_hap.py +++ b/tests/components/homematicip_cloud/test_hap.py @@ -1,4 +1,5 @@ """Test HomematicIP Cloud accesspoint.""" + from unittest.mock import Mock, patch from homematicip.aio.auth import AsyncAuth diff --git a/tests/components/homematicip_cloud/test_init.py b/tests/components/homematicip_cloud/test_init.py index 5b9472d329b..d3e89e1bbb9 100644 --- a/tests/components/homematicip_cloud/test_init.py +++ b/tests/components/homematicip_cloud/test_init.py @@ -1,4 +1,5 @@ """Test HomematicIP Cloud setup process.""" + from unittest.mock import AsyncMock, Mock, patch from homematicip.base.base_connection import HmipConnectionError diff --git a/tests/components/homematicip_cloud/test_light.py b/tests/components/homematicip_cloud/test_light.py index 517978e74c0..18f002a5dbc 100644 --- a/tests/components/homematicip_cloud/test_light.py +++ b/tests/components/homematicip_cloud/test_light.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud light.""" + from homematicip.base.enums import RGBColorState from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN diff --git a/tests/components/homematicip_cloud/test_lock.py b/tests/components/homematicip_cloud/test_lock.py index 61457fd5119..f49ad42b013 100644 --- a/tests/components/homematicip_cloud/test_lock.py +++ b/tests/components/homematicip_cloud/test_lock.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud locks.""" + from unittest.mock import patch from homematicip.base.enums import LockState, MotorState diff --git a/tests/components/homematicip_cloud/test_sensor.py b/tests/components/homematicip_cloud/test_sensor.py index 908a881878b..3089bb062e5 100644 --- a/tests/components/homematicip_cloud/test_sensor.py +++ b/tests/components/homematicip_cloud/test_sensor.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud sensor.""" + from homematicip.base.enums import ValveState from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN diff --git a/tests/components/homematicip_cloud/test_switch.py b/tests/components/homematicip_cloud/test_switch.py index d8f58159e06..a249c52393d 100644 --- a/tests/components/homematicip_cloud/test_switch.py +++ b/tests/components/homematicip_cloud/test_switch.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud switch.""" + from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN from homeassistant.components.homematicip_cloud.generic_entity import ( ATTR_GROUP_MEMBER_UNREACHABLE, diff --git a/tests/components/homematicip_cloud/test_weather.py b/tests/components/homematicip_cloud/test_weather.py index b0cd0bde923..44005afd511 100644 --- a/tests/components/homematicip_cloud/test_weather.py +++ b/tests/components/homematicip_cloud/test_weather.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud weather.""" + from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN from homeassistant.components.weather import ( ATTR_WEATHER_HUMIDITY, diff --git a/tests/components/homewizard/conftest.py b/tests/components/homewizard/conftest.py index 0c24d9daebe..4f78449af82 100644 --- a/tests/components/homewizard/conftest.py +++ b/tests/components/homewizard/conftest.py @@ -1,4 +1,5 @@ """Fixtures for HomeWizard integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/homewizard/test_button.py b/tests/components/homewizard/test_button.py index b73a194c5ae..7f7ada37644 100644 --- a/tests/components/homewizard/test_button.py +++ b/tests/components/homewizard/test_button.py @@ -1,4 +1,5 @@ """Test the identify button for HomeWizard.""" + from unittest.mock import MagicMock from homewizard_energy.errors import DisabledError, RequestError diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index 5e71826b28d..5eef6978815 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -1,4 +1,5 @@ """Test the homewizard config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index e777b2d43c6..d8aeb302aa0 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -1,4 +1,5 @@ """Tests for the homewizard component.""" + from unittest.mock import MagicMock from homewizard_energy.errors import DisabledError, HomeWizardEnergyException diff --git a/tests/components/homewizard/test_number.py b/tests/components/homewizard/test_number.py index a7fb2834bd3..dade8f4eef5 100644 --- a/tests/components/homewizard/test_number.py +++ b/tests/components/homewizard/test_number.py @@ -1,4 +1,5 @@ """Test the number entity for HomeWizard.""" + from unittest.mock import MagicMock from homewizard_energy.errors import DisabledError, RequestError diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index 85c2bee709c..01aa021783f 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -1,4 +1,5 @@ """Test the switch entity for HomeWizard.""" + from unittest.mock import MagicMock from homewizard_energy import UnsupportedError diff --git a/tests/components/homeworks/conftest.py b/tests/components/homeworks/conftest.py index 273b8f1ae4b..d1366f89641 100644 --- a/tests/components/homeworks/conftest.py +++ b/tests/components/homeworks/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Lutron Homeworks Series 4 and 8 tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/homeworks/test_config_flow.py b/tests/components/homeworks/test_config_flow.py index 22a8127b135..a50e89dcc5f 100644 --- a/tests/components/homeworks/test_config_flow.py +++ b/tests/components/homeworks/test_config_flow.py @@ -1,4 +1,5 @@ """Test Lutron Homeworks Series 4 and 8 config flow.""" + from unittest.mock import ANY, MagicMock import pytest diff --git a/tests/components/honeywell/test_config_flow.py b/tests/components/honeywell/test_config_flow.py index b76eb1bf1e4..a978a14daa1 100644 --- a/tests/components/honeywell/test_config_flow.py +++ b/tests/components/honeywell/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for honeywell config flow.""" + from unittest.mock import MagicMock, patch import aiosomecomfort diff --git a/tests/components/honeywell/test_diagnostics.py b/tests/components/honeywell/test_diagnostics.py index aafc50d5545..b180bf0e5bc 100644 --- a/tests/components/honeywell/test_diagnostics.py +++ b/tests/components/honeywell/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Honeywell diagnostics.""" + from unittest.mock import MagicMock from syrupy import SnapshotAssertion diff --git a/tests/components/honeywell/test_init.py b/tests/components/honeywell/test_init.py index 8be7cfeb61a..d27428fcf65 100644 --- a/tests/components/honeywell/test_init.py +++ b/tests/components/honeywell/test_init.py @@ -1,4 +1,5 @@ """Test honeywell setup process.""" + from unittest.mock import MagicMock, create_autospec, patch import aiosomecomfort diff --git a/tests/components/honeywell/test_sensor.py b/tests/components/honeywell/test_sensor.py index b286132a40f..424764847be 100644 --- a/tests/components/honeywell/test_sensor.py +++ b/tests/components/honeywell/test_sensor.py @@ -1,4 +1,5 @@ """Test honeywell sensor.""" + from aiosomecomfort.device import Device from aiosomecomfort.location import Location import pytest diff --git a/tests/components/html5/test_notify.py b/tests/components/html5/test_notify.py index edaa6b56897..1d87603fe67 100644 --- a/tests/components/html5/test_notify.py +++ b/tests/components/html5/test_notify.py @@ -1,4 +1,5 @@ """Test HTML5 notify platform.""" + from http import HTTPStatus import json from unittest.mock import MagicMock, mock_open, patch diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index a54a697240a..de6f323bc8a 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -1,4 +1,5 @@ """The tests for the Home Assistant HTTP component.""" + from datetime import timedelta from http import HTTPStatus from ipaddress import ip_network diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index 5ab9db4e64e..103e0484fb0 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -1,4 +1,5 @@ """The tests for the Home Assistant HTTP component.""" + from http import HTTPStatus from ipaddress import ip_address import os diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py index f280c151b7d..c4fd101f733 100644 --- a/tests/components/http/test_cors.py +++ b/tests/components/http/test_cors.py @@ -1,4 +1,5 @@ """Test cors for the HTTP component.""" + from http import HTTPStatus from pathlib import Path from unittest.mock import patch diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index 3e18bdb4783..af55e0b8597 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -1,4 +1,5 @@ """Test data validator decorator.""" + from http import HTTPStatus from unittest.mock import Mock diff --git a/tests/components/http/test_forwarded.py b/tests/components/http/test_forwarded.py index 421dbaf2dfc..ce9b8198377 100644 --- a/tests/components/http/test_forwarded.py +++ b/tests/components/http/test_forwarded.py @@ -1,4 +1,5 @@ """Test real forwarded middleware.""" + from http import HTTPStatus from ipaddress import ip_network from unittest.mock import Mock, patch diff --git a/tests/components/http/test_headers.py b/tests/components/http/test_headers.py index 16b897b9f99..41c974b5239 100644 --- a/tests/components/http/test_headers.py +++ b/tests/components/http/test_headers.py @@ -1,4 +1,5 @@ """Test headers middleware.""" + from http import HTTPStatus from aiohttp import web diff --git a/tests/components/http/test_request_context.py b/tests/components/http/test_request_context.py index 6e891be1799..076780a9685 100644 --- a/tests/components/http/test_request_context.py +++ b/tests/components/http/test_request_context.py @@ -1,4 +1,5 @@ """Test request context middleware.""" + from contextvars import ContextVar from http import HTTPStatus diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index 6f8386ae36f..e3bb3ac303b 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -1,4 +1,5 @@ """Tests for Home Assistant View.""" + from decimal import Decimal from http import HTTPStatus import json diff --git a/tests/components/huawei_lte/test_button.py b/tests/components/huawei_lte/test_button.py index 982fba166c3..c99c08c436c 100644 --- a/tests/components/huawei_lte/test_button.py +++ b/tests/components/huawei_lte/test_button.py @@ -1,4 +1,5 @@ """Tests for the Huawei LTE switches.""" + from unittest.mock import MagicMock, patch from huawei_lte_api.enums.device import ControlModeEnum diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index e358920b07b..997de3c879e 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Huawei LTE config flow.""" + from typing import Any from unittest.mock import patch from urllib.parse import urlparse, urlunparse diff --git a/tests/components/huawei_lte/test_select.py b/tests/components/huawei_lte/test_select.py index c3f6ded65b6..f6c8d34c4a0 100644 --- a/tests/components/huawei_lte/test_select.py +++ b/tests/components/huawei_lte/test_select.py @@ -1,4 +1,5 @@ """Tests for the Huawei LTE selects.""" + from unittest.mock import MagicMock, patch from huawei_lte_api.enums.net import LTEBandEnum, NetworkBandEnum, NetworkModeEnum diff --git a/tests/components/huawei_lte/test_switches.py b/tests/components/huawei_lte/test_switches.py index acaffdbd0ba..288416c8c99 100644 --- a/tests/components/huawei_lte/test_switches.py +++ b/tests/components/huawei_lte/test_switches.py @@ -1,4 +1,5 @@ """Tests for the Huawei LTE switches.""" + from unittest.mock import MagicMock, patch from homeassistant.components.huawei_lte.const import DOMAIN diff --git a/tests/components/hue/test_binary_sensor.py b/tests/components/hue/test_binary_sensor.py index ab6f4ab0581..8f299a4b6a6 100644 --- a/tests/components/hue/test_binary_sensor.py +++ b/tests/components/hue/test_binary_sensor.py @@ -1,4 +1,5 @@ """Philips Hue binary_sensor platform tests for V2 bridge/api.""" + from homeassistant.core import HomeAssistant from .conftest import setup_platform diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 30d2a8c0b42..121156fa9dc 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Philips Hue config flow.""" + from ipaddress import ip_address from unittest.mock import Mock, patch diff --git a/tests/components/hue/test_device_trigger_v1.py b/tests/components/hue/test_device_trigger_v1.py index 3be150f0269..b12c3cce584 100644 --- a/tests/components/hue/test_device_trigger_v1.py +++ b/tests/components/hue/test_device_trigger_v1.py @@ -1,4 +1,5 @@ """The tests for Philips Hue device triggers for V1 bridge.""" + from pytest_unordered import unordered from homeassistant.components import automation, hue diff --git a/tests/components/hue/test_device_trigger_v2.py b/tests/components/hue/test_device_trigger_v2.py index e79fce7ab13..0a89b3263c7 100644 --- a/tests/components/hue/test_device_trigger_v2.py +++ b/tests/components/hue/test_device_trigger_v2.py @@ -1,4 +1,5 @@ """The tests for Philips Hue device triggers for V2 bridge.""" + from aiohue.v2.models.button import ButtonEvent from pytest_unordered import unordered diff --git a/tests/components/hue/test_diagnostics.py b/tests/components/hue/test_diagnostics.py index 766d3fe321c..7e64ba1ad93 100644 --- a/tests/components/hue/test_diagnostics.py +++ b/tests/components/hue/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Hue diagnostics.""" + from homeassistant.core import HomeAssistant from .conftest import setup_platform diff --git a/tests/components/hue/test_event.py b/tests/components/hue/test_event.py index 9953bb11796..b33509543e9 100644 --- a/tests/components/hue/test_event.py +++ b/tests/components/hue/test_event.py @@ -1,4 +1,5 @@ """Philips Hue Event platform tests for V2 bridge/api.""" + from homeassistant.components.event import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES from homeassistant.core import HomeAssistant diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index bdca6ee135c..8f5820baf1c 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -1,4 +1,5 @@ """Test Hue setup process.""" + from unittest.mock import AsyncMock, Mock, patch import aiohue.v2 as aiohue_v2 diff --git a/tests/components/hue/test_light_v1.py b/tests/components/hue/test_light_v1.py index 2e67eb6d0e1..fc2163326bb 100644 --- a/tests/components/hue/test_light_v1.py +++ b/tests/components/hue/test_light_v1.py @@ -1,4 +1,5 @@ """Philips Hue lights platform tests.""" + from unittest.mock import Mock import aiohue diff --git a/tests/components/hue/test_light_v2.py b/tests/components/hue/test_light_v2.py index 0c79933a246..46d22842373 100644 --- a/tests/components/hue/test_light_v2.py +++ b/tests/components/hue/test_light_v2.py @@ -1,4 +1,5 @@ """Philips Hue lights platform tests for V2 bridge/api.""" + from homeassistant.components.light import ColorMode from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/hue/test_migration.py b/tests/components/hue/test_migration.py index 0a6c24a5756..88e7de47b07 100644 --- a/tests/components/hue/test_migration.py +++ b/tests/components/hue/test_migration.py @@ -1,4 +1,5 @@ """Test Hue migration logic.""" + from unittest.mock import patch from homeassistant.components import hue diff --git a/tests/components/hue/test_scene.py b/tests/components/hue/test_scene.py index ad2d11ff6b6..5e2fd939087 100644 --- a/tests/components/hue/test_scene.py +++ b/tests/components/hue/test_scene.py @@ -1,4 +1,5 @@ """Philips Hue scene platform tests for V2 bridge/api.""" + from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/hue/test_sensor_v1.py b/tests/components/hue/test_sensor_v1.py index df8c45119df..6e620ded365 100644 --- a/tests/components/hue/test_sensor_v1.py +++ b/tests/components/hue/test_sensor_v1.py @@ -1,4 +1,5 @@ """Philips Hue sensors platform tests.""" + from unittest.mock import Mock import aiohue diff --git a/tests/components/hue/test_sensor_v2.py b/tests/components/hue/test_sensor_v2.py index b8793c99d6c..ef13ba15235 100644 --- a/tests/components/hue/test_sensor_v2.py +++ b/tests/components/hue/test_sensor_v2.py @@ -1,4 +1,5 @@ """Philips Hue sensor platform tests for V2 bridge/api.""" + from homeassistant.components import hue from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/hue/test_services.py b/tests/components/hue/test_services.py index ec1c1154d75..5b91b84a33a 100644 --- a/tests/components/hue/test_services.py +++ b/tests/components/hue/test_services.py @@ -1,4 +1,5 @@ """Test Hue services.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/hue/test_switch.py b/tests/components/hue/test_switch.py index c3384ae1e44..2e25dd715c1 100644 --- a/tests/components/hue/test_switch.py +++ b/tests/components/hue/test_switch.py @@ -1,4 +1,5 @@ """Philips Hue switch platform tests for V2 bridge/api.""" + from homeassistant.core import HomeAssistant from .conftest import setup_platform diff --git a/tests/components/huisbaasje/test_config_flow.py b/tests/components/huisbaasje/test_config_flow.py index 87b605473f4..0283f4d3fb0 100644 --- a/tests/components/huisbaasje/test_config_flow.py +++ b/tests/components/huisbaasje/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Huisbaasje config flow.""" + from unittest.mock import patch from energyflip import ( diff --git a/tests/components/huisbaasje/test_init.py b/tests/components/huisbaasje/test_init.py index 2047be40367..c465ae02490 100644 --- a/tests/components/huisbaasje/test_init.py +++ b/tests/components/huisbaasje/test_init.py @@ -1,4 +1,5 @@ """Test cases for the initialisation of the Huisbaasje integration.""" + from unittest.mock import patch from energyflip import EnergyFlipException diff --git a/tests/components/huisbaasje/test_sensor.py b/tests/components/huisbaasje/test_sensor.py index 570c5656051..d04ad8cf35f 100644 --- a/tests/components/huisbaasje/test_sensor.py +++ b/tests/components/huisbaasje/test_sensor.py @@ -1,4 +1,5 @@ """Test cases for the sensors of the Huisbaasje integration.""" + from unittest.mock import patch from homeassistant.components import huisbaasje diff --git a/tests/components/humidifier/test_init.py b/tests/components/humidifier/test_init.py index 3ef3fca8589..33ea6d14a19 100644 --- a/tests/components/humidifier/test_init.py +++ b/tests/components/humidifier/test_init.py @@ -1,4 +1,5 @@ """The tests for the humidifier component.""" + from enum import Enum from types import ModuleType from unittest.mock import MagicMock diff --git a/tests/components/humidifier/test_recorder.py b/tests/components/humidifier/test_recorder.py index 0a38ff05080..b023b8cc4d9 100644 --- a/tests/components/humidifier/test_recorder.py +++ b/tests/components/humidifier/test_recorder.py @@ -1,4 +1,5 @@ """The tests for humidifier recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py index 2eaf194ee00..b98af94870b 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Hunter Douglas Powerview config flow.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/hunterdouglas_powerview/test_scene.py b/tests/components/hunterdouglas_powerview/test_scene.py index 5f24bbc36ea..9628805d0e8 100644 --- a/tests/components/hunterdouglas_powerview/test_scene.py +++ b/tests/components/hunterdouglas_powerview/test_scene.py @@ -1,4 +1,5 @@ """Test the Hunter Douglas Powerview scene platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/husqvarna_automower/conftest.py b/tests/components/husqvarna_automower/conftest.py index 3194f1b3188..5d7cb43698b 100644 --- a/tests/components/husqvarna_automower/conftest.py +++ b/tests/components/husqvarna_automower/conftest.py @@ -1,4 +1,5 @@ """Test helpers for Husqvarna Automower.""" + from collections.abc import Generator import time from unittest.mock import AsyncMock, patch diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index 3fba90d7032..5398178e339 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -1,4 +1,5 @@ """Tests for init module.""" + from datetime import timedelta import http import time diff --git a/tests/components/husqvarna_automower/test_lawn_mower.py b/tests/components/husqvarna_automower/test_lawn_mower.py index 8c444913641..6e491fd4a28 100644 --- a/tests/components/husqvarna_automower/test_lawn_mower.py +++ b/tests/components/husqvarna_automower/test_lawn_mower.py @@ -1,4 +1,5 @@ """Tests for lawn_mower module.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/husqvarna_automower/test_sensor.py b/tests/components/husqvarna_automower/test_sensor.py index 1775caac7f8..dd12e0942c7 100644 --- a/tests/components/husqvarna_automower/test_sensor.py +++ b/tests/components/husqvarna_automower/test_sensor.py @@ -1,4 +1,5 @@ """Tests for sensor platform.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/husqvarna_automower/test_switch.py b/tests/components/husqvarna_automower/test_switch.py index c4a73fec641..22137a35323 100644 --- a/tests/components/husqvarna_automower/test_switch.py +++ b/tests/components/husqvarna_automower/test_switch.py @@ -1,4 +1,5 @@ """Tests for switch platform.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/huum/test_config_flow.py b/tests/components/huum/test_config_flow.py index 7163521b446..ce5e2cf37c0 100644 --- a/tests/components/huum/test_config_flow.py +++ b/tests/components/huum/test_config_flow.py @@ -1,4 +1,5 @@ """Test the huum config flow.""" + from unittest.mock import patch from huum.exceptions import Forbidden diff --git a/tests/components/hyperion/conftest.py b/tests/components/hyperion/conftest.py index f971fa3c767..7cbaa07ec03 100644 --- a/tests/components/hyperion/conftest.py +++ b/tests/components/hyperion/conftest.py @@ -1,2 +1,3 @@ """hyperion conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/hyperion/test_camera.py b/tests/components/hyperion/test_camera.py index e087b0fc1a5..0169759f328 100644 --- a/tests/components/hyperion/test_camera.py +++ b/tests/components/hyperion/test_camera.py @@ -1,4 +1,5 @@ """Tests for the Hyperion integration.""" + from __future__ import annotations import asyncio diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index 97b705ef731..17f635eef4f 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Hyperion config flow.""" + from __future__ import annotations import asyncio diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 0dd2ad9fc94..797107b0c34 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -1,4 +1,5 @@ """Tests for the Hyperion integration.""" + from __future__ import annotations from unittest.mock import AsyncMock, Mock, call, patch diff --git a/tests/components/hyperion/test_switch.py b/tests/components/hyperion/test_switch.py index 79b9454e29f..da458820c81 100644 --- a/tests/components/hyperion/test_switch.py +++ b/tests/components/hyperion/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Hyperion integration.""" + from datetime import timedelta from unittest.mock import AsyncMock, call, patch diff --git a/tests/components/ialarm/test_config_flow.py b/tests/components/ialarm/test_config_flow.py index c242f360f30..18fd9d03015 100644 --- a/tests/components/ialarm/test_config_flow.py +++ b/tests/components/ialarm/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Antifurto365 iAlarm config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/ialarm/test_init.py b/tests/components/ialarm/test_init.py index ba44ae7a080..3600ce62fcd 100644 --- a/tests/components/ialarm/test_init.py +++ b/tests/components/ialarm/test_init.py @@ -1,4 +1,5 @@ """Test the Antifurto365 iAlarm init.""" + from unittest.mock import Mock, patch from uuid import uuid4 diff --git a/tests/components/iaqualink/test_config_flow.py b/tests/components/iaqualink/test_config_flow.py index 7c43cf5307c..64a09e218c3 100644 --- a/tests/components/iaqualink/test_config_flow.py +++ b/tests/components/iaqualink/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for iAqualink config flow.""" + from unittest.mock import patch from iaqualink.exception import ( diff --git a/tests/components/iaqualink/test_utils.py b/tests/components/iaqualink/test_utils.py index b6aa3002f0e..c803fb48b09 100644 --- a/tests/components/iaqualink/test_utils.py +++ b/tests/components/iaqualink/test_utils.py @@ -1,4 +1,5 @@ """Tests for iAqualink integration utility functions.""" + from iaqualink.exception import AqualinkServiceException import pytest diff --git a/tests/components/ibeacon/test_config_flow.py b/tests/components/ibeacon/test_config_flow.py index 2f79474dea7..3a3e1d90d91 100644 --- a/tests/components/ibeacon/test_config_flow.py +++ b/tests/components/ibeacon/test_config_flow.py @@ -1,4 +1,5 @@ """Test the ibeacon config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/ibeacon/test_coordinator.py b/tests/components/ibeacon/test_coordinator.py index 372907307a7..f94214fa47a 100644 --- a/tests/components/ibeacon/test_coordinator.py +++ b/tests/components/ibeacon/test_coordinator.py @@ -1,4 +1,5 @@ """Test the ibeacon sensors.""" + from datetime import timedelta import time diff --git a/tests/components/ibeacon/test_device_tracker.py b/tests/components/ibeacon/test_device_tracker.py index 3b8268cee60..6a8d079ba46 100644 --- a/tests/components/ibeacon/test_device_tracker.py +++ b/tests/components/ibeacon/test_device_tracker.py @@ -1,4 +1,5 @@ """Test the ibeacon device trackers.""" + from datetime import timedelta import time from unittest.mock import patch diff --git a/tests/components/ibeacon/test_sensor.py b/tests/components/ibeacon/test_sensor.py index 30a50305d2d..fb6322162d4 100644 --- a/tests/components/ibeacon/test_sensor.py +++ b/tests/components/ibeacon/test_sensor.py @@ -1,4 +1,5 @@ """Test the ibeacon sensors.""" + from datetime import timedelta import pytest diff --git a/tests/components/icloud/conftest.py b/tests/components/icloud/conftest.py index 2437d05f575..e9d5c0e5620 100644 --- a/tests/components/icloud/conftest.py +++ b/tests/components/icloud/conftest.py @@ -1,4 +1,5 @@ """Configure iCloud tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/icloud/const.py b/tests/components/icloud/const.py index 459f18e17cc..463ae6a7da2 100644 --- a/tests/components/icloud/const.py +++ b/tests/components/icloud/const.py @@ -1,4 +1,5 @@ """Constants for the iCloud tests.""" + from homeassistant.components.icloud.const import ( CONF_GPS_ACCURACY_THRESHOLD, CONF_MAX_INTERVAL, diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py index c9e3129f865..f13a0e14595 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the iCloud config flow.""" + from unittest.mock import MagicMock, Mock, patch from pyicloud.exceptions import PyiCloudFailedLoginException diff --git a/tests/components/icloud/test_init.py b/tests/components/icloud/test_init.py index 60ab00a6262..4423fd598a4 100644 --- a/tests/components/icloud/test_init.py +++ b/tests/components/icloud/test_init.py @@ -1,4 +1,5 @@ """Tests for the iCloud config flow.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/idasen_desk/test_buttons.py b/tests/components/idasen_desk/test_buttons.py index d576b2fe580..47d37a90ece 100644 --- a/tests/components/idasen_desk/test_buttons.py +++ b/tests/components/idasen_desk/test_buttons.py @@ -1,4 +1,5 @@ """Test the IKEA Idasen Desk connection buttons.""" + from unittest.mock import MagicMock from homeassistant.core import HomeAssistant diff --git a/tests/components/idasen_desk/test_config_flow.py b/tests/components/idasen_desk/test_config_flow.py index ca585c65e4d..32ed6b89f30 100644 --- a/tests/components/idasen_desk/test_config_flow.py +++ b/tests/components/idasen_desk/test_config_flow.py @@ -1,4 +1,5 @@ """Test the IKEA Idasen Desk config flow.""" + from unittest.mock import ANY, patch from bleak.exc import BleakError diff --git a/tests/components/idasen_desk/test_cover.py b/tests/components/idasen_desk/test_cover.py index 4c8bf7806e0..3c18d604549 100644 --- a/tests/components/idasen_desk/test_cover.py +++ b/tests/components/idasen_desk/test_cover.py @@ -1,4 +1,5 @@ """Test the IKEA Idasen Desk cover.""" + from typing import Any from unittest.mock import MagicMock diff --git a/tests/components/idasen_desk/test_init.py b/tests/components/idasen_desk/test_init.py index cc8daaf98ea..5b8258c8d33 100644 --- a/tests/components/idasen_desk/test_init.py +++ b/tests/components/idasen_desk/test_init.py @@ -1,4 +1,5 @@ """Test the IKEA Idasen Desk init.""" + from unittest import mock from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/idasen_desk/test_sensors.py b/tests/components/idasen_desk/test_sensors.py index 23d7ac2447b..f56a45104eb 100644 --- a/tests/components/idasen_desk/test_sensors.py +++ b/tests/components/idasen_desk/test_sensors.py @@ -1,4 +1,5 @@ """Test the IKEA Idasen Desk sensors.""" + from unittest.mock import MagicMock from homeassistant.core import HomeAssistant diff --git a/tests/components/ifttt/test_init.py b/tests/components/ifttt/test_init.py index 8e38e683914..71bd2bc297f 100644 --- a/tests/components/ifttt/test_init.py +++ b/tests/components/ifttt/test_init.py @@ -1,4 +1,5 @@ """Test the init file of IFTTT.""" + from homeassistant import config_entries, data_entry_flow from homeassistant.components import ifttt from homeassistant.config import async_process_ha_core_config diff --git a/tests/components/image/conftest.py b/tests/components/image/conftest.py index 6c9deea852f..35c9f0a86af 100644 --- a/tests/components/image/conftest.py +++ b/tests/components/image/conftest.py @@ -1,4 +1,5 @@ """Test helpers for image.""" + from collections.abc import Generator import pytest diff --git a/tests/components/image/test_recorder.py b/tests/components/image/test_recorder.py index f0ecc43e6dc..18258d3819a 100644 --- a/tests/components/image/test_recorder.py +++ b/tests/components/image/test_recorder.py @@ -1,4 +1,5 @@ """The tests for image recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/image_processing/common.py b/tests/components/image_processing/common.py index 8522353d3f2..d0354696e8a 100644 --- a/tests/components/image_processing/common.py +++ b/tests/components/image_processing/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.image_processing import DOMAIN, SERVICE_SCAN from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL from homeassistant.core import callback diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 40b4c47a3c9..88aedc548ce 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -1,4 +1,5 @@ """The tests for the image_processing component.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/imap/test_diagnostics.py b/tests/components/imap/test_diagnostics.py index 68b6831fa5b..79d51b73401 100644 --- a/tests/components/imap/test_diagnostics.py +++ b/tests/components/imap/test_diagnostics.py @@ -1,4 +1,5 @@ """Test IMAP diagnostics.""" + from datetime import timedelta from typing import Any from unittest.mock import MagicMock diff --git a/tests/components/improv_ble/test_config_flow.py b/tests/components/improv_ble/test_config_flow.py index d5e5e0c33ee..a64a03a139a 100644 --- a/tests/components/improv_ble/test_config_flow.py +++ b/tests/components/improv_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Improv via BLE config flow.""" + from collections.abc import Callable from unittest.mock import patch diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index aa73e12a611..883b40ab1df 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -1,4 +1,5 @@ """The tests for the InfluxDB component.""" + from dataclasses import dataclass import datetime from http import HTTPStatus diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index c1aaf88932e..d69bfef1b0a 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the InfluxDB sensor.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/tests/components/inkbird/test_config_flow.py b/tests/components/inkbird/test_config_flow.py index 2824009987c..ffb25ebd093 100644 --- a/tests/components/inkbird/test_config_flow.py +++ b/tests/components/inkbird/test_config_flow.py @@ -1,4 +1,5 @@ """Test the INKBIRD config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/inkbird/test_sensor.py b/tests/components/inkbird/test_sensor.py index 7585868485c..822136b9021 100644 --- a/tests/components/inkbird/test_sensor.py +++ b/tests/components/inkbird/test_sensor.py @@ -1,4 +1,5 @@ """Test the INKBIRD config flow.""" + from homeassistant.components.inkbird.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/input_boolean/test_recorder.py b/tests/components/input_boolean/test_recorder.py index a59ae7b85c3..8f041d6c848 100644 --- a/tests/components/input_boolean/test_recorder.py +++ b/tests/components/input_boolean/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/input_boolean/test_reproduce_state.py b/tests/components/input_boolean/test_reproduce_state.py index 8d1a8827e61..b61f110d5b7 100644 --- a/tests/components/input_boolean/test_reproduce_state.py +++ b/tests/components/input_boolean/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for input boolean.""" + from homeassistant.core import HomeAssistant, State from homeassistant.helpers.state import async_reproduce_state from homeassistant.setup import async_setup_component diff --git a/tests/components/input_button/test_recorder.py b/tests/components/input_button/test_recorder.py index dd5f7530493..74023b73342 100644 --- a/tests/components/input_button/test_recorder.py +++ b/tests/components/input_button/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/input_datetime/test_recorder.py b/tests/components/input_datetime/test_recorder.py index fe96b7cfb2d..d32e8ec3471 100644 --- a/tests/components/input_datetime/test_recorder.py +++ b/tests/components/input_datetime/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 305ff74b6bf..62b95fe16b3 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -1,4 +1,5 @@ """The tests for the Input number component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/input_number/test_recorder.py b/tests/components/input_number/test_recorder.py index 4172d169deb..78f709511de 100644 --- a/tests/components/input_number/test_recorder.py +++ b/tests/components/input_number/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 3978d0cf175..431f8b7d078 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -1,4 +1,5 @@ """The tests for the Input select component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/input_select/test_recorder.py b/tests/components/input_select/test_recorder.py index f4ac98dfc39..b12fe57d431 100644 --- a/tests/components/input_select/test_recorder.py +++ b/tests/components/input_select/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index c057407a644..d98ee4f7668 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -1,4 +1,5 @@ """The tests for the Input text component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/input_text/test_recorder.py b/tests/components/input_text/test_recorder.py index 001f56a5a3e..a81160b32c7 100644 --- a/tests/components/input_text/test_recorder.py +++ b/tests/components/input_text/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recorder platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/insteon/const.py b/tests/components/insteon/const.py index 53db12acb04..c35db3b7092 100644 --- a/tests/components/insteon/const.py +++ b/tests/components/insteon/const.py @@ -1,4 +1,5 @@ """Constants used for Insteon test cases.""" + from homeassistant.components.insteon.const import ( CONF_CAT, CONF_DIM_STEPS, diff --git a/tests/components/insteon/test_config_flow.py b/tests/components/insteon/test_config_flow.py index aec60d45961..f9636488d93 100644 --- a/tests/components/insteon/test_config_flow.py +++ b/tests/components/insteon/test_config_flow.py @@ -1,4 +1,5 @@ """Test the config flow for the Insteon integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/insteon/test_lock.py b/tests/components/insteon/test_lock.py index c100acae3ce..43b50e78cb1 100644 --- a/tests/components/insteon/test_lock.py +++ b/tests/components/insteon/test_lock.py @@ -1,4 +1,5 @@ """Tests for the Insteon lock.""" + from unittest.mock import patch import pytest diff --git a/tests/components/integration/test_config_flow.py b/tests/components/integration/test_config_flow.py index c92cf70b0c2..3ccd192c4f1 100644 --- a/tests/components/integration/test_config_flow.py +++ b/tests/components/integration/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Integration - Riemann sum integral config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/integration/test_sensor.py b/tests/components/integration/test_sensor.py index 8ef9caf4928..904c31e9896 100644 --- a/tests/components/integration/test_sensor.py +++ b/tests/components/integration/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the integration sensor platform.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/intellifire/conftest.py b/tests/components/intellifire/conftest.py index 7ad41cec4e5..fa7a48ef9ac 100644 --- a/tests/components/intellifire/conftest.py +++ b/tests/components/intellifire/conftest.py @@ -1,4 +1,5 @@ """Fixtures for IntelliFire integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, Mock, patch diff --git a/tests/components/intellifire/test_config_flow.py b/tests/components/intellifire/test_config_flow.py index 1bc8cc0e06a..7f6f509a3a3 100644 --- a/tests/components/intellifire/test_config_flow.py +++ b/tests/components/intellifire/test_config_flow.py @@ -1,4 +1,5 @@ """Test the IntelliFire config flow.""" + from unittest.mock import AsyncMock, MagicMock, patch from intellifire4py.exceptions import LoginException diff --git a/tests/components/intent_script/test_init.py b/tests/components/intent_script/test_init.py index fe694607def..14e5dd62d51 100644 --- a/tests/components/intent_script/test_init.py +++ b/tests/components/intent_script/test_init.py @@ -1,4 +1,5 @@ """Test intent_script component.""" + from unittest.mock import patch from homeassistant import config as hass_config diff --git a/tests/components/ios/test_init.py b/tests/components/ios/test_init.py index 9586bd3c011..afefec1530c 100644 --- a/tests/components/ios/test_init.py +++ b/tests/components/ios/test_init.py @@ -1,4 +1,5 @@ """Tests for the iOS init file.""" + from unittest.mock import patch import pytest diff --git a/tests/components/iotawatt/conftest.py b/tests/components/iotawatt/conftest.py index f96201ba50e..f3a60e69021 100644 --- a/tests/components/iotawatt/conftest.py +++ b/tests/components/iotawatt/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for IoTaWatt.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/iotawatt/test_config_flow.py b/tests/components/iotawatt/test_config_flow.py index 06c9cce0da9..6ed781b2f5b 100644 --- a/tests/components/iotawatt/test_config_flow.py +++ b/tests/components/iotawatt/test_config_flow.py @@ -1,4 +1,5 @@ """Test the IoTawatt config flow.""" + from unittest.mock import patch import httpx diff --git a/tests/components/iotawatt/test_sensor.py b/tests/components/iotawatt/test_sensor.py index 5646115f59a..aa6180e306b 100644 --- a/tests/components/iotawatt/test_sensor.py +++ b/tests/components/iotawatt/test_sensor.py @@ -1,4 +1,5 @@ """Test setting up sensors.""" + from datetime import timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py index aff8af16bc3..4ca4abdaa2e 100644 --- a/tests/components/ipma/test_config_flow.py +++ b/tests/components/ipma/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for IPMA config flow.""" + from unittest.mock import patch from pyipma import IPMAException diff --git a/tests/components/ipma/test_init.py b/tests/components/ipma/test_init.py index 54755a7ff08..7967b97dd23 100644 --- a/tests/components/ipma/test_init.py +++ b/tests/components/ipma/test_init.py @@ -1,4 +1,5 @@ """Test the IPMA integration.""" + from unittest.mock import patch from pyipma import IPMAException diff --git a/tests/components/ipp/conftest.py b/tests/components/ipp/conftest.py index de3f1e0e73c..f650b370200 100644 --- a/tests/components/ipp/conftest.py +++ b/tests/components/ipp/conftest.py @@ -1,4 +1,5 @@ """Fixtures for IPP integration tests.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/ipp/test_init.py b/tests/components/ipp/test_init.py index f502c30068c..5742d47674d 100644 --- a/tests/components/ipp/test_init.py +++ b/tests/components/ipp/test_init.py @@ -1,4 +1,5 @@ """Tests for the IPP integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from pyipp import IPPConnectionError diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index 9673d614c10..9f0079a4e40 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the IPP sensor platform.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/iqvia/test_config_flow.py b/tests/components/iqvia/test_config_flow.py index ead163b0269..a75eed8ecd0 100644 --- a/tests/components/iqvia/test_config_flow.py +++ b/tests/components/iqvia/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the IQVIA config flow.""" + from homeassistant import data_entry_flow from homeassistant.components.iqvia import CONF_ZIP_CODE, DOMAIN from homeassistant.config_entries import SOURCE_USER diff --git a/tests/components/iqvia/test_diagnostics.py b/tests/components/iqvia/test_diagnostics.py index bde2af57447..7c445c9b3e4 100644 --- a/tests/components/iqvia/test_diagnostics.py +++ b/tests/components/iqvia/test_diagnostics.py @@ -1,4 +1,5 @@ """Test IQVIA diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/islamic_prayer_times/conftest.py b/tests/components/islamic_prayer_times/conftest.py index 63c6ad8414b..f1b4a8f675c 100644 --- a/tests/components/islamic_prayer_times/conftest.py +++ b/tests/components/islamic_prayer_times/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the islamic_prayer_times tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/islamic_prayer_times/test_config_flow.py b/tests/components/islamic_prayer_times/test_config_flow.py index 0375c788b11..41a5c3df0ac 100644 --- a/tests/components/islamic_prayer_times/test_config_flow.py +++ b/tests/components/islamic_prayer_times/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Islamic Prayer Times config flow.""" + from unittest.mock import patch from prayer_times_calculator import InvalidResponseError diff --git a/tests/components/islamic_prayer_times/test_init.py b/tests/components/islamic_prayer_times/test_init.py index 746abf27d43..33828de41cd 100644 --- a/tests/components/islamic_prayer_times/test_init.py +++ b/tests/components/islamic_prayer_times/test_init.py @@ -1,4 +1,5 @@ """Tests for Islamic Prayer Times init.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index 164ac8818fe..2e323e4f5d6 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Islamic prayer times sensor platform.""" + from unittest.mock import patch from freezegun import freeze_time diff --git a/tests/components/iss/test_config_flow.py b/tests/components/iss/test_config_flow.py index fec4d9b192c..77a61eaa770 100644 --- a/tests/components/iss/test_config_flow.py +++ b/tests/components/iss/test_config_flow.py @@ -1,4 +1,5 @@ """Test iss config flow.""" + from unittest.mock import patch from homeassistant import data_entry_flow diff --git a/tests/components/izone/test_config_flow.py b/tests/components/izone/test_config_flow.py index 479c66c8b13..7043baae11a 100644 --- a/tests/components/izone/test_config_flow.py +++ b/tests/components/izone/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for iZone.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/jellyfin/conftest.py b/tests/components/jellyfin/conftest.py index 671c9881ae0..ea46c669af7 100644 --- a/tests/components/jellyfin/conftest.py +++ b/tests/components/jellyfin/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Jellyfin integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/jellyfin/test_config_flow.py b/tests/components/jellyfin/test_config_flow.py index c59efd7efb9..23c530d7e4d 100644 --- a/tests/components/jellyfin/test_config_flow.py +++ b/tests/components/jellyfin/test_config_flow.py @@ -1,4 +1,5 @@ """Test the jellyfin config flow.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/jellyfin/test_diagnostics.py b/tests/components/jellyfin/test_diagnostics.py index b56d864eaac..bd34e3a8e31 100644 --- a/tests/components/jellyfin/test_diagnostics.py +++ b/tests/components/jellyfin/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Jellyfin diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/jellyfin/test_init.py b/tests/components/jellyfin/test_init.py index eb184592bb8..9bfe37f9874 100644 --- a/tests/components/jellyfin/test_init.py +++ b/tests/components/jellyfin/test_init.py @@ -1,4 +1,5 @@ """Tests for the Jellyfin integration.""" + from unittest.mock import MagicMock from homeassistant.components.jellyfin.const import DOMAIN diff --git a/tests/components/jellyfin/test_media_player.py b/tests/components/jellyfin/test_media_player.py index 00fe230b31f..3263639a32f 100644 --- a/tests/components/jellyfin/test_media_player.py +++ b/tests/components/jellyfin/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the Jellyfin media_player platform.""" + from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/jellyfin/test_media_source.py b/tests/components/jellyfin/test_media_source.py index e87b3c15b0b..b8bbfea00d9 100644 --- a/tests/components/jellyfin/test_media_source.py +++ b/tests/components/jellyfin/test_media_source.py @@ -1,4 +1,5 @@ """Tests for the Jellyfin media_player platform.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/jellyfin/test_sensor.py b/tests/components/jellyfin/test_sensor.py index e1377d81100..40a3e62a6c0 100644 --- a/tests/components/jellyfin/test_sensor.py +++ b/tests/components/jellyfin/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Jellyfin sensor platform.""" + from unittest.mock import MagicMock from homeassistant.components.jellyfin.const import DOMAIN diff --git a/tests/components/jewish_calendar/test_binary_sensor.py b/tests/components/jewish_calendar/test_binary_sensor.py index d14ae0faad2..bced831462a 100644 --- a/tests/components/jewish_calendar/test_binary_sensor.py +++ b/tests/components/jewish_calendar/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Jewish calendar binary sensors.""" + from datetime import datetime as dt, timedelta import pytest diff --git a/tests/components/jewish_calendar/test_sensor.py b/tests/components/jewish_calendar/test_sensor.py index 0f2912e9de3..d9f43236965 100644 --- a/tests/components/jewish_calendar/test_sensor.py +++ b/tests/components/jewish_calendar/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Jewish calendar sensors.""" + from datetime import datetime as dt, timedelta import pytest diff --git a/tests/components/juicenet/test_config_flow.py b/tests/components/juicenet/test_config_flow.py index 6adc841862e..2a2d55549cd 100644 --- a/tests/components/juicenet/test_config_flow.py +++ b/tests/components/juicenet/test_config_flow.py @@ -1,4 +1,5 @@ """Test the JuiceNet config flow.""" + from unittest.mock import MagicMock, patch import aiohttp diff --git a/tests/components/justnimbus/test_config_flow.py b/tests/components/justnimbus/test_config_flow.py index 8db8dd09b23..8140168751c 100644 --- a/tests/components/justnimbus/test_config_flow.py +++ b/tests/components/justnimbus/test_config_flow.py @@ -1,4 +1,5 @@ """Test the JustNimbus config flow.""" + from unittest.mock import patch from justnimbus.exceptions import InvalidClientID, JustNimbusError diff --git a/tests/components/justnimbus/test_init.py b/tests/components/justnimbus/test_init.py index 223e36d2bbc..fb4a40acb9b 100644 --- a/tests/components/justnimbus/test_init.py +++ b/tests/components/justnimbus/test_init.py @@ -1,4 +1,5 @@ """Tests for JustNimbus initialization.""" + from homeassistant.components.justnimbus.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/kegtron/test_config_flow.py b/tests/components/kegtron/test_config_flow.py index ccc4774e3df..4e21dc238bc 100644 --- a/tests/components/kegtron/test_config_flow.py +++ b/tests/components/kegtron/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Kegtron config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/kegtron/test_sensor.py b/tests/components/kegtron/test_sensor.py index 9825df00cc3..bbae920afff 100644 --- a/tests/components/kegtron/test_sensor.py +++ b/tests/components/kegtron/test_sensor.py @@ -1,4 +1,5 @@ """Test the Kegtron sensors.""" + from homeassistant.components.kegtron.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/keymitt_ble/test_config_flow.py b/tests/components/keymitt_ble/test_config_flow.py index 3bf2dcc954c..d2c76c14c6d 100644 --- a/tests/components/keymitt_ble/test_config_flow.py +++ b/tests/components/keymitt_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the MicroBot config flow.""" + from unittest.mock import ANY, AsyncMock, patch from homeassistant.config_entries import SOURCE_BLUETOOTH, SOURCE_USER diff --git a/tests/components/kira/test_remote.py b/tests/components/kira/test_remote.py index 105d457bf89..94d0bb9d818 100644 --- a/tests/components/kira/test_remote.py +++ b/tests/components/kira/test_remote.py @@ -1,4 +1,5 @@ """The tests for Kira sensor platform.""" + from unittest.mock import MagicMock from homeassistant.components.kira import remote as kira diff --git a/tests/components/kira/test_sensor.py b/tests/components/kira/test_sensor.py index fec5f982f61..d1eae78c788 100644 --- a/tests/components/kira/test_sensor.py +++ b/tests/components/kira/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Kira sensor platform.""" + from unittest.mock import MagicMock, patch from homeassistant.components.kira import sensor as kira diff --git a/tests/components/kitchen_sink/test_config_flow.py b/tests/components/kitchen_sink/test_config_flow.py index e157c3e5d0a..86c1698669e 100644 --- a/tests/components/kitchen_sink/test_config_flow.py +++ b/tests/components/kitchen_sink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Everything but the Kitchen Sink config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow, setup diff --git a/tests/components/kitchen_sink/test_image.py b/tests/components/kitchen_sink/test_image.py index 4c64bd77eb2..d85530ecad6 100644 --- a/tests/components/kitchen_sink/test_image.py +++ b/tests/components/kitchen_sink/test_image.py @@ -1,4 +1,5 @@ """The tests for the kitchen_sink image platform.""" + from http import HTTPStatus from pathlib import Path from unittest.mock import patch diff --git a/tests/components/kitchen_sink/test_lawn_mower.py b/tests/components/kitchen_sink/test_lawn_mower.py index efd1b7485ab..48914ab5a46 100644 --- a/tests/components/kitchen_sink/test_lawn_mower.py +++ b/tests/components/kitchen_sink/test_lawn_mower.py @@ -1,4 +1,5 @@ """The tests for the kitchen_sink lawn mower platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/kitchen_sink/test_lock.py b/tests/components/kitchen_sink/test_lock.py index a74c9a19a23..ad5e9b7515d 100644 --- a/tests/components/kitchen_sink/test_lock.py +++ b/tests/components/kitchen_sink/test_lock.py @@ -1,4 +1,5 @@ """The tests for the kitchen_sink lock platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/kitchen_sink/test_sensor.py b/tests/components/kitchen_sink/test_sensor.py index 8d3f611f15d..c4b5f03499e 100644 --- a/tests/components/kitchen_sink/test_sensor.py +++ b/tests/components/kitchen_sink/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the kitchen_sink sensor platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/kmtronic/conftest.py b/tests/components/kmtronic/conftest.py index 4310f99242e..98205288aa3 100644 --- a/tests/components/kmtronic/conftest.py +++ b/tests/components/kmtronic/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for kmtronic tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/kmtronic/test_config_flow.py b/tests/components/kmtronic/test_config_flow.py index ba8f2f5b87e..222bb8bead2 100644 --- a/tests/components/kmtronic/test_config_flow.py +++ b/tests/components/kmtronic/test_config_flow.py @@ -1,4 +1,5 @@ """Test the kmtronic config flow.""" + from http import HTTPStatus from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/kmtronic/test_switch.py b/tests/components/kmtronic/test_switch.py index fa60dddf1cd..a5b0e7a4cba 100644 --- a/tests/components/kmtronic/test_switch.py +++ b/tests/components/kmtronic/test_switch.py @@ -1,4 +1,5 @@ """The tests for the KMtronic switch platform.""" + from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/knx/conftest.py b/tests/components/knx/conftest.py index f2feaac2f08..bd724029516 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -1,4 +1,5 @@ """Conftest for the KNX integration.""" + from __future__ import annotations import asyncio diff --git a/tests/components/knx/test_binary_sensor.py b/tests/components/knx/test_binary_sensor.py index aace7a0224c..b9216aa149a 100644 --- a/tests/components/knx/test_binary_sensor.py +++ b/tests/components/knx/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test KNX binary sensor.""" + from datetime import timedelta from homeassistant.components.knx.const import CONF_STATE_ADDRESS, CONF_SYNC_STATE diff --git a/tests/components/knx/test_button.py b/tests/components/knx/test_button.py index 3dedea7d8d4..613208d5595 100644 --- a/tests/components/knx/test_button.py +++ b/tests/components/knx/test_button.py @@ -1,4 +1,5 @@ """Test KNX button.""" + from datetime import timedelta import logging diff --git a/tests/components/knx/test_config_flow.py b/tests/components/knx/test_config_flow.py index 0f2d8e56050..717d362265e 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -1,4 +1,5 @@ """Test the KNX config flow.""" + from contextlib import contextmanager from unittest.mock import Mock, patch diff --git a/tests/components/knx/test_date.py b/tests/components/knx/test_date.py index bfde519f3c0..d3b1ff2058e 100644 --- a/tests/components/knx/test_date.py +++ b/tests/components/knx/test_date.py @@ -1,4 +1,5 @@ """Test KNX date.""" + from homeassistant.components.date import ATTR_DATE, DOMAIN, SERVICE_SET_VALUE from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS from homeassistant.components.knx.schema import DateSchema diff --git a/tests/components/knx/test_datetime.py b/tests/components/knx/test_datetime.py index f9d9f039367..e2dcfc8d112 100644 --- a/tests/components/knx/test_datetime.py +++ b/tests/components/knx/test_datetime.py @@ -1,4 +1,5 @@ """Test KNX date.""" + from homeassistant.components.datetime import ATTR_DATETIME, DOMAIN, SERVICE_SET_VALUE from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS from homeassistant.components.knx.schema import DateTimeSchema diff --git a/tests/components/knx/test_expose.py b/tests/components/knx/test_expose.py index 4359c54164a..0eea78d85b7 100644 --- a/tests/components/knx/test_expose.py +++ b/tests/components/knx/test_expose.py @@ -1,4 +1,5 @@ """Test KNX expose.""" + from datetime import timedelta import time from unittest.mock import patch diff --git a/tests/components/knx/test_fan.py b/tests/components/knx/test_fan.py index 3e89aea7201..39cb851af51 100644 --- a/tests/components/knx/test_fan.py +++ b/tests/components/knx/test_fan.py @@ -1,4 +1,5 @@ """Test KNX fan.""" + from homeassistant.components.knx.const import KNX_ADDRESS from homeassistant.components.knx.schema import FanSchema from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON diff --git a/tests/components/knx/test_init.py b/tests/components/knx/test_init.py index a5d3d0f3263..5a6770dc92e 100644 --- a/tests/components/knx/test_init.py +++ b/tests/components/knx/test_init.py @@ -1,4 +1,5 @@ """Test KNX init.""" + from unittest.mock import patch import pytest diff --git a/tests/components/knx/test_interface_device.py b/tests/components/knx/test_interface_device.py index 12ae0ac7d0e..c857022750c 100644 --- a/tests/components/knx/test_interface_device.py +++ b/tests/components/knx/test_interface_device.py @@ -1,4 +1,5 @@ """Test KNX scene.""" + from unittest.mock import patch from xknx.core import XknxConnectionState, XknxConnectionType diff --git a/tests/components/knx/test_light.py b/tests/components/knx/test_light.py index 1f2f23e9cca..a14d1bb32ae 100644 --- a/tests/components/knx/test_light.py +++ b/tests/components/knx/test_light.py @@ -1,4 +1,5 @@ """Test KNX light.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/knx/test_sensor.py b/tests/components/knx/test_sensor.py index 10178324c93..22d9993b58f 100644 --- a/tests/components/knx/test_sensor.py +++ b/tests/components/knx/test_sensor.py @@ -1,4 +1,5 @@ """Test KNX sensor.""" + from homeassistant.components.knx.const import CONF_STATE_ADDRESS, CONF_SYNC_STATE from homeassistant.components.knx.schema import SensorSchema from homeassistant.const import CONF_NAME, CONF_TYPE, STATE_UNKNOWN diff --git a/tests/components/knx/test_services.py b/tests/components/knx/test_services.py index 30b297218cc..0bfb2141ab8 100644 --- a/tests/components/knx/test_services.py +++ b/tests/components/knx/test_services.py @@ -1,4 +1,5 @@ """Test KNX services.""" + from unittest.mock import patch import pytest diff --git a/tests/components/knx/test_switch.py b/tests/components/knx/test_switch.py index d68970537ab..8dce4cf9c27 100644 --- a/tests/components/knx/test_switch.py +++ b/tests/components/knx/test_switch.py @@ -1,4 +1,5 @@ """Test KNX switch.""" + from homeassistant.components.knx.const import ( CONF_RESPOND_TO_READ, CONF_STATE_ADDRESS, diff --git a/tests/components/knx/test_telegrams.py b/tests/components/knx/test_telegrams.py index be24b3cd3ec..844fc073d61 100644 --- a/tests/components/knx/test_telegrams.py +++ b/tests/components/knx/test_telegrams.py @@ -1,4 +1,5 @@ """KNX Telegrams Tests.""" + from copy import copy from datetime import datetime from typing import Any diff --git a/tests/components/knx/test_text.py b/tests/components/knx/test_text.py index 77f96100b89..e50f3056979 100644 --- a/tests/components/knx/test_text.py +++ b/tests/components/knx/test_text.py @@ -1,4 +1,5 @@ """Test KNX number.""" + from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS from homeassistant.components.knx.schema import TextSchema from homeassistant.const import CONF_NAME diff --git a/tests/components/knx/test_time.py b/tests/components/knx/test_time.py index 25a22fe8146..9dc4c401ed8 100644 --- a/tests/components/knx/test_time.py +++ b/tests/components/knx/test_time.py @@ -1,4 +1,5 @@ """Test KNX time.""" + from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS from homeassistant.components.knx.schema import TimeSchema from homeassistant.components.time import ATTR_TIME, DOMAIN, SERVICE_SET_VALUE diff --git a/tests/components/knx/test_weather.py b/tests/components/knx/test_weather.py index 8aaf4fa4338..0adcc309252 100644 --- a/tests/components/knx/test_weather.py +++ b/tests/components/knx/test_weather.py @@ -1,4 +1,5 @@ """Test KNX weather.""" + from homeassistant.components.knx.schema import WeatherSchema from homeassistant.components.weather import ( ATTR_CONDITION_EXCEPTIONAL, diff --git a/tests/components/knx/test_websocket.py b/tests/components/knx/test_websocket.py index 5e5d46af4a6..f36d5bb5f7a 100644 --- a/tests/components/knx/test_websocket.py +++ b/tests/components/knx/test_websocket.py @@ -1,4 +1,5 @@ """KNX Websocket Tests.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/kodi/test_config_flow.py b/tests/components/kodi/test_config_flow.py index 419254bd738..83795978522 100644 --- a/tests/components/kodi/test_config_flow.py +++ b/tests/components/kodi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Kodi config flow.""" + from unittest.mock import AsyncMock, PropertyMock, patch import pytest diff --git a/tests/components/kodi/test_init.py b/tests/components/kodi/test_init.py index 9c6d67ff120..8b9c5efbaf9 100644 --- a/tests/components/kodi/test_init.py +++ b/tests/components/kodi/test_init.py @@ -1,4 +1,5 @@ """Test the Kodi integration init.""" + from unittest.mock import patch from homeassistant.components.kodi.const import DOMAIN diff --git a/tests/components/kodi/util.py b/tests/components/kodi/util.py index 2b9d819c244..dba0822b1d8 100644 --- a/tests/components/kodi/util.py +++ b/tests/components/kodi/util.py @@ -1,4 +1,5 @@ """Test the Kodi config flow.""" + from ipaddress import ip_address from homeassistant.components import zeroconf diff --git a/tests/components/konnected/test_config_flow.py b/tests/components/konnected/test_config_flow.py index a6dcce08889..c46e115d159 100644 --- a/tests/components/konnected/test_config_flow.py +++ b/tests/components/konnected/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Konnected Alarm Panel config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/konnected/test_init.py b/tests/components/konnected/test_init.py index 658f1053f93..1a2da88624d 100644 --- a/tests/components/konnected/test_init.py +++ b/tests/components/konnected/test_init.py @@ -1,4 +1,5 @@ """Test Konnected setup process.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/konnected/test_panel.py b/tests/components/konnected/test_panel.py index 00b4617c062..64cc414cdd3 100644 --- a/tests/components/konnected/test_panel.py +++ b/tests/components/konnected/test_panel.py @@ -1,4 +1,5 @@ """Test Konnected setup process.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/kostal_plenticore/conftest.py b/tests/components/kostal_plenticore/conftest.py index a83d9fd5e17..6c97b65554d 100644 --- a/tests/components/kostal_plenticore/conftest.py +++ b/tests/components/kostal_plenticore/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Kostal Plenticore tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/kostal_plenticore/test_config_flow.py b/tests/components/kostal_plenticore/test_config_flow.py index d832dbcad47..41acfb1d136 100644 --- a/tests/components/kostal_plenticore/test_config_flow.py +++ b/tests/components/kostal_plenticore/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Kostal Plenticore Solar Inverter config flow.""" + from collections.abc import Generator from unittest.mock import ANY, AsyncMock, MagicMock, patch diff --git a/tests/components/kostal_plenticore/test_diagnostics.py b/tests/components/kostal_plenticore/test_diagnostics.py index d509a323e6a..57d1bb50bba 100644 --- a/tests/components/kostal_plenticore/test_diagnostics.py +++ b/tests/components/kostal_plenticore/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Kostal Plenticore diagnostics.""" + from pykoplenti import SettingsData from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/kostal_plenticore/test_number.py b/tests/components/kostal_plenticore/test_number.py index fc7d9f213fe..41e3a6c0b6c 100644 --- a/tests/components/kostal_plenticore/test_number.py +++ b/tests/components/kostal_plenticore/test_number.py @@ -1,4 +1,5 @@ """Test Kostal Plenticore number.""" + from collections.abc import Generator from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/kostal_plenticore/test_select.py b/tests/components/kostal_plenticore/test_select.py index 9af2589af9b..121300457fe 100644 --- a/tests/components/kostal_plenticore/test_select.py +++ b/tests/components/kostal_plenticore/test_select.py @@ -1,4 +1,5 @@ """Test the Kostal Plenticore Solar Inverter select platform.""" + from pykoplenti import SettingsData from homeassistant.components.kostal_plenticore.helper import Plenticore diff --git a/tests/components/kraken/conftest.py b/tests/components/kraken/conftest.py index f34dedc4df9..e75122e7f0e 100644 --- a/tests/components/kraken/conftest.py +++ b/tests/components/kraken/conftest.py @@ -1,4 +1,5 @@ """Provide common pytest fixtures for kraken tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/kraken/test_config_flow.py b/tests/components/kraken/test_config_flow.py index 74767a9496a..3d04e3183c3 100644 --- a/tests/components/kraken/test_config_flow.py +++ b/tests/components/kraken/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the kraken config_flow.""" + from unittest.mock import patch from homeassistant.components.kraken.const import CONF_TRACKED_ASSET_PAIRS, DOMAIN diff --git a/tests/components/kraken/test_init.py b/tests/components/kraken/test_init.py index 44e809eb815..2564fb97b26 100644 --- a/tests/components/kraken/test_init.py +++ b/tests/components/kraken/test_init.py @@ -1,4 +1,5 @@ """Tests for the kraken integration.""" + from unittest.mock import patch from pykrakenapi.pykrakenapi import CallRateLimitError, KrakenAPIError diff --git a/tests/components/kraken/test_sensor.py b/tests/components/kraken/test_sensor.py index 791b70c1283..398177070df 100644 --- a/tests/components/kraken/test_sensor.py +++ b/tests/components/kraken/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the kraken sensor platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/kulersky/test_config_flow.py b/tests/components/kulersky/test_config_flow.py index a09fc78797b..5ff3f51f1ad 100644 --- a/tests/components/kulersky/test_config_flow.py +++ b/tests/components/kulersky/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Kuler Sky config flow.""" + from unittest.mock import MagicMock, patch import pykulersky diff --git a/tests/components/kulersky/test_light.py b/tests/components/kulersky/test_light.py index b9cad7c5f9c..90f40d327e4 100644 --- a/tests/components/kulersky/test_light.py +++ b/tests/components/kulersky/test_light.py @@ -1,4 +1,5 @@ """Test the Kuler Sky lights.""" + from unittest.mock import MagicMock, patch import pykulersky From 38adfbf1a39758a02e97693e6ab7388e4b86a06c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:50:25 +0100 Subject: [PATCH 0545/1691] Add empty line after module docstring [tests a-e] (#112708) --- tests/components/abode/common.py | 1 + tests/components/abode/conftest.py | 1 + tests/components/abode/test_alarm_control_panel.py | 1 + tests/components/abode/test_binary_sensor.py | 1 + tests/components/abode/test_camera.py | 1 + tests/components/abode/test_config_flow.py | 1 + tests/components/abode/test_cover.py | 1 + tests/components/abode/test_init.py | 1 + tests/components/abode/test_light.py | 1 + tests/components/abode/test_lock.py | 1 + tests/components/abode/test_sensor.py | 1 + tests/components/abode/test_switch.py | 1 + tests/components/accuweather/test_config_flow.py | 1 + tests/components/accuweather/test_diagnostics.py | 1 + tests/components/accuweather/test_init.py | 1 + tests/components/accuweather/test_sensor.py | 1 + tests/components/accuweather/test_weather.py | 1 + tests/components/acmeda/test_config_flow.py | 1 + tests/components/adax/test_config_flow.py | 1 + tests/components/advantage_air/conftest.py | 1 + tests/components/advantage_air/test_binary_sensor.py | 1 + tests/components/advantage_air/test_config_flow.py | 1 + tests/components/advantage_air/test_cover.py | 1 + tests/components/advantage_air/test_diagnostics.py | 1 + tests/components/advantage_air/test_init.py | 1 + tests/components/advantage_air/test_sensor.py | 1 + tests/components/aemet/conftest.py | 1 + tests/components/aemet/test_config_flow.py | 1 + tests/components/aemet/test_coordinator.py | 1 + tests/components/aemet/test_init.py | 1 + tests/components/aemet/util.py | 1 + tests/components/aftership/conftest.py | 1 + tests/components/aftership/test_config_flow.py | 1 + tests/components/agent_dvr/conftest.py | 1 + tests/components/agent_dvr/test_init.py | 1 + tests/components/airly/test_config_flow.py | 1 + tests/components/airly/test_init.py | 1 + tests/components/airly/test_sensor.py | 1 + tests/components/airnow/test_config_flow.py | 1 + tests/components/airnow/test_diagnostics.py | 1 + tests/components/airq/conftest.py | 1 + tests/components/airq/test_config_flow.py | 1 + tests/components/airthings/test_config_flow.py | 1 + tests/components/airthings_ble/test_config_flow.py | 1 + tests/components/airtouch4/test_config_flow.py | 1 + tests/components/airtouch5/conftest.py | 1 + tests/components/airtouch5/test_config_flow.py | 1 + tests/components/airvisual/conftest.py | 1 + tests/components/airvisual/test_config_flow.py | 1 + tests/components/airvisual/test_diagnostics.py | 1 + tests/components/airvisual/test_init.py | 1 + tests/components/airvisual_pro/conftest.py | 1 + tests/components/airvisual_pro/test_config_flow.py | 1 + tests/components/airvisual_pro/test_diagnostics.py | 1 + tests/components/airzone/test_water_heater.py | 1 + tests/components/airzone_cloud/test_climate.py | 1 + tests/components/aladdin_connect/conftest.py | 1 + tests/components/aladdin_connect/test_config_flow.py | 1 + tests/components/aladdin_connect/test_cover.py | 1 + tests/components/aladdin_connect/test_diagnostics.py | 1 + tests/components/aladdin_connect/test_init.py | 1 + tests/components/aladdin_connect/test_model.py | 1 + tests/components/aladdin_connect/test_sensor.py | 1 + tests/components/alarm_control_panel/common.py | 1 + tests/components/alarm_control_panel/test_device_trigger.py | 1 + tests/components/alarmdecoder/test_config_flow.py | 1 + tests/components/alexa/test_auth.py | 1 + tests/components/alexa/test_capabilities.py | 1 + tests/components/alexa/test_common.py | 1 + tests/components/alexa/test_entities.py | 1 + tests/components/alexa/test_init.py | 1 + tests/components/alexa/test_intent.py | 1 + tests/components/alexa/test_smart_home.py | 1 + tests/components/alexa/test_smart_home_http.py | 1 + tests/components/amberelectric/conftest.py | 1 + tests/components/amberelectric/test_binary_sensor.py | 1 + tests/components/amberelectric/test_coordinator.py | 1 + tests/components/amberelectric/test_sensor.py | 1 + tests/components/ambiclimate/test_config_flow.py | 1 + tests/components/ambient_station/test_config_flow.py | 1 + tests/components/ambient_station/test_diagnostics.py | 1 + tests/components/analytics/test_analytics.py | 1 + tests/components/analytics/test_init.py | 1 + tests/components/analytics_insights/conftest.py | 1 + tests/components/analytics_insights/test_config_flow.py | 1 + tests/components/analytics_insights/test_init.py | 1 + tests/components/analytics_insights/test_sensor.py | 1 + tests/components/android_ip_webcam/conftest.py | 1 + tests/components/android_ip_webcam/test_config_flow.py | 1 + tests/components/androidtv/patchers.py | 1 + tests/components/androidtv/test_config_flow.py | 1 + tests/components/androidtv/test_media_player.py | 1 + tests/components/androidtv_remote/conftest.py | 1 + tests/components/androidtv_remote/test_config_flow.py | 1 + tests/components/androidtv_remote/test_diagnostics.py | 1 + tests/components/androidtv_remote/test_init.py | 1 + tests/components/androidtv_remote/test_media_player.py | 1 + tests/components/androidtv_remote/test_remote.py | 1 + tests/components/anova/conftest.py | 1 + tests/components/anthemav/conftest.py | 1 + tests/components/anthemav/test_config_flow.py | 1 + tests/components/anthemav/test_init.py | 1 + tests/components/anthemav/test_media_player.py | 1 + tests/components/aosmith/conftest.py | 1 + tests/components/aosmith/test_config_flow.py | 1 + tests/components/apache_kafka/test_init.py | 1 + tests/components/apcupsd/test_binary_sensor.py | 1 + tests/components/apcupsd/test_config_flow.py | 1 + tests/components/apple_tv/test_config_flow.py | 1 + tests/components/apple_tv/test_remote.py | 1 + tests/components/application_credentials/test_init.py | 1 + tests/components/apprise/test_notify.py | 1 + tests/components/aprs/test_device_tracker.py | 1 + tests/components/aranet/test_config_flow.py | 1 + tests/components/aranet/test_sensor.py | 1 + tests/components/arcam_fmj/conftest.py | 1 + tests/components/arcam_fmj/test_config_flow.py | 1 + tests/components/arcam_fmj/test_media_player.py | 1 + tests/components/aseko_pool_live/test_config_flow.py | 1 + tests/components/assist_pipeline/conftest.py | 1 + tests/components/assist_pipeline/test_logbook.py | 1 + tests/components/assist_pipeline/test_pipeline.py | 1 + tests/components/assist_pipeline/test_ring_buffer.py | 1 + tests/components/asterisk_mbox/test_init.py | 1 + tests/components/asuswrt/test_config_flow.py | 1 + tests/components/asuswrt/test_sensor.py | 1 + tests/components/atag/test_climate.py | 1 + tests/components/atag/test_config_flow.py | 1 + tests/components/atag/test_sensors.py | 1 + tests/components/atag/test_water_heater.py | 1 + tests/components/august/conftest.py | 1 + tests/components/august/mocks.py | 1 + tests/components/august/test_button.py | 1 + tests/components/august/test_camera.py | 1 + tests/components/august/test_config_flow.py | 1 + tests/components/august/test_diagnostics.py | 1 + tests/components/august/test_gateway.py | 1 + tests/components/august/test_init.py | 1 + tests/components/august/test_sensor.py | 1 + tests/components/aurora/test_config_flow.py | 1 + tests/components/aurora_abb_powerone/test_config_flow.py | 1 + tests/components/aurora_abb_powerone/test_init.py | 1 + tests/components/aurora_abb_powerone/test_sensor.py | 1 + tests/components/aussie_broadband/common.py | 1 + tests/components/aussie_broadband/test_config_flow.py | 1 + tests/components/aussie_broadband/test_init.py | 1 + tests/components/aussie_broadband/test_sensor.py | 1 + tests/components/auth/test_init.py | 1 + tests/components/auth/test_init_link_user.py | 1 + tests/components/auth/test_login_flow.py | 1 + tests/components/auth/test_mfa_setup_flow.py | 1 + tests/components/automation/test_logbook.py | 1 + tests/components/automation/test_recorder.py | 1 + tests/components/awair/test_config_flow.py | 1 + tests/components/awair/test_init.py | 1 + tests/components/awair/test_sensor.py | 1 + tests/components/axis/conftest.py | 1 + tests/components/axis/test_config_flow.py | 1 + tests/components/axis/test_hub.py | 1 + tests/components/axis/test_init.py | 1 + tests/components/axis/test_light.py | 1 + tests/components/axis/test_switch.py | 1 + tests/components/azure_devops/test_config_flow.py | 1 + tests/components/azure_event_hub/conftest.py | 1 + tests/components/azure_event_hub/const.py | 1 + tests/components/azure_event_hub/test_init.py | 1 + tests/components/backup/common.py | 1 + tests/components/backup/test_http.py | 1 + tests/components/backup/test_init.py | 1 + tests/components/backup/test_manager.py | 1 + tests/components/backup/test_websocket.py | 1 + tests/components/baf/test_config_flow.py | 1 + tests/components/baf/test_init.py | 1 + tests/components/balboa/conftest.py | 1 + tests/components/balboa/test_binary_sensor.py | 1 + tests/components/balboa/test_climate.py | 1 + tests/components/balboa/test_config_flow.py | 1 + tests/components/balboa/test_fan.py | 1 + tests/components/balboa/test_light.py | 1 + tests/components/bang_olufsen/conftest.py | 1 + tests/components/binary_sensor/test_device_condition.py | 1 + tests/components/binary_sensor/test_device_trigger.py | 1 + tests/components/binary_sensor/test_init.py | 1 + tests/components/binary_sensor/test_significant_change.py | 1 + tests/components/blackbird/test_media_player.py | 1 + tests/components/blebox/conftest.py | 1 + tests/components/blebox/test_binary_sensor.py | 1 + tests/components/blebox/test_config_flow.py | 1 + tests/components/blink/conftest.py | 1 + tests/components/blink/test_config_flow.py | 1 + tests/components/blink/test_diagnostics.py | 1 + tests/components/blink/test_init.py | 1 + tests/components/blink/test_services.py | 1 + tests/components/blue_current/test_config_flow.py | 1 + tests/components/blue_current/test_init.py | 1 + tests/components/blue_current/test_sensor.py | 1 + tests/components/bluemaestro/test_config_flow.py | 1 + tests/components/bluemaestro/test_sensor.py | 1 + tests/components/blueprint/test_websocket_api.py | 1 + tests/components/bluetooth/test_active_update_coordinator.py | 1 + tests/components/bluetooth/test_active_update_processor.py | 1 + tests/components/bluetooth/test_advertisement_tracker.py | 1 + tests/components/bluetooth/test_base_scanner.py | 1 + tests/components/bluetooth/test_config_flow.py | 1 + tests/components/bluetooth/test_diagnostics.py | 1 + tests/components/bluetooth/test_manager.py | 1 + tests/components/bluetooth/test_models.py | 1 + tests/components/bluetooth/test_passive_update_coordinator.py | 1 + tests/components/bluetooth/test_passive_update_processor.py | 1 + tests/components/bluetooth/test_usage.py | 1 + tests/components/bluetooth/test_wrappers.py | 1 + tests/components/bluetooth_le_tracker/test_device_tracker.py | 1 + tests/components/bmw_connected_drive/test_button.py | 1 + tests/components/bmw_connected_drive/test_config_flow.py | 1 + tests/components/bmw_connected_drive/test_coordinator.py | 1 + tests/components/bmw_connected_drive/test_init.py | 1 + tests/components/bmw_connected_drive/test_number.py | 1 + tests/components/bmw_connected_drive/test_select.py | 1 + tests/components/bmw_connected_drive/test_sensor.py | 1 + tests/components/bmw_connected_drive/test_switch.py | 1 + tests/components/bond/common.py | 1 + tests/components/bond/conftest.py | 1 + tests/components/bond/test_button.py | 1 + tests/components/bond/test_config_flow.py | 1 + tests/components/bond/test_cover.py | 1 + tests/components/bond/test_diagnostics.py | 1 + tests/components/bond/test_entity.py | 1 + tests/components/bond/test_fan.py | 1 + tests/components/bond/test_init.py | 1 + tests/components/bond/test_light.py | 1 + tests/components/bond/test_switch.py | 1 + tests/components/bosch_shc/test_config_flow.py | 1 + tests/components/braviatv/conftest.py | 1 + tests/components/braviatv/test_config_flow.py | 1 + tests/components/braviatv/test_diagnostics.py | 1 + tests/components/bring/conftest.py | 1 + tests/components/bring/test_config_flow.py | 1 + tests/components/bring/test_init.py | 1 + tests/components/broadlink/conftest.py | 1 + tests/components/broadlink/test_device.py | 1 + tests/components/broadlink/test_heartbeat.py | 1 + tests/components/broadlink/test_remote.py | 1 + tests/components/broadlink/test_sensors.py | 1 + tests/components/broadlink/test_switch.py | 1 + tests/components/brother/conftest.py | 1 + tests/components/brother/test_config_flow.py | 1 + tests/components/brother/test_diagnostics.py | 1 + tests/components/brother/test_init.py | 1 + tests/components/brother/test_sensor.py | 1 + tests/components/brottsplatskartan/conftest.py | 1 + tests/components/brottsplatskartan/test_config_flow.py | 1 + tests/components/brottsplatskartan/test_init.py | 1 + tests/components/brunt/conftest.py | 1 + tests/components/brunt/test_config_flow.py | 1 + tests/components/bsblan/conftest.py | 1 + tests/components/bsblan/test_config_flow.py | 1 + tests/components/bsblan/test_init.py | 1 + tests/components/bthome/test_binary_sensor.py | 1 + tests/components/bthome/test_config_flow.py | 1 + tests/components/bthome/test_logbook.py | 1 + tests/components/bthome/test_sensor.py | 1 + tests/components/buienradar/conftest.py | 1 + tests/components/buienradar/test_init.py | 1 + tests/components/buienradar/test_sensor.py | 1 + tests/components/buienradar/test_weather.py | 1 + tests/components/button/test_device_trigger.py | 1 + tests/components/button/test_init.py | 1 + tests/components/caldav/conftest.py | 1 + tests/components/caldav/test_calendar.py | 1 + tests/components/caldav/test_todo.py | 1 + tests/components/calendar/conftest.py | 1 + tests/components/calendar/test_init.py | 1 + tests/components/calendar/test_recorder.py | 1 + tests/components/calendar/test_trigger.py | 1 + tests/components/camera/common.py | 1 + tests/components/camera/conftest.py | 1 + tests/components/camera/test_img_util.py | 1 + tests/components/camera/test_init.py | 1 + tests/components/camera/test_media_source.py | 1 + tests/components/camera/test_recorder.py | 1 + tests/components/camera/test_significant_change.py | 1 + tests/components/canary/conftest.py | 1 + tests/components/canary/test_alarm_control_panel.py | 1 + tests/components/canary/test_config_flow.py | 1 + tests/components/canary/test_init.py | 1 + tests/components/canary/test_sensor.py | 1 + tests/components/cast/test_config_flow.py | 1 + tests/components/cast/test_home_assistant_cast.py | 1 + tests/components/cast/test_media_player.py | 1 + tests/components/ccm15/conftest.py | 1 + tests/components/ccm15/test_climate.py | 1 + tests/components/ccm15/test_config_flow.py | 1 + tests/components/ccm15/test_diagnostics.py | 1 + tests/components/ccm15/test_init.py | 1 + tests/components/cert_expiry/conftest.py | 1 + tests/components/cert_expiry/helpers.py | 1 + tests/components/cert_expiry/test_init.py | 1 + tests/components/cert_expiry/test_sensors.py | 1 + tests/components/climate/common.py | 1 + tests/components/climate/conftest.py | 1 + tests/components/climate/test_init.py | 1 + tests/components/climate/test_recorder.py | 1 + tests/components/cloud/conftest.py | 1 + tests/components/cloud/test_binary_sensor.py | 1 + tests/components/cloud/test_client.py | 1 + tests/components/cloud/test_config_flow.py | 1 + tests/components/cloud/test_google_config.py | 1 + tests/components/cloud/test_http_api.py | 1 + tests/components/cloud/test_init.py | 1 + tests/components/cloud/test_prefs.py | 1 + tests/components/cloud/test_repairs.py | 1 + tests/components/cloud/test_stt.py | 1 + tests/components/cloud/test_subscription.py | 1 + tests/components/cloud/test_tts.py | 1 + tests/components/cloudflare/conftest.py | 1 + tests/components/cloudflare/test_helpers.py | 1 + tests/components/cloudflare/test_init.py | 1 + tests/components/co2signal/conftest.py | 1 + tests/components/co2signal/test_config_flow.py | 1 + tests/components/co2signal/test_sensor.py | 1 + tests/components/coinbase/common.py | 1 + tests/components/coinbase/test_diagnostics.py | 1 + tests/components/coinbase/test_init.py | 1 + tests/components/color_extractor/test_config_flow.py | 1 + tests/components/color_extractor/test_init.py | 1 + tests/components/comelit/test_config_flow.py | 1 + tests/components/command_line/test_binary_sensor.py | 1 + tests/components/command_line/test_cover.py | 1 + tests/components/command_line/test_init.py | 1 + tests/components/command_line/test_notify.py | 1 + tests/components/command_line/test_sensor.py | 1 + tests/components/command_line/test_switch.py | 1 + tests/components/config/conftest.py | 1 + tests/components/config/test_auth_provider_homeassistant.py | 1 + tests/components/config/test_automation.py | 1 + tests/components/config/test_config_entries.py | 1 + tests/components/config/test_core.py | 1 + tests/components/config/test_init.py | 1 + tests/components/config/test_scene.py | 1 + tests/components/config/test_script.py | 1 + tests/components/conftest.py | 1 + tests/components/control4/test_config_flow.py | 1 + tests/components/conversation/conftest.py | 1 + tests/components/conversation/test_init.py | 1 + tests/components/conversation/test_util.py | 1 + tests/components/coolmaster/conftest.py | 1 + tests/components/coolmaster/test_binary_sensor.py | 1 + tests/components/coolmaster/test_button.py | 1 + tests/components/coolmaster/test_climate.py | 1 + tests/components/coolmaster/test_config_flow.py | 1 + tests/components/coolmaster/test_init.py | 1 + tests/components/coolmaster/test_sensor.py | 1 + tests/components/counter/common.py | 1 + tests/components/cover/test_device_trigger.py | 1 + tests/components/cover/test_init.py | 1 + tests/components/cpuspeed/conftest.py | 1 + tests/components/cpuspeed/test_diagnostics.py | 1 + tests/components/cpuspeed/test_init.py | 1 + tests/components/crownstone/test_config_flow.py | 1 + tests/components/daikin/test_config_flow.py | 1 + tests/components/daikin/test_init.py | 1 + tests/components/daikin/test_temperature_format.py | 1 + tests/components/datadog/test_init.py | 1 + tests/components/date/test_init.py | 1 + tests/components/datetime/test_init.py | 1 + tests/components/debugpy/test_init.py | 1 + tests/components/deconz/conftest.py | 1 + tests/components/deconz/test_alarm_control_panel.py | 1 + tests/components/deconz/test_binary_sensor.py | 1 + tests/components/deconz/test_button.py | 1 + tests/components/deconz/test_climate.py | 1 + tests/components/deconz/test_cover.py | 1 + tests/components/deconz/test_deconz_event.py | 1 + tests/components/deconz/test_device_trigger.py | 1 + tests/components/deconz/test_diagnostics.py | 1 + tests/components/deconz/test_fan.py | 1 + tests/components/deconz/test_gateway.py | 1 + tests/components/deconz/test_light.py | 1 + tests/components/deconz/test_lock.py | 1 + tests/components/deconz/test_logbook.py | 1 + tests/components/deconz/test_number.py | 1 + tests/components/deconz/test_scene.py | 1 + tests/components/deconz/test_select.py | 1 + tests/components/deconz/test_sensor.py | 1 + tests/components/deconz/test_services.py | 1 + tests/components/deconz/test_siren.py | 1 + tests/components/deconz/test_switch.py | 1 + tests/components/default_config/test_init.py | 1 + tests/components/deluge/test_config_flow.py | 1 + tests/components/demo/conftest.py | 1 + tests/components/demo/test_camera.py | 1 + tests/components/demo/test_climate.py | 1 + tests/components/demo/test_cover.py | 1 + tests/components/demo/test_date.py | 1 + tests/components/demo/test_datetime.py | 1 + tests/components/demo/test_fan.py | 1 + tests/components/demo/test_geo_location.py | 1 + tests/components/demo/test_light.py | 1 + tests/components/demo/test_lock.py | 1 + tests/components/demo/test_media_player.py | 1 + tests/components/demo/test_number.py | 1 + tests/components/demo/test_remote.py | 1 + tests/components/demo/test_select.py | 1 + tests/components/demo/test_sensor.py | 1 + tests/components/demo/test_siren.py | 1 + tests/components/demo/test_stt.py | 1 + tests/components/demo/test_switch.py | 1 + tests/components/demo/test_text.py | 1 + tests/components/demo/test_time.py | 1 + tests/components/demo/test_update.py | 1 + tests/components/demo/test_vacuum.py | 1 + tests/components/demo/test_water_heater.py | 1 + tests/components/denonavr/test_config_flow.py | 1 + tests/components/denonavr/test_media_player.py | 1 + tests/components/derivative/test_config_flow.py | 1 + tests/components/derivative/test_sensor.py | 1 + tests/components/devialet/test_config_flow.py | 1 + tests/components/devialet/test_init.py | 1 + tests/components/devialet/test_media_player.py | 1 + tests/components/device_automation/test_init.py | 1 + tests/components/device_automation/test_toggle_entity.py | 1 + tests/components/device_sun_light_trigger/test_init.py | 1 + tests/components/device_tracker/common.py | 1 + tests/components/device_tracker/test_config_entry.py | 1 + tests/components/device_tracker/test_init.py | 1 + tests/components/device_tracker/test_legacy.py | 1 + tests/components/devolo_home_control/test_binary_sensor.py | 1 + tests/components/devolo_home_control/test_climate.py | 1 + tests/components/devolo_home_control/test_config_flow.py | 1 + tests/components/devolo_home_control/test_cover.py | 1 + tests/components/devolo_home_control/test_diagnostics.py | 1 + tests/components/devolo_home_control/test_init.py | 1 + tests/components/devolo_home_control/test_light.py | 1 + tests/components/devolo_home_control/test_sensor.py | 1 + tests/components/devolo_home_control/test_siren.py | 1 + tests/components/devolo_home_control/test_switch.py | 1 + tests/components/devolo_home_network/conftest.py | 1 + tests/components/devolo_home_network/mock.py | 1 + tests/components/devolo_home_network/test_binary_sensor.py | 1 + tests/components/devolo_home_network/test_button.py | 1 + tests/components/devolo_home_network/test_config_flow.py | 1 + tests/components/devolo_home_network/test_device_tracker.py | 1 + tests/components/devolo_home_network/test_diagnostics.py | 1 + tests/components/devolo_home_network/test_image.py | 1 + tests/components/devolo_home_network/test_init.py | 1 + tests/components/devolo_home_network/test_sensor.py | 1 + tests/components/devolo_home_network/test_switch.py | 1 + tests/components/devolo_home_network/test_update.py | 1 + tests/components/dexcom/test_config_flow.py | 1 + tests/components/dexcom/test_init.py | 1 + tests/components/dexcom/test_sensor.py | 1 + tests/components/dhcp/test_init.py | 1 + tests/components/diagnostics/test_init.py | 1 + tests/components/diagnostics/test_util.py | 1 + tests/components/directv/test_init.py | 1 + tests/components/directv/test_media_player.py | 1 + tests/components/directv/test_remote.py | 1 + tests/components/discord/conftest.py | 1 + tests/components/discovergy/conftest.py | 1 + tests/components/discovergy/test_config_flow.py | 1 + tests/components/discovergy/test_init.py | 1 + tests/components/discovergy/test_sensor.py | 1 + tests/components/dlink/test_config_flow.py | 1 + tests/components/dlink/test_init.py | 1 + tests/components/dlink/test_switch.py | 1 + tests/components/dlna_dmr/conftest.py | 1 + tests/components/dlna_dmr/test_config_flow.py | 1 + tests/components/dlna_dmr/test_data.py | 1 + tests/components/dlna_dmr/test_media_player.py | 1 + tests/components/dlna_dms/conftest.py | 1 + tests/components/dlna_dms/test_config_flow.py | 1 + tests/components/dlna_dms/test_device_availability.py | 1 + tests/components/dlna_dms/test_dms_device_source.py | 1 + tests/components/dlna_dms/test_media_source.py | 1 + tests/components/dnsip/test_config_flow.py | 1 + tests/components/dnsip/test_init.py | 1 + tests/components/dnsip/test_sensor.py | 1 + tests/components/doorbird/test_config_flow.py | 1 + tests/components/dormakaba_dkey/test_config_flow.py | 1 + tests/components/dremel_3d_printer/conftest.py | 1 + tests/components/dremel_3d_printer/test_binary_sensor.py | 1 + tests/components/dremel_3d_printer/test_button.py | 1 + tests/components/dremel_3d_printer/test_config_flow.py | 1 + tests/components/dremel_3d_printer/test_init.py | 1 + tests/components/dremel_3d_printer/test_sensor.py | 1 + tests/components/drop_connect/test_config_flow.py | 1 + tests/components/dsmr/test_config_flow.py | 1 + tests/components/dsmr/test_init.py | 1 + tests/components/dsmr_reader/test_config_flow.py | 1 + tests/components/duckdns/test_init.py | 1 + tests/components/dunehd/test_config_flow.py | 1 + tests/components/duotecno/conftest.py | 1 + tests/components/duotecno/test_config_flow.py | 1 + tests/components/dynalite/common.py | 1 + tests/components/dynalite/conftest.py | 1 + tests/components/dynalite/test_bridge.py | 1 + tests/components/dynalite/test_config_flow.py | 1 + tests/components/dynalite/test_cover.py | 1 + tests/components/dynalite/test_init.py | 1 + tests/components/dynalite/test_light.py | 1 + tests/components/dynalite/test_switch.py | 1 + tests/components/eafm/test_config_flow.py | 1 + tests/components/easyenergy/conftest.py | 1 + tests/components/easyenergy/test_config_flow.py | 1 + tests/components/easyenergy/test_diagnostics.py | 1 + tests/components/easyenergy/test_init.py | 1 + tests/components/ecobee/common.py | 1 + tests/components/ecobee/conftest.py | 1 + tests/components/ecobee/test_config_flow.py | 1 + tests/components/ecobee/test_humidifier.py | 1 + tests/components/ecobee/test_number.py | 1 + tests/components/ecoforest/conftest.py | 1 + tests/components/ecoforest/test_config_flow.py | 1 + tests/components/econet/test_config_flow.py | 1 + tests/components/ecovacs/conftest.py | 1 + tests/components/ecovacs/test_config_flow.py | 1 + tests/components/ecovacs/test_init.py | 1 + tests/components/ecowitt/test_config_flow.py | 1 + tests/components/efergy/test_config_flow.py | 1 + tests/components/efergy/test_init.py | 1 + tests/components/efergy/test_sensor.py | 1 + tests/components/electrasmart/test_config_flow.py | 1 + tests/components/electric_kiwi/conftest.py | 1 + tests/components/electric_kiwi/test_config_flow.py | 1 + tests/components/elgato/conftest.py | 1 + tests/components/elgato/test_button.py | 1 + tests/components/elgato/test_config_flow.py | 1 + tests/components/elgato/test_init.py | 1 + tests/components/elgato/test_light.py | 1 + tests/components/elgato/test_switch.py | 1 + tests/components/elkm1/test_config_flow.py | 1 + tests/components/elkm1/test_logbook.py | 1 + tests/components/elmax/test_config_flow.py | 1 + tests/components/elvia/conftest.py | 1 + tests/components/elvia/test_config_flow.py | 1 + tests/components/emonitor/test_config_flow.py | 1 + tests/components/emulated_hue/test_init.py | 1 + tests/components/emulated_hue/test_upnp.py | 1 + tests/components/emulated_roku/test_binding.py | 1 + tests/components/emulated_roku/test_config_flow.py | 1 + tests/components/emulated_roku/test_init.py | 1 + tests/components/energy/test_validate.py | 1 + tests/components/energy/test_websocket_api.py | 1 + tests/components/energyzero/conftest.py | 1 + tests/components/energyzero/test_config_flow.py | 1 + tests/components/energyzero/test_diagnostics.py | 1 + tests/components/energyzero/test_init.py | 1 + tests/components/energyzero/test_sensor.py | 1 + tests/components/enocean/test_config_flow.py | 1 + tests/components/enphase_envoy/conftest.py | 1 + tests/components/enphase_envoy/test_config_flow.py | 1 + tests/components/enphase_envoy/test_diagnostics.py | 1 + tests/components/enphase_envoy/test_sensor.py | 1 + tests/components/environment_canada/test_config_flow.py | 1 + tests/components/environment_canada/test_diagnostics.py | 1 + tests/components/epion/test_config_flow.py | 1 + tests/components/epson/test_config_flow.py | 1 + tests/components/epson/test_media_player.py | 1 + tests/components/esphome/bluetooth/test_client.py | 1 + tests/components/esphome/conftest.py | 1 + tests/components/esphome/test_alarm_control_panel.py | 1 + tests/components/esphome/test_binary_sensor.py | 1 + tests/components/esphome/test_camera.py | 1 + tests/components/esphome/test_config_flow.py | 1 + tests/components/esphome/test_dashboard.py | 1 + tests/components/esphome/test_diagnostics.py | 1 + tests/components/esphome/test_sensor.py | 1 + tests/components/esphome/test_update.py | 1 + tests/components/eufylife_ble/test_config_flow.py | 1 + tests/components/event/test_init.py | 1 + tests/components/event/test_recorder.py | 1 + tests/components/everlights/conftest.py | 1 + tests/components/everlights/test_light.py | 1 + tests/components/evil_genius_labs/test_config_flow.py | 1 + tests/components/evil_genius_labs/test_light.py | 1 + tests/components/ezviz/conftest.py | 1 + tests/components/ezviz/test_config_flow.py | 1 + 577 files changed, 577 insertions(+) diff --git a/tests/components/abode/common.py b/tests/components/abode/common.py index f9ae52a2709..20739cf58a1 100644 --- a/tests/components/abode/common.py +++ b/tests/components/abode/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for Abode.""" + from unittest.mock import patch from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN diff --git a/tests/components/abode/conftest.py b/tests/components/abode/conftest.py index 1f9ff37ecf1..0e5e24b24f4 100644 --- a/tests/components/abode/conftest.py +++ b/tests/components/abode/conftest.py @@ -1,4 +1,5 @@ """Configuration for Abode tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/abode/test_alarm_control_panel.py b/tests/components/abode/test_alarm_control_panel.py index c5500717c5a..428e2791ee2 100644 --- a/tests/components/abode/test_alarm_control_panel.py +++ b/tests/components/abode/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Tests for the Abode alarm control panel device.""" + from unittest.mock import PropertyMock, patch from jaraco.abode.helpers import constants as CONST diff --git a/tests/components/abode/test_binary_sensor.py b/tests/components/abode/test_binary_sensor.py index 987eea7d891..1fcc032c48d 100644 --- a/tests/components/abode/test_binary_sensor.py +++ b/tests/components/abode/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Abode binary sensor device.""" + from homeassistant.components.abode import ATTR_DEVICE_ID from homeassistant.components.abode.const import ATTRIBUTION from homeassistant.components.binary_sensor import ( diff --git a/tests/components/abode/test_camera.py b/tests/components/abode/test_camera.py index d0c47eff045..5cf3263876b 100644 --- a/tests/components/abode/test_camera.py +++ b/tests/components/abode/test_camera.py @@ -1,4 +1,5 @@ """Tests for the Abode camera device.""" + from unittest.mock import patch from homeassistant.components.abode.const import DOMAIN as ABODE_DOMAIN diff --git a/tests/components/abode/test_config_flow.py b/tests/components/abode/test_config_flow.py index 7619cf9325d..2f73ee052c1 100644 --- a/tests/components/abode/test_config_flow.py +++ b/tests/components/abode/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Abode config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/abode/test_cover.py b/tests/components/abode/test_cover.py index bc3abd32cd1..cdbec0ddf68 100644 --- a/tests/components/abode/test_cover.py +++ b/tests/components/abode/test_cover.py @@ -1,4 +1,5 @@ """Tests for the Abode cover device.""" + from unittest.mock import patch from homeassistant.components.abode import ATTR_DEVICE_ID diff --git a/tests/components/abode/test_init.py b/tests/components/abode/test_init.py index ae7ed51e086..e23fa5aa1b9 100644 --- a/tests/components/abode/test_init.py +++ b/tests/components/abode/test_init.py @@ -1,4 +1,5 @@ """Tests for the Abode module.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/abode/test_light.py b/tests/components/abode/test_light.py index d7fd719a2b9..fc9000a39f8 100644 --- a/tests/components/abode/test_light.py +++ b/tests/components/abode/test_light.py @@ -1,4 +1,5 @@ """Tests for the Abode light device.""" + from unittest.mock import patch from homeassistant.components.abode import ATTR_DEVICE_ID diff --git a/tests/components/abode/test_lock.py b/tests/components/abode/test_lock.py index ac988a1ee12..6be1aef22ca 100644 --- a/tests/components/abode/test_lock.py +++ b/tests/components/abode/test_lock.py @@ -1,4 +1,5 @@ """Tests for the Abode lock device.""" + from unittest.mock import patch from homeassistant.components.abode import ATTR_DEVICE_ID diff --git a/tests/components/abode/test_sensor.py b/tests/components/abode/test_sensor.py index 9f4b3374fc2..e92748bb162 100644 --- a/tests/components/abode/test_sensor.py +++ b/tests/components/abode/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Abode sensor device.""" + from homeassistant.components.abode import ATTR_DEVICE_ID from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass from homeassistant.const import ( diff --git a/tests/components/abode/test_switch.py b/tests/components/abode/test_switch.py index b5b93d05481..9f8e4d3205b 100644 --- a/tests/components/abode/test_switch.py +++ b/tests/components/abode/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Abode switch device.""" + from unittest.mock import patch from homeassistant.components.abode import ( diff --git a/tests/components/accuweather/test_config_flow.py b/tests/components/accuweather/test_config_flow.py index 35b6e095c0f..bb338618ccc 100644 --- a/tests/components/accuweather/test_config_flow.py +++ b/tests/components/accuweather/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the AccuWeather config flow.""" + from unittest.mock import PropertyMock, patch from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError diff --git a/tests/components/accuweather/test_diagnostics.py b/tests/components/accuweather/test_diagnostics.py index 7c13f318cc3..ab77fc337d0 100644 --- a/tests/components/accuweather/test_diagnostics.py +++ b/tests/components/accuweather/test_diagnostics.py @@ -1,4 +1,5 @@ """Test AccuWeather diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/accuweather/test_init.py b/tests/components/accuweather/test_init.py index 342cc2f5914..5f2f9f07a2f 100644 --- a/tests/components/accuweather/test_init.py +++ b/tests/components/accuweather/test_init.py @@ -1,4 +1,5 @@ """Test init of AccuWeather integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/accuweather/test_sensor.py b/tests/components/accuweather/test_sensor.py index eb5e26a8e20..6c93c21ccc0 100644 --- a/tests/components/accuweather/test_sensor.py +++ b/tests/components/accuweather/test_sensor.py @@ -1,4 +1,5 @@ """Test sensor of AccuWeather integration.""" + from datetime import timedelta from unittest.mock import PropertyMock, patch diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index 920e5cf82b9..2b2c304f948 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -1,4 +1,5 @@ """Test weather of AccuWeather integration.""" + from datetime import timedelta from unittest.mock import PropertyMock, patch diff --git a/tests/components/acmeda/test_config_flow.py b/tests/components/acmeda/test_config_flow.py index 4b726e046b5..c39470ebbb6 100644 --- a/tests/components/acmeda/test_config_flow.py +++ b/tests/components/acmeda/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Acmeda config flow.""" + from unittest.mock import patch import aiopulse diff --git a/tests/components/adax/test_config_flow.py b/tests/components/adax/test_config_flow.py index f19d90a5772..1189258d81d 100644 --- a/tests/components/adax/test_config_flow.py +++ b/tests/components/adax/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Adax config flow.""" + from unittest.mock import patch import adax_local diff --git a/tests/components/advantage_air/conftest.py b/tests/components/advantage_air/conftest.py index 9da0a176309..fbd45c70396 100644 --- a/tests/components/advantage_air/conftest.py +++ b/tests/components/advantage_air/conftest.py @@ -1,4 +1,5 @@ """Fixtures for advantage_air.""" + from __future__ import annotations import pytest diff --git a/tests/components/advantage_air/test_binary_sensor.py b/tests/components/advantage_air/test_binary_sensor.py index 19b0dba2eda..4395fb82542 100644 --- a/tests/components/advantage_air/test_binary_sensor.py +++ b/tests/components/advantage_air/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Advantage Air Binary Sensor Platform.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/advantage_air/test_config_flow.py b/tests/components/advantage_air/test_config_flow.py index 64d445a0b20..f5f972a9884 100644 --- a/tests/components/advantage_air/test_config_flow.py +++ b/tests/components/advantage_air/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Advantage Air config flow.""" + from unittest.mock import AsyncMock, patch from advantage_air import ApiError diff --git a/tests/components/advantage_air/test_cover.py b/tests/components/advantage_air/test_cover.py index 8166b5da941..4752601d9ad 100644 --- a/tests/components/advantage_air/test_cover.py +++ b/tests/components/advantage_air/test_cover.py @@ -1,4 +1,5 @@ """Test the Advantage Air Cover Platform.""" + from unittest.mock import AsyncMock from homeassistant.components.cover import ( diff --git a/tests/components/advantage_air/test_diagnostics.py b/tests/components/advantage_air/test_diagnostics.py index 80de9019715..c225ab21863 100644 --- a/tests/components/advantage_air/test_diagnostics.py +++ b/tests/components/advantage_air/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Advantage Air Diagnostics.""" + from unittest.mock import AsyncMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/advantage_air/test_init.py b/tests/components/advantage_air/test_init.py index 21cadbc4b3d..e700485c75a 100644 --- a/tests/components/advantage_air/test_init.py +++ b/tests/components/advantage_air/test_init.py @@ -1,4 +1,5 @@ """Test the Advantage Air Initialization.""" + from unittest.mock import AsyncMock from advantage_air import ApiError diff --git a/tests/components/advantage_air/test_sensor.py b/tests/components/advantage_air/test_sensor.py index 0099e1844c6..967afe20ddb 100644 --- a/tests/components/advantage_air/test_sensor.py +++ b/tests/components/advantage_air/test_sensor.py @@ -1,4 +1,5 @@ """Test the Advantage Air Sensor Platform.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/aemet/conftest.py b/tests/components/aemet/conftest.py index 606f01c5403..ead27103348 100644 --- a/tests/components/aemet/conftest.py +++ b/tests/components/aemet/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for aemet.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/aemet/test_config_flow.py b/tests/components/aemet/test_config_flow.py index d4cedcaf3d0..a7a689381e0 100644 --- a/tests/components/aemet/test_config_flow.py +++ b/tests/components/aemet/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the AEMET OpenData config flow.""" + from unittest.mock import AsyncMock, MagicMock, patch from aemet_opendata.exceptions import AuthError diff --git a/tests/components/aemet/test_coordinator.py b/tests/components/aemet/test_coordinator.py index a91256a9518..890c3476da2 100644 --- a/tests/components/aemet/test_coordinator.py +++ b/tests/components/aemet/test_coordinator.py @@ -1,4 +1,5 @@ """Define tests for the AEMET OpenData coordinator.""" + from unittest.mock import patch from aemet_opendata.exceptions import AemetError diff --git a/tests/components/aemet/test_init.py b/tests/components/aemet/test_init.py index 7a4f73dc62b..df69349848b 100644 --- a/tests/components/aemet/test_init.py +++ b/tests/components/aemet/test_init.py @@ -1,4 +1,5 @@ """Define tests for the AEMET OpenData init.""" + from unittest.mock import patch from aemet_opendata.exceptions import AemetTimeout diff --git a/tests/components/aemet/util.py b/tests/components/aemet/util.py index 9714da1c5e7..81a184864a4 100644 --- a/tests/components/aemet/util.py +++ b/tests/components/aemet/util.py @@ -1,4 +1,5 @@ """Tests for the AEMET OpenData integration.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/aftership/conftest.py b/tests/components/aftership/conftest.py index e3fdc00bc30..0bea797dce6 100644 --- a/tests/components/aftership/conftest.py +++ b/tests/components/aftership/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the AfterShip tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/aftership/test_config_flow.py b/tests/components/aftership/test_config_flow.py index 8e9f1a34d31..d5e34ac0ae2 100644 --- a/tests/components/aftership/test_config_flow.py +++ b/tests/components/aftership/test_config_flow.py @@ -1,4 +1,5 @@ """Test AfterShip config flow.""" + from unittest.mock import AsyncMock, patch from pyaftership import AfterShipException diff --git a/tests/components/agent_dvr/conftest.py b/tests/components/agent_dvr/conftest.py index da2cd90ed18..e9f719a6eeb 100644 --- a/tests/components/agent_dvr/conftest.py +++ b/tests/components/agent_dvr/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for Agent DVR.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/agent_dvr/test_init.py b/tests/components/agent_dvr/test_init.py index b601e79d817..5b360430b78 100644 --- a/tests/components/agent_dvr/test_init.py +++ b/tests/components/agent_dvr/test_init.py @@ -1,4 +1,5 @@ """Test Agent DVR integration.""" + from unittest.mock import AsyncMock, patch from agent import AgentError diff --git a/tests/components/airly/test_config_flow.py b/tests/components/airly/test_config_flow.py index 2abd9bd1204..fcc024f7cee 100644 --- a/tests/components/airly/test_config_flow.py +++ b/tests/components/airly/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Airly config flow.""" + from http import HTTPStatus from airly.exceptions import AirlyError diff --git a/tests/components/airly/test_init.py b/tests/components/airly/test_init.py index f24a75bbb6e..74051737197 100644 --- a/tests/components/airly/test_init.py +++ b/tests/components/airly/test_init.py @@ -1,4 +1,5 @@ """Test init of Airly integration.""" + from typing import Any from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/airly/test_sensor.py b/tests/components/airly/test_sensor.py index 2a2bf9fb923..19f073496db 100644 --- a/tests/components/airly/test_sensor.py +++ b/tests/components/airly/test_sensor.py @@ -1,4 +1,5 @@ """Test sensor of Airly integration.""" + from datetime import timedelta from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/airnow/test_config_flow.py b/tests/components/airnow/test_config_flow.py index f4a0fdeec1e..ece28a77a87 100644 --- a/tests/components/airnow/test_config_flow.py +++ b/tests/components/airnow/test_config_flow.py @@ -1,4 +1,5 @@ """Test the AirNow config flow.""" + from unittest.mock import AsyncMock, patch from pyairnow.errors import AirNowError, EmptyResponseError, InvalidKeyError diff --git a/tests/components/airnow/test_diagnostics.py b/tests/components/airnow/test_diagnostics.py index 50ff3ed2b32..78f6c410fdf 100644 --- a/tests/components/airnow/test_diagnostics.py +++ b/tests/components/airnow/test_diagnostics.py @@ -1,4 +1,5 @@ """Test AirNow diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/airq/conftest.py b/tests/components/airq/conftest.py index 28053c9c20a..647569b63f0 100644 --- a/tests/components/airq/conftest.py +++ b/tests/components/airq/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for air-Q.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/airq/test_config_flow.py b/tests/components/airq/test_config_flow.py index 1619440a6f7..8069ce4450d 100644 --- a/tests/components/airq/test_config_flow.py +++ b/tests/components/airq/test_config_flow.py @@ -1,4 +1,5 @@ """Test the air-Q config flow.""" + from unittest.mock import patch from aioairq import DeviceInfo, InvalidAuth diff --git a/tests/components/airthings/test_config_flow.py b/tests/components/airthings/test_config_flow.py index 3228b3c7229..f685f4c021c 100644 --- a/tests/components/airthings/test_config_flow.py +++ b/tests/components/airthings/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Airthings config flow.""" + from unittest.mock import patch import airthings diff --git a/tests/components/airthings_ble/test_config_flow.py b/tests/components/airthings_ble/test_config_flow.py index 65ec91e69c2..279e08ef4a9 100644 --- a/tests/components/airthings_ble/test_config_flow.py +++ b/tests/components/airthings_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Airthings BLE config flow.""" + from unittest.mock import patch from airthings_ble import AirthingsDevice diff --git a/tests/components/airtouch4/test_config_flow.py b/tests/components/airtouch4/test_config_flow.py index 243fc603ef5..a75312bc55b 100644 --- a/tests/components/airtouch4/test_config_flow.py +++ b/tests/components/airtouch4/test_config_flow.py @@ -1,4 +1,5 @@ """Test the AirTouch 4 config flow.""" + from unittest.mock import AsyncMock, Mock, patch from airtouch4pyapi.airtouch import AirTouch, AirTouchAc, AirTouchGroup, AirTouchStatus diff --git a/tests/components/airtouch5/conftest.py b/tests/components/airtouch5/conftest.py index 836ce81301a..016843e6874 100644 --- a/tests/components/airtouch5/conftest.py +++ b/tests/components/airtouch5/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Airtouch 5 tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/airtouch5/test_config_flow.py b/tests/components/airtouch5/test_config_flow.py index 4f608fd4788..8b4b9890e57 100644 --- a/tests/components/airtouch5/test_config_flow.py +++ b/tests/components/airtouch5/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Airtouch 5 config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/airvisual/conftest.py b/tests/components/airvisual/conftest.py index 58b8864ea9c..dde75738e80 100644 --- a/tests/components/airvisual/conftest.py +++ b/tests/components/airvisual/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for AirVisual.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index 1761f55d17f..8c5bbded662 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the AirVisual config flow.""" + from unittest.mock import AsyncMock, patch from pyairvisual.cloud_api import ( diff --git a/tests/components/airvisual/test_diagnostics.py b/tests/components/airvisual/test_diagnostics.py index 32a083ec985..072e4559705 100644 --- a/tests/components/airvisual/test_diagnostics.py +++ b/tests/components/airvisual/test_diagnostics.py @@ -1,4 +1,5 @@ """Test AirVisual diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/airvisual/test_init.py b/tests/components/airvisual/test_init.py index 4f71e75da1e..e6cd5968cea 100644 --- a/tests/components/airvisual/test_init.py +++ b/tests/components/airvisual/test_init.py @@ -1,4 +1,5 @@ """Define tests for AirVisual init.""" + from unittest.mock import patch from homeassistant.components.airvisual import ( diff --git a/tests/components/airvisual_pro/conftest.py b/tests/components/airvisual_pro/conftest.py index 9ebe13c83e6..34426e8e9c8 100644 --- a/tests/components/airvisual_pro/conftest.py +++ b/tests/components/airvisual_pro/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for AirVisual Pro.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/airvisual_pro/test_config_flow.py b/tests/components/airvisual_pro/test_config_flow.py index f1c6d93e357..b0469b5288b 100644 --- a/tests/components/airvisual_pro/test_config_flow.py +++ b/tests/components/airvisual_pro/test_config_flow.py @@ -1,4 +1,5 @@ """Test the AirVisual Pro config flow.""" + from unittest.mock import AsyncMock, patch from pyairvisual.node import ( diff --git a/tests/components/airvisual_pro/test_diagnostics.py b/tests/components/airvisual_pro/test_diagnostics.py index 7c69a7e636f..dd87d00be30 100644 --- a/tests/components/airvisual_pro/test_diagnostics.py +++ b/tests/components/airvisual_pro/test_diagnostics.py @@ -1,4 +1,5 @@ """Test AirVisual Pro diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/airzone/test_water_heater.py b/tests/components/airzone/test_water_heater.py index a1157192f23..ca7344204cf 100644 --- a/tests/components/airzone/test_water_heater.py +++ b/tests/components/airzone/test_water_heater.py @@ -1,4 +1,5 @@ """The water heater tests for the Airzone platform.""" + from unittest.mock import patch from aioairzone.const import ( diff --git a/tests/components/airzone_cloud/test_climate.py b/tests/components/airzone_cloud/test_climate.py index 913d41b072a..848c9b8fb2d 100644 --- a/tests/components/airzone_cloud/test_climate.py +++ b/tests/components/airzone_cloud/test_climate.py @@ -1,4 +1,5 @@ """The climate tests for the Airzone Cloud platform.""" + from unittest.mock import patch from aioairzone_cloud.const import API_DEFAULT_TEMP_STEP diff --git a/tests/components/aladdin_connect/conftest.py b/tests/components/aladdin_connect/conftest.py index 3f5fc4f8f97..979c30bdcea 100644 --- a/tests/components/aladdin_connect/conftest.py +++ b/tests/components/aladdin_connect/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Aladdin Connect integration tests.""" + from unittest import mock from unittest.mock import AsyncMock diff --git a/tests/components/aladdin_connect/test_config_flow.py b/tests/components/aladdin_connect/test_config_flow.py index 6f879994fbe..2bdc05f57a3 100644 --- a/tests/components/aladdin_connect/test_config_flow.py +++ b/tests/components/aladdin_connect/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Aladdin Connect config flow.""" + from unittest.mock import MagicMock, patch from AIOAladdinConnect.session_manager import InvalidPasswordError diff --git a/tests/components/aladdin_connect/test_cover.py b/tests/components/aladdin_connect/test_cover.py index ba82ec6589a..9ad9febc762 100644 --- a/tests/components/aladdin_connect/test_cover.py +++ b/tests/components/aladdin_connect/test_cover.py @@ -1,4 +1,5 @@ """Test the Aladdin Connect Cover.""" + from unittest.mock import AsyncMock, MagicMock, patch from AIOAladdinConnect import session_manager diff --git a/tests/components/aladdin_connect/test_diagnostics.py b/tests/components/aladdin_connect/test_diagnostics.py index 4d5fe903798..48741c77cd1 100644 --- a/tests/components/aladdin_connect/test_diagnostics.py +++ b/tests/components/aladdin_connect/test_diagnostics.py @@ -1,4 +1,5 @@ """Test AccuWeather diagnostics.""" + from unittest.mock import MagicMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/aladdin_connect/test_init.py b/tests/components/aladdin_connect/test_init.py index 2fc09d1641d..8f0b40328e4 100644 --- a/tests/components/aladdin_connect/test_init.py +++ b/tests/components/aladdin_connect/test_init.py @@ -1,4 +1,5 @@ """Test for Aladdin Connect init logic.""" + from unittest.mock import MagicMock, patch from AIOAladdinConnect.session_manager import InvalidPasswordError diff --git a/tests/components/aladdin_connect/test_model.py b/tests/components/aladdin_connect/test_model.py index e802ae53b74..84b1c9ae40a 100644 --- a/tests/components/aladdin_connect/test_model.py +++ b/tests/components/aladdin_connect/test_model.py @@ -1,4 +1,5 @@ """Test the Aladdin Connect model class.""" + from homeassistant.components.aladdin_connect.model import DoorDevice from homeassistant.core import HomeAssistant diff --git a/tests/components/aladdin_connect/test_sensor.py b/tests/components/aladdin_connect/test_sensor.py index dca8ecaa513..c6fac4cf79e 100644 --- a/tests/components/aladdin_connect/test_sensor.py +++ b/tests/components/aladdin_connect/test_sensor.py @@ -1,4 +1,5 @@ """Test the Aladdin Connect Sensors.""" + from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/alarm_control_panel/common.py b/tests/components/alarm_control_panel/common.py index e46bac2fc1f..99f8a3d24bd 100644 --- a/tests/components/alarm_control_panel/common.py +++ b/tests/components/alarm_control_panel/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.alarm_control_panel import DOMAIN from homeassistant.const import ( ATTR_CODE, diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 70d700bb290..76883516957 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Alarm control panel device triggers.""" + from datetime import timedelta import pytest diff --git a/tests/components/alarmdecoder/test_config_flow.py b/tests/components/alarmdecoder/test_config_flow.py index 22df6e445c3..ce7fc1ef333 100644 --- a/tests/components/alarmdecoder/test_config_flow.py +++ b/tests/components/alarmdecoder/test_config_flow.py @@ -1,4 +1,5 @@ """Test the AlarmDecoder config flow.""" + from unittest.mock import patch from alarmdecoder.util import NoDeviceError diff --git a/tests/components/alexa/test_auth.py b/tests/components/alexa/test_auth.py index 6a1865d96e0..8d4308ba792 100644 --- a/tests/components/alexa/test_auth.py +++ b/tests/components/alexa/test_auth.py @@ -1,4 +1,5 @@ """Test Alexa auth endpoints.""" + from homeassistant.components.alexa.auth import Auth from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import HomeAssistant diff --git a/tests/components/alexa/test_capabilities.py b/tests/components/alexa/test_capabilities.py index 5011fee8838..7efc851a9c5 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -1,4 +1,5 @@ """Test Alexa capabilities.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/alexa/test_common.py b/tests/components/alexa/test_common.py index 8c9cea526b6..0cc4d995efa 100644 --- a/tests/components/alexa/test_common.py +++ b/tests/components/alexa/test_common.py @@ -1,4 +1,5 @@ """Test helpers for the Alexa integration.""" + from unittest.mock import Mock from uuid import uuid4 diff --git a/tests/components/alexa/test_entities.py b/tests/components/alexa/test_entities.py index c7949253af0..9ec490c4f83 100644 --- a/tests/components/alexa/test_entities.py +++ b/tests/components/alexa/test_entities.py @@ -1,4 +1,5 @@ """Test Alexa entity representation.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/alexa/test_init.py b/tests/components/alexa/test_init.py index 3ddf33776bb..3c6c54b7c76 100644 --- a/tests/components/alexa/test_init.py +++ b/tests/components/alexa/test_init.py @@ -1,4 +1,5 @@ """Tests for alexa.""" + from homeassistant.components.alexa.const import EVENT_ALEXA_SMART_HOME from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/alexa/test_intent.py b/tests/components/alexa/test_intent.py index c63825b3c12..4670db4ffa9 100644 --- a/tests/components/alexa/test_intent.py +++ b/tests/components/alexa/test_intent.py @@ -1,4 +1,5 @@ """The tests for the Alexa component.""" + from http import HTTPStatus import json diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 97b8bac4cd1..5204b32821c 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1,4 +1,5 @@ """Test for smart home alexa support.""" + from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/alexa/test_smart_home_http.py b/tests/components/alexa/test_smart_home_http.py index 1426eac5c5d..153442552a4 100644 --- a/tests/components/alexa/test_smart_home_http.py +++ b/tests/components/alexa/test_smart_home_http.py @@ -1,4 +1,5 @@ """Test Smart Home HTTP endpoints.""" + from http import HTTPStatus import json import logging diff --git a/tests/components/amberelectric/conftest.py b/tests/components/amberelectric/conftest.py index f7d7d6623e1..8912c248a71 100644 --- a/tests/components/amberelectric/conftest.py +++ b/tests/components/amberelectric/conftest.py @@ -1,4 +1,5 @@ """Provide common Amber fixtures.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/amberelectric/test_binary_sensor.py b/tests/components/amberelectric/test_binary_sensor.py index fb95cd1c41e..92877c57c61 100644 --- a/tests/components/amberelectric/test_binary_sensor.py +++ b/tests/components/amberelectric/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Amber Electric Sensors.""" + from __future__ import annotations from collections.abc import AsyncGenerator diff --git a/tests/components/amberelectric/test_coordinator.py b/tests/components/amberelectric/test_coordinator.py index 7808d1adcde..cb3912cb5ac 100644 --- a/tests/components/amberelectric/test_coordinator.py +++ b/tests/components/amberelectric/test_coordinator.py @@ -1,4 +1,5 @@ """Tests for the Amber Electric Data Coordinator.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/amberelectric/test_sensor.py b/tests/components/amberelectric/test_sensor.py index 286345dba10..c2d4886bbe9 100644 --- a/tests/components/amberelectric/test_sensor.py +++ b/tests/components/amberelectric/test_sensor.py @@ -1,4 +1,5 @@ """Test the Amber Electric Sensors.""" + from collections.abc import AsyncGenerator from unittest.mock import Mock, patch diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index 9de287ea369..e9b85eaaa40 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Ambiclimate config flow.""" + from unittest.mock import AsyncMock, patch import ambiclimate diff --git a/tests/components/ambient_station/test_config_flow.py b/tests/components/ambient_station/test_config_flow.py index 23124f5ec43..ae3af962b0a 100644 --- a/tests/components/ambient_station/test_config_flow.py +++ b/tests/components/ambient_station/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Ambient PWS config flow.""" + from unittest.mock import AsyncMock, patch from aioambient.errors import AmbientError diff --git a/tests/components/ambient_station/test_diagnostics.py b/tests/components/ambient_station/test_diagnostics.py index 4c7a0f66f6a..bc034d0e6f3 100644 --- a/tests/components/ambient_station/test_diagnostics.py +++ b/tests/components/ambient_station/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Ambient PWS diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.components.ambient_station import DOMAIN diff --git a/tests/components/analytics/test_analytics.py b/tests/components/analytics/test_analytics.py index f6b813606a6..7c380d7bbd3 100644 --- a/tests/components/analytics/test_analytics.py +++ b/tests/components/analytics/test_analytics.py @@ -1,4 +1,5 @@ """The tests for the analytics .""" + from collections.abc import Generator from typing import Any from unittest.mock import AsyncMock, Mock, PropertyMock, patch diff --git a/tests/components/analytics/test_init.py b/tests/components/analytics/test_init.py index a9cb5d28767..cf8d4838415 100644 --- a/tests/components/analytics/test_init.py +++ b/tests/components/analytics/test_init.py @@ -1,4 +1,5 @@ """The tests for the analytics .""" + from unittest.mock import patch from homeassistant.components.analytics.const import ANALYTICS_ENDPOINT_URL, DOMAIN diff --git a/tests/components/analytics_insights/conftest.py b/tests/components/analytics_insights/conftest.py index 6ca98d294e6..f5f811c6d26 100644 --- a/tests/components/analytics_insights/conftest.py +++ b/tests/components/analytics_insights/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Homeassistant Analytics tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/analytics_insights/test_config_flow.py b/tests/components/analytics_insights/test_config_flow.py index 6ddbe285df7..49ec0ce8d52 100644 --- a/tests/components/analytics_insights/test_config_flow.py +++ b/tests/components/analytics_insights/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Homeassistant Analytics config flow.""" + from typing import Any from unittest.mock import AsyncMock diff --git a/tests/components/analytics_insights/test_init.py b/tests/components/analytics_insights/test_init.py index 08b898f13c1..4f1ca7cda95 100644 --- a/tests/components/analytics_insights/test_init.py +++ b/tests/components/analytics_insights/test_init.py @@ -1,4 +1,5 @@ """Test the Home Assistant analytics init module.""" + from __future__ import annotations from unittest.mock import AsyncMock diff --git a/tests/components/analytics_insights/test_sensor.py b/tests/components/analytics_insights/test_sensor.py index 83ea2885456..e0850bbd55b 100644 --- a/tests/components/analytics_insights/test_sensor.py +++ b/tests/components/analytics_insights/test_sensor.py @@ -1,4 +1,5 @@ """Test the Home Assistant analytics sensor module.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/android_ip_webcam/conftest.py b/tests/components/android_ip_webcam/conftest.py index 83a040222f8..17fc3e451a3 100644 --- a/tests/components/android_ip_webcam/conftest.py +++ b/tests/components/android_ip_webcam/conftest.py @@ -1,4 +1,5 @@ """Fixtures for tests.""" + from http import HTTPStatus import pytest diff --git a/tests/components/android_ip_webcam/test_config_flow.py b/tests/components/android_ip_webcam/test_config_flow.py index 881585ed5dc..2e4522188eb 100644 --- a/tests/components/android_ip_webcam/test_config_flow.py +++ b/tests/components/android_ip_webcam/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Android IP Webcam config flow.""" + from unittest.mock import Mock, patch import aiohttp diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index aae99b34438..67393a21f41 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -1,4 +1,5 @@ """Define patches used for androidtv tests.""" + from unittest.mock import patch from androidtv.constants import CMD_DEVICE_PROPERTIES, CMD_MAC_ETH0, CMD_MAC_WLAN0 diff --git a/tests/components/androidtv/test_config_flow.py b/tests/components/androidtv/test_config_flow.py index ad7d3be290d..7946ba4db99 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the AndroidTV config flow.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index da56133abb0..f09e21dd056 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the androidtv platform.""" + from datetime import timedelta import logging from typing import Any diff --git a/tests/components/androidtv_remote/conftest.py b/tests/components/androidtv_remote/conftest.py index b981581becd..3b69da6d742 100644 --- a/tests/components/androidtv_remote/conftest.py +++ b/tests/components/androidtv_remote/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Android TV Remote integration tests.""" + from collections.abc import Callable, Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/androidtv_remote/test_config_flow.py b/tests/components/androidtv_remote/test_config_flow.py index fb4bc829160..f4e141ce952 100644 --- a/tests/components/androidtv_remote/test_config_flow.py +++ b/tests/components/androidtv_remote/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Android TV Remote config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/androidtv_remote/test_diagnostics.py b/tests/components/androidtv_remote/test_diagnostics.py index 93410fd4511..09f38a32201 100644 --- a/tests/components/androidtv_remote/test_diagnostics.py +++ b/tests/components/androidtv_remote/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Android TV Remote integration.""" + from unittest.mock import MagicMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/androidtv_remote/test_init.py b/tests/components/androidtv_remote/test_init.py index f3f61eb268e..ad97df2b9c9 100644 --- a/tests/components/androidtv_remote/test_init.py +++ b/tests/components/androidtv_remote/test_init.py @@ -1,4 +1,5 @@ """Tests for the Android TV Remote integration.""" + from collections.abc import Callable from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/androidtv_remote/test_media_player.py b/tests/components/androidtv_remote/test_media_player.py index 0e9013550c2..c7937e9e02d 100644 --- a/tests/components/androidtv_remote/test_media_player.py +++ b/tests/components/androidtv_remote/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the Android TV Remote remote platform.""" + from unittest.mock import MagicMock, call from androidtvremote2 import ConnectionClosed diff --git a/tests/components/androidtv_remote/test_remote.py b/tests/components/androidtv_remote/test_remote.py index 5157361a158..eba955a6aba 100644 --- a/tests/components/androidtv_remote/test_remote.py +++ b/tests/components/androidtv_remote/test_remote.py @@ -1,4 +1,5 @@ """Tests for the Android TV Remote remote platform.""" + from unittest.mock import MagicMock, call from androidtvremote2 import ConnectionClosed diff --git a/tests/components/anova/conftest.py b/tests/components/anova/conftest.py index 34f713502dd..cca58996031 100644 --- a/tests/components/anova/conftest.py +++ b/tests/components/anova/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for Anova.""" + from unittest.mock import AsyncMock, patch from anova_wifi import AnovaApi, AnovaPrecisionCooker, InvalidLogin, NoDevicesFound diff --git a/tests/components/anthemav/conftest.py b/tests/components/anthemav/conftest.py index 4c1abdd3c9b..b615b2710c0 100644 --- a/tests/components/anthemav/conftest.py +++ b/tests/components/anthemav/conftest.py @@ -1,4 +1,5 @@ """Fixtures for anthemav integration tests.""" + from collections.abc import Callable from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/anthemav/test_config_flow.py b/tests/components/anthemav/test_config_flow.py index caa76006976..a0a6bf82762 100644 --- a/tests/components/anthemav/test_config_flow.py +++ b/tests/components/anthemav/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Anthem A/V Receivers config flow.""" + from unittest.mock import AsyncMock, patch from anthemav.device_error import DeviceError diff --git a/tests/components/anthemav/test_init.py b/tests/components/anthemav/test_init.py index 019668769ed..6989ffc69c5 100644 --- a/tests/components/anthemav/test_init.py +++ b/tests/components/anthemav/test_init.py @@ -1,4 +1,5 @@ """Test the Anthem A/V Receivers config flow.""" + from collections.abc import Callable from unittest.mock import ANY, AsyncMock, patch diff --git a/tests/components/anthemav/test_media_player.py b/tests/components/anthemav/test_media_player.py index 9dd8af24efb..a9a945c86a1 100644 --- a/tests/components/anthemav/test_media_player.py +++ b/tests/components/anthemav/test_media_player.py @@ -1,4 +1,5 @@ """Test the Anthem A/V Receivers config flow.""" + from collections.abc import Callable from unittest.mock import AsyncMock diff --git a/tests/components/aosmith/conftest.py b/tests/components/aosmith/conftest.py index fe35f6b337d..74e995deaf1 100644 --- a/tests/components/aosmith/conftest.py +++ b/tests/components/aosmith/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the A. O. Smith tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/aosmith/test_config_flow.py b/tests/components/aosmith/test_config_flow.py index d6cf1655b14..b981047c926 100644 --- a/tests/components/aosmith/test_config_flow.py +++ b/tests/components/aosmith/test_config_flow.py @@ -1,4 +1,5 @@ """Test the A. O. Smith config flow.""" + from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/apache_kafka/test_init.py b/tests/components/apache_kafka/test_init.py index 2f8b035cda9..2a7a92b773b 100644 --- a/tests/components/apache_kafka/test_init.py +++ b/tests/components/apache_kafka/test_init.py @@ -1,4 +1,5 @@ """The tests for the Apache Kafka component.""" + from __future__ import annotations from asyncio import AbstractEventLoop diff --git a/tests/components/apcupsd/test_binary_sensor.py b/tests/components/apcupsd/test_binary_sensor.py index 033b1ff6b82..21d972ec0ea 100644 --- a/tests/components/apcupsd/test_binary_sensor.py +++ b/tests/components/apcupsd/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test binary sensors of APCUPSd integration.""" + from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/apcupsd/test_config_flow.py b/tests/components/apcupsd/test_config_flow.py index 6a69d4e974e..3b9b1ee6e2e 100644 --- a/tests/components/apcupsd/test_config_flow.py +++ b/tests/components/apcupsd/test_config_flow.py @@ -1,4 +1,5 @@ """Test APCUPSd config flow setup process.""" + from copy import copy from unittest.mock import patch diff --git a/tests/components/apple_tv/test_config_flow.py b/tests/components/apple_tv/test_config_flow.py index 9b9020c3cf1..28d87ef1b03 100644 --- a/tests/components/apple_tv/test_config_flow.py +++ b/tests/components/apple_tv/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from ipaddress import IPv4Address, ip_address from unittest.mock import ANY, Mock, patch diff --git a/tests/components/apple_tv/test_remote.py b/tests/components/apple_tv/test_remote.py index db2a4964f6c..f831518d75a 100644 --- a/tests/components/apple_tv/test_remote.py +++ b/tests/components/apple_tv/test_remote.py @@ -1,4 +1,5 @@ """Test apple_tv remote.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/application_credentials/test_init.py b/tests/components/application_credentials/test_init.py index 807eff4ef8d..2d44aec4461 100644 --- a/tests/components/application_credentials/test_init.py +++ b/tests/components/application_credentials/test_init.py @@ -1,4 +1,5 @@ """Test the Developer Credentials integration.""" + from __future__ import annotations from collections.abc import Callable, Generator diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index ead8f735236..ef7f878a234 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -1,4 +1,5 @@ """The tests for the apprise notification platform.""" + from pathlib import Path from unittest.mock import MagicMock, patch diff --git a/tests/components/aprs/test_device_tracker.py b/tests/components/aprs/test_device_tracker.py index 858a0f041de..ca2a5ce1833 100644 --- a/tests/components/aprs/test_device_tracker.py +++ b/tests/components/aprs/test_device_tracker.py @@ -1,4 +1,5 @@ """Test APRS device tracker.""" + from collections.abc import Generator from unittest.mock import MagicMock, Mock, patch diff --git a/tests/components/aranet/test_config_flow.py b/tests/components/aranet/test_config_flow.py index 4c3575f64b5..f3558c66daf 100644 --- a/tests/components/aranet/test_config_flow.py +++ b/tests/components/aranet/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Aranet config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/aranet/test_sensor.py b/tests/components/aranet/test_sensor.py index 0b2b4771069..20aea65989d 100644 --- a/tests/components/aranet/test_sensor.py +++ b/tests/components/aranet/test_sensor.py @@ -1,4 +1,5 @@ """Test the Aranet sensors.""" + from homeassistant.components.aranet.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/arcam_fmj/conftest.py b/tests/components/arcam_fmj/conftest.py index ba32951efe4..7c42fb9e0ff 100644 --- a/tests/components/arcam_fmj/conftest.py +++ b/tests/components/arcam_fmj/conftest.py @@ -1,4 +1,5 @@ """Tests for the arcam_fmj component.""" + from unittest.mock import Mock, patch from arcam.fmj.client import Client diff --git a/tests/components/arcam_fmj/test_config_flow.py b/tests/components/arcam_fmj/test_config_flow.py index 38053797215..470a91feb3b 100644 --- a/tests/components/arcam_fmj/test_config_flow.py +++ b/tests/components/arcam_fmj/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Arcam FMJ config flow module.""" + from dataclasses import replace from unittest.mock import AsyncMock, patch diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py index 9287e8dbc18..0baa8ba6870 100644 --- a/tests/components/arcam_fmj/test_media_player.py +++ b/tests/components/arcam_fmj/test_media_player.py @@ -1,4 +1,5 @@ """Tests for arcam fmj receivers.""" + from math import isclose from unittest.mock import ANY, PropertyMock, patch diff --git a/tests/components/aseko_pool_live/test_config_flow.py b/tests/components/aseko_pool_live/test_config_flow.py index fc109f8ac4e..4ebcf0e0c7d 100644 --- a/tests/components/aseko_pool_live/test_config_flow.py +++ b/tests/components/aseko_pool_live/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Aseko Pool Live config flow.""" + from unittest.mock import patch from aioaseko import AccountInfo, APIUnavailable, InvalidAuthCredentials diff --git a/tests/components/assist_pipeline/conftest.py b/tests/components/assist_pipeline/conftest.py index 0c9d83200b4..8c5cfe9d599 100644 --- a/tests/components/assist_pipeline/conftest.py +++ b/tests/components/assist_pipeline/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for voice assistant.""" + from __future__ import annotations from collections.abc import AsyncIterable, Generator diff --git a/tests/components/assist_pipeline/test_logbook.py b/tests/components/assist_pipeline/test_logbook.py index aaa36b730fe..d8c79c40cb6 100644 --- a/tests/components/assist_pipeline/test_logbook.py +++ b/tests/components/assist_pipeline/test_logbook.py @@ -1,4 +1,5 @@ """The tests for assist_pipeline logbook.""" + from homeassistant.components import assist_pipeline, logbook from homeassistant.const import ATTR_DEVICE_ID from homeassistant.core import HomeAssistant diff --git a/tests/components/assist_pipeline/test_pipeline.py b/tests/components/assist_pipeline/test_pipeline.py index 35913df7400..978b3363cd8 100644 --- a/tests/components/assist_pipeline/test_pipeline.py +++ b/tests/components/assist_pipeline/test_pipeline.py @@ -1,4 +1,5 @@ """Websocket tests for Voice Assistant integration.""" + from collections.abc import AsyncGenerator from typing import Any from unittest.mock import ANY, patch diff --git a/tests/components/assist_pipeline/test_ring_buffer.py b/tests/components/assist_pipeline/test_ring_buffer.py index 22185c3ad5b..7531bcdad80 100644 --- a/tests/components/assist_pipeline/test_ring_buffer.py +++ b/tests/components/assist_pipeline/test_ring_buffer.py @@ -1,4 +1,5 @@ """Tests for audio ring buffer.""" + from homeassistant.components.assist_pipeline.ring_buffer import RingBuffer diff --git a/tests/components/asterisk_mbox/test_init.py b/tests/components/asterisk_mbox/test_init.py index 17a8fe67f36..9c6bbf01f0e 100644 --- a/tests/components/asterisk_mbox/test_init.py +++ b/tests/components/asterisk_mbox/test_init.py @@ -1,4 +1,5 @@ """Test mailbox.""" + from collections.abc import Generator from unittest.mock import Mock, patch diff --git a/tests/components/asuswrt/test_config_flow.py b/tests/components/asuswrt/test_config_flow.py index 0b5b0ace720..08ab2ae6c98 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the AsusWrt config flow.""" + from socket import gaierror from unittest.mock import patch diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 0ee90b111f5..3de830f3f34 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the AsusWrt sensor.""" + from datetime import timedelta from pyasuswrt.exceptions import AsusWrtError, AsusWrtNotAvailableInfoError diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py index da5eefa589b..bc78ee58216 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -1,4 +1,5 @@ """Tests for the Atag climate platform.""" + from unittest.mock import PropertyMock, patch from homeassistant.components.atag.climate import DOMAIN, PRESET_MAP diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index 69e2327c616..138790e77e8 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Atag config flow.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/atag/test_sensors.py b/tests/components/atag/test_sensors.py index 358fe27804a..df3f8101c58 100644 --- a/tests/components/atag/test_sensors.py +++ b/tests/components/atag/test_sensors.py @@ -1,4 +1,5 @@ """Tests for the Atag sensor platform.""" + from homeassistant.components.atag.sensor import SENSORS from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/atag/test_water_heater.py b/tests/components/atag/test_water_heater.py index 49425972d88..e7abd2e07d9 100644 --- a/tests/components/atag/test_water_heater.py +++ b/tests/components/atag/test_water_heater.py @@ -1,4 +1,5 @@ """Tests for the Atag water heater platform.""" + from unittest.mock import patch from homeassistant.components.atag import DOMAIN diff --git a/tests/components/august/conftest.py b/tests/components/august/conftest.py index 1cb52966fea..8640ffeecd4 100644 --- a/tests/components/august/conftest.py +++ b/tests/components/august/conftest.py @@ -1,4 +1,5 @@ """August tests conftest.""" + from unittest.mock import patch import pytest diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 910c1d29ed6..08677764f35 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -1,4 +1,5 @@ """Mocks for the august component.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/august/test_button.py b/tests/components/august/test_button.py index eb0bce2faca..8ae2bc8a70d 100644 --- a/tests/components/august/test_button.py +++ b/tests/components/august/test_button.py @@ -1,4 +1,5 @@ """The button tests for the august platform.""" + from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant diff --git a/tests/components/august/test_camera.py b/tests/components/august/test_camera.py index d1998dc4c49..e19b935b672 100644 --- a/tests/components/august/test_camera.py +++ b/tests/components/august/test_camera.py @@ -1,4 +1,5 @@ """The camera tests for the august platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index f30828a5d72..3eb87c2b096 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -1,4 +1,5 @@ """Test the August config flow.""" + from unittest.mock import patch from yalexs.authenticator import ValidationResult diff --git a/tests/components/august/test_diagnostics.py b/tests/components/august/test_diagnostics.py index 72008f02d03..0b00bde7b23 100644 --- a/tests/components/august/test_diagnostics.py +++ b/tests/components/august/test_diagnostics.py @@ -1,4 +1,5 @@ """Test august diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/august/test_gateway.py b/tests/components/august/test_gateway.py index 2a364304c4b..535e547d915 100644 --- a/tests/components/august/test_gateway.py +++ b/tests/components/august/test_gateway.py @@ -1,4 +1,5 @@ """The gateway tests for the august platform.""" + from unittest.mock import MagicMock, patch from yalexs.authenticator_common import AuthenticationState diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index 81d8992948f..6795491abe3 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -1,4 +1,5 @@ """The tests for the august platform.""" + from unittest.mock import Mock, patch from aiohttp import ClientResponseError diff --git a/tests/components/august/test_sensor.py b/tests/components/august/test_sensor.py index 10b7eb86235..0227ee64ef1 100644 --- a/tests/components/august/test_sensor.py +++ b/tests/components/august/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the august platform.""" + from typing import Any from homeassistant import core as ha diff --git a/tests/components/aurora/test_config_flow.py b/tests/components/aurora/test_config_flow.py index ebd7780900a..087c4f55ee0 100644 --- a/tests/components/aurora/test_config_flow.py +++ b/tests/components/aurora/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Aurora config flow.""" + from unittest.mock import patch from aiohttp import ClientError diff --git a/tests/components/aurora_abb_powerone/test_config_flow.py b/tests/components/aurora_abb_powerone/test_config_flow.py index 3b5b375ed8b..6b7a227ed04 100644 --- a/tests/components/aurora_abb_powerone/test_config_flow.py +++ b/tests/components/aurora_abb_powerone/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Aurora ABB PowerOne Solar PV config flow.""" + from unittest.mock import patch from aurorapy.client import AuroraError, AuroraTimeoutError diff --git a/tests/components/aurora_abb_powerone/test_init.py b/tests/components/aurora_abb_powerone/test_init.py index a330507c779..811a0c84f3d 100644 --- a/tests/components/aurora_abb_powerone/test_init.py +++ b/tests/components/aurora_abb_powerone/test_init.py @@ -1,4 +1,5 @@ """Pytest modules for Aurora ABB Powerone PV inverter sensor integration.""" + from unittest.mock import patch from homeassistant.components.aurora_abb_powerone.const import ( diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index 4dbbf5f0048..d1e8bbb015a 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -1,4 +1,5 @@ """Test the Aurora ABB PowerOne Solar PV sensors.""" + from unittest.mock import patch from aurorapy.client import AuroraError, AuroraTimeoutError diff --git a/tests/components/aussie_broadband/common.py b/tests/components/aussie_broadband/common.py index 5d050388b38..1ad56ab47f6 100644 --- a/tests/components/aussie_broadband/common.py +++ b/tests/components/aussie_broadband/common.py @@ -1,4 +1,5 @@ """Aussie Broadband common helpers for tests.""" + from unittest.mock import patch from homeassistant.components.aussie_broadband.const import ( diff --git a/tests/components/aussie_broadband/test_config_flow.py b/tests/components/aussie_broadband/test_config_flow.py index 9b4be6d9463..37839e92cdd 100644 --- a/tests/components/aussie_broadband/test_config_flow.py +++ b/tests/components/aussie_broadband/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Aussie Broadband config flow.""" + from unittest.mock import patch from aiohttp import ClientConnectionError diff --git a/tests/components/aussie_broadband/test_init.py b/tests/components/aussie_broadband/test_init.py index 9ac7f6628b4..00f7e5e7a83 100644 --- a/tests/components/aussie_broadband/test_init.py +++ b/tests/components/aussie_broadband/test_init.py @@ -1,4 +1,5 @@ """Test the Aussie Broadband init.""" + from unittest.mock import patch from aiohttp import ClientConnectionError diff --git a/tests/components/aussie_broadband/test_sensor.py b/tests/components/aussie_broadband/test_sensor.py index d4c8fb2e32b..ba26e9d67ec 100644 --- a/tests/components/aussie_broadband/test_sensor.py +++ b/tests/components/aussie_broadband/test_sensor.py @@ -1,4 +1,5 @@ """Aussie Broadband sensor platform tests.""" + from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 3926cd2f82b..18b86f561d0 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -1,4 +1,5 @@ """Integration tests for the auth component.""" + from datetime import timedelta from http import HTTPStatus import logging diff --git a/tests/components/auth/test_init_link_user.py b/tests/components/auth/test_init_link_user.py index 507d24e5dac..d1a5fa51af2 100644 --- a/tests/components/auth/test_init_link_user.py +++ b/tests/components/auth/test_init_link_user.py @@ -1,4 +1,5 @@ """Tests for the link user flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/auth/test_login_flow.py b/tests/components/auth/test_login_flow.py index c8b0261b79c..59c066a4f85 100644 --- a/tests/components/auth/test_login_flow.py +++ b/tests/components/auth/test_login_flow.py @@ -1,4 +1,5 @@ """Tests for the login flow.""" + from http import HTTPStatus from typing import Any from unittest.mock import patch diff --git a/tests/components/auth/test_mfa_setup_flow.py b/tests/components/auth/test_mfa_setup_flow.py index d87eb0cba73..f8c3153fba6 100644 --- a/tests/components/auth/test_mfa_setup_flow.py +++ b/tests/components/auth/test_mfa_setup_flow.py @@ -1,4 +1,5 @@ """Tests for the mfa setup flow.""" + from homeassistant import data_entry_flow from homeassistant.auth import auth_manager_from_config from homeassistant.components.auth import mfa_setup_flow diff --git a/tests/components/automation/test_logbook.py b/tests/components/automation/test_logbook.py index 7063f2bfb3e..4aa494ad5b7 100644 --- a/tests/components/automation/test_logbook.py +++ b/tests/components/automation/test_logbook.py @@ -1,4 +1,5 @@ """Test automation logbook.""" + from homeassistant.components import automation from homeassistant.core import Context, HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/automation/test_recorder.py b/tests/components/automation/test_recorder.py index 4aa84dbd602..c983cc949ad 100644 --- a/tests/components/automation/test_recorder.py +++ b/tests/components/automation/test_recorder.py @@ -1,4 +1,5 @@ """The tests for automation recorder.""" + from __future__ import annotations import pytest diff --git a/tests/components/awair/test_config_flow.py b/tests/components/awair/test_config_flow.py index b9f466174af..d03dde0d867 100644 --- a/tests/components/awair/test_config_flow.py +++ b/tests/components/awair/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Awair config flow.""" + from typing import Any from unittest.mock import Mock, patch diff --git a/tests/components/awair/test_init.py b/tests/components/awair/test_init.py index f3a4bb636e6..954dd51a5e5 100644 --- a/tests/components/awair/test_init.py +++ b/tests/components/awair/test_init.py @@ -1,4 +1,5 @@ """Test Awair init.""" + from unittest.mock import patch from homeassistant.core import HomeAssistant diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index 849ac59a22f..8af1fdd9c7c 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Awair sensor platform.""" + from unittest.mock import patch from homeassistant.components.awair.const import ( diff --git a/tests/components/axis/conftest.py b/tests/components/axis/conftest.py index a15abb224a4..f3c395d092d 100644 --- a/tests/components/axis/conftest.py +++ b/tests/components/axis/conftest.py @@ -1,4 +1,5 @@ """Axis conftest.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 41af5a58876..76b2d8e2f5c 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -1,4 +1,5 @@ """Test Axis config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index 136ef924c8f..1d36e0bbda0 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -1,4 +1,5 @@ """Test Axis device.""" + from ipaddress import ip_address from unittest import mock from unittest.mock import Mock, patch diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index 5482e3c5223..7a22597197b 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -1,4 +1,5 @@ """Test Axis component setup process.""" + from unittest.mock import AsyncMock, Mock, patch import pytest diff --git a/tests/components/axis/test_light.py b/tests/components/axis/test_light.py index b5503e1486a..2b46330bd2d 100644 --- a/tests/components/axis/test_light.py +++ b/tests/components/axis/test_light.py @@ -1,4 +1,5 @@ """Axis light platform tests.""" + from unittest.mock import patch from axis.vapix.models.api import CONTEXT diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index 14e27c3437a..13411d86943 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -1,4 +1,5 @@ """Axis switch platform tests.""" + from unittest.mock import patch from axis.vapix.models.api import CONTEXT diff --git a/tests/components/azure_devops/test_config_flow.py b/tests/components/azure_devops/test_config_flow.py index 713d3f31a2f..08856fccb17 100644 --- a/tests/components/azure_devops/test_config_flow.py +++ b/tests/components/azure_devops/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Azure DevOps config flow.""" + from unittest.mock import patch from aioazuredevops.core import DevOpsProject diff --git a/tests/components/azure_event_hub/conftest.py b/tests/components/azure_event_hub/conftest.py index fac93e32ab9..622b11000d7 100644 --- a/tests/components/azure_event_hub/conftest.py +++ b/tests/components/azure_event_hub/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for AEH.""" + from dataclasses import dataclass from datetime import timedelta import logging diff --git a/tests/components/azure_event_hub/const.py b/tests/components/azure_event_hub/const.py index 1daf100238c..1d887139479 100644 --- a/tests/components/azure_event_hub/const.py +++ b/tests/components/azure_event_hub/const.py @@ -1,4 +1,5 @@ """Constants for testing AEH.""" + from homeassistant.components.azure_event_hub.const import ( CONF_EVENT_HUB_CON_STRING, CONF_EVENT_HUB_INSTANCE_NAME, diff --git a/tests/components/azure_event_hub/test_init.py b/tests/components/azure_event_hub/test_init.py index 13ded3c31be..0d5cfff80e9 100644 --- a/tests/components/azure_event_hub/test_init.py +++ b/tests/components/azure_event_hub/test_init.py @@ -1,4 +1,5 @@ """Test the init functions for AEH.""" + from datetime import timedelta import logging from unittest.mock import patch diff --git a/tests/components/backup/common.py b/tests/components/backup/common.py index 824057b6500..70b33d2de3f 100644 --- a/tests/components/backup/common.py +++ b/tests/components/backup/common.py @@ -1,4 +1,5 @@ """Common helpers for the Backup integration tests.""" + from __future__ import annotations from pathlib import Path diff --git a/tests/components/backup/test_http.py b/tests/components/backup/test_http.py index cb10d1b9947..02e446f5b88 100644 --- a/tests/components/backup/test_http.py +++ b/tests/components/backup/test_http.py @@ -1,4 +1,5 @@ """Tests for the Backup integration.""" + from unittest.mock import patch from aiohttp import web diff --git a/tests/components/backup/test_init.py b/tests/components/backup/test_init.py index 1e164abb1bb..9fdfa978f94 100644 --- a/tests/components/backup/test_init.py +++ b/tests/components/backup/test_init.py @@ -1,4 +1,5 @@ """Tests for the Backup integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index 129607e7304..00581597524 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -1,4 +1,5 @@ """Tests for the Backup integration.""" + from __future__ import annotations from pathlib import Path diff --git a/tests/components/backup/test_websocket.py b/tests/components/backup/test_websocket.py index e79b958be20..18c5ff355c5 100644 --- a/tests/components/backup/test_websocket.py +++ b/tests/components/backup/test_websocket.py @@ -1,4 +1,5 @@ """Tests for the Backup integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/baf/test_config_flow.py b/tests/components/baf/test_config_flow.py index f15c624447c..0369e1e1955 100644 --- a/tests/components/baf/test_config_flow.py +++ b/tests/components/baf/test_config_flow.py @@ -1,4 +1,5 @@ """Test the baf config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/baf/test_init.py b/tests/components/baf/test_init.py index c87237892ad..f2616fdd96d 100644 --- a/tests/components/baf/test_init.py +++ b/tests/components/baf/test_init.py @@ -1,4 +1,5 @@ """Test the baf init flow.""" + from unittest.mock import patch from aiobafi6.exceptions import DeviceUUIDMismatchError diff --git a/tests/components/balboa/conftest.py b/tests/components/balboa/conftest.py index fa2787dd6e2..fce022572c3 100644 --- a/tests/components/balboa/conftest.py +++ b/tests/components/balboa/conftest.py @@ -1,4 +1,5 @@ """Provide common fixtures.""" + from __future__ import annotations from collections.abc import Callable, Generator diff --git a/tests/components/balboa/test_binary_sensor.py b/tests/components/balboa/test_binary_sensor.py index ee5f2bc353c..bcce2b96a0b 100644 --- a/tests/components/balboa/test_binary_sensor.py +++ b/tests/components/balboa/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests of the climate entity of the balboa integration.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/balboa/test_climate.py b/tests/components/balboa/test_climate.py index a4b758eeab8..c75244ecb94 100644 --- a/tests/components/balboa/test_climate.py +++ b/tests/components/balboa/test_climate.py @@ -1,4 +1,5 @@ """Tests of the climate entity of the balboa integration.""" + from __future__ import annotations from unittest.mock import MagicMock, patch diff --git a/tests/components/balboa/test_config_flow.py b/tests/components/balboa/test_config_flow.py index 95c415b8909..d2a8def9e63 100644 --- a/tests/components/balboa/test_config_flow.py +++ b/tests/components/balboa/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Balboa Spa Client config flow.""" + from unittest.mock import MagicMock, patch from pybalboa.exceptions import SpaConnectionError diff --git a/tests/components/balboa/test_fan.py b/tests/components/balboa/test_fan.py index fbf9f1854cd..878a14784f7 100644 --- a/tests/components/balboa/test_fan.py +++ b/tests/components/balboa/test_fan.py @@ -1,4 +1,5 @@ """Tests of the pump fan entity of the balboa integration.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/balboa/test_light.py b/tests/components/balboa/test_light.py index e20e55c5980..da969a7e2d8 100644 --- a/tests/components/balboa/test_light.py +++ b/tests/components/balboa/test_light.py @@ -1,4 +1,5 @@ """Tests of the light entity of the balboa integration.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/bang_olufsen/conftest.py b/tests/components/bang_olufsen/conftest.py index e714f55de4a..b7b6b84b7fd 100644 --- a/tests/components/bang_olufsen/conftest.py +++ b/tests/components/bang_olufsen/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for bang_olufsen.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index c902caf31ae..23907586c01 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -1,4 +1,5 @@ """The test for binary_sensor device automation.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index 47abb29ae86..bd357f25ba3 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -1,4 +1,5 @@ """The test for binary_sensor device automation.""" + from datetime import timedelta import pytest diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 6ca189113b9..059e387c4fd 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -1,4 +1,5 @@ """The tests for the Binary sensor component.""" + from collections.abc import Generator from unittest import mock diff --git a/tests/components/binary_sensor/test_significant_change.py b/tests/components/binary_sensor/test_significant_change.py index fed3581eeee..3042630540a 100644 --- a/tests/components/binary_sensor/test_significant_change.py +++ b/tests/components/binary_sensor/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Binary Sensor significant change platform.""" + from homeassistant.components.binary_sensor.significant_change import ( async_check_significant_change, ) diff --git a/tests/components/blackbird/test_media_player.py b/tests/components/blackbird/test_media_player.py index de8be9d4ed5..5e6ece7d792 100644 --- a/tests/components/blackbird/test_media_player.py +++ b/tests/components/blackbird/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the Monoprice Blackbird media player platform.""" + from collections import defaultdict from unittest import mock diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index 82698633c30..868d936d83a 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -1,4 +1,5 @@ """PyTest fixtures and test helpers.""" + from unittest import mock from unittest.mock import AsyncMock, PropertyMock, patch diff --git a/tests/components/blebox/test_binary_sensor.py b/tests/components/blebox/test_binary_sensor.py index 3c05a425b12..f04feab86de 100644 --- a/tests/components/blebox/test_binary_sensor.py +++ b/tests/components/blebox/test_binary_sensor.py @@ -1,4 +1,5 @@ """Blebox binary_sensor entities test.""" + from unittest.mock import AsyncMock, PropertyMock import blebox_uniapi diff --git a/tests/components/blebox/test_config_flow.py b/tests/components/blebox/test_config_flow.py index dafba61d77a..3f59ed022fd 100644 --- a/tests/components/blebox/test_config_flow.py +++ b/tests/components/blebox/test_config_flow.py @@ -1,4 +1,5 @@ """Test Home Assistant config flow for BleBox devices.""" + from ipaddress import ip_address from unittest.mock import DEFAULT, AsyncMock, PropertyMock, patch diff --git a/tests/components/blink/conftest.py b/tests/components/blink/conftest.py index d15d35e1c08..b18fdf7615e 100644 --- a/tests/components/blink/conftest.py +++ b/tests/components/blink/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Blink integration tests.""" + from unittest.mock import AsyncMock, MagicMock, create_autospec, patch from uuid import uuid4 diff --git a/tests/components/blink/test_config_flow.py b/tests/components/blink/test_config_flow.py index e5ce3c83fb7..304f1bd97a9 100644 --- a/tests/components/blink/test_config_flow.py +++ b/tests/components/blink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Blink config flow.""" + from unittest.mock import patch from blinkpy.auth import LoginError diff --git a/tests/components/blink/test_diagnostics.py b/tests/components/blink/test_diagnostics.py index d447203dae6..3b120d23038 100644 --- a/tests/components/blink/test_diagnostics.py +++ b/tests/components/blink/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Blink diagnostics.""" + from unittest.mock import MagicMock from syrupy import SnapshotAssertion diff --git a/tests/components/blink/test_init.py b/tests/components/blink/test_init.py index 454195c4d60..1f3a4c956c4 100644 --- a/tests/components/blink/test_init.py +++ b/tests/components/blink/test_init.py @@ -1,4 +1,5 @@ """Test the Blink init.""" + from unittest.mock import AsyncMock, MagicMock from aiohttp import ClientError diff --git a/tests/components/blink/test_services.py b/tests/components/blink/test_services.py index 40b9cce7bad..d2685bd04eb 100644 --- a/tests/components/blink/test_services.py +++ b/tests/components/blink/test_services.py @@ -1,4 +1,5 @@ """Test the Blink services.""" + from unittest.mock import AsyncMock, MagicMock, Mock import pytest diff --git a/tests/components/blue_current/test_config_flow.py b/tests/components/blue_current/test_config_flow.py index b34decd8264..f56e722d785 100644 --- a/tests/components/blue_current/test_config_flow.py +++ b/tests/components/blue_current/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Blue Current config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/blue_current/test_init.py b/tests/components/blue_current/test_init.py index ce6eb2d9716..37c14922674 100644 --- a/tests/components/blue_current/test_init.py +++ b/tests/components/blue_current/test_init.py @@ -1,4 +1,5 @@ """Test Blue Current Init Component.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/blue_current/test_sensor.py b/tests/components/blue_current/test_sensor.py index 68b42498c2f..5213cc0ff72 100644 --- a/tests/components/blue_current/test_sensor.py +++ b/tests/components/blue_current/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Blue current sensors.""" + from datetime import datetime import pytest diff --git a/tests/components/bluemaestro/test_config_flow.py b/tests/components/bluemaestro/test_config_flow.py index df3a6385e7c..f87ea053ffe 100644 --- a/tests/components/bluemaestro/test_config_flow.py +++ b/tests/components/bluemaestro/test_config_flow.py @@ -1,4 +1,5 @@ """Test the BlueMaestro config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/bluemaestro/test_sensor.py b/tests/components/bluemaestro/test_sensor.py index db66fbee2c2..fdcb16730ff 100644 --- a/tests/components/bluemaestro/test_sensor.py +++ b/tests/components/bluemaestro/test_sensor.py @@ -1,4 +1,5 @@ """Test the BlueMaestro sensors.""" + from homeassistant.components.bluemaestro.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/blueprint/test_websocket_api.py b/tests/components/blueprint/test_websocket_api.py index b0439896c25..a5ed448aeca 100644 --- a/tests/components/blueprint/test_websocket_api.py +++ b/tests/components/blueprint/test_websocket_api.py @@ -1,4 +1,5 @@ """Test websocket API.""" + from pathlib import Path from unittest.mock import Mock, patch diff --git a/tests/components/bluetooth/test_active_update_coordinator.py b/tests/components/bluetooth/test_active_update_coordinator.py index 83fee1456cd..995b208ead9 100644 --- a/tests/components/bluetooth/test_active_update_coordinator.py +++ b/tests/components/bluetooth/test_active_update_coordinator.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration ActiveBluetoothDataUpdateCoordinator.""" + from __future__ import annotations import asyncio diff --git a/tests/components/bluetooth/test_active_update_processor.py b/tests/components/bluetooth/test_active_update_processor.py index 00562a20daf..13045048fc1 100644 --- a/tests/components/bluetooth/test_active_update_processor.py +++ b/tests/components/bluetooth/test_active_update_processor.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration PassiveBluetoothDataUpdateCoordinator.""" + from __future__ import annotations import asyncio diff --git a/tests/components/bluetooth/test_advertisement_tracker.py b/tests/components/bluetooth/test_advertisement_tracker.py index f90b82fc379..12d34e0a7bc 100644 --- a/tests/components/bluetooth/test_advertisement_tracker.py +++ b/tests/components/bluetooth/test_advertisement_tracker.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration advertisement tracking.""" + from datetime import timedelta import time diff --git a/tests/components/bluetooth/test_base_scanner.py b/tests/components/bluetooth/test_base_scanner.py index e1d64115e86..cd3cea5c33a 100644 --- a/tests/components/bluetooth/test_base_scanner.py +++ b/tests/components/bluetooth/test_base_scanner.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth base scanner models.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/bluetooth/test_config_flow.py b/tests/components/bluetooth/test_config_flow.py index 1489f349cf4..bc8e93d6b38 100644 --- a/tests/components/bluetooth/test_config_flow.py +++ b/tests/components/bluetooth/test_config_flow.py @@ -1,4 +1,5 @@ """Test the bluetooth config flow.""" + from unittest.mock import MagicMock, patch from bluetooth_adapters import DEFAULT_ADDRESS, AdapterDetails diff --git a/tests/components/bluetooth/test_diagnostics.py b/tests/components/bluetooth/test_diagnostics.py index eae5f6507ac..0cd96854320 100644 --- a/tests/components/bluetooth/test_diagnostics.py +++ b/tests/components/bluetooth/test_diagnostics.py @@ -1,4 +1,5 @@ """Test bluetooth diagnostics.""" + from unittest.mock import ANY, MagicMock, patch from bleak.backends.scanner import AdvertisementData, BLEDevice diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index 4726c12f681..87fbdfb0936 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration manager.""" + from collections.abc import Generator from datetime import timedelta import time diff --git a/tests/components/bluetooth/test_models.py b/tests/components/bluetooth/test_models.py index 680d7c2e798..837a95ea2d1 100644 --- a/tests/components/bluetooth/test_models.py +++ b/tests/components/bluetooth/test_models.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration models.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/bluetooth/test_passive_update_coordinator.py b/tests/components/bluetooth/test_passive_update_coordinator.py index b6e50ebc565..ba8e150d028 100644 --- a/tests/components/bluetooth/test_passive_update_coordinator.py +++ b/tests/components/bluetooth/test_passive_update_coordinator.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration PassiveBluetoothDataUpdateCoordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/bluetooth/test_passive_update_processor.py b/tests/components/bluetooth/test_passive_update_processor.py index 4c9aa7a94b8..d7248e8f405 100644 --- a/tests/components/bluetooth/test_passive_update_processor.py +++ b/tests/components/bluetooth/test_passive_update_processor.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/bluetooth/test_usage.py b/tests/components/bluetooth/test_usage.py index 0edff02aa0e..35aa0eb9022 100644 --- a/tests/components/bluetooth/test_usage.py +++ b/tests/components/bluetooth/test_usage.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration.""" + from unittest.mock import patch import bleak diff --git a/tests/components/bluetooth/test_wrappers.py b/tests/components/bluetooth/test_wrappers.py index e3531a57447..9a191cdbf90 100644 --- a/tests/components/bluetooth/test_wrappers.py +++ b/tests/components/bluetooth/test_wrappers.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration.""" + from __future__ import annotations from contextlib import contextmanager diff --git a/tests/components/bluetooth_le_tracker/test_device_tracker.py b/tests/components/bluetooth_le_tracker/test_device_tracker.py index 78ce96bde99..cbf823e4cee 100644 --- a/tests/components/bluetooth_le_tracker/test_device_tracker.py +++ b/tests/components/bluetooth_le_tracker/test_device_tracker.py @@ -1,4 +1,5 @@ """Test Bluetooth LE device tracker.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/bmw_connected_drive/test_button.py b/tests/components/bmw_connected_drive/test_button.py index 9cea5f2fd91..f55e199682f 100644 --- a/tests/components/bmw_connected_drive/test_button.py +++ b/tests/components/bmw_connected_drive/test_button.py @@ -1,4 +1,5 @@ """Test BMW buttons.""" + from unittest.mock import AsyncMock from bimmer_connected.models import MyBMWRemoteServiceError diff --git a/tests/components/bmw_connected_drive/test_config_flow.py b/tests/components/bmw_connected_drive/test_config_flow.py index 3540df851e9..07c84e09707 100644 --- a/tests/components/bmw_connected_drive/test_config_flow.py +++ b/tests/components/bmw_connected_drive/test_config_flow.py @@ -1,4 +1,5 @@ """Test the for the BMW Connected Drive config flow.""" + from copy import deepcopy from unittest.mock import patch diff --git a/tests/components/bmw_connected_drive/test_coordinator.py b/tests/components/bmw_connected_drive/test_coordinator.py index bf47b7fed29..c449a9c4a59 100644 --- a/tests/components/bmw_connected_drive/test_coordinator.py +++ b/tests/components/bmw_connected_drive/test_coordinator.py @@ -1,4 +1,5 @@ """Test BMW coordinator.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/bmw_connected_drive/test_init.py b/tests/components/bmw_connected_drive/test_init.py index c57bfc5a9b0..b8081d8d119 100644 --- a/tests/components/bmw_connected_drive/test_init.py +++ b/tests/components/bmw_connected_drive/test_init.py @@ -1,4 +1,5 @@ """Test Axis component setup process.""" + from unittest.mock import patch import pytest diff --git a/tests/components/bmw_connected_drive/test_number.py b/tests/components/bmw_connected_drive/test_number.py index bcd880fa0a6..30214555b92 100644 --- a/tests/components/bmw_connected_drive/test_number.py +++ b/tests/components/bmw_connected_drive/test_number.py @@ -1,4 +1,5 @@ """Test BMW numbers.""" + from unittest.mock import AsyncMock from bimmer_connected.models import MyBMWAPIError, MyBMWRemoteServiceError diff --git a/tests/components/bmw_connected_drive/test_select.py b/tests/components/bmw_connected_drive/test_select.py index 1860ed19720..cb20805c809 100644 --- a/tests/components/bmw_connected_drive/test_select.py +++ b/tests/components/bmw_connected_drive/test_select.py @@ -1,4 +1,5 @@ """Test BMW selects.""" + from unittest.mock import AsyncMock from bimmer_connected.models import MyBMWAPIError, MyBMWRemoteServiceError diff --git a/tests/components/bmw_connected_drive/test_sensor.py b/tests/components/bmw_connected_drive/test_sensor.py index c6cb12cf047..a066b967250 100644 --- a/tests/components/bmw_connected_drive/test_sensor.py +++ b/tests/components/bmw_connected_drive/test_sensor.py @@ -1,4 +1,5 @@ """Test BMW sensors.""" + from freezegun import freeze_time import pytest import respx diff --git a/tests/components/bmw_connected_drive/test_switch.py b/tests/components/bmw_connected_drive/test_switch.py index c050f4b6cc2..b759c33ca3b 100644 --- a/tests/components/bmw_connected_drive/test_switch.py +++ b/tests/components/bmw_connected_drive/test_switch.py @@ -1,4 +1,5 @@ """Test BMW switches.""" + from unittest.mock import AsyncMock from bimmer_connected.models import MyBMWAPIError, MyBMWRemoteServiceError diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index d97ef9a7a31..6f1cd30a484 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for Bond.""" + from __future__ import annotations from contextlib import nullcontext diff --git a/tests/components/bond/conftest.py b/tests/components/bond/conftest.py index 378c3340e29..d9067194e17 100644 --- a/tests/components/bond/conftest.py +++ b/tests/components/bond/conftest.py @@ -1,2 +1,3 @@ """bond conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/bond/test_button.py b/tests/components/bond/test_button.py index 6984831626d..8c8f38db72b 100644 --- a/tests/components/bond/test_button.py +++ b/tests/components/bond/test_button.py @@ -1,4 +1,5 @@ """Tests for the Bond button device.""" + from bond_async import Action, DeviceType from homeassistant.components.bond.button import STEP_SIZE diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 7d639309ddc..9e5457197b2 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Bond config flow.""" + from __future__ import annotations from http import HTTPStatus diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index e489f8550d6..4af096f1431 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -1,4 +1,5 @@ """Tests for the Bond cover device.""" + from datetime import timedelta from bond_async import Action, DeviceType diff --git a/tests/components/bond/test_diagnostics.py b/tests/components/bond/test_diagnostics.py index f8d0313ee9c..d919c74178b 100644 --- a/tests/components/bond/test_diagnostics.py +++ b/tests/components/bond/test_diagnostics.py @@ -1,4 +1,5 @@ """Test bond diagnostics.""" + from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/bond/test_entity.py b/tests/components/bond/test_entity.py index bcb61ddc92d..a2d38787ed1 100644 --- a/tests/components/bond/test_entity.py +++ b/tests/components/bond/test_entity.py @@ -1,4 +1,5 @@ """Tests for the Bond entities.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index e202433c8d6..766e1e8ad07 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -1,4 +1,5 @@ """Tests for the Bond fan device.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index 6453fa39807..db10a842b27 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -1,4 +1,5 @@ """Tests for the Bond module.""" + from unittest.mock import MagicMock, Mock from aiohttp import ClientConnectionError, ClientResponseError diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index 10395f395dd..eed8166b1a6 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -1,4 +1,5 @@ """Tests for the Bond light device.""" + from datetime import timedelta from bond_async import Action, DeviceType diff --git a/tests/components/bond/test_switch.py b/tests/components/bond/test_switch.py index 1ab9ef2165c..6bef33939ab 100644 --- a/tests/components/bond/test_switch.py +++ b/tests/components/bond/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Bond switch device.""" + from datetime import timedelta from bond_async import Action, DeviceType diff --git a/tests/components/bosch_shc/test_config_flow.py b/tests/components/bosch_shc/test_config_flow.py index b2920cfde0a..99def2b0f29 100644 --- a/tests/components/bosch_shc/test_config_flow.py +++ b/tests/components/bosch_shc/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Bosch SHC config flow.""" + from ipaddress import ip_address from unittest.mock import PropertyMock, mock_open, patch diff --git a/tests/components/braviatv/conftest.py b/tests/components/braviatv/conftest.py index e4ee2ebc868..33f55fbb390 100644 --- a/tests/components/braviatv/conftest.py +++ b/tests/components/braviatv/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for Bravia TV.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/braviatv/test_config_flow.py b/tests/components/braviatv/test_config_flow.py index 0f1d08792fa..36673cfe011 100644 --- a/tests/components/braviatv/test_config_flow.py +++ b/tests/components/braviatv/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Bravia TV config flow.""" + from unittest.mock import patch from pybravia import ( diff --git a/tests/components/braviatv/test_diagnostics.py b/tests/components/braviatv/test_diagnostics.py index d0974774e7b..f5c174ee672 100644 --- a/tests/components/braviatv/test_diagnostics.py +++ b/tests/components/braviatv/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the BraviaTV diagnostics.""" + from unittest.mock import patch from syrupy import SnapshotAssertion diff --git a/tests/components/bring/conftest.py b/tests/components/bring/conftest.py index 0b15d31eecb..c296642f020 100644 --- a/tests/components/bring/conftest.py +++ b/tests/components/bring/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Bring! tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/bring/test_config_flow.py b/tests/components/bring/test_config_flow.py index f5a8398539a..29abad94fad 100644 --- a/tests/components/bring/test_config_flow.py +++ b/tests/components/bring/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Bring! config flow.""" + from unittest.mock import AsyncMock from bring_api.exceptions import ( diff --git a/tests/components/bring/test_init.py b/tests/components/bring/test_init.py index e5fcedefc22..6bf9fd1cb54 100644 --- a/tests/components/bring/test_init.py +++ b/tests/components/bring/test_init.py @@ -1,4 +1,5 @@ """Unit tests for the bring integration.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/broadlink/conftest.py b/tests/components/broadlink/conftest.py index 0a9ee4813da..c20c444c342 100644 --- a/tests/components/broadlink/conftest.py +++ b/tests/components/broadlink/conftest.py @@ -1,4 +1,5 @@ """Broadlink test helpers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/broadlink/test_device.py b/tests/components/broadlink/test_device.py index b97911262ef..52aad8f4f63 100644 --- a/tests/components/broadlink/test_device.py +++ b/tests/components/broadlink/test_device.py @@ -1,4 +1,5 @@ """Tests for Broadlink devices.""" + from unittest.mock import patch import broadlink.exceptions as blke diff --git a/tests/components/broadlink/test_heartbeat.py b/tests/components/broadlink/test_heartbeat.py index 566dd4ba86f..92046000268 100644 --- a/tests/components/broadlink/test_heartbeat.py +++ b/tests/components/broadlink/test_heartbeat.py @@ -1,4 +1,5 @@ """Tests for Broadlink heartbeats.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/broadlink/test_remote.py b/tests/components/broadlink/test_remote.py index 5665f7529d5..a55bf63f227 100644 --- a/tests/components/broadlink/test_remote.py +++ b/tests/components/broadlink/test_remote.py @@ -1,4 +1,5 @@ """Tests for Broadlink remotes.""" + from base64 import b64decode from unittest.mock import call diff --git a/tests/components/broadlink/test_sensors.py b/tests/components/broadlink/test_sensors.py index e00350b7627..e7908104663 100644 --- a/tests/components/broadlink/test_sensors.py +++ b/tests/components/broadlink/test_sensors.py @@ -1,4 +1,5 @@ """Tests for Broadlink sensors.""" + from datetime import timedelta from homeassistant.components.broadlink.const import DOMAIN diff --git a/tests/components/broadlink/test_switch.py b/tests/components/broadlink/test_switch.py index 93bad2db295..2d4eb8e0e0b 100644 --- a/tests/components/broadlink/test_switch.py +++ b/tests/components/broadlink/test_switch.py @@ -1,4 +1,5 @@ """Tests for Broadlink switches.""" + from homeassistant.components.broadlink.const import DOMAIN from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, diff --git a/tests/components/brother/conftest.py b/tests/components/brother/conftest.py index 9e81cce9d12..1834cb2c36b 100644 --- a/tests/components/brother/conftest.py +++ b/tests/components/brother/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for brother.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index f83f882b8a0..f0c55549fd7 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Brother Printer config flow.""" + from ipaddress import ip_address import json from unittest.mock import patch diff --git a/tests/components/brother/test_diagnostics.py b/tests/components/brother/test_diagnostics.py index 26ed77931b4..c935ae5e50a 100644 --- a/tests/components/brother/test_diagnostics.py +++ b/tests/components/brother/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Brother diagnostics.""" + from datetime import datetime import json from unittest.mock import Mock, patch diff --git a/tests/components/brother/test_init.py b/tests/components/brother/test_init.py index cd439a3a41f..331f022e2a9 100644 --- a/tests/components/brother/test_init.py +++ b/tests/components/brother/test_init.py @@ -1,4 +1,5 @@ """Test init of Brother integration.""" + from unittest.mock import patch from brother import SnmpError diff --git a/tests/components/brother/test_sensor.py b/tests/components/brother/test_sensor.py index 58690e5605e..25015b28269 100644 --- a/tests/components/brother/test_sensor.py +++ b/tests/components/brother/test_sensor.py @@ -1,4 +1,5 @@ """Test sensor of Brother integration.""" + from datetime import datetime, timedelta import json from unittest.mock import Mock, patch diff --git a/tests/components/brottsplatskartan/conftest.py b/tests/components/brottsplatskartan/conftest.py index 430fec9620b..6d3769edd71 100644 --- a/tests/components/brottsplatskartan/conftest.py +++ b/tests/components/brottsplatskartan/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for Brottplatskartan.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/brottsplatskartan/test_config_flow.py b/tests/components/brottsplatskartan/test_config_flow.py index f27139ad381..3571277c6f3 100644 --- a/tests/components/brottsplatskartan/test_config_flow.py +++ b/tests/components/brottsplatskartan/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Brottsplatskartan config flow.""" + from __future__ import annotations import pytest diff --git a/tests/components/brottsplatskartan/test_init.py b/tests/components/brottsplatskartan/test_init.py index 6205fddc9da..e5d18d28203 100644 --- a/tests/components/brottsplatskartan/test_init.py +++ b/tests/components/brottsplatskartan/test_init.py @@ -1,4 +1,5 @@ """Test Brottsplatskartan component setup process.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/brunt/conftest.py b/tests/components/brunt/conftest.py index 8ae0bbaf317..f9a518292ac 100644 --- a/tests/components/brunt/conftest.py +++ b/tests/components/brunt/conftest.py @@ -1,4 +1,5 @@ """Configuration for brunt tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/brunt/test_config_flow.py b/tests/components/brunt/test_config_flow.py index c8b0a3955c8..dfa1e9f992a 100644 --- a/tests/components/brunt/test_config_flow.py +++ b/tests/components/brunt/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Brunt config flow.""" + from unittest.mock import AsyncMock, Mock, patch from aiohttp import ClientResponseError diff --git a/tests/components/bsblan/conftest.py b/tests/components/bsblan/conftest.py index b7939e4cb50..98e2410383b 100644 --- a/tests/components/bsblan/conftest.py +++ b/tests/components/bsblan/conftest.py @@ -1,4 +1,5 @@ """Fixtures for BSBLAN integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/bsblan/test_config_flow.py b/tests/components/bsblan/test_config_flow.py index d82c32463d8..db2b0f8f85c 100644 --- a/tests/components/bsblan/test_config_flow.py +++ b/tests/components/bsblan/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the BSBLan device config flow.""" + from unittest.mock import AsyncMock, MagicMock from bsblan import BSBLANConnectionError diff --git a/tests/components/bsblan/test_init.py b/tests/components/bsblan/test_init.py index 34ee30a35e1..a9c3605f67f 100644 --- a/tests/components/bsblan/test_init.py +++ b/tests/components/bsblan/test_init.py @@ -1,4 +1,5 @@ """Tests for the BSBLan integration.""" + from unittest.mock import MagicMock from bsblan import BSBLANConnectionError diff --git a/tests/components/bthome/test_binary_sensor.py b/tests/components/bthome/test_binary_sensor.py index c38bec3ba44..41d467410c1 100644 --- a/tests/components/bthome/test_binary_sensor.py +++ b/tests/components/bthome/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test BTHome binary sensors.""" + from datetime import timedelta import logging import time diff --git a/tests/components/bthome/test_config_flow.py b/tests/components/bthome/test_config_flow.py index ee983148fd4..b6a3498d570 100644 --- a/tests/components/bthome/test_config_flow.py +++ b/tests/components/bthome/test_config_flow.py @@ -1,4 +1,5 @@ """Test the BTHome config flow.""" + from unittest.mock import patch from bthome_ble import BTHomeBluetoothDeviceData as DeviceData diff --git a/tests/components/bthome/test_logbook.py b/tests/components/bthome/test_logbook.py index f68197f9fe5..60b211e1d75 100644 --- a/tests/components/bthome/test_logbook.py +++ b/tests/components/bthome/test_logbook.py @@ -1,4 +1,5 @@ """The tests for bthome logbook.""" + from homeassistant.components.bthome.const import ( BTHOME_BLE_EVENT, DOMAIN, diff --git a/tests/components/bthome/test_sensor.py b/tests/components/bthome/test_sensor.py index 481520f0434..ce0d1de4f26 100644 --- a/tests/components/bthome/test_sensor.py +++ b/tests/components/bthome/test_sensor.py @@ -1,4 +1,5 @@ """Test the BTHome sensors.""" + from datetime import timedelta import logging import time diff --git a/tests/components/buienradar/conftest.py b/tests/components/buienradar/conftest.py index b896e54e628..616976b292f 100644 --- a/tests/components/buienradar/conftest.py +++ b/tests/components/buienradar/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for buienradar2.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/buienradar/test_init.py b/tests/components/buienradar/test_init.py index 1f46cd667ea..dd09e3e5236 100644 --- a/tests/components/buienradar/test_init.py +++ b/tests/components/buienradar/test_init.py @@ -1,4 +1,5 @@ """Tests for the buienradar component.""" + from homeassistant.components.buienradar.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index fb83d7a13db..ea5ef74f72e 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Buienradar sensor platform.""" + from http import HTTPStatus from homeassistant.components.buienradar.const import DOMAIN diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index d4c4af5f62a..6a3b3e202b2 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -1,4 +1,5 @@ """The tests for the buienradar weather component.""" + from http import HTTPStatus from homeassistant.components.buienradar.const import DOMAIN diff --git a/tests/components/button/test_device_trigger.py b/tests/components/button/test_device_trigger.py index e231fc3ae19..ba40fb30d08 100644 --- a/tests/components/button/test_device_trigger.py +++ b/tests/components/button/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Button device triggers.""" + from __future__ import annotations import pytest diff --git a/tests/components/button/test_init.py b/tests/components/button/test_init.py index acf7bd39e10..5290d145d69 100644 --- a/tests/components/button/test_init.py +++ b/tests/components/button/test_init.py @@ -1,4 +1,5 @@ """The tests for the Button component.""" + from collections.abc import Generator from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/caldav/conftest.py b/tests/components/caldav/conftest.py index 504103afe13..686a0b75a76 100644 --- a/tests/components/caldav/conftest.py +++ b/tests/components/caldav/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for caldav.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/caldav/test_calendar.py b/tests/components/caldav/test_calendar.py index 11f1524b4b0..942a4913f6e 100644 --- a/tests/components/caldav/test_calendar.py +++ b/tests/components/caldav/test_calendar.py @@ -1,4 +1,5 @@ """The tests for the webdav calendar component.""" + from collections.abc import Awaitable, Callable import datetime from http import HTTPStatus diff --git a/tests/components/caldav/test_todo.py b/tests/components/caldav/test_todo.py index 7b67a1af714..67fc5f7f443 100644 --- a/tests/components/caldav/test_todo.py +++ b/tests/components/caldav/test_todo.py @@ -1,4 +1,5 @@ """The tests for the webdav todo component.""" + from datetime import UTC, date, datetime from typing import Any from unittest.mock import MagicMock, Mock diff --git a/tests/components/calendar/conftest.py b/tests/components/calendar/conftest.py index 29d4bb9f5ff..7a3f27c8e08 100644 --- a/tests/components/calendar/conftest.py +++ b/tests/components/calendar/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for calendar sensor platforms.""" + from collections.abc import Generator import datetime import secrets diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index d786ce8d8ad..c2842eafb2c 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -1,4 +1,5 @@ """The tests for the calendar component.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/calendar/test_recorder.py b/tests/components/calendar/test_recorder.py index ef6c7658a89..aeddebc226c 100644 --- a/tests/components/calendar/test_recorder.py +++ b/tests/components/calendar/test_recorder.py @@ -1,4 +1,5 @@ """The tests for calendar recorder.""" + from datetime import timedelta from typing import Any diff --git a/tests/components/calendar/test_trigger.py b/tests/components/calendar/test_trigger.py index 0111f11c27b..6f0ccdece28 100644 --- a/tests/components/calendar/test_trigger.py +++ b/tests/components/calendar/test_trigger.py @@ -6,6 +6,7 @@ tests use a fixture that mocks out events returned by the calendar entity, and create events using a relative time offset and then advance the clock forward exercising the triggers. """ + from __future__ import annotations from collections.abc import AsyncIterator, Callable, Generator diff --git a/tests/components/camera/common.py b/tests/components/camera/common.py index e30de46c07b..9cacf85d907 100644 --- a/tests/components/camera/common.py +++ b/tests/components/camera/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from unittest.mock import Mock EMPTY_8_6_JPEG = b"empty_8_6" diff --git a/tests/components/camera/conftest.py b/tests/components/camera/conftest.py index 21737323671..37c3ba8057c 100644 --- a/tests/components/camera/conftest.py +++ b/tests/components/camera/conftest.py @@ -1,4 +1,5 @@ """Test helpers for camera.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/camera/test_img_util.py b/tests/components/camera/test_img_util.py index 5be8e49c1d3..4fb863d414d 100644 --- a/tests/components/camera/test_img_util.py +++ b/tests/components/camera/test_img_util.py @@ -1,4 +1,5 @@ """Test img_util module.""" + from unittest.mock import patch import pytest diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 528c13bc08c..82a2652b8bc 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -1,4 +1,5 @@ """The tests for the camera component.""" + from http import HTTPStatus import io from types import ModuleType diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index f965bdadb09..5e8fbbd1fb2 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -1,4 +1,5 @@ """Test camera media source.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/camera/test_recorder.py b/tests/components/camera/test_recorder.py index df2b8cbe737..e07b376aefb 100644 --- a/tests/components/camera/test_recorder.py +++ b/tests/components/camera/test_recorder.py @@ -1,4 +1,5 @@ """The tests for camera recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/camera/test_significant_change.py b/tests/components/camera/test_significant_change.py index b1e1eb66589..a2a7ef20e71 100644 --- a/tests/components/camera/test_significant_change.py +++ b/tests/components/camera/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Camera significant change platform.""" + from homeassistant.components.camera import STATE_IDLE, STATE_RECORDING from homeassistant.components.camera.significant_change import ( async_check_significant_change, diff --git a/tests/components/canary/conftest.py b/tests/components/canary/conftest.py index 546dbca39de..25c9b3c3a4f 100644 --- a/tests/components/canary/conftest.py +++ b/tests/components/canary/conftest.py @@ -1,4 +1,5 @@ """Define fixtures available for all tests.""" + from unittest.mock import MagicMock, patch from canary.api import Api diff --git a/tests/components/canary/test_alarm_control_panel.py b/tests/components/canary/test_alarm_control_panel.py index 6e1096d31c9..83e801d67c4 100644 --- a/tests/components/canary/test_alarm_control_panel.py +++ b/tests/components/canary/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """The tests for the Canary alarm_control_panel platform.""" + from unittest.mock import PropertyMock, patch from canary.const import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, LOCATION_MODE_NIGHT diff --git a/tests/components/canary/test_config_flow.py b/tests/components/canary/test_config_flow.py index 6fcd4290ba8..1f28daaeaa3 100644 --- a/tests/components/canary/test_config_flow.py +++ b/tests/components/canary/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Canary config flow.""" + from unittest.mock import patch from requests import ConnectTimeout, HTTPError diff --git a/tests/components/canary/test_init.py b/tests/components/canary/test_init.py index 403454662d8..e0d1c532efc 100644 --- a/tests/components/canary/test_init.py +++ b/tests/components/canary/test_init.py @@ -1,4 +1,5 @@ """The tests for the Canary component.""" + from unittest.mock import patch from requests import ConnectTimeout diff --git a/tests/components/canary/test_sensor.py b/tests/components/canary/test_sensor.py index f8e26289691..afcf9f16db4 100644 --- a/tests/components/canary/test_sensor.py +++ b/tests/components/canary/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Canary sensor platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/cast/test_config_flow.py b/tests/components/cast/test_config_flow.py index 9b5c2d56d4c..7c7bb64d552 100644 --- a/tests/components/cast/test_config_flow.py +++ b/tests/components/cast/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Cast config flow.""" + from unittest.mock import ANY, patch import pytest diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index 637ba53fc93..aa84afb734c 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -1,4 +1,5 @@ """Test Home Assistant Cast.""" + from unittest.mock import patch import pytest diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 66d23043935..6b658ece853 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the Cast Media player platform.""" + from __future__ import annotations import asyncio diff --git a/tests/components/ccm15/conftest.py b/tests/components/ccm15/conftest.py index 910a74fa0bc..6098a95b3ce 100644 --- a/tests/components/ccm15/conftest.py +++ b/tests/components/ccm15/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Midea ccm15 AC Controller tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/ccm15/test_climate.py b/tests/components/ccm15/test_climate.py index 36a77aa15ab..329caafd11c 100644 --- a/tests/components/ccm15/test_climate.py +++ b/tests/components/ccm15/test_climate.py @@ -1,4 +1,5 @@ """Unit test for CCM15 coordinator component.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/ccm15/test_config_flow.py b/tests/components/ccm15/test_config_flow.py index 9b6314228cc..87c93179f4e 100644 --- a/tests/components/ccm15/test_config_flow.py +++ b/tests/components/ccm15/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Midea ccm15 AC Controller config flow.""" + from unittest.mock import AsyncMock, patch from homeassistant import config_entries diff --git a/tests/components/ccm15/test_diagnostics.py b/tests/components/ccm15/test_diagnostics.py index 3700faa51ce..a433591d86e 100644 --- a/tests/components/ccm15/test_diagnostics.py +++ b/tests/components/ccm15/test_diagnostics.py @@ -1,4 +1,5 @@ """Test CCM15 diagnostics.""" + from unittest.mock import AsyncMock from syrupy import SnapshotAssertion diff --git a/tests/components/ccm15/test_init.py b/tests/components/ccm15/test_init.py index b65f170a656..3069b61f10f 100644 --- a/tests/components/ccm15/test_init.py +++ b/tests/components/ccm15/test_init.py @@ -1,4 +1,5 @@ """Tests for the ccm15 component.""" + from unittest.mock import AsyncMock from homeassistant.components.ccm15.const import DOMAIN diff --git a/tests/components/cert_expiry/conftest.py b/tests/components/cert_expiry/conftest.py index 0a3f5420f60..41c2d90b1a0 100644 --- a/tests/components/cert_expiry/conftest.py +++ b/tests/components/cert_expiry/conftest.py @@ -1,4 +1,5 @@ """Configuration for cert_expiry tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/cert_expiry/helpers.py b/tests/components/cert_expiry/helpers.py index cf7cff511f7..929ad7e6f9a 100644 --- a/tests/components/cert_expiry/helpers.py +++ b/tests/components/cert_expiry/helpers.py @@ -1,4 +1,5 @@ """Helpers for Cert Expiry tests.""" + from datetime import datetime, timedelta from homeassistant.util import dt as dt_util diff --git a/tests/components/cert_expiry/test_init.py b/tests/components/cert_expiry/test_init.py index 0e0ff1444eb..360cd118a13 100644 --- a/tests/components/cert_expiry/test_init.py +++ b/tests/components/cert_expiry/test_init.py @@ -1,4 +1,5 @@ """Tests for Cert Expiry setup.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/cert_expiry/test_sensors.py b/tests/components/cert_expiry/test_sensors.py index 1c66a1c91ff..01eb1ba6ea6 100644 --- a/tests/components/cert_expiry/test_sensors.py +++ b/tests/components/cert_expiry/test_sensors.py @@ -1,4 +1,5 @@ """Tests for the Cert Expiry sensors.""" + from datetime import timedelta import socket import ssl diff --git a/tests/components/climate/common.py b/tests/components/climate/common.py index 7d66b886810..20f6bfd880d 100644 --- a/tests/components/climate/common.py +++ b/tests/components/climate/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.climate import ( _LOGGER, ATTR_AUX_HEAT, diff --git a/tests/components/climate/conftest.py b/tests/components/climate/conftest.py index 2db96a20a0b..c65414ea68d 100644 --- a/tests/components/climate/conftest.py +++ b/tests/components/climate/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Climate platform tests.""" + from collections.abc import Generator import pytest diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 11ec825f96c..fcf98876238 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1,4 +1,5 @@ """The tests for the climate component.""" + from __future__ import annotations from enum import Enum diff --git a/tests/components/climate/test_recorder.py b/tests/components/climate/test_recorder.py index 150d70d1dba..f3ecedb9cd4 100644 --- a/tests/components/climate/test_recorder.py +++ b/tests/components/climate/test_recorder.py @@ -1,4 +1,5 @@ """The tests for climate recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index 798b169393a..4350c58e625 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -1,4 +1,5 @@ """Fixtures for cloud tests.""" + from collections.abc import AsyncGenerator, Callable, Coroutine from typing import Any from unittest.mock import DEFAULT, MagicMock, PropertyMock, patch diff --git a/tests/components/cloud/test_binary_sensor.py b/tests/components/cloud/test_binary_sensor.py index 6505be1fe10..5e83fa34c3c 100644 --- a/tests/components/cloud/test_binary_sensor.py +++ b/tests/components/cloud/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the cloud binary sensor.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index 66a5ed8e4ad..748d3de673b 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -1,4 +1,5 @@ """Test the cloud.iot module.""" + from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, Mock, PropertyMock, patch diff --git a/tests/components/cloud/test_config_flow.py b/tests/components/cloud/test_config_flow.py index ee4e37276dc..4c9823ee8eb 100644 --- a/tests/components/cloud/test_config_flow.py +++ b/tests/components/cloud/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Home Assistant Cloud config flow.""" + from unittest.mock import patch from homeassistant.components.cloud.const import DOMAIN diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 77648353f67..dfadf80a601 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,4 +1,5 @@ """Test the Cloud Google Config.""" + from http import HTTPStatus from unittest.mock import Mock, patch diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 9abe0088b20..389361693ed 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -1,4 +1,5 @@ """Tests for the HTTP API for the cloud component.""" + from copy import deepcopy from http import HTTPStatus from typing import Any diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 4cef8c8437e..4733d5e907c 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -1,4 +1,5 @@ """Test the cloud component.""" + from collections.abc import Callable, Coroutine from typing import Any from unittest.mock import MagicMock, patch diff --git a/tests/components/cloud/test_prefs.py b/tests/components/cloud/test_prefs.py index 608f6ef3df0..e7d7a174135 100644 --- a/tests/components/cloud/test_prefs.py +++ b/tests/components/cloud/test_prefs.py @@ -1,4 +1,5 @@ """Test Cloud preferences.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/cloud/test_repairs.py b/tests/components/cloud/test_repairs.py index 9380cec2ebd..abfc917016d 100644 --- a/tests/components/cloud/test_repairs.py +++ b/tests/components/cloud/test_repairs.py @@ -1,4 +1,5 @@ """Test cloud repairs.""" + from collections.abc import Generator from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/cloud/test_stt.py b/tests/components/cloud/test_stt.py index 305780e33e1..540aa173beb 100644 --- a/tests/components/cloud/test_stt.py +++ b/tests/components/cloud/test_stt.py @@ -1,4 +1,5 @@ """Test the speech-to-text platform for the cloud integration.""" + from collections.abc import AsyncGenerator from copy import deepcopy from http import HTTPStatus diff --git a/tests/components/cloud/test_subscription.py b/tests/components/cloud/test_subscription.py index c7297e35744..22839b585fd 100644 --- a/tests/components/cloud/test_subscription.py +++ b/tests/components/cloud/test_subscription.py @@ -1,4 +1,5 @@ """Test cloud subscription functions.""" + from unittest.mock import AsyncMock, Mock from hass_nabucasa import Cloud diff --git a/tests/components/cloud/test_tts.py b/tests/components/cloud/test_tts.py index db43c40b69a..2fd42012c77 100644 --- a/tests/components/cloud/test_tts.py +++ b/tests/components/cloud/test_tts.py @@ -1,4 +1,5 @@ """Tests for cloud tts.""" + from collections.abc import AsyncGenerator, Callable, Coroutine from copy import deepcopy from http import HTTPStatus diff --git a/tests/components/cloudflare/conftest.py b/tests/components/cloudflare/conftest.py index de0e1a85b77..81b52dd291d 100644 --- a/tests/components/cloudflare/conftest.py +++ b/tests/components/cloudflare/conftest.py @@ -1,4 +1,5 @@ """Define fixtures available for all tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/cloudflare/test_helpers.py b/tests/components/cloudflare/test_helpers.py index 74bf8420f8a..2d0546882dd 100644 --- a/tests/components/cloudflare/test_helpers.py +++ b/tests/components/cloudflare/test_helpers.py @@ -1,4 +1,5 @@ """Test Cloudflare integration helpers.""" + from homeassistant.components.cloudflare.helpers import get_zone_id diff --git a/tests/components/cloudflare/test_init.py b/tests/components/cloudflare/test_init.py index 02001f83995..9136d229649 100644 --- a/tests/components/cloudflare/test_init.py +++ b/tests/components/cloudflare/test_init.py @@ -1,4 +1,5 @@ """Test the Cloudflare integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/co2signal/conftest.py b/tests/components/co2signal/conftest.py index 8eb0116bc88..bcc22ae3d55 100644 --- a/tests/components/co2signal/conftest.py +++ b/tests/components/co2signal/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Electricity maps integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/co2signal/test_config_flow.py b/tests/components/co2signal/test_config_flow.py index 518a747f852..e3bf9e3c818 100644 --- a/tests/components/co2signal/test_config_flow.py +++ b/tests/components/co2signal/test_config_flow.py @@ -1,4 +1,5 @@ """Test the CO2 Signal config flow.""" + from unittest.mock import AsyncMock, patch from aioelectricitymaps import ( diff --git a/tests/components/co2signal/test_sensor.py b/tests/components/co2signal/test_sensor.py index 4d663e1026b..d3e02023142 100644 --- a/tests/components/co2signal/test_sensor.py +++ b/tests/components/co2signal/test_sensor.py @@ -1,4 +1,5 @@ """Tests Electricity Maps sensor platform.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/coinbase/common.py b/tests/components/coinbase/common.py index 0f8930dbeff..3421c4ce838 100644 --- a/tests/components/coinbase/common.py +++ b/tests/components/coinbase/common.py @@ -1,4 +1,5 @@ """Collection of helpers.""" + from homeassistant.components.coinbase.const import ( CONF_CURRENCIES, CONF_EXCHANGE_RATES, diff --git a/tests/components/coinbase/test_diagnostics.py b/tests/components/coinbase/test_diagnostics.py index 897722b32b4..55454a48352 100644 --- a/tests/components/coinbase/test_diagnostics.py +++ b/tests/components/coinbase/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Coinbase diagnostics.""" + from unittest.mock import patch from syrupy import SnapshotAssertion diff --git a/tests/components/coinbase/test_init.py b/tests/components/coinbase/test_init.py index c518c71098d..a2350654d08 100644 --- a/tests/components/coinbase/test_init.py +++ b/tests/components/coinbase/test_init.py @@ -1,4 +1,5 @@ """Test the Coinbase integration.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/color_extractor/test_config_flow.py b/tests/components/color_extractor/test_config_flow.py index 9dc928da73f..844712f1938 100644 --- a/tests/components/color_extractor/test_config_flow.py +++ b/tests/components/color_extractor/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Color extractor config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/color_extractor/test_init.py b/tests/components/color_extractor/test_init.py index b4874b575e8..cf4354db48d 100644 --- a/tests/components/color_extractor/test_init.py +++ b/tests/components/color_extractor/test_init.py @@ -1,4 +1,5 @@ """Test Color extractor component setup process.""" + from homeassistant.components.color_extractor import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/comelit/test_config_flow.py b/tests/components/comelit/test_config_flow.py index 0a0dc04eae0..dda29aaf922 100644 --- a/tests/components/comelit/test_config_flow.py +++ b/tests/components/comelit/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Comelit SimpleHome config flow.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index 46d072fb94f..adbca6124f1 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Command line Binary sensor platform.""" + from __future__ import annotations import asyncio diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index 2a66acf8787..58506355ad0 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -1,4 +1,5 @@ """The tests the cover command line platform.""" + from __future__ import annotations import asyncio diff --git a/tests/components/command_line/test_init.py b/tests/components/command_line/test_init.py index 53f985961f3..4f58705e7bf 100644 --- a/tests/components/command_line/test_init.py +++ b/tests/components/command_line/test_init.py @@ -1,4 +1,5 @@ """Test Command line component setup process.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/command_line/test_notify.py b/tests/components/command_line/test_notify.py index 96ad5ce2ee8..98bfb856bb8 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -1,4 +1,5 @@ """The tests for the command line notification platform.""" + from __future__ import annotations import os diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index e169ef2d99b..86f6d4d3179 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Command line sensor platform.""" + from __future__ import annotations import asyncio diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index 370573e4274..53d9cc96560 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Command line switch platform.""" + from __future__ import annotations import asyncio diff --git a/tests/components/config/conftest.py b/tests/components/config/conftest.py index 83c8cd014f3..2a0552b997f 100644 --- a/tests/components/config/conftest.py +++ b/tests/components/config/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the config integration.""" + from contextlib import contextmanager from copy import deepcopy import json diff --git a/tests/components/config/test_auth_provider_homeassistant.py b/tests/components/config/test_auth_provider_homeassistant.py index a8d5a1a23fd..d2631cd7a7c 100644 --- a/tests/components/config/test_auth_provider_homeassistant.py +++ b/tests/components/config/test_auth_provider_homeassistant.py @@ -1,4 +1,5 @@ """Test config entries API.""" + from typing import Any import pytest diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 9ba5762e1d0..616aae58bdd 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -1,4 +1,5 @@ """Test Automation config panel.""" + from http import HTTPStatus import json from typing import Any diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index a55657d792c..a373587e1ab 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -1,4 +1,5 @@ """Test config entries API.""" + from collections import OrderedDict from http import HTTPStatus from unittest.mock import ANY, AsyncMock, patch diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index e50ec97e1b1..408947be00e 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -1,4 +1,5 @@ """Test core config.""" + from http import HTTPStatus from unittest.mock import Mock, patch diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py index 4dd786edfd1..135cea28eff 100644 --- a/tests/components/config/test_init.py +++ b/tests/components/config/test_init.py @@ -1,4 +1,5 @@ """Test config init.""" + from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index 2f2ca9b1c37..b270ac26a9b 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -1,4 +1,5 @@ """Test Automation config panel.""" + from http import HTTPStatus import json from unittest.mock import ANY, patch diff --git a/tests/components/config/test_script.py b/tests/components/config/test_script.py index bebec0aedba..745ee708875 100644 --- a/tests/components/config/test_script.py +++ b/tests/components/config/test_script.py @@ -1,4 +1,5 @@ """Tests for config/script.""" + from http import HTTPStatus import json from typing import Any diff --git a/tests/components/conftest.py b/tests/components/conftest.py index adf79a2ef96..24fdc7743ce 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -1,4 +1,5 @@ """Fixtures for component testing.""" + from collections.abc import Generator from typing import Any from unittest.mock import MagicMock, patch diff --git a/tests/components/control4/test_config_flow.py b/tests/components/control4/test_config_flow.py index 4909ead6c48..b17a1cd6ea1 100644 --- a/tests/components/control4/test_config_flow.py +++ b/tests/components/control4/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Control4 config flow.""" + from unittest.mock import AsyncMock, patch from pyControl4.account import C4Account diff --git a/tests/components/conversation/conftest.py b/tests/components/conversation/conftest.py index a08823255e9..99794dbea4a 100644 --- a/tests/components/conversation/conftest.py +++ b/tests/components/conversation/conftest.py @@ -1,4 +1,5 @@ """Conversation test helpers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 61712761250..7129176adc3 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -1,4 +1,5 @@ """The tests for the Conversation component.""" + from http import HTTPStatus from typing import Any from unittest.mock import patch diff --git a/tests/components/conversation/test_util.py b/tests/components/conversation/test_util.py index 8b26c61a651..72a334232c1 100644 --- a/tests/components/conversation/test_util.py +++ b/tests/components/conversation/test_util.py @@ -1,4 +1,5 @@ """Test the conversation utils.""" + from homeassistant.components.conversation.util import create_matcher diff --git a/tests/components/coolmaster/conftest.py b/tests/components/coolmaster/conftest.py index fadce747d6a..e2c4b050514 100644 --- a/tests/components/coolmaster/conftest.py +++ b/tests/components/coolmaster/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Coolmaster integration.""" + from __future__ import annotations import copy diff --git a/tests/components/coolmaster/test_binary_sensor.py b/tests/components/coolmaster/test_binary_sensor.py index 2f5c8c5f1be..77e26ed803c 100644 --- a/tests/components/coolmaster/test_binary_sensor.py +++ b/tests/components/coolmaster/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the Coolmaster binary sensor platform.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/tests/components/coolmaster/test_button.py b/tests/components/coolmaster/test_button.py index 67461f63087..5d2ea07649e 100644 --- a/tests/components/coolmaster/test_button.py +++ b/tests/components/coolmaster/test_button.py @@ -1,4 +1,5 @@ """The test for the Coolmaster button platform.""" + from __future__ import annotations from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS diff --git a/tests/components/coolmaster/test_climate.py b/tests/components/coolmaster/test_climate.py index 0e306faa8ab..ddc4b5b53d6 100644 --- a/tests/components/coolmaster/test_climate.py +++ b/tests/components/coolmaster/test_climate.py @@ -1,4 +1,5 @@ """The test for the Coolmaster climate platform.""" + from __future__ import annotations from pycoolmasternet_async import SWING_MODES diff --git a/tests/components/coolmaster/test_config_flow.py b/tests/components/coolmaster/test_config_flow.py index 47a31538560..6c62ffa482d 100644 --- a/tests/components/coolmaster/test_config_flow.py +++ b/tests/components/coolmaster/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Coolmaster config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/coolmaster/test_init.py b/tests/components/coolmaster/test_init.py index ce6dd8f60a4..4a90d0d9276 100644 --- a/tests/components/coolmaster/test_init.py +++ b/tests/components/coolmaster/test_init.py @@ -1,4 +1,5 @@ """The test for the Coolmaster integration.""" + from homeassistant.components.coolmaster.const import DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/coolmaster/test_sensor.py b/tests/components/coolmaster/test_sensor.py index 3072106ec62..388edb5096b 100644 --- a/tests/components/coolmaster/test_sensor.py +++ b/tests/components/coolmaster/test_sensor.py @@ -1,4 +1,5 @@ """The test for the Coolmaster sensor platform.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/tests/components/counter/common.py b/tests/components/counter/common.py index 5f47e4faa77..3cd7dd3c300 100644 --- a/tests/components/counter/common.py +++ b/tests/components/counter/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.counter import ( DOMAIN, SERVICE_DECREMENT, diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index e464ff87c3f..be91ee38be9 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Cover device triggers.""" + from datetime import timedelta import pytest diff --git a/tests/components/cover/test_init.py b/tests/components/cover/test_init.py index 0503017f634..e44b0788c9a 100644 --- a/tests/components/cover/test_init.py +++ b/tests/components/cover/test_init.py @@ -1,4 +1,5 @@ """The tests for Cover.""" + from enum import Enum import pytest diff --git a/tests/components/cpuspeed/conftest.py b/tests/components/cpuspeed/conftest.py index be5a87b8d13..82dfb5eac30 100644 --- a/tests/components/cpuspeed/conftest.py +++ b/tests/components/cpuspeed/conftest.py @@ -1,4 +1,5 @@ """Fixtures for CPU Speed integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/cpuspeed/test_diagnostics.py b/tests/components/cpuspeed/test_diagnostics.py index 2c91566216d..a596c7d62d9 100644 --- a/tests/components/cpuspeed/test_diagnostics.py +++ b/tests/components/cpuspeed/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the CPU Speed integration.""" + from unittest.mock import patch from syrupy import SnapshotAssertion diff --git a/tests/components/cpuspeed/test_init.py b/tests/components/cpuspeed/test_init.py index cdb86ba2f46..76158f22473 100644 --- a/tests/components/cpuspeed/test_init.py +++ b/tests/components/cpuspeed/test_init.py @@ -1,4 +1,5 @@ """Tests for the CPU Speed integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/crownstone/test_config_flow.py b/tests/components/crownstone/test_config_flow.py index d112a0f7bd3..04f69f3a74a 100644 --- a/tests/components/crownstone/test_config_flow.py +++ b/tests/components/crownstone/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Crownstone integration.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index 942137e8f6d..ece17b6aafe 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Daikin config flow.""" + from ipaddress import ip_address from unittest.mock import PropertyMock, patch diff --git a/tests/components/daikin/test_init.py b/tests/components/daikin/test_init.py index 7c4467c3031..01b21ebb6fd 100644 --- a/tests/components/daikin/test_init.py +++ b/tests/components/daikin/test_init.py @@ -1,4 +1,5 @@ """Define tests for the Daikin init.""" + from datetime import timedelta from unittest.mock import AsyncMock, PropertyMock, patch diff --git a/tests/components/daikin/test_temperature_format.py b/tests/components/daikin/test_temperature_format.py index bc92c1ab10b..c6ea5c0fea1 100644 --- a/tests/components/daikin/test_temperature_format.py +++ b/tests/components/daikin/test_temperature_format.py @@ -1,4 +1,5 @@ """The tests for the Daikin target temperature conversion.""" + from homeassistant.components.daikin.climate import format_target_temperature diff --git a/tests/components/datadog/test_init.py b/tests/components/datadog/test_init.py index 1ae795f2e95..cbf77de3a69 100644 --- a/tests/components/datadog/test_init.py +++ b/tests/components/datadog/test_init.py @@ -1,4 +1,5 @@ """The tests for the Datadog component.""" + from unittest import mock from unittest.mock import patch diff --git a/tests/components/date/test_init.py b/tests/components/date/test_init.py index 2ae17673119..f0a0094f8b8 100644 --- a/tests/components/date/test_init.py +++ b/tests/components/date/test_init.py @@ -1,4 +1,5 @@ """The tests for the date component.""" + from datetime import date from homeassistant.components.date import DOMAIN, SERVICE_SET_VALUE, DateEntity diff --git a/tests/components/datetime/test_init.py b/tests/components/datetime/test_init.py index 6f2e2db29a1..f85754f5e1f 100644 --- a/tests/components/datetime/test_init.py +++ b/tests/components/datetime/test_init.py @@ -1,4 +1,5 @@ """The tests for the datetime component.""" + from datetime import UTC, datetime from zoneinfo import ZoneInfo diff --git a/tests/components/debugpy/test_init.py b/tests/components/debugpy/test_init.py index 97d08e13bfc..e4d77fc480a 100644 --- a/tests/components/debugpy/test_init.py +++ b/tests/components/debugpy/test_init.py @@ -1,4 +1,5 @@ """Tests for the Remote Python Debugger integration.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index 44411ca40cf..d0f0f11c99b 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -1,4 +1,5 @@ """deconz conftest.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/deconz/test_alarm_control_panel.py b/tests/components/deconz/test_alarm_control_panel.py index 14eee701c67..ec926491724 100644 --- a/tests/components/deconz/test_alarm_control_panel.py +++ b/tests/components/deconz/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """deCONZ alarm control panel platform tests.""" + from unittest.mock import patch from pydeconz.models.sensor.ancillary_control import AncillaryControlPanel diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 68396c8ff9c..ee5a30f2fab 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -1,4 +1,5 @@ """deCONZ binary sensor platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_button.py b/tests/components/deconz/test_button.py index 7f4dd59bf16..4d85270ddca 100644 --- a/tests/components/deconz/test_button.py +++ b/tests/components/deconz/test_button.py @@ -1,4 +1,5 @@ """deCONZ button platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index dd0de559ba8..0e51f31cec4 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -1,4 +1,5 @@ """deCONZ climate platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 54a2c0f65a2..69452c3285e 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -1,4 +1,5 @@ """deCONZ cover platform tests.""" + from unittest.mock import patch from homeassistant.components.cover import ( diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index 403feb07915..1193f348e38 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -1,4 +1,5 @@ """Test deCONZ remote events.""" + from unittest.mock import patch from pydeconz.models.sensor.ancillary_control import ( diff --git a/tests/components/deconz/test_device_trigger.py b/tests/components/deconz/test_device_trigger.py index 4c3344f5822..329cf0405db 100644 --- a/tests/components/deconz/test_device_trigger.py +++ b/tests/components/deconz/test_device_trigger.py @@ -1,4 +1,5 @@ """deCONZ device automation tests.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/deconz/test_diagnostics.py b/tests/components/deconz/test_diagnostics.py index e7e470cdf81..bfbc27b206d 100644 --- a/tests/components/deconz/test_diagnostics.py +++ b/tests/components/deconz/test_diagnostics.py @@ -1,4 +1,5 @@ """Test deCONZ diagnostics.""" + from pydeconz.websocket import State from syrupy import SnapshotAssertion diff --git a/tests/components/deconz/test_fan.py b/tests/components/deconz/test_fan.py index 7360e442dfa..5da0398c3e6 100644 --- a/tests/components/deconz/test_fan.py +++ b/tests/components/deconz/test_fan.py @@ -1,4 +1,5 @@ """deCONZ fan platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 84a57fe7595..04850321a5d 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -1,4 +1,5 @@ """Test deCONZ gateway.""" + from copy import deepcopy from unittest.mock import patch diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 07e284d65f2..a3a282f4557 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1,4 +1,5 @@ """deCONZ light platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_lock.py b/tests/components/deconz/test_lock.py index 16879d48631..03d14802083 100644 --- a/tests/components/deconz/test_lock.py +++ b/tests/components/deconz/test_lock.py @@ -1,4 +1,5 @@ """deCONZ lock platform tests.""" + from unittest.mock import patch from homeassistant.components.lock import ( diff --git a/tests/components/deconz/test_logbook.py b/tests/components/deconz/test_logbook.py index 83f2e463992..5940d2e8e34 100644 --- a/tests/components/deconz/test_logbook.py +++ b/tests/components/deconz/test_logbook.py @@ -1,4 +1,5 @@ """The tests for deCONZ logbook.""" + from unittest.mock import patch from homeassistant.components.deconz.const import CONF_GESTURE, DOMAIN as DECONZ_DOMAIN diff --git a/tests/components/deconz/test_number.py b/tests/components/deconz/test_number.py index 17cbc2917ec..19d1cdf2bea 100644 --- a/tests/components/deconz/test_number.py +++ b/tests/components/deconz/test_number.py @@ -1,4 +1,5 @@ """deCONZ number platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index 7d16f0bd513..2bace605db5 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -1,4 +1,5 @@ """deCONZ scene platform tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_select.py b/tests/components/deconz/test_select.py index 7b7a9c86168..fb8f41293a2 100644 --- a/tests/components/deconz/test_select.py +++ b/tests/components/deconz/test_select.py @@ -1,4 +1,5 @@ """deCONZ select platform tests.""" + from unittest.mock import patch from pydeconz.models.sensor.presence import ( diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 38d68d135b6..4cc1d349ef3 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -1,4 +1,5 @@ """deCONZ sensor platform tests.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index ade7aba2346..7cf55ae75c3 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -1,4 +1,5 @@ """deCONZ service tests.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deconz/test_siren.py b/tests/components/deconz/test_siren.py index 2dfaa5090de..62ed1b732b8 100644 --- a/tests/components/deconz/test_siren.py +++ b/tests/components/deconz/test_siren.py @@ -1,4 +1,5 @@ """deCONZ switch platform tests.""" + from unittest.mock import patch from homeassistant.components.siren import ATTR_DURATION, DOMAIN as SIREN_DOMAIN diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 31555a71011..9ef2382a2e2 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -1,4 +1,5 @@ """deCONZ switch platform tests.""" + from unittest.mock import patch from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index 20029fe3cdc..b9ce596465e 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -1,4 +1,5 @@ """Test the default_config init.""" + from unittest.mock import patch import pytest diff --git a/tests/components/deluge/test_config_flow.py b/tests/components/deluge/test_config_flow.py index 12753f74caf..6cbde04b8bd 100644 --- a/tests/components/deluge/test_config_flow.py +++ b/tests/components/deluge/test_config_flow.py @@ -1,4 +1,5 @@ """Test Deluge config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/conftest.py b/tests/components/demo/conftest.py index 0e54587ba4e..731a33360d7 100644 --- a/tests/components/demo/conftest.py +++ b/tests/components/demo/conftest.py @@ -1,4 +1,5 @@ """demo conftest.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_camera.py b/tests/components/demo/test_camera.py index 132e7bdb096..ea115e72f72 100644 --- a/tests/components/demo/test_camera.py +++ b/tests/components/demo/test_camera.py @@ -1,4 +1,5 @@ """The tests for local file camera component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_climate.py b/tests/components/demo/test_climate.py index 18992c0d0f4..11e9b9c01dd 100644 --- a/tests/components/demo/test_climate.py +++ b/tests/components/demo/test_climate.py @@ -1,4 +1,5 @@ """The tests for the demo climate component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_cover.py b/tests/components/demo/test_cover.py index 3a8fbb02f80..9ea743a0a01 100644 --- a/tests/components/demo/test_cover.py +++ b/tests/components/demo/test_cover.py @@ -1,4 +1,5 @@ """The tests for the Demo cover platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/demo/test_date.py b/tests/components/demo/test_date.py index d0208c8e6dd..5e0fc2c29cd 100644 --- a/tests/components/demo/test_date.py +++ b/tests/components/demo/test_date.py @@ -1,4 +1,5 @@ """The tests for the demo date component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_datetime.py b/tests/components/demo/test_datetime.py index 41ed6969df3..c1f88d7686b 100644 --- a/tests/components/demo/test_datetime.py +++ b/tests/components/demo/test_datetime.py @@ -1,4 +1,5 @@ """The tests for the demo datetime component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_fan.py b/tests/components/demo/test_fan.py index a3f607aee76..a5a0b731cb8 100644 --- a/tests/components/demo/test_fan.py +++ b/tests/components/demo/test_fan.py @@ -1,4 +1,5 @@ """Test cases around the demo fan platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_geo_location.py b/tests/components/demo/test_geo_location.py index 8cd176934bc..13657c88ac8 100644 --- a/tests/components/demo/test_geo_location.py +++ b/tests/components/demo/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the demo platform.""" + from freezegun import freeze_time from homeassistant.components import geo_location diff --git a/tests/components/demo/test_light.py b/tests/components/demo/test_light.py index 90fa26885dc..b67acf3f60f 100644 --- a/tests/components/demo/test_light.py +++ b/tests/components/demo/test_light.py @@ -1,4 +1,5 @@ """The tests for the demo light component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_lock.py b/tests/components/demo/test_lock.py index f72f5b01c19..634eee44385 100644 --- a/tests/components/demo/test_lock.py +++ b/tests/components/demo/test_lock.py @@ -1,4 +1,5 @@ """The tests for the Demo lock platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index b1bd77a74a1..6bc4c7a980b 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the Demo Media player platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/demo/test_number.py b/tests/components/demo/test_number.py index f444b2f4831..3c41b98a3fa 100644 --- a/tests/components/demo/test_number.py +++ b/tests/components/demo/test_number.py @@ -1,4 +1,5 @@ """The tests for the demo number component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_remote.py b/tests/components/demo/test_remote.py index 5fafffae372..e2a82248fdf 100644 --- a/tests/components/demo/test_remote.py +++ b/tests/components/demo/test_remote.py @@ -1,4 +1,5 @@ """The tests for the demo remote component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_select.py b/tests/components/demo/test_select.py index 013a9900a83..f9805f44866 100644 --- a/tests/components/demo/test_select.py +++ b/tests/components/demo/test_select.py @@ -1,4 +1,5 @@ """The tests for the demo select component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_sensor.py b/tests/components/demo/test_sensor.py index 0fbe8f3fa7f..57650f3fce4 100644 --- a/tests/components/demo/test_sensor.py +++ b/tests/components/demo/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the demo sensor component.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/demo/test_siren.py b/tests/components/demo/test_siren.py index 1434248599c..e21cd96efc9 100644 --- a/tests/components/demo/test_siren.py +++ b/tests/components/demo/test_siren.py @@ -1,4 +1,5 @@ """The tests for the demo siren component.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/demo/test_stt.py b/tests/components/demo/test_stt.py index 6ce25135ae0..dccdddd84e8 100644 --- a/tests/components/demo/test_stt.py +++ b/tests/components/demo/test_stt.py @@ -1,4 +1,5 @@ """The tests for the demo stt component.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/demo/test_switch.py b/tests/components/demo/test_switch.py index 95963ba0cbd..d8c3284875e 100644 --- a/tests/components/demo/test_switch.py +++ b/tests/components/demo/test_switch.py @@ -1,4 +1,5 @@ """The tests for the demo switch component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_text.py b/tests/components/demo/test_text.py index 2b8d8d122a3..faf611d9875 100644 --- a/tests/components/demo/test_text.py +++ b/tests/components/demo/test_text.py @@ -1,4 +1,5 @@ """The tests for the demo text component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_time.py b/tests/components/demo/test_time.py index efa62c1436b..8ef093a38f3 100644 --- a/tests/components/demo/test_time.py +++ b/tests/components/demo/test_time.py @@ -1,4 +1,5 @@ """The tests for the demo time component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_update.py b/tests/components/demo/test_update.py index a645c45019c..c8f46ff1b41 100644 --- a/tests/components/demo/test_update.py +++ b/tests/components/demo/test_update.py @@ -1,4 +1,5 @@ """The tests for the demo update platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/demo/test_vacuum.py b/tests/components/demo/test_vacuum.py index 987bc6b9384..e70f0144e6a 100644 --- a/tests/components/demo/test_vacuum.py +++ b/tests/components/demo/test_vacuum.py @@ -1,4 +1,5 @@ """The tests for the Demo vacuum platform.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/demo/test_water_heater.py b/tests/components/demo/test_water_heater.py index 6b133297e34..48859610d39 100644 --- a/tests/components/demo/test_water_heater.py +++ b/tests/components/demo/test_water_heater.py @@ -1,4 +1,5 @@ """The tests for the demo water_heater component.""" + from unittest.mock import patch import pytest diff --git a/tests/components/denonavr/test_config_flow.py b/tests/components/denonavr/test_config_flow.py index a0fb908d920..bcadade5eb5 100644 --- a/tests/components/denonavr/test_config_flow.py +++ b/tests/components/denonavr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DenonAVR config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/denonavr/test_media_player.py b/tests/components/denonavr/test_media_player.py index 85cbf91d850..8836e78ad5a 100644 --- a/tests/components/denonavr/test_media_player.py +++ b/tests/components/denonavr/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the denonavr media player platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/derivative/test_config_flow.py b/tests/components/derivative/test_config_flow.py index bc440723df2..89bf5bb201e 100644 --- a/tests/components/derivative/test_config_flow.py +++ b/tests/components/derivative/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Derivative config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/derivative/test_sensor.py b/tests/components/derivative/test_sensor.py index 4d954fcbb43..ae566151065 100644 --- a/tests/components/derivative/test_sensor.py +++ b/tests/components/derivative/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the derivative sensor platform.""" + from datetime import timedelta from math import sin import random diff --git a/tests/components/devialet/test_config_flow.py b/tests/components/devialet/test_config_flow.py index 0bacc558b74..05174b50f0d 100644 --- a/tests/components/devialet/test_config_flow.py +++ b/tests/components/devialet/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Devialet config flow.""" + from unittest.mock import patch from aiohttp import ClientError as HTTPClientError diff --git a/tests/components/devialet/test_init.py b/tests/components/devialet/test_init.py index 86d383e91d8..a87e8ac05c3 100644 --- a/tests/components/devialet/test_init.py +++ b/tests/components/devialet/test_init.py @@ -1,4 +1,5 @@ """Test the Devialet init.""" + from homeassistant.components.devialet.const import DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, MediaPlayerState from homeassistant.config_entries import ConfigEntryState diff --git a/tests/components/devialet/test_media_player.py b/tests/components/devialet/test_media_player.py index 56381bf6de4..4e8f9b1dc03 100644 --- a/tests/components/devialet/test_media_player.py +++ b/tests/components/devialet/test_media_player.py @@ -1,4 +1,5 @@ """Test the Devialet init.""" + from unittest.mock import PropertyMock, patch from devialet import DevialetApi diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 9a7d54fb690..772737b4137 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -1,4 +1,5 @@ """The test for light device automation.""" + from unittest.mock import AsyncMock, Mock, patch import attr diff --git a/tests/components/device_automation/test_toggle_entity.py b/tests/components/device_automation/test_toggle_entity.py index 30c9e5b542e..f034dd9ab12 100644 --- a/tests/components/device_automation/test_toggle_entity.py +++ b/tests/components/device_automation/test_toggle_entity.py @@ -1,4 +1,5 @@ """The test for device automation toggle entity helpers.""" + from datetime import timedelta import pytest diff --git a/tests/components/device_sun_light_trigger/test_init.py b/tests/components/device_sun_light_trigger/test_init.py index 3831d247ed4..570708cec79 100644 --- a/tests/components/device_sun_light_trigger/test_init.py +++ b/tests/components/device_sun_light_trigger/test_init.py @@ -1,4 +1,5 @@ """The tests device sun light trigger component.""" + from datetime import datetime from unittest.mock import patch diff --git a/tests/components/device_tracker/common.py b/tests/components/device_tracker/common.py index dfb3f9e8462..499c3ac6cda 100644 --- a/tests/components/device_tracker/common.py +++ b/tests/components/device_tracker/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.device_tracker import ( ATTR_ATTRIBUTES, ATTR_BATTERY, diff --git a/tests/components/device_tracker/test_config_entry.py b/tests/components/device_tracker/test_config_entry.py index ba258af068e..d8236c697c3 100644 --- a/tests/components/device_tracker/test_config_entry.py +++ b/tests/components/device_tracker/test_config_entry.py @@ -1,4 +1,5 @@ """Test Device Tracker config entry things.""" + from collections.abc import Generator from typing import Any diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index eb8fde8f0e2..520e13e4c83 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -1,4 +1,5 @@ """The tests for the device tracker component.""" + from datetime import datetime, timedelta import json import logging diff --git a/tests/components/device_tracker/test_legacy.py b/tests/components/device_tracker/test_legacy.py index d7a2f33c23b..e22aa54b45c 100644 --- a/tests/components/device_tracker/test_legacy.py +++ b/tests/components/device_tracker/test_legacy.py @@ -1,4 +1,5 @@ """Tests for the legacy device tracker component.""" + from unittest.mock import mock_open, patch from homeassistant.components.device_tracker import legacy diff --git a/tests/components/devolo_home_control/test_binary_sensor.py b/tests/components/devolo_home_control/test_binary_sensor.py index ffb1794f006..e809c94c129 100644 --- a/tests/components/devolo_home_control/test_binary_sensor.py +++ b/tests/components/devolo_home_control/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control binary sensors.""" + from unittest.mock import patch import pytest diff --git a/tests/components/devolo_home_control/test_climate.py b/tests/components/devolo_home_control/test_climate.py index 11d5b01ec5a..953ff835b89 100644 --- a/tests/components/devolo_home_control/test_climate.py +++ b/tests/components/devolo_home_control/test_climate.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control climate.""" + from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/devolo_home_control/test_config_flow.py b/tests/components/devolo_home_control/test_config_flow.py index 1c04f0c83a6..3ace53bfc3b 100644 --- a/tests/components/devolo_home_control/test_config_flow.py +++ b/tests/components/devolo_home_control/test_config_flow.py @@ -1,4 +1,5 @@ """Test the devolo_home_control config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/devolo_home_control/test_cover.py b/tests/components/devolo_home_control/test_cover.py index 54f0cc34222..c21dabadb1a 100644 --- a/tests/components/devolo_home_control/test_cover.py +++ b/tests/components/devolo_home_control/test_cover.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control cover platform.""" + from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/devolo_home_control/test_diagnostics.py b/tests/components/devolo_home_control/test_diagnostics.py index ad267c4c52e..e31bc360845 100644 --- a/tests/components/devolo_home_control/test_diagnostics.py +++ b/tests/components/devolo_home_control/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control diagnostics.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/devolo_home_control/test_init.py b/tests/components/devolo_home_control/test_init.py index cb4c87aebdc..250a31843eb 100644 --- a/tests/components/devolo_home_control/test_init.py +++ b/tests/components/devolo_home_control/test_init.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control integration.""" + from unittest.mock import patch from devolo_home_control_api.exceptions.gateway import GatewayOfflineError diff --git a/tests/components/devolo_home_control/test_light.py b/tests/components/devolo_home_control/test_light.py index 8cb31dde8cc..f72136ee287 100644 --- a/tests/components/devolo_home_control/test_light.py +++ b/tests/components/devolo_home_control/test_light.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control light platform.""" + from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/devolo_home_control/test_sensor.py b/tests/components/devolo_home_control/test_sensor.py index afc4289fccf..62023982e81 100644 --- a/tests/components/devolo_home_control/test_sensor.py +++ b/tests/components/devolo_home_control/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control sensor platform.""" + from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/devolo_home_control/test_siren.py b/tests/components/devolo_home_control/test_siren.py index 5e72f771112..037d7b5021f 100644 --- a/tests/components/devolo_home_control/test_siren.py +++ b/tests/components/devolo_home_control/test_siren.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control binary sensors.""" + from unittest.mock import patch import pytest diff --git a/tests/components/devolo_home_control/test_switch.py b/tests/components/devolo_home_control/test_switch.py index 9216768b9c3..86f93bfddf6 100644 --- a/tests/components/devolo_home_control/test_switch.py +++ b/tests/components/devolo_home_control/test_switch.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Control switch platform.""" + from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/devolo_home_network/conftest.py b/tests/components/devolo_home_network/conftest.py index 9f30ca74cb0..f6a6e233b6d 100644 --- a/tests/components/devolo_home_network/conftest.py +++ b/tests/components/devolo_home_network/conftest.py @@ -1,4 +1,5 @@ """Fixtures for tests.""" + from itertools import cycle from unittest.mock import patch diff --git a/tests/components/devolo_home_network/mock.py b/tests/components/devolo_home_network/mock.py index 612df4da2e0..4b999667e53 100644 --- a/tests/components/devolo_home_network/mock.py +++ b/tests/components/devolo_home_network/mock.py @@ -1,4 +1,5 @@ """Mock of a devolo Home Network device.""" + from __future__ import annotations from unittest.mock import AsyncMock diff --git a/tests/components/devolo_home_network/test_binary_sensor.py b/tests/components/devolo_home_network/test_binary_sensor.py index 17d95fc51a3..3e4bf8471c1 100644 --- a/tests/components/devolo_home_network/test_binary_sensor.py +++ b/tests/components/devolo_home_network/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network sensors.""" + from unittest.mock import AsyncMock from devolo_plc_api.exceptions.device import DeviceUnavailable diff --git a/tests/components/devolo_home_network/test_button.py b/tests/components/devolo_home_network/test_button.py index 41820210dee..a46007e4c42 100644 --- a/tests/components/devolo_home_network/test_button.py +++ b/tests/components/devolo_home_network/test_button.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network buttons.""" + from unittest.mock import AsyncMock from devolo_plc_api.exceptions.device import DevicePasswordProtected, DeviceUnavailable diff --git a/tests/components/devolo_home_network/test_config_flow.py b/tests/components/devolo_home_network/test_config_flow.py index 9050181cc8f..77b147e0e48 100644 --- a/tests/components/devolo_home_network/test_config_flow.py +++ b/tests/components/devolo_home_network/test_config_flow.py @@ -1,4 +1,5 @@ """Test the devolo Home Network config flow.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/devolo_home_network/test_device_tracker.py b/tests/components/devolo_home_network/test_device_tracker.py index 8f58b1154de..1cce11c36f9 100644 --- a/tests/components/devolo_home_network/test_device_tracker.py +++ b/tests/components/devolo_home_network/test_device_tracker.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network device tracker.""" + from unittest.mock import AsyncMock from devolo_plc_api.exceptions.device import DeviceUnavailable diff --git a/tests/components/devolo_home_network/test_diagnostics.py b/tests/components/devolo_home_network/test_diagnostics.py index 0248d755e22..75794250908 100644 --- a/tests/components/devolo_home_network/test_diagnostics.py +++ b/tests/components/devolo_home_network/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network diagnostics.""" + from __future__ import annotations import pytest diff --git a/tests/components/devolo_home_network/test_image.py b/tests/components/devolo_home_network/test_image.py index ef7c4b2bbba..0ca3936e1ac 100644 --- a/tests/components/devolo_home_network/test_image.py +++ b/tests/components/devolo_home_network/test_image.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network images.""" + from http import HTTPStatus from unittest.mock import AsyncMock diff --git a/tests/components/devolo_home_network/test_init.py b/tests/components/devolo_home_network/test_init.py index e34af0dcbaf..bbf83957d6f 100644 --- a/tests/components/devolo_home_network/test_init.py +++ b/tests/components/devolo_home_network/test_init.py @@ -1,4 +1,5 @@ """Test the devolo Home Network integration setup.""" + from unittest.mock import patch from devolo_plc_api.exceptions.device import DeviceNotFound diff --git a/tests/components/devolo_home_network/test_sensor.py b/tests/components/devolo_home_network/test_sensor.py index e6f02033425..f82ec732d3e 100644 --- a/tests/components/devolo_home_network/test_sensor.py +++ b/tests/components/devolo_home_network/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network sensors.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/devolo_home_network/test_switch.py b/tests/components/devolo_home_network/test_switch.py index c77a77e87de..a2e87ed1b25 100644 --- a/tests/components/devolo_home_network/test_switch.py +++ b/tests/components/devolo_home_network/test_switch.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network switch.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/devolo_home_network/test_update.py b/tests/components/devolo_home_network/test_update.py index d80e9133a0a..7f70524fa5b 100644 --- a/tests/components/devolo_home_network/test_update.py +++ b/tests/components/devolo_home_network/test_update.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network update.""" + from devolo_plc_api.device_api import UPDATE_NOT_AVAILABLE, UpdateFirmwareCheck from devolo_plc_api.exceptions.device import DevicePasswordProtected, DeviceUnavailable from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/dexcom/test_config_flow.py b/tests/components/dexcom/test_config_flow.py index 80ca65eabc9..802df9513d3 100644 --- a/tests/components/dexcom/test_config_flow.py +++ b/tests/components/dexcom/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Dexcom config flow.""" + from unittest.mock import patch from pydexcom import AccountError, SessionError diff --git a/tests/components/dexcom/test_init.py b/tests/components/dexcom/test_init.py index 019ea8a58bd..f43d39626b0 100644 --- a/tests/components/dexcom/test_init.py +++ b/tests/components/dexcom/test_init.py @@ -1,4 +1,5 @@ """Test the Dexcom config flow.""" + from unittest.mock import patch from pydexcom import AccountError, SessionError diff --git a/tests/components/dexcom/test_sensor.py b/tests/components/dexcom/test_sensor.py index a211f0606f3..e79cd0f4e01 100644 --- a/tests/components/dexcom/test_sensor.py +++ b/tests/components/dexcom/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the griddy platform.""" + from unittest.mock import patch from pydexcom import SessionError diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 487435ef3f5..ea0b64e4219 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -1,4 +1,5 @@ """Test the DHCP discovery integration.""" + from collections.abc import Awaitable, Callable import datetime import threading diff --git a/tests/components/diagnostics/test_init.py b/tests/components/diagnostics/test_init.py index bc208e5f0f0..3303e51a5a5 100644 --- a/tests/components/diagnostics/test_init.py +++ b/tests/components/diagnostics/test_init.py @@ -1,4 +1,5 @@ """Test the Diagnostics integration.""" + from http import HTTPStatus from unittest.mock import AsyncMock, Mock diff --git a/tests/components/diagnostics/test_util.py b/tests/components/diagnostics/test_util.py index 87e29f31cc8..3781b980e97 100644 --- a/tests/components/diagnostics/test_util.py +++ b/tests/components/diagnostics/test_util.py @@ -1,4 +1,5 @@ """Test Diagnostics utils.""" + from homeassistant.components.diagnostics import REDACTED, async_redact_data diff --git a/tests/components/directv/test_init.py b/tests/components/directv/test_init.py index dd7b5b09f9f..4bfe8e2121f 100644 --- a/tests/components/directv/test_init.py +++ b/tests/components/directv/test_init.py @@ -1,4 +1,5 @@ """Tests for the DirecTV integration.""" + from homeassistant.components.directv.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index 55dd2e758dc..33eb35ed268 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the DirecTV Media player platform.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/tests/components/directv/test_remote.py b/tests/components/directv/test_remote.py index 9d326903933..a1a04967482 100644 --- a/tests/components/directv/test_remote.py +++ b/tests/components/directv/test_remote.py @@ -1,4 +1,5 @@ """The tests for the DirecTV remote platform.""" + from unittest.mock import patch from homeassistant.components.remote import ( diff --git a/tests/components/discord/conftest.py b/tests/components/discord/conftest.py index c98944fdc85..128869d0b80 100644 --- a/tests/components/discord/conftest.py +++ b/tests/components/discord/conftest.py @@ -1,4 +1,5 @@ """Discord notification test helpers.""" + from http import HTTPStatus import pytest diff --git a/tests/components/discovergy/conftest.py b/tests/components/discovergy/conftest.py index 819a1cbb72a..2cf59adca04 100644 --- a/tests/components/discovergy/conftest.py +++ b/tests/components/discovergy/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Discovergy integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/discovergy/test_config_flow.py b/tests/components/discovergy/test_config_flow.py index 7c257f814c4..b8da429d881 100644 --- a/tests/components/discovergy/test_config_flow.py +++ b/tests/components/discovergy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Discovergy config flow.""" + from unittest.mock import AsyncMock, patch from pydiscovergy.error import DiscovergyClientError, HTTPError, InvalidLogin diff --git a/tests/components/discovergy/test_init.py b/tests/components/discovergy/test_init.py index ac8f79540f5..adcf0545d7f 100644 --- a/tests/components/discovergy/test_init.py +++ b/tests/components/discovergy/test_init.py @@ -1,4 +1,5 @@ """Test Discovergy component setup.""" + from unittest.mock import AsyncMock from pydiscovergy.error import DiscovergyClientError, HTTPError, InvalidLogin diff --git a/tests/components/discovergy/test_sensor.py b/tests/components/discovergy/test_sensor.py index aba8229acf5..814efb1ba57 100644 --- a/tests/components/discovergy/test_sensor.py +++ b/tests/components/discovergy/test_sensor.py @@ -1,4 +1,5 @@ """Tests Discovergy sensor component.""" + from datetime import timedelta from unittest.mock import AsyncMock diff --git a/tests/components/dlink/test_config_flow.py b/tests/components/dlink/test_config_flow.py index a778a4ccb22..01e61f7a8fa 100644 --- a/tests/components/dlink/test_config_flow.py +++ b/tests/components/dlink/test_config_flow.py @@ -1,4 +1,5 @@ """Test D-Link Smart Plug config flow.""" + from unittest.mock import MagicMock, patch from homeassistant import data_entry_flow diff --git a/tests/components/dlink/test_init.py b/tests/components/dlink/test_init.py index 4725d0cd3e8..484927340fa 100644 --- a/tests/components/dlink/test_init.py +++ b/tests/components/dlink/test_init.py @@ -1,4 +1,5 @@ """Test D-Link Smart Plug setup.""" + from unittest.mock import MagicMock from homeassistant.components.dlink.const import DOMAIN diff --git a/tests/components/dlink/test_switch.py b/tests/components/dlink/test_switch.py index 845e8dfe85a..d070158d9fb 100644 --- a/tests/components/dlink/test_switch.py +++ b/tests/components/dlink/test_switch.py @@ -1,4 +1,5 @@ """Switch tests for the D-Link Smart Plug integration.""" + from unittest.mock import patch from homeassistant.components.dlink import DOMAIN diff --git a/tests/components/dlna_dmr/conftest.py b/tests/components/dlna_dmr/conftest.py index 9e9bcbf3056..bb47a468dc4 100644 --- a/tests/components/dlna_dmr/conftest.py +++ b/tests/components/dlna_dmr/conftest.py @@ -1,4 +1,5 @@ """Fixtures for DLNA tests.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/dlna_dmr/test_config_flow.py b/tests/components/dlna_dmr/test_config_flow.py index d9b1d60708b..912b105bb58 100644 --- a/tests/components/dlna_dmr/test_config_flow.py +++ b/tests/components/dlna_dmr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DLNA config flow.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/dlna_dmr/test_data.py b/tests/components/dlna_dmr/test_data.py index 06d5e01558c..57652747ffd 100644 --- a/tests/components/dlna_dmr/test_data.py +++ b/tests/components/dlna_dmr/test_data.py @@ -1,4 +1,5 @@ """Tests for the DLNA DMR data module.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 1da970c4c92..6d39c8e74be 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the DLNA DMR media_player module.""" + from __future__ import annotations import asyncio diff --git a/tests/components/dlna_dms/conftest.py b/tests/components/dlna_dms/conftest.py index 5b785fb4ba5..eacf03e9da7 100644 --- a/tests/components/dlna_dms/conftest.py +++ b/tests/components/dlna_dms/conftest.py @@ -1,4 +1,5 @@ """Fixtures for DLNA DMS tests.""" + from __future__ import annotations from collections.abc import AsyncIterable, Iterable diff --git a/tests/components/dlna_dms/test_config_flow.py b/tests/components/dlna_dms/test_config_flow.py index c8c2998458f..8a2bda611a7 100644 --- a/tests/components/dlna_dms/test_config_flow.py +++ b/tests/components/dlna_dms/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DLNA DMS config flow.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/dlna_dms/test_device_availability.py b/tests/components/dlna_dms/test_device_availability.py index a3ec5326f00..c54588ce473 100644 --- a/tests/components/dlna_dms/test_device_availability.py +++ b/tests/components/dlna_dms/test_device_availability.py @@ -1,4 +1,5 @@ """Test how the DmsDeviceSource handles available and unavailable devices.""" + from __future__ import annotations import asyncio diff --git a/tests/components/dlna_dms/test_dms_device_source.py b/tests/components/dlna_dms/test_dms_device_source.py index 622a3b8a4f9..75b26ceb900 100644 --- a/tests/components/dlna_dms/test_dms_device_source.py +++ b/tests/components/dlna_dms/test_dms_device_source.py @@ -1,4 +1,5 @@ """Test the browse and resolve methods of DmsDeviceSource.""" + from __future__ import annotations from typing import Final, Union diff --git a/tests/components/dlna_dms/test_media_source.py b/tests/components/dlna_dms/test_media_source.py index 35f34d0689b..641232e356a 100644 --- a/tests/components/dlna_dms/test_media_source.py +++ b/tests/components/dlna_dms/test_media_source.py @@ -1,4 +1,5 @@ """Tests for dlna_dms.media_source, mostly testing DmsMediaSource.""" + from unittest.mock import ANY, Mock from async_upnp_client.exceptions import UpnpError diff --git a/tests/components/dnsip/test_config_flow.py b/tests/components/dnsip/test_config_flow.py index 7e219326ee9..7575efe220d 100644 --- a/tests/components/dnsip/test_config_flow.py +++ b/tests/components/dnsip/test_config_flow.py @@ -1,4 +1,5 @@ """Test the dnsip config flow.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/dnsip/test_init.py b/tests/components/dnsip/test_init.py index 2869f13ca87..37595444c44 100644 --- a/tests/components/dnsip/test_init.py +++ b/tests/components/dnsip/test_init.py @@ -1,4 +1,5 @@ """Test for DNS IP component Init.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/dnsip/test_sensor.py b/tests/components/dnsip/test_sensor.py index 6fd24ad9b13..e1353d83268 100644 --- a/tests/components/dnsip/test_sensor.py +++ b/tests/components/dnsip/test_sensor.py @@ -1,4 +1,5 @@ """The test for the DNS IP sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/doorbird/test_config_flow.py b/tests/components/doorbird/test_config_flow.py index 7ad7fbe07ac..7df281481ad 100644 --- a/tests/components/doorbird/test_config_flow.py +++ b/tests/components/doorbird/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DoorBird config flow.""" + from ipaddress import ip_address from unittest.mock import MagicMock, Mock, patch diff --git a/tests/components/dormakaba_dkey/test_config_flow.py b/tests/components/dormakaba_dkey/test_config_flow.py index 8c0156e221b..9b070071210 100644 --- a/tests/components/dormakaba_dkey/test_config_flow.py +++ b/tests/components/dormakaba_dkey/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Dormakaba dKey config flow.""" + from unittest.mock import patch from bleak.exc import BleakError diff --git a/tests/components/dremel_3d_printer/conftest.py b/tests/components/dremel_3d_printer/conftest.py index 8df59a2e64a..0284d8baebf 100644 --- a/tests/components/dremel_3d_printer/conftest.py +++ b/tests/components/dremel_3d_printer/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the Dremel 3D Printer integration.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/dremel_3d_printer/test_binary_sensor.py b/tests/components/dremel_3d_printer/test_binary_sensor.py index 081cc7a02fb..6581b6ff13d 100644 --- a/tests/components/dremel_3d_printer/test_binary_sensor.py +++ b/tests/components/dremel_3d_printer/test_binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor tests for the Dremel 3D Printer integration.""" + from unittest.mock import AsyncMock from homeassistant.components.binary_sensor import BinarySensorDeviceClass diff --git a/tests/components/dremel_3d_printer/test_button.py b/tests/components/dremel_3d_printer/test_button.py index 00102b3306b..e0f76caf0ed 100644 --- a/tests/components/dremel_3d_printer/test_button.py +++ b/tests/components/dremel_3d_printer/test_button.py @@ -1,4 +1,5 @@ """Button tests for the Dremel 3D Printer integration.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/dremel_3d_printer/test_config_flow.py b/tests/components/dremel_3d_printer/test_config_flow.py index e968e0af491..938068aa9b0 100644 --- a/tests/components/dremel_3d_printer/test_config_flow.py +++ b/tests/components/dremel_3d_printer/test_config_flow.py @@ -1,4 +1,5 @@ """Test Dremel 3D Printer config flow.""" + from unittest.mock import patch from requests.exceptions import ConnectTimeout diff --git a/tests/components/dremel_3d_printer/test_init.py b/tests/components/dremel_3d_printer/test_init.py index fa41b74a5d2..8216054587d 100644 --- a/tests/components/dremel_3d_printer/test_init.py +++ b/tests/components/dremel_3d_printer/test_init.py @@ -1,4 +1,5 @@ """Test Dremel 3D Printer integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/dremel_3d_printer/test_sensor.py b/tests/components/dremel_3d_printer/test_sensor.py index 49d66fe1e61..c1e3a9bc14b 100644 --- a/tests/components/dremel_3d_printer/test_sensor.py +++ b/tests/components/dremel_3d_printer/test_sensor.py @@ -1,4 +1,5 @@ """Sensor tests for the Dremel 3D Printer integration.""" + from datetime import datetime from unittest.mock import AsyncMock diff --git a/tests/components/drop_connect/test_config_flow.py b/tests/components/drop_connect/test_config_flow.py index fb727d2c7fd..180b6fef860 100644 --- a/tests/components/drop_connect/test_config_flow.py +++ b/tests/components/drop_connect/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from homeassistant import config_entries from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 2d44b67e870..5a677794e46 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DSMR config flow.""" + from itertools import chain, repeat import os from typing import Any diff --git a/tests/components/dsmr/test_init.py b/tests/components/dsmr/test_init.py index b42f26f4ccc..f87c17f6a19 100644 --- a/tests/components/dsmr/test_init.py +++ b/tests/components/dsmr/test_init.py @@ -1,4 +1,5 @@ """Tests for the DSMR integration.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/dsmr_reader/test_config_flow.py b/tests/components/dsmr_reader/test_config_flow.py index 42d18d866a9..cc605eaa49c 100644 --- a/tests/components/dsmr_reader/test_config_flow.py +++ b/tests/components/dsmr_reader/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the config flow.""" + from homeassistant.components.dsmr_reader.const import DOMAIN from homeassistant.config_entries import SOURCE_USER from homeassistant.core import HomeAssistant diff --git a/tests/components/duckdns/test_init.py b/tests/components/duckdns/test_init.py index c890fa78e2f..d019861af1b 100644 --- a/tests/components/duckdns/test_init.py +++ b/tests/components/duckdns/test_init.py @@ -1,4 +1,5 @@ """Test the DuckDNS component.""" + from datetime import timedelta import logging diff --git a/tests/components/dunehd/test_config_flow.py b/tests/components/dunehd/test_config_flow.py index 18f00281fc7..1ef799420a5 100644 --- a/tests/components/dunehd/test_config_flow.py +++ b/tests/components/dunehd/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Dune HD config flow.""" + from unittest.mock import patch from homeassistant import data_entry_flow diff --git a/tests/components/duotecno/conftest.py b/tests/components/duotecno/conftest.py index 82c3e0c7f44..c79210bdfe0 100644 --- a/tests/components/duotecno/conftest.py +++ b/tests/components/duotecno/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the duotecno tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/duotecno/test_config_flow.py b/tests/components/duotecno/test_config_flow.py index a02fea8008c..b62b6e90801 100644 --- a/tests/components/duotecno/test_config_flow.py +++ b/tests/components/duotecno/test_config_flow.py @@ -1,4 +1,5 @@ """Test the duotecno config flow.""" + from unittest.mock import AsyncMock, patch from duotecno.exceptions import InvalidPassword diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index 355a1285a56..91458b0aaff 100644 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -1,4 +1,5 @@ """Common functions for tests.""" + from unittest.mock import AsyncMock, Mock, call, patch from homeassistant.components import dynalite diff --git a/tests/components/dynalite/conftest.py b/tests/components/dynalite/conftest.py index 59f109e7e47..4d193cbb38b 100644 --- a/tests/components/dynalite/conftest.py +++ b/tests/components/dynalite/conftest.py @@ -1,2 +1,3 @@ """dynalite conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/dynalite/test_bridge.py b/tests/components/dynalite/test_bridge.py index efadfd0b3f8..b0517b89031 100644 --- a/tests/components/dynalite/test_bridge.py +++ b/tests/components/dynalite/test_bridge.py @@ -1,4 +1,5 @@ """Test Dynalite bridge.""" + from unittest.mock import AsyncMock, Mock, patch from dynalite_devices_lib.dynalite_devices import ( diff --git a/tests/components/dynalite/test_config_flow.py b/tests/components/dynalite/test_config_flow.py index f337c7c3e74..724cb616deb 100644 --- a/tests/components/dynalite/test_config_flow.py +++ b/tests/components/dynalite/test_config_flow.py @@ -1,4 +1,5 @@ """Test Dynalite config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/dynalite/test_cover.py b/tests/components/dynalite/test_cover.py index 8ee205741e4..c43d349d184 100644 --- a/tests/components/dynalite/test_cover.py +++ b/tests/components/dynalite/test_cover.py @@ -1,4 +1,5 @@ """Test Dynalite cover.""" + from unittest.mock import Mock from dynalite_devices_lib.cover import DynaliteTimeCoverWithTiltDevice diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index a95ef72f61f..94ff75d4a57 100644 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -1,4 +1,5 @@ """Test Dynalite __init__.""" + from unittest.mock import call, patch import pytest diff --git a/tests/components/dynalite/test_light.py b/tests/components/dynalite/test_light.py index a17c336efb0..901544cdf27 100644 --- a/tests/components/dynalite/test_light.py +++ b/tests/components/dynalite/test_light.py @@ -1,4 +1,5 @@ """Test Dynalite light.""" + from unittest.mock import Mock, PropertyMock from dynalite_devices_lib.light import DynaliteChannelLightDevice diff --git a/tests/components/dynalite/test_switch.py b/tests/components/dynalite/test_switch.py index 9871c0817d3..97fb14f2840 100644 --- a/tests/components/dynalite/test_switch.py +++ b/tests/components/dynalite/test_switch.py @@ -1,4 +1,5 @@ """Test Dynalite switch.""" + from unittest.mock import Mock from dynalite_devices_lib.switch import DynalitePresetSwitchDevice diff --git a/tests/components/eafm/test_config_flow.py b/tests/components/eafm/test_config_flow.py index 208f406d8b9..e8f86154e67 100644 --- a/tests/components/eafm/test_config_flow.py +++ b/tests/components/eafm/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for eafm config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/easyenergy/conftest.py b/tests/components/easyenergy/conftest.py index 442221c0147..dd8abae4d4a 100644 --- a/tests/components/easyenergy/conftest.py +++ b/tests/components/easyenergy/conftest.py @@ -1,4 +1,5 @@ """Fixtures for easyEnergy integration tests.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/easyenergy/test_config_flow.py b/tests/components/easyenergy/test_config_flow.py index 30d4924db8c..4e76d48b663 100644 --- a/tests/components/easyenergy/test_config_flow.py +++ b/tests/components/easyenergy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the easyEnergy config flow.""" + from unittest.mock import MagicMock from homeassistant.components.easyenergy.const import DOMAIN diff --git a/tests/components/easyenergy/test_diagnostics.py b/tests/components/easyenergy/test_diagnostics.py index f76821cf265..d0eb9de3b00 100644 --- a/tests/components/easyenergy/test_diagnostics.py +++ b/tests/components/easyenergy/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the easyEnergy integration.""" + from unittest.mock import MagicMock from easyenergy import EasyEnergyNoDataError diff --git a/tests/components/easyenergy/test_init.py b/tests/components/easyenergy/test_init.py index ed12d805e7a..74293049fd1 100644 --- a/tests/components/easyenergy/test_init.py +++ b/tests/components/easyenergy/test_init.py @@ -1,4 +1,5 @@ """Tests for the easyEnergy integration.""" + from unittest.mock import MagicMock, patch from easyenergy import EasyEnergyConnectionError diff --git a/tests/components/ecobee/common.py b/tests/components/ecobee/common.py index ff9bc1b61fd..60f17c3618d 100644 --- a/tests/components/ecobee/common.py +++ b/tests/components/ecobee/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for Ecobee.""" + from unittest.mock import patch from homeassistant.components.ecobee.const import CONF_REFRESH_TOKEN, DOMAIN diff --git a/tests/components/ecobee/conftest.py b/tests/components/ecobee/conftest.py index 05700fa2e20..952c2f3fba3 100644 --- a/tests/components/ecobee/conftest.py +++ b/tests/components/ecobee/conftest.py @@ -1,4 +1,5 @@ """Fixtures for tests.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/ecobee/test_config_flow.py b/tests/components/ecobee/test_config_flow.py index a0f34e3cd21..832e3f6c66f 100644 --- a/tests/components/ecobee/test_config_flow.py +++ b/tests/components/ecobee/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the ecobee config flow.""" + from unittest.mock import patch from pyecobee import ECOBEE_API_KEY, ECOBEE_REFRESH_TOKEN diff --git a/tests/components/ecobee/test_humidifier.py b/tests/components/ecobee/test_humidifier.py index 734c94c8752..36b52c9c357 100644 --- a/tests/components/ecobee/test_humidifier.py +++ b/tests/components/ecobee/test_humidifier.py @@ -1,4 +1,5 @@ """The test for the ecobee thermostat humidifier module.""" + from unittest.mock import patch import pytest diff --git a/tests/components/ecobee/test_number.py b/tests/components/ecobee/test_number.py index 20974a3b55e..da5c8135a05 100644 --- a/tests/components/ecobee/test_number.py +++ b/tests/components/ecobee/test_number.py @@ -1,4 +1,5 @@ """The test for the ecobee thermostat number module.""" + from unittest.mock import patch from homeassistant.components.number import ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE diff --git a/tests/components/ecoforest/conftest.py b/tests/components/ecoforest/conftest.py index 09860546c15..79d1ea7f77b 100644 --- a/tests/components/ecoforest/conftest.py +++ b/tests/components/ecoforest/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Ecoforest tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/ecoforest/test_config_flow.py b/tests/components/ecoforest/test_config_flow.py index 302cbe76fa9..95c63a2515d 100644 --- a/tests/components/ecoforest/test_config_flow.py +++ b/tests/components/ecoforest/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ecoforest config flow.""" + from unittest.mock import AsyncMock, patch from pyecoforest.exceptions import EcoforestAuthenticationRequired diff --git a/tests/components/econet/test_config_flow.py b/tests/components/econet/test_config_flow.py index 3444cc83834..7cd6d35deeb 100644 --- a/tests/components/econet/test_config_flow.py +++ b/tests/components/econet/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Econet component.""" + from unittest.mock import patch from pyeconet.api import EcoNetApiInterface diff --git a/tests/components/ecovacs/conftest.py b/tests/components/ecovacs/conftest.py index 31d7246e6bc..29649e91573 100644 --- a/tests/components/ecovacs/conftest.py +++ b/tests/components/ecovacs/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Ecovacs tests.""" + from collections.abc import Generator from typing import Any from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/ecovacs/test_config_flow.py b/tests/components/ecovacs/test_config_flow.py index 5e02ec7dede..6bd30c3a201 100644 --- a/tests/components/ecovacs/test_config_flow.py +++ b/tests/components/ecovacs/test_config_flow.py @@ -1,4 +1,5 @@ """Test Ecovacs config flow.""" + from collections.abc import Awaitable, Callable import ssl from typing import Any diff --git a/tests/components/ecovacs/test_init.py b/tests/components/ecovacs/test_init.py index 4cad3e74ae0..bfaf2005e6d 100644 --- a/tests/components/ecovacs/test_init.py +++ b/tests/components/ecovacs/test_init.py @@ -1,4 +1,5 @@ """Test init of ecovacs.""" + from typing import Any from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/ecowitt/test_config_flow.py b/tests/components/ecowitt/test_config_flow.py index c09fb951b11..24a45e2d31b 100644 --- a/tests/components/ecowitt/test_config_flow.py +++ b/tests/components/ecowitt/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ecowitt Weather Station config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/efergy/test_config_flow.py b/tests/components/efergy/test_config_flow.py index ccc826616ff..3a7529da395 100644 --- a/tests/components/efergy/test_config_flow.py +++ b/tests/components/efergy/test_config_flow.py @@ -1,4 +1,5 @@ """Test Efergy config flow.""" + from unittest.mock import patch from pyefergy import exceptions diff --git a/tests/components/efergy/test_init.py b/tests/components/efergy/test_init.py index df6d6a7b112..5c72e1a5cfd 100644 --- a/tests/components/efergy/test_init.py +++ b/tests/components/efergy/test_init.py @@ -1,4 +1,5 @@ """Test Efergy integration.""" + from pyefergy import exceptions from homeassistant.components.efergy.const import DEFAULT_NAME, DOMAIN diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index afeb5f6e382..e1a893f4f86 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Efergy sensor platform.""" + from datetime import timedelta import pytest diff --git a/tests/components/electrasmart/test_config_flow.py b/tests/components/electrasmart/test_config_flow.py index 929259a0ccf..0da8ae4e400 100644 --- a/tests/components/electrasmart/test_config_flow.py +++ b/tests/components/electrasmart/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Electra Smart config flow.""" + from json import loads from unittest.mock import patch diff --git a/tests/components/electric_kiwi/conftest.py b/tests/components/electric_kiwi/conftest.py index 684fef24240..0a1d32f0ec0 100644 --- a/tests/components/electric_kiwi/conftest.py +++ b/tests/components/electric_kiwi/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for electric kiwi tests.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Generator diff --git a/tests/components/electric_kiwi/test_config_flow.py b/tests/components/electric_kiwi/test_config_flow.py index 1199c3e555a..d91936eeebf 100644 --- a/tests/components/electric_kiwi/test_config_flow.py +++ b/tests/components/electric_kiwi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Electric Kiwi config flow.""" + from __future__ import annotations from http import HTTPStatus diff --git a/tests/components/elgato/conftest.py b/tests/components/elgato/conftest.py index e8be6a4810b..b1dd94ce069 100644 --- a/tests/components/elgato/conftest.py +++ b/tests/components/elgato/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Elgato integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/elgato/test_button.py b/tests/components/elgato/test_button.py index bd6c9a1bfe5..ab2169b623e 100644 --- a/tests/components/elgato/test_button.py +++ b/tests/components/elgato/test_button.py @@ -1,4 +1,5 @@ """Tests for the Elgato Light button platform.""" + from unittest.mock import MagicMock from elgato import ElgatoError diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py index bfae6fc9a17..def12307107 100644 --- a/tests/components/elgato/test_config_flow.py +++ b/tests/components/elgato/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Elgato Key Light config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/elgato/test_init.py b/tests/components/elgato/test_init.py index 1b566ef8ab2..a4ccb302461 100644 --- a/tests/components/elgato/test_init.py +++ b/tests/components/elgato/test_init.py @@ -1,4 +1,5 @@ """Tests for the Elgato Key Light integration.""" + from unittest.mock import MagicMock from elgato import ElgatoConnectionError diff --git a/tests/components/elgato/test_light.py b/tests/components/elgato/test_light.py index 8a3d3382a5f..40c0232c2b3 100644 --- a/tests/components/elgato/test_light.py +++ b/tests/components/elgato/test_light.py @@ -1,4 +1,5 @@ """Tests for the Elgato Key Light light platform.""" + from unittest.mock import MagicMock from elgato import ElgatoError diff --git a/tests/components/elgato/test_switch.py b/tests/components/elgato/test_switch.py index 336c2a9376b..fc6dbfb1828 100644 --- a/tests/components/elgato/test_switch.py +++ b/tests/components/elgato/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Elgato switch platform.""" + from unittest.mock import MagicMock from elgato import ElgatoError diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index 0295781dc5f..b69c23bf9cb 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Elk-M1 Control config flow.""" + from dataclasses import asdict from unittest.mock import patch diff --git a/tests/components/elkm1/test_logbook.py b/tests/components/elkm1/test_logbook.py index 90e33b3911e..35977ec98f0 100644 --- a/tests/components/elkm1/test_logbook.py +++ b/tests/components/elkm1/test_logbook.py @@ -1,4 +1,5 @@ """The tests for elkm1 logbook.""" + from homeassistant.components.elkm1.const import ( ATTR_KEY, ATTR_KEY_NAME, diff --git a/tests/components/elmax/test_config_flow.py b/tests/components/elmax/test_config_flow.py index ed23d3b8fb5..6782b3f9b7a 100644 --- a/tests/components/elmax/test_config_flow.py +++ b/tests/components/elmax/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Elmax config flow.""" + from unittest.mock import patch from elmax_api.exceptions import ElmaxBadLoginError, ElmaxBadPinError, ElmaxNetworkError diff --git a/tests/components/elvia/conftest.py b/tests/components/elvia/conftest.py index a2a10e67893..c8b98f18f3f 100644 --- a/tests/components/elvia/conftest.py +++ b/tests/components/elvia/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Elvia tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/elvia/test_config_flow.py b/tests/components/elvia/test_config_flow.py index 630aca4f16c..470037e5dd6 100644 --- a/tests/components/elvia/test_config_flow.py +++ b/tests/components/elvia/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Elvia config flow.""" + from unittest.mock import AsyncMock, patch from elvia import error as ElviaError diff --git a/tests/components/emonitor/test_config_flow.py b/tests/components/emonitor/test_config_flow.py index af4c1ac2a8a..56474673f70 100644 --- a/tests/components/emonitor/test_config_flow.py +++ b/tests/components/emonitor/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SiteSage Emonitor config flow.""" + from unittest.mock import MagicMock, patch from aioemonitor.monitor import EmonitorNetwork, EmonitorStatus diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index e8e79e78833..9bc29ce4899 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,4 +1,5 @@ """Test the Emulated Hue component.""" + from datetime import timedelta from typing import Any from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 138c7c49f93..f69bd1b0651 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -1,4 +1,5 @@ """The tests for the emulated Hue component.""" + from http import HTTPStatus import json import unittest diff --git a/tests/components/emulated_roku/test_binding.py b/tests/components/emulated_roku/test_binding.py index 1b598483f01..5bde72d2e4d 100644 --- a/tests/components/emulated_roku/test_binding.py +++ b/tests/components/emulated_roku/test_binding.py @@ -1,4 +1,5 @@ """Tests for emulated_roku library bindings.""" + from unittest.mock import AsyncMock, Mock, patch from homeassistant.components.emulated_roku.binding import ( diff --git a/tests/components/emulated_roku/test_config_flow.py b/tests/components/emulated_roku/test_config_flow.py index 3d620ef6954..700adbf0039 100644 --- a/tests/components/emulated_roku/test_config_flow.py +++ b/tests/components/emulated_roku/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for emulated_roku config flow.""" + from homeassistant import config_entries from homeassistant.components.emulated_roku import config_flow from homeassistant.core import HomeAssistant diff --git a/tests/components/emulated_roku/test_init.py b/tests/components/emulated_roku/test_init.py index 117f5954b61..17a6e106fc3 100644 --- a/tests/components/emulated_roku/test_init.py +++ b/tests/components/emulated_roku/test_init.py @@ -1,4 +1,5 @@ """Test emulated_roku component setup process.""" + from unittest.mock import AsyncMock, Mock, patch from homeassistant.components import emulated_roku diff --git a/tests/components/energy/test_validate.py b/tests/components/energy/test_validate.py index 3c4a28df11d..afefe7810c9 100644 --- a/tests/components/energy/test_validate.py +++ b/tests/components/energy/test_validate.py @@ -1,4 +1,5 @@ """Test that validation works.""" + from unittest.mock import patch import pytest diff --git a/tests/components/energy/test_websocket_api.py b/tests/components/energy/test_websocket_api.py index f136c37b544..afb23e4e88a 100644 --- a/tests/components/energy/test_websocket_api.py +++ b/tests/components/energy/test_websocket_api.py @@ -1,4 +1,5 @@ """Test the Energy websocket API.""" + from typing import Any from unittest.mock import AsyncMock, Mock diff --git a/tests/components/energyzero/conftest.py b/tests/components/energyzero/conftest.py index 42b05eff444..2198e8c0c79 100644 --- a/tests/components/energyzero/conftest.py +++ b/tests/components/energyzero/conftest.py @@ -1,4 +1,5 @@ """Fixtures for EnergyZero integration tests.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/energyzero/test_config_flow.py b/tests/components/energyzero/test_config_flow.py index 5f7b4925036..d16ea5cc8a8 100644 --- a/tests/components/energyzero/test_config_flow.py +++ b/tests/components/energyzero/test_config_flow.py @@ -1,4 +1,5 @@ """Test the EnergyZero config flow.""" + from unittest.mock import MagicMock from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/energyzero/test_diagnostics.py b/tests/components/energyzero/test_diagnostics.py index db0821cc951..f4408ded05d 100644 --- a/tests/components/energyzero/test_diagnostics.py +++ b/tests/components/energyzero/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the EnergyZero integration.""" + from unittest.mock import MagicMock from energyzero import EnergyZeroNoDataError diff --git a/tests/components/energyzero/test_init.py b/tests/components/energyzero/test_init.py index b7072108b35..287157026f4 100644 --- a/tests/components/energyzero/test_init.py +++ b/tests/components/energyzero/test_init.py @@ -1,4 +1,5 @@ """Tests for the EnergyZero integration.""" + from unittest.mock import MagicMock, patch from energyzero import EnergyZeroConnectionError diff --git a/tests/components/energyzero/test_sensor.py b/tests/components/energyzero/test_sensor.py index 6c7eec9d5d8..5c4700c21f1 100644 --- a/tests/components/energyzero/test_sensor.py +++ b/tests/components/energyzero/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the EnergyZero integration.""" + from unittest.mock import MagicMock from energyzero import EnergyZeroNoDataError diff --git a/tests/components/enocean/test_config_flow.py b/tests/components/enocean/test_config_flow.py index ad13af0caf2..0acecd6c0b4 100644 --- a/tests/components/enocean/test_config_flow.py +++ b/tests/components/enocean/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for EnOcean config flow.""" + from unittest.mock import Mock, patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/enphase_envoy/conftest.py b/tests/components/enphase_envoy/conftest.py index c042846cd0b..c8ee5e2631f 100644 --- a/tests/components/enphase_envoy/conftest.py +++ b/tests/components/enphase_envoy/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for Enphase Envoy.""" + from unittest.mock import AsyncMock, Mock, patch from pyenphase import ( diff --git a/tests/components/enphase_envoy/test_config_flow.py b/tests/components/enphase_envoy/test_config_flow.py index 11f0cbeb871..9000cf92e0e 100644 --- a/tests/components/enphase_envoy/test_config_flow.py +++ b/tests/components/enphase_envoy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Enphase Envoy config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock diff --git a/tests/components/enphase_envoy/test_diagnostics.py b/tests/components/enphase_envoy/test_diagnostics.py index c3659b2a9bb..10c77992b6a 100644 --- a/tests/components/enphase_envoy/test_diagnostics.py +++ b/tests/components/enphase_envoy/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Enphase Envoy diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/enphase_envoy/test_sensor.py b/tests/components/enphase_envoy/test_sensor.py index 424ce2cecdd..784fb47e8c1 100644 --- a/tests/components/enphase_envoy/test_sensor.py +++ b/tests/components/enphase_envoy/test_sensor.py @@ -1,4 +1,5 @@ """Test Enphase Envoy sensors.""" + from unittest.mock import patch import pytest diff --git a/tests/components/environment_canada/test_config_flow.py b/tests/components/environment_canada/test_config_flow.py index b745ac02693..3b6661b53bc 100644 --- a/tests/components/environment_canada/test_config_flow.py +++ b/tests/components/environment_canada/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Environment Canada (EC) config flow.""" + from unittest.mock import AsyncMock, MagicMock, Mock, patch import xml.etree.ElementTree as et diff --git a/tests/components/environment_canada/test_diagnostics.py b/tests/components/environment_canada/test_diagnostics.py index fb1597e3622..75389defb86 100644 --- a/tests/components/environment_canada/test_diagnostics.py +++ b/tests/components/environment_canada/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Environment Canada diagnostics.""" + from datetime import UTC, datetime import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/epion/test_config_flow.py b/tests/components/epion/test_config_flow.py index d7329f64324..8d246ac4dd4 100644 --- a/tests/components/epion/test_config_flow.py +++ b/tests/components/epion/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Epion config flow.""" + from unittest.mock import MagicMock, patch from epion import EpionAuthenticationError, EpionConnectionError diff --git a/tests/components/epson/test_config_flow.py b/tests/components/epson/test_config_flow.py index be0267a4af8..d18d7fe5f4a 100644 --- a/tests/components/epson/test_config_flow.py +++ b/tests/components/epson/test_config_flow.py @@ -1,4 +1,5 @@ """Test the epson config flow.""" + from unittest.mock import patch from epson_projector.const import PWR_OFF_STATE diff --git a/tests/components/epson/test_media_player.py b/tests/components/epson/test_media_player.py index 874a12173d6..e5869777482 100644 --- a/tests/components/epson/test_media_player.py +++ b/tests/components/epson/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the epson integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/esphome/bluetooth/test_client.py b/tests/components/esphome/bluetooth/test_client.py index cd250bc1080..2898ae2bda2 100644 --- a/tests/components/esphome/bluetooth/test_client.py +++ b/tests/components/esphome/bluetooth/test_client.py @@ -1,4 +1,5 @@ """Tests for ESPHomeClient.""" + from __future__ import annotations from aioesphomeapi import APIClient, APIVersion, BluetoothProxyFeature, DeviceInfo diff --git a/tests/components/esphome/conftest.py b/tests/components/esphome/conftest.py index 86eb6ef3d77..09a23824054 100644 --- a/tests/components/esphome/conftest.py +++ b/tests/components/esphome/conftest.py @@ -1,4 +1,5 @@ """esphome session fixtures.""" + from __future__ import annotations import asyncio diff --git a/tests/components/esphome/test_alarm_control_panel.py b/tests/components/esphome/test_alarm_control_panel.py index e7409bdfae4..af717ac1b49 100644 --- a/tests/components/esphome/test_alarm_control_panel.py +++ b/tests/components/esphome/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Test ESPHome alarm_control_panels.""" + from unittest.mock import call from aioesphomeapi import ( diff --git a/tests/components/esphome/test_binary_sensor.py b/tests/components/esphome/test_binary_sensor.py index 209ea344328..81bebe45c08 100644 --- a/tests/components/esphome/test_binary_sensor.py +++ b/tests/components/esphome/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test ESPHome binary sensors.""" + from collections.abc import Awaitable, Callable from aioesphomeapi import ( diff --git a/tests/components/esphome/test_camera.py b/tests/components/esphome/test_camera.py index 682ce369b57..c6a61cd18e8 100644 --- a/tests/components/esphome/test_camera.py +++ b/tests/components/esphome/test_camera.py @@ -1,4 +1,5 @@ """Test ESPHome cameras.""" + from collections.abc import Awaitable, Callable from aioesphomeapi import ( diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 9b07f9b4ee0..e06b96356ae 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from ipaddress import ip_address import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/esphome/test_dashboard.py b/tests/components/esphome/test_dashboard.py index 11db8a73731..11234e790c5 100644 --- a/tests/components/esphome/test_dashboard.py +++ b/tests/components/esphome/test_dashboard.py @@ -1,4 +1,5 @@ """Test ESPHome dashboard features.""" + from unittest.mock import patch from aioesphomeapi import DeviceInfo, InvalidAuthAPIError diff --git a/tests/components/esphome/test_diagnostics.py b/tests/components/esphome/test_diagnostics.py index d528010af1b..0f2b18218ff 100644 --- a/tests/components/esphome/test_diagnostics.py +++ b/tests/components/esphome/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the ESPHome integration.""" + from unittest.mock import ANY from syrupy import SnapshotAssertion diff --git a/tests/components/esphome/test_sensor.py b/tests/components/esphome/test_sensor.py index a824a4e3905..9f8e45ed64d 100644 --- a/tests/components/esphome/test_sensor.py +++ b/tests/components/esphome/test_sensor.py @@ -1,4 +1,5 @@ """Test ESPHome sensors.""" + from collections.abc import Awaitable, Callable import logging import math diff --git a/tests/components/esphome/test_update.py b/tests/components/esphome/test_update.py index 842480d9433..9d5745e6594 100644 --- a/tests/components/esphome/test_update.py +++ b/tests/components/esphome/test_update.py @@ -1,4 +1,5 @@ """Test ESPHome update entities.""" + from collections.abc import Awaitable, Callable import dataclasses from unittest.mock import Mock, patch diff --git a/tests/components/eufylife_ble/test_config_flow.py b/tests/components/eufylife_ble/test_config_flow.py index 477aa53c12d..c3590077d93 100644 --- a/tests/components/eufylife_ble/test_config_flow.py +++ b/tests/components/eufylife_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the EufyLife config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/event/test_init.py b/tests/components/event/test_init.py index b8ba5fb6a18..41bdac5e6bd 100644 --- a/tests/components/event/test_init.py +++ b/tests/components/event/test_init.py @@ -1,4 +1,5 @@ """The tests for the event integration.""" + from collections.abc import Generator from typing import Any diff --git a/tests/components/event/test_recorder.py b/tests/components/event/test_recorder.py index 133f7e173e3..f45846c9ecc 100644 --- a/tests/components/event/test_recorder.py +++ b/tests/components/event/test_recorder.py @@ -1,4 +1,5 @@ """The tests for event recorder.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/everlights/conftest.py b/tests/components/everlights/conftest.py index 5009251b102..b9e398e3f64 100644 --- a/tests/components/everlights/conftest.py +++ b/tests/components/everlights/conftest.py @@ -1,2 +1,3 @@ """everlights conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/everlights/test_light.py b/tests/components/everlights/test_light.py index fbcc967fdba..828b817b236 100644 --- a/tests/components/everlights/test_light.py +++ b/tests/components/everlights/test_light.py @@ -1,4 +1,5 @@ """The tests for the everlights component.""" + from homeassistant.components.everlights import light as everlights diff --git a/tests/components/evil_genius_labs/test_config_flow.py b/tests/components/evil_genius_labs/test_config_flow.py index 7826104b326..0c3ab2298ea 100644 --- a/tests/components/evil_genius_labs/test_config_flow.py +++ b/tests/components/evil_genius_labs/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Evil Genius Labs config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/evil_genius_labs/test_light.py b/tests/components/evil_genius_labs/test_light.py index 5ff7f06804a..cbc68ccf2d7 100644 --- a/tests/components/evil_genius_labs/test_light.py +++ b/tests/components/evil_genius_labs/test_light.py @@ -1,4 +1,5 @@ """Test Evil Genius Labs light.""" + from unittest.mock import patch import pytest diff --git a/tests/components/ezviz/conftest.py b/tests/components/ezviz/conftest.py index e89e375fb5e..ccfa9616efa 100644 --- a/tests/components/ezviz/conftest.py +++ b/tests/components/ezviz/conftest.py @@ -1,4 +1,5 @@ """Define pytest.fixtures available for all tests.""" + from unittest.mock import MagicMock, patch from pyezviz import EzvizClient diff --git a/tests/components/ezviz/test_config_flow.py b/tests/components/ezviz/test_config_flow.py index d3bd2a1bed6..c99c9c0fe9e 100644 --- a/tests/components/ezviz/test_config_flow.py +++ b/tests/components/ezviz/test_config_flow.py @@ -1,4 +1,5 @@ """Test the EZVIZ config flow.""" + from unittest.mock import patch from pyezviz.exceptions import ( From ff3a8019361e0f8f148d51c8de6299542ee59408 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:51:32 +0100 Subject: [PATCH 0546/1691] Add empty line after module docstring [a-d] (#112697) --- homeassistant/components/abode/__init__.py | 1 + homeassistant/components/abode/alarm_control_panel.py | 1 + homeassistant/components/abode/binary_sensor.py | 1 + homeassistant/components/abode/camera.py | 1 + homeassistant/components/abode/config_flow.py | 1 + homeassistant/components/abode/cover.py | 1 + homeassistant/components/abode/light.py | 1 + homeassistant/components/abode/lock.py | 1 + homeassistant/components/abode/sensor.py | 1 + homeassistant/components/abode/switch.py | 1 + homeassistant/components/accuweather/__init__.py | 1 + homeassistant/components/accuweather/config_flow.py | 1 + homeassistant/components/accuweather/const.py | 1 + homeassistant/components/accuweather/diagnostics.py | 1 + homeassistant/components/accuweather/sensor.py | 1 + homeassistant/components/accuweather/system_health.py | 1 + homeassistant/components/accuweather/weather.py | 1 + homeassistant/components/acer_projector/const.py | 1 + homeassistant/components/acer_projector/switch.py | 1 + homeassistant/components/acmeda/__init__.py | 1 + homeassistant/components/acmeda/base.py | 1 + homeassistant/components/acmeda/config_flow.py | 1 + homeassistant/components/acmeda/cover.py | 1 + homeassistant/components/acmeda/errors.py | 1 + homeassistant/components/acmeda/helpers.py | 1 + homeassistant/components/acmeda/hub.py | 1 + homeassistant/components/acmeda/sensor.py | 1 + homeassistant/components/actiontec/const.py | 1 + homeassistant/components/actiontec/device_tracker.py | 1 + homeassistant/components/actiontec/model.py | 1 + homeassistant/components/adax/__init__.py | 1 + homeassistant/components/adax/climate.py | 1 + homeassistant/components/adax/config_flow.py | 1 + homeassistant/components/adax/const.py | 1 + homeassistant/components/adguard/__init__.py | 1 + homeassistant/components/adguard/config_flow.py | 1 + homeassistant/components/adguard/entity.py | 1 + homeassistant/components/adguard/sensor.py | 1 + homeassistant/components/adguard/switch.py | 1 + homeassistant/components/ads/binary_sensor.py | 1 + homeassistant/components/ads/cover.py | 1 + homeassistant/components/ads/light.py | 1 + homeassistant/components/ads/sensor.py | 1 + homeassistant/components/ads/switch.py | 1 + homeassistant/components/advantage_air/__init__.py | 1 + homeassistant/components/advantage_air/binary_sensor.py | 1 + homeassistant/components/advantage_air/climate.py | 1 + homeassistant/components/advantage_air/config_flow.py | 1 + homeassistant/components/advantage_air/cover.py | 1 + homeassistant/components/advantage_air/diagnostics.py | 1 + homeassistant/components/advantage_air/entity.py | 1 + homeassistant/components/advantage_air/light.py | 1 + homeassistant/components/advantage_air/models.py | 1 + homeassistant/components/advantage_air/sensor.py | 1 + homeassistant/components/advantage_air/switch.py | 1 + homeassistant/components/aemet/config_flow.py | 1 + homeassistant/components/aemet/const.py | 1 + homeassistant/components/aemet/coordinator.py | 1 + homeassistant/components/aemet/diagnostics.py | 1 + homeassistant/components/aemet/entity.py | 1 + homeassistant/components/aemet/sensor.py | 1 + homeassistant/components/aftership/__init__.py | 1 + homeassistant/components/aftership/config_flow.py | 1 + homeassistant/components/aftership/const.py | 1 + homeassistant/components/aftership/sensor.py | 1 + homeassistant/components/agent_dvr/__init__.py | 1 + homeassistant/components/agent_dvr/alarm_control_panel.py | 1 + homeassistant/components/agent_dvr/camera.py | 1 + homeassistant/components/agent_dvr/config_flow.py | 1 + homeassistant/components/air_quality/__init__.py | 1 + homeassistant/components/airly/__init__.py | 1 + homeassistant/components/airly/config_flow.py | 1 + homeassistant/components/airly/const.py | 1 + homeassistant/components/airly/coordinator.py | 1 + homeassistant/components/airly/diagnostics.py | 1 + homeassistant/components/airly/sensor.py | 1 + homeassistant/components/airly/system_health.py | 1 + homeassistant/components/airnow/coordinator.py | 1 + homeassistant/components/airnow/diagnostics.py | 1 + homeassistant/components/airnow/sensor.py | 1 + homeassistant/components/airq/__init__.py | 1 + homeassistant/components/airq/config_flow.py | 1 + homeassistant/components/airq/const.py | 1 + homeassistant/components/airq/coordinator.py | 1 + homeassistant/components/airq/sensor.py | 1 + homeassistant/components/airthings/__init__.py | 1 + homeassistant/components/airthings/config_flow.py | 1 + homeassistant/components/airthings/sensor.py | 1 + homeassistant/components/airthings_ble/__init__.py | 1 + homeassistant/components/airthings_ble/sensor.py | 1 + homeassistant/components/airtouch4/__init__.py | 1 + homeassistant/components/airtouch4/climate.py | 1 + homeassistant/components/airtouch4/config_flow.py | 1 + homeassistant/components/airtouch5/__init__.py | 1 + homeassistant/components/airtouch5/config_flow.py | 1 + homeassistant/components/airtouch5/entity.py | 1 + homeassistant/components/airvisual/__init__.py | 1 + homeassistant/components/airvisual/config_flow.py | 1 + homeassistant/components/airvisual/diagnostics.py | 1 + homeassistant/components/airvisual/sensor.py | 1 + homeassistant/components/airvisual_pro/__init__.py | 1 + homeassistant/components/airvisual_pro/config_flow.py | 1 + homeassistant/components/airvisual_pro/diagnostics.py | 1 + homeassistant/components/airvisual_pro/sensor.py | 1 + homeassistant/components/airzone/__init__.py | 1 + homeassistant/components/airzone/binary_sensor.py | 1 + homeassistant/components/airzone/climate.py | 1 + homeassistant/components/airzone/config_flow.py | 1 + homeassistant/components/airzone/coordinator.py | 1 + homeassistant/components/airzone/diagnostics.py | 1 + homeassistant/components/airzone/entity.py | 1 + homeassistant/components/airzone/select.py | 1 + homeassistant/components/airzone/sensor.py | 1 + homeassistant/components/airzone/water_heater.py | 1 + homeassistant/components/airzone_cloud/__init__.py | 1 + homeassistant/components/airzone_cloud/binary_sensor.py | 1 + homeassistant/components/airzone_cloud/climate.py | 1 + homeassistant/components/airzone_cloud/config_flow.py | 1 + homeassistant/components/airzone_cloud/coordinator.py | 1 + homeassistant/components/airzone_cloud/diagnostics.py | 1 + homeassistant/components/airzone_cloud/entity.py | 1 + homeassistant/components/airzone_cloud/sensor.py | 1 + homeassistant/components/aladdin_connect/config_flow.py | 1 + homeassistant/components/aladdin_connect/const.py | 1 + homeassistant/components/aladdin_connect/cover.py | 1 + homeassistant/components/aladdin_connect/diagnostics.py | 1 + homeassistant/components/aladdin_connect/model.py | 1 + homeassistant/components/aladdin_connect/sensor.py | 1 + homeassistant/components/alarm_control_panel/__init__.py | 1 + homeassistant/components/alarm_control_panel/const.py | 1 + homeassistant/components/alarm_control_panel/device_action.py | 1 + homeassistant/components/alarm_control_panel/device_condition.py | 1 + homeassistant/components/alarm_control_panel/device_trigger.py | 1 + homeassistant/components/alarm_control_panel/reproduce_state.py | 1 + .../components/alarm_control_panel/significant_change.py | 1 + homeassistant/components/alarmdecoder/__init__.py | 1 + homeassistant/components/alarmdecoder/alarm_control_panel.py | 1 + homeassistant/components/alarmdecoder/config_flow.py | 1 + homeassistant/components/alarmdecoder/sensor.py | 1 + homeassistant/components/alert/__init__.py | 1 + homeassistant/components/alert/reproduce_state.py | 1 + homeassistant/components/alexa/__init__.py | 1 + homeassistant/components/alexa/capabilities.py | 1 + homeassistant/components/alexa/config.py | 1 + homeassistant/components/alexa/const.py | 1 + homeassistant/components/alexa/entities.py | 1 + homeassistant/components/alexa/errors.py | 1 + homeassistant/components/alexa/handlers.py | 1 + homeassistant/components/alexa/logbook.py | 1 + homeassistant/components/alexa/state_report.py | 1 + homeassistant/components/alpha_vantage/sensor.py | 1 + homeassistant/components/amazon_polly/const.py | 1 + homeassistant/components/amazon_polly/tts.py | 1 + homeassistant/components/amberelectric/config_flow.py | 1 + homeassistant/components/amberelectric/coordinator.py | 1 + homeassistant/components/ambiclimate/climate.py | 1 + homeassistant/components/ambient_station/__init__.py | 1 + homeassistant/components/ambient_station/binary_sensor.py | 1 + homeassistant/components/ambient_station/config_flow.py | 1 + homeassistant/components/ambient_station/diagnostics.py | 1 + homeassistant/components/ambient_station/entity.py | 1 + homeassistant/components/ambient_station/sensor.py | 1 + homeassistant/components/amcrest/__init__.py | 1 + homeassistant/components/amcrest/binary_sensor.py | 1 + homeassistant/components/amcrest/camera.py | 1 + homeassistant/components/amcrest/helpers.py | 1 + homeassistant/components/amcrest/sensor.py | 1 + homeassistant/components/amcrest/switch.py | 1 + homeassistant/components/ampio/air_quality.py | 1 + homeassistant/components/ampio/const.py | 1 + homeassistant/components/analytics/__init__.py | 1 + homeassistant/components/analytics/analytics.py | 1 + homeassistant/components/analytics/const.py | 1 + homeassistant/components/analytics_insights/__init__.py | 1 + homeassistant/components/analytics_insights/config_flow.py | 1 + homeassistant/components/analytics_insights/coordinator.py | 1 + homeassistant/components/analytics_insights/sensor.py | 1 + homeassistant/components/android_ip_webcam/__init__.py | 1 + homeassistant/components/android_ip_webcam/binary_sensor.py | 1 + homeassistant/components/android_ip_webcam/camera.py | 1 + homeassistant/components/android_ip_webcam/config_flow.py | 1 + homeassistant/components/android_ip_webcam/sensor.py | 1 + homeassistant/components/android_ip_webcam/switch.py | 1 + homeassistant/components/androidtv/__init__.py | 1 + homeassistant/components/androidtv/config_flow.py | 1 + homeassistant/components/androidtv/diagnostics.py | 1 + homeassistant/components/androidtv/entity.py | 1 + homeassistant/components/androidtv/media_player.py | 1 + homeassistant/components/androidtv_remote/__init__.py | 1 + homeassistant/components/androidtv_remote/config_flow.py | 1 + homeassistant/components/androidtv_remote/const.py | 1 + homeassistant/components/androidtv_remote/diagnostics.py | 1 + homeassistant/components/androidtv_remote/entity.py | 1 + homeassistant/components/androidtv_remote/helpers.py | 1 + homeassistant/components/androidtv_remote/media_player.py | 1 + homeassistant/components/androidtv_remote/remote.py | 1 + homeassistant/components/anel_pwrctrl/switch.py | 1 + homeassistant/components/anova/__init__.py | 1 + homeassistant/components/anova/config_flow.py | 1 + homeassistant/components/anova/coordinator.py | 1 + homeassistant/components/anova/entity.py | 1 + homeassistant/components/anova/models.py | 1 + homeassistant/components/anova/sensor.py | 1 + homeassistant/components/anthemav/__init__.py | 1 + homeassistant/components/anthemav/config_flow.py | 1 + homeassistant/components/anthemav/media_player.py | 1 + homeassistant/components/aosmith/__init__.py | 1 + homeassistant/components/aosmith/config_flow.py | 1 + homeassistant/components/aosmith/diagnostics.py | 1 + homeassistant/components/aosmith/entity.py | 1 + homeassistant/components/apache_kafka/__init__.py | 1 + homeassistant/components/apcupsd/__init__.py | 1 + homeassistant/components/apcupsd/binary_sensor.py | 1 + homeassistant/components/apcupsd/config_flow.py | 1 + homeassistant/components/apcupsd/const.py | 1 + homeassistant/components/apcupsd/coordinator.py | 1 + homeassistant/components/apcupsd/sensor.py | 1 + homeassistant/components/apple_tv/__init__.py | 1 + homeassistant/components/apple_tv/browse_media.py | 1 + homeassistant/components/apple_tv/config_flow.py | 1 + homeassistant/components/apple_tv/media_player.py | 1 + homeassistant/components/application_credentials/__init__.py | 1 + homeassistant/components/apprise/notify.py | 1 + homeassistant/components/aprs/device_tracker.py | 1 + homeassistant/components/aqualogic/__init__.py | 1 + homeassistant/components/aqualogic/sensor.py | 1 + homeassistant/components/aqualogic/switch.py | 1 + homeassistant/components/aquostv/media_player.py | 1 + homeassistant/components/aranet/__init__.py | 1 + homeassistant/components/aranet/config_flow.py | 1 + homeassistant/components/aranet/sensor.py | 1 + homeassistant/components/arcam_fmj/config_flow.py | 1 + homeassistant/components/arcam_fmj/device_trigger.py | 1 + homeassistant/components/arcam_fmj/media_player.py | 1 + homeassistant/components/arest/binary_sensor.py | 1 + homeassistant/components/arest/sensor.py | 1 + homeassistant/components/arest/switch.py | 1 + homeassistant/components/arris_tg2492lg/device_tracker.py | 1 + homeassistant/components/aruba/device_tracker.py | 1 + homeassistant/components/arwn/sensor.py | 1 + homeassistant/components/aseko_pool_live/__init__.py | 1 + homeassistant/components/aseko_pool_live/binary_sensor.py | 1 + homeassistant/components/aseko_pool_live/config_flow.py | 1 + homeassistant/components/aseko_pool_live/coordinator.py | 1 + homeassistant/components/aseko_pool_live/entity.py | 1 + homeassistant/components/aseko_pool_live/sensor.py | 1 + homeassistant/components/assist_pipeline/__init__.py | 1 + homeassistant/components/assist_pipeline/logbook.py | 1 + homeassistant/components/assist_pipeline/pipeline.py | 1 + homeassistant/components/assist_pipeline/vad.py | 1 + homeassistant/components/asterisk_cdr/mailbox.py | 1 + homeassistant/components/asterisk_mbox/mailbox.py | 1 + homeassistant/components/asuswrt/bridge.py | 1 + homeassistant/components/asuswrt/device_tracker.py | 1 + homeassistant/components/asuswrt/diagnostics.py | 1 + homeassistant/components/asuswrt/router.py | 1 + homeassistant/components/asuswrt/sensor.py | 1 + homeassistant/components/atag/__init__.py | 1 + homeassistant/components/atag/climate.py | 1 + homeassistant/components/atag/config_flow.py | 1 + homeassistant/components/atag/sensor.py | 1 + homeassistant/components/atag/water_heater.py | 1 + homeassistant/components/aten_pe/switch.py | 1 + homeassistant/components/atome/sensor.py | 1 + homeassistant/components/august/__init__.py | 1 + homeassistant/components/august/activity.py | 1 + homeassistant/components/august/binary_sensor.py | 1 + homeassistant/components/august/button.py | 1 + homeassistant/components/august/camera.py | 1 + homeassistant/components/august/config_flow.py | 1 + homeassistant/components/august/diagnostics.py | 1 + homeassistant/components/august/entity.py | 1 + homeassistant/components/august/lock.py | 1 + homeassistant/components/august/sensor.py | 1 + homeassistant/components/august/subscriber.py | 1 + homeassistant/components/aurora/binary_sensor.py | 1 + homeassistant/components/aurora/config_flow.py | 1 + homeassistant/components/aurora/coordinator.py | 1 + homeassistant/components/aurora/sensor.py | 1 + homeassistant/components/aurora_abb_powerone/config_flow.py | 1 + homeassistant/components/aurora_abb_powerone/sensor.py | 1 + homeassistant/components/aussie_broadband/__init__.py | 1 + homeassistant/components/aussie_broadband/config_flow.py | 1 + homeassistant/components/aussie_broadband/diagnostics.py | 1 + homeassistant/components/aussie_broadband/sensor.py | 1 + homeassistant/components/auth/__init__.py | 1 + homeassistant/components/auth/indieauth.py | 1 + homeassistant/components/auth/login_flow.py | 1 + homeassistant/components/auth/mfa_setup_flow.py | 1 + homeassistant/components/automation/__init__.py | 1 + homeassistant/components/automation/config.py | 1 + homeassistant/components/automation/helpers.py | 1 + homeassistant/components/automation/logbook.py | 1 + homeassistant/components/automation/reproduce_state.py | 1 + homeassistant/components/automation/trace.py | 1 + homeassistant/components/avea/light.py | 1 + homeassistant/components/avion/light.py | 1 + homeassistant/components/awair/__init__.py | 1 + homeassistant/components/awair/config_flow.py | 1 + homeassistant/components/awair/const.py | 1 + homeassistant/components/awair/coordinator.py | 1 + homeassistant/components/awair/sensor.py | 1 + homeassistant/components/aws/notify.py | 1 + homeassistant/components/axis/binary_sensor.py | 1 + homeassistant/components/axis/camera.py | 1 + homeassistant/components/axis/config_flow.py | 1 + homeassistant/components/axis/diagnostics.py | 1 + homeassistant/components/axis/errors.py | 1 + homeassistant/components/axis/light.py | 1 + homeassistant/components/axis/switch.py | 1 + homeassistant/components/azure_devops/__init__.py | 1 + homeassistant/components/azure_devops/config_flow.py | 1 + homeassistant/components/azure_devops/sensor.py | 1 + homeassistant/components/azure_event_hub/__init__.py | 1 + homeassistant/components/azure_event_hub/client.py | 1 + homeassistant/components/azure_event_hub/config_flow.py | 1 + homeassistant/components/azure_event_hub/const.py | 1 + homeassistant/components/azure_service_bus/notify.py | 1 + homeassistant/components/backup/__init__.py | 1 + homeassistant/components/backup/const.py | 1 + homeassistant/components/backup/http.py | 1 + homeassistant/components/backup/manager.py | 1 + homeassistant/components/backup/websocket.py | 1 + homeassistant/components/baf/__init__.py | 1 + homeassistant/components/baf/binary_sensor.py | 1 + homeassistant/components/baf/climate.py | 1 + homeassistant/components/baf/config_flow.py | 1 + homeassistant/components/baf/entity.py | 1 + homeassistant/components/baf/fan.py | 1 + homeassistant/components/baf/light.py | 1 + homeassistant/components/baf/models.py | 1 + homeassistant/components/baf/number.py | 1 + homeassistant/components/baf/sensor.py | 1 + homeassistant/components/baf/switch.py | 1 + homeassistant/components/balboa/__init__.py | 1 + homeassistant/components/balboa/binary_sensor.py | 1 + homeassistant/components/balboa/climate.py | 1 + homeassistant/components/balboa/config_flow.py | 1 + homeassistant/components/balboa/entity.py | 1 + homeassistant/components/balboa/fan.py | 1 + homeassistant/components/balboa/light.py | 1 + homeassistant/components/bang_olufsen/__init__.py | 1 + homeassistant/components/bang_olufsen/config_flow.py | 1 + homeassistant/components/bang_olufsen/entity.py | 1 + homeassistant/components/bang_olufsen/media_player.py | 1 + homeassistant/components/bayesian/binary_sensor.py | 1 + homeassistant/components/bayesian/helpers.py | 1 + homeassistant/components/bayesian/issues.py | 1 + homeassistant/components/bbox/device_tracker.py | 1 + homeassistant/components/bbox/sensor.py | 1 + homeassistant/components/beewi_smartclim/sensor.py | 1 + homeassistant/components/binary_sensor/__init__.py | 1 + homeassistant/components/binary_sensor/device_condition.py | 1 + homeassistant/components/binary_sensor/significant_change.py | 1 + homeassistant/components/bitcoin/sensor.py | 1 + homeassistant/components/bizkaibus/sensor.py | 1 + homeassistant/components/blackbird/media_player.py | 1 + homeassistant/components/blebox/button.py | 1 + homeassistant/components/blebox/climate.py | 1 + homeassistant/components/blebox/config_flow.py | 1 + homeassistant/components/blebox/cover.py | 1 + homeassistant/components/blebox/helpers.py | 1 + homeassistant/components/blebox/light.py | 1 + homeassistant/components/blebox/switch.py | 1 + homeassistant/components/blink/__init__.py | 1 + homeassistant/components/blink/alarm_control_panel.py | 1 + homeassistant/components/blink/binary_sensor.py | 1 + homeassistant/components/blink/camera.py | 1 + homeassistant/components/blink/config_flow.py | 1 + homeassistant/components/blink/const.py | 1 + homeassistant/components/blink/coordinator.py | 1 + homeassistant/components/blink/diagnostics.py | 1 + homeassistant/components/blink/sensor.py | 1 + homeassistant/components/blink/services.py | 1 + homeassistant/components/blink/switch.py | 1 + homeassistant/components/blinksticklight/light.py | 1 + homeassistant/components/blockchain/sensor.py | 1 + homeassistant/components/bloomsky/__init__.py | 1 + homeassistant/components/bloomsky/binary_sensor.py | 1 + homeassistant/components/bloomsky/camera.py | 1 + homeassistant/components/bloomsky/sensor.py | 1 + homeassistant/components/blue_current/__init__.py | 1 + homeassistant/components/blue_current/config_flow.py | 1 + homeassistant/components/blue_current/entity.py | 1 + homeassistant/components/blue_current/sensor.py | 1 + homeassistant/components/bluemaestro/__init__.py | 1 + homeassistant/components/bluemaestro/config_flow.py | 1 + homeassistant/components/bluemaestro/device.py | 1 + homeassistant/components/bluemaestro/sensor.py | 1 + homeassistant/components/blueprint/__init__.py | 1 + homeassistant/components/blueprint/errors.py | 1 + homeassistant/components/blueprint/models.py | 1 + homeassistant/components/blueprint/schemas.py | 1 + homeassistant/components/blueprint/websocket_api.py | 1 + homeassistant/components/bluesound/media_player.py | 1 + homeassistant/components/bluetooth/__init__.py | 1 + homeassistant/components/bluetooth/active_update_coordinator.py | 1 + homeassistant/components/bluetooth/active_update_processor.py | 1 + homeassistant/components/bluetooth/api.py | 1 + homeassistant/components/bluetooth/config_flow.py | 1 + homeassistant/components/bluetooth/const.py | 1 + homeassistant/components/bluetooth/diagnostics.py | 1 + homeassistant/components/bluetooth/manager.py | 1 + homeassistant/components/bluetooth/match.py | 1 + homeassistant/components/bluetooth/models.py | 1 + homeassistant/components/bluetooth/passive_update_coordinator.py | 1 + homeassistant/components/bluetooth/passive_update_processor.py | 1 + homeassistant/components/bluetooth/storage.py | 1 + homeassistant/components/bluetooth/update_coordinator.py | 1 + homeassistant/components/bluetooth/util.py | 1 + homeassistant/components/bluetooth_adapters/__init__.py | 1 + homeassistant/components/bluetooth_le_tracker/device_tracker.py | 1 + homeassistant/components/bluetooth_tracker/const.py | 1 + homeassistant/components/bluetooth_tracker/device_tracker.py | 1 + homeassistant/components/bmw_connected_drive/__init__.py | 1 + homeassistant/components/bmw_connected_drive/binary_sensor.py | 1 + homeassistant/components/bmw_connected_drive/button.py | 1 + homeassistant/components/bmw_connected_drive/config_flow.py | 1 + homeassistant/components/bmw_connected_drive/const.py | 1 + homeassistant/components/bmw_connected_drive/coordinator.py | 1 + homeassistant/components/bmw_connected_drive/device_tracker.py | 1 + homeassistant/components/bmw_connected_drive/diagnostics.py | 1 + homeassistant/components/bmw_connected_drive/lock.py | 1 + homeassistant/components/bmw_connected_drive/notify.py | 1 + homeassistant/components/bmw_connected_drive/select.py | 1 + homeassistant/components/bmw_connected_drive/sensor.py | 1 + homeassistant/components/bond/__init__.py | 1 + homeassistant/components/bond/button.py | 1 + homeassistant/components/bond/config_flow.py | 1 + homeassistant/components/bond/cover.py | 1 + homeassistant/components/bond/diagnostics.py | 1 + homeassistant/components/bond/entity.py | 1 + homeassistant/components/bond/fan.py | 1 + homeassistant/components/bond/light.py | 1 + homeassistant/components/bond/models.py | 1 + homeassistant/components/bond/switch.py | 1 + homeassistant/components/bond/utils.py | 1 + homeassistant/components/bosch_shc/binary_sensor.py | 1 + homeassistant/components/bosch_shc/config_flow.py | 1 + homeassistant/components/bosch_shc/cover.py | 1 + homeassistant/components/bosch_shc/entity.py | 1 + homeassistant/components/bosch_shc/sensor.py | 1 + homeassistant/components/bosch_shc/switch.py | 1 + homeassistant/components/braviatv/__init__.py | 1 + homeassistant/components/braviatv/button.py | 1 + homeassistant/components/braviatv/config_flow.py | 1 + homeassistant/components/braviatv/const.py | 1 + homeassistant/components/braviatv/coordinator.py | 1 + homeassistant/components/braviatv/diagnostics.py | 1 + homeassistant/components/braviatv/entity.py | 1 + homeassistant/components/braviatv/media_player.py | 1 + homeassistant/components/braviatv/remote.py | 1 + homeassistant/components/bring/__init__.py | 1 + homeassistant/components/bring/config_flow.py | 1 + homeassistant/components/bring/coordinator.py | 1 + homeassistant/components/bring/todo.py | 1 + homeassistant/components/broadlink/__init__.py | 1 + homeassistant/components/broadlink/climate.py | 1 + homeassistant/components/broadlink/config_flow.py | 1 + homeassistant/components/broadlink/const.py | 1 + homeassistant/components/broadlink/device.py | 1 + homeassistant/components/broadlink/helpers.py | 1 + homeassistant/components/broadlink/sensor.py | 1 + homeassistant/components/broadlink/switch.py | 1 + homeassistant/components/broadlink/updater.py | 1 + homeassistant/components/brother/__init__.py | 1 + homeassistant/components/brother/config_flow.py | 1 + homeassistant/components/brother/const.py | 1 + homeassistant/components/brother/diagnostics.py | 1 + homeassistant/components/brother/sensor.py | 1 + homeassistant/components/brother/utils.py | 1 + homeassistant/components/brottsplatskartan/__init__.py | 1 + homeassistant/components/brottsplatskartan/config_flow.py | 1 + homeassistant/components/brottsplatskartan/sensor.py | 1 + homeassistant/components/brunt/__init__.py | 1 + homeassistant/components/brunt/config_flow.py | 1 + homeassistant/components/brunt/const.py | 1 + homeassistant/components/brunt/cover.py | 1 + homeassistant/components/bsblan/climate.py | 1 + homeassistant/components/bsblan/config_flow.py | 1 + homeassistant/components/bsblan/const.py | 1 + homeassistant/components/bsblan/coordinator.py | 1 + homeassistant/components/bsblan/diagnostics.py | 1 + homeassistant/components/bsblan/entity.py | 1 + homeassistant/components/bt_home_hub_5/device_tracker.py | 1 + homeassistant/components/bt_smarthub/device_tracker.py | 1 + homeassistant/components/bthome/__init__.py | 1 + homeassistant/components/bthome/binary_sensor.py | 1 + homeassistant/components/bthome/config_flow.py | 1 + homeassistant/components/bthome/const.py | 1 + homeassistant/components/bthome/coordinator.py | 1 + homeassistant/components/bthome/device.py | 1 + homeassistant/components/bthome/device_trigger.py | 1 + homeassistant/components/bthome/event.py | 1 + homeassistant/components/bthome/logbook.py | 1 + homeassistant/components/bthome/sensor.py | 1 + homeassistant/components/buienradar/__init__.py | 1 + homeassistant/components/buienradar/camera.py | 1 + homeassistant/components/buienradar/config_flow.py | 1 + homeassistant/components/buienradar/sensor.py | 1 + homeassistant/components/buienradar/util.py | 1 + homeassistant/components/button/__init__.py | 1 + homeassistant/components/button/device_action.py | 1 + homeassistant/components/button/device_trigger.py | 1 + homeassistant/components/caldav/calendar.py | 1 + homeassistant/components/caldav/todo.py | 1 + homeassistant/components/calendar/__init__.py | 1 + homeassistant/components/calendar/trigger.py | 1 + homeassistant/components/camera/__init__.py | 1 + homeassistant/components/camera/const.py | 1 + homeassistant/components/camera/img_util.py | 1 + homeassistant/components/camera/media_source.py | 1 + homeassistant/components/camera/prefs.py | 1 + homeassistant/components/camera/significant_change.py | 1 + homeassistant/components/canary/__init__.py | 1 + homeassistant/components/canary/alarm_control_panel.py | 1 + homeassistant/components/canary/camera.py | 1 + homeassistant/components/canary/config_flow.py | 1 + homeassistant/components/canary/coordinator.py | 1 + homeassistant/components/canary/model.py | 1 + homeassistant/components/canary/sensor.py | 1 + homeassistant/components/cast/__init__.py | 1 + homeassistant/components/cast/config_flow.py | 1 + homeassistant/components/cast/const.py | 1 + homeassistant/components/cast/helpers.py | 1 + homeassistant/components/cast/home_assistant_cast.py | 1 + homeassistant/components/cast/media_player.py | 1 + homeassistant/components/ccm15/__init__.py | 1 + homeassistant/components/ccm15/config_flow.py | 1 + homeassistant/components/ccm15/diagnostics.py | 1 + homeassistant/components/cert_expiry/__init__.py | 1 + homeassistant/components/cert_expiry/config_flow.py | 1 + homeassistant/components/cert_expiry/coordinator.py | 1 + homeassistant/components/cert_expiry/errors.py | 1 + homeassistant/components/cert_expiry/sensor.py | 1 + homeassistant/components/channels/media_player.py | 1 + homeassistant/components/circuit/notify.py | 1 + homeassistant/components/cisco_ios/device_tracker.py | 1 + .../components/cisco_mobility_express/device_tracker.py | 1 + homeassistant/components/cisco_webex_teams/notify.py | 1 + homeassistant/components/citybikes/sensor.py | 1 + homeassistant/components/clementine/media_player.py | 1 + homeassistant/components/clickatell/notify.py | 1 + homeassistant/components/clicksend/notify.py | 1 + homeassistant/components/clicksend_tts/notify.py | 1 + homeassistant/components/climate/__init__.py | 1 + homeassistant/components/climate/device_action.py | 1 + homeassistant/components/climate/device_condition.py | 1 + homeassistant/components/climate/device_trigger.py | 1 + homeassistant/components/climate/reproduce_state.py | 1 + homeassistant/components/climate/significant_change.py | 1 + homeassistant/components/cloud/__init__.py | 1 + homeassistant/components/cloud/account_link.py | 1 + homeassistant/components/cloud/alexa_config.py | 1 + homeassistant/components/cloud/binary_sensor.py | 1 + homeassistant/components/cloud/client.py | 1 + homeassistant/components/cloud/config_flow.py | 1 + homeassistant/components/cloud/const.py | 1 + homeassistant/components/cloud/google_config.py | 1 + homeassistant/components/cloud/http_api.py | 1 + homeassistant/components/cloud/prefs.py | 1 + homeassistant/components/cloud/repairs.py | 1 + homeassistant/components/cloud/stt.py | 1 + homeassistant/components/cloud/subscription.py | 1 + homeassistant/components/cloud/system_health.py | 1 + homeassistant/components/cloud/tts.py | 1 + homeassistant/components/cloudflare/__init__.py | 1 + homeassistant/components/cloudflare/config_flow.py | 1 + homeassistant/components/cmus/media_player.py | 1 + homeassistant/components/co2signal/__init__.py | 1 + homeassistant/components/co2signal/config_flow.py | 1 + homeassistant/components/co2signal/coordinator.py | 1 + homeassistant/components/co2signal/diagnostics.py | 1 + homeassistant/components/co2signal/helpers.py | 1 + homeassistant/components/co2signal/sensor.py | 1 + homeassistant/components/co2signal/util.py | 1 + homeassistant/components/coinbase/__init__.py | 1 + homeassistant/components/coinbase/config_flow.py | 1 + homeassistant/components/coinbase/sensor.py | 1 + homeassistant/components/color_extractor/config_flow.py | 1 + homeassistant/components/comed_hourly_pricing/sensor.py | 1 + homeassistant/components/comelit/alarm_control_panel.py | 1 + homeassistant/components/comelit/climate.py | 1 + homeassistant/components/comelit/config_flow.py | 1 + homeassistant/components/comelit/coordinator.py | 1 + homeassistant/components/comelit/cover.py | 1 + homeassistant/components/comelit/humidifier.py | 1 + homeassistant/components/comelit/light.py | 1 + homeassistant/components/comelit/sensor.py | 1 + homeassistant/components/comelit/switch.py | 1 + homeassistant/components/comfoconnect/fan.py | 1 + homeassistant/components/comfoconnect/sensor.py | 1 + homeassistant/components/command_line/__init__.py | 1 + homeassistant/components/command_line/binary_sensor.py | 1 + homeassistant/components/command_line/cover.py | 1 + homeassistant/components/command_line/notify.py | 1 + homeassistant/components/command_line/sensor.py | 1 + homeassistant/components/command_line/switch.py | 1 + homeassistant/components/command_line/utils.py | 1 + homeassistant/components/compensation/sensor.py | 1 + homeassistant/components/concord232/alarm_control_panel.py | 1 + homeassistant/components/concord232/binary_sensor.py | 1 + homeassistant/components/config/__init__.py | 1 + homeassistant/components/config/area_registry.py | 1 + homeassistant/components/config/auth.py | 1 + homeassistant/components/config/auth_provider_homeassistant.py | 1 + homeassistant/components/config/automation.py | 1 + homeassistant/components/config/config_entries.py | 1 + homeassistant/components/config/core.py | 1 + homeassistant/components/config/device_registry.py | 1 + homeassistant/components/config/entity_registry.py | 1 + homeassistant/components/config/floor_registry.py | 1 + homeassistant/components/config/label_registry.py | 1 + homeassistant/components/config/scene.py | 1 + homeassistant/components/config/script.py | 1 + homeassistant/components/config/view.py | 1 + homeassistant/components/configurator/__init__.py | 1 + homeassistant/components/control4/__init__.py | 1 + homeassistant/components/control4/config_flow.py | 1 + homeassistant/components/control4/director_utils.py | 1 + homeassistant/components/control4/light.py | 1 + homeassistant/components/conversation/__init__.py | 1 + homeassistant/components/conversation/agent.py | 1 + homeassistant/components/conversation/trigger.py | 1 + homeassistant/components/conversation/util.py | 1 + homeassistant/components/coolmaster/__init__.py | 1 + homeassistant/components/coolmaster/binary_sensor.py | 1 + homeassistant/components/coolmaster/button.py | 1 + homeassistant/components/coolmaster/climate.py | 1 + homeassistant/components/coolmaster/config_flow.py | 1 + homeassistant/components/coolmaster/entity.py | 1 + homeassistant/components/coolmaster/sensor.py | 1 + homeassistant/components/counter/__init__.py | 1 + homeassistant/components/counter/reproduce_state.py | 1 + homeassistant/components/cover/__init__.py | 1 + homeassistant/components/cover/device_action.py | 1 + homeassistant/components/cover/device_condition.py | 1 + homeassistant/components/cover/device_trigger.py | 1 + homeassistant/components/cover/reproduce_state.py | 1 + homeassistant/components/cover/significant_change.py | 1 + homeassistant/components/cppm_tracker/device_tracker.py | 1 + homeassistant/components/cpuspeed/__init__.py | 1 + homeassistant/components/cpuspeed/config_flow.py | 1 + homeassistant/components/cpuspeed/diagnostics.py | 1 + homeassistant/components/cpuspeed/sensor.py | 1 + homeassistant/components/crownstone/__init__.py | 1 + homeassistant/components/crownstone/config_flow.py | 1 + homeassistant/components/crownstone/const.py | 1 + homeassistant/components/crownstone/devices.py | 1 + homeassistant/components/crownstone/entry_manager.py | 1 + homeassistant/components/crownstone/helpers.py | 1 + homeassistant/components/crownstone/light.py | 1 + homeassistant/components/crownstone/listeners.py | 1 + homeassistant/components/cups/sensor.py | 1 + homeassistant/components/currencylayer/sensor.py | 1 + 655 files changed, 655 insertions(+) diff --git a/homeassistant/components/abode/__init__.py b/homeassistant/components/abode/__init__.py index 55ce9e054c3..a27c2d93ead 100644 --- a/homeassistant/components/abode/__init__.py +++ b/homeassistant/components/abode/__init__.py @@ -1,4 +1,5 @@ """Support for the Abode Security System.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index 4671b71059d..d22fb86537a 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Abode Security System alarm control panels.""" + from __future__ import annotations from jaraco.abode.devices.alarm import Alarm as AbodeAl diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py index a10dbc8e664..d19b17219a2 100644 --- a/homeassistant/components/abode/binary_sensor.py +++ b/homeassistant/components/abode/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Abode Security System binary sensors.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index 326e845b16b..4ccd0725df6 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -1,4 +1,5 @@ """Support for Abode Security System cameras.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/abode/config_flow.py b/homeassistant/components/abode/config_flow.py index 21d8872088f..57cad604274 100644 --- a/homeassistant/components/abode/config_flow.py +++ b/homeassistant/components/abode/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Abode Security System component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/abode/cover.py b/homeassistant/components/abode/cover.py index d504040ee90..511435e50c3 100644 --- a/homeassistant/components/abode/cover.py +++ b/homeassistant/components/abode/cover.py @@ -1,4 +1,5 @@ """Support for Abode Security System covers.""" + from typing import Any from jaraco.abode.devices.cover import Cover as AbodeCV diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py index 539b89a5546..f3df1810bcd 100644 --- a/homeassistant/components/abode/light.py +++ b/homeassistant/components/abode/light.py @@ -1,4 +1,5 @@ """Support for Abode Security System lights.""" + from __future__ import annotations from math import ceil diff --git a/homeassistant/components/abode/lock.py b/homeassistant/components/abode/lock.py index c110b3fd558..6458c9abf29 100644 --- a/homeassistant/components/abode/lock.py +++ b/homeassistant/components/abode/lock.py @@ -1,4 +1,5 @@ """Support for the Abode Security System locks.""" + from typing import Any from jaraco.abode.devices.lock import Lock as AbodeLK diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 873953952bd..405f3db5a67 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -1,4 +1,5 @@ """Support for Abode Security System sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py index 8443a16ef8f..3d33632e19a 100644 --- a/homeassistant/components/abode/switch.py +++ b/homeassistant/components/abode/switch.py @@ -1,4 +1,5 @@ """Support for Abode Security System switches.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index dfbf5119981..1f7e72681e6 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -1,4 +1,5 @@ """The AccuWeather component.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/accuweather/config_flow.py b/homeassistant/components/accuweather/config_flow.py index fe9565a9e2e..666171af8df 100644 --- a/homeassistant/components/accuweather/config_flow.py +++ b/homeassistant/components/accuweather/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for AccuWeather.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/accuweather/const.py b/homeassistant/components/accuweather/const.py index 2e18977d112..31925172d1c 100644 --- a/homeassistant/components/accuweather/const.py +++ b/homeassistant/components/accuweather/const.py @@ -1,4 +1,5 @@ """Constants for AccuWeather integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/accuweather/diagnostics.py b/homeassistant/components/accuweather/diagnostics.py index f307c6b5335..e7bc41eaaf2 100644 --- a/homeassistant/components/accuweather/diagnostics.py +++ b/homeassistant/components/accuweather/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for AccuWeather.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 0ac0e0d9296..521dfdfbead 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -1,4 +1,5 @@ """Support for the AccuWeather service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/accuweather/system_health.py b/homeassistant/components/accuweather/system_health.py index df1e607d15d..607a557f333 100644 --- a/homeassistant/components/accuweather/system_health.py +++ b/homeassistant/components/accuweather/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/accuweather/weather.py b/homeassistant/components/accuweather/weather.py index d446b4b58d9..a734847fa18 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -1,4 +1,5 @@ """Support for the AccuWeather service.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/acer_projector/const.py b/homeassistant/components/acer_projector/const.py index 98864ab957f..95e32dc97d4 100644 --- a/homeassistant/components/acer_projector/const.py +++ b/homeassistant/components/acer_projector/const.py @@ -1,4 +1,5 @@ """Use serial protocol of Acer projector to obtain state of the projector.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index 75e3d6081ba..b29bbf9fa3f 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -1,4 +1,5 @@ """Use serial protocol of Acer projector to obtain state of the projector.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/acmeda/__init__.py b/homeassistant/components/acmeda/__init__.py index 0bb7cbdc177..b4a0f237522 100644 --- a/homeassistant/components/acmeda/__init__.py +++ b/homeassistant/components/acmeda/__init__.py @@ -1,4 +1,5 @@ """The Rollease Acmeda Automate integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/acmeda/base.py b/homeassistant/components/acmeda/base.py index 5d1f643418a..7596374684d 100644 --- a/homeassistant/components/acmeda/base.py +++ b/homeassistant/components/acmeda/base.py @@ -1,4 +1,5 @@ """Base class for Acmeda Roller Blinds.""" + from __future__ import annotations import aiopulse diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index f705e78d483..c10f2c1b105 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rollease Acmeda Automate Pulse Hub.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/acmeda/cover.py b/homeassistant/components/acmeda/cover.py index 32b6cf31ee5..f8116221668 100644 --- a/homeassistant/components/acmeda/cover.py +++ b/homeassistant/components/acmeda/cover.py @@ -1,4 +1,5 @@ """Support for Acmeda Roller Blinds.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/acmeda/errors.py b/homeassistant/components/acmeda/errors.py index f26090df03d..2e54d86b353 100644 --- a/homeassistant/components/acmeda/errors.py +++ b/homeassistant/components/acmeda/errors.py @@ -1,4 +1,5 @@ """Errors for the Acmeda Pulse component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/acmeda/helpers.py b/homeassistant/components/acmeda/helpers.py index a87cbcd1635..9e48124208a 100644 --- a/homeassistant/components/acmeda/helpers.py +++ b/homeassistant/components/acmeda/helpers.py @@ -1,4 +1,5 @@ """Helper functions for Acmeda Pulse.""" + from __future__ import annotations from aiopulse import Roller diff --git a/homeassistant/components/acmeda/hub.py b/homeassistant/components/acmeda/hub.py index 9c6ef6156f0..a5daf27f445 100644 --- a/homeassistant/components/acmeda/hub.py +++ b/homeassistant/components/acmeda/hub.py @@ -1,4 +1,5 @@ """Code to handle a Pulse Hub.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/acmeda/sensor.py b/homeassistant/components/acmeda/sensor.py index 20d0929f341..0b458a8c32a 100644 --- a/homeassistant/components/acmeda/sensor.py +++ b/homeassistant/components/acmeda/sensor.py @@ -1,4 +1,5 @@ """Support for Acmeda Roller Blind Batteries.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/actiontec/const.py b/homeassistant/components/actiontec/const.py index de309b68476..af28b0abb41 100644 --- a/homeassistant/components/actiontec/const.py +++ b/homeassistant/components/actiontec/const.py @@ -1,4 +1,5 @@ """Support for Actiontec MI424WR (Verizon FIOS) routers.""" + from __future__ import annotations import re diff --git a/homeassistant/components/actiontec/device_tracker.py b/homeassistant/components/actiontec/device_tracker.py index 40ff869c43b..2afa772421c 100644 --- a/homeassistant/components/actiontec/device_tracker.py +++ b/homeassistant/components/actiontec/device_tracker.py @@ -1,4 +1,5 @@ """Support for Actiontec MI424WR (Verizon FIOS) routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/actiontec/model.py b/homeassistant/components/actiontec/model.py index ff28d6d4ac6..ea313529131 100644 --- a/homeassistant/components/actiontec/model.py +++ b/homeassistant/components/actiontec/model.py @@ -1,4 +1,5 @@ """Model definitions for Actiontec MI424WR (Verizon FIOS) routers.""" + from dataclasses import dataclass diff --git a/homeassistant/components/adax/__init__.py b/homeassistant/components/adax/__init__.py index cf60d40631c..d4fe13ee4f6 100644 --- a/homeassistant/components/adax/__init__.py +++ b/homeassistant/components/adax/__init__.py @@ -1,4 +1,5 @@ """The Adax integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/adax/climate.py b/homeassistant/components/adax/climate.py index 6b0adcb52cf..69b89cfe8cc 100644 --- a/homeassistant/components/adax/climate.py +++ b/homeassistant/components/adax/climate.py @@ -1,4 +1,5 @@ """Support for Adax wifi-enabled home heaters.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/adax/config_flow.py b/homeassistant/components/adax/config_flow.py index 6defde6f508..3e8ca646cad 100644 --- a/homeassistant/components/adax/config_flow.py +++ b/homeassistant/components/adax/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Adax integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/adax/const.py b/homeassistant/components/adax/const.py index 86c627aa130..306dd52e657 100644 --- a/homeassistant/components/adax/const.py +++ b/homeassistant/components/adax/const.py @@ -1,4 +1,5 @@ """Constants for the Adax integration.""" + from typing import Final ACCOUNT_ID: Final = "account_id" diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index fbcbea61316..b3cbb3300bf 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -1,4 +1,5 @@ """Support for AdGuard Home.""" + from __future__ import annotations from adguardhome import AdGuardHome, AdGuardHomeConnectionError diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index cf88674a815..c07967ec2c5 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the AdGuard Home integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/adguard/entity.py b/homeassistant/components/adguard/entity.py index 909acd89b80..74f69442f81 100644 --- a/homeassistant/components/adguard/entity.py +++ b/homeassistant/components/adguard/entity.py @@ -1,4 +1,5 @@ """AdGuard Home base entity.""" + from __future__ import annotations from adguardhome import AdGuardHome, AdGuardHomeError diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index e1cec6c4d3b..1e95a07bffa 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -1,4 +1,5 @@ """Support for AdGuard Home sensors.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/adguard/switch.py b/homeassistant/components/adguard/switch.py index 0aa88aa3ffd..ae4bee85d23 100644 --- a/homeassistant/components/adguard/switch.py +++ b/homeassistant/components/adguard/switch.py @@ -1,4 +1,5 @@ """Support for AdGuard Home switches.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index b20ef010f1f..2da76382c51 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -1,4 +1,5 @@ """Support for ADS binary sensors.""" + from __future__ import annotations import pyads diff --git a/homeassistant/components/ads/cover.py b/homeassistant/components/ads/cover.py index a2fb1888cd3..c54b3e14267 100644 --- a/homeassistant/components/ads/cover.py +++ b/homeassistant/components/ads/cover.py @@ -1,4 +1,5 @@ """Support for ADS covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index 8dd55775b7a..13ce9ec261c 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -1,4 +1,5 @@ """Support for ADS light sources.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ads/sensor.py b/homeassistant/components/ads/sensor.py index 17aede2bd2b..4bcc8f776df 100644 --- a/homeassistant/components/ads/sensor.py +++ b/homeassistant/components/ads/sensor.py @@ -1,4 +1,5 @@ """Support for ADS sensors.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py index 3f597fb9f5c..a793a5996cf 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -1,4 +1,5 @@ """Support for ADS switch platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/advantage_air/__init__.py b/homeassistant/components/advantage_air/__init__.py index 0ef2c0eada5..c89d6f609b8 100644 --- a/homeassistant/components/advantage_air/__init__.py +++ b/homeassistant/components/advantage_air/__init__.py @@ -1,4 +1,5 @@ """Advantage Air climate integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/advantage_air/binary_sensor.py b/homeassistant/components/advantage_air/binary_sensor.py index 74a276dc67b..cf813a429e5 100644 --- a/homeassistant/components/advantage_air/binary_sensor.py +++ b/homeassistant/components/advantage_air/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor platform for Advantage Air integration.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/advantage_air/climate.py b/homeassistant/components/advantage_air/climate.py index 6abd0b18fd4..b20171fa603 100644 --- a/homeassistant/components/advantage_air/climate.py +++ b/homeassistant/components/advantage_air/climate.py @@ -1,4 +1,5 @@ """Climate platform for Advantage Air integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/advantage_air/config_flow.py b/homeassistant/components/advantage_air/config_flow.py index e80e884e4d9..4a4e18301bf 100644 --- a/homeassistant/components/advantage_air/config_flow.py +++ b/homeassistant/components/advantage_air/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for Advantage Air integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/advantage_air/cover.py b/homeassistant/components/advantage_air/cover.py index afb38dee931..3c6e3ffa3a6 100644 --- a/homeassistant/components/advantage_air/cover.py +++ b/homeassistant/components/advantage_air/cover.py @@ -1,4 +1,5 @@ """Cover platform for Advantage Air integration.""" + from typing import Any from homeassistant.components.cover import ( diff --git a/homeassistant/components/advantage_air/diagnostics.py b/homeassistant/components/advantage_air/diagnostics.py index 4c440610838..9eebb97d3c5 100644 --- a/homeassistant/components/advantage_air/diagnostics.py +++ b/homeassistant/components/advantage_air/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Advantage Air.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/advantage_air/entity.py b/homeassistant/components/advantage_air/entity.py index 9079e69ae09..be2135e4767 100644 --- a/homeassistant/components/advantage_air/entity.py +++ b/homeassistant/components/advantage_air/entity.py @@ -1,4 +1,5 @@ """Advantage Air parent entity class.""" + from typing import Any from advantage_air import ApiError diff --git a/homeassistant/components/advantage_air/light.py b/homeassistant/components/advantage_air/light.py index 47c8c7c1768..30617c52acf 100644 --- a/homeassistant/components/advantage_air/light.py +++ b/homeassistant/components/advantage_air/light.py @@ -1,4 +1,5 @@ """Light platform for Advantage Air integration.""" + from typing import Any from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity diff --git a/homeassistant/components/advantage_air/models.py b/homeassistant/components/advantage_air/models.py index f56b3f8823b..77135644d11 100644 --- a/homeassistant/components/advantage_air/models.py +++ b/homeassistant/components/advantage_air/models.py @@ -1,4 +1,5 @@ """The Advantage Air integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/advantage_air/sensor.py b/homeassistant/components/advantage_air/sensor.py index 4af028e6db0..6bfa6bbad4b 100644 --- a/homeassistant/components/advantage_air/sensor.py +++ b/homeassistant/components/advantage_air/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Advantage Air integration.""" + from __future__ import annotations from decimal import Decimal diff --git a/homeassistant/components/advantage_air/switch.py b/homeassistant/components/advantage_air/switch.py index abc9b795d43..05cba606bf7 100644 --- a/homeassistant/components/advantage_air/switch.py +++ b/homeassistant/components/advantage_air/switch.py @@ -1,4 +1,5 @@ """Switch platform for Advantage Air integration.""" + from typing import Any from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity diff --git a/homeassistant/components/aemet/config_flow.py b/homeassistant/components/aemet/config_flow.py index a402eb290b8..6b2eca3f5c9 100644 --- a/homeassistant/components/aemet/config_flow.py +++ b/homeassistant/components/aemet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for AEMET OpenData.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aemet/const.py b/homeassistant/components/aemet/const.py index 9623766b64c..6734ea3d1f1 100644 --- a/homeassistant/components/aemet/const.py +++ b/homeassistant/components/aemet/const.py @@ -1,4 +1,5 @@ """Constant values for the AEMET OpenData component.""" + from __future__ import annotations from aemet_opendata.const import ( diff --git a/homeassistant/components/aemet/coordinator.py b/homeassistant/components/aemet/coordinator.py index 04810077f28..8d179ccdb02 100644 --- a/homeassistant/components/aemet/coordinator.py +++ b/homeassistant/components/aemet/coordinator.py @@ -1,4 +1,5 @@ """Weather data coordinator for the AEMET OpenData service.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/aemet/diagnostics.py b/homeassistant/components/aemet/diagnostics.py index f49170d9576..20b6c208514 100644 --- a/homeassistant/components/aemet/diagnostics.py +++ b/homeassistant/components/aemet/diagnostics.py @@ -1,4 +1,5 @@ """Support for the AEMET OpenData diagnostics.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aemet/entity.py b/homeassistant/components/aemet/entity.py index b83c0c98807..ba3f7e56193 100644 --- a/homeassistant/components/aemet/entity.py +++ b/homeassistant/components/aemet/entity.py @@ -1,4 +1,5 @@ """Entity classes for the AEMET OpenData integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index 75f7f5c0f97..2d93d43698b 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -1,4 +1,5 @@ """Support for the AEMET OpenData service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/aftership/__init__.py b/homeassistant/components/aftership/__init__.py index 66610e6e01b..b079079db08 100644 --- a/homeassistant/components/aftership/__init__.py +++ b/homeassistant/components/aftership/__init__.py @@ -1,4 +1,5 @@ """The AfterShip integration.""" + from __future__ import annotations from pyaftership import AfterShip, AfterShipException diff --git a/homeassistant/components/aftership/config_flow.py b/homeassistant/components/aftership/config_flow.py index f48221840ba..99de28b2fc2 100644 --- a/homeassistant/components/aftership/config_flow.py +++ b/homeassistant/components/aftership/config_flow.py @@ -1,4 +1,5 @@ """Config flow for AfterShip integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aftership/const.py b/homeassistant/components/aftership/const.py index dda5fb7e426..385570e145f 100644 --- a/homeassistant/components/aftership/const.py +++ b/homeassistant/components/aftership/const.py @@ -1,4 +1,5 @@ """Constants for the Aftership integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/aftership/sensor.py b/homeassistant/components/aftership/sensor.py index f96281fce8f..c403c4a571d 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -1,4 +1,5 @@ """Support for non-delivered packages recorded in AfterShip.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/agent_dvr/__init__.py b/homeassistant/components/agent_dvr/__init__.py index 6723d62e9e0..6dc83d3766d 100644 --- a/homeassistant/components/agent_dvr/__init__.py +++ b/homeassistant/components/agent_dvr/__init__.py @@ -1,4 +1,5 @@ """Support for Agent.""" + from agent import AgentError from agent.a import Agent diff --git a/homeassistant/components/agent_dvr/alarm_control_panel.py b/homeassistant/components/agent_dvr/alarm_control_panel.py index 9e5586b21f4..8dae49aa0ea 100644 --- a/homeassistant/components/agent_dvr/alarm_control_panel.py +++ b/homeassistant/components/agent_dvr/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Agent DVR Alarm Control Panels.""" + from __future__ import annotations from homeassistant.components.alarm_control_panel import ( diff --git a/homeassistant/components/agent_dvr/camera.py b/homeassistant/components/agent_dvr/camera.py index cf171987fcb..e2012ee13ca 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -1,4 +1,5 @@ """Support for Agent camera streaming.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/agent_dvr/config_flow.py b/homeassistant/components/agent_dvr/config_flow.py index 065209e8199..ac2ff46d9ef 100644 --- a/homeassistant/components/agent_dvr/config_flow.py +++ b/homeassistant/components/agent_dvr/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Agent devices.""" + from contextlib import suppress from typing import Any diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index c2992cc804b..b7104d46152 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -1,4 +1,5 @@ """Component for handling Air Quality data for your location.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/airly/__init__.py b/homeassistant/components/airly/__init__.py index 91208de519b..651caee272c 100644 --- a/homeassistant/components/airly/__init__.py +++ b/homeassistant/components/airly/__init__.py @@ -1,4 +1,5 @@ """The Airly integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/airly/config_flow.py b/homeassistant/components/airly/config_flow.py index 3fd27d76c60..2811156ac90 100644 --- a/homeassistant/components/airly/config_flow.py +++ b/homeassistant/components/airly/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Airly.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/airly/const.py b/homeassistant/components/airly/const.py index 76260699dbd..5939bfa62de 100644 --- a/homeassistant/components/airly/const.py +++ b/homeassistant/components/airly/const.py @@ -1,4 +1,5 @@ """Constants for Airly integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/airly/coordinator.py b/homeassistant/components/airly/coordinator.py index 9f2a1c96511..6db50950ba1 100644 --- a/homeassistant/components/airly/coordinator.py +++ b/homeassistant/components/airly/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Airly integration.""" + from asyncio import timeout from datetime import timedelta import logging diff --git a/homeassistant/components/airly/diagnostics.py b/homeassistant/components/airly/diagnostics.py index bb270e6a664..1d63fbc8277 100644 --- a/homeassistant/components/airly/diagnostics.py +++ b/homeassistant/components/airly/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Airly.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index f91a242b8d5..7bb759609f4 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -1,4 +1,5 @@ """Support for the Airly sensor service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/airly/system_health.py b/homeassistant/components/airly/system_health.py index b1f6bc36c91..6e56b15ef92 100644 --- a/homeassistant/components/airly/system_health.py +++ b/homeassistant/components/airly/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airnow/coordinator.py b/homeassistant/components/airnow/coordinator.py index 4bdaadff0da..32185080d25 100644 --- a/homeassistant/components/airnow/coordinator.py +++ b/homeassistant/components/airnow/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the AirNow integration.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/airnow/diagnostics.py b/homeassistant/components/airnow/diagnostics.py index 284fd65013b..39db915bef9 100644 --- a/homeassistant/components/airnow/diagnostics.py +++ b/homeassistant/components/airnow/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for AirNow.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airnow/sensor.py b/homeassistant/components/airnow/sensor.py index 258a0c680a7..1289b6c2b16 100644 --- a/homeassistant/components/airnow/sensor.py +++ b/homeassistant/components/airnow/sensor.py @@ -1,4 +1,5 @@ """Support for the AirNow sensor service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/airq/__init__.py b/homeassistant/components/airq/__init__.py index 06d7ba30749..dc35cd6ae87 100644 --- a/homeassistant/components/airq/__init__.py +++ b/homeassistant/components/airq/__init__.py @@ -1,4 +1,5 @@ """The air-Q integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/airq/config_flow.py b/homeassistant/components/airq/config_flow.py index b260c372efc..9e51552a309 100644 --- a/homeassistant/components/airq/config_flow.py +++ b/homeassistant/components/airq/config_flow.py @@ -1,4 +1,5 @@ """Config flow for air-Q integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airq/const.py b/homeassistant/components/airq/const.py index d1a2340b4bc..845fa7f1de8 100644 --- a/homeassistant/components/airq/const.py +++ b/homeassistant/components/airq/const.py @@ -1,4 +1,5 @@ """Constants for the air-Q integration.""" + from typing import Final DOMAIN: Final = "airq" diff --git a/homeassistant/components/airq/coordinator.py b/homeassistant/components/airq/coordinator.py index 6f49303bc6c..b03ce36d776 100644 --- a/homeassistant/components/airq/coordinator.py +++ b/homeassistant/components/airq/coordinator.py @@ -1,4 +1,5 @@ """The air-Q integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/airq/sensor.py b/homeassistant/components/airq/sensor.py index 660487fef53..e3ef6504731 100644 --- a/homeassistant/components/airq/sensor.py +++ b/homeassistant/components/airq/sensor.py @@ -1,4 +1,5 @@ """Definition of air-Q sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/airthings/__init__.py b/homeassistant/components/airthings/__init__.py index a5b962d1bf7..bc12f19a33d 100644 --- a/homeassistant/components/airthings/__init__.py +++ b/homeassistant/components/airthings/__init__.py @@ -1,4 +1,5 @@ """The Airthings integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/airthings/config_flow.py b/homeassistant/components/airthings/config_flow.py index e5c800dfb55..eae7d35c62b 100644 --- a/homeassistant/components/airthings/config_flow.py +++ b/homeassistant/components/airthings/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Airthings integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airthings/sensor.py b/homeassistant/components/airthings/sensor.py index 9d772d11996..6787326be8c 100644 --- a/homeassistant/components/airthings/sensor.py +++ b/homeassistant/components/airthings/sensor.py @@ -1,4 +1,5 @@ """Support for Airthings sensors.""" + from __future__ import annotations from airthings import AirthingsDevice diff --git a/homeassistant/components/airthings_ble/__init__.py b/homeassistant/components/airthings_ble/__init__.py index 3a97813741b..dd05c58ed81 100644 --- a/homeassistant/components/airthings_ble/__init__.py +++ b/homeassistant/components/airthings_ble/__init__.py @@ -1,4 +1,5 @@ """The Airthings BLE integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index 39c55e0b465..941ea1d6b45 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -1,4 +1,5 @@ """Support for airthings ble sensors.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/airtouch4/__init__.py b/homeassistant/components/airtouch4/__init__.py index dc5172096a7..5f63fe023dc 100644 --- a/homeassistant/components/airtouch4/__init__.py +++ b/homeassistant/components/airtouch4/__init__.py @@ -1,4 +1,5 @@ """The AirTouch4 integration.""" + from airtouch4pyapi import AirTouch from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/airtouch4/climate.py b/homeassistant/components/airtouch4/climate.py index 89afddad76e..3fdace0f553 100644 --- a/homeassistant/components/airtouch4/climate.py +++ b/homeassistant/components/airtouch4/climate.py @@ -1,4 +1,5 @@ """AirTouch 4 component to control of AirTouch 4 Climate Devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airtouch4/config_flow.py b/homeassistant/components/airtouch4/config_flow.py index 18050308cc0..12e01ffde29 100644 --- a/homeassistant/components/airtouch4/config_flow.py +++ b/homeassistant/components/airtouch4/config_flow.py @@ -1,4 +1,5 @@ """Config flow for AirTouch4.""" + from airtouch4pyapi import AirTouch, AirTouchStatus import voluptuous as vol diff --git a/homeassistant/components/airtouch5/__init__.py b/homeassistant/components/airtouch5/__init__.py index 6ec32eaa021..8518d8a442e 100644 --- a/homeassistant/components/airtouch5/__init__.py +++ b/homeassistant/components/airtouch5/__init__.py @@ -1,4 +1,5 @@ """The Airtouch 5 integration.""" + from __future__ import annotations from airtouch5py.airtouch5_simple_client import Airtouch5SimpleClient diff --git a/homeassistant/components/airtouch5/config_flow.py b/homeassistant/components/airtouch5/config_flow.py index 4f3b69de42c..65755350b47 100644 --- a/homeassistant/components/airtouch5/config_flow.py +++ b/homeassistant/components/airtouch5/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Airtouch 5 integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airtouch5/entity.py b/homeassistant/components/airtouch5/entity.py index a6ac76b5187..e5899850e0f 100644 --- a/homeassistant/components/airtouch5/entity.py +++ b/homeassistant/components/airtouch5/entity.py @@ -1,4 +1,5 @@ """Base class for Airtouch5 entities.""" + from airtouch5py.airtouch5_client import Airtouch5ConnectionStateChange from airtouch5py.airtouch5_simple_client import Airtouch5SimpleClient diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 42cc1e1fade..ec3e0ad7028 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -1,4 +1,5 @@ """The AirVisual component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index 6cea9499314..2d7a0d8886e 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -1,4 +1,5 @@ """Define a config flow manager for AirVisual.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/airvisual/diagnostics.py b/homeassistant/components/airvisual/diagnostics.py index 05e716367bb..348bb249b0f 100644 --- a/homeassistant/components/airvisual/diagnostics.py +++ b/homeassistant/components/airvisual/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for AirVisual.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 69835188750..df0e3da1f45 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -1,4 +1,5 @@ """Support for AirVisual air quality sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/airvisual_pro/__init__.py b/homeassistant/components/airvisual_pro/__init__.py index 3e53fc15b4f..74c90b9ed02 100644 --- a/homeassistant/components/airvisual_pro/__init__.py +++ b/homeassistant/components/airvisual_pro/__init__.py @@ -1,4 +1,5 @@ """The AirVisual Pro integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/airvisual_pro/config_flow.py b/homeassistant/components/airvisual_pro/config_flow.py index af362c7318e..97265b33913 100644 --- a/homeassistant/components/airvisual_pro/config_flow.py +++ b/homeassistant/components/airvisual_pro/config_flow.py @@ -1,4 +1,5 @@ """Define a config flow manager for AirVisual Pro.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/airvisual_pro/diagnostics.py b/homeassistant/components/airvisual_pro/diagnostics.py index d6e60207214..9fea6e59c1d 100644 --- a/homeassistant/components/airvisual_pro/diagnostics.py +++ b/homeassistant/components/airvisual_pro/diagnostics.py @@ -1,4 +1,5 @@ """Support for AirVisual Pro diagnostics.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airvisual_pro/sensor.py b/homeassistant/components/airvisual_pro/sensor.py index 2708cc5857d..d53def57959 100644 --- a/homeassistant/components/airvisual_pro/sensor.py +++ b/homeassistant/components/airvisual_pro/sensor.py @@ -1,4 +1,5 @@ """Support for AirVisual Pro sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/airzone/__init__.py b/homeassistant/components/airzone/__init__.py index 1a54be0ac41..1a65b92c3f4 100644 --- a/homeassistant/components/airzone/__init__.py +++ b/homeassistant/components/airzone/__init__.py @@ -1,4 +1,5 @@ """The Airzone integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airzone/binary_sensor.py b/homeassistant/components/airzone/binary_sensor.py index 488c2c96132..374507aeb98 100644 --- a/homeassistant/components/airzone/binary_sensor.py +++ b/homeassistant/components/airzone/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the Airzone sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/airzone/climate.py b/homeassistant/components/airzone/climate.py index 2b4cae18086..f5b42c4ccbd 100644 --- a/homeassistant/components/airzone/climate.py +++ b/homeassistant/components/airzone/climate.py @@ -1,4 +1,5 @@ """Support for the Airzone climate.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/airzone/config_flow.py b/homeassistant/components/airzone/config_flow.py index 02a7a3f378a..24ee37bbcb4 100644 --- a/homeassistant/components/airzone/config_flow.py +++ b/homeassistant/components/airzone/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Airzone.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airzone/coordinator.py b/homeassistant/components/airzone/coordinator.py index 6053c587550..8ec2cbe07ca 100644 --- a/homeassistant/components/airzone/coordinator.py +++ b/homeassistant/components/airzone/coordinator.py @@ -1,4 +1,5 @@ """The Airzone integration.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/airzone/diagnostics.py b/homeassistant/components/airzone/diagnostics.py index f56a5106b25..8c75302d692 100644 --- a/homeassistant/components/airzone/diagnostics.py +++ b/homeassistant/components/airzone/diagnostics.py @@ -1,4 +1,5 @@ """Support for the Airzone diagnostics.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airzone/entity.py b/homeassistant/components/airzone/entity.py index 2c3dba472ef..7d2c883f484 100644 --- a/homeassistant/components/airzone/entity.py +++ b/homeassistant/components/airzone/entity.py @@ -1,4 +1,5 @@ """Entity classes for the Airzone integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/airzone/select.py b/homeassistant/components/airzone/select.py index 5d60b777f2e..fd3dec551dd 100644 --- a/homeassistant/components/airzone/select.py +++ b/homeassistant/components/airzone/select.py @@ -1,4 +1,5 @@ """Support for the Airzone sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/airzone/sensor.py b/homeassistant/components/airzone/sensor.py index c14eaf48ff1..b4e0c7aa818 100644 --- a/homeassistant/components/airzone/sensor.py +++ b/homeassistant/components/airzone/sensor.py @@ -1,4 +1,5 @@ """Support for the Airzone sensors.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/airzone/water_heater.py b/homeassistant/components/airzone/water_heater.py index 58164edf3e9..4e502776185 100644 --- a/homeassistant/components/airzone/water_heater.py +++ b/homeassistant/components/airzone/water_heater.py @@ -1,4 +1,5 @@ """Support for the Airzone water heater.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/airzone_cloud/__init__.py b/homeassistant/components/airzone_cloud/__init__.py index 697b80942f2..83be481a4de 100644 --- a/homeassistant/components/airzone_cloud/__init__.py +++ b/homeassistant/components/airzone_cloud/__init__.py @@ -1,4 +1,5 @@ """The Airzone Cloud integration.""" + from __future__ import annotations from aioairzone_cloud.cloudapi import AirzoneCloudApi diff --git a/homeassistant/components/airzone_cloud/binary_sensor.py b/homeassistant/components/airzone_cloud/binary_sensor.py index 20b747dfae3..8730eb8977e 100644 --- a/homeassistant/components/airzone_cloud/binary_sensor.py +++ b/homeassistant/components/airzone_cloud/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the Airzone Cloud binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/airzone_cloud/climate.py b/homeassistant/components/airzone_cloud/climate.py index 1bab9dd6c33..5aa03a14ada 100644 --- a/homeassistant/components/airzone_cloud/climate.py +++ b/homeassistant/components/airzone_cloud/climate.py @@ -1,4 +1,5 @@ """Support for the Airzone Cloud climate.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/airzone_cloud/config_flow.py b/homeassistant/components/airzone_cloud/config_flow.py index 486c1dfa8b6..e4e6a6ccbf3 100644 --- a/homeassistant/components/airzone_cloud/config_flow.py +++ b/homeassistant/components/airzone_cloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Airzone Cloud.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/airzone_cloud/coordinator.py b/homeassistant/components/airzone_cloud/coordinator.py index 5d15edffdf9..e510dcfb401 100644 --- a/homeassistant/components/airzone_cloud/coordinator.py +++ b/homeassistant/components/airzone_cloud/coordinator.py @@ -1,4 +1,5 @@ """The Airzone Cloud integration coordinator.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/airzone_cloud/diagnostics.py b/homeassistant/components/airzone_cloud/diagnostics.py index 0bce3251d5a..372455a4597 100644 --- a/homeassistant/components/airzone_cloud/diagnostics.py +++ b/homeassistant/components/airzone_cloud/diagnostics.py @@ -1,4 +1,5 @@ """Support for the Airzone Cloud diagnostics.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/airzone_cloud/entity.py b/homeassistant/components/airzone_cloud/entity.py index a175167be5a..f53321ce353 100644 --- a/homeassistant/components/airzone_cloud/entity.py +++ b/homeassistant/components/airzone_cloud/entity.py @@ -1,4 +1,5 @@ """Entity classes for the Airzone Cloud integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/airzone_cloud/sensor.py b/homeassistant/components/airzone_cloud/sensor.py index 965ac24a64f..430248d0c64 100644 --- a/homeassistant/components/airzone_cloud/sensor.py +++ b/homeassistant/components/airzone_cloud/sensor.py @@ -1,4 +1,5 @@ """Support for the Airzone Cloud sensors.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/aladdin_connect/config_flow.py b/homeassistant/components/aladdin_connect/config_flow.py index e3a6867445c..df822086db7 100644 --- a/homeassistant/components/aladdin_connect/config_flow.py +++ b/homeassistant/components/aladdin_connect/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Aladdin Connect cover integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/aladdin_connect/const.py b/homeassistant/components/aladdin_connect/const.py index 46d5d468f71..bf77c032d1b 100644 --- a/homeassistant/components/aladdin_connect/const.py +++ b/homeassistant/components/aladdin_connect/const.py @@ -1,4 +1,5 @@ """Platform for the Aladdin Connect cover component.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py index f4104a39365..61c8df92eaf 100644 --- a/homeassistant/components/aladdin_connect/cover.py +++ b/homeassistant/components/aladdin_connect/cover.py @@ -1,4 +1,5 @@ """Platform for the Aladdin Connect cover component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/aladdin_connect/diagnostics.py b/homeassistant/components/aladdin_connect/diagnostics.py index c49d321631e..b838ff79da3 100644 --- a/homeassistant/components/aladdin_connect/diagnostics.py +++ b/homeassistant/components/aladdin_connect/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Aladdin Connect.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aladdin_connect/model.py b/homeassistant/components/aladdin_connect/model.py index 9b250459d3b..73e445f2f3b 100644 --- a/homeassistant/components/aladdin_connect/model.py +++ b/homeassistant/components/aladdin_connect/model.py @@ -1,4 +1,5 @@ """Models for Aladdin connect cover platform.""" + from __future__ import annotations from typing import TypedDict diff --git a/homeassistant/components/aladdin_connect/sensor.py b/homeassistant/components/aladdin_connect/sensor.py index 5ea1c13fc27..22aa9c6faf0 100644 --- a/homeassistant/components/aladdin_connect/sensor.py +++ b/homeassistant/components/aladdin_connect/sensor.py @@ -1,4 +1,5 @@ """Support for Aladdin Connect Garage Door sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 45e1d63e0c2..a4f950d4db2 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -1,4 +1,5 @@ """Component to interface with an alarm control panel.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/alarm_control_panel/const.py b/homeassistant/components/alarm_control_panel/const.py index fe4be649e19..2e8fe98da3b 100644 --- a/homeassistant/components/alarm_control_panel/const.py +++ b/homeassistant/components/alarm_control_panel/const.py @@ -1,4 +1,5 @@ """Provides the constants needed for component.""" + from enum import IntFlag, StrEnum from functools import partial from typing import Final diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 9c068bb3327..72b1084d072 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Alarm control panel.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/alarm_control_panel/device_condition.py b/homeassistant/components/alarm_control_panel/device_condition.py index e3c627d17a3..227fc31413e 100644 --- a/homeassistant/components/alarm_control_panel/device_condition.py +++ b/homeassistant/components/alarm_control_panel/device_condition.py @@ -1,4 +1,5 @@ """Provide the device automations for Alarm control panel.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index e5141a1dfd5..557666720e8 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Alarm control panel.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/alarm_control_panel/reproduce_state.py b/homeassistant/components/alarm_control_panel/reproduce_state.py index ad992012c04..5a3d79fe2ed 100644 --- a/homeassistant/components/alarm_control_panel/reproduce_state.py +++ b/homeassistant/components/alarm_control_panel/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Alarm control panel state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/alarm_control_panel/significant_change.py b/homeassistant/components/alarm_control_panel/significant_change.py index bde6d151393..4a2209e0868 100644 --- a/homeassistant/components/alarm_control_panel/significant_change.py +++ b/homeassistant/components/alarm_control_panel/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Alarm Control Panel state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 19d1d729a5e..c05c6ea6119 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -1,4 +1,5 @@ """Support for AlarmDecoder devices.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index ca11b9d6894..2e2db6f070f 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for AlarmDecoder-based alarm control panels (Honeywell/DSC).""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/alarmdecoder/config_flow.py b/homeassistant/components/alarmdecoder/config_flow.py index 0b1ebdf8af7..a775375b835 100644 --- a/homeassistant/components/alarmdecoder/config_flow.py +++ b/homeassistant/components/alarmdecoder/config_flow.py @@ -1,4 +1,5 @@ """Config flow for AlarmDecoder.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index 1598171649b..e796334a91c 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -1,4 +1,5 @@ """Support for AlarmDecoder sensors (Shows Panel Display).""" + from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 721ed0d0c21..07a6c735696 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -1,4 +1,5 @@ """Support for repeating alerts when conditions are met.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/alert/reproduce_state.py b/homeassistant/components/alert/reproduce_state.py index 1e813768b3a..db540369d84 100644 --- a/homeassistant/components/alert/reproduce_state.py +++ b/homeassistant/components/alert/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Alert state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index 2a9637772b1..eeeb8e53e43 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -1,4 +1,5 @@ """Support for Alexa skill service end point.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index d30f3f7376d..d553be2e8cc 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1,4 +1,5 @@ """Alexa capabilities.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/alexa/config.py b/homeassistant/components/alexa/config.py index 02aaed25742..fb589dde566 100644 --- a/homeassistant/components/alexa/config.py +++ b/homeassistant/components/alexa/config.py @@ -1,4 +1,5 @@ """Config helpers for Alexa.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index abdef0cb566..2c615b71166 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -1,4 +1,5 @@ """Constants for the Alexa integration.""" + from collections import OrderedDict from homeassistant.components import climate diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index ddc0bc70987..61ab220c60c 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -1,4 +1,5 @@ """Alexa entity adapters.""" + from __future__ import annotations from collections.abc import Generator, Iterable diff --git a/homeassistant/components/alexa/errors.py b/homeassistant/components/alexa/errors.py index f8e3720e160..c341356db86 100644 --- a/homeassistant/components/alexa/errors.py +++ b/homeassistant/components/alexa/errors.py @@ -1,4 +1,5 @@ """Alexa related errors.""" + from __future__ import annotations from typing import Any, Literal diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index b5b72bc6dc5..30c2fecccf8 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -1,4 +1,5 @@ """Alexa message handlers.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/alexa/logbook.py b/homeassistant/components/alexa/logbook.py index cb6835c7ba5..3e641e715f3 100644 --- a/homeassistant/components/alexa/logbook.py +++ b/homeassistant/components/alexa/logbook.py @@ -1,4 +1,5 @@ """Describe logbook events.""" + from collections.abc import Callable from typing import Any diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index 3ad863747e5..9c640d76dd4 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -1,4 +1,5 @@ """Alexa state report code.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/alpha_vantage/sensor.py b/homeassistant/components/alpha_vantage/sensor.py index 52427065f68..dc62a734d42 100644 --- a/homeassistant/components/alpha_vantage/sensor.py +++ b/homeassistant/components/alpha_vantage/sensor.py @@ -1,4 +1,5 @@ """Stock market information from Alpha Vantage.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/amazon_polly/const.py b/homeassistant/components/amazon_polly/const.py index e1f7afce174..66084735c39 100644 --- a/homeassistant/components/amazon_polly/const.py +++ b/homeassistant/components/amazon_polly/const.py @@ -1,4 +1,5 @@ """Constants for the Amazon Polly text to speech service.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/amazon_polly/tts.py b/homeassistant/components/amazon_polly/tts.py index 5db46fc019e..bde690a3163 100644 --- a/homeassistant/components/amazon_polly/tts.py +++ b/homeassistant/components/amazon_polly/tts.py @@ -1,4 +1,5 @@ """Support for the Amazon Polly text to speech service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/amberelectric/config_flow.py b/homeassistant/components/amberelectric/config_flow.py index ee94f445498..174e8716e0b 100644 --- a/homeassistant/components/amberelectric/config_flow.py +++ b/homeassistant/components/amberelectric/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Amber Electric integration.""" + from __future__ import annotations import amberelectric diff --git a/homeassistant/components/amberelectric/coordinator.py b/homeassistant/components/amberelectric/coordinator.py index 3e420be2f68..9fb6293c9a2 100644 --- a/homeassistant/components/amberelectric/coordinator.py +++ b/homeassistant/components/amberelectric/coordinator.py @@ -1,4 +1,5 @@ """Amber Electric Coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index 58b2334260e..2e81cbd3595 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -1,4 +1,5 @@ """Support for Ambiclimate ac.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 7dd6b455e73..0984e21a722 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -1,4 +1,5 @@ """Support for Ambient Weather Station Service.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index 25c95b2e20e..fc21455a00f 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Ambient Weather Station binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ambient_station/config_flow.py b/homeassistant/components/ambient_station/config_flow.py index 8e9ff87a5f6..66e603ba2ff 100644 --- a/homeassistant/components/ambient_station/config_flow.py +++ b/homeassistant/components/ambient_station/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Ambient PWS component.""" + from __future__ import annotations from aioambient import API diff --git a/homeassistant/components/ambient_station/diagnostics.py b/homeassistant/components/ambient_station/diagnostics.py index d18047fe8e4..f3508b8df38 100644 --- a/homeassistant/components/ambient_station/diagnostics.py +++ b/homeassistant/components/ambient_station/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Ambient PWS.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ambient_station/entity.py b/homeassistant/components/ambient_station/entity.py index 277b69e8f68..a1a81d97c3f 100644 --- a/homeassistant/components/ambient_station/entity.py +++ b/homeassistant/components/ambient_station/entity.py @@ -1,4 +1,5 @@ """Base entity Ambient Weather Station Service.""" + from __future__ import annotations from aioambient.util import get_public_device_id diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 951bfc5c8ff..db729197a59 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -1,4 +1,5 @@ """Support for Ambient Weather Station sensors.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index ce07741c37f..1b205e4a424 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -1,4 +1,5 @@ """Support for Amcrest IP cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index a0b6b4f6527..ccbf5efd8f4 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Amcrest IP camera binary sensors.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 43201aba77a..3c579e37f8a 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -1,4 +1,5 @@ """Support for Amcrest IP cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/amcrest/helpers.py b/homeassistant/components/amcrest/helpers.py index 306c24a94ac..5da1ea412bf 100644 --- a/homeassistant/components/amcrest/helpers.py +++ b/homeassistant/components/amcrest/helpers.py @@ -1,4 +1,5 @@ """Helpers for amcrest component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 8ba274e62ee..6e096d8c764 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -1,4 +1,5 @@ """Support for Amcrest IP camera sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/amcrest/switch.py b/homeassistant/components/amcrest/switch.py index fc7347bff97..0566e26b7ed 100644 --- a/homeassistant/components/amcrest/switch.py +++ b/homeassistant/components/amcrest/switch.py @@ -1,4 +1,5 @@ """Support for Amcrest Switches.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/ampio/air_quality.py b/homeassistant/components/ampio/air_quality.py index a423a628367..ce7bff10aa8 100644 --- a/homeassistant/components/ampio/air_quality.py +++ b/homeassistant/components/ampio/air_quality.py @@ -1,4 +1,5 @@ """Support for Ampio Air Quality data.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ampio/const.py b/homeassistant/components/ampio/const.py index b1a13ce9414..7e2df12fcc2 100644 --- a/homeassistant/components/ampio/const.py +++ b/homeassistant/components/ampio/const.py @@ -1,4 +1,5 @@ """Constants for Ampio Air Quality platform.""" + from datetime import timedelta from typing import Final diff --git a/homeassistant/components/analytics/__init__.py b/homeassistant/components/analytics/__init__.py index ee36aa78e63..a49fe15b41f 100644 --- a/homeassistant/components/analytics/__init__.py +++ b/homeassistant/components/analytics/__init__.py @@ -1,4 +1,5 @@ """Send instance and usage analytics.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index d2c0cec20eb..2528dbd78e8 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -1,4 +1,5 @@ """Analytics helper class for the analytics integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/analytics/const.py b/homeassistant/components/analytics/const.py index fd253c32f93..6f74cc60f84 100644 --- a/homeassistant/components/analytics/const.py +++ b/homeassistant/components/analytics/const.py @@ -1,4 +1,5 @@ """Constants for the analytics integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/analytics_insights/__init__.py b/homeassistant/components/analytics_insights/__init__.py index 23965a9fcb5..65c3930e97d 100644 --- a/homeassistant/components/analytics_insights/__init__.py +++ b/homeassistant/components/analytics_insights/__init__.py @@ -1,4 +1,5 @@ """The Homeassistant Analytics integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/analytics_insights/config_flow.py b/homeassistant/components/analytics_insights/config_flow.py index edc69a0d161..30b8ca12579 100644 --- a/homeassistant/components/analytics_insights/config_flow.py +++ b/homeassistant/components/analytics_insights/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Homeassistant Analytics integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/analytics_insights/coordinator.py b/homeassistant/components/analytics_insights/coordinator.py index c646288cbe0..759ce567898 100644 --- a/homeassistant/components/analytics_insights/coordinator.py +++ b/homeassistant/components/analytics_insights/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Homeassistant Analytics integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/analytics_insights/sensor.py b/homeassistant/components/analytics_insights/sensor.py index 90e9ff51b87..e776ddb9f41 100644 --- a/homeassistant/components/analytics_insights/sensor.py +++ b/homeassistant/components/analytics_insights/sensor.py @@ -1,4 +1,5 @@ """Sensor for Home Assistant analytics.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/android_ip_webcam/__init__.py b/homeassistant/components/android_ip_webcam/__init__.py index 47307fb3690..db50d6d3e1a 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -1,4 +1,5 @@ """The Android IP Webcam integration.""" + from __future__ import annotations from pydroid_ipcam import PyDroidIPCam diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index 6f17616a216..85edfc8a6a6 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Android IP Webcam binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/android_ip_webcam/camera.py b/homeassistant/components/android_ip_webcam/camera.py index a12798a5b91..2149e40b6e1 100644 --- a/homeassistant/components/android_ip_webcam/camera.py +++ b/homeassistant/components/android_ip_webcam/camera.py @@ -1,4 +1,5 @@ """Support for Android IP Webcam Cameras.""" + from __future__ import annotations from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging diff --git a/homeassistant/components/android_ip_webcam/config_flow.py b/homeassistant/components/android_ip_webcam/config_flow.py index 920c9cdf702..70870debfb1 100644 --- a/homeassistant/components/android_ip_webcam/config_flow.py +++ b/homeassistant/components/android_ip_webcam/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Android IP Webcam integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index bd057bac7c7..eb00dd5dbf7 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -1,4 +1,5 @@ """Support for Android IP Webcam sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py index d3edd4a0439..038c3330d82 100644 --- a/homeassistant/components/android_ip_webcam/switch.py +++ b/homeassistant/components/android_ip_webcam/switch.py @@ -1,4 +1,5 @@ """Support for Android IP Webcam settings.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/androidtv/__init__.py b/homeassistant/components/androidtv/__init__.py index cd9e42aeb4d..884a06bca68 100644 --- a/homeassistant/components/androidtv/__init__.py +++ b/homeassistant/components/androidtv/__init__.py @@ -1,4 +1,5 @@ """Support for functionality to interact with Android/Fire TV devices.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index 5df4bf2f89b..765ee5def16 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Android Debug Bridge integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/androidtv/diagnostics.py b/homeassistant/components/androidtv/diagnostics.py index 0921fecc500..5dba4109f32 100644 --- a/homeassistant/components/androidtv/diagnostics.py +++ b/homeassistant/components/androidtv/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for AndroidTV.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/androidtv/entity.py b/homeassistant/components/androidtv/entity.py index e9cbd435d9b..2185f6d151a 100644 --- a/homeassistant/components/androidtv/entity.py +++ b/homeassistant/components/androidtv/entity.py @@ -1,4 +1,5 @@ """Base AndroidTV Entity.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 5e97396b369..016a7a5a7a2 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -1,4 +1,5 @@ """Support for functionality to interact with Android / Fire TV devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/androidtv_remote/__init__.py b/homeassistant/components/androidtv_remote/__init__.py index 9e99a93efa6..c64fc273a2a 100644 --- a/homeassistant/components/androidtv_remote/__init__.py +++ b/homeassistant/components/androidtv_remote/__init__.py @@ -1,4 +1,5 @@ """The Android TV Remote integration.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/androidtv_remote/config_flow.py b/homeassistant/components/androidtv_remote/config_flow.py index 78f039d83fc..2fd9f607218 100644 --- a/homeassistant/components/androidtv_remote/config_flow.py +++ b/homeassistant/components/androidtv_remote/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Android TV Remote integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/androidtv_remote/const.py b/homeassistant/components/androidtv_remote/const.py index 44d7098adc1..9d2a7fcb240 100644 --- a/homeassistant/components/androidtv_remote/const.py +++ b/homeassistant/components/androidtv_remote/const.py @@ -1,4 +1,5 @@ """Constants for the Android TV Remote integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/androidtv_remote/diagnostics.py b/homeassistant/components/androidtv_remote/diagnostics.py index 28d16bf94fe..757b3bd4e83 100644 --- a/homeassistant/components/androidtv_remote/diagnostics.py +++ b/homeassistant/components/androidtv_remote/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Android TV Remote.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/androidtv_remote/entity.py b/homeassistant/components/androidtv_remote/entity.py index 86c8d16260c..fa070e1ec18 100644 --- a/homeassistant/components/androidtv_remote/entity.py +++ b/homeassistant/components/androidtv_remote/entity.py @@ -1,4 +1,5 @@ """Base entity for Android TV Remote.""" + from __future__ import annotations from androidtvremote2 import AndroidTVRemote, ConnectionClosed diff --git a/homeassistant/components/androidtv_remote/helpers.py b/homeassistant/components/androidtv_remote/helpers.py index 41b056269f2..cdd67b029fc 100644 --- a/homeassistant/components/androidtv_remote/helpers.py +++ b/homeassistant/components/androidtv_remote/helpers.py @@ -1,4 +1,5 @@ """Helper functions for Android TV Remote integration.""" + from __future__ import annotations from androidtvremote2 import AndroidTVRemote diff --git a/homeassistant/components/androidtv_remote/media_player.py b/homeassistant/components/androidtv_remote/media_player.py index eccfc8ce25b..997f3fb040a 100644 --- a/homeassistant/components/androidtv_remote/media_player.py +++ b/homeassistant/components/androidtv_remote/media_player.py @@ -1,4 +1,5 @@ """Media player support for Android TV Remote.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/androidtv_remote/remote.py b/homeassistant/components/androidtv_remote/remote.py index f4c2ae51ce1..3dc5534e54f 100644 --- a/homeassistant/components/androidtv_remote/remote.py +++ b/homeassistant/components/androidtv_remote/remote.py @@ -1,4 +1,5 @@ """Remote control support for Android TV Remote.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/anel_pwrctrl/switch.py b/homeassistant/components/anel_pwrctrl/switch.py index 827fc0037a7..94cd0a59398 100644 --- a/homeassistant/components/anel_pwrctrl/switch.py +++ b/homeassistant/components/anel_pwrctrl/switch.py @@ -1,4 +1,5 @@ """Support for ANEL PwrCtrl switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/anova/__init__.py b/homeassistant/components/anova/__init__.py index 6181d02025d..653b556089c 100644 --- a/homeassistant/components/anova/__init__.py +++ b/homeassistant/components/anova/__init__.py @@ -1,4 +1,5 @@ """The Anova integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/anova/config_flow.py b/homeassistant/components/anova/config_flow.py index 13f62451e19..08a3d4e832f 100644 --- a/homeassistant/components/anova/config_flow.py +++ b/homeassistant/components/anova/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Anova.""" + from __future__ import annotations from anova_wifi import AnovaApi, InvalidLogin, NoDevicesFound diff --git a/homeassistant/components/anova/coordinator.py b/homeassistant/components/anova/coordinator.py index 83dc2c295c3..c0261c139c1 100644 --- a/homeassistant/components/anova/coordinator.py +++ b/homeassistant/components/anova/coordinator.py @@ -1,4 +1,5 @@ """Support for Anova Coordinators.""" + from asyncio import timeout from datetime import timedelta import logging diff --git a/homeassistant/components/anova/entity.py b/homeassistant/components/anova/entity.py index d3ed2eb2667..a8e3ce0ae70 100644 --- a/homeassistant/components/anova/entity.py +++ b/homeassistant/components/anova/entity.py @@ -1,4 +1,5 @@ """Base entity for the Anova integration.""" + from __future__ import annotations from homeassistant.helpers.entity import Entity, EntityDescription diff --git a/homeassistant/components/anova/models.py b/homeassistant/components/anova/models.py index a63355b2bbd..4a6338eb081 100644 --- a/homeassistant/components/anova/models.py +++ b/homeassistant/components/anova/models.py @@ -1,4 +1,5 @@ """Dataclass models for the Anova integration.""" + from dataclasses import dataclass from anova_wifi import AnovaPrecisionCooker diff --git a/homeassistant/components/anova/sensor.py b/homeassistant/components/anova/sensor.py index 24bda4dbed6..7e94f8f4b0b 100644 --- a/homeassistant/components/anova/sensor.py +++ b/homeassistant/components/anova/sensor.py @@ -1,4 +1,5 @@ """Support for Anova Sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/anthemav/__init__.py b/homeassistant/components/anthemav/__init__.py index 0a7e36d8a95..4efeb9245c8 100644 --- a/homeassistant/components/anthemav/__init__.py +++ b/homeassistant/components/anthemav/__init__.py @@ -1,4 +1,5 @@ """The Anthem A/V Receivers integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/anthemav/config_flow.py b/homeassistant/components/anthemav/config_flow.py index 51124451156..400ac6d5899 100644 --- a/homeassistant/components/anthemav/config_flow.py +++ b/homeassistant/components/anthemav/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Anthem A/V Receivers integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index a4982b2e9e8..1dbfdf275f2 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -1,4 +1,5 @@ """Support for Anthem Network Receivers and Processors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aosmith/__init__.py b/homeassistant/components/aosmith/__init__.py index 4da390685ab..c42096cd3a7 100644 --- a/homeassistant/components/aosmith/__init__.py +++ b/homeassistant/components/aosmith/__init__.py @@ -1,4 +1,5 @@ """The A. O. Smith integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/aosmith/config_flow.py b/homeassistant/components/aosmith/config_flow.py index 65262890868..ec38460116d 100644 --- a/homeassistant/components/aosmith/config_flow.py +++ b/homeassistant/components/aosmith/config_flow.py @@ -1,4 +1,5 @@ """Config flow for A. O. Smith integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/aosmith/diagnostics.py b/homeassistant/components/aosmith/diagnostics.py index a821c980faa..96b049b904f 100644 --- a/homeassistant/components/aosmith/diagnostics.py +++ b/homeassistant/components/aosmith/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for A. O. Smith.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aosmith/entity.py b/homeassistant/components/aosmith/entity.py index 7407fbac3cb..d35b8b36410 100644 --- a/homeassistant/components/aosmith/entity.py +++ b/homeassistant/components/aosmith/entity.py @@ -1,4 +1,5 @@ """The base entity for the A. O. Smith integration.""" + from typing import TypeVar from py_aosmith import AOSmithAPIClient diff --git a/homeassistant/components/apache_kafka/__init__.py b/homeassistant/components/apache_kafka/__init__.py index c49d2954424..f3ace718638 100644 --- a/homeassistant/components/apache_kafka/__init__.py +++ b/homeassistant/components/apache_kafka/__init__.py @@ -1,4 +1,5 @@ """Support for Apache Kafka.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/apcupsd/__init__.py b/homeassistant/components/apcupsd/__init__.py index 550e1014d2a..73ed721158d 100644 --- a/homeassistant/components/apcupsd/__init__.py +++ b/homeassistant/components/apcupsd/__init__.py @@ -1,4 +1,5 @@ """Support for APCUPSd via its Network Information Server (NIS).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index 89de03fced2..ea0308f5450 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -1,4 +1,5 @@ """Support for tracking the online status of a UPS.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/apcupsd/config_flow.py b/homeassistant/components/apcupsd/config_flow.py index 3e37cb209c3..8d15b0478dd 100644 --- a/homeassistant/components/apcupsd/config_flow.py +++ b/homeassistant/components/apcupsd/config_flow.py @@ -1,4 +1,5 @@ """Config flow for APCUPSd integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/apcupsd/const.py b/homeassistant/components/apcupsd/const.py index 1bdf87bc57b..e24a66fdca1 100644 --- a/homeassistant/components/apcupsd/const.py +++ b/homeassistant/components/apcupsd/const.py @@ -1,4 +1,5 @@ """Constants for APCUPSd component.""" + from typing import Final DOMAIN: Final = "apcupsd" diff --git a/homeassistant/components/apcupsd/coordinator.py b/homeassistant/components/apcupsd/coordinator.py index 5d71fd3e219..768e9605967 100644 --- a/homeassistant/components/apcupsd/coordinator.py +++ b/homeassistant/components/apcupsd/coordinator.py @@ -1,4 +1,5 @@ """Support for APCUPSd via its Network Information Server (NIS).""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index e27071c75c3..8162653abb3 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -1,4 +1,5 @@ """Support for APCUPSd sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index c369b07de36..7ae2b7575bc 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -1,4 +1,5 @@ """The Apple TV integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/apple_tv/browse_media.py b/homeassistant/components/apple_tv/browse_media.py index d706c9aa7e9..0b8d2ab06c3 100644 --- a/homeassistant/components/apple_tv/browse_media.py +++ b/homeassistant/components/apple_tv/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + from typing import Any from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index 02117424f80..dea4763a04d 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Apple TV integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index a7b5957ecff..8d10509d615 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -1,4 +1,5 @@ """Support for Apple TV media player.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/application_credentials/__init__.py b/homeassistant/components/application_credentials/__init__.py index cd6e85a427a..aacd18fc795 100644 --- a/homeassistant/components/application_credentials/__init__.py +++ b/homeassistant/components/application_credentials/__init__.py @@ -5,6 +5,7 @@ of other integrations. Integrations register an authorization server, and then the APIs are used to add one or more client credentials. Integrations may also provide credentials from yaml for backwards compatibility. """ + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index acd1db00d93..57a7feb6e5c 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -1,4 +1,5 @@ """Apprise platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aprs/device_tracker.py b/homeassistant/components/aprs/device_tracker.py index 8b952f88c7c..0915643340b 100644 --- a/homeassistant/components/aprs/device_tracker.py +++ b/homeassistant/components/aprs/device_tracker.py @@ -1,4 +1,5 @@ """Support for APRS device tracking.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aqualogic/__init__.py b/homeassistant/components/aqualogic/__init__.py index 28e57c2b351..7c3a5966d1c 100644 --- a/homeassistant/components/aqualogic/__init__.py +++ b/homeassistant/components/aqualogic/__init__.py @@ -1,4 +1,5 @@ """Support for AquaLogic devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/aqualogic/sensor.py b/homeassistant/components/aqualogic/sensor.py index 90f87bfde23..bdb582826dc 100644 --- a/homeassistant/components/aqualogic/sensor.py +++ b/homeassistant/components/aqualogic/sensor.py @@ -1,4 +1,5 @@ """Support for AquaLogic sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py index e693df0a0c1..1ba268f0f2f 100644 --- a/homeassistant/components/aqualogic/switch.py +++ b/homeassistant/components/aqualogic/switch.py @@ -1,4 +1,5 @@ """Support for AquaLogic switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aquostv/media_player.py b/homeassistant/components/aquostv/media_player.py index a87756334e2..7160810e0dc 100644 --- a/homeassistant/components/aquostv/media_player.py +++ b/homeassistant/components/aquostv/media_player.py @@ -1,4 +1,5 @@ """Support for interface with an Aquos TV.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/aranet/__init__.py b/homeassistant/components/aranet/__init__.py index 07e19ca2618..7d9254e4223 100644 --- a/homeassistant/components/aranet/__init__.py +++ b/homeassistant/components/aranet/__init__.py @@ -1,4 +1,5 @@ """The Aranet integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aranet/config_flow.py b/homeassistant/components/aranet/config_flow.py index 8d813884a82..cf5f24263dd 100644 --- a/homeassistant/components/aranet/config_flow.py +++ b/homeassistant/components/aranet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Aranet integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aranet/sensor.py b/homeassistant/components/aranet/sensor.py index 23d3b64fdca..b55fe2bc5ce 100644 --- a/homeassistant/components/aranet/sensor.py +++ b/homeassistant/components/aranet/sensor.py @@ -1,4 +1,5 @@ """Support for Aranet sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/arcam_fmj/config_flow.py b/homeassistant/components/arcam_fmj/config_flow.py index 481657bcd85..a1aefc3a755 100644 --- a/homeassistant/components/arcam_fmj/config_flow.py +++ b/homeassistant/components/arcam_fmj/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Arcam FMJ component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index 174ffda9622..6147e05f804 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Arcam FMJ Receiver control.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index 7ec5bcdfa64..ac8d389304b 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -1,4 +1,5 @@ """Arcam media player.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/arest/binary_sensor.py b/homeassistant/components/arest/binary_sensor.py index 5d65a23335a..71f1c081f2d 100644 --- a/homeassistant/components/arest/binary_sensor.py +++ b/homeassistant/components/arest/binary_sensor.py @@ -1,4 +1,5 @@ """Support for an exposed aREST RESTful API of a device.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index 2e6012e0e6b..917b255ef14 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -1,4 +1,5 @@ """Support for an exposed aREST RESTful API of a device.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/arest/switch.py b/homeassistant/components/arest/switch.py index 1c67723fc02..4b15e6726fe 100644 --- a/homeassistant/components/arest/switch.py +++ b/homeassistant/components/arest/switch.py @@ -1,4 +1,5 @@ """Support for an exposed aREST RESTful API of a device.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/arris_tg2492lg/device_tracker.py b/homeassistant/components/arris_tg2492lg/device_tracker.py index bb917af5c39..f9485636365 100644 --- a/homeassistant/components/arris_tg2492lg/device_tracker.py +++ b/homeassistant/components/arris_tg2492lg/device_tracker.py @@ -1,4 +1,5 @@ """Support for Arris TG2492LG router.""" + from __future__ import annotations from arris_tg2492lg import ConnectBox, Device diff --git a/homeassistant/components/aruba/device_tracker.py b/homeassistant/components/aruba/device_tracker.py index 1b449450cf8..35053233428 100644 --- a/homeassistant/components/aruba/device_tracker.py +++ b/homeassistant/components/aruba/device_tracker.py @@ -1,4 +1,5 @@ """Support for Aruba Access Points.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index caf7dc6f45e..ada96c07340 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -1,4 +1,5 @@ """Support for collecting data from the ARWN project.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aseko_pool_live/__init__.py b/homeassistant/components/aseko_pool_live/__init__.py index 8f973af7cf7..5773b3eb5b9 100644 --- a/homeassistant/components/aseko_pool_live/__init__.py +++ b/homeassistant/components/aseko_pool_live/__init__.py @@ -1,4 +1,5 @@ """The Aseko Pool Live integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aseko_pool_live/binary_sensor.py b/homeassistant/components/aseko_pool_live/binary_sensor.py index a84ac0799d4..1013ee66c8e 100644 --- a/homeassistant/components/aseko_pool_live/binary_sensor.py +++ b/homeassistant/components/aseko_pool_live/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Aseko Pool Live binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/aseko_pool_live/config_flow.py b/homeassistant/components/aseko_pool_live/config_flow.py index 49143652fa9..f4df44aa2d7 100644 --- a/homeassistant/components/aseko_pool_live/config_flow.py +++ b/homeassistant/components/aseko_pool_live/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Aseko Pool Live integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/aseko_pool_live/coordinator.py b/homeassistant/components/aseko_pool_live/coordinator.py index 383ab7116b6..a7f2d5ad5ac 100644 --- a/homeassistant/components/aseko_pool_live/coordinator.py +++ b/homeassistant/components/aseko_pool_live/coordinator.py @@ -1,4 +1,5 @@ """The Aseko Pool Live integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/aseko_pool_live/entity.py b/homeassistant/components/aseko_pool_live/entity.py index 1defbe18345..cd96b8f59a7 100644 --- a/homeassistant/components/aseko_pool_live/entity.py +++ b/homeassistant/components/aseko_pool_live/entity.py @@ -1,4 +1,5 @@ """Aseko entity.""" + from aioaseko import Unit from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/aseko_pool_live/sensor.py b/homeassistant/components/aseko_pool_live/sensor.py index 55a40195750..262c7acd1aa 100644 --- a/homeassistant/components/aseko_pool_live/sensor.py +++ b/homeassistant/components/aseko_pool_live/sensor.py @@ -1,4 +1,5 @@ """Support for Aseko Pool Live sensors.""" + from __future__ import annotations from aioaseko import Unit, Variable diff --git a/homeassistant/components/assist_pipeline/__init__.py b/homeassistant/components/assist_pipeline/__init__.py index a009cfb1095..f15657d5a91 100644 --- a/homeassistant/components/assist_pipeline/__init__.py +++ b/homeassistant/components/assist_pipeline/__init__.py @@ -1,4 +1,5 @@ """The Assist pipeline integration.""" + from __future__ import annotations from collections.abc import AsyncIterable diff --git a/homeassistant/components/assist_pipeline/logbook.py b/homeassistant/components/assist_pipeline/logbook.py index 0c00c57adb9..50c5176bb22 100644 --- a/homeassistant/components/assist_pipeline/logbook.py +++ b/homeassistant/components/assist_pipeline/logbook.py @@ -1,4 +1,5 @@ """Describe assist_pipeline logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/assist_pipeline/pipeline.py b/homeassistant/components/assist_pipeline/pipeline.py index bf511f6cff5..5a3f01b7fc1 100644 --- a/homeassistant/components/assist_pipeline/pipeline.py +++ b/homeassistant/components/assist_pipeline/pipeline.py @@ -1,4 +1,5 @@ """Classes for voice assistant pipelines.""" + from __future__ import annotations import array diff --git a/homeassistant/components/assist_pipeline/vad.py b/homeassistant/components/assist_pipeline/vad.py index 9cc5fe9dfc6..6dacd2ff8e9 100644 --- a/homeassistant/components/assist_pipeline/vad.py +++ b/homeassistant/components/assist_pipeline/vad.py @@ -1,4 +1,5 @@ """Voice activity detection.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/asterisk_cdr/mailbox.py b/homeassistant/components/asterisk_cdr/mailbox.py index 971b893ef6b..fde4826fcee 100644 --- a/homeassistant/components/asterisk_cdr/mailbox.py +++ b/homeassistant/components/asterisk_cdr/mailbox.py @@ -1,4 +1,5 @@ """Support for the Asterisk CDR interface.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/asterisk_mbox/mailbox.py b/homeassistant/components/asterisk_mbox/mailbox.py index 95b3b7e3b15..14d54596eea 100644 --- a/homeassistant/components/asterisk_mbox/mailbox.py +++ b/homeassistant/components/asterisk_mbox/mailbox.py @@ -1,4 +1,5 @@ """Support for the Asterisk Voicemail interface.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/asuswrt/bridge.py b/homeassistant/components/asuswrt/bridge.py index cb04ccdec3f..35f3a98251f 100644 --- a/homeassistant/components/asuswrt/bridge.py +++ b/homeassistant/components/asuswrt/bridge.py @@ -1,4 +1,5 @@ """aioasuswrt and pyasuswrt bridge classes.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index fc0a9ee539e..65d33500970 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -1,4 +1,5 @@ """Support for ASUSWRT routers.""" + from __future__ import annotations from homeassistant.components.device_tracker import ScannerEntity, SourceType diff --git a/homeassistant/components/asuswrt/diagnostics.py b/homeassistant/components/asuswrt/diagnostics.py index 0a3cc809c32..47ad1f29363 100644 --- a/homeassistant/components/asuswrt/diagnostics.py +++ b/homeassistant/components/asuswrt/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Asuswrt.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index d868065be47..ed97b1f6871 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -1,4 +1,5 @@ """Represent the AsusWrt router.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 3399071daa4..80da4b51f0a 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -1,4 +1,5 @@ """Asuswrt status sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index b0cc83ab88e..85732485165 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -1,4 +1,5 @@ """The ATAG Integration.""" + from asyncio import timeout from datetime import timedelta import logging diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index a5f119e3a2b..ff66839926f 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -1,4 +1,5 @@ """Initialization of ATAG One climate platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index f4e60bc6062..c1a78da2ac0 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Atag component.""" + from typing import Any import pyatag diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index a006d1dfe05..25a3de34556 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -1,4 +1,5 @@ """Initialization of ATAG One sensor platform.""" + from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index 8976a3f78ec..8bae3df7436 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -1,4 +1,5 @@ """ATAG water heater component.""" + from typing import Any from homeassistant.components.water_heater import ( diff --git a/homeassistant/components/aten_pe/switch.py b/homeassistant/components/aten_pe/switch.py index 3293a3e7a09..75c43dca079 100644 --- a/homeassistant/components/aten_pe/switch.py +++ b/homeassistant/components/aten_pe/switch.py @@ -1,4 +1,5 @@ """The ATEN PE switch component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/atome/sensor.py b/homeassistant/components/atome/sensor.py index 37a8fd4460f..84751b84855 100644 --- a/homeassistant/components/atome/sensor.py +++ b/homeassistant/components/atome/sensor.py @@ -1,4 +1,5 @@ """Linky Atome.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index fe16819bf9c..82552ffa3d6 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -1,4 +1,5 @@ """Support for August devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/august/activity.py b/homeassistant/components/august/activity.py index 9a41d9bad81..a42a5aa2d67 100644 --- a/homeassistant/components/august/activity.py +++ b/homeassistant/components/august/activity.py @@ -1,4 +1,5 @@ """Consume the august activity stream.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index c24228006cb..14b9dca9b7d 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -1,4 +1,5 @@ """Support for August binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/august/button.py b/homeassistant/components/august/button.py index 3997a2d72bf..579f0012223 100644 --- a/homeassistant/components/august/button.py +++ b/homeassistant/components/august/button.py @@ -1,4 +1,5 @@ """Support for August buttons.""" + from yalexs.lock import Lock from homeassistant.components.button import ButtonEntity diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index e5835a69e07..f5380ce6732 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -1,4 +1,5 @@ """Support for August doorbell camera.""" + from __future__ import annotations from aiohttp import ClientSession diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index 3c0208412b9..e6803da2ae0 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -1,4 +1,5 @@ """Config flow for August integration.""" + from collections.abc import Mapping from dataclasses import dataclass import logging diff --git a/homeassistant/components/august/diagnostics.py b/homeassistant/components/august/diagnostics.py index 57e56795c2d..a1f76bf690b 100644 --- a/homeassistant/components/august/diagnostics.py +++ b/homeassistant/components/august/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for august.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/august/entity.py b/homeassistant/components/august/entity.py index bcd2c6e2503..47cb966bdc1 100644 --- a/homeassistant/components/august/entity.py +++ b/homeassistant/components/august/entity.py @@ -1,4 +1,5 @@ """Base class for August entity.""" + from abc import abstractmethod from yalexs.doorbell import Doorbell, DoorbellDetail diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 93e0de018b0..f4b6db55779 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -1,4 +1,5 @@ """Support for August lock.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 2cf0bb36d08..8d5270dcfee 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -1,4 +1,5 @@ """Support for August sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/august/subscriber.py b/homeassistant/components/august/subscriber.py index f2096506c4a..2ba1454a114 100644 --- a/homeassistant/components/august/subscriber.py +++ b/homeassistant/components/august/subscriber.py @@ -1,4 +1,5 @@ """Base class for August entity.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index 94e1f3fc2da..5c9166a0f60 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Aurora Forecast binary sensor.""" + from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/aurora/config_flow.py b/homeassistant/components/aurora/config_flow.py index 81e67acc27a..744624c2eb8 100644 --- a/homeassistant/components/aurora/config_flow.py +++ b/homeassistant/components/aurora/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Aurora.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/aurora/coordinator.py b/homeassistant/components/aurora/coordinator.py index 8195f6d30ec..ae1101f8054 100644 --- a/homeassistant/components/aurora/coordinator.py +++ b/homeassistant/components/aurora/coordinator.py @@ -1,4 +1,5 @@ """The aurora component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/aurora/sensor.py b/homeassistant/components/aurora/sensor.py index 7801a84d58b..e3ae9f9cf1b 100644 --- a/homeassistant/components/aurora/sensor.py +++ b/homeassistant/components/aurora/sensor.py @@ -1,4 +1,5 @@ """Support for Aurora Forecast sensor.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity, SensorStateClass diff --git a/homeassistant/components/aurora_abb_powerone/config_flow.py b/homeassistant/components/aurora_abb_powerone/config_flow.py index 37d802502bb..3f635595258 100644 --- a/homeassistant/components/aurora_abb_powerone/config_flow.py +++ b/homeassistant/components/aurora_abb_powerone/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Aurora ABB PowerOne integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 2ca7fa3e7ef..9c1a8e2a12c 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -1,4 +1,5 @@ """Support for Aurora ABB PowerOne Solar Photovoltaic (PV) inverter.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index 093480afd7d..d25a70221cd 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -1,4 +1,5 @@ """The Aussie Broadband integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/aussie_broadband/config_flow.py b/homeassistant/components/aussie_broadband/config_flow.py index dedab9684e6..587c7df2b36 100644 --- a/homeassistant/components/aussie_broadband/config_flow.py +++ b/homeassistant/components/aussie_broadband/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Aussie Broadband integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/aussie_broadband/diagnostics.py b/homeassistant/components/aussie_broadband/diagnostics.py index f4e95a99f56..499a739637e 100644 --- a/homeassistant/components/aussie_broadband/diagnostics.py +++ b/homeassistant/components/aussie_broadband/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Aussie Broadband.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aussie_broadband/sensor.py b/homeassistant/components/aussie_broadband/sensor.py index d92ba503412..49796b3f6cd 100644 --- a/homeassistant/components/aussie_broadband/sensor.py +++ b/homeassistant/components/aussie_broadband/sensor.py @@ -1,4 +1,5 @@ """Support for Aussie Broadband metric sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index 5f3836252df..d0e605e7c1e 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -122,6 +122,7 @@ This is an endpoint for OAuth2 Authorization callbacks used by integrations that link accounts with other cloud providers using LocalOAuth2Implementation as part of a config flow. """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index cf7f38fa32a..9284c232d38 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -1,4 +1,5 @@ """Helpers to resolve client ID/secret.""" + from __future__ import annotations from html.parser import HTMLParser diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 8051e871776..6c33d270f5f 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -66,6 +66,7 @@ associate with an credential if "type" set to "link_user" in "version": 1 } """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index a7999af666a..aee08186267 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -1,4 +1,5 @@ """Helpers to setup multi-factor auth module.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 98ec92a3771..eb5d6d840ec 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,4 +1,5 @@ """Allow to set up simple automation rules via the config file.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 72fb0101b24..71b4b3c0c6a 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -1,4 +1,5 @@ """Config validation helper for the automation integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/automation/helpers.py b/homeassistant/components/automation/helpers.py index a7c329a544a..6aefa2b150a 100644 --- a/homeassistant/components/automation/helpers.py +++ b/homeassistant/components/automation/helpers.py @@ -1,4 +1,5 @@ """Helpers for automation integration.""" + from homeassistant.components import blueprint from homeassistant.const import SERVICE_RELOAD from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/automation/logbook.py b/homeassistant/components/automation/logbook.py index e5ab351b0be..7b9c8cf5809 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -1,4 +1,5 @@ """Describe logbook events.""" + from homeassistant.components.logbook import ( LOGBOOK_ENTRY_CONTEXT_ID, LOGBOOK_ENTRY_ENTITY_ID, diff --git a/homeassistant/components/automation/reproduce_state.py b/homeassistant/components/automation/reproduce_state.py index aa6e6a501b6..06c982f5670 100644 --- a/homeassistant/components/automation/reproduce_state.py +++ b/homeassistant/components/automation/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Automation state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/automation/trace.py b/homeassistant/components/automation/trace.py index ae0d0339bfa..754c062ec2c 100644 --- a/homeassistant/components/automation/trace.py +++ b/homeassistant/components/automation/trace.py @@ -1,4 +1,5 @@ """Trace support for automation.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/avea/light.py b/homeassistant/components/avea/light.py index a33fbfeab79..48471b41633 100644 --- a/homeassistant/components/avea/light.py +++ b/homeassistant/components/avea/light.py @@ -1,4 +1,5 @@ """Support for the Elgato Avea lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index 54d06d50a60..91a63330249 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -1,4 +1,5 @@ """Support for Avion dimmers.""" + from __future__ import annotations import importlib diff --git a/homeassistant/components/awair/__init__.py b/homeassistant/components/awair/__init__.py index cb974707e93..aa810bf532b 100644 --- a/homeassistant/components/awair/__init__.py +++ b/homeassistant/components/awair/__init__.py @@ -1,4 +1,5 @@ """The awair component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index 751bcf6847d..cce447d33f8 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Awair.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/awair/const.py b/homeassistant/components/awair/const.py index 19341ab6050..a1c5781e9a4 100644 --- a/homeassistant/components/awair/const.py +++ b/homeassistant/components/awair/const.py @@ -1,4 +1,5 @@ """Constants for the Awair component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/awair/coordinator.py b/homeassistant/components/awair/coordinator.py index b687a916a2d..8e554b3b9e0 100644 --- a/homeassistant/components/awair/coordinator.py +++ b/homeassistant/components/awair/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinators for awair integration.""" + from __future__ import annotations from asyncio import gather, timeout diff --git a/homeassistant/components/awair/sensor.py b/homeassistant/components/awair/sensor.py index 45b40d70399..b9a226e9c2c 100644 --- a/homeassistant/components/awair/sensor.py +++ b/homeassistant/components/awair/sensor.py @@ -1,4 +1,5 @@ """Support for Awair sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py index 2e3ea341f60..9fdf0f5b193 100644 --- a/homeassistant/components/aws/notify.py +++ b/homeassistant/components/aws/notify.py @@ -1,4 +1,5 @@ """AWS platform for notify component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 8b39a8b42b5..cba98b81e30 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Axis binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 7c93449ec0b..79543b738da 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -1,4 +1,5 @@ """Support for Axis camera streaming.""" + from urllib.parse import urlencode from homeassistant.components.camera import CameraEntityFeature diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index f2dd6eac62a..4d6d4e12b0a 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Axis devices.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/axis/diagnostics.py b/homeassistant/components/axis/diagnostics.py index 2c93cac9b11..d2386047e71 100644 --- a/homeassistant/components/axis/diagnostics.py +++ b/homeassistant/components/axis/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Axis.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/axis/errors.py b/homeassistant/components/axis/errors.py index 56105b28b1b..caa872c4b18 100644 --- a/homeassistant/components/axis/errors.py +++ b/homeassistant/components/axis/errors.py @@ -1,4 +1,5 @@ """Errors for the Axis component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/axis/light.py b/homeassistant/components/axis/light.py index 8606335a5b4..2fe4278ef06 100644 --- a/homeassistant/components/axis/light.py +++ b/homeassistant/components/axis/light.py @@ -1,4 +1,5 @@ """Support for Axis lights.""" + from typing import Any from axis.models.event import Event, EventOperation, EventTopic diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index 6d3448fca67..d7072bb877f 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -1,4 +1,5 @@ """Support for Axis switches.""" + from typing import Any from axis.models.event import Event, EventOperation, EventTopic diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index 5a447d485d2..e2b761708a5 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -1,4 +1,5 @@ """Support for Azure DevOps.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/azure_devops/config_flow.py b/homeassistant/components/azure_devops/config_flow.py index 4b7320eeba5..336fd2ca8df 100644 --- a/homeassistant/components/azure_devops/config_flow.py +++ b/homeassistant/components/azure_devops/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Azure DevOps integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py index 94161d4ccbf..a6e4ee95cad 100644 --- a/homeassistant/components/azure_devops/sensor.py +++ b/homeassistant/components/azure_devops/sensor.py @@ -1,4 +1,5 @@ """Support for Azure DevOps sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index 4e69bea1a38..668444f9990 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -1,4 +1,5 @@ """Support for Azure Event Hubs.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/azure_event_hub/client.py b/homeassistant/components/azure_event_hub/client.py index f03f4339e69..0bf2cb69583 100644 --- a/homeassistant/components/azure_event_hub/client.py +++ b/homeassistant/components/azure_event_hub/client.py @@ -1,4 +1,5 @@ """File for Azure Event Hub models.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/azure_event_hub/config_flow.py b/homeassistant/components/azure_event_hub/config_flow.py index 17144da0b30..c088b35a002 100644 --- a/homeassistant/components/azure_event_hub/config_flow.py +++ b/homeassistant/components/azure_event_hub/config_flow.py @@ -1,4 +1,5 @@ """Config flow for azure_event_hub integration.""" + from __future__ import annotations from copy import deepcopy diff --git a/homeassistant/components/azure_event_hub/const.py b/homeassistant/components/azure_event_hub/const.py index 8c90b5daaa0..174fdddc6a1 100644 --- a/homeassistant/components/azure_event_hub/const.py +++ b/homeassistant/components/azure_event_hub/const.py @@ -1,4 +1,5 @@ """Constants and shared schema for the Azure Event Hub integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/azure_service_bus/notify.py b/homeassistant/components/azure_service_bus/notify.py index 4005460ecae..38c57b3db19 100644 --- a/homeassistant/components/azure_service_bus/notify.py +++ b/homeassistant/components/azure_service_bus/notify.py @@ -1,4 +1,5 @@ """Support for azure service bus notification.""" + from __future__ import annotations import json diff --git a/homeassistant/components/backup/__init__.py b/homeassistant/components/backup/__init__.py index 8f19436fb1d..2f9019300db 100644 --- a/homeassistant/components/backup/__init__.py +++ b/homeassistant/components/backup/__init__.py @@ -1,4 +1,5 @@ """The Backup integration.""" + from homeassistant.components.hassio import is_hassio from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/backup/const.py b/homeassistant/components/backup/const.py index a4a08fff75d..9573d522b56 100644 --- a/homeassistant/components/backup/const.py +++ b/homeassistant/components/backup/const.py @@ -1,4 +1,5 @@ """Constants for the Backup integration.""" + from logging import getLogger DOMAIN = "backup" diff --git a/homeassistant/components/backup/http.py b/homeassistant/components/backup/http.py index a2aa7026445..793192aa623 100644 --- a/homeassistant/components/backup/http.py +++ b/homeassistant/components/backup/http.py @@ -1,4 +1,5 @@ """Http view for the Backup integration.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/backup/manager.py b/homeassistant/components/backup/manager.py index ba5d8c8733c..e3331836202 100644 --- a/homeassistant/components/backup/manager.py +++ b/homeassistant/components/backup/manager.py @@ -1,4 +1,5 @@ """Backup manager for the Backup integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/backup/websocket.py b/homeassistant/components/backup/websocket.py index c1eed4294c2..08d6fda3663 100644 --- a/homeassistant/components/backup/websocket.py +++ b/homeassistant/components/backup/websocket.py @@ -1,4 +1,5 @@ """Websocket commands for the Backup integration.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/baf/__init__.py b/homeassistant/components/baf/__init__.py index e685ec6dc8c..d3b29b52e44 100644 --- a/homeassistant/components/baf/__init__.py +++ b/homeassistant/components/baf/__init__.py @@ -1,4 +1,5 @@ """The Big Ass Fans integration.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/baf/binary_sensor.py b/homeassistant/components/baf/binary_sensor.py index a4f501df5c1..e95e197b8be 100644 --- a/homeassistant/components/baf/binary_sensor.py +++ b/homeassistant/components/baf/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/baf/climate.py b/homeassistant/components/baf/climate.py index 907e8ff2356..f451c5e7a71 100644 --- a/homeassistant/components/baf/climate.py +++ b/homeassistant/components/baf/climate.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans auto comfort.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/baf/config_flow.py b/homeassistant/components/baf/config_flow.py index d5ac869526e..d0a3a82b396 100644 --- a/homeassistant/components/baf/config_flow.py +++ b/homeassistant/components/baf/config_flow.py @@ -1,4 +1,5 @@ """Config flow for baf.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/baf/entity.py b/homeassistant/components/baf/entity.py index 82ea0c16092..487e601b542 100644 --- a/homeassistant/components/baf/entity.py +++ b/homeassistant/components/baf/entity.py @@ -1,4 +1,5 @@ """The baf integration entities.""" + from __future__ import annotations from aiobafi6 import Device diff --git a/homeassistant/components/baf/fan.py b/homeassistant/components/baf/fan.py index e2d1c5fcb3a..15c6519747d 100644 --- a/homeassistant/components/baf/fan.py +++ b/homeassistant/components/baf/fan.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans fan.""" + from __future__ import annotations import math diff --git a/homeassistant/components/baf/light.py b/homeassistant/components/baf/light.py index ed5eea8796f..e203e12cf96 100644 --- a/homeassistant/components/baf/light.py +++ b/homeassistant/components/baf/light.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/baf/models.py b/homeassistant/components/baf/models.py index de5c4a3498b..c94b73d9abd 100644 --- a/homeassistant/components/baf/models.py +++ b/homeassistant/components/baf/models.py @@ -1,4 +1,5 @@ """The baf integration models.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/baf/number.py b/homeassistant/components/baf/number.py index 438dbbb689d..43da381391c 100644 --- a/homeassistant/components/baf/number.py +++ b/homeassistant/components/baf/number.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans number.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/baf/sensor.py b/homeassistant/components/baf/sensor.py index 33cee901594..7a7845c2f67 100644 --- a/homeassistant/components/baf/sensor.py +++ b/homeassistant/components/baf/sensor.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/baf/switch.py b/homeassistant/components/baf/switch.py index 406fcacadde..38248e48d09 100644 --- a/homeassistant/components/baf/switch.py +++ b/homeassistant/components/baf/switch.py @@ -1,4 +1,5 @@ """Support for Big Ass Fans switch.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/balboa/__init__.py b/homeassistant/components/balboa/__init__.py index 1d2cd042918..d6a80e8fa8f 100644 --- a/homeassistant/components/balboa/__init__.py +++ b/homeassistant/components/balboa/__init__.py @@ -1,4 +1,5 @@ """The Balboa Spa Client integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/balboa/binary_sensor.py b/homeassistant/components/balboa/binary_sensor.py index 053b94007c2..d3352208cd9 100644 --- a/homeassistant/components/balboa/binary_sensor.py +++ b/homeassistant/components/balboa/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Balboa Spa binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/balboa/climate.py b/homeassistant/components/balboa/climate.py index b9cce73de75..456fa0dd081 100644 --- a/homeassistant/components/balboa/climate.py +++ b/homeassistant/components/balboa/climate.py @@ -1,4 +1,5 @@ """Support for Balboa Spa Wifi adaptor.""" + from __future__ import annotations from enum import IntEnum diff --git a/homeassistant/components/balboa/config_flow.py b/homeassistant/components/balboa/config_flow.py index 7705695f538..2dc98fbcd69 100644 --- a/homeassistant/components/balboa/config_flow.py +++ b/homeassistant/components/balboa/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Balboa Spa Client integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/balboa/entity.py b/homeassistant/components/balboa/entity.py index e02579658da..a7d75bfbdf5 100644 --- a/homeassistant/components/balboa/entity.py +++ b/homeassistant/components/balboa/entity.py @@ -1,4 +1,5 @@ """Balboa entities.""" + from __future__ import annotations from pybalboa import EVENT_UPDATE, SpaClient diff --git a/homeassistant/components/balboa/fan.py b/homeassistant/components/balboa/fan.py index f6edc45c342..24fe3bdd71a 100644 --- a/homeassistant/components/balboa/fan.py +++ b/homeassistant/components/balboa/fan.py @@ -1,4 +1,5 @@ """Support for Balboa Spa pumps.""" + from __future__ import annotations import math diff --git a/homeassistant/components/balboa/light.py b/homeassistant/components/balboa/light.py index 00b8eb979a2..5dc8d48ef9d 100644 --- a/homeassistant/components/balboa/light.py +++ b/homeassistant/components/balboa/light.py @@ -1,4 +1,5 @@ """Support for Balboa Spa lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/bang_olufsen/__init__.py b/homeassistant/components/bang_olufsen/__init__.py index 3071b8fc6b2..2488c2e64f5 100644 --- a/homeassistant/components/bang_olufsen/__init__.py +++ b/homeassistant/components/bang_olufsen/__init__.py @@ -1,4 +1,5 @@ """The Bang & Olufsen integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/bang_olufsen/config_flow.py b/homeassistant/components/bang_olufsen/config_flow.py index 73b6586adb9..e3b8f9979d1 100644 --- a/homeassistant/components/bang_olufsen/config_flow.py +++ b/homeassistant/components/bang_olufsen/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Bang & Olufsen integration.""" + from __future__ import annotations from ipaddress import AddressValueError, IPv4Address diff --git a/homeassistant/components/bang_olufsen/entity.py b/homeassistant/components/bang_olufsen/entity.py index bf6966f83bf..4f8ff43e0a8 100644 --- a/homeassistant/components/bang_olufsen/entity.py +++ b/homeassistant/components/bang_olufsen/entity.py @@ -1,4 +1,5 @@ """Entity representing a Bang & Olufsen device.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/bang_olufsen/media_player.py b/homeassistant/components/bang_olufsen/media_player.py index 3209c676af7..4ab2ce236a1 100644 --- a/homeassistant/components/bang_olufsen/media_player.py +++ b/homeassistant/components/bang_olufsen/media_player.py @@ -1,4 +1,5 @@ """Media player entity for the Bang & Olufsen integration.""" + from __future__ import annotations import json diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 49965a38b77..8ff1f2476d5 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -1,4 +1,5 @@ """Use Bayesian Inference to trigger a binary sensor.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/components/bayesian/helpers.py b/homeassistant/components/bayesian/helpers.py index 6e78de63607..cc8966a90b6 100644 --- a/homeassistant/components/bayesian/helpers.py +++ b/homeassistant/components/bayesian/helpers.py @@ -1,4 +1,5 @@ """Helpers to deal with bayesian observations.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/bayesian/issues.py b/homeassistant/components/bayesian/issues.py index fbc3a86258d..b35c788053d 100644 --- a/homeassistant/components/bayesian/issues.py +++ b/homeassistant/components/bayesian/issues.py @@ -1,4 +1,5 @@ """Helpers for generating issues.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/bbox/device_tracker.py b/homeassistant/components/bbox/device_tracker.py index 9c83aaa1734..5413c75d8e7 100644 --- a/homeassistant/components/bbox/device_tracker.py +++ b/homeassistant/components/bbox/device_tracker.py @@ -1,4 +1,5 @@ """Support for French FAI Bouygues Bbox routers.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/bbox/sensor.py b/homeassistant/components/bbox/sensor.py index 4e89788ddae..858ad6c6e47 100644 --- a/homeassistant/components/bbox/sensor.py +++ b/homeassistant/components/bbox/sensor.py @@ -1,4 +1,5 @@ """Support for Bbox Bouygues Modem Router.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/beewi_smartclim/sensor.py b/homeassistant/components/beewi_smartclim/sensor.py index 08f2410ee06..3aaf4daaa80 100644 --- a/homeassistant/components/beewi_smartclim/sensor.py +++ b/homeassistant/components/beewi_smartclim/sensor.py @@ -1,4 +1,5 @@ """Platform for beewi_smartclim integration.""" + from __future__ import annotations from beewi_smartclim import BeewiSmartClimPoller diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 06185489419..4fd99c309bc 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -1,4 +1,5 @@ """Component to interface with binary sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/binary_sensor/device_condition.py b/homeassistant/components/binary_sensor/device_condition.py index 81d2ebf26a2..bbd80959b12 100644 --- a/homeassistant/components/binary_sensor/device_condition.py +++ b/homeassistant/components/binary_sensor/device_condition.py @@ -1,4 +1,5 @@ """Implement device conditions for binary sensor.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/binary_sensor/significant_change.py b/homeassistant/components/binary_sensor/significant_change.py index 8421483ba0c..4801af1f54d 100644 --- a/homeassistant/components/binary_sensor/significant_change.py +++ b/homeassistant/components/binary_sensor/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Binary Sensor state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bitcoin/sensor.py b/homeassistant/components/bitcoin/sensor.py index 29a7957fde7..e003362ac7e 100644 --- a/homeassistant/components/bitcoin/sensor.py +++ b/homeassistant/components/bitcoin/sensor.py @@ -1,4 +1,5 @@ """Bitcoin information service that uses blockchain.com.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/bizkaibus/sensor.py b/homeassistant/components/bizkaibus/sensor.py index 7078de158e7..ff7d28b96c7 100644 --- a/homeassistant/components/bizkaibus/sensor.py +++ b/homeassistant/components/bizkaibus/sensor.py @@ -1,4 +1,5 @@ """Support for Bizkaibus, Biscay (Basque Country, Spain) Bus service.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/blackbird/media_player.py b/homeassistant/components/blackbird/media_player.py index 61dca6550c0..4006b12738f 100644 --- a/homeassistant/components/blackbird/media_player.py +++ b/homeassistant/components/blackbird/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with Monoprice Blackbird 4k 8x8 HDBaseT Matrix.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/blebox/button.py b/homeassistant/components/blebox/button.py index 93a5a4e0c5a..940fe7f8f6f 100644 --- a/homeassistant/components/blebox/button.py +++ b/homeassistant/components/blebox/button.py @@ -1,4 +1,5 @@ """BleBox button entities implementation.""" + from __future__ import annotations from blebox_uniapi.box import Box diff --git a/homeassistant/components/blebox/climate.py b/homeassistant/components/blebox/climate.py index 1350f1f29a2..24f036dcd49 100644 --- a/homeassistant/components/blebox/climate.py +++ b/homeassistant/components/blebox/climate.py @@ -1,4 +1,5 @@ """BleBox climate entity.""" + from datetime import timedelta from typing import Any diff --git a/homeassistant/components/blebox/config_flow.py b/homeassistant/components/blebox/config_flow.py index 86b6380ff9f..1f04f06a05a 100644 --- a/homeassistant/components/blebox/config_flow.py +++ b/homeassistant/components/blebox/config_flow.py @@ -1,4 +1,5 @@ """Config flow for BleBox devices integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/blebox/cover.py b/homeassistant/components/blebox/cover.py index 658a9bc30cc..f9e974991f5 100644 --- a/homeassistant/components/blebox/cover.py +++ b/homeassistant/components/blebox/cover.py @@ -1,4 +1,5 @@ """BleBox cover entity.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/blebox/helpers.py b/homeassistant/components/blebox/helpers.py index 82b8080b61d..8061fff5645 100644 --- a/homeassistant/components/blebox/helpers.py +++ b/homeassistant/components/blebox/helpers.py @@ -1,4 +1,5 @@ """Blebox helpers.""" + from __future__ import annotations import aiohttp diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py index 6446949cb89..1f994db7243 100644 --- a/homeassistant/components/blebox/light.py +++ b/homeassistant/components/blebox/light.py @@ -1,4 +1,5 @@ """BleBox light entities implementation.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py index 94edc32bc8c..a68b9f01cf2 100644 --- a/homeassistant/components/blebox/switch.py +++ b/homeassistant/components/blebox/switch.py @@ -1,4 +1,5 @@ """BleBox switch implementation.""" + from datetime import timedelta from typing import Any diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index e86d07c8780..d21994ecc8f 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -1,4 +1,5 @@ """Support for Blink Home Camera System.""" + from copy import deepcopy import logging diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index f2c01de4f18..b7dc50a5c51 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Blink Alarm Control Panel.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py index b2a23b0aa31..2f0a56a901c 100644 --- a/homeassistant/components/blink/binary_sensor.py +++ b/homeassistant/components/blink/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Blink system camera control.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index ff4fa6380a7..318bb18772a 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -1,4 +1,5 @@ """Support for Blink system camera.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/blink/config_flow.py b/homeassistant/components/blink/config_flow.py index c49ef67be98..1531728aa79 100644 --- a/homeassistant/components/blink/config_flow.py +++ b/homeassistant/components/blink/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Blink.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/blink/const.py b/homeassistant/components/blink/const.py index 7aa3d0d388e..a524d2c599a 100644 --- a/homeassistant/components/blink/const.py +++ b/homeassistant/components/blink/const.py @@ -1,4 +1,5 @@ """Constants for Blink.""" + from homeassistant.const import Platform DOMAIN = "blink" diff --git a/homeassistant/components/blink/coordinator.py b/homeassistant/components/blink/coordinator.py index aaf666208a6..e71ff4e449e 100644 --- a/homeassistant/components/blink/coordinator.py +++ b/homeassistant/components/blink/coordinator.py @@ -1,4 +1,5 @@ """Blink Coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/blink/diagnostics.py b/homeassistant/components/blink/diagnostics.py index 664d1421ac2..88ff2aff928 100644 --- a/homeassistant/components/blink/diagnostics.py +++ b/homeassistant/components/blink/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Blink.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index fb429e79dc8..8a807b9303e 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,4 +1,5 @@ """Support for Blink system camera sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/blink/services.py b/homeassistant/components/blink/services.py index 5c034cdb7c5..e01371c5c09 100644 --- a/homeassistant/components/blink/services.py +++ b/homeassistant/components/blink/services.py @@ -1,4 +1,5 @@ """Services for the Blink integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/blink/switch.py b/homeassistant/components/blink/switch.py index 2b25d1bce0c..1bfd257ecbe 100644 --- a/homeassistant/components/blink/switch.py +++ b/homeassistant/components/blink/switch.py @@ -1,4 +1,5 @@ """Support for Blink Motion detection switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/blinksticklight/light.py b/homeassistant/components/blinksticklight/light.py index 91d3d7d1b96..3e1f60e0f50 100644 --- a/homeassistant/components/blinksticklight/light.py +++ b/homeassistant/components/blinksticklight/light.py @@ -1,4 +1,5 @@ """Support for Blinkstick lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/blockchain/sensor.py b/homeassistant/components/blockchain/sensor.py index 6c65987ef57..dafd47bcb20 100644 --- a/homeassistant/components/blockchain/sensor.py +++ b/homeassistant/components/blockchain/sensor.py @@ -1,4 +1,5 @@ """Support for Blockchain.com sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 59e224b0b6b..c2a46baaeb3 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -1,4 +1,5 @@ """Support for BloomSky weather station.""" + from datetime import timedelta from http import HTTPStatus import logging diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index b99fdfe0c78..3582b186013 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -1,4 +1,5 @@ """Support the binary sensors of a BloomSky weather station.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/bloomsky/camera.py b/homeassistant/components/bloomsky/camera.py index 97c451ef178..f07dd1e9d14 100644 --- a/homeassistant/components/bloomsky/camera.py +++ b/homeassistant/components/bloomsky/camera.py @@ -1,4 +1,5 @@ """Support for a camera of a BloomSky weather station.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index 4361af9ad37..c6a74731d37 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -1,4 +1,5 @@ """Support the sensor of a BloomSky weather station.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/blue_current/__init__.py b/homeassistant/components/blue_current/__init__.py index 16b81c3c1e7..39c77c1bdfe 100644 --- a/homeassistant/components/blue_current/__init__.py +++ b/homeassistant/components/blue_current/__init__.py @@ -1,4 +1,5 @@ """The Blue Current integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/blue_current/config_flow.py b/homeassistant/components/blue_current/config_flow.py index 56980649c0a..66070094c29 100644 --- a/homeassistant/components/blue_current/config_flow.py +++ b/homeassistant/components/blue_current/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Blue Current integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/blue_current/entity.py b/homeassistant/components/blue_current/entity.py index c797fec08b0..547b2410000 100644 --- a/homeassistant/components/blue_current/entity.py +++ b/homeassistant/components/blue_current/entity.py @@ -1,4 +1,5 @@ """Entity representing a Blue Current charge point.""" + from abc import abstractmethod from homeassistant.const import ATTR_NAME diff --git a/homeassistant/components/blue_current/sensor.py b/homeassistant/components/blue_current/sensor.py index 02a40e09089..4d6c82ee530 100644 --- a/homeassistant/components/blue_current/sensor.py +++ b/homeassistant/components/blue_current/sensor.py @@ -1,4 +1,5 @@ """Support for Blue Current sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/bluemaestro/__init__.py b/homeassistant/components/bluemaestro/__init__.py index 45eebedcfb2..2b9a2ab15ec 100644 --- a/homeassistant/components/bluemaestro/__init__.py +++ b/homeassistant/components/bluemaestro/__init__.py @@ -1,4 +1,5 @@ """The BlueMaestro integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bluemaestro/config_flow.py b/homeassistant/components/bluemaestro/config_flow.py index b8bfec40f19..be83eafb2ce 100644 --- a/homeassistant/components/bluemaestro/config_flow.py +++ b/homeassistant/components/bluemaestro/config_flow.py @@ -1,4 +1,5 @@ """Config flow for bluemaestro ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bluemaestro/device.py b/homeassistant/components/bluemaestro/device.py index 19d955dd945..2d1a33347c3 100644 --- a/homeassistant/components/bluemaestro/device.py +++ b/homeassistant/components/bluemaestro/device.py @@ -1,4 +1,5 @@ """Support for BlueMaestro devices.""" + from __future__ import annotations from bluemaestro_ble import DeviceKey diff --git a/homeassistant/components/bluemaestro/sensor.py b/homeassistant/components/bluemaestro/sensor.py index dcd559a06ef..4024b8b3326 100644 --- a/homeassistant/components/bluemaestro/sensor.py +++ b/homeassistant/components/bluemaestro/sensor.py @@ -1,4 +1,5 @@ """Support for BlueMaestro sensors.""" + from __future__ import annotations from bluemaestro_ble import ( diff --git a/homeassistant/components/blueprint/__init__.py b/homeassistant/components/blueprint/__init__.py index 1fe1ad8e189..92d94708e0f 100644 --- a/homeassistant/components/blueprint/__init__.py +++ b/homeassistant/components/blueprint/__init__.py @@ -1,4 +1,5 @@ """The blueprint integration.""" + from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType diff --git a/homeassistant/components/blueprint/errors.py b/homeassistant/components/blueprint/errors.py index fe714542e0f..221279a39ac 100644 --- a/homeassistant/components/blueprint/errors.py +++ b/homeassistant/components/blueprint/errors.py @@ -1,4 +1,5 @@ """Blueprint errors.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py index 33fb87cc578..2475ccf8d14 100644 --- a/homeassistant/components/blueprint/models.py +++ b/homeassistant/components/blueprint/models.py @@ -1,4 +1,5 @@ """Blueprint models.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/blueprint/schemas.py b/homeassistant/components/blueprint/schemas.py index fd3aa967336..390bb1ddc80 100644 --- a/homeassistant/components/blueprint/schemas.py +++ b/homeassistant/components/blueprint/schemas.py @@ -1,4 +1,5 @@ """Schemas for the blueprint integration.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/blueprint/websocket_api.py b/homeassistant/components/blueprint/websocket_api.py index 1989f0f563c..98cc8131166 100644 --- a/homeassistant/components/blueprint/websocket_api.py +++ b/homeassistant/components/blueprint/websocket_api.py @@ -1,4 +1,5 @@ """Websocket API for blueprint.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 70c19b5fa6f..a054dadcbc9 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -1,4 +1,5 @@ """Support for Bluesound devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index c2f1724b340..5a59858093c 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -1,4 +1,5 @@ """The bluetooth integration.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/bluetooth/active_update_coordinator.py b/homeassistant/components/bluetooth/active_update_coordinator.py index cf8590079bc..4673e6adaae 100644 --- a/homeassistant/components/bluetooth/active_update_coordinator.py +++ b/homeassistant/components/bluetooth/active_update_coordinator.py @@ -2,6 +2,7 @@ Receives data from advertisements but can also poll. """ + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/bluetooth/active_update_processor.py b/homeassistant/components/bluetooth/active_update_processor.py index d0be6c61811..e028017dd31 100644 --- a/homeassistant/components/bluetooth/active_update_processor.py +++ b/homeassistant/components/bluetooth/active_update_processor.py @@ -2,6 +2,7 @@ Collects data from advertisements but can also poll. """ + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/bluetooth/api.py b/homeassistant/components/bluetooth/api.py index 29054a54e72..b1a6bc87728 100644 --- a/homeassistant/components/bluetooth/api.py +++ b/homeassistant/components/bluetooth/api.py @@ -2,6 +2,7 @@ These APIs are the only documented way to interact with the bluetooth integration. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/components/bluetooth/config_flow.py b/homeassistant/components/bluetooth/config_flow.py index a71c32fc3ca..2b5980fbcd6 100644 --- a/homeassistant/components/bluetooth/config_flow.py +++ b/homeassistant/components/bluetooth/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Bluetooth integration.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/bluetooth/const.py b/homeassistant/components/bluetooth/const.py index fa8efabcb1d..a3238befbb8 100644 --- a/homeassistant/components/bluetooth/const.py +++ b/homeassistant/components/bluetooth/const.py @@ -1,4 +1,5 @@ """Constants for the Bluetooth integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/bluetooth/diagnostics.py b/homeassistant/components/bluetooth/diagnostics.py index 612c51806dd..a45500265cf 100644 --- a/homeassistant/components/bluetooth/diagnostics.py +++ b/homeassistant/components/bluetooth/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for bluetooth.""" + from __future__ import annotations import platform diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py index 32589d822d3..2eb07c5133f 100644 --- a/homeassistant/components/bluetooth/manager.py +++ b/homeassistant/components/bluetooth/manager.py @@ -1,4 +1,5 @@ """The bluetooth integration.""" + from __future__ import annotations from collections.abc import Callable, Iterable diff --git a/homeassistant/components/bluetooth/match.py b/homeassistant/components/bluetooth/match.py index 2fd650d9580..6eba9b65cbb 100644 --- a/homeassistant/components/bluetooth/match.py +++ b/homeassistant/components/bluetooth/match.py @@ -1,4 +1,5 @@ """The bluetooth integration matchers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/bluetooth/models.py b/homeassistant/components/bluetooth/models.py index 001a47767a1..a14aaf1d379 100644 --- a/homeassistant/components/bluetooth/models.py +++ b/homeassistant/components/bluetooth/models.py @@ -1,4 +1,5 @@ """Models for bluetooth.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/bluetooth/passive_update_coordinator.py b/homeassistant/components/bluetooth/passive_update_coordinator.py index 0de1fadce30..81a67f6caef 100644 --- a/homeassistant/components/bluetooth/passive_update_coordinator.py +++ b/homeassistant/components/bluetooth/passive_update_coordinator.py @@ -1,4 +1,5 @@ """Passive update coordinator for the Bluetooth integration.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/bluetooth/passive_update_processor.py b/homeassistant/components/bluetooth/passive_update_processor.py index a92a5317ba4..b0b490a70f2 100644 --- a/homeassistant/components/bluetooth/passive_update_processor.py +++ b/homeassistant/components/bluetooth/passive_update_processor.py @@ -1,4 +1,5 @@ """Passive update processors for the Bluetooth integration.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/bluetooth/storage.py b/homeassistant/components/bluetooth/storage.py index 41354e95b2e..6b4c7695fd2 100644 --- a/homeassistant/components/bluetooth/storage.py +++ b/homeassistant/components/bluetooth/storage.py @@ -1,4 +1,5 @@ """Storage for remote scanners.""" + from __future__ import annotations from bluetooth_adapters import ( diff --git a/homeassistant/components/bluetooth/update_coordinator.py b/homeassistant/components/bluetooth/update_coordinator.py index 2d495a0659c..eb2f8c0cf82 100644 --- a/homeassistant/components/bluetooth/update_coordinator.py +++ b/homeassistant/components/bluetooth/update_coordinator.py @@ -1,4 +1,5 @@ """Update coordinator for the Bluetooth integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/bluetooth/util.py b/homeassistant/components/bluetooth/util.py index d531e46f911..166930d64f3 100644 --- a/homeassistant/components/bluetooth/util.py +++ b/homeassistant/components/bluetooth/util.py @@ -1,4 +1,5 @@ """The bluetooth integration utilities.""" + from __future__ import annotations from bluetooth_adapters import BluetoothAdapters diff --git a/homeassistant/components/bluetooth_adapters/__init__.py b/homeassistant/components/bluetooth_adapters/__init__.py index 3d5580aabf1..90593bf1018 100644 --- a/homeassistant/components/bluetooth_adapters/__init__.py +++ b/homeassistant/components/bluetooth_adapters/__init__.py @@ -1,4 +1,5 @@ """The Bluetooth Adapters integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index f85a9506d72..1a88e1c5fa3 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -1,4 +1,5 @@ """Tracking for bluetooth low energy devices.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/bluetooth_tracker/const.py b/homeassistant/components/bluetooth_tracker/const.py index 8257e5554ec..6be453be9ff 100644 --- a/homeassistant/components/bluetooth_tracker/const.py +++ b/homeassistant/components/bluetooth_tracker/const.py @@ -1,4 +1,5 @@ """Constants for the Bluetooth Tracker component.""" + from typing import Final DOMAIN: Final = "bluetooth_tracker" diff --git a/homeassistant/components/bluetooth_tracker/device_tracker.py b/homeassistant/components/bluetooth_tracker/device_tracker.py index 6fecc428c10..7cde6f848d5 100644 --- a/homeassistant/components/bluetooth_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_tracker/device_tracker.py @@ -1,4 +1,5 @@ """Tracking for bluetooth devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index 079563b1ad3..663003a5e4b 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -1,4 +1,5 @@ """Reads vehicle status from MyBMW portal.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index e7886d79cad..85a0cbf8812 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -1,4 +1,5 @@ """Reads vehicle status from BMW MyBMW portal.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/bmw_connected_drive/button.py b/homeassistant/components/bmw_connected_drive/button.py index 8ef0ac3d3ef..fe103f0e003 100644 --- a/homeassistant/components/bmw_connected_drive/button.py +++ b/homeassistant/components/bmw_connected_drive/button.py @@ -1,4 +1,5 @@ """Support for MyBMW button entities.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/bmw_connected_drive/config_flow.py b/homeassistant/components/bmw_connected_drive/config_flow.py index 3fbee2a79bb..fc274fc0f54 100644 --- a/homeassistant/components/bmw_connected_drive/config_flow.py +++ b/homeassistant/components/bmw_connected_drive/config_flow.py @@ -1,4 +1,5 @@ """Config flow for BMW ConnectedDrive integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/bmw_connected_drive/const.py b/homeassistant/components/bmw_connected_drive/const.py index 96ef152307d..49990977f71 100644 --- a/homeassistant/components/bmw_connected_drive/const.py +++ b/homeassistant/components/bmw_connected_drive/const.py @@ -1,4 +1,5 @@ """Const file for the MyBMW integration.""" + from homeassistant.const import UnitOfLength, UnitOfVolume DOMAIN = "bmw_connected_drive" diff --git a/homeassistant/components/bmw_connected_drive/coordinator.py b/homeassistant/components/bmw_connected_drive/coordinator.py index 4e811d48647..14875c54719 100644 --- a/homeassistant/components/bmw_connected_drive/coordinator.py +++ b/homeassistant/components/bmw_connected_drive/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for BMW.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index a97ed1e1092..d6846d0b88e 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -1,4 +1,5 @@ """Device tracker for MyBMW vehicles.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bmw_connected_drive/diagnostics.py b/homeassistant/components/bmw_connected_drive/diagnostics.py index c69d06d818f..c2bd4b6d24a 100644 --- a/homeassistant/components/bmw_connected_drive/diagnostics.py +++ b/homeassistant/components/bmw_connected_drive/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for the BMW Connected Drive integration.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 6608206a0ee..d7898f98bc8 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -1,4 +1,5 @@ """Support for BMW car locks with BMW ConnectedDrive.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bmw_connected_drive/notify.py b/homeassistant/components/bmw_connected_drive/notify.py index 4a9f7679dc4..84bc2d8459a 100644 --- a/homeassistant/components/bmw_connected_drive/notify.py +++ b/homeassistant/components/bmw_connected_drive/notify.py @@ -1,4 +1,5 @@ """Support for BMW notifications.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bmw_connected_drive/select.py b/homeassistant/components/bmw_connected_drive/select.py index 24172857e70..409002b48e9 100644 --- a/homeassistant/components/bmw_connected_drive/select.py +++ b/homeassistant/components/bmw_connected_drive/select.py @@ -1,4 +1,5 @@ """Select platform for BMW.""" + from collections.abc import Callable, Coroutine from dataclasses import dataclass import logging diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 27a5824a7d7..49842305af0 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -1,4 +1,5 @@ """Support for reading vehicle status from MyBMW portal.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 2e60512156f..9ecfedee570 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -1,4 +1,5 @@ """The Bond integration.""" + from http import HTTPStatus import logging from typing import Any diff --git a/homeassistant/components/bond/button.py b/homeassistant/components/bond/button.py index b90727100bd..a75e2ad74b8 100644 --- a/homeassistant/components/bond/button.py +++ b/homeassistant/components/bond/button.py @@ -1,4 +1,5 @@ """Support for bond buttons.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 210dd3b3c4e..45170a0404f 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Bond integration.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/bond/cover.py b/homeassistant/components/bond/cover.py index a41e188ed9d..06576277520 100644 --- a/homeassistant/components/bond/cover.py +++ b/homeassistant/components/bond/cover.py @@ -1,4 +1,5 @@ """Support for Bond covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bond/diagnostics.py b/homeassistant/components/bond/diagnostics.py index 53e8b5c8225..8b79f36dd0b 100644 --- a/homeassistant/components/bond/diagnostics.py +++ b/homeassistant/components/bond/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for bond.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bond/entity.py b/homeassistant/components/bond/entity.py index dd307547b81..02137d27b3d 100644 --- a/homeassistant/components/bond/entity.py +++ b/homeassistant/components/bond/entity.py @@ -1,4 +1,5 @@ """An abstract class common to all Bond entities.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 403e0ae01e6..1b7a06fcd37 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -1,4 +1,5 @@ """Support for Bond fans.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index c5816153c8d..9173916d00d 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -1,4 +1,5 @@ """Support for Bond lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bond/models.py b/homeassistant/components/bond/models.py index 0caa01af7a0..7564961ee78 100644 --- a/homeassistant/components/bond/models.py +++ b/homeassistant/components/bond/models.py @@ -1,4 +1,5 @@ """The bond integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/bond/switch.py b/homeassistant/components/bond/switch.py index 887532defd1..aa39f871c95 100644 --- a/homeassistant/components/bond/switch.py +++ b/homeassistant/components/bond/switch.py @@ -1,4 +1,5 @@ """Support for Bond generic devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bond/utils.py b/homeassistant/components/bond/utils.py index 60b9a7b492f..0a1067de709 100644 --- a/homeassistant/components/bond/utils.py +++ b/homeassistant/components/bond/utils.py @@ -1,4 +1,5 @@ """Reusable utilities for the Bond component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bosch_shc/binary_sensor.py b/homeassistant/components/bosch_shc/binary_sensor.py index c9969fcf415..9a016e47f9d 100644 --- a/homeassistant/components/bosch_shc/binary_sensor.py +++ b/homeassistant/components/bosch_shc/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for binarysensor integration.""" + from __future__ import annotations from boschshcpy import SHCBatteryDevice, SHCSession, SHCShutterContact diff --git a/homeassistant/components/bosch_shc/config_flow.py b/homeassistant/components/bosch_shc/config_flow.py index 28bf3a797b5..5483c080f39 100644 --- a/homeassistant/components/bosch_shc/config_flow.py +++ b/homeassistant/components/bosch_shc/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Bosch Smart Home Controller integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/bosch_shc/cover.py b/homeassistant/components/bosch_shc/cover.py index 8b2a2f65c12..c135cdfa186 100644 --- a/homeassistant/components/bosch_shc/cover.py +++ b/homeassistant/components/bosch_shc/cover.py @@ -1,4 +1,5 @@ """Platform for cover integration.""" + from typing import Any from boschshcpy import SHCSession, SHCShutterControl diff --git a/homeassistant/components/bosch_shc/entity.py b/homeassistant/components/bosch_shc/entity.py index 8c26d2e6d5a..b7697191d27 100644 --- a/homeassistant/components/bosch_shc/entity.py +++ b/homeassistant/components/bosch_shc/entity.py @@ -1,4 +1,5 @@ """Bosch Smart Home Controller base entity.""" + from __future__ import annotations from boschshcpy import SHCDevice, SHCIntrusionSystem diff --git a/homeassistant/components/bosch_shc/sensor.py b/homeassistant/components/bosch_shc/sensor.py index c9c194bdc08..14da3a4b92b 100644 --- a/homeassistant/components/bosch_shc/sensor.py +++ b/homeassistant/components/bosch_shc/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from boschshcpy import SHCSession diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index a6eee973206..6f815d8f66c 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -1,4 +1,5 @@ """Platform for switch integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/braviatv/__init__.py b/homeassistant/components/braviatv/__init__.py index ecf119c8a3d..9027a8372ab 100644 --- a/homeassistant/components/braviatv/__init__.py +++ b/homeassistant/components/braviatv/__init__.py @@ -1,4 +1,5 @@ """The Bravia TV integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/braviatv/button.py b/homeassistant/components/braviatv/button.py index 02f66167c61..0b502a3773b 100644 --- a/homeassistant/components/braviatv/button.py +++ b/homeassistant/components/braviatv/button.py @@ -1,4 +1,5 @@ """Button support for Bravia TV.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index 6fc5c07130b..b3ad55dbb7d 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Bravia TV integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/braviatv/const.py b/homeassistant/components/braviatv/const.py index aff02aa9e8b..aadd851fc7f 100644 --- a/homeassistant/components/braviatv/const.py +++ b/homeassistant/components/braviatv/const.py @@ -1,4 +1,5 @@ """Constants for Bravia TV integration.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/braviatv/coordinator.py b/homeassistant/components/braviatv/coordinator.py index 72d2107271f..15e6744ceb8 100644 --- a/homeassistant/components/braviatv/coordinator.py +++ b/homeassistant/components/braviatv/coordinator.py @@ -1,4 +1,5 @@ """Update coordinator for Bravia TV integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine, Iterable diff --git a/homeassistant/components/braviatv/diagnostics.py b/homeassistant/components/braviatv/diagnostics.py index f1822b545e9..917572ffcca 100644 --- a/homeassistant/components/braviatv/diagnostics.py +++ b/homeassistant/components/braviatv/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for BraviaTV.""" + from typing import Any from homeassistant.components.diagnostics import async_redact_data diff --git a/homeassistant/components/braviatv/entity.py b/homeassistant/components/braviatv/entity.py index 0f941d05e75..ac08543b875 100644 --- a/homeassistant/components/braviatv/entity.py +++ b/homeassistant/components/braviatv/entity.py @@ -1,4 +1,5 @@ """A entity class for Bravia TV integration.""" + from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index 111f08e441a..ea4f3cce4a8 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -1,4 +1,5 @@ """Media player support for Bravia TV integration.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/braviatv/remote.py b/homeassistant/components/braviatv/remote.py index f9e3f464dcb..01d1bb6378c 100644 --- a/homeassistant/components/braviatv/remote.py +++ b/homeassistant/components/braviatv/remote.py @@ -1,4 +1,5 @@ """Remote control support for Bravia TV.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/bring/__init__.py b/homeassistant/components/bring/__init__.py index aaf11130b8d..7c300a0e013 100644 --- a/homeassistant/components/bring/__init__.py +++ b/homeassistant/components/bring/__init__.py @@ -1,4 +1,5 @@ """The Bring! integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bring/config_flow.py b/homeassistant/components/bring/config_flow.py index de4e278407b..0b423f5af36 100644 --- a/homeassistant/components/bring/config_flow.py +++ b/homeassistant/components/bring/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Bring! integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bring/coordinator.py b/homeassistant/components/bring/coordinator.py index 550c589aa4e..057e7549503 100644 --- a/homeassistant/components/bring/coordinator.py +++ b/homeassistant/components/bring/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Bring! integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/bring/todo.py b/homeassistant/components/bring/todo.py index 5d3fc5bbf68..a1988e667b5 100644 --- a/homeassistant/components/bring/todo.py +++ b/homeassistant/components/bring/todo.py @@ -1,4 +1,5 @@ """Todo platform for the Bring! integration.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py index e6a769fd2c4..8dd6cee82cb 100644 --- a/homeassistant/components/broadlink/__init__.py +++ b/homeassistant/components/broadlink/__init__.py @@ -1,4 +1,5 @@ """The Broadlink integration.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/broadlink/climate.py b/homeassistant/components/broadlink/climate.py index be0eaf78f26..0573c342490 100644 --- a/homeassistant/components/broadlink/climate.py +++ b/homeassistant/components/broadlink/climate.py @@ -1,4 +1,5 @@ """Support for Broadlink climate devices.""" + from typing import Any from homeassistant.components.climate import ( diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index e3d6dda3488..89d540a27fc 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Broadlink devices.""" + from collections.abc import Mapping import errno from functools import partial diff --git a/homeassistant/components/broadlink/const.py b/homeassistant/components/broadlink/const.py index 2b9e8787a43..91d4358a077 100644 --- a/homeassistant/components/broadlink/const.py +++ b/homeassistant/components/broadlink/const.py @@ -1,4 +1,5 @@ """Constants.""" + from homeassistant.const import Platform DOMAIN = "broadlink" diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index 69e1161a65c..8f5cf43ad7e 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -1,4 +1,5 @@ """Support for Broadlink devices.""" + from contextlib import suppress from functools import partial import logging diff --git a/homeassistant/components/broadlink/helpers.py b/homeassistant/components/broadlink/helpers.py index bec61ba5bbd..43c2715531c 100644 --- a/homeassistant/components/broadlink/helpers.py +++ b/homeassistant/components/broadlink/helpers.py @@ -1,4 +1,5 @@ """Helper functions for the Broadlink integration.""" + from base64 import b64decode from homeassistant import config_entries diff --git a/homeassistant/components/broadlink/sensor.py b/homeassistant/components/broadlink/sensor.py index 747418e1e79..b7ae71ff803 100644 --- a/homeassistant/components/broadlink/sensor.py +++ b/homeassistant/components/broadlink/sensor.py @@ -1,4 +1,5 @@ """Support for Broadlink sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index b8744865898..f61e726b1d5 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -1,4 +1,5 @@ """Support for Broadlink switches.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/broadlink/updater.py b/homeassistant/components/broadlink/updater.py index 10ac4df4bb8..20b241b0d89 100644 --- a/homeassistant/components/broadlink/updater.py +++ b/homeassistant/components/broadlink/updater.py @@ -1,4 +1,5 @@ """Support for fetching data from Broadlink devices.""" + from abc import ABC, abstractmethod from datetime import timedelta import logging diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index 32fee44de99..d52c7587742 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -1,4 +1,5 @@ """The Brother component.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 13668224de3..9660b02c453 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Brother Printer.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/brother/const.py b/homeassistant/components/brother/const.py index 21f535ec1e4..fda815ceee5 100644 --- a/homeassistant/components/brother/const.py +++ b/homeassistant/components/brother/const.py @@ -1,4 +1,5 @@ """Constants for Brother integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/brother/diagnostics.py b/homeassistant/components/brother/diagnostics.py index 4733431f8e2..a4afb385f8d 100644 --- a/homeassistant/components/brother/diagnostics.py +++ b/homeassistant/components/brother/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Brother.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index c940e03bc0a..12b5bd0fb59 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -1,4 +1,5 @@ """Support for the Brother service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/brother/utils.py b/homeassistant/components/brother/utils.py index 47b7ae31a67..d7636cdd2e8 100644 --- a/homeassistant/components/brother/utils.py +++ b/homeassistant/components/brother/utils.py @@ -1,4 +1,5 @@ """Brother helpers functions.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/brottsplatskartan/__init__.py b/homeassistant/components/brottsplatskartan/__init__.py index 14e6e383e85..486bee5bcd5 100644 --- a/homeassistant/components/brottsplatskartan/__init__.py +++ b/homeassistant/components/brottsplatskartan/__init__.py @@ -1,4 +1,5 @@ """The brottsplatskartan component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/brottsplatskartan/config_flow.py b/homeassistant/components/brottsplatskartan/config_flow.py index 070708ee379..ef35b3bd4f1 100644 --- a/homeassistant/components/brottsplatskartan/config_flow.py +++ b/homeassistant/components/brottsplatskartan/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Brottsplatskartan integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index b30b31be985..750b99abb67 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Brottsplatskartan information.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/brunt/__init__.py b/homeassistant/components/brunt/__init__.py index 660c43f1004..ec3ecd0ce6c 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -1,4 +1,5 @@ """The brunt component.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/brunt/config_flow.py b/homeassistant/components/brunt/config_flow.py index 8a9cf13a786..789a5a48bd9 100644 --- a/homeassistant/components/brunt/config_flow.py +++ b/homeassistant/components/brunt/config_flow.py @@ -1,4 +1,5 @@ """Config flow for brunt integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/brunt/const.py b/homeassistant/components/brunt/const.py index cc85ac9a415..4c246d28d64 100644 --- a/homeassistant/components/brunt/const.py +++ b/homeassistant/components/brunt/const.py @@ -1,4 +1,5 @@ """Constants for Brunt.""" + from datetime import timedelta from homeassistant.const import Platform diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index 1bde667a237..519885fe542 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -1,4 +1,5 @@ """Support for Brunt Blind Engine covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bsblan/climate.py b/homeassistant/components/bsblan/climate.py index 1be595bf1cc..eaa412978c0 100644 --- a/homeassistant/components/bsblan/climate.py +++ b/homeassistant/components/bsblan/climate.py @@ -1,4 +1,5 @@ """BSBLAN platform to control a compatible Climate Device.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index 8935bff26a3..9732f0a77a9 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -1,4 +1,5 @@ """Config flow for BSB-Lan integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bsblan/const.py b/homeassistant/components/bsblan/const.py index 0de9a29a27b..5bca20cb4d4 100644 --- a/homeassistant/components/bsblan/const.py +++ b/homeassistant/components/bsblan/const.py @@ -1,4 +1,5 @@ """Constants for the BSB-Lan integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/bsblan/coordinator.py b/homeassistant/components/bsblan/coordinator.py index 15eff37e6db..864daacc562 100644 --- a/homeassistant/components/bsblan/coordinator.py +++ b/homeassistant/components/bsblan/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the BSB-Lan integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/bsblan/diagnostics.py b/homeassistant/components/bsblan/diagnostics.py index 91d959ea0e2..0bceed0bf23 100644 --- a/homeassistant/components/bsblan/diagnostics.py +++ b/homeassistant/components/bsblan/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for BSBLan.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bsblan/entity.py b/homeassistant/components/bsblan/entity.py index 3c7f41ce34d..a69c4d2217e 100644 --- a/homeassistant/components/bsblan/entity.py +++ b/homeassistant/components/bsblan/entity.py @@ -1,4 +1,5 @@ """Base entity for the BSBLAN integration.""" + from __future__ import annotations from bsblan import BSBLAN, Device, Info, StaticState diff --git a/homeassistant/components/bt_home_hub_5/device_tracker.py b/homeassistant/components/bt_home_hub_5/device_tracker.py index 0ffa3bc699b..8706a04e7ad 100644 --- a/homeassistant/components/bt_home_hub_5/device_tracker.py +++ b/homeassistant/components/bt_home_hub_5/device_tracker.py @@ -1,4 +1,5 @@ """Support for BT Home Hub 5.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bt_smarthub/device_tracker.py b/homeassistant/components/bt_smarthub/device_tracker.py index 65aa1bd6a61..8b5411e2014 100644 --- a/homeassistant/components/bt_smarthub/device_tracker.py +++ b/homeassistant/components/bt_smarthub/device_tracker.py @@ -1,4 +1,5 @@ """Support for BT Smart Hub (Sometimes referred to as BT Home Hub 6).""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/bthome/__init__.py b/homeassistant/components/bthome/__init__.py index 0031f09bb81..d677bd28260 100644 --- a/homeassistant/components/bthome/__init__.py +++ b/homeassistant/components/bthome/__init__.py @@ -1,4 +1,5 @@ """The BTHome Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/bthome/binary_sensor.py b/homeassistant/components/bthome/binary_sensor.py index 02a226d1f7c..6de9506c54b 100644 --- a/homeassistant/components/bthome/binary_sensor.py +++ b/homeassistant/components/bthome/binary_sensor.py @@ -1,4 +1,5 @@ """Support for BTHome binary sensors.""" + from __future__ import annotations from bthome_ble import ( diff --git a/homeassistant/components/bthome/config_flow.py b/homeassistant/components/bthome/config_flow.py index c26e1aae4e9..5a3d90f1355 100644 --- a/homeassistant/components/bthome/config_flow.py +++ b/homeassistant/components/bthome/config_flow.py @@ -1,4 +1,5 @@ """Config flow for BTHome Bluetooth integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/bthome/const.py b/homeassistant/components/bthome/const.py index 780833bf92e..3e7deac9303 100644 --- a/homeassistant/components/bthome/const.py +++ b/homeassistant/components/bthome/const.py @@ -1,4 +1,5 @@ """Constants for the BTHome Bluetooth integration.""" + from __future__ import annotations from typing import Final, TypedDict diff --git a/homeassistant/components/bthome/coordinator.py b/homeassistant/components/bthome/coordinator.py index 837ad58b7c2..0abbf20d655 100644 --- a/homeassistant/components/bthome/coordinator.py +++ b/homeassistant/components/bthome/coordinator.py @@ -1,4 +1,5 @@ """The BTHome Bluetooth integration.""" + from collections.abc import Callable from logging import Logger from typing import Any diff --git a/homeassistant/components/bthome/device.py b/homeassistant/components/bthome/device.py index eecd8161d6c..1afe558db42 100644 --- a/homeassistant/components/bthome/device.py +++ b/homeassistant/components/bthome/device.py @@ -1,4 +1,5 @@ """Support for BTHome Bluetooth devices.""" + from __future__ import annotations from bthome_ble import DeviceKey diff --git a/homeassistant/components/bthome/device_trigger.py b/homeassistant/components/bthome/device_trigger.py index 834b08ad39d..c49664b1146 100644 --- a/homeassistant/components/bthome/device_trigger.py +++ b/homeassistant/components/bthome/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for BTHome BLE.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/bthome/event.py b/homeassistant/components/bthome/event.py index 39ad66d1d13..a0f59c0ddb7 100644 --- a/homeassistant/components/bthome/event.py +++ b/homeassistant/components/bthome/event.py @@ -1,4 +1,5 @@ """Support for bthome event entities.""" + from __future__ import annotations from dataclasses import replace diff --git a/homeassistant/components/bthome/logbook.py b/homeassistant/components/bthome/logbook.py index 158253ec8a7..475fa84fb76 100644 --- a/homeassistant/components/bthome/logbook.py +++ b/homeassistant/components/bthome/logbook.py @@ -1,4 +1,5 @@ """Describe bthome logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/bthome/sensor.py b/homeassistant/components/bthome/sensor.py index 17f8f6c7a3c..c3848ac976f 100644 --- a/homeassistant/components/bthome/sensor.py +++ b/homeassistant/components/bthome/sensor.py @@ -1,4 +1,5 @@ """Support for BTHome sensors.""" + from __future__ import annotations from bthome_ble import SensorDeviceClass as BTHomeSensorDeviceClass, SensorUpdate, Units diff --git a/homeassistant/components/buienradar/__init__.py b/homeassistant/components/buienradar/__init__.py index e259dbac692..3bf593b2dab 100644 --- a/homeassistant/components/buienradar/__init__.py +++ b/homeassistant/components/buienradar/__init__.py @@ -1,4 +1,5 @@ """The buienradar integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/buienradar/camera.py b/homeassistant/components/buienradar/camera.py index ba62cbfbb19..72bf6b7a3eb 100644 --- a/homeassistant/components/buienradar/camera.py +++ b/homeassistant/components/buienradar/camera.py @@ -1,4 +1,5 @@ """Provide animated GIF loops of Buienradar imagery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/buienradar/config_flow.py b/homeassistant/components/buienradar/config_flow.py index db9e6b02614..45ad9028eb0 100644 --- a/homeassistant/components/buienradar/config_flow.py +++ b/homeassistant/components/buienradar/config_flow.py @@ -1,4 +1,5 @@ """Config flow for buienradar integration.""" + from __future__ import annotations import copy diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index fe3ce3164fe..a52fca25c87 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -1,4 +1,5 @@ """Support for Buienradar.nl weather service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/buienradar/util.py b/homeassistant/components/buienradar/util.py index 426f982bafc..b641644cebe 100644 --- a/homeassistant/components/buienradar/util.py +++ b/homeassistant/components/buienradar/util.py @@ -1,4 +1,5 @@ """Shared utilities for different supported platforms.""" + from asyncio import timeout from datetime import datetime, timedelta from http import HTTPStatus diff --git a/homeassistant/components/button/__init__.py b/homeassistant/components/button/__init__.py index 0acc5b63339..0385e6b7f98 100644 --- a/homeassistant/components/button/__init__.py +++ b/homeassistant/components/button/__init__.py @@ -1,4 +1,5 @@ """Component to pressing a button as platforms.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/button/device_action.py b/homeassistant/components/button/device_action.py index 338b11e765b..f4db7b619f8 100644 --- a/homeassistant/components/button/device_action.py +++ b/homeassistant/components/button/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Button.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/button/device_trigger.py b/homeassistant/components/button/device_trigger.py index 1b206337f33..f1028a0ca6a 100644 --- a/homeassistant/components/button/device_trigger.py +++ b/homeassistant/components/button/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for Button.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index b2114dfc829..b9f967d1a08 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -1,4 +1,5 @@ """Support for WebDav Calendar.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/caldav/todo.py b/homeassistant/components/caldav/todo.py index 90380805c31..e8cd4fc9334 100644 --- a/homeassistant/components/caldav/todo.py +++ b/homeassistant/components/caldav/todo.py @@ -1,4 +1,5 @@ """CalDAV todo platform.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index aff33dd9c31..87f6efa49c3 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -1,4 +1,5 @@ """Support for Calendar event device sensors.""" + from __future__ import annotations from collections.abc import Callable, Iterable diff --git a/homeassistant/components/calendar/trigger.py b/homeassistant/components/calendar/trigger.py index e4fe5d22efd..844232c4b22 100644 --- a/homeassistant/components/calendar/trigger.py +++ b/homeassistant/components/calendar/trigger.py @@ -1,4 +1,5 @@ """Offer calendar automation rules.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index ff4687dd493..c91c8c403ca 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -1,4 +1,5 @@ """Component to interface with cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/camera/const.py b/homeassistant/components/camera/const.py index 09c4c7c1fb2..ad863f374d1 100644 --- a/homeassistant/components/camera/const.py +++ b/homeassistant/components/camera/const.py @@ -1,4 +1,5 @@ """Constants for Camera component.""" + from enum import StrEnum from functools import partial from typing import Final diff --git a/homeassistant/components/camera/img_util.py b/homeassistant/components/camera/img_util.py index e41e43c3a3c..bf07d6a2f1f 100644 --- a/homeassistant/components/camera/img_util.py +++ b/homeassistant/components/camera/img_util.py @@ -1,4 +1,5 @@ """Image processing for cameras.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index 3c9a386f958..cf896ee0456 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -1,4 +1,5 @@ """Expose cameras as media sources.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/camera/prefs.py b/homeassistant/components/camera/prefs.py index 7f3f142378a..2eccaf500e1 100644 --- a/homeassistant/components/camera/prefs.py +++ b/homeassistant/components/camera/prefs.py @@ -1,4 +1,5 @@ """Preference management for camera component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/camera/significant_change.py b/homeassistant/components/camera/significant_change.py index 4fc175b0723..5240e16376c 100644 --- a/homeassistant/components/camera/significant_change.py +++ b/homeassistant/components/camera/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Camera state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index bc360f99581..60ce50484d8 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -1,4 +1,5 @@ """Support for Canary devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index f668da25e2e..445579b9e4a 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Canary alarm.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index af78dceca23..f1aac2b17a0 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -1,4 +1,5 @@ """Support for Canary camera.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/canary/config_flow.py b/homeassistant/components/canary/config_flow.py index 22bc26ee0f4..f586a7e4e85 100644 --- a/homeassistant/components/canary/config_flow.py +++ b/homeassistant/components/canary/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Canary.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/canary/coordinator.py b/homeassistant/components/canary/coordinator.py index 1b47d6d70b7..534ca4df9fb 100644 --- a/homeassistant/components/canary/coordinator.py +++ b/homeassistant/components/canary/coordinator.py @@ -1,4 +1,5 @@ """Provides the Canary DataUpdateCoordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/canary/model.py b/homeassistant/components/canary/model.py index 4ed868a7e60..261e59b8cfa 100644 --- a/homeassistant/components/canary/model.py +++ b/homeassistant/components/canary/model.py @@ -1,4 +1,5 @@ """Constants for the Canary integration.""" + from __future__ import annotations from collections.abc import ValuesView diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index bdba9d4f130..1fcce204d3c 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -1,4 +1,5 @@ """Support for Canary sensors.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 8c574e0792b..28df9e4d7ef 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,4 +1,5 @@ """Component to embed Google Cast.""" + from __future__ import annotations from typing import Protocol diff --git a/homeassistant/components/cast/config_flow.py b/homeassistant/components/cast/config_flow.py index 6a6bb667b82..6ccd7be19c3 100644 --- a/homeassistant/components/cast/config_flow.py +++ b/homeassistant/components/cast/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Cast.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cast/const.py b/homeassistant/components/cast/const.py index f05c2c4c143..c57b686143d 100644 --- a/homeassistant/components/cast/const.py +++ b/homeassistant/components/cast/const.py @@ -1,4 +1,5 @@ """Consts for Cast integration.""" + from __future__ import annotations from typing import TYPE_CHECKING, TypedDict diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index bfe0bc70d79..eff6bf18e8b 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -1,4 +1,5 @@ """Helpers to deal with Cast devices.""" + from __future__ import annotations import configparser diff --git a/homeassistant/components/cast/home_assistant_cast.py b/homeassistant/components/cast/home_assistant_cast.py index f7518b9519a..5db37519bdf 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -1,4 +1,5 @@ """Home Assistant Cast integration for Cast.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index b2893a54310..9d041cb7cfb 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -1,4 +1,5 @@ """Provide functionality to interact with Cast devices on the network.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ccm15/__init__.py b/homeassistant/components/ccm15/__init__.py index ae48394c732..a35568047ad 100644 --- a/homeassistant/components/ccm15/__init__.py +++ b/homeassistant/components/ccm15/__init__.py @@ -1,4 +1,5 @@ """The Midea ccm15 AC Controller integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/ccm15/config_flow.py b/homeassistant/components/ccm15/config_flow.py index e4ebb758cd8..f115aa8f6e1 100644 --- a/homeassistant/components/ccm15/config_flow.py +++ b/homeassistant/components/ccm15/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Midea ccm15 AC Controller integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ccm15/diagnostics.py b/homeassistant/components/ccm15/diagnostics.py index b4a3c80f319..08cc239e972 100644 --- a/homeassistant/components/ccm15/diagnostics.py +++ b/homeassistant/components/ccm15/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for CCM15.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cert_expiry/__init__.py b/homeassistant/components/cert_expiry/__init__.py index d46cecc7edb..717a55b2027 100644 --- a/homeassistant/components/cert_expiry/__init__.py +++ b/homeassistant/components/cert_expiry/__init__.py @@ -1,4 +1,5 @@ """The cert_expiry component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/cert_expiry/config_flow.py b/homeassistant/components/cert_expiry/config_flow.py index 2201bf2714f..60863523553 100644 --- a/homeassistant/components/cert_expiry/config_flow.py +++ b/homeassistant/components/cert_expiry/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Cert Expiry platform.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/cert_expiry/coordinator.py b/homeassistant/components/cert_expiry/coordinator.py index abb0b4ca727..80c91f1d890 100644 --- a/homeassistant/components/cert_expiry/coordinator.py +++ b/homeassistant/components/cert_expiry/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for cert_expiry coordinator.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/cert_expiry/errors.py b/homeassistant/components/cert_expiry/errors.py index a3b73c84f2a..25a1a9a8358 100644 --- a/homeassistant/components/cert_expiry/errors.py +++ b/homeassistant/components/cert_expiry/errors.py @@ -1,4 +1,5 @@ """Errors for the cert_expiry integration.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 3e171006bdc..6a55e630a35 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -1,4 +1,5 @@ """Counter for the days until an HTTPS (TLS) certificate will expire.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/channels/media_player.py b/homeassistant/components/channels/media_player.py index a834e9010ce..002ec8d4efb 100644 --- a/homeassistant/components/channels/media_player.py +++ b/homeassistant/components/channels/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with an instance of getchannels.com.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/circuit/notify.py b/homeassistant/components/circuit/notify.py index 836c4118df0..23884ebd9be 100644 --- a/homeassistant/components/circuit/notify.py +++ b/homeassistant/components/circuit/notify.py @@ -1,4 +1,5 @@ """Unify Circuit platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/cisco_ios/device_tracker.py b/homeassistant/components/cisco_ios/device_tracker.py index 1424d41006d..8a21b64cb9f 100644 --- a/homeassistant/components/cisco_ios/device_tracker.py +++ b/homeassistant/components/cisco_ios/device_tracker.py @@ -1,4 +1,5 @@ """Support for Cisco IOS Routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/cisco_mobility_express/device_tracker.py b/homeassistant/components/cisco_mobility_express/device_tracker.py index a5ca469d101..c156f43942e 100644 --- a/homeassistant/components/cisco_mobility_express/device_tracker.py +++ b/homeassistant/components/cisco_mobility_express/device_tracker.py @@ -1,4 +1,5 @@ """Support for Cisco Mobility Express.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/cisco_webex_teams/notify.py b/homeassistant/components/cisco_webex_teams/notify.py index d2c75d78390..60cb4a581c2 100644 --- a/homeassistant/components/cisco_webex_teams/notify.py +++ b/homeassistant/components/cisco_webex_teams/notify.py @@ -1,4 +1,5 @@ """Cisco Webex Teams notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/citybikes/sensor.py b/homeassistant/components/citybikes/sensor.py index fc49331c1b7..0cf27c20fa6 100644 --- a/homeassistant/components/citybikes/sensor.py +++ b/homeassistant/components/citybikes/sensor.py @@ -1,4 +1,5 @@ """Sensor for the CityBikes data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/clementine/media_player.py b/homeassistant/components/clementine/media_player.py index 770f19e9970..84052aa64b9 100644 --- a/homeassistant/components/clementine/media_player.py +++ b/homeassistant/components/clementine/media_player.py @@ -1,4 +1,5 @@ """Support for Clementine Music Player as media player.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/clickatell/notify.py b/homeassistant/components/clickatell/notify.py index 8422f7295b3..70170217af2 100644 --- a/homeassistant/components/clickatell/notify.py +++ b/homeassistant/components/clickatell/notify.py @@ -1,4 +1,5 @@ """Clickatell platform for notify component.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/clicksend/notify.py b/homeassistant/components/clicksend/notify.py index 36ac21d8dd3..44954211748 100644 --- a/homeassistant/components/clicksend/notify.py +++ b/homeassistant/components/clicksend/notify.py @@ -1,4 +1,5 @@ """Clicksend platform for notify component.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/clicksend_tts/notify.py b/homeassistant/components/clicksend_tts/notify.py index 8eb3782415e..aeda1b26162 100644 --- a/homeassistant/components/clicksend_tts/notify.py +++ b/homeassistant/components/clicksend_tts/notify.py @@ -1,4 +1,5 @@ """clicksend_tts platform for notify component.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 7e3cb027506..c4faed875e9 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -1,4 +1,5 @@ """Provides functionality to interact with climate devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index a920884c252..84f166b752e 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Climate.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 78f358db32e..1becbf84915 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -1,4 +1,5 @@ """Provide the device automations for Climate.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index 0afd2485517..30c282344a4 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Climate.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/climate/reproduce_state.py b/homeassistant/components/climate/reproduce_state.py index e5fb5d6004b..110a8579ece 100644 --- a/homeassistant/components/climate/reproduce_state.py +++ b/homeassistant/components/climate/reproduce_state.py @@ -1,4 +1,5 @@ """Module that groups code required to handle state restore for component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/climate/significant_change.py b/homeassistant/components/climate/significant_change.py index 7198153f9af..0c4cdd4ac6a 100644 --- a/homeassistant/components/climate/significant_change.py +++ b/homeassistant/components/climate/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Climate state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 5a393b35961..aefab869955 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -1,4 +1,5 @@ """Component to integrate the Home Assistant cloud.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/account_link.py b/homeassistant/components/cloud/account_link.py index f1e5d1a6903..df2789663c0 100644 --- a/homeassistant/components/cloud/account_link.py +++ b/homeassistant/components/cloud/account_link.py @@ -1,4 +1,5 @@ """Account linking via the cloud.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 415f2415095..12f2b04d856 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -1,4 +1,5 @@ """Alexa configuration for Home Assistant Cloud.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/binary_sensor.py b/homeassistant/components/cloud/binary_sensor.py index d56896dd7b1..0693a8285ce 100644 --- a/homeassistant/components/cloud/binary_sensor.py +++ b/homeassistant/components/cloud/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Home Assistant Cloud binary sensors.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/client.py b/homeassistant/components/cloud/client.py index e569602f944..01c8de77156 100644 --- a/homeassistant/components/cloud/client.py +++ b/homeassistant/components/cloud/client.py @@ -1,4 +1,5 @@ """Interface implementation for cloud client.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/config_flow.py b/homeassistant/components/cloud/config_flow.py index 0cf4b941680..932291c2bfa 100644 --- a/homeassistant/components/cloud/config_flow.py +++ b/homeassistant/components/cloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Cloud integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index f704fb61f69..b134d67403a 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -1,4 +1,5 @@ """Constants for the cloud component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index bda2412b476..c7ac0acd986 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -1,4 +1,5 @@ """Google config for Cloud.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index f5cbb287ef1..87e5d00ff8f 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -1,4 +1,5 @@ """The HTTP api to control the cloud integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 010a9697f26..7c27aa0f130 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -1,4 +1,5 @@ """Preference management for cloud.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/cloud/repairs.py b/homeassistant/components/cloud/repairs.py index f7368731d92..4dd17e1609e 100644 --- a/homeassistant/components/cloud/repairs.py +++ b/homeassistant/components/cloud/repairs.py @@ -1,4 +1,5 @@ """Repairs implementation for the cloud integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/stt.py b/homeassistant/components/cloud/stt.py index 3368f25f94a..d718cc5201e 100644 --- a/homeassistant/components/cloud/stt.py +++ b/homeassistant/components/cloud/stt.py @@ -1,4 +1,5 @@ """Support for the cloud for speech to text service.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/subscription.py b/homeassistant/components/cloud/subscription.py index 63b57d2fa3d..dc6679a6e40 100644 --- a/homeassistant/components/cloud/subscription.py +++ b/homeassistant/components/cloud/subscription.py @@ -1,4 +1,5 @@ """Subscription information.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloud/system_health.py b/homeassistant/components/cloud/system_health.py index d149e13c996..866626f4c79 100644 --- a/homeassistant/components/cloud/system_health.py +++ b/homeassistant/components/cloud/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from typing import Any from hass_nabucasa import Cloud diff --git a/homeassistant/components/cloud/tts.py b/homeassistant/components/cloud/tts.py index 59ae5b22214..baaec15ac57 100644 --- a/homeassistant/components/cloud/tts.py +++ b/homeassistant/components/cloud/tts.py @@ -1,4 +1,5 @@ """Support for the cloud for text-to-speech service.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloudflare/__init__.py b/homeassistant/components/cloudflare/__init__.py index d4c6775c6b9..5934e43f8a2 100644 --- a/homeassistant/components/cloudflare/__init__.py +++ b/homeassistant/components/cloudflare/__init__.py @@ -1,4 +1,5 @@ """Update the IP addresses of your Cloudflare DNS records.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cloudflare/config_flow.py b/homeassistant/components/cloudflare/config_flow.py index e92ba43c503..f4becf12067 100644 --- a/homeassistant/components/cloudflare/config_flow.py +++ b/homeassistant/components/cloudflare/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Cloudflare integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index 65bfef3a0cb..ca9ad8f8489 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -1,4 +1,5 @@ """Support for interacting with and controlling the cmus music player.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/co2signal/__init__.py b/homeassistant/components/co2signal/__init__.py index 028d37a73c5..087b3148ea7 100644 --- a/homeassistant/components/co2signal/__init__.py +++ b/homeassistant/components/co2signal/__init__.py @@ -1,4 +1,5 @@ """The CO2 Signal integration.""" + from __future__ import annotations from aioelectricitymaps import ElectricityMaps diff --git a/homeassistant/components/co2signal/config_flow.py b/homeassistant/components/co2signal/config_flow.py index 37f5edaa227..bf5d645638f 100644 --- a/homeassistant/components/co2signal/config_flow.py +++ b/homeassistant/components/co2signal/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Co2signal integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/co2signal/coordinator.py b/homeassistant/components/co2signal/coordinator.py index b06bee38bc4..475ebd1225d 100644 --- a/homeassistant/components/co2signal/coordinator.py +++ b/homeassistant/components/co2signal/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the co2signal integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/co2signal/diagnostics.py b/homeassistant/components/co2signal/diagnostics.py index 1c53f7c5b08..4e553f0c7da 100644 --- a/homeassistant/components/co2signal/diagnostics.py +++ b/homeassistant/components/co2signal/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for CO2Signal.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/co2signal/helpers.py b/homeassistant/components/co2signal/helpers.py index f61fadaf88c..3feabef2fdd 100644 --- a/homeassistant/components/co2signal/helpers.py +++ b/homeassistant/components/co2signal/helpers.py @@ -1,4 +1,5 @@ """Helper functions for the CO2 Signal integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index bff17becede..685c03136da 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -1,4 +1,5 @@ """Support for the CO2signal platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/co2signal/util.py b/homeassistant/components/co2signal/util.py index b588e0abef9..5ec1f79c466 100644 --- a/homeassistant/components/co2signal/util.py +++ b/homeassistant/components/co2signal/util.py @@ -1,4 +1,5 @@ """Utils for CO2 signal.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/coinbase/__init__.py b/homeassistant/components/coinbase/__init__.py index 40c8ca0c65a..0a34168b4ee 100644 --- a/homeassistant/components/coinbase/__init__.py +++ b/homeassistant/components/coinbase/__init__.py @@ -1,4 +1,5 @@ """The Coinbase integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/coinbase/config_flow.py b/homeassistant/components/coinbase/config_flow.py index 3ffb93b5ab9..dafa50bafcb 100644 --- a/homeassistant/components/coinbase/config_flow.py +++ b/homeassistant/components/coinbase/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Coinbase integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index 1442a626f74..16fb8006fe9 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -1,4 +1,5 @@ """Support for Coinbase sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/color_extractor/config_flow.py b/homeassistant/components/color_extractor/config_flow.py index dea971913b2..de1f9cb35be 100644 --- a/homeassistant/components/color_extractor/config_flow.py +++ b/homeassistant/components/color_extractor/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Color extractor integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/comed_hourly_pricing/sensor.py b/homeassistant/components/comed_hourly_pricing/sensor.py index 195bfa97b7d..5d30387a9cb 100644 --- a/homeassistant/components/comed_hourly_pricing/sensor.py +++ b/homeassistant/components/comed_hourly_pricing/sensor.py @@ -1,4 +1,5 @@ """Support for ComEd Hourly Pricing data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/comelit/alarm_control_panel.py b/homeassistant/components/comelit/alarm_control_panel.py index 33107dd3e82..b325de25e97 100644 --- a/homeassistant/components/comelit/alarm_control_panel.py +++ b/homeassistant/components/comelit/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Comelit VEDO system.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/comelit/climate.py b/homeassistant/components/comelit/climate.py index 5a879bc2d24..d92067651b6 100644 --- a/homeassistant/components/comelit/climate.py +++ b/homeassistant/components/comelit/climate.py @@ -1,4 +1,5 @@ """Support for climates.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/comelit/config_flow.py b/homeassistant/components/comelit/config_flow.py index 54d5cfc0d32..53d08e0097c 100644 --- a/homeassistant/components/comelit/config_flow.py +++ b/homeassistant/components/comelit/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Comelit integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/comelit/coordinator.py b/homeassistant/components/comelit/coordinator.py index fe23cb1f5d3..807f389a6d3 100644 --- a/homeassistant/components/comelit/coordinator.py +++ b/homeassistant/components/comelit/coordinator.py @@ -1,4 +1,5 @@ """Support for Comelit.""" + from abc import abstractmethod from datetime import timedelta from typing import Any diff --git a/homeassistant/components/comelit/cover.py b/homeassistant/components/comelit/cover.py index d35180c761b..011ed81b5cb 100644 --- a/homeassistant/components/comelit/cover.py +++ b/homeassistant/components/comelit/cover.py @@ -1,4 +1,5 @@ """Support for covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/comelit/humidifier.py b/homeassistant/components/comelit/humidifier.py index 8ec2e9fd28b..97cd3beb168 100644 --- a/homeassistant/components/comelit/humidifier.py +++ b/homeassistant/components/comelit/humidifier.py @@ -1,4 +1,5 @@ """Support for humidifiers.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/comelit/light.py b/homeassistant/components/comelit/light.py index a1743bff12d..bb5eb5fa160 100644 --- a/homeassistant/components/comelit/light.py +++ b/homeassistant/components/comelit/light.py @@ -1,4 +1,5 @@ """Support for lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/comelit/sensor.py b/homeassistant/components/comelit/sensor.py index 7cdb0535f8c..a86d49d73e9 100644 --- a/homeassistant/components/comelit/sensor.py +++ b/homeassistant/components/comelit/sensor.py @@ -1,4 +1,5 @@ """Support for sensors.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/comelit/switch.py b/homeassistant/components/comelit/switch.py index ce08c64fa78..68ba934adb6 100644 --- a/homeassistant/components/comelit/switch.py +++ b/homeassistant/components/comelit/switch.py @@ -1,4 +1,5 @@ """Support for switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/comfoconnect/fan.py b/homeassistant/components/comfoconnect/fan.py index f76ed5939f5..f0d261ab968 100644 --- a/homeassistant/components/comfoconnect/fan.py +++ b/homeassistant/components/comfoconnect/fan.py @@ -1,4 +1,5 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index be0616468a1..97cb7fc61eb 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -1,4 +1,5 @@ """Platform to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/command_line/__init__.py b/homeassistant/components/command_line/__init__.py index 701391ab389..27b69e59ca4 100644 --- a/homeassistant/components/command_line/__init__.py +++ b/homeassistant/components/command_line/__init__.py @@ -1,4 +1,5 @@ """The command_line component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py index d9385714719..a31f8205a28 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -1,4 +1,5 @@ """Support for custom shell commands to retrieve values.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 8b60c0750aa..c27cd97b39a 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -1,4 +1,5 @@ """Support for command line covers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/command_line/notify.py b/homeassistant/components/command_line/notify.py index f61e9959af9..1d025726583 100644 --- a/homeassistant/components/command_line/notify.py +++ b/homeassistant/components/command_line/notify.py @@ -1,4 +1,5 @@ """Support for command line notification services.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/command_line/sensor.py b/homeassistant/components/command_line/sensor.py index abbbf4822d7..4cfd9af0811 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -1,4 +1,5 @@ """Allows to configure custom shell commands to turn a value for a sensor.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index b354b8e7576..f84c55d0320 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -1,4 +1,5 @@ """Support for custom shell commands to turn a switch on/off.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/command_line/utils.py b/homeassistant/components/command_line/utils.py index 0cd2947d9cd..faebccae477 100644 --- a/homeassistant/components/command_line/utils.py +++ b/homeassistant/components/command_line/utils.py @@ -1,4 +1,5 @@ """The command_line component utils.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/compensation/sensor.py b/homeassistant/components/compensation/sensor.py index 6abc5d3d5d0..a5dd4a65917 100644 --- a/homeassistant/components/compensation/sensor.py +++ b/homeassistant/components/compensation/sensor.py @@ -1,4 +1,5 @@ """Support for compensation sensor.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py index de5d4495a85..12123a81a38 100644 --- a/homeassistant/components/concord232/alarm_control_panel.py +++ b/homeassistant/components/concord232/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Concord232 alarm control panels.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/concord232/binary_sensor.py b/homeassistant/components/concord232/binary_sensor.py index 305822222ac..79cf0c758e1 100644 --- a/homeassistant/components/concord232/binary_sensor.py +++ b/homeassistant/components/concord232/binary_sensor.py @@ -1,4 +1,5 @@ """Support for exposing Concord232 elements as sensors.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 77c49c4b412..b536433cf1b 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -1,4 +1,5 @@ """Component to configure Home Assistant via an API.""" + from __future__ import annotations from homeassistant.components import frontend diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index 31841717109..435277c5192 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -1,4 +1,5 @@ """HTTP views to interact with the area registry.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index 0409bf0f0f4..266c06d6ee8 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -1,4 +1,5 @@ """Offer API to configure Home Assistant auth.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py index 0c58cad536e..94c179e1a5f 100644 --- a/homeassistant/components/config/auth_provider_homeassistant.py +++ b/homeassistant/components/config/auth_provider_homeassistant.py @@ -1,4 +1,5 @@ """Offer API to configure the Home Assistant auth provider.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 59d4854533d..a5a010c00a6 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -1,4 +1,5 @@ """Provide configuration end points for Automations.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index eb7b1786671..8eb4eb22fb5 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -1,4 +1,5 @@ """Http views to control the config manager.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index d35b00654b7..5c3e4cfe09b 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -1,4 +1,5 @@ """Component to interact with Hassbian tools.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 8444d407030..d7a9dc1a66d 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -1,4 +1,5 @@ """HTTP views to interact with the device registry.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 66a1ceeba69..8a172b17921 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -1,4 +1,5 @@ """HTTP views to interact with the entity registry.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/floor_registry.py b/homeassistant/components/config/floor_registry.py index 4b3ffbd4575..893f97c567d 100644 --- a/homeassistant/components/config/floor_registry.py +++ b/homeassistant/components/config/floor_registry.py @@ -1,4 +1,5 @@ """Websocket API to interact with the floor registry.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/config/label_registry.py b/homeassistant/components/config/label_registry.py index 7ea80231e82..d1bb74393af 100644 --- a/homeassistant/components/config/label_registry.py +++ b/homeassistant/components/config/label_registry.py @@ -1,4 +1,5 @@ """Websocket API to interact with the label registry.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index dbcce222a4f..a2e2693036a 100644 --- a/homeassistant/components/config/scene.py +++ b/homeassistant/components/config/scene.py @@ -1,4 +1,5 @@ """Provide configuration end points for Scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index 35596c7a84f..c39aad4fcdb 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -1,4 +1,5 @@ """Provide configuration end points for scripts.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/config/view.py b/homeassistant/components/config/view.py index e3497ab91f1..62459a83a7d 100644 --- a/homeassistant/components/config/view.py +++ b/homeassistant/components/config/view.py @@ -1,4 +1,5 @@ """Component to configure Home Assistant via an API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index 6fd3917cc9c..e49b6b25d6c 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -5,6 +5,7 @@ This will return a request id that has to be used for future calls. A callback has to be provided to `request_config` which will be called when the user has submitted configuration information. """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index 63cbd9351c7..f63d437dd4c 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -1,4 +1,5 @@ """The Control4 integration.""" + from __future__ import annotations import json diff --git a/homeassistant/components/control4/config_flow.py b/homeassistant/components/control4/config_flow.py index d0af1d03ed7..2d7c6ade255 100644 --- a/homeassistant/components/control4/config_flow.py +++ b/homeassistant/components/control4/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Control4 integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/control4/director_utils.py b/homeassistant/components/control4/director_utils.py index 3d360e36438..2ce03c2e635 100644 --- a/homeassistant/components/control4/director_utils.py +++ b/homeassistant/components/control4/director_utils.py @@ -1,4 +1,5 @@ """Provides data updates from the Control4 controller for platforms.""" + from collections import defaultdict from collections.abc import Set import logging diff --git a/homeassistant/components/control4/light.py b/homeassistant/components/control4/light.py index a2d1308be98..8f7940e5cc3 100644 --- a/homeassistant/components/control4/light.py +++ b/homeassistant/components/control4/light.py @@ -1,4 +1,5 @@ """Platform for Control4 Lights.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index 38327e0108f..dd8fb967824 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -1,4 +1,5 @@ """Support for functionality to have conversations with Home Assistant.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/conversation/agent.py b/homeassistant/components/conversation/agent.py index 2eae3631187..22b3437907c 100644 --- a/homeassistant/components/conversation/agent.py +++ b/homeassistant/components/conversation/agent.py @@ -1,4 +1,5 @@ """Agent foundation for conversation integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/conversation/trigger.py b/homeassistant/components/conversation/trigger.py index 4600135c1e5..082d798a69e 100644 --- a/homeassistant/components/conversation/trigger.py +++ b/homeassistant/components/conversation/trigger.py @@ -1,4 +1,5 @@ """Offer sentence based automation rules.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/conversation/util.py b/homeassistant/components/conversation/util.py index 78fb4bd0ef5..b4ff2511ca1 100644 --- a/homeassistant/components/conversation/util.py +++ b/homeassistant/components/conversation/util.py @@ -1,4 +1,5 @@ """Util for Conversation.""" + from __future__ import annotations import re diff --git a/homeassistant/components/coolmaster/__init__.py b/homeassistant/components/coolmaster/__init__.py index d01310a6266..513d8b3c5a9 100644 --- a/homeassistant/components/coolmaster/__init__.py +++ b/homeassistant/components/coolmaster/__init__.py @@ -1,4 +1,5 @@ """The Coolmaster integration.""" + from pycoolmasternet_async import CoolMasterNet from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/coolmaster/binary_sensor.py b/homeassistant/components/coolmaster/binary_sensor.py index 884d38c77dc..ba54a073f0a 100644 --- a/homeassistant/components/coolmaster/binary_sensor.py +++ b/homeassistant/components/coolmaster/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor platform for CoolMasterNet integration.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/coolmaster/button.py b/homeassistant/components/coolmaster/button.py index db9dd55ea0b..d958346614c 100644 --- a/homeassistant/components/coolmaster/button.py +++ b/homeassistant/components/coolmaster/button.py @@ -1,4 +1,5 @@ """Button platform for CoolMasterNet integration.""" + from __future__ import annotations from homeassistant.components.button import ButtonEntity, ButtonEntityDescription diff --git a/homeassistant/components/coolmaster/climate.py b/homeassistant/components/coolmaster/climate.py index ecb604a14cc..d3cb7122109 100644 --- a/homeassistant/components/coolmaster/climate.py +++ b/homeassistant/components/coolmaster/climate.py @@ -1,4 +1,5 @@ """CoolMasterNet platform to control of CoolMasterNet Climate Devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/coolmaster/config_flow.py b/homeassistant/components/coolmaster/config_flow.py index 41a96df1e7a..19832eaef0a 100644 --- a/homeassistant/components/coolmaster/config_flow.py +++ b/homeassistant/components/coolmaster/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Coolmaster.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/coolmaster/entity.py b/homeassistant/components/coolmaster/entity.py index 66572a56254..73bd1e13a26 100644 --- a/homeassistant/components/coolmaster/entity.py +++ b/homeassistant/components/coolmaster/entity.py @@ -1,4 +1,5 @@ """Base entity for Coolmaster integration.""" + from pycoolmasternet_async.coolmasternet import CoolMasterNetUnit from homeassistant.core import callback diff --git a/homeassistant/components/coolmaster/sensor.py b/homeassistant/components/coolmaster/sensor.py index 30b22f4f658..4c2a09b1ce5 100644 --- a/homeassistant/components/coolmaster/sensor.py +++ b/homeassistant/components/coolmaster/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for CoolMasterNet integration.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity, SensorEntityDescription diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index 7d69025fb97..a607a7bdebe 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -1,4 +1,5 @@ """Component to count within automations.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/counter/reproduce_state.py b/homeassistant/components/counter/reproduce_state.py index 2308e0fb07a..42c68d1f344 100644 --- a/homeassistant/components/counter/reproduce_state.py +++ b/homeassistant/components/counter/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Counter state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 89f79ca9d7a..3261ee90f29 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -1,4 +1,5 @@ """Support for Cover devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/cover/device_action.py b/homeassistant/components/cover/device_action.py index 2224e5bab1c..acef2cde4d8 100644 --- a/homeassistant/components/cover/device_action.py +++ b/homeassistant/components/cover/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Cover.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/cover/device_condition.py b/homeassistant/components/cover/device_condition.py index 23ec7d75650..9c746284fe5 100644 --- a/homeassistant/components/cover/device_condition.py +++ b/homeassistant/components/cover/device_condition.py @@ -1,4 +1,5 @@ """Provides device automations for Cover.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/cover/device_trigger.py b/homeassistant/components/cover/device_trigger.py index 8225348619d..302b1d4340a 100644 --- a/homeassistant/components/cover/device_trigger.py +++ b/homeassistant/components/cover/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Cover.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/cover/reproduce_state.py b/homeassistant/components/cover/reproduce_state.py index 59846627890..59f3df61795 100644 --- a/homeassistant/components/cover/reproduce_state.py +++ b/homeassistant/components/cover/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Cover state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/cover/significant_change.py b/homeassistant/components/cover/significant_change.py index ca822c5e9e1..32f62057b93 100644 --- a/homeassistant/components/cover/significant_change.py +++ b/homeassistant/components/cover/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Cover state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cppm_tracker/device_tracker.py b/homeassistant/components/cppm_tracker/device_tracker.py index 0c7acd33f23..8978028641d 100644 --- a/homeassistant/components/cppm_tracker/device_tracker.py +++ b/homeassistant/components/cppm_tracker/device_tracker.py @@ -1,4 +1,5 @@ """Support for ClearPass Policy Manager.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/cpuspeed/__init__.py b/homeassistant/components/cpuspeed/__init__.py index da1e0129117..cb967b950c0 100644 --- a/homeassistant/components/cpuspeed/__init__.py +++ b/homeassistant/components/cpuspeed/__init__.py @@ -1,4 +1,5 @@ """The CPU Speed integration.""" + from cpuinfo import cpuinfo from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/cpuspeed/config_flow.py b/homeassistant/components/cpuspeed/config_flow.py index a6d760d49f3..ac35cc0fc4f 100644 --- a/homeassistant/components/cpuspeed/config_flow.py +++ b/homeassistant/components/cpuspeed/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the CPU Speed integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cpuspeed/diagnostics.py b/homeassistant/components/cpuspeed/diagnostics.py index a93c71430ef..64fe7f86fa2 100644 --- a/homeassistant/components/cpuspeed/diagnostics.py +++ b/homeassistant/components/cpuspeed/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for CPU Speed.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/cpuspeed/sensor.py b/homeassistant/components/cpuspeed/sensor.py index 0df83b24d70..6a14f7ad13f 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -1,4 +1,5 @@ """Support for displaying the current CPU speed.""" + from __future__ import annotations from cpuinfo import cpuinfo diff --git a/homeassistant/components/crownstone/__init__.py b/homeassistant/components/crownstone/__init__.py index 92b2f4de5ca..e1443eb9516 100644 --- a/homeassistant/components/crownstone/__init__.py +++ b/homeassistant/components/crownstone/__init__.py @@ -1,4 +1,5 @@ """Integration for Crownstone.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/crownstone/config_flow.py b/homeassistant/components/crownstone/config_flow.py index 424d0a93314..0e707c0805a 100644 --- a/homeassistant/components/crownstone/config_flow.py +++ b/homeassistant/components/crownstone/config_flow.py @@ -1,4 +1,5 @@ """Flow handler for Crownstone.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/crownstone/const.py b/homeassistant/components/crownstone/const.py index 9b3624a4575..5325a476266 100644 --- a/homeassistant/components/crownstone/const.py +++ b/homeassistant/components/crownstone/const.py @@ -1,4 +1,5 @@ """Constants for the crownstone integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/crownstone/devices.py b/homeassistant/components/crownstone/devices.py index 5645d3edd1f..4995702701d 100644 --- a/homeassistant/components/crownstone/devices.py +++ b/homeassistant/components/crownstone/devices.py @@ -1,4 +1,5 @@ """Base classes for Crownstone devices.""" + from __future__ import annotations from crownstone_cloud.cloud_models.crownstones import Crownstone diff --git a/homeassistant/components/crownstone/entry_manager.py b/homeassistant/components/crownstone/entry_manager.py index bb6b00942e5..efee05a19c8 100644 --- a/homeassistant/components/crownstone/entry_manager.py +++ b/homeassistant/components/crownstone/entry_manager.py @@ -1,4 +1,5 @@ """Manager to set up IO with Crownstone devices for a config entry.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/crownstone/helpers.py b/homeassistant/components/crownstone/helpers.py index 94df9b9c5c3..aeb26b19ac3 100644 --- a/homeassistant/components/crownstone/helpers.py +++ b/homeassistant/components/crownstone/helpers.py @@ -1,4 +1,5 @@ """Helper functions for the Crownstone integration.""" + from __future__ import annotations import os diff --git a/homeassistant/components/crownstone/light.py b/homeassistant/components/crownstone/light.py index a95238bcdbe..37904408606 100644 --- a/homeassistant/components/crownstone/light.py +++ b/homeassistant/components/crownstone/light.py @@ -1,4 +1,5 @@ """Support for Crownstone devices.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/crownstone/listeners.py b/homeassistant/components/crownstone/listeners.py index 6c611e27083..2642e1501ef 100644 --- a/homeassistant/components/crownstone/listeners.py +++ b/homeassistant/components/crownstone/listeners.py @@ -3,6 +3,7 @@ For data updates, Cloud Push is used in form of an SSE server that sends out events. For fast device switching Local Push is used in form of a USB dongle that hooks into a BLE mesh. """ + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index dd5366dee6a..91c0bb5c4b8 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -1,4 +1,5 @@ """Details about printers which are connected to CUPS.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index b4a33392894..f5ef455ddfe 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -1,4 +1,5 @@ """Support for currencylayer.com exchange rates service.""" + from __future__ import annotations from datetime import timedelta From 8809d3aa88fa732052063ba953ee076f58bd8166 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:52:48 +0100 Subject: [PATCH 0547/1691] Add empty line after module docstring [g-i] (#112699) --- homeassistant/components/garadget/cover.py | 1 + homeassistant/components/garages_amsterdam/binary_sensor.py | 1 + homeassistant/components/garages_amsterdam/config_flow.py | 1 + homeassistant/components/garages_amsterdam/entity.py | 1 + homeassistant/components/garages_amsterdam/sensor.py | 1 + homeassistant/components/gardena_bluetooth/__init__.py | 1 + homeassistant/components/gardena_bluetooth/binary_sensor.py | 1 + homeassistant/components/gardena_bluetooth/button.py | 1 + homeassistant/components/gardena_bluetooth/config_flow.py | 1 + homeassistant/components/gardena_bluetooth/coordinator.py | 1 + homeassistant/components/gardena_bluetooth/number.py | 1 + homeassistant/components/gardena_bluetooth/sensor.py | 1 + homeassistant/components/gardena_bluetooth/switch.py | 1 + homeassistant/components/gc100/binary_sensor.py | 1 + homeassistant/components/gc100/switch.py | 1 + homeassistant/components/gdacs/__init__.py | 1 + homeassistant/components/gdacs/const.py | 1 + homeassistant/components/gdacs/geo_location.py | 1 + homeassistant/components/gdacs/sensor.py | 1 + homeassistant/components/generic/__init__.py | 1 + homeassistant/components/generic/camera.py | 1 + homeassistant/components/generic/config_flow.py | 1 + homeassistant/components/generic/diagnostics.py | 1 + homeassistant/components/generic_hygrostat/humidifier.py | 1 + homeassistant/components/generic_thermostat/climate.py | 1 + homeassistant/components/geniushub/__init__.py | 1 + homeassistant/components/geniushub/binary_sensor.py | 1 + homeassistant/components/geniushub/climate.py | 1 + homeassistant/components/geniushub/sensor.py | 1 + homeassistant/components/geniushub/switch.py | 1 + homeassistant/components/geniushub/water_heater.py | 1 + homeassistant/components/geo_json_events/__init__.py | 1 + homeassistant/components/geo_json_events/config_flow.py | 1 + homeassistant/components/geo_json_events/const.py | 1 + homeassistant/components/geo_json_events/geo_location.py | 1 + homeassistant/components/geo_json_events/manager.py | 1 + homeassistant/components/geo_location/__init__.py | 1 + homeassistant/components/geo_location/trigger.py | 1 + homeassistant/components/geo_rss_events/sensor.py | 1 + homeassistant/components/geocaching/config_flow.py | 1 + homeassistant/components/geocaching/const.py | 1 + homeassistant/components/geocaching/coordinator.py | 1 + homeassistant/components/geocaching/models.py | 1 + homeassistant/components/geocaching/oauth.py | 1 + homeassistant/components/geocaching/sensor.py | 1 + homeassistant/components/geofency/__init__.py | 1 + homeassistant/components/geofency/config_flow.py | 1 + homeassistant/components/geofency/device_tracker.py | 1 + homeassistant/components/geonetnz_quakes/__init__.py | 1 + homeassistant/components/geonetnz_quakes/const.py | 1 + homeassistant/components/geonetnz_quakes/geo_location.py | 1 + homeassistant/components/geonetnz_quakes/sensor.py | 1 + homeassistant/components/geonetnz_volcano/__init__.py | 1 + homeassistant/components/geonetnz_volcano/const.py | 1 + homeassistant/components/geonetnz_volcano/sensor.py | 1 + homeassistant/components/gios/__init__.py | 1 + homeassistant/components/gios/config_flow.py | 1 + homeassistant/components/gios/const.py | 1 + homeassistant/components/gios/diagnostics.py | 1 + homeassistant/components/gios/sensor.py | 1 + homeassistant/components/gios/system_health.py | 1 + homeassistant/components/github/__init__.py | 1 + homeassistant/components/github/config_flow.py | 1 + homeassistant/components/github/const.py | 1 + homeassistant/components/github/coordinator.py | 1 + homeassistant/components/github/diagnostics.py | 1 + homeassistant/components/github/sensor.py | 1 + homeassistant/components/gitlab_ci/sensor.py | 1 + homeassistant/components/gitter/sensor.py | 1 + homeassistant/components/glances/config_flow.py | 1 + homeassistant/components/glances/sensor.py | 1 + homeassistant/components/goalzero/__init__.py | 1 + homeassistant/components/goalzero/binary_sensor.py | 1 + homeassistant/components/goalzero/config_flow.py | 1 + homeassistant/components/goalzero/sensor.py | 1 + homeassistant/components/goalzero/switch.py | 1 + homeassistant/components/gogogate2/common.py | 1 + homeassistant/components/gogogate2/config_flow.py | 1 + homeassistant/components/gogogate2/cover.py | 1 + homeassistant/components/gogogate2/sensor.py | 1 + homeassistant/components/goodwe/button.py | 1 + homeassistant/components/goodwe/config_flow.py | 1 + homeassistant/components/goodwe/const.py | 1 + homeassistant/components/goodwe/coordinator.py | 1 + homeassistant/components/goodwe/diagnostics.py | 1 + homeassistant/components/goodwe/number.py | 1 + homeassistant/components/goodwe/sensor.py | 1 + homeassistant/components/google/__init__.py | 1 + homeassistant/components/google/config_flow.py | 1 + homeassistant/components/google/const.py | 1 + homeassistant/components/google_assistant/__init__.py | 1 + homeassistant/components/google_assistant/button.py | 1 + homeassistant/components/google_assistant/const.py | 1 + homeassistant/components/google_assistant/data_redaction.py | 1 + homeassistant/components/google_assistant/diagnostics.py | 1 + homeassistant/components/google_assistant/error.py | 1 + homeassistant/components/google_assistant/helpers.py | 1 + homeassistant/components/google_assistant/http.py | 1 + homeassistant/components/google_assistant/logbook.py | 1 + homeassistant/components/google_assistant/report_state.py | 1 + homeassistant/components/google_assistant/trait.py | 1 + homeassistant/components/google_assistant_sdk/__init__.py | 1 + .../components/google_assistant_sdk/application_credentials.py | 1 + homeassistant/components/google_assistant_sdk/config_flow.py | 1 + homeassistant/components/google_assistant_sdk/const.py | 1 + homeassistant/components/google_assistant_sdk/helpers.py | 1 + homeassistant/components/google_assistant_sdk/notify.py | 1 + .../components/google_generative_ai_conversation/__init__.py | 1 + .../components/google_generative_ai_conversation/config_flow.py | 1 + homeassistant/components/google_mail/__init__.py | 1 + homeassistant/components/google_mail/api.py | 1 + homeassistant/components/google_mail/application_credentials.py | 1 + homeassistant/components/google_mail/config_flow.py | 1 + homeassistant/components/google_mail/const.py | 1 + homeassistant/components/google_mail/entity.py | 1 + homeassistant/components/google_mail/notify.py | 1 + homeassistant/components/google_mail/sensor.py | 1 + homeassistant/components/google_mail/services.py | 1 + homeassistant/components/google_maps/device_tracker.py | 1 + homeassistant/components/google_pubsub/__init__.py | 1 + homeassistant/components/google_sheets/__init__.py | 1 + .../components/google_sheets/application_credentials.py | 1 + homeassistant/components/google_sheets/config_flow.py | 1 + homeassistant/components/google_sheets/const.py | 1 + homeassistant/components/google_tasks/__init__.py | 1 + homeassistant/components/google_tasks/todo.py | 1 + homeassistant/components/google_translate/__init__.py | 1 + homeassistant/components/google_translate/config_flow.py | 1 + homeassistant/components/google_translate/const.py | 1 + homeassistant/components/google_translate/tts.py | 1 + homeassistant/components/google_travel_time/__init__.py | 1 + homeassistant/components/google_travel_time/config_flow.py | 1 + homeassistant/components/google_travel_time/sensor.py | 1 + homeassistant/components/google_wifi/sensor.py | 1 + homeassistant/components/govee_ble/__init__.py | 1 + homeassistant/components/govee_ble/config_flow.py | 1 + homeassistant/components/govee_ble/sensor.py | 1 + homeassistant/components/govee_light_local/__init__.py | 1 + homeassistant/components/gpsd/__init__.py | 1 + homeassistant/components/gpsd/config_flow.py | 1 + homeassistant/components/gpsd/sensor.py | 1 + homeassistant/components/gpslogger/__init__.py | 1 + homeassistant/components/gpslogger/config_flow.py | 1 + homeassistant/components/gpslogger/device_tracker.py | 1 + homeassistant/components/graphite/__init__.py | 1 + homeassistant/components/gree/__init__.py | 1 + homeassistant/components/gree/bridge.py | 1 + homeassistant/components/gree/climate.py | 1 + homeassistant/components/gree/config_flow.py | 1 + homeassistant/components/gree/entity.py | 1 + homeassistant/components/gree/switch.py | 1 + homeassistant/components/greeneye_monitor/__init__.py | 1 + homeassistant/components/greeneye_monitor/sensor.py | 1 + homeassistant/components/greenwave/light.py | 1 + homeassistant/components/group/__init__.py | 1 + homeassistant/components/group/binary_sensor.py | 1 + homeassistant/components/group/config_flow.py | 1 + homeassistant/components/group/cover.py | 1 + homeassistant/components/group/event.py | 1 + homeassistant/components/group/fan.py | 1 + homeassistant/components/group/light.py | 1 + homeassistant/components/group/lock.py | 1 + homeassistant/components/group/media_player.py | 1 + homeassistant/components/group/notify.py | 1 + homeassistant/components/group/reproduce_state.py | 1 + homeassistant/components/group/switch.py | 1 + homeassistant/components/group/util.py | 1 + homeassistant/components/growatt_server/__init__.py | 1 + homeassistant/components/growatt_server/const.py | 1 + homeassistant/components/growatt_server/sensor.py | 1 + homeassistant/components/growatt_server/sensor_types/inverter.py | 1 + homeassistant/components/growatt_server/sensor_types/mix.py | 1 + .../growatt_server/sensor_types/sensor_entity_description.py | 1 + homeassistant/components/growatt_server/sensor_types/storage.py | 1 + homeassistant/components/growatt_server/sensor_types/tlx.py | 1 + homeassistant/components/growatt_server/sensor_types/total.py | 1 + homeassistant/components/gstreamer/media_player.py | 1 + homeassistant/components/gtfs/sensor.py | 1 + homeassistant/components/guardian/__init__.py | 1 + homeassistant/components/guardian/binary_sensor.py | 1 + homeassistant/components/guardian/button.py | 1 + homeassistant/components/guardian/config_flow.py | 1 + homeassistant/components/guardian/coordinator.py | 1 + homeassistant/components/guardian/diagnostics.py | 1 + homeassistant/components/guardian/sensor.py | 1 + homeassistant/components/guardian/switch.py | 1 + homeassistant/components/guardian/util.py | 1 + homeassistant/components/guardian/valve.py | 1 + homeassistant/components/habitica/config_flow.py | 1 + homeassistant/components/habitica/sensor.py | 1 + homeassistant/components/hardkernel/__init__.py | 1 + homeassistant/components/hardkernel/config_flow.py | 1 + homeassistant/components/hardkernel/hardware.py | 1 + homeassistant/components/hardware/__init__.py | 1 + homeassistant/components/hardware/hardware.py | 1 + homeassistant/components/hardware/models.py | 1 + homeassistant/components/hardware/websocket_api.py | 1 + homeassistant/components/harman_kardon_avr/media_player.py | 1 + homeassistant/components/harmony/config_flow.py | 1 + homeassistant/components/harmony/const.py | 1 + homeassistant/components/harmony/data.py | 1 + homeassistant/components/harmony/entity.py | 1 + homeassistant/components/harmony/remote.py | 1 + homeassistant/components/harmony/select.py | 1 + homeassistant/components/harmony/subscriber.py | 1 + homeassistant/components/hassio/__init__.py | 1 + homeassistant/components/hassio/addon_manager.py | 1 + homeassistant/components/hassio/addon_panel.py | 1 + homeassistant/components/hassio/auth.py | 1 + homeassistant/components/hassio/binary_sensor.py | 1 + homeassistant/components/hassio/config_flow.py | 1 + homeassistant/components/hassio/const.py | 1 + homeassistant/components/hassio/data.py | 1 + homeassistant/components/hassio/diagnostics.py | 1 + homeassistant/components/hassio/discovery.py | 1 + homeassistant/components/hassio/entity.py | 1 + homeassistant/components/hassio/handler.py | 1 + homeassistant/components/hassio/http.py | 1 + homeassistant/components/hassio/ingress.py | 1 + homeassistant/components/hassio/issues.py | 1 + homeassistant/components/hassio/repairs.py | 1 + homeassistant/components/hassio/sensor.py | 1 + homeassistant/components/hassio/system_health.py | 1 + homeassistant/components/hassio/update.py | 1 + homeassistant/components/haveibeenpwned/sensor.py | 1 + homeassistant/components/hddtemp/sensor.py | 1 + homeassistant/components/hdmi_cec/__init__.py | 1 + homeassistant/components/hdmi_cec/media_player.py | 1 + homeassistant/components/hdmi_cec/switch.py | 1 + homeassistant/components/heatmiser/climate.py | 1 + homeassistant/components/heos/__init__.py | 1 + homeassistant/components/heos/config_flow.py | 1 + homeassistant/components/heos/media_player.py | 1 + homeassistant/components/here_travel_time/__init__.py | 1 + homeassistant/components/here_travel_time/config_flow.py | 1 + homeassistant/components/here_travel_time/const.py | 1 + homeassistant/components/here_travel_time/coordinator.py | 1 + homeassistant/components/here_travel_time/model.py | 1 + homeassistant/components/here_travel_time/sensor.py | 1 + homeassistant/components/hikvision/binary_sensor.py | 1 + homeassistant/components/hikvisioncam/switch.py | 1 + homeassistant/components/hisense_aehw4a1/climate.py | 1 + homeassistant/components/hisense_aehw4a1/config_flow.py | 1 + homeassistant/components/history/__init__.py | 1 + homeassistant/components/history/helpers.py | 1 + homeassistant/components/history/websocket_api.py | 1 + homeassistant/components/history_stats/coordinator.py | 1 + homeassistant/components/history_stats/data.py | 1 + homeassistant/components/history_stats/helpers.py | 1 + homeassistant/components/history_stats/sensor.py | 1 + homeassistant/components/hitron_coda/device_tracker.py | 1 + homeassistant/components/hive/__init__.py | 1 + homeassistant/components/hive/alarm_control_panel.py | 1 + homeassistant/components/hive/binary_sensor.py | 1 + homeassistant/components/hive/climate.py | 1 + homeassistant/components/hive/config_flow.py | 1 + homeassistant/components/hive/const.py | 1 + homeassistant/components/hive/light.py | 1 + homeassistant/components/hive/sensor.py | 1 + homeassistant/components/hive/switch.py | 1 + homeassistant/components/hive/water_heater.py | 1 + homeassistant/components/hko/__init__.py | 1 + homeassistant/components/hko/config_flow.py | 1 + homeassistant/components/hko/const.py | 1 + homeassistant/components/hko/coordinator.py | 1 + homeassistant/components/hko/weather.py | 1 + homeassistant/components/hlk_sw16/errors.py | 1 + homeassistant/components/hlk_sw16/switch.py | 1 + homeassistant/components/holiday/__init__.py | 1 + homeassistant/components/holiday/calendar.py | 1 + homeassistant/components/holiday/config_flow.py | 1 + homeassistant/components/holiday/const.py | 1 + homeassistant/components/home_connect/__init__.py | 1 + homeassistant/components/home_connect/sensor.py | 1 + homeassistant/components/homeassistant/const.py | 1 + homeassistant/components/homeassistant/exposed_entities.py | 1 + homeassistant/components/homeassistant/logbook.py | 1 + homeassistant/components/homeassistant/scene.py | 1 + homeassistant/components/homeassistant/system_health.py | 1 + homeassistant/components/homeassistant/triggers/event.py | 1 + homeassistant/components/homeassistant/triggers/numeric_state.py | 1 + homeassistant/components/homeassistant/triggers/state.py | 1 + homeassistant/components/homeassistant/triggers/time.py | 1 + homeassistant/components/homeassistant/triggers/time_pattern.py | 1 + homeassistant/components/homeassistant_alerts/__init__.py | 1 + homeassistant/components/homeassistant_green/__init__.py | 1 + homeassistant/components/homeassistant_green/config_flow.py | 1 + homeassistant/components/homeassistant_green/hardware.py | 1 + homeassistant/components/homeassistant_hardware/__init__.py | 1 + .../homeassistant_hardware/silabs_multiprotocol_addon.py | 1 + homeassistant/components/homeassistant_sky_connect/__init__.py | 1 + .../components/homeassistant_sky_connect/config_flow.py | 1 + homeassistant/components/homeassistant_sky_connect/hardware.py | 1 + homeassistant/components/homeassistant_sky_connect/util.py | 1 + homeassistant/components/homeassistant_yellow/__init__.py | 1 + homeassistant/components/homeassistant_yellow/config_flow.py | 1 + homeassistant/components/homeassistant_yellow/hardware.py | 1 + homeassistant/components/homekit/__init__.py | 1 + homeassistant/components/homekit/accessories.py | 1 + homeassistant/components/homekit/aidmanager.py | 1 + homeassistant/components/homekit/config_flow.py | 1 + homeassistant/components/homekit/diagnostics.py | 1 + homeassistant/components/homekit/iidmanager.py | 1 + homeassistant/components/homekit/logbook.py | 1 + homeassistant/components/homekit/models.py | 1 + homeassistant/components/homekit/type_lights.py | 1 + homeassistant/components/homekit/type_remotes.py | 1 + homeassistant/components/homekit/type_sensors.py | 1 + homeassistant/components/homekit/type_switches.py | 1 + homeassistant/components/homekit/type_triggers.py | 1 + homeassistant/components/homekit/util.py | 1 + homeassistant/components/homekit_controller/__init__.py | 1 + .../components/homekit_controller/alarm_control_panel.py | 1 + homeassistant/components/homekit_controller/binary_sensor.py | 1 + homeassistant/components/homekit_controller/button.py | 1 + homeassistant/components/homekit_controller/camera.py | 1 + homeassistant/components/homekit_controller/climate.py | 1 + homeassistant/components/homekit_controller/config_flow.py | 1 + homeassistant/components/homekit_controller/connection.py | 1 + homeassistant/components/homekit_controller/cover.py | 1 + homeassistant/components/homekit_controller/device_trigger.py | 1 + homeassistant/components/homekit_controller/diagnostics.py | 1 + homeassistant/components/homekit_controller/entity.py | 1 + homeassistant/components/homekit_controller/event.py | 1 + homeassistant/components/homekit_controller/fan.py | 1 + homeassistant/components/homekit_controller/humidifier.py | 1 + homeassistant/components/homekit_controller/light.py | 1 + homeassistant/components/homekit_controller/lock.py | 1 + homeassistant/components/homekit_controller/media_player.py | 1 + homeassistant/components/homekit_controller/number.py | 1 + homeassistant/components/homekit_controller/select.py | 1 + homeassistant/components/homekit_controller/sensor.py | 1 + homeassistant/components/homekit_controller/switch.py | 1 + homeassistant/components/homekit_controller/utils.py | 1 + homeassistant/components/homematic/__init__.py | 1 + homeassistant/components/homematic/binary_sensor.py | 1 + homeassistant/components/homematic/climate.py | 1 + homeassistant/components/homematic/cover.py | 1 + homeassistant/components/homematic/entity.py | 1 + homeassistant/components/homematic/light.py | 1 + homeassistant/components/homematic/lock.py | 1 + homeassistant/components/homematic/notify.py | 1 + homeassistant/components/homematic/sensor.py | 1 + homeassistant/components/homematic/switch.py | 1 + .../components/homematicip_cloud/alarm_control_panel.py | 1 + homeassistant/components/homematicip_cloud/binary_sensor.py | 1 + homeassistant/components/homematicip_cloud/button.py | 1 + homeassistant/components/homematicip_cloud/climate.py | 1 + homeassistant/components/homematicip_cloud/config_flow.py | 1 + homeassistant/components/homematicip_cloud/cover.py | 1 + homeassistant/components/homematicip_cloud/errors.py | 1 + homeassistant/components/homematicip_cloud/generic_entity.py | 1 + homeassistant/components/homematicip_cloud/hap.py | 1 + homeassistant/components/homematicip_cloud/helpers.py | 1 + homeassistant/components/homematicip_cloud/light.py | 1 + homeassistant/components/homematicip_cloud/lock.py | 1 + homeassistant/components/homematicip_cloud/sensor.py | 1 + homeassistant/components/homematicip_cloud/services.py | 1 + homeassistant/components/homematicip_cloud/switch.py | 1 + homeassistant/components/homematicip_cloud/weather.py | 1 + homeassistant/components/homewizard/__init__.py | 1 + homeassistant/components/homewizard/config_flow.py | 1 + homeassistant/components/homewizard/const.py | 1 + homeassistant/components/homewizard/coordinator.py | 1 + homeassistant/components/homewizard/diagnostics.py | 1 + homeassistant/components/homewizard/entity.py | 1 + homeassistant/components/homewizard/helpers.py | 1 + homeassistant/components/homewizard/number.py | 1 + homeassistant/components/homewizard/sensor.py | 1 + homeassistant/components/homewizard/switch.py | 1 + homeassistant/components/homeworks/__init__.py | 1 + homeassistant/components/homeworks/button.py | 1 + homeassistant/components/homeworks/config_flow.py | 1 + homeassistant/components/homeworks/const.py | 1 + homeassistant/components/homeworks/light.py | 1 + homeassistant/components/honeywell/__init__.py | 1 + homeassistant/components/honeywell/climate.py | 1 + homeassistant/components/honeywell/config_flow.py | 1 + homeassistant/components/honeywell/diagnostics.py | 1 + homeassistant/components/honeywell/sensor.py | 1 + homeassistant/components/horizon/media_player.py | 1 + homeassistant/components/hp_ilo/sensor.py | 1 + homeassistant/components/html5/notify.py | 1 + homeassistant/components/http/__init__.py | 1 + homeassistant/components/http/auth.py | 1 + homeassistant/components/http/ban.py | 1 + homeassistant/components/http/const.py | 1 + homeassistant/components/http/cors.py | 1 + homeassistant/components/http/data_validator.py | 1 + homeassistant/components/http/decorators.py | 1 + homeassistant/components/http/forwarded.py | 1 + homeassistant/components/http/headers.py | 1 + homeassistant/components/http/request_context.py | 1 + homeassistant/components/http/security_filter.py | 1 + homeassistant/components/http/static.py | 1 + homeassistant/components/http/view.py | 1 + homeassistant/components/http/web_runner.py | 1 + homeassistant/components/huawei_lte/__init__.py | 1 + homeassistant/components/huawei_lte/binary_sensor.py | 1 + homeassistant/components/huawei_lte/config_flow.py | 1 + homeassistant/components/huawei_lte/device_tracker.py | 1 + homeassistant/components/huawei_lte/notify.py | 1 + homeassistant/components/huawei_lte/select.py | 1 + homeassistant/components/huawei_lte/sensor.py | 1 + homeassistant/components/huawei_lte/switch.py | 1 + homeassistant/components/huawei_lte/utils.py | 1 + homeassistant/components/hue/__init__.py | 1 + homeassistant/components/hue/binary_sensor.py | 1 + homeassistant/components/hue/bridge.py | 1 + homeassistant/components/hue/config_flow.py | 1 + homeassistant/components/hue/const.py | 1 + homeassistant/components/hue/device_trigger.py | 1 + homeassistant/components/hue/diagnostics.py | 1 + homeassistant/components/hue/errors.py | 1 + homeassistant/components/hue/event.py | 1 + homeassistant/components/hue/light.py | 1 + homeassistant/components/hue/scene.py | 1 + homeassistant/components/hue/sensor.py | 1 + homeassistant/components/hue/services.py | 1 + homeassistant/components/hue/switch.py | 1 + homeassistant/components/hue/v1/binary_sensor.py | 1 + homeassistant/components/hue/v1/device_trigger.py | 1 + homeassistant/components/hue/v1/light.py | 1 + homeassistant/components/hue/v1/sensor.py | 1 + homeassistant/components/hue/v1/sensor_base.py | 1 + homeassistant/components/hue/v1/sensor_device.py | 1 + homeassistant/components/hue/v2/binary_sensor.py | 1 + homeassistant/components/hue/v2/device.py | 1 + homeassistant/components/hue/v2/device_trigger.py | 1 + homeassistant/components/hue/v2/entity.py | 1 + homeassistant/components/hue/v2/group.py | 1 + homeassistant/components/hue/v2/helpers.py | 1 + homeassistant/components/hue/v2/light.py | 1 + homeassistant/components/hue/v2/sensor.py | 1 + homeassistant/components/huisbaasje/const.py | 1 + homeassistant/components/huisbaasje/sensor.py | 1 + homeassistant/components/humidifier/__init__.py | 1 + homeassistant/components/humidifier/const.py | 1 + homeassistant/components/humidifier/device_action.py | 1 + homeassistant/components/humidifier/device_condition.py | 1 + homeassistant/components/humidifier/device_trigger.py | 1 + homeassistant/components/humidifier/intent.py | 1 + homeassistant/components/humidifier/reproduce_state.py | 1 + homeassistant/components/humidifier/significant_change.py | 1 + homeassistant/components/hunterdouglas_powerview/button.py | 1 + homeassistant/components/hunterdouglas_powerview/config_flow.py | 1 + homeassistant/components/hunterdouglas_powerview/coordinator.py | 1 + homeassistant/components/hunterdouglas_powerview/cover.py | 1 + homeassistant/components/hunterdouglas_powerview/diagnostics.py | 1 + homeassistant/components/hunterdouglas_powerview/model.py | 1 + homeassistant/components/hunterdouglas_powerview/scene.py | 1 + homeassistant/components/hunterdouglas_powerview/select.py | 1 + homeassistant/components/hunterdouglas_powerview/shade_data.py | 1 + homeassistant/components/hunterdouglas_powerview/util.py | 1 + homeassistant/components/husqvarna_automower/config_flow.py | 1 + homeassistant/components/husqvarna_automower/sensor.py | 1 + homeassistant/components/huum/__init__.py | 1 + homeassistant/components/huum/climate.py | 1 + homeassistant/components/huum/config_flow.py | 1 + homeassistant/components/hvv_departures/binary_sensor.py | 1 + homeassistant/components/hvv_departures/config_flow.py | 1 + homeassistant/components/hvv_departures/sensor.py | 1 + homeassistant/components/hydrawise/binary_sensor.py | 1 + homeassistant/components/hydrawise/entity.py | 1 + homeassistant/components/hydrawise/sensor.py | 1 + homeassistant/components/hydrawise/switch.py | 1 + homeassistant/components/hyperion/__init__.py | 1 + homeassistant/components/hyperion/config_flow.py | 1 + homeassistant/components/hyperion/light.py | 1 + homeassistant/components/hyperion/sensor.py | 1 + homeassistant/components/hyperion/switch.py | 1 + homeassistant/components/ialarm/__init__.py | 1 + homeassistant/components/ialarm/alarm_control_panel.py | 1 + homeassistant/components/ialarm/const.py | 1 + homeassistant/components/iammeter/const.py | 1 + homeassistant/components/iammeter/sensor.py | 1 + homeassistant/components/iaqualink/__init__.py | 1 + homeassistant/components/iaqualink/binary_sensor.py | 1 + homeassistant/components/iaqualink/climate.py | 1 + homeassistant/components/iaqualink/config_flow.py | 1 + homeassistant/components/iaqualink/const.py | 1 + homeassistant/components/iaqualink/light.py | 1 + homeassistant/components/iaqualink/sensor.py | 1 + homeassistant/components/iaqualink/switch.py | 1 + homeassistant/components/iaqualink/utils.py | 1 + homeassistant/components/ibeacon/__init__.py | 1 + homeassistant/components/ibeacon/config_flow.py | 1 + homeassistant/components/ibeacon/coordinator.py | 1 + homeassistant/components/ibeacon/device_tracker.py | 1 + homeassistant/components/ibeacon/entity.py | 1 + homeassistant/components/ibeacon/sensor.py | 1 + homeassistant/components/icloud/__init__.py | 1 + homeassistant/components/icloud/account.py | 1 + homeassistant/components/icloud/config_flow.py | 1 + homeassistant/components/icloud/const.py | 1 + homeassistant/components/icloud/device_tracker.py | 1 + homeassistant/components/icloud/sensor.py | 1 + homeassistant/components/idasen_desk/__init__.py | 1 + homeassistant/components/idasen_desk/button.py | 1 + homeassistant/components/idasen_desk/config_flow.py | 1 + homeassistant/components/idasen_desk/cover.py | 1 + homeassistant/components/idasen_desk/sensor.py | 1 + homeassistant/components/idteck_prox/__init__.py | 1 + homeassistant/components/ifttt/__init__.py | 1 + homeassistant/components/ifttt/alarm_control_panel.py | 1 + homeassistant/components/ifttt/config_flow.py | 1 + homeassistant/components/iglo/light.py | 1 + homeassistant/components/ign_sismologia/geo_location.py | 1 + homeassistant/components/ihc/binary_sensor.py | 1 + homeassistant/components/ihc/const.py | 1 + homeassistant/components/ihc/light.py | 1 + homeassistant/components/ihc/sensor.py | 1 + homeassistant/components/ihc/switch.py | 1 + homeassistant/components/image/__init__.py | 1 + homeassistant/components/image/const.py | 1 + homeassistant/components/image/media_source.py | 1 + homeassistant/components/image_processing/__init__.py | 1 + homeassistant/components/image_upload/__init__.py | 1 + homeassistant/components/imap/__init__.py | 1 + homeassistant/components/imap/config_flow.py | 1 + homeassistant/components/imap/coordinator.py | 1 + homeassistant/components/imap/diagnostics.py | 1 + homeassistant/components/imap/sensor.py | 1 + homeassistant/components/improv_ble/config_flow.py | 1 + homeassistant/components/incomfort/__init__.py | 1 + homeassistant/components/incomfort/binary_sensor.py | 1 + homeassistant/components/incomfort/climate.py | 1 + homeassistant/components/incomfort/sensor.py | 1 + homeassistant/components/incomfort/water_heater.py | 1 + homeassistant/components/influxdb/__init__.py | 1 + homeassistant/components/influxdb/const.py | 1 + homeassistant/components/influxdb/sensor.py | 1 + homeassistant/components/inkbird/__init__.py | 1 + homeassistant/components/inkbird/config_flow.py | 1 + homeassistant/components/inkbird/sensor.py | 1 + homeassistant/components/input_boolean/__init__.py | 1 + homeassistant/components/input_boolean/reproduce_state.py | 1 + homeassistant/components/input_button/__init__.py | 1 + homeassistant/components/input_datetime/__init__.py | 1 + homeassistant/components/input_datetime/reproduce_state.py | 1 + homeassistant/components/input_number/__init__.py | 1 + homeassistant/components/input_number/reproduce_state.py | 1 + homeassistant/components/input_select/__init__.py | 1 + homeassistant/components/input_select/reproduce_state.py | 1 + homeassistant/components/input_text/__init__.py | 1 + homeassistant/components/input_text/reproduce_state.py | 1 + homeassistant/components/insteon/__init__.py | 1 + homeassistant/components/insteon/binary_sensor.py | 1 + homeassistant/components/insteon/climate.py | 1 + homeassistant/components/insteon/config_flow.py | 1 + homeassistant/components/insteon/fan.py | 1 + homeassistant/components/insteon/ipdb.py | 1 + homeassistant/components/insteon/light.py | 1 + homeassistant/components/insteon/schemas.py | 1 + homeassistant/components/insteon/switch.py | 1 + homeassistant/components/insteon/utils.py | 1 + homeassistant/components/integration/__init__.py | 1 + homeassistant/components/integration/config_flow.py | 1 + homeassistant/components/integration/sensor.py | 1 + homeassistant/components/intellifire/__init__.py | 1 + homeassistant/components/intellifire/binary_sensor.py | 1 + homeassistant/components/intellifire/climate.py | 1 + homeassistant/components/intellifire/config_flow.py | 1 + homeassistant/components/intellifire/const.py | 1 + homeassistant/components/intellifire/coordinator.py | 1 + homeassistant/components/intellifire/entity.py | 1 + homeassistant/components/intellifire/fan.py | 1 + homeassistant/components/intellifire/light.py | 1 + homeassistant/components/intellifire/number.py | 1 + homeassistant/components/intellifire/sensor.py | 1 + homeassistant/components/intellifire/switch.py | 1 + homeassistant/components/intent_script/__init__.py | 1 + homeassistant/components/intesishome/climate.py | 1 + homeassistant/components/ios/config_flow.py | 1 + homeassistant/components/ios/notify.py | 1 + homeassistant/components/ios/sensor.py | 1 + homeassistant/components/iotawatt/__init__.py | 1 + homeassistant/components/iotawatt/config_flow.py | 1 + homeassistant/components/iotawatt/const.py | 1 + homeassistant/components/iotawatt/coordinator.py | 1 + homeassistant/components/iotawatt/sensor.py | 1 + homeassistant/components/iperf3/__init__.py | 1 + homeassistant/components/iperf3/sensor.py | 1 + homeassistant/components/ipma/const.py | 1 + homeassistant/components/ipma/entity.py | 1 + homeassistant/components/ipma/sensor.py | 1 + homeassistant/components/ipma/system_health.py | 1 + homeassistant/components/ipma/weather.py | 1 + homeassistant/components/ipp/__init__.py | 1 + homeassistant/components/ipp/config_flow.py | 1 + homeassistant/components/ipp/coordinator.py | 1 + homeassistant/components/ipp/entity.py | 1 + homeassistant/components/ipp/sensor.py | 1 + homeassistant/components/iqvia/__init__.py | 1 + homeassistant/components/iqvia/config_flow.py | 1 + homeassistant/components/iqvia/diagnostics.py | 1 + homeassistant/components/iqvia/sensor.py | 1 + homeassistant/components/irish_rail_transport/sensor.py | 1 + homeassistant/components/islamic_prayer_times/__init__.py | 1 + homeassistant/components/islamic_prayer_times/config_flow.py | 1 + homeassistant/components/islamic_prayer_times/const.py | 1 + homeassistant/components/islamic_prayer_times/coordinator.py | 1 + homeassistant/components/islamic_prayer_times/sensor.py | 1 + homeassistant/components/iss/__init__.py | 1 + homeassistant/components/iss/sensor.py | 1 + homeassistant/components/isy994/__init__.py | 1 + homeassistant/components/isy994/binary_sensor.py | 1 + homeassistant/components/isy994/button.py | 1 + homeassistant/components/isy994/climate.py | 1 + homeassistant/components/isy994/config_flow.py | 1 + homeassistant/components/isy994/cover.py | 1 + homeassistant/components/isy994/entity.py | 1 + homeassistant/components/isy994/fan.py | 1 + homeassistant/components/isy994/helpers.py | 1 + homeassistant/components/isy994/light.py | 1 + homeassistant/components/isy994/lock.py | 1 + homeassistant/components/isy994/models.py | 1 + homeassistant/components/isy994/number.py | 1 + homeassistant/components/isy994/select.py | 1 + homeassistant/components/isy994/sensor.py | 1 + homeassistant/components/isy994/services.py | 1 + homeassistant/components/isy994/switch.py | 1 + homeassistant/components/isy994/system_health.py | 1 + homeassistant/components/isy994/util.py | 1 + homeassistant/components/itach/remote.py | 1 + homeassistant/components/itunes/media_player.py | 1 + homeassistant/components/izone/climate.py | 1 + 627 files changed, 627 insertions(+) diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index 6d9705cee75..f168652b3cf 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -1,4 +1,5 @@ """Platform for the Garadget cover component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/garages_amsterdam/binary_sensor.py b/homeassistant/components/garages_amsterdam/binary_sensor.py index ad0630249aa..0aebe36baeb 100644 --- a/homeassistant/components/garages_amsterdam/binary_sensor.py +++ b/homeassistant/components/garages_amsterdam/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor platform for Garages Amsterdam.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/garages_amsterdam/config_flow.py b/homeassistant/components/garages_amsterdam/config_flow.py index c1f9acafc7e..6623ad5bd18 100644 --- a/homeassistant/components/garages_amsterdam/config_flow.py +++ b/homeassistant/components/garages_amsterdam/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Garages Amsterdam integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/garages_amsterdam/entity.py b/homeassistant/components/garages_amsterdam/entity.py index 45c85a101a9..671405235d4 100644 --- a/homeassistant/components/garages_amsterdam/entity.py +++ b/homeassistant/components/garages_amsterdam/entity.py @@ -1,4 +1,5 @@ """Generic entity for Garages Amsterdam.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo diff --git a/homeassistant/components/garages_amsterdam/sensor.py b/homeassistant/components/garages_amsterdam/sensor.py index 48a3746a762..d17a8705275 100644 --- a/homeassistant/components/garages_amsterdam/sensor.py +++ b/homeassistant/components/garages_amsterdam/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Garages Amsterdam.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity diff --git a/homeassistant/components/gardena_bluetooth/__init__.py b/homeassistant/components/gardena_bluetooth/__init__.py index 99c8fa69acf..c2b3ae6732b 100644 --- a/homeassistant/components/gardena_bluetooth/__init__.py +++ b/homeassistant/components/gardena_bluetooth/__init__.py @@ -1,4 +1,5 @@ """The Gardena Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/gardena_bluetooth/binary_sensor.py b/homeassistant/components/gardena_bluetooth/binary_sensor.py index bf905bc551d..c552beaf878 100644 --- a/homeassistant/components/gardena_bluetooth/binary_sensor.py +++ b/homeassistant/components/gardena_bluetooth/binary_sensor.py @@ -1,4 +1,5 @@ """Support for binary_sensor entities.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/gardena_bluetooth/button.py b/homeassistant/components/gardena_bluetooth/button.py index cbdbda5f367..bdcf9094f5c 100644 --- a/homeassistant/components/gardena_bluetooth/button.py +++ b/homeassistant/components/gardena_bluetooth/button.py @@ -1,4 +1,5 @@ """Support for button entities.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/gardena_bluetooth/config_flow.py b/homeassistant/components/gardena_bluetooth/config_flow.py index 78e82f56e72..c7631b62f47 100644 --- a/homeassistant/components/gardena_bluetooth/config_flow.py +++ b/homeassistant/components/gardena_bluetooth/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Gardena Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/gardena_bluetooth/coordinator.py b/homeassistant/components/gardena_bluetooth/coordinator.py index 73552e25c03..7d939d27eb5 100644 --- a/homeassistant/components/gardena_bluetooth/coordinator.py +++ b/homeassistant/components/gardena_bluetooth/coordinator.py @@ -1,4 +1,5 @@ """Provides the DataUpdateCoordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/gardena_bluetooth/number.py b/homeassistant/components/gardena_bluetooth/number.py index ef19a921041..cbc4866b0ff 100644 --- a/homeassistant/components/gardena_bluetooth/number.py +++ b/homeassistant/components/gardena_bluetooth/number.py @@ -1,4 +1,5 @@ """Support for number entities.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/gardena_bluetooth/sensor.py b/homeassistant/components/gardena_bluetooth/sensor.py index ca2b1acdd8c..f2bddd3a91a 100644 --- a/homeassistant/components/gardena_bluetooth/sensor.py +++ b/homeassistant/components/gardena_bluetooth/sensor.py @@ -1,4 +1,5 @@ """Support for switch entities.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/gardena_bluetooth/switch.py b/homeassistant/components/gardena_bluetooth/switch.py index bc83e3ed5a9..a57130c3acf 100644 --- a/homeassistant/components/gardena_bluetooth/switch.py +++ b/homeassistant/components/gardena_bluetooth/switch.py @@ -1,4 +1,5 @@ """Support for switch entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/gc100/binary_sensor.py b/homeassistant/components/gc100/binary_sensor.py index e750f928cf7..a03eae509d9 100644 --- a/homeassistant/components/gc100/binary_sensor.py +++ b/homeassistant/components/gc100/binary_sensor.py @@ -1,4 +1,5 @@ """Support for binary sensor using GC100.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/gc100/switch.py b/homeassistant/components/gc100/switch.py index d88b4c9fa79..ea90dde6abf 100644 --- a/homeassistant/components/gc100/switch.py +++ b/homeassistant/components/gc100/switch.py @@ -1,4 +1,5 @@ """Support for switches using GC100.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/gdacs/__init__.py b/homeassistant/components/gdacs/__init__.py index 0ec582f8d06..e96246b70bf 100644 --- a/homeassistant/components/gdacs/__init__.py +++ b/homeassistant/components/gdacs/__init__.py @@ -1,4 +1,5 @@ """The Global Disaster Alert and Coordination System (GDACS) integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/gdacs/const.py b/homeassistant/components/gdacs/const.py index 6be7e7b32fc..d1028ed2d08 100644 --- a/homeassistant/components/gdacs/const.py +++ b/homeassistant/components/gdacs/const.py @@ -1,4 +1,5 @@ """Define constants for the GDACS integration.""" + from datetime import timedelta from aio_georss_gdacs.consts import EVENT_TYPE_MAP diff --git a/homeassistant/components/gdacs/geo_location.py b/homeassistant/components/gdacs/geo_location.py index 5c4fb9d33bc..394c9086d71 100644 --- a/homeassistant/components/gdacs/geo_location.py +++ b/homeassistant/components/gdacs/geo_location.py @@ -1,4 +1,5 @@ """Geolocation support for GDACS Feed.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/gdacs/sensor.py b/homeassistant/components/gdacs/sensor.py index f660c8f73c8..90ea668ea3f 100644 --- a/homeassistant/components/gdacs/sensor.py +++ b/homeassistant/components/gdacs/sensor.py @@ -1,4 +1,5 @@ """Feed Entity Manager Sensor support for GDACS Feed.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/generic/__init__.py b/homeassistant/components/generic/__init__.py index 280b468add8..3de664dd734 100644 --- a/homeassistant/components/generic/__init__.py +++ b/homeassistant/components/generic/__init__.py @@ -1,4 +1,5 @@ """The generic component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index cadc855ade6..80971760b85 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -1,4 +1,5 @@ """Support for IP Cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index b5bbd79def1..0964e7f2b29 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -1,4 +1,5 @@ """Config flow for generic (IP Camera).""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/generic/diagnostics.py b/homeassistant/components/generic/diagnostics.py index 39d6a81ad88..e5bf4294e4a 100644 --- a/homeassistant/components/generic/diagnostics.py +++ b/homeassistant/components/generic/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for generic (IP camera).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/generic_hygrostat/humidifier.py b/homeassistant/components/generic_hygrostat/humidifier.py index d69a8a968c7..fea00d5cc8b 100644 --- a/homeassistant/components/generic_hygrostat/humidifier.py +++ b/homeassistant/components/generic_hygrostat/humidifier.py @@ -1,4 +1,5 @@ """Adds support for generic hygrostat units.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 64fde0dfd26..af082004f39 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -1,4 +1,5 @@ """Adds support for generic thermostat units.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/geniushub/__init__.py b/homeassistant/components/geniushub/__init__.py index 955c76fe0fc..5fc21a3e5b4 100644 --- a/homeassistant/components/geniushub/__init__.py +++ b/homeassistant/components/geniushub/__init__.py @@ -1,4 +1,5 @@ """Support for a Genius Hub system.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/geniushub/binary_sensor.py b/homeassistant/components/geniushub/binary_sensor.py index a2f71bfd9fe..f078bb4b363 100644 --- a/homeassistant/components/geniushub/binary_sensor.py +++ b/homeassistant/components/geniushub/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Genius Hub binary_sensor devices.""" + from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py index cb817c64930..02038ced198 100644 --- a/homeassistant/components/geniushub/climate.py +++ b/homeassistant/components/geniushub/climate.py @@ -1,4 +1,5 @@ """Support for Genius Hub climate devices.""" + from __future__ import annotations from homeassistant.components.climate import ( diff --git a/homeassistant/components/geniushub/sensor.py b/homeassistant/components/geniushub/sensor.py index 22d95be079e..998bd6f1edb 100644 --- a/homeassistant/components/geniushub/sensor.py +++ b/homeassistant/components/geniushub/sensor.py @@ -1,4 +1,5 @@ """Support for Genius Hub sensor devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/geniushub/switch.py b/homeassistant/components/geniushub/switch.py index 7b9bf8f6112..b703df57f90 100644 --- a/homeassistant/components/geniushub/switch.py +++ b/homeassistant/components/geniushub/switch.py @@ -1,4 +1,5 @@ """Support for Genius Hub switch/outlet devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index f8cf7288e57..6c3b5223ef9 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -1,4 +1,5 @@ """Support for Genius Hub water_heater devices.""" + from __future__ import annotations from homeassistant.components.water_heater import ( diff --git a/homeassistant/components/geo_json_events/__init__.py b/homeassistant/components/geo_json_events/__init__.py index 64b589f4f90..a50f7e432d9 100644 --- a/homeassistant/components/geo_json_events/__init__.py +++ b/homeassistant/components/geo_json_events/__init__.py @@ -1,4 +1,5 @@ """The GeoJSON events component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/geo_json_events/config_flow.py b/homeassistant/components/geo_json_events/config_flow.py index 2eb3443b3e5..65e5d2b1c75 100644 --- a/homeassistant/components/geo_json_events/config_flow.py +++ b/homeassistant/components/geo_json_events/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the GeoJSON events integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/geo_json_events/const.py b/homeassistant/components/geo_json_events/const.py index 15f8b0a5b84..679e8f2e565 100644 --- a/homeassistant/components/geo_json_events/const.py +++ b/homeassistant/components/geo_json_events/const.py @@ -1,4 +1,5 @@ """Define constants for the GeoJSON events integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index 134f6a0e943..e0067bcfdc9 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -1,4 +1,5 @@ """Support for generic GeoJSON events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/geo_json_events/manager.py b/homeassistant/components/geo_json_events/manager.py index 93f74831ecb..deff15436a6 100644 --- a/homeassistant/components/geo_json_events/manager.py +++ b/homeassistant/components/geo_json_events/manager.py @@ -1,4 +1,5 @@ """Entity manager for generic GeoJSON events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/geo_location/__init__.py b/homeassistant/components/geo_location/__init__.py index c5e91d32b20..d81fab65dc9 100644 --- a/homeassistant/components/geo_location/__init__.py +++ b/homeassistant/components/geo_location/__init__.py @@ -1,4 +1,5 @@ """Support for Geolocation.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/geo_location/trigger.py b/homeassistant/components/geo_location/trigger.py index f4ed94f1cf4..bb63440836f 100644 --- a/homeassistant/components/geo_location/trigger.py +++ b/homeassistant/components/geo_location/trigger.py @@ -1,4 +1,5 @@ """Offer geolocation automation rules.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index 3e6a0923886..d64c08a6917 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -4,6 +4,7 @@ Retrieves current events (typically incidents or alerts) in GeoRSS format, and shows information on events filtered by distance to the HA instance's location and grouped by category. """ + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/geocaching/config_flow.py b/homeassistant/components/geocaching/config_flow.py index f8b2391afba..05676cc346e 100644 --- a/homeassistant/components/geocaching/config_flow.py +++ b/homeassistant/components/geocaching/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Geocaching.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/geocaching/const.py b/homeassistant/components/geocaching/const.py index 13b42b318c0..8c255f5452a 100644 --- a/homeassistant/components/geocaching/const.py +++ b/homeassistant/components/geocaching/const.py @@ -1,4 +1,5 @@ """Constants for the Geocaching integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/geocaching/coordinator.py b/homeassistant/components/geocaching/coordinator.py index f02cccf544b..8f56cd9d846 100644 --- a/homeassistant/components/geocaching/coordinator.py +++ b/homeassistant/components/geocaching/coordinator.py @@ -1,4 +1,5 @@ """Provides the Geocaching DataUpdateCoordinator.""" + from __future__ import annotations from geocachingapi.exceptions import GeocachingApiError diff --git a/homeassistant/components/geocaching/models.py b/homeassistant/components/geocaching/models.py index 60ee4e05978..3ddea00217f 100644 --- a/homeassistant/components/geocaching/models.py +++ b/homeassistant/components/geocaching/models.py @@ -1,4 +1,5 @@ """Models for the Geocaching integration.""" + from typing import TypedDict diff --git a/homeassistant/components/geocaching/oauth.py b/homeassistant/components/geocaching/oauth.py index 848c4fce66c..c872f9a7522 100644 --- a/homeassistant/components/geocaching/oauth.py +++ b/homeassistant/components/geocaching/oauth.py @@ -1,4 +1,5 @@ """oAuth2 functions and classes for Geocaching API integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/geocaching/sensor.py b/homeassistant/components/geocaching/sensor.py index 91f7addae44..4065f199285 100644 --- a/homeassistant/components/geocaching/sensor.py +++ b/homeassistant/components/geocaching/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/geofency/__init__.py b/homeassistant/components/geofency/__init__.py index 2e6ed8429dd..547d7bdb5bc 100644 --- a/homeassistant/components/geofency/__init__.py +++ b/homeassistant/components/geofency/__init__.py @@ -1,4 +1,5 @@ """Support for Geofency.""" + from http import HTTPStatus from aiohttp import web diff --git a/homeassistant/components/geofency/config_flow.py b/homeassistant/components/geofency/config_flow.py index 2d8bce86d74..e51ef86b0cc 100644 --- a/homeassistant/components/geofency/config_flow.py +++ b/homeassistant/components/geofency/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Geofency.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index f0159915d32..eff937a2170 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -1,4 +1,5 @@ """Support for the Geofency device tracker platform.""" + from homeassistant.components.device_tracker import SourceType, TrackerEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE diff --git a/homeassistant/components/geonetnz_quakes/__init__.py b/homeassistant/components/geonetnz_quakes/__init__.py index dfe51f3119e..b9443d4aed8 100644 --- a/homeassistant/components/geonetnz_quakes/__init__.py +++ b/homeassistant/components/geonetnz_quakes/__init__.py @@ -1,4 +1,5 @@ """The GeoNet NZ Quakes integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/geonetnz_quakes/const.py b/homeassistant/components/geonetnz_quakes/const.py index 6ec2199f9e4..db529a17fbe 100644 --- a/homeassistant/components/geonetnz_quakes/const.py +++ b/homeassistant/components/geonetnz_quakes/const.py @@ -1,4 +1,5 @@ """Define constants for the GeoNet NZ Quakes integration.""" + from datetime import timedelta from homeassistant.const import Platform diff --git a/homeassistant/components/geonetnz_quakes/geo_location.py b/homeassistant/components/geonetnz_quakes/geo_location.py index 6fa84f590f1..f3458d96a18 100644 --- a/homeassistant/components/geonetnz_quakes/geo_location.py +++ b/homeassistant/components/geonetnz_quakes/geo_location.py @@ -1,4 +1,5 @@ """Geolocation support for GeoNet NZ Quakes Feeds.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/geonetnz_quakes/sensor.py b/homeassistant/components/geonetnz_quakes/sensor.py index e69ba6eb005..020b76a6c97 100644 --- a/homeassistant/components/geonetnz_quakes/sensor.py +++ b/homeassistant/components/geonetnz_quakes/sensor.py @@ -1,4 +1,5 @@ """Feed Entity Manager Sensor support for GeoNet NZ Quakes Feeds.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/geonetnz_volcano/__init__.py b/homeassistant/components/geonetnz_volcano/__init__.py index fb7770a5461..b08d6d62c55 100644 --- a/homeassistant/components/geonetnz_volcano/__init__.py +++ b/homeassistant/components/geonetnz_volcano/__init__.py @@ -1,4 +1,5 @@ """The GeoNet NZ Volcano integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/geonetnz_volcano/const.py b/homeassistant/components/geonetnz_volcano/const.py index 8c17ff42544..be04a25d27a 100644 --- a/homeassistant/components/geonetnz_volcano/const.py +++ b/homeassistant/components/geonetnz_volcano/const.py @@ -1,4 +1,5 @@ """Define constants for the GeoNet NZ Volcano integration.""" + from datetime import timedelta from homeassistant.const import Platform diff --git a/homeassistant/components/geonetnz_volcano/sensor.py b/homeassistant/components/geonetnz_volcano/sensor.py index f02e076b66c..6197577b56c 100644 --- a/homeassistant/components/geonetnz_volcano/sensor.py +++ b/homeassistant/components/geonetnz_volcano/sensor.py @@ -1,4 +1,5 @@ """Feed Entity Manager Sensor support for GeoNet NZ Volcano Feeds.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/gios/__init__.py b/homeassistant/components/gios/__init__.py index 88c505fc4ae..5810a32f80f 100644 --- a/homeassistant/components/gios/__init__.py +++ b/homeassistant/components/gios/__init__.py @@ -1,4 +1,5 @@ """The GIOS component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/gios/config_flow.py b/homeassistant/components/gios/config_flow.py index bad7c37eebd..a089aeab820 100644 --- a/homeassistant/components/gios/config_flow.py +++ b/homeassistant/components/gios/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for GIOS.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/gios/const.py b/homeassistant/components/gios/const.py index 33ddfae6fe1..a8490511ab8 100644 --- a/homeassistant/components/gios/const.py +++ b/homeassistant/components/gios/const.py @@ -1,4 +1,5 @@ """Constants for GIOS integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/gios/diagnostics.py b/homeassistant/components/gios/diagnostics.py index 954580d5c3f..1cdd9299a1c 100644 --- a/homeassistant/components/gios/diagnostics.py +++ b/homeassistant/components/gios/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for GIOS.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/gios/sensor.py b/homeassistant/components/gios/sensor.py index 1b13430128f..f097a14c943 100644 --- a/homeassistant/components/gios/sensor.py +++ b/homeassistant/components/gios/sensor.py @@ -1,4 +1,5 @@ """Support for the GIOS service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/gios/system_health.py b/homeassistant/components/gios/system_health.py index 589dc428bcb..46fe78556e2 100644 --- a/homeassistant/components/gios/system_health.py +++ b/homeassistant/components/gios/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/github/__init__.py b/homeassistant/components/github/__init__.py index 725c319dd58..20706c1acbc 100644 --- a/homeassistant/components/github/__init__.py +++ b/homeassistant/components/github/__init__.py @@ -1,4 +1,5 @@ """The GitHub integration.""" + from __future__ import annotations from aiogithubapi import GitHubAPI diff --git a/homeassistant/components/github/config_flow.py b/homeassistant/components/github/config_flow.py index 36f1b19eea7..2d9440928e3 100644 --- a/homeassistant/components/github/config_flow.py +++ b/homeassistant/components/github/config_flow.py @@ -1,4 +1,5 @@ """Config flow for GitHub integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/github/const.py b/homeassistant/components/github/const.py index d01656ee8ae..df44860b780 100644 --- a/homeassistant/components/github/const.py +++ b/homeassistant/components/github/const.py @@ -1,4 +1,5 @@ """Constants for the GitHub integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/github/coordinator.py b/homeassistant/components/github/coordinator.py index 45ab055aa9a..e73e02932e9 100644 --- a/homeassistant/components/github/coordinator.py +++ b/homeassistant/components/github/coordinator.py @@ -1,4 +1,5 @@ """Custom data update coordinator for the GitHub integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/github/diagnostics.py b/homeassistant/components/github/diagnostics.py index 15626497344..37313be3690 100644 --- a/homeassistant/components/github/diagnostics.py +++ b/homeassistant/components/github/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for the GitHub integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index cec0e6b763f..2d373de56ea 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the GitHub integration.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/gitlab_ci/sensor.py b/homeassistant/components/gitlab_ci/sensor.py index ed0db5416c1..d247ef5af60 100644 --- a/homeassistant/components/gitlab_ci/sensor.py +++ b/homeassistant/components/gitlab_ci/sensor.py @@ -1,4 +1,5 @@ """Sensor for retrieving latest GitLab CI job information.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/gitter/sensor.py b/homeassistant/components/gitter/sensor.py index db5b189d5ea..056c275c785 100644 --- a/homeassistant/components/gitter/sensor.py +++ b/homeassistant/components/gitter/sensor.py @@ -1,4 +1,5 @@ """Support for displaying details about a Gitter.im chat room.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/glances/config_flow.py b/homeassistant/components/glances/config_flow.py index 9fcf25aae0d..9208a4b0ebd 100644 --- a/homeassistant/components/glances/config_flow.py +++ b/homeassistant/components/glances/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Glances.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index f3718dc4c0e..35350e863fa 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -1,4 +1,5 @@ """Support gathering system information of hosts which are running glances.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/goalzero/__init__.py b/homeassistant/components/goalzero/__init__.py index 7a7000ba780..60b0338c258 100644 --- a/homeassistant/components/goalzero/__init__.py +++ b/homeassistant/components/goalzero/__init__.py @@ -1,4 +1,5 @@ """The Goal Zero Yeti integration.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/goalzero/binary_sensor.py b/homeassistant/components/goalzero/binary_sensor.py index 9464067d426..eec8773db30 100644 --- a/homeassistant/components/goalzero/binary_sensor.py +++ b/homeassistant/components/goalzero/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Goal Zero Yeti Sensors.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/goalzero/config_flow.py b/homeassistant/components/goalzero/config_flow.py index e4d997d41f6..c276db135fa 100644 --- a/homeassistant/components/goalzero/config_flow.py +++ b/homeassistant/components/goalzero/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Goal Zero Yeti integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/goalzero/sensor.py b/homeassistant/components/goalzero/sensor.py index b9a83453d7f..86f8bc9455b 100644 --- a/homeassistant/components/goalzero/sensor.py +++ b/homeassistant/components/goalzero/sensor.py @@ -1,4 +1,5 @@ """Support for Goal Zero Yeti Sensors.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/goalzero/switch.py b/homeassistant/components/goalzero/switch.py index 30680c6ff72..9c0aee03b83 100644 --- a/homeassistant/components/goalzero/switch.py +++ b/homeassistant/components/goalzero/switch.py @@ -1,4 +1,5 @@ """Support for Goal Zero Yeti Switches.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index 093c93699ff..01834187c70 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -1,4 +1,5 @@ """Common code for GogoGate2 component.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Mapping diff --git a/homeassistant/components/gogogate2/config_flow.py b/homeassistant/components/gogogate2/config_flow.py index d0a63260107..96ab97f5ba5 100644 --- a/homeassistant/components/gogogate2/config_flow.py +++ b/homeassistant/components/gogogate2/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Gogogate2.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index f0039d85295..17cfebe4a70 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,4 +1,5 @@ """Support for Gogogate2 garage Doors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/gogogate2/sensor.py b/homeassistant/components/gogogate2/sensor.py index 9100137843a..c67b7f371e2 100644 --- a/homeassistant/components/gogogate2/sensor.py +++ b/homeassistant/components/gogogate2/sensor.py @@ -1,4 +1,5 @@ """Support for Gogogate2 garage Doors.""" + from __future__ import annotations from itertools import chain diff --git a/homeassistant/components/goodwe/button.py b/homeassistant/components/goodwe/button.py index f551e09fc6a..62d9151061b 100644 --- a/homeassistant/components/goodwe/button.py +++ b/homeassistant/components/goodwe/button.py @@ -1,4 +1,5 @@ """GoodWe PV inverter selection settings entities.""" + from collections.abc import Awaitable, Callable from dataclasses import dataclass from datetime import datetime diff --git a/homeassistant/components/goodwe/config_flow.py b/homeassistant/components/goodwe/config_flow.py index 7e87f0307c8..d6a3be7e56a 100644 --- a/homeassistant/components/goodwe/config_flow.py +++ b/homeassistant/components/goodwe/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Goodwe inverters using their local API.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/goodwe/const.py b/homeassistant/components/goodwe/const.py index c5dbaaa49e5..730433c4a66 100644 --- a/homeassistant/components/goodwe/const.py +++ b/homeassistant/components/goodwe/const.py @@ -1,4 +1,5 @@ """Constants for the Goodwe component.""" + from datetime import timedelta from homeassistant.const import Platform diff --git a/homeassistant/components/goodwe/coordinator.py b/homeassistant/components/goodwe/coordinator.py index ac91fba787d..a8ee7df6337 100644 --- a/homeassistant/components/goodwe/coordinator.py +++ b/homeassistant/components/goodwe/coordinator.py @@ -1,4 +1,5 @@ """Update coordinator for Goodwe.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/goodwe/diagnostics.py b/homeassistant/components/goodwe/diagnostics.py index 285036c0254..600f02b9b7e 100644 --- a/homeassistant/components/goodwe/diagnostics.py +++ b/homeassistant/components/goodwe/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Goodwe.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py index 09e056da607..8bdcf13ae53 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -1,4 +1,5 @@ """GoodWe PV inverter numeric settings entities.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/goodwe/sensor.py b/homeassistant/components/goodwe/sensor.py index a43ff971a9a..795b26a0c9f 100644 --- a/homeassistant/components/goodwe/sensor.py +++ b/homeassistant/components/goodwe/sensor.py @@ -1,4 +1,5 @@ """Support for GoodWe inverter via UDP.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index e05a6f6fb97..9bb6dbd059f 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -1,4 +1,5 @@ """Support for Google - Calendar Event Devices.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/google/config_flow.py b/homeassistant/components/google/config_flow.py index cc6810df216..6207303c8a6 100644 --- a/homeassistant/components/google/config_flow.py +++ b/homeassistant/components/google/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/google/const.py b/homeassistant/components/google/const.py index 6f497543b2d..1e0b2fc910b 100644 --- a/homeassistant/components/google/const.py +++ b/homeassistant/components/google/const.py @@ -1,4 +1,5 @@ """Constants for google integration.""" + from __future__ import annotations from enum import Enum, StrEnum diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index 37f3a2c3edc..eb71361672a 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -1,4 +1,5 @@ """Support for Actions on Google Assistant Smart Home Control.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/google_assistant/button.py b/homeassistant/components/google_assistant/button.py index 139e3032f14..cf3a42e251b 100644 --- a/homeassistant/components/google_assistant/button.py +++ b/homeassistant/components/google_assistant/button.py @@ -1,4 +1,5 @@ """Support for buttons.""" + from __future__ import annotations from homeassistant import config_entries diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 431433e2bba..e97d8108965 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -1,4 +1,5 @@ """Constants for Google Assistant.""" + from homeassistant.components import ( alarm_control_panel, binary_sensor, diff --git a/homeassistant/components/google_assistant/data_redaction.py b/homeassistant/components/google_assistant/data_redaction.py index 6a187113bb9..50bd6dabf4c 100644 --- a/homeassistant/components/google_assistant/data_redaction.py +++ b/homeassistant/components/google_assistant/data_redaction.py @@ -1,4 +1,5 @@ """Helpers to redact Google Assistant data when logging.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/google_assistant/diagnostics.py b/homeassistant/components/google_assistant/diagnostics.py index fd4347ddd2c..48902147b05 100644 --- a/homeassistant/components/google_assistant/diagnostics.py +++ b/homeassistant/components/google_assistant/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Hue.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/google_assistant/error.py b/homeassistant/components/google_assistant/error.py index 82c256067eb..24d6b497103 100644 --- a/homeassistant/components/google_assistant/error.py +++ b/homeassistant/components/google_assistant/error.py @@ -1,4 +1,5 @@ """Errors for Google Assistant.""" + from .const import ERR_CHALLENGE_NEEDED diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 28479fd1e97..480a7e73d8b 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -1,4 +1,5 @@ """Helper classes for Google Assistant integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 20e9c3bf842..580d0e4a749 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -1,4 +1,5 @@ """Support for Google Actions Smart Home Control.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/google_assistant/logbook.py b/homeassistant/components/google_assistant/logbook.py index 9559188cbd2..686b11b63b5 100644 --- a/homeassistant/components/google_assistant/logbook.py +++ b/homeassistant/components/google_assistant/logbook.py @@ -1,4 +1,5 @@ """Describe logbook events.""" + from homeassistant.components.logbook import LOGBOOK_ENTRY_MESSAGE, LOGBOOK_ENTRY_NAME from homeassistant.core import callback diff --git a/homeassistant/components/google_assistant/report_state.py b/homeassistant/components/google_assistant/report_state.py index aec50011200..26a341bd7b6 100644 --- a/homeassistant/components/google_assistant/report_state.py +++ b/homeassistant/components/google_assistant/report_state.py @@ -1,4 +1,5 @@ """Google Report State implementation.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 169fa30386d..bf68ebd758c 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1,4 +1,5 @@ """Implement the Google Smart Home traits.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/google_assistant_sdk/__init__.py b/homeassistant/components/google_assistant_sdk/__init__.py index f77931b8d89..7d8653b509d 100644 --- a/homeassistant/components/google_assistant_sdk/__init__.py +++ b/homeassistant/components/google_assistant_sdk/__init__.py @@ -1,4 +1,5 @@ """Support for Google Assistant SDK.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/google_assistant_sdk/application_credentials.py b/homeassistant/components/google_assistant_sdk/application_credentials.py index c8a7922bc7a..8fa99157479 100644 --- a/homeassistant/components/google_assistant_sdk/application_credentials.py +++ b/homeassistant/components/google_assistant_sdk/application_credentials.py @@ -1,4 +1,5 @@ """application_credentials platform for Google Assistant SDK.""" + from homeassistant.components.application_credentials import AuthorizationServer from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/google_assistant_sdk/config_flow.py b/homeassistant/components/google_assistant_sdk/config_flow.py index 3813abb9b3a..85dfd974b22 100644 --- a/homeassistant/components/google_assistant_sdk/config_flow.py +++ b/homeassistant/components/google_assistant_sdk/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Assistant SDK integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/google_assistant_sdk/const.py b/homeassistant/components/google_assistant_sdk/const.py index d63aec0ebd5..4059f006d4b 100644 --- a/homeassistant/components/google_assistant_sdk/const.py +++ b/homeassistant/components/google_assistant_sdk/const.py @@ -1,4 +1,5 @@ """Constants for Google Assistant SDK integration.""" + from typing import Final DOMAIN: Final = "google_assistant_sdk" diff --git a/homeassistant/components/google_assistant_sdk/helpers.py b/homeassistant/components/google_assistant_sdk/helpers.py index a55ff92afe6..0c4d081961f 100644 --- a/homeassistant/components/google_assistant_sdk/helpers.py +++ b/homeassistant/components/google_assistant_sdk/helpers.py @@ -1,4 +1,5 @@ """Helper classes for Google Assistant SDK integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/google_assistant_sdk/notify.py b/homeassistant/components/google_assistant_sdk/notify.py index adcd07a0cda..5592f22ffeb 100644 --- a/homeassistant/components/google_assistant_sdk/notify.py +++ b/homeassistant/components/google_assistant_sdk/notify.py @@ -1,4 +1,5 @@ """Support for Google Assistant SDK broadcast notifications.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/google_generative_ai_conversation/__init__.py b/homeassistant/components/google_generative_ai_conversation/__init__.py index 73450e9f5b9..e956c288b53 100644 --- a/homeassistant/components/google_generative_ai_conversation/__init__.py +++ b/homeassistant/components/google_generative_ai_conversation/__init__.py @@ -1,4 +1,5 @@ """The Google Generative AI Conversation integration.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/google_generative_ai_conversation/config_flow.py b/homeassistant/components/google_generative_ai_conversation/config_flow.py index e386e924a7b..dde82db91cc 100644 --- a/homeassistant/components/google_generative_ai_conversation/config_flow.py +++ b/homeassistant/components/google_generative_ai_conversation/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Generative AI Conversation integration.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/google_mail/__init__.py b/homeassistant/components/google_mail/__init__.py index 311af064fcd..1ac963b430a 100644 --- a/homeassistant/components/google_mail/__init__.py +++ b/homeassistant/components/google_mail/__init__.py @@ -1,4 +1,5 @@ """Support for Google Mail.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry, ConfigEntryState diff --git a/homeassistant/components/google_mail/api.py b/homeassistant/components/google_mail/api.py index 10b2fec7467..e824e4b3ddd 100644 --- a/homeassistant/components/google_mail/api.py +++ b/homeassistant/components/google_mail/api.py @@ -1,4 +1,5 @@ """API for Google Mail bound to Home Assistant OAuth.""" + from aiohttp.client_exceptions import ClientError, ClientResponseError from google.auth.exceptions import RefreshError from google.oauth2.credentials import Credentials diff --git a/homeassistant/components/google_mail/application_credentials.py b/homeassistant/components/google_mail/application_credentials.py index 0b3b1dfd056..a1d3ddd6dc7 100644 --- a/homeassistant/components/google_mail/application_credentials.py +++ b/homeassistant/components/google_mail/application_credentials.py @@ -1,4 +1,5 @@ """application_credentials platform for Google Mail.""" + from homeassistant.components.application_credentials import AuthorizationServer from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/google_mail/config_flow.py b/homeassistant/components/google_mail/config_flow.py index 19f82663c34..5b5c760628b 100644 --- a/homeassistant/components/google_mail/config_flow.py +++ b/homeassistant/components/google_mail/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Mail integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/google_mail/const.py b/homeassistant/components/google_mail/const.py index 6e70ea9838c..523182df072 100644 --- a/homeassistant/components/google_mail/const.py +++ b/homeassistant/components/google_mail/const.py @@ -1,4 +1,5 @@ """Constants for Google Mail integration.""" + from __future__ import annotations ATTR_BCC = "bcc" diff --git a/homeassistant/components/google_mail/entity.py b/homeassistant/components/google_mail/entity.py index fed8ff481f0..d83b18b9a50 100644 --- a/homeassistant/components/google_mail/entity.py +++ b/homeassistant/components/google_mail/entity.py @@ -1,4 +1,5 @@ """Entity representing a Google Mail account.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo diff --git a/homeassistant/components/google_mail/notify.py b/homeassistant/components/google_mail/notify.py index 974b2e4e4bf..73c99d54ff3 100644 --- a/homeassistant/components/google_mail/notify.py +++ b/homeassistant/components/google_mail/notify.py @@ -1,4 +1,5 @@ """Notification service for Google Mail integration.""" + from __future__ import annotations import base64 diff --git a/homeassistant/components/google_mail/sensor.py b/homeassistant/components/google_mail/sensor.py index 78b0e3c9a91..1de72632de1 100644 --- a/homeassistant/components/google_mail/sensor.py +++ b/homeassistant/components/google_mail/sensor.py @@ -1,4 +1,5 @@ """Support for Google Mail Sensors.""" + from __future__ import annotations from datetime import UTC, datetime, timedelta diff --git a/homeassistant/components/google_mail/services.py b/homeassistant/components/google_mail/services.py index 1450a5d31b8..e07e2be2101 100644 --- a/homeassistant/components/google_mail/services.py +++ b/homeassistant/components/google_mail/services.py @@ -1,4 +1,5 @@ """Services for Google Mail integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/google_maps/device_tracker.py b/homeassistant/components/google_maps/device_tracker.py index 93810d0f21d..b3b0430271a 100644 --- a/homeassistant/components/google_maps/device_tracker.py +++ b/homeassistant/components/google_maps/device_tracker.py @@ -1,4 +1,5 @@ """Support for Google Maps location sharing.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/google_pubsub/__init__.py b/homeassistant/components/google_pubsub/__init__.py index 22b7b358dbc..74e2a297ff4 100644 --- a/homeassistant/components/google_pubsub/__init__.py +++ b/homeassistant/components/google_pubsub/__init__.py @@ -1,4 +1,5 @@ """Support for Google Cloud Pub/Sub.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/google_sheets/__init__.py b/homeassistant/components/google_sheets/__init__.py index ba2a0884e22..bf7cf7c40df 100644 --- a/homeassistant/components/google_sheets/__init__.py +++ b/homeassistant/components/google_sheets/__init__.py @@ -1,4 +1,5 @@ """Support for Google Sheets.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/google_sheets/application_credentials.py b/homeassistant/components/google_sheets/application_credentials.py index f10f6891125..8e1f96bfa6e 100644 --- a/homeassistant/components/google_sheets/application_credentials.py +++ b/homeassistant/components/google_sheets/application_credentials.py @@ -1,4 +1,5 @@ """application_credentials platform for Google Sheets.""" + from homeassistant.components.application_credentials import AuthorizationServer from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/google_sheets/config_flow.py b/homeassistant/components/google_sheets/config_flow.py index 4c1455f3c2e..a0a99742249 100644 --- a/homeassistant/components/google_sheets/config_flow.py +++ b/homeassistant/components/google_sheets/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Sheets integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/google_sheets/const.py b/homeassistant/components/google_sheets/const.py index 71ef8f4a4f4..b93916db0a6 100644 --- a/homeassistant/components/google_sheets/const.py +++ b/homeassistant/components/google_sheets/const.py @@ -1,4 +1,5 @@ """Constants for Google Sheets integration.""" + from __future__ import annotations DOMAIN = "google_sheets" diff --git a/homeassistant/components/google_tasks/__init__.py b/homeassistant/components/google_tasks/__init__.py index da6fc85b287..b62bd0fe5a2 100644 --- a/homeassistant/components/google_tasks/__init__.py +++ b/homeassistant/components/google_tasks/__init__.py @@ -1,4 +1,5 @@ """The Google Tasks integration.""" + from __future__ import annotations from aiohttp import ClientError diff --git a/homeassistant/components/google_tasks/todo.py b/homeassistant/components/google_tasks/todo.py index e83b0d39a30..95c5f1c3a16 100644 --- a/homeassistant/components/google_tasks/todo.py +++ b/homeassistant/components/google_tasks/todo.py @@ -1,4 +1,5 @@ """Google Tasks todo platform.""" + from __future__ import annotations from datetime import date, datetime, timedelta diff --git a/homeassistant/components/google_translate/__init__.py b/homeassistant/components/google_translate/__init__.py index ac6b07bd4b3..17400bbd0e2 100644 --- a/homeassistant/components/google_translate/__init__.py +++ b/homeassistant/components/google_translate/__init__.py @@ -1,4 +1,5 @@ """The Google Translate text-to-speech integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/google_translate/config_flow.py b/homeassistant/components/google_translate/config_flow.py index a5b94bafd90..5c140167b81 100644 --- a/homeassistant/components/google_translate/config_flow.py +++ b/homeassistant/components/google_translate/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Translate text-to-speech integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/google_translate/const.py b/homeassistant/components/google_translate/const.py index 0bb8663119b..76827606816 100644 --- a/homeassistant/components/google_translate/const.py +++ b/homeassistant/components/google_translate/const.py @@ -1,4 +1,5 @@ """Constants for the Google Translate text-to-speech integration.""" + from dataclasses import dataclass CONF_TLD = "tld" diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index 7774d9fd6c8..df7130e09e0 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -1,4 +1,5 @@ """Support for the Google speech service.""" + from __future__ import annotations from io import BytesIO diff --git a/homeassistant/components/google_travel_time/__init__.py b/homeassistant/components/google_travel_time/__init__.py index b2778a34c10..4ee9d53cf3b 100644 --- a/homeassistant/components/google_travel_time/__init__.py +++ b/homeassistant/components/google_travel_time/__init__.py @@ -1,4 +1,5 @@ """The google_travel_time component.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/google_travel_time/config_flow.py b/homeassistant/components/google_travel_time/config_flow.py index 8076df53433..424ad56b9d4 100644 --- a/homeassistant/components/google_travel_time/config_flow.py +++ b/homeassistant/components/google_travel_time/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Maps Travel Time integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/google_travel_time/sensor.py b/homeassistant/components/google_travel_time/sensor.py index 95eb965a4ff..6c45033eeb7 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -1,4 +1,5 @@ """Support for Google travel time sensors.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index f90cc028fdf..71e3e2e3652 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -1,4 +1,5 @@ """Support for retrieving status info from Google Wifi/OnHub routers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/govee_ble/__init__.py b/homeassistant/components/govee_ble/__init__.py index a2dbecf85a2..d5c601b55d9 100644 --- a/homeassistant/components/govee_ble/__init__.py +++ b/homeassistant/components/govee_ble/__init__.py @@ -1,4 +1,5 @@ """The Govee Bluetooth BLE integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/govee_ble/config_flow.py b/homeassistant/components/govee_ble/config_flow.py index ecd39cfbff0..f580fca68d8 100644 --- a/homeassistant/components/govee_ble/config_flow.py +++ b/homeassistant/components/govee_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for govee ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/govee_ble/sensor.py b/homeassistant/components/govee_ble/sensor.py index 3809a2390f3..33f4761d02a 100644 --- a/homeassistant/components/govee_ble/sensor.py +++ b/homeassistant/components/govee_ble/sensor.py @@ -1,4 +1,5 @@ """Support for govee ble sensors.""" + from __future__ import annotations from govee_ble import DeviceClass, DeviceKey, SensorUpdate, Units diff --git a/homeassistant/components/govee_light_local/__init__.py b/homeassistant/components/govee_light_local/__init__.py index 2d4594755c4..5a791b9e3d1 100644 --- a/homeassistant/components/govee_light_local/__init__.py +++ b/homeassistant/components/govee_light_local/__init__.py @@ -1,4 +1,5 @@ """The Govee Light local integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/gpsd/__init__.py b/homeassistant/components/gpsd/__init__.py index bdd5ddb13b0..a0e3db2e404 100644 --- a/homeassistant/components/gpsd/__init__.py +++ b/homeassistant/components/gpsd/__init__.py @@ -1,4 +1,5 @@ """The GPSD integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/gpsd/config_flow.py b/homeassistant/components/gpsd/config_flow.py index 9979fd6c922..10fb8a3a252 100644 --- a/homeassistant/components/gpsd/config_flow.py +++ b/homeassistant/components/gpsd/config_flow.py @@ -1,4 +1,5 @@ """Config flow for GPSD integration.""" + from __future__ import annotations import socket diff --git a/homeassistant/components/gpsd/sensor.py b/homeassistant/components/gpsd/sensor.py index 135d9c6c28f..bc08b7b6203 100644 --- a/homeassistant/components/gpsd/sensor.py +++ b/homeassistant/components/gpsd/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for GPSD integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/gpslogger/__init__.py b/homeassistant/components/gpslogger/__init__.py index 9f00e2cb52d..5d35314adf8 100644 --- a/homeassistant/components/gpslogger/__init__.py +++ b/homeassistant/components/gpslogger/__init__.py @@ -1,4 +1,5 @@ """Support for GPSLogger.""" + from http import HTTPStatus from aiohttp import web diff --git a/homeassistant/components/gpslogger/config_flow.py b/homeassistant/components/gpslogger/config_flow.py index ef90a8d1607..b0a114a4330 100644 --- a/homeassistant/components/gpslogger/config_flow.py +++ b/homeassistant/components/gpslogger/config_flow.py @@ -1,4 +1,5 @@ """Config flow for GPSLogger.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index 278d6571cb7..025183718fc 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -1,4 +1,5 @@ """Support for the GPSLogger device tracking.""" + from homeassistant.components.device_tracker import SourceType, TrackerEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index 8e9b015cdad..17dd140aef7 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to a Graphite installation.""" + from contextlib import suppress import logging import queue diff --git a/homeassistant/components/gree/__init__.py b/homeassistant/components/gree/__init__.py index 13e93d780b2..5b2e95b15e2 100644 --- a/homeassistant/components/gree/__init__.py +++ b/homeassistant/components/gree/__init__.py @@ -1,4 +1,5 @@ """The Gree Climate integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/gree/bridge.py b/homeassistant/components/gree/bridge.py index ebd5e78a820..867f742e821 100644 --- a/homeassistant/components/gree/bridge.py +++ b/homeassistant/components/gree/bridge.py @@ -1,4 +1,5 @@ """Helper and wrapper classes for Gree module.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/gree/climate.py b/homeassistant/components/gree/climate.py index 1d061c06901..66b025d52b5 100644 --- a/homeassistant/components/gree/climate.py +++ b/homeassistant/components/gree/climate.py @@ -1,4 +1,5 @@ """Support for interface with a Gree climate systems.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/gree/config_flow.py b/homeassistant/components/gree/config_flow.py index 58f83cd4486..5e118088916 100644 --- a/homeassistant/components/gree/config_flow.py +++ b/homeassistant/components/gree/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Gree.""" + from greeclimate.discovery import Discovery from homeassistant.components.network import async_get_ipv4_broadcast_addresses diff --git a/homeassistant/components/gree/entity.py b/homeassistant/components/gree/entity.py index c965ad45721..4eb4a0cbaeb 100644 --- a/homeassistant/components/gree/entity.py +++ b/homeassistant/components/gree/entity.py @@ -1,4 +1,5 @@ """Entity object for shared properties of Gree entities.""" + from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/gree/switch.py b/homeassistant/components/gree/switch.py index e18cf28e174..c1612ce99de 100644 --- a/homeassistant/components/gree/switch.py +++ b/homeassistant/components/gree/switch.py @@ -1,4 +1,5 @@ """Support for interface with a Gree climate systems.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/greeneye_monitor/__init__.py b/homeassistant/components/greeneye_monitor/__init__.py index e4090787e65..083d431e338 100644 --- a/homeassistant/components/greeneye_monitor/__init__.py +++ b/homeassistant/components/greeneye_monitor/__init__.py @@ -1,4 +1,5 @@ """Support for monitoring a GreenEye Monitor energy monitor.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index c11a4168045..c11e08eaa84 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -1,4 +1,5 @@ """Support for the sensors in a GreenEye Monitor.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/greenwave/light.py b/homeassistant/components/greenwave/light.py index 9f904018796..aa592727220 100644 --- a/homeassistant/components/greenwave/light.py +++ b/homeassistant/components/greenwave/light.py @@ -1,4 +1,5 @@ """Support for Greenwave Reality (TCP Connected) lights.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 9ee81191bf8..af843db6257 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -1,4 +1,5 @@ """Provide the functionality to group entities.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/group/binary_sensor.py b/homeassistant/components/group/binary_sensor.py index d63dcb5e8f2..16665e8970f 100644 --- a/homeassistant/components/group/binary_sensor.py +++ b/homeassistant/components/group/binary_sensor.py @@ -1,4 +1,5 @@ """Platform allowing several binary sensor to be grouped into one binary sensor.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index 488f5e131f3..71243ffbd74 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Group integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine, Mapping diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index 78d29378076..8d521314f96 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -1,4 +1,5 @@ """Platform allowing several cover to be grouped into one cover.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/group/event.py b/homeassistant/components/group/event.py index b98991e13fc..def92b3d6d4 100644 --- a/homeassistant/components/group/event.py +++ b/homeassistant/components/group/event.py @@ -1,4 +1,5 @@ """Platform allowing several event entities to be grouped into one event.""" + from __future__ import annotations import itertools diff --git a/homeassistant/components/group/fan.py b/homeassistant/components/group/fan.py index afd240c5767..c8add0b6724 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -1,4 +1,5 @@ """Platform allowing several fans to be grouped into one fan.""" + from __future__ import annotations from functools import reduce diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index c8689cdaa1c..d014ca5d618 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -1,4 +1,5 @@ """Platform allowing several lights to be grouped into one light.""" + from __future__ import annotations from collections import Counter diff --git a/homeassistant/components/group/lock.py b/homeassistant/components/group/lock.py index 4a6fdc3e2ed..08c2c053b0e 100644 --- a/homeassistant/components/group/lock.py +++ b/homeassistant/components/group/lock.py @@ -1,4 +1,5 @@ """Platform allowing several locks to be grouped into one lock.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/group/media_player.py b/homeassistant/components/group/media_player.py index aa38f364d93..82a15177a4a 100644 --- a/homeassistant/components/group/media_player.py +++ b/homeassistant/components/group/media_player.py @@ -1,4 +1,5 @@ """Platform allowing several media players to be grouped into one media player.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/group/notify.py b/homeassistant/components/group/notify.py index 2747ba55ee1..bad3d7944d3 100644 --- a/homeassistant/components/group/notify.py +++ b/homeassistant/components/group/notify.py @@ -1,4 +1,5 @@ """Group platform for notify component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index c99f098b222..3646f957597 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -1,4 +1,5 @@ """Module that groups code required to handle state restore for component.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/group/switch.py b/homeassistant/components/group/switch.py index 3f68d7125aa..ec70f137b33 100644 --- a/homeassistant/components/group/switch.py +++ b/homeassistant/components/group/switch.py @@ -1,4 +1,5 @@ """Platform allowing several switches to be grouped into one switch.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/group/util.py b/homeassistant/components/group/util.py index da67e071f27..14f5064290f 100644 --- a/homeassistant/components/group/util.py +++ b/homeassistant/components/group/util.py @@ -1,4 +1,5 @@ """Utility functions to combine state attributes from multiple entities.""" + from __future__ import annotations from collections.abc import Callable, Iterator diff --git a/homeassistant/components/growatt_server/__init__.py b/homeassistant/components/growatt_server/__init__.py index 177d0957883..66df76bc6cb 100644 --- a/homeassistant/components/growatt_server/__init__.py +++ b/homeassistant/components/growatt_server/__init__.py @@ -1,4 +1,5 @@ """The Growatt server PV inverter sensor integration.""" + from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/growatt_server/const.py b/homeassistant/components/growatt_server/const.py index e4e7c638fa3..fe8622bea7f 100644 --- a/homeassistant/components/growatt_server/const.py +++ b/homeassistant/components/growatt_server/const.py @@ -1,4 +1,5 @@ """Define constants for the Growatt Server component.""" + from homeassistant.const import Platform CONF_PLANT_ID = "plant_id" diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 06d06ed26ce..3cf1fa30c99 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -1,4 +1,5 @@ """Read status of growatt inverters.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/growatt_server/sensor_types/inverter.py b/homeassistant/components/growatt_server/sensor_types/inverter.py index cfacadce528..6f78e019184 100644 --- a/homeassistant/components/growatt_server/sensor_types/inverter.py +++ b/homeassistant/components/growatt_server/sensor_types/inverter.py @@ -1,4 +1,5 @@ """Growatt Sensor definitions for the Inverter type.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/growatt_server/sensor_types/mix.py b/homeassistant/components/growatt_server/sensor_types/mix.py index e9722abda11..b741a589b8f 100644 --- a/homeassistant/components/growatt_server/sensor_types/mix.py +++ b/homeassistant/components/growatt_server/sensor_types/mix.py @@ -1,4 +1,5 @@ """Growatt Sensor definitions for the Mix type.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py b/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py index cfeb98a382e..e1ee4c30326 100644 --- a/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py +++ b/homeassistant/components/growatt_server/sensor_types/sensor_entity_description.py @@ -1,4 +1,5 @@ """Sensor Entity Description for the Growatt integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/growatt_server/sensor_types/storage.py b/homeassistant/components/growatt_server/sensor_types/storage.py index 4b60a73c979..e8895755c65 100644 --- a/homeassistant/components/growatt_server/sensor_types/storage.py +++ b/homeassistant/components/growatt_server/sensor_types/storage.py @@ -1,4 +1,5 @@ """Growatt Sensor definitions for the Storage type.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/growatt_server/sensor_types/tlx.py b/homeassistant/components/growatt_server/sensor_types/tlx.py index 645b32db9d0..d8f158f2421 100644 --- a/homeassistant/components/growatt_server/sensor_types/tlx.py +++ b/homeassistant/components/growatt_server/sensor_types/tlx.py @@ -2,6 +2,7 @@ TLX Type is also shown on the UI as: "MIN/MIC/MOD/NEO" """ + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/growatt_server/sensor_types/total.py b/homeassistant/components/growatt_server/sensor_types/total.py index 5945ad20e40..8111728d1e9 100644 --- a/homeassistant/components/growatt_server/sensor_types/total.py +++ b/homeassistant/components/growatt_server/sensor_types/total.py @@ -1,4 +1,5 @@ """Growatt Sensor definitions for Totals.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index cb221d49417..054b31c2fbe 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -1,4 +1,5 @@ """Play media via gstreamer.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 87d2b55aa24..8a20755e936 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -1,4 +1,5 @@ """Support for GTFS (Google/General Transport Format Schema).""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 8a3ac265618..4c6a979cd1d 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -1,4 +1,5 @@ """The Elexa Guardian integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index c7094cf624c..c3621ea2d79 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensors for the Elexa Guardian integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/guardian/button.py b/homeassistant/components/guardian/button.py index cb9c6f0121c..8313ad23007 100644 --- a/homeassistant/components/guardian/button.py +++ b/homeassistant/components/guardian/button.py @@ -1,4 +1,5 @@ """Buttons for the Elexa Guardian integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index fb160c7f959..e73e6c586ce 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Elexa Guardian integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/guardian/coordinator.py b/homeassistant/components/guardian/coordinator.py index dda0a20be69..819fda8bdc7 100644 --- a/homeassistant/components/guardian/coordinator.py +++ b/homeassistant/components/guardian/coordinator.py @@ -1,4 +1,5 @@ """Define Guardian-specific utilities.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/guardian/diagnostics.py b/homeassistant/components/guardian/diagnostics.py index b0317167f79..2f4287bea29 100644 --- a/homeassistant/components/guardian/diagnostics.py +++ b/homeassistant/components/guardian/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Guardian.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 1941dc54248..448a7231df1 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -1,4 +1,5 @@ """Sensors for the Elexa Guardian integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py index ebe8e5549ce..25bc8115208 100644 --- a/homeassistant/components/guardian/switch.py +++ b/homeassistant/components/guardian/switch.py @@ -1,4 +1,5 @@ """Switches for the Elexa Guardian integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Mapping diff --git a/homeassistant/components/guardian/util.py b/homeassistant/components/guardian/util.py index a5e91dce813..6d407f9c7cc 100644 --- a/homeassistant/components/guardian/util.py +++ b/homeassistant/components/guardian/util.py @@ -1,4 +1,5 @@ """Define Guardian-specific utilities.""" + from __future__ import annotations from collections.abc import Callable, Coroutine, Iterable diff --git a/homeassistant/components/guardian/valve.py b/homeassistant/components/guardian/valve.py index a2b6b5b6ab7..fcedc71f188 100644 --- a/homeassistant/components/guardian/valve.py +++ b/homeassistant/components/guardian/valve.py @@ -1,4 +1,5 @@ """Valves for the Elexa Guardian integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/habitica/config_flow.py b/homeassistant/components/habitica/config_flow.py index 4c607ea4a38..18bdb4b45ce 100644 --- a/homeassistant/components/habitica/config_flow.py +++ b/homeassistant/components/habitica/config_flow.py @@ -1,4 +1,5 @@ """Config flow for habitica integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index d9e0fb227c0..eb37f585899 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -1,4 +1,5 @@ """Support for Habitica sensors.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/hardkernel/__init__.py b/homeassistant/components/hardkernel/__init__.py index 41e65ff8b5e..5d70f6cbfe0 100644 --- a/homeassistant/components/hardkernel/__init__.py +++ b/homeassistant/components/hardkernel/__init__.py @@ -1,4 +1,5 @@ """The Hardkernel integration.""" + from __future__ import annotations from homeassistant.components.hassio import get_os_info, is_hassio diff --git a/homeassistant/components/hardkernel/config_flow.py b/homeassistant/components/hardkernel/config_flow.py index 457ad3b97b5..cf70adae55a 100644 --- a/homeassistant/components/hardkernel/config_flow.py +++ b/homeassistant/components/hardkernel/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Hardkernel integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hardkernel/hardware.py b/homeassistant/components/hardkernel/hardware.py index c94de0db68d..86dcf073680 100644 --- a/homeassistant/components/hardkernel/hardware.py +++ b/homeassistant/components/hardkernel/hardware.py @@ -1,4 +1,5 @@ """The Hardkernel hardware platform.""" + from __future__ import annotations from homeassistant.components.hardware.models import BoardInfo, HardwareInfo diff --git a/homeassistant/components/hardware/__init__.py b/homeassistant/components/hardware/__init__.py index 2e00771199c..9de281b1e50 100644 --- a/homeassistant/components/hardware/__init__.py +++ b/homeassistant/components/hardware/__init__.py @@ -1,4 +1,5 @@ """The Hardware integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/hardware/hardware.py b/homeassistant/components/hardware/hardware.py index 800f15137c6..f2de9182b57 100644 --- a/homeassistant/components/hardware/hardware.py +++ b/homeassistant/components/hardware/hardware.py @@ -1,4 +1,5 @@ """The Hardware integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/hardware/models.py b/homeassistant/components/hardware/models.py index 6b852291323..6f25d6669cf 100644 --- a/homeassistant/components/hardware/models.py +++ b/homeassistant/components/hardware/models.py @@ -1,4 +1,5 @@ """Models for Hardware.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/hardware/websocket_api.py b/homeassistant/components/hardware/websocket_api.py index d4e4f2fed5c..694c6e782e1 100644 --- a/homeassistant/components/hardware/websocket_api.py +++ b/homeassistant/components/hardware/websocket_api.py @@ -1,4 +1,5 @@ """The Hardware websocket API.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/harman_kardon_avr/media_player.py b/homeassistant/components/harman_kardon_avr/media_player.py index 17b3d0c5717..815a8f52b42 100644 --- a/homeassistant/components/harman_kardon_avr/media_player.py +++ b/homeassistant/components/harman_kardon_avr/media_player.py @@ -1,4 +1,5 @@ """Support for interface with an Harman/Kardon or JBL AVR.""" + from __future__ import annotations import hkavr diff --git a/homeassistant/components/harmony/config_flow.py b/homeassistant/components/harmony/config_flow.py index 5732740dd20..b579e7659f4 100644 --- a/homeassistant/components/harmony/config_flow.py +++ b/homeassistant/components/harmony/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Logitech Harmony Hub integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/harmony/const.py b/homeassistant/components/harmony/const.py index 8df5b3d578c..69ef2cb66c9 100644 --- a/homeassistant/components/harmony/const.py +++ b/homeassistant/components/harmony/const.py @@ -1,4 +1,5 @@ """Constants for the Harmony component.""" + from homeassistant.const import Platform DOMAIN = "harmony" diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index f7eb96d6a8f..75678391e7b 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -1,4 +1,5 @@ """Harmony data object which contains the Harmony Client.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/harmony/entity.py b/homeassistant/components/harmony/entity.py index b1b1599a16c..f87d30a0e40 100644 --- a/homeassistant/components/harmony/entity.py +++ b/homeassistant/components/harmony/entity.py @@ -1,4 +1,5 @@ """Base class Harmony entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 863c3fe5c56..90b7f29b17f 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -1,4 +1,5 @@ """Support for Harmony Hub devices.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/harmony/select.py b/homeassistant/components/harmony/select.py index f08030c0152..6338f0c6269 100644 --- a/homeassistant/components/harmony/select.py +++ b/homeassistant/components/harmony/select.py @@ -1,4 +1,5 @@ """Support for Harmony Hub select activities.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/harmony/subscriber.py b/homeassistant/components/harmony/subscriber.py index 8a47e437e17..4623e8fa48d 100644 --- a/homeassistant/components/harmony/subscriber.py +++ b/homeassistant/components/harmony/subscriber.py @@ -1,4 +1,5 @@ """Mixin class for handling harmony callback subscriptions.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 1a666fcc42d..802b84cee43 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -1,4 +1,5 @@ """Support for Hass.io.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/addon_manager.py b/homeassistant/components/hassio/addon_manager.py index 7f9299fa2b1..fcdcd38f776 100644 --- a/homeassistant/components/hassio/addon_manager.py +++ b/homeassistant/components/hassio/addon_manager.py @@ -1,4 +1,5 @@ """Provide add-on management.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/addon_panel.py b/homeassistant/components/hassio/addon_panel.py index db53b2f90fc..f0ccecb22f1 100644 --- a/homeassistant/components/hassio/addon_panel.py +++ b/homeassistant/components/hassio/addon_panel.py @@ -1,4 +1,5 @@ """Implement the Ingress Panel feature for Hass.io Add-ons.""" + from http import HTTPStatus import logging from typing import Any diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py index 7ee9f04120a..52016b20659 100644 --- a/homeassistant/components/hassio/auth.py +++ b/homeassistant/components/hassio/auth.py @@ -1,4 +1,5 @@ """Implement the auth feature from Hass.io for Add-ons.""" + from http import HTTPStatus from ipaddress import ip_address import logging diff --git a/homeassistant/components/hassio/binary_sensor.py b/homeassistant/components/hassio/binary_sensor.py index b5b4bef1bd8..9d6e2ba19da 100644 --- a/homeassistant/components/hassio/binary_sensor.py +++ b/homeassistant/components/hassio/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform for Hass.io addons.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/hassio/config_flow.py b/homeassistant/components/hassio/config_flow.py index a4c79a97675..57be400acc7 100644 --- a/homeassistant/components/hassio/config_flow.py +++ b/homeassistant/components/hassio/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Home Assistant Supervisor integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 4559326b521..2aa5fdd13b5 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -1,4 +1,5 @@ """Hass.io const variables.""" + from datetime import timedelta from enum import StrEnum diff --git a/homeassistant/components/hassio/data.py b/homeassistant/components/hassio/data.py index cb496bd0e5a..eaa7c2431fe 100644 --- a/homeassistant/components/hassio/data.py +++ b/homeassistant/components/hassio/data.py @@ -1,4 +1,5 @@ """Data for Hass.io.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/diagnostics.py b/homeassistant/components/hassio/diagnostics.py index e4f98e87fc5..ae8b8b3b740 100644 --- a/homeassistant/components/hassio/diagnostics.py +++ b/homeassistant/components/hassio/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Supervisor.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index 1810e3ed2c5..173583c9891 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -1,4 +1,5 @@ """Implement the services discovery feature from Hass.io for Add-ons.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/entity.py b/homeassistant/components/hassio/entity.py index 8ffe4a37bb0..11259c65d24 100644 --- a/homeassistant/components/hassio/entity.py +++ b/homeassistant/components/hassio/entity.py @@ -1,4 +1,5 @@ """Base for Hass.io entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 9b8e6367647..bcfb4a497a0 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -1,4 +1,5 @@ """Handler for Hass.io.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index afaa57dda9a..edbcf93d7e5 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -1,4 +1,5 @@ """HTTP Support for Hass.io.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index f9ff1dd7770..d325fb10f35 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -1,4 +1,5 @@ """Hass.io Add-on ingress service.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/issues.py b/homeassistant/components/hassio/issues.py index 8bd47faef08..c47ea315089 100644 --- a/homeassistant/components/hassio/issues.py +++ b/homeassistant/components/hassio/issues.py @@ -1,4 +1,5 @@ """Supervisor events monitor.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hassio/repairs.py b/homeassistant/components/hassio/repairs.py index fcfe23dda6e..42fe5148be7 100644 --- a/homeassistant/components/hassio/repairs.py +++ b/homeassistant/components/hassio/repairs.py @@ -1,4 +1,5 @@ """Repairs implementation for supervisor integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index aeb0a8fc056..5a981d6c96c 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Hass.io addons.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/hassio/system_health.py b/homeassistant/components/hassio/system_health.py index e46db2f8e75..b77187718bb 100644 --- a/homeassistant/components/hassio/system_health.py +++ b/homeassistant/components/hassio/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations import os diff --git a/homeassistant/components/hassio/update.py b/homeassistant/components/hassio/update.py index 41a505bb043..6a3bba90234 100644 --- a/homeassistant/components/hassio/update.py +++ b/homeassistant/components/hassio/update.py @@ -1,4 +1,5 @@ """Update platform for Supervisor.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index 7caf9690bd8..c0a55279b82 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -1,4 +1,5 @@ """Support for haveibeenpwned (email breaches) sensor.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index 77c2a28190b..27a514bd58f 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -1,4 +1,5 @@ """Support for getting the disk temperature of a host.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 54ea2f3e5bd..07a45cf8480 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -1,4 +1,5 @@ """Support for HDMI CEC.""" + from __future__ import annotations from functools import reduce diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index df7df830fdb..4cd95a6c382 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -1,4 +1,5 @@ """Support for HDMI CEC devices as media players.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py index a554594a219..280ea20413b 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -1,4 +1,5 @@ """Support for HDMI CEC devices as switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index 566a4696a73..8639d1f953e 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -1,4 +1,5 @@ """Support for the PRT Heatmiser themostats using the V3 protocol.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index c50b70245e3..ed7c768e161 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -1,4 +1,5 @@ """Denon HEOS Media Player.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 9e7e288dffc..b68d7d16717 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Heos.""" + from typing import TYPE_CHECKING from urllib.parse import urlparse diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 8502dec28fa..5fd64ec7d38 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -1,4 +1,5 @@ """Denon HEOS Media Player.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py index 1c728bcc12c..19ba1f8ebaf 100644 --- a/homeassistant/components/here_travel_time/__init__.py +++ b/homeassistant/components/here_travel_time/__init__.py @@ -1,4 +1,5 @@ """The HERE Travel Time integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/here_travel_time/config_flow.py b/homeassistant/components/here_travel_time/config_flow.py index 9bca80b7843..d27ea577c29 100644 --- a/homeassistant/components/here_travel_time/config_flow.py +++ b/homeassistant/components/here_travel_time/config_flow.py @@ -1,4 +1,5 @@ """Config flow for HERE Travel Time integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/here_travel_time/const.py b/homeassistant/components/here_travel_time/const.py index a2a14a445d7..785070cd3b1 100644 --- a/homeassistant/components/here_travel_time/const.py +++ b/homeassistant/components/here_travel_time/const.py @@ -1,4 +1,5 @@ """Constants for the HERE Travel Time integration.""" + from typing import Final DOMAIN = "here_travel_time" diff --git a/homeassistant/components/here_travel_time/coordinator.py b/homeassistant/components/here_travel_time/coordinator.py index dbb17b58336..6591f4cb5cc 100644 --- a/homeassistant/components/here_travel_time/coordinator.py +++ b/homeassistant/components/here_travel_time/coordinator.py @@ -1,4 +1,5 @@ """The HERE Travel Time integration.""" + from __future__ import annotations from datetime import datetime, time, timedelta diff --git a/homeassistant/components/here_travel_time/model.py b/homeassistant/components/here_travel_time/model.py index 433653cbf56..178c0d8c805 100644 --- a/homeassistant/components/here_travel_time/model.py +++ b/homeassistant/components/here_travel_time/model.py @@ -1,4 +1,5 @@ """Model Classes for here_travel_time.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 193a86a3d37..1fd4f8d94bc 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -1,4 +1,5 @@ """Support for HERE travel time sensors.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index ef8a18ec3ae..2e4af361b38 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Hikvision event stream events represented as binary sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hikvisioncam/switch.py b/homeassistant/components/hikvisioncam/switch.py index ea521ec3a6b..c455fcb5bbc 100644 --- a/homeassistant/components/hikvisioncam/switch.py +++ b/homeassistant/components/hikvisioncam/switch.py @@ -1,4 +1,5 @@ """Support turning on/off motion detection on Hikvision cameras.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hisense_aehw4a1/climate.py b/homeassistant/components/hisense_aehw4a1/climate.py index 0e3fa9981c1..656ba6c68c0 100644 --- a/homeassistant/components/hisense_aehw4a1/climate.py +++ b/homeassistant/components/hisense_aehw4a1/climate.py @@ -1,4 +1,5 @@ """Pyaehw4a1 platform to control of Hisense AEH-W4A1 Climate Devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hisense_aehw4a1/config_flow.py b/homeassistant/components/hisense_aehw4a1/config_flow.py index 8fd651d1782..3d4c83d6f53 100644 --- a/homeassistant/components/hisense_aehw4a1/config_flow.py +++ b/homeassistant/components/hisense_aehw4a1/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Hisense AEH-W4A1 integration.""" + from pyaehw4a1.aehw4a1 import AehW4a1 from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index de5f994f8c7..365be06fd2d 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -1,4 +1,5 @@ """Provide pre-made queries on top of the recorder component.""" + from __future__ import annotations from datetime import datetime as dt, timedelta diff --git a/homeassistant/components/history/helpers.py b/homeassistant/components/history/helpers.py index 7e28e69e5f9..bd477e7e4ed 100644 --- a/homeassistant/components/history/helpers.py +++ b/homeassistant/components/history/helpers.py @@ -1,4 +1,5 @@ """Helpers for the history integration.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/history/websocket_api.py b/homeassistant/components/history/websocket_api.py index dc249ddb035..63142b139bc 100644 --- a/homeassistant/components/history/websocket_api.py +++ b/homeassistant/components/history/websocket_api.py @@ -1,4 +1,5 @@ """Websocket API for the history integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/history_stats/coordinator.py b/homeassistant/components/history_stats/coordinator.py index 6d4d6e55fa9..0ab68adda57 100644 --- a/homeassistant/components/history_stats/coordinator.py +++ b/homeassistant/components/history_stats/coordinator.py @@ -1,4 +1,5 @@ """History stats data coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/history_stats/data.py b/homeassistant/components/history_stats/data.py index 69e56ba0333..9a26853cbb5 100644 --- a/homeassistant/components/history_stats/data.py +++ b/homeassistant/components/history_stats/data.py @@ -1,4 +1,5 @@ """Manage the history_stats data.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/history_stats/helpers.py b/homeassistant/components/history_stats/helpers.py index 0c914e1fd41..33a45d10735 100644 --- a/homeassistant/components/history_stats/helpers.py +++ b/homeassistant/components/history_stats/helpers.py @@ -1,4 +1,5 @@ """Helpers to make instant statistics about your history.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 7f318b03e06..0134f4682a5 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -1,4 +1,5 @@ """Component to make instant statistics about your history.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index df1189f9e76..ba672e38106 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -1,4 +1,5 @@ """Support for the Hitron CODA-4582U, provided by Rogers.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 1a386d3b271..834054dd91c 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -1,4 +1,5 @@ """Support for the Hive devices and services.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/hive/alarm_control_panel.py b/homeassistant/components/hive/alarm_control_panel.py index 46cc37751a0..78e8606a43c 100644 --- a/homeassistant/components/hive/alarm_control_panel.py +++ b/homeassistant/components/hive/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for the Hive alarm.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 6306b48f733..472395afa9e 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the Hive binary sensors.""" + from datetime import timedelta from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index 8085719d8c5..8c674605b11 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,4 +1,5 @@ """Support for the Hive climate devices.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/hive/config_flow.py b/homeassistant/components/hive/config_flow.py index 436c1e35da1..3b11e7a8246 100644 --- a/homeassistant/components/hive/config_flow.py +++ b/homeassistant/components/hive/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for Hive.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/hive/const.py b/homeassistant/components/hive/const.py index b7a2be6910f..2c07469185a 100644 --- a/homeassistant/components/hive/const.py +++ b/homeassistant/components/hive/const.py @@ -1,4 +1,5 @@ """Constants for Hive.""" + from homeassistant.const import Platform ATTR_MODE = "mode" diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index d173751c6c8..ebf9efb3d26 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -1,4 +1,5 @@ """Support for Hive light devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index 0849cf39782..e52e608af43 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -1,4 +1,5 @@ """Support for the Hive sensors.""" + from datetime import timedelta from homeassistant.components.sensor import ( diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 80e9d485f5b..9f97c8e920b 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -1,4 +1,5 @@ """Support for the Hive switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index 2dbb6cb0230..11f8dddb3bd 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -1,4 +1,5 @@ """Support for hive water heaters.""" + from datetime import timedelta import voluptuous as vol diff --git a/homeassistant/components/hko/__init__.py b/homeassistant/components/hko/__init__.py index a83c1dd2d89..5b06644580e 100644 --- a/homeassistant/components/hko/__init__.py +++ b/homeassistant/components/hko/__init__.py @@ -1,4 +1,5 @@ """The Hong Kong Observatory integration.""" + from __future__ import annotations from hko import LOCATIONS diff --git a/homeassistant/components/hko/config_flow.py b/homeassistant/components/hko/config_flow.py index 17d74f59f8f..aeee7d4aff8 100644 --- a/homeassistant/components/hko/config_flow.py +++ b/homeassistant/components/hko/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Hong Kong Observatory integration.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/hko/const.py b/homeassistant/components/hko/const.py index a9a554850b0..288fdf56682 100644 --- a/homeassistant/components/hko/const.py +++ b/homeassistant/components/hko/const.py @@ -1,4 +1,5 @@ """Constants for the Hong Kong Observatory integration.""" + from hko import LOCATIONS from homeassistant.components.weather import ( diff --git a/homeassistant/components/hko/coordinator.py b/homeassistant/components/hko/coordinator.py index 05280c4a3bd..c7d80ae299e 100644 --- a/homeassistant/components/hko/coordinator.py +++ b/homeassistant/components/hko/coordinator.py @@ -1,4 +1,5 @@ """Weather data coordinator for the HKO API.""" + from asyncio import timeout from datetime import timedelta import logging diff --git a/homeassistant/components/hko/weather.py b/homeassistant/components/hko/weather.py index f4a784c5308..6d3d12d8ab4 100644 --- a/homeassistant/components/hko/weather.py +++ b/homeassistant/components/hko/weather.py @@ -1,4 +1,5 @@ """Support for the HKO service.""" + from homeassistant.components.weather import ( Forecast, WeatherEntity, diff --git a/homeassistant/components/hlk_sw16/errors.py b/homeassistant/components/hlk_sw16/errors.py index 87453737531..01ad3deb4eb 100644 --- a/homeassistant/components/hlk_sw16/errors.py +++ b/homeassistant/components/hlk_sw16/errors.py @@ -1,4 +1,5 @@ """Errors for the HLK-SW16 component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/hlk_sw16/switch.py b/homeassistant/components/hlk_sw16/switch.py index 2189a285ad8..590ab9c4497 100644 --- a/homeassistant/components/hlk_sw16/switch.py +++ b/homeassistant/components/hlk_sw16/switch.py @@ -1,4 +1,5 @@ """Support for HLK-SW16 switches.""" + from typing import Any from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/holiday/__init__.py b/homeassistant/components/holiday/__init__.py index 224b1b01294..4f2c593d38e 100644 --- a/homeassistant/components/holiday/__init__.py +++ b/homeassistant/components/holiday/__init__.py @@ -1,4 +1,5 @@ """The Holiday integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/holiday/calendar.py b/homeassistant/components/holiday/calendar.py index e48cc11d677..57503b340d9 100644 --- a/homeassistant/components/holiday/calendar.py +++ b/homeassistant/components/holiday/calendar.py @@ -1,4 +1,5 @@ """Holiday Calendar.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/holiday/config_flow.py b/homeassistant/components/holiday/config_flow.py index c99efae789d..f9108f02bc9 100644 --- a/homeassistant/components/holiday/config_flow.py +++ b/homeassistant/components/holiday/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Holiday integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/holiday/const.py b/homeassistant/components/holiday/const.py index 5d2a567a488..ed283f82412 100644 --- a/homeassistant/components/holiday/const.py +++ b/homeassistant/components/holiday/const.py @@ -1,4 +1,5 @@ """Constants for the Holiday integration.""" + from typing import Final DOMAIN: Final = "holiday" diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index 79303725249..ebfd6f91c76 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -1,4 +1,5 @@ """Support for BSH Home Connect appliances.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index a01cae5862a..9bd48617fb3 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -1,4 +1,5 @@ """Provides a sensor for Home Connect.""" + from datetime import datetime, timedelta import logging from typing import cast diff --git a/homeassistant/components/homeassistant/const.py b/homeassistant/components/homeassistant/const.py index 871ea5a0371..d56ab4397d9 100644 --- a/homeassistant/components/homeassistant/const.py +++ b/homeassistant/components/homeassistant/const.py @@ -1,4 +1,5 @@ """Constants for the Homeassistant integration.""" + from typing import Final import homeassistant.core as ha diff --git a/homeassistant/components/homeassistant/exposed_entities.py b/homeassistant/components/homeassistant/exposed_entities.py index 38c7f8e8128..135b2847520 100644 --- a/homeassistant/components/homeassistant/exposed_entities.py +++ b/homeassistant/components/homeassistant/exposed_entities.py @@ -1,4 +1,5 @@ """Control which entities are exposed to voice assistants.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/homeassistant/logbook.py b/homeassistant/components/homeassistant/logbook.py index 60e8794799d..12d6c66b69c 100644 --- a/homeassistant/components/homeassistant/logbook.py +++ b/homeassistant/components/homeassistant/logbook.py @@ -1,4 +1,5 @@ """Describe homeassistant logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index f8fd901a18a..9d03b35e079 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -1,4 +1,5 @@ """Allow users to set and activate scenes.""" + from __future__ import annotations from collections.abc import Mapping, ValuesView diff --git a/homeassistant/components/homeassistant/system_health.py b/homeassistant/components/homeassistant/system_health.py index 488328b6e4e..8a51b9cd418 100644 --- a/homeassistant/components/homeassistant/system_health.py +++ b/homeassistant/components/homeassistant/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 37a91d06d1a..e045ece12ba 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -1,4 +1,5 @@ """Offer event listening automation rules.""" + from __future__ import annotations from collections.abc import ItemsView diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index dad57bbcdb3..713c72acb89 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -1,4 +1,5 @@ """Offer numeric state listening automation rules.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 061c2468c30..a728a8f42e4 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -1,4 +1,5 @@ """Offer state listening automation rules.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homeassistant/triggers/time.py b/homeassistant/components/homeassistant/triggers/time.py index 3cb8809a7ad..4f2ac9e8480 100644 --- a/homeassistant/components/homeassistant/triggers/time.py +++ b/homeassistant/components/homeassistant/triggers/time.py @@ -1,4 +1,5 @@ """Offer time listening automation rules.""" + from datetime import datetime from functools import partial diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index d8ac55eb04f..df49a79bcb6 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -1,4 +1,5 @@ """Offer time listening automation rules.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/homeassistant_alerts/__init__.py b/homeassistant/components/homeassistant_alerts/__init__.py index f391b990761..6e6aaa0838e 100644 --- a/homeassistant/components/homeassistant_alerts/__init__.py +++ b/homeassistant/components/homeassistant_alerts/__init__.py @@ -1,4 +1,5 @@ """The Home Assistant alerts integration.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/homeassistant_green/__init__.py b/homeassistant/components/homeassistant_green/__init__.py index ed86723ab94..2d35b5bbed3 100644 --- a/homeassistant/components/homeassistant_green/__init__.py +++ b/homeassistant/components/homeassistant_green/__init__.py @@ -1,4 +1,5 @@ """The Home Assistant Green integration.""" + from __future__ import annotations from homeassistant.components.hassio import get_os_info, is_hassio diff --git a/homeassistant/components/homeassistant_green/config_flow.py b/homeassistant/components/homeassistant_green/config_flow.py index 83b16ac9799..4b71c7f1056 100644 --- a/homeassistant/components/homeassistant_green/config_flow.py +++ b/homeassistant/components/homeassistant_green/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Home Assistant Green integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/homeassistant_green/hardware.py b/homeassistant/components/homeassistant_green/hardware.py index c7b1641c09c..0537d17620b 100644 --- a/homeassistant/components/homeassistant_green/hardware.py +++ b/homeassistant/components/homeassistant_green/hardware.py @@ -1,4 +1,5 @@ """The Home Assistant Green hardware platform.""" + from __future__ import annotations from homeassistant.components.hardware.models import BoardInfo, HardwareInfo diff --git a/homeassistant/components/homeassistant_hardware/__init__.py b/homeassistant/components/homeassistant_hardware/__init__.py index 057cdd3b0db..c33dabe1ec8 100644 --- a/homeassistant/components/homeassistant_hardware/__init__.py +++ b/homeassistant/components/homeassistant_hardware/__init__.py @@ -1,4 +1,5 @@ """The Home Assistant Hardware integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py index 5a1cf1d5421..535d1706737 100644 --- a/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py +++ b/homeassistant/components/homeassistant_hardware/silabs_multiprotocol_addon.py @@ -1,4 +1,5 @@ """Manage the Silicon Labs Multiprotocol add-on.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/homeassistant_sky_connect/__init__.py b/homeassistant/components/homeassistant_sky_connect/__init__.py index 4880d2e375f..763c421350f 100644 --- a/homeassistant/components/homeassistant_sky_connect/__init__.py +++ b/homeassistant/components/homeassistant_sky_connect/__init__.py @@ -1,4 +1,5 @@ """The Home Assistant SkyConnect integration.""" + from __future__ import annotations from homeassistant.components import usb diff --git a/homeassistant/components/homeassistant_sky_connect/config_flow.py b/homeassistant/components/homeassistant_sky_connect/config_flow.py index 002fc9c4f62..4e4cbfe0a03 100644 --- a/homeassistant/components/homeassistant_sky_connect/config_flow.py +++ b/homeassistant/components/homeassistant_sky_connect/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Home Assistant SkyConnect integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homeassistant_sky_connect/hardware.py b/homeassistant/components/homeassistant_sky_connect/hardware.py index bd752278397..1c32bce531b 100644 --- a/homeassistant/components/homeassistant_sky_connect/hardware.py +++ b/homeassistant/components/homeassistant_sky_connect/hardware.py @@ -1,4 +1,5 @@ """The Home Assistant SkyConnect hardware platform.""" + from __future__ import annotations from homeassistant.components.hardware.models import HardwareInfo, USBInfo diff --git a/homeassistant/components/homeassistant_sky_connect/util.py b/homeassistant/components/homeassistant_sky_connect/util.py index 7a87964f5c4..84aa858c847 100644 --- a/homeassistant/components/homeassistant_sky_connect/util.py +++ b/homeassistant/components/homeassistant_sky_connect/util.py @@ -1,4 +1,5 @@ """Utility functions for Home Assistant SkyConnect integration.""" + from __future__ import annotations from homeassistant.components import usb diff --git a/homeassistant/components/homeassistant_yellow/__init__.py b/homeassistant/components/homeassistant_yellow/__init__.py index 84ad464e779..14c2de2c9a1 100644 --- a/homeassistant/components/homeassistant_yellow/__init__.py +++ b/homeassistant/components/homeassistant_yellow/__init__.py @@ -1,4 +1,5 @@ """The Home Assistant Yellow integration.""" + from __future__ import annotations from homeassistant.components.hassio import get_os_info, is_hassio diff --git a/homeassistant/components/homeassistant_yellow/config_flow.py b/homeassistant/components/homeassistant_yellow/config_flow.py index 94dd511cac5..d2212a968db 100644 --- a/homeassistant/components/homeassistant_yellow/config_flow.py +++ b/homeassistant/components/homeassistant_yellow/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Home Assistant Yellow integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/homeassistant_yellow/hardware.py b/homeassistant/components/homeassistant_yellow/hardware.py index 0749ca8edc6..2b9ee0673db 100644 --- a/homeassistant/components/homeassistant_yellow/hardware.py +++ b/homeassistant/components/homeassistant_yellow/hardware.py @@ -1,4 +1,5 @@ """The Home Assistant Yellow hardware platform.""" + from __future__ import annotations from homeassistant.components.hardware.models import BoardInfo, HardwareInfo diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 76256ea1a7e..376962f376e 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -1,4 +1,5 @@ """Support for Apple HomeKit.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 25b1c143f54..5303b2a6736 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -1,4 +1,5 @@ """Extend the basic Accessory and Bridge functions.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homekit/aidmanager.py b/homeassistant/components/homekit/aidmanager.py index 43beaaa8dc6..36df47e8a93 100644 --- a/homeassistant/components/homekit/aidmanager.py +++ b/homeassistant/components/homekit/aidmanager.py @@ -8,6 +8,7 @@ can't change the hash without causing breakages for HA users. This module generates and stores them in a HA storage. """ + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 772e2bbce1d..30fb80cbdfc 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -1,4 +1,5 @@ """Config flow for HomeKit integration.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/homekit/diagnostics.py b/homeassistant/components/homekit/diagnostics.py index 347a3df0dd4..f31dd268b26 100644 --- a/homeassistant/components/homekit/diagnostics.py +++ b/homeassistant/components/homekit/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for HomeKit.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homekit/iidmanager.py b/homeassistant/components/homekit/iidmanager.py index f44d76d3ee7..d6daeb49f82 100644 --- a/homeassistant/components/homekit/iidmanager.py +++ b/homeassistant/components/homekit/iidmanager.py @@ -5,6 +5,7 @@ be stable between reboots and upgrades. This module generates and stores them in a HA storage. """ + from __future__ import annotations from uuid import UUID diff --git a/homeassistant/components/homekit/logbook.py b/homeassistant/components/homekit/logbook.py index e71695883a8..c1609350d68 100644 --- a/homeassistant/components/homekit/logbook.py +++ b/homeassistant/components/homekit/logbook.py @@ -1,4 +1,5 @@ """Describe logbook events.""" + from collections.abc import Callable from typing import Any diff --git a/homeassistant/components/homekit/models.py b/homeassistant/components/homekit/models.py index e96af00fead..fee081c9e51 100644 --- a/homeassistant/components/homekit/models.py +++ b/homeassistant/components/homekit/models.py @@ -1,4 +1,5 @@ """Models for the HomeKit component.""" + from dataclasses import dataclass from typing import TYPE_CHECKING diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index b45e9e1c17b..cb446ea551c 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -1,4 +1,5 @@ """Class to hold all light accessories.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/homekit/type_remotes.py b/homeassistant/components/homekit/type_remotes.py index e03b14f943a..75b3a5200b8 100644 --- a/homeassistant/components/homekit/type_remotes.py +++ b/homeassistant/components/homekit/type_remotes.py @@ -1,4 +1,5 @@ """Class to hold remote accessories.""" + from abc import ABC, abstractmethod import logging from typing import Any diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index dbf2808a55a..bfa97756bb4 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -1,4 +1,5 @@ """Class to hold all sensor accessories.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 5c0c2c74f0a..86861417bdb 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -1,4 +1,5 @@ """Class to hold all switch accessories.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homekit/type_triggers.py b/homeassistant/components/homekit/type_triggers.py index 625ed0a4a44..b958817bbac 100644 --- a/homeassistant/components/homekit/type_triggers.py +++ b/homeassistant/components/homekit/type_triggers.py @@ -1,4 +1,5 @@ """Class to hold all sensor accessories.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 8a51f35564e..35e60bff2df 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -1,4 +1,5 @@ """Collection of useful functions for the HomeKit component.""" + from __future__ import annotations import io diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index e3ff4d47fcf..9244a9f95d8 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -1,4 +1,5 @@ """Support for Homekit device discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index f1c2440ce9e..1cb94926e8b 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Homekit Alarm Control Panel.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 93bf20ef493..26e19c8944a 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Homekit motion sensors.""" + from __future__ import annotations from aiohomekit.model.characteristics import CharacteristicsTypes diff --git a/homeassistant/components/homekit_controller/button.py b/homeassistant/components/homekit_controller/button.py index a0c61578e66..abd00f02aa0 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -3,6 +3,7 @@ These are mostly used where a HomeKit accessory exposes additional non-standard characteristics that don't map to a Home Assistant feature. """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homekit_controller/camera.py b/homeassistant/components/homekit_controller/camera.py index 35a4b089641..4332032867a 100644 --- a/homeassistant/components/homekit_controller/camera.py +++ b/homeassistant/components/homekit_controller/camera.py @@ -1,4 +1,5 @@ """Support for Homekit cameras.""" + from __future__ import annotations from aiohomekit.model import Accessory diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 0ca85da3fa2..7fe11b8f875 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -1,4 +1,5 @@ """Support for Homekit climate devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index f0931e382dd..e48cb069dfe 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure homekit_controller.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index a3c68fffe64..66552978e8d 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -1,4 +1,5 @@ """Helpers for managing a pairing with a HomeKit accessory or bridge.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index f99563843c7..b15ce645a29 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -1,4 +1,5 @@ """Support for Homekit covers.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index 6dc97bf6821..c88928e1c2e 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for homekit devices.""" + from __future__ import annotations from collections.abc import Callable, Generator diff --git a/homeassistant/components/homekit_controller/diagnostics.py b/homeassistant/components/homekit_controller/diagnostics.py index 9b17c0c2fe7..bfd034807c9 100644 --- a/homeassistant/components/homekit_controller/diagnostics.py +++ b/homeassistant/components/homekit_controller/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for HomeKit Controller.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homekit_controller/entity.py b/homeassistant/components/homekit_controller/entity.py index 496866299d6..136c063f280 100644 --- a/homeassistant/components/homekit_controller/entity.py +++ b/homeassistant/components/homekit_controller/entity.py @@ -1,4 +1,5 @@ """Homekit Controller entities.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/homekit_controller/event.py b/homeassistant/components/homekit_controller/event.py index 8f3d71682f1..7c9052f984b 100644 --- a/homeassistant/components/homekit_controller/event.py +++ b/homeassistant/components/homekit_controller/event.py @@ -1,4 +1,5 @@ """Support for Homekit motion sensors.""" + from __future__ import annotations from aiohomekit.model.characteristics import CharacteristicsTypes diff --git a/homeassistant/components/homekit_controller/fan.py b/homeassistant/components/homekit_controller/fan.py index 1b2d572f2b6..39df2b7ce51 100644 --- a/homeassistant/components/homekit_controller/fan.py +++ b/homeassistant/components/homekit_controller/fan.py @@ -1,4 +1,5 @@ """Support for Homekit fans.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index b5e67e7f1a4..fef8fed33ad 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -1,4 +1,5 @@ """Support for HomeKit Controller humidifier.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index fd3bf4f800b..b314ffe85de 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -1,4 +1,5 @@ """Support for Homekit lights.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index df03a1fef34..8e1bcd424d4 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -1,4 +1,5 @@ """Support for HomeKit Controller locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 90d1ba754f2..4232d1b7649 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -1,4 +1,5 @@ """Support for HomeKit Controller Televisions.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index e2d856126da..340d31c91ae 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -3,6 +3,7 @@ These are mostly used where a HomeKit accessory exposes additional non-standard characteristics that don't map to a Home Assistant feature. """ + from __future__ import annotations from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes diff --git a/homeassistant/components/homekit_controller/select.py b/homeassistant/components/homekit_controller/select.py index be1a7313301..6ae42a69940 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -1,4 +1,5 @@ """Support for Homekit select entities.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 28bb0cd309c..059be5bad99 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -1,4 +1,5 @@ """Support for Homekit sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index b7e1b27ef7f..65f98ed8f5e 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -1,4 +1,5 @@ """Support for Homekit switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/homekit_controller/utils.py b/homeassistant/components/homekit_controller/utils.py index 489dee5584c..2f94f5bac92 100644 --- a/homeassistant/components/homekit_controller/utils.py +++ b/homeassistant/components/homekit_controller/utils.py @@ -1,4 +1,5 @@ """Helper functions for the homekit_controller component.""" + from functools import lru_cache from typing import cast diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 364bfd11210..6eb01728df0 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -1,4 +1,5 @@ """Support for HomeMatic devices.""" + from datetime import datetime from functools import partial import logging diff --git a/homeassistant/components/homematic/binary_sensor.py b/homeassistant/components/homematic/binary_sensor.py index ba6d9781293..0d94c2bb78b 100644 --- a/homeassistant/components/homematic/binary_sensor.py +++ b/homeassistant/components/homematic/binary_sensor.py @@ -1,4 +1,5 @@ """Support for HomeMatic binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index 76d9dff4d46..f0e15b7a055 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -1,4 +1,5 @@ """Support for Homematic thermostats.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematic/cover.py b/homeassistant/components/homematic/cover.py index d9fb44219f6..b9f4a4fa96a 100644 --- a/homeassistant/components/homematic/cover.py +++ b/homeassistant/components/homematic/cover.py @@ -1,4 +1,5 @@ """Support for HomeMatic covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index 700ef5cdc94..b728e85f959 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -1,4 +1,5 @@ """Homematic base entity.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index 39e6df9d0ec..b05cc6a46d6 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -1,4 +1,5 @@ """Support for Homematic lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematic/lock.py b/homeassistant/components/homematic/lock.py index abca46ddf58..bcd5c7ce7a0 100644 --- a/homeassistant/components/homematic/lock.py +++ b/homeassistant/components/homematic/lock.py @@ -1,4 +1,5 @@ """Support for Homematic locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematic/notify.py b/homeassistant/components/homematic/notify.py index 33a06336104..6b7e71bb7a9 100644 --- a/homeassistant/components/homematic/notify.py +++ b/homeassistant/components/homematic/notify.py @@ -1,4 +1,5 @@ """Notification support for Homematic.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index 8b5d7b6bff1..eebcad95446 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -1,4 +1,5 @@ """Support for HomeMatic sensors.""" + from __future__ import annotations from copy import copy diff --git a/homeassistant/components/homematic/switch.py b/homeassistant/components/homematic/switch.py index 7accb011ebf..5f7c1f93dc8 100644 --- a/homeassistant/components/homematic/switch.py +++ b/homeassistant/components/homematic/switch.py @@ -1,4 +1,5 @@ """Support for HomeMatic switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index 1a2f2293c1c..2913896d511 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud alarm control panel.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 2afe803e1eb..b3322107bfa 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud binary sensor.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/button.py b/homeassistant/components/homematicip_cloud/button.py index 3fb8ebe20bd..7537c15fc9b 100644 --- a/homeassistant/components/homematicip_cloud/button.py +++ b/homeassistant/components/homematicip_cloud/button.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud button devices.""" + from __future__ import annotations from homematicip.aio.device import AsyncWallMountedGarageDoorController diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index 63b78e91a2f..9c79e1a0c55 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud climate devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/config_flow.py b/homeassistant/components/homematicip_cloud/config_flow.py index 643a525ea25..c2277e16c79 100644 --- a/homeassistant/components/homematicip_cloud/config_flow.py +++ b/homeassistant/components/homematicip_cloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the HomematicIP Cloud component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index f5a9919579c..17e1b7a94f1 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud cover devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/errors.py b/homeassistant/components/homematicip_cloud/errors.py index 1102cde6fbe..bbee58f7a41 100644 --- a/homeassistant/components/homematicip_cloud/errors.py +++ b/homeassistant/components/homematicip_cloud/errors.py @@ -1,4 +1,5 @@ """Errors for the HomematicIP Cloud component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index 46d036c777b..c75649e6886 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -1,4 +1,5 @@ """Generic entity for the HomematicIP Cloud component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index b40b6ec56f6..058b7ec6c00 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -1,4 +1,5 @@ """Access point for the HomematicIP Cloud component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/homematicip_cloud/helpers.py b/homeassistant/components/homematicip_cloud/helpers.py index 4647e553382..43edca4774a 100644 --- a/homeassistant/components/homematicip_cloud/helpers.py +++ b/homeassistant/components/homematicip_cloud/helpers.py @@ -1,4 +1,5 @@ """Helper functions for Homematicip Cloud Integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 3000024db43..6a9bd5a3c4e 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/lock.py b/homeassistant/components/homematicip_cloud/lock.py index 563f0103060..cf98828598f 100644 --- a/homeassistant/components/homematicip_cloud/lock.py +++ b/homeassistant/components/homematicip_cloud/lock.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud lock devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 04fba2cfd92..d344639bbc9 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/services.py b/homeassistant/components/homematicip_cloud/services.py index 38ce6de7caf..37cda9e7683 100644 --- a/homeassistant/components/homematicip_cloud/services.py +++ b/homeassistant/components/homematicip_cloud/services.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 2b5f2f01cd3..1fa3007c863 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/homematicip_cloud/weather.py b/homeassistant/components/homematicip_cloud/weather.py index 573f291d557..34e3f58d6ef 100644 --- a/homeassistant/components/homematicip_cloud/weather.py +++ b/homeassistant/components/homematicip_cloud/weather.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud weather devices.""" + from __future__ import annotations from homematicip.aio.device import ( diff --git a/homeassistant/components/homewizard/__init__.py b/homeassistant/components/homewizard/__init__.py index 35b303a62e3..6efcd7be75b 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -1,4 +1,5 @@ """The Homewizard integration.""" + from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 067007589bb..795edfaf629 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -1,4 +1,5 @@ """Config flow for HomeWizard.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/homewizard/const.py b/homeassistant/components/homewizard/const.py index f1a1bee2568..8cee8350268 100644 --- a/homeassistant/components/homewizard/const.py +++ b/homeassistant/components/homewizard/const.py @@ -1,4 +1,5 @@ """Constants for the Homewizard integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index e38b1d54471..22133a04234 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -1,4 +1,5 @@ """Update coordinator for HomeWizard.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/homewizard/diagnostics.py b/homeassistant/components/homewizard/diagnostics.py index b8103f7a4cb..82d9b7d72d1 100644 --- a/homeassistant/components/homewizard/diagnostics.py +++ b/homeassistant/components/homewizard/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for P1 Monitor.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/homewizard/entity.py b/homeassistant/components/homewizard/entity.py index 2090cc363ba..9ba54c8218c 100644 --- a/homeassistant/components/homewizard/entity.py +++ b/homeassistant/components/homewizard/entity.py @@ -1,4 +1,5 @@ """Base entity for the HomeWizard integration.""" + from __future__ import annotations from homeassistant.const import ATTR_CONNECTIONS, ATTR_IDENTIFIERS diff --git a/homeassistant/components/homewizard/helpers.py b/homeassistant/components/homewizard/helpers.py index 4c3ae76a327..a69e09204e7 100644 --- a/homeassistant/components/homewizard/helpers.py +++ b/homeassistant/components/homewizard/helpers.py @@ -1,4 +1,5 @@ """Helpers for HomeWizard.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/homewizard/number.py b/homeassistant/components/homewizard/number.py index 6145db125a1..c6261c62a9b 100644 --- a/homeassistant/components/homewizard/number.py +++ b/homeassistant/components/homewizard/number.py @@ -1,4 +1,5 @@ """Creates HomeWizard Number entities.""" + from __future__ import annotations from homeassistant.components.number import NumberEntity diff --git a/homeassistant/components/homewizard/sensor.py b/homeassistant/components/homewizard/sensor.py index e544ee601c0..19102e5b985 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -1,4 +1,5 @@ """Creates HomeWizard sensor entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homewizard/switch.py b/homeassistant/components/homewizard/switch.py index 72e0f43a2cf..eb954709be0 100644 --- a/homeassistant/components/homewizard/switch.py +++ b/homeassistant/components/homewizard/switch.py @@ -1,4 +1,5 @@ """Creates HomeWizard Energy switch entities.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 3d9de889ae7..3ab833e2b08 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -1,4 +1,5 @@ """Support for Lutron Homeworks Series 4 and 8 systems.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/homeworks/button.py b/homeassistant/components/homeworks/button.py index 04cf3594e0e..c8cb616d95b 100644 --- a/homeassistant/components/homeworks/button.py +++ b/homeassistant/components/homeworks/button.py @@ -1,4 +1,5 @@ """Support for Lutron Homeworks buttons.""" + from __future__ import annotations from time import sleep diff --git a/homeassistant/components/homeworks/config_flow.py b/homeassistant/components/homeworks/config_flow.py index 269abd70535..68f6aa37c27 100644 --- a/homeassistant/components/homeworks/config_flow.py +++ b/homeassistant/components/homeworks/config_flow.py @@ -1,4 +1,5 @@ """Lutron Homeworks Series 4 and 8 config flow.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/homeworks/const.py b/homeassistant/components/homeworks/const.py index c0d4439ff93..a4645f1d357 100644 --- a/homeassistant/components/homeworks/const.py +++ b/homeassistant/components/homeworks/const.py @@ -1,4 +1,5 @@ """Constants for the Lutron Homeworks integration.""" + from __future__ import annotations DOMAIN = "homeworks" diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index a9b0e2587df..3e3c199c75c 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -1,4 +1,5 @@ """Support for Lutron Homeworks lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/honeywell/__init__.py b/homeassistant/components/honeywell/__init__.py index f58db72a07e..c1c46e2b7af 100644 --- a/homeassistant/components/honeywell/__init__.py +++ b/homeassistant/components/honeywell/__init__.py @@ -1,4 +1,5 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" + from dataclasses import dataclass import aiosomecomfort diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index fb8537ce36b..5ac5e8a2472 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -1,4 +1,5 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/honeywell/config_flow.py b/homeassistant/components/honeywell/config_flow.py index 8fb05f7e47d..85877046bc0 100644 --- a/homeassistant/components/honeywell/config_flow.py +++ b/homeassistant/components/honeywell/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the honeywell integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/honeywell/diagnostics.py b/homeassistant/components/honeywell/diagnostics.py index 4aebfc4c905..b489eb4a596 100644 --- a/homeassistant/components/honeywell/diagnostics.py +++ b/homeassistant/components/honeywell/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Honeywell.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py index 0841b7df1cc..76e8a5b2588 100644 --- a/homeassistant/components/honeywell/sensor.py +++ b/homeassistant/components/honeywell/sensor.py @@ -1,4 +1,5 @@ """Support for Honeywell (US) Total Connect Comfort sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py index d91fe7019d6..c03bcc73f41 100644 --- a/homeassistant/components/horizon/media_player.py +++ b/homeassistant/components/horizon/media_player.py @@ -1,4 +1,5 @@ """Support for the Unitymedia Horizon HD Recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hp_ilo/sensor.py b/homeassistant/components/hp_ilo/sensor.py index fcb8788c646..8d29b20381d 100644 --- a/homeassistant/components/hp_ilo/sensor.py +++ b/homeassistant/components/hp_ilo/sensor.py @@ -1,4 +1,5 @@ """Support for information from HP iLO sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index a9ecabb37a8..782340dffa6 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -1,4 +1,5 @@ """HTML5 Push Messaging notification service.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 51c98b080f0..b01d80c9997 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -1,4 +1,5 @@ """Support to serve the Home Assistant API as WSGI application.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py index 640d899924e..2073c998384 100644 --- a/homeassistant/components/http/auth.py +++ b/homeassistant/components/http/auth.py @@ -1,4 +1,5 @@ """Authentication for HTTP component.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index e5a65c2fe72..5a0f40cdeab 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -1,4 +1,5 @@ """Ban logic for HTTP component.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/http/const.py b/homeassistant/components/http/const.py index 3181b043b88..1254744f258 100644 --- a/homeassistant/components/http/const.py +++ b/homeassistant/components/http/const.py @@ -1,4 +1,5 @@ """HTTP specific constants.""" + from typing import Final from homeassistant.helpers.http import KEY_AUTHENTICATED, KEY_HASS # noqa: F401 diff --git a/homeassistant/components/http/cors.py b/homeassistant/components/http/cors.py index 1bb5d2b5a6b..ebae2480589 100644 --- a/homeassistant/components/http/cors.py +++ b/homeassistant/components/http/cors.py @@ -1,4 +1,5 @@ """Provide CORS support for the HTTP component.""" + from __future__ import annotations from typing import Final, cast diff --git a/homeassistant/components/http/data_validator.py b/homeassistant/components/http/data_validator.py index 2868bee9432..749c4f63a2f 100644 --- a/homeassistant/components/http/data_validator.py +++ b/homeassistant/components/http/data_validator.py @@ -1,4 +1,5 @@ """Decorator for view methods to help with data validation.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/http/decorators.py b/homeassistant/components/http/decorators.py index b2e8e535fd2..bffb42d5817 100644 --- a/homeassistant/components/http/decorators.py +++ b/homeassistant/components/http/decorators.py @@ -1,4 +1,5 @@ """Decorators for the Home Assistant API.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/http/forwarded.py b/homeassistant/components/http/forwarded.py index c5698ebe919..134a31d1625 100644 --- a/homeassistant/components/http/forwarded.py +++ b/homeassistant/components/http/forwarded.py @@ -1,4 +1,5 @@ """Middleware to handle forwarded data by a reverse proxy.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/http/headers.py b/homeassistant/components/http/headers.py index 20c0a58967b..bd05401ebce 100644 --- a/homeassistant/components/http/headers.py +++ b/homeassistant/components/http/headers.py @@ -1,4 +1,5 @@ """Middleware that helps with the control of headers in our responses.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/http/request_context.py b/homeassistant/components/http/request_context.py index b516b63dc5c..c5fcdfb18f3 100644 --- a/homeassistant/components/http/request_context.py +++ b/homeassistant/components/http/request_context.py @@ -1,4 +1,5 @@ """Middleware to set the request context.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/http/security_filter.py b/homeassistant/components/http/security_filter.py index 4d71334f1cf..524d125b857 100644 --- a/homeassistant/components/http/security_filter.py +++ b/homeassistant/components/http/security_filter.py @@ -1,4 +1,5 @@ """Middleware to add some basic security filtering to requests.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index 57f3f22866e..bdc7b08ee4a 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -1,4 +1,5 @@ """Static file handling for HTTP component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index ce02879dbb3..712b4e9894f 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -1,4 +1,5 @@ """Support for views.""" + from __future__ import annotations from homeassistant.helpers.http import ( # noqa: F401 diff --git a/homeassistant/components/http/web_runner.py b/homeassistant/components/http/web_runner.py index 5c0931b97ca..fcdfbc661a7 100644 --- a/homeassistant/components/http/web_runner.py +++ b/homeassistant/components/http/web_runner.py @@ -1,4 +1,5 @@ """HomeAssistant specific aiohttp Site.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 81be4e462d1..7d28d6c187f 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -1,4 +1,5 @@ """Support for Huawei LTE routers.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index d4fa0b6db6f..c90a7854a91 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Huawei LTE binary sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index 9138a709c62..84cf88786a9 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Huawei LTE platform.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 1bb5077a2b4..1f9905f4e9c 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -1,4 +1,5 @@ """Support for device tracking of Huawei LTE routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 3b72e2216a6..fc154de3811 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -1,4 +1,5 @@ """Support for Huawei LTE router notifications.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/huawei_lte/select.py b/homeassistant/components/huawei_lte/select.py index 6fef2d745cb..f4ff0a93d98 100644 --- a/homeassistant/components/huawei_lte/select.py +++ b/homeassistant/components/huawei_lte/select.py @@ -1,4 +1,5 @@ """Support for Huawei LTE selects.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index d7fb5565969..a52aff5fd68 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -1,4 +1,5 @@ """Support for Huawei LTE sensors.""" + from __future__ import annotations from bisect import bisect diff --git a/homeassistant/components/huawei_lte/switch.py b/homeassistant/components/huawei_lte/switch.py index 3743716390e..3a499851f9a 100644 --- a/homeassistant/components/huawei_lte/switch.py +++ b/homeassistant/components/huawei_lte/switch.py @@ -1,4 +1,5 @@ """Support for Huawei LTE switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/huawei_lte/utils.py b/homeassistant/components/huawei_lte/utils.py index df212a1c25d..2225fb13ffc 100644 --- a/homeassistant/components/huawei_lte/utils.py +++ b/homeassistant/components/huawei_lte/utils.py @@ -1,4 +1,5 @@ """Utilities for the Huawei LTE integration.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py index 21e9bfab6be..d4c2959771b 100644 --- a/homeassistant/components/hue/__init__.py +++ b/homeassistant/components/hue/__init__.py @@ -1,4 +1,5 @@ """Support for the Philips Hue system.""" + from aiohue.util import normalize_bridge_id from homeassistant.components import persistent_notification diff --git a/homeassistant/components/hue/binary_sensor.py b/homeassistant/components/hue/binary_sensor.py index b66b85a4844..2cb8e8b5d90 100644 --- a/homeassistant/components/hue/binary_sensor.py +++ b/homeassistant/components/hue/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Hue binary sensors.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py index 86db9c4021c..f167897d77b 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -1,4 +1,5 @@ """Code to handle a Hue bridge.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index b7624f2c0f0..9014f2644f5 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Philips Hue.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index 5033aaa427a..62700478b53 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -1,4 +1,5 @@ """Constants for the Hue component.""" + from aiohue.v2.models.button import ButtonEvent from aiohue.v2.models.relative_rotary import ( RelativeRotaryAction, diff --git a/homeassistant/components/hue/device_trigger.py b/homeassistant/components/hue/device_trigger.py index 069f0d42d8d..4104c667d74 100644 --- a/homeassistant/components/hue/device_trigger.py +++ b/homeassistant/components/hue/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Philips Hue events.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/hue/diagnostics.py b/homeassistant/components/hue/diagnostics.py index 17f00a50bbe..6bb23d832cd 100644 --- a/homeassistant/components/hue/diagnostics.py +++ b/homeassistant/components/hue/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Hue.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hue/errors.py b/homeassistant/components/hue/errors.py index dd217c3bc26..670d1282c96 100644 --- a/homeassistant/components/hue/errors.py +++ b/homeassistant/components/hue/errors.py @@ -1,4 +1,5 @@ """Errors for the Hue component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/hue/event.py b/homeassistant/components/hue/event.py index 183d2bfb3ae..c12468c0f36 100644 --- a/homeassistant/components/hue/event.py +++ b/homeassistant/components/hue/event.py @@ -1,4 +1,5 @@ """Hue event entities from Button resources.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 2bd9652f9b0..c3168b5c8c1 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -1,4 +1,5 @@ """Support for Hue lights.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/hue/scene.py b/homeassistant/components/hue/scene.py index 17f7a81b2a5..67148eb8be8 100644 --- a/homeassistant/components/hue/scene.py +++ b/homeassistant/components/hue/scene.py @@ -1,4 +1,5 @@ """Support for scene platform for Hue scenes (V2 only).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py index 7218831abe2..45cff053aef 100644 --- a/homeassistant/components/hue/sensor.py +++ b/homeassistant/components/hue/sensor.py @@ -1,4 +1,5 @@ """Support for Hue sensors.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/hue/services.py b/homeassistant/components/hue/services.py index 5b0b252e8d4..30555339f19 100644 --- a/homeassistant/components/hue/services.py +++ b/homeassistant/components/hue/services.py @@ -1,4 +1,5 @@ """Handle Hue Service calls.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hue/switch.py b/homeassistant/components/hue/switch.py index c9da30a779c..022a12f6ffc 100644 --- a/homeassistant/components/hue/switch.py +++ b/homeassistant/components/hue/switch.py @@ -1,4 +1,5 @@ """Support for switch platform for Hue resources (V2 only).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hue/v1/binary_sensor.py b/homeassistant/components/hue/v1/binary_sensor.py index 78cedc4437f..01524b48b79 100644 --- a/homeassistant/components/hue/v1/binary_sensor.py +++ b/homeassistant/components/hue/v1/binary_sensor.py @@ -1,4 +1,5 @@ """Hue binary sensor entities.""" + from aiohue.v1.sensors import TYPE_ZLL_PRESENCE from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/hue/v1/device_trigger.py b/homeassistant/components/hue/v1/device_trigger.py index 930c85c0414..554926cdc70 100644 --- a/homeassistant/components/hue/v1/device_trigger.py +++ b/homeassistant/components/hue/v1/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Philips Hue events in V1 bridge/api.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/hue/v1/light.py b/homeassistant/components/hue/v1/light.py index 18440f68239..68e05932e7a 100644 --- a/homeassistant/components/hue/v1/light.py +++ b/homeassistant/components/hue/v1/light.py @@ -1,4 +1,5 @@ """Support for the Philips Hue lights.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hue/v1/sensor.py b/homeassistant/components/hue/v1/sensor.py index 20ea4330e30..9a85f83f3e8 100644 --- a/homeassistant/components/hue/v1/sensor.py +++ b/homeassistant/components/hue/v1/sensor.py @@ -1,4 +1,5 @@ """Hue sensor entities.""" + from aiohue.v1.sensors import ( TYPE_ZLL_LIGHTLEVEL, TYPE_ZLL_ROTARY, diff --git a/homeassistant/components/hue/v1/sensor_base.py b/homeassistant/components/hue/v1/sensor_base.py index 723ecfff451..bac02c45209 100644 --- a/homeassistant/components/hue/v1/sensor_base.py +++ b/homeassistant/components/hue/v1/sensor_base.py @@ -1,4 +1,5 @@ """Support for the Philips Hue sensors as a platform.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hue/v1/sensor_device.py b/homeassistant/components/hue/v1/sensor_device.py index 9ffc1518cba..1ff97af2e62 100644 --- a/homeassistant/components/hue/v1/sensor_device.py +++ b/homeassistant/components/hue/v1/sensor_device.py @@ -1,4 +1,5 @@ """Support for the Philips Hue sensor devices.""" + from homeassistant.helpers import entity from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/hue/v2/binary_sensor.py b/homeassistant/components/hue/v2/binary_sensor.py index 4707302d288..bc650569a63 100644 --- a/homeassistant/components/hue/v2/binary_sensor.py +++ b/homeassistant/components/hue/v2/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Hue binary sensors.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/hue/v2/device.py b/homeassistant/components/hue/v2/device.py index 75f474cc0ea..38c5724d4a8 100644 --- a/homeassistant/components/hue/v2/device.py +++ b/homeassistant/components/hue/v2/device.py @@ -1,4 +1,5 @@ """Handles Hue resource of type `device` mapping to Home Assistant device.""" + from typing import TYPE_CHECKING from aiohue.v2 import HueBridgeV2 diff --git a/homeassistant/components/hue/v2/device_trigger.py b/homeassistant/components/hue/v2/device_trigger.py index a3027736661..eca7908e930 100644 --- a/homeassistant/components/hue/v2/device_trigger.py +++ b/homeassistant/components/hue/v2/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Philips Hue events.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/hue/v2/entity.py b/homeassistant/components/hue/v2/entity.py index 75e4bb1edd4..8aeac4d8180 100644 --- a/homeassistant/components/hue/v2/entity.py +++ b/homeassistant/components/hue/v2/entity.py @@ -1,4 +1,5 @@ """Generic Hue Entity Model.""" + from __future__ import annotations from typing import TYPE_CHECKING, TypeAlias diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 8ce6d287551..32ac29b71f2 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -1,4 +1,5 @@ """Support for Hue groups (room/zone).""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hue/v2/helpers.py b/homeassistant/components/hue/v2/helpers.py index 97fdbe6160a..480296760e7 100644 --- a/homeassistant/components/hue/v2/helpers.py +++ b/homeassistant/components/hue/v2/helpers.py @@ -1,4 +1,5 @@ """Helper functions for Philips Hue v2.""" + from __future__ import annotations diff --git a/homeassistant/components/hue/v2/light.py b/homeassistant/components/hue/v2/light.py index bbf5dc9c19f..9fb883d1930 100644 --- a/homeassistant/components/hue/v2/light.py +++ b/homeassistant/components/hue/v2/light.py @@ -1,4 +1,5 @@ """Support for Hue lights.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/hue/v2/sensor.py b/homeassistant/components/hue/v2/sensor.py index 59dc8de2975..e46ca561964 100644 --- a/homeassistant/components/hue/v2/sensor.py +++ b/homeassistant/components/hue/v2/sensor.py @@ -1,4 +1,5 @@ """Support for Hue sensors.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/huisbaasje/const.py b/homeassistant/components/huisbaasje/const.py index f9084831263..108e3fffa1e 100644 --- a/homeassistant/components/huisbaasje/const.py +++ b/homeassistant/components/huisbaasje/const.py @@ -1,4 +1,5 @@ """Constants for the Huisbaasje integration.""" + from energyflip.const import ( SOURCE_TYPE_ELECTRICITY, SOURCE_TYPE_ELECTRICITY_IN, diff --git a/homeassistant/components/huisbaasje/sensor.py b/homeassistant/components/huisbaasje/sensor.py index f07711268d5..d09b559516b 100644 --- a/homeassistant/components/huisbaasje/sensor.py +++ b/homeassistant/components/huisbaasje/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 37f9d49f0dd..97f78aba21c 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -1,4 +1,5 @@ """Provides functionality to interact with humidifier devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/humidifier/const.py b/homeassistant/components/humidifier/const.py index 66ac0fcf18d..fc6b0fc14d4 100644 --- a/homeassistant/components/humidifier/const.py +++ b/homeassistant/components/humidifier/const.py @@ -1,4 +1,5 @@ """Provides the constants needed for component.""" + from enum import IntFlag, StrEnum from functools import partial diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index f1f25101e93..74ef73443d6 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Humidifier.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index c2c0378a746..7ea9899bba7 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -1,4 +1,5 @@ """Provide the device automations for Humidifier.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/humidifier/device_trigger.py b/homeassistant/components/humidifier/device_trigger.py index 0d689a35318..757b0d9a078 100644 --- a/homeassistant/components/humidifier/device_trigger.py +++ b/homeassistant/components/humidifier/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Climate.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py index 103521aeb04..361de8e36db 100644 --- a/homeassistant/components/humidifier/intent.py +++ b/homeassistant/components/humidifier/intent.py @@ -1,4 +1,5 @@ """Intents for the humidifier integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/humidifier/reproduce_state.py b/homeassistant/components/humidifier/reproduce_state.py index be4f1afbeb9..7caff04acdb 100644 --- a/homeassistant/components/humidifier/reproduce_state.py +++ b/homeassistant/components/humidifier/reproduce_state.py @@ -1,4 +1,5 @@ """Module that groups code required to handle state restore for component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/humidifier/significant_change.py b/homeassistant/components/humidifier/significant_change.py index cc279a9fa41..dcf89f2eba9 100644 --- a/homeassistant/components/humidifier/significant_change.py +++ b/homeassistant/components/humidifier/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Humidifier state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/hunterdouglas_powerview/button.py b/homeassistant/components/hunterdouglas_powerview/button.py index c37741fcb09..1ce30e8df81 100644 --- a/homeassistant/components/hunterdouglas_powerview/button.py +++ b/homeassistant/components/hunterdouglas_powerview/button.py @@ -1,4 +1,5 @@ """Buttons for Hunter Douglas Powerview advanced features.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/hunterdouglas_powerview/config_flow.py b/homeassistant/components/hunterdouglas_powerview/config_flow.py index cee5d65fd0a..2b511144380 100644 --- a/homeassistant/components/hunterdouglas_powerview/config_flow.py +++ b/homeassistant/components/hunterdouglas_powerview/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Hunter Douglas PowerView integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hunterdouglas_powerview/coordinator.py b/homeassistant/components/hunterdouglas_powerview/coordinator.py index db4079f2b58..2ffb463dc92 100644 --- a/homeassistant/components/hunterdouglas_powerview/coordinator.py +++ b/homeassistant/components/hunterdouglas_powerview/coordinator.py @@ -1,4 +1,5 @@ """Coordinate data for powerview devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 5b998b697a4..a3919aefd7c 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -1,4 +1,5 @@ """Support for hunter douglas shades.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hunterdouglas_powerview/diagnostics.py b/homeassistant/components/hunterdouglas_powerview/diagnostics.py index 12f424ea501..1fbf721d2bd 100644 --- a/homeassistant/components/hunterdouglas_powerview/diagnostics.py +++ b/homeassistant/components/hunterdouglas_powerview/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Powerview Hunter Douglas.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/hunterdouglas_powerview/model.py b/homeassistant/components/hunterdouglas_powerview/model.py index e2311eb4e4c..7cf259ced18 100644 --- a/homeassistant/components/hunterdouglas_powerview/model.py +++ b/homeassistant/components/hunterdouglas_powerview/model.py @@ -1,4 +1,5 @@ """Define Hunter Douglas data models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index 0ba9b13d03b..af5b86960c4 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -1,4 +1,5 @@ """Support for Powerview scenes from a Powerview hub.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hunterdouglas_powerview/select.py b/homeassistant/components/hunterdouglas_powerview/select.py index bbe4614afd1..c902c2b1a58 100644 --- a/homeassistant/components/hunterdouglas_powerview/select.py +++ b/homeassistant/components/hunterdouglas_powerview/select.py @@ -1,4 +1,5 @@ """Support for hunterdouglass_powerview settings.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/hunterdouglas_powerview/shade_data.py b/homeassistant/components/hunterdouglas_powerview/shade_data.py index 86f232c3b66..4d780b83ecb 100644 --- a/homeassistant/components/hunterdouglas_powerview/shade_data.py +++ b/homeassistant/components/hunterdouglas_powerview/shade_data.py @@ -1,4 +1,5 @@ """Shade data for the Hunter Douglas PowerView integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hunterdouglas_powerview/util.py b/homeassistant/components/hunterdouglas_powerview/util.py index 15330f30bdb..1d670f46429 100644 --- a/homeassistant/components/hunterdouglas_powerview/util.py +++ b/homeassistant/components/hunterdouglas_powerview/util.py @@ -1,4 +1,5 @@ """Coordinate data for powerview devices.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/husqvarna_automower/config_flow.py b/homeassistant/components/husqvarna_automower/config_flow.py index 5ba0aeae154..88b4899ff12 100644 --- a/homeassistant/components/husqvarna_automower/config_flow.py +++ b/homeassistant/components/husqvarna_automower/config_flow.py @@ -1,4 +1,5 @@ """Config flow to add the integration via the UI.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/husqvarna_automower/sensor.py b/homeassistant/components/husqvarna_automower/sensor.py index 970c444737c..5358ad6e4cf 100644 --- a/homeassistant/components/husqvarna_automower/sensor.py +++ b/homeassistant/components/husqvarna_automower/sensor.py @@ -1,4 +1,5 @@ """Creates a the sensor entities for the mower.""" + from collections.abc import Callable from dataclasses import dataclass import datetime diff --git a/homeassistant/components/huum/__init__.py b/homeassistant/components/huum/__init__.py index a5daf471a2d..75faf1923df 100644 --- a/homeassistant/components/huum/__init__.py +++ b/homeassistant/components/huum/__init__.py @@ -1,4 +1,5 @@ """The Huum integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/huum/climate.py b/homeassistant/components/huum/climate.py index 2bc3c626deb..df740aea3d1 100644 --- a/homeassistant/components/huum/climate.py +++ b/homeassistant/components/huum/climate.py @@ -1,4 +1,5 @@ """Support for Huum wifi-enabled sauna.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/huum/config_flow.py b/homeassistant/components/huum/config_flow.py index 76a9f3563bb..e2ea2a7dbe1 100644 --- a/homeassistant/components/huum/config_flow.py +++ b/homeassistant/components/huum/config_flow.py @@ -1,4 +1,5 @@ """Config flow for huum integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hvv_departures/binary_sensor.py b/homeassistant/components/hvv_departures/binary_sensor.py index 8337921acf6..913c61f91b4 100644 --- a/homeassistant/components/hvv_departures/binary_sensor.py +++ b/homeassistant/components/hvv_departures/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform for hvv_departures.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hvv_departures/config_flow.py b/homeassistant/components/hvv_departures/config_flow.py index e0ab65ba1c4..0c909e2d8c1 100644 --- a/homeassistant/components/hvv_departures/config_flow.py +++ b/homeassistant/components/hvv_departures/config_flow.py @@ -1,4 +1,5 @@ """Config flow for HVV integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/hvv_departures/sensor.py b/homeassistant/components/hvv_departures/sensor.py index 2267522e21b..5998a3dd826 100644 --- a/homeassistant/components/hvv_departures/sensor.py +++ b/homeassistant/components/hvv_departures/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for hvv.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index 0b12fcb3ddb..7740334e7fb 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Hydrawise sprinkler binary sensors.""" + from __future__ import annotations from pydrawise.schema import Zone diff --git a/homeassistant/components/hydrawise/entity.py b/homeassistant/components/hydrawise/entity.py index 887de6ba648..2ae893887e6 100644 --- a/homeassistant/components/hydrawise/entity.py +++ b/homeassistant/components/hydrawise/entity.py @@ -1,4 +1,5 @@ """Base classes for Hydrawise entities.""" + from __future__ import annotations from pydrawise.schema import Controller, Zone diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index 9e3445b2c5d..eedeb4a07bc 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -1,4 +1,5 @@ """Support for Hydrawise sprinkler sensors.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py index 8a92a56975a..49106a5938a 100644 --- a/homeassistant/components/hydrawise/switch.py +++ b/homeassistant/components/hydrawise/switch.py @@ -1,4 +1,5 @@ """Support for Hydrawise cloud switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/hyperion/__init__.py b/homeassistant/components/hyperion/__init__.py index 58eaedb3ff9..94137b5dd3f 100644 --- a/homeassistant/components/hyperion/__init__.py +++ b/homeassistant/components/hyperion/__init__.py @@ -1,4 +1,5 @@ """The Hyperion component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hyperion/config_flow.py b/homeassistant/components/hyperion/config_flow.py index 3770402dbf0..e29caa27ef7 100644 --- a/homeassistant/components/hyperion/config_flow.py +++ b/homeassistant/components/hyperion/config_flow.py @@ -1,4 +1,5 @@ """Hyperion config flow.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index 824d83591ef..5fa129ce7ad 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -1,4 +1,5 @@ """Support for Hyperion-NG remotes.""" + from __future__ import annotations from collections.abc import Callable, Mapping, Sequence diff --git a/homeassistant/components/hyperion/sensor.py b/homeassistant/components/hyperion/sensor.py index f0d1c6b1314..f537c282686 100644 --- a/homeassistant/components/hyperion/sensor.py +++ b/homeassistant/components/hyperion/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Hyperion.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index eb7b260a370..dbe0bbb5c70 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -1,4 +1,5 @@ """Switch platform for Hyperion.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/ialarm/__init__.py b/homeassistant/components/ialarm/__init__.py index ff54c02a2d4..6ebd219f6ec 100644 --- a/homeassistant/components/ialarm/__init__.py +++ b/homeassistant/components/ialarm/__init__.py @@ -1,4 +1,5 @@ """iAlarm integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ialarm/alarm_control_panel.py b/homeassistant/components/ialarm/alarm_control_panel.py index b09e31f5312..44e676fc32e 100644 --- a/homeassistant/components/ialarm/alarm_control_panel.py +++ b/homeassistant/components/ialarm/alarm_control_panel.py @@ -1,4 +1,5 @@ """Interfaces with iAlarm control panels.""" + from __future__ import annotations from homeassistant.components.alarm_control_panel import ( diff --git a/homeassistant/components/ialarm/const.py b/homeassistant/components/ialarm/const.py index c6eaf0ec979..d1561cc86d5 100644 --- a/homeassistant/components/ialarm/const.py +++ b/homeassistant/components/ialarm/const.py @@ -1,4 +1,5 @@ """Constants for the iAlarm integration.""" + from pyialarm import IAlarm from homeassistant.const import ( diff --git a/homeassistant/components/iammeter/const.py b/homeassistant/components/iammeter/const.py index c2d122c9e32..0336007ef3e 100644 --- a/homeassistant/components/iammeter/const.py +++ b/homeassistant/components/iammeter/const.py @@ -1,4 +1,5 @@ """Constants for the Iammeter integration.""" + from __future__ import annotations DOMAIN = "iammeter" diff --git a/homeassistant/components/iammeter/sensor.py b/homeassistant/components/iammeter/sensor.py index 3537737f122..2a5d3ed7a81 100644 --- a/homeassistant/components/iammeter/sensor.py +++ b/homeassistant/components/iammeter/sensor.py @@ -1,4 +1,5 @@ """Support for iammeter via local API.""" + from __future__ import annotations from asyncio import timeout diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index 49eaa2b24a5..6468347b001 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -1,4 +1,5 @@ """Component to embed Aqualink devices.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/iaqualink/binary_sensor.py b/homeassistant/components/iaqualink/binary_sensor.py index 149261f97fc..defacb26e29 100644 --- a/homeassistant/components/iaqualink/binary_sensor.py +++ b/homeassistant/components/iaqualink/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Aqualink temperature sensors.""" + from __future__ import annotations from iaqualink.device import AqualinkBinarySensor diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index 5a81ad3d681..77981c98aa4 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -1,4 +1,5 @@ """Support for Aqualink Thermostats.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index abacdf7a42f..3605c328903 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure zone component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/iaqualink/const.py b/homeassistant/components/iaqualink/const.py index ef939e3fc52..1db4b5a6f16 100644 --- a/homeassistant/components/iaqualink/const.py +++ b/homeassistant/components/iaqualink/const.py @@ -1,4 +1,5 @@ """Constants for the iaqualink component.""" + from datetime import timedelta DOMAIN = "iaqualink" diff --git a/homeassistant/components/iaqualink/light.py b/homeassistant/components/iaqualink/light.py index 3a166ba593d..d5e37b9bac6 100644 --- a/homeassistant/components/iaqualink/light.py +++ b/homeassistant/components/iaqualink/light.py @@ -1,4 +1,5 @@ """Support for Aqualink pool lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 15e8fc5836d..9859e535c60 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -1,4 +1,5 @@ """Support for Aqualink temperature sensors.""" + from __future__ import annotations from iaqualink.device import AqualinkSensor diff --git a/homeassistant/components/iaqualink/switch.py b/homeassistant/components/iaqualink/switch.py index 590fcd61419..65d1c81d7fe 100644 --- a/homeassistant/components/iaqualink/switch.py +++ b/homeassistant/components/iaqualink/switch.py @@ -1,4 +1,5 @@ """Support for Aqualink pool feature switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/iaqualink/utils.py b/homeassistant/components/iaqualink/utils.py index 87bc863a7f8..62d2d4d2e93 100644 --- a/homeassistant/components/iaqualink/utils.py +++ b/homeassistant/components/iaqualink/utils.py @@ -1,4 +1,5 @@ """Utility functions for Aqualink devices.""" + from __future__ import annotations from collections.abc import Awaitable diff --git a/homeassistant/components/ibeacon/__init__.py b/homeassistant/components/ibeacon/__init__.py index 02a19ef6332..0e89ee3bbcd 100644 --- a/homeassistant/components/ibeacon/__init__.py +++ b/homeassistant/components/ibeacon/__init__.py @@ -1,4 +1,5 @@ """The iBeacon tracker integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/ibeacon/config_flow.py b/homeassistant/components/ibeacon/config_flow.py index c315090ef3d..ccedaa675b6 100644 --- a/homeassistant/components/ibeacon/config_flow.py +++ b/homeassistant/components/ibeacon/config_flow.py @@ -1,4 +1,5 @@ """Config flow for iBeacon Tracker integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ibeacon/coordinator.py b/homeassistant/components/ibeacon/coordinator.py index b23ea77e013..27181e80ed8 100644 --- a/homeassistant/components/ibeacon/coordinator.py +++ b/homeassistant/components/ibeacon/coordinator.py @@ -1,4 +1,5 @@ """Tracking for iBeacon devices.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/ibeacon/device_tracker.py b/homeassistant/components/ibeacon/device_tracker.py index 80d5733eccf..8d24d7f0aa9 100644 --- a/homeassistant/components/ibeacon/device_tracker.py +++ b/homeassistant/components/ibeacon/device_tracker.py @@ -1,4 +1,5 @@ """Support for tracking iBeacon devices.""" + from __future__ import annotations from ibeacon_ble import iBeaconAdvertisement diff --git a/homeassistant/components/ibeacon/entity.py b/homeassistant/components/ibeacon/entity.py index b25c82037e1..d4f969ff94a 100644 --- a/homeassistant/components/ibeacon/entity.py +++ b/homeassistant/components/ibeacon/entity.py @@ -1,4 +1,5 @@ """Support for iBeacon device sensors.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/ibeacon/sensor.py b/homeassistant/components/ibeacon/sensor.py index 500b0de93f6..8ec3ef9b67b 100644 --- a/homeassistant/components/ibeacon/sensor.py +++ b/homeassistant/components/ibeacon/sensor.py @@ -1,4 +1,5 @@ """Support for iBeacon device sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/icloud/__init__.py b/homeassistant/components/icloud/__init__.py index 010de4ec4ba..431a1abd2e1 100644 --- a/homeassistant/components/icloud/__init__.py +++ b/homeassistant/components/icloud/__init__.py @@ -1,4 +1,5 @@ """The iCloud component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 257bf536006..cee80535f55 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -1,4 +1,5 @@ """iCloud account.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index 5a46ab3b4f3..caec770743b 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the iCloud integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/icloud/const.py b/homeassistant/components/icloud/const.py index 231f2cc1d0a..b7ea2691ca4 100644 --- a/homeassistant/components/icloud/const.py +++ b/homeassistant/components/icloud/const.py @@ -1,4 +1,5 @@ """iCloud component constants.""" + from homeassistant.const import Platform DOMAIN = "icloud" diff --git a/homeassistant/components/icloud/device_tracker.py b/homeassistant/components/icloud/device_tracker.py index 8513b47be2a..48070a7f153 100644 --- a/homeassistant/components/icloud/device_tracker.py +++ b/homeassistant/components/icloud/device_tracker.py @@ -1,4 +1,5 @@ """Support for tracking for iCloud devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/icloud/sensor.py b/homeassistant/components/icloud/sensor.py index 320c3f9f240..53c9765f6cc 100644 --- a/homeassistant/components/icloud/sensor.py +++ b/homeassistant/components/icloud/sensor.py @@ -1,4 +1,5 @@ """Support for iCloud sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/idasen_desk/__init__.py b/homeassistant/components/idasen_desk/__init__.py index c3e5f3de429..75ef70fcb50 100644 --- a/homeassistant/components/idasen_desk/__init__.py +++ b/homeassistant/components/idasen_desk/__init__.py @@ -1,4 +1,5 @@ """The IKEA Idasen Desk integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/idasen_desk/button.py b/homeassistant/components/idasen_desk/button.py index 1887ffe438b..7119d890a64 100644 --- a/homeassistant/components/idasen_desk/button.py +++ b/homeassistant/components/idasen_desk/button.py @@ -1,4 +1,5 @@ """Representation of Idasen Desk buttons.""" + from collections.abc import Callable, Coroutine from dataclasses import dataclass import logging diff --git a/homeassistant/components/idasen_desk/config_flow.py b/homeassistant/components/idasen_desk/config_flow.py index 8fec40cde80..8d6af14f043 100644 --- a/homeassistant/components/idasen_desk/config_flow.py +++ b/homeassistant/components/idasen_desk/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Idasen Desk integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/idasen_desk/cover.py b/homeassistant/components/idasen_desk/cover.py index a29d2557f93..f5591eff0d8 100644 --- a/homeassistant/components/idasen_desk/cover.py +++ b/homeassistant/components/idasen_desk/cover.py @@ -1,4 +1,5 @@ """Idasen Desk integration cover platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/idasen_desk/sensor.py b/homeassistant/components/idasen_desk/sensor.py index 44a001960c8..fcbdbc6c25e 100644 --- a/homeassistant/components/idasen_desk/sensor.py +++ b/homeassistant/components/idasen_desk/sensor.py @@ -1,4 +1,5 @@ """Representation of Idasen Desk sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/idteck_prox/__init__.py b/homeassistant/components/idteck_prox/__init__.py index 4de6af70995..7b92499a197 100644 --- a/homeassistant/components/idteck_prox/__init__.py +++ b/homeassistant/components/idteck_prox/__init__.py @@ -1,4 +1,5 @@ """Component for interfacing RFK101 proximity card readers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py index 736efcb03a7..e3db68e2302 100644 --- a/homeassistant/components/ifttt/__init__.py +++ b/homeassistant/components/ifttt/__init__.py @@ -1,4 +1,5 @@ """Support to trigger Maker IFTTT recipes.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py index b568693303a..81ed9320bcb 100644 --- a/homeassistant/components/ifttt/alarm_control_panel.py +++ b/homeassistant/components/ifttt/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for alarm control panels that can be controlled through IFTTT.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ifttt/config_flow.py b/homeassistant/components/ifttt/config_flow.py index dc28f6bbaa2..8c0de38a7f8 100644 --- a/homeassistant/components/ifttt/config_flow.py +++ b/homeassistant/components/ifttt/config_flow.py @@ -1,4 +1,5 @@ """Config flow for IFTTT.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/iglo/light.py b/homeassistant/components/iglo/light.py index 5de24625ea3..1cd303b8856 100644 --- a/homeassistant/components/iglo/light.py +++ b/homeassistant/components/iglo/light.py @@ -1,4 +1,5 @@ """Support for lights under the iGlo brand.""" + from __future__ import annotations import math diff --git a/homeassistant/components/ign_sismologia/geo_location.py b/homeassistant/components/ign_sismologia/geo_location.py index 794da41ea12..af7fab5b79b 100644 --- a/homeassistant/components/ign_sismologia/geo_location.py +++ b/homeassistant/components/ign_sismologia/geo_location.py @@ -1,4 +1,5 @@ """Support for IGN Sismologia (Earthquakes) Feeds.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ihc/binary_sensor.py b/homeassistant/components/ihc/binary_sensor.py index badf0f4e92f..ed273878cb4 100644 --- a/homeassistant/components/ihc/binary_sensor.py +++ b/homeassistant/components/ihc/binary_sensor.py @@ -1,4 +1,5 @@ """Support for IHC binary sensors.""" + from __future__ import annotations from ihcsdk.ihccontroller import IHCController diff --git a/homeassistant/components/ihc/const.py b/homeassistant/components/ihc/const.py index c86e77870c8..8a07bd4fec4 100644 --- a/homeassistant/components/ihc/const.py +++ b/homeassistant/components/ihc/const.py @@ -1,4 +1,5 @@ """IHC component constants.""" + from homeassistant.const import Platform ATTR_IHC_ID = "ihc_id" diff --git a/homeassistant/components/ihc/light.py b/homeassistant/components/ihc/light.py index b469cb54aee..98e373daff4 100644 --- a/homeassistant/components/ihc/light.py +++ b/homeassistant/components/ihc/light.py @@ -1,4 +1,5 @@ """Support for IHC lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ihc/sensor.py b/homeassistant/components/ihc/sensor.py index c1210a358d6..1ca41ed2666 100644 --- a/homeassistant/components/ihc/sensor.py +++ b/homeassistant/components/ihc/sensor.py @@ -1,4 +1,5 @@ """Support for IHC sensors.""" + from __future__ import annotations from ihcsdk.ihccontroller import IHCController diff --git a/homeassistant/components/ihc/switch.py b/homeassistant/components/ihc/switch.py index d4593dad570..f41f17bc998 100644 --- a/homeassistant/components/ihc/switch.py +++ b/homeassistant/components/ihc/switch.py @@ -1,4 +1,5 @@ """Support for IHC switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index 9b05dcddffb..101a1cdf8c4 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -1,4 +1,5 @@ """The image integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/image/const.py b/homeassistant/components/image/const.py index d262bb460f7..d96f13b4951 100644 --- a/homeassistant/components/image/const.py +++ b/homeassistant/components/image/const.py @@ -1,4 +1,5 @@ """Constants for the image integration.""" + from typing import Final DOMAIN: Final = "image" diff --git a/homeassistant/components/image/media_source.py b/homeassistant/components/image/media_source.py index 39f00b587c0..5543ab2e887 100644 --- a/homeassistant/components/image/media_source.py +++ b/homeassistant/components/image/media_source.py @@ -1,4 +1,5 @@ """Expose iamges as media sources.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 178d40d1139..b75cb445895 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -1,4 +1,5 @@ """Provides functionality to interact with image processing services.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/image_upload/__init__.py b/homeassistant/components/image_upload/__init__.py index d7c5052af30..4bde9730674 100644 --- a/homeassistant/components/image_upload/__init__.py +++ b/homeassistant/components/image_upload/__init__.py @@ -1,4 +1,5 @@ """The Image Upload integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/imap/__init__.py b/homeassistant/components/imap/__init__.py index 924408c30b9..091110c5e51 100644 --- a/homeassistant/components/imap/__init__.py +++ b/homeassistant/components/imap/__init__.py @@ -1,4 +1,5 @@ """The imap integration.""" + from __future__ import annotations from aioimaplib import IMAP4_SSL, AioImapException diff --git a/homeassistant/components/imap/config_flow.py b/homeassistant/components/imap/config_flow.py index c4df769384e..414d5830bae 100644 --- a/homeassistant/components/imap/config_flow.py +++ b/homeassistant/components/imap/config_flow.py @@ -1,4 +1,5 @@ """Config flow for imap integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/imap/coordinator.py b/homeassistant/components/imap/coordinator.py index f0c9099863a..2881f9f515e 100644 --- a/homeassistant/components/imap/coordinator.py +++ b/homeassistant/components/imap/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for imag integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/imap/diagnostics.py b/homeassistant/components/imap/diagnostics.py index c7d5151ba49..467f19d6338 100644 --- a/homeassistant/components/imap/diagnostics.py +++ b/homeassistant/components/imap/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for IMAP.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/imap/sensor.py b/homeassistant/components/imap/sensor.py index 07e77b31470..0a9070d7a5e 100644 --- a/homeassistant/components/imap/sensor.py +++ b/homeassistant/components/imap/sensor.py @@ -1,4 +1,5 @@ """IMAP sensor support.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/improv_ble/config_flow.py b/homeassistant/components/improv_ble/config_flow.py index a7d3c2cf000..a1a2d6b1b65 100644 --- a/homeassistant/components/improv_ble/config_flow.py +++ b/homeassistant/components/improv_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Improv via BLE integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/incomfort/__init__.py b/homeassistant/components/incomfort/__init__.py index 7245aff3c35..3311bda23ee 100644 --- a/homeassistant/components/incomfort/__init__.py +++ b/homeassistant/components/incomfort/__init__.py @@ -1,4 +1,5 @@ """Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/incomfort/binary_sensor.py b/homeassistant/components/incomfort/binary_sensor.py index 52edfc6ef02..59096038d6c 100644 --- a/homeassistant/components/incomfort/binary_sensor.py +++ b/homeassistant/components/incomfort/binary_sensor.py @@ -1,4 +1,5 @@ """Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index 0dba00ff416..cc61e179aa4 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -1,4 +1,5 @@ """Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/incomfort/sensor.py b/homeassistant/components/incomfort/sensor.py index 535d8b61653..9106afacb26 100644 --- a/homeassistant/components/incomfort/sensor.py +++ b/homeassistant/components/incomfort/sensor.py @@ -1,4 +1,5 @@ """Support for an Intergas heater via an InComfort/InTouch Lan2RF gateway.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index 367af73810b..2cd7c84a666 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -1,4 +1,5 @@ """Support for an Intergas boiler via an InComfort/Intouch Lan2RF gateway.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index cd7e6a7ed88..daa49a4ba20 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to an Influx database.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/influxdb/const.py b/homeassistant/components/influxdb/const.py index 5ffd70fe992..cab9d1e4c41 100644 --- a/homeassistant/components/influxdb/const.py +++ b/homeassistant/components/influxdb/const.py @@ -1,4 +1,5 @@ """Constants for InfluxDB integration.""" + from datetime import timedelta import re diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index a46ec581207..b9ce68e198c 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -1,4 +1,5 @@ """InfluxDB component which allows you to get data from an Influx database.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/inkbird/__init__.py b/homeassistant/components/inkbird/__init__.py index 5ed0d6fb367..f78a6c4e1b8 100644 --- a/homeassistant/components/inkbird/__init__.py +++ b/homeassistant/components/inkbird/__init__.py @@ -1,4 +1,5 @@ """The INKBIRD Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/inkbird/config_flow.py b/homeassistant/components/inkbird/config_flow.py index 19c27dd5757..0d4e404c9b5 100644 --- a/homeassistant/components/inkbird/config_flow.py +++ b/homeassistant/components/inkbird/config_flow.py @@ -1,4 +1,5 @@ """Config flow for inkbird ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/inkbird/sensor.py b/homeassistant/components/inkbird/sensor.py index f93f2024289..a7bd71005ab 100644 --- a/homeassistant/components/inkbird/sensor.py +++ b/homeassistant/components/inkbird/sensor.py @@ -1,4 +1,5 @@ """Support for inkbird ble sensors.""" + from __future__ import annotations from inkbird_ble import DeviceClass, DeviceKey, SensorUpdate, Units diff --git a/homeassistant/components/input_boolean/__init__.py b/homeassistant/components/input_boolean/__init__.py index 613e8829aa1..91c7de96fe0 100644 --- a/homeassistant/components/input_boolean/__init__.py +++ b/homeassistant/components/input_boolean/__init__.py @@ -1,4 +1,5 @@ """Support to keep track of user controlled booleans for within automation.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/input_boolean/reproduce_state.py b/homeassistant/components/input_boolean/reproduce_state.py index 6c68489e4cb..7af28f8a92a 100644 --- a/homeassistant/components/input_boolean/reproduce_state.py +++ b/homeassistant/components/input_boolean/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an input boolean state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/input_button/__init__.py b/homeassistant/components/input_button/__init__.py index 3318354392c..d6c3644487b 100644 --- a/homeassistant/components/input_button/__init__.py +++ b/homeassistant/components/input_button/__init__.py @@ -1,4 +1,5 @@ """Support to keep track of user controlled buttons which can be used in automations.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/input_datetime/__init__.py b/homeassistant/components/input_datetime/__init__.py index 73a4df12d03..c64ef506670 100644 --- a/homeassistant/components/input_datetime/__init__.py +++ b/homeassistant/components/input_datetime/__init__.py @@ -1,4 +1,5 @@ """Support to select a date and/or a time.""" + from __future__ import annotations import datetime as py_datetime diff --git a/homeassistant/components/input_datetime/reproduce_state.py b/homeassistant/components/input_datetime/reproduce_state.py index 6f36be0850a..ccadbccd8d4 100644 --- a/homeassistant/components/input_datetime/reproduce_state.py +++ b/homeassistant/components/input_datetime/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Input datetime state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/input_number/__init__.py b/homeassistant/components/input_number/__init__.py index 4a74201be15..e37f530b8af 100644 --- a/homeassistant/components/input_number/__init__.py +++ b/homeassistant/components/input_number/__init__.py @@ -1,4 +1,5 @@ """Support to set a numeric value from a slider or text box.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/input_number/reproduce_state.py b/homeassistant/components/input_number/reproduce_state.py index 368f68b5178..c2f9cfc4702 100644 --- a/homeassistant/components/input_number/reproduce_state.py +++ b/homeassistant/components/input_number/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Input number state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/input_select/__init__.py b/homeassistant/components/input_select/__init__.py index 4a384e0c17a..dcb75a92d20 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -1,4 +1,5 @@ """Support to select an option from a list.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/input_select/reproduce_state.py b/homeassistant/components/input_select/reproduce_state.py index 8ba16391d7e..b451f8c3f09 100644 --- a/homeassistant/components/input_select/reproduce_state.py +++ b/homeassistant/components/input_select/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Input select state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/input_text/__init__.py b/homeassistant/components/input_text/__init__.py index 01bd76d1241..52788066ba2 100644 --- a/homeassistant/components/input_text/__init__.py +++ b/homeassistant/components/input_text/__init__.py @@ -1,4 +1,5 @@ """Support to enter a value into a text box.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/input_text/reproduce_state.py b/homeassistant/components/input_text/reproduce_state.py index ef82579f4b7..78e81dba95a 100644 --- a/homeassistant/components/input_text/reproduce_state.py +++ b/homeassistant/components/input_text/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Input text state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index a074ad4600b..529ac20df52 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -1,4 +1,5 @@ """Support for INSTEON Modems (PLM and Hub).""" + from contextlib import suppress import logging diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py index 02af89dba01..fb19d2287cc 100644 --- a/homeassistant/components/insteon/binary_sensor.py +++ b/homeassistant/components/insteon/binary_sensor.py @@ -1,4 +1,5 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" + from pyinsteon.groups import ( CO_SENSOR, DOOR_SENSOR, diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py index 22bd776e1c8..ffdd17f3ac0 100644 --- a/homeassistant/components/insteon/climate.py +++ b/homeassistant/components/insteon/climate.py @@ -1,4 +1,5 @@ """Support for Insteon thermostat.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/insteon/config_flow.py b/homeassistant/components/insteon/config_flow.py index 66a131207bf..ae4216d3c65 100644 --- a/homeassistant/components/insteon/config_flow.py +++ b/homeassistant/components/insteon/config_flow.py @@ -1,4 +1,5 @@ """Test config flow for Insteon.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index da9e3de6422..cbdae434df6 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -1,4 +1,5 @@ """Support for INSTEON fans via PowerLinc Modem.""" + from __future__ import annotations import math diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index 9e9f987d611..5f56c0d6976 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -1,4 +1,5 @@ """Utility methods for the Insteon platform.""" + from collections.abc import Iterable from pyinsteon.device_types.device_base import Device diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index 121d8d62c66..f6752db3cf1 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -1,4 +1,5 @@ """Support for Insteon lights via PowerLinc Modem.""" + from typing import Any from pyinsteon.config import ON_LEVEL diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 497af743195..1c1b54b4747 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -1,4 +1,5 @@ """Schemas used by insteon component.""" + from __future__ import annotations from binascii import Error as HexError, unhexlify diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py index 8acde0429cd..b60729232f2 100644 --- a/homeassistant/components/insteon/switch.py +++ b/homeassistant/components/insteon/switch.py @@ -1,4 +1,5 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" + from typing import Any from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 1438b6c9c52..272018ea507 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -1,4 +1,5 @@ """Utilities used by insteon component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/integration/__init__.py b/homeassistant/components/integration/__init__.py index f482f4e41e8..4a8d4baa3f2 100644 --- a/homeassistant/components/integration/__init__.py +++ b/homeassistant/components/integration/__init__.py @@ -1,4 +1,5 @@ """The Integration integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/integration/config_flow.py b/homeassistant/components/integration/config_flow.py index 3a9e1d15ffe..318f1355aae 100644 --- a/homeassistant/components/integration/config_flow.py +++ b/homeassistant/components/integration/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Integration - Riemann sum integral integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index d7d5c84f17a..63b01d13805 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -1,4 +1,5 @@ """Numeric integration of data coming from a source sensor over time.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/intellifire/__init__.py b/homeassistant/components/intellifire/__init__.py index 81ef383dfab..7af472c8745 100644 --- a/homeassistant/components/intellifire/__init__.py +++ b/homeassistant/components/intellifire/__init__.py @@ -1,4 +1,5 @@ """The IntelliFire integration.""" + from __future__ import annotations from aiohttp import ClientConnectionError diff --git a/homeassistant/components/intellifire/binary_sensor.py b/homeassistant/components/intellifire/binary_sensor.py index b630828d7e9..a1b8865c876 100644 --- a/homeassistant/components/intellifire/binary_sensor.py +++ b/homeassistant/components/intellifire/binary_sensor.py @@ -1,4 +1,5 @@ """Support for IntelliFire Binary Sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/intellifire/climate.py b/homeassistant/components/intellifire/climate.py index 9fed9c08bb6..ed4facffc67 100644 --- a/homeassistant/components/intellifire/climate.py +++ b/homeassistant/components/intellifire/climate.py @@ -1,4 +1,5 @@ """Intellifire Climate Entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/intellifire/config_flow.py b/homeassistant/components/intellifire/config_flow.py index 17d442428e7..268fc6623d3 100644 --- a/homeassistant/components/intellifire/config_flow.py +++ b/homeassistant/components/intellifire/config_flow.py @@ -1,4 +1,5 @@ """Config flow for IntelliFire integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/intellifire/const.py b/homeassistant/components/intellifire/const.py index cae25ea11ae..5c8af1eefe9 100644 --- a/homeassistant/components/intellifire/const.py +++ b/homeassistant/components/intellifire/const.py @@ -1,4 +1,5 @@ """Constants for the IntelliFire integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/intellifire/coordinator.py b/homeassistant/components/intellifire/coordinator.py index 4045c19217b..0a46ff61435 100644 --- a/homeassistant/components/intellifire/coordinator.py +++ b/homeassistant/components/intellifire/coordinator.py @@ -1,4 +1,5 @@ """The IntelliFire integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/intellifire/entity.py b/homeassistant/components/intellifire/entity.py index 6ef63f5347c..3b35c9dabd8 100644 --- a/homeassistant/components/intellifire/entity.py +++ b/homeassistant/components/intellifire/entity.py @@ -1,4 +1,5 @@ """Platform for shared base classes for sensors.""" + from __future__ import annotations from homeassistant.helpers.entity import EntityDescription diff --git a/homeassistant/components/intellifire/fan.py b/homeassistant/components/intellifire/fan.py index 7c376eeec4c..387b6d059c6 100644 --- a/homeassistant/components/intellifire/fan.py +++ b/homeassistant/components/intellifire/fan.py @@ -1,4 +1,5 @@ """Fan definition for Intellifire.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/intellifire/light.py b/homeassistant/components/intellifire/light.py index a807735ed79..a7f2befaf33 100644 --- a/homeassistant/components/intellifire/light.py +++ b/homeassistant/components/intellifire/light.py @@ -1,4 +1,5 @@ """The IntelliFire Light.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/intellifire/number.py b/homeassistant/components/intellifire/number.py index 4cedb3de5d9..dca7a74c78e 100644 --- a/homeassistant/components/intellifire/number.py +++ b/homeassistant/components/intellifire/number.py @@ -1,4 +1,5 @@ """Flame height number sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/intellifire/sensor.py b/homeassistant/components/intellifire/sensor.py index 583e2dc22f9..dd3eef9c9b4 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/intellifire/switch.py b/homeassistant/components/intellifire/switch.py index 161d75de2d8..00de6d74a9c 100644 --- a/homeassistant/components/intellifire/switch.py +++ b/homeassistant/components/intellifire/switch.py @@ -1,4 +1,5 @@ """Define switch func.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/intent_script/__init__.py b/homeassistant/components/intent_script/__init__.py index d184dad47c9..63b37c08950 100644 --- a/homeassistant/components/intent_script/__init__.py +++ b/homeassistant/components/intent_script/__init__.py @@ -1,4 +1,5 @@ """Handle intents with scripts.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py index 64f52fae0a6..7a504d7aced 100644 --- a/homeassistant/components/intesishome/climate.py +++ b/homeassistant/components/intesishome/climate.py @@ -1,4 +1,5 @@ """Support for IntesisHome and airconwithme Smart AC Controllers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ios/config_flow.py b/homeassistant/components/ios/config_flow.py index 47959e34074..6b00589d048 100644 --- a/homeassistant/components/ios/config_flow.py +++ b/homeassistant/components/ios/config_flow.py @@ -1,4 +1,5 @@ """Config flow for iOS.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/ios/notify.py b/homeassistant/components/ios/notify.py index a8d1b2514cd..92a706b3a38 100644 --- a/homeassistant/components/ios/notify.py +++ b/homeassistant/components/ios/notify.py @@ -1,4 +1,5 @@ """Support for iOS push notifications.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/ios/sensor.py b/homeassistant/components/ios/sensor.py index 59a9d499d93..4171b8ecd46 100644 --- a/homeassistant/components/ios/sensor.py +++ b/homeassistant/components/ios/sensor.py @@ -1,4 +1,5 @@ """Support for Home Assistant iOS app sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/iotawatt/__init__.py b/homeassistant/components/iotawatt/__init__.py index 9f51382f98e..8f35d4e0796 100644 --- a/homeassistant/components/iotawatt/__init__.py +++ b/homeassistant/components/iotawatt/__init__.py @@ -1,4 +1,5 @@ """The iotawatt integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/iotawatt/config_flow.py b/homeassistant/components/iotawatt/config_flow.py index 6b7b86b73af..b9310b8a2b9 100644 --- a/homeassistant/components/iotawatt/config_flow.py +++ b/homeassistant/components/iotawatt/config_flow.py @@ -1,4 +1,5 @@ """Config flow for iotawatt integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/iotawatt/const.py b/homeassistant/components/iotawatt/const.py index db847f3dfe8..de008388f62 100644 --- a/homeassistant/components/iotawatt/const.py +++ b/homeassistant/components/iotawatt/const.py @@ -1,4 +1,5 @@ """Constants for the IoTaWatt integration.""" + from __future__ import annotations import json diff --git a/homeassistant/components/iotawatt/coordinator.py b/homeassistant/components/iotawatt/coordinator.py index 6c97fc99169..e741c7a5a27 100644 --- a/homeassistant/components/iotawatt/coordinator.py +++ b/homeassistant/components/iotawatt/coordinator.py @@ -1,4 +1,5 @@ """IoTaWatt DataUpdateCoordinator.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/iotawatt/sensor.py b/homeassistant/components/iotawatt/sensor.py index 4faac347c40..c9af588c160 100644 --- a/homeassistant/components/iotawatt/sensor.py +++ b/homeassistant/components/iotawatt/sensor.py @@ -1,4 +1,5 @@ """Support for IoTaWatt Energy monitor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/iperf3/__init__.py b/homeassistant/components/iperf3/__init__.py index 0448c3e48b2..a621f1fb27e 100644 --- a/homeassistant/components/iperf3/__init__.py +++ b/homeassistant/components/iperf3/__init__.py @@ -1,4 +1,5 @@ """Support for Iperf3 network measurement tool.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/iperf3/sensor.py b/homeassistant/components/iperf3/sensor.py index d3db0e76631..27b3eac26b5 100644 --- a/homeassistant/components/iperf3/sensor.py +++ b/homeassistant/components/iperf3/sensor.py @@ -1,4 +1,5 @@ """Support for Iperf3 sensors.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity, SensorEntityDescription diff --git a/homeassistant/components/ipma/const.py b/homeassistant/components/ipma/const.py index 26fdee779b6..dd6f1fba64a 100644 --- a/homeassistant/components/ipma/const.py +++ b/homeassistant/components/ipma/const.py @@ -1,4 +1,5 @@ """Constants for IPMA component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ipma/entity.py b/homeassistant/components/ipma/entity.py index 7eb8e2fe1a7..ef9401fcb07 100644 --- a/homeassistant/components/ipma/entity.py +++ b/homeassistant/components/ipma/entity.py @@ -1,4 +1,5 @@ """Base Entity for IPMA.""" + from __future__ import annotations from pyipma.api import IPMA_API diff --git a/homeassistant/components/ipma/sensor.py b/homeassistant/components/ipma/sensor.py index 99e994069a5..5f2cb98646b 100644 --- a/homeassistant/components/ipma/sensor.py +++ b/homeassistant/components/ipma/sensor.py @@ -1,4 +1,5 @@ """Support for IPMA sensors.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ipma/system_health.py b/homeassistant/components/ipma/system_health.py index cd783490c35..7b6a5c517c7 100644 --- a/homeassistant/components/ipma/system_health.py +++ b/homeassistant/components/ipma/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from homeassistant.components import system_health from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/ipma/weather.py b/homeassistant/components/ipma/weather.py index 866f44f0617..cd4c650beef 100644 --- a/homeassistant/components/ipma/weather.py +++ b/homeassistant/components/ipma/weather.py @@ -1,4 +1,5 @@ """Support for IPMA weather service.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index 98870c44f5a..10f24a1499d 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -1,4 +1,5 @@ """The Internet Printing Protocol (IPP) integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index 334fb5d7690..ecd4d1af9f6 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the IPP integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ipp/coordinator.py b/homeassistant/components/ipp/coordinator.py index 8eb8c972fab..535b18bcaf0 100644 --- a/homeassistant/components/ipp/coordinator.py +++ b/homeassistant/components/ipp/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for The Internet Printing Protocol (IPP) integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ipp/entity.py b/homeassistant/components/ipp/entity.py index 05adf711fd9..fdaa4cf035e 100644 --- a/homeassistant/components/ipp/entity.py +++ b/homeassistant/components/ipp/entity.py @@ -1,4 +1,5 @@ """Entities for The Internet Printing Protocol (IPP) integration.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 72fe0751f9f..8e3162ec61d 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -1,4 +1,5 @@ """Support for IPP sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index aa5528cc06a..8f85d98c576 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -1,4 +1,5 @@ """Support for IQVIA.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/iqvia/config_flow.py b/homeassistant/components/iqvia/config_flow.py index d293aedd7b2..444d86a7fb8 100644 --- a/homeassistant/components/iqvia/config_flow.py +++ b/homeassistant/components/iqvia/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the IQVIA component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/iqvia/diagnostics.py b/homeassistant/components/iqvia/diagnostics.py index 6f2df6bd7d3..64827f183ff 100644 --- a/homeassistant/components/iqvia/diagnostics.py +++ b/homeassistant/components/iqvia/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for IQVIA.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index d17a278a106..da8556d9218 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -1,4 +1,5 @@ """Support for IQVIA sensors.""" + from __future__ import annotations from statistics import mean diff --git a/homeassistant/components/irish_rail_transport/sensor.py b/homeassistant/components/irish_rail_transport/sensor.py index 70b53b80d9c..b0ad9372f86 100644 --- a/homeassistant/components/irish_rail_transport/sensor.py +++ b/homeassistant/components/irish_rail_transport/sensor.py @@ -1,4 +1,5 @@ """Support for Irish Rail RTPI information.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/islamic_prayer_times/__init__.py b/homeassistant/components/islamic_prayer_times/__init__.py index 55e5618d9d4..15e165d2f48 100644 --- a/homeassistant/components/islamic_prayer_times/__init__.py +++ b/homeassistant/components/islamic_prayer_times/__init__.py @@ -1,4 +1,5 @@ """The islamic_prayer_times component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/islamic_prayer_times/config_flow.py b/homeassistant/components/islamic_prayer_times/config_flow.py index 766163a9b0d..12730c9be08 100644 --- a/homeassistant/components/islamic_prayer_times/config_flow.py +++ b/homeassistant/components/islamic_prayer_times/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Islamic Prayer Times integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/islamic_prayer_times/const.py b/homeassistant/components/islamic_prayer_times/const.py index 926651738a2..dc4237e5efa 100644 --- a/homeassistant/components/islamic_prayer_times/const.py +++ b/homeassistant/components/islamic_prayer_times/const.py @@ -1,4 +1,5 @@ """Constants for the Islamic Prayer component.""" + from typing import Final DOMAIN: Final = "islamic_prayer_times" diff --git a/homeassistant/components/islamic_prayer_times/coordinator.py b/homeassistant/components/islamic_prayer_times/coordinator.py index be138e7b45b..d70d0e2f4fe 100644 --- a/homeassistant/components/islamic_prayer_times/coordinator.py +++ b/homeassistant/components/islamic_prayer_times/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for the Islamic prayer times integration.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/islamic_prayer_times/sensor.py b/homeassistant/components/islamic_prayer_times/sensor.py index 70b2c9d9cc6..eb042d83c49 100644 --- a/homeassistant/components/islamic_prayer_times/sensor.py +++ b/homeassistant/components/islamic_prayer_times/sensor.py @@ -1,4 +1,5 @@ """Platform to retrieve Islamic prayer times information for Home Assistant.""" + from datetime import datetime from homeassistant.components.sensor import ( diff --git a/homeassistant/components/iss/__init__.py b/homeassistant/components/iss/__init__.py index 640e9d5d1da..606263ce769 100644 --- a/homeassistant/components/iss/__init__.py +++ b/homeassistant/components/iss/__init__.py @@ -1,4 +1,5 @@ """The iss component.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/iss/sensor.py b/homeassistant/components/iss/sensor.py index d7b7083cdef..f4f91f0099e 100644 --- a/homeassistant/components/iss/sensor.py +++ b/homeassistant/components/iss/sensor.py @@ -1,4 +1,5 @@ """Support for iss sensor.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 0c5ea27a0b9..0c238182849 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -1,4 +1,5 @@ """Support the Universal Devices ISY/IoX controllers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 7be3b87a0d3..c130ba32746 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -1,4 +1,5 @@ """Support for ISY binary sensors.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/isy994/button.py b/homeassistant/components/isy994/button.py index 6e00e1934f2..9e0a3320f6a 100644 --- a/homeassistant/components/isy994/button.py +++ b/homeassistant/components/isy994/button.py @@ -1,4 +1,5 @@ """Representation of ISY/IoX buttons.""" + from __future__ import annotations from pyisy import ISY diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index 06b73978456..ba64fe887bc 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -1,4 +1,5 @@ """Support for Insteon Thermostats via ISY Platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index 658f7204d41..c66e8af4022 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Universal Devices ISY/IoX integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index 2ada6339295..19e8a3da197 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -1,4 +1,5 @@ """Support for ISY covers.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py index a93f2d91d31..893b33644fe 100644 --- a/homeassistant/components/isy994/entity.py +++ b/homeassistant/components/isy994/entity.py @@ -1,4 +1,5 @@ """Representation of ISYEntity Types.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index ebdef4146e0..a7d79c29549 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -1,4 +1,5 @@ """Support for ISY fans.""" + from __future__ import annotations import math diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index 5e0ff592ea9..1e496446a30 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -1,4 +1,5 @@ """Sorting helpers for ISY device classifications.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index b16b4ca5a83..ec026a2dc22 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -1,4 +1,5 @@ """Support for ISY lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py index 67c2587a238..73b10990d29 100644 --- a/homeassistant/components/isy994/lock.py +++ b/homeassistant/components/isy994/lock.py @@ -1,4 +1,5 @@ """Support for ISY locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/isy994/models.py b/homeassistant/components/isy994/models.py index c8a7e1dbefe..5b599df9458 100644 --- a/homeassistant/components/isy994/models.py +++ b/homeassistant/components/isy994/models.py @@ -1,4 +1,5 @@ """The ISY/IoX integration data models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/isy994/number.py b/homeassistant/components/isy994/number.py index baadf3b2dc7..c8feba1bf8d 100644 --- a/homeassistant/components/isy994/number.py +++ b/homeassistant/components/isy994/number.py @@ -1,4 +1,5 @@ """Support for ISY number entities.""" + from __future__ import annotations from dataclasses import replace diff --git a/homeassistant/components/isy994/select.py b/homeassistant/components/isy994/select.py index 3c55e5cbda9..41e5899504d 100644 --- a/homeassistant/components/isy994/select.py +++ b/homeassistant/components/isy994/select.py @@ -1,4 +1,5 @@ """Support for ISY select entities.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index 9e39f5d04e4..94c333346af 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -1,4 +1,5 @@ """Support for ISY sensors.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index a6adfcfb917..9159627c9eb 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -1,4 +1,5 @@ """ISY Services and Commands.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py index da208dcc79c..391ad18e02f 100644 --- a/homeassistant/components/isy994/switch.py +++ b/homeassistant/components/isy994/switch.py @@ -1,4 +1,5 @@ """Support for ISY switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/isy994/system_health.py b/homeassistant/components/isy994/system_health.py index 44286111a62..dfc45c267dd 100644 --- a/homeassistant/components/isy994/system_health.py +++ b/homeassistant/components/isy994/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/isy994/util.py b/homeassistant/components/isy994/util.py index f4846b61aed..ed1a5abca8b 100644 --- a/homeassistant/components/isy994/util.py +++ b/homeassistant/components/isy994/util.py @@ -1,4 +1,5 @@ """ISY utils.""" + from __future__ import annotations from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/itach/remote.py b/homeassistant/components/itach/remote.py index c0dddfa080e..606ca4fd021 100644 --- a/homeassistant/components/itach/remote.py +++ b/homeassistant/components/itach/remote.py @@ -1,4 +1,5 @@ """Support for iTach IR devices.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/itunes/media_player.py b/homeassistant/components/itunes/media_player.py index d73086f9ab1..13ad66f1417 100644 --- a/homeassistant/components/itunes/media_player.py +++ b/homeassistant/components/itunes/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing to iTunes API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index e85b7ef4d56..1786ef23522 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -1,4 +1,5 @@ """Support for the iZone HVAC.""" + from __future__ import annotations from collections.abc import Callable, Mapping From 7cbe49520e7ff8c038ceb35d75b37dc788f9f6cd Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 8 Mar 2024 14:54:00 +0100 Subject: [PATCH 0548/1691] Downgrade `pysnmp-lextudio` to version `5.0.34` (#112696) Downgrade pysnmp-lextudio to version 5.0.34 Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/snmp/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index beacf55dda9..6ce96a945dd 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -6,5 +6,5 @@ "import_executor": true, "iot_class": "local_polling", "loggers": ["pyasn1", "pysmi", "pysnmp"], - "requirements": ["pysnmp-lextudio==6.0.2"] + "requirements": ["pysnmp-lextudio==5.0.34"] } diff --git a/requirements_all.txt b/requirements_all.txt index f38f474b1cd..0cb57a27bc7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2149,7 +2149,7 @@ pysmartthings==0.7.8 pysml==0.0.12 # homeassistant.components.snmp -pysnmp-lextudio==6.0.2 +pysnmp-lextudio==5.0.34 # homeassistant.components.snooz pysnooz==0.8.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c4a038e88f5..d0ba8c7e25e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1670,7 +1670,7 @@ pysmartthings==0.7.8 pysml==0.0.12 # homeassistant.components.snmp -pysnmp-lextudio==6.0.2 +pysnmp-lextudio==5.0.34 # homeassistant.components.snooz pysnooz==0.8.6 From 1722e23df897229c8158a6c23964ef6383f99f13 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:55:15 +0100 Subject: [PATCH 0549/1691] Add empty line after module docstring [tests l-p] (#112710) --- tests/components/lacrosse_view/conftest.py | 1 + tests/components/lacrosse_view/test_config_flow.py | 1 + tests/components/lacrosse_view/test_diagnostics.py | 1 + tests/components/lacrosse_view/test_init.py | 1 + tests/components/lacrosse_view/test_sensor.py | 1 + tests/components/lamarzocco/test_binary_sensor.py | 1 + tests/components/lamarzocco/test_config_flow.py | 1 + tests/components/lamarzocco/test_diagnostics.py | 1 + tests/components/lamarzocco/test_init.py | 1 + tests/components/lamarzocco/test_sensor.py | 1 + tests/components/lamarzocco/test_switch.py | 1 + tests/components/lametric/conftest.py | 1 + tests/components/lametric/test_button.py | 1 + tests/components/lametric/test_config_flow.py | 1 + tests/components/lametric/test_diagnostics.py | 1 + tests/components/lametric/test_helpers.py | 1 + tests/components/lametric/test_init.py | 1 + tests/components/lametric/test_notify.py | 1 + tests/components/lametric/test_number.py | 1 + tests/components/lametric/test_select.py | 1 + tests/components/lametric/test_services.py | 1 + tests/components/lametric/test_switch.py | 1 + tests/components/landisgyr_heat_meter/conftest.py | 1 + tests/components/landisgyr_heat_meter/test_config_flow.py | 1 + tests/components/landisgyr_heat_meter/test_init.py | 1 + tests/components/lastfm/conftest.py | 1 + tests/components/lastfm/test_config_flow.py | 1 + tests/components/lastfm/test_init.py | 1 + tests/components/launch_library/test_config_flow.py | 1 + tests/components/lawn_mower/test_init.py | 1 + tests/components/lcn/test_binary_sensor.py | 1 + tests/components/lcn/test_config_flow.py | 1 + tests/components/lcn/test_cover.py | 1 + tests/components/lcn/test_device_trigger.py | 1 + tests/components/lcn/test_events.py | 1 + tests/components/lcn/test_init.py | 1 + tests/components/lcn/test_light.py | 1 + tests/components/lcn/test_sensor.py | 1 + tests/components/lcn/test_switch.py | 1 + tests/components/ld2410_ble/test_config_flow.py | 1 + tests/components/leaone/test_config_flow.py | 1 + tests/components/led_ble/test_config_flow.py | 1 + tests/components/lg_soundbar/test_config_flow.py | 1 + tests/components/lidarr/conftest.py | 1 + tests/components/lidarr/test_config_flow.py | 1 + tests/components/lidarr/test_init.py | 1 + tests/components/lidarr/test_sensor.py | 1 + tests/components/lifx/conftest.py | 1 + tests/components/lifx/test_binary_sensor.py | 1 + tests/components/lifx/test_button.py | 1 + tests/components/lifx/test_config_flow.py | 1 + tests/components/lifx/test_diagnostics.py | 1 + tests/components/lifx/test_init.py | 1 + tests/components/lifx/test_migration.py | 1 + tests/components/lifx/test_select.py | 1 + tests/components/lifx/test_sensor.py | 1 + tests/components/light/common.py | 1 + tests/components/light/test_device_condition.py | 1 + tests/components/light/test_device_trigger.py | 1 + tests/components/light/test_init.py | 1 + tests/components/light/test_intent.py | 1 + tests/components/light/test_recorder.py | 1 + tests/components/light/test_significant_change.py | 1 + tests/components/litejet/conftest.py | 1 + tests/components/litejet/test_config_flow.py | 1 + tests/components/litejet/test_diagnostics.py | 1 + tests/components/litejet/test_init.py | 1 + tests/components/litejet/test_light.py | 1 + tests/components/litejet/test_scene.py | 1 + tests/components/litejet/test_switch.py | 1 + tests/components/litejet/test_trigger.py | 1 + tests/components/litterrobot/common.py | 1 + tests/components/litterrobot/conftest.py | 1 + tests/components/litterrobot/test_binary_sensor.py | 1 + tests/components/litterrobot/test_button.py | 1 + tests/components/litterrobot/test_config_flow.py | 1 + tests/components/litterrobot/test_init.py | 1 + tests/components/litterrobot/test_select.py | 1 + tests/components/litterrobot/test_sensor.py | 1 + tests/components/litterrobot/test_switch.py | 1 + tests/components/litterrobot/test_time.py | 1 + tests/components/litterrobot/test_update.py | 1 + tests/components/litterrobot/test_vacuum.py | 1 + tests/components/livisi/test_config_flow.py | 1 + tests/components/local_calendar/test_config_flow.py | 1 + tests/components/local_calendar/test_diagnostics.py | 1 + tests/components/local_file/test_camera.py | 1 + tests/components/local_ip/test_config_flow.py | 1 + tests/components/local_ip/test_init.py | 1 + tests/components/local_todo/conftest.py | 1 + tests/components/local_todo/test_config_flow.py | 1 + tests/components/locative/test_init.py | 1 + tests/components/lock/test_device_trigger.py | 1 + tests/components/lock/test_init.py | 1 + tests/components/lock/test_significant_change.py | 1 + tests/components/logbook/common.py | 1 + tests/components/logbook/test_models.py | 1 + tests/components/logentries/test_init.py | 1 + tests/components/logger/test_init.py | 1 + tests/components/london_air/test_sensor.py | 1 + tests/components/london_underground/test_sensor.py | 1 + tests/components/lookin/test_config_flow.py | 1 + tests/components/loqed/test_config_flow.py | 1 + tests/components/loqed/test_lock.py | 1 + tests/components/lovelace/test_cast.py | 1 + tests/components/lovelace/test_dashboard.py | 1 + tests/components/lovelace/test_system_health.py | 1 + tests/components/luftdaten/conftest.py | 1 + tests/components/luftdaten/test_config_flow.py | 1 + tests/components/luftdaten/test_init.py | 1 + tests/components/luftdaten/test_sensor.py | 1 + tests/components/lutron/test_config_flow.py | 1 + tests/components/lutron_caseta/test_config_flow.py | 1 + tests/components/lutron_caseta/test_device_trigger.py | 1 + tests/components/lutron_caseta/test_diagnostics.py | 1 + tests/components/lutron_caseta/test_logbook.py | 1 + tests/components/lyric/test_config_flow.py | 1 + tests/components/mailbox/test_init.py | 1 + tests/components/manual/test_alarm_control_panel.py | 1 + tests/components/manual_mqtt/test_alarm_control_panel.py | 1 + tests/components/marytts/test_tts.py | 1 + tests/components/matrix/conftest.py | 1 + tests/components/matrix/test_commands.py | 1 + tests/components/matter/common.py | 1 + tests/components/matter/conftest.py | 1 + tests/components/matter/test_adapter.py | 1 + tests/components/matter/test_api.py | 1 + tests/components/matter/test_binary_sensor.py | 1 + tests/components/matter/test_climate.py | 1 + tests/components/matter/test_config_flow.py | 1 + tests/components/matter/test_cover.py | 1 + tests/components/matter/test_diagnostics.py | 1 + tests/components/matter/test_door_lock.py | 1 + tests/components/matter/test_event.py | 1 + tests/components/matter/test_helpers.py | 1 + tests/components/matter/test_init.py | 1 + tests/components/matter/test_light.py | 1 + tests/components/matter/test_sensor.py | 1 + tests/components/matter/test_switch.py | 1 + tests/components/maxcube/conftest.py | 1 + tests/components/maxcube/test_maxcube_binary_sensor.py | 1 + tests/components/maxcube/test_maxcube_climate.py | 1 + tests/components/meater/test_config_flow.py | 1 + tests/components/medcom_ble/test_config_flow.py | 1 + tests/components/media_extractor/conftest.py | 1 + tests/components/media_player/common.py | 1 + tests/components/media_player/test_browse_media.py | 1 + tests/components/media_player/test_device_trigger.py | 1 + tests/components/media_player/test_init.py | 1 + tests/components/media_player/test_recorder.py | 1 + tests/components/media_source/test_init.py | 1 + tests/components/media_source/test_local_source.py | 1 + tests/components/media_source/test_models.py | 1 + tests/components/melcloud/test_atw_zone_sensor.py | 1 + tests/components/melcloud/test_config_flow.py | 1 + tests/components/melissa/test_init.py | 1 + tests/components/melnor/conftest.py | 1 + tests/components/melnor/test_config_flow.py | 1 + tests/components/melnor/test_number.py | 1 + tests/components/melnor/test_sensor.py | 1 + tests/components/melnor/test_switch.py | 1 + tests/components/melnor/test_time.py | 1 + tests/components/meraki/test_device_tracker.py | 1 + tests/components/met/conftest.py | 1 + tests/components/met/test_config_flow.py | 1 + tests/components/met/test_weather.py | 1 + tests/components/met_eireann/conftest.py | 1 + tests/components/met_eireann/test_config_flow.py | 1 + tests/components/met_eireann/test_init.py | 1 + tests/components/meteo_france/conftest.py | 1 + tests/components/meteo_france/test_config_flow.py | 1 + tests/components/meteoclimatic/conftest.py | 1 + tests/components/meteoclimatic/test_config_flow.py | 1 + tests/components/metoffice/conftest.py | 1 + tests/components/metoffice/test_init.py | 1 + tests/components/mfi/test_sensor.py | 1 + tests/components/microbees/test_config_flow.py | 1 + tests/components/microsoft/test_tts.py | 1 + tests/components/microsoft_face/test_init.py | 1 + tests/components/microsoft_face_detect/test_image_processing.py | 1 + .../components/microsoft_face_identify/test_image_processing.py | 1 + tests/components/mikrotik/test_config_flow.py | 1 + tests/components/mikrotik/test_device_tracker.py | 1 + tests/components/mikrotik/test_init.py | 1 + tests/components/mill/test_config_flow.py | 1 + tests/components/mill/test_init.py | 1 + tests/components/min_max/test_config_flow.py | 1 + tests/components/minecraft_server/const.py | 1 + tests/components/minecraft_server/test_binary_sensor.py | 1 + tests/components/minecraft_server/test_diagnostics.py | 1 + tests/components/minecraft_server/test_init.py | 1 + tests/components/minecraft_server/test_sensor.py | 1 + tests/components/mjpeg/conftest.py | 1 + tests/components/mjpeg/test_init.py | 1 + tests/components/moat/test_config_flow.py | 1 + tests/components/moat/test_sensor.py | 1 + tests/components/mobile_app/conftest.py | 1 + tests/components/mobile_app/test_binary_sensor.py | 1 + tests/components/mobile_app/test_device_action.py | 1 + tests/components/mobile_app/test_device_tracker.py | 1 + tests/components/mobile_app/test_http_api.py | 1 + tests/components/mobile_app/test_init.py | 1 + tests/components/mobile_app/test_logbook.py | 1 + tests/components/mobile_app/test_notify.py | 1 + tests/components/mobile_app/test_sensor.py | 1 + tests/components/mobile_app/test_webhook.py | 1 + tests/components/mochad/conftest.py | 1 + tests/components/modbus/test_cover.py | 1 + tests/components/modbus/test_fan.py | 1 + tests/components/modbus/test_init.py | 1 + tests/components/modbus/test_light.py | 1 + tests/components/modbus/test_switch.py | 1 + tests/components/modem_callerid/test_config_flow.py | 1 + tests/components/modem_callerid/test_init.py | 1 + tests/components/modern_forms/test_binary_sensor.py | 1 + tests/components/modern_forms/test_config_flow.py | 1 + tests/components/modern_forms/test_fan.py | 1 + tests/components/modern_forms/test_init.py | 1 + tests/components/modern_forms/test_light.py | 1 + tests/components/modern_forms/test_sensor.py | 1 + tests/components/modern_forms/test_switch.py | 1 + tests/components/moehlenhoff_alpha2/test_config_flow.py | 1 + tests/components/monoprice/test_config_flow.py | 1 + tests/components/monoprice/test_media_player.py | 1 + tests/components/moon/conftest.py | 1 + tests/components/moon/test_config_flow.py | 1 + tests/components/moon/test_sensor.py | 1 + tests/components/mopeka/test_config_flow.py | 1 + tests/components/mopeka/test_sensor.py | 1 + tests/components/motion_blinds/test_gateway.py | 1 + tests/components/motioneye/test_config_flow.py | 1 + tests/components/motionmount/conftest.py | 1 + tests/components/mqtt/test_camera.py | 1 + tests/components/mqtt/test_common.py | 1 + tests/components/mqtt/test_config_flow.py | 1 + tests/components/mqtt/test_cover.py | 1 + tests/components/mqtt/test_device_tracker.py | 1 + tests/components/mqtt/test_image.py | 1 + tests/components/mqtt/test_lock.py | 1 + tests/components/mqtt/test_select.py | 1 + tests/components/mqtt/test_subscription.py | 1 + tests/components/mqtt/test_tag.py | 1 + tests/components/mqtt/test_text.py | 1 + tests/components/mqtt/test_trigger.py | 1 + tests/components/mqtt/test_vacuum.py | 1 + tests/components/mqtt/test_valve.py | 1 + tests/components/mqtt_json/test_device_tracker.py | 1 + tests/components/mqtt_statestream/test_init.py | 1 + tests/components/mullvad/test_config_flow.py | 1 + tests/components/mutesync/test_config_flow.py | 1 + tests/components/my/test_init.py | 1 + tests/components/mysensors/conftest.py | 1 + tests/components/mysensors/test_binary_sensor.py | 1 + tests/components/mysensors/test_climate.py | 1 + tests/components/mysensors/test_config_flow.py | 1 + tests/components/mysensors/test_cover.py | 1 + tests/components/mysensors/test_device_tracker.py | 1 + tests/components/mysensors/test_gateway.py | 1 + tests/components/mysensors/test_init.py | 1 + tests/components/mysensors/test_light.py | 1 + tests/components/mysensors/test_remote.py | 1 + tests/components/mysensors/test_sensor.py | 1 + tests/components/mysensors/test_switch.py | 1 + tests/components/mysensors/test_text.py | 1 + tests/components/mystrom/test_config_flow.py | 1 + tests/components/mystrom/test_init.py | 1 + tests/components/myuplink/conftest.py | 1 + tests/components/nam/test_button.py | 1 + tests/components/nam/test_config_flow.py | 1 + tests/components/nam/test_init.py | 1 + tests/components/nam/test_sensor.py | 1 + tests/components/namecheapdns/test_init.py | 1 + tests/components/nanoleaf/test_config_flow.py | 1 + tests/components/neato/test_config_flow.py | 1 + tests/components/ness_alarm/test_init.py | 1 + tests/components/nest/conftest.py | 1 + tests/components/nest/test_config_flow.py | 1 + tests/components/nest/test_device_trigger.py | 1 + tests/components/nest/test_diagnostics.py | 1 + tests/components/nest/test_events.py | 1 + tests/components/nest/test_media_source.py | 1 + tests/components/netatmo/common.py | 1 + tests/components/netatmo/conftest.py | 1 + tests/components/netatmo/test_camera.py | 1 + tests/components/netatmo/test_climate.py | 1 + tests/components/netatmo/test_config_flow.py | 1 + tests/components/netatmo/test_cover.py | 1 + tests/components/netatmo/test_diagnostics.py | 1 + tests/components/netatmo/test_fan.py | 1 + tests/components/netatmo/test_init.py | 1 + tests/components/netatmo/test_light.py | 1 + tests/components/netatmo/test_select.py | 1 + tests/components/netatmo/test_sensor.py | 1 + tests/components/netatmo/test_switch.py | 1 + tests/components/netgear/test_config_flow.py | 1 + tests/components/netgear_lte/conftest.py | 1 + tests/components/netgear_lte/test_binary_sensor.py | 1 + tests/components/netgear_lte/test_config_flow.py | 1 + tests/components/netgear_lte/test_init.py | 1 + tests/components/netgear_lte/test_notify.py | 1 + tests/components/netgear_lte/test_sensor.py | 1 + tests/components/netgear_lte/test_services.py | 1 + tests/components/network/test_init.py | 1 + tests/components/nexia/test_binary_sensor.py | 1 + tests/components/nexia/test_climate.py | 1 + tests/components/nexia/test_config_flow.py | 1 + tests/components/nexia/test_diagnostics.py | 1 + tests/components/nexia/test_scene.py | 1 + tests/components/nexia/test_sensor.py | 1 + tests/components/nexia/test_switch.py | 1 + tests/components/nexia/util.py | 1 + tests/components/nextbus/conftest.py | 1 + tests/components/nextbus/test_config_flow.py | 1 + tests/components/nextbus/test_sensor.py | 1 + tests/components/nextbus/test_util.py | 1 + tests/components/nextcloud/test_config_flow.py | 1 + tests/components/nextdns/test_binary_sensor.py | 1 + tests/components/nextdns/test_button.py | 1 + tests/components/nextdns/test_config_flow.py | 1 + tests/components/nextdns/test_init.py | 1 + tests/components/nextdns/test_sensor.py | 1 + tests/components/nextdns/test_switch.py | 1 + tests/components/nfandroidtv/test_config_flow.py | 1 + tests/components/nibe_heatpump/conftest.py | 1 + tests/components/nibe_heatpump/test_button.py | 1 + tests/components/nibe_heatpump/test_climate.py | 1 + tests/components/nibe_heatpump/test_config_flow.py | 1 + tests/components/nibe_heatpump/test_number.py | 1 + tests/components/nightscout/test_config_flow.py | 1 + tests/components/nightscout/test_init.py | 1 + tests/components/nightscout/test_sensor.py | 1 + tests/components/nina/test_binary_sensor.py | 1 + tests/components/nina/test_config_flow.py | 1 + tests/components/nina/test_init.py | 1 + tests/components/nmap_tracker/test_config_flow.py | 1 + tests/components/no_ip/test_init.py | 1 + tests/components/nobo_hub/test_config_flow.py | 1 + tests/components/notify/common.py | 1 + tests/components/notify/test_persistent_notification.py | 1 + tests/components/notify_events/test_init.py | 1 + tests/components/notify_events/test_notify.py | 1 + tests/components/notion/conftest.py | 1 + tests/components/notion/test_config_flow.py | 1 + tests/components/notion/test_diagnostics.py | 1 + tests/components/nsw_fuel_station/test_sensor.py | 1 + tests/components/nuheat/mocks.py | 1 + tests/components/nuheat/test_climate.py | 1 + tests/components/nuheat/test_config_flow.py | 1 + tests/components/nuheat/test_init.py | 1 + tests/components/nuki/test_config_flow.py | 1 + tests/components/numato/numato_mock.py | 1 + tests/components/numato/test_binary_sensor.py | 1 + tests/components/numato/test_init.py | 1 + tests/components/numato/test_sensor.py | 1 + tests/components/numato/test_switch.py | 1 + tests/components/number/test_init.py | 1 + tests/components/number/test_recorder.py | 1 + tests/components/number/test_websocket_api.py | 1 + tests/components/nut/test_config_flow.py | 1 + tests/components/nut/test_device_action.py | 1 + tests/components/nut/test_init.py | 1 + tests/components/nut/test_sensor.py | 1 + tests/components/nws/conftest.py | 1 + tests/components/nws/const.py | 1 + tests/components/nws/test_config_flow.py | 1 + tests/components/nws/test_init.py | 1 + tests/components/nws/test_weather.py | 1 + tests/components/nx584/test_binary_sensor.py | 1 + tests/components/nzbget/conftest.py | 1 + tests/components/nzbget/test_config_flow.py | 1 + tests/components/nzbget/test_init.py | 1 + tests/components/nzbget/test_sensor.py | 1 + tests/components/nzbget/test_switch.py | 1 + tests/components/octoprint/test_binary_sensor.py | 1 + tests/components/octoprint/test_button.py | 1 + tests/components/octoprint/test_camera.py | 1 + tests/components/octoprint/test_config_flow.py | 1 + tests/components/octoprint/test_sensor.py | 1 + tests/components/octoprint/test_servics.py | 1 + tests/components/omnilogic/test_config_flow.py | 1 + tests/components/onboarding/test_init.py | 1 + tests/components/oncue/test_binary_sensor.py | 1 + tests/components/oncue/test_config_flow.py | 1 + tests/components/oncue/test_init.py | 1 + tests/components/oncue/test_sensor.py | 1 + tests/components/ondilo_ico/test_config_flow.py | 1 + tests/components/onewire/conftest.py | 1 + tests/components/onewire/const.py | 1 + tests/components/onewire/test_binary_sensor.py | 1 + tests/components/onewire/test_config_flow.py | 1 + tests/components/onewire/test_diagnostics.py | 1 + tests/components/onewire/test_init.py | 1 + tests/components/onewire/test_sensor.py | 1 + tests/components/onewire/test_switch.py | 1 + tests/components/onvif/test_button.py | 1 + tests/components/onvif/test_diagnostics.py | 1 + tests/components/onvif/test_switch.py | 1 + tests/components/open_meteo/conftest.py | 1 + tests/components/open_meteo/test_init.py | 1 + tests/components/openai_conversation/conftest.py | 1 + tests/components/openai_conversation/test_config_flow.py | 1 + tests/components/openai_conversation/test_init.py | 1 + tests/components/openalpr_cloud/test_image_processing.py | 1 + tests/components/openerz/test_sensor.py | 1 + tests/components/openexchangerates/conftest.py | 1 + tests/components/opengarage/conftest.py | 1 + tests/components/opengarage/test_button.py | 1 + tests/components/opengarage/test_config_flow.py | 1 + tests/components/openhome/test_update.py | 1 + tests/components/opensky/conftest.py | 1 + tests/components/opensky/test_config_flow.py | 1 + tests/components/opensky/test_init.py | 1 + tests/components/opensky/test_sensor.py | 1 + tests/components/opentherm_gw/test_config_flow.py | 1 + tests/components/opentherm_gw/test_init.py | 1 + tests/components/openuv/conftest.py | 1 + tests/components/openuv/test_config_flow.py | 1 + tests/components/openuv/test_diagnostics.py | 1 + tests/components/openweathermap/test_config_flow.py | 1 + tests/components/opnsense/test_device_tracker.py | 1 + tests/components/opower/test_config_flow.py | 1 + tests/components/oralb/test_config_flow.py | 1 + tests/components/osoenergy/test_config_flow.py | 1 + tests/components/otbr/conftest.py | 1 + tests/components/otbr/test_silabs_multiprotocol.py | 1 + tests/components/otbr/test_util.py | 1 + tests/components/otbr/test_websocket_api.py | 1 + tests/components/ourgroceries/conftest.py | 1 + tests/components/ourgroceries/test_config_flow.py | 1 + tests/components/ourgroceries/test_init.py | 1 + tests/components/ourgroceries/test_todo.py | 1 + tests/components/overkiz/conftest.py | 1 + tests/components/overkiz/test_config_flow.py | 1 + tests/components/overkiz/test_diagnostics.py | 1 + tests/components/overkiz/test_init.py | 1 + tests/components/ovo_energy/test_config_flow.py | 1 + tests/components/owntracks/test_config_flow.py | 1 + tests/components/owntracks/test_helper.py | 1 + tests/components/p1_monitor/test_config_flow.py | 1 + tests/components/p1_monitor/test_init.py | 1 + tests/components/p1_monitor/test_sensor.py | 1 + tests/components/panasonic_viera/test_config_flow.py | 1 + tests/components/panasonic_viera/test_init.py | 1 + tests/components/panasonic_viera/test_remote.py | 1 + tests/components/panel_custom/test_init.py | 1 + tests/components/peco/test_config_flow.py | 1 + tests/components/peco/test_init.py | 1 + tests/components/peco/test_sensor.py | 1 + tests/components/pegel_online/test_config_flow.py | 1 + tests/components/pegel_online/test_init.py | 1 + tests/components/pegel_online/test_sensor.py | 1 + tests/components/permobil/conftest.py | 1 + tests/components/permobil/test_config_flow.py | 1 + tests/components/persistent_notification/test_trigger.py | 1 + tests/components/person/test_init.py | 1 + tests/components/person/test_recorder.py | 1 + tests/components/person/test_significant_change.py | 1 + tests/components/philips_js/conftest.py | 1 + tests/components/philips_js/test_config_flow.py | 1 + tests/components/pi_hole/test_config_flow.py | 1 + tests/components/picnic/conftest.py | 1 + tests/components/picnic/test_config_flow.py | 1 + tests/components/picnic/test_services.py | 1 + tests/components/pilight/test_init.py | 1 + tests/components/ping/conftest.py | 1 + tests/components/ping/const.py | 1 + tests/components/ping/test_binary_sensor.py | 1 + tests/components/ping/test_config_flow.py | 1 + tests/components/ping/test_device_tracker.py | 1 + tests/components/pjlink/test_media_player.py | 1 + tests/components/plaato/test_config_flow.py | 1 + tests/components/plant/test_init.py | 1 + tests/components/plex/conftest.py | 1 + tests/components/plex/const.py | 1 + tests/components/plex/helpers.py | 1 + tests/components/plex/test_browse_media.py | 1 + tests/components/plex/test_button.py | 1 + tests/components/plex/test_media_players.py | 1 + tests/components/plex/test_media_search.py | 1 + tests/components/plex/test_playback.py | 1 + tests/components/plex/test_sensor.py | 1 + tests/components/plex/test_services.py | 1 + tests/components/plugwise/conftest.py | 1 + tests/components/plugwise/test_config_flow.py | 1 + tests/components/plugwise/test_diagnostics.py | 1 + tests/components/plugwise/test_init.py | 1 + tests/components/plugwise/test_switch.py | 1 + tests/components/plum_lightpad/test_config_flow.py | 1 + tests/components/plum_lightpad/test_init.py | 1 + tests/components/point/test_config_flow.py | 1 + tests/components/poolsense/test_config_flow.py | 1 + tests/components/powerwall/test_binary_sensor.py | 1 + tests/components/powerwall/test_sensor.py | 1 + tests/components/powerwall/test_switch.py | 1 + tests/components/private_ble_device/test_config_flow.py | 1 + tests/components/profiler/test_config_flow.py | 1 + tests/components/profiler/test_init.py | 1 + tests/components/progettihwsw/test_config_flow.py | 1 + tests/components/prometheus/test_init.py | 1 + tests/components/prosegur/conftest.py | 1 + tests/components/prosegur/test_alarm_control_panel.py | 1 + tests/components/prosegur/test_config_flow.py | 1 + tests/components/prosegur/test_diagnostics.py | 1 + tests/components/prosegur/test_init.py | 1 + tests/components/proximity/test_config_flow.py | 1 + tests/components/proximity/test_diagnostics.py | 1 + tests/components/prusalink/conftest.py | 1 + tests/components/prusalink/test_button.py | 1 + tests/components/prusalink/test_camera.py | 1 + tests/components/prusalink/test_config_flow.py | 1 + tests/components/prusalink/test_init.py | 1 + tests/components/ps4/conftest.py | 1 + tests/components/ps4/test_config_flow.py | 1 + tests/components/ps4/test_init.py | 1 + tests/components/ps4/test_media_player.py | 1 + tests/components/pure_energie/conftest.py | 1 + tests/components/pure_energie/test_config_flow.py | 1 + tests/components/pure_energie/test_init.py | 1 + tests/components/purpleair/conftest.py | 1 + tests/components/purpleair/test_config_flow.py | 1 + tests/components/purpleair/test_diagnostics.py | 1 + tests/components/push/test_camera.py | 1 + tests/components/pushbullet/test_config_flow.py | 1 + tests/components/pushbullet/test_init.py | 1 + tests/components/pushbullet/test_notify.py | 1 + tests/components/pushover/test_config_flow.py | 1 + tests/components/pushover/test_init.py | 1 + tests/components/pvoutput/conftest.py | 1 + tests/components/pvoutput/test_init.py | 1 + tests/components/pvoutput/test_sensor.py | 1 + tests/components/pvpc_hourly_pricing/conftest.py | 1 + tests/components/pvpc_hourly_pricing/test_config_flow.py | 1 + 532 files changed, 532 insertions(+) diff --git a/tests/components/lacrosse_view/conftest.py b/tests/components/lacrosse_view/conftest.py index 1ea3144e4c2..8edee952bf0 100644 --- a/tests/components/lacrosse_view/conftest.py +++ b/tests/components/lacrosse_view/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for LaCrosse View tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/lacrosse_view/test_config_flow.py b/tests/components/lacrosse_view/test_config_flow.py index 075aa7a3767..b80dab1a7ec 100644 --- a/tests/components/lacrosse_view/test_config_flow.py +++ b/tests/components/lacrosse_view/test_config_flow.py @@ -1,4 +1,5 @@ """Test the LaCrosse View config flow.""" + from unittest.mock import AsyncMock, patch from lacrosse_view import Location, LoginError diff --git a/tests/components/lacrosse_view/test_diagnostics.py b/tests/components/lacrosse_view/test_diagnostics.py index 29d6f7cacbe..9fef7bf5955 100644 --- a/tests/components/lacrosse_view/test_diagnostics.py +++ b/tests/components/lacrosse_view/test_diagnostics.py @@ -1,4 +1,5 @@ """Test diagnostics of LaCrosse View.""" + from unittest.mock import patch from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/lacrosse_view/test_init.py b/tests/components/lacrosse_view/test_init.py index 2b3f5927bd2..7036371d323 100644 --- a/tests/components/lacrosse_view/test_init.py +++ b/tests/components/lacrosse_view/test_init.py @@ -1,4 +1,5 @@ """Test the LaCrosse View initialization.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/lacrosse_view/test_sensor.py b/tests/components/lacrosse_view/test_sensor.py index 8fc028e2da1..f9e52850685 100644 --- a/tests/components/lacrosse_view/test_sensor.py +++ b/tests/components/lacrosse_view/test_sensor.py @@ -1,4 +1,5 @@ """Test the LaCrosse View sensors.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/lamarzocco/test_binary_sensor.py b/tests/components/lamarzocco/test_binary_sensor.py index e475e663768..bb1e16f09a5 100644 --- a/tests/components/lamarzocco/test_binary_sensor.py +++ b/tests/components/lamarzocco/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for La Marzocco binary sensors.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/lamarzocco/test_config_flow.py b/tests/components/lamarzocco/test_config_flow.py index 803055ef8ab..ffdf43df3ae 100644 --- a/tests/components/lamarzocco/test_config_flow.py +++ b/tests/components/lamarzocco/test_config_flow.py @@ -1,4 +1,5 @@ """Test the La Marzocco config flow.""" + from unittest.mock import MagicMock from lmcloud.exceptions import AuthFail, RequestNotSuccessful diff --git a/tests/components/lamarzocco/test_diagnostics.py b/tests/components/lamarzocco/test_diagnostics.py index a42b15dec3c..762b33cc696 100644 --- a/tests/components/lamarzocco/test_diagnostics.py +++ b/tests/components/lamarzocco/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the La Marzocco integration.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/lamarzocco/test_init.py b/tests/components/lamarzocco/test_init.py index b89ff23b771..5647129b5a5 100644 --- a/tests/components/lamarzocco/test_init.py +++ b/tests/components/lamarzocco/test_init.py @@ -1,4 +1,5 @@ """Test initialization of lamarzocco.""" + from unittest.mock import MagicMock from lmcloud.exceptions import AuthFail, RequestNotSuccessful diff --git a/tests/components/lamarzocco/test_sensor.py b/tests/components/lamarzocco/test_sensor.py index 3333fed1464..b5f551309b6 100644 --- a/tests/components/lamarzocco/test_sensor.py +++ b/tests/components/lamarzocco/test_sensor.py @@ -1,4 +1,5 @@ """Tests for La Marzocco sensors.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/lamarzocco/test_switch.py b/tests/components/lamarzocco/test_switch.py index 70024e3e340..db4bc5541b9 100644 --- a/tests/components/lamarzocco/test_switch.py +++ b/tests/components/lamarzocco/test_switch.py @@ -1,4 +1,5 @@ """Tests for La Marzocco switches.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/lametric/conftest.py b/tests/components/lametric/conftest.py index b3a9f2d8665..b321041461a 100644 --- a/tests/components/lametric/conftest.py +++ b/tests/components/lametric/conftest.py @@ -1,4 +1,5 @@ """Fixtures for LaMetric integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/lametric/test_button.py b/tests/components/lametric/test_button.py index ce8639db717..f1ebbc65e44 100644 --- a/tests/components/lametric/test_button.py +++ b/tests/components/lametric/test_button.py @@ -1,4 +1,5 @@ """Tests for the LaMetric button platform.""" + from unittest.mock import MagicMock from demetriek import LaMetricConnectionError, LaMetricError diff --git a/tests/components/lametric/test_config_flow.py b/tests/components/lametric/test_config_flow.py index 9b00c1e89aa..e5fa1229e07 100644 --- a/tests/components/lametric/test_config_flow.py +++ b/tests/components/lametric/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the LaMetric config flow.""" + from http import HTTPStatus from unittest.mock import MagicMock diff --git a/tests/components/lametric/test_diagnostics.py b/tests/components/lametric/test_diagnostics.py index 333985f71a0..e1fcbafcb73 100644 --- a/tests/components/lametric/test_diagnostics.py +++ b/tests/components/lametric/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the LaMetric integration.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/lametric/test_helpers.py b/tests/components/lametric/test_helpers.py index a1b824086d2..f4341289219 100644 --- a/tests/components/lametric/test_helpers.py +++ b/tests/components/lametric/test_helpers.py @@ -1,4 +1,5 @@ """Tests for the LaMetric helpers.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/lametric/test_init.py b/tests/components/lametric/test_init.py index eee09b3acce..7352721e992 100644 --- a/tests/components/lametric/test_init.py +++ b/tests/components/lametric/test_init.py @@ -1,4 +1,5 @@ """Tests for the LaMetric integration.""" + from unittest.mock import MagicMock from demetriek import ( diff --git a/tests/components/lametric/test_notify.py b/tests/components/lametric/test_notify.py index a94b8f2ce53..a46d97f8f81 100644 --- a/tests/components/lametric/test_notify.py +++ b/tests/components/lametric/test_notify.py @@ -1,4 +1,5 @@ """Tests for the LaMetric notify platform.""" + from unittest.mock import MagicMock from demetriek import ( diff --git a/tests/components/lametric/test_number.py b/tests/components/lametric/test_number.py index 7e033722c3d..5a1b617c24b 100644 --- a/tests/components/lametric/test_number.py +++ b/tests/components/lametric/test_number.py @@ -1,4 +1,5 @@ """Tests for the LaMetric number platform.""" + from unittest.mock import MagicMock from demetriek import LaMetricConnectionError, LaMetricError diff --git a/tests/components/lametric/test_select.py b/tests/components/lametric/test_select.py index 4215ffe2dea..7e528e26905 100644 --- a/tests/components/lametric/test_select.py +++ b/tests/components/lametric/test_select.py @@ -1,4 +1,5 @@ """Tests for the LaMetric select platform.""" + from unittest.mock import MagicMock from demetriek import BrightnessMode, LaMetricConnectionError, LaMetricError diff --git a/tests/components/lametric/test_services.py b/tests/components/lametric/test_services.py index 9a1258a82bb..d3fbd0a18e0 100644 --- a/tests/components/lametric/test_services.py +++ b/tests/components/lametric/test_services.py @@ -1,4 +1,5 @@ """Tests for the LaMetric services.""" + from unittest.mock import MagicMock from demetriek import ( diff --git a/tests/components/lametric/test_switch.py b/tests/components/lametric/test_switch.py index bd4b7856a22..7ca5c800121 100644 --- a/tests/components/lametric/test_switch.py +++ b/tests/components/lametric/test_switch.py @@ -1,4 +1,5 @@ """Tests for the LaMetric switch platform.""" + from unittest.mock import MagicMock from demetriek import LaMetricConnectionError, LaMetricError diff --git a/tests/components/landisgyr_heat_meter/conftest.py b/tests/components/landisgyr_heat_meter/conftest.py index 711fa2110f4..df7e4a44ce9 100644 --- a/tests/components/landisgyr_heat_meter/conftest.py +++ b/tests/components/landisgyr_heat_meter/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for Landis + Gyr Heat Meter tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/landisgyr_heat_meter/test_config_flow.py b/tests/components/landisgyr_heat_meter/test_config_flow.py index 19338d8d576..d53a81a7edf 100644 --- a/tests/components/landisgyr_heat_meter/test_config_flow.py +++ b/tests/components/landisgyr_heat_meter/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Landis + Gyr Heat Meter config flow.""" + from dataclasses import dataclass from unittest.mock import patch diff --git a/tests/components/landisgyr_heat_meter/test_init.py b/tests/components/landisgyr_heat_meter/test_init.py index f8615aa77af..c9768ec681f 100644 --- a/tests/components/landisgyr_heat_meter/test_init.py +++ b/tests/components/landisgyr_heat_meter/test_init.py @@ -1,4 +1,5 @@ """Test the Landis + Gyr Heat Meter init.""" + from unittest.mock import patch from homeassistant.components.landisgyr_heat_meter.const import ( diff --git a/tests/components/lastfm/conftest.py b/tests/components/lastfm/conftest.py index c7cada9ba0a..0575df2bbca 100644 --- a/tests/components/lastfm/conftest.py +++ b/tests/components/lastfm/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the LastFM integration.""" + from collections.abc import Awaitable, Callable from unittest.mock import patch diff --git a/tests/components/lastfm/test_config_flow.py b/tests/components/lastfm/test_config_flow.py index 8a2c556a8d0..0b5906c5268 100644 --- a/tests/components/lastfm/test_config_flow.py +++ b/tests/components/lastfm/test_config_flow.py @@ -1,4 +1,5 @@ """Test Lastfm config flow.""" + from unittest.mock import patch from pylast import WSError diff --git a/tests/components/lastfm/test_init.py b/tests/components/lastfm/test_init.py index 2f126af11a3..e7b367fd57f 100644 --- a/tests/components/lastfm/test_init.py +++ b/tests/components/lastfm/test_init.py @@ -1,4 +1,5 @@ """Test LastFM component setup process.""" + from __future__ import annotations from homeassistant.components.lastfm.const import DOMAIN diff --git a/tests/components/launch_library/test_config_flow.py b/tests/components/launch_library/test_config_flow.py index 8a8a2a94937..80a634318b9 100644 --- a/tests/components/launch_library/test_config_flow.py +++ b/tests/components/launch_library/test_config_flow.py @@ -1,4 +1,5 @@ """Test launch_library config flow.""" + from unittest.mock import patch from homeassistant import data_entry_flow diff --git a/tests/components/lawn_mower/test_init.py b/tests/components/lawn_mower/test_init.py index 39d594e1e17..1cf6c7f4b24 100644 --- a/tests/components/lawn_mower/test_init.py +++ b/tests/components/lawn_mower/test_init.py @@ -1,4 +1,5 @@ """The tests for the lawn mower integration.""" + from collections.abc import Generator from unittest.mock import MagicMock diff --git a/tests/components/lcn/test_binary_sensor.py b/tests/components/lcn/test_binary_sensor.py index c92a45d7cc9..9ba04ac94c7 100644 --- a/tests/components/lcn/test_binary_sensor.py +++ b/tests/components/lcn/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test for the LCN binary sensor platform.""" + from pypck.inputs import ModStatusBinSensors, ModStatusKeyLocks, ModStatusVar from pypck.lcn_addr import LcnAddr from pypck.lcn_defs import Var, VarValue diff --git a/tests/components/lcn/test_config_flow.py b/tests/components/lcn/test_config_flow.py index 5ceb82ed4a1..6580fb35d02 100644 --- a/tests/components/lcn/test_config_flow.py +++ b/tests/components/lcn/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the LCN config flow.""" + from unittest.mock import patch from pypck.connection import PchkAuthenticationError, PchkLicenseError diff --git a/tests/components/lcn/test_cover.py b/tests/components/lcn/test_cover.py index 4705591e1d3..f50921c08a1 100644 --- a/tests/components/lcn/test_cover.py +++ b/tests/components/lcn/test_cover.py @@ -1,4 +1,5 @@ """Test for the LCN cover platform.""" + from unittest.mock import patch from pypck.inputs import ModStatusOutput, ModStatusRelays diff --git a/tests/components/lcn/test_device_trigger.py b/tests/components/lcn/test_device_trigger.py index 59cabb309b0..4ef43e826f3 100644 --- a/tests/components/lcn/test_device_trigger.py +++ b/tests/components/lcn/test_device_trigger.py @@ -1,4 +1,5 @@ """Tests for LCN device triggers.""" + from pypck.inputs import ModSendKeysHost, ModStatusAccessControl from pypck.lcn_addr import LcnAddr from pypck.lcn_defs import AccessControlPeriphery, KeyAction, SendKeyCommand diff --git a/tests/components/lcn/test_events.py b/tests/components/lcn/test_events.py index 4e20e202ffc..eb62f820103 100644 --- a/tests/components/lcn/test_events.py +++ b/tests/components/lcn/test_events.py @@ -1,4 +1,5 @@ """Tests for LCN events.""" + from pypck.inputs import Input, ModSendKeysHost, ModStatusAccessControl from pypck.lcn_addr import LcnAddr from pypck.lcn_defs import AccessControlPeriphery, KeyAction, SendKeyCommand diff --git a/tests/components/lcn/test_init.py b/tests/components/lcn/test_init.py index fb1d09d91d6..cfa518a9367 100644 --- a/tests/components/lcn/test_init.py +++ b/tests/components/lcn/test_init.py @@ -1,4 +1,5 @@ """Test init of LCN integration.""" + from unittest.mock import patch from pypck.connection import ( diff --git a/tests/components/lcn/test_light.py b/tests/components/lcn/test_light.py index 7f23c1e6214..b91f3d5b17c 100644 --- a/tests/components/lcn/test_light.py +++ b/tests/components/lcn/test_light.py @@ -1,4 +1,5 @@ """Test for the LCN light platform.""" + from unittest.mock import patch from pypck.inputs import ModStatusOutput, ModStatusRelays diff --git a/tests/components/lcn/test_sensor.py b/tests/components/lcn/test_sensor.py index b46de397255..cdcd5a195a3 100644 --- a/tests/components/lcn/test_sensor.py +++ b/tests/components/lcn/test_sensor.py @@ -1,4 +1,5 @@ """Test for the LCN sensor platform.""" + from pypck.inputs import ModStatusLedsAndLogicOps, ModStatusVar from pypck.lcn_addr import LcnAddr from pypck.lcn_defs import LedStatus, LogicOpStatus, Var, VarValue diff --git a/tests/components/lcn/test_switch.py b/tests/components/lcn/test_switch.py index a83d45c0889..f24828c5fcb 100644 --- a/tests/components/lcn/test_switch.py +++ b/tests/components/lcn/test_switch.py @@ -1,4 +1,5 @@ """Test for the LCN switch platform.""" + from unittest.mock import patch from pypck.inputs import ModStatusOutput, ModStatusRelays diff --git a/tests/components/ld2410_ble/test_config_flow.py b/tests/components/ld2410_ble/test_config_flow.py index f34ddbfb841..1c87b72330d 100644 --- a/tests/components/ld2410_ble/test_config_flow.py +++ b/tests/components/ld2410_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the LD2410 BLE Bluetooth config flow.""" + from unittest.mock import patch from bleak import BleakError diff --git a/tests/components/leaone/test_config_flow.py b/tests/components/leaone/test_config_flow.py index b7e4abdcf6b..edacc3975c4 100644 --- a/tests/components/leaone/test_config_flow.py +++ b/tests/components/leaone/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Leaone config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/led_ble/test_config_flow.py b/tests/components/led_ble/test_config_flow.py index 947dc304bab..d0274e9a6dc 100644 --- a/tests/components/led_ble/test_config_flow.py +++ b/tests/components/led_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the LED BLE Bluetooth config flow.""" + from unittest.mock import patch from bleak import BleakError diff --git a/tests/components/lg_soundbar/test_config_flow.py b/tests/components/lg_soundbar/test_config_flow.py index e4a56fd98ff..e41466f3204 100644 --- a/tests/components/lg_soundbar/test_config_flow.py +++ b/tests/components/lg_soundbar/test_config_flow.py @@ -1,4 +1,5 @@ """Test the lg_soundbar config flow.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/lidarr/conftest.py b/tests/components/lidarr/conftest.py index 308de36954e..5aabc0a822b 100644 --- a/tests/components/lidarr/conftest.py +++ b/tests/components/lidarr/conftest.py @@ -1,4 +1,5 @@ """Configure pytest for Lidarr tests.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Generator diff --git a/tests/components/lidarr/test_config_flow.py b/tests/components/lidarr/test_config_flow.py index 89bb6614739..01fa05ebb18 100644 --- a/tests/components/lidarr/test_config_flow.py +++ b/tests/components/lidarr/test_config_flow.py @@ -1,4 +1,5 @@ """Test Lidarr config flow.""" + from homeassistant.components.lidarr.const import DEFAULT_NAME, DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_API_KEY, CONF_SOURCE diff --git a/tests/components/lidarr/test_init.py b/tests/components/lidarr/test_init.py index ce3a8536b2f..48c5e3ff9a6 100644 --- a/tests/components/lidarr/test_init.py +++ b/tests/components/lidarr/test_init.py @@ -1,4 +1,5 @@ """Test Lidarr integration.""" + from homeassistant.components.lidarr.const import DEFAULT_NAME, DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/lidarr/test_sensor.py b/tests/components/lidarr/test_sensor.py index 7dec62f1c47..3b3f661ce23 100644 --- a/tests/components/lidarr/test_sensor.py +++ b/tests/components/lidarr/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Lidarr sensor platform.""" + from homeassistant.components.sensor import CONF_STATE_CLASS, SensorStateClass from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT from homeassistant.core import HomeAssistant diff --git a/tests/components/lifx/conftest.py b/tests/components/lifx/conftest.py index 592dd07080f..50296c978f0 100644 --- a/tests/components/lifx/conftest.py +++ b/tests/components/lifx/conftest.py @@ -1,4 +1,5 @@ """Tests for the lifx integration.""" + from unittest.mock import AsyncMock, MagicMock, patch import pytest diff --git a/tests/components/lifx/test_binary_sensor.py b/tests/components/lifx/test_binary_sensor.py index 9fa065f3632..a16bf1173b0 100644 --- a/tests/components/lifx/test_binary_sensor.py +++ b/tests/components/lifx/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the lifx binary sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/lifx/test_button.py b/tests/components/lifx/test_button.py index 1fd4da4531e..4b0a33e8d0c 100644 --- a/tests/components/lifx/test_button.py +++ b/tests/components/lifx/test_button.py @@ -1,4 +1,5 @@ """Tests for button platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/lifx/test_config_flow.py b/tests/components/lifx/test_config_flow.py index a934c0ce831..8381a07cb7f 100644 --- a/tests/components/lifx/test_config_flow.py +++ b/tests/components/lifx/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the lifx integration config flow.""" + from ipaddress import ip_address import socket from unittest.mock import patch diff --git a/tests/components/lifx/test_diagnostics.py b/tests/components/lifx/test_diagnostics.py index a72695502a4..b0fa2bf4d6a 100644 --- a/tests/components/lifx/test_diagnostics.py +++ b/tests/components/lifx/test_diagnostics.py @@ -1,4 +1,5 @@ """Test LIFX diagnostics.""" + from homeassistant.components import lifx from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant diff --git a/tests/components/lifx/test_init.py b/tests/components/lifx/test_init.py index 3f16cc44f41..31a558da052 100644 --- a/tests/components/lifx/test_init.py +++ b/tests/components/lifx/test_init.py @@ -1,4 +1,5 @@ """Tests for the lifx component.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/lifx/test_migration.py b/tests/components/lifx/test_migration.py index 8aafcce670b..6f68f9e798e 100644 --- a/tests/components/lifx/test_migration.py +++ b/tests/components/lifx/test_migration.py @@ -1,4 +1,5 @@ """Tests the lifx migration.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/lifx/test_select.py b/tests/components/lifx/test_select.py index 529925be726..9cc361fc1f4 100644 --- a/tests/components/lifx/test_select.py +++ b/tests/components/lifx/test_select.py @@ -1,4 +1,5 @@ """Tests for the lifx integration select entity.""" + from datetime import timedelta from homeassistant.components import lifx diff --git a/tests/components/lifx/test_sensor.py b/tests/components/lifx/test_sensor.py index e27bc0de3a8..91000350f89 100644 --- a/tests/components/lifx/test_sensor.py +++ b/tests/components/lifx/test_sensor.py @@ -1,4 +1,5 @@ """Test the LIFX sensor platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/light/common.py b/tests/components/light/common.py index 4f83ffacbdc..f76f1d4146d 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 000784ce63c..5f47829f949 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -1,4 +1,5 @@ """The test for light device automation.""" + from datetime import timedelta from freezegun import freeze_time diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index 5ee6752640e..415a70a0661 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -1,4 +1,5 @@ """The test for light device automation.""" + from datetime import timedelta import pytest diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index b2292f59bb1..2c6c84cc35c 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1,4 +1,5 @@ """The tests for the Light component.""" + from unittest.mock import MagicMock, mock_open, patch import pytest diff --git a/tests/components/light/test_intent.py b/tests/components/light/test_intent.py index 4fccc298192..b21b9367bba 100644 --- a/tests/components/light/test_intent.py +++ b/tests/components/light/test_intent.py @@ -1,4 +1,5 @@ """Tests for the light intents.""" + from homeassistant.components import light from homeassistant.components.light import ATTR_SUPPORTED_COLOR_MODES, ColorMode, intent from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON diff --git a/tests/components/light/test_recorder.py b/tests/components/light/test_recorder.py index 1376ee53649..49c9a567856 100644 --- a/tests/components/light/test_recorder.py +++ b/tests/components/light/test_recorder.py @@ -1,4 +1,5 @@ """The tests for light recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/light/test_significant_change.py b/tests/components/light/test_significant_change.py index 6bececc0244..87a60b58325 100644 --- a/tests/components/light/test_significant_change.py +++ b/tests/components/light/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Light significant change platform.""" + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, diff --git a/tests/components/litejet/conftest.py b/tests/components/litejet/conftest.py index 2c631265c30..41517acf1e9 100644 --- a/tests/components/litejet/conftest.py +++ b/tests/components/litejet/conftest.py @@ -1,4 +1,5 @@ """Fixtures for LiteJet testing.""" + from datetime import timedelta from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/litejet/test_config_flow.py b/tests/components/litejet/test_config_flow.py index b490643f622..b92aa59c9ce 100644 --- a/tests/components/litejet/test_config_flow.py +++ b/tests/components/litejet/test_config_flow.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from unittest.mock import patch from serial import SerialException diff --git a/tests/components/litejet/test_diagnostics.py b/tests/components/litejet/test_diagnostics.py index a2c8bc72476..b20d19e8f16 100644 --- a/tests/components/litejet/test_diagnostics.py +++ b/tests/components/litejet/test_diagnostics.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from homeassistant.core import HomeAssistant from . import async_init_integration diff --git a/tests/components/litejet/test_init.py b/tests/components/litejet/test_init.py index c6f0d5c5b02..d1be342d771 100644 --- a/tests/components/litejet/test_init.py +++ b/tests/components/litejet/test_init.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from homeassistant.components import litejet from homeassistant.components.litejet.const import DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/litejet/test_light.py b/tests/components/litejet/test_light.py index 32f121a88a3..bde75bd1b32 100644 --- a/tests/components/litejet/test_light.py +++ b/tests/components/litejet/test_light.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from homeassistant.components import light from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_TRANSITION from homeassistant.components.litejet.const import CONF_DEFAULT_TRANSITION diff --git a/tests/components/litejet/test_scene.py b/tests/components/litejet/test_scene.py index 76c1556f66d..16475e2fe31 100644 --- a/tests/components/litejet/test_scene.py +++ b/tests/components/litejet/test_scene.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from homeassistant.components import scene from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/litejet/test_switch.py b/tests/components/litejet/test_switch.py index 472ccc9491a..59b2ecf66e6 100644 --- a/tests/components/litejet/test_switch.py +++ b/tests/components/litejet/test_switch.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from homeassistant.components import switch from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/litejet/test_trigger.py b/tests/components/litejet/test_trigger.py index e3d7caad65e..b9379efdad4 100644 --- a/tests/components/litejet/test_trigger.py +++ b/tests/components/litejet/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the litejet component.""" + from datetime import timedelta import logging from unittest import mock diff --git a/tests/components/litterrobot/common.py b/tests/components/litterrobot/common.py index 5bf6fb7cce6..fe6202edc47 100644 --- a/tests/components/litterrobot/common.py +++ b/tests/components/litterrobot/common.py @@ -1,4 +1,5 @@ """Common utils for Litter-Robot tests.""" + from homeassistant.components.litterrobot import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index ce80471797d..d8d6ecd171d 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -1,4 +1,5 @@ """Configure pytest for Litter-Robot tests.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/litterrobot/test_binary_sensor.py b/tests/components/litterrobot/test_binary_sensor.py index c6cfbff907e..c72f747db88 100644 --- a/tests/components/litterrobot/test_binary_sensor.py +++ b/tests/components/litterrobot/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Litter-Robot binary sensor entity.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/litterrobot/test_button.py b/tests/components/litterrobot/test_button.py index 17efd21c76e..e9ef65c01a4 100644 --- a/tests/components/litterrobot/test_button.py +++ b/tests/components/litterrobot/test_button.py @@ -1,4 +1,5 @@ """Test the Litter-Robot button entity.""" + from unittest.mock import MagicMock from freezegun import freeze_time diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py index fcfb373a7b0..e2c5290a946 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Litter-Robot config flow.""" + from unittest.mock import patch from pylitterbot import Account diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py index 25c47ee4945..ca68ea0d843 100644 --- a/tests/components/litterrobot/test_init.py +++ b/tests/components/litterrobot/test_init.py @@ -1,4 +1,5 @@ """Test Litter-Robot setup process.""" + from unittest.mock import MagicMock, patch from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException diff --git a/tests/components/litterrobot/test_select.py b/tests/components/litterrobot/test_select.py index b35fdf5c917..76bba67f932 100644 --- a/tests/components/litterrobot/test_select.py +++ b/tests/components/litterrobot/test_select.py @@ -1,4 +1,5 @@ """Test the Litter-Robot select entity.""" + from unittest.mock import AsyncMock, MagicMock from pylitterbot import LitterRobot3, LitterRobot4 diff --git a/tests/components/litterrobot/test_sensor.py b/tests/components/litterrobot/test_sensor.py index adb44d59bff..9002894d0ab 100644 --- a/tests/components/litterrobot/test_sensor.py +++ b/tests/components/litterrobot/test_sensor.py @@ -1,4 +1,5 @@ """Test the Litter-Robot sensor entity.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/litterrobot/test_switch.py b/tests/components/litterrobot/test_switch.py index eee06101cf3..feaaa1217b8 100644 --- a/tests/components/litterrobot/test_switch.py +++ b/tests/components/litterrobot/test_switch.py @@ -1,4 +1,5 @@ """Test the Litter-Robot switch entity.""" + from unittest.mock import MagicMock from pylitterbot import Robot diff --git a/tests/components/litterrobot/test_time.py b/tests/components/litterrobot/test_time.py index 53f254008e7..f77263d9493 100644 --- a/tests/components/litterrobot/test_time.py +++ b/tests/components/litterrobot/test_time.py @@ -1,4 +1,5 @@ """Test the Litter-Robot time entity.""" + from __future__ import annotations from datetime import datetime, time diff --git a/tests/components/litterrobot/test_update.py b/tests/components/litterrobot/test_update.py index 259b8ad09fe..b1b092e1f02 100644 --- a/tests/components/litterrobot/test_update.py +++ b/tests/components/litterrobot/test_update.py @@ -1,4 +1,5 @@ """Test the Litter-Robot update entity.""" + from unittest.mock import AsyncMock, MagicMock from pylitterbot import LitterRobot4 diff --git a/tests/components/litterrobot/test_vacuum.py b/tests/components/litterrobot/test_vacuum.py index c2df2bc5095..9013d6e83eb 100644 --- a/tests/components/litterrobot/test_vacuum.py +++ b/tests/components/litterrobot/test_vacuum.py @@ -1,4 +1,5 @@ """Test the Litter-Robot vacuum entity.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/livisi/test_config_flow.py b/tests/components/livisi/test_config_flow.py index 8f13502bbc3..7f4f8568030 100644 --- a/tests/components/livisi/test_config_flow.py +++ b/tests/components/livisi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Livisi Home Assistant config flow.""" + from unittest.mock import patch from aiolivisi import errors as livisi_errors diff --git a/tests/components/local_calendar/test_config_flow.py b/tests/components/local_calendar/test_config_flow.py index 6cebd42cf30..89ea9d21ff5 100644 --- a/tests/components/local_calendar/test_config_flow.py +++ b/tests/components/local_calendar/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Local Calendar config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/local_calendar/test_diagnostics.py b/tests/components/local_calendar/test_diagnostics.py index 9a1da25d770..721eed19736 100644 --- a/tests/components/local_calendar/test_diagnostics.py +++ b/tests/components/local_calendar/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for diagnostics platform of local calendar.""" + from aiohttp.test_utils import TestClient from freezegun import freeze_time import pytest diff --git a/tests/components/local_file/test_camera.py b/tests/components/local_file/test_camera.py index d47c5fa69fa..38d6bdd84cc 100644 --- a/tests/components/local_file/test_camera.py +++ b/tests/components/local_file/test_camera.py @@ -1,4 +1,5 @@ """The tests for local file camera component.""" + from http import HTTPStatus from unittest import mock diff --git a/tests/components/local_ip/test_config_flow.py b/tests/components/local_ip/test_config_flow.py index 4de150eaf7a..82fcbf6d6e6 100644 --- a/tests/components/local_ip/test_config_flow.py +++ b/tests/components/local_ip/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the local_ip config_flow.""" + from __future__ import annotations from homeassistant import data_entry_flow diff --git a/tests/components/local_ip/test_init.py b/tests/components/local_ip/test_init.py index 5c9e9b4f551..54126b21243 100644 --- a/tests/components/local_ip/test_init.py +++ b/tests/components/local_ip/test_init.py @@ -1,4 +1,5 @@ """Tests for the local_ip component.""" + from __future__ import annotations from homeassistant import config_entries diff --git a/tests/components/local_todo/conftest.py b/tests/components/local_todo/conftest.py index 5afa005dd64..ca0ef4d3965 100644 --- a/tests/components/local_todo/conftest.py +++ b/tests/components/local_todo/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the local_todo tests.""" + from collections.abc import Generator from pathlib import Path from typing import Any diff --git a/tests/components/local_todo/test_config_flow.py b/tests/components/local_todo/test_config_flow.py index 6677a39e54a..381c97be167 100644 --- a/tests/components/local_todo/test_config_flow.py +++ b/tests/components/local_todo/test_config_flow.py @@ -1,4 +1,5 @@ """Test the local_todo config flow.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/locative/test_init.py b/tests/components/locative/test_init.py index 7a1e071958d..938892ad411 100644 --- a/tests/components/locative/test_init.py +++ b/tests/components/locative/test_init.py @@ -1,4 +1,5 @@ """The tests the for Locative device tracker platform.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/lock/test_device_trigger.py b/tests/components/lock/test_device_trigger.py index 9c1594760c9..f4e0a7a38a3 100644 --- a/tests/components/lock/test_device_trigger.py +++ b/tests/components/lock/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Lock device triggers.""" + from datetime import timedelta import pytest diff --git a/tests/components/lock/test_init.py b/tests/components/lock/test_init.py index 7ebb5bf3027..e98a7bd9eda 100644 --- a/tests/components/lock/test_init.py +++ b/tests/components/lock/test_init.py @@ -1,4 +1,5 @@ """The tests for the lock component.""" + from __future__ import annotations import re diff --git a/tests/components/lock/test_significant_change.py b/tests/components/lock/test_significant_change.py index e7ee3fa07c9..6931af97dd9 100644 --- a/tests/components/lock/test_significant_change.py +++ b/tests/components/lock/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Lock significant change platform.""" + from homeassistant.components.lock.significant_change import ( async_check_significant_change, ) diff --git a/tests/components/logbook/common.py b/tests/components/logbook/common.py index 824bbbde21d..67b83a19768 100644 --- a/tests/components/logbook/common.py +++ b/tests/components/logbook/common.py @@ -1,4 +1,5 @@ """Tests for the logbook component.""" + from __future__ import annotations import json diff --git a/tests/components/logbook/test_models.py b/tests/components/logbook/test_models.py index dcafd7e4765..459fd0e06c9 100644 --- a/tests/components/logbook/test_models.py +++ b/tests/components/logbook/test_models.py @@ -1,4 +1,5 @@ """The tests for the logbook component models.""" + from unittest.mock import Mock from homeassistant.components.logbook.models import LazyEventPartialState diff --git a/tests/components/logentries/test_init.py b/tests/components/logentries/test_init.py index 98b171c813f..40e73a86c05 100644 --- a/tests/components/logentries/test_init.py +++ b/tests/components/logentries/test_init.py @@ -1,4 +1,5 @@ """The tests for the Logentries component.""" + from unittest.mock import ANY, call, patch import pytest diff --git a/tests/components/logger/test_init.py b/tests/components/logger/test_init.py index 7b47ee60790..3e30ea0ead0 100644 --- a/tests/components/logger/test_init.py +++ b/tests/components/logger/test_init.py @@ -1,4 +1,5 @@ """The tests for the Logger component.""" + from collections import defaultdict import logging from typing import Any diff --git a/tests/components/london_air/test_sensor.py b/tests/components/london_air/test_sensor.py index 71ac81de1a8..0434cba8a37 100644 --- a/tests/components/london_air/test_sensor.py +++ b/tests/components/london_air/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the london_air platform.""" + from http import HTTPStatus import requests_mock diff --git a/tests/components/london_underground/test_sensor.py b/tests/components/london_underground/test_sensor.py index 4dda341279d..98f1cc0e09b 100644 --- a/tests/components/london_underground/test_sensor.py +++ b/tests/components/london_underground/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the london_underground platform.""" + from london_tube_status import API_URL from homeassistant.components.london_underground.const import CONF_LINE diff --git a/tests/components/lookin/test_config_flow.py b/tests/components/lookin/test_config_flow.py index 873e21a5cac..d489cee8101 100644 --- a/tests/components/lookin/test_config_flow.py +++ b/tests/components/lookin/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the lookin config flow.""" + from __future__ import annotations import dataclasses diff --git a/tests/components/loqed/test_config_flow.py b/tests/components/loqed/test_config_flow.py index 617b6818a64..87c531b67dc 100644 --- a/tests/components/loqed/test_config_flow.py +++ b/tests/components/loqed/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Loqed config flow.""" + from ipaddress import ip_address import json from unittest.mock import Mock, patch diff --git a/tests/components/loqed/test_lock.py b/tests/components/loqed/test_lock.py index 59e70212f92..5fd00b66c43 100644 --- a/tests/components/loqed/test_lock.py +++ b/tests/components/loqed/test_lock.py @@ -1,4 +1,5 @@ """Tests the lock platform of the Loqed integration.""" + from loqedAPI import loqed from homeassistant.components.loqed import LoqedDataCoordinator diff --git a/tests/components/lovelace/test_cast.py b/tests/components/lovelace/test_cast.py index 4181d73c4d3..12cf4a84095 100644 --- a/tests/components/lovelace/test_cast.py +++ b/tests/components/lovelace/test_cast.py @@ -1,4 +1,5 @@ """Test the Lovelace Cast platform.""" + from time import time from unittest.mock import patch diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py index a772b37f047..0fca21a9a45 100644 --- a/tests/components/lovelace/test_dashboard.py +++ b/tests/components/lovelace/test_dashboard.py @@ -1,4 +1,5 @@ """Test the Lovelace initialization.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/lovelace/test_system_health.py b/tests/components/lovelace/test_system_health.py index 72e7adb3a13..11cb8776e62 100644 --- a/tests/components/lovelace/test_system_health.py +++ b/tests/components/lovelace/test_system_health.py @@ -1,4 +1,5 @@ """Tests for Lovelace system health.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/luftdaten/conftest.py b/tests/components/luftdaten/conftest.py index 08cbe7a2c3c..e6fb0d37288 100644 --- a/tests/components/luftdaten/conftest.py +++ b/tests/components/luftdaten/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Luftdaten tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/luftdaten/test_config_flow.py b/tests/components/luftdaten/test_config_flow.py index a0b741f7d2a..7469fe6e486 100644 --- a/tests/components/luftdaten/test_config_flow.py +++ b/tests/components/luftdaten/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Luftdaten config flow.""" + from unittest.mock import MagicMock from luftdaten.exceptions import LuftdatenConnectionError diff --git a/tests/components/luftdaten/test_init.py b/tests/components/luftdaten/test_init.py index 40e40bcd137..dda7c147672 100644 --- a/tests/components/luftdaten/test_init.py +++ b/tests/components/luftdaten/test_init.py @@ -1,4 +1,5 @@ """Tests for the Luftdaten integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from luftdaten.exceptions import LuftdatenError diff --git a/tests/components/luftdaten/test_sensor.py b/tests/components/luftdaten/test_sensor.py index 7a2cac1721b..f2cf12b3fda 100644 --- a/tests/components/luftdaten/test_sensor.py +++ b/tests/components/luftdaten/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the Luftdaten integration.""" + from homeassistant.components.luftdaten.const import DOMAIN from homeassistant.components.sensor import ( ATTR_STATE_CLASS, diff --git a/tests/components/lutron/test_config_flow.py b/tests/components/lutron/test_config_flow.py index b1f4b3365c9..0d641e4c2e4 100644 --- a/tests/components/lutron/test_config_flow.py +++ b/tests/components/lutron/test_config_flow.py @@ -1,4 +1,5 @@ """Test the lutron config flow.""" + from email.message import Message from unittest.mock import AsyncMock, patch from urllib.error import HTTPError diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 759b23b8f4f..f2014ce70ca 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Lutron Caseta config flow.""" + from ipaddress import ip_address from pathlib import Path import ssl diff --git a/tests/components/lutron_caseta/test_device_trigger.py b/tests/components/lutron_caseta/test_device_trigger.py index 3fafbb8a57f..0e638065cf7 100644 --- a/tests/components/lutron_caseta/test_device_trigger.py +++ b/tests/components/lutron_caseta/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Lutron Caséta device triggers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/lutron_caseta/test_diagnostics.py b/tests/components/lutron_caseta/test_diagnostics.py index 0abf54b9589..5c7d20da208 100644 --- a/tests/components/lutron_caseta/test_diagnostics.py +++ b/tests/components/lutron_caseta/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Lutron Caseta diagnostics.""" + from unittest.mock import ANY, patch from homeassistant.components.lutron_caseta import DOMAIN diff --git a/tests/components/lutron_caseta/test_logbook.py b/tests/components/lutron_caseta/test_logbook.py index c0bac43ba6f..b6e8840c85c 100644 --- a/tests/components/lutron_caseta/test_logbook.py +++ b/tests/components/lutron_caseta/test_logbook.py @@ -1,4 +1,5 @@ """The tests for lutron caseta logbook.""" + from unittest.mock import patch from homeassistant.components.lutron_caseta.const import ( diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index 5a88728cc75..7f1e0549ee8 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Honeywell Lyric config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/mailbox/test_init.py b/tests/components/mailbox/test_init.py index 0e48f3c8ccc..1af7f2e06b9 100644 --- a/tests/components/mailbox/test_init.py +++ b/tests/components/mailbox/test_init.py @@ -1,4 +1,5 @@ """The tests for the mailbox component.""" + from datetime import datetime from hashlib import sha1 from http import HTTPStatus diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index 14e28b0999d..7a264134320 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """The tests for the manual Alarm Control Panel component.""" + from datetime import timedelta from unittest.mock import MagicMock, patch diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index 0df1114bf30..5c2704db937 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """The tests for the manual_mqtt Alarm Control Panel component.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/marytts/test_tts.py b/tests/components/marytts/test_tts.py index 474d2f19faf..953c66f58d1 100644 --- a/tests/components/marytts/test_tts.py +++ b/tests/components/marytts/test_tts.py @@ -1,4 +1,5 @@ """The tests for the MaryTTS speech platform.""" + from http import HTTPStatus import io from unittest.mock import patch diff --git a/tests/components/matrix/conftest.py b/tests/components/matrix/conftest.py index 3e7d4833d6f..b3fefe3ac67 100644 --- a/tests/components/matrix/conftest.py +++ b/tests/components/matrix/conftest.py @@ -1,4 +1,5 @@ """Define fixtures available for all tests.""" + from __future__ import annotations import re diff --git a/tests/components/matrix/test_commands.py b/tests/components/matrix/test_commands.py index cbf85ccc597..f71ec22e794 100644 --- a/tests/components/matrix/test_commands.py +++ b/tests/components/matrix/test_commands.py @@ -1,4 +1,5 @@ """Test MatrixBot's ability to parse and respond to commands in matrix rooms.""" + from functools import partial from itertools import chain from typing import Any diff --git a/tests/components/matter/common.py b/tests/components/matter/common.py index d5093367db5..7878ac564fd 100644 --- a/tests/components/matter/common.py +++ b/tests/components/matter/common.py @@ -1,4 +1,5 @@ """Provide common test tools.""" + from __future__ import annotations from functools import cache diff --git a/tests/components/matter/conftest.py b/tests/components/matter/conftest.py index 03443e4c4b9..a04bf68d28a 100644 --- a/tests/components/matter/conftest.py +++ b/tests/components/matter/conftest.py @@ -1,4 +1,5 @@ """Provide common fixtures.""" + from __future__ import annotations import asyncio diff --git a/tests/components/matter/test_adapter.py b/tests/components/matter/test_adapter.py index 0cc3e360ab6..603e984779e 100644 --- a/tests/components/matter/test_adapter.py +++ b/tests/components/matter/test_adapter.py @@ -1,4 +1,5 @@ """Test the adapter.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/matter/test_api.py b/tests/components/matter/test_api.py index 8e463800f98..b47c014f6b2 100644 --- a/tests/components/matter/test_api.py +++ b/tests/components/matter/test_api.py @@ -1,4 +1,5 @@ """Test the api module.""" + from unittest.mock import AsyncMock, MagicMock, call from matter_server.client.models.node import ( diff --git a/tests/components/matter/test_binary_sensor.py b/tests/components/matter/test_binary_sensor.py index e231012f90d..97a22d6dc98 100644 --- a/tests/components/matter/test_binary_sensor.py +++ b/tests/components/matter/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test Matter binary sensors.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/matter/test_climate.py b/tests/components/matter/test_climate.py index 81d210ed579..80e2d1b72da 100644 --- a/tests/components/matter/test_climate.py +++ b/tests/components/matter/test_climate.py @@ -1,4 +1,5 @@ """Test Matter locks.""" + from unittest.mock import MagicMock, call from chip.clusters import Objects as clusters diff --git a/tests/components/matter/test_config_flow.py b/tests/components/matter/test_config_flow.py index eddf6506bfd..e690844c228 100644 --- a/tests/components/matter/test_config_flow.py +++ b/tests/components/matter/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Matter config flow.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/matter/test_cover.py b/tests/components/matter/test_cover.py index d409983307f..ff6e933a1ab 100644 --- a/tests/components/matter/test_cover.py +++ b/tests/components/matter/test_cover.py @@ -1,4 +1,5 @@ """Test Matter covers.""" + from math import floor from unittest.mock import MagicMock, call diff --git a/tests/components/matter/test_diagnostics.py b/tests/components/matter/test_diagnostics.py index c14eb93f24c..6863619e145 100644 --- a/tests/components/matter/test_diagnostics.py +++ b/tests/components/matter/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Matter diagnostics platform.""" + from __future__ import annotations import json diff --git a/tests/components/matter/test_door_lock.py b/tests/components/matter/test_door_lock.py index 51d48cddba7..a44b5929f65 100644 --- a/tests/components/matter/test_door_lock.py +++ b/tests/components/matter/test_door_lock.py @@ -1,4 +1,5 @@ """Test Matter locks.""" + from unittest.mock import MagicMock, call from chip.clusters import Objects as clusters diff --git a/tests/components/matter/test_event.py b/tests/components/matter/test_event.py index 0aa9385a74c..2bdcfb6adb7 100644 --- a/tests/components/matter/test_event.py +++ b/tests/components/matter/test_event.py @@ -1,4 +1,5 @@ """Test Matter Event entities.""" + from unittest.mock import MagicMock from matter_server.client.models.node import MatterNode diff --git a/tests/components/matter/test_helpers.py b/tests/components/matter/test_helpers.py index 61988a37122..a4b5e165a93 100644 --- a/tests/components/matter/test_helpers.py +++ b/tests/components/matter/test_helpers.py @@ -1,4 +1,5 @@ """Test the Matter helpers.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/matter/test_init.py b/tests/components/matter/test_init.py index 2286249bd5d..327e73dd4de 100644 --- a/tests/components/matter/test_init.py +++ b/tests/components/matter/test_init.py @@ -1,4 +1,5 @@ """Test the Matter integration init.""" + from __future__ import annotations import asyncio diff --git a/tests/components/matter/test_light.py b/tests/components/matter/test_light.py index 0376a902f32..9c3c2610d92 100644 --- a/tests/components/matter/test_light.py +++ b/tests/components/matter/test_light.py @@ -1,4 +1,5 @@ """Test Matter lights.""" + from unittest.mock import MagicMock, call from chip.clusters import Objects as clusters diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py index 579dd7d94c5..c8af0647d31 100644 --- a/tests/components/matter/test_sensor.py +++ b/tests/components/matter/test_sensor.py @@ -1,4 +1,5 @@ """Test Matter sensors.""" + from datetime import UTC, datetime, timedelta from unittest.mock import MagicMock, patch diff --git a/tests/components/matter/test_switch.py b/tests/components/matter/test_switch.py index ac03d731ee1..5fc23fa7b34 100644 --- a/tests/components/matter/test_switch.py +++ b/tests/components/matter/test_switch.py @@ -1,4 +1,5 @@ """Test Matter switches.""" + from unittest.mock import MagicMock, call from chip.clusters import Objects as clusters diff --git a/tests/components/maxcube/conftest.py b/tests/components/maxcube/conftest.py index f0dd12eb6c6..82a852a5201 100644 --- a/tests/components/maxcube/conftest.py +++ b/tests/components/maxcube/conftest.py @@ -1,4 +1,5 @@ """Tests for EQ3 Max! component.""" + from unittest.mock import create_autospec, patch from maxcube.device import MAX_DEVICE_MODE_AUTOMATIC, MAX_DEVICE_MODE_MANUAL diff --git a/tests/components/maxcube/test_maxcube_binary_sensor.py b/tests/components/maxcube/test_maxcube_binary_sensor.py index 0c73c548211..cc86f389884 100644 --- a/tests/components/maxcube/test_maxcube_binary_sensor.py +++ b/tests/components/maxcube/test_maxcube_binary_sensor.py @@ -1,4 +1,5 @@ """Test EQ3 Max! Window Shutters.""" + from datetime import timedelta from maxcube.cube import MaxCube diff --git a/tests/components/maxcube/test_maxcube_climate.py b/tests/components/maxcube/test_maxcube_climate.py index 76ab214f3ac..cb4dc510605 100644 --- a/tests/components/maxcube/test_maxcube_climate.py +++ b/tests/components/maxcube/test_maxcube_climate.py @@ -1,4 +1,5 @@ """Test EQ3 Max! Thermostats.""" + from datetime import timedelta from maxcube.cube import MaxCube diff --git a/tests/components/meater/test_config_flow.py b/tests/components/meater/test_config_flow.py index 66f304c2d6c..6d294259c01 100644 --- a/tests/components/meater/test_config_flow.py +++ b/tests/components/meater/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Meater config flow.""" + from unittest.mock import AsyncMock, patch from meater import AuthenticationError, ServiceUnavailableError diff --git a/tests/components/medcom_ble/test_config_flow.py b/tests/components/medcom_ble/test_config_flow.py index 620b6811757..34fc145e7dc 100644 --- a/tests/components/medcom_ble/test_config_flow.py +++ b/tests/components/medcom_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Medcom Inspector BLE config flow.""" + from unittest.mock import patch from bleak import BleakError diff --git a/tests/components/media_extractor/conftest.py b/tests/components/media_extractor/conftest.py index 8c8a6d6fb8d..4b7411340ae 100644 --- a/tests/components/media_extractor/conftest.py +++ b/tests/components/media_extractor/conftest.py @@ -1,4 +1,5 @@ """The tests for Media Extractor integration.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/media_player/common.py b/tests/components/media_player/common.py index db2ef76b210..77076d903a6 100644 --- a/tests/components/media_player/common.py +++ b/tests/components/media_player/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, diff --git a/tests/components/media_player/test_browse_media.py b/tests/components/media_player/test_browse_media.py index c7ce52eb12a..8c5554793a6 100644 --- a/tests/components/media_player/test_browse_media.py +++ b/tests/components/media_player/test_browse_media.py @@ -1,4 +1,5 @@ """Test media browser helpers for media player.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/media_player/test_device_trigger.py b/tests/components/media_player/test_device_trigger.py index afc46c87cff..9639a13968f 100644 --- a/tests/components/media_player/test_device_trigger.py +++ b/tests/components/media_player/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Media player device triggers.""" + from datetime import timedelta import pytest diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index d44ff28c772..bc92c591d27 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -1,4 +1,5 @@ """Test the base functions of the media player.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/media_player/test_recorder.py b/tests/components/media_player/test_recorder.py index a04c7e85119..075e1d37f1a 100644 --- a/tests/components/media_player/test_recorder.py +++ b/tests/components/media_player/test_recorder.py @@ -1,4 +1,5 @@ """The tests for media_player recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index eecfe6cde6e..c37e418020b 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -1,4 +1,5 @@ """Test Media Source initialization.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/media_source/test_local_source.py b/tests/components/media_source/test_local_source.py index bc637caab80..9902aa689ae 100644 --- a/tests/components/media_source/test_local_source.py +++ b/tests/components/media_source/test_local_source.py @@ -1,4 +1,5 @@ """Test Local Media Source.""" + from collections.abc import AsyncGenerator from http import HTTPStatus import io diff --git a/tests/components/media_source/test_models.py b/tests/components/media_source/test_models.py index 35127e88798..12685e28d69 100644 --- a/tests/components/media_source/test_models.py +++ b/tests/components/media_source/test_models.py @@ -1,4 +1,5 @@ """Test Media Source model methods.""" + from homeassistant.components.media_player import MediaClass, MediaType from homeassistant.components.media_source import const, models diff --git a/tests/components/melcloud/test_atw_zone_sensor.py b/tests/components/melcloud/test_atw_zone_sensor.py index 7defe3277cb..5ffb6dd7ff5 100644 --- a/tests/components/melcloud/test_atw_zone_sensor.py +++ b/tests/components/melcloud/test_atw_zone_sensor.py @@ -1,4 +1,5 @@ """Test the MELCloud ATW zone sensor.""" + from unittest.mock import patch import pytest diff --git a/tests/components/melcloud/test_config_flow.py b/tests/components/melcloud/test_config_flow.py index 5ca44d4fe46..0a21a8747e3 100644 --- a/tests/components/melcloud/test_config_flow.py +++ b/tests/components/melcloud/test_config_flow.py @@ -1,4 +1,5 @@ """Test the MELCloud config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/melissa/test_init.py b/tests/components/melissa/test_init.py index 96f3a08bb11..8f1e0c7a43c 100644 --- a/tests/components/melissa/test_init.py +++ b/tests/components/melissa/test_init.py @@ -1,4 +1,5 @@ """The test for the Melissa Climate component.""" + from unittest.mock import AsyncMock, patch from homeassistant.components import melissa diff --git a/tests/components/melnor/conftest.py b/tests/components/melnor/conftest.py index 3e87a4e646f..361102f22e6 100644 --- a/tests/components/melnor/conftest.py +++ b/tests/components/melnor/conftest.py @@ -1,4 +1,5 @@ """Tests for the melnor integration.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/melnor/test_config_flow.py b/tests/components/melnor/test_config_flow.py index bb0a017611f..ae4e7b84288 100644 --- a/tests/components/melnor/test_config_flow.py +++ b/tests/components/melnor/test_config_flow.py @@ -1,4 +1,5 @@ """Test the melnor config flow.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/melnor/test_number.py b/tests/components/melnor/test_number.py index f8fc31f27c3..4b185c3700c 100644 --- a/tests/components/melnor/test_number.py +++ b/tests/components/melnor/test_number.py @@ -1,4 +1,5 @@ """Test the Melnor sensors.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/tests/components/melnor/test_sensor.py b/tests/components/melnor/test_sensor.py index 291115fae9d..0f7eec0bbd4 100644 --- a/tests/components/melnor/test_sensor.py +++ b/tests/components/melnor/test_sensor.py @@ -1,4 +1,5 @@ """Test the Melnor sensors.""" + from __future__ import annotations from freezegun import freeze_time diff --git a/tests/components/melnor/test_switch.py b/tests/components/melnor/test_switch.py index ec2d4c25c55..a5efcaef18e 100644 --- a/tests/components/melnor/test_switch.py +++ b/tests/components/melnor/test_switch.py @@ -1,4 +1,5 @@ """Test the Melnor sensors.""" + from __future__ import annotations from homeassistant.components.switch import SwitchDeviceClass diff --git a/tests/components/melnor/test_time.py b/tests/components/melnor/test_time.py index 682f518d40b..a49999d507b 100644 --- a/tests/components/melnor/test_time.py +++ b/tests/components/melnor/test_time.py @@ -1,4 +1,5 @@ """Test the Melnor time platform.""" + from __future__ import annotations from datetime import time diff --git a/tests/components/meraki/test_device_tracker.py b/tests/components/meraki/test_device_tracker.py index 69d1bc1b3a8..02b9ba06b72 100644 --- a/tests/components/meraki/test_device_tracker.py +++ b/tests/components/meraki/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests the for Meraki device tracker.""" + from http import HTTPStatus import json diff --git a/tests/components/met/conftest.py b/tests/components/met/conftest.py index a007620988f..699c1c81795 100644 --- a/tests/components/met/conftest.py +++ b/tests/components/met/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Met weather testing.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/met/test_config_flow.py b/tests/components/met/test_config_flow.py index 24ce8660346..8d0b1620022 100644 --- a/tests/components/met/test_config_flow.py +++ b/tests/components/met/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Met.no config flow.""" + from unittest.mock import ANY, patch import pytest diff --git a/tests/components/met/test_weather.py b/tests/components/met/test_weather.py index d1714e7b69e..95547ead14d 100644 --- a/tests/components/met/test_weather.py +++ b/tests/components/met/test_weather.py @@ -1,4 +1,5 @@ """Test Met weather entity.""" + from homeassistant import config_entries from homeassistant.components.met import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN diff --git a/tests/components/met_eireann/conftest.py b/tests/components/met_eireann/conftest.py index e73d1e41cca..d1fd5bd7c89 100644 --- a/tests/components/met_eireann/conftest.py +++ b/tests/components/met_eireann/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Met Éireann weather testing.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/met_eireann/test_config_flow.py b/tests/components/met_eireann/test_config_flow.py index bad0e8b3e05..8c5e7f43ced 100644 --- a/tests/components/met_eireann/test_config_flow.py +++ b/tests/components/met_eireann/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Met Éireann config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/met_eireann/test_init.py b/tests/components/met_eireann/test_init.py index dd34ac25cfb..14457ae2f7d 100644 --- a/tests/components/met_eireann/test_init.py +++ b/tests/components/met_eireann/test_init.py @@ -1,4 +1,5 @@ """Test the Met Éireann integration init.""" + from homeassistant.components.met_eireann.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant diff --git a/tests/components/meteo_france/conftest.py b/tests/components/meteo_france/conftest.py index 0b62a03a53c..123fc00e42a 100644 --- a/tests/components/meteo_france/conftest.py +++ b/tests/components/meteo_france/conftest.py @@ -1,4 +1,5 @@ """Meteo-France generic test utils.""" + from unittest.mock import patch import pytest diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index 0405f8efa18..4b9e26f883b 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Meteo-France config flow.""" + from unittest.mock import patch from meteofrance_api.model import Place diff --git a/tests/components/meteoclimatic/conftest.py b/tests/components/meteoclimatic/conftest.py index 964f67d6473..a481b811a77 100644 --- a/tests/components/meteoclimatic/conftest.py +++ b/tests/components/meteoclimatic/conftest.py @@ -1,4 +1,5 @@ """Meteoclimatic generic test utils.""" + from unittest.mock import patch import pytest diff --git a/tests/components/meteoclimatic/test_config_flow.py b/tests/components/meteoclimatic/test_config_flow.py index 46527d675f0..42fb5e78d4a 100644 --- a/tests/components/meteoclimatic/test_config_flow.py +++ b/tests/components/meteoclimatic/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Meteoclimatic config flow.""" + from unittest.mock import patch from meteoclimatic.exceptions import MeteoclimaticError, StationNotFound diff --git a/tests/components/metoffice/conftest.py b/tests/components/metoffice/conftest.py index b1d1c9f508e..83c7e7853f7 100644 --- a/tests/components/metoffice/conftest.py +++ b/tests/components/metoffice/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Met Office weather integration tests.""" + from unittest.mock import patch from datapoint.exceptions import APIException diff --git a/tests/components/metoffice/test_init.py b/tests/components/metoffice/test_init.py index 10ed0a83f0c..159587ca7c1 100644 --- a/tests/components/metoffice/test_init.py +++ b/tests/components/metoffice/test_init.py @@ -1,4 +1,5 @@ """Tests for metoffice init.""" + from __future__ import annotations import datetime diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index 842812f68f5..fda318489fb 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the mFi sensor platform.""" + from copy import deepcopy import unittest.mock as mock diff --git a/tests/components/microbees/test_config_flow.py b/tests/components/microbees/test_config_flow.py index 62ecbbeb9be..5e57c723f5b 100644 --- a/tests/components/microbees/test_config_flow.py +++ b/tests/components/microbees/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for config flow.""" + from unittest.mock import AsyncMock, patch from microBeesPy import MicroBeesException diff --git a/tests/components/microsoft/test_tts.py b/tests/components/microsoft/test_tts.py index bc6a3ac7dd7..cd4ccabcf0d 100644 --- a/tests/components/microsoft/test_tts.py +++ b/tests/components/microsoft/test_tts.py @@ -1,4 +1,5 @@ """Tests for Microsoft text-to-speech.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index affdbb4e932..dd99a1d48aa 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -1,4 +1,5 @@ """The tests for the microsoft face platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/microsoft_face_detect/test_image_processing.py b/tests/components/microsoft_face_detect/test_image_processing.py index 349440124ff..b1aa2a00c85 100644 --- a/tests/components/microsoft_face_detect/test_image_processing.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -1,4 +1,5 @@ """The tests for the microsoft face detect platform.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/microsoft_face_identify/test_image_processing.py b/tests/components/microsoft_face_identify/test_image_processing.py index 6581aea835f..59a93bd9db5 100644 --- a/tests/components/microsoft_face_identify/test_image_processing.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -1,4 +1,5 @@ """The tests for the microsoft face identify platform.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/mikrotik/test_config_flow.py b/tests/components/mikrotik/test_config_flow.py index ea73e581710..f48446e3e14 100644 --- a/tests/components/mikrotik/test_config_flow.py +++ b/tests/components/mikrotik/test_config_flow.py @@ -1,4 +1,5 @@ """Test Mikrotik setup process.""" + from unittest.mock import patch import librouteros diff --git a/tests/components/mikrotik/test_device_tracker.py b/tests/components/mikrotik/test_device_tracker.py index bf1dc3abedf..47ddc038f69 100644 --- a/tests/components/mikrotik/test_device_tracker.py +++ b/tests/components/mikrotik/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the Mikrotik device tracker platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/mikrotik/test_init.py b/tests/components/mikrotik/test_init.py index dc0f0505a4d..a42db5f84da 100644 --- a/tests/components/mikrotik/test_init.py +++ b/tests/components/mikrotik/test_init.py @@ -1,4 +1,5 @@ """Test Mikrotik setup process.""" + from unittest.mock import MagicMock, patch from librouteros.exceptions import ConnectionClosed, LibRouterosError diff --git a/tests/components/mill/test_config_flow.py b/tests/components/mill/test_config_flow.py index 59128cc1749..d7740502412 100644 --- a/tests/components/mill/test_config_flow.py +++ b/tests/components/mill/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Mill config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/mill/test_init.py b/tests/components/mill/test_init.py index 15175dedada..8425c980f80 100644 --- a/tests/components/mill/test_init.py +++ b/tests/components/mill/test_init.py @@ -1,4 +1,5 @@ """Tests for Mill init.""" + from unittest.mock import patch from homeassistant.components import mill diff --git a/tests/components/min_max/test_config_flow.py b/tests/components/min_max/test_config_flow.py index f180f07b657..ba9862f92b4 100644 --- a/tests/components/min_max/test_config_flow.py +++ b/tests/components/min_max/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Min/Max config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/minecraft_server/const.py b/tests/components/minecraft_server/const.py index 92d6c647d8f..6914d36ba5b 100644 --- a/tests/components/minecraft_server/const.py +++ b/tests/components/minecraft_server/const.py @@ -1,4 +1,5 @@ """Constants for Minecraft Server integration tests.""" + from mcstatus.motd import Motd from mcstatus.status_response import ( BedrockStatusPlayers, diff --git a/tests/components/minecraft_server/test_binary_sensor.py b/tests/components/minecraft_server/test_binary_sensor.py index 4db564bc143..40b8a6c5c0c 100644 --- a/tests/components/minecraft_server/test_binary_sensor.py +++ b/tests/components/minecraft_server/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for Minecraft Server binary sensor.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/minecraft_server/test_diagnostics.py b/tests/components/minecraft_server/test_diagnostics.py index 80b5c91c1fb..ce51a38b88f 100644 --- a/tests/components/minecraft_server/test_diagnostics.py +++ b/tests/components/minecraft_server/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for Minecraft Server diagnostics.""" + from unittest.mock import patch from mcstatus import BedrockServer, JavaServer diff --git a/tests/components/minecraft_server/test_init.py b/tests/components/minecraft_server/test_init.py index 3d554bf1a55..86f592f3d76 100644 --- a/tests/components/minecraft_server/test_init.py +++ b/tests/components/minecraft_server/test_init.py @@ -1,4 +1,5 @@ """Tests for the Minecraft Server integration.""" + from unittest.mock import patch from mcstatus import JavaServer diff --git a/tests/components/minecraft_server/test_sensor.py b/tests/components/minecraft_server/test_sensor.py index 7d599669d71..512f6bf3f78 100644 --- a/tests/components/minecraft_server/test_sensor.py +++ b/tests/components/minecraft_server/test_sensor.py @@ -1,4 +1,5 @@ """Tests for Minecraft Server sensors.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/mjpeg/conftest.py b/tests/components/mjpeg/conftest.py index 0bd85920a8e..e10c267d718 100644 --- a/tests/components/mjpeg/conftest.py +++ b/tests/components/mjpeg/conftest.py @@ -1,4 +1,5 @@ """Fixtures for MJPEG IP Camera integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/mjpeg/test_init.py b/tests/components/mjpeg/test_init.py index e96749a98dd..ca3ef03f5c8 100644 --- a/tests/components/mjpeg/test_init.py +++ b/tests/components/mjpeg/test_init.py @@ -1,4 +1,5 @@ """Tests for the MJPEG IP Camera integration.""" + from unittest.mock import AsyncMock, MagicMock from homeassistant.components.mjpeg.const import DOMAIN diff --git a/tests/components/moat/test_config_flow.py b/tests/components/moat/test_config_flow.py index e40cd911dba..ab0825c884e 100644 --- a/tests/components/moat/test_config_flow.py +++ b/tests/components/moat/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Moat config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/moat/test_sensor.py b/tests/components/moat/test_sensor.py index 680f23853e8..6ec090bcac3 100644 --- a/tests/components/moat/test_sensor.py +++ b/tests/components/moat/test_sensor.py @@ -1,4 +1,5 @@ """Test the Moat sensors.""" + from homeassistant.components.moat.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT diff --git a/tests/components/mobile_app/conftest.py b/tests/components/mobile_app/conftest.py index f69912f176c..aa53c4c6136 100644 --- a/tests/components/mobile_app/conftest.py +++ b/tests/components/mobile_app/conftest.py @@ -1,4 +1,5 @@ """Tests for mobile_app component.""" + from http import HTTPStatus import pytest diff --git a/tests/components/mobile_app/test_binary_sensor.py b/tests/components/mobile_app/test_binary_sensor.py index fe3510865fc..acebd8796b7 100644 --- a/tests/components/mobile_app/test_binary_sensor.py +++ b/tests/components/mobile_app/test_binary_sensor.py @@ -1,4 +1,5 @@ """Entity tests for mobile_app.""" + from http import HTTPStatus import pytest diff --git a/tests/components/mobile_app/test_device_action.py b/tests/components/mobile_app/test_device_action.py index fd064ab653b..7be9c8d304b 100644 --- a/tests/components/mobile_app/test_device_action.py +++ b/tests/components/mobile_app/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Mobile App device actions.""" + from homeassistant.components import automation, device_automation from homeassistant.components.mobile_app import DATA_DEVICES, DOMAIN, util from homeassistant.core import HomeAssistant diff --git a/tests/components/mobile_app/test_device_tracker.py b/tests/components/mobile_app/test_device_tracker.py index 5e775fdf265..21d4d80c791 100644 --- a/tests/components/mobile_app/test_device_tracker.py +++ b/tests/components/mobile_app/test_device_tracker.py @@ -1,4 +1,5 @@ """Test mobile app device tracker.""" + from http import HTTPStatus from homeassistant.core import HomeAssistant diff --git a/tests/components/mobile_app/test_http_api.py b/tests/components/mobile_app/test_http_api.py index 28a8a26657a..d080b7a5106 100644 --- a/tests/components/mobile_app/test_http_api.py +++ b/tests/components/mobile_app/test_http_api.py @@ -1,4 +1,5 @@ """Tests for the mobile_app HTTP API.""" + from binascii import unhexlify from http import HTTPStatus import json diff --git a/tests/components/mobile_app/test_init.py b/tests/components/mobile_app/test_init.py index 5d25e9568cf..5e44fbd1c03 100644 --- a/tests/components/mobile_app/test_init.py +++ b/tests/components/mobile_app/test_init.py @@ -1,4 +1,5 @@ """Tests for the mobile app integration.""" + from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import Mock, patch diff --git a/tests/components/mobile_app/test_logbook.py b/tests/components/mobile_app/test_logbook.py index 6c9d77088a9..8d9d0c068f2 100644 --- a/tests/components/mobile_app/test_logbook.py +++ b/tests/components/mobile_app/test_logbook.py @@ -1,4 +1,5 @@ """The tests for mobile_app logbook.""" + from homeassistant.components.mobile_app.logbook import ( DOMAIN, IOS_EVENT_ZONE_ENTERED, diff --git a/tests/components/mobile_app/test_notify.py b/tests/components/mobile_app/test_notify.py index 23e2530c70a..dacaba32e16 100644 --- a/tests/components/mobile_app/test_notify.py +++ b/tests/components/mobile_app/test_notify.py @@ -1,4 +1,5 @@ """Notify platform tests for mobile_app.""" + from datetime import datetime, timedelta from unittest.mock import patch diff --git a/tests/components/mobile_app/test_sensor.py b/tests/components/mobile_app/test_sensor.py index c1414533fd7..5ff006a1773 100644 --- a/tests/components/mobile_app/test_sensor.py +++ b/tests/components/mobile_app/test_sensor.py @@ -1,4 +1,5 @@ """Entity tests for mobile_app.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index f7581f03241..c43b3c7ffb8 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -1,4 +1,5 @@ """Webhook tests for mobile_app.""" + from binascii import unhexlify from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/mochad/conftest.py b/tests/components/mochad/conftest.py index bd543eae943..2500070b2f1 100644 --- a/tests/components/mochad/conftest.py +++ b/tests/components/mochad/conftest.py @@ -1,2 +1,3 @@ """mochad conftest.""" + from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/modbus/test_cover.py b/tests/components/modbus/test_cover.py index 39897822bc8..32c79fb4dff 100644 --- a/tests/components/modbus/test_cover.py +++ b/tests/components/modbus/test_cover.py @@ -1,4 +1,5 @@ """The tests for the Modbus cover component.""" + from pymodbus.exceptions import ModbusException import pytest diff --git a/tests/components/modbus/test_fan.py b/tests/components/modbus/test_fan.py index 0922329d4b7..9719de3601b 100644 --- a/tests/components/modbus/test_fan.py +++ b/tests/components/modbus/test_fan.py @@ -1,4 +1,5 @@ """The tests for the Modbus fan component.""" + from pymodbus.exceptions import ModbusException import pytest diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 4de9a439a01..40a1253b9e1 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -12,6 +12,7 @@ This file is responsible for testing: It uses binary_sensors/sensors to do black box testing of the read calls. """ + from datetime import timedelta import logging from unittest import mock diff --git a/tests/components/modbus/test_light.py b/tests/components/modbus/test_light.py index ecd9abd71b8..e5e1b56d77b 100644 --- a/tests/components/modbus/test_light.py +++ b/tests/components/modbus/test_light.py @@ -1,4 +1,5 @@ """The tests for the Modbus light component.""" + from pymodbus.exceptions import ModbusException import pytest diff --git a/tests/components/modbus/test_switch.py b/tests/components/modbus/test_switch.py index 28c44440581..4eb0a5b3a18 100644 --- a/tests/components/modbus/test_switch.py +++ b/tests/components/modbus/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Modbus switch component.""" + from datetime import timedelta from unittest import mock diff --git a/tests/components/modem_callerid/test_config_flow.py b/tests/components/modem_callerid/test_config_flow.py index a844f436905..aeb8fb6d966 100644 --- a/tests/components/modem_callerid/test_config_flow.py +++ b/tests/components/modem_callerid/test_config_flow.py @@ -1,4 +1,5 @@ """Test Modem Caller ID config flow.""" + from unittest.mock import MagicMock, patch import phone_modem diff --git a/tests/components/modem_callerid/test_init.py b/tests/components/modem_callerid/test_init.py index 748bcfdd85b..b1edf44acf3 100644 --- a/tests/components/modem_callerid/test_init.py +++ b/tests/components/modem_callerid/test_init.py @@ -1,4 +1,5 @@ """Test Modem Caller ID integration.""" + from unittest.mock import patch from phone_modem import exceptions diff --git a/tests/components/modern_forms/test_binary_sensor.py b/tests/components/modern_forms/test_binary_sensor.py index 9c2ce6b5345..a605b86d484 100644 --- a/tests/components/modern_forms/test_binary_sensor.py +++ b/tests/components/modern_forms/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms sensor platform.""" + from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.modern_forms.const import DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/modern_forms/test_config_flow.py b/tests/components/modern_forms/test_config_flow.py index 49bac6a5bb0..6e1d2452479 100644 --- a/tests/components/modern_forms/test_config_flow.py +++ b/tests/components/modern_forms/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms config flow.""" + from ipaddress import ip_address from unittest.mock import MagicMock, patch diff --git a/tests/components/modern_forms/test_fan.py b/tests/components/modern_forms/test_fan.py index 9dc5ca9960f..92660643bfc 100644 --- a/tests/components/modern_forms/test_fan.py +++ b/tests/components/modern_forms/test_fan.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms fan platform.""" + from unittest.mock import patch from aiomodernforms import ModernFormsConnectionError diff --git a/tests/components/modern_forms/test_init.py b/tests/components/modern_forms/test_init.py index 9befb36d00d..4f146dfcea5 100644 --- a/tests/components/modern_forms/test_init.py +++ b/tests/components/modern_forms/test_init.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms integration.""" + from unittest.mock import MagicMock, patch from aiomodernforms import ModernFormsConnectionError diff --git a/tests/components/modern_forms/test_light.py b/tests/components/modern_forms/test_light.py index 080290944b2..42415214116 100644 --- a/tests/components/modern_forms/test_light.py +++ b/tests/components/modern_forms/test_light.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms light platform.""" + from unittest.mock import patch from aiomodernforms import ModernFormsConnectionError diff --git a/tests/components/modern_forms/test_sensor.py b/tests/components/modern_forms/test_sensor.py index dc075181860..9058808443e 100644 --- a/tests/components/modern_forms/test_sensor.py +++ b/tests/components/modern_forms/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms sensor platform.""" + from datetime import datetime from homeassistant.components.sensor import SensorDeviceClass diff --git a/tests/components/modern_forms/test_switch.py b/tests/components/modern_forms/test_switch.py index 0a1b75f1df9..42a52375c3c 100644 --- a/tests/components/modern_forms/test_switch.py +++ b/tests/components/modern_forms/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Modern Forms switch platform.""" + from unittest.mock import patch from aiomodernforms import ModernFormsConnectionError diff --git a/tests/components/moehlenhoff_alpha2/test_config_flow.py b/tests/components/moehlenhoff_alpha2/test_config_flow.py index 7123400365e..f3b37792287 100644 --- a/tests/components/moehlenhoff_alpha2/test_config_flow.py +++ b/tests/components/moehlenhoff_alpha2/test_config_flow.py @@ -1,4 +1,5 @@ """Test the moehlenhoff_alpha2 config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/monoprice/test_config_flow.py b/tests/components/monoprice/test_config_flow.py index 70087910e55..ae9ffea921c 100644 --- a/tests/components/monoprice/test_config_flow.py +++ b/tests/components/monoprice/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Monoprice 6-Zone Amplifier config flow.""" + from unittest.mock import patch from serial import SerialException diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index 36577c259d0..a0afd37f3b2 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -1,4 +1,5 @@ """The tests for Monoprice Media player platform.""" + from collections import defaultdict from unittest.mock import patch diff --git a/tests/components/moon/conftest.py b/tests/components/moon/conftest.py index 5c8157f257d..57e957077ab 100644 --- a/tests/components/moon/conftest.py +++ b/tests/components/moon/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Moon integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/moon/test_config_flow.py b/tests/components/moon/test_config_flow.py index cd2ab94fefc..8fbab51f5a2 100644 --- a/tests/components/moon/test_config_flow.py +++ b/tests/components/moon/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Moon config flow.""" + from unittest.mock import MagicMock from homeassistant.components.moon.const import DOMAIN diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py index 38af8dcb912..b3d5eb03428 100644 --- a/tests/components/moon/test_sensor.py +++ b/tests/components/moon/test_sensor.py @@ -1,4 +1,5 @@ """The test for the moon sensor platform.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/mopeka/test_config_flow.py b/tests/components/mopeka/test_config_flow.py index 67fc9fc37ed..8de1fd81add 100644 --- a/tests/components/mopeka/test_config_flow.py +++ b/tests/components/mopeka/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Mopeka config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/mopeka/test_sensor.py b/tests/components/mopeka/test_sensor.py index 626aa44efd4..47e436bad9a 100644 --- a/tests/components/mopeka/test_sensor.py +++ b/tests/components/mopeka/test_sensor.py @@ -1,4 +1,5 @@ """Test the Mopeka sensors.""" + from homeassistant.components.mopeka.const import DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS from homeassistant.const import ( diff --git a/tests/components/motion_blinds/test_gateway.py b/tests/components/motion_blinds/test_gateway.py index 4166f741c4a..3258790b940 100644 --- a/tests/components/motion_blinds/test_gateway.py +++ b/tests/components/motion_blinds/test_gateway.py @@ -1,4 +1,5 @@ """Test the Motionblinds config flow.""" + from unittest.mock import Mock from motionblinds import DEVICE_TYPES_WIFI, BlindType diff --git a/tests/components/motioneye/test_config_flow.py b/tests/components/motioneye/test_config_flow.py index 5cb6244010e..a5b2763182c 100644 --- a/tests/components/motioneye/test_config_flow.py +++ b/tests/components/motioneye/test_config_flow.py @@ -1,4 +1,5 @@ """Test the motionEye config flow.""" + from unittest.mock import AsyncMock, patch from motioneye_client.client import ( diff --git a/tests/components/motionmount/conftest.py b/tests/components/motionmount/conftest.py index 8a838dac83c..f0b8e2f7df7 100644 --- a/tests/components/motionmount/conftest.py +++ b/tests/components/motionmount/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Vogel's MotionMount integration tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 5552457c213..e0c781701ab 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -1,4 +1,5 @@ """The tests for mqtt camera component.""" + from base64 import b64encode from http import HTTPStatus import json diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 54b0f7f3506..a25a670fa34 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1,4 +1,5 @@ """Common test objects.""" + from collections.abc import Iterable from contextlib import suppress import copy diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index c8ffe7909eb..329d6077300 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -1,4 +1,5 @@ """Test config flow.""" + from collections.abc import Generator, Iterator from contextlib import contextmanager from pathlib import Path diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 3c3349d7f90..793e47f1a17 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1,4 +1,5 @@ """The tests for the MQTT cover platform.""" + from copy import deepcopy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_device_tracker.py b/tests/components/mqtt/test_device_tracker.py index bbed85c2e75..1d975b06f75 100644 --- a/tests/components/mqtt/test_device_tracker.py +++ b/tests/components/mqtt/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the MQTT device_tracker platform.""" + from datetime import UTC, datetime from unittest.mock import patch diff --git a/tests/components/mqtt/test_image.py b/tests/components/mqtt/test_image.py index 5a87f06653b..aa17b878e1c 100644 --- a/tests/components/mqtt/test_image.py +++ b/tests/components/mqtt/test_image.py @@ -1,4 +1,5 @@ """The tests for mqtt image component.""" + from base64 import b64encode from http import HTTPStatus import json diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index 082d328c17f..5a794f6082e 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -1,4 +1,5 @@ """The tests for the MQTT lock platform.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_select.py b/tests/components/mqtt/test_select.py index 5a17e7fc999..b50843951b1 100644 --- a/tests/components/mqtt/test_select.py +++ b/tests/components/mqtt/test_select.py @@ -1,4 +1,5 @@ """The tests for mqtt select component.""" + from collections.abc import Generator import copy import json diff --git a/tests/components/mqtt/test_subscription.py b/tests/components/mqtt/test_subscription.py index fe8c9fb6101..5196c58192d 100644 --- a/tests/components/mqtt/test_subscription.py +++ b/tests/components/mqtt/test_subscription.py @@ -1,4 +1,5 @@ """The tests for the MQTT subscription component.""" + from unittest.mock import ANY, patch import pytest diff --git a/tests/components/mqtt/test_tag.py b/tests/components/mqtt/test_tag.py index 7f5a477e62a..d374d816a0d 100644 --- a/tests/components/mqtt/test_tag.py +++ b/tests/components/mqtt/test_tag.py @@ -1,4 +1,5 @@ """The tests for MQTT tag scanner.""" + from collections.abc import Generator import copy import json diff --git a/tests/components/mqtt/test_text.py b/tests/components/mqtt/test_text.py index 265f4afcb90..8966ea9de56 100644 --- a/tests/components/mqtt/test_text.py +++ b/tests/components/mqtt/test_text.py @@ -1,4 +1,5 @@ """The tests for the MQTT text platform.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 97ded1f2294..756b8d022e0 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the MQTT automation.""" + from unittest.mock import ANY, patch import pytest diff --git a/tests/components/mqtt/test_vacuum.py b/tests/components/mqtt/test_vacuum.py index f48b0b1b375..a862ba3143d 100644 --- a/tests/components/mqtt/test_vacuum.py +++ b/tests/components/mqtt/test_vacuum.py @@ -1,4 +1,5 @@ """The tests for the State vacuum Mqtt platform.""" + from copy import deepcopy import json from typing import Any diff --git a/tests/components/mqtt/test_valve.py b/tests/components/mqtt/test_valve.py index 6a10a038da7..bc0676f5aa4 100644 --- a/tests/components/mqtt/test_valve.py +++ b/tests/components/mqtt/test_valve.py @@ -1,4 +1,5 @@ """The tests for the MQTT valve platform.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt_json/test_device_tracker.py b/tests/components/mqtt_json/test_device_tracker.py index 8423ccd8da2..91f40e2e4df 100644 --- a/tests/components/mqtt_json/test_device_tracker.py +++ b/tests/components/mqtt_json/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the JSON MQTT device tracker platform.""" + from collections.abc import Generator import json import logging diff --git a/tests/components/mqtt_statestream/test_init.py b/tests/components/mqtt_statestream/test_init.py index c9e0334d9d9..d8abb7158bd 100644 --- a/tests/components/mqtt_statestream/test_init.py +++ b/tests/components/mqtt_statestream/test_init.py @@ -1,4 +1,5 @@ """The tests for the MQTT statestream component.""" + from unittest.mock import ANY, call import pytest diff --git a/tests/components/mullvad/test_config_flow.py b/tests/components/mullvad/test_config_flow.py index 46dccafa0a6..2154513d6d1 100644 --- a/tests/components/mullvad/test_config_flow.py +++ b/tests/components/mullvad/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Mullvad config flow.""" + from unittest.mock import patch from mullvad_api import MullvadAPIError diff --git a/tests/components/mutesync/test_config_flow.py b/tests/components/mutesync/test_config_flow.py index bc2c739f15a..18cc7e68aa4 100644 --- a/tests/components/mutesync/test_config_flow.py +++ b/tests/components/mutesync/test_config_flow.py @@ -1,4 +1,5 @@ """Test the mütesync config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/my/test_init.py b/tests/components/my/test_init.py index 7e41ff970e5..0bfb2b5b452 100644 --- a/tests/components/my/test_init.py +++ b/tests/components/my/test_init.py @@ -1,4 +1,5 @@ """Test the my init.""" + from unittest import mock from homeassistant.components.my import URL_PATH diff --git a/tests/components/mysensors/conftest.py b/tests/components/mysensors/conftest.py index 6df50f04ae2..a892730ef6a 100644 --- a/tests/components/mysensors/conftest.py +++ b/tests/components/mysensors/conftest.py @@ -1,4 +1,5 @@ """Provide common mysensors fixtures.""" + from __future__ import annotations from collections.abc import AsyncGenerator, Callable, Generator diff --git a/tests/components/mysensors/test_binary_sensor.py b/tests/components/mysensors/test_binary_sensor.py index a6dce9c78b9..cb63a08d8a6 100644 --- a/tests/components/mysensors/test_binary_sensor.py +++ b/tests/components/mysensors/test_binary_sensor.py @@ -1,4 +1,5 @@ """Provide tests for mysensors binary sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_climate.py b/tests/components/mysensors/test_climate.py index 6c386af6fd6..959f92ff512 100644 --- a/tests/components/mysensors/test_climate.py +++ b/tests/components/mysensors/test_climate.py @@ -1,4 +1,5 @@ """Provide tests for mysensors climate platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_config_flow.py b/tests/components/mysensors/test_config_flow.py index bff13d1604f..2506e350e89 100644 --- a/tests/components/mysensors/test_config_flow.py +++ b/tests/components/mysensors/test_config_flow.py @@ -1,4 +1,5 @@ """Test the MySensors config flow.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/mysensors/test_cover.py b/tests/components/mysensors/test_cover.py index 7d0a098fc0a..e056bff80fa 100644 --- a/tests/components/mysensors/test_cover.py +++ b/tests/components/mysensors/test_cover.py @@ -1,4 +1,5 @@ """Provide tests for mysensors cover platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_device_tracker.py b/tests/components/mysensors/test_device_tracker.py index 4d6e638e665..a0838d980a1 100644 --- a/tests/components/mysensors/test_device_tracker.py +++ b/tests/components/mysensors/test_device_tracker.py @@ -1,4 +1,5 @@ """Provide tests for mysensors device tracker platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_gateway.py b/tests/components/mysensors/test_gateway.py index 81207307b6c..5a840f76bb2 100644 --- a/tests/components/mysensors/test_gateway.py +++ b/tests/components/mysensors/test_gateway.py @@ -1,4 +1,5 @@ """Test function in gateway.py.""" + from unittest.mock import patch import pytest diff --git a/tests/components/mysensors/test_init.py b/tests/components/mysensors/test_init.py index fd61e27a663..8c1eeb64b70 100644 --- a/tests/components/mysensors/test_init.py +++ b/tests/components/mysensors/test_init.py @@ -1,4 +1,5 @@ """Test function in __init__.py.""" + from __future__ import annotations from mysensors import BaseSyncGateway diff --git a/tests/components/mysensors/test_light.py b/tests/components/mysensors/test_light.py index 9696c6e622a..ccff7238d3f 100644 --- a/tests/components/mysensors/test_light.py +++ b/tests/components/mysensors/test_light.py @@ -1,4 +1,5 @@ """Provide tests for mysensors light platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_remote.py b/tests/components/mysensors/test_remote.py index 586e2e2d048..a6ff4963e82 100644 --- a/tests/components/mysensors/test_remote.py +++ b/tests/components/mysensors/test_remote.py @@ -1,4 +1,5 @@ """Provide tests for mysensors remote platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_sensor.py b/tests/components/mysensors/test_sensor.py index d80fddea9e3..9ebf71dd7b3 100644 --- a/tests/components/mysensors/test_sensor.py +++ b/tests/components/mysensors/test_sensor.py @@ -1,4 +1,5 @@ """Provide tests for mysensors sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_switch.py b/tests/components/mysensors/test_switch.py index 49786768ff7..b67c280a10d 100644 --- a/tests/components/mysensors/test_switch.py +++ b/tests/components/mysensors/test_switch.py @@ -1,4 +1,5 @@ """Provide tests for mysensors switch platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mysensors/test_text.py b/tests/components/mysensors/test_text.py index 7490cfddfbf..9905286bb8a 100644 --- a/tests/components/mysensors/test_text.py +++ b/tests/components/mysensors/test_text.py @@ -1,4 +1,5 @@ """Provide tests for mysensors text platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/mystrom/test_config_flow.py b/tests/components/mystrom/test_config_flow.py index 9459519de75..b64b4edf547 100644 --- a/tests/components/mystrom/test_config_flow.py +++ b/tests/components/mystrom/test_config_flow.py @@ -1,4 +1,5 @@ """Test the myStrom config flow.""" + from unittest.mock import AsyncMock, patch from pymystrom.exceptions import MyStromConnectionError diff --git a/tests/components/mystrom/test_init.py b/tests/components/mystrom/test_init.py index 4100a270e0a..f849fec7477 100644 --- a/tests/components/mystrom/test_init.py +++ b/tests/components/mystrom/test_init.py @@ -1,4 +1,5 @@ """Test the myStrom init.""" + from unittest.mock import AsyncMock, PropertyMock, patch from pymystrom.exceptions import MyStromConnectionError diff --git a/tests/components/myuplink/conftest.py b/tests/components/myuplink/conftest.py index e6380b101cb..87f8b37726b 100644 --- a/tests/components/myuplink/conftest.py +++ b/tests/components/myuplink/conftest.py @@ -1,4 +1,5 @@ """Test helpers for myuplink.""" + from collections.abc import AsyncGenerator, Generator import time from typing import Any diff --git a/tests/components/nam/test_button.py b/tests/components/nam/test_button.py index ab4e46975f9..32c69c91202 100644 --- a/tests/components/nam/test_button.py +++ b/tests/components/nam/test_button.py @@ -1,4 +1,5 @@ """Test button of Nettigo Air Monitor integration.""" + from unittest.mock import patch from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass diff --git a/tests/components/nam/test_config_flow.py b/tests/components/nam/test_config_flow.py index 9319eddba81..9081bfbb7b5 100644 --- a/tests/components/nam/test_config_flow.py +++ b/tests/components/nam/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Nettigo Air Monitor config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/nam/test_init.py b/tests/components/nam/test_init.py index 63034d5b075..372c6fb8e66 100644 --- a/tests/components/nam/test_init.py +++ b/tests/components/nam/test_init.py @@ -1,4 +1,5 @@ """Test init of Nettigo Air Monitor integration.""" + from unittest.mock import patch from nettigo_air_monitor import ApiError, AuthFailedError diff --git a/tests/components/nam/test_sensor.py b/tests/components/nam/test_sensor.py index 80eedd5b1a3..5a8f67c4378 100644 --- a/tests/components/nam/test_sensor.py +++ b/tests/components/nam/test_sensor.py @@ -1,4 +1,5 @@ """Test sensor of Nettigo Air Monitor integration.""" + from datetime import timedelta from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/namecheapdns/test_init.py b/tests/components/namecheapdns/test_init.py index 9776d196a11..fdd9081331f 100644 --- a/tests/components/namecheapdns/test_init.py +++ b/tests/components/namecheapdns/test_init.py @@ -1,4 +1,5 @@ """Test the NamecheapDNS component.""" + from datetime import timedelta import pytest diff --git a/tests/components/nanoleaf/test_config_flow.py b/tests/components/nanoleaf/test_config_flow.py index 2fce4e55bbc..2f3620a6a12 100644 --- a/tests/components/nanoleaf/test_config_flow.py +++ b/tests/components/nanoleaf/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Nanoleaf config flow.""" + from __future__ import annotations from ipaddress import ip_address diff --git a/tests/components/neato/test_config_flow.py b/tests/components/neato/test_config_flow.py index 191721c2e74..34b1f37f56f 100644 --- a/tests/components/neato/test_config_flow.py +++ b/tests/components/neato/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Neato Botvac config flow.""" + from unittest.mock import patch from pybotvac.neato import Neato diff --git a/tests/components/ness_alarm/test_init.py b/tests/components/ness_alarm/test_init.py index 5bf48e0667e..fb003d253de 100644 --- a/tests/components/ness_alarm/test_init.py +++ b/tests/components/ness_alarm/test_init.py @@ -1,4 +1,5 @@ """Tests for the ness_alarm component.""" + from unittest.mock import MagicMock, patch from nessclient import ArmingMode, ArmingState diff --git a/tests/components/nest/conftest.py b/tests/components/nest/conftest.py index 6057db382b5..68c77cb7635 100644 --- a/tests/components/nest/conftest.py +++ b/tests/components/nest/conftest.py @@ -1,4 +1,5 @@ """Common libraries for test setup.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/nest/test_config_flow.py b/tests/components/nest/test_config_flow.py index 39ef42477fb..8d2a9e96d63 100644 --- a/tests/components/nest/test_config_flow.py +++ b/tests/components/nest/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Nest Device Access config flow.""" + from __future__ import annotations from http import HTTPStatus diff --git a/tests/components/nest/test_device_trigger.py b/tests/components/nest/test_device_trigger.py index 381cddb2817..51cf4254614 100644 --- a/tests/components/nest/test_device_trigger.py +++ b/tests/components/nest/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Nest device triggers.""" + from google_nest_sdm.event import EventMessage import pytest from pytest_unordered import unordered diff --git a/tests/components/nest/test_diagnostics.py b/tests/components/nest/test_diagnostics.py index c9b5f2f0de1..5fb33ff4a47 100644 --- a/tests/components/nest/test_diagnostics.py +++ b/tests/components/nest/test_diagnostics.py @@ -1,4 +1,5 @@ """Test nest diagnostics.""" + from unittest.mock import patch from google_nest_sdm.exceptions import SubscriberException diff --git a/tests/components/nest/test_events.py b/tests/components/nest/test_events.py index 43ed3d489b5..1bd01f699cf 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -3,6 +3,7 @@ These tests fake out the subscriber/devicemanager, and are not using a real pubsub subscriber. """ + from __future__ import annotations from collections.abc import Mapping diff --git a/tests/components/nest/test_media_source.py b/tests/components/nest/test_media_source.py index a1c62799585..88bef222c89 100644 --- a/tests/components/nest/test_media_source.py +++ b/tests/components/nest/test_media_source.py @@ -3,6 +3,7 @@ These tests simulate recent camera events received by the subscriber exposed as media in the media source. """ + from collections.abc import Generator import datetime from http import HTTPStatus diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index 1bb2ab00d32..ffaddd73bb3 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for Netatmo.""" + from contextlib import contextmanager import json from typing import Any diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index a21bb8aebe7..d2e6c1fdc88 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -1,4 +1,5 @@ """Provide common Netatmo fixtures.""" + from time import time from unittest.mock import AsyncMock, patch diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index e845ca08f06..12641438a3a 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -1,4 +1,5 @@ """The tests for Netatmo camera.""" + from datetime import timedelta from typing import Any from unittest.mock import AsyncMock, patch diff --git a/tests/components/netatmo/test_climate.py b/tests/components/netatmo/test_climate.py index e4b8c298c26..b25f78b5e2f 100644 --- a/tests/components/netatmo/test_climate.py +++ b/tests/components/netatmo/test_climate.py @@ -1,4 +1,5 @@ """The tests for the Netatmo climate platform.""" + from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/netatmo/test_config_flow.py b/tests/components/netatmo/test_config_flow.py index afa9ed02645..7866e448734 100644 --- a/tests/components/netatmo/test_config_flow.py +++ b/tests/components/netatmo/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Netatmo config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/netatmo/test_cover.py b/tests/components/netatmo/test_cover.py index 5a7c33fc6ef..509c1de736e 100644 --- a/tests/components/netatmo/test_cover.py +++ b/tests/components/netatmo/test_cover.py @@ -1,4 +1,5 @@ """The tests for Netatmo cover.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/netatmo/test_diagnostics.py b/tests/components/netatmo/test_diagnostics.py index 2d13e36150d..7c781e7522c 100644 --- a/tests/components/netatmo/test_diagnostics.py +++ b/tests/components/netatmo/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Netatmo diagnostics.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/netatmo/test_fan.py b/tests/components/netatmo/test_fan.py index 72dd579af67..989ea1ac364 100644 --- a/tests/components/netatmo/test_fan.py +++ b/tests/components/netatmo/test_fan.py @@ -1,4 +1,5 @@ """The tests for Netatmo fan.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 2fd9867262d..b55d501e2c2 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -1,4 +1,5 @@ """The tests for Netatmo component.""" + from datetime import timedelta from time import time from unittest.mock import AsyncMock, patch diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py index 1c83f9c6772..5ae368522a3 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -1,4 +1,5 @@ """The tests for Netatmo light.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/netatmo/test_select.py b/tests/components/netatmo/test_select.py index 055ea355b48..274113405f6 100644 --- a/tests/components/netatmo/test_select.py +++ b/tests/components/netatmo/test_select.py @@ -1,4 +1,5 @@ """The tests for the Netatmo climate platform.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/netatmo/test_sensor.py b/tests/components/netatmo/test_sensor.py index 8829e374f29..073b9faf485 100644 --- a/tests/components/netatmo/test_sensor.py +++ b/tests/components/netatmo/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Netatmo sensor platform.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/netatmo/test_switch.py b/tests/components/netatmo/test_switch.py index f5ea08ec1fa..dd82fad3d08 100644 --- a/tests/components/netatmo/test_switch.py +++ b/tests/components/netatmo/test_switch.py @@ -1,4 +1,5 @@ """The tests for Netatmo switch.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/netgear/test_config_flow.py b/tests/components/netgear/test_config_flow.py index 37787024fb6..34df3a50c1b 100644 --- a/tests/components/netgear/test_config_flow.py +++ b/tests/components/netgear/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Netgear config flow.""" + from unittest.mock import Mock, patch from pynetgear import DEFAULT_USER diff --git a/tests/components/netgear_lte/conftest.py b/tests/components/netgear_lte/conftest.py index e32034d660b..ff7f30ced5f 100644 --- a/tests/components/netgear_lte/conftest.py +++ b/tests/components/netgear_lte/conftest.py @@ -1,4 +1,5 @@ """Configure pytest for Netgear LTE tests.""" + from __future__ import annotations from aiohttp.client_exceptions import ClientError diff --git a/tests/components/netgear_lte/test_binary_sensor.py b/tests/components/netgear_lte/test_binary_sensor.py index 660b7dd4fdf..5fbbcfe06f6 100644 --- a/tests/components/netgear_lte/test_binary_sensor.py +++ b/tests/components/netgear_lte/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for Netgear LTE binary sensor platform.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN diff --git a/tests/components/netgear_lte/test_config_flow.py b/tests/components/netgear_lte/test_config_flow.py index 97a624a14e7..08ae2ce0889 100644 --- a/tests/components/netgear_lte/test_config_flow.py +++ b/tests/components/netgear_lte/test_config_flow.py @@ -1,4 +1,5 @@ """Test Netgear LTE config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/netgear_lte/test_init.py b/tests/components/netgear_lte/test_init.py index 9d9b43f5a16..6a71e6d601c 100644 --- a/tests/components/netgear_lte/test_init.py +++ b/tests/components/netgear_lte/test_init.py @@ -1,4 +1,5 @@ """Test Netgear LTE integration.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.components.netgear_lte.const import DOMAIN diff --git a/tests/components/netgear_lte/test_notify.py b/tests/components/netgear_lte/test_notify.py index 12d906138c3..9a55e7a7ad6 100644 --- a/tests/components/netgear_lte/test_notify.py +++ b/tests/components/netgear_lte/test_notify.py @@ -1,4 +1,5 @@ """The tests for the Netgear LTE notify platform.""" + from unittest.mock import patch from homeassistant.components.notify import ( diff --git a/tests/components/netgear_lte/test_sensor.py b/tests/components/netgear_lte/test_sensor.py index 37f6538fe6a..075c3db3b08 100644 --- a/tests/components/netgear_lte/test_sensor.py +++ b/tests/components/netgear_lte/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Netgear LTE sensor platform.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.components.netgear_lte.const import DOMAIN diff --git a/tests/components/netgear_lte/test_services.py b/tests/components/netgear_lte/test_services.py index 5c5c33be980..58e57cf2039 100644 --- a/tests/components/netgear_lte/test_services.py +++ b/tests/components/netgear_lte/test_services.py @@ -1,4 +1,5 @@ """Services tests for the Netgear LTE integration.""" + from unittest.mock import patch from homeassistant.components.netgear_lte.const import DOMAIN diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index 880caecc138..2e0d2eb585b 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -1,4 +1,5 @@ """Test the Network Configuration.""" + from ipaddress import IPv4Address from typing import Any from unittest.mock import MagicMock, Mock, patch diff --git a/tests/components/nexia/test_binary_sensor.py b/tests/components/nexia/test_binary_sensor.py index f59e968d634..e175afe6214 100644 --- a/tests/components/nexia/test_binary_sensor.py +++ b/tests/components/nexia/test_binary_sensor.py @@ -1,4 +1,5 @@ """The binary_sensor tests for the nexia platform.""" + from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant diff --git a/tests/components/nexia/test_climate.py b/tests/components/nexia/test_climate.py index 5553965b418..900838547f2 100644 --- a/tests/components/nexia/test_climate.py +++ b/tests/components/nexia/test_climate.py @@ -1,4 +1,5 @@ """The lock tests for the august platform.""" + from homeassistant.components.climate import HVACMode from homeassistant.core import HomeAssistant diff --git a/tests/components/nexia/test_config_flow.py b/tests/components/nexia/test_config_flow.py index c07b5c8540e..7ed5ecec675 100644 --- a/tests/components/nexia/test_config_flow.py +++ b/tests/components/nexia/test_config_flow.py @@ -1,4 +1,5 @@ """Test the nexia config flow.""" + from unittest.mock import MagicMock, patch import aiohttp diff --git a/tests/components/nexia/test_diagnostics.py b/tests/components/nexia/test_diagnostics.py index 9f8f7f05a8d..ff9696d1567 100644 --- a/tests/components/nexia/test_diagnostics.py +++ b/tests/components/nexia/test_diagnostics.py @@ -1,4 +1,5 @@ """Test august diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/nexia/test_scene.py b/tests/components/nexia/test_scene.py index e01d843c298..20f214fff27 100644 --- a/tests/components/nexia/test_scene.py +++ b/tests/components/nexia/test_scene.py @@ -1,4 +1,5 @@ """The scene tests for the nexia platform.""" + from homeassistant.core import HomeAssistant from .util import async_init_integration diff --git a/tests/components/nexia/test_sensor.py b/tests/components/nexia/test_sensor.py index 23a92af71c8..1f595da43d1 100644 --- a/tests/components/nexia/test_sensor.py +++ b/tests/components/nexia/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the nexia platform.""" + from homeassistant.const import PERCENTAGE, UnitOfTemperature from homeassistant.core import HomeAssistant diff --git a/tests/components/nexia/test_switch.py b/tests/components/nexia/test_switch.py index 0ddef1c807c..821d939bac5 100644 --- a/tests/components/nexia/test_switch.py +++ b/tests/components/nexia/test_switch.py @@ -1,4 +1,5 @@ """The switch tests for the nexia platform.""" + from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant diff --git a/tests/components/nexia/util.py b/tests/components/nexia/util.py index d47e3fd3d6a..f49058e0033 100644 --- a/tests/components/nexia/util.py +++ b/tests/components/nexia/util.py @@ -1,4 +1,5 @@ """Tests for the nexia integration.""" + from unittest.mock import patch import uuid diff --git a/tests/components/nextbus/conftest.py b/tests/components/nextbus/conftest.py index 4f6a6f22270..84445905c2e 100644 --- a/tests/components/nextbus/conftest.py +++ b/tests/components/nextbus/conftest.py @@ -1,4 +1,5 @@ """Test helpers for NextBus tests.""" + from typing import Any from unittest.mock import MagicMock diff --git a/tests/components/nextbus/test_config_flow.py b/tests/components/nextbus/test_config_flow.py index 0b67f817eb2..6bb8d9e2bfe 100644 --- a/tests/components/nextbus/test_config_flow.py +++ b/tests/components/nextbus/test_config_flow.py @@ -1,4 +1,5 @@ """Test the NextBus config flow.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index 92da27783bc..42c4c5bdb44 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the nexbus sensor component.""" + from collections.abc import Generator from copy import deepcopy from unittest.mock import MagicMock, patch diff --git a/tests/components/nextbus/test_util.py b/tests/components/nextbus/test_util.py index 798171464e6..2d19c1fe777 100644 --- a/tests/components/nextbus/test_util.py +++ b/tests/components/nextbus/test_util.py @@ -1,4 +1,5 @@ """Test NextBus util functions.""" + from typing import Any import pytest diff --git a/tests/components/nextcloud/test_config_flow.py b/tests/components/nextcloud/test_config_flow.py index 94c03758621..32c688fb8c2 100644 --- a/tests/components/nextcloud/test_config_flow.py +++ b/tests/components/nextcloud/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Nextcloud config flow.""" + from unittest.mock import Mock, patch from nextcloudmonitor import ( diff --git a/tests/components/nextdns/test_binary_sensor.py b/tests/components/nextdns/test_binary_sensor.py index 08d1f08f5d1..484b4e99aad 100644 --- a/tests/components/nextdns/test_binary_sensor.py +++ b/tests/components/nextdns/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test binary sensor of NextDNS integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/nextdns/test_button.py b/tests/components/nextdns/test_button.py index fabf87f6462..66c83169813 100644 --- a/tests/components/nextdns/test_button.py +++ b/tests/components/nextdns/test_button.py @@ -1,4 +1,5 @@ """Test button of NextDNS integration.""" + from unittest.mock import patch from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN diff --git a/tests/components/nextdns/test_config_flow.py b/tests/components/nextdns/test_config_flow.py index da7fa131543..e1b669c17e8 100644 --- a/tests/components/nextdns/test_config_flow.py +++ b/tests/components/nextdns/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the NextDNS config flow.""" + from unittest.mock import patch from nextdns import ApiError, InvalidApiKeyError diff --git a/tests/components/nextdns/test_init.py b/tests/components/nextdns/test_init.py index fb9ea74509e..f7b85bb8a54 100644 --- a/tests/components/nextdns/test_init.py +++ b/tests/components/nextdns/test_init.py @@ -1,4 +1,5 @@ """Test init of NextDNS integration.""" + from unittest.mock import patch from nextdns import ApiError diff --git a/tests/components/nextdns/test_sensor.py b/tests/components/nextdns/test_sensor.py index e500ff3c626..de303fd6cad 100644 --- a/tests/components/nextdns/test_sensor.py +++ b/tests/components/nextdns/test_sensor.py @@ -1,4 +1,5 @@ """Test sensor of NextDNS integration.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/nextdns/test_switch.py b/tests/components/nextdns/test_switch.py index ef87b51e98e..7e1d592c1f4 100644 --- a/tests/components/nextdns/test_switch.py +++ b/tests/components/nextdns/test_switch.py @@ -1,4 +1,5 @@ """Test switch of NextDNS integration.""" + from datetime import timedelta from unittest.mock import Mock, patch diff --git a/tests/components/nfandroidtv/test_config_flow.py b/tests/components/nfandroidtv/test_config_flow.py index 89ea23e0f0c..04fcf699513 100644 --- a/tests/components/nfandroidtv/test_config_flow.py +++ b/tests/components/nfandroidtv/test_config_flow.py @@ -1,4 +1,5 @@ """Test NFAndroidTV config flow.""" + from unittest.mock import patch from notifications_android_tv.notifications import ConnectError diff --git a/tests/components/nibe_heatpump/conftest.py b/tests/components/nibe_heatpump/conftest.py index a5eb5fb012d..2d88e1b0fd2 100644 --- a/tests/components/nibe_heatpump/conftest.py +++ b/tests/components/nibe_heatpump/conftest.py @@ -1,4 +1,5 @@ """Test configuration for Nibe Heat Pump.""" + from collections.abc import Generator from contextlib import ExitStack from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/nibe_heatpump/test_button.py b/tests/components/nibe_heatpump/test_button.py index d150d3f2d38..e660340c549 100644 --- a/tests/components/nibe_heatpump/test_button.py +++ b/tests/components/nibe_heatpump/test_button.py @@ -1,4 +1,5 @@ """Test the Nibe Heat Pump config flow.""" + from typing import Any from unittest.mock import AsyncMock, patch diff --git a/tests/components/nibe_heatpump/test_climate.py b/tests/components/nibe_heatpump/test_climate.py index 2b3fe5d8c0e..3a468e51e83 100644 --- a/tests/components/nibe_heatpump/test_climate.py +++ b/tests/components/nibe_heatpump/test_climate.py @@ -1,4 +1,5 @@ """Test the Nibe Heat Pump config flow.""" + from typing import Any from unittest.mock import call, patch diff --git a/tests/components/nibe_heatpump/test_config_flow.py b/tests/components/nibe_heatpump/test_config_flow.py index 9b03159af2f..2e615aa55f6 100644 --- a/tests/components/nibe_heatpump/test_config_flow.py +++ b/tests/components/nibe_heatpump/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Nibe Heat Pump config flow.""" + from typing import Any from unittest.mock import AsyncMock, Mock diff --git a/tests/components/nibe_heatpump/test_number.py b/tests/components/nibe_heatpump/test_number.py index 5c4d7f4341b..99f8ab22b6c 100644 --- a/tests/components/nibe_heatpump/test_number.py +++ b/tests/components/nibe_heatpump/test_number.py @@ -1,4 +1,5 @@ """Test the Nibe Heat Pump config flow.""" + from typing import Any from unittest.mock import AsyncMock, patch diff --git a/tests/components/nightscout/test_config_flow.py b/tests/components/nightscout/test_config_flow.py index b8805b76691..43f238ffa13 100644 --- a/tests/components/nightscout/test_config_flow.py +++ b/tests/components/nightscout/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Nightscout config flow.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/nightscout/test_init.py b/tests/components/nightscout/test_init.py index f3e7f8cbfd0..eb4d5a068c9 100644 --- a/tests/components/nightscout/test_init.py +++ b/tests/components/nightscout/test_init.py @@ -1,4 +1,5 @@ """Test the Nightscout config flow.""" + from unittest.mock import patch from aiohttp import ClientError diff --git a/tests/components/nightscout/test_sensor.py b/tests/components/nightscout/test_sensor.py index f224fec43a3..dbc7e143c1a 100644 --- a/tests/components/nightscout/test_sensor.py +++ b/tests/components/nightscout/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the Nightscout platform.""" + from homeassistant.components.nightscout.const import ( ATTR_DELTA, ATTR_DEVICE, diff --git a/tests/components/nina/test_binary_sensor.py b/tests/components/nina/test_binary_sensor.py index 8532415c6b1..380d16f5101 100644 --- a/tests/components/nina/test_binary_sensor.py +++ b/tests/components/nina/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Nina binary sensor.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/nina/test_config_flow.py b/tests/components/nina/test_config_flow.py index aad24691f42..10de928feb8 100644 --- a/tests/components/nina/test_config_flow.py +++ b/tests/components/nina/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Nina config flow.""" + from __future__ import annotations from copy import deepcopy diff --git a/tests/components/nina/test_init.py b/tests/components/nina/test_init.py index da73c8d8711..d7c312a8514 100644 --- a/tests/components/nina/test_init.py +++ b/tests/components/nina/test_init.py @@ -1,4 +1,5 @@ """Test the Nina init file.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/nmap_tracker/test_config_flow.py b/tests/components/nmap_tracker/test_config_flow.py index 95c944449de..ccbdc112e46 100644 --- a/tests/components/nmap_tracker/test_config_flow.py +++ b/tests/components/nmap_tracker/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Nmap Tracker config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/no_ip/test_init.py b/tests/components/no_ip/test_init.py index 40eb650242f..576a04c28a0 100644 --- a/tests/components/no_ip/test_init.py +++ b/tests/components/no_ip/test_init.py @@ -1,4 +1,5 @@ """Test the NO-IP component.""" + from datetime import timedelta import pytest diff --git a/tests/components/nobo_hub/test_config_flow.py b/tests/components/nobo_hub/test_config_flow.py index 5cfcee9cdbf..eed287abbb0 100644 --- a/tests/components/nobo_hub/test_config_flow.py +++ b/tests/components/nobo_hub/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Nobø Ecohub config flow.""" + from unittest.mock import PropertyMock, patch from homeassistant import config_entries diff --git a/tests/components/notify/common.py b/tests/components/notify/common.py index 0bfa3ba7a32..418de96d1aa 100644 --- a/tests/components/notify/common.py +++ b/tests/components/notify/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from homeassistant.components.notify import ( ATTR_DATA, ATTR_MESSAGE, diff --git a/tests/components/notify/test_persistent_notification.py b/tests/components/notify/test_persistent_notification.py index 580b2bdb614..bbf571b69ae 100644 --- a/tests/components/notify/test_persistent_notification.py +++ b/tests/components/notify/test_persistent_notification.py @@ -1,4 +1,5 @@ """The tests for the notify.persistent_notification service.""" + from homeassistant.components import notify import homeassistant.components.persistent_notification as pn from homeassistant.core import HomeAssistant diff --git a/tests/components/notify_events/test_init.py b/tests/components/notify_events/test_init.py index 85f196689a3..bc935b31772 100644 --- a/tests/components/notify_events/test_init.py +++ b/tests/components/notify_events/test_init.py @@ -1,4 +1,5 @@ """The tests for notify_events.""" + from homeassistant.components.notify_events.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/notify_events/test_notify.py b/tests/components/notify_events/test_notify.py index 8df26801f92..dbfc354404b 100644 --- a/tests/components/notify_events/test_notify.py +++ b/tests/components/notify_events/test_notify.py @@ -1,4 +1,5 @@ """The tests for notify_events.""" + from homeassistant.components.notify import ATTR_DATA, ATTR_MESSAGE, DOMAIN from homeassistant.components.notify_events.notify import ( ATTR_LEVEL, diff --git a/tests/components/notion/conftest.py b/tests/components/notion/conftest.py index 61a54ca58ba..0cded62eb31 100644 --- a/tests/components/notion/conftest.py +++ b/tests/components/notion/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for Notion tests.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/notion/test_config_flow.py b/tests/components/notion/test_config_flow.py index 646bd7a6e87..803d7481dba 100644 --- a/tests/components/notion/test_config_flow.py +++ b/tests/components/notion/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the Notion config flow.""" + from unittest.mock import AsyncMock, patch from aionotion.errors import InvalidCredentialsError, NotionError diff --git a/tests/components/notion/test_diagnostics.py b/tests/components/notion/test_diagnostics.py index f0ca64807e1..023b9369f03 100644 --- a/tests/components/notion/test_diagnostics.py +++ b/tests/components/notion/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Notion diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.components.notion import DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 3aa19cdafc2..3e3a5aa9001 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the NSW Fuel Station sensor platform.""" + from unittest.mock import patch from nsw_fuel import FuelCheckError diff --git a/tests/components/nuheat/mocks.py b/tests/components/nuheat/mocks.py index a5c2b403948..091734b8075 100644 --- a/tests/components/nuheat/mocks.py +++ b/tests/components/nuheat/mocks.py @@ -1,4 +1,5 @@ """The test for the NuHeat thermostat module.""" + from unittest.mock import MagicMock, Mock from nuheat.config import SCHEDULE_HOLD, SCHEDULE_RUN, SCHEDULE_TEMPORARY_HOLD diff --git a/tests/components/nuheat/test_climate.py b/tests/components/nuheat/test_climate.py index 7a0e21485c8..bc00df126e5 100644 --- a/tests/components/nuheat/test_climate.py +++ b/tests/components/nuheat/test_climate.py @@ -1,4 +1,5 @@ """The test for the NuHeat thermostat module.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/nuheat/test_config_flow.py b/tests/components/nuheat/test_config_flow.py index 64b7b56348e..37e2af0d307 100644 --- a/tests/components/nuheat/test_config_flow.py +++ b/tests/components/nuheat/test_config_flow.py @@ -1,4 +1,5 @@ """Test the NuHeat config flow.""" + from http import HTTPStatus from unittest.mock import MagicMock, patch diff --git a/tests/components/nuheat/test_init.py b/tests/components/nuheat/test_init.py index 0e1686dbfde..15829935dab 100644 --- a/tests/components/nuheat/test_init.py +++ b/tests/components/nuheat/test_init.py @@ -1,4 +1,5 @@ """NuHeat component tests.""" + from unittest.mock import patch from homeassistant.components.nuheat.const import DOMAIN diff --git a/tests/components/nuki/test_config_flow.py b/tests/components/nuki/test_config_flow.py index b5f1b65af71..cc0a9b67b21 100644 --- a/tests/components/nuki/test_config_flow.py +++ b/tests/components/nuki/test_config_flow.py @@ -1,4 +1,5 @@ """Test the nuki config flow.""" + from unittest.mock import patch from pynuki.bridge import InvalidCredentialsException diff --git a/tests/components/numato/numato_mock.py b/tests/components/numato/numato_mock.py index 1f8b24027de..04c10911faf 100644 --- a/tests/components/numato/numato_mock.py +++ b/tests/components/numato/numato_mock.py @@ -1,4 +1,5 @@ """Mockup for the numato component interface.""" + from numato_gpio import NumatoGpioError diff --git a/tests/components/numato/test_binary_sensor.py b/tests/components/numato/test_binary_sensor.py index 3aa6f027af2..6b45ef05a0d 100644 --- a/tests/components/numato/test_binary_sensor.py +++ b/tests/components/numato/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the numato binary_sensor platform.""" + from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery diff --git a/tests/components/numato/test_init.py b/tests/components/numato/test_init.py index 827d3daa737..1e84813df94 100644 --- a/tests/components/numato/test_init.py +++ b/tests/components/numato/test_init.py @@ -1,4 +1,5 @@ """Tests for the numato integration.""" + from numato_gpio import NumatoGpioError import pytest diff --git a/tests/components/numato/test_sensor.py b/tests/components/numato/test_sensor.py index e5e871ca9b5..30a9f174941 100644 --- a/tests/components/numato/test_sensor.py +++ b/tests/components/numato/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the numato sensor platform.""" + from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery diff --git a/tests/components/numato/test_switch.py b/tests/components/numato/test_switch.py index de57aba7bc7..e69b3481b1d 100644 --- a/tests/components/numato/test_switch.py +++ b/tests/components/numato/test_switch.py @@ -1,4 +1,5 @@ """Tests for the numato switch platform.""" + from homeassistant.components import switch from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/number/test_init.py b/tests/components/number/test_init.py index 279ffbfbbaa..8eb84e90e3e 100644 --- a/tests/components/number/test_init.py +++ b/tests/components/number/test_init.py @@ -1,4 +1,5 @@ """The tests for the Number component.""" + from collections.abc import Generator from typing import Any from unittest.mock import MagicMock diff --git a/tests/components/number/test_recorder.py b/tests/components/number/test_recorder.py index 635354b1176..9117124aac3 100644 --- a/tests/components/number/test_recorder.py +++ b/tests/components/number/test_recorder.py @@ -1,4 +1,5 @@ """The tests for number recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/number/test_websocket_api.py b/tests/components/number/test_websocket_api.py index 194f24dc9da..a405ef8c2fc 100644 --- a/tests/components/number/test_websocket_api.py +++ b/tests/components/number/test_websocket_api.py @@ -1,4 +1,5 @@ """Test the number websocket API.""" + from homeassistant.components.number.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index 46bc2bc2a64..6dca92d1294 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Network UPS Tools (NUT) config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/nut/test_device_action.py b/tests/components/nut/test_device_action.py index c15a2157343..8938de54457 100644 --- a/tests/components/nut/test_device_action.py +++ b/tests/components/nut/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Network UPS Tools (NUT) device actions.""" + from unittest.mock import MagicMock from pynut2.nut2 import PyNUTError diff --git a/tests/components/nut/test_init.py b/tests/components/nut/test_init.py index 698e182c4f7..2cee229897d 100644 --- a/tests/components/nut/test_init.py +++ b/tests/components/nut/test_init.py @@ -1,4 +1,5 @@ """Test init of Nut integration.""" + from unittest.mock import patch from homeassistant.components.nut.const import DOMAIN diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index 014e683b30c..be435fb6e6c 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the nut platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/nws/conftest.py b/tests/components/nws/conftest.py index 022211157b6..7ffde0c5731 100644 --- a/tests/components/nws/conftest.py +++ b/tests/components/nws/conftest.py @@ -1,4 +1,5 @@ """Fixtures for National Weather Service tests.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/nws/const.py b/tests/components/nws/const.py index 106b80998ac..e5fc9df909f 100644 --- a/tests/components/nws/const.py +++ b/tests/components/nws/const.py @@ -1,4 +1,5 @@ """Helpers for interacting with pynws.""" + from homeassistant.components.nws.const import CONF_STATION from homeassistant.components.weather import ( ATTR_CONDITION_LIGHTNING_RAINY, diff --git a/tests/components/nws/test_config_flow.py b/tests/components/nws/test_config_flow.py index 9c02139d67c..fe8017c55e1 100644 --- a/tests/components/nws/test_config_flow.py +++ b/tests/components/nws/test_config_flow.py @@ -1,4 +1,5 @@ """Test the National Weather Service (NWS) config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/nws/test_init.py b/tests/components/nws/test_init.py index cc4ce114cad..121da07a9ce 100644 --- a/tests/components/nws/test_init.py +++ b/tests/components/nws/test_init.py @@ -1,4 +1,5 @@ """Tests for init module.""" + from homeassistant.components.nws.const import DOMAIN from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN from homeassistant.const import STATE_UNAVAILABLE diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index c7478be7c07..b2c67cb58f9 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -1,4 +1,5 @@ """Tests for the NWS weather component.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index 044fbc6ae9e..5b8adada1a2 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the nx584 sensor platform.""" + from unittest import mock from nx584 import client as nx584_client diff --git a/tests/components/nzbget/conftest.py b/tests/components/nzbget/conftest.py index d62e5a2e8b3..8f48a4306c7 100644 --- a/tests/components/nzbget/conftest.py +++ b/tests/components/nzbget/conftest.py @@ -1,4 +1,5 @@ """Define fixtures available for all tests.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/nzbget/test_config_flow.py b/tests/components/nzbget/test_config_flow.py index e26be8b9880..56ca19b0c95 100644 --- a/tests/components/nzbget/test_config_flow.py +++ b/tests/components/nzbget/test_config_flow.py @@ -1,4 +1,5 @@ """Test the NZBGet config flow.""" + from unittest.mock import patch from pynzbgetapi import NZBGetAPIException diff --git a/tests/components/nzbget/test_init.py b/tests/components/nzbget/test_init.py index 2e08ccb5630..a12cacf944e 100644 --- a/tests/components/nzbget/test_init.py +++ b/tests/components/nzbget/test_init.py @@ -1,4 +1,5 @@ """Test the NZBGet config flow.""" + from unittest.mock import patch from pynzbgetapi import NZBGetAPIException diff --git a/tests/components/nzbget/test_sensor.py b/tests/components/nzbget/test_sensor.py index e9365e36b24..350401ed9a2 100644 --- a/tests/components/nzbget/test_sensor.py +++ b/tests/components/nzbget/test_sensor.py @@ -1,4 +1,5 @@ """Test the NZBGet sensors.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/nzbget/test_switch.py b/tests/components/nzbget/test_switch.py index 059763c2da3..61343710254 100644 --- a/tests/components/nzbget/test_switch.py +++ b/tests/components/nzbget/test_switch.py @@ -1,4 +1,5 @@ """Test the NZBGet switches.""" + from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, diff --git a/tests/components/octoprint/test_binary_sensor.py b/tests/components/octoprint/test_binary_sensor.py index fa67c052dc9..50572682e7d 100644 --- a/tests/components/octoprint/test_binary_sensor.py +++ b/tests/components/octoprint/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for Octoptint binary sensor module.""" + from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/octoprint/test_button.py b/tests/components/octoprint/test_button.py index a92afdff3f0..c511227f1b9 100644 --- a/tests/components/octoprint/test_button.py +++ b/tests/components/octoprint/test_button.py @@ -1,4 +1,5 @@ """Test the OctoPrint buttons.""" + from unittest.mock import patch from pyoctoprintapi import OctoprintPrinterInfo diff --git a/tests/components/octoprint/test_camera.py b/tests/components/octoprint/test_camera.py index 49daf9a8227..b1d843f7d39 100644 --- a/tests/components/octoprint/test_camera.py +++ b/tests/components/octoprint/test_camera.py @@ -1,4 +1,5 @@ """The tests for Octoptint camera module.""" + from unittest.mock import patch from pyoctoprintapi import WebcamSettings diff --git a/tests/components/octoprint/test_config_flow.py b/tests/components/octoprint/test_config_flow.py index 8e20983a791..95e0ab0bb2b 100644 --- a/tests/components/octoprint/test_config_flow.py +++ b/tests/components/octoprint/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OctoPrint config flow.""" + from ipaddress import ip_address from unittest.mock import patch diff --git a/tests/components/octoprint/test_sensor.py b/tests/components/octoprint/test_sensor.py index 2a0e14e5ce0..8c1c0a7712e 100644 --- a/tests/components/octoprint/test_sensor.py +++ b/tests/components/octoprint/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Octoptint binary sensor module.""" + from datetime import UTC, datetime from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/octoprint/test_servics.py b/tests/components/octoprint/test_servics.py index 70e983c4bb4..2b5a89970e8 100644 --- a/tests/components/octoprint/test_servics.py +++ b/tests/components/octoprint/test_servics.py @@ -1,4 +1,5 @@ """Test the OctoPrint services.""" + from unittest.mock import patch from homeassistant.components.octoprint.const import ( diff --git a/tests/components/omnilogic/test_config_flow.py b/tests/components/omnilogic/test_config_flow.py index 17f4f782744..efcad70e7b6 100644 --- a/tests/components/omnilogic/test_config_flow.py +++ b/tests/components/omnilogic/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Omnilogic config flow.""" + from unittest.mock import patch from omnilogic import LoginException, OmniLogicException diff --git a/tests/components/onboarding/test_init.py b/tests/components/onboarding/test_init.py index bcaa9ad611f..69063057387 100644 --- a/tests/components/onboarding/test_init.py +++ b/tests/components/onboarding/test_init.py @@ -1,4 +1,5 @@ """Tests for the init.""" + from typing import Any from unittest.mock import Mock, patch diff --git a/tests/components/oncue/test_binary_sensor.py b/tests/components/oncue/test_binary_sensor.py index f2e7657089f..12ecb19ebc4 100644 --- a/tests/components/oncue/test_binary_sensor.py +++ b/tests/components/oncue/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the oncue binary_sensor.""" + from __future__ import annotations from homeassistant.components import oncue diff --git a/tests/components/oncue/test_config_flow.py b/tests/components/oncue/test_config_flow.py index 979cf3b2677..a58bcb98c0b 100644 --- a/tests/components/oncue/test_config_flow.py +++ b/tests/components/oncue/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Oncue config flow.""" + from unittest.mock import patch from aiooncue import LoginFailedException diff --git a/tests/components/oncue/test_init.py b/tests/components/oncue/test_init.py index dd3cb20f373..f10d94d719b 100644 --- a/tests/components/oncue/test_init.py +++ b/tests/components/oncue/test_init.py @@ -1,4 +1,5 @@ """Tests for the oncue component.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/oncue/test_sensor.py b/tests/components/oncue/test_sensor.py index 65a29e34e4c..1544d015c89 100644 --- a/tests/components/oncue/test_sensor.py +++ b/tests/components/oncue/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the oncue sensor.""" + from __future__ import annotations import pytest diff --git a/tests/components/ondilo_ico/test_config_flow.py b/tests/components/ondilo_ico/test_config_flow.py index 002b6d43feb..53483241c0b 100644 --- a/tests/components/ondilo_ico/test_config_flow.py +++ b/tests/components/ondilo_ico/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ondilo ICO config flow.""" + from unittest.mock import patch from homeassistant import config_entries, data_entry_flow, setup diff --git a/tests/components/onewire/conftest.py b/tests/components/onewire/conftest.py index 031b29d47a7..03a8443049e 100644 --- a/tests/components/onewire/conftest.py +++ b/tests/components/onewire/conftest.py @@ -1,4 +1,5 @@ """Provide common 1-Wire fixtures.""" + from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/onewire/const.py b/tests/components/onewire/const.py index 3da37a72459..c7c16be820b 100644 --- a/tests/components/onewire/const.py +++ b/tests/components/onewire/const.py @@ -1,4 +1,5 @@ """Constants for 1-Wire integration.""" + from pyownet.protocol import Error as ProtocolError from homeassistant.components.onewire.const import Platform diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index 1e13da95651..5fd4ab2cee6 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for 1-Wire binary sensors.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/onewire/test_config_flow.py b/tests/components/onewire/test_config_flow.py index 27d8436f8c4..980ecb22d32 100644 --- a/tests/components/onewire/test_config_flow.py +++ b/tests/components/onewire/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for 1-Wire config flow.""" + from unittest.mock import AsyncMock, patch from pyownet import protocol diff --git a/tests/components/onewire/test_diagnostics.py b/tests/components/onewire/test_diagnostics.py index 4e108e41959..dd08e825221 100644 --- a/tests/components/onewire/test_diagnostics.py +++ b/tests/components/onewire/test_diagnostics.py @@ -1,4 +1,5 @@ """Test 1-Wire diagnostics.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/onewire/test_init.py b/tests/components/onewire/test_init.py index 01c1841d178..991277d8329 100644 --- a/tests/components/onewire/test_init.py +++ b/tests/components/onewire/test_init.py @@ -1,4 +1,5 @@ """Tests for 1-Wire config flow.""" + from copy import deepcopy from unittest.mock import MagicMock, patch diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index f4a993d6d96..856c3ec4dea 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -1,4 +1,5 @@ """Tests for 1-Wire sensors.""" + from collections.abc import Generator from copy import deepcopy import logging diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index 5440cbee598..a367ace3b02 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -1,4 +1,5 @@ """Tests for 1-Wire switches.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/onvif/test_button.py b/tests/components/onvif/test_button.py index 4b30bc7bdd1..f8d51ae31a0 100644 --- a/tests/components/onvif/test_button.py +++ b/tests/components/onvif/test_button.py @@ -1,4 +1,5 @@ """Test button of ONVIF integration.""" + from unittest.mock import AsyncMock from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, ButtonDeviceClass diff --git a/tests/components/onvif/test_diagnostics.py b/tests/components/onvif/test_diagnostics.py index af7a68a6e0d..d58c8008ea6 100644 --- a/tests/components/onvif/test_diagnostics.py +++ b/tests/components/onvif/test_diagnostics.py @@ -1,4 +1,5 @@ """Test ONVIF diagnostics.""" + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/onvif/test_switch.py b/tests/components/onvif/test_switch.py index 1228f72ba22..0afa4ff4042 100644 --- a/tests/components/onvif/test_switch.py +++ b/tests/components/onvif/test_switch.py @@ -1,4 +1,5 @@ """Test switch platform of ONVIF integration.""" + from unittest.mock import AsyncMock from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN diff --git a/tests/components/open_meteo/conftest.py b/tests/components/open_meteo/conftest.py index 76bb3039a2f..466d593cd73 100644 --- a/tests/components/open_meteo/conftest.py +++ b/tests/components/open_meteo/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Open-Meteo integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/open_meteo/test_init.py b/tests/components/open_meteo/test_init.py index c149dc2f6a6..19efcbec35c 100644 --- a/tests/components/open_meteo/test_init.py +++ b/tests/components/open_meteo/test_init.py @@ -1,4 +1,5 @@ """Tests for the Open-Meteo integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from open_meteo import OpenMeteoConnectionError diff --git a/tests/components/openai_conversation/conftest.py b/tests/components/openai_conversation/conftest.py index a83c660e509..a8081c01c32 100644 --- a/tests/components/openai_conversation/conftest.py +++ b/tests/components/openai_conversation/conftest.py @@ -1,4 +1,5 @@ """Tests helpers.""" + from unittest.mock import patch import pytest diff --git a/tests/components/openai_conversation/test_config_flow.py b/tests/components/openai_conversation/test_config_flow.py index dd218e88c12..85fd70f6b84 100644 --- a/tests/components/openai_conversation/test_config_flow.py +++ b/tests/components/openai_conversation/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OpenAI Conversation config flow.""" + from unittest.mock import patch from httpx import Response diff --git a/tests/components/openai_conversation/test_init.py b/tests/components/openai_conversation/test_init.py index d3a06cabeb3..ebacc08700e 100644 --- a/tests/components/openai_conversation/test_init.py +++ b/tests/components/openai_conversation/test_init.py @@ -1,4 +1,5 @@ """Tests for the OpenAI integration.""" + from unittest.mock import AsyncMock, patch from httpx import Response diff --git a/tests/components/openalpr_cloud/test_image_processing.py b/tests/components/openalpr_cloud/test_image_processing.py index 700152f80aa..090fcabe8a4 100644 --- a/tests/components/openalpr_cloud/test_image_processing.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -1,4 +1,5 @@ """The tests for the openalpr cloud platform.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/openerz/test_sensor.py b/tests/components/openerz/test_sensor.py index 06fa39363ce..6e65774bda4 100644 --- a/tests/components/openerz/test_sensor.py +++ b/tests/components/openerz/test_sensor.py @@ -1,4 +1,5 @@ """Tests for OpenERZ component.""" + from unittest.mock import MagicMock, patch from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN diff --git a/tests/components/openexchangerates/conftest.py b/tests/components/openexchangerates/conftest.py index a1512442fd1..5cb97e0cc53 100644 --- a/tests/components/openexchangerates/conftest.py +++ b/tests/components/openexchangerates/conftest.py @@ -1,4 +1,5 @@ """Provide common fixtures for tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/opengarage/conftest.py b/tests/components/opengarage/conftest.py index 189c3a877ff..24dc8134e4b 100644 --- a/tests/components/opengarage/conftest.py +++ b/tests/components/opengarage/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the OpenGarage integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/opengarage/test_button.py b/tests/components/opengarage/test_button.py index b4557a116e8..3742b7c8aec 100644 --- a/tests/components/opengarage/test_button.py +++ b/tests/components/opengarage/test_button.py @@ -1,4 +1,5 @@ """Test the OpenGarage Browser buttons.""" + from unittest.mock import MagicMock import homeassistant.components.button as button diff --git a/tests/components/opengarage/test_config_flow.py b/tests/components/opengarage/test_config_flow.py index 39cbb9c4b6b..f730cf95703 100644 --- a/tests/components/opengarage/test_config_flow.py +++ b/tests/components/opengarage/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OpenGarage config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/openhome/test_update.py b/tests/components/openhome/test_update.py index d975cc29af4..74fecdb4ed6 100644 --- a/tests/components/openhome/test_update.py +++ b/tests/components/openhome/test_update.py @@ -1,4 +1,5 @@ """Tests for the Openhome update platform.""" + from unittest.mock import AsyncMock, MagicMock, patch import pytest diff --git a/tests/components/opensky/conftest.py b/tests/components/opensky/conftest.py index 90e0b7251bf..835543b632f 100644 --- a/tests/components/opensky/conftest.py +++ b/tests/components/opensky/conftest.py @@ -1,4 +1,5 @@ """Configure tests for the OpenSky integration.""" + from collections.abc import Awaitable, Callable from unittest.mock import patch diff --git a/tests/components/opensky/test_config_flow.py b/tests/components/opensky/test_config_flow.py index 5207ac52f0c..43192cdaff2 100644 --- a/tests/components/opensky/test_config_flow.py +++ b/tests/components/opensky/test_config_flow.py @@ -1,4 +1,5 @@ """Test OpenSky config flow.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/opensky/test_init.py b/tests/components/opensky/test_init.py index 4c6cb9c3a33..a9e1668d026 100644 --- a/tests/components/opensky/test_init.py +++ b/tests/components/opensky/test_init.py @@ -1,4 +1,5 @@ """Test OpenSky component setup process.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/opensky/test_sensor.py b/tests/components/opensky/test_sensor.py index 27c45d1b8ca..80c53430ea9 100644 --- a/tests/components/opensky/test_sensor.py +++ b/tests/components/opensky/test_sensor.py @@ -1,4 +1,5 @@ """OpenSky sensor tests.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index e071785006a..57229d3b49f 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Opentherm Gateway config flow.""" + from unittest.mock import patch from pyotgw.vars import OTGW, OTGW_ABOUT diff --git a/tests/components/opentherm_gw/test_init.py b/tests/components/opentherm_gw/test_init.py index bd2c772bacb..c068686a607 100644 --- a/tests/components/opentherm_gw/test_init.py +++ b/tests/components/opentherm_gw/test_init.py @@ -1,4 +1,5 @@ """Test Opentherm Gateway init.""" + from unittest.mock import patch from pyotgw.vars import OTGW, OTGW_ABOUT diff --git a/tests/components/openuv/conftest.py b/tests/components/openuv/conftest.py index 0f59c6279fb..414a73435fc 100644 --- a/tests/components/openuv/conftest.py +++ b/tests/components/openuv/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for OpenUV.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index ddc7d3ce85d..db71b712fd9 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the OpenUV config flow.""" + from unittest.mock import AsyncMock, patch from pyopenuv.errors import InvalidApiKeyError diff --git a/tests/components/openuv/test_diagnostics.py b/tests/components/openuv/test_diagnostics.py index e7efc459630..4b5114bccd1 100644 --- a/tests/components/openuv/test_diagnostics.py +++ b/tests/components/openuv/test_diagnostics.py @@ -1,4 +1,5 @@ """Test OpenUV diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/openweathermap/test_config_flow.py b/tests/components/openweathermap/test_config_flow.py index 87f76817044..77a10c5b26f 100644 --- a/tests/components/openweathermap/test_config_flow.py +++ b/tests/components/openweathermap/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the OpenWeatherMap config flow.""" + from unittest.mock import MagicMock, patch from pyowm.commons.exceptions import APIRequestError, UnauthorizedError diff --git a/tests/components/opnsense/test_device_tracker.py b/tests/components/opnsense/test_device_tracker.py index 67301cfbcd6..6da01d8f44d 100644 --- a/tests/components/opnsense/test_device_tracker.py +++ b/tests/components/opnsense/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the opnsense device tracker platform.""" + from unittest import mock import pytest diff --git a/tests/components/opower/test_config_flow.py b/tests/components/opower/test_config_flow.py index 0e96af200df..cb796d8e255 100644 --- a/tests/components/opower/test_config_flow.py +++ b/tests/components/opower/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Opower config flow.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/oralb/test_config_flow.py b/tests/components/oralb/test_config_flow.py index 10f529fff70..197da4264d1 100644 --- a/tests/components/oralb/test_config_flow.py +++ b/tests/components/oralb/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OralB config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/osoenergy/test_config_flow.py b/tests/components/osoenergy/test_config_flow.py index 5c7e0b3442c..3a2de0e44f5 100644 --- a/tests/components/osoenergy/test_config_flow.py +++ b/tests/components/osoenergy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OSO Energy config flow.""" + from unittest.mock import patch from apyosoenergyapi.helper import osoenergy_exceptions diff --git a/tests/components/otbr/conftest.py b/tests/components/otbr/conftest.py index c03eef8dcb7..8b102f32b8e 100644 --- a/tests/components/otbr/conftest.py +++ b/tests/components/otbr/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the Open Thread Border Router integration.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/components/otbr/test_silabs_multiprotocol.py b/tests/components/otbr/test_silabs_multiprotocol.py index 83416ae297d..569d507d087 100644 --- a/tests/components/otbr/test_silabs_multiprotocol.py +++ b/tests/components/otbr/test_silabs_multiprotocol.py @@ -1,4 +1,5 @@ """Test OTBR Silicon Labs Multiprotocol support.""" + from unittest.mock import patch import pytest diff --git a/tests/components/otbr/test_util.py b/tests/components/otbr/test_util.py index 941c80a52da..ac072ffd513 100644 --- a/tests/components/otbr/test_util.py +++ b/tests/components/otbr/test_util.py @@ -1,4 +1,5 @@ """Test OTBR Utility functions.""" + from unittest.mock import patch import pytest diff --git a/tests/components/otbr/test_websocket_api.py b/tests/components/otbr/test_websocket_api.py index 52aa792b814..a872206f833 100644 --- a/tests/components/otbr/test_websocket_api.py +++ b/tests/components/otbr/test_websocket_api.py @@ -1,4 +1,5 @@ """Test OTBR Websocket API.""" + from unittest.mock import patch import pytest diff --git a/tests/components/ourgroceries/conftest.py b/tests/components/ourgroceries/conftest.py index c5fdec3ecb7..00aab0df834 100644 --- a/tests/components/ourgroceries/conftest.py +++ b/tests/components/ourgroceries/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the OurGroceries tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/ourgroceries/test_config_flow.py b/tests/components/ourgroceries/test_config_flow.py index 78504e1fb7a..0eb17cd4ff6 100644 --- a/tests/components/ourgroceries/test_config_flow.py +++ b/tests/components/ourgroceries/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OurGroceries config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/ourgroceries/test_init.py b/tests/components/ourgroceries/test_init.py index 43905c4fcf9..ae8452652ae 100644 --- a/tests/components/ourgroceries/test_init.py +++ b/tests/components/ourgroceries/test_init.py @@ -1,4 +1,5 @@ """Unit tests for the OurGroceries integration.""" + from unittest.mock import AsyncMock import pytest diff --git a/tests/components/ourgroceries/test_todo.py b/tests/components/ourgroceries/test_todo.py index 8ede2a40cc8..d598767409a 100644 --- a/tests/components/ourgroceries/test_todo.py +++ b/tests/components/ourgroceries/test_todo.py @@ -1,4 +1,5 @@ """Unit tests for the OurGroceries todo platform.""" + from unittest.mock import AsyncMock from aiohttp import ClientError diff --git a/tests/components/overkiz/conftest.py b/tests/components/overkiz/conftest.py index da6d3a60839..d1da5d89134 100644 --- a/tests/components/overkiz/conftest.py +++ b/tests/components/overkiz/conftest.py @@ -1,4 +1,5 @@ """Configuration for overkiz tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/overkiz/test_config_flow.py b/tests/components/overkiz/test_config_flow.py index 9b7b9f38287..6754452dea1 100644 --- a/tests/components/overkiz/test_config_flow.py +++ b/tests/components/overkiz/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Overkiz config flow.""" + from __future__ import annotations from ipaddress import ip_address diff --git a/tests/components/overkiz/test_diagnostics.py b/tests/components/overkiz/test_diagnostics.py index 6d0498c237b..672370c2667 100644 --- a/tests/components/overkiz/test_diagnostics.py +++ b/tests/components/overkiz/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Overkiz integration.""" + from unittest.mock import AsyncMock, patch from syrupy import SnapshotAssertion diff --git a/tests/components/overkiz/test_init.py b/tests/components/overkiz/test_init.py index ddecee7c167..ba4de56ad86 100644 --- a/tests/components/overkiz/test_init.py +++ b/tests/components/overkiz/test_init.py @@ -1,4 +1,5 @@ """Tests for Overkiz integration init.""" + from homeassistant.components.overkiz.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/ovo_energy/test_config_flow.py b/tests/components/ovo_energy/test_config_flow.py index 1e9a0672473..84b2719ae9b 100644 --- a/tests/components/ovo_energy/test_config_flow.py +++ b/tests/components/ovo_energy/test_config_flow.py @@ -1,4 +1,5 @@ """Test the OVO Energy config flow.""" + from unittest.mock import patch import aiohttp diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index 498e930f1e4..038609271f4 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for OwnTracks config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/owntracks/test_helper.py b/tests/components/owntracks/test_helper.py index 8e1eee34113..6e6bdccceca 100644 --- a/tests/components/owntracks/test_helper.py +++ b/tests/components/owntracks/test_helper.py @@ -1,4 +1,5 @@ """Test the owntracks_http platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/p1_monitor/test_config_flow.py b/tests/components/p1_monitor/test_config_flow.py index 419f24871ef..754fd31f19e 100644 --- a/tests/components/p1_monitor/test_config_flow.py +++ b/tests/components/p1_monitor/test_config_flow.py @@ -1,4 +1,5 @@ """Test the P1 Monitor config flow.""" + from unittest.mock import patch from p1monitor import P1MonitorError diff --git a/tests/components/p1_monitor/test_init.py b/tests/components/p1_monitor/test_init.py index d7817faecdf..f8de8767a09 100644 --- a/tests/components/p1_monitor/test_init.py +++ b/tests/components/p1_monitor/test_init.py @@ -1,4 +1,5 @@ """Tests for the P1 Monitor integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from p1monitor import P1MonitorConnectionError diff --git a/tests/components/p1_monitor/test_sensor.py b/tests/components/p1_monitor/test_sensor.py index 4e6b7580319..d280a4da39f 100644 --- a/tests/components/p1_monitor/test_sensor.py +++ b/tests/components/p1_monitor/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the P1 Monitor integration.""" + from unittest.mock import MagicMock from p1monitor import P1MonitorNoDataError diff --git a/tests/components/panasonic_viera/test_config_flow.py b/tests/components/panasonic_viera/test_config_flow.py index 34ac218a839..f9664f5d657 100644 --- a/tests/components/panasonic_viera/test_config_flow.py +++ b/tests/components/panasonic_viera/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Panasonic Viera config flow.""" + from unittest.mock import patch from panasonic_viera import SOAPError diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py index 03d23316bcc..7165b90d93c 100644 --- a/tests/components/panasonic_viera/test_init.py +++ b/tests/components/panasonic_viera/test_init.py @@ -1,4 +1,5 @@ """Test the Panasonic Viera setup process.""" + from unittest.mock import Mock, patch from homeassistant.components.panasonic_viera.const import ( diff --git a/tests/components/panasonic_viera/test_remote.py b/tests/components/panasonic_viera/test_remote.py index cb9777c1177..05254753d3f 100644 --- a/tests/components/panasonic_viera/test_remote.py +++ b/tests/components/panasonic_viera/test_remote.py @@ -1,4 +1,5 @@ """Test the Panasonic Viera remote entity.""" + from unittest.mock import Mock, call from panasonic_viera import Keys, SOAPError diff --git a/tests/components/panel_custom/test_init.py b/tests/components/panel_custom/test_init.py index d84b4c812c7..dc0f06d2a56 100644 --- a/tests/components/panel_custom/test_init.py +++ b/tests/components/panel_custom/test_init.py @@ -1,4 +1,5 @@ """The tests for the panel_custom component.""" + from unittest.mock import Mock, patch from homeassistant import setup diff --git a/tests/components/peco/test_config_flow.py b/tests/components/peco/test_config_flow.py index 833c66ab37a..1a91145624a 100644 --- a/tests/components/peco/test_config_flow.py +++ b/tests/components/peco/test_config_flow.py @@ -1,4 +1,5 @@ """Test the PECO Outage Counter config flow.""" + from unittest.mock import patch from peco import HttpError, IncompatibleMeterError, UnresponsiveMeterError diff --git a/tests/components/peco/test_init.py b/tests/components/peco/test_init.py index c8a7c5ccbd5..261624f382f 100644 --- a/tests/components/peco/test_init.py +++ b/tests/components/peco/test_init.py @@ -1,4 +1,5 @@ """Test the PECO Outage Counter init file.""" + from unittest.mock import patch from peco import ( diff --git a/tests/components/peco/test_sensor.py b/tests/components/peco/test_sensor.py index c5dcd843bd4..2734ed4b8d1 100644 --- a/tests/components/peco/test_sensor.py +++ b/tests/components/peco/test_sensor.py @@ -1,4 +1,5 @@ """Test the PECO Outage Counter sensors.""" + from unittest.mock import patch from peco import AlertResults, OutageResults diff --git a/tests/components/pegel_online/test_config_flow.py b/tests/components/pegel_online/test_config_flow.py index 61f7dc75255..c9b22174424 100644 --- a/tests/components/pegel_online/test_config_flow.py +++ b/tests/components/pegel_online/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Pegel Online config flow.""" + from unittest.mock import patch from aiohttp.client_exceptions import ClientError diff --git a/tests/components/pegel_online/test_init.py b/tests/components/pegel_online/test_init.py index 2b5ba3642ec..ee2e78af7cf 100644 --- a/tests/components/pegel_online/test_init.py +++ b/tests/components/pegel_online/test_init.py @@ -1,4 +1,5 @@ """Test pegel_online component.""" + from unittest.mock import patch from aiohttp.client_exceptions import ClientError diff --git a/tests/components/pegel_online/test_sensor.py b/tests/components/pegel_online/test_sensor.py index a02c7538280..e911ec571cd 100644 --- a/tests/components/pegel_online/test_sensor.py +++ b/tests/components/pegel_online/test_sensor.py @@ -1,4 +1,5 @@ """Test pegel_online component.""" + from unittest.mock import patch from aiopegelonline.models import Station, StationMeasurements diff --git a/tests/components/permobil/conftest.py b/tests/components/permobil/conftest.py index 2dcf9bd5ad2..74d17616af7 100644 --- a/tests/components/permobil/conftest.py +++ b/tests/components/permobil/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the MyPermobil tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/permobil/test_config_flow.py b/tests/components/permobil/test_config_flow.py index 0f303cc0482..5968e247a95 100644 --- a/tests/components/permobil/test_config_flow.py +++ b/tests/components/permobil/test_config_flow.py @@ -1,4 +1,5 @@ """Test the MyPermobil config flow.""" + from unittest.mock import Mock, patch from mypermobil import ( diff --git a/tests/components/persistent_notification/test_trigger.py b/tests/components/persistent_notification/test_trigger.py index 3cf3655a3b6..16208143447 100644 --- a/tests/components/persistent_notification/test_trigger.py +++ b/tests/components/persistent_notification/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the persistent notification component triggers.""" + from typing import Any import homeassistant.components.persistent_notification as pn diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index a9f91801883..6485f0ae7ac 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -1,4 +1,5 @@ """The tests for the person component.""" + from typing import Any from unittest.mock import patch diff --git a/tests/components/person/test_recorder.py b/tests/components/person/test_recorder.py index 51b5691bd8e..4d25ce7add4 100644 --- a/tests/components/person/test_recorder.py +++ b/tests/components/person/test_recorder.py @@ -1,4 +1,5 @@ """The tests for update recorder.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/person/test_significant_change.py b/tests/components/person/test_significant_change.py index 36de0d32b59..e642d3b7270 100644 --- a/tests/components/person/test_significant_change.py +++ b/tests/components/person/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Person significant change platform.""" + from homeassistant.components.person.significant_change import ( async_check_significant_change, ) diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py index bc94d721cc9..15e796ec86e 100644 --- a/tests/components/philips_js/conftest.py +++ b/tests/components/philips_js/conftest.py @@ -1,4 +1,5 @@ """Standard setup for tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, create_autospec, patch diff --git a/tests/components/philips_js/test_config_flow.py b/tests/components/philips_js/test_config_flow.py index 8229f4e8fa9..07f1f2be933 100644 --- a/tests/components/philips_js/test_config_flow.py +++ b/tests/components/philips_js/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Philips TV config flow.""" + from unittest.mock import ANY from haphilipsjs import PairingFailure diff --git a/tests/components/pi_hole/test_config_flow.py b/tests/components/pi_hole/test_config_flow.py index 1c8abdcfedb..0ab8b53744c 100644 --- a/tests/components/pi_hole/test_config_flow.py +++ b/tests/components/pi_hole/test_config_flow.py @@ -1,4 +1,5 @@ """Test pi_hole config flow.""" + from homeassistant.components import pi_hole from homeassistant.components.pi_hole.const import DOMAIN from homeassistant.config_entries import SOURCE_USER diff --git a/tests/components/picnic/conftest.py b/tests/components/picnic/conftest.py index 5bb84c7a1c1..569d65df387 100644 --- a/tests/components/picnic/conftest.py +++ b/tests/components/picnic/conftest.py @@ -1,4 +1,5 @@ """Conftest for Picnic tests.""" + from collections.abc import Awaitable, Callable import json from unittest.mock import MagicMock, patch diff --git a/tests/components/picnic/test_config_flow.py b/tests/components/picnic/test_config_flow.py index d90551b01df..ec103e9f3a3 100644 --- a/tests/components/picnic/test_config_flow.py +++ b/tests/components/picnic/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Picnic config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/picnic/test_services.py b/tests/components/picnic/test_services.py index bc80ff73a11..b7d5cb0b011 100644 --- a/tests/components/picnic/test_services.py +++ b/tests/components/picnic/test_services.py @@ -1,4 +1,5 @@ """Tests for the Picnic services.""" + from unittest.mock import MagicMock, patch import pytest diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index 96f384f98b9..9cbbadfbbc8 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -1,4 +1,5 @@ """The tests for the pilight component.""" + from datetime import timedelta import logging import socket diff --git a/tests/components/ping/conftest.py b/tests/components/ping/conftest.py index b0f772e603f..db5de50e659 100644 --- a/tests/components/ping/conftest.py +++ b/tests/components/ping/conftest.py @@ -1,4 +1,5 @@ """Test configuration for ping.""" + from unittest.mock import patch from icmplib import Host diff --git a/tests/components/ping/const.py b/tests/components/ping/const.py index 048924292c7..f680f20d270 100644 --- a/tests/components/ping/const.py +++ b/tests/components/ping/const.py @@ -1,4 +1,5 @@ """Constants for tests.""" + from datetime import timedelta from icmplib import Host diff --git a/tests/components/ping/test_binary_sensor.py b/tests/components/ping/test_binary_sensor.py index 68f647008e3..a8346b9a634 100644 --- a/tests/components/ping/test_binary_sensor.py +++ b/tests/components/ping/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the binary sensor platform of ping.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/ping/test_config_flow.py b/tests/components/ping/test_config_flow.py index 8757a5b5e0d..50b05abd409 100644 --- a/tests/components/ping/test_config_flow.py +++ b/tests/components/ping/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Ping (ICMP) config flow.""" + from __future__ import annotations import pytest diff --git a/tests/components/ping/test_device_tracker.py b/tests/components/ping/test_device_tracker.py index de6b4918262..288231a609b 100644 --- a/tests/components/ping/test_device_tracker.py +++ b/tests/components/ping/test_device_tracker.py @@ -1,4 +1,5 @@ """Test the binary sensor platform of ping.""" + from collections.abc import Generator from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/pjlink/test_media_player.py b/tests/components/pjlink/test_media_player.py index 941a3cefe3a..a6d17233450 100644 --- a/tests/components/pjlink/test_media_player.py +++ b/tests/components/pjlink/test_media_player.py @@ -1,4 +1,5 @@ """Test the pjlink media player platform.""" + from datetime import timedelta import socket from unittest.mock import create_autospec, patch diff --git a/tests/components/plaato/test_config_flow.py b/tests/components/plaato/test_config_flow.py index 8e319e0e257..6b61ca8a649 100644 --- a/tests/components/plaato/test_config_flow.py +++ b/tests/components/plaato/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Plaato config flow.""" + from unittest.mock import patch from pyplaato.models.device import PlaatoDeviceType diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index f1ab4e5963f..d173544284d 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -1,4 +1,5 @@ """Unit tests for platform/plant.py.""" + from datetime import datetime, timedelta import homeassistant.components.plant as plant diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index 92818633df4..d6c91a9d9a8 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Plex tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/plex/const.py b/tests/components/plex/const.py index ff9d3c0e9b3..f1399eb9d7d 100644 --- a/tests/components/plex/const.py +++ b/tests/components/plex/const.py @@ -1,4 +1,5 @@ """Constants used by Plex tests.""" + from homeassistant.components.plex import const from homeassistant.const import ( CONF_CLIENT_ID, diff --git a/tests/components/plex/helpers.py b/tests/components/plex/helpers.py index 246922bccae..00d0a4539c1 100644 --- a/tests/components/plex/helpers.py +++ b/tests/components/plex/helpers.py @@ -1,4 +1,5 @@ """Helper methods for Plex tests.""" + from datetime import timedelta from plexwebsocket import SIGNAL_CONNECTION_STATE, STATE_CONNECTED diff --git a/tests/components/plex/test_browse_media.py b/tests/components/plex/test_browse_media.py index af80b37a4af..11eb73ad608 100644 --- a/tests/components/plex/test_browse_media.py +++ b/tests/components/plex/test_browse_media.py @@ -1,4 +1,5 @@ """Tests for Plex media browser.""" + from http import HTTPStatus from unittest.mock import Mock, patch diff --git a/tests/components/plex/test_button.py b/tests/components/plex/test_button.py index a37a3ea2df2..8033cacb0df 100644 --- a/tests/components/plex/test_button.py +++ b/tests/components/plex/test_button.py @@ -1,4 +1,5 @@ """Tests for Plex buttons.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/plex/test_media_players.py b/tests/components/plex/test_media_players.py index e9efc945f71..6e6fe29e4d4 100644 --- a/tests/components/plex/test_media_players.py +++ b/tests/components/plex/test_media_players.py @@ -1,4 +1,5 @@ """Tests for Plex media_players.""" + from unittest.mock import patch from plexapi.exceptions import NotFound diff --git a/tests/components/plex/test_media_search.py b/tests/components/plex/test_media_search.py index 21b50724786..da5b89c18eb 100644 --- a/tests/components/plex/test_media_search.py +++ b/tests/components/plex/test_media_search.py @@ -1,4 +1,5 @@ """Tests for Plex server.""" + from unittest.mock import patch from plexapi.exceptions import BadRequest, NotFound diff --git a/tests/components/plex/test_playback.py b/tests/components/plex/test_playback.py index 9ea684256c4..0006ea49efb 100644 --- a/tests/components/plex/test_playback.py +++ b/tests/components/plex/test_playback.py @@ -1,4 +1,5 @@ """Tests for Plex player playback methods/services.""" + from http import HTTPStatus from unittest.mock import Mock, patch diff --git a/tests/components/plex/test_sensor.py b/tests/components/plex/test_sensor.py index 93014dfedd1..6002429e84d 100644 --- a/tests/components/plex/test_sensor.py +++ b/tests/components/plex/test_sensor.py @@ -1,4 +1,5 @@ """Tests for Plex sensors.""" + from datetime import datetime, timedelta from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/plex/test_services.py b/tests/components/plex/test_services.py index dfd02bb1d3f..c84322e1c14 100644 --- a/tests/components/plex/test_services.py +++ b/tests/components/plex/test_services.py @@ -1,4 +1,5 @@ """Tests for various Plex services.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index 4d81956eacb..140d56715ef 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -1,4 +1,5 @@ """Setup mocks for the Plugwise integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index 438ab1b0870..6e2f4e63d85 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Plugwise config flow.""" + from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index 045b8641f69..a2b0521d6e1 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Plugwise integration.""" + from unittest.mock import MagicMock from syrupy import SnapshotAssertion diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index 1b5297b71d2..4eb0b2cb56a 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -1,4 +1,5 @@ """Tests for the Plugwise Climate integration.""" + from unittest.mock import MagicMock from plugwise.exceptions import ( diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 2d47a420fe8..fa58bd4c8eb 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Plugwise switch integration.""" + from unittest.mock import MagicMock from plugwise.exceptions import PlugwiseException diff --git a/tests/components/plum_lightpad/test_config_flow.py b/tests/components/plum_lightpad/test_config_flow.py index 40852094f5b..bfa870dd3b1 100644 --- a/tests/components/plum_lightpad/test_config_flow.py +++ b/tests/components/plum_lightpad/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Plum Lightpad config flow.""" + from unittest.mock import patch from requests.exceptions import ConnectTimeout diff --git a/tests/components/plum_lightpad/test_init.py b/tests/components/plum_lightpad/test_init.py index 66402abf13c..6035713a24f 100644 --- a/tests/components/plum_lightpad/test_init.py +++ b/tests/components/plum_lightpad/test_init.py @@ -1,4 +1,5 @@ """Tests for the Plum Lightpad config flow.""" + from unittest.mock import Mock, patch from aiohttp import ContentTypeError diff --git a/tests/components/point/test_config_flow.py b/tests/components/point/test_config_flow.py index d58475f4994..67745251bf9 100644 --- a/tests/components/point/test_config_flow.py +++ b/tests/components/point/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Point config flow.""" + from unittest.mock import AsyncMock, patch import pytest diff --git a/tests/components/poolsense/test_config_flow.py b/tests/components/poolsense/test_config_flow.py index 71303e48dbf..76766ef1d87 100644 --- a/tests/components/poolsense/test_config_flow.py +++ b/tests/components/poolsense/test_config_flow.py @@ -1,4 +1,5 @@ """Test the PoolSense config flow.""" + from unittest.mock import patch from homeassistant import data_entry_flow diff --git a/tests/components/powerwall/test_binary_sensor.py b/tests/components/powerwall/test_binary_sensor.py index f24c0e910a2..1fc576543c3 100644 --- a/tests/components/powerwall/test_binary_sensor.py +++ b/tests/components/powerwall/test_binary_sensor.py @@ -1,4 +1,5 @@ """The binary sensor tests for the powerwall platform.""" + from unittest.mock import patch from homeassistant.components.powerwall.const import DOMAIN diff --git a/tests/components/powerwall/test_sensor.py b/tests/components/powerwall/test_sensor.py index 2de79a6a6dc..8ef577f8eee 100644 --- a/tests/components/powerwall/test_sensor.py +++ b/tests/components/powerwall/test_sensor.py @@ -1,4 +1,5 @@ """The sensor tests for the powerwall platform.""" + from datetime import timedelta from unittest.mock import Mock, patch diff --git a/tests/components/powerwall/test_switch.py b/tests/components/powerwall/test_switch.py index e63d6031155..fdcdd5150ed 100644 --- a/tests/components/powerwall/test_switch.py +++ b/tests/components/powerwall/test_switch.py @@ -1,4 +1,5 @@ """Test for Powerwall off-grid switch.""" + from unittest.mock import patch import pytest diff --git a/tests/components/private_ble_device/test_config_flow.py b/tests/components/private_ble_device/test_config_flow.py index bb58cfedb29..2acb89240a1 100644 --- a/tests/components/private_ble_device/test_config_flow.py +++ b/tests/components/private_ble_device/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for private bluetooth device config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/profiler/test_config_flow.py b/tests/components/profiler/test_config_flow.py index 5d62368822a..93542f90520 100644 --- a/tests/components/profiler/test_config_flow.py +++ b/tests/components/profiler/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Profiler config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index b8a81a40e37..84db1b07106 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -1,4 +1,5 @@ """Test the Profiler config flow.""" + from datetime import timedelta from functools import lru_cache import os diff --git a/tests/components/progettihwsw/test_config_flow.py b/tests/components/progettihwsw/test_config_flow.py index 7685d917644..7774adb5208 100644 --- a/tests/components/progettihwsw/test_config_flow.py +++ b/tests/components/progettihwsw/test_config_flow.py @@ -1,4 +1,5 @@ """Test the ProgettiHWSW Automation config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index 812a88db16e..99b73209ad7 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -1,4 +1,5 @@ """The tests for the Prometheus exporter.""" + from dataclasses import dataclass import datetime from http import HTTPStatus diff --git a/tests/components/prosegur/conftest.py b/tests/components/prosegur/conftest.py index 91bc7f88405..bab871d9952 100644 --- a/tests/components/prosegur/conftest.py +++ b/tests/components/prosegur/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for Prosegur.""" + from unittest.mock import AsyncMock, MagicMock, patch from pyprosegur.installation import Camera diff --git a/tests/components/prosegur/test_alarm_control_panel.py b/tests/components/prosegur/test_alarm_control_panel.py index d5244de1b43..534c852c616 100644 --- a/tests/components/prosegur/test_alarm_control_panel.py +++ b/tests/components/prosegur/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """Tests for the Prosegur alarm control panel device.""" + from unittest.mock import AsyncMock, patch from pyprosegur.installation import Status diff --git a/tests/components/prosegur/test_config_flow.py b/tests/components/prosegur/test_config_flow.py index 2c08f9de109..c0a7ffcb1eb 100644 --- a/tests/components/prosegur/test_config_flow.py +++ b/tests/components/prosegur/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Prosegur Alarm config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/prosegur/test_diagnostics.py b/tests/components/prosegur/test_diagnostics.py index daa92de1aa0..c4cd92d48cf 100644 --- a/tests/components/prosegur/test_diagnostics.py +++ b/tests/components/prosegur/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Prosegur diagnostics.""" + from unittest.mock import patch from homeassistant.core import HomeAssistant diff --git a/tests/components/prosegur/test_init.py b/tests/components/prosegur/test_init.py index cdc7135cf1f..c7de490fdaa 100644 --- a/tests/components/prosegur/test_init.py +++ b/tests/components/prosegur/test_init.py @@ -1,4 +1,5 @@ """Tests prosegur setup.""" + from unittest.mock import patch import pytest diff --git a/tests/components/proximity/test_config_flow.py b/tests/components/proximity/test_config_flow.py index 3c94e941227..1841c10873c 100644 --- a/tests/components/proximity/test_config_flow.py +++ b/tests/components/proximity/test_config_flow.py @@ -1,4 +1,5 @@ """Test proximity config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/proximity/test_diagnostics.py b/tests/components/proximity/test_diagnostics.py index e23d8180672..e161d175d0b 100644 --- a/tests/components/proximity/test_diagnostics.py +++ b/tests/components/proximity/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for proximity diagnostics platform.""" + from __future__ import annotations from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/prusalink/conftest.py b/tests/components/prusalink/conftest.py index 1e514342068..f99ccde2094 100644 --- a/tests/components/prusalink/conftest.py +++ b/tests/components/prusalink/conftest.py @@ -1,4 +1,5 @@ """Fixtures for PrusaLink.""" + from unittest.mock import patch import pytest diff --git a/tests/components/prusalink/test_button.py b/tests/components/prusalink/test_button.py index 5324e337780..da790a2036b 100644 --- a/tests/components/prusalink/test_button.py +++ b/tests/components/prusalink/test_button.py @@ -1,4 +1,5 @@ """Test Prusalink buttons.""" + from unittest.mock import patch from pyprusalink.types import Conflict diff --git a/tests/components/prusalink/test_camera.py b/tests/components/prusalink/test_camera.py index b84a13a3df8..94d9c2f8271 100644 --- a/tests/components/prusalink/test_camera.py +++ b/tests/components/prusalink/test_camera.py @@ -1,4 +1,5 @@ """Test Prusalink camera.""" + from unittest.mock import patch import pytest diff --git a/tests/components/prusalink/test_config_flow.py b/tests/components/prusalink/test_config_flow.py index 3d6f6221a50..43f969182b9 100644 --- a/tests/components/prusalink/test_config_flow.py +++ b/tests/components/prusalink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the PrusaLink config flow.""" + from unittest.mock import patch from homeassistant import config_entries diff --git a/tests/components/prusalink/test_init.py b/tests/components/prusalink/test_init.py index 5b261207e93..a462a64ca31 100644 --- a/tests/components/prusalink/test_init.py +++ b/tests/components/prusalink/test_init.py @@ -1,4 +1,5 @@ """Test setting up and unloading PrusaLink.""" + from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/ps4/conftest.py b/tests/components/ps4/conftest.py index 13b6a6e997c..d95acc7e92f 100644 --- a/tests/components/ps4/conftest.py +++ b/tests/components/ps4/conftest.py @@ -1,4 +1,5 @@ """Test configuration for PS4.""" + from collections.abc import Generator from unittest.mock import MagicMock, patch diff --git a/tests/components/ps4/test_config_flow.py b/tests/components/ps4/test_config_flow.py index 242470fa8e7..61bf01f0a91 100644 --- a/tests/components/ps4/test_config_flow.py +++ b/tests/components/ps4/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the PlayStation 4 config flow.""" + from unittest.mock import patch from pyps4_2ndscreen.errors import CredentialTimeout diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index 1252348b3e0..cc2ec5fde1b 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -1,4 +1,5 @@ """Tests for the PS4 Integration.""" + from unittest.mock import MagicMock, patch from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/ps4/test_media_player.py b/tests/components/ps4/test_media_player.py index 74b13d2f909..a21a0800711 100644 --- a/tests/components/ps4/test_media_player.py +++ b/tests/components/ps4/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the PS4 media player platform.""" + from unittest.mock import MagicMock, patch from pyps4_2ndscreen.credential import get_ddp_message diff --git a/tests/components/pure_energie/conftest.py b/tests/components/pure_energie/conftest.py index 4bb89860ce3..40e6f803e83 100644 --- a/tests/components/pure_energie/conftest.py +++ b/tests/components/pure_energie/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Pure Energie integration tests.""" + from collections.abc import Generator import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/pure_energie/test_config_flow.py b/tests/components/pure_energie/test_config_flow.py index 992ce8bbb2c..596853800aa 100644 --- a/tests/components/pure_energie/test_config_flow.py +++ b/tests/components/pure_energie/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Pure Energie config flow.""" + from ipaddress import ip_address from unittest.mock import MagicMock diff --git a/tests/components/pure_energie/test_init.py b/tests/components/pure_energie/test_init.py index 80dce11e603..0a56240aaad 100644 --- a/tests/components/pure_energie/test_init.py +++ b/tests/components/pure_energie/test_init.py @@ -1,4 +1,5 @@ """Tests for the Pure Energie integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from gridnet import GridNetConnectionError diff --git a/tests/components/purpleair/conftest.py b/tests/components/purpleair/conftest.py index 4883f79b349..7299616ad35 100644 --- a/tests/components/purpleair/conftest.py +++ b/tests/components/purpleair/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for PurpleAir tests.""" + from unittest.mock import AsyncMock, Mock, patch from aiopurpleair.endpoints.sensors import NearbySensorResult diff --git a/tests/components/purpleair/test_config_flow.py b/tests/components/purpleair/test_config_flow.py index b72ac7e3a79..efd0db6fd37 100644 --- a/tests/components/purpleair/test_config_flow.py +++ b/tests/components/purpleair/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the PurpleAir config flow.""" + from unittest.mock import AsyncMock, patch from aiopurpleair.errors import InvalidApiKeyError, PurpleAirError diff --git a/tests/components/purpleair/test_diagnostics.py b/tests/components/purpleair/test_diagnostics.py index 85b078d0765..13dcd1338e0 100644 --- a/tests/components/purpleair/test_diagnostics.py +++ b/tests/components/purpleair/test_diagnostics.py @@ -1,4 +1,5 @@ """Test PurpleAir diagnostics.""" + from homeassistant.components.diagnostics import REDACTED from homeassistant.core import HomeAssistant diff --git a/tests/components/push/test_camera.py b/tests/components/push/test_camera.py index 55db1a94b2b..df296e7cb57 100644 --- a/tests/components/push/test_camera.py +++ b/tests/components/push/test_camera.py @@ -1,4 +1,5 @@ """The tests for generic camera component.""" + from datetime import timedelta from http import HTTPStatus import io diff --git a/tests/components/pushbullet/test_config_flow.py b/tests/components/pushbullet/test_config_flow.py index d7baef682b8..01c946286b4 100644 --- a/tests/components/pushbullet/test_config_flow.py +++ b/tests/components/pushbullet/test_config_flow.py @@ -1,4 +1,5 @@ """Test pushbullet config flow.""" + from unittest.mock import patch from pushbullet import InvalidKeyError, PushbulletError diff --git a/tests/components/pushbullet/test_init.py b/tests/components/pushbullet/test_init.py index 6f8e3776b35..72672f36176 100644 --- a/tests/components/pushbullet/test_init.py +++ b/tests/components/pushbullet/test_init.py @@ -1,4 +1,5 @@ """Test pushbullet integration.""" + from unittest.mock import patch from pushbullet import InvalidKeyError, PushbulletError diff --git a/tests/components/pushbullet/test_notify.py b/tests/components/pushbullet/test_notify.py index 53661f01229..bec098e3310 100644 --- a/tests/components/pushbullet/test_notify.py +++ b/tests/components/pushbullet/test_notify.py @@ -1,4 +1,5 @@ """Test pushbullet notification platform.""" + from http import HTTPStatus import requests_mock diff --git a/tests/components/pushover/test_config_flow.py b/tests/components/pushover/test_config_flow.py index 642f1b1b1bb..fcaedf2b5a6 100644 --- a/tests/components/pushover/test_config_flow.py +++ b/tests/components/pushover/test_config_flow.py @@ -1,4 +1,5 @@ """Test pushbullet config flow.""" + from unittest.mock import MagicMock, patch from pushover_complete import BadAPIRequestError diff --git a/tests/components/pushover/test_init.py b/tests/components/pushover/test_init.py index 261426345d1..15e537fd41f 100644 --- a/tests/components/pushover/test_init.py +++ b/tests/components/pushover/test_init.py @@ -1,4 +1,5 @@ """Test pushbullet integration.""" + from unittest.mock import MagicMock, patch from pushover_complete import BadAPIRequestError diff --git a/tests/components/pvoutput/conftest.py b/tests/components/pvoutput/conftest.py index 2bf85e5070e..118bed94d77 100644 --- a/tests/components/pvoutput/conftest.py +++ b/tests/components/pvoutput/conftest.py @@ -1,4 +1,5 @@ """Fixtures for PVOutput integration tests.""" + from __future__ import annotations from collections.abc import Generator diff --git a/tests/components/pvoutput/test_init.py b/tests/components/pvoutput/test_init.py index 87c432cebb2..c351c2a4296 100644 --- a/tests/components/pvoutput/test_init.py +++ b/tests/components/pvoutput/test_init.py @@ -1,4 +1,5 @@ """Tests for the PVOutput integration.""" + from unittest.mock import MagicMock from pvo import ( diff --git a/tests/components/pvoutput/test_sensor.py b/tests/components/pvoutput/test_sensor.py index 61f55e1f552..6d1e239f0f3 100644 --- a/tests/components/pvoutput/test_sensor.py +++ b/tests/components/pvoutput/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensors provided by the PVOutput integration.""" + from homeassistant.components.pvoutput.const import DOMAIN from homeassistant.components.sensor import ( ATTR_STATE_CLASS, diff --git a/tests/components/pvpc_hourly_pricing/conftest.py b/tests/components/pvpc_hourly_pricing/conftest.py index 3bf1b08a51d..5a09d1f3487 100644 --- a/tests/components/pvpc_hourly_pricing/conftest.py +++ b/tests/components/pvpc_hourly_pricing/conftest.py @@ -1,4 +1,5 @@ """Tests for the pvpc_hourly_pricing integration.""" + from http import HTTPStatus import pytest diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index 087edcc1557..86b0af1dd2c 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the pvpc_hourly_pricing config_flow.""" + from datetime import datetime, timedelta from freezegun.api import FrozenDateTimeFactory From 59a6035d3f5bf8d054a4bdffaa3cdd927a0e0597 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:01:29 +0100 Subject: [PATCH 0550/1691] Add empty line after module docstring [j-m] (#112700) --- homeassistant/components/jellyfin/__init__.py | 1 + homeassistant/components/jellyfin/browse_media.py | 1 + homeassistant/components/jellyfin/client_wrapper.py | 1 + homeassistant/components/jellyfin/config_flow.py | 1 + homeassistant/components/jellyfin/coordinator.py | 1 + homeassistant/components/jellyfin/diagnostics.py | 1 + homeassistant/components/jellyfin/entity.py | 1 + homeassistant/components/jellyfin/media_player.py | 1 + homeassistant/components/jellyfin/media_source.py | 1 + homeassistant/components/jellyfin/models.py | 1 + homeassistant/components/jellyfin/sensor.py | 1 + homeassistant/components/jewish_calendar/__init__.py | 1 + homeassistant/components/jewish_calendar/binary_sensor.py | 1 + homeassistant/components/jewish_calendar/sensor.py | 1 + homeassistant/components/joaoapps_join/notify.py | 1 + homeassistant/components/juicenet/__init__.py | 1 + homeassistant/components/juicenet/number.py | 1 + homeassistant/components/juicenet/sensor.py | 1 + homeassistant/components/juicenet/switch.py | 1 + homeassistant/components/justnimbus/__init__.py | 1 + homeassistant/components/justnimbus/config_flow.py | 1 + homeassistant/components/justnimbus/coordinator.py | 1 + homeassistant/components/justnimbus/entity.py | 1 + homeassistant/components/justnimbus/sensor.py | 1 + homeassistant/components/kaiterra/air_quality.py | 1 + homeassistant/components/kaiterra/sensor.py | 1 + homeassistant/components/kaleidescape/media_player.py | 1 + homeassistant/components/kankun/switch.py | 1 + homeassistant/components/keba/binary_sensor.py | 1 + homeassistant/components/keba/lock.py | 1 + homeassistant/components/keba/notify.py | 1 + homeassistant/components/keba/sensor.py | 1 + homeassistant/components/keenetic_ndms2/__init__.py | 1 + homeassistant/components/keenetic_ndms2/binary_sensor.py | 1 + homeassistant/components/keenetic_ndms2/config_flow.py | 1 + homeassistant/components/keenetic_ndms2/device_tracker.py | 1 + homeassistant/components/keenetic_ndms2/router.py | 1 + homeassistant/components/kef/media_player.py | 1 + homeassistant/components/kegtron/__init__.py | 1 + homeassistant/components/kegtron/config_flow.py | 1 + homeassistant/components/kegtron/device.py | 1 + homeassistant/components/kegtron/sensor.py | 1 + homeassistant/components/keyboard/__init__.py | 1 + homeassistant/components/keyboard_remote/__init__.py | 1 + homeassistant/components/keymitt_ble/__init__.py | 1 + homeassistant/components/keymitt_ble/config_flow.py | 1 + homeassistant/components/keymitt_ble/coordinator.py | 1 + homeassistant/components/keymitt_ble/entity.py | 1 + homeassistant/components/keymitt_ble/switch.py | 1 + homeassistant/components/kira/remote.py | 1 + homeassistant/components/kira/sensor.py | 1 + homeassistant/components/kitchen_sink/__init__.py | 1 + homeassistant/components/kitchen_sink/button.py | 1 + homeassistant/components/kitchen_sink/config_flow.py | 1 + homeassistant/components/kitchen_sink/image.py | 1 + homeassistant/components/kitchen_sink/lawn_mower.py | 1 + homeassistant/components/kitchen_sink/lock.py | 1 + homeassistant/components/kitchen_sink/sensor.py | 1 + homeassistant/components/kitchen_sink/switch.py | 1 + homeassistant/components/kitchen_sink/weather.py | 1 + homeassistant/components/kiwi/lock.py | 1 + homeassistant/components/kmtronic/config_flow.py | 1 + homeassistant/components/kmtronic/switch.py | 1 + homeassistant/components/knx/__init__.py | 1 + homeassistant/components/knx/binary_sensor.py | 1 + homeassistant/components/knx/button.py | 1 + homeassistant/components/knx/climate.py | 1 + homeassistant/components/knx/config_flow.py | 1 + homeassistant/components/knx/const.py | 1 + homeassistant/components/knx/cover.py | 1 + homeassistant/components/knx/date.py | 1 + homeassistant/components/knx/datetime.py | 1 + homeassistant/components/knx/device.py | 1 + homeassistant/components/knx/device_trigger.py | 1 + homeassistant/components/knx/diagnostics.py | 1 + homeassistant/components/knx/expose.py | 1 + homeassistant/components/knx/fan.py | 1 + homeassistant/components/knx/knx_entity.py | 1 + homeassistant/components/knx/light.py | 1 + homeassistant/components/knx/notify.py | 1 + homeassistant/components/knx/number.py | 1 + homeassistant/components/knx/project.py | 1 + homeassistant/components/knx/scene.py | 1 + homeassistant/components/knx/schema.py | 1 + homeassistant/components/knx/select.py | 1 + homeassistant/components/knx/sensor.py | 1 + homeassistant/components/knx/services.py | 1 + homeassistant/components/knx/switch.py | 1 + homeassistant/components/knx/telegrams.py | 1 + homeassistant/components/knx/text.py | 1 + homeassistant/components/knx/time.py | 1 + homeassistant/components/knx/validation.py | 1 + homeassistant/components/knx/weather.py | 1 + homeassistant/components/knx/websocket.py | 1 + homeassistant/components/kodi/config_flow.py | 1 + homeassistant/components/kodi/device_trigger.py | 1 + homeassistant/components/kodi/media_player.py | 1 + homeassistant/components/kodi/notify.py | 1 + homeassistant/components/konnected/binary_sensor.py | 1 + homeassistant/components/konnected/config_flow.py | 1 + homeassistant/components/konnected/errors.py | 1 + homeassistant/components/konnected/sensor.py | 1 + homeassistant/components/kostal_plenticore/diagnostics.py | 1 + homeassistant/components/kostal_plenticore/helper.py | 1 + homeassistant/components/kostal_plenticore/number.py | 1 + homeassistant/components/kostal_plenticore/select.py | 1 + homeassistant/components/kostal_plenticore/sensor.py | 1 + homeassistant/components/kostal_plenticore/switch.py | 1 + homeassistant/components/kraken/__init__.py | 1 + homeassistant/components/kraken/config_flow.py | 1 + homeassistant/components/kraken/const.py | 1 + homeassistant/components/kraken/sensor.py | 1 + homeassistant/components/kraken/utils.py | 1 + homeassistant/components/kulersky/light.py | 1 + homeassistant/components/kwb/sensor.py | 1 + homeassistant/components/lacrosse/sensor.py | 1 + homeassistant/components/lacrosse_view/__init__.py | 1 + homeassistant/components/lacrosse_view/config_flow.py | 1 + homeassistant/components/lacrosse_view/coordinator.py | 1 + homeassistant/components/lacrosse_view/diagnostics.py | 1 + homeassistant/components/lacrosse_view/sensor.py | 1 + homeassistant/components/lamarzocco/config_flow.py | 1 + homeassistant/components/lamarzocco/coordinator.py | 1 + homeassistant/components/lamarzocco/switch.py | 1 + homeassistant/components/lametric/__init__.py | 1 + homeassistant/components/lametric/application_credentials.py | 1 + homeassistant/components/lametric/button.py | 1 + homeassistant/components/lametric/config_flow.py | 1 + homeassistant/components/lametric/coordinator.py | 1 + homeassistant/components/lametric/diagnostics.py | 1 + homeassistant/components/lametric/entity.py | 1 + homeassistant/components/lametric/helpers.py | 1 + homeassistant/components/lametric/notify.py | 1 + homeassistant/components/lametric/number.py | 1 + homeassistant/components/lametric/select.py | 1 + homeassistant/components/lametric/sensor.py | 1 + homeassistant/components/lametric/services.py | 1 + homeassistant/components/lametric/switch.py | 1 + homeassistant/components/landisgyr_heat_meter/__init__.py | 1 + homeassistant/components/landisgyr_heat_meter/config_flow.py | 1 + homeassistant/components/landisgyr_heat_meter/sensor.py | 1 + homeassistant/components/lannouncer/notify.py | 1 + homeassistant/components/lastfm/__init__.py | 1 + homeassistant/components/lastfm/config_flow.py | 1 + homeassistant/components/lastfm/coordinator.py | 1 + homeassistant/components/lastfm/sensor.py | 1 + homeassistant/components/launch_library/__init__.py | 1 + homeassistant/components/launch_library/config_flow.py | 1 + homeassistant/components/launch_library/diagnostics.py | 1 + homeassistant/components/launch_library/sensor.py | 1 + homeassistant/components/laundrify/__init__.py | 1 + homeassistant/components/laundrify/binary_sensor.py | 1 + homeassistant/components/laundrify/config_flow.py | 1 + homeassistant/components/laundrify/model.py | 1 + homeassistant/components/lawn_mower/__init__.py | 1 + homeassistant/components/lawn_mower/const.py | 1 + homeassistant/components/lcn/__init__.py | 1 + homeassistant/components/lcn/binary_sensor.py | 1 + homeassistant/components/lcn/climate.py | 1 + homeassistant/components/lcn/config_flow.py | 1 + homeassistant/components/lcn/const.py | 1 + homeassistant/components/lcn/cover.py | 1 + homeassistant/components/lcn/device_trigger.py | 1 + homeassistant/components/lcn/helpers.py | 1 + homeassistant/components/lcn/light.py | 1 + homeassistant/components/lcn/scene.py | 1 + homeassistant/components/lcn/sensor.py | 1 + homeassistant/components/lcn/switch.py | 1 + homeassistant/components/ld2410_ble/config_flow.py | 1 + homeassistant/components/ld2410_ble/models.py | 1 + homeassistant/components/leaone/__init__.py | 1 + homeassistant/components/leaone/config_flow.py | 1 + homeassistant/components/leaone/device.py | 1 + homeassistant/components/leaone/sensor.py | 1 + homeassistant/components/led_ble/__init__.py | 1 + homeassistant/components/led_ble/config_flow.py | 1 + homeassistant/components/led_ble/light.py | 1 + homeassistant/components/led_ble/models.py | 1 + homeassistant/components/lg_netcast/media_player.py | 1 + homeassistant/components/lg_soundbar/media_player.py | 1 + homeassistant/components/lidarr/__init__.py | 1 + homeassistant/components/lidarr/config_flow.py | 1 + homeassistant/components/lidarr/coordinator.py | 1 + homeassistant/components/lidarr/sensor.py | 1 + homeassistant/components/lifx/__init__.py | 1 + homeassistant/components/lifx/binary_sensor.py | 1 + homeassistant/components/lifx/button.py | 1 + homeassistant/components/lifx/config_flow.py | 1 + homeassistant/components/lifx/coordinator.py | 1 + homeassistant/components/lifx/diagnostics.py | 1 + homeassistant/components/lifx/discovery.py | 1 + homeassistant/components/lifx/entity.py | 1 + homeassistant/components/lifx/light.py | 1 + homeassistant/components/lifx/manager.py | 1 + homeassistant/components/lifx/migration.py | 1 + homeassistant/components/lifx/select.py | 1 + homeassistant/components/lifx/sensor.py | 1 + homeassistant/components/lifx_cloud/scene.py | 1 + homeassistant/components/light/__init__.py | 1 + homeassistant/components/light/device_action.py | 1 + homeassistant/components/light/device_condition.py | 1 + homeassistant/components/light/device_trigger.py | 1 + homeassistant/components/light/intent.py | 1 + homeassistant/components/light/reproduce_state.py | 1 + homeassistant/components/light/significant_change.py | 1 + homeassistant/components/lightwave/climate.py | 1 + homeassistant/components/lightwave/light.py | 1 + homeassistant/components/lightwave/sensor.py | 1 + homeassistant/components/lightwave/switch.py | 1 + homeassistant/components/limitlessled/light.py | 1 + homeassistant/components/linear_garage_door/__init__.py | 1 + homeassistant/components/linear_garage_door/config_flow.py | 1 + homeassistant/components/linear_garage_door/coordinator.py | 1 + homeassistant/components/linear_garage_door/diagnostics.py | 1 + homeassistant/components/linksys_smart/device_tracker.py | 1 + homeassistant/components/linode/__init__.py | 1 + homeassistant/components/linode/binary_sensor.py | 1 + homeassistant/components/linode/switch.py | 1 + homeassistant/components/linux_battery/sensor.py | 1 + homeassistant/components/litejet/config_flow.py | 1 + homeassistant/components/litejet/const.py | 1 + homeassistant/components/litejet/diagnostics.py | 1 + homeassistant/components/litejet/light.py | 1 + homeassistant/components/litejet/switch.py | 1 + homeassistant/components/litejet/trigger.py | 1 + homeassistant/components/litterrobot/__init__.py | 1 + homeassistant/components/litterrobot/binary_sensor.py | 1 + homeassistant/components/litterrobot/button.py | 1 + homeassistant/components/litterrobot/config_flow.py | 1 + homeassistant/components/litterrobot/entity.py | 1 + homeassistant/components/litterrobot/hub.py | 1 + homeassistant/components/litterrobot/select.py | 1 + homeassistant/components/litterrobot/sensor.py | 1 + homeassistant/components/litterrobot/switch.py | 1 + homeassistant/components/litterrobot/time.py | 1 + homeassistant/components/litterrobot/update.py | 1 + homeassistant/components/litterrobot/vacuum.py | 1 + homeassistant/components/livisi/__init__.py | 1 + homeassistant/components/livisi/binary_sensor.py | 1 + homeassistant/components/livisi/climate.py | 1 + homeassistant/components/livisi/config_flow.py | 1 + homeassistant/components/livisi/coordinator.py | 1 + homeassistant/components/livisi/entity.py | 1 + homeassistant/components/livisi/switch.py | 1 + homeassistant/components/llamalab_automate/notify.py | 1 + homeassistant/components/local_calendar/__init__.py | 1 + homeassistant/components/local_calendar/config_flow.py | 1 + homeassistant/components/local_file/camera.py | 1 + homeassistant/components/local_ip/__init__.py | 1 + homeassistant/components/local_ip/config_flow.py | 1 + homeassistant/components/local_ip/const.py | 1 + homeassistant/components/local_todo/__init__.py | 1 + homeassistant/components/local_todo/config_flow.py | 1 + homeassistant/components/locative/__init__.py | 1 + homeassistant/components/locative/config_flow.py | 1 + homeassistant/components/locative/device_tracker.py | 1 + homeassistant/components/lock/__init__.py | 1 + homeassistant/components/lock/device_action.py | 1 + homeassistant/components/lock/device_condition.py | 1 + homeassistant/components/lock/device_trigger.py | 1 + homeassistant/components/lock/reproduce_state.py | 1 + homeassistant/components/lock/significant_change.py | 1 + homeassistant/components/logbook/__init__.py | 1 + homeassistant/components/logbook/const.py | 1 + homeassistant/components/logbook/helpers.py | 1 + homeassistant/components/logbook/models.py | 1 + homeassistant/components/logbook/processor.py | 1 + homeassistant/components/logbook/queries/__init__.py | 1 + homeassistant/components/logbook/queries/all.py | 1 + homeassistant/components/logbook/queries/common.py | 1 + homeassistant/components/logbook/queries/devices.py | 1 + homeassistant/components/logbook/queries/entities.py | 1 + homeassistant/components/logbook/queries/entities_and_devices.py | 1 + homeassistant/components/logbook/rest_api.py | 1 + homeassistant/components/logbook/websocket_api.py | 1 + homeassistant/components/logger/__init__.py | 1 + homeassistant/components/logger/helpers.py | 1 + homeassistant/components/logger/websocket_api.py | 1 + homeassistant/components/logi_circle/camera.py | 1 + homeassistant/components/logi_circle/const.py | 1 + homeassistant/components/logi_circle/sensor.py | 1 + homeassistant/components/london_air/sensor.py | 1 + homeassistant/components/london_underground/const.py | 1 + homeassistant/components/london_underground/coordinator.py | 1 + homeassistant/components/london_underground/sensor.py | 1 + homeassistant/components/lookin/__init__.py | 1 + homeassistant/components/lookin/climate.py | 1 + homeassistant/components/lookin/config_flow.py | 1 + homeassistant/components/lookin/const.py | 1 + homeassistant/components/lookin/coordinator.py | 1 + homeassistant/components/lookin/entity.py | 1 + homeassistant/components/lookin/light.py | 1 + homeassistant/components/lookin/media_player.py | 1 + homeassistant/components/lookin/models.py | 1 + homeassistant/components/lookin/sensor.py | 1 + homeassistant/components/loqed/__init__.py | 1 + homeassistant/components/loqed/config_flow.py | 1 + homeassistant/components/loqed/entity.py | 1 + homeassistant/components/loqed/lock.py | 1 + homeassistant/components/loqed/sensor.py | 1 + homeassistant/components/lovelace/const.py | 1 + homeassistant/components/lovelace/dashboard.py | 1 + homeassistant/components/lovelace/resources.py | 1 + homeassistant/components/lovelace/websocket.py | 1 + homeassistant/components/luci/device_tracker.py | 1 + homeassistant/components/luftdaten/__init__.py | 1 + homeassistant/components/luftdaten/config_flow.py | 1 + homeassistant/components/luftdaten/const.py | 1 + homeassistant/components/luftdaten/diagnostics.py | 1 + homeassistant/components/luftdaten/sensor.py | 1 + homeassistant/components/lupusec/__init__.py | 1 + homeassistant/components/lupusec/alarm_control_panel.py | 1 + homeassistant/components/lupusec/binary_sensor.py | 1 + homeassistant/components/lupusec/switch.py | 1 + homeassistant/components/lutron/__init__.py | 1 + homeassistant/components/lutron/binary_sensor.py | 1 + homeassistant/components/lutron/config_flow.py | 1 + homeassistant/components/lutron/cover.py | 1 + homeassistant/components/lutron/fan.py | 1 + homeassistant/components/lutron/light.py | 1 + homeassistant/components/lutron/scene.py | 1 + homeassistant/components/lutron/switch.py | 1 + homeassistant/components/lutron_caseta/__init__.py | 1 + homeassistant/components/lutron_caseta/binary_sensor.py | 1 + homeassistant/components/lutron_caseta/button.py | 1 + homeassistant/components/lutron_caseta/config_flow.py | 1 + homeassistant/components/lutron_caseta/device_trigger.py | 1 + homeassistant/components/lutron_caseta/diagnostics.py | 1 + homeassistant/components/lutron_caseta/fan.py | 1 + homeassistant/components/lutron_caseta/light.py | 1 + homeassistant/components/lutron_caseta/logbook.py | 1 + homeassistant/components/lutron_caseta/models.py | 1 + homeassistant/components/lutron_caseta/scene.py | 1 + homeassistant/components/lutron_caseta/util.py | 1 + homeassistant/components/lw12wifi/light.py | 1 + homeassistant/components/lyric/__init__.py | 1 + homeassistant/components/lyric/api.py | 1 + homeassistant/components/lyric/climate.py | 1 + homeassistant/components/lyric/config_flow.py | 1 + homeassistant/components/lyric/const.py | 1 + homeassistant/components/lyric/sensor.py | 1 + homeassistant/components/mailbox/__init__.py | 1 + homeassistant/components/mailgun/config_flow.py | 1 + homeassistant/components/mailgun/notify.py | 1 + homeassistant/components/manual/alarm_control_panel.py | 1 + homeassistant/components/manual_mqtt/alarm_control_panel.py | 1 + homeassistant/components/map/__init__.py | 1 + homeassistant/components/marytts/tts.py | 1 + homeassistant/components/mastodon/notify.py | 1 + homeassistant/components/matrix/__init__.py | 1 + homeassistant/components/matrix/notify.py | 1 + homeassistant/components/matter/adapter.py | 1 + homeassistant/components/matter/addon.py | 1 + homeassistant/components/matter/api.py | 1 + homeassistant/components/matter/binary_sensor.py | 1 + homeassistant/components/matter/climate.py | 1 + homeassistant/components/matter/config_flow.py | 1 + homeassistant/components/matter/cover.py | 1 + homeassistant/components/matter/diagnostics.py | 1 + homeassistant/components/matter/discovery.py | 1 + homeassistant/components/matter/entity.py | 1 + homeassistant/components/matter/event.py | 1 + homeassistant/components/matter/helpers.py | 1 + homeassistant/components/matter/light.py | 1 + homeassistant/components/matter/lock.py | 1 + homeassistant/components/matter/models.py | 1 + homeassistant/components/matter/sensor.py | 1 + homeassistant/components/matter/switch.py | 1 + homeassistant/components/matter/util.py | 1 + homeassistant/components/maxcube/binary_sensor.py | 1 + homeassistant/components/maxcube/climate.py | 1 + homeassistant/components/mazda/__init__.py | 1 + homeassistant/components/meater/config_flow.py | 1 + homeassistant/components/meater/sensor.py | 1 + homeassistant/components/medcom_ble/__init__.py | 1 + homeassistant/components/medcom_ble/sensor.py | 1 + homeassistant/components/media_extractor/__init__.py | 1 + homeassistant/components/media_player/__init__.py | 1 + homeassistant/components/media_player/browse_media.py | 1 + homeassistant/components/media_player/const.py | 1 + homeassistant/components/media_player/device_condition.py | 1 + homeassistant/components/media_player/device_trigger.py | 1 + homeassistant/components/media_player/errors.py | 1 + homeassistant/components/media_player/reproduce_state.py | 1 + homeassistant/components/media_player/significant_change.py | 1 + homeassistant/components/media_source/__init__.py | 1 + homeassistant/components/media_source/error.py | 1 + homeassistant/components/media_source/local_source.py | 1 + homeassistant/components/media_source/models.py | 1 + homeassistant/components/mediaroom/media_player.py | 1 + homeassistant/components/melcloud/__init__.py | 1 + homeassistant/components/melcloud/climate.py | 1 + homeassistant/components/melcloud/config_flow.py | 1 + homeassistant/components/melcloud/sensor.py | 1 + homeassistant/components/melcloud/water_heater.py | 1 + homeassistant/components/melissa/climate.py | 1 + homeassistant/components/melnor/sensor.py | 1 + homeassistant/components/meraki/device_tracker.py | 1 + homeassistant/components/message_bird/notify.py | 1 + homeassistant/components/met/__init__.py | 1 + homeassistant/components/met/config_flow.py | 1 + homeassistant/components/met/const.py | 1 + homeassistant/components/met/coordinator.py | 1 + homeassistant/components/met/weather.py | 1 + homeassistant/components/met_eireann/__init__.py | 1 + homeassistant/components/met_eireann/config_flow.py | 1 + homeassistant/components/met_eireann/const.py | 1 + homeassistant/components/meteo_france/__init__.py | 1 + homeassistant/components/meteo_france/config_flow.py | 1 + homeassistant/components/meteo_france/const.py | 1 + homeassistant/components/meteo_france/sensor.py | 1 + homeassistant/components/meteoalarm/binary_sensor.py | 1 + homeassistant/components/meteoclimatic/const.py | 1 + homeassistant/components/meteoclimatic/sensor.py | 1 + homeassistant/components/meteoclimatic/weather.py | 1 + homeassistant/components/metoffice/__init__.py | 1 + homeassistant/components/metoffice/config_flow.py | 1 + homeassistant/components/metoffice/const.py | 1 + homeassistant/components/metoffice/data.py | 1 + homeassistant/components/metoffice/helpers.py | 1 + homeassistant/components/metoffice/sensor.py | 1 + homeassistant/components/metoffice/weather.py | 1 + homeassistant/components/mfi/sensor.py | 1 + homeassistant/components/mfi/switch.py | 1 + homeassistant/components/microbees/button.py | 1 + homeassistant/components/microbees/config_flow.py | 1 + homeassistant/components/microbees/const.py | 1 + homeassistant/components/microbees/light.py | 1 + homeassistant/components/microbees/sensor.py | 1 + homeassistant/components/microbees/switch.py | 1 + homeassistant/components/microsoft_face/__init__.py | 1 + .../components/microsoft_face_detect/image_processing.py | 1 + .../components/microsoft_face_identify/image_processing.py | 1 + homeassistant/components/mikrotik/__init__.py | 1 + homeassistant/components/mikrotik/config_flow.py | 1 + homeassistant/components/mikrotik/const.py | 1 + homeassistant/components/mikrotik/device_tracker.py | 1 + homeassistant/components/mikrotik/errors.py | 1 + homeassistant/components/mikrotik/hub.py | 1 + homeassistant/components/mill/__init__.py | 1 + homeassistant/components/mill/config_flow.py | 1 + homeassistant/components/mill/sensor.py | 1 + homeassistant/components/min_max/config_flow.py | 1 + homeassistant/components/min_max/sensor.py | 1 + homeassistant/components/minecraft_server/__init__.py | 1 + homeassistant/components/minecraft_server/binary_sensor.py | 1 + homeassistant/components/minecraft_server/config_flow.py | 1 + homeassistant/components/minecraft_server/coordinator.py | 1 + homeassistant/components/minecraft_server/diagnostics.py | 1 + homeassistant/components/minecraft_server/sensor.py | 1 + homeassistant/components/minio/__init__.py | 1 + homeassistant/components/minio/minio_helper.py | 1 + homeassistant/components/mjpeg/camera.py | 1 + homeassistant/components/mjpeg/config_flow.py | 1 + homeassistant/components/moat/__init__.py | 1 + homeassistant/components/moat/config_flow.py | 1 + homeassistant/components/moat/sensor.py | 1 + homeassistant/components/mobile_app/__init__.py | 1 + homeassistant/components/mobile_app/binary_sensor.py | 1 + homeassistant/components/mobile_app/device_action.py | 1 + homeassistant/components/mobile_app/device_tracker.py | 1 + homeassistant/components/mobile_app/entity.py | 1 + homeassistant/components/mobile_app/helpers.py | 1 + homeassistant/components/mobile_app/http_api.py | 1 + homeassistant/components/mobile_app/logbook.py | 1 + homeassistant/components/mobile_app/notify.py | 1 + homeassistant/components/mobile_app/push_notification.py | 1 + homeassistant/components/mobile_app/sensor.py | 1 + homeassistant/components/mobile_app/util.py | 1 + homeassistant/components/mobile_app/webhook.py | 1 + homeassistant/components/mobile_app/websocket_api.py | 1 + homeassistant/components/mochad/light.py | 1 + homeassistant/components/mochad/switch.py | 1 + homeassistant/components/modbus/__init__.py | 1 + homeassistant/components/modbus/base_platform.py | 1 + homeassistant/components/modbus/binary_sensor.py | 1 + homeassistant/components/modbus/climate.py | 1 + homeassistant/components/modbus/const.py | 1 + homeassistant/components/modbus/cover.py | 1 + homeassistant/components/modbus/fan.py | 1 + homeassistant/components/modbus/light.py | 1 + homeassistant/components/modbus/modbus.py | 1 + homeassistant/components/modbus/sensor.py | 1 + homeassistant/components/modbus/switch.py | 1 + homeassistant/components/modbus/validators.py | 1 + homeassistant/components/modem_callerid/__init__.py | 1 + homeassistant/components/modem_callerid/button.py | 1 + homeassistant/components/modem_callerid/config_flow.py | 1 + homeassistant/components/modem_callerid/const.py | 1 + homeassistant/components/modem_callerid/sensor.py | 1 + homeassistant/components/modern_forms/__init__.py | 1 + homeassistant/components/modern_forms/binary_sensor.py | 1 + homeassistant/components/modern_forms/config_flow.py | 1 + homeassistant/components/modern_forms/fan.py | 1 + homeassistant/components/modern_forms/light.py | 1 + homeassistant/components/modern_forms/sensor.py | 1 + homeassistant/components/modern_forms/switch.py | 1 + homeassistant/components/moehlenhoff_alpha2/__init__.py | 1 + homeassistant/components/mold_indicator/sensor.py | 1 + homeassistant/components/monoprice/config_flow.py | 1 + homeassistant/components/moon/__init__.py | 1 + homeassistant/components/moon/config_flow.py | 1 + homeassistant/components/moon/const.py | 1 + homeassistant/components/moon/sensor.py | 1 + homeassistant/components/mopeka/__init__.py | 1 + homeassistant/components/mopeka/config_flow.py | 1 + homeassistant/components/mopeka/device.py | 1 + homeassistant/components/mopeka/sensor.py | 1 + homeassistant/components/motion_blinds/config_flow.py | 1 + homeassistant/components/motion_blinds/const.py | 1 + homeassistant/components/motion_blinds/cover.py | 1 + homeassistant/components/motion_blinds/entity.py | 1 + homeassistant/components/motion_blinds/sensor.py | 1 + homeassistant/components/motioneye/__init__.py | 1 + homeassistant/components/motioneye/camera.py | 1 + homeassistant/components/motioneye/config_flow.py | 1 + homeassistant/components/motioneye/const.py | 1 + homeassistant/components/motioneye/media_source.py | 1 + homeassistant/components/motioneye/sensor.py | 1 + homeassistant/components/motioneye/switch.py | 1 + homeassistant/components/motionmount/__init__.py | 1 + homeassistant/components/mpd/media_player.py | 1 + homeassistant/components/mqtt/__init__.py | 1 + homeassistant/components/mqtt/alarm_control_panel.py | 1 + homeassistant/components/mqtt/binary_sensor.py | 1 + homeassistant/components/mqtt/button.py | 1 + homeassistant/components/mqtt/camera.py | 1 + homeassistant/components/mqtt/client.py | 1 + homeassistant/components/mqtt/climate.py | 1 + homeassistant/components/mqtt/config.py | 1 + homeassistant/components/mqtt/config_flow.py | 1 + homeassistant/components/mqtt/config_integration.py | 1 + homeassistant/components/mqtt/cover.py | 1 + homeassistant/components/mqtt/debug_info.py | 1 + homeassistant/components/mqtt/device_automation.py | 1 + homeassistant/components/mqtt/device_tracker.py | 1 + homeassistant/components/mqtt/device_trigger.py | 1 + homeassistant/components/mqtt/diagnostics.py | 1 + homeassistant/components/mqtt/discovery.py | 1 + homeassistant/components/mqtt/event.py | 1 + homeassistant/components/mqtt/fan.py | 1 + homeassistant/components/mqtt/humidifier.py | 1 + homeassistant/components/mqtt/image.py | 1 + homeassistant/components/mqtt/lawn_mower.py | 1 + homeassistant/components/mqtt/light/__init__.py | 1 + homeassistant/components/mqtt/light/schema_basic.py | 1 + homeassistant/components/mqtt/light/schema_json.py | 1 + homeassistant/components/mqtt/light/schema_template.py | 1 + homeassistant/components/mqtt/lock.py | 1 + homeassistant/components/mqtt/mixins.py | 1 + homeassistant/components/mqtt/models.py | 1 + homeassistant/components/mqtt/number.py | 1 + homeassistant/components/mqtt/scene.py | 1 + homeassistant/components/mqtt/select.py | 1 + homeassistant/components/mqtt/sensor.py | 1 + homeassistant/components/mqtt/siren.py | 1 + homeassistant/components/mqtt/subscription.py | 1 + homeassistant/components/mqtt/switch.py | 1 + homeassistant/components/mqtt/tag.py | 1 + homeassistant/components/mqtt/text.py | 1 + homeassistant/components/mqtt/trigger.py | 1 + homeassistant/components/mqtt/update.py | 1 + homeassistant/components/mqtt/valve.py | 1 + homeassistant/components/mqtt/water_heater.py | 1 + homeassistant/components/mqtt_json/device_tracker.py | 1 + homeassistant/components/mqtt_room/sensor.py | 1 + homeassistant/components/msteams/notify.py | 1 + homeassistant/components/mullvad/binary_sensor.py | 1 + homeassistant/components/mullvad/config_flow.py | 1 + homeassistant/components/mutesync/__init__.py | 1 + homeassistant/components/mutesync/binary_sensor.py | 1 + homeassistant/components/mutesync/config_flow.py | 1 + homeassistant/components/mutesync/const.py | 1 + homeassistant/components/mvglive/sensor.py | 1 + homeassistant/components/my/__init__.py | 1 + homeassistant/components/mycroft/notify.py | 1 + homeassistant/components/myq/__init__.py | 1 + homeassistant/components/mysensors/__init__.py | 1 + homeassistant/components/mysensors/binary_sensor.py | 1 + homeassistant/components/mysensors/climate.py | 1 + homeassistant/components/mysensors/config_flow.py | 1 + homeassistant/components/mysensors/const.py | 1 + homeassistant/components/mysensors/cover.py | 1 + homeassistant/components/mysensors/device.py | 1 + homeassistant/components/mysensors/device_tracker.py | 1 + homeassistant/components/mysensors/gateway.py | 1 + homeassistant/components/mysensors/handler.py | 1 + homeassistant/components/mysensors/helpers.py | 1 + homeassistant/components/mysensors/light.py | 1 + homeassistant/components/mysensors/remote.py | 1 + homeassistant/components/mysensors/sensor.py | 1 + homeassistant/components/mysensors/switch.py | 1 + homeassistant/components/mysensors/text.py | 1 + homeassistant/components/mystrom/__init__.py | 1 + homeassistant/components/mystrom/binary_sensor.py | 1 + homeassistant/components/mystrom/config_flow.py | 1 + homeassistant/components/mystrom/light.py | 1 + homeassistant/components/mystrom/models.py | 1 + homeassistant/components/mystrom/sensor.py | 1 + homeassistant/components/mystrom/switch.py | 1 + homeassistant/components/mythicbeastsdns/__init__.py | 1 + homeassistant/components/myuplink/__init__.py | 1 + homeassistant/components/myuplink/api.py | 1 + homeassistant/components/myuplink/config_flow.py | 1 + homeassistant/components/myuplink/diagnostics.py | 1 + homeassistant/components/myuplink/entity.py | 1 + 606 files changed, 606 insertions(+) diff --git a/homeassistant/components/jellyfin/__init__.py b/homeassistant/components/jellyfin/__init__.py index 2e9e6bb71f7..c24f06d7b19 100644 --- a/homeassistant/components/jellyfin/__init__.py +++ b/homeassistant/components/jellyfin/__init__.py @@ -1,4 +1,5 @@ """The Jellyfin integration.""" + from typing import Any from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/jellyfin/browse_media.py b/homeassistant/components/jellyfin/browse_media.py index ac47bcf732f..2af2bac4875 100644 --- a/homeassistant/components/jellyfin/browse_media.py +++ b/homeassistant/components/jellyfin/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/jellyfin/client_wrapper.py b/homeassistant/components/jellyfin/client_wrapper.py index ab771d405ea..ab5d5e7d7f8 100644 --- a/homeassistant/components/jellyfin/client_wrapper.py +++ b/homeassistant/components/jellyfin/client_wrapper.py @@ -1,4 +1,5 @@ """Utility methods for initializing a Jellyfin client.""" + from __future__ import annotations import socket diff --git a/homeassistant/components/jellyfin/config_flow.py b/homeassistant/components/jellyfin/config_flow.py index b6702722006..5ccff4ffdb3 100644 --- a/homeassistant/components/jellyfin/config_flow.py +++ b/homeassistant/components/jellyfin/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Jellyfin integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/jellyfin/coordinator.py b/homeassistant/components/jellyfin/coordinator.py index f4ab98ca268..4d907ac1531 100644 --- a/homeassistant/components/jellyfin/coordinator.py +++ b/homeassistant/components/jellyfin/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Jellyfin integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/jellyfin/diagnostics.py b/homeassistant/components/jellyfin/diagnostics.py index 36b2882fbeb..ecc66868bd0 100644 --- a/homeassistant/components/jellyfin/diagnostics.py +++ b/homeassistant/components/jellyfin/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Jellyfin.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/jellyfin/entity.py b/homeassistant/components/jellyfin/entity.py index e45557fa4b6..2204a36dc61 100644 --- a/homeassistant/components/jellyfin/entity.py +++ b/homeassistant/components/jellyfin/entity.py @@ -1,4 +1,5 @@ """Base Entity for Jellyfin.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo diff --git a/homeassistant/components/jellyfin/media_player.py b/homeassistant/components/jellyfin/media_player.py index 76343818702..ec1f11de9d7 100644 --- a/homeassistant/components/jellyfin/media_player.py +++ b/homeassistant/components/jellyfin/media_player.py @@ -1,4 +1,5 @@ """Support for the Jellyfin media player.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/jellyfin/media_source.py b/homeassistant/components/jellyfin/media_source.py index 3bbe3e0b184..add04d1a1ec 100644 --- a/homeassistant/components/jellyfin/media_source.py +++ b/homeassistant/components/jellyfin/media_source.py @@ -1,4 +1,5 @@ """The Media Source implementation for the Jellyfin integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/jellyfin/models.py b/homeassistant/components/jellyfin/models.py index b6365042127..bfa639a7567 100644 --- a/homeassistant/components/jellyfin/models.py +++ b/homeassistant/components/jellyfin/models.py @@ -1,4 +1,5 @@ """Models for the Jellyfin integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/jellyfin/sensor.py b/homeassistant/components/jellyfin/sensor.py index df503d14378..b4341d2a190 100644 --- a/homeassistant/components/jellyfin/sensor.py +++ b/homeassistant/components/jellyfin/sensor.py @@ -1,4 +1,5 @@ """Support for Jellyfin sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/jewish_calendar/__init__.py b/homeassistant/components/jewish_calendar/__init__.py index 550ca2d9e5d..1ce5386d2c2 100644 --- a/homeassistant/components/jewish_calendar/__init__.py +++ b/homeassistant/components/jewish_calendar/__init__.py @@ -1,4 +1,5 @@ """The jewish_calendar component.""" + from __future__ import annotations from hdate import Location diff --git a/homeassistant/components/jewish_calendar/binary_sensor.py b/homeassistant/components/jewish_calendar/binary_sensor.py index 638d54d6159..73ddca27cc1 100644 --- a/homeassistant/components/jewish_calendar/binary_sensor.py +++ b/homeassistant/components/jewish_calendar/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Jewish Calendar binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index b20364f7e64..2a16ecb9c14 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -1,4 +1,5 @@ """Platform to retrieve Jewish calendar information for Home Assistant.""" + from __future__ import annotations from datetime import date as Date diff --git a/homeassistant/components/joaoapps_join/notify.py b/homeassistant/components/joaoapps_join/notify.py index 33eede4dc21..6e9efc4da21 100644 --- a/homeassistant/components/joaoapps_join/notify.py +++ b/homeassistant/components/joaoapps_join/notify.py @@ -1,4 +1,5 @@ """Support for Join notifications.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/juicenet/__init__.py b/homeassistant/components/juicenet/__init__.py index bcefe763e15..5c32caab36f 100644 --- a/homeassistant/components/juicenet/__init__.py +++ b/homeassistant/components/juicenet/__init__.py @@ -1,4 +1,5 @@ """The JuiceNet integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/juicenet/number.py b/homeassistant/components/juicenet/number.py index fd2535c5bf3..173ebd45265 100644 --- a/homeassistant/components/juicenet/number.py +++ b/homeassistant/components/juicenet/number.py @@ -1,4 +1,5 @@ """Support for controlling juicenet/juicepoint/juicebox based EVSE numbers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py index 5f71e066b9c..1f0b815cd97 100644 --- a/homeassistant/components/juicenet/sensor.py +++ b/homeassistant/components/juicenet/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring juicenet/juicepoint/juicebox based EVSE sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/juicenet/switch.py b/homeassistant/components/juicenet/switch.py index 7c373eeeb24..a0075fe6c3e 100644 --- a/homeassistant/components/juicenet/switch.py +++ b/homeassistant/components/juicenet/switch.py @@ -1,4 +1,5 @@ """Support for monitoring juicenet/juicepoint/juicebox based EVSE switches.""" + from typing import Any from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/justnimbus/__init__.py b/homeassistant/components/justnimbus/__init__.py index c30e213814e..e4be25b8959 100644 --- a/homeassistant/components/justnimbus/__init__.py +++ b/homeassistant/components/justnimbus/__init__.py @@ -1,4 +1,5 @@ """The JustNimbus integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/justnimbus/config_flow.py b/homeassistant/components/justnimbus/config_flow.py index 08c02d40f94..2a286c41b5f 100644 --- a/homeassistant/components/justnimbus/config_flow.py +++ b/homeassistant/components/justnimbus/config_flow.py @@ -1,4 +1,5 @@ """Config flow for JustNimbus integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/justnimbus/coordinator.py b/homeassistant/components/justnimbus/coordinator.py index 9dc7dcbc743..4031ad86fdf 100644 --- a/homeassistant/components/justnimbus/coordinator.py +++ b/homeassistant/components/justnimbus/coordinator.py @@ -1,4 +1,5 @@ """JustNimbus coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/justnimbus/entity.py b/homeassistant/components/justnimbus/entity.py index 7303d4ec2c7..f85c3f33f93 100644 --- a/homeassistant/components/justnimbus/entity.py +++ b/homeassistant/components/justnimbus/entity.py @@ -1,4 +1,5 @@ """Base Entity for JustNimbus sensors.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/justnimbus/sensor.py b/homeassistant/components/justnimbus/sensor.py index 6a570a56003..5acea40fd52 100644 --- a/homeassistant/components/justnimbus/sensor.py +++ b/homeassistant/components/justnimbus/sensor.py @@ -1,4 +1,5 @@ """Support for the JustNimbus platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/kaiterra/air_quality.py b/homeassistant/components/kaiterra/air_quality.py index edbddb361c9..4d0d83a38eb 100644 --- a/homeassistant/components/kaiterra/air_quality.py +++ b/homeassistant/components/kaiterra/air_quality.py @@ -1,4 +1,5 @@ """Support for Kaiterra Air Quality Sensors.""" + from __future__ import annotations from homeassistant.components.air_quality import AirQualityEntity diff --git a/homeassistant/components/kaiterra/sensor.py b/homeassistant/components/kaiterra/sensor.py index bb780aab619..fa15cb9b451 100644 --- a/homeassistant/components/kaiterra/sensor.py +++ b/homeassistant/components/kaiterra/sensor.py @@ -1,4 +1,5 @@ """Support for Kaiterra Temperature ahn Humidity Sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/kaleidescape/media_player.py b/homeassistant/components/kaleidescape/media_player.py index 7751f6b6a29..33acb899728 100644 --- a/homeassistant/components/kaleidescape/media_player.py +++ b/homeassistant/components/kaleidescape/media_player.py @@ -1,4 +1,5 @@ """Kaleidescape Media Player.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kankun/switch.py b/homeassistant/components/kankun/switch.py index f64b11706a1..f650494b3b1 100644 --- a/homeassistant/components/kankun/switch.py +++ b/homeassistant/components/kankun/switch.py @@ -1,4 +1,5 @@ """Support for customised Kankun SP3 Wifi switch.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/keba/binary_sensor.py b/homeassistant/components/keba/binary_sensor.py index 7997130c90a..9f8e0ac3f3e 100644 --- a/homeassistant/components/keba/binary_sensor.py +++ b/homeassistant/components/keba/binary_sensor.py @@ -1,4 +1,5 @@ """Support for KEBA charging station binary sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/keba/lock.py b/homeassistant/components/keba/lock.py index de8d28d7739..be005b92874 100644 --- a/homeassistant/components/keba/lock.py +++ b/homeassistant/components/keba/lock.py @@ -1,4 +1,5 @@ """Support for KEBA charging station switch.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/keba/notify.py b/homeassistant/components/keba/notify.py index 78b3976f656..5358ba32ff9 100644 --- a/homeassistant/components/keba/notify.py +++ b/homeassistant/components/keba/notify.py @@ -1,4 +1,5 @@ """Support for Keba notifications.""" + from __future__ import annotations from homeassistant.components.notify import ATTR_DATA, BaseNotificationService diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index 635683419b2..74c08933cbe 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -1,4 +1,5 @@ """Support for KEBA charging station sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/keenetic_ndms2/__init__.py b/homeassistant/components/keenetic_ndms2/__init__.py index 6f33b11742a..e2ca17ebce8 100644 --- a/homeassistant/components/keenetic_ndms2/__init__.py +++ b/homeassistant/components/keenetic_ndms2/__init__.py @@ -1,4 +1,5 @@ """The keenetic_ndms2 component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/keenetic_ndms2/binary_sensor.py b/homeassistant/components/keenetic_ndms2/binary_sensor.py index ab0b3370197..cb7d83b9238 100644 --- a/homeassistant/components/keenetic_ndms2/binary_sensor.py +++ b/homeassistant/components/keenetic_ndms2/binary_sensor.py @@ -1,4 +1,5 @@ """The Keenetic Client class.""" + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, diff --git a/homeassistant/components/keenetic_ndms2/config_flow.py b/homeassistant/components/keenetic_ndms2/config_flow.py index 553204512e5..f00bbe22939 100644 --- a/homeassistant/components/keenetic_ndms2/config_flow.py +++ b/homeassistant/components/keenetic_ndms2/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Keenetic NDMS2.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index c9e81071ad7..e15c96d8353 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -1,4 +1,5 @@ """Support for Keenetic routers as device tracker.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/keenetic_ndms2/router.py b/homeassistant/components/keenetic_ndms2/router.py index 77101dcbf3e..5a4f32a05cd 100644 --- a/homeassistant/components/keenetic_ndms2/router.py +++ b/homeassistant/components/keenetic_ndms2/router.py @@ -1,4 +1,5 @@ """The Keenetic Client class.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index b8407fd8bde..03f3468dc15 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -1,4 +1,5 @@ """Platform for the KEF Wireless Speakers.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/kegtron/__init__.py b/homeassistant/components/kegtron/__init__.py index 7a1669bdcd4..55b93a0da3d 100644 --- a/homeassistant/components/kegtron/__init__.py +++ b/homeassistant/components/kegtron/__init__.py @@ -1,4 +1,5 @@ """The Kegtron integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kegtron/config_flow.py b/homeassistant/components/kegtron/config_flow.py index 8e657de800d..8dfb28f0caa 100644 --- a/homeassistant/components/kegtron/config_flow.py +++ b/homeassistant/components/kegtron/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Kegtron ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kegtron/device.py b/homeassistant/components/kegtron/device.py index 85516a3aea3..033094e41d7 100644 --- a/homeassistant/components/kegtron/device.py +++ b/homeassistant/components/kegtron/device.py @@ -1,4 +1,5 @@ """Support for Kegtron devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kegtron/sensor.py b/homeassistant/components/kegtron/sensor.py index 8e1ed539385..4fc4ac9242f 100644 --- a/homeassistant/components/kegtron/sensor.py +++ b/homeassistant/components/kegtron/sensor.py @@ -1,4 +1,5 @@ """Support for Kegtron sensors.""" + from __future__ import annotations from kegtron_ble import ( diff --git a/homeassistant/components/keyboard/__init__.py b/homeassistant/components/keyboard/__init__.py index d129505515d..bf935f119d0 100644 --- a/homeassistant/components/keyboard/__init__.py +++ b/homeassistant/components/keyboard/__init__.py @@ -1,4 +1,5 @@ """Support to emulate keyboard presses on host machine.""" + from pykeyboard import PyKeyboard import voluptuous as vol diff --git a/homeassistant/components/keyboard_remote/__init__.py b/homeassistant/components/keyboard_remote/__init__.py index eecde05d1f4..5831a770466 100644 --- a/homeassistant/components/keyboard_remote/__init__.py +++ b/homeassistant/components/keyboard_remote/__init__.py @@ -1,4 +1,5 @@ """Receive signals from a keyboard and use it as a remote control.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/keymitt_ble/__init__.py b/homeassistant/components/keymitt_ble/__init__.py index 1a7df4fe0a9..7fea46d7a02 100644 --- a/homeassistant/components/keymitt_ble/__init__.py +++ b/homeassistant/components/keymitt_ble/__init__.py @@ -1,4 +1,5 @@ """Integration to integrate Keymitt BLE devices with Home Assistant.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/keymitt_ble/config_flow.py b/homeassistant/components/keymitt_ble/config_flow.py index d0fb6ff0317..589798a281a 100644 --- a/homeassistant/components/keymitt_ble/config_flow.py +++ b/homeassistant/components/keymitt_ble/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for MicroBot.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/keymitt_ble/coordinator.py b/homeassistant/components/keymitt_ble/coordinator.py index e3a995e3813..3e72826ac5d 100644 --- a/homeassistant/components/keymitt_ble/coordinator.py +++ b/homeassistant/components/keymitt_ble/coordinator.py @@ -1,4 +1,5 @@ """Integration to integrate Keymitt BLE devices with Home Assistant.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/keymitt_ble/entity.py b/homeassistant/components/keymitt_ble/entity.py index a9294bce239..b5229e6917e 100644 --- a/homeassistant/components/keymitt_ble/entity.py +++ b/homeassistant/components/keymitt_ble/entity.py @@ -1,4 +1,5 @@ """MicroBot class.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/keymitt_ble/switch.py b/homeassistant/components/keymitt_ble/switch.py index 4c9f0c335a7..2c255ecdf28 100644 --- a/homeassistant/components/keymitt_ble/switch.py +++ b/homeassistant/components/keymitt_ble/switch.py @@ -1,4 +1,5 @@ """Switch platform for MicroBot.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kira/remote.py b/homeassistant/components/kira/remote.py index 4c06216a210..f6ee4af75ef 100644 --- a/homeassistant/components/kira/remote.py +++ b/homeassistant/components/kira/remote.py @@ -1,4 +1,5 @@ """Support for Keene Electronics IR-IP devices.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/kira/sensor.py b/homeassistant/components/kira/sensor.py index e1a4f08dd14..5779ed4df35 100644 --- a/homeassistant/components/kira/sensor.py +++ b/homeassistant/components/kira/sensor.py @@ -1,4 +1,5 @@ """KIRA interface to receive UDP packets from an IR-IP bridge.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kitchen_sink/__init__.py b/homeassistant/components/kitchen_sink/__init__.py index 228803097d6..6b6694c920d 100644 --- a/homeassistant/components/kitchen_sink/__init__.py +++ b/homeassistant/components/kitchen_sink/__init__.py @@ -3,6 +3,7 @@ This sets up a demo environment of features which are obscure or which represent incorrect behavior, and are thus not wanted in the demo integration. """ + from __future__ import annotations import datetime diff --git a/homeassistant/components/kitchen_sink/button.py b/homeassistant/components/kitchen_sink/button.py index cdc0cebb348..5c62c4b32d1 100644 --- a/homeassistant/components/kitchen_sink/button.py +++ b/homeassistant/components/kitchen_sink/button.py @@ -1,4 +1,5 @@ """Demo platform that offers a fake button entity.""" + from __future__ import annotations from homeassistant.components import persistent_notification diff --git a/homeassistant/components/kitchen_sink/config_flow.py b/homeassistant/components/kitchen_sink/config_flow.py index 56df82047ad..93c8a292ba9 100644 --- a/homeassistant/components/kitchen_sink/config_flow.py +++ b/homeassistant/components/kitchen_sink/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Kitchen Sink component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kitchen_sink/image.py b/homeassistant/components/kitchen_sink/image.py index 4fe20f08de9..504b36464f5 100644 --- a/homeassistant/components/kitchen_sink/image.py +++ b/homeassistant/components/kitchen_sink/image.py @@ -1,4 +1,5 @@ """Demo image platform.""" + from __future__ import annotations from pathlib import Path diff --git a/homeassistant/components/kitchen_sink/lawn_mower.py b/homeassistant/components/kitchen_sink/lawn_mower.py index 119b37b7569..50ec70f6759 100644 --- a/homeassistant/components/kitchen_sink/lawn_mower.py +++ b/homeassistant/components/kitchen_sink/lawn_mower.py @@ -1,4 +1,5 @@ """Demo platform that has a couple fake lawn mowers.""" + from __future__ import annotations from homeassistant.components.lawn_mower import ( diff --git a/homeassistant/components/kitchen_sink/lock.py b/homeassistant/components/kitchen_sink/lock.py index b25941cf1a3..228e383e94d 100644 --- a/homeassistant/components/kitchen_sink/lock.py +++ b/homeassistant/components/kitchen_sink/lock.py @@ -1,4 +1,5 @@ """Demo platform that has a couple of fake locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kitchen_sink/sensor.py b/homeassistant/components/kitchen_sink/sensor.py index 4800104d17d..95e56c276e4 100644 --- a/homeassistant/components/kitchen_sink/sensor.py +++ b/homeassistant/components/kitchen_sink/sensor.py @@ -1,4 +1,5 @@ """Demo platform that has a couple of fake sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/kitchen_sink/switch.py b/homeassistant/components/kitchen_sink/switch.py index e60de2f09c8..68a8312b496 100644 --- a/homeassistant/components/kitchen_sink/switch.py +++ b/homeassistant/components/kitchen_sink/switch.py @@ -1,4 +1,5 @@ """Demo platform that has some fake switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kitchen_sink/weather.py b/homeassistant/components/kitchen_sink/weather.py index 8449b68b460..0ade3e73889 100644 --- a/homeassistant/components/kitchen_sink/weather.py +++ b/homeassistant/components/kitchen_sink/weather.py @@ -1,4 +1,5 @@ """Demo platform that offers fake meteorological data.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py index 44dc2bb2521..770b842091c 100644 --- a/homeassistant/components/kiwi/lock.py +++ b/homeassistant/components/kiwi/lock.py @@ -1,4 +1,5 @@ """Support for the KIWI.KI lock platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kmtronic/config_flow.py b/homeassistant/components/kmtronic/config_flow.py index e1d0bf95d09..dd0a7652418 100644 --- a/homeassistant/components/kmtronic/config_flow.py +++ b/homeassistant/components/kmtronic/config_flow.py @@ -1,4 +1,5 @@ """Config flow for kmtronic integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kmtronic/switch.py b/homeassistant/components/kmtronic/switch.py index 144c05e927e..f00ecf8623c 100644 --- a/homeassistant/components/kmtronic/switch.py +++ b/homeassistant/components/kmtronic/switch.py @@ -1,4 +1,5 @@ """KMtronic Switch integration.""" + from typing import Any import urllib.parse diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index c6869f34eeb..c84d53d6039 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -1,4 +1,5 @@ """Support KNX devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index 9005ca707b9..dee56608421 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,4 +1,5 @@ """Support for KNX/IP binary sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/knx/button.py b/homeassistant/components/knx/button.py index 94b5b51e401..a38d8ad1b6c 100644 --- a/homeassistant/components/knx/button.py +++ b/homeassistant/components/knx/button.py @@ -1,4 +1,5 @@ """Support for KNX/IP buttons.""" + from __future__ import annotations from xknx import XKNX diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 1038cdde80f..30b7a3bba74 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -1,4 +1,5 @@ """Support for KNX/IP climate devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index e095f71f924..bac16798f63 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -1,4 +1,5 @@ """Config flow for KNX.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index 8cb1986c540..9c0d5e1125a 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -1,4 +1,5 @@ """Constants for the KNX integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 9e86fc8b36e..9d86d6ac272 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -1,4 +1,5 @@ """Support for KNX/IP covers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/knx/date.py b/homeassistant/components/knx/date.py index 1f286d59ecb..fa20a8d04c5 100644 --- a/homeassistant/components/knx/date.py +++ b/homeassistant/components/knx/date.py @@ -1,4 +1,5 @@ """Support for KNX/IP date.""" + from __future__ import annotations from datetime import date as dt_date diff --git a/homeassistant/components/knx/datetime.py b/homeassistant/components/knx/datetime.py index fc63df04233..47d9b9f55b2 100644 --- a/homeassistant/components/knx/datetime.py +++ b/homeassistant/components/knx/datetime.py @@ -1,4 +1,5 @@ """Support for KNX/IP datetime.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/knx/device.py b/homeassistant/components/knx/device.py index 583ca2f768b..fd5abc6a072 100644 --- a/homeassistant/components/knx/device.py +++ b/homeassistant/components/knx/device.py @@ -1,4 +1,5 @@ """Handle KNX Devices.""" + from __future__ import annotations from xknx import XKNX diff --git a/homeassistant/components/knx/device_trigger.py b/homeassistant/components/knx/device_trigger.py index 867a7c075b0..93e1623f88c 100644 --- a/homeassistant/components/knx/device_trigger.py +++ b/homeassistant/components/knx/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for KNX.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/knx/diagnostics.py b/homeassistant/components/knx/diagnostics.py index 2fada718d31..1907539fc61 100644 --- a/homeassistant/components/knx/diagnostics.py +++ b/homeassistant/components/knx/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for KNX.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index d5c871d59ba..aee6e148e62 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -1,4 +1,5 @@ """Exposures to KNX bus.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index a22a16a6e69..e94609bdc86 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -1,4 +1,5 @@ """Support for KNX/IP fans.""" + from __future__ import annotations import math diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index 9545510e635..b03c59486e5 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -1,4 +1,5 @@ """Base class for KNX devices.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index f25e78a4d70..b1c1681a817 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -1,4 +1,5 @@ """Support for KNX/IP lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 9d190ac78b0..21f8290586a 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -1,4 +1,5 @@ """Support for KNX/IP notification services.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/knx/number.py b/homeassistant/components/knx/number.py index fbf4db3f5b2..8a9f1dea87c 100644 --- a/homeassistant/components/knx/number.py +++ b/homeassistant/components/knx/number.py @@ -1,4 +1,5 @@ """Support for KNX/IP numeric values.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/knx/project.py b/homeassistant/components/knx/project.py index d47241b174b..13e71dbbe38 100644 --- a/homeassistant/components/knx/project.py +++ b/homeassistant/components/knx/project.py @@ -1,4 +1,5 @@ """Handle KNX project data.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index a028cebc8f7..342d0f9eb83 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -1,4 +1,5 @@ """Support for KNX scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index d559cd2005a..39670b4f92b 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -1,4 +1,5 @@ """Voluptuous schemas for the KNX integration.""" + from __future__ import annotations from abc import ABC diff --git a/homeassistant/components/knx/select.py b/homeassistant/components/knx/select.py index 2852917e021..5d7532e0e5d 100644 --- a/homeassistant/components/knx/select.py +++ b/homeassistant/components/knx/select.py @@ -1,4 +1,5 @@ """Support for KNX/IP select entities.""" + from __future__ import annotations from xknx import XKNX diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index be6359c783d..173979f78dc 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -1,4 +1,5 @@ """Support for KNX/IP sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/knx/services.py b/homeassistant/components/knx/services.py index 99c44a5eee6..24b9452cf60 100644 --- a/homeassistant/components/knx/services.py +++ b/homeassistant/components/knx/services.py @@ -1,4 +1,5 @@ """KNX integration services.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index 81f8de815c9..096ce235e2c 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -1,4 +1,5 @@ """Support for KNX/IP switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/knx/telegrams.py b/homeassistant/components/knx/telegrams.py index 95250d99f85..7c3ea28c4df 100644 --- a/homeassistant/components/knx/telegrams.py +++ b/homeassistant/components/knx/telegrams.py @@ -1,4 +1,5 @@ """KNX Telegram handler.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/knx/text.py b/homeassistant/components/knx/text.py index abd3f44ae6b..22d008cd5ce 100644 --- a/homeassistant/components/knx/text.py +++ b/homeassistant/components/knx/text.py @@ -1,4 +1,5 @@ """Support for KNX/IP text.""" + from __future__ import annotations from xknx import XKNX diff --git a/homeassistant/components/knx/time.py b/homeassistant/components/knx/time.py index af8ee48b806..c11b40d13dc 100644 --- a/homeassistant/components/knx/time.py +++ b/homeassistant/components/knx/time.py @@ -1,4 +1,5 @@ """Support for KNX/IP time.""" + from __future__ import annotations from datetime import time as dt_time diff --git a/homeassistant/components/knx/validation.py b/homeassistant/components/knx/validation.py index c0ac93d19eb..9fe87a2c3f6 100644 --- a/homeassistant/components/knx/validation.py +++ b/homeassistant/components/knx/validation.py @@ -1,4 +1,5 @@ """Validation helpers for KNX config schemas.""" + from collections.abc import Callable import ipaddress from typing import Any diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py index 92034be95ff..90796f26f1a 100644 --- a/homeassistant/components/knx/weather.py +++ b/homeassistant/components/knx/weather.py @@ -1,4 +1,5 @@ """Support for KNX/IP weather station.""" + from __future__ import annotations from xknx import XKNX diff --git a/homeassistant/components/knx/websocket.py b/homeassistant/components/knx/websocket.py index e3eb5de8530..f6869902793 100644 --- a/homeassistant/components/knx/websocket.py +++ b/homeassistant/components/knx/websocket.py @@ -1,4 +1,5 @@ """KNX Websocket API.""" + from __future__ import annotations from typing import TYPE_CHECKING, Final diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index 74fc5e0cb09..30cac5dbdd4 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Kodi integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/kodi/device_trigger.py b/homeassistant/components/kodi/device_trigger.py index 3f931d1e264..8659872f8c1 100644 --- a/homeassistant/components/kodi/device_trigger.py +++ b/homeassistant/components/kodi/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Kodi.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index bca1c7f6f0e..74140ca873c 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with the XBMC/Kodi JSON-RPC API.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/kodi/notify.py b/homeassistant/components/kodi/notify.py index f3459e891b7..7d65321a807 100644 --- a/homeassistant/components/kodi/notify.py +++ b/homeassistant/components/kodi/notify.py @@ -1,4 +1,5 @@ """Kodi notification service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/konnected/binary_sensor.py b/homeassistant/components/konnected/binary_sensor.py index d7c41337342..75c381c53f2 100644 --- a/homeassistant/components/konnected/binary_sensor.py +++ b/homeassistant/components/konnected/binary_sensor.py @@ -1,4 +1,5 @@ """Support for wired binary sensors attached to a Konnected device.""" + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index f6225760feb..e2b460042f1 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -1,4 +1,5 @@ """Config flow for konnected.io integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/konnected/errors.py b/homeassistant/components/konnected/errors.py index 5a0207f3f8d..a377942a02f 100644 --- a/homeassistant/components/konnected/errors.py +++ b/homeassistant/components/konnected/errors.py @@ -1,4 +1,5 @@ """Errors for the Konnected component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/konnected/sensor.py b/homeassistant/components/konnected/sensor.py index 3f203d5f3e8..6191f98f179 100644 --- a/homeassistant/components/konnected/sensor.py +++ b/homeassistant/components/konnected/sensor.py @@ -1,4 +1,5 @@ """Support for DHT and DS18B20 sensors attached to a Konnected device.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/kostal_plenticore/diagnostics.py b/homeassistant/components/kostal_plenticore/diagnostics.py index eef9f05537f..9b78265971c 100644 --- a/homeassistant/components/kostal_plenticore/diagnostics.py +++ b/homeassistant/components/kostal_plenticore/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Kostal Plenticore.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kostal_plenticore/helper.py b/homeassistant/components/kostal_plenticore/helper.py index a04415a4f31..d9117625d19 100644 --- a/homeassistant/components/kostal_plenticore/helper.py +++ b/homeassistant/components/kostal_plenticore/helper.py @@ -1,4 +1,5 @@ """Code to handle the Plenticore API.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/kostal_plenticore/number.py b/homeassistant/components/kostal_plenticore/number.py index 36e1fc95eb8..d53982535da 100644 --- a/homeassistant/components/kostal_plenticore/number.py +++ b/homeassistant/components/kostal_plenticore/number.py @@ -1,4 +1,5 @@ """Platform for Kostal Plenticore numbers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/kostal_plenticore/select.py b/homeassistant/components/kostal_plenticore/select.py index 321bc4e5d70..2f6d9334071 100644 --- a/homeassistant/components/kostal_plenticore/select.py +++ b/homeassistant/components/kostal_plenticore/select.py @@ -1,4 +1,5 @@ """Platform for Kostal Plenticore select widgets.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/kostal_plenticore/sensor.py b/homeassistant/components/kostal_plenticore/sensor.py index 237a50f85b7..88714620a46 100644 --- a/homeassistant/components/kostal_plenticore/sensor.py +++ b/homeassistant/components/kostal_plenticore/sensor.py @@ -1,4 +1,5 @@ """Platform for Kostal Plenticore sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/kostal_plenticore/switch.py b/homeassistant/components/kostal_plenticore/switch.py index 509a3610884..f1e75edd5bd 100644 --- a/homeassistant/components/kostal_plenticore/switch.py +++ b/homeassistant/components/kostal_plenticore/switch.py @@ -1,4 +1,5 @@ """Platform for Kostal Plenticore switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/kraken/__init__.py b/homeassistant/components/kraken/__init__.py index 395de951bbd..692f602460b 100644 --- a/homeassistant/components/kraken/__init__.py +++ b/homeassistant/components/kraken/__init__.py @@ -1,4 +1,5 @@ """The kraken integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/kraken/config_flow.py b/homeassistant/components/kraken/config_flow.py index fba08b63071..3375746f25d 100644 --- a/homeassistant/components/kraken/config_flow.py +++ b/homeassistant/components/kraken/config_flow.py @@ -1,4 +1,5 @@ """Config flow for kraken integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/kraken/const.py b/homeassistant/components/kraken/const.py index 8a5f7fa828f..3b1bc29c7cd 100644 --- a/homeassistant/components/kraken/const.py +++ b/homeassistant/components/kraken/const.py @@ -1,4 +1,5 @@ """Constants for the kraken integration.""" + from __future__ import annotations from typing import TypedDict diff --git a/homeassistant/components/kraken/sensor.py b/homeassistant/components/kraken/sensor.py index 7e55da2b189..bcffe394bff 100644 --- a/homeassistant/components/kraken/sensor.py +++ b/homeassistant/components/kraken/sensor.py @@ -1,4 +1,5 @@ """The kraken integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/kraken/utils.py b/homeassistant/components/kraken/utils.py index 3c3d80dafb6..210756a7792 100644 --- a/homeassistant/components/kraken/utils.py +++ b/homeassistant/components/kraken/utils.py @@ -1,4 +1,5 @@ """Utility functions for the kraken integration.""" + from __future__ import annotations from pykrakenapi.pykrakenapi import KrakenAPI diff --git a/homeassistant/components/kulersky/light.py b/homeassistant/components/kulersky/light.py index 6636bfdba9f..cb98e52250f 100644 --- a/homeassistant/components/kulersky/light.py +++ b/homeassistant/components/kulersky/light.py @@ -1,4 +1,5 @@ """Kuler Sky light platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/kwb/sensor.py b/homeassistant/components/kwb/sensor.py index c15cff72655..950c0ed7d76 100644 --- a/homeassistant/components/kwb/sensor.py +++ b/homeassistant/components/kwb/sensor.py @@ -1,4 +1,5 @@ """Support for KWB Easyfire.""" + from __future__ import annotations from pykwb import kwb diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index 40d38da55eb..c059248b422 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -1,4 +1,5 @@ """Support for LaCrosse sensor components.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/lacrosse_view/__init__.py b/homeassistant/components/lacrosse_view/__init__.py index 86793a94a4b..d977af418a2 100644 --- a/homeassistant/components/lacrosse_view/__init__.py +++ b/homeassistant/components/lacrosse_view/__init__.py @@ -1,4 +1,5 @@ """The LaCrosse View integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lacrosse_view/config_flow.py b/homeassistant/components/lacrosse_view/config_flow.py index db6def15835..805afc40d2b 100644 --- a/homeassistant/components/lacrosse_view/config_flow.py +++ b/homeassistant/components/lacrosse_view/config_flow.py @@ -1,4 +1,5 @@ """Config flow for LaCrosse View integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lacrosse_view/coordinator.py b/homeassistant/components/lacrosse_view/coordinator.py index b45fe3ae1b4..5ec02a86709 100644 --- a/homeassistant/components/lacrosse_view/coordinator.py +++ b/homeassistant/components/lacrosse_view/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for LaCrosse View.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lacrosse_view/diagnostics.py b/homeassistant/components/lacrosse_view/diagnostics.py index 754cc39d38e..eaf3ded6a4a 100644 --- a/homeassistant/components/lacrosse_view/diagnostics.py +++ b/homeassistant/components/lacrosse_view/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for LaCrosse View.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lacrosse_view/sensor.py b/homeassistant/components/lacrosse_view/sensor.py index 960ab0ff325..fb2c34101ac 100644 --- a/homeassistant/components/lacrosse_view/sensor.py +++ b/homeassistant/components/lacrosse_view/sensor.py @@ -1,4 +1,5 @@ """Sensor component for LaCrosse View.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/lamarzocco/config_flow.py b/homeassistant/components/lamarzocco/config_flow.py index d9fa21c5419..de960f364ce 100644 --- a/homeassistant/components/lamarzocco/config_flow.py +++ b/homeassistant/components/lamarzocco/config_flow.py @@ -1,4 +1,5 @@ """Config flow for La Marzocco integration.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/lamarzocco/coordinator.py b/homeassistant/components/lamarzocco/coordinator.py index 438c4e42634..85fb8bb8854 100644 --- a/homeassistant/components/lamarzocco/coordinator.py +++ b/homeassistant/components/lamarzocco/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for La Marzocco API.""" + from collections.abc import Callable, Coroutine from datetime import timedelta import logging diff --git a/homeassistant/components/lamarzocco/switch.py b/homeassistant/components/lamarzocco/switch.py index 0d4d8d7dc8e..d8f5edec6b9 100644 --- a/homeassistant/components/lamarzocco/switch.py +++ b/homeassistant/components/lamarzocco/switch.py @@ -1,4 +1,5 @@ """Switch platform for La Marzocco espresso machines.""" + from collections.abc import Callable, Coroutine from dataclasses import dataclass from typing import Any diff --git a/homeassistant/components/lametric/__init__.py b/homeassistant/components/lametric/__init__.py index 867b80cf408..10fdee0ddc7 100644 --- a/homeassistant/components/lametric/__init__.py +++ b/homeassistant/components/lametric/__init__.py @@ -1,4 +1,5 @@ """Support for LaMetric time.""" + from homeassistant.components import notify as hass_notify from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, Platform diff --git a/homeassistant/components/lametric/application_credentials.py b/homeassistant/components/lametric/application_credentials.py index ab763c8f6fb..9c478be67f0 100644 --- a/homeassistant/components/lametric/application_credentials.py +++ b/homeassistant/components/lametric/application_credentials.py @@ -1,4 +1,5 @@ """Application credentials platform for LaMetric.""" + from homeassistant.components.application_credentials import AuthorizationServer from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/lametric/button.py b/homeassistant/components/lametric/button.py index dacbf8d2445..3f894495f4b 100644 --- a/homeassistant/components/lametric/button.py +++ b/homeassistant/components/lametric/button.py @@ -1,4 +1,5 @@ """Support for LaMetric buttons.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/lametric/config_flow.py b/homeassistant/components/lametric/config_flow.py index 0fdd566fada..ed1477e1149 100644 --- a/homeassistant/components/lametric/config_flow.py +++ b/homeassistant/components/lametric/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the LaMetric integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lametric/coordinator.py b/homeassistant/components/lametric/coordinator.py index 88f34adf45c..6655b035740 100644 --- a/homeassistant/components/lametric/coordinator.py +++ b/homeassistant/components/lametric/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the LaMatric integration.""" + from __future__ import annotations from demetriek import Device, LaMetricAuthenticationError, LaMetricDevice, LaMetricError diff --git a/homeassistant/components/lametric/diagnostics.py b/homeassistant/components/lametric/diagnostics.py index 256f5f06e91..69c681e911a 100644 --- a/homeassistant/components/lametric/diagnostics.py +++ b/homeassistant/components/lametric/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for LaMetric.""" + from __future__ import annotations import json diff --git a/homeassistant/components/lametric/entity.py b/homeassistant/components/lametric/entity.py index 54626a3838d..43539f13185 100644 --- a/homeassistant/components/lametric/entity.py +++ b/homeassistant/components/lametric/entity.py @@ -1,4 +1,5 @@ """Base entity for the LaMetric integration.""" + from __future__ import annotations from homeassistant.helpers.device_registry import ( diff --git a/homeassistant/components/lametric/helpers.py b/homeassistant/components/lametric/helpers.py index 3a3014a369e..24c028da78c 100644 --- a/homeassistant/components/lametric/helpers.py +++ b/homeassistant/components/lametric/helpers.py @@ -1,4 +1,5 @@ """Helpers for LaMetric.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/lametric/notify.py b/homeassistant/components/lametric/notify.py index d8b9627238c..7362f0ca402 100644 --- a/homeassistant/components/lametric/notify.py +++ b/homeassistant/components/lametric/notify.py @@ -1,4 +1,5 @@ """Support for LaMetric notifications.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lametric/number.py b/homeassistant/components/lametric/number.py index 9acdc6f1411..704a9e7fbe7 100644 --- a/homeassistant/components/lametric/number.py +++ b/homeassistant/components/lametric/number.py @@ -1,4 +1,5 @@ """Support for LaMetric numbers.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/lametric/select.py b/homeassistant/components/lametric/select.py index c7a3f55125b..dd81fd69877 100644 --- a/homeassistant/components/lametric/select.py +++ b/homeassistant/components/lametric/select.py @@ -1,4 +1,5 @@ """Support for LaMetric selects.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/lametric/sensor.py b/homeassistant/components/lametric/sensor.py index 5ef3608d33b..3e516789dce 100644 --- a/homeassistant/components/lametric/sensor.py +++ b/homeassistant/components/lametric/sensor.py @@ -1,4 +1,5 @@ """Support for LaMetric sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/lametric/services.py b/homeassistant/components/lametric/services.py index a20680267d9..d5191e0a434 100644 --- a/homeassistant/components/lametric/services.py +++ b/homeassistant/components/lametric/services.py @@ -1,4 +1,5 @@ """Support for LaMetric time services.""" + from __future__ import annotations from demetriek import ( diff --git a/homeassistant/components/lametric/switch.py b/homeassistant/components/lametric/switch.py index 7fda3a22b8f..d8afa2d076b 100644 --- a/homeassistant/components/lametric/switch.py +++ b/homeassistant/components/lametric/switch.py @@ -1,4 +1,5 @@ """Support for LaMetric switches.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/landisgyr_heat_meter/__init__.py b/homeassistant/components/landisgyr_heat_meter/__init__.py index 101216cd0d4..4e52e246d81 100644 --- a/homeassistant/components/landisgyr_heat_meter/__init__.py +++ b/homeassistant/components/landisgyr_heat_meter/__init__.py @@ -1,4 +1,5 @@ """The Landis+Gyr Heat Meter integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/landisgyr_heat_meter/config_flow.py b/homeassistant/components/landisgyr_heat_meter/config_flow.py index 64f4555c8fb..f7288b8a0cd 100644 --- a/homeassistant/components/landisgyr_heat_meter/config_flow.py +++ b/homeassistant/components/landisgyr_heat_meter/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Landis+Gyr Heat Meter integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/landisgyr_heat_meter/sensor.py b/homeassistant/components/landisgyr_heat_meter/sensor.py index 075aeb67b50..a06e87f83fa 100644 --- a/homeassistant/components/landisgyr_heat_meter/sensor.py +++ b/homeassistant/components/landisgyr_heat_meter/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/lannouncer/notify.py b/homeassistant/components/lannouncer/notify.py index eb89ac416f8..525372710af 100644 --- a/homeassistant/components/lannouncer/notify.py +++ b/homeassistant/components/lannouncer/notify.py @@ -1,4 +1,5 @@ """Lannouncer platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lastfm/__init__.py b/homeassistant/components/lastfm/__init__.py index 72dcf08a2d0..ebcc929c39c 100644 --- a/homeassistant/components/lastfm/__init__.py +++ b/homeassistant/components/lastfm/__init__.py @@ -1,4 +1,5 @@ """The lastfm component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/lastfm/config_flow.py b/homeassistant/components/lastfm/config_flow.py index 51791de54fb..154409ac66d 100644 --- a/homeassistant/components/lastfm/config_flow.py +++ b/homeassistant/components/lastfm/config_flow.py @@ -1,4 +1,5 @@ """Config flow for LastFm.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lastfm/coordinator.py b/homeassistant/components/lastfm/coordinator.py index 6e62fe2c84e..18473745450 100644 --- a/homeassistant/components/lastfm/coordinator.py +++ b/homeassistant/components/lastfm/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the LastFM integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/lastfm/sensor.py b/homeassistant/components/lastfm/sensor.py index 045fac0c727..48770113a80 100644 --- a/homeassistant/components/lastfm/sensor.py +++ b/homeassistant/components/lastfm/sensor.py @@ -1,4 +1,5 @@ """Sensor for Last.fm account status.""" + from __future__ import annotations import hashlib diff --git a/homeassistant/components/launch_library/__init__.py b/homeassistant/components/launch_library/__init__.py index e85c9c81566..23bf159ac61 100644 --- a/homeassistant/components/launch_library/__init__.py +++ b/homeassistant/components/launch_library/__init__.py @@ -1,4 +1,5 @@ """The launch_library component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/launch_library/config_flow.py b/homeassistant/components/launch_library/config_flow.py index 3349efb9c28..3cdff3650b3 100644 --- a/homeassistant/components/launch_library/config_flow.py +++ b/homeassistant/components/launch_library/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure launch library component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/launch_library/diagnostics.py b/homeassistant/components/launch_library/diagnostics.py index 35665e1b39e..35d0a699ab5 100644 --- a/homeassistant/components/launch_library/diagnostics.py +++ b/homeassistant/components/launch_library/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Launch Library.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 2c1934f0c16..4deb444f929 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -1,4 +1,5 @@ """Support for Launch Library sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/laundrify/__init__.py b/homeassistant/components/laundrify/__init__.py index 4bfb84082d5..9eb15625319 100644 --- a/homeassistant/components/laundrify/__init__.py +++ b/homeassistant/components/laundrify/__init__.py @@ -1,4 +1,5 @@ """The laundrify integration.""" + from __future__ import annotations from laundrify_aio import LaundrifyAPI diff --git a/homeassistant/components/laundrify/binary_sensor.py b/homeassistant/components/laundrify/binary_sensor.py index 099575f226f..80732bdc470 100644 --- a/homeassistant/components/laundrify/binary_sensor.py +++ b/homeassistant/components/laundrify/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for binary sensor integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/laundrify/config_flow.py b/homeassistant/components/laundrify/config_flow.py index e11ef88f2df..c131befd7d4 100644 --- a/homeassistant/components/laundrify/config_flow.py +++ b/homeassistant/components/laundrify/config_flow.py @@ -1,4 +1,5 @@ """Config flow for laundrify integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/laundrify/model.py b/homeassistant/components/laundrify/model.py index aa6bf77509f..862824c3154 100644 --- a/homeassistant/components/laundrify/model.py +++ b/homeassistant/components/laundrify/model.py @@ -1,4 +1,5 @@ """Models for laundrify platform.""" + from __future__ import annotations from typing import TypedDict diff --git a/homeassistant/components/lawn_mower/__init__.py b/homeassistant/components/lawn_mower/__init__.py index b1eac0a6609..5be457ab88f 100644 --- a/homeassistant/components/lawn_mower/__init__.py +++ b/homeassistant/components/lawn_mower/__init__.py @@ -1,4 +1,5 @@ """The lawn mower integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lawn_mower/const.py b/homeassistant/components/lawn_mower/const.py index 706c9616450..e060abe6423 100644 --- a/homeassistant/components/lawn_mower/const.py +++ b/homeassistant/components/lawn_mower/const.py @@ -1,4 +1,5 @@ """Constants for the lawn mower integration.""" + from enum import IntFlag, StrEnum diff --git a/homeassistant/components/lcn/__init__.py b/homeassistant/components/lcn/__init__.py index 527c3de7c9e..6866a10d55e 100644 --- a/homeassistant/components/lcn/__init__.py +++ b/homeassistant/components/lcn/__init__.py @@ -1,4 +1,5 @@ """Support for LCN devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py index ceeeecf50c4..2670777d9b3 100644 --- a/homeassistant/components/lcn/binary_sensor.py +++ b/homeassistant/components/lcn/binary_sensor.py @@ -1,4 +1,5 @@ """Support for LCN binary sensors.""" + from __future__ import annotations import pypck diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index d1e92d54fb1..abe0080811d 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -1,4 +1,5 @@ """Support for LCN climate control.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/lcn/config_flow.py b/homeassistant/components/lcn/config_flow.py index 87dc9c2b602..d05eb896f27 100644 --- a/homeassistant/components/lcn/config_flow.py +++ b/homeassistant/components/lcn/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the LCN integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lcn/const.py b/homeassistant/components/lcn/const.py index e8da5b39073..bcf9ecdf295 100644 --- a/homeassistant/components/lcn/const.py +++ b/homeassistant/components/lcn/const.py @@ -1,4 +1,5 @@ """Constants for the LCN component.""" + from itertools import product from homeassistant.const import Platform diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 31b2dbface0..6738add28e4 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -1,4 +1,5 @@ """Support for LCN covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lcn/device_trigger.py b/homeassistant/components/lcn/device_trigger.py index 46a94929d0b..42b5506110f 100644 --- a/homeassistant/components/lcn/device_trigger.py +++ b/homeassistant/components/lcn/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for LCN.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/lcn/helpers.py b/homeassistant/components/lcn/helpers.py index 8cb0201033e..b0b1a2f1c04 100644 --- a/homeassistant/components/lcn/helpers.py +++ b/homeassistant/components/lcn/helpers.py @@ -1,4 +1,5 @@ """Helpers for LCN component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 65c1344edf0..3f1467c74c1 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -1,4 +1,5 @@ """Support for LCN lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lcn/scene.py b/homeassistant/components/lcn/scene.py index f1980b6475d..fe8040bc291 100644 --- a/homeassistant/components/lcn/scene.py +++ b/homeassistant/components/lcn/scene.py @@ -1,4 +1,5 @@ """Support for LCN scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 1428019b59f..38f76e8d2d1 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -1,4 +1,5 @@ """Support for LCN sensors.""" + from __future__ import annotations from itertools import chain diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 8374ff85ab7..05e8d8d3937 100644 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -1,4 +1,5 @@ """Support for LCN switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ld2410_ble/config_flow.py b/homeassistant/components/ld2410_ble/config_flow.py index b0286b299a3..10d282cb8c7 100644 --- a/homeassistant/components/ld2410_ble/config_flow.py +++ b/homeassistant/components/ld2410_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for LD2410BLE integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ld2410_ble/models.py b/homeassistant/components/ld2410_ble/models.py index e2666277495..a7f5f4f2e3e 100644 --- a/homeassistant/components/ld2410_ble/models.py +++ b/homeassistant/components/ld2410_ble/models.py @@ -1,4 +1,5 @@ """The ld2410 ble integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/leaone/__init__.py b/homeassistant/components/leaone/__init__.py index 9f8bac34d55..74dfa872ce2 100644 --- a/homeassistant/components/leaone/__init__.py +++ b/homeassistant/components/leaone/__init__.py @@ -1,4 +1,5 @@ """The Leaone integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/leaone/config_flow.py b/homeassistant/components/leaone/config_flow.py index fba9f103446..8878f9af065 100644 --- a/homeassistant/components/leaone/config_flow.py +++ b/homeassistant/components/leaone/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Leaone integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/leaone/device.py b/homeassistant/components/leaone/device.py index a745873b693..0b95783dfd7 100644 --- a/homeassistant/components/leaone/device.py +++ b/homeassistant/components/leaone/device.py @@ -1,4 +1,5 @@ """Support for Leaone devices.""" + from __future__ import annotations from leaone_ble import DeviceKey diff --git a/homeassistant/components/leaone/sensor.py b/homeassistant/components/leaone/sensor.py index a614e63231a..c57f6678897 100644 --- a/homeassistant/components/leaone/sensor.py +++ b/homeassistant/components/leaone/sensor.py @@ -1,4 +1,5 @@ """Support for Leaone sensors.""" + from __future__ import annotations from leaone_ble import DeviceClass as LeaoneSensorDeviceClass, SensorUpdate, Units diff --git a/homeassistant/components/led_ble/__init__.py b/homeassistant/components/led_ble/__init__.py index 27a273ed7b0..d09f88b145a 100644 --- a/homeassistant/components/led_ble/__init__.py +++ b/homeassistant/components/led_ble/__init__.py @@ -1,4 +1,5 @@ """The LED BLE integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/led_ble/config_flow.py b/homeassistant/components/led_ble/config_flow.py index 70b4f78e840..a5afbcc6c0d 100644 --- a/homeassistant/components/led_ble/config_flow.py +++ b/homeassistant/components/led_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for LEDBLE integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/led_ble/light.py b/homeassistant/components/led_ble/light.py index a1da82dfe6d..3bca7269eba 100644 --- a/homeassistant/components/led_ble/light.py +++ b/homeassistant/components/led_ble/light.py @@ -1,4 +1,5 @@ """LED BLE integration light platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/led_ble/models.py b/homeassistant/components/led_ble/models.py index 0eda9439f11..a8dd3443dce 100644 --- a/homeassistant/components/led_ble/models.py +++ b/homeassistant/components/led_ble/models.py @@ -1,4 +1,5 @@ """The led ble integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/lg_netcast/media_player.py b/homeassistant/components/lg_netcast/media_player.py index 2b59e628705..81927710299 100644 --- a/homeassistant/components/lg_netcast/media_player.py +++ b/homeassistant/components/lg_netcast/media_player.py @@ -1,4 +1,5 @@ """Support for LG TV running on NetCast 3 or 4.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index cfd0ebbd7a7..962f67f714d 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -1,4 +1,5 @@ """Support for LG soundbars.""" + from __future__ import annotations import temescal diff --git a/homeassistant/components/lidarr/__init__.py b/homeassistant/components/lidarr/__init__.py index dd63920b209..acfb8f30f30 100644 --- a/homeassistant/components/lidarr/__init__.py +++ b/homeassistant/components/lidarr/__init__.py @@ -1,4 +1,5 @@ """The Lidarr component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lidarr/config_flow.py b/homeassistant/components/lidarr/config_flow.py index b1b04f6e778..379a01375b6 100644 --- a/homeassistant/components/lidarr/config_flow.py +++ b/homeassistant/components/lidarr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Lidarr.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lidarr/coordinator.py b/homeassistant/components/lidarr/coordinator.py index 9cc606a12b3..8b3116055d4 100644 --- a/homeassistant/components/lidarr/coordinator.py +++ b/homeassistant/components/lidarr/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Lidarr integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/lidarr/sensor.py b/homeassistant/components/lidarr/sensor.py index 82717210dc2..9a3dee1a802 100644 --- a/homeassistant/components/lidarr/sensor.py +++ b/homeassistant/components/lidarr/sensor.py @@ -1,4 +1,5 @@ """Support for Lidarr.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/lifx/__init__.py b/homeassistant/components/lifx/__init__.py index 76d4b7e36c5..47f00959bcd 100644 --- a/homeassistant/components/lifx/__init__.py +++ b/homeassistant/components/lifx/__init__.py @@ -1,4 +1,5 @@ """Support for LIFX.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lifx/binary_sensor.py b/homeassistant/components/lifx/binary_sensor.py index 5719c881d1f..454561a6f4e 100644 --- a/homeassistant/components/lifx/binary_sensor.py +++ b/homeassistant/components/lifx/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor entities for LIFX integration.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/lifx/button.py b/homeassistant/components/lifx/button.py index 86e3bc569b1..694c91b4c27 100644 --- a/homeassistant/components/lifx/button.py +++ b/homeassistant/components/lifx/button.py @@ -1,4 +1,5 @@ """Button entity for LIFX devices..""" + from __future__ import annotations from homeassistant.components.button import ( diff --git a/homeassistant/components/lifx/config_flow.py b/homeassistant/components/lifx/config_flow.py index dc0a0ab2d36..e4db80bec73 100644 --- a/homeassistant/components/lifx/config_flow.py +++ b/homeassistant/components/lifx/config_flow.py @@ -1,4 +1,5 @@ """Config flow flow LIFX.""" + from __future__ import annotations import socket diff --git a/homeassistant/components/lifx/coordinator.py b/homeassistant/components/lifx/coordinator.py index 18a8a24cb94..2b15b65255f 100644 --- a/homeassistant/components/lifx/coordinator.py +++ b/homeassistant/components/lifx/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for lifx.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lifx/diagnostics.py b/homeassistant/components/lifx/diagnostics.py index abe13cd1a50..b9ef1af4dc6 100644 --- a/homeassistant/components/lifx/diagnostics.py +++ b/homeassistant/components/lifx/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for LIFX.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lifx/discovery.py b/homeassistant/components/lifx/discovery.py index a4072ee23ef..81c2d44de87 100644 --- a/homeassistant/components/lifx/discovery.py +++ b/homeassistant/components/lifx/discovery.py @@ -1,4 +1,5 @@ """The lifx integration discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lifx/entity.py b/homeassistant/components/lifx/entity.py index 4bc6b87393d..279bcb86594 100644 --- a/homeassistant/components/lifx/entity.py +++ b/homeassistant/components/lifx/entity.py @@ -1,4 +1,5 @@ """Support for LIFX lights.""" + from __future__ import annotations from aiolifx import products diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index 74ed209742c..d1ee5c836eb 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -1,4 +1,5 @@ """Support for LIFX lights.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lifx/manager.py b/homeassistant/components/lifx/manager.py index 9e72ded620e..038fdceab26 100644 --- a/homeassistant/components/lifx/manager.py +++ b/homeassistant/components/lifx/manager.py @@ -1,4 +1,5 @@ """Support for LIFX lights.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lifx/migration.py b/homeassistant/components/lifx/migration.py index 359480a4507..9f8365cbceb 100644 --- a/homeassistant/components/lifx/migration.py +++ b/homeassistant/components/lifx/migration.py @@ -1,4 +1,5 @@ """Migrate lifx devices to their own config entry.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/lifx/select.py b/homeassistant/components/lifx/select.py index 183e31dec1f..ef2967d1776 100644 --- a/homeassistant/components/lifx/select.py +++ b/homeassistant/components/lifx/select.py @@ -1,4 +1,5 @@ """Select sensor entities for LIFX integration.""" + from __future__ import annotations from aiolifx_themes.themes import ThemeLibrary diff --git a/homeassistant/components/lifx/sensor.py b/homeassistant/components/lifx/sensor.py index e10f9579bc3..2f54317f9bd 100644 --- a/homeassistant/components/lifx/sensor.py +++ b/homeassistant/components/lifx/sensor.py @@ -1,4 +1,5 @@ """Sensors for LIFX lights.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lifx_cloud/scene.py b/homeassistant/components/lifx_cloud/scene.py index 61656741f82..8e7ab0c2d46 100644 --- a/homeassistant/components/lifx_cloud/scene.py +++ b/homeassistant/components/lifx_cloud/scene.py @@ -1,4 +1,5 @@ """Support for LIFX Cloud scenes.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 93a3b23f504..0a41ca2a84e 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1,4 +1,5 @@ """Provides functionality to interact with lights.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index 2b49c963438..50dbf42b677 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for lights.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/light/device_condition.py b/homeassistant/components/light/device_condition.py index 12e86c1e23d..f9bb7c30bd7 100644 --- a/homeassistant/components/light/device_condition.py +++ b/homeassistant/components/light/device_condition.py @@ -1,4 +1,5 @@ """Provides device conditions for lights.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/light/device_trigger.py b/homeassistant/components/light/device_trigger.py index 5ae5b12bf61..033ea75357e 100644 --- a/homeassistant/components/light/device_trigger.py +++ b/homeassistant/components/light/device_trigger.py @@ -1,4 +1,5 @@ """Provides device trigger for lights.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/light/intent.py b/homeassistant/components/light/intent.py index 605434af916..53127babee9 100644 --- a/homeassistant/components/light/intent.py +++ b/homeassistant/components/light/intent.py @@ -1,4 +1,5 @@ """Intents for the light integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/light/reproduce_state.py b/homeassistant/components/light/reproduce_state.py index 54fcd01843c..4024f2f84ba 100644 --- a/homeassistant/components/light/reproduce_state.py +++ b/homeassistant/components/light/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Light state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/light/significant_change.py b/homeassistant/components/light/significant_change.py index dc8f711b579..1877c925622 100644 --- a/homeassistant/components/light/significant_change.py +++ b/homeassistant/components/light/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Light state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lightwave/climate.py b/homeassistant/components/lightwave/climate.py index 5e89e4f8145..1016e8ce80d 100644 --- a/homeassistant/components/lightwave/climate.py +++ b/homeassistant/components/lightwave/climate.py @@ -1,4 +1,5 @@ """Support for LightwaveRF TRVs.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lightwave/light.py b/homeassistant/components/lightwave/light.py index f89dbb6bf5f..fb007b321ab 100644 --- a/homeassistant/components/lightwave/light.py +++ b/homeassistant/components/lightwave/light.py @@ -1,4 +1,5 @@ """Support for LightwaveRF lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lightwave/sensor.py b/homeassistant/components/lightwave/sensor.py index dac591aea34..721c508dd99 100644 --- a/homeassistant/components/lightwave/sensor.py +++ b/homeassistant/components/lightwave/sensor.py @@ -1,4 +1,5 @@ """Support for LightwaveRF TRV - Associated Battery.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/lightwave/switch.py b/homeassistant/components/lightwave/switch.py index 67b69d0e5c4..ca146ca881c 100644 --- a/homeassistant/components/lightwave/switch.py +++ b/homeassistant/components/lightwave/switch.py @@ -1,4 +1,5 @@ """Support for LightwaveRF switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/limitlessled/light.py b/homeassistant/components/limitlessled/light.py index 926e0a8a6d6..0b666b59faa 100644 --- a/homeassistant/components/limitlessled/light.py +++ b/homeassistant/components/limitlessled/light.py @@ -1,4 +1,5 @@ """Support for LimitlessLED bulbs.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/linear_garage_door/__init__.py b/homeassistant/components/linear_garage_door/__init__.py index d168da511e0..e21d8eaba58 100644 --- a/homeassistant/components/linear_garage_door/__init__.py +++ b/homeassistant/components/linear_garage_door/__init__.py @@ -1,4 +1,5 @@ """The Linear Garage Door integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/linear_garage_door/config_flow.py b/homeassistant/components/linear_garage_door/config_flow.py index c8892a8f99c..bfb6f825030 100644 --- a/homeassistant/components/linear_garage_door/config_flow.py +++ b/homeassistant/components/linear_garage_door/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Linear Garage Door integration.""" + from __future__ import annotations from collections.abc import Collection, Mapping, Sequence diff --git a/homeassistant/components/linear_garage_door/coordinator.py b/homeassistant/components/linear_garage_door/coordinator.py index e9234327429..b771b552b62 100644 --- a/homeassistant/components/linear_garage_door/coordinator.py +++ b/homeassistant/components/linear_garage_door/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for Linear.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/linear_garage_door/diagnostics.py b/homeassistant/components/linear_garage_door/diagnostics.py index fffcdd7de87..fc4906daa77 100644 --- a/homeassistant/components/linear_garage_door/diagnostics.py +++ b/homeassistant/components/linear_garage_door/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Linear Garage Door.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/linksys_smart/device_tracker.py b/homeassistant/components/linksys_smart/device_tracker.py index d0440c832c8..a33f0070c70 100644 --- a/homeassistant/components/linksys_smart/device_tracker.py +++ b/homeassistant/components/linksys_smart/device_tracker.py @@ -1,4 +1,5 @@ """Support for Linksys Smart Wifi routers.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/linode/__init__.py b/homeassistant/components/linode/__init__.py index bd61519edc4..2ed3cf244d0 100644 --- a/homeassistant/components/linode/__init__.py +++ b/homeassistant/components/linode/__init__.py @@ -1,4 +1,5 @@ """Support for Linode.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index 17a68e9be9c..02c7a1ef383 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the state of Linode Nodes.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py index b59e8f901e5..f2665671c0b 100644 --- a/homeassistant/components/linode/switch.py +++ b/homeassistant/components/linode/switch.py @@ -1,4 +1,5 @@ """Support for interacting with Linode nodes.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/linux_battery/sensor.py b/homeassistant/components/linux_battery/sensor.py index 08b2dc33bae..9dc0e8c675d 100644 --- a/homeassistant/components/linux_battery/sensor.py +++ b/homeassistant/components/linux_battery/sensor.py @@ -1,4 +1,5 @@ """Details about the built-in battery.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/litejet/config_flow.py b/homeassistant/components/litejet/config_flow.py index 3c452f7b4d7..19ddf0122c4 100644 --- a/homeassistant/components/litejet/config_flow.py +++ b/homeassistant/components/litejet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the LiteJet lighting system.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/litejet/const.py b/homeassistant/components/litejet/const.py index baeadd7f4a9..69492efd0c7 100644 --- a/homeassistant/components/litejet/const.py +++ b/homeassistant/components/litejet/const.py @@ -1,4 +1,5 @@ """LiteJet constants.""" + from homeassistant.const import Platform DOMAIN = "litejet" diff --git a/homeassistant/components/litejet/diagnostics.py b/homeassistant/components/litejet/diagnostics.py index 48f38542dfd..7a10f4d6754 100644 --- a/homeassistant/components/litejet/diagnostics.py +++ b/homeassistant/components/litejet/diagnostics.py @@ -1,4 +1,5 @@ """Support for LiteJet diagnostics.""" + from typing import Any from pylitejet import LiteJet diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index 167f7a62a00..b387c6f8890 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -1,4 +1,5 @@ """Support for LiteJet lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index 5089b9ec0f9..cf294be8054 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -1,4 +1,5 @@ """Support for LiteJet switch.""" + from typing import Any from pylitejet import LiteJet, LiteJetError diff --git a/homeassistant/components/litejet/trigger.py b/homeassistant/components/litejet/trigger.py index df5ffac9b99..2786cc8b76a 100644 --- a/homeassistant/components/litejet/trigger.py +++ b/homeassistant/components/litejet/trigger.py @@ -1,4 +1,5 @@ """Trigger an automation when a LiteJet switch is released.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/litterrobot/__init__.py b/homeassistant/components/litterrobot/__init__.py index daf71fe8a6e..ec9849bbb89 100644 --- a/homeassistant/components/litterrobot/__init__.py +++ b/homeassistant/components/litterrobot/__init__.py @@ -1,4 +1,5 @@ """The Litter-Robot integration.""" + from __future__ import annotations from pylitterbot import FeederRobot, LitterRobot, LitterRobot3, LitterRobot4, Robot diff --git a/homeassistant/components/litterrobot/binary_sensor.py b/homeassistant/components/litterrobot/binary_sensor.py index 22e8bda8dfa..2f44f44ed53 100644 --- a/homeassistant/components/litterrobot/binary_sensor.py +++ b/homeassistant/components/litterrobot/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Litter-Robot binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/litterrobot/button.py b/homeassistant/components/litterrobot/button.py index 79a866333d8..02477e7fa03 100644 --- a/homeassistant/components/litterrobot/button.py +++ b/homeassistant/components/litterrobot/button.py @@ -1,4 +1,5 @@ """Support for Litter-Robot button.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/litterrobot/config_flow.py b/homeassistant/components/litterrobot/config_flow.py index 54377d43980..39a1646a8b7 100644 --- a/homeassistant/components/litterrobot/config_flow.py +++ b/homeassistant/components/litterrobot/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Litter-Robot integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/litterrobot/entity.py b/homeassistant/components/litterrobot/entity.py index fb1fbe58a7b..4639404b92b 100644 --- a/homeassistant/components/litterrobot/entity.py +++ b/homeassistant/components/litterrobot/entity.py @@ -1,4 +1,5 @@ """Litter-Robot entities for common data and methods.""" + from __future__ import annotations from typing import Generic, TypeVar diff --git a/homeassistant/components/litterrobot/hub.py b/homeassistant/components/litterrobot/hub.py index 5dc8098a8df..4af004bddf5 100644 --- a/homeassistant/components/litterrobot/hub.py +++ b/homeassistant/components/litterrobot/hub.py @@ -1,4 +1,5 @@ """A wrapper 'hub' for the Litter-Robot API.""" + from __future__ import annotations from collections.abc import Generator, Mapping diff --git a/homeassistant/components/litterrobot/select.py b/homeassistant/components/litterrobot/select.py index 8b5445269bd..e7ecbada10d 100644 --- a/homeassistant/components/litterrobot/select.py +++ b/homeassistant/components/litterrobot/select.py @@ -1,4 +1,5 @@ """Support for Litter-Robot selects.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/litterrobot/sensor.py b/homeassistant/components/litterrobot/sensor.py index a25921e440c..1b4b7f78fdc 100644 --- a/homeassistant/components/litterrobot/sensor.py +++ b/homeassistant/components/litterrobot/sensor.py @@ -1,4 +1,5 @@ """Support for Litter-Robot sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/litterrobot/switch.py b/homeassistant/components/litterrobot/switch.py index aa4a18c1840..60ca9b4d6c7 100644 --- a/homeassistant/components/litterrobot/switch.py +++ b/homeassistant/components/litterrobot/switch.py @@ -1,4 +1,5 @@ """Support for Litter-Robot switches.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/litterrobot/time.py b/homeassistant/components/litterrobot/time.py index bb840e17a8f..4e5e80a8ca6 100644 --- a/homeassistant/components/litterrobot/time.py +++ b/homeassistant/components/litterrobot/time.py @@ -1,4 +1,5 @@ """Support for Litter-Robot time.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/litterrobot/update.py b/homeassistant/components/litterrobot/update.py index 584a6af77c2..c4d1ada6080 100644 --- a/homeassistant/components/litterrobot/update.py +++ b/homeassistant/components/litterrobot/update.py @@ -1,4 +1,5 @@ """Support for Litter-Robot updates.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/litterrobot/vacuum.py b/homeassistant/components/litterrobot/vacuum.py index 681af81481d..4f9efa2dff7 100644 --- a/homeassistant/components/litterrobot/vacuum.py +++ b/homeassistant/components/litterrobot/vacuum.py @@ -1,4 +1,5 @@ """Support for Litter-Robot "Vacuum".""" + from __future__ import annotations from datetime import time diff --git a/homeassistant/components/livisi/__init__.py b/homeassistant/components/livisi/__init__.py index e638c84a917..26e36e68efa 100644 --- a/homeassistant/components/livisi/__init__.py +++ b/homeassistant/components/livisi/__init__.py @@ -1,4 +1,5 @@ """The Livisi Smart Home integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/livisi/binary_sensor.py b/homeassistant/components/livisi/binary_sensor.py index 42170bbeb4c..d4edd59f2d7 100644 --- a/homeassistant/components/livisi/binary_sensor.py +++ b/homeassistant/components/livisi/binary_sensor.py @@ -1,4 +1,5 @@ """Code to handle a Livisi Binary Sensor.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/livisi/climate.py b/homeassistant/components/livisi/climate.py index 6990dabff1d..56fe63d351f 100644 --- a/homeassistant/components/livisi/climate.py +++ b/homeassistant/components/livisi/climate.py @@ -1,4 +1,5 @@ """Code to handle a Livisi Virtual Climate Control.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/livisi/config_flow.py b/homeassistant/components/livisi/config_flow.py index 1b5c4892eee..7317aec0abc 100644 --- a/homeassistant/components/livisi/config_flow.py +++ b/homeassistant/components/livisi/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Livisi Home Assistant.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/livisi/coordinator.py b/homeassistant/components/livisi/coordinator.py index 17a3b1828d0..7cb5757310f 100644 --- a/homeassistant/components/livisi/coordinator.py +++ b/homeassistant/components/livisi/coordinator.py @@ -1,4 +1,5 @@ """Code to manage fetching LIVISI data API.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/livisi/entity.py b/homeassistant/components/livisi/entity.py index f76901ddb05..3160b8f288a 100644 --- a/homeassistant/components/livisi/entity.py +++ b/homeassistant/components/livisi/entity.py @@ -1,4 +1,5 @@ """Code to handle a Livisi switches.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/livisi/switch.py b/homeassistant/components/livisi/switch.py index 2c5a2b5137b..fa604c5fc87 100644 --- a/homeassistant/components/livisi/switch.py +++ b/homeassistant/components/livisi/switch.py @@ -1,4 +1,5 @@ """Code to handle a Livisi switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/llamalab_automate/notify.py b/homeassistant/components/llamalab_automate/notify.py index 8361d65725c..6ce00db71c3 100644 --- a/homeassistant/components/llamalab_automate/notify.py +++ b/homeassistant/components/llamalab_automate/notify.py @@ -1,4 +1,5 @@ """LlamaLab Automate notification service.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/local_calendar/__init__.py b/homeassistant/components/local_calendar/__init__.py index 3b302742ab6..2be5133a21c 100644 --- a/homeassistant/components/local_calendar/__init__.py +++ b/homeassistant/components/local_calendar/__init__.py @@ -1,4 +1,5 @@ """The Local Calendar integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/local_calendar/config_flow.py b/homeassistant/components/local_calendar/config_flow.py index 8703def7304..8caa3a5d528 100644 --- a/homeassistant/components/local_calendar/config_flow.py +++ b/homeassistant/components/local_calendar/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Local Calendar integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/local_file/camera.py b/homeassistant/components/local_file/camera.py index 5b29516e03d..72fe1a88b86 100644 --- a/homeassistant/components/local_file/camera.py +++ b/homeassistant/components/local_file/camera.py @@ -1,4 +1,5 @@ """Camera that loads a picture from a local file.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/local_ip/__init__.py b/homeassistant/components/local_ip/__init__.py index b5ed762ef5d..45ddbed7150 100644 --- a/homeassistant/components/local_ip/__init__.py +++ b/homeassistant/components/local_ip/__init__.py @@ -1,4 +1,5 @@ """Get the local IP address of the Home Assistant instance.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv diff --git a/homeassistant/components/local_ip/config_flow.py b/homeassistant/components/local_ip/config_flow.py index 09e9835e65e..3a4612d84aa 100644 --- a/homeassistant/components/local_ip/config_flow.py +++ b/homeassistant/components/local_ip/config_flow.py @@ -1,4 +1,5 @@ """Config flow for local_ip.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/local_ip/const.py b/homeassistant/components/local_ip/const.py index b079ec87663..38390cbfdc1 100644 --- a/homeassistant/components/local_ip/const.py +++ b/homeassistant/components/local_ip/const.py @@ -1,4 +1,5 @@ """Local IP constants.""" + from homeassistant.const import Platform DOMAIN = "local_ip" diff --git a/homeassistant/components/local_todo/__init__.py b/homeassistant/components/local_todo/__init__.py index f8403251ba0..8245822bd9f 100644 --- a/homeassistant/components/local_todo/__init__.py +++ b/homeassistant/components/local_todo/__init__.py @@ -1,4 +1,5 @@ """The Local To-do integration.""" + from __future__ import annotations from pathlib import Path diff --git a/homeassistant/components/local_todo/config_flow.py b/homeassistant/components/local_todo/config_flow.py index dc596c91d9c..a79a62c647b 100644 --- a/homeassistant/components/local_todo/config_flow.py +++ b/homeassistant/components/local_todo/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Local To-do integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/locative/__init__.py b/homeassistant/components/locative/__init__.py index cca322f3baa..c27b6b4153b 100644 --- a/homeassistant/components/locative/__init__.py +++ b/homeassistant/components/locative/__init__.py @@ -1,4 +1,5 @@ """Support for Locative.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/locative/config_flow.py b/homeassistant/components/locative/config_flow.py index a1ac8263416..2aaa2e65f1c 100644 --- a/homeassistant/components/locative/config_flow.py +++ b/homeassistant/components/locative/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Locative.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/locative/device_tracker.py b/homeassistant/components/locative/device_tracker.py index 2ec1e7437de..e8135bbd03c 100644 --- a/homeassistant/components/locative/device_tracker.py +++ b/homeassistant/components/locative/device_tracker.py @@ -1,4 +1,5 @@ """Support for the Locative platform.""" + from homeassistant.components.device_tracker import SourceType, TrackerEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index a4e7c4b7d1a..f17c81aed20 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -1,4 +1,5 @@ """Component to interface with locks that can be controlled remotely.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lock/device_action.py b/homeassistant/components/lock/device_action.py index fba95a932de..a75966414f8 100644 --- a/homeassistant/components/lock/device_action.py +++ b/homeassistant/components/lock/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Lock.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/lock/device_condition.py b/homeassistant/components/lock/device_condition.py index 5ba93554aec..327bde2c0e3 100644 --- a/homeassistant/components/lock/device_condition.py +++ b/homeassistant/components/lock/device_condition.py @@ -1,4 +1,5 @@ """Provides device automations for Lock.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/lock/device_trigger.py b/homeassistant/components/lock/device_trigger.py index c6b86eaca4a..57a83c7dc7a 100644 --- a/homeassistant/components/lock/device_trigger.py +++ b/homeassistant/components/lock/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Lock.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/lock/reproduce_state.py b/homeassistant/components/lock/reproduce_state.py index b6eefcfac63..36afcf5f310 100644 --- a/homeassistant/components/lock/reproduce_state.py +++ b/homeassistant/components/lock/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Lock state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lock/significant_change.py b/homeassistant/components/lock/significant_change.py index 172bf2559c5..138f2393257 100644 --- a/homeassistant/components/lock/significant_change.py +++ b/homeassistant/components/lock/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Lock state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 891d1fb3fb0..f19e64aa6f0 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/logbook/const.py b/homeassistant/components/logbook/const.py index 3d4c0b3615f..282580bdc95 100644 --- a/homeassistant/components/logbook/const.py +++ b/homeassistant/components/logbook/const.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED diff --git a/homeassistant/components/logbook/helpers.py b/homeassistant/components/logbook/helpers.py index 68fa5794618..698d7d56aa1 100644 --- a/homeassistant/components/logbook/helpers.py +++ b/homeassistant/components/logbook/helpers.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/logbook/models.py b/homeassistant/components/logbook/models.py index 22420f243c6..7cbafd43d53 100644 --- a/homeassistant/components/logbook/models.py +++ b/homeassistant/components/logbook/models.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/logbook/processor.py b/homeassistant/components/logbook/processor.py index 02a6dae3ce6..29e135fbf62 100644 --- a/homeassistant/components/logbook/processor.py +++ b/homeassistant/components/logbook/processor.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations from collections.abc import Callable, Generator, Sequence diff --git a/homeassistant/components/logbook/queries/__init__.py b/homeassistant/components/logbook/queries/__init__.py index af41374ec9b..c27da37742b 100644 --- a/homeassistant/components/logbook/queries/__init__.py +++ b/homeassistant/components/logbook/queries/__init__.py @@ -1,4 +1,5 @@ """Queries for logbook.""" + from __future__ import annotations from collections.abc import Collection diff --git a/homeassistant/components/logbook/queries/all.py b/homeassistant/components/logbook/queries/all.py index 21f88135a1d..cd596414583 100644 --- a/homeassistant/components/logbook/queries/all.py +++ b/homeassistant/components/logbook/queries/all.py @@ -1,4 +1,5 @@ """All queries for logbook.""" + from __future__ import annotations from sqlalchemy import lambda_stmt diff --git a/homeassistant/components/logbook/queries/common.py b/homeassistant/components/logbook/queries/common.py index cbbe8724ece..8f9ab8a80cd 100644 --- a/homeassistant/components/logbook/queries/common.py +++ b/homeassistant/components/logbook/queries/common.py @@ -1,4 +1,5 @@ """Queries for logbook.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/logbook/queries/devices.py b/homeassistant/components/logbook/queries/devices.py index 75604de6104..f4b1c06c40c 100644 --- a/homeassistant/components/logbook/queries/devices.py +++ b/homeassistant/components/logbook/queries/devices.py @@ -1,4 +1,5 @@ """Devices queries for logbook.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/logbook/queries/entities.py b/homeassistant/components/logbook/queries/entities.py index 95c1d565263..494c2965215 100644 --- a/homeassistant/components/logbook/queries/entities.py +++ b/homeassistant/components/logbook/queries/entities.py @@ -1,4 +1,5 @@ """Entities queries for logbook.""" + from __future__ import annotations from collections.abc import Collection, Iterable diff --git a/homeassistant/components/logbook/queries/entities_and_devices.py b/homeassistant/components/logbook/queries/entities_and_devices.py index c465a343d61..383bb71e223 100644 --- a/homeassistant/components/logbook/queries/entities_and_devices.py +++ b/homeassistant/components/logbook/queries/entities_and_devices.py @@ -1,4 +1,5 @@ """Entities and Devices queries for logbook.""" + from __future__ import annotations from collections.abc import Collection, Iterable diff --git a/homeassistant/components/logbook/rest_api.py b/homeassistant/components/logbook/rest_api.py index 5ffd057670f..5f1918ebccf 100644 --- a/homeassistant/components/logbook/rest_api.py +++ b/homeassistant/components/logbook/rest_api.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/logbook/websocket_api.py b/homeassistant/components/logbook/websocket_api.py index 0b1b34ca375..4e4732c0d0e 100644 --- a/homeassistant/components/logbook/websocket_api.py +++ b/homeassistant/components/logbook/websocket_api.py @@ -1,4 +1,5 @@ """Event parser and human readable log generator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/logger/__init__.py b/homeassistant/components/logger/__init__.py index 32b864047a6..be6e8c1b24e 100644 --- a/homeassistant/components/logger/__init__.py +++ b/homeassistant/components/logger/__init__.py @@ -1,4 +1,5 @@ """Support for setting the level of logging for components.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/logger/helpers.py b/homeassistant/components/logger/helpers.py index bf37ab3625b..a527a081fca 100644 --- a/homeassistant/components/logger/helpers.py +++ b/homeassistant/components/logger/helpers.py @@ -1,4 +1,5 @@ """Helpers for the logger integration.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/logger/websocket_api.py b/homeassistant/components/logger/websocket_api.py index 240db3144af..fafc2d3eedb 100644 --- a/homeassistant/components/logger/websocket_api.py +++ b/homeassistant/components/logger/websocket_api.py @@ -1,4 +1,5 @@ """Websocket API handlers for the logger integration.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/logi_circle/camera.py b/homeassistant/components/logi_circle/camera.py index d1ea01e864c..ad31713d734 100644 --- a/homeassistant/components/logi_circle/camera.py +++ b/homeassistant/components/logi_circle/camera.py @@ -1,4 +1,5 @@ """Support to the Logi Circle cameras.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/logi_circle/const.py b/homeassistant/components/logi_circle/const.py index 3e74611f767..e144f47ce4e 100644 --- a/homeassistant/components/logi_circle/const.py +++ b/homeassistant/components/logi_circle/const.py @@ -1,4 +1,5 @@ """Constants in Logi Circle component.""" + from __future__ import annotations DOMAIN = "logi_circle" diff --git a/homeassistant/components/logi_circle/sensor.py b/homeassistant/components/logi_circle/sensor.py index d06569a19ca..121cb8848ae 100644 --- a/homeassistant/components/logi_circle/sensor.py +++ b/homeassistant/components/logi_circle/sensor.py @@ -1,4 +1,5 @@ """Support for Logi Circle sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index 98cc4c4b4e8..39debb5ba4c 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -1,4 +1,5 @@ """Sensor for checking the status of London air.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/london_underground/const.py b/homeassistant/components/london_underground/const.py index 4928d3bb164..532f4333ba9 100644 --- a/homeassistant/components/london_underground/const.py +++ b/homeassistant/components/london_underground/const.py @@ -1,4 +1,5 @@ """Constants for the London underground integration.""" + from datetime import timedelta DOMAIN = "london_underground" diff --git a/homeassistant/components/london_underground/coordinator.py b/homeassistant/components/london_underground/coordinator.py index 2d3fd6b970f..cf14ad14b43 100644 --- a/homeassistant/components/london_underground/coordinator.py +++ b/homeassistant/components/london_underground/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for London underground integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py index 3f5ec42521e..03bdc1c395f 100644 --- a/homeassistant/components/london_underground/sensor.py +++ b/homeassistant/components/london_underground/sensor.py @@ -1,4 +1,5 @@ """Sensor for checking the status of London Underground tube lines.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lookin/__init__.py b/homeassistant/components/lookin/__init__.py index 358ccc5ae37..a0e529bc189 100644 --- a/homeassistant/components/lookin/__init__.py +++ b/homeassistant/components/lookin/__init__.py @@ -1,4 +1,5 @@ """The lookin integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lookin/climate.py b/homeassistant/components/lookin/climate.py index 1bee2d14295..fadeb6d16fa 100644 --- a/homeassistant/components/lookin/climate.py +++ b/homeassistant/components/lookin/climate.py @@ -1,4 +1,5 @@ """The lookin integration climate platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lookin/config_flow.py b/homeassistant/components/lookin/config_flow.py index 27ba96e9eb0..61dfd9a2c20 100644 --- a/homeassistant/components/lookin/config_flow.py +++ b/homeassistant/components/lookin/config_flow.py @@ -1,4 +1,5 @@ """The lookin integration config_flow.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lookin/const.py b/homeassistant/components/lookin/const.py index 8eb96dcefd8..d4624932ad9 100644 --- a/homeassistant/components/lookin/const.py +++ b/homeassistant/components/lookin/const.py @@ -1,4 +1,5 @@ """The lookin integration constants.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lookin/coordinator.py b/homeassistant/components/lookin/coordinator.py index d556899a914..925a7416731 100644 --- a/homeassistant/components/lookin/coordinator.py +++ b/homeassistant/components/lookin/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for lookin devices.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/lookin/entity.py b/homeassistant/components/lookin/entity.py index 0e518ffc1e5..fd36301ddb6 100644 --- a/homeassistant/components/lookin/entity.py +++ b/homeassistant/components/lookin/entity.py @@ -1,4 +1,5 @@ """The lookin integration entity.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/lookin/light.py b/homeassistant/components/lookin/light.py index c4b263cbad1..804d0ebef01 100644 --- a/homeassistant/components/lookin/light.py +++ b/homeassistant/components/lookin/light.py @@ -1,4 +1,5 @@ """The lookin integration light platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lookin/media_player.py b/homeassistant/components/lookin/media_player.py index f7ae457cbff..b3dda9c9e0c 100644 --- a/homeassistant/components/lookin/media_player.py +++ b/homeassistant/components/lookin/media_player.py @@ -1,4 +1,5 @@ """The lookin integration light platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lookin/models.py b/homeassistant/components/lookin/models.py index 2de3a7ee761..3bf6ae9d862 100644 --- a/homeassistant/components/lookin/models.py +++ b/homeassistant/components/lookin/models.py @@ -1,4 +1,5 @@ """The lookin integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/lookin/sensor.py b/homeassistant/components/lookin/sensor.py index 822c28fda30..cae4f7782a8 100644 --- a/homeassistant/components/lookin/sensor.py +++ b/homeassistant/components/lookin/sensor.py @@ -1,4 +1,5 @@ """The lookin integration sensor platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/loqed/__init__.py b/homeassistant/components/loqed/__init__.py index e22987ba426..b6408880c96 100644 --- a/homeassistant/components/loqed/__init__.py +++ b/homeassistant/components/loqed/__init__.py @@ -1,4 +1,5 @@ """The loqed integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/loqed/config_flow.py b/homeassistant/components/loqed/config_flow.py index a0f23b4359d..8c82a7a6964 100644 --- a/homeassistant/components/loqed/config_flow.py +++ b/homeassistant/components/loqed/config_flow.py @@ -1,4 +1,5 @@ """Config flow for loqed integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/loqed/entity.py b/homeassistant/components/loqed/entity.py index aec50ec8f92..9a443e23924 100644 --- a/homeassistant/components/loqed/entity.py +++ b/homeassistant/components/loqed/entity.py @@ -1,4 +1,5 @@ """Base entity for the LOQED integration.""" + from __future__ import annotations from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo diff --git a/homeassistant/components/loqed/lock.py b/homeassistant/components/loqed/lock.py index d34df19e2d1..be6b39176d6 100644 --- a/homeassistant/components/loqed/lock.py +++ b/homeassistant/components/loqed/lock.py @@ -1,4 +1,5 @@ """LOQED lock integration for Home Assistant.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/loqed/sensor.py b/homeassistant/components/loqed/sensor.py index ee4fa7ecd74..1d4595db8e9 100644 --- a/homeassistant/components/loqed/sensor.py +++ b/homeassistant/components/loqed/sensor.py @@ -1,4 +1,5 @@ """Creates LOQED sensors.""" + from typing import Final from homeassistant.components.sensor import ( diff --git a/homeassistant/components/lovelace/const.py b/homeassistant/components/lovelace/const.py index 01110bb8a7c..0f1e818e8bd 100644 --- a/homeassistant/components/lovelace/const.py +++ b/homeassistant/components/lovelace/const.py @@ -1,4 +1,5 @@ """Constants for Lovelace.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index d935ad9bff5..984c0d14734 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -1,4 +1,5 @@ """Lovelace dashboard support.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/lovelace/resources.py b/homeassistant/components/lovelace/resources.py index b6d0c939fec..2dbbbacabea 100644 --- a/homeassistant/components/lovelace/resources.py +++ b/homeassistant/components/lovelace/resources.py @@ -1,4 +1,5 @@ """Lovelace resources support.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lovelace/websocket.py b/homeassistant/components/lovelace/websocket.py index b756c2765e1..e4eaa42073f 100644 --- a/homeassistant/components/lovelace/websocket.py +++ b/homeassistant/components/lovelace/websocket.py @@ -1,4 +1,5 @@ """Websocket API for Lovelace.""" + from __future__ import annotations from functools import wraps diff --git a/homeassistant/components/luci/device_tracker.py b/homeassistant/components/luci/device_tracker.py index f4ebe4376f3..528596d53c5 100644 --- a/homeassistant/components/luci/device_tracker.py +++ b/homeassistant/components/luci/device_tracker.py @@ -1,4 +1,5 @@ """Support for OpenWRT (luci) routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/luftdaten/__init__.py b/homeassistant/components/luftdaten/__init__.py index d842fdc1a89..2ef7864566f 100644 --- a/homeassistant/components/luftdaten/__init__.py +++ b/homeassistant/components/luftdaten/__init__.py @@ -3,6 +3,7 @@ Sensor.Community was previously called Luftdaten, hence the domain differs from the integration name. """ + from __future__ import annotations import logging diff --git a/homeassistant/components/luftdaten/config_flow.py b/homeassistant/components/luftdaten/config_flow.py index f2f8a9366cd..ba14afeb092 100644 --- a/homeassistant/components/luftdaten/config_flow.py +++ b/homeassistant/components/luftdaten/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Sensor.Community integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/luftdaten/const.py b/homeassistant/components/luftdaten/const.py index b64079933cd..bc6970455c8 100644 --- a/homeassistant/components/luftdaten/const.py +++ b/homeassistant/components/luftdaten/const.py @@ -1,4 +1,5 @@ """Define constants for the Sensor.Community integration.""" + from datetime import timedelta ATTR_SENSOR_ID = "sensor_id" diff --git a/homeassistant/components/luftdaten/diagnostics.py b/homeassistant/components/luftdaten/diagnostics.py index 8146d8eb7d6..a1bbcbcadd7 100644 --- a/homeassistant/components/luftdaten/diagnostics.py +++ b/homeassistant/components/luftdaten/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Sensor.Community.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/luftdaten/sensor.py b/homeassistant/components/luftdaten/sensor.py index 58fa5788bda..8b9def63fda 100644 --- a/homeassistant/components/luftdaten/sensor.py +++ b/homeassistant/components/luftdaten/sensor.py @@ -1,4 +1,5 @@ """Support for Sensor.Community sensors.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/lupusec/__init__.py b/homeassistant/components/lupusec/__init__.py index f937c7edd10..c471902813a 100644 --- a/homeassistant/components/lupusec/__init__.py +++ b/homeassistant/components/lupusec/__init__.py @@ -1,4 +1,5 @@ """Support for Lupusec Home Security system.""" + from json import JSONDecodeError import logging diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py index a13e3684294..090d9ab3ced 100644 --- a/homeassistant/components/lupusec/alarm_control_panel.py +++ b/homeassistant/components/lupusec/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Lupusec System alarm control panels.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lupusec/binary_sensor.py b/homeassistant/components/lupusec/binary_sensor.py index 5cf63579984..e046d356327 100644 --- a/homeassistant/components/lupusec/binary_sensor.py +++ b/homeassistant/components/lupusec/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Lupusec Security System binary sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lupusec/switch.py b/homeassistant/components/lupusec/switch.py index e07c974f033..3fa62235b9a 100644 --- a/homeassistant/components/lupusec/switch.py +++ b/homeassistant/components/lupusec/switch.py @@ -1,4 +1,5 @@ """Support for Lupusec Security System switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/lutron/__init__.py b/homeassistant/components/lutron/__init__.py index ad69a14c0f5..517eb4c8350 100644 --- a/homeassistant/components/lutron/__init__.py +++ b/homeassistant/components/lutron/__init__.py @@ -1,4 +1,5 @@ """Component for interacting with a Lutron RadioRA 2 system.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/lutron/binary_sensor.py b/homeassistant/components/lutron/binary_sensor.py index 8cae9c9714a..c33b545413d 100644 --- a/homeassistant/components/lutron/binary_sensor.py +++ b/homeassistant/components/lutron/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Lutron Powr Savr occupancy sensors.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lutron/config_flow.py b/homeassistant/components/lutron/config_flow.py index 1a21ab37469..8fd11484a72 100644 --- a/homeassistant/components/lutron/config_flow.py +++ b/homeassistant/components/lutron/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Lutron integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index cdcdf93ccbd..247ead3ccec 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -1,4 +1,5 @@ """Support for Lutron shades.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lutron/fan.py b/homeassistant/components/lutron/fan.py index 4aac95759aa..07e0bb444b2 100644 --- a/homeassistant/components/lutron/fan.py +++ b/homeassistant/components/lutron/fan.py @@ -1,4 +1,5 @@ """Lutron fan platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index 0bd00177cc1..103bcb1e39d 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -1,4 +1,5 @@ """Support for Lutron lights.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lutron/scene.py b/homeassistant/components/lutron/scene.py index 9485eddf78b..b66ca08a587 100644 --- a/homeassistant/components/lutron/scene.py +++ b/homeassistant/components/lutron/scene.py @@ -1,4 +1,5 @@ """Support for Lutron scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 37aa26f6313..9ffce0aa530 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -1,4 +1,5 @@ """Support for Lutron switches.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 0dceada821e..f6fed0688c4 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -1,4 +1,5 @@ """Component for interacting with a Lutron Caseta system.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index da7d6106796..73d468a88f2 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Lutron Caseta Occupancy/Vacancy Sensors.""" + from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/lutron_caseta/button.py b/homeassistant/components/lutron_caseta/button.py index d31e4579675..a1ed43a8b03 100644 --- a/homeassistant/components/lutron_caseta/button.py +++ b/homeassistant/components/lutron_caseta/button.py @@ -1,4 +1,5 @@ """Support for pico and keypad buttons.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 7323acc3e4c..d7b47aebc7e 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Lutron Caseta.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index 7e178698afe..fd5d2ad222d 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for lutron caseta.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lutron_caseta/diagnostics.py b/homeassistant/components/lutron_caseta/diagnostics.py index 07bd0a9e8ce..61a24d21b4e 100644 --- a/homeassistant/components/lutron_caseta/diagnostics.py +++ b/homeassistant/components/lutron_caseta/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for lutron_caseta.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index ba69f17d880..1577cf52727 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -1,4 +1,5 @@ """Support for Lutron Caseta fans.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index eb3e38b2e39..44c4c63e094 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -1,4 +1,5 @@ """Support for Lutron Caseta lights.""" + from datetime import timedelta from typing import Any diff --git a/homeassistant/components/lutron_caseta/logbook.py b/homeassistant/components/lutron_caseta/logbook.py index ec612ded375..5b5d2c0f9f1 100644 --- a/homeassistant/components/lutron_caseta/logbook.py +++ b/homeassistant/components/lutron_caseta/logbook.py @@ -1,4 +1,5 @@ """Describe lutron_caseta logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/lutron_caseta/models.py b/homeassistant/components/lutron_caseta/models.py index 91b042106cb..d5ccbecbd61 100644 --- a/homeassistant/components/lutron_caseta/models.py +++ b/homeassistant/components/lutron_caseta/models.py @@ -1,4 +1,5 @@ """The lutron_caseta integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index 520dcd965f2..f4aebdafe9b 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -1,4 +1,5 @@ """Support for Lutron Caseta scenes.""" + from typing import Any from pylutron_caseta.smartbridge import Smartbridge diff --git a/homeassistant/components/lutron_caseta/util.py b/homeassistant/components/lutron_caseta/util.py index dfcf7a32228..07b5b502fd0 100644 --- a/homeassistant/components/lutron_caseta/util.py +++ b/homeassistant/components/lutron_caseta/util.py @@ -1,4 +1,5 @@ """Support for Lutron Caseta.""" + from __future__ import annotations diff --git a/homeassistant/components/lw12wifi/light.py b/homeassistant/components/lw12wifi/light.py index 9f520a79ae1..272fcd4a8a1 100644 --- a/homeassistant/components/lw12wifi/light.py +++ b/homeassistant/components/lw12wifi/light.py @@ -1,4 +1,5 @@ """Support for Lagute LW-12 WiFi LED Controller.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/lyric/__init__.py b/homeassistant/components/lyric/__init__.py index d048b31d0b0..e2c85c1400b 100644 --- a/homeassistant/components/lyric/__init__.py +++ b/homeassistant/components/lyric/__init__.py @@ -1,4 +1,5 @@ """The Honeywell Lyric integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lyric/api.py b/homeassistant/components/lyric/api.py index 171e010137a..c9a424bf8ab 100644 --- a/homeassistant/components/lyric/api.py +++ b/homeassistant/components/lyric/api.py @@ -1,4 +1,5 @@ """API for Honeywell Lyric bound to Home Assistant OAuth.""" + from typing import cast from aiohttp import BasicAuth, ClientSession diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index ecf9b50474d..3bba2f677b0 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -1,4 +1,5 @@ """Support for Honeywell Lyric climate platform.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/lyric/config_flow.py b/homeassistant/components/lyric/config_flow.py index 16c5c40cbea..db4647145fe 100644 --- a/homeassistant/components/lyric/config_flow.py +++ b/homeassistant/components/lyric/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Honeywell Lyric.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/lyric/const.py b/homeassistant/components/lyric/const.py index 4f2f72b937b..6d973b56181 100644 --- a/homeassistant/components/lyric/const.py +++ b/homeassistant/components/lyric/const.py @@ -1,4 +1,5 @@ """Constants for the Honeywell Lyric integration.""" + from aiohttp.client_exceptions import ClientResponseError from aiolyric.exceptions import LyricAuthenticationException, LyricException diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 4e2339368f0..858c6c25bd4 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -1,4 +1,5 @@ """Support for Honeywell Lyric sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 1becc15624e..ef9a36ca20c 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -1,4 +1,5 @@ """Support for Voice mailboxes.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mailgun/config_flow.py b/homeassistant/components/mailgun/config_flow.py index bfeaed5ae5b..3af12460e5e 100644 --- a/homeassistant/components/mailgun/config_flow.py +++ b/homeassistant/components/mailgun/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Mailgun.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/mailgun/notify.py b/homeassistant/components/mailgun/notify.py index b7104d4a0f1..ed5b3a69135 100644 --- a/homeassistant/components/mailgun/notify.py +++ b/homeassistant/components/mailgun/notify.py @@ -1,4 +1,5 @@ """Support for the Mailgun mail notifications.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 099275a98a1..6f4a3306c29 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for manual alarms.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index d1442a4e9ed..9da0ff97838 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for manual alarms controllable via MQTT.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/map/__init__.py b/homeassistant/components/map/__init__.py index a3ba65be7db..a78199ad660 100644 --- a/homeassistant/components/map/__init__.py +++ b/homeassistant/components/map/__init__.py @@ -1,4 +1,5 @@ """Support for showing device locations.""" + from homeassistant.components import frontend from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/marytts/tts.py b/homeassistant/components/marytts/tts.py index 56f31d81d97..168d735a987 100644 --- a/homeassistant/components/marytts/tts.py +++ b/homeassistant/components/marytts/tts.py @@ -1,4 +1,5 @@ """Support for the MaryTTS service.""" + from __future__ import annotations from speak2mary import MaryTTS diff --git a/homeassistant/components/mastodon/notify.py b/homeassistant/components/mastodon/notify.py index 8f259bc3433..97ab2145486 100644 --- a/homeassistant/components/mastodon/notify.py +++ b/homeassistant/components/mastodon/notify.py @@ -1,4 +1,5 @@ """Mastodon platform for notify component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index e91ee4d270c..5c7d66b7a0b 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -1,4 +1,5 @@ """The Matrix bot component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/matrix/notify.py b/homeassistant/components/matrix/notify.py index c71f91eb582..0c8430afacd 100644 --- a/homeassistant/components/matrix/notify.py +++ b/homeassistant/components/matrix/notify.py @@ -1,4 +1,5 @@ """Support for Matrix notifications.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/matter/adapter.py b/homeassistant/components/matter/adapter.py index 6d7d437a206..e6185de17a6 100644 --- a/homeassistant/components/matter/adapter.py +++ b/homeassistant/components/matter/adapter.py @@ -1,4 +1,5 @@ """Matter to Home Assistant adapter.""" + from __future__ import annotations from typing import TYPE_CHECKING, cast diff --git a/homeassistant/components/matter/addon.py b/homeassistant/components/matter/addon.py index 84f430a58d8..a463685a073 100644 --- a/homeassistant/components/matter/addon.py +++ b/homeassistant/components/matter/addon.py @@ -1,4 +1,5 @@ """Provide add-on management.""" + from __future__ import annotations from homeassistant.components.hassio import AddonManager diff --git a/homeassistant/components/matter/api.py b/homeassistant/components/matter/api.py index 21445e469aa..94980617c0d 100644 --- a/homeassistant/components/matter/api.py +++ b/homeassistant/components/matter/api.py @@ -1,4 +1,5 @@ """Handle websocket api for Matter.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py index ea87fabf3f5..23ac2195355 100644 --- a/homeassistant/components/matter/binary_sensor.py +++ b/homeassistant/components/matter/binary_sensor.py @@ -1,4 +1,5 @@ """Matter binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/matter/climate.py b/homeassistant/components/matter/climate.py index 8769fc430d8..5ae1f7ca486 100644 --- a/homeassistant/components/matter/climate.py +++ b/homeassistant/components/matter/climate.py @@ -1,4 +1,5 @@ """Matter climate platform.""" + from __future__ import annotations from enum import IntEnum diff --git a/homeassistant/components/matter/config_flow.py b/homeassistant/components/matter/config_flow.py index 8982067207e..7dc06807a98 100644 --- a/homeassistant/components/matter/config_flow.py +++ b/homeassistant/components/matter/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Matter integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/matter/cover.py b/homeassistant/components/matter/cover.py index 590f325cf22..ea5250c9bd3 100644 --- a/homeassistant/components/matter/cover.py +++ b/homeassistant/components/matter/cover.py @@ -1,4 +1,5 @@ """Matter cover.""" + from __future__ import annotations from enum import IntEnum diff --git a/homeassistant/components/matter/diagnostics.py b/homeassistant/components/matter/diagnostics.py index d875f37dcae..23b6854c791 100644 --- a/homeassistant/components/matter/diagnostics.py +++ b/homeassistant/components/matter/diagnostics.py @@ -1,4 +1,5 @@ """Provide diagnostics for Matter.""" + from __future__ import annotations from copy import deepcopy diff --git a/homeassistant/components/matter/discovery.py b/homeassistant/components/matter/discovery.py index e1d004a15c8..985ac1c996e 100644 --- a/homeassistant/components/matter/discovery.py +++ b/homeassistant/components/matter/discovery.py @@ -1,4 +1,5 @@ """Map Matter Nodes and Attributes to Home Assistant entities.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/matter/entity.py b/homeassistant/components/matter/entity.py index 5c3f65d903c..dcb3586934b 100644 --- a/homeassistant/components/matter/entity.py +++ b/homeassistant/components/matter/entity.py @@ -1,4 +1,5 @@ """Matter entity base class.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/matter/event.py b/homeassistant/components/matter/event.py index e84fcec32d8..1b55b700085 100644 --- a/homeassistant/components/matter/event.py +++ b/homeassistant/components/matter/event.py @@ -1,4 +1,5 @@ """Matter event entities from Node events.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/matter/helpers.py b/homeassistant/components/matter/helpers.py index 8f7f3d81883..9aa58879214 100644 --- a/homeassistant/components/matter/helpers.py +++ b/homeassistant/components/matter/helpers.py @@ -1,4 +1,5 @@ """Provide integration helpers that are aware of the matter integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/matter/light.py b/homeassistant/components/matter/light.py index aa93cef9916..fce780896a4 100644 --- a/homeassistant/components/matter/light.py +++ b/homeassistant/components/matter/light.py @@ -1,4 +1,5 @@ """Matter light.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/matter/lock.py b/homeassistant/components/matter/lock.py index dd29638f765..e5067efd482 100644 --- a/homeassistant/components/matter/lock.py +++ b/homeassistant/components/matter/lock.py @@ -1,4 +1,5 @@ """Matter lock.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/matter/models.py b/homeassistant/components/matter/models.py index 5f47f73b139..18e503523ae 100644 --- a/homeassistant/components/matter/models.py +++ b/homeassistant/components/matter/models.py @@ -1,4 +1,5 @@ """Models used for the Matter integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index 90a9cb3fcc8..6f1bd1d142b 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -1,4 +1,5 @@ """Matter sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index 61922e8e8c9..91a28bdab8c 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -1,4 +1,5 @@ """Matter switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/matter/util.py b/homeassistant/components/matter/util.py index 834c71a0307..0df2230ab96 100644 --- a/homeassistant/components/matter/util.py +++ b/homeassistant/components/matter/util.py @@ -1,4 +1,5 @@ """Provide integration utilities.""" + from __future__ import annotations XY_COLOR_FACTOR = 65536 diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index 6dcfcf8dca4..208b93eb19a 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -1,4 +1,5 @@ """Support for MAX! binary sensors via MAX! Cube.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 42abed48724..1b7dc6b901d 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -1,4 +1,5 @@ """Support for MAX! Thermostats via MAX! Cube.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mazda/__init__.py b/homeassistant/components/mazda/__init__.py index 75e7baf7413..fd323060ac0 100644 --- a/homeassistant/components/mazda/__init__.py +++ b/homeassistant/components/mazda/__init__.py @@ -1,4 +1,5 @@ """The Mazda Connected Services integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry, ConfigEntryState diff --git a/homeassistant/components/meater/config_flow.py b/homeassistant/components/meater/config_flow.py index d325bcd4e5a..0f2bb35755f 100644 --- a/homeassistant/components/meater/config_flow.py +++ b/homeassistant/components/meater/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Meater.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/meater/sensor.py b/homeassistant/components/meater/sensor.py index a7e03ae7c22..30a10531fad 100644 --- a/homeassistant/components/meater/sensor.py +++ b/homeassistant/components/meater/sensor.py @@ -1,4 +1,5 @@ """The Meater Temperature Probe integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/medcom_ble/__init__.py b/homeassistant/components/medcom_ble/__init__.py index a129c4fc7f9..36357746b95 100644 --- a/homeassistant/components/medcom_ble/__init__.py +++ b/homeassistant/components/medcom_ble/__init__.py @@ -1,4 +1,5 @@ """The Medcom BLE integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/medcom_ble/sensor.py b/homeassistant/components/medcom_ble/sensor.py index 74c78a3cf9e..b5cb29be845 100644 --- a/homeassistant/components/medcom_ble/sensor.py +++ b/homeassistant/components/medcom_ble/sensor.py @@ -1,4 +1,5 @@ """Support for Medcom BLE radiation monitor sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index b657caceaff..f6b153b2ef8 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -1,4 +1,5 @@ """Decorator service for the media_player.play_media service.""" + from collections.abc import Callable import logging from pathlib import Path diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index ffb1d6d4a32..4bb026ffc0d 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -1,4 +1,5 @@ """Component to interface with various media players.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/media_player/browse_media.py b/homeassistant/components/media_player/browse_media.py index 1e9be742c53..351d4e9140f 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -1,4 +1,5 @@ """Browse media features for media player.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index 2c609750153..9b69ee62846 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -1,4 +1,5 @@ """Provides the constants needed for component.""" + from enum import IntFlag, StrEnum # How long our auth signature on the content should be valid for diff --git a/homeassistant/components/media_player/device_condition.py b/homeassistant/components/media_player/device_condition.py index 5efee0c0b49..660f53bc8d5 100644 --- a/homeassistant/components/media_player/device_condition.py +++ b/homeassistant/components/media_player/device_condition.py @@ -1,4 +1,5 @@ """Provides device automations for Media player.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/media_player/device_trigger.py b/homeassistant/components/media_player/device_trigger.py index e626059841c..9d1a3fab37e 100644 --- a/homeassistant/components/media_player/device_trigger.py +++ b/homeassistant/components/media_player/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Media player.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/media_player/errors.py b/homeassistant/components/media_player/errors.py index 2e8443c2794..5888ba6b5b0 100644 --- a/homeassistant/components/media_player/errors.py +++ b/homeassistant/components/media_player/errors.py @@ -1,4 +1,5 @@ """Errors for the Media Player component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index 387792a1b60..a40575a9dba 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -1,4 +1,5 @@ """Module that groups code required to handle state restore for component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/media_player/significant_change.py b/homeassistant/components/media_player/significant_change.py index 43a253d9220..ea5cf9d1b27 100644 --- a/homeassistant/components/media_player/significant_change.py +++ b/homeassistant/components/media_player/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Media Player state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index fdb7fa5f1f2..2f996523fdc 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -1,4 +1,5 @@ """The media_source integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/media_source/error.py b/homeassistant/components/media_source/error.py index 00f3ced5d8d..120e7583e23 100644 --- a/homeassistant/components/media_source/error.py +++ b/homeassistant/components/media_source/error.py @@ -1,4 +1,5 @@ """Errors for media source.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index ac6623a3af8..8d739d8d027 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -1,4 +1,5 @@ """Local Media Source Implementation.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index cbe71447a3f..482ed0e855f 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -1,4 +1,5 @@ """Media Source models.""" + from __future__ import annotations from abc import ABC diff --git a/homeassistant/components/mediaroom/media_player.py b/homeassistant/components/mediaroom/media_player.py index 4dc6d677abb..22417adcf51 100644 --- a/homeassistant/components/mediaroom/media_player.py +++ b/homeassistant/components/mediaroom/media_player.py @@ -1,4 +1,5 @@ """Support for the Mediaroom Set-up-box.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/melcloud/__init__.py b/homeassistant/components/melcloud/__init__.py index 2db3e79dfe9..30645661ff1 100644 --- a/homeassistant/components/melcloud/__init__.py +++ b/homeassistant/components/melcloud/__init__.py @@ -1,4 +1,5 @@ """The MELCloud Climate integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index ed37ff76b76..4bf12650b82 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -1,4 +1,5 @@ """Platform for climate integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/melcloud/config_flow.py b/homeassistant/components/melcloud/config_flow.py index f848846409d..f071b64988d 100644 --- a/homeassistant/components/melcloud/config_flow.py +++ b/homeassistant/components/melcloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the MELCloud platform.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index c37c26e722e..73a72ba9dfb 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -1,4 +1,5 @@ """Support for MelCloud device sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index 210b8bd51e2..7d170430b04 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -1,4 +1,5 @@ """Platform for water_heater integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index f94c3af6d9a..af59046c58b 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -1,4 +1,5 @@ """Support for Melissa Climate A/C.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/melnor/sensor.py b/homeassistant/components/melnor/sensor.py index 255c3c9747d..8765cd538f8 100644 --- a/homeassistant/components/melnor/sensor.py +++ b/homeassistant/components/melnor/sensor.py @@ -1,4 +1,5 @@ """Sensor support for Melnor Bluetooth water timer.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/meraki/device_tracker.py b/homeassistant/components/meraki/device_tracker.py index 4149c5ac8ae..58da08d984c 100644 --- a/homeassistant/components/meraki/device_tracker.py +++ b/homeassistant/components/meraki/device_tracker.py @@ -1,4 +1,5 @@ """Support for the Meraki CMX location service.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/message_bird/notify.py b/homeassistant/components/message_bird/notify.py index 241646918c6..b1b7b373e6a 100644 --- a/homeassistant/components/message_bird/notify.py +++ b/homeassistant/components/message_bird/notify.py @@ -1,4 +1,5 @@ """MessageBird platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/met/__init__.py b/homeassistant/components/met/__init__.py index a10a07b5374..ec402a16489 100644 --- a/homeassistant/components/met/__init__.py +++ b/homeassistant/components/met/__init__.py @@ -1,4 +1,5 @@ """The met component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index 2afe6f8cd30..84a44682413 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Met component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/met/const.py b/homeassistant/components/met/const.py index b690f1b6723..c513e98504e 100644 --- a/homeassistant/components/met/const.py +++ b/homeassistant/components/met/const.py @@ -1,4 +1,5 @@ """Constants for Met component.""" + from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, diff --git a/homeassistant/components/met/coordinator.py b/homeassistant/components/met/coordinator.py index 6354e286cee..e5a4dbf754c 100644 --- a/homeassistant/components/met/coordinator.py +++ b/homeassistant/components/met/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for Met.no integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index e4a63e326a6..86ef77f03f6 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -1,4 +1,5 @@ """Support for Met.no weather service.""" + from __future__ import annotations from types import MappingProxyType diff --git a/homeassistant/components/met_eireann/__init__.py b/homeassistant/components/met_eireann/__init__.py index 5edecbbac0b..92f2ffcfac6 100644 --- a/homeassistant/components/met_eireann/__init__.py +++ b/homeassistant/components/met_eireann/__init__.py @@ -1,4 +1,5 @@ """The met_eireann component.""" + from datetime import timedelta import logging from types import MappingProxyType diff --git a/homeassistant/components/met_eireann/config_flow.py b/homeassistant/components/met_eireann/config_flow.py index d1e49e4961e..422b46827da 100644 --- a/homeassistant/components/met_eireann/config_flow.py +++ b/homeassistant/components/met_eireann/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Met Éireann component.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/met_eireann/const.py b/homeassistant/components/met_eireann/const.py index 9316aad1b17..3a4c3dda507 100644 --- a/homeassistant/components/met_eireann/const.py +++ b/homeassistant/components/met_eireann/const.py @@ -1,4 +1,5 @@ """Constants for Met Éireann component.""" + from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLOUDY, diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index 3f1cd2a5e34..e43572ade6c 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -1,4 +1,5 @@ """Support for Meteo-France weather data.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/meteo_france/config_flow.py b/homeassistant/components/meteo_france/config_flow.py index 695b0c9b353..37995534fb1 100644 --- a/homeassistant/components/meteo_france/config_flow.py +++ b/homeassistant/components/meteo_france/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Meteo-France integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index e950dfe1fa8..2230f43b754 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -1,4 +1,5 @@ """Meteo-France component constants.""" + from __future__ import annotations from homeassistant.components.weather import ( diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index c5ff38f2a87..2587c5d4337 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -1,4 +1,5 @@ """Support for Meteo-France raining forecast sensor.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/meteoalarm/binary_sensor.py b/homeassistant/components/meteoalarm/binary_sensor.py index 246074ce585..8b38ac6dbb3 100644 --- a/homeassistant/components/meteoalarm/binary_sensor.py +++ b/homeassistant/components/meteoalarm/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor for MeteoAlarm.eu.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/meteoclimatic/const.py b/homeassistant/components/meteoclimatic/const.py index 4a7276d4e42..3d8f93d014d 100644 --- a/homeassistant/components/meteoclimatic/const.py +++ b/homeassistant/components/meteoclimatic/const.py @@ -1,4 +1,5 @@ """Meteoclimatic component constants.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/meteoclimatic/sensor.py b/homeassistant/components/meteoclimatic/sensor.py index 9a54e766945..2194f82e43e 100644 --- a/homeassistant/components/meteoclimatic/sensor.py +++ b/homeassistant/components/meteoclimatic/sensor.py @@ -1,4 +1,5 @@ """Support for Meteoclimatic sensor.""" + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, diff --git a/homeassistant/components/meteoclimatic/weather.py b/homeassistant/components/meteoclimatic/weather.py index f9b341cf114..75a93689efa 100644 --- a/homeassistant/components/meteoclimatic/weather.py +++ b/homeassistant/components/meteoclimatic/weather.py @@ -1,4 +1,5 @@ """Support for Meteoclimatic weather service.""" + from meteoclimatic import Condition from homeassistant.components.weather import WeatherEntity diff --git a/homeassistant/components/metoffice/__init__.py b/homeassistant/components/metoffice/__init__.py index a658de9a024..cdc6c2c7500 100644 --- a/homeassistant/components/metoffice/__init__.py +++ b/homeassistant/components/metoffice/__init__.py @@ -1,4 +1,5 @@ """The Met Office integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/metoffice/config_flow.py b/homeassistant/components/metoffice/config_flow.py index 0677ae5e44c..196d24ff68a 100644 --- a/homeassistant/components/metoffice/config_flow.py +++ b/homeassistant/components/metoffice/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Met Office integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/metoffice/const.py b/homeassistant/components/metoffice/const.py index 8b86784b70b..966aec7d381 100644 --- a/homeassistant/components/metoffice/const.py +++ b/homeassistant/components/metoffice/const.py @@ -1,4 +1,5 @@ """Constants for Met Office Integration.""" + from datetime import timedelta from homeassistant.components.weather import ( diff --git a/homeassistant/components/metoffice/data.py b/homeassistant/components/metoffice/data.py index c6bb2b4c01b..651e56c3adc 100644 --- a/homeassistant/components/metoffice/data.py +++ b/homeassistant/components/metoffice/data.py @@ -1,4 +1,5 @@ """Common Met Office Data class used by both sensor and entity.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/metoffice/helpers.py b/homeassistant/components/metoffice/helpers.py index 5b698bf19da..56d4d8f971b 100644 --- a/homeassistant/components/metoffice/helpers.py +++ b/homeassistant/components/metoffice/helpers.py @@ -1,4 +1,5 @@ """Helpers used for Met Office integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py index 84a51a0d584..61f825abdc3 100644 --- a/homeassistant/components/metoffice/sensor.py +++ b/homeassistant/components/metoffice/sensor.py @@ -1,4 +1,5 @@ """Support for UK Met Office weather service.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py index b6e35168276..ed4268d1722 100644 --- a/homeassistant/components/metoffice/weather.py +++ b/homeassistant/components/metoffice/weather.py @@ -1,4 +1,5 @@ """Support for UK Met Office weather service.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/mfi/sensor.py b/homeassistant/components/mfi/sensor.py index 84f4abdaead..afa5e00bf02 100644 --- a/homeassistant/components/mfi/sensor.py +++ b/homeassistant/components/mfi/sensor.py @@ -1,4 +1,5 @@ """Support for Ubiquiti mFi sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mfi/switch.py b/homeassistant/components/mfi/switch.py index a25d0dfc87b..fe0aeb902ee 100644 --- a/homeassistant/components/mfi/switch.py +++ b/homeassistant/components/mfi/switch.py @@ -1,4 +1,5 @@ """Support for Ubiquiti mFi switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/microbees/button.py b/homeassistant/components/microbees/button.py index cf82a60bfa4..f449fa9afee 100644 --- a/homeassistant/components/microbees/button.py +++ b/homeassistant/components/microbees/button.py @@ -1,4 +1,5 @@ """Button integration microBees.""" + from typing import Any from homeassistant.components.button import ButtonEntity diff --git a/homeassistant/components/microbees/config_flow.py b/homeassistant/components/microbees/config_flow.py index 2eaed8dbafc..dedb6c1f374 100644 --- a/homeassistant/components/microbees/config_flow.py +++ b/homeassistant/components/microbees/config_flow.py @@ -1,4 +1,5 @@ """Config flow for microBees integration.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/microbees/const.py b/homeassistant/components/microbees/const.py index cf7644c8dfa..df4d3f63bd0 100644 --- a/homeassistant/components/microbees/const.py +++ b/homeassistant/components/microbees/const.py @@ -1,4 +1,5 @@ """Constants for the microBees integration.""" + from homeassistant.const import Platform DOMAIN = "microbees" diff --git a/homeassistant/components/microbees/light.py b/homeassistant/components/microbees/light.py index 7616cba41b0..411eab22324 100644 --- a/homeassistant/components/microbees/light.py +++ b/homeassistant/components/microbees/light.py @@ -1,4 +1,5 @@ """Light integration microBees.""" + from typing import Any from homeassistant.components.light import ATTR_RGBW_COLOR, ColorMode, LightEntity diff --git a/homeassistant/components/microbees/sensor.py b/homeassistant/components/microbees/sensor.py index 56db4c00ee3..360422de735 100644 --- a/homeassistant/components/microbees/sensor.py +++ b/homeassistant/components/microbees/sensor.py @@ -1,4 +1,5 @@ """sensor integration microBees.""" + from microBeesPy import Sensor from homeassistant.components.sensor import ( diff --git a/homeassistant/components/microbees/switch.py b/homeassistant/components/microbees/switch.py index 4a52d95620b..8e3c03e9ba4 100644 --- a/homeassistant/components/microbees/switch.py +++ b/homeassistant/components/microbees/switch.py @@ -1,4 +1,5 @@ """Switch integration microBees.""" + from typing import Any from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index e3f722ae2be..23535911e5c 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -1,4 +1,5 @@ """Support for Microsoft face recognition.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/microsoft_face_detect/image_processing.py b/homeassistant/components/microsoft_face_detect/image_processing.py index 8b69e21428d..536557bdddb 100644 --- a/homeassistant/components/microsoft_face_detect/image_processing.py +++ b/homeassistant/components/microsoft_face_detect/image_processing.py @@ -1,4 +1,5 @@ """Component that will help set the Microsoft face detect processing.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/microsoft_face_identify/image_processing.py b/homeassistant/components/microsoft_face_identify/image_processing.py index 4c76d29ebc6..7903df22996 100644 --- a/homeassistant/components/microsoft_face_identify/image_processing.py +++ b/homeassistant/components/microsoft_face_identify/image_processing.py @@ -1,4 +1,5 @@ """Component that will help set the Microsoft face for verify processing.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mikrotik/__init__.py b/homeassistant/components/mikrotik/__init__.py index 6c98c389984..76d9a57c7ef 100644 --- a/homeassistant/components/mikrotik/__init__.py +++ b/homeassistant/components/mikrotik/__init__.py @@ -1,4 +1,5 @@ """The Mikrotik component.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/mikrotik/config_flow.py b/homeassistant/components/mikrotik/config_flow.py index d2a7c5673b0..8e5ff50e590 100644 --- a/homeassistant/components/mikrotik/config_flow.py +++ b/homeassistant/components/mikrotik/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Mikrotik.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/mikrotik/const.py b/homeassistant/components/mikrotik/const.py index 8407dd14a6e..772c956b54b 100644 --- a/homeassistant/components/mikrotik/const.py +++ b/homeassistant/components/mikrotik/const.py @@ -1,4 +1,5 @@ """Constants used in the Mikrotik components.""" + from typing import Final DOMAIN: Final = "mikrotik" diff --git a/homeassistant/components/mikrotik/device_tracker.py b/homeassistant/components/mikrotik/device_tracker.py index 8136334514f..866eba0b8bb 100644 --- a/homeassistant/components/mikrotik/device_tracker.py +++ b/homeassistant/components/mikrotik/device_tracker.py @@ -1,4 +1,5 @@ """Support for Mikrotik routers as device tracker.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mikrotik/errors.py b/homeassistant/components/mikrotik/errors.py index 22cd63d7468..d3049c7a0b8 100644 --- a/homeassistant/components/mikrotik/errors.py +++ b/homeassistant/components/mikrotik/errors.py @@ -1,4 +1,5 @@ """Errors for the Mikrotik component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/mikrotik/hub.py b/homeassistant/components/mikrotik/hub.py index 044a45fb9b5..6b94a621683 100644 --- a/homeassistant/components/mikrotik/hub.py +++ b/homeassistant/components/mikrotik/hub.py @@ -1,4 +1,5 @@ """The Mikrotik router class.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/mill/__init__.py b/homeassistant/components/mill/__init__.py index 136c4a2940f..401a0f9e854 100644 --- a/homeassistant/components/mill/__init__.py +++ b/homeassistant/components/mill/__init__.py @@ -1,4 +1,5 @@ """The mill component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/mill/config_flow.py b/homeassistant/components/mill/config_flow.py index ce99f6cab65..58660d6358e 100644 --- a/homeassistant/components/mill/config_flow.py +++ b/homeassistant/components/mill/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Mill integration.""" + from mill import Mill from mill_local import Mill as MillLocal import voluptuous as vol diff --git a/homeassistant/components/mill/sensor.py b/homeassistant/components/mill/sensor.py index 8c7c418e8ff..64b9008a82b 100644 --- a/homeassistant/components/mill/sensor.py +++ b/homeassistant/components/mill/sensor.py @@ -1,4 +1,5 @@ """Support for mill wifi-enabled home heaters.""" + from __future__ import annotations import mill diff --git a/homeassistant/components/min_max/config_flow.py b/homeassistant/components/min_max/config_flow.py index f449c98819e..36133f7394d 100644 --- a/homeassistant/components/min_max/config_flow.py +++ b/homeassistant/components/min_max/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Min/Max integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index cc26a684a8d..7ced9df661c 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -1,4 +1,5 @@ """Support for displaying minimal, maximal, mean or median values.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 2cd6c51546a..5ec737c3f73 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -1,4 +1,5 @@ """The Minecraft Server integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/minecraft_server/binary_sensor.py b/homeassistant/components/minecraft_server/binary_sensor.py index 974889b7245..707e60dc0db 100644 --- a/homeassistant/components/minecraft_server/binary_sensor.py +++ b/homeassistant/components/minecraft_server/binary_sensor.py @@ -1,4 +1,5 @@ """The Minecraft Server binary sensor platform.""" + from dataclasses import dataclass from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/minecraft_server/config_flow.py b/homeassistant/components/minecraft_server/config_flow.py index b870c032b1e..654d903068f 100644 --- a/homeassistant/components/minecraft_server/config_flow.py +++ b/homeassistant/components/minecraft_server/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Minecraft Server integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/minecraft_server/coordinator.py b/homeassistant/components/minecraft_server/coordinator.py index e498375cafc..37eeb9f2ac2 100644 --- a/homeassistant/components/minecraft_server/coordinator.py +++ b/homeassistant/components/minecraft_server/coordinator.py @@ -1,4 +1,5 @@ """The Minecraft Server integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/minecraft_server/diagnostics.py b/homeassistant/components/minecraft_server/diagnostics.py index 62e507ef09f..1cae535dc43 100644 --- a/homeassistant/components/minecraft_server/diagnostics.py +++ b/homeassistant/components/minecraft_server/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics for the Minecraft Server integration.""" + from collections.abc import Iterable from dataclasses import asdict from typing import Any diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index f6e0805e71b..ada3e68bab7 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -1,4 +1,5 @@ """The Minecraft Server sensor platform.""" + from __future__ import annotations from collections.abc import Callable, MutableMapping diff --git a/homeassistant/components/minio/__init__.py b/homeassistant/components/minio/__init__.py index 1f325f3866d..e2cbcdf9ed1 100644 --- a/homeassistant/components/minio/__init__.py +++ b/homeassistant/components/minio/__init__.py @@ -1,4 +1,5 @@ """Minio component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/minio/minio_helper.py b/homeassistant/components/minio/minio_helper.py index 7edb11797eb..979de40ece7 100644 --- a/homeassistant/components/minio/minio_helper.py +++ b/homeassistant/components/minio/minio_helper.py @@ -1,4 +1,5 @@ """Minio helper methods.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index d424df620cf..fb8c7e3ef35 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -1,4 +1,5 @@ """Support for IP Cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mjpeg/config_flow.py b/homeassistant/components/mjpeg/config_flow.py index c5c3938aff4..84267936788 100644 --- a/homeassistant/components/mjpeg/config_flow.py +++ b/homeassistant/components/mjpeg/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the MJPEG IP Camera integration.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/moat/__init__.py b/homeassistant/components/moat/__init__.py index ed360f53b65..4c48c03b897 100644 --- a/homeassistant/components/moat/__init__.py +++ b/homeassistant/components/moat/__init__.py @@ -1,4 +1,5 @@ """The Moat Bluetooth BLE integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/moat/config_flow.py b/homeassistant/components/moat/config_flow.py index ce400b6851d..e7a1d55f05c 100644 --- a/homeassistant/components/moat/config_flow.py +++ b/homeassistant/components/moat/config_flow.py @@ -1,4 +1,5 @@ """Config flow for moat ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/moat/sensor.py b/homeassistant/components/moat/sensor.py index f75717fad40..3118c539d3a 100644 --- a/homeassistant/components/moat/sensor.py +++ b/homeassistant/components/moat/sensor.py @@ -1,4 +1,5 @@ """Support for moat ble sensors.""" + from __future__ import annotations from moat_ble import DeviceClass, DeviceKey, SensorUpdate, Units diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 19aba56f260..28f66954ca3 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -1,4 +1,5 @@ """Integrates Native Apps to Home Assistant.""" + from contextlib import suppress from typing import Any diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py index 2be71965371..58683ef378c 100644 --- a/homeassistant/components/mobile_app/binary_sensor.py +++ b/homeassistant/components/mobile_app/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform for mobile_app.""" + from typing import Any from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/mobile_app/device_action.py b/homeassistant/components/mobile_app/device_action.py index f6e870fc7d6..bebdef0e917 100644 --- a/homeassistant/components/mobile_app/device_action.py +++ b/homeassistant/components/mobile_app/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Mobile App.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/mobile_app/device_tracker.py b/homeassistant/components/mobile_app/device_tracker.py index fb555db49cb..2c7a4147811 100644 --- a/homeassistant/components/mobile_app/device_tracker.py +++ b/homeassistant/components/mobile_app/device_tracker.py @@ -1,4 +1,5 @@ """Device tracker for Mobile app.""" + from homeassistant.components.device_tracker import ( ATTR_BATTERY, ATTR_GPS, diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 76cf22cef54..1cac62ce964 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -1,4 +1,5 @@ """A entity class for mobile_app.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index 9265b72d0d0..e30974123df 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -1,4 +1,5 @@ """Helpers for mobile_app.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index b48221c3c56..8c071fe0342 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -1,4 +1,5 @@ """Provides an HTTP API for mobile_app.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/mobile_app/logbook.py b/homeassistant/components/mobile_app/logbook.py index 083db294a1c..6a863e6a75b 100644 --- a/homeassistant/components/mobile_app/logbook.py +++ b/homeassistant/components/mobile_app/logbook.py @@ -1,4 +1,5 @@ """Describe mobile_app logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py index e6f7126b0b8..c0efd302c47 100644 --- a/homeassistant/components/mobile_app/notify.py +++ b/homeassistant/components/mobile_app/notify.py @@ -1,4 +1,5 @@ """Support for mobile_app push notifications.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mobile_app/push_notification.py b/homeassistant/components/mobile_app/push_notification.py index 0ef9739c8bd..d295a844878 100644 --- a/homeassistant/components/mobile_app/push_notification.py +++ b/homeassistant/components/mobile_app/push_notification.py @@ -1,4 +1,5 @@ """Push notification handling.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mobile_app/sensor.py b/homeassistant/components/mobile_app/sensor.py index fd712faf121..6f049d6f2d5 100644 --- a/homeassistant/components/mobile_app/sensor.py +++ b/homeassistant/components/mobile_app/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for mobile_app.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/mobile_app/util.py b/homeassistant/components/mobile_app/util.py index a7871d935ed..f139a203c34 100644 --- a/homeassistant/components/mobile_app/util.py +++ b/homeassistant/components/mobile_app/util.py @@ -1,4 +1,5 @@ """Mobile app utility functions.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 19335507b72..0a55a5337b3 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -1,4 +1,5 @@ """Webhook handlers for mobile_app.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mobile_app/websocket_api.py b/homeassistant/components/mobile_app/websocket_api.py index c900aa8f93b..83c3cb29cea 100644 --- a/homeassistant/components/mobile_app/websocket_api.py +++ b/homeassistant/components/mobile_app/websocket_api.py @@ -1,4 +1,5 @@ """Mobile app websocket API.""" + from __future__ import annotations from functools import wraps diff --git a/homeassistant/components/mochad/light.py b/homeassistant/components/mochad/light.py index b2dba04b205..4740823d85a 100644 --- a/homeassistant/components/mochad/light.py +++ b/homeassistant/components/mochad/light.py @@ -1,4 +1,5 @@ """Support for X10 dimmer over Mochad.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mochad/switch.py b/homeassistant/components/mochad/switch.py index fe90d94e40a..beb12d9d409 100644 --- a/homeassistant/components/mochad/switch.py +++ b/homeassistant/components/mochad/switch.py @@ -1,4 +1,5 @@ """Support for X10 switch over Mochad.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index e5bb9e8bf38..8224a79c835 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -1,4 +1,5 @@ """Support for Modbus.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index ac11bab303d..24d6ce2b307 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -1,4 +1,5 @@ """Base implementation for all modbus platforms.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 6c0f6422df2..0de7a87f219 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Modbus Coil and Discrete Input sensors.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index a57fe53ada7..07dd12d3c94 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -1,4 +1,5 @@ """Support for Generic Modbus Thermostats.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index e536a31c4f6..a2b3324c4ee 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -1,4 +1,5 @@ """Constants used in modbus integration.""" + from enum import Enum from homeassistant.const import ( diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index 072f1bb3d93..1221a05a5ac 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -1,4 +1,5 @@ """Support for Modbus covers.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/modbus/fan.py b/homeassistant/components/modbus/fan.py index e5006b66f81..93e3fdded1a 100644 --- a/homeassistant/components/modbus/fan.py +++ b/homeassistant/components/modbus/fan.py @@ -1,4 +1,5 @@ """Support for Modbus fans.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modbus/light.py b/homeassistant/components/modbus/light.py index acc01f39b46..16714219bc2 100644 --- a/homeassistant/components/modbus/light.py +++ b/homeassistant/components/modbus/light.py @@ -1,4 +1,5 @@ """Support for Modbus lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index c8e7fc3765e..498dcbf0f7e 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -1,4 +1,5 @@ """Support for Modbus.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 4f2a1d6dc76..b21a001c935 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,4 +1,5 @@ """Support for Modbus Register sensors.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 0c955ea409d..ff02e4a7a7e 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -1,4 +1,5 @@ """Support for Modbus switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 1819bc649cb..e31cbf79938 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -1,4 +1,5 @@ """Validate Modbus configuration.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/modem_callerid/__init__.py b/homeassistant/components/modem_callerid/__init__.py index bbdd1b05383..886e33b714b 100644 --- a/homeassistant/components/modem_callerid/__init__.py +++ b/homeassistant/components/modem_callerid/__init__.py @@ -1,4 +1,5 @@ """The Modem Caller ID integration.""" + from phone_modem import PhoneModem from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/modem_callerid/button.py b/homeassistant/components/modem_callerid/button.py index 703339f54e9..3cad9062be9 100644 --- a/homeassistant/components/modem_callerid/button.py +++ b/homeassistant/components/modem_callerid/button.py @@ -1,4 +1,5 @@ """Support for Phone Modem button.""" + from __future__ import annotations from phone_modem import PhoneModem diff --git a/homeassistant/components/modem_callerid/config_flow.py b/homeassistant/components/modem_callerid/config_flow.py index 52bbf51fc61..98e6708a34c 100644 --- a/homeassistant/components/modem_callerid/config_flow.py +++ b/homeassistant/components/modem_callerid/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Modem Caller ID integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modem_callerid/const.py b/homeassistant/components/modem_callerid/const.py index 361766f22a7..d86d2648a10 100644 --- a/homeassistant/components/modem_callerid/const.py +++ b/homeassistant/components/modem_callerid/const.py @@ -1,4 +1,5 @@ """Constants for the Modem Caller ID integration.""" + from typing import Final from phone_modem import exceptions diff --git a/homeassistant/components/modem_callerid/sensor.py b/homeassistant/components/modem_callerid/sensor.py index aac43b72c0d..00c821f3511 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -1,4 +1,5 @@ """A sensor for incoming calls using a USB modem that supports caller ID.""" + from __future__ import annotations from phone_modem import PhoneModem diff --git a/homeassistant/components/modern_forms/__init__.py b/homeassistant/components/modern_forms/__init__.py index c7310ea35a3..5b33a85578c 100644 --- a/homeassistant/components/modern_forms/__init__.py +++ b/homeassistant/components/modern_forms/__init__.py @@ -1,4 +1,5 @@ """The Modern Forms integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/modern_forms/binary_sensor.py b/homeassistant/components/modern_forms/binary_sensor.py index 148499aad2d..0322c5e39d7 100644 --- a/homeassistant/components/modern_forms/binary_sensor.py +++ b/homeassistant/components/modern_forms/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Modern Forms Binary Sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/modern_forms/config_flow.py b/homeassistant/components/modern_forms/config_flow.py index 80e8bf5cf77..c2b88d65a1b 100644 --- a/homeassistant/components/modern_forms/config_flow.py +++ b/homeassistant/components/modern_forms/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Modern Forms.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modern_forms/fan.py b/homeassistant/components/modern_forms/fan.py index e6bcff715b8..b714cf04879 100644 --- a/homeassistant/components/modern_forms/fan.py +++ b/homeassistant/components/modern_forms/fan.py @@ -1,4 +1,5 @@ """Support for Modern Forms Fan Fans.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modern_forms/light.py b/homeassistant/components/modern_forms/light.py index 74763948b9f..3284b96d31f 100644 --- a/homeassistant/components/modern_forms/light.py +++ b/homeassistant/components/modern_forms/light.py @@ -1,4 +1,5 @@ """Support for Modern Forms Fan lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/modern_forms/sensor.py b/homeassistant/components/modern_forms/sensor.py index f6f2255090a..6a92f0fcac2 100644 --- a/homeassistant/components/modern_forms/sensor.py +++ b/homeassistant/components/modern_forms/sensor.py @@ -1,4 +1,5 @@ """Support for Modern Forms switches.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/modern_forms/switch.py b/homeassistant/components/modern_forms/switch.py index fef03a96651..d8c76d733fc 100644 --- a/homeassistant/components/modern_forms/switch.py +++ b/homeassistant/components/modern_forms/switch.py @@ -1,4 +1,5 @@ """Support for Modern Forms switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/moehlenhoff_alpha2/__init__.py b/homeassistant/components/moehlenhoff_alpha2/__init__.py index 4a4c57b676e..1611d8ac4bc 100644 --- a/homeassistant/components/moehlenhoff_alpha2/__init__.py +++ b/homeassistant/components/moehlenhoff_alpha2/__init__.py @@ -1,4 +1,5 @@ """Support for the Moehlenhoff Alpha2.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index ce3844475c5..86f2aced069 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -1,4 +1,5 @@ """Calculates mold growth indication from temperature and humidity.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/monoprice/config_flow.py b/homeassistant/components/monoprice/config_flow.py index 43286946b7d..7b9113821d1 100644 --- a/homeassistant/components/monoprice/config_flow.py +++ b/homeassistant/components/monoprice/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Monoprice 6-Zone Amplifier integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/moon/__init__.py b/homeassistant/components/moon/__init__.py index e2eaaf89948..72305548fde 100644 --- a/homeassistant/components/moon/__init__.py +++ b/homeassistant/components/moon/__init__.py @@ -1,4 +1,5 @@ """The Moon integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/moon/config_flow.py b/homeassistant/components/moon/config_flow.py index 76f1850d6e7..1c424c866e4 100644 --- a/homeassistant/components/moon/config_flow.py +++ b/homeassistant/components/moon/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Moon integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/moon/const.py b/homeassistant/components/moon/const.py index 87c525758b9..3e926b4ff3e 100644 --- a/homeassistant/components/moon/const.py +++ b/homeassistant/components/moon/const.py @@ -1,4 +1,5 @@ """Constants for the Moon integration.""" + from typing import Final from homeassistant.const import Platform diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py index b7c5b8d7726..cef1dbffbb3 100644 --- a/homeassistant/components/moon/sensor.py +++ b/homeassistant/components/moon/sensor.py @@ -1,4 +1,5 @@ """Support for tracking the moon phases.""" + from __future__ import annotations from astral import moon diff --git a/homeassistant/components/mopeka/__init__.py b/homeassistant/components/mopeka/__init__.py index a000ef0cc88..09dec3bf3d2 100644 --- a/homeassistant/components/mopeka/__init__.py +++ b/homeassistant/components/mopeka/__init__.py @@ -1,4 +1,5 @@ """The Mopeka integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mopeka/config_flow.py b/homeassistant/components/mopeka/config_flow.py index 1bd79c1a20d..1732157ce49 100644 --- a/homeassistant/components/mopeka/config_flow.py +++ b/homeassistant/components/mopeka/config_flow.py @@ -1,4 +1,5 @@ """Config flow for mopeka integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mopeka/device.py b/homeassistant/components/mopeka/device.py index 74bc389d3ae..b1b01c07957 100644 --- a/homeassistant/components/mopeka/device.py +++ b/homeassistant/components/mopeka/device.py @@ -1,4 +1,5 @@ """Support for Mopeka devices.""" + from __future__ import annotations from mopeka_iot_ble import DeviceKey diff --git a/homeassistant/components/mopeka/sensor.py b/homeassistant/components/mopeka/sensor.py index 1cddf42c730..b4b02bb083f 100644 --- a/homeassistant/components/mopeka/sensor.py +++ b/homeassistant/components/mopeka/sensor.py @@ -1,4 +1,5 @@ """Support for Mopeka sensors.""" + from __future__ import annotations from mopeka_iot_ble import SensorUpdate diff --git a/homeassistant/components/motion_blinds/config_flow.py b/homeassistant/components/motion_blinds/config_flow.py index 0d63a2e413d..3ba215a3f4c 100644 --- a/homeassistant/components/motion_blinds/config_flow.py +++ b/homeassistant/components/motion_blinds/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Motionblinds using their WLAN API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/motion_blinds/const.py b/homeassistant/components/motion_blinds/const.py index 4d9f8a7934d..e089fd17943 100644 --- a/homeassistant/components/motion_blinds/const.py +++ b/homeassistant/components/motion_blinds/const.py @@ -1,4 +1,5 @@ """Constants for the Motionblinds component.""" + from homeassistant.const import Platform DOMAIN = "motion_blinds" diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index 60d8aae2ff8..eb40a1d66ca 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -1,4 +1,5 @@ """Support for Motionblinds using their WLAN API.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/motion_blinds/entity.py b/homeassistant/components/motion_blinds/entity.py index 36c45c3afc2..b1495dd8ecf 100644 --- a/homeassistant/components/motion_blinds/entity.py +++ b/homeassistant/components/motion_blinds/entity.py @@ -1,4 +1,5 @@ """Support for Motionblinds using their WLAN API.""" + from __future__ import annotations from motionblinds import DEVICE_TYPES_GATEWAY, DEVICE_TYPES_WIFI, MotionGateway diff --git a/homeassistant/components/motion_blinds/sensor.py b/homeassistant/components/motion_blinds/sensor.py index b746b39bdf0..6418cebda0c 100644 --- a/homeassistant/components/motion_blinds/sensor.py +++ b/homeassistant/components/motion_blinds/sensor.py @@ -1,4 +1,5 @@ """Support for Motionblinds sensors.""" + from motionblinds import DEVICE_TYPES_WIFI from motionblinds.motion_blinds import DEVICE_TYPE_TDBU diff --git a/homeassistant/components/motioneye/__init__.py b/homeassistant/components/motioneye/__init__.py index 37519a236ab..43869ef51de 100644 --- a/homeassistant/components/motioneye/__init__.py +++ b/homeassistant/components/motioneye/__init__.py @@ -1,4 +1,5 @@ """The motionEye integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/motioneye/camera.py b/homeassistant/components/motioneye/camera.py index fd3f0ec86c0..da5eb36d494 100644 --- a/homeassistant/components/motioneye/camera.py +++ b/homeassistant/components/motioneye/camera.py @@ -1,4 +1,5 @@ """The motionEye integration.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/motioneye/config_flow.py b/homeassistant/components/motioneye/config_flow.py index fba66c45b8b..bbbd2bc7fba 100644 --- a/homeassistant/components/motioneye/config_flow.py +++ b/homeassistant/components/motioneye/config_flow.py @@ -1,4 +1,5 @@ """Config flow for motionEye integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/motioneye/const.py b/homeassistant/components/motioneye/const.py index ebe4e24d6cf..15a856035e1 100644 --- a/homeassistant/components/motioneye/const.py +++ b/homeassistant/components/motioneye/const.py @@ -1,4 +1,5 @@ """Constants for the motionEye integration.""" + from datetime import timedelta from typing import Final diff --git a/homeassistant/components/motioneye/media_source.py b/homeassistant/components/motioneye/media_source.py index 46300e3d3db..7c12b84f255 100644 --- a/homeassistant/components/motioneye/media_source.py +++ b/homeassistant/components/motioneye/media_source.py @@ -1,4 +1,5 @@ """motionEye Media Source Implementation.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/motioneye/sensor.py b/homeassistant/components/motioneye/sensor.py index 4d0abb84d46..dac4d77cdb4 100644 --- a/homeassistant/components/motioneye/sensor.py +++ b/homeassistant/components/motioneye/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for motionEye.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/motioneye/switch.py b/homeassistant/components/motioneye/switch.py index 069c5edaad7..81a01587aa0 100644 --- a/homeassistant/components/motioneye/switch.py +++ b/homeassistant/components/motioneye/switch.py @@ -1,4 +1,5 @@ """Switch platform for motionEye.""" + from __future__ import annotations from types import MappingProxyType diff --git a/homeassistant/components/motionmount/__init__.py b/homeassistant/components/motionmount/__init__.py index 6f62a0731b6..28963d83d89 100644 --- a/homeassistant/components/motionmount/__init__.py +++ b/homeassistant/components/motionmount/__init__.py @@ -1,4 +1,5 @@ """The Vogel's MotionMount integration.""" + from __future__ import annotations import socket diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 0721afa9d3a..7f69b7bf914 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -1,4 +1,5 @@ """Support to interact with a Music Player Daemon.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 279dbc57c84..14f92e29cdf 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -1,4 +1,5 @@ """Support for MQTT message handling.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index 68aca18f249..e4614817790 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -1,4 +1,5 @@ """Control a MQTT alarm.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 7ab2e9ebf90..80ab11925d4 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -1,4 +1,5 @@ """Support for MQTT binary sensors.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index f0d8037b60d..f6374aaa3cd 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -1,4 +1,5 @@ """Support for MQTT buttons.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 954cddd20f7..605d37834ec 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -1,4 +1,5 @@ """Camera that loads a picture from an MQTT topic.""" + from __future__ import annotations from base64 import b64decode diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index ace3cf9fd64..1cded579c53 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -1,4 +1,5 @@ """Support for MQTT message handling.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 4e85163767c..2deffaea042 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -1,4 +1,5 @@ """Support for MQTT climate devices.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/mqtt/config.py b/homeassistant/components/mqtt/config.py index 895a8e3b581..ed8f58218c6 100644 --- a/homeassistant/components/mqtt/config.py +++ b/homeassistant/components/mqtt/config.py @@ -1,4 +1,5 @@ """Support for MQTT message handling.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 96ee6596f5a..3889af78591 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -1,4 +1,5 @@ """Config flow for MQTT.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/components/mqtt/config_integration.py b/homeassistant/components/mqtt/config_integration.py index 0f2d617930d..2500923ca9b 100644 --- a/homeassistant/components/mqtt/config_integration.py +++ b/homeassistant/components/mqtt/config_integration.py @@ -1,4 +1,5 @@ """Support for MQTT platform config setup.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index dce82774205..a659b1bb0c1 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -1,4 +1,5 @@ """Support for MQTT cover devices.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index 41614a62f30..a3841c27fa3 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -1,4 +1,5 @@ """Helper to handle a set of topics to subscribe to.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/mqtt/device_automation.py b/homeassistant/components/mqtt/device_automation.py index c0e6f5750fb..25fb510a07e 100644 --- a/homeassistant/components/mqtt/device_automation.py +++ b/homeassistant/components/mqtt/device_automation.py @@ -1,4 +1,5 @@ """Provides device automations for MQTT.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/mqtt/device_tracker.py b/homeassistant/components/mqtt/device_tracker.py index 6e5aeb8f228..417a636434f 100644 --- a/homeassistant/components/mqtt/device_tracker.py +++ b/homeassistant/components/mqtt/device_tracker.py @@ -1,4 +1,5 @@ """Support for tracking MQTT enabled devices identified.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index b6d505d7c98..8ca5e460419 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for MQTT.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/diagnostics.py b/homeassistant/components/mqtt/diagnostics.py index 82bae04d2c9..8b278c1e8d6 100644 --- a/homeassistant/components/mqtt/diagnostics.py +++ b/homeassistant/components/mqtt/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for MQTT.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 84163e217df..5fa1b6297d7 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -1,4 +1,5 @@ """Support for MQTT discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mqtt/event.py b/homeassistant/components/mqtt/event.py index 165d273f46e..2a639e55352 100644 --- a/homeassistant/components/mqtt/event.py +++ b/homeassistant/components/mqtt/event.py @@ -1,4 +1,5 @@ """Support for MQTT events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 24783e171c8..848e50e3e2e 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -1,4 +1,5 @@ """Support for MQTT fans.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 75a74a0dcaa..400b8154c8d 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -1,4 +1,5 @@ """Support for MQTT humidifiers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/image.py b/homeassistant/components/mqtt/image.py index 91e11d06371..be3956cc972 100644 --- a/homeassistant/components/mqtt/image.py +++ b/homeassistant/components/mqtt/image.py @@ -1,4 +1,5 @@ """Support for MQTT images.""" + from __future__ import annotations from base64 import b64decode diff --git a/homeassistant/components/mqtt/lawn_mower.py b/homeassistant/components/mqtt/lawn_mower.py index 924d34bf5c7..541ab9e4aa4 100644 --- a/homeassistant/components/mqtt/lawn_mower.py +++ b/homeassistant/components/mqtt/lawn_mower.py @@ -1,4 +1,5 @@ """Support for MQTT lawn mowers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index a5f3f0aca84..29c5cc20d91 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -1,4 +1,5 @@ """Support for MQTT lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 2ca0a7e7e47..052fa394248 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -1,4 +1,5 @@ """Support for MQTT lights.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 8f54edc15f4..6d3cd6328b8 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -1,4 +1,5 @@ """Support for MQTT JSON lights.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index e4900053fb3..95f97f0a736 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -1,4 +1,5 @@ """Support for MQTT Template lights.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 26b6009426c..79e02be9d4f 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -1,4 +1,5 @@ """Support for MQTT locks.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 554e83204dd..a508b05efb4 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1,4 +1,5 @@ """MQTT component mixins and helpers.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 9fcea353299..bfbf9e011eb 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -1,4 +1,5 @@ """Models used by multiple MQTT modules.""" + from __future__ import annotations from ast import literal_eval diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 83eb047519f..88730d6e7a2 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -1,4 +1,5 @@ """Configure number in a device through MQTT topic.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index de75f470228..a5ba2700e80 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -1,4 +1,5 @@ """Support for MQTT scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 5d9bc989c25..af09f5c0202 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -1,4 +1,5 @@ """Configure select in a device through MQTT topic.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 9d1ed964be3..bb63ae5fef0 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -1,4 +1,5 @@ """Support for MQTT sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index cb2ecbafa55..2725743735d 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -1,4 +1,5 @@ """Support for MQTT sirens.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/subscription.py b/homeassistant/components/mqtt/subscription.py index 3f8f0f4ee3e..14f2999fa9c 100644 --- a/homeassistant/components/mqtt/subscription.py +++ b/homeassistant/components/mqtt/subscription.py @@ -1,4 +1,5 @@ """Helper to handle a set of topics to subscribe to.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index c45e6dd77ab..8be42a9ed19 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -1,4 +1,5 @@ """Support for MQTT switches.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py index 42c8760c302..42f6915fc91 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -1,4 +1,5 @@ """Provides tag scanning for MQTT.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/text.py b/homeassistant/components/mqtt/text.py index fb121c25a9c..e5786dbe94d 100644 --- a/homeassistant/components/mqtt/text.py +++ b/homeassistant/components/mqtt/text.py @@ -1,4 +1,5 @@ """Support for MQTT text platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/trigger.py b/homeassistant/components/mqtt/trigger.py index 623c6c53373..d7086885b24 100644 --- a/homeassistant/components/mqtt/trigger.py +++ b/homeassistant/components/mqtt/trigger.py @@ -1,4 +1,5 @@ """Offer MQTT listening automation rules.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mqtt/update.py b/homeassistant/components/mqtt/update.py index 45424995224..0171e8eee2d 100644 --- a/homeassistant/components/mqtt/update.py +++ b/homeassistant/components/mqtt/update.py @@ -1,4 +1,5 @@ """Configure update platform in a device through MQTT topic.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mqtt/valve.py b/homeassistant/components/mqtt/valve.py index 9d167f42d12..241d6748280 100644 --- a/homeassistant/components/mqtt/valve.py +++ b/homeassistant/components/mqtt/valve.py @@ -1,4 +1,5 @@ """Support for MQTT valve devices.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/mqtt/water_heater.py b/homeassistant/components/mqtt/water_heater.py index a2cf2e511a0..f8061873c69 100644 --- a/homeassistant/components/mqtt/water_heater.py +++ b/homeassistant/components/mqtt/water_heater.py @@ -1,4 +1,5 @@ """Support for MQTT water heater devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mqtt_json/device_tracker.py b/homeassistant/components/mqtt_json/device_tracker.py index 2b355eb68e6..68f42479930 100644 --- a/homeassistant/components/mqtt_json/device_tracker.py +++ b/homeassistant/components/mqtt_json/device_tracker.py @@ -1,4 +1,5 @@ """Support for GPS tracking MQTT enabled devices.""" + from __future__ import annotations import json diff --git a/homeassistant/components/mqtt_room/sensor.py b/homeassistant/components/mqtt_room/sensor.py index cb0e840604e..df0be7b4968 100644 --- a/homeassistant/components/mqtt_room/sensor.py +++ b/homeassistant/components/mqtt_room/sensor.py @@ -1,4 +1,5 @@ """Support for MQTT room presence detection.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/msteams/notify.py b/homeassistant/components/msteams/notify.py index 7a729897e76..d6ebdf3e711 100644 --- a/homeassistant/components/msteams/notify.py +++ b/homeassistant/components/msteams/notify.py @@ -1,4 +1,5 @@ """Microsoft Teams platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mullvad/binary_sensor.py b/homeassistant/components/mullvad/binary_sensor.py index 264bbe15520..2e649d9a586 100644 --- a/homeassistant/components/mullvad/binary_sensor.py +++ b/homeassistant/components/mullvad/binary_sensor.py @@ -1,4 +1,5 @@ """Setup Mullvad VPN Binary Sensors.""" + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, diff --git a/homeassistant/components/mullvad/config_flow.py b/homeassistant/components/mullvad/config_flow.py index f576dbc08af..55957f160a3 100644 --- a/homeassistant/components/mullvad/config_flow.py +++ b/homeassistant/components/mullvad/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Mullvad VPN integration.""" + from mullvad_api import MullvadAPI, MullvadAPIError from homeassistant.config_entries import ConfigFlow, ConfigFlowResult diff --git a/homeassistant/components/mutesync/__init__.py b/homeassistant/components/mutesync/__init__.py index cbbbbaa6a11..40aba72b0f6 100644 --- a/homeassistant/components/mutesync/__init__.py +++ b/homeassistant/components/mutesync/__init__.py @@ -1,4 +1,5 @@ """The mütesync integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mutesync/binary_sensor.py b/homeassistant/components/mutesync/binary_sensor.py index 910f91fc4c6..87bf246f4e0 100644 --- a/homeassistant/components/mutesync/binary_sensor.py +++ b/homeassistant/components/mutesync/binary_sensor.py @@ -1,4 +1,5 @@ """mütesync binary sensor entities.""" + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/mutesync/config_flow.py b/homeassistant/components/mutesync/config_flow.py index 75c15ff2f50..2399cdc063e 100644 --- a/homeassistant/components/mutesync/config_flow.py +++ b/homeassistant/components/mutesync/config_flow.py @@ -1,4 +1,5 @@ """Config flow for mütesync integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mutesync/const.py b/homeassistant/components/mutesync/const.py index 027a48f46ca..9fd04d1e0c5 100644 --- a/homeassistant/components/mutesync/const.py +++ b/homeassistant/components/mutesync/const.py @@ -1,4 +1,5 @@ """Constants for the mütesync integration.""" + from datetime import timedelta from typing import Final diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py index 2f8467a0333..39d77edbfa7 100644 --- a/homeassistant/components/mvglive/sensor.py +++ b/homeassistant/components/mvglive/sensor.py @@ -1,4 +1,5 @@ """Support for departure information for public transport in Munich.""" + from __future__ import annotations from copy import deepcopy diff --git a/homeassistant/components/my/__init__.py b/homeassistant/components/my/__init__.py index d699e42e105..d18589f66ce 100644 --- a/homeassistant/components/my/__init__.py +++ b/homeassistant/components/my/__init__.py @@ -1,4 +1,5 @@ """Support for my.home-assistant.io redirect service.""" + from homeassistant.components import frontend from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/mycroft/notify.py b/homeassistant/components/mycroft/notify.py index a9dd82caef1..67203ae0564 100644 --- a/homeassistant/components/mycroft/notify.py +++ b/homeassistant/components/mycroft/notify.py @@ -1,4 +1,5 @@ """Mycroft AI notification platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/myq/__init__.py b/homeassistant/components/myq/__init__.py index 86a158c09fa..41b36a34c20 100644 --- a/homeassistant/components/myq/__init__.py +++ b/homeassistant/components/myq/__init__.py @@ -1,4 +1,5 @@ """The MyQ integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry, ConfigEntryState diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py index a3f52cd28ab..699190a087c 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -1,4 +1,5 @@ """Connect to a MySensors gateway via pymysensors API.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/mysensors/binary_sensor.py b/homeassistant/components/mysensors/binary_sensor.py index b70a7fc8d55..a0a1c92c682 100644 --- a/homeassistant/components/mysensors/binary_sensor.py +++ b/homeassistant/components/mysensors/binary_sensor.py @@ -1,4 +1,5 @@ """Support for MySensors binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index 0058fca021e..0008297f299 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -1,4 +1,5 @@ """MySensors platform that offers a Climate (MySensors-HVAC) component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index c4926696643..7920e8025cf 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -1,4 +1,5 @@ """Config flow for MySensors.""" + from __future__ import annotations import os diff --git a/homeassistant/components/mysensors/const.py b/homeassistant/components/mysensors/const.py index 0a4b4c090ef..3885a2d7a0e 100644 --- a/homeassistant/components/mysensors/const.py +++ b/homeassistant/components/mysensors/const.py @@ -1,4 +1,5 @@ """MySensors constants.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index 8be5f1f8620..acd5643965f 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -1,4 +1,5 @@ """Support for MySensors covers.""" + from __future__ import annotations from enum import Enum, unique diff --git a/homeassistant/components/mysensors/device.py b/homeassistant/components/mysensors/device.py index c70ef1f89ed..5caa42c282c 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -1,4 +1,5 @@ """Handle MySensors devices.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/mysensors/device_tracker.py b/homeassistant/components/mysensors/device_tracker.py index d56e9874560..968ee94b60e 100644 --- a/homeassistant/components/mysensors/device_tracker.py +++ b/homeassistant/components/mysensors/device_tracker.py @@ -1,4 +1,5 @@ """Support for tracking MySensors devices.""" + from __future__ import annotations from homeassistant.components.device_tracker import SourceType, TrackerEntity diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 28cacbe7762..3bdd884079e 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -1,4 +1,5 @@ """Handle MySensors gateways.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/mysensors/handler.py b/homeassistant/components/mysensors/handler.py index aa8a235c7cb..20e0ddd0e5a 100644 --- a/homeassistant/components/mysensors/handler.py +++ b/homeassistant/components/mysensors/handler.py @@ -1,4 +1,5 @@ """Handle MySensors messages.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mysensors/helpers.py b/homeassistant/components/mysensors/helpers.py index 9985929eecd..cb075b8f485 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -1,4 +1,5 @@ """Helper functions for mysensors package.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index 7aea1e906a6..c3691a40140 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -1,4 +1,5 @@ """Support for MySensors lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/mysensors/remote.py b/homeassistant/components/mysensors/remote.py index 8521e407ae1..e9404bb3197 100644 --- a/homeassistant/components/mysensors/remote.py +++ b/homeassistant/components/mysensors/remote.py @@ -1,4 +1,5 @@ """Support MySensors IR transceivers.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index 84ae1ed031f..537bf575af0 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -1,4 +1,5 @@ """Support for MySensors sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index b1ec1a420d2..400ef2c5896 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -1,4 +1,5 @@ """Support for MySensors switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/mysensors/text.py b/homeassistant/components/mysensors/text.py index 68fa2a434d5..021324d7a67 100644 --- a/homeassistant/components/mysensors/text.py +++ b/homeassistant/components/mysensors/text.py @@ -1,4 +1,5 @@ """Provide a text platform for MySensors.""" + from __future__ import annotations from homeassistant.components.text import TextEntity diff --git a/homeassistant/components/mystrom/__init__.py b/homeassistant/components/mystrom/__init__.py index 3b033e3338c..3c6a5805ddb 100644 --- a/homeassistant/components/mystrom/__init__.py +++ b/homeassistant/components/mystrom/__init__.py @@ -1,4 +1,5 @@ """The myStrom integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mystrom/binary_sensor.py b/homeassistant/components/mystrom/binary_sensor.py index 33e78ca3afe..2201eb778d6 100644 --- a/homeassistant/components/mystrom/binary_sensor.py +++ b/homeassistant/components/mystrom/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the myStrom buttons.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/mystrom/config_flow.py b/homeassistant/components/mystrom/config_flow.py index dc603a16c34..38b292e9f97 100644 --- a/homeassistant/components/mystrom/config_flow.py +++ b/homeassistant/components/mystrom/config_flow.py @@ -1,4 +1,5 @@ """Config flow for myStrom integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py index ce9357d23f7..5dabb609437 100644 --- a/homeassistant/components/mystrom/light.py +++ b/homeassistant/components/mystrom/light.py @@ -1,4 +1,5 @@ """Support for myStrom Wifi bulbs.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mystrom/models.py b/homeassistant/components/mystrom/models.py index 96cc40996ef..694a2f43df6 100644 --- a/homeassistant/components/mystrom/models.py +++ b/homeassistant/components/mystrom/models.py @@ -1,4 +1,5 @@ """Models for the mystrom integration.""" + from dataclasses import dataclass from typing import Any diff --git a/homeassistant/components/mystrom/sensor.py b/homeassistant/components/mystrom/sensor.py index 4551c9ebbec..f7805cd9012 100644 --- a/homeassistant/components/mystrom/sensor.py +++ b/homeassistant/components/mystrom/sensor.py @@ -1,4 +1,5 @@ """Support for myStrom sensors of switches/plugs.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/mystrom/switch.py b/homeassistant/components/mystrom/switch.py index 8f459e6801e..9958fcf7f01 100644 --- a/homeassistant/components/mystrom/switch.py +++ b/homeassistant/components/mystrom/switch.py @@ -1,4 +1,5 @@ """Support for myStrom switches/plugs.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/mythicbeastsdns/__init__.py b/homeassistant/components/mythicbeastsdns/__init__.py index 2319bc8f812..f4de18aa0ef 100644 --- a/homeassistant/components/mythicbeastsdns/__init__.py +++ b/homeassistant/components/mythicbeastsdns/__init__.py @@ -1,4 +1,5 @@ """Support for Mythic Beasts Dynamic DNS service.""" + from datetime import timedelta import mbddns diff --git a/homeassistant/components/myuplink/__init__.py b/homeassistant/components/myuplink/__init__.py index fcfffc54b31..4e3305cf713 100644 --- a/homeassistant/components/myuplink/__init__.py +++ b/homeassistant/components/myuplink/__init__.py @@ -1,4 +1,5 @@ """The myUplink integration.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/myuplink/api.py b/homeassistant/components/myuplink/api.py index 1b74d41bc97..89a5d0c19b0 100644 --- a/homeassistant/components/myuplink/api.py +++ b/homeassistant/components/myuplink/api.py @@ -1,4 +1,5 @@ """API for myUplink bound to Home Assistant OAuth.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/myuplink/config_flow.py b/homeassistant/components/myuplink/config_flow.py index fee571effa6..fe31dcc6183 100644 --- a/homeassistant/components/myuplink/config_flow.py +++ b/homeassistant/components/myuplink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for myUplink.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/myuplink/diagnostics.py b/homeassistant/components/myuplink/diagnostics.py index 55cbb07c0d0..d108db595a1 100644 --- a/homeassistant/components/myuplink/diagnostics.py +++ b/homeassistant/components/myuplink/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for myUplink.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/myuplink/entity.py b/homeassistant/components/myuplink/entity.py index e3d6184c368..351ba6bfc92 100644 --- a/homeassistant/components/myuplink/entity.py +++ b/homeassistant/components/myuplink/entity.py @@ -1,4 +1,5 @@ """Provide a common entity class for myUplink entities.""" + from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity From 988c71ecc533238a1b4e82c2d9f8ec95dfd76fe0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:04:07 +0100 Subject: [PATCH 0551/1691] Add empty line after module docstring [n-q] (#112702) --- homeassistant/components/nad/media_player.py | 1 + homeassistant/components/nam/__init__.py | 1 + homeassistant/components/nam/button.py | 1 + homeassistant/components/nam/config_flow.py | 1 + homeassistant/components/nam/const.py | 1 + homeassistant/components/nam/diagnostics.py | 1 + homeassistant/components/nam/sensor.py | 1 + homeassistant/components/namecheapdns/__init__.py | 1 + homeassistant/components/nanoleaf/__init__.py | 1 + homeassistant/components/nanoleaf/config_flow.py | 1 + homeassistant/components/nanoleaf/device_trigger.py | 1 + homeassistant/components/nanoleaf/diagnostics.py | 1 + homeassistant/components/nanoleaf/light.py | 1 + homeassistant/components/neato/api.py | 1 + homeassistant/components/neato/button.py | 1 + homeassistant/components/neato/camera.py | 1 + homeassistant/components/neato/config_flow.py | 1 + homeassistant/components/neato/entity.py | 1 + homeassistant/components/neato/hub.py | 1 + homeassistant/components/neato/sensor.py | 1 + homeassistant/components/neato/switch.py | 1 + homeassistant/components/neato/vacuum.py | 1 + homeassistant/components/nederlandse_spoorwegen/sensor.py | 1 + homeassistant/components/ness_alarm/__init__.py | 1 + homeassistant/components/ness_alarm/alarm_control_panel.py | 1 + homeassistant/components/ness_alarm/binary_sensor.py | 1 + homeassistant/components/nest/__init__.py | 1 + homeassistant/components/nest/camera.py | 1 + homeassistant/components/nest/climate.py | 1 + homeassistant/components/nest/config_flow.py | 1 + homeassistant/components/nest/device_trigger.py | 1 + homeassistant/components/nest/sensor.py | 1 + homeassistant/components/netatmo/__init__.py | 1 + homeassistant/components/netatmo/api.py | 1 + homeassistant/components/netatmo/camera.py | 1 + homeassistant/components/netatmo/climate.py | 1 + homeassistant/components/netatmo/config_flow.py | 1 + homeassistant/components/netatmo/const.py | 1 + homeassistant/components/netatmo/cover.py | 1 + homeassistant/components/netatmo/data_handler.py | 1 + homeassistant/components/netatmo/device_trigger.py | 1 + homeassistant/components/netatmo/diagnostics.py | 1 + homeassistant/components/netatmo/entity.py | 1 + homeassistant/components/netatmo/fan.py | 1 + homeassistant/components/netatmo/helper.py | 1 + homeassistant/components/netatmo/light.py | 1 + homeassistant/components/netatmo/media_source.py | 1 + homeassistant/components/netatmo/select.py | 1 + homeassistant/components/netatmo/sensor.py | 1 + homeassistant/components/netatmo/switch.py | 1 + homeassistant/components/netdata/sensor.py | 1 + homeassistant/components/netgear/__init__.py | 1 + homeassistant/components/netgear/button.py | 1 + homeassistant/components/netgear/config_flow.py | 1 + homeassistant/components/netgear/const.py | 1 + homeassistant/components/netgear/device_tracker.py | 1 + homeassistant/components/netgear/entity.py | 1 + homeassistant/components/netgear/errors.py | 1 + homeassistant/components/netgear/router.py | 1 + homeassistant/components/netgear/sensor.py | 1 + homeassistant/components/netgear/switch.py | 1 + homeassistant/components/netgear/update.py | 1 + homeassistant/components/netgear_lte/__init__.py | 1 + homeassistant/components/netgear_lte/binary_sensor.py | 1 + homeassistant/components/netgear_lte/config_flow.py | 1 + homeassistant/components/netgear_lte/notify.py | 1 + homeassistant/components/netgear_lte/sensor.py | 1 + homeassistant/components/netgear_lte/services.py | 1 + homeassistant/components/netio/switch.py | 1 + homeassistant/components/network/__init__.py | 1 + homeassistant/components/network/const.py | 1 + homeassistant/components/network/models.py | 1 + homeassistant/components/network/network.py | 1 + homeassistant/components/network/util.py | 1 + homeassistant/components/network/websocket.py | 1 + homeassistant/components/neurio_energy/sensor.py | 1 + homeassistant/components/nexia/binary_sensor.py | 1 + homeassistant/components/nexia/climate.py | 1 + homeassistant/components/nexia/const.py | 1 + homeassistant/components/nexia/coordinator.py | 1 + homeassistant/components/nexia/diagnostics.py | 1 + homeassistant/components/nexia/number.py | 1 + homeassistant/components/nexia/scene.py | 1 + homeassistant/components/nexia/sensor.py | 1 + homeassistant/components/nexia/switch.py | 1 + homeassistant/components/nextbus/config_flow.py | 1 + homeassistant/components/nextbus/coordinator.py | 1 + homeassistant/components/nextbus/sensor.py | 1 + homeassistant/components/nextbus/util.py | 1 + homeassistant/components/nextcloud/binary_sensor.py | 1 + homeassistant/components/nextcloud/config_flow.py | 1 + homeassistant/components/nextcloud/const.py | 1 + homeassistant/components/nextcloud/entity.py | 1 + homeassistant/components/nextcloud/sensor.py | 1 + homeassistant/components/nextcloud/update.py | 1 + homeassistant/components/nextdns/__init__.py | 1 + homeassistant/components/nextdns/binary_sensor.py | 1 + homeassistant/components/nextdns/button.py | 1 + homeassistant/components/nextdns/config_flow.py | 1 + homeassistant/components/nextdns/const.py | 1 + homeassistant/components/nextdns/diagnostics.py | 1 + homeassistant/components/nextdns/sensor.py | 1 + homeassistant/components/nextdns/switch.py | 1 + homeassistant/components/nextdns/system_health.py | 1 + homeassistant/components/nfandroidtv/__init__.py | 1 + homeassistant/components/nfandroidtv/config_flow.py | 1 + homeassistant/components/nfandroidtv/notify.py | 1 + homeassistant/components/nibe_heatpump/__init__.py | 1 + homeassistant/components/nibe_heatpump/binary_sensor.py | 1 + homeassistant/components/nibe_heatpump/button.py | 1 + homeassistant/components/nibe_heatpump/climate.py | 1 + homeassistant/components/nibe_heatpump/config_flow.py | 1 + homeassistant/components/nibe_heatpump/coordinator.py | 1 + homeassistant/components/nibe_heatpump/number.py | 1 + homeassistant/components/nibe_heatpump/select.py | 1 + homeassistant/components/nibe_heatpump/sensor.py | 1 + homeassistant/components/nibe_heatpump/switch.py | 1 + homeassistant/components/nibe_heatpump/water_heater.py | 1 + homeassistant/components/nightscout/sensor.py | 1 + homeassistant/components/nightscout/utils.py | 1 + homeassistant/components/niko_home_control/light.py | 1 + homeassistant/components/nilu/air_quality.py | 1 + homeassistant/components/nina/__init__.py | 1 + homeassistant/components/nina/binary_sensor.py | 1 + homeassistant/components/nina/config_flow.py | 1 + homeassistant/components/nina/const.py | 1 + homeassistant/components/nina/coordinator.py | 1 + homeassistant/components/nissan_leaf/__init__.py | 1 + homeassistant/components/nissan_leaf/binary_sensor.py | 1 + homeassistant/components/nissan_leaf/button.py | 1 + homeassistant/components/nissan_leaf/const.py | 1 + homeassistant/components/nissan_leaf/sensor.py | 1 + homeassistant/components/nissan_leaf/switch.py | 1 + homeassistant/components/nmap_tracker/__init__.py | 1 + homeassistant/components/nmap_tracker/config_flow.py | 1 + homeassistant/components/nmap_tracker/const.py | 1 + homeassistant/components/nmap_tracker/device_tracker.py | 1 + homeassistant/components/nmbs/sensor.py | 1 + homeassistant/components/noaa_tides/sensor.py | 1 + homeassistant/components/nobo_hub/__init__.py | 1 + homeassistant/components/nobo_hub/climate.py | 1 + homeassistant/components/nobo_hub/config_flow.py | 1 + homeassistant/components/nobo_hub/select.py | 1 + homeassistant/components/nobo_hub/sensor.py | 1 + homeassistant/components/norway_air/air_quality.py | 1 + homeassistant/components/notify/__init__.py | 1 + homeassistant/components/notify/legacy.py | 1 + homeassistant/components/notify_events/notify.py | 1 + homeassistant/components/notion/__init__.py | 1 + homeassistant/components/notion/binary_sensor.py | 1 + homeassistant/components/notion/config_flow.py | 1 + homeassistant/components/notion/diagnostics.py | 1 + homeassistant/components/notion/model.py | 1 + homeassistant/components/notion/sensor.py | 1 + homeassistant/components/notion/util.py | 1 + homeassistant/components/nsw_fuel_station/__init__.py | 1 + homeassistant/components/nsw_fuel_station/sensor.py | 1 + .../components/nsw_rural_fire_service_feed/geo_location.py | 1 + homeassistant/components/nuheat/__init__.py | 1 + homeassistant/components/nuheat/config_flow.py | 1 + homeassistant/components/nuheat/const.py | 1 + homeassistant/components/nuki/__init__.py | 1 + homeassistant/components/nuki/binary_sensor.py | 1 + homeassistant/components/nuki/config_flow.py | 1 + homeassistant/components/nuki/helpers.py | 1 + homeassistant/components/nuki/lock.py | 1 + homeassistant/components/nuki/sensor.py | 1 + homeassistant/components/numato/binary_sensor.py | 1 + homeassistant/components/numato/sensor.py | 1 + homeassistant/components/numato/switch.py | 1 + homeassistant/components/number/__init__.py | 1 + homeassistant/components/number/const.py | 1 + homeassistant/components/number/device_action.py | 1 + homeassistant/components/number/reproduce_state.py | 1 + homeassistant/components/number/significant_change.py | 1 + homeassistant/components/number/websocket_api.py | 1 + homeassistant/components/nut/__init__.py | 1 + homeassistant/components/nut/config_flow.py | 1 + homeassistant/components/nut/const.py | 1 + homeassistant/components/nut/device_action.py | 1 + homeassistant/components/nut/diagnostics.py | 1 + homeassistant/components/nut/sensor.py | 1 + homeassistant/components/nws/__init__.py | 1 + homeassistant/components/nws/config_flow.py | 1 + homeassistant/components/nws/const.py | 1 + homeassistant/components/nws/sensor.py | 1 + homeassistant/components/nws/weather.py | 1 + homeassistant/components/nx584/alarm_control_panel.py | 1 + homeassistant/components/nx584/binary_sensor.py | 1 + homeassistant/components/nzbget/config_flow.py | 1 + homeassistant/components/nzbget/sensor.py | 1 + homeassistant/components/nzbget/switch.py | 1 + homeassistant/components/oasa_telematics/sensor.py | 1 + homeassistant/components/obihai/sensor.py | 1 + homeassistant/components/octoprint/__init__.py | 1 + homeassistant/components/octoprint/binary_sensor.py | 1 + homeassistant/components/octoprint/button.py | 1 + homeassistant/components/octoprint/camera.py | 1 + homeassistant/components/octoprint/config_flow.py | 1 + homeassistant/components/octoprint/coordinator.py | 1 + homeassistant/components/octoprint/sensor.py | 1 + homeassistant/components/oem/climate.py | 1 + homeassistant/components/ohmconnect/sensor.py | 1 + homeassistant/components/ombi/const.py | 1 + homeassistant/components/ombi/sensor.py | 1 + homeassistant/components/omnilogic/config_flow.py | 1 + homeassistant/components/omnilogic/sensor.py | 1 + homeassistant/components/onboarding/__init__.py | 1 + homeassistant/components/onboarding/views.py | 1 + homeassistant/components/oncue/__init__.py | 1 + homeassistant/components/oncue/binary_sensor.py | 1 + homeassistant/components/oncue/config_flow.py | 1 + homeassistant/components/oncue/entity.py | 1 + homeassistant/components/oncue/sensor.py | 1 + homeassistant/components/ondilo_ico/api.py | 1 + homeassistant/components/ondilo_ico/sensor.py | 1 + homeassistant/components/onewire/binary_sensor.py | 1 + homeassistant/components/onewire/config_flow.py | 1 + homeassistant/components/onewire/const.py | 1 + homeassistant/components/onewire/diagnostics.py | 1 + homeassistant/components/onewire/model.py | 1 + homeassistant/components/onewire/onewire_entities.py | 1 + homeassistant/components/onewire/onewirehub.py | 1 + homeassistant/components/onewire/sensor.py | 1 + homeassistant/components/onewire/switch.py | 1 + homeassistant/components/onkyo/media_player.py | 1 + homeassistant/components/onvif/base.py | 1 + homeassistant/components/onvif/binary_sensor.py | 1 + homeassistant/components/onvif/button.py | 1 + homeassistant/components/onvif/camera.py | 1 + homeassistant/components/onvif/config_flow.py | 1 + homeassistant/components/onvif/device.py | 1 + homeassistant/components/onvif/diagnostics.py | 1 + homeassistant/components/onvif/event.py | 1 + homeassistant/components/onvif/models.py | 1 + homeassistant/components/onvif/parsers.py | 1 + homeassistant/components/onvif/sensor.py | 1 + homeassistant/components/onvif/switch.py | 1 + homeassistant/components/onvif/util.py | 1 + homeassistant/components/open_meteo/__init__.py | 1 + homeassistant/components/open_meteo/config_flow.py | 1 + homeassistant/components/open_meteo/const.py | 1 + homeassistant/components/open_meteo/diagnostics.py | 1 + homeassistant/components/open_meteo/weather.py | 1 + homeassistant/components/openai_conversation/__init__.py | 1 + homeassistant/components/openai_conversation/config_flow.py | 1 + homeassistant/components/openalpr_cloud/image_processing.py | 1 + homeassistant/components/opencv/image_processing.py | 1 + homeassistant/components/openerz/sensor.py | 1 + homeassistant/components/openevse/sensor.py | 1 + homeassistant/components/openexchangerates/__init__.py | 1 + homeassistant/components/openexchangerates/config_flow.py | 1 + homeassistant/components/openexchangerates/const.py | 1 + homeassistant/components/openexchangerates/coordinator.py | 1 + homeassistant/components/openexchangerates/sensor.py | 1 + homeassistant/components/opengarage/__init__.py | 1 + homeassistant/components/opengarage/binary_sensor.py | 1 + homeassistant/components/opengarage/button.py | 1 + homeassistant/components/opengarage/config_flow.py | 1 + homeassistant/components/opengarage/cover.py | 1 + homeassistant/components/opengarage/entity.py | 1 + homeassistant/components/opengarage/sensor.py | 1 + homeassistant/components/openhardwaremonitor/sensor.py | 1 + homeassistant/components/openhome/media_player.py | 1 + homeassistant/components/openhome/update.py | 1 + homeassistant/components/opensensemap/air_quality.py | 1 + homeassistant/components/opensky/__init__.py | 1 + homeassistant/components/opensky/config_flow.py | 1 + homeassistant/components/opensky/coordinator.py | 1 + homeassistant/components/opensky/sensor.py | 1 + homeassistant/components/opentherm_gw/climate.py | 1 + homeassistant/components/opentherm_gw/config_flow.py | 1 + homeassistant/components/opentherm_gw/const.py | 1 + homeassistant/components/openuv/__init__.py | 1 + homeassistant/components/openuv/binary_sensor.py | 1 + homeassistant/components/openuv/config_flow.py | 1 + homeassistant/components/openuv/coordinator.py | 1 + homeassistant/components/openuv/diagnostics.py | 1 + homeassistant/components/openuv/sensor.py | 1 + homeassistant/components/openweathermap/__init__.py | 1 + homeassistant/components/openweathermap/config_flow.py | 1 + homeassistant/components/openweathermap/const.py | 1 + homeassistant/components/openweathermap/sensor.py | 1 + homeassistant/components/openweathermap/weather.py | 1 + homeassistant/components/opnsense/device_tracker.py | 1 + homeassistant/components/opower/__init__.py | 1 + homeassistant/components/opower/config_flow.py | 1 + homeassistant/components/opower/coordinator.py | 1 + homeassistant/components/opower/sensor.py | 1 + homeassistant/components/opple/light.py | 1 + homeassistant/components/oralb/__init__.py | 1 + homeassistant/components/oralb/config_flow.py | 1 + homeassistant/components/oralb/device.py | 1 + homeassistant/components/oralb/sensor.py | 1 + homeassistant/components/oru/sensor.py | 1 + homeassistant/components/orvibo/switch.py | 1 + homeassistant/components/osoenergy/__init__.py | 1 + homeassistant/components/osoenergy/config_flow.py | 1 + homeassistant/components/osoenergy/water_heater.py | 1 + homeassistant/components/osramlightify/light.py | 1 + homeassistant/components/otbr/__init__.py | 1 + homeassistant/components/otbr/config_flow.py | 1 + homeassistant/components/otbr/util.py | 1 + homeassistant/components/otp/sensor.py | 1 + homeassistant/components/ourgroceries/__init__.py | 1 + homeassistant/components/ourgroceries/config_flow.py | 1 + homeassistant/components/ourgroceries/coordinator.py | 1 + homeassistant/components/overkiz/__init__.py | 1 + homeassistant/components/overkiz/alarm_control_panel.py | 1 + homeassistant/components/overkiz/binary_sensor.py | 1 + homeassistant/components/overkiz/button.py | 1 + homeassistant/components/overkiz/climate.py | 1 + homeassistant/components/overkiz/climate_entities/__init__.py | 1 + .../overkiz/climate_entities/atlantic_electrical_heater.py | 1 + ...tic_electrical_heater_with_adjustable_temperature_setpoint.py | 1 + .../overkiz/climate_entities/atlantic_electrical_towel_dryer.py | 1 + .../climate_entities/atlantic_heat_recovery_ventilation.py | 1 + .../overkiz/climate_entities/atlantic_pass_apc_heating_zone.py | 1 + .../overkiz/climate_entities/atlantic_pass_apc_zone_control.py | 1 + .../climate_entities/atlantic_pass_apc_zone_control_zone.py | 1 + .../climate_entities/hitachi_air_to_air_heat_pump_hlrrwifi.py | 1 + .../overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py | 1 + .../climate_entities/somfy_heating_temperature_interface.py | 1 + .../components/overkiz/climate_entities/somfy_thermostat.py | 1 + .../climate_entities/valve_heating_temperature_interface.py | 1 + homeassistant/components/overkiz/config_flow.py | 1 + homeassistant/components/overkiz/const.py | 1 + homeassistant/components/overkiz/coordinator.py | 1 + homeassistant/components/overkiz/cover.py | 1 + homeassistant/components/overkiz/cover_entities/awning.py | 1 + homeassistant/components/overkiz/cover_entities/generic_cover.py | 1 + .../components/overkiz/cover_entities/vertical_cover.py | 1 + homeassistant/components/overkiz/diagnostics.py | 1 + homeassistant/components/overkiz/entity.py | 1 + homeassistant/components/overkiz/executor.py | 1 + homeassistant/components/overkiz/light.py | 1 + homeassistant/components/overkiz/lock.py | 1 + homeassistant/components/overkiz/number.py | 1 + homeassistant/components/overkiz/scene.py | 1 + homeassistant/components/overkiz/select.py | 1 + homeassistant/components/overkiz/sensor.py | 1 + homeassistant/components/overkiz/siren.py | 1 + homeassistant/components/overkiz/switch.py | 1 + homeassistant/components/overkiz/water_heater.py | 1 + .../components/overkiz/water_heater_entities/__init__.py | 1 + .../water_heater_entities/domestic_hot_water_production.py | 1 + .../components/overkiz/water_heater_entities/hitachi_dhw.py | 1 + homeassistant/components/ovo_energy/__init__.py | 1 + homeassistant/components/ovo_energy/config_flow.py | 1 + homeassistant/components/ovo_energy/sensor.py | 1 + homeassistant/components/owntracks/__init__.py | 1 + homeassistant/components/owntracks/device_tracker.py | 1 + homeassistant/components/p1_monitor/__init__.py | 1 + homeassistant/components/p1_monitor/config_flow.py | 1 + homeassistant/components/p1_monitor/const.py | 1 + homeassistant/components/p1_monitor/diagnostics.py | 1 + homeassistant/components/p1_monitor/sensor.py | 1 + homeassistant/components/panasonic_bluray/media_player.py | 1 + homeassistant/components/panasonic_viera/__init__.py | 1 + homeassistant/components/panasonic_viera/config_flow.py | 1 + homeassistant/components/panasonic_viera/media_player.py | 1 + homeassistant/components/panasonic_viera/remote.py | 1 + homeassistant/components/pandora/media_player.py | 1 + homeassistant/components/panel_custom/__init__.py | 1 + homeassistant/components/peco/__init__.py | 1 + homeassistant/components/peco/binary_sensor.py | 1 + homeassistant/components/peco/config_flow.py | 1 + homeassistant/components/peco/sensor.py | 1 + homeassistant/components/pegel_online/__init__.py | 1 + homeassistant/components/pegel_online/config_flow.py | 1 + homeassistant/components/pegel_online/const.py | 1 + homeassistant/components/pegel_online/entity.py | 1 + homeassistant/components/pegel_online/sensor.py | 1 + homeassistant/components/pencom/switch.py | 1 + homeassistant/components/permobil/__init__.py | 1 + homeassistant/components/permobil/binary_sensor.py | 1 + homeassistant/components/permobil/config_flow.py | 1 + homeassistant/components/permobil/sensor.py | 1 + homeassistant/components/persistent_notification/__init__.py | 1 + homeassistant/components/persistent_notification/trigger.py | 1 + homeassistant/components/person/__init__.py | 1 + homeassistant/components/person/significant_change.py | 1 + homeassistant/components/philips_js/__init__.py | 1 + homeassistant/components/philips_js/binary_sensor.py | 1 + homeassistant/components/philips_js/config_flow.py | 1 + homeassistant/components/philips_js/device_trigger.py | 1 + homeassistant/components/philips_js/diagnostics.py | 1 + homeassistant/components/philips_js/entity.py | 1 + homeassistant/components/philips_js/light.py | 1 + homeassistant/components/philips_js/media_player.py | 1 + homeassistant/components/philips_js/remote.py | 1 + homeassistant/components/philips_js/switch.py | 1 + homeassistant/components/pi_hole/__init__.py | 1 + homeassistant/components/pi_hole/binary_sensor.py | 1 + homeassistant/components/pi_hole/config_flow.py | 1 + homeassistant/components/pi_hole/const.py | 1 + homeassistant/components/pi_hole/diagnostics.py | 1 + homeassistant/components/pi_hole/sensor.py | 1 + homeassistant/components/pi_hole/switch.py | 1 + homeassistant/components/pi_hole/update.py | 1 + homeassistant/components/picnic/config_flow.py | 1 + homeassistant/components/picnic/const.py | 1 + homeassistant/components/picnic/sensor.py | 1 + homeassistant/components/picnic/services.py | 1 + homeassistant/components/picnic/todo.py | 1 + homeassistant/components/pilight/__init__.py | 1 + homeassistant/components/pilight/binary_sensor.py | 1 + homeassistant/components/pilight/light.py | 1 + homeassistant/components/pilight/sensor.py | 1 + homeassistant/components/pilight/switch.py | 1 + homeassistant/components/ping/__init__.py | 1 + homeassistant/components/ping/binary_sensor.py | 1 + homeassistant/components/ping/config_flow.py | 1 + homeassistant/components/ping/coordinator.py | 1 + homeassistant/components/ping/device_tracker.py | 1 + homeassistant/components/ping/entity.py | 1 + homeassistant/components/ping/sensor.py | 1 + homeassistant/components/pioneer/media_player.py | 1 + homeassistant/components/pjlink/media_player.py | 1 + homeassistant/components/plaato/__init__.py | 1 + homeassistant/components/plaato/binary_sensor.py | 1 + homeassistant/components/plaato/config_flow.py | 1 + homeassistant/components/plaato/const.py | 1 + homeassistant/components/plaato/entity.py | 1 + homeassistant/components/plaato/sensor.py | 1 + homeassistant/components/plant/__init__.py | 1 + homeassistant/components/plant/const.py | 1 + homeassistant/components/plex/__init__.py | 1 + homeassistant/components/plex/button.py | 1 + homeassistant/components/plex/cast.py | 1 + homeassistant/components/plex/config_flow.py | 1 + homeassistant/components/plex/const.py | 1 + homeassistant/components/plex/errors.py | 1 + homeassistant/components/plex/helpers.py | 1 + homeassistant/components/plex/media_browser.py | 1 + homeassistant/components/plex/media_player.py | 1 + homeassistant/components/plex/media_search.py | 1 + homeassistant/components/plex/sensor.py | 1 + homeassistant/components/plex/server.py | 1 + homeassistant/components/plex/view.py | 1 + homeassistant/components/plugwise/__init__.py | 1 + homeassistant/components/plugwise/binary_sensor.py | 1 + homeassistant/components/plugwise/climate.py | 1 + homeassistant/components/plugwise/config_flow.py | 1 + homeassistant/components/plugwise/const.py | 1 + homeassistant/components/plugwise/coordinator.py | 1 + homeassistant/components/plugwise/diagnostics.py | 1 + homeassistant/components/plugwise/entity.py | 1 + homeassistant/components/plugwise/number.py | 1 + homeassistant/components/plugwise/select.py | 1 + homeassistant/components/plugwise/sensor.py | 1 + homeassistant/components/plugwise/switch.py | 1 + homeassistant/components/plugwise/util.py | 1 + homeassistant/components/plum_lightpad/config_flow.py | 1 + homeassistant/components/plum_lightpad/light.py | 1 + homeassistant/components/pocketcasts/sensor.py | 1 + homeassistant/components/point/alarm_control_panel.py | 1 + homeassistant/components/point/binary_sensor.py | 1 + homeassistant/components/point/const.py | 1 + homeassistant/components/point/sensor.py | 1 + homeassistant/components/poolsense/binary_sensor.py | 1 + homeassistant/components/poolsense/entity.py | 1 + homeassistant/components/poolsense/sensor.py | 1 + homeassistant/components/powerwall/__init__.py | 1 + homeassistant/components/powerwall/config_flow.py | 1 + homeassistant/components/powerwall/const.py | 1 + homeassistant/components/powerwall/models.py | 1 + homeassistant/components/powerwall/sensor.py | 1 + homeassistant/components/private_ble_device/__init__.py | 1 + homeassistant/components/private_ble_device/config_flow.py | 1 + homeassistant/components/private_ble_device/coordinator.py | 1 + homeassistant/components/private_ble_device/device_tracker.py | 1 + homeassistant/components/private_ble_device/entity.py | 1 + homeassistant/components/private_ble_device/sensor.py | 1 + homeassistant/components/profiler/config_flow.py | 1 + homeassistant/components/proliphix/climate.py | 1 + homeassistant/components/prometheus/__init__.py | 1 + homeassistant/components/prosegur/alarm_control_panel.py | 1 + homeassistant/components/prosegur/camera.py | 1 + homeassistant/components/prosegur/config_flow.py | 1 + homeassistant/components/prosegur/diagnostics.py | 1 + homeassistant/components/prowl/notify.py | 1 + homeassistant/components/proximity/__init__.py | 1 + homeassistant/components/proximity/config_flow.py | 1 + homeassistant/components/proximity/diagnostics.py | 1 + homeassistant/components/proximity/helpers.py | 1 + homeassistant/components/proxmoxve/__init__.py | 1 + homeassistant/components/proxmoxve/binary_sensor.py | 1 + homeassistant/components/proxy/camera.py | 1 + homeassistant/components/prusalink/__init__.py | 1 + homeassistant/components/prusalink/button.py | 1 + homeassistant/components/prusalink/camera.py | 1 + homeassistant/components/prusalink/config_flow.py | 1 + homeassistant/components/prusalink/sensor.py | 1 + homeassistant/components/ps4/config_flow.py | 1 + homeassistant/components/ps4/media_player.py | 1 + homeassistant/components/pulseaudio_loopback/switch.py | 1 + homeassistant/components/pure_energie/__init__.py | 1 + homeassistant/components/pure_energie/config_flow.py | 1 + homeassistant/components/pure_energie/const.py | 1 + homeassistant/components/pure_energie/diagnostics.py | 1 + homeassistant/components/pure_energie/sensor.py | 1 + homeassistant/components/purpleair/__init__.py | 1 + homeassistant/components/purpleair/config_flow.py | 1 + homeassistant/components/purpleair/coordinator.py | 1 + homeassistant/components/purpleair/diagnostics.py | 1 + homeassistant/components/purpleair/sensor.py | 1 + homeassistant/components/push/camera.py | 1 + homeassistant/components/pushbullet/__init__.py | 1 + homeassistant/components/pushbullet/api.py | 1 + homeassistant/components/pushbullet/config_flow.py | 1 + homeassistant/components/pushbullet/notify.py | 1 + homeassistant/components/pushbullet/sensor.py | 1 + homeassistant/components/pushover/__init__.py | 1 + homeassistant/components/pushover/config_flow.py | 1 + homeassistant/components/pushover/notify.py | 1 + homeassistant/components/pushsafer/notify.py | 1 + homeassistant/components/pvoutput/__init__.py | 1 + homeassistant/components/pvoutput/config_flow.py | 1 + homeassistant/components/pvoutput/const.py | 1 + homeassistant/components/pvoutput/coordinator.py | 1 + homeassistant/components/pvoutput/diagnostics.py | 1 + homeassistant/components/pvoutput/sensor.py | 1 + homeassistant/components/pvpc_hourly_pricing/__init__.py | 1 + homeassistant/components/pvpc_hourly_pricing/config_flow.py | 1 + homeassistant/components/pvpc_hourly_pricing/const.py | 1 + homeassistant/components/pvpc_hourly_pricing/helpers.py | 1 + homeassistant/components/pvpc_hourly_pricing/sensor.py | 1 + homeassistant/components/pyload/sensor.py | 1 + homeassistant/components/qbittorrent/config_flow.py | 1 + homeassistant/components/qbittorrent/const.py | 1 + homeassistant/components/qbittorrent/coordinator.py | 1 + homeassistant/components/qbittorrent/helpers.py | 1 + homeassistant/components/qbittorrent/sensor.py | 1 + homeassistant/components/qingping/__init__.py | 1 + homeassistant/components/qingping/binary_sensor.py | 1 + homeassistant/components/qingping/config_flow.py | 1 + homeassistant/components/qingping/device.py | 1 + homeassistant/components/qingping/sensor.py | 1 + homeassistant/components/qld_bushfire/geo_location.py | 1 + homeassistant/components/qnap/__init__.py | 1 + homeassistant/components/qnap/config_flow.py | 1 + homeassistant/components/qnap/coordinator.py | 1 + homeassistant/components/qnap/sensor.py | 1 + homeassistant/components/qnap_qsw/__init__.py | 1 + homeassistant/components/qnap_qsw/binary_sensor.py | 1 + homeassistant/components/qnap_qsw/button.py | 1 + homeassistant/components/qnap_qsw/config_flow.py | 1 + homeassistant/components/qnap_qsw/coordinator.py | 1 + homeassistant/components/qnap_qsw/diagnostics.py | 1 + homeassistant/components/qnap_qsw/entity.py | 1 + homeassistant/components/qnap_qsw/sensor.py | 1 + homeassistant/components/qnap_qsw/update.py | 1 + homeassistant/components/qrcode/image_processing.py | 1 + homeassistant/components/quantum_gateway/device_tracker.py | 1 + homeassistant/components/qvr_pro/camera.py | 1 + homeassistant/components/qwikswitch/__init__.py | 1 + homeassistant/components/qwikswitch/binary_sensor.py | 1 + homeassistant/components/qwikswitch/light.py | 1 + homeassistant/components/qwikswitch/sensor.py | 1 + homeassistant/components/qwikswitch/switch.py | 1 + 561 files changed, 561 insertions(+) diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index fd84668d651..fa9ce4dd08e 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with NAD receivers through RS-232.""" + from __future__ import annotations from nad_receiver import NADReceiver, NADReceiverTCP, NADReceiverTelnet diff --git a/homeassistant/components/nam/__init__.py b/homeassistant/components/nam/__init__.py index 9df1b93a4d7..63fd6af9295 100644 --- a/homeassistant/components/nam/__init__.py +++ b/homeassistant/components/nam/__init__.py @@ -1,4 +1,5 @@ """The Nettigo Air Monitor component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nam/button.py b/homeassistant/components/nam/button.py index a280369e7c8..b414e5c5525 100644 --- a/homeassistant/components/nam/button.py +++ b/homeassistant/components/nam/button.py @@ -1,4 +1,5 @@ """Support for the Nettigo Air Monitor service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nam/config_flow.py b/homeassistant/components/nam/config_flow.py index c91f08b743a..efdc8f2514b 100644 --- a/homeassistant/components/nam/config_flow.py +++ b/homeassistant/components/nam/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Nettigo Air Monitor.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nam/const.py b/homeassistant/components/nam/const.py index 5e18b94745c..66718b01c3f 100644 --- a/homeassistant/components/nam/const.py +++ b/homeassistant/components/nam/const.py @@ -1,4 +1,5 @@ """Constants for Nettigo Air Monitor integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nam/diagnostics.py b/homeassistant/components/nam/diagnostics.py index f3e4a20cc76..8ce885f0297 100644 --- a/homeassistant/components/nam/diagnostics.py +++ b/homeassistant/components/nam/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for NAM.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/nam/sensor.py b/homeassistant/components/nam/sensor.py index cd1543affa2..de516c6d607 100644 --- a/homeassistant/components/nam/sensor.py +++ b/homeassistant/components/nam/sensor.py @@ -1,4 +1,5 @@ """Support for the Nettigo Air Monitor service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/namecheapdns/__init__.py b/homeassistant/components/namecheapdns/__init__.py index 84f995c947a..43310c5e922 100644 --- a/homeassistant/components/namecheapdns/__init__.py +++ b/homeassistant/components/namecheapdns/__init__.py @@ -1,4 +1,5 @@ """Support for namecheap DNS services.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 4c3b0880170..9e368353774 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -1,4 +1,5 @@ """The Nanoleaf integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nanoleaf/config_flow.py b/homeassistant/components/nanoleaf/config_flow.py index d4948507a42..ff25a25caf4 100644 --- a/homeassistant/components/nanoleaf/config_flow.py +++ b/homeassistant/components/nanoleaf/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nanoleaf integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/nanoleaf/device_trigger.py b/homeassistant/components/nanoleaf/device_trigger.py index 5de093a4a17..15b14e9719e 100644 --- a/homeassistant/components/nanoleaf/device_trigger.py +++ b/homeassistant/components/nanoleaf/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for Nanoleaf.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/nanoleaf/diagnostics.py b/homeassistant/components/nanoleaf/diagnostics.py index 9783f7854f3..57f385e5039 100644 --- a/homeassistant/components/nanoleaf/diagnostics.py +++ b/homeassistant/components/nanoleaf/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Nanoleaf.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index e8d604d9236..b80048307bb 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -1,4 +1,5 @@ """Support for Nanoleaf Lights.""" + from __future__ import annotations import math diff --git a/homeassistant/components/neato/api.py b/homeassistant/components/neato/api.py index d4596f1658d..75a3d6724de 100644 --- a/homeassistant/components/neato/api.py +++ b/homeassistant/components/neato/api.py @@ -1,4 +1,5 @@ """API for Neato Botvac bound to Home Assistant OAuth.""" + from __future__ import annotations from asyncio import run_coroutine_threadsafe diff --git a/homeassistant/components/neato/button.py b/homeassistant/components/neato/button.py index 8b23bbe4681..29114ce5188 100644 --- a/homeassistant/components/neato/button.py +++ b/homeassistant/components/neato/button.py @@ -1,4 +1,5 @@ """Support for Neato buttons.""" + from __future__ import annotations from pybotvac import Robot diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 9ce66a53622..7aeadb0698d 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -1,4 +1,5 @@ """Support for loading picture from Neato.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 89a8639009f..642fea11081 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Neato Botvac.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/neato/entity.py b/homeassistant/components/neato/entity.py index 46ad358c638..e4486b20ec4 100644 --- a/homeassistant/components/neato/entity.py +++ b/homeassistant/components/neato/entity.py @@ -1,4 +1,5 @@ """Base entity for Neato.""" + from __future__ import annotations from pybotvac import Robot diff --git a/homeassistant/components/neato/hub.py b/homeassistant/components/neato/hub.py index 6ee00b2a8b4..fd5f045c30f 100644 --- a/homeassistant/components/neato/hub.py +++ b/homeassistant/components/neato/hub.py @@ -1,4 +1,5 @@ """Support for Neato botvac connected vacuum cleaners.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/neato/sensor.py b/homeassistant/components/neato/sensor.py index 3b68ddcf3df..f781c49457e 100644 --- a/homeassistant/components/neato/sensor.py +++ b/homeassistant/components/neato/sensor.py @@ -1,4 +1,5 @@ """Support for Neato sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py index ae90a8230b2..34a0b13a870 100644 --- a/homeassistant/components/neato/switch.py +++ b/homeassistant/components/neato/switch.py @@ -1,4 +1,5 @@ """Support for Neato Connected Vacuums switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 15127ed7d66..6f1c7c2f5d7 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -1,4 +1,5 @@ """Support for Neato Connected Vacuums.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index f0c782bc1b5..1e8a7c30b9d 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -1,4 +1,5 @@ """Support for Nederlandse Spoorwegen public transport.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index b5d30219550..a8202434ce5 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -1,4 +1,5 @@ """Support for Ness D8X/D16X devices.""" + from collections import namedtuple import datetime import logging diff --git a/homeassistant/components/ness_alarm/alarm_control_panel.py b/homeassistant/components/ness_alarm/alarm_control_panel.py index a4f77f59e25..2835dee9056 100644 --- a/homeassistant/components/ness_alarm/alarm_control_panel.py +++ b/homeassistant/components/ness_alarm/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Ness D8X/D16X alarm panel.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ness_alarm/binary_sensor.py b/homeassistant/components/ness_alarm/binary_sensor.py index 117d65b0940..bb0fa38ef72 100644 --- a/homeassistant/components/ness_alarm/binary_sensor.py +++ b/homeassistant/components/ness_alarm/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Ness D8X/D16X zone states - represented as binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 8d1d58f9117..383521452d0 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -1,4 +1,5 @@ """Support for Nest devices.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/nest/camera.py b/homeassistant/components/nest/camera.py index c943ea922e9..f0b3ccd477d 100644 --- a/homeassistant/components/nest/camera.py +++ b/homeassistant/components/nest/camera.py @@ -1,4 +1,5 @@ """Support for Google Nest SDM Cameras.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index 2d0186b2bfd..5e9dcc01e97 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -1,4 +1,5 @@ """Support for Google Nest SDM climate devices.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index 8e05bc611c9..e3854b06038 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -7,6 +7,7 @@ This configuration flow supports the following: NestFlowHandler is an implementation of AbstractOAuth2FlowHandler with some overrides to custom steps inserted in the middle of the flow. """ + from __future__ import annotations from collections.abc import Iterable, Mapping diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py index f7369489e22..52c756d6a18 100644 --- a/homeassistant/components/nest/device_trigger.py +++ b/homeassistant/components/nest/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Nest.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/nest/sensor.py b/homeassistant/components/nest/sensor.py index aa170710eb6..edd359619fd 100644 --- a/homeassistant/components/nest/sensor.py +++ b/homeassistant/components/nest/sensor.py @@ -1,4 +1,5 @@ """Support for Google Nest SDM sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/__init__.py b/homeassistant/components/netatmo/__init__.py index c514e7b890d..322af8cf3ac 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -1,4 +1,5 @@ """The Netatmo integration.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/netatmo/api.py b/homeassistant/components/netatmo/api.py index 7605689b3f5..f5fe591bfbf 100644 --- a/homeassistant/components/netatmo/api.py +++ b/homeassistant/components/netatmo/api.py @@ -1,4 +1,5 @@ """API for Netatmo bound to HASS OAuth.""" + from collections.abc import Iterable from typing import cast diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index dc566afd233..bd12e757359 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -1,4 +1,5 @@ """Support for the Netatmo cameras.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index db12efb2f01..df9c7f6fee2 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -1,4 +1,5 @@ """Support for Netatmo Smart thermostats.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/config_flow.py b/homeassistant/components/netatmo/config_flow.py index b2e24420d11..54ec019f815 100644 --- a/homeassistant/components/netatmo/config_flow.py +++ b/homeassistant/components/netatmo/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Netatmo.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index 416c5668eae..34a5c42038e 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -1,4 +1,5 @@ """Constants used by the Netatmo component.""" + from homeassistant.const import Platform API = "api" diff --git a/homeassistant/components/netatmo/cover.py b/homeassistant/components/netatmo/cover.py index b9537fee179..f2b5c801eec 100644 --- a/homeassistant/components/netatmo/cover.py +++ b/homeassistant/components/netatmo/cover.py @@ -1,4 +1,5 @@ """Support for Netatmo/Bubendorff covers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 42d4ced6792..a4c4dbfa21d 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -1,4 +1,5 @@ """The Netatmo data handler.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index f4719badcfa..efc2978a43b 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Netatmo.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/netatmo/diagnostics.py b/homeassistant/components/netatmo/diagnostics.py index 3a30dcd8588..4901ef6bd55 100644 --- a/homeassistant/components/netatmo/diagnostics.py +++ b/homeassistant/components/netatmo/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Netatmo.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/netatmo/entity.py b/homeassistant/components/netatmo/entity.py index e6829604d48..579d2177824 100644 --- a/homeassistant/components/netatmo/entity.py +++ b/homeassistant/components/netatmo/entity.py @@ -1,4 +1,5 @@ """Base class for Netatmo entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/netatmo/fan.py b/homeassistant/components/netatmo/fan.py index 8f22861a249..1b2798dd118 100644 --- a/homeassistant/components/netatmo/fan.py +++ b/homeassistant/components/netatmo/fan.py @@ -1,4 +1,5 @@ """Support for Netatmo/Bubendorff fans.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/helper.py b/homeassistant/components/netatmo/helper.py index ea30e059d3a..026f3f916f5 100644 --- a/homeassistant/components/netatmo/helper.py +++ b/homeassistant/components/netatmo/helper.py @@ -1,4 +1,5 @@ """Helper for Netatmo integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/netatmo/light.py b/homeassistant/components/netatmo/light.py index c38aec41564..9ccab51f792 100644 --- a/homeassistant/components/netatmo/light.py +++ b/homeassistant/components/netatmo/light.py @@ -1,4 +1,5 @@ """Support for the Netatmo camera lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/media_source.py b/homeassistant/components/netatmo/media_source.py index 58bf2f93c96..7ad4acf5316 100644 --- a/homeassistant/components/netatmo/media_source.py +++ b/homeassistant/components/netatmo/media_source.py @@ -1,4 +1,5 @@ """Netatmo Media Source Implementation.""" + from __future__ import annotations import datetime as dt diff --git a/homeassistant/components/netatmo/select.py b/homeassistant/components/netatmo/select.py index 2dd88782ac3..6680242f579 100644 --- a/homeassistant/components/netatmo/select.py +++ b/homeassistant/components/netatmo/select.py @@ -1,4 +1,5 @@ """Support for the Netatmo climate schedule selector.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 430de8e318c..f71ffd76cc8 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -1,4 +1,5 @@ """Support for the Netatmo sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/netatmo/switch.py b/homeassistant/components/netatmo/switch.py index 730f41afeeb..6677adec4b0 100644 --- a/homeassistant/components/netatmo/switch.py +++ b/homeassistant/components/netatmo/switch.py @@ -1,4 +1,5 @@ """Support for Netatmo/BTicino/Legrande switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netdata/sensor.py b/homeassistant/components/netdata/sensor.py index 0e33cd9c952..abbb3bbb6c9 100644 --- a/homeassistant/components/netdata/sensor.py +++ b/homeassistant/components/netdata/sensor.py @@ -1,4 +1,5 @@ """Support gathering system information of hosts which are running netdata.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index b21286ff05b..445453ad2aa 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -1,4 +1,5 @@ """Support for Netgear routers.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/netgear/button.py b/homeassistant/components/netgear/button.py index 6ec988edbe1..307bfab92d4 100644 --- a/homeassistant/components/netgear/button.py +++ b/homeassistant/components/netgear/button.py @@ -1,4 +1,5 @@ """Support for Netgear Button.""" + from collections.abc import Callable, Coroutine from dataclasses import dataclass from typing import Any diff --git a/homeassistant/components/netgear/config_flow.py b/homeassistant/components/netgear/config_flow.py index 85982196cf2..a872e9fb4ac 100644 --- a/homeassistant/components/netgear/config_flow.py +++ b/homeassistant/components/netgear/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Netgear integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netgear/const.py b/homeassistant/components/netgear/const.py index cf6cc827519..40394677362 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -1,4 +1,5 @@ """Netgear component constants.""" + from datetime import timedelta from homeassistant.const import Platform diff --git a/homeassistant/components/netgear/device_tracker.py b/homeassistant/components/netgear/device_tracker.py index 38ad024a2c4..ee3d010e443 100644 --- a/homeassistant/components/netgear/device_tracker.py +++ b/homeassistant/components/netgear/device_tracker.py @@ -1,4 +1,5 @@ """Support for Netgear routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netgear/entity.py b/homeassistant/components/netgear/entity.py index 45418681db0..2610b4c7132 100644 --- a/homeassistant/components/netgear/entity.py +++ b/homeassistant/components/netgear/entity.py @@ -1,4 +1,5 @@ """Represent the Netgear router and its devices.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/netgear/errors.py b/homeassistant/components/netgear/errors.py index 2ac1ed18224..36e5fcb1e63 100644 --- a/homeassistant/components/netgear/errors.py +++ b/homeassistant/components/netgear/errors.py @@ -1,4 +1,5 @@ """Errors for the Netgear component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/netgear/router.py b/homeassistant/components/netgear/router.py index 3c3be7fe9fb..1e4bf2480e9 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -1,4 +1,5 @@ """Represent the Netgear router and its devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index 00623938b46..d569a50d7e1 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -1,4 +1,5 @@ """Support for Netgear routers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/netgear/switch.py b/homeassistant/components/netgear/switch.py index 9e5aab2c866..34a4258b085 100644 --- a/homeassistant/components/netgear/switch.py +++ b/homeassistant/components/netgear/switch.py @@ -1,4 +1,5 @@ """Support for Netgear switches.""" + from collections.abc import Callable from dataclasses import dataclass from datetime import timedelta diff --git a/homeassistant/components/netgear/update.py b/homeassistant/components/netgear/update.py index 78e11e7c174..1fbfee3d892 100644 --- a/homeassistant/components/netgear/update.py +++ b/homeassistant/components/netgear/update.py @@ -1,4 +1,5 @@ """Update entities for Netgear devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py index 491ee0efe59..8c54cb96b3d 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -1,4 +1,5 @@ """Support for Netgear LTE modems.""" + from datetime import timedelta from aiohttp.cookiejar import CookieJar diff --git a/homeassistant/components/netgear_lte/binary_sensor.py b/homeassistant/components/netgear_lte/binary_sensor.py index 2830c551b80..43a9c1bd260 100644 --- a/homeassistant/components/netgear_lte/binary_sensor.py +++ b/homeassistant/components/netgear_lte/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Netgear LTE binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/netgear_lte/config_flow.py b/homeassistant/components/netgear_lte/config_flow.py index f0c731599f5..fe411f79699 100644 --- a/homeassistant/components/netgear_lte/config_flow.py +++ b/homeassistant/components/netgear_lte/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Netgear LTE integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/netgear_lte/notify.py b/homeassistant/components/netgear_lte/notify.py index ddc5e93677c..97ba402dc35 100644 --- a/homeassistant/components/netgear_lte/notify.py +++ b/homeassistant/components/netgear_lte/notify.py @@ -1,4 +1,5 @@ """Support for Netgear LTE notifications.""" + from __future__ import annotations import attr diff --git a/homeassistant/components/netgear_lte/sensor.py b/homeassistant/components/netgear_lte/sensor.py index c1e7aaad171..62b4796f068 100644 --- a/homeassistant/components/netgear_lte/sensor.py +++ b/homeassistant/components/netgear_lte/sensor.py @@ -1,4 +1,5 @@ """Support for Netgear LTE sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/netgear_lte/services.py b/homeassistant/components/netgear_lte/services.py index 2ea98896791..02000820119 100644 --- a/homeassistant/components/netgear_lte/services.py +++ b/homeassistant/components/netgear_lte/services.py @@ -1,4 +1,5 @@ """Services for the Netgear LTE integration.""" + from typing import TYPE_CHECKING import voluptuous as vol diff --git a/homeassistant/components/netio/switch.py b/homeassistant/components/netio/switch.py index 546aa07e22d..0f0c85c1720 100644 --- a/homeassistant/components/netio/switch.py +++ b/homeassistant/components/netio/switch.py @@ -1,4 +1,5 @@ """The Netio switch component.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index 32bb9a574cd..0ec6f71814f 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -1,4 +1,5 @@ """The Network Configuration integration.""" + from __future__ import annotations from ipaddress import IPv4Address, IPv6Address, ip_interface diff --git a/homeassistant/components/network/const.py b/homeassistant/components/network/const.py index 3a166189b85..6c5b6f80eda 100644 --- a/homeassistant/components/network/const.py +++ b/homeassistant/components/network/const.py @@ -1,4 +1,5 @@ """Constants for the network integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/network/models.py b/homeassistant/components/network/models.py index 4428578f8f9..93d34e92302 100644 --- a/homeassistant/components/network/models.py +++ b/homeassistant/components/network/models.py @@ -1,4 +1,5 @@ """Models helper class for the network integration.""" + from __future__ import annotations from typing import TypedDict diff --git a/homeassistant/components/network/network.py b/homeassistant/components/network/network.py index 4613cb91cc9..4158307bb1a 100644 --- a/homeassistant/components/network/network.py +++ b/homeassistant/components/network/network.py @@ -1,4 +1,5 @@ """Network helper class for the network integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/network/util.py b/homeassistant/components/network/util.py index 2fb0690684c..55c3c2f5ead 100644 --- a/homeassistant/components/network/util.py +++ b/homeassistant/components/network/util.py @@ -1,4 +1,5 @@ """Network helper class for the network integration.""" + from __future__ import annotations from ipaddress import IPv4Address, IPv6Address, ip_address diff --git a/homeassistant/components/network/websocket.py b/homeassistant/components/network/websocket.py index 4c55585e112..78626b893e4 100644 --- a/homeassistant/components/network/websocket.py +++ b/homeassistant/components/network/websocket.py @@ -1,4 +1,5 @@ """The Network Configuration integration websocket commands.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/neurio_energy/sensor.py b/homeassistant/components/neurio_energy/sensor.py index a9023ffca2b..4a7ce43a0d7 100644 --- a/homeassistant/components/neurio_energy/sensor.py +++ b/homeassistant/components/neurio_energy/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring a Neurio energy sensor.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nexia/binary_sensor.py b/homeassistant/components/nexia/binary_sensor.py index 02c3fef1162..51306584b7a 100644 --- a/homeassistant/components/nexia/binary_sensor.py +++ b/homeassistant/components/nexia/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Nexia / Trane XL Thermostats.""" + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 63caeb445b7..78c0bc88ef7 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -1,4 +1,5 @@ """Support for Nexia / Trane XL thermostats.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nexia/const.py b/homeassistant/components/nexia/const.py index fe2d6527ea0..b46037eab7b 100644 --- a/homeassistant/components/nexia/const.py +++ b/homeassistant/components/nexia/const.py @@ -1,4 +1,5 @@ """Nexia constants.""" + from homeassistant.const import Platform PLATFORMS = [ diff --git a/homeassistant/components/nexia/coordinator.py b/homeassistant/components/nexia/coordinator.py index cd515e44b14..894c491c45b 100644 --- a/homeassistant/components/nexia/coordinator.py +++ b/homeassistant/components/nexia/coordinator.py @@ -1,4 +1,5 @@ """Component to embed nexia devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nexia/diagnostics.py b/homeassistant/components/nexia/diagnostics.py index 9b3f518217b..e03cf23b83b 100644 --- a/homeassistant/components/nexia/diagnostics.py +++ b/homeassistant/components/nexia/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for nexia.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nexia/number.py b/homeassistant/components/nexia/number.py index 019d425d1ad..a4117584720 100644 --- a/homeassistant/components/nexia/number.py +++ b/homeassistant/components/nexia/number.py @@ -1,4 +1,5 @@ """Support for Nexia / Trane XL Thermostats.""" + from __future__ import annotations from nexia.home import NexiaHome diff --git a/homeassistant/components/nexia/scene.py b/homeassistant/components/nexia/scene.py index ba9e8b12dde..337068e44e9 100644 --- a/homeassistant/components/nexia/scene.py +++ b/homeassistant/components/nexia/scene.py @@ -1,4 +1,5 @@ """Support for Nexia Automations.""" + from typing import Any from nexia.automation import NexiaAutomation diff --git a/homeassistant/components/nexia/sensor.py b/homeassistant/components/nexia/sensor.py index 79e07bc71b4..a77920630f8 100644 --- a/homeassistant/components/nexia/sensor.py +++ b/homeassistant/components/nexia/sensor.py @@ -1,4 +1,5 @@ """Support for Nexia / Trane XL Thermostats.""" + from __future__ import annotations from nexia.const import UNIT_CELSIUS diff --git a/homeassistant/components/nexia/switch.py b/homeassistant/components/nexia/switch.py index 87c3f6dc938..5e19136d55e 100644 --- a/homeassistant/components/nexia/switch.py +++ b/homeassistant/components/nexia/switch.py @@ -1,4 +1,5 @@ """Support for Nexia switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nextbus/config_flow.py b/homeassistant/components/nextbus/config_flow.py index 67bc8581e86..1d2b7f0b00f 100644 --- a/homeassistant/components/nextbus/config_flow.py +++ b/homeassistant/components/nextbus/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Nextbus integration.""" + from collections import Counter import logging diff --git a/homeassistant/components/nextbus/coordinator.py b/homeassistant/components/nextbus/coordinator.py index f130e40ef05..abf280bece9 100644 --- a/homeassistant/components/nextbus/coordinator.py +++ b/homeassistant/components/nextbus/coordinator.py @@ -1,4 +1,5 @@ """NextBus data update coordinator.""" + from datetime import timedelta import logging from typing import Any, cast diff --git a/homeassistant/components/nextbus/sensor.py b/homeassistant/components/nextbus/sensor.py index eaedc50cf3b..68d10726609 100644 --- a/homeassistant/components/nextbus/sensor.py +++ b/homeassistant/components/nextbus/sensor.py @@ -1,4 +1,5 @@ """NextBus sensor.""" + from __future__ import annotations from itertools import chain diff --git a/homeassistant/components/nextbus/util.py b/homeassistant/components/nextbus/util.py index 73b3b400ff4..e9a1e1fd254 100644 --- a/homeassistant/components/nextbus/util.py +++ b/homeassistant/components/nextbus/util.py @@ -1,4 +1,5 @@ """Utils for NextBus integration module.""" + from typing import Any diff --git a/homeassistant/components/nextcloud/binary_sensor.py b/homeassistant/components/nextcloud/binary_sensor.py index e4068695086..6c6f6141975 100644 --- a/homeassistant/components/nextcloud/binary_sensor.py +++ b/homeassistant/components/nextcloud/binary_sensor.py @@ -1,4 +1,5 @@ """Summary binary data from Nextcoud.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/nextcloud/config_flow.py b/homeassistant/components/nextcloud/config_flow.py index f843a7d609e..c469936ac48 100644 --- a/homeassistant/components/nextcloud/config_flow.py +++ b/homeassistant/components/nextcloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Nextcloud integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/nextcloud/const.py b/homeassistant/components/nextcloud/const.py index 248128dd538..2cc6513cd4b 100644 --- a/homeassistant/components/nextcloud/const.py +++ b/homeassistant/components/nextcloud/const.py @@ -1,4 +1,5 @@ """Constants for Nextcloud integration.""" + from datetime import timedelta DOMAIN = "nextcloud" diff --git a/homeassistant/components/nextcloud/entity.py b/homeassistant/components/nextcloud/entity.py index b9dab9179c1..19431756e43 100644 --- a/homeassistant/components/nextcloud/entity.py +++ b/homeassistant/components/nextcloud/entity.py @@ -1,4 +1,5 @@ """Base entity for the Nextcloud integration.""" + from urllib.parse import urlparse from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/nextcloud/sensor.py b/homeassistant/components/nextcloud/sensor.py index 7d3f14e3b7f..d8a2a362ce0 100644 --- a/homeassistant/components/nextcloud/sensor.py +++ b/homeassistant/components/nextcloud/sensor.py @@ -1,4 +1,5 @@ """Summary data from Nextcoud.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/nextcloud/update.py b/homeassistant/components/nextcloud/update.py index 5d52ac2a48f..52583d690bf 100644 --- a/homeassistant/components/nextcloud/update.py +++ b/homeassistant/components/nextcloud/update.py @@ -1,4 +1,5 @@ """Update data from Nextcoud.""" + from __future__ import annotations from homeassistant.components.update import UpdateEntity, UpdateEntityDescription diff --git a/homeassistant/components/nextdns/__init__.py b/homeassistant/components/nextdns/__init__.py index af972fb7509..389173a2694 100644 --- a/homeassistant/components/nextdns/__init__.py +++ b/homeassistant/components/nextdns/__init__.py @@ -1,4 +1,5 @@ """The NextDNS component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nextdns/binary_sensor.py b/homeassistant/components/nextdns/binary_sensor.py index dad29893161..bf230b937c2 100644 --- a/homeassistant/components/nextdns/binary_sensor.py +++ b/homeassistant/components/nextdns/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the NextDNS service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/nextdns/button.py b/homeassistant/components/nextdns/button.py index 2eafe2b477e..d74152248a5 100644 --- a/homeassistant/components/nextdns/button.py +++ b/homeassistant/components/nextdns/button.py @@ -1,4 +1,5 @@ """Support for the NextDNS service.""" + from __future__ import annotations from homeassistant.components.button import ButtonEntity, ButtonEntityDescription diff --git a/homeassistant/components/nextdns/config_flow.py b/homeassistant/components/nextdns/config_flow.py index 2a676f7a3a1..28fd50af2dc 100644 --- a/homeassistant/components/nextdns/config_flow.py +++ b/homeassistant/components/nextdns/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for NextDNS.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nextdns/const.py b/homeassistant/components/nextdns/const.py index 031dd1c5814..b8210c1939c 100644 --- a/homeassistant/components/nextdns/const.py +++ b/homeassistant/components/nextdns/const.py @@ -1,4 +1,5 @@ """Constants for NextDNS integration.""" + from datetime import timedelta ATTR_CONNECTION = "connection" diff --git a/homeassistant/components/nextdns/diagnostics.py b/homeassistant/components/nextdns/diagnostics.py index 2c0d313060f..c0a9071bb9d 100644 --- a/homeassistant/components/nextdns/diagnostics.py +++ b/homeassistant/components/nextdns/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for NextDNS.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/nextdns/sensor.py b/homeassistant/components/nextdns/sensor.py index b6864fea50a..94441f8f658 100644 --- a/homeassistant/components/nextdns/sensor.py +++ b/homeassistant/components/nextdns/sensor.py @@ -1,4 +1,5 @@ """Support for the NextDNS service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/nextdns/switch.py b/homeassistant/components/nextdns/switch.py index a01b8a8c3c3..f9a5dbde4b7 100644 --- a/homeassistant/components/nextdns/switch.py +++ b/homeassistant/components/nextdns/switch.py @@ -1,4 +1,5 @@ """Support for the NextDNS service.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/nextdns/system_health.py b/homeassistant/components/nextdns/system_health.py index a56a89914b8..09c13f0580e 100644 --- a/homeassistant/components/nextdns/system_health.py +++ b/homeassistant/components/nextdns/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nfandroidtv/__init__.py b/homeassistant/components/nfandroidtv/__init__.py index 38622fc0060..42d42e26d1f 100644 --- a/homeassistant/components/nfandroidtv/__init__.py +++ b/homeassistant/components/nfandroidtv/__init__.py @@ -1,4 +1,5 @@ """The NFAndroidTV integration.""" + from notifications_android_tv.notifications import ConnectError, Notifications from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/nfandroidtv/config_flow.py b/homeassistant/components/nfandroidtv/config_flow.py index 3d0b07a2ea3..83621c63789 100644 --- a/homeassistant/components/nfandroidtv/config_flow.py +++ b/homeassistant/components/nfandroidtv/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NFAndroidTV integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nfandroidtv/notify.py b/homeassistant/components/nfandroidtv/notify.py index c70272d3835..dd42a0ab10b 100644 --- a/homeassistant/components/nfandroidtv/notify.py +++ b/homeassistant/components/nfandroidtv/notify.py @@ -1,4 +1,5 @@ """Notifications for Android TV notification service.""" + from __future__ import annotations from io import BufferedReader diff --git a/homeassistant/components/nibe_heatpump/__init__.py b/homeassistant/components/nibe_heatpump/__init__.py index 058f3ef8711..fbb49351e0e 100644 --- a/homeassistant/components/nibe_heatpump/__init__.py +++ b/homeassistant/components/nibe_heatpump/__init__.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump integration.""" + from __future__ import annotations from nibe.connection import Connection diff --git a/homeassistant/components/nibe_heatpump/binary_sensor.py b/homeassistant/components/nibe_heatpump/binary_sensor.py index d1fdfa710a1..035a4a23a08 100644 --- a/homeassistant/components/nibe_heatpump/binary_sensor.py +++ b/homeassistant/components/nibe_heatpump/binary_sensor.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump binary sensors.""" + from __future__ import annotations from nibe.coil import Coil, CoilData diff --git a/homeassistant/components/nibe_heatpump/button.py b/homeassistant/components/nibe_heatpump/button.py index f45b2af2909..0c3122805e1 100644 --- a/homeassistant/components/nibe_heatpump/button.py +++ b/homeassistant/components/nibe_heatpump/button.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump sensors.""" + from __future__ import annotations from nibe.coil_groups import UNIT_COILGROUPS, UnitCoilGroup diff --git a/homeassistant/components/nibe_heatpump/climate.py b/homeassistant/components/nibe_heatpump/climate.py index 3a89f4f6022..746ed26687d 100644 --- a/homeassistant/components/nibe_heatpump/climate.py +++ b/homeassistant/components/nibe_heatpump/climate.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump climate.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/nibe_heatpump/config_flow.py b/homeassistant/components/nibe_heatpump/config_flow.py index 0ba80ad6754..913ebd6b00c 100644 --- a/homeassistant/components/nibe_heatpump/config_flow.py +++ b/homeassistant/components/nibe_heatpump/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nibe Heat Pump integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nibe_heatpump/coordinator.py b/homeassistant/components/nibe_heatpump/coordinator.py index ce75247083b..1711c3d8f2a 100644 --- a/homeassistant/components/nibe_heatpump/coordinator.py +++ b/homeassistant/components/nibe_heatpump/coordinator.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump coordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nibe_heatpump/number.py b/homeassistant/components/nibe_heatpump/number.py index 83ccc124e51..509f3364fee 100644 --- a/homeassistant/components/nibe_heatpump/number.py +++ b/homeassistant/components/nibe_heatpump/number.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump numbers.""" + from __future__ import annotations from nibe.coil import Coil, CoilData diff --git a/homeassistant/components/nibe_heatpump/select.py b/homeassistant/components/nibe_heatpump/select.py index c4794cc18b7..07c958885b8 100644 --- a/homeassistant/components/nibe_heatpump/select.py +++ b/homeassistant/components/nibe_heatpump/select.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump select.""" + from __future__ import annotations from nibe.coil import Coil, CoilData diff --git a/homeassistant/components/nibe_heatpump/sensor.py b/homeassistant/components/nibe_heatpump/sensor.py index 8c9439e6531..c6bac0323b9 100644 --- a/homeassistant/components/nibe_heatpump/sensor.py +++ b/homeassistant/components/nibe_heatpump/sensor.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump sensors.""" + from __future__ import annotations from nibe.coil import Coil, CoilData diff --git a/homeassistant/components/nibe_heatpump/switch.py b/homeassistant/components/nibe_heatpump/switch.py index f55882d529c..594a8078b76 100644 --- a/homeassistant/components/nibe_heatpump/switch.py +++ b/homeassistant/components/nibe_heatpump/switch.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump switch.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nibe_heatpump/water_heater.py b/homeassistant/components/nibe_heatpump/water_heater.py index db688fdb69c..c60f5b6e3b2 100644 --- a/homeassistant/components/nibe_heatpump/water_heater.py +++ b/homeassistant/components/nibe_heatpump/water_heater.py @@ -1,4 +1,5 @@ """The Nibe Heat Pump sensors.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/nightscout/sensor.py b/homeassistant/components/nightscout/sensor.py index bdc46e75cb8..92291bdc4f9 100644 --- a/homeassistant/components/nightscout/sensor.py +++ b/homeassistant/components/nightscout/sensor.py @@ -1,4 +1,5 @@ """Support for Nightscout sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nightscout/utils.py b/homeassistant/components/nightscout/utils.py index ac9ce1a3384..928abd1aa4f 100644 --- a/homeassistant/components/nightscout/utils.py +++ b/homeassistant/components/nightscout/utils.py @@ -1,4 +1,5 @@ """Nightscout util functions.""" + from __future__ import annotations import hashlib diff --git a/homeassistant/components/niko_home_control/light.py b/homeassistant/components/niko_home_control/light.py index b541a145a66..6554bf5eeec 100644 --- a/homeassistant/components/niko_home_control/light.py +++ b/homeassistant/components/niko_home_control/light.py @@ -1,4 +1,5 @@ """Support for Niko Home Control.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nilu/air_quality.py b/homeassistant/components/nilu/air_quality.py index 3745c6bae6f..7b1068771d2 100644 --- a/homeassistant/components/nilu/air_quality.py +++ b/homeassistant/components/nilu/air_quality.py @@ -1,4 +1,5 @@ """Sensor for checking the air quality around Norway.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nina/__init__.py b/homeassistant/components/nina/__init__.py index 435ea288aa7..d5b1c5ccb35 100644 --- a/homeassistant/components/nina/__init__.py +++ b/homeassistant/components/nina/__init__.py @@ -1,4 +1,5 @@ """The Nina integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/nina/binary_sensor.py b/homeassistant/components/nina/binary_sensor.py index 92c7d16dc84..bcc1eb2a4b6 100644 --- a/homeassistant/components/nina/binary_sensor.py +++ b/homeassistant/components/nina/binary_sensor.py @@ -1,4 +1,5 @@ """NINA sensor platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nina/config_flow.py b/homeassistant/components/nina/config_flow.py index 0954ba26874..8b5bcb72252 100644 --- a/homeassistant/components/nina/config_flow.py +++ b/homeassistant/components/nina/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nina integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nina/const.py b/homeassistant/components/nina/const.py index 198e21c2689..1e755056079 100644 --- a/homeassistant/components/nina/const.py +++ b/homeassistant/components/nina/const.py @@ -1,4 +1,5 @@ """Constants for the Nina integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nina/coordinator.py b/homeassistant/components/nina/coordinator.py index b2c97503442..c731c7a62d7 100644 --- a/homeassistant/components/nina/coordinator.py +++ b/homeassistant/components/nina/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the nina integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index a0cb3c4f8cc..85ff6876a05 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -1,4 +1,5 @@ """Support for the Nissan Leaf Carwings/Nissan Connect API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nissan_leaf/binary_sensor.py b/homeassistant/components/nissan_leaf/binary_sensor.py index 40880714c2a..3b15fabe382 100644 --- a/homeassistant/components/nissan_leaf/binary_sensor.py +++ b/homeassistant/components/nissan_leaf/binary_sensor.py @@ -1,4 +1,5 @@ """Plugged In Status Support for the Nissan Leaf.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nissan_leaf/button.py b/homeassistant/components/nissan_leaf/button.py index 1f948fe9847..aa2bbbbca9b 100644 --- a/homeassistant/components/nissan_leaf/button.py +++ b/homeassistant/components/nissan_leaf/button.py @@ -1,4 +1,5 @@ """Button to start charging the Nissan Leaf.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nissan_leaf/const.py b/homeassistant/components/nissan_leaf/const.py index f7ebed99ab8..299576b86a7 100644 --- a/homeassistant/components/nissan_leaf/const.py +++ b/homeassistant/components/nissan_leaf/const.py @@ -1,4 +1,5 @@ """Constants for the Nissan Leaf integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nissan_leaf/sensor.py b/homeassistant/components/nissan_leaf/sensor.py index cd3524eaf87..bde1719e9b1 100644 --- a/homeassistant/components/nissan_leaf/sensor.py +++ b/homeassistant/components/nissan_leaf/sensor.py @@ -1,4 +1,5 @@ """Battery Charge and Range Support for the Nissan Leaf.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nissan_leaf/switch.py b/homeassistant/components/nissan_leaf/switch.py index 97f02a9e0be..39f875ff95f 100644 --- a/homeassistant/components/nissan_leaf/switch.py +++ b/homeassistant/components/nissan_leaf/switch.py @@ -1,4 +1,5 @@ """Charge and Climate Control Support for the Nissan Leaf.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nmap_tracker/__init__.py b/homeassistant/components/nmap_tracker/__init__.py index bab71df94dc..ffc4b975308 100644 --- a/homeassistant/components/nmap_tracker/__init__.py +++ b/homeassistant/components/nmap_tracker/__init__.py @@ -1,4 +1,5 @@ """The Nmap Tracker integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nmap_tracker/config_flow.py b/homeassistant/components/nmap_tracker/config_flow.py index 40ad698b948..6128272fbbb 100644 --- a/homeassistant/components/nmap_tracker/config_flow.py +++ b/homeassistant/components/nmap_tracker/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nmap Tracker integration.""" + from __future__ import annotations from ipaddress import ip_address, ip_network, summarize_address_range diff --git a/homeassistant/components/nmap_tracker/const.py b/homeassistant/components/nmap_tracker/const.py index c7c1342036e..617f84e8aca 100644 --- a/homeassistant/components/nmap_tracker/const.py +++ b/homeassistant/components/nmap_tracker/const.py @@ -1,4 +1,5 @@ """The Nmap Tracker integration.""" + from typing import Final from homeassistant.const import Platform diff --git a/homeassistant/components/nmap_tracker/device_tracker.py b/homeassistant/components/nmap_tracker/device_tracker.py index 9b5597b6ba4..3f07926eaef 100644 --- a/homeassistant/components/nmap_tracker/device_tracker.py +++ b/homeassistant/components/nmap_tracker/device_tracker.py @@ -1,4 +1,5 @@ """Support for scanning a network with nmap.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nmbs/sensor.py b/homeassistant/components/nmbs/sensor.py index 7fe40af3b69..a684b47e245 100644 --- a/homeassistant/components/nmbs/sensor.py +++ b/homeassistant/components/nmbs/sensor.py @@ -1,4 +1,5 @@ """Get ride details and liveboard details for NMBS (Belgian railway).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/noaa_tides/sensor.py b/homeassistant/components/noaa_tides/sensor.py index a83f18fd6ca..235263345e6 100644 --- a/homeassistant/components/noaa_tides/sensor.py +++ b/homeassistant/components/noaa_tides/sensor.py @@ -1,4 +1,5 @@ """Support for the NOAA Tides and Currents API.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/nobo_hub/__init__.py b/homeassistant/components/nobo_hub/__init__.py index 6c77f98d1b1..15a4b48c315 100644 --- a/homeassistant/components/nobo_hub/__init__.py +++ b/homeassistant/components/nobo_hub/__init__.py @@ -1,4 +1,5 @@ """The Nobø Ecohub integration.""" + from __future__ import annotations from pynobo import nobo diff --git a/homeassistant/components/nobo_hub/climate.py b/homeassistant/components/nobo_hub/climate.py index ca8ee08885d..f1e2f4a78f0 100644 --- a/homeassistant/components/nobo_hub/climate.py +++ b/homeassistant/components/nobo_hub/climate.py @@ -1,4 +1,5 @@ """Python Control of Nobø Hub - Nobø Energy Control.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nobo_hub/config_flow.py b/homeassistant/components/nobo_hub/config_flow.py index 20b61010ba9..6fc5bba2c1b 100644 --- a/homeassistant/components/nobo_hub/config_flow.py +++ b/homeassistant/components/nobo_hub/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nobø Ecohub integration.""" + from __future__ import annotations import socket diff --git a/homeassistant/components/nobo_hub/select.py b/homeassistant/components/nobo_hub/select.py index 2708dd75ffe..43f177dd7a0 100644 --- a/homeassistant/components/nobo_hub/select.py +++ b/homeassistant/components/nobo_hub/select.py @@ -1,4 +1,5 @@ """Python Control of Nobø Hub - Nobø Energy Control.""" + from __future__ import annotations from pynobo import nobo diff --git a/homeassistant/components/nobo_hub/sensor.py b/homeassistant/components/nobo_hub/sensor.py index 3446f1ea43b..1632b6ba5e7 100644 --- a/homeassistant/components/nobo_hub/sensor.py +++ b/homeassistant/components/nobo_hub/sensor.py @@ -1,4 +1,5 @@ """Python Control of Nobø Hub - Nobø Energy Control.""" + from __future__ import annotations from pynobo import nobo diff --git a/homeassistant/components/norway_air/air_quality.py b/homeassistant/components/norway_air/air_quality.py index 1a3d3661a15..c16df860751 100644 --- a/homeassistant/components/norway_air/air_quality.py +++ b/homeassistant/components/norway_air/air_quality.py @@ -1,4 +1,5 @@ """Sensor for checking the air quality forecast around Norway.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index e9e61527884..2aa110649f2 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -1,4 +1,5 @@ """Provides functionality to notify people.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/notify/legacy.py b/homeassistant/components/notify/legacy.py index 7c78bfc44d3..664e3a6d10d 100644 --- a/homeassistant/components/notify/legacy.py +++ b/homeassistant/components/notify/legacy.py @@ -1,4 +1,5 @@ """Handle legacy notification platforms.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/notify_events/notify.py b/homeassistant/components/notify_events/notify.py index 3eb1bbac9c9..bfe0e4a2a57 100644 --- a/homeassistant/components/notify_events/notify.py +++ b/homeassistant/components/notify_events/notify.py @@ -1,4 +1,5 @@ """Notify.Events platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index 3ed2c7bdb93..77236ca6e0c 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -1,4 +1,5 @@ """Support for Notion.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/notion/binary_sensor.py b/homeassistant/components/notion/binary_sensor.py index dfa6dc5ec06..0c56e4fc33e 100644 --- a/homeassistant/components/notion/binary_sensor.py +++ b/homeassistant/components/notion/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Notion binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/notion/config_flow.py b/homeassistant/components/notion/config_flow.py index d9464d50a83..9a65f922fd9 100644 --- a/homeassistant/components/notion/config_flow.py +++ b/homeassistant/components/notion/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Notion integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/notion/diagnostics.py b/homeassistant/components/notion/diagnostics.py index 5c32f235639..77cf9e2a90f 100644 --- a/homeassistant/components/notion/diagnostics.py +++ b/homeassistant/components/notion/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Notion.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/notion/model.py b/homeassistant/components/notion/model.py index 059ea551b09..541ca245329 100644 --- a/homeassistant/components/notion/model.py +++ b/homeassistant/components/notion/model.py @@ -1,4 +1,5 @@ """Define Notion model mixins.""" + from dataclasses import dataclass from aionotion.listener.models import ListenerKind diff --git a/homeassistant/components/notion/sensor.py b/homeassistant/components/notion/sensor.py index d42a7ea194d..9bb8e3b4bbf 100644 --- a/homeassistant/components/notion/sensor.py +++ b/homeassistant/components/notion/sensor.py @@ -1,4 +1,5 @@ """Support for Notion sensors.""" + from dataclasses import dataclass from aionotion.listener.models import ListenerKind diff --git a/homeassistant/components/notion/util.py b/homeassistant/components/notion/util.py index 553199b7c7a..b155249268a 100644 --- a/homeassistant/components/notion/util.py +++ b/homeassistant/components/notion/util.py @@ -1,4 +1,5 @@ """Define notion utilities.""" + from aionotion import ( async_get_client_with_credentials as cwc, async_get_client_with_refresh_token as cwrt, diff --git a/homeassistant/components/nsw_fuel_station/__init__.py b/homeassistant/components/nsw_fuel_station/__init__.py index 818656779a3..76dc9d4c6ff 100644 --- a/homeassistant/components/nsw_fuel_station/__init__.py +++ b/homeassistant/components/nsw_fuel_station/__init__.py @@ -1,4 +1,5 @@ """The nsw_fuel_station component.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 7106b487786..7f28a9d28f2 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -1,4 +1,5 @@ """Sensor platform to display the current fuel prices at a NSW fuel station.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py index 28e056e29fb..24bae7f7b12 100644 --- a/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py +++ b/homeassistant/components/nsw_rural_fire_service_feed/geo_location.py @@ -1,4 +1,5 @@ """Support for NSW Rural Fire Service Feeds.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/nuheat/__init__.py b/homeassistant/components/nuheat/__init__.py index 86574920cd0..c8accd6ab73 100644 --- a/homeassistant/components/nuheat/__init__.py +++ b/homeassistant/components/nuheat/__init__.py @@ -1,4 +1,5 @@ """Support for NuHeat thermostats.""" + from datetime import timedelta from http import HTTPStatus import logging diff --git a/homeassistant/components/nuheat/config_flow.py b/homeassistant/components/nuheat/config_flow.py index 96610139a85..a75b65abccd 100644 --- a/homeassistant/components/nuheat/config_flow.py +++ b/homeassistant/components/nuheat/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NuHeat integration.""" + from http import HTTPStatus import logging diff --git a/homeassistant/components/nuheat/const.py b/homeassistant/components/nuheat/const.py index ea43c33d9b0..96e7cdc2d60 100644 --- a/homeassistant/components/nuheat/const.py +++ b/homeassistant/components/nuheat/const.py @@ -1,4 +1,5 @@ """Constants for NuHeat thermostats.""" + from homeassistant.const import Platform DOMAIN = "nuheat" diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index 0ea75590ee3..cbd7af3ecec 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -1,4 +1,5 @@ """The nuki component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nuki/binary_sensor.py b/homeassistant/components/nuki/binary_sensor.py index ac2ce24f2b4..f2a98599e27 100644 --- a/homeassistant/components/nuki/binary_sensor.py +++ b/homeassistant/components/nuki/binary_sensor.py @@ -1,4 +1,5 @@ """Doorsensor Support for the Nuki Lock.""" + from __future__ import annotations from pynuki.constants import STATE_DOORSENSOR_OPENED diff --git a/homeassistant/components/nuki/config_flow.py b/homeassistant/components/nuki/config_flow.py index a29ff75580a..4a3e96f68a5 100644 --- a/homeassistant/components/nuki/config_flow.py +++ b/homeassistant/components/nuki/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Nuki integration.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/nuki/helpers.py b/homeassistant/components/nuki/helpers.py index 1ba8e393f54..9ea4621168c 100644 --- a/homeassistant/components/nuki/helpers.py +++ b/homeassistant/components/nuki/helpers.py @@ -1,4 +1,5 @@ """nuki integration helpers.""" + from homeassistant import exceptions diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 974ffbd4d85..d63bfaf6757 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -1,4 +1,5 @@ """Nuki.io lock platform.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/nuki/sensor.py b/homeassistant/components/nuki/sensor.py index 69d166e4ee5..6647eff5c83 100644 --- a/homeassistant/components/nuki/sensor.py +++ b/homeassistant/components/nuki/sensor.py @@ -1,4 +1,5 @@ """Battery sensor for the Nuki Lock.""" + from __future__ import annotations from pynuki.device import NukiDevice diff --git a/homeassistant/components/numato/binary_sensor.py b/homeassistant/components/numato/binary_sensor.py index 98e71b9fc2d..ad81164d4fc 100644 --- a/homeassistant/components/numato/binary_sensor.py +++ b/homeassistant/components/numato/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform integration for Numato USB GPIO expanders.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/numato/sensor.py b/homeassistant/components/numato/sensor.py index 44adb78e6a0..6efc3f6160f 100644 --- a/homeassistant/components/numato/sensor.py +++ b/homeassistant/components/numato/sensor.py @@ -1,4 +1,5 @@ """Sensor platform integration for ADC ports of Numato USB GPIO expanders.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/numato/switch.py b/homeassistant/components/numato/switch.py index 92fc7e0e2df..37d1229e0b2 100644 --- a/homeassistant/components/numato/switch.py +++ b/homeassistant/components/numato/switch.py @@ -1,4 +1,5 @@ """Switch platform integration for Numato USB GPIO expanders.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/number/__init__.py b/homeassistant/components/number/__init__.py index c95381d09c2..5d21109f3fe 100644 --- a/homeassistant/components/number/__init__.py +++ b/homeassistant/components/number/__init__.py @@ -1,4 +1,5 @@ """Component to allow numeric input for platforms.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index 071f480f766..89829adcc50 100644 --- a/homeassistant/components/number/const.py +++ b/homeassistant/components/number/const.py @@ -1,4 +1,5 @@ """Provides the constants needed for the component.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/number/device_action.py b/homeassistant/components/number/device_action.py index 22a51d85ad9..8882bb22a0d 100644 --- a/homeassistant/components/number/device_action.py +++ b/homeassistant/components/number/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Number.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/number/reproduce_state.py b/homeassistant/components/number/reproduce_state.py index caed64a3d9e..e92573fb40e 100644 --- a/homeassistant/components/number/reproduce_state.py +++ b/homeassistant/components/number/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce a Number entity state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/number/significant_change.py b/homeassistant/components/number/significant_change.py index 11bca6457f1..ff77f25e527 100644 --- a/homeassistant/components/number/significant_change.py +++ b/homeassistant/components/number/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Number state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/number/websocket_api.py b/homeassistant/components/number/websocket_api.py index 1ca61fd158f..5c8730c9eaa 100644 --- a/homeassistant/components/number/websocket_api.py +++ b/homeassistant/components/number/websocket_api.py @@ -1,4 +1,5 @@ """The sensor websocket API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 8b0d8fe4640..d5a2453f62f 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -1,4 +1,5 @@ """The nut component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 782266a460a..5967aef4dac 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Network UPS Tools (NUT) integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index 13951a44d90..9be06de1f73 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -1,4 +1,5 @@ """The nut component.""" + from __future__ import annotations from homeassistant.const import Platform diff --git a/homeassistant/components/nut/device_action.py b/homeassistant/components/nut/device_action.py index 4898d9cc82d..bf16b3664f1 100644 --- a/homeassistant/components/nut/device_action.py +++ b/homeassistant/components/nut/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Network UPS Tools (NUT).""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/nut/diagnostics.py b/homeassistant/components/nut/diagnostics.py index 9ee430a655b..88a05e461c9 100644 --- a/homeassistant/components/nut/diagnostics.py +++ b/homeassistant/components/nut/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Nut.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index e8290f5fa6d..cd5ae64901d 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -1,4 +1,5 @@ """Provides a sensor to track various status aspects of a UPS.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index da54f3b119e..34157769b97 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -1,4 +1,5 @@ """The National Weather Service integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/nws/config_flow.py b/homeassistant/components/nws/config_flow.py index a6e14500ed2..37d5bb5bf82 100644 --- a/homeassistant/components/nws/config_flow.py +++ b/homeassistant/components/nws/config_flow.py @@ -1,4 +1,5 @@ """Config flow for National Weather Service (NWS) integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nws/const.py b/homeassistant/components/nws/const.py index 1e028649d89..3de874b5c10 100644 --- a/homeassistant/components/nws/const.py +++ b/homeassistant/components/nws/const.py @@ -1,4 +1,5 @@ """Constants for National Weather Service Integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nws/sensor.py b/homeassistant/components/nws/sensor.py index 35fb6c0ec1f..1d8c5ab045e 100644 --- a/homeassistant/components/nws/sensor.py +++ b/homeassistant/components/nws/sensor.py @@ -1,4 +1,5 @@ """Sensors for National Weather Service (NWS).""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 9d41e54ccd0..025a3640ec6 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -1,4 +1,5 @@ """Support for NWS weather service.""" + from __future__ import annotations from types import MappingProxyType diff --git a/homeassistant/components/nx584/alarm_control_panel.py b/homeassistant/components/nx584/alarm_control_panel.py index 3eaaf07ad1c..d29ac0388ca 100644 --- a/homeassistant/components/nx584/alarm_control_panel.py +++ b/homeassistant/components/nx584/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for NX584 alarm control panels.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py index ca55ea25c40..627051a4d65 100644 --- a/homeassistant/components/nx584/binary_sensor.py +++ b/homeassistant/components/nx584/binary_sensor.py @@ -1,4 +1,5 @@ """Support for exposing NX584 elements as sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index f528712d6a0..ff89d8090d6 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NZBGet.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index d76e004d720..394e1175c2f 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -1,4 +1,5 @@ """Monitor the NZBGet API.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/nzbget/switch.py b/homeassistant/components/nzbget/switch.py index 5d72cae37cf..c6505fd522d 100644 --- a/homeassistant/components/nzbget/switch.py +++ b/homeassistant/components/nzbget/switch.py @@ -1,4 +1,5 @@ """Support for NZBGet switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/oasa_telematics/sensor.py b/homeassistant/components/oasa_telematics/sensor.py index b9109645943..2a68c7ce15d 100644 --- a/homeassistant/components/oasa_telematics/sensor.py +++ b/homeassistant/components/oasa_telematics/sensor.py @@ -1,4 +1,5 @@ """Support for OASA Telematics from telematics.oasa.gr.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index 53208a1e6a1..cc70296028f 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -1,4 +1,5 @@ """Support for Obihai Sensors.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/octoprint/__init__.py b/homeassistant/components/octoprint/__init__.py index 1a96078c003..7a9f3990435 100644 --- a/homeassistant/components/octoprint/__init__.py +++ b/homeassistant/components/octoprint/__init__.py @@ -1,4 +1,5 @@ """Support for monitoring OctoPrint 3D printers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/octoprint/binary_sensor.py b/homeassistant/components/octoprint/binary_sensor.py index 0bc13f66415..10a637e5a3b 100644 --- a/homeassistant/components/octoprint/binary_sensor.py +++ b/homeassistant/components/octoprint/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring OctoPrint binary sensors.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/octoprint/button.py b/homeassistant/components/octoprint/button.py index b2c1672b3e4..b7c49df7f4c 100644 --- a/homeassistant/components/octoprint/button.py +++ b/homeassistant/components/octoprint/button.py @@ -1,4 +1,5 @@ """Support for Octoprint buttons.""" + from pyoctoprintapi import OctoprintClient, OctoprintPrinterInfo from homeassistant.components.button import ButtonEntity diff --git a/homeassistant/components/octoprint/camera.py b/homeassistant/components/octoprint/camera.py index a6955706508..c5d6f9a62e1 100644 --- a/homeassistant/components/octoprint/camera.py +++ b/homeassistant/components/octoprint/camera.py @@ -1,4 +1,5 @@ """Support for OctoPrint binary camera.""" + from __future__ import annotations from pyoctoprintapi import OctoprintClient, WebcamSettings diff --git a/homeassistant/components/octoprint/config_flow.py b/homeassistant/components/octoprint/config_flow.py index 94c9e940fd3..6bd592c38bf 100644 --- a/homeassistant/components/octoprint/config_flow.py +++ b/homeassistant/components/octoprint/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OctoPrint integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/octoprint/coordinator.py b/homeassistant/components/octoprint/coordinator.py index c6ce8fa66b7..ff00b6c3420 100644 --- a/homeassistant/components/octoprint/coordinator.py +++ b/homeassistant/components/octoprint/coordinator.py @@ -1,4 +1,5 @@ """The data update coordinator for OctoPrint.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index 1ea29c2b4e8..eb54cadd023 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring OctoPrint sensors.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/oem/climate.py b/homeassistant/components/oem/climate.py index 86c770ec82d..6c4b97ca450 100644 --- a/homeassistant/components/oem/climate.py +++ b/homeassistant/components/oem/climate.py @@ -1,4 +1,5 @@ """OpenEnergyMonitor Thermostat Support.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ohmconnect/sensor.py b/homeassistant/components/ohmconnect/sensor.py index 11606bfc6c2..2598e5fe514 100644 --- a/homeassistant/components/ohmconnect/sensor.py +++ b/homeassistant/components/ohmconnect/sensor.py @@ -1,4 +1,5 @@ """Support for OhmConnect.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ombi/const.py b/homeassistant/components/ombi/const.py index 59a57a480c2..6616cd9219d 100644 --- a/homeassistant/components/ombi/const.py +++ b/homeassistant/components/ombi/const.py @@ -1,4 +1,5 @@ """Support for Ombi.""" + from __future__ import annotations ATTR_SEASON = "season" diff --git a/homeassistant/components/ombi/sensor.py b/homeassistant/components/ombi/sensor.py index f534144d02c..ab9df9ad111 100644 --- a/homeassistant/components/ombi/sensor.py +++ b/homeassistant/components/ombi/sensor.py @@ -1,4 +1,5 @@ """Support for Ombi.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/omnilogic/config_flow.py b/homeassistant/components/omnilogic/config_flow.py index 76c42b19866..3f3acc3c100 100644 --- a/homeassistant/components/omnilogic/config_flow.py +++ b/homeassistant/components/omnilogic/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Omnilogic integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/omnilogic/sensor.py b/homeassistant/components/omnilogic/sensor.py index be082584308..5eb5a5dd0c4 100644 --- a/homeassistant/components/omnilogic/sensor.py +++ b/homeassistant/components/omnilogic/sensor.py @@ -1,4 +1,5 @@ """Definition and setup of the Omnilogic Sensors for Home Assistant.""" + from typing import Any from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py index 4243d05c085..61576c831bf 100644 --- a/homeassistant/components/onboarding/__init__.py +++ b/homeassistant/components/onboarding/__init__.py @@ -1,4 +1,5 @@ """Support to help onboard new users.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index 37ace1e1bab..bcc4bd26a58 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -1,4 +1,5 @@ """Onboarding views.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/oncue/__init__.py b/homeassistant/components/oncue/__init__.py index a87b2a9e02c..b3d59f50321 100644 --- a/homeassistant/components/oncue/__init__.py +++ b/homeassistant/components/oncue/__init__.py @@ -1,4 +1,5 @@ """The Oncue integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/oncue/binary_sensor.py b/homeassistant/components/oncue/binary_sensor.py index cfb088ebdb8..8adf422d656 100644 --- a/homeassistant/components/oncue/binary_sensor.py +++ b/homeassistant/components/oncue/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Oncue binary sensors.""" + from __future__ import annotations from aiooncue import OncueDevice diff --git a/homeassistant/components/oncue/config_flow.py b/homeassistant/components/oncue/config_flow.py index e56266202bd..ba672dcc588 100644 --- a/homeassistant/components/oncue/config_flow.py +++ b/homeassistant/components/oncue/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Oncue integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/oncue/entity.py b/homeassistant/components/oncue/entity.py index 6d988d4aaaf..55bd86d8912 100644 --- a/homeassistant/components/oncue/entity.py +++ b/homeassistant/components/oncue/entity.py @@ -1,4 +1,5 @@ """Support for Oncue sensors.""" + from __future__ import annotations from aiooncue import OncueDevice, OncueSensor diff --git a/homeassistant/components/oncue/sensor.py b/homeassistant/components/oncue/sensor.py index d4d45264396..f79beed38b2 100644 --- a/homeassistant/components/oncue/sensor.py +++ b/homeassistant/components/oncue/sensor.py @@ -1,4 +1,5 @@ """Support for Oncue sensors.""" + from __future__ import annotations from aiooncue import OncueDevice, OncueSensor diff --git a/homeassistant/components/ondilo_ico/api.py b/homeassistant/components/ondilo_ico/api.py index e0c6f9001c4..621750c2f58 100644 --- a/homeassistant/components/ondilo_ico/api.py +++ b/homeassistant/components/ondilo_ico/api.py @@ -1,4 +1,5 @@ """API for Ondilo ICO bound to Home Assistant OAuth.""" + from asyncio import run_coroutine_threadsafe import logging from typing import Any diff --git a/homeassistant/components/ondilo_ico/sensor.py b/homeassistant/components/ondilo_ico/sensor.py index 0a39f661b6b..17569fd784f 100644 --- a/homeassistant/components/ondilo_ico/sensor.py +++ b/homeassistant/components/ondilo_ico/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/onewire/binary_sensor.py b/homeassistant/components/onewire/binary_sensor.py index e7e30588f8a..0f98be0d892 100644 --- a/homeassistant/components/onewire/binary_sensor.py +++ b/homeassistant/components/onewire/binary_sensor.py @@ -1,4 +1,5 @@ """Support for 1-Wire binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index 11a57fdaab5..a217674e3b4 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -1,4 +1,5 @@ """Config flow for 1-Wire component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index ed744caee17..110e3da0976 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -1,4 +1,5 @@ """Constants for 1-Wire component.""" + from __future__ import annotations from homeassistant.const import Platform diff --git a/homeassistant/components/onewire/diagnostics.py b/homeassistant/components/onewire/diagnostics.py index 36db7fd5360..387553849f3 100644 --- a/homeassistant/components/onewire/diagnostics.py +++ b/homeassistant/components/onewire/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for 1-Wire.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/onewire/model.py b/homeassistant/components/onewire/model.py index 6e134fd8466..9deaca2d121 100644 --- a/homeassistant/components/onewire/model.py +++ b/homeassistant/components/onewire/model.py @@ -1,4 +1,5 @@ """Type definitions for 1-Wire integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/onewire/onewire_entities.py b/homeassistant/components/onewire/onewire_entities.py index cad55234181..03ed2dd679a 100644 --- a/homeassistant/components/onewire/onewire_entities.py +++ b/homeassistant/components/onewire/onewire_entities.py @@ -1,4 +1,5 @@ """Support for 1-Wire entities.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/onewire/onewirehub.py b/homeassistant/components/onewire/onewirehub.py index d0e2a0f1706..b01cc6ba3d6 100644 --- a/homeassistant/components/onewire/onewirehub.py +++ b/homeassistant/components/onewire/onewirehub.py @@ -1,4 +1,5 @@ """Hub for communication with 1-Wire server or mount_dir.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index a7d199c21a9..2a0f348aa17 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -1,4 +1,5 @@ """Support for 1-Wire environment sensors.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index 00a3f8f65f4..87e049a575d 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -1,4 +1,5 @@ """Support for 1-Wire environment switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 4d6d0f6965d..ef0105bd6d2 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -1,4 +1,5 @@ """Support for Onkyo Receivers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/onvif/base.py b/homeassistant/components/onvif/base.py index 5f8a7d978d1..c9900106256 100644 --- a/homeassistant/components/onvif/base.py +++ b/homeassistant/components/onvif/base.py @@ -1,4 +1,5 @@ """Base classes for ONVIF entities.""" + from __future__ import annotations from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo diff --git a/homeassistant/components/onvif/binary_sensor.py b/homeassistant/components/onvif/binary_sensor.py index 3676e3b6c27..4aa4d81e055 100644 --- a/homeassistant/components/onvif/binary_sensor.py +++ b/homeassistant/components/onvif/binary_sensor.py @@ -1,4 +1,5 @@ """Support for ONVIF binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/onvif/button.py b/homeassistant/components/onvif/button.py index f263821a460..1e86b73fc66 100644 --- a/homeassistant/components/onvif/button.py +++ b/homeassistant/components/onvif/button.py @@ -1,4 +1,5 @@ """ONVIF Buttons.""" + from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index c6ee74c2c50..3fa41d629b2 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -1,4 +1,5 @@ """Support for ONVIF Cameras with FFmpeg as decoder.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index ba1fc1c3098..515f9cd5f68 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ONVIF.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 358cbbf5c83..2001d95e2d4 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -1,4 +1,5 @@ """ONVIF device abstraction.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/onvif/diagnostics.py b/homeassistant/components/onvif/diagnostics.py index a802aed5e80..aa2042f3321 100644 --- a/homeassistant/components/onvif/diagnostics.py +++ b/homeassistant/components/onvif/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for ONVIF.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index c5539818a1c..9dcdba628e0 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -1,4 +1,5 @@ """ONVIF event abstraction.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/onvif/models.py b/homeassistant/components/onvif/models.py index 64edc85f3d1..ad91a514e88 100644 --- a/homeassistant/components/onvif/models.py +++ b/homeassistant/components/onvif/models.py @@ -1,4 +1,5 @@ """ONVIF models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index 02899dbc1a2..721ec08cb48 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -1,4 +1,5 @@ """ONVIF event parsers.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/onvif/sensor.py b/homeassistant/components/onvif/sensor.py index 67da0ed979d..5b0c72e88dd 100644 --- a/homeassistant/components/onvif/sensor.py +++ b/homeassistant/components/onvif/sensor.py @@ -1,4 +1,5 @@ """Support for ONVIF binary sensors.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/onvif/switch.py b/homeassistant/components/onvif/switch.py index 61983ef1ab5..004872606d3 100644 --- a/homeassistant/components/onvif/switch.py +++ b/homeassistant/components/onvif/switch.py @@ -1,4 +1,5 @@ """ONVIF switches for controlling cameras.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/onvif/util.py b/homeassistant/components/onvif/util.py index 5077a65e0b0..064d9cfad5f 100644 --- a/homeassistant/components/onvif/util.py +++ b/homeassistant/components/onvif/util.py @@ -1,4 +1,5 @@ """ONVIF util.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/open_meteo/__init__.py b/homeassistant/components/open_meteo/__init__.py index 4dc6a45e16c..ac09b0f61a2 100644 --- a/homeassistant/components/open_meteo/__init__.py +++ b/homeassistant/components/open_meteo/__init__.py @@ -1,4 +1,5 @@ """Support for Open-Meteo.""" + from __future__ import annotations from open_meteo import ( diff --git a/homeassistant/components/open_meteo/config_flow.py b/homeassistant/components/open_meteo/config_flow.py index 1c5c76500b1..128e9f17f37 100644 --- a/homeassistant/components/open_meteo/config_flow.py +++ b/homeassistant/components/open_meteo/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Open-Meteo integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/open_meteo/const.py b/homeassistant/components/open_meteo/const.py index 94e27293bad..e83fad9d59f 100644 --- a/homeassistant/components/open_meteo/const.py +++ b/homeassistant/components/open_meteo/const.py @@ -1,4 +1,5 @@ """Constants for the Open-Meteo integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/open_meteo/diagnostics.py b/homeassistant/components/open_meteo/diagnostics.py index a88325066fe..0ce9f4fcf3d 100644 --- a/homeassistant/components/open_meteo/diagnostics.py +++ b/homeassistant/components/open_meteo/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Open-Meteo.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/open_meteo/weather.py b/homeassistant/components/open_meteo/weather.py index 3d66422fd60..850ce61bcb5 100644 --- a/homeassistant/components/open_meteo/weather.py +++ b/homeassistant/components/open_meteo/weather.py @@ -1,4 +1,5 @@ """Support for Open-Meteo weather.""" + from __future__ import annotations from open_meteo import Forecast as OpenMeteoForecast diff --git a/homeassistant/components/openai_conversation/__init__.py b/homeassistant/components/openai_conversation/__init__.py index b0762979ca2..07e872a0f5d 100644 --- a/homeassistant/components/openai_conversation/__init__.py +++ b/homeassistant/components/openai_conversation/__init__.py @@ -1,4 +1,5 @@ """The OpenAI Conversation integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/openai_conversation/config_flow.py b/homeassistant/components/openai_conversation/config_flow.py index a971e34ccf5..fdbbbc554df 100644 --- a/homeassistant/components/openai_conversation/config_flow.py +++ b/homeassistant/components/openai_conversation/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OpenAI Conversation integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/openalpr_cloud/image_processing.py b/homeassistant/components/openalpr_cloud/image_processing.py index 0dbebda6962..2c2807f5364 100644 --- a/homeassistant/components/openalpr_cloud/image_processing.py +++ b/homeassistant/components/openalpr_cloud/image_processing.py @@ -1,4 +1,5 @@ """Component that will help set the OpenALPR cloud for ALPR processing.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py index 89c1a16aa59..760c473243d 100644 --- a/homeassistant/components/opencv/image_processing.py +++ b/homeassistant/components/opencv/image_processing.py @@ -1,4 +1,5 @@ """Support for OpenCV classification on images.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/openerz/sensor.py b/homeassistant/components/openerz/sensor.py index 4bfe11ee264..7447f2eafe4 100644 --- a/homeassistant/components/openerz/sensor.py +++ b/homeassistant/components/openerz/sensor.py @@ -1,4 +1,5 @@ """Support for OpenERZ API for Zurich city waste disposal system.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/openevse/sensor.py b/homeassistant/components/openevse/sensor.py index aafda0d038b..b2360b13a6f 100644 --- a/homeassistant/components/openevse/sensor.py +++ b/homeassistant/components/openevse/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring an OpenEVSE Charger.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/openexchangerates/__init__.py b/homeassistant/components/openexchangerates/__init__.py index 1b6ab4e65f1..65005235c6b 100644 --- a/homeassistant/components/openexchangerates/__init__.py +++ b/homeassistant/components/openexchangerates/__init__.py @@ -1,4 +1,5 @@ """The Open Exchange Rates integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/openexchangerates/config_flow.py b/homeassistant/components/openexchangerates/config_flow.py index 4a15ed33fd8..2fc0acea78d 100644 --- a/homeassistant/components/openexchangerates/config_flow.py +++ b/homeassistant/components/openexchangerates/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Open Exchange Rates integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/openexchangerates/const.py b/homeassistant/components/openexchangerates/const.py index 146919cfe44..0dd79158ff7 100644 --- a/homeassistant/components/openexchangerates/const.py +++ b/homeassistant/components/openexchangerates/const.py @@ -1,4 +1,5 @@ """Provide common constants for Open Exchange Rates.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/openexchangerates/coordinator.py b/homeassistant/components/openexchangerates/coordinator.py index beb588c7ce6..627e0d92e32 100644 --- a/homeassistant/components/openexchangerates/coordinator.py +++ b/homeassistant/components/openexchangerates/coordinator.py @@ -1,4 +1,5 @@ """Provide an OpenExchangeRates data coordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/openexchangerates/sensor.py b/homeassistant/components/openexchangerates/sensor.py index 66baf54c16a..55ca7bd2fb9 100644 --- a/homeassistant/components/openexchangerates/sensor.py +++ b/homeassistant/components/openexchangerates/sensor.py @@ -1,4 +1,5 @@ """Support for openexchangerates.org exchange rates service.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity diff --git a/homeassistant/components/opengarage/__init__.py b/homeassistant/components/opengarage/__init__.py index 46d018ec1af..adc96ee0946 100644 --- a/homeassistant/components/opengarage/__init__.py +++ b/homeassistant/components/opengarage/__init__.py @@ -1,4 +1,5 @@ """The OpenGarage integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/opengarage/binary_sensor.py b/homeassistant/components/opengarage/binary_sensor.py index 1b40aacffe5..2eca670b990 100644 --- a/homeassistant/components/opengarage/binary_sensor.py +++ b/homeassistant/components/opengarage/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for the opengarage.io binary sensor component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/opengarage/button.py b/homeassistant/components/opengarage/button.py index 9f676919098..f3a31d1b050 100644 --- a/homeassistant/components/opengarage/button.py +++ b/homeassistant/components/opengarage/button.py @@ -1,4 +1,5 @@ """OpenGarage button.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/opengarage/config_flow.py b/homeassistant/components/opengarage/config_flow.py index 6b9a84a6a77..0b86c563783 100644 --- a/homeassistant/components/opengarage/config_flow.py +++ b/homeassistant/components/opengarage/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OpenGarage integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index 3f3f6b11acf..69338ad4b90 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -1,4 +1,5 @@ """Platform for the opengarage.io cover component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/opengarage/entity.py b/homeassistant/components/opengarage/entity.py index c8380ea9244..4bf63567fe3 100644 --- a/homeassistant/components/opengarage/entity.py +++ b/homeassistant/components/opengarage/entity.py @@ -1,4 +1,5 @@ """Entity for the opengarage.io component.""" + from __future__ import annotations from homeassistant.core import callback diff --git a/homeassistant/components/opengarage/sensor.py b/homeassistant/components/opengarage/sensor.py index c3fa3decc82..39b431157ab 100644 --- a/homeassistant/components/opengarage/sensor.py +++ b/homeassistant/components/opengarage/sensor.py @@ -1,4 +1,5 @@ """Platform for the opengarage.io sensor component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py index 4206bc72c1d..a68b9b11678 100644 --- a/homeassistant/components/openhardwaremonitor/sensor.py +++ b/homeassistant/components/openhardwaremonitor/sensor.py @@ -1,4 +1,5 @@ """Support for Open Hardware Monitor Sensor Platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index 25052824ffe..12e5ed992c2 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -1,4 +1,5 @@ """Support for Openhome Devices.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/openhome/update.py b/homeassistant/components/openhome/update.py index 6d36bccec65..bbe4fdac3b3 100644 --- a/homeassistant/components/openhome/update.py +++ b/homeassistant/components/openhome/update.py @@ -1,4 +1,5 @@ """Update entities for Linn devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/opensensemap/air_quality.py b/homeassistant/components/opensensemap/air_quality.py index 0e918103cd2..c9b4c726a59 100644 --- a/homeassistant/components/opensensemap/air_quality.py +++ b/homeassistant/components/opensensemap/air_quality.py @@ -1,4 +1,5 @@ """Support for openSenseMap Air Quality data.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/opensky/__init__.py b/homeassistant/components/opensky/__init__.py index 6e60c2ec4f1..c95dc1283a4 100644 --- a/homeassistant/components/opensky/__init__.py +++ b/homeassistant/components/opensky/__init__.py @@ -1,4 +1,5 @@ """The opensky component.""" + from __future__ import annotations from aiohttp import BasicAuth diff --git a/homeassistant/components/opensky/config_flow.py b/homeassistant/components/opensky/config_flow.py index 568082b67fd..863b6050616 100644 --- a/homeassistant/components/opensky/config_flow.py +++ b/homeassistant/components/opensky/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OpenSky integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/opensky/coordinator.py b/homeassistant/components/opensky/coordinator.py index d85924737a1..f54e01b0006 100644 --- a/homeassistant/components/opensky/coordinator.py +++ b/homeassistant/components/opensky/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the OpenSky integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/opensky/sensor.py b/homeassistant/components/opensky/sensor.py index 449f17cefbb..9d317ae3e0d 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -1,4 +1,5 @@ """Sensor for the Open Sky Network.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity, SensorStateClass diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index 0b9cd1862be..c020a82f08f 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -1,4 +1,5 @@ """Support for OpenTherm Gateway climate devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 9e2b4d76dd3..19906689b57 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -1,4 +1,5 @@ """OpenTherm Gateway config flow.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py index 82d982b2fa9..65a684de224 100644 --- a/homeassistant/components/opentherm_gw/const.py +++ b/homeassistant/components/opentherm_gw/const.py @@ -1,4 +1,5 @@ """Constants for the opentherm_gw integration.""" + from __future__ import annotations import pyotgw.vars as gw_vars diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index a9ea1946f91..b7c13ad49f1 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -1,4 +1,5 @@ """Support for UV data from openuv.io.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/openuv/binary_sensor.py b/homeassistant/components/openuv/binary_sensor.py index 7946597f3c3..da4dfc3f742 100644 --- a/homeassistant/components/openuv/binary_sensor.py +++ b/homeassistant/components/openuv/binary_sensor.py @@ -1,4 +1,5 @@ """Support for OpenUV binary sensors.""" + from homeassistant.components.binary_sensor import ( BinarySensorEntity, BinarySensorEntityDescription, diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 0e6dfcdad1e..52e369fd6df 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the OpenUV component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/openuv/coordinator.py b/homeassistant/components/openuv/coordinator.py index f82a85e19b0..32d502cb8ce 100644 --- a/homeassistant/components/openuv/coordinator.py +++ b/homeassistant/components/openuv/coordinator.py @@ -1,4 +1,5 @@ """Define an update coordinator for OpenUV.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/openuv/diagnostics.py b/homeassistant/components/openuv/diagnostics.py index 99c5f89d456..e16316d4148 100644 --- a/homeassistant/components/openuv/diagnostics.py +++ b/homeassistant/components/openuv/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for OpenUV.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/openuv/sensor.py b/homeassistant/components/openuv/sensor.py index f2eafa0dffb..a79bc410715 100644 --- a/homeassistant/components/openuv/sensor.py +++ b/homeassistant/components/openuv/sensor.py @@ -1,4 +1,5 @@ """Support for OpenUV sensors.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/openweathermap/__init__.py b/homeassistant/components/openweathermap/__init__.py index 22c97d72fa5..ad99416e448 100644 --- a/homeassistant/components/openweathermap/__init__.py +++ b/homeassistant/components/openweathermap/__init__.py @@ -1,4 +1,5 @@ """The openweathermap component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/openweathermap/config_flow.py b/homeassistant/components/openweathermap/config_flow.py index 717a5148398..cc4c71c2bd5 100644 --- a/homeassistant/components/openweathermap/config_flow.py +++ b/homeassistant/components/openweathermap/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OpenWeatherMap.""" + from __future__ import annotations from pyowm import OWM diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index d7deab21743..dbd536a2556 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -1,4 +1,5 @@ """Consts for the OpenWeatherMap.""" + from __future__ import annotations from homeassistant.components.weather import ( diff --git a/homeassistant/components/openweathermap/sensor.py b/homeassistant/components/openweathermap/sensor.py index a1e0e9d2169..16d9c3064d7 100644 --- a/homeassistant/components/openweathermap/sensor.py +++ b/homeassistant/components/openweathermap/sensor.py @@ -1,4 +1,5 @@ """Support for the OpenWeatherMap (OWM) service.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/openweathermap/weather.py b/homeassistant/components/openweathermap/weather.py index bf1ae5ca7da..063415d0762 100644 --- a/homeassistant/components/openweathermap/weather.py +++ b/homeassistant/components/openweathermap/weather.py @@ -1,4 +1,5 @@ """Support for the OpenWeatherMap (OWM) service.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/opnsense/device_tracker.py b/homeassistant/components/opnsense/device_tracker.py index 78a8315335c..7c018e20a36 100644 --- a/homeassistant/components/opnsense/device_tracker.py +++ b/homeassistant/components/opnsense/device_tracker.py @@ -1,4 +1,5 @@ """Device tracker support for OPNSense routers.""" + from __future__ import annotations from homeassistant.components.device_tracker import DeviceScanner diff --git a/homeassistant/components/opower/__init__.py b/homeassistant/components/opower/__init__.py index f4fca22c9b4..1a34d0547aa 100644 --- a/homeassistant/components/opower/__init__.py +++ b/homeassistant/components/opower/__init__.py @@ -1,4 +1,5 @@ """The Opower integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/opower/config_flow.py b/homeassistant/components/opower/config_flow.py index b21ad72d688..858d14dd832 100644 --- a/homeassistant/components/opower/config_flow.py +++ b/homeassistant/components/opower/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Opower integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/opower/coordinator.py b/homeassistant/components/opower/coordinator.py index 73c60068cd4..22ff9d951d2 100644 --- a/homeassistant/components/opower/coordinator.py +++ b/homeassistant/components/opower/coordinator.py @@ -1,4 +1,5 @@ """Coordinator to handle Opower connections.""" + from datetime import datetime, timedelta import logging import socket diff --git a/homeassistant/components/opower/sensor.py b/homeassistant/components/opower/sensor.py index 9940132dac2..1987abcc69d 100644 --- a/homeassistant/components/opower/sensor.py +++ b/homeassistant/components/opower/sensor.py @@ -1,4 +1,5 @@ """Support for Opower sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/opple/light.py b/homeassistant/components/opple/light.py index 0d42b35b83b..2fbbf6ae02a 100644 --- a/homeassistant/components/opple/light.py +++ b/homeassistant/components/opple/light.py @@ -1,4 +1,5 @@ """Support for the Opple light.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/oralb/__init__.py b/homeassistant/components/oralb/__init__.py index 23a022effef..29aa63410e5 100644 --- a/homeassistant/components/oralb/__init__.py +++ b/homeassistant/components/oralb/__init__.py @@ -1,4 +1,5 @@ """The OralB integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/oralb/config_flow.py b/homeassistant/components/oralb/config_flow.py index 41938cc0c49..ab5d919194e 100644 --- a/homeassistant/components/oralb/config_flow.py +++ b/homeassistant/components/oralb/config_flow.py @@ -1,4 +1,5 @@ """Config flow for oralb ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/oralb/device.py b/homeassistant/components/oralb/device.py index 3cc46fd27c6..0fb6b71981d 100644 --- a/homeassistant/components/oralb/device.py +++ b/homeassistant/components/oralb/device.py @@ -1,4 +1,5 @@ """Support for OralB devices.""" + from __future__ import annotations from oralb_ble import DeviceKey diff --git a/homeassistant/components/oralb/sensor.py b/homeassistant/components/oralb/sensor.py index f743122d0cb..b6e52c1284d 100644 --- a/homeassistant/components/oralb/sensor.py +++ b/homeassistant/components/oralb/sensor.py @@ -1,4 +1,5 @@ """Support for OralB sensors.""" + from __future__ import annotations from oralb_ble import OralBSensor, SensorUpdate diff --git a/homeassistant/components/oru/sensor.py b/homeassistant/components/oru/sensor.py index a971a8e461b..b1d814dd98a 100644 --- a/homeassistant/components/oru/sensor.py +++ b/homeassistant/components/oru/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/orvibo/switch.py b/homeassistant/components/orvibo/switch.py index 9e86d3787af..ece833b7036 100644 --- a/homeassistant/components/orvibo/switch.py +++ b/homeassistant/components/orvibo/switch.py @@ -1,4 +1,5 @@ """Support for Orvibo S20 Wifi Smart Switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/osoenergy/__init__.py b/homeassistant/components/osoenergy/__init__.py index f0b89eaea90..269fd6614b7 100644 --- a/homeassistant/components/osoenergy/__init__.py +++ b/homeassistant/components/osoenergy/__init__.py @@ -1,4 +1,5 @@ """Support for the OSO Energy devices and services.""" + from typing import Any, Generic, TypeVar from aiohttp.web_exceptions import HTTPException diff --git a/homeassistant/components/osoenergy/config_flow.py b/homeassistant/components/osoenergy/config_flow.py index 0591630c660..ce0932571e5 100644 --- a/homeassistant/components/osoenergy/config_flow.py +++ b/homeassistant/components/osoenergy/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for OSO Energy.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/osoenergy/water_heater.py b/homeassistant/components/osoenergy/water_heater.py index 4b2ad7c48d6..146bdaa3a60 100644 --- a/homeassistant/components/osoenergy/water_heater.py +++ b/homeassistant/components/osoenergy/water_heater.py @@ -1,4 +1,5 @@ """Support for OSO Energy water heaters.""" + from typing import Any from apyosoenergyapi.helper.const import OSOEnergyWaterHeaterData diff --git a/homeassistant/components/osramlightify/light.py b/homeassistant/components/osramlightify/light.py index 1c0a134831b..2f0ba1bccb4 100644 --- a/homeassistant/components/osramlightify/light.py +++ b/homeassistant/components/osramlightify/light.py @@ -1,4 +1,5 @@ """Support for Osram Lightify.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/otbr/__init__.py b/homeassistant/components/otbr/__init__.py index fe4cc8c1145..97c2f40eb99 100644 --- a/homeassistant/components/otbr/__init__.py +++ b/homeassistant/components/otbr/__init__.py @@ -1,4 +1,5 @@ """The Open Thread Border Router integration.""" + from __future__ import annotations import aiohttp diff --git a/homeassistant/components/otbr/config_flow.py b/homeassistant/components/otbr/config_flow.py index 98e6c18741f..9a70970bdb6 100644 --- a/homeassistant/components/otbr/config_flow.py +++ b/homeassistant/components/otbr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Open Thread Border Router integration.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/otbr/util.py b/homeassistant/components/otbr/util.py index 9c47df5eaf7..4374412b8c1 100644 --- a/homeassistant/components/otbr/util.py +++ b/homeassistant/components/otbr/util.py @@ -1,4 +1,5 @@ """Utility functions for the Open Thread Border Router integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/otp/sensor.py b/homeassistant/components/otp/sensor.py index 7c7c30df970..a9b4368d1e6 100644 --- a/homeassistant/components/otp/sensor.py +++ b/homeassistant/components/otp/sensor.py @@ -1,4 +1,5 @@ """Support for One-Time Password (OTP).""" + from __future__ import annotations import time diff --git a/homeassistant/components/ourgroceries/__init__.py b/homeassistant/components/ourgroceries/__init__.py index 472313aa315..5086a5cfc9b 100644 --- a/homeassistant/components/ourgroceries/__init__.py +++ b/homeassistant/components/ourgroceries/__init__.py @@ -1,4 +1,5 @@ """The OurGroceries integration.""" + from __future__ import annotations from aiohttp import ClientError diff --git a/homeassistant/components/ourgroceries/config_flow.py b/homeassistant/components/ourgroceries/config_flow.py index 2d95428ee59..98eae900db6 100644 --- a/homeassistant/components/ourgroceries/config_flow.py +++ b/homeassistant/components/ourgroceries/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OurGroceries integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ourgroceries/coordinator.py b/homeassistant/components/ourgroceries/coordinator.py index c583fb4d5b1..bc645b2bdb3 100644 --- a/homeassistant/components/ourgroceries/coordinator.py +++ b/homeassistant/components/ourgroceries/coordinator.py @@ -1,4 +1,5 @@ """The OurGroceries coordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/overkiz/__init__.py b/homeassistant/components/overkiz/__init__.py index 03a81f67308..ce877e15261 100644 --- a/homeassistant/components/overkiz/__init__.py +++ b/homeassistant/components/overkiz/__init__.py @@ -1,4 +1,5 @@ """The Overkiz (by Somfy) integration.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/overkiz/alarm_control_panel.py b/homeassistant/components/overkiz/alarm_control_panel.py index e2555308e34..ccb7c5d919e 100644 --- a/homeassistant/components/overkiz/alarm_control_panel.py +++ b/homeassistant/components/overkiz/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Overkiz alarm control panel.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/overkiz/binary_sensor.py b/homeassistant/components/overkiz/binary_sensor.py index 975ef4ff834..62bf88c5f9c 100644 --- a/homeassistant/components/overkiz/binary_sensor.py +++ b/homeassistant/components/overkiz/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Overkiz binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/overkiz/button.py b/homeassistant/components/overkiz/button.py index f8f33db7eed..3e0321b5055 100644 --- a/homeassistant/components/overkiz/button.py +++ b/homeassistant/components/overkiz/button.py @@ -1,4 +1,5 @@ """Support for Overkiz (virtual) buttons.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/overkiz/climate.py b/homeassistant/components/overkiz/climate.py index 2c24ca4f832..e23403c2162 100644 --- a/homeassistant/components/overkiz/climate.py +++ b/homeassistant/components/overkiz/climate.py @@ -1,4 +1,5 @@ """Support for Overkiz climate devices.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py index 72230c99a05..df997f7a68e 100644 --- a/homeassistant/components/overkiz/climate_entities/__init__.py +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -1,4 +1,5 @@ """Climate entities for the Overkiz (by Somfy) integration.""" + from enum import StrEnum, unique from pyoverkiz.enums import Protocol diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py index 2678986574d..ce9857f9d8c 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater.py @@ -1,4 +1,5 @@ """Support for Atlantic Electrical Heater.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py index 36e958fb49c..47dc75710ec 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py @@ -1,4 +1,5 @@ """Support for Atlantic Electrical Heater (With Adjustable Temperature Setpoint).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py index fefaa75a114..16568c0b1b7 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py @@ -1,4 +1,5 @@ """Support for Atlantic Electrical Towel Dryer.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py b/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py index 5876f7df4a7..698979672ea 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py @@ -1,4 +1,5 @@ """Support for AtlanticHeatRecoveryVentilation.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py index 157ec72a249..72a9182974f 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py @@ -1,4 +1,5 @@ """Support for Atlantic Pass APC Heating Control.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py index cfb92067875..7fbab821b8d 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control.py @@ -1,4 +1,5 @@ """Support for Atlantic Pass APC Zone Control.""" + from typing import cast from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py index a30cb93f287..c61c92a3c44 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py @@ -1,4 +1,5 @@ """Support for Atlantic Pass APC Heating Control.""" + from __future__ import annotations from asyncio import sleep diff --git a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_hlrrwifi.py b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_hlrrwifi.py index 9b956acd014..efdae2165a9 100644 --- a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_hlrrwifi.py +++ b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_hlrrwifi.py @@ -1,4 +1,5 @@ """Support for HitachiAirToAirHeatPump.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py index bf6bb5f95d5..c1792a8e050 100644 --- a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py +++ b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py @@ -1,4 +1,5 @@ """Support for HitachiAirToAirHeatPump.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py b/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py index f98865456e1..becb807d41a 100644 --- a/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py +++ b/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py @@ -1,4 +1,5 @@ """Support for Somfy Heating Temperature Interface.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py b/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py index 2b6840b463d..615c618dd40 100644 --- a/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py +++ b/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py @@ -1,4 +1,5 @@ """Support for Somfy Smart Thermostat.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py b/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py index 7b7493a37bb..893e4ca3d8e 100644 --- a/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py +++ b/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py @@ -1,4 +1,5 @@ """Support for ValveHeatingTemperatureInterface.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/config_flow.py b/homeassistant/components/overkiz/config_flow.py index e9abef5897a..f95f885f7ef 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Overkiz integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 0f30f64444b..59acc4ac232 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -1,4 +1,5 @@ """Constants for the Overkiz (by Somfy) integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/overkiz/coordinator.py b/homeassistant/components/overkiz/coordinator.py index 4630af8bbf8..17068d26b7c 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -1,4 +1,5 @@ """Helpers to help coordinate updates.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/overkiz/cover.py b/homeassistant/components/overkiz/cover.py index 4e741aa68e6..51d2c9f2334 100644 --- a/homeassistant/components/overkiz/cover.py +++ b/homeassistant/components/overkiz/cover.py @@ -1,4 +1,5 @@ """Support for Overkiz covers - shutters etc.""" + from pyoverkiz.enums import OverkizCommand, UIClass from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/overkiz/cover_entities/awning.py b/homeassistant/components/overkiz/cover_entities/awning.py index da940d59218..4b6e5b176a7 100644 --- a/homeassistant/components/overkiz/cover_entities/awning.py +++ b/homeassistant/components/overkiz/cover_entities/awning.py @@ -1,4 +1,5 @@ """Support for Overkiz awnings.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/cover_entities/generic_cover.py b/homeassistant/components/overkiz/cover_entities/generic_cover.py index f4a8a6a0d45..df13072524d 100644 --- a/homeassistant/components/overkiz/cover_entities/generic_cover.py +++ b/homeassistant/components/overkiz/cover_entities/generic_cover.py @@ -1,4 +1,5 @@ """Base class for Overkiz covers, shutters, awnings, etc.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/cover_entities/vertical_cover.py b/homeassistant/components/overkiz/cover_entities/vertical_cover.py index 2bc6f73103f..48ac2c838c5 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -1,4 +1,5 @@ """Support for Overkiz Vertical Covers.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/diagnostics.py b/homeassistant/components/overkiz/diagnostics.py index cb8cf6eb22f..427230b9c82 100644 --- a/homeassistant/components/overkiz/diagnostics.py +++ b/homeassistant/components/overkiz/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Overkiz.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/overkiz/entity.py b/homeassistant/components/overkiz/entity.py index 3c0170e543f..c13b2fc96ba 100644 --- a/homeassistant/components/overkiz/entity.py +++ b/homeassistant/components/overkiz/entity.py @@ -1,4 +1,5 @@ """Parent class for every Overkiz device.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/overkiz/executor.py b/homeassistant/components/overkiz/executor.py index af29dbaf523..e11dc59cc27 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -1,4 +1,5 @@ """Class for helpers and communication with the OverKiz API.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/light.py b/homeassistant/components/overkiz/light.py index bb06b645d24..18d724dd63a 100644 --- a/homeassistant/components/overkiz/light.py +++ b/homeassistant/components/overkiz/light.py @@ -1,4 +1,5 @@ """Support for Overkiz lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/lock.py b/homeassistant/components/overkiz/lock.py index 8a333652b75..2494903d076 100644 --- a/homeassistant/components/overkiz/lock.py +++ b/homeassistant/components/overkiz/lock.py @@ -1,4 +1,5 @@ """Support for Overkiz locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index b53dbb5db75..1842a6cfcd2 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -1,4 +1,5 @@ """Support for Overkiz (virtual) numbers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/overkiz/scene.py b/homeassistant/components/overkiz/scene.py index 464b19d87e6..8cbbb9dbe5d 100644 --- a/homeassistant/components/overkiz/scene.py +++ b/homeassistant/components/overkiz/scene.py @@ -1,4 +1,5 @@ """Support for Overkiz scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/overkiz/select.py b/homeassistant/components/overkiz/select.py index c225d475f63..78bcb3b3e0a 100644 --- a/homeassistant/components/overkiz/select.py +++ b/homeassistant/components/overkiz/select.py @@ -1,4 +1,5 @@ """Support for Overkiz select.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/overkiz/sensor.py b/homeassistant/components/overkiz/sensor.py index 3f1de4c381e..b8693784395 100644 --- a/homeassistant/components/overkiz/sensor.py +++ b/homeassistant/components/overkiz/sensor.py @@ -1,4 +1,5 @@ """Support for Overkiz sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/overkiz/siren.py b/homeassistant/components/overkiz/siren.py index f60eb04cfd3..a7ba41e2fef 100644 --- a/homeassistant/components/overkiz/siren.py +++ b/homeassistant/components/overkiz/siren.py @@ -1,4 +1,5 @@ """Support for Overkiz sirens.""" + from typing import Any from pyoverkiz.enums import OverkizState diff --git a/homeassistant/components/overkiz/switch.py b/homeassistant/components/overkiz/switch.py index 0396e385a3c..185c20824eb 100644 --- a/homeassistant/components/overkiz/switch.py +++ b/homeassistant/components/overkiz/switch.py @@ -1,4 +1,5 @@ """Support for Overkiz switches.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/overkiz/water_heater.py b/homeassistant/components/overkiz/water_heater.py index e22f442c266..c76f6d5099f 100644 --- a/homeassistant/components/overkiz/water_heater.py +++ b/homeassistant/components/overkiz/water_heater.py @@ -1,4 +1,5 @@ """Support for Overkiz water heater devices.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/overkiz/water_heater_entities/__init__.py b/homeassistant/components/overkiz/water_heater_entities/__init__.py index 71b66f6ea93..6f6539ef659 100644 --- a/homeassistant/components/overkiz/water_heater_entities/__init__.py +++ b/homeassistant/components/overkiz/water_heater_entities/__init__.py @@ -1,4 +1,5 @@ """Water heater entities for the Overkiz (by Somfy) integration.""" + from pyoverkiz.enums.ui import UIWidget from .atlantic_pass_apc_dhw import AtlanticPassAPCDHW diff --git a/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py b/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py index 2285e2dc3d2..abd3f40adc2 100644 --- a/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py +++ b/homeassistant/components/overkiz/water_heater_entities/domestic_hot_water_production.py @@ -1,4 +1,5 @@ """Support for DomesticHotWaterProduction.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/overkiz/water_heater_entities/hitachi_dhw.py b/homeassistant/components/overkiz/water_heater_entities/hitachi_dhw.py index 0dd5b70feb6..9f0a8798233 100644 --- a/homeassistant/components/overkiz/water_heater_entities/hitachi_dhw.py +++ b/homeassistant/components/overkiz/water_heater_entities/hitachi_dhw.py @@ -1,4 +1,5 @@ """Support for Hitachi DHW.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ovo_energy/__init__.py b/homeassistant/components/ovo_energy/__init__.py index 3e2e868728d..6ad39ad82cb 100644 --- a/homeassistant/components/ovo_energy/__init__.py +++ b/homeassistant/components/ovo_energy/__init__.py @@ -1,4 +1,5 @@ """Support for OVO Energy.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ovo_energy/config_flow.py b/homeassistant/components/ovo_energy/config_flow.py index 8070dfaac6e..41c64913764 100644 --- a/homeassistant/components/ovo_energy/config_flow.py +++ b/homeassistant/components/ovo_energy/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the OVO Energy integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index 059ced8477e..76c084b368f 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -1,4 +1,5 @@ """Support for OVO Energy sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index 1b3d67ce7b4..dbc4df293c0 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -1,4 +1,5 @@ """Support for OwnTracks.""" + from collections import defaultdict import json import logging diff --git a/homeassistant/components/owntracks/device_tracker.py b/homeassistant/components/owntracks/device_tracker.py index e2053868cb9..8471f734196 100644 --- a/homeassistant/components/owntracks/device_tracker.py +++ b/homeassistant/components/owntracks/device_tracker.py @@ -1,4 +1,5 @@ """Device tracker platform that adds support for OwnTracks over MQTT.""" + from homeassistant.components.device_tracker import ( ATTR_SOURCE_TYPE, DOMAIN, diff --git a/homeassistant/components/p1_monitor/__init__.py b/homeassistant/components/p1_monitor/__init__.py index 18c58525097..201e76d4a76 100644 --- a/homeassistant/components/p1_monitor/__init__.py +++ b/homeassistant/components/p1_monitor/__init__.py @@ -1,4 +1,5 @@ """The P1 Monitor integration.""" + from __future__ import annotations from typing import TypedDict diff --git a/homeassistant/components/p1_monitor/config_flow.py b/homeassistant/components/p1_monitor/config_flow.py index 230cd57d614..9c039d06b94 100644 --- a/homeassistant/components/p1_monitor/config_flow.py +++ b/homeassistant/components/p1_monitor/config_flow.py @@ -1,4 +1,5 @@ """Config flow for P1 Monitor integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/p1_monitor/const.py b/homeassistant/components/p1_monitor/const.py index 045301d38c4..297a06a9629 100644 --- a/homeassistant/components/p1_monitor/const.py +++ b/homeassistant/components/p1_monitor/const.py @@ -1,4 +1,5 @@ """Constants for the P1 Monitor integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/p1_monitor/diagnostics.py b/homeassistant/components/p1_monitor/diagnostics.py index b2668f060a4..b1b3bd2a506 100644 --- a/homeassistant/components/p1_monitor/diagnostics.py +++ b/homeassistant/components/p1_monitor/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for P1 Monitor.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/p1_monitor/sensor.py b/homeassistant/components/p1_monitor/sensor.py index 4108194aced..b97383bdae5 100644 --- a/homeassistant/components/p1_monitor/sensor.py +++ b/homeassistant/components/p1_monitor/sensor.py @@ -1,4 +1,5 @@ """Support for P1 Monitor sensors.""" + from __future__ import annotations from typing import Literal diff --git a/homeassistant/components/panasonic_bluray/media_player.py b/homeassistant/components/panasonic_bluray/media_player.py index ae017e7d72c..a121da93486 100644 --- a/homeassistant/components/panasonic_bluray/media_player.py +++ b/homeassistant/components/panasonic_bluray/media_player.py @@ -1,4 +1,5 @@ """Support for Panasonic Blu-ray players.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 6504aad7509..7d224c7126f 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -1,4 +1,5 @@ """The Panasonic Viera integration.""" + from functools import partial import logging from urllib.error import HTTPError, URLError diff --git a/homeassistant/components/panasonic_viera/config_flow.py b/homeassistant/components/panasonic_viera/config_flow.py index 35719f4cbfd..c06de119244 100644 --- a/homeassistant/components/panasonic_viera/config_flow.py +++ b/homeassistant/components/panasonic_viera/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Panasonic Viera TV integration.""" + from functools import partial import logging from urllib.error import URLError diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py index 9e7fe4168ab..44063022129 100644 --- a/homeassistant/components/panasonic_viera/media_player.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -1,4 +1,5 @@ """Media player support for Panasonic Viera TV.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/panasonic_viera/remote.py b/homeassistant/components/panasonic_viera/remote.py index b0512ededce..c47dce36306 100644 --- a/homeassistant/components/panasonic_viera/remote.py +++ b/homeassistant/components/panasonic_viera/remote.py @@ -1,4 +1,5 @@ """Remote control support for Panasonic Viera TV.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/pandora/media_player.py b/homeassistant/components/pandora/media_player.py index 7b09f40c3f1..eb6815959c2 100644 --- a/homeassistant/components/pandora/media_player.py +++ b/homeassistant/components/pandora/media_player.py @@ -1,4 +1,5 @@ """Component for controlling Pandora stations through the pianobar client.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/panel_custom/__init__.py b/homeassistant/components/panel_custom/__init__.py index 4f084d5900a..89ad6066f48 100644 --- a/homeassistant/components/panel_custom/__init__.py +++ b/homeassistant/components/panel_custom/__init__.py @@ -1,4 +1,5 @@ """Register a custom front end panel.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/peco/__init__.py b/homeassistant/components/peco/__init__.py index 52e988f0f60..168b045ff4d 100644 --- a/homeassistant/components/peco/__init__.py +++ b/homeassistant/components/peco/__init__.py @@ -1,4 +1,5 @@ """The PECO Outage Counter integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/peco/binary_sensor.py b/homeassistant/components/peco/binary_sensor.py index 7f0402b207f..a55f0fcc731 100644 --- a/homeassistant/components/peco/binary_sensor.py +++ b/homeassistant/components/peco/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor for PECO outage counter.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/peco/config_flow.py b/homeassistant/components/peco/config_flow.py index 6fd4bfc0fe4..a5e8f4451fd 100644 --- a/homeassistant/components/peco/config_flow.py +++ b/homeassistant/components/peco/config_flow.py @@ -1,4 +1,5 @@ """Config flow for PECO Outage Counter integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/peco/sensor.py b/homeassistant/components/peco/sensor.py index ed6540b9eb1..60a3a07212d 100644 --- a/homeassistant/components/peco/sensor.py +++ b/homeassistant/components/peco/sensor.py @@ -1,4 +1,5 @@ """Sensor component for PECO outage counter.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/pegel_online/__init__.py b/homeassistant/components/pegel_online/__init__.py index e9e0e9d6aae..38b952293e0 100644 --- a/homeassistant/components/pegel_online/__init__.py +++ b/homeassistant/components/pegel_online/__init__.py @@ -1,4 +1,5 @@ """The PEGELONLINE component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pegel_online/config_flow.py b/homeassistant/components/pegel_online/config_flow.py index b0ce4cc3f38..440d1fbddf9 100644 --- a/homeassistant/components/pegel_online/config_flow.py +++ b/homeassistant/components/pegel_online/config_flow.py @@ -1,4 +1,5 @@ """Config flow for PEGELONLINE.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pegel_online/const.py b/homeassistant/components/pegel_online/const.py index 1e6c26a057b..ae7ef1f032a 100644 --- a/homeassistant/components/pegel_online/const.py +++ b/homeassistant/components/pegel_online/const.py @@ -1,4 +1,5 @@ """Constants for PEGELONLINE.""" + from datetime import timedelta DOMAIN = "pegel_online" diff --git a/homeassistant/components/pegel_online/entity.py b/homeassistant/components/pegel_online/entity.py index e9c4ebdb909..4ad12f12913 100644 --- a/homeassistant/components/pegel_online/entity.py +++ b/homeassistant/components/pegel_online/entity.py @@ -1,4 +1,5 @@ """The PEGELONLINE base entity.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/pegel_online/sensor.py b/homeassistant/components/pegel_online/sensor.py index 657baf29c9f..b2b922f3d23 100644 --- a/homeassistant/components/pegel_online/sensor.py +++ b/homeassistant/components/pegel_online/sensor.py @@ -1,4 +1,5 @@ """PEGELONLINE sensor entities.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/pencom/switch.py b/homeassistant/components/pencom/switch.py index 064ac43e6b8..a1ec25a58e9 100644 --- a/homeassistant/components/pencom/switch.py +++ b/homeassistant/components/pencom/switch.py @@ -1,4 +1,5 @@ """Pencom relay control.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/permobil/__init__.py b/homeassistant/components/permobil/__init__.py index 18025220852..675a803ce91 100644 --- a/homeassistant/components/permobil/__init__.py +++ b/homeassistant/components/permobil/__init__.py @@ -1,4 +1,5 @@ """The MyPermobil integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/permobil/binary_sensor.py b/homeassistant/components/permobil/binary_sensor.py index afa17bde04f..4b768cf5af5 100644 --- a/homeassistant/components/permobil/binary_sensor.py +++ b/homeassistant/components/permobil/binary_sensor.py @@ -1,4 +1,5 @@ """Platform for binary sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/permobil/config_flow.py b/homeassistant/components/permobil/config_flow.py index d70365a9ca1..cb47640e55f 100644 --- a/homeassistant/components/permobil/config_flow.py +++ b/homeassistant/components/permobil/config_flow.py @@ -1,4 +1,5 @@ """Config flow for MyPermobil integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/permobil/sensor.py b/homeassistant/components/permobil/sensor.py index 6b783f3a385..ce9cf906f37 100644 --- a/homeassistant/components/permobil/sensor.py +++ b/homeassistant/components/permobil/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 6d6fb7bfbd6..25e5385cc0d 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -1,4 +1,5 @@ """Support for displaying persistent notifications.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/persistent_notification/trigger.py b/homeassistant/components/persistent_notification/trigger.py index 4c9c2bd9204..431443d9139 100644 --- a/homeassistant/components/persistent_notification/trigger.py +++ b/homeassistant/components/persistent_notification/trigger.py @@ -1,4 +1,5 @@ """Offer persistent_notifications triggered automation rules.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 2075c3fc713..b2511a3cf30 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -1,4 +1,5 @@ """Support for tracking people.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/person/significant_change.py b/homeassistant/components/person/significant_change.py index 680b9194144..c6720bcc4ff 100644 --- a/homeassistant/components/person/significant_change.py +++ b/homeassistant/components/person/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Person state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/philips_js/__init__.py b/homeassistant/components/philips_js/__init__.py index c8540a187da..e56d1cdc651 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -1,4 +1,5 @@ """The Philips TV integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/philips_js/binary_sensor.py b/homeassistant/components/philips_js/binary_sensor.py index 678ba9d98f1..d1004cfd00b 100644 --- a/homeassistant/components/philips_js/binary_sensor.py +++ b/homeassistant/components/philips_js/binary_sensor.py @@ -1,4 +1,5 @@ """Philips TV binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/philips_js/config_flow.py b/homeassistant/components/philips_js/config_flow.py index fd308427ef4..ed0fce05f46 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Philips TV integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/philips_js/device_trigger.py b/homeassistant/components/philips_js/device_trigger.py index bdf47674bc8..4c2ec9b95db 100644 --- a/homeassistant/components/philips_js/device_trigger.py +++ b/homeassistant/components/philips_js/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for control of device.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/philips_js/diagnostics.py b/homeassistant/components/philips_js/diagnostics.py index 889b8e47e3f..34cc71c9b94 100644 --- a/homeassistant/components/philips_js/diagnostics.py +++ b/homeassistant/components/philips_js/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Philips JS.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/philips_js/entity.py b/homeassistant/components/philips_js/entity.py index c2645974f49..e0d97f940d0 100644 --- a/homeassistant/components/philips_js/entity.py +++ b/homeassistant/components/philips_js/entity.py @@ -1,4 +1,5 @@ """Base Philips js entity.""" + from __future__ import annotations from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/philips_js/light.py b/homeassistant/components/philips_js/light.py index 7803938cf7e..6a91b872913 100644 --- a/homeassistant/components/philips_js/light.py +++ b/homeassistant/components/philips_js/light.py @@ -1,4 +1,5 @@ """Component to integrate ambilight for TVs exposing the Joint Space API.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index 6ee70b03d54..c8b89d57854 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -1,4 +1,5 @@ """Media Player component to integrate TVs exposing the Joint Space API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/philips_js/remote.py b/homeassistant/components/philips_js/remote.py index c5b24089809..5972724c54b 100644 --- a/homeassistant/components/philips_js/remote.py +++ b/homeassistant/components/philips_js/remote.py @@ -1,4 +1,5 @@ """Remote control support for Apple TV.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/philips_js/switch.py b/homeassistant/components/philips_js/switch.py index 71548c8238e..697e7f2f060 100644 --- a/homeassistant/components/philips_js/switch.py +++ b/homeassistant/components/philips_js/switch.py @@ -1,4 +1,5 @@ """Philips TV menu switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index c57b969ce9c..f892114b26c 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -1,4 +1,5 @@ """The pi_hole component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pi_hole/binary_sensor.py b/homeassistant/components/pi_hole/binary_sensor.py index 42a52d07656..4c49cf3a98d 100644 --- a/homeassistant/components/pi_hole/binary_sensor.py +++ b/homeassistant/components/pi_hole/binary_sensor.py @@ -1,4 +1,5 @@ """Support for getting status from a Pi-hole system.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/pi_hole/config_flow.py b/homeassistant/components/pi_hole/config_flow.py index 446191ed0ae..d6f42d57deb 100644 --- a/homeassistant/components/pi_hole/config_flow.py +++ b/homeassistant/components/pi_hole/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Pi-hole integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/pi_hole/const.py b/homeassistant/components/pi_hole/const.py index 0114a6621b5..b6c97bc6118 100644 --- a/homeassistant/components/pi_hole/const.py +++ b/homeassistant/components/pi_hole/const.py @@ -1,4 +1,5 @@ """Constants for the pi_hole integration.""" + from datetime import timedelta DOMAIN = "pi_hole" diff --git a/homeassistant/components/pi_hole/diagnostics.py b/homeassistant/components/pi_hole/diagnostics.py index 8b3c32b0ac2..46efebaf475 100644 --- a/homeassistant/components/pi_hole/diagnostics.py +++ b/homeassistant/components/pi_hole/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for the Pi-hole integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pi_hole/sensor.py b/homeassistant/components/pi_hole/sensor.py index 9584c23af38..a62252d10c1 100644 --- a/homeassistant/components/pi_hole/sensor.py +++ b/homeassistant/components/pi_hole/sensor.py @@ -1,4 +1,5 @@ """Support for getting statistical data from a Pi-hole system.""" + from __future__ import annotations from hole import Hole diff --git a/homeassistant/components/pi_hole/switch.py b/homeassistant/components/pi_hole/switch.py index dc699beb26b..963ee7c9738 100644 --- a/homeassistant/components/pi_hole/switch.py +++ b/homeassistant/components/pi_hole/switch.py @@ -1,4 +1,5 @@ """Support for turning on and off Pi-hole system.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pi_hole/update.py b/homeassistant/components/pi_hole/update.py index b559a1cf806..75d4f91f2be 100644 --- a/homeassistant/components/pi_hole/update.py +++ b/homeassistant/components/pi_hole/update.py @@ -1,4 +1,5 @@ """Support for update entities of a Pi-hole system.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/picnic/config_flow.py b/homeassistant/components/picnic/config_flow.py index f28b4dfed4d..1900f39306d 100644 --- a/homeassistant/components/picnic/config_flow.py +++ b/homeassistant/components/picnic/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Picnic integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/picnic/const.py b/homeassistant/components/picnic/const.py index 851df6f41b2..4e8eafd8912 100644 --- a/homeassistant/components/picnic/const.py +++ b/homeassistant/components/picnic/const.py @@ -1,4 +1,5 @@ """Constants for the Picnic integration.""" + from __future__ import annotations DOMAIN = "picnic" diff --git a/homeassistant/components/picnic/sensor.py b/homeassistant/components/picnic/sensor.py index 56d2d22cf29..9127d82ba8f 100644 --- a/homeassistant/components/picnic/sensor.py +++ b/homeassistant/components/picnic/sensor.py @@ -1,4 +1,5 @@ """Definition of Picnic sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/picnic/services.py b/homeassistant/components/picnic/services.py index 03b7576703d..f820daee54b 100644 --- a/homeassistant/components/picnic/services.py +++ b/homeassistant/components/picnic/services.py @@ -1,4 +1,5 @@ """Services for the Picnic integration.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/picnic/todo.py b/homeassistant/components/picnic/todo.py index fea99f7403d..2992c016148 100644 --- a/homeassistant/components/picnic/todo.py +++ b/homeassistant/components/picnic/todo.py @@ -1,4 +1,5 @@ """Definition of Picnic shopping cart.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pilight/__init__.py b/homeassistant/components/pilight/__init__.py index f4d2e539b6a..1f1eee0c92a 100644 --- a/homeassistant/components/pilight/__init__.py +++ b/homeassistant/components/pilight/__init__.py @@ -1,4 +1,5 @@ """Component to create an interface to a Pilight daemon.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/pilight/binary_sensor.py b/homeassistant/components/pilight/binary_sensor.py index 303c755a035..0ddb2de4603 100644 --- a/homeassistant/components/pilight/binary_sensor.py +++ b/homeassistant/components/pilight/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Pilight binary sensors.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/pilight/light.py b/homeassistant/components/pilight/light.py index bcb2197db19..60713b59475 100644 --- a/homeassistant/components/pilight/light.py +++ b/homeassistant/components/pilight/light.py @@ -1,4 +1,5 @@ """Support for switching devices via Pilight to on and off.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pilight/sensor.py b/homeassistant/components/pilight/sensor.py index 21036a94210..003e3428bdd 100644 --- a/homeassistant/components/pilight/sensor.py +++ b/homeassistant/components/pilight/sensor.py @@ -1,4 +1,5 @@ """Support for Pilight sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pilight/switch.py b/homeassistant/components/pilight/switch.py index 75d286afad2..0d0023d9cd6 100644 --- a/homeassistant/components/pilight/switch.py +++ b/homeassistant/components/pilight/switch.py @@ -1,4 +1,5 @@ """Support for switching devices via Pilight to on and off.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/ping/__init__.py b/homeassistant/components/ping/__init__.py index 9aaf8ffbd99..e75b36dc38d 100644 --- a/homeassistant/components/ping/__init__.py +++ b/homeassistant/components/ping/__init__.py @@ -1,4 +1,5 @@ """The ping component.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 5c1435cc3b1..35d4e218dce 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -1,4 +1,5 @@ """Tracks the latency of a host by sending ICMP echo requests (ping).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ping/config_flow.py b/homeassistant/components/ping/config_flow.py index 4163f710f2e..52600c379c4 100644 --- a/homeassistant/components/ping/config_flow.py +++ b/homeassistant/components/ping/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ping (ICMP) integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/ping/coordinator.py b/homeassistant/components/ping/coordinator.py index 57e212ed69d..38ab2e79ffc 100644 --- a/homeassistant/components/ping/coordinator.py +++ b/homeassistant/components/ping/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the ping integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ping/device_tracker.py b/homeassistant/components/ping/device_tracker.py index 6b904043b30..b202c1c406e 100644 --- a/homeassistant/components/ping/device_tracker.py +++ b/homeassistant/components/ping/device_tracker.py @@ -1,4 +1,5 @@ """Tracks devices by sending a ICMP echo request (ping).""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/ping/entity.py b/homeassistant/components/ping/entity.py index 8854634e898..34207b284bb 100644 --- a/homeassistant/components/ping/entity.py +++ b/homeassistant/components/ping/entity.py @@ -1,4 +1,5 @@ """Base entity for the Ping component.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.core import DOMAIN from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/ping/sensor.py b/homeassistant/components/ping/sensor.py index f5e5de81aac..135087f4b5b 100644 --- a/homeassistant/components/ping/sensor.py +++ b/homeassistant/components/ping/sensor.py @@ -1,4 +1,5 @@ """Sensor platform that for Ping integration.""" + from collections.abc import Callable from dataclasses import dataclass diff --git a/homeassistant/components/pioneer/media_player.py b/homeassistant/components/pioneer/media_player.py index 741d2b580e4..15cd3cbf303 100644 --- a/homeassistant/components/pioneer/media_player.py +++ b/homeassistant/components/pioneer/media_player.py @@ -1,4 +1,5 @@ """Support for Pioneer Network Receivers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pjlink/media_player.py b/homeassistant/components/pjlink/media_player.py index 1a7ff877bb8..ff3be3266a0 100644 --- a/homeassistant/components/pjlink/media_player.py +++ b/homeassistant/components/pjlink/media_player.py @@ -1,4 +1,5 @@ """Support for controlling projector via the PJLink protocol.""" + from __future__ import annotations from pypjlink import MUTE_AUDIO, Projector diff --git a/homeassistant/components/plaato/__init__.py b/homeassistant/components/plaato/__init__.py index 69c65383138..0d8678d95ef 100644 --- a/homeassistant/components/plaato/__init__.py +++ b/homeassistant/components/plaato/__init__.py @@ -1,4 +1,5 @@ """Support for Plaato devices.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/plaato/binary_sensor.py b/homeassistant/components/plaato/binary_sensor.py index e8fbaa5d6f1..42019bbec9b 100644 --- a/homeassistant/components/plaato/binary_sensor.py +++ b/homeassistant/components/plaato/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Plaato Airlock sensors.""" + from __future__ import annotations from pyplaato.plaato import PlaatoKeg diff --git a/homeassistant/components/plaato/config_flow.py b/homeassistant/components/plaato/config_flow.py index df99f604146..1240abc5e81 100644 --- a/homeassistant/components/plaato/config_flow.py +++ b/homeassistant/components/plaato/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Plaato.""" + from __future__ import annotations from pyplaato.plaato import PlaatoDeviceType diff --git a/homeassistant/components/plaato/const.py b/homeassistant/components/plaato/const.py index 6825baff906..73382765bfe 100644 --- a/homeassistant/components/plaato/const.py +++ b/homeassistant/components/plaato/const.py @@ -1,4 +1,5 @@ """Const for Plaato.""" + from datetime import timedelta from homeassistant.const import Platform diff --git a/homeassistant/components/plaato/entity.py b/homeassistant/components/plaato/entity.py index b7650567c2b..d4c4622a998 100644 --- a/homeassistant/components/plaato/entity.py +++ b/homeassistant/components/plaato/entity.py @@ -1,4 +1,5 @@ """PlaatoEntity class.""" + from pyplaato.models.device import PlaatoDevice from homeassistant.helpers import entity diff --git a/homeassistant/components/plaato/sensor.py b/homeassistant/components/plaato/sensor.py index f3d9a5c3e41..7aa30dd2fe0 100644 --- a/homeassistant/components/plaato/sensor.py +++ b/homeassistant/components/plaato/sensor.py @@ -1,4 +1,5 @@ """Support for Plaato Airlock sensors.""" + from __future__ import annotations from pyplaato.models.device import PlaatoDevice diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index 28cdece0b02..5d79f8d303a 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -1,4 +1,5 @@ """Support for monitoring plants.""" + from collections import deque from contextlib import suppress from datetime import datetime, timedelta diff --git a/homeassistant/components/plant/const.py b/homeassistant/components/plant/const.py index 0368c55e152..656705b3d12 100644 --- a/homeassistant/components/plant/const.py +++ b/homeassistant/components/plant/const.py @@ -1,4 +1,5 @@ """Const for Plant.""" + from typing import Final DOMAIN: Final = "plant" diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 4ce5a359dcd..4e17e4032aa 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -1,4 +1,5 @@ """Support to embed Plex.""" + from functools import partial import logging diff --git a/homeassistant/components/plex/button.py b/homeassistant/components/plex/button.py index 24bc09bac42..8bb34be38ce 100644 --- a/homeassistant/components/plex/button.py +++ b/homeassistant/components/plex/button.py @@ -1,4 +1,5 @@ """Representation of Plex buttons.""" + from __future__ import annotations from homeassistant.components.button import ButtonEntity diff --git a/homeassistant/components/plex/cast.py b/homeassistant/components/plex/cast.py index 7dc112b72de..bf68be20292 100644 --- a/homeassistant/components/plex/cast.py +++ b/homeassistant/components/plex/cast.py @@ -1,4 +1,5 @@ """Google Cast support for the Plex component.""" + from __future__ import annotations from pychromecast import Chromecast diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index 1bc72b9e639..301716e14d5 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Plex.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index 30b59c73994..8dc75a447af 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -1,4 +1,5 @@ """Constants for the Plex component.""" + from datetime import timedelta from typing import Final diff --git a/homeassistant/components/plex/errors.py b/homeassistant/components/plex/errors.py index ddbc1a2ea40..c1e1a186b9e 100644 --- a/homeassistant/components/plex/errors.py +++ b/homeassistant/components/plex/errors.py @@ -1,4 +1,5 @@ """Errors for the Plex component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/plex/helpers.py b/homeassistant/components/plex/helpers.py index 6a334c5ff61..f51350ac597 100644 --- a/homeassistant/components/plex/helpers.py +++ b/homeassistant/components/plex/helpers.py @@ -1,4 +1,5 @@ """Helper methods for common Plex integration operations.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index d3a0cc0fb2e..e5bbb9284ff 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -1,4 +1,5 @@ """Support to interface with the Plex API.""" + from __future__ import annotations from yarl import URL diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 3e817b4ea1a..21e52171fe8 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -1,4 +1,5 @@ """Support to interface with the Plex API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/plex/media_search.py b/homeassistant/components/plex/media_search.py index a853bac33a5..bd785a08907 100644 --- a/homeassistant/components/plex/media_search.py +++ b/homeassistant/components/plex/media_search.py @@ -1,4 +1,5 @@ """Helper methods to search for Plex media.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 350e0a2dce2..74fab3b1714 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -1,4 +1,5 @@ """Support for Plex media server monitoring.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 1c3c944c9c4..bd2aa7063dd 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -1,4 +1,5 @@ """Shared class to maintain Plex server instances.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/plex/view.py b/homeassistant/components/plex/view.py index 49c0af1c48e..c1254a9795a 100644 --- a/homeassistant/components/plex/view.py +++ b/homeassistant/components/plex/view.py @@ -1,4 +1,5 @@ """Implement a view to provide proxied Plex thumbnails to the media browser.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py index bfae7772b93..28389ffa357 100644 --- a/homeassistant/components/plugwise/__init__.py +++ b/homeassistant/components/plugwise/__init__.py @@ -1,4 +1,5 @@ """Plugwise platform for Home Assistant Core.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index c362652cf47..d32ae94160f 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -1,4 +1,5 @@ """Plugwise Binary Sensor component for Home Assistant.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 3553df02e8d..7820c86a242 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,4 +1,5 @@ """Plugwise Climate component for Home Assistant.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 48a932ad466..4c33e51788f 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Plugwise integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index cad891f16f2..975ddae346a 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -1,4 +1,5 @@ """Constants for Plugwise component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/plugwise/coordinator.py b/homeassistant/components/plugwise/coordinator.py index 395ec4e6e63..15a0e8c4821 100644 --- a/homeassistant/components/plugwise/coordinator.py +++ b/homeassistant/components/plugwise/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for Plugwise.""" + from datetime import timedelta from plugwise import PlugwiseData, Smile diff --git a/homeassistant/components/plugwise/diagnostics.py b/homeassistant/components/plugwise/diagnostics.py index ef54efbe96d..44c0fa9a1da 100644 --- a/homeassistant/components/plugwise/diagnostics.py +++ b/homeassistant/components/plugwise/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Plugwise.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/plugwise/entity.py b/homeassistant/components/plugwise/entity.py index 1c9149fad72..b2562ef8f39 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -1,4 +1,5 @@ """Generic Plugwise Entity Class.""" + from __future__ import annotations from plugwise.constants import DeviceData diff --git a/homeassistant/components/plugwise/number.py b/homeassistant/components/plugwise/number.py index c71b52cf5c8..95d09bd845e 100644 --- a/homeassistant/components/plugwise/number.py +++ b/homeassistant/components/plugwise/number.py @@ -1,4 +1,5 @@ """Number platform for Plugwise integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/plugwise/select.py b/homeassistant/components/plugwise/select.py index ff5eb3af4a5..21b0794a1e3 100644 --- a/homeassistant/components/plugwise/select.py +++ b/homeassistant/components/plugwise/select.py @@ -1,4 +1,5 @@ """Plugwise Select component for Home Assistant.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py index 86992bb08f1..2dfe97a06c5 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -1,4 +1,5 @@ """Plugwise Sensor component for Home Assistant.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 50e0a3cc4f8..3c737e19a4a 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -1,4 +1,5 @@ """Plugwise Switch component for HomeAssistant.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/plugwise/util.py b/homeassistant/components/plugwise/util.py index 4f8d4c8d8fe..df1069cbbc3 100644 --- a/homeassistant/components/plugwise/util.py +++ b/homeassistant/components/plugwise/util.py @@ -1,4 +1,5 @@ """Utilities for Plugwise.""" + from collections.abc import Awaitable, Callable, Coroutine from typing import Any, Concatenate, ParamSpec, TypeVar diff --git a/homeassistant/components/plum_lightpad/config_flow.py b/homeassistant/components/plum_lightpad/config_flow.py index 546398f5fa6..2a929d14c9e 100644 --- a/homeassistant/components/plum_lightpad/config_flow.py +++ b/homeassistant/components/plum_lightpad/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Plum Lightpad.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index 6d591d29fe4..a385565b837 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -1,4 +1,5 @@ """Support for Plum Lightpad lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pocketcasts/sensor.py b/homeassistant/components/pocketcasts/sensor.py index c541e2cc0f2..b2ad050fc14 100644 --- a/homeassistant/components/pocketcasts/sensor.py +++ b/homeassistant/components/pocketcasts/sensor.py @@ -1,4 +1,5 @@ """Support for Pocket Casts.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/point/alarm_control_panel.py b/homeassistant/components/point/alarm_control_panel.py index c2a904ec2a1..b04742af06a 100644 --- a/homeassistant/components/point/alarm_control_panel.py +++ b/homeassistant/components/point/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Minut Point.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index 81101d2da79..8863ee8ed81 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Minut Point binary sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/point/const.py b/homeassistant/components/point/const.py index c21971185f9..c8c8f14d019 100644 --- a/homeassistant/components/point/const.py +++ b/homeassistant/components/point/const.py @@ -1,4 +1,5 @@ """Define constants for the Point component.""" + from datetime import timedelta DOMAIN = "point" diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 471fa72c6c5..101371b0822 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -1,4 +1,5 @@ """Support for Minut Point sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/poolsense/binary_sensor.py b/homeassistant/components/poolsense/binary_sensor.py index 052a205a37b..69c133c8c1e 100644 --- a/homeassistant/components/poolsense/binary_sensor.py +++ b/homeassistant/components/poolsense/binary_sensor.py @@ -1,4 +1,5 @@ """Support for PoolSense binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/poolsense/entity.py b/homeassistant/components/poolsense/entity.py index 0eca39cc48d..eaf2c4ab540 100644 --- a/homeassistant/components/poolsense/entity.py +++ b/homeassistant/components/poolsense/entity.py @@ -1,4 +1,5 @@ """Base entity for poolsense integration.""" + from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/poolsense/sensor.py b/homeassistant/components/poolsense/sensor.py index 23235c09a84..d40ee823664 100644 --- a/homeassistant/components/poolsense/sensor.py +++ b/homeassistant/components/poolsense/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for the PoolSense sensor.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 086e49eef94..5257e5a6299 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -1,4 +1,5 @@ """The Tesla Powerwall integration.""" + from __future__ import annotations from contextlib import AsyncExitStack diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 767c4f1120d..7629d83d9d6 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tesla Powerwall integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index c20ab760f23..bb3a6c2355e 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -1,4 +1,5 @@ """Constants for the Tesla Powerwall integration.""" + from typing import Final DOMAIN = "powerwall" diff --git a/homeassistant/components/powerwall/models.py b/homeassistant/components/powerwall/models.py index 3216b83a7db..1adfd754650 100644 --- a/homeassistant/components/powerwall/models.py +++ b/homeassistant/components/powerwall/models.py @@ -1,4 +1,5 @@ """The powerwall integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index 9e17cd32e9c..38189ecd6f3 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -1,4 +1,5 @@ """Support for powerwall sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/private_ble_device/__init__.py b/homeassistant/components/private_ble_device/__init__.py index dcb6555bbc9..ab4de9ef04d 100644 --- a/homeassistant/components/private_ble_device/__init__.py +++ b/homeassistant/components/private_ble_device/__init__.py @@ -1,4 +1,5 @@ """Private BLE Device integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/private_ble_device/config_flow.py b/homeassistant/components/private_ble_device/config_flow.py index ff6fade1141..c7311e8691b 100644 --- a/homeassistant/components/private_ble_device/config_flow.py +++ b/homeassistant/components/private_ble_device/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the BLE Tracker.""" + from __future__ import annotations import base64 diff --git a/homeassistant/components/private_ble_device/coordinator.py b/homeassistant/components/private_ble_device/coordinator.py index e41c3d02e9e..69db399a454 100644 --- a/homeassistant/components/private_ble_device/coordinator.py +++ b/homeassistant/components/private_ble_device/coordinator.py @@ -1,4 +1,5 @@ """Central manager for tracking devices with random but resolvable MAC addresses.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/private_ble_device/device_tracker.py b/homeassistant/components/private_ble_device/device_tracker.py index 4f22c9b275c..fbaf0d44751 100644 --- a/homeassistant/components/private_ble_device/device_tracker.py +++ b/homeassistant/components/private_ble_device/device_tracker.py @@ -1,4 +1,5 @@ """Tracking for bluetooth low energy devices.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/private_ble_device/entity.py b/homeassistant/components/private_ble_device/entity.py index 978313e9671..2c574805c53 100644 --- a/homeassistant/components/private_ble_device/entity.py +++ b/homeassistant/components/private_ble_device/entity.py @@ -1,4 +1,5 @@ """Tracking for bluetooth low energy devices.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/private_ble_device/sensor.py b/homeassistant/components/private_ble_device/sensor.py index 1cff006794c..d186563da5e 100644 --- a/homeassistant/components/private_ble_device/sensor.py +++ b/homeassistant/components/private_ble_device/sensor.py @@ -1,4 +1,5 @@ """Support for iBeacon device sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/profiler/config_flow.py b/homeassistant/components/profiler/config_flow.py index bb25e7f7e57..4acce51e25f 100644 --- a/homeassistant/components/profiler/config_flow.py +++ b/homeassistant/components/profiler/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Profiler integration.""" + from homeassistant.config_entries import ConfigFlow from .const import DEFAULT_NAME, DOMAIN diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py index 797fd751197..23ccc03a038 100644 --- a/homeassistant/components/proliphix/climate.py +++ b/homeassistant/components/proliphix/climate.py @@ -1,4 +1,5 @@ """Support for Proliphix NT10e Thermostats.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 317b5c569d4..2b8ed6f22b0 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -1,4 +1,5 @@ """Support for Prometheus metrics export.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/prosegur/alarm_control_panel.py b/homeassistant/components/prosegur/alarm_control_panel.py index 77cdb5e11a2..61e7c73e3a5 100644 --- a/homeassistant/components/prosegur/alarm_control_panel.py +++ b/homeassistant/components/prosegur/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Prosegur alarm control panels.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/prosegur/camera.py b/homeassistant/components/prosegur/camera.py index c711ca2eac6..fd911fa5898 100644 --- a/homeassistant/components/prosegur/camera.py +++ b/homeassistant/components/prosegur/camera.py @@ -1,4 +1,5 @@ """Support for Prosegur cameras.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/prosegur/config_flow.py b/homeassistant/components/prosegur/config_flow.py index a1f3dac832b..ca8e4db35cc 100644 --- a/homeassistant/components/prosegur/config_flow.py +++ b/homeassistant/components/prosegur/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Prosegur Alarm integration.""" + from collections.abc import Mapping import logging from typing import Any, cast diff --git a/homeassistant/components/prosegur/diagnostics.py b/homeassistant/components/prosegur/diagnostics.py index 59b51f5b5d1..ec13f5511a4 100644 --- a/homeassistant/components/prosegur/diagnostics.py +++ b/homeassistant/components/prosegur/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Prosegur.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/prowl/notify.py b/homeassistant/components/prowl/notify.py index c365ce151ec..b5556c15c6c 100644 --- a/homeassistant/components/prowl/notify.py +++ b/homeassistant/components/prowl/notify.py @@ -1,4 +1,5 @@ """Prowl notification service.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 349658223f3..f6c67fc088f 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -1,4 +1,5 @@ """Support for tracking the proximity of a device.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/proximity/config_flow.py b/homeassistant/components/proximity/config_flow.py index 77ebf26f52a..14f26f5d45d 100644 --- a/homeassistant/components/proximity/config_flow.py +++ b/homeassistant/components/proximity/config_flow.py @@ -1,4 +1,5 @@ """Config flow for proximity.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/proximity/diagnostics.py b/homeassistant/components/proximity/diagnostics.py index 3ccecbe1f19..d296c489e94 100644 --- a/homeassistant/components/proximity/diagnostics.py +++ b/homeassistant/components/proximity/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Proximity.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/proximity/helpers.py b/homeassistant/components/proximity/helpers.py index 9c0787538e5..af3d6d2a3bb 100644 --- a/homeassistant/components/proximity/helpers.py +++ b/homeassistant/components/proximity/helpers.py @@ -1,4 +1,5 @@ """Helper functions for proximity.""" + from homeassistant.components.automation import automations_with_entity from homeassistant.components.script import scripts_with_entity from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/proxmoxve/__init__.py b/homeassistant/components/proxmoxve/__init__.py index 1c22ca50c23..e0b8f91088d 100644 --- a/homeassistant/components/proxmoxve/__init__.py +++ b/homeassistant/components/proxmoxve/__init__.py @@ -1,4 +1,5 @@ """Support for Proxmox VE.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/proxmoxve/binary_sensor.py b/homeassistant/components/proxmoxve/binary_sensor.py index ea02e547e98..412f40af6e8 100644 --- a/homeassistant/components/proxmoxve/binary_sensor.py +++ b/homeassistant/components/proxmoxve/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor to read Proxmox VE data.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index 79e9faf8d70..c50dc819f9a 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -1,4 +1,5 @@ """Proxy camera platform that enables image processing of camera data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/prusalink/__init__.py b/homeassistant/components/prusalink/__init__.py index 08670ef5433..e8c930c3157 100644 --- a/homeassistant/components/prusalink/__init__.py +++ b/homeassistant/components/prusalink/__init__.py @@ -1,4 +1,5 @@ """The PrusaLink integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/prusalink/button.py b/homeassistant/components/prusalink/button.py index 46d10fc9703..d70356f04d1 100644 --- a/homeassistant/components/prusalink/button.py +++ b/homeassistant/components/prusalink/button.py @@ -1,4 +1,5 @@ """PrusaLink sensors.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/prusalink/camera.py b/homeassistant/components/prusalink/camera.py index 7f6fab0583b..48c2fee9cdb 100644 --- a/homeassistant/components/prusalink/camera.py +++ b/homeassistant/components/prusalink/camera.py @@ -1,4 +1,5 @@ """Camera entity for PrusaLink.""" + from __future__ import annotations from homeassistant.components.camera import Camera diff --git a/homeassistant/components/prusalink/config_flow.py b/homeassistant/components/prusalink/config_flow.py index ac4dba0d20c..c0c8c797133 100644 --- a/homeassistant/components/prusalink/config_flow.py +++ b/homeassistant/components/prusalink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for PrusaLink integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/prusalink/sensor.py b/homeassistant/components/prusalink/sensor.py index efdd044399a..be4230b844c 100644 --- a/homeassistant/components/prusalink/sensor.py +++ b/homeassistant/components/prusalink/sensor.py @@ -1,4 +1,5 @@ """PrusaLink sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/ps4/config_flow.py b/homeassistant/components/ps4/config_flow.py index ef74b3e8712..b842c2f7cfb 100644 --- a/homeassistant/components/ps4/config_flow.py +++ b/homeassistant/components/ps4/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for PlayStation 4.""" + from collections import OrderedDict from pyps4_2ndscreen.errors import CredentialTimeout diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 732d86bef37..f01bc00ba72 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -1,4 +1,5 @@ """Support for PlayStation 4 consoles.""" + from contextlib import suppress import logging from typing import Any, cast diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py index 92dfb235b1b..553a1b4a283 100644 --- a/homeassistant/components/pulseaudio_loopback/switch.py +++ b/homeassistant/components/pulseaudio_loopback/switch.py @@ -1,4 +1,5 @@ """Switch logic for loading/unloading pulseaudio loopback modules.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pure_energie/__init__.py b/homeassistant/components/pure_energie/__init__.py index cda73a7da0b..e018648e95e 100644 --- a/homeassistant/components/pure_energie/__init__.py +++ b/homeassistant/components/pure_energie/__init__.py @@ -1,4 +1,5 @@ """The Pure Energie integration.""" + from __future__ import annotations from typing import NamedTuple diff --git a/homeassistant/components/pure_energie/config_flow.py b/homeassistant/components/pure_energie/config_flow.py index b7dc2552231..a2bbb671ff7 100644 --- a/homeassistant/components/pure_energie/config_flow.py +++ b/homeassistant/components/pure_energie/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Pure Energie integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pure_energie/const.py b/homeassistant/components/pure_energie/const.py index 9c908da6068..bba7708c174 100644 --- a/homeassistant/components/pure_energie/const.py +++ b/homeassistant/components/pure_energie/const.py @@ -1,4 +1,5 @@ """Constants for the Pure Energie integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/pure_energie/diagnostics.py b/homeassistant/components/pure_energie/diagnostics.py index b2e071d58cd..fb93b81a4fd 100644 --- a/homeassistant/components/pure_energie/diagnostics.py +++ b/homeassistant/components/pure_energie/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Pure Energie.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/pure_energie/sensor.py b/homeassistant/components/pure_energie/sensor.py index 09470609c9e..fa3bed72df4 100644 --- a/homeassistant/components/pure_energie/sensor.py +++ b/homeassistant/components/pure_energie/sensor.py @@ -1,4 +1,5 @@ """Support for Pure Energie sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/purpleair/__init__.py b/homeassistant/components/purpleair/__init__.py index f52d0799d35..fb86612597a 100644 --- a/homeassistant/components/purpleair/__init__.py +++ b/homeassistant/components/purpleair/__init__.py @@ -1,4 +1,5 @@ """The PurpleAir integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/purpleair/config_flow.py b/homeassistant/components/purpleair/config_flow.py index 90a250a57b0..f0c09af5135 100644 --- a/homeassistant/components/purpleair/config_flow.py +++ b/homeassistant/components/purpleair/config_flow.py @@ -1,4 +1,5 @@ """Config flow for PurpleAir integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/purpleair/coordinator.py b/homeassistant/components/purpleair/coordinator.py index 9982db667f2..7bf0770c6fc 100644 --- a/homeassistant/components/purpleair/coordinator.py +++ b/homeassistant/components/purpleair/coordinator.py @@ -1,4 +1,5 @@ """Define a PurpleAir DataUpdateCoordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/purpleair/diagnostics.py b/homeassistant/components/purpleair/diagnostics.py index 0c52879b7a7..a3b3af857fb 100644 --- a/homeassistant/components/purpleair/diagnostics.py +++ b/homeassistant/components/purpleair/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for PurpleAir.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/purpleair/sensor.py b/homeassistant/components/purpleair/sensor.py index eee4583ca11..d1db77c2c31 100644 --- a/homeassistant/components/purpleair/sensor.py +++ b/homeassistant/components/purpleair/sensor.py @@ -1,4 +1,5 @@ """Support for PurpleAir sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/push/camera.py b/homeassistant/components/push/camera.py index 2cedcb8598a..8744ce8c2a1 100644 --- a/homeassistant/components/push/camera.py +++ b/homeassistant/components/push/camera.py @@ -1,4 +1,5 @@ """Camera platform that receives images through HTTP POST.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/pushbullet/__init__.py b/homeassistant/components/pushbullet/__init__.py index 14d90d4ca0b..e5892afc926 100644 --- a/homeassistant/components/pushbullet/__init__.py +++ b/homeassistant/components/pushbullet/__init__.py @@ -1,4 +1,5 @@ """The pushbullet component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pushbullet/api.py b/homeassistant/components/pushbullet/api.py index 691ef7413c3..72805a9aa94 100644 --- a/homeassistant/components/pushbullet/api.py +++ b/homeassistant/components/pushbullet/api.py @@ -1,4 +1,5 @@ """Pushbullet Notification provider.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pushbullet/config_flow.py b/homeassistant/components/pushbullet/config_flow.py index 1b2ca3b6040..08ade743aee 100644 --- a/homeassistant/components/pushbullet/config_flow.py +++ b/homeassistant/components/pushbullet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for pushbullet integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pushbullet/notify.py b/homeassistant/components/pushbullet/notify.py index 662240d0bf5..96f78c4a35d 100644 --- a/homeassistant/components/pushbullet/notify.py +++ b/homeassistant/components/pushbullet/notify.py @@ -1,4 +1,5 @@ """Pushbullet platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pushbullet/sensor.py b/homeassistant/components/pushbullet/sensor.py index 2f2a1d066f3..4989fc91d5e 100644 --- a/homeassistant/components/pushbullet/sensor.py +++ b/homeassistant/components/pushbullet/sensor.py @@ -1,4 +1,5 @@ """Pushbullet platform for sensor component.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity, SensorEntityDescription diff --git a/homeassistant/components/pushover/__init__.py b/homeassistant/components/pushover/__init__.py index c3b15b7c130..73c4223889e 100644 --- a/homeassistant/components/pushover/__init__.py +++ b/homeassistant/components/pushover/__init__.py @@ -1,4 +1,5 @@ """The pushover component.""" + from __future__ import annotations from pushover_complete import BadAPIRequestError, PushoverAPI diff --git a/homeassistant/components/pushover/config_flow.py b/homeassistant/components/pushover/config_flow.py index 5f67db08073..fcc28b45ede 100644 --- a/homeassistant/components/pushover/config_flow.py +++ b/homeassistant/components/pushover/config_flow.py @@ -1,4 +1,5 @@ """Config flow for pushover integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/pushover/notify.py b/homeassistant/components/pushover/notify.py index 52eaed227e5..34ee1d08bdd 100644 --- a/homeassistant/components/pushover/notify.py +++ b/homeassistant/components/pushover/notify.py @@ -1,4 +1,5 @@ """Pushover platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/pushsafer/notify.py b/homeassistant/components/pushsafer/notify.py index 5411db05e2d..f4f5bf88a22 100644 --- a/homeassistant/components/pushsafer/notify.py +++ b/homeassistant/components/pushsafer/notify.py @@ -1,4 +1,5 @@ """Pushsafer platform for notify component.""" + from __future__ import annotations import base64 diff --git a/homeassistant/components/pvoutput/__init__.py b/homeassistant/components/pvoutput/__init__.py index bca5a23e62b..7dc02a07d1c 100644 --- a/homeassistant/components/pvoutput/__init__.py +++ b/homeassistant/components/pvoutput/__init__.py @@ -1,4 +1,5 @@ """The PVOutput integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/pvoutput/config_flow.py b/homeassistant/components/pvoutput/config_flow.py index 7056e0f00b2..9d18952e7b4 100644 --- a/homeassistant/components/pvoutput/config_flow.py +++ b/homeassistant/components/pvoutput/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the PVOutput integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/pvoutput/const.py b/homeassistant/components/pvoutput/const.py index f01d10fd13d..be63053a899 100644 --- a/homeassistant/components/pvoutput/const.py +++ b/homeassistant/components/pvoutput/const.py @@ -1,4 +1,5 @@ """Constants for the PVOutput integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/pvoutput/coordinator.py b/homeassistant/components/pvoutput/coordinator.py index 7b307f20274..5c38792c553 100644 --- a/homeassistant/components/pvoutput/coordinator.py +++ b/homeassistant/components/pvoutput/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the PVOutput integration.""" + from __future__ import annotations from pvo import PVOutput, PVOutputAuthenticationError, PVOutputNoDataError, Status diff --git a/homeassistant/components/pvoutput/diagnostics.py b/homeassistant/components/pvoutput/diagnostics.py index dfe215b7ddd..3b9007b77b4 100644 --- a/homeassistant/components/pvoutput/diagnostics.py +++ b/homeassistant/components/pvoutput/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for PVOutput.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/pvoutput/sensor.py b/homeassistant/components/pvoutput/sensor.py index c003e3cfad8..ef2bb3eb660 100644 --- a/homeassistant/components/pvoutput/sensor.py +++ b/homeassistant/components/pvoutput/sensor.py @@ -1,4 +1,5 @@ """Support for getting collected information from PVOutput.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/pvpc_hourly_pricing/__init__.py b/homeassistant/components/pvpc_hourly_pricing/__init__.py index af9154f5512..6ef16ea29b6 100644 --- a/homeassistant/components/pvpc_hourly_pricing/__init__.py +++ b/homeassistant/components/pvpc_hourly_pricing/__init__.py @@ -1,4 +1,5 @@ """The pvpc_hourly_pricing integration to collect Spain official electric prices.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/pvpc_hourly_pricing/config_flow.py b/homeassistant/components/pvpc_hourly_pricing/config_flow.py index 23c6241462c..239e1bcb0e9 100644 --- a/homeassistant/components/pvpc_hourly_pricing/config_flow.py +++ b/homeassistant/components/pvpc_hourly_pricing/config_flow.py @@ -1,4 +1,5 @@ """Config flow for pvpc_hourly_pricing.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/pvpc_hourly_pricing/const.py b/homeassistant/components/pvpc_hourly_pricing/const.py index a6bfc6f3188..9aaa46233cb 100644 --- a/homeassistant/components/pvpc_hourly_pricing/const.py +++ b/homeassistant/components/pvpc_hourly_pricing/const.py @@ -1,4 +1,5 @@ """Constant values for pvpc_hourly_pricing.""" + from aiopvpc.const import TARIFFS import voluptuous as vol diff --git a/homeassistant/components/pvpc_hourly_pricing/helpers.py b/homeassistant/components/pvpc_hourly_pricing/helpers.py index 195d20aee89..e0792f76404 100644 --- a/homeassistant/components/pvpc_hourly_pricing/helpers.py +++ b/homeassistant/components/pvpc_hourly_pricing/helpers.py @@ -1,4 +1,5 @@ """Helper functions to relate sensors keys and unique ids.""" + from aiopvpc.const import ( ALL_SENSORS, KEY_INJECTION, diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index 9cc3ef35a4b..1b97abcddae 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -1,4 +1,5 @@ """Sensor to collect the reference daily prices of electricity ('PVPC') in Spain.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/pyload/sensor.py b/homeassistant/components/pyload/sensor.py index 35e32d510ca..b7d4d1f461b 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring pyLoad.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/qbittorrent/config_flow.py b/homeassistant/components/qbittorrent/config_flow.py index 148e9765c48..c17c842529b 100644 --- a/homeassistant/components/qbittorrent/config_flow.py +++ b/homeassistant/components/qbittorrent/config_flow.py @@ -1,4 +1,5 @@ """Config flow for qBittorrent.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qbittorrent/const.py b/homeassistant/components/qbittorrent/const.py index 96c60e9b380..d8fe2c012a3 100644 --- a/homeassistant/components/qbittorrent/const.py +++ b/homeassistant/components/qbittorrent/const.py @@ -1,4 +1,5 @@ """Constants for qBittorrent.""" + from typing import Final DOMAIN: Final = "qbittorrent" diff --git a/homeassistant/components/qbittorrent/coordinator.py b/homeassistant/components/qbittorrent/coordinator.py index 11467ce62f4..32ce4cf9711 100644 --- a/homeassistant/components/qbittorrent/coordinator.py +++ b/homeassistant/components/qbittorrent/coordinator.py @@ -1,4 +1,5 @@ """The QBittorrent coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/qbittorrent/helpers.py b/homeassistant/components/qbittorrent/helpers.py index 7f7833e912a..b9c29675473 100644 --- a/homeassistant/components/qbittorrent/helpers.py +++ b/homeassistant/components/qbittorrent/helpers.py @@ -1,4 +1,5 @@ """Helper functions for qBittorrent.""" + from qbittorrent.client import Client diff --git a/homeassistant/components/qbittorrent/sensor.py b/homeassistant/components/qbittorrent/sensor.py index 9e9f7626857..84eac7d28cf 100644 --- a/homeassistant/components/qbittorrent/sensor.py +++ b/homeassistant/components/qbittorrent/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the qBittorrent API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/qingping/__init__.py b/homeassistant/components/qingping/__init__.py index 9155c02c07c..48cae5c48e6 100644 --- a/homeassistant/components/qingping/__init__.py +++ b/homeassistant/components/qingping/__init__.py @@ -1,4 +1,5 @@ """The Qingping integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qingping/binary_sensor.py b/homeassistant/components/qingping/binary_sensor.py index 99bcf83ec1a..f4f81eac394 100644 --- a/homeassistant/components/qingping/binary_sensor.py +++ b/homeassistant/components/qingping/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Qingping binary sensors.""" + from __future__ import annotations from qingping_ble import ( diff --git a/homeassistant/components/qingping/config_flow.py b/homeassistant/components/qingping/config_flow.py index d5fd45e4614..c5efe03a878 100644 --- a/homeassistant/components/qingping/config_flow.py +++ b/homeassistant/components/qingping/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Qingping integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/qingping/device.py b/homeassistant/components/qingping/device.py index ec6bb23c2af..466ac43f079 100644 --- a/homeassistant/components/qingping/device.py +++ b/homeassistant/components/qingping/device.py @@ -1,4 +1,5 @@ """Support for Qingping devices.""" + from __future__ import annotations from qingping_ble import DeviceKey diff --git a/homeassistant/components/qingping/sensor.py b/homeassistant/components/qingping/sensor.py index bc99ed80ff3..e75c9b34f49 100644 --- a/homeassistant/components/qingping/sensor.py +++ b/homeassistant/components/qingping/sensor.py @@ -1,4 +1,5 @@ """Support for Qingping sensors.""" + from __future__ import annotations from qingping_ble import ( diff --git a/homeassistant/components/qld_bushfire/geo_location.py b/homeassistant/components/qld_bushfire/geo_location.py index 3606b664a3f..5d0173f8c54 100644 --- a/homeassistant/components/qld_bushfire/geo_location.py +++ b/homeassistant/components/qld_bushfire/geo_location.py @@ -1,4 +1,5 @@ """Support for Queensland Bushfire Alert Feeds.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/qnap/__init__.py b/homeassistant/components/qnap/__init__.py index 2491e69803f..82e912a60cd 100644 --- a/homeassistant/components/qnap/__init__.py +++ b/homeassistant/components/qnap/__init__.py @@ -1,4 +1,5 @@ """The qnap component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/qnap/config_flow.py b/homeassistant/components/qnap/config_flow.py index d8646c722d7..3e0c524f59e 100644 --- a/homeassistant/components/qnap/config_flow.py +++ b/homeassistant/components/qnap/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure qnap component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qnap/coordinator.py b/homeassistant/components/qnap/coordinator.py index b868a931ebd..8c2bf81a47f 100644 --- a/homeassistant/components/qnap/coordinator.py +++ b/homeassistant/components/qnap/coordinator.py @@ -1,4 +1,5 @@ """Data coordinator for the qnap integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/qnap/sensor.py b/homeassistant/components/qnap/sensor.py index 348759012ac..2a5186ad940 100644 --- a/homeassistant/components/qnap/sensor.py +++ b/homeassistant/components/qnap/sensor.py @@ -1,4 +1,5 @@ """Support for QNAP NAS Sensors.""" + from __future__ import annotations from homeassistant import config_entries diff --git a/homeassistant/components/qnap_qsw/__init__.py b/homeassistant/components/qnap_qsw/__init__.py index b3bcc1705de..d7352435b07 100644 --- a/homeassistant/components/qnap_qsw/__init__.py +++ b/homeassistant/components/qnap_qsw/__init__.py @@ -1,4 +1,5 @@ """The QNAP QSW integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qnap_qsw/binary_sensor.py b/homeassistant/components/qnap_qsw/binary_sensor.py index f655beee3d4..907e3d927d3 100644 --- a/homeassistant/components/qnap_qsw/binary_sensor.py +++ b/homeassistant/components/qnap_qsw/binary_sensor.py @@ -1,4 +1,5 @@ """Support for the QNAP QSW binary sensors.""" + from __future__ import annotations from dataclasses import dataclass, replace diff --git a/homeassistant/components/qnap_qsw/button.py b/homeassistant/components/qnap_qsw/button.py index c2c4f9f6043..1a11a5209c0 100644 --- a/homeassistant/components/qnap_qsw/button.py +++ b/homeassistant/components/qnap_qsw/button.py @@ -1,4 +1,5 @@ """Support for the QNAP QSW buttons.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/qnap_qsw/config_flow.py b/homeassistant/components/qnap_qsw/config_flow.py index 8ade8169e57..3a10e54ac82 100644 --- a/homeassistant/components/qnap_qsw/config_flow.py +++ b/homeassistant/components/qnap_qsw/config_flow.py @@ -1,4 +1,5 @@ """Config flow for QNAP QSW.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qnap_qsw/coordinator.py b/homeassistant/components/qnap_qsw/coordinator.py index 6451b525004..c873f2a773d 100644 --- a/homeassistant/components/qnap_qsw/coordinator.py +++ b/homeassistant/components/qnap_qsw/coordinator.py @@ -1,4 +1,5 @@ """The QNAP QSW coordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/qnap_qsw/diagnostics.py b/homeassistant/components/qnap_qsw/diagnostics.py index 2467e9181a3..e732c551a40 100644 --- a/homeassistant/components/qnap_qsw/diagnostics.py +++ b/homeassistant/components/qnap_qsw/diagnostics.py @@ -1,4 +1,5 @@ """Support for the QNAP QSW diagnostics.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/qnap_qsw/entity.py b/homeassistant/components/qnap_qsw/entity.py index de92afe69a2..a3038b1fc7b 100644 --- a/homeassistant/components/qnap_qsw/entity.py +++ b/homeassistant/components/qnap_qsw/entity.py @@ -1,4 +1,5 @@ """Entity classes for the QNAP QSW integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/qnap_qsw/sensor.py b/homeassistant/components/qnap_qsw/sensor.py index 0fcb74174b5..f9299c778cb 100644 --- a/homeassistant/components/qnap_qsw/sensor.py +++ b/homeassistant/components/qnap_qsw/sensor.py @@ -1,4 +1,5 @@ """Support for the QNAP QSW sensors.""" + from __future__ import annotations from dataclasses import dataclass, replace diff --git a/homeassistant/components/qnap_qsw/update.py b/homeassistant/components/qnap_qsw/update.py index 5ea6e80f4bb..ac789235271 100644 --- a/homeassistant/components/qnap_qsw/update.py +++ b/homeassistant/components/qnap_qsw/update.py @@ -1,4 +1,5 @@ """Support for the QNAP QSW update.""" + from __future__ import annotations from typing import Any, Final diff --git a/homeassistant/components/qrcode/image_processing.py b/homeassistant/components/qrcode/image_processing.py index 06fe92e5b1d..aacd3c0c38f 100644 --- a/homeassistant/components/qrcode/image_processing.py +++ b/homeassistant/components/qrcode/image_processing.py @@ -1,4 +1,5 @@ """Support for the QR code image processing.""" + from __future__ import annotations import io diff --git a/homeassistant/components/quantum_gateway/device_tracker.py b/homeassistant/components/quantum_gateway/device_tracker.py index c8e23b68416..1c43cbb14a8 100644 --- a/homeassistant/components/quantum_gateway/device_tracker.py +++ b/homeassistant/components/quantum_gateway/device_tracker.py @@ -1,4 +1,5 @@ """Support for Verizon FiOS Quantum Gateways.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qvr_pro/camera.py b/homeassistant/components/qvr_pro/camera.py index f3caac864d9..544ef808ca7 100644 --- a/homeassistant/components/qvr_pro/camera.py +++ b/homeassistant/components/qvr_pro/camera.py @@ -1,4 +1,5 @@ """Support for QVR Pro streams.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qwikswitch/__init__.py b/homeassistant/components/qwikswitch/__init__.py index d11b71a6dd4..8c753da7cc2 100644 --- a/homeassistant/components/qwikswitch/__init__.py +++ b/homeassistant/components/qwikswitch/__init__.py @@ -1,4 +1,5 @@ """Support for Qwikswitch devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qwikswitch/binary_sensor.py b/homeassistant/components/qwikswitch/binary_sensor.py index a4ab193228c..b35908da12c 100644 --- a/homeassistant/components/qwikswitch/binary_sensor.py +++ b/homeassistant/components/qwikswitch/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Qwikswitch Binary Sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qwikswitch/light.py b/homeassistant/components/qwikswitch/light.py index 2109b8d2c3b..12c2763d3a4 100644 --- a/homeassistant/components/qwikswitch/light.py +++ b/homeassistant/components/qwikswitch/light.py @@ -1,4 +1,5 @@ """Support for Qwikswitch Relays and Dimmers.""" + from __future__ import annotations from homeassistant.components.light import ColorMode, LightEntity diff --git a/homeassistant/components/qwikswitch/sensor.py b/homeassistant/components/qwikswitch/sensor.py index 8a72ca7c811..856949d8926 100644 --- a/homeassistant/components/qwikswitch/sensor.py +++ b/homeassistant/components/qwikswitch/sensor.py @@ -1,4 +1,5 @@ """Support for Qwikswitch Sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/qwikswitch/switch.py b/homeassistant/components/qwikswitch/switch.py index e36fc60d589..1623bfb3361 100644 --- a/homeassistant/components/qwikswitch/switch.py +++ b/homeassistant/components/qwikswitch/switch.py @@ -1,4 +1,5 @@ """Support for Qwikswitch relays.""" + from __future__ import annotations from homeassistant.components.switch import SwitchEntity From a033574ee2b5f8c0777f96857f80598d4be77712 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:05:07 +0100 Subject: [PATCH 0552/1691] Add empty line after module docstring [r] (#112703) --- homeassistant/components/rabbitair/__init__.py | 1 + homeassistant/components/rabbitair/config_flow.py | 1 + homeassistant/components/rabbitair/coordinator.py | 1 + homeassistant/components/rabbitair/entity.py | 1 + homeassistant/components/rabbitair/fan.py | 1 + homeassistant/components/rachio/binary_sensor.py | 1 + homeassistant/components/rachio/config_flow.py | 1 + homeassistant/components/rachio/device.py | 1 + homeassistant/components/rachio/switch.py | 1 + homeassistant/components/rachio/webhooks.py | 1 + homeassistant/components/radarr/__init__.py | 1 + homeassistant/components/radarr/binary_sensor.py | 1 + homeassistant/components/radarr/calendar.py | 1 + homeassistant/components/radarr/config_flow.py | 1 + homeassistant/components/radarr/coordinator.py | 1 + homeassistant/components/radarr/sensor.py | 1 + homeassistant/components/radio_browser/__init__.py | 1 + homeassistant/components/radio_browser/config_flow.py | 1 + homeassistant/components/radio_browser/media_source.py | 1 + homeassistant/components/radiotherm/__init__.py | 1 + homeassistant/components/radiotherm/climate.py | 1 + homeassistant/components/radiotherm/config_flow.py | 1 + homeassistant/components/radiotherm/coordinator.py | 1 + homeassistant/components/radiotherm/data.py | 1 + homeassistant/components/radiotherm/switch.py | 1 + homeassistant/components/radiotherm/util.py | 1 + homeassistant/components/rainbird/__init__.py | 1 + homeassistant/components/rainbird/binary_sensor.py | 1 + homeassistant/components/rainbird/number.py | 1 + homeassistant/components/rainbird/sensor.py | 1 + homeassistant/components/rainbird/switch.py | 1 + homeassistant/components/raincloud/__init__.py | 1 + homeassistant/components/raincloud/binary_sensor.py | 1 + homeassistant/components/raincloud/sensor.py | 1 + homeassistant/components/raincloud/switch.py | 1 + homeassistant/components/rainforest_eagle/__init__.py | 1 + homeassistant/components/rainforest_eagle/config_flow.py | 1 + homeassistant/components/rainforest_eagle/data.py | 1 + homeassistant/components/rainforest_eagle/diagnostics.py | 1 + homeassistant/components/rainforest_eagle/sensor.py | 1 + homeassistant/components/rainforest_raven/__init__.py | 1 + homeassistant/components/rainforest_raven/config_flow.py | 1 + homeassistant/components/rainforest_raven/coordinator.py | 1 + homeassistant/components/rainforest_raven/diagnostics.py | 1 + homeassistant/components/rainforest_raven/sensor.py | 1 + homeassistant/components/rainmachine/__init__.py | 1 + homeassistant/components/rainmachine/binary_sensor.py | 1 + homeassistant/components/rainmachine/button.py | 1 + homeassistant/components/rainmachine/config_flow.py | 1 + homeassistant/components/rainmachine/diagnostics.py | 1 + homeassistant/components/rainmachine/model.py | 1 + homeassistant/components/rainmachine/select.py | 1 + homeassistant/components/rainmachine/sensor.py | 1 + homeassistant/components/rainmachine/switch.py | 1 + homeassistant/components/rainmachine/update.py | 1 + homeassistant/components/rainmachine/util.py | 1 + homeassistant/components/random/__init__.py | 1 + homeassistant/components/random/binary_sensor.py | 1 + homeassistant/components/random/config_flow.py | 1 + homeassistant/components/random/sensor.py | 1 + homeassistant/components/rapt_ble/__init__.py | 1 + homeassistant/components/rapt_ble/config_flow.py | 1 + homeassistant/components/rapt_ble/sensor.py | 1 + homeassistant/components/raspberry_pi/__init__.py | 1 + homeassistant/components/raspberry_pi/config_flow.py | 1 + homeassistant/components/raspberry_pi/hardware.py | 1 + homeassistant/components/raspyrfm/switch.py | 1 + homeassistant/components/rdw/__init__.py | 1 + homeassistant/components/rdw/binary_sensor.py | 1 + homeassistant/components/rdw/config_flow.py | 1 + homeassistant/components/rdw/const.py | 1 + homeassistant/components/rdw/diagnostics.py | 1 + homeassistant/components/rdw/sensor.py | 1 + homeassistant/components/recollect_waste/__init__.py | 1 + homeassistant/components/recollect_waste/calendar.py | 1 + homeassistant/components/recollect_waste/config_flow.py | 1 + homeassistant/components/recollect_waste/diagnostics.py | 1 + homeassistant/components/recollect_waste/entity.py | 1 + homeassistant/components/recollect_waste/sensor.py | 1 + homeassistant/components/recollect_waste/util.py | 1 + homeassistant/components/recorder/__init__.py | 1 + homeassistant/components/recorder/auto_repairs/events/schema.py | 1 + homeassistant/components/recorder/auto_repairs/schema.py | 1 + homeassistant/components/recorder/auto_repairs/states/schema.py | 1 + .../components/recorder/auto_repairs/statistics/duplicates.py | 1 + .../components/recorder/auto_repairs/statistics/schema.py | 1 + homeassistant/components/recorder/backup.py | 1 + homeassistant/components/recorder/core.py | 1 + homeassistant/components/recorder/db_schema.py | 1 + homeassistant/components/recorder/executor.py | 1 + homeassistant/components/recorder/filters.py | 1 + homeassistant/components/recorder/history/__init__.py | 1 + homeassistant/components/recorder/history/common.py | 1 + homeassistant/components/recorder/history/legacy.py | 1 + homeassistant/components/recorder/history/modern.py | 1 + homeassistant/components/recorder/migration.py | 1 + homeassistant/components/recorder/models/__init__.py | 1 + homeassistant/components/recorder/models/context.py | 1 + homeassistant/components/recorder/models/database.py | 1 + homeassistant/components/recorder/models/event.py | 1 + homeassistant/components/recorder/models/legacy.py | 1 + homeassistant/components/recorder/models/state.py | 1 + homeassistant/components/recorder/models/statistics.py | 1 + homeassistant/components/recorder/models/time.py | 1 + homeassistant/components/recorder/purge.py | 1 + homeassistant/components/recorder/queries.py | 1 + homeassistant/components/recorder/repack.py | 1 + homeassistant/components/recorder/services.py | 1 + homeassistant/components/recorder/statistics.py | 1 + homeassistant/components/recorder/system_health/__init__.py | 1 + homeassistant/components/recorder/system_health/mysql.py | 1 + homeassistant/components/recorder/system_health/postgresql.py | 1 + homeassistant/components/recorder/system_health/sqlite.py | 1 + homeassistant/components/recorder/table_managers/event_data.py | 1 + homeassistant/components/recorder/table_managers/event_types.py | 1 + .../components/recorder/table_managers/recorder_runs.py | 1 + .../components/recorder/table_managers/state_attributes.py | 1 + homeassistant/components/recorder/table_managers/states.py | 1 + homeassistant/components/recorder/table_managers/states_meta.py | 1 + .../components/recorder/table_managers/statistics_meta.py | 1 + homeassistant/components/recorder/tasks.py | 1 + homeassistant/components/recorder/util.py | 1 + homeassistant/components/recorder/websocket_api.py | 1 + homeassistant/components/recovery_mode/__init__.py | 1 + homeassistant/components/recswitch/switch.py | 1 + homeassistant/components/reddit/sensor.py | 1 + homeassistant/components/refoss/__init__.py | 1 + homeassistant/components/refoss/bridge.py | 1 + homeassistant/components/refoss/const.py | 1 + homeassistant/components/refoss/coordinator.py | 1 + homeassistant/components/refoss/entity.py | 1 + homeassistant/components/refoss/util.py | 1 + homeassistant/components/rejseplanen/sensor.py | 1 + homeassistant/components/remote/__init__.py | 1 + homeassistant/components/remote/device_action.py | 1 + homeassistant/components/remote/device_condition.py | 1 + homeassistant/components/remote/device_trigger.py | 1 + homeassistant/components/remote/reproduce_state.py | 1 + homeassistant/components/remote/significant_change.py | 1 + homeassistant/components/remote_rpi_gpio/__init__.py | 1 + homeassistant/components/remote_rpi_gpio/binary_sensor.py | 1 + homeassistant/components/remote_rpi_gpio/switch.py | 1 + homeassistant/components/renault/binary_sensor.py | 1 + homeassistant/components/renault/button.py | 1 + homeassistant/components/renault/config_flow.py | 1 + homeassistant/components/renault/const.py | 1 + homeassistant/components/renault/coordinator.py | 1 + homeassistant/components/renault/device_tracker.py | 1 + homeassistant/components/renault/diagnostics.py | 1 + homeassistant/components/renault/entity.py | 1 + homeassistant/components/renault/renault_hub.py | 1 + homeassistant/components/renault/renault_vehicle.py | 1 + homeassistant/components/renault/select.py | 1 + homeassistant/components/renault/sensor.py | 1 + homeassistant/components/renault/services.py | 1 + homeassistant/components/renson/__init__.py | 1 + homeassistant/components/renson/binary_sensor.py | 1 + homeassistant/components/renson/button.py | 1 + homeassistant/components/renson/config_flow.py | 1 + homeassistant/components/renson/coordinator.py | 1 + homeassistant/components/renson/entity.py | 1 + homeassistant/components/renson/fan.py | 1 + homeassistant/components/renson/number.py | 1 + homeassistant/components/renson/sensor.py | 1 + homeassistant/components/renson/switch.py | 1 + homeassistant/components/renson/time.py | 1 + homeassistant/components/reolink/binary_sensor.py | 1 + homeassistant/components/reolink/button.py | 1 + homeassistant/components/reolink/camera.py | 1 + homeassistant/components/reolink/config_flow.py | 1 + homeassistant/components/reolink/diagnostics.py | 1 + homeassistant/components/reolink/entity.py | 1 + homeassistant/components/reolink/exceptions.py | 1 + homeassistant/components/reolink/host.py | 1 + homeassistant/components/reolink/light.py | 1 + homeassistant/components/reolink/number.py | 1 + homeassistant/components/reolink/select.py | 1 + homeassistant/components/reolink/sensor.py | 1 + homeassistant/components/reolink/siren.py | 1 + homeassistant/components/reolink/switch.py | 1 + homeassistant/components/reolink/update.py | 1 + homeassistant/components/reolink/util.py | 1 + homeassistant/components/repairs/__init__.py | 1 + homeassistant/components/repairs/issue_handler.py | 1 + homeassistant/components/repairs/models.py | 1 + homeassistant/components/repairs/websocket_api.py | 1 + homeassistant/components/repetier/__init__.py | 1 + homeassistant/components/repetier/sensor.py | 1 + homeassistant/components/rest/__init__.py | 1 + homeassistant/components/rest/binary_sensor.py | 1 + homeassistant/components/rest/data.py | 1 + homeassistant/components/rest/entity.py | 1 + homeassistant/components/rest/notify.py | 1 + homeassistant/components/rest/sensor.py | 1 + homeassistant/components/rest/switch.py | 1 + homeassistant/components/rest_command/__init__.py | 1 + homeassistant/components/rflink/__init__.py | 1 + homeassistant/components/rflink/binary_sensor.py | 1 + homeassistant/components/rflink/cover.py | 1 + homeassistant/components/rflink/light.py | 1 + homeassistant/components/rflink/sensor.py | 1 + homeassistant/components/rflink/switch.py | 1 + homeassistant/components/rfxtrx/__init__.py | 1 + homeassistant/components/rfxtrx/binary_sensor.py | 1 + homeassistant/components/rfxtrx/config_flow.py | 1 + homeassistant/components/rfxtrx/cover.py | 1 + homeassistant/components/rfxtrx/device_action.py | 1 + homeassistant/components/rfxtrx/device_trigger.py | 1 + homeassistant/components/rfxtrx/diagnostics.py | 1 + homeassistant/components/rfxtrx/event.py | 1 + homeassistant/components/rfxtrx/light.py | 1 + homeassistant/components/rfxtrx/sensor.py | 1 + homeassistant/components/rfxtrx/siren.py | 1 + homeassistant/components/rfxtrx/switch.py | 1 + homeassistant/components/rhasspy/__init__.py | 1 + homeassistant/components/rhasspy/config_flow.py | 1 + homeassistant/components/ridwell/__init__.py | 1 + homeassistant/components/ridwell/calendar.py | 1 + homeassistant/components/ridwell/config_flow.py | 1 + homeassistant/components/ridwell/coordinator.py | 1 + homeassistant/components/ridwell/diagnostics.py | 1 + homeassistant/components/ridwell/entity.py | 1 + homeassistant/components/ridwell/sensor.py | 1 + homeassistant/components/ridwell/switch.py | 1 + homeassistant/components/ring/__init__.py | 1 + homeassistant/components/ring/binary_sensor.py | 1 + homeassistant/components/ring/camera.py | 1 + homeassistant/components/ring/config_flow.py | 1 + homeassistant/components/ring/const.py | 1 + homeassistant/components/ring/coordinator.py | 1 + homeassistant/components/ring/diagnostics.py | 1 + homeassistant/components/ring/entity.py | 1 + homeassistant/components/ring/light.py | 1 + homeassistant/components/ring/sensor.py | 1 + homeassistant/components/ring/switch.py | 1 + homeassistant/components/ripple/sensor.py | 1 + homeassistant/components/risco/__init__.py | 1 + homeassistant/components/risco/alarm_control_panel.py | 1 + homeassistant/components/risco/binary_sensor.py | 1 + homeassistant/components/risco/config_flow.py | 1 + homeassistant/components/risco/entity.py | 1 + homeassistant/components/risco/sensor.py | 1 + homeassistant/components/risco/switch.py | 1 + homeassistant/components/rituals_perfume_genie/binary_sensor.py | 1 + homeassistant/components/rituals_perfume_genie/config_flow.py | 1 + homeassistant/components/rituals_perfume_genie/diagnostics.py | 1 + homeassistant/components/rituals_perfume_genie/entity.py | 1 + homeassistant/components/rituals_perfume_genie/number.py | 1 + homeassistant/components/rituals_perfume_genie/select.py | 1 + homeassistant/components/rituals_perfume_genie/sensor.py | 1 + homeassistant/components/rituals_perfume_genie/switch.py | 1 + homeassistant/components/rmvtransport/sensor.py | 1 + homeassistant/components/roborock/__init__.py | 1 + homeassistant/components/roborock/binary_sensor.py | 1 + homeassistant/components/roborock/button.py | 1 + homeassistant/components/roborock/config_flow.py | 1 + homeassistant/components/roborock/const.py | 1 + homeassistant/components/roborock/coordinator.py | 1 + homeassistant/components/roborock/device.py | 1 + homeassistant/components/roborock/diagnostics.py | 1 + homeassistant/components/roborock/models.py | 1 + homeassistant/components/roborock/select.py | 1 + homeassistant/components/roborock/sensor.py | 1 + homeassistant/components/roborock/switch.py | 1 + homeassistant/components/roborock/vacuum.py | 1 + homeassistant/components/rocketchat/notify.py | 1 + homeassistant/components/roku/__init__.py | 1 + homeassistant/components/roku/binary_sensor.py | 1 + homeassistant/components/roku/browse_media.py | 1 + homeassistant/components/roku/config_flow.py | 1 + homeassistant/components/roku/coordinator.py | 1 + homeassistant/components/roku/diagnostics.py | 1 + homeassistant/components/roku/entity.py | 1 + homeassistant/components/roku/helpers.py | 1 + homeassistant/components/roku/media_player.py | 1 + homeassistant/components/roku/remote.py | 1 + homeassistant/components/roku/select.py | 1 + homeassistant/components/roku/sensor.py | 1 + homeassistant/components/romy/config_flow.py | 1 + homeassistant/components/roomba/binary_sensor.py | 1 + homeassistant/components/roomba/config_flow.py | 1 + homeassistant/components/roomba/const.py | 1 + homeassistant/components/roomba/irobot_base.py | 1 + homeassistant/components/roomba/models.py | 1 + homeassistant/components/roomba/sensor.py | 1 + homeassistant/components/roomba/vacuum.py | 1 + homeassistant/components/roon/__init__.py | 1 + homeassistant/components/roon/media_player.py | 1 + homeassistant/components/route53/__init__.py | 1 + homeassistant/components/rova/sensor.py | 1 + homeassistant/components/rpi_camera/camera.py | 1 + homeassistant/components/rpi_power/__init__.py | 1 + homeassistant/components/rpi_power/config_flow.py | 1 + homeassistant/components/rss_feed_template/__init__.py | 1 + homeassistant/components/rtorrent/sensor.py | 1 + homeassistant/components/rtsp_to_webrtc/config_flow.py | 1 + homeassistant/components/ruckus_unleashed/config_flow.py | 1 + homeassistant/components/ruckus_unleashed/const.py | 1 + homeassistant/components/ruckus_unleashed/coordinator.py | 1 + homeassistant/components/ruckus_unleashed/device_tracker.py | 1 + homeassistant/components/russound_rio/media_player.py | 1 + homeassistant/components/russound_rnet/media_player.py | 1 + homeassistant/components/ruuvi_gateway/__init__.py | 1 + homeassistant/components/ruuvi_gateway/bluetooth.py | 1 + homeassistant/components/ruuvi_gateway/config_flow.py | 1 + homeassistant/components/ruuvi_gateway/const.py | 1 + homeassistant/components/ruuvi_gateway/coordinator.py | 1 + homeassistant/components/ruuvi_gateway/models.py | 1 + homeassistant/components/ruuvi_gateway/schemata.py | 1 + homeassistant/components/ruuvitag_ble/__init__.py | 1 + homeassistant/components/ruuvitag_ble/config_flow.py | 1 + homeassistant/components/ruuvitag_ble/sensor.py | 1 + homeassistant/components/rympro/__init__.py | 1 + homeassistant/components/rympro/config_flow.py | 1 + homeassistant/components/rympro/coordinator.py | 1 + homeassistant/components/rympro/sensor.py | 1 + 316 files changed, 316 insertions(+) diff --git a/homeassistant/components/rabbitair/__init__.py b/homeassistant/components/rabbitair/__init__.py index 97b37f6c03f..e4eb67a67f5 100644 --- a/homeassistant/components/rabbitair/__init__.py +++ b/homeassistant/components/rabbitair/__init__.py @@ -1,4 +1,5 @@ """The Rabbit Air integration.""" + from __future__ import annotations from rabbitair import Client, UdpClient diff --git a/homeassistant/components/rabbitair/config_flow.py b/homeassistant/components/rabbitair/config_flow.py index f13517a1d56..30dfac93236 100644 --- a/homeassistant/components/rabbitair/config_flow.py +++ b/homeassistant/components/rabbitair/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rabbit Air integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rabbitair/coordinator.py b/homeassistant/components/rabbitair/coordinator.py index 36c58f8700c..3c7db126c7d 100644 --- a/homeassistant/components/rabbitair/coordinator.py +++ b/homeassistant/components/rabbitair/coordinator.py @@ -1,4 +1,5 @@ """Rabbit Air Update Coordinator.""" + from collections.abc import Coroutine from datetime import timedelta import logging diff --git a/homeassistant/components/rabbitair/entity.py b/homeassistant/components/rabbitair/entity.py index 07e49aae7cb..47a1b7db3eb 100644 --- a/homeassistant/components/rabbitair/entity.py +++ b/homeassistant/components/rabbitair/entity.py @@ -1,4 +1,5 @@ """A base class for Rabbit Air entities.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rabbitair/fan.py b/homeassistant/components/rabbitair/fan.py index 46465163839..ee4f136930d 100644 --- a/homeassistant/components/rabbitair/fan.py +++ b/homeassistant/components/rabbitair/fan.py @@ -1,4 +1,5 @@ """Support for Rabbit Air fan entity.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py index 652806a2bad..eb7a84867ab 100644 --- a/homeassistant/components/rachio/binary_sensor.py +++ b/homeassistant/components/rachio/binary_sensor.py @@ -1,4 +1,5 @@ """Integration with the Rachio Iro sprinkler system controller.""" + from abc import abstractmethod import logging diff --git a/homeassistant/components/rachio/config_flow.py b/homeassistant/components/rachio/config_flow.py index 0aca6426f65..d0a311db60e 100644 --- a/homeassistant/components/rachio/config_flow.py +++ b/homeassistant/components/rachio/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rachio integration.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py index 485a726acd0..a7d1c991453 100644 --- a/homeassistant/components/rachio/device.py +++ b/homeassistant/components/rachio/device.py @@ -1,4 +1,5 @@ """Adapter to wrap the rachiopy api for home assistant.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index c69773009be..ebe96152d70 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -1,4 +1,5 @@ """Integration with the Rachio Iro sprinkler system controller.""" + from abc import abstractmethod from contextlib import suppress from datetime import timedelta diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index 298b9c03701..06cd0941dcc 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -1,4 +1,5 @@ """Webhooks used by rachio.""" + from __future__ import annotations from aiohttp import web diff --git a/homeassistant/components/radarr/__init__.py b/homeassistant/components/radarr/__init__.py index b6b05b5b568..d3e44e6b7fc 100644 --- a/homeassistant/components/radarr/__init__.py +++ b/homeassistant/components/radarr/__init__.py @@ -1,4 +1,5 @@ """The Radarr component.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/radarr/binary_sensor.py b/homeassistant/components/radarr/binary_sensor.py index 5d439680bc2..4962ef81614 100644 --- a/homeassistant/components/radarr/binary_sensor.py +++ b/homeassistant/components/radarr/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Radarr binary sensors.""" + from __future__ import annotations from aiopyarr import Health diff --git a/homeassistant/components/radarr/calendar.py b/homeassistant/components/radarr/calendar.py index 3a5308fffd5..ad5e1b8ffd9 100644 --- a/homeassistant/components/radarr/calendar.py +++ b/homeassistant/components/radarr/calendar.py @@ -1,4 +1,5 @@ """Support for Radarr calendar items.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/radarr/config_flow.py b/homeassistant/components/radarr/config_flow.py index 367e15e098a..81589c5fe30 100644 --- a/homeassistant/components/radarr/config_flow.py +++ b/homeassistant/components/radarr/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Radarr.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/radarr/coordinator.py b/homeassistant/components/radarr/coordinator.py index 7f395169644..0580fdcc020 100644 --- a/homeassistant/components/radarr/coordinator.py +++ b/homeassistant/components/radarr/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Radarr integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index feb4c7964ee..0435474e423 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -1,4 +1,5 @@ """Support for Radarr.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/radio_browser/__init__.py b/homeassistant/components/radio_browser/__init__.py index fdd7537e9e1..d1c2db3543a 100644 --- a/homeassistant/components/radio_browser/__init__.py +++ b/homeassistant/components/radio_browser/__init__.py @@ -1,4 +1,5 @@ """The Radio Browser integration.""" + from __future__ import annotations from aiodns.error import DNSError diff --git a/homeassistant/components/radio_browser/config_flow.py b/homeassistant/components/radio_browser/config_flow.py index ad7e5b71a8e..137ee7c8e87 100644 --- a/homeassistant/components/radio_browser/config_flow.py +++ b/homeassistant/components/radio_browser/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Radio Browser integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/radio_browser/media_source.py b/homeassistant/components/radio_browser/media_source.py index dffbdc42dbe..5bf0b7f491b 100644 --- a/homeassistant/components/radio_browser/media_source.py +++ b/homeassistant/components/radio_browser/media_source.py @@ -1,4 +1,5 @@ """Expose Radio Browser as a media source.""" + from __future__ import annotations import mimetypes diff --git a/homeassistant/components/radiotherm/__init__.py b/homeassistant/components/radiotherm/__init__.py index 86a9fe58013..d5f1e4c076c 100644 --- a/homeassistant/components/radiotherm/__init__.py +++ b/homeassistant/components/radiotherm/__init__.py @@ -1,4 +1,5 @@ """The radiotherm component.""" + from __future__ import annotations from collections.abc import Coroutine diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index 4ab57fd6821..73ab3644a0b 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -1,4 +1,5 @@ """Support for Radio Thermostat wifi-enabled home thermostats.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/radiotherm/config_flow.py b/homeassistant/components/radiotherm/config_flow.py index d19fa198d0f..a8de05d9963 100644 --- a/homeassistant/components/radiotherm/config_flow.py +++ b/homeassistant/components/radiotherm/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Radio Thermostat integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/radiotherm/coordinator.py b/homeassistant/components/radiotherm/coordinator.py index 5b0161d9f22..06e3554c8d7 100644 --- a/homeassistant/components/radiotherm/coordinator.py +++ b/homeassistant/components/radiotherm/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for radiotherm.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/radiotherm/data.py b/homeassistant/components/radiotherm/data.py index 3aa4e6b7631..4803cacd84b 100644 --- a/homeassistant/components/radiotherm/data.py +++ b/homeassistant/components/radiotherm/data.py @@ -1,4 +1,5 @@ """The radiotherm component data.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/radiotherm/switch.py b/homeassistant/components/radiotherm/switch.py index 1f047a76201..e7b463e3def 100644 --- a/homeassistant/components/radiotherm/switch.py +++ b/homeassistant/components/radiotherm/switch.py @@ -1,4 +1,5 @@ """Support for radiotherm switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/radiotherm/util.py b/homeassistant/components/radiotherm/util.py index 85b927d7935..fb15531987a 100644 --- a/homeassistant/components/radiotherm/util.py +++ b/homeassistant/components/radiotherm/util.py @@ -1,4 +1,5 @@ """Utils for radiotherm.""" + from __future__ import annotations from radiotherm.thermostat import CommonThermostat diff --git a/homeassistant/components/rainbird/__init__.py b/homeassistant/components/rainbird/__init__.py index 945c06943d3..da2a0e4b475 100644 --- a/homeassistant/components/rainbird/__init__.py +++ b/homeassistant/components/rainbird/__init__.py @@ -1,4 +1,5 @@ """Support for Rain Bird Irrigation system LNK WiFi Module.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rainbird/binary_sensor.py b/homeassistant/components/rainbird/binary_sensor.py index 279b8625f20..d44022b0a2d 100644 --- a/homeassistant/components/rainbird/binary_sensor.py +++ b/homeassistant/components/rainbird/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Rain Bird Irrigation system LNK WiFi Module.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rainbird/number.py b/homeassistant/components/rainbird/number.py index 9b7bdd481cb..507a31e59a4 100644 --- a/homeassistant/components/rainbird/number.py +++ b/homeassistant/components/rainbird/number.py @@ -1,4 +1,5 @@ """The number platform for rainbird.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rainbird/sensor.py b/homeassistant/components/rainbird/sensor.py index 9daf0958327..649d643a20c 100644 --- a/homeassistant/components/rainbird/sensor.py +++ b/homeassistant/components/rainbird/sensor.py @@ -1,4 +1,5 @@ """Support for Rain Bird Irrigation system LNK Wi-Fi Module.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rainbird/switch.py b/homeassistant/components/rainbird/switch.py index 810a6fbb721..a929f5b875b 100644 --- a/homeassistant/components/rainbird/switch.py +++ b/homeassistant/components/rainbird/switch.py @@ -1,4 +1,5 @@ """Support for Rain Bird Irrigation system LNK Wi-Fi Module.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/raincloud/__init__.py b/homeassistant/components/raincloud/__init__.py index 1214fd9416f..e6f5d2ecf8d 100644 --- a/homeassistant/components/raincloud/__init__.py +++ b/homeassistant/components/raincloud/__init__.py @@ -1,4 +1,5 @@ """Support for Melnor RainCloud sprinkler water timer.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/raincloud/binary_sensor.py b/homeassistant/components/raincloud/binary_sensor.py index b3766cd02cd..fb949f69d91 100644 --- a/homeassistant/components/raincloud/binary_sensor.py +++ b/homeassistant/components/raincloud/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Melnor RainCloud sprinkler water timer.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index 4d21d36d069..3da30609f63 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -1,4 +1,5 @@ """Support for Melnor RainCloud sprinkler water timer.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py index abcb680daa3..957720305de 100644 --- a/homeassistant/components/raincloud/switch.py +++ b/homeassistant/components/raincloud/switch.py @@ -1,4 +1,5 @@ """Support for Melnor RainCloud sprinkler water timer.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rainforest_eagle/__init__.py b/homeassistant/components/rainforest_eagle/__init__.py index 7cf540de1e6..67baa4dbd99 100644 --- a/homeassistant/components/rainforest_eagle/__init__.py +++ b/homeassistant/components/rainforest_eagle/__init__.py @@ -1,4 +1,5 @@ """The Rainforest Eagle integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/rainforest_eagle/config_flow.py b/homeassistant/components/rainforest_eagle/config_flow.py index 1a034f098b7..b48c1329695 100644 --- a/homeassistant/components/rainforest_eagle/config_flow.py +++ b/homeassistant/components/rainforest_eagle/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rainforest Eagle integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rainforest_eagle/data.py b/homeassistant/components/rainforest_eagle/data.py index 9da6372086f..879aa467d9b 100644 --- a/homeassistant/components/rainforest_eagle/data.py +++ b/homeassistant/components/rainforest_eagle/data.py @@ -1,4 +1,5 @@ """Rainforest data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rainforest_eagle/diagnostics.py b/homeassistant/components/rainforest_eagle/diagnostics.py index f20a20af9e2..14c980bad7d 100644 --- a/homeassistant/components/rainforest_eagle/diagnostics.py +++ b/homeassistant/components/rainforest_eagle/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Eagle.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rainforest_eagle/sensor.py b/homeassistant/components/rainforest_eagle/sensor.py index 987142c6390..27eae0e3e8e 100644 --- a/homeassistant/components/rainforest_eagle/sensor.py +++ b/homeassistant/components/rainforest_eagle/sensor.py @@ -1,4 +1,5 @@ """Support for the Rainforest Eagle energy monitor.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/rainforest_raven/__init__.py b/homeassistant/components/rainforest_raven/__init__.py index d72b12f68c6..76f82624160 100644 --- a/homeassistant/components/rainforest_raven/__init__.py +++ b/homeassistant/components/rainforest_raven/__init__.py @@ -1,4 +1,5 @@ """Integration for Rainforest RAVEn devices.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/rainforest_raven/config_flow.py b/homeassistant/components/rainforest_raven/config_flow.py index 744a36d9993..72d258dc1d3 100644 --- a/homeassistant/components/rainforest_raven/config_flow.py +++ b/homeassistant/components/rainforest_raven/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rainforest RAVEn devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rainforest_raven/coordinator.py b/homeassistant/components/rainforest_raven/coordinator.py index edae4f11433..7f2d3399a47 100644 --- a/homeassistant/components/rainforest_raven/coordinator.py +++ b/homeassistant/components/rainforest_raven/coordinator.py @@ -1,4 +1,5 @@ """Data update coordination for Rainforest RAVEn devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rainforest_raven/diagnostics.py b/homeassistant/components/rainforest_raven/diagnostics.py index 970915888ec..820c4826f00 100644 --- a/homeassistant/components/rainforest_raven/diagnostics.py +++ b/homeassistant/components/rainforest_raven/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for a Rainforest RAVEn device.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/rainforest_raven/sensor.py b/homeassistant/components/rainforest_raven/sensor.py index fd7df25c603..23ca3220694 100644 --- a/homeassistant/components/rainforest_raven/sensor.py +++ b/homeassistant/components/rainforest_raven/sensor.py @@ -1,4 +1,5 @@ """Sensor entity for a Rainforest RAVEn device.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index 2e821fc7a7a..bcd60875c70 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -1,4 +1,5 @@ """Support for RainMachine devices.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py index 7434577405e..866cddbabbd 100644 --- a/homeassistant/components/rainmachine/binary_sensor.py +++ b/homeassistant/components/rainmachine/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensors for key RainMachine data.""" + from dataclasses import dataclass from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/rainmachine/button.py b/homeassistant/components/rainmachine/button.py index 6309d9777a1..24486a34b88 100644 --- a/homeassistant/components/rainmachine/button.py +++ b/homeassistant/components/rainmachine/button.py @@ -1,4 +1,5 @@ """Buttons for the RainMachine integration.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index eb11e6276af..5c07f04c163 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the RainMachine component.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rainmachine/diagnostics.py b/homeassistant/components/rainmachine/diagnostics.py index e4835d514e6..5564ee693a4 100644 --- a/homeassistant/components/rainmachine/diagnostics.py +++ b/homeassistant/components/rainmachine/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for RainMachine.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rainmachine/model.py b/homeassistant/components/rainmachine/model.py index e7f166b67dd..ee5567112cf 100644 --- a/homeassistant/components/rainmachine/model.py +++ b/homeassistant/components/rainmachine/model.py @@ -1,4 +1,5 @@ """Define RainMachine data models.""" + from dataclasses import dataclass from homeassistant.helpers.entity import EntityDescription diff --git a/homeassistant/components/rainmachine/select.py b/homeassistant/components/rainmachine/select.py index 5c68e4b573e..bb622330897 100644 --- a/homeassistant/components/rainmachine/select.py +++ b/homeassistant/components/rainmachine/select.py @@ -1,4 +1,5 @@ """Support for RainMachine selects.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index c9e87ad0d07..15188e86963 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -1,4 +1,5 @@ """Support for sensor data from RainMachine.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py index 8450cb7d5e6..f7be08d71d3 100644 --- a/homeassistant/components/rainmachine/switch.py +++ b/homeassistant/components/rainmachine/switch.py @@ -1,4 +1,5 @@ """Component providing support for RainMachine programs and zones.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rainmachine/update.py b/homeassistant/components/rainmachine/update.py index 8d5690b5320..38bf74effa0 100644 --- a/homeassistant/components/rainmachine/update.py +++ b/homeassistant/components/rainmachine/update.py @@ -1,4 +1,5 @@ """Support for RainMachine updates.""" + from __future__ import annotations from enum import Enum diff --git a/homeassistant/components/rainmachine/util.py b/homeassistant/components/rainmachine/util.py index a557b701824..2848101eca1 100644 --- a/homeassistant/components/rainmachine/util.py +++ b/homeassistant/components/rainmachine/util.py @@ -1,4 +1,5 @@ """Define RainMachine utilities.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Iterable diff --git a/homeassistant/components/random/__init__.py b/homeassistant/components/random/__init__.py index 89a772529bd..bff2ce53dfb 100644 --- a/homeassistant/components/random/__init__.py +++ b/homeassistant/components/random/__init__.py @@ -1,4 +1,5 @@ """The random component.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/random/binary_sensor.py b/homeassistant/components/random/binary_sensor.py index a6d330e6151..bbcf87630c5 100644 --- a/homeassistant/components/random/binary_sensor.py +++ b/homeassistant/components/random/binary_sensor.py @@ -1,4 +1,5 @@ """Support for showing random states.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/random/config_flow.py b/homeassistant/components/random/config_flow.py index 96dde9c8742..dc7d91603a5 100644 --- a/homeassistant/components/random/config_flow.py +++ b/homeassistant/components/random/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Random helper.""" + from collections.abc import Callable, Coroutine, Mapping from enum import StrEnum from typing import Any, cast diff --git a/homeassistant/components/random/sensor.py b/homeassistant/components/random/sensor.py index 8cc21e34ce9..716350b2bb0 100644 --- a/homeassistant/components/random/sensor.py +++ b/homeassistant/components/random/sensor.py @@ -1,4 +1,5 @@ """Support for showing random numbers.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/rapt_ble/__init__.py b/homeassistant/components/rapt_ble/__init__.py index 1b3d65ee2a4..f5587ba5770 100644 --- a/homeassistant/components/rapt_ble/__init__.py +++ b/homeassistant/components/rapt_ble/__init__.py @@ -1,4 +1,5 @@ """The rapt_ble integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rapt_ble/config_flow.py b/homeassistant/components/rapt_ble/config_flow.py index 11971f68c54..805a2cf8cbd 100644 --- a/homeassistant/components/rapt_ble/config_flow.py +++ b/homeassistant/components/rapt_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for rapt_ble.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rapt_ble/sensor.py b/homeassistant/components/rapt_ble/sensor.py index 9967a36faee..d718bbc031a 100644 --- a/homeassistant/components/rapt_ble/sensor.py +++ b/homeassistant/components/rapt_ble/sensor.py @@ -1,4 +1,5 @@ """Support for RAPT Pill hydrometers.""" + from __future__ import annotations from rapt_ble import DeviceClass, DeviceKey, SensorUpdate, Units diff --git a/homeassistant/components/raspberry_pi/__init__.py b/homeassistant/components/raspberry_pi/__init__.py index 19697d9b69d..d1dcd04922f 100644 --- a/homeassistant/components/raspberry_pi/__init__.py +++ b/homeassistant/components/raspberry_pi/__init__.py @@ -1,4 +1,5 @@ """The Raspberry Pi integration.""" + from __future__ import annotations from homeassistant.components.hassio import get_os_info, is_hassio diff --git a/homeassistant/components/raspberry_pi/config_flow.py b/homeassistant/components/raspberry_pi/config_flow.py index c84f3b38670..d049776a6e0 100644 --- a/homeassistant/components/raspberry_pi/config_flow.py +++ b/homeassistant/components/raspberry_pi/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Raspberry Pi integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/raspberry_pi/hardware.py b/homeassistant/components/raspberry_pi/hardware.py index 2141ff6034d..54d375c7b6e 100644 --- a/homeassistant/components/raspberry_pi/hardware.py +++ b/homeassistant/components/raspberry_pi/hardware.py @@ -1,4 +1,5 @@ """The Raspberry Pi hardware platform.""" + from __future__ import annotations from homeassistant.components.hardware.models import BoardInfo, HardwareInfo diff --git a/homeassistant/components/raspyrfm/switch.py b/homeassistant/components/raspyrfm/switch.py index 77360c6d224..ce69818beec 100644 --- a/homeassistant/components/raspyrfm/switch.py +++ b/homeassistant/components/raspyrfm/switch.py @@ -1,4 +1,5 @@ """Support for switches that can be controlled using the RaspyRFM rc module.""" + from __future__ import annotations from raspyrfm_client import RaspyRFMClient diff --git a/homeassistant/components/rdw/__init__.py b/homeassistant/components/rdw/__init__.py index 601d21ee889..f123db7c697 100644 --- a/homeassistant/components/rdw/__init__.py +++ b/homeassistant/components/rdw/__init__.py @@ -1,4 +1,5 @@ """Support for RDW.""" + from __future__ import annotations from vehicle import RDW, Vehicle diff --git a/homeassistant/components/rdw/binary_sensor.py b/homeassistant/components/rdw/binary_sensor.py index c971c3e68b9..5360ce4a7fe 100644 --- a/homeassistant/components/rdw/binary_sensor.py +++ b/homeassistant/components/rdw/binary_sensor.py @@ -1,4 +1,5 @@ """Support for RDW binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/rdw/config_flow.py b/homeassistant/components/rdw/config_flow.py index fb42beb2174..cf59abc650c 100644 --- a/homeassistant/components/rdw/config_flow.py +++ b/homeassistant/components/rdw/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the RDW integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rdw/const.py b/homeassistant/components/rdw/const.py index e39e4048791..d9f99010dd7 100644 --- a/homeassistant/components/rdw/const.py +++ b/homeassistant/components/rdw/const.py @@ -1,4 +1,5 @@ """Constants for the RDW integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/rdw/diagnostics.py b/homeassistant/components/rdw/diagnostics.py index dbf3d8e21c0..f55bc33e026 100644 --- a/homeassistant/components/rdw/diagnostics.py +++ b/homeassistant/components/rdw/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for RDW.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rdw/sensor.py b/homeassistant/components/rdw/sensor.py index a6ad9047852..2c9c9addcfb 100644 --- a/homeassistant/components/rdw/sensor.py +++ b/homeassistant/components/rdw/sensor.py @@ -1,4 +1,5 @@ """Support for RDW sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py index 5f3a50e93ed..bd01aed5473 100644 --- a/homeassistant/components/recollect_waste/__init__.py +++ b/homeassistant/components/recollect_waste/__init__.py @@ -1,4 +1,5 @@ """The ReCollect Waste integration.""" + from __future__ import annotations from datetime import date, timedelta diff --git a/homeassistant/components/recollect_waste/calendar.py b/homeassistant/components/recollect_waste/calendar.py index b39ff430610..3a76451358e 100644 --- a/homeassistant/components/recollect_waste/calendar.py +++ b/homeassistant/components/recollect_waste/calendar.py @@ -1,4 +1,5 @@ """Support for ReCollect Waste calendars.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/recollect_waste/config_flow.py b/homeassistant/components/recollect_waste/config_flow.py index 3d79c895354..882eb6a00d2 100644 --- a/homeassistant/components/recollect_waste/config_flow.py +++ b/homeassistant/components/recollect_waste/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ReCollect Waste integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/recollect_waste/diagnostics.py b/homeassistant/components/recollect_waste/diagnostics.py index 35bc1b56896..f1dbcdb4061 100644 --- a/homeassistant/components/recollect_waste/diagnostics.py +++ b/homeassistant/components/recollect_waste/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for ReCollect Waste.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/recollect_waste/entity.py b/homeassistant/components/recollect_waste/entity.py index 5ccd65cc55a..a300e527fd2 100644 --- a/homeassistant/components/recollect_waste/entity.py +++ b/homeassistant/components/recollect_waste/entity.py @@ -1,4 +1,5 @@ """Define a base ReCollect Waste entity.""" + from aiorecollect.client import PickupEvent from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index da5394a9341..1ae3c296885 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -1,4 +1,5 @@ """Support for ReCollect Waste sensors.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/recollect_waste/util.py b/homeassistant/components/recollect_waste/util.py index 185078f297c..dcd8a1569df 100644 --- a/homeassistant/components/recollect_waste/util.py +++ b/homeassistant/components/recollect_waste/util.py @@ -1,4 +1,5 @@ """Define ReCollect Waste utilities.""" + from aiorecollect.client import PickupType from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 2217d6c7d4e..de75207389f 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -1,4 +1,5 @@ """Support for recording details.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/recorder/auto_repairs/events/schema.py b/homeassistant/components/recorder/auto_repairs/events/schema.py index 3cc2e74f95b..fb3b38c61c5 100644 --- a/homeassistant/components/recorder/auto_repairs/events/schema.py +++ b/homeassistant/components/recorder/auto_repairs/events/schema.py @@ -1,4 +1,5 @@ """Events schema repairs.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/recorder/auto_repairs/schema.py b/homeassistant/components/recorder/auto_repairs/schema.py index aedf917dd22..2b533b79e6c 100644 --- a/homeassistant/components/recorder/auto_repairs/schema.py +++ b/homeassistant/components/recorder/auto_repairs/schema.py @@ -1,4 +1,5 @@ """Schema repairs.""" + from __future__ import annotations from collections.abc import Iterable, Mapping diff --git a/homeassistant/components/recorder/auto_repairs/states/schema.py b/homeassistant/components/recorder/auto_repairs/states/schema.py index 3c0daef452d..3900f4fb763 100644 --- a/homeassistant/components/recorder/auto_repairs/states/schema.py +++ b/homeassistant/components/recorder/auto_repairs/states/schema.py @@ -1,4 +1,5 @@ """States schema repairs.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/recorder/auto_repairs/statistics/duplicates.py b/homeassistant/components/recorder/auto_repairs/statistics/duplicates.py index 5b7a141bd70..06a5c5258f1 100644 --- a/homeassistant/components/recorder/auto_repairs/statistics/duplicates.py +++ b/homeassistant/components/recorder/auto_repairs/statistics/duplicates.py @@ -1,4 +1,5 @@ """Statistics duplication repairs.""" + from __future__ import annotations import json diff --git a/homeassistant/components/recorder/auto_repairs/statistics/schema.py b/homeassistant/components/recorder/auto_repairs/statistics/schema.py index 607935bd6ff..13c172290ca 100644 --- a/homeassistant/components/recorder/auto_repairs/statistics/schema.py +++ b/homeassistant/components/recorder/auto_repairs/statistics/schema.py @@ -1,4 +1,5 @@ """Statistics schema repairs.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/recorder/backup.py b/homeassistant/components/recorder/backup.py index a1f6f4f39bc..d47cbe92bd4 100644 --- a/homeassistant/components/recorder/backup.py +++ b/homeassistant/components/recorder/backup.py @@ -1,4 +1,5 @@ """Backup platform for the Recorder integration.""" + from logging import getLogger from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 8885116dbfd..a32af55720b 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -1,4 +1,5 @@ """Support for recording details.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index 7c7d9a743f3..eb2e0b6ade3 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -1,4 +1,5 @@ """Models for SQLAlchemy.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/recorder/executor.py b/homeassistant/components/recorder/executor.py index 3f677e72fdf..b17547499e8 100644 --- a/homeassistant/components/recorder/executor.py +++ b/homeassistant/components/recorder/executor.py @@ -1,4 +1,5 @@ """Database executor helpers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/recorder/filters.py b/homeassistant/components/recorder/filters.py index fda8716df27..92f4c5d3902 100644 --- a/homeassistant/components/recorder/filters.py +++ b/homeassistant/components/recorder/filters.py @@ -1,4 +1,5 @@ """Provide pre-made queries on top of the recorder component.""" + from __future__ import annotations from collections.abc import Callable, Collection, Iterable diff --git a/homeassistant/components/recorder/history/__init__.py b/homeassistant/components/recorder/history/__init__.py index 7a569e70b15..05452fdac47 100644 --- a/homeassistant/components/recorder/history/__init__.py +++ b/homeassistant/components/recorder/history/__init__.py @@ -1,4 +1,5 @@ """Provide pre-made queries on top of the recorder component.""" + from __future__ import annotations from collections.abc import MutableMapping diff --git a/homeassistant/components/recorder/history/common.py b/homeassistant/components/recorder/history/common.py index 6d0150925d3..3427ee9d7ee 100644 --- a/homeassistant/components/recorder/history/common.py +++ b/homeassistant/components/recorder/history/common.py @@ -1,4 +1,5 @@ """Common functions for history.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/recorder/history/legacy.py b/homeassistant/components/recorder/history/legacy.py index 2e1b02a8b64..6a66599e11b 100644 --- a/homeassistant/components/recorder/history/legacy.py +++ b/homeassistant/components/recorder/history/legacy.py @@ -1,4 +1,5 @@ """Provide pre-made queries on top of the recorder component.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/recorder/history/modern.py b/homeassistant/components/recorder/history/modern.py index da58822e266..b35e1315cbe 100644 --- a/homeassistant/components/recorder/history/modern.py +++ b/homeassistant/components/recorder/history/modern.py @@ -1,4 +1,5 @@ """Provide pre-made queries on top of the recorder component.""" + from __future__ import annotations from collections.abc import Callable, Iterable, Iterator, MutableMapping diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 427e3acab2d..b57070198a5 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -1,4 +1,5 @@ """Schema migration helpers.""" + from __future__ import annotations from collections.abc import Callable, Iterable diff --git a/homeassistant/components/recorder/models/__init__.py b/homeassistant/components/recorder/models/__init__.py index 1a204e767e3..d43a1da161e 100644 --- a/homeassistant/components/recorder/models/__init__.py +++ b/homeassistant/components/recorder/models/__init__.py @@ -1,4 +1,5 @@ """Models for Recorder.""" + from __future__ import annotations from .context import ( diff --git a/homeassistant/components/recorder/models/context.py b/homeassistant/components/recorder/models/context.py index f25e4d4412f..c09ee366b84 100644 --- a/homeassistant/components/recorder/models/context.py +++ b/homeassistant/components/recorder/models/context.py @@ -1,4 +1,5 @@ """Models for Recorder.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/recorder/models/database.py b/homeassistant/components/recorder/models/database.py index a8c23d20061..94c5a7cc027 100644 --- a/homeassistant/components/recorder/models/database.py +++ b/homeassistant/components/recorder/models/database.py @@ -1,4 +1,5 @@ """Models for the database in the Recorder.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/recorder/models/event.py b/homeassistant/components/recorder/models/event.py index 1d644b62f46..379a6fddb1d 100644 --- a/homeassistant/components/recorder/models/event.py +++ b/homeassistant/components/recorder/models/event.py @@ -1,4 +1,5 @@ """Models events in for Recorder.""" + from __future__ import annotations diff --git a/homeassistant/components/recorder/models/legacy.py b/homeassistant/components/recorder/models/legacy.py index 398ad773ba2..af9fcf22f70 100644 --- a/homeassistant/components/recorder/models/legacy.py +++ b/homeassistant/components/recorder/models/legacy.py @@ -1,4 +1,5 @@ """Models for Recorder.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/recorder/models/state.py b/homeassistant/components/recorder/models/state.py index 5f469638ec0..9805aa56909 100644 --- a/homeassistant/components/recorder/models/state.py +++ b/homeassistant/components/recorder/models/state.py @@ -1,4 +1,5 @@ """Models states in for Recorder.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/recorder/models/statistics.py b/homeassistant/components/recorder/models/statistics.py index 4cf465955c5..ad4d82067c4 100644 --- a/homeassistant/components/recorder/models/statistics.py +++ b/homeassistant/components/recorder/models/statistics.py @@ -1,4 +1,5 @@ """Models for statistics in the Recorder.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/recorder/models/time.py b/homeassistant/components/recorder/models/time.py index 40e4afd18a7..af0df4e806d 100644 --- a/homeassistant/components/recorder/models/time.py +++ b/homeassistant/components/recorder/models/time.py @@ -1,4 +1,5 @@ """Models for Recorder.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index a9d8c0b2482..f42bae00abe 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -1,4 +1,5 @@ """Purge old data helper.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/recorder/queries.py b/homeassistant/components/recorder/queries.py index c03057b31b2..fdb15d2d49c 100644 --- a/homeassistant/components/recorder/queries.py +++ b/homeassistant/components/recorder/queries.py @@ -1,4 +1,5 @@ """Queries for the recorder.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/recorder/repack.py b/homeassistant/components/recorder/repack.py index 53c922cf481..8c7ad137d86 100644 --- a/homeassistant/components/recorder/repack.py +++ b/homeassistant/components/recorder/repack.py @@ -1,4 +1,5 @@ """Purge repack helper.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/recorder/services.py b/homeassistant/components/recorder/services.py index fb2cd1f0bef..b4d719a9481 100644 --- a/homeassistant/components/recorder/services.py +++ b/homeassistant/components/recorder/services.py @@ -1,4 +1,5 @@ """Support for recorder services.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 771d85e3569..2c26328fa2d 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -1,4 +1,5 @@ """Statistics helper.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/recorder/system_health/__init__.py b/homeassistant/components/recorder/system_health/__init__.py index a3545ec2c89..16feaa19886 100644 --- a/homeassistant/components/recorder/system_health/__init__.py +++ b/homeassistant/components/recorder/system_health/__init__.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/recorder/system_health/mysql.py b/homeassistant/components/recorder/system_health/mysql.py index 1ade699eaf1..21d9d952d3a 100644 --- a/homeassistant/components/recorder/system_health/mysql.py +++ b/homeassistant/components/recorder/system_health/mysql.py @@ -1,4 +1,5 @@ """Provide info to system health for mysql.""" + from __future__ import annotations from sqlalchemy import text diff --git a/homeassistant/components/recorder/system_health/postgresql.py b/homeassistant/components/recorder/system_health/postgresql.py index aa9197a8e85..b917e548ae5 100644 --- a/homeassistant/components/recorder/system_health/postgresql.py +++ b/homeassistant/components/recorder/system_health/postgresql.py @@ -1,4 +1,5 @@ """Provide info to system health for postgresql.""" + from __future__ import annotations from sqlalchemy import text diff --git a/homeassistant/components/recorder/system_health/sqlite.py b/homeassistant/components/recorder/system_health/sqlite.py index 01c601aa9e9..95123d1fd14 100644 --- a/homeassistant/components/recorder/system_health/sqlite.py +++ b/homeassistant/components/recorder/system_health/sqlite.py @@ -1,4 +1,5 @@ """Provide info to system health for sqlite.""" + from __future__ import annotations from sqlalchemy import text diff --git a/homeassistant/components/recorder/table_managers/event_data.py b/homeassistant/components/recorder/table_managers/event_data.py index 4c46b1b9faf..e8bb3f2300f 100644 --- a/homeassistant/components/recorder/table_managers/event_data.py +++ b/homeassistant/components/recorder/table_managers/event_data.py @@ -1,4 +1,5 @@ """Support managing EventData.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/recorder/table_managers/event_types.py b/homeassistant/components/recorder/table_managers/event_types.py index c74684a0f77..94ceab7bf68 100644 --- a/homeassistant/components/recorder/table_managers/event_types.py +++ b/homeassistant/components/recorder/table_managers/event_types.py @@ -1,4 +1,5 @@ """Support managing EventTypes.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/recorder/table_managers/recorder_runs.py b/homeassistant/components/recorder/table_managers/recorder_runs.py index 455c8375b1c..b0b9818118b 100644 --- a/homeassistant/components/recorder/table_managers/recorder_runs.py +++ b/homeassistant/components/recorder/table_managers/recorder_runs.py @@ -1,4 +1,5 @@ """Track recorder run history.""" + from __future__ import annotations import bisect diff --git a/homeassistant/components/recorder/table_managers/state_attributes.py b/homeassistant/components/recorder/table_managers/state_attributes.py index ddaf8cb4fca..e2fb9153be8 100644 --- a/homeassistant/components/recorder/table_managers/state_attributes.py +++ b/homeassistant/components/recorder/table_managers/state_attributes.py @@ -1,4 +1,5 @@ """Support managing StateAttributes.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/recorder/table_managers/states.py b/homeassistant/components/recorder/table_managers/states.py index fcfdcef0891..80d2fcaddaf 100644 --- a/homeassistant/components/recorder/table_managers/states.py +++ b/homeassistant/components/recorder/table_managers/states.py @@ -1,4 +1,5 @@ """Support managing States.""" + from __future__ import annotations from ..db_schema import States diff --git a/homeassistant/components/recorder/table_managers/states_meta.py b/homeassistant/components/recorder/table_managers/states_meta.py index 9b7aa1f7f96..ebc1dab45f3 100644 --- a/homeassistant/components/recorder/table_managers/states_meta.py +++ b/homeassistant/components/recorder/table_managers/states_meta.py @@ -1,4 +1,5 @@ """Support managing StatesMeta.""" + from __future__ import annotations from collections.abc import Iterable, Sequence diff --git a/homeassistant/components/recorder/table_managers/statistics_meta.py b/homeassistant/components/recorder/table_managers/statistics_meta.py index 76def3a22fe..e216cb79987 100644 --- a/homeassistant/components/recorder/table_managers/statistics_meta.py +++ b/homeassistant/components/recorder/table_managers/statistics_meta.py @@ -1,4 +1,5 @@ """Support managing StatesMeta.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/recorder/tasks.py b/homeassistant/components/recorder/tasks.py index c062eb3915f..1b81d7a983f 100644 --- a/homeassistant/components/recorder/tasks.py +++ b/homeassistant/components/recorder/tasks.py @@ -1,4 +1,5 @@ """Support for recording details.""" + from __future__ import annotations import abc diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index f684160f86f..67cf33ee591 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -1,4 +1,5 @@ """SQLAlchemy util functions.""" + from __future__ import annotations from collections.abc import Callable, Collection, Generator, Iterable, Sequence diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index f2b4df1d0cc..79104485e19 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -1,4 +1,5 @@ """The Recorder websocket API.""" + from __future__ import annotations from datetime import datetime as dt diff --git a/homeassistant/components/recovery_mode/__init__.py b/homeassistant/components/recovery_mode/__init__.py index 46a8d320663..8d02d6044c2 100644 --- a/homeassistant/components/recovery_mode/__init__.py +++ b/homeassistant/components/recovery_mode/__init__.py @@ -1,4 +1,5 @@ """The Recovery Mode integration.""" + from homeassistant.components import persistent_notification from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/recswitch/switch.py b/homeassistant/components/recswitch/switch.py index 43e71ef1df9..a0035d50582 100644 --- a/homeassistant/components/recswitch/switch.py +++ b/homeassistant/components/recswitch/switch.py @@ -1,4 +1,5 @@ """Support for Ankuoo RecSwitch MS6126 devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index 43550658ac3..47aa2ab86f6 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -1,4 +1,5 @@ """Support for Reddit.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/refoss/__init__.py b/homeassistant/components/refoss/__init__.py index d83ca17dd6b..666a17847c9 100644 --- a/homeassistant/components/refoss/__init__.py +++ b/homeassistant/components/refoss/__init__.py @@ -1,4 +1,5 @@ """Refoss devices platform loader.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/refoss/bridge.py b/homeassistant/components/refoss/bridge.py index 888179e8a7c..f7bb526d11a 100644 --- a/homeassistant/components/refoss/bridge.py +++ b/homeassistant/components/refoss/bridge.py @@ -1,4 +1,5 @@ """Refoss integration.""" + from __future__ import annotations from refoss_ha.device import DeviceInfo diff --git a/homeassistant/components/refoss/const.py b/homeassistant/components/refoss/const.py index dd11921c75e..86e40fce43c 100644 --- a/homeassistant/components/refoss/const.py +++ b/homeassistant/components/refoss/const.py @@ -1,4 +1,5 @@ """const.""" + from __future__ import annotations from logging import Logger, getLogger diff --git a/homeassistant/components/refoss/coordinator.py b/homeassistant/components/refoss/coordinator.py index a542f0e1ae8..8b03313d6d6 100644 --- a/homeassistant/components/refoss/coordinator.py +++ b/homeassistant/components/refoss/coordinator.py @@ -1,4 +1,5 @@ """Helper and coordinator for refoss.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/refoss/entity.py b/homeassistant/components/refoss/entity.py index d3425974bb1..3032c32ed51 100644 --- a/homeassistant/components/refoss/entity.py +++ b/homeassistant/components/refoss/entity.py @@ -1,4 +1,5 @@ """Entity object for shared properties of Refoss entities.""" + from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/refoss/util.py b/homeassistant/components/refoss/util.py index cd589022d73..4c44b9537af 100644 --- a/homeassistant/components/refoss/util.py +++ b/homeassistant/components/refoss/util.py @@ -1,4 +1,5 @@ """Refoss helpers functions.""" + from __future__ import annotations from refoss_ha.discovery import Discovery diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 135205aa95d..6486ba329f4 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -3,6 +3,7 @@ For more info on the API see: https://help.rejseplanen.dk/hc/en-us/articles/214174465-Rejseplanen-s-API """ + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index c5facb9785c..fffbc913fe8 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -1,4 +1,5 @@ """Support to interface with universal remote control devices.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/remote/device_action.py b/homeassistant/components/remote/device_action.py index 936c7aca37a..a0ae707724e 100644 --- a/homeassistant/components/remote/device_action.py +++ b/homeassistant/components/remote/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for remotes.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/remote/device_condition.py b/homeassistant/components/remote/device_condition.py index 33f680e6829..f34b7f61580 100644 --- a/homeassistant/components/remote/device_condition.py +++ b/homeassistant/components/remote/device_condition.py @@ -1,4 +1,5 @@ """Provides device conditions for remotes.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/remote/device_trigger.py b/homeassistant/components/remote/device_trigger.py index f2d5c54e3c3..0f08cb155aa 100644 --- a/homeassistant/components/remote/device_trigger.py +++ b/homeassistant/components/remote/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for remotes.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/remote/reproduce_state.py b/homeassistant/components/remote/reproduce_state.py index 064e4a9711a..06a04acf0ef 100644 --- a/homeassistant/components/remote/reproduce_state.py +++ b/homeassistant/components/remote/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Remote state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/remote/significant_change.py b/homeassistant/components/remote/significant_change.py index 8e5a3669041..5d2dff87909 100644 --- a/homeassistant/components/remote/significant_change.py +++ b/homeassistant/components/remote/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Remote state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/remote_rpi_gpio/__init__.py b/homeassistant/components/remote_rpi_gpio/__init__.py index 1654cc0c01d..7ab7e89b4d5 100644 --- a/homeassistant/components/remote_rpi_gpio/__init__.py +++ b/homeassistant/components/remote_rpi_gpio/__init__.py @@ -1,4 +1,5 @@ """Support for controlling GPIO pins of a Raspberry Pi.""" + from gpiozero import LED, DigitalInputDevice from gpiozero.pins.pigpio import PiGPIOFactory diff --git a/homeassistant/components/remote_rpi_gpio/binary_sensor.py b/homeassistant/components/remote_rpi_gpio/binary_sensor.py index bc0e694e8eb..ad995614ed4 100644 --- a/homeassistant/components/remote_rpi_gpio/binary_sensor.py +++ b/homeassistant/components/remote_rpi_gpio/binary_sensor.py @@ -1,4 +1,5 @@ """Support for binary sensor using RPi GPIO.""" + from __future__ import annotations import requests diff --git a/homeassistant/components/remote_rpi_gpio/switch.py b/homeassistant/components/remote_rpi_gpio/switch.py index 962cf6b4f3c..756e9dcfce9 100644 --- a/homeassistant/components/remote_rpi_gpio/switch.py +++ b/homeassistant/components/remote_rpi_gpio/switch.py @@ -1,4 +1,5 @@ """Allows to configure a switch using RPi GPIO.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/renault/binary_sensor.py b/homeassistant/components/renault/binary_sensor.py index f77aaeb7893..37e91a1e435 100644 --- a/homeassistant/components/renault/binary_sensor.py +++ b/homeassistant/components/renault/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Renault binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/renault/button.py b/homeassistant/components/renault/button.py index 9f259594bfa..c1b94fcf6f2 100644 --- a/homeassistant/components/renault/button.py +++ b/homeassistant/components/renault/button.py @@ -1,4 +1,5 @@ """Support for Renault button entities.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/renault/config_flow.py b/homeassistant/components/renault/config_flow.py index cc1b76d4a5f..82429dd146c 100644 --- a/homeassistant/components/renault/config_flow.py +++ b/homeassistant/components/renault/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Renault component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/renault/const.py b/homeassistant/components/renault/const.py index 8e68eac01eb..201a07c6783 100644 --- a/homeassistant/components/renault/const.py +++ b/homeassistant/components/renault/const.py @@ -1,4 +1,5 @@ """Constants for the Renault component.""" + from homeassistant.const import Platform DOMAIN = "renault" diff --git a/homeassistant/components/renault/coordinator.py b/homeassistant/components/renault/coordinator.py index f8e6a21823a..f77a38f2505 100644 --- a/homeassistant/components/renault/coordinator.py +++ b/homeassistant/components/renault/coordinator.py @@ -1,4 +1,5 @@ """Proxy to handle account communication with Renault servers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/renault/device_tracker.py b/homeassistant/components/renault/device_tracker.py index d5171bc954a..922173461a0 100644 --- a/homeassistant/components/renault/device_tracker.py +++ b/homeassistant/components/renault/device_tracker.py @@ -1,4 +1,5 @@ """Support for Renault device trackers.""" + from __future__ import annotations from renault_api.kamereon.models import KamereonVehicleLocationData diff --git a/homeassistant/components/renault/diagnostics.py b/homeassistant/components/renault/diagnostics.py index 2029ef989d6..1234def019e 100644 --- a/homeassistant/components/renault/diagnostics.py +++ b/homeassistant/components/renault/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Renault.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/renault/entity.py b/homeassistant/components/renault/entity.py index fd7f0eb3654..10de028b2d0 100644 --- a/homeassistant/components/renault/entity.py +++ b/homeassistant/components/renault/entity.py @@ -1,4 +1,5 @@ """Base classes for Renault entities.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/renault/renault_hub.py b/homeassistant/components/renault/renault_hub.py index 49819dd919f..97a9d080b86 100644 --- a/homeassistant/components/renault/renault_hub.py +++ b/homeassistant/components/renault/renault_hub.py @@ -1,4 +1,5 @@ """Proxy to handle account communication with Renault servers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/renault/renault_vehicle.py b/homeassistant/components/renault/renault_vehicle.py index e44a50d57a1..55a5574a444 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -1,4 +1,5 @@ """Proxy to handle account communication with Renault servers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/renault/select.py b/homeassistant/components/renault/select.py index 398ccf14fb7..ffae27c5e55 100644 --- a/homeassistant/components/renault/select.py +++ b/homeassistant/components/renault/select.py @@ -1,4 +1,5 @@ """Support for Renault sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index d864bd4936c..f806ca374c2 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -1,4 +1,5 @@ """Support for Renault sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/renault/services.py b/homeassistant/components/renault/services.py index d2c7d451844..b49088ddb7d 100644 --- a/homeassistant/components/renault/services.py +++ b/homeassistant/components/renault/services.py @@ -1,4 +1,5 @@ """Support for Renault services.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/renson/__init__.py b/homeassistant/components/renson/__init__.py index c046f93cdc0..d1eebdf0a5f 100644 --- a/homeassistant/components/renson/__init__.py +++ b/homeassistant/components/renson/__init__.py @@ -1,4 +1,5 @@ """The Renson integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/renson/binary_sensor.py b/homeassistant/components/renson/binary_sensor.py index 012ecee2e98..90258a9ae4b 100644 --- a/homeassistant/components/renson/binary_sensor.py +++ b/homeassistant/components/renson/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensors for renson.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/renson/button.py b/homeassistant/components/renson/button.py index 5cdf0e4787b..c9a03a5bffb 100644 --- a/homeassistant/components/renson/button.py +++ b/homeassistant/components/renson/button.py @@ -1,4 +1,5 @@ """Renson ventilation unit buttons.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/renson/config_flow.py b/homeassistant/components/renson/config_flow.py index 614abdb3665..ec380f5a513 100644 --- a/homeassistant/components/renson/config_flow.py +++ b/homeassistant/components/renson/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Renson integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/renson/coordinator.py b/homeassistant/components/renson/coordinator.py index 924a3b765f5..8613220eee1 100644 --- a/homeassistant/components/renson/coordinator.py +++ b/homeassistant/components/renson/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the renson integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/renson/entity.py b/homeassistant/components/renson/entity.py index 9bb2c27b112..cee991386ea 100644 --- a/homeassistant/components/renson/entity.py +++ b/homeassistant/components/renson/entity.py @@ -1,4 +1,5 @@ """Entity class for Renson ventilation unit.""" + from __future__ import annotations from renson_endura_delta.field_enum import ( diff --git a/homeassistant/components/renson/fan.py b/homeassistant/components/renson/fan.py index d15e14aa628..226d623af2b 100644 --- a/homeassistant/components/renson/fan.py +++ b/homeassistant/components/renson/fan.py @@ -1,4 +1,5 @@ """Platform to control a Renson ventilation unit.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/renson/number.py b/homeassistant/components/renson/number.py index 972368298a6..fb8ab8fc552 100644 --- a/homeassistant/components/renson/number.py +++ b/homeassistant/components/renson/number.py @@ -1,4 +1,5 @@ """Platform to control a Renson ventilation unit.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/renson/sensor.py b/homeassistant/components/renson/sensor.py index 367b4a47a63..78ece708c4c 100644 --- a/homeassistant/components/renson/sensor.py +++ b/homeassistant/components/renson/sensor.py @@ -1,4 +1,5 @@ """Sensor data of the Renson ventilation unit.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/renson/switch.py b/homeassistant/components/renson/switch.py index 8d639a64d48..2cd44d20a6a 100644 --- a/homeassistant/components/renson/switch.py +++ b/homeassistant/components/renson/switch.py @@ -1,4 +1,5 @@ """Breeze switch of the Renson ventilation unit.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/renson/time.py b/homeassistant/components/renson/time.py index 57d6869a72c..feb47fadf99 100644 --- a/homeassistant/components/renson/time.py +++ b/homeassistant/components/renson/time.py @@ -1,4 +1,5 @@ """Renson ventilation unit time.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index 87991709e8b..fe80177da12 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -1,4 +1,5 @@ """Component providing support for Reolink binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/button.py b/homeassistant/components/reolink/button.py index b878505ed84..528807920d3 100644 --- a/homeassistant/components/reolink/button.py +++ b/homeassistant/components/reolink/button.py @@ -1,4 +1,5 @@ """Component providing support for Reolink button entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/camera.py b/homeassistant/components/reolink/camera.py index 715588a8225..a2c396e7ef5 100644 --- a/homeassistant/components/reolink/camera.py +++ b/homeassistant/components/reolink/camera.py @@ -1,4 +1,5 @@ """Component providing support for Reolink IP cameras.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/reolink/config_flow.py b/homeassistant/components/reolink/config_flow.py index 627a41c9511..3257d183de8 100644 --- a/homeassistant/components/reolink/config_flow.py +++ b/homeassistant/components/reolink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Reolink camera component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/reolink/diagnostics.py b/homeassistant/components/reolink/diagnostics.py index 04b476296f8..5c13bccf58d 100644 --- a/homeassistant/components/reolink/diagnostics.py +++ b/homeassistant/components/reolink/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Reolink.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/reolink/entity.py b/homeassistant/components/reolink/entity.py index 042e6b45717..e02fd931f66 100644 --- a/homeassistant/components/reolink/entity.py +++ b/homeassistant/components/reolink/entity.py @@ -1,4 +1,5 @@ """Reolink parent entity class.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/exceptions.py b/homeassistant/components/reolink/exceptions.py index f3e9e0158cd..d166b438f31 100644 --- a/homeassistant/components/reolink/exceptions.py +++ b/homeassistant/components/reolink/exceptions.py @@ -1,4 +1,5 @@ """Exceptions for the Reolink Camera integration.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 77aeffd5412..44750cdeb3c 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -1,4 +1,5 @@ """Module which encapsulates the NVR/camera API and subscription.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/reolink/light.py b/homeassistant/components/reolink/light.py index 108e8fb477b..877bf80080b 100644 --- a/homeassistant/components/reolink/light.py +++ b/homeassistant/components/reolink/light.py @@ -1,4 +1,5 @@ """Component providing support for Reolink light entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index ec6354aa320..c4623c49c91 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -1,4 +1,5 @@ """Component providing support for Reolink number entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/select.py b/homeassistant/components/reolink/select.py index 24c149d2dee..13757e7bb22 100644 --- a/homeassistant/components/reolink/select.py +++ b/homeassistant/components/reolink/select.py @@ -1,4 +1,5 @@ """Component providing support for Reolink select entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py index 6706e49fcf4..9bc72fc7b18 100644 --- a/homeassistant/components/reolink/sensor.py +++ b/homeassistant/components/reolink/sensor.py @@ -1,4 +1,5 @@ """Component providing support for Reolink sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/siren.py b/homeassistant/components/reolink/siren.py index e174be0304f..269c0690105 100644 --- a/homeassistant/components/reolink/siren.py +++ b/homeassistant/components/reolink/siren.py @@ -1,4 +1,5 @@ """Component providing support for Reolink siren entities.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index 3c429185556..adda97debb4 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -1,4 +1,5 @@ """Component providing support for Reolink switch entities.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/reolink/update.py b/homeassistant/components/reolink/update.py index ffd429e92ad..41933ae2efc 100644 --- a/homeassistant/components/reolink/update.py +++ b/homeassistant/components/reolink/update.py @@ -1,4 +1,5 @@ """Update entities for Reolink devices.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/reolink/util.py b/homeassistant/components/reolink/util.py index cc9ad192bc3..cf4659224e3 100644 --- a/homeassistant/components/reolink/util.py +++ b/homeassistant/components/reolink/util.py @@ -1,4 +1,5 @@ """Utility functions for the Reolink component.""" + from __future__ import annotations from homeassistant import config_entries diff --git a/homeassistant/components/repairs/__init__.py b/homeassistant/components/repairs/__init__.py index 228972e8718..8d3fc429ce0 100644 --- a/homeassistant/components/repairs/__init__.py +++ b/homeassistant/components/repairs/__init__.py @@ -1,4 +1,5 @@ """The repairs integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/repairs/issue_handler.py b/homeassistant/components/repairs/issue_handler.py index be7a6310464..8a170b1de8d 100644 --- a/homeassistant/components/repairs/issue_handler.py +++ b/homeassistant/components/repairs/issue_handler.py @@ -1,4 +1,5 @@ """The repairs integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/repairs/models.py b/homeassistant/components/repairs/models.py index 6ae175b29e9..afac8813d1e 100644 --- a/homeassistant/components/repairs/models.py +++ b/homeassistant/components/repairs/models.py @@ -1,4 +1,5 @@ """Models for Repairs.""" + from __future__ import annotations from typing import Protocol diff --git a/homeassistant/components/repairs/websocket_api.py b/homeassistant/components/repairs/websocket_api.py index 78a3c10bbe4..af5f82e49d4 100644 --- a/homeassistant/components/repairs/websocket_api.py +++ b/homeassistant/components/repairs/websocket_api.py @@ -1,4 +1,5 @@ """The repairs websocket API.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/repetier/__init__.py b/homeassistant/components/repetier/__init__.py index 91a16ea3fbe..2642e78e7ec 100644 --- a/homeassistant/components/repetier/__init__.py +++ b/homeassistant/components/repetier/__init__.py @@ -1,4 +1,5 @@ """Support for Repetier-Server sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/repetier/sensor.py b/homeassistant/components/repetier/sensor.py index 578ca58b80f..d413c25c8d4 100644 --- a/homeassistant/components/repetier/sensor.py +++ b/homeassistant/components/repetier/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring Repetier Server Sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rest/__init__.py b/homeassistant/components/rest/__init__.py index d8610f148bd..1c33b4592df 100644 --- a/homeassistant/components/rest/__init__.py +++ b/homeassistant/components/rest/__init__.py @@ -1,4 +1,5 @@ """The rest component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py index 8c629e2240e..0568203a91c 100644 --- a/homeassistant/components/rest/binary_sensor.py +++ b/homeassistant/components/rest/binary_sensor.py @@ -1,4 +1,5 @@ """Support for RESTful binary sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rest/data.py b/homeassistant/components/rest/data.py index 61c88a14400..06be7a4f6ff 100644 --- a/homeassistant/components/rest/data.py +++ b/homeassistant/components/rest/data.py @@ -1,4 +1,5 @@ """Support for RESTful API.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rest/entity.py b/homeassistant/components/rest/entity.py index f0cccc8b762..3695c899371 100644 --- a/homeassistant/components/rest/entity.py +++ b/homeassistant/components/rest/entity.py @@ -1,4 +1,5 @@ """The base entity for the rest component.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/rest/notify.py b/homeassistant/components/rest/notify.py index e155fe47048..7744154c1c5 100644 --- a/homeassistant/components/rest/notify.py +++ b/homeassistant/components/rest/notify.py @@ -1,4 +1,5 @@ """RESTful platform for notify component.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/rest/sensor.py b/homeassistant/components/rest/sensor.py index 67f70a716b0..199ab3721c3 100644 --- a/homeassistant/components/rest/sensor.py +++ b/homeassistant/components/rest/sensor.py @@ -1,4 +1,5 @@ """Support for RESTful API sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py index e021b72ff3d..f0da6366cfc 100644 --- a/homeassistant/components/rest/switch.py +++ b/homeassistant/components/rest/switch.py @@ -1,4 +1,5 @@ """Support for RESTful switches.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/rest_command/__init__.py b/homeassistant/components/rest_command/__init__.py index 199186cf222..81a57e432d2 100644 --- a/homeassistant/components/rest_command/__init__.py +++ b/homeassistant/components/rest_command/__init__.py @@ -1,4 +1,5 @@ """Support for exposing regular REST commands as services.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 5b90e656911..ee2e1cb9fff 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -1,4 +1,5 @@ """Support for Rflink devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rflink/binary_sensor.py b/homeassistant/components/rflink/binary_sensor.py index e307d9de382..789e25c62b1 100644 --- a/homeassistant/components/rflink/binary_sensor.py +++ b/homeassistant/components/rflink/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Rflink binary sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rflink/cover.py b/homeassistant/components/rflink/cover.py index 9492611f439..d440b324532 100644 --- a/homeassistant/components/rflink/cover.py +++ b/homeassistant/components/rflink/cover.py @@ -1,4 +1,5 @@ """Support for Rflink Cover devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 347d2f1074f..d354e317ccb 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -1,4 +1,5 @@ """Support for Rflink lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rflink/sensor.py b/homeassistant/components/rflink/sensor.py index fd6db8f0c60..b01d1f709fe 100644 --- a/homeassistant/components/rflink/sensor.py +++ b/homeassistant/components/rflink/sensor.py @@ -1,4 +1,5 @@ """Support for Rflink sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rflink/switch.py b/homeassistant/components/rflink/switch.py index fe82dd0297b..fdf8f63ab7d 100644 --- a/homeassistant/components/rflink/switch.py +++ b/homeassistant/components/rflink/switch.py @@ -1,4 +1,5 @@ """Support for Rflink switches.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index ce17316e6c7..4cacb27b49a 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -1,4 +1,5 @@ """Support for RFXtrx devices.""" + from __future__ import annotations import binascii diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 03cf65a49ff..03c22167358 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -1,4 +1,5 @@ """Support for RFXtrx binary sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index e79303a4e23..aba6c08b06a 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -1,4 +1,5 @@ """Config flow for RFXCOM RFXtrx integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index 532e41ac50c..9e9e5a090e4 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -1,4 +1,5 @@ """Support for RFXtrx covers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rfxtrx/device_action.py b/homeassistant/components/rfxtrx/device_action.py index 15595b88cd2..e706153ace0 100644 --- a/homeassistant/components/rfxtrx/device_action.py +++ b/homeassistant/components/rfxtrx/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for RFXCOM RFXtrx.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/rfxtrx/device_trigger.py b/homeassistant/components/rfxtrx/device_trigger.py index a2f32395572..3064832a397 100644 --- a/homeassistant/components/rfxtrx/device_trigger.py +++ b/homeassistant/components/rfxtrx/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for RFXCOM RFXtrx.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/rfxtrx/diagnostics.py b/homeassistant/components/rfxtrx/diagnostics.py index bc2fae2452d..d8bebfca2ae 100644 --- a/homeassistant/components/rfxtrx/diagnostics.py +++ b/homeassistant/components/rfxtrx/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for RFXCOM RFXtrx.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rfxtrx/event.py b/homeassistant/components/rfxtrx/event.py index de4b32c5475..7e73919aacd 100644 --- a/homeassistant/components/rfxtrx/event.py +++ b/homeassistant/components/rfxtrx/event.py @@ -1,4 +1,5 @@ """Support for RFXtrx sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index ad84515d41d..f9bbbc28a8d 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -1,4 +1,5 @@ """Support for RFXtrx lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 66803edffc5..0bb98c44896 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -1,4 +1,5 @@ """Support for RFXtrx sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/rfxtrx/siren.py b/homeassistant/components/rfxtrx/siren.py index bfff08d5ea6..67a0c6b7dce 100644 --- a/homeassistant/components/rfxtrx/siren.py +++ b/homeassistant/components/rfxtrx/siren.py @@ -1,4 +1,5 @@ """Support for RFXtrx sirens.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index edc34aeb80d..fad395f41c2 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -1,4 +1,5 @@ """Support for RFXtrx switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rhasspy/__init__.py b/homeassistant/components/rhasspy/__init__.py index 669d81952d4..d673aace40b 100644 --- a/homeassistant/components/rhasspy/__init__.py +++ b/homeassistant/components/rhasspy/__init__.py @@ -1,4 +1,5 @@ """The Rhasspy integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/rhasspy/config_flow.py b/homeassistant/components/rhasspy/config_flow.py index 4253e09a94d..114d74d4d05 100644 --- a/homeassistant/components/rhasspy/config_flow.py +++ b/homeassistant/components/rhasspy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rhasspy integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ridwell/__init__.py b/homeassistant/components/ridwell/__init__.py index 53575e79c45..cf584207091 100644 --- a/homeassistant/components/ridwell/__init__.py +++ b/homeassistant/components/ridwell/__init__.py @@ -1,4 +1,5 @@ """The Ridwell integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ridwell/calendar.py b/homeassistant/components/ridwell/calendar.py index bf9c83914ac..ecca0366754 100644 --- a/homeassistant/components/ridwell/calendar.py +++ b/homeassistant/components/ridwell/calendar.py @@ -1,4 +1,5 @@ """Support for Ridwell calendars.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/ridwell/config_flow.py b/homeassistant/components/ridwell/config_flow.py index d8d4e2b7381..a54d4debe75 100644 --- a/homeassistant/components/ridwell/config_flow.py +++ b/homeassistant/components/ridwell/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ridwell integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/ridwell/coordinator.py b/homeassistant/components/ridwell/coordinator.py index 9561cd26e4b..28190522c76 100644 --- a/homeassistant/components/ridwell/coordinator.py +++ b/homeassistant/components/ridwell/coordinator.py @@ -1,4 +1,5 @@ """Define a Ridwell coordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ridwell/diagnostics.py b/homeassistant/components/ridwell/diagnostics.py index f48861cee19..0eff7583311 100644 --- a/homeassistant/components/ridwell/diagnostics.py +++ b/homeassistant/components/ridwell/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Ridwell.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/ridwell/entity.py b/homeassistant/components/ridwell/entity.py index 095ecc3c5c6..d8323f7aef6 100644 --- a/homeassistant/components/ridwell/entity.py +++ b/homeassistant/components/ridwell/entity.py @@ -1,4 +1,5 @@ """Define a base Ridwell entity.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/ridwell/sensor.py b/homeassistant/components/ridwell/sensor.py index e4626831d7d..7fc7fdb5348 100644 --- a/homeassistant/components/ridwell/sensor.py +++ b/homeassistant/components/ridwell/sensor.py @@ -1,4 +1,5 @@ """Support for Ridwell sensors.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/ridwell/switch.py b/homeassistant/components/ridwell/switch.py index c18e1d44b07..04e3e4c5ff9 100644 --- a/homeassistant/components/ridwell/switch.py +++ b/homeassistant/components/ridwell/switch.py @@ -1,4 +1,5 @@ """Support for Ridwell buttons.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 26fdc6d0575..5470d997fab 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -1,4 +1,5 @@ """Support for Ring Doorbell/Chimes.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index a7e04f4cfb9..1c39e484f97 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -1,4 +1,5 @@ """Component providing HA sensor support for Ring Door Bell/Chimes.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index 265d7102b91..38b9cae055c 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -1,4 +1,5 @@ """Component providing support to the Ring Door Bell camera.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ring/config_flow.py b/homeassistant/components/ring/config_flow.py index 6fc22e407f3..f5557c48183 100644 --- a/homeassistant/components/ring/config_flow.py +++ b/homeassistant/components/ring/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ring integration.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/ring/const.py b/homeassistant/components/ring/const.py index f0e0c63d778..24c584016d5 100644 --- a/homeassistant/components/ring/const.py +++ b/homeassistant/components/ring/const.py @@ -1,4 +1,5 @@ """The Ring constants.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ring/coordinator.py b/homeassistant/components/ring/coordinator.py index 943b1c628bf..bccfccf2808 100644 --- a/homeassistant/components/ring/coordinator.py +++ b/homeassistant/components/ring/coordinator.py @@ -1,4 +1,5 @@ """Data coordinators for the ring integration.""" + from asyncio import TaskGroup from collections.abc import Callable from dataclasses import dataclass diff --git a/homeassistant/components/ring/diagnostics.py b/homeassistant/components/ring/diagnostics.py index 105800f8d13..0f9c91415d7 100644 --- a/homeassistant/components/ring/diagnostics.py +++ b/homeassistant/components/ring/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Ring.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ring/entity.py b/homeassistant/components/ring/entity.py index 78f0c8e468e..94dbb1ba987 100644 --- a/homeassistant/components/ring/entity.py +++ b/homeassistant/components/ring/entity.py @@ -1,4 +1,5 @@ """Base class for Ring entity.""" + from typing import TypeVar from ring_doorbell.generic import RingGeneric diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index 73ec8349384..1d3dde97ae0 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -1,4 +1,5 @@ """Component providing HA switch support for Ring Door Bell/Chimes.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index ab77e0461bb..0c9c0196f11 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -1,4 +1,5 @@ """Component providing HA sensor support for Ring Door Bell/Chimes.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index b0a1e236df5..d2b93cf7154 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -1,4 +1,5 @@ """Component providing HA switch support for Ring Door Bell/Chimes.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/ripple/sensor.py b/homeassistant/components/ripple/sensor.py index bab765c289c..1b65ec7ae09 100644 --- a/homeassistant/components/ripple/sensor.py +++ b/homeassistant/components/ripple/sensor.py @@ -1,4 +1,5 @@ """Support for Ripple sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index d1e1c4f430c..2c55b474e00 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -1,4 +1,5 @@ """The Risco integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/risco/alarm_control_panel.py b/homeassistant/components/risco/alarm_control_panel.py index 8a233d0b5fe..580842e78ad 100644 --- a/homeassistant/components/risco/alarm_control_panel.py +++ b/homeassistant/components/risco/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Risco alarms.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/risco/binary_sensor.py b/homeassistant/components/risco/binary_sensor.py index ea7153b2aee..f2f01202240 100644 --- a/homeassistant/components/risco/binary_sensor.py +++ b/homeassistant/components/risco/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Risco alarm zones.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/risco/config_flow.py b/homeassistant/components/risco/config_flow.py index d18fb91e0c8..bd703ca5a6d 100644 --- a/homeassistant/components/risco/config_flow.py +++ b/homeassistant/components/risco/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Risco integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/risco/entity.py b/homeassistant/components/risco/entity.py index ac3c04cfc2e..b3a3cdd1d4d 100644 --- a/homeassistant/components/risco/entity.py +++ b/homeassistant/components/risco/entity.py @@ -1,4 +1,5 @@ """A risco entity base class.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/risco/sensor.py b/homeassistant/components/risco/sensor.py index 138c08c18f6..f4d6ddaf451 100644 --- a/homeassistant/components/risco/sensor.py +++ b/homeassistant/components/risco/sensor.py @@ -1,4 +1,5 @@ """Sensor for Risco Events.""" + from __future__ import annotations from collections.abc import Collection, Mapping diff --git a/homeassistant/components/risco/switch.py b/homeassistant/components/risco/switch.py index d22b2bb2192..c43b55b0233 100644 --- a/homeassistant/components/risco/switch.py +++ b/homeassistant/components/risco/switch.py @@ -1,4 +1,5 @@ """Support for bypassing Risco alarm zones.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rituals_perfume_genie/binary_sensor.py b/homeassistant/components/rituals_perfume_genie/binary_sensor.py index f33a687b88f..63666fc1aca 100644 --- a/homeassistant/components/rituals_perfume_genie/binary_sensor.py +++ b/homeassistant/components/rituals_perfume_genie/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Rituals Perfume Genie binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/rituals_perfume_genie/config_flow.py b/homeassistant/components/rituals_perfume_genie/config_flow.py index 069d8008945..7bff52fb864 100644 --- a/homeassistant/components/rituals_perfume_genie/config_flow.py +++ b/homeassistant/components/rituals_perfume_genie/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Rituals Perfume Genie integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rituals_perfume_genie/diagnostics.py b/homeassistant/components/rituals_perfume_genie/diagnostics.py index 75b622b48b1..bcc61a01ad6 100644 --- a/homeassistant/components/rituals_perfume_genie/diagnostics.py +++ b/homeassistant/components/rituals_perfume_genie/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Rituals Perfume Genie.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/rituals_perfume_genie/entity.py b/homeassistant/components/rituals_perfume_genie/entity.py index 83564f40488..35dbf639dd0 100644 --- a/homeassistant/components/rituals_perfume_genie/entity.py +++ b/homeassistant/components/rituals_perfume_genie/entity.py @@ -1,4 +1,5 @@ """Base class for Rituals Perfume Genie diffuser entity.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/rituals_perfume_genie/number.py b/homeassistant/components/rituals_perfume_genie/number.py index b21245645cf..0ac9c30f285 100644 --- a/homeassistant/components/rituals_perfume_genie/number.py +++ b/homeassistant/components/rituals_perfume_genie/number.py @@ -1,4 +1,5 @@ """Support for Rituals Perfume Genie numbers.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/rituals_perfume_genie/select.py b/homeassistant/components/rituals_perfume_genie/select.py index 0f4deba6e42..e93d6ae03ef 100644 --- a/homeassistant/components/rituals_perfume_genie/select.py +++ b/homeassistant/components/rituals_perfume_genie/select.py @@ -1,4 +1,5 @@ """Support for Rituals Perfume Genie numbers.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/rituals_perfume_genie/sensor.py b/homeassistant/components/rituals_perfume_genie/sensor.py index 4de3bb9235c..46faa8d73e9 100644 --- a/homeassistant/components/rituals_perfume_genie/sensor.py +++ b/homeassistant/components/rituals_perfume_genie/sensor.py @@ -1,4 +1,5 @@ """Support for Rituals Perfume Genie sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py index 8f93f4fe14d..6b88f4f26c1 100644 --- a/homeassistant/components/rituals_perfume_genie/switch.py +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -1,4 +1,5 @@ """Support for Rituals Perfume Genie switches.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/rmvtransport/sensor.py b/homeassistant/components/rmvtransport/sensor.py index ebe8f34c892..e53423d3b14 100644 --- a/homeassistant/components/rmvtransport/sensor.py +++ b/homeassistant/components/rmvtransport/sensor.py @@ -1,4 +1,5 @@ """Support for departure information for Rhein-Main public transport.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/roborock/__init__.py b/homeassistant/components/roborock/__init__.py index f4293213c00..55f25e6bdd2 100644 --- a/homeassistant/components/roborock/__init__.py +++ b/homeassistant/components/roborock/__init__.py @@ -1,4 +1,5 @@ """The Roborock component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/roborock/binary_sensor.py b/homeassistant/components/roborock/binary_sensor.py index e4e65288832..2c903e0bcc9 100644 --- a/homeassistant/components/roborock/binary_sensor.py +++ b/homeassistant/components/roborock/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Roborock sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/roborock/button.py b/homeassistant/components/roborock/button.py index e64b39c2383..4a37f1b3208 100644 --- a/homeassistant/components/roborock/button.py +++ b/homeassistant/components/roborock/button.py @@ -1,4 +1,5 @@ """Support for Roborock button.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/roborock/config_flow.py b/homeassistant/components/roborock/config_flow.py index 597798af2ea..ede9afc826d 100644 --- a/homeassistant/components/roborock/config_flow.py +++ b/homeassistant/components/roborock/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Roborock.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/roborock/const.py b/homeassistant/components/roborock/const.py index f163c9620d1..77f0be3363e 100644 --- a/homeassistant/components/roborock/const.py +++ b/homeassistant/components/roborock/const.py @@ -1,4 +1,5 @@ """Constants for Roborock.""" + from vacuum_map_parser_base.config.drawable import Drawable from homeassistant.const import Platform diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index 7154a36f7b8..e682b119069 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -1,4 +1,5 @@ """Roborock Coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/roborock/device.py b/homeassistant/components/roborock/device.py index 2921a372e00..01f26df56f3 100644 --- a/homeassistant/components/roborock/device.py +++ b/homeassistant/components/roborock/device.py @@ -1,4 +1,5 @@ """Support for Roborock device base class.""" + from typing import Any from roborock.api import AttributeCache, RoborockClient diff --git a/homeassistant/components/roborock/diagnostics.py b/homeassistant/components/roborock/diagnostics.py index e5fcc834267..79a9f0bafed 100644 --- a/homeassistant/components/roborock/diagnostics.py +++ b/homeassistant/components/roborock/diagnostics.py @@ -1,4 +1,5 @@ """Support for the Airzone diagnostics.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/roborock/models.py b/homeassistant/components/roborock/models.py index c1d32df2d6d..45b98fddbc5 100644 --- a/homeassistant/components/roborock/models.py +++ b/homeassistant/components/roborock/models.py @@ -1,4 +1,5 @@ """Roborock Models.""" + from dataclasses import dataclass from typing import Any diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index 3fdd10c97d5..b6e55708371 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -1,4 +1,5 @@ """Support for Roborock select.""" + from collections.abc import Callable from dataclasses import dataclass diff --git a/homeassistant/components/roborock/sensor.py b/homeassistant/components/roborock/sensor.py index 8d723ec57cd..e1f87ccf603 100644 --- a/homeassistant/components/roborock/sensor.py +++ b/homeassistant/components/roborock/sensor.py @@ -1,4 +1,5 @@ """Support for Roborock sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/roborock/switch.py b/homeassistant/components/roborock/switch.py index acd3e2613af..6c9c5e18bf0 100644 --- a/homeassistant/components/roborock/switch.py +++ b/homeassistant/components/roborock/switch.py @@ -1,4 +1,5 @@ """Support for Roborock switch.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/roborock/vacuum.py b/homeassistant/components/roborock/vacuum.py index 3684b584047..22d9353e2a2 100644 --- a/homeassistant/components/roborock/vacuum.py +++ b/homeassistant/components/roborock/vacuum.py @@ -1,4 +1,5 @@ """Support for Roborock vacuum class.""" + from typing import Any from roborock.code_mappings import RoborockStateCode diff --git a/homeassistant/components/rocketchat/notify.py b/homeassistant/components/rocketchat/notify.py index 30bcc6c4515..9b7b40873ce 100644 --- a/homeassistant/components/rocketchat/notify.py +++ b/homeassistant/components/rocketchat/notify.py @@ -1,4 +1,5 @@ """Rocket.Chat notification service.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index f31a07feb29..0620207a8ee 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -1,4 +1,5 @@ """Support for Roku.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/roku/binary_sensor.py b/homeassistant/components/roku/binary_sensor.py index 144fded24b9..f58c9f9263f 100644 --- a/homeassistant/components/roku/binary_sensor.py +++ b/homeassistant/components/roku/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Roku binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/roku/browse_media.py b/homeassistant/components/roku/browse_media.py index acaf2e5adbc..1ac37f10eb9 100644 --- a/homeassistant/components/roku/browse_media.py +++ b/homeassistant/components/roku/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/roku/config_flow.py b/homeassistant/components/roku/config_flow.py index 2e49dcb2f30..07c1afae9e2 100644 --- a/homeassistant/components/roku/config_flow.py +++ b/homeassistant/components/roku/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Roku.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/roku/coordinator.py b/homeassistant/components/roku/coordinator.py index a0bd9df238c..baef00b2596 100644 --- a/homeassistant/components/roku/coordinator.py +++ b/homeassistant/components/roku/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for Roku.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/roku/diagnostics.py b/homeassistant/components/roku/diagnostics.py index bdd32f251ea..6c6809ee33a 100644 --- a/homeassistant/components/roku/diagnostics.py +++ b/homeassistant/components/roku/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Roku.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/roku/entity.py b/homeassistant/components/roku/entity.py index b783831d4ec..259cb092cb8 100644 --- a/homeassistant/components/roku/entity.py +++ b/homeassistant/components/roku/entity.py @@ -1,4 +1,5 @@ """Base Entity for Roku.""" + from __future__ import annotations from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo diff --git a/homeassistant/components/roku/helpers.py b/homeassistant/components/roku/helpers.py index 60a3cbeec30..fc68e82c2d8 100644 --- a/homeassistant/components/roku/helpers.py +++ b/homeassistant/components/roku/helpers.py @@ -1,4 +1,5 @@ """Helpers for Roku.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 62a1a181459..4418f708a7f 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -1,4 +1,5 @@ """Support for the Roku media player.""" + from __future__ import annotations import datetime as dt diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index ef5350eb741..fa351e021e8 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -1,4 +1,5 @@ """Support for the Roku remote.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py index ef0f198f586..96f9b4c3ab8 100644 --- a/homeassistant/components/roku/select.py +++ b/homeassistant/components/roku/select.py @@ -1,4 +1,5 @@ """Support for Roku selects.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/roku/sensor.py b/homeassistant/components/roku/sensor.py index b462b8c531b..4d81a3cfe80 100644 --- a/homeassistant/components/roku/sensor.py +++ b/homeassistant/components/roku/sensor.py @@ -1,4 +1,5 @@ """Support for Roku sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/romy/config_flow.py b/homeassistant/components/romy/config_flow.py index 055c5b172bb..bccae667695 100644 --- a/homeassistant/components/romy/config_flow.py +++ b/homeassistant/components/romy/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ROMY integration.""" + from __future__ import annotations import romy diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 4e99fa7d8d0..40a5535d5af 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -1,4 +1,5 @@ """Roomba binary sensor entities.""" + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 57ccd02fae3..08973b415ee 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure roomba component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/roomba/const.py b/homeassistant/components/roomba/const.py index 71db96f8c21..331c0900682 100644 --- a/homeassistant/components/roomba/const.py +++ b/homeassistant/components/roomba/const.py @@ -1,4 +1,5 @@ """The roomba constants.""" + from homeassistant.const import Platform DOMAIN = "roomba" diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 38de3a7fb2b..73ef5a813b1 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -1,4 +1,5 @@ """Base class for iRobot devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/roomba/models.py b/homeassistant/components/roomba/models.py index 87610bed1ae..350495cae7b 100644 --- a/homeassistant/components/roomba/models.py +++ b/homeassistant/components/roomba/models.py @@ -1,4 +1,5 @@ """The roomba integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 465e87cde6b..bf76fc1c39f 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Roomba.""" + from collections.abc import Callable from dataclasses import dataclass diff --git a/homeassistant/components/roomba/vacuum.py b/homeassistant/components/roomba/vacuum.py index b6c0e893b1c..e4a83375ccc 100644 --- a/homeassistant/components/roomba/vacuum.py +++ b/homeassistant/components/roomba/vacuum.py @@ -1,4 +1,5 @@ """Support for Wi-Fi enabled iRobot Roombas.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/roon/__init__.py b/homeassistant/components/roon/__init__.py index f721f0bac40..272ad6f011b 100644 --- a/homeassistant/components/roon/__init__.py +++ b/homeassistant/components/roon/__init__.py @@ -1,4 +1,5 @@ """Roon (www.roonlabs.com) component.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index 5fce298a56b..c88b887c10e 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -1,4 +1,5 @@ """MediaPlayer platform for Roon integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/route53/__init__.py b/homeassistant/components/route53/__init__.py index 4f17b507560..92094b0b608 100644 --- a/homeassistant/components/route53/__init__.py +++ b/homeassistant/components/route53/__init__.py @@ -1,4 +1,5 @@ """Update the IP addresses of your Route53 DNS records.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index 3565b3baf0d..9f9c2238ba5 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -1,4 +1,5 @@ """Support for Rova garbage calendar.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/rpi_camera/camera.py b/homeassistant/components/rpi_camera/camera.py index 3f9b5fd5860..a8ebaaaca6f 100644 --- a/homeassistant/components/rpi_camera/camera.py +++ b/homeassistant/components/rpi_camera/camera.py @@ -1,4 +1,5 @@ """Camera platform that has a Raspberry Pi camera.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rpi_power/__init__.py b/homeassistant/components/rpi_power/__init__.py index 79504f17d65..31925784629 100644 --- a/homeassistant/components/rpi_power/__init__.py +++ b/homeassistant/components/rpi_power/__init__.py @@ -1,4 +1,5 @@ """The Raspberry Pi Power Supply Checker integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py index 9f02bdc46ea..c44bb65d79a 100644 --- a/homeassistant/components/rpi_power/config_flow.py +++ b/homeassistant/components/rpi_power/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Raspberry Pi Power Supply Checker.""" + from __future__ import annotations from collections.abc import Awaitable diff --git a/homeassistant/components/rss_feed_template/__init__.py b/homeassistant/components/rss_feed_template/__init__.py index 7b7cbb7e94f..8d2e47315ef 100644 --- a/homeassistant/components/rss_feed_template/__init__.py +++ b/homeassistant/components/rss_feed_template/__init__.py @@ -1,4 +1,5 @@ """Support to export sensor values via RSS feed.""" + from __future__ import annotations from html import escape diff --git a/homeassistant/components/rtorrent/sensor.py b/homeassistant/components/rtorrent/sensor.py index 2ef6786153a..099927f1893 100644 --- a/homeassistant/components/rtorrent/sensor.py +++ b/homeassistant/components/rtorrent/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the rtorrent BitTorrent client API.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rtsp_to_webrtc/config_flow.py b/homeassistant/components/rtsp_to_webrtc/config_flow.py index 7593c11d6f3..adab1a456d0 100644 --- a/homeassistant/components/rtsp_to_webrtc/config_flow.py +++ b/homeassistant/components/rtsp_to_webrtc/config_flow.py @@ -1,4 +1,5 @@ """Config flow for RTSPtoWebRTC.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ruckus_unleashed/config_flow.py b/homeassistant/components/ruckus_unleashed/config_flow.py index 745fb04b702..1a75b8ae139 100644 --- a/homeassistant/components/ruckus_unleashed/config_flow.py +++ b/homeassistant/components/ruckus_unleashed/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ruckus Unleashed integration.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/ruckus_unleashed/const.py b/homeassistant/components/ruckus_unleashed/const.py index 089981348b6..9076437b8c7 100644 --- a/homeassistant/components/ruckus_unleashed/const.py +++ b/homeassistant/components/ruckus_unleashed/const.py @@ -1,4 +1,5 @@ """Constants for the Ruckus Unleashed integration.""" + from homeassistant.const import Platform DOMAIN = "ruckus_unleashed" diff --git a/homeassistant/components/ruckus_unleashed/coordinator.py b/homeassistant/components/ruckus_unleashed/coordinator.py index 7c11aac7f68..989748af86e 100644 --- a/homeassistant/components/ruckus_unleashed/coordinator.py +++ b/homeassistant/components/ruckus_unleashed/coordinator.py @@ -1,4 +1,5 @@ """Ruckus Unleashed DataUpdateCoordinator.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/ruckus_unleashed/device_tracker.py b/homeassistant/components/ruckus_unleashed/device_tracker.py index 89cc22ef766..233e5cd4945 100644 --- a/homeassistant/components/ruckus_unleashed/device_tracker.py +++ b/homeassistant/components/ruckus_unleashed/device_tracker.py @@ -1,4 +1,5 @@ """Support for Ruckus Unleashed devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/russound_rio/media_player.py b/homeassistant/components/russound_rio/media_player.py index fc0dca341a8..74339153f69 100644 --- a/homeassistant/components/russound_rio/media_player.py +++ b/homeassistant/components/russound_rio/media_player.py @@ -1,4 +1,5 @@ """Support for Russound multizone controllers using RIO Protocol.""" + from __future__ import annotations from russound_rio import Russound diff --git a/homeassistant/components/russound_rnet/media_player.py b/homeassistant/components/russound_rnet/media_player.py index b19f4b9dfee..f9419f4be4f 100644 --- a/homeassistant/components/russound_rnet/media_player.py +++ b/homeassistant/components/russound_rnet/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with Russound via RNET Protocol.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ruuvi_gateway/__init__.py b/homeassistant/components/ruuvi_gateway/__init__.py index 59d37abbf7b..77b3e9b57de 100644 --- a/homeassistant/components/ruuvi_gateway/__init__.py +++ b/homeassistant/components/ruuvi_gateway/__init__.py @@ -1,4 +1,5 @@ """The Ruuvi Gateway integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ruuvi_gateway/bluetooth.py b/homeassistant/components/ruuvi_gateway/bluetooth.py index c4fbe474776..bdd1e21d491 100644 --- a/homeassistant/components/ruuvi_gateway/bluetooth.py +++ b/homeassistant/components/ruuvi_gateway/bluetooth.py @@ -1,4 +1,5 @@ """Bluetooth support for Ruuvi Gateway.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ruuvi_gateway/config_flow.py b/homeassistant/components/ruuvi_gateway/config_flow.py index 2b892161f8d..825f57b2cf2 100644 --- a/homeassistant/components/ruuvi_gateway/config_flow.py +++ b/homeassistant/components/ruuvi_gateway/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ruuvi Gateway integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ruuvi_gateway/const.py b/homeassistant/components/ruuvi_gateway/const.py index 80bebda105b..a323b911d1f 100644 --- a/homeassistant/components/ruuvi_gateway/const.py +++ b/homeassistant/components/ruuvi_gateway/const.py @@ -1,4 +1,5 @@ """Constants for the Ruuvi Gateway integration.""" + from datetime import timedelta DOMAIN = "ruuvi_gateway" diff --git a/homeassistant/components/ruuvi_gateway/coordinator.py b/homeassistant/components/ruuvi_gateway/coordinator.py index 38bc3b0e201..ba72dfe4cbc 100644 --- a/homeassistant/components/ruuvi_gateway/coordinator.py +++ b/homeassistant/components/ruuvi_gateway/coordinator.py @@ -1,4 +1,5 @@ """Update coordinator for Ruuvi Gateway.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ruuvi_gateway/models.py b/homeassistant/components/ruuvi_gateway/models.py index adb405f0bf8..3717ffdb25a 100644 --- a/homeassistant/components/ruuvi_gateway/models.py +++ b/homeassistant/components/ruuvi_gateway/models.py @@ -1,4 +1,5 @@ """Models for Ruuvi Gateway integration.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/ruuvi_gateway/schemata.py b/homeassistant/components/ruuvi_gateway/schemata.py index eec86cd129f..4662e07acbf 100644 --- a/homeassistant/components/ruuvi_gateway/schemata.py +++ b/homeassistant/components/ruuvi_gateway/schemata.py @@ -1,4 +1,5 @@ """Schemata for ruuvi_gateway.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/ruuvitag_ble/__init__.py b/homeassistant/components/ruuvitag_ble/__init__.py index 5e30820f837..031bed232bd 100644 --- a/homeassistant/components/ruuvitag_ble/__init__.py +++ b/homeassistant/components/ruuvitag_ble/__init__.py @@ -1,4 +1,5 @@ """The ruuvitag_ble integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ruuvitag_ble/config_flow.py b/homeassistant/components/ruuvitag_ble/config_flow.py index 4f61c548038..564f3c9d0e2 100644 --- a/homeassistant/components/ruuvitag_ble/config_flow.py +++ b/homeassistant/components/ruuvitag_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ruuvitag_ble.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ruuvitag_ble/sensor.py b/homeassistant/components/ruuvitag_ble/sensor.py index 128edd42b19..a098c263c5d 100644 --- a/homeassistant/components/ruuvitag_ble/sensor.py +++ b/homeassistant/components/ruuvitag_ble/sensor.py @@ -1,4 +1,5 @@ """Support for RuuviTag sensors.""" + from __future__ import annotations from sensor_state_data import ( diff --git a/homeassistant/components/rympro/__init__.py b/homeassistant/components/rympro/__init__.py index 7ac6de2fa0b..f24735f4ed0 100644 --- a/homeassistant/components/rympro/__init__.py +++ b/homeassistant/components/rympro/__init__.py @@ -1,4 +1,5 @@ """The Read Your Meter Pro integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/rympro/config_flow.py b/homeassistant/components/rympro/config_flow.py index c501ccfa994..f30e47f09a1 100644 --- a/homeassistant/components/rympro/config_flow.py +++ b/homeassistant/components/rympro/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Read Your Meter Pro integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/rympro/coordinator.py b/homeassistant/components/rympro/coordinator.py index 592c82adc68..f2e5162a0f0 100644 --- a/homeassistant/components/rympro/coordinator.py +++ b/homeassistant/components/rympro/coordinator.py @@ -1,4 +1,5 @@ """The Read Your Meter Pro integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/rympro/sensor.py b/homeassistant/components/rympro/sensor.py index a6b5b8df93d..8bb0af6e9ff 100644 --- a/homeassistant/components/rympro/sensor.py +++ b/homeassistant/components/rympro/sensor.py @@ -1,4 +1,5 @@ """Sensor for RymPro meters.""" + from __future__ import annotations from dataclasses import dataclass From ea89fa6b1e29afc823f58126abd5b0bff0d64d9f Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 8 Mar 2024 15:10:35 +0100 Subject: [PATCH 0553/1691] Allow duplicate names in different modbus entities (#112701) Allow duplicate names in different entities. --- homeassistant/components/modbus/validators.py | 6 +- tests/components/modbus/test_init.py | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index e31cbf79938..431d9de0cf1 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -302,13 +302,14 @@ def check_config(config: dict) -> dict: def validate_entity( hub_name: str, + component: str, entity: dict, minimum_scan_interval: int, ent_names: set, ent_addr: set, ) -> bool: """Validate entity.""" - name = entity[CONF_NAME] + name = f"{component}.{entity[CONF_NAME]}" addr = f"{hub_name}{entity[CONF_ADDRESS]}" scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) if 0 < scan_interval < 5: @@ -369,7 +370,7 @@ def check_config(config: dict) -> dict: if not validate_modbus(hub, hub_name_inx): del config[hub_inx] continue - for _component, conf_key in PLATFORMS: + for component, conf_key in PLATFORMS: if conf_key not in hub: continue entity_inx = 0 @@ -378,6 +379,7 @@ def check_config(config: dict) -> dict: while entity_inx < len(entities): if not validate_entity( hub[CONF_NAME], + component, entities[entity_inx], minimum_scan_interval, ent_names, diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 40a1253b9e1..c3a4c35433b 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -859,6 +859,30 @@ async def test_duplicate_fan_mode_validator(do_config) -> None: ], 2, ), + ( + [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 1179, + CONF_SLAVE: 0, + }, + ], + }, + ], + 1, + ), ], ) async def test_duplicate_addresses(do_config, sensor_cnt) -> None: @@ -868,6 +892,41 @@ async def test_duplicate_addresses(do_config, sensor_cnt) -> None: assert len(do_config[use_inx][CONF_SENSORS]) == sensor_cnt +@pytest.mark.parametrize( + "do_config", + [ + [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 117, + CONF_SLAVE: 0, + }, + ], + CONF_BINARY_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME + "1", + CONF_ADDRESS: 1179, + CONF_SLAVE: 0, + }, + ], + }, + ], + ], +) +async def test_no_duplicate_names(do_config) -> None: + """Test duplicate entity validator.""" + check_config(do_config) + assert len(do_config[0][CONF_SENSORS]) == 1 + assert len(do_config[0][CONF_BINARY_SENSORS]) == 1 + + @pytest.mark.parametrize( "do_config", [ From 062cc4bfce7bba1cc6b9b3e90a30841c5691a084 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:35:23 +0100 Subject: [PATCH 0554/1691] Add empty line after module docstring [t-v] (#112705) --- homeassistant/components/tado/__init__.py | 1 + homeassistant/components/tado/binary_sensor.py | 1 + homeassistant/components/tado/config_flow.py | 1 + homeassistant/components/tado/device_tracker.py | 1 + homeassistant/components/tado/entity.py | 1 + homeassistant/components/tado/sensor.py | 1 + homeassistant/components/tag/__init__.py | 1 + homeassistant/components/tag/trigger.py | 1 + homeassistant/components/tailscale/__init__.py | 1 + homeassistant/components/tailscale/binary_sensor.py | 1 + homeassistant/components/tailscale/config_flow.py | 1 + homeassistant/components/tailscale/const.py | 1 + homeassistant/components/tailscale/coordinator.py | 1 + homeassistant/components/tailscale/diagnostics.py | 1 + homeassistant/components/tailscale/sensor.py | 1 + homeassistant/components/tailwind/__init__.py | 1 + homeassistant/components/tailwind/binary_sensor.py | 1 + homeassistant/components/tailwind/button.py | 1 + homeassistant/components/tailwind/config_flow.py | 1 + homeassistant/components/tailwind/const.py | 1 + homeassistant/components/tailwind/coordinator.py | 1 + homeassistant/components/tailwind/cover.py | 1 + homeassistant/components/tailwind/diagnostics.py | 1 + homeassistant/components/tailwind/entity.py | 1 + homeassistant/components/tailwind/number.py | 1 + homeassistant/components/tami4/__init__.py | 1 + homeassistant/components/tami4/button.py | 1 + homeassistant/components/tami4/config_flow.py | 1 + homeassistant/components/tami4/coordinator.py | 1 + homeassistant/components/tami4/entity.py | 1 + homeassistant/components/tank_utility/sensor.py | 1 + homeassistant/components/tankerkoenig/__init__.py | 1 + homeassistant/components/tankerkoenig/binary_sensor.py | 1 + homeassistant/components/tankerkoenig/config_flow.py | 1 + homeassistant/components/tankerkoenig/coordinator.py | 1 + homeassistant/components/tankerkoenig/diagnostics.py | 1 + homeassistant/components/tankerkoenig/entity.py | 1 + homeassistant/components/tankerkoenig/sensor.py | 1 + homeassistant/components/tapsaff/binary_sensor.py | 1 + homeassistant/components/tasmota/__init__.py | 1 + homeassistant/components/tasmota/binary_sensor.py | 1 + homeassistant/components/tasmota/config_flow.py | 1 + homeassistant/components/tasmota/const.py | 1 + homeassistant/components/tasmota/cover.py | 1 + homeassistant/components/tasmota/device_trigger.py | 1 + homeassistant/components/tasmota/discovery.py | 1 + homeassistant/components/tasmota/fan.py | 1 + homeassistant/components/tasmota/light.py | 1 + homeassistant/components/tasmota/mixins.py | 1 + homeassistant/components/tasmota/sensor.py | 1 + homeassistant/components/tasmota/switch.py | 1 + homeassistant/components/tautulli/__init__.py | 1 + homeassistant/components/tautulli/config_flow.py | 1 + homeassistant/components/tautulli/const.py | 1 + homeassistant/components/tautulli/coordinator.py | 1 + homeassistant/components/tautulli/sensor.py | 1 + homeassistant/components/tcp/binary_sensor.py | 1 + homeassistant/components/tcp/common.py | 1 + homeassistant/components/tcp/const.py | 1 + homeassistant/components/tcp/model.py | 1 + homeassistant/components/tcp/sensor.py | 1 + homeassistant/components/technove/__init__.py | 1 + homeassistant/components/technove/binary_sensor.py | 1 + homeassistant/components/technove/const.py | 1 + homeassistant/components/technove/coordinator.py | 1 + homeassistant/components/technove/entity.py | 1 + homeassistant/components/technove/helpers.py | 1 + homeassistant/components/technove/sensor.py | 1 + homeassistant/components/technove/switch.py | 1 + homeassistant/components/ted5000/sensor.py | 1 + homeassistant/components/tedee/const.py | 1 + homeassistant/components/tedee/coordinator.py | 1 + homeassistant/components/tedee/diagnostics.py | 1 + homeassistant/components/tedee/lock.py | 1 + homeassistant/components/telegram/notify.py | 1 + homeassistant/components/telegram_bot/__init__.py | 1 + homeassistant/components/tellduslive/binary_sensor.py | 1 + homeassistant/components/tellduslive/const.py | 1 + homeassistant/components/tellduslive/cover.py | 1 + homeassistant/components/tellduslive/entry.py | 1 + homeassistant/components/tellduslive/sensor.py | 1 + homeassistant/components/tellduslive/switch.py | 1 + homeassistant/components/tellstick/cover.py | 1 + homeassistant/components/tellstick/light.py | 1 + homeassistant/components/tellstick/sensor.py | 1 + homeassistant/components/tellstick/switch.py | 1 + homeassistant/components/telnet/switch.py | 1 + homeassistant/components/temper/sensor.py | 1 + homeassistant/components/template/__init__.py | 1 + homeassistant/components/template/alarm_control_panel.py | 1 + homeassistant/components/template/binary_sensor.py | 1 + homeassistant/components/template/button.py | 1 + homeassistant/components/template/config_flow.py | 1 + homeassistant/components/template/coordinator.py | 1 + homeassistant/components/template/cover.py | 1 + homeassistant/components/template/fan.py | 1 + homeassistant/components/template/image.py | 1 + homeassistant/components/template/light.py | 1 + homeassistant/components/template/lock.py | 1 + homeassistant/components/template/number.py | 1 + homeassistant/components/template/select.py | 1 + homeassistant/components/template/sensor.py | 1 + homeassistant/components/template/switch.py | 1 + homeassistant/components/template/template_entity.py | 1 + homeassistant/components/template/trigger.py | 1 + homeassistant/components/template/trigger_entity.py | 1 + homeassistant/components/template/vacuum.py | 1 + homeassistant/components/template/weather.py | 1 + homeassistant/components/tensorflow/image_processing.py | 1 + homeassistant/components/tesla_wall_connector/__init__.py | 1 + homeassistant/components/tesla_wall_connector/binary_sensor.py | 1 + homeassistant/components/tesla_wall_connector/config_flow.py | 1 + homeassistant/components/tesla_wall_connector/sensor.py | 1 + homeassistant/components/teslemetry/climate.py | 1 + homeassistant/components/teslemetry/config_flow.py | 1 + homeassistant/components/teslemetry/const.py | 1 + homeassistant/components/teslemetry/coordinator.py | 1 + homeassistant/components/teslemetry/models.py | 1 + homeassistant/components/teslemetry/sensor.py | 1 + homeassistant/components/tessie/__init__.py | 1 + homeassistant/components/tessie/binary_sensor.py | 1 + homeassistant/components/tessie/button.py | 1 + homeassistant/components/tessie/climate.py | 1 + homeassistant/components/tessie/config_flow.py | 1 + homeassistant/components/tessie/const.py | 1 + homeassistant/components/tessie/coordinator.py | 1 + homeassistant/components/tessie/cover.py | 1 + homeassistant/components/tessie/device_tracker.py | 1 + homeassistant/components/tessie/lock.py | 1 + homeassistant/components/tessie/media_player.py | 1 + homeassistant/components/tessie/models.py | 1 + homeassistant/components/tessie/number.py | 1 + homeassistant/components/tessie/select.py | 1 + homeassistant/components/tessie/sensor.py | 1 + homeassistant/components/tessie/switch.py | 1 + homeassistant/components/tessie/update.py | 1 + homeassistant/components/text/__init__.py | 1 + homeassistant/components/text/device_action.py | 1 + homeassistant/components/text/reproduce_state.py | 1 + homeassistant/components/tfiac/climate.py | 1 + homeassistant/components/thermobeacon/__init__.py | 1 + homeassistant/components/thermobeacon/config_flow.py | 1 + homeassistant/components/thermobeacon/device.py | 1 + homeassistant/components/thermobeacon/sensor.py | 1 + homeassistant/components/thermopro/__init__.py | 1 + homeassistant/components/thermopro/config_flow.py | 1 + homeassistant/components/thermopro/sensor.py | 1 + homeassistant/components/thermoworks_smoke/sensor.py | 1 + homeassistant/components/thethingsnetwork/sensor.py | 1 + homeassistant/components/thinkingcleaner/sensor.py | 1 + homeassistant/components/thinkingcleaner/switch.py | 1 + homeassistant/components/thomson/device_tracker.py | 1 + homeassistant/components/thread/__init__.py | 1 + homeassistant/components/thread/config_flow.py | 1 + homeassistant/components/thread/dataset_store.py | 1 + homeassistant/components/thread/discovery.py | 1 + homeassistant/components/thread/websocket_api.py | 1 + homeassistant/components/threshold/binary_sensor.py | 1 + homeassistant/components/threshold/config_flow.py | 1 + homeassistant/components/tibber/config_flow.py | 1 + homeassistant/components/tibber/diagnostics.py | 1 + homeassistant/components/tibber/notify.py | 1 + homeassistant/components/tibber/sensor.py | 1 + homeassistant/components/tikteck/light.py | 1 + homeassistant/components/tile/__init__.py | 1 + homeassistant/components/tile/config_flow.py | 1 + homeassistant/components/tile/device_tracker.py | 1 + homeassistant/components/tile/diagnostics.py | 1 + homeassistant/components/tilt_ble/__init__.py | 1 + homeassistant/components/tilt_ble/config_flow.py | 1 + homeassistant/components/tilt_ble/sensor.py | 1 + homeassistant/components/time/__init__.py | 1 + homeassistant/components/time_date/__init__.py | 1 + homeassistant/components/time_date/config_flow.py | 1 + homeassistant/components/time_date/const.py | 1 + homeassistant/components/time_date/sensor.py | 1 + homeassistant/components/timer/__init__.py | 1 + homeassistant/components/timer/reproduce_state.py | 1 + homeassistant/components/tmb/sensor.py | 1 + homeassistant/components/tod/__init__.py | 1 + homeassistant/components/tod/binary_sensor.py | 1 + homeassistant/components/tod/config_flow.py | 1 + homeassistant/components/todo/intent.py | 1 + homeassistant/components/todoist/calendar.py | 1 + homeassistant/components/todoist/const.py | 1 + homeassistant/components/todoist/coordinator.py | 1 + homeassistant/components/todoist/types.py | 1 + homeassistant/components/tolo/climate.py | 1 + homeassistant/components/tolo/light.py | 1 + homeassistant/components/tolo/number.py | 1 + homeassistant/components/tolo/sensor.py | 1 + homeassistant/components/tomato/device_tracker.py | 1 + homeassistant/components/tomorrowio/__init__.py | 1 + homeassistant/components/tomorrowio/config_flow.py | 1 + homeassistant/components/tomorrowio/const.py | 1 + homeassistant/components/tomorrowio/sensor.py | 1 + homeassistant/components/tomorrowio/weather.py | 1 + homeassistant/components/toon/binary_sensor.py | 1 + homeassistant/components/toon/climate.py | 1 + homeassistant/components/toon/config_flow.py | 1 + homeassistant/components/toon/const.py | 1 + homeassistant/components/toon/coordinator.py | 1 + homeassistant/components/toon/helpers.py | 1 + homeassistant/components/toon/models.py | 1 + homeassistant/components/toon/oauth2.py | 1 + homeassistant/components/toon/sensor.py | 1 + homeassistant/components/toon/switch.py | 1 + homeassistant/components/torque/sensor.py | 1 + homeassistant/components/totalconnect/alarm_control_panel.py | 1 + homeassistant/components/totalconnect/config_flow.py | 1 + homeassistant/components/totalconnect/diagnostics.py | 1 + homeassistant/components/touchline/climate.py | 1 + homeassistant/components/tplink/__init__.py | 1 + homeassistant/components/tplink/config_flow.py | 1 + homeassistant/components/tplink/const.py | 1 + homeassistant/components/tplink/coordinator.py | 1 + homeassistant/components/tplink/diagnostics.py | 1 + homeassistant/components/tplink/entity.py | 1 + homeassistant/components/tplink/light.py | 1 + homeassistant/components/tplink/models.py | 1 + homeassistant/components/tplink/sensor.py | 1 + homeassistant/components/tplink/switch.py | 1 + homeassistant/components/tplink_lte/__init__.py | 1 + homeassistant/components/tplink_lte/notify.py | 1 + homeassistant/components/tplink_omada/__init__.py | 1 + homeassistant/components/tplink_omada/binary_sensor.py | 1 + homeassistant/components/tplink_omada/config_flow.py | 1 + homeassistant/components/tplink_omada/entity.py | 1 + homeassistant/components/tplink_omada/switch.py | 1 + homeassistant/components/tplink_omada/update.py | 1 + homeassistant/components/traccar/__init__.py | 1 + homeassistant/components/traccar/config_flow.py | 1 + homeassistant/components/traccar/device_tracker.py | 1 + homeassistant/components/traccar_server/__init__.py | 1 + homeassistant/components/traccar_server/config_flow.py | 1 + homeassistant/components/traccar_server/const.py | 1 + homeassistant/components/traccar_server/coordinator.py | 1 + homeassistant/components/traccar_server/device_tracker.py | 1 + homeassistant/components/traccar_server/diagnostics.py | 1 + homeassistant/components/traccar_server/entity.py | 1 + homeassistant/components/traccar_server/helpers.py | 1 + homeassistant/components/trace/__init__.py | 1 + homeassistant/components/trace/models.py | 1 + homeassistant/components/tractive/__init__.py | 1 + homeassistant/components/tractive/binary_sensor.py | 1 + homeassistant/components/tractive/config_flow.py | 1 + homeassistant/components/tractive/device_tracker.py | 1 + homeassistant/components/tractive/diagnostics.py | 1 + homeassistant/components/tractive/entity.py | 1 + homeassistant/components/tractive/sensor.py | 1 + homeassistant/components/tractive/switch.py | 1 + homeassistant/components/tradfri/__init__.py | 1 + homeassistant/components/tradfri/base_class.py | 1 + homeassistant/components/tradfri/config_flow.py | 1 + homeassistant/components/tradfri/coordinator.py | 1 + homeassistant/components/tradfri/cover.py | 1 + homeassistant/components/tradfri/diagnostics.py | 1 + homeassistant/components/tradfri/light.py | 1 + homeassistant/components/tradfri/sensor.py | 1 + homeassistant/components/tradfri/switch.py | 1 + homeassistant/components/trafikverket_camera/__init__.py | 1 + homeassistant/components/trafikverket_camera/binary_sensor.py | 1 + homeassistant/components/trafikverket_camera/camera.py | 1 + homeassistant/components/trafikverket_camera/config_flow.py | 1 + homeassistant/components/trafikverket_camera/const.py | 1 + homeassistant/components/trafikverket_camera/coordinator.py | 1 + homeassistant/components/trafikverket_camera/entity.py | 1 + homeassistant/components/trafikverket_camera/sensor.py | 1 + homeassistant/components/trafikverket_ferry/__init__.py | 1 + homeassistant/components/trafikverket_ferry/config_flow.py | 1 + homeassistant/components/trafikverket_ferry/const.py | 1 + homeassistant/components/trafikverket_ferry/coordinator.py | 1 + homeassistant/components/trafikverket_ferry/sensor.py | 1 + homeassistant/components/trafikverket_ferry/util.py | 1 + homeassistant/components/trafikverket_train/__init__.py | 1 + homeassistant/components/trafikverket_train/config_flow.py | 1 + homeassistant/components/trafikverket_train/const.py | 1 + homeassistant/components/trafikverket_train/coordinator.py | 1 + homeassistant/components/trafikverket_train/sensor.py | 1 + homeassistant/components/trafikverket_train/util.py | 1 + homeassistant/components/trafikverket_weatherstation/__init__.py | 1 + .../components/trafikverket_weatherstation/config_flow.py | 1 + homeassistant/components/trafikverket_weatherstation/const.py | 1 + .../components/trafikverket_weatherstation/coordinator.py | 1 + homeassistant/components/trafikverket_weatherstation/sensor.py | 1 + homeassistant/components/transmission/__init__.py | 1 + homeassistant/components/transmission/config_flow.py | 1 + homeassistant/components/transmission/const.py | 1 + homeassistant/components/transmission/coordinator.py | 1 + homeassistant/components/transmission/errors.py | 1 + homeassistant/components/transmission/sensor.py | 1 + homeassistant/components/transmission/switch.py | 1 + homeassistant/components/transport_nsw/sensor.py | 1 + homeassistant/components/travisci/sensor.py | 1 + homeassistant/components/trend/__init__.py | 1 + homeassistant/components/trend/binary_sensor.py | 1 + homeassistant/components/trend/config_flow.py | 1 + homeassistant/components/tts/__init__.py | 1 + homeassistant/components/tts/helper.py | 1 + homeassistant/components/tts/legacy.py | 1 + homeassistant/components/tts/media_source.py | 1 + homeassistant/components/tts/models.py | 1 + homeassistant/components/tts/notify.py | 1 + homeassistant/components/tuya/__init__.py | 1 + homeassistant/components/tuya/alarm_control_panel.py | 1 + homeassistant/components/tuya/base.py | 1 + homeassistant/components/tuya/binary_sensor.py | 1 + homeassistant/components/tuya/button.py | 1 + homeassistant/components/tuya/camera.py | 1 + homeassistant/components/tuya/climate.py | 1 + homeassistant/components/tuya/config_flow.py | 1 + homeassistant/components/tuya/const.py | 1 + homeassistant/components/tuya/cover.py | 1 + homeassistant/components/tuya/diagnostics.py | 1 + homeassistant/components/tuya/fan.py | 1 + homeassistant/components/tuya/humidifier.py | 1 + homeassistant/components/tuya/light.py | 1 + homeassistant/components/tuya/number.py | 1 + homeassistant/components/tuya/scene.py | 1 + homeassistant/components/tuya/select.py | 1 + homeassistant/components/tuya/sensor.py | 1 + homeassistant/components/tuya/siren.py | 1 + homeassistant/components/tuya/switch.py | 1 + homeassistant/components/tuya/util.py | 1 + homeassistant/components/tuya/vacuum.py | 1 + homeassistant/components/twentemilieu/__init__.py | 1 + homeassistant/components/twentemilieu/calendar.py | 1 + homeassistant/components/twentemilieu/config_flow.py | 1 + homeassistant/components/twentemilieu/const.py | 1 + homeassistant/components/twentemilieu/diagnostics.py | 1 + homeassistant/components/twentemilieu/entity.py | 1 + homeassistant/components/twentemilieu/sensor.py | 1 + homeassistant/components/twilio/__init__.py | 1 + homeassistant/components/twilio/config_flow.py | 1 + homeassistant/components/twilio_call/notify.py | 1 + homeassistant/components/twilio_sms/notify.py | 1 + homeassistant/components/twinkly/config_flow.py | 1 + homeassistant/components/twinkly/diagnostics.py | 1 + homeassistant/components/twinkly/light.py | 1 + homeassistant/components/twitch/__init__.py | 1 + homeassistant/components/twitch/config_flow.py | 1 + homeassistant/components/twitch/sensor.py | 1 + homeassistant/components/twitter/notify.py | 1 + homeassistant/components/ubus/device_tracker.py | 1 + homeassistant/components/ue_smart_radio/media_player.py | 1 + homeassistant/components/uk_transport/sensor.py | 1 + homeassistant/components/ukraine_alarm/__init__.py | 1 + homeassistant/components/ukraine_alarm/binary_sensor.py | 1 + homeassistant/components/ukraine_alarm/config_flow.py | 1 + homeassistant/components/ukraine_alarm/const.py | 1 + homeassistant/components/unifi/button.py | 1 + homeassistant/components/unifi/config_flow.py | 1 + homeassistant/components/unifi/diagnostics.py | 1 + homeassistant/components/unifi/entity.py | 1 + homeassistant/components/unifi/errors.py | 1 + homeassistant/components/unifi/hub/config.py | 1 + homeassistant/components/unifi/hub/hub.py | 1 + homeassistant/components/unifi/image.py | 1 + homeassistant/components/unifi/sensor.py | 1 + homeassistant/components/unifi/services.py | 1 + homeassistant/components/unifi/switch.py | 1 + homeassistant/components/unifi/update.py | 1 + homeassistant/components/unifi_direct/device_tracker.py | 1 + homeassistant/components/unifiled/light.py | 1 + homeassistant/components/unifiprotect/__init__.py | 1 + homeassistant/components/unifiprotect/binary_sensor.py | 1 + homeassistant/components/unifiprotect/button.py | 1 + homeassistant/components/unifiprotect/camera.py | 1 + homeassistant/components/unifiprotect/config_flow.py | 1 + homeassistant/components/unifiprotect/data.py | 1 + homeassistant/components/unifiprotect/diagnostics.py | 1 + homeassistant/components/unifiprotect/discovery.py | 1 + homeassistant/components/unifiprotect/entity.py | 1 + homeassistant/components/unifiprotect/light.py | 1 + homeassistant/components/unifiprotect/lock.py | 1 + homeassistant/components/unifiprotect/media_player.py | 1 + homeassistant/components/unifiprotect/migrate.py | 1 + homeassistant/components/unifiprotect/models.py | 1 + homeassistant/components/unifiprotect/number.py | 1 + homeassistant/components/unifiprotect/select.py | 1 + homeassistant/components/unifiprotect/sensor.py | 1 + homeassistant/components/unifiprotect/services.py | 1 + homeassistant/components/unifiprotect/switch.py | 1 + homeassistant/components/unifiprotect/text.py | 1 + homeassistant/components/unifiprotect/utils.py | 1 + homeassistant/components/unifiprotect/views.py | 1 + homeassistant/components/universal/media_player.py | 1 + homeassistant/components/upb/light.py | 1 + homeassistant/components/upb/scene.py | 1 + homeassistant/components/upc_connect/device_tracker.py | 1 + homeassistant/components/upcloud/__init__.py | 1 + homeassistant/components/update/__init__.py | 1 + homeassistant/components/update/const.py | 1 + homeassistant/components/update/device_trigger.py | 1 + homeassistant/components/update/significant_change.py | 1 + homeassistant/components/upnp/__init__.py | 1 + homeassistant/components/upnp/binary_sensor.py | 1 + homeassistant/components/upnp/config_flow.py | 1 + homeassistant/components/upnp/const.py | 1 + homeassistant/components/upnp/device.py | 1 + homeassistant/components/upnp/entity.py | 1 + homeassistant/components/upnp/sensor.py | 1 + homeassistant/components/uptime/__init__.py | 1 + homeassistant/components/uptime/config_flow.py | 1 + homeassistant/components/uptime/const.py | 1 + homeassistant/components/uptime/sensor.py | 1 + homeassistant/components/uptimerobot/__init__.py | 1 + homeassistant/components/uptimerobot/binary_sensor.py | 1 + homeassistant/components/uptimerobot/config_flow.py | 1 + homeassistant/components/uptimerobot/const.py | 1 + homeassistant/components/uptimerobot/coordinator.py | 1 + homeassistant/components/uptimerobot/diagnostics.py | 1 + homeassistant/components/uptimerobot/entity.py | 1 + homeassistant/components/uptimerobot/sensor.py | 1 + homeassistant/components/uptimerobot/switch.py | 1 + homeassistant/components/usb/__init__.py | 1 + homeassistant/components/usb/models.py | 1 + homeassistant/components/usb/utils.py | 1 + homeassistant/components/usgs_earthquakes_feed/geo_location.py | 1 + homeassistant/components/utility_meter/__init__.py | 1 + homeassistant/components/utility_meter/config_flow.py | 1 + homeassistant/components/utility_meter/select.py | 1 + homeassistant/components/utility_meter/sensor.py | 1 + homeassistant/components/uvc/camera.py | 1 + homeassistant/components/v2c/__init__.py | 1 + homeassistant/components/v2c/binary_sensor.py | 1 + homeassistant/components/v2c/config_flow.py | 1 + homeassistant/components/v2c/coordinator.py | 1 + homeassistant/components/v2c/entity.py | 1 + homeassistant/components/v2c/number.py | 1 + homeassistant/components/v2c/sensor.py | 1 + homeassistant/components/v2c/switch.py | 1 + homeassistant/components/vacuum/__init__.py | 1 + homeassistant/components/vacuum/device_action.py | 1 + homeassistant/components/vacuum/device_condition.py | 1 + homeassistant/components/vacuum/device_trigger.py | 1 + homeassistant/components/vacuum/reproduce_state.py | 1 + homeassistant/components/vacuum/significant_change.py | 1 + homeassistant/components/vallox/__init__.py | 1 + homeassistant/components/vallox/binary_sensor.py | 1 + homeassistant/components/vallox/config_flow.py | 1 + homeassistant/components/vallox/date.py | 1 + homeassistant/components/vallox/fan.py | 1 + homeassistant/components/vallox/number.py | 1 + homeassistant/components/vallox/sensor.py | 1 + homeassistant/components/vallox/switch.py | 1 + homeassistant/components/valve/__init__.py | 1 + homeassistant/components/vasttrafik/sensor.py | 1 + homeassistant/components/velbus/__init__.py | 1 + homeassistant/components/velbus/binary_sensor.py | 1 + homeassistant/components/velbus/button.py | 1 + homeassistant/components/velbus/climate.py | 1 + homeassistant/components/velbus/config_flow.py | 1 + homeassistant/components/velbus/const.py | 1 + homeassistant/components/velbus/cover.py | 1 + homeassistant/components/velbus/diagnostics.py | 1 + homeassistant/components/velbus/entity.py | 1 + homeassistant/components/velbus/light.py | 1 + homeassistant/components/velbus/select.py | 1 + homeassistant/components/velbus/sensor.py | 1 + homeassistant/components/velbus/switch.py | 1 + homeassistant/components/velux/__init__.py | 1 + homeassistant/components/velux/config_flow.py | 1 + homeassistant/components/velux/const.py | 1 + homeassistant/components/velux/cover.py | 1 + homeassistant/components/velux/light.py | 1 + homeassistant/components/velux/scene.py | 1 + homeassistant/components/venstar/__init__.py | 1 + homeassistant/components/venstar/binary_sensor.py | 1 + homeassistant/components/venstar/climate.py | 1 + homeassistant/components/venstar/config_flow.py | 1 + homeassistant/components/venstar/sensor.py | 1 + homeassistant/components/vera/__init__.py | 1 + homeassistant/components/vera/binary_sensor.py | 1 + homeassistant/components/vera/climate.py | 1 + homeassistant/components/vera/common.py | 1 + homeassistant/components/vera/config_flow.py | 1 + homeassistant/components/vera/cover.py | 1 + homeassistant/components/vera/light.py | 1 + homeassistant/components/vera/lock.py | 1 + homeassistant/components/vera/scene.py | 1 + homeassistant/components/vera/sensor.py | 1 + homeassistant/components/vera/switch.py | 1 + homeassistant/components/verisure/__init__.py | 1 + homeassistant/components/verisure/alarm_control_panel.py | 1 + homeassistant/components/verisure/binary_sensor.py | 1 + homeassistant/components/verisure/camera.py | 1 + homeassistant/components/verisure/config_flow.py | 1 + homeassistant/components/verisure/const.py | 1 + homeassistant/components/verisure/coordinator.py | 1 + homeassistant/components/verisure/diagnostics.py | 1 + homeassistant/components/verisure/lock.py | 1 + homeassistant/components/verisure/sensor.py | 1 + homeassistant/components/verisure/switch.py | 1 + homeassistant/components/versasense/sensor.py | 1 + homeassistant/components/versasense/switch.py | 1 + homeassistant/components/version/__init__.py | 1 + homeassistant/components/version/binary_sensor.py | 1 + homeassistant/components/version/config_flow.py | 1 + homeassistant/components/version/const.py | 1 + homeassistant/components/version/coordinator.py | 1 + homeassistant/components/version/diagnostics.py | 1 + homeassistant/components/version/sensor.py | 1 + homeassistant/components/vesync/config_flow.py | 1 + homeassistant/components/vesync/diagnostics.py | 1 + homeassistant/components/vesync/fan.py | 1 + homeassistant/components/vesync/sensor.py | 1 + homeassistant/components/viaggiatreno/sensor.py | 1 + homeassistant/components/vicare/__init__.py | 1 + homeassistant/components/vicare/binary_sensor.py | 1 + homeassistant/components/vicare/button.py | 1 + homeassistant/components/vicare/climate.py | 1 + homeassistant/components/vicare/config_flow.py | 1 + homeassistant/components/vicare/diagnostics.py | 1 + homeassistant/components/vicare/entity.py | 1 + homeassistant/components/vicare/number.py | 1 + homeassistant/components/vicare/sensor.py | 1 + homeassistant/components/vicare/types.py | 1 + homeassistant/components/vicare/water_heater.py | 1 + homeassistant/components/vilfo/__init__.py | 1 + homeassistant/components/vilfo/const.py | 1 + homeassistant/components/vilfo/sensor.py | 1 + homeassistant/components/vivotek/camera.py | 1 + homeassistant/components/vizio/__init__.py | 1 + homeassistant/components/vizio/config_flow.py | 1 + homeassistant/components/vizio/const.py | 1 + homeassistant/components/vizio/media_player.py | 1 + homeassistant/components/vlc/media_player.py | 1 + homeassistant/components/vlc_telnet/__init__.py | 1 + homeassistant/components/vlc_telnet/config_flow.py | 1 + homeassistant/components/vlc_telnet/media_player.py | 1 + homeassistant/components/vodafone_station/button.py | 1 + homeassistant/components/vodafone_station/config_flow.py | 1 + homeassistant/components/vodafone_station/coordinator.py | 1 + homeassistant/components/vodafone_station/device_tracker.py | 1 + homeassistant/components/vodafone_station/sensor.py | 1 + homeassistant/components/voip/__init__.py | 1 + homeassistant/components/voip/config_flow.py | 1 + homeassistant/components/voip/devices.py | 1 + homeassistant/components/voip/voip.py | 1 + homeassistant/components/volkszaehler/sensor.py | 1 + homeassistant/components/volumio/config_flow.py | 1 + homeassistant/components/volumio/media_player.py | 1 + homeassistant/components/volvooncall/binary_sensor.py | 1 + homeassistant/components/volvooncall/config_flow.py | 1 + homeassistant/components/volvooncall/device_tracker.py | 1 + homeassistant/components/volvooncall/errors.py | 1 + homeassistant/components/volvooncall/sensor.py | 1 + homeassistant/components/volvooncall/switch.py | 1 + homeassistant/components/vulcan/calendar.py | 1 + homeassistant/components/vulcan/config_flow.py | 1 + homeassistant/components/vultr/__init__.py | 1 + homeassistant/components/vultr/binary_sensor.py | 1 + homeassistant/components/vultr/sensor.py | 1 + homeassistant/components/vultr/switch.py | 1 + 555 files changed, 555 insertions(+) diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index c7225caaff9..f59f15625c1 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -1,4 +1,5 @@ """Support for the (unofficial) Tado API.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index c033ef62e03..65c0b84906c 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Tado sensors for each zone.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tado/config_flow.py b/homeassistant/components/tado/config_flow.py index 911cf6a7aac..2074b62b8d0 100644 --- a/homeassistant/components/tado/config_flow.py +++ b/homeassistant/components/tado/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tado integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tado/device_tracker.py b/homeassistant/components/tado/device_tracker.py index eb57aeaec79..dea92ae3890 100644 --- a/homeassistant/components/tado/device_tracker.py +++ b/homeassistant/components/tado/device_tracker.py @@ -1,4 +1,5 @@ """Support for Tado Smart device trackers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tado/entity.py b/homeassistant/components/tado/entity.py index 417cfe939d4..38110b4fded 100644 --- a/homeassistant/components/tado/entity.py +++ b/homeassistant/components/tado/entity.py @@ -1,4 +1,5 @@ """Base class for Tado entity.""" + from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 4ff12a6e51d..451a52f350f 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -1,4 +1,5 @@ """Support for Tado sensors for each zone.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tag/__init__.py b/homeassistant/components/tag/__init__.py index 59b0fa995e4..4fd20fff24b 100644 --- a/homeassistant/components/tag/__init__.py +++ b/homeassistant/components/tag/__init__.py @@ -1,4 +1,5 @@ """The Tag integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tag/trigger.py b/homeassistant/components/tag/trigger.py index b6d77737eab..4f5f637982b 100644 --- a/homeassistant/components/tag/trigger.py +++ b/homeassistant/components/tag/trigger.py @@ -1,4 +1,5 @@ """Support for tag triggers.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/tailscale/__init__.py b/homeassistant/components/tailscale/__init__.py index 3d0a8e30727..5498687332f 100644 --- a/homeassistant/components/tailscale/__init__.py +++ b/homeassistant/components/tailscale/__init__.py @@ -1,4 +1,5 @@ """The Tailscale integration.""" + from __future__ import annotations from tailscale import Device as TailscaleDevice diff --git a/homeassistant/components/tailscale/binary_sensor.py b/homeassistant/components/tailscale/binary_sensor.py index 2424837354d..35c73cd0223 100644 --- a/homeassistant/components/tailscale/binary_sensor.py +++ b/homeassistant/components/tailscale/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Tailscale binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tailscale/config_flow.py b/homeassistant/components/tailscale/config_flow.py index ca8d91acbfd..ef70ed0afcc 100644 --- a/homeassistant/components/tailscale/config_flow.py +++ b/homeassistant/components/tailscale/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Tailscale integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tailscale/const.py b/homeassistant/components/tailscale/const.py index 7cdf0cddf71..8af45906a61 100644 --- a/homeassistant/components/tailscale/const.py +++ b/homeassistant/components/tailscale/const.py @@ -1,4 +1,5 @@ """Constants for the Tailscale integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/tailscale/coordinator.py b/homeassistant/components/tailscale/coordinator.py index d71116919e7..64ce0147664 100644 --- a/homeassistant/components/tailscale/coordinator.py +++ b/homeassistant/components/tailscale/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Tailscale integration.""" + from __future__ import annotations from tailscale import Device, Tailscale, TailscaleAuthenticationError diff --git a/homeassistant/components/tailscale/diagnostics.py b/homeassistant/components/tailscale/diagnostics.py index 687cee7741f..f9e63491660 100644 --- a/homeassistant/components/tailscale/diagnostics.py +++ b/homeassistant/components/tailscale/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Tailscale.""" + from __future__ import annotations import json diff --git a/homeassistant/components/tailscale/sensor.py b/homeassistant/components/tailscale/sensor.py index 61044eda56c..99b91d17442 100644 --- a/homeassistant/components/tailscale/sensor.py +++ b/homeassistant/components/tailscale/sensor.py @@ -1,4 +1,5 @@ """Support for Tailscale sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tailwind/__init__.py b/homeassistant/components/tailwind/__init__.py index c7ceb88294a..9bd3bb40be0 100644 --- a/homeassistant/components/tailwind/__init__.py +++ b/homeassistant/components/tailwind/__init__.py @@ -1,4 +1,5 @@ """Integration for Tailwind devices.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/tailwind/binary_sensor.py b/homeassistant/components/tailwind/binary_sensor.py index 75262427d9f..e6a1aa67ae1 100644 --- a/homeassistant/components/tailwind/binary_sensor.py +++ b/homeassistant/components/tailwind/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor entity platform for Tailwind.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tailwind/button.py b/homeassistant/components/tailwind/button.py index 019b803901c..6073b8f7f58 100644 --- a/homeassistant/components/tailwind/button.py +++ b/homeassistant/components/tailwind/button.py @@ -1,4 +1,5 @@ """Button entity platform for Tailwind.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/tailwind/config_flow.py b/homeassistant/components/tailwind/config_flow.py index af8a2699190..5005a0fb7f0 100644 --- a/homeassistant/components/tailwind/config_flow.py +++ b/homeassistant/components/tailwind/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Tailwind integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tailwind/const.py b/homeassistant/components/tailwind/const.py index 99e5bb0f1bf..f4320d04374 100644 --- a/homeassistant/components/tailwind/const.py +++ b/homeassistant/components/tailwind/const.py @@ -1,4 +1,5 @@ """Constants for the Tailwind integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tailwind/coordinator.py b/homeassistant/components/tailwind/coordinator.py index d918b093605..d7cbb248885 100644 --- a/homeassistant/components/tailwind/coordinator.py +++ b/homeassistant/components/tailwind/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for Tailwind.""" + from datetime import timedelta from gotailwind import ( diff --git a/homeassistant/components/tailwind/cover.py b/homeassistant/components/tailwind/cover.py index 335c3404cdd..f5dba47c37e 100644 --- a/homeassistant/components/tailwind/cover.py +++ b/homeassistant/components/tailwind/cover.py @@ -1,4 +1,5 @@ """Cover entity platform for Tailwind.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tailwind/diagnostics.py b/homeassistant/components/tailwind/diagnostics.py index 50c9b2266e2..970bb5174eb 100644 --- a/homeassistant/components/tailwind/diagnostics.py +++ b/homeassistant/components/tailwind/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics platform for Tailwind.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tailwind/entity.py b/homeassistant/components/tailwind/entity.py index 843cc600582..ec13dc7bd1f 100644 --- a/homeassistant/components/tailwind/entity.py +++ b/homeassistant/components/tailwind/entity.py @@ -1,4 +1,5 @@ """Base entity for the Tailwind integration.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/tailwind/number.py b/homeassistant/components/tailwind/number.py index b44e98b27f5..f05415e34c6 100644 --- a/homeassistant/components/tailwind/number.py +++ b/homeassistant/components/tailwind/number.py @@ -1,4 +1,5 @@ """Number entity platform for Tailwind.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/tami4/__init__.py b/homeassistant/components/tami4/__init__.py index 643363b1285..2755157214e 100644 --- a/homeassistant/components/tami4/__init__.py +++ b/homeassistant/components/tami4/__init__.py @@ -1,4 +1,5 @@ """The Tami4Edge integration.""" + from __future__ import annotations from Tami4EdgeAPI import Tami4EdgeAPI, exceptions diff --git a/homeassistant/components/tami4/button.py b/homeassistant/components/tami4/button.py index 8b0dd6a5e11..2d8af3fcf89 100644 --- a/homeassistant/components/tami4/button.py +++ b/homeassistant/components/tami4/button.py @@ -1,4 +1,5 @@ """Button entities for Tami4Edge.""" + from collections.abc import Callable from dataclasses import dataclass import logging diff --git a/homeassistant/components/tami4/config_flow.py b/homeassistant/components/tami4/config_flow.py index cf158cfa166..3f70d0a99ca 100644 --- a/homeassistant/components/tami4/config_flow.py +++ b/homeassistant/components/tami4/config_flow.py @@ -1,4 +1,5 @@ """Config flow for edge integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tami4/coordinator.py b/homeassistant/components/tami4/coordinator.py index ef57af71012..78a3723a876 100644 --- a/homeassistant/components/tami4/coordinator.py +++ b/homeassistant/components/tami4/coordinator.py @@ -1,4 +1,5 @@ """Water quality coordinator for Tami4Edge.""" + from dataclasses import dataclass from datetime import date, timedelta import logging diff --git a/homeassistant/components/tami4/entity.py b/homeassistant/components/tami4/entity.py index 50c066b9b6d..d84cd82f39a 100644 --- a/homeassistant/components/tami4/entity.py +++ b/homeassistant/components/tami4/entity.py @@ -1,4 +1,5 @@ """Base entity for Tami4Edge.""" + from __future__ import annotations from Tami4EdgeAPI import Tami4EdgeAPI diff --git a/homeassistant/components/tank_utility/sensor.py b/homeassistant/components/tank_utility/sensor.py index 0aecbb0f405..e0b3376ca2e 100644 --- a/homeassistant/components/tank_utility/sensor.py +++ b/homeassistant/components/tank_utility/sensor.py @@ -1,4 +1,5 @@ """Support for the Tank Utility propane monitor.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/tankerkoenig/__init__.py b/homeassistant/components/tankerkoenig/__init__.py index 3f86ef03df7..7443fa72b5b 100644 --- a/homeassistant/components/tankerkoenig/__init__.py +++ b/homeassistant/components/tankerkoenig/__init__.py @@ -1,4 +1,5 @@ """Ask tankerkoenig.de for petrol price information.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/tankerkoenig/binary_sensor.py b/homeassistant/components/tankerkoenig/binary_sensor.py index 640708e1cb4..03ffb819a1f 100644 --- a/homeassistant/components/tankerkoenig/binary_sensor.py +++ b/homeassistant/components/tankerkoenig/binary_sensor.py @@ -1,4 +1,5 @@ """Tankerkoenig binary sensor integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tankerkoenig/config_flow.py b/homeassistant/components/tankerkoenig/config_flow.py index 2c2906132d4..e5a84374a09 100644 --- a/homeassistant/components/tankerkoenig/config_flow.py +++ b/homeassistant/components/tankerkoenig/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tankerkoenig.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tankerkoenig/coordinator.py b/homeassistant/components/tankerkoenig/coordinator.py index c2d91f20b8a..f6c021cb74e 100644 --- a/homeassistant/components/tankerkoenig/coordinator.py +++ b/homeassistant/components/tankerkoenig/coordinator.py @@ -1,4 +1,5 @@ """The Tankerkoenig update coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/tankerkoenig/diagnostics.py b/homeassistant/components/tankerkoenig/diagnostics.py index d5fd7c8cada..4846d2687a2 100644 --- a/homeassistant/components/tankerkoenig/diagnostics.py +++ b/homeassistant/components/tankerkoenig/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Tankerkoenig.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/tankerkoenig/entity.py b/homeassistant/components/tankerkoenig/entity.py index 96dafa80580..316a8e37dee 100644 --- a/homeassistant/components/tankerkoenig/entity.py +++ b/homeassistant/components/tankerkoenig/entity.py @@ -1,4 +1,5 @@ """The tankerkoenig base entity.""" + from aiotankerkoenig import Station from homeassistant.const import ATTR_ID diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index b36479d4ce5..f2fdc2c45b7 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -1,4 +1,5 @@ """Tankerkoenig sensor integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tapsaff/binary_sensor.py b/homeassistant/components/tapsaff/binary_sensor.py index 5ffa8690f94..a8b3c138db5 100644 --- a/homeassistant/components/tapsaff/binary_sensor.py +++ b/homeassistant/components/tapsaff/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Taps Affs.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index 7d4331f0d40..271cfba9b79 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -1,4 +1,5 @@ """The Tasmota integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tasmota/binary_sensor.py b/homeassistant/components/tasmota/binary_sensor.py index d84087b3132..450cd833fcf 100644 --- a/homeassistant/components/tasmota/binary_sensor.py +++ b/homeassistant/components/tasmota/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Tasmota binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tasmota/config_flow.py b/homeassistant/components/tasmota/config_flow.py index c1740961228..9deb846f8e2 100644 --- a/homeassistant/components/tasmota/config_flow.py +++ b/homeassistant/components/tasmota/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tasmota.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tasmota/const.py b/homeassistant/components/tasmota/const.py index 2e38284e43d..1a2cb431a0b 100644 --- a/homeassistant/components/tasmota/const.py +++ b/homeassistant/components/tasmota/const.py @@ -1,4 +1,5 @@ """Constants used by multiple Tasmota modules.""" + from homeassistant.const import Platform CONF_DISCOVERY_PREFIX = "discovery_prefix" diff --git a/homeassistant/components/tasmota/cover.py b/homeassistant/components/tasmota/cover.py index 4b851a86406..48ac868d64b 100644 --- a/homeassistant/components/tasmota/cover.py +++ b/homeassistant/components/tasmota/cover.py @@ -1,4 +1,5 @@ """Support for Tasmota covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tasmota/device_trigger.py b/homeassistant/components/tasmota/device_trigger.py index f01cdddb1db..a357dfff1c0 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Tasmota.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py index 70cedd9dd3d..5d70330dbdf 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -1,4 +1,5 @@ """Support for Tasmota device discovery.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/tasmota/fan.py b/homeassistant/components/tasmota/fan.py index 38ba5fcc476..22502c474b1 100644 --- a/homeassistant/components/tasmota/fan.py +++ b/homeassistant/components/tasmota/fan.py @@ -1,4 +1,5 @@ """Support for Tasmota fans.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tasmota/light.py b/homeassistant/components/tasmota/light.py index f5b70eca9ce..76859311c6d 100644 --- a/homeassistant/components/tasmota/light.py +++ b/homeassistant/components/tasmota/light.py @@ -1,4 +1,5 @@ """Support for Tasmota lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tasmota/mixins.py b/homeassistant/components/tasmota/mixins.py index 48dbe51fd67..8c0da1bcc2a 100644 --- a/homeassistant/components/tasmota/mixins.py +++ b/homeassistant/components/tasmota/mixins.py @@ -1,4 +1,5 @@ """Tasmota entity mixins.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 29d3f5c8c8a..d919cb73cf5 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -1,4 +1,5 @@ """Support for Tasmota sensors.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/tasmota/switch.py b/homeassistant/components/tasmota/switch.py index 8ee4d2f47ee..0a80992b1d8 100644 --- a/homeassistant/components/tasmota/switch.py +++ b/homeassistant/components/tasmota/switch.py @@ -1,4 +1,5 @@ """Support for Tasmota switches.""" + from typing import Any from hatasmota import relay as tasmota_relay diff --git a/homeassistant/components/tautulli/__init__.py b/homeassistant/components/tautulli/__init__.py index b7e62846ac1..b7fcf48cfdb 100644 --- a/homeassistant/components/tautulli/__init__.py +++ b/homeassistant/components/tautulli/__init__.py @@ -1,4 +1,5 @@ """The Tautulli integration.""" + from __future__ import annotations from pytautulli import PyTautulli, PyTautulliApiUser, PyTautulliHostConfiguration diff --git a/homeassistant/components/tautulli/config_flow.py b/homeassistant/components/tautulli/config_flow.py index 987f3816f15..a8378786d18 100644 --- a/homeassistant/components/tautulli/config_flow.py +++ b/homeassistant/components/tautulli/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tautulli.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tautulli/const.py b/homeassistant/components/tautulli/const.py index c0ca923c3e5..8c5d5e4c8b4 100644 --- a/homeassistant/components/tautulli/const.py +++ b/homeassistant/components/tautulli/const.py @@ -1,4 +1,5 @@ """Constants for the Tautulli integration.""" + from logging import Logger, getLogger ATTR_TOP_USER = "top_user" diff --git a/homeassistant/components/tautulli/coordinator.py b/homeassistant/components/tautulli/coordinator.py index d6e827acd8e..be7dfce4e3a 100644 --- a/homeassistant/components/tautulli/coordinator.py +++ b/homeassistant/components/tautulli/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Tautulli integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index d2d0c3595ac..f28aaddde25 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -1,4 +1,5 @@ """A platform which allows you to get information from Tautulli.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tcp/binary_sensor.py b/homeassistant/components/tcp/binary_sensor.py index ff07d163040..3e432778910 100644 --- a/homeassistant/components/tcp/binary_sensor.py +++ b/homeassistant/components/tcp/binary_sensor.py @@ -1,4 +1,5 @@ """Provides a binary sensor which gets its values from a TCP socket.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/tcp/common.py b/homeassistant/components/tcp/common.py index 31f230d3b23..46520134bf6 100644 --- a/homeassistant/components/tcp/common.py +++ b/homeassistant/components/tcp/common.py @@ -1,4 +1,5 @@ """Common code for TCP component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tcp/const.py b/homeassistant/components/tcp/const.py index 3a42736c753..98cdfa002fd 100644 --- a/homeassistant/components/tcp/const.py +++ b/homeassistant/components/tcp/const.py @@ -1,4 +1,5 @@ """Constants for TCP platform.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/tcp/model.py b/homeassistant/components/tcp/model.py index 814f6fdb126..8cbe10e0b0c 100644 --- a/homeassistant/components/tcp/model.py +++ b/homeassistant/components/tcp/model.py @@ -1,4 +1,5 @@ """Models for TCP platform.""" + from __future__ import annotations from typing import TypedDict diff --git a/homeassistant/components/tcp/sensor.py b/homeassistant/components/tcp/sensor.py index 609a4cc072a..6c1e6563c50 100644 --- a/homeassistant/components/tcp/sensor.py +++ b/homeassistant/components/tcp/sensor.py @@ -1,4 +1,5 @@ """Support for TCP socket based sensors.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/technove/__init__.py b/homeassistant/components/technove/__init__.py index 999cb2e2f34..d2d5b4255ba 100644 --- a/homeassistant/components/technove/__init__.py +++ b/homeassistant/components/technove/__init__.py @@ -1,4 +1,5 @@ """The TechnoVE integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/technove/binary_sensor.py b/homeassistant/components/technove/binary_sensor.py index 09bf08baad6..e9570397dc1 100644 --- a/homeassistant/components/technove/binary_sensor.py +++ b/homeassistant/components/technove/binary_sensor.py @@ -1,4 +1,5 @@ """Support for TechnoVE binary sensor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/technove/const.py b/homeassistant/components/technove/const.py index 6dd7d567353..1c2bbe8aa83 100644 --- a/homeassistant/components/technove/const.py +++ b/homeassistant/components/technove/const.py @@ -1,4 +1,5 @@ """Constants for the TechnoVE integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/technove/coordinator.py b/homeassistant/components/technove/coordinator.py index 66ec7d979f3..c89219e698f 100644 --- a/homeassistant/components/technove/coordinator.py +++ b/homeassistant/components/technove/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for TechnoVE.""" + from __future__ import annotations from technove import Station as TechnoVEStation, TechnoVE, TechnoVEError diff --git a/homeassistant/components/technove/entity.py b/homeassistant/components/technove/entity.py index 964f2941301..8a01eccfe10 100644 --- a/homeassistant/components/technove/entity.py +++ b/homeassistant/components/technove/entity.py @@ -1,4 +1,5 @@ """Entity for TechnoVE.""" + from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/technove/helpers.py b/homeassistant/components/technove/helpers.py index 43bd8f04794..4d8bda38a25 100644 --- a/homeassistant/components/technove/helpers.py +++ b/homeassistant/components/technove/helpers.py @@ -1,4 +1,5 @@ """Helpers for TechnoVE.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/technove/sensor.py b/homeassistant/components/technove/sensor.py index 576ba88cec6..6f8e63ffbc6 100644 --- a/homeassistant/components/technove/sensor.py +++ b/homeassistant/components/technove/sensor.py @@ -1,4 +1,5 @@ """Platform for sensor integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/technove/switch.py b/homeassistant/components/technove/switch.py index 3ee7f1c302d..72c95f676d9 100644 --- a/homeassistant/components/technove/switch.py +++ b/homeassistant/components/technove/switch.py @@ -1,4 +1,5 @@ """Support for TechnoVE switches.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py index 5c3e1d30c4a..9dafe945188 100644 --- a/homeassistant/components/ted5000/sensor.py +++ b/homeassistant/components/ted5000/sensor.py @@ -1,4 +1,5 @@ """Support gathering ted5000 information.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/tedee/const.py b/homeassistant/components/tedee/const.py index bac5bfaec44..b5898871620 100644 --- a/homeassistant/components/tedee/const.py +++ b/homeassistant/components/tedee/const.py @@ -1,4 +1,5 @@ """Constants for the Tedee integration.""" + from datetime import timedelta DOMAIN = "tedee" diff --git a/homeassistant/components/tedee/coordinator.py b/homeassistant/components/tedee/coordinator.py index c846f2a8d9a..f3043b1d78d 100644 --- a/homeassistant/components/tedee/coordinator.py +++ b/homeassistant/components/tedee/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for Tedee locks.""" + from collections.abc import Awaitable, Callable from datetime import timedelta import logging diff --git a/homeassistant/components/tedee/diagnostics.py b/homeassistant/components/tedee/diagnostics.py index d17c4c335bc..b4fb1d279fa 100644 --- a/homeassistant/components/tedee/diagnostics.py +++ b/homeassistant/components/tedee/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for tedee.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tedee/lock.py b/homeassistant/components/tedee/lock.py index 1025942d787..a720652bcbc 100644 --- a/homeassistant/components/tedee/lock.py +++ b/homeassistant/components/tedee/lock.py @@ -1,4 +1,5 @@ """Tedee lock entities.""" + from typing import Any from pytedee_async import TedeeClientException, TedeeLock, TedeeLockState diff --git a/homeassistant/components/telegram/notify.py b/homeassistant/components/telegram/notify.py index 05b966e66d7..e543715d37c 100644 --- a/homeassistant/components/telegram/notify.py +++ b/homeassistant/components/telegram/notify.py @@ -1,4 +1,5 @@ """Telegram platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 1aeed6a25bb..ba9cd88eacc 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -1,4 +1,5 @@ """Support to send and receive Telegram messages.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tellduslive/binary_sensor.py b/homeassistant/components/tellduslive/binary_sensor.py index 4abe1dfd174..1eead7b55a5 100644 --- a/homeassistant/components/tellduslive/binary_sensor.py +++ b/homeassistant/components/tellduslive/binary_sensor.py @@ -1,4 +1,5 @@ """Support for binary sensors using Tellstick Net.""" + from homeassistant.components import binary_sensor from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/tellduslive/const.py b/homeassistant/components/tellduslive/const.py index 6b3bb1c6437..3a24f6b033a 100644 --- a/homeassistant/components/tellduslive/const.py +++ b/homeassistant/components/tellduslive/const.py @@ -1,4 +1,5 @@ """Consts used by TelldusLive.""" + from datetime import timedelta APPLICATION_NAME = "Home Assistant" diff --git a/homeassistant/components/tellduslive/cover.py b/homeassistant/components/tellduslive/cover.py index 2a32756aa1b..57c6ae9e7eb 100644 --- a/homeassistant/components/tellduslive/cover.py +++ b/homeassistant/components/tellduslive/cover.py @@ -1,4 +1,5 @@ """Support for Tellstick covers using Tellstick Net.""" + from typing import Any from homeassistant.components import cover diff --git a/homeassistant/components/tellduslive/entry.py b/homeassistant/components/tellduslive/entry.py index fdacc02bfca..77a04fabd06 100644 --- a/homeassistant/components/tellduslive/entry.py +++ b/homeassistant/components/tellduslive/entry.py @@ -1,4 +1,5 @@ """Base Entity for all TelldusLive entities.""" + from datetime import datetime import logging diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py index e15f89888b1..36520044101 100644 --- a/homeassistant/components/tellduslive/sensor.py +++ b/homeassistant/components/tellduslive/sensor.py @@ -1,4 +1,5 @@ """Support for Tellstick Net/Telstick Live sensors.""" + from __future__ import annotations from homeassistant.components import sensor diff --git a/homeassistant/components/tellduslive/switch.py b/homeassistant/components/tellduslive/switch.py index 5ae5a904689..c26a8dcf951 100644 --- a/homeassistant/components/tellduslive/switch.py +++ b/homeassistant/components/tellduslive/switch.py @@ -1,4 +1,5 @@ """Support for Tellstick switches using Tellstick Net.""" + from typing import Any from homeassistant.components import switch diff --git a/homeassistant/components/tellstick/cover.py b/homeassistant/components/tellstick/cover.py index 17da5684670..cb49d876e71 100644 --- a/homeassistant/components/tellstick/cover.py +++ b/homeassistant/components/tellstick/cover.py @@ -1,4 +1,5 @@ """Support for Tellstick covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tellstick/light.py b/homeassistant/components/tellstick/light.py index 136dd3ebbd6..acbcf2d6cb5 100644 --- a/homeassistant/components/tellstick/light.py +++ b/homeassistant/components/tellstick/light.py @@ -1,4 +1,5 @@ """Support for Tellstick lights.""" + from __future__ import annotations from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity diff --git a/homeassistant/components/tellstick/sensor.py b/homeassistant/components/tellstick/sensor.py index cef118e721d..2469ee49aa2 100644 --- a/homeassistant/components/tellstick/sensor.py +++ b/homeassistant/components/tellstick/sensor.py @@ -1,4 +1,5 @@ """Support for Tellstick sensors.""" + from __future__ import annotations from collections import namedtuple diff --git a/homeassistant/components/tellstick/switch.py b/homeassistant/components/tellstick/switch.py index 966a3defd5e..e3eb4825d91 100644 --- a/homeassistant/components/tellstick/switch.py +++ b/homeassistant/components/tellstick/switch.py @@ -1,4 +1,5 @@ """Support for Tellstick switches.""" + from __future__ import annotations from homeassistant.components.switch import SwitchEntity diff --git a/homeassistant/components/telnet/switch.py b/homeassistant/components/telnet/switch.py index 14e8900f000..6a6f758ff79 100644 --- a/homeassistant/components/telnet/switch.py +++ b/homeassistant/components/telnet/switch.py @@ -1,4 +1,5 @@ """Support for switch controlled using a telnet connection.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/temper/sensor.py b/homeassistant/components/temper/sensor.py index 20ad3102a4b..06ba656dd0d 100644 --- a/homeassistant/components/temper/sensor.py +++ b/homeassistant/components/temper/sensor.py @@ -1,4 +1,5 @@ """Support for getting temperature from TEMPer devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index d52dc0cf166..6d4d3a9367c 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -1,4 +1,5 @@ """The template component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index 2cac5d74a7a..4a1af80e25c 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Template alarm control panels.""" + from __future__ import annotations from enum import Enum diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index 427fe6221cd..654dad94867 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -1,4 +1,5 @@ """Support for exposing a templated binary sensor.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/template/button.py b/homeassistant/components/template/button.py index 2bb2f40d6b4..c58dfcf50b4 100644 --- a/homeassistant/components/template/button.py +++ b/homeassistant/components/template/button.py @@ -1,4 +1,5 @@ """Support for buttons which integrates with other components.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/config_flow.py b/homeassistant/components/template/config_flow.py index 686c12fa4ba..b1d11243469 100644 --- a/homeassistant/components/template/config_flow.py +++ b/homeassistant/components/template/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Template integration.""" + from __future__ import annotations from collections.abc import Callable, Coroutine, Mapping diff --git a/homeassistant/components/template/coordinator.py b/homeassistant/components/template/coordinator.py index 047d58d9208..e0beaead2fe 100644 --- a/homeassistant/components/template/coordinator.py +++ b/homeassistant/components/template/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for trigger based template entities.""" + from collections.abc import Callable import logging diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 5daa4531109..36ea9f93830 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -1,4 +1,5 @@ """Support for covers which integrate with other components.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index 8aeede42552..106d3e4fd70 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -1,4 +1,5 @@ """Support for Template fans.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/image.py b/homeassistant/components/template/image.py index 227109d59e2..92f0fe7b9fa 100644 --- a/homeassistant/components/template/image.py +++ b/homeassistant/components/template/image.py @@ -1,4 +1,5 @@ """Support for image which integrates with other components.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 89c4826f1e6..71443789703 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -1,4 +1,5 @@ """Support for Template lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index de483971ac6..3f9df4818fd 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -1,4 +1,5 @@ """Support for locks which integrates with other components.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/template/number.py b/homeassistant/components/template/number.py index 988cebf08ab..d4004ee9535 100644 --- a/homeassistant/components/template/number.py +++ b/homeassistant/components/template/number.py @@ -1,4 +1,5 @@ """Support for numbers which integrates with other components.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/select.py b/homeassistant/components/template/select.py index fea972a5d6f..650b236faee 100644 --- a/homeassistant/components/template/select.py +++ b/homeassistant/components/template/select.py @@ -1,4 +1,5 @@ """Support for selects which integrates with other components.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 3a3d0682805..a6dbedc6161 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -1,4 +1,5 @@ """Allows the creation of a sensor that breaks out state_attributes.""" + from __future__ import annotations from datetime import date, datetime diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index 5e75eafe233..f585cd929c0 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -1,4 +1,5 @@ """Support for switches which integrates with other components.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 9d08980da32..c2ffa85864d 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -1,4 +1,5 @@ """TemplateEntity utility class.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 327c988106e..3f76aac4eb6 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -1,4 +1,5 @@ """Offer template automation rules.""" + from datetime import timedelta import logging from typing import Any diff --git a/homeassistant/components/template/trigger_entity.py b/homeassistant/components/template/trigger_entity.py index 5f5fbe5b99a..697cd827b9e 100644 --- a/homeassistant/components/template/trigger_entity.py +++ b/homeassistant/components/template/trigger_entity.py @@ -1,4 +1,5 @@ """Trigger entity.""" + from __future__ import annotations from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 4b693c8070c..9062f71d818 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -1,4 +1,5 @@ """Support for Template vacuums.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 0a00d1e79b4..3d84eb1d07a 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -1,4 +1,5 @@ """Template platform that aggregates meteorological data.""" + from __future__ import annotations from dataclasses import asdict, dataclass diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index e2fce4b94c2..3025ffa27c7 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -1,4 +1,5 @@ """Support for performing TensorFlow classification on images.""" + from __future__ import annotations import io diff --git a/homeassistant/components/tesla_wall_connector/__init__.py b/homeassistant/components/tesla_wall_connector/__init__.py index 30e61dc7744..28ddc15ade7 100644 --- a/homeassistant/components/tesla_wall_connector/__init__.py +++ b/homeassistant/components/tesla_wall_connector/__init__.py @@ -1,4 +1,5 @@ """The Tesla Wall Connector integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tesla_wall_connector/binary_sensor.py b/homeassistant/components/tesla_wall_connector/binary_sensor.py index d21d9a75e0b..cf8fbf53b52 100644 --- a/homeassistant/components/tesla_wall_connector/binary_sensor.py +++ b/homeassistant/components/tesla_wall_connector/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensors for Tesla Wall Connector.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/tesla_wall_connector/config_flow.py b/homeassistant/components/tesla_wall_connector/config_flow.py index ad8112be4cb..b00dd8f2b9d 100644 --- a/homeassistant/components/tesla_wall_connector/config_flow.py +++ b/homeassistant/components/tesla_wall_connector/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tesla Wall Connector integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tesla_wall_connector/sensor.py b/homeassistant/components/tesla_wall_connector/sensor.py index 3681d448172..9cbe14982f2 100644 --- a/homeassistant/components/tesla_wall_connector/sensor.py +++ b/homeassistant/components/tesla_wall_connector/sensor.py @@ -1,4 +1,5 @@ """Sensors for Tesla Wall Connector.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/teslemetry/climate.py b/homeassistant/components/teslemetry/climate.py index 31556356caf..0835785d194 100644 --- a/homeassistant/components/teslemetry/climate.py +++ b/homeassistant/components/teslemetry/climate.py @@ -1,4 +1,5 @@ """Climate platform for Teslemetry integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/teslemetry/config_flow.py b/homeassistant/components/teslemetry/config_flow.py index 72ae712f994..f7fc5bbf805 100644 --- a/homeassistant/components/teslemetry/config_flow.py +++ b/homeassistant/components/teslemetry/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for Teslemetry integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/teslemetry/const.py b/homeassistant/components/teslemetry/const.py index 9b31a3270ca..0d9d129877f 100644 --- a/homeassistant/components/teslemetry/const.py +++ b/homeassistant/components/teslemetry/const.py @@ -1,4 +1,5 @@ """Constants used by Teslemetry integration.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/teslemetry/coordinator.py b/homeassistant/components/teslemetry/coordinator.py index b4dfdc3839a..a14ed347193 100644 --- a/homeassistant/components/teslemetry/coordinator.py +++ b/homeassistant/components/teslemetry/coordinator.py @@ -1,4 +1,5 @@ """Teslemetry Data Coordinator.""" + from datetime import timedelta from typing import Any diff --git a/homeassistant/components/teslemetry/models.py b/homeassistant/components/teslemetry/models.py index 2b41adf7979..d6f15e2e932 100644 --- a/homeassistant/components/teslemetry/models.py +++ b/homeassistant/components/teslemetry/models.py @@ -1,4 +1,5 @@ """The Teslemetry integration models.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index 693b2e0b22a..d592135ab8e 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Teslemetry integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tessie/__init__.py b/homeassistant/components/tessie/__init__.py index 869cd46cf51..6ac96fe8865 100644 --- a/homeassistant/components/tessie/__init__.py +++ b/homeassistant/components/tessie/__init__.py @@ -1,4 +1,5 @@ """Tessie integration.""" + from http import HTTPStatus import logging diff --git a/homeassistant/components/tessie/binary_sensor.py b/homeassistant/components/tessie/binary_sensor.py index 34d80b4f932..015fa63736f 100644 --- a/homeassistant/components/tessie/binary_sensor.py +++ b/homeassistant/components/tessie/binary_sensor.py @@ -1,4 +1,5 @@ """Binary Sensor platform for Tessie integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tessie/button.py b/homeassistant/components/tessie/button.py index 62bf6f79a6e..c357863bc4b 100644 --- a/homeassistant/components/tessie/button.py +++ b/homeassistant/components/tessie/button.py @@ -1,4 +1,5 @@ """Button platform for Tessie integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tessie/climate.py b/homeassistant/components/tessie/climate.py index c856e8211cc..4c763726851 100644 --- a/homeassistant/components/tessie/climate.py +++ b/homeassistant/components/tessie/climate.py @@ -1,4 +1,5 @@ """Climate platform for Tessie integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tessie/config_flow.py b/homeassistant/components/tessie/config_flow.py index 428cff5d727..5ab7280a90c 100644 --- a/homeassistant/components/tessie/config_flow.py +++ b/homeassistant/components/tessie/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for Tessie integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tessie/const.py b/homeassistant/components/tessie/const.py index 8ec063bf47c..f717d758f5a 100644 --- a/homeassistant/components/tessie/const.py +++ b/homeassistant/components/tessie/const.py @@ -1,4 +1,5 @@ """Constants used by Tessie integration.""" + from __future__ import annotations from enum import IntEnum, StrEnum diff --git a/homeassistant/components/tessie/coordinator.py b/homeassistant/components/tessie/coordinator.py index c2106af665f..19d2d2c4869 100644 --- a/homeassistant/components/tessie/coordinator.py +++ b/homeassistant/components/tessie/coordinator.py @@ -1,4 +1,5 @@ """Tessie Data Coordinator.""" + from datetime import timedelta from http import HTTPStatus import logging diff --git a/homeassistant/components/tessie/cover.py b/homeassistant/components/tessie/cover.py index 6b4393fce1f..8d275559007 100644 --- a/homeassistant/components/tessie/cover.py +++ b/homeassistant/components/tessie/cover.py @@ -1,4 +1,5 @@ """Cover platform for Tessie integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tessie/device_tracker.py b/homeassistant/components/tessie/device_tracker.py index 9b1ddfcfe4f..da979e5fc31 100644 --- a/homeassistant/components/tessie/device_tracker.py +++ b/homeassistant/components/tessie/device_tracker.py @@ -1,4 +1,5 @@ """Device Tracker platform for Tessie integration.""" + from __future__ import annotations from homeassistant.components.device_tracker import SourceType diff --git a/homeassistant/components/tessie/lock.py b/homeassistant/components/tessie/lock.py index 9a27e95c73e..ccd613af2e3 100644 --- a/homeassistant/components/tessie/lock.py +++ b/homeassistant/components/tessie/lock.py @@ -1,4 +1,5 @@ """Lock platform for Tessie integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tessie/media_player.py b/homeassistant/components/tessie/media_player.py index c4392e1de1d..2b20bf89152 100644 --- a/homeassistant/components/tessie/media_player.py +++ b/homeassistant/components/tessie/media_player.py @@ -1,4 +1,5 @@ """Media Player platform for Tessie integration.""" + from __future__ import annotations from homeassistant.components.media_player import ( diff --git a/homeassistant/components/tessie/models.py b/homeassistant/components/tessie/models.py index 32466a6b2ac..c17947ed941 100644 --- a/homeassistant/components/tessie/models.py +++ b/homeassistant/components/tessie/models.py @@ -1,4 +1,5 @@ """The Tessie integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tessie/number.py b/homeassistant/components/tessie/number.py index ada088f1bd2..196ea877f61 100644 --- a/homeassistant/components/tessie/number.py +++ b/homeassistant/components/tessie/number.py @@ -1,4 +1,5 @@ """Number platform for Tessie integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tessie/select.py b/homeassistant/components/tessie/select.py index 03436b44cfc..a7d8c42472d 100644 --- a/homeassistant/components/tessie/select.py +++ b/homeassistant/components/tessie/select.py @@ -1,4 +1,5 @@ """Select platform for Tessie integration.""" + from __future__ import annotations from tessie_api import set_seat_heat diff --git a/homeassistant/components/tessie/sensor.py b/homeassistant/components/tessie/sensor.py index 3e5a0a60aa3..dd893adb632 100644 --- a/homeassistant/components/tessie/sensor.py +++ b/homeassistant/components/tessie/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Tessie integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tessie/switch.py b/homeassistant/components/tessie/switch.py index b8ac2ede52b..225d65bf852 100644 --- a/homeassistant/components/tessie/switch.py +++ b/homeassistant/components/tessie/switch.py @@ -1,4 +1,5 @@ """Switch platform for Tessie integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tessie/update.py b/homeassistant/components/tessie/update.py index 1d2fb59c492..77cb2a70de9 100644 --- a/homeassistant/components/tessie/update.py +++ b/homeassistant/components/tessie/update.py @@ -1,4 +1,5 @@ """Update platform for Tessie integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/text/__init__.py b/homeassistant/components/text/__init__.py index 89fad759f8b..c69cf8fcbdd 100644 --- a/homeassistant/components/text/__init__.py +++ b/homeassistant/components/text/__init__.py @@ -1,4 +1,5 @@ """Component to allow setting text as platforms.""" + from __future__ import annotations from dataclasses import asdict, dataclass diff --git a/homeassistant/components/text/device_action.py b/homeassistant/components/text/device_action.py index 9d06d4b7441..94269ac12fb 100644 --- a/homeassistant/components/text/device_action.py +++ b/homeassistant/components/text/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Text.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/text/reproduce_state.py b/homeassistant/components/text/reproduce_state.py index 99013a63a06..329ffd374dd 100644 --- a/homeassistant/components/text/reproduce_state.py +++ b/homeassistant/components/text/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce a Text entity state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tfiac/climate.py b/homeassistant/components/tfiac/climate.py index ea6a6f22d2b..74d2c3fbe7e 100644 --- a/homeassistant/components/tfiac/climate.py +++ b/homeassistant/components/tfiac/climate.py @@ -1,4 +1,5 @@ """Climate platform that offers a climate device for the TFIAC protocol.""" + from __future__ import annotations from concurrent import futures diff --git a/homeassistant/components/thermobeacon/__init__.py b/homeassistant/components/thermobeacon/__init__.py index 92b5ef4b4f6..853d2b3dc65 100644 --- a/homeassistant/components/thermobeacon/__init__.py +++ b/homeassistant/components/thermobeacon/__init__.py @@ -1,4 +1,5 @@ """The ThermoBeacon integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/thermobeacon/config_flow.py b/homeassistant/components/thermobeacon/config_flow.py index b635a28dc8c..08994a41008 100644 --- a/homeassistant/components/thermobeacon/config_flow.py +++ b/homeassistant/components/thermobeacon/config_flow.py @@ -1,4 +1,5 @@ """Config flow for thermobeacon ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/thermobeacon/device.py b/homeassistant/components/thermobeacon/device.py index fe8a499d6ed..36af211876f 100644 --- a/homeassistant/components/thermobeacon/device.py +++ b/homeassistant/components/thermobeacon/device.py @@ -1,4 +1,5 @@ """Support for ThermoBeacon devices.""" + from __future__ import annotations from thermobeacon_ble import DeviceKey diff --git a/homeassistant/components/thermobeacon/sensor.py b/homeassistant/components/thermobeacon/sensor.py index c6fb978923e..6bf2e00c420 100644 --- a/homeassistant/components/thermobeacon/sensor.py +++ b/homeassistant/components/thermobeacon/sensor.py @@ -1,4 +1,5 @@ """Support for ThermoBeacon sensors.""" + from __future__ import annotations from thermobeacon_ble import ( diff --git a/homeassistant/components/thermopro/__init__.py b/homeassistant/components/thermopro/__init__.py index 7093484648f..51bc4123870 100644 --- a/homeassistant/components/thermopro/__init__.py +++ b/homeassistant/components/thermopro/__init__.py @@ -1,4 +1,5 @@ """The ThermoPro Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/thermopro/config_flow.py b/homeassistant/components/thermopro/config_flow.py index 02335ef712e..4d080c6e074 100644 --- a/homeassistant/components/thermopro/config_flow.py +++ b/homeassistant/components/thermopro/config_flow.py @@ -1,4 +1,5 @@ """Config flow for thermopro ble integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/thermopro/sensor.py b/homeassistant/components/thermopro/sensor.py index 37cbf10323f..21915ca9998 100644 --- a/homeassistant/components/thermopro/sensor.py +++ b/homeassistant/components/thermopro/sensor.py @@ -1,4 +1,5 @@ """Support for thermopro ble sensors.""" + from __future__ import annotations from thermopro_ble import ( diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index 4b4878fa1c8..33681d5e574 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -2,6 +2,7 @@ Requires Smoke Gateway Wifi with an internet connection. """ + from __future__ import annotations import logging diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index b9568a979fa..ae4fed8600e 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -1,4 +1,5 @@ """Support for The Things Network's Data storage integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/thinkingcleaner/sensor.py b/homeassistant/components/thinkingcleaner/sensor.py index b56c9f61135..86c5a8813d8 100644 --- a/homeassistant/components/thinkingcleaner/sensor.py +++ b/homeassistant/components/thinkingcleaner/sensor.py @@ -1,4 +1,5 @@ """Support for ThinkingCleaner sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/thinkingcleaner/switch.py b/homeassistant/components/thinkingcleaner/switch.py index 1befc53ffff..f99cda4347a 100644 --- a/homeassistant/components/thinkingcleaner/switch.py +++ b/homeassistant/components/thinkingcleaner/switch.py @@ -1,4 +1,5 @@ """Support for ThinkingCleaner switches.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/thomson/device_tracker.py b/homeassistant/components/thomson/device_tracker.py index 0ad2942fb04..2ba5505c6f3 100644 --- a/homeassistant/components/thomson/device_tracker.py +++ b/homeassistant/components/thomson/device_tracker.py @@ -1,4 +1,5 @@ """Support for THOMSON routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/thread/__init__.py b/homeassistant/components/thread/__init__.py index dd2527763ad..65a59e43f31 100644 --- a/homeassistant/components/thread/__init__.py +++ b/homeassistant/components/thread/__init__.py @@ -1,4 +1,5 @@ """The Thread integration.""" + from __future__ import annotations from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry diff --git a/homeassistant/components/thread/config_flow.py b/homeassistant/components/thread/config_flow.py index 2a2062419f7..b4b6eac0fc8 100644 --- a/homeassistant/components/thread/config_flow.py +++ b/homeassistant/components/thread/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Thread integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/thread/dataset_store.py b/homeassistant/components/thread/dataset_store.py index b5a3b39ae26..de322510ef2 100644 --- a/homeassistant/components/thread/dataset_store.py +++ b/homeassistant/components/thread/dataset_store.py @@ -1,4 +1,5 @@ """Persistently store thread datasets.""" + from __future__ import annotations from asyncio import Event, Task, wait diff --git a/homeassistant/components/thread/discovery.py b/homeassistant/components/thread/discovery.py index ad1df757af4..49a77e9c87b 100644 --- a/homeassistant/components/thread/discovery.py +++ b/homeassistant/components/thread/discovery.py @@ -1,4 +1,5 @@ """The Thread integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/thread/websocket_api.py b/homeassistant/components/thread/websocket_api.py index 9dd1971f91c..2ac817c2b75 100644 --- a/homeassistant/components/thread/websocket_api.py +++ b/homeassistant/components/thread/websocket_api.py @@ -1,4 +1,5 @@ """The thread websocket API.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py index 6382c79b9ce..ff0813f650e 100644 --- a/homeassistant/components/threshold/binary_sensor.py +++ b/homeassistant/components/threshold/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring if a sensor value is below/above a threshold.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/threshold/config_flow.py b/homeassistant/components/threshold/config_flow.py index 31d51fee3f3..a8e330cab38 100644 --- a/homeassistant/components/threshold/config_flow.py +++ b/homeassistant/components/threshold/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Threshold integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tibber/config_flow.py b/homeassistant/components/tibber/config_flow.py index 10b0d899de2..abee3ea50bc 100644 --- a/homeassistant/components/tibber/config_flow.py +++ b/homeassistant/components/tibber/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Tibber integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tibber/diagnostics.py b/homeassistant/components/tibber/diagnostics.py index 991d20e9e2d..f0fc6fec58b 100644 --- a/homeassistant/components/tibber/diagnostics.py +++ b/homeassistant/components/tibber/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Tibber.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tibber/notify.py b/homeassistant/components/tibber/notify.py index 997afa62359..b0816de39e2 100644 --- a/homeassistant/components/tibber/notify.py +++ b/homeassistant/components/tibber/notify.py @@ -1,4 +1,5 @@ """Support for Tibber notifications.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index a2bd8d26f75..075b2518bd2 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -1,4 +1,5 @@ """Support for Tibber sensors.""" + from __future__ import annotations import datetime diff --git a/homeassistant/components/tikteck/light.py b/homeassistant/components/tikteck/light.py index 7022138d147..93549b26f48 100644 --- a/homeassistant/components/tikteck/light.py +++ b/homeassistant/components/tikteck/light.py @@ -1,4 +1,5 @@ """Support for Tikteck lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tile/__init__.py b/homeassistant/components/tile/__init__.py index 1e8cebdd5a6..7dbeea1a4f3 100644 --- a/homeassistant/components/tile/__init__.py +++ b/homeassistant/components/tile/__init__.py @@ -1,4 +1,5 @@ """The Tile component.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tile/config_flow.py b/homeassistant/components/tile/config_flow.py index 10bc51ae459..108d9b1b300 100644 --- a/homeassistant/components/tile/config_flow.py +++ b/homeassistant/components/tile/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Tile integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tile/device_tracker.py b/homeassistant/components/tile/device_tracker.py index e4adf808029..b33c2c592b8 100644 --- a/homeassistant/components/tile/device_tracker.py +++ b/homeassistant/components/tile/device_tracker.py @@ -1,4 +1,5 @@ """Support for Tile device trackers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tile/diagnostics.py b/homeassistant/components/tile/diagnostics.py index dda2c3367e3..22991ef24c1 100644 --- a/homeassistant/components/tile/diagnostics.py +++ b/homeassistant/components/tile/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Tile.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tilt_ble/__init__.py b/homeassistant/components/tilt_ble/__init__.py index eebb3660368..7770eb3cd20 100644 --- a/homeassistant/components/tilt_ble/__init__.py +++ b/homeassistant/components/tilt_ble/__init__.py @@ -1,4 +1,5 @@ """The tilt_ble integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tilt_ble/config_flow.py b/homeassistant/components/tilt_ble/config_flow.py index 810a4226117..5c1f9721aae 100644 --- a/homeassistant/components/tilt_ble/config_flow.py +++ b/homeassistant/components/tilt_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for tilt_ble.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tilt_ble/sensor.py b/homeassistant/components/tilt_ble/sensor.py index 7edfec3643f..380bb90ca15 100644 --- a/homeassistant/components/tilt_ble/sensor.py +++ b/homeassistant/components/tilt_ble/sensor.py @@ -1,4 +1,5 @@ """Support for Tilt Hydrometers.""" + from __future__ import annotations from tilt_ble import DeviceClass, DeviceKey, SensorUpdate, Units diff --git a/homeassistant/components/time/__init__.py b/homeassistant/components/time/__init__.py index 387c42f0852..867aada2e8f 100644 --- a/homeassistant/components/time/__init__.py +++ b/homeassistant/components/time/__init__.py @@ -1,4 +1,5 @@ """Component to allow setting time as platforms.""" + from __future__ import annotations from datetime import time, timedelta diff --git a/homeassistant/components/time_date/__init__.py b/homeassistant/components/time_date/__init__.py index cdd69a2bc1f..151f5c6b39f 100644 --- a/homeassistant/components/time_date/__init__.py +++ b/homeassistant/components/time_date/__init__.py @@ -1,4 +1,5 @@ """The time_date component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/time_date/config_flow.py b/homeassistant/components/time_date/config_flow.py index 09a5f2503d0..f65978144c6 100644 --- a/homeassistant/components/time_date/config_flow.py +++ b/homeassistant/components/time_date/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Time & Date integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/time_date/const.py b/homeassistant/components/time_date/const.py index dde9497b9a3..5d13ec0203c 100644 --- a/homeassistant/components/time_date/const.py +++ b/homeassistant/components/time_date/const.py @@ -1,4 +1,5 @@ """Constants for the Time & Date integration.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/time_date/sensor.py b/homeassistant/components/time_date/sensor.py index bd0f9449aea..57bb87e6ea5 100644 --- a/homeassistant/components/time_date/sensor.py +++ b/homeassistant/components/time_date/sensor.py @@ -1,4 +1,5 @@ """Support for showing the date and the time.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 4c611962436..72e93f5655a 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -1,4 +1,5 @@ """Support for Timers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/timer/reproduce_state.py b/homeassistant/components/timer/reproduce_state.py index 5628c0b4bbc..3bdee08016c 100644 --- a/homeassistant/components/timer/reproduce_state.py +++ b/homeassistant/components/timer/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Timer state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tmb/sensor.py b/homeassistant/components/tmb/sensor.py index 7fe8630cc98..4ec86434ea0 100644 --- a/homeassistant/components/tmb/sensor.py +++ b/homeassistant/components/tmb/sensor.py @@ -1,4 +1,5 @@ """Support for TMB (Transports Metropolitans de Barcelona) Barcelona public transport.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/tod/__init__.py b/homeassistant/components/tod/__init__.py index e404826534e..4f3f365ea59 100644 --- a/homeassistant/components/tod/__init__.py +++ b/homeassistant/components/tod/__init__.py @@ -1,4 +1,5 @@ """The Times of the Day integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index d274960c211..c35f92fd27f 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -1,4 +1,5 @@ """Support for representing current time of the day as binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tod/config_flow.py b/homeassistant/components/tod/config_flow.py index 6e21b8046a1..0bbd5a528af 100644 --- a/homeassistant/components/tod/config_flow.py +++ b/homeassistant/components/tod/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Times of the Day integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/todo/intent.py b/homeassistant/components/todo/intent.py index 2cce9da9c0f..81d5ca2ae0c 100644 --- a/homeassistant/components/todo/intent.py +++ b/homeassistant/components/todo/intent.py @@ -1,4 +1,5 @@ """Intents for the todo integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 40ceb71ee5f..9b8d0a7c08f 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -1,4 +1,5 @@ """Support for Todoist task management (https://todoist.com).""" + from __future__ import annotations from datetime import date, datetime, timedelta diff --git a/homeassistant/components/todoist/const.py b/homeassistant/components/todoist/const.py index 021111b48d7..1a66fc9764f 100644 --- a/homeassistant/components/todoist/const.py +++ b/homeassistant/components/todoist/const.py @@ -1,4 +1,5 @@ """Constants for the Todoist component.""" + from typing import Final CONF_EXTRA_PROJECTS: Final = "custom_projects" diff --git a/homeassistant/components/todoist/coordinator.py b/homeassistant/components/todoist/coordinator.py index 702c43883ea..e01b4ecb35a 100644 --- a/homeassistant/components/todoist/coordinator.py +++ b/homeassistant/components/todoist/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Todoist component.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/todoist/types.py b/homeassistant/components/todoist/types.py index e6b4d55fce2..da716131695 100644 --- a/homeassistant/components/todoist/types.py +++ b/homeassistant/components/todoist/types.py @@ -1,4 +1,5 @@ """Types for the Todoist component.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/tolo/climate.py b/homeassistant/components/tolo/climate.py index 033a4c5b51c..bbe7bf57202 100644 --- a/homeassistant/components/tolo/climate.py +++ b/homeassistant/components/tolo/climate.py @@ -1,4 +1,5 @@ """TOLO Sauna climate controls (main sauna control).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tolo/light.py b/homeassistant/components/tolo/light.py index 4b76d4270c6..809bb367072 100644 --- a/homeassistant/components/tolo/light.py +++ b/homeassistant/components/tolo/light.py @@ -1,4 +1,5 @@ """TOLO Sauna light controls.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tolo/number.py b/homeassistant/components/tolo/number.py index 7cc5d6c0591..257b96edd6e 100644 --- a/homeassistant/components/tolo/number.py +++ b/homeassistant/components/tolo/number.py @@ -1,4 +1,5 @@ """TOLO Sauna number controls.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tolo/sensor.py b/homeassistant/components/tolo/sensor.py index f9ee5e34307..5af8abfb117 100644 --- a/homeassistant/components/tolo/sensor.py +++ b/homeassistant/components/tolo/sensor.py @@ -1,4 +1,5 @@ """TOLO Sauna (non-binary, general) sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tomato/device_tracker.py b/homeassistant/components/tomato/device_tracker.py index d71dd45bcfe..d28fa505c61 100644 --- a/homeassistant/components/tomato/device_tracker.py +++ b/homeassistant/components/tomato/device_tracker.py @@ -1,4 +1,5 @@ """Support for Tomato routers.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/tomorrowio/__init__.py b/homeassistant/components/tomorrowio/__init__.py index ea179219153..3ff811369fd 100644 --- a/homeassistant/components/tomorrowio/__init__.py +++ b/homeassistant/components/tomorrowio/__init__.py @@ -1,4 +1,5 @@ """The Tomorrow.io integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tomorrowio/config_flow.py b/homeassistant/components/tomorrowio/config_flow.py index e23f90b2e3b..1a8cd328045 100644 --- a/homeassistant/components/tomorrowio/config_flow.py +++ b/homeassistant/components/tomorrowio/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tomorrow.io integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tomorrowio/const.py b/homeassistant/components/tomorrowio/const.py index 7ad6ea60836..e727be38b16 100644 --- a/homeassistant/components/tomorrowio/const.py +++ b/homeassistant/components/tomorrowio/const.py @@ -1,4 +1,5 @@ """Constants for the Tomorrow.io integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tomorrowio/sensor.py b/homeassistant/components/tomorrowio/sensor.py index d45be4aafdc..371121a9da3 100644 --- a/homeassistant/components/tomorrowio/sensor.py +++ b/homeassistant/components/tomorrowio/sensor.py @@ -1,4 +1,5 @@ """Sensor component that handles additional Tomorrowio data for your location.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/tomorrowio/weather.py b/homeassistant/components/tomorrowio/weather.py index 06a147366e8..cc46fa3dfab 100644 --- a/homeassistant/components/tomorrowio/weather.py +++ b/homeassistant/components/tomorrowio/weather.py @@ -1,4 +1,5 @@ """Weather component that handles meteorological data for your location.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index 6edc656df06..b184e5aacb7 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Toon binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index 16fbdbdd356..1570a637f95 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -1,4 +1,5 @@ """Support for Toon thermostat.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index 4077d352db9..40e83c3c9be 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Toon component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/toon/const.py b/homeassistant/components/toon/const.py index bf70c54e5e0..d509a70f0b9 100644 --- a/homeassistant/components/toon/const.py +++ b/homeassistant/components/toon/const.py @@ -1,4 +1,5 @@ """Constants for the Toon integration.""" + from datetime import timedelta DOMAIN = "toon" diff --git a/homeassistant/components/toon/coordinator.py b/homeassistant/components/toon/coordinator.py index 58165935215..8d27438f7df 100644 --- a/homeassistant/components/toon/coordinator.py +++ b/homeassistant/components/toon/coordinator.py @@ -1,4 +1,5 @@ """Provides the Toon DataUpdateCoordinator.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/toon/helpers.py b/homeassistant/components/toon/helpers.py index 41e6cd1c6bb..cd4e55fd050 100644 --- a/homeassistant/components/toon/helpers.py +++ b/homeassistant/components/toon/helpers.py @@ -1,4 +1,5 @@ """Helpers for Toon.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/toon/models.py b/homeassistant/components/toon/models.py index 44986b02143..0c08c10bfaf 100644 --- a/homeassistant/components/toon/models.py +++ b/homeassistant/components/toon/models.py @@ -1,4 +1,5 @@ """DataUpdate Coordinator, and base Entity and Device models for Toon.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/toon/oauth2.py b/homeassistant/components/toon/oauth2.py index 95cde386215..2535cc5de7d 100644 --- a/homeassistant/components/toon/oauth2.py +++ b/homeassistant/components/toon/oauth2.py @@ -1,4 +1,5 @@ """OAuth2 implementations for Toon.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 7ff9d2b67f7..09fdcb4e4ab 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,4 +1,5 @@ """Support for Toon sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/toon/switch.py b/homeassistant/components/toon/switch.py index 8dddb657df0..b491505a8a5 100644 --- a/homeassistant/components/toon/switch.py +++ b/homeassistant/components/toon/switch.py @@ -1,4 +1,5 @@ """Support for Toon switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py index 0ac91589c35..6839aa39ccd 100644 --- a/homeassistant/components/torque/sensor.py +++ b/homeassistant/components/torque/sensor.py @@ -1,4 +1,5 @@ """Support for the Torque OBD application.""" + from __future__ import annotations import re diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index b89df6c9c25..50d889eeba1 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -1,4 +1,5 @@ """Interfaces with TotalConnect alarm control panels.""" + from __future__ import annotations from total_connect_client import ArmingHelper diff --git a/homeassistant/components/totalconnect/config_flow.py b/homeassistant/components/totalconnect/config_flow.py index d7cc3c237a3..19d8f09933e 100644 --- a/homeassistant/components/totalconnect/config_flow.py +++ b/homeassistant/components/totalconnect/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Total Connect component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/totalconnect/diagnostics.py b/homeassistant/components/totalconnect/diagnostics.py index 4a9a73c89a9..e3f9b9ba6b3 100644 --- a/homeassistant/components/totalconnect/diagnostics.py +++ b/homeassistant/components/totalconnect/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for TotalConnect.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/touchline/climate.py b/homeassistant/components/touchline/climate.py index 5004646a667..04251648cc1 100644 --- a/homeassistant/components/touchline/climate.py +++ b/homeassistant/components/touchline/climate.py @@ -1,4 +1,5 @@ """Platform for Roth Touchline floor heating controller.""" + from __future__ import annotations from typing import Any, NamedTuple diff --git a/homeassistant/components/tplink/__init__.py b/homeassistant/components/tplink/__init__.py index b8510f7ef81..fbb176b2d5f 100644 --- a/homeassistant/components/tplink/__init__.py +++ b/homeassistant/components/tplink/__init__.py @@ -1,4 +1,5 @@ """Component to embed TP-Link smart home devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tplink/config_flow.py b/homeassistant/components/tplink/config_flow.py index 1d818da868b..919e1a537e5 100644 --- a/homeassistant/components/tplink/config_flow.py +++ b/homeassistant/components/tplink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for TP-Link.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tplink/const.py b/homeassistant/components/tplink/const.py index 57047af8092..96892bacee7 100644 --- a/homeassistant/components/tplink/const.py +++ b/homeassistant/components/tplink/const.py @@ -1,4 +1,5 @@ """Const for TP-Link.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/components/tplink/coordinator.py b/homeassistant/components/tplink/coordinator.py index 798580ef3c2..94ad94de0ae 100644 --- a/homeassistant/components/tplink/coordinator.py +++ b/homeassistant/components/tplink/coordinator.py @@ -1,4 +1,5 @@ """Component to embed TP-Link smart home devices.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/tplink/diagnostics.py b/homeassistant/components/tplink/diagnostics.py index c1b0cf12bfc..e5e84b48162 100644 --- a/homeassistant/components/tplink/diagnostics.py +++ b/homeassistant/components/tplink/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for TPLink.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index 987ac455ae1..4720fae1259 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -1,4 +1,5 @@ """Common code for tplink.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index e27ee7de49f..d007868930a 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -1,4 +1,5 @@ """Support for TPLink lights.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/tplink/models.py b/homeassistant/components/tplink/models.py index 4367f46711d..ced58d3d21f 100644 --- a/homeassistant/components/tplink/models.py +++ b/homeassistant/components/tplink/models.py @@ -1,4 +1,5 @@ """The tplink integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index a3bb35840b2..1f6b07365b5 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -1,4 +1,5 @@ """Support for TPLink HS100/HS110/HS200 smart switch energy sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 8d64fc73147..0d5aa9d2224 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -1,4 +1,5 @@ """Support for TPLink HS100/HS110/HS200 smart switch.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tplink_lte/__init__.py b/homeassistant/components/tplink_lte/__init__.py index d64dc003576..ca9b8311ebe 100644 --- a/homeassistant/components/tplink_lte/__init__.py +++ b/homeassistant/components/tplink_lte/__init__.py @@ -1,4 +1,5 @@ """Support for TP-Link LTE modems.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tplink_lte/notify.py b/homeassistant/components/tplink_lte/notify.py index eb742a5e4e9..674f09efcd7 100644 --- a/homeassistant/components/tplink_lte/notify.py +++ b/homeassistant/components/tplink_lte/notify.py @@ -1,4 +1,5 @@ """Support for TP-Link LTE notifications.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tplink_omada/__init__.py b/homeassistant/components/tplink_omada/__init__.py index 265b31bce9c..f6efeea7678 100644 --- a/homeassistant/components/tplink_omada/__init__.py +++ b/homeassistant/components/tplink_omada/__init__.py @@ -1,4 +1,5 @@ """The TP-Link Omada integration.""" + from __future__ import annotations from tplink_omada_client import OmadaSite diff --git a/homeassistant/components/tplink_omada/binary_sensor.py b/homeassistant/components/tplink_omada/binary_sensor.py index d2679b8b8d4..7b2191f7832 100644 --- a/homeassistant/components/tplink_omada/binary_sensor.py +++ b/homeassistant/components/tplink_omada/binary_sensor.py @@ -1,4 +1,5 @@ """Support for TPLink Omada binary sensors.""" + from __future__ import annotations from collections.abc import Callable, Generator diff --git a/homeassistant/components/tplink_omada/config_flow.py b/homeassistant/components/tplink_omada/config_flow.py index 0071e5cda17..4666968924d 100644 --- a/homeassistant/components/tplink_omada/config_flow.py +++ b/homeassistant/components/tplink_omada/config_flow.py @@ -1,4 +1,5 @@ """Config flow for TP-Link Omada integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tplink_omada/entity.py b/homeassistant/components/tplink_omada/entity.py index 5008b7e4b18..4ae9dc733d8 100644 --- a/homeassistant/components/tplink_omada/entity.py +++ b/homeassistant/components/tplink_omada/entity.py @@ -1,4 +1,5 @@ """Base entity definitions.""" + from typing import Generic, TypeVar from tplink_omada_client.devices import OmadaDevice diff --git a/homeassistant/components/tplink_omada/switch.py b/homeassistant/components/tplink_omada/switch.py index 618b49556d2..ba65f397bd0 100644 --- a/homeassistant/components/tplink_omada/switch.py +++ b/homeassistant/components/tplink_omada/switch.py @@ -1,4 +1,5 @@ """Support for TPLink Omada device toggle options.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tplink_omada/update.py b/homeassistant/components/tplink_omada/update.py index 014302cec65..8a0d32bda18 100644 --- a/homeassistant/components/tplink_omada/update.py +++ b/homeassistant/components/tplink_omada/update.py @@ -1,4 +1,5 @@ """Support for TPLink Omada device firmware updates.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/traccar/__init__.py b/homeassistant/components/traccar/__init__.py index 492f609907e..c42bbd1ab72 100644 --- a/homeassistant/components/traccar/__init__.py +++ b/homeassistant/components/traccar/__init__.py @@ -1,4 +1,5 @@ """Support for Traccar Client.""" + from http import HTTPStatus from aiohttp import web diff --git a/homeassistant/components/traccar/config_flow.py b/homeassistant/components/traccar/config_flow.py index 3d62d0a842d..a1c84e47467 100644 --- a/homeassistant/components/traccar/config_flow.py +++ b/homeassistant/components/traccar/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Traccar Client.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/traccar/device_tracker.py b/homeassistant/components/traccar/device_tracker.py index dbcb30e3a23..93ee63df397 100644 --- a/homeassistant/components/traccar/device_tracker.py +++ b/homeassistant/components/traccar/device_tracker.py @@ -1,4 +1,5 @@ """Support for Traccar device tracking.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/traccar_server/__init__.py b/homeassistant/components/traccar_server/__init__.py index dac54f5e3f8..fc513136681 100644 --- a/homeassistant/components/traccar_server/__init__.py +++ b/homeassistant/components/traccar_server/__init__.py @@ -1,4 +1,5 @@ """The Traccar Server integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/traccar_server/config_flow.py b/homeassistant/components/traccar_server/config_flow.py index 86b0cfa8779..0fa97c8100e 100644 --- a/homeassistant/components/traccar_server/config_flow.py +++ b/homeassistant/components/traccar_server/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Traccar Server integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/traccar_server/const.py b/homeassistant/components/traccar_server/const.py index ca95e706d61..36b88490f95 100644 --- a/homeassistant/components/traccar_server/const.py +++ b/homeassistant/components/traccar_server/const.py @@ -1,4 +1,5 @@ """Constants for the Traccar Server integration.""" + from logging import getLogger DOMAIN = "traccar_server" diff --git a/homeassistant/components/traccar_server/coordinator.py b/homeassistant/components/traccar_server/coordinator.py index 960fdc01fa0..8afa245f1e8 100644 --- a/homeassistant/components/traccar_server/coordinator.py +++ b/homeassistant/components/traccar_server/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for Traccar Server.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/traccar_server/device_tracker.py b/homeassistant/components/traccar_server/device_tracker.py index 226d942e465..e459cdacf14 100644 --- a/homeassistant/components/traccar_server/device_tracker.py +++ b/homeassistant/components/traccar_server/device_tracker.py @@ -1,4 +1,5 @@ """Support for Traccar server device tracking.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/traccar_server/diagnostics.py b/homeassistant/components/traccar_server/diagnostics.py index 15b94a2b880..a6ad6084daf 100644 --- a/homeassistant/components/traccar_server/diagnostics.py +++ b/homeassistant/components/traccar_server/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics platform for Traccar Server.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/traccar_server/entity.py b/homeassistant/components/traccar_server/entity.py index 1c32008d09b..e773bf66562 100644 --- a/homeassistant/components/traccar_server/entity.py +++ b/homeassistant/components/traccar_server/entity.py @@ -1,4 +1,5 @@ """Base entity for Traccar Server.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/traccar_server/helpers.py b/homeassistant/components/traccar_server/helpers.py index ee812c35b8b..971f51376b8 100644 --- a/homeassistant/components/traccar_server/helpers.py +++ b/homeassistant/components/traccar_server/helpers.py @@ -1,4 +1,5 @@ """Helper functions for the Traccar Server integration.""" + from __future__ import annotations from pytraccar import DeviceModel, GeofenceModel diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index 43e591bc6e1..9551c5c7276 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -1,4 +1,5 @@ """Support for script and automation tracing and debugging.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/trace/models.py b/homeassistant/components/trace/models.py index 2fe37412dfb..9f65b05dcd5 100644 --- a/homeassistant/components/trace/models.py +++ b/homeassistant/components/trace/models.py @@ -1,4 +1,5 @@ """Containers for a script or automation trace.""" + from __future__ import annotations import abc diff --git a/homeassistant/components/tractive/__init__.py b/homeassistant/components/tractive/__init__.py index 38080fffe6e..41e691f783e 100644 --- a/homeassistant/components/tractive/__init__.py +++ b/homeassistant/components/tractive/__init__.py @@ -1,4 +1,5 @@ """The tractive integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tractive/binary_sensor.py b/homeassistant/components/tractive/binary_sensor.py index 940ff82687e..dd7237a2b38 100644 --- a/homeassistant/components/tractive/binary_sensor.py +++ b/homeassistant/components/tractive/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Tractive binary sensors.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tractive/config_flow.py b/homeassistant/components/tractive/config_flow.py index 3299b2981a8..a6b0d43a2b7 100644 --- a/homeassistant/components/tractive/config_flow.py +++ b/homeassistant/components/tractive/config_flow.py @@ -1,4 +1,5 @@ """Config flow for tractive integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tractive/device_tracker.py b/homeassistant/components/tractive/device_tracker.py index c115a549fd4..134515469fc 100644 --- a/homeassistant/components/tractive/device_tracker.py +++ b/homeassistant/components/tractive/device_tracker.py @@ -1,4 +1,5 @@ """Support for Tractive device trackers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tractive/diagnostics.py b/homeassistant/components/tractive/diagnostics.py index 6defd91c0fb..f2bc80c51a1 100644 --- a/homeassistant/components/tractive/diagnostics.py +++ b/homeassistant/components/tractive/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Tractive.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tractive/entity.py b/homeassistant/components/tractive/entity.py index da7beb8bcdd..d6050c865b6 100644 --- a/homeassistant/components/tractive/entity.py +++ b/homeassistant/components/tractive/entity.py @@ -1,4 +1,5 @@ """A entity class for Tractive integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tractive/sensor.py b/homeassistant/components/tractive/sensor.py index b563f536e21..928d52c29df 100644 --- a/homeassistant/components/tractive/sensor.py +++ b/homeassistant/components/tractive/sensor.py @@ -1,4 +1,5 @@ """Support for Tractive sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tractive/switch.py b/homeassistant/components/tractive/switch.py index 4c838e5a468..9b8e6002d80 100644 --- a/homeassistant/components/tractive/switch.py +++ b/homeassistant/components/tractive/switch.py @@ -1,4 +1,5 @@ """Support for Tractive switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index aa61be1e782..2e267ffaa14 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -1,4 +1,5 @@ """Support for IKEA Tradfri.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/tradfri/base_class.py b/homeassistant/components/tradfri/base_class.py index abb35df62aa..b06d0081477 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -1,4 +1,5 @@ """Base class for IKEA TRADFRI.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 0a2b6eaef2c..8de40140339 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tradfri.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/tradfri/coordinator.py b/homeassistant/components/tradfri/coordinator.py index 0d90b4ee28c..5246545ae65 100644 --- a/homeassistant/components/tradfri/coordinator.py +++ b/homeassistant/components/tradfri/coordinator.py @@ -1,4 +1,5 @@ """Tradfri DataUpdateCoordinator.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index c51918b4a4f..873b5f3cd07 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -1,4 +1,5 @@ """Support for IKEA Tradfri covers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tradfri/diagnostics.py b/homeassistant/components/tradfri/diagnostics.py index 271e2a226fe..0237382ec39 100644 --- a/homeassistant/components/tradfri/diagnostics.py +++ b/homeassistant/components/tradfri/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for IKEA Tradfri.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index 769c8f6f9e1..ef65c6bf957 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -1,4 +1,5 @@ """Support for IKEA Tradfri lights.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index da7e5336ba5..ac433f89272 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -1,4 +1,5 @@ """Support for IKEA Tradfri sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py index 2f6f1996157..4ad1424aa9a 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -1,4 +1,5 @@ """Support for IKEA Tradfri switches.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/trafikverket_camera/__init__.py b/homeassistant/components/trafikverket_camera/__init__.py index 7303ba6836b..998b667add3 100644 --- a/homeassistant/components/trafikverket_camera/__init__.py +++ b/homeassistant/components/trafikverket_camera/__init__.py @@ -1,4 +1,5 @@ """The trafikverket_camera component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/trafikverket_camera/binary_sensor.py b/homeassistant/components/trafikverket_camera/binary_sensor.py index 0927dc87718..d1c7c7fbc0b 100644 --- a/homeassistant/components/trafikverket_camera/binary_sensor.py +++ b/homeassistant/components/trafikverket_camera/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform for Trafikverket Camera integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/trafikverket_camera/camera.py b/homeassistant/components/trafikverket_camera/camera.py index 808d687a131..0fa70a886b2 100644 --- a/homeassistant/components/trafikverket_camera/camera.py +++ b/homeassistant/components/trafikverket_camera/camera.py @@ -1,4 +1,5 @@ """Camera for the Trafikverket Camera integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/trafikverket_camera/config_flow.py b/homeassistant/components/trafikverket_camera/config_flow.py index 566041ec790..1c2e025ece9 100644 --- a/homeassistant/components/trafikverket_camera/config_flow.py +++ b/homeassistant/components/trafikverket_camera/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Trafikverket Camera integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/trafikverket_camera/const.py b/homeassistant/components/trafikverket_camera/const.py index 728ba9f7bd5..276481752ad 100644 --- a/homeassistant/components/trafikverket_camera/const.py +++ b/homeassistant/components/trafikverket_camera/const.py @@ -1,4 +1,5 @@ """Adds constants for Trafikverket Camera integration.""" + from homeassistant.const import Platform DOMAIN = "trafikverket_camera" diff --git a/homeassistant/components/trafikverket_camera/coordinator.py b/homeassistant/components/trafikverket_camera/coordinator.py index 8270fecd487..03b70009189 100644 --- a/homeassistant/components/trafikverket_camera/coordinator.py +++ b/homeassistant/components/trafikverket_camera/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Trafikverket Camera integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/trafikverket_camera/entity.py b/homeassistant/components/trafikverket_camera/entity.py index ec1d4d8f76b..c564c2673d3 100644 --- a/homeassistant/components/trafikverket_camera/entity.py +++ b/homeassistant/components/trafikverket_camera/entity.py @@ -1,4 +1,5 @@ """Base entity for Trafikverket Camera.""" + from __future__ import annotations from homeassistant.core import callback diff --git a/homeassistant/components/trafikverket_camera/sensor.py b/homeassistant/components/trafikverket_camera/sensor.py index f6d136ef8e7..4cdc809c338 100644 --- a/homeassistant/components/trafikverket_camera/sensor.py +++ b/homeassistant/components/trafikverket_camera/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for Trafikverket Camera integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/trafikverket_ferry/__init__.py b/homeassistant/components/trafikverket_ferry/__init__.py index c522acb6d12..8c8c121881f 100644 --- a/homeassistant/components/trafikverket_ferry/__init__.py +++ b/homeassistant/components/trafikverket_ferry/__init__.py @@ -1,4 +1,5 @@ """The trafikverket_ferry component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/trafikverket_ferry/config_flow.py b/homeassistant/components/trafikverket_ferry/config_flow.py index 007e1cd72c1..3b79cc0f0bd 100644 --- a/homeassistant/components/trafikverket_ferry/config_flow.py +++ b/homeassistant/components/trafikverket_ferry/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Trafikverket Ferry integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/trafikverket_ferry/const.py b/homeassistant/components/trafikverket_ferry/const.py index cdcec530d08..4ec694cbf01 100644 --- a/homeassistant/components/trafikverket_ferry/const.py +++ b/homeassistant/components/trafikverket_ferry/const.py @@ -1,4 +1,5 @@ """Adds constants for Trafikverket Ferry integration.""" + from homeassistant.const import Platform DOMAIN = "trafikverket_ferry" diff --git a/homeassistant/components/trafikverket_ferry/coordinator.py b/homeassistant/components/trafikverket_ferry/coordinator.py index 4c209a3ba87..8d0492b1e43 100644 --- a/homeassistant/components/trafikverket_ferry/coordinator.py +++ b/homeassistant/components/trafikverket_ferry/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Trafikverket Ferry integration.""" + from __future__ import annotations from datetime import date, datetime, time, timedelta diff --git a/homeassistant/components/trafikverket_ferry/sensor.py b/homeassistant/components/trafikverket_ferry/sensor.py index f21e9e494b1..ff6b9d2e1ca 100644 --- a/homeassistant/components/trafikverket_ferry/sensor.py +++ b/homeassistant/components/trafikverket_ferry/sensor.py @@ -1,4 +1,5 @@ """Ferry information for departures, provided by Trafikverket.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/trafikverket_ferry/util.py b/homeassistant/components/trafikverket_ferry/util.py index a78f6f82f1a..a45e8b31daa 100644 --- a/homeassistant/components/trafikverket_ferry/util.py +++ b/homeassistant/components/trafikverket_ferry/util.py @@ -1,4 +1,5 @@ """Utils for trafikverket_ferry.""" + from __future__ import annotations from datetime import time diff --git a/homeassistant/components/trafikverket_train/__init__.py b/homeassistant/components/trafikverket_train/__init__.py index a7defa2956a..8b427c3431d 100644 --- a/homeassistant/components/trafikverket_train/__init__.py +++ b/homeassistant/components/trafikverket_train/__init__.py @@ -1,4 +1,5 @@ """The trafikverket_train component.""" + from __future__ import annotations from pytrafikverket import TrafikverketTrain diff --git a/homeassistant/components/trafikverket_train/config_flow.py b/homeassistant/components/trafikverket_train/config_flow.py index 5022921c310..48e603eff02 100644 --- a/homeassistant/components/trafikverket_train/config_flow.py +++ b/homeassistant/components/trafikverket_train/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Trafikverket Train integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/trafikverket_train/const.py b/homeassistant/components/trafikverket_train/const.py index e1852ce9ada..a97f3a547e2 100644 --- a/homeassistant/components/trafikverket_train/const.py +++ b/homeassistant/components/trafikverket_train/const.py @@ -1,4 +1,5 @@ """Adds constants for Trafikverket Train integration.""" + from homeassistant.const import Platform DOMAIN = "trafikverket_train" diff --git a/homeassistant/components/trafikverket_train/coordinator.py b/homeassistant/components/trafikverket_train/coordinator.py index d5402e44ec6..cf78228ed58 100644 --- a/homeassistant/components/trafikverket_train/coordinator.py +++ b/homeassistant/components/trafikverket_train/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Trafikverket Train integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index 3dd513e6650..adc8250b8d9 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -1,4 +1,5 @@ """Train information for departures and delays, provided by Trafikverket.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/trafikverket_train/util.py b/homeassistant/components/trafikverket_train/util.py index c5553c4a4a7..b28a51d339d 100644 --- a/homeassistant/components/trafikverket_train/util.py +++ b/homeassistant/components/trafikverket_train/util.py @@ -1,4 +1,5 @@ """Utils for trafikverket_train.""" + from __future__ import annotations from datetime import date, time, timedelta diff --git a/homeassistant/components/trafikverket_weatherstation/__init__.py b/homeassistant/components/trafikverket_weatherstation/__init__.py index 13b88918133..e1cd9c90909 100644 --- a/homeassistant/components/trafikverket_weatherstation/__init__.py +++ b/homeassistant/components/trafikverket_weatherstation/__init__.py @@ -1,4 +1,5 @@ """The trafikverket_weatherstation component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/trafikverket_weatherstation/config_flow.py b/homeassistant/components/trafikverket_weatherstation/config_flow.py index 9de085bcddd..05be4fc460e 100644 --- a/homeassistant/components/trafikverket_weatherstation/config_flow.py +++ b/homeassistant/components/trafikverket_weatherstation/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Trafikverket Weather integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/trafikverket_weatherstation/const.py b/homeassistant/components/trafikverket_weatherstation/const.py index 34c18359ee4..880de3867ba 100644 --- a/homeassistant/components/trafikverket_weatherstation/const.py +++ b/homeassistant/components/trafikverket_weatherstation/const.py @@ -1,4 +1,5 @@ """Adds constants for Trafikverket Weather integration.""" + from homeassistant.const import Platform DOMAIN = "trafikverket_weatherstation" diff --git a/homeassistant/components/trafikverket_weatherstation/coordinator.py b/homeassistant/components/trafikverket_weatherstation/coordinator.py index 40c551089d2..508ae7eec16 100644 --- a/homeassistant/components/trafikverket_weatherstation/coordinator.py +++ b/homeassistant/components/trafikverket_weatherstation/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Trafikverket Weather integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index 0553213862e..6fd63c83f4e 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -1,4 +1,5 @@ """Weather information for air and road temperature (by Trafikverket).""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index df78c5d96aa..bbf4bf5d630 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -1,4 +1,5 @@ """Support for the Transmission BitTorrent client API.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index 9aed54c3e71..de3616646bf 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Transmission Bittorent Client.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py index 64b15c51691..98f9184f1af 100644 --- a/homeassistant/components/transmission/const.py +++ b/homeassistant/components/transmission/const.py @@ -1,4 +1,5 @@ """Constants for the Transmission Bittorent Client component.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/transmission/coordinator.py b/homeassistant/components/transmission/coordinator.py index d03ef5e37fb..1c379685c1c 100644 --- a/homeassistant/components/transmission/coordinator.py +++ b/homeassistant/components/transmission/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for transmssion integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/transmission/errors.py b/homeassistant/components/transmission/errors.py index b5f74f7bf40..68d442c3a74 100644 --- a/homeassistant/components/transmission/errors.py +++ b/homeassistant/components/transmission/errors.py @@ -1,4 +1,5 @@ """Errors for the Transmission component.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/transmission/sensor.py b/homeassistant/components/transmission/sensor.py index 87bcb87da9a..9ee42045aab 100644 --- a/homeassistant/components/transmission/sensor.py +++ b/homeassistant/components/transmission/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the Transmission BitTorrent client API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 643b2f0ba70..1f95fc492fe 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -1,4 +1,5 @@ """Support for setting the Transmission BitTorrent client Turtle Mode.""" + from collections.abc import Callable from dataclasses import dataclass import logging diff --git a/homeassistant/components/transport_nsw/sensor.py b/homeassistant/components/transport_nsw/sensor.py index 520b1a5626b..4ec4301dc7b 100644 --- a/homeassistant/components/transport_nsw/sensor.py +++ b/homeassistant/components/transport_nsw/sensor.py @@ -1,4 +1,5 @@ """Support for Transport NSW (AU) to query next leave event.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/travisci/sensor.py b/homeassistant/components/travisci/sensor.py index 6a30c1b62ba..0a3118b3cca 100644 --- a/homeassistant/components/travisci/sensor.py +++ b/homeassistant/components/travisci/sensor.py @@ -1,4 +1,5 @@ """Component providing HA sensor support for Travis CI framework.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/trend/__init__.py b/homeassistant/components/trend/__init__.py index 91d50bcc928..7ec2d140c5e 100644 --- a/homeassistant/components/trend/__init__.py +++ b/homeassistant/components/trend/__init__.py @@ -1,4 +1,5 @@ """A sensor that monitors trends in other components.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index c86fb65e966..a9d909fd8b4 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -1,4 +1,5 @@ """A sensor that monitors trends in other components.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/components/trend/config_flow.py b/homeassistant/components/trend/config_flow.py index 3d29618281a..f91e81bf4e8 100644 --- a/homeassistant/components/trend/config_flow.py +++ b/homeassistant/components/trend/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Trend integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 9a44382e851..ca1ed0d6027 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -1,4 +1,5 @@ """Provide functionality for TTS.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/tts/helper.py b/homeassistant/components/tts/helper.py index 8cbfcbd8935..4b5ef168550 100644 --- a/homeassistant/components/tts/helper.py +++ b/homeassistant/components/tts/helper.py @@ -1,4 +1,5 @@ """Provide helper functions for the TTS.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/tts/legacy.py b/homeassistant/components/tts/legacy.py index 05be2e284e3..9d70139ff74 100644 --- a/homeassistant/components/tts/legacy.py +++ b/homeassistant/components/tts/legacy.py @@ -1,4 +1,5 @@ """Provide the legacy TTS service provider interface.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/tts/media_source.py b/homeassistant/components/tts/media_source.py index 837a15a4f88..a907fc485c9 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -1,4 +1,5 @@ """Text-to-speech media source.""" + from __future__ import annotations import mimetypes diff --git a/homeassistant/components/tts/models.py b/homeassistant/components/tts/models.py index 1ea49b1e9ed..2d693571a0f 100644 --- a/homeassistant/components/tts/models.py +++ b/homeassistant/components/tts/models.py @@ -1,4 +1,5 @@ """Text-to-speech data models.""" + from dataclasses import dataclass diff --git a/homeassistant/components/tts/notify.py b/homeassistant/components/tts/notify.py index c2576e12bb5..e6963619043 100644 --- a/homeassistant/components/tts/notify.py +++ b/homeassistant/components/tts/notify.py @@ -1,4 +1,5 @@ """Support notifications through TTS service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index a1ce3a60efe..ceb8f056c22 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -1,4 +1,5 @@ """Support for Tuya Smart devices.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/tuya/alarm_control_panel.py b/homeassistant/components/tuya/alarm_control_panel.py index 3cf9cd718ae..74392b20148 100644 --- a/homeassistant/components/tuya/alarm_control_panel.py +++ b/homeassistant/components/tuya/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Tuya Alarm.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index 7c4e213fe65..409e0b2e763 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -1,4 +1,5 @@ """Tuya Home Assistant Base Device Model.""" + from __future__ import annotations import base64 diff --git a/homeassistant/components/tuya/binary_sensor.py b/homeassistant/components/tuya/binary_sensor.py index c66581ef46c..c9f4734a7df 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Tuya binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tuya/button.py b/homeassistant/components/tuya/button.py index 41957fe7e73..c0363eebe72 100644 --- a/homeassistant/components/tuya/button.py +++ b/homeassistant/components/tuya/button.py @@ -1,4 +1,5 @@ """Support for Tuya buttons.""" + from __future__ import annotations from tuya_sharing import CustomerDevice, Manager diff --git a/homeassistant/components/tuya/camera.py b/homeassistant/components/tuya/camera.py index 07c4adb8889..79f8c1b1692 100644 --- a/homeassistant/components/tuya/camera.py +++ b/homeassistant/components/tuya/camera.py @@ -1,4 +1,5 @@ """Support for Tuya cameras.""" + from __future__ import annotations from tuya_sharing import CustomerDevice, Manager diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 45adb532705..3f0626ba82c 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,4 +1,5 @@ """Support for Tuya Climate.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index 397da0618fb..bdef321de7a 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tuya.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 8f15114aa80..a9c53d807bc 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -1,4 +1,5 @@ """Constants for the Tuya integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 912087d2c8c..21191ece553 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -1,4 +1,5 @@ """Support for Tuya Cover.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tuya/diagnostics.py b/homeassistant/components/tuya/diagnostics.py index cdd0d5ed51c..f817261c8fc 100644 --- a/homeassistant/components/tuya/diagnostics.py +++ b/homeassistant/components/tuya/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Tuya.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index 0971462e450..3925da1d507 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -1,4 +1,5 @@ """Support for Tuya Fan.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tuya/humidifier.py b/homeassistant/components/tuya/humidifier.py index 7cc4fee03fc..927aaf8a74a 100644 --- a/homeassistant/components/tuya/humidifier.py +++ b/homeassistant/components/tuya/humidifier.py @@ -1,4 +1,5 @@ """Support for Tuya (de)humidifiers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 55833d50bdd..0f769191710 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -1,4 +1,5 @@ """Support for the Tuya lights.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index fdc578bc53e..f8d05d0841e 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -1,4 +1,5 @@ """Support for Tuya number.""" + from __future__ import annotations from tuya_sharing import CustomerDevice, Manager diff --git a/homeassistant/components/tuya/scene.py b/homeassistant/components/tuya/scene.py index 8db3ef60658..dcc1aae1fba 100644 --- a/homeassistant/components/tuya/scene.py +++ b/homeassistant/components/tuya/scene.py @@ -1,4 +1,5 @@ """Support for Tuya scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index ecc0571962b..472f11e12b0 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -1,4 +1,5 @@ """Support for Tuya select.""" + from __future__ import annotations from tuya_sharing import CustomerDevice, Manager diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 00ee4ea5902..7669cfa0f32 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -1,4 +1,5 @@ """Support for Tuya sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/tuya/siren.py b/homeassistant/components/tuya/siren.py index baba339318d..1f1707a1fc7 100644 --- a/homeassistant/components/tuya/siren.py +++ b/homeassistant/components/tuya/siren.py @@ -1,4 +1,5 @@ """Support for Tuya siren.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index c2797be0adc..9b90d391d98 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -1,4 +1,5 @@ """Support for Tuya switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/tuya/util.py b/homeassistant/components/tuya/util.py index 3b29a3e13cf..b6e6f17f49b 100644 --- a/homeassistant/components/tuya/util.py +++ b/homeassistant/components/tuya/util.py @@ -1,4 +1,5 @@ """Utility methods for the Tuya integration.""" + from __future__ import annotations diff --git a/homeassistant/components/tuya/vacuum.py b/homeassistant/components/tuya/vacuum.py index 14ae9c4c426..6774aaac8a1 100644 --- a/homeassistant/components/tuya/vacuum.py +++ b/homeassistant/components/tuya/vacuum.py @@ -1,4 +1,5 @@ """Support for Tuya Vacuums.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/twentemilieu/__init__.py b/homeassistant/components/twentemilieu/__init__.py index c4fe53c67f0..293bab54e16 100644 --- a/homeassistant/components/twentemilieu/__init__.py +++ b/homeassistant/components/twentemilieu/__init__.py @@ -1,4 +1,5 @@ """Support for Twente Milieu.""" + from __future__ import annotations from datetime import date, timedelta diff --git a/homeassistant/components/twentemilieu/calendar.py b/homeassistant/components/twentemilieu/calendar.py index e17e61e727a..8bd008e3eb3 100644 --- a/homeassistant/components/twentemilieu/calendar.py +++ b/homeassistant/components/twentemilieu/calendar.py @@ -1,4 +1,5 @@ """Support for Twente Milieu Calendar.""" + from __future__ import annotations from datetime import date, datetime, timedelta diff --git a/homeassistant/components/twentemilieu/config_flow.py b/homeassistant/components/twentemilieu/config_flow.py index 160aecef947..e87dde3a699 100644 --- a/homeassistant/components/twentemilieu/config_flow.py +++ b/homeassistant/components/twentemilieu/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Twente Milieu integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/twentemilieu/const.py b/homeassistant/components/twentemilieu/const.py index ac7354a42f2..e5415e09b81 100644 --- a/homeassistant/components/twentemilieu/const.py +++ b/homeassistant/components/twentemilieu/const.py @@ -1,4 +1,5 @@ """Constants for the Twente Milieu integration.""" + from datetime import timedelta import logging from typing import Final diff --git a/homeassistant/components/twentemilieu/diagnostics.py b/homeassistant/components/twentemilieu/diagnostics.py index acc3f802796..ea68473ae3b 100644 --- a/homeassistant/components/twentemilieu/diagnostics.py +++ b/homeassistant/components/twentemilieu/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for TwenteMilieu.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/twentemilieu/entity.py b/homeassistant/components/twentemilieu/entity.py index 5c1d71fa03b..1e0fa651998 100644 --- a/homeassistant/components/twentemilieu/entity.py +++ b/homeassistant/components/twentemilieu/entity.py @@ -1,4 +1,5 @@ """Base entity for the Twente Milieu integration.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index f47d4100a6a..f799fa62314 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -1,4 +1,5 @@ """Support for Twente Milieu sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/twilio/__init__.py b/homeassistant/components/twilio/__init__.py index e71f3181b55..72e69912774 100644 --- a/homeassistant/components/twilio/__init__.py +++ b/homeassistant/components/twilio/__init__.py @@ -1,4 +1,5 @@ """Support for Twilio.""" + from aiohttp import web from twilio.rest import Client import voluptuous as vol diff --git a/homeassistant/components/twilio/config_flow.py b/homeassistant/components/twilio/config_flow.py index 1539c1ffadc..14dd336f0d7 100644 --- a/homeassistant/components/twilio/config_flow.py +++ b/homeassistant/components/twilio/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Twilio.""" + from homeassistant.helpers import config_entry_flow from .const import DOMAIN diff --git a/homeassistant/components/twilio_call/notify.py b/homeassistant/components/twilio_call/notify.py index 44eaa0bf994..d3d128ccd25 100644 --- a/homeassistant/components/twilio_call/notify.py +++ b/homeassistant/components/twilio_call/notify.py @@ -1,4 +1,5 @@ """Twilio Call platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/twilio_sms/notify.py b/homeassistant/components/twilio_sms/notify.py index 0b0c724e479..2c04594f314 100644 --- a/homeassistant/components/twilio_sms/notify.py +++ b/homeassistant/components/twilio_sms/notify.py @@ -1,4 +1,5 @@ """Twilio SMS platform for notify component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/twinkly/config_flow.py b/homeassistant/components/twinkly/config_flow.py index 17acb4f165e..98802c8bd33 100644 --- a/homeassistant/components/twinkly/config_flow.py +++ b/homeassistant/components/twinkly/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Twinkly integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/twinkly/diagnostics.py b/homeassistant/components/twinkly/diagnostics.py index 598eab0fca5..e188e92ecd5 100644 --- a/homeassistant/components/twinkly/diagnostics.py +++ b/homeassistant/components/twinkly/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Twinkly.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 35b5f8bd1d3..6bfc205b553 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -1,4 +1,5 @@ """The Twinkly light component.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/twitch/__init__.py b/homeassistant/components/twitch/__init__.py index a26b7e94035..5882b1da74d 100644 --- a/homeassistant/components/twitch/__init__.py +++ b/homeassistant/components/twitch/__init__.py @@ -1,4 +1,5 @@ """The Twitch component.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/twitch/config_flow.py b/homeassistant/components/twitch/config_flow.py index b8be2ac6e5a..f9e121f3a17 100644 --- a/homeassistant/components/twitch/config_flow.py +++ b/homeassistant/components/twitch/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Twitch.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index c1af382ff86..9a936981f24 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -1,4 +1,5 @@ """Support for the Twitch stream status.""" + from __future__ import annotations from twitchAPI.helper import first diff --git a/homeassistant/components/twitter/notify.py b/homeassistant/components/twitter/notify.py index c6d223d2413..718f4f7dbcf 100644 --- a/homeassistant/components/twitter/notify.py +++ b/homeassistant/components/twitter/notify.py @@ -1,4 +1,5 @@ """Twitter platform for notify component.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/ubus/device_tracker.py b/homeassistant/components/ubus/device_tracker.py index f49a06be9dd..0aebea84c7d 100644 --- a/homeassistant/components/ubus/device_tracker.py +++ b/homeassistant/components/ubus/device_tracker.py @@ -1,4 +1,5 @@ """Support for OpenWRT (ubus) routers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ue_smart_radio/media_player.py b/homeassistant/components/ue_smart_radio/media_player.py index 7fc727cf9fe..90afca69816 100644 --- a/homeassistant/components/ue_smart_radio/media_player.py +++ b/homeassistant/components/ue_smart_radio/media_player.py @@ -1,4 +1,5 @@ """Support for Logitech UE Smart Radios.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/uk_transport/sensor.py b/homeassistant/components/uk_transport/sensor.py index 3412a004167..24a88724add 100644 --- a/homeassistant/components/uk_transport/sensor.py +++ b/homeassistant/components/uk_transport/sensor.py @@ -1,4 +1,5 @@ """Support for UK public transport data provided by transportapi.com.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/ukraine_alarm/__init__.py b/homeassistant/components/ukraine_alarm/__init__.py index 1132bd56b72..b90fb20af75 100644 --- a/homeassistant/components/ukraine_alarm/__init__.py +++ b/homeassistant/components/ukraine_alarm/__init__.py @@ -1,4 +1,5 @@ """The ukraine_alarm component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/ukraine_alarm/binary_sensor.py b/homeassistant/components/ukraine_alarm/binary_sensor.py index cc816e17704..0eb8bd7b43c 100644 --- a/homeassistant/components/ukraine_alarm/binary_sensor.py +++ b/homeassistant/components/ukraine_alarm/binary_sensor.py @@ -1,4 +1,5 @@ """binary sensors for Ukraine Alarm integration.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/ukraine_alarm/config_flow.py b/homeassistant/components/ukraine_alarm/config_flow.py index a9d50443092..9b593b77639 100644 --- a/homeassistant/components/ukraine_alarm/config_flow.py +++ b/homeassistant/components/ukraine_alarm/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ukraine Alarm.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ukraine_alarm/const.py b/homeassistant/components/ukraine_alarm/const.py index bb0902293d4..6634bacf698 100644 --- a/homeassistant/components/ukraine_alarm/const.py +++ b/homeassistant/components/ukraine_alarm/const.py @@ -1,4 +1,5 @@ """Consts for the Ukraine Alarm.""" + from __future__ import annotations from homeassistant.const import Platform diff --git a/homeassistant/components/unifi/button.py b/homeassistant/components/unifi/button.py index 545bb08a85a..950ad2f8361 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -2,6 +2,7 @@ Support for restarting UniFi devices. """ + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index ae55d7ffcc9..435d36d8feb 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -5,6 +5,7 @@ Discovery of UniFi Network instances hosted on UDM and UDM Pro devices through SSDP. Reauthentication when issue with credentials are reported. Configuration of options through options flow. """ + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/unifi/diagnostics.py b/homeassistant/components/unifi/diagnostics.py index 2482f5ca314..7df082ca0a4 100644 --- a/homeassistant/components/unifi/diagnostics.py +++ b/homeassistant/components/unifi/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for UniFi Network.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/unifi/entity.py b/homeassistant/components/unifi/entity.py index d3dc1f51873..5f63fdb1980 100644 --- a/homeassistant/components/unifi/entity.py +++ b/homeassistant/components/unifi/entity.py @@ -1,4 +1,5 @@ """UniFi entity representation.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/unifi/errors.py b/homeassistant/components/unifi/errors.py index 568bd5fb842..d33e862cafd 100644 --- a/homeassistant/components/unifi/errors.py +++ b/homeassistant/components/unifi/errors.py @@ -1,4 +1,5 @@ """Errors for the UniFi Network integration.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/unifi/hub/config.py b/homeassistant/components/unifi/hub/config.py index be2da7e47c9..52b15e1353c 100644 --- a/homeassistant/components/unifi/hub/config.py +++ b/homeassistant/components/unifi/hub/config.py @@ -1,4 +1,5 @@ """UniFi Network config entry abstraction.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index 6acfd1759f6..89e741c43d5 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -1,4 +1,5 @@ """UniFi Network abstraction.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/unifi/image.py b/homeassistant/components/unifi/image.py index fb3cdf0b39e..3ea53d5b3f1 100644 --- a/homeassistant/components/unifi/image.py +++ b/homeassistant/components/unifi/image.py @@ -2,6 +2,7 @@ Support for QR code for guest WLANs. """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index e83add10040..ccdabc54d24 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -3,6 +3,7 @@ Support for bandwidth sensors of network clients. Support for uptime sensors of network clients. """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/unifi/services.py b/homeassistant/components/unifi/services.py index 2017db4a0a8..096f4f27dae 100644 --- a/homeassistant/components/unifi/services.py +++ b/homeassistant/components/unifi/services.py @@ -1,4 +1,5 @@ """UniFi Network services.""" + from collections.abc import Mapping from typing import Any diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 7e8a422ef23..1e6e0beaa8f 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -5,6 +5,7 @@ Support for controlling network access of clients selected in option flow. Support for controlling deep packet inspection (DPI) restriction groups. Support for controlling WLAN availability. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/components/unifi/update.py b/homeassistant/components/unifi/update.py index bb9771c170a..a8a2dbe3b23 100644 --- a/homeassistant/components/unifi/update.py +++ b/homeassistant/components/unifi/update.py @@ -1,4 +1,5 @@ """Update entities for Ubiquiti network devices.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/unifi_direct/device_tracker.py b/homeassistant/components/unifi_direct/device_tracker.py index 77ce5d80cf9..51c9c412dad 100644 --- a/homeassistant/components/unifi_direct/device_tracker.py +++ b/homeassistant/components/unifi_direct/device_tracker.py @@ -1,4 +1,5 @@ """Support for Unifi AP direct access.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/unifiled/light.py b/homeassistant/components/unifiled/light.py index f1d3ad15a02..f69ea5712de 100644 --- a/homeassistant/components/unifiled/light.py +++ b/homeassistant/components/unifiled/light.py @@ -1,4 +1,5 @@ """Support for Unifi Led lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index c4a6bc88068..45d80b85c58 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -1,4 +1,5 @@ """UniFi Protect Platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index e82a0ef5553..66c1a0a4e33 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -1,4 +1,5 @@ """Component providing binary sensors for UniFi Protect.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 2046c12ddbd..db27306aedf 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -1,4 +1,5 @@ """Support for Ubiquiti's UniFi Protect NVR.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index ca7abaac3c4..781653d4ca4 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -1,4 +1,5 @@ """Support for Ubiquiti's UniFi Protect NVR.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 55c93be58b6..555ddcb8d5e 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -1,4 +1,5 @@ """Config Flow to configure UniFi Protect Integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 2825c2a4f3c..0e11c28e09d 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -1,4 +1,5 @@ """Base class for protect data.""" + from __future__ import annotations from collections.abc import Callable, Generator, Iterable diff --git a/homeassistant/components/unifiprotect/diagnostics.py b/homeassistant/components/unifiprotect/diagnostics.py index 6d4ebcd975d..b85870a08c5 100644 --- a/homeassistant/components/unifiprotect/diagnostics.py +++ b/homeassistant/components/unifiprotect/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for UniFi Network.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/unifiprotect/discovery.py b/homeassistant/components/unifiprotect/discovery.py index 885781c6557..860ebeb2787 100644 --- a/homeassistant/components/unifiprotect/discovery.py +++ b/homeassistant/components/unifiprotect/discovery.py @@ -1,4 +1,5 @@ """The unifiprotect integration discovery.""" + from __future__ import annotations from dataclasses import asdict diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 59c716d4aa4..8366f2b7f04 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -1,4 +1,5 @@ """Shared Entity definition for UniFi Protect Integration.""" + from __future__ import annotations from collections.abc import Callable, Sequence diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index e068172037a..5a1e5dfd0c4 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -1,4 +1,5 @@ """Component providing Lights for UniFi Protect.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index 5bfa65fccf9..c54f9b316ff 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -1,4 +1,5 @@ """Support for locks on Ubiquiti's UniFi Protect NVR.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 82e2ccd0be0..50fec39e9cb 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -1,4 +1,5 @@ """Support for Ubiquiti's UniFi Protect NVR.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index 3a6dde653b4..601220bc4f0 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -1,4 +1,5 @@ """UniFi Protect data migrations.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/unifiprotect/models.py b/homeassistant/components/unifiprotect/models.py index f7da2f781ff..a9c79556135 100644 --- a/homeassistant/components/unifiprotect/models.py +++ b/homeassistant/components/unifiprotect/models.py @@ -1,4 +1,5 @@ """The unifiprotect integration models.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 68ae3a66d10..89d8552f3ab 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -1,4 +1,5 @@ """Component providing number entities for UniFi Protect.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 5611ba79eca..7d1ca436c78 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -1,4 +1,5 @@ """Component providing select entities for UniFi Protect.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index c4d1f8a530d..b19b3daadee 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -1,4 +1,5 @@ """Component providing sensors for UniFi Protect.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/unifiprotect/services.py b/homeassistant/components/unifiprotect/services.py index 90a2d5167c5..da11dbd7bc4 100644 --- a/homeassistant/components/unifiprotect/services.py +++ b/homeassistant/components/unifiprotect/services.py @@ -1,4 +1,5 @@ """UniFi Protect Integration services.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 2090e8baef8..148170d00c0 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -1,4 +1,5 @@ """Component providing Switches for UniFi Protect.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/unifiprotect/text.py b/homeassistant/components/unifiprotect/text.py index 2aebcfa1da9..584bd511ee5 100644 --- a/homeassistant/components/unifiprotect/text.py +++ b/homeassistant/components/unifiprotect/text.py @@ -1,4 +1,5 @@ """Text entities for UniFi Protect.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/unifiprotect/utils.py b/homeassistant/components/unifiprotect/utils.py index f07e1eb9554..00f9f9c0cd1 100644 --- a/homeassistant/components/unifiprotect/utils.py +++ b/homeassistant/components/unifiprotect/utils.py @@ -1,4 +1,5 @@ """UniFi Protect Integration utils.""" + from __future__ import annotations from collections.abc import Generator, Iterable diff --git a/homeassistant/components/unifiprotect/views.py b/homeassistant/components/unifiprotect/views.py index e05dcde1751..0aa7056976b 100644 --- a/homeassistant/components/unifiprotect/views.py +++ b/homeassistant/components/unifiprotect/views.py @@ -1,4 +1,5 @@ """UniFi Protect Integration views.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 00f345fd248..033fd35ae51 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -1,4 +1,5 @@ """Combination of multiple media players for a universal controller.""" + from __future__ import annotations from copy import copy diff --git a/homeassistant/components/upb/light.py b/homeassistant/components/upb/light.py index 50e6d50bb4c..eb20fc949dc 100644 --- a/homeassistant/components/upb/light.py +++ b/homeassistant/components/upb/light.py @@ -1,4 +1,5 @@ """Platform for UPB light integration.""" + from typing import Any from homeassistant.components.light import ( diff --git a/homeassistant/components/upb/scene.py b/homeassistant/components/upb/scene.py index d1272b7a1f6..9cf6788de4f 100644 --- a/homeassistant/components/upb/scene.py +++ b/homeassistant/components/upb/scene.py @@ -1,4 +1,5 @@ """Platform for UPB link integration.""" + from typing import Any from homeassistant.components.scene import Scene diff --git a/homeassistant/components/upc_connect/device_tracker.py b/homeassistant/components/upc_connect/device_tracker.py index 2b5ee2915ef..9e570c9d26b 100644 --- a/homeassistant/components/upc_connect/device_tracker.py +++ b/homeassistant/components/upc_connect/device_tracker.py @@ -1,4 +1,5 @@ """Support for UPC ConnectBox router.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/upcloud/__init__.py b/homeassistant/components/upcloud/__init__.py index 49ec97f073b..371dedab49c 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -1,4 +1,5 @@ """Support for UpCloud.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/update/__init__.py b/homeassistant/components/update/__init__.py index 8ec14b6e3a8..d52216974aa 100644 --- a/homeassistant/components/update/__init__.py +++ b/homeassistant/components/update/__init__.py @@ -1,4 +1,5 @@ """Component to allow for providing device or service updates.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/update/const.py b/homeassistant/components/update/const.py index c9340473298..0d7da94f656 100644 --- a/homeassistant/components/update/const.py +++ b/homeassistant/components/update/const.py @@ -1,4 +1,5 @@ """Constants for the update component.""" + from __future__ import annotations from enum import IntFlag diff --git a/homeassistant/components/update/device_trigger.py b/homeassistant/components/update/device_trigger.py index bd0e1a6e1b7..1058acc3ee3 100644 --- a/homeassistant/components/update/device_trigger.py +++ b/homeassistant/components/update/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for update entities.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/update/significant_change.py b/homeassistant/components/update/significant_change.py index 8b37d227a1f..30f6dd3244e 100644 --- a/homeassistant/components/update/significant_change.py +++ b/homeassistant/components/update/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant update state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 2e546f8893f..893b86cd1b2 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -1,4 +1,5 @@ """UPnP/IGD integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/upnp/binary_sensor.py b/homeassistant/components/upnp/binary_sensor.py index 676b9588ddb..71c13d0c8a9 100644 --- a/homeassistant/components/upnp/binary_sensor.py +++ b/homeassistant/components/upnp/binary_sensor.py @@ -1,4 +1,5 @@ """Support for UPnP/IGD Binary Sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 939be005f64..d02877ccd05 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,4 +1,5 @@ """Config flow for UPNP.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py index 1f33f7cc676..e7b44329546 100644 --- a/homeassistant/components/upnp/const.py +++ b/homeassistant/components/upnp/const.py @@ -1,4 +1,5 @@ """Constants for the IGD component.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 2f52a5d008f..9e8aaea198b 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -1,4 +1,5 @@ """Home Assistant representation of an UPnP/IGD.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/upnp/entity.py b/homeassistant/components/upnp/entity.py index 504602372f7..9fef27cb7a1 100644 --- a/homeassistant/components/upnp/entity.py +++ b/homeassistant/components/upnp/entity.py @@ -1,4 +1,5 @@ """Entity for UPnP/IGD.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/upnp/sensor.py b/homeassistant/components/upnp/sensor.py index ce7b95bbbf3..5d72904bfaf 100644 --- a/homeassistant/components/upnp/sensor.py +++ b/homeassistant/components/upnp/sensor.py @@ -1,4 +1,5 @@ """Support for UPnP/IGD Sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/uptime/__init__.py b/homeassistant/components/uptime/__init__.py index b2f912751f7..3b20abd14d2 100644 --- a/homeassistant/components/uptime/__init__.py +++ b/homeassistant/components/uptime/__init__.py @@ -1,4 +1,5 @@ """The Uptime integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/uptime/config_flow.py b/homeassistant/components/uptime/config_flow.py index d5215cc6c06..2cfec38d200 100644 --- a/homeassistant/components/uptime/config_flow.py +++ b/homeassistant/components/uptime/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Uptime integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/uptime/const.py b/homeassistant/components/uptime/const.py index 559e0f62273..9db69cbdfe6 100644 --- a/homeassistant/components/uptime/const.py +++ b/homeassistant/components/uptime/const.py @@ -1,4 +1,5 @@ """Constants for the Uptime integration.""" + from typing import Final from homeassistant.const import Platform diff --git a/homeassistant/components/uptime/sensor.py b/homeassistant/components/uptime/sensor.py index 55faf7ccb3a..266542de9d6 100644 --- a/homeassistant/components/uptime/sensor.py +++ b/homeassistant/components/uptime/sensor.py @@ -1,4 +1,5 @@ """Platform to retrieve uptime for Home Assistant.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/uptimerobot/__init__.py b/homeassistant/components/uptimerobot/__init__.py index 58979d7defb..75fa5eeabcc 100644 --- a/homeassistant/components/uptimerobot/__init__.py +++ b/homeassistant/components/uptimerobot/__init__.py @@ -1,4 +1,5 @@ """The UptimeRobot integration.""" + from __future__ import annotations from pyuptimerobot import UptimeRobot diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index 2710d5166c2..0c1bd972387 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -1,4 +1,5 @@ """UptimeRobot binary_sensor platform.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/uptimerobot/config_flow.py b/homeassistant/components/uptimerobot/config_flow.py index 1303f2cc6e3..0afee9cfacb 100644 --- a/homeassistant/components/uptimerobot/config_flow.py +++ b/homeassistant/components/uptimerobot/config_flow.py @@ -1,4 +1,5 @@ """Config flow for UptimeRobot integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/uptimerobot/const.py b/homeassistant/components/uptimerobot/const.py index e89c1c38e0e..1ac234afa64 100644 --- a/homeassistant/components/uptimerobot/const.py +++ b/homeassistant/components/uptimerobot/const.py @@ -1,4 +1,5 @@ """Constants for the UptimeRobot integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/uptimerobot/coordinator.py b/homeassistant/components/uptimerobot/coordinator.py index 4c1d3ea2c78..3069884eb99 100644 --- a/homeassistant/components/uptimerobot/coordinator.py +++ b/homeassistant/components/uptimerobot/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the uptimerobot integration.""" + from __future__ import annotations from pyuptimerobot import ( diff --git a/homeassistant/components/uptimerobot/diagnostics.py b/homeassistant/components/uptimerobot/diagnostics.py index 15173a5e43c..23c65373045 100644 --- a/homeassistant/components/uptimerobot/diagnostics.py +++ b/homeassistant/components/uptimerobot/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for UptimeRobot.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/uptimerobot/entity.py b/homeassistant/components/uptimerobot/entity.py index 3057bd7c220..71f7a2f1c00 100644 --- a/homeassistant/components/uptimerobot/entity.py +++ b/homeassistant/components/uptimerobot/entity.py @@ -1,4 +1,5 @@ """Base UptimeRobot entity.""" + from __future__ import annotations from pyuptimerobot import UptimeRobotMonitor diff --git a/homeassistant/components/uptimerobot/sensor.py b/homeassistant/components/uptimerobot/sensor.py index c1e4db0fef7..c5ff8abf5d9 100644 --- a/homeassistant/components/uptimerobot/sensor.py +++ b/homeassistant/components/uptimerobot/sensor.py @@ -1,4 +1,5 @@ """UptimeRobot sensor platform.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/uptimerobot/switch.py b/homeassistant/components/uptimerobot/switch.py index e76418e3300..aa7d07e10fd 100644 --- a/homeassistant/components/uptimerobot/switch.py +++ b/homeassistant/components/uptimerobot/switch.py @@ -1,4 +1,5 @@ """UptimeRobot switch platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/usb/__init__.py b/homeassistant/components/usb/__init__.py index 916e9b1ea32..f91603ce158 100644 --- a/homeassistant/components/usb/__init__.py +++ b/homeassistant/components/usb/__init__.py @@ -1,4 +1,5 @@ """The USB Discovery integration.""" + from __future__ import annotations from collections.abc import Coroutine diff --git a/homeassistant/components/usb/models.py b/homeassistant/components/usb/models.py index bdc8bc71ced..efc5b11c26e 100644 --- a/homeassistant/components/usb/models.py +++ b/homeassistant/components/usb/models.py @@ -1,4 +1,5 @@ """Models helper class for the usb integration.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/usb/utils.py b/homeassistant/components/usb/utils.py index d6bd96882b2..d1d6fb17f3c 100644 --- a/homeassistant/components/usb/utils.py +++ b/homeassistant/components/usb/utils.py @@ -1,4 +1,5 @@ """The USB Discovery integration.""" + from __future__ import annotations from serial.tools.list_ports_common import ListPortInfo diff --git a/homeassistant/components/usgs_earthquakes_feed/geo_location.py b/homeassistant/components/usgs_earthquakes_feed/geo_location.py index 1c6c1b04231..c8ee88a84ed 100644 --- a/homeassistant/components/usgs_earthquakes_feed/geo_location.py +++ b/homeassistant/components/usgs_earthquakes_feed/geo_location.py @@ -1,4 +1,5 @@ """Support for U.S. Geological Survey Earthquake Hazards Program Feeds.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index a3b489dc55c..0e3e864461d 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -1,4 +1,5 @@ """Support for tracking consumption over given periods of time.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/utility_meter/config_flow.py b/homeassistant/components/utility_meter/config_flow.py index 0ca9ee12f58..e8acca88cbe 100644 --- a/homeassistant/components/utility_meter/config_flow.py +++ b/homeassistant/components/utility_meter/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Utility Meter integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/utility_meter/select.py b/homeassistant/components/utility_meter/select.py index 86433ca77f8..461fee3ba9f 100644 --- a/homeassistant/components/utility_meter/select.py +++ b/homeassistant/components/utility_meter/select.py @@ -1,4 +1,5 @@ """Support for tariff selection.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index d9c5fabd7c8..097707b04d0 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -1,4 +1,5 @@ """Utility meter from sensors providing raw data.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index cecec49b36b..307db17c2b8 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -1,4 +1,5 @@ """Support for Ubiquiti's UVC cameras.""" + from __future__ import annotations from datetime import datetime diff --git a/homeassistant/components/v2c/__init__.py b/homeassistant/components/v2c/__init__.py index ea82a9b64fc..75d306b392a 100644 --- a/homeassistant/components/v2c/__init__.py +++ b/homeassistant/components/v2c/__init__.py @@ -1,4 +1,5 @@ """The V2C integration.""" + from __future__ import annotations from pytrydan import Trydan diff --git a/homeassistant/components/v2c/binary_sensor.py b/homeassistant/components/v2c/binary_sensor.py index b30c632174a..e0d5306d205 100644 --- a/homeassistant/components/v2c/binary_sensor.py +++ b/homeassistant/components/v2c/binary_sensor.py @@ -1,4 +1,5 @@ """Support for V2C binary sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/v2c/config_flow.py b/homeassistant/components/v2c/config_flow.py index 4bb36f3191a..4d798795cbe 100644 --- a/homeassistant/components/v2c/config_flow.py +++ b/homeassistant/components/v2c/config_flow.py @@ -1,4 +1,5 @@ """Config flow for V2C integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/v2c/coordinator.py b/homeassistant/components/v2c/coordinator.py index f61d58b844d..b121c84563c 100644 --- a/homeassistant/components/v2c/coordinator.py +++ b/homeassistant/components/v2c/coordinator.py @@ -1,4 +1,5 @@ """The v2c component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/v2c/entity.py b/homeassistant/components/v2c/entity.py index ee3c94d8d0c..e71c4d5d7c5 100644 --- a/homeassistant/components/v2c/entity.py +++ b/homeassistant/components/v2c/entity.py @@ -1,4 +1,5 @@ """Support for V2C EVSE.""" + from __future__ import annotations from pytrydan import TrydanData diff --git a/homeassistant/components/v2c/number.py b/homeassistant/components/v2c/number.py index dd20b0de787..e87be9f716a 100644 --- a/homeassistant/components/v2c/number.py +++ b/homeassistant/components/v2c/number.py @@ -1,4 +1,5 @@ """Number platform for V2C settings.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/v2c/sensor.py b/homeassistant/components/v2c/sensor.py index 29697bd7fe6..b8d3919b1cb 100644 --- a/homeassistant/components/v2c/sensor.py +++ b/homeassistant/components/v2c/sensor.py @@ -1,4 +1,5 @@ """Support for V2C EVSE sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/v2c/switch.py b/homeassistant/components/v2c/switch.py index 4e02f810a7a..1ff86b1677b 100644 --- a/homeassistant/components/v2c/switch.py +++ b/homeassistant/components/v2c/switch.py @@ -1,4 +1,5 @@ """Switch platform for V2C EVSE.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 1bd9719c51c..cdac8c553ff 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -1,4 +1,5 @@ """Support for vacuum cleaner robots (botvacs).""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vacuum/device_action.py b/homeassistant/components/vacuum/device_action.py index 0f212235673..82c00a57b5e 100644 --- a/homeassistant/components/vacuum/device_action.py +++ b/homeassistant/components/vacuum/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Vacuum.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/vacuum/device_condition.py b/homeassistant/components/vacuum/device_condition.py index 8b7227e788e..f528b0918a1 100644 --- a/homeassistant/components/vacuum/device_condition.py +++ b/homeassistant/components/vacuum/device_condition.py @@ -1,4 +1,5 @@ """Provide the device automations for Vacuum.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/vacuum/device_trigger.py b/homeassistant/components/vacuum/device_trigger.py index 95c1938ccfa..45b0696f871 100644 --- a/homeassistant/components/vacuum/device_trigger.py +++ b/homeassistant/components/vacuum/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for Vacuum.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/vacuum/reproduce_state.py b/homeassistant/components/vacuum/reproduce_state.py index 4d0d6b4b12c..762cd6f2e90 100644 --- a/homeassistant/components/vacuum/reproduce_state.py +++ b/homeassistant/components/vacuum/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Vacuum state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/vacuum/significant_change.py b/homeassistant/components/vacuum/significant_change.py index 5699050c7cb..857e6e822c5 100644 --- a/homeassistant/components/vacuum/significant_change.py +++ b/homeassistant/components/vacuum/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Vacuum state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vallox/__init__.py b/homeassistant/components/vallox/__init__.py index ec2904385df..7c9234d35c2 100644 --- a/homeassistant/components/vallox/__init__.py +++ b/homeassistant/components/vallox/__init__.py @@ -1,4 +1,5 @@ """Support for Vallox ventilation units.""" + from __future__ import annotations import ipaddress diff --git a/homeassistant/components/vallox/binary_sensor.py b/homeassistant/components/vallox/binary_sensor.py index 4d1778a81e1..9ba81dcd4d1 100644 --- a/homeassistant/components/vallox/binary_sensor.py +++ b/homeassistant/components/vallox/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Vallox ventilation unit binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/vallox/config_flow.py b/homeassistant/components/vallox/config_flow.py index d3fbaf8b7f2..4812097d4e0 100644 --- a/homeassistant/components/vallox/config_flow.py +++ b/homeassistant/components/vallox/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Vallox integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/vallox/date.py b/homeassistant/components/vallox/date.py index b9ea6d66015..0cdb7cdbb3f 100644 --- a/homeassistant/components/vallox/date.py +++ b/homeassistant/components/vallox/date.py @@ -1,4 +1,5 @@ """Support for Vallox date platform.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/vallox/fan.py b/homeassistant/components/vallox/fan.py index 24448e6f53b..46f6fb022e4 100644 --- a/homeassistant/components/vallox/fan.py +++ b/homeassistant/components/vallox/fan.py @@ -1,4 +1,5 @@ """Support for the Vallox ventilation unit fan.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vallox/number.py b/homeassistant/components/vallox/number.py index 8f198ea8947..c89cdbf17b9 100644 --- a/homeassistant/components/vallox/number.py +++ b/homeassistant/components/vallox/number.py @@ -1,4 +1,5 @@ """Support for Vallox ventilation unit numbers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/vallox/sensor.py b/homeassistant/components/vallox/sensor.py index decfec9d52c..8fca6f3b05d 100644 --- a/homeassistant/components/vallox/sensor.py +++ b/homeassistant/components/vallox/sensor.py @@ -1,4 +1,5 @@ """Support for Vallox ventilation unit sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/vallox/switch.py b/homeassistant/components/vallox/switch.py index c2358e1022f..8adb8274a64 100644 --- a/homeassistant/components/vallox/switch.py +++ b/homeassistant/components/vallox/switch.py @@ -1,4 +1,5 @@ """Support for Vallox ventilation unit switches.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/valve/__init__.py b/homeassistant/components/valve/__init__.py index c04e25355ff..f2d2fcf4760 100644 --- a/homeassistant/components/valve/__init__.py +++ b/homeassistant/components/valve/__init__.py @@ -1,4 +1,5 @@ """Support for Valve devices.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index 6a083232079..a065d6e1f6d 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -1,4 +1,5 @@ """Support for Västtrafik public transport.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 609823b1310..931888d0f99 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -1,4 +1,5 @@ """Support for Velbus devices.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 25591cc1cb0..5f363c1a035 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Velbus Binary Sensors.""" + from velbusaio.channels import Button as VelbusButton from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/velbus/button.py b/homeassistant/components/velbus/button.py index 2a0392c48cb..bd5b81d67a0 100644 --- a/homeassistant/components/velbus/button.py +++ b/homeassistant/components/velbus/button.py @@ -1,4 +1,5 @@ """Support for Velbus Buttons.""" + from __future__ import annotations from velbusaio.channels import ( diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 9afbfc683a8..34a565c2b37 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -1,4 +1,5 @@ """Support for Velbus thermostat.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 306e69c24ed..0b47dfe6498 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -1,4 +1,5 @@ """Config flow for the Velbus platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/velbus/const.py b/homeassistant/components/velbus/const.py index a3949646598..2d9f6e98a4c 100644 --- a/homeassistant/components/velbus/const.py +++ b/homeassistant/components/velbus/const.py @@ -1,4 +1,5 @@ """Const for Velbus.""" + from typing import Final from homeassistant.components.climate import ( diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 7c6f15a808d..f37de104659 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -1,4 +1,5 @@ """Support for Velbus covers.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/velbus/diagnostics.py b/homeassistant/components/velbus/diagnostics.py index 5b991fa35fb..f7e29e2f57e 100644 --- a/homeassistant/components/velbus/diagnostics.py +++ b/homeassistant/components/velbus/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Velbus.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/velbus/entity.py b/homeassistant/components/velbus/entity.py index 1a99f796eb2..202666e6123 100644 --- a/homeassistant/components/velbus/entity.py +++ b/homeassistant/components/velbus/entity.py @@ -1,4 +1,5 @@ """Support for Velbus devices.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 1806c2905e9..dd740e7e850 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -1,4 +1,5 @@ """Support for Velbus light.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/velbus/select.py b/homeassistant/components/velbus/select.py index 6e2b4d1a746..7eecb85fc47 100644 --- a/homeassistant/components/velbus/select.py +++ b/homeassistant/components/velbus/select.py @@ -1,4 +1,5 @@ """Support for Velbus select.""" + from velbusaio.channels import SelectedProgram from homeassistant.components.select import SelectEntity diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 8e1f8bba74a..b765eebcddc 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,4 +1,5 @@ """Support for Velbus sensors.""" + from __future__ import annotations from velbusaio.channels import ButtonCounter, LightSensor, SensorNumber, Temperature diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index db7c165840e..ebb281753bf 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -1,4 +1,5 @@ """Support for Velbus switches.""" + from typing import Any from velbusaio.channels import Relay as VelbusRelay diff --git a/homeassistant/components/velux/__init__.py b/homeassistant/components/velux/__init__.py index 4c84eb687ad..4b89fc66a84 100644 --- a/homeassistant/components/velux/__init__.py +++ b/homeassistant/components/velux/__init__.py @@ -1,4 +1,5 @@ """Support for VELUX KLF 200 devices.""" + from pyvlx import Node, PyVLX, PyVLXException import voluptuous as vol diff --git a/homeassistant/components/velux/config_flow.py b/homeassistant/components/velux/config_flow.py index 882a6673d2e..da6502b86da 100644 --- a/homeassistant/components/velux/config_flow.py +++ b/homeassistant/components/velux/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Velux integration.""" + from typing import Any from pyvlx import PyVLX, PyVLXException diff --git a/homeassistant/components/velux/const.py b/homeassistant/components/velux/const.py index 9a686adf920..49a762e87ca 100644 --- a/homeassistant/components/velux/const.py +++ b/homeassistant/components/velux/const.py @@ -1,4 +1,5 @@ """Constants for the Velux integration.""" + from logging import getLogger from homeassistant.const import Platform diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 2162e63096a..6f5f453911f 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -1,4 +1,5 @@ """Support for Velux covers.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/velux/light.py b/homeassistant/components/velux/light.py index dae38f3d9bf..bbe9822648e 100644 --- a/homeassistant/components/velux/light.py +++ b/homeassistant/components/velux/light.py @@ -1,4 +1,5 @@ """Support for Velux lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/velux/scene.py b/homeassistant/components/velux/scene.py index 956663c23f1..30858b25002 100644 --- a/homeassistant/components/velux/scene.py +++ b/homeassistant/components/velux/scene.py @@ -1,4 +1,5 @@ """Support for VELUX scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/venstar/__init__.py b/homeassistant/components/venstar/__init__.py index 78cb20b33cc..13368a60350 100644 --- a/homeassistant/components/venstar/__init__.py +++ b/homeassistant/components/venstar/__init__.py @@ -1,4 +1,5 @@ """The venstar component.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/venstar/binary_sensor.py b/homeassistant/components/venstar/binary_sensor.py index a5e15b04917..38bdc208d15 100644 --- a/homeassistant/components/venstar/binary_sensor.py +++ b/homeassistant/components/venstar/binary_sensor.py @@ -1,4 +1,5 @@ """Alarm sensors for the Venstar Thermostat.""" + from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index a9ee56c4dbb..e0aacadffa7 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -1,4 +1,5 @@ """Support for Venstar WiFi Thermostats.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/venstar/config_flow.py b/homeassistant/components/venstar/config_flow.py index a88c048a611..5a193568c87 100644 --- a/homeassistant/components/venstar/config_flow.py +++ b/homeassistant/components/venstar/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Venstar integration.""" + from typing import Any from venstarcolortouch import VenstarColorTouch diff --git a/homeassistant/components/venstar/sensor.py b/homeassistant/components/venstar/sensor.py index 1e31fb9407b..8b0dbc7a7e6 100644 --- a/homeassistant/components/venstar/sensor.py +++ b/homeassistant/components/venstar/sensor.py @@ -1,4 +1,5 @@ """Representation of Venstar sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index a63e63d74c0..3111e878820 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -1,4 +1,5 @@ """Support for Vera devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 82c7d187b88..d90f6a78858 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Vera binary sensors.""" + from __future__ import annotations import pyvera as veraApi diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 93d0fbf2aee..79a6c2566e0 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -1,4 +1,5 @@ """Support for Vera thermostats.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vera/common.py b/homeassistant/components/vera/common.py index 658ed7904f4..4309a0d43f3 100644 --- a/homeassistant/components/vera/common.py +++ b/homeassistant/components/vera/common.py @@ -1,4 +1,5 @@ """Common vera code.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 5106b50bf67..7c6a4376cd8 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Vera.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 25b345b7e31..542680925f2 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -1,4 +1,5 @@ """Support for Vera cover - curtains, rollershutters etc.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index c76cd76ad19..86e5dfa6a91 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -1,4 +1,5 @@ """Support for Vera lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index 8994076ca31..01509aa8388 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -1,4 +1,5 @@ """Support for Vera locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index daa3a6fc530..22061f98929 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,4 +1,5 @@ """Support for Vera scenes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index 2cee8f309aa..97e6d6d6314 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -1,4 +1,5 @@ """Support for Vera sensors.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index 011f777b1b2..3e594685d6b 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -1,4 +1,5 @@ """Support for Vera switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 7d2ea7b7d6d..9e5f0ca2703 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -1,4 +1,5 @@ """Support for Verisure devices.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 26e74cceb9e..fc7e7551145 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Verisure alarm control panels.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index 19a60602540..542ee3485ce 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Verisure binary sensors.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/verisure/camera.py b/homeassistant/components/verisure/camera.py index e0505328245..72f5ab93c70 100644 --- a/homeassistant/components/verisure/camera.py +++ b/homeassistant/components/verisure/camera.py @@ -1,4 +1,5 @@ """Support for Verisure cameras.""" + from __future__ import annotations import errno diff --git a/homeassistant/components/verisure/config_flow.py b/homeassistant/components/verisure/config_flow.py index a7ba3bc6822..ccf74cd6791 100644 --- a/homeassistant/components/verisure/config_flow.py +++ b/homeassistant/components/verisure/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Verisure integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/verisure/const.py b/homeassistant/components/verisure/const.py index ac30c58fde5..5b1aa1a0740 100644 --- a/homeassistant/components/verisure/const.py +++ b/homeassistant/components/verisure/const.py @@ -1,4 +1,5 @@ """Constants for the Verisure integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index f31d36aa2da..930d862257b 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Verisure integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/verisure/diagnostics.py b/homeassistant/components/verisure/diagnostics.py index 8dbffe6eee3..a14e6e00b98 100644 --- a/homeassistant/components/verisure/diagnostics.py +++ b/homeassistant/components/verisure/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Verisure.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 8e57c9695c0..227356a2525 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -1,4 +1,5 @@ """Support for Verisure locks.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/verisure/sensor.py b/homeassistant/components/verisure/sensor.py index 51947484dca..4f6e6b3d3c5 100644 --- a/homeassistant/components/verisure/sensor.py +++ b/homeassistant/components/verisure/sensor.py @@ -1,4 +1,5 @@ """Support for Verisure sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 96992cadb75..b7dd06ac3a5 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -1,4 +1,5 @@ """Support for Verisure Smartplugs.""" + from __future__ import annotations from time import monotonic diff --git a/homeassistant/components/versasense/sensor.py b/homeassistant/components/versasense/sensor.py index 349ed429b33..59d092ccdc1 100644 --- a/homeassistant/components/versasense/sensor.py +++ b/homeassistant/components/versasense/sensor.py @@ -1,4 +1,5 @@ """Support for VersaSense sensor peripheral.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/versasense/switch.py b/homeassistant/components/versasense/switch.py index b57013b539d..195045882ff 100644 --- a/homeassistant/components/versasense/switch.py +++ b/homeassistant/components/versasense/switch.py @@ -1,4 +1,5 @@ """Support for VersaSense actuator peripheral.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/version/__init__.py b/homeassistant/components/version/__init__.py index f05c2147449..4112cc51e46 100644 --- a/homeassistant/components/version/__init__.py +++ b/homeassistant/components/version/__init__.py @@ -1,4 +1,5 @@ """The Version integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/version/binary_sensor.py b/homeassistant/components/version/binary_sensor.py index cdfcb45fb5c..ff4f51e409f 100644 --- a/homeassistant/components/version/binary_sensor.py +++ b/homeassistant/components/version/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform for Version.""" + from __future__ import annotations from awesomeversion import AwesomeVersion diff --git a/homeassistant/components/version/config_flow.py b/homeassistant/components/version/config_flow.py index d08d741a241..17cd07aac6f 100644 --- a/homeassistant/components/version/config_flow.py +++ b/homeassistant/components/version/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Version integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 069da3dca64..56a1c88c5f2 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -1,4 +1,5 @@ """Constants for the Version integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/version/coordinator.py b/homeassistant/components/version/coordinator.py index a41816c0824..05adf07642b 100644 --- a/homeassistant/components/version/coordinator.py +++ b/homeassistant/components/version/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for Version entities.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/version/diagnostics.py b/homeassistant/components/version/diagnostics.py index 20b84fabce7..194027d6ef4 100644 --- a/homeassistant/components/version/diagnostics.py +++ b/homeassistant/components/version/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Version.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 82e49155603..e1e930b50a6 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -1,4 +1,5 @@ """Sensor that can display the current Home Assistant versions.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vesync/config_flow.py b/homeassistant/components/vesync/config_flow.py index 5e71c434449..15f9f548e35 100644 --- a/homeassistant/components/vesync/config_flow.py +++ b/homeassistant/components/vesync/config_flow.py @@ -1,4 +1,5 @@ """Config flow utilities.""" + from collections import OrderedDict from pyvesync import VeSync diff --git a/homeassistant/components/vesync/diagnostics.py b/homeassistant/components/vesync/diagnostics.py index 8043e93b9e4..b56c8fc5db6 100644 --- a/homeassistant/components/vesync/diagnostics.py +++ b/homeassistant/components/vesync/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for VeSync.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index f0d4d02a9a3..1d8ea6463bf 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -1,4 +1,5 @@ """Support for VeSync fans.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/vesync/sensor.py b/homeassistant/components/vesync/sensor.py index 8cde8ea1036..87a87f049c2 100644 --- a/homeassistant/components/vesync/sensor.py +++ b/homeassistant/components/vesync/sensor.py @@ -1,4 +1,5 @@ """Support for voltage, power & energy sensors for VeSync outlets.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/viaggiatreno/sensor.py b/homeassistant/components/viaggiatreno/sensor.py index ce439b9e628..3738b0f956a 100644 --- a/homeassistant/components/viaggiatreno/sensor.py +++ b/homeassistant/components/viaggiatreno/sensor.py @@ -1,4 +1,5 @@ """Support for the Italian train system using ViaggiaTreno API.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 74ebffa53cd..0c87cd6f4fe 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -1,4 +1,5 @@ """The ViCare integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vicare/binary_sensor.py b/homeassistant/components/vicare/binary_sensor.py index a67a7096e0b..2df8a2f06d3 100644 --- a/homeassistant/components/vicare/binary_sensor.py +++ b/homeassistant/components/vicare/binary_sensor.py @@ -1,4 +1,5 @@ """Viessmann ViCare sensor device.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/vicare/button.py b/homeassistant/components/vicare/button.py index f0eaac25724..c927055dadd 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -1,4 +1,5 @@ """Viessmann ViCare button device.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 10cc1a15c9e..f798f3646fc 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -1,4 +1,5 @@ """Viessmann ViCare climate device.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/vicare/config_flow.py b/homeassistant/components/vicare/config_flow.py index 6d9f72a426f..67ce4f2c186 100644 --- a/homeassistant/components/vicare/config_flow.py +++ b/homeassistant/components/vicare/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ViCare integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vicare/diagnostics.py b/homeassistant/components/vicare/diagnostics.py index 23a3c8640c5..b8cec966199 100644 --- a/homeassistant/components/vicare/diagnostics.py +++ b/homeassistant/components/vicare/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for ViCare.""" + from __future__ import annotations import json diff --git a/homeassistant/components/vicare/entity.py b/homeassistant/components/vicare/entity.py index af35c7bf8dd..1bb2993cd3a 100644 --- a/homeassistant/components/vicare/entity.py +++ b/homeassistant/components/vicare/entity.py @@ -1,4 +1,5 @@ """Entities for the ViCare integration.""" + from PyViCare.PyViCareDevice import Device as PyViCareDevice from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig diff --git a/homeassistant/components/vicare/number.py b/homeassistant/components/vicare/number.py index a0ae8f3c3e7..f92241ceace 100644 --- a/homeassistant/components/vicare/number.py +++ b/homeassistant/components/vicare/number.py @@ -1,4 +1,5 @@ """Number for ViCare.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index fe0171bd476..e37476286c1 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -1,4 +1,5 @@ """Viessmann ViCare sensor device.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/vicare/types.py b/homeassistant/components/vicare/types.py index 83b15a6bcf7..2bed638bfb9 100644 --- a/homeassistant/components/vicare/types.py +++ b/homeassistant/components/vicare/types.py @@ -1,4 +1,5 @@ """Types for the ViCare integration.""" + from collections.abc import Callable from dataclasses import dataclass import enum diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index 9a8fb7eb092..223217f4e13 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -1,4 +1,5 @@ """Viessmann ViCare water_heater device.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/vilfo/__init__.py b/homeassistant/components/vilfo/__init__.py index 82fe4c7bb70..fe00fa494b5 100644 --- a/homeassistant/components/vilfo/__init__.py +++ b/homeassistant/components/vilfo/__init__.py @@ -1,4 +1,5 @@ """The Vilfo Router integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/vilfo/const.py b/homeassistant/components/vilfo/const.py index e562add4e0f..e129437df7e 100644 --- a/homeassistant/components/vilfo/const.py +++ b/homeassistant/components/vilfo/const.py @@ -1,4 +1,5 @@ """Constants for the Vilfo Router integration.""" + from __future__ import annotations DOMAIN = "vilfo" diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index 5f62b62a976..34c862e1a8f 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -1,4 +1,5 @@ """Support for Vilfo Router sensors.""" + from dataclasses import dataclass from homeassistant.components.sensor import ( diff --git a/homeassistant/components/vivotek/camera.py b/homeassistant/components/vivotek/camera.py index a61897f996e..8719d55ec29 100644 --- a/homeassistant/components/vivotek/camera.py +++ b/homeassistant/components/vivotek/camera.py @@ -1,4 +1,5 @@ """Support for Vivotek IP Cameras.""" + from __future__ import annotations from libpyvivotek import VivotekCamera diff --git a/homeassistant/components/vizio/__init__.py b/homeassistant/components/vizio/__init__.py index 2e468087725..b8df8fb4529 100644 --- a/homeassistant/components/vizio/__init__.py +++ b/homeassistant/components/vizio/__init__.py @@ -1,4 +1,5 @@ """The vizio component.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index b0936b0e81b..fb5f74f4e09 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Vizio.""" + from __future__ import annotations import copy diff --git a/homeassistant/components/vizio/const.py b/homeassistant/components/vizio/const.py index 954d819b1ac..12de3af1cb0 100644 --- a/homeassistant/components/vizio/const.py +++ b/homeassistant/components/vizio/const.py @@ -1,4 +1,5 @@ """Constants used by vizio component.""" + from pyvizio.const import ( DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV, diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index ba9cd509499..c19c091bb3d 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -1,4 +1,5 @@ """Vizio SmartCast Device support.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index 9f28a60427c..53831fb8db0 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -1,4 +1,5 @@ """Provide functionality to interact with vlc devices on the network.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/vlc_telnet/__init__.py b/homeassistant/components/vlc_telnet/__init__.py index a8a0c16be8e..659a5d29d1f 100644 --- a/homeassistant/components/vlc_telnet/__init__.py +++ b/homeassistant/components/vlc_telnet/__init__.py @@ -1,4 +1,5 @@ """The VLC media player Telnet integration.""" + from aiovlc.client import Client from aiovlc.exceptions import AuthError, ConnectError diff --git a/homeassistant/components/vlc_telnet/config_flow.py b/homeassistant/components/vlc_telnet/config_flow.py index 48f8cc3f077..67325686282 100644 --- a/homeassistant/components/vlc_telnet/config_flow.py +++ b/homeassistant/components/vlc_telnet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for VLC media player Telnet integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index b84676776f5..fa021352d81 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -1,4 +1,5 @@ """Provide functionality to interact with the vlc telnet interface.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine diff --git a/homeassistant/components/vodafone_station/button.py b/homeassistant/components/vodafone_station/button.py index 3840af3d593..3bd012734b6 100644 --- a/homeassistant/components/vodafone_station/button.py +++ b/homeassistant/components/vodafone_station/button.py @@ -1,4 +1,5 @@ """Vodafone Station buttons.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/vodafone_station/config_flow.py b/homeassistant/components/vodafone_station/config_flow.py index b8b08463999..ed7f63b6c39 100644 --- a/homeassistant/components/vodafone_station/config_flow.py +++ b/homeassistant/components/vodafone_station/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Vodafone Station integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/vodafone_station/coordinator.py b/homeassistant/components/vodafone_station/coordinator.py index ff51f009f3c..cf096a93d50 100644 --- a/homeassistant/components/vodafone_station/coordinator.py +++ b/homeassistant/components/vodafone_station/coordinator.py @@ -1,4 +1,5 @@ """Support for Vodafone Station.""" + from dataclasses import dataclass from datetime import datetime, timedelta from typing import Any diff --git a/homeassistant/components/vodafone_station/device_tracker.py b/homeassistant/components/vodafone_station/device_tracker.py index ebe985cb744..85ad834cd23 100644 --- a/homeassistant/components/vodafone_station/device_tracker.py +++ b/homeassistant/components/vodafone_station/device_tracker.py @@ -1,4 +1,5 @@ """Support for Vodafone Station routers.""" + from __future__ import annotations from aiovodafone import VodafoneStationDevice diff --git a/homeassistant/components/vodafone_station/sensor.py b/homeassistant/components/vodafone_station/sensor.py index c5fff05a164..5bc7465a902 100644 --- a/homeassistant/components/vodafone_station/sensor.py +++ b/homeassistant/components/vodafone_station/sensor.py @@ -1,4 +1,5 @@ """Vodafone Station sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/voip/__init__.py b/homeassistant/components/voip/__init__.py index f29705cf41b..9ab6a8bf0e8 100644 --- a/homeassistant/components/voip/__init__.py +++ b/homeassistant/components/voip/__init__.py @@ -1,4 +1,5 @@ """The Voice over IP integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/voip/config_flow.py b/homeassistant/components/voip/config_flow.py index 44709d2b674..821c7f29a1e 100644 --- a/homeassistant/components/voip/config_flow.py +++ b/homeassistant/components/voip/config_flow.py @@ -1,4 +1,5 @@ """Config flow for VoIP integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/voip/devices.py b/homeassistant/components/voip/devices.py index 5da7a97ec24..aed8e6de740 100644 --- a/homeassistant/components/voip/devices.py +++ b/homeassistant/components/voip/devices.py @@ -1,4 +1,5 @@ """Class to manage devices.""" + from __future__ import annotations from collections.abc import Callable, Iterator diff --git a/homeassistant/components/voip/voip.py b/homeassistant/components/voip/voip.py index a41f0965e8f..4d97720934c 100644 --- a/homeassistant/components/voip/voip.py +++ b/homeassistant/components/voip/voip.py @@ -1,4 +1,5 @@ """Voice over IP (VoIP) implementation.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/volkszaehler/sensor.py b/homeassistant/components/volkszaehler/sensor.py index 83b242d0bc0..ce5691b1193 100644 --- a/homeassistant/components/volkszaehler/sensor.py +++ b/homeassistant/components/volkszaehler/sensor.py @@ -1,4 +1,5 @@ """Support for consuming values for the Volkszaehler API.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/volumio/config_flow.py b/homeassistant/components/volumio/config_flow.py index 9a19130113d..e86fcd4417d 100644 --- a/homeassistant/components/volumio/config_flow.py +++ b/homeassistant/components/volumio/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Volumio integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/volumio/media_player.py b/homeassistant/components/volumio/media_player.py index a11ea62e355..5ba67d7974f 100644 --- a/homeassistant/components/volumio/media_player.py +++ b/homeassistant/components/volumio/media_player.py @@ -2,6 +2,7 @@ Volumio rest API: https://volumio.github.io/docs/API/REST_API.html """ + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/volvooncall/binary_sensor.py b/homeassistant/components/volvooncall/binary_sensor.py index 77e1b7183db..390779407fa 100644 --- a/homeassistant/components/volvooncall/binary_sensor.py +++ b/homeassistant/components/volvooncall/binary_sensor.py @@ -1,4 +1,5 @@ """Support for VOC.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/volvooncall/config_flow.py b/homeassistant/components/volvooncall/config_flow.py index f9188ac79c1..1cb434e49bc 100644 --- a/homeassistant/components/volvooncall/config_flow.py +++ b/homeassistant/components/volvooncall/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Volvo On Call integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/volvooncall/device_tracker.py b/homeassistant/components/volvooncall/device_tracker.py index 0cd61a336b7..039679fa413 100644 --- a/homeassistant/components/volvooncall/device_tracker.py +++ b/homeassistant/components/volvooncall/device_tracker.py @@ -1,4 +1,5 @@ """Support for tracking a Volvo.""" + from __future__ import annotations from volvooncall.dashboard import Instrument diff --git a/homeassistant/components/volvooncall/errors.py b/homeassistant/components/volvooncall/errors.py index a3af1125b48..3736c5b9290 100644 --- a/homeassistant/components/volvooncall/errors.py +++ b/homeassistant/components/volvooncall/errors.py @@ -1,4 +1,5 @@ """Exceptions specific to volvooncall.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/volvooncall/sensor.py b/homeassistant/components/volvooncall/sensor.py index 0f4269732e3..d8acaf34d67 100644 --- a/homeassistant/components/volvooncall/sensor.py +++ b/homeassistant/components/volvooncall/sensor.py @@ -1,4 +1,5 @@ """Support for Volvo On Call sensors.""" + from __future__ import annotations from volvooncall.dashboard import Instrument diff --git a/homeassistant/components/volvooncall/switch.py b/homeassistant/components/volvooncall/switch.py index 89300cd54e1..571aa88757a 100644 --- a/homeassistant/components/volvooncall/switch.py +++ b/homeassistant/components/volvooncall/switch.py @@ -1,4 +1,5 @@ """Support for Volvo heater.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/vulcan/calendar.py b/homeassistant/components/vulcan/calendar.py index 073ac88fbda..a0ccaefdb15 100644 --- a/homeassistant/components/vulcan/calendar.py +++ b/homeassistant/components/vulcan/calendar.py @@ -1,4 +1,5 @@ """Support for Vulcan Calendar platform.""" + from __future__ import annotations from datetime import date, datetime, timedelta diff --git a/homeassistant/components/vulcan/config_flow.py b/homeassistant/components/vulcan/config_flow.py index ae4226162e5..b761527e660 100644 --- a/homeassistant/components/vulcan/config_flow.py +++ b/homeassistant/components/vulcan/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Vulcan.""" + from collections.abc import Mapping import logging from typing import Any diff --git a/homeassistant/components/vultr/__init__.py b/homeassistant/components/vultr/__init__.py index 7bf3b4b07f5..36f43cf0ac0 100644 --- a/homeassistant/components/vultr/__init__.py +++ b/homeassistant/components/vultr/__init__.py @@ -1,4 +1,5 @@ """Support for Vultr.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/vultr/binary_sensor.py b/homeassistant/components/vultr/binary_sensor.py index 1d877216e93..5c0db81e843 100644 --- a/homeassistant/components/vultr/binary_sensor.py +++ b/homeassistant/components/vultr/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the state of Vultr subscriptions (VPS).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/vultr/sensor.py b/homeassistant/components/vultr/sensor.py index a77fab62bd4..816a55736be 100644 --- a/homeassistant/components/vultr/sensor.py +++ b/homeassistant/components/vultr/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the state of Vultr Subscriptions.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/vultr/switch.py b/homeassistant/components/vultr/switch.py index a12cfab3e83..6758748b9f3 100644 --- a/homeassistant/components/vultr/switch.py +++ b/homeassistant/components/vultr/switch.py @@ -1,4 +1,5 @@ """Support for interacting with Vultr subscriptions.""" + from __future__ import annotations import logging From c773d57d392dc1062948a5cd935b419587896575 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:35:45 +0100 Subject: [PATCH 0555/1691] Add empty line after module docstring [w-z] (#112706) --- homeassistant/components/w800rf32/binary_sensor.py | 1 + homeassistant/components/wake_on_lan/__init__.py | 1 + homeassistant/components/wake_on_lan/switch.py | 1 + homeassistant/components/wake_word/__init__.py | 1 + homeassistant/components/wake_word/models.py | 1 + homeassistant/components/wallbox/__init__.py | 1 + homeassistant/components/wallbox/config_flow.py | 1 + homeassistant/components/wallbox/const.py | 1 + homeassistant/components/wallbox/coordinator.py | 1 + homeassistant/components/wallbox/entity.py | 1 + homeassistant/components/wallbox/lock.py | 1 + homeassistant/components/wallbox/number.py | 1 + homeassistant/components/wallbox/sensor.py | 1 + homeassistant/components/wallbox/switch.py | 1 + homeassistant/components/waqi/__init__.py | 1 + homeassistant/components/waqi/config_flow.py | 1 + homeassistant/components/waqi/coordinator.py | 1 + homeassistant/components/waqi/sensor.py | 1 + homeassistant/components/water_heater/__init__.py | 1 + homeassistant/components/water_heater/device_action.py | 1 + homeassistant/components/water_heater/reproduce_state.py | 1 + homeassistant/components/water_heater/significant_change.py | 1 + homeassistant/components/waterfurnace/__init__.py | 1 + homeassistant/components/waterfurnace/sensor.py | 1 + homeassistant/components/watttime/__init__.py | 1 + homeassistant/components/watttime/config_flow.py | 1 + homeassistant/components/watttime/diagnostics.py | 1 + homeassistant/components/watttime/sensor.py | 1 + homeassistant/components/waze_travel_time/config_flow.py | 1 + homeassistant/components/waze_travel_time/const.py | 1 + homeassistant/components/waze_travel_time/sensor.py | 1 + homeassistant/components/weather/__init__.py | 1 + homeassistant/components/weather/const.py | 1 + homeassistant/components/weather/intent.py | 1 + homeassistant/components/weather/significant_change.py | 1 + homeassistant/components/weather/websocket_api.py | 1 + homeassistant/components/weatherflow/__init__.py | 1 + homeassistant/components/weatherflow/config_flow.py | 1 + homeassistant/components/weatherflow/sensor.py | 1 + homeassistant/components/weatherflow_cloud/__init__.py | 1 + homeassistant/components/weatherflow_cloud/config_flow.py | 1 + homeassistant/components/weatherflow_cloud/coordinator.py | 1 + homeassistant/components/weatherflow_cloud/weather.py | 1 + homeassistant/components/weatherkit/__init__.py | 1 + homeassistant/components/weatherkit/config_flow.py | 1 + homeassistant/components/weatherkit/const.py | 1 + homeassistant/components/weatherkit/coordinator.py | 1 + homeassistant/components/webhook/__init__.py | 1 + homeassistant/components/webhook/trigger.py | 1 + homeassistant/components/webmin/config_flow.py | 1 + homeassistant/components/webmin/coordinator.py | 1 + homeassistant/components/webmin/sensor.py | 1 + homeassistant/components/webostv/__init__.py | 1 + homeassistant/components/webostv/config_flow.py | 1 + homeassistant/components/webostv/device_trigger.py | 1 + homeassistant/components/webostv/diagnostics.py | 1 + homeassistant/components/webostv/helpers.py | 1 + homeassistant/components/webostv/media_player.py | 1 + homeassistant/components/webostv/notify.py | 1 + homeassistant/components/webostv/trigger.py | 1 + homeassistant/components/webostv/triggers/turn_on.py | 1 + homeassistant/components/websocket_api/__init__.py | 1 + homeassistant/components/websocket_api/auth.py | 1 + homeassistant/components/websocket_api/commands.py | 1 + homeassistant/components/websocket_api/connection.py | 1 + homeassistant/components/websocket_api/const.py | 1 + homeassistant/components/websocket_api/decorators.py | 1 + homeassistant/components/websocket_api/error.py | 1 + homeassistant/components/websocket_api/http.py | 1 + homeassistant/components/websocket_api/messages.py | 1 + homeassistant/components/websocket_api/sensor.py | 1 + homeassistant/components/wemo/__init__.py | 1 + homeassistant/components/wemo/device_trigger.py | 1 + homeassistant/components/wemo/entity.py | 1 + homeassistant/components/wemo/fan.py | 1 + homeassistant/components/wemo/light.py | 1 + homeassistant/components/wemo/sensor.py | 1 + homeassistant/components/wemo/switch.py | 1 + homeassistant/components/wemo/wemo_device.py | 1 + homeassistant/components/whirlpool/__init__.py | 1 + homeassistant/components/whirlpool/climate.py | 1 + homeassistant/components/whirlpool/config_flow.py | 1 + homeassistant/components/whirlpool/sensor.py | 1 + homeassistant/components/whois/__init__.py | 1 + homeassistant/components/whois/config_flow.py | 1 + homeassistant/components/whois/const.py | 1 + homeassistant/components/whois/diagnostics.py | 1 + homeassistant/components/whois/sensor.py | 1 + homeassistant/components/wiffi/__init__.py | 1 + homeassistant/components/wiffi/binary_sensor.py | 1 + homeassistant/components/wiffi/config_flow.py | 1 + homeassistant/components/wiffi/sensor.py | 1 + homeassistant/components/wilight/config_flow.py | 1 + homeassistant/components/wilight/cover.py | 1 + homeassistant/components/wilight/fan.py | 1 + homeassistant/components/wilight/light.py | 1 + homeassistant/components/wilight/parent_device.py | 1 + homeassistant/components/wilight/support.py | 1 + homeassistant/components/wilight/switch.py | 1 + homeassistant/components/wirelesstag/binary_sensor.py | 1 + homeassistant/components/wirelesstag/sensor.py | 1 + homeassistant/components/wirelesstag/switch.py | 1 + homeassistant/components/withings/__init__.py | 1 + homeassistant/components/withings/binary_sensor.py | 1 + homeassistant/components/withings/calendar.py | 1 + homeassistant/components/withings/config_flow.py | 1 + homeassistant/components/withings/coordinator.py | 1 + homeassistant/components/withings/diagnostics.py | 1 + homeassistant/components/withings/entity.py | 1 + homeassistant/components/withings/sensor.py | 1 + homeassistant/components/wiz/__init__.py | 1 + homeassistant/components/wiz/binary_sensor.py | 1 + homeassistant/components/wiz/config_flow.py | 1 + homeassistant/components/wiz/const.py | 1 + homeassistant/components/wiz/diagnostics.py | 1 + homeassistant/components/wiz/discovery.py | 1 + homeassistant/components/wiz/entity.py | 1 + homeassistant/components/wiz/light.py | 1 + homeassistant/components/wiz/models.py | 1 + homeassistant/components/wiz/number.py | 1 + homeassistant/components/wiz/sensor.py | 1 + homeassistant/components/wiz/switch.py | 1 + homeassistant/components/wiz/utils.py | 1 + homeassistant/components/wled/__init__.py | 1 + homeassistant/components/wled/binary_sensor.py | 1 + homeassistant/components/wled/button.py | 1 + homeassistant/components/wled/config_flow.py | 1 + homeassistant/components/wled/const.py | 1 + homeassistant/components/wled/coordinator.py | 1 + homeassistant/components/wled/diagnostics.py | 1 + homeassistant/components/wled/helpers.py | 1 + homeassistant/components/wled/light.py | 1 + homeassistant/components/wled/models.py | 1 + homeassistant/components/wled/number.py | 1 + homeassistant/components/wled/select.py | 1 + homeassistant/components/wled/sensor.py | 1 + homeassistant/components/wled/switch.py | 1 + homeassistant/components/wled/update.py | 1 + homeassistant/components/wolflink/__init__.py | 1 + homeassistant/components/wolflink/sensor.py | 1 + homeassistant/components/workday/__init__.py | 1 + homeassistant/components/workday/config_flow.py | 1 + homeassistant/components/workday/const.py | 1 + homeassistant/components/worldclock/sensor.py | 1 + homeassistant/components/worldtidesinfo/sensor.py | 1 + homeassistant/components/worxlandroid/sensor.py | 1 + homeassistant/components/ws66i/__init__.py | 1 + homeassistant/components/ws66i/config_flow.py | 1 + homeassistant/components/ws66i/const.py | 1 + homeassistant/components/ws66i/coordinator.py | 1 + homeassistant/components/ws66i/media_player.py | 1 + homeassistant/components/ws66i/models.py | 1 + homeassistant/components/wsdot/sensor.py | 1 + homeassistant/components/wyoming/__init__.py | 1 + homeassistant/components/wyoming/config_flow.py | 1 + homeassistant/components/wyoming/devices.py | 1 + homeassistant/components/wyoming/error.py | 1 + homeassistant/components/wyoming/models.py | 1 + homeassistant/components/wyoming/stt.py | 1 + homeassistant/components/wyoming/tts.py | 1 + homeassistant/components/x10/light.py | 1 + homeassistant/components/xbox/__init__.py | 1 + homeassistant/components/xbox/api.py | 1 + homeassistant/components/xbox/base_sensor.py | 1 + homeassistant/components/xbox/binary_sensor.py | 1 + homeassistant/components/xbox/browse_media.py | 1 + homeassistant/components/xbox/media_player.py | 1 + homeassistant/components/xbox/media_source.py | 1 + homeassistant/components/xbox/remote.py | 1 + homeassistant/components/xbox/sensor.py | 1 + homeassistant/components/xeoma/camera.py | 1 + homeassistant/components/xiaomi/camera.py | 1 + homeassistant/components/xiaomi/device_tracker.py | 1 + homeassistant/components/xiaomi_aqara/cover.py | 1 + homeassistant/components/xiaomi_aqara/lock.py | 1 + homeassistant/components/xiaomi_aqara/sensor.py | 1 + homeassistant/components/xiaomi_ble/__init__.py | 1 + homeassistant/components/xiaomi_ble/binary_sensor.py | 1 + homeassistant/components/xiaomi_ble/config_flow.py | 1 + homeassistant/components/xiaomi_ble/const.py | 1 + homeassistant/components/xiaomi_ble/coordinator.py | 1 + homeassistant/components/xiaomi_ble/device.py | 1 + homeassistant/components/xiaomi_ble/device_trigger.py | 1 + homeassistant/components/xiaomi_ble/event.py | 1 + homeassistant/components/xiaomi_ble/sensor.py | 1 + homeassistant/components/xiaomi_miio/__init__.py | 1 + homeassistant/components/xiaomi_miio/air_quality.py | 1 + homeassistant/components/xiaomi_miio/alarm_control_panel.py | 1 + homeassistant/components/xiaomi_miio/binary_sensor.py | 1 + homeassistant/components/xiaomi_miio/button.py | 1 + homeassistant/components/xiaomi_miio/config_flow.py | 1 + homeassistant/components/xiaomi_miio/const.py | 1 + homeassistant/components/xiaomi_miio/device_tracker.py | 1 + homeassistant/components/xiaomi_miio/diagnostics.py | 1 + homeassistant/components/xiaomi_miio/fan.py | 1 + homeassistant/components/xiaomi_miio/light.py | 1 + homeassistant/components/xiaomi_miio/number.py | 1 + homeassistant/components/xiaomi_miio/remote.py | 1 + homeassistant/components/xiaomi_miio/select.py | 1 + homeassistant/components/xiaomi_miio/sensor.py | 1 + homeassistant/components/xiaomi_miio/switch.py | 1 + homeassistant/components/xiaomi_miio/vacuum.py | 1 + homeassistant/components/xiaomi_tv/media_player.py | 1 + homeassistant/components/xmpp/notify.py | 1 + homeassistant/components/xs1/climate.py | 1 + homeassistant/components/xs1/sensor.py | 1 + homeassistant/components/xs1/switch.py | 1 + homeassistant/components/yale_smart_alarm/__init__.py | 1 + homeassistant/components/yale_smart_alarm/alarm_control_panel.py | 1 + homeassistant/components/yale_smart_alarm/binary_sensor.py | 1 + homeassistant/components/yale_smart_alarm/button.py | 1 + homeassistant/components/yale_smart_alarm/config_flow.py | 1 + homeassistant/components/yale_smart_alarm/coordinator.py | 1 + homeassistant/components/yale_smart_alarm/diagnostics.py | 1 + homeassistant/components/yale_smart_alarm/lock.py | 1 + homeassistant/components/yalexs_ble/__init__.py | 1 + homeassistant/components/yalexs_ble/binary_sensor.py | 1 + homeassistant/components/yalexs_ble/config_flow.py | 1 + homeassistant/components/yalexs_ble/entity.py | 1 + homeassistant/components/yalexs_ble/lock.py | 1 + homeassistant/components/yalexs_ble/models.py | 1 + homeassistant/components/yalexs_ble/sensor.py | 1 + homeassistant/components/yalexs_ble/util.py | 1 + homeassistant/components/yamaha/media_player.py | 1 + homeassistant/components/yamaha_musiccast/__init__.py | 1 + homeassistant/components/yamaha_musiccast/config_flow.py | 1 + homeassistant/components/yamaha_musiccast/media_player.py | 1 + homeassistant/components/yamaha_musiccast/number.py | 1 + homeassistant/components/yamaha_musiccast/select.py | 1 + homeassistant/components/yamaha_musiccast/switch.py | 1 + homeassistant/components/yandex_transport/sensor.py | 1 + homeassistant/components/yardian/__init__.py | 1 + homeassistant/components/yardian/config_flow.py | 1 + homeassistant/components/yardian/switch.py | 1 + homeassistant/components/yeelight/__init__.py | 1 + homeassistant/components/yeelight/config_flow.py | 1 + homeassistant/components/yeelight/device.py | 1 + homeassistant/components/yeelight/entity.py | 1 + homeassistant/components/yeelight/light.py | 1 + homeassistant/components/yeelight/scanner.py | 1 + homeassistant/components/yeelightsunflower/light.py | 1 + homeassistant/components/yi/camera.py | 1 + homeassistant/components/yolink/__init__.py | 1 + homeassistant/components/yolink/api.py | 1 + homeassistant/components/yolink/binary_sensor.py | 1 + homeassistant/components/yolink/climate.py | 1 + homeassistant/components/yolink/config_flow.py | 1 + homeassistant/components/yolink/coordinator.py | 1 + homeassistant/components/yolink/cover.py | 1 + homeassistant/components/yolink/device_trigger.py | 1 + homeassistant/components/yolink/entity.py | 1 + homeassistant/components/yolink/light.py | 1 + homeassistant/components/yolink/lock.py | 1 + homeassistant/components/yolink/sensor.py | 1 + homeassistant/components/yolink/siren.py | 1 + homeassistant/components/yolink/switch.py | 1 + homeassistant/components/youless/__init__.py | 1 + homeassistant/components/youless/config_flow.py | 1 + homeassistant/components/youless/sensor.py | 1 + homeassistant/components/youtube/__init__.py | 1 + homeassistant/components/youtube/api.py | 1 + homeassistant/components/youtube/application_credentials.py | 1 + homeassistant/components/youtube/config_flow.py | 1 + homeassistant/components/youtube/coordinator.py | 1 + homeassistant/components/youtube/diagnostics.py | 1 + homeassistant/components/youtube/entity.py | 1 + homeassistant/components/youtube/sensor.py | 1 + homeassistant/components/zabbix/__init__.py | 1 + homeassistant/components/zabbix/sensor.py | 1 + homeassistant/components/zamg/__init__.py | 1 + homeassistant/components/zamg/config_flow.py | 1 + homeassistant/components/zamg/coordinator.py | 1 + homeassistant/components/zamg/sensor.py | 1 + homeassistant/components/zamg/weather.py | 1 + homeassistant/components/zengge/light.py | 1 + homeassistant/components/zeroconf/__init__.py | 1 + homeassistant/components/zerproc/__init__.py | 1 + homeassistant/components/zerproc/light.py | 1 + homeassistant/components/zestimate/sensor.py | 1 + homeassistant/components/zeversolar/__init__.py | 1 + homeassistant/components/zeversolar/config_flow.py | 1 + homeassistant/components/zeversolar/coordinator.py | 1 + homeassistant/components/zeversolar/entity.py | 1 + homeassistant/components/zeversolar/sensor.py | 1 + homeassistant/components/zha/alarm_control_panel.py | 1 + homeassistant/components/zha/binary_sensor.py | 1 + homeassistant/components/zha/button.py | 1 + homeassistant/components/zha/climate.py | 1 + homeassistant/components/zha/config_flow.py | 1 + homeassistant/components/zha/core/cluster_handlers/__init__.py | 1 + homeassistant/components/zha/core/cluster_handlers/closures.py | 1 + homeassistant/components/zha/core/cluster_handlers/general.py | 1 + homeassistant/components/zha/core/cluster_handlers/helpers.py | 1 + .../components/zha/core/cluster_handlers/homeautomation.py | 1 + homeassistant/components/zha/core/cluster_handlers/hvac.py | 1 + homeassistant/components/zha/core/cluster_handlers/lighting.py | 1 + .../components/zha/core/cluster_handlers/manufacturerspecific.py | 1 + .../components/zha/core/cluster_handlers/measurement.py | 1 + homeassistant/components/zha/core/cluster_handlers/protocol.py | 1 + homeassistant/components/zha/core/cluster_handlers/security.py | 1 + .../components/zha/core/cluster_handlers/smartenergy.py | 1 + homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/core/decorators.py | 1 + homeassistant/components/zha/core/device.py | 1 + homeassistant/components/zha/core/discovery.py | 1 + homeassistant/components/zha/core/endpoint.py | 1 + homeassistant/components/zha/core/gateway.py | 1 + homeassistant/components/zha/core/group.py | 1 + homeassistant/components/zha/core/helpers.py | 1 + homeassistant/components/zha/core/registries.py | 1 + homeassistant/components/zha/cover.py | 1 + homeassistant/components/zha/device_action.py | 1 + homeassistant/components/zha/device_tracker.py | 1 + homeassistant/components/zha/diagnostics.py | 1 + homeassistant/components/zha/entity.py | 1 + homeassistant/components/zha/fan.py | 1 + homeassistant/components/zha/light.py | 1 + homeassistant/components/zha/logbook.py | 1 + homeassistant/components/zha/number.py | 1 + homeassistant/components/zha/radio_manager.py | 1 + homeassistant/components/zha/repairs/__init__.py | 1 + .../components/zha/repairs/network_settings_inconsistent.py | 1 + homeassistant/components/zha/repairs/wrong_silabs_firmware.py | 1 + homeassistant/components/zha/select.py | 1 + homeassistant/components/zha/sensor.py | 1 + homeassistant/components/zha/siren.py | 1 + homeassistant/components/zha/switch.py | 1 + homeassistant/components/zha/update.py | 1 + homeassistant/components/zha/websocket_api.py | 1 + homeassistant/components/zhong_hong/climate.py | 1 + homeassistant/components/ziggo_mediabox_xl/media_player.py | 1 + homeassistant/components/zodiac/config_flow.py | 1 + homeassistant/components/zodiac/sensor.py | 1 + homeassistant/components/zone/__init__.py | 1 + homeassistant/components/zone/config_flow.py | 1 + homeassistant/components/zone/trigger.py | 1 + homeassistant/components/zoneminder/binary_sensor.py | 1 + homeassistant/components/zoneminder/camera.py | 1 + homeassistant/components/zoneminder/sensor.py | 1 + homeassistant/components/zoneminder/switch.py | 1 + homeassistant/components/zwave_js/__init__.py | 1 + homeassistant/components/zwave_js/addon.py | 1 + homeassistant/components/zwave_js/api.py | 1 + homeassistant/components/zwave_js/binary_sensor.py | 1 + homeassistant/components/zwave_js/button.py | 1 + homeassistant/components/zwave_js/climate.py | 1 + homeassistant/components/zwave_js/config_flow.py | 1 + homeassistant/components/zwave_js/config_validation.py | 1 + homeassistant/components/zwave_js/const.py | 1 + homeassistant/components/zwave_js/cover.py | 1 + homeassistant/components/zwave_js/device_action.py | 1 + homeassistant/components/zwave_js/device_automation_helpers.py | 1 + homeassistant/components/zwave_js/device_condition.py | 1 + homeassistant/components/zwave_js/device_trigger.py | 1 + homeassistant/components/zwave_js/diagnostics.py | 1 + homeassistant/components/zwave_js/discovery.py | 1 + homeassistant/components/zwave_js/discovery_data_template.py | 1 + homeassistant/components/zwave_js/entity.py | 1 + homeassistant/components/zwave_js/event.py | 1 + homeassistant/components/zwave_js/fan.py | 1 + homeassistant/components/zwave_js/helpers.py | 1 + homeassistant/components/zwave_js/humidifier.py | 1 + homeassistant/components/zwave_js/light.py | 1 + homeassistant/components/zwave_js/lock.py | 1 + homeassistant/components/zwave_js/logbook.py | 1 + homeassistant/components/zwave_js/migrate.py | 1 + homeassistant/components/zwave_js/number.py | 1 + homeassistant/components/zwave_js/repairs.py | 1 + .../zwave_js/scripts/convert_device_diagnostics_to_fixture.py | 1 + homeassistant/components/zwave_js/select.py | 1 + homeassistant/components/zwave_js/sensor.py | 1 + homeassistant/components/zwave_js/services.py | 1 + homeassistant/components/zwave_js/siren.py | 1 + homeassistant/components/zwave_js/switch.py | 1 + homeassistant/components/zwave_js/trigger.py | 1 + homeassistant/components/zwave_js/triggers/event.py | 1 + homeassistant/components/zwave_js/triggers/trigger_helpers.py | 1 + homeassistant/components/zwave_js/triggers/value_updated.py | 1 + homeassistant/components/zwave_js/update.py | 1 + homeassistant/components/zwave_me/binary_sensor.py | 1 + homeassistant/components/zwave_me/button.py | 1 + homeassistant/components/zwave_me/climate.py | 1 + homeassistant/components/zwave_me/config_flow.py | 1 + homeassistant/components/zwave_me/const.py | 1 + homeassistant/components/zwave_me/cover.py | 1 + homeassistant/components/zwave_me/fan.py | 1 + homeassistant/components/zwave_me/helpers.py | 1 + homeassistant/components/zwave_me/light.py | 1 + homeassistant/components/zwave_me/lock.py | 1 + homeassistant/components/zwave_me/number.py | 1 + homeassistant/components/zwave_me/sensor.py | 1 + homeassistant/components/zwave_me/siren.py | 1 + 392 files changed, 392 insertions(+) diff --git a/homeassistant/components/w800rf32/binary_sensor.py b/homeassistant/components/w800rf32/binary_sensor.py index 2bc0c0eea75..49eec35cb1e 100644 --- a/homeassistant/components/w800rf32/binary_sensor.py +++ b/homeassistant/components/w800rf32/binary_sensor.py @@ -1,4 +1,5 @@ """Support for w800rf32 binary sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/wake_on_lan/__init__.py b/homeassistant/components/wake_on_lan/__init__.py index 54809d6cec3..37837da683a 100644 --- a/homeassistant/components/wake_on_lan/__init__.py +++ b/homeassistant/components/wake_on_lan/__init__.py @@ -1,4 +1,5 @@ """Support for sending Wake-On-LAN magic packets.""" + from functools import partial import logging diff --git a/homeassistant/components/wake_on_lan/switch.py b/homeassistant/components/wake_on_lan/switch.py index 446df402c87..a0b54fd8db0 100644 --- a/homeassistant/components/wake_on_lan/switch.py +++ b/homeassistant/components/wake_on_lan/switch.py @@ -1,4 +1,5 @@ """Support for wake on lan.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/wake_word/__init__.py b/homeassistant/components/wake_word/__init__.py index bf502023e2b..f05a61e34dc 100644 --- a/homeassistant/components/wake_word/__init__.py +++ b/homeassistant/components/wake_word/__init__.py @@ -1,4 +1,5 @@ """Provide functionality to wake word.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/wake_word/models.py b/homeassistant/components/wake_word/models.py index c341df188ce..ba92b3a0799 100644 --- a/homeassistant/components/wake_word/models.py +++ b/homeassistant/components/wake_word/models.py @@ -1,4 +1,5 @@ """Wake word models.""" + from dataclasses import dataclass diff --git a/homeassistant/components/wallbox/__init__.py b/homeassistant/components/wallbox/__init__.py index 4ca6e768f64..4ea2cf98be1 100644 --- a/homeassistant/components/wallbox/__init__.py +++ b/homeassistant/components/wallbox/__init__.py @@ -1,4 +1,5 @@ """The Wallbox integration.""" + from __future__ import annotations from wallbox import Wallbox diff --git a/homeassistant/components/wallbox/config_flow.py b/homeassistant/components/wallbox/config_flow.py index 17b3f02a61f..44c47149554 100644 --- a/homeassistant/components/wallbox/config_flow.py +++ b/homeassistant/components/wallbox/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Wallbox integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/wallbox/const.py b/homeassistant/components/wallbox/const.py index 6caa3c070c8..69633cbda22 100644 --- a/homeassistant/components/wallbox/const.py +++ b/homeassistant/components/wallbox/const.py @@ -1,4 +1,5 @@ """Constants for the Wallbox integration.""" + from enum import StrEnum DOMAIN = "wallbox" diff --git a/homeassistant/components/wallbox/coordinator.py b/homeassistant/components/wallbox/coordinator.py index 96d66bb4395..a33d90db700 100644 --- a/homeassistant/components/wallbox/coordinator.py +++ b/homeassistant/components/wallbox/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the wallbox integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wallbox/entity.py b/homeassistant/components/wallbox/entity.py index 1152530dbd1..489e81ed6b0 100644 --- a/homeassistant/components/wallbox/entity.py +++ b/homeassistant/components/wallbox/entity.py @@ -1,4 +1,5 @@ """Base entity for the wallbox integration.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/wallbox/lock.py b/homeassistant/components/wallbox/lock.py index 195d76e9238..4853a9104f2 100644 --- a/homeassistant/components/wallbox/lock.py +++ b/homeassistant/components/wallbox/lock.py @@ -1,4 +1,5 @@ """Home Assistant component for accessing the Wallbox Portal API. The lock component creates a lock entity.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index 695a7960e51..f09321a0849 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -2,6 +2,7 @@ The number component allows control of charging current. """ + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index 7fb9a488244..eadbc04dca2 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -1,4 +1,5 @@ """Home Assistant component for accessing the Wallbox Portal API. The sensor component creates multiple sensors regarding wallbox performance.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/wallbox/switch.py b/homeassistant/components/wallbox/switch.py index 2de6379eb18..06c2674579d 100644 --- a/homeassistant/components/wallbox/switch.py +++ b/homeassistant/components/wallbox/switch.py @@ -1,4 +1,5 @@ """Home Assistant component for accessing the Wallbox Portal API. The switch component creates a switch entity.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/waqi/__init__.py b/homeassistant/components/waqi/__init__.py index d4e41095b26..387d06da1a1 100644 --- a/homeassistant/components/waqi/__init__.py +++ b/homeassistant/components/waqi/__init__.py @@ -1,4 +1,5 @@ """The World Air Quality Index (WAQI) integration.""" + from __future__ import annotations from aiowaqi import WAQIClient diff --git a/homeassistant/components/waqi/config_flow.py b/homeassistant/components/waqi/config_flow.py index 7a0d4eda546..b8c6cdf57a3 100644 --- a/homeassistant/components/waqi/config_flow.py +++ b/homeassistant/components/waqi/config_flow.py @@ -1,4 +1,5 @@ """Config flow for World Air Quality Index (WAQI) integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/waqi/coordinator.py b/homeassistant/components/waqi/coordinator.py index b7beef8fda9..d1a44e9f5b8 100644 --- a/homeassistant/components/waqi/coordinator.py +++ b/homeassistant/components/waqi/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for the World Air Quality Index (WAQI) integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index 43be729e10f..8df4bf05b06 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -1,4 +1,5 @@ """Support for the World Air Quality Index service.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 79572973090..dad3bd8c48b 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -1,4 +1,5 @@ """Support for water heater devices.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/water_heater/device_action.py b/homeassistant/components/water_heater/device_action.py index 5b0fe46934c..49cfc7e9a07 100644 --- a/homeassistant/components/water_heater/device_action.py +++ b/homeassistant/components/water_heater/device_action.py @@ -1,4 +1,5 @@ """Provides device automations for Water Heater.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/water_heater/reproduce_state.py b/homeassistant/components/water_heater/reproduce_state.py index b3be0f62273..de0bb320020 100644 --- a/homeassistant/components/water_heater/reproduce_state.py +++ b/homeassistant/components/water_heater/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an Water heater state.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/water_heater/significant_change.py b/homeassistant/components/water_heater/significant_change.py index bacb0232ee3..c0db97c6e40 100644 --- a/homeassistant/components/water_heater/significant_change.py +++ b/homeassistant/components/water_heater/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Water Heater state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/waterfurnace/__init__.py b/homeassistant/components/waterfurnace/__init__.py index 50031216609..4c150b99f43 100644 --- a/homeassistant/components/waterfurnace/__init__.py +++ b/homeassistant/components/waterfurnace/__init__.py @@ -1,4 +1,5 @@ """Support for Waterfurnaces.""" + from datetime import timedelta import logging import threading diff --git a/homeassistant/components/waterfurnace/sensor.py b/homeassistant/components/waterfurnace/sensor.py index 9d60de29264..46f76a58eb5 100644 --- a/homeassistant/components/waterfurnace/sensor.py +++ b/homeassistant/components/waterfurnace/sensor.py @@ -1,4 +1,5 @@ """Support for Waterfurnace.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/watttime/__init__.py b/homeassistant/components/watttime/__init__.py index cac73f597f6..6b32cf723a3 100644 --- a/homeassistant/components/watttime/__init__.py +++ b/homeassistant/components/watttime/__init__.py @@ -1,4 +1,5 @@ """The WattTime integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/watttime/config_flow.py b/homeassistant/components/watttime/config_flow.py index 776fd130824..549f6fc7679 100644 --- a/homeassistant/components/watttime/config_flow.py +++ b/homeassistant/components/watttime/config_flow.py @@ -1,4 +1,5 @@ """Config flow for WattTime integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/watttime/diagnostics.py b/homeassistant/components/watttime/diagnostics.py index 2808e8e3c35..adedcd13835 100644 --- a/homeassistant/components/watttime/diagnostics.py +++ b/homeassistant/components/watttime/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for WattTime.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/watttime/sensor.py b/homeassistant/components/watttime/sensor.py index 93560cef808..c6cc81580d7 100644 --- a/homeassistant/components/watttime/sensor.py +++ b/homeassistant/components/watttime/sensor.py @@ -1,4 +1,5 @@ """Support for WattTime sensors.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/waze_travel_time/config_flow.py b/homeassistant/components/waze_travel_time/config_flow.py index 2b863d74f7c..a196c5f4f57 100644 --- a/homeassistant/components/waze_travel_time/config_flow.py +++ b/homeassistant/components/waze_travel_time/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Waze Travel Time integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/waze_travel_time/const.py b/homeassistant/components/waze_travel_time/const.py index 572676e1966..84e41c3963f 100644 --- a/homeassistant/components/waze_travel_time/const.py +++ b/homeassistant/components/waze_travel_time/const.py @@ -1,4 +1,5 @@ """Constants for waze_travel_time.""" + from __future__ import annotations DOMAIN = "waze_travel_time" diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index 5204c0b1075..e759c06bf10 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -1,4 +1,5 @@ """Support for Waze travel time sensor.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index bdc8ae4d514..5c4c53079ba 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -1,4 +1,5 @@ """Weather component that handles meteorological data for your location.""" + from __future__ import annotations import abc diff --git a/homeassistant/components/weather/const.py b/homeassistant/components/weather/const.py index c6da2c28c71..0b5246ab31c 100644 --- a/homeassistant/components/weather/const.py +++ b/homeassistant/components/weather/const.py @@ -1,4 +1,5 @@ """Constants for weather.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/weather/intent.py b/homeassistant/components/weather/intent.py index 4fd22ceb0a9..c216fcda17d 100644 --- a/homeassistant/components/weather/intent.py +++ b/homeassistant/components/weather/intent.py @@ -1,4 +1,5 @@ """Intents for the weather integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/weather/significant_change.py b/homeassistant/components/weather/significant_change.py index 87e1246ce85..d36139904f5 100644 --- a/homeassistant/components/weather/significant_change.py +++ b/homeassistant/components/weather/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant Weather state changes.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/weather/websocket_api.py b/homeassistant/components/weather/websocket_api.py index 39a487dcb2f..98adbd1bd02 100644 --- a/homeassistant/components/weather/websocket_api.py +++ b/homeassistant/components/weather/websocket_api.py @@ -1,4 +1,5 @@ """The weather websocket API.""" + from __future__ import annotations from typing import Any, Literal diff --git a/homeassistant/components/weatherflow/__init__.py b/homeassistant/components/weatherflow/__init__.py index fbd206b63f5..819ad90b354 100644 --- a/homeassistant/components/weatherflow/__init__.py +++ b/homeassistant/components/weatherflow/__init__.py @@ -1,4 +1,5 @@ """Get data from Smart Weather station via UDP.""" + from __future__ import annotations from pyweatherflowudp.client import EVENT_DEVICE_DISCOVERED, WeatherFlowListener diff --git a/homeassistant/components/weatherflow/config_flow.py b/homeassistant/components/weatherflow/config_flow.py index e96972437fc..11951d60272 100644 --- a/homeassistant/components/weatherflow/config_flow.py +++ b/homeassistant/components/weatherflow/config_flow.py @@ -1,4 +1,5 @@ """Config flow for WeatherFlow.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/weatherflow/sensor.py b/homeassistant/components/weatherflow/sensor.py index 74642ae04af..5bedaa9d711 100644 --- a/homeassistant/components/weatherflow/sensor.py +++ b/homeassistant/components/weatherflow/sensor.py @@ -1,4 +1,5 @@ """Sensors for the weatherflow integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/weatherflow_cloud/__init__.py b/homeassistant/components/weatherflow_cloud/__init__.py index 24b862433bd..a40386100e7 100644 --- a/homeassistant/components/weatherflow_cloud/__init__.py +++ b/homeassistant/components/weatherflow_cloud/__init__.py @@ -1,4 +1,5 @@ """The WeatherflowCloud integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/weatherflow_cloud/config_flow.py b/homeassistant/components/weatherflow_cloud/config_flow.py index 889b7e81d52..6e6212042e1 100644 --- a/homeassistant/components/weatherflow_cloud/config_flow.py +++ b/homeassistant/components/weatherflow_cloud/config_flow.py @@ -1,4 +1,5 @@ """Config flow for WeatherflowCloud integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/weatherflow_cloud/coordinator.py b/homeassistant/components/weatherflow_cloud/coordinator.py index 7b9ddaafaae..56554b555b0 100644 --- a/homeassistant/components/weatherflow_cloud/coordinator.py +++ b/homeassistant/components/weatherflow_cloud/coordinator.py @@ -1,4 +1,5 @@ """Data coordinator for WeatherFlow Cloud Data.""" + from datetime import timedelta from aiohttp import ClientResponseError diff --git a/homeassistant/components/weatherflow_cloud/weather.py b/homeassistant/components/weatherflow_cloud/weather.py index b4ed6a3a9d8..0d754eebb64 100644 --- a/homeassistant/components/weatherflow_cloud/weather.py +++ b/homeassistant/components/weatherflow_cloud/weather.py @@ -1,4 +1,5 @@ """Support for WeatherFlow Forecast weather service.""" + from __future__ import annotations from weatherflow4py.models.unified import WeatherFlowData diff --git a/homeassistant/components/weatherkit/__init__.py b/homeassistant/components/weatherkit/__init__.py index 307b2272d6c..49158182696 100644 --- a/homeassistant/components/weatherkit/__init__.py +++ b/homeassistant/components/weatherkit/__init__.py @@ -1,4 +1,5 @@ """Integration for Apple's WeatherKit API.""" + from __future__ import annotations from apple_weatherkit.client import ( diff --git a/homeassistant/components/weatherkit/config_flow.py b/homeassistant/components/weatherkit/config_flow.py index 1fbb0c1a36f..760516e894d 100644 --- a/homeassistant/components/weatherkit/config_flow.py +++ b/homeassistant/components/weatherkit/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for WeatherKit.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/weatherkit/const.py b/homeassistant/components/weatherkit/const.py index e35dd33c561..99cd50df8fa 100644 --- a/homeassistant/components/weatherkit/const.py +++ b/homeassistant/components/weatherkit/const.py @@ -1,4 +1,5 @@ """Constants for WeatherKit.""" + from logging import Logger, getLogger LOGGER: Logger = getLogger(__package__) diff --git a/homeassistant/components/weatherkit/coordinator.py b/homeassistant/components/weatherkit/coordinator.py index 824c85781ea..ddabba2fc1f 100644 --- a/homeassistant/components/weatherkit/coordinator.py +++ b/homeassistant/components/weatherkit/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for WeatherKit integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 8d73e444279..09b7541cbea 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -1,4 +1,5 @@ """Webhooks for Home Assistant.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Iterable diff --git a/homeassistant/components/webhook/trigger.py b/homeassistant/components/webhook/trigger.py index 05bb53564bd..b4fd3008cd8 100644 --- a/homeassistant/components/webhook/trigger.py +++ b/homeassistant/components/webhook/trigger.py @@ -1,4 +1,5 @@ """Offer webhook triggered automation rules.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/webmin/config_flow.py b/homeassistant/components/webmin/config_flow.py index 783590d35ba..1d9c86edbac 100644 --- a/homeassistant/components/webmin/config_flow.py +++ b/homeassistant/components/webmin/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Webmin.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/webmin/coordinator.py b/homeassistant/components/webmin/coordinator.py index 9a725ee2a77..28c8d54b0d2 100644 --- a/homeassistant/components/webmin/coordinator.py +++ b/homeassistant/components/webmin/coordinator.py @@ -1,4 +1,5 @@ """Data update coordinator for the Webmin integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/webmin/sensor.py b/homeassistant/components/webmin/sensor.py index f20f8f9b625..90d3fd71532 100644 --- a/homeassistant/components/webmin/sensor.py +++ b/homeassistant/components/webmin/sensor.py @@ -1,4 +1,5 @@ """Support for Webmin sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 6e960ceb143..479407c3199 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -1,4 +1,5 @@ """Support for LG webOS Smart TV.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/webostv/config_flow.py b/homeassistant/components/webostv/config_flow.py index 52c17a8b411..f380e49f8a3 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure webostv component.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/webostv/device_trigger.py b/homeassistant/components/webostv/device_trigger.py index c7e5701af02..0175df5d828 100644 --- a/homeassistant/components/webostv/device_trigger.py +++ b/homeassistant/components/webostv/device_trigger.py @@ -1,4 +1,5 @@ """Provides device automations for control of LG webOS Smart TV.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/webostv/diagnostics.py b/homeassistant/components/webostv/diagnostics.py index 5d88d61aa9d..1657fb71d26 100644 --- a/homeassistant/components/webostv/diagnostics.py +++ b/homeassistant/components/webostv/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for LG webOS Smart TV.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/webostv/helpers.py b/homeassistant/components/webostv/helpers.py index b61e4af3e6b..edcfdcfed8b 100644 --- a/homeassistant/components/webostv/helpers.py +++ b/homeassistant/components/webostv/helpers.py @@ -1,4 +1,5 @@ """Helper functions for webOS Smart TV.""" + from __future__ import annotations from aiowebostv import WebOsClient diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index aefb6e77444..647cf64ea8e 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -1,4 +1,5 @@ """Support for interface with an LG webOS Smart TV.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/webostv/notify.py b/homeassistant/components/webostv/notify.py index c4cefc3cffe..43320687ce8 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -1,4 +1,5 @@ """Support for LG WebOS TV notification service.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/webostv/trigger.py b/homeassistant/components/webostv/trigger.py index 4d237993f95..3290aa4a448 100644 --- a/homeassistant/components/webostv/trigger.py +++ b/homeassistant/components/webostv/trigger.py @@ -1,4 +1,5 @@ """webOS Smart TV trigger dispatcher.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/webostv/triggers/turn_on.py b/homeassistant/components/webostv/triggers/turn_on.py index 403219f1372..f2ecb8aa98d 100644 --- a/homeassistant/components/webostv/triggers/turn_on.py +++ b/homeassistant/components/webostv/triggers/turn_on.py @@ -1,4 +1,5 @@ """webOS Smart TV device turn on trigger.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py index f7086cc81db..291b652ac09 100644 --- a/homeassistant/components/websocket_api/__init__.py +++ b/homeassistant/components/websocket_api/__init__.py @@ -1,4 +1,5 @@ """WebSocket based API for Home Assistant.""" + from __future__ import annotations from typing import Final, cast diff --git a/homeassistant/components/websocket_api/auth.py b/homeassistant/components/websocket_api/auth.py index 3940e1333d0..5093ef187c5 100644 --- a/homeassistant/components/websocket_api/auth.py +++ b/homeassistant/components/websocket_api/auth.py @@ -1,4 +1,5 @@ """Handle the auth of a connection.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 368785c17bc..376ef1cfacc 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -1,4 +1,5 @@ """Commands part of Websocket API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py index aa7bcefadae..63b4418a19d 100644 --- a/homeassistant/components/websocket_api/connection.py +++ b/homeassistant/components/websocket_api/connection.py @@ -1,4 +1,5 @@ """Connection session.""" + from __future__ import annotations from collections.abc import Callable, Hashable diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index 9a44f80a5c8..c5de49e4033 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -1,4 +1,5 @@ """Websocket constants.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index b4c72d497cd..4de83530642 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -1,4 +1,5 @@ """Decorators for the Websocket API.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/websocket_api/error.py b/homeassistant/components/websocket_api/error.py index 5d4ca93105d..4ed80adb365 100644 --- a/homeassistant/components/websocket_api/error.py +++ b/homeassistant/components/websocket_api/error.py @@ -1,4 +1,5 @@ """WebSocket API related errors.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 06b9c0c4bfa..fc75b46ddbd 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -1,4 +1,5 @@ """View to accept incoming websocket connection.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index 3916cdd3af7..2592f9d7356 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -1,4 +1,5 @@ """Message templates for websocket commands.""" + from __future__ import annotations from functools import lru_cache diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py index 5857ead2c11..7d668466bc2 100644 --- a/homeassistant/components/websocket_api/sensor.py +++ b/homeassistant/components/websocket_api/sensor.py @@ -1,4 +1,5 @@ """Entity to track connections to websocket API.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index 2f4d4c84c5c..f354a5d5e50 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -1,4 +1,5 @@ """Support for WeMo device discovery.""" + from __future__ import annotations from collections.abc import Callable, Coroutine, Sequence diff --git a/homeassistant/components/wemo/device_trigger.py b/homeassistant/components/wemo/device_trigger.py index 077c32fb1ad..d9cadcdd576 100644 --- a/homeassistant/components/wemo/device_trigger.py +++ b/homeassistant/components/wemo/device_trigger.py @@ -1,4 +1,5 @@ """Triggers for WeMo devices.""" + from __future__ import annotations from pywemo.subscribe import EVENT_TYPE_LONG_PRESS diff --git a/homeassistant/components/wemo/entity.py b/homeassistant/components/wemo/entity.py index cbb2f31c79d..a6fe677d357 100644 --- a/homeassistant/components/wemo/entity.py +++ b/homeassistant/components/wemo/entity.py @@ -1,4 +1,5 @@ """Classes shared among Wemo entities.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 39abdba6e82..89b20bdde25 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -1,4 +1,5 @@ """Support for WeMo humidifier.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 0205a10521d..00c5204eba9 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -1,4 +1,5 @@ """Support for Belkin WeMo lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/wemo/sensor.py b/homeassistant/components/wemo/sensor.py index ecb0c16055c..555e2591832 100644 --- a/homeassistant/components/wemo/sensor.py +++ b/homeassistant/components/wemo/sensor.py @@ -1,4 +1,5 @@ """Support for power sensors in WeMo Insight devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 508621ba415..8cc95325c7e 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -1,4 +1,5 @@ """Support for WeMo switches.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/wemo/wemo_device.py b/homeassistant/components/wemo/wemo_device.py index f9b111d7eb1..f2bd93fa2b1 100644 --- a/homeassistant/components/wemo/wemo_device.py +++ b/homeassistant/components/wemo/wemo_device.py @@ -1,4 +1,5 @@ """Home Assistant wrapper for a pyWeMo device.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/whirlpool/__init__.py b/homeassistant/components/whirlpool/__init__.py index 10b26801c10..919f9b9be89 100644 --- a/homeassistant/components/whirlpool/__init__.py +++ b/homeassistant/components/whirlpool/__init__.py @@ -1,4 +1,5 @@ """The Whirlpool Appliances integration.""" + from dataclasses import dataclass import logging diff --git a/homeassistant/components/whirlpool/climate.py b/homeassistant/components/whirlpool/climate.py index 48b9b99c1e2..aa399746006 100644 --- a/homeassistant/components/whirlpool/climate.py +++ b/homeassistant/components/whirlpool/climate.py @@ -1,4 +1,5 @@ """Platform for climate integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/whirlpool/config_flow.py b/homeassistant/components/whirlpool/config_flow.py index aa6d7875865..e8ab765feba 100644 --- a/homeassistant/components/whirlpool/config_flow.py +++ b/homeassistant/components/whirlpool/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Whirlpool Appliances integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/whirlpool/sensor.py b/homeassistant/components/whirlpool/sensor.py index 227c0e9f653..ff92a059e96 100644 --- a/homeassistant/components/whirlpool/sensor.py +++ b/homeassistant/components/whirlpool/sensor.py @@ -1,4 +1,5 @@ """The Washer/Dryer Sensor for Whirlpool Appliances.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/whois/__init__.py b/homeassistant/components/whois/__init__.py index 746e83a2677..b9f5938d93b 100644 --- a/homeassistant/components/whois/__init__.py +++ b/homeassistant/components/whois/__init__.py @@ -1,4 +1,5 @@ """The Whois integration.""" + from __future__ import annotations from whois import Domain, query as whois_query diff --git a/homeassistant/components/whois/config_flow.py b/homeassistant/components/whois/config_flow.py index 1a35ca9749e..cb4326d996d 100644 --- a/homeassistant/components/whois/config_flow.py +++ b/homeassistant/components/whois/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Whois integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/whois/const.py b/homeassistant/components/whois/const.py index 3fbbd6ff3ab..f196053f48d 100644 --- a/homeassistant/components/whois/const.py +++ b/homeassistant/components/whois/const.py @@ -1,4 +1,5 @@ """Constants for the Whois integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/whois/diagnostics.py b/homeassistant/components/whois/diagnostics.py index 19b3822e55c..0f93461d8d8 100644 --- a/homeassistant/components/whois/diagnostics.py +++ b/homeassistant/components/whois/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Whois.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/whois/sensor.py b/homeassistant/components/whois/sensor.py index 2de6509698f..fe193b16eea 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -1,4 +1,5 @@ """Get WHOIS information for a given host.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index 3a35ec1ed29..c465bc0d2ca 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -1,4 +1,5 @@ """Component for wiffi support.""" + from datetime import timedelta import errno import logging diff --git a/homeassistant/components/wiffi/binary_sensor.py b/homeassistant/components/wiffi/binary_sensor.py index cb1e1da41d8..23aebd122f2 100644 --- a/homeassistant/components/wiffi/binary_sensor.py +++ b/homeassistant/components/wiffi/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor platform support for wiffi devices.""" + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/wiffi/config_flow.py b/homeassistant/components/wiffi/config_flow.py index a186b7fe4da..17262dd0276 100644 --- a/homeassistant/components/wiffi/config_flow.py +++ b/homeassistant/components/wiffi/config_flow.py @@ -2,6 +2,7 @@ Used by UI to setup a wiffi integration. """ + from __future__ import annotations import errno diff --git a/homeassistant/components/wiffi/sensor.py b/homeassistant/components/wiffi/sensor.py index e460a346bd7..7b64628085a 100644 --- a/homeassistant/components/wiffi/sensor.py +++ b/homeassistant/components/wiffi/sensor.py @@ -1,4 +1,5 @@ """Sensor platform support for wiffi devices.""" + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, diff --git a/homeassistant/components/wilight/config_flow.py b/homeassistant/components/wilight/config_flow.py index 15c7a4d2c3b..52b3b426c20 100644 --- a/homeassistant/components/wilight/config_flow.py +++ b/homeassistant/components/wilight/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure WiLight.""" + from urllib.parse import urlparse import pywilight diff --git a/homeassistant/components/wilight/cover.py b/homeassistant/components/wilight/cover.py index aa50b79f139..4ae4692db40 100644 --- a/homeassistant/components/wilight/cover.py +++ b/homeassistant/components/wilight/cover.py @@ -1,4 +1,5 @@ """Support for WiLight Cover.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wilight/fan.py b/homeassistant/components/wilight/fan.py index 4c34bd16bd0..5c05575c4f8 100644 --- a/homeassistant/components/wilight/fan.py +++ b/homeassistant/components/wilight/fan.py @@ -1,4 +1,5 @@ """Support for WiLight Fan.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wilight/light.py b/homeassistant/components/wilight/light.py index b17eac36f09..1a51ecd884e 100644 --- a/homeassistant/components/wilight/light.py +++ b/homeassistant/components/wilight/light.py @@ -1,4 +1,5 @@ """Support for WiLight lights.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wilight/parent_device.py b/homeassistant/components/wilight/parent_device.py index 8091e78cc76..6e96274f0a4 100644 --- a/homeassistant/components/wilight/parent_device.py +++ b/homeassistant/components/wilight/parent_device.py @@ -1,4 +1,5 @@ """The WiLight Device integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/wilight/support.py b/homeassistant/components/wilight/support.py index 9470bf74c8a..39578618d50 100644 --- a/homeassistant/components/wilight/support.py +++ b/homeassistant/components/wilight/support.py @@ -1,4 +1,5 @@ """Support for config validation using voluptuous and Translate Trigger.""" + from __future__ import annotations import calendar diff --git a/homeassistant/components/wilight/switch.py b/homeassistant/components/wilight/switch.py index ac4d65b041b..94e39492626 100644 --- a/homeassistant/components/wilight/switch.py +++ b/homeassistant/components/wilight/switch.py @@ -1,4 +1,5 @@ """Support for WiLight switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wirelesstag/binary_sensor.py b/homeassistant/components/wirelesstag/binary_sensor.py index 64a1097bcab..85efab16e70 100644 --- a/homeassistant/components/wirelesstag/binary_sensor.py +++ b/homeassistant/components/wirelesstag/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensor support for Wireless Sensor Tags.""" + from __future__ import annotations import voluptuous as vol diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index 8ae20031723..0e88272a41c 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -1,4 +1,5 @@ """Sensor support for Wireless Sensor Tags platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/wirelesstag/switch.py b/homeassistant/components/wirelesstag/switch.py index 7f4008623b1..0eafea0699b 100644 --- a/homeassistant/components/wirelesstag/switch.py +++ b/homeassistant/components/wirelesstag/switch.py @@ -1,4 +1,5 @@ """Switch implementation for Wireless Sensor Tags (wirelesstag.net).""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index f42fb7a57b9..5ca2a610e3d 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -2,6 +2,7 @@ For more details about this platform, please refer to the documentation at """ + from __future__ import annotations import asyncio diff --git a/homeassistant/components/withings/binary_sensor.py b/homeassistant/components/withings/binary_sensor.py index 12583ba4758..89e2c3227ae 100644 --- a/homeassistant/components/withings/binary_sensor.py +++ b/homeassistant/components/withings/binary_sensor.py @@ -1,4 +1,5 @@ """Sensors flow for Withings.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/withings/calendar.py b/homeassistant/components/withings/calendar.py index 132f00936f3..3e543e8e9ef 100644 --- a/homeassistant/components/withings/calendar.py +++ b/homeassistant/components/withings/calendar.py @@ -1,4 +1,5 @@ """Calendar platform for Withings.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/withings/config_flow.py b/homeassistant/components/withings/config_flow.py index e7baf2714a2..1b92f23685f 100644 --- a/homeassistant/components/withings/config_flow.py +++ b/homeassistant/components/withings/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Withings.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/withings/coordinator.py b/homeassistant/components/withings/coordinator.py index 2639ccccf7d..19b362dfa0a 100644 --- a/homeassistant/components/withings/coordinator.py +++ b/homeassistant/components/withings/coordinator.py @@ -1,4 +1,5 @@ """Withings coordinator.""" + from abc import abstractmethod from datetime import date, datetime, timedelta from typing import TypeVar diff --git a/homeassistant/components/withings/diagnostics.py b/homeassistant/components/withings/diagnostics.py index 31c9ffef569..bc51036e6ec 100644 --- a/homeassistant/components/withings/diagnostics.py +++ b/homeassistant/components/withings/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Withings.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/withings/entity.py b/homeassistant/components/withings/entity.py index 7f3e694533c..4c9b27c72fc 100644 --- a/homeassistant/components/withings/entity.py +++ b/homeassistant/components/withings/entity.py @@ -1,4 +1,5 @@ """Base entity for Withings.""" + from __future__ import annotations from typing import TypeVar diff --git a/homeassistant/components/withings/sensor.py b/homeassistant/components/withings/sensor.py index d882cd8cddd..a3862485da4 100644 --- a/homeassistant/components/withings/sensor.py +++ b/homeassistant/components/withings/sensor.py @@ -1,4 +1,5 @@ """Sensors flow for Withings.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wiz/__init__.py b/homeassistant/components/wiz/__init__.py index 26d4e33a7a6..130cc73efd3 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -1,4 +1,5 @@ """WiZ Platform integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/wiz/binary_sensor.py b/homeassistant/components/wiz/binary_sensor.py index 6b3caf23a1c..b58e120a9dd 100644 --- a/homeassistant/components/wiz/binary_sensor.py +++ b/homeassistant/components/wiz/binary_sensor.py @@ -1,4 +1,5 @@ """WiZ integration binary sensor platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wiz/config_flow.py b/homeassistant/components/wiz/config_flow.py index a5a9d2e5ed4..3220856b89d 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -1,4 +1,5 @@ """Config flow for WiZ Platform.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/wiz/const.py b/homeassistant/components/wiz/const.py index 1aeb2ada580..78074a3d5fb 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -1,4 +1,5 @@ """Constants for the WiZ Platform integration.""" + from datetime import timedelta from pywizlight.exceptions import ( diff --git a/homeassistant/components/wiz/diagnostics.py b/homeassistant/components/wiz/diagnostics.py index 4fdf62b3c8c..5f617ebafe9 100644 --- a/homeassistant/components/wiz/diagnostics.py +++ b/homeassistant/components/wiz/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for WiZ.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wiz/discovery.py b/homeassistant/components/wiz/discovery.py index 350ddfe278a..118ed20ff87 100644 --- a/homeassistant/components/wiz/discovery.py +++ b/homeassistant/components/wiz/discovery.py @@ -1,4 +1,5 @@ """The wiz integration discovery.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/wiz/entity.py b/homeassistant/components/wiz/entity.py index 87c3171d836..e7a95234e16 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -1,4 +1,5 @@ """WiZ integration entities.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/wiz/light.py b/homeassistant/components/wiz/light.py index 3a6ba2a9f5b..aece184720d 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -1,4 +1,5 @@ """WiZ integration light platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wiz/models.py b/homeassistant/components/wiz/models.py index 547ff830303..125a8cfa73b 100644 --- a/homeassistant/components/wiz/models.py +++ b/homeassistant/components/wiz/models.py @@ -1,4 +1,5 @@ """WiZ integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/wiz/number.py b/homeassistant/components/wiz/number.py index 91436674d7f..ed47aec44c3 100644 --- a/homeassistant/components/wiz/number.py +++ b/homeassistant/components/wiz/number.py @@ -1,4 +1,5 @@ """Support for WiZ effect speed numbers.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/wiz/sensor.py b/homeassistant/components/wiz/sensor.py index a66c37fabb5..aae443e60d0 100644 --- a/homeassistant/components/wiz/sensor.py +++ b/homeassistant/components/wiz/sensor.py @@ -1,4 +1,5 @@ """Support for WiZ sensors.""" + from __future__ import annotations from homeassistant.components.sensor import ( diff --git a/homeassistant/components/wiz/switch.py b/homeassistant/components/wiz/switch.py index 1bebaba7579..d94bf12da9f 100644 --- a/homeassistant/components/wiz/switch.py +++ b/homeassistant/components/wiz/switch.py @@ -1,4 +1,5 @@ """WiZ integration switch platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wiz/utils.py b/homeassistant/components/wiz/utils.py index 278989b5b2b..4849e0fb22c 100644 --- a/homeassistant/components/wiz/utils.py +++ b/homeassistant/components/wiz/utils.py @@ -1,4 +1,5 @@ """WiZ utils.""" + from __future__ import annotations from pywizlight import BulbType diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index fe6ac5a1fc1..6f5bb25b162 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -1,4 +1,5 @@ """Support for WLED.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/wled/binary_sensor.py b/homeassistant/components/wled/binary_sensor.py index 6191235f423..260c43c8ba0 100644 --- a/homeassistant/components/wled/binary_sensor.py +++ b/homeassistant/components/wled/binary_sensor.py @@ -1,4 +1,5 @@ """Support for WLED binary sensor.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/wled/button.py b/homeassistant/components/wled/button.py index 430ee067486..7d3047c7c35 100644 --- a/homeassistant/components/wled/button.py +++ b/homeassistant/components/wled/button.py @@ -1,4 +1,5 @@ """Support for WLED button.""" + from __future__ import annotations from homeassistant.components.button import ButtonDeviceClass, ButtonEntity diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index 2c8edf4066c..c40753b686a 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the WLED integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wled/const.py b/homeassistant/components/wled/const.py index cee9984a3f6..f698347537c 100644 --- a/homeassistant/components/wled/const.py +++ b/homeassistant/components/wled/const.py @@ -1,4 +1,5 @@ """Constants for the WLED integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/wled/coordinator.py b/homeassistant/components/wled/coordinator.py index 6bbcb1747f0..f6219c63cb8 100644 --- a/homeassistant/components/wled/coordinator.py +++ b/homeassistant/components/wled/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for WLED.""" + from __future__ import annotations from wled import WLED, Device as WLEDDevice, WLEDConnectionClosedError, WLEDError diff --git a/homeassistant/components/wled/diagnostics.py b/homeassistant/components/wled/diagnostics.py index d0b3de5eb6b..f1eed3fc0aa 100644 --- a/homeassistant/components/wled/diagnostics.py +++ b/homeassistant/components/wled/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for WLED.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/wled/helpers.py b/homeassistant/components/wled/helpers.py index b4b5ee4c892..ad9a02b38ca 100644 --- a/homeassistant/components/wled/helpers.py +++ b/homeassistant/components/wled/helpers.py @@ -1,4 +1,5 @@ """Helpers for WLED.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index 4327261d4be..1e31f090c70 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -1,4 +1,5 @@ """Support for LED lights.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/wled/models.py b/homeassistant/components/wled/models.py index 81405190228..ac7103303cc 100644 --- a/homeassistant/components/wled/models.py +++ b/homeassistant/components/wled/models.py @@ -1,4 +1,5 @@ """Models for WLED.""" + from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/wled/number.py b/homeassistant/components/wled/number.py index fd734c07fbc..4dcdc493e53 100644 --- a/homeassistant/components/wled/number.py +++ b/homeassistant/components/wled/number.py @@ -1,4 +1,5 @@ """Support for LED numbers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wled/select.py b/homeassistant/components/wled/select.py index 36aff0f4536..755cd5746e8 100644 --- a/homeassistant/components/wled/select.py +++ b/homeassistant/components/wled/select.py @@ -1,4 +1,5 @@ """Support for LED selects.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index a2e052eacd9..daf5748021f 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -1,4 +1,5 @@ """Support for WLED sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wled/switch.py b/homeassistant/components/wled/switch.py index f42e1cc7f9f..a5e998ec548 100644 --- a/homeassistant/components/wled/switch.py +++ b/homeassistant/components/wled/switch.py @@ -1,4 +1,5 @@ """Support for WLED switches.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/wled/update.py b/homeassistant/components/wled/update.py index 954279366be..bde2986a841 100644 --- a/homeassistant/components/wled/update.py +++ b/homeassistant/components/wled/update.py @@ -1,4 +1,5 @@ """Support for WLED updates.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/wolflink/__init__.py b/homeassistant/components/wolflink/__init__.py index a8c09fc664e..e1c23893f75 100644 --- a/homeassistant/components/wolflink/__init__.py +++ b/homeassistant/components/wolflink/__init__.py @@ -1,4 +1,5 @@ """The Wolf SmartSet Service integration.""" + from datetime import timedelta import logging diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index 2a030f69171..3179a9ff6bd 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -1,4 +1,5 @@ """The Wolf SmartSet sensors.""" + from __future__ import annotations from wolf_comm.models import ( diff --git a/homeassistant/components/workday/__init__.py b/homeassistant/components/workday/__init__.py index edada92aef4..195221ef088 100644 --- a/homeassistant/components/workday/__init__.py +++ b/homeassistant/components/workday/__init__.py @@ -1,4 +1,5 @@ """Sensor to indicate whether the current day is a workday.""" + from __future__ import annotations from holidays import HolidayBase, country_holidays diff --git a/homeassistant/components/workday/config_flow.py b/homeassistant/components/workday/config_flow.py index d65bc71c6e8..a1a5ed37a14 100644 --- a/homeassistant/components/workday/config_flow.py +++ b/homeassistant/components/workday/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Workday integration.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/workday/const.py b/homeassistant/components/workday/const.py index ad9375830dd..847c3809822 100644 --- a/homeassistant/components/workday/const.py +++ b/homeassistant/components/workday/const.py @@ -1,4 +1,5 @@ """Add constants for Workday integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/worldclock/sensor.py b/homeassistant/components/worldclock/sensor.py index e626add2534..9b2cb600ac1 100644 --- a/homeassistant/components/worldclock/sensor.py +++ b/homeassistant/components/worldclock/sensor.py @@ -1,4 +1,5 @@ """Support for showing the time in a different time zone.""" + from __future__ import annotations from datetime import tzinfo diff --git a/homeassistant/components/worldtidesinfo/sensor.py b/homeassistant/components/worldtidesinfo/sensor.py index 1a5c7ae39a2..a4d663cc184 100644 --- a/homeassistant/components/worldtidesinfo/sensor.py +++ b/homeassistant/components/worldtidesinfo/sensor.py @@ -1,4 +1,5 @@ """Support for the worldtides.info API.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/worxlandroid/sensor.py b/homeassistant/components/worxlandroid/sensor.py index 16073a3d862..10f40bea685 100644 --- a/homeassistant/components/worxlandroid/sensor.py +++ b/homeassistant/components/worxlandroid/sensor.py @@ -1,4 +1,5 @@ """Support for Worx Landroid mower.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/ws66i/__init__.py b/homeassistant/components/ws66i/__init__.py index 64f049aa9ac..1993f38e0ab 100644 --- a/homeassistant/components/ws66i/__init__.py +++ b/homeassistant/components/ws66i/__init__.py @@ -1,4 +1,5 @@ """The Soundavo WS66i 6-Zone Amplifier integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ws66i/config_flow.py b/homeassistant/components/ws66i/config_flow.py index 8f9e1f20682..5692ffcb81b 100644 --- a/homeassistant/components/ws66i/config_flow.py +++ b/homeassistant/components/ws66i/config_flow.py @@ -1,4 +1,5 @@ """Config flow for WS66i 6-Zone Amplifier integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ws66i/const.py b/homeassistant/components/ws66i/const.py index f824d991c1d..f56a100d4fe 100644 --- a/homeassistant/components/ws66i/const.py +++ b/homeassistant/components/ws66i/const.py @@ -1,4 +1,5 @@ """Constants for the Soundavo WS66i 6-Zone Amplifier Media Player component.""" + from datetime import timedelta DOMAIN = "ws66i" diff --git a/homeassistant/components/ws66i/coordinator.py b/homeassistant/components/ws66i/coordinator.py index be8ae3aad38..013e4d02b15 100644 --- a/homeassistant/components/ws66i/coordinator.py +++ b/homeassistant/components/ws66i/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for WS66i.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ws66i/media_player.py b/homeassistant/components/ws66i/media_player.py index 7119002cbc4..a2cd7ba471b 100644 --- a/homeassistant/components/ws66i/media_player.py +++ b/homeassistant/components/ws66i/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with WS66i 6 zone home audio controller.""" + from pyws66i import WS66i, ZoneStatus from homeassistant.components.media_player import ( diff --git a/homeassistant/components/ws66i/models.py b/homeassistant/components/ws66i/models.py index 84f481b9a4a..3c46d071790 100644 --- a/homeassistant/components/ws66i/models.py +++ b/homeassistant/components/ws66i/models.py @@ -1,4 +1,5 @@ """The ws66i integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/wsdot/sensor.py b/homeassistant/components/wsdot/sensor.py index d9df2b7abaf..14e21f79282 100644 --- a/homeassistant/components/wsdot/sensor.py +++ b/homeassistant/components/wsdot/sensor.py @@ -1,4 +1,5 @@ """Support for Washington State Department of Transportation (WSDOT) data.""" + from __future__ import annotations from datetime import datetime, timedelta, timezone diff --git a/homeassistant/components/wyoming/__init__.py b/homeassistant/components/wyoming/__init__.py index 88e490d6dc9..3ef71e2901b 100644 --- a/homeassistant/components/wyoming/__init__.py +++ b/homeassistant/components/wyoming/__init__.py @@ -1,4 +1,5 @@ """The Wyoming integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/wyoming/config_flow.py b/homeassistant/components/wyoming/config_flow.py index d1767c558a5..8461d9e83ac 100644 --- a/homeassistant/components/wyoming/config_flow.py +++ b/homeassistant/components/wyoming/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Wyoming integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/wyoming/devices.py b/homeassistant/components/wyoming/devices.py index 6865669fbf0..2ca66f3b21a 100644 --- a/homeassistant/components/wyoming/devices.py +++ b/homeassistant/components/wyoming/devices.py @@ -1,4 +1,5 @@ """Class to manage satellite devices.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/wyoming/error.py b/homeassistant/components/wyoming/error.py index 40b2e70ce69..0ccd35462b7 100644 --- a/homeassistant/components/wyoming/error.py +++ b/homeassistant/components/wyoming/error.py @@ -1,4 +1,5 @@ """Errors for the Wyoming integration.""" + from homeassistant.exceptions import HomeAssistantError diff --git a/homeassistant/components/wyoming/models.py b/homeassistant/components/wyoming/models.py index dce45d509eb..066af144d78 100644 --- a/homeassistant/components/wyoming/models.py +++ b/homeassistant/components/wyoming/models.py @@ -1,4 +1,5 @@ """Models for wyoming.""" + from dataclasses import dataclass from .data import WyomingService diff --git a/homeassistant/components/wyoming/stt.py b/homeassistant/components/wyoming/stt.py index 8a21ef051fc..227fa3a0eca 100644 --- a/homeassistant/components/wyoming/stt.py +++ b/homeassistant/components/wyoming/stt.py @@ -1,4 +1,5 @@ """Support for Wyoming speech-to-text services.""" + from collections.abc import AsyncIterable import logging diff --git a/homeassistant/components/wyoming/tts.py b/homeassistant/components/wyoming/tts.py index f024f925514..65ce4d942f1 100644 --- a/homeassistant/components/wyoming/tts.py +++ b/homeassistant/components/wyoming/tts.py @@ -1,4 +1,5 @@ """Support for Wyoming text-to-speech services.""" + from collections import defaultdict import io import logging diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py index b7e331e2199..5fbbd4f2ca6 100644 --- a/homeassistant/components/x10/light.py +++ b/homeassistant/components/x10/light.py @@ -1,4 +1,5 @@ """Support for X10 lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 37e11dd2693..3c9b5a44f04 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -1,4 +1,5 @@ """The xbox integration.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/xbox/api.py b/homeassistant/components/xbox/api.py index 04714afa33e..a0c2d4cfb16 100644 --- a/homeassistant/components/xbox/api.py +++ b/homeassistant/components/xbox/api.py @@ -1,4 +1,5 @@ """API for xbox bound to Home Assistant OAuth.""" + from aiohttp import ClientSession from xbox.webapi.authentication.manager import AuthenticationManager from xbox.webapi.authentication.models import OAuth2TokenResponse diff --git a/homeassistant/components/xbox/base_sensor.py b/homeassistant/components/xbox/base_sensor.py index 9aecb100df0..7769d639f44 100644 --- a/homeassistant/components/xbox/base_sensor.py +++ b/homeassistant/components/xbox/base_sensor.py @@ -1,4 +1,5 @@ """Base Sensor for the Xbox Integration.""" + from __future__ import annotations from yarl import URL diff --git a/homeassistant/components/xbox/binary_sensor.py b/homeassistant/components/xbox/binary_sensor.py index 3c262bce82e..ffd99cde30e 100644 --- a/homeassistant/components/xbox/binary_sensor.py +++ b/homeassistant/components/xbox/binary_sensor.py @@ -1,4 +1,5 @@ """Xbox friends binary sensors.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index 138b0bd612c..7dcfa90dcec 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + from __future__ import annotations from typing import NamedTuple diff --git a/homeassistant/components/xbox/media_player.py b/homeassistant/components/xbox/media_player.py index 060720338e8..f2cbc2e7c87 100644 --- a/homeassistant/components/xbox/media_player.py +++ b/homeassistant/components/xbox/media_player.py @@ -1,4 +1,5 @@ """Xbox Media Player Support.""" + from __future__ import annotations import re diff --git a/homeassistant/components/xbox/media_source.py b/homeassistant/components/xbox/media_source.py index 6a2def82a96..ea444ce1bc9 100644 --- a/homeassistant/components/xbox/media_source.py +++ b/homeassistant/components/xbox/media_source.py @@ -1,4 +1,5 @@ """Xbox Media Source Implementation.""" + from __future__ import annotations from contextlib import suppress diff --git a/homeassistant/components/xbox/remote.py b/homeassistant/components/xbox/remote.py index fdb4e80cf9e..a720025a1e6 100644 --- a/homeassistant/components/xbox/remote.py +++ b/homeassistant/components/xbox/remote.py @@ -1,4 +1,5 @@ """Xbox Remote support.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/xbox/sensor.py b/homeassistant/components/xbox/sensor.py index 77d52719c88..4e258399a5d 100644 --- a/homeassistant/components/xbox/sensor.py +++ b/homeassistant/components/xbox/sensor.py @@ -1,4 +1,5 @@ """Xbox friends binary sensors.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/xeoma/camera.py b/homeassistant/components/xeoma/camera.py index 31b80618d9e..7d6abde8535 100644 --- a/homeassistant/components/xeoma/camera.py +++ b/homeassistant/components/xeoma/camera.py @@ -1,4 +1,5 @@ """Support for Xeoma Cameras.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/xiaomi/camera.py b/homeassistant/components/xiaomi/camera.py index e9d686a6365..f3e850a7839 100644 --- a/homeassistant/components/xiaomi/camera.py +++ b/homeassistant/components/xiaomi/camera.py @@ -1,4 +1,5 @@ """Component providing support for Xiaomi Cameras.""" + from __future__ import annotations from ftplib import FTP, error_perm diff --git a/homeassistant/components/xiaomi/device_tracker.py b/homeassistant/components/xiaomi/device_tracker.py index f277060304a..f50d67a01b4 100644 --- a/homeassistant/components/xiaomi/device_tracker.py +++ b/homeassistant/components/xiaomi/device_tracker.py @@ -1,4 +1,5 @@ """Support for Xiaomi Mi routers.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/components/xiaomi_aqara/cover.py b/homeassistant/components/xiaomi_aqara/cover.py index b6de7189d83..64c9f6f208a 100644 --- a/homeassistant/components/xiaomi_aqara/cover.py +++ b/homeassistant/components/xiaomi_aqara/cover.py @@ -1,4 +1,5 @@ """Support for Xiaomi curtain.""" + from typing import Any from homeassistant.components.cover import ATTR_POSITION, CoverEntity diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index fea729b2b47..ae6cf2c4e8f 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -1,4 +1,5 @@ """Support for Xiaomi Aqara locks.""" + from __future__ import annotations from homeassistant.components.lock import LockEntity diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index e114adaa8c4..4b354a6e730 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -1,4 +1,5 @@ """Support for Xiaomi Aqara sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/xiaomi_ble/__init__.py b/homeassistant/components/xiaomi_ble/__init__.py index 3adafc6d05e..1402a15a56d 100644 --- a/homeassistant/components/xiaomi_ble/__init__.py +++ b/homeassistant/components/xiaomi_ble/__init__.py @@ -1,4 +1,5 @@ """The Xiaomi Bluetooth integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/xiaomi_ble/binary_sensor.py b/homeassistant/components/xiaomi_ble/binary_sensor.py index cd6f7b453bb..2e2c3251add 100644 --- a/homeassistant/components/xiaomi_ble/binary_sensor.py +++ b/homeassistant/components/xiaomi_ble/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Xiaomi binary sensors.""" + from __future__ import annotations from xiaomi_ble.parser import ( diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index 2c8be4954eb..8209c9565bd 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Xiaomi Bluetooth integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/xiaomi_ble/const.py b/homeassistant/components/xiaomi_ble/const.py index 5f9dea9eb45..a421dda7195 100644 --- a/homeassistant/components/xiaomi_ble/const.py +++ b/homeassistant/components/xiaomi_ble/const.py @@ -1,4 +1,5 @@ """Constants for the Xiaomi Bluetooth integration.""" + from __future__ import annotations from typing import Final, TypedDict diff --git a/homeassistant/components/xiaomi_ble/coordinator.py b/homeassistant/components/xiaomi_ble/coordinator.py index a935f3ea199..ef5212584d8 100644 --- a/homeassistant/components/xiaomi_ble/coordinator.py +++ b/homeassistant/components/xiaomi_ble/coordinator.py @@ -1,4 +1,5 @@ """The Xiaomi BLE integration.""" + from collections.abc import Callable, Coroutine from logging import Logger from typing import Any diff --git a/homeassistant/components/xiaomi_ble/device.py b/homeassistant/components/xiaomi_ble/device.py index 5714db4eadd..4f712a7a77c 100644 --- a/homeassistant/components/xiaomi_ble/device.py +++ b/homeassistant/components/xiaomi_ble/device.py @@ -1,4 +1,5 @@ """Support for Xioami BLE devices.""" + from __future__ import annotations from xiaomi_ble import DeviceKey diff --git a/homeassistant/components/xiaomi_ble/device_trigger.py b/homeassistant/components/xiaomi_ble/device_trigger.py index 8d281ddc8a9..eb345e91094 100644 --- a/homeassistant/components/xiaomi_ble/device_trigger.py +++ b/homeassistant/components/xiaomi_ble/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for Xiaomi BLE.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/xiaomi_ble/event.py b/homeassistant/components/xiaomi_ble/event.py index 2c1550dc5d7..4b0f9a61433 100644 --- a/homeassistant/components/xiaomi_ble/event.py +++ b/homeassistant/components/xiaomi_ble/event.py @@ -1,4 +1,5 @@ """Support for Xiaomi event entities.""" + from __future__ import annotations from dataclasses import replace diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index cdb7b3a8fd8..2bcc67e2668 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -1,4 +1,5 @@ """Support for xiaomi ble sensors.""" + from __future__ import annotations from xiaomi_ble import DeviceClass, SensorUpdate, Units diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 716d4a04fa7..aa9bbe5dcc6 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -1,4 +1,5 @@ """Support for Xiaomi Miio.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/xiaomi_miio/air_quality.py b/homeassistant/components/xiaomi_miio/air_quality.py index f9248ba5ff3..80dd751a98c 100644 --- a/homeassistant/components/xiaomi_miio/air_quality.py +++ b/homeassistant/components/xiaomi_miio/air_quality.py @@ -1,4 +1,5 @@ """Support for Xiaomi Mi Air Quality Monitor (PM2.5).""" + from collections.abc import Callable import logging diff --git a/homeassistant/components/xiaomi_miio/alarm_control_panel.py b/homeassistant/components/xiaomi_miio/alarm_control_panel.py index e92dd76be39..72530227e88 100644 --- a/homeassistant/components/xiaomi_miio/alarm_control_panel.py +++ b/homeassistant/components/xiaomi_miio/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Xiomi Gateway alarm control panels.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/xiaomi_miio/binary_sensor.py b/homeassistant/components/xiaomi_miio/binary_sensor.py index e1b06175493..7729ce27d29 100644 --- a/homeassistant/components/xiaomi_miio/binary_sensor.py +++ b/homeassistant/components/xiaomi_miio/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Xiaomi Miio binary sensors.""" + from __future__ import annotations from collections.abc import Callable, Iterable diff --git a/homeassistant/components/xiaomi_miio/button.py b/homeassistant/components/xiaomi_miio/button.py index 4ebbf34f295..38e6afa5ffb 100644 --- a/homeassistant/components/xiaomi_miio/button.py +++ b/homeassistant/components/xiaomi_miio/button.py @@ -1,4 +1,5 @@ """Support for Xiaomi buttons.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index f47a14ec89c..e2a129e147d 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Xiaomi Miio.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index ef9668dbee4..d643602531d 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -1,4 +1,5 @@ """Constants for the Xiaomi Miio component.""" + from miio.integrations.vacuum.roborock.vacuum import ( ROCKROBO_E2, ROCKROBO_S4, diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py index 977dc29ac42..af023e5e999 100644 --- a/homeassistant/components/xiaomi_miio/device_tracker.py +++ b/homeassistant/components/xiaomi_miio/device_tracker.py @@ -1,4 +1,5 @@ """Support for Xiaomi Mi WiFi Repeater 2.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/xiaomi_miio/diagnostics.py b/homeassistant/components/xiaomi_miio/diagnostics.py index eb823cd5abc..749bea45f96 100644 --- a/homeassistant/components/xiaomi_miio/diagnostics.py +++ b/homeassistant/components/xiaomi_miio/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Xiaomi Miio.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 30383426210..75533513b5e 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -1,4 +1,5 @@ """Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 8d198ae2a8f..cbbf12f9ab1 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -1,4 +1,5 @@ """Support for Xiaomi Philips Lights.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/xiaomi_miio/number.py b/homeassistant/components/xiaomi_miio/number.py index 3e952c1ab3f..71e92ad04a9 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -1,4 +1,5 @@ """Motor speed support for Xiaomi Mi Air Humidifier.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index ffae02a2ee4..c1234b77bbc 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -1,4 +1,5 @@ """Support for the Xiaomi IR Remote (Chuangmi IR).""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/xiaomi_miio/select.py b/homeassistant/components/xiaomi_miio/select.py index b70dab1921a..d87037cdd2d 100644 --- a/homeassistant/components/xiaomi_miio/select.py +++ b/homeassistant/components/xiaomi_miio/select.py @@ -1,4 +1,5 @@ """Support led_brightness for Mi Air Humidifier.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index a8435d6a8a1..a7b3ff176bd 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -1,4 +1,5 @@ """Support for Xiaomi Mi Air Quality Monitor (PM2.5) and Humidifier.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 68714f1a6ff..85e9e77e120 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -1,4 +1,5 @@ """Support for Xiaomi Smart WiFi Socket and Smart Power Strip.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 73e2e54b62f..41f2c2386e1 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -1,4 +1,5 @@ """Support for the Xiaomi vacuum cleaner robot.""" + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/xiaomi_tv/media_player.py b/homeassistant/components/xiaomi_tv/media_player.py index 82444c26f65..da692d21bfc 100644 --- a/homeassistant/components/xiaomi_tv/media_player.py +++ b/homeassistant/components/xiaomi_tv/media_player.py @@ -1,4 +1,5 @@ """Add support for the Xiaomi TVs.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/xmpp/notify.py b/homeassistant/components/xmpp/notify.py index 0150e761838..50797536aee 100644 --- a/homeassistant/components/xmpp/notify.py +++ b/homeassistant/components/xmpp/notify.py @@ -1,4 +1,5 @@ """Jabber (XMPP) notification service.""" + from __future__ import annotations from concurrent.futures import TimeoutError as FutTimeoutError diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py index 949d2330347..e594f32adff 100644 --- a/homeassistant/components/xs1/climate.py +++ b/homeassistant/components/xs1/climate.py @@ -1,4 +1,5 @@ """Support for XS1 climate devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/xs1/sensor.py b/homeassistant/components/xs1/sensor.py index 4855c8b8dcf..e98fd33743b 100644 --- a/homeassistant/components/xs1/sensor.py +++ b/homeassistant/components/xs1/sensor.py @@ -1,4 +1,5 @@ """Support for XS1 sensors.""" + from __future__ import annotations from xs1_api_client.api_constants import ActuatorType diff --git a/homeassistant/components/xs1/switch.py b/homeassistant/components/xs1/switch.py index 0b231a0303c..1a2113973e3 100644 --- a/homeassistant/components/xs1/switch.py +++ b/homeassistant/components/xs1/switch.py @@ -1,4 +1,5 @@ """Support for XS1 switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yale_smart_alarm/__init__.py b/homeassistant/components/yale_smart_alarm/__init__.py index dac5a98d738..94728ee020c 100644 --- a/homeassistant/components/yale_smart_alarm/__init__.py +++ b/homeassistant/components/yale_smart_alarm/__init__.py @@ -1,4 +1,5 @@ """The yale_smart_alarm component.""" + from __future__ import annotations from homeassistant.components.lock import CONF_DEFAULT_CODE, DOMAIN as LOCK_DOMAIN diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py index 31851ad3ceb..e4729622a64 100644 --- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py +++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py @@ -1,4 +1,5 @@ """Support for Yale Alarm.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/yale_smart_alarm/binary_sensor.py b/homeassistant/components/yale_smart_alarm/binary_sensor.py index 2aad449a3f7..87101418a14 100644 --- a/homeassistant/components/yale_smart_alarm/binary_sensor.py +++ b/homeassistant/components/yale_smart_alarm/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensors for Yale Alarm.""" + from __future__ import annotations from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/yale_smart_alarm/button.py b/homeassistant/components/yale_smart_alarm/button.py index 4521c4925b1..54fc905d1aa 100644 --- a/homeassistant/components/yale_smart_alarm/button.py +++ b/homeassistant/components/yale_smart_alarm/button.py @@ -1,4 +1,5 @@ """Support for Yale Smart Alarm button.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/yale_smart_alarm/config_flow.py b/homeassistant/components/yale_smart_alarm/config_flow.py index 6cdf7a298f3..644160a8d93 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -1,4 +1,5 @@ """Adds config flow for Yale Smart Alarm integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/yale_smart_alarm/coordinator.py b/homeassistant/components/yale_smart_alarm/coordinator.py index e1cff8fb2a5..642704b637d 100644 --- a/homeassistant/components/yale_smart_alarm/coordinator.py +++ b/homeassistant/components/yale_smart_alarm/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the Yale integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/yale_smart_alarm/diagnostics.py b/homeassistant/components/yale_smart_alarm/diagnostics.py index c650ff5f5ed..99ec977de20 100644 --- a/homeassistant/components/yale_smart_alarm/diagnostics.py +++ b/homeassistant/components/yale_smart_alarm/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Yale Smart Alarm.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yale_smart_alarm/lock.py b/homeassistant/components/yale_smart_alarm/lock.py index c5a9bb79ba8..8acfecbf034 100644 --- a/homeassistant/components/yale_smart_alarm/lock.py +++ b/homeassistant/components/yale_smart_alarm/lock.py @@ -1,4 +1,5 @@ """Lock for Yale Alarm.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/homeassistant/components/yalexs_ble/__init__.py b/homeassistant/components/yalexs_ble/__init__.py index 5082029af29..8c9c5176003 100644 --- a/homeassistant/components/yalexs_ble/__init__.py +++ b/homeassistant/components/yalexs_ble/__init__.py @@ -1,4 +1,5 @@ """The Yale Access Bluetooth integration.""" + from __future__ import annotations from yalexs_ble import ( diff --git a/homeassistant/components/yalexs_ble/binary_sensor.py b/homeassistant/components/yalexs_ble/binary_sensor.py index 8213baf33aa..a127aa66b93 100644 --- a/homeassistant/components/yalexs_ble/binary_sensor.py +++ b/homeassistant/components/yalexs_ble/binary_sensor.py @@ -1,4 +1,5 @@ """Support for yalexs ble binary sensors.""" + from __future__ import annotations from yalexs_ble import ConnectionInfo, DoorStatus, LockInfo, LockState diff --git a/homeassistant/components/yalexs_ble/config_flow.py b/homeassistant/components/yalexs_ble/config_flow.py index 01e55c66ab1..3ec7f675d7a 100644 --- a/homeassistant/components/yalexs_ble/config_flow.py +++ b/homeassistant/components/yalexs_ble/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Yale Access Bluetooth integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/yalexs_ble/entity.py b/homeassistant/components/yalexs_ble/entity.py index 9135f0c0896..afa80b8e313 100644 --- a/homeassistant/components/yalexs_ble/entity.py +++ b/homeassistant/components/yalexs_ble/entity.py @@ -1,4 +1,5 @@ """The yalexs_ble integration entities.""" + from __future__ import annotations from yalexs_ble import ConnectionInfo, LockInfo, LockState diff --git a/homeassistant/components/yalexs_ble/lock.py b/homeassistant/components/yalexs_ble/lock.py index f6fa1917d7e..9f508b1a8ee 100644 --- a/homeassistant/components/yalexs_ble/lock.py +++ b/homeassistant/components/yalexs_ble/lock.py @@ -1,4 +1,5 @@ """Support for Yale Access Bluetooth locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yalexs_ble/models.py b/homeassistant/components/yalexs_ble/models.py index 3b83b52cf73..cc6b3697e72 100644 --- a/homeassistant/components/yalexs_ble/models.py +++ b/homeassistant/components/yalexs_ble/models.py @@ -1,4 +1,5 @@ """The yalexs_ble integration models.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/yalexs_ble/sensor.py b/homeassistant/components/yalexs_ble/sensor.py index da698d1b501..e400111ddf5 100644 --- a/homeassistant/components/yalexs_ble/sensor.py +++ b/homeassistant/components/yalexs_ble/sensor.py @@ -1,4 +1,5 @@ """Support for yalexs ble sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/yalexs_ble/util.py b/homeassistant/components/yalexs_ble/util.py index e361e141a42..328aa2b6375 100644 --- a/homeassistant/components/yalexs_ble/util.py +++ b/homeassistant/components/yalexs_ble/util.py @@ -1,4 +1,5 @@ """The yalexs_ble integration models.""" + from __future__ import annotations import platform diff --git a/homeassistant/components/yamaha/media_player.py b/homeassistant/components/yamaha/media_player.py index e2658c21f37..c648994c38d 100644 --- a/homeassistant/components/yamaha/media_player.py +++ b/homeassistant/components/yamaha/media_player.py @@ -1,4 +1,5 @@ """Support for Yamaha Receivers.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yamaha_musiccast/__init__.py b/homeassistant/components/yamaha_musiccast/__init__.py index 5242aa90819..9c7e6d07b82 100644 --- a/homeassistant/components/yamaha_musiccast/__init__.py +++ b/homeassistant/components/yamaha_musiccast/__init__.py @@ -1,4 +1,5 @@ """The MusicCast integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/yamaha_musiccast/config_flow.py b/homeassistant/components/yamaha_musiccast/config_flow.py index 078efdc9471..34d352b790e 100644 --- a/homeassistant/components/yamaha_musiccast/config_flow.py +++ b/homeassistant/components/yamaha_musiccast/config_flow.py @@ -1,4 +1,5 @@ """Config flow for MusicCast.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index 42549fb20d9..399d645ff20 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -1,4 +1,5 @@ """Implementation of the musiccast media player.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/yamaha_musiccast/number.py b/homeassistant/components/yamaha_musiccast/number.py index 105cb0edb3a..f820a31c4be 100644 --- a/homeassistant/components/yamaha_musiccast/number.py +++ b/homeassistant/components/yamaha_musiccast/number.py @@ -1,4 +1,5 @@ """Number entities for musiccast.""" + from __future__ import annotations from aiomusiccast.capabilities import NumberSetter diff --git a/homeassistant/components/yamaha_musiccast/select.py b/homeassistant/components/yamaha_musiccast/select.py index 8ef9df1ba2f..c139dfb71fc 100644 --- a/homeassistant/components/yamaha_musiccast/select.py +++ b/homeassistant/components/yamaha_musiccast/select.py @@ -1,4 +1,5 @@ """The select entities for musiccast.""" + from __future__ import annotations from aiomusiccast.capabilities import OptionSetter diff --git a/homeassistant/components/yamaha_musiccast/switch.py b/homeassistant/components/yamaha_musiccast/switch.py index f48e7c11713..4402ddc1c38 100644 --- a/homeassistant/components/yamaha_musiccast/switch.py +++ b/homeassistant/components/yamaha_musiccast/switch.py @@ -1,4 +1,5 @@ """The switch entities for musiccast.""" + from typing import Any from aiomusiccast.capabilities import BinarySetter diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index 1fbae6c88a6..bcef8248aa3 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -1,4 +1,5 @@ """Service for obtaining information about closer bus from Transport Yandex Service.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/yardian/__init__.py b/homeassistant/components/yardian/__init__.py index d6cee9015b8..eafdc8e68db 100644 --- a/homeassistant/components/yardian/__init__.py +++ b/homeassistant/components/yardian/__init__.py @@ -1,4 +1,5 @@ """The Yardian integration.""" + from __future__ import annotations from pyyardian import AsyncYardianClient diff --git a/homeassistant/components/yardian/config_flow.py b/homeassistant/components/yardian/config_flow.py index 83ff1ef5c81..e23ca536d4e 100644 --- a/homeassistant/components/yardian/config_flow.py +++ b/homeassistant/components/yardian/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Yardian integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yardian/switch.py b/homeassistant/components/yardian/switch.py index 5c4d0b93e74..549331b6b5f 100644 --- a/homeassistant/components/yardian/switch.py +++ b/homeassistant/components/yardian/switch.py @@ -1,4 +1,5 @@ """Support for Yardian integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index f77e4d08dc9..0ed75318ac7 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -1,4 +1,5 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yeelight/config_flow.py b/homeassistant/components/yeelight/config_flow.py index afa2eff64c4..d7bf4e25996 100644 --- a/homeassistant/components/yeelight/config_flow.py +++ b/homeassistant/components/yeelight/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Yeelight integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yeelight/device.py b/homeassistant/components/yeelight/device.py index bb5159c0b3b..c42fd072728 100644 --- a/homeassistant/components/yeelight/device.py +++ b/homeassistant/components/yeelight/device.py @@ -1,4 +1,5 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yeelight/entity.py b/homeassistant/components/yeelight/entity.py index 8056ea085b7..c0bc45f6a51 100644 --- a/homeassistant/components/yeelight/entity.py +++ b/homeassistant/components/yeelight/entity.py @@ -1,4 +1,5 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index abc17b8abd8..a9a35cb71b7 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -1,4 +1,5 @@ """Light platform support for yeelight.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/yeelight/scanner.py b/homeassistant/components/yeelight/scanner.py index 8fa41bb92b1..c98ca625029 100644 --- a/homeassistant/components/yeelight/scanner.py +++ b/homeassistant/components/yeelight/scanner.py @@ -1,4 +1,5 @@ """Support for Xiaomi Yeelight WiFi color bulb.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index c50c176c157..45b662846d5 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -1,4 +1,5 @@ """Support for Yeelight Sunflower color bulbs (not Yeelight Blue or WiFi).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yi/camera.py b/homeassistant/components/yi/camera.py index 632260a899c..fbc3294e25d 100644 --- a/homeassistant/components/yi/camera.py +++ b/homeassistant/components/yi/camera.py @@ -1,4 +1,5 @@ """Support for Xiaomi Cameras (HiSilicon Hi3518e V200).""" + from __future__ import annotations import logging diff --git a/homeassistant/components/yolink/__init__.py b/homeassistant/components/yolink/__init__.py index 270bd550038..f1d2ec6602b 100644 --- a/homeassistant/components/yolink/__init__.py +++ b/homeassistant/components/yolink/__init__.py @@ -1,4 +1,5 @@ """The yolink integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/yolink/api.py b/homeassistant/components/yolink/api.py index 0991baed23f..fd6f8832faf 100644 --- a/homeassistant/components/yolink/api.py +++ b/homeassistant/components/yolink/api.py @@ -1,4 +1,5 @@ """API for yolink bound to Home Assistant OAuth.""" + from aiohttp import ClientSession from yolink.auth_mgr import YoLinkAuthMgr diff --git a/homeassistant/components/yolink/binary_sensor.py b/homeassistant/components/yolink/binary_sensor.py index 51c66be3e5f..ba28089546f 100644 --- a/homeassistant/components/yolink/binary_sensor.py +++ b/homeassistant/components/yolink/binary_sensor.py @@ -1,4 +1,5 @@ """YoLink BinarySensor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/yolink/climate.py b/homeassistant/components/yolink/climate.py index a1e2fdd90a2..21e0a71ebcb 100644 --- a/homeassistant/components/yolink/climate.py +++ b/homeassistant/components/yolink/climate.py @@ -1,4 +1,5 @@ """YoLink Thermostat.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yolink/config_flow.py b/homeassistant/components/yolink/config_flow.py index f22c50c3345..abdac696248 100644 --- a/homeassistant/components/yolink/config_flow.py +++ b/homeassistant/components/yolink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for yolink.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/yolink/coordinator.py b/homeassistant/components/yolink/coordinator.py index f2c942caab9..b7db36541b1 100644 --- a/homeassistant/components/yolink/coordinator.py +++ b/homeassistant/components/yolink/coordinator.py @@ -1,4 +1,5 @@ """YoLink DataUpdateCoordinator.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/yolink/cover.py b/homeassistant/components/yolink/cover.py index 6cc1ea3acd6..03e36a2ba29 100644 --- a/homeassistant/components/yolink/cover.py +++ b/homeassistant/components/yolink/cover.py @@ -1,4 +1,5 @@ """YoLink Garage Door.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yolink/device_trigger.py b/homeassistant/components/yolink/device_trigger.py index aac860c6a27..d3176e146d2 100644 --- a/homeassistant/components/yolink/device_trigger.py +++ b/homeassistant/components/yolink/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for YoLink.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yolink/entity.py b/homeassistant/components/yolink/entity.py index 0221bd94a7e..d9ca2968493 100644 --- a/homeassistant/components/yolink/entity.py +++ b/homeassistant/components/yolink/entity.py @@ -1,4 +1,5 @@ """Support for YoLink Device.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/yolink/light.py b/homeassistant/components/yolink/light.py index 248a42df60c..e07d17f7d74 100644 --- a/homeassistant/components/yolink/light.py +++ b/homeassistant/components/yolink/light.py @@ -1,4 +1,5 @@ """YoLink Dimmer.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yolink/lock.py b/homeassistant/components/yolink/lock.py index 3b0f68c175c..177a8808de1 100644 --- a/homeassistant/components/yolink/lock.py +++ b/homeassistant/components/yolink/lock.py @@ -1,4 +1,5 @@ """YoLink Lock.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index 80cbaf27bb3..b69e29db031 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -1,4 +1,5 @@ """YoLink Sensor.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/yolink/siren.py b/homeassistant/components/yolink/siren.py index 4a35e9506e9..eca6a958108 100644 --- a/homeassistant/components/yolink/siren.py +++ b/homeassistant/components/yolink/siren.py @@ -1,4 +1,5 @@ """YoLink Siren.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/yolink/switch.py b/homeassistant/components/yolink/switch.py index 920ebd245cf..9e1b8eba9db 100644 --- a/homeassistant/components/yolink/switch.py +++ b/homeassistant/components/yolink/switch.py @@ -1,4 +1,5 @@ """YoLink Switch.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/youless/__init__.py b/homeassistant/components/youless/__init__.py index 5724f417a7f..a968d052922 100644 --- a/homeassistant/components/youless/__init__.py +++ b/homeassistant/components/youless/__init__.py @@ -1,4 +1,5 @@ """The youless integration.""" + from datetime import timedelta import logging from urllib.error import URLError diff --git a/homeassistant/components/youless/config_flow.py b/homeassistant/components/youless/config_flow.py index a6a993aa72a..d13d2ed076f 100644 --- a/homeassistant/components/youless/config_flow.py +++ b/homeassistant/components/youless/config_flow.py @@ -1,4 +1,5 @@ """Config flow for youless integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/youless/sensor.py b/homeassistant/components/youless/sensor.py index 36175ae9cf3..81cd8b384d2 100644 --- a/homeassistant/components/youless/sensor.py +++ b/homeassistant/components/youless/sensor.py @@ -1,4 +1,5 @@ """The sensor entity for the Youless integration.""" + from __future__ import annotations from youless_api import YoulessAPI diff --git a/homeassistant/components/youtube/__init__.py b/homeassistant/components/youtube/__init__.py index 46c3b0d1902..8460a105fcb 100644 --- a/homeassistant/components/youtube/__init__.py +++ b/homeassistant/components/youtube/__init__.py @@ -1,4 +1,5 @@ """Support for YouTube.""" + from __future__ import annotations from aiohttp.client_exceptions import ClientError, ClientResponseError diff --git a/homeassistant/components/youtube/api.py b/homeassistant/components/youtube/api.py index b98169e3589..beb0a9a70dc 100644 --- a/homeassistant/components/youtube/api.py +++ b/homeassistant/components/youtube/api.py @@ -1,4 +1,5 @@ """API for YouTube bound to Home Assistant OAuth.""" + from youtubeaio.types import AuthScope from youtubeaio.youtube import YouTube diff --git a/homeassistant/components/youtube/application_credentials.py b/homeassistant/components/youtube/application_credentials.py index ba6188d9a3d..72c1b9a72d4 100644 --- a/homeassistant/components/youtube/application_credentials.py +++ b/homeassistant/components/youtube/application_credentials.py @@ -1,4 +1,5 @@ """application_credentials platform for YouTube.""" + from homeassistant.components.application_credentials import AuthorizationServer from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/youtube/config_flow.py b/homeassistant/components/youtube/config_flow.py index 89ee02f9c90..025ed8780e6 100644 --- a/homeassistant/components/youtube/config_flow.py +++ b/homeassistant/components/youtube/config_flow.py @@ -1,4 +1,5 @@ """Config flow for YouTube integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/youtube/coordinator.py b/homeassistant/components/youtube/coordinator.py index 07420233baf..4599342c84d 100644 --- a/homeassistant/components/youtube/coordinator.py +++ b/homeassistant/components/youtube/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the YouTube integration.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/youtube/diagnostics.py b/homeassistant/components/youtube/diagnostics.py index 7cd32d50d27..9a898b7e2de 100644 --- a/homeassistant/components/youtube/diagnostics.py +++ b/homeassistant/components/youtube/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for YouTube.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/youtube/entity.py b/homeassistant/components/youtube/entity.py index 6f7f0b28dd2..698b14fa6a7 100644 --- a/homeassistant/components/youtube/entity.py +++ b/homeassistant/components/youtube/entity.py @@ -1,4 +1,5 @@ """Entity representing a YouTube account.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo diff --git a/homeassistant/components/youtube/sensor.py b/homeassistant/components/youtube/sensor.py index e6205ad1646..56c3f960bdf 100644 --- a/homeassistant/components/youtube/sensor.py +++ b/homeassistant/components/youtube/sensor.py @@ -1,4 +1,5 @@ """Support for YouTube Sensors.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index a59465bf400..edc0e28cce2 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -1,4 +1,5 @@ """Support for Zabbix.""" + from contextlib import suppress import json import logging diff --git a/homeassistant/components/zabbix/sensor.py b/homeassistant/components/zabbix/sensor.py index 2fd2d48faba..eaa06367408 100644 --- a/homeassistant/components/zabbix/sensor.py +++ b/homeassistant/components/zabbix/sensor.py @@ -1,4 +1,5 @@ """Support for Zabbix sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zamg/__init__.py b/homeassistant/components/zamg/__init__.py index 0e57bef64ff..f6241e53fbe 100644 --- a/homeassistant/components/zamg/__init__.py +++ b/homeassistant/components/zamg/__init__.py @@ -1,4 +1,5 @@ """The zamg component.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/zamg/config_flow.py b/homeassistant/components/zamg/config_flow.py index 630c9e747c3..24045ba8f4e 100644 --- a/homeassistant/components/zamg/config_flow.py +++ b/homeassistant/components/zamg/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for the zamg integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zamg/coordinator.py b/homeassistant/components/zamg/coordinator.py index f785524e866..d53c743f500 100644 --- a/homeassistant/components/zamg/coordinator.py +++ b/homeassistant/components/zamg/coordinator.py @@ -1,4 +1,5 @@ """Data Update coordinator for ZAMG weather data.""" + from __future__ import annotations from zamg import ZamgData as ZamgDevice diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index adc07212a5f..4e1a1d49bec 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -1,4 +1,5 @@ """Sensor for the zamg integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/zamg/weather.py b/homeassistant/components/zamg/weather.py index e855bde29d8..241b2232eeb 100644 --- a/homeassistant/components/zamg/weather.py +++ b/homeassistant/components/zamg/weather.py @@ -1,4 +1,5 @@ """Sensor for the zamg integration.""" + from __future__ import annotations from homeassistant.components.weather import WeatherEntity diff --git a/homeassistant/components/zengge/light.py b/homeassistant/components/zengge/light.py index 2d4ba4614e6..5de4f3fdce3 100644 --- a/homeassistant/components/zengge/light.py +++ b/homeassistant/components/zengge/light.py @@ -1,4 +1,5 @@ """Support for Zengge lights.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index d580f097179..775353a29f6 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,4 +1,5 @@ """Support for exposing Home Assistant via Zeroconf.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/components/zerproc/__init__.py b/homeassistant/components/zerproc/__init__.py index edd41d1a8e3..953720038cc 100644 --- a/homeassistant/components/zerproc/__init__.py +++ b/homeassistant/components/zerproc/__init__.py @@ -1,4 +1,5 @@ """Zerproc lights integration.""" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/zerproc/light.py b/homeassistant/components/zerproc/light.py index 94a99743cdc..71bb38dd80f 100644 --- a/homeassistant/components/zerproc/light.py +++ b/homeassistant/components/zerproc/light.py @@ -1,4 +1,5 @@ """Zerproc light platform.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/zestimate/sensor.py b/homeassistant/components/zestimate/sensor.py index 9b520c46819..8bbda7de73a 100644 --- a/homeassistant/components/zestimate/sensor.py +++ b/homeassistant/components/zestimate/sensor.py @@ -1,4 +1,5 @@ """Support for zestimate data from zillow.com.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/zeversolar/__init__.py b/homeassistant/components/zeversolar/__init__.py index cff5cf413e5..cb48579367b 100644 --- a/homeassistant/components/zeversolar/__init__.py +++ b/homeassistant/components/zeversolar/__init__.py @@ -1,4 +1,5 @@ """The Zeversolar integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/zeversolar/config_flow.py b/homeassistant/components/zeversolar/config_flow.py index 376fc43bcec..e4deae47c8f 100644 --- a/homeassistant/components/zeversolar/config_flow.py +++ b/homeassistant/components/zeversolar/config_flow.py @@ -1,4 +1,5 @@ """Config flow for zeversolar integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zeversolar/coordinator.py b/homeassistant/components/zeversolar/coordinator.py index 554fe195eab..9f6ff49eaf8 100644 --- a/homeassistant/components/zeversolar/coordinator.py +++ b/homeassistant/components/zeversolar/coordinator.py @@ -1,4 +1,5 @@ """Zeversolar coordinator.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/components/zeversolar/entity.py b/homeassistant/components/zeversolar/entity.py index 77ae5ee61f8..18ac4dcde32 100644 --- a/homeassistant/components/zeversolar/entity.py +++ b/homeassistant/components/zeversolar/entity.py @@ -1,4 +1,5 @@ """Base Entity for Zeversolar sensors.""" + from __future__ import annotations from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/zeversolar/sensor.py b/homeassistant/components/zeversolar/sensor.py index ca09e5773b0..69dd525f7f7 100644 --- a/homeassistant/components/zeversolar/sensor.py +++ b/homeassistant/components/zeversolar/sensor.py @@ -1,4 +1,5 @@ """Support for the Zeversolar platform.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zha/alarm_control_panel.py b/homeassistant/components/zha/alarm_control_panel.py index 7f1f6a85d15..7750e7f280d 100644 --- a/homeassistant/components/zha/alarm_control_panel.py +++ b/homeassistant/components/zha/alarm_control_panel.py @@ -1,4 +1,5 @@ """Alarm control panels on Zigbee Home Automation networks.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index aed0a16a681..3333bf6bb3e 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -1,4 +1,5 @@ """Binary sensors on Zigbee Home Automation networks.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index 2c0028cd3d1..48b27ee6892 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -1,4 +1,5 @@ """Support for ZHA button.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index cbc759e7008..61c5f28ca8f 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -3,6 +3,7 @@ For more details on this platform, please refer to the documentation at https://home-assistant.io/components/zha.climate/ """ + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index 1fbe7b689ca..7ecf3357334 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -1,4 +1,5 @@ """Config flow for ZHA.""" + from __future__ import annotations import collections diff --git a/homeassistant/components/zha/core/cluster_handlers/__init__.py b/homeassistant/components/zha/core/cluster_handlers/__init__.py index 1f7485d4922..9e483f1700a 100644 --- a/homeassistant/components/zha/core/cluster_handlers/__init__.py +++ b/homeassistant/components/zha/core/cluster_handlers/__init__.py @@ -1,4 +1,5 @@ """Cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine, Iterator diff --git a/homeassistant/components/zha/core/cluster_handlers/closures.py b/homeassistant/components/zha/core/cluster_handlers/closures.py index 879765aec3c..c57ad507317 100644 --- a/homeassistant/components/zha/core/cluster_handlers/closures.py +++ b/homeassistant/components/zha/core/cluster_handlers/closures.py @@ -1,4 +1,5 @@ """Closures cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zha/core/cluster_handlers/general.py b/homeassistant/components/zha/core/cluster_handlers/general.py index d2927f6d028..478f41da3b7 100644 --- a/homeassistant/components/zha/core/cluster_handlers/general.py +++ b/homeassistant/components/zha/core/cluster_handlers/general.py @@ -1,4 +1,5 @@ """General cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations from collections.abc import Coroutine diff --git a/homeassistant/components/zha/core/cluster_handlers/helpers.py b/homeassistant/components/zha/core/cluster_handlers/helpers.py index f4444f3995c..46557bf23a8 100644 --- a/homeassistant/components/zha/core/cluster_handlers/helpers.py +++ b/homeassistant/components/zha/core/cluster_handlers/helpers.py @@ -1,4 +1,5 @@ """Helpers for use with ZHA Zigbee cluster handlers.""" + from . import ClusterHandler diff --git a/homeassistant/components/zha/core/cluster_handlers/homeautomation.py b/homeassistant/components/zha/core/cluster_handlers/homeautomation.py index bb7b96d367e..b287cb98f6a 100644 --- a/homeassistant/components/zha/core/cluster_handlers/homeautomation.py +++ b/homeassistant/components/zha/core/cluster_handlers/homeautomation.py @@ -1,4 +1,5 @@ """Home automation cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations import enum diff --git a/homeassistant/components/zha/core/cluster_handlers/hvac.py b/homeassistant/components/zha/core/cluster_handlers/hvac.py index 4c03d31135e..d455ade4e66 100644 --- a/homeassistant/components/zha/core/cluster_handlers/hvac.py +++ b/homeassistant/components/zha/core/cluster_handlers/hvac.py @@ -3,6 +3,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zha/core/cluster_handlers/lighting.py b/homeassistant/components/zha/core/cluster_handlers/lighting.py index bb3ac3c80e3..f19ad311f9e 100644 --- a/homeassistant/components/zha/core/cluster_handlers/lighting.py +++ b/homeassistant/components/zha/core/cluster_handlers/lighting.py @@ -1,4 +1,5 @@ """Lighting cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations from zigpy.zcl.clusters.lighting import Ballast, Color diff --git a/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py b/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py index 608a256606f..dc8af821724 100644 --- a/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py +++ b/homeassistant/components/zha/core/cluster_handlers/manufacturerspecific.py @@ -1,4 +1,5 @@ """Manufacturer specific cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zha/core/cluster_handlers/measurement.py b/homeassistant/components/zha/core/cluster_handlers/measurement.py index be079328228..768de8c4c73 100644 --- a/homeassistant/components/zha/core/cluster_handlers/measurement.py +++ b/homeassistant/components/zha/core/cluster_handlers/measurement.py @@ -1,4 +1,5 @@ """Measurement cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/components/zha/core/cluster_handlers/protocol.py b/homeassistant/components/zha/core/cluster_handlers/protocol.py index 14f01a55b6a..e1e3d7a5413 100644 --- a/homeassistant/components/zha/core/cluster_handlers/protocol.py +++ b/homeassistant/components/zha/core/cluster_handlers/protocol.py @@ -1,4 +1,5 @@ """Protocol cluster handlers module for Zigbee Home Automation.""" + from zigpy.zcl.clusters.protocol import ( AnalogInputExtended, AnalogInputRegular, diff --git a/homeassistant/components/zha/core/cluster_handlers/security.py b/homeassistant/components/zha/core/cluster_handlers/security.py index c37fdc43766..8ebe09cef03 100644 --- a/homeassistant/components/zha/core/cluster_handlers/security.py +++ b/homeassistant/components/zha/core/cluster_handlers/security.py @@ -3,6 +3,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zha/core/cluster_handlers/smartenergy.py b/homeassistant/components/zha/core/cluster_handlers/smartenergy.py index 65a02a01e02..d167b8b1752 100644 --- a/homeassistant/components/zha/core/cluster_handlers/smartenergy.py +++ b/homeassistant/components/zha/core/cluster_handlers/smartenergy.py @@ -1,4 +1,5 @@ """Smart energy cluster handlers module for Zigbee Home Automation.""" + from __future__ import annotations import enum diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index fd54351739e..8d56652d8ee 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -1,4 +1,5 @@ """All constants related to the ZHA component.""" + from __future__ import annotations import enum diff --git a/homeassistant/components/zha/core/decorators.py b/homeassistant/components/zha/core/decorators.py index 192f6848989..b8e15024811 100644 --- a/homeassistant/components/zha/core/decorators.py +++ b/homeassistant/components/zha/core/decorators.py @@ -1,4 +1,5 @@ """Decorators for ZHA core registries.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index f1b7ec60728..fe19e1662d0 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -1,4 +1,5 @@ """Device for Zigbee Home Automation.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 221c601827e..98d37f1a7db 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -1,4 +1,5 @@ """Device discovery functions for Zigbee Home Automation.""" + from __future__ import annotations from collections import Counter diff --git a/homeassistant/components/zha/core/endpoint.py b/homeassistant/components/zha/core/endpoint.py index 37a2c951a7f..b0d617eb2c2 100644 --- a/homeassistant/components/zha/core/endpoint.py +++ b/homeassistant/components/zha/core/endpoint.py @@ -1,4 +1,5 @@ """Representation of a Zigbee endpoint for zha.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 14fd329f1bc..62112f70584 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -1,4 +1,5 @@ """Virtual gateway for Zigbee Home Automation.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index a62c00e7106..f57f5df1861 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -1,4 +1,5 @@ """Group for Zigbee Home Automation.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 5506ffb8289..22efe995954 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -3,6 +3,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/integrations/zha/ """ + from __future__ import annotations import binascii diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index b302116694d..10e63dd3590 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -1,4 +1,5 @@ """Mapping registries for Zigbee Home Automation.""" + from __future__ import annotations import collections diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index d94a2f907d1..718b6fed3a2 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -1,4 +1,5 @@ """Support for ZHA covers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/device_action.py b/homeassistant/components/zha/device_action.py index d393cfb1471..076cb1d420e 100644 --- a/homeassistant/components/zha/device_action.py +++ b/homeassistant/components/zha/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for ZHA devices.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zha/device_tracker.py b/homeassistant/components/zha/device_tracker.py index ea27c58eb19..9c96fd0e346 100644 --- a/homeassistant/components/zha/device_tracker.py +++ b/homeassistant/components/zha/device_tracker.py @@ -1,4 +1,5 @@ """Support for the ZHA platform.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/diagnostics.py b/homeassistant/components/zha/diagnostics.py index 57088818c66..fff816777c0 100644 --- a/homeassistant/components/zha/diagnostics.py +++ b/homeassistant/components/zha/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for ZHA.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 3f127c74c0e..f03da7a5fd6 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -1,4 +1,5 @@ """Entity for Zigbee Home Automation.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/fan.py b/homeassistant/components/zha/fan.py index 7364aed0d1b..3677befb76e 100644 --- a/homeassistant/components/zha/fan.py +++ b/homeassistant/components/zha/fan.py @@ -1,4 +1,5 @@ """Fans on Zigbee Home Automation networks.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index aa117c7ef9b..8d65899707e 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -1,4 +1,5 @@ """Lights on Zigbee Home Automation networks.""" + from __future__ import annotations from collections import Counter diff --git a/homeassistant/components/zha/logbook.py b/homeassistant/components/zha/logbook.py index ce9c1f1227b..e63ef565824 100644 --- a/homeassistant/components/zha/logbook.py +++ b/homeassistant/components/zha/logbook.py @@ -1,4 +1,5 @@ """Describe ZHA logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index c452752f14b..ef8f8287e1e 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -1,4 +1,5 @@ """Support for ZHA AnalogOutput cluster.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/radio_manager.py b/homeassistant/components/zha/radio_manager.py index d3ca03de8d8..44b7304c58e 100644 --- a/homeassistant/components/zha/radio_manager.py +++ b/homeassistant/components/zha/radio_manager.py @@ -1,4 +1,5 @@ """Config flow for ZHA.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/repairs/__init__.py b/homeassistant/components/zha/repairs/__init__.py index a3c2ea6f292..3d8f2553baa 100644 --- a/homeassistant/components/zha/repairs/__init__.py +++ b/homeassistant/components/zha/repairs/__init__.py @@ -1,4 +1,5 @@ """ZHA repairs for common environmental and device problems.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/zha/repairs/network_settings_inconsistent.py b/homeassistant/components/zha/repairs/network_settings_inconsistent.py index 0a478f4b36a..2598ff8f98a 100644 --- a/homeassistant/components/zha/repairs/network_settings_inconsistent.py +++ b/homeassistant/components/zha/repairs/network_settings_inconsistent.py @@ -1,4 +1,5 @@ """ZHA repair for inconsistent network settings.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zha/repairs/wrong_silabs_firmware.py b/homeassistant/components/zha/repairs/wrong_silabs_firmware.py index 93c5489eda7..3326f5685df 100644 --- a/homeassistant/components/zha/repairs/wrong_silabs_firmware.py +++ b/homeassistant/components/zha/repairs/wrong_silabs_firmware.py @@ -1,4 +1,5 @@ """ZHA repairs for common environmental and device problems.""" + from __future__ import annotations import enum diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index 53acc5cdd02..c629a16a7c6 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -1,4 +1,5 @@ """Support for ZHA controls using the select platform.""" + from __future__ import annotations from enum import Enum diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 6a68b55a8be..1bb6b355f6e 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -1,4 +1,5 @@ """Sensors on Zigbee Home Automation networks.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zha/siren.py b/homeassistant/components/zha/siren.py index 717eb2df033..3aab332f746 100644 --- a/homeassistant/components/zha/siren.py +++ b/homeassistant/components/zha/siren.py @@ -1,4 +1,5 @@ """Support for ZHA sirens.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 960124c4a8a..726b73c4675 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -1,4 +1,5 @@ """Switches on Zigbee Home Automation networks.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/update.py b/homeassistant/components/zha/update.py index b7b721766b8..7ceba4fc924 100644 --- a/homeassistant/components/zha/update.py +++ b/homeassistant/components/zha/update.py @@ -1,4 +1,5 @@ """Representation of ZHA updates.""" + from __future__ import annotations import functools diff --git a/homeassistant/components/zha/websocket_api.py b/homeassistant/components/zha/websocket_api.py index e3e67ea0e41..38cce07550a 100644 --- a/homeassistant/components/zha/websocket_api.py +++ b/homeassistant/components/zha/websocket_api.py @@ -1,4 +1,5 @@ """Web socket API for Zigbee Home Automation devices.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index fbada765cde..b0a8f02a2f3 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -1,4 +1,5 @@ """Support for ZhongHong HVAC Controller.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ziggo_mediabox_xl/media_player.py b/homeassistant/components/ziggo_mediabox_xl/media_player.py index 20a78c80ac5..7c97d38cff3 100644 --- a/homeassistant/components/ziggo_mediabox_xl/media_player.py +++ b/homeassistant/components/ziggo_mediabox_xl/media_player.py @@ -1,4 +1,5 @@ """Support for interface with a Ziggo Mediabox XL.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zodiac/config_flow.py b/homeassistant/components/zodiac/config_flow.py index d599c9747f9..a9ed49568ca 100644 --- a/homeassistant/components/zodiac/config_flow.py +++ b/homeassistant/components/zodiac/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Zodiac integration.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py index 71d995bb73e..d7cac07a322 100644 --- a/homeassistant/components/zodiac/sensor.py +++ b/homeassistant/components/zodiac/sensor.py @@ -1,4 +1,5 @@ """Support for tracking the zodiac sign.""" + from __future__ import annotations from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 86593c36737..a73126e2971 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -1,4 +1,5 @@ """Support for the definition of zones.""" + from __future__ import annotations from collections.abc import Callable, Iterable diff --git a/homeassistant/components/zone/config_flow.py b/homeassistant/components/zone/config_flow.py index c176054bd34..ed71a79470e 100644 --- a/homeassistant/components/zone/config_flow.py +++ b/homeassistant/components/zone/config_flow.py @@ -4,6 +4,7 @@ This is no longer in use. This file is around so that existing config entries will remain to be loaded and then automatically migrated to the storage collection. """ + from homeassistant.config_entries import ConfigFlow from .const import DOMAIN diff --git a/homeassistant/components/zone/trigger.py b/homeassistant/components/zone/trigger.py index 09f93f9b786..f5c0f574320 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -1,4 +1,5 @@ """Offer zone automation rules.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py index 268823c9470..926780fc6da 100644 --- a/homeassistant/components/zoneminder/binary_sensor.py +++ b/homeassistant/components/zoneminder/binary_sensor.py @@ -1,4 +1,5 @@ """Support for ZoneMinder binary sensors.""" + from __future__ import annotations from zoneminder.zm import ZoneMinder diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index d8b2aa805e7..ab938472ed7 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -1,4 +1,5 @@ """Support for ZoneMinder camera streaming.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index 47863b5a5df..700344f44da 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -1,4 +1,5 @@ """Support for ZoneMinder sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index b722ef53a77..fa91478f29c 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -1,4 +1,5 @@ """Support for ZoneMinder switches.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 1e2a17fdf63..0fdf7a77895 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -1,4 +1,5 @@ """The Z-Wave JS integration.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zwave_js/addon.py b/homeassistant/components/zwave_js/addon.py index f9adf9f19fb..12d81146c03 100644 --- a/homeassistant/components/zwave_js/addon.py +++ b/homeassistant/components/zwave_js/addon.py @@ -1,4 +1,5 @@ """Provide add-on management.""" + from __future__ import annotations from homeassistant.components.hassio import AddonManager diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 068ce348caf..b4c8967f0ad 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -1,4 +1,5 @@ """Websocket API for Z-Wave JS.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index cb460f37000..dd3ded326b4 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -1,4 +1,5 @@ """Representation of Z-Wave binary sensors.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/zwave_js/button.py b/homeassistant/components/zwave_js/button.py index 876cf60b4cb..5526faf9c59 100644 --- a/homeassistant/components/zwave_js/button.py +++ b/homeassistant/components/zwave_js/button.py @@ -1,4 +1,5 @@ """Representation of Z-Wave buttons.""" + from __future__ import annotations from zwave_js_server.client import Client as ZwaveClient diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 2f84b52b7da..04e3d8c3950 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -1,4 +1,5 @@ """Representation of Z-Wave thermostats.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 8c5aa55713a..ca05dc2117b 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Z-Wave JS integration.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/components/zwave_js/config_validation.py b/homeassistant/components/zwave_js/config_validation.py index 9fc502bdafb..6c060f90ce5 100644 --- a/homeassistant/components/zwave_js/config_validation.py +++ b/homeassistant/components/zwave_js/config_validation.py @@ -1,4 +1,5 @@ """Config validation for the Z-Wave JS integration.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index 656620d01dd..f022cd42d20 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -1,4 +1,5 @@ """Constants for the Z-Wave JS integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 27919a17614..8c6aea0ff75 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -1,4 +1,5 @@ """Support for Z-Wave cover devices.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index b9b0c3a6e86..8ed2c07def3 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for Z-Wave JS.""" + from __future__ import annotations from collections import defaultdict diff --git a/homeassistant/components/zwave_js/device_automation_helpers.py b/homeassistant/components/zwave_js/device_automation_helpers.py index 2c375485e6b..5c94b2bb02d 100644 --- a/homeassistant/components/zwave_js/device_automation_helpers.py +++ b/homeassistant/components/zwave_js/device_automation_helpers.py @@ -1,4 +1,5 @@ """Provides helpers for Z-Wave JS device automations.""" + from __future__ import annotations from zwave_js_server.client import Client as ZwaveClient diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index 26b4c637b6e..dcd42d4d85d 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -1,4 +1,5 @@ """Provide the device conditions for Z-Wave JS.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/zwave_js/device_trigger.py b/homeassistant/components/zwave_js/device_trigger.py index d2b6ab7af15..49027d4d43b 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for Z-Wave JS.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index afae214ab2b..777d45efddb 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -1,4 +1,5 @@ """Provides diagnostics for Z-Wave JS.""" + from __future__ import annotations from copy import deepcopy diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index dfe2294e710..272f6e3ddc0 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -1,4 +1,5 @@ """Map Z-Wave nodes and values to Home Assistant entities.""" + from __future__ import annotations from collections.abc import Generator diff --git a/homeassistant/components/zwave_js/discovery_data_template.py b/homeassistant/components/zwave_js/discovery_data_template.py index 61a0cfdb802..7eb85e0ea4d 100644 --- a/homeassistant/components/zwave_js/discovery_data_template.py +++ b/homeassistant/components/zwave_js/discovery_data_template.py @@ -1,4 +1,5 @@ """Data template classes for discovery used to generate additional data for setup.""" + from __future__ import annotations from collections.abc import Iterable, Mapping diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index e7e110e7db6..0f594161bb9 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -1,4 +1,5 @@ """Generic Z-Wave Entity Class.""" + from __future__ import annotations from collections.abc import Sequence diff --git a/homeassistant/components/zwave_js/event.py b/homeassistant/components/zwave_js/event.py index 93860b6273e..2b170bdf5bd 100644 --- a/homeassistant/components/zwave_js/event.py +++ b/homeassistant/components/zwave_js/event.py @@ -1,4 +1,5 @@ """Support for Z-Wave controls using the event platform.""" + from __future__ import annotations from zwave_js_server.client import Client as ZwaveClient diff --git a/homeassistant/components/zwave_js/fan.py b/homeassistant/components/zwave_js/fan.py index d4247b65c8b..4cf9a5d40cf 100644 --- a/homeassistant/components/zwave_js/fan.py +++ b/homeassistant/components/zwave_js/fan.py @@ -1,4 +1,5 @@ """Support for Z-Wave fans.""" + from __future__ import annotations import math diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index c8eb02ad6cb..4f1b902d8ba 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -1,4 +1,5 @@ """Helper functions for Z-Wave JS integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zwave_js/humidifier.py b/homeassistant/components/zwave_js/humidifier.py index 14a43bea3af..be9e9a791fc 100644 --- a/homeassistant/components/zwave_js/humidifier.py +++ b/homeassistant/components/zwave_js/humidifier.py @@ -1,4 +1,5 @@ """Representation of Z-Wave humidifiers.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index b105b556e24..eba2d4a0cce 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -1,4 +1,5 @@ """Support for Z-Wave lights.""" + from __future__ import annotations from typing import Any, cast diff --git a/homeassistant/components/zwave_js/lock.py b/homeassistant/components/zwave_js/lock.py index 59faf7fbbb6..d102e5b5f22 100644 --- a/homeassistant/components/zwave_js/lock.py +++ b/homeassistant/components/zwave_js/lock.py @@ -1,4 +1,5 @@ """Representation of Z-Wave locks.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_js/logbook.py b/homeassistant/components/zwave_js/logbook.py index 1f634ba5ffd..315793b9726 100644 --- a/homeassistant/components/zwave_js/logbook.py +++ b/homeassistant/components/zwave_js/logbook.py @@ -1,4 +1,5 @@ """Describe Z-Wave JS logbook events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zwave_js/migrate.py b/homeassistant/components/zwave_js/migrate.py index a7543c8f6f6..bde53137dc1 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -1,4 +1,5 @@ """Functions used to migrate unique IDs for Z-Wave JS entities.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/zwave_js/number.py b/homeassistant/components/zwave_js/number.py index 6aa4a57ea4c..15262710095 100644 --- a/homeassistant/components/zwave_js/number.py +++ b/homeassistant/components/zwave_js/number.py @@ -1,4 +1,5 @@ """Support for Z-Wave controls using the number platform.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/components/zwave_js/repairs.py b/homeassistant/components/zwave_js/repairs.py index 1010d9abd90..e515ae10549 100644 --- a/homeassistant/components/zwave_js/repairs.py +++ b/homeassistant/components/zwave_js/repairs.py @@ -1,4 +1,5 @@ """Repairs for Z-Wave JS.""" + from __future__ import annotations from homeassistant import data_entry_flow diff --git a/homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py b/homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py index 826f3eebe0c..1005c3bb4db 100644 --- a/homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py +++ b/homeassistant/components/zwave_js/scripts/convert_device_diagnostics_to_fixture.py @@ -1,4 +1,5 @@ """Script to convert a device diagnostics file to a fixture.""" + from __future__ import annotations import argparse diff --git a/homeassistant/components/zwave_js/select.py b/homeassistant/components/zwave_js/select.py index e838949d3e1..c970c17f5f0 100644 --- a/homeassistant/components/zwave_js/select.py +++ b/homeassistant/components/zwave_js/select.py @@ -1,4 +1,5 @@ """Support for Z-Wave controls using the select platform.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index af3bc8a622e..f799a70110d 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -1,4 +1,5 @@ """Representation of Z-Wave sensors.""" + from __future__ import annotations from collections.abc import Callable, Mapping diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index e8ef1df4b96..5567c64ab97 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -1,4 +1,5 @@ """Methods and classes related to executing Z-Wave commands.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zwave_js/siren.py b/homeassistant/components/zwave_js/siren.py index 7df88f7dca4..b3f54ae9904 100644 --- a/homeassistant/components/zwave_js/siren.py +++ b/homeassistant/components/zwave_js/siren.py @@ -1,4 +1,5 @@ """Support for Z-Wave controls using the siren platform.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index 409bcd1dbb7..30ee5fb72bc 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -1,4 +1,5 @@ """Representation of Z-Wave switches.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_js/trigger.py b/homeassistant/components/zwave_js/trigger.py index 94cb05b1b20..9cb1a3e1d7e 100644 --- a/homeassistant/components/zwave_js/trigger.py +++ b/homeassistant/components/zwave_js/trigger.py @@ -1,4 +1,5 @@ """Z-Wave JS trigger dispatcher.""" + from __future__ import annotations from homeassistant.const import CONF_PLATFORM diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py index edc10d4a16e..61c53630354 100644 --- a/homeassistant/components/zwave_js/triggers/event.py +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -1,4 +1,5 @@ """Offer Z-Wave JS event listening automation trigger.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zwave_js/triggers/trigger_helpers.py b/homeassistant/components/zwave_js/triggers/trigger_helpers.py index 0fd9c3b4291..1dbe1f48f0a 100644 --- a/homeassistant/components/zwave_js/triggers/trigger_helpers.py +++ b/homeassistant/components/zwave_js/triggers/trigger_helpers.py @@ -1,4 +1,5 @@ """Helpers for Z-Wave JS custom triggers.""" + from zwave_js_server.client import Client as ZwaveClient from homeassistant.config_entries import ConfigEntryState diff --git a/homeassistant/components/zwave_js/triggers/value_updated.py b/homeassistant/components/zwave_js/triggers/value_updated.py index c44a0c6336a..ef10af1cec0 100644 --- a/homeassistant/components/zwave_js/triggers/value_updated.py +++ b/homeassistant/components/zwave_js/triggers/value_updated.py @@ -1,4 +1,5 @@ """Offer Z-Wave JS value updated listening automation trigger.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zwave_js/update.py b/homeassistant/components/zwave_js/update.py index f3e60f925e6..3fdbab8aacf 100644 --- a/homeassistant/components/zwave_js/update.py +++ b/homeassistant/components/zwave_js/update.py @@ -1,4 +1,5 @@ """Representation of Z-Wave updates.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/zwave_me/binary_sensor.py b/homeassistant/components/zwave_me/binary_sensor.py index f1ee6896b25..3be8f912b6d 100644 --- a/homeassistant/components/zwave_me/binary_sensor.py +++ b/homeassistant/components/zwave_me/binary_sensor.py @@ -1,4 +1,5 @@ """Representation of a sensorBinary.""" + from __future__ import annotations from zwave_me_ws import ZWaveMeData diff --git a/homeassistant/components/zwave_me/button.py b/homeassistant/components/zwave_me/button.py index 7e0b4f02728..f7f1d5d7945 100644 --- a/homeassistant/components/zwave_me/button.py +++ b/homeassistant/components/zwave_me/button.py @@ -1,4 +1,5 @@ """Representation of a toggleButton.""" + from homeassistant.components.button import ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/zwave_me/climate.py b/homeassistant/components/zwave_me/climate.py index 35e0d745619..02112e51617 100644 --- a/homeassistant/components/zwave_me/climate.py +++ b/homeassistant/components/zwave_me/climate.py @@ -1,4 +1,5 @@ """Representation of a thermostat.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_me/config_flow.py b/homeassistant/components/zwave_me/config_flow.py index 5fbfc1a475b..1444bfc1b95 100644 --- a/homeassistant/components/zwave_me/config_flow.py +++ b/homeassistant/components/zwave_me/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure ZWaveMe integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/zwave_me/const.py b/homeassistant/components/zwave_me/const.py index 1ec4f8d1601..8dcfc369db7 100644 --- a/homeassistant/components/zwave_me/const.py +++ b/homeassistant/components/zwave_me/const.py @@ -1,4 +1,5 @@ """Constants for ZWaveMe.""" + from enum import StrEnum from homeassistant.const import Platform diff --git a/homeassistant/components/zwave_me/cover.py b/homeassistant/components/zwave_me/cover.py index e717df027e3..4794e807049 100644 --- a/homeassistant/components/zwave_me/cover.py +++ b/homeassistant/components/zwave_me/cover.py @@ -1,4 +1,5 @@ """Representation of a cover.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_me/fan.py b/homeassistant/components/zwave_me/fan.py index c332fb305c5..25ccec9a0fb 100644 --- a/homeassistant/components/zwave_me/fan.py +++ b/homeassistant/components/zwave_me/fan.py @@ -1,4 +1,5 @@ """Representation of a fan.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_me/helpers.py b/homeassistant/components/zwave_me/helpers.py index 0d53512d1cb..3b5cb4ad0be 100644 --- a/homeassistant/components/zwave_me/helpers.py +++ b/homeassistant/components/zwave_me/helpers.py @@ -1,4 +1,5 @@ """Helpers for zwave_me config flow.""" + from __future__ import annotations from zwave_me_ws import ZWaveMe diff --git a/homeassistant/components/zwave_me/light.py b/homeassistant/components/zwave_me/light.py index 7fad88a4a49..b1065d45160 100644 --- a/homeassistant/components/zwave_me/light.py +++ b/homeassistant/components/zwave_me/light.py @@ -1,4 +1,5 @@ """Representation of an RGB light.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_me/lock.py b/homeassistant/components/zwave_me/lock.py index 17e64ff1602..6218dac1627 100644 --- a/homeassistant/components/zwave_me/lock.py +++ b/homeassistant/components/zwave_me/lock.py @@ -1,4 +1,5 @@ """Representation of a doorlock.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/zwave_me/number.py b/homeassistant/components/zwave_me/number.py index 4e9acc1f76b..28fd8abe460 100644 --- a/homeassistant/components/zwave_me/number.py +++ b/homeassistant/components/zwave_me/number.py @@ -1,4 +1,5 @@ """Representation of a switchMultilevel.""" + from homeassistant.components.number import NumberEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/zwave_me/sensor.py b/homeassistant/components/zwave_me/sensor.py index f96e2d789ff..20470e6e62b 100644 --- a/homeassistant/components/zwave_me/sensor.py +++ b/homeassistant/components/zwave_me/sensor.py @@ -1,4 +1,5 @@ """Representation of a sensorMultilevel.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/zwave_me/siren.py b/homeassistant/components/zwave_me/siren.py index c6757f61ad7..a1bf8081616 100644 --- a/homeassistant/components/zwave_me/siren.py +++ b/homeassistant/components/zwave_me/siren.py @@ -1,4 +1,5 @@ """Representation of a sirenBinary.""" + from typing import Any from homeassistant.components.siren import SirenEntity, SirenEntityFeature From 19ab3d6daf39e17075748669f82569467cdbc8b9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:36:11 +0100 Subject: [PATCH 0556/1691] Add empty line after module docstring [helpers + other] (#112707) --- homeassistant/__main__.py | 1 + homeassistant/auth/__init__.py | 1 + homeassistant/auth/auth_store.py | 1 + homeassistant/auth/const.py | 1 + homeassistant/auth/jwt_wrapper.py | 1 + homeassistant/auth/mfa_modules/__init__.py | 1 + homeassistant/auth/mfa_modules/insecure_example.py | 1 + homeassistant/auth/mfa_modules/notify.py | 1 + homeassistant/auth/mfa_modules/totp.py | 1 + homeassistant/auth/models.py | 1 + homeassistant/auth/permissions/__init__.py | 1 + homeassistant/auth/permissions/entities.py | 1 + homeassistant/auth/permissions/events.py | 1 + homeassistant/auth/permissions/merge.py | 1 + homeassistant/auth/permissions/models.py | 1 + homeassistant/auth/permissions/system_policies.py | 1 + homeassistant/auth/permissions/types.py | 1 + homeassistant/auth/permissions/util.py | 1 + homeassistant/auth/providers/__init__.py | 1 + homeassistant/auth/providers/command_line.py | 1 + homeassistant/auth/providers/homeassistant.py | 1 + homeassistant/auth/providers/insecure_example.py | 1 + homeassistant/auth/providers/legacy_api_password.py | 1 + homeassistant/auth/providers/trusted_networks.py | 1 + homeassistant/backports/enum.py | 1 + homeassistant/block_async_io.py | 1 + homeassistant/bootstrap.py | 1 + homeassistant/components/__init__.py | 1 + homeassistant/config.py | 1 + homeassistant/config_entries.py | 1 + homeassistant/const.py | 1 + homeassistant/core.py | 1 + homeassistant/data_entry_flow.py | 1 + homeassistant/exceptions.py | 1 + homeassistant/helpers/__init__.py | 1 + homeassistant/helpers/aiohttp_client.py | 1 + homeassistant/helpers/area_registry.py | 1 + homeassistant/helpers/check_config.py | 1 + homeassistant/helpers/collection.py | 1 + homeassistant/helpers/condition.py | 1 + homeassistant/helpers/config_entry_flow.py | 1 + homeassistant/helpers/config_entry_oauth2_flow.py | 1 + homeassistant/helpers/config_validation.py | 1 + homeassistant/helpers/data_entry_flow.py | 1 + homeassistant/helpers/debounce.py | 1 + homeassistant/helpers/deprecation.py | 1 + homeassistant/helpers/device_registry.py | 1 + homeassistant/helpers/discovery.py | 1 + homeassistant/helpers/discovery_flow.py | 1 + homeassistant/helpers/dispatcher.py | 1 + homeassistant/helpers/entity.py | 1 + homeassistant/helpers/entity_component.py | 1 + homeassistant/helpers/entity_platform.py | 1 + homeassistant/helpers/entity_registry.py | 1 + homeassistant/helpers/entity_values.py | 1 + homeassistant/helpers/entityfilter.py | 1 + homeassistant/helpers/event.py | 1 + homeassistant/helpers/floor_registry.py | 1 + homeassistant/helpers/frame.py | 1 + homeassistant/helpers/group.py | 1 + homeassistant/helpers/http.py | 1 + homeassistant/helpers/httpx_client.py | 1 + homeassistant/helpers/icon.py | 1 + homeassistant/helpers/instance_id.py | 1 + homeassistant/helpers/integration_platform.py | 1 + homeassistant/helpers/issue_registry.py | 1 + homeassistant/helpers/json.py | 1 + homeassistant/helpers/label_registry.py | 1 + homeassistant/helpers/location.py | 1 + homeassistant/helpers/network.py | 1 + homeassistant/helpers/normalized_name_base_registry.py | 1 + homeassistant/helpers/ratelimit.py | 1 + homeassistant/helpers/redact.py | 1 + homeassistant/helpers/registry.py | 1 + homeassistant/helpers/reload.py | 1 + homeassistant/helpers/restore_state.py | 1 + homeassistant/helpers/schema_config_entry_flow.py | 1 + homeassistant/helpers/script.py | 1 + homeassistant/helpers/script_variables.py | 1 + homeassistant/helpers/selector.py | 1 + homeassistant/helpers/sensor.py | 1 + homeassistant/helpers/service.py | 1 + homeassistant/helpers/service_info/mqtt.py | 1 + homeassistant/helpers/significant_change.py | 1 + homeassistant/helpers/singleton.py | 1 + homeassistant/helpers/start.py | 1 + homeassistant/helpers/state.py | 1 + homeassistant/helpers/storage.py | 1 + homeassistant/helpers/sun.py | 1 + homeassistant/helpers/system_info.py | 1 + homeassistant/helpers/temperature.py | 1 + homeassistant/helpers/template.py | 1 + homeassistant/helpers/trace.py | 1 + homeassistant/helpers/translation.py | 1 + homeassistant/helpers/trigger.py | 1 + homeassistant/helpers/trigger_template_entity.py | 1 + homeassistant/helpers/typing.py | 1 + homeassistant/helpers/update_coordinator.py | 1 + homeassistant/loader.py | 1 + homeassistant/requirements.py | 1 + homeassistant/runner.py | 1 + homeassistant/scripts/__init__.py | 1 + homeassistant/scripts/benchmark/__init__.py | 1 + homeassistant/scripts/check_config.py | 1 + homeassistant/setup.py | 1 + homeassistant/util/__init__.py | 1 + homeassistant/util/aiohttp.py | 1 + homeassistant/util/async_.py | 1 + homeassistant/util/color.py | 1 + homeassistant/util/decorator.py | 1 + homeassistant/util/dt.py | 1 + homeassistant/util/enum.py | 1 + homeassistant/util/executor.py | 1 + homeassistant/util/file.py | 1 + homeassistant/util/frozen_dataclass_compat.py | 1 + homeassistant/util/json.py | 1 + homeassistant/util/language.py | 1 + homeassistant/util/limited_size_dict.py | 1 + homeassistant/util/location.py | 1 + homeassistant/util/logging.py | 1 + homeassistant/util/network.py | 1 + homeassistant/util/package.py | 1 + homeassistant/util/percentage.py | 1 + homeassistant/util/pil.py | 1 + homeassistant/util/read_only_dict.py | 1 + homeassistant/util/scaling.py | 1 + homeassistant/util/timeout.py | 1 + homeassistant/util/ulid.py | 1 + homeassistant/util/unit_conversion.py | 1 + homeassistant/util/unit_system.py | 1 + homeassistant/util/variance.py | 1 + homeassistant/util/yaml/__init__.py | 1 + homeassistant/util/yaml/dumper.py | 1 + homeassistant/util/yaml/input.py | 1 + homeassistant/util/yaml/loader.py | 1 + homeassistant/util/yaml/objects.py | 1 + pylint/plugins/hass_enforce_coordinator_module.py | 1 + pylint/plugins/hass_enforce_sorted_platforms.py | 1 + pylint/plugins/hass_enforce_super_call.py | 1 + pylint/plugins/hass_enforce_type_hints.py | 1 + pylint/plugins/hass_imports.py | 1 + pylint/plugins/hass_inheritance.py | 1 + pylint/plugins/hass_logger.py | 1 + script/const.py | 1 + script/countries.py | 1 + script/currencies.py | 1 + script/gen_requirements_all.py | 1 + script/hassfest/__main__.py | 1 + script/hassfest/application_credentials.py | 1 + script/hassfest/bluetooth.py | 1 + script/hassfest/brand.py | 1 + script/hassfest/codeowners.py | 1 + script/hassfest/config_flow.py | 1 + script/hassfest/config_schema.py | 1 + script/hassfest/coverage.py | 1 + script/hassfest/dependencies.py | 1 + script/hassfest/dhcp.py | 1 + script/hassfest/docker.py | 1 + script/hassfest/icons.py | 1 + script/hassfest/json.py | 1 + script/hassfest/manifest.py | 1 + script/hassfest/model.py | 1 + script/hassfest/mqtt.py | 1 + script/hassfest/mypy_config.py | 1 + script/hassfest/requirements.py | 1 + script/hassfest/serializer.py | 1 + script/hassfest/services.py | 1 + script/hassfest/ssdp.py | 1 + script/hassfest/translations.py | 1 + script/hassfest/usb.py | 1 + script/hassfest/zeroconf.py | 1 + script/microsoft_tts.py | 1 + script/scaffold/const.py | 1 + script/scaffold/docs.py | 1 + script/scaffold/generate.py | 1 + script/scaffold/model.py | 1 + script/scaffold/templates/backup/integration/backup.py | 1 + script/scaffold/templates/backup/tests/test_backup.py | 1 + script/scaffold/templates/config_flow/integration/__init__.py | 1 + .../scaffold/templates/config_flow/integration/config_flow.py | 1 + script/scaffold/templates/config_flow/tests/conftest.py | 1 + .../scaffold/templates/config_flow/tests/test_config_flow.py | 1 + .../templates/config_flow_discovery/integration/__init__.py | 1 + .../templates/config_flow_helper/integration/__init__.py | 1 + .../templates/config_flow_helper/integration/config_flow.py | 1 + .../templates/config_flow_helper/integration/sensor.py | 1 + .../scaffold/templates/config_flow_helper/tests/conftest.py | 1 + .../templates/config_flow_helper/tests/test_config_flow.py | 1 + .../templates/config_flow_oauth2/integration/__init__.py | 1 + .../scaffold/templates/config_flow_oauth2/integration/api.py | 1 + .../templates/device_action/integration/device_action.py | 1 + .../device_condition/integration/device_condition.py | 1 + .../templates/device_condition/tests/test_device_condition.py | 1 + .../templates/device_trigger/integration/device_trigger.py | 1 + script/scaffold/templates/integration/integration/__init__.py | 1 + .../templates/reproduce_state/integration/reproduce_state.py | 1 + .../significant_change/integration/significant_change.py | 1 + .../significant_change/tests/test_significant_change.py | 1 + script/translations/download.py | 1 + script/translations/lokalise.py | 1 + tests/auth/mfa_modules/test_insecure_example.py | 1 + tests/auth/permissions/test_merge.py | 1 + tests/auth/permissions/test_system_policies.py | 1 + tests/auth/providers/test_insecure_example.py | 1 + tests/auth/providers/test_trusted_networks.py | 1 + tests/auth/test_init.py | 1 + tests/auth/test_models.py | 1 + tests/common.py | 1 + tests/conftest.py | 1 + tests/hassfest/test_dependencies.py | 4 ++++ tests/hassfest/test_requirements.py | 1 + tests/helpers/test_aiohttp_client.py | 1 + tests/helpers/test_area_registry.py | 1 + tests/helpers/test_collection.py | 1 + tests/helpers/test_condition.py | 1 + tests/helpers/test_config_entry_flow.py | 1 + tests/helpers/test_config_entry_oauth2_flow.py | 1 + tests/helpers/test_config_validation.py | 1 + tests/helpers/test_deprecation.py | 1 + tests/helpers/test_device_registry.py | 1 + tests/helpers/test_discovery.py | 1 + tests/helpers/test_discovery_flow.py | 1 + tests/helpers/test_dispatcher.py | 1 + tests/helpers/test_entity_registry.py | 1 + tests/helpers/test_entity_values.py | 1 + tests/helpers/test_entityfilter.py | 1 + tests/helpers/test_httpx_client.py | 1 + tests/helpers/test_instance_id.py | 1 + tests/helpers/test_integration_platform.py | 1 + tests/helpers/test_issue_registry.py | 1 + tests/helpers/test_location.py | 1 + tests/helpers/test_network.py | 1 + tests/helpers/test_redact.py | 1 + tests/helpers/test_registry.py | 1 + tests/helpers/test_restore_state.py | 1 + tests/helpers/test_schema_config_entry_flow.py | 1 + tests/helpers/test_selector.py | 1 + tests/helpers/test_singleton.py | 1 + tests/helpers/test_storage_remove.py | 1 + tests/helpers/test_template.py | 1 + tests/helpers/test_trigger.py | 1 + tests/helpers/test_update_coordinator.py | 1 + tests/patch_time.py | 1 + tests/pylint/conftest.py | 1 + tests/pylint/test_enforce_coordinator_module.py | 1 + tests/pylint/test_enforce_sorted_platforms.py | 1 + tests/pylint/test_enforce_super_call.py | 1 + tests/scripts/test_init.py | 1 + tests/syrupy.py | 1 + tests/test_config.py | 1 + tests/test_config_entries.py | 1 + tests/test_core.py | 1 + tests/test_exceptions.py | 1 + tests/test_main.py | 1 + tests/test_test_fixtures.py | 1 + .../custom_components/test/alarm_control_panel.py | 1 + tests/testing_config/custom_components/test/binary_sensor.py | 1 + tests/testing_config/custom_components/test/cover.py | 1 + tests/testing_config/custom_components/test/date.py | 1 + tests/testing_config/custom_components/test/datetime.py | 1 + tests/testing_config/custom_components/test/event.py | 1 + tests/testing_config/custom_components/test/fan.py | 1 + tests/testing_config/custom_components/test/light.py | 1 + tests/testing_config/custom_components/test/lock.py | 1 + tests/testing_config/custom_components/test/number.py | 1 + tests/testing_config/custom_components/test/remote.py | 1 + tests/testing_config/custom_components/test/select.py | 1 + tests/testing_config/custom_components/test/sensor.py | 1 + tests/testing_config/custom_components/test/switch.py | 1 + tests/testing_config/custom_components/test/text.py | 1 + tests/testing_config/custom_components/test/time.py | 1 + tests/testing_config/custom_components/test/update.py | 1 + tests/testing_config/custom_components/test/weather.py | 1 + tests/typing.py | 1 + tests/util/test_dt.py | 1 + tests/util/test_enum.py | 1 + tests/util/test_init.py | 1 + tests/util/test_json.py | 1 + tests/util/test_language.py | 1 + tests/util/test_location.py | 1 + tests/util/test_unit_conversion.py | 1 + tests/util/test_unit_system.py | 1 + tests/util/test_variance.py | 1 + tests/util/yaml/test_init.py | 1 + 284 files changed, 287 insertions(+) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 4ea324878ec..4ed80c27bf0 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -1,4 +1,5 @@ """Start Home Assistant.""" + from __future__ import annotations import argparse diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index fa89134305e..969fcc3529e 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -1,4 +1,5 @@ """Provide an authentication layer for Home Assistant.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index 983ba7da6a1..b9f7b3a7e14 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -1,4 +1,5 @@ """Storage for auth models.""" + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/auth/const.py b/homeassistant/auth/const.py index 704f5d1d57c..05b9e6d7ad6 100644 --- a/homeassistant/auth/const.py +++ b/homeassistant/auth/const.py @@ -1,4 +1,5 @@ """Constants for the auth module.""" + from datetime import timedelta ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30) diff --git a/homeassistant/auth/jwt_wrapper.py b/homeassistant/auth/jwt_wrapper.py index c681df66557..58f9260ff8f 100644 --- a/homeassistant/auth/jwt_wrapper.py +++ b/homeassistant/auth/jwt_wrapper.py @@ -4,6 +4,7 @@ Since we decode the same tokens over and over again we can cache the result of the decode of valid tokens to speed up the process. """ + from __future__ import annotations from datetime import timedelta diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index aa28710d8c6..0cfb7508dbf 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -1,4 +1,5 @@ """Pluggable auth modules for Home Assistant.""" + from __future__ import annotations import importlib diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index a50b762b121..fc696fe1b63 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -1,4 +1,5 @@ """Example auth module.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py index 4c5b3a2380b..72edb195a81 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -2,6 +2,7 @@ Sending HOTP through notify service """ + from __future__ import annotations import asyncio diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 0c02ef4bd8d..e9055b45f05 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -1,4 +1,5 @@ """Time-based One Time Password auth module.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index d71cd682086..2e5f5940544 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -1,4 +1,5 @@ """Auth models.""" + from __future__ import annotations from datetime import datetime, timedelta diff --git a/homeassistant/auth/permissions/__init__.py b/homeassistant/auth/permissions/__init__.py index 935f9bfbf65..c0574e9f0ea 100644 --- a/homeassistant/auth/permissions/__init__.py +++ b/homeassistant/auth/permissions/__init__.py @@ -1,4 +1,5 @@ """Permissions for Home Assistant.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/auth/permissions/entities.py b/homeassistant/auth/permissions/entities.py index 4dc221a9ff4..dbe2fea0021 100644 --- a/homeassistant/auth/permissions/entities.py +++ b/homeassistant/auth/permissions/entities.py @@ -1,4 +1,5 @@ """Entity permissions.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/auth/permissions/events.py b/homeassistant/auth/permissions/events.py index aec23331664..3146cd99787 100644 --- a/homeassistant/auth/permissions/events.py +++ b/homeassistant/auth/permissions/events.py @@ -1,4 +1,5 @@ """Permission for events.""" + from __future__ import annotations from typing import Final diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py index 121d87f7848..841959b7f31 100644 --- a/homeassistant/auth/permissions/merge.py +++ b/homeassistant/auth/permissions/merge.py @@ -1,4 +1,5 @@ """Merging of policies.""" + from __future__ import annotations from typing import cast diff --git a/homeassistant/auth/permissions/models.py b/homeassistant/auth/permissions/models.py index 9b9c384c74d..086fdd7bd76 100644 --- a/homeassistant/auth/permissions/models.py +++ b/homeassistant/auth/permissions/models.py @@ -1,4 +1,5 @@ """Models for permissions.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/auth/permissions/system_policies.py b/homeassistant/auth/permissions/system_policies.py index b45984653fb..0d445802e7e 100644 --- a/homeassistant/auth/permissions/system_policies.py +++ b/homeassistant/auth/permissions/system_policies.py @@ -1,4 +1,5 @@ """System policies.""" + from .const import CAT_ENTITIES, POLICY_READ, SUBCAT_ALL ADMIN_POLICY = {CAT_ENTITIES: True} diff --git a/homeassistant/auth/permissions/types.py b/homeassistant/auth/permissions/types.py index cf3632d06d5..3411ae860fb 100644 --- a/homeassistant/auth/permissions/types.py +++ b/homeassistant/auth/permissions/types.py @@ -1,4 +1,5 @@ """Common code for permissions.""" + from collections.abc import Mapping # MyPy doesn't support recursion yet. So writing it out as far as we need. diff --git a/homeassistant/auth/permissions/util.py b/homeassistant/auth/permissions/util.py index 402d43b7ab7..db85e18f60c 100644 --- a/homeassistant/auth/permissions/util.py +++ b/homeassistant/auth/permissions/util.py @@ -1,4 +1,5 @@ """Helpers to deal with permissions.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 2036178b225..0a3d697eeea 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -1,4 +1,5 @@ """Auth providers for Home Assistant.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/auth/providers/command_line.py b/homeassistant/auth/providers/command_line.py index 346d2cc503f..43cde284a25 100644 --- a/homeassistant/auth/providers/command_line.py +++ b/homeassistant/auth/providers/command_line.py @@ -1,4 +1,5 @@ """Auth provider that validates credentials via an external command.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index 2c4fb034dc9..d277ce96fe2 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -1,4 +1,5 @@ """Home Assistant auth provider.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 851bed6638c..8bcf7569f5a 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -1,4 +1,5 @@ """Example auth provider.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index 4fcfd4f7b12..f04490a354e 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -2,6 +2,7 @@ It will be removed when auth system production ready """ + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index 54633744bd9..32d1934e093 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -3,6 +3,7 @@ It shows list of users if access from trusted network. Abort login flow if not access from trusted network. """ + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/backports/enum.py b/homeassistant/backports/enum.py index 871244b4567..3c09d8e7f57 100644 --- a/homeassistant/backports/enum.py +++ b/homeassistant/backports/enum.py @@ -6,6 +6,7 @@ Since we have dropped support for Python 3.10, we can remove this backport. This file is kept for now to avoid breaking custom components that might import it. """ + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/block_async_io.py b/homeassistant/block_async_io.py index d7c1a7c9eea..bf805b5ef21 100644 --- a/homeassistant/block_async_io.py +++ b/homeassistant/block_async_io.py @@ -1,4 +1,5 @@ """Block blocking calls being done in asyncio.""" + from http.client import HTTPConnection import time diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 3d4e79c1ca2..da51332d93d 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,4 +1,5 @@ """Provide methods to bootstrap a Home Assistant instance.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 839a66af25d..6a3d12fec5e 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -6,6 +6,7 @@ Component design guidelines: format ".". - Each component should publish services only under its own domain. """ + from __future__ import annotations import logging diff --git a/homeassistant/config.py b/homeassistant/config.py index 354eb0d858f..92ed7556445 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1,4 +1,5 @@ """Module to help with parsing and generating configuration files.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 6976d6d36a8..b2206912fb3 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1,4 +1,5 @@ """Manage config entries in Home Assistant.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/const.py b/homeassistant/const.py index 3a8d1a09e85..6331b5d46ce 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,4 +1,5 @@ """Constants used by Home Assistant components.""" + from __future__ import annotations from enum import StrEnum diff --git a/homeassistant/core.py b/homeassistant/core.py index 4771213e0d3..a44f8d894ca 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -3,6 +3,7 @@ Home Assistant is a Home Automation framework for observing the state of entities and react to changes. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 4334ec2b274..ac93851bbda 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -1,4 +1,5 @@ """Classes to help gather user submissions.""" + from __future__ import annotations import abc diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 8d5e2bbde95..a5999510f9f 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -1,4 +1,5 @@ """The exceptions used by Home Assistant.""" + from __future__ import annotations from collections.abc import Generator, Sequence diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index 52197e83495..9f72445822e 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -1,4 +1,5 @@ """Helper methods for components within Home Assistant.""" + from __future__ import annotations from collections.abc import Iterable, Sequence diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index cc0be0d5515..70546e54bcc 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -1,4 +1,5 @@ """Helper for aiohttp webclient stuff.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 7565c87b6b2..f9f4abc6468 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -1,4 +1,5 @@ """Provide a way to connect devices to one physical location.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 0de11303cf9..2f716ac8bb1 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -1,4 +1,5 @@ """Helper to check the configuration file.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index c3c2ae4ec37..451f96379ac 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -1,4 +1,5 @@ """Helper to deal with YAML + storage.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 18894893b88..ab0da9f4478 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -1,4 +1,5 @@ """Offer reusable conditions.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 23638dc3549..c4da43a7061 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -1,4 +1,5 @@ """Helpers for data entry flows for config entries.""" + from __future__ import annotations from collections.abc import Awaitable, Callable diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index dcc0265ab61..83e76ca4ebe 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -5,6 +5,7 @@ This module exists of the following parts: - OAuth2 implementation that works with local provided client ID/secret """ + from __future__ import annotations from abc import ABC, ABCMeta, abstractmethod diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 7bf760c3136..3f93eabc786 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,4 +1,5 @@ """Helpers for config validation using voluptuous.""" + from __future__ import annotations from collections.abc import Callable, Hashable diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 524fd4ebf7d..1edeb28d88f 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -1,4 +1,5 @@ """Helpers for the data entry flow.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 298d20485a0..2fe42e19a67 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -1,4 +1,5 @@ """Debounce helper.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index cf76bc78aa5..3266c69db20 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -1,4 +1,5 @@ """Deprecation helpers for Home Assistant.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 253b7eaa65c..85d0e8f9966 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,4 +1,5 @@ """Provide a way to connect entities belonging to one device.""" + from __future__ import annotations from collections import UserDict diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 7045966c529..98419ae6bf2 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -5,6 +5,7 @@ There are two different types of discoveries that can be fired/listened for. - listen_platform/discover_platform is for platforms. These are used by components to allow discovery of their platforms. """ + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/helpers/discovery_flow.py b/homeassistant/helpers/discovery_flow.py index a24e87325ae..b6633a3f718 100644 --- a/homeassistant/helpers/discovery_flow.py +++ b/homeassistant/helpers/discovery_flow.py @@ -1,4 +1,5 @@ """The discovery flow helper.""" + from __future__ import annotations from collections.abc import Coroutine diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index 59d680a60ee..cb812ef071b 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -1,4 +1,5 @@ """Helpers for Home Assistant dispatcher & internal component/platform.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 9fe4af2c6a6..6ec7cbba9b7 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -1,4 +1,5 @@ """An abstract class for entities.""" + from __future__ import annotations from abc import ABCMeta diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 40b665ed373..fe2d78c02ed 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -1,4 +1,5 @@ """Helpers for components that manage entities.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index e9258f8d1c7..9175d46355e 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -1,4 +1,5 @@ """Class to manage the entities for a single platform.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index df923c15c37..49dfde913e4 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -7,6 +7,7 @@ The Entity Registry will persist itself 10 seconds after a new entity is registered. Registering a new entity while a timer is in progress resets the timer. """ + from __future__ import annotations from collections import UserDict diff --git a/homeassistant/helpers/entity_values.py b/homeassistant/helpers/entity_values.py index fe4a4249c54..7e7bdc7be41 100644 --- a/homeassistant/helpers/entity_values.py +++ b/homeassistant/helpers/entity_values.py @@ -1,4 +1,5 @@ """A class to hold entity values.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index dd61357f53e..cd1960fde12 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -1,4 +1,5 @@ """Helper class to implement include/exclude of entities and domains.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 0233eea37d6..471a996ccdc 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,4 +1,5 @@ """Helpers for listening to events.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index d966f6045b6..a701cd5bf79 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -1,4 +1,5 @@ """Provide a way to assign areas to floors in one's home.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/helpers/frame.py b/homeassistant/helpers/frame.py index 3b9c556966a..ee092717753 100644 --- a/homeassistant/helpers/frame.py +++ b/homeassistant/helpers/frame.py @@ -1,4 +1,5 @@ """Provide frame helper for finding the current frame context.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/group.py b/homeassistant/helpers/group.py index 437df226118..7d4eeb6d133 100644 --- a/homeassistant/helpers/group.py +++ b/homeassistant/helpers/group.py @@ -1,4 +1,5 @@ """Helper for groups.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index e0f7d59ae9a..7bcb1bedd20 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -1,4 +1,5 @@ """Helper to track the current http request.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index ed02f8a710e..306d9899cd5 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -1,4 +1,5 @@ """Helper for httpx.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/helpers/icon.py b/homeassistant/helpers/icon.py index f1638732527..973c93674b1 100644 --- a/homeassistant/helpers/icon.py +++ b/homeassistant/helpers/icon.py @@ -1,4 +1,5 @@ """Icon helper methods.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/instance_id.py b/homeassistant/helpers/instance_id.py index 5bb8be5a9fe..8bad8f90b9c 100644 --- a/homeassistant/helpers/instance_id.py +++ b/homeassistant/helpers/instance_id.py @@ -1,4 +1,5 @@ """Helper to create a unique instance ID.""" + from __future__ import annotations import logging diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 7debae6072f..54e4e0f9fb9 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -1,4 +1,5 @@ """Helpers to help with integration platforms.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/issue_registry.py b/homeassistant/helpers/issue_registry.py index fc8c547404d..11bde0edf6b 100644 --- a/homeassistant/helpers/issue_registry.py +++ b/homeassistant/helpers/issue_registry.py @@ -1,4 +1,5 @@ """Persistently store issues raised by integrations.""" + from __future__ import annotations import dataclasses diff --git a/homeassistant/helpers/json.py b/homeassistant/helpers/json.py index ba2486a196e..28b3d509a0c 100644 --- a/homeassistant/helpers/json.py +++ b/homeassistant/helpers/json.py @@ -1,4 +1,5 @@ """Helpers to help with encoding Home Assistant objects in JSON.""" + from collections import deque from collections.abc import Callable import datetime diff --git a/homeassistant/helpers/label_registry.py b/homeassistant/helpers/label_registry.py index 34c23c98f2a..666a573fe43 100644 --- a/homeassistant/helpers/label_registry.py +++ b/homeassistant/helpers/label_registry.py @@ -1,4 +1,5 @@ """Provide a way to label and group anything.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/helpers/location.py b/homeassistant/helpers/location.py index 086150115da..a12de4f9029 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -1,4 +1,5 @@ """Location helpers for Home Assistant.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 46d71ccb390..510639b5506 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -1,4 +1,5 @@ """Network helpers.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/helpers/normalized_name_base_registry.py b/homeassistant/helpers/normalized_name_base_registry.py index 13a4cb10312..16280a73750 100644 --- a/homeassistant/helpers/normalized_name_base_registry.py +++ b/homeassistant/helpers/normalized_name_base_registry.py @@ -1,4 +1,5 @@ """Provide a base class for registries that use a normalized name index.""" + from collections import UserDict from collections.abc import ValuesView from dataclasses import dataclass diff --git a/homeassistant/helpers/ratelimit.py b/homeassistant/helpers/ratelimit.py index 12a4cfac406..430ec906bdb 100644 --- a/homeassistant/helpers/ratelimit.py +++ b/homeassistant/helpers/ratelimit.py @@ -1,4 +1,5 @@ """Ratelimit helper.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/redact.py b/homeassistant/helpers/redact.py index f8df73b9180..1ee9eeaae42 100644 --- a/homeassistant/helpers/redact.py +++ b/homeassistant/helpers/redact.py @@ -1,4 +1,5 @@ """Helpers to redact sensitive data.""" + from __future__ import annotations from collections.abc import Callable, Iterable, Mapping diff --git a/homeassistant/helpers/registry.py b/homeassistant/helpers/registry.py index 7b13941e5d2..83079af6228 100644 --- a/homeassistant/helpers/registry.py +++ b/homeassistant/helpers/registry.py @@ -1,4 +1,5 @@ """Provide a base implementation for registries.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index 983b4e2da52..dcf6a59a71e 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -1,4 +1,5 @@ """Class to reload platforms.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 7df83cd0ab9..d0ef3acd426 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -1,4 +1,5 @@ """Support for restoring entity states on startup.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index d5563c995ff..b5b53c91def 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -1,4 +1,5 @@ """Helpers for creating schema based data entry flows.""" + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index ee5015ad862..da710bd7e24 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1,4 +1,5 @@ """Helpers to execute scripts.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/script_variables.py b/homeassistant/helpers/script_variables.py index 6c5edfd0ac3..043101b9b86 100644 --- a/homeassistant/helpers/script_variables.py +++ b/homeassistant/helpers/script_variables.py @@ -1,4 +1,5 @@ """Script variables.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 8f2d9bf4938..0a893aa87c2 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -1,4 +1,5 @@ """Selectors for Home Assistant.""" + from __future__ import annotations from collections.abc import Callable, Mapping, Sequence diff --git a/homeassistant/helpers/sensor.py b/homeassistant/helpers/sensor.py index 0785a78850a..3cccfb661ee 100644 --- a/homeassistant/helpers/sensor.py +++ b/homeassistant/helpers/sensor.py @@ -1,4 +1,5 @@ """Common functions related to sensor device management.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 42d725c3ce4..c4238c0d116 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -1,4 +1,5 @@ """Service calling related helpers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/service_info/mqtt.py b/homeassistant/helpers/service_info/mqtt.py index 906072a2d4b..172a5eeff33 100644 --- a/homeassistant/helpers/service_info/mqtt.py +++ b/homeassistant/helpers/service_info/mqtt.py @@ -1,4 +1,5 @@ """MQTT Discovery data.""" + from dataclasses import dataclass import datetime as dt diff --git a/homeassistant/helpers/significant_change.py b/homeassistant/helpers/significant_change.py index 12b78b75fa2..44b103e5c27 100644 --- a/homeassistant/helpers/significant_change.py +++ b/homeassistant/helpers/significant_change.py @@ -26,6 +26,7 @@ The following cases will never be passed to your function: - if either state is unknown/unavailable - state adding/removing """ + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/helpers/singleton.py b/homeassistant/helpers/singleton.py index 5579106bb55..91e7a671b69 100644 --- a/homeassistant/helpers/singleton.py +++ b/homeassistant/helpers/singleton.py @@ -1,4 +1,5 @@ """Helper to help coordinating calls.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/start.py b/homeassistant/helpers/start.py index 30e8466070e..148b416e087 100644 --- a/homeassistant/helpers/start.py +++ b/homeassistant/helpers/start.py @@ -1,4 +1,5 @@ """Helpers to help during startup.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 673cdf48bc5..71b1b2658e2 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -1,4 +1,5 @@ """Helpers that help with state related things.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 39cfac885cc..2af21b070da 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -1,4 +1,5 @@ """Helper to help store data.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/sun.py b/homeassistant/helpers/sun.py index cf944dfc479..a490a7a8213 100644 --- a/homeassistant/helpers/sun.py +++ b/homeassistant/helpers/sun.py @@ -1,4 +1,5 @@ """Helpers for sun events.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index 31097ec923e..a01c3aad377 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -1,4 +1,5 @@ """Helper to gather system info.""" + from __future__ import annotations from functools import cache diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py index 15d38063f63..0311486fdd2 100644 --- a/homeassistant/helpers/temperature.py +++ b/homeassistant/helpers/temperature.py @@ -1,4 +1,5 @@ """Temperature helpers for Home Assistant.""" + from __future__ import annotations from numbers import Number diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 287e8b065b9..62652e15be9 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,4 +1,5 @@ """Template helper methods for rendering strings with Home Assistant data.""" + from __future__ import annotations from ast import literal_eval diff --git a/homeassistant/helpers/trace.py b/homeassistant/helpers/trace.py index 21154914f17..1f5aa47f4e2 100644 --- a/homeassistant/helpers/trace.py +++ b/homeassistant/helpers/trace.py @@ -1,4 +1,5 @@ """Helpers for script and condition tracing.""" + from __future__ import annotations from collections import deque diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index b799469e2d3..40e8351a3ac 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -1,4 +1,5 @@ """Translation string lookup helpers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/trigger.py b/homeassistant/helpers/trigger.py index d98b24f9afd..cb14102cb04 100644 --- a/homeassistant/helpers/trigger.py +++ b/homeassistant/helpers/trigger.py @@ -1,4 +1,5 @@ """Triggers.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/helpers/trigger_template_entity.py b/homeassistant/helpers/trigger_template_entity.py index bc7deceefef..7b1c4ab8078 100644 --- a/homeassistant/helpers/trigger_template_entity.py +++ b/homeassistant/helpers/trigger_template_entity.py @@ -1,4 +1,5 @@ """TemplateEntity utility class.""" + from __future__ import annotations import contextlib diff --git a/homeassistant/helpers/typing.py b/homeassistant/helpers/typing.py index 4b94e153aef..0f372689809 100644 --- a/homeassistant/helpers/typing.py +++ b/homeassistant/helpers/typing.py @@ -1,4 +1,5 @@ """Typing Helpers for Home Assistant.""" + from collections.abc import Mapping from enum import Enum from typing import Any, TypeVar diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index c3f2c60c8c3..42d7232a4cb 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -1,4 +1,5 @@ """Helpers to help coordinate updates.""" + from __future__ import annotations from abc import abstractmethod diff --git a/homeassistant/loader.py b/homeassistant/loader.py index cba7df278f2..57c91f84818 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -3,6 +3,7 @@ This module has quite some complex parts. I have tried to add as much documentation as possible to keep it understandable. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index f1add1ff3f8..e78398ebf03 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -1,4 +1,5 @@ """Module to handle installing requirements.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/runner.py b/homeassistant/runner.py index 622e69ecf8c..f036c7d6322 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -1,4 +1,5 @@ """Run Home Assistant.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/scripts/__init__.py b/homeassistant/scripts/__init__.py index 5b781d4eb37..f0600b70f48 100644 --- a/homeassistant/scripts/__init__.py +++ b/homeassistant/scripts/__init__.py @@ -1,4 +1,5 @@ """Home Assistant command line scripts.""" + from __future__ import annotations import argparse diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index a04493a8935..c4c47f06418 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -1,4 +1,5 @@ """Script to run benchmarks.""" + from __future__ import annotations import argparse diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 5a9f62c938e..f688a96b11d 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -1,4 +1,5 @@ """Script to check the configuration file.""" + from __future__ import annotations import argparse diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 7da82156c3f..3ec72654a64 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -1,4 +1,5 @@ """All methods needed to bootstrap a Home Assistant instance.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index 0a4b156fc8c..1ee33bdd173 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -1,4 +1,5 @@ """Helper methods for various modules.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/util/aiohttp.py b/homeassistant/util/aiohttp.py index ceb5e502221..94906e29f00 100644 --- a/homeassistant/util/aiohttp.py +++ b/homeassistant/util/aiohttp.py @@ -1,4 +1,5 @@ """Utilities to help with aiohttp.""" + from __future__ import annotations from http import HTTPStatus diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 4ec5028252e..acf5d8e6639 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -1,4 +1,5 @@ """Asyncio utilities.""" + from __future__ import annotations from asyncio import AbstractEventLoop, Future, Semaphore, Task, gather, get_running_loop diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 0ab4ac8c6c1..ab5c4037f9b 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -1,4 +1,5 @@ """Color util methods.""" + from __future__ import annotations import colorsys diff --git a/homeassistant/util/decorator.py b/homeassistant/util/decorator.py index c648f6f1cab..5bd817de103 100644 --- a/homeassistant/util/decorator.py +++ b/homeassistant/util/decorator.py @@ -1,4 +1,5 @@ """Decorator utility functions.""" + from __future__ import annotations from collections.abc import Callable, Hashable diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 47863d32e67..5fa27c826b8 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -1,4 +1,5 @@ """Helper methods to handle the time in Home Assistant.""" + from __future__ import annotations import bisect diff --git a/homeassistant/util/enum.py b/homeassistant/util/enum.py index f0de2206f1f..d0ef010f8bb 100644 --- a/homeassistant/util/enum.py +++ b/homeassistant/util/enum.py @@ -1,4 +1,5 @@ """Helpers for working with enums.""" + from collections.abc import Callable import contextlib from enum import Enum diff --git a/homeassistant/util/executor.py b/homeassistant/util/executor.py index 145c2ba4f04..cfd81e26e34 100644 --- a/homeassistant/util/executor.py +++ b/homeassistant/util/executor.py @@ -1,4 +1,5 @@ """Executor util helpers.""" + from __future__ import annotations from concurrent.futures import ThreadPoolExecutor diff --git a/homeassistant/util/file.py b/homeassistant/util/file.py index 1af65fa51d7..6d1c9b6e522 100644 --- a/homeassistant/util/file.py +++ b/homeassistant/util/file.py @@ -1,4 +1,5 @@ """File utility functions.""" + from __future__ import annotations import logging diff --git a/homeassistant/util/frozen_dataclass_compat.py b/homeassistant/util/frozen_dataclass_compat.py index 858084bcabc..2bbcadb4ccf 100644 --- a/homeassistant/util/frozen_dataclass_compat.py +++ b/homeassistant/util/frozen_dataclass_compat.py @@ -3,6 +3,7 @@ This module enabled a non-breaking transition from mutable to frozen dataclasses derived from EntityDescription and sub classes thereof. """ + from __future__ import annotations import dataclasses diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py index 3a337cf0e18..9a30ae8f104 100644 --- a/homeassistant/util/json.py +++ b/homeassistant/util/json.py @@ -1,4 +1,5 @@ """JSON utility functions.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/util/language.py b/homeassistant/util/language.py index 73db81c91ce..38fa22730a6 100644 --- a/homeassistant/util/language.py +++ b/homeassistant/util/language.py @@ -1,4 +1,5 @@ """Helper methods for language selection in Home Assistant.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/homeassistant/util/limited_size_dict.py b/homeassistant/util/limited_size_dict.py index a12618af0ec..6166a6c8239 100644 --- a/homeassistant/util/limited_size_dict.py +++ b/homeassistant/util/limited_size_dict.py @@ -1,4 +1,5 @@ """Helpers for script and automation tracing and debugging.""" + from __future__ import annotations from collections import OrderedDict diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index d2179ecd112..24c49c5427c 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -2,6 +2,7 @@ detect_location_info and elevation are mocked by default during tests. """ + from __future__ import annotations from functools import lru_cache diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 0f86cde50fe..ac85691b7c2 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -1,4 +1,5 @@ """Logging utilities.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py index 46eaece25c4..08a2c2a3967 100644 --- a/homeassistant/util/network.py +++ b/homeassistant/util/network.py @@ -1,4 +1,5 @@ """Network utilities.""" + from __future__ import annotations from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index ce6276ef4d4..44f9be3272f 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -1,4 +1,5 @@ """Helpers to install PyPi packages.""" + from __future__ import annotations import asyncio diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index cc4835022d3..5f2a1193d96 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -1,4 +1,5 @@ """Percentage util functions.""" + from __future__ import annotations from typing import TypeVar diff --git a/homeassistant/util/pil.py b/homeassistant/util/pil.py index 58dd48dec5e..733f640ce48 100644 --- a/homeassistant/util/pil.py +++ b/homeassistant/util/pil.py @@ -2,6 +2,7 @@ Can only be used by integrations that have pillow in their requirements. """ + from __future__ import annotations from PIL.ImageDraw import ImageDraw diff --git a/homeassistant/util/read_only_dict.py b/homeassistant/util/read_only_dict.py index bb93dd41298..90245ce7ca9 100644 --- a/homeassistant/util/read_only_dict.py +++ b/homeassistant/util/read_only_dict.py @@ -1,4 +1,5 @@ """Read only dictionary.""" + from typing import Any, TypeVar diff --git a/homeassistant/util/scaling.py b/homeassistant/util/scaling.py index 70e2ac2516a..9b2a4f4afcb 100644 --- a/homeassistant/util/scaling.py +++ b/homeassistant/util/scaling.py @@ -1,4 +1,5 @@ """Scaling util functions.""" + from __future__ import annotations diff --git a/homeassistant/util/timeout.py b/homeassistant/util/timeout.py index 33cc0bd18e6..cf73ee6b220 100644 --- a/homeassistant/util/timeout.py +++ b/homeassistant/util/timeout.py @@ -3,6 +3,7 @@ Set of helper classes to handle timeouts of tasks with advanced options like zones and freezing of timeouts. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/util/ulid.py b/homeassistant/util/ulid.py index 818b8015549..65f1b8226c0 100644 --- a/homeassistant/util/ulid.py +++ b/homeassistant/util/ulid.py @@ -1,4 +1,5 @@ """Helpers to generate ulids.""" + from __future__ import annotations from ulid_transform import ( diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 18318f89da4..04ce0715192 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -1,4 +1,5 @@ """Typing Helpers for Home Assistant.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/util/unit_system.py b/homeassistant/util/unit_system.py index c9da324e8a5..bd31b4286ab 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -1,4 +1,5 @@ """Unit system helper class and methods.""" + from __future__ import annotations from numbers import Number diff --git a/homeassistant/util/variance.py b/homeassistant/util/variance.py index d28bdc9d63e..974a0f88e90 100644 --- a/homeassistant/util/variance.py +++ b/homeassistant/util/variance.py @@ -1,4 +1,5 @@ """Util functions to help filter out similar results.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/util/yaml/__init__.py b/homeassistant/util/yaml/__init__.py index fe4f01677cd..cf90b223cb6 100644 --- a/homeassistant/util/yaml/__init__.py +++ b/homeassistant/util/yaml/__init__.py @@ -1,4 +1,5 @@ """YAML utility functions.""" + from .const import SECRET_YAML from .dumper import dump, save_yaml from .input import UndefinedSubstitution, extract_inputs, substitute diff --git a/homeassistant/util/yaml/dumper.py b/homeassistant/util/yaml/dumper.py index ec4700ef17e..61772b6989d 100644 --- a/homeassistant/util/yaml/dumper.py +++ b/homeassistant/util/yaml/dumper.py @@ -1,4 +1,5 @@ """Custom dumper and representers.""" + from collections import OrderedDict from typing import Any diff --git a/homeassistant/util/yaml/input.py b/homeassistant/util/yaml/input.py index ab5948db605..ff9b37f18f1 100644 --- a/homeassistant/util/yaml/input.py +++ b/homeassistant/util/yaml/input.py @@ -1,4 +1,5 @@ """Deal with YAML input.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 055284b1e18..2a2883ed544 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -1,4 +1,5 @@ """Custom loader.""" + from __future__ import annotations from collections.abc import Callable, Iterator diff --git a/homeassistant/util/yaml/objects.py b/homeassistant/util/yaml/objects.py index 6aedc85cf60..70c229c1a2f 100644 --- a/homeassistant/util/yaml/objects.py +++ b/homeassistant/util/yaml/objects.py @@ -1,4 +1,5 @@ """Custom yaml object types.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/pylint/plugins/hass_enforce_coordinator_module.py b/pylint/plugins/hass_enforce_coordinator_module.py index 3546632547b..924b69f1b86 100644 --- a/pylint/plugins/hass_enforce_coordinator_module.py +++ b/pylint/plugins/hass_enforce_coordinator_module.py @@ -1,4 +1,5 @@ """Plugin for checking if coordinator is in its own module.""" + from __future__ import annotations from astroid import nodes diff --git a/pylint/plugins/hass_enforce_sorted_platforms.py b/pylint/plugins/hass_enforce_sorted_platforms.py index f597a7a1191..aa6a6c16efa 100644 --- a/pylint/plugins/hass_enforce_sorted_platforms.py +++ b/pylint/plugins/hass_enforce_sorted_platforms.py @@ -1,4 +1,5 @@ """Plugin for checking sorted platforms list.""" + from __future__ import annotations from astroid import nodes diff --git a/pylint/plugins/hass_enforce_super_call.py b/pylint/plugins/hass_enforce_super_call.py index f2efb8bc8a2..b0f523aef72 100644 --- a/pylint/plugins/hass_enforce_super_call.py +++ b/pylint/plugins/hass_enforce_super_call.py @@ -1,4 +1,5 @@ """Plugin for checking super calls.""" + from __future__ import annotations from astroid import nodes diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index c3c74c95c84..21438ab48ff 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -1,4 +1,5 @@ """Plugin to enforce type hints on specific functions.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/pylint/plugins/hass_imports.py b/pylint/plugins/hass_imports.py index f28986b90e2..b8ec65e4460 100644 --- a/pylint/plugins/hass_imports.py +++ b/pylint/plugins/hass_imports.py @@ -1,4 +1,5 @@ """Plugin for checking imports.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/pylint/plugins/hass_inheritance.py b/pylint/plugins/hass_inheritance.py index 7ae24ec6e6d..e386986fa23 100644 --- a/pylint/plugins/hass_inheritance.py +++ b/pylint/plugins/hass_inheritance.py @@ -1,4 +1,5 @@ """Plugin to enforce type hints on specific functions.""" + from __future__ import annotations import re diff --git a/pylint/plugins/hass_logger.py b/pylint/plugins/hass_logger.py index e92fad2bdc0..6cbb72d4f78 100644 --- a/pylint/plugins/hass_logger.py +++ b/pylint/plugins/hass_logger.py @@ -1,4 +1,5 @@ """Plugin for logger invocations.""" + from __future__ import annotations from astroid import nodes diff --git a/script/const.py b/script/const.py index de9b559e634..2f0b12784b5 100644 --- a/script/const.py +++ b/script/const.py @@ -1,4 +1,5 @@ """Script constants.""" + from pathlib import Path COMPONENT_DIR = Path("homeassistant/components") diff --git a/script/countries.py b/script/countries.py index 0d776f0805d..d67caa4da65 100644 --- a/script/countries.py +++ b/script/countries.py @@ -3,6 +3,7 @@ ISO does not publish a machine readable list free of charge, so the list is generated with help of the pycountry package. """ + from pathlib import Path import pycountry diff --git a/script/currencies.py b/script/currencies.py index 753b3363626..388df83e088 100644 --- a/script/currencies.py +++ b/script/currencies.py @@ -1,4 +1,5 @@ """Helper script to update currency list from the official source.""" + from pathlib import Path from bs4 import BeautifulSoup diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index e3fb7c390c0..aea5548aa76 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Generate updated constraint and requirements files.""" + from __future__ import annotations import difflib diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index 308c006defc..bcb19a14c37 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -1,4 +1,5 @@ """Validate manifests.""" + from __future__ import annotations import argparse diff --git a/script/hassfest/application_credentials.py b/script/hassfest/application_credentials.py index 1be644054c4..40cd15b4676 100644 --- a/script/hassfest/application_credentials.py +++ b/script/hassfest/application_credentials.py @@ -1,4 +1,5 @@ """Generate application_credentials data.""" + from __future__ import annotations from .model import Config, Integration diff --git a/script/hassfest/bluetooth.py b/script/hassfest/bluetooth.py index 295bcac1d1e..9f11c36360e 100644 --- a/script/hassfest/bluetooth.py +++ b/script/hassfest/bluetooth.py @@ -1,4 +1,5 @@ """Generate bluetooth file.""" + from __future__ import annotations from .model import Config, Integration diff --git a/script/hassfest/brand.py b/script/hassfest/brand.py index 80f7416a018..fe47d31067a 100644 --- a/script/hassfest/brand.py +++ b/script/hassfest/brand.py @@ -1,4 +1,5 @@ """Brand validation.""" + from __future__ import annotations import voluptuous as vol diff --git a/script/hassfest/codeowners.py b/script/hassfest/codeowners.py index 458391b1fb4..15e34c23416 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -1,4 +1,5 @@ """Generate CODEOWNERS.""" + from __future__ import annotations from .model import Config, Integration diff --git a/script/hassfest/config_flow.py b/script/hassfest/config_flow.py index 3f5479fd118..382e77bde74 100644 --- a/script/hassfest/config_flow.py +++ b/script/hassfest/config_flow.py @@ -1,4 +1,5 @@ """Generate config flow file.""" + from __future__ import annotations import json diff --git a/script/hassfest/config_schema.py b/script/hassfest/config_schema.py index da2de9a6013..9f652691887 100644 --- a/script/hassfest/config_schema.py +++ b/script/hassfest/config_schema.py @@ -1,4 +1,5 @@ """Validate integrations which can be setup from YAML have config schemas.""" + from __future__ import annotations import ast diff --git a/script/hassfest/coverage.py b/script/hassfest/coverage.py index b2ee99e896b..64951fb0288 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -1,4 +1,5 @@ """Validate coverage files.""" + from __future__ import annotations from pathlib import Path diff --git a/script/hassfest/dependencies.py b/script/hassfest/dependencies.py index 31fd31dfc96..d9ec114e5bb 100644 --- a/script/hassfest/dependencies.py +++ b/script/hassfest/dependencies.py @@ -1,4 +1,5 @@ """Validate dependencies.""" + from __future__ import annotations import ast diff --git a/script/hassfest/dhcp.py b/script/hassfest/dhcp.py index 882e39f300e..bc4c78544c4 100644 --- a/script/hassfest/dhcp.py +++ b/script/hassfest/dhcp.py @@ -1,4 +1,5 @@ """Generate dhcp file.""" + from __future__ import annotations from .model import Config, Integration diff --git a/script/hassfest/docker.py b/script/hassfest/docker.py index 2856c1ee0ea..49b457f8a74 100644 --- a/script/hassfest/docker.py +++ b/script/hassfest/docker.py @@ -1,4 +1,5 @@ """Generate and validate the dockerfile.""" + from homeassistant import core from homeassistant.util import executor, thread diff --git a/script/hassfest/icons.py b/script/hassfest/icons.py index 60dc2b79f56..17f7d6075ce 100644 --- a/script/hassfest/icons.py +++ b/script/hassfest/icons.py @@ -1,4 +1,5 @@ """Validate integration icon translation files.""" + from __future__ import annotations from typing import Any diff --git a/script/hassfest/json.py b/script/hassfest/json.py index 5e3d05f78dc..5e4568cd241 100644 --- a/script/hassfest/json.py +++ b/script/hassfest/json.py @@ -1,4 +1,5 @@ """Validate integration JSON files.""" + from __future__ import annotations import json diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 6339d8293ec..d8637ad8962 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -1,4 +1,5 @@ """Manifest validation.""" + from __future__ import annotations from enum import IntEnum diff --git a/script/hassfest/model.py b/script/hassfest/model.py index 7df65b8221e..736fb6874be 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -1,4 +1,5 @@ """Models for manifest validator.""" + from __future__ import annotations from dataclasses import dataclass, field diff --git a/script/hassfest/mqtt.py b/script/hassfest/mqtt.py index 2619e22911a..b2112d9bb6a 100644 --- a/script/hassfest/mqtt.py +++ b/script/hassfest/mqtt.py @@ -1,4 +1,5 @@ """Generate MQTT file.""" + from __future__ import annotations from collections import defaultdict diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 5513105fa17..c02ebd8de2e 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -1,4 +1,5 @@ """Generate mypy config.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index 8b9f73336fe..18d560f840f 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -1,4 +1,5 @@ """Validate requirements.""" + from __future__ import annotations from collections import deque diff --git a/script/hassfest/serializer.py b/script/hassfest/serializer.py index b56306a8d7e..1de4c48a0c4 100644 --- a/script/hassfest/serializer.py +++ b/script/hassfest/serializer.py @@ -1,4 +1,5 @@ """Hassfest utils.""" + from __future__ import annotations from collections.abc import Collection, Iterable, Mapping diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 8f59d33830d..8489a3a9d68 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -1,4 +1,5 @@ """Validate dependencies.""" + from __future__ import annotations import contextlib diff --git a/script/hassfest/ssdp.py b/script/hassfest/ssdp.py index de548ee2992..0a61284eb46 100644 --- a/script/hassfest/ssdp.py +++ b/script/hassfest/ssdp.py @@ -1,4 +1,5 @@ """Generate ssdp file.""" + from __future__ import annotations from collections import defaultdict diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 1501e4983c5..f595776febe 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -1,4 +1,5 @@ """Validate integration translation files.""" + from __future__ import annotations from functools import partial diff --git a/script/hassfest/usb.py b/script/hassfest/usb.py index c5e3148f7b1..0c7998f4fc2 100644 --- a/script/hassfest/usb.py +++ b/script/hassfest/usb.py @@ -1,4 +1,5 @@ """Generate usb file.""" + from __future__ import annotations from .model import Config, Integration diff --git a/script/hassfest/zeroconf.py b/script/hassfest/zeroconf.py index bb6a84e1e2c..63f10fcf294 100644 --- a/script/hassfest/zeroconf.py +++ b/script/hassfest/zeroconf.py @@ -1,4 +1,5 @@ """Generate zeroconf file.""" + from __future__ import annotations from collections import defaultdict diff --git a/script/microsoft_tts.py b/script/microsoft_tts.py index bc0b577c97c..f3761db2f71 100644 --- a/script/microsoft_tts.py +++ b/script/microsoft_tts.py @@ -1,4 +1,5 @@ """Helper script to update supported languages for Microsoft text-to-speech (TTS).""" + from pathlib import Path from lxml import html diff --git a/script/scaffold/const.py b/script/scaffold/const.py index cf66bb4e2ae..2f8126b3393 100644 --- a/script/scaffold/const.py +++ b/script/scaffold/const.py @@ -1,4 +1,5 @@ """Constants for scaffolding.""" + from pathlib import Path COMPONENT_DIR = Path("homeassistant/components") diff --git a/script/scaffold/docs.py b/script/scaffold/docs.py index 3ce86b0e138..a969fa699dd 100644 --- a/script/scaffold/docs.py +++ b/script/scaffold/docs.py @@ -1,4 +1,5 @@ """Print links to relevant docs.""" + from .model import Info DATA = { diff --git a/script/scaffold/generate.py b/script/scaffold/generate.py index 197c36e22d1..0bee69b93f8 100644 --- a/script/scaffold/generate.py +++ b/script/scaffold/generate.py @@ -1,4 +1,5 @@ """Generate an integration.""" + from pathlib import Path from .model import Info diff --git a/script/scaffold/model.py b/script/scaffold/model.py index 2b1ee71fc63..3b5a5e50fe4 100644 --- a/script/scaffold/model.py +++ b/script/scaffold/model.py @@ -1,4 +1,5 @@ """Models for scaffolding.""" + from __future__ import annotations import json diff --git a/script/scaffold/templates/backup/integration/backup.py b/script/scaffold/templates/backup/integration/backup.py index 88df5ead221..ab3eb1be440 100644 --- a/script/scaffold/templates/backup/integration/backup.py +++ b/script/scaffold/templates/backup/integration/backup.py @@ -1,4 +1,5 @@ """Backup platform for the NEW_NAME integration.""" + from homeassistant.core import HomeAssistant diff --git a/script/scaffold/templates/backup/tests/test_backup.py b/script/scaffold/templates/backup/tests/test_backup.py index 43d23ac7a90..9f3c2be657c 100644 --- a/script/scaffold/templates/backup/tests/test_backup.py +++ b/script/scaffold/templates/backup/tests/test_backup.py @@ -1,4 +1,5 @@ """Test the NEW_NAME backup platform.""" + from homeassistant.components.NEW_DOMAIN.backup import ( async_post_backup, async_pre_backup, diff --git a/script/scaffold/templates/config_flow/integration/__init__.py b/script/scaffold/templates/config_flow/integration/__init__.py index 4d74c5d41ff..87391f1733e 100644 --- a/script/scaffold/templates/config_flow/integration/__init__.py +++ b/script/scaffold/templates/config_flow/integration/__init__.py @@ -1,4 +1,5 @@ """The NEW_NAME integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/script/scaffold/templates/config_flow/integration/config_flow.py b/script/scaffold/templates/config_flow/integration/config_flow.py index f68059584f7..797ca5c7066 100644 --- a/script/scaffold/templates/config_flow/integration/config_flow.py +++ b/script/scaffold/templates/config_flow/integration/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NEW_NAME integration.""" + from __future__ import annotations import logging diff --git a/script/scaffold/templates/config_flow/tests/conftest.py b/script/scaffold/templates/config_flow/tests/conftest.py index 05993acc329..84b6bb381bf 100644 --- a/script/scaffold/templates/config_flow/tests/conftest.py +++ b/script/scaffold/templates/config_flow/tests/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the NEW_NAME tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/script/scaffold/templates/config_flow/tests/test_config_flow.py b/script/scaffold/templates/config_flow/tests/test_config_flow.py index bb9e6380cdc..9a712834bae 100644 --- a/script/scaffold/templates/config_flow/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow/tests/test_config_flow.py @@ -1,4 +1,5 @@ """Test the NEW_NAME config flow.""" + from unittest.mock import AsyncMock, patch from homeassistant import config_entries diff --git a/script/scaffold/templates/config_flow_discovery/integration/__init__.py b/script/scaffold/templates/config_flow_discovery/integration/__init__.py index efa59ddd82a..4d18fecc2fa 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/__init__.py +++ b/script/scaffold/templates/config_flow_discovery/integration/__init__.py @@ -1,4 +1,5 @@ """The NEW_NAME integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/script/scaffold/templates/config_flow_helper/integration/__init__.py b/script/scaffold/templates/config_flow_helper/integration/__init__.py index 2ad917394b9..c8817fb76ad 100644 --- a/script/scaffold/templates/config_flow_helper/integration/__init__.py +++ b/script/scaffold/templates/config_flow_helper/integration/__init__.py @@ -1,4 +1,5 @@ """The NEW_NAME integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/script/scaffold/templates/config_flow_helper/integration/config_flow.py b/script/scaffold/templates/config_flow_helper/integration/config_flow.py index f9b8e81d364..5d89fec2da2 100644 --- a/script/scaffold/templates/config_flow_helper/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_helper/integration/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NEW_NAME integration.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/script/scaffold/templates/config_flow_helper/integration/sensor.py b/script/scaffold/templates/config_flow_helper/integration/sensor.py index fbf92bfdd6b..741b2e85eb2 100644 --- a/script/scaffold/templates/config_flow_helper/integration/sensor.py +++ b/script/scaffold/templates/config_flow_helper/integration/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for NEW_NAME integration.""" + from __future__ import annotations from homeassistant.components.sensor import SensorEntity diff --git a/script/scaffold/templates/config_flow_helper/tests/conftest.py b/script/scaffold/templates/config_flow_helper/tests/conftest.py index 05993acc329..84b6bb381bf 100644 --- a/script/scaffold/templates/config_flow_helper/tests/conftest.py +++ b/script/scaffold/templates/config_flow_helper/tests/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the NEW_NAME tests.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py b/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py index d21c66797d8..085ae289f65 100644 --- a/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py @@ -1,4 +1,5 @@ """Test the NEW_NAME config flow.""" + from unittest.mock import AsyncMock import pytest diff --git a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py index 213740005e5..7e7641a535b 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/__init__.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/__init__.py @@ -1,4 +1,5 @@ """The NEW_NAME integration.""" + from __future__ import annotations from homeassistant.config_entries import ConfigEntry diff --git a/script/scaffold/templates/config_flow_oauth2/integration/api.py b/script/scaffold/templates/config_flow_oauth2/integration/api.py index 9ae65bb4d85..3f4aa3cfb82 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/api.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/api.py @@ -1,4 +1,5 @@ """API for NEW_NAME bound to Home Assistant OAuth.""" + from asyncio import run_coroutine_threadsafe from aiohttp import ClientSession diff --git a/script/scaffold/templates/device_action/integration/device_action.py b/script/scaffold/templates/device_action/integration/device_action.py index 4732d9bd71c..02be0afa602 100644 --- a/script/scaffold/templates/device_action/integration/device_action.py +++ b/script/scaffold/templates/device_action/integration/device_action.py @@ -1,4 +1,5 @@ """Provides device actions for NEW_NAME.""" + from __future__ import annotations import voluptuous as vol diff --git a/script/scaffold/templates/device_condition/integration/device_condition.py b/script/scaffold/templates/device_condition/integration/device_condition.py index 00acd23698a..ba7752a6d60 100644 --- a/script/scaffold/templates/device_condition/integration/device_condition.py +++ b/script/scaffold/templates/device_condition/integration/device_condition.py @@ -1,4 +1,5 @@ """Provide the device conditions for NEW_NAME.""" + from __future__ import annotations import voluptuous as vol diff --git a/script/scaffold/templates/device_condition/tests/test_device_condition.py b/script/scaffold/templates/device_condition/tests/test_device_condition.py index 1f6e0ffde3c..ad6d527bd65 100644 --- a/script/scaffold/templates/device_condition/tests/test_device_condition.py +++ b/script/scaffold/templates/device_condition/tests/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for NEW_NAME device conditions.""" + from __future__ import annotations import pytest diff --git a/script/scaffold/templates/device_trigger/integration/device_trigger.py b/script/scaffold/templates/device_trigger/integration/device_trigger.py index 4179bf5248a..8dcf438efc0 100644 --- a/script/scaffold/templates/device_trigger/integration/device_trigger.py +++ b/script/scaffold/templates/device_trigger/integration/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for NEW_NAME.""" + from __future__ import annotations from typing import Any diff --git a/script/scaffold/templates/integration/integration/__init__.py b/script/scaffold/templates/integration/integration/__init__.py index e30cd400bf2..66da2698015 100644 --- a/script/scaffold/templates/integration/integration/__init__.py +++ b/script/scaffold/templates/integration/integration/__init__.py @@ -1,4 +1,5 @@ """The NEW_NAME integration.""" + from __future__ import annotations import voluptuous as vol diff --git a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py index 4247a1dc8d2..ae1eb622e5b 100644 --- a/script/scaffold/templates/reproduce_state/integration/reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/integration/reproduce_state.py @@ -1,4 +1,5 @@ """Reproduce an NEW_NAME state.""" + from __future__ import annotations import asyncio diff --git a/script/scaffold/templates/significant_change/integration/significant_change.py b/script/scaffold/templates/significant_change/integration/significant_change.py index 8e6022f171b..803edc035b2 100644 --- a/script/scaffold/templates/significant_change/integration/significant_change.py +++ b/script/scaffold/templates/significant_change/integration/significant_change.py @@ -1,4 +1,5 @@ """Helper to test significant NEW_NAME state changes.""" + from __future__ import annotations from typing import Any diff --git a/script/scaffold/templates/significant_change/tests/test_significant_change.py b/script/scaffold/templates/significant_change/tests/test_significant_change.py index 89194cc52a3..519a80d8de6 100644 --- a/script/scaffold/templates/significant_change/tests/test_significant_change.py +++ b/script/scaffold/templates/significant_change/tests/test_significant_change.py @@ -1,4 +1,5 @@ """Test the NEW_NAME significant change platform.""" + from homeassistant.components.NEW_DOMAIN.significant_change import ( async_check_significant_change, ) diff --git a/script/translations/download.py b/script/translations/download.py index d02b5c869aa..958a4b35a7b 100755 --- a/script/translations/download.py +++ b/script/translations/download.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Merge all translation sources into a single JSON file.""" + from __future__ import annotations import json diff --git a/script/translations/lokalise.py b/script/translations/lokalise.py index a23291169f4..5cd0a695471 100644 --- a/script/translations/lokalise.py +++ b/script/translations/lokalise.py @@ -1,4 +1,5 @@ """API for Lokalise.""" + from __future__ import annotations from pprint import pprint diff --git a/tests/auth/mfa_modules/test_insecure_example.py b/tests/auth/mfa_modules/test_insecure_example.py index d22bfaa0719..f7f8a327059 100644 --- a/tests/auth/mfa_modules/test_insecure_example.py +++ b/tests/auth/mfa_modules/test_insecure_example.py @@ -1,4 +1,5 @@ """Test the example module auth module.""" + from homeassistant import auth, data_entry_flow from homeassistant.auth.mfa_modules import auth_mfa_module_from_config from homeassistant.auth.models import Credentials diff --git a/tests/auth/permissions/test_merge.py b/tests/auth/permissions/test_merge.py index f6251e52998..aeba076b170 100644 --- a/tests/auth/permissions/test_merge.py +++ b/tests/auth/permissions/test_merge.py @@ -1,4 +1,5 @@ """Tests for permissions merging.""" + from homeassistant.auth.permissions.merge import merge_policies diff --git a/tests/auth/permissions/test_system_policies.py b/tests/auth/permissions/test_system_policies.py index c6d9202524b..81b53154485 100644 --- a/tests/auth/permissions/test_system_policies.py +++ b/tests/auth/permissions/test_system_policies.py @@ -1,4 +1,5 @@ """Test system policies.""" + from homeassistant.auth.permissions import ( POLICY_SCHEMA, PolicyPermissions, diff --git a/tests/auth/providers/test_insecure_example.py b/tests/auth/providers/test_insecure_example.py index ceb8b02ae65..f0043231c04 100644 --- a/tests/auth/providers/test_insecure_example.py +++ b/tests/auth/providers/test_insecure_example.py @@ -1,4 +1,5 @@ """Tests for the insecure example auth provider.""" + from unittest.mock import AsyncMock import uuid diff --git a/tests/auth/providers/test_trusted_networks.py b/tests/auth/providers/test_trusted_networks.py index 3ccff990b9c..2f84a256f2d 100644 --- a/tests/auth/providers/test_trusted_networks.py +++ b/tests/auth/providers/test_trusted_networks.py @@ -1,4 +1,5 @@ """Test the Trusted Networks auth provider.""" + from ipaddress import ip_address, ip_network from unittest.mock import Mock, patch diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py index b561b17112b..91db3fd51d4 100644 --- a/tests/auth/test_init.py +++ b/tests/auth/test_init.py @@ -1,4 +1,5 @@ """Tests for the Home Assistant auth module.""" + from datetime import timedelta import time from typing import Any diff --git a/tests/auth/test_models.py b/tests/auth/test_models.py index 3f0ad7acc1d..639e43287b2 100644 --- a/tests/auth/test_models.py +++ b/tests/auth/test_models.py @@ -1,4 +1,5 @@ """Tests for the auth models.""" + from homeassistant.auth import models, permissions diff --git a/tests/common.py b/tests/common.py index cc2b4fa2ad6..5b2314ee038 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,4 +1,5 @@ """Test the helper method for writing tests.""" + from __future__ import annotations import asyncio diff --git a/tests/conftest.py b/tests/conftest.py index 5753729213f..798e3980f7b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ """Set up some common test helper things.""" + from __future__ import annotations import asyncio diff --git a/tests/hassfest/test_dependencies.py b/tests/hassfest/test_dependencies.py index 98ccbe4eae6..92a7fa27dc4 100644 --- a/tests/hassfest/test_dependencies.py +++ b/tests/hassfest/test_dependencies.py @@ -20,6 +20,7 @@ def test_child_import(mock_collector) -> None: mock_collector.visit( ast.parse( """ + from homeassistant.components import child_import """ ) @@ -32,6 +33,7 @@ def test_subimport(mock_collector) -> None: mock_collector.visit( ast.parse( """ + from homeassistant.components.subimport.smart_home import EVENT_ALEXA_SMART_HOME """ ) @@ -44,6 +46,7 @@ def test_child_import_field(mock_collector) -> None: mock_collector.visit( ast.parse( """ + from homeassistant.components.child_import_field import bla """ ) @@ -95,6 +98,7 @@ def test_all_imports(mock_collector) -> None: mock_collector.visit( ast.parse( """ + from homeassistant.components import child_import from homeassistant.components.subimport.smart_home import EVENT_ALEXA_SMART_HOME diff --git a/tests/hassfest/test_requirements.py b/tests/hassfest/test_requirements.py index e6b247047e2..7cb08621e83 100644 --- a/tests/hassfest/test_requirements.py +++ b/tests/hassfest/test_requirements.py @@ -1,4 +1,5 @@ """Tests for hassfest requirements.""" + from pathlib import Path import pytest diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 1a9c2b0212e..7ec4e996629 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -1,4 +1,5 @@ """Test the aiohttp client helper.""" + from unittest.mock import Mock, patch import aiohttp diff --git a/tests/helpers/test_area_registry.py b/tests/helpers/test_area_registry.py index a1c18d41ec9..1ee8a42b6b9 100644 --- a/tests/helpers/test_area_registry.py +++ b/tests/helpers/test_area_registry.py @@ -1,4 +1,5 @@ """Tests for the Area Registry.""" + from typing import Any import pytest diff --git a/tests/helpers/test_collection.py b/tests/helpers/test_collection.py index a385ca8aeb6..6d2764afb16 100644 --- a/tests/helpers/test_collection.py +++ b/tests/helpers/test_collection.py @@ -1,4 +1,5 @@ """Tests for the collection helper.""" + from __future__ import annotations import logging diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index bcb6f4fa971..44a360af215 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,4 +1,5 @@ """Test the condition helper.""" + from datetime import datetime, timedelta from typing import Any from unittest.mock import AsyncMock, patch diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 71c81b096ca..f59b2163591 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -1,4 +1,5 @@ """Tests for the Config Entry Flow helper.""" + from collections.abc import Generator from unittest.mock import Mock, PropertyMock, patch diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index d237c24f6f3..f91cb3d2f35 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -1,4 +1,5 @@ """Tests for the Somfy config flow.""" + from http import HTTPStatus import logging import time diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 060800be62d..6481b8e134d 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,4 +1,5 @@ """Test config validators.""" + from collections import OrderedDict from datetime import date, datetime, timedelta import enum diff --git a/tests/helpers/test_deprecation.py b/tests/helpers/test_deprecation.py index f5089ecbaec..4cca4174e9c 100644 --- a/tests/helpers/test_deprecation.py +++ b/tests/helpers/test_deprecation.py @@ -1,4 +1,5 @@ """Test deprecation helpers.""" + from enum import StrEnum import logging import sys diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index cda117cca26..7f7398ec3de 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1,4 +1,5 @@ """Tests for the Device Registry.""" + from collections.abc import Iterable from contextlib import AbstractContextManager, nullcontext import time diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index d73bfe84607..dc4b2951b2f 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -1,4 +1,5 @@ """Test discovery helpers.""" + from unittest.mock import patch import pytest diff --git a/tests/helpers/test_discovery_flow.py b/tests/helpers/test_discovery_flow.py index 0b3386f8e04..7710eb2c7c7 100644 --- a/tests/helpers/test_discovery_flow.py +++ b/tests/helpers/test_discovery_flow.py @@ -1,4 +1,5 @@ """Test the discovery flow helper.""" + from unittest.mock import AsyncMock, call, patch import pytest diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py index add80c941a1..3dd708906b9 100644 --- a/tests/helpers/test_dispatcher.py +++ b/tests/helpers/test_dispatcher.py @@ -1,4 +1,5 @@ """Test dispatcher helpers.""" + from functools import partial import pytest diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 271494b60a6..15703af8103 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -1,4 +1,5 @@ """Tests for the Entity Registry.""" + from datetime import timedelta from typing import Any from unittest.mock import patch diff --git a/tests/helpers/test_entity_values.py b/tests/helpers/test_entity_values.py index f32db73a788..d41a3718f0c 100644 --- a/tests/helpers/test_entity_values.py +++ b/tests/helpers/test_entity_values.py @@ -1,4 +1,5 @@ """Test the entity values helper.""" + from collections import OrderedDict from homeassistant.helpers.entity_values import EntityValues as EV diff --git a/tests/helpers/test_entityfilter.py b/tests/helpers/test_entityfilter.py index 48bc8110ec5..6ea986f6d20 100644 --- a/tests/helpers/test_entityfilter.py +++ b/tests/helpers/test_entityfilter.py @@ -1,4 +1,5 @@ """The tests for the EntityFilter component.""" + from homeassistant.helpers.entityfilter import ( FILTER_SCHEMA, INCLUDE_EXCLUDE_FILTER_SCHEMA, diff --git a/tests/helpers/test_httpx_client.py b/tests/helpers/test_httpx_client.py index 52a6226c307..2d1baf88b16 100644 --- a/tests/helpers/test_httpx_client.py +++ b/tests/helpers/test_httpx_client.py @@ -1,4 +1,5 @@ """Test the httpx client helper.""" + from unittest.mock import Mock, patch import httpx diff --git a/tests/helpers/test_instance_id.py b/tests/helpers/test_instance_id.py index 8fb6bfd8d7e..3f1326bcd2e 100644 --- a/tests/helpers/test_instance_id.py +++ b/tests/helpers/test_instance_id.py @@ -1,4 +1,5 @@ """Tests for instance ID helper.""" + from json import JSONDecodeError from typing import Any from unittest.mock import patch diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index 70874a7a58c..c9f25e796bb 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -1,4 +1,5 @@ """Test integration platform helpers.""" + from collections.abc import Callable from types import ModuleType from unittest.mock import Mock, patch diff --git a/tests/helpers/test_issue_registry.py b/tests/helpers/test_issue_registry.py index 88f97a65421..66fc9662f75 100644 --- a/tests/helpers/test_issue_registry.py +++ b/tests/helpers/test_issue_registry.py @@ -1,4 +1,5 @@ """Test the repairs websocket API.""" + from typing import Any import pytest diff --git a/tests/helpers/test_location.py b/tests/helpers/test_location.py index 435df26b94c..4c14433188c 100644 --- a/tests/helpers/test_location.py +++ b/tests/helpers/test_location.py @@ -1,4 +1,5 @@ """Tests Home Assistant location helpers.""" + from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_LATITUDE, ATTR_LONGITUDE from homeassistant.core import HomeAssistant, State from homeassistant.helpers import location diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index 37603a99a8b..cbffdc88e19 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -1,4 +1,5 @@ """Test network helper.""" + from unittest.mock import Mock, patch import pytest diff --git a/tests/helpers/test_redact.py b/tests/helpers/test_redact.py index 73461012907..bdc06d77b49 100644 --- a/tests/helpers/test_redact.py +++ b/tests/helpers/test_redact.py @@ -1,4 +1,5 @@ """Test the data redation helper.""" + from homeassistant.helpers.redact import REDACTED, async_redact_data, partial_redact diff --git a/tests/helpers/test_registry.py b/tests/helpers/test_registry.py index 2782d3140f7..40e3d0cbb28 100644 --- a/tests/helpers/test_registry.py +++ b/tests/helpers/test_registry.py @@ -1,4 +1,5 @@ """Tests for the registry.""" + from typing import Any from freezegun.api import FrozenDateTimeFactory diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index 2a01439ccbd..4f8c51c5397 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -1,4 +1,5 @@ """The tests for the Restore component.""" + from collections.abc import Coroutine from datetime import datetime, timedelta import logging diff --git a/tests/helpers/test_schema_config_entry_flow.py b/tests/helpers/test_schema_config_entry_flow.py index 58f6a261aef..a23b0311b86 100644 --- a/tests/helpers/test_schema_config_entry_flow.py +++ b/tests/helpers/test_schema_config_entry_flow.py @@ -1,4 +1,5 @@ """Tests for the schema based data entry flows.""" + from __future__ import annotations from collections.abc import Mapping diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 00942b396e8..a01ee4ef69e 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -1,4 +1,5 @@ """Test selectors.""" + from enum import Enum import pytest diff --git a/tests/helpers/test_singleton.py b/tests/helpers/test_singleton.py index 81579202799..2278683f95e 100644 --- a/tests/helpers/test_singleton.py +++ b/tests/helpers/test_singleton.py @@ -1,4 +1,5 @@ """Test singleton helper.""" + from unittest.mock import Mock import pytest diff --git a/tests/helpers/test_storage_remove.py b/tests/helpers/test_storage_remove.py index affa3429af3..c5d0b4be921 100644 --- a/tests/helpers/test_storage_remove.py +++ b/tests/helpers/test_storage_remove.py @@ -1,4 +1,5 @@ """Tests for the storage helper with minimal mocking.""" + from datetime import timedelta import os from unittest.mock import patch diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 6ca93e79d5f..47230ef2e67 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -1,4 +1,5 @@ """Test Home Assistant template helper methods.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/helpers/test_trigger.py b/tests/helpers/test_trigger.py index 75394cf0e7a..0a15cf9a330 100644 --- a/tests/helpers/test_trigger.py +++ b/tests/helpers/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the trigger helper.""" + from unittest.mock import ANY, AsyncMock, MagicMock, call, patch import pytest diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 1e8ef93b872..25f72d76e3c 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -1,4 +1,5 @@ """Tests for the update coordinator.""" + from datetime import timedelta import logging from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/patch_time.py b/tests/patch_time.py index 5f5dc467c9d..3489d4a6baf 100644 --- a/tests/patch_time.py +++ b/tests/patch_time.py @@ -1,4 +1,5 @@ """Patch time related functions.""" + from __future__ import annotations import datetime diff --git a/tests/pylint/conftest.py b/tests/pylint/conftest.py index 2aeb5fbd5b7..2b0fdcf7df5 100644 --- a/tests/pylint/conftest.py +++ b/tests/pylint/conftest.py @@ -1,4 +1,5 @@ """Configuration for pylint tests.""" + from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path import sys diff --git a/tests/pylint/test_enforce_coordinator_module.py b/tests/pylint/test_enforce_coordinator_module.py index 746da8c1d7e..90d88246974 100644 --- a/tests/pylint/test_enforce_coordinator_module.py +++ b/tests/pylint/test_enforce_coordinator_module.py @@ -1,4 +1,5 @@ """Tests for pylint hass_enforce_coordinator_module plugin.""" + from __future__ import annotations import astroid diff --git a/tests/pylint/test_enforce_sorted_platforms.py b/tests/pylint/test_enforce_sorted_platforms.py index 71bde88bc13..d1e6d500cc3 100644 --- a/tests/pylint/test_enforce_sorted_platforms.py +++ b/tests/pylint/test_enforce_sorted_platforms.py @@ -1,4 +1,5 @@ """Tests for pylint hass_enforce_sorted_platforms plugin.""" + from __future__ import annotations import astroid diff --git a/tests/pylint/test_enforce_super_call.py b/tests/pylint/test_enforce_super_call.py index 5e2861b1c74..39a70369e0c 100644 --- a/tests/pylint/test_enforce_super_call.py +++ b/tests/pylint/test_enforce_super_call.py @@ -1,4 +1,5 @@ """Tests for pylint hass_enforce_super_call plugin.""" + from __future__ import annotations from types import ModuleType diff --git a/tests/scripts/test_init.py b/tests/scripts/test_init.py index 44090019a29..44edece1812 100644 --- a/tests/scripts/test_init.py +++ b/tests/scripts/test_init.py @@ -1,4 +1,5 @@ """Test script init.""" + from unittest.mock import patch import homeassistant.scripts as scripts diff --git a/tests/syrupy.py b/tests/syrupy.py index 9209654a607..348a618e90e 100644 --- a/tests/syrupy.py +++ b/tests/syrupy.py @@ -1,4 +1,5 @@ """Home Assistant extension for Syrupy.""" + from __future__ import annotations from contextlib import suppress diff --git a/tests/test_config.py b/tests/test_config.py index 73c541bc7e1..7a7f83b5e6b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,5 @@ """Test config utils.""" + from collections import OrderedDict import contextlib import copy diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 089d0f6b21b..00601173e71 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1,4 +1,5 @@ """Test the config manager.""" + from __future__ import annotations import asyncio diff --git a/tests/test_core.py b/tests/test_core.py index 9960e8a1671..9c78c670ba3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,4 +1,5 @@ """Test to verify that Home Assistant core works.""" + from __future__ import annotations import array diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3f4acd15440..3f18b3527b7 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,4 +1,5 @@ """Test to verify that Home Assistant exceptions work.""" + from __future__ import annotations import pytest diff --git a/tests/test_main.py b/tests/test_main.py index b676040252a..32371ae4e6f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,4 +1,5 @@ """Test methods in __main__.""" + from unittest.mock import PropertyMock, patch from homeassistant import __main__ as main diff --git a/tests/test_test_fixtures.py b/tests/test_test_fixtures.py index eb2103d4272..b240da3e31e 100644 --- a/tests/test_test_fixtures.py +++ b/tests/test_test_fixtures.py @@ -1,4 +1,5 @@ """Test test fixture configuration.""" + from http import HTTPStatus import socket diff --git a/tests/testing_config/custom_components/test/alarm_control_panel.py b/tests/testing_config/custom_components/test/alarm_control_panel.py index 7490a7703a4..204281af3d3 100644 --- a/tests/testing_config/custom_components/test/alarm_control_panel.py +++ b/tests/testing_config/custom_components/test/alarm_control_panel.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( AlarmControlPanelEntityFeature, diff --git a/tests/testing_config/custom_components/test/binary_sensor.py b/tests/testing_config/custom_components/test/binary_sensor.py index 6deaad22b30..e45e614a82c 100644 --- a/tests/testing_config/custom_components/test/binary_sensor.py +++ b/tests/testing_config/custom_components/test/binary_sensor.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity from tests.common import MockEntity diff --git a/tests/testing_config/custom_components/test/cover.py b/tests/testing_config/custom_components/test/cover.py index dc89b95981b..66ecd1c9dbc 100644 --- a/tests/testing_config/custom_components/test/cover.py +++ b/tests/testing_config/custom_components/test/cover.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from typing import Any from homeassistant.components.cover import CoverEntity, CoverEntityFeature diff --git a/tests/testing_config/custom_components/test/date.py b/tests/testing_config/custom_components/test/date.py index b35be6f1919..0a51bea029d 100644 --- a/tests/testing_config/custom_components/test/date.py +++ b/tests/testing_config/custom_components/test/date.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from datetime import date from homeassistant.components.date import DateEntity diff --git a/tests/testing_config/custom_components/test/datetime.py b/tests/testing_config/custom_components/test/datetime.py index ba511e81648..fa9dfff8a60 100644 --- a/tests/testing_config/custom_components/test/datetime.py +++ b/tests/testing_config/custom_components/test/datetime.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from datetime import UTC, datetime from homeassistant.components.datetime import DateTimeEntity diff --git a/tests/testing_config/custom_components/test/event.py b/tests/testing_config/custom_components/test/event.py index 9acb24f37cf..7dee8390148 100644 --- a/tests/testing_config/custom_components/test/event.py +++ b/tests/testing_config/custom_components/test/event.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.event import EventEntity from tests.common import MockEntity diff --git a/tests/testing_config/custom_components/test/fan.py b/tests/testing_config/custom_components/test/fan.py index 133f372f4fa..cc38972bc71 100644 --- a/tests/testing_config/custom_components/test/fan.py +++ b/tests/testing_config/custom_components/test/fan.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.fan import FanEntity, FanEntityFeature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback diff --git a/tests/testing_config/custom_components/test/light.py b/tests/testing_config/custom_components/test/light.py index e22aca289a8..eed98a8210a 100644 --- a/tests/testing_config/custom_components/test/light.py +++ b/tests/testing_config/custom_components/test/light.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.light import ColorMode, LightEntity from homeassistant.const import STATE_OFF, STATE_ON diff --git a/tests/testing_config/custom_components/test/lock.py b/tests/testing_config/custom_components/test/lock.py index 9cefa34363e..ba5a91e2d24 100644 --- a/tests/testing_config/custom_components/test/lock.py +++ b/tests/testing_config/custom_components/test/lock.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.lock import LockEntity, LockEntityFeature from tests.common import MockEntity diff --git a/tests/testing_config/custom_components/test/number.py b/tests/testing_config/custom_components/test/number.py index 3d9a7f4a8c7..aa4d6fdcd8f 100644 --- a/tests/testing_config/custom_components/test/number.py +++ b/tests/testing_config/custom_components/test/number.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.number import NumberEntity, RestoreNumber from tests.common import MockEntity diff --git a/tests/testing_config/custom_components/test/remote.py b/tests/testing_config/custom_components/test/remote.py index 6c5242a739e..541215f1c47 100644 --- a/tests/testing_config/custom_components/test/remote.py +++ b/tests/testing_config/custom_components/test/remote.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.remote import RemoteEntity from homeassistant.const import STATE_OFF, STATE_ON diff --git a/tests/testing_config/custom_components/test/select.py b/tests/testing_config/custom_components/test/select.py index d68b3008a50..fece370bdf1 100644 --- a/tests/testing_config/custom_components/test/select.py +++ b/tests/testing_config/custom_components/test/select.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.select import SelectEntity from tests.common import MockEntity diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index d436a94e329..b6040227574 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.sensor import ( DEVICE_CLASSES, RestoreSensor, diff --git a/tests/testing_config/custom_components/test/switch.py b/tests/testing_config/custom_components/test/switch.py index 6f132b85a3a..5a2cd7bc17d 100644 --- a/tests/testing_config/custom_components/test/switch.py +++ b/tests/testing_config/custom_components/test/switch.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.const import STATE_OFF, STATE_ON from tests.common import MockToggleEntity diff --git a/tests/testing_config/custom_components/test/text.py b/tests/testing_config/custom_components/test/text.py index b113423b4e3..d3b048747bf 100644 --- a/tests/testing_config/custom_components/test/text.py +++ b/tests/testing_config/custom_components/test/text.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from homeassistant.components.text import RestoreText, TextEntity, TextMode from tests.common import MockEntity diff --git a/tests/testing_config/custom_components/test/time.py b/tests/testing_config/custom_components/test/time.py index 9c2f991d694..998406d7830 100644 --- a/tests/testing_config/custom_components/test/time.py +++ b/tests/testing_config/custom_components/test/time.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from datetime import time from homeassistant.components.time import TimeEntity diff --git a/tests/testing_config/custom_components/test/update.py b/tests/testing_config/custom_components/test/update.py index 36b4e7c692f..40b5742b220 100644 --- a/tests/testing_config/custom_components/test/update.py +++ b/tests/testing_config/custom_components/test/update.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from __future__ import annotations import logging diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index 5afb6001b8a..a9ba1c99872 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + from __future__ import annotations from typing import Any diff --git a/tests/typing.py b/tests/typing.py index 7c5391d132c..3e6a7cd4bc3 100644 --- a/tests/typing.py +++ b/tests/typing.py @@ -1,4 +1,5 @@ """Typing helpers for Home Assistant tests.""" + from __future__ import annotations from collections.abc import Callable, Coroutine diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 3b6293d7c17..5716e4e524c 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -1,4 +1,5 @@ """Test Home Assistant date util methods.""" + from __future__ import annotations from datetime import UTC, datetime, timedelta diff --git a/tests/util/test_enum.py b/tests/util/test_enum.py index e975960bbe0..892c74dedef 100644 --- a/tests/util/test_enum.py +++ b/tests/util/test_enum.py @@ -1,4 +1,5 @@ """Test enum helpers.""" + from enum import Enum, IntEnum, IntFlag, StrEnum from typing import Any diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 6f560376ea4..759f0d6e5ea 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -1,4 +1,5 @@ """Test Home Assistant util methods.""" + from datetime import datetime, timedelta from unittest.mock import MagicMock, patch diff --git a/tests/util/test_json.py b/tests/util/test_json.py index ba07c7cbb6c..b4a52cb4b41 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -1,4 +1,5 @@ """Test Home Assistant json utility functions.""" + from pathlib import Path import re diff --git a/tests/util/test_language.py b/tests/util/test_language.py index cc4e5d66c31..a1d0cdf1a53 100644 --- a/tests/util/test_language.py +++ b/tests/util/test_language.py @@ -1,4 +1,5 @@ """Test Home Assistant language util methods.""" + from __future__ import annotations import pytest diff --git a/tests/util/test_location.py b/tests/util/test_location.py index d52362d5ee6..b9252c33e9d 100644 --- a/tests/util/test_location.py +++ b/tests/util/test_location.py @@ -1,4 +1,5 @@ """Test Home Assistant location util methods.""" + from unittest.mock import Mock, patch import aiohttp diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index be8f51af6f9..efac252aa5f 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -1,4 +1,5 @@ """Test Home Assistant unit conversion utility functions.""" + from __future__ import annotations import inspect diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 5a199783346..6749627519b 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -1,4 +1,5 @@ """Test the unit system helper.""" + from __future__ import annotations import pytest diff --git a/tests/util/test_variance.py b/tests/util/test_variance.py index 2072e3f3417..e51f38d5cfd 100644 --- a/tests/util/test_variance.py +++ b/tests/util/test_variance.py @@ -1,4 +1,5 @@ """Test variance method.""" + from datetime import datetime, timedelta import pytest diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index 9fda5be73a8..185c4cdb22c 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -1,4 +1,5 @@ """Test Home Assistant yaml loader.""" + from collections.abc import Generator import importlib import io From aa7acb89c511e0bdff5cc1151daa557a5ba54b39 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 8 Mar 2024 17:42:18 +0100 Subject: [PATCH 0557/1691] Remove Python 3.11 from CI (#112626) --- .github/workflows/ci.yaml | 4 ++-- homeassistant/helpers/config_validation.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4042319cd0b..29c45ca4820 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,8 +37,8 @@ env: PIP_CACHE_VERSION: 4 MYPY_CACHE_VERSION: 8 HA_SHORT_VERSION: "2024.4" - DEFAULT_PYTHON: "3.11" - ALL_PYTHON_VERSIONS: "['3.11', '3.12']" + DEFAULT_PYTHON: "3.12" + ALL_PYTHON_VERSIONS: "['3.12']" # 10.3 is the oldest supported version # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) # 10.6 is the current long-term-support diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 3f93eabc786..c545862f48a 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -905,7 +905,7 @@ def _deprecated_or_removed( try: near = ( f"near {config.__config_file__}" # type: ignore[attr-defined] - f":{config.__line__} " + f":{config.__line__} " # type: ignore[attr-defined] ) except AttributeError: near = "" From f416d67d21e0b5a562265c333a4863f05665f44e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 8 Mar 2024 12:02:32 -0500 Subject: [PATCH 0558/1691] Remove built-in support for import_executor in manifest (#112725) --- homeassistant/components/airvisual/manifest.json | 1 - homeassistant/components/ambient_station/manifest.json | 1 - homeassistant/components/analytics/manifest.json | 1 - homeassistant/components/analytics_insights/manifest.json | 1 - homeassistant/components/androidtv_remote/manifest.json | 1 - homeassistant/components/apple_tv/manifest.json | 1 - homeassistant/components/august/manifest.json | 1 - homeassistant/components/backup/manifest.json | 1 - homeassistant/components/baf/manifest.json | 1 - homeassistant/components/blink/manifest.json | 1 - homeassistant/components/bluetooth/manifest.json | 1 - homeassistant/components/caldav/manifest.json | 1 - homeassistant/components/cloud/manifest.json | 1 - homeassistant/components/co2signal/manifest.json | 1 - homeassistant/components/coinbase/manifest.json | 1 - homeassistant/components/denonavr/manifest.json | 1 - homeassistant/components/dhcp/manifest.json | 1 - homeassistant/components/discord/manifest.json | 1 - homeassistant/components/discovergy/manifest.json | 1 - homeassistant/components/emulated_kasa/manifest.json | 1 - homeassistant/components/enphase_envoy/manifest.json | 1 - homeassistant/components/esphome/manifest.json | 1 - homeassistant/components/flux_led/manifest.json | 1 - homeassistant/components/hardware/manifest.json | 1 - homeassistant/components/homekit/manifest.json | 1 - homeassistant/components/homekit_controller/manifest.json | 1 - homeassistant/components/influxdb/manifest.json | 1 - homeassistant/components/isy994/manifest.json | 1 - homeassistant/components/logbook/manifest.json | 1 - homeassistant/components/matter/manifest.json | 1 - homeassistant/components/mobile_app/manifest.json | 1 - homeassistant/components/mqtt/manifest.json | 1 - homeassistant/components/nexia/manifest.json | 1 - homeassistant/components/opower/manifest.json | 1 - homeassistant/components/plex/manifest.json | 1 - homeassistant/components/powerwall/manifest.json | 1 - homeassistant/components/radio_browser/manifest.json | 1 - homeassistant/components/rest/manifest.json | 1 - homeassistant/components/roomba/manifest.json | 1 - homeassistant/components/samsungtv/manifest.json | 1 - homeassistant/components/screenlogic/manifest.json | 1 - homeassistant/components/sense/manifest.json | 1 - homeassistant/components/shelly/manifest.json | 1 - homeassistant/components/smtp/manifest.json | 1 - homeassistant/components/snmp/manifest.json | 1 - homeassistant/components/sonos/manifest.json | 1 - homeassistant/components/spotify/manifest.json | 1 - homeassistant/components/ssdp/manifest.json | 1 - homeassistant/components/steamist/manifest.json | 1 - homeassistant/components/stream/manifest.json | 1 - homeassistant/components/switchbot/manifest.json | 1 - homeassistant/components/synology_dsm/manifest.json | 1 - homeassistant/components/template/manifest.json | 1 - homeassistant/components/thread/manifest.json | 1 - homeassistant/components/tplink/manifest.json | 1 - homeassistant/components/unifi/manifest.json | 1 - homeassistant/components/unifiprotect/manifest.json | 1 - homeassistant/components/upnp/manifest.json | 1 - homeassistant/components/usb/manifest.json | 1 - homeassistant/components/wemo/manifest.json | 1 - homeassistant/components/wyoming/manifest.json | 1 - homeassistant/components/xbox/manifest.json | 1 - homeassistant/components/xiaomi_ble/manifest.json | 1 - homeassistant/components/yeelight/manifest.json | 1 - homeassistant/components/zeroconf/manifest.json | 1 - homeassistant/components/zha/manifest.json | 1 - homeassistant/components/zwave_js/manifest.json | 1 - script/hassfest/manifest.py | 2 +- 68 files changed, 1 insertion(+), 68 deletions(-) diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index 4da5c395765..7934d809287 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["airvisual_pro"], "documentation": "https://www.home-assistant.io/integrations/airvisual", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["pyairvisual", "pysmb"], diff --git a/homeassistant/components/ambient_station/manifest.json b/homeassistant/components/ambient_station/manifest.json index a656b297a1c..046ab9f73e9 100644 --- a/homeassistant/components/ambient_station/manifest.json +++ b/homeassistant/components/ambient_station/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@bachya"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ambient_station", - "import_executor": true, "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["aioambient"], diff --git a/homeassistant/components/analytics/manifest.json b/homeassistant/components/analytics/manifest.json index 6ab6898ec27..955c4a813f4 100644 --- a/homeassistant/components/analytics/manifest.json +++ b/homeassistant/components/analytics/manifest.json @@ -5,7 +5,6 @@ "codeowners": ["@home-assistant/core", "@ludeeus"], "dependencies": ["api", "websocket_api"], "documentation": "https://www.home-assistant.io/integrations/analytics", - "import_executor": true, "integration_type": "system", "iot_class": "cloud_push", "quality_scale": "internal" diff --git a/homeassistant/components/analytics_insights/manifest.json b/homeassistant/components/analytics_insights/manifest.json index b55e08a8141..d33bb23b1b7 100644 --- a/homeassistant/components/analytics_insights/manifest.json +++ b/homeassistant/components/analytics_insights/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@joostlek"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/analytics_insights", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["python_homeassistant_analytics"], diff --git a/homeassistant/components/androidtv_remote/manifest.json b/homeassistant/components/androidtv_remote/manifest.json index 02197a61681..f45dee34afe 100644 --- a/homeassistant/components/androidtv_remote/manifest.json +++ b/homeassistant/components/androidtv_remote/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@tronikos", "@Drafteed"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/androidtv_remote", - "import_executor": true, "integration_type": "device", "iot_class": "local_push", "loggers": ["androidtvremote2"], diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index 0a14e11ecb7..1f7ac45372e 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["zeroconf"], "documentation": "https://www.home-assistant.io/integrations/apple_tv", - "import_executor": true, "iot_class": "local_push", "loggers": ["pyatv", "srptools"], "requirements": ["pyatv==0.14.3"], diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index ec4eb77605c..83685e6e6a1 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -26,7 +26,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/august", - "import_executor": true, "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], "requirements": ["yalexs==1.11.4", "yalexs-ble==2.4.2"] diff --git a/homeassistant/components/backup/manifest.json b/homeassistant/components/backup/manifest.json index be75e4717ef..1ec9b748cda 100644 --- a/homeassistant/components/backup/manifest.json +++ b/homeassistant/components/backup/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@home-assistant/core"], "dependencies": ["http", "websocket_api"], "documentation": "https://www.home-assistant.io/integrations/backup", - "import_executor": true, "integration_type": "system", "iot_class": "calculated", "quality_scale": "internal", diff --git a/homeassistant/components/baf/manifest.json b/homeassistant/components/baf/manifest.json index 869c9706624..497b3638fce 100644 --- a/homeassistant/components/baf/manifest.json +++ b/homeassistant/components/baf/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@bdraco", "@jfroy"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/baf", - "import_executor": true, "iot_class": "local_push", "requirements": ["aiobafi6==0.9.0"], "zeroconf": [ diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index 48db78b572c..445a469b141 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -18,7 +18,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/blink", - "import_executor": true, "iot_class": "cloud_polling", "loggers": ["blinkpy"], "requirements": ["blinkpy==0.22.6"] diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index b8158a06f7e..1c58a56b47c 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["usb"], "documentation": "https://www.home-assistant.io/integrations/bluetooth", - "import_executor": true, "iot_class": "local_push", "loggers": [ "btsocket", diff --git a/homeassistant/components/caldav/manifest.json b/homeassistant/components/caldav/manifest.json index 2c77c86696d..e0d598e6493 100644 --- a/homeassistant/components/caldav/manifest.json +++ b/homeassistant/components/caldav/manifest.json @@ -4,7 +4,6 @@ "codeowners": [], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/caldav", - "import_executor": true, "iot_class": "cloud_polling", "loggers": ["caldav", "vobject"], "requirements": ["caldav==1.3.9"] diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index ef2d32fcb0c..e816516fd7a 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -5,7 +5,6 @@ "codeowners": ["@home-assistant/cloud"], "dependencies": ["http", "webhook"], "documentation": "https://www.home-assistant.io/integrations/cloud", - "import_executor": true, "integration_type": "system", "iot_class": "cloud_push", "loggers": ["hass_nabucasa"], diff --git a/homeassistant/components/co2signal/manifest.json b/homeassistant/components/co2signal/manifest.json index 980a3c4e20a..ff6d5bdb18b 100644 --- a/homeassistant/components/co2signal/manifest.json +++ b/homeassistant/components/co2signal/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@jpbede", "@VIKTORVAV99"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/co2signal", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["aioelectricitymaps"], diff --git a/homeassistant/components/coinbase/manifest.json b/homeassistant/components/coinbase/manifest.json index dbb40b24fcc..515fe9f9abb 100644 --- a/homeassistant/components/coinbase/manifest.json +++ b/homeassistant/components/coinbase/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@tombrien"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/coinbase", - "import_executor": true, "iot_class": "cloud_polling", "loggers": ["coinbase"], "requirements": ["coinbase==2.1.0"] diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index d595c7616ba..9188009bde5 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@ol-iver", "@starkillerOG"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/denonavr", - "import_executor": true, "iot_class": "local_push", "loggers": ["denonavr"], "requirements": ["denonavr==0.11.6"], diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index d609e9ec7ae..142aab52cc8 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -3,7 +3,6 @@ "name": "DHCP Discovery", "codeowners": ["@bdraco"], "documentation": "https://www.home-assistant.io/integrations/dhcp", - "import_executor": true, "integration_type": "system", "iot_class": "local_push", "loggers": [ diff --git a/homeassistant/components/discord/manifest.json b/homeassistant/components/discord/manifest.json index 78d4dc203e2..5f1ba2a13ef 100644 --- a/homeassistant/components/discord/manifest.json +++ b/homeassistant/components/discord/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@tkdrob"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/discord", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_push", "loggers": ["discord"], diff --git a/homeassistant/components/discovergy/manifest.json b/homeassistant/components/discovergy/manifest.json index 97bed214609..da9fb117353 100644 --- a/homeassistant/components/discovergy/manifest.json +++ b/homeassistant/components/discovergy/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@jpbede"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/discovergy", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "requirements": ["pydiscovergy==3.0.0"] diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index 63125bc0f3a..843aeddde7b 100644 --- a/homeassistant/components/emulated_kasa/manifest.json +++ b/homeassistant/components/emulated_kasa/manifest.json @@ -3,7 +3,6 @@ "name": "Emulated Kasa", "codeowners": ["@kbickar"], "documentation": "https://www.home-assistant.io/integrations/emulated_kasa", - "import_executor": true, "iot_class": "local_push", "loggers": ["sense_energy"], "quality_scale": "internal", diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 8885819ba5e..63e10547ead 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@bdraco", "@cgarwood", "@dgomes", "@joostlek", "@catsmanac"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", - "import_executor": true, "iot_class": "local_polling", "loggers": ["pyenphase"], "requirements": ["pyenphase==1.19.1"], diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index de26fd7aeba..693ecbad2fc 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -11,7 +11,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/esphome", - "import_executor": true, "integration_type": "device", "iot_class": "local_push", "loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"], diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 327ee0f5b4e..a55ae028342 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -51,7 +51,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "import_executor": true, "iot_class": "local_push", "loggers": ["flux_led"], "requirements": ["flux-led==1.0.4"] diff --git a/homeassistant/components/hardware/manifest.json b/homeassistant/components/hardware/manifest.json index 8056a4cca4f..f2772e609db 100644 --- a/homeassistant/components/hardware/manifest.json +++ b/homeassistant/components/hardware/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@home-assistant/core"], "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/hardware", - "import_executor": true, "integration_type": "system", "quality_scale": "internal", "requirements": ["psutil-home-assistant==0.0.1"] diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index a7e2412270b..17d1237e579 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -6,7 +6,6 @@ "config_flow": true, "dependencies": ["ffmpeg", "http", "network"], "documentation": "https://www.home-assistant.io/integrations/homekit", - "import_executor": true, "iot_class": "local_push", "loggers": ["pyhap"], "requirements": [ diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 22d78123e0a..72c1a0478a3 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -12,7 +12,6 @@ "config_flow": true, "dependencies": ["bluetooth_adapters", "zeroconf"], "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "import_executor": true, "iot_class": "local_push", "loggers": ["aiohomekit", "commentjson"], "requirements": ["aiohomekit==3.1.5"], diff --git a/homeassistant/components/influxdb/manifest.json b/homeassistant/components/influxdb/manifest.json index 46cd5ecb6ca..ad3f282eff7 100644 --- a/homeassistant/components/influxdb/manifest.json +++ b/homeassistant/components/influxdb/manifest.json @@ -3,7 +3,6 @@ "name": "InfluxDB", "codeowners": ["@mdegat01"], "documentation": "https://www.home-assistant.io/integrations/influxdb", - "import_executor": true, "iot_class": "local_push", "loggers": ["influxdb", "influxdb_client"], "requirements": ["influxdb==5.3.1", "influxdb-client==1.24.0"] diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 8c9815cd425..3aa81027b4f 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -21,7 +21,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/isy994", - "import_executor": true, "integration_type": "hub", "iot_class": "local_push", "loggers": ["pyisy"], diff --git a/homeassistant/components/logbook/manifest.json b/homeassistant/components/logbook/manifest.json index 923a3fa87b4..b6b68a1489e 100644 --- a/homeassistant/components/logbook/manifest.json +++ b/homeassistant/components/logbook/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@home-assistant/core"], "dependencies": ["frontend", "http", "recorder"], "documentation": "https://www.home-assistant.io/integrations/logbook", - "import_executor": true, "integration_type": "system", "quality_scale": "internal" } diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index 0e1ed4e80b6..716e296ec15 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["websocket_api"], "documentation": "https://www.home-assistant.io/integrations/matter", - "import_executor": true, "iot_class": "local_push", "requirements": ["python-matter-server==5.7.0"] } diff --git a/homeassistant/components/mobile_app/manifest.json b/homeassistant/components/mobile_app/manifest.json index c3d15be3468..aeab576a7cd 100644 --- a/homeassistant/components/mobile_app/manifest.json +++ b/homeassistant/components/mobile_app/manifest.json @@ -6,7 +6,6 @@ "config_flow": true, "dependencies": ["http", "webhook", "person", "tag", "websocket_api"], "documentation": "https://www.home-assistant.io/integrations/mobile_app", - "import_executor": true, "iot_class": "local_push", "loggers": ["nacl"], "quality_scale": "internal", diff --git a/homeassistant/components/mqtt/manifest.json b/homeassistant/components/mqtt/manifest.json index 2fc77fb1d4a..3a284c6719c 100644 --- a/homeassistant/components/mqtt/manifest.json +++ b/homeassistant/components/mqtt/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["file_upload", "http"], "documentation": "https://www.home-assistant.io/integrations/mqtt", - "import_executor": true, "iot_class": "local_push", "quality_scale": "gold", "requirements": ["paho-mqtt==1.6.1"] diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index 1384226eac1..0013cd63de1 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -10,7 +10,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/nexia", - "import_executor": true, "iot_class": "cloud_polling", "loggers": ["nexia"], "requirements": ["nexia==2.0.8"] diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 820aac5d20a..418f2a5723b 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["recorder"], "documentation": "https://www.home-assistant.io/integrations/opower", - "import_executor": true, "iot_class": "cloud_polling", "loggers": ["opower"], "requirements": ["opower==0.3.1"] diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 99dd44c1ed8..e33cbc2e0c1 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/plex", - "import_executor": true, "iot_class": "local_push", "loggers": ["plexapi", "plexwebsocket"], "requirements": [ diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index df57396c7bf..4185e90ab7b 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -12,7 +12,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/powerwall", - "import_executor": true, "iot_class": "local_polling", "loggers": ["tesla_powerwall"], "requirements": ["tesla-powerwall==0.5.1"] diff --git a/homeassistant/components/radio_browser/manifest.json b/homeassistant/components/radio_browser/manifest.json index e8e03fd1828..3aa94e0d402 100644 --- a/homeassistant/components/radio_browser/manifest.json +++ b/homeassistant/components/radio_browser/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@frenck"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/radio_browser", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "requirements": ["radios==0.2.0"] diff --git a/homeassistant/components/rest/manifest.json b/homeassistant/components/rest/manifest.json index d8dc1b19f1c..d638c20d2a4 100644 --- a/homeassistant/components/rest/manifest.json +++ b/homeassistant/components/rest/manifest.json @@ -3,7 +3,6 @@ "name": "RESTful", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/rest", - "import_executor": true, "iot_class": "local_polling", "requirements": ["jsonpath==0.82.2", "xmltodict==0.13.0"] } diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 530ba8e8137..ae08d8f6a1f 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -22,7 +22,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/roomba", - "import_executor": true, "iot_class": "local_push", "loggers": ["paho_mqtt", "roombapy"], "requirements": ["roombapy==1.6.13"], diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 63a78925a6e..00b8fec8e6a 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -31,7 +31,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/samsungtv", - "import_executor": true, "integration_type": "device", "iot_class": "local_push", "loggers": ["samsungctl", "samsungtvws"], diff --git a/homeassistant/components/screenlogic/manifest.json b/homeassistant/components/screenlogic/manifest.json index 6e720db29bc..434b8921bc2 100644 --- a/homeassistant/components/screenlogic/manifest.json +++ b/homeassistant/components/screenlogic/manifest.json @@ -13,7 +13,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/screenlogic", - "import_executor": true, "iot_class": "local_push", "loggers": ["screenlogicpy"], "requirements": ["screenlogicpy==0.10.0"] diff --git a/homeassistant/components/sense/manifest.json b/homeassistant/components/sense/manifest.json index f930b486baa..7ef1caefe48 100644 --- a/homeassistant/components/sense/manifest.json +++ b/homeassistant/components/sense/manifest.json @@ -18,7 +18,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/sense", - "import_executor": true, "iot_class": "cloud_polling", "loggers": ["sense_energy"], "requirements": ["sense-energy==0.12.2"] diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 0e0f9d7d796..38db658bba9 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["bluetooth", "http"], "documentation": "https://www.home-assistant.io/integrations/shelly", - "import_executor": true, "integration_type": "device", "iot_class": "local_push", "loggers": ["aioshelly"], diff --git a/homeassistant/components/smtp/manifest.json b/homeassistant/components/smtp/manifest.json index f321c17efa5..0e0bba707ac 100644 --- a/homeassistant/components/smtp/manifest.json +++ b/homeassistant/components/smtp/manifest.json @@ -3,6 +3,5 @@ "name": "SMTP", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/smtp", - "import_executor": true, "iot_class": "cloud_push" } diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index 6ce96a945dd..8d046b609b8 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -3,7 +3,6 @@ "name": "SNMP", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/snmp", - "import_executor": true, "iot_class": "local_polling", "loggers": ["pyasn1", "pysmi", "pysnmp"], "requirements": ["pysnmp-lextudio==5.0.34"] diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 929b6639e9f..58a0ec3b7ee 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -6,7 +6,6 @@ "config_flow": true, "dependencies": ["ssdp"], "documentation": "https://www.home-assistant.io/integrations/sonos", - "import_executor": true, "iot_class": "local_push", "loggers": ["soco"], "requirements": ["soco==0.30.2", "sonos-websocket==0.1.3"], diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index 94475794fdf..84f2bc102e3 100644 --- a/homeassistant/components/spotify/manifest.json +++ b/homeassistant/components/spotify/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/spotify", - "import_executor": true, "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["spotipy"], diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 6d8010d6b8e..a9ef8af8c90 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -4,7 +4,6 @@ "codeowners": [], "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/ssdp", - "import_executor": true, "integration_type": "system", "iot_class": "local_push", "loggers": ["async_upnp_client"], diff --git a/homeassistant/components/steamist/manifest.json b/homeassistant/components/steamist/manifest.json index a703ed588bc..91ebc7f6a21 100644 --- a/homeassistant/components/steamist/manifest.json +++ b/homeassistant/components/steamist/manifest.json @@ -14,7 +14,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/steamist", - "import_executor": true, "iot_class": "local_polling", "loggers": ["aiosteamist", "discovery30303"], "requirements": ["aiosteamist==0.3.2", "discovery30303==0.2.1"] diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 0a1b60485d8..37158aa5fe3 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/stream", - "import_executor": true, "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 2233adf35b7..401d85e7376 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -37,7 +37,6 @@ "config_flow": true, "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/switchbot", - "import_executor": true, "iot_class": "local_push", "loggers": ["switchbot"], "requirements": ["PySwitchbot==0.45.0"] diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index 2820c99f889..8060bce5c9b 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "import_executor": true, "iot_class": "local_polling", "loggers": ["synology_dsm"], "requirements": ["py-synologydsm-api==2.1.4"], diff --git a/homeassistant/components/template/manifest.json b/homeassistant/components/template/manifest.json index 89aa9997d89..4112ca7a73f 100644 --- a/homeassistant/components/template/manifest.json +++ b/homeassistant/components/template/manifest.json @@ -5,7 +5,6 @@ "codeowners": ["@PhracturedBlue", "@tetienne", "@home-assistant/core"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/template", - "import_executor": true, "integration_type": "helper", "iot_class": "local_push", "quality_scale": "internal" diff --git a/homeassistant/components/thread/manifest.json b/homeassistant/components/thread/manifest.json index 19d8fa76c66..65d4c9d044c 100644 --- a/homeassistant/components/thread/manifest.json +++ b/homeassistant/components/thread/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["zeroconf"], "documentation": "https://www.home-assistant.io/integrations/thread", - "import_executor": true, "integration_type": "service", "iot_class": "local_polling", "requirements": ["python-otbr-api==2.6.0", "pyroute2==0.7.5"], diff --git a/homeassistant/components/tplink/manifest.json b/homeassistant/components/tplink/manifest.json index f0a4696fd0b..a91e7e5a46f 100644 --- a/homeassistant/components/tplink/manifest.json +++ b/homeassistant/components/tplink/manifest.json @@ -266,7 +266,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/tplink", - "import_executor": true, "iot_class": "local_polling", "loggers": ["kasa"], "quality_scale": "platinum", diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index bcc25b22059..b0d6b31c7be 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@Kane610"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifi", - "import_executor": true, "integration_type": "hub", "iot_class": "local_push", "loggers": ["aiounifi"], diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index eba2b934e05..37b810495a1 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -37,7 +37,6 @@ } ], "documentation": "https://www.home-assistant.io/integrations/unifiprotect", - "import_executor": true, "integration_type": "hub", "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index e4e94d90430..edfde84a2ac 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["network", "ssdp"], "documentation": "https://www.home-assistant.io/integrations/upnp", - "import_executor": true, "integration_type": "device", "iot_class": "local_polling", "loggers": ["async_upnp_client"], diff --git a/homeassistant/components/usb/manifest.json b/homeassistant/components/usb/manifest.json index cd8c801d50c..71df5ba2c05 100644 --- a/homeassistant/components/usb/manifest.json +++ b/homeassistant/components/usb/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@bdraco"], "dependencies": ["websocket_api"], "documentation": "https://www.home-assistant.io/integrations/usb", - "import_executor": true, "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index fa0b618b3f9..71a1eac62a8 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -7,7 +7,6 @@ "homekit": { "models": ["Socket", "Wemo"] }, - "import_executor": true, "iot_class": "local_push", "loggers": ["pywemo"], "requirements": ["pywemo==1.4.0"], diff --git a/homeassistant/components/wyoming/manifest.json b/homeassistant/components/wyoming/manifest.json index 0300f420c35..830ba5a3435 100644 --- a/homeassistant/components/wyoming/manifest.json +++ b/homeassistant/components/wyoming/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["assist_pipeline"], "documentation": "https://www.home-assistant.io/integrations/wyoming", - "import_executor": true, "iot_class": "local_push", "requirements": ["wyoming==1.5.3"], "zeroconf": ["_wyoming._tcp.local."] diff --git a/homeassistant/components/xbox/manifest.json b/homeassistant/components/xbox/manifest.json index 67e53e326ee..30a6c3bc700 100644 --- a/homeassistant/components/xbox/manifest.json +++ b/homeassistant/components/xbox/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["auth", "application_credentials"], "documentation": "https://www.home-assistant.io/integrations/xbox", - "import_executor": true, "iot_class": "cloud_polling", "requirements": ["xbox-webapi==2.0.11"] } diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 22629d3e326..a380ecb8e94 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -23,7 +23,6 @@ "config_flow": true, "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", - "import_executor": true, "iot_class": "local_push", "requirements": ["xiaomi-ble==0.25.2"] } diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 58d2d6991ee..20f8ed3ed4d 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -14,7 +14,6 @@ "homekit": { "models": ["YL*"] }, - "import_executor": true, "iot_class": "local_push", "loggers": ["async_upnp_client", "yeelight"], "quality_scale": "platinum", diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index f7ca2eeeed0..aecc88968f3 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -4,7 +4,6 @@ "codeowners": ["@bdraco"], "dependencies": ["network", "api"], "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "import_executor": true, "integration_type": "system", "iot_class": "local_push", "loggers": ["zeroconf"], diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index fc050c9b2d1..95a4feadc19 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -6,7 +6,6 @@ "config_flow": true, "dependencies": ["file_upload"], "documentation": "https://www.home-assistant.io/integrations/zha", - "import_executor": true, "iot_class": "local_polling", "loggers": [ "aiosqlite", diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 40c896c516a..a06de5cb8ee 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -5,7 +5,6 @@ "config_flow": true, "dependencies": ["http", "repairs", "usb", "websocket_api"], "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "import_executor": true, "integration_type": "hub", "iot_class": "local_push", "loggers": ["zwave_js_server"], diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index d8637ad8962..19860f3c4f6 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -266,7 +266,6 @@ INTEGRATION_MANIFEST_SCHEMA = vol.Schema( vol.Optional("loggers"): [str], vol.Optional("disabled"): str, vol.Optional("iot_class"): vol.In(SUPPORTED_IOT_CLASSES), - vol.Optional("import_executor"): bool, vol.Optional("single_config_entry"): bool, } ) @@ -294,6 +293,7 @@ def manifest_schema(value: dict[str, Any]) -> vol.Schema: CUSTOM_INTEGRATION_MANIFEST_SCHEMA = INTEGRATION_MANIFEST_SCHEMA.extend( { vol.Optional("version"): vol.All(str, verify_version), + vol.Optional("import_executor"): bool, } ) From cb8c14496ca3aeccd737131972a393c8a4829b7d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:44:42 +0100 Subject: [PATCH 0559/1691] Use more f-strings [ruff] (#112695) --- homeassistant/components/buienradar/sensor.py | 5 +++-- homeassistant/components/buienradar/weather.py | 4 ++-- homeassistant/components/signal_messenger/notify.py | 4 ++-- homeassistant/components/smartthings/smartapp.py | 8 +++----- homeassistant/components/statistics/sensor.py | 4 ++-- homeassistant/components/tellstick/sensor.py | 4 ++-- homeassistant/components/utility_meter/__init__.py | 6 +++--- homeassistant/components/webhook/__init__.py | 6 +++--- homeassistant/components/wemo/switch.py | 5 +++-- homeassistant/helpers/aiohttp_client.py | 5 +++-- homeassistant/helpers/httpx_client.py | 5 +++-- tests/common.py | 6 +++--- tests/components/binary_sensor/test_device_trigger.py | 5 +++-- tests/components/device_automation/test_toggle_entity.py | 5 +++-- 14 files changed, 38 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index a52fca25c87..d0847c111fb 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -742,8 +742,9 @@ class BrSensor(SensorEntity): """Initialize the sensor.""" self.entity_description = description self._measured = None - self._attr_unique_id = "{:2.6f}{:2.6f}{}".format( - coordinates[CONF_LATITUDE], coordinates[CONF_LONGITUDE], description.key + self._attr_unique_id = ( + f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}" + f"{description.key}" ) # All continuous sensors should be forced to be updated diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index de00faadd64..c7387857da5 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -134,8 +134,8 @@ class BrWeather(WeatherEntity): self._stationname = config.get(CONF_NAME, "Buienradar") self._attr_name = self._stationname or f"BR {'(unknown station)'}" - self._attr_unique_id = "{:2.6f}{:2.6f}".format( - coordinates[CONF_LATITUDE], coordinates[CONF_LONGITUDE] + self._attr_unique_id = ( + f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}" ) @callback diff --git a/homeassistant/components/signal_messenger/notify.py b/homeassistant/components/signal_messenger/notify.py index 3659864efd7..58cd85fb26e 100644 --- a/homeassistant/components/signal_messenger/notify.py +++ b/homeassistant/components/signal_messenger/notify.py @@ -165,8 +165,8 @@ class SignalNotificationService(BaseNotificationService): size += len(chunk) if size > attachment_size_limit: raise ValueError( - "Attachment too large (Stream reports {}). Max size: {}" - " bytes".format(size, CONF_MAX_ALLOWED_DOWNLOAD_SIZE_BYTES) + f"Attachment too large (Stream reports {size}). " + f"Max size: {CONF_MAX_ALLOWED_DOWNLOAD_SIZE_BYTES} bytes" ) chunks.extend(chunk) diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 78c0bfa86b1..11446771130 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -84,11 +84,9 @@ async def validate_installed_app(api, installed_app_id: str): installed_app = await api.installed_app(installed_app_id) if installed_app.installed_app_status != InstalledAppStatus.AUTHORIZED: raise RuntimeWarning( - "Installed SmartApp instance '{}' ({}) is not AUTHORIZED but instead {}".format( - installed_app.display_name, - installed_app.installed_app_id, - installed_app.installed_app_status, - ) + f"Installed SmartApp instance '{installed_app.display_name}' " + f"({installed_app.installed_app_id}) is not AUTHORIZED " + f"but instead {installed_app.installed_app_status}" ) return installed_app diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 0a88e9d93af..e1e530e60ed 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -204,8 +204,8 @@ def valid_state_characteristic_configuration(config: dict[str, Any]) -> dict[str not is_binary and characteristic not in STATS_NUMERIC_SUPPORT ): raise vol.ValueInvalid( - "The configured characteristic '{}' is not supported for the configured" - " source sensor".format(characteristic) + f"The configured characteristic '{characteristic}' is not supported " + "for the configured source sensor" ) return config diff --git a/homeassistant/components/tellstick/sensor.py b/homeassistant/components/tellstick/sensor.py index 2469ee49aa2..a2cba41b028 100644 --- a/homeassistant/components/tellstick/sensor.py +++ b/homeassistant/components/tellstick/sensor.py @@ -130,8 +130,8 @@ def setup_platform( sensor_name = str(tellcore_sensor.id) else: proto_id = f"{tellcore_sensor.protocol}{tellcore_sensor.id}" - proto_model_id = "{}{}{}".format( - tellcore_sensor.protocol, tellcore_sensor.model, tellcore_sensor.id + proto_model_id = ( + f"{tellcore_sensor.protocol}{tellcore_sensor.model}{tellcore_sensor.id}" ) if tellcore_sensor.id in named_sensors: sensor_name = named_sensors[tellcore_sensor.id] diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index 0e3e864461d..aeed9885872 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -164,9 +164,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) - hass.data[DATA_UTILITY][meter][CONF_TARIFF_ENTITY] = "{}.{}".format( - SELECT_DOMAIN, meter - ) + hass.data[DATA_UTILITY][meter][ + CONF_TARIFF_ENTITY + ] = f"{SELECT_DOMAIN}.{meter}" # add one meter for each tariff tariff_confs = {} diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 09b7541cbea..081147cc7f0 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -89,9 +89,9 @@ def async_generate_id() -> str: @bind_hass def async_generate_url(hass: HomeAssistant, webhook_id: str) -> str: """Generate the full URL for a webhook_id.""" - return "{}{}".format( - get_url(hass, prefer_external=True, allow_cloud=False), - async_generate_path(webhook_id), + return ( + f"{get_url(hass, prefer_external=True, allow_cloud=False)}" + f"{async_generate_path(webhook_id)}" ) diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 8cc95325c7e..14e3013afc1 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -90,8 +90,9 @@ class WemoSwitch(WemoBinaryStateEntity, SwitchEntity): def as_uptime(_seconds: int) -> str: """Format seconds into uptime string in the format: 00d 00h 00m 00s.""" uptime = datetime(1, 1, 1) + timedelta(seconds=_seconds) - return "{:0>2d}d {:0>2d}h {:0>2d}m {:0>2d}s".format( - uptime.day - 1, uptime.hour, uptime.minute, uptime.second + return ( + f"{uptime.day - 1:0>2d}d {uptime.hour:0>2d}h " + f"{uptime.minute:0>2d}m {uptime.second:0>2d}s" ) @property diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 70546e54bcc..17784d12aa2 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -32,8 +32,9 @@ if TYPE_CHECKING: DATA_CONNECTOR = "aiohttp_connector" DATA_CLIENTSESSION = "aiohttp_clientsession" -SERVER_SOFTWARE = "{0}/{1} aiohttp/{2} Python/{3[0]}.{3[1]}".format( - APPLICATION_NAME, __version__, aiohttp.__version__, sys.version_info +SERVER_SOFTWARE = ( + f"{APPLICATION_NAME}/{__version__} " + f"aiohttp/{aiohttp.__version__} Python/{sys.version_info[0]}.{sys.version_info[1]}" ) ENABLE_CLEANUP_CLOSED = not (3, 11, 1) <= sys.version_info < (3, 11, 4) diff --git a/homeassistant/helpers/httpx_client.py b/homeassistant/helpers/httpx_client.py index 306d9899cd5..2855705b9c1 100644 --- a/homeassistant/helpers/httpx_client.py +++ b/homeassistant/helpers/httpx_client.py @@ -26,8 +26,9 @@ KEEP_ALIVE_TIMEOUT = 15 DATA_ASYNC_CLIENT = "httpx_async_client" DATA_ASYNC_CLIENT_NOVERIFY = "httpx_async_client_noverify" DEFAULT_LIMITS = limits = httpx.Limits(keepalive_expiry=KEEP_ALIVE_TIMEOUT) -SERVER_SOFTWARE = "{0}/{1} httpx/{2} Python/{3[0]}.{3[1]}".format( - APPLICATION_NAME, __version__, httpx.__version__, sys.version_info +SERVER_SOFTWARE = ( + f"{APPLICATION_NAME}/{__version__} " + f"httpx/{httpx.__version__} Python/{sys.version_info[0]}.{sys.version_info[1]}" ) USER_AGENT = "User-Agent" diff --git a/tests/common.py b/tests/common.py index 5b2314ee038..0766da158d1 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1075,9 +1075,9 @@ def assert_setup_component(count, domain=None): yield config if domain is None: - assert len(config) == 1, "assert_setup_component requires DOMAIN: {}".format( - list(config.keys()) - ) + assert ( + len(config) == 1 + ), f"assert_setup_component requires DOMAIN: {list(config.keys())}" domain = list(config.keys())[0] res = config.get(domain) diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index bd357f25ba3..66fa8d35a92 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -479,6 +479,7 @@ async def test_if_fires_on_state_change_legacy( hass.states.async_set(entry.entity_id, STATE_OFF) await hass.async_block_till_done() assert len(calls) == 1 - assert calls[0].data["some"] == "turn_off device - {} - on - off - None".format( - entry.entity_id + assert ( + calls[0].data["some"] + == f"turn_off device - {entry.entity_id} - on - off - None" ) diff --git a/tests/components/device_automation/test_toggle_entity.py b/tests/components/device_automation/test_toggle_entity.py index f034dd9ab12..59d316545fa 100644 --- a/tests/components/device_automation/test_toggle_entity.py +++ b/tests/components/device_automation/test_toggle_entity.py @@ -214,6 +214,7 @@ async def test_if_fires_on_state_change_with_for( await hass.async_block_till_done() assert len(calls) == 1 await hass.async_block_till_done() - assert calls[0].data["some"] == "turn_off device - {} - on - off - 0:00:05".format( - entry.entity_id + assert ( + calls[0].data["some"] + == f"turn_off device - {entry.entity_id} - on - off - 0:00:05" ) From 2c06d4fcb9f9591710188d597c926e72bd385c50 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:15:59 +0100 Subject: [PATCH 0560/1691] Add empty line after module docstring (2) [components] (#112736) --- homeassistant/components/abode/const.py | 1 + homeassistant/components/acmeda/const.py | 1 + homeassistant/components/adguard/const.py | 1 + homeassistant/components/ads/__init__.py | 1 + homeassistant/components/airnow/__init__.py | 1 + homeassistant/components/airnow/config_flow.py | 1 + homeassistant/components/airtouch4/coordinator.py | 1 + homeassistant/components/airtouch5/climate.py | 1 + homeassistant/components/airvisual/const.py | 1 + homeassistant/components/airvisual_pro/const.py | 1 + homeassistant/components/aladdin_connect/__init__.py | 1 + homeassistant/components/alarmdecoder/binary_sensor.py | 1 + homeassistant/components/alexa/auth.py | 1 + homeassistant/components/alexa/flash_briefings.py | 1 + homeassistant/components/alexa/intent.py | 1 + homeassistant/components/alexa/smart_home.py | 1 + homeassistant/components/amberelectric/const.py | 1 + homeassistant/components/ambiclimate/__init__.py | 1 + homeassistant/components/ambiclimate/config_flow.py | 1 + homeassistant/components/ambient_station/const.py | 1 + homeassistant/components/analytics_insights/const.py | 1 + homeassistant/components/aosmith/coordinator.py | 1 + homeassistant/components/api/__init__.py | 1 + homeassistant/components/apple_tv/remote.py | 1 + homeassistant/components/arcam_fmj/__init__.py | 1 + homeassistant/components/assist_pipeline/websocket_api.py | 1 + homeassistant/components/asterisk_mbox/__init__.py | 1 + homeassistant/components/automation/const.py | 1 + homeassistant/components/aws/__init__.py | 1 + homeassistant/components/axis/__init__.py | 1 + homeassistant/components/axis/const.py | 1 + homeassistant/components/baidu/tts.py | 1 + homeassistant/components/binary_sensor/device_trigger.py | 1 + homeassistant/components/blebox/__init__.py | 1 + homeassistant/components/bosch_shc/__init__.py | 1 + homeassistant/components/broadlink/heartbeat.py | 1 + homeassistant/components/broadlink/light.py | 1 + homeassistant/components/broadlink/remote.py | 1 + homeassistant/components/browser/__init__.py | 1 + homeassistant/components/bsblan/__init__.py | 1 + homeassistant/components/buienradar/weather.py | 1 + homeassistant/components/cast/discovery.py | 1 + homeassistant/components/ccm15/climate.py | 1 + homeassistant/components/ccm15/coordinator.py | 1 + homeassistant/components/cert_expiry/helper.py | 1 + homeassistant/components/circuit/__init__.py | 1 + homeassistant/components/cloud/assist_pipeline.py | 1 + homeassistant/components/cloudflare/helpers.py | 1 + homeassistant/components/color_extractor/__init__.py | 1 + homeassistant/components/comelit/const.py | 1 + homeassistant/components/comfoconnect/__init__.py | 1 + homeassistant/components/compensation/__init__.py | 1 + homeassistant/components/coolmaster/coordinator.py | 1 + homeassistant/components/cpuspeed/const.py | 1 + homeassistant/components/datadog/__init__.py | 1 + homeassistant/components/deconz/const.py | 1 + homeassistant/components/deluge/const.py | 1 + homeassistant/components/denonavr/__init__.py | 1 + homeassistant/components/devolo_home_control/const.py | 1 + homeassistant/components/dialogflow/__init__.py | 1 + homeassistant/components/dynalite/const.py | 1 + homeassistant/components/eafm/__init__.py | 1 + homeassistant/components/ebusd/__init__.py | 1 + homeassistant/components/ecoal_boiler/__init__.py | 1 + homeassistant/components/ecobee/const.py | 1 + homeassistant/components/econet/__init__.py | 1 + homeassistant/components/ecovacs/__init__.py | 1 + homeassistant/components/ecowitt/binary_sensor.py | 1 + homeassistant/components/edl21/const.py | 1 + homeassistant/components/egardia/__init__.py | 1 + homeassistant/components/electric_kiwi/coordinator.py | 1 + homeassistant/components/elmax/switch.py | 1 + homeassistant/components/elv/__init__.py | 1 + homeassistant/components/emonitor/config_flow.py | 1 + homeassistant/components/emulated_kasa/__init__.py | 1 + homeassistant/components/emulated_roku/__init__.py | 1 + homeassistant/components/emulated_roku/binding.py | 1 + homeassistant/components/emulated_roku/config_flow.py | 1 + homeassistant/components/enocean/__init__.py | 1 + homeassistant/components/enocean/const.py | 1 + homeassistant/components/enocean/dongle.py | 1 + homeassistant/components/environment_canada/config_flow.py | 1 + homeassistant/components/envisalink/__init__.py | 1 + homeassistant/components/epson/__init__.py | 1 + homeassistant/components/epson/config_flow.py | 1 + homeassistant/components/escea/config_flow.py | 1 + homeassistant/components/eufy/__init__.py | 1 + homeassistant/components/ezviz/__init__.py | 1 + homeassistant/components/ezviz/coordinator.py | 1 + homeassistant/components/faa_delays/config_flow.py | 1 + homeassistant/components/faa_delays/coordinator.py | 1 + homeassistant/components/fastdotcom/const.py | 1 + homeassistant/components/fireservicerota/sensor.py | 1 + homeassistant/components/fireservicerota/switch.py | 1 + homeassistant/components/firmata/__init__.py | 1 + homeassistant/components/firmata/binary_sensor.py | 1 + homeassistant/components/firmata/sensor.py | 1 + homeassistant/components/firmata/switch.py | 1 + homeassistant/components/flexit_bacnet/climate.py | 1 + homeassistant/components/flexit_bacnet/coordinator.py | 1 + homeassistant/components/flexit_bacnet/number.py | 1 + homeassistant/components/flexit_bacnet/switch.py | 1 + homeassistant/components/flick_electric/config_flow.py | 1 + homeassistant/components/flick_electric/sensor.py | 1 + homeassistant/components/flo/__init__.py | 1 + homeassistant/components/flo/const.py | 1 + homeassistant/components/foscam/const.py | 1 + homeassistant/components/freebox/config_flow.py | 1 + homeassistant/components/freedns/__init__.py | 1 + homeassistant/components/freedompro/cover.py | 1 + homeassistant/components/freedompro/lock.py | 1 + homeassistant/components/freedompro/switch.py | 1 + homeassistant/components/fritz/__init__.py | 1 + homeassistant/components/fritzbox_callmonitor/__init__.py | 1 + homeassistant/components/frontier_silicon/browse_media.py | 1 + homeassistant/components/fully_kiosk/coordinator.py | 1 + homeassistant/components/garages_amsterdam/__init__.py | 1 + homeassistant/components/gc100/__init__.py | 1 + homeassistant/components/gdacs/config_flow.py | 1 + homeassistant/components/generic_hygrostat/__init__.py | 1 + homeassistant/components/geonetnz_quakes/config_flow.py | 1 + homeassistant/components/geonetnz_volcano/config_flow.py | 1 + homeassistant/components/glances/__init__.py | 1 + homeassistant/components/glances/coordinator.py | 1 + homeassistant/components/goalzero/const.py | 1 + homeassistant/components/goodwe/select.py | 1 + homeassistant/components/google_assistant/smart_home.py | 1 + homeassistant/components/google_cloud/tts.py | 1 + homeassistant/components/google_domains/__init__.py | 1 + homeassistant/components/google_tasks/config_flow.py | 1 + homeassistant/components/google_travel_time/helpers.py | 1 + homeassistant/components/growatt_server/config_flow.py | 1 + homeassistant/components/guardian/const.py | 1 + homeassistant/components/habitica/__init__.py | 1 + homeassistant/components/harmony/__init__.py | 1 + homeassistant/components/harmony/switch.py | 1 + homeassistant/components/harmony/util.py | 1 + homeassistant/components/hassio/websocket_api.py | 1 + homeassistant/components/heos/services.py | 1 + homeassistant/components/hisense_aehw4a1/__init__.py | 1 + homeassistant/components/hlk_sw16/__init__.py | 1 + homeassistant/components/hlk_sw16/config_flow.py | 1 + homeassistant/components/home_connect/binary_sensor.py | 1 + homeassistant/components/home_connect/config_flow.py | 1 + homeassistant/components/home_connect/light.py | 1 + homeassistant/components/home_connect/switch.py | 1 + homeassistant/components/homeassistant/__init__.py | 1 + homeassistant/components/homeassistant/trigger.py | 1 + homeassistant/components/homeassistant/triggers/homeassistant.py | 1 + homeassistant/components/homekit/type_cameras.py | 1 + homeassistant/components/homekit/type_covers.py | 1 + homeassistant/components/homekit/type_fans.py | 1 + homeassistant/components/homekit/type_humidifiers.py | 1 + homeassistant/components/homekit/type_locks.py | 1 + homeassistant/components/homekit/type_media_players.py | 1 + homeassistant/components/homekit/type_security_systems.py | 1 + homeassistant/components/homekit/type_thermostats.py | 1 + homeassistant/components/homematicip_cloud/__init__.py | 1 + homeassistant/components/homematicip_cloud/const.py | 1 + homeassistant/components/honeywell/const.py | 1 + homeassistant/components/hue/v1/hue_event.py | 1 + homeassistant/components/hue/v2/hue_event.py | 1 + homeassistant/components/huisbaasje/__init__.py | 1 + homeassistant/components/huisbaasje/config_flow.py | 1 + homeassistant/components/hunterdouglas_powerview/__init__.py | 1 + homeassistant/components/husqvarna_automower/coordinator.py | 1 + homeassistant/components/husqvarna_automower/lawn_mower.py | 1 + homeassistant/components/husqvarna_automower/switch.py | 1 + homeassistant/components/ialarm/config_flow.py | 1 + homeassistant/components/ihc/__init__.py | 1 + homeassistant/components/ihc/auto_setup.py | 1 + homeassistant/components/ihc/ihcdevice.py | 1 + homeassistant/components/ihc/manual_setup.py | 1 + homeassistant/components/ihc/service_functions.py | 1 + homeassistant/components/insteon/const.py | 1 + homeassistant/components/insteon/cover.py | 1 + homeassistant/components/insteon/insteon_entity.py | 1 + homeassistant/components/ios/__init__.py | 1 + homeassistant/components/ipma/__init__.py | 1 + homeassistant/components/ipma/config_flow.py | 1 + homeassistant/components/iqvia/const.py | 1 + homeassistant/components/isy994/const.py | 1 + homeassistant/components/izone/__init__.py | 1 + homeassistant/components/izone/discovery.py | 1 + homeassistant/components/jellyfin/const.py | 1 + homeassistant/components/joaoapps_join/__init__.py | 1 + homeassistant/components/juicenet/config_flow.py | 1 + homeassistant/components/kaiterra/__init__.py | 1 + homeassistant/components/kaiterra/api_data.py | 1 + homeassistant/components/keba/__init__.py | 1 + homeassistant/components/kira/__init__.py | 1 + homeassistant/components/kmtronic/__init__.py | 1 + homeassistant/components/knx/helpers/keyring.py | 1 + homeassistant/components/kodi/browse_media.py | 1 + homeassistant/components/konnected/__init__.py | 1 + homeassistant/components/konnected/handlers.py | 1 + homeassistant/components/konnected/panel.py | 1 + homeassistant/components/konnected/switch.py | 1 + homeassistant/components/kostal_plenticore/__init__.py | 1 + homeassistant/components/kostal_plenticore/config_flow.py | 1 + homeassistant/components/kulersky/config_flow.py | 1 + homeassistant/components/lastfm/const.py | 1 + homeassistant/components/laundrify/coordinator.py | 1 + homeassistant/components/lcn/schemas.py | 1 + homeassistant/components/lg_soundbar/__init__.py | 1 + homeassistant/components/lg_soundbar/config_flow.py | 1 + homeassistant/components/lidarr/const.py | 1 + homeassistant/components/lightwave/__init__.py | 1 + homeassistant/components/lirc/__init__.py | 1 + homeassistant/components/litejet/__init__.py | 1 + homeassistant/components/litejet/scene.py | 1 + homeassistant/components/livisi/const.py | 1 + homeassistant/components/logentries/__init__.py | 1 + homeassistant/components/logger/const.py | 1 + homeassistant/components/logi_circle/__init__.py | 1 + homeassistant/components/logi_circle/config_flow.py | 1 + homeassistant/components/loqed/coordinator.py | 1 + homeassistant/components/lovelace/__init__.py | 1 + homeassistant/components/lovelace/system_health.py | 1 + homeassistant/components/lupusec/entity.py | 1 + homeassistant/components/mailgun/__init__.py | 1 + homeassistant/components/matter/const.py | 1 + homeassistant/components/maxcube/__init__.py | 1 + homeassistant/components/meater/__init__.py | 1 + homeassistant/components/media_source/const.py | 1 + homeassistant/components/melissa/__init__.py | 1 + homeassistant/components/met_eireann/weather.py | 1 + homeassistant/components/meteo_france/weather.py | 1 + homeassistant/components/meteoclimatic/__init__.py | 1 + homeassistant/components/meteoclimatic/config_flow.py | 1 + homeassistant/components/microsoft/tts.py | 1 + homeassistant/components/mobile_app/config_flow.py | 1 + homeassistant/components/mobile_app/const.py | 1 + homeassistant/components/mochad/__init__.py | 1 + homeassistant/components/moehlenhoff_alpha2/climate.py | 1 + homeassistant/components/moehlenhoff_alpha2/config_flow.py | 1 + homeassistant/components/monoprice/__init__.py | 1 + homeassistant/components/monoprice/media_player.py | 1 + homeassistant/components/motion_blinds/__init__.py | 1 + homeassistant/components/motion_blinds/coordinator.py | 1 + homeassistant/components/motion_blinds/gateway.py | 1 + homeassistant/components/motionmount/binary_sensor.py | 1 + homeassistant/components/motionmount/config_flow.py | 1 + homeassistant/components/motionmount/number.py | 1 + homeassistant/components/motionmount/select.py | 1 + homeassistant/components/motionmount/sensor.py | 1 + homeassistant/components/mqtt/light/schema.py | 1 + homeassistant/components/mqtt_eventstream/__init__.py | 1 + homeassistant/components/mqtt_statestream/__init__.py | 1 + homeassistant/components/mullvad/__init__.py | 1 + homeassistant/components/mycroft/__init__.py | 1 + homeassistant/components/myuplink/coordinator.py | 1 + homeassistant/components/neato/__init__.py | 1 + homeassistant/components/netatmo/webhook.py | 1 + homeassistant/components/netgear_lte/const.py | 1 + homeassistant/components/nexia/__init__.py | 1 + homeassistant/components/nexia/config_flow.py | 1 + homeassistant/components/nibe_heatpump/const.py | 1 + homeassistant/components/nightscout/config_flow.py | 1 + homeassistant/components/no_ip/__init__.py | 1 + homeassistant/components/notify/const.py | 1 + homeassistant/components/notify_events/__init__.py | 1 + homeassistant/components/notion/const.py | 1 + homeassistant/components/nuheat/climate.py | 1 + homeassistant/components/numato/__init__.py | 1 + homeassistant/components/nzbget/__init__.py | 1 + homeassistant/components/nzbget/coordinator.py | 1 + homeassistant/components/ombi/__init__.py | 1 + homeassistant/components/omnilogic/__init__.py | 1 + homeassistant/components/omnilogic/switch.py | 1 + homeassistant/components/ondilo_ico/config_flow.py | 1 + homeassistant/components/onewire/__init__.py | 1 + homeassistant/components/onvif/__init__.py | 1 + homeassistant/components/onvif/const.py | 1 + homeassistant/components/opensky/const.py | 1 + homeassistant/components/opentherm_gw/__init__.py | 1 + homeassistant/components/opentherm_gw/binary_sensor.py | 1 + homeassistant/components/opentherm_gw/sensor.py | 1 + homeassistant/components/openuv/const.py | 1 + .../components/openweathermap/weather_update_coordinator.py | 1 + homeassistant/components/opnsense/__init__.py | 1 + homeassistant/components/owntracks/config_flow.py | 1 + homeassistant/components/owntracks/messages.py | 1 + homeassistant/components/panel_iframe/__init__.py | 1 + homeassistant/components/peco/const.py | 1 + homeassistant/components/pegel_online/coordinator.py | 1 + homeassistant/components/picnic/coordinator.py | 1 + homeassistant/components/picotts/tts.py | 1 + homeassistant/components/pilight/base_class.py | 1 + homeassistant/components/ping/helpers.py | 1 + homeassistant/components/plex/models.py | 1 + homeassistant/components/plex/services.py | 1 + homeassistant/components/plex/update.py | 1 + homeassistant/components/plum_lightpad/__init__.py | 1 + homeassistant/components/point/__init__.py | 1 + homeassistant/components/point/config_flow.py | 1 + homeassistant/components/poolsense/__init__.py | 1 + homeassistant/components/poolsense/config_flow.py | 1 + homeassistant/components/poolsense/coordinator.py | 1 + homeassistant/components/profiler/__init__.py | 1 + homeassistant/components/progettihwsw/binary_sensor.py | 1 + homeassistant/components/progettihwsw/switch.py | 1 + homeassistant/components/prosegur/__init__.py | 1 + homeassistant/components/ps4/__init__.py | 1 + homeassistant/components/purpleair/const.py | 1 + homeassistant/components/python_script/__init__.py | 1 + homeassistant/components/qbittorrent/__init__.py | 1 + homeassistant/components/qvr_pro/__init__.py | 1 + homeassistant/components/rachio/__init__.py | 1 + homeassistant/components/radarr/const.py | 1 + homeassistant/components/radio_browser/const.py | 1 + homeassistant/components/rainmachine/const.py | 1 + homeassistant/components/recollect_waste/const.py | 1 + homeassistant/components/recorder/entity_registry.py | 1 + homeassistant/components/recorder/pool.py | 1 + homeassistant/components/remember_the_milk/__init__.py | 1 + homeassistant/components/renault/__init__.py | 1 + homeassistant/components/ridwell/const.py | 1 + homeassistant/components/ring/siren.py | 1 + homeassistant/components/rituals_perfume_genie/__init__.py | 1 + homeassistant/components/rituals_perfume_genie/coordinator.py | 1 + homeassistant/components/roborock/image.py | 1 + homeassistant/components/roborock/number.py | 1 + homeassistant/components/roborock/time.py | 1 + homeassistant/components/roomba/__init__.py | 1 + homeassistant/components/roomba/braava.py | 1 + homeassistant/components/roomba/roomba.py | 1 + homeassistant/components/roon/config_flow.py | 1 + homeassistant/components/roon/event.py | 1 + homeassistant/components/roon/media_browser.py | 1 + homeassistant/components/roon/server.py | 1 + homeassistant/components/rova/const.py | 1 + homeassistant/components/rpi_camera/__init__.py | 1 + homeassistant/components/rpi_power/binary_sensor.py | 1 + homeassistant/components/ruckus_unleashed/__init__.py | 1 + homeassistant/components/samsungtv/const.py | 1 + homeassistant/components/satel_integra/__init__.py | 1 + homeassistant/components/schedule/const.py | 1 + homeassistant/components/schluter/__init__.py | 1 + homeassistant/components/screenlogic/__init__.py | 1 + homeassistant/components/screenlogic/util.py | 1 + homeassistant/components/script/const.py | 1 + homeassistant/components/scsgate/__init__.py | 1 + homeassistant/components/sense/binary_sensor.py | 1 + homeassistant/components/sensor/device_trigger.py | 1 + homeassistant/components/senz/config_flow.py | 1 + homeassistant/components/sharkiq/__init__.py | 1 + homeassistant/components/simplisafe/const.py | 1 + homeassistant/components/sisyphus/__init__.py | 1 + homeassistant/components/skybell/const.py | 1 + homeassistant/components/sleepiq/coordinator.py | 1 + homeassistant/components/sleepiq/light.py | 1 + homeassistant/components/smappee/config_flow.py | 1 + homeassistant/components/smart_meter_texas/__init__.py | 1 + homeassistant/components/smart_meter_texas/config_flow.py | 1 + homeassistant/components/smartthings/smartapp.py | 1 + homeassistant/components/smarttub/entity.py | 1 + homeassistant/components/smarttub/switch.py | 1 + homeassistant/components/sms/__init__.py | 1 + homeassistant/components/sms/config_flow.py | 1 + homeassistant/components/sms/coordinator.py | 1 + homeassistant/components/sms/gateway.py | 1 + homeassistant/components/snapcast/__init__.py | 1 + homeassistant/components/solarlog/config_flow.py | 1 + homeassistant/components/soma/config_flow.py | 1 + homeassistant/components/somfy_mylink/__init__.py | 1 + homeassistant/components/somfy_mylink/cover.py | 1 + homeassistant/components/sonarr/const.py | 1 + homeassistant/components/songpal/__init__.py | 1 + homeassistant/components/soundtouch/__init__.py | 1 + homeassistant/components/soundtouch/config_flow.py | 1 + homeassistant/components/spc/__init__.py | 1 + homeassistant/components/spider/__init__.py | 1 + homeassistant/components/spider/config_flow.py | 1 + homeassistant/components/sql/const.py | 1 + homeassistant/components/squeezebox/browse_media.py | 1 + homeassistant/components/squeezebox/config_flow.py | 1 + homeassistant/components/starline/const.py | 1 + homeassistant/components/statsd/__init__.py | 1 + homeassistant/components/steam_online/const.py | 1 + homeassistant/components/stookalert/const.py | 1 + homeassistant/components/streamlabswater/const.py | 1 + homeassistant/components/subaru/lock.py | 1 + homeassistant/components/subaru/remote_service.py | 1 + homeassistant/components/sunweg/__init__.py | 1 + homeassistant/components/swiss_public_transport/__init__.py | 1 + homeassistant/components/swiss_public_transport/config_flow.py | 1 + homeassistant/components/switchbee/entity.py | 1 + homeassistant/components/syncthing/__init__.py | 1 + homeassistant/components/syncthing/config_flow.py | 1 + homeassistant/components/syncthing/sensor.py | 1 + homeassistant/components/tado/services.py | 1 + homeassistant/components/tado/water_heater.py | 1 + homeassistant/components/tami4/sensor.py | 1 + homeassistant/components/tedee/__init__.py | 1 + homeassistant/components/telegram_bot/polling.py | 1 + homeassistant/components/telegram_bot/webhooks.py | 1 + homeassistant/components/tellduslive/__init__.py | 1 + homeassistant/components/tellduslive/config_flow.py | 1 + homeassistant/components/tellduslive/light.py | 1 + homeassistant/components/tellstick/__init__.py | 1 + homeassistant/components/template/config.py | 1 + homeassistant/components/teslemetry/__init__.py | 1 + homeassistant/components/thethingsnetwork/__init__.py | 1 + homeassistant/components/thingspeak/__init__.py | 1 + homeassistant/components/tibber/__init__.py | 1 + homeassistant/components/tile/const.py | 1 + homeassistant/components/toon/__init__.py | 1 + homeassistant/components/totalconnect/binary_sensor.py | 1 + homeassistant/components/tplink_omada/coordinator.py | 1 + homeassistant/components/trace/websocket_api.py | 1 + homeassistant/components/tradfri/const.py | 1 + homeassistant/components/twitch/const.py | 1 + homeassistant/components/upb/__init__.py | 1 + homeassistant/components/upb/config_flow.py | 1 + homeassistant/components/venstar/const.py | 1 + homeassistant/components/versasense/__init__.py | 1 + homeassistant/components/vesync/__init__.py | 1 + homeassistant/components/vesync/common.py | 1 + homeassistant/components/vesync/light.py | 1 + homeassistant/components/vesync/switch.py | 1 + homeassistant/components/vicare/const.py | 1 + homeassistant/components/vicare/utils.py | 1 + homeassistant/components/vilfo/config_flow.py | 1 + homeassistant/components/vlc_telnet/const.py | 1 + homeassistant/components/vodafone_station/const.py | 1 + homeassistant/components/voicerss/tts.py | 1 + homeassistant/components/volumio/browse_media.py | 1 + homeassistant/components/w800rf32/__init__.py | 1 + homeassistant/components/waqi/const.py | 1 + homeassistant/components/watson_iot/__init__.py | 1 + homeassistant/components/watson_tts/tts.py | 1 + homeassistant/components/watttime/const.py | 1 + homeassistant/components/waze_travel_time/__init__.py | 1 + homeassistant/components/waze_travel_time/helpers.py | 1 + homeassistant/components/weatherflow_cloud/const.py | 1 + homeassistant/components/webostv/const.py | 1 + homeassistant/components/wirelesstag/__init__.py | 1 + homeassistant/components/withings/const.py | 1 + homeassistant/components/wolflink/config_flow.py | 1 + homeassistant/components/xbox/config_flow.py | 1 + homeassistant/components/xiaomi_aqara/__init__.py | 1 + homeassistant/components/xiaomi_aqara/binary_sensor.py | 1 + homeassistant/components/xiaomi_aqara/config_flow.py | 1 + homeassistant/components/xiaomi_aqara/light.py | 1 + homeassistant/components/xiaomi_aqara/switch.py | 1 + homeassistant/components/xiaomi_miio/device.py | 1 + homeassistant/components/xiaomi_miio/gateway.py | 1 + homeassistant/components/xiaomi_miio/humidifier.py | 1 + homeassistant/components/xs1/__init__.py | 1 + homeassistant/components/yale_smart_alarm/const.py | 1 + homeassistant/components/yandextts/tts.py | 1 + homeassistant/components/yeelight/binary_sensor.py | 1 + homeassistant/components/youtube/const.py | 1 + homeassistant/components/zerproc/config_flow.py | 1 + homeassistant/components/zha/__init__.py | 1 + homeassistant/components/zha/backup.py | 1 + homeassistant/components/zha/lock.py | 1 + homeassistant/components/zoneminder/__init__.py | 1 + homeassistant/components/zwave_me/__init__.py | 1 + homeassistant/components/zwave_me/switch.py | 1 + 461 files changed, 461 insertions(+) diff --git a/homeassistant/components/abode/const.py b/homeassistant/components/abode/const.py index e24fe066823..7515b83ea07 100644 --- a/homeassistant/components/abode/const.py +++ b/homeassistant/components/abode/const.py @@ -1,4 +1,5 @@ """Constants for the Abode Security System component.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/acmeda/const.py b/homeassistant/components/acmeda/const.py index b8712fee4ba..c65efcc02f6 100644 --- a/homeassistant/components/acmeda/const.py +++ b/homeassistant/components/acmeda/const.py @@ -1,4 +1,5 @@ """Constants for the Rollease Acmeda Automate integration.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/adguard/const.py b/homeassistant/components/adguard/const.py index a4ccde68539..7b6827c19d4 100644 --- a/homeassistant/components/adguard/const.py +++ b/homeassistant/components/adguard/const.py @@ -1,4 +1,5 @@ """Constants for the AdGuard Home integration.""" + import logging DOMAIN = "adguard" diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 84d9e29a518..7041a757a42 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -1,4 +1,5 @@ """Support for Automation Device Specification (ADS).""" + import asyncio from asyncio import timeout from collections import namedtuple diff --git a/homeassistant/components/airnow/__init__.py b/homeassistant/components/airnow/__init__.py index a494ac0c93f..8fba13164e7 100644 --- a/homeassistant/components/airnow/__init__.py +++ b/homeassistant/components/airnow/__init__.py @@ -1,4 +1,5 @@ """The AirNow integration.""" + import datetime import logging diff --git a/homeassistant/components/airnow/config_flow.py b/homeassistant/components/airnow/config_flow.py index e6ffbf1931a..dd17e7f98db 100644 --- a/homeassistant/components/airnow/config_flow.py +++ b/homeassistant/components/airnow/config_flow.py @@ -1,4 +1,5 @@ """Config flow for AirNow integration.""" + import logging from typing import Any diff --git a/homeassistant/components/airtouch4/coordinator.py b/homeassistant/components/airtouch4/coordinator.py index e78bf62dbd0..5a080566416 100644 --- a/homeassistant/components/airtouch4/coordinator.py +++ b/homeassistant/components/airtouch4/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for the airtouch integration.""" + import logging from airtouch4pyapi.airtouch import AirTouchStatus diff --git a/homeassistant/components/airtouch5/climate.py b/homeassistant/components/airtouch5/climate.py index ee92f68c0ed..443f4b71a4a 100644 --- a/homeassistant/components/airtouch5/climate.py +++ b/homeassistant/components/airtouch5/climate.py @@ -1,4 +1,5 @@ """AirTouch 5 component to control AirTouch 5 Climate Devices.""" + import logging from typing import Any diff --git a/homeassistant/components/airvisual/const.py b/homeassistant/components/airvisual/const.py index 0afa7d32d41..c81ea8d8d00 100644 --- a/homeassistant/components/airvisual/const.py +++ b/homeassistant/components/airvisual/const.py @@ -1,4 +1,5 @@ """Define AirVisual constants.""" + import logging DOMAIN = "airvisual" diff --git a/homeassistant/components/airvisual_pro/const.py b/homeassistant/components/airvisual_pro/const.py index 83a6cc5739c..ac47eff1cf0 100644 --- a/homeassistant/components/airvisual_pro/const.py +++ b/homeassistant/components/airvisual_pro/const.py @@ -1,4 +1,5 @@ """Constants for the AirVisual Pro integration.""" + import logging DOMAIN = "airvisual_pro" diff --git a/homeassistant/components/aladdin_connect/__init__.py b/homeassistant/components/aladdin_connect/__init__.py index d1c7bc5668b..84710c3f74e 100644 --- a/homeassistant/components/aladdin_connect/__init__.py +++ b/homeassistant/components/aladdin_connect/__init__.py @@ -1,4 +1,5 @@ """The aladdin_connect component.""" + import logging from typing import Final diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index 47e6066400c..1d41dcd2364 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -1,4 +1,5 @@ """Support for AlarmDecoder zone states- represented as binary sensors.""" + import logging from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index 10a7be4967e..63fa7edc17a 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -1,4 +1,5 @@ """Support for Alexa skill auth.""" + import asyncio from asyncio import timeout from datetime import datetime, timedelta diff --git a/homeassistant/components/alexa/flash_briefings.py b/homeassistant/components/alexa/flash_briefings.py index 3361908ce9a..eed700602ce 100644 --- a/homeassistant/components/alexa/flash_briefings.py +++ b/homeassistant/components/alexa/flash_briefings.py @@ -1,4 +1,5 @@ """Support for Alexa skill service end point.""" + import hmac from http import HTTPStatus import logging diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index a55ff7b774f..03c6ac640e3 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -1,4 +1,5 @@ """Support for Alexa skill service end point.""" + import enum import logging from typing import Any diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index dc93ec56810..81ce2981acb 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -1,4 +1,5 @@ """Support for alexa Smart Home Skill API.""" + import logging from typing import Any diff --git a/homeassistant/components/amberelectric/const.py b/homeassistant/components/amberelectric/const.py index 6166b21c19f..56324628ed6 100644 --- a/homeassistant/components/amberelectric/const.py +++ b/homeassistant/components/amberelectric/const.py @@ -1,4 +1,5 @@ """Amber Electric Constants.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/ambiclimate/__init__.py b/homeassistant/components/ambiclimate/__init__.py index 7ed9deec898..75691aebbf8 100644 --- a/homeassistant/components/ambiclimate/__init__.py +++ b/homeassistant/components/ambiclimate/__init__.py @@ -1,4 +1,5 @@ """Support for Ambiclimate devices.""" + import voluptuous as vol from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 1087f29643c..9d5848ea899 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ambiclimate.""" + import logging from typing import Any diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index 4e0ec598fb1..eb8994d5f02 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -1,4 +1,5 @@ """Define constants for the Ambient PWS component.""" + import logging DOMAIN = "ambient_station" diff --git a/homeassistant/components/analytics_insights/const.py b/homeassistant/components/analytics_insights/const.py index 745c05302a1..56ea3f59794 100644 --- a/homeassistant/components/analytics_insights/const.py +++ b/homeassistant/components/analytics_insights/const.py @@ -1,4 +1,5 @@ """Constants for the Homeassistant Analytics integration.""" + import logging DOMAIN = "analytics_insights" diff --git a/homeassistant/components/aosmith/coordinator.py b/homeassistant/components/aosmith/coordinator.py index a0dd703b800..3bf97e49cae 100644 --- a/homeassistant/components/aosmith/coordinator.py +++ b/homeassistant/components/aosmith/coordinator.py @@ -1,4 +1,5 @@ """The data update coordinator for the A. O. Smith integration.""" + import logging from py_aosmith import ( diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index a6b899d01ec..15ece3e296a 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -1,4 +1,5 @@ """Rest API for Home Assistant.""" + import asyncio from asyncio import shield, timeout from functools import lru_cache diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py index 7baa6321f21..822a9c3306a 100644 --- a/homeassistant/components/apple_tv/remote.py +++ b/homeassistant/components/apple_tv/remote.py @@ -1,4 +1,5 @@ """Remote control support for Apple TV.""" + import asyncio from collections.abc import Iterable import logging diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index a45dd89e180..ff6bd872065 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -1,4 +1,5 @@ """Arcam component.""" + import asyncio from asyncio import timeout import logging diff --git a/homeassistant/components/assist_pipeline/websocket_api.py b/homeassistant/components/assist_pipeline/websocket_api.py index f7a6d3c43fa..7550f860a9b 100644 --- a/homeassistant/components/assist_pipeline/websocket_api.py +++ b/homeassistant/components/assist_pipeline/websocket_api.py @@ -1,4 +1,5 @@ """Assist pipeline Websocket API.""" + import asyncio # Suppressing disable=deprecated-module is needed for Python 3.11 diff --git a/homeassistant/components/asterisk_mbox/__init__.py b/homeassistant/components/asterisk_mbox/__init__.py index 7fa1e1f14da..3e3913b7d42 100644 --- a/homeassistant/components/asterisk_mbox/__init__.py +++ b/homeassistant/components/asterisk_mbox/__init__.py @@ -1,4 +1,5 @@ """Support for Asterisk Voicemail interface.""" + import logging from typing import Any, cast diff --git a/homeassistant/components/automation/const.py b/homeassistant/components/automation/const.py index a82c78ded77..e6be35494d7 100644 --- a/homeassistant/components/automation/const.py +++ b/homeassistant/components/automation/const.py @@ -1,4 +1,5 @@ """Constants for the automation integration.""" + import logging CONF_ACTION = "action" diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index 0638ae94de2..9335594b925 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -1,4 +1,5 @@ """Support for Amazon Web Services (AWS).""" + import asyncio from collections import OrderedDict import logging diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index d37b2ebe5ac..f955ff398d7 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -1,4 +1,5 @@ """Support for Axis devices.""" + import logging from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py index f4cc35dd7d2..d315214c0e7 100644 --- a/homeassistant/components/axis/const.py +++ b/homeassistant/components/axis/const.py @@ -1,4 +1,5 @@ """Constants for the Axis component.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/baidu/tts.py b/homeassistant/components/baidu/tts.py index 05e2956c10e..859e79adec9 100644 --- a/homeassistant/components/baidu/tts.py +++ b/homeassistant/components/baidu/tts.py @@ -1,4 +1,5 @@ """Support for Baidu speech service.""" + import logging from aip import AipSpeech diff --git a/homeassistant/components/binary_sensor/device_trigger.py b/homeassistant/components/binary_sensor/device_trigger.py index de6dbdbe075..803aff97197 100644 --- a/homeassistant/components/binary_sensor/device_trigger.py +++ b/homeassistant/components/binary_sensor/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for binary sensors.""" + import voluptuous as vol from homeassistant.components.device_automation import ( diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index d6c3cda7ef4..ce142101c3e 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -1,4 +1,5 @@ """The BleBox devices integration.""" + import logging from typing import Generic, TypeVar diff --git a/homeassistant/components/bosch_shc/__init__.py b/homeassistant/components/bosch_shc/__init__.py index a8b2a389f9f..1ccb31d1dc9 100644 --- a/homeassistant/components/bosch_shc/__init__.py +++ b/homeassistant/components/bosch_shc/__init__.py @@ -1,4 +1,5 @@ """The Bosch Smart Home Controller integration.""" + import logging from boschshcpy import SHCSession diff --git a/homeassistant/components/broadlink/heartbeat.py b/homeassistant/components/broadlink/heartbeat.py index 70f6aec0d0f..7777e4da94e 100644 --- a/homeassistant/components/broadlink/heartbeat.py +++ b/homeassistant/components/broadlink/heartbeat.py @@ -1,4 +1,5 @@ """Heartbeats for Broadlink devices.""" + import datetime as dt import logging diff --git a/homeassistant/components/broadlink/light.py b/homeassistant/components/broadlink/light.py index fde6d322bc6..39d6caaa49f 100644 --- a/homeassistant/components/broadlink/light.py +++ b/homeassistant/components/broadlink/light.py @@ -1,4 +1,5 @@ """Support for Broadlink lights.""" + import logging from typing import Any diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index c0fb80971ca..f8d903c51eb 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -1,4 +1,5 @@ """Support for Broadlink remotes.""" + import asyncio from base64 import b64encode from collections import defaultdict diff --git a/homeassistant/components/browser/__init__.py b/homeassistant/components/browser/__init__.py index 9dc3e1fe66a..7f562cd3bed 100644 --- a/homeassistant/components/browser/__init__.py +++ b/homeassistant/components/browser/__init__.py @@ -1,4 +1,5 @@ """Support for launching a web browser on the host machine.""" + import webbrowser import voluptuous as vol diff --git a/homeassistant/components/bsblan/__init__.py b/homeassistant/components/bsblan/__init__.py index def2cfaf56a..9a471329ba9 100644 --- a/homeassistant/components/bsblan/__init__.py +++ b/homeassistant/components/bsblan/__init__.py @@ -1,4 +1,5 @@ """The BSB-Lan integration.""" + import dataclasses from bsblan import BSBLAN, Device, Info, StaticState diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index c7387857da5..a7a6d3dc02e 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -1,4 +1,5 @@ """Support for Buienradar.nl weather service.""" + import logging from buienradar.constants import ( diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 485d2888a41..4d956205990 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -1,4 +1,5 @@ """Deal with Cast discovery.""" + import logging import threading diff --git a/homeassistant/components/ccm15/climate.py b/homeassistant/components/ccm15/climate.py index 1f90f317fe0..e364bf0af5c 100644 --- a/homeassistant/components/ccm15/climate.py +++ b/homeassistant/components/ccm15/climate.py @@ -1,4 +1,5 @@ """Climate device for CCM15 coordinator.""" + import logging from typing import Any diff --git a/homeassistant/components/ccm15/coordinator.py b/homeassistant/components/ccm15/coordinator.py index 9d8a0281706..cd3b313f700 100644 --- a/homeassistant/components/ccm15/coordinator.py +++ b/homeassistant/components/ccm15/coordinator.py @@ -1,4 +1,5 @@ """Climate device for CCM15 coordinator.""" + import datetime import logging diff --git a/homeassistant/components/cert_expiry/helper.py b/homeassistant/components/cert_expiry/helper.py index 6d10d750705..f3e0688c015 100644 --- a/homeassistant/components/cert_expiry/helper.py +++ b/homeassistant/components/cert_expiry/helper.py @@ -1,4 +1,5 @@ """Helper functions for the Cert Expiry platform.""" + import asyncio import datetime from functools import cache diff --git a/homeassistant/components/circuit/__init__.py b/homeassistant/components/circuit/__init__.py index 488b0e94e09..f71babad3d5 100644 --- a/homeassistant/components/circuit/__init__.py +++ b/homeassistant/components/circuit/__init__.py @@ -1,4 +1,5 @@ """The Unify Circuit component.""" + import voluptuous as vol from homeassistant.const import CONF_NAME, CONF_URL, Platform diff --git a/homeassistant/components/cloud/assist_pipeline.py b/homeassistant/components/cloud/assist_pipeline.py index 2c381dd0ac0..e9d66bdcc1f 100644 --- a/homeassistant/components/cloud/assist_pipeline.py +++ b/homeassistant/components/cloud/assist_pipeline.py @@ -1,4 +1,5 @@ """Handle Cloud assist pipelines.""" + import asyncio from homeassistant.components.assist_pipeline import ( diff --git a/homeassistant/components/cloudflare/helpers.py b/homeassistant/components/cloudflare/helpers.py index 0542bce0980..937f6036703 100644 --- a/homeassistant/components/cloudflare/helpers.py +++ b/homeassistant/components/cloudflare/helpers.py @@ -1,4 +1,5 @@ """Helpers for the CloudFlare integration.""" + import pycfdns diff --git a/homeassistant/components/color_extractor/__init__.py b/homeassistant/components/color_extractor/__init__.py index e6095c9f925..56270ce0f75 100644 --- a/homeassistant/components/color_extractor/__init__.py +++ b/homeassistant/components/color_extractor/__init__.py @@ -1,4 +1,5 @@ """Module for color_extractor (RGB extraction from images) component.""" + import asyncio import io import logging diff --git a/homeassistant/components/comelit/const.py b/homeassistant/components/comelit/const.py index ca10e0b0a74..84d8fbd6315 100644 --- a/homeassistant/components/comelit/const.py +++ b/homeassistant/components/comelit/const.py @@ -1,4 +1,5 @@ """Comelit constants.""" + import logging from aiocomelit.const import BRIDGE, VEDO diff --git a/homeassistant/components/comfoconnect/__init__.py b/homeassistant/components/comfoconnect/__init__.py index 5ff34526cc0..118b59d6cae 100644 --- a/homeassistant/components/comfoconnect/__init__.py +++ b/homeassistant/components/comfoconnect/__init__.py @@ -1,4 +1,5 @@ """Support to control a Zehnder ComfoAir Q350/450/600 ventilation unit.""" + import logging from pycomfoconnect import Bridge, ComfoConnect diff --git a/homeassistant/components/compensation/__init__.py b/homeassistant/components/compensation/__init__.py index 01003020108..dc1f903e8f6 100644 --- a/homeassistant/components/compensation/__init__.py +++ b/homeassistant/components/compensation/__init__.py @@ -1,4 +1,5 @@ """The Compensation integration.""" + import logging from operator import itemgetter diff --git a/homeassistant/components/coolmaster/coordinator.py b/homeassistant/components/coolmaster/coordinator.py index 241f287e297..54d69b1c540 100644 --- a/homeassistant/components/coolmaster/coordinator.py +++ b/homeassistant/components/coolmaster/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for coolmaster integration.""" + import logging from homeassistant.components.climate import SCAN_INTERVAL diff --git a/homeassistant/components/cpuspeed/const.py b/homeassistant/components/cpuspeed/const.py index 8fdb8d3c986..73c637a8e26 100644 --- a/homeassistant/components/cpuspeed/const.py +++ b/homeassistant/components/cpuspeed/const.py @@ -1,4 +1,5 @@ """Constants for the CPU Speed integration.""" + import logging from typing import Final diff --git a/homeassistant/components/datadog/__init__.py b/homeassistant/components/datadog/__init__.py index 47a2d34ec8e..2d550e48e2f 100644 --- a/homeassistant/components/datadog/__init__.py +++ b/homeassistant/components/datadog/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to Datadog.""" + import logging from datadog import initialize, statsd diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index ca38edf0625..873f5cde284 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,4 +1,5 @@ """Constants for the deCONZ component.""" + import logging from pydeconz.models import ResourceType diff --git a/homeassistant/components/deluge/const.py b/homeassistant/components/deluge/const.py index 704c024c41b..91e08da3470 100644 --- a/homeassistant/components/deluge/const.py +++ b/homeassistant/components/deluge/const.py @@ -1,4 +1,5 @@ """Constants for the Deluge integration.""" + import logging from typing import Final diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py index d9a1300ed0e..98b77a994f6 100644 --- a/homeassistant/components/denonavr/__init__.py +++ b/homeassistant/components/denonavr/__init__.py @@ -1,4 +1,5 @@ """The denonavr component.""" + import logging from denonavr import DenonAVR diff --git a/homeassistant/components/devolo_home_control/const.py b/homeassistant/components/devolo_home_control/const.py index 6ba934f6591..eb48a6d269e 100644 --- a/homeassistant/components/devolo_home_control/const.py +++ b/homeassistant/components/devolo_home_control/const.py @@ -1,4 +1,5 @@ """Constants for the devolo_home_control integration.""" + import re from homeassistant.const import Platform diff --git a/homeassistant/components/dialogflow/__init__.py b/homeassistant/components/dialogflow/__init__.py index d34b27233be..95c8861d665 100644 --- a/homeassistant/components/dialogflow/__init__.py +++ b/homeassistant/components/dialogflow/__init__.py @@ -1,4 +1,5 @@ """Support for Dialogflow webhook.""" + import logging from aiohttp import web diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index f46719febb1..c1cb1a0fb1b 100644 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -1,4 +1,5 @@ """Constants for the Dynalite component.""" + import logging from homeassistant.const import CONF_ROOM, Platform diff --git a/homeassistant/components/eafm/__init__.py b/homeassistant/components/eafm/__init__.py index d18553b6ee6..1f95437484f 100644 --- a/homeassistant/components/eafm/__init__.py +++ b/homeassistant/components/eafm/__init__.py @@ -1,4 +1,5 @@ """UK Environment Agency Flood Monitoring Integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index b1eb03989ea..debfc335496 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -1,4 +1,5 @@ """Support for Ebusd daemon for communication with eBUS heating systems.""" + import logging import ebusdpy diff --git a/homeassistant/components/ecoal_boiler/__init__.py b/homeassistant/components/ecoal_boiler/__init__.py index 4db8a3ab6cc..e9b519c7095 100644 --- a/homeassistant/components/ecoal_boiler/__init__.py +++ b/homeassistant/components/ecoal_boiler/__init__.py @@ -1,4 +1,5 @@ """Support to control ecoal/esterownik.pl coal/wood boiler controller.""" + import logging from ecoaliface.simple import ECoalController diff --git a/homeassistant/components/ecobee/const.py b/homeassistant/components/ecobee/const.py index 23fe544d3c9..e20acb5cfca 100644 --- a/homeassistant/components/ecobee/const.py +++ b/homeassistant/components/ecobee/const.py @@ -1,4 +1,5 @@ """Constants for the ecobee integration.""" + import logging from homeassistant.components.weather import ( diff --git a/homeassistant/components/econet/__init__.py b/homeassistant/components/econet/__init__.py index 5728c87938b..84e636e660b 100644 --- a/homeassistant/components/econet/__init__.py +++ b/homeassistant/components/econet/__init__.py @@ -1,4 +1,5 @@ """Support for EcoNet products.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/ecovacs/__init__.py b/homeassistant/components/ecovacs/__init__.py index 945f999cf79..229a2ac8ac6 100644 --- a/homeassistant/components/ecovacs/__init__.py +++ b/homeassistant/components/ecovacs/__init__.py @@ -1,4 +1,5 @@ """Support for Ecovacs Deebot vacuums.""" + import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry diff --git a/homeassistant/components/ecowitt/binary_sensor.py b/homeassistant/components/ecowitt/binary_sensor.py index fbe2e017339..f73467288a2 100644 --- a/homeassistant/components/ecowitt/binary_sensor.py +++ b/homeassistant/components/ecowitt/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Ecowitt Weather Stations.""" + import dataclasses from typing import Final diff --git a/homeassistant/components/edl21/const.py b/homeassistant/components/edl21/const.py index 2bde0ff379a..37b8c140f55 100644 --- a/homeassistant/components/edl21/const.py +++ b/homeassistant/components/edl21/const.py @@ -1,4 +1,5 @@ """Constants for the EDL21 component.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/egardia/__init__.py b/homeassistant/components/egardia/__init__.py index 12091cf7a8a..9ff4b9af94f 100644 --- a/homeassistant/components/egardia/__init__.py +++ b/homeassistant/components/egardia/__init__.py @@ -1,4 +1,5 @@ """Interfaces with Egardia/Woonveilig alarm control panel.""" + import logging from pythonegardia import egardiadevice, egardiaserver diff --git a/homeassistant/components/electric_kiwi/coordinator.py b/homeassistant/components/electric_kiwi/coordinator.py index c3f49d1aba9..a10be5eafdd 100644 --- a/homeassistant/components/electric_kiwi/coordinator.py +++ b/homeassistant/components/electric_kiwi/coordinator.py @@ -1,4 +1,5 @@ """Electric Kiwi coordinators.""" + import asyncio from collections import OrderedDict from datetime import timedelta diff --git a/homeassistant/components/elmax/switch.py b/homeassistant/components/elmax/switch.py index 5f3ca4aea7c..911ad864b50 100644 --- a/homeassistant/components/elmax/switch.py +++ b/homeassistant/components/elmax/switch.py @@ -1,4 +1,5 @@ """Elmax switch platform.""" + import asyncio import logging from typing import Any diff --git a/homeassistant/components/elv/__init__.py b/homeassistant/components/elv/__init__.py index 7e5b8a322a5..208d19a0f8e 100644 --- a/homeassistant/components/elv/__init__.py +++ b/homeassistant/components/elv/__init__.py @@ -1,4 +1,5 @@ """The Elv integration.""" + import voluptuous as vol from homeassistant.const import CONF_DEVICE, Platform diff --git a/homeassistant/components/emonitor/config_flow.py b/homeassistant/components/emonitor/config_flow.py index 5d0ba30946a..70bd58e4cc0 100644 --- a/homeassistant/components/emonitor/config_flow.py +++ b/homeassistant/components/emonitor/config_flow.py @@ -1,4 +1,5 @@ """Config flow for SiteSage Emonitor integration.""" + import logging from aioemonitor import Emonitor diff --git a/homeassistant/components/emulated_kasa/__init__.py b/homeassistant/components/emulated_kasa/__init__.py index 41198f922cf..d5fc8af1aa4 100644 --- a/homeassistant/components/emulated_kasa/__init__.py +++ b/homeassistant/components/emulated_kasa/__init__.py @@ -1,4 +1,5 @@ """Support for local power state reporting of entities by emulating TP-Link Kasa smart plugs.""" + import logging from sense_energy import PlugInstance, SenseLink diff --git a/homeassistant/components/emulated_roku/__init__.py b/homeassistant/components/emulated_roku/__init__.py index 59929eab118..4ebd31730bf 100644 --- a/homeassistant/components/emulated_roku/__init__.py +++ b/homeassistant/components/emulated_roku/__init__.py @@ -1,4 +1,5 @@ """Support for Roku API emulation.""" + import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/emulated_roku/binding.py b/homeassistant/components/emulated_roku/binding.py index 3559c0da99b..a84db4bd77b 100644 --- a/homeassistant/components/emulated_roku/binding.py +++ b/homeassistant/components/emulated_roku/binding.py @@ -1,4 +1,5 @@ """Bridge between emulated_roku and Home Assistant.""" + import logging from emulated_roku import EmulatedRokuCommandHandler, EmulatedRokuServer diff --git a/homeassistant/components/emulated_roku/config_flow.py b/homeassistant/components/emulated_roku/config_flow.py index cdfd0d93843..1a3b2c0e2af 100644 --- a/homeassistant/components/emulated_roku/config_flow.py +++ b/homeassistant/components/emulated_roku/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure emulated_roku component.""" + import voluptuous as vol from homeassistant.config_entries import ConfigFlow diff --git a/homeassistant/components/enocean/__init__.py b/homeassistant/components/enocean/__init__.py index 18c3b888d19..6dcec5ec218 100644 --- a/homeassistant/components/enocean/__init__.py +++ b/homeassistant/components/enocean/__init__.py @@ -1,4 +1,5 @@ """Support for EnOcean devices.""" + import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry diff --git a/homeassistant/components/enocean/const.py b/homeassistant/components/enocean/const.py index f9c522393d7..3624493b42e 100644 --- a/homeassistant/components/enocean/const.py +++ b/homeassistant/components/enocean/const.py @@ -1,4 +1,5 @@ """Constants for the ENOcean integration.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/enocean/dongle.py b/homeassistant/components/enocean/dongle.py index 9ccaeba504c..6402b4c3a28 100644 --- a/homeassistant/components/enocean/dongle.py +++ b/homeassistant/components/enocean/dongle.py @@ -1,4 +1,5 @@ """Representation of an EnOcean dongle.""" + import glob import logging from os.path import basename, normpath diff --git a/homeassistant/components/environment_canada/config_flow.py b/homeassistant/components/environment_canada/config_flow.py index 9249a22ba1d..369a419f2a6 100644 --- a/homeassistant/components/environment_canada/config_flow.py +++ b/homeassistant/components/environment_canada/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Environment Canada integration.""" + import logging import xml.etree.ElementTree as et diff --git a/homeassistant/components/envisalink/__init__.py b/homeassistant/components/envisalink/__init__.py index b0a4619bbf9..65fdc1b5c63 100644 --- a/homeassistant/components/envisalink/__init__.py +++ b/homeassistant/components/envisalink/__init__.py @@ -1,4 +1,5 @@ """Support for Envisalink devices.""" + import asyncio import logging diff --git a/homeassistant/components/epson/__init__.py b/homeassistant/components/epson/__init__.py index 5a8544c7bf1..5171865594d 100644 --- a/homeassistant/components/epson/__init__.py +++ b/homeassistant/components/epson/__init__.py @@ -1,4 +1,5 @@ """The epson integration.""" + import logging from epson_projector import Projector diff --git a/homeassistant/components/epson/config_flow.py b/homeassistant/components/epson/config_flow.py index 32a41a38b55..4f038de9318 100644 --- a/homeassistant/components/epson/config_flow.py +++ b/homeassistant/components/epson/config_flow.py @@ -1,4 +1,5 @@ """Config flow for epson integration.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/escea/config_flow.py b/homeassistant/components/escea/config_flow.py index eb50e7d0fdc..957b1f6d146 100644 --- a/homeassistant/components/escea/config_flow.py +++ b/homeassistant/components/escea/config_flow.py @@ -1,4 +1,5 @@ """Config flow for escea.""" + import asyncio from contextlib import suppress import logging diff --git a/homeassistant/components/eufy/__init__.py b/homeassistant/components/eufy/__init__.py index 52d6fead3eb..8ebe3e08843 100644 --- a/homeassistant/components/eufy/__init__.py +++ b/homeassistant/components/eufy/__init__.py @@ -1,4 +1,5 @@ """Support for EufyHome devices.""" + import lakeside import voluptuous as vol diff --git a/homeassistant/components/ezviz/__init__.py b/homeassistant/components/ezviz/__init__.py index 12754af25e8..c453060b472 100644 --- a/homeassistant/components/ezviz/__init__.py +++ b/homeassistant/components/ezviz/__init__.py @@ -1,4 +1,5 @@ """Support for EZVIZ camera.""" + import logging from pyezviz.client import EzvizClient diff --git a/homeassistant/components/ezviz/coordinator.py b/homeassistant/components/ezviz/coordinator.py index 427e52f7dd0..c983371f4f8 100644 --- a/homeassistant/components/ezviz/coordinator.py +++ b/homeassistant/components/ezviz/coordinator.py @@ -1,4 +1,5 @@ """Provides the ezviz DataUpdateCoordinator.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/faa_delays/config_flow.py b/homeassistant/components/faa_delays/config_flow.py index 91a1cbfdb48..5a42c9f7602 100644 --- a/homeassistant/components/faa_delays/config_flow.py +++ b/homeassistant/components/faa_delays/config_flow.py @@ -1,4 +1,5 @@ """Config flow for FAA Delays integration.""" + import logging from typing import Any diff --git a/homeassistant/components/faa_delays/coordinator.py b/homeassistant/components/faa_delays/coordinator.py index 2f110cf7730..9de10b2ebbb 100644 --- a/homeassistant/components/faa_delays/coordinator.py +++ b/homeassistant/components/faa_delays/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for faa_delays integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/fastdotcom/const.py b/homeassistant/components/fastdotcom/const.py index 340be6f50ae..8cae797e052 100644 --- a/homeassistant/components/fastdotcom/const.py +++ b/homeassistant/components/fastdotcom/const.py @@ -1,4 +1,5 @@ """Constants for the Fast.com integration.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/fireservicerota/sensor.py b/homeassistant/components/fireservicerota/sensor.py index 797e39e99cd..864838ddaff 100644 --- a/homeassistant/components/fireservicerota/sensor.py +++ b/homeassistant/components/fireservicerota/sensor.py @@ -1,4 +1,5 @@ """Sensor platform for FireServiceRota integration.""" + import logging from typing import Any diff --git a/homeassistant/components/fireservicerota/switch.py b/homeassistant/components/fireservicerota/switch.py index 7409b2e53b4..04e1e4ef5eb 100644 --- a/homeassistant/components/fireservicerota/switch.py +++ b/homeassistant/components/fireservicerota/switch.py @@ -1,4 +1,5 @@ """Switch platform for FireServiceRota integration.""" + import logging from typing import Any diff --git a/homeassistant/components/firmata/__init__.py b/homeassistant/components/firmata/__init__.py index 78b5592a54e..283fd585d35 100644 --- a/homeassistant/components/firmata/__init__.py +++ b/homeassistant/components/firmata/__init__.py @@ -1,4 +1,5 @@ """Support for Arduino-compatible Microcontrollers through Firmata.""" + import asyncio from copy import copy import logging diff --git a/homeassistant/components/firmata/binary_sensor.py b/homeassistant/components/firmata/binary_sensor.py index 0cb38d1df8b..c25a61ddac7 100644 --- a/homeassistant/components/firmata/binary_sensor.py +++ b/homeassistant/components/firmata/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Firmata binary sensor input.""" + import logging from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/firmata/sensor.py b/homeassistant/components/firmata/sensor.py index 3497aec9e88..32559b9197d 100644 --- a/homeassistant/components/firmata/sensor.py +++ b/homeassistant/components/firmata/sensor.py @@ -1,4 +1,5 @@ """Support for Firmata sensor input.""" + import logging from homeassistant.components.sensor import SensorEntity diff --git a/homeassistant/components/firmata/switch.py b/homeassistant/components/firmata/switch.py index f52300b6db3..4203b221d4f 100644 --- a/homeassistant/components/firmata/switch.py +++ b/homeassistant/components/firmata/switch.py @@ -1,4 +1,5 @@ """Support for Firmata switch output.""" + import logging from typing import Any diff --git a/homeassistant/components/flexit_bacnet/climate.py b/homeassistant/components/flexit_bacnet/climate.py index 84785720fb2..0526a0d6bd3 100644 --- a/homeassistant/components/flexit_bacnet/climate.py +++ b/homeassistant/components/flexit_bacnet/climate.py @@ -1,4 +1,5 @@ """The Flexit Nordic (BACnet) integration.""" + import asyncio.exceptions from typing import Any diff --git a/homeassistant/components/flexit_bacnet/coordinator.py b/homeassistant/components/flexit_bacnet/coordinator.py index 556264e1268..79f3b6a05ad 100644 --- a/homeassistant/components/flexit_bacnet/coordinator.py +++ b/homeassistant/components/flexit_bacnet/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for Flexit Nordic (BACnet) integration..""" + import asyncio.exceptions from datetime import timedelta import logging diff --git a/homeassistant/components/flexit_bacnet/number.py b/homeassistant/components/flexit_bacnet/number.py index 2731d5e8b09..6e6e2eea980 100644 --- a/homeassistant/components/flexit_bacnet/number.py +++ b/homeassistant/components/flexit_bacnet/number.py @@ -1,4 +1,5 @@ """The Flexit Nordic (BACnet) integration.""" + import asyncio.exceptions from collections.abc import Awaitable, Callable from dataclasses import dataclass diff --git a/homeassistant/components/flexit_bacnet/switch.py b/homeassistant/components/flexit_bacnet/switch.py index 0a7785eaa38..c58e35cda75 100644 --- a/homeassistant/components/flexit_bacnet/switch.py +++ b/homeassistant/components/flexit_bacnet/switch.py @@ -1,4 +1,5 @@ """The Flexit Nordic (BACnet) integration.""" + import asyncio.exceptions from collections.abc import Awaitable, Callable from dataclasses import dataclass diff --git a/homeassistant/components/flick_electric/config_flow.py b/homeassistant/components/flick_electric/config_flow.py index 372429bf7b5..efe8a5725f7 100644 --- a/homeassistant/components/flick_electric/config_flow.py +++ b/homeassistant/components/flick_electric/config_flow.py @@ -1,4 +1,5 @@ """Config Flow for Flick Electric integration.""" + import asyncio import logging diff --git a/homeassistant/components/flick_electric/sensor.py b/homeassistant/components/flick_electric/sensor.py index 8280e7b2fe0..347109c66c0 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -1,4 +1,5 @@ """Support for Flick Electric Pricing data.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/flo/__init__.py b/homeassistant/components/flo/__init__.py index b30e31de361..0d65e12a2a3 100644 --- a/homeassistant/components/flo/__init__.py +++ b/homeassistant/components/flo/__init__.py @@ -1,4 +1,5 @@ """The flo integration.""" + import asyncio import logging diff --git a/homeassistant/components/flo/const.py b/homeassistant/components/flo/const.py index 907561b5b9c..9eb00ebfa62 100644 --- a/homeassistant/components/flo/const.py +++ b/homeassistant/components/flo/const.py @@ -1,4 +1,5 @@ """Constants for the flo integration.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/foscam/const.py b/homeassistant/components/foscam/const.py index d5ac0f5c567..38088cf3f6f 100644 --- a/homeassistant/components/foscam/const.py +++ b/homeassistant/components/foscam/const.py @@ -1,4 +1,5 @@ """Constants for Foscam component.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index de46087c60c..b790556b8e3 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Freebox integration.""" + import logging from typing import Any diff --git a/homeassistant/components/freedns/__init__.py b/homeassistant/components/freedns/__init__.py index feb1fb9fed9..5338c0d0700 100644 --- a/homeassistant/components/freedns/__init__.py +++ b/homeassistant/components/freedns/__init__.py @@ -1,4 +1,5 @@ """Integrate with FreeDNS Dynamic DNS service at freedns.afraid.org.""" + import asyncio from datetime import datetime, timedelta import logging diff --git a/homeassistant/components/freedompro/cover.py b/homeassistant/components/freedompro/cover.py index b57acfacb4f..06ad5c80b6a 100644 --- a/homeassistant/components/freedompro/cover.py +++ b/homeassistant/components/freedompro/cover.py @@ -1,4 +1,5 @@ """Support for Freedompro cover.""" + import json from typing import Any diff --git a/homeassistant/components/freedompro/lock.py b/homeassistant/components/freedompro/lock.py index b1544d9b20d..c429ef6aa99 100644 --- a/homeassistant/components/freedompro/lock.py +++ b/homeassistant/components/freedompro/lock.py @@ -1,4 +1,5 @@ """Support for Freedompro lock.""" + import json from typing import Any diff --git a/homeassistant/components/freedompro/switch.py b/homeassistant/components/freedompro/switch.py index 4de27c270b4..91e67506173 100644 --- a/homeassistant/components/freedompro/switch.py +++ b/homeassistant/components/freedompro/switch.py @@ -1,4 +1,5 @@ """Support for Freedompro switch.""" + import json from typing import Any diff --git a/homeassistant/components/fritz/__init__.py b/homeassistant/components/fritz/__init__.py index 137aaa5ba2e..ba9e2191901 100644 --- a/homeassistant/components/fritz/__init__.py +++ b/homeassistant/components/fritz/__init__.py @@ -1,4 +1,5 @@ """Support for AVM Fritz!Box functions.""" + import logging from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/fritzbox_callmonitor/__init__.py b/homeassistant/components/fritzbox_callmonitor/__init__.py index 534f3b0c8fc..bd6b6ab125f 100644 --- a/homeassistant/components/fritzbox_callmonitor/__init__.py +++ b/homeassistant/components/fritzbox_callmonitor/__init__.py @@ -1,4 +1,5 @@ """The fritzbox_callmonitor integration.""" + import logging from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError diff --git a/homeassistant/components/frontier_silicon/browse_media.py b/homeassistant/components/frontier_silicon/browse_media.py index cc4452b2d6b..da5169b8e7c 100644 --- a/homeassistant/components/frontier_silicon/browse_media.py +++ b/homeassistant/components/frontier_silicon/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + import logging from afsapi import AFSAPI, FSApiException, OutOfRangeException, Preset diff --git a/homeassistant/components/fully_kiosk/coordinator.py b/homeassistant/components/fully_kiosk/coordinator.py index 203251351ae..405f0746437 100644 --- a/homeassistant/components/fully_kiosk/coordinator.py +++ b/homeassistant/components/fully_kiosk/coordinator.py @@ -1,4 +1,5 @@ """Provides the Fully Kiosk Browser DataUpdateCoordinator.""" + import asyncio from typing import Any, cast diff --git a/homeassistant/components/garages_amsterdam/__init__.py b/homeassistant/components/garages_amsterdam/__init__.py index 82e0c832e7b..81ec72d9fbf 100644 --- a/homeassistant/components/garages_amsterdam/__init__.py +++ b/homeassistant/components/garages_amsterdam/__init__.py @@ -1,4 +1,5 @@ """The Garages Amsterdam integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/gc100/__init__.py b/homeassistant/components/gc100/__init__.py index fff68f48fc0..57c8e92499f 100644 --- a/homeassistant/components/gc100/__init__.py +++ b/homeassistant/components/gc100/__init__.py @@ -1,4 +1,5 @@ """Support for controlling Global Cache gc100.""" + import gc100 import voluptuous as vol diff --git a/homeassistant/components/gdacs/config_flow.py b/homeassistant/components/gdacs/config_flow.py index 535b31ff77a..eefc9b93438 100644 --- a/homeassistant/components/gdacs/config_flow.py +++ b/homeassistant/components/gdacs/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the GDACS integration.""" + import logging from typing import Any diff --git a/homeassistant/components/generic_hygrostat/__init__.py b/homeassistant/components/generic_hygrostat/__init__.py index 585d0aa1fe3..3109e9bb563 100644 --- a/homeassistant/components/generic_hygrostat/__init__.py +++ b/homeassistant/components/generic_hygrostat/__init__.py @@ -1,4 +1,5 @@ """The generic_hygrostat component.""" + import voluptuous as vol from homeassistant.components.humidifier import HumidifierDeviceClass diff --git a/homeassistant/components/geonetnz_quakes/config_flow.py b/homeassistant/components/geonetnz_quakes/config_flow.py index 56cd45a13aa..4367f820bd3 100644 --- a/homeassistant/components/geonetnz_quakes/config_flow.py +++ b/homeassistant/components/geonetnz_quakes/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the GeoNet NZ Quakes integration.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/geonetnz_volcano/config_flow.py b/homeassistant/components/geonetnz_volcano/config_flow.py index d42f285481a..461da61ae1a 100644 --- a/homeassistant/components/geonetnz_volcano/config_flow.py +++ b/homeassistant/components/geonetnz_volcano/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the GeoNet NZ Volcano integration.""" + import voluptuous as vol from homeassistant.config_entries import ConfigFlow diff --git a/homeassistant/components/glances/__init__.py b/homeassistant/components/glances/__init__.py index 1c03f8c1dbf..b6c4f477b46 100644 --- a/homeassistant/components/glances/__init__.py +++ b/homeassistant/components/glances/__init__.py @@ -1,4 +1,5 @@ """The Glances component.""" + import logging from typing import Any diff --git a/homeassistant/components/glances/coordinator.py b/homeassistant/components/glances/coordinator.py index 9fa9346b95f..9a1b281eec2 100644 --- a/homeassistant/components/glances/coordinator.py +++ b/homeassistant/components/glances/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for Glances integration.""" + import logging from typing import Any diff --git a/homeassistant/components/goalzero/const.py b/homeassistant/components/goalzero/const.py index 280a70abbf1..0096cffc2aa 100644 --- a/homeassistant/components/goalzero/const.py +++ b/homeassistant/components/goalzero/const.py @@ -1,4 +1,5 @@ """Constants for the Goal Zero Yeti integration.""" + import logging from typing import Final diff --git a/homeassistant/components/goodwe/select.py b/homeassistant/components/goodwe/select.py index 6d033eab242..f42f50c93fc 100644 --- a/homeassistant/components/goodwe/select.py +++ b/homeassistant/components/goodwe/select.py @@ -1,4 +1,5 @@ """GoodWe PV inverter selection settings entities.""" + import logging from goodwe import Inverter, InverterError, OperationMode diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 8172d0ca92d..df55fc0d7c8 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -1,4 +1,5 @@ """Support for Google Assistant Smart Home API.""" + import asyncio from collections.abc import Callable, Coroutine from itertools import product diff --git a/homeassistant/components/google_cloud/tts.py b/homeassistant/components/google_cloud/tts.py index 8f30448ad61..6f4751850aa 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -1,4 +1,5 @@ """Support for the Google Cloud TTS service.""" + import asyncio import logging import os diff --git a/homeassistant/components/google_domains/__init__.py b/homeassistant/components/google_domains/__init__.py index 1d420cb1497..d6537c5e135 100644 --- a/homeassistant/components/google_domains/__init__.py +++ b/homeassistant/components/google_domains/__init__.py @@ -1,4 +1,5 @@ """Support for Google Domains.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/google_tasks/config_flow.py b/homeassistant/components/google_tasks/config_flow.py index 6fd71cd3d06..14cd89fcec7 100644 --- a/homeassistant/components/google_tasks/config_flow.py +++ b/homeassistant/components/google_tasks/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Google Tasks.""" + import logging from typing import Any diff --git a/homeassistant/components/google_travel_time/helpers.py b/homeassistant/components/google_travel_time/helpers.py index 9c25d02b8a5..d36adb21c18 100644 --- a/homeassistant/components/google_travel_time/helpers.py +++ b/homeassistant/components/google_travel_time/helpers.py @@ -1,4 +1,5 @@ """Helpers for Google Time Travel integration.""" + import logging from googlemaps import Client diff --git a/homeassistant/components/growatt_server/config_flow.py b/homeassistant/components/growatt_server/config_flow.py index e04a623c72d..95002a70a95 100644 --- a/homeassistant/components/growatt_server/config_flow.py +++ b/homeassistant/components/growatt_server/config_flow.py @@ -1,4 +1,5 @@ """Config flow for growatt server integration.""" + import growattServer import voluptuous as vol diff --git a/homeassistant/components/guardian/const.py b/homeassistant/components/guardian/const.py index c7d025ba712..593cba65264 100644 --- a/homeassistant/components/guardian/const.py +++ b/homeassistant/components/guardian/const.py @@ -1,4 +1,5 @@ """Constants for the Elexa Guardian integration.""" + import logging DOMAIN = "guardian" diff --git a/homeassistant/components/habitica/__init__.py b/homeassistant/components/habitica/__init__.py index 25738893689..f05bc9c1713 100644 --- a/homeassistant/components/habitica/__init__.py +++ b/homeassistant/components/habitica/__init__.py @@ -1,4 +1,5 @@ """The habitica integration.""" + import logging from habitipy.aio import HabitipyAsync diff --git a/homeassistant/components/harmony/__init__.py b/homeassistant/components/harmony/__init__.py index 327dbad343b..a1c513a4654 100644 --- a/homeassistant/components/harmony/__init__.py +++ b/homeassistant/components/harmony/__init__.py @@ -1,4 +1,5 @@ """The Logitech Harmony Hub integration.""" + import logging from homeassistant.components.remote import ATTR_ACTIVITY, ATTR_DELAY_SECS diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index c5bba39eb95..f253e19efbd 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -1,4 +1,5 @@ """Support for Harmony Hub activities.""" + import logging from typing import Any, cast diff --git a/homeassistant/components/harmony/util.py b/homeassistant/components/harmony/util.py index 0bfee32b414..13ca67c9e76 100644 --- a/homeassistant/components/harmony/util.py +++ b/homeassistant/components/harmony/util.py @@ -1,4 +1,5 @@ """The Logitech Harmony Hub integration utils.""" + import aioharmony.exceptions as harmony_exceptions from aioharmony.harmonyapi import HarmonyAPI diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index cf59f8de7f7..94e83f12107 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -1,4 +1,5 @@ """Websocekt API handlers for the hassio integration.""" + import logging from numbers import Number import re diff --git a/homeassistant/components/heos/services.py b/homeassistant/components/heos/services.py index 9331f786f9d..2ef80b6efd9 100644 --- a/homeassistant/components/heos/services.py +++ b/homeassistant/components/heos/services.py @@ -1,4 +1,5 @@ """Services for the HEOS integration.""" + import functools import logging diff --git a/homeassistant/components/hisense_aehw4a1/__init__.py b/homeassistant/components/hisense_aehw4a1/__init__.py index cc599aa31fc..d20f6d13217 100644 --- a/homeassistant/components/hisense_aehw4a1/__init__.py +++ b/homeassistant/components/hisense_aehw4a1/__init__.py @@ -1,4 +1,5 @@ """The Hisense AEH-W4A1 integration.""" + import ipaddress import logging diff --git a/homeassistant/components/hlk_sw16/__init__.py b/homeassistant/components/hlk_sw16/__init__.py index 9be0b5203fd..3e6a9f6b0d6 100644 --- a/homeassistant/components/hlk_sw16/__init__.py +++ b/homeassistant/components/hlk_sw16/__init__.py @@ -1,4 +1,5 @@ """Support for HLK-SW16 relay switches.""" + import logging from hlk_sw16 import create_hlk_sw16_connection diff --git a/homeassistant/components/hlk_sw16/config_flow.py b/homeassistant/components/hlk_sw16/config_flow.py index dc96a560298..b315d0daa78 100644 --- a/homeassistant/components/hlk_sw16/config_flow.py +++ b/homeassistant/components/hlk_sw16/config_flow.py @@ -1,4 +1,5 @@ """Config flow for HLK-SW16.""" + import asyncio from hlk_sw16 import create_hlk_sw16_connection diff --git a/homeassistant/components/home_connect/binary_sensor.py b/homeassistant/components/home_connect/binary_sensor.py index 93b90cbfbd3..84b02be1cc4 100644 --- a/homeassistant/components/home_connect/binary_sensor.py +++ b/homeassistant/components/home_connect/binary_sensor.py @@ -1,4 +1,5 @@ """Provides a binary sensor for Home Connect.""" + import logging from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/home_connect/config_flow.py b/homeassistant/components/home_connect/config_flow.py index 6239f1e3f60..f6616bf98ca 100644 --- a/homeassistant/components/home_connect/config_flow.py +++ b/homeassistant/components/home_connect/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Home Connect.""" + import logging from homeassistant.helpers import config_entry_oauth2_flow diff --git a/homeassistant/components/home_connect/light.py b/homeassistant/components/home_connect/light.py index 7e65fed034d..3b062fac66c 100644 --- a/homeassistant/components/home_connect/light.py +++ b/homeassistant/components/home_connect/light.py @@ -1,4 +1,5 @@ """Provides a light for Home Connect.""" + import logging from math import ceil from typing import Any diff --git a/homeassistant/components/home_connect/switch.py b/homeassistant/components/home_connect/switch.py index dbcbfde9dc2..6dc308ac022 100644 --- a/homeassistant/components/home_connect/switch.py +++ b/homeassistant/components/home_connect/switch.py @@ -1,4 +1,5 @@ """Provides a switch for Home Connect.""" + import logging from typing import Any diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 02a86150ff0..b14194c425f 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -1,4 +1,5 @@ """Integration providing core pieces of infrastructure.""" + import asyncio from collections.abc import Callable, Coroutine import itertools as it diff --git a/homeassistant/components/homeassistant/trigger.py b/homeassistant/components/homeassistant/trigger.py index 401da9d01e7..3ee73255a4d 100644 --- a/homeassistant/components/homeassistant/trigger.py +++ b/homeassistant/components/homeassistant/trigger.py @@ -1,4 +1,5 @@ """Home Assistant trigger dispatcher.""" + import importlib from homeassistant.const import CONF_PLATFORM diff --git a/homeassistant/components/homeassistant/triggers/homeassistant.py b/homeassistant/components/homeassistant/triggers/homeassistant.py index 84aafb44808..51e3a947a29 100644 --- a/homeassistant/components/homeassistant/triggers/homeassistant.py +++ b/homeassistant/components/homeassistant/triggers/homeassistant.py @@ -1,4 +1,5 @@ """Offer Home Assistant core automation rules.""" + import voluptuous as vol from homeassistant.const import CONF_EVENT, CONF_PLATFORM diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index 078ab8818ac..28b34503355 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -1,4 +1,5 @@ """Class to hold all camera accessories.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 47660e486f2..5e60875e485 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -1,4 +1,5 @@ """Class to hold all cover accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index d371998aaf8..0dee5fa2b71 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -1,4 +1,5 @@ """Class to hold all fan accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homekit/type_humidifiers.py b/homeassistant/components/homekit/type_humidifiers.py index 0b2c965c7f3..a9782aedaf0 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -1,4 +1,5 @@ """Class to hold all thermostat accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index 18dfe48b2bd..e5b0ad22396 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -1,4 +1,5 @@ """Class to hold all lock accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 23fbd5b454d..4cdb471b4ff 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -1,4 +1,5 @@ """Class to hold all media player accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index de2c463bfb2..27c479de6ba 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -1,4 +1,5 @@ """Class to hold all alarm control panel accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index c638da55764..ba0f9790efb 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -1,4 +1,5 @@ """Class to hold all thermostat accessories.""" + import logging from typing import Any diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py index 2f7d8d86012..2b2ddb64700 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -1,4 +1,5 @@ """Support for HomematicIP Cloud devices.""" + import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/homematicip_cloud/const.py b/homeassistant/components/homematicip_cloud/const.py index 4ea1a2fc7e0..bba67e10d4c 100644 --- a/homeassistant/components/homematicip_cloud/const.py +++ b/homeassistant/components/homematicip_cloud/const.py @@ -1,4 +1,5 @@ """Constants for the HomematicIP Cloud component.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/honeywell/const.py b/homeassistant/components/honeywell/const.py index 28868812e24..9f0034a0623 100644 --- a/homeassistant/components/honeywell/const.py +++ b/homeassistant/components/honeywell/const.py @@ -1,4 +1,5 @@ """Support for Honeywell (US) Total Connect Comfort climate systems.""" + import logging DOMAIN = "honeywell" diff --git a/homeassistant/components/hue/v1/hue_event.py b/homeassistant/components/hue/v1/hue_event.py index b3faf88c2d9..a7fe447f3f4 100644 --- a/homeassistant/components/hue/v1/hue_event.py +++ b/homeassistant/components/hue/v1/hue_event.py @@ -1,4 +1,5 @@ """Representation of a Hue remote firing events for button presses.""" + import logging from aiohue.v1.sensors import ( diff --git a/homeassistant/components/hue/v2/hue_event.py b/homeassistant/components/hue/v2/hue_event.py index b8521a80af7..6aee6c67bf3 100644 --- a/homeassistant/components/hue/v2/hue_event.py +++ b/homeassistant/components/hue/v2/hue_event.py @@ -1,4 +1,5 @@ """Handle forward of events transmitted by Hue devices to HASS.""" + import logging from typing import TYPE_CHECKING diff --git a/homeassistant/components/huisbaasje/__init__.py b/homeassistant/components/huisbaasje/__init__.py index 9ea4b547596..b02d0bf577c 100644 --- a/homeassistant/components/huisbaasje/__init__.py +++ b/homeassistant/components/huisbaasje/__init__.py @@ -1,4 +1,5 @@ """The Huisbaasje integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/huisbaasje/config_flow.py b/homeassistant/components/huisbaasje/config_flow.py index 4c94f25914a..3697c1fcb86 100644 --- a/homeassistant/components/huisbaasje/config_flow.py +++ b/homeassistant/components/huisbaasje/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Huisbaasje integration.""" + import logging from energyflip import EnergyFlip, EnergyFlipConnectionException, EnergyFlipException diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 4156dcdafae..35b28e312bc 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -1,4 +1,5 @@ """The Hunter Douglas PowerView integration.""" + import asyncio import logging diff --git a/homeassistant/components/husqvarna_automower/coordinator.py b/homeassistant/components/husqvarna_automower/coordinator.py index 2840823415a..2188725ed76 100644 --- a/homeassistant/components/husqvarna_automower/coordinator.py +++ b/homeassistant/components/husqvarna_automower/coordinator.py @@ -1,4 +1,5 @@ """Data UpdateCoordinator for the Husqvarna Automower integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/husqvarna_automower/lawn_mower.py b/homeassistant/components/husqvarna_automower/lawn_mower.py index abf27af02f0..e9ed9187530 100644 --- a/homeassistant/components/husqvarna_automower/lawn_mower.py +++ b/homeassistant/components/husqvarna_automower/lawn_mower.py @@ -1,4 +1,5 @@ """Husqvarna Automower lawn mower entity.""" + import logging from aioautomower.exceptions import ApiException diff --git a/homeassistant/components/husqvarna_automower/switch.py b/homeassistant/components/husqvarna_automower/switch.py index 9ba760a90e9..b178fc05c50 100644 --- a/homeassistant/components/husqvarna_automower/switch.py +++ b/homeassistant/components/husqvarna_automower/switch.py @@ -1,4 +1,5 @@ """Creates a switch entity for the mower.""" + import logging from typing import Any diff --git a/homeassistant/components/ialarm/config_flow.py b/homeassistant/components/ialarm/config_flow.py index 0681160c741..6aef66922b4 100644 --- a/homeassistant/components/ialarm/config_flow.py +++ b/homeassistant/components/ialarm/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Antifurto365 iAlarm integration.""" + import logging from pyialarm import IAlarm diff --git a/homeassistant/components/ihc/__init__.py b/homeassistant/components/ihc/__init__.py index 34b65c5b791..d443ac335db 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -1,4 +1,5 @@ """Support for IHC devices.""" + import logging from ihcsdk.ihccontroller import IHCController diff --git a/homeassistant/components/ihc/auto_setup.py b/homeassistant/components/ihc/auto_setup.py index d23c3e65a41..2d6e59131cd 100644 --- a/homeassistant/components/ihc/auto_setup.py +++ b/homeassistant/components/ihc/auto_setup.py @@ -1,4 +1,5 @@ """Handle auto setup of IHC products from the ihc project file.""" + import logging import os.path diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py index 30c84da40f8..07ff71b812a 100644 --- a/homeassistant/components/ihc/ihcdevice.py +++ b/homeassistant/components/ihc/ihcdevice.py @@ -1,4 +1,5 @@ """Implementation of a base class for all IHC devices.""" + import logging from ihcsdk.ihccontroller import IHCController diff --git a/homeassistant/components/ihc/manual_setup.py b/homeassistant/components/ihc/manual_setup.py index b4775f9193b..c453494e263 100644 --- a/homeassistant/components/ihc/manual_setup.py +++ b/homeassistant/components/ihc/manual_setup.py @@ -1,4 +1,5 @@ """Handle manual setup of ihc resources as entities in Home Assistant.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/ihc/service_functions.py b/homeassistant/components/ihc/service_functions.py index 3d7008ee38b..cfd91f0960c 100644 --- a/homeassistant/components/ihc/service_functions.py +++ b/homeassistant/components/ihc/service_functions.py @@ -1,4 +1,5 @@ """Support for IHC devices.""" + import voluptuous as vol from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index 69040199589..b7e6e6055e1 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -1,4 +1,5 @@ """Constants used by insteon component.""" + import re from pyinsteon.groups import ( diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index 0756e603579..60c4593f3c5 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -1,4 +1,5 @@ """Support for Insteon covers via PowerLinc Modem.""" + import math from typing import Any diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index d1762fa8d35..f81298dfe48 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -1,4 +1,5 @@ """Insteon base entity.""" + import functools import logging diff --git a/homeassistant/components/ios/__init__.py b/homeassistant/components/ios/__init__.py index 5dcec01e138..4b2b92a482d 100644 --- a/homeassistant/components/ios/__init__.py +++ b/homeassistant/components/ios/__init__.py @@ -1,4 +1,5 @@ """Native Home Assistant iOS app component.""" + import datetime from http import HTTPStatus from typing import Any diff --git a/homeassistant/components/ipma/__init__.py b/homeassistant/components/ipma/__init__.py index 7668802c9e0..68289d13289 100644 --- a/homeassistant/components/ipma/__init__.py +++ b/homeassistant/components/ipma/__init__.py @@ -1,4 +1,5 @@ """Component for the Portuguese weather service - IPMA.""" + import asyncio import logging diff --git a/homeassistant/components/ipma/config_flow.py b/homeassistant/components/ipma/config_flow.py index cf468c9013a..36e70243c93 100644 --- a/homeassistant/components/ipma/config_flow.py +++ b/homeassistant/components/ipma/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure IPMA component.""" + import logging from typing import Any diff --git a/homeassistant/components/iqvia/const.py b/homeassistant/components/iqvia/const.py index 3ed961f2e74..4c4ad5c06ba 100644 --- a/homeassistant/components/iqvia/const.py +++ b/homeassistant/components/iqvia/const.py @@ -1,4 +1,5 @@ """Define IQVIA constants.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index 70f0f49d7a1..fae05421f63 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -1,4 +1,5 @@ """Constants for the ISY Platform.""" + import logging from pyisy.constants import PROP_ON_LEVEL, PROP_RAMP_RATE diff --git a/homeassistant/components/izone/__init__.py b/homeassistant/components/izone/__init__.py index fd8d27ac422..c00f2d1f83f 100644 --- a/homeassistant/components/izone/__init__.py +++ b/homeassistant/components/izone/__init__.py @@ -1,4 +1,5 @@ """Platform for the iZone AC.""" + import voluptuous as vol from homeassistant import config_entries diff --git a/homeassistant/components/izone/discovery.py b/homeassistant/components/izone/discovery.py index a170ed30a74..327c5c0dc85 100644 --- a/homeassistant/components/izone/discovery.py +++ b/homeassistant/components/izone/discovery.py @@ -1,4 +1,5 @@ """Internal discovery service for iZone AC.""" + import logging import pizone diff --git a/homeassistant/components/jellyfin/const.py b/homeassistant/components/jellyfin/const.py index fb8b4f15d82..764356e2ea6 100644 --- a/homeassistant/components/jellyfin/const.py +++ b/homeassistant/components/jellyfin/const.py @@ -1,4 +1,5 @@ """Constants for the Jellyfin integration.""" + import logging from typing import Final diff --git a/homeassistant/components/joaoapps_join/__init__.py b/homeassistant/components/joaoapps_join/__init__.py index b53855c79fb..f537866054f 100644 --- a/homeassistant/components/joaoapps_join/__init__.py +++ b/homeassistant/components/joaoapps_join/__init__.py @@ -1,4 +1,5 @@ """Support for Joaoapps Join services.""" + import logging from pyjoin import ( diff --git a/homeassistant/components/juicenet/config_flow.py b/homeassistant/components/juicenet/config_flow.py index 743e4098a9a..237c89922b2 100644 --- a/homeassistant/components/juicenet/config_flow.py +++ b/homeassistant/components/juicenet/config_flow.py @@ -1,4 +1,5 @@ """Config flow for JuiceNet integration.""" + import logging import aiohttp diff --git a/homeassistant/components/kaiterra/__init__.py b/homeassistant/components/kaiterra/__init__.py index 6cf866d2d63..67fcfbe482c 100644 --- a/homeassistant/components/kaiterra/__init__.py +++ b/homeassistant/components/kaiterra/__init__.py @@ -1,4 +1,5 @@ """Support for Kaiterra devices.""" + import voluptuous as vol from homeassistant.const import ( diff --git a/homeassistant/components/kaiterra/api_data.py b/homeassistant/components/kaiterra/api_data.py index 6ee73b8ace7..945cc6e9b86 100644 --- a/homeassistant/components/kaiterra/api_data.py +++ b/homeassistant/components/kaiterra/api_data.py @@ -1,4 +1,5 @@ """Data for all Kaiterra devices.""" + import asyncio from logging import getLogger diff --git a/homeassistant/components/keba/__init__.py b/homeassistant/components/keba/__init__.py index 1c99a6500c5..34eb7c99166 100644 --- a/homeassistant/components/keba/__init__.py +++ b/homeassistant/components/keba/__init__.py @@ -1,4 +1,5 @@ """Support for KEBA charging stations.""" + import asyncio import logging diff --git a/homeassistant/components/kira/__init__.py b/homeassistant/components/kira/__init__.py index c54b8a91208..b0305bc0643 100644 --- a/homeassistant/components/kira/__init__.py +++ b/homeassistant/components/kira/__init__.py @@ -1,4 +1,5 @@ """KIRA interface to receive UDP packets from an IR-IP bridge.""" + import logging import os diff --git a/homeassistant/components/kmtronic/__init__.py b/homeassistant/components/kmtronic/__init__.py index 638884dff26..5f93de3c60e 100644 --- a/homeassistant/components/kmtronic/__init__.py +++ b/homeassistant/components/kmtronic/__init__.py @@ -1,4 +1,5 @@ """The kmtronic integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/knx/helpers/keyring.py b/homeassistant/components/knx/helpers/keyring.py index 5d1dfea6383..9e9cfda2b80 100644 --- a/homeassistant/components/knx/helpers/keyring.py +++ b/homeassistant/components/knx/helpers/keyring.py @@ -1,4 +1,5 @@ """KNX Keyring handler.""" + import logging from pathlib import Path import shutil diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index 5df714e27da..60e99d98cb1 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + import asyncio import contextlib import logging diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index f23f9110a74..25c731ac7f4 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -1,4 +1,5 @@ """Support for Konnected devices.""" + import copy import hmac from http import HTTPStatus diff --git a/homeassistant/components/konnected/handlers.py b/homeassistant/components/konnected/handlers.py index af784750627..55fdc57bc46 100644 --- a/homeassistant/components/konnected/handlers.py +++ b/homeassistant/components/konnected/handlers.py @@ -1,4 +1,5 @@ """Handle Konnected messages.""" + import logging from homeassistant.components.sensor import SensorDeviceClass diff --git a/homeassistant/components/konnected/panel.py b/homeassistant/components/konnected/panel.py index 61c77f5a7de..605b27f7547 100644 --- a/homeassistant/components/konnected/panel.py +++ b/homeassistant/components/konnected/panel.py @@ -1,4 +1,5 @@ """Support for Konnected devices.""" + import asyncio import logging diff --git a/homeassistant/components/konnected/switch.py b/homeassistant/components/konnected/switch.py index 18132a913ad..424a2d9164d 100644 --- a/homeassistant/components/konnected/switch.py +++ b/homeassistant/components/konnected/switch.py @@ -1,4 +1,5 @@ """Support for wired switches attached to a Konnected device.""" + import logging from typing import Any diff --git a/homeassistant/components/kostal_plenticore/__init__.py b/homeassistant/components/kostal_plenticore/__init__.py index b7e4c86f772..d3fb65ad77b 100644 --- a/homeassistant/components/kostal_plenticore/__init__.py +++ b/homeassistant/components/kostal_plenticore/__init__.py @@ -1,4 +1,5 @@ """The Kostal Plenticore Solar Inverter integration.""" + import logging from pykoplenti import ApiException diff --git a/homeassistant/components/kostal_plenticore/config_flow.py b/homeassistant/components/kostal_plenticore/config_flow.py index c88006e59b6..c1c8ac249e0 100644 --- a/homeassistant/components/kostal_plenticore/config_flow.py +++ b/homeassistant/components/kostal_plenticore/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Kostal Plenticore Solar Inverter integration.""" + import logging from aiohttp.client_exceptions import ClientError diff --git a/homeassistant/components/kulersky/config_flow.py b/homeassistant/components/kulersky/config_flow.py index 1f9c67b9aa1..fca214dd9a3 100644 --- a/homeassistant/components/kulersky/config_flow.py +++ b/homeassistant/components/kulersky/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Kuler Sky.""" + import logging import pykulersky diff --git a/homeassistant/components/lastfm/const.py b/homeassistant/components/lastfm/const.py index f895876c3c3..03cad974a57 100644 --- a/homeassistant/components/lastfm/const.py +++ b/homeassistant/components/lastfm/const.py @@ -1,4 +1,5 @@ """Constants for LastFM.""" + import logging from typing import Final diff --git a/homeassistant/components/laundrify/coordinator.py b/homeassistant/components/laundrify/coordinator.py index d67410c6aa3..c3fdc265174 100644 --- a/homeassistant/components/laundrify/coordinator.py +++ b/homeassistant/components/laundrify/coordinator.py @@ -1,4 +1,5 @@ """Custom DataUpdateCoordinator for the laundrify integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/lcn/schemas.py b/homeassistant/components/lcn/schemas.py index bd02c80da66..b907525747d 100644 --- a/homeassistant/components/lcn/schemas.py +++ b/homeassistant/components/lcn/schemas.py @@ -1,4 +1,5 @@ """Schema definitions for LCN configuration and websockets api.""" + import voluptuous as vol from homeassistant.components.climate import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP diff --git a/homeassistant/components/lg_soundbar/__init__.py b/homeassistant/components/lg_soundbar/__init__.py index 21d7fa4e773..cd1ce1c8139 100644 --- a/homeassistant/components/lg_soundbar/__init__.py +++ b/homeassistant/components/lg_soundbar/__init__.py @@ -1,4 +1,5 @@ """The lg_soundbar component.""" + import logging from homeassistant import config_entries, core diff --git a/homeassistant/components/lg_soundbar/config_flow.py b/homeassistant/components/lg_soundbar/config_flow.py index c28c3e458af..cc8e696b065 100644 --- a/homeassistant/components/lg_soundbar/config_flow.py +++ b/homeassistant/components/lg_soundbar/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the LG Soundbar integration.""" + import logging from queue import Empty, Full, Queue diff --git a/homeassistant/components/lidarr/const.py b/homeassistant/components/lidarr/const.py index ccf56db802e..1ef32a7e68f 100644 --- a/homeassistant/components/lidarr/const.py +++ b/homeassistant/components/lidarr/const.py @@ -1,4 +1,5 @@ """Constants for Lidarr.""" + import logging from typing import Final diff --git a/homeassistant/components/lightwave/__init__.py b/homeassistant/components/lightwave/__init__.py index 9feefd6e24d..6c462b040d4 100644 --- a/homeassistant/components/lightwave/__init__.py +++ b/homeassistant/components/lightwave/__init__.py @@ -1,4 +1,5 @@ """Support for device connected via Lightwave WiFi-link hub.""" + import logging from lightwave.lightwave import LWLink diff --git a/homeassistant/components/lirc/__init__.py b/homeassistant/components/lirc/__init__.py index 1b9688906ff..b847a160f51 100644 --- a/homeassistant/components/lirc/__init__.py +++ b/homeassistant/components/lirc/__init__.py @@ -1,4 +1,5 @@ """Support for LIRC devices.""" + import logging import threading import time diff --git a/homeassistant/components/litejet/__init__.py b/homeassistant/components/litejet/__init__.py index da24aee9ab8..e9d1cca74cb 100644 --- a/homeassistant/components/litejet/__init__.py +++ b/homeassistant/components/litejet/__init__.py @@ -1,4 +1,5 @@ """Support for the LiteJet lighting system.""" + import logging import pylitejet diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index ec8e4d697fe..ab1e0687710 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -1,4 +1,5 @@ """Support for LiteJet scenes.""" + import logging from typing import Any diff --git a/homeassistant/components/livisi/const.py b/homeassistant/components/livisi/const.py index 2769e6030ee..49e7ef8cfda 100644 --- a/homeassistant/components/livisi/const.py +++ b/homeassistant/components/livisi/const.py @@ -1,4 +1,5 @@ """Constants for the Livisi Smart Home integration.""" + import logging from typing import Final diff --git a/homeassistant/components/logentries/__init__.py b/homeassistant/components/logentries/__init__.py index fc7a30de762..6357510a07e 100644 --- a/homeassistant/components/logentries/__init__.py +++ b/homeassistant/components/logentries/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to Logentries webhook endpoint.""" + import json import logging diff --git a/homeassistant/components/logger/const.py b/homeassistant/components/logger/const.py index 4a7edfacead..483d1e8a097 100644 --- a/homeassistant/components/logger/const.py +++ b/homeassistant/components/logger/const.py @@ -1,4 +1,5 @@ """Constants for the Logger integration.""" + import logging DOMAIN = "logger" diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py index fa358d05fcd..0713bcc438e 100644 --- a/homeassistant/components/logi_circle/__init__.py +++ b/homeassistant/components/logi_circle/__init__.py @@ -1,4 +1,5 @@ """Support for Logi Circle devices.""" + import asyncio from aiohttp.client_exceptions import ClientResponseError diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index 86ef530d0d3..6c1a549aa04 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Logi Circle component.""" + import asyncio from collections import OrderedDict from http import HTTPStatus diff --git a/homeassistant/components/loqed/coordinator.py b/homeassistant/components/loqed/coordinator.py index d33cd8772b2..1447934103e 100644 --- a/homeassistant/components/loqed/coordinator.py +++ b/homeassistant/components/loqed/coordinator.py @@ -1,4 +1,5 @@ """Provides the coordinator for a LOQED lock.""" + import asyncio import logging from typing import TypedDict diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index daa44bf60be..73be119880f 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -1,4 +1,5 @@ """Support for the Lovelace UI.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/lovelace/system_health.py b/homeassistant/components/lovelace/system_health.py index 96ae2f47540..1e703768ae6 100644 --- a/homeassistant/components/lovelace/system_health.py +++ b/homeassistant/components/lovelace/system_health.py @@ -1,4 +1,5 @@ """Provide info to system health.""" + import asyncio from homeassistant.components import system_health diff --git a/homeassistant/components/lupusec/entity.py b/homeassistant/components/lupusec/entity.py index 6237e5dd16b..dc0dac89dc8 100644 --- a/homeassistant/components/lupusec/entity.py +++ b/homeassistant/components/lupusec/entity.py @@ -1,4 +1,5 @@ """Provides the Lupusec entity for Home Assistant.""" + import lupupy from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/mailgun/__init__.py b/homeassistant/components/mailgun/__init__.py index fc2cfcb4cef..25a4bf494ee 100644 --- a/homeassistant/components/mailgun/__init__.py +++ b/homeassistant/components/mailgun/__init__.py @@ -1,4 +1,5 @@ """Support for Mailgun.""" + import hashlib import hmac import json diff --git a/homeassistant/components/matter/const.py b/homeassistant/components/matter/const.py index e7f96bd2448..a0e160a6c01 100644 --- a/homeassistant/components/matter/const.py +++ b/homeassistant/components/matter/const.py @@ -1,4 +1,5 @@ """Constants for the Matter integration.""" + import logging ADDON_SLUG = "core_matter_server" diff --git a/homeassistant/components/maxcube/__init__.py b/homeassistant/components/maxcube/__init__.py index 41aed4be15c..82cdc56e5d9 100644 --- a/homeassistant/components/maxcube/__init__.py +++ b/homeassistant/components/maxcube/__init__.py @@ -1,4 +1,5 @@ """Support for the MAX! Cube LAN Gateway.""" + import logging from threading import Lock import time diff --git a/homeassistant/components/meater/__init__.py b/homeassistant/components/meater/__init__.py index ee2307fbc84..08ca32029cb 100644 --- a/homeassistant/components/meater/__init__.py +++ b/homeassistant/components/meater/__init__.py @@ -1,4 +1,5 @@ """The Meater Temperature Probe integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/media_source/const.py b/homeassistant/components/media_source/const.py index 73599efb6c3..809e0d8a1fd 100644 --- a/homeassistant/components/media_source/const.py +++ b/homeassistant/components/media_source/const.py @@ -1,4 +1,5 @@ """Constants for the media_source integration.""" + import re from homeassistant.components.media_player import MediaClass diff --git a/homeassistant/components/melissa/__init__.py b/homeassistant/components/melissa/__init__.py index 10027999dda..93e26231005 100644 --- a/homeassistant/components/melissa/__init__.py +++ b/homeassistant/components/melissa/__init__.py @@ -1,4 +1,5 @@ """Support for Melissa climate.""" + import melissa import voluptuous as vol diff --git a/homeassistant/components/met_eireann/weather.py b/homeassistant/components/met_eireann/weather.py index 84fc20cead7..7fa94920c74 100644 --- a/homeassistant/components/met_eireann/weather.py +++ b/homeassistant/components/met_eireann/weather.py @@ -1,4 +1,5 @@ """Support for Met Éireann weather service.""" + import logging from types import MappingProxyType from typing import Any, cast diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index 79e35b6219f..8454d7672a3 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -1,4 +1,5 @@ """Support for Meteo-France weather service.""" + import logging import time diff --git a/homeassistant/components/meteoclimatic/__init__.py b/homeassistant/components/meteoclimatic/__init__.py index 7510c4bec4c..1e729258218 100644 --- a/homeassistant/components/meteoclimatic/__init__.py +++ b/homeassistant/components/meteoclimatic/__init__.py @@ -1,4 +1,5 @@ """Support for Meteoclimatic weather data.""" + import logging from meteoclimatic import MeteoclimaticClient diff --git a/homeassistant/components/meteoclimatic/config_flow.py b/homeassistant/components/meteoclimatic/config_flow.py index 3067368319a..d772a6c9d62 100644 --- a/homeassistant/components/meteoclimatic/config_flow.py +++ b/homeassistant/components/meteoclimatic/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure the Meteoclimatic integration.""" + import logging from meteoclimatic import MeteoclimaticClient diff --git a/homeassistant/components/microsoft/tts.py b/homeassistant/components/microsoft/tts.py index 9bcd7f533f8..ea95771429f 100644 --- a/homeassistant/components/microsoft/tts.py +++ b/homeassistant/components/microsoft/tts.py @@ -1,4 +1,5 @@ """Support for the Microsoft Cognitive Services text-to-speech service.""" + import logging from pycsspeechtts import pycsspeechtts diff --git a/homeassistant/components/mobile_app/config_flow.py b/homeassistant/components/mobile_app/config_flow.py index 46b0c9ba09f..66035733c33 100644 --- a/homeassistant/components/mobile_app/config_flow.py +++ b/homeassistant/components/mobile_app/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Mobile App.""" + import uuid from homeassistant.components import person diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index efc105a80ea..25c35b3e87e 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -1,4 +1,5 @@ """Constants for mobile_app.""" + import voluptuous as vol from homeassistant.helpers import config_validation as cv diff --git a/homeassistant/components/mochad/__init__.py b/homeassistant/components/mochad/__init__.py index 114ade17740..36ebb74edc3 100644 --- a/homeassistant/components/mochad/__init__.py +++ b/homeassistant/components/mochad/__init__.py @@ -1,4 +1,5 @@ """Support for CM15A/CM19A X10 Controller using mochad daemon.""" + import logging import threading diff --git a/homeassistant/components/moehlenhoff_alpha2/climate.py b/homeassistant/components/moehlenhoff_alpha2/climate.py index 063628d6d32..147e4bda2fa 100644 --- a/homeassistant/components/moehlenhoff_alpha2/climate.py +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -1,4 +1,5 @@ """Support for Alpha2 room control unit via Alpha2 base.""" + import logging from typing import Any diff --git a/homeassistant/components/moehlenhoff_alpha2/config_flow.py b/homeassistant/components/moehlenhoff_alpha2/config_flow.py index 64cad25f461..a2a43c7bc5d 100644 --- a/homeassistant/components/moehlenhoff_alpha2/config_flow.py +++ b/homeassistant/components/moehlenhoff_alpha2/config_flow.py @@ -1,4 +1,5 @@ """Alpha2 config flow.""" + import logging from typing import Any diff --git a/homeassistant/components/monoprice/__init__.py b/homeassistant/components/monoprice/__init__.py index 1c9a2fa7868..57282fb6545 100644 --- a/homeassistant/components/monoprice/__init__.py +++ b/homeassistant/components/monoprice/__init__.py @@ -1,4 +1,5 @@ """The Monoprice 6-Zone Amplifier integration.""" + import logging from pymonoprice import get_monoprice diff --git a/homeassistant/components/monoprice/media_player.py b/homeassistant/components/monoprice/media_player.py index 92b98abf374..daf13b4d7b8 100644 --- a/homeassistant/components/monoprice/media_player.py +++ b/homeassistant/components/monoprice/media_player.py @@ -1,4 +1,5 @@ """Support for interfacing with Monoprice 6 zone home audio controller.""" + import logging from serial import SerialException diff --git a/homeassistant/components/motion_blinds/__init__.py b/homeassistant/components/motion_blinds/__init__.py index a4868c0a210..182ea310029 100644 --- a/homeassistant/components/motion_blinds/__init__.py +++ b/homeassistant/components/motion_blinds/__init__.py @@ -1,4 +1,5 @@ """The motion_blinds component.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/motion_blinds/coordinator.py b/homeassistant/components/motion_blinds/coordinator.py index f0cb67a6261..b2abd205ce5 100644 --- a/homeassistant/components/motion_blinds/coordinator.py +++ b/homeassistant/components/motion_blinds/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for Motionblinds integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/motion_blinds/gateway.py b/homeassistant/components/motion_blinds/gateway.py index ff37b640127..44f7caa74b2 100644 --- a/homeassistant/components/motion_blinds/gateway.py +++ b/homeassistant/components/motion_blinds/gateway.py @@ -1,4 +1,5 @@ """Code to handle a Motion Gateway.""" + import contextlib import logging import socket diff --git a/homeassistant/components/motionmount/binary_sensor.py b/homeassistant/components/motionmount/binary_sensor.py index 6bbed2e90c5..45b6e821440 100644 --- a/homeassistant/components/motionmount/binary_sensor.py +++ b/homeassistant/components/motionmount/binary_sensor.py @@ -1,4 +1,5 @@ """Support for MotionMount binary sensors.""" + import motionmount from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/motionmount/config_flow.py b/homeassistant/components/motionmount/config_flow.py index 3b0c8ffd830..19d3557d36b 100644 --- a/homeassistant/components/motionmount/config_flow.py +++ b/homeassistant/components/motionmount/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Vogel's MotionMount.""" + import logging import socket from typing import Any diff --git a/homeassistant/components/motionmount/number.py b/homeassistant/components/motionmount/number.py index 476e14c3a82..3217a4558e1 100644 --- a/homeassistant/components/motionmount/number.py +++ b/homeassistant/components/motionmount/number.py @@ -1,4 +1,5 @@ """Support for MotionMount numeric control.""" + import motionmount from homeassistant.components.number import NumberEntity diff --git a/homeassistant/components/motionmount/select.py b/homeassistant/components/motionmount/select.py index ef0b1e918ae..7d8a6ccdbc4 100644 --- a/homeassistant/components/motionmount/select.py +++ b/homeassistant/components/motionmount/select.py @@ -1,4 +1,5 @@ """Support for MotionMount numeric control.""" + import motionmount from homeassistant.components.select import SelectEntity diff --git a/homeassistant/components/motionmount/sensor.py b/homeassistant/components/motionmount/sensor.py index ed3cbd7d38b..933b637b0c2 100644 --- a/homeassistant/components/motionmount/sensor.py +++ b/homeassistant/components/motionmount/sensor.py @@ -1,4 +1,5 @@ """Support for MotionMount sensors.""" + import motionmount from homeassistant.components.sensor import SensorDeviceClass, SensorEntity diff --git a/homeassistant/components/mqtt/light/schema.py b/homeassistant/components/mqtt/light/schema.py index 6e2ac60b28d..baec7dd40e5 100644 --- a/homeassistant/components/mqtt/light/schema.py +++ b/homeassistant/components/mqtt/light/schema.py @@ -1,4 +1,5 @@ """Shared schema code.""" + import voluptuous as vol from ..const import CONF_SCHEMA diff --git a/homeassistant/components/mqtt_eventstream/__init__.py b/homeassistant/components/mqtt_eventstream/__init__.py index af370fe82f3..5e677d13cfe 100644 --- a/homeassistant/components/mqtt_eventstream/__init__.py +++ b/homeassistant/components/mqtt_eventstream/__init__.py @@ -1,4 +1,5 @@ """Connect two Home Assistant instances via MQTT.""" + import json import logging diff --git a/homeassistant/components/mqtt_statestream/__init__.py b/homeassistant/components/mqtt_statestream/__init__.py index 32836825876..1f9fa2fad0f 100644 --- a/homeassistant/components/mqtt_statestream/__init__.py +++ b/homeassistant/components/mqtt_statestream/__init__.py @@ -1,4 +1,5 @@ """Publish simple item state changes via MQTT.""" + import json import logging diff --git a/homeassistant/components/mullvad/__init__.py b/homeassistant/components/mullvad/__init__.py index cd692f00537..b79b9b4aa6a 100644 --- a/homeassistant/components/mullvad/__init__.py +++ b/homeassistant/components/mullvad/__init__.py @@ -1,4 +1,5 @@ """The Mullvad VPN integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/mycroft/__init__.py b/homeassistant/components/mycroft/__init__.py index 7c4b75aaad1..557eca972e6 100644 --- a/homeassistant/components/mycroft/__init__.py +++ b/homeassistant/components/mycroft/__init__.py @@ -1,4 +1,5 @@ """Support for Mycroft AI.""" + import voluptuous as vol from homeassistant.const import CONF_HOST, Platform diff --git a/homeassistant/components/myuplink/coordinator.py b/homeassistant/components/myuplink/coordinator.py index 03a902fc4bb..211fd894ac5 100644 --- a/homeassistant/components/myuplink/coordinator.py +++ b/homeassistant/components/myuplink/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for myUplink.""" + import asyncio.timeouts from dataclasses import dataclass from datetime import datetime, timedelta diff --git a/homeassistant/components/neato/__init__.py b/homeassistant/components/neato/__init__.py index b172d84533c..e0a3f4bc37a 100644 --- a/homeassistant/components/neato/__init__.py +++ b/homeassistant/components/neato/__init__.py @@ -1,4 +1,5 @@ """Support for Neato botvac connected vacuum cleaners.""" + import logging import aiohttp diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 9761c8298c7..e5e17133690 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -1,4 +1,5 @@ """The Netatmo integration.""" + import logging from aiohttp.web import Request diff --git a/homeassistant/components/netgear_lte/const.py b/homeassistant/components/netgear_lte/const.py index b47218bf4e1..69a96c289e8 100644 --- a/homeassistant/components/netgear_lte/const.py +++ b/homeassistant/components/netgear_lte/const.py @@ -1,4 +1,5 @@ """Constants for the Netgear LTE integration.""" + import logging from typing import Final diff --git a/homeassistant/components/nexia/__init__.py b/homeassistant/components/nexia/__init__.py index f1954eb50b8..9d9299b1ce9 100644 --- a/homeassistant/components/nexia/__init__.py +++ b/homeassistant/components/nexia/__init__.py @@ -1,4 +1,5 @@ """Support for Nexia / Trane XL Thermostats.""" + import logging import aiohttp diff --git a/homeassistant/components/nexia/config_flow.py b/homeassistant/components/nexia/config_flow.py index 8112e019f0d..5af4ff52fbb 100644 --- a/homeassistant/components/nexia/config_flow.py +++ b/homeassistant/components/nexia/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nexia integration.""" + import logging import aiohttp diff --git a/homeassistant/components/nibe_heatpump/const.py b/homeassistant/components/nibe_heatpump/const.py index 0f16567671c..ccdd92783de 100644 --- a/homeassistant/components/nibe_heatpump/const.py +++ b/homeassistant/components/nibe_heatpump/const.py @@ -1,4 +1,5 @@ """Constants for the Nibe Heat Pump integration.""" + import logging DOMAIN = "nibe_heatpump" diff --git a/homeassistant/components/nightscout/config_flow.py b/homeassistant/components/nightscout/config_flow.py index 032af8975eb..6d2a0e6c385 100644 --- a/homeassistant/components/nightscout/config_flow.py +++ b/homeassistant/components/nightscout/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Nightscout integration.""" + import logging from typing import Any diff --git a/homeassistant/components/no_ip/__init__.py b/homeassistant/components/no_ip/__init__.py index 8ab277c3def..d9084719cbd 100644 --- a/homeassistant/components/no_ip/__init__.py +++ b/homeassistant/components/no_ip/__init__.py @@ -1,4 +1,5 @@ """Integrate with NO-IP Dynamic DNS service.""" + import asyncio import base64 from datetime import datetime, timedelta diff --git a/homeassistant/components/notify/const.py b/homeassistant/components/notify/const.py index 38dba680635..b653b5d1cbf 100644 --- a/homeassistant/components/notify/const.py +++ b/homeassistant/components/notify/const.py @@ -1,4 +1,5 @@ """Provide common notify constants.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/notify_events/__init__.py b/homeassistant/components/notify_events/__init__.py index 12efa693b19..2be97d709a9 100644 --- a/homeassistant/components/notify_events/__init__.py +++ b/homeassistant/components/notify_events/__init__.py @@ -1,4 +1,5 @@ """The notify_events component.""" + import voluptuous as vol from homeassistant.const import CONF_TOKEN, Platform diff --git a/homeassistant/components/notion/const.py b/homeassistant/components/notion/const.py index b1ea921a71b..590431d1a59 100644 --- a/homeassistant/components/notion/const.py +++ b/homeassistant/components/notion/const.py @@ -1,4 +1,5 @@ """Define constants for the Notion integration.""" + import logging DOMAIN = "notion" diff --git a/homeassistant/components/nuheat/climate.py b/homeassistant/components/nuheat/climate.py index b2ebbfa8485..db85827fc9b 100644 --- a/homeassistant/components/nuheat/climate.py +++ b/homeassistant/components/nuheat/climate.py @@ -1,4 +1,5 @@ """Support for NuHeat thermostats.""" + import logging from typing import Any diff --git a/homeassistant/components/numato/__init__.py b/homeassistant/components/numato/__init__.py index 7a66ac55d70..978264d867e 100644 --- a/homeassistant/components/numato/__init__.py +++ b/homeassistant/components/numato/__init__.py @@ -1,4 +1,5 @@ """Support for controlling GPIO pins of a Numato Labs USB GPIO expander.""" + import logging import numato_gpio as gpio diff --git a/homeassistant/components/nzbget/__init__.py b/homeassistant/components/nzbget/__init__.py index 9d6fafd30c7..61b3f98739c 100644 --- a/homeassistant/components/nzbget/__init__.py +++ b/homeassistant/components/nzbget/__init__.py @@ -1,4 +1,5 @@ """The NZBGet integration.""" + import voluptuous as vol from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/nzbget/coordinator.py b/homeassistant/components/nzbget/coordinator.py index dcefe25eae9..cf9625ce4ec 100644 --- a/homeassistant/components/nzbget/coordinator.py +++ b/homeassistant/components/nzbget/coordinator.py @@ -1,4 +1,5 @@ """Provides the NZBGet DataUpdateCoordinator.""" + import asyncio from collections.abc import Mapping from datetime import timedelta diff --git a/homeassistant/components/ombi/__init__.py b/homeassistant/components/ombi/__init__.py index b67b097dfbf..719efdc8ae3 100644 --- a/homeassistant/components/ombi/__init__.py +++ b/homeassistant/components/ombi/__init__.py @@ -1,4 +1,5 @@ """Support for Ombi.""" + import logging import pyombi diff --git a/homeassistant/components/omnilogic/__init__.py b/homeassistant/components/omnilogic/__init__.py index 27f145f82b6..d9966290986 100644 --- a/homeassistant/components/omnilogic/__init__.py +++ b/homeassistant/components/omnilogic/__init__.py @@ -1,4 +1,5 @@ """The Omnilogic integration.""" + import logging from omnilogic import LoginException, OmniLogic, OmniLogicException diff --git a/homeassistant/components/omnilogic/switch.py b/homeassistant/components/omnilogic/switch.py index dfefd17bf11..9bdc59a14c8 100644 --- a/homeassistant/components/omnilogic/switch.py +++ b/homeassistant/components/omnilogic/switch.py @@ -1,4 +1,5 @@ """Platform for Omnilogic switch integration.""" + import time from typing import Any diff --git a/homeassistant/components/ondilo_ico/config_flow.py b/homeassistant/components/ondilo_ico/config_flow.py index 503f3936303..5a0fe8c21a5 100644 --- a/homeassistant/components/ondilo_ico/config_flow.py +++ b/homeassistant/components/ondilo_ico/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Ondilo ICO.""" + import logging from homeassistant.helpers import config_entry_oauth2_flow diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index e3454a5eb5c..95082c9d9e2 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -1,4 +1,5 @@ """The 1-Wire component.""" + import logging from pyownet import protocol diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index ea6cd542fea..5c97abee77a 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -1,4 +1,5 @@ """The ONVIF integration.""" + import asyncio from contextlib import suppress from http import HTTPStatus diff --git a/homeassistant/components/onvif/const.py b/homeassistant/components/onvif/const.py index 77fa098a316..d191a1710d5 100644 --- a/homeassistant/components/onvif/const.py +++ b/homeassistant/components/onvif/const.py @@ -1,4 +1,5 @@ """Constants for the onvif component.""" + import logging from httpx import RequestError diff --git a/homeassistant/components/opensky/const.py b/homeassistant/components/opensky/const.py index 7fe26b424d3..46cd7f4263e 100644 --- a/homeassistant/components/opensky/const.py +++ b/homeassistant/components/opensky/const.py @@ -1,4 +1,5 @@ """OpenSky constants.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 12f4724e056..ca37b7baaef 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -1,4 +1,5 @@ """Support for OpenTherm Gateway devices.""" + import asyncio from datetime import date, datetime import logging diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index d6aa5a3b700..de949dafbaf 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -1,4 +1,5 @@ """Support for OpenTherm Gateway binary sensors.""" + import logging from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorEntity diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index 5848d50ad95..bf393794450 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -1,4 +1,5 @@ """Support for OpenTherm Gateway sensors.""" + import logging from homeassistant.components.sensor import ENTITY_ID_FORMAT, SensorEntity diff --git a/homeassistant/components/openuv/const.py b/homeassistant/components/openuv/const.py index b03726d5749..9d711bb8901 100644 --- a/homeassistant/components/openuv/const.py +++ b/homeassistant/components/openuv/const.py @@ -1,4 +1,5 @@ """Define constants for the OpenUV component.""" + import logging DOMAIN = "openuv" diff --git a/homeassistant/components/openweathermap/weather_update_coordinator.py b/homeassistant/components/openweathermap/weather_update_coordinator.py index 05b24d60f79..d54a7fa899f 100644 --- a/homeassistant/components/openweathermap/weather_update_coordinator.py +++ b/homeassistant/components/openweathermap/weather_update_coordinator.py @@ -1,4 +1,5 @@ """Weather data coordinator for the OpenWeatherMap (OWM) service.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/opnsense/__init__.py b/homeassistant/components/opnsense/__init__.py index 0111379df44..d2ee2e2dfbd 100644 --- a/homeassistant/components/opnsense/__init__.py +++ b/homeassistant/components/opnsense/__init__.py @@ -1,4 +1,5 @@ """Support for OPNSense Routers.""" + import logging from pyopnsense import diagnostics diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index 3c1f6708342..29fe4f0cf65 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -1,4 +1,5 @@ """Config flow for OwnTracks.""" + import secrets from homeassistant.components import cloud, webhook diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index df61aa6e968..3e669079848 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -1,4 +1,5 @@ """OwnTracks Message handlers.""" + import json import logging diff --git a/homeassistant/components/panel_iframe/__init__.py b/homeassistant/components/panel_iframe/__init__.py index e33e5078288..c51768952eb 100644 --- a/homeassistant/components/panel_iframe/__init__.py +++ b/homeassistant/components/panel_iframe/__init__.py @@ -1,4 +1,5 @@ """Register an iFrame front end panel.""" + import voluptuous as vol from homeassistant.components import frontend diff --git a/homeassistant/components/peco/const.py b/homeassistant/components/peco/const.py index 1df8ae41ecb..5d73057698f 100644 --- a/homeassistant/components/peco/const.py +++ b/homeassistant/components/peco/const.py @@ -1,4 +1,5 @@ """Constants for the PECO Outage Counter integration.""" + import logging from typing import Final diff --git a/homeassistant/components/pegel_online/coordinator.py b/homeassistant/components/pegel_online/coordinator.py index 9463aa48872..1802af8e05c 100644 --- a/homeassistant/components/pegel_online/coordinator.py +++ b/homeassistant/components/pegel_online/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for pegel_online.""" + import logging from aiopegelonline import CONNECT_ERRORS, PegelOnline, Station, StationMeasurements diff --git a/homeassistant/components/picnic/coordinator.py b/homeassistant/components/picnic/coordinator.py index 61af7e5cc91..7a76d3174cd 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -1,4 +1,5 @@ """Coordinator to fetch data from the Picnic API.""" + import asyncio from contextlib import suppress import copy diff --git a/homeassistant/components/picotts/tts.py b/homeassistant/components/picotts/tts.py index 4d9f1755145..8ba17fdac17 100644 --- a/homeassistant/components/picotts/tts.py +++ b/homeassistant/components/picotts/tts.py @@ -1,4 +1,5 @@ """Support for the Pico TTS speech service.""" + import logging import os import shutil diff --git a/homeassistant/components/pilight/base_class.py b/homeassistant/components/pilight/base_class.py index cb96d89e6a2..d2d83813516 100644 --- a/homeassistant/components/pilight/base_class.py +++ b/homeassistant/components/pilight/base_class.py @@ -1,4 +1,5 @@ """Base class for pilight.""" + import voluptuous as vol from homeassistant.const import ( diff --git a/homeassistant/components/ping/helpers.py b/homeassistant/components/ping/helpers.py index 16d36b61ab7..f9afcef7be9 100644 --- a/homeassistant/components/ping/helpers.py +++ b/homeassistant/components/ping/helpers.py @@ -1,4 +1,5 @@ """Ping classes shared between platforms.""" + import asyncio from contextlib import suppress import logging diff --git a/homeassistant/components/plex/models.py b/homeassistant/components/plex/models.py index 9c274774d07..f2fa3f60d24 100644 --- a/homeassistant/components/plex/models.py +++ b/homeassistant/components/plex/models.py @@ -1,4 +1,5 @@ """Models to represent various Plex objects used in the integration.""" + import logging from homeassistant.components.media_player import MediaType diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index 39d41369a4b..e0fe79be182 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -1,4 +1,5 @@ """Services for the Plex integration.""" + import json import logging diff --git a/homeassistant/components/plex/update.py b/homeassistant/components/plex/update.py index e48c3a339d5..c1f1ffd5b14 100644 --- a/homeassistant/components/plex/update.py +++ b/homeassistant/components/plex/update.py @@ -1,4 +1,5 @@ """Representation of Plex updates.""" + import logging from typing import Any diff --git a/homeassistant/components/plum_lightpad/__init__.py b/homeassistant/components/plum_lightpad/__init__.py index 78c7bf7ff6a..f1816f03d3b 100644 --- a/homeassistant/components/plum_lightpad/__init__.py +++ b/homeassistant/components/plum_lightpad/__init__.py @@ -1,4 +1,5 @@ """Support for Plum Lightpad devices.""" + import logging from aiohttp import ContentTypeError diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 9fe63bf1d55..9f0f6e6dc7c 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -1,4 +1,5 @@ """Support for Minut Point.""" + import asyncio import logging diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index b88862758af..acf4b3e6d34 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Minut Point.""" + import asyncio from collections import OrderedDict import logging diff --git a/homeassistant/components/poolsense/__init__.py b/homeassistant/components/poolsense/__init__.py index 644ecb8cf3d..808d2300798 100644 --- a/homeassistant/components/poolsense/__init__.py +++ b/homeassistant/components/poolsense/__init__.py @@ -1,4 +1,5 @@ """The PoolSense integration.""" + import logging from poolsense import PoolSense diff --git a/homeassistant/components/poolsense/config_flow.py b/homeassistant/components/poolsense/config_flow.py index 13ccfa4abc0..915fa1c8d06 100644 --- a/homeassistant/components/poolsense/config_flow.py +++ b/homeassistant/components/poolsense/config_flow.py @@ -1,4 +1,5 @@ """Config flow for PoolSense integration.""" + import logging from typing import Any diff --git a/homeassistant/components/poolsense/coordinator.py b/homeassistant/components/poolsense/coordinator.py index e5e3e6ad1bd..8b6f99ed72b 100644 --- a/homeassistant/components/poolsense/coordinator.py +++ b/homeassistant/components/poolsense/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinator for poolsense integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index 89240820057..da70e587b88 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -1,4 +1,5 @@ """The profiler integration.""" + import asyncio from contextlib import suppress from datetime import timedelta diff --git a/homeassistant/components/progettihwsw/binary_sensor.py b/homeassistant/components/progettihwsw/binary_sensor.py index ea7a7dce5c3..fd4f478c045 100644 --- a/homeassistant/components/progettihwsw/binary_sensor.py +++ b/homeassistant/components/progettihwsw/binary_sensor.py @@ -1,4 +1,5 @@ """Control binary sensor instances.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/progettihwsw/switch.py b/homeassistant/components/progettihwsw/switch.py index f466e11a1cc..accabcfb32f 100644 --- a/homeassistant/components/progettihwsw/switch.py +++ b/homeassistant/components/progettihwsw/switch.py @@ -1,4 +1,5 @@ """Control switches.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/prosegur/__init__.py b/homeassistant/components/prosegur/__init__.py index fd79a091e39..bf2aad451df 100644 --- a/homeassistant/components/prosegur/__init__.py +++ b/homeassistant/components/prosegur/__init__.py @@ -1,4 +1,5 @@ """The Prosegur Alarm integration.""" + import logging from pyprosegur.auth import Auth diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index f68ad6ce896..3e92861b963 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -1,4 +1,5 @@ """Support for PlayStation 4 consoles.""" + import logging import os diff --git a/homeassistant/components/purpleair/const.py b/homeassistant/components/purpleair/const.py index 60f51a9e7dd..5f1ec84d469 100644 --- a/homeassistant/components/purpleair/const.py +++ b/homeassistant/components/purpleair/const.py @@ -1,4 +1,5 @@ """Constants for the PurpleAir integration.""" + import logging DOMAIN = "purpleair" diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py index c10c6de5b3f..e976ae5d1b0 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -1,4 +1,5 @@ """Component to allow running Python scripts.""" + import datetime import glob import logging diff --git a/homeassistant/components/qbittorrent/__init__.py b/homeassistant/components/qbittorrent/__init__.py index 84315186097..7b1a38b7e31 100644 --- a/homeassistant/components/qbittorrent/__init__.py +++ b/homeassistant/components/qbittorrent/__init__.py @@ -1,4 +1,5 @@ """The qbittorrent component.""" + import logging from qbittorrent.client import LoginRequired diff --git a/homeassistant/components/qvr_pro/__init__.py b/homeassistant/components/qvr_pro/__init__.py index d0fc8511dd3..9aad94790c6 100644 --- a/homeassistant/components/qvr_pro/__init__.py +++ b/homeassistant/components/qvr_pro/__init__.py @@ -1,4 +1,5 @@ """Support for QVR Pro NVR software by QNAP.""" + import logging from pyqvrpro import Client diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index 13299b4e7dc..e5a64d25fc8 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -1,4 +1,5 @@ """Integration with the Rachio Iro sprinkler system controller.""" + import logging import secrets diff --git a/homeassistant/components/radarr/const.py b/homeassistant/components/radarr/const.py index 37388dd51ef..ef3b29af4e5 100644 --- a/homeassistant/components/radarr/const.py +++ b/homeassistant/components/radarr/const.py @@ -1,4 +1,5 @@ """Constants for Radarr.""" + import logging from typing import Final diff --git a/homeassistant/components/radio_browser/const.py b/homeassistant/components/radio_browser/const.py index eb456db08e8..206cc3de933 100644 --- a/homeassistant/components/radio_browser/const.py +++ b/homeassistant/components/radio_browser/const.py @@ -1,4 +1,5 @@ """Constants for the Radio Browser integration.""" + import logging from typing import Final diff --git a/homeassistant/components/rainmachine/const.py b/homeassistant/components/rainmachine/const.py index e28b2326b79..3bd89ab7e23 100644 --- a/homeassistant/components/rainmachine/const.py +++ b/homeassistant/components/rainmachine/const.py @@ -1,4 +1,5 @@ """Define constants for the SimpliSafe component.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/recollect_waste/const.py b/homeassistant/components/recollect_waste/const.py index 5589507d4ac..b1fa1b0e6f0 100644 --- a/homeassistant/components/recollect_waste/const.py +++ b/homeassistant/components/recollect_waste/const.py @@ -1,4 +1,5 @@ """Define ReCollect Waste constants.""" + import logging DOMAIN = "recollect_waste" diff --git a/homeassistant/components/recorder/entity_registry.py b/homeassistant/components/recorder/entity_registry.py index fbf6e691777..cb464c7f543 100644 --- a/homeassistant/components/recorder/entity_registry.py +++ b/homeassistant/components/recorder/entity_registry.py @@ -1,4 +1,5 @@ """Recorder entity registry helper.""" + import logging from homeassistant.core import Event, HomeAssistant, callback diff --git a/homeassistant/components/recorder/pool.py b/homeassistant/components/recorder/pool.py index 46f140305e3..27bc313b162 100644 --- a/homeassistant/components/recorder/pool.py +++ b/homeassistant/components/recorder/pool.py @@ -1,4 +1,5 @@ """A pool for sqlite connections.""" + import logging import threading import traceback diff --git a/homeassistant/components/remember_the_milk/__init__.py b/homeassistant/components/remember_the_milk/__init__.py index 82025eeba5d..3d1654960a7 100644 --- a/homeassistant/components/remember_the_milk/__init__.py +++ b/homeassistant/components/remember_the_milk/__init__.py @@ -1,4 +1,5 @@ """Support to interact with Remember The Milk.""" + import json import logging import os diff --git a/homeassistant/components/renault/__init__.py b/homeassistant/components/renault/__init__.py index 6b5679088a0..f2a02be7b49 100644 --- a/homeassistant/components/renault/__init__.py +++ b/homeassistant/components/renault/__init__.py @@ -1,4 +1,5 @@ """Support for Renault devices.""" + import aiohttp from renault_api.gigya.exceptions import GigyaException diff --git a/homeassistant/components/ridwell/const.py b/homeassistant/components/ridwell/const.py index 69c5ead5277..75ceb2dbbd6 100644 --- a/homeassistant/components/ridwell/const.py +++ b/homeassistant/components/ridwell/const.py @@ -1,4 +1,5 @@ """Constants for the Ridwell integration.""" + import logging DOMAIN = "ridwell" diff --git a/homeassistant/components/ring/siren.py b/homeassistant/components/ring/siren.py index 0844f650e57..60290469fa7 100644 --- a/homeassistant/components/ring/siren.py +++ b/homeassistant/components/ring/siren.py @@ -1,4 +1,5 @@ """Component providing HA Siren support for Ring Chimes.""" + import logging from typing import Any diff --git a/homeassistant/components/rituals_perfume_genie/__init__.py b/homeassistant/components/rituals_perfume_genie/__init__.py index e0fac0abfcf..792a470ca3c 100644 --- a/homeassistant/components/rituals_perfume_genie/__init__.py +++ b/homeassistant/components/rituals_perfume_genie/__init__.py @@ -1,4 +1,5 @@ """The Rituals Perfume Genie integration.""" + import asyncio import aiohttp diff --git a/homeassistant/components/rituals_perfume_genie/coordinator.py b/homeassistant/components/rituals_perfume_genie/coordinator.py index b63b28e4de9..4c86f110b17 100644 --- a/homeassistant/components/rituals_perfume_genie/coordinator.py +++ b/homeassistant/components/rituals_perfume_genie/coordinator.py @@ -1,4 +1,5 @@ """The Rituals Perfume Genie data update coordinator.""" + import logging from pyrituals import Diffuser diff --git a/homeassistant/components/roborock/image.py b/homeassistant/components/roborock/image.py index 66957232679..f62a0aac1e4 100644 --- a/homeassistant/components/roborock/image.py +++ b/homeassistant/components/roborock/image.py @@ -1,4 +1,5 @@ """Support for Roborock image.""" + import asyncio import io from itertools import chain diff --git a/homeassistant/components/roborock/number.py b/homeassistant/components/roborock/number.py index 2218e5ec2ce..20f4c76fe75 100644 --- a/homeassistant/components/roborock/number.py +++ b/homeassistant/components/roborock/number.py @@ -1,4 +1,5 @@ """Support for Roborock number.""" + import asyncio from collections.abc import Callable, Coroutine from dataclasses import dataclass diff --git a/homeassistant/components/roborock/time.py b/homeassistant/components/roborock/time.py index 71dee773fa4..e142713213c 100644 --- a/homeassistant/components/roborock/time.py +++ b/homeassistant/components/roborock/time.py @@ -1,4 +1,5 @@ """Support for Roborock time.""" + import asyncio from collections.abc import Callable, Coroutine from dataclasses import dataclass diff --git a/homeassistant/components/roomba/__init__.py b/homeassistant/components/roomba/__init__.py index bd302e16a90..d00010aa3e9 100644 --- a/homeassistant/components/roomba/__init__.py +++ b/homeassistant/components/roomba/__init__.py @@ -1,4 +1,5 @@ """The roomba component.""" + import asyncio import contextlib from functools import partial diff --git a/homeassistant/components/roomba/braava.py b/homeassistant/components/roomba/braava.py index db517a065ea..da9007f8b2c 100644 --- a/homeassistant/components/roomba/braava.py +++ b/homeassistant/components/roomba/braava.py @@ -1,4 +1,5 @@ """Class for Braava devices.""" + import logging from homeassistant.components.vacuum import VacuumEntityFeature diff --git a/homeassistant/components/roomba/roomba.py b/homeassistant/components/roomba/roomba.py index 2c50508a637..5d774120634 100644 --- a/homeassistant/components/roomba/roomba.py +++ b/homeassistant/components/roomba/roomba.py @@ -1,4 +1,5 @@ """Class for Roomba devices.""" + import logging from homeassistant.components.vacuum import VacuumEntityFeature diff --git a/homeassistant/components/roon/config_flow.py b/homeassistant/components/roon/config_flow.py index e7f9bfb6860..cb70187b988 100644 --- a/homeassistant/components/roon/config_flow.py +++ b/homeassistant/components/roon/config_flow.py @@ -1,4 +1,5 @@ """Config flow for roon integration.""" + import asyncio import logging diff --git a/homeassistant/components/roon/event.py b/homeassistant/components/roon/event.py index fc1bb339cd7..ea5014c8755 100644 --- a/homeassistant/components/roon/event.py +++ b/homeassistant/components/roon/event.py @@ -1,4 +1,5 @@ """Roon event entities.""" + import logging from typing import cast diff --git a/homeassistant/components/roon/media_browser.py b/homeassistant/components/roon/media_browser.py index dd7a2a1faa3..806375bc902 100644 --- a/homeassistant/components/roon/media_browser.py +++ b/homeassistant/components/roon/media_browser.py @@ -1,4 +1,5 @@ """Support to interface with the Roon API.""" + import logging from homeassistant.components.media_player import BrowseMedia, MediaClass diff --git a/homeassistant/components/roon/server.py b/homeassistant/components/roon/server.py index 488fe18aae4..3f2e541b125 100644 --- a/homeassistant/components/roon/server.py +++ b/homeassistant/components/roon/server.py @@ -1,4 +1,5 @@ """Code to handle the api connection to a Roon server.""" + import asyncio import logging diff --git a/homeassistant/components/rova/const.py b/homeassistant/components/rova/const.py index 71d39d3703b..a0b233dabca 100644 --- a/homeassistant/components/rova/const.py +++ b/homeassistant/components/rova/const.py @@ -1,4 +1,5 @@ """Const file for Rova.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/rpi_camera/__init__.py b/homeassistant/components/rpi_camera/__init__.py index 15c59476ab2..31b667d2b4e 100644 --- a/homeassistant/components/rpi_camera/__init__.py +++ b/homeassistant/components/rpi_camera/__init__.py @@ -1,4 +1,5 @@ """The rpi_camera component.""" + import voluptuous as vol from homeassistant.const import CONF_FILE_PATH, CONF_NAME, Platform diff --git a/homeassistant/components/rpi_power/binary_sensor.py b/homeassistant/components/rpi_power/binary_sensor.py index b5337923d6c..a7306899bde 100644 --- a/homeassistant/components/rpi_power/binary_sensor.py +++ b/homeassistant/components/rpi_power/binary_sensor.py @@ -2,6 +2,7 @@ Minimal Kernel needed is 4.14+ """ + import logging from rpi_bad_power import UnderVoltage, new_under_voltage diff --git a/homeassistant/components/ruckus_unleashed/__init__.py b/homeassistant/components/ruckus_unleashed/__init__.py index 63521a622cd..c2c46fcc125 100644 --- a/homeassistant/components/ruckus_unleashed/__init__.py +++ b/homeassistant/components/ruckus_unleashed/__init__.py @@ -1,4 +1,5 @@ """The Ruckus Unleashed integration.""" + import logging from aioruckus import AjaxSession diff --git a/homeassistant/components/samsungtv/const.py b/homeassistant/components/samsungtv/const.py index 6c657145d7a..9fe8fb58cbd 100644 --- a/homeassistant/components/samsungtv/const.py +++ b/homeassistant/components/samsungtv/const.py @@ -1,4 +1,5 @@ """Constants for the Samsung TV integration.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/satel_integra/__init__.py b/homeassistant/components/satel_integra/__init__.py index e9edd1ff52c..466faf27b12 100644 --- a/homeassistant/components/satel_integra/__init__.py +++ b/homeassistant/components/satel_integra/__init__.py @@ -1,4 +1,5 @@ """Support for Satel Integra devices.""" + import collections import logging diff --git a/homeassistant/components/schedule/const.py b/homeassistant/components/schedule/const.py index e044a614e4d..5ec57aae78d 100644 --- a/homeassistant/components/schedule/const.py +++ b/homeassistant/components/schedule/const.py @@ -1,4 +1,5 @@ """Constants for the schedule integration.""" + import logging from typing import Final diff --git a/homeassistant/components/schluter/__init__.py b/homeassistant/components/schluter/__init__.py index 5abb6b2a112..907841a2e5e 100644 --- a/homeassistant/components/schluter/__init__.py +++ b/homeassistant/components/schluter/__init__.py @@ -1,4 +1,5 @@ """The Schluter DITRA-HEAT integration.""" + import logging from requests import RequestException, Session diff --git a/homeassistant/components/screenlogic/__init__.py b/homeassistant/components/screenlogic/__init__.py index 56c686df6b4..c7dbaabd565 100644 --- a/homeassistant/components/screenlogic/__init__.py +++ b/homeassistant/components/screenlogic/__init__.py @@ -1,4 +1,5 @@ """The Screenlogic integration.""" + import logging from typing import Any diff --git a/homeassistant/components/screenlogic/util.py b/homeassistant/components/screenlogic/util.py index 928effc73fc..781d0fcab24 100644 --- a/homeassistant/components/screenlogic/util.py +++ b/homeassistant/components/screenlogic/util.py @@ -1,4 +1,5 @@ """Utility functions for the ScreenLogic integration.""" + import logging from screenlogicpy.const.data import SHARED_VALUES diff --git a/homeassistant/components/script/const.py b/homeassistant/components/script/const.py index 7b4b0f5fe9c..e6e5fdca13f 100644 --- a/homeassistant/components/script/const.py +++ b/homeassistant/components/script/const.py @@ -1,4 +1,5 @@ """Constants for the script integration.""" + import logging DOMAIN = "script" diff --git a/homeassistant/components/scsgate/__init__.py b/homeassistant/components/scsgate/__init__.py index af5921e2c3b..7f00f8abe84 100644 --- a/homeassistant/components/scsgate/__init__.py +++ b/homeassistant/components/scsgate/__init__.py @@ -1,4 +1,5 @@ """Support for SCSGate components.""" + import logging from threading import Lock diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index 094ecbdfcf7..7dde4c029b1 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -1,4 +1,5 @@ """Support for monitoring a Sense energy sensor device.""" + import logging from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/sensor/device_trigger.py b/homeassistant/components/sensor/device_trigger.py index c5c19a19d0b..b46f6260285 100644 --- a/homeassistant/components/sensor/device_trigger.py +++ b/homeassistant/components/sensor/device_trigger.py @@ -1,4 +1,5 @@ """Provides device triggers for sensors.""" + import voluptuous as vol from homeassistant.components.device_automation import ( diff --git a/homeassistant/components/senz/config_flow.py b/homeassistant/components/senz/config_flow.py index 1bc38321370..457c4f10dd8 100644 --- a/homeassistant/components/senz/config_flow.py +++ b/homeassistant/components/senz/config_flow.py @@ -1,4 +1,5 @@ """Config flow for nVent RAYCHEM SENZ.""" + import logging from homeassistant.helpers import config_entry_oauth2_flow diff --git a/homeassistant/components/sharkiq/__init__.py b/homeassistant/components/sharkiq/__init__.py index 53a8c4cba3d..a29a2b2e773 100644 --- a/homeassistant/components/sharkiq/__init__.py +++ b/homeassistant/components/sharkiq/__init__.py @@ -1,4 +1,5 @@ """Shark IQ Integration.""" + import asyncio from contextlib import suppress diff --git a/homeassistant/components/simplisafe/const.py b/homeassistant/components/simplisafe/const.py index 1405f60b400..1ed77bcd685 100644 --- a/homeassistant/components/simplisafe/const.py +++ b/homeassistant/components/simplisafe/const.py @@ -1,4 +1,5 @@ """Define constants for the SimpliSafe component.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/sisyphus/__init__.py b/homeassistant/components/sisyphus/__init__.py index 721c51ca964..da8d670d412 100644 --- a/homeassistant/components/sisyphus/__init__.py +++ b/homeassistant/components/sisyphus/__init__.py @@ -1,4 +1,5 @@ """Support for controlling Sisyphus Kinetic Art Tables.""" + import asyncio import logging diff --git a/homeassistant/components/skybell/const.py b/homeassistant/components/skybell/const.py index 1d46e45dad1..38c52ea23aa 100644 --- a/homeassistant/components/skybell/const.py +++ b/homeassistant/components/skybell/const.py @@ -1,4 +1,5 @@ """Constants for the Skybell HD Doorbell.""" + import logging from typing import Final diff --git a/homeassistant/components/sleepiq/coordinator.py b/homeassistant/components/sleepiq/coordinator.py index 8d51da5f47a..7fe4f964b36 100644 --- a/homeassistant/components/sleepiq/coordinator.py +++ b/homeassistant/components/sleepiq/coordinator.py @@ -1,4 +1,5 @@ """Coordinator for SleepIQ.""" + import asyncio from dataclasses import dataclass from datetime import timedelta diff --git a/homeassistant/components/sleepiq/light.py b/homeassistant/components/sleepiq/light.py index e684d383b40..781bd8e600a 100644 --- a/homeassistant/components/sleepiq/light.py +++ b/homeassistant/components/sleepiq/light.py @@ -1,4 +1,5 @@ """Support for SleepIQ outlet lights.""" + import logging from typing import Any diff --git a/homeassistant/components/smappee/config_flow.py b/homeassistant/components/smappee/config_flow.py index 1b79d4ff622..6ed18905233 100644 --- a/homeassistant/components/smappee/config_flow.py +++ b/homeassistant/components/smappee/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Smappee.""" + import logging from pysmappee import helper, mqtt diff --git a/homeassistant/components/smart_meter_texas/__init__.py b/homeassistant/components/smart_meter_texas/__init__.py index 47b74c53db6..c6e466392f0 100644 --- a/homeassistant/components/smart_meter_texas/__init__.py +++ b/homeassistant/components/smart_meter_texas/__init__.py @@ -1,4 +1,5 @@ """The Smart Meter Texas integration.""" + import logging import ssl diff --git a/homeassistant/components/smart_meter_texas/config_flow.py b/homeassistant/components/smart_meter_texas/config_flow.py index fef3b579414..f2fab31caaa 100644 --- a/homeassistant/components/smart_meter_texas/config_flow.py +++ b/homeassistant/components/smart_meter_texas/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Smart Meter Texas integration.""" + import logging from aiohttp import ClientError diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 11446771130..1c18a39b1e6 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -1,4 +1,5 @@ """SmartApp functionality to receive cloud-push notifications.""" + import asyncio import functools import logging diff --git a/homeassistant/components/smarttub/entity.py b/homeassistant/components/smarttub/entity.py index 6e6cb00a7d3..f9ab1d10bfe 100644 --- a/homeassistant/components/smarttub/entity.py +++ b/homeassistant/components/smarttub/entity.py @@ -1,4 +1,5 @@ """Base classes for SmartTub entities.""" + import smarttub from homeassistant.helpers.device_registry import DeviceInfo diff --git a/homeassistant/components/smarttub/switch.py b/homeassistant/components/smarttub/switch.py index aeeca46aaa9..6e1cf9bef2a 100644 --- a/homeassistant/components/smarttub/switch.py +++ b/homeassistant/components/smarttub/switch.py @@ -1,4 +1,5 @@ """Platform for switch integration.""" + import asyncio from typing import Any diff --git a/homeassistant/components/sms/__init__.py b/homeassistant/components/sms/__init__.py index a606b83896f..2d18d44de3a 100644 --- a/homeassistant/components/sms/__init__.py +++ b/homeassistant/components/sms/__init__.py @@ -1,4 +1,5 @@ """The sms component.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/sms/config_flow.py b/homeassistant/components/sms/config_flow.py index 7b507bac9b2..ff509bbbb97 100644 --- a/homeassistant/components/sms/config_flow.py +++ b/homeassistant/components/sms/config_flow.py @@ -1,4 +1,5 @@ """Config flow for SMS integration.""" + import logging import gammu diff --git a/homeassistant/components/sms/coordinator.py b/homeassistant/components/sms/coordinator.py index fd212fce4f2..7bc691afedf 100644 --- a/homeassistant/components/sms/coordinator.py +++ b/homeassistant/components/sms/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinators for the sms integration.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index 578b2191bd2..50abc9b39ef 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -1,4 +1,5 @@ """The sms gateway to interact with a GSM modem.""" + import logging import gammu diff --git a/homeassistant/components/snapcast/__init__.py b/homeassistant/components/snapcast/__init__.py index d8ff55cc175..a4163355944 100644 --- a/homeassistant/components/snapcast/__init__.py +++ b/homeassistant/components/snapcast/__init__.py @@ -1,4 +1,5 @@ """Snapcast Integration.""" + import logging import snapcast.control diff --git a/homeassistant/components/solarlog/config_flow.py b/homeassistant/components/solarlog/config_flow.py index 021bc24364a..83b9c600de8 100644 --- a/homeassistant/components/solarlog/config_flow.py +++ b/homeassistant/components/solarlog/config_flow.py @@ -1,4 +1,5 @@ """Config flow for solarlog integration.""" + import logging from urllib.parse import ParseResult, urlparse diff --git a/homeassistant/components/soma/config_flow.py b/homeassistant/components/soma/config_flow.py index db8b75e1422..773a24d5b44 100644 --- a/homeassistant/components/soma/config_flow.py +++ b/homeassistant/components/soma/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Soma.""" + import logging from api.soma_api import SomaApi diff --git a/homeassistant/components/somfy_mylink/__init__.py b/homeassistant/components/somfy_mylink/__init__.py index 7883c88f0b8..ed9652de55a 100644 --- a/homeassistant/components/somfy_mylink/__init__.py +++ b/homeassistant/components/somfy_mylink/__init__.py @@ -1,4 +1,5 @@ """Component for the Somfy MyLink device supporting the Synergy API.""" + import logging from somfy_mylink_synergy import SomfyMyLinkSynergy diff --git a/homeassistant/components/somfy_mylink/cover.py b/homeassistant/components/somfy_mylink/cover.py index 38f5fdc12f8..577795d172b 100644 --- a/homeassistant/components/somfy_mylink/cover.py +++ b/homeassistant/components/somfy_mylink/cover.py @@ -1,4 +1,5 @@ """Cover Platform for the Somfy MyLink component.""" + import logging from typing import Any diff --git a/homeassistant/components/sonarr/const.py b/homeassistant/components/sonarr/const.py index 5468953184a..7e703f02957 100644 --- a/homeassistant/components/sonarr/const.py +++ b/homeassistant/components/sonarr/const.py @@ -1,4 +1,5 @@ """Constants for Sonarr.""" + import logging DOMAIN = "sonarr" diff --git a/homeassistant/components/songpal/__init__.py b/homeassistant/components/songpal/__init__.py index 8ab1cb18bdd..aa3f850c9e3 100644 --- a/homeassistant/components/songpal/__init__.py +++ b/homeassistant/components/songpal/__init__.py @@ -1,4 +1,5 @@ """The songpal component.""" + import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry diff --git a/homeassistant/components/soundtouch/__init__.py b/homeassistant/components/soundtouch/__init__.py index b021604af4a..c35c1e6f9c3 100644 --- a/homeassistant/components/soundtouch/__init__.py +++ b/homeassistant/components/soundtouch/__init__.py @@ -1,4 +1,5 @@ """The soundtouch component.""" + import logging from libsoundtouch import soundtouch_device diff --git a/homeassistant/components/soundtouch/config_flow.py b/homeassistant/components/soundtouch/config_flow.py index 757e14438e9..c8e8ce945db 100644 --- a/homeassistant/components/soundtouch/config_flow.py +++ b/homeassistant/components/soundtouch/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Bose SoundTouch integration.""" + import logging from libsoundtouch import soundtouch_device diff --git a/homeassistant/components/spc/__init__.py b/homeassistant/components/spc/__init__.py index 49dc7b13fca..bb025d699fc 100644 --- a/homeassistant/components/spc/__init__.py +++ b/homeassistant/components/spc/__init__.py @@ -1,4 +1,5 @@ """Support for Vanderbilt (formerly Siemens) SPC alarm systems.""" + import logging from pyspcwebgw import SpcWebGateway diff --git a/homeassistant/components/spider/__init__.py b/homeassistant/components/spider/__init__.py index d4122a91d62..782486de2d8 100644 --- a/homeassistant/components/spider/__init__.py +++ b/homeassistant/components/spider/__init__.py @@ -1,4 +1,5 @@ """Support for Spider Smart devices.""" + import logging from spiderpy.spiderapi import SpiderApi, SpiderApiException, UnauthorizedException diff --git a/homeassistant/components/spider/config_flow.py b/homeassistant/components/spider/config_flow.py index a8466041dde..a678ea73051 100644 --- a/homeassistant/components/spider/config_flow.py +++ b/homeassistant/components/spider/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Spider.""" + import logging from spiderpy.spiderapi import SpiderApi, SpiderApiException, UnauthorizedException diff --git a/homeassistant/components/sql/const.py b/homeassistant/components/sql/const.py index 2443e617395..d8d13ab1699 100644 --- a/homeassistant/components/sql/const.py +++ b/homeassistant/components/sql/const.py @@ -1,4 +1,5 @@ """Adds constants for SQL integration.""" + import re from homeassistant.const import Platform diff --git a/homeassistant/components/squeezebox/browse_media.py b/homeassistant/components/squeezebox/browse_media.py index c66bc8af9a5..b770a3e22a3 100644 --- a/homeassistant/components/squeezebox/browse_media.py +++ b/homeassistant/components/squeezebox/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + import contextlib from homeassistant.components import media_source diff --git a/homeassistant/components/squeezebox/config_flow.py b/homeassistant/components/squeezebox/config_flow.py index c05f4ac9fef..effa4f2c970 100644 --- a/homeassistant/components/squeezebox/config_flow.py +++ b/homeassistant/components/squeezebox/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Logitech Squeezebox integration.""" + import asyncio from http import HTTPStatus import logging diff --git a/homeassistant/components/starline/const.py b/homeassistant/components/starline/const.py index 06465c7b50e..15db38e84fd 100644 --- a/homeassistant/components/starline/const.py +++ b/homeassistant/components/starline/const.py @@ -1,4 +1,5 @@ """StarLine constants.""" + import logging from homeassistant.const import Platform diff --git a/homeassistant/components/statsd/__init__.py b/homeassistant/components/statsd/__init__.py index 046c01cba32..efe1c818025 100644 --- a/homeassistant/components/statsd/__init__.py +++ b/homeassistant/components/statsd/__init__.py @@ -1,4 +1,5 @@ """Support for sending data to StatsD.""" + import logging import statsd diff --git a/homeassistant/components/steam_online/const.py b/homeassistant/components/steam_online/const.py index 01f4410a7c9..3c1bb3892b9 100644 --- a/homeassistant/components/steam_online/const.py +++ b/homeassistant/components/steam_online/const.py @@ -1,4 +1,5 @@ """Steam constants.""" + import logging from typing import Final diff --git a/homeassistant/components/stookalert/const.py b/homeassistant/components/stookalert/const.py index 3d370fb135d..9896eea212a 100644 --- a/homeassistant/components/stookalert/const.py +++ b/homeassistant/components/stookalert/const.py @@ -1,4 +1,5 @@ """Constants for the Stookalert integration.""" + import logging from typing import Final diff --git a/homeassistant/components/streamlabswater/const.py b/homeassistant/components/streamlabswater/const.py index ee407d376d4..f0ac6613a42 100644 --- a/homeassistant/components/streamlabswater/const.py +++ b/homeassistant/components/streamlabswater/const.py @@ -1,4 +1,5 @@ """Constants for the StreamLabs integration.""" + import logging DOMAIN = "streamlabswater" diff --git a/homeassistant/components/subaru/lock.py b/homeassistant/components/subaru/lock.py index 342fe34b97d..e21102f0b0c 100644 --- a/homeassistant/components/subaru/lock.py +++ b/homeassistant/components/subaru/lock.py @@ -1,4 +1,5 @@ """Support for Subaru door locks.""" + import logging from typing import Any diff --git a/homeassistant/components/subaru/remote_service.py b/homeassistant/components/subaru/remote_service.py index 04c87b6b8d2..acd71e186da 100644 --- a/homeassistant/components/subaru/remote_service.py +++ b/homeassistant/components/subaru/remote_service.py @@ -1,4 +1,5 @@ """Remote vehicle services for Subaru integration.""" + import logging from subarulink.exceptions import SubaruException diff --git a/homeassistant/components/sunweg/__init__.py b/homeassistant/components/sunweg/__init__.py index 9da91ccda0f..6c39a04127e 100644 --- a/homeassistant/components/sunweg/__init__.py +++ b/homeassistant/components/sunweg/__init__.py @@ -1,4 +1,5 @@ """The Sun WEG inverter sensor integration.""" + import datetime import json import logging diff --git a/homeassistant/components/swiss_public_transport/__init__.py b/homeassistant/components/swiss_public_transport/__init__.py index d0713ddf1d1..74a7d90cfb2 100644 --- a/homeassistant/components/swiss_public_transport/__init__.py +++ b/homeassistant/components/swiss_public_transport/__init__.py @@ -1,4 +1,5 @@ """The swiss_public_transport component.""" + import logging from opendata_transport import OpendataTransport diff --git a/homeassistant/components/swiss_public_transport/config_flow.py b/homeassistant/components/swiss_public_transport/config_flow.py index 59d01743de4..6c5de3c7883 100644 --- a/homeassistant/components/swiss_public_transport/config_flow.py +++ b/homeassistant/components/swiss_public_transport/config_flow.py @@ -1,4 +1,5 @@ """Config flow for swiss_public_transport.""" + import logging from typing import Any diff --git a/homeassistant/components/switchbee/entity.py b/homeassistant/components/switchbee/entity.py index 6aae5adb3d6..c601324b2a5 100644 --- a/homeassistant/components/switchbee/entity.py +++ b/homeassistant/components/switchbee/entity.py @@ -1,4 +1,5 @@ """Support for SwitchBee entity.""" + import logging from typing import Generic, TypeVar, cast diff --git a/homeassistant/components/syncthing/__init__.py b/homeassistant/components/syncthing/__init__.py index 1f492656166..28ec14a1935 100644 --- a/homeassistant/components/syncthing/__init__.py +++ b/homeassistant/components/syncthing/__init__.py @@ -1,4 +1,5 @@ """The syncthing integration.""" + import asyncio import logging diff --git a/homeassistant/components/syncthing/config_flow.py b/homeassistant/components/syncthing/config_flow.py index e936cc23183..2d7d2ddcc92 100644 --- a/homeassistant/components/syncthing/config_flow.py +++ b/homeassistant/components/syncthing/config_flow.py @@ -1,4 +1,5 @@ """Config flow for syncthing integration.""" + import aiosyncthing import voluptuous as vol diff --git a/homeassistant/components/syncthing/sensor.py b/homeassistant/components/syncthing/sensor.py index 7b22017c9c1..fc1f9ae8aea 100644 --- a/homeassistant/components/syncthing/sensor.py +++ b/homeassistant/components/syncthing/sensor.py @@ -1,4 +1,5 @@ """Support for monitoring the Syncthing instance.""" + import aiosyncthing from homeassistant.components.sensor import SensorEntity diff --git a/homeassistant/components/tado/services.py b/homeassistant/components/tado/services.py index a5c5387ce94..d2a0250016f 100644 --- a/homeassistant/components/tado/services.py +++ b/homeassistant/components/tado/services.py @@ -1,4 +1,5 @@ """Services for the Tado integration.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index cdbc041f535..87cc93879fd 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -1,4 +1,5 @@ """Support for Tado hot water zones.""" + import logging from typing import Any diff --git a/homeassistant/components/tami4/sensor.py b/homeassistant/components/tami4/sensor.py index 177e4cc784d..b93342d9144 100644 --- a/homeassistant/components/tami4/sensor.py +++ b/homeassistant/components/tami4/sensor.py @@ -1,4 +1,5 @@ """Sensor entities for Tami4Edge.""" + import logging from Tami4EdgeAPI import Tami4EdgeAPI diff --git a/homeassistant/components/tedee/__init__.py b/homeassistant/components/tedee/__init__.py index eeb0f8e0d5a..9468008ae8a 100644 --- a/homeassistant/components/tedee/__init__.py +++ b/homeassistant/components/tedee/__init__.py @@ -1,4 +1,5 @@ """Init the tedee component.""" + import logging from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py index bac6262cc6a..45d2ee65b45 100644 --- a/homeassistant/components/telegram_bot/polling.py +++ b/homeassistant/components/telegram_bot/polling.py @@ -1,4 +1,5 @@ """Support for Telegram bot using polling.""" + import logging from telegram import Update diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py index 50fd7bc8427..41835f955ed 100644 --- a/homeassistant/components/telegram_bot/webhooks.py +++ b/homeassistant/components/telegram_bot/webhooks.py @@ -1,4 +1,5 @@ """Support for Telegram bots using webhooks.""" + import datetime as dt from http import HTTPStatus from ipaddress import ip_address diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index 92feb69c60c..fcabea070b1 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -1,4 +1,5 @@ """Support for Telldus Live.""" + import asyncio from functools import partial import logging diff --git a/homeassistant/components/tellduslive/config_flow.py b/homeassistant/components/tellduslive/config_flow.py index a5b9607b221..4537abcdece 100644 --- a/homeassistant/components/tellduslive/config_flow.py +++ b/homeassistant/components/tellduslive/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Tellduslive.""" + import asyncio import logging import os diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py index 8284b386250..63af8a32527 100644 --- a/homeassistant/components/tellduslive/light.py +++ b/homeassistant/components/tellduslive/light.py @@ -1,4 +1,5 @@ """Support for Tellstick lights using Tellstick Net.""" + import logging from typing import Any diff --git a/homeassistant/components/tellstick/__init__.py b/homeassistant/components/tellstick/__init__.py index 42685f03e03..1a60927e25f 100644 --- a/homeassistant/components/tellstick/__init__.py +++ b/homeassistant/components/tellstick/__init__.py @@ -1,4 +1,5 @@ """Support for Tellstick.""" + import logging import threading diff --git a/homeassistant/components/template/config.py b/homeassistant/components/template/config.py index 9da43082d2b..42a57cfc4aa 100644 --- a/homeassistant/components/template/config.py +++ b/homeassistant/components/template/config.py @@ -1,4 +1,5 @@ """Template config validator.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/teslemetry/__init__.py b/homeassistant/components/teslemetry/__init__.py index b1083473858..1da3533fef1 100644 --- a/homeassistant/components/teslemetry/__init__.py +++ b/homeassistant/components/teslemetry/__init__.py @@ -1,4 +1,5 @@ """Teslemetry integration.""" + import asyncio from typing import Final diff --git a/homeassistant/components/thethingsnetwork/__init__.py b/homeassistant/components/thethingsnetwork/__init__.py index af0f9965f56..32850d05e57 100644 --- a/homeassistant/components/thethingsnetwork/__init__.py +++ b/homeassistant/components/thethingsnetwork/__init__.py @@ -1,4 +1,5 @@ """Support for The Things network.""" + import voluptuous as vol from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/thingspeak/__init__.py b/homeassistant/components/thingspeak/__init__.py index e4788674845..fdf06a9709a 100644 --- a/homeassistant/components/thingspeak/__init__.py +++ b/homeassistant/components/thingspeak/__init__.py @@ -1,4 +1,5 @@ """Support for submitting data to Thingspeak.""" + import logging from requests.exceptions import RequestException diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index 52db8421781..7305cf835c5 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -1,4 +1,5 @@ """Support for Tibber.""" + import logging import aiohttp diff --git a/homeassistant/components/tile/const.py b/homeassistant/components/tile/const.py index eed3eb698ef..50b02cff963 100644 --- a/homeassistant/components/tile/const.py +++ b/homeassistant/components/tile/const.py @@ -1,4 +1,5 @@ """Define Tile constants.""" + import logging DOMAIN = "tile" diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index 36f7ca12b84..43c787b2301 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -1,4 +1,5 @@ """Support for Toon van Eneco devices.""" + import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py index 9caa642b5f4..c6c7c75e0b5 100644 --- a/homeassistant/components/totalconnect/binary_sensor.py +++ b/homeassistant/components/totalconnect/binary_sensor.py @@ -1,4 +1,5 @@ """Interfaces with TotalConnect sensors.""" + import logging from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/tplink_omada/coordinator.py b/homeassistant/components/tplink_omada/coordinator.py index a0f3e6ff9b3..893d2e2778d 100644 --- a/homeassistant/components/tplink_omada/coordinator.py +++ b/homeassistant/components/tplink_omada/coordinator.py @@ -1,4 +1,5 @@ """Generic Omada API coordinator.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/trace/websocket_api.py b/homeassistant/components/trace/websocket_api.py index 6a5280aacf7..f1ea6133d43 100644 --- a/homeassistant/components/trace/websocket_api.py +++ b/homeassistant/components/trace/websocket_api.py @@ -1,4 +1,5 @@ """Websocket API for automation.""" + import json from typing import Any diff --git a/homeassistant/components/tradfri/const.py b/homeassistant/components/tradfri/const.py index 23b97efedd5..e42bb6f5f4d 100644 --- a/homeassistant/components/tradfri/const.py +++ b/homeassistant/components/tradfri/const.py @@ -1,4 +1,5 @@ """Consts used by Tradfri.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/twitch/const.py b/homeassistant/components/twitch/const.py index 22286437eab..fc7c2f73487 100644 --- a/homeassistant/components/twitch/const.py +++ b/homeassistant/components/twitch/const.py @@ -1,4 +1,5 @@ """Const for Twitch.""" + import logging from twitchAPI.twitch import AuthScope diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index 4e1b003a504..f2db6ff1b3c 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -1,4 +1,5 @@ """Support the UPB PIM.""" + import upb_lib from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index 8d47ce3d64a..18a427a40bd 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -1,4 +1,5 @@ """Config flow for UPB PIM integration.""" + import asyncio from contextlib import suppress import logging diff --git a/homeassistant/components/venstar/const.py b/homeassistant/components/venstar/const.py index 31eb5724558..a485adad8e7 100644 --- a/homeassistant/components/venstar/const.py +++ b/homeassistant/components/venstar/const.py @@ -1,4 +1,5 @@ """The venstar component.""" + import logging DOMAIN = "venstar" diff --git a/homeassistant/components/versasense/__init__.py b/homeassistant/components/versasense/__init__.py index d1ca23d82af..f209234f8c2 100644 --- a/homeassistant/components/versasense/__init__.py +++ b/homeassistant/components/versasense/__init__.py @@ -1,4 +1,5 @@ """Support for VersaSense MicroPnP devices.""" + import logging import pyversasense as pyv diff --git a/homeassistant/components/vesync/__init__.py b/homeassistant/components/vesync/__init__.py index 55addd81066..e758636900b 100644 --- a/homeassistant/components/vesync/__init__.py +++ b/homeassistant/components/vesync/__init__.py @@ -1,4 +1,5 @@ """VeSync integration.""" + import logging from pyvesync import VeSync diff --git a/homeassistant/components/vesync/common.py b/homeassistant/components/vesync/common.py index 0e01a593021..0212a7afa57 100644 --- a/homeassistant/components/vesync/common.py +++ b/homeassistant/components/vesync/common.py @@ -1,4 +1,5 @@ """Common utilities for VeSync Component.""" + import logging from typing import Any diff --git a/homeassistant/components/vesync/light.py b/homeassistant/components/vesync/light.py index 040e9d5696d..9b15e635903 100644 --- a/homeassistant/components/vesync/light.py +++ b/homeassistant/components/vesync/light.py @@ -1,4 +1,5 @@ """Support for VeSync bulbs and wall dimmers.""" + import logging from typing import Any diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index e6101b2ba51..1d0c3472d53 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -1,4 +1,5 @@ """Support for VeSync switches.""" + import logging from typing import Any diff --git a/homeassistant/components/vicare/const.py b/homeassistant/components/vicare/const.py index 9f57bb5e5e8..24ab94778e3 100644 --- a/homeassistant/components/vicare/const.py +++ b/homeassistant/components/vicare/const.py @@ -1,4 +1,5 @@ """Constants for the ViCare integration.""" + import enum from homeassistant.const import Platform diff --git a/homeassistant/components/vicare/utils.py b/homeassistant/components/vicare/utils.py index 649b1859442..2019f28a896 100644 --- a/homeassistant/components/vicare/utils.py +++ b/homeassistant/components/vicare/utils.py @@ -1,4 +1,5 @@ """ViCare helpers functions.""" + import logging from PyViCare.PyViCareDevice import Device as PyViCareDevice diff --git a/homeassistant/components/vilfo/config_flow.py b/homeassistant/components/vilfo/config_flow.py index 4fa7fd4ae9b..47e45aecadd 100644 --- a/homeassistant/components/vilfo/config_flow.py +++ b/homeassistant/components/vilfo/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Vilfo Router integration.""" + import logging from vilfo import Client as VilfoClient diff --git a/homeassistant/components/vlc_telnet/const.py b/homeassistant/components/vlc_telnet/const.py index 432de5aa854..e51339bdd1e 100644 --- a/homeassistant/components/vlc_telnet/const.py +++ b/homeassistant/components/vlc_telnet/const.py @@ -1,4 +1,5 @@ """Integration shared constants.""" + import logging DATA_VLC = "vlc" diff --git a/homeassistant/components/vodafone_station/const.py b/homeassistant/components/vodafone_station/const.py index c4828e19951..14cfaabdf7a 100644 --- a/homeassistant/components/vodafone_station/const.py +++ b/homeassistant/components/vodafone_station/const.py @@ -1,4 +1,5 @@ """Vodafone Station constants.""" + import logging _LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/voicerss/tts.py b/homeassistant/components/voicerss/tts.py index 4ac2aae0a71..581f4090657 100644 --- a/homeassistant/components/voicerss/tts.py +++ b/homeassistant/components/voicerss/tts.py @@ -1,4 +1,5 @@ """Support for the voicerss speech service.""" + import asyncio from http import HTTPStatus import logging diff --git a/homeassistant/components/volumio/browse_media.py b/homeassistant/components/volumio/browse_media.py index 019b4e33666..69a568731e1 100644 --- a/homeassistant/components/volumio/browse_media.py +++ b/homeassistant/components/volumio/browse_media.py @@ -1,4 +1,5 @@ """Support for media browsing.""" + import json from homeassistant.components.media_player import ( diff --git a/homeassistant/components/w800rf32/__init__.py b/homeassistant/components/w800rf32/__init__.py index 29701ec82ab..62b9ba810d9 100644 --- a/homeassistant/components/w800rf32/__init__.py +++ b/homeassistant/components/w800rf32/__init__.py @@ -1,4 +1,5 @@ """Support for w800rf32 devices.""" + import logging import voluptuous as vol diff --git a/homeassistant/components/waqi/const.py b/homeassistant/components/waqi/const.py index 2847a29b8ad..c5ffea20b46 100644 --- a/homeassistant/components/waqi/const.py +++ b/homeassistant/components/waqi/const.py @@ -1,4 +1,5 @@ """Constants for the World Air Quality Index (WAQI) integration.""" + import logging DOMAIN = "waqi" diff --git a/homeassistant/components/watson_iot/__init__.py b/homeassistant/components/watson_iot/__init__.py index 5e3a150bed1..8a412f81575 100644 --- a/homeassistant/components/watson_iot/__init__.py +++ b/homeassistant/components/watson_iot/__init__.py @@ -1,4 +1,5 @@ """Support for the IBM Watson IoT Platform.""" + import logging import queue import threading diff --git a/homeassistant/components/watson_tts/tts.py b/homeassistant/components/watson_tts/tts.py index 7adb1b1582f..3cf1582e008 100644 --- a/homeassistant/components/watson_tts/tts.py +++ b/homeassistant/components/watson_tts/tts.py @@ -1,4 +1,5 @@ """Support for IBM Watson TTS integration.""" + import logging from ibm_cloud_sdk_core.authenticators import IAMAuthenticator diff --git a/homeassistant/components/watttime/const.py b/homeassistant/components/watttime/const.py index ce2731e7832..fe517af6d65 100644 --- a/homeassistant/components/watttime/const.py +++ b/homeassistant/components/watttime/const.py @@ -1,4 +1,5 @@ """Constants for the WattTime integration.""" + import logging DOMAIN = "watttime" diff --git a/homeassistant/components/waze_travel_time/__init__.py b/homeassistant/components/waze_travel_time/__init__.py index beaa2ecc69a..9c131f3242c 100644 --- a/homeassistant/components/waze_travel_time/__init__.py +++ b/homeassistant/components/waze_travel_time/__init__.py @@ -1,4 +1,5 @@ """The waze_travel_time component.""" + import asyncio from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/waze_travel_time/helpers.py b/homeassistant/components/waze_travel_time/helpers.py index 0659424429f..c9273c1b774 100644 --- a/homeassistant/components/waze_travel_time/helpers.py +++ b/homeassistant/components/waze_travel_time/helpers.py @@ -1,4 +1,5 @@ """Helpers for Waze Travel Time integration.""" + import logging from pywaze.route_calculator import WazeRouteCalculator, WRCError diff --git a/homeassistant/components/weatherflow_cloud/const.py b/homeassistant/components/weatherflow_cloud/const.py index 73245346b50..43594863e14 100644 --- a/homeassistant/components/weatherflow_cloud/const.py +++ b/homeassistant/components/weatherflow_cloud/const.py @@ -1,4 +1,5 @@ """Constants for the WeatherflowCloud integration.""" + import logging DOMAIN = "weatherflow_cloud" diff --git a/homeassistant/components/webostv/const.py b/homeassistant/components/webostv/const.py index 84675196d86..c20060cae91 100644 --- a/homeassistant/components/webostv/const.py +++ b/homeassistant/components/webostv/const.py @@ -1,4 +1,5 @@ """Constants used for LG webOS Smart TV.""" + import asyncio from aiowebostv import WebOsTvCommandError diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index cfbdb6bdc92..78d22bb79d9 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -1,4 +1,5 @@ """Support for Wireless Sensor Tags.""" + import logging from requests.exceptions import ConnectTimeout, HTTPError diff --git a/homeassistant/components/withings/const.py b/homeassistant/components/withings/const.py index a4a34375459..a87fc8bfe83 100644 --- a/homeassistant/components/withings/const.py +++ b/homeassistant/components/withings/const.py @@ -1,4 +1,5 @@ """Constants used by the Withings component.""" + import logging LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/wolflink/config_flow.py b/homeassistant/components/wolflink/config_flow.py index f8b528f431a..bfa66648b4b 100644 --- a/homeassistant/components/wolflink/config_flow.py +++ b/homeassistant/components/wolflink/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Wolf SmartSet Service integration.""" + import logging from httpcore import ConnectError diff --git a/homeassistant/components/xbox/config_flow.py b/homeassistant/components/xbox/config_flow.py index ef00d1381e5..e1434aac67c 100644 --- a/homeassistant/components/xbox/config_flow.py +++ b/homeassistant/components/xbox/config_flow.py @@ -1,4 +1,5 @@ """Config flow for xbox.""" + import logging from homeassistant.helpers import config_entry_oauth2_flow diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index f7bc1910521..f39daa9b594 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -1,4 +1,5 @@ """Support for Xiaomi Gateways.""" + import asyncio from datetime import timedelta import logging diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index 591e97304db..89071432c2b 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Xiaomi aqara binary sensors.""" + import logging from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index d5a0332cf00..ca1a5851f7e 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -1,4 +1,5 @@ """Config flow to configure Xiaomi Aqara.""" + import logging from socket import gaierror diff --git a/homeassistant/components/xiaomi_aqara/light.py b/homeassistant/components/xiaomi_aqara/light.py index 822d4121c68..49dae825de7 100644 --- a/homeassistant/components/xiaomi_aqara/light.py +++ b/homeassistant/components/xiaomi_aqara/light.py @@ -1,4 +1,5 @@ """Support for Xiaomi Gateway Light.""" + import binascii import logging import struct diff --git a/homeassistant/components/xiaomi_aqara/switch.py b/homeassistant/components/xiaomi_aqara/switch.py index 0ea6a65f68e..b6bd2ca1e6a 100644 --- a/homeassistant/components/xiaomi_aqara/switch.py +++ b/homeassistant/components/xiaomi_aqara/switch.py @@ -1,4 +1,5 @@ """Support for Xiaomi Aqara binary sensors.""" + import logging from typing import Any diff --git a/homeassistant/components/xiaomi_miio/device.py b/homeassistant/components/xiaomi_miio/device.py index 0c87f74a7e6..2f5e6e299e9 100644 --- a/homeassistant/components/xiaomi_miio/device.py +++ b/homeassistant/components/xiaomi_miio/device.py @@ -1,4 +1,5 @@ """Code to handle a Xiaomi Device.""" + import datetime from enum import Enum from functools import partial diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py index e1b3aee9ff4..39e8ce503a4 100644 --- a/homeassistant/components/xiaomi_miio/gateway.py +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -1,4 +1,5 @@ """Code to handle a Xiaomi Gateway.""" + import logging from construct.core import ChecksumError diff --git a/homeassistant/components/xiaomi_miio/humidifier.py b/homeassistant/components/xiaomi_miio/humidifier.py index f2660bef68a..83eb84f62ce 100644 --- a/homeassistant/components/xiaomi_miio/humidifier.py +++ b/homeassistant/components/xiaomi_miio/humidifier.py @@ -1,4 +1,5 @@ """Support for Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier with humidifier entity.""" + import logging import math from typing import Any diff --git a/homeassistant/components/xs1/__init__.py b/homeassistant/components/xs1/__init__.py index 67452ce9426..e24fbc0181e 100644 --- a/homeassistant/components/xs1/__init__.py +++ b/homeassistant/components/xs1/__init__.py @@ -1,4 +1,5 @@ """Support for the EZcontrol XS1 gateway.""" + import asyncio import logging diff --git a/homeassistant/components/yale_smart_alarm/const.py b/homeassistant/components/yale_smart_alarm/const.py index 71b34a3011a..58126449e53 100644 --- a/homeassistant/components/yale_smart_alarm/const.py +++ b/homeassistant/components/yale_smart_alarm/const.py @@ -1,4 +1,5 @@ """Yale integration constants.""" + import logging from yalesmartalarmclient.client import ( diff --git a/homeassistant/components/yandextts/tts.py b/homeassistant/components/yandextts/tts.py index ca4f8400022..1a5fc4a7903 100644 --- a/homeassistant/components/yandextts/tts.py +++ b/homeassistant/components/yandextts/tts.py @@ -1,4 +1,5 @@ """Support for the yandex speechkit tts service.""" + import asyncio from http import HTTPStatus import logging diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py index 88779e03b6c..9993272d510 100644 --- a/homeassistant/components/yeelight/binary_sensor.py +++ b/homeassistant/components/yeelight/binary_sensor.py @@ -1,4 +1,5 @@ """Sensor platform support for yeelight.""" + import logging from homeassistant.components.binary_sensor import BinarySensorEntity diff --git a/homeassistant/components/youtube/const.py b/homeassistant/components/youtube/const.py index 63c4480c007..a663c487d0a 100644 --- a/homeassistant/components/youtube/const.py +++ b/homeassistant/components/youtube/const.py @@ -1,4 +1,5 @@ """Constants for YouTube integration.""" + import logging DEFAULT_ACCESS = ["https://www.googleapis.com/auth/youtube.readonly"] diff --git a/homeassistant/components/zerproc/config_flow.py b/homeassistant/components/zerproc/config_flow.py index a9fd20ce241..f4401c6c390 100644 --- a/homeassistant/components/zerproc/config_flow.py +++ b/homeassistant/components/zerproc/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Zerproc.""" + import logging import pyzerproc diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 34ba0d33482..ef603a4ea71 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -1,4 +1,5 @@ """Support for Zigbee Home Automation devices.""" + import asyncio import contextlib import copy diff --git a/homeassistant/components/zha/backup.py b/homeassistant/components/zha/backup.py index e125a8085f6..25d5a83b6a4 100644 --- a/homeassistant/components/zha/backup.py +++ b/homeassistant/components/zha/backup.py @@ -1,4 +1,5 @@ """Backup platform for the ZHA integration.""" + import logging from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index ccfb5434154..fa719075c05 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -1,4 +1,5 @@ """Locks on Zigbee Home Automation networks.""" + import functools from typing import Any diff --git a/homeassistant/components/zoneminder/__init__.py b/homeassistant/components/zoneminder/__init__.py index 1ff73048440..0510ff58d35 100644 --- a/homeassistant/components/zoneminder/__init__.py +++ b/homeassistant/components/zoneminder/__init__.py @@ -1,4 +1,5 @@ """Support for ZoneMinder.""" + import logging from requests.exceptions import ConnectionError as RequestsConnectionError diff --git a/homeassistant/components/zwave_me/__init__.py b/homeassistant/components/zwave_me/__init__.py index e35f55d6fda..dae065b5829 100644 --- a/homeassistant/components/zwave_me/__init__.py +++ b/homeassistant/components/zwave_me/__init__.py @@ -1,4 +1,5 @@ """The Z-Wave-Me WS integration.""" + import logging from zwave_me_ws import ZWaveMe, ZWaveMeData diff --git a/homeassistant/components/zwave_me/switch.py b/homeassistant/components/zwave_me/switch.py index c9a824c5e0d..4c11f079b12 100644 --- a/homeassistant/components/zwave_me/switch.py +++ b/homeassistant/components/zwave_me/switch.py @@ -1,4 +1,5 @@ """Representation of a switchBinary.""" + import logging from typing import Any From 32f3f46542df303501db8153ccf242054c08fd2b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:16:21 +0100 Subject: [PATCH 0561/1691] Add empty line after module docstring (2) [tests.components] (#112737) --- tests/components/accuweather/test_system_health.py | 1 + tests/components/adguard/test_config_flow.py | 1 + tests/components/aemet/test_weather.py | 1 + tests/components/agent_dvr/test_config_flow.py | 1 + tests/components/air_quality/test_air_quality.py | 1 + tests/components/airly/test_system_health.py | 1 + tests/components/airnow/conftest.py | 1 + tests/components/airthings_ble/test_sensor.py | 1 + tests/components/airzone/test_climate.py | 1 + tests/components/alarm_control_panel/test_device_action.py | 1 + tests/components/alarm_control_panel/test_device_condition.py | 1 + tests/components/alarm_control_panel/test_reproduce_state.py | 1 + tests/components/alarm_control_panel/test_significant_change.py | 1 + tests/components/alert/test_reproduce_state.py | 1 + tests/components/alexa/test_config.py | 1 + tests/components/alexa/test_state_report.py | 1 + tests/components/ambient_station/conftest.py | 1 + tests/components/apcupsd/test_init.py | 1 + tests/components/api/test_init.py | 1 + tests/components/arcam_fmj/test_device_trigger.py | 1 + tests/components/assist_pipeline/test_init.py | 1 + tests/components/assist_pipeline/test_vad.py | 1 + tests/components/assist_pipeline/test_websocket.py | 1 + tests/components/atag/conftest.py | 1 + tests/components/august/test_binary_sensor.py | 1 + tests/components/august/test_lock.py | 1 + tests/components/auth/conftest.py | 1 + tests/components/auth/test_indieauth.py | 1 + tests/components/automation/test_blueprint.py | 1 + tests/components/automation/test_init.py | 1 + tests/components/automation/test_reproduce_state.py | 1 + tests/components/aws/test_init.py | 1 + tests/components/axis/test_binary_sensor.py | 1 + tests/components/axis/test_diagnostics.py | 1 + tests/components/azure_event_hub/test_config_flow.py | 1 + tests/components/bayesian/test_binary_sensor.py | 1 + tests/components/blebox/test_button.py | 1 + tests/components/blebox/test_climate.py | 1 + tests/components/blebox/test_cover.py | 1 + tests/components/blebox/test_init.py | 1 + tests/components/blebox/test_light.py | 1 + tests/components/blebox/test_sensor.py | 1 + tests/components/blebox/test_switch.py | 1 + tests/components/blueprint/test_default_blueprints.py | 1 + tests/components/blueprint/test_importer.py | 1 + tests/components/blueprint/test_models.py | 1 + tests/components/blueprint/test_schemas.py | 1 + tests/components/bluetooth/test_api.py | 1 + tests/components/bluetooth/test_init.py | 1 + tests/components/bluetooth/test_scanner.py | 1 + tests/components/bmw_connected_drive/test_diagnostics.py | 1 + tests/components/broadlink/test_config_flow.py | 1 + tests/components/broadlink/test_helpers.py | 1 + tests/components/bthome/test_device_trigger.py | 1 + tests/components/buienradar/test_camera.py | 1 + tests/components/button/test_device_action.py | 1 + tests/components/cert_expiry/test_config_flow.py | 1 + tests/components/clicksend_tts/test_notify.py | 1 + tests/components/climate/test_device_action.py | 1 + tests/components/climate/test_device_condition.py | 1 + tests/components/climate/test_device_trigger.py | 1 + tests/components/climate/test_reproduce_state.py | 1 + tests/components/climate/test_significant_change.py | 1 + tests/components/cloud/test_account_link.py | 1 + tests/components/cloud/test_alexa_config.py | 1 + tests/components/cloud/test_assist_pipeline.py | 1 + tests/components/cloud/test_system_health.py | 1 + tests/components/cloudflare/test_config_flow.py | 1 + tests/components/coinbase/test_config_flow.py | 1 + tests/components/color_extractor/conftest.py | 1 + tests/components/color_extractor/test_service.py | 1 + tests/components/compensation/test_sensor.py | 1 + tests/components/config/test_area_registry.py | 1 + tests/components/config/test_auth.py | 1 + tests/components/config/test_device_registry.py | 1 + tests/components/config/test_entity_registry.py | 1 + tests/components/config/test_floor_registry.py | 1 + tests/components/config/test_label_registry.py | 1 + tests/components/counter/test_init.py | 1 + tests/components/counter/test_reproduce_state.py | 1 + tests/components/cover/test_device_action.py | 1 + tests/components/cover/test_device_condition.py | 1 + tests/components/cover/test_reproduce_state.py | 1 + tests/components/cover/test_significant_change.py | 1 + tests/components/deconz/test_config_flow.py | 1 + tests/components/deconz/test_init.py | 1 + tests/components/demo/test_init.py | 1 + tests/components/demo/test_notify.py | 1 + tests/components/demo/test_weather.py | 1 + tests/components/derivative/test_init.py | 1 + tests/components/devialet/test_diagnostics.py | 1 + tests/components/device_tracker/test_device_condition.py | 1 + tests/components/device_tracker/test_device_trigger.py | 1 + tests/components/dialogflow/test_init.py | 1 + tests/components/directv/test_config_flow.py | 1 + tests/components/discord/test_config_flow.py | 1 + tests/components/discord/test_notify.py | 1 + tests/components/discovergy/const.py | 1 + tests/components/discovergy/test_diagnostics.py | 1 + tests/components/discovergy/test_system_health.py | 1 + tests/components/dsmr/conftest.py | 1 + tests/components/dsmr/test_mbus_migration.py | 1 + tests/components/dsmr/test_sensor.py | 1 + tests/components/dte_energy_bridge/test_sensor.py | 1 + tests/components/eafm/test_sensor.py | 1 + tests/components/ecobee/test_climate.py | 1 + tests/components/ecobee/test_util.py | 1 + tests/components/ecovacs/util.py | 1 + tests/components/elgato/test_sensor.py | 1 + tests/components/elmax/conftest.py | 1 + tests/components/emulated_hue/test_hue_api.py | 1 + tests/components/emulated_kasa/test_init.py | 1 + tests/components/energy/test_sensor.py | 1 + tests/components/esphome/test_entity.py | 1 + tests/components/esphome/test_manager.py | 1 + tests/components/evil_genius_labs/conftest.py | 1 + tests/components/evil_genius_labs/test_diagnostics.py | 1 + tests/components/evil_genius_labs/test_init.py | 1 + tests/components/fan/test_device_action.py | 1 + tests/components/fan/test_device_condition.py | 1 + tests/components/fan/test_init.py | 1 + tests/components/fan/test_reproduce_state.py | 1 + tests/components/fan/test_significant_change.py | 1 + tests/components/fido/test_sensor.py | 1 + tests/components/file/test_notify.py | 1 + tests/components/filesize/test_sensor.py | 1 + tests/components/flexit_bacnet/test_config_flow.py | 1 + tests/components/flo/test_services.py | 1 + tests/components/folder/test_sensor.py | 1 + tests/components/folder_watcher/test_init.py | 1 + tests/components/freebox/conftest.py | 1 + tests/components/freebox/test_router.py | 1 + tests/components/freedns/test_init.py | 1 + tests/components/freedompro/test_init.py | 1 + tests/components/fritz/conftest.py | 1 + tests/components/fritz/test_config_flow.py | 1 + tests/components/fritzbox/test_config_flow.py | 1 + tests/components/fully_kiosk/test_init.py | 1 + tests/components/gdacs/conftest.py | 1 + tests/components/gdacs/test_geo_location.py | 1 + tests/components/generic/test_camera.py | 1 + tests/components/generic/test_config_flow.py | 1 + tests/components/generic/test_diagnostics.py | 1 + tests/components/generic_hygrostat/test_humidifier.py | 1 + tests/components/generic_thermostat/test_climate.py | 1 + tests/components/geo_location/test_init.py | 1 + tests/components/geo_location/test_trigger.py | 1 + tests/components/geonetnz_quakes/conftest.py | 1 + tests/components/geonetnz_quakes/test_geo_location.py | 1 + tests/components/geonetnz_quakes/test_sensor.py | 1 + tests/components/geonetnz_volcano/conftest.py | 1 + tests/components/gios/test_config_flow.py | 1 + tests/components/gios/test_init.py | 1 + tests/components/gios/test_system_health.py | 1 + tests/components/github/test_init.py | 1 + tests/components/github/test_sensor.py | 1 + tests/components/google_assistant/test_data_redaction.py | 1 + tests/components/google_assistant/test_smart_home.py | 1 + tests/components/google_mail/test_init.py | 1 + tests/components/google_travel_time/test_config_flow.py | 1 + tests/components/graphite/test_init.py | 1 + tests/components/gree/common.py | 1 + tests/components/group/conftest.py | 1 + tests/components/group/test_cover.py | 1 + tests/components/group/test_fan.py | 1 + tests/components/group/test_light.py | 1 + tests/components/group/test_media_player.py | 1 + tests/components/group/test_switch.py | 1 + tests/components/harmony/test_subscriber.py | 1 + tests/components/hassio/conftest.py | 1 + tests/components/hassio/test_binary_sensor.py | 1 + tests/components/hassio/test_system_health.py | 1 + tests/components/hassio/test_websocket_api.py | 1 + tests/components/hddtemp/test_sensor.py | 1 + tests/components/heos/test_init.py | 1 + tests/components/heos/test_media_player.py | 1 + tests/components/here_travel_time/conftest.py | 1 + tests/components/history/conftest.py | 1 + tests/components/history/test_websocket_api.py | 1 + tests/components/hko/conftest.py | 1 + tests/components/hlk_sw16/test_config_flow.py | 1 + tests/components/homeassistant/test_exposed_entities.py | 1 + tests/components/homeassistant/test_init.py | 1 + tests/components/homeassistant/triggers/test_event.py | 1 + tests/components/homekit/test_aidmanager.py | 1 + tests/components/homekit/test_type_cameras.py | 1 + tests/components/homekit/test_type_locks.py | 1 + tests/components/homekit/test_type_media_players.py | 1 + tests/components/homekit_controller/conftest.py | 1 + tests/components/homekit_controller/test_camera.py | 1 + tests/components/homekit_controller/test_config_flow.py | 1 + tests/components/homematic/test_notify.py | 1 + tests/components/homematicip_cloud/helper.py | 1 + tests/components/homematicip_cloud/test_climate.py | 1 + tests/components/honeywell/test_climate.py | 1 + tests/components/http/conftest.py | 1 + tests/components/http/test_init.py | 1 + tests/components/http/test_security_filter.py | 1 + tests/components/hue/conftest.py | 1 + tests/components/hue/test_bridge.py | 1 + tests/components/humidifier/test_device_action.py | 1 + tests/components/humidifier/test_device_condition.py | 1 + tests/components/humidifier/test_device_trigger.py | 1 + tests/components/humidifier/test_intent.py | 1 + tests/components/humidifier/test_reproduce_state.py | 1 + tests/components/humidifier/test_significant_change.py | 1 + tests/components/hvv_departures/test_config_flow.py | 1 + tests/components/iaqualink/conftest.py | 1 + tests/components/iaqualink/test_init.py | 1 + tests/components/ibeacon/test_init.py | 1 + tests/components/ign_sismologia/test_geo_location.py | 1 + tests/components/image/test_init.py | 1 + tests/components/image_upload/test_init.py | 1 + tests/components/imap/test_config_flow.py | 1 + tests/components/imap/test_init.py | 1 + tests/components/input_boolean/test_init.py | 1 + tests/components/input_button/test_init.py | 1 + tests/components/input_datetime/test_init.py | 1 + tests/components/input_datetime/test_reproduce_state.py | 1 + tests/components/input_number/test_reproduce_state.py | 1 + tests/components/input_select/test_reproduce_state.py | 1 + tests/components/input_text/test_reproduce_state.py | 1 + tests/components/insteon/test_api_aldb.py | 1 + tests/components/insteon/test_api_device.py | 1 + tests/components/insteon/test_api_properties.py | 1 + tests/components/insteon/test_api_scenes.py | 1 + tests/components/insteon/test_init.py | 1 + tests/components/integration/test_init.py | 1 + tests/components/iotawatt/test_init.py | 1 + tests/components/ipma/test_system_health.py | 1 + tests/components/ipma/test_weather.py | 1 + tests/components/ipp/test_config_flow.py | 1 + tests/components/iqvia/conftest.py | 1 + tests/components/isy994/test_config_flow.py | 1 + tests/components/isy994/test_system_health.py | 1 + tests/components/kira/test_init.py | 1 + tests/components/kitchen_sink/test_init.py | 1 + tests/components/knx/test_device_trigger.py | 1 + tests/components/knx/test_events.py | 1 + tests/components/knx/test_number.py | 1 + tests/components/knx/test_select.py | 1 + tests/components/kodi/test_device_trigger.py | 1 + tests/components/kraken/const.py | 1 + tests/components/lametric/test_sensor.py | 1 + tests/components/landisgyr_heat_meter/test_sensor.py | 1 + tests/components/laundrify/conftest.py | 1 + tests/components/lcn/conftest.py | 1 + tests/components/light/test_device_action.py | 1 + tests/components/light/test_reproduce_state.py | 1 + tests/components/lock/test_device_action.py | 1 + tests/components/lock/test_device_condition.py | 1 + tests/components/lock/test_reproduce_state.py | 1 + tests/components/logbook/test_init.py | 1 + tests/components/logbook/test_websocket_api.py | 1 + tests/components/logger/conftest.py | 1 + tests/components/logger/test_websocket_api.py | 1 + tests/components/logi_circle/test_config_flow.py | 1 + tests/components/lovelace/test_resources.py | 1 + tests/components/mailgun/test_init.py | 1 + tests/components/media_extractor/test_init.py | 1 + tests/components/media_player/test_async_helpers.py | 1 + tests/components/media_player/test_device_condition.py | 1 + tests/components/media_player/test_reproduce_state.py | 1 + tests/components/media_player/test_significant_change.py | 1 + tests/components/melissa/test_climate.py | 1 + tests/components/met/test_init.py | 1 + tests/components/met_eireann/test_weather.py | 1 + tests/components/metoffice/test_config_flow.py | 1 + tests/components/metoffice/test_sensor.py | 1 + tests/components/metoffice/test_weather.py | 1 + tests/components/mfi/test_switch.py | 1 + tests/components/microbees/conftest.py | 1 + tests/components/min_max/test_init.py | 1 + tests/components/min_max/test_sensor.py | 1 + tests/components/minecraft_server/conftest.py | 1 + tests/components/minio/test_minio.py | 1 + tests/components/mochad/test_light.py | 1 + tests/components/mochad/test_switch.py | 1 + tests/components/modbus/conftest.py | 1 + tests/components/modbus/test_binary_sensor.py | 1 + tests/components/modbus/test_climate.py | 1 + tests/components/modbus/test_sensor.py | 1 + tests/components/mold_indicator/test_sensor.py | 1 + tests/components/motion_blinds/test_config_flow.py | 1 + tests/components/motioneye/test_camera.py | 1 + tests/components/motioneye/test_media_source.py | 1 + tests/components/motioneye/test_sensor.py | 1 + tests/components/motioneye/test_switch.py | 1 + tests/components/motioneye/test_web_hooks.py | 1 + tests/components/motionmount/test_config_flow.py | 1 + tests/components/mqtt/test_alarm_control_panel.py | 1 + tests/components/mqtt/test_binary_sensor.py | 1 + tests/components/mqtt/test_button.py | 1 + tests/components/mqtt/test_climate.py | 1 + tests/components/mqtt/test_device_trigger.py | 1 + tests/components/mqtt/test_diagnostics.py | 1 + tests/components/mqtt/test_discovery.py | 1 + tests/components/mqtt/test_event.py | 1 + tests/components/mqtt/test_fan.py | 1 + tests/components/mqtt/test_humidifier.py | 1 + tests/components/mqtt/test_init.py | 1 + tests/components/mqtt/test_lawn_mower.py | 1 + tests/components/mqtt/test_light.py | 1 + tests/components/mqtt/test_light_json.py | 1 + tests/components/mqtt/test_light_template.py | 1 + tests/components/mqtt/test_number.py | 1 + tests/components/mqtt/test_scene.py | 1 + tests/components/mqtt/test_sensor.py | 1 + tests/components/mqtt/test_siren.py | 1 + tests/components/mqtt/test_switch.py | 1 + tests/components/mqtt/test_update.py | 1 + tests/components/mqtt/test_water_heater.py | 1 + tests/components/mqtt_eventstream/test_init.py | 1 + tests/components/mqtt_room/test_sensor.py | 1 + tests/components/mythicbeastsdns/test_init.py | 1 + tests/components/myuplink/test_init.py | 1 + tests/components/nam/test_diagnostics.py | 1 + tests/components/nest/test_api.py | 1 + tests/components/nest/test_camera.py | 1 + tests/components/nest/test_init.py | 1 + tests/components/netatmo/test_device_trigger.py | 1 + tests/components/netatmo/test_media_source.py | 1 + tests/components/nexia/test_init.py | 1 + tests/components/nextdns/test_system_health.py | 1 + tests/components/nibe_heatpump/test_coordinator.py | 1 + tests/components/notify/test_init.py | 1 + .../components/nsw_rural_fire_service_feed/test_geo_location.py | 1 + tests/components/number/test_device_action.py | 1 + tests/components/number/test_reproduce_state.py | 1 + tests/components/number/test_significant_change.py | 1 + tests/components/nws/test_sensor.py | 1 + tests/components/onboarding/test_views.py | 1 + tests/components/onvif/test_config_flow.py | 1 + tests/components/openexchangerates/test_config_flow.py | 1 + tests/components/openhardwaremonitor/test_sensor.py | 1 + tests/components/opower/conftest.py | 1 + tests/components/otbr/test_config_flow.py | 1 + tests/components/otbr/test_init.py | 1 + tests/components/owntracks/test_device_tracker.py | 1 + tests/components/owntracks/test_init.py | 1 + tests/components/p1_monitor/conftest.py | 1 + tests/components/panel_iframe/test_init.py | 1 + tests/components/person/conftest.py | 1 + tests/components/philips_js/test_binary_sensor.py | 1 + tests/components/philips_js/test_device_trigger.py | 1 + tests/components/pi_hole/test_init.py | 1 + tests/components/picnic/test_sensor.py | 1 + tests/components/pilight/test_sensor.py | 1 + tests/components/plex/test_config_flow.py | 1 + tests/components/plex/test_device_handling.py | 1 + tests/components/plex/test_init.py | 1 + tests/components/plex/test_server.py | 1 + tests/components/plex/test_update.py | 1 + tests/components/prosegur/test_camera.py | 1 + tests/components/proximity/conftest.py | 1 + tests/components/python_script/test_init.py | 1 + tests/components/qbittorrent/test_config_flow.py | 1 + tests/components/qld_bushfire/test_geo_location.py | 1 + tests/components/qwikswitch/test_init.py | 1 + tests/components/radarr/test_binary_sensor.py | 1 + tests/components/radarr/test_init.py | 1 + tests/components/radarr/test_sensor.py | 1 + tests/components/rainforest_raven/test_init.py | 1 + tests/components/rainforest_raven/test_sensor.py | 1 + tests/components/rainmachine/conftest.py | 1 + tests/components/recorder/test_filters_with_entityfilter.py | 1 + .../recorder/test_filters_with_entityfilter_schema_37.py | 1 + tests/components/recorder/test_migration_from_schema_32.py | 1 + tests/components/recorder/test_pool.py | 1 + tests/components/recorder/test_websocket_api.py | 1 + tests/components/reddit/test_sensor.py | 1 + tests/components/remote/test_device_action.py | 1 + tests/components/remote/test_init.py | 1 + tests/components/remote/test_reproduce_state.py | 1 + tests/components/renault/test_diagnostics.py | 1 + tests/components/rest_command/test_init.py | 1 + tests/components/rfxtrx/test_binary_sensor.py | 1 + tests/components/rfxtrx/test_config_flow.py | 1 + tests/components/rfxtrx/test_sensor.py | 1 + tests/components/ring/test_light.py | 1 + tests/components/ring/test_sensor.py | 1 + tests/components/ring/test_siren.py | 1 + tests/components/ring/test_switch.py | 1 + tests/components/risco/test_init.py | 1 + tests/components/rituals_perfume_genie/test_select.py | 1 + tests/components/rmvtransport/test_sensor.py | 1 + tests/components/roborock/test_image.py | 1 + tests/components/roku/test_config_flow.py | 1 + tests/components/samsungtv/test_device_trigger.py | 1 + tests/components/scene/test_init.py | 1 + tests/components/screenlogic/conftest.py | 1 + tests/components/script/test_blueprint.py | 1 + tests/components/script/test_init.py | 1 + tests/components/search/test_init.py | 1 + tests/components/select/test_device_action.py | 1 + tests/components/select/test_reproduce_state.py | 1 + tests/components/sensor/test_device_condition.py | 1 + tests/components/sensor/test_helpers.py | 1 + tests/components/sensor/test_significant_change.py | 1 + tests/components/sentry/test_config_flow.py | 1 + tests/components/sentry/test_init.py | 1 + tests/components/sfr_box/test_config_flow.py | 1 + tests/components/signal_messenger/test_notify.py | 1 + tests/components/simplisafe/conftest.py | 1 + tests/components/simplisafe/test_config_flow.py | 1 + tests/components/smartthings/conftest.py | 1 + tests/components/smarttub/test_climate.py | 1 + tests/components/smarttub/test_light.py | 1 + tests/components/smarttub/test_sensor.py | 1 + tests/components/smarttub/test_switch.py | 1 + tests/components/smhi/conftest.py | 1 + tests/components/snips/test_init.py | 1 + tests/components/songpal/test_config_flow.py | 1 + tests/components/sonos/test_init.py | 1 + tests/components/sonos/test_plex_playback.py | 1 + tests/components/soundtouch/conftest.py | 1 + tests/components/srp_energy/test_sensor.py | 1 + tests/components/starline/test_config_flow.py | 1 + tests/components/starlink/patchers.py | 1 + tests/components/steam_online/test_init.py | 1 + tests/components/stream/test_init.py | 1 + tests/components/stream/test_ll_hls.py | 1 + tests/components/stream/test_recorder.py | 1 + tests/components/stream/test_worker.py | 1 + tests/components/subaru/test_diagnostics.py | 1 + tests/components/surepetcare/test_lock.py | 1 + tests/components/switch/test_device_action.py | 1 + tests/components/switch/test_init.py | 1 + tests/components/switch/test_light.py | 1 + tests/components/switch/test_reproduce_state.py | 1 + tests/components/switchbee/test_config_flow.py | 1 + tests/components/switcher_kis/test_sensor.py | 1 + tests/components/syncthru/test_config_flow.py | 1 + tests/components/tado/test_service.py | 1 + tests/components/tag/test_trigger.py | 1 + tests/components/tami4/test_init.py | 1 + tests/components/tasmota/test_binary_sensor.py | 1 + tests/components/tasmota/test_common.py | 1 + tests/components/tasmota/test_cover.py | 1 + tests/components/tasmota/test_device_trigger.py | 1 + tests/components/tasmota/test_discovery.py | 1 + tests/components/tasmota/test_fan.py | 1 + tests/components/tasmota/test_init.py | 1 + tests/components/tasmota/test_light.py | 1 + tests/components/tasmota/test_mixins.py | 1 + tests/components/tasmota/test_sensor.py | 1 + tests/components/tasmota/test_switch.py | 1 + tests/components/template/conftest.py | 1 + tests/components/template/test_alarm_control_panel.py | 1 + tests/components/template/test_button.py | 1 + tests/components/template/test_fan.py | 1 + tests/components/template/test_light.py | 1 + tests/components/template/test_lock.py | 1 + tests/components/template/test_switch.py | 1 + tests/components/template/test_template_entity.py | 1 + tests/components/template/test_vacuum.py | 1 + tests/components/tessie/test_binary_sensors.py | 1 + tests/components/text/test_device_action.py | 1 + tests/components/text/test_reproduce_state.py | 1 + tests/components/thread/test_dataset_store.py | 1 + tests/components/threshold/test_init.py | 1 + tests/components/tibber/conftest.py | 1 + tests/components/tibber/test_common.py | 1 + tests/components/tile/conftest.py | 1 + tests/components/timer/test_reproduce_state.py | 1 + tests/components/tomorrowio/conftest.py | 1 + tests/components/tplink/test_diagnostics.py | 1 + tests/components/trace/test_websocket_api.py | 1 + tests/components/tradfri/test_util.py | 1 + tests/components/tts/test_init.py | 1 + tests/components/twentemilieu/test_sensor.py | 1 + tests/components/twitch/test_init.py | 1 + tests/components/uk_transport/test_sensor.py | 1 + tests/components/unifi/test_config_flow.py | 1 + tests/components/uptime/test_sensor.py | 1 + tests/components/uptimerobot/test_diagnostics.py | 1 + tests/components/usb/test_init.py | 1 + tests/components/usgs_earthquakes_feed/test_geo_location.py | 1 + tests/components/vacuum/test_device_action.py | 1 + tests/components/vacuum/test_device_condition.py | 1 + tests/components/vacuum/test_reproduce_state.py | 1 + tests/components/vacuum/test_significant_change.py | 1 + tests/components/vallox/test_number.py | 1 + tests/components/venstar/test_config_flow.py | 1 + tests/components/vesync/common.py | 1 + tests/components/vesync/test_fan.py | 1 + tests/components/vesync/test_light.py | 1 + tests/components/vesync/test_sensor.py | 1 + tests/components/vesync/test_switch.py | 1 + tests/components/vizio/test_config_flow.py | 1 + tests/components/voip/test_sip.py | 1 + tests/components/voip/test_voip.py | 1 + tests/components/vulcan/test_config_flow.py | 1 + tests/components/vultr/conftest.py | 1 + tests/components/vultr/test_binary_sensor.py | 1 + tests/components/vultr/test_sensor.py | 1 + tests/components/wallbox/conftest.py | 1 + tests/components/wallbox/test_init.py | 1 + tests/components/wallbox/test_lock.py | 1 + tests/components/wallbox/test_number.py | 1 + tests/components/wallbox/test_switch.py | 1 + tests/components/waqi/test_config_flow.py | 1 + tests/components/waqi/test_sensor.py | 1 + tests/components/water_heater/test_device_action.py | 1 + tests/components/water_heater/test_reproduce_state.py | 1 + tests/components/water_heater/test_significant_change.py | 1 + tests/components/watttime/conftest.py | 1 + tests/components/waze_travel_time/test_config_flow.py | 1 + tests/components/waze_travel_time/test_sensor.py | 1 + tests/components/weatherflow/conftest.py | 1 + tests/components/weatherflow_cloud/test_config_flow.py | 1 + tests/components/webmin/test_sensor.py | 1 + tests/components/webostv/test_config_flow.py | 1 + tests/components/webostv/test_device_trigger.py | 1 + tests/components/websocket_api/conftest.py | 1 + tests/components/websocket_api/test_commands.py | 1 + tests/components/websocket_api/test_connection.py | 1 + tests/components/websocket_api/test_http.py | 1 + tests/components/websocket_api/test_messages.py | 1 + tests/components/wemo/conftest.py | 1 + tests/components/wemo/entity_test_helpers.py | 1 + tests/components/wemo/test_device_trigger.py | 1 + tests/components/wemo/test_fan.py | 1 + tests/components/wemo/test_init.py | 1 + tests/components/wemo/test_light_dimmer.py | 1 + tests/components/wemo/test_switch.py | 1 + tests/components/wemo/test_wemo_device.py | 1 + tests/components/wiffi/test_config_flow.py | 1 + tests/components/wilight/test_config_flow.py | 1 + tests/components/wiz/test_init.py | 1 + tests/components/wled/test_binary_sensor.py | 1 + tests/components/wled/test_coordinator.py | 1 + tests/components/wled/test_init.py | 1 + tests/components/wled/test_light.py | 1 + tests/components/wled/test_number.py | 1 + tests/components/wled/test_select.py | 1 + tests/components/wled/test_switch.py | 1 + tests/components/worldclock/test_sensor.py | 1 + tests/components/xiaomi_ble/test_device_trigger.py | 1 + tests/components/xiaomi_ble/test_event.py | 1 + tests/components/yalexs_ble/test_config_flow.py | 1 + .../components/yandex_transport/test_yandex_transport_sensor.py | 1 + tests/components/yeelight/conftest.py | 1 + tests/components/yolink/test_device_trigger.py | 1 + tests/components/youtube/test_init.py | 1 + tests/components/zha/common.py | 1 + tests/components/zha/test_config_flow.py | 1 + tests/components/zha/test_cover.py | 1 + tests/components/zha/test_gateway.py | 1 + tests/components/zha/test_helpers.py | 1 + tests/components/zha/test_init.py | 1 + tests/components/zone/test_trigger.py | 1 + tests/components/zwave_js/conftest.py | 1 + .../scripts/test_convert_device_diagnostics_to_fixture.py | 1 + tests/components/zwave_js/test_button.py | 1 + tests/components/zwave_js/test_climate.py | 1 + tests/components/zwave_js/test_config_flow.py | 1 + tests/components/zwave_js/test_config_validation.py | 1 + tests/components/zwave_js/test_cover.py | 1 + tests/components/zwave_js/test_diagnostics.py | 1 + tests/components/zwave_js/test_discovery.py | 1 + tests/components/zwave_js/test_fan.py | 1 + tests/components/zwave_js/test_helpers.py | 1 + tests/components/zwave_js/test_init.py | 1 + tests/components/zwave_js/test_lock.py | 1 + tests/components/zwave_js/test_migrate.py | 1 + tests/components/zwave_js/test_sensor.py | 1 + tests/components/zwave_js/test_switch.py | 1 + tests/components/zwave_js/test_update.py | 1 + 569 files changed, 569 insertions(+) diff --git a/tests/components/accuweather/test_system_health.py b/tests/components/accuweather/test_system_health.py index 34f5b761ccb..6321071eaa5 100644 --- a/tests/components/accuweather/test_system_health.py +++ b/tests/components/accuweather/test_system_health.py @@ -1,4 +1,5 @@ """Test AccuWeather system health.""" + import asyncio from unittest.mock import Mock diff --git a/tests/components/adguard/test_config_flow.py b/tests/components/adguard/test_config_flow.py index f62172ebfad..3f12dd1508a 100644 --- a/tests/components/adguard/test_config_flow.py +++ b/tests/components/adguard/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the AdGuard Home config flow.""" + import aiohttp from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index 595275427f4..88427a5a38f 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -1,4 +1,5 @@ """The sensor tests for the AEMET OpenData platform.""" + import datetime from unittest.mock import patch diff --git a/tests/components/agent_dvr/test_config_flow.py b/tests/components/agent_dvr/test_config_flow.py index 9d1be278ada..958ec97a3ca 100644 --- a/tests/components/agent_dvr/test_config_flow.py +++ b/tests/components/agent_dvr/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Agent DVR config flow.""" + import pytest from homeassistant import data_entry_flow diff --git a/tests/components/air_quality/test_air_quality.py b/tests/components/air_quality/test_air_quality.py index faaebda0aae..7bc21dee03c 100644 --- a/tests/components/air_quality/test_air_quality.py +++ b/tests/components/air_quality/test_air_quality.py @@ -1,4 +1,5 @@ """The tests for the Air Quality component.""" + import pytest from homeassistant.components.air_quality import ATTR_N2O, ATTR_OZONE, ATTR_PM_10 diff --git a/tests/components/airly/test_system_health.py b/tests/components/airly/test_system_health.py index fa26f6ccd0c..4ae94ca280c 100644 --- a/tests/components/airly/test_system_health.py +++ b/tests/components/airly/test_system_health.py @@ -1,4 +1,5 @@ """Test Airly system health.""" + import asyncio from unittest.mock import Mock diff --git a/tests/components/airnow/conftest.py b/tests/components/airnow/conftest.py index 0356c6f3395..a374168df6a 100644 --- a/tests/components/airnow/conftest.py +++ b/tests/components/airnow/conftest.py @@ -1,4 +1,5 @@ """Define fixtures for AirNow tests.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/airthings_ble/test_sensor.py b/tests/components/airthings_ble/test_sensor.py index 1bf036b735d..eee4de263d6 100644 --- a/tests/components/airthings_ble/test_sensor.py +++ b/tests/components/airthings_ble/test_sensor.py @@ -1,4 +1,5 @@ """Test the Airthings Wave sensor.""" + import logging from homeassistant.components.airthings_ble.const import DOMAIN diff --git a/tests/components/airzone/test_climate.py b/tests/components/airzone/test_climate.py index f33d1a8b28a..07d7f3127fa 100644 --- a/tests/components/airzone/test_climate.py +++ b/tests/components/airzone/test_climate.py @@ -1,4 +1,5 @@ """The climate tests for the Airzone platform.""" + import copy from unittest.mock import patch diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index 08ccef37336..ff999a0a273 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Alarm control panel device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/alarm_control_panel/test_device_condition.py b/tests/components/alarm_control_panel/test_device_condition.py index 6e85c94379f..72ea9530fbd 100644 --- a/tests/components/alarm_control_panel/test_device_condition.py +++ b/tests/components/alarm_control_panel/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Alarm control panel device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/alarm_control_panel/test_reproduce_state.py b/tests/components/alarm_control_panel/test_reproduce_state.py index 61bb8f18397..c7984b0793e 100644 --- a/tests/components/alarm_control_panel/test_reproduce_state.py +++ b/tests/components/alarm_control_panel/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Alarm control panel.""" + import pytest from homeassistant.const import ( diff --git a/tests/components/alarm_control_panel/test_significant_change.py b/tests/components/alarm_control_panel/test_significant_change.py index d65a1d5cb00..e614467098a 100644 --- a/tests/components/alarm_control_panel/test_significant_change.py +++ b/tests/components/alarm_control_panel/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Alarm Control Panel significant change platform.""" + import pytest from homeassistant.components.alarm_control_panel import ( diff --git a/tests/components/alert/test_reproduce_state.py b/tests/components/alert/test_reproduce_state.py index bca2a22ce7e..e0f817443dc 100644 --- a/tests/components/alert/test_reproduce_state.py +++ b/tests/components/alert/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Alert.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/alexa/test_config.py b/tests/components/alexa/test_config.py index 9536e3d471b..62330c8059c 100644 --- a/tests/components/alexa/test_config.py +++ b/tests/components/alexa/test_config.py @@ -1,4 +1,5 @@ """Test config.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index 08d198145df..c851b7991ac 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -1,4 +1,5 @@ """Test report state.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/ambient_station/conftest.py b/tests/components/ambient_station/conftest.py index ab5eb6239c8..25b11740db6 100644 --- a/tests/components/ambient_station/conftest.py +++ b/tests/components/ambient_station/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for Ambient PWS.""" + import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/apcupsd/test_init.py b/tests/components/apcupsd/test_init.py index c65efe25bb9..e6db0adc4c7 100644 --- a/tests/components/apcupsd/test_init.py +++ b/tests/components/apcupsd/test_init.py @@ -1,4 +1,5 @@ """Test init of APCUPSd integration.""" + import asyncio from collections import OrderedDict from unittest.mock import patch diff --git a/tests/components/api/test_init.py b/tests/components/api/test_init.py index 49da0078229..0ac2e5973fe 100644 --- a/tests/components/api/test_init.py +++ b/tests/components/api/test_init.py @@ -1,4 +1,5 @@ """The tests for the Home Assistant API component.""" + import asyncio from http import HTTPStatus import json diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py index d073e9c75da..a8510628b26 100644 --- a/tests/components/arcam_fmj/test_device_trigger.py +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Arcam FMJ Receiver control device triggers.""" + import pytest from homeassistant.components.arcam_fmj.const import DOMAIN diff --git a/tests/components/assist_pipeline/test_init.py b/tests/components/assist_pipeline/test_init.py index 882d3a80fb3..81347e96235 100644 --- a/tests/components/assist_pipeline/test_init.py +++ b/tests/components/assist_pipeline/test_init.py @@ -1,4 +1,5 @@ """Test Voice Assistant init.""" + import asyncio from dataclasses import asdict import itertools as it diff --git a/tests/components/assist_pipeline/test_vad.py b/tests/components/assist_pipeline/test_vad.py index 57b567c49df..139ae915263 100644 --- a/tests/components/assist_pipeline/test_vad.py +++ b/tests/components/assist_pipeline/test_vad.py @@ -1,4 +1,5 @@ """Tests for voice command segmenter.""" + import itertools as it from unittest.mock import patch diff --git a/tests/components/assist_pipeline/test_websocket.py b/tests/components/assist_pipeline/test_websocket.py index 9138819de12..91f07fc08c0 100644 --- a/tests/components/assist_pipeline/test_websocket.py +++ b/tests/components/assist_pipeline/test_websocket.py @@ -1,4 +1,5 @@ """Websocket tests for Voice Assistant integration.""" + import asyncio import base64 from typing import Any diff --git a/tests/components/atag/conftest.py b/tests/components/atag/conftest.py index a1270696540..567b835d8b4 100644 --- a/tests/components/atag/conftest.py +++ b/tests/components/atag/conftest.py @@ -1,4 +1,5 @@ """Provide common Atag fixtures.""" + import asyncio from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 72352477b4a..377a5bf2897 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -1,4 +1,5 @@ """The binary_sensor tests for the august platform.""" + import datetime import time from unittest.mock import Mock, patch diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index bc2cd23b23d..39c1745d967 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -1,4 +1,5 @@ """The lock tests for the august platform.""" + import datetime from unittest.mock import Mock diff --git a/tests/components/auth/conftest.py b/tests/components/auth/conftest.py index b7e69a44a0d..a17661f5635 100644 --- a/tests/components/auth/conftest.py +++ b/tests/components/auth/conftest.py @@ -1,4 +1,5 @@ """Test configuration for auth.""" + import pytest diff --git a/tests/components/auth/test_indieauth.py b/tests/components/auth/test_indieauth.py index 612d97808a7..2a8d6894dc6 100644 --- a/tests/components/auth/test_indieauth.py +++ b/tests/components/auth/test_indieauth.py @@ -1,4 +1,5 @@ """Tests for the client validator.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/automation/test_blueprint.py b/tests/components/automation/test_blueprint.py index 5df33f9b4b8..24d77800508 100644 --- a/tests/components/automation/test_blueprint.py +++ b/tests/components/automation/test_blueprint.py @@ -1,4 +1,5 @@ """Test built-in blueprints.""" + import asyncio import contextlib from datetime import timedelta diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index e613a997939..e099c25f58f 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,4 +1,5 @@ """The tests for the automation component.""" + import asyncio from datetime import timedelta import logging diff --git a/tests/components/automation/test_reproduce_state.py b/tests/components/automation/test_reproduce_state.py index 88ed26b564c..2b987572ae4 100644 --- a/tests/components/automation/test_reproduce_state.py +++ b/tests/components/automation/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Automation.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py index 2a69f02e35d..9cf8966483b 100644 --- a/tests/components/aws/test_init.py +++ b/tests/components/aws/test_init.py @@ -1,4 +1,5 @@ """Tests for the aws component config and setup.""" + import json from unittest.mock import AsyncMock, MagicMock, call, patch as async_patch diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index d928d3beaa9..64e567889f1 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -1,4 +1,5 @@ """Axis binary sensor platform tests.""" + import pytest from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN diff --git a/tests/components/axis/test_diagnostics.py b/tests/components/axis/test_diagnostics.py index af11fdc388a..026e1ae4d22 100644 --- a/tests/components/axis/test_diagnostics.py +++ b/tests/components/axis/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Axis diagnostics.""" + import pytest from syrupy import SnapshotAssertion diff --git a/tests/components/azure_event_hub/test_config_flow.py b/tests/components/azure_event_hub/test_config_flow.py index 8cebbe6fbd4..38454e46dd1 100644 --- a/tests/components/azure_event_hub/test_config_flow.py +++ b/tests/components/azure_event_hub/test_config_flow.py @@ -1,4 +1,5 @@ """Test the AEH config flow.""" + import logging from unittest.mock import AsyncMock diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index dba0769bc82..2c94da10ce8 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -1,4 +1,5 @@ """The test for the bayesian sensor platform.""" + import json from unittest.mock import patch diff --git a/tests/components/blebox/test_button.py b/tests/components/blebox/test_button.py index cc5ab769853..fe596c41e33 100644 --- a/tests/components/blebox/test_button.py +++ b/tests/components/blebox/test_button.py @@ -1,4 +1,5 @@ """Blebox button entities tests.""" + import logging from unittest.mock import PropertyMock diff --git a/tests/components/blebox/test_climate.py b/tests/components/blebox/test_climate.py index 6ea6d995900..8ba0c3f630e 100644 --- a/tests/components/blebox/test_climate.py +++ b/tests/components/blebox/test_climate.py @@ -1,4 +1,5 @@ """BleBox climate entities tests.""" + import logging from unittest.mock import AsyncMock, PropertyMock diff --git a/tests/components/blebox/test_cover.py b/tests/components/blebox/test_cover.py index 8691c886faa..1596de134c0 100644 --- a/tests/components/blebox/test_cover.py +++ b/tests/components/blebox/test_cover.py @@ -1,4 +1,5 @@ """BleBox cover entities tests.""" + import logging from unittest.mock import AsyncMock, PropertyMock diff --git a/tests/components/blebox/test_init.py b/tests/components/blebox/test_init.py index 00ee62568a5..f406df51bd4 100644 --- a/tests/components/blebox/test_init.py +++ b/tests/components/blebox/test_init.py @@ -1,4 +1,5 @@ """BleBox devices setup tests.""" + import logging import blebox_uniapi diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index 47f38ba815b..bfa5478265b 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -1,4 +1,5 @@ """BleBox light entities tests.""" + import logging from unittest.mock import AsyncMock, PropertyMock diff --git a/tests/components/blebox/test_sensor.py b/tests/components/blebox/test_sensor.py index 68990a09a32..56c240705b0 100644 --- a/tests/components/blebox/test_sensor.py +++ b/tests/components/blebox/test_sensor.py @@ -1,4 +1,5 @@ """Blebox sensors tests.""" + import logging from unittest.mock import AsyncMock, PropertyMock diff --git a/tests/components/blebox/test_switch.py b/tests/components/blebox/test_switch.py index db98a2705b2..7d1da7ba4c7 100644 --- a/tests/components/blebox/test_switch.py +++ b/tests/components/blebox/test_switch.py @@ -1,4 +1,5 @@ """Blebox switch tests.""" + import logging from unittest.mock import AsyncMock, PropertyMock diff --git a/tests/components/blueprint/test_default_blueprints.py b/tests/components/blueprint/test_default_blueprints.py index 28047e0a41e..9bd60a7cb6b 100644 --- a/tests/components/blueprint/test_default_blueprints.py +++ b/tests/components/blueprint/test_default_blueprints.py @@ -1,4 +1,5 @@ """Test default blueprints.""" + import importlib import logging import pathlib diff --git a/tests/components/blueprint/test_importer.py b/tests/components/blueprint/test_importer.py index d41c8417b57..78a70a57fe2 100644 --- a/tests/components/blueprint/test_importer.py +++ b/tests/components/blueprint/test_importer.py @@ -1,4 +1,5 @@ """Test blueprint importing.""" + import json from pathlib import Path diff --git a/tests/components/blueprint/test_models.py b/tests/components/blueprint/test_models.py index c11a467de9b..2bc6d60ccda 100644 --- a/tests/components/blueprint/test_models.py +++ b/tests/components/blueprint/test_models.py @@ -1,4 +1,5 @@ """Test blueprint models.""" + import logging from unittest.mock import AsyncMock, patch diff --git a/tests/components/blueprint/test_schemas.py b/tests/components/blueprint/test_schemas.py index 4c941b56744..0afe9e780f5 100644 --- a/tests/components/blueprint/test_schemas.py +++ b/tests/components/blueprint/test_schemas.py @@ -1,4 +1,5 @@ """Test schemas.""" + import logging import pytest diff --git a/tests/components/bluetooth/test_api.py b/tests/components/bluetooth/test_api.py index bc65874b0cc..a3ec3814a92 100644 --- a/tests/components/bluetooth/test_api.py +++ b/tests/components/bluetooth/test_api.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration API.""" + import time from bleak.backends.scanner import AdvertisementData, BLEDevice diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index e2003229213..2130e883af4 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration.""" + import asyncio from datetime import timedelta import time diff --git a/tests/components/bluetooth/test_scanner.py b/tests/components/bluetooth/test_scanner.py index 837c058fa6b..aefc36fe6eb 100644 --- a/tests/components/bluetooth/test_scanner.py +++ b/tests/components/bluetooth/test_scanner.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration scanners.""" + import asyncio from datetime import timedelta import time diff --git a/tests/components/bmw_connected_drive/test_diagnostics.py b/tests/components/bmw_connected_drive/test_diagnostics.py index 11c2b055f6d..5b22fdcf71d 100644 --- a/tests/components/bmw_connected_drive/test_diagnostics.py +++ b/tests/components/bmw_connected_drive/test_diagnostics.py @@ -1,4 +1,5 @@ """Test BMW diagnostics.""" + import datetime import os import time diff --git a/tests/components/broadlink/test_config_flow.py b/tests/components/broadlink/test_config_flow.py index 09365b8f5f4..6eb87966082 100644 --- a/tests/components/broadlink/test_config_flow.py +++ b/tests/components/broadlink/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Broadlink config flow.""" + import errno import socket from unittest.mock import call, patch diff --git a/tests/components/broadlink/test_helpers.py b/tests/components/broadlink/test_helpers.py index 937615d7e51..e3ca11a3e2d 100644 --- a/tests/components/broadlink/test_helpers.py +++ b/tests/components/broadlink/test_helpers.py @@ -1,4 +1,5 @@ """Tests for Broadlink helper functions.""" + import pytest import voluptuous as vol diff --git a/tests/components/bthome/test_device_trigger.py b/tests/components/bthome/test_device_trigger.py index 85169e80394..ec45ae25150 100644 --- a/tests/components/bthome/test_device_trigger.py +++ b/tests/components/bthome/test_device_trigger.py @@ -1,4 +1,5 @@ """Test BTHome BLE events.""" + import pytest from homeassistant.components import automation diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index f048f8d69a7..799fa37c7e3 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -1,4 +1,5 @@ """The tests for generic camera component.""" + import asyncio from contextlib import suppress import copy diff --git a/tests/components/button/test_device_action.py b/tests/components/button/test_device_action.py index 3fefa580724..3e05052f978 100644 --- a/tests/components/button/test_device_action.py +++ b/tests/components/button/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Button device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 1e72e708d44..64afed59d5a 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Cert Expiry config flow.""" + import socket import ssl from unittest.mock import patch diff --git a/tests/components/clicksend_tts/test_notify.py b/tests/components/clicksend_tts/test_notify.py index ae814aa1332..e73f0576d9e 100644 --- a/tests/components/clicksend_tts/test_notify.py +++ b/tests/components/clicksend_tts/test_notify.py @@ -1,4 +1,5 @@ """The test for the Facebook notify module.""" + import base64 from http import HTTPStatus import logging diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index 1fc379487ed..f15ee3817e7 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Climate device actions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 4dc365e59ee..0d6767c374c 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Climate device conditions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/climate/test_device_trigger.py b/tests/components/climate/test_device_trigger.py index 59efb66ff65..13426ba02a2 100644 --- a/tests/components/climate/test_device_trigger.py +++ b/tests/components/climate/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Climate device triggers.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/climate/test_reproduce_state.py b/tests/components/climate/test_reproduce_state.py index b8719fd8fd0..636ab326a2b 100644 --- a/tests/components/climate/test_reproduce_state.py +++ b/tests/components/climate/test_reproduce_state.py @@ -1,4 +1,5 @@ """The tests for reproduction of state.""" + import pytest from homeassistant.components.climate import ( diff --git a/tests/components/climate/test_significant_change.py b/tests/components/climate/test_significant_change.py index 369e5e67004..f060344722a 100644 --- a/tests/components/climate/test_significant_change.py +++ b/tests/components/climate/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Climate significant change platform.""" + import pytest from homeassistant.components.climate import ( diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index 14f99fe0fb1..0708c952ca5 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -1,4 +1,5 @@ """Test account link services.""" + import asyncio import logging from time import time diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 3ac2417247c..21fe75b9494 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -1,4 +1,5 @@ """Test Alexa config.""" + import contextlib from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/cloud/test_assist_pipeline.py b/tests/components/cloud/test_assist_pipeline.py index 7f1411dab45..5c2fc074898 100644 --- a/tests/components/cloud/test_assist_pipeline.py +++ b/tests/components/cloud/test_assist_pipeline.py @@ -1,4 +1,5 @@ """Test the cloud assist pipeline.""" + import pytest from homeassistant.components.cloud.assist_pipeline import ( diff --git a/tests/components/cloud/test_system_health.py b/tests/components/cloud/test_system_health.py index 5480cd557fd..c6e738011d6 100644 --- a/tests/components/cloud/test_system_health.py +++ b/tests/components/cloud/test_system_health.py @@ -1,4 +1,5 @@ """Test cloud system health.""" + import asyncio from collections.abc import Callable, Coroutine from typing import Any diff --git a/tests/components/cloudflare/test_config_flow.py b/tests/components/cloudflare/test_config_flow.py index 21ee364eca3..142eab621e5 100644 --- a/tests/components/cloudflare/test_config_flow.py +++ b/tests/components/cloudflare/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Cloudflare config flow.""" + import pycfdns from homeassistant.components.cloudflare.const import CONF_RECORDS, DOMAIN diff --git a/tests/components/coinbase/test_config_flow.py b/tests/components/coinbase/test_config_flow.py index 592481df129..971b6565d00 100644 --- a/tests/components/coinbase/test_config_flow.py +++ b/tests/components/coinbase/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Coinbase config flow.""" + import logging from unittest.mock import patch diff --git a/tests/components/color_extractor/conftest.py b/tests/components/color_extractor/conftest.py index 299c8019f94..5c7d1136088 100644 --- a/tests/components/color_extractor/conftest.py +++ b/tests/components/color_extractor/conftest.py @@ -1,4 +1,5 @@ """Common fixtures for the Color extractor tests.""" + import pytest from homeassistant.components.color_extractor.const import DOMAIN diff --git a/tests/components/color_extractor/test_service.py b/tests/components/color_extractor/test_service.py index 647d945f158..6ad4830c2c4 100644 --- a/tests/components/color_extractor/test_service.py +++ b/tests/components/color_extractor/test_service.py @@ -1,4 +1,5 @@ """Tests for color_extractor component service calls.""" + import base64 import io from typing import Any diff --git a/tests/components/compensation/test_sensor.py b/tests/components/compensation/test_sensor.py index 5bc5a5e1c39..877a4f972a9 100644 --- a/tests/components/compensation/test_sensor.py +++ b/tests/components/compensation/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the integration sensor platform.""" + import pytest from homeassistant.components.compensation.const import CONF_PRECISION, DOMAIN diff --git a/tests/components/config/test_area_registry.py b/tests/components/config/test_area_registry.py index c4e275651ff..fb9c5fd453b 100644 --- a/tests/components/config/test_area_registry.py +++ b/tests/components/config/test_area_registry.py @@ -1,4 +1,5 @@ """Test area_registry API.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/config/test_auth.py b/tests/components/config/test_auth.py index 21ca81e74ac..b839d2de7a0 100644 --- a/tests/components/config/test_auth.py +++ b/tests/components/config/test_auth.py @@ -1,4 +1,5 @@ """Test config entries API.""" + import pytest from homeassistant.auth import models as auth_models diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index 53746d2aa34..19c30a43858 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -1,4 +1,5 @@ """Test device_registry API.""" + import pytest from homeassistant.components.config import device_registry diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 7069b22bf09..36e3b8b46ff 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -1,4 +1,5 @@ """Test entity_registry API.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/config/test_floor_registry.py b/tests/components/config/test_floor_registry.py index 6928a82898e..a3866c88ffa 100644 --- a/tests/components/config/test_floor_registry.py +++ b/tests/components/config/test_floor_registry.py @@ -1,4 +1,5 @@ """Test floor registry API.""" + import pytest from homeassistant.components.config import floor_registry diff --git a/tests/components/config/test_label_registry.py b/tests/components/config/test_label_registry.py index f65654462b9..aa082a3de38 100644 --- a/tests/components/config/test_label_registry.py +++ b/tests/components/config/test_label_registry.py @@ -1,4 +1,5 @@ """Test label registry API.""" + import pytest from homeassistant.components.config import label_registry diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py index a52c083d10f..c0bd6344adb 100644 --- a/tests/components/counter/test_init.py +++ b/tests/components/counter/test_init.py @@ -1,4 +1,5 @@ """The tests for the counter component.""" + import logging import pytest diff --git a/tests/components/counter/test_reproduce_state.py b/tests/components/counter/test_reproduce_state.py index 44d0eca4d72..420c9b8907d 100644 --- a/tests/components/counter/test_reproduce_state.py +++ b/tests/components/counter/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Counter.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index c476f78702e..814d70e5c43 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Cover device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index 2dcc719f35f..dc92b209e57 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Cover device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/cover/test_reproduce_state.py b/tests/components/cover/test_reproduce_state.py index 70f8201fedc..f5dd01745d3 100644 --- a/tests/components/cover/test_reproduce_state.py +++ b/tests/components/cover/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Cover.""" + import pytest from homeassistant.components.cover import ( diff --git a/tests/components/cover/test_significant_change.py b/tests/components/cover/test_significant_change.py index 9ddb2cb9498..5e5032dce3f 100644 --- a/tests/components/cover/test_significant_change.py +++ b/tests/components/cover/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Cover significant change platform.""" + import pytest from homeassistant.components.cover import ( diff --git a/tests/components/deconz/test_config_flow.py b/tests/components/deconz/test_config_flow.py index 7874b7899c8..a6910ef4b55 100644 --- a/tests/components/deconz/test_config_flow.py +++ b/tests/components/deconz/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for deCONZ config flow.""" + import logging from unittest.mock import patch diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index fe364779059..278a137bb89 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -1,4 +1,5 @@ """Test deCONZ component setup process.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index fdf72c7fd34..05532d7503b 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -1,4 +1,5 @@ """The tests for the Demo component.""" + import json from unittest.mock import patch diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index 96ffcdec96b..54eadc3bd91 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -1,4 +1,5 @@ """The tests for the notify demo platform.""" + import logging from unittest.mock import patch diff --git a/tests/components/demo/test_weather.py b/tests/components/demo/test_weather.py index bb91535192c..403b8b1d346 100644 --- a/tests/components/demo/test_weather.py +++ b/tests/components/demo/test_weather.py @@ -1,4 +1,5 @@ """The tests for the demo weather component.""" + import datetime from typing import Any from unittest.mock import patch diff --git a/tests/components/derivative/test_init.py b/tests/components/derivative/test_init.py index eab8ca67be7..8d387c4ecab 100644 --- a/tests/components/derivative/test_init.py +++ b/tests/components/derivative/test_init.py @@ -1,4 +1,5 @@ """Test the Derivative integration.""" + import pytest from homeassistant.components.derivative.const import DOMAIN diff --git a/tests/components/devialet/test_diagnostics.py b/tests/components/devialet/test_diagnostics.py index 82600de7cf5..97c23efe713 100644 --- a/tests/components/devialet/test_diagnostics.py +++ b/tests/components/devialet/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Devialet diagnostics.""" + import json from homeassistant.core import HomeAssistant diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index f550b803fda..c5abff2e517 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Device tracker device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/device_tracker/test_device_trigger.py b/tests/components/device_tracker/test_device_trigger.py index 3e19570ebcb..217f7641a58 100644 --- a/tests/components/device_tracker/test_device_trigger.py +++ b/tests/components/device_tracker/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Device Tracker device triggers.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/dialogflow/test_init.py b/tests/components/dialogflow/test_init.py index 3e90856fc12..7f5fe79d146 100644 --- a/tests/components/dialogflow/test_init.py +++ b/tests/components/dialogflow/test_init.py @@ -1,4 +1,5 @@ """The tests for the Dialogflow component.""" + import copy from http import HTTPStatus import json diff --git a/tests/components/directv/test_config_flow.py b/tests/components/directv/test_config_flow.py index 8017dc290c9..569e165a0a6 100644 --- a/tests/components/directv/test_config_flow.py +++ b/tests/components/directv/test_config_flow.py @@ -1,4 +1,5 @@ """Test the DirecTV config flow.""" + import dataclasses from unittest.mock import patch diff --git a/tests/components/discord/test_config_flow.py b/tests/components/discord/test_config_flow.py index b6504e851ca..ba1909c48c8 100644 --- a/tests/components/discord/test_config_flow.py +++ b/tests/components/discord/test_config_flow.py @@ -1,4 +1,5 @@ """Test Discord config flow.""" + import nextcord from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/discord/test_notify.py b/tests/components/discord/test_notify.py index 810898cdf73..011698867a8 100644 --- a/tests/components/discord/test_notify.py +++ b/tests/components/discord/test_notify.py @@ -1,4 +1,5 @@ """Test Discord notify.""" + import logging import pytest diff --git a/tests/components/discovergy/const.py b/tests/components/discovergy/const.py index 3c9f696fb02..700d001814d 100644 --- a/tests/components/discovergy/const.py +++ b/tests/components/discovergy/const.py @@ -1,4 +1,5 @@ """Constants for Discovergy integration tests.""" + import datetime from pydiscovergy.models import Location, Meter, Reading diff --git a/tests/components/discovergy/test_diagnostics.py b/tests/components/discovergy/test_diagnostics.py index f2db5fb854d..5c231c3d221 100644 --- a/tests/components/discovergy/test_diagnostics.py +++ b/tests/components/discovergy/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Discovergy diagnostics.""" + import pytest from syrupy import SnapshotAssertion diff --git a/tests/components/discovergy/test_system_health.py b/tests/components/discovergy/test_system_health.py index e64072f1023..af8d9dde5b4 100644 --- a/tests/components/discovergy/test_system_health.py +++ b/tests/components/discovergy/test_system_health.py @@ -1,4 +1,5 @@ """Test Discovergy system health.""" + import asyncio from aiohttp import ClientError diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index 01aff5ae48e..7b9e583253b 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -1,4 +1,5 @@ """Common test tools.""" + import asyncio from unittest.mock import MagicMock, patch diff --git a/tests/components/dsmr/test_mbus_migration.py b/tests/components/dsmr/test_mbus_migration.py index 5e31fa7a82e..95def2f66cf 100644 --- a/tests/components/dsmr/test_mbus_migration.py +++ b/tests/components/dsmr/test_mbus_migration.py @@ -1,4 +1,5 @@ """Tests for the DSMR integration.""" + import datetime from decimal import Decimal diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index cd3e60c7aeb..f63422e0543 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -4,6 +4,7 @@ Tests setup of the DSMR component and ensure incoming telegrams cause Entity to be updated with new values. """ + import asyncio import datetime from decimal import Decimal diff --git a/tests/components/dte_energy_bridge/test_sensor.py b/tests/components/dte_energy_bridge/test_sensor.py index f62520701bd..244bec4e270 100644 --- a/tests/components/dte_energy_bridge/test_sensor.py +++ b/tests/components/dte_energy_bridge/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the DTE Energy Bridge.""" + import requests_mock from homeassistant.core import HomeAssistant diff --git a/tests/components/eafm/test_sensor.py b/tests/components/eafm/test_sensor.py index 04659a79d1c..380e1df5f37 100644 --- a/tests/components/eafm/test_sensor.py +++ b/tests/components/eafm/test_sensor.py @@ -1,4 +1,5 @@ """Tests for polling measures.""" + import datetime import aiohttp diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 642e4830016..7b9a31739dc 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -1,4 +1,5 @@ """The test for the Ecobee thermostat module.""" + import copy from http import HTTPStatus from unittest import mock diff --git a/tests/components/ecobee/test_util.py b/tests/components/ecobee/test_util.py index 368721f71c0..88032362af0 100644 --- a/tests/components/ecobee/test_util.py +++ b/tests/components/ecobee/test_util.py @@ -1,4 +1,5 @@ """Tests for the ecobee.util module.""" + import pytest import voluptuous as vol diff --git a/tests/components/ecovacs/util.py b/tests/components/ecovacs/util.py index 73762128202..c587494f5e2 100644 --- a/tests/components/ecovacs/util.py +++ b/tests/components/ecovacs/util.py @@ -1,4 +1,5 @@ """Ecovacs test util.""" + import asyncio from deebot_client.event_bus import EventBus diff --git a/tests/components/elgato/test_sensor.py b/tests/components/elgato/test_sensor.py index f53e2e43156..1ad37d938e7 100644 --- a/tests/components/elgato/test_sensor.py +++ b/tests/components/elgato/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Elgato sensor platform.""" + import pytest from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/elmax/conftest.py b/tests/components/elmax/conftest.py index 85e4902b010..e69f52f4cad 100644 --- a/tests/components/elmax/conftest.py +++ b/tests/components/elmax/conftest.py @@ -1,4 +1,5 @@ """Configuration for Elmax tests.""" + import json from unittest.mock import patch diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 167562578f2..08974b36215 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -1,4 +1,5 @@ """The tests for the emulated Hue component.""" + import asyncio from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/emulated_kasa/test_init.py b/tests/components/emulated_kasa/test_init.py index 5d294ec3bba..36eaa749f11 100644 --- a/tests/components/emulated_kasa/test_init.py +++ b/tests/components/emulated_kasa/test_init.py @@ -1,4 +1,5 @@ """Tests for emulated_kasa library bindings.""" + import math from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/energy/test_sensor.py b/tests/components/energy/test_sensor.py index 522bbe5af06..c51a228f7f0 100644 --- a/tests/components/energy/test_sensor.py +++ b/tests/components/energy/test_sensor.py @@ -1,4 +1,5 @@ """Test the Energy sensors.""" + import copy from datetime import timedelta from typing import Any diff --git a/tests/components/esphome/test_entity.py b/tests/components/esphome/test_entity.py index 609af788ceb..4056c286b51 100644 --- a/tests/components/esphome/test_entity.py +++ b/tests/components/esphome/test_entity.py @@ -1,4 +1,5 @@ """Test ESPHome binary sensors.""" + import asyncio from collections.abc import Awaitable, Callable from typing import Any diff --git a/tests/components/esphome/test_manager.py b/tests/components/esphome/test_manager.py index 424ab7338aa..55369e54b53 100644 --- a/tests/components/esphome/test_manager.py +++ b/tests/components/esphome/test_manager.py @@ -1,4 +1,5 @@ """Test ESPHome manager.""" + import asyncio from collections.abc import Awaitable, Callable from unittest.mock import AsyncMock, call diff --git a/tests/components/evil_genius_labs/conftest.py b/tests/components/evil_genius_labs/conftest.py index a4f10fe97c4..dd3f5fe24f4 100644 --- a/tests/components/evil_genius_labs/conftest.py +++ b/tests/components/evil_genius_labs/conftest.py @@ -1,4 +1,5 @@ """Test helpers for Evil Genius Labs.""" + import json from unittest.mock import patch diff --git a/tests/components/evil_genius_labs/test_diagnostics.py b/tests/components/evil_genius_labs/test_diagnostics.py index 2435244d3c5..0ad7f6e27f3 100644 --- a/tests/components/evil_genius_labs/test_diagnostics.py +++ b/tests/components/evil_genius_labs/test_diagnostics.py @@ -1,4 +1,5 @@ """Test evil genius labs diagnostics.""" + import pytest from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/evil_genius_labs/test_init.py b/tests/components/evil_genius_labs/test_init.py index 85055c0a73c..71b8a6164a6 100644 --- a/tests/components/evil_genius_labs/test_init.py +++ b/tests/components/evil_genius_labs/test_init.py @@ -1,4 +1,5 @@ """Test evil genius labs init.""" + import pytest from homeassistant import config_entries diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index b8756d9ace5..49d6f441b1e 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Fan device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/fan/test_device_condition.py b/tests/components/fan/test_device_condition.py index 1ee168f28ab..18744155f91 100644 --- a/tests/components/fan/test_device_condition.py +++ b/tests/components/fan/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Fan device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/fan/test_init.py b/tests/components/fan/test_init.py index 1beea47c6fa..911954d1ecd 100644 --- a/tests/components/fan/test_init.py +++ b/tests/components/fan/test_init.py @@ -1,4 +1,5 @@ """Tests for fan platforms.""" + import pytest from homeassistant.components import fan diff --git a/tests/components/fan/test_reproduce_state.py b/tests/components/fan/test_reproduce_state.py index 3d9ea90e735..e1904b34769 100644 --- a/tests/components/fan/test_reproduce_state.py +++ b/tests/components/fan/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Fan.""" + import pytest from homeassistant.components.fan import ( diff --git a/tests/components/fan/test_significant_change.py b/tests/components/fan/test_significant_change.py index 764abb6e8ee..9b940e33138 100644 --- a/tests/components/fan/test_significant_change.py +++ b/tests/components/fan/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Fan significant change platform.""" + import pytest from homeassistant.components.fan import ( diff --git a/tests/components/fido/test_sensor.py b/tests/components/fido/test_sensor.py index 81ae54174ca..a067f060af8 100644 --- a/tests/components/fido/test_sensor.py +++ b/tests/components/fido/test_sensor.py @@ -1,4 +1,5 @@ """The test for the fido sensor platform.""" + import logging from unittest.mock import MagicMock, patch diff --git a/tests/components/file/test_notify.py b/tests/components/file/test_notify.py index 9cde648d27c..071c68caea3 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -1,4 +1,5 @@ """The tests for the notify file platform.""" + import os from unittest.mock import call, mock_open, patch diff --git a/tests/components/filesize/test_sensor.py b/tests/components/filesize/test_sensor.py index 20354df13bd..880563f0ad8 100644 --- a/tests/components/filesize/test_sensor.py +++ b/tests/components/filesize/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the filesize sensor.""" + import os from pathlib import Path diff --git a/tests/components/flexit_bacnet/test_config_flow.py b/tests/components/flexit_bacnet/test_config_flow.py index 860d25e4b75..7d864a80c2d 100644 --- a/tests/components/flexit_bacnet/test_config_flow.py +++ b/tests/components/flexit_bacnet/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Flexit Nordic (BACnet) config flow.""" + import asyncio.exceptions from flexit_bacnet import DecodingError diff --git a/tests/components/flo/test_services.py b/tests/components/flo/test_services.py index d38c5c45031..c65aa7937ee 100644 --- a/tests/components/flo/test_services.py +++ b/tests/components/flo/test_services.py @@ -1,4 +1,5 @@ """Test the services for the Flo by Moen integration.""" + import pytest from voluptuous.error import MultipleInvalid diff --git a/tests/components/folder/test_sensor.py b/tests/components/folder/test_sensor.py index c45fe2ae5f5..ad0969c6a0f 100644 --- a/tests/components/folder/test_sensor.py +++ b/tests/components/folder/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the folder sensor.""" + import os from homeassistant.components.folder.sensor import CONF_FOLDER_PATHS diff --git a/tests/components/folder_watcher/test_init.py b/tests/components/folder_watcher/test_init.py index b09e060725f..2e9eb99f678 100644 --- a/tests/components/folder_watcher/test_init.py +++ b/tests/components/folder_watcher/test_init.py @@ -1,4 +1,5 @@ """The tests for the folder_watcher component.""" + import os from types import SimpleNamespace from unittest.mock import Mock, patch diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index 6042248561c..e223e868d83 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -1,4 +1,5 @@ """Test helpers for Freebox.""" + import json from unittest.mock import AsyncMock, PropertyMock, patch diff --git a/tests/components/freebox/test_router.py b/tests/components/freebox/test_router.py index 88cf56de2bb..623f595e1ad 100644 --- a/tests/components/freebox/test_router.py +++ b/tests/components/freebox/test_router.py @@ -1,4 +1,5 @@ """Tests for the Freebox utility methods.""" + import json from unittest.mock import Mock diff --git a/tests/components/freedns/test_init.py b/tests/components/freedns/test_init.py index 9163a8faf2d..bdb60933a19 100644 --- a/tests/components/freedns/test_init.py +++ b/tests/components/freedns/test_init.py @@ -1,4 +1,5 @@ """Test the FreeDNS component.""" + import pytest from homeassistant.components import freedns diff --git a/tests/components/freedompro/test_init.py b/tests/components/freedompro/test_init.py index 5e1b050a4e7..18bf6d41ae2 100644 --- a/tests/components/freedompro/test_init.py +++ b/tests/components/freedompro/test_init.py @@ -1,4 +1,5 @@ """Freedompro component tests.""" + import logging from unittest.mock import patch diff --git a/tests/components/fritz/conftest.py b/tests/components/fritz/conftest.py index 08dce14f18d..2e26f67c1eb 100644 --- a/tests/components/fritz/conftest.py +++ b/tests/components/fritz/conftest.py @@ -1,4 +1,5 @@ """Common stuff for Fritz!Tools tests.""" + import logging from unittest.mock import MagicMock, patch diff --git a/tests/components/fritz/test_config_flow.py b/tests/components/fritz/test_config_flow.py index 5b87d897dd9..5db70e348dd 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Fritz!Tools config flow.""" + import dataclasses from unittest.mock import patch diff --git a/tests/components/fritzbox/test_config_flow.py b/tests/components/fritzbox/test_config_flow.py index 674beda13ee..cf93e74edac 100644 --- a/tests/components/fritzbox/test_config_flow.py +++ b/tests/components/fritzbox/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for AVM Fritz!Box config flow.""" + import dataclasses from unittest import mock from unittest.mock import Mock, patch diff --git a/tests/components/fully_kiosk/test_init.py b/tests/components/fully_kiosk/test_init.py index e74da6434cd..f3fb945c8f0 100644 --- a/tests/components/fully_kiosk/test_init.py +++ b/tests/components/fully_kiosk/test_init.py @@ -1,4 +1,5 @@ """Tests for the Fully Kiosk Browser integration.""" + import json from unittest.mock import MagicMock, patch diff --git a/tests/components/gdacs/conftest.py b/tests/components/gdacs/conftest.py index ee82a3131b1..9d9a91aa407 100644 --- a/tests/components/gdacs/conftest.py +++ b/tests/components/gdacs/conftest.py @@ -1,4 +1,5 @@ """Configuration for GDACS tests.""" + import pytest from homeassistant.components.gdacs import CONF_CATEGORIES, DOMAIN diff --git a/tests/components/gdacs/test_geo_location.py b/tests/components/gdacs/test_geo_location.py index c7f5a0f72e3..467b6dc2fc4 100644 --- a/tests/components/gdacs/test_geo_location.py +++ b/tests/components/gdacs/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the GDACS Feed integration.""" + import datetime from unittest.mock import patch diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index 608b8666027..ac882716103 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -1,4 +1,5 @@ """The tests for generic camera component.""" + import asyncio from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 86bd552bcf3..6ced5086282 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -1,4 +1,5 @@ """Test The generic (IP Camera) config flow.""" + import contextlib import errno from http import HTTPStatus diff --git a/tests/components/generic/test_diagnostics.py b/tests/components/generic/test_diagnostics.py index 99dc3e22a0b..f68c3ba4bc6 100644 --- a/tests/components/generic/test_diagnostics.py +++ b/tests/components/generic/test_diagnostics.py @@ -1,4 +1,5 @@ """Test generic (IP camera) diagnostics.""" + import pytest from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/generic_hygrostat/test_humidifier.py b/tests/components/generic_hygrostat/test_humidifier.py index e1a33a19834..fdad20f5b2d 100644 --- a/tests/components/generic_hygrostat/test_humidifier.py +++ b/tests/components/generic_hygrostat/test_humidifier.py @@ -1,4 +1,5 @@ """The tests for the generic_hygrostat.""" + import datetime from freezegun import freeze_time diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index e181b14efb0..c7b6c03a2c5 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -1,4 +1,5 @@ """The tests for the generic_thermostat.""" + import datetime from unittest.mock import patch diff --git a/tests/components/geo_location/test_init.py b/tests/components/geo_location/test_init.py index 0861cd7cba1..747135def9b 100644 --- a/tests/components/geo_location/test_init.py +++ b/tests/components/geo_location/test_init.py @@ -1,4 +1,5 @@ """The tests for the geolocation component.""" + import pytest from homeassistant.components import geo_location diff --git a/tests/components/geo_location/test_trigger.py b/tests/components/geo_location/test_trigger.py index a5e0f99c5c2..85461d60aac 100644 --- a/tests/components/geo_location/test_trigger.py +++ b/tests/components/geo_location/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the geolocation trigger.""" + import logging import pytest diff --git a/tests/components/geonetnz_quakes/conftest.py b/tests/components/geonetnz_quakes/conftest.py index 7715b17796b..c93d4a2e50c 100644 --- a/tests/components/geonetnz_quakes/conftest.py +++ b/tests/components/geonetnz_quakes/conftest.py @@ -1,4 +1,5 @@ """Configuration for GeoNet NZ Quakes tests.""" + import pytest from homeassistant.components.geonetnz_quakes import ( diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index afc6ada75cd..19f1954d06a 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the GeoNet NZ Quakes Feed integration.""" + import datetime from unittest.mock import patch diff --git a/tests/components/geonetnz_quakes/test_sensor.py b/tests/components/geonetnz_quakes/test_sensor.py index 27f67dad322..02edfc910b2 100644 --- a/tests/components/geonetnz_quakes/test_sensor.py +++ b/tests/components/geonetnz_quakes/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the GeoNet NZ Quakes Feed integration.""" + import datetime from unittest.mock import patch diff --git a/tests/components/geonetnz_volcano/conftest.py b/tests/components/geonetnz_volcano/conftest.py index 33a299eeb79..3df67e91c95 100644 --- a/tests/components/geonetnz_volcano/conftest.py +++ b/tests/components/geonetnz_volcano/conftest.py @@ -1,4 +1,5 @@ """Configuration for GeoNet NZ Volcano tests.""" + import pytest from homeassistant.components.geonetnz_volcano import DOMAIN diff --git a/tests/components/gios/test_config_flow.py b/tests/components/gios/test_config_flow.py index efe46be9b8d..a735b45975a 100644 --- a/tests/components/gios/test_config_flow.py +++ b/tests/components/gios/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the GIOS config flow.""" + import json from unittest.mock import patch diff --git a/tests/components/gios/test_init.py b/tests/components/gios/test_init.py index d20aecad3df..6082fa8c522 100644 --- a/tests/components/gios/test_init.py +++ b/tests/components/gios/test_init.py @@ -1,4 +1,5 @@ """Test init of GIOS integration.""" + import json from unittest.mock import patch diff --git a/tests/components/gios/test_system_health.py b/tests/components/gios/test_system_health.py index 571614ed1de..896f3cca04a 100644 --- a/tests/components/gios/test_system_health.py +++ b/tests/components/gios/test_system_health.py @@ -1,4 +1,5 @@ """Test GIOS system health.""" + import asyncio from aiohttp import ClientError diff --git a/tests/components/github/test_init.py b/tests/components/github/test_init.py index 03cc2e92720..a25e27df835 100644 --- a/tests/components/github/test_init.py +++ b/tests/components/github/test_init.py @@ -1,4 +1,5 @@ """Test the GitHub init file.""" + import pytest from homeassistant.components.github import CONF_REPOSITORIES diff --git a/tests/components/github/test_sensor.py b/tests/components/github/test_sensor.py index f81f59e88c9..b0eaed3ae0e 100644 --- a/tests/components/github/test_sensor.py +++ b/tests/components/github/test_sensor.py @@ -1,4 +1,5 @@ """Test GitHub sensor.""" + import json import pytest diff --git a/tests/components/google_assistant/test_data_redaction.py b/tests/components/google_assistant/test_data_redaction.py index 86b15782fe5..d650a223e15 100644 --- a/tests/components/google_assistant/test_data_redaction.py +++ b/tests/components/google_assistant/test_data_redaction.py @@ -1,4 +1,5 @@ """Test data redaction helpers.""" + import json from homeassistant.components.google_assistant.data_redaction import async_redact_msg diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 587d89a1d8a..04ceafb004a 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -1,4 +1,5 @@ """Test Google Smart Home.""" + import asyncio from types import SimpleNamespace from unittest.mock import ANY, patch diff --git a/tests/components/google_mail/test_init.py b/tests/components/google_mail/test_init.py index ef2f1475dad..6e7def716a9 100644 --- a/tests/components/google_mail/test_init.py +++ b/tests/components/google_mail/test_init.py @@ -1,4 +1,5 @@ """Tests for Google Mail.""" + import http import time from unittest.mock import patch diff --git a/tests/components/google_travel_time/test_config_flow.py b/tests/components/google_travel_time/test_config_flow.py index b701fcb2143..24e9cb1297a 100644 --- a/tests/components/google_travel_time/test_config_flow.py +++ b/tests/components/google_travel_time/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Google Maps Travel Time config flow.""" + import pytest from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index fec75bf5fcc..738f8eda333 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -1,4 +1,5 @@ """The tests for the Graphite component.""" + import socket from unittest import mock from unittest.mock import patch diff --git a/tests/components/gree/common.py b/tests/components/gree/common.py index aa88688486c..97656596ce6 100644 --- a/tests/components/gree/common.py +++ b/tests/components/gree/common.py @@ -1,4 +1,5 @@ """Common helpers for gree test cases.""" + import asyncio import logging from unittest.mock import AsyncMock, Mock diff --git a/tests/components/group/conftest.py b/tests/components/group/conftest.py index 3aefbfacdf8..5d332f8d904 100644 --- a/tests/components/group/conftest.py +++ b/tests/components/group/conftest.py @@ -1,4 +1,5 @@ """group conftest.""" + import pytest from homeassistant.core import HomeAssistant diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index d0eb3788763..5b5d8fa873c 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -1,4 +1,5 @@ """The tests for the group cover platform.""" + import asyncio from datetime import timedelta diff --git a/tests/components/group/test_fan.py b/tests/components/group/test_fan.py index ecc21ab0cd2..6aa6fc2933d 100644 --- a/tests/components/group/test_fan.py +++ b/tests/components/group/test_fan.py @@ -1,4 +1,5 @@ """The tests for the group fan platform.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index 63f21456066..b279c38d169 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -1,4 +1,5 @@ """The tests for the Group Light platform.""" + import asyncio from unittest.mock import MagicMock, patch diff --git a/tests/components/group/test_media_player.py b/tests/components/group/test_media_player.py index 9f36693d9ef..451aae200b3 100644 --- a/tests/components/group/test_media_player.py +++ b/tests/components/group/test_media_player.py @@ -1,4 +1,5 @@ """The tests for the Media group platform.""" + import asyncio from unittest.mock import Mock, patch diff --git a/tests/components/group/test_switch.py b/tests/components/group/test_switch.py index 86f6eb43ed9..32b21fcb0d7 100644 --- a/tests/components/group/test_switch.py +++ b/tests/components/group/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Group Switch platform.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/harmony/test_subscriber.py b/tests/components/harmony/test_subscriber.py index bdc57385852..94a8ffbc37a 100644 --- a/tests/components/harmony/test_subscriber.py +++ b/tests/components/harmony/test_subscriber.py @@ -1,4 +1,5 @@ """Test the HarmonySubscriberMixin class.""" + import asyncio from unittest.mock import AsyncMock, MagicMock diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index e54fdcafd1d..f795e19d91f 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Hass.io.""" + import os import re from unittest.mock import Mock, patch diff --git a/tests/components/hassio/test_binary_sensor.py b/tests/components/hassio/test_binary_sensor.py index 854a6782b1a..d502d6ea730 100644 --- a/tests/components/hassio/test_binary_sensor.py +++ b/tests/components/hassio/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the hassio binary sensors.""" + import os from unittest.mock import patch diff --git a/tests/components/hassio/test_system_health.py b/tests/components/hassio/test_system_health.py index bce3e5991fb..873365aa3a0 100644 --- a/tests/components/hassio/test_system_health.py +++ b/tests/components/hassio/test_system_health.py @@ -1,4 +1,5 @@ """Test hassio system health.""" + import asyncio import os from unittest.mock import patch diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py index b2f9e06cb43..7152b248336 100644 --- a/tests/components/hassio/test_websocket_api.py +++ b/tests/components/hassio/test_websocket_api.py @@ -1,4 +1,5 @@ """Test websocket API.""" + import pytest from homeassistant.components.hassio.const import ( diff --git a/tests/components/hddtemp/test_sensor.py b/tests/components/hddtemp/test_sensor.py index 70a9910595c..170f30eae37 100644 --- a/tests/components/hddtemp/test_sensor.py +++ b/tests/components/hddtemp/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the hddtemp platform.""" + import socket from unittest.mock import patch diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index 761ab45dabc..4aca77fd90f 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -1,4 +1,5 @@ """Tests for the init module.""" + import asyncio from unittest.mock import Mock, patch diff --git a/tests/components/heos/test_media_player.py b/tests/components/heos/test_media_player.py index 70d96a0d5cb..99d09cfb7b1 100644 --- a/tests/components/heos/test_media_player.py +++ b/tests/components/heos/test_media_player.py @@ -1,4 +1,5 @@ """Tests for the Heos Media Player platform.""" + import asyncio from pyheos import CommandFailedError, const diff --git a/tests/components/here_travel_time/conftest.py b/tests/components/here_travel_time/conftest.py index dff91a4e1fb..3cbbc6d8c78 100644 --- a/tests/components/here_travel_time/conftest.py +++ b/tests/components/here_travel_time/conftest.py @@ -1,4 +1,5 @@ """Fixtures for HERE Travel Time tests.""" + import json from unittest.mock import patch diff --git a/tests/components/history/conftest.py b/tests/components/history/conftest.py index 86d8d2a9421..0ce6a190f55 100644 --- a/tests/components/history/conftest.py +++ b/tests/components/history/conftest.py @@ -1,4 +1,5 @@ """Fixtures for history tests.""" + import pytest from homeassistant.components import history diff --git a/tests/components/history/test_websocket_api.py b/tests/components/history/test_websocket_api.py index 9ba47303e53..72fd9420a61 100644 --- a/tests/components/history/test_websocket_api.py +++ b/tests/components/history/test_websocket_api.py @@ -1,4 +1,5 @@ """The tests the History component websocket_api.""" + import asyncio from datetime import timedelta from unittest.mock import patch diff --git a/tests/components/hko/conftest.py b/tests/components/hko/conftest.py index fd2181ddfc9..853eca6507b 100644 --- a/tests/components/hko/conftest.py +++ b/tests/components/hko/conftest.py @@ -1,4 +1,5 @@ """Configure py.test.""" + import json from unittest.mock import patch diff --git a/tests/components/hlk_sw16/test_config_flow.py b/tests/components/hlk_sw16/test_config_flow.py index d390bcc6c79..3079d72baf8 100644 --- a/tests/components/hlk_sw16/test_config_flow.py +++ b/tests/components/hlk_sw16/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Hi-Link HLK-SW16 config flow.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/homeassistant/test_exposed_entities.py b/tests/components/homeassistant/test_exposed_entities.py index fd09bcee45a..e20fcb69d00 100644 --- a/tests/components/homeassistant/test_exposed_entities.py +++ b/tests/components/homeassistant/test_exposed_entities.py @@ -1,4 +1,5 @@ """Test Home Assistant exposed entities helper.""" + import pytest from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 09a9ffc58b8..2294e7a8f75 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -1,4 +1,5 @@ """The tests for Core components.""" + import asyncio import unittest from unittest.mock import Mock, patch diff --git a/tests/components/homeassistant/triggers/test_event.py b/tests/components/homeassistant/triggers/test_event.py index d996cd74da7..af781bd1802 100644 --- a/tests/components/homeassistant/triggers/test_event.py +++ b/tests/components/homeassistant/triggers/test_event.py @@ -1,4 +1,5 @@ """The tests for the Event automation.""" + import pytest import homeassistant.components.automation as automation diff --git a/tests/components/homekit/test_aidmanager.py b/tests/components/homekit/test_aidmanager.py index 64c5cd9cc74..9cd618e307c 100644 --- a/tests/components/homekit/test_aidmanager.py +++ b/tests/components/homekit/test_aidmanager.py @@ -1,4 +1,5 @@ """Tests for the HomeKit AID manager.""" + import os from unittest.mock import patch diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index db2e6c6e8ea..34770428d89 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -1,4 +1,5 @@ """Test different accessory types: Camera.""" + import asyncio from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from uuid import UUID diff --git a/tests/components/homekit/test_type_locks.py b/tests/components/homekit/test_type_locks.py index 3a9f7d93d88..4d83fe41f48 100644 --- a/tests/components/homekit/test_type_locks.py +++ b/tests/components/homekit/test_type_locks.py @@ -1,4 +1,5 @@ """Test different accessory types: Locks.""" + import pytest from homeassistant.components.homekit.const import ATTR_VALUE diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 33eb1e6c4ee..b17f16231af 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -1,4 +1,5 @@ """Test different accessory types: Media Players.""" + import pytest from homeassistant.components.homekit.accessories import HomeDriver diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 904b752205e..ae2ca721cfa 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -1,4 +1,5 @@ """HomeKit controller session fixtures.""" + import datetime import unittest.mock diff --git a/tests/components/homekit_controller/test_camera.py b/tests/components/homekit_controller/test_camera.py index f74f2e62772..de64ee95d74 100644 --- a/tests/components/homekit_controller/test_camera.py +++ b/tests/components/homekit_controller/test_camera.py @@ -1,4 +1,5 @@ """Basic checks for HomeKit cameras.""" + import base64 from aiohomekit.model.services import ServicesTypes diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 2b375566b5b..6b658e9eef4 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for homekit_controller config flow.""" + import asyncio from ipaddress import ip_address import unittest.mock diff --git a/tests/components/homematic/test_notify.py b/tests/components/homematic/test_notify.py index bccf5884d35..33c9b0f359e 100644 --- a/tests/components/homematic/test_notify.py +++ b/tests/components/homematic/test_notify.py @@ -1,4 +1,5 @@ """The tests for the Homematic notification platform.""" + import homeassistant.components.notify as notify_comp from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/homematicip_cloud/helper.py b/tests/components/homematicip_cloud/helper.py index 7b4071e9808..d073cfc4e3d 100644 --- a/tests/components/homematicip_cloud/helper.py +++ b/tests/components/homematicip_cloud/helper.py @@ -1,4 +1,5 @@ """Helper for HomematicIP Cloud Tests.""" + import json from unittest.mock import Mock, patch diff --git a/tests/components/homematicip_cloud/test_climate.py b/tests/components/homematicip_cloud/test_climate.py index 20193d91239..9ede89859dc 100644 --- a/tests/components/homematicip_cloud/test_climate.py +++ b/tests/components/homematicip_cloud/test_climate.py @@ -1,4 +1,5 @@ """Tests for HomematicIP Cloud climate.""" + import datetime from homematicip.base.enums import AbsenceType diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index 743689da43d..751ba8aa288 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -1,4 +1,5 @@ """Test the Whirlpool Sixth Sense climate domain.""" + import datetime from unittest.mock import MagicMock diff --git a/tests/components/http/conftest.py b/tests/components/http/conftest.py index ed2c78bafd7..60b1b73ff83 100644 --- a/tests/components/http/conftest.py +++ b/tests/components/http/conftest.py @@ -1,4 +1,5 @@ """Test configuration for http.""" + import pytest diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index a1be00e3a2b..4ed2e56f1a5 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -1,4 +1,5 @@ """The tests for the Home Assistant HTTP component.""" + import asyncio from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/http/test_security_filter.py b/tests/components/http/test_security_filter.py index 0cd85b48b06..c5b928b426f 100644 --- a/tests/components/http/test_security_filter.py +++ b/tests/components/http/test_security_filter.py @@ -1,4 +1,5 @@ """Test security filter middleware.""" + import asyncio from http import HTTPStatus diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index 3350ea15185..f87faf6294b 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -1,4 +1,5 @@ """Test helpers for Hue.""" + import asyncio from collections import deque import json diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 5c27b532570..946240116d8 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -1,4 +1,5 @@ """Test Hue bridge.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/humidifier/test_device_action.py b/tests/components/humidifier/test_device_action.py index ff508bd3a67..e8ac6006451 100644 --- a/tests/components/humidifier/test_device_action.py +++ b/tests/components/humidifier/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Humidifier device actions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index 224c69b9fb5..7864a119393 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Humidifier device conditions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index 34067d96ff2..f6fcd450768 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Humidifier device triggers.""" + import datetime import pytest diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py index d8c9f199f57..20e23b14605 100644 --- a/tests/components/humidifier/test_intent.py +++ b/tests/components/humidifier/test_intent.py @@ -1,4 +1,5 @@ """Tests for the humidifier intents.""" + import pytest from homeassistant.components.humidifier import ( diff --git a/tests/components/humidifier/test_reproduce_state.py b/tests/components/humidifier/test_reproduce_state.py index cedb0284104..cab3a11e609 100644 --- a/tests/components/humidifier/test_reproduce_state.py +++ b/tests/components/humidifier/test_reproduce_state.py @@ -1,4 +1,5 @@ """The tests for reproduction of state.""" + import pytest from homeassistant.components.humidifier.const import ( diff --git a/tests/components/humidifier/test_significant_change.py b/tests/components/humidifier/test_significant_change.py index 3d1b2a7e1ab..93e22d7ddaa 100644 --- a/tests/components/humidifier/test_significant_change.py +++ b/tests/components/humidifier/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Humidifier significant change platform.""" + import pytest from homeassistant.components.humidifier import ( diff --git a/tests/components/hvv_departures/test_config_flow.py b/tests/components/hvv_departures/test_config_flow.py index 0d885febb3c..11e5da00d11 100644 --- a/tests/components/hvv_departures/test_config_flow.py +++ b/tests/components/hvv_departures/test_config_flow.py @@ -1,4 +1,5 @@ """Test the HVV Departures config flow.""" + import json from unittest.mock import patch diff --git a/tests/components/iaqualink/conftest.py b/tests/components/iaqualink/conftest.py index b4db99dbe40..c7e7373f4c2 100644 --- a/tests/components/iaqualink/conftest.py +++ b/tests/components/iaqualink/conftest.py @@ -1,4 +1,5 @@ """Configuration for iAqualink tests.""" + import random from unittest.mock import AsyncMock, PropertyMock, patch diff --git a/tests/components/iaqualink/test_init.py b/tests/components/iaqualink/test_init.py index a1e3ee6ac35..8c64b1072d6 100644 --- a/tests/components/iaqualink/test_init.py +++ b/tests/components/iaqualink/test_init.py @@ -1,4 +1,5 @@ """Tests for iAqualink integration.""" + import logging from unittest.mock import AsyncMock, patch diff --git a/tests/components/ibeacon/test_init.py b/tests/components/ibeacon/test_init.py index b29cc3a4b2e..99c45b3dfe7 100644 --- a/tests/components/ibeacon/test_init.py +++ b/tests/components/ibeacon/test_init.py @@ -1,4 +1,5 @@ """Test the ibeacon init.""" + import pytest from homeassistant.components.ibeacon.const import DOMAIN diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index fd3c34a506c..d208a1d42ae 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the IGN Sismologia (Earthquakes) Feed platform.""" + import datetime from unittest.mock import MagicMock, call, patch diff --git a/tests/components/image/test_init.py b/tests/components/image/test_init.py index e68a58d7298..e407b142c52 100644 --- a/tests/components/image/test_init.py +++ b/tests/components/image/test_init.py @@ -1,4 +1,5 @@ """The tests for the image component.""" + import datetime from http import HTTPStatus import ssl diff --git a/tests/components/image_upload/test_init.py b/tests/components/image_upload/test_init.py index 9f842d25b64..394c0672485 100644 --- a/tests/components/image_upload/test_init.py +++ b/tests/components/image_upload/test_init.py @@ -1,4 +1,5 @@ """Test that we can upload images.""" + import pathlib import tempfile from unittest.mock import patch diff --git a/tests/components/imap/test_config_flow.py b/tests/components/imap/test_config_flow.py index 177aba04950..9d9edae5b14 100644 --- a/tests/components/imap/test_config_flow.py +++ b/tests/components/imap/test_config_flow.py @@ -1,4 +1,5 @@ """Test the imap config flow.""" + import ssl from unittest.mock import AsyncMock, patch diff --git a/tests/components/imap/test_init.py b/tests/components/imap/test_init.py index 9c194bf08a0..8608963413a 100644 --- a/tests/components/imap/test_init.py +++ b/tests/components/imap/test_init.py @@ -1,4 +1,5 @@ """Test the imap entry initialization.""" + import asyncio from datetime import datetime, timedelta, timezone from typing import Any diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index 65d7fb93d19..2a616691e62 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -1,4 +1,5 @@ """The tests for the input_boolean component.""" + import logging from unittest.mock import patch diff --git a/tests/components/input_button/test_init.py b/tests/components/input_button/test_init.py index 2f9b677e134..568d0076318 100644 --- a/tests/components/input_button/test_init.py +++ b/tests/components/input_button/test_init.py @@ -1,4 +1,5 @@ """The tests for the input_test component.""" + import logging from unittest.mock import patch diff --git a/tests/components/input_datetime/test_init.py b/tests/components/input_datetime/test_init.py index 0a3f9b3ed6c..9d218e6d6ec 100644 --- a/tests/components/input_datetime/test_init.py +++ b/tests/components/input_datetime/test_init.py @@ -1,4 +1,5 @@ """Tests for the Input slider component.""" + import datetime from unittest.mock import patch diff --git a/tests/components/input_datetime/test_reproduce_state.py b/tests/components/input_datetime/test_reproduce_state.py index b5f75b81068..323849bc882 100644 --- a/tests/components/input_datetime/test_reproduce_state.py +++ b/tests/components/input_datetime/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Input datetime.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/input_number/test_reproduce_state.py b/tests/components/input_number/test_reproduce_state.py index 087b2fe1001..6c8c61a0b58 100644 --- a/tests/components/input_number/test_reproduce_state.py +++ b/tests/components/input_number/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Input number.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/input_select/test_reproduce_state.py b/tests/components/input_select/test_reproduce_state.py index a00b6b02ade..13672ebc708 100644 --- a/tests/components/input_select/test_reproduce_state.py +++ b/tests/components/input_select/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Input select.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/input_text/test_reproduce_state.py b/tests/components/input_text/test_reproduce_state.py index 877a784853a..88b8131000b 100644 --- a/tests/components/input_text/test_reproduce_state.py +++ b/tests/components/input_text/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Input text.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/insteon/test_api_aldb.py b/tests/components/insteon/test_api_aldb.py index 4bd299d3d05..4e0df12c6f1 100644 --- a/tests/components/insteon/test_api_aldb.py +++ b/tests/components/insteon/test_api_aldb.py @@ -1,4 +1,5 @@ """Test the Insteon All-Link Database APIs.""" + import json from unittest.mock import patch diff --git a/tests/components/insteon/test_api_device.py b/tests/components/insteon/test_api_device.py index 7485914026a..f3c67d479d0 100644 --- a/tests/components/insteon/test_api_device.py +++ b/tests/components/insteon/test_api_device.py @@ -1,4 +1,5 @@ """Test the device level APIs.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/insteon/test_api_properties.py b/tests/components/insteon/test_api_properties.py index 850ccc85411..d2a388929b5 100644 --- a/tests/components/insteon/test_api_properties.py +++ b/tests/components/insteon/test_api_properties.py @@ -1,4 +1,5 @@ """Test the Insteon properties APIs.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/insteon/test_api_scenes.py b/tests/components/insteon/test_api_scenes.py index cc9b11f4632..aa4eb774229 100644 --- a/tests/components/insteon/test_api_scenes.py +++ b/tests/components/insteon/test_api_scenes.py @@ -1,4 +1,5 @@ """Test the Insteon Scenes APIs.""" + import json import os from unittest.mock import AsyncMock, patch diff --git a/tests/components/insteon/test_init.py b/tests/components/insteon/test_init.py index f772eed2d26..bfc8a8b8cc1 100644 --- a/tests/components/insteon/test_init.py +++ b/tests/components/insteon/test_init.py @@ -1,4 +1,5 @@ """Test the init file for the Insteon component.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/integration/test_init.py b/tests/components/integration/test_init.py index 885c10277f8..9f9decaef45 100644 --- a/tests/components/integration/test_init.py +++ b/tests/components/integration/test_init.py @@ -1,4 +1,5 @@ """Test the Integration - Riemann sum integral integration.""" + import pytest from homeassistant.components.integration.const import DOMAIN diff --git a/tests/components/iotawatt/test_init.py b/tests/components/iotawatt/test_init.py index cdf58a0a2d2..c185fec0e4d 100644 --- a/tests/components/iotawatt/test_init.py +++ b/tests/components/iotawatt/test_init.py @@ -1,4 +1,5 @@ """Test init.""" + import httpx from homeassistant.config_entries import ConfigEntryState diff --git a/tests/components/ipma/test_system_health.py b/tests/components/ipma/test_system_health.py index b4cefad80c0..5d532611969 100644 --- a/tests/components/ipma/test_system_health.py +++ b/tests/components/ipma/test_system_health.py @@ -1,4 +1,5 @@ """Test ipma system health.""" + import asyncio from homeassistant.components.ipma.system_health import IPMA_API_URL diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py index 9e0262733a3..06f15922a2e 100644 --- a/tests/components/ipma/test_weather.py +++ b/tests/components/ipma/test_weather.py @@ -1,4 +1,5 @@ """The tests for the IPMA weather component.""" + import datetime from unittest.mock import patch diff --git a/tests/components/ipp/test_config_flow.py b/tests/components/ipp/test_config_flow.py index 5dd6c1af5bf..1ea75ecf167 100644 --- a/tests/components/ipp/test_config_flow.py +++ b/tests/components/ipp/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the IPP config flow.""" + import dataclasses from ipaddress import ip_address import json diff --git a/tests/components/iqvia/conftest.py b/tests/components/iqvia/conftest.py index b24d473c7df..c1d0341aaa6 100644 --- a/tests/components/iqvia/conftest.py +++ b/tests/components/iqvia/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for IQVIA.""" + import json from unittest.mock import patch diff --git a/tests/components/isy994/test_config_flow.py b/tests/components/isy994/test_config_flow.py index 4a5bfb007f0..e20c4f0b185 100644 --- a/tests/components/isy994/test_config_flow.py +++ b/tests/components/isy994/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Universal Devices ISY/IoX config flow.""" + import re from unittest.mock import patch diff --git a/tests/components/isy994/test_system_health.py b/tests/components/isy994/test_system_health.py index fa5bab0025c..5f472189513 100644 --- a/tests/components/isy994/test_system_health.py +++ b/tests/components/isy994/test_system_health.py @@ -1,4 +1,5 @@ """Test ISY system health.""" + import asyncio from unittest.mock import Mock diff --git a/tests/components/kira/test_init.py b/tests/components/kira/test_init.py index 50c2f4f4651..6b6ad4c1fcf 100644 --- a/tests/components/kira/test_init.py +++ b/tests/components/kira/test_init.py @@ -1,4 +1,5 @@ """The tests for Kira.""" + import os import shutil import tempfile diff --git a/tests/components/kitchen_sink/test_init.py b/tests/components/kitchen_sink/test_init.py index fb81c87008e..1547a10bd2b 100644 --- a/tests/components/kitchen_sink/test_init.py +++ b/tests/components/kitchen_sink/test_init.py @@ -1,4 +1,5 @@ """The tests for the Everything but the Kitchen Sink integration.""" + import datetime from http import HTTPStatus from unittest.mock import ANY diff --git a/tests/components/knx/test_device_trigger.py b/tests/components/knx/test_device_trigger.py index f3448947cf8..3c8bf58169b 100644 --- a/tests/components/knx/test_device_trigger.py +++ b/tests/components/knx/test_device_trigger.py @@ -1,4 +1,5 @@ """Tests for KNX device triggers.""" + import pytest import voluptuous_serialize diff --git a/tests/components/knx/test_events.py b/tests/components/knx/test_events.py index f5c1aed7fde..ddb9d50240c 100644 --- a/tests/components/knx/test_events.py +++ b/tests/components/knx/test_events.py @@ -1,4 +1,5 @@ """Test KNX events.""" + import logging import pytest diff --git a/tests/components/knx/test_number.py b/tests/components/knx/test_number.py index a0ef514f5f7..be3fe070c10 100644 --- a/tests/components/knx/test_number.py +++ b/tests/components/knx/test_number.py @@ -1,4 +1,5 @@ """Test KNX number.""" + import pytest from homeassistant.components.knx.const import CONF_RESPOND_TO_READ, KNX_ADDRESS diff --git a/tests/components/knx/test_select.py b/tests/components/knx/test_select.py index 1b408a298a2..b53dfae2658 100644 --- a/tests/components/knx/test_select.py +++ b/tests/components/knx/test_select.py @@ -1,4 +1,5 @@ """Test KNX select.""" + import pytest from homeassistant.components.knx.const import ( diff --git a/tests/components/kodi/test_device_trigger.py b/tests/components/kodi/test_device_trigger.py index 4dbfe3abb57..1737fe5d7c9 100644 --- a/tests/components/kodi/test_device_trigger.py +++ b/tests/components/kodi/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Kodi device triggers.""" + import pytest import homeassistant.components.automation as automation diff --git a/tests/components/kraken/const.py b/tests/components/kraken/const.py index 263618dfd2d..9afd2d84007 100644 --- a/tests/components/kraken/const.py +++ b/tests/components/kraken/const.py @@ -1,4 +1,5 @@ """Constants for kraken tests.""" + import pandas as pd TRADEABLE_ASSET_PAIR_RESPONSE = pd.DataFrame( diff --git a/tests/components/lametric/test_sensor.py b/tests/components/lametric/test_sensor.py index 743ed0e8bde..bb15ebd5aa4 100644 --- a/tests/components/lametric/test_sensor.py +++ b/tests/components/lametric/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the LaMetric sensor platform.""" + import pytest from homeassistant.components.lametric.const import DOMAIN diff --git a/tests/components/landisgyr_heat_meter/test_sensor.py b/tests/components/landisgyr_heat_meter/test_sensor.py index f05d12e49a2..1578c67432d 100644 --- a/tests/components/landisgyr_heat_meter/test_sensor.py +++ b/tests/components/landisgyr_heat_meter/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Landis+Gyr Heat Meter sensor platform.""" + import datetime from unittest.mock import patch diff --git a/tests/components/laundrify/conftest.py b/tests/components/laundrify/conftest.py index 13e408b61a9..18884cdfb7b 100644 --- a/tests/components/laundrify/conftest.py +++ b/tests/components/laundrify/conftest.py @@ -1,4 +1,5 @@ """Configure py.test.""" + import json from unittest.mock import patch diff --git a/tests/components/lcn/conftest.py b/tests/components/lcn/conftest.py index a6bcdf63950..2e50f20561e 100644 --- a/tests/components/lcn/conftest.py +++ b/tests/components/lcn/conftest.py @@ -1,4 +1,5 @@ """Test configuration and mocks for LCN component.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index 3b60b886b02..6fd83d9c29a 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -1,4 +1,5 @@ """The test for light device automation.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/light/test_reproduce_state.py b/tests/components/light/test_reproduce_state.py index 65b83aa0269..ed02ad891e2 100644 --- a/tests/components/light/test_reproduce_state.py +++ b/tests/components/light/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Light.""" + import pytest from homeassistant.components import light diff --git a/tests/components/lock/test_device_action.py b/tests/components/lock/test_device_action.py index 1e451920baf..0356bc206cc 100644 --- a/tests/components/lock/test_device_action.py +++ b/tests/components/lock/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Lock device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/lock/test_device_condition.py b/tests/components/lock/test_device_condition.py index 59dcbcb4629..31aa9aab143 100644 --- a/tests/components/lock/test_device_condition.py +++ b/tests/components/lock/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Lock device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/lock/test_reproduce_state.py b/tests/components/lock/test_reproduce_state.py index 4f3c94f0b90..4fa06d9320b 100644 --- a/tests/components/lock/test_reproduce_state.py +++ b/tests/components/lock/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Lock.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 803aaba346a..732437107de 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1,4 +1,5 @@ """The tests for the logbook component.""" + import asyncio import collections from collections.abc import Callable diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py index 7100f914ff4..d17ed34cda4 100644 --- a/tests/components/logbook/test_websocket_api.py +++ b/tests/components/logbook/test_websocket_api.py @@ -1,4 +1,5 @@ """The tests for the logbook component.""" + import asyncio from collections.abc import Callable from datetime import timedelta diff --git a/tests/components/logger/conftest.py b/tests/components/logger/conftest.py index 00d27753a61..76fbde68e78 100644 --- a/tests/components/logger/conftest.py +++ b/tests/components/logger/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the Logger component.""" + import logging import pytest diff --git a/tests/components/logger/test_websocket_api.py b/tests/components/logger/test_websocket_api.py index 10c1ceb2f20..15252ad2661 100644 --- a/tests/components/logger/test_websocket_api.py +++ b/tests/components/logger/test_websocket_api.py @@ -1,4 +1,5 @@ """Tests for Logger Websocket API commands.""" + import logging from unittest.mock import patch diff --git a/tests/components/logi_circle/test_config_flow.py b/tests/components/logi_circle/test_config_flow.py index 2c1642aefba..f0de828c186 100644 --- a/tests/components/logi_circle/test_config_flow.py +++ b/tests/components/logi_circle/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Logi Circle config flow.""" + import asyncio from http import HTTPStatus from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/lovelace/test_resources.py b/tests/components/lovelace/test_resources.py index 4a280eccfda..7591960b589 100644 --- a/tests/components/lovelace/test_resources.py +++ b/tests/components/lovelace/test_resources.py @@ -1,4 +1,5 @@ """Test Lovelace resources.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mailgun/test_init.py b/tests/components/mailgun/test_init.py index 03bbfd80287..a36051bd102 100644 --- a/tests/components/mailgun/test_init.py +++ b/tests/components/mailgun/test_init.py @@ -1,4 +1,5 @@ """Test the init file of Mailgun.""" + import hashlib import hmac diff --git a/tests/components/media_extractor/test_init.py b/tests/components/media_extractor/test_init.py index d32ad90d87c..4a979b6aec6 100644 --- a/tests/components/media_extractor/test_init.py +++ b/tests/components/media_extractor/test_init.py @@ -1,4 +1,5 @@ """The tests for Media Extractor integration.""" + import os import os.path from typing import Any diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py index f3b70187f33..e3d89a9ca2e 100644 --- a/tests/components/media_player/test_async_helpers.py +++ b/tests/components/media_player/test_async_helpers.py @@ -1,4 +1,5 @@ """The tests for the Async Media player helper functions.""" + import pytest import homeassistant.components.media_player as mp diff --git a/tests/components/media_player/test_device_condition.py b/tests/components/media_player/test_device_condition.py index ea1f65eab95..8e085152954 100644 --- a/tests/components/media_player/test_device_condition.py +++ b/tests/components/media_player/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Media player device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/media_player/test_reproduce_state.py b/tests/components/media_player/test_reproduce_state.py index 0ff930dbdbd..0759296ce35 100644 --- a/tests/components/media_player/test_reproduce_state.py +++ b/tests/components/media_player/test_reproduce_state.py @@ -1,4 +1,5 @@ """The tests for reproduction of state.""" + import pytest from homeassistant.components.media_player import ( diff --git a/tests/components/media_player/test_significant_change.py b/tests/components/media_player/test_significant_change.py index 233f133c342..e0635391042 100644 --- a/tests/components/media_player/test_significant_change.py +++ b/tests/components/media_player/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Media Player significant change platform.""" + import pytest from homeassistant.components.media_player import ( diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index dc2ca4391f1..7903744ff13 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -1,4 +1,5 @@ """Test for Melissa climate component.""" + import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/met/test_init.py b/tests/components/met/test_init.py index 0e4e46b09da..b329e2ff01c 100644 --- a/tests/components/met/test_init.py +++ b/tests/components/met/test_init.py @@ -1,4 +1,5 @@ """Test the Met integration init.""" + import pytest from homeassistant.components.met.const import ( diff --git a/tests/components/met_eireann/test_weather.py b/tests/components/met_eireann/test_weather.py index e5c2c66b626..1c938f97574 100644 --- a/tests/components/met_eireann/test_weather.py +++ b/tests/components/met_eireann/test_weather.py @@ -1,4 +1,5 @@ """Test Met Éireann weather entity.""" + import datetime from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/metoffice/test_config_flow.py b/tests/components/metoffice/test_config_flow.py index 020f5ff154b..d5749dad035 100644 --- a/tests/components/metoffice/test_config_flow.py +++ b/tests/components/metoffice/test_config_flow.py @@ -1,4 +1,5 @@ """Test the National Weather Service (NWS) config flow.""" + import json from unittest.mock import patch diff --git a/tests/components/metoffice/test_sensor.py b/tests/components/metoffice/test_sensor.py index 6e40dd66efe..87d3f66aeb5 100644 --- a/tests/components/metoffice/test_sensor.py +++ b/tests/components/metoffice/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Met Office sensor component.""" + import datetime import json diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index 19c27873d5e..85d66bf7686 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -1,4 +1,5 @@ """The tests for the Met Office sensor component.""" + import datetime from datetime import timedelta import json diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index 00daa4c353d..fb4e42fb137 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -1,4 +1,5 @@ """The tests for the mFi switch platform.""" + import unittest.mock as mock import pytest diff --git a/tests/components/microbees/conftest.py b/tests/components/microbees/conftest.py index a27d1e01194..e89d4780ad9 100644 --- a/tests/components/microbees/conftest.py +++ b/tests/components/microbees/conftest.py @@ -1,4 +1,5 @@ """Conftest for microBees tests.""" + import time from unittest.mock import AsyncMock, patch diff --git a/tests/components/min_max/test_init.py b/tests/components/min_max/test_init.py index cd07f7060f6..f6c67e60326 100644 --- a/tests/components/min_max/test_init.py +++ b/tests/components/min_max/test_init.py @@ -1,4 +1,5 @@ """Test the Min/Max integration.""" + import pytest from homeassistant.components.min_max.const import DOMAIN diff --git a/tests/components/min_max/test_sensor.py b/tests/components/min_max/test_sensor.py index acd42f9355e..4d86ee72cc6 100644 --- a/tests/components/min_max/test_sensor.py +++ b/tests/components/min_max/test_sensor.py @@ -1,4 +1,5 @@ """The test for the min/max sensor platform.""" + import statistics from unittest.mock import patch diff --git a/tests/components/minecraft_server/conftest.py b/tests/components/minecraft_server/conftest.py index b118b15d08a..ef8a9d960f6 100644 --- a/tests/components/minecraft_server/conftest.py +++ b/tests/components/minecraft_server/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Minecraft Server integration tests.""" + import pytest from homeassistant.components.minecraft_server.api import MinecraftServerType diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index 7455ede2994..746085fafc2 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -1,4 +1,5 @@ """Tests for Minio Hass related code.""" + import asyncio import json from unittest.mock import MagicMock, call, patch diff --git a/tests/components/mochad/test_light.py b/tests/components/mochad/test_light.py index 8bd4e1e2a59..b04f9a13933 100644 --- a/tests/components/mochad/test_light.py +++ b/tests/components/mochad/test_light.py @@ -1,4 +1,5 @@ """The tests for the mochad light platform.""" + import unittest.mock as mock import pytest diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index 9780ac3a481..e89a9ec6269 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -1,4 +1,5 @@ """The tests for the mochad switch platform.""" + import unittest.mock as mock import pytest diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index d7e4556f746..4fdda5d9aea 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -1,4 +1,5 @@ """The tests for the Modbus sensor component.""" + import copy from dataclasses import dataclass from datetime import timedelta diff --git a/tests/components/modbus/test_binary_sensor.py b/tests/components/modbus/test_binary_sensor.py index e47a6165b30..567618de3c6 100644 --- a/tests/components/modbus/test_binary_sensor.py +++ b/tests/components/modbus/test_binary_sensor.py @@ -1,4 +1,5 @@ """Thetests for the Modbus sensor component.""" + import pytest from homeassistant.components.binary_sensor import DOMAIN as SENSOR_DOMAIN diff --git a/tests/components/modbus/test_climate.py b/tests/components/modbus/test_climate.py index 47d468ee1d8..3752358c071 100644 --- a/tests/components/modbus/test_climate.py +++ b/tests/components/modbus/test_climate.py @@ -1,4 +1,5 @@ """The tests for the Modbus climate component.""" + import pytest from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index aa8b15585dc..1c6b332d391 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Modbus sensor component.""" + import struct import pytest diff --git a/tests/components/mold_indicator/test_sensor.py b/tests/components/mold_indicator/test_sensor.py index bfea5bccde4..0acea3d03e6 100644 --- a/tests/components/mold_indicator/test_sensor.py +++ b/tests/components/mold_indicator/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the MoldIndicator sensor.""" + import pytest from homeassistant.components.mold_indicator.sensor import ( diff --git a/tests/components/motion_blinds/test_config_flow.py b/tests/components/motion_blinds/test_config_flow.py index 43051e88562..5ffb0b356ad 100644 --- a/tests/components/motion_blinds/test_config_flow.py +++ b/tests/components/motion_blinds/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Motionblinds config flow.""" + import socket from unittest.mock import Mock, patch diff --git a/tests/components/motioneye/test_camera.py b/tests/components/motioneye/test_camera.py index 5af8d4139eb..32763fbed3a 100644 --- a/tests/components/motioneye/test_camera.py +++ b/tests/components/motioneye/test_camera.py @@ -1,4 +1,5 @@ """Test the motionEye camera.""" + import copy from typing import Any, cast from unittest.mock import AsyncMock, Mock, call diff --git a/tests/components/motioneye/test_media_source.py b/tests/components/motioneye/test_media_source.py index 6b90870c4da..f895ed7fcb2 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -1,4 +1,5 @@ """Test Local Media Source.""" + import logging from unittest.mock import AsyncMock, Mock, call diff --git a/tests/components/motioneye/test_sensor.py b/tests/components/motioneye/test_sensor.py index 0892c0dead0..02e34c16c30 100644 --- a/tests/components/motioneye/test_sensor.py +++ b/tests/components/motioneye/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the motionEye switch platform.""" + import copy from datetime import timedelta from unittest.mock import AsyncMock, patch diff --git a/tests/components/motioneye/test_switch.py b/tests/components/motioneye/test_switch.py index a6fbcc49052..56401e7a28d 100644 --- a/tests/components/motioneye/test_switch.py +++ b/tests/components/motioneye/test_switch.py @@ -1,4 +1,5 @@ """Tests for the motionEye switch platform.""" + import copy from datetime import timedelta from unittest.mock import AsyncMock, call, patch diff --git a/tests/components/motioneye/test_web_hooks.py b/tests/components/motioneye/test_web_hooks.py index 7c66645bb44..a828e5964d0 100644 --- a/tests/components/motioneye/test_web_hooks.py +++ b/tests/components/motioneye/test_web_hooks.py @@ -1,4 +1,5 @@ """Test the motionEye camera web hooks.""" + import copy from http import HTTPStatus from unittest.mock import AsyncMock, Mock, call, patch diff --git a/tests/components/motionmount/test_config_flow.py b/tests/components/motionmount/test_config_flow.py index aa7ea73b577..f24c4e7a2e4 100644 --- a/tests/components/motionmount/test_config_flow.py +++ b/tests/components/motionmount/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Vogel's MotionMount config flow.""" + import dataclasses import socket from unittest.mock import MagicMock, PropertyMock diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 89c5d2ffd91..dfe8d21a23b 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """The tests the MQTT alarm control panel component.""" + import copy import json from typing import Any diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 033026226e2..c9c76bff67c 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the MQTT binary sensor platform.""" + import copy from datetime import datetime, timedelta import json diff --git a/tests/components/mqtt/test_button.py b/tests/components/mqtt/test_button.py index f2f91c5ca75..fd070e0dd11 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -1,4 +1,5 @@ """The tests for the MQTT button platform.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 8d513b98179..249715a9085 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -1,4 +1,5 @@ """The tests for the mqtt climate component.""" + import copy import json from typing import Any diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index bb8d973b9ed..e4e3149559a 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for MQTT device triggers.""" + import json from unittest.mock import patch diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index eb923ac2f07..e09b32de2d4 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -1,4 +1,5 @@ """Test MQTT diagnostics.""" + import json from unittest.mock import ANY, patch diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index f86800fd95f..6121e1dc15e 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1,4 +1,5 @@ """The tests for the MQTT discovery.""" + import asyncio import copy import json diff --git a/tests/components/mqtt/test_event.py b/tests/components/mqtt/test_event.py index bd00120d098..bcfbb1d8d40 100644 --- a/tests/components/mqtt/test_event.py +++ b/tests/components/mqtt/test_event.py @@ -1,4 +1,5 @@ """The tests for the MQTT event platform.""" + import copy import json from unittest.mock import patch diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 8980d6951e5..493e1e5ddf7 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -1,4 +1,5 @@ """Test MQTT fans.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index d76fe84d0a9..0778d36f873 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -1,4 +1,5 @@ """Test MQTT humidifiers.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 16fa06ccd27..3188f17feff 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1,4 +1,5 @@ """The tests for the MQTT component.""" + import asyncio from collections.abc import Generator from datetime import datetime, timedelta diff --git a/tests/components/mqtt/test_lawn_mower.py b/tests/components/mqtt/test_lawn_mower.py index d2680824fcd..a6e338c17da 100644 --- a/tests/components/mqtt/test_lawn_mower.py +++ b/tests/components/mqtt/test_lawn_mower.py @@ -1,4 +1,5 @@ """The tests for mqtt lawn_mower component.""" + import copy import json from typing import Any diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index aac79ae1893..749b5c5403a 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -168,6 +168,7 @@ mqtt: payload_off: "off" """ + import copy from typing import Any from unittest.mock import call, patch diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 1d154f7eb26..796ee9ef9d2 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -78,6 +78,7 @@ light: brightness: true brightness_scale: 99 """ + import copy from typing import Any from unittest.mock import call, patch diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 69e6b52de17..f562a11bb95 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -24,6 +24,7 @@ If your light doesn't support color temp feature, omit `color_temp_template`. If your light doesn't support RGB feature, omit `(red|green|blue)_template`. """ + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_number.py b/tests/components/mqtt/test_number.py index bf4e51dc519..40d7cd60df5 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -1,4 +1,5 @@ """The tests for mqtt number component.""" + import json from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_scene.py b/tests/components/mqtt/test_scene.py index 141bfc526d3..67fd794c906 100644 --- a/tests/components/mqtt/test_scene.py +++ b/tests/components/mqtt/test_scene.py @@ -1,4 +1,5 @@ """The tests for the MQTT scene platform.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index faa48012514..0345b76c241 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the MQTT sensor platform.""" + import copy from datetime import datetime, timedelta import json diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index 8d319d3a80b..c583c4dfe68 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -1,4 +1,5 @@ """The tests for the MQTT siren platform.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 46a7ebb2060..fff96b290ff 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -1,4 +1,5 @@ """The tests for the MQTT switch platform.""" + import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/mqtt/test_update.py b/tests/components/mqtt/test_update.py index 6a80e18ff23..2fcfbb59952 100644 --- a/tests/components/mqtt/test_update.py +++ b/tests/components/mqtt/test_update.py @@ -1,4 +1,5 @@ """The tests for mqtt update component.""" + import json from unittest.mock import patch diff --git a/tests/components/mqtt/test_water_heater.py b/tests/components/mqtt/test_water_heater.py index 61f430b34f8..945a2296435 100644 --- a/tests/components/mqtt/test_water_heater.py +++ b/tests/components/mqtt/test_water_heater.py @@ -1,4 +1,5 @@ """The tests for the mqtt water heater component.""" + import copy import json from typing import Any diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index 5eabb2202aa..2045a14bf1f 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -1,4 +1,5 @@ """The tests for the MQTT eventstream component.""" + import json from unittest.mock import ANY, patch diff --git a/tests/components/mqtt_room/test_sensor.py b/tests/components/mqtt_room/test_sensor.py index 822e028f4f6..bc1890f08fa 100644 --- a/tests/components/mqtt_room/test_sensor.py +++ b/tests/components/mqtt_room/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the MQTT room presence sensor.""" + import datetime import json from unittest.mock import patch diff --git a/tests/components/mythicbeastsdns/test_init.py b/tests/components/mythicbeastsdns/test_init.py index 7efcf762df2..81d17d2bf77 100644 --- a/tests/components/mythicbeastsdns/test_init.py +++ b/tests/components/mythicbeastsdns/test_init.py @@ -1,4 +1,5 @@ """Test the Mythic Beasts DNS component.""" + import logging from unittest.mock import patch diff --git a/tests/components/myuplink/test_init.py b/tests/components/myuplink/test_init.py index 17b899aa662..328dc55d4ad 100644 --- a/tests/components/myuplink/test_init.py +++ b/tests/components/myuplink/test_init.py @@ -1,4 +1,5 @@ """Tests for init module.""" + import http import time from unittest.mock import MagicMock diff --git a/tests/components/nam/test_diagnostics.py b/tests/components/nam/test_diagnostics.py index 998819128a9..9d13121392f 100644 --- a/tests/components/nam/test_diagnostics.py +++ b/tests/components/nam/test_diagnostics.py @@ -1,4 +1,5 @@ """Test NAM diagnostics.""" + import json from homeassistant.core import HomeAssistant diff --git a/tests/components/nest/test_api.py b/tests/components/nest/test_api.py index 91975f3559f..31f5d03dc2b 100644 --- a/tests/components/nest/test_api.py +++ b/tests/components/nest/test_api.py @@ -7,6 +7,7 @@ for token refresh and for testing: The tests below exercise both cases during integration setup. """ + import time from unittest.mock import patch diff --git a/tests/components/nest/test_camera.py b/tests/components/nest/test_camera.py index 647a3419501..b68173be201 100644 --- a/tests/components/nest/test_camera.py +++ b/tests/components/nest/test_camera.py @@ -3,6 +3,7 @@ These tests fake out the subscriber/devicemanager, and are not using a real pubsub subscriber. """ + import datetime from http import HTTPStatus from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/nest/test_init.py b/tests/components/nest/test_init.py index 1e3eed91f19..082f40094aa 100644 --- a/tests/components/nest/test_init.py +++ b/tests/components/nest/test_init.py @@ -7,6 +7,7 @@ By default all tests use test fixtures that run in each possible configuration mode (e.g. yaml, ConfigEntry, etc) however some tests override and just run in relevant modes. """ + import logging from typing import Any from unittest.mock import patch diff --git a/tests/components/netatmo/test_device_trigger.py b/tests/components/netatmo/test_device_trigger.py index ebafd313ff4..65d7274cdc8 100644 --- a/tests/components/netatmo/test_device_trigger.py +++ b/tests/components/netatmo/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Netatmo device triggers.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/netatmo/test_media_source.py b/tests/components/netatmo/test_media_source.py index c22bfd51acb..f9aff2749d2 100644 --- a/tests/components/netatmo/test_media_source.py +++ b/tests/components/netatmo/test_media_source.py @@ -1,4 +1,5 @@ """Test Local Media Source.""" + import ast import pytest diff --git a/tests/components/nexia/test_init.py b/tests/components/nexia/test_init.py index f920592f8a6..8eeb8a9f729 100644 --- a/tests/components/nexia/test_init.py +++ b/tests/components/nexia/test_init.py @@ -1,4 +1,5 @@ """The init tests for the nexia platform.""" + import aiohttp from homeassistant.components.nexia.const import DOMAIN diff --git a/tests/components/nextdns/test_system_health.py b/tests/components/nextdns/test_system_health.py index 4d4299621e8..758437c9acb 100644 --- a/tests/components/nextdns/test_system_health.py +++ b/tests/components/nextdns/test_system_health.py @@ -1,4 +1,5 @@ """Test NextDNS system health.""" + import asyncio from aiohttp import ClientError diff --git a/tests/components/nibe_heatpump/test_coordinator.py b/tests/components/nibe_heatpump/test_coordinator.py index 474802541f2..ffd5c545645 100644 --- a/tests/components/nibe_heatpump/test_coordinator.py +++ b/tests/components/nibe_heatpump/test_coordinator.py @@ -1,4 +1,5 @@ """Test the Nibe Heat Pump config flow.""" + import asyncio from typing import Any from unittest.mock import patch diff --git a/tests/components/notify/test_init.py b/tests/components/notify/test_init.py index 9fb5b9e531a..0b75a3c4691 100644 --- a/tests/components/notify/test_init.py +++ b/tests/components/notify/test_init.py @@ -1,4 +1,5 @@ """The tests for notify services that change targets.""" + import asyncio from pathlib import Path from unittest.mock import Mock, patch diff --git a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py index bf56cb8a985..f329e456db1 100644 --- a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py +++ b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the NSW Rural Fire Service Feeds platform.""" + import datetime from unittest.mock import ANY, MagicMock, call, patch diff --git a/tests/components/number/test_device_action.py b/tests/components/number/test_device_action.py index 17c63dd3394..17546218a14 100644 --- a/tests/components/number/test_device_action.py +++ b/tests/components/number/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Number device actions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/number/test_reproduce_state.py b/tests/components/number/test_reproduce_state.py index 01b0383ecf7..e941291c04e 100644 --- a/tests/components/number/test_reproduce_state.py +++ b/tests/components/number/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Number entities.""" + import pytest from homeassistant.components.number.const import ( diff --git a/tests/components/number/test_significant_change.py b/tests/components/number/test_significant_change.py index 1a6491f3de9..e0e02fc1d35 100644 --- a/tests/components/number/test_significant_change.py +++ b/tests/components/number/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Number significant change platform.""" + import pytest from homeassistant.components.number import NumberDeviceClass diff --git a/tests/components/nws/test_sensor.py b/tests/components/nws/test_sensor.py index 5e36c9c0717..4d29e48ae0b 100644 --- a/tests/components/nws/test_sensor.py +++ b/tests/components/nws/test_sensor.py @@ -1,4 +1,5 @@ """Sensors for National Weather Service (NWS).""" + import pytest from homeassistant.components.nws.const import ATTRIBUTION, DOMAIN diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index af9e6f9ebf0..406faff0dac 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -1,4 +1,5 @@ """Test the onboarding views.""" + import asyncio from http import HTTPStatus import os diff --git a/tests/components/onvif/test_config_flow.py b/tests/components/onvif/test_config_flow.py index fcf0ea5c9dc..5640472d6a2 100644 --- a/tests/components/onvif/test_config_flow.py +++ b/tests/components/onvif/test_config_flow.py @@ -1,4 +1,5 @@ """Test ONVIF config flow.""" + import logging from unittest.mock import MagicMock, patch diff --git a/tests/components/openexchangerates/test_config_flow.py b/tests/components/openexchangerates/test_config_flow.py index 213badcab08..e0896f64340 100644 --- a/tests/components/openexchangerates/test_config_flow.py +++ b/tests/components/openexchangerates/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Open Exchange Rates config flow.""" + import asyncio from collections.abc import Generator from typing import Any diff --git a/tests/components/openhardwaremonitor/test_sensor.py b/tests/components/openhardwaremonitor/test_sensor.py index 5598abfabc3..9bf339898a0 100644 --- a/tests/components/openhardwaremonitor/test_sensor.py +++ b/tests/components/openhardwaremonitor/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Open Hardware Monitor platform.""" + import requests_mock from homeassistant.core import HomeAssistant diff --git a/tests/components/opower/conftest.py b/tests/components/opower/conftest.py index 0ee910f84f4..12d1a0dcdce 100644 --- a/tests/components/opower/conftest.py +++ b/tests/components/opower/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the Opower integration tests.""" + import pytest from homeassistant.components.opower.const import DOMAIN diff --git a/tests/components/otbr/test_config_flow.py b/tests/components/otbr/test_config_flow.py index 03d54981b1a..18c62257803 100644 --- a/tests/components/otbr/test_config_flow.py +++ b/tests/components/otbr/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Open Thread Border Router config flow.""" + import asyncio from http import HTTPStatus from typing import Any diff --git a/tests/components/otbr/test_init.py b/tests/components/otbr/test_init.py index 89a8f433016..39805728a3e 100644 --- a/tests/components/otbr/test_init.py +++ b/tests/components/otbr/test_init.py @@ -1,4 +1,5 @@ """Test the Open Thread Border Router integration.""" + import asyncio from http import HTTPStatus from typing import Any diff --git a/tests/components/owntracks/test_device_tracker.py b/tests/components/owntracks/test_device_tracker.py index 1be21e8b1b2..d855c318fb7 100644 --- a/tests/components/owntracks/test_device_tracker.py +++ b/tests/components/owntracks/test_device_tracker.py @@ -1,4 +1,5 @@ """The tests for the Owntracks device tracker.""" + import json from unittest.mock import patch diff --git a/tests/components/owntracks/test_init.py b/tests/components/owntracks/test_init.py index a54a841030b..7e85b67f9de 100644 --- a/tests/components/owntracks/test_init.py +++ b/tests/components/owntracks/test_init.py @@ -1,4 +1,5 @@ """Test the owntracks_http platform.""" + import pytest from homeassistant.components import owntracks diff --git a/tests/components/p1_monitor/conftest.py b/tests/components/p1_monitor/conftest.py index 5f6713f5228..e95cb245f5e 100644 --- a/tests/components/p1_monitor/conftest.py +++ b/tests/components/p1_monitor/conftest.py @@ -1,4 +1,5 @@ """Fixtures for P1 Monitor integration tests.""" + import json from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index bd8950163a9..147ab879ed6 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -1,4 +1,5 @@ """The tests for the panel_iframe component.""" + import pytest from homeassistant.components import frontend diff --git a/tests/components/person/conftest.py b/tests/components/person/conftest.py index 4079ff24267..7f06b854c5c 100644 --- a/tests/components/person/conftest.py +++ b/tests/components/person/conftest.py @@ -1,4 +1,5 @@ """The tests for the person component.""" + import logging import pytest diff --git a/tests/components/philips_js/test_binary_sensor.py b/tests/components/philips_js/test_binary_sensor.py index 01233706d07..c50c776d416 100644 --- a/tests/components/philips_js/test_binary_sensor.py +++ b/tests/components/philips_js/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for philips_js binary_sensor.""" + import pytest from homeassistant.const import STATE_OFF, STATE_ON diff --git a/tests/components/philips_js/test_device_trigger.py b/tests/components/philips_js/test_device_trigger.py index 0e8427b29e5..3a59e3c6662 100644 --- a/tests/components/philips_js/test_device_trigger.py +++ b/tests/components/philips_js/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Philips TV device triggers.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index b0b71fe13ed..a58a46680bb 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -1,4 +1,5 @@ """Test pi_hole component.""" + import logging from unittest.mock import AsyncMock diff --git a/tests/components/picnic/test_sensor.py b/tests/components/picnic/test_sensor.py index 1535ac1d9ad..37191642c07 100644 --- a/tests/components/picnic/test_sensor.py +++ b/tests/components/picnic/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Picnic sensor platform.""" + import copy from datetime import timedelta import unittest diff --git a/tests/components/pilight/test_sensor.py b/tests/components/pilight/test_sensor.py index 54a26f690d5..629f0f13de4 100644 --- a/tests/components/pilight/test_sensor.py +++ b/tests/components/pilight/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Pilight sensor platform.""" + import logging import pytest diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index b2a4f3c23a5..30b20eec4ab 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Plex config flow.""" + import copy from http import HTTPStatus import ssl diff --git a/tests/components/plex/test_device_handling.py b/tests/components/plex/test_device_handling.py index 5887079ce21..c3c26ec0bdd 100644 --- a/tests/components/plex/test_device_handling.py +++ b/tests/components/plex/test_device_handling.py @@ -1,4 +1,5 @@ """Tests for handling the device registry.""" + import requests_mock from homeassistant.components.plex.const import DOMAIN diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index 47bdc472b2b..e514ea7baa3 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -1,4 +1,5 @@ """Tests for Plex setup.""" + import copy from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index 64abe27b16b..ec798af6d03 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -1,4 +1,5 @@ """Tests for Plex server.""" + import copy from unittest.mock import patch diff --git a/tests/components/plex/test_update.py b/tests/components/plex/test_update.py index ce50f67a0d9..a96e0409bbb 100644 --- a/tests/components/plex/test_update.py +++ b/tests/components/plex/test_update.py @@ -1,4 +1,5 @@ """Tests for update entities.""" + import pytest import requests_mock diff --git a/tests/components/prosegur/test_camera.py b/tests/components/prosegur/test_camera.py index 58017085aed..fdf13e30359 100644 --- a/tests/components/prosegur/test_camera.py +++ b/tests/components/prosegur/test_camera.py @@ -1,4 +1,5 @@ """The camera tests for the prosegur platform.""" + import logging from unittest.mock import AsyncMock diff --git a/tests/components/proximity/conftest.py b/tests/components/proximity/conftest.py index 960ab6cf916..ba73d971131 100644 --- a/tests/components/proximity/conftest.py +++ b/tests/components/proximity/conftest.py @@ -1,4 +1,5 @@ """Config test for proximity.""" + import pytest from homeassistant.core import HomeAssistant diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index 0692bdfd816..81a98f5828a 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -1,4 +1,5 @@ """Test the python_script component.""" + import logging from unittest.mock import mock_open, patch diff --git a/tests/components/qbittorrent/test_config_flow.py b/tests/components/qbittorrent/test_config_flow.py index 4131b9142e2..8a424f5c87b 100644 --- a/tests/components/qbittorrent/test_config_flow.py +++ b/tests/components/qbittorrent/test_config_flow.py @@ -1,4 +1,5 @@ """Test the qBittorrent config flow.""" + import pytest from requests.exceptions import RequestException import requests_mock diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 9cd12ea0447..916c7567b55 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the Queensland Bushfire Alert Feed platform.""" + import datetime from unittest.mock import MagicMock, call, patch diff --git a/tests/components/qwikswitch/test_init.py b/tests/components/qwikswitch/test_init.py index 6d440691780..08e27ec42bd 100644 --- a/tests/components/qwikswitch/test_init.py +++ b/tests/components/qwikswitch/test_init.py @@ -1,4 +1,5 @@ """Test qwikswitch sensors.""" + import asyncio from typing import Any from unittest.mock import Mock diff --git a/tests/components/radarr/test_binary_sensor.py b/tests/components/radarr/test_binary_sensor.py index cd1df721d5f..bd622ded78d 100644 --- a/tests/components/radarr/test_binary_sensor.py +++ b/tests/components/radarr/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for Radarr binary sensor platform.""" + import pytest from homeassistant.components.binary_sensor import BinarySensorDeviceClass diff --git a/tests/components/radarr/test_init.py b/tests/components/radarr/test_init.py index 62660c12874..c4226d3f3fb 100644 --- a/tests/components/radarr/test_init.py +++ b/tests/components/radarr/test_init.py @@ -1,4 +1,5 @@ """Test Radarr integration.""" + import pytest from homeassistant.components.radarr.const import DEFAULT_NAME, DOMAIN diff --git a/tests/components/radarr/test_sensor.py b/tests/components/radarr/test_sensor.py index 11f55b712cd..b75034acc8f 100644 --- a/tests/components/radarr/test_sensor.py +++ b/tests/components/radarr/test_sensor.py @@ -1,4 +1,5 @@ """The tests for Radarr sensor platform.""" + import pytest from homeassistant.components.sensor import ( diff --git a/tests/components/rainforest_raven/test_init.py b/tests/components/rainforest_raven/test_init.py index b99d94f4b43..1cc50971e09 100644 --- a/tests/components/rainforest_raven/test_init.py +++ b/tests/components/rainforest_raven/test_init.py @@ -1,4 +1,5 @@ """Tests for the Rainforest RAVEn component initialisation.""" + import pytest from homeassistant.components.rainforest_raven.const import DOMAIN diff --git a/tests/components/rainforest_raven/test_sensor.py b/tests/components/rainforest_raven/test_sensor.py index e637e22ecf9..36e6572149f 100644 --- a/tests/components/rainforest_raven/test_sensor.py +++ b/tests/components/rainforest_raven/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Rainforest RAVEn sensors.""" + import pytest from homeassistant.core import HomeAssistant diff --git a/tests/components/rainmachine/conftest.py b/tests/components/rainmachine/conftest.py index 2697e908c94..c3724334776 100644 --- a/tests/components/rainmachine/conftest.py +++ b/tests/components/rainmachine/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for RainMachine.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/recorder/test_filters_with_entityfilter.py b/tests/components/recorder/test_filters_with_entityfilter.py index 896987ee58d..d888a1de52e 100644 --- a/tests/components/recorder/test_filters_with_entityfilter.py +++ b/tests/components/recorder/test_filters_with_entityfilter.py @@ -1,4 +1,5 @@ """The tests for the recorder filter matching the EntityFilter component.""" + import json from sqlalchemy import select diff --git a/tests/components/recorder/test_filters_with_entityfilter_schema_37.py b/tests/components/recorder/test_filters_with_entityfilter_schema_37.py index 9a03c024a83..6794827bd6a 100644 --- a/tests/components/recorder/test_filters_with_entityfilter_schema_37.py +++ b/tests/components/recorder/test_filters_with_entityfilter_schema_37.py @@ -1,4 +1,5 @@ """The tests for the recorder filter matching the EntityFilter component.""" + import json from unittest.mock import patch diff --git a/tests/components/recorder/test_migration_from_schema_32.py b/tests/components/recorder/test_migration_from_schema_32.py index 2849c0703da..cd9969206f5 100644 --- a/tests/components/recorder/test_migration_from_schema_32.py +++ b/tests/components/recorder/test_migration_from_schema_32.py @@ -1,4 +1,5 @@ """The tests for the recorder filter matching the EntityFilter component.""" + import datetime import importlib import sys diff --git a/tests/components/recorder/test_pool.py b/tests/components/recorder/test_pool.py index 3a6ff50af2b..541fc8d714b 100644 --- a/tests/components/recorder/test_pool.py +++ b/tests/components/recorder/test_pool.py @@ -1,4 +1,5 @@ """Test pool.""" + import threading import pytest diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index d35be0abc9b..2ca386734bb 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -1,4 +1,5 @@ """The tests for sensor recorder platform.""" + import datetime from datetime import timedelta from statistics import fmean diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py index e3d31d29ff8..92ee282e9c8 100644 --- a/tests/components/reddit/test_sensor.py +++ b/tests/components/reddit/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Reddit platform.""" + import copy from unittest.mock import patch diff --git a/tests/components/remote/test_device_action.py b/tests/components/remote/test_device_action.py index 76e6075a18f..ef8364c050e 100644 --- a/tests/components/remote/test_device_action.py +++ b/tests/components/remote/test_device_action.py @@ -1,4 +1,5 @@ """The test for remote device automation.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py index be4a4843097..15fbb1174c6 100644 --- a/tests/components/remote/test_init.py +++ b/tests/components/remote/test_init.py @@ -1,4 +1,5 @@ """The tests for the Remote component, adapted from Light Test.""" + import pytest import homeassistant.components.remote as remote diff --git a/tests/components/remote/test_reproduce_state.py b/tests/components/remote/test_reproduce_state.py index 910e3e867c6..7d1954e3ce1 100644 --- a/tests/components/remote/test_reproduce_state.py +++ b/tests/components/remote/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Remote.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/renault/test_diagnostics.py b/tests/components/renault/test_diagnostics.py index 76ea88b4b45..3c8c1c7449e 100644 --- a/tests/components/renault/test_diagnostics.py +++ b/tests/components/renault/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Renault diagnostics.""" + import pytest from homeassistant.components.diagnostics import REDACTED diff --git a/tests/components/rest_command/test_init.py b/tests/components/rest_command/test_init.py index ef7707cad76..dc71333370a 100644 --- a/tests/components/rest_command/test_init.py +++ b/tests/components/rest_command/test_init.py @@ -1,4 +1,5 @@ """The tests for the rest command platform.""" + import base64 from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/rfxtrx/test_binary_sensor.py b/tests/components/rfxtrx/test_binary_sensor.py index 2c1f763ea66..d71cd040c87 100644 --- a/tests/components/rfxtrx/test_binary_sensor.py +++ b/tests/components/rfxtrx/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx sensor platform.""" + import pytest from homeassistant.components.rfxtrx import DOMAIN diff --git a/tests/components/rfxtrx/test_config_flow.py b/tests/components/rfxtrx/test_config_flow.py index e527b5dfb7b..d3c87885782 100644 --- a/tests/components/rfxtrx/test_config_flow.py +++ b/tests/components/rfxtrx/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Rfxtrx config flow.""" + import os from unittest.mock import MagicMock, patch, sentinel diff --git a/tests/components/rfxtrx/test_sensor.py b/tests/components/rfxtrx/test_sensor.py index e80e612b283..566237156a6 100644 --- a/tests/components/rfxtrx/test_sensor.py +++ b/tests/components/rfxtrx/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Rfxtrx sensor platform.""" + import pytest from homeassistant.components.rfxtrx import DOMAIN diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index 7607f9fa5db..fd58e0ffbee 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -1,4 +1,5 @@ """The tests for the Ring light platform.""" + import requests_mock from homeassistant.const import Platform diff --git a/tests/components/ring/test_sensor.py b/tests/components/ring/test_sensor.py index 34b6295b740..0299e626670 100644 --- a/tests/components/ring/test_sensor.py +++ b/tests/components/ring/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Ring sensor platform.""" + import logging from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/ring/test_siren.py b/tests/components/ring/test_siren.py index 916da5d24fb..9a54a739fbd 100644 --- a/tests/components/ring/test_siren.py +++ b/tests/components/ring/test_siren.py @@ -1,4 +1,5 @@ """The tests for the Ring button platform.""" + import requests_mock from homeassistant.const import Platform diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index fa3da8b683e..0012db61f97 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Ring switch platform.""" + import requests_mock from homeassistant.const import ATTR_ENTITY_ID, Platform diff --git a/tests/components/risco/test_init.py b/tests/components/risco/test_init.py index a1a9e3bd6a7..18378c5c9c4 100644 --- a/tests/components/risco/test_init.py +++ b/tests/components/risco/test_init.py @@ -1,4 +1,5 @@ """Tests for the Risco initialization.""" + import pytest from homeassistant.components.risco import CannotConnectError diff --git a/tests/components/rituals_perfume_genie/test_select.py b/tests/components/rituals_perfume_genie/test_select.py index be602552d76..17612edfd97 100644 --- a/tests/components/rituals_perfume_genie/test_select.py +++ b/tests/components/rituals_perfume_genie/test_select.py @@ -1,4 +1,5 @@ """Tests for the Rituals Perfume Genie select platform.""" + import pytest from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY diff --git a/tests/components/rmvtransport/test_sensor.py b/tests/components/rmvtransport/test_sensor.py index 92cc04ca3d4..c17eaac2105 100644 --- a/tests/components/rmvtransport/test_sensor.py +++ b/tests/components/rmvtransport/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the rmvtransport platform.""" + import datetime from unittest.mock import patch diff --git a/tests/components/roborock/test_image.py b/tests/components/roborock/test_image.py index 19fa5152e2d..b8d1dc408a2 100644 --- a/tests/components/roborock/test_image.py +++ b/tests/components/roborock/test_image.py @@ -1,4 +1,5 @@ """Test Roborock Image platform.""" + import copy from datetime import timedelta from http import HTTPStatus diff --git a/tests/components/roku/test_config_flow.py b/tests/components/roku/test_config_flow.py index e86084e7718..34640474bcd 100644 --- a/tests/components/roku/test_config_flow.py +++ b/tests/components/roku/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Roku config flow.""" + import dataclasses from unittest.mock import MagicMock diff --git a/tests/components/samsungtv/test_device_trigger.py b/tests/components/samsungtv/test_device_trigger.py index 92df6356f58..a1fb585bfaa 100644 --- a/tests/components/samsungtv/test_device_trigger.py +++ b/tests/components/samsungtv/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Samsung TV device triggers.""" + import pytest from homeassistant.components import automation diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index e0b2af87187..82f29328850 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -1,4 +1,5 @@ """The tests for the Scene component.""" + import io from unittest.mock import patch diff --git a/tests/components/screenlogic/conftest.py b/tests/components/screenlogic/conftest.py index 3795df3dddc..7c4d6adf16b 100644 --- a/tests/components/screenlogic/conftest.py +++ b/tests/components/screenlogic/conftest.py @@ -1,4 +1,5 @@ """Setup fixtures for ScreenLogic integration tests.""" + import pytest from homeassistant.components.screenlogic import DOMAIN diff --git a/tests/components/script/test_blueprint.py b/tests/components/script/test_blueprint.py index 6c9f17f29b7..bccf1d9aa50 100644 --- a/tests/components/script/test_blueprint.py +++ b/tests/components/script/test_blueprint.py @@ -1,4 +1,5 @@ """Test script blueprints.""" + import asyncio from collections.abc import Iterator import contextlib diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 5a5c2c4c9ce..8affd8b7cf9 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -1,4 +1,5 @@ """The tests for the Script component.""" + import asyncio from datetime import timedelta from typing import Any diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index ebf70a6239c..57b72de2bcd 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -1,4 +1,5 @@ """Tests for Search integration.""" + import pytest from homeassistant.components import search diff --git a/tests/components/select/test_device_action.py b/tests/components/select/test_device_action.py index 121b41fcb2b..8245dfe7d56 100644 --- a/tests/components/select/test_device_action.py +++ b/tests/components/select/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Select device actions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/select/test_reproduce_state.py b/tests/components/select/test_reproduce_state.py index bbd1ae17a7b..579ef3301d9 100644 --- a/tests/components/select/test_reproduce_state.py +++ b/tests/components/select/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for select entities.""" + import pytest from homeassistant.components.select.const import ( diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index e0a8bebf5fc..31f234109e5 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -1,4 +1,5 @@ """The test for sensor device automation.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/sensor/test_helpers.py b/tests/components/sensor/test_helpers.py index f103a762842..e197579fa66 100644 --- a/tests/components/sensor/test_helpers.py +++ b/tests/components/sensor/test_helpers.py @@ -1,4 +1,5 @@ """The test for sensor helpers.""" + import pytest from homeassistant.components.sensor import SensorDeviceClass diff --git a/tests/components/sensor/test_significant_change.py b/tests/components/sensor/test_significant_change.py index fa530b66381..e1a2325cd11 100644 --- a/tests/components/sensor/test_significant_change.py +++ b/tests/components/sensor/test_significant_change.py @@ -1,4 +1,5 @@ """Test the sensor significant change platform.""" + import pytest from homeassistant.components.sensor import SensorDeviceClass, significant_change diff --git a/tests/components/sentry/test_config_flow.py b/tests/components/sentry/test_config_flow.py index 34ed1d2c4b5..edcd87cd5d9 100644 --- a/tests/components/sentry/test_config_flow.py +++ b/tests/components/sentry/test_config_flow.py @@ -1,4 +1,5 @@ """Test the sentry config flow.""" + import logging from unittest.mock import patch diff --git a/tests/components/sentry/test_init.py b/tests/components/sentry/test_init.py index 73f6a7cfd09..b8c5c4cbde9 100644 --- a/tests/components/sentry/test_init.py +++ b/tests/components/sentry/test_init.py @@ -1,4 +1,5 @@ """Tests for Sentry integration.""" + import logging from unittest.mock import Mock, patch diff --git a/tests/components/sfr_box/test_config_flow.py b/tests/components/sfr_box/test_config_flow.py index c8130d5d617..282d7dbbb4c 100644 --- a/tests/components/sfr_box/test_config_flow.py +++ b/tests/components/sfr_box/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SFR Box config flow.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/signal_messenger/test_notify.py b/tests/components/signal_messenger/test_notify.py index 360a2b24bec..d1f590ab5bc 100644 --- a/tests/components/signal_messenger/test_notify.py +++ b/tests/components/signal_messenger/test_notify.py @@ -1,4 +1,5 @@ """The tests for the signal_messenger platform.""" + import base64 import json import logging diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index 1b9f9f02cee..0594ac51850 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for SimpliSafe.""" + import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index 53d9c86185f..308f04d12a1 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -1,4 +1,5 @@ """Define tests for the SimpliSafe config flow.""" + import logging from unittest.mock import patch diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index a7a819d6ac9..283b478fdb0 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -1,4 +1,5 @@ """Test configuration and mocks for the SmartThings component.""" + import secrets from unittest.mock import Mock, patch from uuid import uuid4 diff --git a/tests/components/smarttub/test_climate.py b/tests/components/smarttub/test_climate.py index 40e3c05b509..e4e73b8b131 100644 --- a/tests/components/smarttub/test_climate.py +++ b/tests/components/smarttub/test_climate.py @@ -1,4 +1,5 @@ """Test the SmartTub climate platform.""" + import smarttub from homeassistant.components.climate import ( diff --git a/tests/components/smarttub/test_light.py b/tests/components/smarttub/test_light.py index f6a3977f373..a3e4e4a5cf7 100644 --- a/tests/components/smarttub/test_light.py +++ b/tests/components/smarttub/test_light.py @@ -1,4 +1,5 @@ """Test the SmartTub light platform.""" + import pytest from smarttub import SpaLight diff --git a/tests/components/smarttub/test_sensor.py b/tests/components/smarttub/test_sensor.py index 5e476dcaaa5..9bdffb62534 100644 --- a/tests/components/smarttub/test_sensor.py +++ b/tests/components/smarttub/test_sensor.py @@ -1,4 +1,5 @@ """Test the SmartTub sensor platform.""" + import pytest import smarttub diff --git a/tests/components/smarttub/test_switch.py b/tests/components/smarttub/test_switch.py index d7e284e9ac2..42fd3d45d88 100644 --- a/tests/components/smarttub/test_switch.py +++ b/tests/components/smarttub/test_switch.py @@ -1,4 +1,5 @@ """Test the SmartTub switch platform.""" + import pytest from homeassistant.const import STATE_OFF, STATE_ON diff --git a/tests/components/smhi/conftest.py b/tests/components/smhi/conftest.py index c474bc50b51..df6a81a223d 100644 --- a/tests/components/smhi/conftest.py +++ b/tests/components/smhi/conftest.py @@ -1,4 +1,5 @@ """Provide common smhi fixtures.""" + import pytest from homeassistant.components.smhi.const import DOMAIN diff --git a/tests/components/snips/test_init.py b/tests/components/snips/test_init.py index da2f23bc49c..9590473f218 100644 --- a/tests/components/snips/test_init.py +++ b/tests/components/snips/test_init.py @@ -1,4 +1,5 @@ """Test the Snips component.""" + import json import logging diff --git a/tests/components/songpal/test_config_flow.py b/tests/components/songpal/test_config_flow.py index 0a3d5d1acb2..338207a5d13 100644 --- a/tests/components/songpal/test_config_flow.py +++ b/tests/components/songpal/test_config_flow.py @@ -1,4 +1,5 @@ """Test the songpal config flow.""" + import copy import dataclasses from unittest.mock import patch diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index 83171e2029e..c073f9f636e 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -1,4 +1,5 @@ """Tests for the Sonos config flow.""" + import asyncio from datetime import timedelta import logging diff --git a/tests/components/sonos/test_plex_playback.py b/tests/components/sonos/test_plex_playback.py index 99269ca10ef..d5d5884bb50 100644 --- a/tests/components/sonos/test_plex_playback.py +++ b/tests/components/sonos/test_plex_playback.py @@ -1,4 +1,5 @@ """Tests for the Sonos Media Player platform.""" + import json from unittest.mock import Mock, patch diff --git a/tests/components/soundtouch/conftest.py b/tests/components/soundtouch/conftest.py index 1cdd37add4b..c81d76072d7 100644 --- a/tests/components/soundtouch/conftest.py +++ b/tests/components/soundtouch/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Bose SoundTouch integration tests.""" + import pytest from requests_mock import Mocker diff --git a/tests/components/srp_energy/test_sensor.py b/tests/components/srp_energy/test_sensor.py index 2d49fd13bf1..70b4eae14df 100644 --- a/tests/components/srp_energy/test_sensor.py +++ b/tests/components/srp_energy/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the srp_energy sensor platform.""" + import time from unittest.mock import patch diff --git a/tests/components/starline/test_config_flow.py b/tests/components/starline/test_config_flow.py index 4277f01037f..0d7d7cc0731 100644 --- a/tests/components/starline/test_config_flow.py +++ b/tests/components/starline/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for StarLine config flow.""" + import requests_mock from homeassistant import config_entries diff --git a/tests/components/starlink/patchers.py b/tests/components/starlink/patchers.py index d83451ecc17..85e7a77c78c 100644 --- a/tests/components/starlink/patchers.py +++ b/tests/components/starlink/patchers.py @@ -1,4 +1,5 @@ """General Starlink patchers.""" + import json from unittest.mock import patch diff --git a/tests/components/steam_online/test_init.py b/tests/components/steam_online/test_init.py index e3f473e01c6..48584dab7a5 100644 --- a/tests/components/steam_online/test_init.py +++ b/tests/components/steam_online/test_init.py @@ -1,4 +1,5 @@ """Tests for the Steam component.""" + import steam from homeassistant.components.steam_online.const import DEFAULT_NAME, DOMAIN diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py index 8372e5d5e61..1ae6f9e8931 100644 --- a/tests/components/stream/test_init.py +++ b/tests/components/stream/test_init.py @@ -1,4 +1,5 @@ """Test stream init.""" + import logging import av diff --git a/tests/components/stream/test_ll_hls.py b/tests/components/stream/test_ll_hls.py index cd13ab340c2..02fac835bdc 100644 --- a/tests/components/stream/test_ll_hls.py +++ b/tests/components/stream/test_ll_hls.py @@ -1,4 +1,5 @@ """The tests for hls streams.""" + import asyncio from collections import deque from http import HTTPStatus diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index b8c27037a25..29442620a10 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -1,4 +1,5 @@ """The tests for recording streams.""" + import asyncio from datetime import timedelta from io import BytesIO diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index bd998b008be..cf61368e12b 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -12,6 +12,7 @@ creates a packet sequence, with a mocked output buffer to capture the segments pushed to the output streams. The packet sequence can be used to exercise failure modes or corner cases like how out of order packets are handled. """ + import asyncio import fractions import io diff --git a/tests/components/subaru/test_diagnostics.py b/tests/components/subaru/test_diagnostics.py index 7da53c66c46..9445f1ca235 100644 --- a/tests/components/subaru/test_diagnostics.py +++ b/tests/components/subaru/test_diagnostics.py @@ -1,4 +1,5 @@ """Test Subaru diagnostics.""" + import json from unittest.mock import patch diff --git a/tests/components/surepetcare/test_lock.py b/tests/components/surepetcare/test_lock.py index 14a6a361793..d4275e8385c 100644 --- a/tests/components/surepetcare/test_lock.py +++ b/tests/components/surepetcare/test_lock.py @@ -1,4 +1,5 @@ """The tests for the Sure Petcare lock platform.""" + import pytest from surepy.exceptions import SurePetcareError diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index e86c32c1e32..a4e0472b2e7 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -1,4 +1,5 @@ """The test for switch device automation.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index deb7acb512a..62801346744 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -1,4 +1,5 @@ """The tests for the Switch component.""" + import pytest from homeassistant import core diff --git a/tests/components/switch/test_light.py b/tests/components/switch/test_light.py index 2254abc08f9..e7681575ce4 100644 --- a/tests/components/switch/test_light.py +++ b/tests/components/switch/test_light.py @@ -1,4 +1,5 @@ """The tests for the Light Switch platform.""" + import pytest from homeassistant.components.light import ( diff --git a/tests/components/switch/test_reproduce_state.py b/tests/components/switch/test_reproduce_state.py index 764efb17036..27bc492494c 100644 --- a/tests/components/switch/test_reproduce_state.py +++ b/tests/components/switch/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Switch.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/tests/components/switchbee/test_config_flow.py b/tests/components/switchbee/test_config_flow.py index 98d413c3b96..c31ada589c8 100644 --- a/tests/components/switchbee/test_config_flow.py +++ b/tests/components/switchbee/test_config_flow.py @@ -1,4 +1,5 @@ """Test the SwitchBee Smart Home config flow.""" + import json from unittest.mock import patch diff --git a/tests/components/switcher_kis/test_sensor.py b/tests/components/switcher_kis/test_sensor.py index 03073b21a96..99bc0aa3a69 100644 --- a/tests/components/switcher_kis/test_sensor.py +++ b/tests/components/switcher_kis/test_sensor.py @@ -1,4 +1,5 @@ """Test the Switcher Sensor Platform.""" + import pytest from homeassistant.components.switcher_kis.const import DATA_DEVICE, DOMAIN diff --git a/tests/components/syncthru/test_config_flow.py b/tests/components/syncthru/test_config_flow.py index 948e55649fc..a413789dc60 100644 --- a/tests/components/syncthru/test_config_flow.py +++ b/tests/components/syncthru/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for syncthru config flow.""" + import re from unittest.mock import patch diff --git a/tests/components/tado/test_service.py b/tests/components/tado/test_service.py index fe7e78f4ba5..759470cb5ea 100644 --- a/tests/components/tado/test_service.py +++ b/tests/components/tado/test_service.py @@ -1,4 +1,5 @@ """The serive tests for the tado platform.""" + import json from unittest.mock import patch diff --git a/tests/components/tag/test_trigger.py b/tests/components/tag/test_trigger.py index 2780b928027..53c511b8594 100644 --- a/tests/components/tag/test_trigger.py +++ b/tests/components/tag/test_trigger.py @@ -1,4 +1,5 @@ """Tests for tag triggers.""" + import pytest import homeassistant.components.automation as automation diff --git a/tests/components/tami4/test_init.py b/tests/components/tami4/test_init.py index ad3f50a377e..62e5861e13c 100644 --- a/tests/components/tami4/test_init.py +++ b/tests/components/tami4/test_init.py @@ -1,4 +1,5 @@ """Test the Tami4 component.""" + import pytest from Tami4EdgeAPI import exceptions diff --git a/tests/components/tasmota/test_binary_sensor.py b/tests/components/tasmota/test_binary_sensor.py index d5f1e4d7101..5abb9ab9bf2 100644 --- a/tests/components/tasmota/test_binary_sensor.py +++ b/tests/components/tasmota/test_binary_sensor.py @@ -1,4 +1,5 @@ """The tests for the Tasmota binary sensor platform.""" + import copy from datetime import timedelta import json diff --git a/tests/components/tasmota/test_common.py b/tests/components/tasmota/test_common.py index 1f414cb4e5a..360794e280f 100644 --- a/tests/components/tasmota/test_common.py +++ b/tests/components/tasmota/test_common.py @@ -1,4 +1,5 @@ """Common test objects.""" + import copy import json from unittest.mock import ANY diff --git a/tests/components/tasmota/test_cover.py b/tests/components/tasmota/test_cover.py index 26f8dee4a9d..a28739d59be 100644 --- a/tests/components/tasmota/test_cover.py +++ b/tests/components/tasmota/test_cover.py @@ -1,4 +1,5 @@ """The tests for the Tasmota cover platform.""" + import copy import json from unittest.mock import patch diff --git a/tests/components/tasmota/test_device_trigger.py b/tests/components/tasmota/test_device_trigger.py index 190c56b33f6..a5d30814b38 100644 --- a/tests/components/tasmota/test_device_trigger.py +++ b/tests/components/tasmota/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for Tasmota device triggers.""" + import copy import json from unittest.mock import Mock, patch diff --git a/tests/components/tasmota/test_discovery.py b/tests/components/tasmota/test_discovery.py index 4fd9f293498..122c22f752e 100644 --- a/tests/components/tasmota/test_discovery.py +++ b/tests/components/tasmota/test_discovery.py @@ -1,4 +1,5 @@ """The tests for the MQTT discovery.""" + import copy import json from unittest.mock import ANY, patch diff --git a/tests/components/tasmota/test_fan.py b/tests/components/tasmota/test_fan.py index 727fddc9bd3..654b8c955d2 100644 --- a/tests/components/tasmota/test_fan.py +++ b/tests/components/tasmota/test_fan.py @@ -1,4 +1,5 @@ """The tests for the Tasmota fan platform.""" + import copy import json from unittest.mock import patch diff --git a/tests/components/tasmota/test_init.py b/tests/components/tasmota/test_init.py index 09467b893e0..95fb186a46d 100644 --- a/tests/components/tasmota/test_init.py +++ b/tests/components/tasmota/test_init.py @@ -1,4 +1,5 @@ """The tests for the Tasmota binary sensor platform.""" + import copy import json from unittest.mock import call diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index 50f11fb7757..c4c3f0ec8dc 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -1,4 +1,5 @@ """The tests for the Tasmota light platform.""" + import copy import json from unittest.mock import patch diff --git a/tests/components/tasmota/test_mixins.py b/tests/components/tasmota/test_mixins.py index aad0d0e723a..86696a0813d 100644 --- a/tests/components/tasmota/test_mixins.py +++ b/tests/components/tasmota/test_mixins.py @@ -1,4 +1,5 @@ """The tests for the Tasmota mixins.""" + import copy import json from unittest.mock import call diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index dc4820779a6..61034ae66e9 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Tasmota sensor platform.""" + import copy import datetime from datetime import timedelta diff --git a/tests/components/tasmota/test_switch.py b/tests/components/tasmota/test_switch.py index 1a16f372fc9..1ab5a50d3f5 100644 --- a/tests/components/tasmota/test_switch.py +++ b/tests/components/tasmota/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Tasmota switch platform.""" + import copy import json from unittest.mock import patch diff --git a/tests/components/template/conftest.py b/tests/components/template/conftest.py index 207a7c87886..894c1777fef 100644 --- a/tests/components/template/conftest.py +++ b/tests/components/template/conftest.py @@ -1,4 +1,5 @@ """template conftest.""" + import pytest from homeassistant.setup import async_setup_component diff --git a/tests/components/template/test_alarm_control_panel.py b/tests/components/template/test_alarm_control_panel.py index ef2390680b6..eb4daa3bcb8 100644 --- a/tests/components/template/test_alarm_control_panel.py +++ b/tests/components/template/test_alarm_control_panel.py @@ -1,4 +1,5 @@ """The tests for the Template alarm control panel platform.""" + import pytest from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN diff --git a/tests/components/template/test_button.py b/tests/components/template/test_button.py index ece568eee49..2e83100734a 100644 --- a/tests/components/template/test_button.py +++ b/tests/components/template/test_button.py @@ -1,4 +1,5 @@ """The tests for the Template button platform.""" + import datetime as dt from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index ccdafebd8bb..11a0afbaf33 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -1,4 +1,5 @@ """The tests for the Template fan platform.""" + import pytest import voluptuous as vol diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index ec830d4daf6..a40f093a573 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -1,4 +1,5 @@ """The tests for the Template light platform.""" + import pytest import homeassistant.components.light as light diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 118a73264d7..77b7c9657d4 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -1,4 +1,5 @@ """The tests for the Template lock platform.""" + import pytest from homeassistant import setup diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index 1e979fc9926..acf80006798 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Template switch platform.""" + import pytest from homeassistant import setup diff --git a/tests/components/template/test_template_entity.py b/tests/components/template/test_template_entity.py index 4fe3d7cb780..dcceea95181 100644 --- a/tests/components/template/test_template_entity.py +++ b/tests/components/template/test_template_entity.py @@ -1,4 +1,5 @@ """Test template entity.""" + import pytest from homeassistant.components.template import template_entity diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index e6850728450..2c6f083abce 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -1,4 +1,5 @@ """The tests for the Template vacuum platform.""" + import pytest from homeassistant import setup diff --git a/tests/components/tessie/test_binary_sensors.py b/tests/components/tessie/test_binary_sensors.py index b6dccd9d3b1..0ced8a6d8aa 100644 --- a/tests/components/tessie/test_binary_sensors.py +++ b/tests/components/tessie/test_binary_sensors.py @@ -1,4 +1,5 @@ """Test the Tessie binary sensor platform.""" + import pytest from syrupy import SnapshotAssertion diff --git a/tests/components/text/test_device_action.py b/tests/components/text/test_device_action.py index 59b77ecfa06..e2357fc7794 100644 --- a/tests/components/text/test_device_action.py +++ b/tests/components/text/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Text device actions.""" + import pytest from pytest_unordered import unordered import voluptuous_serialize diff --git a/tests/components/text/test_reproduce_state.py b/tests/components/text/test_reproduce_state.py index 94c9b8127f6..d87e0e983de 100644 --- a/tests/components/text/test_reproduce_state.py +++ b/tests/components/text/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Text entities.""" + import pytest from homeassistant.components.text.const import ( diff --git a/tests/components/thread/test_dataset_store.py b/tests/components/thread/test_dataset_store.py index bcc16de4ed2..a0d85fc6cea 100644 --- a/tests/components/thread/test_dataset_store.py +++ b/tests/components/thread/test_dataset_store.py @@ -1,4 +1,5 @@ """Test the thread dataset store.""" + import asyncio from typing import Any from unittest.mock import ANY, AsyncMock, patch diff --git a/tests/components/threshold/test_init.py b/tests/components/threshold/test_init.py index 82d183ad380..a580ec50a9d 100644 --- a/tests/components/threshold/test_init.py +++ b/tests/components/threshold/test_init.py @@ -1,4 +1,5 @@ """Test the Min/Max integration.""" + import pytest from homeassistant.components.threshold.const import DOMAIN diff --git a/tests/components/tibber/conftest.py b/tests/components/tibber/conftest.py index 3fa503ed002..da3f3df1bd9 100644 --- a/tests/components/tibber/conftest.py +++ b/tests/components/tibber/conftest.py @@ -1,4 +1,5 @@ """Test helpers for Tibber.""" + import pytest from homeassistant.components.tibber.const import DOMAIN diff --git a/tests/components/tibber/test_common.py b/tests/components/tibber/test_common.py index 7faa07a6d9f..8d6b22ad7f7 100644 --- a/tests/components/tibber/test_common.py +++ b/tests/components/tibber/test_common.py @@ -1,4 +1,5 @@ """Test common.""" + import datetime as dt from unittest.mock import AsyncMock diff --git a/tests/components/tile/conftest.py b/tests/components/tile/conftest.py index 577abd6fa70..964986ba151 100644 --- a/tests/components/tile/conftest.py +++ b/tests/components/tile/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for Tile.""" + import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/timer/test_reproduce_state.py b/tests/components/timer/test_reproduce_state.py index 2d4fca68dfe..fcc331fef50 100644 --- a/tests/components/timer/test_reproduce_state.py +++ b/tests/components/timer/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Timer.""" + import pytest from homeassistant.components.timer import ( diff --git a/tests/components/tomorrowio/conftest.py b/tests/components/tomorrowio/conftest.py index 2d36d68c57a..94adda43f23 100644 --- a/tests/components/tomorrowio/conftest.py +++ b/tests/components/tomorrowio/conftest.py @@ -1,4 +1,5 @@ """Configure py.test.""" + import json from unittest.mock import PropertyMock, patch diff --git a/tests/components/tplink/test_diagnostics.py b/tests/components/tplink/test_diagnostics.py index 3ef42c48b2f..bda5b143a6a 100644 --- a/tests/components/tplink/test_diagnostics.py +++ b/tests/components/tplink/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the TP-Link integration.""" + import json from kasa import SmartDevice diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index 511667a7462..5e584de2b4f 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -1,4 +1,5 @@ """Test Trace websocket API.""" + import asyncio from collections import defaultdict import json diff --git a/tests/components/tradfri/test_util.py b/tests/components/tradfri/test_util.py index c4f3509dfef..37b532c7798 100644 --- a/tests/components/tradfri/test_util.py +++ b/tests/components/tradfri/test_util.py @@ -1,4 +1,5 @@ """Tradfri utility function tests.""" + import pytest from homeassistant.components.tradfri.fan import _from_fan_percentage, _from_fan_speed diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index d56542b2a57..cbf18fe8771 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -1,4 +1,5 @@ """The tests for the TTS component.""" + import asyncio from http import HTTPStatus from typing import Any diff --git a/tests/components/twentemilieu/test_sensor.py b/tests/components/twentemilieu/test_sensor.py index e4b845264db..6fd39e38d48 100644 --- a/tests/components/twentemilieu/test_sensor.py +++ b/tests/components/twentemilieu/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the Twente Milieu sensors.""" + import pytest from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/twitch/test_init.py b/tests/components/twitch/test_init.py index da03857a95d..d3b9313c46e 100644 --- a/tests/components/twitch/test_init.py +++ b/tests/components/twitch/test_init.py @@ -1,4 +1,5 @@ """Tests for YouTube.""" + import http import time from unittest.mock import patch diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index d60966fc6d7..baed4dbd44b 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the uk_transport platform.""" + import re from unittest.mock import patch diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 572f7a2ff05..0f8efc8b483 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -1,4 +1,5 @@ """Test UniFi Network config flow.""" + import socket from unittest.mock import patch diff --git a/tests/components/uptime/test_sensor.py b/tests/components/uptime/test_sensor.py index 41c097badd1..169b1e80c7d 100644 --- a/tests/components/uptime/test_sensor.py +++ b/tests/components/uptime/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the uptime sensor platform.""" + import pytest from syrupy.assertion import SnapshotAssertion from syrupy.filters import props diff --git a/tests/components/uptimerobot/test_diagnostics.py b/tests/components/uptimerobot/test_diagnostics.py index 9eca73d0240..12a91077552 100644 --- a/tests/components/uptimerobot/test_diagnostics.py +++ b/tests/components/uptimerobot/test_diagnostics.py @@ -1,4 +1,5 @@ """Test UptimeRobot diagnostics.""" + import json from unittest.mock import patch diff --git a/tests/components/usb/test_init.py b/tests/components/usb/test_init.py index a1637f62b01..dd6d9fff874 100644 --- a/tests/components/usb/test_init.py +++ b/tests/components/usb/test_init.py @@ -1,4 +1,5 @@ """Tests for the USB Discovery integration.""" + import os import sys from unittest.mock import MagicMock, Mock, call, patch, sentinel diff --git a/tests/components/usgs_earthquakes_feed/test_geo_location.py b/tests/components/usgs_earthquakes_feed/test_geo_location.py index 6307125930c..effaa119a07 100644 --- a/tests/components/usgs_earthquakes_feed/test_geo_location.py +++ b/tests/components/usgs_earthquakes_feed/test_geo_location.py @@ -1,4 +1,5 @@ """The tests for the USGS Earthquake Hazards Program Feed platform.""" + import datetime from unittest.mock import ANY, MagicMock, call, patch diff --git a/tests/components/vacuum/test_device_action.py b/tests/components/vacuum/test_device_action.py index cf0ab3c20d8..6b7b56ccf18 100644 --- a/tests/components/vacuum/test_device_action.py +++ b/tests/components/vacuum/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Vacuum device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/vacuum/test_device_condition.py b/tests/components/vacuum/test_device_condition.py index a2ba75cc752..029765afd77 100644 --- a/tests/components/vacuum/test_device_condition.py +++ b/tests/components/vacuum/test_device_condition.py @@ -1,4 +1,5 @@ """The tests for Vacuum device conditions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/vacuum/test_reproduce_state.py b/tests/components/vacuum/test_reproduce_state.py index 2f7817a3b9e..ff8da28e98c 100644 --- a/tests/components/vacuum/test_reproduce_state.py +++ b/tests/components/vacuum/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Vacuum.""" + import pytest from homeassistant.components.vacuum import ( diff --git a/tests/components/vacuum/test_significant_change.py b/tests/components/vacuum/test_significant_change.py index 5f46080fb8d..f4def0ccbf6 100644 --- a/tests/components/vacuum/test_significant_change.py +++ b/tests/components/vacuum/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Vacuum significant change platform.""" + import pytest from homeassistant.components.vacuum import ( diff --git a/tests/components/vallox/test_number.py b/tests/components/vallox/test_number.py index 29de3d54d1c..2e440c5e304 100644 --- a/tests/components/vallox/test_number.py +++ b/tests/components/vallox/test_number.py @@ -1,4 +1,5 @@ """Tests for Vallox number platform.""" + import pytest from homeassistant.components.number.const import ( diff --git a/tests/components/venstar/test_config_flow.py b/tests/components/venstar/test_config_flow.py index 521e5a8512e..c7a0f0a394f 100644 --- a/tests/components/venstar/test_config_flow.py +++ b/tests/components/venstar/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Venstar config flow.""" + import logging from unittest.mock import patch diff --git a/tests/components/vesync/common.py b/tests/components/vesync/common.py index 47cce03afe3..2e5a973c143 100644 --- a/tests/components/vesync/common.py +++ b/tests/components/vesync/common.py @@ -1,4 +1,5 @@ """Common methods used across tests for VeSync.""" + import json import requests_mock diff --git a/tests/components/vesync/test_fan.py b/tests/components/vesync/test_fan.py index 26ac565e1a6..4d444036a60 100644 --- a/tests/components/vesync/test_fan.py +++ b/tests/components/vesync/test_fan.py @@ -1,4 +1,5 @@ """Tests for the fan module.""" + import pytest import requests_mock from syrupy import SnapshotAssertion diff --git a/tests/components/vesync/test_light.py b/tests/components/vesync/test_light.py index b293d6e808e..866e6b295bf 100644 --- a/tests/components/vesync/test_light.py +++ b/tests/components/vesync/test_light.py @@ -1,4 +1,5 @@ """Tests for the light module.""" + import pytest import requests_mock from syrupy import SnapshotAssertion diff --git a/tests/components/vesync/test_sensor.py b/tests/components/vesync/test_sensor.py index c50b916df66..bd3a8eb8591 100644 --- a/tests/components/vesync/test_sensor.py +++ b/tests/components/vesync/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the sensor module.""" + import pytest import requests_mock from syrupy import SnapshotAssertion diff --git a/tests/components/vesync/test_switch.py b/tests/components/vesync/test_switch.py index af721724d91..111f2b80960 100644 --- a/tests/components/vesync/test_switch.py +++ b/tests/components/vesync/test_switch.py @@ -1,4 +1,5 @@ """Tests for the switch module.""" + import pytest import requests_mock from syrupy import SnapshotAssertion diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index 578d79fcba0..d19cf319a5a 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for Vizio config flow.""" + import dataclasses import pytest diff --git a/tests/components/voip/test_sip.py b/tests/components/voip/test_sip.py index 975b8f326d9..2e144b87ec0 100644 --- a/tests/components/voip/test_sip.py +++ b/tests/components/voip/test_sip.py @@ -1,4 +1,5 @@ """Test SIP server.""" + import socket import pytest diff --git a/tests/components/voip/test_voip.py b/tests/components/voip/test_voip.py index 703b99db962..3d34218e999 100644 --- a/tests/components/voip/test_voip.py +++ b/tests/components/voip/test_voip.py @@ -1,4 +1,5 @@ """Test VoIP protocol.""" + import asyncio import io import time diff --git a/tests/components/vulcan/test_config_flow.py b/tests/components/vulcan/test_config_flow.py index d575fe73600..97dd47d7ea4 100644 --- a/tests/components/vulcan/test_config_flow.py +++ b/tests/components/vulcan/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Uonet+ Vulcan config flow.""" + import json from unittest import mock from unittest.mock import patch diff --git a/tests/components/vultr/conftest.py b/tests/components/vultr/conftest.py index 0d69cf53335..f8ecd1cf321 100644 --- a/tests/components/vultr/conftest.py +++ b/tests/components/vultr/conftest.py @@ -1,4 +1,5 @@ """Test configuration for the Vultr tests.""" + import json from unittest.mock import patch diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index d7f421e4463..f6b46b54d25 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -1,4 +1,5 @@ """Test the Vultr binary sensor platform.""" + import pytest import voluptuous as vol diff --git a/tests/components/vultr/test_sensor.py b/tests/components/vultr/test_sensor.py index 0f2b1b7d4a4..f9f922b35d4 100644 --- a/tests/components/vultr/test_sensor.py +++ b/tests/components/vultr/test_sensor.py @@ -1,4 +1,5 @@ """The tests for the Vultr sensor platform.""" + import pytest import voluptuous as vol diff --git a/tests/components/wallbox/conftest.py b/tests/components/wallbox/conftest.py index 4677dc95e6f..72d493ceb69 100644 --- a/tests/components/wallbox/conftest.py +++ b/tests/components/wallbox/conftest.py @@ -1,4 +1,5 @@ """Test fixtures for the Wallbox integration.""" + import pytest from homeassistant.components.wallbox.const import CONF_STATION, DOMAIN diff --git a/tests/components/wallbox/test_init.py b/tests/components/wallbox/test_init.py index 93082737f1f..3dfc391aa3b 100644 --- a/tests/components/wallbox/test_init.py +++ b/tests/components/wallbox/test_init.py @@ -1,4 +1,5 @@ """Test Wallbox Init Component.""" + import json import requests_mock diff --git a/tests/components/wallbox/test_lock.py b/tests/components/wallbox/test_lock.py index 065a43b2789..637f0c827f4 100644 --- a/tests/components/wallbox/test_lock.py +++ b/tests/components/wallbox/test_lock.py @@ -1,4 +1,5 @@ """Test Wallbox Lock component.""" + import json import pytest diff --git a/tests/components/wallbox/test_number.py b/tests/components/wallbox/test_number.py index 837df4dfd47..5d782224ce5 100644 --- a/tests/components/wallbox/test_number.py +++ b/tests/components/wallbox/test_number.py @@ -1,4 +1,5 @@ """Test Wallbox Switch component.""" + import json import pytest diff --git a/tests/components/wallbox/test_switch.py b/tests/components/wallbox/test_switch.py index edd85c6ccc7..d06251db003 100644 --- a/tests/components/wallbox/test_switch.py +++ b/tests/components/wallbox/test_switch.py @@ -1,4 +1,5 @@ """Test Wallbox Lock component.""" + import json import pytest diff --git a/tests/components/waqi/test_config_flow.py b/tests/components/waqi/test_config_flow.py index ecc7e07158d..f19d0991f46 100644 --- a/tests/components/waqi/test_config_flow.py +++ b/tests/components/waqi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the World Air Quality Index (WAQI) config flow.""" + import json from typing import Any from unittest.mock import AsyncMock, patch diff --git a/tests/components/waqi/test_sensor.py b/tests/components/waqi/test_sensor.py index ebe0c87736d..7b19fbee083 100644 --- a/tests/components/waqi/test_sensor.py +++ b/tests/components/waqi/test_sensor.py @@ -1,4 +1,5 @@ """Test the World Air Quality Index (WAQI) sensor.""" + import json from unittest.mock import patch diff --git a/tests/components/water_heater/test_device_action.py b/tests/components/water_heater/test_device_action.py index 8254fb77a77..1a3645af80b 100644 --- a/tests/components/water_heater/test_device_action.py +++ b/tests/components/water_heater/test_device_action.py @@ -1,4 +1,5 @@ """The tests for Water Heater device actions.""" + import pytest from pytest_unordered import unordered diff --git a/tests/components/water_heater/test_reproduce_state.py b/tests/components/water_heater/test_reproduce_state.py index a050d28a7d2..2aa10fa004f 100644 --- a/tests/components/water_heater/test_reproduce_state.py +++ b/tests/components/water_heater/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for Water heater.""" + import pytest from homeassistant.components.water_heater import ( diff --git a/tests/components/water_heater/test_significant_change.py b/tests/components/water_heater/test_significant_change.py index 40803eea09a..dd0a8cc5924 100644 --- a/tests/components/water_heater/test_significant_change.py +++ b/tests/components/water_heater/test_significant_change.py @@ -1,4 +1,5 @@ """Test the Water Heater significant change platform.""" + import pytest from homeassistant.components.water_heater import ( diff --git a/tests/components/watttime/conftest.py b/tests/components/watttime/conftest.py index f636ffefcfb..72bdc2d3419 100644 --- a/tests/components/watttime/conftest.py +++ b/tests/components/watttime/conftest.py @@ -1,4 +1,5 @@ """Define test fixtures for WattTime.""" + import json from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/waze_travel_time/test_config_flow.py b/tests/components/waze_travel_time/test_config_flow.py index f1fd3041d46..9bd5016c2f4 100644 --- a/tests/components/waze_travel_time/test_config_flow.py +++ b/tests/components/waze_travel_time/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Waze Travel Time config flow.""" + import pytest from homeassistant import config_entries, data_entry_flow diff --git a/tests/components/waze_travel_time/test_sensor.py b/tests/components/waze_travel_time/test_sensor.py index 60e5535fdba..0b2ec4297e6 100644 --- a/tests/components/waze_travel_time/test_sensor.py +++ b/tests/components/waze_travel_time/test_sensor.py @@ -1,4 +1,5 @@ """Test Waze Travel Time sensors.""" + import pytest from pywaze.route_calculator import WRCError diff --git a/tests/components/weatherflow/conftest.py b/tests/components/weatherflow/conftest.py index 0bf6b69b9a7..dc533f153e2 100644 --- a/tests/components/weatherflow/conftest.py +++ b/tests/components/weatherflow/conftest.py @@ -1,4 +1,5 @@ """Fixtures for Weatherflow integration tests.""" + import asyncio from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/weatherflow_cloud/test_config_flow.py b/tests/components/weatherflow_cloud/test_config_flow.py index 9a6c64fc32c..cef0e224434 100644 --- a/tests/components/weatherflow_cloud/test_config_flow.py +++ b/tests/components/weatherflow_cloud/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WeatherflowCloud config flow.""" + import pytest from homeassistant import config_entries diff --git a/tests/components/webmin/test_sensor.py b/tests/components/webmin/test_sensor.py index 528aebca990..5fb874825a3 100644 --- a/tests/components/webmin/test_sensor.py +++ b/tests/components/webmin/test_sensor.py @@ -1,4 +1,5 @@ """Test cases for the Webmin sensors.""" + import pytest from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/webostv/test_config_flow.py b/tests/components/webostv/test_config_flow.py index ad57b4647ea..07a11b5bf29 100644 --- a/tests/components/webostv/test_config_flow.py +++ b/tests/components/webostv/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WebOS Tv config flow.""" + import dataclasses from unittest.mock import Mock diff --git a/tests/components/webostv/test_device_trigger.py b/tests/components/webostv/test_device_trigger.py index db3bd85bbf7..5205f6ae7a1 100644 --- a/tests/components/webostv/test_device_trigger.py +++ b/tests/components/webostv/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for WebOS TV device triggers.""" + import pytest from homeassistant.components import automation diff --git a/tests/components/websocket_api/conftest.py b/tests/components/websocket_api/conftest.py index 75a7834f629..7cfd0e204a7 100644 --- a/tests/components/websocket_api/conftest.py +++ b/tests/components/websocket_api/conftest.py @@ -1,4 +1,5 @@ """Fixtures for websocket tests.""" + import pytest from homeassistant.components.websocket_api.auth import TYPE_AUTH_REQUIRED diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index a6a2fb06d7b..5e4eff5da53 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -1,4 +1,5 @@ """Tests for WebSocket API commands.""" + import asyncio from copy import deepcopy import logging diff --git a/tests/components/websocket_api/test_connection.py b/tests/components/websocket_api/test_connection.py index 7a6bd86586c..d6c2765522e 100644 --- a/tests/components/websocket_api/test_connection.py +++ b/tests/components/websocket_api/test_connection.py @@ -1,4 +1,5 @@ """Test WebSocket Connection class.""" + import logging from typing import Any from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index 090f034b3d3..465c4aea749 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -1,4 +1,5 @@ """Test Websocket API http module.""" + import asyncio from datetime import timedelta from typing import Any, cast diff --git a/tests/components/websocket_api/test_messages.py b/tests/components/websocket_api/test_messages.py index 5fc0e4ea315..350aed8b5f7 100644 --- a/tests/components/websocket_api/test_messages.py +++ b/tests/components/websocket_api/test_messages.py @@ -1,4 +1,5 @@ """Test Websocket API messages module.""" + import pytest from homeassistant.components.websocket_api.messages import ( diff --git a/tests/components/wemo/conftest.py b/tests/components/wemo/conftest.py index 6c4d28ecae7..cdf1e7deb68 100644 --- a/tests/components/wemo/conftest.py +++ b/tests/components/wemo/conftest.py @@ -1,4 +1,5 @@ """Fixtures for pywemo.""" + import contextlib from unittest.mock import create_autospec, patch diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index 90bfadf402f..f999de408c0 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -2,6 +2,7 @@ This is not a test module. These test methods are used by the platform test modules. """ + import asyncio import threading diff --git a/tests/components/wemo/test_device_trigger.py b/tests/components/wemo/test_device_trigger.py index 9140f5f1e35..47b704dae5d 100644 --- a/tests/components/wemo/test_device_trigger.py +++ b/tests/components/wemo/test_device_trigger.py @@ -1,4 +1,5 @@ """Verify that WeMo device triggers work as expected.""" + import pytest from pytest_unordered import unordered from pywemo.subscribe import EVENT_TYPE_LONG_PRESS diff --git a/tests/components/wemo/test_fan.py b/tests/components/wemo/test_fan.py index 48ad5774b0a..7a1bb2c628b 100644 --- a/tests/components/wemo/test_fan.py +++ b/tests/components/wemo/test_fan.py @@ -1,4 +1,5 @@ """Tests for the Wemo fan entity.""" + import pytest from pywemo.exceptions import ActionException from pywemo.ouimeaux_device.humidifier import DesiredHumidity, FanMode diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 1d4271063f2..5512c2ee989 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -1,4 +1,5 @@ """Tests for the wemo component.""" + import asyncio from datetime import timedelta from unittest.mock import create_autospec, patch diff --git a/tests/components/wemo/test_light_dimmer.py b/tests/components/wemo/test_light_dimmer.py index 51ddc6cab2d..96d27b56963 100644 --- a/tests/components/wemo/test_light_dimmer.py +++ b/tests/components/wemo/test_light_dimmer.py @@ -1,4 +1,5 @@ """Tests for the Wemo standalone/non-bridge light entity.""" + import pytest from pywemo.exceptions import ActionException diff --git a/tests/components/wemo/test_switch.py b/tests/components/wemo/test_switch.py index 4df13a777d4..b73844bdb58 100644 --- a/tests/components/wemo/test_switch.py +++ b/tests/components/wemo/test_switch.py @@ -1,4 +1,5 @@ """Tests for the Wemo switch entity.""" + import pytest import pywemo diff --git a/tests/components/wemo/test_wemo_device.py b/tests/components/wemo/test_wemo_device.py index 5c8353fc8bc..7d23b590b57 100644 --- a/tests/components/wemo/test_wemo_device.py +++ b/tests/components/wemo/test_wemo_device.py @@ -1,4 +1,5 @@ """Tests for wemo_device.py.""" + import asyncio from dataclasses import asdict from datetime import timedelta diff --git a/tests/components/wiffi/test_config_flow.py b/tests/components/wiffi/test_config_flow.py index bf0b072df8c..14cb8a03f7a 100644 --- a/tests/components/wiffi/test_config_flow.py +++ b/tests/components/wiffi/test_config_flow.py @@ -1,4 +1,5 @@ """Test the wiffi integration config flow.""" + import errno from unittest.mock import patch diff --git a/tests/components/wilight/test_config_flow.py b/tests/components/wilight/test_config_flow.py index a46af6e7d82..e3496010c95 100644 --- a/tests/components/wilight/test_config_flow.py +++ b/tests/components/wilight/test_config_flow.py @@ -1,4 +1,5 @@ """Test the WiLight config flow.""" + import dataclasses from unittest.mock import patch diff --git a/tests/components/wiz/test_init.py b/tests/components/wiz/test_init.py index 41871381d3c..bd494da95b7 100644 --- a/tests/components/wiz/test_init.py +++ b/tests/components/wiz/test_init.py @@ -1,4 +1,5 @@ """Tests for wiz integration.""" + import datetime from unittest.mock import AsyncMock, patch diff --git a/tests/components/wled/test_binary_sensor.py b/tests/components/wled/test_binary_sensor.py index eb5faadd530..aa75b0c6696 100644 --- a/tests/components/wled/test_binary_sensor.py +++ b/tests/components/wled/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the WLED binary sensor platform.""" + import pytest from syrupy.assertion import SnapshotAssertion diff --git a/tests/components/wled/test_coordinator.py b/tests/components/wled/test_coordinator.py index 89817fb8569..14e8b620983 100644 --- a/tests/components/wled/test_coordinator.py +++ b/tests/components/wled/test_coordinator.py @@ -1,4 +1,5 @@ """Tests for the coordinator of the WLED integration.""" + import asyncio from collections.abc import Callable from copy import deepcopy diff --git a/tests/components/wled/test_init.py b/tests/components/wled/test_init.py index 38793898243..6f4c47ec201 100644 --- a/tests/components/wled/test_init.py +++ b/tests/components/wled/test_init.py @@ -1,4 +1,5 @@ """Tests for the WLED integration.""" + import asyncio from collections.abc import Callable from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index fc1d5503c07..2b64619e306 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -1,4 +1,5 @@ """Tests for the WLED light platform.""" + import json from unittest.mock import MagicMock diff --git a/tests/components/wled/test_number.py b/tests/components/wled/test_number.py index e91ec4f2e66..b692de37282 100644 --- a/tests/components/wled/test_number.py +++ b/tests/components/wled/test_number.py @@ -1,4 +1,5 @@ """Tests for the WLED number platform.""" + import json from unittest.mock import MagicMock diff --git a/tests/components/wled/test_select.py b/tests/components/wled/test_select.py index 219ec945021..380af1a286a 100644 --- a/tests/components/wled/test_select.py +++ b/tests/components/wled/test_select.py @@ -1,4 +1,5 @@ """Tests for the WLED select platform.""" + import json from unittest.mock import MagicMock diff --git a/tests/components/wled/test_switch.py b/tests/components/wled/test_switch.py index 40b7783fc04..6dfd62e363f 100644 --- a/tests/components/wled/test_switch.py +++ b/tests/components/wled/test_switch.py @@ -1,4 +1,5 @@ """Tests for the WLED switch platform.""" + import json from unittest.mock import MagicMock diff --git a/tests/components/worldclock/test_sensor.py b/tests/components/worldclock/test_sensor.py index 3ec7579bf68..00195a49827 100644 --- a/tests/components/worldclock/test_sensor.py +++ b/tests/components/worldclock/test_sensor.py @@ -1,4 +1,5 @@ """The test for the World clock sensor platform.""" + import pytest from homeassistant.core import HomeAssistant diff --git a/tests/components/xiaomi_ble/test_device_trigger.py b/tests/components/xiaomi_ble/test_device_trigger.py index 28195f784e8..6c6ad1c0f94 100644 --- a/tests/components/xiaomi_ble/test_device_trigger.py +++ b/tests/components/xiaomi_ble/test_device_trigger.py @@ -1,4 +1,5 @@ """Test Xiaomi BLE events.""" + import pytest from homeassistant.components import automation diff --git a/tests/components/xiaomi_ble/test_event.py b/tests/components/xiaomi_ble/test_event.py index a0e84c0ac2e..785c336106e 100644 --- a/tests/components/xiaomi_ble/test_event.py +++ b/tests/components/xiaomi_ble/test_event.py @@ -1,4 +1,5 @@ """Test the Xiaomi BLE events.""" + import pytest from homeassistant.components.event import ATTR_EVENT_TYPE diff --git a/tests/components/yalexs_ble/test_config_flow.py b/tests/components/yalexs_ble/test_config_flow.py index a8ce68a3209..510e2c8be56 100644 --- a/tests/components/yalexs_ble/test_config_flow.py +++ b/tests/components/yalexs_ble/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Yale Access Bluetooth config flow.""" + import asyncio from unittest.mock import AsyncMock, MagicMock, Mock, patch diff --git a/tests/components/yandex_transport/test_yandex_transport_sensor.py b/tests/components/yandex_transport/test_yandex_transport_sensor.py index bf9d0fd92b3..deb4fd1f360 100644 --- a/tests/components/yandex_transport/test_yandex_transport_sensor.py +++ b/tests/components/yandex_transport/test_yandex_transport_sensor.py @@ -1,4 +1,5 @@ """Tests for the yandex transport platform.""" + import json from unittest.mock import AsyncMock, patch diff --git a/tests/components/yeelight/conftest.py b/tests/components/yeelight/conftest.py index 9a9b9d19ec2..e4ce0afc9bf 100644 --- a/tests/components/yeelight/conftest.py +++ b/tests/components/yeelight/conftest.py @@ -1,4 +1,5 @@ """yeelight conftest.""" + import pytest from tests.components.light.conftest import mock_light_profiles # noqa: F401 diff --git a/tests/components/yolink/test_device_trigger.py b/tests/components/yolink/test_device_trigger.py index 0e258d0e1c7..678fe6e35cc 100644 --- a/tests/components/yolink/test_device_trigger.py +++ b/tests/components/yolink/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for YoLink device triggers.""" + import pytest from pytest_unordered import unordered from yolink.const import ATTR_DEVICE_DIMMER, ATTR_DEVICE_SMART_REMOTER diff --git a/tests/components/youtube/test_init.py b/tests/components/youtube/test_init.py index bd3babdc383..a6c3acbdd3b 100644 --- a/tests/components/youtube/test_init.py +++ b/tests/components/youtube/test_init.py @@ -1,4 +1,5 @@ """Tests for YouTube.""" + import http import time from unittest.mock import patch diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index d679ac5cb03..63d3e9cf747 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,4 +1,5 @@ """Common test objects.""" + import asyncio from datetime import timedelta import math diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 3cd20771e6e..57833f0e67e 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for ZHA config flow.""" + import copy from datetime import timedelta from ipaddress import ip_address diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index 964868118c4..a1b320097e8 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -1,4 +1,5 @@ """Test ZHA cover.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/zha/test_gateway.py b/tests/components/zha/test_gateway.py index e4d8d2a5d65..5ae1cc0e7e5 100644 --- a/tests/components/zha/test_gateway.py +++ b/tests/components/zha/test_gateway.py @@ -1,4 +1,5 @@ """Test ZHA Gateway.""" + import asyncio from unittest.mock import MagicMock, PropertyMock, patch diff --git a/tests/components/zha/test_helpers.py b/tests/components/zha/test_helpers.py index 7e3c4a40872..b97a2d0fa5d 100644 --- a/tests/components/zha/test_helpers.py +++ b/tests/components/zha/test_helpers.py @@ -1,4 +1,5 @@ """Tests for ZHA helpers.""" + import logging from unittest.mock import patch diff --git a/tests/components/zha/test_init.py b/tests/components/zha/test_init.py index 5887fa2d8bc..f516dca7094 100644 --- a/tests/components/zha/test_init.py +++ b/tests/components/zha/test_init.py @@ -1,4 +1,5 @@ """Tests for ZHA integration init.""" + import asyncio import typing from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/zone/test_trigger.py b/tests/components/zone/test_trigger.py index 7f44cecefe1..8987481f460 100644 --- a/tests/components/zone/test_trigger.py +++ b/tests/components/zone/test_trigger.py @@ -1,4 +1,5 @@ """The tests for the location automation.""" + import pytest from homeassistant.components import automation, zone diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index f2c3abd362a..98453071bc1 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -1,4 +1,5 @@ """Provide common Z-Wave JS fixtures.""" + import asyncio import copy import io diff --git a/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py b/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py index ee03d57f4c7..545c56d6f9d 100644 --- a/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py +++ b/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py @@ -1,4 +1,5 @@ """Test convert_device_diagnostics_to_fixture script.""" + import copy import json from pathlib import Path diff --git a/tests/components/zwave_js/test_button.py b/tests/components/zwave_js/test_button.py index 68a8e07ffe4..e1a1c6d665a 100644 --- a/tests/components/zwave_js/test_button.py +++ b/tests/components/zwave_js/test_button.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS button entities.""" + import pytest from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index c96f1c48797..9a4559de1a5 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS climate platform.""" + import copy import pytest diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index e8d6b0fd44c..ad066dec992 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS config flow.""" + import asyncio from collections.abc import Generator from copy import copy diff --git a/tests/components/zwave_js/test_config_validation.py b/tests/components/zwave_js/test_config_validation.py index b811ab7c10b..8428972bde1 100644 --- a/tests/components/zwave_js/test_config_validation.py +++ b/tests/components/zwave_js/test_config_validation.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS config validation helpers.""" + import pytest import voluptuous as vol diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index 54be2b43765..4ecd697f4d1 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS cover platform.""" + import logging from zwave_js_server.const import ( diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py index 2510143695c..054906cd0f6 100644 --- a/tests/components/zwave_js/test_diagnostics.py +++ b/tests/components/zwave_js/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS diagnostics.""" + import copy from unittest.mock import patch diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index 67f4a8d962f..2b597339e8f 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -1,4 +1,5 @@ """Test entity discovery for device-specific schemas for the Z-Wave JS integration.""" + import pytest from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index c26a5366d37..03cd6bfb704 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS fan platform.""" + import copy import pytest diff --git a/tests/components/zwave_js/test_helpers.py b/tests/components/zwave_js/test_helpers.py index b40c09b249d..38e15df52cc 100644 --- a/tests/components/zwave_js/test_helpers.py +++ b/tests/components/zwave_js/test_helpers.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS helpers module.""" + import voluptuous as vol from homeassistant.components.zwave_js.helpers import ( diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 4555ee59e1e..ee0e9d61707 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS init module.""" + import asyncio from copy import deepcopy import logging diff --git a/tests/components/zwave_js/test_lock.py b/tests/components/zwave_js/test_lock.py index 2213e9cf069..e8a8a2035d8 100644 --- a/tests/components/zwave_js/test_lock.py +++ b/tests/components/zwave_js/test_lock.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS lock platform.""" + import pytest from zwave_js_server.const import CommandClass from zwave_js_server.const.command_class.lock import ( diff --git a/tests/components/zwave_js/test_migrate.py b/tests/components/zwave_js/test_migrate.py index 1f5a96eb5f3..b5e88a745ef 100644 --- a/tests/components/zwave_js/test_migrate.py +++ b/tests/components/zwave_js/test_migrate.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS migration module.""" + import copy import pytest diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index a3d36b84382..2ae4dc7686a 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS sensor platform.""" + import copy import pytest diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index fd5c626bdd2..e9ffdef55cb 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS switch platform.""" + import pytest from zwave_js_server.const import CURRENT_VALUE_PROPERTY, CommandClass from zwave_js_server.event import Event diff --git a/tests/components/zwave_js/test_update.py b/tests/components/zwave_js/test_update.py index 1774254a3c5..64c72ddc1fa 100644 --- a/tests/components/zwave_js/test_update.py +++ b/tests/components/zwave_js/test_update.py @@ -1,4 +1,5 @@ """Test the Z-Wave JS update entities.""" + import asyncio from datetime import timedelta From a6b842f8188ccc0f893a7225911cf16bf7a7516b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:16:38 +0100 Subject: [PATCH 0562/1691] Add empty line after module docstring (2) [other] (#112738) --- homeassistant/helpers/signal.py | 1 + homeassistant/scripts/auth.py | 1 + homeassistant/scripts/ensure_config.py | 1 + homeassistant/scripts/macos/__init__.py | 1 + homeassistant/util/ssl.py | 1 + homeassistant/util/thread.py | 1 + script/hassfest/metadata.py | 1 + script/inspect_schemas.py | 1 + script/languages.py | 1 + script/lint_and_test.py | 1 + script/scaffold/__main__.py | 1 + script/scaffold/gather_info.py | 1 + .../templates/config_flow_discovery/integration/config_flow.py | 1 + script/scaffold/templates/config_flow_helper/tests/test_init.py | 1 + .../templates/config_flow_oauth2/integration/config_flow.py | 1 + .../templates/device_action/tests/test_device_action.py | 1 + .../templates/device_trigger/tests/test_device_trigger.py | 1 + .../templates/reproduce_state/tests/test_reproduce_state.py | 1 + script/translations/__main__.py | 1 + script/translations/clean.py | 1 + script/translations/const.py | 1 + script/translations/develop.py | 1 + script/translations/error.py | 1 + script/translations/frontend.py | 1 + script/translations/migrate.py | 1 + script/translations/upload.py | 1 + script/translations/util.py | 1 + script/version_bump.py | 1 + tests/auth/mfa_modules/test_notify.py | 1 + tests/auth/mfa_modules/test_totp.py | 1 + tests/auth/permissions/test_entities.py | 1 + tests/auth/providers/test_homeassistant.py | 1 + tests/auth/providers/test_legacy_api_password.py | 1 + tests/auth/test_auth_store.py | 1 + tests/hassfest/test_dependencies.py | 2 ++ tests/hassfest/test_version.py | 1 + tests/helpers/test_check_config.py | 1 + tests/helpers/test_debounce.py | 1 + tests/helpers/test_entity.py | 1 + tests/helpers/test_entity_platform.py | 1 + tests/helpers/test_floor_registry.py | 1 + tests/helpers/test_json.py | 1 + tests/helpers/test_label_registry.py | 1 + tests/helpers/test_normalized_name_base_registry.py | 1 + tests/helpers/test_ratelimit.py | 1 + tests/helpers/test_reload.py | 1 + tests/helpers/test_script.py | 1 + tests/helpers/test_script_variables.py | 1 + tests/helpers/test_service.py | 1 + tests/helpers/test_significant_change.py | 1 + tests/helpers/test_start.py | 1 + tests/helpers/test_state.py | 1 + tests/helpers/test_storage.py | 1 + tests/helpers/test_system_info.py | 1 + tests/helpers/test_temperature.py | 1 + tests/helpers/test_translation.py | 1 + tests/scripts/test_auth.py | 1 + tests/scripts/test_check_config.py | 1 + tests/test_bootstrap.py | 1 + tests/test_circular_imports.py | 1 + tests/test_data_entry_flow.py | 1 + tests/test_loader.py | 1 + tests/test_requirements.py | 1 + tests/test_runner.py | 1 + tests/test_util/aiohttp.py | 1 + tests/test_util/test_aiohttp.py | 1 + tests/testing_config/custom_components/test/button.py | 1 + tests/util/test_async.py | 1 + tests/util/test_color.py | 1 + tests/util/test_file.py | 1 + tests/util/test_logging.py | 1 + tests/util/test_package.py | 1 + tests/util/test_read_only_dict.py | 1 + tests/util/test_timeout.py | 1 + tests/util/yaml/test_input.py | 1 + 75 files changed, 76 insertions(+) diff --git a/homeassistant/helpers/signal.py b/homeassistant/helpers/signal.py index c7035d5a0d2..baaa36e83ce 100644 --- a/homeassistant/helpers/signal.py +++ b/homeassistant/helpers/signal.py @@ -1,4 +1,5 @@ """Signal handling related helpers.""" + import asyncio import logging import signal diff --git a/homeassistant/scripts/auth.py b/homeassistant/scripts/auth.py index dd3b9b7ba48..fff57c7adfe 100644 --- a/homeassistant/scripts/auth.py +++ b/homeassistant/scripts/auth.py @@ -1,4 +1,5 @@ """Script to manage users for the Home Assistant auth provider.""" + import argparse import asyncio import logging diff --git a/homeassistant/scripts/ensure_config.py b/homeassistant/scripts/ensure_config.py index 786b16ca923..e1ae7bc9142 100644 --- a/homeassistant/scripts/ensure_config.py +++ b/homeassistant/scripts/ensure_config.py @@ -1,4 +1,5 @@ """Script to ensure a configuration file exists.""" + import argparse import asyncio import os diff --git a/homeassistant/scripts/macos/__init__.py b/homeassistant/scripts/macos/__init__.py index 7668c73be9d..f629492ec39 100644 --- a/homeassistant/scripts/macos/__init__.py +++ b/homeassistant/scripts/macos/__init__.py @@ -1,4 +1,5 @@ """Script to install/uninstall HA into OS X.""" + import os import time diff --git a/homeassistant/util/ssl.py b/homeassistant/util/ssl.py index 6bfbec88a33..7c1e653ce75 100644 --- a/homeassistant/util/ssl.py +++ b/homeassistant/util/ssl.py @@ -1,4 +1,5 @@ """Helper to create SSL contexts.""" + import contextlib from enum import StrEnum from functools import cache diff --git a/homeassistant/util/thread.py b/homeassistant/util/thread.py index 0d600486f2f..7673d962d74 100644 --- a/homeassistant/util/thread.py +++ b/homeassistant/util/thread.py @@ -1,4 +1,5 @@ """Threading util helpers.""" + import ctypes import inspect import logging diff --git a/script/hassfest/metadata.py b/script/hassfest/metadata.py index 091c1b88e30..bd3ac4514e7 100644 --- a/script/hassfest/metadata.py +++ b/script/hassfest/metadata.py @@ -1,4 +1,5 @@ """Package metadata validation.""" + import tomllib from homeassistant.const import REQUIRED_PYTHON_VER, __version__ diff --git a/script/inspect_schemas.py b/script/inspect_schemas.py index cd72f55a855..a8ffe0afb60 100755 --- a/script/inspect_schemas.py +++ b/script/inspect_schemas.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Inspect all component SCHEMAS.""" + import importlib import os import pkgutil diff --git a/script/languages.py b/script/languages.py index f55fc21db93..bfc811a0905 100644 --- a/script/languages.py +++ b/script/languages.py @@ -1,4 +1,5 @@ """Helper script to update language list from the frontend source.""" + import json from pathlib import Path import sys diff --git a/script/lint_and_test.py b/script/lint_and_test.py index 48809ae4dcd..0b0562a0a84 100755 --- a/script/lint_and_test.py +++ b/script/lint_and_test.py @@ -3,6 +3,7 @@ This is NOT a full CI/linting replacement, only a quick check during development. """ + import asyncio from collections import namedtuple from contextlib import suppress diff --git a/script/scaffold/__main__.py b/script/scaffold/__main__.py index c303fc2c247..f81f9144f98 100644 --- a/script/scaffold/__main__.py +++ b/script/scaffold/__main__.py @@ -1,4 +1,5 @@ """Validate manifests.""" + import argparse from pathlib import Path import subprocess diff --git a/script/scaffold/gather_info.py b/script/scaffold/gather_info.py index 47106e7f956..cfa2669ebfe 100644 --- a/script/scaffold/gather_info.py +++ b/script/scaffold/gather_info.py @@ -1,4 +1,5 @@ """Gather info for scaffolding.""" + import json from homeassistant.util import slugify diff --git a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py index 4e1b70a51f3..e2cfed40e1d 100644 --- a/script/scaffold/templates/config_flow_discovery/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_discovery/integration/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NEW_NAME.""" + import my_pypi_dependency from homeassistant.core import HomeAssistant diff --git a/script/scaffold/templates/config_flow_helper/tests/test_init.py b/script/scaffold/templates/config_flow_helper/tests/test_init.py index 0e86874c745..9f6951189ac 100644 --- a/script/scaffold/templates/config_flow_helper/tests/test_init.py +++ b/script/scaffold/templates/config_flow_helper/tests/test_init.py @@ -1,4 +1,5 @@ """Test the NEW_NAME integration.""" + import pytest from homeassistant.components.NEW_DOMAIN.const import DOMAIN diff --git a/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py b/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py index a035a65dbb3..d855cc2a70b 100644 --- a/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/integration/config_flow.py @@ -1,4 +1,5 @@ """Config flow for NEW_NAME.""" + import logging from homeassistant.helpers import config_entry_oauth2_flow diff --git a/script/scaffold/templates/device_action/tests/test_device_action.py b/script/scaffold/templates/device_action/tests/test_device_action.py index a6e8f99854a..cd7adfbc844 100644 --- a/script/scaffold/templates/device_action/tests/test_device_action.py +++ b/script/scaffold/templates/device_action/tests/test_device_action.py @@ -1,4 +1,5 @@ """The tests for NEW_NAME device actions.""" + import pytest from pytest_unordered import unordered diff --git a/script/scaffold/templates/device_trigger/tests/test_device_trigger.py b/script/scaffold/templates/device_trigger/tests/test_device_trigger.py index 177c095b601..54b202c978c 100644 --- a/script/scaffold/templates/device_trigger/tests/test_device_trigger.py +++ b/script/scaffold/templates/device_trigger/tests/test_device_trigger.py @@ -1,4 +1,5 @@ """The tests for NEW_NAME device triggers.""" + import pytest from pytest_unordered import unordered diff --git a/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py b/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py index cb4e37c0933..a8cb7b09c96 100644 --- a/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py +++ b/script/scaffold/templates/reproduce_state/tests/test_reproduce_state.py @@ -1,4 +1,5 @@ """Test reproduce state for NEW_NAME.""" + import pytest from homeassistant.core import HomeAssistant, State diff --git a/script/translations/__main__.py b/script/translations/__main__.py index 52a39038107..c381b621e69 100644 --- a/script/translations/__main__.py +++ b/script/translations/__main__.py @@ -1,4 +1,5 @@ """Validate manifests.""" + import argparse import importlib from pathlib import Path diff --git a/script/translations/clean.py b/script/translations/clean.py index 0f2eb40300d..0403e04f789 100644 --- a/script/translations/clean.py +++ b/script/translations/clean.py @@ -1,4 +1,5 @@ """Find translation keys that are in Lokalise but no longer defined in source.""" + import argparse from .const import CORE_PROJECT_ID, FRONTEND_DIR, FRONTEND_PROJECT_ID, INTEGRATIONS_DIR diff --git a/script/translations/const.py b/script/translations/const.py index ef8e3f2df74..9ff8aeb2d70 100644 --- a/script/translations/const.py +++ b/script/translations/const.py @@ -1,4 +1,5 @@ """Translation constants.""" + import pathlib CORE_PROJECT_ID = "130246255a974bd3b5e8a1.51616605" diff --git a/script/translations/develop.py b/script/translations/develop.py index 3e386afb641..14e3c320c3e 100644 --- a/script/translations/develop.py +++ b/script/translations/develop.py @@ -1,4 +1,5 @@ """Compile the current translation strings files for testing.""" + import argparse import json from pathlib import Path diff --git a/script/translations/error.py b/script/translations/error.py index 210af95f325..4811210ae3c 100644 --- a/script/translations/error.py +++ b/script/translations/error.py @@ -1,4 +1,5 @@ """Errors for translations.""" + import json diff --git a/script/translations/frontend.py b/script/translations/frontend.py index bb0e98e1c93..289c885fe33 100644 --- a/script/translations/frontend.py +++ b/script/translations/frontend.py @@ -1,4 +1,5 @@ """Write updated translations to the frontend.""" + import argparse import json diff --git a/script/translations/migrate.py b/script/translations/migrate.py index c3057800973..a6cffe28f7f 100644 --- a/script/translations/migrate.py +++ b/script/translations/migrate.py @@ -1,4 +1,5 @@ """Migrate things.""" + import json import pathlib from pprint import pprint diff --git a/script/translations/upload.py b/script/translations/upload.py index eaf1c07ad91..ee4a57bc00a 100755 --- a/script/translations/upload.py +++ b/script/translations/upload.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Merge all translation sources into a single JSON file.""" + import json import os import pathlib diff --git a/script/translations/util.py b/script/translations/util.py index aab98e049d9..8892bb46b7a 100644 --- a/script/translations/util.py +++ b/script/translations/util.py @@ -1,4 +1,5 @@ """Translation utils.""" + import argparse import json import os diff --git a/script/version_bump.py b/script/version_bump.py index 3a6c0fa7540..50f749d6983 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Helper script to bump the current version.""" + import argparse import re import subprocess diff --git a/tests/auth/mfa_modules/test_notify.py b/tests/auth/mfa_modules/test_notify.py index d5b7e0bbc62..662da38b35b 100644 --- a/tests/auth/mfa_modules/test_notify.py +++ b/tests/auth/mfa_modules/test_notify.py @@ -1,4 +1,5 @@ """Test the HMAC-based One Time Password (MFA) auth module.""" + import asyncio from unittest.mock import patch diff --git a/tests/auth/mfa_modules/test_totp.py b/tests/auth/mfa_modules/test_totp.py index e5b1a930dd7..961db3f44ca 100644 --- a/tests/auth/mfa_modules/test_totp.py +++ b/tests/auth/mfa_modules/test_totp.py @@ -1,4 +1,5 @@ """Test the Time-based One Time Password (MFA) auth module.""" + import asyncio from unittest.mock import patch diff --git a/tests/auth/permissions/test_entities.py b/tests/auth/permissions/test_entities.py index ce539d51dde..7f5355b3cc0 100644 --- a/tests/auth/permissions/test_entities.py +++ b/tests/auth/permissions/test_entities.py @@ -1,4 +1,5 @@ """Tests for entity permissions.""" + import pytest import voluptuous as vol diff --git a/tests/auth/providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py index 222911a61c2..22d4ba26845 100644 --- a/tests/auth/providers/test_homeassistant.py +++ b/tests/auth/providers/test_homeassistant.py @@ -1,4 +1,5 @@ """Test the Home Assistant local auth provider.""" + import asyncio from unittest.mock import Mock, patch diff --git a/tests/auth/providers/test_legacy_api_password.py b/tests/auth/providers/test_legacy_api_password.py index 75c4f733285..9f1f98aeaf0 100644 --- a/tests/auth/providers/test_legacy_api_password.py +++ b/tests/auth/providers/test_legacy_api_password.py @@ -1,4 +1,5 @@ """Tests for the legacy_api_password auth provider.""" + import pytest from homeassistant import auth, data_entry_flow diff --git a/tests/auth/test_auth_store.py b/tests/auth/test_auth_store.py index 858d4d082b1..796f2335569 100644 --- a/tests/auth/test_auth_store.py +++ b/tests/auth/test_auth_store.py @@ -1,4 +1,5 @@ """Tests for the auth store.""" + import asyncio from datetime import timedelta from typing import Any diff --git a/tests/hassfest/test_dependencies.py b/tests/hassfest/test_dependencies.py index 92a7fa27dc4..84e02b2d9d5 100644 --- a/tests/hassfest/test_dependencies.py +++ b/tests/hassfest/test_dependencies.py @@ -1,4 +1,5 @@ """Tests for hassfest dependency finder.""" + import ast import pytest @@ -59,6 +60,7 @@ def test_renamed_absolute(mock_collector) -> None: mock_collector.visit( ast.parse( """ + import homeassistant.components.renamed_absolute as hue """ ) diff --git a/tests/hassfest/test_version.py b/tests/hassfest/test_version.py index f473e988027..9cc1bbb11e5 100644 --- a/tests/hassfest/test_version.py +++ b/tests/hassfest/test_version.py @@ -1,4 +1,5 @@ """Tests for hassfest version.""" + import pytest import voluptuous as vol diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index a7c5fe36cff..fdb61505d5e 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -1,4 +1,5 @@ """Test check_config helper.""" + import logging from unittest.mock import Mock, patch diff --git a/tests/helpers/test_debounce.py b/tests/helpers/test_debounce.py index 958b88951ce..24f243086e5 100644 --- a/tests/helpers/test_debounce.py +++ b/tests/helpers/test_debounce.py @@ -1,4 +1,5 @@ """Tests for debounce.""" + import asyncio from datetime import timedelta import logging diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index e9950ec4dfc..241a26e6529 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -1,4 +1,5 @@ """Test the entity helper.""" + import asyncio from collections.abc import Iterable import dataclasses diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index f6fc5888c1c..4a13e6c84d1 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1,4 +1,5 @@ """Tests for the EntityPlatform helper.""" + import asyncio from collections.abc import Iterable from datetime import timedelta diff --git a/tests/helpers/test_floor_registry.py b/tests/helpers/test_floor_registry.py index feb5ce505ac..c5e5b42fafa 100644 --- a/tests/helpers/test_floor_registry.py +++ b/tests/helpers/test_floor_registry.py @@ -1,4 +1,5 @@ """Tests for the floor registry.""" + import re from typing import Any diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 2106a397baf..192db706974 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -1,4 +1,5 @@ """Test Home Assistant remote methods and classes.""" + import datetime from functools import partial import json diff --git a/tests/helpers/test_label_registry.py b/tests/helpers/test_label_registry.py index 13f3837737b..785919b25c0 100644 --- a/tests/helpers/test_label_registry.py +++ b/tests/helpers/test_label_registry.py @@ -1,4 +1,5 @@ """Tests for the Label Registry.""" + import re from typing import Any diff --git a/tests/helpers/test_normalized_name_base_registry.py b/tests/helpers/test_normalized_name_base_registry.py index 0b0e53abe83..495d147340f 100644 --- a/tests/helpers/test_normalized_name_base_registry.py +++ b/tests/helpers/test_normalized_name_base_registry.py @@ -1,4 +1,5 @@ """Tests for the normalized name base registry helper.""" + import pytest from homeassistant.helpers.normalized_name_base_registry import ( diff --git a/tests/helpers/test_ratelimit.py b/tests/helpers/test_ratelimit.py index 39e83953c45..9bd18a7e882 100644 --- a/tests/helpers/test_ratelimit.py +++ b/tests/helpers/test_ratelimit.py @@ -1,4 +1,5 @@ """Tests for ratelimit.""" + import asyncio from datetime import timedelta diff --git a/tests/helpers/test_reload.py b/tests/helpers/test_reload.py index 4425ce00ce1..4f7a2053a87 100644 --- a/tests/helpers/test_reload.py +++ b/tests/helpers/test_reload.py @@ -1,4 +1,5 @@ """Tests for the reload helper.""" + import logging from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 3dfe8fdbad9..b58b03fa9a9 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -1,4 +1,5 @@ """The tests for the Script component.""" + import asyncio from contextlib import contextmanager from datetime import timedelta diff --git a/tests/helpers/test_script_variables.py b/tests/helpers/test_script_variables.py index 99aae08587e..ca942acdf66 100644 --- a/tests/helpers/test_script_variables.py +++ b/tests/helpers/test_script_variables.py @@ -1,4 +1,5 @@ """Test script variables.""" + import pytest from homeassistant.core import HomeAssistant diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 90f9b65aaba..b0d94bad23b 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -1,4 +1,5 @@ """Test service helpers.""" + import asyncio from collections.abc import Iterable from copy import deepcopy diff --git a/tests/helpers/test_significant_change.py b/tests/helpers/test_significant_change.py index 6444781aa85..8dda63bb72b 100644 --- a/tests/helpers/test_significant_change.py +++ b/tests/helpers/test_significant_change.py @@ -1,4 +1,5 @@ """Test significant change helper.""" + import pytest from homeassistant.components.sensor import SensorDeviceClass diff --git a/tests/helpers/test_start.py b/tests/helpers/test_start.py index d203b336f27..d9c6bbf441c 100644 --- a/tests/helpers/test_start.py +++ b/tests/helpers/test_start.py @@ -1,4 +1,5 @@ """Test starting HA helpers.""" + import pytest from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 255fba0e7e7..150f31f5fe9 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -1,4 +1,5 @@ """Test state helpers.""" + import asyncio from unittest.mock import patch diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 66dd8c10463..0918a9551f4 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -1,4 +1,5 @@ """Tests for the storage helper.""" + import asyncio from datetime import timedelta import json diff --git a/tests/helpers/test_system_info.py b/tests/helpers/test_system_info.py index 5c3697ad936..1b3f26ee763 100644 --- a/tests/helpers/test_system_info.py +++ b/tests/helpers/test_system_info.py @@ -1,4 +1,5 @@ """Tests for the system info helper.""" + import json import os from unittest.mock import patch diff --git a/tests/helpers/test_temperature.py b/tests/helpers/test_temperature.py index ceb4f7bdef2..a5daeac2a6a 100644 --- a/tests/helpers/test_temperature.py +++ b/tests/helpers/test_temperature.py @@ -1,4 +1,5 @@ """Tests Home Assistant temperature helpers.""" + import pytest from homeassistant.const import ( diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 839607cc8b8..772b5d05f79 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -1,4 +1,5 @@ """Test the translation helper.""" + import asyncio from os import path import pathlib diff --git a/tests/scripts/test_auth.py b/tests/scripts/test_auth.py index 3d9f26d1204..8367eda76e8 100644 --- a/tests/scripts/test_auth.py +++ b/tests/scripts/test_auth.py @@ -1,4 +1,5 @@ """Test the auth script to manage local users.""" + import logging from typing import Any from unittest.mock import Mock, patch diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 9e2f6708c99..a77e5bf504a 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -1,4 +1,5 @@ """Test check_config script.""" + import logging from unittest.mock import patch diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 83722566590..4f3bcb74d7d 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,4 +1,5 @@ """Test the bootstrapping.""" + import asyncio from collections.abc import Generator, Iterable import glob diff --git a/tests/test_circular_imports.py b/tests/test_circular_imports.py index 1c5157b74e1..79f0fd9caf7 100644 --- a/tests/test_circular_imports.py +++ b/tests/test_circular_imports.py @@ -1,4 +1,5 @@ """Test to check for circular imports in core components.""" + import asyncio import sys diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index c6a5f65be92..710a637e941 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -1,4 +1,5 @@ """Test the flow classes.""" + import asyncio import dataclasses import logging diff --git a/tests/test_loader.py b/tests/test_loader.py index d73ae161041..7828d197fc1 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,4 +1,5 @@ """Test to verify that we can load components.""" + import asyncio import os import sys diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 228ab2df2b4..87d055e2122 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -1,4 +1,5 @@ """Test requirements module.""" + import asyncio import logging import os diff --git a/tests/test_runner.py b/tests/test_runner.py index 14728321721..c49093bd4d6 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -1,4 +1,5 @@ """Test the runner.""" + import asyncio from collections.abc import Iterator import threading diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 4f2518253ff..df232e51aee 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -1,4 +1,5 @@ """Aiohttp test utils.""" + import asyncio from contextlib import contextmanager from http import HTTPStatus diff --git a/tests/test_util/test_aiohttp.py b/tests/test_util/test_aiohttp.py index 525022685bd..7226e84b04c 100644 --- a/tests/test_util/test_aiohttp.py +++ b/tests/test_util/test_aiohttp.py @@ -1,4 +1,5 @@ """Tests for our aiohttp mocker.""" + import pytest from .aiohttp import AiohttpClientMocker diff --git a/tests/testing_config/custom_components/test/button.py b/tests/testing_config/custom_components/test/button.py index 99c6868f834..8daf3a741a1 100644 --- a/tests/testing_config/custom_components/test/button.py +++ b/tests/testing_config/custom_components/test/button.py @@ -2,6 +2,7 @@ Call init before using it in your tests to ensure clean test data. """ + import logging from homeassistant.components.button import ButtonEntity diff --git a/tests/util/test_async.py b/tests/util/test_async.py index 199e21960e4..fc0032204d7 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -1,4 +1,5 @@ """Tests for async util methods from Python source.""" + import asyncio import sys import time diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 5dd20d8d887..53c243a1e4f 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -1,4 +1,5 @@ """Test Home Assistant color util methods.""" + import math import pytest diff --git a/tests/util/test_file.py b/tests/util/test_file.py index dc09ff83e9e..64c74b73dc3 100644 --- a/tests/util/test_file.py +++ b/tests/util/test_file.py @@ -1,4 +1,5 @@ """Test Home Assistant file utility functions.""" + import os from pathlib import Path from unittest.mock import patch diff --git a/tests/util/test_logging.py b/tests/util/test_logging.py index 350baa9d4c2..2473485e103 100644 --- a/tests/util/test_logging.py +++ b/tests/util/test_logging.py @@ -1,4 +1,5 @@ """Test Home Assistant logging util methods.""" + import asyncio from functools import partial import logging diff --git a/tests/util/test_package.py b/tests/util/test_package.py index 42ba0131d71..0e2e9278676 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -1,4 +1,5 @@ """Test Home Assistant package util methods.""" + import asyncio from importlib.metadata import metadata import logging diff --git a/tests/util/test_read_only_dict.py b/tests/util/test_read_only_dict.py index c975bf01304..888ea59fb11 100644 --- a/tests/util/test_read_only_dict.py +++ b/tests/util/test_read_only_dict.py @@ -1,4 +1,5 @@ """Test read only dictionary.""" + import json import pytest diff --git a/tests/util/test_timeout.py b/tests/util/test_timeout.py index 4f841c27f0d..99430cc0361 100644 --- a/tests/util/test_timeout.py +++ b/tests/util/test_timeout.py @@ -1,4 +1,5 @@ """Test Home Assistant timeout handler.""" + import asyncio from contextlib import suppress import time diff --git a/tests/util/yaml/test_input.py b/tests/util/yaml/test_input.py index 42582de4aff..682e4bcac03 100644 --- a/tests/util/yaml/test_input.py +++ b/tests/util/yaml/test_input.py @@ -1,4 +1,5 @@ """Test inputs.""" + import pytest from homeassistant.util.yaml import ( From 25237e0377a2dfbaa4d8400a3645a243e79b0137 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:35:17 +0100 Subject: [PATCH 0563/1691] Replace EventType with Event [a-g] (#112739) --- homeassistant/components/alert/__init__.py | 8 +++----- homeassistant/components/apache_kafka/__init__.py | 6 +++--- homeassistant/components/api/__init__.py | 4 ++-- homeassistant/components/bayesian/binary_sensor.py | 8 ++++---- homeassistant/components/compensation/sensor.py | 6 +++--- .../components/conversation/default_agent.py | 4 ++-- homeassistant/components/derivative/sensor.py | 6 +++--- homeassistant/components/dhcp/__init__.py | 6 ++---- homeassistant/components/emulated_hue/config.py | 6 +++--- homeassistant/components/emulated_hue/hue_api.py | 5 ++--- homeassistant/components/esphome/manager.py | 3 +-- homeassistant/components/filter/sensor.py | 11 +++-------- .../components/generic_thermostat/climate.py | 8 +++----- homeassistant/components/geo_location/trigger.py | 13 ++++++++++--- homeassistant/components/group/__init__.py | 9 +++++---- homeassistant/components/group/event.py | 6 +++--- homeassistant/components/group/media_player.py | 8 ++++---- 17 files changed, 56 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/alert/__init__.py b/homeassistant/components/alert/__init__.py index 07a6c735696..471d32227c2 100644 --- a/homeassistant/components/alert/__init__.py +++ b/homeassistant/components/alert/__init__.py @@ -26,7 +26,7 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, ) -from homeassistant.core import HassJob, HomeAssistant +from homeassistant.core import Event, HassJob, HomeAssistant from homeassistant.exceptions import ServiceNotFound import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -37,7 +37,7 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.template import Template -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.util.dt import now from .const import ( @@ -199,9 +199,7 @@ class Alert(Entity): return STATE_ON return STATE_IDLE - async def watched_entity_change( - self, event: EventType[EventStateChangedData] - ) -> None: + async def watched_entity_change(self, event: Event[EventStateChangedData]) -> None: """Determine if the alert should start or stop.""" if (to_state := event.data["new_state"]) is None: return diff --git a/homeassistant/components/apache_kafka/__init__.py b/homeassistant/components/apache_kafka/__init__.py index f3ace718638..f7abfcf66ef 100644 --- a/homeassistant/components/apache_kafka/__init__.py +++ b/homeassistant/components/apache_kafka/__init__.py @@ -23,7 +23,7 @@ from homeassistant.core import Event, HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA, EntityFilter from homeassistant.helpers.event import EventStateChangedData -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.util import ssl as ssl_util DOMAIN = "apache_kafka" @@ -117,7 +117,7 @@ class KafkaManager: ) self._topic = topic - def _encode_event(self, event: EventType[EventStateChangedData]) -> bytes | None: + def _encode_event(self, event: Event[EventStateChangedData]) -> bytes | None: """Translate events into a binary JSON payload.""" state = event.data["new_state"] if ( @@ -140,7 +140,7 @@ class KafkaManager: """Shut the manager down.""" await self._producer.stop() - async def write(self, event: EventType[EventStateChangedData]) -> None: + async def write(self, event: Event[EventStateChangedData]) -> None: """Write a binary payload to Kafka.""" payload = self._encode_event(event) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 15ece3e296a..c4543b55514 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -49,7 +49,7 @@ from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.event import EventStateChangedData from homeassistant.helpers.json import json_dumps, json_fragment from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.util.json import json_loads _LOGGER = logging.getLogger(__name__) @@ -390,7 +390,7 @@ class APIDomainServicesView(HomeAssistantView): @ha.callback def _async_save_changed_entities( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: if event.context == context and (state := event.data["new_state"]): changed_states.append(state.json_fragment) diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 8ff1f2476d5..cccb08c5540 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -28,7 +28,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConditionError, TemplateError from homeassistant.helpers import condition import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.template import Template, result_as_boolean -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import DOMAIN, PLATFORMS from .const import ( @@ -234,7 +234,7 @@ class BayesianBinarySensor(BinarySensorEntity): @callback def async_threshold_sensor_state_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle sensor state changes. @@ -260,7 +260,7 @@ class BayesianBinarySensor(BinarySensorEntity): @callback def _async_template_result_changed( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_template_result = updates.pop() diff --git a/homeassistant/components/compensation/sensor.py b/homeassistant/components/compensation/sensor.py index a5dd4a65917..11d838e2467 100644 --- a/homeassistant/components/compensation/sensor.py +++ b/homeassistant/components/compensation/sensor.py @@ -18,13 +18,13 @@ from homeassistant.const import ( CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ( CONF_COMPENSATION, @@ -129,7 +129,7 @@ class CompensationSensor(SensorEntity): @callback def _async_compensation_sensor_state_listener( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle sensor state changes.""" new_state: State | None diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index cd371ff0630..4d7ca4a47ee 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -115,7 +115,7 @@ def async_setup(hass: core.HomeAssistant) -> None: async_should_expose(hass, DOMAIN, entity_id) @core.callback - def async_entity_state_listener(event: EventType[EventStateChangedData]) -> None: + def async_entity_state_listener(event: core.Event[EventStateChangedData]) -> None: """Set expose flag on new entities.""" async_should_expose(hass, DOMAIN, event.data["entity_id"]) @@ -714,7 +714,7 @@ class DefaultAgent(AbstractConversationAgent): @core.callback def _async_handle_state_changed( - self, event: EventType[EventStateChangedData] + self, event: core.Event[EventStateChangedData] ) -> None: """Clear names list cache when a state is added or removed from the state machine.""" if event.data["old_state"] and event.data["new_state"]: diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index e46482babc8..ea343288c9c 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -19,7 +19,7 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfTime, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -31,7 +31,7 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ( CONF_ROUND_DIGITS, @@ -212,7 +212,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity): _LOGGER.warning("Could not restore last state: %s", err) @callback - def calc_derivative(event: EventType[EventStateChangedData]) -> None: + def calc_derivative(event: Event[EventStateChangedData]) -> None: """Handle the sensor state changes.""" if ( (old_state := event.data["old_state"]) is None diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 305e5870663..45d3ff77c3a 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -53,7 +53,7 @@ from homeassistant.helpers.event import ( async_track_state_added_domain, async_track_time_interval, ) -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import DHCPMatcher, async_get_dhcp from .const import DOMAIN @@ -345,9 +345,7 @@ class DeviceTrackerWatcher(WatcherBase): self._async_process_device_state(state) @callback - def _async_process_device_event( - self, event: EventType[EventStateChangedData] - ) -> None: + def _async_process_device_event(self, event: Event[EventStateChangedData]) -> None: """Process a device tracker state change event.""" self._async_process_device_state(event.data["new_state"]) diff --git a/homeassistant/components/emulated_hue/config.py b/homeassistant/components/emulated_hue/config.py index cd0617134f6..b4208c1f3f6 100644 --- a/homeassistant/components/emulated_hue/config.py +++ b/homeassistant/components/emulated_hue/config.py @@ -16,14 +16,14 @@ from homeassistant.components import ( script, ) from homeassistant.const import CONF_ENTITIES, CONF_TYPE -from homeassistant.core import HomeAssistant, State, callback, split_entity_id +from homeassistant.core import Event, HomeAssistant, State, callback, split_entity_id from homeassistant.helpers import storage from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_added_domain, async_track_state_removed_domain, ) -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType SUPPORTED_DOMAINS = { climate.DOMAIN, @@ -224,7 +224,7 @@ class Config: ] @callback - def _clear_exposed_cache(self, event: EventType[EventStateChangedData]) -> None: + def _clear_exposed_cache(self, event: Event[EventStateChangedData]) -> None: """Clear the cache of exposed entity ids.""" self.get_exposed_entity_ids.cache_clear() diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 73242da4b01..91c4440d875 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -65,12 +65,11 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import State +from homeassistant.core import Event, State from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from homeassistant.util.json import json_loads from homeassistant.util.network import is_local @@ -916,7 +915,7 @@ async def wait_for_state_change_or_timeout( ev = asyncio.Event() @core.callback - def _async_event_changed(event: EventType[EventStateChangedData]) -> None: + def _async_event_changed(event: Event[EventStateChangedData]) -> None: ev.set() unsub = async_track_state_change_event(hass, [entity_id], _async_event_changed) diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index 07c4a39e113..27fb2cece89 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -50,7 +50,6 @@ from homeassistant.helpers.issue_registry import ( ) from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.template import Template -from homeassistant.helpers.typing import EventType from homeassistant.util.async_ import create_eager_task from .bluetooth import async_connect_scanner @@ -283,7 +282,7 @@ class ESPHomeManager: def _send_home_assistant_state_event( self, attribute: str | None, - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Forward Home Assistant states updates to ESPHome.""" event_data = event.data diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 783bbbe740e..4289444c6f4 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -35,7 +35,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( @@ -44,12 +44,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.start import async_at_started -from homeassistant.helpers.typing import ( - ConfigType, - DiscoveryInfoType, - EventType, - StateType, -) +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.util.decorator import Registry import homeassistant.util.dt as dt_util @@ -227,7 +222,7 @@ class SensorFilter(SensorEntity): @callback def _update_filter_sensor_state_event( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle device state changes.""" _LOGGER.debug("Update filter on event: %s", event) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index af082004f39..0c63f7d2d15 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -60,7 +60,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import DOMAIN, PLATFORMS @@ -413,9 +413,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): # Get default temp from super class return super().max_temp - async def _async_sensor_changed( - self, event: EventType[EventStateChangedData] - ) -> None: + async def _async_sensor_changed(self, event: Event[EventStateChangedData]) -> None: """Handle temperature changes.""" new_state = event.data["new_state"] if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): @@ -438,7 +436,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): await self._async_heater_turn_off() @callback - def _async_switch_changed(self, event: EventType[EventStateChangedData]) -> None: + def _async_switch_changed(self, event: Event[EventStateChangedData]) -> None: """Handle heater switch state changes.""" new_state = event.data["new_state"] old_state = event.data["old_state"] diff --git a/homeassistant/components/geo_location/trigger.py b/homeassistant/components/geo_location/trigger.py index bb63440836f..fb6140f707c 100644 --- a/homeassistant/components/geo_location/trigger.py +++ b/homeassistant/components/geo_location/trigger.py @@ -8,7 +8,14 @@ from typing import Final import voluptuous as vol from homeassistant.const import CONF_EVENT, CONF_PLATFORM, CONF_SOURCE, CONF_ZONE -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, State, callback +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + HassJob, + HomeAssistant, + State, + callback, +) from homeassistant.helpers import condition, config_validation as cv from homeassistant.helpers.config_validation import entity_domain from homeassistant.helpers.event import ( @@ -17,7 +24,7 @@ from homeassistant.helpers.event import ( async_track_state_change_filtered, ) from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from . import DOMAIN @@ -58,7 +65,7 @@ async def async_attach_trigger( job = HassJob(action) @callback - def state_change_listener(event: EventType[EventStateChangedData]) -> None: + def state_change_listener(event: Event[EventStateChangedData]) -> None: """Handle specific state changes.""" # Skip if the event's source does not match the trigger's source. from_state = event.data["old_state"] diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index af843db6257..cc060be88cc 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -27,6 +27,7 @@ from homeassistant.const import ( ) from homeassistant.core import ( CALLBACK_TYPE, + Event, HomeAssistant, ServiceCall, State, @@ -48,7 +49,7 @@ from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, ) from homeassistant.helpers.reload import async_reload_integration_platforms -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from .const import CONF_HIDE_MEMBERS @@ -456,7 +457,7 @@ class GroupEntity(Entity): @callback def async_state_changed_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, ) -> None: """Handle child updates.""" self.async_update_group_state() @@ -481,7 +482,7 @@ class GroupEntity(Entity): @callback def async_state_changed_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle child updates.""" self.async_set_context(event.context) @@ -762,7 +763,7 @@ class Group(Entity): self._async_stop() async def _async_state_changed_listener( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Respond to a member state changing. diff --git a/homeassistant/components/group/event.py b/homeassistant/components/group/event.py index def92b3d6d4..8095a0e89c1 100644 --- a/homeassistant/components/group/event.py +++ b/homeassistant/components/group/event.py @@ -25,14 +25,14 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from . import GroupEntity @@ -125,7 +125,7 @@ class EventGroup(GroupEntity, EventEntity): @callback def async_state_changed_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle child updates.""" if not self.hass.is_running: diff --git a/homeassistant/components/group/media_player.py b/homeassistant/components/group/media_player.py index 82a15177a4a..ccb7154f7c1 100644 --- a/homeassistant/components/group/media_player.py +++ b/homeassistant/components/group/media_player.py @@ -45,14 +45,14 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, State, callback +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType KEY_ANNOUNCE = "announce" KEY_CLEAR_PLAYLIST = "clear_playlist" @@ -148,7 +148,7 @@ class MediaPlayerGroup(MediaPlayerEntity): } @callback - def async_on_state_change(self, event: EventType[EventStateChangedData]) -> None: + def async_on_state_change(self, event: Event[EventStateChangedData]) -> None: """Update supported features and state when a new state is received.""" self.async_set_context(event.context) self.async_update_supported_features( @@ -233,7 +233,7 @@ class MediaPlayerGroup(MediaPlayerEntity): @callback def async_state_changed_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, ) -> None: """Handle child updates.""" self.async_update_group_state() From 8f1e2f1a7b1a40e46fbf6f7cdf0111d9a593ab56 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:35:47 +0100 Subject: [PATCH 0564/1691] Replace EventType with Event [h-i] (#112740) --- homeassistant/components/history/websocket_api.py | 3 +-- .../components/history_stats/coordinator.py | 5 ++--- homeassistant/components/history_stats/data.py | 5 ++--- .../homeassistant/triggers/numeric_state.py | 13 ++++++++++--- .../components/homeassistant/triggers/state.py | 13 ++++++++++--- .../components/homeassistant/triggers/time.py | 13 ++++++++++--- homeassistant/components/homekit/accessories.py | 8 ++++---- homeassistant/components/homekit/type_cameras.py | 7 +++---- homeassistant/components/homekit/type_covers.py | 5 ++--- .../components/homekit/type_humidifiers.py | 5 ++--- homeassistant/components/homekit/util.py | 5 ++--- homeassistant/components/integration/sensor.py | 6 +++--- homeassistant/components/knx/expose.py | 8 +++----- 13 files changed, 54 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/history/websocket_api.py b/homeassistant/components/history/websocket_api.py index 63142b139bc..1f1c5064c5a 100644 --- a/homeassistant/components/history/websocket_api.py +++ b/homeassistant/components/history/websocket_api.py @@ -36,7 +36,6 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.json import json_bytes -from homeassistant.helpers.typing import EventType import homeassistant.util.dt as dt_util from .const import EVENT_COALESCE_TIME, MAX_PENDING_HISTORY_STATES @@ -373,7 +372,7 @@ def _async_subscribe_events( assert is_callback(target), "target must be a callback" @callback - def _forward_state_events_filtered(event: EventType[EventStateChangedData]) -> None: + def _forward_state_events_filtered(event: Event[EventStateChangedData]) -> None: """Filter state events and forward them.""" if (new_state := event.data["new_state"]) is None or ( old_state := event.data["old_state"] diff --git a/homeassistant/components/history_stats/coordinator.py b/homeassistant/components/history_stats/coordinator.py index 0ab68adda57..2127f1d3dc5 100644 --- a/homeassistant/components/history_stats/coordinator.py +++ b/homeassistant/components/history_stats/coordinator.py @@ -6,14 +6,13 @@ from datetime import timedelta import logging from typing import Any -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) from homeassistant.helpers.start import async_at_start -from homeassistant.helpers.typing import EventType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .data import HistoryStats, HistoryStatsState @@ -88,7 +87,7 @@ class HistoryStatsUpdateCoordinator(DataUpdateCoordinator[HistoryStatsState]): ) async def _async_update_from_event( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Process an update from an event.""" self.async_set_updated_data(await self._history_stats.async_update(event)) diff --git a/homeassistant/components/history_stats/data.py b/homeassistant/components/history_stats/data.py index 9a26853cbb5..62ab28dc4f1 100644 --- a/homeassistant/components/history_stats/data.py +++ b/homeassistant/components/history_stats/data.py @@ -6,10 +6,9 @@ from dataclasses import dataclass import datetime from homeassistant.components.recorder import get_instance, history -from homeassistant.core import HomeAssistant, State +from homeassistant.core import Event, HomeAssistant, State from homeassistant.helpers.event import EventStateChangedData from homeassistant.helpers.template import Template -from homeassistant.helpers.typing import EventType import homeassistant.util.dt as dt_util from .helpers import async_calculate_period, floored_timestamp @@ -59,7 +58,7 @@ class HistoryStats: self._end = end async def async_update( - self, event: EventType[EventStateChangedData] | None + self, event: Event[EventStateChangedData] | None ) -> HistoryStatsState: """Update the stats at a given time.""" # Get previous values of start and end diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 713c72acb89..2575af41401 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -19,7 +19,14 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_VALUE_TEMPLATE, ) -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, State, callback +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + HassJob, + HomeAssistant, + State, + callback, +) from homeassistant.helpers import ( condition, config_validation as cv, @@ -32,7 +39,7 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType _T = TypeVar("_T", bound=dict[str, Any]) @@ -152,7 +159,7 @@ async def async_attach_trigger( ) @callback - def state_automation_listener(event: EventType[EventStateChangedData]) -> None: + def state_automation_listener(event: Event[EventStateChangedData]) -> None: """Listen for state changes and calls action.""" entity_id = event.data["entity_id"] from_s = event.data["old_state"] diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index a728a8f42e4..6f3183e2b40 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -10,7 +10,14 @@ import voluptuous as vol from homeassistant import exceptions from homeassistant.const import CONF_ATTRIBUTE, CONF_FOR, CONF_PLATFORM, MATCH_ALL -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, State, callback +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + HassJob, + HomeAssistant, + State, + callback, +) from homeassistant.helpers import ( config_validation as cv, entity_registry as er, @@ -23,7 +30,7 @@ from homeassistant.helpers.event import ( process_state_match, ) from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -125,7 +132,7 @@ async def async_attach_trigger( _variables = trigger_info["variables"] or {} @callback - def state_automation_listener(event: EventType[EventStateChangedData]) -> None: + def state_automation_listener(event: Event[EventStateChangedData]) -> None: """Listen for state changes and calls action.""" entity = event.data["entity_id"] from_s = event.data["old_state"] diff --git a/homeassistant/components/homeassistant/triggers/time.py b/homeassistant/components/homeassistant/triggers/time.py index 4f2ac9e8480..b1d19d54795 100644 --- a/homeassistant/components/homeassistant/triggers/time.py +++ b/homeassistant/components/homeassistant/triggers/time.py @@ -13,7 +13,14 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, State, callback +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + HassJob, + HomeAssistant, + State, + callback, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import ( EventStateChangedData, @@ -22,7 +29,7 @@ from homeassistant.helpers.event import ( async_track_time_change, ) from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType import homeassistant.util.dt as dt_util _TIME_TRIGGER_SCHEMA = vol.Any( @@ -72,7 +79,7 @@ async def async_attach_trigger( ) @callback - def update_entity_trigger_event(event: EventType[EventStateChangedData]) -> None: + def update_entity_trigger_event(event: Event[EventStateChangedData]) -> None: """update_entity_trigger from the event.""" return update_entity_trigger(event.data["entity_id"], event.data["new_state"]) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 5303b2a6736..7c8eb973911 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -44,6 +44,7 @@ from homeassistant.const import ( from homeassistant.core import ( CALLBACK_TYPE, Context, + Event, HomeAssistant, State, callback as ha_callback, @@ -54,7 +55,6 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from homeassistant.util.decorator import Registry from .const import ( @@ -478,7 +478,7 @@ class HomeAccessory(Accessory): # type: ignore[misc] @ha_callback def async_update_event_state_callback( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle state change event listener callback.""" new_state = event.data["new_state"] @@ -530,7 +530,7 @@ class HomeAccessory(Accessory): # type: ignore[misc] @ha_callback def async_update_linked_battery_callback( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle linked battery sensor state change listener callback.""" if (new_state := event.data["new_state"]) is None: @@ -543,7 +543,7 @@ class HomeAccessory(Accessory): # type: ignore[misc] @ha_callback def async_update_linked_battery_charging_callback( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle linked battery charging sensor state change listener callback.""" if (new_state := event.data["new_state"]) is None: diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index 28b34503355..d47d9775ed2 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -17,13 +17,12 @@ from pyhap.util import callback as pyhap_callback from homeassistant.components import camera from homeassistant.components.ffmpeg import get_ffmpeg_manager from homeassistant.const import STATE_ON -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, async_track_time_interval, ) -from homeassistant.helpers.typing import EventType from .accessories import TYPES, HomeAccessory, HomeDriver from .const import ( @@ -284,7 +283,7 @@ class Camera(HomeAccessory, PyhapCamera): # type: ignore[misc] @callback def _async_update_motion_state_event( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle state change event listener callback.""" if not state_changed_event_is_same_state(event): @@ -311,7 +310,7 @@ class Camera(HomeAccessory, PyhapCamera): # type: ignore[misc] @callback def _async_update_doorbell_state_event( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle state change event listener callback.""" if not state_changed_event_is_same_state(event): diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 5e60875e485..2452fd65026 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -34,12 +34,11 @@ from homeassistant.const import ( STATE_OPEN, STATE_OPENING, ) -from homeassistant.core import State, callback +from homeassistant.core import Event, State, callback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from .accessories import TYPES, HomeAccessory from .const import ( @@ -147,7 +146,7 @@ class GarageDoorOpener(HomeAccessory): @callback def _async_update_obstruction_event( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle state change event listener callback.""" self._async_update_obstruction_state(event.data["new_state"]) diff --git a/homeassistant/components/homekit/type_humidifiers.py b/homeassistant/components/homekit/type_humidifiers.py index a9782aedaf0..2b4de072b6a 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -25,12 +25,11 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.core import State, callback +from homeassistant.core import Event, State, callback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from .accessories import TYPES, HomeAccessory from .const import ( @@ -195,7 +194,7 @@ class HumidifierDehumidifier(HomeAccessory): @callback def async_update_current_humidity_event( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Handle state change event listener callback.""" self._async_update_current_humidity(event.data["new_state"]) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 35e60bff2df..2d0eccf9b7c 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -38,11 +38,10 @@ from homeassistant.const import ( CONF_TYPE, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, State, callback, split_entity_id +from homeassistant.core import Event, HomeAssistant, State, callback, split_entity_id import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import EventStateChangedData from homeassistant.helpers.storage import STORAGE_DIR -from homeassistant.helpers.typing import EventType from homeassistant.util.unit_conversion import TemperatureConverter from .const import ( @@ -624,7 +623,7 @@ def state_needs_accessory_mode(state: State) -> bool: ) -def state_changed_event_is_same_state(event: EventType[EventStateChangedData]) -> bool: +def state_changed_event_is_same_state(event: Event[EventStateChangedData]) -> bool: """Check if a state changed event is the same state.""" event_data = event.data old_state = event_data["old_state"] diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 63b01d13805..956b868272f 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -27,7 +27,7 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfTime, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -39,7 +39,7 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import ( CONF_ROUND_DIGITS, @@ -293,7 +293,7 @@ class IntegrationSensor(RestoreSensor): self._unit_of_measurement = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) @callback - def calc_integration(event: EventType[EventStateChangedData]) -> None: + def calc_integration(event: Event[EventStateChangedData]) -> None: """Handle the sensor state changes.""" old_state = event.data["old_state"] new_state = event.data["new_state"] diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index aee6e148e62..6e4a3b80f6e 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -18,12 +18,12 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, EventType, StateType +from homeassistant.helpers.typing import ConfigType, StateType from .const import CONF_RESPOND_TO_READ, KNX_ADDRESS from .schema import ExposeSchema @@ -149,9 +149,7 @@ class KNXExposeSensor: return str(value)[:14] return value - async def _async_entity_changed( - self, event: EventType[EventStateChangedData] - ) -> None: + async def _async_entity_changed(self, event: Event[EventStateChangedData]) -> None: """Handle entity change.""" new_state = event.data["new_state"] if (new_value := self._get_expose_value(new_state)) is None: From 84c44c1835de34786dd159af82d783155288d35f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:37:18 +0100 Subject: [PATCH 0565/1691] Replace EventType with Event [l-s] (#112741) --- homeassistant/components/logbook/helpers.py | 3 +-- .../components/manual_mqtt/alarm_control_panel.py | 6 +++--- homeassistant/components/min_max/sensor.py | 13 ++++--------- homeassistant/components/mold_indicator/sensor.py | 6 +++--- homeassistant/components/mqtt/mixins.py | 6 +++--- homeassistant/components/person/__init__.py | 6 ++---- homeassistant/components/plant/__init__.py | 6 +++--- homeassistant/components/prometheus/__init__.py | 10 ++++------ homeassistant/components/purpleair/config_flow.py | 5 ++--- homeassistant/components/statistics/sensor.py | 10 +++------- homeassistant/components/switch/light.py | 6 +++--- homeassistant/components/switch_as_x/cover.py | 5 ++--- homeassistant/components/switch_as_x/entity.py | 9 ++++----- homeassistant/components/switch_as_x/lock.py | 5 ++--- homeassistant/components/switch_as_x/valve.py | 5 ++--- 15 files changed, 41 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/logbook/helpers.py b/homeassistant/components/logbook/helpers.py index 698d7d56aa1..4f534c74981 100644 --- a/homeassistant/components/logbook/helpers.py +++ b/homeassistant/components/logbook/helpers.py @@ -28,7 +28,6 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from .const import ALWAYS_CONTINUOUS_DOMAINS, AUTOMATION_EVENTS, BUILT_IN_EVENTS, DOMAIN from .models import LogbookConfig @@ -189,7 +188,7 @@ def async_subscribe_events( return @callback - def _forward_state_events_filtered(event: EventType[EventStateChangedData]) -> None: + def _forward_state_events_filtered(event: Event[EventStateChangedData]) -> None: if (old_state := event.data["old_state"]) is None or ( new_state := event.data["new_state"] ) is None: diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index 9da0ff97838..0cd92b552c6 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -28,7 +28,7 @@ from homeassistant.const import ( STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -37,7 +37,7 @@ from homeassistant.helpers.event import ( async_track_point_in_time, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -483,7 +483,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): ) async def _async_state_changed_listener( - self, event: EventType[EventStateChangedData] + self, event: Event[EventStateChangedData] ) -> None: """Publish state change to MQTT.""" if (new_state := event.data["new_state"]) is None: diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index 7ced9df661c..4ea63f5a472 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -23,7 +23,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( @@ -31,12 +31,7 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.reload import async_setup_reload_service -from homeassistant.helpers.typing import ( - ConfigType, - DiscoveryInfoType, - EventType, - StateType, -) +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from . import PLATFORMS from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN @@ -258,7 +253,7 @@ class MinMaxSensor(SensorEntity): # Replay current state of source entities for entity_id in self._entity_ids: state = self.hass.states.get(entity_id) - state_event: EventType[EventStateChangedData] = EventType( + state_event: Event[EventStateChangedData] = Event( "", {"entity_id": entity_id, "new_state": state, "old_state": None} ) self._async_min_max_sensor_state_listener(state_event, update_state=False) @@ -293,7 +288,7 @@ class MinMaxSensor(SensorEntity): @callback def _async_min_max_sensor_state_listener( - self, event: EventType[EventStateChangedData], update_state: bool = True + self, event: Event[EventStateChangedData], update_state: bool = True ) -> None: """Handle the sensor state changes.""" new_state = event.data["new_state"] diff --git a/homeassistant/components/mold_indicator/sensor.py b/homeassistant/components/mold_indicator/sensor.py index 86f2aced069..6839e57c838 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -17,14 +17,14 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.unit_conversion import TemperatureConverter from homeassistant.util.unit_system import METRIC_SYSTEM @@ -122,7 +122,7 @@ class MoldIndicator(SensorEntity): @callback def mold_indicator_sensors_state_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle for state changes for dependent sensors.""" new_state = event.data["new_state"] diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index a508b05efb4..b6d83e2d420 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -30,7 +30,7 @@ from homeassistant.const import ( CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, ) -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -873,7 +873,7 @@ class MqttDiscoveryDeviceUpdate(ABC): return async def _async_device_removed( - self, event: EventType[EventDeviceRegistryUpdatedData] + self, event: Event[EventDeviceRegistryUpdatedData] ) -> None: """Handle the manual removal of a device.""" if self._skip_device_removal or not async_removed_from_device( @@ -1344,7 +1344,7 @@ def update_device( @callback def async_removed_from_device( hass: HomeAssistant, - event: EventType[EventDeviceRegistryUpdatedData], + event: Event[EventDeviceRegistryUpdatedData], mqtt_device_id: str, config_entry_id: str, ) -> bool: diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index b2511a3cf30..77e88c8509a 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -54,7 +54,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.storage import Store -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) @@ -518,9 +518,7 @@ class Person(collection.CollectionEntity, RestoreEntity): self._update_state() @callback - def _async_handle_tracker_update( - self, event: EventType[EventStateChangedData] - ) -> None: + def _async_handle_tracker_update(self, event: Event[EventStateChangedData]) -> None: """Handle the device tracker state changes.""" self._update_state() diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index 5d79f8d303a..efa0b45d25c 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -20,7 +20,7 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -29,7 +29,7 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util from .const import ( @@ -180,7 +180,7 @@ class Plant(Entity): self._brightness_history = DailyHistory(self._conf_check_days) @callback - def _state_changed_event(self, event: EventType[EventStateChangedData]) -> None: + def _state_changed_event(self, event: Event[EventStateChangedData]) -> None: """Sensor state change event.""" self.state_changed(event.data["entity_id"], event.data["new_state"]) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 2b8ed6f22b0..22b6bdaa78f 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -49,7 +49,7 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant, State +from homeassistant.core import Event, HomeAssistant, State from homeassistant.helpers import entityfilter, state as state_helper import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_registry import ( @@ -58,7 +58,7 @@ from homeassistant.helpers.entity_registry import ( ) from homeassistant.helpers.entity_values import EntityValues from homeassistant.helpers.event import EventStateChangedData -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.util.dt import as_timestamp from homeassistant.util.unit_conversion import TemperatureConverter @@ -180,9 +180,7 @@ class PrometheusMetrics: self._metrics: dict[str, MetricWrapperBase] = {} self._climate_units = climate_units - def handle_state_changed_event( - self, event: EventType[EventStateChangedData] - ) -> None: + def handle_state_changed_event(self, event: Event[EventStateChangedData]) -> None: """Handle new messages from the bus.""" if (state := event.data.get("new_state")) is None: return @@ -232,7 +230,7 @@ class PrometheusMetrics: last_updated_time_seconds.labels(**labels).set(state.last_updated.timestamp()) def handle_entity_registry_updated( - self, event: EventType[EventEntityRegistryUpdatedData] + self, event: Event[EventEntityRegistryUpdatedData] ) -> None: """Listen for deleted, disabled or renamed entities and remove them from the Prometheus Registry.""" if event.data["action"] in (None, "create"): diff --git a/homeassistant/components/purpleair/config_flow.py b/homeassistant/components/purpleair/config_flow.py index f0c09af5135..f9a6415bb38 100644 --- a/homeassistant/components/purpleair/config_flow.py +++ b/homeassistant/components/purpleair/config_flow.py @@ -25,7 +25,7 @@ from homeassistant.const import ( CONF_LONGITUDE, CONF_SHOW_ON_MAP, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import ( aiohttp_client, config_validation as cv, @@ -42,7 +42,6 @@ from homeassistant.helpers.selector import ( SelectSelectorConfig, SelectSelectorMode, ) -from homeassistant.helpers.typing import EventType from .const import CONF_SENSOR_INDICES, DOMAIN, LOGGER @@ -436,7 +435,7 @@ class PurpleAirOptionsFlowHandler(OptionsFlow): @callback def async_device_entity_state_changed( - _: EventType[EventStateChangedData], + _: Event[EventStateChangedData], ) -> None: """Listen and respond when all device entities are removed.""" if all( diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index e1e530e60ed..a39137e9d81 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -34,6 +34,7 @@ from homeassistant.const import ( ) from homeassistant.core import ( CALLBACK_TYPE, + Event, HomeAssistant, State, callback, @@ -48,12 +49,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.start import async_at_start -from homeassistant.helpers.typing import ( - ConfigType, - DiscoveryInfoType, - EventType, - StateType, -) +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.util import dt as dt_util from homeassistant.util.enum import try_parse_enum @@ -335,7 +331,7 @@ class StatisticsSensor(SensorEntity): @callback def async_stats_sensor_state_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle the sensor state changes.""" if (new_state := event.data["new_state"]) is None: diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 25dc572b136..25214822bdb 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -16,7 +16,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -24,7 +24,7 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import DOMAIN as SWITCH_DOMAIN @@ -98,7 +98,7 @@ class LightSwitch(LightEntity): @callback def async_state_changed_listener( - event: EventType[EventStateChangedData] | None = None, + event: Event[EventStateChangedData] | None = None, ) -> None: """Handle child updates.""" if ( diff --git a/homeassistant/components/switch_as_x/cover.py b/homeassistant/components/switch_as_x/cover.py index 3db5d6aba81..9d03965a242 100644 --- a/homeassistant/components/switch_as_x/cover.py +++ b/homeassistant/components/switch_as_x/cover.py @@ -18,11 +18,10 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import EventStateChangedData -from homeassistant.helpers.typing import EventType from .const import CONF_INVERT from .entity import BaseInvertableEntity @@ -80,7 +79,7 @@ class CoverSwitch(BaseInvertableEntity, CoverEntity): @callback def async_state_changed_listener( - self, event: EventType[EventStateChangedData] | None = None + self, event: Event[EventStateChangedData] | None = None ) -> None: """Handle child updates.""" super().async_state_changed_listener(event) diff --git a/homeassistant/components/switch_as_x/entity.py b/homeassistant/components/switch_as_x/entity.py index 04b1deac7fc..e8e57570617 100644 --- a/homeassistant/components/switch_as_x/entity.py +++ b/homeassistant/components/switch_as_x/entity.py @@ -13,7 +13,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity, ToggleEntity @@ -21,7 +21,6 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from .const import DOMAIN as SWITCH_AS_X_DOMAIN @@ -70,7 +69,7 @@ class BaseEntity(Entity): @callback def async_state_changed_listener( - self, event: EventType[EventStateChangedData] | None = None + self, event: Event[EventStateChangedData] | None = None ) -> None: """Handle child updates.""" if ( @@ -86,7 +85,7 @@ class BaseEntity(Entity): @callback def _async_state_changed_listener( - event: EventType[EventStateChangedData] | None = None, + event: Event[EventStateChangedData] | None = None, ) -> None: """Handle child updates.""" self.async_state_changed_listener(event) @@ -173,7 +172,7 @@ class BaseToggleEntity(BaseEntity, ToggleEntity): @callback def async_state_changed_listener( - self, event: EventType[EventStateChangedData] | None = None + self, event: Event[EventStateChangedData] | None = None ) -> None: """Handle child updates.""" super().async_state_changed_listener(event) diff --git a/homeassistant/components/switch_as_x/lock.py b/homeassistant/components/switch_as_x/lock.py index de836904b66..5243ae184ee 100644 --- a/homeassistant/components/switch_as_x/lock.py +++ b/homeassistant/components/switch_as_x/lock.py @@ -14,11 +14,10 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import EventStateChangedData -from homeassistant.helpers.typing import EventType from .const import CONF_INVERT from .entity import BaseInvertableEntity @@ -74,7 +73,7 @@ class LockSwitch(BaseInvertableEntity, LockEntity): @callback def async_state_changed_listener( - self, event: EventType[EventStateChangedData] | None = None + self, event: Event[EventStateChangedData] | None = None ) -> None: """Handle child updates.""" super().async_state_changed_listener(event) diff --git a/homeassistant/components/switch_as_x/valve.py b/homeassistant/components/switch_as_x/valve.py index cae2b4b2430..98f0e52c8a2 100644 --- a/homeassistant/components/switch_as_x/valve.py +++ b/homeassistant/components/switch_as_x/valve.py @@ -18,11 +18,10 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import EventStateChangedData -from homeassistant.helpers.typing import EventType from .const import CONF_INVERT from .entity import BaseInvertableEntity @@ -81,7 +80,7 @@ class ValveSwitch(BaseInvertableEntity, ValveEntity): @callback def async_state_changed_listener( - self, event: EventType[EventStateChangedData] | None = None + self, event: Event[EventStateChangedData] | None = None ) -> None: """Handle child updates.""" super().async_state_changed_listener(event) From 3db28d46b210d7e1e6659fbe6480d5872b8914e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:37:26 +0100 Subject: [PATCH 0566/1691] Replace EventType with Event [t-z] (#112742) --- homeassistant/components/template/template_entity.py | 7 ++++--- homeassistant/components/template/trigger.py | 6 +++--- homeassistant/components/threshold/binary_sensor.py | 6 +++--- homeassistant/components/trend/binary_sensor.py | 6 +++--- homeassistant/components/universal/media_player.py | 8 ++++---- homeassistant/components/utility_meter/sensor.py | 8 ++++---- homeassistant/components/websocket_api/commands.py | 3 +-- homeassistant/components/zha/entity.py | 7 ++----- homeassistant/components/zone/__init__.py | 8 ++++---- homeassistant/components/zone/trigger.py | 6 +++--- 10 files changed, 31 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index c2ffa85864d..a851e547844 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -21,6 +21,7 @@ from homeassistant.const import ( from homeassistant.core import ( CALLBACK_TYPE, Context, + Event, HomeAssistant, State, callback, @@ -47,7 +48,7 @@ from homeassistant.helpers.trigger_template_entity import ( TEMPLATE_ENTITY_BASE_SCHEMA, make_template_entity_base_schema, ) -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from .const import ( CONF_ATTRIBUTE_TEMPLATES, @@ -190,7 +191,7 @@ class _TemplateAttribute: @callback def handle_result( self, - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, template: Template, last_result: str | None | TemplateError, result: str | TemplateError, @@ -399,7 +400,7 @@ class TemplateEntity(Entity): @callback def _handle_results( self, - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: """Call back the results to the attributes.""" diff --git a/homeassistant/components/template/trigger.py b/homeassistant/components/template/trigger.py index 3f76aac4eb6..8e95362ff88 100644 --- a/homeassistant/components/template/trigger.py +++ b/homeassistant/components/template/trigger.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant import exceptions from homeassistant.const import CONF_FOR, CONF_PLATFORM, CONF_VALUE_TEMPLATE -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant, callback from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.event import ( EventStateChangedData, @@ -19,7 +19,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.template import Template, result_as_boolean from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -65,7 +65,7 @@ async def async_attach_trigger( @callback def template_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: """Listen for state changes and calls action.""" diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py index ff0813f650e..364511ca291 100644 --- a/homeassistant/components/threshold/binary_sensor.py +++ b/homeassistant/components/threshold/binary_sensor.py @@ -22,7 +22,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -34,7 +34,7 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import CONF_HYSTERESIS, CONF_LOWER, CONF_UPPER @@ -215,7 +215,7 @@ class ThresholdSensor(BinarySensorEntity): @callback def async_threshold_sensor_state_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle sensor state changes.""" _update_sensor_state() diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index a9d909fd8b4..526228c2be1 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -31,7 +31,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -41,7 +41,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util.dt import utcnow from . import PLATFORMS @@ -215,7 +215,7 @@ class SensorTrend(BinarySensorEntity, RestoreEntity): @callback def trend_sensor_state_listener( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Handle state changes on the observed device.""" if (new_state := event.data["new_state"]) is None: diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 033fd35ae51..54b5d337605 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -79,7 +79,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_component import EntityComponent @@ -93,7 +93,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.service import async_call_from_config -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType ATTR_ACTIVE_CHILD = "active_child" @@ -186,7 +186,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): @callback def _async_on_dependency_update( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], ) -> None: """Update ha state when dependencies update.""" self.async_set_context(event.context) @@ -194,7 +194,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): @callback def _async_on_template_update( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: """Update state when template state changes.""" diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 097707b04d0..4e9be403cf7 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -28,7 +28,7 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfEnergy, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers import ( device_registry as dr, entity_platform, @@ -44,7 +44,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.start import async_at_started from homeassistant.helpers.template import is_number -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, EventType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import slugify import homeassistant.util.dt as dt_util @@ -466,7 +466,7 @@ class UtilityMeterSensor(RestoreSensor): return None @callback - def async_reading(self, event: EventType[EventStateChangedData]) -> None: + def async_reading(self, event: Event[EventStateChangedData]) -> None: """Handle the sensor state changes.""" if ( source_state := self.hass.states.get(self._sensor_source_id) @@ -517,7 +517,7 @@ class UtilityMeterSensor(RestoreSensor): self.async_write_ha_state() @callback - def async_tariff_change(self, event: EventType[EventStateChangedData]) -> None: + def async_tariff_change(self, event: Event[EventStateChangedData]) -> None: """Handle tariff changes.""" if (new_state := event.data["new_state"]) is None: return diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 376ef1cfacc..0f9dba1cfc3 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -48,7 +48,6 @@ from homeassistant.helpers.json import ( json_bytes, ) from homeassistant.helpers.service import async_get_all_descriptions -from homeassistant.helpers.typing import EventType from homeassistant.loader import ( Integration, IntegrationNotFound, @@ -622,7 +621,7 @@ async def handle_render_template( @callback def _template_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_template_result = updates.pop() diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index f03da7a5fd6..98e56c69075 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any, Self from zigpy.quirks.v2 import EntityMetadata, EntityType from homeassistant.const import ATTR_NAME, EntityCategory -from homeassistant.core import CALLBACK_TYPE, callback +from homeassistant.core import CALLBACK_TYPE, Event, callback from homeassistant.helpers import entity from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE, DeviceInfo @@ -24,7 +24,6 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.typing import EventType from .core.const import ( ATTR_MANUFACTURER, @@ -346,9 +345,7 @@ class ZhaGroupEntity(BaseZhaEntity): self.async_on_remove(send_removed_signal) @callback - def async_state_changed_listener( - self, event: EventType[EventStateChangedData] - ) -> None: + def async_state_changed_listener(self, event: Event[EventStateChangedData]) -> None: """Handle child updates.""" # Delay to ensure that we get updates from all members before updating the group assert self._change_listener_debouncer diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index a73126e2971..4c4b9c7c229 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -38,7 +38,7 @@ from homeassistant.helpers import ( service, storage, ) -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from homeassistant.util.location import distance @@ -166,7 +166,7 @@ def async_setup_track_zone_entity_ids(hass: HomeAssistant) -> None: @callback def _async_add_zone_entity_id( - event_: EventType[event.EventStateChangedData], + event_: Event[event.EventStateChangedData], ) -> None: """Add zone entity ID.""" zone_entity_ids.append(event_.data["entity_id"]) @@ -174,7 +174,7 @@ def async_setup_track_zone_entity_ids(hass: HomeAssistant) -> None: @callback def _async_remove_zone_entity_id( - event_: EventType[event.EventStateChangedData], + event_: Event[event.EventStateChangedData], ) -> None: """Remove zone entity ID.""" zone_entity_ids.remove(event_.data["entity_id"]) @@ -389,7 +389,7 @@ class Zone(collection.CollectionEntity): @callback def _person_state_change_listener( - self, evt: EventType[event.EventStateChangedData] + self, evt: Event[event.EventStateChangedData] ) -> None: person_entity_id = evt.data["entity_id"] cur_count = len(self._persons_in_zone) diff --git a/homeassistant/components/zone/trigger.py b/homeassistant/components/zone/trigger.py index f5c0f574320..8aea25f1f6c 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -13,7 +13,7 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_ZONE, ) -from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant, callback from homeassistant.helpers import ( condition, config_validation as cv, @@ -25,7 +25,7 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType EVENT_ENTER = "enter" EVENT_LEAVE = "leave" @@ -75,7 +75,7 @@ async def async_attach_trigger( job = HassJob(action) @callback - def zone_automation_listener(zone_event: EventType[EventStateChangedData]) -> None: + def zone_automation_listener(zone_event: Event[EventStateChangedData]) -> None: """Listen for state changes and calls action.""" entity = zone_event.data["entity_id"] from_s = zone_event.data["old_state"] From b026b5d5899bc54bbb6a814406f59649c690ae7c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:41:50 +0100 Subject: [PATCH 0567/1691] Replace EventType with Event [helpers] (#112743) --- homeassistant/helpers/entity.py | 3 +- homeassistant/helpers/event.py | 103 ++++++++++++------------- tests/helpers/test_event.py | 129 ++++++++++++++++---------------- 3 files changed, 114 insertions(+), 121 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 6ec7cbba9b7..6318cf8e13b 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -47,6 +47,7 @@ from homeassistant.const import ( from homeassistant.core import ( CALLBACK_TYPE, Context, + Event, HassJobType, HomeAssistant, callback, @@ -1519,7 +1520,7 @@ class Entity( @callback def _async_device_registry_updated( - self, event: EventType[EventDeviceRegistryUpdatedData] + self, event: Event[EventDeviceRegistryUpdatedData] ) -> None: """Handle device registry update.""" data = event.data diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 471a996ccdc..4fd2949a23b 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -32,6 +32,7 @@ from homeassistant.const import ( ) from homeassistant.core import ( CALLBACK_TYPE, + Event, HassJob, HassJobType, HomeAssistant, @@ -55,7 +56,7 @@ from .entity_registry import ( from .ratelimit import KeyedRateLimit from .sun import get_astral_event_next from .template import RenderInfo, Template, result_as_boolean -from .typing import EventType, TemplateVarsType +from .typing import TemplateVarsType TRACK_STATE_CHANGE_CALLBACKS = "track_state_change_callbacks" TRACK_STATE_CHANGE_LISTENER = "track_state_change_listener" @@ -99,16 +100,16 @@ class _KeyedEventTracker(Generic[_TypedDictT]): dispatcher_callable: Callable[ [ HomeAssistant, - dict[str, list[HassJob[[EventType[_TypedDictT]], Any]]], - EventType[_TypedDictT], + dict[str, list[HassJob[[Event[_TypedDictT]], Any]]], + Event[_TypedDictT], ], None, ] filter_callable: Callable[ [ HomeAssistant, - dict[str, list[HassJob[[EventType[_TypedDictT]], Any]]], - EventType[_TypedDictT], + dict[str, list[HassJob[[Event[_TypedDictT]], Any]]], + Event[_TypedDictT], ], bool, ] @@ -236,7 +237,7 @@ def async_track_state_change( job = HassJob(action, f"track state change {entity_ids} {from_state} {to_state}") @callback - def state_change_filter(event: EventType[EventStateChangedData]) -> bool: + def state_change_filter(event: Event[EventStateChangedData]) -> bool: """Handle specific state changes.""" if from_state is not None: old_state_str: str | None = None @@ -257,7 +258,7 @@ def async_track_state_change( return True @callback - def state_change_dispatcher(event: EventType[EventStateChangedData]) -> None: + def state_change_dispatcher(event: Event[EventStateChangedData]) -> None: """Handle specific state changes.""" hass.async_run_hass_job( job, @@ -267,7 +268,7 @@ def async_track_state_change( ) @callback - def state_change_listener(event: EventType[EventStateChangedData]) -> None: + def state_change_listener(event: Event[EventStateChangedData]) -> None: """Handle specific state changes.""" if not state_change_filter(event): return @@ -299,7 +300,7 @@ track_state_change = threaded_listener_factory(async_track_state_change) def async_track_state_change_event( hass: HomeAssistant, entity_ids: str | Iterable[str], - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track specific state change events indexed by entity_id. @@ -321,8 +322,8 @@ def async_track_state_change_event( @callback def _async_dispatch_entity_id_event( hass: HomeAssistant, - callbacks: dict[str, list[HassJob[[EventType[EventStateChangedData]], Any]]], - event: EventType[EventStateChangedData], + callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], + event: Event[EventStateChangedData], ) -> None: """Dispatch to listeners.""" if not (callbacks_list := callbacks.get(event.data["entity_id"])): @@ -341,8 +342,8 @@ def _async_dispatch_entity_id_event( @callback def _async_state_change_filter( hass: HomeAssistant, - callbacks: dict[str, list[HassJob[[EventType[EventStateChangedData]], Any]]], - event: EventType[EventStateChangedData], + callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], + event: Event[EventStateChangedData], ) -> bool: """Filter state changes by entity_id.""" return event.data["entity_id"] in callbacks @@ -362,7 +363,7 @@ _KEYED_TRACK_STATE_CHANGE = _KeyedEventTracker( def _async_track_state_change_event( hass: HomeAssistant, entity_ids: str | Iterable[str], - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], job_type: HassJobType | None, ) -> CALLBACK_TYPE: """async_track_state_change_event without lowercasing.""" @@ -381,8 +382,8 @@ def _remove_listener( hass: HomeAssistant, listeners_key: str, keys: Iterable[str], - job: HassJob[[EventType[_TypedDictT]], Any], - callbacks: dict[str, list[HassJob[[EventType[_TypedDictT]], Any]]], + job: HassJob[[Event[_TypedDictT]], Any], + callbacks: dict[str, list[HassJob[[Event[_TypedDictT]], Any]]], ) -> None: """Remove listener.""" for key in keys: @@ -401,7 +402,7 @@ def _async_track_event( tracker: _KeyedEventTracker[_TypedDictT], hass: HomeAssistant, keys: str | Iterable[str], - action: Callable[[EventType[_TypedDictT]], None], + action: Callable[[Event[_TypedDictT]], None], job_type: HassJobType | None, ) -> CALLBACK_TYPE: """Track an event by a specific key. @@ -421,7 +422,7 @@ def _async_track_event( hass_data = hass.data callbacks_key = tracker.callbacks_key - callbacks: dict[str, list[HassJob[[EventType[_TypedDictT]], Any]]] | None + callbacks: dict[str, list[HassJob[[Event[_TypedDictT]], Any]]] | None if not (callbacks := hass_data.get(callbacks_key)): callbacks = hass_data[callbacks_key] = {} @@ -449,10 +450,8 @@ def _async_track_event( @callback def _async_dispatch_old_entity_id_or_entity_id_event( hass: HomeAssistant, - callbacks: dict[ - str, list[HassJob[[EventType[EventEntityRegistryUpdatedData]], Any]] - ], - event: EventType[EventEntityRegistryUpdatedData], + callbacks: dict[str, list[HassJob[[Event[EventEntityRegistryUpdatedData]], Any]]], + event: Event[EventEntityRegistryUpdatedData], ) -> None: """Dispatch to listeners.""" if not ( @@ -475,10 +474,8 @@ def _async_dispatch_old_entity_id_or_entity_id_event( @callback def _async_entity_registry_updated_filter( hass: HomeAssistant, - callbacks: dict[ - str, list[HassJob[[EventType[EventEntityRegistryUpdatedData]], Any]] - ], - event: EventType[EventEntityRegistryUpdatedData], + callbacks: dict[str, list[HassJob[[Event[EventEntityRegistryUpdatedData]], Any]]], + event: Event[EventEntityRegistryUpdatedData], ) -> bool: """Filter entity registry updates by entity_id.""" return event.data.get("old_entity_id", event.data["entity_id"]) in callbacks @@ -499,7 +496,7 @@ _KEYED_TRACK_ENTITY_REGISTRY_UPDATED = _KeyedEventTracker( def async_track_entity_registry_updated_event( hass: HomeAssistant, entity_ids: str | Iterable[str], - action: Callable[[EventType[EventEntityRegistryUpdatedData]], Any], + action: Callable[[Event[EventEntityRegistryUpdatedData]], Any], job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track specific entity registry updated events indexed by entity_id. @@ -516,10 +513,8 @@ def async_track_entity_registry_updated_event( @callback def _async_device_registry_updated_filter( hass: HomeAssistant, - callbacks: dict[ - str, list[HassJob[[EventType[EventDeviceRegistryUpdatedData]], Any]] - ], - event: EventType[EventDeviceRegistryUpdatedData], + callbacks: dict[str, list[HassJob[[Event[EventDeviceRegistryUpdatedData]], Any]]], + event: Event[EventDeviceRegistryUpdatedData], ) -> bool: """Filter device registry updates by device_id.""" return event.data["device_id"] in callbacks @@ -528,10 +523,8 @@ def _async_device_registry_updated_filter( @callback def _async_dispatch_device_id_event( hass: HomeAssistant, - callbacks: dict[ - str, list[HassJob[[EventType[EventDeviceRegistryUpdatedData]], Any]] - ], - event: EventType[EventDeviceRegistryUpdatedData], + callbacks: dict[str, list[HassJob[[Event[EventDeviceRegistryUpdatedData]], Any]]], + event: Event[EventDeviceRegistryUpdatedData], ) -> None: """Dispatch to listeners.""" if not (callbacks_list := callbacks.get(event.data["device_id"])): @@ -561,7 +554,7 @@ _KEYED_TRACK_DEVICE_REGISTRY_UPDATED = _KeyedEventTracker( def async_track_device_registry_updated_event( hass: HomeAssistant, device_ids: str | Iterable[str], - action: Callable[[EventType[EventDeviceRegistryUpdatedData]], Any], + action: Callable[[Event[EventDeviceRegistryUpdatedData]], Any], job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track specific device registry updated events indexed by device_id. @@ -576,8 +569,8 @@ def async_track_device_registry_updated_event( @callback def _async_dispatch_domain_event( hass: HomeAssistant, - callbacks: dict[str, list[HassJob[[EventType[EventStateChangedData]], Any]]], - event: EventType[EventStateChangedData], + callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], + event: Event[EventStateChangedData], ) -> None: """Dispatch domain event listeners.""" domain = split_entity_id(event.data["entity_id"])[0] @@ -593,8 +586,8 @@ def _async_dispatch_domain_event( @callback def _async_domain_added_filter( hass: HomeAssistant, - callbacks: dict[str, list[HassJob[[EventType[EventStateChangedData]], Any]]], - event: EventType[EventStateChangedData], + callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], + event: Event[EventStateChangedData], ) -> bool: """Filter state changes by entity_id.""" return event.data["old_state"] is None and ( @@ -607,7 +600,7 @@ def _async_domain_added_filter( def async_track_state_added_domain( hass: HomeAssistant, domains: str | Iterable[str], - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track state change events when an entity is added to domains.""" @@ -630,7 +623,7 @@ _KEYED_TRACK_STATE_ADDED_DOMAIN = _KeyedEventTracker( def _async_track_state_added_domain( hass: HomeAssistant, domains: str | Iterable[str], - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], job_type: HassJobType | None, ) -> CALLBACK_TYPE: """Track state change events when an entity is added to domains.""" @@ -642,8 +635,8 @@ def _async_track_state_added_domain( @callback def _async_domain_removed_filter( hass: HomeAssistant, - callbacks: dict[str, list[HassJob[[EventType[EventStateChangedData]], Any]]], - event: EventType[EventStateChangedData], + callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], + event: Event[EventStateChangedData], ) -> bool: """Filter state changes by entity_id.""" return event.data["new_state"] is None and ( @@ -666,7 +659,7 @@ _KEYED_TRACK_STATE_REMOVED_DOMAIN = _KeyedEventTracker( def async_track_state_removed_domain( hass: HomeAssistant, domains: str | Iterable[str], - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], job_type: HassJobType | None = None, ) -> CALLBACK_TYPE: """Track state change events when an entity is removed from domains.""" @@ -690,7 +683,7 @@ class _TrackStateChangeFiltered: self, hass: HomeAssistant, track_states: TrackStates, - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], ) -> None: """Handle removal / refresh of tracker init.""" self.hass = hass @@ -794,7 +787,7 @@ class _TrackStateChangeFiltered: ) @callback - def _state_added(self, event: EventType[EventStateChangedData]) -> None: + def _state_added(self, event: Event[EventStateChangedData]) -> None: self._cancel_listener(_ENTITIES_LISTENER) self._setup_entities_listener( self._last_track_states.domains, self._last_track_states.entities @@ -823,7 +816,7 @@ class _TrackStateChangeFiltered: def async_track_state_change_filtered( hass: HomeAssistant, track_states: TrackStates, - action: Callable[[EventType[EventStateChangedData]], Any], + action: Callable[[Event[EventStateChangedData]], Any], ) -> _TrackStateChangeFiltered: """Track state changes with a TrackStates filter that can be updated. @@ -897,7 +890,7 @@ def async_track_template( @callback def _template_changed_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: """Check if condition is correct and run action.""" @@ -1088,7 +1081,7 @@ class TrackTemplateResultInfo: self, track_template_: TrackTemplate, now: datetime, - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, ) -> bool | TrackTemplateResult: """Re-render the template if conditions match. @@ -1177,7 +1170,7 @@ class TrackTemplateResultInfo: @callback def _refresh( self, - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, track_templates: Iterable[TrackTemplate] | None = None, replayed: bool | None = False, ) -> None: @@ -1273,7 +1266,7 @@ class TrackTemplateResultInfo: TrackTemplateResultListener = Callable[ [ - EventType[EventStateChangedData] | None, + Event[EventStateChangedData] | None, list[TrackTemplateResult], ], Coroutine[Any, Any, None] | None, @@ -1381,7 +1374,7 @@ def async_track_same_state( hass.async_run_hass_job(job) @callback - def state_for_cancel_listener(event: EventType[EventStateChangedData]) -> None: + def state_for_cancel_listener(event: Event[EventStateChangedData]) -> None: """Fire on changes and cancel for listener if changed.""" entity = event.data["entity_id"] from_state = event.data["old_state"] @@ -1919,7 +1912,7 @@ def _render_infos_to_track_states(render_infos: Iterable[RenderInfo]) -> TrackSt @callback def _event_triggers_rerender( - event: EventType[EventStateChangedData], info: RenderInfo + event: Event[EventStateChangedData], info: RenderInfo ) -> bool: """Determine if a template should be re-rendered from an event.""" entity_id = event.data["entity_id"] @@ -1935,7 +1928,7 @@ def _event_triggers_rerender( @callback def _rate_limit_for_event( - event: EventType[EventStateChangedData], + event: Event[EventStateChangedData], info: RenderInfo, track_template_: TrackTemplate, ) -> timedelta | None: diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index d18014b9f6f..9535c714bb3 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -15,7 +15,7 @@ import pytest from homeassistant.const import MATCH_ALL import homeassistant.core as ha -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import TemplateError from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED @@ -45,7 +45,6 @@ from homeassistant.helpers.event import ( track_point_in_utc_time, ) from homeassistant.helpers.template import Template, result_as_boolean -from homeassistant.helpers.typing import EventType from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -299,21 +298,21 @@ async def test_async_track_state_change_filtered(hass: HomeAssistant) -> None: multiple_entity_id_tracker = [] @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] single_entity_id_tracker.append((old_state, new_state)) @ha.callback - def multiple_run_callback(event: EventType[EventStateChangedData]) -> None: + def multiple_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] multiple_entity_id_tracker.append((old_state, new_state)) @ha.callback - def callback_that_throws(event: EventType[EventStateChangedData]) -> None: + def callback_that_throws(event: Event[EventStateChangedData]) -> None: raise ValueError track_single = async_track_state_change_filtered( @@ -435,21 +434,21 @@ async def test_async_track_state_change_event(hass: HomeAssistant) -> None: multiple_entity_id_tracker = [] @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] single_entity_id_tracker.append((old_state, new_state)) @ha.callback - def multiple_run_callback(event: EventType[EventStateChangedData]) -> None: + def multiple_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] multiple_entity_id_tracker.append((old_state, new_state)) @ha.callback - def callback_that_throws(event: EventType[EventStateChangedData]) -> None: + def callback_that_throws(event: Event[EventStateChangedData]) -> None: raise ValueError unsub_single = async_track_state_change_event( @@ -543,14 +542,14 @@ async def test_async_track_state_added_domain(hass: HomeAssistant) -> None: multiple_entity_id_tracker = [] @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] single_entity_id_tracker.append((old_state, new_state)) @ha.callback - def multiple_run_callback(event: EventType[EventStateChangedData]) -> None: + def multiple_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] @@ -657,14 +656,14 @@ async def test_async_track_state_removed_domain(hass: HomeAssistant) -> None: multiple_entity_id_tracker = [] @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] single_entity_id_tracker.append((old_state, new_state)) @ha.callback - def multiple_run_callback(event: EventType[EventStateChangedData]) -> None: + def multiple_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] @@ -741,14 +740,14 @@ async def test_async_track_state_removed_domain_match_all(hass: HomeAssistant) - match_all_entity_id_tracker = [] @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] single_entity_id_tracker.append((old_state, new_state)) @ha.callback - def match_all_run_callback(event: EventType[EventStateChangedData]) -> None: + def match_all_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] @@ -969,7 +968,7 @@ async def test_track_template_result(hass: HomeAssistant) -> None: ) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -981,7 +980,7 @@ async def test_track_template_result(hass: HomeAssistant) -> None: @ha.callback def wildcard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -994,7 +993,7 @@ async def test_track_template_result(hass: HomeAssistant) -> None: ) async def wildercard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -1064,7 +1063,7 @@ async def test_track_template_result_none(hass: HomeAssistant) -> None: ) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -1077,7 +1076,7 @@ async def test_track_template_result_none(hass: HomeAssistant) -> None: @ha.callback def wildcard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -1094,7 +1093,7 @@ async def test_track_template_result_none(hass: HomeAssistant) -> None: ) async def wildercard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -1144,7 +1143,7 @@ async def test_track_template_result_super_template(hass: HomeAssistant) -> None ) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1165,7 +1164,7 @@ async def test_track_template_result_super_template(hass: HomeAssistant) -> None @ha.callback def wildcard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1187,7 +1186,7 @@ async def test_track_template_result_super_template(hass: HomeAssistant) -> None ) async def wildercard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1303,7 +1302,7 @@ async def test_track_template_result_super_template_initially_false( await hass.async_block_till_done() def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1324,7 +1323,7 @@ async def test_track_template_result_super_template_initially_false( @ha.callback def wildcard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1346,7 +1345,7 @@ async def test_track_template_result_super_template_initially_false( ) async def wildercard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1474,7 +1473,7 @@ async def test_track_template_result_super_template_2( return result_as_boolean(result) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1497,7 +1496,7 @@ async def test_track_template_result_super_template_2( @ha.callback def wildcard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1521,7 +1520,7 @@ async def test_track_template_result_super_template_2( ) async def wildercard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1629,7 +1628,7 @@ async def test_track_template_result_super_template_2_initially_false( return result_as_boolean(result) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1652,7 +1651,7 @@ async def test_track_template_result_super_template_2_initially_false( @ha.callback def wildcard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1676,7 +1675,7 @@ async def test_track_template_result_super_template_2_initially_false( ) async def wildercard_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -1759,7 +1758,7 @@ async def test_track_template_result_complex(hass: HomeAssistant) -> None: template_complex = Template(template_complex_str, hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -1915,7 +1914,7 @@ async def test_track_template_result_with_wildcard(hass: HomeAssistant) -> None: template_complex = Template(template_complex_str, hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -1970,7 +1969,7 @@ async def test_track_template_result_with_group(hass: HomeAssistant) -> None: template_complex = Template(template_complex_str, hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -2030,7 +2029,7 @@ async def test_track_template_result_and_conditional(hass: HomeAssistant) -> Non template = Template(template_str, hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -2098,7 +2097,7 @@ async def test_track_template_result_and_conditional_upper_case( template = Template(template_str, hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -2160,7 +2159,7 @@ async def test_track_template_result_iterator(hass: HomeAssistant) -> None: @ha.callback def iterator_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: iterator_runs.append(updates.pop().result) @@ -2196,7 +2195,7 @@ async def test_track_template_result_iterator(hass: HomeAssistant) -> None: @ha.callback def filter_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: filter_runs.append(updates.pop().result) @@ -2249,7 +2248,7 @@ async def test_track_template_result_errors( @ha.callback def syntax_error_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -2272,7 +2271,7 @@ async def test_track_template_result_errors( @ha.callback def not_exist_runs_error_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: template_track = updates.pop() @@ -2340,7 +2339,7 @@ async def test_track_template_result_transient_errors( @ha.callback def sometimes_error_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: track_result = updates.pop() @@ -2388,7 +2387,7 @@ async def test_static_string(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2411,7 +2410,7 @@ async def test_track_template_rate_limit(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2473,7 +2472,7 @@ async def test_track_template_rate_limit_super(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -2549,7 +2548,7 @@ async def test_track_template_rate_limit_super_2(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -2621,7 +2620,7 @@ async def test_track_template_rate_limit_super_3(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for track_result in updates: @@ -2695,7 +2694,7 @@ async def test_track_template_rate_limit_suppress_listener(hass: HomeAssistant) @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2795,7 +2794,7 @@ async def test_track_template_rate_limit_five(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2834,7 +2833,7 @@ async def test_track_template_has_default_rate_limit(hass: HomeAssistant) -> Non @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2878,7 +2877,7 @@ async def test_track_template_unavailable_states_has_default_rate_limit( @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2922,7 +2921,7 @@ async def test_specifically_referenced_entity_is_not_rate_limited( @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -2968,7 +2967,7 @@ async def test_track_two_templates_with_different_rate_limits( @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: for update in updates: @@ -3032,7 +3031,7 @@ async def test_string(hass: HomeAssistant) -> None: @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -3055,7 +3054,7 @@ async def test_track_template_result_refresh_cancel(hass: HomeAssistant) -> None @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates.pop().result) @@ -3120,7 +3119,7 @@ async def test_async_track_template_result_multiple_templates( @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates) @@ -3184,7 +3183,7 @@ async def test_async_track_template_result_multiple_templates_mixing_domain( @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates) @@ -3251,7 +3250,7 @@ async def test_track_template_with_time(hass: HomeAssistant) -> None: template_complex = Template("{{ states.switch.test.state and now() }}", hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -3284,7 +3283,7 @@ async def test_track_template_with_time_default(hass: HomeAssistant) -> None: template_complex = Template("{{ now() }}", hass) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -3338,7 +3337,7 @@ async def test_track_template_with_time_that_leaves_scope( ) def specific_run_callback( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: specific_runs.append(updates.pop().result) @@ -3409,7 +3408,7 @@ async def test_async_track_template_result_multiple_templates_mixing_listeners( @ha.callback def refresh_listener( - event: EventType[EventStateChangedData] | None, + event: Event[EventStateChangedData] | None, updates: list[TrackTemplateResult], ) -> None: refresh_runs.append(updates) @@ -4437,14 +4436,14 @@ async def test_track_state_change_event_chain_multple_entity( tracker_unsub = [] @ha.callback - def chained_single_run_callback(event: EventType[EventStateChangedData]) -> None: + def chained_single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] chained_tracker_called.append((old_state, new_state)) @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] @@ -4491,14 +4490,14 @@ async def test_track_state_change_event_chain_single_entity( tracker_unsub = [] @ha.callback - def chained_single_run_callback(event: EventType[EventStateChangedData]) -> None: + def chained_single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] chained_tracker_called.append((old_state, new_state)) @ha.callback - def single_run_callback(event: EventType[EventStateChangedData]) -> None: + def single_run_callback(event: Event[EventStateChangedData]) -> None: old_state = event.data["old_state"] new_state = event.data["new_state"] From 0e86f182cc8418d9791ef1f62b75b796957e1c15 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:09:31 +0100 Subject: [PATCH 0568/1691] Add diagnostics to webmin (#112543) --- .../components/webmin/diagnostics.py | 35 +++++ .../webmin/snapshots/test_diagnostics.ambr | 138 ++++++++++++++++++ tests/components/webmin/test_diagnostics.py | 23 +++ 3 files changed, 196 insertions(+) create mode 100644 homeassistant/components/webmin/diagnostics.py create mode 100644 tests/components/webmin/snapshots/test_diagnostics.ambr create mode 100644 tests/components/webmin/test_diagnostics.py diff --git a/homeassistant/components/webmin/diagnostics.py b/homeassistant/components/webmin/diagnostics.py new file mode 100644 index 00000000000..86e2c9eef62 --- /dev/null +++ b/homeassistant/components/webmin/diagnostics.py @@ -0,0 +1,35 @@ +"""Diagnostics support for Webmin.""" +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import WebminUpdateCoordinator + +TO_REDACT = { + CONF_HOST, + CONF_UNIQUE_ID, + CONF_USERNAME, + CONF_PASSWORD, + "address", + "address6", + "ether", + "broadcast", + "device", + "dir", + "title", + "entry_id", +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: WebminUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + return async_redact_data( + {"entry": entry.as_dict(), "data": coordinator.data}, TO_REDACT + ) diff --git a/tests/components/webmin/snapshots/test_diagnostics.ambr b/tests/components/webmin/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..f227f6332fe --- /dev/null +++ b/tests/components/webmin/snapshots/test_diagnostics.ambr @@ -0,0 +1,138 @@ +# serializer version: 1 +# name: test_diagnostics + dict({ + 'data': dict({ + 'active_interfaces': list([ + dict({ + 'address': '**REDACTED**', + 'address6': '**REDACTED**', + 'broadcast': '**REDACTED**', + 'edit': 1, + 'fullname': 'lo', + 'index': 0, + 'mtu': 65536, + 'name': 'lo', + 'netmask': '255.0.0.0', + 'netmask6': list([ + 128, + ]), + 'scope6': list([ + 'host', + ]), + 'up': 1, + }), + dict({ + 'address6': '**REDACTED**', + 'edit': 1, + 'ether': '**REDACTED**', + 'fullname': 'enp6s0', + 'index': 1, + 'mtu': 1500, + 'name': 'enp6s0', + 'netmask6': list([ + ]), + 'scope6': list([ + ]), + 'up': 1, + }), + dict({ + 'address': '**REDACTED**', + 'address6': '**REDACTED**', + 'broadcast': '**REDACTED**', + 'edit': 1, + 'ether': '**REDACTED**', + 'fullname': 'eno1', + 'index': 2, + 'mtu': 1500, + 'name': 'eno1', + 'netmask': '255.255.255.0', + 'netmask6': list([ + 64, + ]), + 'scope6': list([ + 'link', + ]), + 'up': 1, + }), + ]), + 'free_space': 8641328926720, + 'fs': list([ + dict({ + 'device': '**REDACTED**', + 'dir': '**REDACTED**', + 'free': 174511820800, + 'ifree': 15091734, + 'itotal': 15482880, + 'iused': 391146, + 'iused_percent': 3, + 'total': 248431161344, + 'type': 'ext4', + 'used': 61225123840, + 'used_percent': 26, + }), + dict({ + 'device': '**REDACTED**', + 'dir': '**REDACTED**', + 'free': 1044483624960, + 'ifree': 183131475, + 'itotal': 183140352, + 'iused': 8877, + 'iused_percent': 1, + 'total': 5952635744256, + 'type': 'ext4', + 'used': 4608079593472, + 'used_percent': 82, + }), + dict({ + 'device': '**REDACTED**', + 'dir': '**REDACTED**', + 'free': 7422333480960, + 'ifree': 362787383, + 'itotal': 366198784, + 'iused': 3411401, + 'iused_percent': 1, + 'total': 11903838912512, + 'type': 'ext4', + 'used': 3881508986880, + 'used_percent': 35, + }), + ]), + 'load_15m': 1.0, + 'load_1m': 0.98, + 'load_5m': 1.02, + 'mem_free': 26162544, + 'mem_total': 32767008, + 'swap_free': 1953088, + 'swap_total': 1953088, + 'total_space': 18104905818112, + 'uptime': dict({ + 'days': 3, + 'minutes': 23, + 'seconds': 12, + }), + 'used_space': 8550813704192, + }), + 'entry': dict({ + 'data': dict({ + }), + 'disabled_by': None, + 'domain': 'webmin', + 'entry_id': '**REDACTED**', + 'minor_version': 1, + 'options': dict({ + 'host': '**REDACTED**', + 'password': '**REDACTED**', + 'port': 10000, + 'ssl': True, + 'username': '**REDACTED**', + 'verify_ssl': False, + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': '**REDACTED**', + 'unique_id': None, + 'version': 1, + }), + }) +# --- diff --git a/tests/components/webmin/test_diagnostics.py b/tests/components/webmin/test_diagnostics.py new file mode 100644 index 00000000000..33a349878d1 --- /dev/null +++ b/tests/components/webmin/test_diagnostics.py @@ -0,0 +1,23 @@ +"""Tests for the diagnostics data provided by the Webmin integration.""" +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from .conftest import async_init_integration + +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics.""" + assert ( + await get_diagnostics_for_config_entry( + hass, hass_client, await async_init_integration(hass) + ) + == snapshot + ) From 959826c4b4358d88f3a0d9e2a58fa8f4561a93d9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:32:40 +0100 Subject: [PATCH 0569/1691] Update byte string formatting (#112752) --- homeassistant/components/stream/fmp4utils.py | 2 +- tests/components/airthings_ble/__init__.py | 4 +-- tests/components/bthome/test_binary_sensor.py | 4 +-- .../components/bthome/test_device_trigger.py | 12 ++++---- tests/components/bthome/test_event.py | 4 +-- tests/components/bthome/test_sensor.py | 30 +++++++++---------- tests/components/mqtt/test_init.py | 10 +++---- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/stream/fmp4utils.py b/homeassistant/components/stream/fmp4utils.py index d84b98f8b6f..4175b91fd11 100644 --- a/homeassistant/components/stream/fmp4utils.py +++ b/homeassistant/components/stream/fmp4utils.py @@ -170,7 +170,7 @@ def read_init(bytes_io: BufferedIOBase) -> bytes: ZERO32 = b"\x00\x00\x00\x00" ONE32 = b"\x00\x01\x00\x00" -NEGONE32 = b"\xFF\xFF\x00\x00" +NEGONE32 = b"\xff\xff\x00\x00" XYW_ROW = ZERO32 + ZERO32 + b"\x40\x00\x00\x00" ROTATE_RIGHT = (ZERO32 + ONE32 + ZERO32) + (NEGONE32 + ZERO32 + ZERO32) ROTATE_LEFT = (ZERO32 + NEGONE32 + ZERO32) + (ONE32 + ZERO32 + ZERO32) diff --git a/tests/components/airthings_ble/__init__.py b/tests/components/airthings_ble/__init__.py index 231ec12cb5f..5da9d8b48cd 100644 --- a/tests/components/airthings_ble/__init__.py +++ b/tests/components/airthings_ble/__init__.py @@ -59,7 +59,7 @@ WAVE_SERVICE_INFO = BluetoothServiceInfoBleak( service_data={ # Sensor data "b42e2a68-ade7-11e4-89d3-123b93f75cba": bytearray( - b"\x01\x02\x03\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0A" + b"\x01\x02\x03\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a" ), # Manufacturer "00002a29-0000-1000-8000-00805f9b34fb": bytearray(b"Airthings AS"), @@ -104,7 +104,7 @@ VIEW_PLUS_SERVICE_INFO = BluetoothServiceInfoBleak( manufacturer_data={820: b"\xe4/\xa5\xae\t\x00"}, service_data={ "b42eb4a6-ade7-11e4-89d3-123b93f75cba": bytearray( - b"\x01\x02\x03\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0A" + b"\x01\x02\x03\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0a" ), # Manufacturer "00002a29-0000-1000-8000-00805f9b34fb": bytearray(b"Airthings AS"), diff --git a/tests/components/bthome/test_binary_sensor.py b/tests/components/bthome/test_binary_sensor.py index 41d467410c1..dd12bbd4ee0 100644 --- a/tests/components/bthome/test_binary_sensor.py +++ b/tests/components/bthome/test_binary_sensor.py @@ -68,7 +68,7 @@ _LOGGER = logging.getLogger(__name__) "A4:C1:38:8D:18:B2", make_bthome_v1_adv( "A4:C1:38:8D:18:B2", - b"\x02\x0F\x01", + b"\x02\x0f\x01", ), None, [ @@ -154,7 +154,7 @@ async def test_v1_binary_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x0F\x01", + b"\x40\x0f\x01", ), None, [ diff --git a/tests/components/bthome/test_device_trigger.py b/tests/components/bthome/test_device_trigger.py index ec45ae25150..240eb7ab3d8 100644 --- a/tests/components/bthome/test_device_trigger.py +++ b/tests/components/bthome/test_device_trigger.py @@ -59,7 +59,7 @@ async def test_event_long_press(hass: HomeAssistant) -> None: # Emit long press event inject_bluetooth_service_info_bleak( hass, - make_bthome_v2_adv(mac, b"\x40\x3A\x04"), + make_bthome_v2_adv(mac, b"\x40\x3a\x04"), ) # wait for the event @@ -82,7 +82,7 @@ async def test_event_rotate_dimmer(hass: HomeAssistant) -> None: # Emit rotate dimmer 3 steps left event inject_bluetooth_service_info_bleak( hass, - make_bthome_v2_adv(mac, b"\x40\x3C\x01\x03"), + make_bthome_v2_adv(mac, b"\x40\x3c\x01\x03"), ) # wait for the event @@ -105,7 +105,7 @@ async def test_get_triggers_button(hass: HomeAssistant) -> None: # Emit long press event so it creates the device in the registry inject_bluetooth_service_info_bleak( hass, - make_bthome_v2_adv(mac, b"\x40\x3A\x04"), + make_bthome_v2_adv(mac, b"\x40\x3a\x04"), ) # wait for the event @@ -141,7 +141,7 @@ async def test_get_triggers_dimmer(hass: HomeAssistant) -> None: # Emit rotate left with 3 steps event so it creates the device in the registry inject_bluetooth_service_info_bleak( hass, - make_bthome_v2_adv(mac, b"\x40\x3C\x01\x03"), + make_bthome_v2_adv(mac, b"\x40\x3c\x01\x03"), ) # wait for the event @@ -237,7 +237,7 @@ async def test_if_fires_on_motion_detected(hass: HomeAssistant, calls) -> None: # Emit a button event so it creates the device in the registry inject_bluetooth_service_info_bleak( hass, - make_bthome_v2_adv(mac, b"\x40\x3A\x03"), + make_bthome_v2_adv(mac, b"\x40\x3a\x03"), ) # # wait for the event @@ -272,7 +272,7 @@ async def test_if_fires_on_motion_detected(hass: HomeAssistant, calls) -> None: # Emit long press event inject_bluetooth_service_info_bleak( hass, - make_bthome_v2_adv(mac, b"\x40\x3A\x04"), + make_bthome_v2_adv(mac, b"\x40\x3a\x04"), ) await hass.async_block_till_done() diff --git a/tests/components/bthome/test_event.py b/tests/components/bthome/test_event.py index f6cf3fd49c7..34a74087110 100644 --- a/tests/components/bthome/test_event.py +++ b/tests/components/bthome/test_event.py @@ -23,7 +23,7 @@ from tests.components.bluetooth import ( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x3A\x00\x3A\x01\x3A\x03", + b"\x40\x3a\x00\x3a\x01\x3a\x03", ), None, [ @@ -43,7 +43,7 @@ from tests.components.bluetooth import ( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x3A\x04", + b"\x40\x3a\x04", ), None, [ diff --git a/tests/components/bthome/test_sensor.py b/tests/components/bthome/test_sensor.py index ce0d1de4f26..f1cffa8583f 100644 --- a/tests/components/bthome/test_sensor.py +++ b/tests/components/bthome/test_sensor.py @@ -162,7 +162,7 @@ _LOGGER = logging.getLogger(__name__) "A4:C1:38:8D:18:B2", make_bthome_v1_adv( "A4:C1:38:8D:18:B2", - b"\x23\x08\xCA\x06", + b"\x23\x08\xca\x06", ), None, [ @@ -482,7 +482,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x06\x5E\x1F", + b"\x40\x06\x5e\x1f", ), None, [ @@ -499,7 +499,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x07\x3E\x1d", + b"\x40\x07\x3e\x1d", ), None, [ @@ -516,7 +516,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x08\xCA\x06", + b"\x40\x08\xca\x06", ), None, [ @@ -677,7 +677,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x3F\x02\x0c", + b"\x40\x3f\x02\x0c", ), None, [ @@ -694,7 +694,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x40\x0C\x00", + b"\x40\x40\x0c\x00", ), None, [ @@ -711,7 +711,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x41\x4E\x00", + b"\x40\x41\x4e\x00", ), None, [ @@ -728,7 +728,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x42\x4E\x34\x00", + b"\x40\x42\x4e\x34\x00", ), None, [ @@ -745,7 +745,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x43\x4E\x34", + b"\x40\x43\x4e\x34", ), None, [ @@ -762,7 +762,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x44\x4E\x34", + b"\x40\x44\x4e\x34", ), None, [ @@ -829,7 +829,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x48\xDC\x87", + b"\x40\x48\xdc\x87", ), None, [ @@ -846,7 +846,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x40\x49\xDC\x87", + b"\x40\x49\xdc\x87", ), None, [ @@ -863,7 +863,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x44\x50\x5D\x39\x61\x64", + b"\x44\x50\x5d\x39\x61\x64", ), None, [ @@ -947,7 +947,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x44\x53\x0C\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x21", + b"\x44\x53\x0c\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21", ), None, [ @@ -962,7 +962,7 @@ async def test_v1_sensors( "A4:C1:38:8D:18:B2", make_bthome_v2_adv( "A4:C1:38:8D:18:B2", - b"\x44\x54\x0C\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x21", + b"\x44\x54\x0c\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21", ), None, [ diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 3188f17feff..b10031c75f8 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -748,11 +748,11 @@ def test_validate_topic() -> None: with pytest.raises(vol.Invalid): mqtt.util.valid_topic("\u0001") with pytest.raises(vol.Invalid): - mqtt.util.valid_topic("\u001F") + mqtt.util.valid_topic("\u001f") with pytest.raises(vol.Invalid): - mqtt.util.valid_topic("\u007F") + mqtt.util.valid_topic("\u007f") with pytest.raises(vol.Invalid): - mqtt.util.valid_topic("\u009F") + mqtt.util.valid_topic("\u009f") with pytest.raises(vol.Invalid): mqtt.util.valid_topic("\ufdd0") with pytest.raises(vol.Invalid): @@ -2723,7 +2723,7 @@ async def test_mqtt_ws_subscription( async_fire_mqtt_message(hass, "test-topic", "test1") async_fire_mqtt_message(hass, "test-topic", "test2") - async_fire_mqtt_message(hass, "test-topic", b"\xDE\xAD\xBE\xEF") + async_fire_mqtt_message(hass, "test-topic", b"\xde\xad\xbe\xef") response = await client.receive_json() assert response["event"]["topic"] == "test-topic" @@ -2751,7 +2751,7 @@ async def test_mqtt_ws_subscription( async_fire_mqtt_message(hass, "test-topic", "test1", 2) async_fire_mqtt_message(hass, "test-topic", "test2", 2) - async_fire_mqtt_message(hass, "test-topic", b"\xDE\xAD\xBE\xEF", 2) + async_fire_mqtt_message(hass, "test-topic", b"\xde\xad\xbe\xef", 2) response = await client.receive_json() assert response["event"]["topic"] == "test-topic" From 55b2d1a00c4dab390cc749eef2ed0f4a0e6eedc0 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:33:33 -0500 Subject: [PATCH 0570/1691] Disable updating ZHA coordinator path from discovery info (#112415) * Never update the device path from config flows * Bring coverage up to 100% * Update tests/components/zha/test_config_flow.py Co-authored-by: TheJulianJES --------- Co-authored-by: TheJulianJES --- homeassistant/components/zha/config_flow.py | 30 ++++++++------ tests/components/zha/test_config_flow.py | 45 ++------------------- 2 files changed, 20 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index 7ecf3357334..42febb3b36d 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -501,23 +501,27 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN): VERSION = 4 - async def _set_unique_id_or_update_path( + async def _set_unique_id_and_update_ignored_flow( self, unique_id: str, device_path: str ) -> None: - """Set the flow's unique ID and update the device path if it isn't unique.""" + """Set the flow's unique ID and update the device path in an ignored flow.""" current_entry = await self.async_set_unique_id(unique_id) if not current_entry: return - self._abort_if_unique_id_configured( - updates={ - CONF_DEVICE: { - **current_entry.data.get(CONF_DEVICE, {}), - CONF_DEVICE_PATH: device_path, - }, - } - ) + if current_entry.source != SOURCE_IGNORE: + self._abort_if_unique_id_configured() + else: + # Only update the current entry if it is an ignored discovery + self._abort_if_unique_id_configured( + updates={ + CONF_DEVICE: { + **current_entry.data.get(CONF_DEVICE, {}), + CONF_DEVICE_PATH: device_path, + }, + } + ) @staticmethod @callback @@ -587,7 +591,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN): description = discovery_info.description dev_path = discovery_info.device - await self._set_unique_id_or_update_path( + await self._set_unique_id_and_update_ignored_flow( unique_id=f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}", device_path=dev_path, ) @@ -637,7 +641,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN): node_name = local_name.removesuffix(".local") device_path = f"socket://{discovery_info.host}:{port}" - await self._set_unique_id_or_update_path( + await self._set_unique_id_and_update_ignored_flow( unique_id=node_name, device_path=device_path, ) @@ -662,7 +666,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN): device_settings = discovery_data["port"] device_path = device_settings[CONF_DEVICE_PATH] - await self._set_unique_id_or_update_path( + await self._set_unique_id_and_update_ignored_flow( unique_id=f"{name}_{radio_type.name}_{device_path}", device_path=device_path, ) diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 57833f0e67e..9ec3cc9f497 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -294,45 +294,6 @@ async def test_efr32_via_zeroconf(hass: HomeAssistant) -> None: } -@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) -@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True)) -async def test_discovery_via_zeroconf_ip_change(hass: HomeAssistant) -> None: - """Test zeroconf flow -- radio detected.""" - entry = MockConfigEntry( - domain=DOMAIN, - unique_id="tube_zb_gw_cc2652p2_poe", - data={ - CONF_DEVICE: { - CONF_DEVICE_PATH: "socket://192.168.1.5:6638", - CONF_BAUDRATE: 115200, - CONF_FLOW_CONTROL: None, - } - }, - ) - entry.add_to_hass(hass) - - service_info = zeroconf.ZeroconfServiceInfo( - ip_address=ip_address("192.168.1.22"), - ip_addresses=[ip_address("192.168.1.22")], - hostname="tube_zb_gw_cc2652p2_poe.local.", - name="mock_name", - port=6053, - properties={"address": "tube_zb_gw_cc2652p2_poe.local"}, - type="mock_type", - ) - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_ZEROCONF}, data=service_info - ) - - assert result["type"] == FlowResultType.ABORT - assert result["reason"] == "already_configured" - assert entry.data[CONF_DEVICE] == { - CONF_DEVICE_PATH: "socket://192.168.1.22:6638", - CONF_BAUDRATE: 115200, - CONF_FLOW_CONTROL: None, - } - - @patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) @patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True)) async def test_discovery_via_zeroconf_ip_change_ignored(hass: HomeAssistant) -> None: @@ -548,8 +509,8 @@ async def test_discovery_via_usb_already_setup(hass: HomeAssistant) -> None: @patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) -async def test_discovery_via_usb_path_changes(hass: HomeAssistant) -> None: - """Test usb flow already setup and the path changes.""" +async def test_discovery_via_usb_path_does_not_change(hass: HomeAssistant) -> None: + """Test usb flow already set up and the path does not change.""" entry = MockConfigEntry( domain=DOMAIN, @@ -580,7 +541,7 @@ async def test_discovery_via_usb_path_changes(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.ABORT assert result["reason"] == "already_configured" assert entry.data[CONF_DEVICE] == { - CONF_DEVICE_PATH: "/dev/ttyZIGBEE", + CONF_DEVICE_PATH: "/dev/ttyUSB1", CONF_BAUDRATE: 115200, CONF_FLOW_CONTROL: None, } From aa16a9d707f94525ddf73bfb48c41c20119b7ba0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:38:34 +0100 Subject: [PATCH 0571/1691] Add empty line after module docstring (3) (#112750) --- homeassistant/auth/permissions/const.py | 1 + homeassistant/components/advantage_air/const.py | 1 + homeassistant/components/agent_dvr/const.py | 1 + homeassistant/components/air_quality/group.py | 1 - homeassistant/components/airnow/const.py | 1 + homeassistant/components/alarm_control_panel/group.py | 1 - homeassistant/components/alexa/resources.py | 1 - homeassistant/components/amberelectric/sensor.py | 1 - homeassistant/components/amcrest/const.py | 1 + homeassistant/components/androidtv/const.py | 1 + homeassistant/components/anthemav/const.py | 1 + homeassistant/components/arcam_fmj/const.py | 1 + homeassistant/components/assist_pipeline/const.py | 1 + homeassistant/components/asuswrt/const.py | 1 + homeassistant/components/aussie_broadband/const.py | 1 + homeassistant/components/aws/const.py | 1 + homeassistant/components/azure_devops/const.py | 1 + homeassistant/components/balboa/const.py | 1 + homeassistant/components/binary_sensor/group.py | 1 - homeassistant/components/blackbird/const.py | 1 + homeassistant/components/blebox/const.py | 1 - homeassistant/components/blueprint/const.py | 1 + homeassistant/components/bluesound/const.py | 1 + homeassistant/components/caldav/api.py | 1 - homeassistant/components/channels/const.py | 1 + homeassistant/components/climate/group.py | 1 - homeassistant/components/co2signal/const.py | 1 - homeassistant/components/color_extractor/const.py | 1 + homeassistant/components/comelit/__init__.py | 1 - homeassistant/components/compensation/const.py | 1 + homeassistant/components/cover/group.py | 1 - homeassistant/components/cover/intent.py | 1 - homeassistant/components/daikin/const.py | 1 + homeassistant/components/demo/const.py | 1 + homeassistant/components/device_automation/const.py | 1 + homeassistant/components/device_tracker/group.py | 1 - homeassistant/components/directv/const.py | 1 + homeassistant/components/doorbird/entity.py | 1 - homeassistant/components/ecoforest/coordinator.py | 1 - homeassistant/components/emulated_roku/const.py | 1 + homeassistant/components/enigma2/const.py | 1 + homeassistant/components/evohome/const.py | 1 + homeassistant/components/fan/group.py | 1 - homeassistant/components/fitbit/__init__.py | 1 - homeassistant/components/frontier_silicon/const.py | 1 + homeassistant/components/google_travel_time/const.py | 1 + homeassistant/components/html5/const.py | 1 + homeassistant/components/humidifier/group.py | 1 - homeassistant/components/hunterdouglas_powerview/const.py | 1 - homeassistant/components/hydrawise/__init__.py | 1 - homeassistant/components/idasen_desk/const.py | 1 - homeassistant/components/justnimbus/const.py | 1 - homeassistant/components/keymitt_ble/const.py | 1 + homeassistant/components/kodi/const.py | 1 + homeassistant/components/kostal_plenticore/const.py | 1 + homeassistant/components/kulersky/const.py | 1 + homeassistant/components/lamarzocco/diagnostics.py | 1 - homeassistant/components/ld2410_ble/binary_sensor.py | 1 - homeassistant/components/ld2410_ble/sensor.py | 1 - homeassistant/components/lg_netcast/const.py | 1 + homeassistant/components/lg_soundbar/const.py | 1 + homeassistant/components/light/group.py | 1 - homeassistant/components/litterrobot/const.py | 1 + homeassistant/components/local_file/const.py | 1 + homeassistant/components/lock/group.py | 1 - homeassistant/components/loqed/const.py | 1 - homeassistant/components/matrix/const.py | 1 + homeassistant/components/media_player/group.py | 1 - homeassistant/components/melnor/const.py | 1 - homeassistant/components/minecraft_server/api.py | 1 - homeassistant/components/mystrom/const.py | 1 + homeassistant/components/myuplink/number.py | 1 - homeassistant/components/nextbus/const.py | 1 + homeassistant/components/nfandroidtv/const.py | 1 + homeassistant/components/nuki/const.py | 1 + homeassistant/components/nzbget/const.py | 1 + homeassistant/components/onboarding/const.py | 1 + homeassistant/components/oncue/const.py | 1 - homeassistant/components/openhome/const.py | 1 + homeassistant/components/ovo_energy/const.py | 1 + homeassistant/components/owntracks/helper.py | 1 + homeassistant/components/panasonic_viera/const.py | 1 + homeassistant/components/person/group.py | 1 - homeassistant/components/plant/group.py | 1 - homeassistant/components/private_ble_device/const.py | 1 + homeassistant/components/ps4/const.py | 1 + homeassistant/components/rainforest_raven/const.py | 1 + homeassistant/components/random/const.py | 1 + homeassistant/components/recorder/history/const.py | 1 - homeassistant/components/remote/group.py | 1 - homeassistant/components/renson/const.py | 1 - homeassistant/components/rfxtrx/helpers.py | 1 - homeassistant/components/roku/const.py | 1 + homeassistant/components/romy/vacuum.py | 1 - homeassistant/components/sensor/group.py | 1 - homeassistant/components/slimproto/const.py | 1 - homeassistant/components/snmp/const.py | 1 + homeassistant/components/solax/const.py | 1 - homeassistant/components/songpal/const.py | 1 + homeassistant/components/soundtouch/const.py | 1 + homeassistant/components/squeezebox/const.py | 1 + homeassistant/components/steamist/const.py | 1 - homeassistant/components/stream/const.py | 1 + homeassistant/components/surepetcare/const.py | 1 + homeassistant/components/switch/group.py | 1 - homeassistant/components/switcher_kis/const.py | 1 + homeassistant/components/tami4/const.py | 1 + homeassistant/components/trend/const.py | 1 + homeassistant/components/tts/const.py | 1 + homeassistant/components/twinkly/__init__.py | 1 - homeassistant/components/utility_meter/const.py | 1 + homeassistant/components/vacuum/group.py | 1 - homeassistant/components/vacuum/intent.py | 1 - homeassistant/components/vera/const.py | 1 + homeassistant/components/versasense/const.py | 1 + homeassistant/components/wake_on_lan/const.py | 1 + homeassistant/components/wake_word/const.py | 1 + homeassistant/components/water_heater/group.py | 1 - homeassistant/components/weather/group.py | 1 - homeassistant/components/weatherkit/sensor.py | 1 - homeassistant/components/wemo/const.py | 1 + homeassistant/components/yamaha/const.py | 1 + homeassistant/components/zerproc/const.py | 1 + homeassistant/components/zodiac/const.py | 1 + homeassistant/util/yaml/const.py | 1 + script/translations/deduplicate.py | 1 - tests/components/accuweather/__init__.py | 1 + tests/components/advantage_air/test_light.py | 1 - tests/components/advantage_air/test_select.py | 1 - tests/components/airly/__init__.py | 1 + tests/components/airthings_ble/__init__.py | 1 + tests/components/analytics_insights/__init__.py | 1 + tests/components/android_ip_webcam/test_init.py | 1 - tests/components/apcupsd/__init__.py | 1 + tests/components/apple_tv/__init__.py | 1 + tests/components/assist_pipeline/__init__.py | 1 + tests/components/auth/__init__.py | 1 + tests/components/awair/__init__.py | 1 - tests/components/baf/__init__.py | 1 - tests/components/balboa/__init__.py | 1 + tests/components/bang_olufsen/const.py | 1 - tests/components/bang_olufsen/test_config_flow.py | 1 - tests/components/blue_current/__init__.py | 1 + tests/components/bluemaestro/__init__.py | 1 - tests/components/bluetooth/__init__.py | 1 - tests/components/bmw_connected_drive/__init__.py | 1 - tests/components/bmw_connected_drive/conftest.py | 1 - tests/components/broadlink/__init__.py | 1 + tests/components/brother/__init__.py | 1 + tests/components/bthome/__init__.py | 1 - tests/components/canary/__init__.py | 1 + tests/components/cert_expiry/const.py | 1 + tests/components/cloud/__init__.py | 1 + tests/components/cloudflare/__init__.py | 1 + tests/components/co2signal/__init__.py | 1 + tests/components/coinbase/const.py | 1 - tests/components/comfoconnect/test_sensor.py | 1 + tests/components/conversation/__init__.py | 1 + tests/components/conversation/test_default_agent_intents.py | 1 - tests/components/devolo_home_control/__init__.py | 1 + tests/components/devolo_home_network/__init__.py | 1 + tests/components/diagnostics/__init__.py | 1 + tests/components/directv/__init__.py | 1 + tests/components/dnsip/__init__.py | 1 + tests/components/dynalite/test_panel.py | 1 - tests/components/ecovacs/const.py | 1 - tests/components/efergy/__init__.py | 1 + tests/components/electric_kiwi/test_sensor.py | 1 - tests/components/elmax/__init__.py | 1 + tests/components/esphome/test_button.py | 1 - tests/components/esphome/test_climate.py | 1 - tests/components/esphome/test_fan.py | 1 - tests/components/esphome/test_init.py | 1 - tests/components/esphome/test_light.py | 1 - tests/components/esphome/test_lock.py | 1 - tests/components/esphome/test_select.py | 1 - tests/components/esphome/test_switch.py | 1 - tests/components/eufylife_ble/__init__.py | 1 - tests/components/ezviz/__init__.py | 1 + tests/components/fitbit/test_sensor.py | 1 - tests/components/fjaraskupan/__init__.py | 1 - tests/components/flexit_bacnet/__init__.py | 1 + tests/components/flo/common.py | 1 + tests/components/flux_led/__init__.py | 1 + tests/components/flux_led/test_number.py | 1 - tests/components/freebox/const.py | 1 - tests/components/fritzbox/__init__.py | 1 + tests/components/fronius/__init__.py | 1 + tests/components/gardena_bluetooth/test_binary_sensor.py | 1 - tests/components/gardena_bluetooth/test_button.py | 1 - tests/components/gardena_bluetooth/test_number.py | 1 - tests/components/gardena_bluetooth/test_switch.py | 1 - tests/components/gdacs/__init__.py | 1 + tests/components/geo_json_events/__init__.py | 1 + tests/components/geonetnz_quakes/__init__.py | 1 + tests/components/geonetnz_volcano/__init__.py | 1 + tests/components/gios/__init__.py | 1 + tests/components/goalzero/__init__.py | 1 + tests/components/google_assistant/__init__.py | 1 + tests/components/google_tasks/conftest.py | 1 - tests/components/google_tasks/test_todo.py | 1 - tests/components/google_travel_time/const.py | 1 - tests/components/govee_ble/__init__.py | 1 - tests/components/hassio/__init__.py | 1 + tests/components/hdmi_cec/__init__.py | 1 + .../specific_devices/test_cover_that_changes_features.py | 1 - .../specific_devices/test_fan_that_changes_features.py | 1 - .../test_heater_cooler_that_changes_features.py | 1 - .../test_humidifier_that_changes_value_range.py | 1 - .../specific_devices/test_light_that_changes_features.py | 1 - tests/components/honeywell/__init__.py | 1 + tests/components/http/__init__.py | 1 + tests/components/http/test_static.py | 1 - tests/components/huisbaasje/test_data.py | 1 + tests/components/husqvarna_automower/__init__.py | 1 + tests/components/husqvarna_automower/const.py | 1 + tests/components/hyperion/__init__.py | 1 + tests/components/ibeacon/__init__.py | 1 + tests/components/image_upload/__init__.py | 1 + tests/components/imap/const.py | 1 - tests/components/inkbird/__init__.py | 1 - tests/components/iotawatt/__init__.py | 1 + tests/components/ipma/__init__.py | 1 + tests/components/jellyfin/__init__.py | 1 + tests/components/jewish_calendar/__init__.py | 1 + tests/components/keenetic_ndms2/__init__.py | 1 + tests/components/keymitt_ble/__init__.py | 1 + tests/components/kodi/__init__.py | 1 + tests/components/lamarzocco/test_button.py | 1 - tests/components/lamarzocco/test_select.py | 1 - tests/components/lamarzocco/test_update.py | 1 - tests/components/lastfm/__init__.py | 1 + tests/components/leaone/__init__.py | 1 - tests/components/lifx/__init__.py | 1 + tests/components/litejet/__init__.py | 1 + tests/components/livisi/__init__.py | 1 + tests/components/lookin/__init__.py | 1 + tests/components/lupusec/test_config_flow.py | 2 +- tests/components/lutron_caseta/__init__.py | 1 - tests/components/lutron_caseta/test_cover.py | 1 - tests/components/lutron_caseta/test_fan.py | 1 - tests/components/lutron_caseta/test_light.py | 1 - tests/components/medcom_ble/__init__.py | 1 + tests/components/media_extractor/__init__.py | 1 + tests/components/met/__init__.py | 1 + tests/components/met_eireann/__init__.py | 1 + tests/components/microbees/__init__.py | 1 + tests/components/mikrotik/__init__.py | 1 + tests/components/minio/common.py | 1 + tests/components/moat/__init__.py | 1 - tests/components/mobile_app/const.py | 1 + tests/components/mopeka/__init__.py | 1 - tests/components/motioneye/__init__.py | 1 + tests/components/mystrom/__init__.py | 1 + tests/components/myuplink/const.py | 1 + tests/components/nam/__init__.py | 1 + tests/components/nextdns/__init__.py | 1 + tests/components/nightscout/__init__.py | 1 + tests/components/nina/__init__.py | 1 + tests/components/nzbget/__init__.py | 1 + tests/components/octoprint/__init__.py | 1 + tests/components/oncue/__init__.py | 1 + tests/components/onewire/__init__.py | 1 + tests/components/onvif/__init__.py | 1 + tests/components/opensky/__init__.py | 1 + tests/components/otbr/__init__.py | 1 + tests/components/overkiz/__init__.py | 1 + tests/components/persistent_notification/test_init.py | 1 - tests/components/pi_hole/__init__.py | 1 + tests/components/private_ble_device/test_device_tracker.py | 1 - tests/components/private_ble_device/test_sensor.py | 1 - tests/components/qingping/__init__.py | 1 - tests/components/radarr/__init__.py | 1 + tests/components/rainbird/test_binary_sensor.py | 1 - tests/components/rainbird/test_calendar.py | 1 - tests/components/rainforest_eagle/__init__.py | 1 - tests/components/refoss/__init__.py | 1 + tests/components/renault/__init__.py | 1 + tests/components/repairs/__init__.py | 1 - tests/components/roborock/test_vacuum.py | 1 - tests/components/roku/__init__.py | 1 + tests/components/ruckus_unleashed/__init__.py | 1 + tests/components/samsungtv/__init__.py | 1 + tests/components/scrape/__init__.py | 1 + tests/components/screenlogic/__init__.py | 1 + tests/components/sensibo/__init__.py | 1 + tests/components/sensor/__init__.py | 1 + tests/components/sensorpro/__init__.py | 1 - tests/components/sensorpush/__init__.py | 1 - tests/components/shelly/__init__.py | 1 + tests/components/shelly/bluetooth/__init__.py | 1 + tests/components/simplisafe/common.py | 1 + tests/components/slack/__init__.py | 1 + tests/components/sma/__init__.py | 1 + tests/components/smhi/__init__.py | 1 + tests/components/snooz/__init__.py | 1 + tests/components/sonarr/__init__.py | 1 + tests/components/songpal/__init__.py | 1 + tests/components/sql/__init__.py | 1 + tests/components/steam_online/__init__.py | 1 + tests/components/steamist/__init__.py | 1 + tests/components/streamlabswater/__init__.py | 1 + tests/components/switchbot/__init__.py | 1 + tests/components/switchbot_cloud/__init__.py | 1 + tests/components/switcher_kis/__init__.py | 1 + tests/components/technove/__init__.py | 1 + tests/components/tedee/test_sensor.py | 1 - tests/components/thermobeacon/__init__.py | 1 - tests/components/thermopro/__init__.py | 1 - tests/components/tradfri/__init__.py | 1 + tests/components/trafikverket_camera/__init__.py | 1 + tests/components/trafikverket_ferry/__init__.py | 1 + tests/components/trafikverket_train/__init__.py | 1 + tests/components/twinkly/__init__.py | 1 - tests/components/twitch/__init__.py | 1 + tests/components/usb/__init__.py | 1 - tests/components/velbus/const.py | 1 + tests/components/venstar/__init__.py | 1 + tests/components/version/test_diagnostics.py | 1 - tests/components/vicare/__init__.py | 1 + tests/components/wallbox/const.py | 1 + tests/components/weather/__init__.py | 1 - tests/components/weatherkit/__init__.py | 1 + tests/components/whirlpool/__init__.py | 1 + tests/components/withings/__init__.py | 1 + tests/components/wyoming/__init__.py | 1 + tests/components/xiaomi_ble/__init__.py | 1 - tests/components/xiaomi_miio/__init__.py | 1 + tests/components/yeelight/__init__.py | 1 + tests/components/youtube/__init__.py | 1 + tests/helpers/test_group.py | 1 - tests/ignore_uncaught_exceptions.py | 1 + tests/pylint/__init__.py | 1 + tests/test_const.py | 1 - tests/test_util/__init__.py | 1 + .../testing_config/custom_components/test_embedded/__init__.py | 1 + .../custom_components/test_integration_platform/__init__.py | 1 + .../custom_components/test_integration_platform/const.py | 1 + tests/testing_config/custom_components/test_package/__init__.py | 1 + tests/testing_config/custom_components/test_package/const.py | 1 + .../custom_components/test_package_loaded_executor/__init__.py | 1 + .../custom_components/test_package_loaded_executor/const.py | 1 + .../test_package_raises_cancelled_error/__init__.py | 1 + .../__init__.py | 1 + tests/testing_config/custom_components/test_standalone.py | 1 + 345 files changed, 216 insertions(+), 130 deletions(-) diff --git a/homeassistant/auth/permissions/const.py b/homeassistant/auth/permissions/const.py index e6c44036a7e..2a49971954e 100644 --- a/homeassistant/auth/permissions/const.py +++ b/homeassistant/auth/permissions/const.py @@ -1,4 +1,5 @@ """Permission constants.""" + CAT_ENTITIES = "entities" CAT_CONFIG_ENTRIES = "config_entries" SUBCAT_ALL = "all" diff --git a/homeassistant/components/advantage_air/const.py b/homeassistant/components/advantage_air/const.py index 80ce9b6eaa1..6ae0a0e06d5 100644 --- a/homeassistant/components/advantage_air/const.py +++ b/homeassistant/components/advantage_air/const.py @@ -1,4 +1,5 @@ """Constants used by Advantage Air integration.""" + DOMAIN = "advantage_air" ADVANTAGE_AIR_RETRY = 10 ADVANTAGE_AIR_STATE_OPEN = "open" diff --git a/homeassistant/components/agent_dvr/const.py b/homeassistant/components/agent_dvr/const.py index e571edf9800..cd0284ca87c 100644 --- a/homeassistant/components/agent_dvr/const.py +++ b/homeassistant/components/agent_dvr/const.py @@ -1,4 +1,5 @@ """Constants for agent_dvr component.""" + DOMAIN = "agent_dvr" SERVERS = "servers" DEVICES = "devices" diff --git a/homeassistant/components/air_quality/group.py b/homeassistant/components/air_quality/group.py index 2ac081496cd..162457d336f 100644 --- a/homeassistant/components/air_quality/group.py +++ b/homeassistant/components/air_quality/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/airnow/const.py b/homeassistant/components/airnow/const.py index 137c8f1efad..c61136b3eeb 100644 --- a/homeassistant/components/airnow/const.py +++ b/homeassistant/components/airnow/const.py @@ -1,4 +1,5 @@ """Constants for the AirNow integration.""" + ATTR_API_AQI = "AQI" ATTR_API_AQI_LEVEL = "Category.Number" ATTR_API_AQI_DESCRIPTION = "Category.Name" diff --git a/homeassistant/components/alarm_control_panel/group.py b/homeassistant/components/alarm_control_panel/group.py index dabe49069d5..69a5e6d367e 100644 --- a/homeassistant/components/alarm_control_panel/group.py +++ b/homeassistant/components/alarm_control_panel/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index 3606c5401ee..b75e5597b6f 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -1,6 +1,5 @@ """Alexa Resources and Assets.""" - from typing import Any diff --git a/homeassistant/components/amberelectric/sensor.py b/homeassistant/components/amberelectric/sensor.py index 547b51a0f67..aafdd730a0c 100644 --- a/homeassistant/components/amberelectric/sensor.py +++ b/homeassistant/components/amberelectric/sensor.py @@ -4,7 +4,6 @@ # Current and forecast will create general, controlled load and feed in as required # At the moment renewables in the only grid sensor. - from __future__ import annotations from typing import Any diff --git a/homeassistant/components/amcrest/const.py b/homeassistant/components/amcrest/const.py index 6c2fe431d43..377c5642b4b 100644 --- a/homeassistant/components/amcrest/const.py +++ b/homeassistant/components/amcrest/const.py @@ -1,4 +1,5 @@ """Constants for amcrest component.""" + DOMAIN = "amcrest" DATA_AMCREST = DOMAIN CAMERAS = "cameras" diff --git a/homeassistant/components/androidtv/const.py b/homeassistant/components/androidtv/const.py index 17936421680..fb43e0af090 100644 --- a/homeassistant/components/androidtv/const.py +++ b/homeassistant/components/androidtv/const.py @@ -1,4 +1,5 @@ """Android Debug Bridge component constants.""" + DOMAIN = "androidtv" ANDROID_DEV = DOMAIN diff --git a/homeassistant/components/anthemav/const.py b/homeassistant/components/anthemav/const.py index 7cf586fb05d..8bcdd013a63 100644 --- a/homeassistant/components/anthemav/const.py +++ b/homeassistant/components/anthemav/const.py @@ -1,4 +1,5 @@ """Constants for the Anthem A/V Receivers integration.""" + ANTHEMAV_UPDATE_SIGNAL = "anthemav_update" DEFAULT_NAME = "Anthem AV" diff --git a/homeassistant/components/arcam_fmj/const.py b/homeassistant/components/arcam_fmj/const.py index e3c5ae3075a..94e8f5a9ee1 100644 --- a/homeassistant/components/arcam_fmj/const.py +++ b/homeassistant/components/arcam_fmj/const.py @@ -1,4 +1,5 @@ """Constants used for arcam.""" + DOMAIN = "arcam_fmj" SIGNAL_CLIENT_STARTED = "arcam.client_started" diff --git a/homeassistant/components/assist_pipeline/const.py b/homeassistant/components/assist_pipeline/const.py index ef1ed1177a6..3463d94fb84 100644 --- a/homeassistant/components/assist_pipeline/const.py +++ b/homeassistant/components/assist_pipeline/const.py @@ -1,4 +1,5 @@ """Constants for the Assist pipeline integration.""" + DOMAIN = "assist_pipeline" DATA_CONFIG = f"{DOMAIN}.config" diff --git a/homeassistant/components/asuswrt/const.py b/homeassistant/components/asuswrt/const.py index a60046b50c2..d31d986574e 100644 --- a/homeassistant/components/asuswrt/const.py +++ b/homeassistant/components/asuswrt/const.py @@ -1,4 +1,5 @@ """AsusWrt component constants.""" + DOMAIN = "asuswrt" CONF_DNSMASQ = "dnsmasq" diff --git a/homeassistant/components/aussie_broadband/const.py b/homeassistant/components/aussie_broadband/const.py index 5747ee86fea..ad19b7d8a27 100644 --- a/homeassistant/components/aussie_broadband/const.py +++ b/homeassistant/components/aussie_broadband/const.py @@ -1,4 +1,5 @@ """Constants for the Aussie Broadband integration.""" + DEFAULT_UPDATE_INTERVAL = 30 DOMAIN = "aussie_broadband" SERVICE_ID = "service_id" diff --git a/homeassistant/components/aws/const.py b/homeassistant/components/aws/const.py index 8be6afec7ff..c885495934f 100644 --- a/homeassistant/components/aws/const.py +++ b/homeassistant/components/aws/const.py @@ -1,4 +1,5 @@ """Constant for AWS component.""" + DOMAIN = "aws" DATA_CONFIG = "aws_config" diff --git a/homeassistant/components/azure_devops/const.py b/homeassistant/components/azure_devops/const.py index adaf5ebe767..a5bbe9dac54 100644 --- a/homeassistant/components/azure_devops/const.py +++ b/homeassistant/components/azure_devops/const.py @@ -1,4 +1,5 @@ """Constants for the Azure DevOps integration.""" + DOMAIN = "azure_devops" CONF_ORG = "organization" diff --git a/homeassistant/components/balboa/const.py b/homeassistant/components/balboa/const.py index 23189a4d7e9..1536959f8be 100644 --- a/homeassistant/components/balboa/const.py +++ b/homeassistant/components/balboa/const.py @@ -1,4 +1,5 @@ """Constants for the Balboa Spa Client integration.""" + DOMAIN = "balboa" CONF_SYNC_TIME = "sync_time" DEFAULT_SYNC_TIME = False diff --git a/homeassistant/components/binary_sensor/group.py b/homeassistant/components/binary_sensor/group.py index 234883ffd5a..e1a0b18dd08 100644 --- a/homeassistant/components/binary_sensor/group.py +++ b/homeassistant/components/binary_sensor/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/blackbird/const.py b/homeassistant/components/blackbird/const.py index aa8d7e7d514..27a4f1dcffb 100644 --- a/homeassistant/components/blackbird/const.py +++ b/homeassistant/components/blackbird/const.py @@ -1,3 +1,4 @@ """Constants for the Monoprice Blackbird Matrix Switch component.""" + DOMAIN = "blackbird" SERVICE_SETALLZONES = "set_all_zones" diff --git a/homeassistant/components/blebox/const.py b/homeassistant/components/blebox/const.py index 533c33f37bb..ff6a6b33af6 100644 --- a/homeassistant/components/blebox/const.py +++ b/homeassistant/components/blebox/const.py @@ -1,6 +1,5 @@ """Constants for the BleBox devices integration.""" - DOMAIN = "blebox" PRODUCT = "product" diff --git a/homeassistant/components/blueprint/const.py b/homeassistant/components/blueprint/const.py index 1ca6117f153..18433aa6ba6 100644 --- a/homeassistant/components/blueprint/const.py +++ b/homeassistant/components/blueprint/const.py @@ -1,4 +1,5 @@ """Constants for the blueprint integration.""" + BLUEPRINT_FOLDER = "blueprints" CONF_BLUEPRINT = "blueprint" diff --git a/homeassistant/components/bluesound/const.py b/homeassistant/components/bluesound/const.py index af1a8e5187c..ae5291c6513 100644 --- a/homeassistant/components/bluesound/const.py +++ b/homeassistant/components/bluesound/const.py @@ -1,4 +1,5 @@ """Constants for the Bluesound HiFi wireless speakers and audio integrations component.""" + DOMAIN = "bluesound" SERVICE_CLEAR_TIMER = "clear_sleep_timer" SERVICE_JOIN = "join" diff --git a/homeassistant/components/caldav/api.py b/homeassistant/components/caldav/api.py index 3b524e29370..a48336331ca 100644 --- a/homeassistant/components/caldav/api.py +++ b/homeassistant/components/caldav/api.py @@ -1,6 +1,5 @@ """Library for working with CalDAV api.""" - import caldav from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/channels/const.py b/homeassistant/components/channels/const.py index 5ae7fdebb0b..d05848d40f4 100644 --- a/homeassistant/components/channels/const.py +++ b/homeassistant/components/channels/const.py @@ -1,4 +1,5 @@ """Constants for the Channels component.""" + DOMAIN = "channels" SERVICE_SEEK_FORWARD = "seek_forward" SERVICE_SEEK_BACKWARD = "seek_backward" diff --git a/homeassistant/components/climate/group.py b/homeassistant/components/climate/group.py index cec41f81b28..45d6a9b5fcb 100644 --- a/homeassistant/components/climate/group.py +++ b/homeassistant/components/climate/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/co2signal/const.py b/homeassistant/components/co2signal/const.py index b025c655ce6..34ddbdc05e5 100644 --- a/homeassistant/components/co2signal/const.py +++ b/homeassistant/components/co2signal/const.py @@ -1,5 +1,4 @@ """Constants for the Co2signal integration.""" - DOMAIN = "co2signal" ATTRIBUTION = "Data provided by Electricity Maps" diff --git a/homeassistant/components/color_extractor/const.py b/homeassistant/components/color_extractor/const.py index e783dcb533d..25c15ed9dc0 100644 --- a/homeassistant/components/color_extractor/const.py +++ b/homeassistant/components/color_extractor/const.py @@ -1,4 +1,5 @@ """Constants for the color_extractor component.""" + ATTR_PATH = "color_extract_path" ATTR_URL = "color_extract_url" diff --git a/homeassistant/components/comelit/__init__.py b/homeassistant/components/comelit/__init__.py index 2cf7a145eee..478be85c1d4 100644 --- a/homeassistant/components/comelit/__init__.py +++ b/homeassistant/components/comelit/__init__.py @@ -1,6 +1,5 @@ """Comelit integration.""" - from aiocomelit.const import BRIDGE from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/compensation/const.py b/homeassistant/components/compensation/const.py index d49a6982166..ce959469700 100644 --- a/homeassistant/components/compensation/const.py +++ b/homeassistant/components/compensation/const.py @@ -1,4 +1,5 @@ """Compensation constants.""" + DOMAIN = "compensation" SENSOR = "compensation" diff --git a/homeassistant/components/cover/group.py b/homeassistant/components/cover/group.py index 28a1dc530fe..78335286d10 100644 --- a/homeassistant/components/cover/group.py +++ b/homeassistant/components/cover/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_CLOSED, STATE_OPEN from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/cover/intent.py b/homeassistant/components/cover/intent.py index dc8f722c7ed..a77bfbcbd16 100644 --- a/homeassistant/components/cover/intent.py +++ b/homeassistant/components/cover/intent.py @@ -1,6 +1,5 @@ """Intents for the cover integration.""" - from homeassistant.const import SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER from homeassistant.core import HomeAssistant from homeassistant.helpers import intent diff --git a/homeassistant/components/daikin/const.py b/homeassistant/components/daikin/const.py index 67d014ecdb3..690267e5c83 100644 --- a/homeassistant/components/daikin/const.py +++ b/homeassistant/components/daikin/const.py @@ -1,4 +1,5 @@ """Constants for Daikin.""" + DOMAIN = "daikin" ATTR_TARGET_TEMPERATURE = "target_temperature" diff --git a/homeassistant/components/demo/const.py b/homeassistant/components/demo/const.py index e11b0b0731a..cd964af5f3d 100644 --- a/homeassistant/components/demo/const.py +++ b/homeassistant/components/demo/const.py @@ -1,3 +1,4 @@ """Constants for the Demo component.""" + DOMAIN = "demo" SERVICE_RANDOMIZE_DEVICE_TRACKER_DATA = "randomize_device_tracker_data" diff --git a/homeassistant/components/device_automation/const.py b/homeassistant/components/device_automation/const.py index 2970e5e79b2..5cbc0378c41 100644 --- a/homeassistant/components/device_automation/const.py +++ b/homeassistant/components/device_automation/const.py @@ -1,4 +1,5 @@ """Constants for device automations.""" + CONF_CHANGED_STATES = "changed_states" CONF_IS_OFF = "is_off" CONF_IS_ON = "is_on" diff --git a/homeassistant/components/device_tracker/group.py b/homeassistant/components/device_tracker/group.py index 9bd2c991678..97e9556eaeb 100644 --- a/homeassistant/components/device_tracker/group.py +++ b/homeassistant/components/device_tracker/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/directv/const.py b/homeassistant/components/directv/const.py index e90fd6879c7..fe9aba3b4f4 100644 --- a/homeassistant/components/directv/const.py +++ b/homeassistant/components/directv/const.py @@ -1,4 +1,5 @@ """Constants for the DirecTV integration.""" + DOMAIN = "directv" # Attributes diff --git a/homeassistant/components/doorbird/entity.py b/homeassistant/components/doorbird/entity.py index 4360a8ff490..f09ef54d5ac 100644 --- a/homeassistant/components/doorbird/entity.py +++ b/homeassistant/components/doorbird/entity.py @@ -1,6 +1,5 @@ """The DoorBird integration base entity.""" - from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/ecoforest/coordinator.py b/homeassistant/components/ecoforest/coordinator.py index b44ccc850ce..ae0b353a1df 100644 --- a/homeassistant/components/ecoforest/coordinator.py +++ b/homeassistant/components/ecoforest/coordinator.py @@ -1,6 +1,5 @@ """The ecoforest coordinator.""" - import logging from pyecoforest.api import EcoforestApi diff --git a/homeassistant/components/emulated_roku/const.py b/homeassistant/components/emulated_roku/const.py index 6c780367188..b8a70596048 100644 --- a/homeassistant/components/emulated_roku/const.py +++ b/homeassistant/components/emulated_roku/const.py @@ -1,4 +1,5 @@ """Constants for the emulated_roku component.""" + DOMAIN = "emulated_roku" CONF_SERVERS = "servers" diff --git a/homeassistant/components/enigma2/const.py b/homeassistant/components/enigma2/const.py index 0511a794172..277efad50eb 100644 --- a/homeassistant/components/enigma2/const.py +++ b/homeassistant/components/enigma2/const.py @@ -1,4 +1,5 @@ """Constants for the Enigma2 platform.""" + DOMAIN = "enigma2" CONF_USE_CHANNEL_ICON = "use_channel_icon" diff --git a/homeassistant/components/evohome/const.py b/homeassistant/components/evohome/const.py index 6bd3a59c225..1347c1f797c 100644 --- a/homeassistant/components/evohome/const.py +++ b/homeassistant/components/evohome/const.py @@ -1,4 +1,5 @@ """Support for (EMEA/EU-based) Honeywell TCC climate systems.""" + DOMAIN = "evohome" STORAGE_VER = 1 diff --git a/homeassistant/components/fan/group.py b/homeassistant/components/fan/group.py index 234883ffd5a..e1a0b18dd08 100644 --- a/homeassistant/components/fan/group.py +++ b/homeassistant/components/fan/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/fitbit/__init__.py b/homeassistant/components/fitbit/__init__.py index 40ea9fb1152..22d0e302d63 100644 --- a/homeassistant/components/fitbit/__init__.py +++ b/homeassistant/components/fitbit/__init__.py @@ -1,6 +1,5 @@ """The fitbit component.""" - from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/frontier_silicon/const.py b/homeassistant/components/frontier_silicon/const.py index 94f4e09a35a..ce0126aa803 100644 --- a/homeassistant/components/frontier_silicon/const.py +++ b/homeassistant/components/frontier_silicon/const.py @@ -1,4 +1,5 @@ """Constants for the Frontier Silicon Media Player integration.""" + DOMAIN = "frontier_silicon" CONF_WEBFSAPI_URL = "webfsapi_url" diff --git a/homeassistant/components/google_travel_time/const.py b/homeassistant/components/google_travel_time/const.py index 041858d948f..7e086640e2b 100644 --- a/homeassistant/components/google_travel_time/const.py +++ b/homeassistant/components/google_travel_time/const.py @@ -1,4 +1,5 @@ """Constants for Google Travel Time.""" + DOMAIN = "google_travel_time" ATTRIBUTION = "Powered by Google" diff --git a/homeassistant/components/html5/const.py b/homeassistant/components/html5/const.py index 1d0689511b2..bf7eaca7e24 100644 --- a/homeassistant/components/html5/const.py +++ b/homeassistant/components/html5/const.py @@ -1,3 +1,4 @@ """Constants for the HTML5 component.""" + DOMAIN = "html5" SERVICE_DISMISS = "dismiss" diff --git a/homeassistant/components/humidifier/group.py b/homeassistant/components/humidifier/group.py index 234883ffd5a..e1a0b18dd08 100644 --- a/homeassistant/components/humidifier/group.py +++ b/homeassistant/components/humidifier/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py index a2d18c6f512..ec55d413416 100644 --- a/homeassistant/components/hunterdouglas_powerview/const.py +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -1,6 +1,5 @@ """Constants for Hunter Douglas Powerview hub.""" - from aiohttp.client_exceptions import ServerDisconnectedError from aiopvapi.helpers.aiorequest import ( PvApiConnectionError, diff --git a/homeassistant/components/hydrawise/__init__.py b/homeassistant/components/hydrawise/__init__.py index 9f44d47ecf6..541d4211e49 100644 --- a/homeassistant/components/hydrawise/__init__.py +++ b/homeassistant/components/hydrawise/__init__.py @@ -1,6 +1,5 @@ """Support for Hydrawise cloud.""" - from pydrawise import legacy import voluptuous as vol diff --git a/homeassistant/components/idasen_desk/const.py b/homeassistant/components/idasen_desk/const.py index 0d37d77307b..26d673dde37 100644 --- a/homeassistant/components/idasen_desk/const.py +++ b/homeassistant/components/idasen_desk/const.py @@ -1,6 +1,5 @@ """Constants for the Idasen Desk integration.""" - DOMAIN = "idasen_desk" EXPECTED_SERVICE_UUID = "99fa0001-338a-1024-8a49-009c0215f78a" diff --git a/homeassistant/components/justnimbus/const.py b/homeassistant/components/justnimbus/const.py index 11a4ae487c4..cf2d099becd 100644 --- a/homeassistant/components/justnimbus/const.py +++ b/homeassistant/components/justnimbus/const.py @@ -1,6 +1,5 @@ """Constants for the JustNimbus integration.""" - from typing import Final from homeassistant.const import Platform diff --git a/homeassistant/components/keymitt_ble/const.py b/homeassistant/components/keymitt_ble/const.py index a10e7124226..9d4be4fa278 100644 --- a/homeassistant/components/keymitt_ble/const.py +++ b/homeassistant/components/keymitt_ble/const.py @@ -1,4 +1,5 @@ """Constants for Keymitt BLE.""" + # Base component constants DOMAIN = "keymitt_ble" MANUFACTURER = "Naran/Keymitt" diff --git a/homeassistant/components/kodi/const.py b/homeassistant/components/kodi/const.py index 8f0ae5de737..479b02e0fb5 100644 --- a/homeassistant/components/kodi/const.py +++ b/homeassistant/components/kodi/const.py @@ -1,4 +1,5 @@ """Constants for the Kodi platform.""" + DOMAIN = "kodi" CONF_WS_PORT = "ws_port" diff --git a/homeassistant/components/kostal_plenticore/const.py b/homeassistant/components/kostal_plenticore/const.py index c0e897e6131..668b10e6971 100644 --- a/homeassistant/components/kostal_plenticore/const.py +++ b/homeassistant/components/kostal_plenticore/const.py @@ -1,2 +1,3 @@ """Constants for the Kostal Plenticore Solar Inverter integration.""" + DOMAIN = "kostal_plenticore" diff --git a/homeassistant/components/kulersky/const.py b/homeassistant/components/kulersky/const.py index 8b314d7bde9..8d0b4380bb3 100644 --- a/homeassistant/components/kulersky/const.py +++ b/homeassistant/components/kulersky/const.py @@ -1,4 +1,5 @@ """Constants for the Kuler Sky integration.""" + DOMAIN = "kulersky" DATA_ADDRESSES = "addresses" diff --git a/homeassistant/components/lamarzocco/diagnostics.py b/homeassistant/components/lamarzocco/diagnostics.py index 6e75152bd60..648d1357a35 100644 --- a/homeassistant/components/lamarzocco/diagnostics.py +++ b/homeassistant/components/lamarzocco/diagnostics.py @@ -1,6 +1,5 @@ """Diagnostics support for La Marzocco.""" - from __future__ import annotations from typing import Any diff --git a/homeassistant/components/ld2410_ble/binary_sensor.py b/homeassistant/components/ld2410_ble/binary_sensor.py index cca87de7a60..c52bc34b699 100644 --- a/homeassistant/components/ld2410_ble/binary_sensor.py +++ b/homeassistant/components/ld2410_ble/binary_sensor.py @@ -1,6 +1,5 @@ """LD2410 BLE integration binary sensor platform.""" - from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, diff --git a/homeassistant/components/ld2410_ble/sensor.py b/homeassistant/components/ld2410_ble/sensor.py index 5bd4a0d4d2d..933a2151a17 100644 --- a/homeassistant/components/ld2410_ble/sensor.py +++ b/homeassistant/components/ld2410_ble/sensor.py @@ -1,6 +1,5 @@ """LD2410 BLE integration sensor platform.""" - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, diff --git a/homeassistant/components/lg_netcast/const.py b/homeassistant/components/lg_netcast/const.py index 6cb44a5a3f8..0344ad6f177 100644 --- a/homeassistant/components/lg_netcast/const.py +++ b/homeassistant/components/lg_netcast/const.py @@ -1,2 +1,3 @@ """Constants for the lg_netcast component.""" + DOMAIN = "lg_netcast" diff --git a/homeassistant/components/lg_soundbar/const.py b/homeassistant/components/lg_soundbar/const.py index c71e43c0d60..2ac0602568b 100644 --- a/homeassistant/components/lg_soundbar/const.py +++ b/homeassistant/components/lg_soundbar/const.py @@ -1,4 +1,5 @@ """Constants for the LG Soundbar integration.""" + DOMAIN = "lg_soundbar" DEFAULT_PORT = 9741 diff --git a/homeassistant/components/light/group.py b/homeassistant/components/light/group.py index 234883ffd5a..e1a0b18dd08 100644 --- a/homeassistant/components/light/group.py +++ b/homeassistant/components/light/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/litterrobot/const.py b/homeassistant/components/litterrobot/const.py index 5ac889d9b73..632465b902d 100644 --- a/homeassistant/components/litterrobot/const.py +++ b/homeassistant/components/litterrobot/const.py @@ -1,2 +1,3 @@ """Constants for the Litter-Robot integration.""" + DOMAIN = "litterrobot" diff --git a/homeassistant/components/local_file/const.py b/homeassistant/components/local_file/const.py index 3ea98f89c0e..03f6cf641f1 100644 --- a/homeassistant/components/local_file/const.py +++ b/homeassistant/components/local_file/const.py @@ -1,4 +1,5 @@ """Constants for the Local File Camera component.""" + DOMAIN = "local_file" SERVICE_UPDATE_FILE_PATH = "update_file_path" DATA_LOCAL_FILE = "local_file_cameras" diff --git a/homeassistant/components/lock/group.py b/homeassistant/components/lock/group.py index 9cf460dc019..03e576af307 100644 --- a/homeassistant/components/lock/group.py +++ b/homeassistant/components/lock/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/loqed/const.py b/homeassistant/components/loqed/const.py index 59011f26566..262009e20ae 100644 --- a/homeassistant/components/loqed/const.py +++ b/homeassistant/components/loqed/const.py @@ -1,5 +1,4 @@ """Constants for the loqed integration.""" - DOMAIN = "loqed" CONF_CLOUDHOOK_URL = "cloudhook_url" diff --git a/homeassistant/components/matrix/const.py b/homeassistant/components/matrix/const.py index b7e0c22e2ac..bae53f05727 100644 --- a/homeassistant/components/matrix/const.py +++ b/homeassistant/components/matrix/const.py @@ -1,4 +1,5 @@ """Constants for the Matrix integration.""" + DOMAIN = "matrix" SERVICE_SEND_MESSAGE = "send_message" diff --git a/homeassistant/components/media_player/group.py b/homeassistant/components/media_player/group.py index b7b2efb55c8..2194f71edf4 100644 --- a/homeassistant/components/media_player/group.py +++ b/homeassistant/components/media_player/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import ( STATE_IDLE, diff --git a/homeassistant/components/melnor/const.py b/homeassistant/components/melnor/const.py index cadf9c0a618..be6931799af 100644 --- a/homeassistant/components/melnor/const.py +++ b/homeassistant/components/melnor/const.py @@ -1,6 +1,5 @@ """Constants for the melnor integration.""" - DOMAIN = "melnor" DEFAULT_NAME = "Melnor Bluetooth" diff --git a/homeassistant/components/minecraft_server/api.py b/homeassistant/components/minecraft_server/api.py index d86f8453413..7b0d7475f84 100644 --- a/homeassistant/components/minecraft_server/api.py +++ b/homeassistant/components/minecraft_server/api.py @@ -1,6 +1,5 @@ """API for the Minecraft Server integration.""" - from dataclasses import dataclass from enum import StrEnum import logging diff --git a/homeassistant/components/mystrom/const.py b/homeassistant/components/mystrom/const.py index 5641463abf1..0e576d47438 100644 --- a/homeassistant/components/mystrom/const.py +++ b/homeassistant/components/mystrom/const.py @@ -1,4 +1,5 @@ """Constants for the myStrom integration.""" + DOMAIN = "mystrom" DEFAULT_NAME = "myStrom" MANUFACTURER = "myStrom" diff --git a/homeassistant/components/myuplink/number.py b/homeassistant/components/myuplink/number.py index ddfcdb109d4..55662fb57fb 100644 --- a/homeassistant/components/myuplink/number.py +++ b/homeassistant/components/myuplink/number.py @@ -1,6 +1,5 @@ """Number entity for myUplink.""" - from aiohttp import ClientError from myuplink import DevicePoint diff --git a/homeassistant/components/nextbus/const.py b/homeassistant/components/nextbus/const.py index 0a2eabf57b3..aa125193031 100644 --- a/homeassistant/components/nextbus/const.py +++ b/homeassistant/components/nextbus/const.py @@ -1,4 +1,5 @@ """NextBus Constants.""" + DOMAIN = "nextbus" CONF_AGENCY = "agency" diff --git a/homeassistant/components/nfandroidtv/const.py b/homeassistant/components/nfandroidtv/const.py index 4d4a7c82ecb..cd4b99d0981 100644 --- a/homeassistant/components/nfandroidtv/const.py +++ b/homeassistant/components/nfandroidtv/const.py @@ -1,4 +1,5 @@ """Constants for the NFAndroidTV integration.""" + DOMAIN: str = "nfandroidtv" CONF_DURATION = "duration" CONF_FONTSIZE = "fontsize" diff --git a/homeassistant/components/nuki/const.py b/homeassistant/components/nuki/const.py index 21a2dcf9e5b..28975f37432 100644 --- a/homeassistant/components/nuki/const.py +++ b/homeassistant/components/nuki/const.py @@ -1,4 +1,5 @@ """Constants for Nuki.""" + DOMAIN = "nuki" # Attributes diff --git a/homeassistant/components/nzbget/const.py b/homeassistant/components/nzbget/const.py index 7838d64c6d7..6742567bbf2 100644 --- a/homeassistant/components/nzbget/const.py +++ b/homeassistant/components/nzbget/const.py @@ -1,4 +1,5 @@ """Constants for NZBGet.""" + DOMAIN = "nzbget" # Attributes diff --git a/homeassistant/components/onboarding/const.py b/homeassistant/components/onboarding/const.py index 5a771f524ac..7ccfc13774d 100644 --- a/homeassistant/components/onboarding/const.py +++ b/homeassistant/components/onboarding/const.py @@ -1,4 +1,5 @@ """Constants for the onboarding component.""" + DOMAIN = "onboarding" STEP_USER = "user" STEP_CORE_CONFIG = "core_config" diff --git a/homeassistant/components/oncue/const.py b/homeassistant/components/oncue/const.py index 599ef5ee22b..bc14133b0d3 100644 --- a/homeassistant/components/oncue/const.py +++ b/homeassistant/components/oncue/const.py @@ -1,6 +1,5 @@ """Constants for the Oncue integration.""" - import aiohttp from aiooncue import ServiceFailedException diff --git a/homeassistant/components/openhome/const.py b/homeassistant/components/openhome/const.py index 09fcd2ef0e2..4c9925df19e 100644 --- a/homeassistant/components/openhome/const.py +++ b/homeassistant/components/openhome/const.py @@ -1,4 +1,5 @@ """Constants for the Openhome component.""" + DOMAIN = "openhome" SERVICE_INVOKE_PIN = "invoke_pin" ATTR_PIN_INDEX = "pin" diff --git a/homeassistant/components/ovo_energy/const.py b/homeassistant/components/ovo_energy/const.py index 1068c5443fd..2d615e7c44a 100644 --- a/homeassistant/components/ovo_energy/const.py +++ b/homeassistant/components/ovo_energy/const.py @@ -1,4 +1,5 @@ """Constants for the OVO Energy integration.""" + DOMAIN = "ovo_energy" DATA_CLIENT = "ovo_client" diff --git a/homeassistant/components/owntracks/helper.py b/homeassistant/components/owntracks/helper.py index a9499059ba0..f88dcb03864 100644 --- a/homeassistant/components/owntracks/helper.py +++ b/homeassistant/components/owntracks/helper.py @@ -1,4 +1,5 @@ """Helper for OwnTracks.""" + try: import nacl except ImportError: diff --git a/homeassistant/components/panasonic_viera/const.py b/homeassistant/components/panasonic_viera/const.py index a2e3fc2eece..f76c01e396b 100644 --- a/homeassistant/components/panasonic_viera/const.py +++ b/homeassistant/components/panasonic_viera/const.py @@ -1,4 +1,5 @@ """Constants for the Panasonic Viera integration.""" + DOMAIN = "panasonic_viera" DEVICE_MANUFACTURER = "Panasonic" diff --git a/homeassistant/components/person/group.py b/homeassistant/components/person/group.py index 9bd2c991678..97e9556eaeb 100644 --- a/homeassistant/components/person/group.py +++ b/homeassistant/components/person/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/plant/group.py b/homeassistant/components/plant/group.py index 90e894abb0f..0a29f748a76 100644 --- a/homeassistant/components/plant/group.py +++ b/homeassistant/components/plant/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OK, STATE_PROBLEM from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/private_ble_device/const.py b/homeassistant/components/private_ble_device/const.py index 086fd06bfd5..4eaf862ca75 100644 --- a/homeassistant/components/private_ble_device/const.py +++ b/homeassistant/components/private_ble_device/const.py @@ -1,2 +1,3 @@ """Constants for Private BLE Device.""" + DOMAIN = "private_ble_device" diff --git a/homeassistant/components/ps4/const.py b/homeassistant/components/ps4/const.py index 20b4feda5fa..bd1144c4d98 100644 --- a/homeassistant/components/ps4/const.py +++ b/homeassistant/components/ps4/const.py @@ -1,4 +1,5 @@ """Constants for PlayStation 4.""" + ATTR_MEDIA_IMAGE_URL = "media_image_url" CONFIG_ENTRY_VERSION = 3 DEFAULT_NAME = "PlayStation 4" diff --git a/homeassistant/components/rainforest_raven/const.py b/homeassistant/components/rainforest_raven/const.py index a5269ddbc26..ca46f0303cc 100644 --- a/homeassistant/components/rainforest_raven/const.py +++ b/homeassistant/components/rainforest_raven/const.py @@ -1,3 +1,4 @@ """Constants for the Rainforest RAVEn integration.""" + DEFAULT_NAME = "Rainforest RAVEn" DOMAIN = "rainforest_raven" diff --git a/homeassistant/components/random/const.py b/homeassistant/components/random/const.py index df6a18f8d11..a35ce4315a4 100644 --- a/homeassistant/components/random/const.py +++ b/homeassistant/components/random/const.py @@ -1,4 +1,5 @@ """Constants for random helper.""" + DOMAIN = "random" DEFAULT_MIN = 0 diff --git a/homeassistant/components/recorder/history/const.py b/homeassistant/components/recorder/history/const.py index 61a615a7979..f5286a3ff3d 100644 --- a/homeassistant/components/recorder/history/const.py +++ b/homeassistant/components/recorder/history/const.py @@ -1,6 +1,5 @@ """Constants for history.""" - STATE_KEY = "state" LAST_CHANGED_KEY = "last_changed" diff --git a/homeassistant/components/remote/group.py b/homeassistant/components/remote/group.py index 234883ffd5a..e1a0b18dd08 100644 --- a/homeassistant/components/remote/group.py +++ b/homeassistant/components/remote/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/renson/const.py b/homeassistant/components/renson/const.py index 53bbd90c4b7..840e1ce428a 100644 --- a/homeassistant/components/renson/const.py +++ b/homeassistant/components/renson/const.py @@ -1,4 +1,3 @@ """Constants for the Renson integration.""" - DOMAIN = "renson" diff --git a/homeassistant/components/rfxtrx/helpers.py b/homeassistant/components/rfxtrx/helpers.py index cfc16126359..184d814a6cc 100644 --- a/homeassistant/components/rfxtrx/helpers.py +++ b/homeassistant/components/rfxtrx/helpers.py @@ -1,6 +1,5 @@ """Provides helpers for RFXtrx.""" - from RFXtrx import RFXtrxDevice, get_device from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/roku/const.py b/homeassistant/components/roku/const.py index f098483e0c6..ab633a4044c 100644 --- a/homeassistant/components/roku/const.py +++ b/homeassistant/components/roku/const.py @@ -1,4 +1,5 @@ """Constants for the Roku integration.""" + DOMAIN = "roku" # Attributes diff --git a/homeassistant/components/romy/vacuum.py b/homeassistant/components/romy/vacuum.py index f352f528c58..90189da7e2f 100644 --- a/homeassistant/components/romy/vacuum.py +++ b/homeassistant/components/romy/vacuum.py @@ -4,7 +4,6 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/vacuum.romy/. """ - from typing import Any from romy import RomyRobot diff --git a/homeassistant/components/sensor/group.py b/homeassistant/components/sensor/group.py index 2ac081496cd..162457d336f 100644 --- a/homeassistant/components/sensor/group.py +++ b/homeassistant/components/sensor/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/slimproto/const.py b/homeassistant/components/slimproto/const.py index 3b85de5d794..e8ef4fbd148 100644 --- a/homeassistant/components/slimproto/const.py +++ b/homeassistant/components/slimproto/const.py @@ -1,6 +1,5 @@ """Constants for SlimProto Player integration.""" - DOMAIN = "slimproto" DEFAULT_NAME = "SlimProto Player" diff --git a/homeassistant/components/snmp/const.py b/homeassistant/components/snmp/const.py index e51bbc33b90..842c0eaec9e 100644 --- a/homeassistant/components/snmp/const.py +++ b/homeassistant/components/snmp/const.py @@ -1,4 +1,5 @@ """SNMP constants.""" + CONF_ACCEPT_ERRORS = "accept_errors" CONF_AUTH_KEY = "auth_key" CONF_AUTH_PROTOCOL = "auth_protocol" diff --git a/homeassistant/components/solax/const.py b/homeassistant/components/solax/const.py index 65894013adc..26d5962e385 100644 --- a/homeassistant/components/solax/const.py +++ b/homeassistant/components/solax/const.py @@ -1,6 +1,5 @@ """Constants for the solax integration.""" - DOMAIN = "solax" MANUFACTURER = "SolaX Power" diff --git a/homeassistant/components/songpal/const.py b/homeassistant/components/songpal/const.py index 496618f35f0..04b005e2a09 100644 --- a/homeassistant/components/songpal/const.py +++ b/homeassistant/components/songpal/const.py @@ -1,4 +1,5 @@ """Constants for the Songpal component.""" + DOMAIN = "songpal" SET_SOUND_SETTING = "set_sound_setting" diff --git a/homeassistant/components/soundtouch/const.py b/homeassistant/components/soundtouch/const.py index a6b2b3c9f5f..ef9d7e71bf9 100644 --- a/homeassistant/components/soundtouch/const.py +++ b/homeassistant/components/soundtouch/const.py @@ -1,4 +1,5 @@ """Constants for the Bose SoundTouch component.""" + DOMAIN = "soundtouch" SERVICE_PLAY_EVERYWHERE = "play_everywhere" SERVICE_CREATE_ZONE = "create_zone" diff --git a/homeassistant/components/squeezebox/const.py b/homeassistant/components/squeezebox/const.py index 38a9ef7668f..96a541a16ba 100644 --- a/homeassistant/components/squeezebox/const.py +++ b/homeassistant/components/squeezebox/const.py @@ -1,4 +1,5 @@ """Constants for the Squeezebox component.""" + DOMAIN = "squeezebox" ENTRY_PLAYERS = "entry_players" KNOWN_PLAYERS = "known_players" diff --git a/homeassistant/components/steamist/const.py b/homeassistant/components/steamist/const.py index ae75193a3cc..d505e534f04 100644 --- a/homeassistant/components/steamist/const.py +++ b/homeassistant/components/steamist/const.py @@ -1,6 +1,5 @@ """Constants for the Steamist integration.""" - import aiohttp DOMAIN = "steamist" diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index eb954a6a8f5..a2fa065e019 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -1,4 +1,5 @@ """Constants for Stream component.""" + DOMAIN = "stream" ATTR_ENDPOINTS = "endpoints" diff --git a/homeassistant/components/surepetcare/const.py b/homeassistant/components/surepetcare/const.py index 6617137b026..e0940b41002 100644 --- a/homeassistant/components/surepetcare/const.py +++ b/homeassistant/components/surepetcare/const.py @@ -1,4 +1,5 @@ """Constants for the Sure Petcare component.""" + DOMAIN = "surepetcare" CONF_FEEDERS = "feeders" diff --git a/homeassistant/components/switch/group.py b/homeassistant/components/switch/group.py index 234883ffd5a..e1a0b18dd08 100644 --- a/homeassistant/components/switch/group.py +++ b/homeassistant/components/switch/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/switcher_kis/const.py b/homeassistant/components/switcher_kis/const.py index fdd5b02fe9b..248b7afbc81 100644 --- a/homeassistant/components/switcher_kis/const.py +++ b/homeassistant/components/switcher_kis/const.py @@ -1,4 +1,5 @@ """Constants for the Switcher integration.""" + DOMAIN = "switcher_kis" CONF_DEVICE_PASSWORD = "device_password" diff --git a/homeassistant/components/tami4/const.py b/homeassistant/components/tami4/const.py index 4e64bdf896d..be737b5c974 100644 --- a/homeassistant/components/tami4/const.py +++ b/homeassistant/components/tami4/const.py @@ -1,4 +1,5 @@ """Constants for tami4 component.""" + DOMAIN = "tami4" CONF_PHONE = "phone" CONF_REFRESH_TOKEN = "refresh_token" diff --git a/homeassistant/components/trend/const.py b/homeassistant/components/trend/const.py index 838056bfc4d..5c1c5b9629b 100644 --- a/homeassistant/components/trend/const.py +++ b/homeassistant/components/trend/const.py @@ -1,4 +1,5 @@ """Constant values for Trend integration.""" + DOMAIN = "trend" ATTR_ATTRIBUTE = "attribute" diff --git a/homeassistant/components/tts/const.py b/homeassistant/components/tts/const.py index f721731330c..99015512498 100644 --- a/homeassistant/components/tts/const.py +++ b/homeassistant/components/tts/const.py @@ -1,4 +1,5 @@ """Text-to-speech constants.""" + ATTR_CACHE = "cache" ATTR_LANGUAGE = "language" ATTR_MESSAGE = "message" diff --git a/homeassistant/components/twinkly/__init__.py b/homeassistant/components/twinkly/__init__.py index 3b47a10d499..b09e58ff12f 100644 --- a/homeassistant/components/twinkly/__init__.py +++ b/homeassistant/components/twinkly/__init__.py @@ -1,6 +1,5 @@ """The twinkly component.""" - from aiohttp import ClientError from ttls.client import Twinkly diff --git a/homeassistant/components/utility_meter/const.py b/homeassistant/components/utility_meter/const.py index 4f62925069d..49799ba1e67 100644 --- a/homeassistant/components/utility_meter/const.py +++ b/homeassistant/components/utility_meter/const.py @@ -1,4 +1,5 @@ """Constants for the utility meter component.""" + DOMAIN = "utility_meter" QUARTER_HOURLY = "quarter-hourly" diff --git a/homeassistant/components/vacuum/group.py b/homeassistant/components/vacuum/group.py index e5a1734420f..71aecfbabc5 100644 --- a/homeassistant/components/vacuum/group.py +++ b/homeassistant/components/vacuum/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/vacuum/intent.py b/homeassistant/components/vacuum/intent.py index c485686aa23..534078ec8af 100644 --- a/homeassistant/components/vacuum/intent.py +++ b/homeassistant/components/vacuum/intent.py @@ -1,6 +1,5 @@ """Intents for the vacuum integration.""" - from homeassistant.core import HomeAssistant from homeassistant.helpers import intent diff --git a/homeassistant/components/vera/const.py b/homeassistant/components/vera/const.py index 34ac7faa669..0eb7534a81d 100644 --- a/homeassistant/components/vera/const.py +++ b/homeassistant/components/vera/const.py @@ -1,4 +1,5 @@ """Vera constants.""" + DOMAIN = "vera" CONF_CONTROLLER = "vera_controller_url" diff --git a/homeassistant/components/versasense/const.py b/homeassistant/components/versasense/const.py index 5283f61ac26..4f24fe637bb 100644 --- a/homeassistant/components/versasense/const.py +++ b/homeassistant/components/versasense/const.py @@ -1,4 +1,5 @@ """Constants for versasense.""" + KEY_CONSUMER = "consumer" KEY_IDENTIFIER = "identifier" KEY_MEASUREMENT = "measurement" diff --git a/homeassistant/components/wake_on_lan/const.py b/homeassistant/components/wake_on_lan/const.py index 14f2bd0263f..2560ef40382 100644 --- a/homeassistant/components/wake_on_lan/const.py +++ b/homeassistant/components/wake_on_lan/const.py @@ -1,2 +1,3 @@ """Constants for the Wake-On-LAN component.""" + DOMAIN = "wake_on_lan" diff --git a/homeassistant/components/wake_word/const.py b/homeassistant/components/wake_word/const.py index fdca6cfab6e..26027c61934 100644 --- a/homeassistant/components/wake_word/const.py +++ b/homeassistant/components/wake_word/const.py @@ -1,2 +1,3 @@ """Wake word constants.""" + DOMAIN = "wake_word" diff --git a/homeassistant/components/water_heater/group.py b/homeassistant/components/water_heater/group.py index 59d5478b1ab..16387d5abd7 100644 --- a/homeassistant/components/water_heater/group.py +++ b/homeassistant/components/water_heater/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/weather/group.py b/homeassistant/components/weather/group.py index 2ac081496cd..162457d336f 100644 --- a/homeassistant/components/weather/group.py +++ b/homeassistant/components/weather/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.core import HomeAssistant, callback diff --git a/homeassistant/components/weatherkit/sensor.py b/homeassistant/components/weatherkit/sensor.py index 28e828eee94..d9c17bb855a 100644 --- a/homeassistant/components/weatherkit/sensor.py +++ b/homeassistant/components/weatherkit/sensor.py @@ -1,6 +1,5 @@ """WeatherKit sensors.""" - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, diff --git a/homeassistant/components/wemo/const.py b/homeassistant/components/wemo/const.py index ec59e713b0d..0bde98acb24 100644 --- a/homeassistant/components/wemo/const.py +++ b/homeassistant/components/wemo/const.py @@ -1,4 +1,5 @@ """Constants for the Belkin Wemo component.""" + DOMAIN = "wemo" SERVICE_SET_HUMIDITY = "set_humidity" diff --git a/homeassistant/components/yamaha/const.py b/homeassistant/components/yamaha/const.py index bcfdc55a511..c0f4e34dd50 100644 --- a/homeassistant/components/yamaha/const.py +++ b/homeassistant/components/yamaha/const.py @@ -1,4 +1,5 @@ """Constants for the Yamaha component.""" + DOMAIN = "yamaha" CURSOR_TYPE_DOWN = "down" CURSOR_TYPE_LEFT = "left" diff --git a/homeassistant/components/zerproc/const.py b/homeassistant/components/zerproc/const.py index 69d5fcfb740..4922bef91d0 100644 --- a/homeassistant/components/zerproc/const.py +++ b/homeassistant/components/zerproc/const.py @@ -1,4 +1,5 @@ """Constants for the Zerproc integration.""" + DOMAIN = "zerproc" DATA_ADDRESSES = "addresses" diff --git a/homeassistant/components/zodiac/const.py b/homeassistant/components/zodiac/const.py index f50e108c2aa..9b2600b0c96 100644 --- a/homeassistant/components/zodiac/const.py +++ b/homeassistant/components/zodiac/const.py @@ -1,4 +1,5 @@ """Constants for Zodiac.""" + DOMAIN = "zodiac" DEFAULT_NAME = "Zodiac" diff --git a/homeassistant/util/yaml/const.py b/homeassistant/util/yaml/const.py index 9d930b50fd6..811c7d149f7 100644 --- a/homeassistant/util/yaml/const.py +++ b/homeassistant/util/yaml/const.py @@ -1,2 +1,3 @@ """Constants.""" + SECRET_YAML = "secrets.yaml" diff --git a/script/translations/deduplicate.py b/script/translations/deduplicate.py index 27764f0987f..8cc4cee3b10 100644 --- a/script/translations/deduplicate.py +++ b/script/translations/deduplicate.py @@ -1,6 +1,5 @@ """Deduplicate translations in strings.json.""" - import argparse import json from pathlib import Path diff --git a/tests/components/accuweather/__init__.py b/tests/components/accuweather/__init__.py index 3c7f81450c6..51ced2202bd 100644 --- a/tests/components/accuweather/__init__.py +++ b/tests/components/accuweather/__init__.py @@ -1,4 +1,5 @@ """Tests for AccuWeather.""" + from unittest.mock import PropertyMock, patch from homeassistant.components.accuweather.const import DOMAIN diff --git a/tests/components/advantage_air/test_light.py b/tests/components/advantage_air/test_light.py index 4d21781772d..b22c22fa185 100644 --- a/tests/components/advantage_air/test_light.py +++ b/tests/components/advantage_air/test_light.py @@ -1,6 +1,5 @@ """Test the Advantage Air Switch Platform.""" - from unittest.mock import AsyncMock from homeassistant.components.light import ( diff --git a/tests/components/advantage_air/test_select.py b/tests/components/advantage_air/test_select.py index 3367595d777..f0ed2b41e36 100644 --- a/tests/components/advantage_air/test_select.py +++ b/tests/components/advantage_air/test_select.py @@ -1,6 +1,5 @@ """Test the Advantage Air Select Platform.""" - from unittest.mock import AsyncMock from homeassistant.components.select import ( diff --git a/tests/components/airly/__init__.py b/tests/components/airly/__init__.py index ca26dbaf87f..cf76296d49a 100644 --- a/tests/components/airly/__init__.py +++ b/tests/components/airly/__init__.py @@ -1,4 +1,5 @@ """Tests for Airly.""" + from homeassistant.components.airly.const import DOMAIN from tests.common import MockConfigEntry, load_fixture diff --git a/tests/components/airthings_ble/__init__.py b/tests/components/airthings_ble/__init__.py index 5da9d8b48cd..0dfc894252e 100644 --- a/tests/components/airthings_ble/__init__.py +++ b/tests/components/airthings_ble/__init__.py @@ -1,4 +1,5 @@ """Tests for the Airthings BLE integration.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/analytics_insights/__init__.py b/tests/components/analytics_insights/__init__.py index 9e20a72c438..7dad32c2ed5 100644 --- a/tests/components/analytics_insights/__init__.py +++ b/tests/components/analytics_insights/__init__.py @@ -1,4 +1,5 @@ """Tests for the Homeassistant Analytics integration.""" + from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry diff --git a/tests/components/android_ip_webcam/test_init.py b/tests/components/android_ip_webcam/test_init.py index fa5f551e9b1..9aa677b8708 100644 --- a/tests/components/android_ip_webcam/test_init.py +++ b/tests/components/android_ip_webcam/test_init.py @@ -1,6 +1,5 @@ """Tests for the Android IP Webcam integration.""" - from unittest.mock import Mock import aiohttp diff --git a/tests/components/apcupsd/__init__.py b/tests/components/apcupsd/__init__.py index 4c4e0af8705..b75f3eab3af 100644 --- a/tests/components/apcupsd/__init__.py +++ b/tests/components/apcupsd/__init__.py @@ -1,4 +1,5 @@ """Tests for the APCUPSd component.""" + from collections import OrderedDict from typing import Final from unittest.mock import patch diff --git a/tests/components/apple_tv/__init__.py b/tests/components/apple_tv/__init__.py index 118c3d6f735..514d77bde4d 100644 --- a/tests/components/apple_tv/__init__.py +++ b/tests/components/apple_tv/__init__.py @@ -1,4 +1,5 @@ """Tests for Apple TV.""" + import pytest # Make asserts in the common module display differences diff --git a/tests/components/assist_pipeline/__init__.py b/tests/components/assist_pipeline/__init__.py index 40aa48fbc54..7400fe32d70 100644 --- a/tests/components/assist_pipeline/__init__.py +++ b/tests/components/assist_pipeline/__init__.py @@ -1,4 +1,5 @@ """Tests for the Voice Assistant integration.""" + MANY_LANGUAGES = [ "ar", "bg", diff --git a/tests/components/auth/__init__.py b/tests/components/auth/__init__.py index 8b731934913..18904cb2710 100644 --- a/tests/components/auth/__init__.py +++ b/tests/components/auth/__init__.py @@ -1,4 +1,5 @@ """Tests for the auth component.""" + from typing import Any from homeassistant import auth diff --git a/tests/components/awair/__init__.py b/tests/components/awair/__init__.py index e8b93e47fd7..f93866263a2 100644 --- a/tests/components/awair/__init__.py +++ b/tests/components/awair/__init__.py @@ -1,6 +1,5 @@ """Tests for the awair component.""" - from unittest.mock import patch from homeassistant.components.awair import DOMAIN diff --git a/tests/components/baf/__init__.py b/tests/components/baf/__init__.py index 4e435dc1a2e..648f235349d 100644 --- a/tests/components/baf/__init__.py +++ b/tests/components/baf/__init__.py @@ -1,6 +1,5 @@ """Tests for the Big Ass Fans integration.""" - import asyncio from aiobafi6 import Device diff --git a/tests/components/balboa/__init__.py b/tests/components/balboa/__init__.py index 9ea41eb2463..a27293e955f 100644 --- a/tests/components/balboa/__init__.py +++ b/tests/components/balboa/__init__.py @@ -1,4 +1,5 @@ """Test the Balboa Spa Client integration.""" + from __future__ import annotations from unittest.mock import MagicMock diff --git a/tests/components/bang_olufsen/const.py b/tests/components/bang_olufsen/const.py index 1b13e1b3412..187f93108a1 100644 --- a/tests/components/bang_olufsen/const.py +++ b/tests/components/bang_olufsen/const.py @@ -1,6 +1,5 @@ """Constants used for testing the bang_olufsen integration.""" - from ipaddress import IPv4Address, IPv6Address from homeassistant.components.bang_olufsen.const import ( diff --git a/tests/components/bang_olufsen/test_config_flow.py b/tests/components/bang_olufsen/test_config_flow.py index 793ef0c2c9b..d813ddf185b 100644 --- a/tests/components/bang_olufsen/test_config_flow.py +++ b/tests/components/bang_olufsen/test_config_flow.py @@ -1,6 +1,5 @@ """Test the bang_olufsen config_flow.""" - from unittest.mock import Mock from aiohttp.client_exceptions import ClientConnectorError diff --git a/tests/components/blue_current/__init__.py b/tests/components/blue_current/__init__.py index 63d8d084fae..e020855b59c 100644 --- a/tests/components/blue_current/__init__.py +++ b/tests/components/blue_current/__init__.py @@ -1,4 +1,5 @@ """Tests for the Blue Current integration.""" + from __future__ import annotations from asyncio import Event, Future diff --git a/tests/components/bluemaestro/__init__.py b/tests/components/bluemaestro/__init__.py index bd9b86e040f..412bc3cb7b3 100644 --- a/tests/components/bluemaestro/__init__.py +++ b/tests/components/bluemaestro/__init__.py @@ -1,6 +1,5 @@ """Tests for the BlueMaestro integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_BLUEMAESTRO_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/bluetooth/__init__.py b/tests/components/bluetooth/__init__.py index f4616abf8e5..b6d7220da16 100644 --- a/tests/components/bluetooth/__init__.py +++ b/tests/components/bluetooth/__init__.py @@ -1,6 +1,5 @@ """Tests for the Bluetooth integration.""" - from collections.abc import Iterable from contextlib import contextmanager import itertools diff --git a/tests/components/bmw_connected_drive/__init__.py b/tests/components/bmw_connected_drive/__init__.py index 020e4c978ed..84384e6b482 100644 --- a/tests/components/bmw_connected_drive/__init__.py +++ b/tests/components/bmw_connected_drive/__init__.py @@ -1,6 +1,5 @@ """Tests for the for the BMW Connected Drive integration.""" - from bimmer_connected.const import REMOTE_SERVICE_BASE_URL, VEHICLE_CHARGING_BASE_URL import respx diff --git a/tests/components/bmw_connected_drive/conftest.py b/tests/components/bmw_connected_drive/conftest.py index 4191c7a4dd2..c3a89e28bd6 100644 --- a/tests/components/bmw_connected_drive/conftest.py +++ b/tests/components/bmw_connected_drive/conftest.py @@ -1,6 +1,5 @@ """Fixtures for BMW tests.""" - from collections.abc import Generator from bimmer_connected.tests import ALL_CHARGING_SETTINGS, ALL_STATES diff --git a/tests/components/broadlink/__init__.py b/tests/components/broadlink/__init__.py index 8cdb4f478a3..c9245fb16fa 100644 --- a/tests/components/broadlink/__init__.py +++ b/tests/components/broadlink/__init__.py @@ -1,4 +1,5 @@ """Tests for the Broadlink integration.""" + from dataclasses import dataclass from unittest.mock import MagicMock, patch diff --git a/tests/components/brother/__init__.py b/tests/components/brother/__init__.py index 8e24c2d8058..591a227c3b5 100644 --- a/tests/components/brother/__init__.py +++ b/tests/components/brother/__init__.py @@ -1,4 +1,5 @@ """Tests for Brother Printer integration.""" + import json from unittest.mock import patch diff --git a/tests/components/bthome/__init__.py b/tests/components/bthome/__init__.py index de46cd8231d..ae7231b8740 100644 --- a/tests/components/bthome/__init__.py +++ b/tests/components/bthome/__init__.py @@ -1,6 +1,5 @@ """Tests for the BTHome integration.""" - from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from tests.components.bluetooth import generate_advertisement_data, generate_ble_device diff --git a/tests/components/canary/__init__.py b/tests/components/canary/__init__.py index 46737929dc5..8aed2fa1337 100644 --- a/tests/components/canary/__init__.py +++ b/tests/components/canary/__init__.py @@ -1,4 +1,5 @@ """Tests for the Canary integration.""" + from unittest.mock import MagicMock, PropertyMock, patch from canary.model import SensorType diff --git a/tests/components/cert_expiry/const.py b/tests/components/cert_expiry/const.py index 9ddbeca61c3..14a002fc6e5 100644 --- a/tests/components/cert_expiry/const.py +++ b/tests/components/cert_expiry/const.py @@ -1,3 +1,4 @@ """Constants for cert_expiry tests.""" + PORT = 443 HOST = "example.com" diff --git a/tests/components/cloud/__init__.py b/tests/components/cloud/__init__.py index e6e793ed106..2b4a95a61d9 100644 --- a/tests/components/cloud/__init__.py +++ b/tests/components/cloud/__init__.py @@ -1,4 +1,5 @@ """Tests for the cloud component.""" + from unittest.mock import AsyncMock, patch from hass_nabucasa import Cloud diff --git a/tests/components/cloudflare/__init__.py b/tests/components/cloudflare/__init__.py index 6b9e77dcb2a..ce9c6844f5a 100644 --- a/tests/components/cloudflare/__init__.py +++ b/tests/components/cloudflare/__init__.py @@ -1,4 +1,5 @@ """Tests for the Cloudflare integration.""" + from __future__ import annotations from unittest.mock import AsyncMock, patch diff --git a/tests/components/co2signal/__init__.py b/tests/components/co2signal/__init__.py index 65764d75fe4..394db24347b 100644 --- a/tests/components/co2signal/__init__.py +++ b/tests/components/co2signal/__init__.py @@ -1,4 +1,5 @@ """Tests for the CO2 Signal integration.""" + from aioelectricitymaps.models import ( CarbonIntensityData, CarbonIntensityResponse, diff --git a/tests/components/coinbase/const.py b/tests/components/coinbase/const.py index 138b941c62c..dcd14555ca3 100644 --- a/tests/components/coinbase/const.py +++ b/tests/components/coinbase/const.py @@ -1,6 +1,5 @@ """Constants for testing the Coinbase integration.""" - GOOD_CURRENCY = "BTC" GOOD_CURRENCY_2 = "USD" GOOD_CURRENCY_3 = "EUR" diff --git a/tests/components/comfoconnect/test_sensor.py b/tests/components/comfoconnect/test_sensor.py index a2cfdd3d13f..cea5ed0122f 100644 --- a/tests/components/comfoconnect/test_sensor.py +++ b/tests/components/comfoconnect/test_sensor.py @@ -1,4 +1,5 @@ """Tests for the comfoconnect sensor platform.""" + # import json from unittest.mock import patch diff --git a/tests/components/conversation/__init__.py b/tests/components/conversation/__init__.py index 648f8f33811..7209148e21f 100644 --- a/tests/components/conversation/__init__.py +++ b/tests/components/conversation/__init__.py @@ -1,4 +1,5 @@ """Tests for the conversation component.""" + from __future__ import annotations from typing import Literal diff --git a/tests/components/conversation/test_default_agent_intents.py b/tests/components/conversation/test_default_agent_intents.py index edf7e17682e..c57d93d8cef 100644 --- a/tests/components/conversation/test_default_agent_intents.py +++ b/tests/components/conversation/test_default_agent_intents.py @@ -1,6 +1,5 @@ """Test intents for the default agent.""" - import pytest from homeassistant.components import conversation, cover, media_player, vacuum, valve diff --git a/tests/components/devolo_home_control/__init__.py b/tests/components/devolo_home_control/__init__.py index a7217b0d530..f0e18eaf1a2 100644 --- a/tests/components/devolo_home_control/__init__.py +++ b/tests/components/devolo_home_control/__init__.py @@ -1,4 +1,5 @@ """Tests for the devolo_home_control integration.""" + from homeassistant.components.devolo_home_control.const import DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/devolo_home_network/__init__.py b/tests/components/devolo_home_network/__init__.py index ac6a960fd8f..05ccbca0c56 100644 --- a/tests/components/devolo_home_network/__init__.py +++ b/tests/components/devolo_home_network/__init__.py @@ -1,4 +1,5 @@ """Tests for the devolo Home Network integration.""" + from homeassistant.components.devolo_home_network.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.core import HomeAssistant diff --git a/tests/components/diagnostics/__init__.py b/tests/components/diagnostics/__init__.py index 5c81917b22c..81d62e7c2fe 100644 --- a/tests/components/diagnostics/__init__.py +++ b/tests/components/diagnostics/__init__.py @@ -1,4 +1,5 @@ """Tests for the Diagnostics integration.""" + from http import HTTPStatus from typing import cast diff --git a/tests/components/directv/__init__.py b/tests/components/directv/__init__.py index 584a7c95509..ae22e280000 100644 --- a/tests/components/directv/__init__.py +++ b/tests/components/directv/__init__.py @@ -1,4 +1,5 @@ """Tests for the DirecTV component.""" + from http import HTTPStatus from homeassistant.components import ssdp diff --git a/tests/components/dnsip/__init__.py b/tests/components/dnsip/__init__.py index 1a465b59ab6..d98de181892 100644 --- a/tests/components/dnsip/__init__.py +++ b/tests/components/dnsip/__init__.py @@ -1,4 +1,5 @@ """Tests for the dnsip integration.""" + from __future__ import annotations diff --git a/tests/components/dynalite/test_panel.py b/tests/components/dynalite/test_panel.py index a0acad54551..a1cd9749eb5 100644 --- a/tests/components/dynalite/test_panel.py +++ b/tests/components/dynalite/test_panel.py @@ -1,6 +1,5 @@ """Test websocket commands for the panel.""" - from unittest.mock import patch from homeassistant.components import dynalite diff --git a/tests/components/ecovacs/const.py b/tests/components/ecovacs/const.py index 237c7fa5c85..89d2e2a8166 100644 --- a/tests/components/ecovacs/const.py +++ b/tests/components/ecovacs/const.py @@ -1,6 +1,5 @@ """Test ecovacs constants.""" - from homeassistant.components.ecovacs.const import ( CONF_CONTINENT, CONF_OVERRIDE_MQTT_URL, diff --git a/tests/components/efergy/__init__.py b/tests/components/efergy/__init__.py index 3780bcb5494..d763aaa2fb6 100644 --- a/tests/components/efergy/__init__.py +++ b/tests/components/efergy/__init__.py @@ -1,4 +1,5 @@ """Tests for Efergy integration.""" + from unittest.mock import AsyncMock, patch from pyefergy import exceptions diff --git a/tests/components/electric_kiwi/test_sensor.py b/tests/components/electric_kiwi/test_sensor.py index 4961f5fdcd4..f91e4d9c58c 100644 --- a/tests/components/electric_kiwi/test_sensor.py +++ b/tests/components/electric_kiwi/test_sensor.py @@ -1,6 +1,5 @@ """The tests for Electric Kiwi sensors.""" - from datetime import UTC, datetime from unittest.mock import AsyncMock, Mock diff --git a/tests/components/elmax/__init__.py b/tests/components/elmax/__init__.py index 6d2e2333560..1434c831df3 100644 --- a/tests/components/elmax/__init__.py +++ b/tests/components/elmax/__init__.py @@ -1,4 +1,5 @@ """Tests for the Elmax component.""" + from tests.common import load_fixture MOCK_USER_JWT = ( diff --git a/tests/components/esphome/test_button.py b/tests/components/esphome/test_button.py index 71406341175..8c120949caa 100644 --- a/tests/components/esphome/test_button.py +++ b/tests/components/esphome/test_button.py @@ -1,6 +1,5 @@ """Test ESPHome buttones.""" - from unittest.mock import call from aioesphomeapi import APIClient, ButtonInfo diff --git a/tests/components/esphome/test_climate.py b/tests/components/esphome/test_climate.py index dbdee826137..4ec7fee6447 100644 --- a/tests/components/esphome/test_climate.py +++ b/tests/components/esphome/test_climate.py @@ -1,6 +1,5 @@ """Test ESPHome climates.""" - import math from unittest.mock import call diff --git a/tests/components/esphome/test_fan.py b/tests/components/esphome/test_fan.py index 6f383dcb6ba..064b37b1ec1 100644 --- a/tests/components/esphome/test_fan.py +++ b/tests/components/esphome/test_fan.py @@ -1,6 +1,5 @@ """Test ESPHome fans.""" - from unittest.mock import call from aioesphomeapi import ( diff --git a/tests/components/esphome/test_init.py b/tests/components/esphome/test_init.py index 8e7e228e422..7e008cde212 100644 --- a/tests/components/esphome/test_init.py +++ b/tests/components/esphome/test_init.py @@ -1,6 +1,5 @@ """ESPHome set up tests.""" - from homeassistant.components.esphome import DOMAIN from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT from homeassistant.core import HomeAssistant diff --git a/tests/components/esphome/test_light.py b/tests/components/esphome/test_light.py index fc63508a836..2324c73b16f 100644 --- a/tests/components/esphome/test_light.py +++ b/tests/components/esphome/test_light.py @@ -1,6 +1,5 @@ """Test ESPHome lights.""" - from unittest.mock import call from aioesphomeapi import ( diff --git a/tests/components/esphome/test_lock.py b/tests/components/esphome/test_lock.py index 83312c85934..82c24b59a2c 100644 --- a/tests/components/esphome/test_lock.py +++ b/tests/components/esphome/test_lock.py @@ -1,6 +1,5 @@ """Test ESPHome locks.""" - from unittest.mock import call from aioesphomeapi import APIClient, LockCommand, LockEntityState, LockInfo, LockState diff --git a/tests/components/esphome/test_select.py b/tests/components/esphome/test_select.py index 528483d4290..a433b1b0ab0 100644 --- a/tests/components/esphome/test_select.py +++ b/tests/components/esphome/test_select.py @@ -1,6 +1,5 @@ """Test ESPHome selects.""" - from unittest.mock import call from aioesphomeapi import APIClient, SelectInfo, SelectState diff --git a/tests/components/esphome/test_switch.py b/tests/components/esphome/test_switch.py index cd60eb70edd..561ac0b369f 100644 --- a/tests/components/esphome/test_switch.py +++ b/tests/components/esphome/test_switch.py @@ -1,6 +1,5 @@ """Test ESPHome switches.""" - from unittest.mock import call from aioesphomeapi import APIClient, SwitchInfo, SwitchState diff --git a/tests/components/eufylife_ble/__init__.py b/tests/components/eufylife_ble/__init__.py index 7fbeed9d798..f18d868efe1 100644 --- a/tests/components/eufylife_ble/__init__.py +++ b/tests/components/eufylife_ble/__init__.py @@ -1,6 +1,5 @@ """Tests for the EufyLife integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_EUFYLIFE_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/ezviz/__init__.py b/tests/components/ezviz/__init__.py index 768fc30cc81..7872cf37b68 100644 --- a/tests/components/ezviz/__init__.py +++ b/tests/components/ezviz/__init__.py @@ -1,4 +1,5 @@ """Tests for the EZVIZ integration.""" + from unittest.mock import patch from homeassistant.components.ezviz.const import ( diff --git a/tests/components/fitbit/test_sensor.py b/tests/components/fitbit/test_sensor.py index 59405e3ea91..9443d0500eb 100644 --- a/tests/components/fitbit/test_sensor.py +++ b/tests/components/fitbit/test_sensor.py @@ -1,6 +1,5 @@ """Tests for the fitbit sensor platform.""" - from collections.abc import Awaitable, Callable from http import HTTPStatus from typing import Any diff --git a/tests/components/fjaraskupan/__init__.py b/tests/components/fjaraskupan/__init__.py index 5025fbeaf06..a55d7ea84c0 100644 --- a/tests/components/fjaraskupan/__init__.py +++ b/tests/components/fjaraskupan/__init__.py @@ -1,6 +1,5 @@ """Tests for the Fjäråskupan integration.""" - from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from tests.components.bluetooth import generate_advertisement_data, generate_ble_device diff --git a/tests/components/flexit_bacnet/__init__.py b/tests/components/flexit_bacnet/__init__.py index e934f3c7e5f..c43f55dc33b 100644 --- a/tests/components/flexit_bacnet/__init__.py +++ b/tests/components/flexit_bacnet/__init__.py @@ -1,4 +1,5 @@ """Tests for the Flexit Nordic (BACnet) integration.""" + from unittest.mock import patch from homeassistant.const import Platform diff --git a/tests/components/flo/common.py b/tests/components/flo/common.py index d4018aae090..9ec1834b0c2 100644 --- a/tests/components/flo/common.py +++ b/tests/components/flo/common.py @@ -1,4 +1,5 @@ """Define common test utilities.""" + TEST_ACCOUNT_ID = "aabbccdd" TEST_DEVICE_ID = "98765" TEST_EMAIL_ADDRESS = "email@address.com" diff --git a/tests/components/flux_led/__init__.py b/tests/components/flux_led/__init__.py index 80f072328d6..4b36826728a 100644 --- a/tests/components/flux_led/__init__.py +++ b/tests/components/flux_led/__init__.py @@ -1,4 +1,5 @@ """Tests for the flux_led integration.""" + from __future__ import annotations import asyncio diff --git a/tests/components/flux_led/test_number.py b/tests/components/flux_led/test_number.py index 83bd0d1d517..5e16e4c0c2c 100644 --- a/tests/components/flux_led/test_number.py +++ b/tests/components/flux_led/test_number.py @@ -1,6 +1,5 @@ """Tests for the flux_led number platform.""" - from unittest.mock import patch from flux_led.const import COLOR_MODE_RGB as FLUX_COLOR_MODE_RGB diff --git a/tests/components/freebox/const.py b/tests/components/freebox/const.py index ae07b39c5e8..5211b793918 100644 --- a/tests/components/freebox/const.py +++ b/tests/components/freebox/const.py @@ -1,6 +1,5 @@ """Test constants.""" - from tests.common import load_json_array_fixture, load_json_object_fixture MOCK_HOST = "myrouter.freeboxos.fr" diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py index 1faf37c84ee..8d366e39f6d 100644 --- a/tests/components/fritzbox/__init__.py +++ b/tests/components/fritzbox/__init__.py @@ -1,4 +1,5 @@ """Tests for the AVM Fritz!Box integration.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/fronius/__init__.py b/tests/components/fronius/__init__.py index 2e053f7ccc5..5f0e86c292d 100644 --- a/tests/components/fronius/__init__.py +++ b/tests/components/fronius/__init__.py @@ -1,4 +1,5 @@ """Tests for the Fronius integration.""" + from __future__ import annotations from collections.abc import Callable diff --git a/tests/components/gardena_bluetooth/test_binary_sensor.py b/tests/components/gardena_bluetooth/test_binary_sensor.py index d12f825b1a7..97ba69ba239 100644 --- a/tests/components/gardena_bluetooth/test_binary_sensor.py +++ b/tests/components/gardena_bluetooth/test_binary_sensor.py @@ -1,6 +1,5 @@ """Test Gardena Bluetooth binary sensor.""" - from collections.abc import Awaitable, Callable from gardena_bluetooth.const import Valve diff --git a/tests/components/gardena_bluetooth/test_button.py b/tests/components/gardena_bluetooth/test_button.py index 480f0c3572e..685afd8c337 100644 --- a/tests/components/gardena_bluetooth/test_button.py +++ b/tests/components/gardena_bluetooth/test_button.py @@ -1,6 +1,5 @@ """Test Gardena Bluetooth sensor.""" - from collections.abc import Awaitable, Callable from unittest.mock import Mock, call diff --git a/tests/components/gardena_bluetooth/test_number.py b/tests/components/gardena_bluetooth/test_number.py index ce2d19b8c63..0bbe2e926cd 100644 --- a/tests/components/gardena_bluetooth/test_number.py +++ b/tests/components/gardena_bluetooth/test_number.py @@ -1,6 +1,5 @@ """Test Gardena Bluetooth sensor.""" - from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import Mock, call diff --git a/tests/components/gardena_bluetooth/test_switch.py b/tests/components/gardena_bluetooth/test_switch.py index 40e8c148335..8478788de04 100644 --- a/tests/components/gardena_bluetooth/test_switch.py +++ b/tests/components/gardena_bluetooth/test_switch.py @@ -1,6 +1,5 @@ """Test Gardena Bluetooth sensor.""" - from collections.abc import Awaitable, Callable from unittest.mock import Mock, call diff --git a/tests/components/gdacs/__init__.py b/tests/components/gdacs/__init__.py index 6e61b86dbb7..01e13cca900 100644 --- a/tests/components/gdacs/__init__.py +++ b/tests/components/gdacs/__init__.py @@ -1,4 +1,5 @@ """Tests for the GDACS component.""" + from unittest.mock import MagicMock diff --git a/tests/components/geo_json_events/__init__.py b/tests/components/geo_json_events/__init__.py index 7d7148b3c20..18fbba47b6e 100644 --- a/tests/components/geo_json_events/__init__.py +++ b/tests/components/geo_json_events/__init__.py @@ -1,4 +1,5 @@ """Tests for the geo_json_events component.""" + from typing import Any from unittest.mock import MagicMock diff --git a/tests/components/geonetnz_quakes/__init__.py b/tests/components/geonetnz_quakes/__init__.py index 424c6372ea8..90375079daa 100644 --- a/tests/components/geonetnz_quakes/__init__.py +++ b/tests/components/geonetnz_quakes/__init__.py @@ -1,4 +1,5 @@ """Tests for the geonetnz_quakes component.""" + from unittest.mock import MagicMock diff --git a/tests/components/geonetnz_volcano/__init__.py b/tests/components/geonetnz_volcano/__init__.py index 708b69e0031..b8a36a124a3 100644 --- a/tests/components/geonetnz_volcano/__init__.py +++ b/tests/components/geonetnz_volcano/__init__.py @@ -1,4 +1,5 @@ """The tests for the GeoNet NZ Volcano Feed integration.""" + from unittest.mock import MagicMock diff --git a/tests/components/gios/__init__.py b/tests/components/gios/__init__.py index 4e69420f66e..482b58ad7ad 100644 --- a/tests/components/gios/__init__.py +++ b/tests/components/gios/__init__.py @@ -1,4 +1,5 @@ """Tests for GIOS.""" + import json from unittest.mock import patch diff --git a/tests/components/goalzero/__init__.py b/tests/components/goalzero/__init__.py index cde970f67a3..d2e990ca122 100644 --- a/tests/components/goalzero/__init__.py +++ b/tests/components/goalzero/__init__.py @@ -1,4 +1,5 @@ """Tests for the Goal Zero Yeti integration.""" + from unittest.mock import AsyncMock, patch from homeassistant.components import dhcp diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index e24e6b740d3..73dc109f7e6 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -1,4 +1,5 @@ """Tests for the Google Assistant integration.""" + from unittest.mock import MagicMock from homeassistant.components.google_assistant import helpers, http diff --git a/tests/components/google_tasks/conftest.py b/tests/components/google_tasks/conftest.py index 60387889aad..87ddb2ed81d 100644 --- a/tests/components/google_tasks/conftest.py +++ b/tests/components/google_tasks/conftest.py @@ -1,6 +1,5 @@ """Test fixtures for Google Tasks.""" - from collections.abc import Awaitable, Callable import time from typing import Any diff --git a/tests/components/google_tasks/test_todo.py b/tests/components/google_tasks/test_todo.py index ee1b1e4cfd4..83d419439d7 100644 --- a/tests/components/google_tasks/test_todo.py +++ b/tests/components/google_tasks/test_todo.py @@ -1,6 +1,5 @@ """Tests for Google Tasks todo platform.""" - from collections.abc import Awaitable, Callable from http import HTTPStatus import json diff --git a/tests/components/google_travel_time/const.py b/tests/components/google_travel_time/const.py index 844766ceffa..77e99ffbf68 100644 --- a/tests/components/google_travel_time/const.py +++ b/tests/components/google_travel_time/const.py @@ -1,6 +1,5 @@ """Constants for google_travel_time tests.""" - from homeassistant.components.google_travel_time.const import ( CONF_DESTINATION, CONF_ORIGIN, diff --git a/tests/components/govee_ble/__init__.py b/tests/components/govee_ble/__init__.py index c093a6dddb5..60930d1dd0e 100644 --- a/tests/components/govee_ble/__init__.py +++ b/tests/components/govee_ble/__init__.py @@ -1,6 +1,5 @@ """Tests for the Govee BLE integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_GOVEE_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/hassio/__init__.py b/tests/components/hassio/__init__.py index 76aecd64098..e5669f777d2 100644 --- a/tests/components/hassio/__init__.py +++ b/tests/components/hassio/__init__.py @@ -1,2 +1,3 @@ """Tests for Hass.io component.""" + SUPERVISOR_TOKEN = "123456" diff --git a/tests/components/hdmi_cec/__init__.py b/tests/components/hdmi_cec/__init__.py index c131bf96b41..31e09489d4a 100644 --- a/tests/components/hdmi_cec/__init__.py +++ b/tests/components/hdmi_cec/__init__.py @@ -1,4 +1,5 @@ """Tests for the HDMI-CEC component.""" + from unittest.mock import AsyncMock, Mock from homeassistant.components.hdmi_cec import KeyPressCommand, KeyReleaseCommand diff --git a/tests/components/homekit_controller/specific_devices/test_cover_that_changes_features.py b/tests/components/homekit_controller/specific_devices/test_cover_that_changes_features.py index 87948c92214..2833346288a 100644 --- a/tests/components/homekit_controller/specific_devices/test_cover_that_changes_features.py +++ b/tests/components/homekit_controller/specific_devices/test_cover_that_changes_features.py @@ -1,6 +1,5 @@ """Test for a Home Assistant bridge that changes cover features at runtime.""" - from homeassistant.components.cover import CoverEntityFeature from homeassistant.const import ATTR_SUPPORTED_FEATURES from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_fan_that_changes_features.py b/tests/components/homekit_controller/specific_devices/test_fan_that_changes_features.py index 9921808c371..aea53e74d46 100644 --- a/tests/components/homekit_controller/specific_devices/test_fan_that_changes_features.py +++ b/tests/components/homekit_controller/specific_devices/test_fan_that_changes_features.py @@ -1,6 +1,5 @@ """Test for a Home Assistant bridge that changes fan features at runtime.""" - from homeassistant.components.fan import FanEntityFeature from homeassistant.const import ATTR_SUPPORTED_FEATURES from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_heater_cooler_that_changes_features.py b/tests/components/homekit_controller/specific_devices/test_heater_cooler_that_changes_features.py index 5d0f63b07ff..e98bed4b0de 100644 --- a/tests/components/homekit_controller/specific_devices/test_heater_cooler_that_changes_features.py +++ b/tests/components/homekit_controller/specific_devices/test_heater_cooler_that_changes_features.py @@ -1,6 +1,5 @@ """Test for a Home Assistant bridge that changes climate features at runtime.""" - from homeassistant.components.climate import ATTR_SWING_MODES, ClimateEntityFeature from homeassistant.const import ATTR_SUPPORTED_FEATURES from homeassistant.core import HomeAssistant diff --git a/tests/components/homekit_controller/specific_devices/test_humidifier_that_changes_value_range.py b/tests/components/homekit_controller/specific_devices/test_humidifier_that_changes_value_range.py index 518bcbbef38..2235b35a9a9 100644 --- a/tests/components/homekit_controller/specific_devices/test_humidifier_that_changes_value_range.py +++ b/tests/components/homekit_controller/specific_devices/test_humidifier_that_changes_value_range.py @@ -1,6 +1,5 @@ """Test for a Home Assistant bridge that changes humidifier min/max at runtime.""" - from homeassistant.components.humidifier import ATTR_MAX_HUMIDITY, ATTR_MIN_HUMIDITY from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/homekit_controller/specific_devices/test_light_that_changes_features.py b/tests/components/homekit_controller/specific_devices/test_light_that_changes_features.py index 4e62c75d8f2..d99c1fb2dba 100644 --- a/tests/components/homekit_controller/specific_devices/test_light_that_changes_features.py +++ b/tests/components/homekit_controller/specific_devices/test_light_that_changes_features.py @@ -1,6 +1,5 @@ """Test for a Home Assistant bridge that changes light features at runtime.""" - from homeassistant.components.light import ATTR_SUPPORTED_COLOR_MODES, ColorMode from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/honeywell/__init__.py b/tests/components/honeywell/__init__.py index 6299097b104..98fcaa551bf 100644 --- a/tests/components/honeywell/__init__.py +++ b/tests/components/honeywell/__init__.py @@ -1,4 +1,5 @@ """Tests for honeywell component.""" + from unittest.mock import MagicMock from homeassistant.core import HomeAssistant diff --git a/tests/components/http/__init__.py b/tests/components/http/__init__.py index cd1d5916ab8..931af50cbc6 100644 --- a/tests/components/http/__init__.py +++ b/tests/components/http/__init__.py @@ -1,3 +1,4 @@ """Tests for the HTTP component.""" + # Relic from the past. Kept here so we can run negative tests. HTTP_HEADER_HA_AUTH = "X-HA-access" diff --git a/tests/components/http/test_static.py b/tests/components/http/test_static.py index 7f6504bdb7c..d129f6641a5 100644 --- a/tests/components/http/test_static.py +++ b/tests/components/http/test_static.py @@ -1,6 +1,5 @@ """The tests for http static files.""" - from pathlib import Path from aiohttp.test_utils import TestClient diff --git a/tests/components/huisbaasje/test_data.py b/tests/components/huisbaasje/test_data.py index e14976443f3..181dcd7640e 100644 --- a/tests/components/huisbaasje/test_data.py +++ b/tests/components/huisbaasje/test_data.py @@ -1,4 +1,5 @@ """Test data for the tests of the Huisbaasje integration.""" + MOCK_CURRENT_MEASUREMENTS = { "electricity": { "measurement": { diff --git a/tests/components/husqvarna_automower/__init__.py b/tests/components/husqvarna_automower/__init__.py index 069fa0d7372..8c51d69ba3d 100644 --- a/tests/components/husqvarna_automower/__init__.py +++ b/tests/components/husqvarna_automower/__init__.py @@ -1,4 +1,5 @@ """Tests for the Husqvarna Automower integration.""" + from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry diff --git a/tests/components/husqvarna_automower/const.py b/tests/components/husqvarna_automower/const.py index a8b018fa839..dc5893c6749 100644 --- a/tests/components/husqvarna_automower/const.py +++ b/tests/components/husqvarna_automower/const.py @@ -1,4 +1,5 @@ """Constants for Husqvarna Automower tests.""" + CLIENT_ID = "1234" CLIENT_SECRET = "5678" TEST_MOWER_ID = "c7233734-b219-4287-a173-08e3643f89f0" diff --git a/tests/components/hyperion/__init__.py b/tests/components/hyperion/__init__.py index 3714e58479b..72aba96e81f 100644 --- a/tests/components/hyperion/__init__.py +++ b/tests/components/hyperion/__init__.py @@ -1,4 +1,5 @@ """Tests for the Hyperion component.""" + from __future__ import annotations from types import TracebackType diff --git a/tests/components/ibeacon/__init__.py b/tests/components/ibeacon/__init__.py index a18a90f6c3d..b4aa04fd0bb 100644 --- a/tests/components/ibeacon/__init__.py +++ b/tests/components/ibeacon/__init__.py @@ -1,4 +1,5 @@ """Tests for the ibeacon integration.""" + from typing import Any from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo diff --git a/tests/components/image_upload/__init__.py b/tests/components/image_upload/__init__.py index 94acd4a5485..34064e0d40a 100644 --- a/tests/components/image_upload/__init__.py +++ b/tests/components/image_upload/__init__.py @@ -1,4 +1,5 @@ """Tests for the Image Upload integration.""" + import pathlib TEST_IMAGE = pathlib.Path(__file__).parent / "logo.png" diff --git a/tests/components/imap/const.py b/tests/components/imap/const.py index 713261936c7..8ec5e258059 100644 --- a/tests/components/imap/const.py +++ b/tests/components/imap/const.py @@ -1,6 +1,5 @@ """Constants for tests imap integration.""" - DATE_HEADER1 = b"Date: Fri, 24 Mar 2023 13:52:00 +0100\r\n" DATE_HEADER2 = b"Date: Fri, 24 Mar 2023 13:52:00 +0100 (CET)\r\n" DATE_HEADER3 = b"Date: 24 Mar 2023 13:52:00 +0100\r\n" diff --git a/tests/components/inkbird/__init__.py b/tests/components/inkbird/__init__.py index 0a74e50ae0e..30ca369672c 100644 --- a/tests/components/inkbird/__init__.py +++ b/tests/components/inkbird/__init__.py @@ -1,6 +1,5 @@ """Tests for the INKBIRD integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_INKBIRD_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/iotawatt/__init__.py b/tests/components/iotawatt/__init__.py index 5f66c145ad6..5233ce6fc83 100644 --- a/tests/components/iotawatt/__init__.py +++ b/tests/components/iotawatt/__init__.py @@ -1,4 +1,5 @@ """Tests for the IoTaWatt integration.""" + from iotawattpy.sensor import Sensor INPUT_SENSOR = Sensor( diff --git a/tests/components/ipma/__init__.py b/tests/components/ipma/__init__.py index 02a61f0b201..65cff43c8d4 100644 --- a/tests/components/ipma/__init__.py +++ b/tests/components/ipma/__init__.py @@ -1,4 +1,5 @@ """Tests for the IPMA component.""" + from collections import namedtuple from datetime import UTC, datetime diff --git a/tests/components/jellyfin/__init__.py b/tests/components/jellyfin/__init__.py index c1f7bbb2f35..7db0ba2d8a3 100644 --- a/tests/components/jellyfin/__init__.py +++ b/tests/components/jellyfin/__init__.py @@ -1,4 +1,5 @@ """Tests for the jellyfin integration.""" + import json from typing import Any diff --git a/tests/components/jewish_calendar/__init__.py b/tests/components/jewish_calendar/__init__.py index f92bfe7d71e..e1352f789ac 100644 --- a/tests/components/jewish_calendar/__init__.py +++ b/tests/components/jewish_calendar/__init__.py @@ -1,4 +1,5 @@ """Tests for the jewish_calendar component.""" + from collections import namedtuple from datetime import datetime diff --git a/tests/components/keenetic_ndms2/__init__.py b/tests/components/keenetic_ndms2/__init__.py index f6bc72cb1e4..8ca91d00386 100644 --- a/tests/components/keenetic_ndms2/__init__.py +++ b/tests/components/keenetic_ndms2/__init__.py @@ -1,4 +1,5 @@ """Tests for the Keenetic NDMS2 component.""" + from homeassistant.components import ssdp from homeassistant.components.keenetic_ndms2 import const from homeassistant.const import ( diff --git a/tests/components/keymitt_ble/__init__.py b/tests/components/keymitt_ble/__init__.py index c6e56739d76..242c1ebe7d6 100644 --- a/tests/components/keymitt_ble/__init__.py +++ b/tests/components/keymitt_ble/__init__.py @@ -1,4 +1,5 @@ """Tests for the MicroBot integration.""" + from unittest.mock import patch from homeassistant.components.bluetooth import BluetoothServiceInfoBleak diff --git a/tests/components/kodi/__init__.py b/tests/components/kodi/__init__.py index bef576dd6bf..a15d1e6681d 100644 --- a/tests/components/kodi/__init__.py +++ b/tests/components/kodi/__init__.py @@ -1,4 +1,5 @@ """Tests for the Kodi integration.""" + from unittest.mock import patch from homeassistant.components.kodi.const import CONF_WS_PORT, DOMAIN diff --git a/tests/components/lamarzocco/test_button.py b/tests/components/lamarzocco/test_button.py index 7d910a57561..e1a036df17a 100644 --- a/tests/components/lamarzocco/test_button.py +++ b/tests/components/lamarzocco/test_button.py @@ -1,6 +1,5 @@ """Tests for the La Marzocco Buttons.""" - from unittest.mock import MagicMock import pytest diff --git a/tests/components/lamarzocco/test_select.py b/tests/components/lamarzocco/test_select.py index a2e4248f0af..3c96f16de9c 100644 --- a/tests/components/lamarzocco/test_select.py +++ b/tests/components/lamarzocco/test_select.py @@ -1,6 +1,5 @@ """Tests for the La Marzocco select entities.""" - from unittest.mock import MagicMock from lmcloud.const import LaMarzoccoModel diff --git a/tests/components/lamarzocco/test_update.py b/tests/components/lamarzocco/test_update.py index 55c5bb0da3d..3b1323d1c73 100644 --- a/tests/components/lamarzocco/test_update.py +++ b/tests/components/lamarzocco/test_update.py @@ -1,6 +1,5 @@ """Tests for the La Marzocco Update Entities.""" - from unittest.mock import MagicMock from lmcloud.const import LaMarzoccoUpdateableComponent diff --git a/tests/components/lastfm/__init__.py b/tests/components/lastfm/__init__.py index 7e6bb6500b2..8f133607c8d 100644 --- a/tests/components/lastfm/__init__.py +++ b/tests/components/lastfm/__init__.py @@ -1,4 +1,5 @@ """The tests for lastfm.""" + from unittest.mock import patch from pylast import PyLastError, Track diff --git a/tests/components/leaone/__init__.py b/tests/components/leaone/__init__.py index c54e07ccd87..3d62314fd9a 100644 --- a/tests/components/leaone/__init__.py +++ b/tests/components/leaone/__init__.py @@ -1,6 +1,5 @@ """Tests for the Leaone integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo SCALE_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/lifx/__init__.py b/tests/components/lifx/__init__.py index bb798f6f8a0..dc730565cc7 100644 --- a/tests/components/lifx/__init__.py +++ b/tests/components/lifx/__init__.py @@ -1,4 +1,5 @@ """Tests for the lifx integration.""" + from __future__ import annotations import asyncio diff --git a/tests/components/litejet/__init__.py b/tests/components/litejet/__init__.py index 1ba39e46bf3..3116d9e810d 100644 --- a/tests/components/litejet/__init__.py +++ b/tests/components/litejet/__init__.py @@ -1,4 +1,5 @@ """Tests for the litejet component.""" + from homeassistant.components import scene, switch from homeassistant.components.litejet import DOMAIN from homeassistant.const import CONF_PORT diff --git a/tests/components/livisi/__init__.py b/tests/components/livisi/__init__.py index 48a7e21ad8d..c8700aeebc6 100644 --- a/tests/components/livisi/__init__.py +++ b/tests/components/livisi/__init__.py @@ -1,4 +1,5 @@ """Tests for the LIVISI Smart Home integration.""" + from unittest.mock import patch from homeassistant.const import CONF_HOST, CONF_PASSWORD diff --git a/tests/components/lookin/__init__.py b/tests/components/lookin/__init__.py index bfbb5f66887..e19bdc9fd73 100644 --- a/tests/components/lookin/__init__.py +++ b/tests/components/lookin/__init__.py @@ -1,4 +1,5 @@ """Tests for the lookin integration.""" + from __future__ import annotations from ipaddress import ip_address diff --git a/tests/components/lupusec/test_config_flow.py b/tests/components/lupusec/test_config_flow.py index 6b07952ff54..89adedb86cb 100644 --- a/tests/components/lupusec/test_config_flow.py +++ b/tests/components/lupusec/test_config_flow.py @@ -1,4 +1,4 @@ -""""Unit tests for the Lupusec config flow.""" +"""Unit tests for the Lupusec config flow.""" from json import JSONDecodeError from unittest.mock import patch diff --git a/tests/components/lutron_caseta/__init__.py b/tests/components/lutron_caseta/__init__.py index 4e8e2d9a504..c82a0c78ea1 100644 --- a/tests/components/lutron_caseta/__init__.py +++ b/tests/components/lutron_caseta/__init__.py @@ -1,6 +1,5 @@ """Tests for the Lutron Caseta integration.""" - from unittest.mock import patch from homeassistant.components.lutron_caseta import DOMAIN diff --git a/tests/components/lutron_caseta/test_cover.py b/tests/components/lutron_caseta/test_cover.py index 7fe8ed22866..5d45f185aef 100644 --- a/tests/components/lutron_caseta/test_cover.py +++ b/tests/components/lutron_caseta/test_cover.py @@ -1,6 +1,5 @@ """Tests for the Lutron Caseta integration.""" - from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/lutron_caseta/test_fan.py b/tests/components/lutron_caseta/test_fan.py index 0147817514d..07744796679 100644 --- a/tests/components/lutron_caseta/test_fan.py +++ b/tests/components/lutron_caseta/test_fan.py @@ -1,6 +1,5 @@ """Tests for the Lutron Caseta integration.""" - from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/lutron_caseta/test_light.py b/tests/components/lutron_caseta/test_light.py index cdba9a956e5..5a1568c3cb8 100644 --- a/tests/components/lutron_caseta/test_light.py +++ b/tests/components/lutron_caseta/test_light.py @@ -1,6 +1,5 @@ """Tests for the Lutron Caseta integration.""" - from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er diff --git a/tests/components/medcom_ble/__init__.py b/tests/components/medcom_ble/__init__.py index e38b8ce8f01..aa367b93a14 100644 --- a/tests/components/medcom_ble/__init__.py +++ b/tests/components/medcom_ble/__init__.py @@ -1,4 +1,5 @@ """Tests for the Medcom Inspector BLE integration.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/media_extractor/__init__.py b/tests/components/media_extractor/__init__.py index d6faa60d3b4..7aac726501b 100644 --- a/tests/components/media_extractor/__init__.py +++ b/tests/components/media_extractor/__init__.py @@ -1,4 +1,5 @@ """The tests for Media Extractor integration.""" + from typing import Any from tests.common import load_json_object_fixture diff --git a/tests/components/met/__init__.py b/tests/components/met/__init__.py index 2ef0f7e12f0..8ea5ce605f0 100644 --- a/tests/components/met/__init__.py +++ b/tests/components/met/__init__.py @@ -1,4 +1,5 @@ """Tests for Met.no.""" + from unittest.mock import patch from homeassistant.components.met.const import CONF_TRACK_HOME, DOMAIN diff --git a/tests/components/met_eireann/__init__.py b/tests/components/met_eireann/__init__.py index 3dfadc06f6b..86c3090b0ca 100644 --- a/tests/components/met_eireann/__init__.py +++ b/tests/components/met_eireann/__init__.py @@ -1,4 +1,5 @@ """Tests for Met Éireann.""" + from unittest.mock import patch from homeassistant.components.met_eireann.const import DOMAIN diff --git a/tests/components/microbees/__init__.py b/tests/components/microbees/__init__.py index a33e1c59f5e..59250efa5ee 100644 --- a/tests/components/microbees/__init__.py +++ b/tests/components/microbees/__init__.py @@ -1,4 +1,5 @@ """Tests for the MicroBees component.""" + from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry diff --git a/tests/components/mikrotik/__init__.py b/tests/components/mikrotik/__init__.py index 8e3d5eda19d..acf5fcb2d5e 100644 --- a/tests/components/mikrotik/__init__.py +++ b/tests/components/mikrotik/__init__.py @@ -1,4 +1,5 @@ """Tests for the Mikrotik component.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/minio/common.py b/tests/components/minio/common.py index 4719fc79e49..107afe7fe8d 100644 --- a/tests/components/minio/common.py +++ b/tests/components/minio/common.py @@ -1,4 +1,5 @@ """Minio Test event.""" + TEST_EVENT = { "Records": [ { diff --git a/tests/components/moat/__init__.py b/tests/components/moat/__init__.py index e0af0229cba..09c4c39d1fa 100644 --- a/tests/components/moat/__init__.py +++ b/tests/components/moat/__init__.py @@ -1,6 +1,5 @@ """Tests for the Moat BLE integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_MOAT_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/mobile_app/const.py b/tests/components/mobile_app/const.py index 9316af4a6a2..da80eb07513 100644 --- a/tests/components/mobile_app/const.py +++ b/tests/components/mobile_app/const.py @@ -1,4 +1,5 @@ """Constants for mobile_app tests.""" + CALL_SERVICE = { "type": "call_service", "data": {"domain": "test", "service": "mobile_app", "service_data": {"foo": "bar"}}, diff --git a/tests/components/mopeka/__init__.py b/tests/components/mopeka/__init__.py index 3446b1dc66b..43650349be8 100644 --- a/tests/components/mopeka/__init__.py +++ b/tests/components/mopeka/__init__.py @@ -1,6 +1,5 @@ """Tests for the Mopeka integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_MOPEKA_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/motioneye/__init__.py b/tests/components/motioneye/__init__.py index 7558e6fbcc4..183d1b3e6bf 100644 --- a/tests/components/motioneye/__init__.py +++ b/tests/components/motioneye/__init__.py @@ -1,4 +1,5 @@ """Tests for the motionEye integration.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/mystrom/__init__.py b/tests/components/mystrom/__init__.py index acd520cebaa..ac6ac1d8c54 100644 --- a/tests/components/mystrom/__init__.py +++ b/tests/components/mystrom/__init__.py @@ -1,4 +1,5 @@ """Tests for the myStrom integration.""" + from typing import Any diff --git a/tests/components/myuplink/const.py b/tests/components/myuplink/const.py index 6ba324db12a..6001cb151c0 100644 --- a/tests/components/myuplink/const.py +++ b/tests/components/myuplink/const.py @@ -1,3 +1,4 @@ """Constants for myuplink tests.""" + CLIENT_ID = "12345" CLIENT_SECRET = "67890" diff --git a/tests/components/nam/__init__.py b/tests/components/nam/__init__.py index 0f5befcac09..3d95d6cc7e0 100644 --- a/tests/components/nam/__init__.py +++ b/tests/components/nam/__init__.py @@ -1,4 +1,5 @@ """Tests for the Nettigo Air Monitor integration.""" + from unittest.mock import AsyncMock, Mock, patch from homeassistant.components.nam.const import DOMAIN diff --git a/tests/components/nextdns/__init__.py b/tests/components/nextdns/__init__.py index a175bffbb75..d741811acf0 100644 --- a/tests/components/nextdns/__init__.py +++ b/tests/components/nextdns/__init__.py @@ -1,4 +1,5 @@ """Tests for the NextDNS integration.""" + from unittest.mock import patch from nextdns import ( diff --git a/tests/components/nightscout/__init__.py b/tests/components/nightscout/__init__.py index 6c1a34ebe41..3433e889b92 100644 --- a/tests/components/nightscout/__init__.py +++ b/tests/components/nightscout/__init__.py @@ -1,4 +1,5 @@ """Tests for the Nightscout integration.""" + import json from unittest.mock import patch diff --git a/tests/components/nina/__init__.py b/tests/components/nina/__init__.py index d8a70a180dd..923df6b6337 100644 --- a/tests/components/nina/__init__.py +++ b/tests/components/nina/__init__.py @@ -1,4 +1,5 @@ """Tests for the Nina integration.""" + import json from typing import Any diff --git a/tests/components/nzbget/__init__.py b/tests/components/nzbget/__init__.py index 4446ac0cd55..d3216b62ef3 100644 --- a/tests/components/nzbget/__init__.py +++ b/tests/components/nzbget/__init__.py @@ -1,4 +1,5 @@ """Tests for the NZBGet integration.""" + from unittest.mock import patch from homeassistant.components.nzbget.const import DOMAIN diff --git a/tests/components/octoprint/__init__.py b/tests/components/octoprint/__init__.py index 5176d2209b1..d97e4b1ce21 100644 --- a/tests/components/octoprint/__init__.py +++ b/tests/components/octoprint/__init__.py @@ -1,4 +1,5 @@ """Tests for the OctoPrint integration.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/oncue/__init__.py b/tests/components/oncue/__init__.py index 26b08460a93..b90f9df4f89 100644 --- a/tests/components/oncue/__init__.py +++ b/tests/components/oncue/__init__.py @@ -1,4 +1,5 @@ """Tests for the Oncue integration.""" + from contextlib import contextmanager from unittest.mock import patch diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py index 4185337dce2..2929ed3ed50 100644 --- a/tests/components/onewire/__init__.py +++ b/tests/components/onewire/__init__.py @@ -1,4 +1,5 @@ """Tests for 1-Wire integration.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/onvif/__init__.py b/tests/components/onvif/__init__.py index ae48dd26220..5164939eead 100644 --- a/tests/components/onvif/__init__.py +++ b/tests/components/onvif/__init__.py @@ -1,4 +1,5 @@ """Tests for the ONVIF integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from onvif.exceptions import ONVIFError diff --git a/tests/components/opensky/__init__.py b/tests/components/opensky/__init__.py index 0f24f8931af..668416c6dcb 100644 --- a/tests/components/opensky/__init__.py +++ b/tests/components/opensky/__init__.py @@ -1,4 +1,5 @@ """Opensky tests.""" + from unittest.mock import patch from python_opensky import StatesResponse diff --git a/tests/components/otbr/__init__.py b/tests/components/otbr/__init__.py index c839cb0d06e..2c9daa127c2 100644 --- a/tests/components/otbr/__init__.py +++ b/tests/components/otbr/__init__.py @@ -1,4 +1,5 @@ """Tests for the Open Thread Border Router integration.""" + BASE_URL = "http://core-silabs-multiprotocol:8081" CONFIG_ENTRY_DATA_MULTIPAN = {"url": "http://core-silabs-multiprotocol:8081"} CONFIG_ENTRY_DATA_THREAD = {"url": "/dev/ttyAMA1"} diff --git a/tests/components/overkiz/__init__.py b/tests/components/overkiz/__init__.py index 407527b619e..e7d729ea41c 100644 --- a/tests/components/overkiz/__init__.py +++ b/tests/components/overkiz/__init__.py @@ -1,4 +1,5 @@ """Tests for the overkiz component.""" + import humps from pyoverkiz.models import Setup diff --git a/tests/components/persistent_notification/test_init.py b/tests/components/persistent_notification/test_init.py index 921f4b12045..3e99e268231 100644 --- a/tests/components/persistent_notification/test_init.py +++ b/tests/components/persistent_notification/test_init.py @@ -1,6 +1,5 @@ """The tests for the persistent notification component.""" - import homeassistant.components.persistent_notification as pn from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.core import HomeAssistant diff --git a/tests/components/pi_hole/__init__.py b/tests/components/pi_hole/__init__.py index 8295f933d46..38231778624 100644 --- a/tests/components/pi_hole/__init__.py +++ b/tests/components/pi_hole/__init__.py @@ -1,4 +1,5 @@ """Tests for the pi_hole component.""" + from unittest.mock import AsyncMock, MagicMock, patch from hole.exceptions import HoleError diff --git a/tests/components/private_ble_device/test_device_tracker.py b/tests/components/private_ble_device/test_device_tracker.py index 3834254ac7f..9d784ecdfa7 100644 --- a/tests/components/private_ble_device/test_device_tracker.py +++ b/tests/components/private_ble_device/test_device_tracker.py @@ -1,6 +1,5 @@ """Tests for polling measures.""" - import time from habluetooth.advertisement_tracker import ADVERTISING_TIMES_NEEDED diff --git a/tests/components/private_ble_device/test_sensor.py b/tests/components/private_ble_device/test_sensor.py index 15e205c8c86..43667a0e9d2 100644 --- a/tests/components/private_ble_device/test_sensor.py +++ b/tests/components/private_ble_device/test_sensor.py @@ -1,6 +1,5 @@ """Tests for sensors.""" - from habluetooth.advertisement_tracker import ADVERTISING_TIMES_NEEDED from homeassistant.components.bluetooth import async_set_fallback_availability_interval diff --git a/tests/components/qingping/__init__.py b/tests/components/qingping/__init__.py index e06d80e66c5..8b61166f61c 100644 --- a/tests/components/qingping/__init__.py +++ b/tests/components/qingping/__init__.py @@ -1,6 +1,5 @@ """Tests for the Qingping integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_QINGPING_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/radarr/__init__.py b/tests/components/radarr/__init__.py index 47204ebf537..0e6f708d329 100644 --- a/tests/components/radarr/__init__.py +++ b/tests/components/radarr/__init__.py @@ -1,4 +1,5 @@ """Tests for the Radarr component.""" + from http import HTTPStatus from unittest.mock import patch diff --git a/tests/components/rainbird/test_binary_sensor.py b/tests/components/rainbird/test_binary_sensor.py index 77ad640e790..83a45de93ff 100644 --- a/tests/components/rainbird/test_binary_sensor.py +++ b/tests/components/rainbird/test_binary_sensor.py @@ -1,6 +1,5 @@ """Tests for rainbird sensor platform.""" - from http import HTTPStatus import pytest diff --git a/tests/components/rainbird/test_calendar.py b/tests/components/rainbird/test_calendar.py index 497a88e4c3c..6258ac56249 100644 --- a/tests/components/rainbird/test_calendar.py +++ b/tests/components/rainbird/test_calendar.py @@ -1,6 +1,5 @@ """Tests for rainbird calendar platform.""" - from collections.abc import Awaitable, Callable import datetime from http import HTTPStatus diff --git a/tests/components/rainforest_eagle/__init__.py b/tests/components/rainforest_eagle/__init__.py index 3d0a51e8452..67d0a3180d4 100644 --- a/tests/components/rainforest_eagle/__init__.py +++ b/tests/components/rainforest_eagle/__init__.py @@ -1,6 +1,5 @@ """Tests for the Rainforest Eagle integration.""" - MOCK_CLOUD_ID = "12345" MOCK_200_RESPONSE_WITH_PRICE = { "zigbee:InstantaneousDemand": { diff --git a/tests/components/refoss/__init__.py b/tests/components/refoss/__init__.py index 34df1b41714..e7c160ef0af 100644 --- a/tests/components/refoss/__init__.py +++ b/tests/components/refoss/__init__.py @@ -1,4 +1,5 @@ """Common helpers for refoss test cases.""" + import asyncio import logging from unittest.mock import AsyncMock, Mock diff --git a/tests/components/renault/__init__.py b/tests/components/renault/__init__.py index 7053bf8df2d..86fddfd5bac 100644 --- a/tests/components/renault/__init__.py +++ b/tests/components/renault/__init__.py @@ -1,4 +1,5 @@ """Tests for the Renault integration.""" + from __future__ import annotations from types import MappingProxyType diff --git a/tests/components/repairs/__init__.py b/tests/components/repairs/__init__.py index 4d584da1706..a6786db9685 100644 --- a/tests/components/repairs/__init__.py +++ b/tests/components/repairs/__init__.py @@ -1,6 +1,5 @@ """Tests for the repairs integration.""" - from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/roborock/test_vacuum.py b/tests/components/roborock/test_vacuum.py index 61a2ef5d8e2..97a76803b8f 100644 --- a/tests/components/roborock/test_vacuum.py +++ b/tests/components/roborock/test_vacuum.py @@ -1,6 +1,5 @@ """Tests for Roborock vacuums.""" - import copy from typing import Any from unittest.mock import patch diff --git a/tests/components/roku/__init__.py b/tests/components/roku/__init__.py index fc12bb9731d..fe3ef215524 100644 --- a/tests/components/roku/__init__.py +++ b/tests/components/roku/__init__.py @@ -1,4 +1,5 @@ """Tests for the Roku component.""" + from ipaddress import ip_address from homeassistant.components import ssdp, zeroconf diff --git a/tests/components/ruckus_unleashed/__init__.py b/tests/components/ruckus_unleashed/__init__.py index 06ad2352988..97b554b1eb5 100644 --- a/tests/components/ruckus_unleashed/__init__.py +++ b/tests/components/ruckus_unleashed/__init__.py @@ -1,4 +1,5 @@ """Tests for the Ruckus Unleashed integration.""" + from unittest.mock import AsyncMock, patch from aioruckus import AjaxSession, RuckusAjaxApi diff --git a/tests/components/samsungtv/__init__.py b/tests/components/samsungtv/__init__.py index be28d6132ab..f77cd7a9b3e 100644 --- a/tests/components/samsungtv/__init__.py +++ b/tests/components/samsungtv/__init__.py @@ -1,4 +1,5 @@ """Tests for the samsungtv component.""" + from __future__ import annotations from datetime import timedelta diff --git a/tests/components/scrape/__init__.py b/tests/components/scrape/__init__.py index 3d57970a528..de061d051b2 100644 --- a/tests/components/scrape/__init__.py +++ b/tests/components/scrape/__init__.py @@ -1,4 +1,5 @@ """Tests for scrape component.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/screenlogic/__init__.py b/tests/components/screenlogic/__init__.py index ee747a6ca74..c9889e6b4b8 100644 --- a/tests/components/screenlogic/__init__.py +++ b/tests/components/screenlogic/__init__.py @@ -1,4 +1,5 @@ """Tests for the Screenlogic integration.""" + from collections.abc import Callable import logging diff --git a/tests/components/sensibo/__init__.py b/tests/components/sensibo/__init__.py index da585f8d1e8..09a57640472 100644 --- a/tests/components/sensibo/__init__.py +++ b/tests/components/sensibo/__init__.py @@ -1,4 +1,5 @@ """Tests for the Sensibo integration.""" + from __future__ import annotations from homeassistant.const import CONF_API_KEY diff --git a/tests/components/sensor/__init__.py b/tests/components/sensor/__init__.py index 6563041ce99..58e0e8e4c7d 100644 --- a/tests/components/sensor/__init__.py +++ b/tests/components/sensor/__init__.py @@ -1,4 +1,5 @@ """The tests for Sensor platforms.""" + import pytest pytest.register_assert_rewrite("tests.components.recorder.common") diff --git a/tests/components/sensorpro/__init__.py b/tests/components/sensorpro/__init__.py index f92eb700093..da40ff9a3f7 100644 --- a/tests/components/sensorpro/__init__.py +++ b/tests/components/sensorpro/__init__.py @@ -1,6 +1,5 @@ """Tests for the SensorPro integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_SENSORPRO_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/sensorpush/__init__.py b/tests/components/sensorpush/__init__.py index c281d4dc086..aae960970dd 100644 --- a/tests/components/sensorpush/__init__.py +++ b/tests/components/sensorpush/__init__.py @@ -1,6 +1,5 @@ """Tests for the SensorPush integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_SENSOR_PUSH_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/shelly/__init__.py b/tests/components/shelly/__init__.py index 266e2de4920..2dc9012d863 100644 --- a/tests/components/shelly/__init__.py +++ b/tests/components/shelly/__init__.py @@ -1,4 +1,5 @@ """Tests for the Shelly integration.""" + from collections.abc import Mapping from copy import deepcopy from datetime import timedelta diff --git a/tests/components/shelly/bluetooth/__init__.py b/tests/components/shelly/bluetooth/__init__.py index a4b1f4cdb7e..14588d5762c 100644 --- a/tests/components/shelly/bluetooth/__init__.py +++ b/tests/components/shelly/bluetooth/__init__.py @@ -1,2 +1,3 @@ """Bluetooth tests for Shelly integration.""" + from __future__ import annotations diff --git a/tests/components/simplisafe/common.py b/tests/components/simplisafe/common.py index 68d1c4c94b7..27de1224f36 100644 --- a/tests/components/simplisafe/common.py +++ b/tests/components/simplisafe/common.py @@ -1,4 +1,5 @@ """Define common SimpliSafe test constants/etc.""" + REFRESH_TOKEN = "token123" USERNAME = "user@email.com" USER_ID = "12345" diff --git a/tests/components/slack/__init__.py b/tests/components/slack/__init__.py index 6a258ce9027..acb52a11a6c 100644 --- a/tests/components/slack/__init__.py +++ b/tests/components/slack/__init__.py @@ -1,4 +1,5 @@ """Tests for the Slack integration.""" + from __future__ import annotations import json diff --git a/tests/components/sma/__init__.py b/tests/components/sma/__init__.py index 0c6c8a7ee67..aefb99cf1b1 100644 --- a/tests/components/sma/__init__.py +++ b/tests/components/sma/__init__.py @@ -1,4 +1,5 @@ """Tests for the sma integration.""" + from unittest.mock import patch MOCK_DEVICE = { diff --git a/tests/components/smhi/__init__.py b/tests/components/smhi/__init__.py index 377552da4d5..a0bbf854699 100644 --- a/tests/components/smhi/__init__.py +++ b/tests/components/smhi/__init__.py @@ -1,4 +1,5 @@ """Tests for the SMHI component.""" + ENTITY_ID = "weather.smhi_test" TEST_CONFIG = { "name": "test", diff --git a/tests/components/snooz/__init__.py b/tests/components/snooz/__init__.py index 1e414fb337c..3f04ddbbc03 100644 --- a/tests/components/snooz/__init__.py +++ b/tests/components/snooz/__init__.py @@ -1,4 +1,5 @@ """Tests for the Snooz component.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/tests/components/sonarr/__init__.py b/tests/components/sonarr/__init__.py index ca9fc91bd5e..b6050808a34 100644 --- a/tests/components/sonarr/__init__.py +++ b/tests/components/sonarr/__init__.py @@ -1,4 +1,5 @@ """Tests for the Sonarr component.""" + from homeassistant.const import CONF_API_KEY, CONF_URL MOCK_REAUTH_INPUT = {CONF_API_KEY: "test-api-key-reauth"} diff --git a/tests/components/songpal/__init__.py b/tests/components/songpal/__init__.py index d98ec4175fc..6ebc2ec5ef4 100644 --- a/tests/components/songpal/__init__.py +++ b/tests/components/songpal/__init__.py @@ -1,4 +1,5 @@ """Test the songpal integration.""" + from unittest.mock import AsyncMock, MagicMock, patch from songpal import SongpalException diff --git a/tests/components/sql/__init__.py b/tests/components/sql/__init__.py index 1d3ce0878c3..fc122ad1a95 100644 --- a/tests/components/sql/__init__.py +++ b/tests/components/sql/__init__.py @@ -1,4 +1,5 @@ """Tests for the sql component.""" + from __future__ import annotations from typing import Any diff --git a/tests/components/steam_online/__init__.py b/tests/components/steam_online/__init__.py index 786c5d67782..83fdfdda5de 100644 --- a/tests/components/steam_online/__init__.py +++ b/tests/components/steam_online/__init__.py @@ -1,4 +1,5 @@ """Tests for Steam integration.""" + import random import string from unittest.mock import patch diff --git a/tests/components/steamist/__init__.py b/tests/components/steamist/__init__.py index 8761d675d54..47fa2236849 100644 --- a/tests/components/steamist/__init__.py +++ b/tests/components/steamist/__init__.py @@ -1,4 +1,5 @@ """Tests for the Steamist integration.""" + from __future__ import annotations from contextlib import contextmanager diff --git a/tests/components/streamlabswater/__init__.py b/tests/components/streamlabswater/__init__.py index a467c9553de..f8776708887 100644 --- a/tests/components/streamlabswater/__init__.py +++ b/tests/components/streamlabswater/__init__.py @@ -1,4 +1,5 @@ """Tests for the StreamLabs integration.""" + from homeassistant.core import HomeAssistant from homeassistant.util.unit_system import IMPERIAL_SYSTEM diff --git a/tests/components/switchbot/__init__.py b/tests/components/switchbot/__init__.py index 257501ea196..a5adab4c77f 100644 --- a/tests/components/switchbot/__init__.py +++ b/tests/components/switchbot/__init__.py @@ -1,4 +1,5 @@ """Tests for the switchbot integration.""" + from unittest.mock import patch from homeassistant.components.bluetooth import BluetoothServiceInfoBleak diff --git a/tests/components/switchbot_cloud/__init__.py b/tests/components/switchbot_cloud/__init__.py index 72d23c837ac..ce570499b3a 100644 --- a/tests/components/switchbot_cloud/__init__.py +++ b/tests/components/switchbot_cloud/__init__.py @@ -1,4 +1,5 @@ """Tests for the SwitchBot Cloud integration.""" + from homeassistant.components.switchbot_cloud.const import DOMAIN from homeassistant.const import CONF_API_KEY, CONF_API_TOKEN from homeassistant.core import HomeAssistant diff --git a/tests/components/switcher_kis/__init__.py b/tests/components/switcher_kis/__init__.py index 671af5e11b9..3f08afcbc9f 100644 --- a/tests/components/switcher_kis/__init__.py +++ b/tests/components/switcher_kis/__init__.py @@ -1,4 +1,5 @@ """Test cases and object for the Switcher integration tests.""" + from homeassistant.components.switcher_kis.const import DOMAIN from homeassistant.core import HomeAssistant diff --git a/tests/components/technove/__init__.py b/tests/components/technove/__init__.py index 2d9f639244f..6afffc41e00 100644 --- a/tests/components/technove/__init__.py +++ b/tests/components/technove/__init__.py @@ -1,4 +1,5 @@ """Tests for the TechnoVE integration.""" + from unittest.mock import patch from homeassistant.const import Platform diff --git a/tests/components/tedee/test_sensor.py b/tests/components/tedee/test_sensor.py index 274048082c0..72fbd9cbe8d 100644 --- a/tests/components/tedee/test_sensor.py +++ b/tests/components/tedee/test_sensor.py @@ -1,6 +1,5 @@ """Tests for the Tedee Sensors.""" - from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/thermobeacon/__init__.py b/tests/components/thermobeacon/__init__.py index 1ff1ad20df1..2f7e220ebaa 100644 --- a/tests/components/thermobeacon/__init__.py +++ b/tests/components/thermobeacon/__init__.py @@ -1,6 +1,5 @@ """Tests for the ThermoBeacon integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_THERMOBEACON_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/thermopro/__init__.py b/tests/components/thermopro/__init__.py index f66b608f6d3..264e556756c 100644 --- a/tests/components/thermopro/__init__.py +++ b/tests/components/thermopro/__init__.py @@ -1,6 +1,5 @@ """Tests for the ThermoPro integration.""" - from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo NOT_THERMOPRO_SERVICE_INFO = BluetoothServiceInfo( diff --git a/tests/components/tradfri/__init__.py b/tests/components/tradfri/__init__.py index 01b5edf5c44..37792ae7e32 100644 --- a/tests/components/tradfri/__init__.py +++ b/tests/components/tradfri/__init__.py @@ -1,3 +1,4 @@ """Tests for the tradfri component.""" + GATEWAY_ID = "mock-gateway-id" TRADFRI_PATH = "homeassistant.components.tradfri" diff --git a/tests/components/trafikverket_camera/__init__.py b/tests/components/trafikverket_camera/__init__.py index dd23c7bce7e..9eb6c471c1e 100644 --- a/tests/components/trafikverket_camera/__init__.py +++ b/tests/components/trafikverket_camera/__init__.py @@ -1,4 +1,5 @@ """Tests for the Trafikverket Camera integration.""" + from __future__ import annotations from homeassistant.const import CONF_API_KEY, CONF_ID, CONF_LOCATION diff --git a/tests/components/trafikverket_ferry/__init__.py b/tests/components/trafikverket_ferry/__init__.py index 97bedb30281..5f01b1b65ec 100644 --- a/tests/components/trafikverket_ferry/__init__.py +++ b/tests/components/trafikverket_ferry/__init__.py @@ -1,4 +1,5 @@ """Tests for the Trafikverket Ferry integration.""" + from __future__ import annotations from homeassistant.components.trafikverket_ferry.const import ( diff --git a/tests/components/trafikverket_train/__init__.py b/tests/components/trafikverket_train/__init__.py index 9a02ebbf3b6..632f082c73b 100644 --- a/tests/components/trafikverket_train/__init__.py +++ b/tests/components/trafikverket_train/__init__.py @@ -1,4 +1,5 @@ """Tests for the Trafikverket Train integration.""" + from __future__ import annotations from homeassistant.components.trafikverket_ferry.const import ( diff --git a/tests/components/twinkly/__init__.py b/tests/components/twinkly/__init__.py index 4b1411e9223..c77dd0ac963 100644 --- a/tests/components/twinkly/__init__.py +++ b/tests/components/twinkly/__init__.py @@ -1,6 +1,5 @@ """Constants and mock for the twinkly component tests.""" - from aiohttp.client_exceptions import ClientConnectionError from homeassistant.components.twinkly.const import DEV_NAME diff --git a/tests/components/twitch/__init__.py b/tests/components/twitch/__init__.py index 26746c7abb4..ba49390ebdf 100644 --- a/tests/components/twitch/__init__.py +++ b/tests/components/twitch/__init__.py @@ -1,4 +1,5 @@ """Tests for the Twitch component.""" + import asyncio from collections.abc import AsyncGenerator, AsyncIterator from dataclasses import dataclass diff --git a/tests/components/usb/__init__.py b/tests/components/usb/__init__.py index 7dbfdfdcff6..f5f32336931 100644 --- a/tests/components/usb/__init__.py +++ b/tests/components/usb/__init__.py @@ -1,6 +1,5 @@ """Tests for the USB Discovery integration.""" - from homeassistant.components.usb.models import USBDevice conbee_device = USBDevice( diff --git a/tests/components/velbus/const.py b/tests/components/velbus/const.py index 374dbce2529..427ed2e42b7 100644 --- a/tests/components/velbus/const.py +++ b/tests/components/velbus/const.py @@ -1,3 +1,4 @@ """Constants for the Velbus tests.""" + PORT_SERIAL = "/dev/ttyACME100" PORT_TCP = "127.0.1.0.1:3788" diff --git a/tests/components/venstar/__init__.py b/tests/components/venstar/__init__.py index f91f8f28bdf..116a3be0925 100644 --- a/tests/components/venstar/__init__.py +++ b/tests/components/venstar/__init__.py @@ -1,4 +1,5 @@ """Tests for the venstar integration.""" + from requests import RequestException diff --git a/tests/components/version/test_diagnostics.py b/tests/components/version/test_diagnostics.py index 91bbcd3aa9f..9a436aa0740 100644 --- a/tests/components/version/test_diagnostics.py +++ b/tests/components/version/test_diagnostics.py @@ -1,6 +1,5 @@ """Test version diagnostics.""" - from homeassistant.core import HomeAssistant from .common import MOCK_VERSION, setup_version_integration diff --git a/tests/components/vicare/__init__.py b/tests/components/vicare/__init__.py index 8c0f7941ba6..329a3b04d58 100644 --- a/tests/components/vicare/__init__.py +++ b/tests/components/vicare/__init__.py @@ -1,4 +1,5 @@ """Test for ViCare.""" + from __future__ import annotations from typing import Final diff --git a/tests/components/wallbox/const.py b/tests/components/wallbox/const.py index 00ede14771b..452b3af0af8 100644 --- a/tests/components/wallbox/const.py +++ b/tests/components/wallbox/const.py @@ -1,4 +1,5 @@ """Provides constants for Wallbox component tests.""" + JWT = "jwt" USER_ID = "user_id" TTL = "ttl" diff --git a/tests/components/weather/__init__.py b/tests/components/weather/__init__.py index 35a818735d0..c24baad5237 100644 --- a/tests/components/weather/__init__.py +++ b/tests/components/weather/__init__.py @@ -1,6 +1,5 @@ """The tests for Weather platforms.""" - from typing import Any from homeassistant.components.weather import ( diff --git a/tests/components/weatherkit/__init__.py b/tests/components/weatherkit/__init__.py index 5118c44c45b..5450205d579 100644 --- a/tests/components/weatherkit/__init__.py +++ b/tests/components/weatherkit/__init__.py @@ -1,4 +1,5 @@ """Tests for the Apple WeatherKit integration.""" + from unittest.mock import patch from apple_weatherkit import DataSetType diff --git a/tests/components/whirlpool/__init__.py b/tests/components/whirlpool/__init__.py index d47fb5337fd..eda7541ca56 100644 --- a/tests/components/whirlpool/__init__.py +++ b/tests/components/whirlpool/__init__.py @@ -1,4 +1,5 @@ """Tests for the Whirlpool Sixth Sense integration.""" + from homeassistant.components.whirlpool.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant diff --git a/tests/components/withings/__init__.py b/tests/components/withings/__init__.py index cd0e9994f74..4b97fc48834 100644 --- a/tests/components/withings/__init__.py +++ b/tests/components/withings/__init__.py @@ -1,4 +1,5 @@ """Tests for the withings component.""" + from dataclasses import dataclass from datetime import timedelta from typing import Any diff --git a/tests/components/wyoming/__init__.py b/tests/components/wyoming/__init__.py index 6b049b04c42..45fff55a89c 100644 --- a/tests/components/wyoming/__init__.py +++ b/tests/components/wyoming/__init__.py @@ -1,4 +1,5 @@ """Tests for the Wyoming integration.""" + import asyncio from unittest.mock import patch diff --git a/tests/components/xiaomi_ble/__init__.py b/tests/components/xiaomi_ble/__init__.py index 197745b70f1..40bd965fd9d 100644 --- a/tests/components/xiaomi_ble/__init__.py +++ b/tests/components/xiaomi_ble/__init__.py @@ -1,6 +1,5 @@ """Tests for the SensorPush integration.""" - from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from tests.components.bluetooth import generate_advertisement_data, generate_ble_device diff --git a/tests/components/xiaomi_miio/__init__.py b/tests/components/xiaomi_miio/__init__.py index 24e66e16b08..ceee4aea744 100644 --- a/tests/components/xiaomi_miio/__init__.py +++ b/tests/components/xiaomi_miio/__init__.py @@ -1,2 +1,3 @@ """Tests for the Xiaomi Miio integration.""" + TEST_MAC = "ab:cd:ef:gh:ij:kl" diff --git a/tests/components/yeelight/__init__.py b/tests/components/yeelight/__init__.py index c7d279220f8..6c940b0b229 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -1,4 +1,5 @@ """Tests for the Yeelight integration.""" + from datetime import timedelta from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/components/youtube/__init__.py b/tests/components/youtube/__init__.py index 665f5f3a762..62808bc7ad9 100644 --- a/tests/components/youtube/__init__.py +++ b/tests/components/youtube/__init__.py @@ -1,4 +1,5 @@ """Tests for the YouTube integration.""" + from collections.abc import AsyncGenerator import json diff --git a/tests/helpers/test_group.py b/tests/helpers/test_group.py index b1300009607..26f4ffda256 100644 --- a/tests/helpers/test_group.py +++ b/tests/helpers/test_group.py @@ -1,6 +1,5 @@ """Test the group helper.""" - from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import group diff --git a/tests/ignore_uncaught_exceptions.py b/tests/ignore_uncaught_exceptions.py index e9327c0255a..3be2093057b 100644 --- a/tests/ignore_uncaught_exceptions.py +++ b/tests/ignore_uncaught_exceptions.py @@ -1,4 +1,5 @@ """List of tests that have uncaught exceptions today. Will be shrunk over time.""" + IGNORE_UNCAUGHT_EXCEPTIONS = [ ( # This test explicitly throws an uncaught exception diff --git a/tests/pylint/__init__.py b/tests/pylint/__init__.py index e03a2d2a118..abe4c14c879 100644 --- a/tests/pylint/__init__.py +++ b/tests/pylint/__init__.py @@ -1,4 +1,5 @@ """Tests for pylint.""" + import contextlib from pylint.testutils.unittest_linter import UnittestLinter diff --git a/tests/test_const.py b/tests/test_const.py index b43f677ba8f..5b7cf851fcc 100644 --- a/tests/test_const.py +++ b/tests/test_const.py @@ -1,6 +1,5 @@ """Test const module.""" - from enum import Enum import pytest diff --git a/tests/test_util/__init__.py b/tests/test_util/__init__.py index fe2c2c640e5..dd88af965fe 100644 --- a/tests/test_util/__init__.py +++ b/tests/test_util/__init__.py @@ -1,4 +1,5 @@ """Test utilities.""" + from collections.abc import Awaitable, Callable from aiohttp.web import Application, Request, StreamResponse, middleware diff --git a/tests/testing_config/custom_components/test_embedded/__init__.py b/tests/testing_config/custom_components/test_embedded/__init__.py index 1d861392bf3..b83493817fd 100644 --- a/tests/testing_config/custom_components/test_embedded/__init__.py +++ b/tests/testing_config/custom_components/test_embedded/__init__.py @@ -1,4 +1,5 @@ """Component with embedded platforms.""" + DOMAIN = "test_embedded" diff --git a/tests/testing_config/custom_components/test_integration_platform/__init__.py b/tests/testing_config/custom_components/test_integration_platform/__init__.py index 6b70949231b..220beb05367 100644 --- a/tests/testing_config/custom_components/test_integration_platform/__init__.py +++ b/tests/testing_config/custom_components/test_integration_platform/__init__.py @@ -1,4 +1,5 @@ """Provide a mock package component.""" + from .const import TEST # noqa: F401 DOMAIN = "test_integration_platform" diff --git a/tests/testing_config/custom_components/test_integration_platform/const.py b/tests/testing_config/custom_components/test_integration_platform/const.py index 7e13e04cb47..e55504e7f4f 100644 --- a/tests/testing_config/custom_components/test_integration_platform/const.py +++ b/tests/testing_config/custom_components/test_integration_platform/const.py @@ -1,2 +1,3 @@ """Constants for test_package custom component.""" + TEST = 5 diff --git a/tests/testing_config/custom_components/test_package/__init__.py b/tests/testing_config/custom_components/test_package/__init__.py index 44f62380c92..50e132e2c07 100644 --- a/tests/testing_config/custom_components/test_package/__init__.py +++ b/tests/testing_config/custom_components/test_package/__init__.py @@ -1,4 +1,5 @@ """Provide a mock package component.""" + from .const import TEST # noqa: F401 DOMAIN = "test_package" diff --git a/tests/testing_config/custom_components/test_package/const.py b/tests/testing_config/custom_components/test_package/const.py index 7e13e04cb47..e55504e7f4f 100644 --- a/tests/testing_config/custom_components/test_package/const.py +++ b/tests/testing_config/custom_components/test_package/const.py @@ -1,2 +1,3 @@ """Constants for test_package custom component.""" + TEST = 5 diff --git a/tests/testing_config/custom_components/test_package_loaded_executor/__init__.py b/tests/testing_config/custom_components/test_package_loaded_executor/__init__.py index 44f62380c92..50e132e2c07 100644 --- a/tests/testing_config/custom_components/test_package_loaded_executor/__init__.py +++ b/tests/testing_config/custom_components/test_package_loaded_executor/__init__.py @@ -1,4 +1,5 @@ """Provide a mock package component.""" + from .const import TEST # noqa: F401 DOMAIN = "test_package" diff --git a/tests/testing_config/custom_components/test_package_loaded_executor/const.py b/tests/testing_config/custom_components/test_package_loaded_executor/const.py index 7e13e04cb47..e55504e7f4f 100644 --- a/tests/testing_config/custom_components/test_package_loaded_executor/const.py +++ b/tests/testing_config/custom_components/test_package_loaded_executor/const.py @@ -1,2 +1,3 @@ """Constants for test_package custom component.""" + TEST = 5 diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py b/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py index e77df90a00b..37d3becb2d3 100644 --- a/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error/__init__.py @@ -1,4 +1,5 @@ """Provide a mock package component.""" + import asyncio diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py index 1283e79f21b..55ce19865c6 100644 --- a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/__init__.py @@ -1,4 +1,5 @@ """Provide a mock package component.""" + import asyncio diff --git a/tests/testing_config/custom_components/test_standalone.py b/tests/testing_config/custom_components/test_standalone.py index b3acc9917c1..0b7ce8033e5 100644 --- a/tests/testing_config/custom_components/test_standalone.py +++ b/tests/testing_config/custom_components/test_standalone.py @@ -1,4 +1,5 @@ """Provide a mock standalone component.""" + DOMAIN = "test_standalone" From c7eabd95e66a99c4a6fcf42a0c3bc10adc90d611 Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Fri, 8 Mar 2024 21:28:03 +0100 Subject: [PATCH 0572/1691] Bump pyenphase to 1.19.2 (#112747) --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 63e10547ead..9f437ee9945 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "iot_class": "local_polling", "loggers": ["pyenphase"], - "requirements": ["pyenphase==1.19.1"], + "requirements": ["pyenphase==1.19.2"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index 0cb57a27bc7..639a6423030 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1788,7 +1788,7 @@ pyedimax==0.2.1 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.19.1 +pyenphase==1.19.2 # homeassistant.components.envisalink pyenvisalink==4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d0ba8c7e25e..d8b926b6687 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1384,7 +1384,7 @@ pyeconet==0.1.22 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.19.1 +pyenphase==1.19.2 # homeassistant.components.everlights pyeverlights==0.1.0 From cef20506dce9e3cc54a2dd924d1e3f965357f31e Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 21:56:42 +0100 Subject: [PATCH 0573/1691] Replace EventType with Event [missing] (#112753) --- homeassistant/components/bthome/logbook.py | 7 +++---- homeassistant/components/conversation/default_agent.py | 5 ++--- homeassistant/components/image/__init__.py | 4 ++-- homeassistant/components/mqtt/mixins.py | 3 +-- homeassistant/components/proximity/coordinator.py | 6 +++--- homeassistant/components/switch_as_x/__init__.py | 5 ++--- homeassistant/helpers/entity.py | 6 +++--- homeassistant/helpers/floor_registry.py | 6 +++--- homeassistant/helpers/integration_platform.py | 6 ++---- homeassistant/helpers/label_registry.py | 6 +++--- homeassistant/setup.py | 4 ++-- tests/components/esphome/test_entity.py | 5 ++--- 12 files changed, 28 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/bthome/logbook.py b/homeassistant/components/bthome/logbook.py index 475fa84fb76..23976e368ad 100644 --- a/homeassistant/components/bthome/logbook.py +++ b/homeassistant/components/bthome/logbook.py @@ -5,9 +5,8 @@ from __future__ import annotations from collections.abc import Callable from homeassistant.components.logbook import LOGBOOK_ENTRY_MESSAGE, LOGBOOK_ENTRY_NAME -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.device_registry import async_get -from homeassistant.helpers.typing import EventType from .const import BTHOME_BLE_EVENT, DOMAIN, BTHomeBleEvent @@ -16,14 +15,14 @@ from .const import BTHOME_BLE_EVENT, DOMAIN, BTHomeBleEvent def async_describe_events( hass: HomeAssistant, async_describe_event: Callable[ - [str, str, Callable[[EventType[BTHomeBleEvent]], dict[str, str]]], None + [str, str, Callable[[Event[BTHomeBleEvent]], dict[str, str]]], None ], ) -> None: """Describe logbook events.""" dr = async_get(hass) @callback - def async_describe_bthome_event(event: EventType[BTHomeBleEvent]) -> dict[str, str]: + def async_describe_bthome_event(event: Event[BTHomeBleEvent]) -> dict[str, str]: """Describe bthome logbook event.""" data = event.data device = dr.async_get(data["device_id"]) diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 4d7ca4a47ee..4ca5c85676e 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -43,7 +43,6 @@ from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_added_domain, ) -from homeassistant.helpers.typing import EventType from homeassistant.util.json import JsonObjectType, json_loads_object from .agent import AbstractConversationAgent, ConversationInput, ConversationResult @@ -696,14 +695,14 @@ class DefaultAgent(AbstractConversationAgent): @core.callback def _async_handle_area_registry_changed( - self, event: EventType[ar.EventAreaRegistryUpdatedData] + self, event: core.Event[ar.EventAreaRegistryUpdatedData] ) -> None: """Clear area area cache when the area registry has changed.""" self._slot_lists = None @core.callback def _async_handle_entity_registry_changed( - self, event: EventType[er.EventEntityRegistryUpdatedData] + self, event: core.Event[er.EventEntityRegistryUpdatedData] ) -> None: """Clear names list cache when an entity registry entry has changed.""" if event.data["action"] != "update" or not any( diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index 101a1cdf8c4..02ebee49701 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -31,7 +31,7 @@ from homeassistant.helpers.event import ( async_track_time_interval, ) from homeassistant.helpers.httpx_client import get_async_client -from homeassistant.helpers.typing import UNDEFINED, ConfigType, EventType, UndefinedType +from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType from .const import DOMAIN, IMAGE_TIMEOUT # noqa: F401 @@ -342,7 +342,7 @@ async def async_get_still_stream( event = asyncio.Event() - async def image_state_update(_event: EventType[EventStateChangedData]) -> None: + async def image_state_update(_event: Event[EventStateChangedData]) -> None: """Write image to stream.""" event.set() diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index b6d83e2d420..84afe26bc97 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -60,7 +60,6 @@ from homeassistant.helpers.typing import ( UNDEFINED, ConfigType, DiscoveryInfoType, - EventType, UndefinedType, ) from homeassistant.util.json import json_loads @@ -769,7 +768,7 @@ async def async_remove_discovery_payload( async def async_clear_discovery_topic_if_entity_removed( hass: HomeAssistant, discovery_data: DiscoveryInfoType, - event: EventType[er.EventEntityRegistryUpdatedData], + event: Event[er.EventEntityRegistryUpdatedData], ) -> None: """Clear the discovery topic if the entity is removed.""" if event.data["action"] == "remove": diff --git a/homeassistant/components/proximity/coordinator.py b/homeassistant/components/proximity/coordinator.py index 047ab1b6b3a..d5f4a33d31e 100644 --- a/homeassistant/components/proximity/coordinator.py +++ b/homeassistant/components/proximity/coordinator.py @@ -15,10 +15,10 @@ from homeassistant.const import ( CONF_ZONE, UnitOfLength, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from homeassistant.helpers.typing import ConfigType, EventType +from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.location import distance from homeassistant.util.unit_conversion import DistanceConverter @@ -107,7 +107,7 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): await self.async_refresh() async def async_check_tracked_entity_change( - self, event: EventType[er.EventEntityRegistryUpdatedData] + self, event: Event[er.EventEntityRegistryUpdatedData] ) -> None: """Fetch and process tracked entity change event.""" data = event.data diff --git a/homeassistant/components/switch_as_x/__init__.py b/homeassistant/components/switch_as_x/__init__.py index 23de494396e..71cb9e9c225 100644 --- a/homeassistant/components/switch_as_x/__init__.py +++ b/homeassistant/components/switch_as_x/__init__.py @@ -9,10 +9,9 @@ import voluptuous as vol from homeassistant.components.homeassistant import exposed_entities from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ENTITY_ID -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.event import async_track_entity_registry_updated_event -from homeassistant.helpers.typing import EventType from .const import CONF_INVERT, CONF_TARGET_DOMAIN from .light import LightSwitch @@ -58,7 +57,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return False async def async_registry_updated( - event: EventType[er.EventEntityRegistryUpdatedData], + event: Event[er.EventEntityRegistryUpdatedData], ) -> None: """Handle entity registry update.""" data = event.data diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 6318cf8e13b..767b24a67a6 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -68,7 +68,7 @@ from .event import ( async_track_device_registry_updated_event, async_track_entity_registry_updated_event, ) -from .typing import UNDEFINED, EventType, StateType, UndefinedType +from .typing import UNDEFINED, StateType, UndefinedType if TYPE_CHECKING: from functools import cached_property @@ -1455,7 +1455,7 @@ class Entity( @callback def _async_registry_updated( - self, event: EventType[er.EventEntityRegistryUpdatedData] + self, event: Event[er.EventEntityRegistryUpdatedData] ) -> None: """Handle entity registry update.""" action = event.data["action"] @@ -1467,7 +1467,7 @@ class Entity( ) async def _async_process_registry_update_or_remove( - self, event: EventType[er.EventEntityRegistryUpdatedData] + self, event: Event[er.EventEntityRegistryUpdatedData] ) -> None: """Handle entity registry update or remove.""" data = event.data diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index a701cd5bf79..eba3a05cea8 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -7,7 +7,7 @@ import dataclasses from dataclasses import dataclass from typing import TYPE_CHECKING, Literal, TypedDict, cast -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.util import slugify from .normalized_name_base_registry import ( @@ -17,7 +17,7 @@ from .normalized_name_base_registry import ( ) from .registry import BaseRegistry from .storage import Store -from .typing import UNDEFINED, EventType, UndefinedType +from .typing import UNDEFINED, UndefinedType DATA_REGISTRY = "floor_registry" EVENT_FLOOR_REGISTRY_UPDATED = "floor_registry_updated" @@ -32,7 +32,7 @@ class EventFloorRegistryUpdatedData(TypedDict): floor_id: str -EventFloorRegistryUpdated = EventType[EventFloorRegistryUpdatedData] +EventFloorRegistryUpdated = Event[EventFloorRegistryUpdatedData] @dataclass(slots=True, kw_only=True, frozen=True) diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 54e4e0f9fb9..e142f9c2e5a 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -11,7 +11,7 @@ from types import ModuleType from typing import Any from homeassistant.const import EVENT_COMPONENT_LOADED -from homeassistant.core import HassJob, HomeAssistant, callback +from homeassistant.core import Event, HassJob, HomeAssistant, callback from homeassistant.loader import ( Integration, async_get_integrations, @@ -22,8 +22,6 @@ from homeassistant.loader import ( from homeassistant.setup import ATTR_COMPONENT, EventComponentLoaded from homeassistant.util.logging import catch_log_exception -from .typing import EventType - _LOGGER = logging.getLogger(__name__) DATA_INTEGRATION_PLATFORMS = "integration_platforms" @@ -41,7 +39,7 @@ class IntegrationPlatform: def _async_integration_platform_component_loaded( hass: HomeAssistant, integration_platforms: list[IntegrationPlatform], - event: EventType[EventComponentLoaded], + event: Event[EventComponentLoaded], ) -> None: """Process integration platforms for a component.""" if "." in (component_name := event.data[ATTR_COMPONENT]): diff --git a/homeassistant/helpers/label_registry.py b/homeassistant/helpers/label_registry.py index 666a573fe43..b3ca89140a1 100644 --- a/homeassistant/helpers/label_registry.py +++ b/homeassistant/helpers/label_registry.py @@ -7,7 +7,7 @@ import dataclasses from dataclasses import dataclass from typing import Literal, TypedDict, cast -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.util import slugify from .normalized_name_base_registry import ( @@ -17,7 +17,7 @@ from .normalized_name_base_registry import ( ) from .registry import BaseRegistry from .storage import Store -from .typing import UNDEFINED, EventType, UndefinedType +from .typing import UNDEFINED, UndefinedType DATA_REGISTRY = "label_registry" EVENT_LABEL_REGISTRY_UPDATED = "label_registry_updated" @@ -32,7 +32,7 @@ class EventLabelRegistryUpdatedData(TypedDict): label_id: str -EventLabelRegistryUpdated = EventType[EventLabelRegistryUpdatedData] +EventLabelRegistryUpdated = Event[EventLabelRegistryUpdatedData] @dataclass(slots=True, frozen=True, kw_only=True) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 3ec72654a64..4b920bf364b 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -28,7 +28,7 @@ from .core import ( from .exceptions import DependencyError, HomeAssistantError from .helpers import translation from .helpers.issue_registry import IssueSeverity, async_create_issue -from .helpers.typing import ConfigType, EventType +from .helpers.typing import ConfigType from .util import ensure_unique_string from .util.async_ import create_eager_task @@ -592,7 +592,7 @@ def _async_when_setup( await when_setup() @callback - def _async_is_component_filter(event: EventType[EventComponentLoaded]) -> bool: + def _async_is_component_filter(event: Event[EventComponentLoaded]) -> bool: """Check if the event is for the component.""" return event.data[ATTR_COMPONENT] == component diff --git a/tests/components/esphome/test_entity.py b/tests/components/esphome/test_entity.py index 4056c286b51..303d50f3103 100644 --- a/tests/components/esphome/test_entity.py +++ b/tests/components/esphome/test_entity.py @@ -23,13 +23,12 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, ) -from homeassistant.helpers.typing import EventType from .conftest import MockESPHomeDevice @@ -225,7 +224,7 @@ async def test_entities_removed_after_reload( on_future = hass.loop.create_future() @callback - def _async_wait_for_on(event: EventType[EventStateChangedData]) -> None: + def _async_wait_for_on(event: Event[EventStateChangedData]) -> None: if event.data["new_state"].state == STATE_ON: on_future.set_result(None) From de886d8c4955b72275bd51974814c87def331d28 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 8 Mar 2024 21:57:23 +0100 Subject: [PATCH 0574/1691] Update EventBus listen type signatures (#112760) --- homeassistant/components/apache_kafka/__init__.py | 2 +- homeassistant/components/api/__init__.py | 2 +- .../components/conversation/default_agent.py | 6 +++--- homeassistant/components/logbook/helpers.py | 2 +- homeassistant/components/prometheus/__init__.py | 4 ++-- homeassistant/core.py | 12 ++++++------ homeassistant/helpers/area_registry.py | 8 ++++---- homeassistant/helpers/device_registry.py | 4 ++-- homeassistant/helpers/entity_registry.py | 4 ++-- homeassistant/helpers/event.py | 10 +++------- homeassistant/setup.py | 4 ++-- 11 files changed, 27 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/apache_kafka/__init__.py b/homeassistant/components/apache_kafka/__init__.py index f7abfcf66ef..fb29c0d5e49 100644 --- a/homeassistant/components/apache_kafka/__init__.py +++ b/homeassistant/components/apache_kafka/__init__.py @@ -133,7 +133,7 @@ class KafkaManager: async def start(self) -> None: """Start the Kafka manager.""" - self._hass.bus.async_listen(EVENT_STATE_CHANGED, self.write) # type: ignore[arg-type] + self._hass.bus.async_listen(EVENT_STATE_CHANGED, self.write) await self._producer.start() async def shutdown(self, _: Event) -> None: diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index c4543b55514..a427957de15 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -397,7 +397,7 @@ class APIDomainServicesView(HomeAssistantView): cancel_listen = hass.bus.async_listen( EVENT_STATE_CHANGED, - _async_save_changed_entities, # type: ignore[arg-type] + _async_save_changed_entities, run_immediately=True, ) diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 4ca5c85676e..b07919c9d5c 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -161,17 +161,17 @@ class DefaultAgent(AbstractConversationAgent): self.hass.bus.async_listen( ar.EVENT_AREA_REGISTRY_UPDATED, - self._async_handle_area_registry_changed, # type: ignore[arg-type] + self._async_handle_area_registry_changed, run_immediately=True, ) self.hass.bus.async_listen( er.EVENT_ENTITY_REGISTRY_UPDATED, - self._async_handle_entity_registry_changed, # type: ignore[arg-type] + self._async_handle_entity_registry_changed, run_immediately=True, ) self.hass.bus.async_listen( EVENT_STATE_CHANGED, - self._async_handle_state_changed, # type: ignore[arg-type] + self._async_handle_state_changed, run_immediately=True, ) async_listen_entity_updates( diff --git a/homeassistant/components/logbook/helpers.py b/homeassistant/components/logbook/helpers.py index 4f534c74981..e3288452fb7 100644 --- a/homeassistant/components/logbook/helpers.py +++ b/homeassistant/components/logbook/helpers.py @@ -211,7 +211,7 @@ def async_subscribe_events( subscriptions.append( hass.bus.async_listen( EVENT_STATE_CHANGED, - _forward_state_events_filtered, # type: ignore[arg-type] + _forward_state_events_filtered, run_immediately=True, ) ) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 22b6bdaa78f..d3a307a6616 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -132,10 +132,10 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: default_metric, ) - hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_state_changed_event) # type: ignore[arg-type] + hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_state_changed_event) hass.bus.listen( EVENT_ENTITY_REGISTRY_UPDATED, - metrics.handle_entity_registry_updated, # type: ignore[arg-type] + metrics.handle_entity_registry_updated, ) for state in hass.states.all(): diff --git a/homeassistant/core.py b/homeassistant/core.py index a44f8d894ca..a386f4725fe 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1406,7 +1406,7 @@ class EventBus: def listen( self, event_type: str, - listener: Callable[[Event], Coroutine[Any, Any, None] | None], + listener: Callable[[Event[Any]], Coroutine[Any, Any, None] | None], ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. @@ -1427,8 +1427,8 @@ class EventBus: def async_listen( self, event_type: str, - listener: Callable[[Event], Coroutine[Any, Any, None] | None], - event_filter: Callable[[Event], bool] | None = None, + listener: Callable[[Event[_DataT]], Coroutine[Any, Any, None] | None], + event_filter: Callable[[Event[_DataT]], bool] | None = None, run_immediately: bool = False, ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. @@ -1464,7 +1464,7 @@ class EventBus: @callback def _async_listen_filterable_job( - self, event_type: str, filterable_job: _FilterableJobType + self, event_type: str, filterable_job: _FilterableJobType[Any] ) -> CALLBACK_TYPE: self._listeners.setdefault(event_type, []).append(filterable_job) return functools.partial( @@ -1474,7 +1474,7 @@ class EventBus: def listen_once( self, event_type: str, - listener: Callable[[Event], Coroutine[Any, Any, None] | None], + listener: Callable[[Event[Any]], Coroutine[Any, Any, None] | None], ) -> CALLBACK_TYPE: """Listen once for event of a specific type. @@ -1497,7 +1497,7 @@ class EventBus: def async_listen_once( self, event_type: str, - listener: Callable[[Event], Coroutine[Any, Any, None] | None], + listener: Callable[[Event[Any]], Coroutine[Any, Any, None] | None], ) -> CALLBACK_TYPE: """Listen once for event of a specific type. diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index f9f4abc6468..5a14eb3ad16 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -329,8 +329,8 @@ class AreaRegistry(BaseRegistry): self.hass.bus.async_listen( event_type=fr.EVENT_FLOOR_REGISTRY_UPDATED, - event_filter=_removed_from_registry_filter, # type: ignore[arg-type] - listener=_handle_floor_registry_update, # type: ignore[arg-type] + event_filter=_removed_from_registry_filter, + listener=_handle_floor_registry_update, ) @callback @@ -345,8 +345,8 @@ class AreaRegistry(BaseRegistry): self.hass.bus.async_listen( event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, - event_filter=_removed_from_registry_filter, # type: ignore[arg-type] - listener=_handle_label_registry_update, # type: ignore[arg-type] + event_filter=_removed_from_registry_filter, + listener=_handle_label_registry_update, ) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 85d0e8f9966..e823f356f4e 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1141,8 +1141,8 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: hass.bus.async_listen( event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, - event_filter=_label_removed_from_registry_filter, # type: ignore[arg-type] - listener=_handle_label_registry_update, # type: ignore[arg-type] + event_filter=_label_removed_from_registry_filter, + listener=_handle_label_registry_update, ) @callback diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 49dfde913e4..84d1bd43e2e 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -1380,8 +1380,8 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: hass.bus.async_listen( event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, - event_filter=_label_removed_from_registry_filter, # type: ignore[arg-type] - listener=_handle_label_registry_update, # type: ignore[arg-type] + event_filter=_label_removed_from_registry_filter, + listener=_handle_label_registry_update, ) @callback diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 4fd2949a23b..15b8b083ba6 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -287,9 +287,7 @@ def async_track_state_change( return async_track_state_change_event(hass, entity_ids, state_change_listener) return hass.bus.async_listen( - EVENT_STATE_CHANGED, - state_change_dispatcher, # type: ignore[arg-type] - event_filter=state_change_filter, # type: ignore[arg-type] + EVENT_STATE_CHANGED, state_change_dispatcher, event_filter=state_change_filter ) @@ -806,8 +804,7 @@ class _TrackStateChangeFiltered: @callback def _setup_all_listener(self) -> None: self._listeners[_ALL_LISTENER] = self.hass.bus.async_listen( - EVENT_STATE_CHANGED, - self._action, # type: ignore[arg-type] + EVENT_STATE_CHANGED, self._action ) @@ -1387,8 +1384,7 @@ def async_track_same_state( if entity_ids == MATCH_ALL: async_remove_state_for_cancel = hass.bus.async_listen( - EVENT_STATE_CHANGED, - state_for_cancel_listener, # type: ignore[arg-type] + EVENT_STATE_CHANGED, state_for_cancel_listener ) else: async_remove_state_for_cancel = async_track_state_change_event( diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 4b920bf364b..186d5d6b75c 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -585,7 +585,7 @@ def _async_when_setup( listeners: list[CALLBACK_TYPE] = [] - async def _matched_event(event: Event) -> None: + async def _matched_event(event: Event[Any]) -> None: """Call the callback when we matched an event.""" for listener in listeners: listener() @@ -600,7 +600,7 @@ def _async_when_setup( hass.bus.async_listen( EVENT_COMPONENT_LOADED, _matched_event, - event_filter=_async_is_component_filter, # type: ignore[arg-type] + event_filter=_async_is_component_filter, ) ) if start_event: From d868b8d4c5d040949f335567fcb942b1952b4927 Mon Sep 17 00:00:00 2001 From: Alin Balutoiu Date: Fri, 8 Mar 2024 21:31:02 +0000 Subject: [PATCH 0575/1691] Fix tado climate service (#112686) --- homeassistant/components/tado/climate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 5d17655c104..47bd2bc16f3 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -401,9 +401,9 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): def set_timer( self, - temperature: float, - time_period: int, - requested_overlay: str, + temperature: float | None = None, + time_period: int | None = None, + requested_overlay: str | None = None, ): """Set the timer on the entity, and temperature if supported.""" From af6f2a516ef0888abea765fd4908a0da98f2c6a1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 11:34:07 -1000 Subject: [PATCH 0576/1691] Guard against db corruption when renaming entities (#112718) --- homeassistant/components/recorder/core.py | 2 +- .../components/recorder/entity_registry.py | 7 +- .../components/recorder/statistics.py | 68 +++-------- .../table_managers/statistics_meta.py | 9 +- homeassistant/components/recorder/util.py | 54 +++++++- .../recorder/test_entity_registry.py | 99 +++++++++++++++ tests/components/recorder/test_init.py | 70 +++++++++++ tests/components/recorder/test_statistics.py | 115 +++++++++++++++++- 8 files changed, 364 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index a32af55720b..664fb59644f 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -925,7 +925,7 @@ class Recorder(threading.Thread): # that is pending before running the task if TYPE_CHECKING: assert isinstance(task, RecorderTask) - if not task.commit_before: + if task.commit_before: self._commit_event_session_or_retry() return task.run(self) except exc.DatabaseError as err: diff --git a/homeassistant/components/recorder/entity_registry.py b/homeassistant/components/recorder/entity_registry.py index cb464c7f543..89e6864cb06 100644 --- a/homeassistant/components/recorder/entity_registry.py +++ b/homeassistant/components/recorder/entity_registry.py @@ -7,7 +7,7 @@ from homeassistant.helpers import entity_registry as er from homeassistant.helpers.start import async_at_start from .core import Recorder -from .util import get_instance, session_scope +from .util import filter_unique_constraint_integrity_error, get_instance, session_scope _LOGGER = logging.getLogger(__name__) @@ -62,7 +62,10 @@ def update_states_metadata( ) return - with session_scope(session=instance.get_session()) as session: + with session_scope( + session=instance.get_session(), + exception_filter=filter_unique_constraint_integrity_error(instance, "state"), + ) as session: if not states_meta_manager.update_metadata(session, entity_id, new_entity_id): _LOGGER.warning( "Cannot migrate history for entity_id `%s` to `%s` " diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 2c26328fa2d..ffe3136f841 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -4,7 +4,6 @@ from __future__ import annotations from collections import defaultdict from collections.abc import Callable, Iterable, Sequence -import contextlib import dataclasses from datetime import datetime, timedelta from functools import lru_cache, partial @@ -16,7 +15,7 @@ from typing import TYPE_CHECKING, Any, Literal, TypedDict, cast from sqlalchemy import Select, and_, bindparam, func, lambda_stmt, select, text from sqlalchemy.engine.row import Row -from sqlalchemy.exc import SQLAlchemyError, StatementError +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm.session import Session from sqlalchemy.sql.lambdas import StatementLambdaElement import voluptuous as vol @@ -73,6 +72,7 @@ from .models import ( from .util import ( execute, execute_stmt_lambda_element, + filter_unique_constraint_integrity_error, get_instance, retryable_database_job, session_scope, @@ -455,7 +455,9 @@ def compile_missing_statistics(instance: Recorder) -> bool: with session_scope( session=instance.get_session(), - exception_filter=_filter_unique_constraint_integrity_error(instance), + exception_filter=filter_unique_constraint_integrity_error( + instance, "statistic" + ), ) as session: # Find the newest statistics run, if any if last_run := session.query(func.max(StatisticsRuns.start)).scalar(): @@ -487,7 +489,9 @@ def compile_statistics(instance: Recorder, start: datetime, fire_events: bool) - # Return if we already have 5-minute statistics for the requested period with session_scope( session=instance.get_session(), - exception_filter=_filter_unique_constraint_integrity_error(instance), + exception_filter=filter_unique_constraint_integrity_error( + instance, "statistic" + ), ) as session: modified_statistic_ids = _compile_statistics( instance, session, start, fire_events @@ -738,7 +742,9 @@ def update_statistics_metadata( if new_statistic_id is not UNDEFINED and new_statistic_id is not None: with session_scope( session=instance.get_session(), - exception_filter=_filter_unique_constraint_integrity_error(instance), + exception_filter=filter_unique_constraint_integrity_error( + instance, "statistic" + ), ) as session: statistics_meta_manager.update_statistic_id( session, DOMAIN, statistic_id, new_statistic_id @@ -2247,54 +2253,6 @@ def async_add_external_statistics( _async_import_statistics(hass, metadata, statistics) -def _filter_unique_constraint_integrity_error( - instance: Recorder, -) -> Callable[[Exception], bool]: - def _filter_unique_constraint_integrity_error(err: Exception) -> bool: - """Handle unique constraint integrity errors.""" - if not isinstance(err, StatementError): - return False - - assert instance.engine is not None - dialect_name = instance.engine.dialect.name - - ignore = False - if ( - dialect_name == SupportedDialect.SQLITE - and "UNIQUE constraint failed" in str(err) - ): - ignore = True - if ( - dialect_name == SupportedDialect.POSTGRESQL - and err.orig - and hasattr(err.orig, "pgcode") - and err.orig.pgcode == "23505" - ): - ignore = True - if ( - dialect_name == SupportedDialect.MYSQL - and err.orig - and hasattr(err.orig, "args") - ): - with contextlib.suppress(TypeError): - if err.orig.args[0] == 1062: - ignore = True - - if ignore: - _LOGGER.warning( - ( - "Blocked attempt to insert duplicated statistic rows, please report" - " at %s" - ), - "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+recorder%22", - exc_info=err, - ) - - return ignore - - return _filter_unique_constraint_integrity_error - - def _import_statistics_with_session( instance: Recorder, session: Session, @@ -2398,7 +2356,9 @@ def import_statistics( with session_scope( session=instance.get_session(), - exception_filter=_filter_unique_constraint_integrity_error(instance), + exception_filter=filter_unique_constraint_integrity_error( + instance, "statistic" + ), ) as session: return _import_statistics_with_session( instance, session, metadata, statistics, table diff --git a/homeassistant/components/recorder/table_managers/statistics_meta.py b/homeassistant/components/recorder/table_managers/statistics_meta.py index e216cb79987..32e989b0e3d 100644 --- a/homeassistant/components/recorder/table_managers/statistics_meta.py +++ b/homeassistant/components/recorder/table_managers/statistics_meta.py @@ -308,11 +308,18 @@ class StatisticsMetaManager: recorder thread. """ self._assert_in_recorder_thread() + if self.get(session, new_statistic_id): + _LOGGER.error( + "Cannot rename statistic_id `%s` to `%s` because the new statistic_id is already in use", + old_statistic_id, + new_statistic_id, + ) + return session.query(StatisticsMeta).filter( (StatisticsMeta.statistic_id == old_statistic_id) & (StatisticsMeta.source == source) ).update({StatisticsMeta.statistic_id: new_statistic_id}) - self._clear_cache([old_statistic_id, new_statistic_id]) + self._clear_cache([old_statistic_id]) def delete(self, session: Session, statistic_ids: list[str]) -> None: """Clear statistics for a list of statistic_ids. diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index 67cf33ee591..0b9ec0cf68c 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable, Collection, Generator, Iterable, Sequence +import contextlib from contextlib import contextmanager from datetime import date, datetime, timedelta import functools @@ -22,7 +23,7 @@ import ciso8601 from sqlalchemy import inspect, text from sqlalchemy.engine import Result, Row from sqlalchemy.engine.interfaces import DBAPIConnection -from sqlalchemy.exc import OperationalError, SQLAlchemyError +from sqlalchemy.exc import OperationalError, SQLAlchemyError, StatementError from sqlalchemy.orm.query import Query from sqlalchemy.orm.session import Session from sqlalchemy.sql.lambdas import StatementLambdaElement @@ -907,3 +908,54 @@ def get_index_by_name(session: Session, table_name: str, index_name: str) -> str ), None, ) + + +def filter_unique_constraint_integrity_error( + instance: Recorder, row_type: str +) -> Callable[[Exception], bool]: + """Create a filter for unique constraint integrity errors.""" + + def _filter_unique_constraint_integrity_error(err: Exception) -> bool: + """Handle unique constraint integrity errors.""" + if not isinstance(err, StatementError): + return False + + assert instance.engine is not None + dialect_name = instance.engine.dialect.name + + ignore = False + if ( + dialect_name == SupportedDialect.SQLITE + and "UNIQUE constraint failed" in str(err) + ): + ignore = True + if ( + dialect_name == SupportedDialect.POSTGRESQL + and err.orig + and hasattr(err.orig, "pgcode") + and err.orig.pgcode == "23505" + ): + ignore = True + if ( + dialect_name == SupportedDialect.MYSQL + and err.orig + and hasattr(err.orig, "args") + ): + with contextlib.suppress(TypeError): + if err.orig.args[0] == 1062: + ignore = True + + if ignore: + _LOGGER.warning( + ( + "Blocked attempt to insert duplicated %s rows, please report" + " at %s" + ), + row_type, + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+recorder%22", + exc_info=err, + ) + + return ignore + + return _filter_unique_constraint_integrity_error diff --git a/tests/components/recorder/test_entity_registry.py b/tests/components/recorder/test_entity_registry.py index 22989b761dc..37223f206a1 100644 --- a/tests/components/recorder/test_entity_registry.py +++ b/tests/components/recorder/test_entity_registry.py @@ -1,11 +1,13 @@ """The tests for sensor recorder platform.""" from collections.abc import Callable +from unittest.mock import patch import pytest from sqlalchemy import select from sqlalchemy.orm import Session +from homeassistant.components import recorder from homeassistant.components.recorder import history from homeassistant.components.recorder.db_schema import StatesMeta from homeassistant.components.recorder.util import session_scope @@ -261,4 +263,101 @@ def test_rename_entity_collision( assert _count_entity_id_in_states_meta(hass, session, "sensor.test99") == 1 assert _count_entity_id_in_states_meta(hass, session, "sensor.test1") == 1 + # We should hit the safeguard in the states_meta_manager assert "the new entity_id is already in use" in caplog.text + + # We should not hit the safeguard in the entity_registry + assert "Blocked attempt to insert duplicated state rows" not in caplog.text + + +def test_rename_entity_collision_without_states_meta_safeguard( + hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture +) -> None: + """Test states meta is not migrated when there is a collision. + + This test disables the safeguard in the states_meta_manager + and relies on the filter_unique_constraint_integrity_error safeguard. + """ + hass = hass_recorder() + setup_component(hass, "sensor", {}) + + entity_reg = mock_registry(hass) + + @callback + def add_entry(): + reg_entry = entity_reg.async_get_or_create( + "sensor", + "test", + "unique_0000", + suggested_object_id="test1", + ) + assert reg_entry.entity_id == "sensor.test1" + + hass.add_job(add_entry) + hass.block_till_done() + + zero, four, states = record_states(hass) + hist = history.get_significant_states( + hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"}) + ) + assert_dict_of_states_equal_without_context_and_last_changed(states, hist) + assert len(hist["sensor.test1"]) == 3 + + hass.states.set("sensor.test99", "collision") + hass.states.remove("sensor.test99") + + hass.block_till_done() + wait_recording_done(hass) + + # Verify history before collision + hist = history.get_significant_states( + hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"}) + ) + assert len(hist["sensor.test1"]) == 3 + assert len(hist["sensor.test99"]) == 2 + + instance = recorder.get_instance(hass) + # Patch out the safeguard in the states meta manager + # so that we hit the filter_unique_constraint_integrity_error safeguard in the entity_registry + with patch.object(instance.states_meta_manager, "get", return_value=None): + # Rename entity sensor.test1 to sensor.test99 + @callback + def rename_entry(): + entity_reg.async_update_entity( + "sensor.test1", new_entity_id="sensor.test99" + ) + + hass.add_job(rename_entry) + wait_recording_done(hass) + + # History is not migrated on collision + hist = history.get_significant_states( + hass, zero, four, list(set(states) | {"sensor.test99", "sensor.test1"}) + ) + assert len(hist["sensor.test1"]) == 3 + assert len(hist["sensor.test99"]) == 2 + + with session_scope(hass=hass) as session: + assert _count_entity_id_in_states_meta(hass, session, "sensor.test99") == 1 + + hass.states.set("sensor.test99", "post_migrate") + wait_recording_done(hass) + + new_hist = history.get_significant_states( + hass, + zero, + dt_util.utcnow(), + list(set(states) | {"sensor.test99", "sensor.test1"}), + ) + assert new_hist["sensor.test99"][-1].state == "post_migrate" + assert len(hist["sensor.test99"]) == 2 + + with session_scope(hass=hass) as session: + assert _count_entity_id_in_states_meta(hass, session, "sensor.test99") == 1 + assert _count_entity_id_in_states_meta(hass, session, "sensor.test1") == 1 + + # We should not hit the safeguard in the states_meta_manager + assert "the new entity_id is already in use" not in caplog.text + + # We should hit the safeguard in the entity_registry + assert "Blocked attempt to insert duplicated state rows" in caplog.text diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index acfe6189af9..96814da5171 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -2490,3 +2490,73 @@ async def test_events_are_recorded_until_final_write( await hass.async_block_till_done() assert not instance.engine + + +async def test_commit_before_commits_pending_writes( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, + recorder_db_url: str, + tmp_path: Path, +) -> None: + """Test commit_before with a non-zero commit interval. + + All of our test run with a commit interval of 0 by + default, so we need to test this with a non-zero commit + """ + config = { + recorder.CONF_DB_URL: recorder_db_url, + recorder.CONF_COMMIT_INTERVAL: 60, + } + + recorder_helper.async_initialize_recorder(hass) + hass.create_task(async_setup_recorder_instance(hass, config)) + await recorder_helper.async_wait_recorder(hass) + instance = get_instance(hass) + assert instance.commit_interval == 60 + verify_states_in_queue_future = hass.loop.create_future() + verify_session_commit_future = hass.loop.create_future() + + class VerifyCommitBeforeTask(recorder.tasks.RecorderTask): + """Task to verify that commit before ran. + + If commit_before is true, we should have no pending writes. + """ + + commit_before = True + + def run(self, instance: Recorder) -> None: + if not instance._event_session_has_pending_writes: + hass.loop.call_soon_threadsafe( + verify_session_commit_future.set_result, None + ) + return + hass.loop.call_soon_threadsafe( + verify_session_commit_future.set_exception, + RuntimeError("Session still has pending write"), + ) + + class VerifyStatesInQueueTask(recorder.tasks.RecorderTask): + """Task to verify that states are in the queue.""" + + commit_before = False + + def run(self, instance: Recorder) -> None: + if instance._event_session_has_pending_writes: + hass.loop.call_soon_threadsafe( + verify_states_in_queue_future.set_result, None + ) + return + hass.loop.call_soon_threadsafe( + verify_states_in_queue_future.set_exception, + RuntimeError("Session has no pending write"), + ) + + # First insert an event + instance.queue_task(Event("fake_event")) + # Next verify that the event session has pending writes + instance.queue_task(VerifyStatesInQueueTask()) + # Finally, verify that the session was committed + instance.queue_task(VerifyCommitBeforeTask()) + + await verify_states_in_queue_future + await verify_session_commit_future diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 548a9d17502..2b320cffccc 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -454,7 +454,11 @@ def test_statistics_during_period_set_back_compat( def test_rename_entity_collision( hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture ) -> None: - """Test statistics is migrated when entity_id is changed.""" + """Test statistics is migrated when entity_id is changed. + + This test relies on the the safeguard in the statistics_meta_manager + and should not hit the filter_unique_constraint_integrity_error safeguard. + """ hass = hass_recorder() setup_component(hass, "sensor", {}) @@ -531,8 +535,117 @@ def test_rename_entity_collision( # Statistics failed to migrate due to the collision stats = statistics_during_period(hass, zero, period="5minute") assert stats == {"sensor.test1": expected_stats1, "sensor.test2": expected_stats2} + + # Verify the safeguard in the states meta manager was hit + assert ( + "Cannot rename statistic_id `sensor.test1` to `sensor.test99` " + "because the new statistic_id is already in use" + ) in caplog.text + + # Verify the filter_unique_constraint_integrity_error safeguard was not hit + assert "Blocked attempt to insert duplicated statistic rows" not in caplog.text + + +def test_rename_entity_collision_states_meta_check_disabled( + hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture +) -> None: + """Test statistics is migrated when entity_id is changed. + + This test disables the safeguard in the statistics_meta_manager + and relies on the filter_unique_constraint_integrity_error safeguard. + """ + hass = hass_recorder() + setup_component(hass, "sensor", {}) + + entity_reg = mock_registry(hass) + + @callback + def add_entry(): + reg_entry = entity_reg.async_get_or_create( + "sensor", + "test", + "unique_0000", + suggested_object_id="test1", + ) + assert reg_entry.entity_id == "sensor.test1" + + hass.add_job(add_entry) + hass.block_till_done() + + zero, four, states = record_states(hass) + hist = history.get_significant_states(hass, zero, four, list(states)) + assert_dict_of_states_equal_without_context_and_last_changed(states, hist) + + for kwargs in ({}, {"statistic_ids": ["sensor.test1"]}): + stats = statistics_during_period(hass, zero, period="5minute", **kwargs) + assert stats == {} + stats = get_last_short_term_statistics( + hass, + 0, + "sensor.test1", + True, + {"last_reset", "max", "mean", "min", "state", "sum"}, + ) + assert stats == {} + + do_adhoc_statistics(hass, start=zero) + wait_recording_done(hass) + expected_1 = { + "start": process_timestamp(zero).timestamp(), + "end": process_timestamp(zero + timedelta(minutes=5)).timestamp(), + "mean": pytest.approx(14.915254237288135), + "min": pytest.approx(10.0), + "max": pytest.approx(20.0), + "last_reset": None, + "state": None, + "sum": None, + } + expected_stats1 = [expected_1] + expected_stats2 = [expected_1] + + stats = statistics_during_period(hass, zero, period="5minute") + assert stats == {"sensor.test1": expected_stats1, "sensor.test2": expected_stats2} + + # Insert metadata for sensor.test99 + metadata_1 = { + "has_mean": True, + "has_sum": False, + "name": "Total imported energy", + "source": "test", + "statistic_id": "sensor.test99", + "unit_of_measurement": "kWh", + } + + with session_scope(hass=hass) as session: + session.add(recorder.db_schema.StatisticsMeta.from_meta(metadata_1)) + + instance = recorder.get_instance(hass) + # Patch out the safeguard in the states meta manager + # so that we hit the filter_unique_constraint_integrity_error safeguard in the statistics + with patch.object(instance.statistics_meta_manager, "get", return_value=None): + # Rename entity sensor.test1 to sensor.test99 + @callback + def rename_entry(): + entity_reg.async_update_entity( + "sensor.test1", new_entity_id="sensor.test99" + ) + + hass.add_job(rename_entry) + wait_recording_done(hass) + + # Statistics failed to migrate due to the collision + stats = statistics_during_period(hass, zero, period="5minute") + assert stats == {"sensor.test1": expected_stats1, "sensor.test2": expected_stats2} + + # Verify the filter_unique_constraint_integrity_error safeguard was hit assert "Blocked attempt to insert duplicated statistic rows" in caplog.text + # Verify the safeguard in the states meta manager was not hit + assert ( + "Cannot rename statistic_id `sensor.test1` to `sensor.test99` " + "because the new statistic_id is already in use" + ) not in caplog.text + def test_statistics_duplicated( hass_recorder: Callable[..., HomeAssistant], caplog: pytest.LogCaptureFixture From 945710874bf549a0ec9d1281d53dbba99d203128 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 8 Mar 2024 23:09:17 +0100 Subject: [PATCH 0577/1691] Remove entity description mixin in Fully Kiosk (#112768) --- homeassistant/components/fully_kiosk/button.py | 13 +++---------- homeassistant/components/fully_kiosk/switch.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/fully_kiosk/button.py b/homeassistant/components/fully_kiosk/button.py index 975a225853b..94c34b50de1 100644 --- a/homeassistant/components/fully_kiosk/button.py +++ b/homeassistant/components/fully_kiosk/button.py @@ -23,20 +23,13 @@ from .coordinator import FullyKioskDataUpdateCoordinator from .entity import FullyKioskEntity -@dataclass(frozen=True) -class FullyButtonEntityDescriptionMixin: - """Mixin to describe a Fully Kiosk Browser button entity.""" +@dataclass(frozen=True, kw_only=True) +class FullyButtonEntityDescription(ButtonEntityDescription): + """Fully Kiosk Browser button description.""" press_action: Callable[[FullyKiosk], Any] -@dataclass(frozen=True) -class FullyButtonEntityDescription( - ButtonEntityDescription, FullyButtonEntityDescriptionMixin -): - """Fully Kiosk Browser button description.""" - - BUTTONS: tuple[FullyButtonEntityDescription, ...] = ( FullyButtonEntityDescription( key="restartApp", diff --git a/homeassistant/components/fully_kiosk/switch.py b/homeassistant/components/fully_kiosk/switch.py index 601517e50b6..9d5af87abe9 100644 --- a/homeassistant/components/fully_kiosk/switch.py +++ b/homeassistant/components/fully_kiosk/switch.py @@ -19,9 +19,9 @@ from .coordinator import FullyKioskDataUpdateCoordinator from .entity import FullyKioskEntity -@dataclass(frozen=True) -class FullySwitchEntityDescriptionMixin: - """Fully Kiosk Browser switch entity description mixin.""" +@dataclass(frozen=True, kw_only=True) +class FullySwitchEntityDescription(SwitchEntityDescription): + """Fully Kiosk Browser switch entity description.""" on_action: Callable[[FullyKiosk], Any] off_action: Callable[[FullyKiosk], Any] @@ -30,13 +30,6 @@ class FullySwitchEntityDescriptionMixin: mqtt_off_event: str | None -@dataclass(frozen=True) -class FullySwitchEntityDescription( - SwitchEntityDescription, FullySwitchEntityDescriptionMixin -): - """Fully Kiosk Browser switch entity description.""" - - SWITCHES: tuple[FullySwitchEntityDescription, ...] = ( FullySwitchEntityDescription( key="screensaver", From 6671a8466154a311651a6288dc5ce612c2da3dbb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 8 Mar 2024 23:28:14 +0100 Subject: [PATCH 0578/1691] Remove entity description mixin in Flume (#112765) --- homeassistant/components/flume/binary_sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/flume/binary_sensor.py b/homeassistant/components/flume/binary_sensor.py index 1e0808e7e7d..28f56168d9c 100644 --- a/homeassistant/components/flume/binary_sensor.py +++ b/homeassistant/components/flume/binary_sensor.py @@ -40,20 +40,13 @@ BINARY_SENSOR_DESCRIPTION_CONNECTED = BinarySensorEntityDescription( ) -@dataclass(frozen=True) -class FlumeBinarySensorRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class FlumeBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes a binary sensor entity.""" event_rule: str -@dataclass(frozen=True) -class FlumeBinarySensorEntityDescription( - BinarySensorEntityDescription, FlumeBinarySensorRequiredKeysMixin -): - """Describes a binary sensor entity.""" - - FLUME_BINARY_NOTIFICATION_SENSORS: tuple[FlumeBinarySensorEntityDescription, ...] = ( FlumeBinarySensorEntityDescription( key="leak", From bfd9199ad99598f2c1146e91bb7d6983e8b11bde Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 8 Mar 2024 23:29:02 +0100 Subject: [PATCH 0579/1691] Remove entity description mixin in HomeKit Device (#112775) --- .../components/homekit_controller/select.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/homekit_controller/select.py b/homeassistant/components/homekit_controller/select.py index 6ae42a69940..f672f293122 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -23,19 +23,11 @@ from .connection import HKDevice from .entity import CharacteristicEntity -@dataclass(frozen=True) -class HomeKitSelectEntityDescriptionRequired: - """Required fields for HomeKitSelectEntityDescription.""" - - choices: dict[str, IntEnum] - - -@dataclass(frozen=True) -class HomeKitSelectEntityDescription( - SelectEntityDescription, HomeKitSelectEntityDescriptionRequired -): +@dataclass(frozen=True, kw_only=True) +class HomeKitSelectEntityDescription(SelectEntityDescription): """A generic description of a select entity backed by a single characteristic.""" + choices: dict[str, IntEnum] name: str | None = None From cf5b11576b3c57b9cf02985cb726fd588935b280 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 00:04:52 +0100 Subject: [PATCH 0580/1691] Remove entity description mixin in iBeacon (#112779) --- homeassistant/components/ibeacon/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/ibeacon/sensor.py b/homeassistant/components/ibeacon/sensor.py index 8ec3ef9b67b..3b7ba3d5dbf 100644 --- a/homeassistant/components/ibeacon/sensor.py +++ b/homeassistant/components/ibeacon/sensor.py @@ -24,18 +24,13 @@ from .coordinator import IBeaconCoordinator from .entity import IBeaconEntity -@dataclass(frozen=True) -class IBeaconRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class IBeaconSensorEntityDescription(SensorEntityDescription): + """Describes iBeacon sensor entity.""" value_fn: Callable[[iBeaconAdvertisement], str | int | None] -@dataclass(frozen=True) -class IBeaconSensorEntityDescription(SensorEntityDescription, IBeaconRequiredKeysMixin): - """Describes iBeacon sensor entity.""" - - SENSOR_DESCRIPTIONS = ( IBeaconSensorEntityDescription( key="rssi", From 08416974c9e81a2a6521419012930423b40894d5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 14:21:44 -1000 Subject: [PATCH 0581/1691] Avoid trying to load translations for integrations that have none (#112683) --- homeassistant/helpers/translation.py | 33 +++++++++++-------- homeassistant/loader.py | 5 +++ tests/helpers/test_translation.py | 32 ++++++++++++++++++ .../translations/en.json | 1 + .../translations/en.json | 1 + 5 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 tests/testing_config/custom_components/test_legacy_state_translations/translations/en.json create mode 100644 tests/testing_config/custom_components/test_legacy_state_translations_bad_data/translations/en.json diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 40e8351a3ac..641d200afe3 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -163,6 +163,7 @@ async def _async_get_component_strings( translations_by_language: dict[str, dict[str, Any]] = {} # Determine paths of missing components/platforms files_to_load_by_language: dict[str, dict[str, str]] = {} + loaded_translations_by_language: dict[str, dict[str, Any]] = {} has_files_to_load = False for language in languages: files_to_load: dict[str, str] = {} @@ -171,7 +172,10 @@ async def _async_get_component_strings( for comp in components: domain, _, platform = comp.partition(".") - if not (integration := integrations.get(domain)): + if ( + not (integration := integrations.get(domain)) + or not integration.has_translations + ): continue if platform and integration.is_built_in: @@ -185,22 +189,23 @@ async def _async_get_component_strings( files_to_load[comp] = path has_files_to_load = True - if not has_files_to_load: - return translations_by_language + if has_files_to_load: + loaded_translations_by_language = await hass.async_add_executor_job( + _load_translations_files_by_language, files_to_load_by_language + ) - # Load files - loaded_translations_by_language = await hass.async_add_executor_job( - _load_translations_files_by_language, files_to_load_by_language - ) - - # Translations that miss "title" will get integration put in. - for language, loaded_translations in loaded_translations_by_language.items(): - for loaded, loaded_translation in loaded_translations.items(): - if "." in loaded: + for language in languages: + loaded_translations = loaded_translations_by_language.setdefault(language, {}) + for comp in components: + if "." in comp: continue - if "title" not in loaded_translation: - loaded_translation["title"] = integrations[loaded].name + # Translations that miss "title" will get integration put in. + component_translations = loaded_translations.setdefault(comp, {}) + if "title" not in component_translations and ( + integration := integrations.get(comp) + ): + component_translations["title"] = integration.name translations_by_language[language].update(loaded_translations) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 57c91f84818..622c8524709 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -825,6 +825,11 @@ class Integration: # In the future, we want to default to True for all integrations. return self.manifest.get("import_executor", self.is_built_in) + @cached_property + def has_translations(self) -> bool: + """Return if the integration has translations.""" + return "translations" in self._top_level_files + @property def mqtt(self) -> list[str] | None: """Return Integration MQTT entries.""" diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 772b5d05f79..45c43144da7 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -832,3 +832,35 @@ async def test_translate_state(hass: HomeAssistant): ] ) assert result == "on" + + +async def test_get_translations_still_has_title_without_translations_files( + hass: HomeAssistant, mock_config_flows +) -> None: + """Test the title still gets added in if there are no translation files.""" + mock_config_flows["integration"].append("component1") + integration = Mock(file_path=pathlib.Path(__file__)) + integration.name = "Component 1" + + with patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), patch( + "homeassistant.helpers.translation._load_translations_files_by_language", + return_value={}, + ), patch( + "homeassistant.helpers.translation.async_get_integrations", + return_value={"component1": integration}, + ): + translations = await translation.async_get_translations( + hass, "en", "title", config_flow=True + ) + translations_again = await translation.async_get_translations( + hass, "en", "title", config_flow=True + ) + + assert translations == translations_again + + assert translations == { + "component.component1.title": "Component 1", + } diff --git a/tests/testing_config/custom_components/test_legacy_state_translations/translations/en.json b/tests/testing_config/custom_components/test_legacy_state_translations/translations/en.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/tests/testing_config/custom_components/test_legacy_state_translations/translations/en.json @@ -0,0 +1 @@ +{} diff --git a/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/translations/en.json b/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/translations/en.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/tests/testing_config/custom_components/test_legacy_state_translations_bad_data/translations/en.json @@ -0,0 +1 @@ +{} From 65358c129ab0fc3585a0de2843085c355d627a44 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 16:45:10 -1000 Subject: [PATCH 0582/1691] Replace periodic tasks with background tasks (#112726) * Phase out periodic tasks * false by default or some tests will block forever, will need to fix each one manually * kwarg works * kwarg works * kwarg works * fixes * fix more tests * fix more tests * fix lifx * opensky * pvpc_hourly_pricing * adjust more * adjust more * smarttub * adjust more * adjust more * adjust more * adjust more * adjust * no eager executor * zha * qnap_qsw * fix more * fix fix * docs * its a wrapper now * add more coverage * coverage * cover all combos * more fixes * more fixes * more fixes * remaining issues are legit bugs in tests * make tplink test more predictable * more fixes * feedreader * grind out some more * make test race safe * one more --- homeassistant/bootstrap.py | 2 +- homeassistant/config_entries.py | 37 +-- homeassistant/core.py | 156 +++++------- homeassistant/helpers/entity_platform.py | 4 +- homeassistant/helpers/event.py | 8 +- homeassistant/helpers/update_coordinator.py | 4 +- tests/components/aemet/test_coordinator.py | 2 +- .../airzone_cloud/test_coordinator.py | 2 +- tests/components/broadlink/test_heartbeat.py | 7 +- tests/components/cloudflare/test_init.py | 6 +- .../command_line/test_binary_sensor.py | 4 +- tests/components/command_line/test_cover.py | 10 +- tests/components/command_line/test_init.py | 2 +- tests/components/command_line/test_sensor.py | 8 +- tests/components/command_line/test_switch.py | 6 +- tests/components/efergy/test_sensor.py | 4 +- tests/components/feedreader/test_init.py | 4 +- .../fully_kiosk/test_binary_sensor.py | 4 +- tests/components/fully_kiosk/test_number.py | 4 +- tests/components/fully_kiosk/test_sensor.py | 6 +- tests/components/hassio/test_init.py | 4 +- tests/components/hassio/test_sensor.py | 12 +- tests/components/history_stats/test_sensor.py | 32 +-- .../ign_sismologia/test_geo_location.py | 6 +- tests/components/lifx/conftest.py | 9 + tests/components/lifx/test_binary_sensor.py | 7 +- tests/components/lifx/test_init.py | 6 +- tests/components/lifx/test_light.py | 11 +- tests/components/lifx/test_migration.py | 6 +- tests/components/lifx/test_select.py | 17 +- tests/components/opensky/test_sensor.py | 2 +- tests/components/ourgroceries/test_todo.py | 2 +- .../pvpc_hourly_pricing/test_config_flow.py | 4 +- .../qld_bushfire/test_geo_location.py | 4 +- tests/components/qnap_qsw/test_coordinator.py | 6 +- tests/components/ring/test_init.py | 6 +- .../components/samsungtv/test_media_player.py | 34 +-- .../components/seventeentrack/test_sensor.py | 6 +- tests/components/sleepiq/test_light.py | 2 +- tests/components/sleepiq/test_switch.py | 2 +- tests/components/smarttub/__init__.py | 5 +- tests/components/sql/test_sensor.py | 14 +- tests/components/steamist/test_init.py | 12 +- tests/components/tplink/test_init.py | 18 +- tests/components/zha/test_sensor.py | 2 +- tests/helpers/test_entity_platform.py | 6 +- tests/test_config_entries.py | 8 - tests/test_core.py | 223 ++++++++++++++---- 48 files changed, 413 insertions(+), 333 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index da51332d93d..f1ccfcbdb89 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -878,7 +878,7 @@ async def _async_set_up_integrations( _LOGGER.debug("Waiting for startup to wrap up") try: async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME): - await hass.async_block_till_done(wait_periodic_tasks=False) + await hass.async_block_till_done() except TimeoutError: _LOGGER.warning( "Setup timed out for bootstrap waiting on %s - moving forward", diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b2206912fb3..150c377a6ee 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -18,7 +18,6 @@ from contextvars import ContextVar from copy import deepcopy from enum import Enum, StrEnum import functools -from itertools import chain import logging from random import randint from types import MappingProxyType @@ -379,7 +378,6 @@ class ConfigEntry: self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() - self._periodic_tasks: set[asyncio.Future[Any]] = set() self._integration_for_domain: loader.Integration | None = None self._tries = 0 @@ -857,15 +855,15 @@ class ConfigEntry: if job := self._on_unload.pop()(): self.async_create_task(hass, job) - if not self._tasks and not self._background_tasks and not self._periodic_tasks: + if not self._tasks and not self._background_tasks: return cancel_message = f"Config entry {self.title} with {self.domain} unloading" - for task in chain(self._background_tasks, self._periodic_tasks): + for task in self._background_tasks: task.cancel(cancel_message) _, pending = await asyncio.wait( - [*self._tasks, *self._background_tasks, *self._periodic_tasks], timeout=10 + [*self._tasks, *self._background_tasks], timeout=10 ) for task in pending: @@ -1044,35 +1042,6 @@ class ConfigEntry: task.add_done_callback(self._background_tasks.remove) return task - @callback - def async_create_periodic_task( - self, - hass: HomeAssistant, - target: Coroutine[Any, Any, _R], - name: str, - eager_start: bool = False, - ) -> asyncio.Task[_R]: - """Create a periodic task tied to the config entry lifecycle. - - Periodic tasks are automatically canceled when config entry is unloaded. - - This type of task is typically used for polling. - - A periodic task is different from a normal task: - - - Will not block startup - - Will be automatically cancelled on shutdown - - Calls to async_block_till_done will wait for completion by default - - This method must be run in the event loop. - """ - task = hass.async_create_periodic_task(target, name, eager_start) - if task.done(): - return task - self._periodic_tasks.add(task) - task.add_done_callback(self._periodic_tasks.remove) - return task - current_entry: ContextVar[ConfigEntry | None] = ContextVar( "current_entry", default=None diff --git a/homeassistant/core.py b/homeassistant/core.py index a386f4725fe..21a7de4311d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -375,7 +375,6 @@ class HomeAssistant: self.loop = asyncio.get_running_loop() self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() - self._periodic_tasks: set[asyncio.Future[Any]] = set() self.bus = EventBus(self) self.services = ServiceRegistry(self) self.states = StateMachine(self.bus, self.loop) @@ -585,23 +584,38 @@ class HomeAssistant: @overload @callback def async_add_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any + self, + hassjob: HassJob[..., Coroutine[Any, Any, _R]], + *args: Any, + eager_start: bool = False, + background: bool = False, ) -> asyncio.Future[_R] | None: ... @overload @callback def async_add_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any + self, + hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], + *args: Any, + eager_start: bool = False, + background: bool = False, ) -> asyncio.Future[_R] | None: ... @callback def async_add_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any + self, + hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], + *args: Any, + eager_start: bool = False, + background: bool = False, ) -> asyncio.Future[_R] | None: """Add a HassJob from within the event loop. + If eager_start is True, coroutine functions will be scheduled eagerly. + If background is True, the task will created as a background task. + This method must be run in the event loop. hassjob: HassJob to call. args: parameters for method to call. @@ -618,7 +632,14 @@ class HomeAssistant: ) # Use loop.create_task # to avoid the extra function call in asyncio.create_task. - task = self.loop.create_task(hassjob.target(*args), name=hassjob.name) + if eager_start: + task = create_eager_task( + hassjob.target(*args), name=hassjob.name, loop=self.loop + ) + if task.done(): + return task + else: + task = self.loop.create_task(hassjob.target(*args), name=hassjob.name) elif hassjob.job_type is HassJobType.Callback: if TYPE_CHECKING: hassjob.target = cast(Callable[..., _R], hassjob.target) @@ -629,58 +650,9 @@ class HomeAssistant: hassjob.target = cast(Callable[..., _R], hassjob.target) task = self.loop.run_in_executor(None, hassjob.target, *args) - self._tasks.add(task) - task.add_done_callback(self._tasks.remove) - - return task - - @overload - @callback - def async_run_periodic_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any - ) -> asyncio.Future[_R] | None: - ... - - @overload - @callback - def async_run_periodic_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any - ) -> asyncio.Future[_R] | None: - ... - - @callback - def async_run_periodic_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any - ) -> asyncio.Future[_R] | None: - """Add a periodic HassJob from within the event loop. - - This method must be run in the event loop. - hassjob: HassJob to call. - args: parameters for method to call. - """ - task: asyncio.Future[_R] - # This code path is performance sensitive and uses - # if TYPE_CHECKING to avoid the overhead of constructing - # the type used for the cast. For history see: - # https://github.com/home-assistant/core/pull/71960 - if hassjob.job_type is HassJobType.Coroutinefunction: - if TYPE_CHECKING: - hassjob.target = cast( - Callable[..., Coroutine[Any, Any, _R]], hassjob.target - ) - task = create_eager_task(hassjob.target(*args), name=hassjob.name) - elif hassjob.job_type is HassJobType.Callback: - if TYPE_CHECKING: - hassjob.target = cast(Callable[..., _R], hassjob.target) - hassjob.target(*args) - return None - else: - if TYPE_CHECKING: - hassjob.target = cast(Callable[..., _R], hassjob.target) - task = self.loop.run_in_executor(None, hassjob.target, *args) - - self._periodic_tasks.add(task) - task.add_done_callback(self._periodic_tasks.remove) + task_bucket = self._background_tasks if background else self._tasks + task_bucket.add(task) + task.add_done_callback(task_bucket.remove) return task @@ -751,37 +723,6 @@ class HomeAssistant: task.add_done_callback(self._background_tasks.remove) return task - @callback - def async_create_periodic_task( - self, target: Coroutine[Any, Any, _R], name: str, eager_start: bool = False - ) -> asyncio.Task[_R]: - """Create a task from within the event loop. - - This type of task is typically used for polling. - - A periodic task is different from a normal task: - - - Will not block startup - - Will be automatically cancelled on shutdown - - Calls to async_block_till_done will wait for completion by default - - If you are using this in your integration, use the create task - methods on the config entry instead. - - This method must be run in the event loop. - """ - if eager_start: - task = create_eager_task(target, name=name, loop=self.loop) - if task.done(): - return task - else: - # Use loop.create_task - # to avoid the extra function call in asyncio.create_task. - task = self.loop.create_task(target, name=name) - self._periodic_tasks.add(task) - task.add_done_callback(self._periodic_tasks.remove) - return task - @callback def async_add_executor_job( self, target: Callable[..., _T], *args: Any @@ -806,25 +747,40 @@ class HomeAssistant: @overload @callback def async_run_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any + self, + hassjob: HassJob[..., Coroutine[Any, Any, _R]], + *args: Any, + eager_start: bool = False, + background: bool = False, ) -> asyncio.Future[_R] | None: ... @overload @callback def async_run_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any + self, + hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], + *args: Any, + eager_start: bool = False, + background: bool = False, ) -> asyncio.Future[_R] | None: ... @callback def async_run_hass_job( - self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any + self, + hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], + *args: Any, + eager_start: bool = False, + background: bool = False, ) -> asyncio.Future[_R] | None: """Run a HassJob from within the event loop. This method must be run in the event loop. + If eager_start is True, coroutine functions will be scheduled eagerly. + If background is True, the task will created as a background task. + hassjob: HassJob args: parameters for method to call. """ @@ -838,7 +794,9 @@ class HomeAssistant: hassjob.target(*args) return None - return self.async_add_hass_job(hassjob, *args) + return self.async_add_hass_job( + hassjob, *args, eager_start=eager_start, background=background + ) @overload @callback @@ -891,7 +849,7 @@ class HomeAssistant: self.async_block_till_done(), self.loop ).result() - async def async_block_till_done(self, wait_periodic_tasks: bool = True) -> None: + async def async_block_till_done(self, wait_background_tasks: bool = False) -> None: """Block until all pending work is done.""" # To flush out any call_soon_threadsafe await asyncio.sleep(0) @@ -900,8 +858,8 @@ class HomeAssistant: while tasks := [ task for task in ( - self._tasks | self._periodic_tasks - if wait_periodic_tasks + self._tasks | self._background_tasks + if wait_background_tasks else self._tasks ) if task is not current_task and not cancelling(task) @@ -1034,7 +992,7 @@ class HomeAssistant: self._tasks = set() # Cancel all background tasks - for task in self._background_tasks | self._periodic_tasks: + for task in self._background_tasks: self._tasks.add(task) task.add_done_callback(self._tasks.remove) task.cancel("Home Assistant is stopping") @@ -1046,7 +1004,7 @@ class HomeAssistant: self.bus.async_fire(EVENT_HOMEASSISTANT_STOP) try: async with self.timeout.async_timeout(STOP_STAGE_SHUTDOWN_TIMEOUT): - await self.async_block_till_done(wait_periodic_tasks=False) + await self.async_block_till_done() except TimeoutError: _LOGGER.warning( "Timed out waiting for integrations to stop, the shutdown will" @@ -1059,7 +1017,7 @@ class HomeAssistant: self.bus.async_fire(EVENT_HOMEASSISTANT_FINAL_WRITE) try: async with self.timeout.async_timeout(FINAL_WRITE_STAGE_SHUTDOWN_TIMEOUT): - await self.async_block_till_done(wait_periodic_tasks=False) + await self.async_block_till_done() except TimeoutError: _LOGGER.warning( "Timed out waiting for final writes to complete, the shutdown will" @@ -1111,7 +1069,7 @@ class HomeAssistant: try: async with self.timeout.async_timeout(CLOSE_STAGE_SHUTDOWN_TIMEOUT): - await self.async_block_till_done(wait_periodic_tasks=False) + await self.async_block_till_done() except TimeoutError: _LOGGER.warning( "Timed out waiting for close event to be processed, the shutdown will" diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 9175d46355e..299b8c214b3 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -643,14 +643,14 @@ class EntityPlatform: def _async_handle_interval_callback(self, now: datetime) -> None: """Update all the entity states in a single platform.""" if self.config_entry: - self.config_entry.async_create_periodic_task( + self.config_entry.async_create_background_task( self.hass, self._update_entity_states(now), name=f"EntityPlatform poll {self.domain}.{self.platform_name}", eager_start=True, ) else: - self.hass.async_create_periodic_task( + self.hass.async_create_background_task( self._update_entity_states(now), name=f"EntityPlatform poll {self.domain}.{self.platform_name}", eager_start=True, diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 15b8b083ba6..e7beddec0c7 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1599,7 +1599,7 @@ class _TrackTimeInterval: self._track_job, hass.loop.time() + self.seconds, ) - hass.async_run_periodic_hass_job(self._run_job, now) + hass.async_run_hass_job(self._run_job, now, eager_start=True, background=True) @callback def async_cancel(self) -> None: @@ -1684,7 +1684,7 @@ class SunListener: """Handle solar event.""" self._unsub_sun = None self._listen_next_sun_event() - self.hass.async_run_periodic_hass_job(self.job) + self.hass.async_run_hass_job(self.job, eager_start=True, background=True) @callback def _handle_config_event(self, _event: Any) -> None: @@ -1770,7 +1770,9 @@ class _TrackUTCTimeChange: # time when the timer was scheduled utc_now = time_tracker_utcnow() localized_now = dt_util.as_local(utc_now) if self.local else utc_now - hass.async_run_periodic_hass_job(self.job, localized_now) + hass.async_run_hass_job( + self.job, localized_now, eager_start=True, background=True + ) if TYPE_CHECKING: assert self._pattern_time_change_listener_job is not None self._cancel_callback = async_track_point_in_utc_time( diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 42d7232a4cb..9479c356f24 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -258,14 +258,14 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): def __wrap_handle_refresh_interval(self) -> None: """Handle a refresh interval occurrence.""" if self.config_entry: - self.config_entry.async_create_periodic_task( + self.config_entry.async_create_background_task( self.hass, self._handle_refresh_interval(), name=f"{self.name} - {self.config_entry.title} - refresh", eager_start=True, ) else: - self.hass.async_create_periodic_task( + self.hass.async_create_background_task( self._handle_refresh_interval(), name=f"{self.name} - refresh", eager_start=True, diff --git a/tests/components/aemet/test_coordinator.py b/tests/components/aemet/test_coordinator.py index 890c3476da2..e830f50c54a 100644 --- a/tests/components/aemet/test_coordinator.py +++ b/tests/components/aemet/test_coordinator.py @@ -30,7 +30,7 @@ async def test_coordinator_error( ): freezer.tick(WEATHER_UPDATE_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("weather.aemet") assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/airzone_cloud/test_coordinator.py b/tests/components/airzone_cloud/test_coordinator.py index 40b6c937ed2..4b42ab7c329 100644 --- a/tests/components/airzone_cloud/test_coordinator.py +++ b/tests/components/airzone_cloud/test_coordinator.py @@ -62,7 +62,7 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None: mock_device_status.side_effect = AirzoneCloudError async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_device_status.assert_called() diff --git a/tests/components/broadlink/test_heartbeat.py b/tests/components/broadlink/test_heartbeat.py index 92046000268..d6ce7104bf7 100644 --- a/tests/components/broadlink/test_heartbeat.py +++ b/tests/components/broadlink/test_heartbeat.py @@ -51,7 +51,7 @@ async def test_heartbeat_trigger_right_time(hass: HomeAssistant) -> None: async_fire_time_changed( hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert mock_ping.call_count == 1 assert mock_ping.call_args == call(device.host) @@ -69,7 +69,7 @@ async def test_heartbeat_do_not_trigger_before_time(hass: HomeAssistant) -> None hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL // 2, ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert mock_ping.call_count == 0 @@ -88,6 +88,7 @@ async def test_heartbeat_unload(hass: HomeAssistant) -> None: async_fire_time_changed( hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL ) + await hass.async_block_till_done(wait_background_tasks=True) assert mock_ping.call_count == 0 @@ -108,7 +109,7 @@ async def test_heartbeat_do_not_unload(hass: HomeAssistant) -> None: async_fire_time_changed( hass, dt_util.utcnow() + BroadlinkHeartbeat.HEARTBEAT_INTERVAL ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert mock_ping.call_count == 1 assert mock_ping.call_args == call(device_b.host) diff --git a/tests/components/cloudflare/test_init.py b/tests/components/cloudflare/test_init.py index 9136d229649..128c865e744 100644 --- a/tests/components/cloudflare/test_init.py +++ b/tests/components/cloudflare/test_init.py @@ -209,7 +209,7 @@ async def test_integration_update_interval( async_fire_time_changed( hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL) ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(instance.update_dns_record.mock_calls) == 2 assert "All target records are up to date" not in caplog.text @@ -217,12 +217,12 @@ async def test_integration_update_interval( async_fire_time_changed( hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL) ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(instance.update_dns_record.mock_calls) == 2 instance.list_dns_records.side_effect = pycfdns.ComunicationException() async_fire_time_changed( hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_UPDATE_INTERVAL) ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(instance.update_dns_record.mock_calls) == 2 diff --git a/tests/components/command_line/test_binary_sensor.py b/tests/components/command_line/test_binary_sensor.py index adbca6124f1..fd726ab77a4 100644 --- a/tests/components/command_line/test_binary_sensor.py +++ b/tests/components/command_line/test_binary_sensor.py @@ -324,7 +324,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "on") freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("binary_sensor.test") assert entity_state @@ -335,7 +335,7 @@ async def test_availability( with mock_asyncio_subprocess_run(b"0"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("binary_sensor.test") assert entity_state diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index 58506355ad0..8b98d8d1623 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -265,7 +265,7 @@ async def test_updating_to_often( not in caplog.text ) async_fire_time_changed(hass, dt_util.now() + timedelta(seconds=11)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert called called.clear() @@ -282,7 +282,7 @@ async def test_updating_to_often( wait_till_event.set() # Finish processing update - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert called assert ( "Updating Command Line Cover Test took longer than the scheduled update interval" @@ -327,7 +327,7 @@ async def test_updating_manually( await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.now() + timedelta(seconds=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert called called.clear() @@ -367,7 +367,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "on") freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("cover.test") assert entity_state @@ -378,7 +378,7 @@ async def test_availability( with mock_asyncio_subprocess_run(b"50\n"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("cover.test") assert entity_state diff --git a/tests/components/command_line/test_init.py b/tests/components/command_line/test_init.py index 4f58705e7bf..3fbd0e0f898 100644 --- a/tests/components/command_line/test_init.py +++ b/tests/components/command_line/test_init.py @@ -20,7 +20,7 @@ async def test_setup_config(hass: HomeAssistant, load_yaml_integration: None) -> """Test setup from yaml.""" async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state_binary_sensor = hass.states.get("binary_sensor.test") state_sensor = hass.states.get("sensor.test") diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index 86f6d4d3179..26f97e37543 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -108,7 +108,7 @@ async def test_template_render( hass, dt_util.utcnow() + timedelta(minutes=1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("sensor.test") assert entity_state @@ -140,7 +140,7 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None: hass, dt_util.utcnow() + timedelta(minutes=1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(mock_subprocess_run.mock_calls) == 1 mock_subprocess_run.assert_called_with( @@ -734,7 +734,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "on") freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("sensor.test") assert entity_state @@ -745,7 +745,7 @@ async def test_availability( with mock_asyncio_subprocess_run(b"January 17, 2022"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("sensor.test") assert entity_state diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index 53d9cc96560..c464ded34fb 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -350,7 +350,7 @@ async def test_switch_command_state_fail( await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("switch.test") assert entity_state @@ -734,7 +734,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "on") freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("switch.test") assert entity_state @@ -745,7 +745,7 @@ async def test_availability( with mock_asyncio_subprocess_run(b"50\n"): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_state = hass.states.get("switch.test") assert entity_state diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index e1a893f4f86..d7ab3101900 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -129,11 +129,11 @@ async def test_failed_update_and_reconnection( await mock_responses(hass, aioclient_mock, error=True) next_update = dt_util.utcnow() + timedelta(seconds=30) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.efergy_power_usage").state == STATE_UNAVAILABLE aioclient_mock.clear_requests() await mock_responses(hass, aioclient_mock) next_update = dt_util.utcnow() + timedelta(seconds=30) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.efergy_power_usage").state == "1580" diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index 25760da0028..b0f2f9e4e72 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -370,14 +370,14 @@ async def test_feed_updates( # Change time and fetch more entries future = dt_util.utcnow() + timedelta(hours=1, seconds=1) async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(events) == 2 # Change time but no new entries future = dt_util.utcnow() + timedelta(hours=2, seconds=2) async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(events) == 2 diff --git a/tests/components/fully_kiosk/test_binary_sensor.py b/tests/components/fully_kiosk/test_binary_sensor.py index 23843eef19c..70ae2d15b61 100644 --- a/tests/components/fully_kiosk/test_binary_sensor.py +++ b/tests/components/fully_kiosk/test_binary_sensor.py @@ -79,7 +79,7 @@ async def test_binary_sensors( mock_fully_kiosk.getDeviceInfo.return_value = {} freezer.tick(UPDATE_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("binary_sensor.amazon_fire_plugged_in") assert state @@ -89,7 +89,7 @@ async def test_binary_sensors( mock_fully_kiosk.getDeviceInfo.side_effect = FullyKioskError("error", "status") freezer.tick(UPDATE_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("binary_sensor.amazon_fire_plugged_in") assert state diff --git a/tests/components/fully_kiosk/test_number.py b/tests/components/fully_kiosk/test_number.py index 6bdad9af520..b4ac50cb076 100644 --- a/tests/components/fully_kiosk/test_number.py +++ b/tests/components/fully_kiosk/test_number.py @@ -53,7 +53,7 @@ async def test_numbers( # Test invalid numeric data mock_fully_kiosk.getSettings.return_value = {"screenBrightness": "invalid"} async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("number.amazon_fire_screen_brightness") assert state @@ -62,7 +62,7 @@ async def test_numbers( # Test unknown/missing data mock_fully_kiosk.getSettings.return_value = {} async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("number.amazon_fire_screensaver_timer") assert state diff --git a/tests/components/fully_kiosk/test_sensor.py b/tests/components/fully_kiosk/test_sensor.py index 6342e3216d7..ebf01f5e3d7 100644 --- a/tests/components/fully_kiosk/test_sensor.py +++ b/tests/components/fully_kiosk/test_sensor.py @@ -145,7 +145,7 @@ async def test_sensors_sensors( mock_fully_kiosk.getDeviceInfo.return_value = {} freezer.tick(UPDATE_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.amazon_fire_internal_storage_free_space") assert state @@ -155,7 +155,7 @@ async def test_sensors_sensors( mock_fully_kiosk.getDeviceInfo.side_effect = FullyKioskError("error", "status") freezer.tick(UPDATE_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.amazon_fire_internal_storage_free_space") assert state @@ -181,7 +181,7 @@ async def test_url_sensor_truncating( "currentPage": long_url, } async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.amazon_fire_current_page") assert state diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 462fc845c44..099b75d3686 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -831,11 +831,11 @@ async def test_device_registry_calls(hass: HomeAssistant) -> None: return_value=os_mock_data, ): async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(dev_reg.devices) == 5 async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(dev_reg.devices) == 5 supervisor_mock_data = { diff --git a/tests/components/hassio/test_sensor.py b/tests/components/hassio/test_sensor.py index fbf9a9acc79..82ac3eccdf5 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -298,7 +298,7 @@ async def test_stats_addon_sensor( freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Could not fetch stats" not in caplog.text @@ -308,7 +308,7 @@ async def test_stats_addon_sensor( freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Could not fetch stats" not in caplog.text @@ -316,7 +316,7 @@ async def test_stats_addon_sensor( entity_registry.async_update_entity(entity_id, disabled_by=None) freezer.tick(config_entries.RELOAD_AFTER_UPDATE_DELAY) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert config_entry.state is config_entries.ConfigEntryState.LOADED # Verify the entity is still enabled assert entity_registry.async_get(entity_id).disabled_by is None @@ -324,13 +324,13 @@ async def test_stats_addon_sensor( # The config entry just reloaded, so we need to wait for the next update freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(entity_id) is not None freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Verify that the entity have the expected state. state = hass.states.get(entity_id) assert state.state == expected @@ -341,7 +341,7 @@ async def test_stats_addon_sensor( freezer.tick(HASSIO_UPDATE_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index a6422996726..e51fba9990e 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -1003,7 +1003,7 @@ async def test_does_not_work_into_the_future( one_hour_in = start_time + timedelta(minutes=60) with freeze_time(one_hour_in): async_fire_time_changed(hass, one_hour_in) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN @@ -1013,7 +1013,7 @@ async def test_does_not_work_into_the_future( hass.states.async_set("binary_sensor.state", "off") await hass.async_block_till_done() async_fire_time_changed(hass, turn_off_time) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN @@ -1021,7 +1021,7 @@ async def test_does_not_work_into_the_future( turn_back_on_time = start_time + timedelta(minutes=105) with freeze_time(turn_back_on_time): async_fire_time_changed(hass, turn_back_on_time) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN @@ -1036,7 +1036,7 @@ async def test_does_not_work_into_the_future( end_time = start_time + timedelta(minutes=120) with freeze_time(end_time): async_fire_time_changed(hass, end_time) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN assert hass.states.get("sensor.sensor2").state == STATE_UNKNOWN @@ -1044,7 +1044,7 @@ async def test_does_not_work_into_the_future( in_the_window = start_time + timedelta(hours=23, minutes=5) with freeze_time(in_the_window): async_fire_time_changed(hass, in_the_window) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == "0.08" assert hass.states.get("sensor.sensor2").state == "0.0833333333333333" @@ -1055,7 +1055,7 @@ async def test_does_not_work_into_the_future( return_value=[], ), freeze_time(past_the_window): async_fire_time_changed(hass, past_the_window) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN @@ -1077,7 +1077,7 @@ async def test_does_not_work_into_the_future( _fake_off_states, ), freeze_time(past_the_window_with_data): async_fire_time_changed(hass, past_the_window_with_data) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN @@ -1087,7 +1087,7 @@ async def test_does_not_work_into_the_future( _fake_off_states, ), freeze_time(at_the_next_window_with_data): async_fire_time_changed(hass, at_the_next_window_with_data) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == "0.0" @@ -1487,7 +1487,7 @@ async def test_end_time_with_microseconds_zeroed( ) async_fire_time_changed(hass, time_200) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83" assert ( hass.states.get("sensor.heatpump_compressor_today2").state @@ -1499,7 +1499,7 @@ async def test_end_time_with_microseconds_zeroed( time_400 = start_of_today + timedelta(hours=4) with freeze_time(time_400): async_fire_time_changed(hass, time_400) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "1.83" assert ( hass.states.get("sensor.heatpump_compressor_today2").state @@ -1510,7 +1510,7 @@ async def test_end_time_with_microseconds_zeroed( time_600 = start_of_today + timedelta(hours=6) with freeze_time(time_600): async_fire_time_changed(hass, time_600) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "3.83" assert ( hass.states.get("sensor.heatpump_compressor_today2").state @@ -1525,7 +1525,7 @@ async def test_end_time_with_microseconds_zeroed( with freeze_time(rolled_to_next_day): async_fire_time_changed(hass, rolled_to_next_day) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "0.0" assert hass.states.get("sensor.heatpump_compressor_today2").state == "0.0" @@ -1534,7 +1534,7 @@ async def test_end_time_with_microseconds_zeroed( ) with freeze_time(rolled_to_next_day_plus_12): async_fire_time_changed(hass, rolled_to_next_day_plus_12) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "12.0" assert hass.states.get("sensor.heatpump_compressor_today2").state == "12.0" @@ -1543,7 +1543,7 @@ async def test_end_time_with_microseconds_zeroed( ) with freeze_time(rolled_to_next_day_plus_14): async_fire_time_changed(hass, rolled_to_next_day_plus_14) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "14.0" assert hass.states.get("sensor.heatpump_compressor_today2").state == "14.0" @@ -1554,12 +1554,12 @@ async def test_end_time_with_microseconds_zeroed( hass.states.async_set("binary_sensor.heatpump_compressor_state", "off") await async_wait_recording_done(hass) async_fire_time_changed(hass, rolled_to_next_day_plus_16_860000) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) rolled_to_next_day_plus_18 = start_of_today + timedelta(days=1, hours=18) with freeze_time(rolled_to_next_day_plus_18): async_fire_time_changed(hass, rolled_to_next_day_plus_18) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.heatpump_compressor_today").state == "16.0" assert ( hass.states.get("sensor.heatpump_compressor_today2").state diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index d208a1d42ae..d9b268ff575 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -177,7 +177,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non [mock_entry_1, mock_entry_4, mock_entry_3], ) async_fire_time_changed(hass, utcnow + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 3 @@ -186,7 +186,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non # so no changes to entities. mock_feed_update.return_value = "OK_NO_DATA", None async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 3 @@ -194,7 +194,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non # Simulate an update - empty data, removes all entities mock_feed_update.return_value = "ERROR", None async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 0 diff --git a/tests/components/lifx/conftest.py b/tests/components/lifx/conftest.py index 50296c978f0..15fe8898a5f 100644 --- a/tests/components/lifx/conftest.py +++ b/tests/components/lifx/conftest.py @@ -6,9 +6,18 @@ import pytest from homeassistant.components.lifx import config_flow, coordinator, util +from . import _patch_discovery + from tests.common import mock_device_registry, mock_registry +@pytest.fixture +def mock_discovery(): + """Mock discovery.""" + with _patch_discovery(): + yield + + @pytest.fixture def mock_effect_conductor(): """Mock the effect conductor.""" diff --git a/tests/components/lifx/test_binary_sensor.py b/tests/components/lifx/test_binary_sensor.py index a16bf1173b0..fa57591e305 100644 --- a/tests/components/lifx/test_binary_sensor.py +++ b/tests/components/lifx/test_binary_sensor.py @@ -4,6 +4,8 @@ from __future__ import annotations from datetime import timedelta +import pytest + from homeassistant.components import lifx from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.const import ( @@ -32,6 +34,7 @@ from . import ( from tests.common import MockConfigEntry, async_fire_time_changed +@pytest.mark.usefixtures("mock_discovery") async def test_hev_cycle_state( hass: HomeAssistant, entity_registry: er.EntityRegistry ) -> None: @@ -65,11 +68,11 @@ async def test_hev_cycle_state( bulb.hev_cycle = {"duration": 7200, "remaining": 0, "last_power": False} async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(entity_id).state == STATE_OFF bulb.hev_cycle = None async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(entity_id).state == STATE_UNKNOWN diff --git a/tests/components/lifx/test_init.py b/tests/components/lifx/test_init.py index 31a558da052..c79e4bb3856 100644 --- a/tests/components/lifx/test_init.py +++ b/tests/components/lifx/test_init.py @@ -64,15 +64,15 @@ async def test_configuring_lifx_causes_discovery(hass: HomeAssistant) -> None: assert start_calls == 1 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 2 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 3 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 4 diff --git a/tests/components/lifx/test_light.py b/tests/components/lifx/test_light.py index 887e622b5cc..452112d2235 100644 --- a/tests/components/lifx/test_light.py +++ b/tests/components/lifx/test_light.py @@ -663,6 +663,7 @@ async def test_extended_multizone_messages(hass: HomeAssistant) -> None: ) +@pytest.mark.usefixtures("mock_discovery") async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None: """Test the firmware flame and morph effects on a matrix device.""" config_entry = MockConfigEntry( @@ -721,7 +722,7 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None: ], } async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state.state == STATE_ON @@ -777,7 +778,7 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None: ], } async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state.state == STATE_ON @@ -803,6 +804,7 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None: bulb.set_power.reset_mock() +@pytest.mark.usefixtures("mock_discovery") async def test_lightstrip_move_effect(hass: HomeAssistant) -> None: """Test the firmware move effect on a light strip.""" config_entry = MockConfigEntry( @@ -859,7 +861,7 @@ async def test_lightstrip_move_effect(hass: HomeAssistant) -> None: bulb.power_level = 65535 bulb.effect = {"name": "MOVE", "speed": 4.5, "direction": "Left"} async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state.state == STATE_ON @@ -1119,6 +1121,7 @@ async def test_white_bulb(hass: HomeAssistant) -> None: bulb.set_color.reset_mock() +@pytest.mark.usefixtures("mock_discovery") async def test_config_zoned_light_strip_fails( hass: HomeAssistant, entity_registry: er.EntityRegistry ) -> None: @@ -1154,7 +1157,7 @@ async def test_config_zoned_light_strip_fails( assert hass.states.get(entity_id).state == STATE_OFF async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(entity_id).state == STATE_UNAVAILABLE diff --git a/tests/components/lifx/test_migration.py b/tests/components/lifx/test_migration.py index 6f68f9e798e..3621eb165fa 100644 --- a/tests/components/lifx/test_migration.py +++ b/tests/components/lifx/test_migration.py @@ -144,15 +144,15 @@ async def test_discovery_is_more_frequent_during_migration( assert start_calls == 1 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 3 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 4 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert start_calls == 5 diff --git a/tests/components/lifx/test_select.py b/tests/components/lifx/test_select.py index 9cc361fc1f4..c639dd441e7 100644 --- a/tests/components/lifx/test_select.py +++ b/tests/components/lifx/test_select.py @@ -2,6 +2,8 @@ from datetime import timedelta +import pytest + from homeassistant.components import lifx from homeassistant.components.lifx.const import DOMAIN from homeassistant.components.select import DOMAIN as SELECT_DOMAIN @@ -95,6 +97,7 @@ async def test_infrared_brightness( assert state.state == "100%" +@pytest.mark.usefixtures("mock_discovery") async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None: """Test getting and setting infrared brightness.""" @@ -124,7 +127,7 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None: bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=16383) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert bulb.set_infrared.calls[0][0][0] == 16383 @@ -134,6 +137,7 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None: bulb.set_infrared.reset_mock() +@pytest.mark.usefixtures("mock_discovery") async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None: """Test getting and setting infrared brightness.""" @@ -163,7 +167,7 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None: bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=32767) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert bulb.set_infrared.calls[0][0][0] == 32767 @@ -173,6 +177,7 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None: bulb.set_infrared.reset_mock() +@pytest.mark.usefixtures("mock_discovery") async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None: """Test getting and setting infrared brightness.""" @@ -202,7 +207,7 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None: bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=65535) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert bulb.set_infrared.calls[0][0][0] == 65535 @@ -212,6 +217,7 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None: bulb.set_infrared.reset_mock() +@pytest.mark.usefixtures("mock_discovery") async def test_disable_infrared(hass: HomeAssistant) -> None: """Test getting and setting infrared brightness.""" @@ -241,7 +247,7 @@ async def test_disable_infrared(hass: HomeAssistant) -> None: bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=0) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert bulb.set_infrared.calls[0][0][0] == 0 @@ -251,6 +257,7 @@ async def test_disable_infrared(hass: HomeAssistant) -> None: bulb.set_infrared.reset_mock() +@pytest.mark.usefixtures("mock_discovery") async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None: """Test getting and setting infrared brightness.""" @@ -273,7 +280,7 @@ async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None: bulb.get_infrared = MockLifxCommand(bulb, infrared_brightness=12345) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=30)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state.state == STATE_UNKNOWN diff --git a/tests/components/opensky/test_sensor.py b/tests/components/opensky/test_sensor.py index 80c53430ea9..df4faaa3e4a 100644 --- a/tests/components/opensky/test_sensor.py +++ b/tests/components/opensky/test_sensor.py @@ -73,7 +73,7 @@ async def test_sensor_updating( async def skip_time_and_check_events() -> None: freezer.tick(timedelta(minutes=15)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert events == snapshot diff --git a/tests/components/ourgroceries/test_todo.py b/tests/components/ourgroceries/test_todo.py index d598767409a..672e2e14447 100644 --- a/tests/components/ourgroceries/test_todo.py +++ b/tests/components/ourgroceries/test_todo.py @@ -275,7 +275,7 @@ async def test_coordinator_error( ourgroceries.get_list_items.side_effect = exception freezer.tick(SCAN_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("todo.test_list") assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index 86b0af1dd2c..2a4c5688b5f 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -223,7 +223,7 @@ async def test_reauth( # check reauth trigger with bad-auth responses freezer.move_to(_MOCK_TIME_BAD_AUTH_RESPONSES) async_fire_time_changed(hass, _MOCK_TIME_BAD_AUTH_RESPONSES) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert pvpc_aioclient_mock.call_count == 6 result = hass.config_entries.flow.async_progress_by_handler(DOMAIN)[0] @@ -252,5 +252,5 @@ async def test_reauth( assert result["reason"] == "reauth_successful" assert pvpc_aioclient_mock.call_count == 8 - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert pvpc_aioclient_mock.call_count == 10 diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 916c7567b55..997d2f96aab 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -178,7 +178,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non # so no changes to entities. mock_feed_update.return_value = "OK_NO_DATA", None async_fire_time_changed(hass, utcnow + 2 * SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 3 @@ -186,7 +186,7 @@ async def test_setup(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non # Simulate an update - empty data, removes all entities mock_feed_update.return_value = "ERROR", None async_fire_time_changed(hass, utcnow + 3 * SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 0 diff --git a/tests/components/qnap_qsw/test_coordinator.py b/tests/components/qnap_qsw/test_coordinator.py index 8a5f07e8173..893a86b262d 100644 --- a/tests/components/qnap_qsw/test_coordinator.py +++ b/tests/components/qnap_qsw/test_coordinator.py @@ -103,7 +103,7 @@ async def test_coordinator_client_connector_error( mock_system_sensor.side_effect = QswError freezer.tick(DATA_SCAN_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_system_sensor.assert_called_once() mock_users_verification.assert_called() @@ -115,7 +115,7 @@ async def test_coordinator_client_connector_error( mock_firmware_update_check.side_effect = APIError freezer.tick(FW_SCAN_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_firmware_update_check.assert_called_once() mock_firmware_update_check.reset_mock() @@ -123,7 +123,7 @@ async def test_coordinator_client_connector_error( mock_firmware_update_check.side_effect = QswError freezer.tick(FW_SCAN_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_firmware_update_check.assert_called_once() diff --git a/tests/components/ring/test_init.py b/tests/components/ring/test_init.py index 8d169002e38..64fca9eac2f 100644 --- a/tests/components/ring/test_init.py +++ b/tests/components/ring/test_init.py @@ -147,7 +147,7 @@ async def test_auth_failure_on_device_update( side_effect=AuthenticationError, ): async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Authentication failed while fetching devices data: " in [ record.message @@ -191,7 +191,7 @@ async def test_error_on_global_update( side_effect=error_type, ): async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert log_msg in [ record.message for record in caplog.records if record.levelname == "ERROR" @@ -232,7 +232,7 @@ async def test_error_on_device_update( side_effect=error_type, ): async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert log_msg in [ record.message for record in caplog.records if record.levelname == "ERROR" diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 446888a1f54..4cfb2195310 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -326,7 +326,7 @@ async def test_update_off_ws_with_power_state( next_update = mock_now + timedelta(minutes=1) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) remotews.start_listening.assert_called_once() rest_api.rest_device_info.assert_called_once() @@ -342,7 +342,7 @@ async def test_update_off_ws_with_power_state( next_update = mock_now + timedelta(minutes=2) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) rest_api.rest_device_info.assert_called_once() @@ -355,7 +355,7 @@ async def test_update_off_ws_with_power_state( next_update = mock_now + timedelta(minutes=3) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) rest_api.rest_device_info.assert_called_once() @@ -386,7 +386,7 @@ async def test_update_off_encryptedws( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_OFF @@ -407,12 +407,12 @@ async def test_update_access_denied( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) next_update = mock_now + timedelta(minutes=10) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert [ flow @@ -442,7 +442,7 @@ async def test_update_ws_connection_failure( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ( "Unexpected ConnectionFailure trying to get remote for fake_host, please " @@ -470,7 +470,7 @@ async def test_update_ws_connection_closed( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_OFF @@ -492,7 +492,7 @@ async def test_update_ws_unauthorized_error( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert [ flow @@ -517,7 +517,7 @@ async def test_update_unhandled_response( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON @@ -537,7 +537,7 @@ async def test_connection_closed_during_update_can_recover( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_UNAVAILABLE @@ -545,7 +545,7 @@ async def test_connection_closed_during_update_can_recover( next_update = mock_now + timedelta(minutes=10) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON @@ -705,7 +705,7 @@ async def test_state(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> Non ): freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) # Should be STATE_UNAVAILABLE since there is no way to turn it back on @@ -1444,7 +1444,7 @@ async def test_upnp_re_subscribe_events( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_OFF @@ -1454,7 +1454,7 @@ async def test_upnp_re_subscribe_events( next_update = mock_now + timedelta(minutes=10) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON @@ -1490,7 +1490,7 @@ async def test_upnp_failed_re_subscribe_events( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_OFF @@ -1501,7 +1501,7 @@ async def test_upnp_failed_re_subscribe_events( with patch.object(dmr_device, "async_subscribe_services", side_effect=error): freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 995aaaaac87..2334fbd4057 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -139,15 +139,15 @@ async def _setup_seventeentrack(hass, config=None, summary_data=None): await hass.async_block_till_done() -async def _goto_future(hass, future=None): +async def _goto_future(hass: HomeAssistant, future=None): """Move to future.""" if not future: future = utcnow() + datetime.timedelta(minutes=10) with patch("homeassistant.util.utcnow", return_value=future): async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) async def test_full_valid_config(hass: HomeAssistant) -> None: diff --git a/tests/components/sleepiq/test_light.py b/tests/components/sleepiq/test_light.py index 55961ba989f..e261115c415 100644 --- a/tests/components/sleepiq/test_light.py +++ b/tests/components/sleepiq/test_light.py @@ -64,7 +64,7 @@ async def test_switch_get_states(hass: HomeAssistant, mock_asyncsleepiq) -> None mock_asyncsleepiq.beds[BED_ID].foundation.lights[0].is_on = True async_fire_time_changed(hass, utcnow() + LONGER_UPDATE_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ( hass.states.get(f"light.sleepnumber_{BED_NAME_LOWER}_light_1").state == STATE_ON diff --git a/tests/components/sleepiq/test_switch.py b/tests/components/sleepiq/test_switch.py index 3cc9db235b6..8ab865663dc 100644 --- a/tests/components/sleepiq/test_switch.py +++ b/tests/components/sleepiq/test_switch.py @@ -59,7 +59,7 @@ async def test_switch_get_states(hass: HomeAssistant, mock_asyncsleepiq) -> None mock_asyncsleepiq.beds[BED_ID].paused = True async_fire_time_changed(hass, utcnow() + LONGER_UPDATE_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ( hass.states.get(f"switch.sleepnumber_{BED_NAME_LOWER}_pause_mode").state diff --git a/tests/components/smarttub/__init__.py b/tests/components/smarttub/__init__.py index f6abd4cb5d7..747a901bbf6 100644 --- a/tests/components/smarttub/__init__.py +++ b/tests/components/smarttub/__init__.py @@ -3,13 +3,14 @@ from datetime import timedelta from homeassistant.components.smarttub.const import SCAN_INTERVAL +from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util from tests.common import async_fire_time_changed -async def trigger_update(hass): +async def trigger_update(hass: HomeAssistant) -> None: """Trigger a polling update by moving time forward.""" new_time = dt_util.utcnow() + timedelta(seconds=SCAN_INTERVAL + 1) async_fire_time_changed(hass, new_time) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index 701c2c363ef..9eabffd9421 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -248,7 +248,7 @@ async def test_invalid_url_on_update( hass, dt_util.utcnow() + timedelta(minutes=1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "sqlite://****:****@homeassistant.local" in caplog.text @@ -287,7 +287,7 @@ async def test_templates_with_yaml( hass, dt_util.utcnow() + timedelta(minutes=1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.get_values_with_template") assert state.state == "5" @@ -301,7 +301,7 @@ async def test_templates_with_yaml( hass, dt_util.utcnow() + timedelta(minutes=2), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.get_values_with_template") assert state.state == STATE_UNAVAILABLE @@ -314,7 +314,7 @@ async def test_templates_with_yaml( hass, dt_util.utcnow() + timedelta(minutes=3), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.get_values_with_template") assert state.state == "5" @@ -488,7 +488,7 @@ async def test_no_issue_when_view_has_the_text_entity_id_in_it( hass, dt_util.utcnow() + timedelta(minutes=1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ( "Query contains entity_id but does not reference states_meta" not in caplog.text @@ -622,7 +622,7 @@ async def test_query_recover_from_rollback( ): freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "sqlite3.OperationalError" in caplog.text state = hass.states.get("sensor.select_value_sql_query") @@ -631,7 +631,7 @@ async def test_query_recover_from_rollback( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.select_value_sql_query") assert state.state == "5" diff --git a/tests/components/steamist/test_init.py b/tests/components/steamist/test_init.py index 911ac790206..a38b14ab15d 100644 --- a/tests/components/steamist/test_init.py +++ b/tests/components/steamist/test_init.py @@ -5,6 +5,7 @@ from __future__ import annotations from unittest.mock import AsyncMock, MagicMock, patch from discovery30303 import AIODiscovery30303 +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.components import steamist @@ -113,7 +114,9 @@ async def test_config_entry_fills_unique_id_with_directed_discovery( @pytest.mark.usefixtures("mock_single_broadcast_address") -async def test_discovery_happens_at_interval(hass: HomeAssistant) -> None: +async def test_discovery_happens_at_interval( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: """Test that discovery happens at interval.""" config_entry = MockConfigEntry( domain=DOMAIN, data=DEFAULT_ENTRY_DATA, unique_id=FORMATTED_MAC_ADDRESS @@ -126,10 +129,11 @@ async def test_discovery_happens_at_interval(hass: HomeAssistant) -> None: return_value=mock_aio_discovery, ), _patch_status(MOCK_ASYNC_GET_STATUS_ACTIVE): await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(mock_aio_discovery.async_scan.mock_calls) == 2 - async_fire_time_changed(hass, utcnow() + steamist.DISCOVERY_INTERVAL) - await hass.async_block_till_done() + freezer.move_to(utcnow() + steamist.DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) assert len(mock_aio_discovery.async_scan.mock_calls) == 3 diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 4ad8687afe3..07d3916cc87 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -6,6 +6,7 @@ import copy from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch +from freezegun.api import FrozenDateTimeFactory from kasa.exceptions import AuthenticationException import pytest @@ -41,23 +42,28 @@ from . import ( from tests.common import MockConfigEntry, async_fire_time_changed -async def test_configuring_tplink_causes_discovery(hass: HomeAssistant) -> None: +async def test_configuring_tplink_causes_discovery( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: """Test that specifying empty config does discovery.""" with patch("homeassistant.components.tplink.Discover.discover") as discover, patch( "homeassistant.components.tplink.Discover.discover_single" ): discover.return_value = {MagicMock(): MagicMock()} await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) + # call_count will differ based on number of broadcast addresses call_count = len(discover.mock_calls) assert discover.mock_calls - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15)) - await hass.async_block_till_done() + freezer.tick(tplink.DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) assert len(discover.mock_calls) == call_count * 2 - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30)) - await hass.async_block_till_done() + freezer.tick(tplink.DISCOVERY_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) assert len(discover.mock_calls) == call_count * 3 diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index 0ff5e91f4a9..b97ca1b8927 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -1118,7 +1118,7 @@ async def test_elec_measurement_sensor_polling( # let the polling happen future = dt_util.utcnow() + timedelta(seconds=90) async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # ensure the state has been updated to 6.0 state = hass.states.get(entity_id) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 4a13e6c84d1..2486626f82f 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -72,7 +72,7 @@ async def test_polling_only_updates_entities_it_should_poll( poll_ent.async_update.reset_mock() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert not no_poll_ent.async_update.called assert poll_ent.async_update.called @@ -121,7 +121,7 @@ async def test_polling_updates_entities_with_exception(hass: HomeAssistant) -> N update_err.clear() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=20)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(update_ok) == 3 assert len(update_err) == 1 @@ -140,7 +140,7 @@ async def test_update_state_adds_entities(hass: HomeAssistant) -> None: ent2.update = lambda *_: component.add_entities([ent1]) async_fire_time_changed(hass, dt_util.utcnow() + DEFAULT_SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(hass.states.async_entity_ids()) == 2 diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 00601173e71..4ada764a9ba 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -4330,12 +4330,6 @@ async def test_task_tracking(hass: HomeAssistant) -> None: entry.async_create_background_task( hass, test_task(), "background-task-name", eager_start=False ) - entry.async_create_periodic_task( - hass, test_task(), "periodic-task-name", eager_start=False - ) - entry.async_create_periodic_task( - hass, test_task(), "periodic-task-name", eager_start=True - ) await asyncio.sleep(0) hass.loop.call_soon(event.set) await entry._async_process_on_unload(hass) @@ -4343,8 +4337,6 @@ async def test_task_tracking(hass: HomeAssistant) -> None: "on_unload", "background", "background", - "background", - "background", "normal", ] diff --git a/tests/test_core.py b/tests/test_core.py index 9c78c670ba3..37a6251fa68 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -57,6 +57,7 @@ from homeassistant.exceptions import ( ServiceNotFound, ) from homeassistant.helpers.json import json_dumps +from homeassistant.util.async_ import create_eager_task import homeassistant.util.dt as dt_util from homeassistant.util.read_only_dict import ReadOnlyDict from homeassistant.util.unit_system import METRIC_SYSTEM @@ -97,6 +98,134 @@ async def test_async_add_hass_job_schedule_callback() -> None: assert len(hass.add_job.mock_calls) == 0 +async def test_async_add_hass_job_eager_start_coro_suspends( + hass: HomeAssistant, +) -> None: + """Test scheduling a coro as a task that will suspend with eager_start.""" + + async def job_that_suspends(): + await asyncio.sleep(0) + + task = hass.async_add_hass_job( + ha.HassJob(ha.callback(job_that_suspends)), eager_start=True + ) + assert not task.done() + assert task in hass._tasks + await task + assert task not in hass._tasks + + +async def test_async_run_hass_job_eager_start_coro_suspends( + hass: HomeAssistant, +) -> None: + """Test scheduling a coro as a task that will suspend with eager_start.""" + + async def job_that_suspends(): + await asyncio.sleep(0) + + task = hass.async_run_hass_job( + ha.HassJob(ha.callback(job_that_suspends)), eager_start=True + ) + assert not task.done() + assert task in hass._tasks + await task + assert task not in hass._tasks + + +async def test_async_add_hass_job_background(hass: HomeAssistant) -> None: + """Test scheduling a coro as a background task with async_add_hass_job.""" + + async def job_that_suspends(): + await asyncio.sleep(0) + + task = hass.async_add_hass_job( + ha.HassJob(ha.callback(job_that_suspends)), background=True + ) + assert not task.done() + assert task in hass._background_tasks + await task + assert task not in hass._background_tasks + + +async def test_async_run_hass_job_background(hass: HomeAssistant) -> None: + """Test scheduling a coro as a background task with async_run_hass_job.""" + + async def job_that_suspends(): + await asyncio.sleep(0) + + task = hass.async_run_hass_job( + ha.HassJob(ha.callback(job_that_suspends)), background=True + ) + assert not task.done() + assert task in hass._background_tasks + await task + assert task not in hass._background_tasks + + +async def test_async_add_hass_job_eager_background(hass: HomeAssistant) -> None: + """Test scheduling a coro as an eager background task with async_add_hass_job.""" + + async def job_that_suspends(): + await asyncio.sleep(0) + + task = hass.async_add_hass_job( + ha.HassJob(ha.callback(job_that_suspends)), eager_start=True, background=True + ) + assert not task.done() + assert task in hass._background_tasks + await task + assert task not in hass._background_tasks + + +async def test_async_run_hass_job_eager_background(hass: HomeAssistant) -> None: + """Test scheduling a coro as an eager background task with async_run_hass_job.""" + + async def job_that_suspends(): + await asyncio.sleep(0) + + task = hass.async_run_hass_job( + ha.HassJob(ha.callback(job_that_suspends)), eager_start=True, background=True + ) + assert not task.done() + assert task in hass._background_tasks + await task + assert task not in hass._background_tasks + + +async def test_async_run_hass_job_background_synchronous(hass: HomeAssistant) -> None: + """Test scheduling a coro as an eager background task with async_run_hass_job.""" + + async def job_that_does_not_suspends(): + pass + + task = hass.async_run_hass_job( + ha.HassJob(ha.callback(job_that_does_not_suspends)), + eager_start=True, + background=True, + ) + assert task.done() + assert task not in hass._background_tasks + assert task not in hass._tasks + await task + + +async def test_async_run_hass_job_synchronous(hass: HomeAssistant) -> None: + """Test scheduling a coro as an eager task with async_run_hass_job.""" + + async def job_that_does_not_suspends(): + pass + + task = hass.async_run_hass_job( + ha.HassJob(ha.callback(job_that_does_not_suspends)), + eager_start=True, + background=False, + ) + assert task.done() + assert task not in hass._background_tasks + assert task not in hass._tasks + await task + + async def test_async_add_hass_job_coro_named(hass: HomeAssistant) -> None: """Test that we schedule coroutines and add jobs to the job pool with a name.""" @@ -110,6 +239,19 @@ async def test_async_add_hass_job_coro_named(hass: HomeAssistant) -> None: assert "named coro" in str(task) +async def test_async_add_hass_job_eager_start(hass: HomeAssistant) -> None: + """Test eager_start with async_add_hass_job.""" + + async def mycoro(): + pass + + job = ha.HassJob(mycoro, "named coro") + assert "named coro" in str(job) + assert job.name == "named coro" + task = ha.HomeAssistant.async_add_hass_job(hass, job, eager_start=True) + assert "named coro" in str(task) + + async def test_async_add_hass_job_schedule_partial_callback() -> None: """Test that we schedule partial coros and add jobs to the job pool.""" hass = MagicMock() @@ -135,6 +277,24 @@ async def test_async_add_hass_job_schedule_coroutinefunction() -> None: assert len(hass.add_job.mock_calls) == 0 +async def test_async_add_hass_job_schedule_corofunction_eager_start() -> None: + """Test that we schedule coroutines and add jobs to the job pool.""" + hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop())) + + async def job(): + pass + + with patch( + "homeassistant.core.create_eager_task", wraps=create_eager_task + ) as mock_create_eager_task: + hass_job = ha.HassJob(job) + task = ha.HomeAssistant.async_add_hass_job(hass, hass_job, eager_start=True) + assert len(hass.loop.call_soon.mock_calls) == 0 + assert len(hass.add_job.mock_calls) == 0 + assert mock_create_eager_task.mock_calls + await task + + async def test_async_add_hass_job_schedule_partial_coroutinefunction() -> None: """Test that we schedule partial coros and add jobs to the job pool.""" hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop())) @@ -224,7 +384,7 @@ async def test_async_create_task_schedule_coroutine_with_name() -> None: assert "named task" in str(task) -async def test_async_run_periodic_hass_job_calls_callback() -> None: +async def test_async_run_eager_hass_job_calls_callback() -> None: """Test that the callback annotation is respected.""" hass = MagicMock() calls = [] @@ -233,36 +393,21 @@ async def test_async_run_periodic_hass_job_calls_callback() -> None: asyncio.get_running_loop() # ensure we are in the event loop calls.append(1) - ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(ha.callback(job))) + ha.HomeAssistant.async_run_hass_job( + hass, ha.HassJob(ha.callback(job)), eager_start=True + ) assert len(calls) == 1 -async def test_async_run_periodic_hass_job_calls_coro_function() -> None: - """Test running coros from async_run_periodic_hass_job.""" +async def test_async_run_eager_hass_job_calls_coro_function() -> None: + """Test running coros from async_run_hass_job with eager_start.""" hass = MagicMock() - calls = [] async def job(): - calls.append(1) + pass - await ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(job)) - assert len(calls) == 1 - - -async def test_async_run_periodic_hass_job_calls_executor_function() -> None: - """Test running in the executor from async_run_periodic_hass_job.""" - hass = MagicMock() - hass.loop = asyncio.get_running_loop() - calls = [] - - def job(): - try: - asyncio.get_running_loop() # ensure we are not in the event loop - except RuntimeError: - calls.append(1) - - await ha.HomeAssistant.async_run_periodic_hass_job(hass, ha.HassJob(job)) - assert len(calls) == 1 + ha.HomeAssistant.async_run_hass_job(hass, ha.HassJob(job), eager_start=True) + assert len(hass.async_add_hass_job.mock_calls) == 1 async def test_async_run_hass_job_calls_callback() -> None: @@ -556,7 +701,7 @@ async def test_shutdown_calls_block_till_done_after_shutdown_run_callback_thread """Ensure shutdown_run_callback_threadsafe is called before the final async_block_till_done.""" stop_calls = [] - async def _record_block_till_done(wait_periodic_tasks: bool = True): + async def _record_block_till_done(wait_background_tasks: bool = False): nonlocal stop_calls stop_calls.append("async_block_till_done") @@ -2142,7 +2287,7 @@ async def test_chained_logging_hits_log_timeout( with patch.object(ha, "BLOCK_LOG_TIMEOUT", 0.0): hass.async_create_task(_task_chain_1()) - await hass.async_block_till_done(wait_periodic_tasks=False) + await hass.async_block_till_done(wait_background_tasks=False) assert "_task_chain_" in caplog.text @@ -2696,27 +2841,6 @@ async def test_background_task(hass: HomeAssistant, eager_start: bool) -> None: assert result.result() == ha.CoreState.stopping -@pytest.mark.parametrize("eager_start", (True, False)) -async def test_periodic_task(hass: HomeAssistant, eager_start: bool) -> None: - """Test periodic tasks being quit.""" - result = asyncio.Future() - - async def test_task(): - try: - await asyncio.sleep(1) - except asyncio.CancelledError: - result.set_result(hass.state) - raise - - task = hass.async_create_periodic_task( - test_task(), "happy task", eager_start=eager_start - ) - assert "happy task" in str(task) - await asyncio.sleep(0) - await hass.async_stop() - assert result.result() == ha.CoreState.stopping - - async def test_shutdown_does_not_block_on_normal_tasks( hass: HomeAssistant, ) -> None: @@ -2767,14 +2891,15 @@ async def test_shutdown_does_not_block_on_shielded_tasks( sleep_task.cancel() -async def test_cancellable_hassjob(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("eager_start", (True, False)) +async def test_cancellable_hassjob(hass: HomeAssistant, eager_start: bool) -> None: """Simulate a shutdown, ensure cancellable jobs are cancelled.""" job = MagicMock() @ha.callback def run_job(job: HassJob) -> None: """Call the action.""" - hass.async_run_hass_job(job) + hass.async_run_hass_job(job, eager_start=True) timer1 = hass.loop.call_later( 60, run_job, HassJob(ha.callback(job), cancel_on_shutdown=True) From 5b2a24b1bbe0c910e8ef2a69598e153dd707e0c9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 17:27:08 -1000 Subject: [PATCH 0583/1691] Fix race reloading homekit_controller (#112790) If the subscribe task was still running during the reload we would not cancel it. Make it a config entry task so it gets cancelled at unload --- .../components/homekit_controller/connection.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 66552978e8d..b91203004ad 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -197,13 +197,19 @@ class HKDevice: self._subscribe_timer() self._subscribe_timer = None - async def _async_subscribe(self, _now: datetime) -> None: + @callback + def _async_subscribe(self, _now: datetime) -> None: """Subscribe to characteristics.""" self._subscribe_timer = None if self._pending_subscribes: subscribes = self._pending_subscribes.copy() self._pending_subscribes.clear() - await self.pairing.subscribe(subscribes) + self.config_entry.async_create_task( + self.hass, + self.pairing.subscribe(subscribes), + name=f"hkc subscriptions {self.unique_id}", + eager_start=True, + ) def remove_watchable_characteristics( self, characteristics: list[tuple[int, int]] From 3405bda835733a49573db3aa0ff92e21d28a6069 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 8 Mar 2024 20:27:56 -0700 Subject: [PATCH 0584/1691] Make sure Notion saves new refresh token upon startup (#112676) * Make sure Notion saves new refresh token upon startup * Code review * Typing * Smoother syntax * Fix tests * Fix tests for real --- homeassistant/components/notion/__init__.py | 4 ++++ tests/components/notion/conftest.py | 7 ++++--- tests/components/notion/test_config_flow.py | 16 ++++++++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index 77236ca6e0c..9587f22f6cb 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -181,6 +181,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create a callback to save the refresh token when it changes: entry.async_on_unload(client.add_refresh_token_callback(async_save_refresh_token)) + # Save the client's refresh token if it's different than what we already have: + if (token := client.refresh_token) and token != entry.data[CONF_REFRESH_TOKEN]: + async_save_refresh_token(token) + hass.config_entries.async_update_entry(entry, **entry_updates) async def async_update() -> NotionData: diff --git a/tests/components/notion/conftest.py b/tests/components/notion/conftest.py index 0cded62eb31..3f98491ddb6 100644 --- a/tests/components/notion/conftest.py +++ b/tests/components/notion/conftest.py @@ -10,8 +10,8 @@ from aionotion.sensor.models import Sensor from aionotion.user.models import UserPreferences import pytest -from homeassistant.components.notion import DOMAIN -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.components.notion import CONF_REFRESH_TOKEN, CONF_USER_UUID, DOMAIN +from homeassistant.const import CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture @@ -82,7 +82,8 @@ def config_fixture(): """Define a config entry data fixture.""" return { CONF_USERNAME: TEST_USERNAME, - CONF_PASSWORD: TEST_PASSWORD, + CONF_USER_UUID: TEST_USER_UUID, + CONF_REFRESH_TOKEN: TEST_REFRESH_TOKEN, } diff --git a/tests/components/notion/test_config_flow.py b/tests/components/notion/test_config_flow.py index 803d7481dba..827565db339 100644 --- a/tests/components/notion/test_config_flow.py +++ b/tests/components/notion/test_config_flow.py @@ -11,7 +11,7 @@ from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from .conftest import TEST_REFRESH_TOKEN, TEST_USER_UUID, TEST_USERNAME +from .conftest import TEST_PASSWORD, TEST_REFRESH_TOKEN, TEST_USER_UUID, TEST_USERNAME pytestmark = pytest.mark.usefixtures("mock_setup_entry") @@ -27,7 +27,6 @@ pytestmark = pytest.mark.usefixtures("mock_setup_entry") async def test_create_entry( hass: HomeAssistant, client, - config, errors, get_client_with_exception, mock_aionotion, @@ -45,13 +44,22 @@ async def test_create_entry( get_client_with_exception, ): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=config + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: TEST_PASSWORD, + }, ) assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == errors result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=config + result["flow_id"], + user_input={ + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: TEST_PASSWORD, + }, ) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["title"] == TEST_USERNAME From 2789060bbecdd9a35b2c6e5bdd3f9519e3a8d6b7 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 8 Mar 2024 19:28:04 -0800 Subject: [PATCH 0585/1691] Fix local calendar handling of empty recurrence ids (#112745) * Fix handling of empty recurrence ids * Revert logging changes --- homeassistant/components/calendar/__init__.py | 13 ++- .../local_calendar/test_calendar.py | 92 +++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 87f6efa49c3..cb0256e8346 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -190,6 +190,11 @@ def _validate_rrule(value: Any) -> str: return str(value) +def _empty_as_none(value: str | None) -> str | None: + """Convert any empty string values to None.""" + return value or None + + CREATE_EVENT_SERVICE = "create_event" CREATE_EVENT_SCHEMA = vol.All( cv.has_at_least_one_key(EVENT_START_DATE, EVENT_START_DATETIME, EVENT_IN), @@ -735,7 +740,9 @@ async def handle_calendar_event_create( vol.Required("type"): "calendar/event/delete", vol.Required("entity_id"): cv.entity_id, vol.Required(EVENT_UID): cv.string, - vol.Optional(EVENT_RECURRENCE_ID): cv.string, + vol.Optional(EVENT_RECURRENCE_ID): vol.Any( + vol.All(cv.string, _empty_as_none), None + ), vol.Optional(EVENT_RECURRENCE_RANGE): cv.string, } ) @@ -779,7 +786,9 @@ async def handle_calendar_event_delete( vol.Required("type"): "calendar/event/update", vol.Required("entity_id"): cv.entity_id, vol.Required(EVENT_UID): cv.string, - vol.Optional(EVENT_RECURRENCE_ID): cv.string, + vol.Optional(EVENT_RECURRENCE_ID): vol.Any( + vol.All(cv.string, _empty_as_none), None + ), vol.Optional(EVENT_RECURRENCE_RANGE): cv.string, vol.Required(CONF_EVENT): WEBSOCKET_EVENT_SCHEMA, } diff --git a/tests/components/local_calendar/test_calendar.py b/tests/components/local_calendar/test_calendar.py index 060ea114973..2fa0063dfd8 100644 --- a/tests/components/local_calendar/test_calendar.py +++ b/tests/components/local_calendar/test_calendar.py @@ -408,6 +408,46 @@ async def test_websocket_delete_recurring( ] +async def test_websocket_delete_empty_recurrence_id( + ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn +) -> None: + """Test websocket delete command with an empty recurrence id no-op.""" + client = await ws_client() + await client.cmd_result( + "create", + { + "entity_id": TEST_ENTITY, + "event": { + "summary": "Bastille Day Party", + "dtstart": "1997-07-14T17:00:00+00:00", + "dtend": "1997-07-15T04:00:00+00:00", + }, + }, + ) + + events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00") + assert list(map(event_fields, events)) == [ + { + "summary": "Bastille Day Party", + "start": {"dateTime": "1997-07-14T11:00:00-06:00"}, + "end": {"dateTime": "1997-07-14T22:00:00-06:00"}, + } + ] + uid = events[0]["uid"] + + # Delete the event with an empty recurrence id + await client.cmd_result( + "delete", + { + "entity_id": TEST_ENTITY, + "uid": uid, + "recurrence_id": "", + }, + ) + events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00") + assert list(map(event_fields, events)) == [] + + async def test_websocket_update( ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn ) -> None: @@ -458,6 +498,58 @@ async def test_websocket_update( ] +async def test_websocket_update_empty_recurrence( + ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn +) -> None: + """Test an edit with an empty recurrence id (no-op).""" + client = await ws_client() + await client.cmd_result( + "create", + { + "entity_id": TEST_ENTITY, + "event": { + "summary": "Bastille Day Party", + "dtstart": "1997-07-14T17:00:00+00:00", + "dtend": "1997-07-15T04:00:00+00:00", + }, + }, + ) + + events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00") + assert list(map(event_fields, events)) == [ + { + "summary": "Bastille Day Party", + "start": {"dateTime": "1997-07-14T11:00:00-06:00"}, + "end": {"dateTime": "1997-07-14T22:00:00-06:00"}, + } + ] + uid = events[0]["uid"] + + # Update the event with an empty string for the recurrence id which should + # have no effect. + await client.cmd_result( + "update", + { + "entity_id": TEST_ENTITY, + "uid": uid, + "recurrence_id": "", + "event": { + "summary": "Bastille Day Party [To be rescheduled]", + "dtstart": "1997-07-15T11:00:00-06:00", + "dtend": "1997-07-15T22:00:00-06:00", + }, + }, + ) + events = await get_events("1997-07-14T00:00:00", "1997-07-16T00:00:00") + assert list(map(event_fields, events)) == [ + { + "summary": "Bastille Day Party [To be rescheduled]", + "start": {"dateTime": "1997-07-15T11:00:00-06:00"}, + "end": {"dateTime": "1997-07-15T22:00:00-06:00"}, + } + ] + + async def test_websocket_update_recurring_this_and_future( ws_client: ClientFixture, setup_integration: None, get_events: GetEventsFn ) -> None: From ed3ec85e55b82eb76ff8ad80cf6ebf01ae95d6fe Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 9 Mar 2024 05:28:56 +0200 Subject: [PATCH 0586/1691] Bump bthome-ble to 3.7.0 (#112783) --- homeassistant/components/bthome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index a3e974bf71e..764dff3770c 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -20,5 +20,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/bthome", "iot_class": "local_push", - "requirements": ["bthome-ble==3.6.0"] + "requirements": ["bthome-ble==3.7.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 639a6423030..cde8555cb9f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -618,7 +618,7 @@ brunt==1.2.0 bt-proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==3.6.0 +bthome-ble==3.7.0 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d8b926b6687..d8afd6223f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -526,7 +526,7 @@ brottsplatskartan==1.0.5 brunt==1.2.0 # homeassistant.components.bthome -bthome-ble==3.6.0 +bthome-ble==3.7.0 # homeassistant.components.buienradar buienradar==1.0.5 From a66399ad3d9e16997bf6085d41b3ce8d3fc570a9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 17:29:46 -1000 Subject: [PATCH 0587/1691] Add Event typing to websocket_api for entity subscriptions (#112786) --- homeassistant/components/websocket_api/commands.py | 2 +- homeassistant/components/websocket_api/messages.py | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 0f9dba1cfc3..dcdbae98689 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -367,7 +367,7 @@ def _forward_entity_changes( entity_ids: set[str], user: User, msg_id: int, - event: Event, + event: Event[EventStateChangedData], ) -> None: """Forward entity state changed events to websocket.""" entity_id = event.data["entity_id"] diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index 2592f9d7356..8de43c57f00 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -4,7 +4,7 @@ from __future__ import annotations from functools import lru_cache import logging -from typing import TYPE_CHECKING, Any, Final, cast +from typing import Any, Final import voluptuous as vol @@ -17,6 +17,7 @@ from homeassistant.const import ( ) from homeassistant.core import Event, State from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import EventStateChangedData from homeassistant.helpers.json import ( JSON_DUMP, find_paths_unserializable_data, @@ -141,7 +142,7 @@ def _partial_cached_event_message(event: Event) -> bytes: ) -def cached_state_diff_message(iden: int, event: Event) -> bytes: +def cached_state_diff_message(iden: int, event: Event[EventStateChangedData]) -> bytes: """Return an event message. Serialize to json once per message. @@ -161,7 +162,7 @@ def cached_state_diff_message(iden: int, event: Event) -> bytes: @lru_cache(maxsize=128) -def _partial_cached_state_diff_message(event: Event) -> bytes: +def _partial_cached_state_diff_message(event: Event[EventStateChangedData]) -> bytes: """Cache and serialize the event to json. The message is constructed without the id which @@ -175,7 +176,7 @@ def _partial_cached_state_diff_message(event: Event) -> bytes: ) -def _state_diff_event(event: Event) -> dict: +def _state_diff_event(event: Event[EventStateChangedData]) -> dict: """Convert a state_changed event to the minimal version. State update example @@ -188,16 +189,12 @@ def _state_diff_event(event: Event) -> dict: """ if (event_new_state := event.data["new_state"]) is None: return {ENTITY_EVENT_REMOVE: [event.data["entity_id"]]} - if TYPE_CHECKING: - event_new_state = cast(State, event_new_state) if (event_old_state := event.data["old_state"]) is None: return { ENTITY_EVENT_ADD: { event_new_state.entity_id: event_new_state.as_compressed_state } } - if TYPE_CHECKING: - event_old_state = cast(State, event_old_state) return _state_diff(event_old_state, event_new_state) From 6a7c255b939e54c7f440bcac0ec9dcaa3ddd1137 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 17:30:13 -1000 Subject: [PATCH 0588/1691] Improve entity translation typing (#112788) * Improve entity translation typing * Improve entity translation typing --- homeassistant/helpers/entity.py | 6 ++---- homeassistant/helpers/entity_platform.py | 10 +++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 767b24a67a6..137d9075b65 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -610,7 +610,7 @@ class Entity( def _device_class_name_helper( self, - component_translations: dict[str, Any], + component_translations: dict[str, str], ) -> str | None: """Return a translated name of the entity based on its device class.""" if not self.has_entity_name: @@ -675,7 +675,7 @@ class Entity( def _name_internal( self, device_class_name: str | None, - platform_translations: dict[str, Any], + platform_translations: dict[str, str], ) -> str | UndefinedType | None: """Return the name of the entity.""" if hasattr(self, "_attr_name"): @@ -685,8 +685,6 @@ class Entity( and (name_translation_key := self._name_translation_key) and (name := platform_translations.get(name_translation_key)) ): - if TYPE_CHECKING: - assert isinstance(name, str) return self._substitute_name_placeholders(name) if hasattr(self, "entity_description"): description_name = self.entity_description.name diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 299b8c214b3..cc829cc9bd2 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -130,10 +130,10 @@ class EntityPlatform: # Storage for entities for this specific platform only # which are indexed by entity_id self.entities: dict[str, Entity] = {} - self.component_translations: dict[str, Any] = {} - self.platform_translations: dict[str, Any] = {} - self.object_id_component_translations: dict[str, Any] = {} - self.object_id_platform_translations: dict[str, Any] = {} + self.component_translations: dict[str, str] = {} + self.platform_translations: dict[str, str] = {} + self.object_id_component_translations: dict[str, str] = {} + self.object_id_platform_translations: dict[str, str] = {} self._tasks: list[asyncio.Task[None]] = [] # Stop tracking tasks after setup is completed self._setup_complete = False @@ -419,7 +419,7 @@ class EntityPlatform: async def _async_get_translations( self, language: str, category: str, integration: str - ) -> dict[str, Any]: + ) -> dict[str, str]: """Get translations for a language, category, and integration.""" try: return await translation.async_get_translations( From a50883d9751bc11e3ed3120c8778588e6246afa0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 20:37:21 -1000 Subject: [PATCH 0589/1691] Run service call tasks eagerly (#112791) --- homeassistant/helpers/service.py | 9 +++-- tests/components/cloud/test_init.py | 2 ++ tests/components/demo/test_geo_location.py | 2 +- tests/components/flux_led/test_number.py | 36 ++++++++++++------- .../homekit_controller/test_connection.py | 1 + tests/components/knx/test_services.py | 1 + tests/components/qwikswitch/test_init.py | 2 ++ tests/components/smartthings/test_light.py | 14 ++++++++ tests/components/template/test_weather.py | 1 + tests/components/update/test_init.py | 2 ++ 10 files changed, 52 insertions(+), 18 deletions(-) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index c4238c0d116..223833fc5a5 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -963,11 +963,10 @@ async def _handle_entity_call( task: asyncio.Future[ServiceResponse] | None if isinstance(func, str): - task = hass.async_run_hass_job( - HassJob(partial(getattr(entity, func), **data)) # type: ignore[arg-type] - ) + job = HassJob(partial(getattr(entity, func), **data)) # type: ignore[arg-type] + task = hass.async_run_hass_job(job, eager_start=True) else: - task = hass.async_run_hass_job(func, entity, data) + task = hass.async_run_hass_job(func, entity, data, eager_start=True) # Guard because callback functions do not return a task when passed to # async_run_job. @@ -1002,7 +1001,7 @@ async def _async_admin_handler( if not user.is_admin: raise Unauthorized(context=call.context) - result = hass.async_run_hass_job(service_job, call) + result = hass.async_run_hass_job(service_job, call, eager_start=True) if result is not None: await result diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 4733d5e907c..9fb8b383501 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -72,12 +72,14 @@ async def test_remote_services( with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect: await hass.services.async_call(DOMAIN, "remote_connect", blocking=True) + await hass.async_block_till_done() assert mock_connect.called assert cloud.client.remote_autostart with patch("hass_nabucasa.remote.RemoteUI.disconnect") as mock_disconnect: await hass.services.async_call(DOMAIN, "remote_disconnect", blocking=True) + await hass.async_block_till_done() assert mock_disconnect.called assert not cloud.client.remote_autostart diff --git a/tests/components/demo/test_geo_location.py b/tests/components/demo/test_geo_location.py index 13657c88ac8..d3c2937d12b 100644 --- a/tests/components/demo/test_geo_location.py +++ b/tests/components/demo/test_geo_location.py @@ -50,7 +50,7 @@ async def test_setup_platform(hass: HomeAssistant, disable_platforms) -> None: # Update (replaces 1 device). async_fire_time_changed(hass, utcnow + DEFAULT_UPDATE_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Get all states again, ensure that the number of states is still # the same, but the lists are different. all_states_updated = [ diff --git a/tests/components/flux_led/test_number.py b/tests/components/flux_led/test_number.py index 5e16e4c0c2c..8858e7c7c93 100644 --- a/tests/components/flux_led/test_number.py +++ b/tests/components/flux_led/test_number.py @@ -1,6 +1,7 @@ """Tests for the flux_led number platform.""" -from unittest.mock import patch + +from datetime import timedelta from flux_led.const import COLOR_MODE_RGB as FLUX_COLOR_MODE_RGB import pytest @@ -24,6 +25,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util from . import ( DEFAULT_ENTRY_TITLE, @@ -37,7 +39,7 @@ from . import ( async_mock_effect_speed, ) -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed async def test_effects_speed_unique_id( @@ -268,9 +270,7 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None: ) # Original addressable model bulb.color_modes = {FLUX_COLOR_MODE_RGB} bulb.color_mode = FLUX_COLOR_MODE_RGB - with patch.object( - flux_number, "DEBOUNCE_TIME", 0 - ), _patch_discovery(), _patch_wifibulb(device=bulb): + with _patch_discovery(), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -291,6 +291,7 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None: music_segments_entity_id = "number.bulb_rgbcw_ddeeff_music_segments" state = hass.states.get(music_segments_entity_id) assert state.state == "4" + await hass.async_block_till_done(wait_background_tasks=True) with pytest.raises(ValueError): await hass.services.async_call( @@ -306,7 +307,11 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: pixels_per_segment_entity_id, ATTR_VALUE: 100}, blocking=True, ) - await hass.async_block_till_done() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=flux_number.DEBOUNCE_TIME) + ) + await hass.async_block_till_done(wait_background_tasks=True) + bulb.async_set_device_config.assert_called_with(pixels_per_segment=100) bulb.async_set_device_config.reset_mock() @@ -324,7 +329,10 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: music_pixels_per_segment_entity_id, ATTR_VALUE: 100}, blocking=True, ) - await hass.async_block_till_done() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=flux_number.DEBOUNCE_TIME) + ) + await hass.async_block_till_done(wait_background_tasks=True) bulb.async_set_device_config.assert_called_with(music_pixels_per_segment=100) bulb.async_set_device_config.reset_mock() @@ -342,7 +350,10 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: segments_entity_id, ATTR_VALUE: 5}, blocking=True, ) - await hass.async_block_till_done() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=flux_number.DEBOUNCE_TIME) + ) + await hass.async_block_till_done(wait_background_tasks=True) bulb.async_set_device_config.assert_called_with(segments=5) bulb.async_set_device_config.reset_mock() @@ -360,7 +371,10 @@ async def test_addressable_light_pixel_config(hass: HomeAssistant) -> None: {ATTR_ENTITY_ID: music_segments_entity_id, ATTR_VALUE: 5}, blocking=True, ) - await hass.async_block_till_done() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=flux_number.DEBOUNCE_TIME) + ) + await hass.async_block_till_done(wait_background_tasks=True) bulb.async_set_device_config.assert_called_with(music_segments=5) bulb.async_set_device_config.reset_mock() @@ -385,9 +399,7 @@ async def test_addressable_light_pixel_config_music_disabled( ) # Original addressable model bulb.color_modes = {FLUX_COLOR_MODE_RGB} bulb.color_mode = FLUX_COLOR_MODE_RGB - with patch.object( - flux_number, "DEBOUNCE_TIME", 0 - ), _patch_discovery(), _patch_wifibulb(device=bulb): + with _patch_discovery(), _patch_wifibulb(device=bulb): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/homekit_controller/test_connection.py b/tests/components/homekit_controller/test_connection.py index d8382bdde86..0a77509d675 100644 --- a/tests/components/homekit_controller/test_connection.py +++ b/tests/components/homekit_controller/test_connection.py @@ -273,6 +273,7 @@ async def test_thread_provision( }, blocking=True, ) + await hass.async_block_till_done(wait_background_tasks=True) assert config_entry.data["Connection"] == "CoAP" diff --git a/tests/components/knx/test_services.py b/tests/components/knx/test_services.py index 0bfb2141ab8..95cf60f1e68 100644 --- a/tests/components/knx/test_services.py +++ b/tests/components/knx/test_services.py @@ -253,6 +253,7 @@ async def test_exposure_register(hass: HomeAssistant, knx: KNXTestKit) -> None: hass.states.async_set(test_entity, STATE_ON, {test_attribute: 25}) hass.states.async_set(test_entity, STATE_ON, {test_attribute: 25, "unrelated": 2}) hass.states.async_set(test_entity, STATE_OFF, {test_attribute: 25}) + await hass.async_block_till_done() await knx.assert_telegram_count(1) await knx.assert_write(test_address, (25,)) diff --git a/tests/components/qwikswitch/test_init.py b/tests/components/qwikswitch/test_init.py index 08e27ec42bd..32a0d0d20db 100644 --- a/tests/components/qwikswitch/test_init.py +++ b/tests/components/qwikswitch/test_init.py @@ -174,6 +174,7 @@ async def test_switch_device( await hass.services.async_call( "switch", "turn_off", {"entity_id": "switch.switch_1"}, blocking=True ) + await hass.async_block_till_done() assert ( "GET", URL("http://127.0.0.1:2020/@a00001=0"), @@ -254,6 +255,7 @@ async def test_light_device( await hass.services.async_call( "light", "turn_on", {"entity_id": "light.dim_3"}, blocking=True ) + await hass.async_block_till_done() assert ( "GET", URL("http://127.0.0.1:2020/@a00003=100"), diff --git a/tests/components/smartthings/test_light.py b/tests/components/smartthings/test_light.py index 5a92023cc6c..53de2273707 100644 --- a/tests/components/smartthings/test_light.py +++ b/tests/components/smartthings/test_light.py @@ -148,6 +148,8 @@ async def test_turn_off(hass: HomeAssistant, light_devices) -> None: await hass.services.async_call( "light", "turn_off", {"entity_id": "light.color_dimmer_2"}, blocking=True ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_2") assert state is not None @@ -165,6 +167,8 @@ async def test_turn_off_with_transition(hass: HomeAssistant, light_devices) -> N {ATTR_ENTITY_ID: "light.color_dimmer_2", ATTR_TRANSITION: 2}, blocking=True, ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_2") assert state is not None @@ -179,6 +183,8 @@ async def test_turn_on(hass: HomeAssistant, light_devices) -> None: await hass.services.async_call( "light", "turn_on", {ATTR_ENTITY_ID: "light.color_dimmer_1"}, blocking=True ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_1") assert state is not None @@ -200,6 +206,8 @@ async def test_turn_on_with_brightness(hass: HomeAssistant, light_devices) -> No }, blocking=True, ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_1") assert state is not None @@ -226,6 +234,8 @@ async def test_turn_on_with_minimal_brightness( {ATTR_ENTITY_ID: "light.color_dimmer_1", ATTR_BRIGHTNESS: 2}, blocking=True, ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_1") assert state is not None @@ -245,6 +255,8 @@ async def test_turn_on_with_color(hass: HomeAssistant, light_devices) -> None: {ATTR_ENTITY_ID: "light.color_dimmer_2", ATTR_HS_COLOR: (180, 50)}, blocking=True, ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_2") assert state is not None @@ -263,6 +275,8 @@ async def test_turn_on_with_color_temp(hass: HomeAssistant, light_devices) -> No {ATTR_ENTITY_ID: "light.color_dimmer_2", ATTR_COLOR_TEMP: 300}, blocking=True, ) + # This test schedules and update right after the call + await hass.async_block_till_done() # Assert state = hass.states.get("light.color_dimmer_2") assert state is not None diff --git a/tests/components/template/test_weather.py b/tests/components/template/test_weather.py index 3941194c586..4165a509ee4 100644 --- a/tests/components/template/test_weather.py +++ b/tests/components/template/test_weather.py @@ -507,6 +507,7 @@ async def test_forecast_format_error( } }, ) + await hass.async_block_till_done() await hass.services.async_call( WEATHER_DOMAIN, diff --git a/tests/components/update/test_init.py b/tests/components/update/test_init.py index 3a923c8ed18..106273a3b0e 100644 --- a/tests/components/update/test_init.py +++ b/tests/components/update/test_init.py @@ -647,6 +647,8 @@ async def test_entity_without_progress_support_raising( blocking=True, ) + await hass.async_block_till_done() + assert len(events) == 2 assert events[0].data.get("old_state").attributes[ATTR_IN_PROGRESS] is False assert events[0].data.get("old_state").attributes[ATTR_INSTALLED_VERSION] == "1.0.0" From 6a2f7a634795838c9302859673a22b527a2cf11c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 9 Mar 2024 07:37:59 +0100 Subject: [PATCH 0590/1691] Use Mapping as default for Event data (#112769) --- homeassistant/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 21a7de4311d..b906f458bf3 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -125,7 +125,7 @@ _P = ParamSpec("_P") # Internal; not helpers.typing.UNDEFINED due to circular dependency _UNDEF: dict[Any, Any] = {} _CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) -_DataT = TypeVar("_DataT", bound=Mapping[str, Any], default=dict[str, Any]) +_DataT = TypeVar("_DataT", bound=Mapping[str, Any], default=Mapping[str, Any]) CALLBACK_TYPE = Callable[[], None] CORE_STORAGE_KEY = "core.config" From 693f72eff1a52b268a169666bf611db03f6fd14c Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 9 Mar 2024 08:49:11 +0100 Subject: [PATCH 0591/1691] Issue warning modbus configuration when modbus configuration is empty (#112618) --- homeassistant/components/modbus/validators.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 431d9de0cf1..c484b270afc 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -370,12 +370,14 @@ def check_config(config: dict) -> dict: if not validate_modbus(hub, hub_name_inx): del config[hub_inx] continue + minimum_scan_interval = 9999 + no_entities = True for component, conf_key in PLATFORMS: if conf_key not in hub: continue + no_entities = False entity_inx = 0 entities = hub[conf_key] - minimum_scan_interval = 9999 while entity_inx < len(entities): if not validate_entity( hub[CONF_NAME], @@ -388,7 +390,11 @@ def check_config(config: dict) -> dict: del entities[entity_inx] else: entity_inx += 1 - + if no_entities: + err = f"Modbus {hub[CONF_NAME]} contain no entities, this will cause instability, please add at least one entity!" + _LOGGER.warning(err) + # Ensure timeout is not started/handled. + hub[CONF_TIMEOUT] = -1 if hub[CONF_TIMEOUT] >= minimum_scan_interval: hub[CONF_TIMEOUT] = minimum_scan_interval - 1 _LOGGER.warning( From b591bb43f5a81b985b15218a1d303bb902fa9d6c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 22:10:44 -1000 Subject: [PATCH 0592/1691] Use eager_start for homeassistant triggers (#112801) --- homeassistant/components/homeassistant/triggers/event.py | 1 + homeassistant/components/homeassistant/triggers/homeassistant.py | 1 + homeassistant/components/homeassistant/triggers/numeric_state.py | 1 + homeassistant/components/homeassistant/triggers/state.py | 1 + homeassistant/components/homeassistant/triggers/time.py | 1 + homeassistant/components/homeassistant/triggers/time_pattern.py | 1 + 6 files changed, 6 insertions(+) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index e045ece12ba..90ca131fdff 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -144,6 +144,7 @@ async def async_attach_trigger( } }, event.context, + eager_start=True, ) removes = [ diff --git a/homeassistant/components/homeassistant/triggers/homeassistant.py b/homeassistant/components/homeassistant/triggers/homeassistant.py index 51e3a947a29..600bce910cd 100644 --- a/homeassistant/components/homeassistant/triggers/homeassistant.py +++ b/homeassistant/components/homeassistant/triggers/homeassistant.py @@ -56,6 +56,7 @@ async def async_attach_trigger( "description": "Home Assistant starting", } }, + eager_start=True, ) return lambda: None diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index 2575af41401..aad7cb7d9cb 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -187,6 +187,7 @@ async def async_attach_trigger( } }, to_s.context, + eager_start=True, ) @callback diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 6f3183e2b40..724f0d06680 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -184,6 +184,7 @@ async def async_attach_trigger( } }, event.context, + eager_start=True, ) if not time_delta: diff --git a/homeassistant/components/homeassistant/triggers/time.py b/homeassistant/components/homeassistant/triggers/time.py index b1d19d54795..6e648c3994f 100644 --- a/homeassistant/components/homeassistant/triggers/time.py +++ b/homeassistant/components/homeassistant/triggers/time.py @@ -76,6 +76,7 @@ async def async_attach_trigger( "entity_id": entity_id, } }, + eager_start=True, ) @callback diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index df49a79bcb6..f69968eedbf 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -93,6 +93,7 @@ async def async_attach_trigger( "description": "time pattern", } }, + eager_start=True, ) return async_track_time_change( From 9ca9d7f48fab34bdeba1ad131871f3d0d1312425 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 22:10:57 -1000 Subject: [PATCH 0593/1691] Use eager_start for tasks to register integration platforms (#112800) --- homeassistant/helpers/integration_platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index e142f9c2e5a..9d98fa24b72 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -140,6 +140,7 @@ def _process_integration_platforms( hass, integration.domain, platform, + eager_start=True, ) ) ] @@ -249,7 +250,7 @@ async def _async_process_integration_platforms( continue if future := hass.async_run_hass_job( - process_job, hass, integration.domain, platform + process_job, hass, integration.domain, platform, eager_start=True ): futures.append(future) From f2879e6f393a6e655003ece9d0f7714d2b4d52b0 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 09:18:23 +0100 Subject: [PATCH 0594/1691] Break out UniFi platform registration to its own class (#112514) --- homeassistant/components/unifi/__init__.py | 1 + homeassistant/components/unifi/button.py | 4 +- .../components/unifi/device_tracker.py | 4 +- homeassistant/components/unifi/entity.py | 6 +- .../components/unifi/hub/entity_loader.py | 131 ++++++++++++++++++ homeassistant/components/unifi/hub/hub.py | 101 ++------------ homeassistant/components/unifi/image.py | 4 +- homeassistant/components/unifi/sensor.py | 4 +- homeassistant/components/unifi/switch.py | 4 +- homeassistant/components/unifi/update.py | 4 +- tests/components/unifi/test_config_flow.py | 12 +- 11 files changed, 158 insertions(+), 117 deletions(-) create mode 100644 homeassistant/components/unifi/hub/entity_loader.py diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index dda91801084..5174a1a7796 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -49,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) hub.async_update_device_registry() + hub.entity_loader.load_entities() if len(hass.data[UNIFI_DOMAIN]) == 1: async_setup_services(hass) diff --git a/homeassistant/components/unifi/button.py b/homeassistant/components/unifi/button.py index 950ad2f8361..c8f5d5b33f7 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -107,9 +107,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up button platform for UniFi Network integration.""" - UnifiHub.register_platform( - hass, - config_entry, + UnifiHub.get_hub(hass, config_entry).entity_loader.register_platform( async_add_entities, UnifiButtonEntity, ENTITY_DESCRIPTIONS, diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index cce41135e4c..d84691b814c 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -220,8 +220,8 @@ async def async_setup_entry( ) -> None: """Set up device tracker for UniFi Network integration.""" async_update_unique_id(hass, config_entry) - UnifiHub.register_platform( - hass, config_entry, async_add_entities, UnifiScannerEntity, ENTITY_DESCRIPTIONS + UnifiHub.get_hub(hass, config_entry).entity_loader.register_platform( + async_add_entities, UnifiScannerEntity, ENTITY_DESCRIPTIONS ) diff --git a/homeassistant/components/unifi/entity.py b/homeassistant/components/unifi/entity.py index 5f63fdb1980..5a626ec56f6 100644 --- a/homeassistant/components/unifi/entity.py +++ b/homeassistant/components/unifi/entity.py @@ -133,7 +133,7 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]): self.hub = hub self.entity_description = description - hub.known_objects.add((description.key, obj_id)) + hub.entity_loader.known_objects.add((description.key, obj_id)) self._removed = False @@ -154,7 +154,9 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]): @callback def unregister_object() -> None: """Remove object ID from known_objects when unloaded.""" - self.hub.known_objects.discard((description.key, self._obj_id)) + self.hub.entity_loader.known_objects.discard( + (description.key, self._obj_id) + ) self.async_on_remove(unregister_object) diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py new file mode 100644 index 00000000000..be053403bff --- /dev/null +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -0,0 +1,131 @@ +"""UniFi Network entity loader. + +Central point to load entities for the different platforms. +Make sure expected clients are available for platforms. +""" +from __future__ import annotations + +from collections.abc import Iterable +from datetime import timedelta +from functools import partial +from typing import TYPE_CHECKING + +from aiounifi.interfaces.api_handlers import ItemEvent + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from ..entity import UnifiEntity, UnifiEntityDescription + +if TYPE_CHECKING: + from .hub import UnifiHub + +CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) + + +class UnifiEntityLoader: + """UniFi Network integration handling platforms for entity registration.""" + + def __init__(self, hub: UnifiHub) -> None: + """Initialize the UniFi entity loader.""" + self.hub = hub + + self.platforms: list[ + tuple[ + AddEntitiesCallback, + type[UnifiEntity], + tuple[UnifiEntityDescription, ...], + bool, + ] + ] = [] + + self.known_objects: set[tuple[str, str]] = set() + """Tuples of entity description key and object ID of loaded entities.""" + + @callback + def register_platform( + self, + async_add_entities: AddEntitiesCallback, + entity_class: type[UnifiEntity], + descriptions: tuple[UnifiEntityDescription, ...], + requires_admin: bool = False, + ) -> None: + """Register UniFi entity platforms.""" + self.platforms.append( + (async_add_entities, entity_class, descriptions, requires_admin) + ) + + @callback + def load_entities(self) -> None: + """Populate UniFi platforms with entities.""" + for ( + async_add_entities, + entity_class, + descriptions, + requires_admin, + ) in self.platforms: + if requires_admin and not self.hub.is_admin: + continue + self._load_entities(entity_class, descriptions, async_add_entities) + + @callback + def _should_add_entity( + self, description: UnifiEntityDescription, obj_id: str + ) -> bool: + """Check if entity should be added.""" + return bool( + (description.key, obj_id) not in self.known_objects + and description.allowed_fn(self.hub, obj_id) + and description.supported_fn(self.hub, obj_id) + ) + + @callback + def _load_entities( + self, + unifi_platform_entity: type[UnifiEntity], + descriptions: tuple[UnifiEntityDescription, ...], + async_add_entities: AddEntitiesCallback, + ) -> None: + """Subscribe to UniFi API handlers and create entities.""" + + @callback + def async_load_entities(descriptions: Iterable[UnifiEntityDescription]) -> None: + """Load and subscribe to UniFi endpoints.""" + + @callback + def _add_unifi_entities() -> None: + """Add UniFi entity.""" + async_add_entities( + unifi_platform_entity(obj_id, self.hub, description) + for description in descriptions + for obj_id in description.api_handler_fn(self.hub.api) + if self._should_add_entity(description, obj_id) + ) + + _add_unifi_entities() + + @callback + def _create_unifi_entity( + description: UnifiEntityDescription, event: ItemEvent, obj_id: str + ) -> None: + """Create new UniFi entity on event.""" + if self._should_add_entity(description, obj_id): + async_add_entities( + [unifi_platform_entity(obj_id, self.hub, description)] + ) + + for description in descriptions: + description.api_handler_fn(self.hub.api).subscribe( + partial(_create_unifi_entity, description), ItemEvent.ADDED + ) + + self.hub.config.entry.async_on_unload( + async_dispatcher_connect( + self.hub.hass, + self.hub.signal_options_update, + _add_unifi_entities, + ) + ) + + async_load_entities(descriptions) diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index 89e741c43d5..ae485ec36b8 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -2,12 +2,9 @@ from __future__ import annotations -from collections.abc import Iterable from datetime import datetime, timedelta -from functools import partial import aiounifi -from aiounifi.interfaces.api_handlers import ItemEvent from aiounifi.models.device import DeviceSetPoePortModeRequest from homeassistant.config_entries import ConfigEntry @@ -19,11 +16,7 @@ from homeassistant.helpers.device_registry import ( DeviceEntryType, DeviceInfo, ) -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.event import async_call_later, async_track_time_interval import homeassistant.util.dt as dt_util @@ -35,8 +28,8 @@ from ..const import ( PLATFORMS, UNIFI_WIRELESS_CLIENTS, ) -from ..entity import UnifiEntity, UnifiEntityDescription from .config import UnifiConfig +from .entity_loader import UnifiEntityLoader from .websocket import UnifiWebsocket CHECK_HEARTBEAT_INTERVAL = timedelta(seconds=1) @@ -52,6 +45,7 @@ class UnifiHub: self.hass = hass self.api = api self.config = UnifiConfig.from_config_entry(config_entry) + self.entity_loader = UnifiEntityLoader(self) self.websocket = UnifiWebsocket(hass, api, self.signal_reachable) self.wireless_clients = hass.data[UNIFI_WIRELESS_CLIENTS] @@ -62,96 +56,21 @@ class UnifiHub: self._cancel_heartbeat_check: CALLBACK_TYPE | None = None self._heartbeat_time: dict[str, datetime] = {} - self.entities: dict[str, str] = {} - self.known_objects: set[tuple[str, str]] = set() - self.poe_command_queue: dict[str, dict[int, str]] = {} self._cancel_poe_command: CALLBACK_TYPE | None = None + @callback + @staticmethod + def get_hub(hass: HomeAssistant, config_entry: ConfigEntry) -> UnifiHub: + """Get UniFi hub from config entry.""" + hub: UnifiHub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] + return hub + @property def available(self) -> bool: """Websocket connection state.""" return self.websocket.available - @callback - @staticmethod - def register_platform( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, - entity_class: type[UnifiEntity], - descriptions: tuple[UnifiEntityDescription, ...], - requires_admin: bool = False, - ) -> None: - """Register platform for UniFi entity management.""" - hub: UnifiHub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - if requires_admin and not hub.is_admin: - return - hub.register_platform_add_entities( - entity_class, descriptions, async_add_entities - ) - - @callback - def _async_should_add_entity( - self, description: UnifiEntityDescription, obj_id: str - ) -> bool: - """Check if entity should be added.""" - return bool( - (description.key, obj_id) not in self.known_objects - and description.allowed_fn(self, obj_id) - and description.supported_fn(self, obj_id) - ) - - @callback - def register_platform_add_entities( - self, - unifi_platform_entity: type[UnifiEntity], - descriptions: tuple[UnifiEntityDescription, ...], - async_add_entities: AddEntitiesCallback, - ) -> None: - """Subscribe to UniFi API handlers and create entities.""" - - @callback - def async_load_entities(descriptions: Iterable[UnifiEntityDescription]) -> None: - """Load and subscribe to UniFi endpoints.""" - - @callback - def async_add_unifi_entities() -> None: - """Add UniFi entity.""" - async_add_entities( - unifi_platform_entity(obj_id, self, description) - for description in descriptions - for obj_id in description.api_handler_fn(self.api) - if self._async_should_add_entity(description, obj_id) - ) - - async_add_unifi_entities() - - @callback - def async_create_entity( - description: UnifiEntityDescription, event: ItemEvent, obj_id: str - ) -> None: - """Create new UniFi entity on event.""" - if self._async_should_add_entity(description, obj_id): - async_add_entities( - [unifi_platform_entity(obj_id, self, description)] - ) - - for description in descriptions: - description.api_handler_fn(self.api).subscribe( - partial(async_create_entity, description), ItemEvent.ADDED - ) - - self.config.entry.async_on_unload( - async_dispatcher_connect( - self.hass, - self.signal_options_update, - async_add_unifi_entities, - ) - ) - - async_load_entities(descriptions) - @property def signal_reachable(self) -> str: """Integration specific event to signal a change in connection status.""" diff --git a/homeassistant/components/unifi/image.py b/homeassistant/components/unifi/image.py index 3ea53d5b3f1..8360fb3b095 100644 --- a/homeassistant/components/unifi/image.py +++ b/homeassistant/components/unifi/image.py @@ -72,9 +72,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up image platform for UniFi Network integration.""" - UnifiHub.register_platform( - hass, - config_entry, + UnifiHub.get_hub(hass, config_entry).entity_loader.register_platform( async_add_entities, UnifiImageEntity, ENTITY_DESCRIPTIONS, diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index ccdabc54d24..eda4599b02a 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -374,8 +374,8 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up sensors for UniFi Network integration.""" - UnifiHub.register_platform( - hass, config_entry, async_add_entities, UnifiSensorEntity, ENTITY_DESCRIPTIONS + UnifiHub.get_hub(hass, config_entry).entity_loader.register_platform( + async_add_entities, UnifiSensorEntity, ENTITY_DESCRIPTIONS ) diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 1e6e0beaa8f..92c25b90f06 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -321,9 +321,7 @@ async def async_setup_entry( ) -> None: """Set up switches for UniFi Network integration.""" async_update_unique_id(hass, config_entry) - UnifiHub.register_platform( - hass, - config_entry, + UnifiHub.get_hub(hass, config_entry).entity_loader.register_platform( async_add_entities, UnifiSwitchEntity, ENTITY_DESCRIPTIONS, diff --git a/homeassistant/components/unifi/update.py b/homeassistant/components/unifi/update.py index a8a2dbe3b23..f112f47232c 100644 --- a/homeassistant/components/unifi/update.py +++ b/homeassistant/components/unifi/update.py @@ -76,9 +76,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up update entities for UniFi Network integration.""" - UnifiHub.register_platform( - hass, - config_entry, + UnifiHub.get_hub(hass, config_entry).entity_loader.register_platform( async_add_entities, UnifiDeviceUpdateEntity, ENTITY_DESCRIPTIONS, diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 0f8efc8b483..ee309ca2579 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -76,14 +76,15 @@ DEVICES = [ ] WLANS = [ - {"_id": "1", "name": "SSID 1"}, + {"_id": "1", "name": "SSID 1", "enabled": True}, { "_id": "2", "name": "SSID 2", "name_combine_enabled": False, "name_combine_suffix": "_IOT", + "enabled": True, }, - {"_id": "3", "name": "SSID 4", "name_combine_enabled": False}, + {"_id": "3", "name": "SSID 4", "name_combine_enabled": False, "enabled": True}, ] DPI_GROUPS = [ @@ -542,12 +543,7 @@ async def test_simple_option_flow( ) -> None: """Test simple config flow options.""" config_entry = await setup_unifi_integration( - hass, - aioclient_mock, - clients_response=CLIENTS, - wlans_response=WLANS, - dpigroup_response=DPI_GROUPS, - dpiapp_response=[], + hass, aioclient_mock, clients_response=CLIENTS ) result = await hass.config_entries.options.async_init( From 19e54debba13966c6c752c56f61b1bf982f10809 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 09:19:38 +0100 Subject: [PATCH 0595/1691] Unifi more polish on entity description (#112281) * Make has_entity_name default value True * Remove previously missed event_is_on and event_to_subscribe * Provide default value for allowed_fn and supported_fn * Provide default value for name_fn * Provide default value for available_fn * Add doc strings to required functions * Fix some missed renames from variations of controller to hub --- homeassistant/components/unifi/button.py | 9 +----- .../components/unifi/device_tracker.py | 7 +--- homeassistant/components/unifi/entity.py | 22 ++++++++++--- homeassistant/components/unifi/image.py | 3 -- homeassistant/components/unifi/sensor.py | 32 ++----------------- homeassistant/components/unifi/switch.py | 27 +++------------- homeassistant/components/unifi/update.py | 4 --- 7 files changed, 27 insertions(+), 77 deletions(-) diff --git a/homeassistant/components/unifi/button.py b/homeassistant/components/unifi/button.py index c8f5d5b33f7..45fc76c73df 100644 --- a/homeassistant/components/unifi/button.py +++ b/homeassistant/components/unifi/button.py @@ -69,33 +69,26 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = ( UnifiButtonEntityDescription[Devices, Device]( key="Device restart", entity_category=EntityCategory.CONFIG, - has_entity_name=True, device_class=ButtonDeviceClass.RESTART, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, control_fn=async_restart_device_control_fn, device_info_fn=async_device_device_info_fn, name_fn=lambda _: "Restart", object_fn=lambda api, obj_id: api.devices[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_restart-{obj_id}", ), UnifiButtonEntityDescription[Ports, Port]( key="PoE power cycle", entity_category=EntityCategory.CONFIG, - has_entity_name=True, device_class=ButtonDeviceClass.RESTART, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.ports, available_fn=async_device_available_fn, control_fn=async_power_cycle_port_control_fn, device_info_fn=async_device_device_info_fn, - event_is_on=None, - event_to_subscribe=None, name_fn=lambda port: f"{port.name} Power Cycle", object_fn=lambda api, obj_id: api.ports[obj_id], - supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe, + supported_fn=lambda hub, obj_id: bool(hub.api.ports[obj_id].port_poe), unique_id_fn=lambda hub, obj_id: f"power_cycle-{obj_id}", ), ) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index d84691b814c..ffd4566b77c 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -149,10 +149,8 @@ class UnifiTrackerEntityDescription(UnifiEntityDescription[HandlerT, ApiItemT]): ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( UnifiTrackerEntityDescription[Clients, Client]( key="Client device scanner", - has_entity_name=True, allowed_fn=async_client_allowed_fn, api_handler_fn=lambda api: api.clients, - available_fn=lambda hub, obj_id: hub.available, device_info_fn=lambda api, obj_id: None, event_is_on=(WIRED_CONNECTION + WIRELESS_CONNECTION), event_to_subscribe=( @@ -165,23 +163,20 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = ( is_connected_fn=async_client_is_connected_fn, name_fn=lambda client: client.name or client.hostname, object_fn=lambda api, obj_id: api.clients[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"{hub.site}-{obj_id}", ip_address_fn=lambda api, obj_id: api.clients[obj_id].ip, hostname_fn=lambda api, obj_id: api.clients[obj_id].hostname, ), UnifiTrackerEntityDescription[Devices, Device]( key="Device scanner", - has_entity_name=True, allowed_fn=lambda hub, obj_id: hub.config.option_track_devices, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=lambda api, obj_id: None, heartbeat_timedelta_fn=async_device_heartbeat_timedelta_fn, - is_connected_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].state == 1, + is_connected_fn=lambda hub, obj_id: hub.api.devices[obj_id].state == 1, name_fn=lambda device: device.name or device.model, object_fn=lambda api, obj_id: api.devices[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: obj_id, ip_address_fn=lambda api, obj_id: api.devices[obj_id].ip, hostname_fn=lambda api, obj_id: None, diff --git a/homeassistant/components/unifi/entity.py b/homeassistant/components/unifi/entity.py index 5a626ec56f6..649c9fc0c13 100644 --- a/homeassistant/components/unifi/entity.py +++ b/homeassistant/components/unifi/entity.py @@ -98,16 +98,28 @@ def async_client_device_info_fn(hub: UnifiHub, obj_id: str) -> DeviceInfo: class UnifiEntityDescription(EntityDescription, Generic[HandlerT, ApiItemT]): """UniFi Entity Description.""" - allowed_fn: Callable[[UnifiHub, str], bool] api_handler_fn: Callable[[aiounifi.Controller], HandlerT] - available_fn: Callable[[UnifiHub, str], bool] + """Provide api_handler from api.""" device_info_fn: Callable[[UnifiHub, str], DeviceInfo | None] - name_fn: Callable[[ApiItemT], str | None] + """Provide device info object based on hub and obj_id.""" object_fn: Callable[[aiounifi.Controller, str], ApiItemT] - supported_fn: Callable[[UnifiHub, str], bool | None] + """Retrieve object based on api and obj_id.""" unique_id_fn: Callable[[UnifiHub, str], str] + """Provide a unique ID based on hub and obj_id.""" - # Optional + # Optional functions + allowed_fn: Callable[[UnifiHub, str], bool] = lambda hub, obj_id: True + """Determine if config entry options allow creation of entity.""" + available_fn: Callable[[UnifiHub, str], bool] = lambda hub, obj_id: hub.available + """Determine if entity is available, default is if connection is working.""" + name_fn: Callable[[ApiItemT], str | None] = lambda obj: None + """Entity name function, can be used to extend entity name beyond device name.""" + supported_fn: Callable[[UnifiHub, str], bool] = lambda hub, obj_id: True + """Determine if UniFi object supports providing relevant data for entity.""" + + # Optional constants + has_entity_name = True # Part of EntityDescription + """Has entity name defaults to true.""" event_is_on: tuple[EventKey, ...] | None = None """Which UniFi events should be used to consider state 'on'.""" event_to_subscribe: tuple[EventKey, ...] | None = None diff --git a/homeassistant/components/unifi/image.py b/homeassistant/components/unifi/image.py index 8360fb3b095..285477fe133 100644 --- a/homeassistant/components/unifi/image.py +++ b/homeassistant/components/unifi/image.py @@ -50,15 +50,12 @@ ENTITY_DESCRIPTIONS: tuple[UnifiImageEntityDescription, ...] = ( UnifiImageEntityDescription[Wlans, Wlan]( key="WLAN QR Code", entity_category=EntityCategory.DIAGNOSTIC, - has_entity_name=True, entity_registry_enabled_default=False, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.wlans, available_fn=async_wlan_available_fn, device_info_fn=async_wlan_device_info_fn, name_fn=lambda wlan: "QR Code", object_fn=lambda api, obj_id: api.wlans[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"qr_code-{obj_id}", image_fn=async_wlan_qr_code_image_fn, value_fn=lambda obj: obj.x_passphrase, diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index eda4599b02a..818b5e53b6a 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -186,10 +186,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND, icon="mdi:upload", - has_entity_name=True, allowed_fn=async_bandwidth_sensor_allowed_fn, api_handler_fn=lambda api: api.clients, - available_fn=lambda hub, _: hub.available, device_info_fn=async_client_device_info_fn, is_connected_fn=async_client_is_connected_fn, name_fn=lambda _: "RX", @@ -205,10 +203,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND, icon="mdi:download", - has_entity_name=True, allowed_fn=async_bandwidth_sensor_allowed_fn, api_handler_fn=lambda api: api.clients, - available_fn=lambda hub, _: hub.available, device_info_fn=async_client_device_info_fn, is_connected_fn=async_client_is_connected_fn, name_fn=lambda _: "TX", @@ -222,15 +218,13 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.POWER, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfPower.WATT, - has_entity_name=True, entity_registry_enabled_default=False, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.ports, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, name_fn=lambda port: f"{port.name} PoE Power", object_fn=lambda api, obj_id: api.ports[obj_id], - supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe, + supported_fn=lambda hub, obj_id: bool(hub.api.ports[obj_id].port_poe), unique_id_fn=lambda hub, obj_id: f"poe_power-{obj_id}", value_fn=lambda _, obj: obj.poe_power if obj.poe_mode != "off" else "0", ), @@ -238,11 +232,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( key="Client uptime", device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, - has_entity_name=True, entity_registry_enabled_default=False, allowed_fn=async_uptime_sensor_allowed_fn, api_handler_fn=lambda api: api.clients, - available_fn=lambda hub, obj_id: hub.available, device_info_fn=async_client_device_info_fn, name_fn=lambda client: "Uptime", object_fn=lambda api, obj_id: api.clients[obj_id], @@ -253,16 +245,12 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( UnifiSensorEntityDescription[Wlans, Wlan]( key="WLAN clients", entity_category=EntityCategory.DIAGNOSTIC, - has_entity_name=True, state_class=SensorStateClass.MEASUREMENT, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.wlans, available_fn=async_wlan_available_fn, device_info_fn=async_wlan_device_info_fn, - name_fn=lambda wlan: None, object_fn=lambda api, obj_id: api.wlans[obj_id], should_poll=True, - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"wlan_clients-{obj_id}", value_fn=async_wlan_client_value_fn, ), @@ -271,8 +259,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.POWER, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfPower.WATT, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.outlets, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, @@ -289,8 +275,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfPower.WATT, suggested_display_precision=1, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, @@ -306,8 +290,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfPower.WATT, suggested_display_precision=1, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, @@ -321,14 +303,11 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( key="Device uptime", device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, name_fn=lambda device: "Uptime", object_fn=lambda api, obj_id: api.devices[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_uptime-{obj_id}", value_fn=async_device_uptime_value_fn, value_changed_fn=async_device_uptime_value_changed_fn, @@ -338,29 +317,24 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.TEMPERATURE, entity_category=EntityCategory.DIAGNOSTIC, native_unit_of_measurement=UnitOfTemperature.CELSIUS, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, name_fn=lambda device: "Temperature", object_fn=lambda api, obj_id: api.devices[obj_id], - supported_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].has_temperature, + supported_fn=lambda hub, obj_id: hub.api.devices[obj_id].has_temperature, unique_id_fn=lambda hub, obj_id: f"device_temperature-{obj_id}", - value_fn=lambda ctrlr, device: device.general_temperature, + value_fn=lambda hub, device: device.general_temperature, ), UnifiSensorEntityDescription[Devices, Device]( key="Device State", device_class=SensorDeviceClass.ENUM, entity_category=EntityCategory.DIAGNOSTIC, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, device_info_fn=async_device_device_info_fn, name_fn=lambda device: "State", object_fn=lambda api, obj_id: api.devices[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_state-{obj_id}", value_fn=async_device_state_value_fn, options=list(DEVICE_STATES.values()), diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 92c25b90f06..6e073a655a5 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -126,7 +126,7 @@ async def async_dpi_group_control_fn(hub: UnifiHub, obj_id: str, target: bool) - @callback -def async_outlet_supports_switching_fn(hub: UnifiHub, obj_id: str) -> bool: +def async_outlet_switching_supported_fn(hub: UnifiHub, obj_id: str) -> bool: """Determine if an outlet supports switching.""" outlet = hub.api.outlets[obj_id] return outlet.has_relay or outlet.caps in (1, 3) @@ -184,43 +184,37 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( key="Block client", device_class=SwitchDeviceClass.SWITCH, entity_category=EntityCategory.CONFIG, - has_entity_name=True, icon="mdi:ethernet", allowed_fn=async_block_client_allowed_fn, api_handler_fn=lambda api: api.clients, - available_fn=lambda hub, obj_id: hub.available, control_fn=async_block_client_control_fn, device_info_fn=async_client_device_info_fn, event_is_on=CLIENT_UNBLOCKED, event_to_subscribe=CLIENT_BLOCKED + CLIENT_UNBLOCKED, is_on_fn=lambda hub, client: not client.blocked, - name_fn=lambda client: None, object_fn=lambda api, obj_id: api.clients[obj_id], only_event_for_state_change=True, - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"block-{obj_id}", ), UnifiSwitchEntityDescription[DPIRestrictionGroups, DPIRestrictionGroup]( key="DPI restriction", + has_entity_name=False, entity_category=EntityCategory.CONFIG, icon="mdi:network", allowed_fn=lambda hub, obj_id: hub.config.option_dpi_restrictions, api_handler_fn=lambda api: api.dpi_groups, - available_fn=lambda hub, obj_id: hub.available, control_fn=async_dpi_group_control_fn, custom_subscribe=lambda api: api.dpi_apps.subscribe, device_info_fn=async_dpi_group_device_info_fn, is_on_fn=async_dpi_group_is_on_fn, name_fn=lambda group: group.name, object_fn=lambda api, obj_id: api.dpi_groups[obj_id], - supported_fn=lambda c, obj_id: bool(c.api.dpi_groups[obj_id].dpiapp_ids), + supported_fn=lambda hub, obj_id: bool(hub.api.dpi_groups[obj_id].dpiapp_ids), unique_id_fn=lambda hub, obj_id: obj_id, ), UnifiSwitchEntityDescription[Outlets, Outlet]( key="Outlet control", device_class=SwitchDeviceClass.OUTLET, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.outlets, available_fn=async_device_available_fn, control_fn=async_outlet_control_fn, @@ -228,34 +222,28 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( is_on_fn=lambda hub, outlet: outlet.relay_state, name_fn=lambda outlet: outlet.name, object_fn=lambda api, obj_id: api.outlets[obj_id], - supported_fn=async_outlet_supports_switching_fn, + supported_fn=async_outlet_switching_supported_fn, unique_id_fn=lambda hub, obj_id: f"outlet-{obj_id}", ), UnifiSwitchEntityDescription[PortForwarding, PortForward]( key="Port forward control", device_class=SwitchDeviceClass.SWITCH, entity_category=EntityCategory.CONFIG, - has_entity_name=True, icon="mdi:upload-network", - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.port_forwarding, - available_fn=lambda hub, obj_id: hub.available, control_fn=async_port_forward_control_fn, device_info_fn=async_port_forward_device_info_fn, is_on_fn=lambda hub, port_forward: port_forward.enabled, name_fn=lambda port_forward: f"{port_forward.name}", object_fn=lambda api, obj_id: api.port_forwarding[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"port_forward-{obj_id}", ), UnifiSwitchEntityDescription[Ports, Port]( key="PoE port control", device_class=SwitchDeviceClass.OUTLET, entity_category=EntityCategory.CONFIG, - has_entity_name=True, entity_registry_enabled_default=False, icon="mdi:ethernet", - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.ports, available_fn=async_device_available_fn, control_fn=async_poe_port_control_fn, @@ -263,24 +251,19 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = ( is_on_fn=lambda hub, port: port.poe_mode != "off", name_fn=lambda port: f"{port.name} PoE", object_fn=lambda api, obj_id: api.ports[obj_id], - supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe, + supported_fn=lambda hub, obj_id: bool(hub.api.ports[obj_id].port_poe), unique_id_fn=lambda hub, obj_id: f"poe-{obj_id}", ), UnifiSwitchEntityDescription[Wlans, Wlan]( key="WLAN control", device_class=SwitchDeviceClass.SWITCH, entity_category=EntityCategory.CONFIG, - has_entity_name=True, icon="mdi:wifi-check", - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.wlans, - available_fn=lambda hub, _: hub.available, control_fn=async_wlan_control_fn, device_info_fn=async_wlan_device_info_fn, is_on_fn=lambda hub, wlan: wlan.enabled, - name_fn=lambda wlan: None, object_fn=lambda api, obj_id: api.wlans[obj_id], - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"wlan-{obj_id}", ), ) diff --git a/homeassistant/components/unifi/update.py b/homeassistant/components/unifi/update.py index f112f47232c..a8fe3c83427 100644 --- a/homeassistant/components/unifi/update.py +++ b/homeassistant/components/unifi/update.py @@ -55,16 +55,12 @@ ENTITY_DESCRIPTIONS: tuple[UnifiUpdateEntityDescription, ...] = ( UnifiUpdateEntityDescription[Devices, Device]( key="Upgrade device", device_class=UpdateDeviceClass.FIRMWARE, - has_entity_name=True, - allowed_fn=lambda hub, obj_id: True, api_handler_fn=lambda api: api.devices, available_fn=async_device_available_fn, control_fn=async_device_control_fn, device_info_fn=async_device_device_info_fn, - name_fn=lambda device: None, object_fn=lambda api, obj_id: api.devices[obj_id], state_fn=lambda api, device: device.state == 4, - supported_fn=lambda hub, obj_id: True, unique_id_fn=lambda hub, obj_id: f"device_update-{obj_id}", ), ) From b7d9f26cee5f189213adeee07b4d6ef396a5256f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 22:49:08 -1000 Subject: [PATCH 0596/1691] Cache the job type for entity service calls (#112793) --- homeassistant/core.py | 4 ++-- homeassistant/helpers/entity.py | 17 +++++++++++++++++ homeassistant/helpers/service.py | 5 ++++- tests/helpers/test_entity.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index b906f458bf3..6169df32cfb 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -299,7 +299,7 @@ class HassJob(Generic[_P, _R_co]): @cached_property def job_type(self) -> HassJobType: """Return the job type.""" - return self._job_type or _get_hassjob_callable_job_type(self.target) + return self._job_type or get_hassjob_callable_job_type(self.target) @property def cancel_on_shutdown(self) -> bool | None: @@ -319,7 +319,7 @@ class HassJobWithArgs: args: Iterable[Any] -def _get_hassjob_callable_job_type(target: Callable[..., Any]) -> HassJobType: +def get_hassjob_callable_job_type(target: Callable[..., Any]) -> HassJobType: """Determine the job type from the callable.""" # Check for partials to properly determine if coroutine function check_target = target diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 137d9075b65..191882b7afa 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -51,6 +51,7 @@ from homeassistant.core import ( HassJobType, HomeAssistant, callback, + get_hassjob_callable_job_type, get_release_channel, ) from homeassistant.exceptions import ( @@ -527,6 +528,8 @@ class Entity( __combined_unrecorded_attributes: frozenset[str] = ( _entity_component_unrecorded_attributes | _unrecorded_attributes ) + # Job type cache + _job_types: dict[str, HassJobType] | None = None # StateInfo. Set by EntityPlatform by calling async_internal_added_to_hass # While not purely typed, it makes typehinting more useful for us @@ -568,6 +571,20 @@ class Entity( cls._entity_component_unrecorded_attributes | cls._unrecorded_attributes ) + def get_hassjob_type(self, function_name: str) -> HassJobType: + """Get the job type function for the given name. + + This is used for entity service calls to avoid + figuring out the job type each time. + """ + if not self._job_types: + self._job_types = {} + if function_name not in self._job_types: + self._job_types[function_name] = get_hassjob_callable_job_type( + getattr(self, function_name) + ) + return self._job_types[function_name] + @cached_property def should_poll(self) -> bool: """Return True if entity has to be polled for state. diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 223833fc5a5..d954d7b9682 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -963,7 +963,10 @@ async def _handle_entity_call( task: asyncio.Future[ServiceResponse] | None if isinstance(func, str): - job = HassJob(partial(getattr(entity, func), **data)) # type: ignore[arg-type] + job = HassJob( + partial(getattr(entity, func), **data), # type: ignore[arg-type] + job_type=entity.get_hassjob_type(func), + ) task = hass.async_run_hass_job(job, eager_start=True) else: task = hass.async_run_hass_job(func, entity, data, eager_start=True) diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 241a26e6529..ec281bf4c0d 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -23,7 +23,13 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import Context, HomeAssistant, HomeAssistantError +from homeassistant.core import ( + Context, + HassJobType, + HomeAssistant, + HomeAssistantError, + callback, +) from homeassistant.helpers import device_registry as dr, entity, entity_registry as er from homeassistant.helpers.entity_component import async_update_entity from homeassistant.helpers.typing import UNDEFINED, UndefinedType @@ -2559,3 +2565,26 @@ async def test_reset_right_after_remove_entity_registry( assert len(ent.remove_calls) == 1 assert hass.states.get("test.test") is None + + +async def test_get_hassjob_type(hass: HomeAssistant) -> None: + """Test get_hassjob_type.""" + + class AsyncEntity(entity.Entity): + """Test entity.""" + + def update(self): + """Test update Executor.""" + + async def async_update(self): + """Test update Coroutinefunction.""" + + @callback + def update_callback(self): + """Test update Callback.""" + + ent_1 = AsyncEntity() + + assert ent_1.get_hassjob_type("update") is HassJobType.Executor + assert ent_1.get_hassjob_type("async_update") is HassJobType.Coroutinefunction + assert ent_1.get_hassjob_type("update_callback") is HassJobType.Callback From 2b0b3c238a4442f124918ab3879350565186ee03 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Mar 2024 23:11:00 -1000 Subject: [PATCH 0597/1691] Make SSDP tasks background HassJob to avoid delaying startup (#112668) --- homeassistant/components/ssdp/__init__.py | 53 +++++++++++++------ .../components/dlna_dmr/test_media_player.py | 16 +++--- .../dlna_dms/test_device_availability.py | 14 ++--- .../dlna_dms/test_dms_device_source.py | 2 +- tests/components/ssdp/test_init.py | 15 +++--- 5 files changed, 60 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index ce6b7e30a84..72e94996361 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -3,10 +3,11 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable, Mapping +from collections.abc import Callable, Coroutine, Mapping from dataclasses import dataclass, field from datetime import timedelta from enum import Enum +from functools import partial from ipaddress import IPv4Address, IPv6Address import logging import socket @@ -42,7 +43,7 @@ from homeassistant.const import ( MATCH_ALL, __version__ as current_version, ) -from homeassistant.core import Event, HomeAssistant, callback as core_callback +from homeassistant.core import Event, HassJob, HomeAssistant, callback as core_callback from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.helpers import config_validation as cv, discovery_flow from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -53,6 +54,7 @@ from homeassistant.helpers.system_info import async_get_system_info from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_ssdp, bind_hass from homeassistant.util.async_ import create_eager_task +from homeassistant.util.logging import catch_log_exception DOMAIN = "ssdp" SSDP_SCANNER = "scanner" @@ -124,7 +126,9 @@ class SsdpServiceInfo(BaseServiceInfo): SsdpChange = Enum("SsdpChange", "ALIVE BYEBYE UPDATE") -SsdpCallback = Callable[[SsdpServiceInfo, SsdpChange], Awaitable] +SsdpHassJobCallback = HassJob[ + [SsdpServiceInfo, SsdpChange], Coroutine[Any, Any, None] | None +] SSDP_SOURCE_SSDP_CHANGE_MAPPING: Mapping[SsdpSource, SsdpChange] = { SsdpSource.SEARCH_ALIVE: SsdpChange.ALIVE, @@ -135,10 +139,15 @@ SSDP_SOURCE_SSDP_CHANGE_MAPPING: Mapping[SsdpSource, SsdpChange] = { } +def _format_err(name: str, *args: Any) -> str: + """Format error message.""" + return f"Exception in SSDP callback {name}: {args}" + + @bind_hass async def async_register_callback( hass: HomeAssistant, - callback: SsdpCallback, + callback: Callable[[SsdpServiceInfo, SsdpChange], Coroutine[Any, Any, None] | None], match_dict: None | dict[str, str] = None, ) -> Callable[[], None]: """Register to receive a callback on ssdp broadcast. @@ -146,7 +155,14 @@ async def async_register_callback( Returns a callback that can be used to cancel the registration. """ scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER] - return await scanner.async_register_callback(callback, match_dict) + job = HassJob( + catch_log_exception( + callback, + partial(_format_err, str(callback)), + ), + f"ssdp callback {match_dict}", + ) + return await scanner.async_register_callback(job, match_dict) @bind_hass @@ -206,14 +222,18 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -async def _async_process_callbacks( - callbacks: list[SsdpCallback], +@core_callback +def _async_process_callbacks( + hass: HomeAssistant, + callbacks: list[SsdpHassJobCallback], discovery_info: SsdpServiceInfo, ssdp_change: SsdpChange, ) -> None: for callback in callbacks: try: - await callback(discovery_info, ssdp_change) + hass.async_run_hass_job( + callback, discovery_info, ssdp_change, eager_start=True, background=True + ) except Exception: # pylint: disable=broad-except _LOGGER.exception("Failed to callback info: %s", discovery_info) @@ -287,7 +307,7 @@ class Scanner: self._cancel_scan: Callable[[], None] | None = None self._ssdp_listeners: list[SsdpListener] = [] self._device_tracker = SsdpDeviceTracker() - self._callbacks: list[tuple[SsdpCallback, dict[str, str]]] = [] + self._callbacks: list[tuple[SsdpHassJobCallback, dict[str, str]]] = [] self._description_cache: DescriptionCache | None = None self.integration_matchers = integration_matchers @@ -297,7 +317,7 @@ class Scanner: return list(self._device_tracker.devices.values()) async def async_register_callback( - self, callback: SsdpCallback, match_dict: None | dict[str, str] = None + self, callback: SsdpHassJobCallback, match_dict: None | dict[str, str] = None ) -> Callable[[], None]: """Register a callback.""" if match_dict is None: @@ -310,7 +330,8 @@ class Scanner: for ssdp_device in self._ssdp_devices: for headers in ssdp_device.all_combined_headers.values(): if _async_headers_match(headers, lower_match_dict): - await _async_process_callbacks( + _async_process_callbacks( + self.hass, [callback], await self._async_headers_to_discovery_info( ssdp_device, headers @@ -426,7 +447,7 @@ class Scanner: def _async_get_matching_callbacks( self, combined_headers: CaseInsensitiveDict, - ) -> list[SsdpCallback]: + ) -> list[SsdpHassJobCallback]: """Return a list of callbacks that match.""" return [ callback @@ -451,10 +472,11 @@ class Scanner: _, info_desc = self._description_cache.peek_description_dict(location) if info_desc is None: # Fetch info desc in separate task and process from there. - self.hass.async_create_task( + self.hass.async_create_background_task( self._ssdp_listener_process_callback_with_lookup( ssdp_device, dst, source ), + name=f"ssdp_info_desc_lookup_{location}", eager_start=True, ) return @@ -509,10 +531,7 @@ class Scanner: if callbacks: ssdp_change = SSDP_SOURCE_SSDP_CHANGE_MAPPING[source] - self.hass.async_create_task( - _async_process_callbacks(callbacks, discovery_info, ssdp_change), - eager_start=True, - ) + _async_process_callbacks(self.hass, callbacks, discovery_info, ssdp_change) # Config flows should only be created for alive/update messages from alive devices if source == SsdpSource.ADVERTISEMENT_BYEBYE: diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 6d39c8e74be..4eb4780add3 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -1425,7 +1425,7 @@ async def test_become_available( domain_data_mock.upnp_factory.async_create_device.reset_mock() # Send an SSDP notification from the now alive device - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -1498,7 +1498,7 @@ async def test_alive_but_gone( domain_data_mock.upnp_factory.async_create_device.side_effect = UpnpError # Send an SSDP notification from the still missing device - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -1611,7 +1611,7 @@ async def test_multiple_ssdp_alive( ) # Send two SSDP notifications with the new device URL - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -1651,7 +1651,7 @@ async def test_ssdp_byebye( ) -> None: """Test device is disconnected when byebye is received.""" # First byebye will cause a disconnect - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -1703,7 +1703,7 @@ async def test_ssdp_update_seen_bootid( domain_data_mock.upnp_factory.async_create_device.side_effect = None # Send SSDP alive with boot ID - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -1830,7 +1830,7 @@ async def test_ssdp_update_missed_bootid( domain_data_mock.upnp_factory.async_create_device.side_effect = None # Send SSDP alive with boot ID - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -1907,7 +1907,7 @@ async def test_ssdp_bootid( domain_data_mock.upnp_factory.async_create_device.side_effect = None # Send SSDP alive with boot ID - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -2367,7 +2367,7 @@ async def test_connections_restored( domain_data_mock.upnp_factory.async_create_device.reset_mock() # Send an SSDP notification from the now alive device - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, diff --git a/tests/components/dlna_dms/test_device_availability.py b/tests/components/dlna_dms/test_device_availability.py index c54588ce473..c1ad3c91a7b 100644 --- a/tests/components/dlna_dms/test_device_availability.py +++ b/tests/components/dlna_dms/test_device_availability.py @@ -177,7 +177,7 @@ async def test_become_available( upnp_factory_mock.async_create_device.reset_mock() # Send an SSDP notification from the now alive device - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -205,7 +205,7 @@ async def test_alive_but_gone( upnp_factory_mock.async_create_device.side_effect = UpnpError # Send an SSDP notification from the still missing device - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -308,7 +308,7 @@ async def test_multiple_ssdp_alive( upnp_factory_mock.async_create_device.side_effect = create_device_delayed # Send two SSDP notifications with the new device URL - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -343,7 +343,7 @@ async def test_ssdp_byebye( ) -> None: """Test device is disconnected when byebye is received.""" # First byebye will cause a disconnect - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -386,7 +386,7 @@ async def test_ssdp_update_seen_bootid( upnp_factory_mock.async_create_device.side_effect = None # Send SSDP alive with boot ID - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -498,7 +498,7 @@ async def test_ssdp_update_missed_bootid( upnp_factory_mock.async_create_device.side_effect = None # Send SSDP alive with boot ID - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, @@ -568,7 +568,7 @@ async def test_ssdp_bootid( upnp_factory_mock.async_create_device.reset_mock() # Send SSDP alive with boot ID - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, diff --git a/tests/components/dlna_dms/test_dms_device_source.py b/tests/components/dlna_dms/test_dms_device_source.py index 75b26ceb900..47bd7b0b39b 100644 --- a/tests/components/dlna_dms/test_dms_device_source.py +++ b/tests/components/dlna_dms/test_dms_device_source.py @@ -67,7 +67,7 @@ async def test_catch_request_error_unavailable( ) -> None: """Test the device is checked for availability before trying requests.""" # DmsDevice notifies of disconnect via SSDP - ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0] + ssdp_callback = ssdp_scanner_mock.async_register_callback.call_args.args[0].target await ssdp_callback( ssdp.SsdpServiceInfo( ssdp_usn=MOCK_DEVICE_USN, diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index e88214f01f5..7af2287c893 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -343,7 +343,7 @@ async def test_flow_start_only_alive( } ) ssdp_listener._on_search(mock_ssdp_search_response) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_flow_init.assert_awaited_once_with( "mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY @@ -464,6 +464,7 @@ async def test_start_stop_scanner(mock_source_set, hass: HomeAssistant) -> None: @pytest.mark.usefixtures("mock_get_source_ip") +@pytest.mark.no_fail_on_log_exception @patch("homeassistant.components.ssdp.async_get_ssdp", return_value={}) async def test_scan_with_registered_callback( mock_get_ssdp, @@ -523,9 +524,9 @@ async def test_scan_with_registered_callback( async_match_any_callback = AsyncMock() await ssdp.async_register_callback(hass, async_match_any_callback) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) ssdp_listener._on_search(mock_ssdp_search_response) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_integration_callback.call_count == 1 assert async_integration_match_all_callback.call_count == 1 @@ -549,7 +550,7 @@ async def test_scan_with_registered_callback( ssdp.ATTR_UPNP_DEVICE_TYPE: "Paulus", ssdp.ATTR_UPNP_UDN: "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL", } - assert "Failed to callback info" in caplog.text + assert "Exception in SSDP callback" in caplog.text async_integration_callback_from_cache = AsyncMock() await ssdp.async_register_callback( @@ -835,7 +836,7 @@ async def test_flow_dismiss_on_byebye( } ) ssdp_listener._on_search(mock_ssdp_search_response) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_flow_init.assert_awaited_once_with( "mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY @@ -853,7 +854,7 @@ async def test_flow_dismiss_on_byebye( } ) ssdp_listener._on_alive(mock_ssdp_advertisement) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_flow_init.assert_awaited_once_with( "mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY ) @@ -868,7 +869,7 @@ async def test_flow_dismiss_on_byebye( hass.config_entries.flow, "async_abort" ) as mock_async_abort: ssdp_listener._on_byebye(mock_ssdp_advertisement) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert len(mock_async_progress_by_init_data_type.mock_calls) == 1 assert mock_async_abort.mock_calls[0][1][0] == "mock_flow_id" From bf5537eb5a23ce3d932688e077a534aebf2f1c13 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 10:35:25 +0100 Subject: [PATCH 0598/1691] Give the UniFi integration better control over what data to load (#112804) --- .../components/unifi/hub/entity_loader.py | 23 +++++++++++++++++++ homeassistant/components/unifi/hub/hub.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index be053403bff..17e31836790 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -5,6 +5,7 @@ Make sure expected clients are available for platforms. """ from __future__ import annotations +import asyncio from collections.abc import Iterable from datetime import timedelta from functools import partial @@ -16,6 +17,7 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from ..const import LOGGER from ..entity import UnifiEntity, UnifiEntityDescription if TYPE_CHECKING: @@ -30,6 +32,17 @@ class UnifiEntityLoader: def __init__(self, hub: UnifiHub) -> None: """Initialize the UniFi entity loader.""" self.hub = hub + self.api_updaters = ( + hub.api.clients.update, + hub.api.clients_all.update, + hub.api.devices.update, + hub.api.dpi_apps.update, + hub.api.dpi_groups.update, + hub.api.port_forwarding.update, + hub.api.sites.update, + hub.api.system_information.update, + hub.api.wlans.update, + ) self.platforms: list[ tuple[ @@ -43,6 +56,16 @@ class UnifiEntityLoader: self.known_objects: set[tuple[str, str]] = set() """Tuples of entity description key and object ID of loaded entities.""" + async def refresh_api_data(self) -> None: + """Refresh API data from network application.""" + results = await asyncio.gather( + *[update() for update in self.api_updaters], + return_exceptions=True, + ) + for result in results: + if result is not None: + LOGGER.warning("Exception on update %s", result) + @callback def register_platform( self, diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index ae485ec36b8..f152c928659 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -88,7 +88,7 @@ class UnifiHub: async def initialize(self) -> None: """Set up a UniFi Network instance.""" - await self.api.initialize() + await self.entity_loader.refresh_api_data() assert self.config.entry.unique_id is not None self.is_admin = self.api.sites[self.config.entry.unique_id].role == "admin" From 8b2759d810cb7f949caeb305ac6748e6fb8fa0d5 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 10:52:59 +0100 Subject: [PATCH 0599/1691] Move restoring inactive clients method into UniFi entity loader (#112805) * Move restoring inactive clients method into UniFi entity loader * Use an initialize method in entity_loader --- .../components/unifi/hub/entity_loader.py | 28 +++++++++++++++++++ homeassistant/components/unifi/hub/hub.py | 23 ++------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index 17e31836790..940d4dbdcad 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -13,9 +13,12 @@ from typing import TYPE_CHECKING from aiounifi.interfaces.api_handlers import ItemEvent +from homeassistant.const import Platform from homeassistant.core import callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_registry import async_entries_for_config_entry from ..const import LOGGER from ..entity import UnifiEntity, UnifiEntityDescription @@ -56,6 +59,11 @@ class UnifiEntityLoader: self.known_objects: set[tuple[str, str]] = set() """Tuples of entity description key and object ID of loaded entities.""" + async def initialize(self) -> None: + """Initialize API data and extra client support.""" + await self.refresh_api_data() + self.restore_inactive_clients() + async def refresh_api_data(self) -> None: """Refresh API data from network application.""" results = await asyncio.gather( @@ -66,6 +74,26 @@ class UnifiEntityLoader: if result is not None: LOGGER.warning("Exception on update %s", result) + @callback + def restore_inactive_clients(self) -> None: + """Restore inactive clients. + + Provide inactive clients to device tracker and switch platform. + """ + config = self.hub.config + macs: list[str] = [] + entity_registry = er.async_get(self.hub.hass) + for entry in async_entries_for_config_entry( + entity_registry, config.entry.entry_id + ): + if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id: + macs.append(entry.unique_id.split("-", 1)[1]) + + api = self.hub.api + for mac in config.option_supported_clients + config.option_block_clients + macs: + if mac not in api.clients and mac in api.clients_all: + api.clients.process_raw([dict(api.clients_all[mac].raw)]) + @callback def register_platform( self, diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index f152c928659..b17e0d154a7 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -8,16 +8,14 @@ import aiounifi from aiounifi.models.device import DeviceSetPoePortModeRequest from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import ( DeviceEntry, DeviceEntryType, DeviceInfo, ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity_registry import async_entries_for_config_entry from homeassistant.helpers.event import async_call_later, async_track_time_interval import homeassistant.util.dt as dt_util @@ -88,28 +86,11 @@ class UnifiHub: async def initialize(self) -> None: """Set up a UniFi Network instance.""" - await self.entity_loader.refresh_api_data() + await self.entity_loader.initialize() assert self.config.entry.unique_id is not None self.is_admin = self.api.sites[self.config.entry.unique_id].role == "admin" - # Restore device tracker clients that are not a part of active clients list. - macs: list[str] = [] - entity_registry = er.async_get(self.hass) - for entry in async_entries_for_config_entry( - entity_registry, self.config.entry.entry_id - ): - if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id: - macs.append(entry.unique_id.split("-", 1)[1]) - - for mac in ( - self.config.option_supported_clients - + self.config.option_block_clients - + macs - ): - if mac not in self.api.clients and mac in self.api.clients_all: - self.api.clients.process_raw([dict(self.api.clients_all[mac].raw)]) - self.wireless_clients.update_clients(set(self.api.clients.values())) self.config.entry.add_update_listener(self.async_config_entry_updated) From 3301117223d164fb5dfddeb85f80ebde4bbf0722 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 11:00:23 +0100 Subject: [PATCH 0600/1691] Remove entity description mixin in Jellyfin (#112782) --- homeassistant/components/jellyfin/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/jellyfin/sensor.py b/homeassistant/components/jellyfin/sensor.py index b4341d2a190..85c7e9e9ee1 100644 --- a/homeassistant/components/jellyfin/sensor.py +++ b/homeassistant/components/jellyfin/sensor.py @@ -17,20 +17,13 @@ from .entity import JellyfinEntity from .models import JellyfinData -@dataclass(frozen=True) -class JellyfinSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class JellyfinSensorEntityDescription(SensorEntityDescription): + """Describes Jellyfin sensor entity.""" value_fn: Callable[[JellyfinDataT], StateType] -@dataclass(frozen=True) -class JellyfinSensorEntityDescription( - SensorEntityDescription, JellyfinSensorEntityDescriptionMixin -): - """Describes Jellyfin sensor entity.""" - - def _count_now_playing(data: JellyfinDataT) -> int: """Count the number of now playing.""" session_ids = [ From b26f00bf3978d224e19f727089a0fdf415f0f242 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 11:00:36 +0100 Subject: [PATCH 0601/1691] Remove entity description mixin in Goodwe (#112773) --- homeassistant/components/goodwe/button.py | 13 +++---------- homeassistant/components/goodwe/number.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/goodwe/button.py b/homeassistant/components/goodwe/button.py index 62d9151061b..d3d96a19a76 100644 --- a/homeassistant/components/goodwe/button.py +++ b/homeassistant/components/goodwe/button.py @@ -19,20 +19,13 @@ from .const import DOMAIN, KEY_DEVICE_INFO, KEY_INVERTER _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class GoodweButtonEntityDescriptionRequired: - """Required attributes of GoodweButtonEntityDescription.""" +@dataclass(frozen=True, kw_only=True) +class GoodweButtonEntityDescription(ButtonEntityDescription): + """Class describing Goodwe button entities.""" action: Callable[[Inverter], Awaitable[None]] -@dataclass(frozen=True) -class GoodweButtonEntityDescription( - ButtonEntityDescription, GoodweButtonEntityDescriptionRequired -): - """Class describing Goodwe button entities.""" - - SYNCHRONIZE_CLOCK = GoodweButtonEntityDescription( key="synchronize_clock", translation_key="synchronize_clock", diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py index 8bdcf13ae53..fc8b3864ae9 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -24,22 +24,15 @@ from .const import DOMAIN, KEY_DEVICE_INFO, KEY_INVERTER _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class GoodweNumberEntityDescriptionBase: - """Required values when describing Goodwe number entities.""" +@dataclass(frozen=True, kw_only=True) +class GoodweNumberEntityDescription(NumberEntityDescription): + """Class describing Goodwe number entities.""" getter: Callable[[Inverter], Awaitable[int]] setter: Callable[[Inverter, int], Awaitable[None]] filter: Callable[[Inverter], bool] -@dataclass(frozen=True) -class GoodweNumberEntityDescription( - NumberEntityDescription, GoodweNumberEntityDescriptionBase -): - """Class describing Goodwe number entities.""" - - def _get_setting_unit(inverter: Inverter, setting: str) -> str: """Return the unit of an inverter setting.""" return next((s.unit for s in inverter.settings() if s.id_ == setting), "") From a2180b16c3289529e5d911f11fb4c1c7c53019bc Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 11:30:01 +0100 Subject: [PATCH 0602/1691] Move wireless clients into entity_loader (#112813) --- homeassistant/components/unifi/device_tracker.py | 4 ++-- homeassistant/components/unifi/hub/entity_loader.py | 4 +++- homeassistant/components/unifi/hub/hub.py | 12 +----------- homeassistant/components/unifi/sensor.py | 4 ++-- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index ffd4566b77c..96a8a5dc1f8 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -89,7 +89,7 @@ def async_client_allowed_fn(hub: UnifiHub, obj_id: str) -> bool: return False client = hub.api.clients[obj_id] - if client.mac not in hub.wireless_clients: + if client.mac not in hub.entity_loader.wireless_clients: if not hub.config.option_track_wired_clients: return False @@ -108,7 +108,7 @@ def async_client_is_connected_fn(hub: UnifiHub, obj_id: str) -> bool: """Check if device object is disabled.""" client = hub.api.clients[obj_id] - if hub.wireless_clients.is_wireless(client) and client.is_wired: + if hub.entity_loader.wireless_clients.is_wireless(client) and client.is_wired: if not hub.config.option_ignore_wired_bug: return False # Wired bug in action diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index 940d4dbdcad..af63419d82a 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -20,7 +20,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_registry import async_entries_for_config_entry -from ..const import LOGGER +from ..const import LOGGER, UNIFI_WIRELESS_CLIENTS from ..entity import UnifiEntity, UnifiEntityDescription if TYPE_CHECKING: @@ -46,6 +46,7 @@ class UnifiEntityLoader: hub.api.system_information.update, hub.api.wlans.update, ) + self.wireless_clients = hub.hass.data[UNIFI_WIRELESS_CLIENTS] self.platforms: list[ tuple[ @@ -63,6 +64,7 @@ class UnifiEntityLoader: """Initialize API data and extra client support.""" await self.refresh_api_data() self.restore_inactive_clients() + self.wireless_clients.update_clients(set(self.hub.api.clients.values())) async def refresh_api_data(self) -> None: """Refresh API data from network application.""" diff --git a/homeassistant/components/unifi/hub/hub.py b/homeassistant/components/unifi/hub/hub.py index b17e0d154a7..df91584f267 100644 --- a/homeassistant/components/unifi/hub/hub.py +++ b/homeassistant/components/unifi/hub/hub.py @@ -19,13 +19,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later, async_track_time_interval import homeassistant.util.dt as dt_util -from ..const import ( - ATTR_MANUFACTURER, - CONF_SITE_ID, - DOMAIN as UNIFI_DOMAIN, - PLATFORMS, - UNIFI_WIRELESS_CLIENTS, -) +from ..const import ATTR_MANUFACTURER, CONF_SITE_ID, DOMAIN as UNIFI_DOMAIN, PLATFORMS from .config import UnifiConfig from .entity_loader import UnifiEntityLoader from .websocket import UnifiWebsocket @@ -46,8 +40,6 @@ class UnifiHub: self.entity_loader = UnifiEntityLoader(self) self.websocket = UnifiWebsocket(hass, api, self.signal_reachable) - self.wireless_clients = hass.data[UNIFI_WIRELESS_CLIENTS] - self.site = config_entry.data[CONF_SITE_ID] self.is_admin = False @@ -91,8 +83,6 @@ class UnifiHub: assert self.config.entry.unique_id is not None self.is_admin = self.api.sites[self.config.entry.unique_id].role == "admin" - self.wireless_clients.update_clients(set(self.api.clients.values())) - self.config.entry.add_update_listener(self.async_config_entry_updated) self._cancel_heartbeat_check = async_track_time_interval( diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 818b5e53b6a..efb3eed4de4 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -72,7 +72,7 @@ def async_uptime_sensor_allowed_fn(hub: UnifiHub, obj_id: str) -> bool: @callback def async_client_rx_value_fn(hub: UnifiHub, client: Client) -> float: """Calculate receiving data transfer value.""" - if hub.wireless_clients.is_wireless(client): + if hub.entity_loader.wireless_clients.is_wireless(client): return client.rx_bytes_r / 1000000 return client.wired_rx_bytes_r / 1000000 @@ -80,7 +80,7 @@ def async_client_rx_value_fn(hub: UnifiHub, client: Client) -> float: @callback def async_client_tx_value_fn(hub: UnifiHub, client: Client) -> float: """Calculate transmission data transfer value.""" - if hub.wireless_clients.is_wireless(client): + if hub.entity_loader.wireless_clients.is_wireless(client): return client.tx_bytes_r / 1000000 return client.wired_tx_bytes_r / 1000000 From 87318c91119093e01f08b551211ce3ee83660c0c Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 9 Mar 2024 11:54:27 +0100 Subject: [PATCH 0603/1691] Cleanup mqtt discovery code (#112749) * Cleanup mqtt discovery code * Cleanup mqtt discovery code --- homeassistant/components/mqtt/__init__.py | 2 +- homeassistant/components/mqtt/discovery.py | 9 +++------ tests/components/mqtt/test_discovery.py | 9 +++------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 14f92e29cdf..0bf2d8f53e1 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -463,7 +463,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Setup discovery if conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY): await discovery.async_start( - hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX), entry + hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX) ) # Setup reload service after all platforms have loaded await async_setup_reload_service() diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 5fa1b6297d7..806059b77f9 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING, Any import voluptuous as vol -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_PLATFORM from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResultType @@ -137,11 +136,10 @@ def async_log_discovery_origin_info( async def async_start( # noqa: C901 - hass: HomeAssistant, discovery_topic: str, config_entry: ConfigEntry + hass: HomeAssistant, discovery_topic: str ) -> None: """Start MQTT Discovery.""" mqtt_data = get_mqtt_data(hass) - mqtt_integrations = {} @callback def async_discovery_message_received(msg: ReceiveMessage) -> None: # noqa: C901 @@ -155,9 +153,8 @@ async def async_start( # noqa: C901 if topic_trimmed.endswith("config"): _LOGGER.warning( ( - "Received message on illegal discovery topic '%s'. The topic" - " contains " - "not allowed characters. For more information see " + "Received message on illegal discovery topic '%s'. The topic " + "contains not allowed characters. For more information see " "https://www.home-assistant.io/integrations/mqtt/#discovery-topic" ), topic, diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 6121e1dc15e..5249789a13f 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -55,10 +55,9 @@ async def test_subscribing_config_topic( ) -> None: """Test setting up discovery.""" mqtt_mock = await mqtt_mock_entry() - entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] discovery_topic = "homeassistant" - await async_start(hass, discovery_topic, entry) + await async_start(hass, discovery_topic) call_args1 = mqtt_mock.async_subscribe.mock_calls[0][1] assert call_args1[2] == 0 @@ -1503,14 +1502,13 @@ async def test_mqtt_integration_discovery_subscribe_unsubscribe( mqtt_mock = await mqtt_mock_entry() mock_platform(hass, "comp.config_flow", None) - entry = hass.config_entries.async_entries("mqtt")[0] mqtt_mock().connected = True with patch( "homeassistant.components.mqtt.discovery.async_get_mqtt", return_value={"comp": ["comp/discovery/#"]}, ): - await async_start(hass, "homeassistant", entry) + await async_start(hass, "homeassistant") await hass.async_block_till_done() await hass.async_block_till_done() @@ -1553,14 +1551,13 @@ async def test_mqtt_discovery_unsubscribe_once( mqtt_mock = await mqtt_mock_entry() mock_platform(hass, "comp.config_flow", None) - entry = hass.config_entries.async_entries("mqtt")[0] mqtt_mock().connected = True with patch( "homeassistant.components.mqtt.discovery.async_get_mqtt", return_value={"comp": ["comp/discovery/#"]}, ): - await async_start(hass, "homeassistant", entry) + await async_start(hass, "homeassistant") await hass.async_block_till_done() await hass.async_block_till_done() From fca3ac9903382aaa8cfb086e26cd5537b4456d9b Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 9 Mar 2024 12:19:57 +0100 Subject: [PATCH 0604/1691] Update modbus test to include a dummy sensor (#112820) --- tests/components/modbus/conftest.py | 15 +++++++++- tests/components/modbus/test_init.py | 42 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index 4fdda5d9aea..e470f30fe82 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -11,7 +11,14 @@ from pymodbus.exceptions import ModbusException import pytest from homeassistant.components.modbus.const import MODBUS_DOMAIN as DOMAIN, TCP -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE +from homeassistant.const import ( + CONF_ADDRESS, + CONF_HOST, + CONF_NAME, + CONF_PORT, + CONF_SENSORS, + CONF_TYPE, +) from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -101,6 +108,12 @@ async def mock_modbus_fixture( CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_NAME: TEST_MODBUS_NAME, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], **conf, } ] diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index c3a4c35433b..3101f2d3376 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -362,6 +362,12 @@ async def test_exception_struct_validator(do_config) -> None: CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_NAME: TEST_MODBUS_NAME, @@ -369,6 +375,12 @@ async def test_exception_struct_validator(do_config) -> None: CONF_HOST: TEST_MODBUS_HOST + " 2", CONF_PORT: TEST_PORT_TCP, CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_NAME: TEST_MODBUS_NAME + "2", @@ -376,6 +388,12 @@ async def test_exception_struct_validator(do_config) -> None: CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, ], [ @@ -384,6 +402,12 @@ async def test_exception_struct_validator(do_config) -> None: CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_NAME: TEST_MODBUS_NAME + " 2", @@ -391,6 +415,12 @@ async def test_exception_struct_validator(do_config) -> None: CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_TIMEOUT: 3, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, ], ], @@ -1093,6 +1123,12 @@ SERVICE = "service" CONF_PORT: TEST_PORT_SERIAL, CONF_PARITY: "E", CONF_STOPBITS: 1, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, ], ) @@ -1287,6 +1323,12 @@ async def test_pymodbus_constructor_fail( CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + }, + ], } ] } From 6534943837ccd5a1c55c0a3d16784aed4dddfa1d Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 9 Mar 2024 12:54:10 +0100 Subject: [PATCH 0605/1691] Revert "Cleanup mqtt discovery code" (#112818) Revert "Cleanup mqtt discovery code (#112749)" This reverts commit 87318c91119093e01f08b551211ce3ee83660c0c. --- homeassistant/components/mqtt/__init__.py | 2 +- homeassistant/components/mqtt/discovery.py | 9 ++++++--- tests/components/mqtt/test_discovery.py | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 0bf2d8f53e1..14f92e29cdf 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -463,7 +463,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Setup discovery if conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY): await discovery.async_start( - hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX) + hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX), entry ) # Setup reload service after all platforms have loaded await async_setup_reload_service() diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 806059b77f9..5fa1b6297d7 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Any import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE, CONF_NAME, CONF_PLATFORM from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResultType @@ -136,10 +137,11 @@ def async_log_discovery_origin_info( async def async_start( # noqa: C901 - hass: HomeAssistant, discovery_topic: str + hass: HomeAssistant, discovery_topic: str, config_entry: ConfigEntry ) -> None: """Start MQTT Discovery.""" mqtt_data = get_mqtt_data(hass) + mqtt_integrations = {} @callback def async_discovery_message_received(msg: ReceiveMessage) -> None: # noqa: C901 @@ -153,8 +155,9 @@ async def async_start( # noqa: C901 if topic_trimmed.endswith("config"): _LOGGER.warning( ( - "Received message on illegal discovery topic '%s'. The topic " - "contains not allowed characters. For more information see " + "Received message on illegal discovery topic '%s'. The topic" + " contains " + "not allowed characters. For more information see " "https://www.home-assistant.io/integrations/mqtt/#discovery-topic" ), topic, diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 5249789a13f..6121e1dc15e 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -55,9 +55,10 @@ async def test_subscribing_config_topic( ) -> None: """Test setting up discovery.""" mqtt_mock = await mqtt_mock_entry() + entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] discovery_topic = "homeassistant" - await async_start(hass, discovery_topic) + await async_start(hass, discovery_topic, entry) call_args1 = mqtt_mock.async_subscribe.mock_calls[0][1] assert call_args1[2] == 0 @@ -1502,13 +1503,14 @@ async def test_mqtt_integration_discovery_subscribe_unsubscribe( mqtt_mock = await mqtt_mock_entry() mock_platform(hass, "comp.config_flow", None) + entry = hass.config_entries.async_entries("mqtt")[0] mqtt_mock().connected = True with patch( "homeassistant.components.mqtt.discovery.async_get_mqtt", return_value={"comp": ["comp/discovery/#"]}, ): - await async_start(hass, "homeassistant") + await async_start(hass, "homeassistant", entry) await hass.async_block_till_done() await hass.async_block_till_done() @@ -1551,13 +1553,14 @@ async def test_mqtt_discovery_unsubscribe_once( mqtt_mock = await mqtt_mock_entry() mock_platform(hass, "comp.config_flow", None) + entry = hass.config_entries.async_entries("mqtt")[0] mqtt_mock().connected = True with patch( "homeassistant.components.mqtt.discovery.async_get_mqtt", return_value={"comp": ["comp/discovery/#"]}, ): - await async_start(hass, "homeassistant") + await async_start(hass, "homeassistant", entry) await hass.async_block_till_done() await hass.async_block_till_done() From 797983236f20b9e89ea5c79d7d05c75df33d97d9 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 9 Mar 2024 13:24:06 +0100 Subject: [PATCH 0606/1691] Remove deprecated CLOSE_COMM_ON_ERROR from modbus (#112821) --- homeassistant/components/modbus/__init__.py | 2 -- homeassistant/components/modbus/const.py | 1 - homeassistant/components/modbus/modbus.py | 19 ------------------- homeassistant/components/modbus/strings.json | 4 ---- tests/components/modbus/test_init.py | 2 -- 5 files changed, 28 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 8224a79c835..09112e16767 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -61,7 +61,6 @@ from .const import ( # noqa: F401 CONF_BAUDRATE, CONF_BYTESIZE, CONF_CLIMATES, - CONF_CLOSE_COMM_ON_ERROR, CONF_DATA_TYPE, CONF_DEVICE_ADDRESS, CONF_FAN_MODE_AUTO, @@ -374,7 +373,6 @@ MODBUS_SCHEMA = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string, vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, - vol.Optional(CONF_CLOSE_COMM_ON_ERROR): cv.boolean, vol.Optional(CONF_DELAY, default=0): cv.positive_int, vol.Optional(CONF_RETRIES): cv.positive_int, vol.Optional(CONF_RETRY_ON_EMPTY): cv.boolean, diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index a2b3324c4ee..3a7b25ad786 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -16,7 +16,6 @@ from homeassistant.const import ( CONF_BAUDRATE = "baudrate" CONF_BYTESIZE = "bytesize" CONF_CLIMATES = "climates" -CONF_CLOSE_COMM_ON_ERROR = "close_comm_on_error" CONF_DATA_TYPE = "data_type" CONF_DEVICE_ADDRESS = "device_address" CONF_FANS = "fans" diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 498dcbf0f7e..36c081df616 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -51,7 +51,6 @@ from .const import ( CALL_TYPE_WRITE_REGISTERS, CONF_BAUDRATE, CONF_BYTESIZE, - CONF_CLOSE_COMM_ON_ERROR, CONF_MSG_WAIT, CONF_PARITY, CONF_RETRIES, @@ -273,24 +272,6 @@ class ModbusHub: ) else: client_config[CONF_RETRIES] = 3 - if CONF_CLOSE_COMM_ON_ERROR in client_config: - async_create_issue( - hass, - DOMAIN, - "deprecated_close_comm_config", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_close_comm_config", - translation_placeholders={ - "config_key": "close_comm_on_error", - "integration": DOMAIN, - "url": "https://www.home-assistant.io/integrations/modbus", - }, - ) - _LOGGER.warning( - "`close_comm_on_error`: is deprecated and will be removed in version 2024.4" - ) if CONF_RETRY_ON_EMPTY in client_config: async_create_issue( hass, diff --git a/homeassistant/components/modbus/strings.json b/homeassistant/components/modbus/strings.json index 12e66f5d2ca..12023579669 100644 --- a/homeassistant/components/modbus/strings.json +++ b/homeassistant/components/modbus/strings.json @@ -78,10 +78,6 @@ "title": "`{config_key}` configuration key is being removed", "description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3." }, - "deprecated_close_comm_config": { - "title": "`{config_key}` configuration key is being removed", - "description": "Please remove the `{config_key}` key from the {integration} entry in your `configuration.yaml` file and restart Home Assistant to fix this issue. All errors will be reported, as `lazy_error_count` is accepted but ignored." - }, "deprecated_retry_on_empty": { "title": "`{config_key}` configuration key is being removed", "description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nRetry on empty is automatically applied, see [the documentation]({url}) for other error handling parameters." diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 3101f2d3376..20eecf49591 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -42,7 +42,6 @@ from homeassistant.components.modbus.const import ( CONF_BAUDRATE, CONF_BYTESIZE, CONF_CLIMATES, - CONF_CLOSE_COMM_ON_ERROR, CONF_DATA_TYPE, CONF_DEVICE_ADDRESS, CONF_FAN_MODE_HIGH, @@ -964,7 +963,6 @@ async def test_no_duplicate_names(do_config) -> None: CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, - CONF_CLOSE_COMM_ON_ERROR: True, }, { CONF_TYPE: TCP, From fed40a89cae9e3570d452b3573008c1b4ec32f35 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 13:41:08 +0100 Subject: [PATCH 0607/1691] Remove entity description mixin in Huawei LTE (#112777) --- homeassistant/components/huawei_lte/select.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/huawei_lte/select.py b/homeassistant/components/huawei_lte/select.py index f4ff0a93d98..bf8f65a8ba5 100644 --- a/homeassistant/components/huawei_lte/select.py +++ b/homeassistant/components/huawei_lte/select.py @@ -27,18 +27,13 @@ from .const import DOMAIN, KEY_NET_NET_MODE _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class HuaweiSelectEntityMixin: - """Mixin for Huawei LTE select entities, to ensure required fields are set.""" +@dataclass(frozen=True, kw_only=True) +class HuaweiSelectEntityDescription(SelectEntityDescription): + """Class describing Huawei LTE select entities.""" setter_fn: Callable[[str], None] -@dataclass(frozen=True) -class HuaweiSelectEntityDescription(SelectEntityDescription, HuaweiSelectEntityMixin): - """Class describing Huawei LTE select entities.""" - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, From 40aaba6b1df644b3f6ef69b246147a0dce700cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 9 Mar 2024 14:44:05 +0200 Subject: [PATCH 0608/1691] Remove outdated prettier and yamllint `azure-*.yml` ignores (#112819) --- .prettierignore | 1 - .yamllint | 1 - 2 files changed, 2 deletions(-) diff --git a/.prettierignore b/.prettierignore index b249b537137..c6329099666 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,5 @@ *.md .strict-typing -azure-*.yml homeassistant/components/*/translations/*.json homeassistant/generated/* tests/components/lidarr/fixtures/initialize.js diff --git a/.yamllint b/.yamllint index d8387c634ee..aa14d14c173 100644 --- a/.yamllint +++ b/.yamllint @@ -1,5 +1,4 @@ ignore: | - azure-*.yml tests/fixtures/core/config/yaml_errors/ rules: braces: From 59083b4e82ad113a3911501ea5de53bfa766d517 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 9 Mar 2024 13:59:09 +0100 Subject: [PATCH 0609/1691] Remove deprecated retry_on_empty from modbus (#112822) Remove deprecated retry_on_empty. --- homeassistant/components/modbus/__init__.py | 2 -- homeassistant/components/modbus/const.py | 1 - homeassistant/components/modbus/modbus.py | 19 ------------------- homeassistant/components/modbus/strings.json | 4 ---- tests/components/modbus/test_init.py | 2 -- 5 files changed, 28 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 09112e16767..d008f5d167c 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -97,7 +97,6 @@ from .const import ( # noqa: F401 CONF_PARITY, CONF_PRECISION, CONF_RETRIES, - CONF_RETRY_ON_EMPTY, CONF_SCALE, CONF_SLAVE_COUNT, CONF_STATE_CLOSED, @@ -375,7 +374,6 @@ MODBUS_SCHEMA = vol.Schema( vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, vol.Optional(CONF_DELAY, default=0): cv.positive_int, vol.Optional(CONF_RETRIES): cv.positive_int, - vol.Optional(CONF_RETRY_ON_EMPTY): cv.boolean, vol.Optional(CONF_MSG_WAIT): cv.positive_int, vol.Optional(CONF_BINARY_SENSORS): vol.All( cv.ensure_list, [BINARY_SENSOR_SCHEMA] diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index 3a7b25ad786..425bd744a1e 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -29,7 +29,6 @@ CONF_MSG_WAIT = "message_wait_milliseconds" CONF_NAN_VALUE = "nan_value" CONF_PARITY = "parity" CONF_RETRIES = "retries" -CONF_RETRY_ON_EMPTY = "retry_on_empty" CONF_PRECISION = "precision" CONF_SCALE = "scale" CONF_SLAVE_COUNT = "slave_count" diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 36c081df616..7138341d445 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -54,7 +54,6 @@ from .const import ( CONF_MSG_WAIT, CONF_PARITY, CONF_RETRIES, - CONF_RETRY_ON_EMPTY, CONF_STOPBITS, DEFAULT_HUB, MODBUS_DOMAIN as DOMAIN, @@ -272,24 +271,6 @@ class ModbusHub: ) else: client_config[CONF_RETRIES] = 3 - if CONF_RETRY_ON_EMPTY in client_config: - async_create_issue( - hass, - DOMAIN, - "deprecated_retry_on_empty", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_retry_on_empty", - translation_placeholders={ - "config_key": "retry_on_empty", - "integration": DOMAIN, - "url": "https://www.home-assistant.io/integrations/modbus", - }, - ) - _LOGGER.warning( - "`retry_on_empty`: is deprecated and will be removed in version 2024.4" - ) # generic configuration self._client: ModbusBaseClient | None = None self._async_cancel_listener: Callable[[], None] | None = None diff --git a/homeassistant/components/modbus/strings.json b/homeassistant/components/modbus/strings.json index 12023579669..4f7bb1c60e3 100644 --- a/homeassistant/components/modbus/strings.json +++ b/homeassistant/components/modbus/strings.json @@ -77,10 +77,6 @@ "deprecated_retries": { "title": "`{config_key}` configuration key is being removed", "description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3." - }, - "deprecated_retry_on_empty": { - "title": "`{config_key}` configuration key is being removed", - "description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nRetry on empty is automatically applied, see [the documentation]({url}) for other error handling parameters." } } } diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 20eecf49591..aaf2b335b90 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -60,7 +60,6 @@ from homeassistant.components.modbus.const import ( CONF_MSG_WAIT, CONF_PARITY, CONF_RETRIES, - CONF_RETRY_ON_EMPTY, CONF_SLAVE_COUNT, CONF_STOPBITS, CONF_SWAP, @@ -974,7 +973,6 @@ async def test_no_duplicate_names(do_config) -> None: CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, - CONF_RETRY_ON_EMPTY: True, }, { CONF_TYPE: TCP, From bfd75828255b6ebb59ff2d15d626a9f7b774cb42 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:00:31 +0100 Subject: [PATCH 0610/1691] Remove entity description mixin in Glances (#112772) --- homeassistant/components/glances/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 35350e863fa..7591bf954d3 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -28,20 +28,13 @@ from . import GlancesDataUpdateCoordinator from .const import CPU_ICON, DOMAIN -@dataclass(frozen=True) -class GlancesSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class GlancesSensorEntityDescription(SensorEntityDescription): + """Describe Glances sensor entity.""" type: str -@dataclass(frozen=True) -class GlancesSensorEntityDescription( - SensorEntityDescription, GlancesSensorEntityDescriptionMixin -): - """Describe Glances sensor entity.""" - - SENSOR_TYPES = { ("fs", "disk_use_percent"): GlancesSensorEntityDescription( key="disk_use_percent", From 06637c0960135b7238993fd4f3b507705afc400b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:01:45 +0100 Subject: [PATCH 0611/1691] Remove entity description mixin in EZVIZ (#112764) --- .../components/ezviz/alarm_control_panel.py | 13 +++---------- homeassistant/components/ezviz/button.py | 13 +++---------- homeassistant/components/ezviz/number.py | 13 +++---------- homeassistant/components/ezviz/select.py | 13 +++---------- homeassistant/components/ezviz/switch.py | 13 +++---------- 5 files changed, 15 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/ezviz/alarm_control_panel.py b/homeassistant/components/ezviz/alarm_control_panel.py index f847b8445f9..21e9f2d0422 100644 --- a/homeassistant/components/ezviz/alarm_control_panel.py +++ b/homeassistant/components/ezviz/alarm_control_panel.py @@ -34,20 +34,13 @@ SCAN_INTERVAL = timedelta(seconds=60) PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class EzvizAlarmControlPanelEntityDescriptionMixin: - """Mixin values for EZVIZ Alarm control panel entities.""" +@dataclass(frozen=True, kw_only=True) +class EzvizAlarmControlPanelEntityDescription(AlarmControlPanelEntityDescription): + """Describe an EZVIZ Alarm control panel entity.""" ezviz_alarm_states: list -@dataclass(frozen=True) -class EzvizAlarmControlPanelEntityDescription( - AlarmControlPanelEntityDescription, EzvizAlarmControlPanelEntityDescriptionMixin -): - """Describe an EZVIZ Alarm control panel entity.""" - - ALARM_TYPE = EzvizAlarmControlPanelEntityDescription( key="ezviz_alarm", ezviz_alarm_states=[ diff --git a/homeassistant/components/ezviz/button.py b/homeassistant/components/ezviz/button.py index deb2ca33355..a4c101a81bc 100644 --- a/homeassistant/components/ezviz/button.py +++ b/homeassistant/components/ezviz/button.py @@ -23,21 +23,14 @@ from .entity import EzvizEntity PARALLEL_UPDATES = 1 -@dataclass(frozen=True) -class EzvizButtonEntityDescriptionMixin: - """Mixin values for EZVIZ button entities.""" +@dataclass(frozen=True, kw_only=True) +class EzvizButtonEntityDescription(ButtonEntityDescription): + """Describe a EZVIZ Button.""" method: Callable[[EzvizClient, str, str], Any] supported_ext: str -@dataclass(frozen=True) -class EzvizButtonEntityDescription( - ButtonEntityDescription, EzvizButtonEntityDescriptionMixin -): - """Describe a EZVIZ Button.""" - - BUTTON_ENTITIES = ( EzvizButtonEntityDescription( key="ptz_up", diff --git a/homeassistant/components/ezviz/number.py b/homeassistant/components/ezviz/number.py index 8fe0ba1330f..08fbd3afb34 100644 --- a/homeassistant/components/ezviz/number.py +++ b/homeassistant/components/ezviz/number.py @@ -31,21 +31,14 @@ PARALLEL_UPDATES = 0 _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class EzvizNumberEntityDescriptionMixin: - """Mixin values for EZVIZ Number entities.""" +@dataclass(frozen=True, kw_only=True) +class EzvizNumberEntityDescription(NumberEntityDescription): + """Describe a EZVIZ Number.""" supported_ext: str supported_ext_value: list -@dataclass(frozen=True) -class EzvizNumberEntityDescription( - NumberEntityDescription, EzvizNumberEntityDescriptionMixin -): - """Describe a EZVIZ Number.""" - - NUMBER_TYPE = EzvizNumberEntityDescription( key="detection_sensibility", translation_key="detection_sensibility", diff --git a/homeassistant/components/ezviz/select.py b/homeassistant/components/ezviz/select.py index a63dad5ff7b..d6dc3dc8550 100644 --- a/homeassistant/components/ezviz/select.py +++ b/homeassistant/components/ezviz/select.py @@ -21,20 +21,13 @@ from .entity import EzvizEntity PARALLEL_UPDATES = 1 -@dataclass(frozen=True) -class EzvizSelectEntityDescriptionMixin: - """Mixin values for EZVIZ Select entities.""" +@dataclass(frozen=True, kw_only=True) +class EzvizSelectEntityDescription(SelectEntityDescription): + """Describe a EZVIZ Select entity.""" supported_switch: int -@dataclass(frozen=True) -class EzvizSelectEntityDescription( - SelectEntityDescription, EzvizSelectEntityDescriptionMixin -): - """Describe a EZVIZ Select entity.""" - - SELECT_TYPE = EzvizSelectEntityDescription( key="alarm_sound_mod", translation_key="alarm_sound_mode", diff --git a/homeassistant/components/ezviz/switch.py b/homeassistant/components/ezviz/switch.py index 7f568654870..65fb7b9f36b 100644 --- a/homeassistant/components/ezviz/switch.py +++ b/homeassistant/components/ezviz/switch.py @@ -23,20 +23,13 @@ from .coordinator import EzvizDataUpdateCoordinator from .entity import EzvizEntity -@dataclass(frozen=True) -class EzvizSwitchEntityDescriptionMixin: - """Mixin values for EZVIZ Switch entities.""" +@dataclass(frozen=True, kw_only=True) +class EzvizSwitchEntityDescription(SwitchEntityDescription): + """Describe a EZVIZ switch.""" supported_ext: str | None -@dataclass(frozen=True) -class EzvizSwitchEntityDescription( - SwitchEntityDescription, EzvizSwitchEntityDescriptionMixin -): - """Describe a EZVIZ switch.""" - - SWITCH_TYPES: dict[int, EzvizSwitchEntityDescription] = { 3: EzvizSwitchEntityDescription( key="3", From b0f0bc6c5c143977af9669a2f2f222f64cd22922 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:02:45 +0100 Subject: [PATCH 0612/1691] Remove entity description mixin in Environment Canada (#112763) --- .../components/environment_canada/sensor.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index 1c9a6fa1709..8a734f74dd6 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -34,19 +34,11 @@ from .const import ATTR_STATION, DOMAIN ATTR_TIME = "alert time" -@dataclass(frozen=True) -class ECSensorEntityDescriptionMixin: - """Mixin for required keys.""" - - value_fn: Callable[[Any], Any] - - -@dataclass(frozen=True) -class ECSensorEntityDescription( - SensorEntityDescription, ECSensorEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class ECSensorEntityDescription(SensorEntityDescription): """Describes Environment Canada sensor entity.""" + value_fn: Callable[[Any], Any] transform: Callable[[Any], Any] | None = None From 6f03c305d48436994a502265e50e123bdd360776 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:03:30 +0100 Subject: [PATCH 0613/1691] Remove entity description mixin in Google wifi (#112774) --- homeassistant/components/google_wifi/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/google_wifi/sensor.py b/homeassistant/components/google_wifi/sensor.py index 71e3e2e3652..776fb44a51b 100644 --- a/homeassistant/components/google_wifi/sensor.py +++ b/homeassistant/components/google_wifi/sensor.py @@ -43,21 +43,14 @@ ENDPOINT = "/api/v1/status" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1) -@dataclass(frozen=True) -class GoogleWifiRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class GoogleWifiSensorEntityDescription(SensorEntityDescription): + """Describes GoogleWifi sensor entity.""" primary_key: str sensor_key: str -@dataclass(frozen=True) -class GoogleWifiSensorEntityDescription( - SensorEntityDescription, GoogleWifiRequiredKeysMixin -): - """Describes GoogleWifi sensor entity.""" - - SENSOR_TYPES: tuple[GoogleWifiSensorEntityDescription, ...] = ( GoogleWifiSensorEntityDescription( key=ATTR_CURRENT_VERSION, From 24b7f03e3104bb550a200dea067058709ddb7b85 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:05:07 +0100 Subject: [PATCH 0614/1691] Remove entity description mixin in idasen desk (#112780) --- homeassistant/components/idasen_desk/button.py | 13 +++---------- homeassistant/components/idasen_desk/sensor.py | 14 +++----------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/idasen_desk/button.py b/homeassistant/components/idasen_desk/button.py index 7119d890a64..0de3125576d 100644 --- a/homeassistant/components/idasen_desk/button.py +++ b/homeassistant/components/idasen_desk/button.py @@ -18,22 +18,15 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class IdasenDeskButtonDescriptionMixin: - """Mixin to describe a IdasenDesk button entity.""" +@dataclass(frozen=True, kw_only=True) +class IdasenDeskButtonDescription(ButtonEntityDescription): + """Class to describe a IdasenDesk button entity.""" press_action: Callable[ [IdasenDeskCoordinator], Callable[[], Coroutine[Any, Any, Any]] ] -@dataclass(frozen=True) -class IdasenDeskButtonDescription( - ButtonEntityDescription, IdasenDeskButtonDescriptionMixin -): - """Class to describe a IdasenDesk button entity.""" - - BUTTONS: Final = [ IdasenDeskButtonDescription( key="connect", diff --git a/homeassistant/components/idasen_desk/sensor.py b/homeassistant/components/idasen_desk/sensor.py index fcbdbc6c25e..12a3b2ed4d9 100644 --- a/homeassistant/components/idasen_desk/sensor.py +++ b/homeassistant/components/idasen_desk/sensor.py @@ -23,21 +23,13 @@ from . import DeskData, IdasenDeskCoordinator from .const import DOMAIN -@dataclass(frozen=True) -class IdasenDeskSensorDescriptionMixin: - """Required values for IdasenDesk sensors.""" +@dataclass(frozen=True, kw_only=True) +class IdasenDeskSensorDescription(SensorEntityDescription): + """Class describing IdasenDesk sensor entities.""" value_fn: Callable[[IdasenDeskCoordinator], float | None] -@dataclass(frozen=True) -class IdasenDeskSensorDescription( - SensorEntityDescription, - IdasenDeskSensorDescriptionMixin, -): - """Class describing IdasenDesk sensor entities.""" - - SENSORS = ( IdasenDeskSensorDescription( key="height", From 6e1981c43c1b21989ea9ab855ffccdc2681833ce Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:06:27 +0100 Subject: [PATCH 0615/1691] Remove entity description mixin in Honeywell (#112776) --- homeassistant/components/honeywell/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py index 76e8a5b2588..030f8be779b 100644 --- a/homeassistant/components/honeywell/sensor.py +++ b/homeassistant/components/honeywell/sensor.py @@ -37,21 +37,14 @@ def _get_temperature_sensor_unit(device: Device) -> str: return UnitOfTemperature.FAHRENHEIT -@dataclass(frozen=True) -class HoneywellSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class HoneywellSensorEntityDescription(SensorEntityDescription): + """Describes a Honeywell sensor entity.""" value_fn: Callable[[Device], Any] unit_fn: Callable[[Device], Any] -@dataclass(frozen=True) -class HoneywellSensorEntityDescription( - SensorEntityDescription, HoneywellSensorEntityDescriptionMixin -): - """Describes a Honeywell sensor entity.""" - - SENSOR_TYPES: tuple[HoneywellSensorEntityDescription, ...] = ( HoneywellSensorEntityDescription( key=OUTDOOR_TEMPERATURE_STATUS_KEY, From 2b2f7d1193b374ef3b5a58a9c6d8b3dac26cdeea Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:07:10 +0100 Subject: [PATCH 0616/1691] Remove entity description mixin in Geocaching (#112770) --- homeassistant/components/geocaching/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/geocaching/sensor.py b/homeassistant/components/geocaching/sensor.py index 4065f199285..c082e5308a1 100644 --- a/homeassistant/components/geocaching/sensor.py +++ b/homeassistant/components/geocaching/sensor.py @@ -19,20 +19,13 @@ from .const import DOMAIN from .coordinator import GeocachingDataUpdateCoordinator -@dataclass(frozen=True) -class GeocachingRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class GeocachingSensorEntityDescription(SensorEntityDescription): + """Define Sensor entity description class.""" value_fn: Callable[[GeocachingStatus], str | int | None] -@dataclass(frozen=True) -class GeocachingSensorEntityDescription( - SensorEntityDescription, GeocachingRequiredKeysMixin -): - """Define Sensor entity description class.""" - - SENSORS: tuple[GeocachingSensorEntityDescription, ...] = ( GeocachingSensorEntityDescription( key="find_count", From 378806f1fa7eb5982494bb9577a47e56d8d31dad Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 16:38:44 +0100 Subject: [PATCH 0617/1691] Remove entity description mixin in AVM Fritz!Tools (#112767) --- homeassistant/components/fritz/button.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/fritz/button.py b/homeassistant/components/fritz/button.py index 7c7dcaba80f..d56350dd1d0 100644 --- a/homeassistant/components/fritz/button.py +++ b/homeassistant/components/fritz/button.py @@ -24,18 +24,13 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class FritzButtonDescriptionMixin: - """Mixin to describe a Button entity.""" +@dataclass(frozen=True, kw_only=True) +class FritzButtonDescription(ButtonEntityDescription): + """Class to describe a Button entity.""" press_action: Callable -@dataclass(frozen=True) -class FritzButtonDescription(ButtonEntityDescription, FritzButtonDescriptionMixin): - """Class to describe a Button entity.""" - - BUTTONS: Final = [ FritzButtonDescription( key="firmware_update", From 9ba142f4dd93a7e82911e8d9caa066f28a382379 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 06:07:15 -1000 Subject: [PATCH 0618/1691] Use eager_start for discovery listeners (#112803) Many platforms can be loaded without having to suspend or be scheduled on the event loop --- homeassistant/helpers/discovery.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 98419ae6bf2..1f8ba096a69 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -50,7 +50,9 @@ def async_listen( @core.callback def _async_discovery_event_listener(discovered: DiscoveryDict) -> None: """Listen for discovery events.""" - hass.async_run_hass_job(job, discovered["service"], discovered["discovered"]) + hass.async_run_hass_job( + job, discovered["service"], discovered["discovered"], eager_start=True + ) async_dispatcher_connect( hass, @@ -113,7 +115,9 @@ def async_listen_platform( """Listen for platform discovery events.""" if not (platform := discovered["platform"]): return - hass.async_run_hass_job(job, platform, discovered.get("discovered")) + hass.async_run_hass_job( + job, platform, discovered.get("discovered"), eager_start=True + ) return async_dispatcher_connect( hass, From 03e4a20cdf998c2be6c5e4f2b10ca00112a2c094 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 18:32:31 +0100 Subject: [PATCH 0619/1691] Rename get_deconz_session to get_deconz_api (#112826) Move and rename get_deconz_session to get_deconz_api --- homeassistant/components/deconz/__init__.py | 5 ++- homeassistant/components/deconz/gateway.py | 43 ++----------------- .../components/deconz/hub/__init__.py | 3 ++ homeassistant/components/deconz/hub/api.py | 39 +++++++++++++++++ tests/components/deconz/test_gateway.py | 14 +++--- tests/components/deconz/test_init.py | 4 +- 6 files changed, 57 insertions(+), 51 deletions(-) create mode 100644 homeassistant/components/deconz/hub/__init__.py create mode 100644 homeassistant/components/deconz/hub/api.py diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 4750c40fab2..aad03b6960d 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -19,7 +19,8 @@ from .config_flow import get_master_gateway from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect -from .gateway import DeconzGateway, get_deconz_session +from .gateway import DeconzGateway +from .hub import get_deconz_api from .services import async_setup_services, async_unload_services @@ -37,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b await async_update_master_gateway(hass, config_entry) try: - api = await get_deconz_session(hass, config_entry.data) + api = await get_deconz_api(hass, config_entry.data) except CannotConnect as err: raise ConfigEntryNotReady from err except AuthenticationRequired as err: diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index a9286cca112..adc75e987b9 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -2,25 +2,19 @@ from __future__ import annotations -import asyncio from collections.abc import Callable -from types import MappingProxyType -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, cast -from pydeconz import DeconzSession, errors +from pydeconz import DeconzSession from pydeconz.interfaces import sensors from pydeconz.interfaces.api_handlers import APIHandler, GroupedAPIHandler from pydeconz.interfaces.groups import GroupHandler from pydeconz.models.event import EventType from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import Event, HomeAssistant, callback -from homeassistant.helpers import ( - aiohttp_client, - device_registry as dr, - entity_registry as er, -) +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -34,10 +28,8 @@ from .const import ( DEFAULT_ALLOW_NEW_DEVICES, DOMAIN as DECONZ_DOMAIN, HASSIO_CONFIGURATION_URL, - LOGGER, PLATFORMS, ) -from .errors import AuthenticationRequired, CannotConnect if TYPE_CHECKING: from .deconz_event import ( @@ -339,30 +331,3 @@ def get_gateway_from_config_entry( ) -> DeconzGateway: """Return gateway with a matching config entry ID.""" return cast(DeconzGateway, hass.data[DECONZ_DOMAIN][config_entry.entry_id]) - - -async def get_deconz_session( - hass: HomeAssistant, - config: MappingProxyType[str, Any], -) -> DeconzSession: - """Create a gateway object and verify configuration.""" - session = aiohttp_client.async_get_clientsession(hass) - - deconz_session = DeconzSession( - session, - config[CONF_HOST], - config[CONF_PORT], - config[CONF_API_KEY], - ) - try: - async with asyncio.timeout(10): - await deconz_session.refresh_state() - return deconz_session - - except errors.Unauthorized as err: - LOGGER.warning("Invalid key for deCONZ at %s", config[CONF_HOST]) - raise AuthenticationRequired from err - - except (TimeoutError, errors.RequestError, errors.ResponseError) as err: - LOGGER.error("Error connecting to deCONZ gateway at %s", config[CONF_HOST]) - raise CannotConnect from err diff --git a/homeassistant/components/deconz/hub/__init__.py b/homeassistant/components/deconz/hub/__init__.py new file mode 100644 index 00000000000..962ecf47787 --- /dev/null +++ b/homeassistant/components/deconz/hub/__init__.py @@ -0,0 +1,3 @@ +"""Internal functionality not part of HA infrastructure.""" + +from .api import get_deconz_api # noqa: F401 diff --git a/homeassistant/components/deconz/hub/api.py b/homeassistant/components/deconz/hub/api.py new file mode 100644 index 00000000000..33c49e189d5 --- /dev/null +++ b/homeassistant/components/deconz/hub/api.py @@ -0,0 +1,39 @@ +"""deCONZ API representation.""" + +from __future__ import annotations + +import asyncio +from types import MappingProxyType +from typing import Any + +from pydeconz import DeconzSession, errors + +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.helpers import aiohttp_client + +from ..const import LOGGER +from ..errors import AuthenticationRequired, CannotConnect + + +async def get_deconz_api( + hass: HomeAssistant, config: MappingProxyType[str, Any] +) -> DeconzSession: + """Create a gateway object and verify configuration.""" + session = aiohttp_client.async_get_clientsession(hass) + + api = DeconzSession( + session, config[CONF_HOST], config[CONF_PORT], config[CONF_API_KEY] + ) + try: + async with asyncio.timeout(10): + await api.refresh_state() + return api + + except errors.Unauthorized as err: + LOGGER.warning("Invalid key for deCONZ at %s", config[CONF_HOST]) + raise AuthenticationRequired from err + + except (TimeoutError, errors.RequestError, errors.ResponseError) as err: + LOGGER.error("Error connecting to deCONZ gateway at %s", config[CONF_HOST]) + raise CannotConnect from err diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 04850321a5d..a9974b61d08 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -18,10 +18,8 @@ from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.deconz.config_flow import DECONZ_MANUFACTURERURL from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.errors import AuthenticationRequired, CannotConnect -from homeassistant.components.deconz.gateway import ( - get_deconz_session, - get_gateway_from_config_entry, -) +from homeassistant.components.deconz.gateway import get_gateway_from_config_entry +from homeassistant.components.deconz.hub import get_deconz_api from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN @@ -288,10 +286,10 @@ async def test_reset_after_successful_setup( assert result is True -async def test_get_deconz_session(hass: HomeAssistant) -> None: +async def test_get_deconz_api(hass: HomeAssistant) -> None: """Successful call.""" with patch("pydeconz.DeconzSession.refresh_state", return_value=True): - assert await get_deconz_session(hass, ENTRY_CONFIG) + assert await get_deconz_api(hass, ENTRY_CONFIG) @pytest.mark.parametrize( @@ -303,7 +301,7 @@ async def test_get_deconz_session(hass: HomeAssistant) -> None: (pydeconz.Unauthorized, AuthenticationRequired), ], ) -async def test_get_deconz_session_fails( +async def test_get_deconz_api_fails( hass: HomeAssistant, side_effect, raised_exception ) -> None: """Failed call.""" @@ -311,4 +309,4 @@ async def test_get_deconz_session_fails( "pydeconz.DeconzSession.refresh_state", side_effect=side_effect, ), pytest.raises(raised_exception): - assert await get_deconz_session(hass, ENTRY_CONFIG) + assert await get_deconz_api(hass, ENTRY_CONFIG) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 278a137bb89..972f82b1db3 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -59,7 +59,7 @@ async def test_setup_entry_successful( async def test_setup_entry_fails_config_entry_not_ready(hass: HomeAssistant) -> None: """Failed authentication trigger a reauthentication flow.""" with patch( - "homeassistant.components.deconz.get_deconz_session", + "homeassistant.components.deconz.get_deconz_api", side_effect=CannotConnect, ): await setup_deconz_integration(hass) @@ -70,7 +70,7 @@ async def test_setup_entry_fails_config_entry_not_ready(hass: HomeAssistant) -> async def test_setup_entry_fails_trigger_reauth_flow(hass: HomeAssistant) -> None: """Failed authentication trigger a reauthentication flow.""" with patch( - "homeassistant.components.deconz.get_deconz_session", + "homeassistant.components.deconz.get_deconz_api", side_effect=AuthenticationRequired, ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: await setup_deconz_integration(hass) From 020b656f51bd436e788cca5aaf929095fc04e559 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 9 Mar 2024 19:00:25 +0100 Subject: [PATCH 0620/1691] Split out Axis config data (#112825) Split out config data --- .../components/axis/binary_sensor.py | 4 +- homeassistant/components/axis/camera.py | 22 ++--- homeassistant/components/axis/config_flow.py | 4 +- homeassistant/components/axis/hub/config.py | 66 ++++++++++++++ homeassistant/components/axis/hub/hub.py | 90 +++---------------- tests/components/axis/test_hub.py | 6 +- 6 files changed, 94 insertions(+), 98 deletions(-) create mode 100644 homeassistant/components/axis/hub/config.py diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index cba98b81e30..4e724c804bd 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -94,13 +94,13 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): self.cancel_scheduled_update() self.cancel_scheduled_update = None - if self.is_on or self.hub.option_trigger_time == 0: + if self.is_on or self.hub.config.trigger_time == 0: self.async_write_ha_state() return self.cancel_scheduled_update = async_call_later( self.hass, - timedelta(seconds=self.hub.option_trigger_time), + timedelta(seconds=self.hub.config.trigger_time), scheduled_update, ) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 79543b738da..769be676a78 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -51,8 +51,8 @@ class AxisCamera(AxisEntity, MjpegCamera): MjpegCamera.__init__( self, - username=hub.username, - password=hub.password, + username=hub.config.username, + password=hub.config.password, mjpeg_url=self.mjpeg_source, still_image_url=self.image_source, authentication=HTTP_DIGEST_AUTHENTICATION, @@ -76,27 +76,27 @@ class AxisCamera(AxisEntity, MjpegCamera): """ image_options = self.generate_options(skip_stream_profile=True) self._still_image_url = ( - f"http://{self.hub.host}:{self.hub.port}/axis-cgi" + f"http://{self.hub.config.host}:{self.hub.config.port}/axis-cgi" f"/jpg/image.cgi{image_options}" ) mjpeg_options = self.generate_options() self._mjpeg_url = ( - f"http://{self.hub.host}:{self.hub.port}/axis-cgi" + f"http://{self.hub.config.host}:{self.hub.config.port}/axis-cgi" f"/mjpg/video.cgi{mjpeg_options}" ) stream_options = self.generate_options(add_video_codec_h264=True) self._stream_source = ( - f"rtsp://{self.hub.username}:{self.hub.password}" - f"@{self.hub.host}/axis-media/media.amp{stream_options}" + f"rtsp://{self.hub.config.username}:{self.hub.config.password}" + f"@{self.hub.config.host}/axis-media/media.amp{stream_options}" ) self.hub.additional_diagnostics["camera_sources"] = { "Image": self._still_image_url, "MJPEG": self._mjpeg_url, "Stream": ( - f"rtsp://user:pass@{self.hub.host}/axis-media" + f"rtsp://user:pass@{self.hub.config.host}/axis-media" f"/media.amp{stream_options}" ), } @@ -126,12 +126,12 @@ class AxisCamera(AxisEntity, MjpegCamera): if ( not skip_stream_profile - and self.hub.option_stream_profile != DEFAULT_STREAM_PROFILE + and self.hub.config.stream_profile != DEFAULT_STREAM_PROFILE ): - options_dict["streamprofile"] = self.hub.option_stream_profile + options_dict["streamprofile"] = self.hub.config.stream_profile - if self.hub.option_video_source != DEFAULT_VIDEO_SOURCE: - options_dict["camera"] = self.hub.option_video_source + if self.hub.config.video_source != DEFAULT_VIDEO_SOURCE: + options_dict["camera"] = self.hub.config.video_source if not options_dict: return "" diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 4d6d4e12b0a..66e338f0fdf 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -272,7 +272,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): schema[ vol.Optional( - CONF_STREAM_PROFILE, default=self.hub.option_stream_profile + CONF_STREAM_PROFILE, default=self.hub.config.stream_profile ) ] = vol.In(stream_profiles) @@ -291,7 +291,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): video_sources[int(idx) + 1] = video_source.name schema[ - vol.Optional(CONF_VIDEO_SOURCE, default=self.hub.option_video_source) + vol.Optional(CONF_VIDEO_SOURCE, default=self.hub.config.video_source) ] = vol.In(video_sources) return self.async_show_form( diff --git a/homeassistant/components/axis/hub/config.py b/homeassistant/components/axis/hub/config.py new file mode 100644 index 00000000000..e6d8378b45c --- /dev/null +++ b/homeassistant/components/axis/hub/config.py @@ -0,0 +1,66 @@ +"""Axis network device abstraction.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Self + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_MODEL, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_TRIGGER_TIME, + CONF_USERNAME, +) + +from ..const import ( + CONF_STREAM_PROFILE, + CONF_VIDEO_SOURCE, + DEFAULT_STREAM_PROFILE, + DEFAULT_TRIGGER_TIME, + DEFAULT_VIDEO_SOURCE, +) + + +@dataclass +class AxisConfig: + """Represent a Axis config entry.""" + + entry: ConfigEntry + + host: str + port: int + username: str + password: str + model: str + name: str + + # Options + + stream_profile: str + """Option defining what stream profile camera platform should use.""" + trigger_time: int + """Option defining minimum number of seconds to keep trigger high.""" + video_source: str + """Option defining what video source camera platform should use.""" + + @classmethod + def from_config_entry(cls, config_entry: ConfigEntry) -> Self: + """Create object from config entry.""" + config = config_entry.data + options = config_entry.options + return cls( + entry=config_entry, + host=config[CONF_HOST], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + port=config[CONF_PORT], + model=config[CONF_MODEL], + name=config[CONF_NAME], + stream_profile=options.get(CONF_STREAM_PROFILE, DEFAULT_STREAM_PROFILE), + trigger_time=options.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME), + video_source=options.get(CONF_VIDEO_SOURCE, DEFAULT_VIDEO_SOURCE), + ) diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index 5a0f45a1682..b352a514021 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -14,30 +14,14 @@ from homeassistant.components import mqtt from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_MODEL, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_TRIGGER_TIME, - CONF_USERNAME, -) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_when_setup -from ..const import ( - ATTR_MANUFACTURER, - CONF_STREAM_PROFILE, - CONF_VIDEO_SOURCE, - DEFAULT_STREAM_PROFILE, - DEFAULT_TRIGGER_TIME, - DEFAULT_VIDEO_SOURCE, - DOMAIN as AXIS_DOMAIN, -) +from ..const import ATTR_MANUFACTURER, DOMAIN as AXIS_DOMAIN +from .config import AxisConfig class AxisHub: @@ -48,7 +32,7 @@ class AxisHub: ) -> None: """Initialize the device.""" self.hass = hass - self.config_entry = config_entry + self.config = AxisConfig.from_config_entry(config_entry) self.api = api self.available = True @@ -64,65 +48,10 @@ class AxisHub: hub: AxisHub = hass.data[AXIS_DOMAIN][config_entry.entry_id] return hub - @property - def host(self) -> str: - """Return the host address of this device.""" - host: str = self.config_entry.data[CONF_HOST] - return host - - @property - def port(self) -> int: - """Return the HTTP port of this device.""" - port: int = self.config_entry.data[CONF_PORT] - return port - - @property - def username(self) -> str: - """Return the username of this device.""" - username: str = self.config_entry.data[CONF_USERNAME] - return username - - @property - def password(self) -> str: - """Return the password of this device.""" - password: str = self.config_entry.data[CONF_PASSWORD] - return password - - @property - def model(self) -> str: - """Return the model of this device.""" - model: str = self.config_entry.data[CONF_MODEL] - return model - - @property - def name(self) -> str: - """Return the name of this device.""" - name: str = self.config_entry.data[CONF_NAME] - return name - @property def unique_id(self) -> str | None: """Return the unique ID (serial number) of this device.""" - return self.config_entry.unique_id - - # Options - - @property - def option_stream_profile(self) -> str: - """Config entry option defining what stream profile camera platform should use.""" - return self.config_entry.options.get( - CONF_STREAM_PROFILE, DEFAULT_STREAM_PROFILE - ) - - @property - def option_trigger_time(self) -> int: - """Config entry option defining minimum number of seconds to keep trigger high.""" - return self.config_entry.options.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME) - - @property - def option_video_source(self) -> str: - """Config entry option defining what video source camera platform should use.""" - return self.config_entry.options.get(CONF_VIDEO_SOURCE, DEFAULT_VIDEO_SOURCE) + return self.config.entry.unique_id # Signals @@ -161,20 +90,21 @@ class AxisHub: cannot be used with weak references. """ hub = AxisHub.get_hub(hass, config_entry) - hub.api.config.host = hub.host + hub.config = AxisConfig.from_config_entry(config_entry) + hub.api.config.host = hub.config.host async_dispatcher_send(hass, hub.signal_new_address) async def async_update_device_registry(self) -> None: """Update device registry.""" device_registry = dr.async_get(self.hass) device_registry.async_get_or_create( - config_entry_id=self.config_entry.entry_id, + config_entry_id=self.config.entry.entry_id, configuration_url=self.api.config.url, connections={(CONNECTION_NETWORK_MAC, self.unique_id)}, # type: ignore[arg-type] identifiers={(AXIS_DOMAIN, self.unique_id)}, # type: ignore[arg-type] manufacturer=ATTR_MANUFACTURER, - model=f"{self.model} {self.product_type}", - name=self.name, + model=f"{self.config.model} {self.product_type}", + name=self.config.name, sw_version=self.fw_version, ) @@ -186,7 +116,7 @@ class AxisHub: # This means the user has too low privileges return if status.status.state == ClientState.ACTIVE: - self.config_entry.async_on_unload( + self.config.entry.async_on_unload( await mqtt.async_subscribe( hass, f"{self.api.vapix.serial_number}/#", self.mqtt_message ) diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index 1d36e0bbda0..f3d0dd3a225 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -63,9 +63,9 @@ async def test_device_setup( assert forward_entry_setup.mock_calls[2][1][1] == "light" assert forward_entry_setup.mock_calls[3][1][1] == "switch" - assert hub.host == config[CONF_HOST] - assert hub.model == config[CONF_MODEL] - assert hub.name == config[CONF_NAME] + assert hub.config.host == config[CONF_HOST] + assert hub.config.model == config[CONF_MODEL] + assert hub.config.name == config[CONF_NAME] assert hub.unique_id == FORMATTED_MAC device_entry = device_registry.async_get_device( From 033dd356553b3dced8b0468dd9e74fbd757891aa Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 20:48:21 +0100 Subject: [PATCH 0621/1691] Add icon translations to Yeelight (#112363) * Add icon translations to Yeelight * Add icon translations to Yeelight --- homeassistant/components/yeelight/icons.json | 19 +++++++++++++++++++ homeassistant/components/yeelight/light.py | 1 - tests/components/yeelight/test_light.py | 1 - 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/yeelight/icons.json diff --git a/homeassistant/components/yeelight/icons.json b/homeassistant/components/yeelight/icons.json new file mode 100644 index 00000000000..bf0d0c497f0 --- /dev/null +++ b/homeassistant/components/yeelight/icons.json @@ -0,0 +1,19 @@ +{ + "entity": { + "light": { + "nightlight": { + "default": "mdi:weather-night" + } + } + }, + "services": { + "set_mode": "mdi:cog", + "set_color_scene": "mdi:palette", + "set_hsv_scene": "mdi:palette", + "set_color_temp_scene": "mdi:palette", + "set_color_flow_scene": "mdi:palette", + "set_auto_delay_off_scene": "mdi:timer", + "start_flow": "mdi:play", + "set_music_mode": "mdi:music" + } +} diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index a9a35cb71b7..ede652dd037 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -1002,7 +1002,6 @@ class YeelightNightLightMode(YeelightBaseLight): """Representation of a Yeelight when in nightlight mode.""" _attr_color_mode = ColorMode.BRIGHTNESS - _attr_icon = "mdi:weather-night" _attr_supported_color_modes = {ColorMode.BRIGHTNESS} _attr_translation_key = "nightlight" diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index d61532e3f21..d3d2ba52124 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -857,7 +857,6 @@ async def test_device_types( state = hass.states.get(f"{entity_id}_nightlight") assert state.state == "on" nightlight_entity_properties["friendly_name"] = f"{name} Nightlight" - nightlight_entity_properties["icon"] = "mdi:weather-night" nightlight_entity_properties["flowing"] = False nightlight_entity_properties["night_light"] = True nightlight_entity_properties["music_mode"] = False From 9974af39ac386c8a88f6692fd7d7b62b3d123e4f Mon Sep 17 00:00:00 2001 From: Lex Li <425130+lextm@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:51:08 -0500 Subject: [PATCH 0622/1691] Upgrade `pysnmp-lextudio` to version `6.0.9` (#112795) --- homeassistant/components/snmp/manifest.json | 2 +- homeassistant/components/snmp/switch.py | 2 +- pyproject.toml | 3 --- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index 8d046b609b8..cd6c1dd9152 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/snmp", "iot_class": "local_polling", "loggers": ["pyasn1", "pysmi", "pysnmp"], - "requirements": ["pysnmp-lextudio==5.0.34"] + "requirements": ["pysnmp-lextudio==6.0.9"] } diff --git a/homeassistant/components/snmp/switch.py b/homeassistant/components/snmp/switch.py index 11bb4768e38..a447cdc8e9c 100644 --- a/homeassistant/components/snmp/switch.py +++ b/homeassistant/components/snmp/switch.py @@ -238,7 +238,7 @@ class SnmpSwitch(SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the switch.""" - # If vartype set, use it - http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.smi.rfc1902.ObjectType + # If vartype set, use it - https://www.pysnmp.com/pysnmp/docs/api-reference.html#pysnmp.smi.rfc1902.ObjectType await self._execute_command(self._command_payload_on) async def async_turn_off(self, **kwargs: Any) -> None: diff --git a/pyproject.toml b/pyproject.toml index e27562d1a8d..5d2573ab0ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -530,9 +530,6 @@ filterwarnings = [ "ignore:loop argument is deprecated:DeprecationWarning:emulated_roku", # https://github.com/thecynic/pylutron - v0.2.10 "ignore:setDaemon\\(\\) is deprecated, set the daemon attribute instead:DeprecationWarning:pylutron", - # Fixed for Python 3.12 - # https://github.com/lextudio/pysnmp/issues/10 - "ignore:The asyncore module is deprecated and will be removed in Python 3.12:DeprecationWarning:pysnmp.carrier.asyncore.base", # Wrong stacklevel # https://bugs.launchpad.net/beautifulsoup/+bug/2034451 "ignore:It looks like you're parsing an XML document using an HTML parser:UserWarning:bs4.builder", diff --git a/requirements_all.txt b/requirements_all.txt index cde8555cb9f..8bfeb90c806 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2149,7 +2149,7 @@ pysmartthings==0.7.8 pysml==0.0.12 # homeassistant.components.snmp -pysnmp-lextudio==5.0.34 +pysnmp-lextudio==6.0.9 # homeassistant.components.snooz pysnooz==0.8.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d8afd6223f9..a584d53ec81 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1670,7 +1670,7 @@ pysmartthings==0.7.8 pysml==0.0.12 # homeassistant.components.snmp -pysnmp-lextudio==5.0.34 +pysnmp-lextudio==6.0.9 # homeassistant.components.snooz pysnooz==0.8.6 From 2a6de1c33500a21a37fb11673082011a4e6d2895 Mon Sep 17 00:00:00 2001 From: Jeffrey Stone Date: Sat, 9 Mar 2024 15:20:11 -0500 Subject: [PATCH 0623/1691] Bump mastodon.py version to 1.8.1 (#112728) bump mastodon.py version to 1.8.1 --- homeassistant/components/mastodon/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mastodon/manifest.json b/homeassistant/components/mastodon/manifest.json index 2505dcc5dd0..673a60166c0 100644 --- a/homeassistant/components/mastodon/manifest.json +++ b/homeassistant/components/mastodon/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/mastodon", "iot_class": "cloud_push", "loggers": ["mastodon"], - "requirements": ["Mastodon.py==1.5.1"] + "requirements": ["Mastodon.py==1.8.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8bfeb90c806..bb22d21f40d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -31,7 +31,7 @@ HAP-python==4.9.1 HATasmota==0.8.0 # homeassistant.components.mastodon -Mastodon.py==1.5.1 +Mastodon.py==1.8.1 # homeassistant.components.doods # homeassistant.components.generic From d0d1af8991010d5b5e6a33efb1516bb970d82d5c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 10:26:48 -1000 Subject: [PATCH 0624/1691] Improve performance of image streams (#112810) --- homeassistant/components/image/__init__.py | 7 ++-- tests/components/image/test_init.py | 43 ++++++++++++++-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index 02ebee49701..d3b627f8292 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -336,13 +336,12 @@ async def async_get_still_stream( # given the low frequency of image updates, it is acceptable. frame.extend(frame) await response.write(frame) - # Drain to ensure that the latest frame is available to the client - await response.drain() return True event = asyncio.Event() - async def image_state_update(_event: Event[EventStateChangedData]) -> None: + @callback + def _async_image_state_update(_event: Event[EventStateChangedData]) -> None: """Write image to stream.""" event.set() @@ -350,7 +349,7 @@ async def async_get_still_stream( remove = async_track_state_change_event( hass, image_entity.entity_id, - image_state_update, + _async_image_state_update, ) try: while True: diff --git a/tests/components/image/test_init.py b/tests/components/image/test_init.py index e407b142c52..75816e1350f 100644 --- a/tests/components/image/test_init.py +++ b/tests/components/image/test_init.py @@ -306,22 +306,35 @@ async def test_image_stream( client = await hass_client() - with patch.object(mock_image, "async_image", return_value=b""): - resp = await client.get("/api/image_proxy_stream/image.test") - assert not resp.closed - assert resp.status == HTTPStatus.OK + close_future = hass.loop.create_future() + original_get_still_stream = image.async_get_still_stream - mock_image.image_last_updated = datetime.datetime.now() - mock_image.async_write_ha_state() - # Two blocks to ensure the frame is written - await hass.async_block_till_done() - await hass.async_block_till_done() + async def _wrap_async_get_still_stream(*args, **kwargs): + result = await original_get_still_stream(*args, **kwargs) + hass.loop.call_soon(close_future.set_result, None) + return result - with patch.object(mock_image, "async_image", return_value=None): - mock_image.image_last_updated = datetime.datetime.now() - mock_image.async_write_ha_state() - # Two blocks to ensure the frame is written - await hass.async_block_till_done() - await hass.async_block_till_done() + with patch( + "homeassistant.components.image.async_get_still_stream", + _wrap_async_get_still_stream, + ): + with patch.object(mock_image, "async_image", return_value=b""): + resp = await client.get("/api/image_proxy_stream/image.test") + assert not resp.closed + assert resp.status == HTTPStatus.OK + mock_image.image_last_updated = datetime.datetime.now() + mock_image.async_write_ha_state() + # Two blocks to ensure the frame is written + await hass.async_block_till_done() + await hass.async_block_till_done() + + with patch.object(mock_image, "async_image", return_value=None): + mock_image.image_last_updated = datetime.datetime.now() + mock_image.async_write_ha_state() + # Two blocks to ensure the frame is written + await hass.async_block_till_done() + await hass.async_block_till_done() + + await close_future assert resp.closed From 3b0ea52167ebf669d735979e559319d42c51d287 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 9 Mar 2024 21:55:00 +0100 Subject: [PATCH 0625/1691] Allow Just-in-Time platform setup for mqtt (#112720) * Allow Just-in-Time platform setup for mqtt * Only forward the setup of new platforms * Fix new platforms being setup at reload + test * Revert not related changes * Remove unused partial * Address comments, only import plaforms if needed * Apply suggestions from code review * Add multipl platform discovery test * Improve test * Use a lock per platform --- homeassistant/components/mqtt/__init__.py | 51 +++------ homeassistant/components/mqtt/discovery.py | 36 +++++- homeassistant/components/mqtt/models.py | 3 +- homeassistant/components/mqtt/util.py | 47 +++++++- tests/components/mqtt/test_init.py | 127 ++++++++++++++++++++- 5 files changed, 226 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 14f92e29cdf..397e530cb4b 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -41,7 +41,6 @@ from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration -from homeassistant.util.async_ import create_eager_task # Loading the config flow file will register the flow from . import debug_info, discovery @@ -100,9 +99,11 @@ from .models import ( # noqa: F401 ) from .util import ( # noqa: F401 async_create_certificate_temp_files, + async_forward_entry_setup_and_setup_discovery, async_wait_for_mqtt_client, get_mqtt_data, mqtt_config_entry_enabled, + platforms_from_config, valid_publish_topic, valid_qos_schema, valid_subscribe_topic, @@ -411,13 +412,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: translation_placeholders=ex.translation_placeholders, ) from ex + new_config: list[ConfigType] = config_yaml.get(DOMAIN, []) + platforms_used = platforms_from_config(new_config) + new_platforms = platforms_used - mqtt_data.platforms_loaded + await async_forward_entry_setup_and_setup_discovery( + hass, entry, new_platforms + ) # Check the schema before continuing reload await async_check_config_schema(hass, config_yaml) # Remove repair issues _async_remove_mqtt_issues(hass, mqtt_data) - mqtt_data.config = config_yaml.get(DOMAIN, {}) + mqtt_data.config = new_config # Reload the modern yaml platforms mqtt_platforms = async_get_platforms(hass, DOMAIN) @@ -439,36 +446,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, _reload_config) - async def async_forward_entry_setup_and_setup_discovery( - config_entry: ConfigEntry, - conf: ConfigType, - ) -> None: - """Forward the config entry setup to the platforms and set up discovery.""" - # Local import to avoid circular dependencies - # pylint: disable-next=import-outside-toplevel - from . import device_automation, tag - - # Forward the entry setup to the MQTT platforms - await asyncio.gather( - *( - create_eager_task( - device_automation.async_setup_entry(hass, config_entry) - ), - create_eager_task(tag.async_setup_entry(hass, config_entry)), - create_eager_task( - hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - ), - ) + platforms_used = platforms_from_config(mqtt_data.config) + await async_forward_entry_setup_and_setup_discovery(hass, entry, platforms_used) + # Setup reload service after all platforms have loaded + await async_setup_reload_service() + # Setup discovery + if conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY): + await discovery.async_start( + hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX), entry ) - # Setup discovery - if conf.get(CONF_DISCOVERY, DEFAULT_DISCOVERY): - await discovery.async_start( - hass, conf.get(CONF_DISCOVERY_PREFIX, DEFAULT_PREFIX), entry - ) - # Setup reload service after all platforms have loaded - await async_setup_reload_service() - - await async_forward_entry_setup_and_setup_discovery(entry, conf) return True @@ -605,9 +591,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await asyncio.gather( *( hass.config_entries.async_forward_entry_unload(entry, component) - for component in PLATFORMS + for component in mqtt_data.platforms_loaded ) ) + mqtt_data.platforms_loaded = set() await asyncio.sleep(0) # Unsubscribe reload dispatchers while reload_dispatchers := mqtt_data.reload_dispatchers: diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 5fa1b6297d7..180f3524dee 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -40,7 +40,7 @@ from .const import ( DOMAIN, ) from .models import MqttOriginInfo, ReceiveMessage -from .util import get_mqtt_data +from .util import async_forward_entry_setup_and_setup_discovery, get_mqtt_data _LOGGER = logging.getLogger(__name__) @@ -81,6 +81,7 @@ SUPPORTED_COMPONENTS = { MQTT_DISCOVERY_UPDATED = "mqtt_discovery_updated_{}" MQTT_DISCOVERY_NEW = "mqtt_discovery_new_{}_{}" +MQTT_DISCOVERY_NEW_COMPONENT = "mqtt_discovery_new_component" MQTT_DISCOVERY_DONE = "mqtt_discovery_done_{}" TOPIC_BASE = "~" @@ -141,7 +142,33 @@ async def async_start( # noqa: C901 ) -> None: """Start MQTT Discovery.""" mqtt_data = get_mqtt_data(hass) - mqtt_integrations = {} + platform_setup_lock: dict[str, asyncio.Lock] = {} + + async def _async_jit_component_setup( + discovery_payload: MQTTDiscoveryPayload, + ) -> None: + """Perform just in time components set up.""" + discovery_hash = discovery_payload.discovery_data[ATTR_DISCOVERY_HASH] + component, discovery_id = discovery_hash + platform_setup_lock.setdefault(component, asyncio.Lock()) + async with platform_setup_lock[component]: + if component not in mqtt_data.platforms_loaded: + await async_forward_entry_setup_and_setup_discovery( + hass, config_entry, {component} + ) + # Add component + message = f"Found new component: {component} {discovery_id}" + async_log_discovery_origin_info(message, discovery_payload) + mqtt_data.discovery_already_discovered.add(discovery_hash) + async_dispatcher_send( + hass, MQTT_DISCOVERY_NEW.format(component, "mqtt"), discovery_payload + ) + + mqtt_data.reload_dispatchers.append( + async_dispatcher_connect( + hass, MQTT_DISCOVERY_NEW_COMPONENT, _async_jit_component_setup + ) + ) @callback def async_discovery_message_received(msg: ReceiveMessage) -> None: # noqa: C901 @@ -304,7 +331,10 @@ async def async_start( # noqa: C901 "pending": deque([]), } - if already_discovered: + if component not in mqtt_data.platforms_loaded and payload: + # Load component first + async_dispatcher_send(hass, MQTT_DISCOVERY_NEW_COMPONENT, payload) + elif already_discovered: # Dispatch update message = f"Component has already been discovered: {component} {discovery_id}, sending update" async_log_discovery_origin_info(message, payload) diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index bfbf9e011eb..9c961a3b543 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Any, TypedDict import voluptuous as vol -from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME +from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME, Platform from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.exceptions import ServiceValidationError, TemplateError from homeassistant.helpers import template @@ -409,6 +409,7 @@ class MqttData: discovery_unsubscribe: list[CALLBACK_TYPE] = field(default_factory=list) integration_unsubscribe: dict[str, CALLBACK_TYPE] = field(default_factory=dict) last_discovery: float = 0.0 + platforms_loaded: set[Platform | str] = field(default_factory=set) reload_dispatchers: list[CALLBACK_TYPE] = field(default_factory=list) reload_handlers: dict[str, CALLBACK_TYPE] = field(default_factory=dict) reload_schema: dict[str, vol.Schema] = field(default_factory=dict) diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index fb47bbfc667..53462f87321 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -10,10 +10,12 @@ from typing import Any import voluptuous as vol -from homeassistant.config_entries import ConfigEntryState +from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.typing import ConfigType +from homeassistant.util.async_ import create_eager_task from .const import ( ATTR_PAYLOAD, @@ -39,6 +41,49 @@ TEMP_DIR_NAME = f"home-assistant-{DOMAIN}" _VALID_QOS_SCHEMA = vol.All(vol.Coerce(int), vol.In([0, 1, 2])) +def platforms_from_config(config: list[ConfigType]) -> set[Platform | str]: + """Return the platforms to be set up.""" + return {key for platform in config for key in platform} + + +async def async_forward_entry_setup_and_setup_discovery( + hass: HomeAssistant, config_entry: ConfigEntry, platforms: set[Platform | str] +) -> None: + """Forward the config entry setup to the platforms and set up discovery.""" + mqtt_data = get_mqtt_data(hass) + platforms_loaded = mqtt_data.platforms_loaded + new_platforms: set[Platform | str] = platforms - platforms_loaded + platforms_loaded.update(new_platforms) + tasks: list[asyncio.Task] = [] + if "device_automation" in new_platforms: + # Local import to avoid circular dependencies + # pylint: disable-next=import-outside-toplevel + from . import device_automation + + new_platforms.remove("device_automation") + tasks.append( + create_eager_task(device_automation.async_setup_entry(hass, config_entry)) + ) + if "tag" in new_platforms: + # Local import to avoid circular dependencies + # pylint: disable-next=import-outside-toplevel + from . import tag + + new_platforms.remove("tag") + tasks.append(create_eager_task(tag.async_setup_entry(hass, config_entry))) + if new_platforms: + tasks.append( + create_eager_task( + hass.config_entries.async_forward_entry_setups( + config_entry, new_platforms + ) + ) + ) + if not tasks: + return + await asyncio.gather(*tasks) + + def mqtt_config_entry_enabled(hass: HomeAssistant) -> bool | None: """Return true when the MQTT config entry is enabled.""" if not bool(hass.config_entries.async_entries(DOMAIN)): diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index b10031c75f8..60a909395f1 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -2,6 +2,7 @@ import asyncio from collections.abc import Generator +from copy import deepcopy from datetime import datetime, timedelta from functools import partial import json @@ -3546,7 +3547,6 @@ async def test_subscribe_connection_status( assert mqtt_connected_calls_async[1] is False -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) async def test_unload_config_entry( hass: HomeAssistant, mqtt_mock: MqttMockHAClient, @@ -3563,6 +3563,7 @@ async def test_unload_config_entry( # Publish just before unloading to test await cleanup mqtt_client_mock.reset_mock() mqtt.publish(hass, "just_in_time", "published", qos=0, retain=False) + await hass.async_block_till_done() assert await hass.config_entries.async_unload(mqtt_config_entry.entry_id) new_mqtt_config_entry = mqtt_config_entry @@ -4046,3 +4047,127 @@ async def test_reload_with_empty_config( await hass.async_block_till_done() assert hass.states.get("sensor.test") is None + + +@pytest.mark.parametrize( + "hass_config", + [ + { + "mqtt": [ + { + "sensor": { + "name": "test", + "state_topic": "test-topic", + } + }, + ] + } + ], +) +async def test_reload_with_new_platform_config( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, +) -> None: + """Test reloading yaml with new platform config.""" + await mqtt_mock_entry() + assert hass.states.get("sensor.test") is not None + assert hass.states.get("binary_sensor.test") is None + + new_config = { + "mqtt": [ + { + "sensor": { + "name": "test", + "state_topic": "test-topic1", + }, + "binary_sensor": { + "name": "test", + "state_topic": "test-topic2", + }, + }, + ] + } + + # Reload with an new platform config and assert again + with patch("homeassistant.config.load_yaml_config_file", return_value=new_config): + await hass.services.async_call( + "mqtt", + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.test") is not None + assert hass.states.get("binary_sensor.test") is not None + + +async def test_multi_platform_discovery( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + mqtt_mock_entry: MqttMockHAClientGenerator, +) -> None: + """Test setting up multiple platforms simultaneous.""" + await mqtt_mock_entry() + entity_configs = { + "alarm_control_panel": { + "name": "test", + "state_topic": "alarm/state", + "command_topic": "alarm/command", + }, + "button": {"name": "test", "command_topic": "test-topic"}, + "camera": {"name": "test", "topic": "test_topic"}, + "cover": {"name": "test", "state_topic": "test-topic"}, + "device_tracker": { + "name": "test", + "state_topic": "test-topic", + }, + "fan": { + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + }, + "sensor": {"name": "test", "state_topic": "test-topic"}, + "switch": {"name": "test", "command_topic": "test-topic"}, + "select": { + "name": "test", + "command_topic": "test-topic", + "options": ["milk", "beer"], + }, + } + non_entity_configs = { + "tag": { + "device": {"identifiers": ["tag_0AFFD2"]}, + "topic": "foobar/tag_scanned", + }, + "device_automation": { + "automation_type": "trigger", + "device": {"identifiers": ["device_automation_0AFFD2"]}, + "payload": "short_press", + "topic": "foobar/triggers/button1", + "type": "button_short_press", + "subtype": "button_1", + }, + } + for platform, config in entity_configs.items(): + for set_number in range(0, 2): + set_config = deepcopy(config) + set_config["name"] = f"test_{set_number}" + topic = f"homeassistant/{platform}/bla_{set_number}/config" + async_fire_mqtt_message(hass, topic, json.dumps(set_config)) + for platform, config in non_entity_configs.items(): + topic = f"homeassistant/{platform}/bla/config" + async_fire_mqtt_message(hass, topic, json.dumps(config)) + await hass.async_block_till_done() + for set_number in range(0, 2): + for platform in entity_configs: + entity_id = f"{platform}.test_{set_number}" + state = hass.states.get(entity_id) + assert state is not None + for platform in non_entity_configs: + assert ( + device_registry.async_get_device( + identifiers={("mqtt", f"{platform}_0AFFD2")} + ) + is not None + ) From 44abe329a23c353f15cf8414e167819f891e46cc Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:03:07 +0100 Subject: [PATCH 0626/1691] Enphase Envoy refactor and extend diagnostics (#109080) Co-authored-by: J. Nick Koston --- .../components/enphase_envoy/diagnostics.py | 90 +- .../snapshots/test_diagnostics.ambr | 3161 ++++++++++++++++- .../enphase_envoy/test_diagnostics.py | 24 +- 3 files changed, 3257 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/enphase_envoy/diagnostics.py b/homeassistant/components/enphase_envoy/diagnostics.py index 6c1472fedc8..1df7606f8af 100644 --- a/homeassistant/components/enphase_envoy/diagnostics.py +++ b/homeassistant/components/enphase_envoy/diagnostics.py @@ -2,7 +2,10 @@ from __future__ import annotations -from typing import Any +import copy +from typing import TYPE_CHECKING, Any + +from attr import asdict from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry @@ -14,11 +17,15 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.json import json_dumps +from homeassistant.util.json import json_loads from .const import DOMAIN from .coordinator import EnphaseUpdateCoordinator CONF_TITLE = "title" +CLEAN_TEXT = "<>" TO_REDACT = { CONF_NAME, @@ -37,11 +44,78 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" coordinator: EnphaseUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - return async_redact_data( - { - "entry": entry.as_dict(), - "envoy_firmware": coordinator.envoy.firmware, - "data": coordinator.data, - }, - TO_REDACT, + if TYPE_CHECKING: + assert coordinator.envoy.data + envoy_data = coordinator.envoy.data + envoy = coordinator.envoy + + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + + device_entities = [] + # for each device associated with the envoy get entity and state information + for device in dr.async_entries_for_config_entry(device_registry, entry.entry_id): + entities = [] + for entity in er.async_entries_for_device( + entity_registry, device_id=device.id, include_disabled_entities=True + ): + state_dict = None + if state := hass.states.get(entity.entity_id): + state_dict = dict(state.as_dict()) + state_dict.pop("context", None) + entities.append({"entity": asdict(entity), "state": state_dict}) + device_entities.append({"device": asdict(device), "entities": entities}) + + # remove envoy serial + old_serial = coordinator.envoy_serial_number + + coordinator_data = copy.deepcopy(coordinator.data) + coordinator_data_cleaned = json_dumps(coordinator_data).replace( + old_serial, CLEAN_TEXT ) + + device_entities_cleaned = json_dumps(device_entities).replace( + old_serial, CLEAN_TEXT + ) + + envoy_model: dict[str, Any] = { + "encharge_inventory": envoy_data.encharge_inventory, + "encharge_power": envoy_data.encharge_power, + "encharge_aggregate": envoy_data.encharge_aggregate, + "enpower": envoy_data.enpower, + "system_consumption": envoy_data.system_consumption, + "system_production": envoy_data.system_production, + "system_consumption_phases": envoy_data.system_consumption_phases, + "system_production_phases": envoy_data.system_production_phases, + "ctmeter_production": envoy_data.ctmeter_production, + "ctmeter_consumption": envoy_data.ctmeter_consumption, + "ctmeter_production_phases": envoy_data.ctmeter_production_phases, + "ctmeter_consumption_phases": envoy_data.ctmeter_consumption_phases, + "dry_contact_status": envoy_data.dry_contact_status, + "dry_contact_settings": envoy_data.dry_contact_settings, + "inverters": envoy_data.inverters, + "tariff": envoy_data.tariff, + } + + envoy_properties: dict[str, Any] = { + "envoy_firmware": envoy.firmware, + "part_number": envoy.part_number, + "envoy_model": envoy.envoy_model, + "supported_features": [feature.name for feature in envoy.supported_features], + "phase_mode": envoy.phase_mode, + "phase_count": envoy.phase_count, + "active_phasecount": envoy.active_phase_count, + "ct_count": envoy.ct_meter_count, + "ct_consumption_meter": envoy.consumption_meter_type, + "ct_production_meter": envoy.production_meter_type, + } + + diagnostic_data: dict[str, Any] = { + "config_entry": async_redact_data(entry.as_dict(), TO_REDACT), + "envoy_properties": envoy_properties, + "raw_data": json_loads(coordinator_data_cleaned), + "envoy_model_data": envoy_model, + "envoy_entities_by_device": json_loads(device_entities_cleaned), + } + + return diagnostic_data diff --git a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr index 9266ffcf94e..18ad22921ed 100644 --- a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr +++ b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr @@ -1,10 +1,7 @@ # serializer version: 1 # name: test_entry_diagnostics dict({ - 'data': dict({ - 'varies_by': 'firmware_version', - }), - 'entry': dict({ + 'config_entry': dict({ 'data': dict({ 'host': '1.1.1.1', 'name': '**REDACTED**', @@ -25,6 +22,3160 @@ 'unique_id': '**REDACTED**', 'version': 1, }), - 'envoy_firmware': '7.1.2', + 'envoy_entities_by_device': list([ + dict({ + 'device': dict({ + 'area_id': None, + 'config_entries': list([ + '45a36e55aaddb2007c5f6602e0c38e72', + ]), + 'configuration_url': None, + 'connections': list([ + ]), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': '<>56789', + 'identifiers': list([ + list([ + 'enphase_envoy', + '<>', + ]), + ]), + 'is_new': False, + 'labels': list([ + ]), + 'manufacturer': 'Enphase', + 'model': 'Envoy, phases: 3, phase mode: three, net-consumption CT, production CT', + 'name': 'Envoy <>', + 'name_by_user': None, + 'serial_number': '<>', + 'suggested_area': None, + 'sw_version': '7.1.2', + }), + 'entities': list([ + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_production', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power production', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_production', + 'unique_id': '<>_production', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power production', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_production', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production today', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_production', + 'unique_id': '<>_daily_production', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production today', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_today', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production last seven days', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_production', + 'unique_id': '<>_seven_days_production', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production last seven days', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy production', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_production', + 'unique_id': '<>_lifetime_production', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy production', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production', + 'state': '0.00<>', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_consumption', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power consumption', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_consumption', + 'unique_id': '<>_consumption', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power consumption', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_consumption', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_today', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption today', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_consumption', + 'unique_id': '<>_daily_consumption', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption today', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_today', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption last seven days', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_consumption', + 'unique_id': '<>_seven_days_consumption', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption last seven days', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy consumption', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_consumption', + 'unique_id': '<>_lifetime_consumption', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy consumption', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption', + 'state': '0.00<>', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_production_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power production l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_production_phase', + 'unique_id': '<>_production_l1', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power production l1', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_production_l1', + 'state': '1.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_today_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production today l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_production_phase', + 'unique_id': '<>_daily_production_l1', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production today l1', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_today_l1', + 'state': '1.233', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production last seven days l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_production_phase', + 'unique_id': '<>_seven_days_production_l1', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production last seven days l1', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days_l1', + 'state': '1.231', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy production l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_production_phase', + 'unique_id': '<>_lifetime_production_l1', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy production l1', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production_l1', + 'state': '0.001232', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_production_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power production l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_production_phase', + 'unique_id': '<>_production_l2', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power production l2', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_production_l2', + 'state': '2.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_today_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production today l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_production_phase', + 'unique_id': '<>_daily_production_l2', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production today l2', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_today_l2', + 'state': '2.233', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production last seven days l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_production_phase', + 'unique_id': '<>_seven_days_production_l2', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production last seven days l2', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days_l2', + 'state': '2.231', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy production l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_production_phase', + 'unique_id': '<>_lifetime_production_l2', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy production l2', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production_l2', + 'state': '0.002232', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_production_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power production l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_production_phase', + 'unique_id': '<>_production_l3', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power production l3', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_production_l3', + 'state': '3.234', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_today_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production today l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_production_phase', + 'unique_id': '<>_daily_production_l3', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production today l3', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_today_l3', + 'state': '3.233', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy production last seven days l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_production_phase', + 'unique_id': '<>_seven_days_production_l3', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy production last seven days l3', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_production_last_seven_days_l3', + 'state': '3.231', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy production l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_production_phase', + 'unique_id': '<>_lifetime_production_l3', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy production l3', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_production_l3', + 'state': '0.003232', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_consumption_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power consumption l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_consumption_phase', + 'unique_id': '<>_consumption_l1', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power consumption l1', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_consumption_l1', + 'state': '1.324', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_today_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption today l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_consumption_phase', + 'unique_id': '<>_daily_consumption_l1', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption today l1', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_today_l1', + 'state': '1.323', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption last seven days l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_consumption_phase', + 'unique_id': '<>_seven_days_consumption_l1', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption last seven days l1', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days_l1', + 'state': '1.321', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy consumption l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_consumption_phase', + 'unique_id': '<>_lifetime_consumption_l1', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy consumption l1', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption_l1', + 'state': '0.001322', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_consumption_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power consumption l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_consumption_phase', + 'unique_id': '<>_consumption_l2', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power consumption l2', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_consumption_l2', + 'state': '2.324', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_today_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption today l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_consumption_phase', + 'unique_id': '<>_daily_consumption_l2', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption today l2', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_today_l2', + 'state': '2.323', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption last seven days l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_consumption_phase', + 'unique_id': '<>_seven_days_consumption_l2', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption last seven days l2', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days_l2', + 'state': '2.321', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy consumption l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_consumption_phase', + 'unique_id': '<>_lifetime_consumption_l2', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy consumption l2', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption_l2', + 'state': '0.002322', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_power_consumption_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current power consumption l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'current_power_consumption_phase', + 'unique_id': '<>_consumption_l3', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current power consumption l3', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_power_consumption_l3', + 'state': '3.324', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_today_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption today l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_consumption_phase', + 'unique_id': '<>_daily_consumption_l3', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption today l3', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_today_l3', + 'state': '3.323', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Energy consumption last seven days l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'seven_days_consumption_phase', + 'unique_id': '<>_seven_days_consumption_l3', + 'unit_of_measurement': 'kWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Energy consumption last seven days l3', + 'icon': 'mdi:flash', + 'unit_of_measurement': 'kWh', + }), + 'entity_id': 'sensor.envoy_<>_energy_consumption_last_seven_days_l3', + 'state': '3.321', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime energy consumption l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_consumption_phase', + 'unique_id': '<>_lifetime_consumption_l3', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime energy consumption l3', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_energy_consumption_l3', + 'state': '0.003322', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_consumption', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy consumption', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_consumption', + 'unique_id': '<>_lifetime_net_consumption', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime net energy consumption', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_consumption', + 'state': '0.02<>', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_production', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy production', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_production', + 'unique_id': '<>_lifetime_net_production', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime net energy production', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_production', + 'state': '0.022345', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_net_power_consumption', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current net power consumption', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_consumption', + 'unique_id': '<>_net_consumption', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current net power consumption', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_net_power_consumption', + 'state': '0.101', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_frequency_net_consumption_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'frequency', + 'original_icon': 'mdi:flash', + 'original_name': 'Frequency net consumption CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_frequency', + 'unique_id': '<>_frequency', + 'unit_of_measurement': 'Hz', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_net_consumption_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage net consumption CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_voltage', + 'unique_id': '<>_voltage', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_net_consumption_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status net consumption CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_metering_status', + 'unique_id': '<>_net_consumption_ct_metering_status', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_net_consumption_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active net consumption CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_status_flags', + 'unique_id': '<>_net_consumption_ct_status_flags', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_consumption_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy consumption l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_consumption_phase', + 'unique_id': '<>_lifetime_net_consumption_l1', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_production_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy production l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_production_phase', + 'unique_id': '<>_lifetime_net_production_l1', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_net_power_consumption_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current net power consumption l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_consumption_phase', + 'unique_id': '<>_net_consumption_l1', + 'unit_of_measurement': 'kW', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_frequency_net_consumption_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'frequency', + 'original_icon': 'mdi:flash', + 'original_name': 'Frequency net consumption CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_frequency_phase', + 'unique_id': '<>_frequency_l1', + 'unit_of_measurement': 'Hz', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_net_consumption_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage net consumption CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_voltage_phase', + 'unique_id': '<>_voltage_l1', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_net_consumption_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status net consumption CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_metering_status_phase', + 'unique_id': '<>_net_consumption_ct_metering_status_l1', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_net_consumption_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active net consumption CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_status_flags_phase', + 'unique_id': '<>_net_consumption_ct_status_flags_l1', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_consumption_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy consumption l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_consumption_phase', + 'unique_id': '<>_lifetime_net_consumption_l2', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_production_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy production l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_production_phase', + 'unique_id': '<>_lifetime_net_production_l2', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_net_power_consumption_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current net power consumption l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_consumption_phase', + 'unique_id': '<>_net_consumption_l2', + 'unit_of_measurement': 'kW', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_frequency_net_consumption_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'frequency', + 'original_icon': 'mdi:flash', + 'original_name': 'Frequency net consumption CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_frequency_phase', + 'unique_id': '<>_frequency_l2', + 'unit_of_measurement': 'Hz', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_net_consumption_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage net consumption CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_voltage_phase', + 'unique_id': '<>_voltage_l2', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_net_consumption_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status net consumption CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_metering_status_phase', + 'unique_id': '<>_net_consumption_ct_metering_status_l2', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_net_consumption_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active net consumption CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_status_flags_phase', + 'unique_id': '<>_net_consumption_ct_status_flags_l2', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_consumption_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy consumption l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_consumption_phase', + 'unique_id': '<>_lifetime_net_consumption_l3', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_net_energy_production_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime net energy production l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_net_production_phase', + 'unique_id': '<>_lifetime_net_production_l3', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_net_power_consumption_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current net power consumption l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_consumption_phase', + 'unique_id': '<>_net_consumption_l3', + 'unit_of_measurement': 'kW', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_frequency_net_consumption_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'frequency', + 'original_icon': 'mdi:flash', + 'original_name': 'Frequency net consumption CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_frequency_phase', + 'unique_id': '<>_frequency_l3', + 'unit_of_measurement': 'Hz', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_net_consumption_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage net consumption CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_voltage_phase', + 'unique_id': '<>_voltage_l3', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_net_consumption_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status net consumption CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_metering_status_phase', + 'unique_id': '<>_net_consumption_ct_metering_status_l3', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_net_consumption_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active net consumption CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'net_ct_status_flags_phase', + 'unique_id': '<>_net_consumption_ct_status_flags_l3', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_production_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status production CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_metering_status', + 'unique_id': '<>_production_ct_metering_status', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_production_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active production CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_status_flags', + 'unique_id': '<>_production_ct_status_flags', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_production_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status production CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_metering_status_phase', + 'unique_id': '<>_production_ct_metering_status_l1', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_production_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active production CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_status_flags_phase', + 'unique_id': '<>_production_ct_status_flags_l1', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_production_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status production CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_metering_status_phase', + 'unique_id': '<>_production_ct_metering_status_l2', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_production_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active production CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_status_flags_phase', + 'unique_id': '<>_production_ct_status_flags_l2', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_production_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status production CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_metering_status_phase', + 'unique_id': '<>_production_ct_metering_status_l3', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_production_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active production CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'production_ct_status_flags_phase', + 'unique_id': '<>_production_ct_status_flags_l3', + 'unit_of_measurement': None, + }), + 'state': None, + }), + ]), + }), + dict({ + 'device': dict({ + 'area_id': None, + 'config_entries': list([ + '45a36e55aaddb2007c5f6602e0c38e72', + ]), + 'configuration_url': None, + 'connections': list([ + ]), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'identifiers': list([ + list([ + 'enphase_envoy', + '1', + ]), + ]), + 'is_new': False, + 'labels': list([ + ]), + 'manufacturer': 'Enphase', + 'model': 'Inverter', + 'name': 'Inverter 1', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + }), + 'entities': list([ + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.inverter_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': None, + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '1', + 'unit_of_measurement': 'W', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Inverter 1', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'W', + }), + 'entity_id': 'sensor.inverter_1', + 'state': '1', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.inverter_1_last_reported', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'timestamp', + 'original_icon': 'mdi:flash', + 'original_name': 'Last reported', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'last_reported', + 'unique_id': '1_last_reported', + 'unit_of_measurement': None, + }), + 'state': None, + }), + ]), + }), + ]), + 'envoy_model_data': dict({ + 'ctmeter_consumption': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000020', timestamp=1708006120, energy_delivered=21234, energy_received=22345, active_power=101, power_factor=0.21, voltage=112, current=0.3, frequency=50.2, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'ctmeter_consumption_phases': dict({ + 'L1': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000021', timestamp=1708006121, energy_delivered=212341, energy_received=223451, active_power=21, power_factor=0.22, voltage=112, current=0.3, frequency=50.2, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'L2': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000022', timestamp=1708006122, energy_delivered=212342, energy_received=223452, active_power=31, power_factor=0.23, voltage=112, current=0.3, frequency=50.2, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'L3': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000023', timestamp=1708006123, energy_delivered=212343, energy_received=223453, active_power=51, power_factor=0.24, voltage=112, current=0.3, frequency=50.2, state=, measurement_type=, metering_status=, status_flags=[])", + }), + }), + 'ctmeter_production': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000010', timestamp=1708006110, energy_delivered=11234, energy_received=12345, active_power=100, power_factor=0.11, voltage=111, current=0.2, frequency=50.1, state=, measurement_type=, metering_status=, status_flags=[, ])", + }), + 'ctmeter_production_phases': dict({ + 'L1': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000011', timestamp=1708006111, energy_delivered=112341, energy_received=123451, active_power=20, power_factor=0.12, voltage=111, current=0.2, frequency=50.1, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'L2': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000012', timestamp=1708006112, energy_delivered=112342, energy_received=123452, active_power=30, power_factor=0.13, voltage=111, current=0.2, frequency=50.1, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'L3': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000013', timestamp=1708006113, energy_delivered=112343, energy_received=123453, active_power=50, power_factor=0.14, voltage=111, current=0.2, frequency=50.1, state=, measurement_type=, metering_status=, status_flags=[])", + }), + }), + 'dry_contact_settings': dict({ + }), + 'dry_contact_status': dict({ + }), + 'encharge_aggregate': None, + 'encharge_inventory': None, + 'encharge_power': None, + 'enpower': None, + 'inverters': dict({ + '1': dict({ + '__type': "", + 'repr': "EnvoyInverter(serial_number='1', last_report_date=1, last_report_watts=1, max_report_watts=1)", + }), + }), + 'system_consumption': dict({ + '__type': "", + 'repr': 'EnvoySystemConsumption(watt_hours_lifetime=1234, watt_hours_last_7_days=1234, watt_hours_today=1234, watts_now=1234)', + }), + 'system_consumption_phases': dict({ + 'L1': dict({ + '__type': "", + 'repr': 'EnvoySystemConsumption(watt_hours_lifetime=1322, watt_hours_last_7_days=1321, watt_hours_today=1323, watts_now=1324)', + }), + 'L2': dict({ + '__type': "", + 'repr': 'EnvoySystemConsumption(watt_hours_lifetime=2322, watt_hours_last_7_days=2321, watt_hours_today=2323, watts_now=2324)', + }), + 'L3': dict({ + '__type': "", + 'repr': 'EnvoySystemConsumption(watt_hours_lifetime=3322, watt_hours_last_7_days=3321, watt_hours_today=3323, watts_now=3324)', + }), + }), + 'system_production': dict({ + '__type': "", + 'repr': 'EnvoySystemProduction(watt_hours_lifetime=1234, watt_hours_last_7_days=1234, watt_hours_today=1234, watts_now=1234)', + }), + 'system_production_phases': dict({ + 'L1': dict({ + '__type': "", + 'repr': 'EnvoySystemProduction(watt_hours_lifetime=1232, watt_hours_last_7_days=1231, watt_hours_today=1233, watts_now=1234)', + }), + 'L2': dict({ + '__type': "", + 'repr': 'EnvoySystemProduction(watt_hours_lifetime=2232, watt_hours_last_7_days=2231, watt_hours_today=2233, watts_now=2234)', + }), + 'L3': dict({ + '__type': "", + 'repr': 'EnvoySystemProduction(watt_hours_lifetime=3232, watt_hours_last_7_days=3231, watt_hours_today=3233, watts_now=3234)', + }), + }), + 'tariff': None, + }), + 'envoy_properties': dict({ + 'active_phasecount': 3, + 'ct_consumption_meter': 'net-consumption', + 'ct_count': 2, + 'ct_production_meter': 'production', + 'envoy_firmware': '7.1.2', + 'envoy_model': 'Envoy, phases: 3, phase mode: three, net-consumption CT, production CT', + 'part_number': '123456789', + 'phase_count': 3, + 'phase_mode': 'three', + 'supported_features': list([ + 'INVERTERS', + 'METERING', + 'PRODUCTION', + 'THREEPHASE', + 'CTMETERS', + ]), + }), + 'raw_data': dict({ + 'varies_by': 'firmware_version', + }), }) # --- diff --git a/tests/components/enphase_envoy/test_diagnostics.py b/tests/components/enphase_envoy/test_diagnostics.py index 10c77992b6a..f1e601a48d7 100644 --- a/tests/components/enphase_envoy/test_diagnostics.py +++ b/tests/components/enphase_envoy/test_diagnostics.py @@ -2,21 +2,35 @@ from syrupy import SnapshotAssertion +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator +# Fields to exclude from snapshot as they change each run +TO_EXCLUDE = { + "id", + "device_id", + "via_device_id", + "last_updated", + "last_changed", +} + + +def limit_diagnostic_attrs(prop, path) -> bool: + """Mark attributes to exclude from diagnostic snapshot.""" + return prop in TO_EXCLUDE + async def test_entry_diagnostics( hass: HomeAssistant, - config_entry, + config_entry: ConfigEntry, hass_client: ClientSessionGenerator, setup_enphase_envoy, snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" - assert ( - await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - == snapshot - ) + assert await get_diagnostics_for_config_entry( + hass, hass_client, config_entry + ) == snapshot(exclude=limit_diagnostic_attrs) From 4882fed939863369a5fb9b7bbe4179266fe1686a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 11:03:22 -1000 Subject: [PATCH 0627/1691] Avoid saving auth right after we load it during startup (#112008) --- homeassistant/auth/auth_store.py | 17 ++- tests/auth/test_auth_store.py | 192 +++++++++++++------------------ 2 files changed, 95 insertions(+), 114 deletions(-) diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index b9f7b3a7e14..b3481acca3c 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -31,6 +31,17 @@ GROUP_NAME_ADMIN = "Administrators" GROUP_NAME_USER = "Users" GROUP_NAME_READ_ONLY = "Read Only" +# We always save the auth store after we load it since +# we may migrate data and do not want to have to do it again +# but we don't want to do it during startup so we schedule +# the first save 5 minutes out knowing something else may +# want to save the auth store before then, and since Storage +# will honor the lower of the two delays, it will save it +# faster if something else saves it. +INITIAL_LOAD_SAVE_DELAY = 300 + +DEFAULT_SAVE_DELAY = 1 + class AuthStore: """Stores authentication info. @@ -468,12 +479,12 @@ class AuthStore: self._groups = groups self._users = users - self._async_schedule_save() + self._async_schedule_save(INITIAL_LOAD_SAVE_DELAY) @callback - def _async_schedule_save(self) -> None: + def _async_schedule_save(self, delay: float = DEFAULT_SAVE_DELAY) -> None: """Save users.""" - self._store.async_delay_save(self._data_to_save, 1) + self._store.async_delay_save(self._data_to_save, delay) @callback def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: diff --git a/tests/auth/test_auth_store.py b/tests/auth/test_auth_store.py index 796f2335569..91048cd8568 100644 --- a/tests/auth/test_auth_store.py +++ b/tests/auth/test_auth_store.py @@ -6,71 +6,74 @@ from typing import Any from unittest.mock import patch from freezegun import freeze_time +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.auth import auth_store from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util +MOCK_STORAGE_DATA = { + "version": 1, + "data": { + "credentials": [], + "users": [ + { + "id": "user-id", + "is_active": True, + "is_owner": True, + "name": "Paulus", + "system_generated": False, + }, + { + "id": "system-id", + "is_active": True, + "is_owner": True, + "name": "Hass.io", + "system_generated": True, + }, + ], + "refresh_tokens": [ + { + "access_token_expiration": 1800.0, + "client_id": "http://localhost:8123/", + "created_at": "2018-10-03T13:43:19.774637+00:00", + "id": "user-token-id", + "jwt_key": "some-key", + "last_used_at": "2018-10-03T13:43:19.774712+00:00", + "token": "some-token", + "user_id": "user-id", + "version": "1.2.3", + }, + { + "access_token_expiration": 1800.0, + "client_id": None, + "created_at": "2018-10-03T13:43:19.774637+00:00", + "id": "system-token-id", + "jwt_key": "some-key", + "last_used_at": "2018-10-03T13:43:19.774712+00:00", + "token": "some-token", + "user_id": "system-id", + }, + { + "access_token_expiration": 1800.0, + "client_id": "http://localhost:8123/", + "created_at": "2018-10-03T13:43:19.774637+00:00", + "id": "hidden-because-no-jwt-id", + "last_used_at": "2018-10-03T13:43:19.774712+00:00", + "token": "some-token", + "user_id": "user-id", + }, + ], + }, +} + async def test_loading_no_group_data_format( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test we correctly load old data without any groups.""" - hass_storage[auth_store.STORAGE_KEY] = { - "version": 1, - "data": { - "credentials": [], - "users": [ - { - "id": "user-id", - "is_active": True, - "is_owner": True, - "name": "Paulus", - "system_generated": False, - }, - { - "id": "system-id", - "is_active": True, - "is_owner": True, - "name": "Hass.io", - "system_generated": True, - }, - ], - "refresh_tokens": [ - { - "access_token_expiration": 1800.0, - "client_id": "http://localhost:8123/", - "created_at": "2018-10-03T13:43:19.774637+00:00", - "id": "user-token-id", - "jwt_key": "some-key", - "last_used_at": "2018-10-03T13:43:19.774712+00:00", - "token": "some-token", - "user_id": "user-id", - "version": "1.2.3", - }, - { - "access_token_expiration": 1800.0, - "client_id": None, - "created_at": "2018-10-03T13:43:19.774637+00:00", - "id": "system-token-id", - "jwt_key": "some-key", - "last_used_at": "2018-10-03T13:43:19.774712+00:00", - "token": "some-token", - "user_id": "system-id", - }, - { - "access_token_expiration": 1800.0, - "client_id": "http://localhost:8123/", - "created_at": "2018-10-03T13:43:19.774637+00:00", - "id": "hidden-because-no-jwt-id", - "last_used_at": "2018-10-03T13:43:19.774712+00:00", - "token": "some-token", - "user_id": "user-id", - }, - ], - }, - } + hass_storage[auth_store.STORAGE_KEY] = MOCK_STORAGE_DATA store = auth_store.AuthStore(hass) await store.async_load() @@ -113,63 +116,7 @@ async def test_loading_all_access_group_data_format( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test we correctly load old data with single group.""" - hass_storage[auth_store.STORAGE_KEY] = { - "version": 1, - "data": { - "credentials": [], - "users": [ - { - "id": "user-id", - "is_active": True, - "is_owner": True, - "name": "Paulus", - "system_generated": False, - "group_ids": ["abcd-all-access"], - }, - { - "id": "system-id", - "is_active": True, - "is_owner": True, - "name": "Hass.io", - "system_generated": True, - }, - ], - "groups": [{"id": "abcd-all-access", "name": "All Access"}], - "refresh_tokens": [ - { - "access_token_expiration": 1800.0, - "client_id": "http://localhost:8123/", - "created_at": "2018-10-03T13:43:19.774637+00:00", - "id": "user-token-id", - "jwt_key": "some-key", - "last_used_at": "2018-10-03T13:43:19.774712+00:00", - "token": "some-token", - "user_id": "user-id", - "version": "1.2.3", - }, - { - "access_token_expiration": 1800.0, - "client_id": None, - "created_at": "2018-10-03T13:43:19.774637+00:00", - "id": "system-token-id", - "jwt_key": "some-key", - "last_used_at": "2018-10-03T13:43:19.774712+00:00", - "token": "some-token", - "user_id": "system-id", - "version": None, - }, - { - "access_token_expiration": 1800.0, - "client_id": "http://localhost:8123/", - "created_at": "2018-10-03T13:43:19.774637+00:00", - "id": "hidden-because-no-jwt-id", - "last_used_at": "2018-10-03T13:43:19.774712+00:00", - "token": "some-token", - "user_id": "user-id", - }, - ], - }, - } + hass_storage[auth_store.STORAGE_KEY] = MOCK_STORAGE_DATA store = auth_store.AuthStore(hass) await store.async_load() @@ -335,3 +282,26 @@ async def test_add_expire_at_property( assert token1.expire_at == now.timestamp() + timedelta(days=80).total_seconds() assert token2.expire_at assert token2.expire_at == now.timestamp() + timedelta(days=90).total_seconds() + + +async def test_loading_does_not_write_right_away( + hass: HomeAssistant, hass_storage: dict[str, Any], freezer: FrozenDateTimeFactory +) -> None: + """Test after calling load we wait five minutes to write.""" + hass_storage[auth_store.STORAGE_KEY] = MOCK_STORAGE_DATA + + store = auth_store.AuthStore(hass) + await store.async_load() + + # Wipe storage so we can verify if it was written + hass_storage[auth_store.STORAGE_KEY] = {} + + freezer.tick(auth_store.DEFAULT_SAVE_DELAY) + await hass.async_block_till_done() + assert hass_storage[auth_store.STORAGE_KEY] == {} + freezer.tick(auth_store.INITIAL_LOAD_SAVE_DELAY) + # Once for scheduling the task + await hass.async_block_till_done() + # Once for the task + await hass.async_block_till_done() + assert hass_storage[auth_store.STORAGE_KEY] != {} From 23ebd802857b216e85ec9aa18be8cdac21720fb4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 11:24:54 -1000 Subject: [PATCH 0628/1691] Schedule coroutines with eager_task from async_track_state_change_event (#112807) --- homeassistant/helpers/event.py | 2 +- tests/components/knx/test_expose.py | 22 ++++++++++++++++++++++ tests/components/knx/test_services.py | 6 ++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index e7beddec0c7..e9da56bf3af 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -328,7 +328,7 @@ def _async_dispatch_entity_id_event( return for job in callbacks_list.copy(): try: - hass.async_run_hass_job(job, event) + hass.async_run_hass_job(job, event, eager_start=True) except Exception: # pylint: disable=broad-except _LOGGER.exception( "Error while dispatching event for %s to %s", diff --git a/tests/components/knx/test_expose.py b/tests/components/knx/test_expose.py index 0eea78d85b7..d2b7653cfe8 100644 --- a/tests/components/knx/test_expose.py +++ b/tests/components/knx/test_expose.py @@ -32,14 +32,17 @@ async def test_binary_expose(hass: HomeAssistant, knx: KNXTestKit) -> None: # Change state to on hass.states.async_set(entity_id, "on", {}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", True) # Change attribute; keep state hass.states.async_set(entity_id, "on", {"brightness": 180}) + await hass.async_block_till_done() await knx.assert_no_telegram() # Change attribute and state hass.states.async_set(entity_id, "off", {"brightness": 0}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", False) @@ -64,10 +67,12 @@ async def test_expose_attribute(hass: HomeAssistant, knx: KNXTestKit) -> None: # Change state to "on"; no attribute hass.states.async_set(entity_id, "on", {}) + await hass.async_block_till_done() await knx.assert_telegram_count(0) # Change attribute; keep state hass.states.async_set(entity_id, "on", {attribute: 1}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (1,)) # Read in between @@ -76,22 +81,27 @@ async def test_expose_attribute(hass: HomeAssistant, knx: KNXTestKit) -> None: # Change state keep attribute hass.states.async_set(entity_id, "off", {attribute: 1}) + await hass.async_block_till_done() await knx.assert_telegram_count(0) # Change state and attribute hass.states.async_set(entity_id, "on", {attribute: 0}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (0,)) # Change state to "off"; no attribute hass.states.async_set(entity_id, "off", {}) + await hass.async_block_till_done() await knx.assert_telegram_count(0) # Change attribute; keep state hass.states.async_set(entity_id, "on", {attribute: 1}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (1,)) # Change state to "off"; null attribute hass.states.async_set(entity_id, "off", {attribute: None}) + await hass.async_block_till_done() await knx.assert_telegram_count(0) @@ -119,18 +129,22 @@ async def test_expose_attribute_with_default( # Change state to "on"; no attribute hass.states.async_set(entity_id, "on", {}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (0,)) # Change attribute; keep state hass.states.async_set(entity_id, "on", {attribute: 1}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (1,)) # Change state keep attribute hass.states.async_set(entity_id, "off", {attribute: 1}) + await hass.async_block_till_done() await knx.assert_no_telegram() # Change state and attribute hass.states.async_set(entity_id, "on", {attribute: 3}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (3,)) # Read in between @@ -139,14 +153,17 @@ async def test_expose_attribute_with_default( # Change state to "off"; no attribute hass.states.async_set(entity_id, "off", {}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (0,)) # Change state and attribute hass.states.async_set(entity_id, "on", {attribute: 1}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (1,)) # Change state to "off"; null attribute hass.states.async_set(entity_id, "off", {attribute: None}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (0,)) @@ -179,6 +196,7 @@ async def test_expose_string(hass: HomeAssistant, knx: KNXTestKit) -> None: "on", {attribute: "This is a very long string that is larger than 14 bytes"}, ) + await hass.async_block_till_done() await knx.assert_write( "1/1/8", (84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 118, 101, 114, 121) ) @@ -200,13 +218,16 @@ async def test_expose_cooldown(hass: HomeAssistant, knx: KNXTestKit) -> None: ) # Change state to 1 hass.states.async_set(entity_id, "1", {}) + await hass.async_block_till_done() await knx.assert_write("1/1/8", (1,)) # Change state to 2 - skip because of cooldown hass.states.async_set(entity_id, "2", {}) + await hass.async_block_till_done() await knx.assert_no_telegram() # Change state to 3 hass.states.async_set(entity_id, "3", {}) + await hass.async_block_till_done() await knx.assert_no_telegram() # Wait for cooldown to pass async_fire_time_changed_exact( @@ -245,6 +266,7 @@ async def test_expose_conversion_exception( "on", {attribute: 101}, ) + await hass.async_block_till_done() await knx.assert_no_telegram() assert ( 'Could not expose fake.entity fake_attribute value "101.0" to KNX:' diff --git a/tests/components/knx/test_services.py b/tests/components/knx/test_services.py index 95cf60f1e68..a6688f7a14c 100644 --- a/tests/components/knx/test_services.py +++ b/tests/components/knx/test_services.py @@ -208,6 +208,7 @@ async def test_exposure_register(hass: HomeAssistant, knx: KNXTestKit) -> None: # no exposure registered hass.states.async_set(test_entity, STATE_ON, {}) + await hass.async_block_till_done() await knx.assert_no_telegram() # register exposure @@ -218,6 +219,7 @@ async def test_exposure_register(hass: HomeAssistant, knx: KNXTestKit) -> None: blocking=True, ) hass.states.async_set(test_entity, STATE_OFF, {}) + await hass.async_block_till_done() await knx.assert_write(test_address, False) # register exposure @@ -228,6 +230,7 @@ async def test_exposure_register(hass: HomeAssistant, knx: KNXTestKit) -> None: blocking=True, ) hass.states.async_set(test_entity, STATE_ON, {}) + await hass.async_block_till_done() await knx.assert_no_telegram() # register exposure for attribute with default @@ -245,8 +248,10 @@ async def test_exposure_register(hass: HomeAssistant, knx: KNXTestKit) -> None: ) # no attribute on first change wouldn't work because no attribute change since last test hass.states.async_set(test_entity, STATE_ON, {test_attribute: 30}) + await hass.async_block_till_done() await knx.assert_write(test_address, (30,)) hass.states.async_set(test_entity, STATE_OFF, {}) + await hass.async_block_till_done() await knx.assert_write(test_address, (0,)) # don't send same value sequentially hass.states.async_set(test_entity, STATE_ON, {test_attribute: 25}) @@ -254,6 +259,7 @@ async def test_exposure_register(hass: HomeAssistant, knx: KNXTestKit) -> None: hass.states.async_set(test_entity, STATE_ON, {test_attribute: 25, "unrelated": 2}) hass.states.async_set(test_entity, STATE_OFF, {test_attribute: 25}) await hass.async_block_till_done() + await hass.async_block_till_done() await knx.assert_telegram_count(1) await knx.assert_write(test_address, (25,)) From 8ea093ca3bc7900b03b352b7a71217261dc75436 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Mar 2024 22:48:12 +0100 Subject: [PATCH 0629/1691] Remove entity description mixin in IPP (#112781) --- homeassistant/components/ipp/sensor.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 8e3162ec61d..1aad6ae6b21 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -38,19 +38,11 @@ from .coordinator import IPPDataUpdateCoordinator from .entity import IPPEntity -@dataclass(frozen=True) -class IPPSensorEntityDescriptionMixin: - """Mixin for required keys.""" - - value_fn: Callable[[Printer], StateType | datetime] - - -@dataclass(frozen=True) -class IPPSensorEntityDescription( - SensorEntityDescription, IPPSensorEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class IPPSensorEntityDescription(SensorEntityDescription): """Describes IPP sensor entity.""" + value_fn: Callable[[Printer], StateType | datetime] attributes_fn: Callable[[Printer], dict[Any, StateType]] = lambda _: {} From caaa03536b51101656404e11f944579965793900 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 9 Mar 2024 23:48:54 +0100 Subject: [PATCH 0630/1691] Cleanup mqtt PLATFORMS constant and CI-test platform filters (#112847) --- homeassistant/components/mqtt/__init__.py | 1 - homeassistant/components/mqtt/const.py | 28 --------------- .../mqtt/test_alarm_control_panel.py | 10 ------ tests/components/mqtt/test_binary_sensor.py | 8 ----- tests/components/mqtt/test_button.py | 14 +------- tests/components/mqtt/test_camera.py | 8 ----- tests/components/mqtt/test_climate.py | 9 +---- tests/components/mqtt/test_cover.py | 8 ----- tests/components/mqtt/test_device_tracker.py | 10 +----- tests/components/mqtt/test_device_trigger.py | 12 ------- tests/components/mqtt/test_diagnostics.py | 13 +------ tests/components/mqtt/test_discovery.py | 35 ++----------------- tests/components/mqtt/test_event.py | 9 +---- tests/components/mqtt/test_fan.py | 8 ----- tests/components/mqtt/test_humidifier.py | 8 ----- tests/components/mqtt/test_image.py | 9 +---- tests/components/mqtt/test_init.py | 26 -------------- tests/components/mqtt/test_lawn_mower.py | 14 +------- tests/components/mqtt/test_legacy_vacuum.py | 9 ----- tests/components/mqtt/test_light.py | 15 +------- tests/components/mqtt/test_light_json.py | 8 ----- tests/components/mqtt/test_light_template.py | 8 ----- tests/components/mqtt/test_lock.py | 8 ----- tests/components/mqtt/test_mixins.py | 4 --- tests/components/mqtt/test_number.py | 8 ----- tests/components/mqtt/test_scene.py | 9 +---- tests/components/mqtt/test_select.py | 14 +------- tests/components/mqtt/test_sensor.py | 8 ----- tests/components/mqtt/test_siren.py | 8 ----- tests/components/mqtt/test_subscription.py | 9 +---- tests/components/mqtt/test_switch.py | 8 ----- tests/components/mqtt/test_tag.py | 8 ----- tests/components/mqtt/test_text.py | 14 +------- tests/components/mqtt/test_trigger.py | 9 +---- tests/components/mqtt/test_update.py | 15 +------- tests/components/mqtt/test_util.py | 2 -- tests/components/mqtt/test_vacuum.py | 9 +---- tests/components/mqtt/test_valve.py | 8 ----- tests/components/mqtt/test_water_heater.py | 9 +---- tests/conftest.py | 3 +- 40 files changed, 20 insertions(+), 403 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 397e530cb4b..3b34ef79291 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -84,7 +84,6 @@ from .const import ( # noqa: F401 DOMAIN, MQTT_CONNECTED, MQTT_DISCONNECTED, - PLATFORMS, RELOADABLE_PLATFORMS, TEMPLATE_ERRORS, ) diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 7f97910961d..82320cd2f11 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -142,34 +142,6 @@ MQTT_DISCONNECTED = "mqtt_disconnected" PAYLOAD_EMPTY_JSON = "{}" PAYLOAD_NONE = "None" -PLATFORMS = [ - Platform.ALARM_CONTROL_PANEL, - Platform.BINARY_SENSOR, - Platform.BUTTON, - Platform.CAMERA, - Platform.CLIMATE, - Platform.COVER, - Platform.DEVICE_TRACKER, - Platform.EVENT, - Platform.FAN, - Platform.HUMIDIFIER, - Platform.IMAGE, - Platform.LAWN_MOWER, - Platform.LIGHT, - Platform.LOCK, - Platform.NUMBER, - Platform.SCENE, - Platform.SELECT, - Platform.SENSOR, - Platform.SIREN, - Platform.SWITCH, - Platform.TEXT, - Platform.UPDATE, - Platform.VACUUM, - Platform.VALVE, - Platform.WATER_HEATER, -] - RELOADABLE_PLATFORMS = [ Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR, diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index dfe8d21a23b..055724a4659 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -35,7 +35,6 @@ from homeassistant.const import ( STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -135,15 +134,6 @@ DEFAULT_CONFIG_REMOTE_CODE_TEXT = { } -@pytest.fixture(autouse=True) -def alarm_control_panel_platform_only(): - """Only setup the alarm_control_panel platform to speed up tests.""" - with patch( - "homeassistant.components.mqtt.PLATFORMS", [Platform.ALARM_CONTROL_PANEL] - ): - yield - - @pytest.mark.parametrize( ("hass_config", "valid"), [ diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index c9c76bff67c..995aadd7dba 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -18,7 +18,6 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers.typing import ConfigType @@ -72,13 +71,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def binary_sensor_platform_only(): - """Only setup the binary_sensor platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_button.py b/tests/components/mqtt/test_button.py index fd070e0dd11..3d5d295d4d4 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -7,12 +7,7 @@ from unittest.mock import patch import pytest from homeassistant.components import button, mqtt -from homeassistant.const import ( - ATTR_ENTITY_ID, - ATTR_FRIENDLY_NAME, - STATE_UNKNOWN, - Platform, -) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_UNKNOWN from homeassistant.core import HomeAssistant from .test_common import ( @@ -50,13 +45,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def button_platform_only(): - """Only setup the button platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BUTTON]): - yield - - @pytest.mark.freeze_time("2021-11-08 13:31:44+00:00") @pytest.mark.parametrize( "hass_config", diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index e0c781701ab..fb0107d6780 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -9,7 +9,6 @@ import pytest from homeassistant.components import camera, mqtt from homeassistant.components.mqtt.camera import MQTT_CAMERA_ATTRIBUTES_BLOCKED -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from .test_common import ( @@ -49,13 +48,6 @@ from tests.typing import ( DEFAULT_CONFIG = {mqtt.DOMAIN: {camera.DOMAIN: {"name": "test", "topic": "test_topic"}}} -@pytest.fixture(autouse=True) -def camera_platform_only(): - """Only setup the camera platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.CAMERA]): - yield - - @pytest.mark.parametrize( "hass_config", [{mqtt.DOMAIN: {camera.DOMAIN: {"topic": "test/camera", "name": "Test Camera"}}}], diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 249715a9085..1224fce098d 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -32,7 +32,7 @@ from homeassistant.components.mqtt.climate import ( MQTT_CLIMATE_ATTRIBUTES_BLOCKED, VALUE_TEMPLATE_KEYS, ) -from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError @@ -99,13 +99,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def climate_platform_only(): - """Only setup the climate platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.CLIMATE]): - yield - - @pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG]) async def test_setup_params( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 793e47f1a17..b2b1d1bd9c6 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -43,7 +43,6 @@ from homeassistant.const import ( STATE_OPEN, STATE_OPENING, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant @@ -86,13 +85,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def cover_platform_only(): - """Only setup the cover platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.COVER]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_device_tracker.py b/tests/components/mqtt/test_device_tracker.py index 1d975b06f75..680c48d13c7 100644 --- a/tests/components/mqtt/test_device_tracker.py +++ b/tests/components/mqtt/test_device_tracker.py @@ -1,14 +1,13 @@ """The tests for the MQTT device_tracker platform.""" from datetime import UTC, datetime -from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.components import device_tracker, mqtt from homeassistant.components.mqtt.const import DOMAIN as MQTT_DOMAIN -from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN, Platform +from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component @@ -37,13 +36,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def device_tracker_platform_only(): - """Only setup the device_tracker platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.DEVICE_TRACKER]): - yield - - async def test_discover_device_tracker( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index e4e3149559a..4f4c9a18bd9 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -1,7 +1,6 @@ """The tests for MQTT device triggers.""" import json -from unittest.mock import patch import pytest from pytest_unordered import unordered @@ -9,7 +8,6 @@ from pytest_unordered import unordered import homeassistant.components.automation as automation from homeassistant.components.device_automation import DeviceAutomationType from homeassistant.components.mqtt import _LOGGER, DOMAIN, debug_info -from homeassistant.const import Platform from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr @@ -37,16 +35,6 @@ def calls(hass: HomeAssistant) -> list[ServiceCall]: return async_mock_service(hass, "test", "automation") -@pytest.fixture(autouse=True) -def binary_sensor_and_sensor_only(): - """Only setup the binary_sensor and sensor platform to speed up tests.""" - with patch( - "homeassistant.components.mqtt.PLATFORMS", - [Platform.BINARY_SENSOR, Platform.SENSOR], - ): - yield - - async def test_get_triggers( hass: HomeAssistant, device_registry: dr.DeviceRegistry, diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index e09b32de2d4..9ca903b88b7 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -1,12 +1,11 @@ """Test MQTT diagnostics.""" import json -from unittest.mock import ANY, patch +from unittest.mock import ANY import pytest from homeassistant.components import mqtt -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr @@ -23,16 +22,6 @@ default_config = { } -@pytest.fixture(autouse=True) -def device_tracker_sensor_only(): - """Only setup the device_tracker and sensor platforms to speed up tests.""" - with patch( - "homeassistant.components.mqtt.PLATFORMS", - [Platform.DEVICE_TRACKER, Platform.SENSOR], - ): - yield - - async def test_entry_diagnostics( hass: HomeAssistant, device_registry: dr.DeviceRegistry, diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 6121e1dc15e..e1fbb95791e 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -69,7 +69,6 @@ async def test_subscribing_config_topic( assert discovery_topic + "/+/+/+/config" in topics -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) @pytest.mark.parametrize( ("topic", "log"), [ @@ -103,7 +102,6 @@ async def test_invalid_topic( caplog.clear() -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_invalid_json( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -124,7 +122,9 @@ async def test_invalid_json( assert not mock_dispatcher_send.called -@pytest.mark.parametrize("domain", [*list(mqtt.PLATFORMS), "device_automation", "tag"]) +@pytest.mark.parametrize( + "domain", ["tag", "device_automation", Platform.SENSOR, Platform.LIGHT] +) @pytest.mark.no_fail_on_log_exception async def test_discovery_schema_error( hass: HomeAssistant, @@ -147,7 +147,6 @@ async def test_discovery_schema_error( assert "AttributeError: Attribute abc not found" in caplog.text -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.ALARM_CONTROL_PANEL]) async def test_invalid_config( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -191,7 +190,6 @@ async def test_only_valid_components( assert not mock_dispatcher_send.called -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_correct_config_discovery( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -212,7 +210,6 @@ async def test_correct_config_discovery( assert ("binary_sensor", "bla") in hass.data["mqtt"].discovery_already_discovered -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_discovery_integration_info( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -265,7 +262,6 @@ async def test_discovery_integration_info( '{ "name": "Beer", "state_topic": "test-topic", "o": {"sw": "bla2mqtt"} }', ], ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_discovery_with_invalid_integration_info( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -289,7 +285,6 @@ async def test_discovery_with_invalid_integration_info( ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.FAN]) async def test_discover_fan( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -310,7 +305,6 @@ async def test_discover_fan( assert ("fan", "bla") in hass.data["mqtt"].discovery_already_discovered -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.CLIMATE]) async def test_discover_climate( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -334,7 +328,6 @@ async def test_discover_climate( assert ("climate", "bla") in hass.data["mqtt"].discovery_already_discovered -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.ALARM_CONTROL_PANEL]) async def test_discover_alarm_control_panel( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -525,7 +518,6 @@ async def test_discovery_with_object_id( assert (domain, "object bla") in hass.data["mqtt"].discovery_already_discovered -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_discovery_incl_nodeid( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -548,7 +540,6 @@ async def test_discovery_incl_nodeid( ].discovery_already_discovered -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_non_duplicate_discovery( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -577,7 +568,6 @@ async def test_non_duplicate_discovery( assert "Component has already been discovered: binary_sensor bla" in caplog.text -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_removal( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -599,7 +589,6 @@ async def test_removal( assert state is None -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_rediscover( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -630,7 +619,6 @@ async def test_rediscover( assert state is not None -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_rapid_rediscover( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -683,7 +671,6 @@ async def test_rapid_rediscover( assert events[4].data["old_state"] is None -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_rapid_rediscover_unique( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -746,7 +733,6 @@ async def test_rapid_rediscover_unique( assert events[3].data["old_state"] is None -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_rapid_reconfigure( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -802,7 +788,6 @@ async def test_rapid_reconfigure( assert events[2].data["new_state"].attributes["friendly_name"] == "Wine" -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_duplicate_removal( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -888,7 +873,6 @@ async def test_cleanup_device( ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_cleanup_device_mqtt( hass: HomeAssistant, device_registry: dr.DeviceRegistry, @@ -934,7 +918,6 @@ async def test_cleanup_device_mqtt( mqtt_mock.async_publish.assert_not_called() -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_cleanup_device_multiple_config_entries( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, @@ -1126,7 +1109,6 @@ async def test_cleanup_device_multiple_config_entries_mqtt( mqtt_mock.async_publish.assert_not_called() -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SWITCH]) async def test_discovery_expansion( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1189,7 +1171,6 @@ async def test_discovery_expansion( assert state and state.state == STATE_UNAVAILABLE -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SWITCH]) async def test_discovery_expansion_2( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1234,7 +1215,6 @@ async def test_discovery_expansion_2( assert state.state == STATE_UNKNOWN -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SWITCH]) async def test_discovery_expansion_3( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1319,7 +1299,6 @@ async def test_discovery_expansion_without_encoding_and_value_template_1( assert state and state.state == STATE_UNAVAILABLE -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SWITCH]) async def test_discovery_expansion_without_encoding_and_value_template_2( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1433,7 +1412,6 @@ async def test_missing_discover_abbreviations( assert not missing -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SWITCH]) async def test_no_implicit_state_topic_switch( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1458,7 +1436,6 @@ async def test_no_implicit_state_topic_switch( assert state and state.state == STATE_UNKNOWN -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) @pytest.mark.parametrize( "mqtt_config_entry_data", [ @@ -1490,7 +1467,6 @@ async def test_complex_discovery_topic_prefix( ].discovery_already_discovered -@patch("homeassistant.components.mqtt.PLATFORMS", []) @patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.UNSUBSCRIBE_COOLDOWN", 0.0) @@ -1540,7 +1516,6 @@ async def test_mqtt_integration_discovery_subscribe_unsubscribe( mqtt_client_mock.unsubscribe.assert_called_once_with(["comp/discovery/#"]) -@patch("homeassistant.components.mqtt.PLATFORMS", []) @patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.UNSUBSCRIBE_COOLDOWN", 0.0) @@ -1583,7 +1558,6 @@ async def test_mqtt_discovery_unsubscribe_once( mqtt_client_mock.unsubscribe.assert_called_once_with(["comp/discovery/#"]) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_clear_config_topic_disabled_entity( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1658,7 +1632,6 @@ async def test_clear_config_topic_disabled_entity( ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_clean_up_registry_monitoring( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1713,7 +1686,6 @@ async def test_clean_up_registry_monitoring( assert len(hooks) == 0 -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_unique_id_collission_has_priority( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -1760,7 +1732,6 @@ async def test_unique_id_collission_has_priority( assert entity_registry.async_get("sensor.abc123_sbfspot_12345_2") is None -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_update_with_bad_config_not_breaks_discovery( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator ) -> None: diff --git a/tests/components/mqtt/test_event.py b/tests/components/mqtt/test_event.py index bcfbb1d8d40..488612a0fa3 100644 --- a/tests/components/mqtt/test_event.py +++ b/tests/components/mqtt/test_event.py @@ -9,7 +9,7 @@ import pytest from homeassistant.components import event, mqtt from homeassistant.components.mqtt.event import MQTT_EVENT_ATTRIBUTES_BLOCKED -from homeassistant.const import STATE_UNKNOWN, Platform +from homeassistant.const import STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr @@ -65,13 +65,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def event_platform_only(): - """Only setup the event platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.EVENT]): - yield - - @pytest.mark.freeze_time("2023-08-01 00:00:00+00:00") @pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG]) async def test_setting_event_value_via_mqtt_message( diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index 493e1e5ddf7..0dbfa3037b2 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -33,7 +33,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant @@ -83,13 +82,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def fan_platform_only(): - """Only setup the fan platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.FAN]): - yield - - @pytest.mark.parametrize("hass_config", [{mqtt.DOMAIN: {fan.DOMAIN: {"name": "test"}}}]) async def test_fail_setup_if_no_command_topic( hass: HomeAssistant, diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index 0778d36f873..75baca046bd 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -34,7 +34,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant @@ -84,13 +83,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def humidifer_platform_only(): - """Only setup the humidifer platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.HUMIDIFIER]): - yield - - async def async_turn_on( hass: HomeAssistant, entity_id=ENTITY_MATCH_ALL, diff --git a/tests/components/mqtt/test_image.py b/tests/components/mqtt/test_image.py index aa17b878e1c..2f1676c0c03 100644 --- a/tests/components/mqtt/test_image.py +++ b/tests/components/mqtt/test_image.py @@ -11,7 +11,7 @@ import pytest import respx from homeassistant.components import image, mqtt -from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant from .test_common import ( @@ -54,13 +54,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def image_platform_only(): - """Only setup the image platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.IMAGE]): - yield - - @pytest.mark.freeze_time("2023-04-01 00:00:00+00:00") @pytest.mark.parametrize( "hass_config", diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 60a909395f1..71b103c8bcc 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1,7 +1,6 @@ """The tests for the MQTT component.""" import asyncio -from collections.abc import Generator from copy import deepcopy from datetime import datetime, timedelta from functools import partial @@ -32,7 +31,6 @@ from homeassistant.const import ( SERVICE_RELOAD, STATE_UNAVAILABLE, STATE_UNKNOWN, - Platform, UnitOfTemperature, ) import homeassistant.core as ha @@ -92,16 +90,6 @@ class RecordCallsPartial(partial[Any]): __name__ = "RecordCallPartialTest" -@pytest.fixture(autouse=True) -def sensor_platforms_only() -> Generator[None, None, None]: - """Only setup the sensor platforms to speed up tests.""" - with patch( - "homeassistant.components.mqtt.PLATFORMS", - [Platform.SENSOR, Platform.BINARY_SENSOR], - ): - yield - - @pytest.fixture(autouse=True) def mock_storage(hass_storage: dict[str, Any]) -> None: """Autouse hass_storage for the TestCase tests.""" @@ -168,7 +156,6 @@ async def test_mqtt_disconnects_on_home_assistant_stop( assert mqtt_client_mock.loop_stop.call_count == 1 -@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_mqtt_await_ack_at_disconnect( hass: HomeAssistant, ) -> None: @@ -312,7 +299,6 @@ async def test_command_template_value(hass: HomeAssistant) -> None: assert cmd_tpl.async_render(None, variables=variables) == "beer" -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SELECT]) @pytest.mark.parametrize( "config", [ @@ -2162,7 +2148,6 @@ async def test_handle_message_callback( } ], ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) async def test_setup_manual_mqtt_with_platform_key( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -2177,7 +2162,6 @@ async def test_setup_manual_mqtt_with_platform_key( @pytest.mark.parametrize("hass_config", [{mqtt.DOMAIN: {"light": {"name": "test"}}}]) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) async def test_setup_manual_mqtt_with_invalid_config( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -2188,7 +2172,6 @@ async def test_setup_manual_mqtt_with_invalid_config( assert "required key not provided" in caplog.text -@patch("homeassistant.components.mqtt.PLATFORMS", []) @pytest.mark.parametrize( ("mqtt_config_entry_data", "protocol"), [ @@ -2229,7 +2212,6 @@ async def test_setup_mqtt_client_protocol( @patch("homeassistant.components.mqtt.client.TIMEOUT_ACK", 0.2) -@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_handle_mqtt_timeout_on_callback( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: @@ -2300,7 +2282,6 @@ async def test_setup_raises_config_entry_not_ready_if_no_connect_broker( ({"broker": "test-broker", "certificate": "auto", "tls_insecure": True}, True), ], ) -@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_uses_certificate_on_certificate_set_to_auto_and_insecure( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -2921,7 +2902,6 @@ async def test_mqtt_ws_get_device_debug_info( assert response["result"] == expected_result -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.CAMERA]) async def test_mqtt_ws_get_device_debug_info_binary( hass: HomeAssistant, device_registry: dr.DeviceRegistry, @@ -3575,7 +3555,6 @@ async def test_unload_config_entry( assert "No ACK from MQTT server" not in caplog.text -@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_publish_or_subscribe_without_valid_config_entry( hass: HomeAssistant, record_calls: MessageCallbackType ) -> None: @@ -3588,10 +3567,6 @@ async def test_publish_or_subscribe_without_valid_config_entry( await mqtt.async_subscribe(hass, "some-topic", record_calls, qos=0) -@patch( - "homeassistant.components.mqtt.PLATFORMS", - [Platform.ALARM_CONTROL_PANEL, Platform.LIGHT], -) @pytest.mark.parametrize( "hass_config", [ @@ -3666,7 +3641,6 @@ async def test_disabling_and_enabling_entry( assert hass.states.get("alarm_control_panel.test") is not None -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) @pytest.mark.parametrize( ("hass_config", "unique"), [ diff --git a/tests/components/mqtt/test_lawn_mower.py b/tests/components/mqtt/test_lawn_mower.py index a6e338c17da..a95c613cdc4 100644 --- a/tests/components/mqtt/test_lawn_mower.py +++ b/tests/components/mqtt/test_lawn_mower.py @@ -16,12 +16,7 @@ from homeassistant.components.lawn_mower import ( LawnMowerEntityFeature, ) from homeassistant.components.mqtt.lawn_mower import MQTT_LAWN_MOWER_ATTRIBUTES_BLOCKED -from homeassistant.const import ( - ATTR_ASSUMED_STATE, - ATTR_ENTITY_ID, - STATE_UNKNOWN, - Platform, -) +from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State from .test_common import ( @@ -79,13 +74,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def lawn_mower_platform_only(): - """Only setup the lawn_mower platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LAWN_MOWER]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index 3e88d4a4335..e4f5e3cd481 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -5,12 +5,10 @@ # cleanup is planned with HA Core 2025.2 import json -from unittest.mock import patch import pytest from homeassistant.components import mqtt, vacuum -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import DiscoveryInfoType @@ -20,13 +18,6 @@ from tests.typing import MqttMockHAClientGenerator DEFAULT_CONFIG = {mqtt.DOMAIN: {vacuum.DOMAIN: {"name": "test"}}} -@pytest.fixture(autouse=True) -def vacuum_platform_only(): - """Only setup the vacuum platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.VACUUM]): - yield - - @pytest.mark.parametrize( ("hass_config", "removed"), [ diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 749b5c5403a..492bc6806da 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -190,13 +190,7 @@ from homeassistant.components.mqtt.light.schema_basic import ( VALUE_TEMPLATE_KEYS, ) from homeassistant.components.mqtt.models import PublishPayloadType -from homeassistant.const import ( - ATTR_ASSUMED_STATE, - STATE_OFF, - STATE_ON, - STATE_UNKNOWN, - Platform, -) +from homeassistant.const import ATTR_ASSUMED_STATE, STATE_OFF, STATE_ON, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State from .test_common import ( @@ -239,13 +233,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def light_platform_only(): - """Only setup the light platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]): - yield - - @pytest.mark.parametrize( "hass_config", [{mqtt.DOMAIN: {light.DOMAIN: {"name": "test"}}}] ) diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 796ee9ef9d2..8adbf05e46c 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -96,7 +96,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant, State from homeassistant.helpers.json import json_dumps @@ -170,13 +169,6 @@ COLOR_MODES_CONFIG = { } -@pytest.fixture(autouse=True) -def light_platform_only(): - """Only setup the light platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]): - yield - - class JsonValidator: """Helper to compare JSON.""" diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index f562a11bb95..da6195fa32e 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -42,7 +42,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant, State @@ -94,13 +93,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def light_platform_only(): - """Only setup the light platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index 5a794f6082e..a52d1ab42f4 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -24,7 +24,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant @@ -83,13 +82,6 @@ CONFIG_WITH_STATES = { } -@pytest.fixture(autouse=True) -def lock_platform_only(): - """Only setup the lock platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LOCK]): - yield - - @pytest.mark.parametrize( ("hass_config", "payload", "lock_state"), [ diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 7b4b10ad776..2bcd663c243 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -10,7 +10,6 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, EVENT_HOMEASSISTANT_STARTED, EVENT_STATE_CHANGED, - Platform, ) from homeassistant.core import CoreState, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, issue_registry as ir @@ -37,7 +36,6 @@ from tests.typing import MqttMockHAClientGenerator, MqttMockPahoClient } ], ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) async def test_availability_with_shared_state_topic( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, @@ -296,7 +294,6 @@ async def test_availability_with_shared_state_topic( "entity_name_startswith_device_name2", ], ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) @patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0) async def test_default_entity_and_device_name( hass: HomeAssistant, @@ -340,7 +337,6 @@ async def test_default_entity_and_device_name( assert len(events) == 0 -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) async def test_name_attribute_is_set_or_not( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, diff --git a/tests/components/mqtt/test_number.py b/tests/components/mqtt/test_number.py index 40d7cd60df5..b0f9e79cb3e 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -26,7 +26,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, ATTR_UNIT_OF_MEASUREMENT, - Platform, UnitOfTemperature, ) from homeassistant.core import HomeAssistant, State @@ -71,13 +70,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def number_platform_only(): - """Only setup the number platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.NUMBER]): - yield - - @pytest.mark.parametrize( ("hass_config", "device_class", "unit_of_measurement", "values"), [ diff --git a/tests/components/mqtt/test_scene.py b/tests/components/mqtt/test_scene.py index 67fd794c906..3e9eacd3be2 100644 --- a/tests/components/mqtt/test_scene.py +++ b/tests/components/mqtt/test_scene.py @@ -7,7 +7,7 @@ from unittest.mock import patch import pytest from homeassistant.components import mqtt, scene -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNKNOWN, Platform +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State from .test_common import ( @@ -51,13 +51,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def scene_platform_only(): - """Only setup the scene platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SCENE]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_select.py b/tests/components/mqtt/test_select.py index b50843951b1..e5e1352abb7 100644 --- a/tests/components/mqtt/test_select.py +++ b/tests/components/mqtt/test_select.py @@ -16,12 +16,7 @@ from homeassistant.components.select import ( DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, ) -from homeassistant.const import ( - ATTR_ASSUMED_STATE, - ATTR_ENTITY_ID, - STATE_UNKNOWN, - Platform, -) +from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import HomeAssistant, State from homeassistant.helpers.typing import ConfigType @@ -71,13 +66,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def select_platform_only(): - """Only setup the select platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SELECT]): - yield - - def _test_run_select_setup_params( topic: str, ) -> Generator[tuple[ConfigType, str], None]: diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 0345b76c241..d88bd3a544a 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -17,7 +17,6 @@ from homeassistant.const import ( EVENT_STATE_CHANGED, STATE_UNAVAILABLE, STATE_UNKNOWN, - Platform, UnitOfTemperature, ) from homeassistant.core import Event, HomeAssistant, State, callback @@ -81,13 +80,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def sensor_platform_only(): - """Only setup the sensor platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index c583c4dfe68..c2cb39cca0a 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -17,7 +17,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant @@ -60,13 +59,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def siren_platform_only(): - """Only setup the siren platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SIREN]): - yield - - async def async_turn_on( hass: HomeAssistant, entity_id: str = ENTITY_MATCH_ALL, diff --git a/tests/components/mqtt/test_subscription.py b/tests/components/mqtt/test_subscription.py index 5196c58192d..54acc935f1d 100644 --- a/tests/components/mqtt/test_subscription.py +++ b/tests/components/mqtt/test_subscription.py @@ -1,6 +1,6 @@ """The tests for the MQTT subscription component.""" -from unittest.mock import ANY, patch +from unittest.mock import ANY import pytest @@ -15,13 +15,6 @@ from tests.common import async_fire_mqtt_message from tests.typing import MqttMockHAClientGenerator -@pytest.fixture(autouse=True) -def no_platforms(): - """Skip platform setup to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", []): - yield - - async def test_subscribe_topics( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index fff96b290ff..b497d4a2f52 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -13,7 +13,6 @@ from homeassistant.const import ( STATE_OFF, STATE_ON, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant, State @@ -57,13 +56,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def switch_platform_only(): - """Only setup the switch platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SWITCH]): - yield - - @pytest.mark.parametrize( ("hass_config", "device_class"), [ diff --git a/tests/components/mqtt/test_tag.py b/tests/components/mqtt/test_tag.py index d374d816a0d..9a0da989216 100644 --- a/tests/components/mqtt/test_tag.py +++ b/tests/components/mqtt/test_tag.py @@ -9,7 +9,6 @@ import pytest from homeassistant.components.device_automation import DeviceAutomationType from homeassistant.components.mqtt.const import DOMAIN as MQTT_DOMAIN -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr from homeassistant.setup import async_setup_component @@ -47,13 +46,6 @@ DEFAULT_TAG_SCAN_JSON = ( ) -@pytest.fixture(autouse=True) -def binary_sensor_only() -> Generator[None, None, None]: - """Only setup the binary_sensor platform to speed up test.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]): - yield - - @pytest.fixture def tag_mock() -> Generator[AsyncMock, None, None]: """Fixture to mock tag.""" diff --git a/tests/components/mqtt/test_text.py b/tests/components/mqtt/test_text.py index 8966ea9de56..63c69d3cfac 100644 --- a/tests/components/mqtt/test_text.py +++ b/tests/components/mqtt/test_text.py @@ -8,12 +8,7 @@ from unittest.mock import patch import pytest from homeassistant.components import mqtt, text -from homeassistant.const import ( - ATTR_ASSUMED_STATE, - ATTR_ENTITY_ID, - STATE_UNKNOWN, - Platform, -) +from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.core import HomeAssistant from .test_common import ( @@ -55,13 +50,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def text_platform_only(): - """Only setup the text platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.TEXT]): - yield - - async def async_set_value( hass: HomeAssistant, entity_id: str, value: str | None ) -> None: diff --git a/tests/components/mqtt/test_trigger.py b/tests/components/mqtt/test_trigger.py index 756b8d022e0..90a39bfd4fb 100644 --- a/tests/components/mqtt/test_trigger.py +++ b/tests/components/mqtt/test_trigger.py @@ -1,6 +1,6 @@ """The tests for the MQTT automation.""" -from unittest.mock import ANY, patch +from unittest.mock import ANY import pytest @@ -23,13 +23,6 @@ def calls(hass: HomeAssistant): return async_mock_service(hass, "test", "automation") -@pytest.fixture(autouse=True) -def no_platforms(): - """Skip platform setup to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", []): - yield - - @pytest.fixture(autouse=True) async def setup_comp(hass: HomeAssistant, mqtt_mock_entry): """Initialize components.""" diff --git a/tests/components/mqtt/test_update.py b/tests/components/mqtt/test_update.py index 2fcfbb59952..bb80a0c274f 100644 --- a/tests/components/mqtt/test_update.py +++ b/tests/components/mqtt/test_update.py @@ -7,13 +7,7 @@ import pytest from homeassistant.components import mqtt, update from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL -from homeassistant.const import ( - ATTR_ENTITY_ID, - STATE_OFF, - STATE_ON, - STATE_UNKNOWN, - Platform, -) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNKNOWN from homeassistant.core import HomeAssistant from .test_common import ( @@ -58,13 +52,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def update_platform_only(): - """Only setup the update platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.UPDATE]): - yield - - @pytest.mark.parametrize( ("hass_config", "device_class"), [ diff --git a/tests/components/mqtt/test_util.py b/tests/components/mqtt/test_util.py index 14153b44d87..0b8d7002319 100644 --- a/tests/components/mqtt/test_util.py +++ b/tests/components/mqtt/test_util.py @@ -134,7 +134,6 @@ async def test_return_default_get_file_path( assert await hass.async_add_executor_job(_get_file_path, tempdir) -@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_waiting_for_client_not_loaded( hass: HomeAssistant, mqtt_client_mock: MqttMockPahoClient, @@ -172,7 +171,6 @@ async def test_waiting_for_client_not_loaded( unsub() -@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_waiting_for_client_loaded( hass: HomeAssistant, mqtt_mock: MqttMockHAClient, diff --git a/tests/components/mqtt/test_vacuum.py b/tests/components/mqtt/test_vacuum.py index a862ba3143d..7563752b2d7 100644 --- a/tests/components/mqtt/test_vacuum.py +++ b/tests/components/mqtt/test_vacuum.py @@ -32,7 +32,7 @@ from homeassistant.components.vacuum import ( STATE_CLEANING, STATE_DOCKED, ) -from homeassistant.const import CONF_NAME, ENTITY_MATCH_ALL, STATE_UNKNOWN, Platform +from homeassistant.const import CONF_NAME, ENTITY_MATCH_ALL, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -103,13 +103,6 @@ CONFIG_ALL_SERVICES = help_custom_config( ) -@pytest.fixture(autouse=True) -def vacuum_platform_only(): - """Only setup the vacuum platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.VACUUM]): - yield - - @pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG]) async def test_default_supported_features( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator diff --git a/tests/components/mqtt/test_valve.py b/tests/components/mqtt/test_valve.py index bc0676f5aa4..7fd9b10c005 100644 --- a/tests/components/mqtt/test_valve.py +++ b/tests/components/mqtt/test_valve.py @@ -27,7 +27,6 @@ from homeassistant.const import ( STATE_OPEN, STATE_OPENING, STATE_UNKNOWN, - Platform, ) from homeassistant.core import HomeAssistant @@ -87,13 +86,6 @@ DEFAULT_CONFIG_REPORTS_POSITION = { } -@pytest.fixture(autouse=True) -def valve_platform_only(): - """Only setup the valve platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.VALVE]): - yield - - @pytest.mark.parametrize( "hass_config", [ diff --git a/tests/components/mqtt/test_water_heater.py b/tests/components/mqtt/test_water_heater.py index 945a2296435..ee0aa1c0949 100644 --- a/tests/components/mqtt/test_water_heater.py +++ b/tests/components/mqtt/test_water_heater.py @@ -25,7 +25,7 @@ from homeassistant.components.water_heater import ( STATE_PERFORMANCE, WaterHeaterEntityFeature, ) -from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, Platform, UnitOfTemperature +from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.util.unit_conversion import TemperatureConverter @@ -96,13 +96,6 @@ DEFAULT_CONFIG = { } -@pytest.fixture(autouse=True) -def water_heater_platform_only(): - """Only setup the water heater platform to speed up tests.""" - with patch("homeassistant.components.mqtt.PLATFORMS", [Platform.WATER_HEATER]): - yield - - @pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG]) async def test_setup_params( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator diff --git a/tests/conftest.py b/tests/conftest.py index 798e3980f7b..40bd4157957 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -935,8 +935,7 @@ async def mqtt_mock( mqtt_mock_entry: MqttMockHAClientGenerator, ) -> AsyncGenerator[MqttMockHAClient, None]: """Fixture to mock MQTT component.""" - with patch("homeassistant.components.mqtt.PLATFORMS", []): - return await mqtt_mock_entry() + return await mqtt_mock_entry() @asynccontextmanager From 1ffc459aa7bda990062e7c055dcbb05daf99a1c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 18:00:53 -1000 Subject: [PATCH 0631/1691] Only read cpu once during systemmonitor setup (#112863) * Only read cpu once during systemmonitor setup * type --- .../components/systemmonitor/sensor.py | 29 ++++++++----------- .../components/systemmonitor/util.py | 7 +---- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 94fc1719c4a..fb04ac815b9 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Callable +import contextlib from dataclasses import dataclass from datetime import datetime from functools import lru_cache @@ -71,13 +72,6 @@ def get_cpu_icon() -> Literal["mdi:cpu-64-bit", "mdi:cpu-32-bit"]: return "mdi:cpu-32-bit" -def get_processor_temperature( - entity: SystemMonitorSensor, -) -> float | None: - """Return processor temperature.""" - return read_cpu_temperature(entity.hass, entity.coordinator.data.temperatures) - - def get_process(entity: SystemMonitorSensor) -> str: """Return process.""" state = STATE_OFF @@ -374,7 +368,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription] = { native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - value_fn=get_processor_temperature, + value_fn=lambda entity: read_cpu_temperature( + entity.coordinator.data.temperatures + ), none_is_unavailable=True, add_to_update=lambda entity: ("temperatures", ""), ), @@ -506,22 +502,21 @@ async def async_setup_entry( # noqa: C901 legacy_resources: set[str] = set(entry.options.get("resources", [])) loaded_resources: set[str] = set() coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR] + sensor_data = coordinator.data def get_arguments() -> dict[str, Any]: """Return startup information.""" - disk_arguments = get_all_disk_mounts(hass) - network_arguments = get_all_network_interfaces(hass) - try: - cpu_temperature = read_cpu_temperature(hass) - except AttributeError: - cpu_temperature = 0.0 return { - "disk_arguments": disk_arguments, - "network_arguments": network_arguments, - "cpu_temperature": cpu_temperature, + "disk_arguments": get_all_disk_mounts(hass), + "network_arguments": get_all_network_interfaces(hass), } + cpu_temperature: float | None = None + with contextlib.suppress(AttributeError): + cpu_temperature = read_cpu_temperature(sensor_data.temperatures) + startup_arguments = await hass.async_add_executor_job(get_arguments) + startup_arguments["cpu_temperature"] = cpu_temperature _LOGGER.debug("Setup from options %s", entry.options) diff --git a/homeassistant/components/systemmonitor/util.py b/homeassistant/components/systemmonitor/util.py index 75f8d2b2df1..1889e443b2d 100644 --- a/homeassistant/components/systemmonitor/util.py +++ b/homeassistant/components/systemmonitor/util.py @@ -77,13 +77,8 @@ def get_all_running_processes(hass: HomeAssistant) -> set[str]: return processes -def read_cpu_temperature( - hass: HomeAssistant, temps: dict[str, list[shwtemp]] | None = None -) -> float | None: +def read_cpu_temperature(temps: dict[str, list[shwtemp]]) -> float | None: """Attempt to read CPU / processor temperature.""" - if temps is None: - psutil_wrapper: ha_psutil = hass.data[DOMAIN] - temps = psutil_wrapper.psutil.sensors_temperatures() entry: shwtemp _LOGGER.debug("CPU Temperatures: %s", temps) From 0ad14da40817bcd5a4db31346bf386b08e64bf94 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 18:02:42 -1000 Subject: [PATCH 0632/1691] Fix MJPEG fallback when still image URL is missing with basic auth (#112861) * Fix MJPEG fallback when still image URL is missing with basic auth I picked up an old DCS-930L (circa 2010) camera to test with to fix #94877 * Fix MJPEG fallback when still image URL is missing with basic auth I picked up an old DCS-930L (circa 2010) camera to test with to fix #94877 * Fix MJPEG fallback when still image URL is missing with basic auth I picked up an old DCS-930L (circa 2010) camera to test with to fix #94877 * Fix MJPEG fallback when still image URL is missing with basic auth I picked up an old DCS-930L (circa 2010) camera to test with to fix #94877 --- homeassistant/components/mjpeg/camera.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mjpeg/camera.py b/homeassistant/components/mjpeg/camera.py index fb8c7e3ef35..ab8c67f2ca9 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -135,12 +135,11 @@ class MjpegCamera(Camera): self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return a still image response from the camera.""" - # DigestAuth is not supported if ( self._authentication == HTTP_DIGEST_AUTHENTICATION or self._still_image_url is None ): - return await self._async_digest_camera_image() + return await self._async_digest_or_fallback_camera_image() websession = async_get_clientsession(self.hass, verify_ssl=self._verify_ssl) try: @@ -158,15 +157,17 @@ class MjpegCamera(Camera): return None - def _get_digest_auth(self) -> httpx.DigestAuth: - """Return a DigestAuth object.""" + def _get_httpx_auth(self) -> httpx.Auth: + """Return a httpx auth object.""" username = "" if self._username is None else self._username - return httpx.DigestAuth(username, self._password) + digest_auth = self._authentication == HTTP_DIGEST_AUTHENTICATION + cls = httpx.DigestAuth if digest_auth else httpx.BasicAuth + return cls(username, self._password) - async def _async_digest_camera_image(self) -> bytes | None: + async def _async_digest_or_fallback_camera_image(self) -> bytes | None: """Return a still image response from the camera using digest authentication.""" client = get_async_client(self.hass, verify_ssl=self._verify_ssl) - auth = self._get_digest_auth() + auth = self._get_httpx_auth() try: if self._still_image_url: # Fallback to MJPEG stream if still image URL is not available @@ -197,7 +198,7 @@ class MjpegCamera(Camera): ) -> web.StreamResponse | None: """Generate an HTTP MJPEG stream from the camera using digest authentication.""" async with get_async_client(self.hass, verify_ssl=self._verify_ssl).stream( - "get", self._mjpeg_url, auth=self._get_digest_auth(), timeout=TIMEOUT + "get", self._mjpeg_url, auth=self._get_httpx_auth(), timeout=TIMEOUT ) as stream: response = web.StreamResponse(headers=stream.headers) await response.prepare(request) From 60bddc286118a11b6c4eff38a0ca0f3722cc51e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 18:04:25 -1000 Subject: [PATCH 0633/1691] Schedule coroutine functions eagerly when async_listen uses run_immediately (#112846) We have a few places where we call async_listen with a callback so we can schedule the coro eagerly. We can drop these in favor of setting run_immediately now. --- homeassistant/core.py | 15 +++++---------- tests/test_core.py | 21 ++++++++++++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 6169df32cfb..8c6a7f230de 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1355,7 +1355,7 @@ class EventBus: continue if run_immediately: try: - job.target(event) + self._hass.async_run_hass_job(job, event, eager_start=True) except Exception: # pylint: disable=broad-except _LOGGER.exception("Error running job: %s", job) else: @@ -1398,23 +1398,18 @@ class EventBus: @callback that returns a boolean value, determines if the listener callable should run. - If run_immediately is passed, the callback will be run - right away instead of using call_soon. Only use this if - the callback results in scheduling another task. + If run_immediately is passed: + - callbacks will be run right away instead of using call_soon. + - coroutine functions will be scheduled eagerly. This method must be run in the event loop. """ - job_type: HassJobType | None = None if event_filter is not None and not is_callback_check_partial(event_filter): raise HomeAssistantError(f"Event filter {event_filter} is not a callback") - if run_immediately: - if not is_callback_check_partial(listener): - raise HomeAssistantError(f"Event listener {listener} is not a callback") - job_type = HassJobType.Callback return self._async_listen_filterable_job( event_type, ( - HassJob(listener, f"listen {event_type}", job_type=job_type), + HassJob(listener, f"listen {event_type}"), event_filter, run_immediately, ), diff --git a/tests/test_core.py b/tests/test_core.py index 37a6251fa68..18e0d352710 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1132,8 +1132,8 @@ async def test_eventbus_filtered_listener(hass: HomeAssistant) -> None: unsub() -async def test_eventbus_run_immediately(hass: HomeAssistant) -> None: - """Test we can call events immediately.""" +async def test_eventbus_run_immediately_callback(hass: HomeAssistant) -> None: + """Test we can call events immediately with a callback.""" calls = [] @ha.callback @@ -1150,14 +1150,21 @@ async def test_eventbus_run_immediately(hass: HomeAssistant) -> None: unsub() -async def test_eventbus_run_immediately_not_callback(hass: HomeAssistant) -> None: - """Test we raise when passing a non-callback with run_immediately.""" +async def test_eventbus_run_immediately_coro(hass: HomeAssistant) -> None: + """Test we can call events immediately with a coro.""" + calls = [] - def listener(event): + async def listener(event): """Mock listener.""" + calls.append(event) - with pytest.raises(HomeAssistantError): - hass.bus.async_listen("test", listener, run_immediately=True) + unsub = hass.bus.async_listen("test", listener, run_immediately=True) + + hass.bus.async_fire("test", {"event": True}) + # No async_block_till_done here + assert len(calls) == 1 + + unsub() async def test_eventbus_unsubscribe_listener(hass: HomeAssistant) -> None: From 57ce0f77ed1128445de3075eca0e8654796f7a11 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 10 Mar 2024 05:08:36 +0100 Subject: [PATCH 0634/1691] Update pytest to 8.1.1 (#112859) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 36e625e5993..7001a60f6bf 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -28,7 +28,7 @@ pytest-timeout==2.2.0 pytest-unordered==0.5.2 pytest-picked==0.5.0 pytest-xdist==3.3.1 -pytest==8.1.0 +pytest==8.1.1 requests-mock==1.11.0 respx==0.20.2 syrupy==4.6.1 From f1b5dcdd1bd9d1118b886d60daa66b97057034c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Mar 2024 20:30:17 -1000 Subject: [PATCH 0635/1691] Refactor handling of device updates in ESPHome (#112864) --- homeassistant/components/esphome/entity.py | 13 +--- .../components/esphome/entry_data.py | 46 +++++++------ homeassistant/components/esphome/manager.py | 4 +- homeassistant/components/esphome/update.py | 10 +-- tests/components/esphome/test_update.py | 65 +++++++------------ 5 files changed, 58 insertions(+), 80 deletions(-) diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index aa98ccef70c..b4cc54b0bb7 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -22,7 +22,6 @@ from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv import homeassistant.helpers.device_registry as dr from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -205,25 +204,19 @@ class EsphomeEntity(Entity, Generic[_InfoT, _StateT]): async def async_added_to_hass(self) -> None: """Register callbacks.""" entry_data = self._entry_data - hass = self.hass - key = self._key - static_info = self._static_info - self.async_on_remove( - async_dispatcher_connect( - hass, - entry_data.signal_device_updated, + entry_data.async_subscribe_device_updated( self._on_device_update, ) ) self.async_on_remove( entry_data.async_subscribe_state_update( - self._state_type, key, self._on_state_update + self._state_type, self._key, self._on_state_update ) ) self.async_on_remove( entry_data.async_register_key_static_info_updated_callback( - static_info, self._on_static_info_update + self._static_info, self._on_static_info_update ) ) self._update_state_from_entry_data() diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 2a34089cbe9..66ac1ac6c05 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -108,18 +108,17 @@ class RuntimeEntryData: device_info: DeviceInfo | None = None bluetooth_device: ESPHomeBluetoothDevice | None = None api_version: APIVersion = field(default_factory=APIVersion) - cleanup_callbacks: list[Callable[[], None]] = field(default_factory=list) - disconnect_callbacks: set[Callable[[], None]] = field(default_factory=set) - state_subscriptions: dict[ - tuple[type[EntityState], int], Callable[[], None] - ] = field(default_factory=dict) + cleanup_callbacks: list[CALLBACK_TYPE] = field(default_factory=list) + disconnect_callbacks: set[CALLBACK_TYPE] = field(default_factory=set) + state_subscriptions: dict[tuple[type[EntityState], int], CALLBACK_TYPE] = field( + default_factory=dict + ) + device_update_subscriptions: set[CALLBACK_TYPE] = field(default_factory=set) loaded_platforms: set[Platform] = field(default_factory=set) platform_load_lock: asyncio.Lock = field(default_factory=asyncio.Lock) _storage_contents: StoreData | None = None _pending_storage: Callable[[], StoreData] | None = None - assist_pipeline_update_callbacks: list[Callable[[], None]] = field( - default_factory=list - ) + assist_pipeline_update_callbacks: list[CALLBACK_TYPE] = field(default_factory=list) assist_pipeline_state: bool = False entity_info_callbacks: dict[ type[EntityInfo], list[Callable[[list[EntityInfo]], None]] @@ -143,11 +142,6 @@ class RuntimeEntryData: "_", " " ) - @property - def signal_device_updated(self) -> str: - """Return the signal to listen to for core device state update.""" - return f"esphome_{self.entry_id}_on_device_update" - @property def signal_static_info_updated(self) -> str: """Return the signal to listen to for updates on static info.""" @@ -216,15 +210,15 @@ class RuntimeEntryData: @callback def async_subscribe_assist_pipeline_update( - self, update_callback: Callable[[], None] - ) -> Callable[[], None]: + self, update_callback: CALLBACK_TYPE + ) -> CALLBACK_TYPE: """Subscribe to assist pipeline updates.""" self.assist_pipeline_update_callbacks.append(update_callback) return partial(self._async_unsubscribe_assist_pipeline_update, update_callback) @callback def _async_unsubscribe_assist_pipeline_update( - self, update_callback: Callable[[], None] + self, update_callback: CALLBACK_TYPE ) -> None: """Unsubscribe to assist pipeline updates.""" self.assist_pipeline_update_callbacks.remove(update_callback) @@ -307,13 +301,24 @@ class RuntimeEntryData: # Then send dispatcher event async_dispatcher_send(hass, self.signal_static_info_updated, infos) + @callback + def async_subscribe_device_updated(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE: + """Subscribe to state updates.""" + self.device_update_subscriptions.add(callback_) + return partial(self._async_unsubscribe_device_update, callback_) + + @callback + def _async_unsubscribe_device_update(self, callback_: CALLBACK_TYPE) -> None: + """Unsubscribe to device updates.""" + self.device_update_subscriptions.remove(callback_) + @callback def async_subscribe_state_update( self, state_type: type[EntityState], state_key: int, - entity_callback: Callable[[], None], - ) -> Callable[[], None]: + entity_callback: CALLBACK_TYPE, + ) -> CALLBACK_TYPE: """Subscribe to state updates.""" subscription_key = (state_type, state_key) self.state_subscriptions[subscription_key] = entity_callback @@ -359,9 +364,10 @@ class RuntimeEntryData: _LOGGER.exception("Error while calling subscription: %s", ex) @callback - def async_update_device_state(self, hass: HomeAssistant) -> None: + def async_update_device_state(self) -> None: """Distribute an update of a core device state like availability.""" - async_dispatcher_send(hass, self.signal_device_updated) + for callback_ in self.device_update_subscriptions.copy(): + callback_() async def async_load_from_store(self) -> tuple[list[EntityInfo], list[UserService]]: """Load the retained data from store and return de-serialized data.""" diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index 27fb2cece89..3848171f806 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -455,7 +455,7 @@ class ESPHomeManager: self.device_id = _async_setup_device_registry(hass, entry, entry_data) - entry_data.async_update_device_state(hass) + entry_data.async_update_device_state() await entry_data.async_update_static_infos( hass, entry, entity_infos, device_info.mac_address ) @@ -510,7 +510,7 @@ class ESPHomeManager: # since it generates a lot of state changed events and database # writes when we already know we're shutting down and the state # will be cleared anyway. - entry_data.async_update_device_state(hass) + entry_data.async_update_device_state() async def on_connect_error(self, err: Exception) -> None: """Start reauth flow if appropriate connect error type.""" diff --git a/homeassistant/components/esphome/update.py b/homeassistant/components/esphome/update.py index 2219900cced..5a565f9914d 100644 --- a/homeassistant/components/esphome/update.py +++ b/homeassistant/components/esphome/update.py @@ -61,9 +61,7 @@ async def async_setup_entry( return unsubs = [ - async_dispatcher_connect( - hass, entry_data.signal_device_updated, _async_setup_update_entity - ), + entry_data.async_subscribe_device_updated(_async_setup_update_entity), dashboard.async_add_listener(_async_setup_update_entity), ] @@ -159,11 +157,7 @@ class ESPHomeUpdateEntity(CoordinatorEntity[ESPHomeDashboard], UpdateEntity): ) ) self.async_on_remove( - async_dispatcher_connect( - hass, - entry_data.signal_device_updated, - self._handle_device_update, - ) + entry_data.async_subscribe_device_updated(self._handle_device_update) ) async def async_install( diff --git a/tests/components/esphome/test_update.py b/tests/components/esphome/test_update.py index 9d5745e6594..d17f2f4623a 100644 --- a/tests/components/esphome/test_update.py +++ b/tests/components/esphome/test_update.py @@ -208,15 +208,25 @@ async def test_update_static_info( @pytest.mark.parametrize( - "expected_disconnect_state", [(True, STATE_ON), (False, STATE_UNAVAILABLE)] + ("expected_disconnect", "expected_state", "has_deep_sleep"), + [ + (True, STATE_ON, False), + (False, STATE_UNAVAILABLE, False), + (True, STATE_ON, True), + (False, STATE_ON, True), + ], ) async def test_update_device_state_for_availability( hass: HomeAssistant, - stub_reconnect, - expected_disconnect_state: tuple[bool, str], - mock_config_entry, - mock_device_info, + expected_disconnect: bool, + expected_state: str, + has_deep_sleep: bool, mock_dashboard, + mock_client: APIClient, + mock_esphome_device: Callable[ + [APIClient, list[EntityInfo], list[UserService], list[EntityState]], + Awaitable[MockESPHomeDevice], + ], ) -> None: """Test ESPHome update entity changes availability with the device.""" mock_dashboard["configured"] = [ @@ -226,46 +236,21 @@ async def test_update_device_state_for_availability( }, ] await async_get_dashboard(hass).async_refresh() - - signal_device_updated = f"esphome_{mock_config_entry.entry_id}_on_device_update" - runtime_data = Mock( - available=True, - expected_disconnect=False, - device_info=mock_device_info, - signal_device_updated=signal_device_updated, + mock_device = await mock_esphome_device( + mock_client=mock_client, + entity_info=[], + user_service=[], + states=[], + device_info={"has_deep_sleep": has_deep_sleep}, ) - with patch( - "homeassistant.components.esphome.update.DomainData.get_entry_data", - return_value=runtime_data, - ): - assert await hass.config_entries.async_forward_entry_setup( - mock_config_entry, "update" - ) - - state = hass.states.get("update.none_firmware") + state = hass.states.get("update.test_firmware") assert state is not None - assert state.state == "on" - - expected_disconnect, expected_state = expected_disconnect_state - - runtime_data.available = False - runtime_data.expected_disconnect = expected_disconnect - async_dispatcher_send(hass, signal_device_updated) - - state = hass.states.get("update.none_firmware") + assert state.state == STATE_ON + await mock_device.mock_disconnect(expected_disconnect) + state = hass.states.get("update.test_firmware") assert state.state == expected_state - # Deep sleep devices should still be available - runtime_data.device_info = dataclasses.replace( - runtime_data.device_info, has_deep_sleep=True - ) - - async_dispatcher_send(hass, signal_device_updated) - - state = hass.states.get("update.none_firmware") - assert state.state == "on" - async def test_update_entity_dashboard_not_available_startup( hass: HomeAssistant, From ffcbab1c207ad181ae60d4e1c265830cc4302f5f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 10 Mar 2024 08:25:12 +0100 Subject: [PATCH 0636/1691] Split out deCONZ hub (#112854) * Rename DeconzGateway to DeconzHub * Move gateway.py to hub/hub.py --- homeassistant/components/deconz/__init__.py | 7 +++---- homeassistant/components/deconz/alarm_control_panel.py | 8 +++----- homeassistant/components/deconz/binary_sensor.py | 4 ++-- homeassistant/components/deconz/button.py | 4 ++-- homeassistant/components/deconz/climate.py | 4 ++-- homeassistant/components/deconz/config_flow.py | 8 ++++---- homeassistant/components/deconz/cover.py | 4 ++-- homeassistant/components/deconz/deconz_device.py | 8 ++++---- homeassistant/components/deconz/deconz_event.py | 8 ++++---- homeassistant/components/deconz/device_trigger.py | 4 ++-- homeassistant/components/deconz/diagnostics.py | 2 +- homeassistant/components/deconz/fan.py | 4 ++-- homeassistant/components/deconz/hub/__init__.py | 1 + .../components/deconz/{gateway.py => hub/hub.py} | 10 +++++----- homeassistant/components/deconz/light.py | 6 +++--- homeassistant/components/deconz/lock.py | 2 +- homeassistant/components/deconz/number.py | 4 ++-- homeassistant/components/deconz/scene.py | 2 +- homeassistant/components/deconz/select.py | 2 +- homeassistant/components/deconz/sensor.py | 6 +++--- homeassistant/components/deconz/services.py | 8 ++++---- homeassistant/components/deconz/siren.py | 2 +- homeassistant/components/deconz/switch.py | 2 +- tests/components/deconz/test_gateway.py | 6 ++++-- tests/components/deconz/test_init.py | 6 +++--- 25 files changed, 61 insertions(+), 61 deletions(-) rename homeassistant/components/deconz/{gateway.py => hub/hub.py} (98%) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index aad03b6960d..140fbe12d82 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -19,8 +19,7 @@ from .config_flow import get_master_gateway from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect -from .gateway import DeconzGateway -from .hub import get_deconz_api +from .hub import DeconzHub, get_deconz_api from .services import async_setup_services, async_unload_services @@ -47,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not hass.data[DOMAIN]: async_setup_services(hass) - gateway = hass.data[DOMAIN][config_entry.entry_id] = DeconzGateway( + gateway = hass.data[DOMAIN][config_entry.entry_id] = DeconzHub( hass, config_entry, api ) await gateway.async_update_device_registry() @@ -68,7 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload deCONZ config entry.""" - gateway: DeconzGateway = hass.data[DOMAIN].pop(config_entry.entry_id) + gateway: DeconzHub = hass.data[DOMAIN].pop(config_entry.entry_id) async_unload_events(gateway) if not hass.data[DOMAIN]: diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index 862ecece40f..764bcecc17f 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -29,7 +29,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry DECONZ_TO_ALARM_STATE = { AncillaryControlPanel.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, @@ -45,9 +45,7 @@ DECONZ_TO_ALARM_STATE = { } -def get_alarm_system_id_for_unique_id( - gateway: DeconzGateway, unique_id: str -) -> str | None: +def get_alarm_system_id_for_unique_id(gateway: DeconzHub, unique_id: str) -> str | None: """Retrieve alarm system ID the unique ID is registered to.""" for alarm_system in gateway.api.alarm_systems.values(): if unique_id in alarm_system.devices: @@ -97,7 +95,7 @@ class DeconzAlarmControlPanel(DeconzDevice[AncillaryControl], AlarmControlPanelE def __init__( self, device: AncillaryControl, - gateway: DeconzGateway, + gateway: DeconzHub, alarm_system_id: str, ) -> None: """Set up alarm control panel device.""" diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 5dd56a4c5b1..754e5ab427f 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -32,7 +32,7 @@ import homeassistant.helpers.entity_registry as er from .const import ATTR_DARK, ATTR_ON, DOMAIN as DECONZ_DOMAIN from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry from .util import serial_from_unique_id _SensorDeviceT = TypeVar("_SensorDeviceT", bound=PydeconzSensorBase) @@ -225,7 +225,7 @@ class DeconzBinarySensor(DeconzDevice[SensorResources], BinarySensorEntity): def __init__( self, device: SensorResources, - gateway: DeconzGateway, + gateway: DeconzHub, description: DeconzBinarySensorDescription, ) -> None: """Initialize deCONZ binary sensor.""" diff --git a/homeassistant/components/deconz/button.py b/homeassistant/components/deconz/button.py index 52105c10203..64801880583 100644 --- a/homeassistant/components/deconz/button.py +++ b/homeassistant/components/deconz/button.py @@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice, DeconzSceneMixin -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry @dataclass(frozen=True, kw_only=True) @@ -88,7 +88,7 @@ class DeconzSceneButton(DeconzSceneMixin, ButtonEntity): def __init__( self, device: PydeconzScene, - gateway: DeconzGateway, + gateway: DeconzHub, description: DeconzButtonDescription, ) -> None: """Initialize deCONZ number entity.""" diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 2fddedb00d5..931995196cc 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -35,7 +35,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_LOCKED, ATTR_OFFSET, ATTR_VALVE from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry DECONZ_FAN_SMART = "smart" @@ -103,7 +103,7 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): _attr_temperature_unit = UnitOfTemperature.CELSIUS _enable_turn_on_off_backwards_compatibility = False - def __init__(self, device: Thermostat, gateway: DeconzGateway) -> None: + def __init__(self, device: Thermostat, gateway: DeconzHub) -> None: """Set up thermostat device.""" super().__init__(device, gateway) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 3eb6b808805..88f4dd3edb7 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -44,7 +44,7 @@ from .const import ( HASSIO_CONFIGURATION_URL, LOGGER, ) -from .gateway import DeconzGateway +from .hub import DeconzHub DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" CONF_SERIAL = "serial" @@ -52,11 +52,11 @@ CONF_MANUAL_INPUT = "Manually define gateway" @callback -def get_master_gateway(hass: HomeAssistant) -> DeconzGateway: +def get_master_gateway(hass: HomeAssistant) -> DeconzHub: """Return the gateway which is marked as master.""" for gateway in hass.data[DOMAIN].values(): if gateway.master: - return cast(DeconzGateway, gateway) + return cast(DeconzHub, gateway) raise ValueError @@ -300,7 +300,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): class DeconzOptionsFlowHandler(OptionsFlow): """Handle deCONZ options.""" - gateway: DeconzGateway + gateway: DeconzHub def __init__(self, config_entry: ConfigEntry) -> None: """Initialize deCONZ options flow.""" diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index bac1ea404b9..bccdac1fc4c 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -22,7 +22,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry DECONZ_TYPE_TO_DEVICE_CLASS = { ResourceType.LEVEL_CONTROLLABLE_OUTPUT.value: CoverDeviceClass.DAMPER, @@ -56,7 +56,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): TYPE = DOMAIN - def __init__(self, cover_id: str, gateway: DeconzGateway) -> None: + def __init__(self, cover_id: str, gateway: DeconzHub) -> None: """Set up cover device.""" super().__init__(cover := gateway.api.lights.covers[cover_id], gateway) diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index 8a5ced2c678..90f6a5a251f 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -16,7 +16,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from .const import DOMAIN as DECONZ_DOMAIN -from .gateway import DeconzGateway +from .hub import DeconzHub from .util import serial_from_unique_id _DeviceT = TypeVar( @@ -33,7 +33,7 @@ class DeconzBase(Generic[_DeviceT]): def __init__( self, device: _DeviceT, - gateway: DeconzGateway, + gateway: DeconzHub, ) -> None: """Set up device and add update callback to get data from websocket.""" self._device: _DeviceT = device @@ -85,7 +85,7 @@ class DeconzDevice(DeconzBase[_DeviceT], Entity): def __init__( self, device: _DeviceT, - gateway: DeconzGateway, + gateway: DeconzHub, ) -> None: """Set up device and add update callback to get data from websocket.""" super().__init__(device, gateway) @@ -152,7 +152,7 @@ class DeconzSceneMixin(DeconzDevice[PydeconzScene]): def __init__( self, device: PydeconzScene, - gateway: DeconzGateway, + gateway: DeconzHub, ) -> None: """Set up a scene.""" super().__init__(device, gateway) diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 9bde87e5e17..70ee964f2f5 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -26,7 +26,7 @@ from homeassistant.util import slugify from .const import ATTR_DURATION, ATTR_ROTATION, CONF_ANGLE, CONF_GESTURE, LOGGER from .deconz_device import DeconzBase -from .gateway import DeconzGateway +from .hub import DeconzHub CONF_DECONZ_EVENT = "deconz_event" CONF_DECONZ_ALARM_EVENT = "deconz_alarm_event" @@ -55,7 +55,7 @@ RELATIVE_ROTARY_DECONZ_TO_EVENT = { } -async def async_setup_events(gateway: DeconzGateway) -> None: +async def async_setup_events(gateway: DeconzHub) -> None: """Set up the deCONZ events.""" @callback @@ -100,7 +100,7 @@ async def async_setup_events(gateway: DeconzGateway) -> None: @callback -def async_unload_events(gateway: DeconzGateway) -> None: +def async_unload_events(gateway: DeconzHub) -> None: """Unload all deCONZ events.""" for event in gateway.events: event.async_will_remove_from_hass() @@ -118,7 +118,7 @@ class DeconzEventBase(DeconzBase): def __init__( self, device: AncillaryControl | Presence | RelativeRotary | Switch, - gateway: DeconzGateway, + gateway: DeconzHub, ) -> None: """Register callback that will be used for signals.""" super().__init__(device, gateway) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index a4c24b7b7f4..27c82ab0be5 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -31,7 +31,7 @@ from .deconz_event import ( DeconzPresenceEvent, DeconzRelativeRotaryEvent, ) -from .gateway import DeconzGateway +from .hub import DeconzHub CONF_SUBTYPE = "subtype" @@ -656,7 +656,7 @@ def _get_deconz_event_from_device( device: dr.DeviceEntry, ) -> DeconzAlarmEvent | DeconzEvent | DeconzPresenceEvent | DeconzRelativeRotaryEvent: """Resolve deconz event from device.""" - gateways: dict[str, DeconzGateway] = hass.data.get(DOMAIN, {}) + gateways: dict[str, DeconzHub] = hass.data.get(DOMAIN, {}) for gateway in gateways.values(): for deconz_event in gateway.events: if device.id == deconz_event.device_id: diff --git a/homeassistant/components/deconz/diagnostics.py b/homeassistant/components/deconz/diagnostics.py index aa40c314802..3200ea162e4 100644 --- a/homeassistant/components/deconz/diagnostics.py +++ b/homeassistant/components/deconz/diagnostics.py @@ -9,7 +9,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_UNIQUE_ID from homeassistant.core import HomeAssistant -from .gateway import get_gateway_from_config_entry +from .hub import get_gateway_from_config_entry REDACT_CONFIG = {CONF_API_KEY, CONF_UNIQUE_ID} REDACT_DECONZ_CONFIG = {"bridgeid", "mac", "panid"} diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 65cc2f81ee2..45b1a83878f 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -17,7 +17,7 @@ from homeassistant.util.percentage import ( ) from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry ORDERED_NAMED_FAN_SPEEDS: list[LightFanSpeed] = [ LightFanSpeed.PERCENT_25, @@ -58,7 +58,7 @@ class DeconzFan(DeconzDevice[Light], FanEntity): _attr_supported_features = FanEntityFeature.SET_SPEED - def __init__(self, device: Light, gateway: DeconzGateway) -> None: + def __init__(self, device: Light, gateway: DeconzHub) -> None: """Set up fan.""" super().__init__(device, gateway) _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) diff --git a/homeassistant/components/deconz/hub/__init__.py b/homeassistant/components/deconz/hub/__init__.py index 962ecf47787..a99a578a89d 100644 --- a/homeassistant/components/deconz/hub/__init__.py +++ b/homeassistant/components/deconz/hub/__init__.py @@ -1,3 +1,4 @@ """Internal functionality not part of HA infrastructure.""" from .api import get_deconz_api # noqa: F401 +from .hub import DeconzHub, get_gateway_from_config_entry # noqa: F401 diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/hub/hub.py similarity index 98% rename from homeassistant/components/deconz/gateway.py rename to homeassistant/components/deconz/hub/hub.py index adc75e987b9..5c422f845bd 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/hub/hub.py @@ -18,7 +18,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import ( +from ..const import ( CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_NEW_DEVICES, @@ -32,7 +32,7 @@ from .const import ( ) if TYPE_CHECKING: - from .deconz_event import ( + from ..deconz_event import ( DeconzAlarmEvent, DeconzEvent, DeconzPresenceEvent, @@ -69,7 +69,7 @@ SENSORS = ( ) -class DeconzGateway: +class DeconzHub: """Manages a single deCONZ gateway.""" def __init__( @@ -328,6 +328,6 @@ class DeconzGateway: @callback def get_gateway_from_config_entry( hass: HomeAssistant, config_entry: ConfigEntry -) -> DeconzGateway: +) -> DeconzHub: """Return gateway with a matching config entry ID.""" - return cast(DeconzGateway, hass.data[DECONZ_DOMAIN][config_entry.entry_id]) + return cast(DeconzHub, hass.data[DECONZ_DOMAIN][config_entry.entry_id]) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 1f9bc953448..4565e72e9f5 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -36,7 +36,7 @@ from homeassistant.util.color import color_hs_to_xy from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry DECONZ_GROUP = "is_deconz_group" EFFECT_TO_DECONZ = { @@ -168,7 +168,7 @@ class DeconzBaseLight(DeconzDevice[_LightDeviceT], LightEntity): TYPE = DOMAIN _attr_color_mode = ColorMode.UNKNOWN - def __init__(self, device: _LightDeviceT, gateway: DeconzGateway) -> None: + def __init__(self, device: _LightDeviceT, gateway: DeconzHub) -> None: """Set up light.""" super().__init__(device, gateway) @@ -334,7 +334,7 @@ class DeconzGroup(DeconzBaseLight[Group]): _attr_has_entity_name = True - def __init__(self, device: Group, gateway: DeconzGateway) -> None: + def __init__(self, device: Group, gateway: DeconzHub) -> None: """Set up group and create an unique id.""" self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" super().__init__(device, gateway) diff --git a/homeassistant/components/deconz/lock.py b/homeassistant/components/deconz/lock.py index 7afde4ada11..c5fdcd43625 100644 --- a/homeassistant/components/deconz/lock.py +++ b/homeassistant/components/deconz/lock.py @@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .hub import get_gateway_from_config_entry async def async_setup_entry( diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index e98f5d726ac..a829831b511 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -25,7 +25,7 @@ import homeassistant.helpers.entity_registry as er from .const import DOMAIN as DECONZ_DOMAIN from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry from .util import serial_from_unique_id T = TypeVar("T", Presence, PydeconzSensorBase) @@ -129,7 +129,7 @@ class DeconzNumber(DeconzDevice[SensorResources], NumberEntity): def __init__( self, device: SensorResources, - gateway: DeconzGateway, + gateway: DeconzHub, description: DeconzNumberDescription, ) -> None: """Initialize deCONZ number entity.""" diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index 236389cc100..75e0a5ff44d 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzSceneMixin -from .gateway import get_gateway_from_config_entry +from .hub import get_gateway_from_config_entry async def async_setup_entry( diff --git a/homeassistant/components/deconz/select.py b/homeassistant/components/deconz/select.py index d38fee2fdc7..7720df7c8cd 100644 --- a/homeassistant/components/deconz/select.py +++ b/homeassistant/components/deconz/select.py @@ -17,7 +17,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .hub import get_gateway_from_config_entry SENSITIVITY_TO_DECONZ = { "High": PresenceConfigSensitivity.HIGH.value, diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 8366c811318..7f5495bdd98 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -54,7 +54,7 @@ import homeassistant.util.dt as dt_util from .const import ATTR_DARK, ATTR_ON, DOMAIN as DECONZ_DOMAIN from .deconz_device import DeconzDevice -from .gateway import DeconzGateway, get_gateway_from_config_entry +from .hub import DeconzHub, get_gateway_from_config_entry from .util import serial_from_unique_id PROVIDES_EXTRA_ATTRIBUTES = ( @@ -386,7 +386,7 @@ class DeconzSensor(DeconzDevice[SensorResources], SensorEntity): def __init__( self, device: SensorResources, - gateway: DeconzGateway, + gateway: DeconzHub, description: DeconzSensorDescription, ) -> None: """Initialize deCONZ sensor.""" @@ -453,7 +453,7 @@ class DeconzBatteryTracker: def __init__( self, sensor_id: str, - gateway: DeconzGateway, + gateway: DeconzHub, description: DeconzSensorDescription, async_add_entities: AddEntitiesCallback, ) -> None: diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index bcac6ac1e1d..d27c2a67615 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -18,7 +18,7 @@ from homeassistant.util.read_only_dict import ReadOnlyDict from .config_flow import get_master_gateway from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER -from .gateway import DeconzGateway +from .hub import DeconzHub DECONZ_SERVICES = "deconz_services" @@ -110,7 +110,7 @@ def async_unload_services(hass: HomeAssistant) -> None: hass.services.async_remove(DOMAIN, service) -async def async_configure_service(gateway: DeconzGateway, data: ReadOnlyDict) -> None: +async def async_configure_service(gateway: DeconzHub, data: ReadOnlyDict) -> None: """Set attribute of device in deCONZ. Entity is used to resolve to a device path (e.g. '/lights/1'). @@ -140,7 +140,7 @@ async def async_configure_service(gateway: DeconzGateway, data: ReadOnlyDict) -> await gateway.api.request("put", field, json=data) -async def async_refresh_devices_service(gateway: DeconzGateway) -> None: +async def async_refresh_devices_service(gateway: DeconzHub) -> None: """Refresh available devices from deCONZ.""" gateway.ignore_state_updates = True await gateway.api.refresh_state() @@ -148,7 +148,7 @@ async def async_refresh_devices_service(gateway: DeconzGateway) -> None: gateway.ignore_state_updates = False -async def async_remove_orphaned_entries_service(gateway: DeconzGateway) -> None: +async def async_remove_orphaned_entries_service(gateway: DeconzHub) -> None: """Remove orphaned deCONZ entries from device and entity registries.""" device_registry = dr.async_get(gateway.hass) entity_registry = er.async_get(gateway.hass) diff --git a/homeassistant/components/deconz/siren.py b/homeassistant/components/deconz/siren.py index a6c4a441e1f..e054dc8b473 100644 --- a/homeassistant/components/deconz/siren.py +++ b/homeassistant/components/deconz/siren.py @@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .hub import get_gateway_from_config_entry async def async_setup_entry( diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index 990de24dffc..214f3f17897 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -14,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import POWER_PLUGS from .deconz_device import DeconzDevice -from .gateway import get_gateway_from_config_entry +from .hub import get_gateway_from_config_entry async def async_setup_entry( diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index a9974b61d08..aec39dc5b8b 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -18,8 +18,10 @@ from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.deconz.config_flow import DECONZ_MANUFACTURERURL from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.errors import AuthenticationRequired, CannotConnect -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry -from homeassistant.components.deconz.hub import get_deconz_api +from homeassistant.components.deconz.hub import ( + get_deconz_api, + get_gateway_from_config_entry, +) from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 972f82b1db3..64b1594faea 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -4,7 +4,7 @@ import asyncio from unittest.mock import patch from homeassistant.components.deconz import ( - DeconzGateway, + DeconzHub, async_setup_entry, async_unload_entry, async_update_group_unique_id, @@ -39,8 +39,8 @@ ENTRY2_UUID = "789ACE" async def setup_entry(hass, entry): """Test that setup entry works.""" - with patch.object(DeconzGateway, "async_setup", return_value=True), patch.object( - DeconzGateway, "async_update_device_registry", return_value=True + with patch.object(DeconzHub, "async_setup", return_value=True), patch.object( + DeconzHub, "async_update_device_registry", return_value=True ): assert await async_setup_entry(hass, entry) is True From e63122437273bd49550f6498310201eaee6df844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=20Bj=C3=B6rck?= Date: Sun, 10 Mar 2024 09:11:10 +0100 Subject: [PATCH 0637/1691] Bump yalexs to 2.0.0 (#111706) Co-authored-by: J. Nick Koston --- homeassistant/components/august/__init__.py | 7 +++ homeassistant/components/august/camera.py | 35 ++++++++++-- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/august/mocks.py | 50 +++++++++++----- tests/components/august/test_camera.py | 57 ++++++++++++++++++- 7 files changed, 133 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 82552ffa3d6..2db2b173f6b 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -306,6 +306,13 @@ class AugustData(AugustSubscriberMixin): exc_info=err, ) + async def refresh_camera_by_id(self, device_id: str) -> None: + """Re-fetch doorbell/camera data from API.""" + await self._async_update_device_detail( + self._doorbells_by_id[device_id], + self._api.async_get_doorbell_detail, + ) + async def _async_refresh_device_detail_by_id(self, device_id: str) -> None: if device_id in self._locks_by_id: if self.activity_stream and self.activity_stream.pubnub.connected: diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index f5380ce6732..188a55bd4b9 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -2,9 +2,12 @@ from __future__ import annotations +import logging + from aiohttp import ClientSession from yalexs.activity import ActivityType -from yalexs.doorbell import Doorbell +from yalexs.const import Brand +from yalexs.doorbell import ContentTokenExpired, Doorbell from yalexs.util import update_doorbell_image_from_activity from homeassistant.components.camera import Camera @@ -17,6 +20,8 @@ from . import AugustData from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN from .entity import AugustEntityMixin +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, @@ -48,6 +53,7 @@ class AugustCamera(AugustEntityMixin, Camera): self._timeout = timeout self._session = session self._image_url = None + self._content_token = None self._image_content = None self._attr_unique_id = f"{self._device_id:s}_camera" self._attr_motion_detection_enabled = True @@ -63,6 +69,12 @@ class AugustCamera(AugustEntityMixin, Camera): """Return the camera model.""" return self._detail.model + async def _async_update(self): + """Update device.""" + _LOGGER.debug("async_update called %s", self._detail.device_name) + await self._data.refresh_camera_by_id(self._device_id) + self._update_from_data() + @callback def _update_from_data(self) -> None: """Get the latest state of the sensor.""" @@ -70,7 +82,6 @@ class AugustCamera(AugustEntityMixin, Camera): self._device_id, {ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE}, ) - if doorbell_activity is not None: update_doorbell_image_from_activity(self._detail, doorbell_activity) @@ -82,7 +93,23 @@ class AugustCamera(AugustEntityMixin, Camera): if self._image_url is not self._detail.image_url: self._image_url = self._detail.image_url - self._image_content = await self._detail.async_get_doorbell_image( - self._session, timeout=self._timeout + self._content_token = self._detail.content_token or self._content_token + _LOGGER.debug( + "calling doorbell async_get_doorbell_image, %s", + self._detail.device_name, ) + try: + self._image_content = await self._detail.async_get_doorbell_image( + self._session, timeout=self._timeout + ) + except ContentTokenExpired: + if self._data.brand == Brand.YALE_HOME: + _LOGGER.debug( + "Error fetching camera image, updating content-token from api to retry" + ) + await self._async_update() + self._image_content = await self._detail.async_get_doorbell_image( + self._session, timeout=self._timeout + ) + return self._image_content diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 83685e6e6a1..27c5f11ec6e 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -28,5 +28,5 @@ "documentation": "https://www.home-assistant.io/integrations/august", "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], - "requirements": ["yalexs==1.11.4", "yalexs-ble==2.4.2"] + "requirements": ["yalexs==2.0.0", "yalexs-ble==2.4.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index bb22d21f40d..73440b45d50 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2893,7 +2893,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.2 # homeassistant.components.august -yalexs==1.11.4 +yalexs==2.0.0 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a584d53ec81..8fc51989fbf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2228,7 +2228,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.2 # homeassistant.components.august -yalexs==1.11.4 +yalexs==2.0.0 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 08677764f35..4e017066e90 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -26,11 +26,12 @@ from yalexs.activity import ( LockOperationActivity, ) from yalexs.authenticator import AuthenticationState +from yalexs.const import Brand from yalexs.doorbell import Doorbell, DoorbellDetail from yalexs.lock import Lock, LockDetail from yalexs.pubnub_async import AugustPubNub -from homeassistant.components.august.const import CONF_LOGIN_METHOD, DOMAIN +from homeassistant.components.august.const import CONF_BRAND, CONF_LOGIN_METHOD, DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -38,13 +39,14 @@ from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture -def _mock_get_config(): +def _mock_get_config(brand: Brand = Brand.AUGUST): """Return a default august config.""" return { DOMAIN: { CONF_LOGIN_METHOD: "email", CONF_USERNAME: "mocked_username", CONF_PASSWORD: "mocked_password", + CONF_BRAND: brand, } } @@ -59,7 +61,7 @@ def _mock_authenticator(auth_state): @patch("homeassistant.components.august.gateway.ApiAsync") @patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate") async def _mock_setup_august( - hass, api_instance, pubnub_mock, authenticate_mock, api_mock + hass, api_instance, pubnub_mock, authenticate_mock, api_mock, brand ): """Set up august integration.""" authenticate_mock.side_effect = MagicMock( @@ -70,7 +72,7 @@ async def _mock_setup_august( api_mock.return_value = api_instance entry = MockConfigEntry( domain=DOMAIN, - data=_mock_get_config()[DOMAIN], + data=_mock_get_config(brand)[DOMAIN], options={}, ) entry.add_to_hass(hass) @@ -88,21 +90,26 @@ async def _create_august_with_devices( api_call_side_effects: dict[str, Any] | None = None, activities: list[Any] | None = None, pubnub: AugustPubNub | None = None, + brand: Brand = Brand.AUGUST, ) -> ConfigEntry: entry, _ = await _create_august_api_with_devices( - hass, devices, api_call_side_effects, activities, pubnub + hass, devices, api_call_side_effects, activities, pubnub, brand ) return entry async def _create_august_api_with_devices( # noqa: C901 - hass, devices, api_call_side_effects=None, activities=None, pubnub=None + hass, + devices, + api_call_side_effects=None, + activities=None, + pubnub=None, + brand=Brand.AUGUST, ): if api_call_side_effects is None: api_call_side_effects = {} if pubnub is None: pubnub = AugustPubNub() - device_data = {"doorbells": [], "locks": []} for device in devices: if isinstance(device, LockDetail): @@ -111,7 +118,13 @@ async def _create_august_api_with_devices( # noqa: C901 ) elif isinstance(device, DoorbellDetail): device_data["doorbells"].append( - {"base": _mock_august_doorbell(device.device_id), "detail": device} + { + "base": _mock_august_doorbell( + deviceid=device.device_id, + brand=device._data.get("brand", Brand.AUGUST), + ), + "detail": device, + } ) else: raise ValueError # noqa: TRY004 @@ -182,7 +195,7 @@ async def _create_august_api_with_devices( # noqa: C901 ) api_instance, entry = await _mock_setup_august_with_api_side_effects( - hass, api_call_side_effects, pubnub + hass, api_call_side_effects, pubnub, brand ) if device_data["locks"]: @@ -193,7 +206,9 @@ async def _create_august_api_with_devices( # noqa: C901 return entry, api_instance -async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, pubnub): +async def _mock_setup_august_with_api_side_effects( + hass, api_call_side_effects, pubnub, brand=Brand.AUGUST +): api_instance = MagicMock(name="Api") if api_call_side_effects["get_lock_detail"]: @@ -236,7 +251,9 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects, api_instance.async_status_async = AsyncMock() api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"}) - return api_instance, await _mock_setup_august(hass, api_instance, pubnub) + return api_instance, await _mock_setup_august( + hass, api_instance, pubnub, brand=brand + ) def _mock_august_authentication(token_text, token_timestamp, state): @@ -253,13 +270,18 @@ def _mock_august_lock(lockid="mocklockid1", houseid="mockhouseid1"): return Lock(lockid, _mock_august_lock_data(lockid=lockid, houseid=houseid)) -def _mock_august_doorbell(deviceid="mockdeviceid1", houseid="mockhouseid1"): +def _mock_august_doorbell( + deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST +): return Doorbell( - deviceid, _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid) + deviceid, + _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid, brand=brand), ) -def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1"): +def _mock_august_doorbell_data( + deviceid="mockdeviceid1", houseid="mockhouseid1", brand=Brand.AUGUST +): return { "_id": deviceid, "DeviceID": deviceid, diff --git a/tests/components/august/test_camera.py b/tests/components/august/test_camera.py index e19b935b672..539a26cc30f 100644 --- a/tests/components/august/test_camera.py +++ b/tests/components/august/test_camera.py @@ -3,6 +3,9 @@ from http import HTTPStatus from unittest.mock import patch +from yalexs.const import Brand +from yalexs.doorbell import ContentTokenExpired + from homeassistant.const import STATE_IDLE from homeassistant.core import HomeAssistant @@ -20,7 +23,7 @@ async def test_create_doorbell( with patch.object( doorbell_one, "async_get_doorbell_image", create=False, return_value="image" ): - await _create_august_with_devices(hass, [doorbell_one]) + await _create_august_with_devices(hass, [doorbell_one], brand=Brand.AUGUST) camera_k98gidt45gul_name_camera = hass.states.get( "camera.k98gidt45gul_name_camera" @@ -36,3 +39,55 @@ async def test_create_doorbell( assert resp.status == HTTPStatus.OK body = await resp.text() assert body == "image" + + +async def test_doorbell_refresh_content_token_recover( + hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator +) -> None: + """Test camera image content token expired.""" + doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + with patch.object( + doorbell_two, + "async_get_doorbell_image", + create=False, + side_effect=[ContentTokenExpired, "image"], + ): + await _create_august_with_devices( + hass, + [doorbell_two], + brand=Brand.YALE_HOME, + ) + url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[ + "entity_picture" + ] + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.OK + body = await resp.text() + assert body == "image" + + +async def test_doorbell_refresh_content_token_fail( + hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator +) -> None: + """Test camera image content token expired.""" + doorbell_two = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + with patch.object( + doorbell_two, + "async_get_doorbell_image", + create=False, + side_effect=ContentTokenExpired, + ): + await _create_august_with_devices( + hass, + [doorbell_two], + brand=Brand.YALE_HOME, + ) + url = hass.states.get("camera.k98gidt45gul_name_camera").attributes[ + "entity_picture" + ] + + client = await hass_client_no_auth() + resp = await client.get(url) + assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR From e91a38eede5131aebe1ed6d03ce9debe1994ab6a Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 10 Mar 2024 09:29:48 +0100 Subject: [PATCH 0638/1691] Add Locks to Xiaomi-BLE (#111156) --- .../components/xiaomi_ble/binary_sensor.py | 18 ++++ homeassistant/components/xiaomi_ble/const.py | 13 ++- .../components/xiaomi_ble/device_trigger.py | 89 ++++++++++++++++-- homeassistant/components/xiaomi_ble/event.py | 60 ++++++++++++ .../components/xiaomi_ble/manifest.json | 2 +- homeassistant/components/xiaomi_ble/sensor.py | 14 ++- .../components/xiaomi_ble/strings.json | 83 ++++++++++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../xiaomi_ble/test_device_trigger.py | 93 +++++++++++++++++++ tests/components/xiaomi_ble/test_event.py | 93 ++++++++++++++++++- 11 files changed, 452 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/binary_sensor.py b/homeassistant/components/xiaomi_ble/binary_sensor.py index 2e2c3251add..c8d4666e482 100644 --- a/homeassistant/components/xiaomi_ble/binary_sensor.py +++ b/homeassistant/components/xiaomi_ble/binary_sensor.py @@ -42,6 +42,10 @@ BINARY_SENSOR_DESCRIPTIONS = { key=XiaomiBinarySensorDeviceClass.LIGHT, device_class=BinarySensorDeviceClass.LIGHT, ), + XiaomiBinarySensorDeviceClass.LOCK: BinarySensorEntityDescription( + key=XiaomiBinarySensorDeviceClass.LOCK, + device_class=BinarySensorDeviceClass.LOCK, + ), XiaomiBinarySensorDeviceClass.MOISTURE: BinarySensorEntityDescription( key=XiaomiBinarySensorDeviceClass.MOISTURE, device_class=BinarySensorDeviceClass.MOISTURE, @@ -62,6 +66,16 @@ BINARY_SENSOR_DESCRIPTIONS = { key=XiaomiBinarySensorDeviceClass.SMOKE, device_class=BinarySensorDeviceClass.SMOKE, ), + ExtendedBinarySensorDeviceClass.ANTILOCK: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.ANTILOCK, + ), + ExtendedBinarySensorDeviceClass.ARMED: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.ARMED, + icon="mdi:shield-check", + ), + ExtendedBinarySensorDeviceClass.CHILDLOCK: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.CHILDLOCK, + ), ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED: BinarySensorEntityDescription( key=ExtendedBinarySensorDeviceClass.DEVICE_FORCIBLY_REMOVED, device_class=BinarySensorDeviceClass.PROBLEM, @@ -74,6 +88,10 @@ BINARY_SENSOR_DESCRIPTIONS = { key=ExtendedBinarySensorDeviceClass.DOOR_STUCK, device_class=BinarySensorDeviceClass.PROBLEM, ), + ExtendedBinarySensorDeviceClass.FINGERPRINT: BinarySensorEntityDescription( + key=ExtendedBinarySensorDeviceClass.FINGERPRINT, + icon="mdi:fingerprint", + ), ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR: BinarySensorEntityDescription( key=ExtendedBinarySensorDeviceClass.KNOCK_ON_THE_DOOR, ), diff --git a/homeassistant/components/xiaomi_ble/const.py b/homeassistant/components/xiaomi_ble/const.py index a421dda7195..8ea99cf1f84 100644 --- a/homeassistant/components/xiaomi_ble/const.py +++ b/homeassistant/components/xiaomi_ble/const.py @@ -20,13 +20,21 @@ EVENT_PROPERTIES: Final = "event_properties" XIAOMI_BLE_EVENT: Final = "xiaomi_ble_event" EVENT_CLASS_BUTTON: Final = "button" -EVENT_CLASS_DIMMER: Final = "dimmer" -EVENT_CLASS_MOTION: Final = "motion" EVENT_CLASS_CUBE: Final = "cube" +EVENT_CLASS_DIMMER: Final = "dimmer" +EVENT_CLASS_ERROR: Final = "error" +EVENT_CLASS_FINGERPRINT: Final = "fingerprint" +EVENT_CLASS_LOCK: Final = "lock" +EVENT_CLASS_MOTION: Final = "motion" BUTTON: Final = "button" CUBE: Final = "cube" DIMMER: Final = "dimmer" +ERROR: Final = "error" +FINGERPRINT: Final = "fingerprint" +LOCK: Final = "lock" +LOCK_FINGERPRINT = "lock_fingerprint" +MOTION_DEVICE: Final = "motion_device" DOUBLE_BUTTON: Final = "double_button" TRIPPLE_BUTTON: Final = "tripple_button" REMOTE: Final = "remote" @@ -40,7 +48,6 @@ BUTTON_PRESS_LONG: Final = "button_press_long" BUTTON_PRESS_DOUBLE_LONG: Final = "button_press_double_long" DOUBLE_BUTTON_PRESS_DOUBLE_LONG: Final = "double_button_press_double_long" TRIPPLE_BUTTON_PRESS_DOUBLE_LONG: Final = "tripple_button_press_double_long" -MOTION_DEVICE: Final = "motion_device" class XiaomiBleEvent(TypedDict): diff --git a/homeassistant/components/xiaomi_ble/device_trigger.py b/homeassistant/components/xiaomi_ble/device_trigger.py index eb345e91094..86749bd8dd3 100644 --- a/homeassistant/components/xiaomi_ble/device_trigger.py +++ b/homeassistant/components/xiaomi_ble/device_trigger.py @@ -32,12 +32,19 @@ from .const import ( DOMAIN, DOUBLE_BUTTON, DOUBLE_BUTTON_PRESS_DOUBLE_LONG, + ERROR, EVENT_CLASS, EVENT_CLASS_BUTTON, EVENT_CLASS_CUBE, EVENT_CLASS_DIMMER, + EVENT_CLASS_ERROR, + EVENT_CLASS_FINGERPRINT, + EVENT_CLASS_LOCK, EVENT_CLASS_MOTION, EVENT_TYPE, + FINGERPRINT, + LOCK, + LOCK_FINGERPRINT, MOTION, MOTION_DEVICE, REMOTE, @@ -62,6 +69,51 @@ TRIGGERS_BY_TYPE = { "rotate_left_pressed", "rotate_right_pressed", ], + ERROR: [ + "frequent_unlocking_with_incorrect_password", + "frequent_unlocking_with_wrong_fingerprints", + "operation_timeout_password_input_timeout", + "lock_picking", + "reset_button_is_pressed", + "the_wrong_key_is_frequently_unlocked", + "foreign_body_in_the_keyhole", + "the_key_has_not_been_taken_out", + "error_nfc_frequently_unlocks", + "timeout_is_not_locked_as_required", + "failure_to_unlock_frequently_in_multiple_ways", + "unlocking_the_face_frequently_fails", + "failure_to_unlock_the_vein_frequently", + "hijacking_alarm", + "unlock_inside_the_door_after_arming", + "palmprints_frequently_fail_to_unlock", + "the_safe_was_moved", + "the_battery_level_is_less_than_10_percent", + "the_battery_level_is_less_than_5_percent", + "the_fingerprint_sensor_is_abnormal", + "the_accessory_battery_is_low", + "mechanical_failure", + "the_lock_sensor_is_faulty", + ], + FINGERPRINT: [ + "match_successful", + "match_failed", + "low_quality_too_light_fuzzy", + "insufficient_area", + "skin_is_too_dry", + "skin_is_too_wet", + ], + LOCK: [ + "lock_outside_the_door", + "unlock_outside_the_door", + "lock_inside_the_door", + "unlock_inside_the_door", + "locked", + "turn_on_antilock", + "release_the_antilock", + "turn_on_child_lock", + "turn_off_child_lock", + "abnormal", + ], MOTION_DEVICE: ["motion_detected"], } @@ -71,6 +123,10 @@ EVENT_TYPES = { DIMMER: ["dimmer"], DOUBLE_BUTTON: ["button_left", "button_right"], TRIPPLE_BUTTON: ["button_left", "button_middle", "button_right"], + ERROR: ["error"], + FINGERPRINT: ["fingerprint"], + LOCK: ["lock"], + MOTION: ["motion"], REMOTE: [ "button_on", "button_off", @@ -106,7 +162,6 @@ EVENT_TYPES = { "button_increase_wind_speed", "button_decrease_wind_speed", ], - MOTION: ["motion"], } @@ -150,6 +205,26 @@ TRIGGER_MODEL_DATA = { event_types=EVENT_TYPES[TRIPPLE_BUTTON], triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG], ), + ERROR: TriggerModelData( + event_class=EVENT_CLASS_ERROR, + event_types=EVENT_TYPES[ERROR], + triggers=TRIGGERS_BY_TYPE[ERROR], + ), + LOCK_FINGERPRINT: TriggerModelData( + event_class=EVENT_CLASS_FINGERPRINT, + event_types=EVENT_TYPES[LOCK] + EVENT_TYPES[FINGERPRINT], + triggers=TRIGGERS_BY_TYPE[LOCK] + TRIGGERS_BY_TYPE[FINGERPRINT], + ), + LOCK: TriggerModelData( + event_class=EVENT_CLASS_LOCK, + event_types=EVENT_TYPES[LOCK], + triggers=TRIGGERS_BY_TYPE[LOCK], + ), + MOTION_DEVICE: TriggerModelData( + event_class=EVENT_CLASS_MOTION, + event_types=EVENT_TYPES[MOTION], + triggers=TRIGGERS_BY_TYPE[MOTION_DEVICE], + ), REMOTE: TriggerModelData( event_class=EVENT_CLASS_BUTTON, event_types=EVENT_TYPES[REMOTE], @@ -170,11 +245,6 @@ TRIGGER_MODEL_DATA = { event_types=EVENT_TYPES[REMOTE_VENFAN], triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG], ), - MOTION_DEVICE: TriggerModelData( - event_class=EVENT_CLASS_MOTION, - event_types=EVENT_TYPES[MOTION], - triggers=TRIGGERS_BY_TYPE[MOTION_DEVICE], - ), } @@ -197,6 +267,13 @@ MODEL_DATA = { "MUE4094RT": TRIGGER_MODEL_DATA[MOTION_DEVICE], "XMMF01JQD": TRIGGER_MODEL_DATA[CUBE], "YLKG07YL/YLKG08YL": TRIGGER_MODEL_DATA[DIMMER], + "DSL-C08": TRIGGER_MODEL_DATA[LOCK], + "XMZNMS08LM": TRIGGER_MODEL_DATA[LOCK], + "Lockin-SV40": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "MJZNMSQ01YD": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "XMZNMS04LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "ZNMS16LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], + "ZNMS17LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT], } diff --git a/homeassistant/components/xiaomi_ble/event.py b/homeassistant/components/xiaomi_ble/event.py index 4b0f9a61433..e39a4adb3c7 100644 --- a/homeassistant/components/xiaomi_ble/event.py +++ b/homeassistant/components/xiaomi_ble/event.py @@ -21,6 +21,9 @@ from .const import ( EVENT_CLASS_BUTTON, EVENT_CLASS_CUBE, EVENT_CLASS_DIMMER, + EVENT_CLASS_ERROR, + EVENT_CLASS_FINGERPRINT, + EVENT_CLASS_LOCK, EVENT_CLASS_MOTION, EVENT_PROPERTIES, EVENT_TYPE, @@ -59,6 +62,63 @@ DESCRIPTIONS_BY_EVENT_CLASS = { "rotate_right_pressed", ], ), + EVENT_CLASS_ERROR: EventEntityDescription( + key=EVENT_CLASS_ERROR, + translation_key="error", + event_types=[ + "frequent_unlocking_with_incorrect_password", + "frequent_unlocking_with_wrong_fingerprints", + "operation_timeout_password_input_timeout", + "lock_picking", + "reset_button_is_pressed", + "the_wrong_key_is_frequently_unlocked", + "foreign_body_in_the_keyhole", + "the_key_has_not_been_taken_out", + "error_nfc_frequently_unlocks", + "timeout_is_not_locked_as_required", + "failure_to_unlock_frequently_in_multiple_ways", + "unlocking_the_face_frequently_fails", + "failure_to_unlock_the_vein_frequently", + "hijacking_alarm", + "unlock_inside_the_door_after_arming", + "palmprints_frequently_fail_to_unlock", + "the_safe_was_moved", + "the_battery_level_is_less_than_10_percent", + "the_battery_is_less_than_5_percent", + "the_fingerprint_sensor_is_abnormal", + "the_accessory_battery_is_low", + "mechanical_failure", + "the_lock_sensor_is_faulty", + ], + ), + EVENT_CLASS_FINGERPRINT: EventEntityDescription( + key=EVENT_CLASS_FINGERPRINT, + translation_key="fingerprint", + event_types=[ + "match_successful", + "match_failed", + "low_quality_too_light_fuzzy", + "insufficient_area", + "skin_is_too_dry", + "skin_is_too_wet", + ], + ), + EVENT_CLASS_LOCK: EventEntityDescription( + key=EVENT_CLASS_LOCK, + translation_key="lock", + event_types=[ + "lock_outside_the_door", + "unlock_outside_the_door", + "lock_inside_the_door", + "unlock_inside_the_door", + "locked", + "turn_on_antilock", + "release_the_antilock", + "turn_on_child_lock", + "turn_off_child_lock", + "abnormal", + ], + ), EVENT_CLASS_MOTION: EventEntityDescription( key=EVENT_CLASS_MOTION, translation_key="motion", diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index a380ecb8e94..603a2dc2c71 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.25.2"] + "requirements": ["xiaomi-ble==0.26.1"] } diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index 2bcc67e2668..c5354a54394 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -132,23 +132,31 @@ SENSOR_DESCRIPTIONS = { state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, ), - # Used for e.g. consumable sensor on WX08ZM and M1S-T500 + # E.g. consumable sensor on WX08ZM and M1S-T500 (ExtendedSensorDeviceClass.CONSUMABLE, Units.PERCENTAGE): SensorEntityDescription( key=str(ExtendedSensorDeviceClass.CONSUMABLE), native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), - # Used for score after brushing with a toothbrush + # Score after brushing with a toothbrush (ExtendedSensorDeviceClass.SCORE, None): SensorEntityDescription( key=str(ExtendedSensorDeviceClass.SCORE), state_class=SensorStateClass.MEASUREMENT, ), - # Used for counting during brushing + # Counting during brushing (ExtendedSensorDeviceClass.COUNTER, Units.TIME_SECONDS): SensorEntityDescription( key=str(ExtendedSensorDeviceClass.COUNTER), native_unit_of_measurement=UnitOfTime.SECONDS, state_class=SensorStateClass.MEASUREMENT, ), + # Key id for locks and fingerprint readers + (ExtendedSensorDeviceClass.KEY_ID, None): SensorEntityDescription( + key=str(ExtendedSensorDeviceClass.KEY_ID), icon="mdi:identifier" + ), + # Lock method for locks + (ExtendedSensorDeviceClass.LOCK_METHOD, None): SensorEntityDescription( + key=str(ExtendedSensorDeviceClass.LOCK_METHOD), icon="mdi:key-variant" + ), } diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index d764a436f4c..2f2b705ff60 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -48,7 +48,23 @@ "rotate_left": "Rotate Left", "rotate_right": "Rotate Right", "rotate_left_pressed": "Rotate Left (Pressed)", - "rotate_right_pressed": "Rotate Right (Pressed)" + "rotate_right_pressed": "Rotate Right (Pressed)", + "match_successful": "Match successful", + "match_failed": "Match failed", + "low_quality_too_light_fuzzy": "Low quality (too light, fuzzy)", + "insufficient_area": "Insufficient area", + "skin_is_too_dry": "Skin is too dry", + "skin_is_too_wet": "Skin is too wet", + "lock_outside_the_door": "Lock outside the door", + "unlock_outside_the_door": "Unlock outside the door", + "lock_inside_the_door": "Lock inside the door", + "unlock_inside_the_door": "Unlock outside the door", + "locked": "Locked", + "turn_on_antilock": "Turn on antilock", + "release_the_antilock": "Release antilock", + "turn_on_child_lock": "Turn on child lock", + "turn_off_child_lock": "Turn off child lock", + "abnormal": "Abnormal" }, "trigger_type": { "button": "Button \"{subtype}\"", @@ -79,6 +95,8 @@ "button_increase_wind_speed": "Button Increase Wind Speed \"{subtype}\"", "button_decrease_wind_speed": "Button Decrease Wind Speed \"{subtype}\"", "dimmer": "{subtype}", + "fingerprint": "{subtype}", + "lock": "{subtype}", "motion": "{subtype}", "cube": "{subtype}" } @@ -120,6 +138,69 @@ } } }, + "error": { + "state_attributes": { + "event_type": { + "state": { + "frequent_unlocking_with_incorrect_password": "Frequent unlocking with incorrect password", + "frequent_unlocking_with_wrong_fingerprints": "Frequent unlocking with wrong fingerprints", + "operation_timeout_password_input_timeout": "Operation timeout password input timeout", + "lock_picking": "Lock picking", + "reset_button_is_pressed": "Reset button is pressed", + "the_wrong_key_is_frequently_unlocked": "The wrong key is frequently unlocked", + "foreign_body_in_the_keyhole": "Foreign body in the keyhole", + "the_key_has_not_been_taken_out": "The key has not been taken out", + "error_nfc_frequently_unlocks": "Error NFC frequently unlocks", + "timeout_is_not_locked_as_required": "Timeout is not locked as required", + "failure_to_unlock_frequently_in_multiple_ways": "Failure to unlock frequently in multiple ways", + "unlocking_the_face_frequently_fails": "Unlocking the face frequently fails", + "failure_to_unlock_the_vein_frequently": "Failure to unlock the vein frequently", + "hijacking_alarm": "Hijacking alarm", + "unlock_inside_the_door_after_arming": "Unlock inside the door after arming", + "palmprints_frequently_fail_to_unlock": "Palmprints frequently fail to unlock", + "the_safe_was_moved": "The safe was moved", + "the_battery_level_is_less_than_10_percent": "The battery level is less than 10%", + "the_battery_level_is_less_than_5_percent": "The battery level is less than 5%", + "the_fingerprint_sensor_is_abnormal": "The fingerprint sensor is abnormal", + "the_accessory_battery_is_low": "The accessory battery is low", + "mechanical_failure": "Mechanical failure", + "the_lock_sensor_is_faulty": "The lock sensor is faulty" + } + } + } + }, + "fingerprint": { + "state_attributes": { + "event_type": { + "state": { + "match_successful": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::match_successful%]", + "match_failed": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::match_failed%]", + "low_quality_too_light_fuzzy": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::low_quality_too_light_fuzzy%]", + "insufficient_area": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::insufficient_area%]", + "skin_is_too_dry": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::skin_is_too_dry%]", + "skin_is_too_wet": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::skin_is_too_wet%]" + } + } + } + }, + "lock": { + "state_attributes": { + "event_type": { + "state": { + "lock_outside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::lock_outside_the_door%]", + "unlock_outside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::unlock_outside_the_door%]", + "lock_inside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::lock_inside_the_door%]", + "unlock_inside_the_door": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::unlock_inside_the_door%]", + "locked": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::locked%]", + "turn_on_antilock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::turn_on_antilock%]", + "release_the_antilock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::release_the_antilock%]", + "turn_on_child_lock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::turn_on_child_lock%]", + "turn_off_child_lock": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::turn_off_child_lock%]", + "abnormal": "[%key:component::xiaomi_ble::device_automation::trigger_subtype::abnormal%]" + } + } + } + }, "motion": { "state_attributes": { "event_type": { diff --git a/requirements_all.txt b/requirements_all.txt index 73440b45d50..db14d8f521d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2866,7 +2866,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.25.2 +xiaomi-ble==0.26.1 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8fc51989fbf..a3d4c19621b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2204,7 +2204,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.25.2 +xiaomi-ble==0.26.1 # homeassistant.components.knx xknx==2.12.2 diff --git a/tests/components/xiaomi_ble/test_device_trigger.py b/tests/components/xiaomi_ble/test_device_trigger.py index 6c6ad1c0f94..49619af618e 100644 --- a/tests/components/xiaomi_ble/test_device_trigger.py +++ b/tests/components/xiaomi_ble/test_device_trigger.py @@ -75,6 +75,58 @@ async def test_event_button_press(hass: HomeAssistant) -> None: await hass.async_block_till_done() +async def test_event_unlock_outside_the_door(hass: HomeAssistant) -> None: + """Make sure that a unlock outside the door event is fired.""" + mac = "D7:1F:44:EB:8A:91" + entry = await _async_setup_xiaomi_device(hass, mac) + events = async_capture_events(hass, "xiaomi_ble_event") + + # Emit button press event + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + mac, + b"PD\x9e\x06C\x91\x8a\xebD\x1f\xd7\x0b\x00\t" b" \x02\x00\x01\x80|D/a", + ), + ) + + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["address"] == "D7:1F:44:EB:8A:91" + assert events[0].data["event_type"] == "unlock_outside_the_door" + assert events[0].data["event_properties"] is None + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_event_successful_fingerprint_match_the_door(hass: HomeAssistant) -> None: + """Make sure that a successful fingerprint match event is fired.""" + mac = "D7:1F:44:EB:8A:91" + entry = await _async_setup_xiaomi_device(hass, mac) + events = async_capture_events(hass, "xiaomi_ble_event") + + # Emit button press event + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + mac, + b"PD\x9e\x06B\x91\x8a\xebD\x1f\xd7" b"\x06\x00\x05\xff\xff\xff\xff\x00", + ), + ) + + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["address"] == "D7:1F:44:EB:8A:91" + assert events[0].data["event_type"] == "match_successful" + assert events[0].data["event_properties"] is None + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + async def test_event_motion_detected(hass: HomeAssistant) -> None: """Make sure that a motion detected event is fired.""" mac = "DE:70:E8:B2:39:0C" @@ -204,6 +256,47 @@ async def test_get_triggers_double_button(hass: HomeAssistant) -> None: await hass.async_block_till_done() +async def test_get_triggers_lock(hass: HomeAssistant) -> None: + """Test that we get the expected triggers from a Xiaomi BLE lock with fingerprint scanner.""" + mac = "98:0C:33:A3:04:3D" + data = {"bindkey": "54d84797cb77f9538b224b305c877d1e"} + entry = await _async_setup_xiaomi_device(hass, mac, data) + events = async_capture_events(hass, "xiaomi_ble_event") + + # Emit unlock inside the door event so it creates the device in the registry + inject_bluetooth_service_info_bleak( + hass, + make_advertisement( + mac, + b"\x48\x55\xc2\x11\x16\x50\x68\xb6\xfe\x3c\x87" + b"\x80\x95\xc8\xa5\x83\x4f\x00\x00\x00\x46\x32\x21\xc6", + ), + ) + + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + + dev_reg = async_get_dev_reg(hass) + device = dev_reg.async_get_device(identifiers={get_device_id(mac)}) + assert device + expected_trigger = { + CONF_PLATFORM: "device", + CONF_DOMAIN: DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: "fingerprint", + CONF_SUBTYPE: "skin_is_too_dry", + "metadata": {}, + } + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, device.id + ) + assert expected_trigger in triggers + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + async def test_get_triggers_motion(hass: HomeAssistant) -> None: """Test that we get the expected triggers from a Xiaomi BLE motion sensor.""" mac = "DE:70:E8:B2:39:0C" diff --git a/tests/components/xiaomi_ble/test_event.py b/tests/components/xiaomi_ble/test_event.py index 785c336106e..1de5859c35e 100644 --- a/tests/components/xiaomi_ble/test_event.py +++ b/tests/components/xiaomi_ble/test_event.py @@ -4,7 +4,7 @@ import pytest from homeassistant.components.event import ATTR_EVENT_TYPE from homeassistant.components.xiaomi_ble.const import DOMAIN -from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE +from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from . import make_advertisement @@ -202,3 +202,94 @@ async def test_events( assert attributes[ATTR_EVENT_TYPE] == meas[ATTR_EVENT_TYPE] assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() + + +async def test_xiaomi_fingerprint(hass: HomeAssistant) -> None: + """Make sure that fingerprint reader events are correctly mapped.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="D7:1F:44:EB:8A:91", + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + + inject_bluetooth_service_info( + hass, + make_advertisement( + "D7:1F:44:EB:8A:91", + b"PD\x9e\x06B\x91\x8a\xebD\x1f\xd7" b"\x06\x00\x05\xff\xff\xff\xff\x00", + ), + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 3 + + sensor = hass.states.get("sensor.door_lock_8a91_key_id") + sensor_attr = sensor.attributes + assert sensor.state == "unknown operator" + assert sensor_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Key id" + + binary_sensor = hass.states.get("binary_sensor.door_lock_8a91_fingerprint") + binary_sensor_attribtes = binary_sensor.attributes + assert binary_sensor.state == STATE_ON + assert binary_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Fingerprint" + + event = hass.states.get("event.door_lock_8a91_fingerprint") + event_attr = event.attributes + assert event_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Fingerprint" + assert event_attr[ATTR_EVENT_TYPE] == "match_successful" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + +async def test_xiaomi_lock(hass: HomeAssistant) -> None: + """Make sure that lock events are correctly mapped.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="D7:1F:44:EB:8A:91", + ) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + + inject_bluetooth_service_info( + hass, + make_advertisement( + "D7:1F:44:EB:8A:91", + b"PD\x9e\x06C\x91\x8a\xebD\x1f\xd7\x0b\x00\t" b" \x02\x00\x01\x80|D/a", + ), + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 4 + + event = hass.states.get("event.door_lock_8a91_lock") + event_attr = event.attributes + assert event_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Lock" + assert event_attr[ATTR_EVENT_TYPE] == "unlock_outside_the_door" + + sensor = hass.states.get("sensor.door_lock_8a91_lock_method") + sensor_attr = sensor.attributes + assert sensor.state == "biometrics" + assert sensor_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Lock method" + + sensor = hass.states.get("sensor.door_lock_8a91_key_id") + sensor_attr = sensor.attributes + assert sensor.state == "Fingerprint key id 2" + assert sensor_attr[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Key id" + + binary_sensor = hass.states.get("binary_sensor.door_lock_8a91_lock") + binary_sensor_attribtes = binary_sensor.attributes + assert binary_sensor.state == STATE_ON + assert binary_sensor_attribtes[ATTR_FRIENDLY_NAME] == "Door Lock 8A91 Lock" + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() From c04438caaee1f619163526ba1563e1e6f36ae949 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 10:50:32 +0100 Subject: [PATCH 0639/1691] Remove entity description mixin in Freebox (#112766) --- homeassistant/components/freebox/button.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/freebox/button.py b/homeassistant/components/freebox/button.py index 046003b209d..79e3c98b8b7 100644 --- a/homeassistant/components/freebox/button.py +++ b/homeassistant/components/freebox/button.py @@ -19,20 +19,13 @@ from .const import DOMAIN from .router import FreeboxRouter -@dataclass(frozen=True) -class FreeboxButtonRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class FreeboxButtonEntityDescription(ButtonEntityDescription): + """Class describing Freebox button entities.""" async_press: Callable[[FreeboxRouter], Awaitable] -@dataclass(frozen=True) -class FreeboxButtonEntityDescription( - ButtonEntityDescription, FreeboxButtonRequiredKeysMixin -): - """Class describing Freebox button entities.""" - - BUTTON_DESCRIPTIONS: tuple[FreeboxButtonEntityDescription, ...] = ( FreeboxButtonEntityDescription( key="reboot", From 34d316e7b5b4465aaddcefa2d58dae02a9bb9afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Cla=C3=9Fen?= Date: Sun, 10 Mar 2024 11:02:25 +0100 Subject: [PATCH 0640/1691] Update the numato-gpio dependency of the numato integration to v0.12.0 (#112272) * Update the numato-gpio dependency of the numato integration to v0.12.0 * Augment numato integration manifest with integration_type Fulfills a requirement in the PR checklist. --- homeassistant/components/numato/manifest.json | 3 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/numato/manifest.json b/homeassistant/components/numato/manifest.json index 7dcbeae1f21..e6efcea5315 100644 --- a/homeassistant/components/numato/manifest.json +++ b/homeassistant/components/numato/manifest.json @@ -3,7 +3,8 @@ "name": "Numato USB GPIO Expander", "codeowners": ["@clssn"], "documentation": "https://www.home-assistant.io/integrations/numato", + "integration_type": "hub", "iot_class": "local_push", "loggers": ["numato_gpio"], - "requirements": ["numato-gpio==0.10.0"] + "requirements": ["numato-gpio==0.12.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index db14d8f521d..6a0fc5031b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1403,7 +1403,7 @@ nsw-fuel-api-client==1.1.0 nuheat==1.0.1 # homeassistant.components.numato -numato-gpio==0.10.0 +numato-gpio==0.12.0 # homeassistant.components.compensation # homeassistant.components.iqvia diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a3d4c19621b..ac87156f109 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1121,7 +1121,7 @@ nsw-fuel-api-client==1.1.0 nuheat==1.0.1 # homeassistant.components.numato -numato-gpio==0.10.0 +numato-gpio==0.12.0 # homeassistant.components.compensation # homeassistant.components.iqvia From 201f7333945450605b066296458d1261c70a2fd0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 15:21:12 +0100 Subject: [PATCH 0641/1691] Remove entity description mixin in Private BLE Device (#112922) --- .../components/private_ble_device/sensor.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/private_ble_device/sensor.py b/homeassistant/components/private_ble_device/sensor.py index d186563da5e..e2c4fb0c7da 100644 --- a/homeassistant/components/private_ble_device/sensor.py +++ b/homeassistant/components/private_ble_device/sensor.py @@ -1,4 +1,4 @@ -"""Support for iBeacon device sensors.""" +"""Support for Private BLE Device sensors.""" from __future__ import annotations @@ -27,22 +27,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .entity import BasePrivateDeviceEntity -@dataclass(frozen=True) -class PrivateDeviceSensorEntityDescriptionRequired: - """Required domain specific fields for sensor entity.""" +@dataclass(frozen=True, kw_only=True) +class PrivateDeviceSensorEntityDescription(SensorEntityDescription): + """Describes sensor entity.""" value_fn: Callable[ [HomeAssistant, bluetooth.BluetoothServiceInfoBleak], str | int | float | None ] -@dataclass(frozen=True) -class PrivateDeviceSensorEntityDescription( - SensorEntityDescription, PrivateDeviceSensorEntityDescriptionRequired -): - """Describes sensor entity.""" - - SENSOR_DESCRIPTIONS = ( PrivateDeviceSensorEntityDescription( key="rssi", From f668dfecb255c882709968e7b558c68c1ffe124e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 16:43:43 +0100 Subject: [PATCH 0642/1691] Remove entity description mixin in Netgear (#112911) --- homeassistant/components/netgear/button.py | 13 +++---------- homeassistant/components/netgear/switch.py | 12 +++++------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/netgear/button.py b/homeassistant/components/netgear/button.py index 307bfab92d4..e5b9ec209c7 100644 --- a/homeassistant/components/netgear/button.py +++ b/homeassistant/components/netgear/button.py @@ -20,20 +20,13 @@ from .entity import NetgearRouterCoordinatorEntity from .router import NetgearRouter -@dataclass(frozen=True) -class NetgearButtonEntityDescriptionRequired: - """Required attributes of NetgearButtonEntityDescription.""" +@dataclass(frozen=True, kw_only=True) +class NetgearButtonEntityDescription(ButtonEntityDescription): + """Class describing Netgear button entities.""" action: Callable[[NetgearRouter], Callable[[], Coroutine[Any, Any, None]]] -@dataclass(frozen=True) -class NetgearButtonEntityDescription( - ButtonEntityDescription, NetgearButtonEntityDescriptionRequired -): - """Class describing Netgear button entities.""" - - BUTTONS = [ NetgearButtonEntityDescription( key="reboot", diff --git a/homeassistant/components/netgear/switch.py b/homeassistant/components/netgear/switch.py index 34a4258b085..e39d4030f3f 100644 --- a/homeassistant/components/netgear/switch.py +++ b/homeassistant/components/netgear/switch.py @@ -36,17 +36,15 @@ SWITCH_TYPES = [ class NetgearSwitchEntityDescriptionRequired: """Required attributes of NetgearSwitchEntityDescription.""" + +@dataclass(frozen=True, kw_only=True) +class NetgearSwitchEntityDescription(SwitchEntityDescription): + """Class describing Netgear Switch entities.""" + update: Callable[[NetgearRouter], bool] action: Callable[[NetgearRouter], bool] -@dataclass(frozen=True) -class NetgearSwitchEntityDescription( - SwitchEntityDescription, NetgearSwitchEntityDescriptionRequired -): - """Class describing Netgear Switch entities.""" - - ROUTER_SWITCH_TYPES = [ NetgearSwitchEntityDescription( key="access_control", From ae003c21c8839434add531d4f3250f215b0f7258 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 17:10:44 +0100 Subject: [PATCH 0643/1691] Remove entity description mixin in SFR Box (#112937) --- homeassistant/components/sfr_box/binary_sensor.py | 13 +++---------- homeassistant/components/sfr_box/button.py | 11 +++-------- homeassistant/components/sfr_box/sensor.py | 11 +++-------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/sfr_box/binary_sensor.py b/homeassistant/components/sfr_box/binary_sensor.py index ca81129f699..7ddcb16c9f8 100644 --- a/homeassistant/components/sfr_box/binary_sensor.py +++ b/homeassistant/components/sfr_box/binary_sensor.py @@ -27,20 +27,13 @@ from .models import DomainData _T = TypeVar("_T") -@dataclass(frozen=True) -class SFRBoxBinarySensorMixin(Generic[_T]): - """Mixin for SFR Box sensors.""" +@dataclass(frozen=True, kw_only=True) +class SFRBoxBinarySensorEntityDescription(BinarySensorEntityDescription, Generic[_T]): + """Description for SFR Box binary sensors.""" value_fn: Callable[[_T], bool | None] -@dataclass(frozen=True) -class SFRBoxBinarySensorEntityDescription( - BinarySensorEntityDescription, SFRBoxBinarySensorMixin[_T] -): - """Description for SFR Box binary sensors.""" - - DSL_SENSOR_TYPES: tuple[SFRBoxBinarySensorEntityDescription[DslInfo], ...] = ( SFRBoxBinarySensorEntityDescription[DslInfo]( key="status", diff --git a/homeassistant/components/sfr_box/button.py b/homeassistant/components/sfr_box/button.py index 5f26bc82f1b..6dc91149d86 100644 --- a/homeassistant/components/sfr_box/button.py +++ b/homeassistant/components/sfr_box/button.py @@ -50,18 +50,13 @@ def with_error_wrapping( return wrapper -@dataclass(frozen=True) -class SFRBoxButtonMixin: - """Mixin for SFR Box buttons.""" +@dataclass(frozen=True, kw_only=True) +class SFRBoxButtonEntityDescription(ButtonEntityDescription): + """Description for SFR Box buttons.""" async_press: Callable[[SFRBox], Coroutine[None, None, None]] -@dataclass(frozen=True) -class SFRBoxButtonEntityDescription(ButtonEntityDescription, SFRBoxButtonMixin): - """Description for SFR Box buttons.""" - - BUTTON_TYPES: tuple[SFRBoxButtonEntityDescription, ...] = ( SFRBoxButtonEntityDescription( async_press=lambda x: x.system_reboot(), diff --git a/homeassistant/components/sfr_box/sensor.py b/homeassistant/components/sfr_box/sensor.py index 626b4181486..403ec762768 100644 --- a/homeassistant/components/sfr_box/sensor.py +++ b/homeassistant/components/sfr_box/sensor.py @@ -33,18 +33,13 @@ from .models import DomainData _T = TypeVar("_T") -@dataclass(frozen=True) -class SFRBoxSensorMixin(Generic[_T]): - """Mixin for SFR Box sensors.""" +@dataclass(frozen=True, kw_only=True) +class SFRBoxSensorEntityDescription(SensorEntityDescription, Generic[_T]): + """Description for SFR Box sensors.""" value_fn: Callable[[_T], StateType] -@dataclass(frozen=True) -class SFRBoxSensorEntityDescription(SensorEntityDescription, SFRBoxSensorMixin[_T]): - """Description for SFR Box sensors.""" - - DSL_SENSOR_TYPES: tuple[SFRBoxSensorEntityDescription[DslInfo], ...] = ( SFRBoxSensorEntityDescription[DslInfo]( key="linemode", From 85bc72f9f1c75e2bb77fbf5a9767fdc749cc2617 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 17:27:47 +0100 Subject: [PATCH 0644/1691] Remove entity description mixin in Renault (#112925) * Remove entity description mixin in Renault * Update homeassistant/components/renault/sensor.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/renault/button.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --------- Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/components/renault/button.py | 14 +++----------- homeassistant/components/renault/select.py | 15 ++++----------- homeassistant/components/renault/sensor.py | 16 ++++------------ 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/renault/button.py b/homeassistant/components/renault/button.py index c1b94fcf6f2..9a6e1d76df6 100644 --- a/homeassistant/components/renault/button.py +++ b/homeassistant/components/renault/button.py @@ -16,19 +16,11 @@ from .entity import RenaultEntity from .renault_hub import RenaultHub -@dataclass(frozen=True) -class RenaultButtonRequiredKeysMixin: - """Mixin for required keys.""" - - async_press: Callable[[RenaultButtonEntity], Coroutine[Any, Any, Any]] - - -@dataclass(frozen=True) -class RenaultButtonEntityDescription( - ButtonEntityDescription, RenaultButtonRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class RenaultButtonEntityDescription(ButtonEntityDescription): """Class describing Renault button entities.""" + async_press: Callable[[RenaultButtonEntity], Coroutine[Any, Any, Any]] requires_electricity: bool = False diff --git a/homeassistant/components/renault/select.py b/homeassistant/components/renault/select.py index ffae27c5e55..f6c8f73d24b 100644 --- a/homeassistant/components/renault/select.py +++ b/homeassistant/components/renault/select.py @@ -18,21 +18,14 @@ from .entity import RenaultDataEntity, RenaultDataEntityDescription from .renault_hub import RenaultHub -@dataclass(frozen=True) -class RenaultSelectRequiredKeysMixin: - """Mixin for required keys.""" - - data_key: str - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RenaultSelectEntityDescription( - SelectEntityDescription, - RenaultDataEntityDescription, - RenaultSelectRequiredKeysMixin, + SelectEntityDescription, RenaultDataEntityDescription ): """Class describing Renault select entities.""" + data_key: str + async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/renault/sensor.py b/homeassistant/components/renault/sensor.py index f806ca374c2..352fddb8d8b 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -43,22 +43,14 @@ from .renault_hub import RenaultHub from .renault_vehicle import RenaultVehicleProxy -@dataclass(frozen=True) -class RenaultSensorRequiredKeysMixin(Generic[T]): - """Mixin for required keys.""" - - data_key: str - entity_class: type[RenaultSensor[T]] - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RenaultSensorEntityDescription( - SensorEntityDescription, - RenaultDataEntityDescription, - RenaultSensorRequiredKeysMixin[T], + SensorEntityDescription, RenaultDataEntityDescription, Generic[T] ): """Class describing Renault sensor entities.""" + data_key: str + entity_class: type[RenaultSensor[T]] condition_lambda: Callable[[RenaultVehicleProxy], bool] | None = None requires_fuel: bool = False value_lambda: Callable[[RenaultSensor[T]], StateType | datetime] | None = None From b59bba8951966d1ad9cbaad9a9810a58ca00675b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 17:46:40 +0100 Subject: [PATCH 0645/1691] Remove entity description mixin in Roku (#112931) --- .../components/roku/binary_sensor.py | 13 +++---------- homeassistant/components/roku/select.py | 19 ++++++------------- homeassistant/components/roku/sensor.py | 13 +++---------- 3 files changed, 12 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/roku/binary_sensor.py b/homeassistant/components/roku/binary_sensor.py index f58c9f9263f..17d44a0a160 100644 --- a/homeassistant/components/roku/binary_sensor.py +++ b/homeassistant/components/roku/binary_sensor.py @@ -20,20 +20,13 @@ from .const import DOMAIN from .entity import RokuEntity -@dataclass(frozen=True) -class RokuBinarySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class RokuBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes a Roku binary sensor entity.""" value_fn: Callable[[RokuDevice], bool | None] -@dataclass(frozen=True) -class RokuBinarySensorEntityDescription( - BinarySensorEntityDescription, RokuBinarySensorEntityDescriptionMixin -): - """Describes a Roku binary sensor entity.""" - - BINARY_SENSORS: tuple[RokuBinarySensorEntityDescription, ...] = ( RokuBinarySensorEntityDescription( key="headphones_connected", diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py index 96f9b4c3ab8..4c900579739 100644 --- a/homeassistant/components/roku/select.py +++ b/homeassistant/components/roku/select.py @@ -19,15 +19,6 @@ from .entity import RokuEntity from .helpers import format_channel_name, roku_exception_handler -@dataclass(frozen=True) -class RokuSelectEntityDescriptionMixin: - """Mixin for required keys.""" - - options_fn: Callable[[RokuDevice], list[str]] - value_fn: Callable[[RokuDevice], str | None] - set_fn: Callable[[RokuDevice, Roku, str], Awaitable[None]] - - def _get_application_name(device: RokuDevice) -> str | None: if device.app is None or device.app.name is None: return None @@ -86,12 +77,14 @@ async def _tune_channel(device: RokuDevice, roku: Roku, value: str) -> None: await roku.tune(_channel.number) -@dataclass(frozen=True) -class RokuSelectEntityDescription( - SelectEntityDescription, RokuSelectEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class RokuSelectEntityDescription(SelectEntityDescription): """Describes Roku select entity.""" + options_fn: Callable[[RokuDevice], list[str]] + value_fn: Callable[[RokuDevice], str | None] + set_fn: Callable[[RokuDevice, Roku, str], Awaitable[None]] + ENTITIES: tuple[RokuSelectEntityDescription, ...] = ( RokuSelectEntityDescription( diff --git a/homeassistant/components/roku/sensor.py b/homeassistant/components/roku/sensor.py index 4d81a3cfe80..1b8b429ef16 100644 --- a/homeassistant/components/roku/sensor.py +++ b/homeassistant/components/roku/sensor.py @@ -18,20 +18,13 @@ from .coordinator import RokuDataUpdateCoordinator from .entity import RokuEntity -@dataclass(frozen=True) -class RokuSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class RokuSensorEntityDescription(SensorEntityDescription): + """Describes Roku sensor entity.""" value_fn: Callable[[RokuDevice], str | None] -@dataclass(frozen=True) -class RokuSensorEntityDescription( - SensorEntityDescription, RokuSensorEntityDescriptionMixin -): - """Describes Roku sensor entity.""" - - SENSORS: tuple[RokuSensorEntityDescription, ...] = ( RokuSensorEntityDescription( key="active_app", From 113df1ab62f56980970926ead8ff6d5e9183c562 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 10 Mar 2024 11:04:17 -0600 Subject: [PATCH 0646/1691] Streamline Notion config entry updates (refresh token and user ID) (#112832) --- homeassistant/components/notion/__init__.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index 9587f22f6cb..9b62eb840c0 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -166,9 +166,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except NotionError as err: raise ConfigEntryNotReady("Config entry failed to load") from err - # Always update the config entry with the latest refresh token and user UUID: - entry_updates["data"][CONF_REFRESH_TOKEN] = client.refresh_token - entry_updates["data"][CONF_USER_UUID] = client.user_uuid + # Update the Notion user UUID and refresh token if they've changed: + for key, value in ( + (CONF_REFRESH_TOKEN, client.refresh_token), + (CONF_USER_UUID, client.user_uuid), + ): + if entry.data[key] == value: + continue + entry_updates["data"][key] = value + + hass.config_entries.async_update_entry(entry, **entry_updates) @callback def async_save_refresh_token(refresh_token: str) -> None: @@ -181,12 +188,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create a callback to save the refresh token when it changes: entry.async_on_unload(client.add_refresh_token_callback(async_save_refresh_token)) - # Save the client's refresh token if it's different than what we already have: - if (token := client.refresh_token) and token != entry.data[CONF_REFRESH_TOKEN]: - async_save_refresh_token(token) - - hass.config_entries.async_update_entry(entry, **entry_updates) - async def async_update() -> NotionData: """Get the latest data from the Notion API.""" data = NotionData(hass=hass, entry=entry) From dec98d424f27ccd697eac926a20cfe6e72cc1db2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 18:06:17 +0100 Subject: [PATCH 0647/1691] Remove entity description mixin in Overkiz (#112914) --- .../components/overkiz/alarm_control_panel.py | 13 +++---------- homeassistant/components/overkiz/binary_sensor.py | 13 +++---------- homeassistant/components/overkiz/number.py | 11 +++-------- homeassistant/components/overkiz/select.py | 11 +++-------- homeassistant/components/overkiz/switch.py | 12 +++--------- 5 files changed, 15 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/overkiz/alarm_control_panel.py b/homeassistant/components/overkiz/alarm_control_panel.py index ccb7c5d919e..1f088874d43 100644 --- a/homeassistant/components/overkiz/alarm_control_panel.py +++ b/homeassistant/components/overkiz/alarm_control_panel.py @@ -36,20 +36,13 @@ from .coordinator import OverkizDataUpdateCoordinator from .entity import OverkizDescriptiveEntity -@dataclass(frozen=True) -class OverkizAlarmDescriptionMixin: - """Define an entity description mixin for switch entities.""" +@dataclass(frozen=True, kw_only=True) +class OverkizAlarmDescription(AlarmControlPanelEntityDescription): + """Class to describe an Overkiz alarm control panel.""" supported_features: AlarmControlPanelEntityFeature fn_state: Callable[[Callable[[str], OverkizStateType]], str] - -@dataclass(frozen=True) -class OverkizAlarmDescription( - AlarmControlPanelEntityDescription, OverkizAlarmDescriptionMixin -): - """Class to describe an Overkiz alarm control panel.""" - alarm_disarm: str | None = None alarm_disarm_args: OverkizStateType | list[OverkizStateType] = None alarm_arm_home: str | None = None diff --git a/homeassistant/components/overkiz/binary_sensor.py b/homeassistant/components/overkiz/binary_sensor.py index 62bf88c5f9c..b4365a66e06 100644 --- a/homeassistant/components/overkiz/binary_sensor.py +++ b/homeassistant/components/overkiz/binary_sensor.py @@ -23,20 +23,13 @@ from .const import DOMAIN, IGNORED_OVERKIZ_DEVICES from .entity import OverkizDescriptiveEntity -@dataclass(frozen=True) -class OverkizBinarySensorDescriptionMixin: - """Define an entity description mixin for binary sensor entities.""" +@dataclass(frozen=True, kw_only=True) +class OverkizBinarySensorDescription(BinarySensorEntityDescription): + """Class to describe an Overkiz binary sensor.""" value_fn: Callable[[OverkizStateType], bool] -@dataclass(frozen=True) -class OverkizBinarySensorDescription( - BinarySensorEntityDescription, OverkizBinarySensorDescriptionMixin -): - """Class to describe an Overkiz binary sensor.""" - - BINARY_SENSOR_DESCRIPTIONS: list[OverkizBinarySensorDescription] = [ # RainSensor/RainSensor OverkizBinarySensorDescription( diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index 1842a6cfcd2..e9664527335 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -28,17 +28,12 @@ BOOST_MODE_DURATION_DELAY = 1 OPERATING_MODE_DELAY = 3 -@dataclass(frozen=True) -class OverkizNumberDescriptionMixin: - """Define an entity description mixin for number entities.""" +@dataclass(frozen=True, kw_only=True) +class OverkizNumberDescription(NumberEntityDescription): + """Class to describe an Overkiz number.""" command: str - -@dataclass(frozen=True) -class OverkizNumberDescription(NumberEntityDescription, OverkizNumberDescriptionMixin): - """Class to describe an Overkiz number.""" - min_value_state_name: str | None = None max_value_state_name: str | None = None inverted: bool = False diff --git a/homeassistant/components/overkiz/select.py b/homeassistant/components/overkiz/select.py index 78bcb3b3e0a..155c72a7f7a 100644 --- a/homeassistant/components/overkiz/select.py +++ b/homeassistant/components/overkiz/select.py @@ -18,18 +18,13 @@ from .const import DOMAIN, IGNORED_OVERKIZ_DEVICES from .entity import OverkizDescriptiveEntity -@dataclass(frozen=True) -class OverkizSelectDescriptionMixin: - """Define an entity description mixin for select entities.""" +@dataclass(frozen=True, kw_only=True) +class OverkizSelectDescription(SelectEntityDescription): + """Class to describe an Overkiz select entity.""" select_option: Callable[[str, Callable[..., Awaitable[None]]], Awaitable[None]] -@dataclass(frozen=True) -class OverkizSelectDescription(SelectEntityDescription, OverkizSelectDescriptionMixin): - """Class to describe an Overkiz select entity.""" - - def _select_option_open_closed_pedestrian( option: str, execute_command: Callable[..., Awaitable[None]] ) -> Awaitable[None]: diff --git a/homeassistant/components/overkiz/switch.py b/homeassistant/components/overkiz/switch.py index 185c20824eb..5d1fe0493ac 100644 --- a/homeassistant/components/overkiz/switch.py +++ b/homeassistant/components/overkiz/switch.py @@ -25,18 +25,12 @@ from .const import DOMAIN from .entity import OverkizDescriptiveEntity -@dataclass(frozen=True) -class OverkizSwitchDescriptionMixin: - """Define an entity description mixin for switch entities.""" +@dataclass(frozen=True, kw_only=True) +class OverkizSwitchDescription(SwitchEntityDescription): + """Class to describe an Overkiz switch.""" turn_on: str turn_off: str - - -@dataclass(frozen=True) -class OverkizSwitchDescription(SwitchEntityDescription, OverkizSwitchDescriptionMixin): - """Class to describe an Overkiz switch.""" - is_on: Callable[[Callable[[str], OverkizStateType]], bool] | None = None turn_on_args: OverkizStateType | list[OverkizStateType] | None = None turn_off_args: OverkizStateType | list[OverkizStateType] | None = None From 13b8fd41493b3d80e57cadfdac1c21b7f728c667 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 18:14:47 +0100 Subject: [PATCH 0648/1691] Remove entity description mixin in Trafikverket Weatherstation (#112957) --- .../trafikverket_weatherstation/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index 6fd63c83f4e..bd15c34ff01 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -43,20 +43,13 @@ PRECIPITATION_TYPE = [ ] -@dataclass(frozen=True) -class TrafikverketRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TrafikverketSensorEntityDescription(SensorEntityDescription): + """Describes Trafikverket sensor entity.""" value_fn: Callable[[WeatherStationInfo], StateType | datetime] -@dataclass(frozen=True) -class TrafikverketSensorEntityDescription( - SensorEntityDescription, TrafikverketRequiredKeysMixin -): - """Describes Trafikverket sensor entity.""" - - def add_utc_timezone(date_time: datetime | None) -> datetime | None: """Add UTC timezone if datetime.""" if date_time: From 39c617eee6626646d5b436f7d8a438775a0394d9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 18:15:18 +0100 Subject: [PATCH 0649/1691] Remove entity description mixin in Trafikverket Train (#112956) --- .../components/trafikverket_train/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/trafikverket_train/sensor.py b/homeassistant/components/trafikverket_train/sensor.py index adc8250b8d9..22d8aba4725 100644 --- a/homeassistant/components/trafikverket_train/sensor.py +++ b/homeassistant/components/trafikverket_train/sensor.py @@ -26,20 +26,13 @@ from .coordinator import TrainData, TVDataUpdateCoordinator ATTR_PRODUCT_FILTER = "product_filter" -@dataclass(frozen=True) -class TrafikverketRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TrafikverketSensorEntityDescription(SensorEntityDescription): + """Describes Trafikverket sensor entity.""" value_fn: Callable[[TrainData], StateType | datetime] -@dataclass(frozen=True) -class TrafikverketSensorEntityDescription( - SensorEntityDescription, TrafikverketRequiredKeysMixin -): - """Describes Trafikverket sensor entity.""" - - SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_time", From a64f043a9322a559ac75869b3f9d5a1c922be7e3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 18:15:47 +0100 Subject: [PATCH 0650/1691] Remove entity description mixin in Trafikverket Ferry (#112955) --- .../components/trafikverket_ferry/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/trafikverket_ferry/sensor.py b/homeassistant/components/trafikverket_ferry/sensor.py index ff6b9d2e1ca..93f2d1987b6 100644 --- a/homeassistant/components/trafikverket_ferry/sensor.py +++ b/homeassistant/components/trafikverket_ferry/sensor.py @@ -32,21 +32,14 @@ ATTR_OTHER_INFO = "other_info" SCAN_INTERVAL = timedelta(minutes=5) -@dataclass(frozen=True) -class TrafikverketRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TrafikverketSensorEntityDescription(SensorEntityDescription): + """Describes Trafikverket sensor entity.""" value_fn: Callable[[dict[str, Any]], StateType | datetime] info_fn: Callable[[dict[str, Any]], StateType | list] | None -@dataclass(frozen=True) -class TrafikverketSensorEntityDescription( - SensorEntityDescription, TrafikverketRequiredKeysMixin -): - """Describes Trafikverket sensor entity.""" - - SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = ( TrafikverketSensorEntityDescription( key="departure_time", From 5f4881cb2807d5b466e9ad51925d29fcb2af045b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 18:16:18 +0100 Subject: [PATCH 0651/1691] Remove entity description mixin in Trafikverket Camera (#112954) --- .../components/trafikverket_camera/binary_sensor.py | 13 +++---------- .../components/trafikverket_camera/sensor.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/trafikverket_camera/binary_sensor.py b/homeassistant/components/trafikverket_camera/binary_sensor.py index d1c7c7fbc0b..56af099d54b 100644 --- a/homeassistant/components/trafikverket_camera/binary_sensor.py +++ b/homeassistant/components/trafikverket_camera/binary_sensor.py @@ -20,20 +20,13 @@ from .entity import TrafikverketCameraNonCameraEntity PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class DeviceBaseEntityDescriptionMixin: - """Mixin for required Trafikverket Camera base description keys.""" +@dataclass(frozen=True, kw_only=True) +class TVCameraSensorEntityDescription(BinarySensorEntityDescription): + """Describes Trafikverket Camera binary sensor entity.""" value_fn: Callable[[CameraData], bool | None] -@dataclass(frozen=True) -class TVCameraSensorEntityDescription( - BinarySensorEntityDescription, DeviceBaseEntityDescriptionMixin -): - """Describes Trafikverket Camera binary sensor entity.""" - - BINARY_SENSOR_TYPE = TVCameraSensorEntityDescription( key="active", translation_key="active", diff --git a/homeassistant/components/trafikverket_camera/sensor.py b/homeassistant/components/trafikverket_camera/sensor.py index 4cdc809c338..f41eb1fa2a2 100644 --- a/homeassistant/components/trafikverket_camera/sensor.py +++ b/homeassistant/components/trafikverket_camera/sensor.py @@ -24,20 +24,13 @@ from .entity import TrafikverketCameraNonCameraEntity PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class DeviceBaseEntityDescriptionMixin: - """Mixin for required Trafikverket Camera base description keys.""" +@dataclass(frozen=True, kw_only=True) +class TVCameraSensorEntityDescription(SensorEntityDescription): + """Describes Trafikverket Camera sensor entity.""" value_fn: Callable[[CameraData], StateType | datetime] -@dataclass(frozen=True) -class TVCameraSensorEntityDescription( - SensorEntityDescription, DeviceBaseEntityDescriptionMixin -): - """Describes Trafikverket Camera sensor entity.""" - - SENSOR_TYPES: tuple[TVCameraSensorEntityDescription, ...] = ( TVCameraSensorEntityDescription( key="direction", From 049f0f5e3bd3eafe6c35b9fd79e859f116abd47a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 18:17:43 +0100 Subject: [PATCH 0652/1691] Remove entity description mixin in Sensibo (#112936) --- .../components/sensibo/binary_sensor.py | 26 +++++-------------- homeassistant/components/sensibo/button.py | 13 +++------- homeassistant/components/sensibo/number.py | 13 +++------- homeassistant/components/sensibo/select.py | 13 +++------- homeassistant/components/sensibo/sensor.py | 26 +++++-------------- homeassistant/components/sensibo/switch.py | 13 +++------- homeassistant/components/sensibo/update.py | 13 +++------- 7 files changed, 27 insertions(+), 90 deletions(-) diff --git a/homeassistant/components/sensibo/binary_sensor.py b/homeassistant/components/sensibo/binary_sensor.py index fa0ea9231f9..a34c7884ac7 100644 --- a/homeassistant/components/sensibo/binary_sensor.py +++ b/homeassistant/components/sensibo/binary_sensor.py @@ -25,34 +25,20 @@ from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class MotionBaseEntityDescriptionMixin: - """Mixin for required Sensibo base description keys.""" +@dataclass(frozen=True, kw_only=True) +class SensiboMotionBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Sensibo Motion sensor entity.""" value_fn: Callable[[MotionSensor], bool | None] -@dataclass(frozen=True) -class DeviceBaseEntityDescriptionMixin: - """Mixin for required Sensibo base description keys.""" +@dataclass(frozen=True, kw_only=True) +class SensiboDeviceBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Sensibo Motion sensor entity.""" value_fn: Callable[[SensiboDevice], bool | None] -@dataclass(frozen=True) -class SensiboMotionBinarySensorEntityDescription( - BinarySensorEntityDescription, MotionBaseEntityDescriptionMixin -): - """Describes Sensibo Motion sensor entity.""" - - -@dataclass(frozen=True) -class SensiboDeviceBinarySensorEntityDescription( - BinarySensorEntityDescription, DeviceBaseEntityDescriptionMixin -): - """Describes Sensibo Motion sensor entity.""" - - FILTER_CLEAN_REQUIRED_DESCRIPTION = SensiboDeviceBinarySensorEntityDescription( key="filter_clean", translation_key="filter_clean", diff --git a/homeassistant/components/sensibo/button.py b/homeassistant/components/sensibo/button.py index f6d540f54d1..fbfabaa97fb 100644 --- a/homeassistant/components/sensibo/button.py +++ b/homeassistant/components/sensibo/button.py @@ -18,20 +18,13 @@ from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class SensiboEntityDescriptionMixin: - """Mixin values for Sensibo entities.""" +@dataclass(frozen=True, kw_only=True) +class SensiboButtonEntityDescription(ButtonEntityDescription): + """Class describing Sensibo Button entities.""" data_key: str -@dataclass(frozen=True) -class SensiboButtonEntityDescription( - ButtonEntityDescription, SensiboEntityDescriptionMixin -): - """Class describing Sensibo Button entities.""" - - DEVICE_BUTTON_TYPES = SensiboButtonEntityDescription( key="reset_filter", translation_key="reset_filter", diff --git a/homeassistant/components/sensibo/number.py b/homeassistant/components/sensibo/number.py index 35500664041..9c7b97ff79f 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -25,21 +25,14 @@ from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class SensiboEntityDescriptionMixin: - """Mixin values for Sensibo entities.""" +@dataclass(frozen=True, kw_only=True) +class SensiboNumberEntityDescription(NumberEntityDescription): + """Class describing Sensibo Number entities.""" remote_key: str value_fn: Callable[[SensiboDevice], float | None] -@dataclass(frozen=True) -class SensiboNumberEntityDescription( - NumberEntityDescription, SensiboEntityDescriptionMixin -): - """Class describing Sensibo Number entities.""" - - DEVICE_NUMBER_TYPES = ( SensiboNumberEntityDescription( key="calibration_temp", diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index 97fce5ae832..f3f8f55fc8e 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -21,9 +21,9 @@ from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class SensiboSelectDescriptionMixin: - """Mixin values for Sensibo entities.""" +@dataclass(frozen=True, kw_only=True) +class SensiboSelectEntityDescription(SelectEntityDescription): + """Class describing Sensibo Select entities.""" data_key: str value_fn: Callable[[SensiboDevice], str | None] @@ -31,13 +31,6 @@ class SensiboSelectDescriptionMixin: transformation: Callable[[SensiboDevice], dict | None] -@dataclass(frozen=True) -class SensiboSelectEntityDescription( - SelectEntityDescription, SensiboSelectDescriptionMixin -): - """Class describing Sensibo Select entities.""" - - DEVICE_SELECT_TYPES = ( SensiboSelectEntityDescription( key="horizontalSwing", diff --git a/homeassistant/components/sensibo/sensor.py b/homeassistant/components/sensibo/sensor.py index b58be0e5bdf..0a2b23b2cd9 100644 --- a/homeassistant/components/sensibo/sensor.py +++ b/homeassistant/components/sensibo/sensor.py @@ -37,35 +37,21 @@ from .entity import SensiboDeviceBaseEntity, SensiboMotionBaseEntity PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class MotionBaseEntityDescriptionMixin: - """Mixin for required Sensibo base description keys.""" +@dataclass(frozen=True, kw_only=True) +class SensiboMotionSensorEntityDescription(SensorEntityDescription): + """Describes Sensibo Motion sensor entity.""" value_fn: Callable[[MotionSensor], StateType] -@dataclass(frozen=True) -class DeviceBaseEntityDescriptionMixin: - """Mixin for required Sensibo base description keys.""" +@dataclass(frozen=True, kw_only=True) +class SensiboDeviceSensorEntityDescription(SensorEntityDescription): + """Describes Sensibo Device sensor entity.""" value_fn: Callable[[SensiboDevice], StateType | datetime] extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None] | None] | None -@dataclass(frozen=True) -class SensiboMotionSensorEntityDescription( - SensorEntityDescription, MotionBaseEntityDescriptionMixin -): - """Describes Sensibo Motion sensor entity.""" - - -@dataclass(frozen=True) -class SensiboDeviceSensorEntityDescription( - SensorEntityDescription, DeviceBaseEntityDescriptionMixin -): - """Describes Sensibo Device sensor entity.""" - - FILTER_LAST_RESET_DESCRIPTION = SensiboDeviceSensorEntityDescription( key="filter_last_reset", translation_key="filter_last_reset", diff --git a/homeassistant/components/sensibo/switch.py b/homeassistant/components/sensibo/switch.py index 7edb2d3f72e..b4e3dbf13fd 100644 --- a/homeassistant/components/sensibo/switch.py +++ b/homeassistant/components/sensibo/switch.py @@ -25,9 +25,9 @@ from .entity import SensiboDeviceBaseEntity, async_handle_api_call PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class DeviceBaseEntityDescriptionMixin: - """Mixin for required Sensibo Device description keys.""" +@dataclass(frozen=True, kw_only=True) +class SensiboDeviceSwitchEntityDescription(SwitchEntityDescription): + """Describes Sensibo Switch entity.""" value_fn: Callable[[SensiboDevice], bool | None] extra_fn: Callable[[SensiboDevice], dict[str, str | bool | None]] | None @@ -36,13 +36,6 @@ class DeviceBaseEntityDescriptionMixin: data_key: str -@dataclass(frozen=True) -class SensiboDeviceSwitchEntityDescription( - SwitchEntityDescription, DeviceBaseEntityDescriptionMixin -): - """Describes Sensibo Switch entity.""" - - DEVICE_SWITCH_TYPES: tuple[SensiboDeviceSwitchEntityDescription, ...] = ( SensiboDeviceSwitchEntityDescription( key="timer_on_switch", diff --git a/homeassistant/components/sensibo/update.py b/homeassistant/components/sensibo/update.py index 13c89e8c9bf..9376cd1eb38 100644 --- a/homeassistant/components/sensibo/update.py +++ b/homeassistant/components/sensibo/update.py @@ -24,21 +24,14 @@ from .entity import SensiboDeviceBaseEntity PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class DeviceBaseEntityDescriptionMixin: - """Mixin for required Sensibo base description keys.""" +@dataclass(frozen=True, kw_only=True) +class SensiboDeviceUpdateEntityDescription(UpdateEntityDescription): + """Describes Sensibo Update entity.""" value_version: Callable[[SensiboDevice], str | None] value_available: Callable[[SensiboDevice], str | None] -@dataclass(frozen=True) -class SensiboDeviceUpdateEntityDescription( - UpdateEntityDescription, DeviceBaseEntityDescriptionMixin -): - """Describes Sensibo Update entity.""" - - DEVICE_SENSOR_TYPES: tuple[SensiboDeviceUpdateEntityDescription, ...] = ( SensiboDeviceUpdateEntityDescription( key="fw_ver_available", From eb81bf1d490e6b847c90ee0dfcba2be459cdb104 Mon Sep 17 00:00:00 2001 From: Shai Ungar Date: Sun, 10 Mar 2024 19:57:28 +0200 Subject: [PATCH 0653/1691] Improve 17track tests (#112734) * 17Track tests * add 17Track sensor to coverage * extract repeated code * 1. _goto_future - call tick only once 2. change test name to reflect test 3. remove ifs from test * remove undersocre from _goto_future --- .coveragerc | 1 - tests/components/seventeentrack/__init__.py | 24 + tests/components/seventeentrack/conftest.py | 108 ++++ .../components/seventeentrack/test_sensor.py | 509 +++++++----------- 4 files changed, 317 insertions(+), 325 deletions(-) create mode 100644 tests/components/seventeentrack/conftest.py diff --git a/.coveragerc b/.coveragerc index f5f22096115..a595efd4bcc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1183,7 +1183,6 @@ omit = homeassistant/components/serial_pm/sensor.py homeassistant/components/sesame/lock.py homeassistant/components/seven_segments/image_processing.py - homeassistant/components/seventeentrack/sensor.py homeassistant/components/shodan/sensor.py homeassistant/components/sia/__init__.py homeassistant/components/sia/alarm_control_panel.py diff --git a/tests/components/seventeentrack/__init__.py b/tests/components/seventeentrack/__init__.py index 5cc3bf34871..61590f2bd3f 100644 --- a/tests/components/seventeentrack/__init__.py +++ b/tests/components/seventeentrack/__init__.py @@ -1 +1,25 @@ """Tests for the seventeentrack component.""" + +from datetime import timedelta + +from freezegun.api import FrozenDateTimeFactory + +from homeassistant.components.seventeentrack.sensor import DEFAULT_SCAN_INTERVAL +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from homeassistant.setup import async_setup_component + +from tests.common import async_fire_time_changed + + +async def init_integration(hass: HomeAssistant, config: ConfigType): + """Set up the seventeentrack integration in Home Assistant.""" + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + +async def goto_future(hass: HomeAssistant, freezer: FrozenDateTimeFactory): + """Move to future.""" + freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(minutes=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() diff --git a/tests/components/seventeentrack/conftest.py b/tests/components/seventeentrack/conftest.py new file mode 100644 index 00000000000..6d37e9f60eb --- /dev/null +++ b/tests/components/seventeentrack/conftest.py @@ -0,0 +1,108 @@ +"""Configuration for 17Track tests.""" + +from typing import Optional +from unittest.mock import AsyncMock, patch + +from py17track.package import Package +import pytest + +from homeassistant.components.seventeentrack.sensor import ( + CONF_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED, +) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +VALID_CONFIG_MINIMAL = { + "sensor": { + "platform": "seventeentrack", + CONF_USERNAME: "test", + CONF_PASSWORD: "test", + } +} + +INVALID_CONFIG = {"sensor": {"platform": "seventeentrack", "boom": "test"}} + +VALID_CONFIG_FULL = { + "sensor": { + "platform": "seventeentrack", + CONF_USERNAME: "test", + CONF_PASSWORD: "test", + CONF_SHOW_ARCHIVED: True, + CONF_SHOW_DELIVERED: True, + } +} + +VALID_CONFIG_FULL_NO_DELIVERED = { + "sensor": { + "platform": "seventeentrack", + CONF_USERNAME: "test", + CONF_PASSWORD: "test", + CONF_SHOW_ARCHIVED: False, + CONF_SHOW_DELIVERED: False, + } +} + +DEFAULT_SUMMARY = { + "Not Found": 0, + "In Transit": 0, + "Expired": 0, + "Ready to be Picked Up": 0, + "Undelivered": 0, + "Delivered": 0, + "Returned": 0, +} + +NEW_SUMMARY_DATA = { + "Not Found": 1, + "In Transit": 1, + "Expired": 1, + "Ready to be Picked Up": 1, + "Undelivered": 1, + "Delivered": 1, + "Returned": 1, +} + + +@pytest.fixture +def mock_seventeentrack(): + """Build a fixture for the 17Track API.""" + mock_seventeentrack_api = AsyncMock() + with ( + patch( + "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient", + return_value=mock_seventeentrack_api, + ) as mock_seventeentrack_api, + ): + mock_seventeentrack_api.return_value.profile.login.return_value = True + mock_seventeentrack_api.return_value.profile.packages.return_value = [] + mock_seventeentrack_api.return_value.profile.summary.return_value = ( + DEFAULT_SUMMARY + ) + yield mock_seventeentrack_api + + +def get_package( + tracking_number: str = "456", + destination_country: int = 206, + friendly_name: Optional[str] = "friendly name 1", + info_text: str = "info text 1", + location: str = "location 1", + timestamp: str = "2020-08-10 10:32", + origin_country: int = 206, + package_type: int = 2, + status: int = 0, + tz: str = "UTC", +): + """Build a Package of the 17Track API.""" + return Package( + tracking_number=tracking_number, + destination_country=destination_country, + friendly_name=friendly_name, + info_text=info_text, + location=location, + timestamp=timestamp, + origin_country=origin_country, + package_type=package_type, + status=status, + tz=tz, + ) diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 2334fbd4057..de345dcf11a 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -2,283 +2,169 @@ from __future__ import annotations -import datetime -from unittest.mock import patch +from unittest.mock import AsyncMock, patch -from py17track.package import Package -import pytest +from freezegun.api import FrozenDateTimeFactory +from py17track.errors import SeventeenTrackError -from homeassistant.components.seventeentrack.sensor import ( - CONF_SHOW_ARCHIVED, - CONF_SHOW_DELIVERED, -) -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component -from homeassistant.util import utcnow -from tests.common import async_fire_time_changed - -VALID_CONFIG_MINIMAL = { - "sensor": { - "platform": "seventeentrack", - CONF_USERNAME: "test", - CONF_PASSWORD: "test", - } -} - -INVALID_CONFIG = {"sensor": {"platform": "seventeentrack", "boom": "test"}} - -VALID_CONFIG_FULL = { - "sensor": { - "platform": "seventeentrack", - CONF_USERNAME: "test", - CONF_PASSWORD: "test", - CONF_SHOW_ARCHIVED: True, - CONF_SHOW_DELIVERED: True, - } -} - -VALID_CONFIG_FULL_NO_DELIVERED = { - "sensor": { - "platform": "seventeentrack", - CONF_USERNAME: "test", - CONF_PASSWORD: "test", - CONF_SHOW_ARCHIVED: False, - CONF_SHOW_DELIVERED: False, - } -} - -DEFAULT_SUMMARY = { - "Not Found": 0, - "In Transit": 0, - "Expired": 0, - "Ready to be Picked Up": 0, - "Undelivered": 0, - "Delivered": 0, - "Returned": 0, -} - -NEW_SUMMARY_DATA = { - "Not Found": 1, - "In Transit": 1, - "Expired": 1, - "Ready to be Picked Up": 1, - "Undelivered": 1, - "Delivered": 1, - "Returned": 1, -} +from . import goto_future, init_integration +from .conftest import ( + DEFAULT_SUMMARY, + INVALID_CONFIG, + NEW_SUMMARY_DATA, + VALID_CONFIG_FULL, + VALID_CONFIG_FULL_NO_DELIVERED, + VALID_CONFIG_MINIMAL, + get_package, +) -class ClientMock: - """Mock the py17track client to inject the ProfileMock.""" - - def __init__(self, session) -> None: - """Mock the profile.""" - self.profile = ProfileMock() - - -class ProfileMock: - """ProfileMock will mock data coming from 17track.""" - - package_list = [] - login_result = True - summary_data = DEFAULT_SUMMARY - account_id = "123" - - @classmethod - def reset(cls): - """Reset data to defaults.""" - cls.package_list = [] - cls.login_result = True - cls.summary_data = DEFAULT_SUMMARY - cls.account_id = "123" - - def __init__(self) -> None: - """Override Account id.""" - self.account_id = self.__class__.account_id - - async def login(self, email: str, password: str) -> bool: - """Login mock.""" - return self.__class__.login_result - - async def packages( - self, - package_state: int | str = "", - show_archived: bool = False, - tz: str = "UTC", - ) -> list: - """Packages mock.""" # noqa: D401 - return self.__class__.package_list[:] - - async def summary(self, show_archived: bool = False) -> dict: - """Summary mock.""" - return self.__class__.summary_data - - -@pytest.fixture(autouse=True, name="mock_client") -def fixture_mock_client(): - """Mock py17track client.""" - with patch( - "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient", - new=ClientMock, - ): - yield - ProfileMock.reset() - - -async def _setup_seventeentrack(hass, config=None, summary_data=None): - """Set up component using config.""" - if not config: - config = VALID_CONFIG_MINIMAL - if not summary_data: - summary_data = {} - - ProfileMock.summary_data = summary_data - assert await async_setup_component(hass, "sensor", config) - await hass.async_block_till_done() - - -async def _goto_future(hass: HomeAssistant, future=None): - """Move to future.""" - if not future: - future = utcnow() + datetime.timedelta(minutes=10) - with patch("homeassistant.util.utcnow", return_value=future): - async_fire_time_changed(hass, future) - await hass.async_block_till_done(wait_background_tasks=True) - async_fire_time_changed(hass, future) - await hass.async_block_till_done(wait_background_tasks=True) - - -async def test_full_valid_config(hass: HomeAssistant) -> None: +async def test_full_valid_config( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: """Ensure everything starts correctly.""" - assert await async_setup_component(hass, "sensor", VALID_CONFIG_FULL) - await hass.async_block_till_done() - assert len(hass.states.async_entity_ids()) == len(ProfileMock.summary_data.keys()) + await init_integration(hass, VALID_CONFIG_MINIMAL) + assert len(hass.states.async_entity_ids()) == len(DEFAULT_SUMMARY.keys()) -async def test_valid_config(hass: HomeAssistant) -> None: +async def test_valid_config( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: """Ensure everything starts correctly.""" - assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL) - await hass.async_block_till_done() - assert len(hass.states.async_entity_ids()) == len(ProfileMock.summary_data.keys()) + await init_integration(hass, VALID_CONFIG_FULL) + assert len(hass.states.async_entity_ids()) == len(DEFAULT_SUMMARY.keys()) async def test_invalid_config(hass: HomeAssistant) -> None: """Ensure nothing is created when config is wrong.""" - assert await async_setup_component(hass, "sensor", INVALID_CONFIG) - + await init_integration(hass, INVALID_CONFIG) assert not hass.states.async_entity_ids("sensor") -async def test_add_package(hass: HomeAssistant) -> None: - """Ensure package is added correctly when user add a new package.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, +async def test_login_exception( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: + """Ensure everything starts correctly.""" + mock_seventeentrack.return_value.profile.login.side_effect = SeventeenTrackError( + "Error" ) - ProfileMock.package_list = [package] + await init_integration(hass, VALID_CONFIG_FULL) + assert not hass.states.async_entity_ids("sensor") - await _setup_seventeentrack(hass) + +async def test_add_package( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock +) -> None: + """Ensure package is added correctly when user add a new package.""" + package = get_package() + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} + + await init_integration(hass, VALID_CONFIG_FULL) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 - package2 = Package( + package2 = get_package( tracking_number="789", - destination_country=206, friendly_name="friendly name 2", info_text="info text 2", location="location 2", timestamp="2020-08-10 14:25", - origin_country=206, - package_type=2, ) - ProfileMock.package_list = [package, package2] + mock_seventeentrack.return_value.profile.packages.return_value = [package, package2] - await _goto_future(hass) + await goto_future(hass, freezer) assert hass.states.get("sensor.seventeentrack_package_789") is not None assert len(hass.states.async_entity_ids()) == 2 -async def test_remove_package(hass: HomeAssistant) -> None: +async def test_add_package_default_friendly_name( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: + """Ensure package is added correctly with default friendly name when user add a new package without his own friendly name.""" + package = get_package(friendly_name=None) + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} + + await init_integration(hass, VALID_CONFIG_FULL) + state_456 = hass.states.get("sensor.seventeentrack_package_456") + assert state_456 is not None + assert state_456.attributes["friendly_name"] == "Seventeentrack Package: 456" + assert len(hass.states.async_entity_ids()) == 1 + + +async def test_remove_package( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock +) -> None: """Ensure entity is not there anymore if package is not there.""" - package1 = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - ) - package2 = Package( + package1 = get_package() + package2 = get_package( tracking_number="789", - destination_country=206, friendly_name="friendly name 2", info_text="info text 2", location="location 2", timestamp="2020-08-10 14:25", - origin_country=206, - package_type=2, ) - ProfileMock.package_list = [package1, package2] + mock_seventeentrack.return_value.profile.packages.return_value = [ + package1, + package2, + ] + mock_seventeentrack.return_value.profile.summary.return_value = {} - await _setup_seventeentrack(hass) + await init_integration(hass, VALID_CONFIG_FULL) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert hass.states.get("sensor.seventeentrack_package_789") is not None assert len(hass.states.async_entity_ids()) == 2 - ProfileMock.package_list = [package2] + mock_seventeentrack.return_value.profile.packages.return_value = [package2] - await _goto_future(hass) + await goto_future(hass, freezer) + + assert hass.states.get("sensor.seventeentrack_package_456").state == "unavailable" + assert len(hass.states.async_entity_ids()) == 2 + + await goto_future(hass, freezer) assert hass.states.get("sensor.seventeentrack_package_456") is None assert hass.states.get("sensor.seventeentrack_package_789") is not None assert len(hass.states.async_entity_ids()) == 1 -async def test_friendly_name_changed(hass: HomeAssistant) -> None: - """Test friendly name change.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, +async def test_package_error( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: + """Ensure package is added correctly when user add a new package.""" + mock_seventeentrack.return_value.profile.packages.side_effect = SeventeenTrackError( + "Error" ) - ProfileMock.package_list = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} - await _setup_seventeentrack(hass) + await init_integration(hass, VALID_CONFIG_FULL) + assert hass.states.get("sensor.seventeentrack_package_456") is None + + +async def test_friendly_name_changed( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock +) -> None: + """Test friendly name change.""" + package = get_package() + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} + + await init_integration(hass, VALID_CONFIG_FULL) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 2", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - ) - ProfileMock.package_list = [package] + package = get_package(friendly_name="friendly name 2") + mock_seventeentrack.return_value.profile.packages.return_value = [package] - await _goto_future(hass) + await goto_future(hass, freezer) assert hass.states.get("sensor.seventeentrack_package_456") is not None entity = hass.data["entity_components"]["sensor"].get_entity( @@ -288,169 +174,144 @@ async def test_friendly_name_changed(hass: HomeAssistant) -> None: assert len(hass.states.async_entity_ids()) == 1 -async def test_delivered_not_shown(hass: HomeAssistant) -> None: +async def test_delivered_not_shown( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock +) -> None: """Ensure delivered packages are not shown.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - status=40, - ) - ProfileMock.package_list = [package] + package = get_package(status=40) + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" ) as persistent_notification_mock: - await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED) - await _goto_future(hass) + await init_integration(hass, VALID_CONFIG_FULL_NO_DELIVERED) + await goto_future(hass, freezer) assert not hass.states.async_entity_ids() persistent_notification_mock.create.assert_called() -async def test_delivered_shown(hass: HomeAssistant) -> None: +async def test_delivered_shown( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: """Ensure delivered packages are show when user choose to show them.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - status=40, - ) - ProfileMock.package_list = [package] + package = get_package(status=40) + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" ) as persistent_notification_mock: - await _setup_seventeentrack(hass, VALID_CONFIG_FULL) + await init_integration(hass, VALID_CONFIG_FULL) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 persistent_notification_mock.create.assert_not_called() -async def test_becomes_delivered_not_shown_notification(hass: HomeAssistant) -> None: +async def test_becomes_delivered_not_shown_notification( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock +) -> None: """Ensure notification is triggered when package becomes delivered.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - ) - ProfileMock.package_list = [package] + package = get_package() + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} - await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED) + await init_integration(hass, VALID_CONFIG_FULL_NO_DELIVERED) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 - package_delivered = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - status=40, - ) - ProfileMock.package_list = [package_delivered] + package_delivered = get_package(status=40) + mock_seventeentrack.return_value.profile.packages.return_value = [package_delivered] with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" ) as persistent_notification_mock: - await _goto_future(hass) + await goto_future(hass, freezer) + await goto_future(hass, freezer) persistent_notification_mock.create.assert_called() assert not hass.states.async_entity_ids() -async def test_summary_correctly_updated(hass: HomeAssistant) -> None: +async def test_summary_correctly_updated( + hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock +) -> None: """Ensure summary entities are not duplicated.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - status=30, - ) - ProfileMock.package_list = [package] + package = get_package(status=30) + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = DEFAULT_SUMMARY - await _setup_seventeentrack(hass, summary_data=DEFAULT_SUMMARY) + await init_integration(hass, VALID_CONFIG_FULL) assert len(hass.states.async_entity_ids()) == 8 - for state in hass.states.async_all(): - if state.entity_id == "sensor.seventeentrack_package_456": - break - assert state.state == "0" - assert ( - len( - hass.states.get( - "sensor.seventeentrack_packages_ready_to_be_picked_up" - ).attributes["packages"] - ) - == 1 + state_ready_picked = hass.states.get( + "sensor.seventeentrack_packages_ready_to_be_picked_up" ) + assert state_ready_picked is not None + assert len(state_ready_picked.attributes["packages"]) == 1 - ProfileMock.package_list = [] - ProfileMock.summary_data = NEW_SUMMARY_DATA + mock_seventeentrack.return_value.profile.packages.return_value = [] + mock_seventeentrack.return_value.profile.summary.return_value = NEW_SUMMARY_DATA - await _goto_future(hass) + await goto_future(hass, freezer) + await goto_future(hass, freezer) assert len(hass.states.async_entity_ids()) == 7 for state in hass.states.async_all(): assert state.state == "1" + state_ready_picked = hass.states.get( + "sensor.seventeentrack_packages_ready_to_be_picked_up" + ) + assert state_ready_picked is not None + assert state_ready_picked.attributes["packages"] is None + + +async def test_summary_error( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: + """Test summary empty if error.""" + package = get_package(status=30) + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.side_effect = SeventeenTrackError( + "Error" + ) + + await init_integration(hass, VALID_CONFIG_FULL) + + assert len(hass.states.async_entity_ids()) == 1 + assert ( - hass.states.get( - "sensor.seventeentrack_packages_ready_to_be_picked_up" - ).attributes["packages"] - is None + hass.states.get("sensor.seventeentrack_packages_ready_to_be_picked_up") is None ) -async def test_utc_timestamp(hass: HomeAssistant) -> None: +async def test_utc_timestamp( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: """Ensure package timestamp is converted correctly from HA-defined time zone to UTC.""" - package = Package( - tracking_number="456", - destination_country=206, - friendly_name="friendly name 1", - info_text="info text 1", - location="location 1", - timestamp="2020-08-10 10:32", - origin_country=206, - package_type=2, - tz="Asia/Jakarta", - ) - ProfileMock.package_list = [package] - await _setup_seventeentrack(hass) + package = get_package(tz="Asia/Jakarta") + mock_seventeentrack.return_value.profile.packages.return_value = [package] + mock_seventeentrack.return_value.profile.summary.return_value = {} + + await init_integration(hass, VALID_CONFIG_FULL) + assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 - assert ( - str( - hass.states.get("sensor.seventeentrack_package_456").attributes.get( - "timestamp" - ) - ) - == "2020-08-10 03:32:00+00:00" - ) + state_456 = hass.states.get("sensor.seventeentrack_package_456") + assert state_456 is not None + assert str(state_456.attributes.get("timestamp")) == "2020-08-10 03:32:00+00:00" + + +async def test_non_valid_platform_config( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: + """Test if login fails.""" + mock_seventeentrack.return_value.profile.login.return_value = False + await init_integration(hass, VALID_CONFIG_FULL) + assert len(hass.states.async_entity_ids()) == 0 From c608d1cb8575c79e6a8f968f220189dc4472736d Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 10 Mar 2024 19:36:17 +0100 Subject: [PATCH 0654/1691] Fix mqtt platform setup race (#112888) --- homeassistant/components/mqtt/util.py | 8 +++----- tests/components/mqtt/test_common.py | 3 +++ tests/components/mqtt/test_init.py | 20 +++++++++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index 53462f87321..43efc3b8928 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -53,14 +53,12 @@ async def async_forward_entry_setup_and_setup_discovery( mqtt_data = get_mqtt_data(hass) platforms_loaded = mqtt_data.platforms_loaded new_platforms: set[Platform | str] = platforms - platforms_loaded - platforms_loaded.update(new_platforms) tasks: list[asyncio.Task] = [] if "device_automation" in new_platforms: # Local import to avoid circular dependencies # pylint: disable-next=import-outside-toplevel from . import device_automation - new_platforms.remove("device_automation") tasks.append( create_eager_task(device_automation.async_setup_entry(hass, config_entry)) ) @@ -69,19 +67,19 @@ async def async_forward_entry_setup_and_setup_discovery( # pylint: disable-next=import-outside-toplevel from . import tag - new_platforms.remove("tag") tasks.append(create_eager_task(tag.async_setup_entry(hass, config_entry))) - if new_platforms: + if new_entity_platforms := (new_platforms - {"tag", "device_automation"}): tasks.append( create_eager_task( hass.config_entries.async_forward_entry_setups( - config_entry, new_platforms + config_entry, new_entity_platforms ) ) ) if not tasks: return await asyncio.gather(*tasks) + platforms_loaded.update(new_platforms) def mqtt_config_entry_enabled(hass: HomeAssistant) -> bool | None: diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index a25a670fa34..257b0b256dc 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -936,6 +936,9 @@ async def help_test_encoding_subscribable_topics( hass, f"homeassistant/{domain}/item3/config", json.dumps(config3) ) await hass.async_block_till_done() + await hass.async_block_till_done() + await hass.async_block_till_done() + await hass.async_block_till_done() expected_result = attribute_value or value diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 71b103c8bcc..0f3069ef1a1 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -3598,14 +3598,20 @@ async def test_disabling_and_enabling_entry( config_alarm_control_panel = '{"name": "test_new", "state_topic": "home/alarm", "command_topic": "home/alarm/set"}' config_light = '{"name": "test_new", "command_topic": "test-topic_new"}' - # Discovery of mqtt tag - async_fire_mqtt_message(hass, "homeassistant/tag/abc/config", config_tag) + with patch( + "homeassistant.components.mqtt.mixins.mqtt_config_entry_enabled", + return_value=False, + ): + # Discovery of mqtt tag + async_fire_mqtt_message(hass, "homeassistant/tag/abc/config", config_tag) - # Late discovery of mqtt entities - async_fire_mqtt_message( - hass, "homeassistant/alarm_control_panel/abc/config", config_alarm_control_panel - ) - async_fire_mqtt_message(hass, "homeassistant/light/abc/config", config_light) + # Late discovery of mqtt entities + async_fire_mqtt_message( + hass, + "homeassistant/alarm_control_panel/abc/config", + config_alarm_control_panel, + ) + async_fire_mqtt_message(hass, "homeassistant/light/abc/config", config_light) # Disable MQTT config entry await hass.config_entries.async_set_disabled_by( From a2318c26c9d70cbdde8415cfa3746094893bd828 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 08:37:10 -1000 Subject: [PATCH 0655/1691] Run debouncer tasks eagerly to avoid scheduling on the event loop (#112789) --- homeassistant/helpers/debounce.py | 4 ++-- .../bluetooth/test_active_update_processor.py | 2 +- tests/components/hue/test_light_v1.py | 4 ++++ tests/components/shelly/test_config_flow.py | 14 +++++++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 2fe42e19a67..773fff6ebc6 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -109,7 +109,7 @@ class Debouncer(Generic[_R_co]): assert self._job is not None try: - if task := self.hass.async_run_hass_job(self._job): + if task := self.hass.async_run_hass_job(self._job, eager_start=True): await task finally: self._schedule_timer() @@ -130,7 +130,7 @@ class Debouncer(Generic[_R_co]): return try: - if task := self.hass.async_run_hass_job(self._job): + if task := self.hass.async_run_hass_job(self._job, eager_start=True): await task except Exception: # pylint: disable=broad-except self.logger.exception("Unexpected exception from %s", self.function) diff --git a/tests/components/bluetooth/test_active_update_processor.py b/tests/components/bluetooth/test_active_update_processor.py index 13045048fc1..e894ef3dab4 100644 --- a/tests/components/bluetooth/test_active_update_processor.py +++ b/tests/components/bluetooth/test_active_update_processor.py @@ -330,7 +330,7 @@ async def test_second_poll_needed( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) await hass.async_block_till_done() - assert async_handle_update.mock_calls[-1] == call({"testdata": 1}) + assert async_handle_update.mock_calls[1] == call({"testdata": 1}) cancel() diff --git a/tests/components/hue/test_light_v1.py b/tests/components/hue/test_light_v1.py index fc2163326bb..9a74d9cd994 100644 --- a/tests/components/hue/test_light_v1.py +++ b/tests/components/hue/test_light_v1.py @@ -413,6 +413,8 @@ async def test_group_removed(hass: HomeAssistant, mock_bridge_v1) -> None: await hass.services.async_call( "light", "turn_on", {"entity_id": "light.group_1"}, blocking=True ) + # Wait for the group to be updated + await hass.async_block_till_done() # 2x group update, 1x light update, 1 turn on request assert len(mock_bridge_v1.mock_requests) == 4 @@ -440,6 +442,8 @@ async def test_light_removed(hass: HomeAssistant, mock_bridge_v1) -> None: await hass.services.async_call( "light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True ) + # Wait for the light to be updated + await hass.async_block_till_done() # 2x light update, 1 group update, 1 turn on request assert len(mock_bridge_v1.mock_requests) == 4 diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 7ce111fdf21..c3c1fc36c2e 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -1,6 +1,7 @@ """Test the Shelly config flow.""" from dataclasses import replace +from datetime import timedelta from ipaddress import ip_address from typing import Any from unittest.mock import AsyncMock, Mock, patch @@ -21,13 +22,15 @@ from homeassistant.components.shelly.const import ( DOMAIN, BLEScannerMode, ) +from homeassistant.components.shelly.coordinator import ENTRY_RELOAD_COOLDOWN from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util from . import init_integration -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed from tests.typing import WebSocketGenerator DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( @@ -1030,6 +1033,9 @@ async def test_zeroconf_already_configured_triggers_refresh_mac_in_name( monkeypatch.setattr(mock_rpc_device, "connected", False) mock_rpc_device.mock_disconnected() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=ENTRY_RELOAD_COOLDOWN) + ) await hass.async_block_till_done() assert len(mock_rpc_device.initialize.mock_calls) == 2 @@ -1062,6 +1068,9 @@ async def test_zeroconf_already_configured_triggers_refresh( monkeypatch.setattr(mock_rpc_device, "connected", False) mock_rpc_device.mock_disconnected() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=ENTRY_RELOAD_COOLDOWN) + ) await hass.async_block_till_done() assert len(mock_rpc_device.initialize.mock_calls) == 2 @@ -1100,6 +1109,9 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh( monkeypatch.setattr(mock_rpc_device, "connected", False) mock_rpc_device.mock_disconnected() + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(seconds=ENTRY_RELOAD_COOLDOWN) + ) await hass.async_block_till_done() assert len(mock_rpc_device.initialize.mock_calls) == 0 assert "device did not update" not in caplog.text From afa69cca388d32b48e7207f66f6a9799338b8496 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 08:41:33 -1000 Subject: [PATCH 0656/1691] Import custom components in the executor by default (#112177) Co-authored-by: Paulus Schoutsen --- homeassistant/loader.py | 14 +++++++++++--- tests/test_loader.py | 12 +++++++++++- .../test_package_loaded_loop/__init__.py | 7 +++++++ .../test_package_loaded_loop/config_flow.py | 7 +++++++ .../test_package_loaded_loop/const.py | 2 ++ .../test_package_loaded_loop/manifest.json | 11 +++++++++++ 6 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/testing_config/custom_components/test_package_loaded_loop/__init__.py create mode 100644 tests/testing_config/custom_components/test_package_loaded_loop/config_flow.py create mode 100644 tests/testing_config/custom_components/test_package_loaded_loop/const.py create mode 100644 tests/testing_config/custom_components/test_package_loaded_loop/manifest.json diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 622c8524709..71f746e322b 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -109,6 +109,12 @@ CUSTOM_WARNING = ( "cause stability problems, be sure to disable it if you " "experience issues with Home Assistant" ) +IMPORT_EVENT_LOOP_WARNING = ( + "We found an integration %s which is configured to " + "to import its code in the event loop. This component might " + "cause stability problems, be sure to disable it if you " + "experience issues with Home Assistant" +) _UNDEF = object() # Internal; not helpers.typing.UNDEFINED due to circular dependency @@ -653,6 +659,9 @@ class Integration: None if is_virtual else set(os.listdir(file_path)), ) + if not integration.import_executor: + _LOGGER.warning(IMPORT_EVENT_LOOP_WARNING, integration.domain) + if integration.is_built_in: return integration @@ -821,9 +830,8 @@ class Integration: def import_executor(self) -> bool: """Import integration in the executor.""" # If the integration does not explicitly set import_executor, we default to - # True if it's a built-in integration and False if it's a custom integration. - # In the future, we want to default to True for all integrations. - return self.manifest.get("import_executor", self.is_built_in) + # True. + return self.manifest.get("import_executor", True) @cached_property def has_translations(self) -> bool: diff --git a/tests/test_loader.py b/tests/test_loader.py index 7828d197fc1..b51a9a846b8 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1098,7 +1098,7 @@ async def test_async_suggest_report_issue( def test_import_executor_default(hass: HomeAssistant) -> None: """Test that import_executor defaults.""" custom_comp = mock_integration(hass, MockModule("any_random"), built_in=False) - assert custom_comp.import_executor is False + assert custom_comp.import_executor is True built_in_comp = mock_integration(hass, MockModule("other_random"), built_in=True) assert built_in_comp.import_executor is True @@ -1675,3 +1675,13 @@ async def test_async_get_platforms_concurrent_loads( assert imports == [button_module_name] assert integration.get_platform_cached("button") is button_module_mock + + +async def test_integration_warnings( + hass: HomeAssistant, + enable_custom_integrations: None, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test integration warnings.""" + await loader.async_get_integration(hass, "test_package_loaded_loop") + assert "configured to to import its code in the event loop" in caplog.text diff --git a/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py b/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py new file mode 100644 index 00000000000..c4d88fd17d7 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py @@ -0,0 +1,7 @@ +"""Provide a mock package component.""" +from .const import TEST # noqa: F401 + + +async def async_setup(hass, config): + """Mock a successful setup.""" + return True diff --git a/tests/testing_config/custom_components/test_package_loaded_loop/config_flow.py b/tests/testing_config/custom_components/test_package_loaded_loop/config_flow.py new file mode 100644 index 00000000000..9153b666828 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_loop/config_flow.py @@ -0,0 +1,7 @@ +"""Config flow.""" + +from homeassistant.core import HomeAssistant + + +async def _async_has_devices(hass: HomeAssistant) -> bool: + return True diff --git a/tests/testing_config/custom_components/test_package_loaded_loop/const.py b/tests/testing_config/custom_components/test_package_loaded_loop/const.py new file mode 100644 index 00000000000..7e13e04cb47 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_loop/const.py @@ -0,0 +1,2 @@ +"""Constants for test_package custom component.""" +TEST = 5 diff --git a/tests/testing_config/custom_components/test_package_loaded_loop/manifest.json b/tests/testing_config/custom_components/test_package_loaded_loop/manifest.json new file mode 100644 index 00000000000..e614d10b004 --- /dev/null +++ b/tests/testing_config/custom_components/test_package_loaded_loop/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "test_package_loaded_loop", + "name": "Test Package that loads in the loop", + "documentation": "http://test-package.io", + "requirements": [], + "dependencies": [], + "codeowners": [], + "config_flow": true, + "import_executor": false, + "version": "1.2.3" +} From 5d231ed61f82ecbe556d920d5244caa4cb1e4dc4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:43:07 +0100 Subject: [PATCH 0657/1691] Remove entity description mixin in Vodafone Station (#112967) --- homeassistant/components/vodafone_station/button.py | 13 +++---------- homeassistant/components/vodafone_station/sensor.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/vodafone_station/button.py b/homeassistant/components/vodafone_station/button.py index 3bd012734b6..efea011a541 100644 --- a/homeassistant/components/vodafone_station/button.py +++ b/homeassistant/components/vodafone_station/button.py @@ -21,21 +21,14 @@ from .const import _LOGGER, DOMAIN from .coordinator import VodafoneStationRouter -@dataclass(frozen=True) -class VodafoneStationBaseEntityDescriptionMixin: - """Mixin to describe a Button entity.""" +@dataclass(frozen=True, kw_only=True) +class VodafoneStationEntityDescription(ButtonEntityDescription): + """Vodafone Station entity description.""" press_action: Callable[[VodafoneStationRouter], Any] is_suitable: Callable[[dict], bool] -@dataclass(frozen=True) -class VodafoneStationEntityDescription( - ButtonEntityDescription, VodafoneStationBaseEntityDescriptionMixin -): - """Vodafone Station entity description.""" - - BUTTON_TYPES: Final = ( VodafoneStationEntityDescription( key="reboot", diff --git a/homeassistant/components/vodafone_station/sensor.py b/homeassistant/components/vodafone_station/sensor.py index 5bc7465a902..937c0220cbf 100644 --- a/homeassistant/components/vodafone_station/sensor.py +++ b/homeassistant/components/vodafone_station/sensor.py @@ -25,9 +25,9 @@ from .coordinator import VodafoneStationRouter NOT_AVAILABLE: list = ["", "N/A", "0.0.0.0"] -@dataclass(frozen=True) -class VodafoneStationBaseEntityDescription: - """Vodafone Station entity base description.""" +@dataclass(frozen=True, kw_only=True) +class VodafoneStationEntityDescription(SensorEntityDescription): + """Vodafone Station entity description.""" value: Callable[[Any, Any], Any] = ( lambda coordinator, key: coordinator.data.sensors[key] @@ -35,13 +35,6 @@ class VodafoneStationBaseEntityDescription: is_suitable: Callable[[dict], bool] = lambda val: True -@dataclass(frozen=True, kw_only=True) -class VodafoneStationEntityDescription( - VodafoneStationBaseEntityDescription, SensorEntityDescription -): - """Vodafone Station entity description.""" - - def _calculate_uptime(coordinator: VodafoneStationRouter, key: str) -> datetime: """Calculate device uptime.""" From 65624a9491dc109f3c7b0de13a1351891a2d908b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:47:39 +0100 Subject: [PATCH 0658/1691] Remove entity description mixin in Zeversolar (#112977) --- homeassistant/components/zeversolar/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/zeversolar/sensor.py b/homeassistant/components/zeversolar/sensor.py index 69dd525f7f7..5023e274267 100644 --- a/homeassistant/components/zeversolar/sensor.py +++ b/homeassistant/components/zeversolar/sensor.py @@ -23,20 +23,13 @@ from .coordinator import ZeversolarCoordinator from .entity import ZeversolarEntity -@dataclass(frozen=True) -class ZeversolarEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class ZeversolarEntityDescription(SensorEntityDescription): + """Describes Zeversolar sensor entity.""" value_fn: Callable[[zeversolar.ZeverSolarData], zeversolar.kWh | zeversolar.Watt] -@dataclass(frozen=True) -class ZeversolarEntityDescription( - SensorEntityDescription, ZeversolarEntityDescriptionMixin -): - """Describes Zeversolar sensor entity.""" - - SENSOR_TYPES = ( ZeversolarEntityDescription( key="pac", From 5bfbe00c572b88a4afca89b5e9c0dacee4c71640 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:48:14 +0100 Subject: [PATCH 0659/1691] Remove entity description mixin in Youtube (#112975) --- homeassistant/components/youtube/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/youtube/sensor.py b/homeassistant/components/youtube/sensor.py index 56c3f960bdf..bc69f92e8fd 100644 --- a/homeassistant/components/youtube/sensor.py +++ b/homeassistant/components/youtube/sensor.py @@ -27,9 +27,9 @@ from .const import ( from .entity import YouTubeChannelEntity -@dataclass(frozen=True) -class YouTubeMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class YouTubeSensorEntityDescription(SensorEntityDescription): + """Describes YouTube sensor entity.""" available_fn: Callable[[Any], bool] value_fn: Callable[[Any], StateType] @@ -37,11 +37,6 @@ class YouTubeMixin: attributes_fn: Callable[[Any], dict[str, Any] | None] | None -@dataclass(frozen=True) -class YouTubeSensorEntityDescription(SensorEntityDescription, YouTubeMixin): - """Describes YouTube sensor entity.""" - - SENSOR_TYPES = [ YouTubeSensorEntityDescription( key="latest_upload", From 7ea1f42b84f25d62b7db26288890706849c3eed0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:48:41 +0100 Subject: [PATCH 0660/1691] Remove entity description mixin in WAQI (#112970) --- homeassistant/components/waqi/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index 8df4bf05b06..198eaef536b 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -154,19 +154,14 @@ async def async_setup_platform( ) -@dataclass(frozen=True) -class WAQIMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class WAQISensorEntityDescription(SensorEntityDescription): + """Describes WAQI sensor entity.""" available_fn: Callable[[WAQIAirQuality], bool] value_fn: Callable[[WAQIAirQuality], StateType] -@dataclass(frozen=True) -class WAQISensorEntityDescription(SensorEntityDescription, WAQIMixin): - """Describes WAQI sensor entity.""" - - SENSORS: list[WAQISensorEntityDescription] = [ WAQISensorEntityDescription( key="air_quality", From 83dc99cd9eaa3ca4d96e5f6b2e389496ed4bec6b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:49:31 +0100 Subject: [PATCH 0661/1691] Remove entity description mixin in Tradfri (#112953) --- homeassistant/components/tradfri/sensor.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index ac433f89272..5d3e63d3a5d 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -38,21 +38,13 @@ from .const import ( from .coordinator import TradfriDeviceDataUpdateCoordinator -@dataclass(frozen=True) -class TradfriSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TradfriSensorEntityDescription(SensorEntityDescription): + """Class describing Tradfri sensor entities.""" value: Callable[[Device], Any | None] -@dataclass(frozen=True) -class TradfriSensorEntityDescription( - SensorEntityDescription, - TradfriSensorEntityDescriptionMixin, -): - """Class describing Tradfri sensor entities.""" - - def _get_air_quality(device: Device) -> int | None: """Fetch the air quality value.""" assert device.air_purifier_control is not None From 3f1b8eadd23eda0d7c3cd351912f612af466f902 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 10 Mar 2024 19:49:57 +0100 Subject: [PATCH 0662/1691] Use async_at_started in Speedtest.Net (#112110) --- .../components/speedtestdotnet/__init__.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index f4f483e7ff8..4ee46478fb9 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -7,9 +7,10 @@ from functools import partial import speedtest from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, Platform -from homeassistant.core import CoreState, Event, HomeAssistant +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.start import async_at_started from .const import DOMAIN from .coordinator import SpeedTestDataCoordinator @@ -28,17 +29,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b except speedtest.SpeedtestException as err: raise ConfigEntryNotReady from err - async def _request_refresh(event: Event) -> None: - """Request a refresh.""" - await coordinator.async_request_refresh() - - if hass.state is CoreState.running: + async def _async_finish_startup(hass: HomeAssistant) -> None: + """Run this only when HA has finished its startup.""" await coordinator.async_config_entry_first_refresh() - else: - # Running a speed test during startup can prevent - # integrations from being able to setup because it - # can saturate the network interface. - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _request_refresh) + + # Don't start a speedtest during startup + async_at_started(hass, _async_finish_startup) hass.data[DOMAIN] = coordinator From cfc99af9ad1658855401e2c242266db95cab934f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:50:20 +0100 Subject: [PATCH 0663/1691] Remove entity description mixin in Permobil (#112918) --- homeassistant/components/permobil/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/permobil/sensor.py b/homeassistant/components/permobil/sensor.py index ce9cf906f37..54d3a61c519 100644 --- a/homeassistant/components/permobil/sensor.py +++ b/homeassistant/components/permobil/sensor.py @@ -41,21 +41,14 @@ from .entity import PermobilEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class PermobilRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class PermobilSensorEntityDescription(SensorEntityDescription): + """Describes Permobil sensor entity.""" value_fn: Callable[[Any], float | int] available_fn: Callable[[Any], bool] -@dataclass(frozen=True) -class PermobilSensorEntityDescription( - SensorEntityDescription, PermobilRequiredKeysMixin -): - """Describes Permobil sensor entity.""" - - SENSOR_DESCRIPTIONS: tuple[PermobilSensorEntityDescription, ...] = ( PermobilSensorEntityDescription( # Current battery as a percentage From 69a322a6f28ded8859b4d9878a9b89610cd76ddb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:50:49 +0100 Subject: [PATCH 0664/1691] Remove entity description mixin in PEGELONLINE (#112917) --- homeassistant/components/pegel_online/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/pegel_online/sensor.py b/homeassistant/components/pegel_online/sensor.py index b2b922f3d23..6471b8cbd4b 100644 --- a/homeassistant/components/pegel_online/sensor.py +++ b/homeassistant/components/pegel_online/sensor.py @@ -22,20 +22,13 @@ from .coordinator import PegelOnlineDataUpdateCoordinator from .entity import PegelOnlineEntity -@dataclass(frozen=True) -class PegelOnlineRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class PegelOnlineSensorEntityDescription(SensorEntityDescription): + """PEGELONLINE sensor entity description.""" measurement_key: str -@dataclass(frozen=True) -class PegelOnlineSensorEntityDescription( - SensorEntityDescription, PegelOnlineRequiredKeysMixin -): - """PEGELONLINE sensor entity description.""" - - SENSORS: tuple[PegelOnlineSensorEntityDescription, ...] = ( PegelOnlineSensorEntityDescription( key="air_temperature", From f37bb6b1bf85cc52d3b14543b3fd9b2841696270 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:51:21 +0100 Subject: [PATCH 0665/1691] Remove entity description mixin in Melcloud (#112905) --- homeassistant/components/melcloud/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index 73a72ba9dfb..84585c556ca 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -24,21 +24,14 @@ from . import MelCloudDevice from .const import DOMAIN -@dataclasses.dataclass(frozen=True) -class MelcloudRequiredKeysMixin: - """Mixin for required keys.""" +@dataclasses.dataclass(frozen=True, kw_only=True) +class MelcloudSensorEntityDescription(SensorEntityDescription): + """Describes Melcloud sensor entity.""" value_fn: Callable[[Any], float] enabled: Callable[[Any], bool] -@dataclasses.dataclass(frozen=True) -class MelcloudSensorEntityDescription( - SensorEntityDescription, MelcloudRequiredKeysMixin -): - """Describes Melcloud sensor entity.""" - - ATA_SENSORS: tuple[MelcloudSensorEntityDescription, ...] = ( MelcloudSensorEntityDescription( key="room_temperature", From a85571c84055a2093096c4db65c95a6b4ae63c0b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:51:52 +0100 Subject: [PATCH 0666/1691] Remove entity description mixin in Justnimbus (#112893) --- homeassistant/components/justnimbus/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/justnimbus/sensor.py b/homeassistant/components/justnimbus/sensor.py index 5acea40fd52..c2c22307371 100644 --- a/homeassistant/components/justnimbus/sensor.py +++ b/homeassistant/components/justnimbus/sensor.py @@ -29,20 +29,13 @@ from .const import DOMAIN from .entity import JustNimbusEntity -@dataclass(frozen=True) -class JustNimbusEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class JustNimbusEntityDescription(SensorEntityDescription): + """Describes JustNimbus sensor entity.""" value_fn: Callable[[JustNimbusCoordinator], Any] -@dataclass(frozen=True) -class JustNimbusEntityDescription( - SensorEntityDescription, JustNimbusEntityDescriptionMixin -): - """Describes JustNimbus sensor entity.""" - - SENSOR_TYPES = ( JustNimbusEntityDescription( key="pump_pressure", From d531b6e9b7e88614f84041a62c7bcf68e671decd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 19:54:11 +0100 Subject: [PATCH 0667/1691] Remove entity description mixin in LaCrosse View (#112900) --- homeassistant/components/lacrosse_view/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/lacrosse_view/sensor.py b/homeassistant/components/lacrosse_view/sensor.py index fb2c34101ac..1a42aac9109 100644 --- a/homeassistant/components/lacrosse_view/sensor.py +++ b/homeassistant/components/lacrosse_view/sensor.py @@ -36,20 +36,13 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class LaCrosseSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class LaCrosseSensorEntityDescription(SensorEntityDescription): + """Description for LaCrosse View sensor.""" value_fn: Callable[[Sensor, str], float | int | str | None] -@dataclass(frozen=True) -class LaCrosseSensorEntityDescription( - SensorEntityDescription, LaCrosseSensorEntityDescriptionMixin -): - """Description for LaCrosse View sensor.""" - - def get_value(sensor: Sensor, field: str) -> float | int | str | None: """Get the value of a sensor field.""" field_data = sensor.data.get(field) From 65e1502b505463225824d659198c58ac070c1b60 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 20:12:29 +0100 Subject: [PATCH 0668/1691] Move wsdot fixture to integration test (#112996) --- tests/{ => components/wsdot}/fixtures/wsdot.json | 0 tests/components/wsdot/test_sensor.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{ => components/wsdot}/fixtures/wsdot.json (100%) diff --git a/tests/fixtures/wsdot.json b/tests/components/wsdot/fixtures/wsdot.json similarity index 100% rename from tests/fixtures/wsdot.json rename to tests/components/wsdot/fixtures/wsdot.json diff --git a/tests/components/wsdot/test_sensor.py b/tests/components/wsdot/test_sensor.py index 2ab5f3dc4b5..4a912340078 100644 --- a/tests/components/wsdot/test_sensor.py +++ b/tests/components/wsdot/test_sensor.py @@ -50,7 +50,7 @@ async def test_setup(hass: HomeAssistant, requests_mock: requests_mock.Mocker) - entities.append(entity) uri = re.compile(RESOURCE + "*") - requests_mock.get(uri, text=load_fixture("wsdot.json")) + requests_mock.get(uri, text=load_fixture("wsdot/wsdot.json")) wsdot.setup_platform(hass, config, add_entities) assert len(entities) == 1 sensor = entities[0] From d76c20a483a51353bd8d26ef1ab209e7f5d2cd2a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 20:17:06 +0100 Subject: [PATCH 0669/1691] Move uk_transport fixture to integration test (#112995) --- .../uk_transport/fixtures/bus.json} | 0 .../uk_transport/fixtures/train.json} | 0 tests/components/uk_transport/test_sensor.py | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/{fixtures/uk_transport_bus.json => components/uk_transport/fixtures/bus.json} (100%) rename tests/{fixtures/uk_transport_train.json => components/uk_transport/fixtures/train.json} (100%) diff --git a/tests/fixtures/uk_transport_bus.json b/tests/components/uk_transport/fixtures/bus.json similarity index 100% rename from tests/fixtures/uk_transport_bus.json rename to tests/components/uk_transport/fixtures/bus.json diff --git a/tests/fixtures/uk_transport_train.json b/tests/components/uk_transport/fixtures/train.json similarity index 100% rename from tests/fixtures/uk_transport_train.json rename to tests/components/uk_transport/fixtures/train.json diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index baed4dbd44b..a93ebedd5a8 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -49,7 +49,7 @@ async def test_bus(hass: HomeAssistant) -> None: """Test for operational uk_transport sensor with proper attributes.""" with requests_mock.Mocker() as mock_req: uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + "*") - mock_req.get(uri, text=load_fixture("uk_transport_bus.json")) + mock_req.get(uri, text=load_fixture("uk_transport/bus.json")) assert await async_setup_component(hass, "sensor", VALID_CONFIG) await hass.async_block_till_done() @@ -73,7 +73,7 @@ async def test_train(hass: HomeAssistant) -> None: "homeassistant.util.dt.now", return_value=now().replace(hour=13) ): uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + "*") - mock_req.get(uri, text=load_fixture("uk_transport_train.json")) + mock_req.get(uri, text=load_fixture("uk_transport/train.json")) assert await async_setup_component(hass, "sensor", VALID_CONFIG) await hass.async_block_till_done() From 958c8435124631b40660adabca25eb57b0026e00 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 20:17:24 +0100 Subject: [PATCH 0670/1691] Move OpenALPR cloud fixture to integration test (#112994) --- tests/{ => components/openalpr_cloud}/fixtures/alpr_cloud.json | 0 tests/components/openalpr_cloud/test_image_processing.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{ => components/openalpr_cloud}/fixtures/alpr_cloud.json (100%) diff --git a/tests/fixtures/alpr_cloud.json b/tests/components/openalpr_cloud/fixtures/alpr_cloud.json similarity index 100% rename from tests/fixtures/alpr_cloud.json rename to tests/components/openalpr_cloud/fixtures/alpr_cloud.json diff --git a/tests/components/openalpr_cloud/test_image_processing.py b/tests/components/openalpr_cloud/test_image_processing.py index 090fcabe8a4..7115c3e7bf0 100644 --- a/tests/components/openalpr_cloud/test_image_processing.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -136,7 +136,7 @@ async def test_openalpr_process_image( aioclient_mock.post( OPENALPR_API_URL, params=PARAMS, - text=load_fixture("alpr_cloud.json"), + text=load_fixture("alpr_cloud.json", "openalpr_cloud"), status=200, ) From 02ef7ba134c540df92e8fed578f5395e991345c3 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 10 Mar 2024 20:46:25 +0100 Subject: [PATCH 0671/1691] Remove not needed hass.async_block_till_done in test (#113002) --- tests/components/mqtt/test_common.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 257b0b256dc..a25a670fa34 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -936,9 +936,6 @@ async def help_test_encoding_subscribable_topics( hass, f"homeassistant/{domain}/item3/config", json.dumps(config3) ) await hass.async_block_till_done() - await hass.async_block_till_done() - await hass.async_block_till_done() - await hass.async_block_till_done() expected_result = attribute_value or value From b8ae5e2388c4698d60da7d7ee712c2c0215e806f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 21:06:28 +0100 Subject: [PATCH 0672/1691] Remove entity description mixin in Switcher Kis (#112945) --- homeassistant/components/switcher_kis/button.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/switcher_kis/button.py b/homeassistant/components/switcher_kis/button.py index 093077ed818..b0e45f1374a 100644 --- a/homeassistant/components/switcher_kis/button.py +++ b/homeassistant/components/switcher_kis/button.py @@ -30,21 +30,14 @@ from .const import SIGNAL_DEVICE_ADD from .utils import get_breeze_remote_manager -@dataclass(frozen=True) -class SwitcherThermostatButtonDescriptionMixin: - """Mixin to describe a Switcher Thermostat Button entity.""" +@dataclass(frozen=True, kw_only=True) +class SwitcherThermostatButtonEntityDescription(ButtonEntityDescription): + """Class to describe a Switcher Thermostat Button entity.""" press_fn: Callable[[SwitcherType2Api, SwitcherBreezeRemote], SwitcherBaseResponse] supported: Callable[[SwitcherBreezeRemote], bool] -@dataclass(frozen=True) -class SwitcherThermostatButtonEntityDescription( - ButtonEntityDescription, SwitcherThermostatButtonDescriptionMixin -): - """Class to describe a Switcher Thermostat Button entity.""" - - THERMOSTAT_BUTTONS = [ SwitcherThermostatButtonEntityDescription( key="assume_on", From 08874354c733305984e47918336e409d2248f357 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Sun, 10 Mar 2024 16:20:46 -0400 Subject: [PATCH 0673/1691] Add missing translation placeholder in Hydrawise (#113007) Add missing translation placeholder --- homeassistant/components/hydrawise/config_flow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hydrawise/config_flow.py b/homeassistant/components/hydrawise/config_flow.py index 7087c4d2c34..cfaaefcd03a 100644 --- a/homeassistant/components/hydrawise/config_flow.py +++ b/homeassistant/components/hydrawise/config_flow.py @@ -52,7 +52,10 @@ class HydrawiseConfigFlow(ConfigFlow, domain=DOMAIN): is_fixable=False, severity=IssueSeverity.ERROR, translation_key="deprecated_yaml_import_issue", - translation_placeholders={"error_type": error_type}, + translation_placeholders={ + "error_type": error_type, + "url": "/config/integrations/dashboard/add?domain=hydrawise", + }, ) return self.async_abort(reason=error_type) From c8c00a86a6649cff6e2c9a5bd6a01d5e40f99a04 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 21:44:41 +0100 Subject: [PATCH 0674/1691] Remove entity description mixin in Nettigo Air Monitor (#112909) --- homeassistant/components/nam/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/nam/sensor.py b/homeassistant/components/nam/sensor.py index de516c6d607..848c3396668 100644 --- a/homeassistant/components/nam/sensor.py +++ b/homeassistant/components/nam/sensor.py @@ -75,18 +75,13 @@ PARALLEL_UPDATES = 1 _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class NAMSensorRequiredKeysMixin: - """Class for NAM entity required keys.""" +@dataclass(frozen=True, kw_only=True) +class NAMSensorEntityDescription(SensorEntityDescription): + """NAM sensor entity description.""" value: Callable[[NAMSensors], StateType | datetime] -@dataclass(frozen=True) -class NAMSensorEntityDescription(SensorEntityDescription, NAMSensorRequiredKeysMixin): - """NAM sensor entity description.""" - - SENSORS: tuple[NAMSensorEntityDescription, ...] = ( NAMSensorEntityDescription( key=ATTR_BME280_HUMIDITY, From a7f71eaa3550699687cdfe36caaa3335b2caa2d3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 21:45:06 +0100 Subject: [PATCH 0675/1691] Remove entity description mixin in Tractive (#112952) Remove entity description mixin in Tractive --- homeassistant/components/tractive/sensor.py | 13 +++---------- homeassistant/components/tractive/switch.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/tractive/sensor.py b/homeassistant/components/tractive/sensor.py index 928d52c29df..b73b5faba05 100644 --- a/homeassistant/components/tractive/sensor.py +++ b/homeassistant/components/tractive/sensor.py @@ -44,19 +44,12 @@ from .const import ( from .entity import TractiveEntity -@dataclass(frozen=True) -class TractiveRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TractiveSensorEntityDescription(SensorEntityDescription): + """Class describing Tractive sensor entities.""" signal_prefix: str - -@dataclass(frozen=True) -class TractiveSensorEntityDescription( - SensorEntityDescription, TractiveRequiredKeysMixin -): - """Class describing Tractive sensor entities.""" - hardware_sensor: bool = False value_fn: Callable[[StateType], StateType] = lambda state: state diff --git a/homeassistant/components/tractive/switch.py b/homeassistant/components/tractive/switch.py index 9b8e6002d80..52aa9f1e901 100644 --- a/homeassistant/components/tractive/switch.py +++ b/homeassistant/components/tractive/switch.py @@ -29,20 +29,13 @@ from .entity import TractiveEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class TractiveRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TractiveSwitchEntityDescription(SwitchEntityDescription): + """Class describing Tractive switch entities.""" method: Literal["async_set_buzzer", "async_set_led", "async_set_live_tracking"] -@dataclass(frozen=True) -class TractiveSwitchEntityDescription( - SwitchEntityDescription, TractiveRequiredKeysMixin -): - """Class describing Tractive switch entities.""" - - SWITCH_TYPES: tuple[TractiveSwitchEntityDescription, ...] = ( TractiveSwitchEntityDescription( key=ATTR_BUZZER, From 2a5be33f341dd06088da954fe160a909c562b839 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 21:52:01 +0100 Subject: [PATCH 0676/1691] Remove entity description mixin in Synology DSM (#112946) --- .../components/synology_dsm/binary_sensor.py | 2 +- homeassistant/components/synology_dsm/button.py | 13 +++---------- homeassistant/components/synology_dsm/camera.py | 2 +- homeassistant/components/synology_dsm/entity.py | 11 +++-------- homeassistant/components/synology_dsm/sensor.py | 2 +- homeassistant/components/synology_dsm/switch.py | 2 +- homeassistant/components/synology_dsm/update.py | 2 +- 7 files changed, 11 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index 3c60b99c39f..7579f350774 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -28,7 +28,7 @@ from .entity import ( from .models import SynologyDSMData -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SynologyDSMBinarySensorEntityDescription( BinarySensorEntityDescription, SynologyDSMEntityDescription ): diff --git a/homeassistant/components/synology_dsm/button.py b/homeassistant/components/synology_dsm/button.py index e74aa034aa1..529682b4c6e 100644 --- a/homeassistant/components/synology_dsm/button.py +++ b/homeassistant/components/synology_dsm/button.py @@ -25,20 +25,13 @@ from .models import SynologyDSMData LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class SynologyDSMbuttonDescriptionMixin: - """Mixin to describe a Synology DSM button entity.""" +@dataclass(frozen=True, kw_only=True) +class SynologyDSMbuttonDescription(ButtonEntityDescription): + """Class to describe a Synology DSM button entity.""" press_action: Callable[[SynoApi], Callable[[], Coroutine[Any, Any, None]]] -@dataclass(frozen=True) -class SynologyDSMbuttonDescription( - ButtonEntityDescription, SynologyDSMbuttonDescriptionMixin -): - """Class to describe a Synology DSM button entity.""" - - BUTTONS: Final = [ SynologyDSMbuttonDescription( key="reboot", diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index 6289c816740..19f95c710d0 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -36,7 +36,7 @@ from .models import SynologyDSMData _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SynologyDSMCameraEntityDescription( CameraEntityDescription, SynologyDSMEntityDescription ): diff --git a/homeassistant/components/synology_dsm/entity.py b/homeassistant/components/synology_dsm/entity.py index 0c91d7914f3..4bd1e526194 100644 --- a/homeassistant/components/synology_dsm/entity.py +++ b/homeassistant/components/synology_dsm/entity.py @@ -19,18 +19,13 @@ from .coordinator import ( _CoordinatorT = TypeVar("_CoordinatorT", bound=SynologyDSMUpdateCoordinator[Any]) -@dataclass(frozen=True) -class SynologyDSMRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class SynologyDSMEntityDescription(EntityDescription): + """Generic Synology DSM entity description.""" api_key: str -@dataclass(frozen=True) -class SynologyDSMEntityDescription(EntityDescription, SynologyDSMRequiredKeysMixin): - """Generic Synology DSM entity description.""" - - class SynologyDSMBaseEntity(CoordinatorEntity[_CoordinatorT]): """Representation of a Synology NAS entry.""" diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 7e2f2582afb..47483ee4a63 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -40,7 +40,7 @@ from .entity import ( from .models import SynologyDSMData -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SynologyDSMSensorEntityDescription( SensorEntityDescription, SynologyDSMEntityDescription ): diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index d5845e3affe..6e1e38675a0 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -23,7 +23,7 @@ from .models import SynologyDSMData _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SynologyDSMSwitchEntityDescription( SwitchEntityDescription, SynologyDSMEntityDescription ): diff --git a/homeassistant/components/synology_dsm/update.py b/homeassistant/components/synology_dsm/update.py index 6505b116b73..7b1a36c57b3 100644 --- a/homeassistant/components/synology_dsm/update.py +++ b/homeassistant/components/synology_dsm/update.py @@ -20,7 +20,7 @@ from .entity import SynologyDSMBaseEntity, SynologyDSMEntityDescription from .models import SynologyDSMData -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SynologyDSMUpdateEntityEntityDescription( UpdateEntityDescription, SynologyDSMEntityDescription ): From c1d1479bdea034d88e13424d3820dd13a8525144 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 21:53:07 +0100 Subject: [PATCH 0677/1691] Add icon translations to Picnic (#112181) * Add icon translations to Picnic * fix --- homeassistant/components/picnic/icons.json | 62 ++++++++++++++++++++++ homeassistant/components/picnic/sensor.py | 16 ------ homeassistant/components/picnic/todo.py | 1 - 3 files changed, 62 insertions(+), 17 deletions(-) create mode 100644 homeassistant/components/picnic/icons.json diff --git a/homeassistant/components/picnic/icons.json b/homeassistant/components/picnic/icons.json new file mode 100644 index 00000000000..d8f99153f33 --- /dev/null +++ b/homeassistant/components/picnic/icons.json @@ -0,0 +1,62 @@ +{ + "entity": { + "sensor": { + "cart_items_count": { + "default": "mdi:format-list-numbered" + }, + "cart_total_price": { + "default": "mdi:currency-eur" + }, + "selected_slot_end": { + "default": "mdi:calendar-end" + }, + "selected_slot_max_order_time": { + "default": "mdi:clock-alert-outline" + }, + "selected_slot_min_order_value": { + "default": "mdi:currency-eur" + }, + "last_order_slot_start": { + "default": "mdi:calendar-start" + }, + "last_order_slot_end": { + "default": "mdi:calendar-end" + }, + "last_order_status": { + "default": "mdi:list-status" + }, + "last_order_max_order_time": { + "default": "mdi:clock-alert-outline" + }, + "last_order_delivery_time": { + "default": "mdi:timeline-clock" + }, + "last_order_total_price": { + "default": "mdi:cash-marker" + }, + "next_delivery_eta_start": { + "default": "mdi:clock-start" + }, + "next_delivery_eta_end": { + "default": "mdi:clock-end" + }, + "next_delivery_slot_start": { + "default": "mdi:calendar-start" + }, + "next_delivery_slot_end": { + "default": "mdi:calendar-end" + }, + "selected_slot_start": { + "default": "mdi:calendar-start" + } + }, + "todo": { + "shopping_cart": { + "default": "mdi:cart" + } + } + }, + "services": { + "add_product": "mdi:cart-plus" + } +} diff --git a/homeassistant/components/picnic/sensor.py b/homeassistant/components/picnic/sensor.py index 9127d82ba8f..3c186ccc034 100644 --- a/homeassistant/components/picnic/sensor.py +++ b/homeassistant/components/picnic/sensor.py @@ -66,7 +66,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( PicnicSensorEntityDescription( key=SENSOR_CART_ITEMS_COUNT, translation_key=SENSOR_CART_ITEMS_COUNT, - icon="mdi:format-list-numbered", data_type="cart_data", value_fn=lambda cart: cart.get("total_count", 0), ), @@ -74,7 +73,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_CART_TOTAL_PRICE, translation_key=SENSOR_CART_TOTAL_PRICE, native_unit_of_measurement=CURRENCY_EURO, - icon="mdi:currency-eur", entity_registry_enabled_default=True, data_type="cart_data", value_fn=lambda cart: cart.get("total_price", 0) / 100, @@ -83,7 +81,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_SELECTED_SLOT_START, translation_key=SENSOR_SELECTED_SLOT_START, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-start", entity_registry_enabled_default=True, data_type="slot_data", value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_start"))), @@ -92,7 +89,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_SELECTED_SLOT_END, translation_key=SENSOR_SELECTED_SLOT_END, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-end", entity_registry_enabled_default=True, data_type="slot_data", value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("window_end"))), @@ -101,7 +97,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME, translation_key=SENSOR_SELECTED_SLOT_MAX_ORDER_TIME, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-alert-outline", entity_registry_enabled_default=True, data_type="slot_data", value_fn=lambda slot: dt_util.parse_datetime(str(slot.get("cut_off_time"))), @@ -110,7 +105,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE, translation_key=SENSOR_SELECTED_SLOT_MIN_ORDER_VALUE, native_unit_of_measurement=CURRENCY_EURO, - icon="mdi:currency-eur", entity_registry_enabled_default=True, data_type="slot_data", value_fn=lambda slot: ( @@ -123,7 +117,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_LAST_ORDER_SLOT_START, translation_key=SENSOR_LAST_ORDER_SLOT_START, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-start", data_type="last_order_data", value_fn=lambda last_order: dt_util.parse_datetime( str(last_order.get("slot", {}).get("window_start")) @@ -133,7 +126,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_LAST_ORDER_SLOT_END, translation_key=SENSOR_LAST_ORDER_SLOT_END, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-end", data_type="last_order_data", value_fn=lambda last_order: dt_util.parse_datetime( str(last_order.get("slot", {}).get("window_end")) @@ -142,7 +134,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( PicnicSensorEntityDescription( key=SENSOR_LAST_ORDER_STATUS, translation_key=SENSOR_LAST_ORDER_STATUS, - icon="mdi:list-status", data_type="last_order_data", value_fn=lambda last_order: last_order.get("status"), ), @@ -150,7 +141,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_LAST_ORDER_MAX_ORDER_TIME, translation_key=SENSOR_LAST_ORDER_MAX_ORDER_TIME, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-alert-outline", entity_registry_enabled_default=True, data_type="last_order_data", value_fn=lambda last_order: dt_util.parse_datetime( @@ -161,7 +151,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_LAST_ORDER_DELIVERY_TIME, translation_key=SENSOR_LAST_ORDER_DELIVERY_TIME, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:timeline-clock", entity_registry_enabled_default=True, data_type="last_order_data", value_fn=lambda last_order: dt_util.parse_datetime( @@ -172,7 +161,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_LAST_ORDER_TOTAL_PRICE, translation_key=SENSOR_LAST_ORDER_TOTAL_PRICE, native_unit_of_measurement=CURRENCY_EURO, - icon="mdi:cash-marker", data_type="last_order_data", value_fn=lambda last_order: last_order.get("total_price", 0) / 100, ), @@ -180,7 +168,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_NEXT_DELIVERY_ETA_START, translation_key=SENSOR_NEXT_DELIVERY_ETA_START, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-start", entity_registry_enabled_default=True, data_type="next_delivery_data", value_fn=lambda next_delivery: dt_util.parse_datetime( @@ -191,7 +178,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_NEXT_DELIVERY_ETA_END, translation_key=SENSOR_NEXT_DELIVERY_ETA_END, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:clock-end", entity_registry_enabled_default=True, data_type="next_delivery_data", value_fn=lambda next_delivery: dt_util.parse_datetime( @@ -202,7 +188,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_NEXT_DELIVERY_SLOT_START, translation_key=SENSOR_NEXT_DELIVERY_SLOT_START, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-start", data_type="next_delivery_data", value_fn=lambda next_delivery: dt_util.parse_datetime( str(next_delivery.get("slot", {}).get("window_start")) @@ -212,7 +197,6 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( key=SENSOR_NEXT_DELIVERY_SLOT_END, translation_key=SENSOR_NEXT_DELIVERY_SLOT_END, device_class=SensorDeviceClass.TIMESTAMP, - icon="mdi:calendar-end", data_type="next_delivery_data", value_fn=lambda next_delivery: dt_util.parse_datetime( str(next_delivery.get("slot", {}).get("window_end")) diff --git a/homeassistant/components/picnic/todo.py b/homeassistant/components/picnic/todo.py index 2992c016148..c645a5ebf83 100644 --- a/homeassistant/components/picnic/todo.py +++ b/homeassistant/components/picnic/todo.py @@ -40,7 +40,6 @@ class PicnicCart(TodoListEntity, CoordinatorEntity[PicnicUpdateCoordinator]): """A Picnic Shopping Cart TodoListEntity.""" _attr_has_entity_name = True - _attr_icon = "mdi:cart" _attr_supported_features = TodoListEntityFeature.CREATE_TODO_ITEM _attr_translation_key = "shopping_cart" From d9f6e4be242c3449a1be464ac7c07f5ab68cef65 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 21:55:31 +0100 Subject: [PATCH 0678/1691] Add icon translations to Husqvarna automower (#111727) * Add icon translations to Husqvarna automower * fix --- .../components/husqvarna_automower/icons.json | 12 ++++++++++++ .../components/husqvarna_automower/sensor.py | 2 -- .../husqvarna_automower/snapshots/test_sensor.ambr | 6 ++---- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/husqvarna_automower/icons.json diff --git a/homeassistant/components/husqvarna_automower/icons.json b/homeassistant/components/husqvarna_automower/icons.json new file mode 100644 index 00000000000..a0dd316b535 --- /dev/null +++ b/homeassistant/components/husqvarna_automower/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "number_of_charging_cycles": { + "default": "mdi:battery-sync-outline" + }, + "number_of_collisions": { + "default": "mdi:counter" + } + } + } +} diff --git a/homeassistant/components/husqvarna_automower/sensor.py b/homeassistant/components/husqvarna_automower/sensor.py index 5358ad6e4cf..40f676df541 100644 --- a/homeassistant/components/husqvarna_automower/sensor.py +++ b/homeassistant/components/husqvarna_automower/sensor.py @@ -105,7 +105,6 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( AutomowerSensorEntityDescription( key="number_of_charging_cycles", translation_key="number_of_charging_cycles", - icon="mdi:battery-sync-outline", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL, value_fn=lambda data: data.statistics.number_of_charging_cycles, @@ -113,7 +112,6 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( AutomowerSensorEntityDescription( key="number_of_collisions", translation_key="number_of_collisions", - icon="mdi:counter", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL, value_fn=lambda data: data.statistics.number_of_collisions, diff --git a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr index f03624627bf..2e6dfcc52f0 100644 --- a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr @@ -234,7 +234,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:battery-sync-outline', + 'original_icon': None, 'original_name': 'Number of charging cycles', 'platform': 'husqvarna_automower', 'previous_unique_id': None, @@ -248,7 +248,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test Mower 1 Number of charging cycles', - 'icon': 'mdi:battery-sync-outline', 'state_class': , }), 'context': , @@ -283,7 +282,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:counter', + 'original_icon': None, 'original_name': 'Number of collisions', 'platform': 'husqvarna_automower', 'previous_unique_id': None, @@ -297,7 +296,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test Mower 1 Number of collisions', - 'icon': 'mdi:counter', 'state_class': , }), 'context': , From 9a647d9b17ee6b7f1d7aa4f664ef3bd2da0b6b04 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Sun, 10 Mar 2024 22:10:27 +0100 Subject: [PATCH 0679/1691] Bump aioautomower to 2024.3.0 (#112627) Fix error in Husqvarna automower in Zones dataclass --- .../husqvarna_automower/config_flow.py | 4 +-- .../husqvarna_automower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../husqvarna_automower/fixtures/mower.json | 30 ++++++++++++++++--- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/config_flow.py b/homeassistant/components/husqvarna_automower/config_flow.py index 88b4899ff12..b25a185c75f 100644 --- a/homeassistant/components/husqvarna_automower/config_flow.py +++ b/homeassistant/components/husqvarna_automower/config_flow.py @@ -4,7 +4,7 @@ from collections.abc import Mapping import logging from typing import Any -from aioautomower.utils import async_structure_token +from aioautomower.utils import structure_token from homeassistant.config_entries import ConfigEntry, ConfigFlowResult from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN @@ -34,7 +34,7 @@ class HusqvarnaConfigFlowHandler( if self.reauth_entry.unique_id != user_id: return self.async_abort(reason="wrong_account") return self.async_update_reload_and_abort(self.reauth_entry, data=data) - structured_token = await async_structure_token(token[CONF_ACCESS_TOKEN]) + structured_token = structure_token(token[CONF_ACCESS_TOKEN]) first_name = structured_token.user.first_name last_name = structured_token.user.last_name await self.async_set_unique_id(user_id) diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json index dc40116f31e..525f057c1ff 100644 --- a/homeassistant/components/husqvarna_automower/manifest.json +++ b/homeassistant/components/husqvarna_automower/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "iot_class": "cloud_push", - "requirements": ["aioautomower==2024.2.10"] + "requirements": ["aioautomower==2024.3.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6a0fc5031b4..64282d37743 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -203,7 +203,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.2.10 +aioautomower==2024.3.0 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ac87156f109..ff423968b11 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -182,7 +182,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.2.10 +aioautomower==2024.3.0 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/tests/components/husqvarna_automower/fixtures/mower.json b/tests/components/husqvarna_automower/fixtures/mower.json index 0af4926c561..1e608e654a6 100644 --- a/tests/components/husqvarna_automower/fixtures/mower.json +++ b/tests/components/husqvarna_automower/fixtures/mower.json @@ -62,6 +62,18 @@ "connected": true, "statusTimestamp": 1697669932683 }, + "workAreas": [ + { + "workAreaId": 123456, + "name": "Front lawn", + "cuttingHeight": 50 + }, + { + "workAreaId": 0, + "name": "", + "cuttingHeight": 50 + } + ], "positions": [ { "latitude": 35.5402913, @@ -120,10 +132,6 @@ "longitude": -82.5520054 } ], - "cuttingHeight": 4, - "headlight": { - "mode": "EVENING_ONLY" - }, "statistics": { "cuttingBladeUsageTime": 123, "numberOfChargingCycles": 1380, @@ -133,6 +141,20 @@ "totalDriveDistance": 1780272, "totalRunningTime": 4564800, "totalSearchingTime": 370800 + }, + "stayOutZones": { + "dirty": false, + "zones": [ + { + "id": "81C6EEA2-D139-4FEA-B134-F22A6B3EA403", + "name": "Springflowers", + "enabled": true + } + ] + }, + "cuttingHeight": 4, + "headlight": { + "mode": "EVENING_ONLY" } } } From b125a6b1bb88937706e2d0c34afb273be76a3391 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 10 Mar 2024 22:15:23 +0100 Subject: [PATCH 0680/1691] Remove entity description mixin in Shelly (#112938) * Remove entity description mixin in Shelly * fix * Fix --- .../components/shelly/binary_sensor.py | 6 ++-- homeassistant/components/shelly/button.py | 13 ++----- homeassistant/components/shelly/entity.py | 13 +++---- homeassistant/components/shelly/event.py | 4 +-- homeassistant/components/shelly/number.py | 2 +- homeassistant/components/shelly/sensor.py | 6 ++-- homeassistant/components/shelly/switch.py | 2 +- homeassistant/components/shelly/update.py | 34 ++++++------------- 8 files changed, 27 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index bce84a829ed..f2d1fe15bbe 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -39,19 +39,19 @@ from .utils import ( ) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BlockBinarySensorDescription( BlockEntityDescription, BinarySensorEntityDescription ): """Class to describe a BLOCK binary sensor.""" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RpcBinarySensorDescription(RpcEntityDescription, BinarySensorEntityDescription): """Class to describe a RPC binary sensor.""" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescription): """Class to describe a REST binary sensor.""" diff --git a/homeassistant/components/shelly/button.py b/homeassistant/components/shelly/button.py index 1c497231b3e..12c347908fb 100644 --- a/homeassistant/components/shelly/button.py +++ b/homeassistant/components/shelly/button.py @@ -32,19 +32,12 @@ _ShellyCoordinatorT = TypeVar( ) -@dataclass(frozen=True) -class ShellyButtonDescriptionMixin(Generic[_ShellyCoordinatorT]): - """Mixin to describe a Button entity.""" +@dataclass(frozen=True, kw_only=True) +class ShellyButtonDescription(ButtonEntityDescription, Generic[_ShellyCoordinatorT]): + """Class to describe a Button entity.""" press_action: Callable[[_ShellyCoordinatorT], Coroutine[Any, Any, None]] - -@dataclass(frozen=True) -class ShellyButtonDescription( - ButtonEntityDescription, ShellyButtonDescriptionMixin[_ShellyCoordinatorT] -): - """Class to describe a Button entity.""" - supported: Callable[[_ShellyCoordinatorT], bool] = lambda _: True diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 6cb4c7f6542..d5202713405 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -279,21 +279,16 @@ class BlockEntityDescription(EntityDescription): extra_state_attributes: Callable[[Block], dict | None] | None = None -@dataclass(frozen=True) -class RpcEntityRequiredKeysMixin: - """Class for RPC entity required keys.""" - - sub_key: str - - -@dataclass(frozen=True) -class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin): +@dataclass(frozen=True, kw_only=True) +class RpcEntityDescription(EntityDescription): """Class to describe a RPC entity.""" # BlockEntity does not support UNDEFINED or None, # restrict the type to str. name: str = "" + sub_key: str + value: Callable[[Any, Any], Any] | None = None available: Callable[[dict], bool] | None = None removal_condition: Callable[[dict, dict, str], bool] | None = None diff --git a/homeassistant/components/shelly/event.py b/homeassistant/components/shelly/event.py index ea7cac2d253..0b6b81461ac 100644 --- a/homeassistant/components/shelly/event.py +++ b/homeassistant/components/shelly/event.py @@ -38,14 +38,14 @@ from .utils import ( ) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ShellyBlockEventDescription(EventEntityDescription): """Class to describe Shelly event.""" removal_condition: Callable[[dict, Block], bool] | None = None -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ShellyRpcEventDescription(EventEntityDescription): """Class to describe Shelly event.""" diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index 3dd2550b6ff..1b1c9c42a11 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -30,7 +30,7 @@ from .entity import ( ) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BlockNumberDescription(BlockEntityDescription, NumberEntityDescription): """Class to describe a BLOCK sensor.""" diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 3dc9102df70..ec5f31dae8a 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -59,17 +59,17 @@ from .utils import ( ) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BlockSensorDescription(BlockEntityDescription, SensorEntityDescription): """Class to describe a BLOCK sensor.""" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RpcSensorDescription(RpcEntityDescription, SensorEntityDescription): """Class to describe a RPC sensor.""" -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class RestSensorDescription(RestEntityDescription, SensorEntityDescription): """Class to describe a REST sensor.""" diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index b690cfab6c8..48ff337d22a 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -46,7 +46,7 @@ from .utils import ( ) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class BlockSwitchDescription(BlockEntityDescription, SwitchEntityDescription): """Class to describe a BLOCK switch.""" diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index 765e4ee441b..414bb4d6258 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -41,35 +41,21 @@ from .utils import get_device_entry_gen, get_release_url LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class RpcUpdateRequiredKeysMixin: - """Class for RPC update required keys.""" - - latest_version: Callable[[dict], Any] - beta: bool - - -@dataclass(frozen=True) -class RestUpdateRequiredKeysMixin: - """Class for REST update required keys.""" - - latest_version: Callable[[dict], Any] - beta: bool - - -@dataclass(frozen=True) -class RpcUpdateDescription( - RpcEntityDescription, UpdateEntityDescription, RpcUpdateRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class RpcUpdateDescription(RpcEntityDescription, UpdateEntityDescription): """Class to describe a RPC update.""" + latest_version: Callable[[dict], Any] + beta: bool -@dataclass(frozen=True) -class RestUpdateDescription( - RestEntityDescription, UpdateEntityDescription, RestUpdateRequiredKeysMixin -): + +@dataclass(frozen=True, kw_only=True) +class RestUpdateDescription(RestEntityDescription, UpdateEntityDescription): """Class to describe a REST update.""" + latest_version: Callable[[dict], Any] + beta: bool + REST_UPDATES: Final = { "fwupdate": RestUpdateDescription( From db31afe019c0737e37c7afa72fa4117957e7bf82 Mon Sep 17 00:00:00 2001 From: Yuxin Wang Date: Sun, 10 Mar 2024 18:56:25 -0400 Subject: [PATCH 0681/1691] Migrate APCUPSD to has entity name (#112997) * Properly set entity names for APCUPSD * Add test cases to prevent future regressions * Fix tests due to the updated entity IDs * Prettify code * Remove redundant translation key --- .../components/apcupsd/binary_sensor.py | 3 +- homeassistant/components/apcupsd/sensor.py | 86 ++------ homeassistant/components/apcupsd/strings.json | 204 ++++++++++++++++++ .../components/apcupsd/test_binary_sensor.py | 11 +- tests/components/apcupsd/test_init.py | 57 ++++- tests/components/apcupsd/test_sensor.py | 91 +++++--- 6 files changed, 344 insertions(+), 108 deletions(-) diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index ea0308f5450..bc214e56d80 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -20,7 +20,6 @@ from .coordinator import APCUPSdCoordinator _LOGGER = logging.getLogger(__name__) _DESCRIPTION = BinarySensorEntityDescription( key="statflag", - name="UPS Online Status", translation_key="online_status", ) # The bit in STATFLAG that indicates the online status of the APC UPS. @@ -46,6 +45,8 @@ async def async_setup_entry( class OnlineStatus(CoordinatorEntity[APCUPSdCoordinator], BinarySensorEntity): """Representation of a UPS online status.""" + _attr_has_entity_name = True + def __init__( self, coordinator: APCUPSdCoordinator, diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 8162653abb3..008171cfe3c 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -34,11 +34,10 @@ SENSORS: dict[str, SensorEntityDescription] = { "alarmdel": SensorEntityDescription( key="alarmdel", translation_key="alarm_delay", - name="UPS Alarm Delay", ), "ambtemp": SensorEntityDescription( key="ambtemp", - name="UPS Ambient Temperature", + translation_key="ambient_temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -46,40 +45,34 @@ SENSORS: dict[str, SensorEntityDescription] = { "apc": SensorEntityDescription( key="apc", translation_key="apc_status", - name="UPS Status Data", entity_registry_enabled_default=False, ), "apcmodel": SensorEntityDescription( key="apcmodel", translation_key="apc_model", - name="UPS Model", entity_registry_enabled_default=False, ), "badbatts": SensorEntityDescription( key="badbatts", translation_key="bad_batteries", - name="UPS Bad Batteries", ), "battdate": SensorEntityDescription( key="battdate", translation_key="battery_replacement_date", - name="UPS Battery Replaced", ), "battstat": SensorEntityDescription( key="battstat", translation_key="battery_status", - name="UPS Battery Status", ), "battv": SensorEntityDescription( key="battv", - name="UPS Battery Voltage", + translation_key="battery_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, ), "bcharge": SensorEntityDescription( key="bcharge", - name="UPS Battery", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, @@ -87,86 +80,74 @@ SENSORS: dict[str, SensorEntityDescription] = { "cable": SensorEntityDescription( key="cable", translation_key="cable_type", - name="UPS Cable Type", entity_registry_enabled_default=False, ), "cumonbatt": SensorEntityDescription( key="cumonbatt", translation_key="total_time_on_battery", - name="UPS Total Time on Battery", state_class=SensorStateClass.TOTAL_INCREASING, ), "date": SensorEntityDescription( key="date", translation_key="date", - name="UPS Status Date", entity_registry_enabled_default=False, ), "dipsw": SensorEntityDescription( key="dipsw", translation_key="dip_switch_settings", - name="UPS Dip Switch Settings", ), "dlowbatt": SensorEntityDescription( key="dlowbatt", translation_key="low_battery_signal", - name="UPS Low Battery Signal", ), "driver": SensorEntityDescription( key="driver", translation_key="driver", - name="UPS Driver", entity_registry_enabled_default=False, ), "dshutd": SensorEntityDescription( key="dshutd", translation_key="shutdown_delay", - name="UPS Shutdown Delay", ), "dwake": SensorEntityDescription( key="dwake", translation_key="wake_delay", - name="UPS Wake Delay", ), "end apc": SensorEntityDescription( key="end apc", translation_key="date_and_time", - name="UPS Date and Time", entity_registry_enabled_default=False, ), "extbatts": SensorEntityDescription( key="extbatts", translation_key="external_batteries", - name="UPS External Batteries", ), "firmware": SensorEntityDescription( key="firmware", translation_key="firmware_version", - name="UPS Firmware Version", entity_registry_enabled_default=False, ), "hitrans": SensorEntityDescription( key="hitrans", - name="UPS Transfer High", + translation_key="transfer_high", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "hostname": SensorEntityDescription( key="hostname", translation_key="hostname", - name="UPS Hostname", entity_registry_enabled_default=False, ), "humidity": SensorEntityDescription( key="humidity", - name="UPS Ambient Humidity", + translation_key="humidity", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), "itemp": SensorEntityDescription( key="itemp", - name="UPS Internal Temperature", + translation_key="internal_temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, @@ -174,29 +155,26 @@ SENSORS: dict[str, SensorEntityDescription] = { "laststest": SensorEntityDescription( key="laststest", translation_key="last_self_test", - name="UPS Last Self Test", ), "lastxfer": SensorEntityDescription( key="lastxfer", translation_key="last_transfer", - name="UPS Last Transfer", entity_registry_enabled_default=False, ), "linefail": SensorEntityDescription( key="linefail", translation_key="line_failure", - name="UPS Input Voltage Status", ), "linefreq": SensorEntityDescription( key="linefreq", - name="UPS Line Frequency", + translation_key="line_frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, state_class=SensorStateClass.MEASUREMENT, ), "linev": SensorEntityDescription( key="linev", - name="UPS Input Voltage", + translation_key="line_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, @@ -204,113 +182,104 @@ SENSORS: dict[str, SensorEntityDescription] = { "loadpct": SensorEntityDescription( key="loadpct", translation_key="load_capacity", - name="UPS Load", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), "loadapnt": SensorEntityDescription( key="loadapnt", translation_key="apparent_power", - name="UPS Load Apparent Power", native_unit_of_measurement=PERCENTAGE, ), "lotrans": SensorEntityDescription( key="lotrans", - name="UPS Transfer Low", + translation_key="transfer_low", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "mandate": SensorEntityDescription( key="mandate", translation_key="manufacture_date", - name="UPS Manufacture Date", entity_registry_enabled_default=False, ), "masterupd": SensorEntityDescription( key="masterupd", translation_key="master_update", - name="UPS Master Update", ), "maxlinev": SensorEntityDescription( key="maxlinev", - name="UPS Input Voltage High", + translation_key="input_voltage_high", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "maxtime": SensorEntityDescription( key="maxtime", translation_key="max_time", - name="UPS Battery Timeout", ), "mbattchg": SensorEntityDescription( key="mbattchg", translation_key="max_battery_charge", - name="UPS Battery Shutdown", native_unit_of_measurement=PERCENTAGE, ), "minlinev": SensorEntityDescription( key="minlinev", - name="UPS Input Voltage Low", + translation_key="input_voltage_low", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "mintimel": SensorEntityDescription( key="mintimel", translation_key="min_time", - name="UPS Shutdown Time", ), "model": SensorEntityDescription( key="model", translation_key="model", - name="UPS Model", entity_registry_enabled_default=False, ), "nombattv": SensorEntityDescription( key="nombattv", - name="UPS Battery Nominal Voltage", + translation_key="battery_nominal_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "nominv": SensorEntityDescription( key="nominv", - name="UPS Nominal Input Voltage", + translation_key="nominal_input_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "nomoutv": SensorEntityDescription( key="nomoutv", - name="UPS Nominal Output Voltage", + translation_key="nominal_output_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, ), "nompower": SensorEntityDescription( key="nompower", - name="UPS Nominal Output Power", + translation_key="nominal_output_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, ), "nomapnt": SensorEntityDescription( key="nomapnt", - name="UPS Nominal Apparent Power", + translation_key="nominal_apparent_power", native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE, device_class=SensorDeviceClass.APPARENT_POWER, ), "numxfers": SensorEntityDescription( key="numxfers", translation_key="transfer_count", - name="UPS Transfer Count", state_class=SensorStateClass.TOTAL_INCREASING, ), "outcurnt": SensorEntityDescription( key="outcurnt", - name="UPS Output Current", + translation_key="output_current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, ), "outputv": SensorEntityDescription( key="outputv", - name="UPS Output Voltage", + translation_key="output_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, @@ -318,108 +287,89 @@ SENSORS: dict[str, SensorEntityDescription] = { "reg1": SensorEntityDescription( key="reg1", translation_key="register_1_fault", - name="UPS Register 1 Fault", entity_registry_enabled_default=False, ), "reg2": SensorEntityDescription( key="reg2", translation_key="register_2_fault", - name="UPS Register 2 Fault", entity_registry_enabled_default=False, ), "reg3": SensorEntityDescription( key="reg3", translation_key="register_3_fault", - name="UPS Register 3 Fault", entity_registry_enabled_default=False, ), "retpct": SensorEntityDescription( key="retpct", translation_key="restore_capacity", - name="UPS Restore Requirement", native_unit_of_measurement=PERCENTAGE, ), "selftest": SensorEntityDescription( key="selftest", translation_key="self_test_result", - name="UPS Self Test result", ), "sense": SensorEntityDescription( key="sense", translation_key="sensitivity", - name="UPS Sensitivity", entity_registry_enabled_default=False, ), "serialno": SensorEntityDescription( key="serialno", translation_key="serial_number", - name="UPS Serial Number", entity_registry_enabled_default=False, ), "starttime": SensorEntityDescription( key="starttime", translation_key="startup_time", - name="UPS Startup Time", ), "statflag": SensorEntityDescription( key="statflag", translation_key="online_status", - name="UPS Status Flag", entity_registry_enabled_default=False, ), "status": SensorEntityDescription( key="status", translation_key="status", - name="UPS Status", ), "stesti": SensorEntityDescription( key="stesti", translation_key="self_test_interval", - name="UPS Self Test Interval", ), "timeleft": SensorEntityDescription( key="timeleft", translation_key="time_left", - name="UPS Time Left", state_class=SensorStateClass.MEASUREMENT, ), "tonbatt": SensorEntityDescription( key="tonbatt", translation_key="time_on_battery", - name="UPS Time on Battery", state_class=SensorStateClass.TOTAL_INCREASING, ), "upsmode": SensorEntityDescription( key="upsmode", translation_key="ups_mode", - name="UPS Mode", ), "upsname": SensorEntityDescription( key="upsname", translation_key="ups_name", - name="UPS Name", entity_registry_enabled_default=False, ), "version": SensorEntityDescription( key="version", translation_key="version", - name="UPS Daemon Info", entity_registry_enabled_default=False, ), "xoffbat": SensorEntityDescription( key="xoffbat", translation_key="transfer_from_battery", - name="UPS Transfer from Battery", ), "xoffbatt": SensorEntityDescription( key="xoffbatt", translation_key="transfer_from_battery", - name="UPS Transfer from Battery", ), "xonbatt": SensorEntityDescription( key="xonbatt", translation_key="transfer_to_battery", - name="UPS Transfer to Battery", ), } @@ -486,6 +436,8 @@ def infer_unit(value: str) -> tuple[str, str | None]: class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity): """Representation of a sensor entity for APCUPSd status values.""" + _attr_has_entity_name = True + def __init__( self, coordinator: APCUPSdCoordinator, diff --git a/homeassistant/components/apcupsd/strings.json b/homeassistant/components/apcupsd/strings.json index c7ebf8a0a3b..15ffb928f00 100644 --- a/homeassistant/components/apcupsd/strings.json +++ b/homeassistant/components/apcupsd/strings.json @@ -16,5 +16,209 @@ "description": "Enter the host and port on which the apcupsd NIS is being served." } } + }, + "entity": { + "binary_sensor": { + "online_status": { + "name": "Online status" + } + }, + "sensor": { + "alarm_delay": { + "name": "Alarm delay" + }, + "ambient_temperature": { + "name": "Ambient temperature" + }, + "apc_status": { + "name": "Status data" + }, + "apc_model": { + "name": "Model" + }, + "bad_batteries": { + "name": "Bad batteries" + }, + "battery_replacement_date": { + "name": "Battery replaced" + }, + "battery_status": { + "name": "Battery status" + }, + "battery_voltage": { + "name": "Battery voltage" + }, + "cable_type": { + "name": "Cable type" + }, + "total_time_on_battery": { + "name": "Total time on battery" + }, + "date": { + "name": "Status date" + }, + "dip_switch_settings": { + "name": "Dip switch settings" + }, + "low_battery_signal": { + "name": "Low battery signal" + }, + "driver": { + "name": "Driver" + }, + "shutdown_delay": { + "name": "Shutdown delay" + }, + "wake_delay": { + "name": "Wake delay" + }, + "date_and_time": { + "name": "Date and time" + }, + "external_batteries": { + "name": "External batteries" + }, + "firmware_version": { + "name": "Firmware version" + }, + "transfer_high": { + "name": "Transfer high" + }, + "hostname": { + "name": "Hostname" + }, + "humidity": { + "name": "Ambient humidity" + }, + "internal_temperature": { + "name": "Internal temperature" + }, + "last_self_test": { + "name": "Last self test" + }, + "last_transfer": { + "name": "Last transfer" + }, + "line_failure": { + "name": "Input voltage status" + }, + "line_frequency": { + "name": "Line frequency" + }, + "line_voltage": { + "name": "Input voltage" + }, + "load_capacity": { + "name": "Load" + }, + "apparent_power": { + "name": "Load apparent power" + }, + "transfer_low": { + "name": "Transfer low" + }, + "manufacture_date": { + "name": "Manufacture date" + }, + "master_update": { + "name": "Master update" + }, + "input_voltage_high": { + "name": "Input voltage high" + }, + "max_time": { + "name": "Battery timeout" + }, + "max_battery_charge": { + "name": "Battery shutdown" + }, + "input_voltage_low": { + "name": "Input voltage low" + }, + "min_time": { + "name": "Shutdown time" + }, + "model": { + "name": "Model" + }, + "battery_nominal_voltage": { + "name": "Battery nominal voltage" + }, + "nominal_input_voltage": { + "name": "Nominal input voltage" + }, + "nominal_output_voltage": { + "name": "Nominal output voltage" + }, + "nominal_output_power": { + "name": "Nominal output power" + }, + "nominal_apparent_power": { + "name": "Nominal apparent power" + }, + "transfer_count": { + "name": "Transfer count" + }, + "output_current": { + "name": "Output current" + }, + "output_voltage": { + "name": "Output voltage" + }, + "register_1_fault": { + "name": "Register 1 fault" + }, + "register_2_fault": { + "name": "Register 2 fault" + }, + "register_3_fault": { + "name": "Register 3 fault" + }, + "restore_capacity": { + "name": "Restore requirement" + }, + "self_test_result": { + "name": "Self test result" + }, + "sensitivity": { + "name": "Sensitivity" + }, + "serial_number": { + "name": "Serial number" + }, + "startup_time": { + "name": "Startup time" + }, + "online_status": { + "name": "Status flag" + }, + "status": { + "name": "Status" + }, + "self_test_interval": { + "name": "Self test interval" + }, + "time_left": { + "name": "Time left" + }, + "time_on_battery": { + "name": "Time on battery" + }, + "ups_mode": { + "name": "Mode" + }, + "ups_name": { + "name": "Name" + }, + "version": { + "name": "Daemon version" + }, + "transfer_from_battery": { + "name": "Transfer from battery" + }, + "transfer_to_battery": { + "name": "Transfer to battery" + } + } } } diff --git a/tests/components/apcupsd/test_binary_sensor.py b/tests/components/apcupsd/test_binary_sensor.py index 21d972ec0ea..7616a960b21 100644 --- a/tests/components/apcupsd/test_binary_sensor.py +++ b/tests/components/apcupsd/test_binary_sensor.py @@ -2,6 +2,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from homeassistant.util import slugify from . import MOCK_STATUS, async_init_integration @@ -12,12 +13,13 @@ async def test_binary_sensor( """Test states of binary sensor.""" await async_init_integration(hass, status=MOCK_STATUS) - state = hass.states.get("binary_sensor.ups_online_status") + device_slug, serialno = slugify(MOCK_STATUS["UPSNAME"]), MOCK_STATUS["SERIALNO"] + state = hass.states.get(f"binary_sensor.{device_slug}_online_status") assert state assert state.state == "on" - entry = entity_registry.async_get("binary_sensor.ups_online_status") + entry = entity_registry.async_get(f"binary_sensor.{device_slug}_online_status") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_statflag" + assert entry.unique_id == f"{serialno}_statflag" async def test_no_binary_sensor(hass: HomeAssistant) -> None: @@ -26,5 +28,6 @@ async def test_no_binary_sensor(hass: HomeAssistant) -> None: status.pop("STATFLAG") await async_init_integration(hass, status=status) - state = hass.states.get("binary_sensor.ups_online_status") + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state = hass.states.get(f"binary_sensor.{device_slug}_online_status") assert state is None diff --git a/tests/components/apcupsd/test_init.py b/tests/components/apcupsd/test_init.py index e6db0adc4c7..4a579cdb899 100644 --- a/tests/components/apcupsd/test_init.py +++ b/tests/components/apcupsd/test_init.py @@ -12,23 +12,41 @@ from homeassistant.config_entries import SOURCE_USER, ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from homeassistant.util import utcnow +from homeassistant.util import slugify, utcnow from . import CONF_DATA, MOCK_MINIMAL_STATUS, MOCK_STATUS, async_init_integration from tests.common import MockConfigEntry, async_fire_time_changed -@pytest.mark.parametrize("status", (MOCK_STATUS, MOCK_MINIMAL_STATUS)) +@pytest.mark.parametrize( + "status", + ( + # Contains "SERIALNO" and "UPSNAME" fields. + # We should create devices for the entities and prefix their IDs with "MyUPS". + MOCK_STATUS, + # Contains "SERIALNO" but no "UPSNAME" field. + # We should create devices for the entities and prefix their IDs with default "APC UPS". + MOCK_MINIMAL_STATUS | {"SERIALNO": "XXXX"}, + # Does not contain either "SERIALNO" field. + # We should _not_ create devices for the entities and their IDs will not have prefixes. + MOCK_MINIMAL_STATUS, + ), +) async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> None: """Test a successful setup entry.""" # Minimal status does not contain "SERIALNO" field, which is used to determine the # unique ID of this integration. But, the integration should work fine without it. + # In such a case, the device will not be added either await async_init_integration(hass, status=status) + prefix = "" + if "SERIALNO" in status: + prefix = slugify(status.get("UPSNAME", "APC UPS")) + "_" + # Verify successful setup by querying the status sensor. - state = hass.states.get("binary_sensor.ups_online_status") - assert state is not None + state = hass.states.get(f"binary_sensor.{prefix}online_status") + assert state assert state.state != STATE_UNAVAILABLE assert state.state == "on" @@ -93,12 +111,32 @@ async def test_multiple_integrations(hass: HomeAssistant) -> None: assert len(hass.config_entries.async_entries(DOMAIN)) == 2 assert all(entry.state is ConfigEntryState.LOADED for entry in entries) - state1 = hass.states.get("sensor.ups_load") - state2 = hass.states.get("sensor.ups_load_2") + # Since the two UPS device names are the same, we will have to add a "_2" suffix. + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state1 = hass.states.get(f"sensor.{device_slug}_load") + state2 = hass.states.get(f"sensor.{device_slug}_load_2") assert state1 is not None and state2 is not None assert state1.state != state2.state +async def test_multiple_integrations_different_devices(hass: HomeAssistant) -> None: + """Test successful setup for multiple entries with different device names.""" + status1 = MOCK_STATUS | {"SERIALNO": "XXXXX1", "UPSNAME": "MyUPS1"} + status2 = MOCK_STATUS | {"SERIALNO": "XXXXX2", "UPSNAME": "MyUPS2"} + entries = ( + await async_init_integration(hass, host="test1", status=status1), + await async_init_integration(hass, host="test2", status=status2), + ) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 2 + assert all(entry.state is ConfigEntryState.LOADED for entry in entries) + + # The device names are different, so they are prefixed differently. + state1 = hass.states.get("sensor.myups1_load") + state2 = hass.states.get("sensor.myups2_load") + assert state1 is not None and state2 is not None + + @pytest.mark.parametrize( "error", (OSError(), asyncio.IncompleteReadError(partial=b"", expected=0)), @@ -154,7 +192,8 @@ async def test_availability(hass: HomeAssistant) -> None: """Ensure that we mark the entity's availability properly when network is down / back up.""" await async_init_integration(hass) - state = hass.states.get("sensor.ups_load") + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert pytest.approx(float(state.state)) == 14.0 @@ -167,7 +206,7 @@ async def test_availability(hass: HomeAssistant) -> None: await hass.async_block_till_done() # Sensors should be marked as unavailable. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state == STATE_UNAVAILABLE @@ -179,7 +218,7 @@ async def test_availability(hass: HomeAssistant) -> None: await hass.async_block_till_done() # Sensors should be online now with the new value. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert pytest.approx(float(state.state)) == 15.0 diff --git a/tests/components/apcupsd/test_sensor.py b/tests/components/apcupsd/test_sensor.py index 24aae1d3937..77987e455c7 100644 --- a/tests/components/apcupsd/test_sensor.py +++ b/tests/components/apcupsd/test_sensor.py @@ -22,6 +22,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component +from homeassistant.util import slugify from homeassistant.util.dt import utcnow from . import MOCK_STATUS, async_init_integration @@ -32,17 +33,18 @@ from tests.common import async_fire_time_changed async def test_sensor(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> None: """Test states of sensor.""" await async_init_integration(hass, status=MOCK_STATUS) + device_slug, serialno = slugify(MOCK_STATUS["UPSNAME"]), MOCK_STATUS["SERIALNO"] # Test a representative string sensor. - state = hass.states.get("sensor.ups_mode") + state = hass.states.get(f"sensor.{device_slug}_mode") assert state assert state.state == "Stand Alone" - entry = entity_registry.async_get("sensor.ups_mode") + entry = entity_registry.async_get(f"sensor.{device_slug}_mode") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_upsmode" + assert entry.unique_id == f"{serialno}_upsmode" # Test two representative voltage sensors. - state = hass.states.get("sensor.ups_input_voltage") + state = hass.states.get(f"sensor.{device_slug}_input_voltage") assert state assert state.state == "124.0" assert ( @@ -50,11 +52,11 @@ async def test_sensor(hass: HomeAssistant, entity_registry: er.EntityRegistry) - ) assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE - entry = entity_registry.async_get("sensor.ups_input_voltage") + entry = entity_registry.async_get(f"sensor.{device_slug}_input_voltage") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_linev" + assert entry.unique_id == f"{serialno}_linev" - state = hass.states.get("sensor.ups_battery_voltage") + state = hass.states.get(f"sensor.{device_slug}_battery_voltage") assert state assert state.state == "13.7" assert ( @@ -62,38 +64,59 @@ async def test_sensor(hass: HomeAssistant, entity_registry: er.EntityRegistry) - ) assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLTAGE - entry = entity_registry.async_get("sensor.ups_battery_voltage") + entry = entity_registry.async_get(f"sensor.{device_slug}_battery_voltage") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_battv" + assert entry.unique_id == f"{serialno}_battv" - # test a representative time sensor. - state = hass.states.get("sensor.ups_self_test_interval") + # Test a representative time sensor. + state = hass.states.get(f"sensor.{device_slug}_self_test_interval") assert state assert state.state == "7" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTime.DAYS - entry = entity_registry.async_get("sensor.ups_self_test_interval") + entry = entity_registry.async_get(f"sensor.{device_slug}_self_test_interval") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_stesti" + assert entry.unique_id == f"{serialno}_stesti" # Test a representative percentage sensor. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state == "14.0" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.MEASUREMENT - entry = entity_registry.async_get("sensor.ups_load") + entry = entity_registry.async_get(f"sensor.{device_slug}_load") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_loadpct" + assert entry.unique_id == f"{serialno}_loadpct" # Test a representative wattage sensor. - state = hass.states.get("sensor.ups_nominal_output_power") + state = hass.states.get(f"sensor.{device_slug}_nominal_output_power") assert state assert state.state == "330" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPower.WATT assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.POWER - entry = entity_registry.async_get("sensor.ups_nominal_output_power") + entry = entity_registry.async_get(f"sensor.{device_slug}_nominal_output_power") assert entry - assert entry.unique_id == "XXXXXXXXXXXX_nompower" + assert entry.unique_id == f"{serialno}_nompower" + + +async def test_sensor_name(hass: HomeAssistant) -> None: + """Test if sensor name follows the recommended entity naming scheme. + + See https://developers.home-assistant.io/docs/core/entity/#entity-naming for more details. + """ + await async_init_integration(hass, status=MOCK_STATUS) + + all_states = hass.states.async_all() + assert len(all_states) != 0 + + device_name = MOCK_STATUS["UPSNAME"] + for state in all_states: + # Friendly name must start with the device name. + friendly_name = state.name + assert friendly_name.startswith(device_name) + + # Entity names should start with a capital letter, the rest of the words are lower case. + entity_name = friendly_name.removeprefix(device_name).strip() + assert entity_name == entity_name.capitalize() async def test_sensor_disabled( @@ -102,10 +125,11 @@ async def test_sensor_disabled( """Test sensor disabled by default.""" await async_init_integration(hass) + device_slug, serialno = slugify(MOCK_STATUS["UPSNAME"]), MOCK_STATUS["SERIALNO"] # Test a representative integration-disabled sensor. - entry = entity_registry.async_get("sensor.ups_model") + entry = entity_registry.async_get(f"sensor.{device_slug}_model") assert entry.disabled - assert entry.unique_id == "XXXXXXXXXXXX_model" + assert entry.unique_id == f"{serialno}_model" assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION # Test enabling entity. @@ -121,7 +145,8 @@ async def test_state_update(hass: HomeAssistant) -> None: """Ensure the sensor state changes after updating the data.""" await async_init_integration(hass) - state = hass.states.get("sensor.ups_load") + device_slug = slugify(MOCK_STATUS["UPSNAME"]) + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "14.0" @@ -132,7 +157,7 @@ async def test_state_update(hass: HomeAssistant) -> None: async_fire_time_changed(hass, future) await hass.async_block_till_done() - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "15.0" @@ -142,8 +167,9 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: """Test manual update entity via service homeassistant/update_entity.""" await async_init_integration(hass) + device_slug = slugify(MOCK_STATUS["UPSNAME"]) # Assert the initial state of sensor.ups_load. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "14.0" @@ -163,7 +189,12 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: await hass.services.async_call( "homeassistant", "update_entity", - {ATTR_ENTITY_ID: ["sensor.ups_load", "sensor.ups_battery"]}, + { + ATTR_ENTITY_ID: [ + f"sensor.{device_slug}_load", + f"sensor.{device_slug}_battery", + ] + }, blocking=True, ) # Even if we requested updates for two entities, our integration should smartly @@ -171,7 +202,7 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: assert mock_request_status.call_count == 1 # The new state should be effective. - state = hass.states.get("sensor.ups_load") + state = hass.states.get(f"sensor.{device_slug}_load") assert state assert state.state != STATE_UNAVAILABLE assert state.state == "15.0" @@ -184,6 +215,7 @@ async def test_multiple_manual_update_entity(hass: HomeAssistant) -> None: """ await async_init_integration(hass) + device_slug = slugify(MOCK_STATUS["UPSNAME"]) # Setup HASS for calling the update_entity service. await async_setup_component(hass, "homeassistant", {}) @@ -196,7 +228,12 @@ async def test_multiple_manual_update_entity(hass: HomeAssistant) -> None: await hass.services.async_call( "homeassistant", "update_entity", - {ATTR_ENTITY_ID: ["sensor.ups_load", "sensor.ups_input_voltage"]}, + { + ATTR_ENTITY_ID: [ + f"sensor.{device_slug}_load", + f"sensor.{device_slug}_input_voltage", + ] + }, blocking=True, ) assert mock_request_status.call_count == 1 From fad5fc5256f32a000932b909471a7662785fb862 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Sun, 10 Mar 2024 19:12:27 -0400 Subject: [PATCH 0682/1691] Fix race condition when ZHA group members change (#113030) --- homeassistant/components/zha/entity.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 98e56c69075..5b9a11cb621 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -311,6 +311,10 @@ class ZhaGroupEntity(BaseZhaEntity): self._handled_group_membership = True await self.async_remove(force_remove=True) + if len(self._group.members) >= 2: + async_dispatcher_send( + self.hass, SIGNAL_GROUP_ENTITY_REMOVED, self._group_id + ) async def async_added_to_hass(self) -> None: """Register callbacks.""" @@ -337,13 +341,6 @@ class ZhaGroupEntity(BaseZhaEntity): self.hass, self._entity_ids, self.async_state_changed_listener ) - def send_removed_signal(): - async_dispatcher_send( - self.hass, SIGNAL_GROUP_ENTITY_REMOVED, self._group_id - ) - - self.async_on_remove(send_removed_signal) - @callback def async_state_changed_listener(self, event: Event[EventStateChangedData]) -> None: """Handle child updates.""" From 7ea79148bae36f7b1a92c60333600b4aa4fbfa40 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 13:13:26 -1000 Subject: [PATCH 0683/1691] Ignore logging events in zha websocket tests (#113031) --- tests/components/zha/test_websocket_api.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/components/zha/test_websocket_api.py b/tests/components/zha/test_websocket_api.py index 4bd6540f411..041329123f8 100644 --- a/tests/components/zha/test_websocket_api.py +++ b/tests/components/zha/test_websocket_api.py @@ -704,7 +704,13 @@ async def test_ws_permit_with_qr_code( {ID: 14, TYPE: f"{DOMAIN}/devices/{SERVICE_PERMIT}", **params} ) - msg = await zha_client.receive_json() + msg_type = None + while msg_type != const.TYPE_RESULT: + # There will be logging events coming over the websocket + # as well so we want to ignore those + msg = await zha_client.receive_json() + msg_type = msg["type"] + assert msg["id"] == 14 assert msg["type"] == const.TYPE_RESULT assert msg["success"] @@ -761,7 +767,13 @@ async def test_ws_permit_ha12( {ID: 14, TYPE: f"{DOMAIN}/devices/{SERVICE_PERMIT}", **params} ) - msg = await zha_client.receive_json() + msg_type = None + while msg_type != const.TYPE_RESULT: + # There will be logging events coming over the websocket + # as well so we want to ignore those + msg = await zha_client.receive_json() + msg_type = msg["type"] + assert msg["id"] == 14 assert msg["type"] == const.TYPE_RESULT assert msg["success"] From 23f9aea64d519d2f0934c1859576812757bd60ba Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 10 Mar 2024 20:13:02 -0700 Subject: [PATCH 0684/1691] Bump ical to 7.0.1 and always use home assistant timezone for local todo dtstart (#113034) --- homeassistant/components/google/manifest.json | 2 +- homeassistant/components/local_calendar/manifest.json | 2 +- homeassistant/components/local_todo/manifest.json | 2 +- homeassistant/components/local_todo/todo.py | 10 +++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index 01c20595c55..a08daee8961 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/calendar.google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==7.0.0"] + "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==7.0.1"] } diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 53fd61a2924..25ec9f2ccc6 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/local_calendar", "iot_class": "local_polling", "loggers": ["ical"], - "requirements": ["ical==7.0.0"] + "requirements": ["ical==7.0.1"] } diff --git a/homeassistant/components/local_todo/manifest.json b/homeassistant/components/local_todo/manifest.json index b45eec12e62..81f0f9dc199 100644 --- a/homeassistant/components/local_todo/manifest.json +++ b/homeassistant/components/local_todo/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_todo", "iot_class": "local_polling", - "requirements": ["ical==7.0.0"] + "requirements": ["ical==7.0.1"] } diff --git a/homeassistant/components/local_todo/todo.py b/homeassistant/components/local_todo/todo.py index 292f8237776..5b25abf8e21 100644 --- a/homeassistant/components/local_todo/todo.py +++ b/homeassistant/components/local_todo/todo.py @@ -18,6 +18,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import dt as dt_util from .const import CONF_TODO_LIST_NAME, DOMAIN from .store import LocalTodoListStore @@ -124,6 +125,9 @@ class LocalTodoListEntity(TodoListEntity): self._attr_name = name.capitalize() self._attr_unique_id = unique_id + def _new_todo_store(self) -> TodoStore: + return TodoStore(self._calendar, tzinfo=dt_util.DEFAULT_TIME_ZONE) + async def async_update(self) -> None: """Update entity state based on the local To-do items.""" todo_items = [] @@ -147,20 +151,20 @@ class LocalTodoListEntity(TodoListEntity): async def async_create_todo_item(self, item: TodoItem) -> None: """Add an item to the To-do list.""" todo = _convert_item(item) - TodoStore(self._calendar).add(todo) + self._new_todo_store().add(todo) await self.async_save() await self.async_update_ha_state(force_refresh=True) async def async_update_todo_item(self, item: TodoItem) -> None: """Update an item to the To-do list.""" todo = _convert_item(item) - TodoStore(self._calendar).edit(todo.uid, todo) + self._new_todo_store().edit(todo.uid, todo) await self.async_save() await self.async_update_ha_state(force_refresh=True) async def async_delete_todo_items(self, uids: list[str]) -> None: """Delete an item from the To-do list.""" - store = TodoStore(self._calendar) + store = self._new_todo_store() for uid in uids: store.delete(uid) await self.async_save() diff --git a/requirements_all.txt b/requirements_all.txt index 64282d37743..95e522aeea3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1112,7 +1112,7 @@ ibmiotf==0.3.4 # homeassistant.components.google # homeassistant.components.local_calendar # homeassistant.components.local_todo -ical==7.0.0 +ical==7.0.1 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ff423968b11..6e504161d9e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -902,7 +902,7 @@ ibeacon-ble==1.2.0 # homeassistant.components.google # homeassistant.components.local_calendar # homeassistant.components.local_todo -ical==7.0.0 +ical==7.0.1 # homeassistant.components.ping icmplib==3.0 From 0ea91515cf5245187d6276d50f85f09ab71e19d2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 04:16:05 +0100 Subject: [PATCH 0685/1691] Fix Bang & Olufsen enum naming (#113022) --- .../components/bang_olufsen/const.py | 56 +++++++++--------- .../components/bang_olufsen/media_player.py | 57 ++++++++++--------- .../components/bang_olufsen/websocket.py | 18 +++--- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/bang_olufsen/const.py b/homeassistant/components/bang_olufsen/const.py index 3a6638fe31a..4d53daeb510 100644 --- a/homeassistant/components/bang_olufsen/const.py +++ b/homeassistant/components/bang_olufsen/const.py @@ -10,27 +10,27 @@ from mozart_api.models import Source, SourceArray, SourceTypeEnum from homeassistant.components.media_player import MediaPlayerState, MediaType -class SOURCE_ENUM(StrEnum): +class BangOlufsenSource(StrEnum): """Enum used for associating device source ids with friendly names. May not include all sources.""" - uriStreamer = "Audio Streamer" # noqa: N815 - bluetooth = "Bluetooth" - airPlay = "AirPlay" # noqa: N815 - chromeCast = "Chromecast built-in" # noqa: N815 - spotify = "Spotify Connect" - generator = "Tone Generator" - lineIn = "Line-In" # noqa: N815 - spdif = "Optical" - netRadio = "B&O Radio" # noqa: N815 - local = "Local" - dlna = "DLNA" - qplay = "QPlay" - wpl = "Wireless Powerlink" - pl = "Powerlink" - tv = "TV" - deezer = "Deezer" - beolink = "Networklink" - tidalConnect = "Tidal Connect" # noqa: N815 + URI_STREAMER = "Audio Streamer" + BLUETOOTH = "Bluetooth" + AIR_PLAY = "AirPlay" + CHROMECAST = "Chromecast built-in" + SPOTIFY = "Spotify Connect" + GENERATOR = "Tone Generator" + LINE_IN = "Line-In" + SPDIF = "Optical" + NET_RADIO = "B&O Radio" + LOCAL = "Local" + DLNA = "DLNA" + QPLAY = "QPlay" + WPL = "Wireless Powerlink" + PL = "Powerlink" + TV = "TV" + DEEZER = "Deezer" + BEOLINK = "Networklink" + TIDAL_CONNECT = "Tidal Connect" BANG_OLUFSEN_STATES: dict[str, MediaPlayerState] = { @@ -48,7 +48,7 @@ BANG_OLUFSEN_STATES: dict[str, MediaPlayerState] = { # Media types for play_media -class BANG_OLUFSEN_MEDIA_TYPE(StrEnum): +class BangOlufsenMediaType(StrEnum): """Bang & Olufsen specific media types.""" FAVOURITE = "favourite" @@ -57,7 +57,7 @@ class BANG_OLUFSEN_MEDIA_TYPE(StrEnum): TTS = "provider" -class MODEL_ENUM(StrEnum): +class BangOlufsenModel(StrEnum): """Enum for compatible model names.""" BEOLAB_8 = "BeoLab 8" @@ -72,7 +72,7 @@ class MODEL_ENUM(StrEnum): # Dispatcher events -class WEBSOCKET_NOTIFICATION(StrEnum): +class WebsocketNotification(StrEnum): """Enum for WebSocket notification types.""" PLAYBACK_ERROR: Final[str] = "playback_error" @@ -94,14 +94,14 @@ class WEBSOCKET_NOTIFICATION(StrEnum): DOMAIN: Final[str] = "bang_olufsen" # Default values for configuration. -DEFAULT_MODEL: Final[str] = MODEL_ENUM.BEOSOUND_BALANCE +DEFAULT_MODEL: Final[str] = BangOlufsenModel.BEOSOUND_BALANCE # Configuration. CONF_SERIAL_NUMBER: Final = "serial_number" CONF_BEOLINK_JID: Final = "jid" # Models to choose from in manual configuration. -COMPATIBLE_MODELS: list[str] = [x.value for x in MODEL_ENUM] +COMPATIBLE_MODELS: list[str] = [x.value for x in BangOlufsenModel] # Attribute names for zeroconf discovery. ATTR_TYPE_NUMBER: Final[str] = "tn" @@ -113,10 +113,10 @@ ATTR_FRIENDLY_NAME: Final[str] = "fn" BANG_OLUFSEN_ON: Final[str] = "on" VALID_MEDIA_TYPES: Final[tuple] = ( - BANG_OLUFSEN_MEDIA_TYPE.FAVOURITE, - BANG_OLUFSEN_MEDIA_TYPE.DEEZER, - BANG_OLUFSEN_MEDIA_TYPE.RADIO, - BANG_OLUFSEN_MEDIA_TYPE.TTS, + BangOlufsenMediaType.FAVOURITE, + BangOlufsenMediaType.DEEZER, + BangOlufsenMediaType.RADIO, + BangOlufsenMediaType.TTS, MediaType.MUSIC, MediaType.URL, MediaType.CHANNEL, diff --git a/homeassistant/components/bang_olufsen/media_player.py b/homeassistant/components/bang_olufsen/media_player.py index 4ab2ce236a1..935c057efc8 100644 --- a/homeassistant/components/bang_olufsen/media_player.py +++ b/homeassistant/components/bang_olufsen/media_player.py @@ -51,16 +51,16 @@ from homeassistant.util.dt import utcnow from . import BangOlufsenData from .const import ( - BANG_OLUFSEN_MEDIA_TYPE, BANG_OLUFSEN_STATES, CONF_BEOLINK_JID, CONNECTION_STATUS, DOMAIN, FALLBACK_SOURCES, HIDDEN_SOURCE_IDS, - SOURCE_ENUM, VALID_MEDIA_TYPES, - WEBSOCKET_NOTIFICATION, + BangOlufsenMediaType, + BangOlufsenSource, + WebsocketNotification, ) from .entity import BangOlufsenEntity @@ -145,7 +145,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_ERROR}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_ERROR}", self._update_playback_error, ) ) @@ -153,7 +153,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_METADATA}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_METADATA}", self._update_playback_metadata, ) ) @@ -161,35 +161,35 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_PROGRESS}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_PROGRESS}", self._update_playback_progress, ) ) self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_STATE}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_STATE}", self._update_playback_state, ) ) self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.REMOTE_MENU_CHANGED}", + f"{self._unique_id}_{WebsocketNotification.REMOTE_MENU_CHANGED}", self._update_sources, ) ) self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.SOURCE_CHANGE}", + f"{self._unique_id}_{WebsocketNotification.SOURCE_CHANGE}", self._update_source_change, ) ) self.async_on_remove( async_dispatcher_connect( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.VOLUME}", + f"{self._unique_id}_{WebsocketNotification.VOLUME}", self._update_volume, ) ) @@ -335,7 +335,10 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): self._source_change = data # Check if source is line-in or optical and progress should be updated - if self._source_change.id in (SOURCE_ENUM.lineIn, SOURCE_ENUM.spdif): + if self._source_change.id in ( + BangOlufsenSource.LINE_IN, + BangOlufsenSource.SPDIF, + ): self._playback_progress = PlaybackProgress(progress=0) async def _update_volume(self, data: VolumeState) -> None: @@ -367,7 +370,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): def media_content_type(self) -> str: """Return the current media type.""" # Hard to determine content type - if self.source == SOURCE_ENUM.uriStreamer: + if self.source == BangOlufsenSource.URI_STREAMER: return MediaType.URL return MediaType.MUSIC @@ -425,21 +428,21 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): # Try to fix some of the source_change chromecast weirdness. if hasattr(self._playback_metadata, "title"): # source_change is chromecast but line in is selected. - if self._playback_metadata.title == SOURCE_ENUM.lineIn: - return SOURCE_ENUM.lineIn + if self._playback_metadata.title == BangOlufsenSource.LINE_IN: + return BangOlufsenSource.LINE_IN # source_change is chromecast but bluetooth is selected. - if self._playback_metadata.title == SOURCE_ENUM.bluetooth: - return SOURCE_ENUM.bluetooth + if self._playback_metadata.title == BangOlufsenSource.BLUETOOTH: + return BangOlufsenSource.BLUETOOTH # source_change is line in, bluetooth or optical but stale metadata is sent through the WebSocket, # And the source has not changed. if self._source_change.id in ( - SOURCE_ENUM.bluetooth, - SOURCE_ENUM.lineIn, - SOURCE_ENUM.spdif, + BangOlufsenSource.BLUETOOTH, + BangOlufsenSource.LINE_IN, + BangOlufsenSource.SPDIF, ): - return SOURCE_ENUM.chromeCast + return BangOlufsenSource.CHROMECAST # source_change is chromecast and there is metadata but no artwork. Bluetooth does support metadata but not artwork # So i assume that it is bluetooth and not chromecast @@ -449,9 +452,9 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): ): if ( len(self._playback_metadata.art) == 0 - and self._source_change.name == SOURCE_ENUM.bluetooth + and self._source_change.name == BangOlufsenSource.BLUETOOTH ): - return SOURCE_ENUM.bluetooth + return BangOlufsenSource.BLUETOOTH return self._source_change.name @@ -494,7 +497,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): async def async_media_seek(self, position: float) -> None: """Seek to position in ms.""" - if self.source == SOURCE_ENUM.deezer: + if self.source == BangOlufsenSource.DEEZER: await self._client.seek_to_position(position_ms=int(position * 1000)) # Try to prevent the playback progress from bouncing in the UI. self._attr_media_position_updated_at = utcnow() @@ -568,14 +571,14 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): # The "provider" media_type may not be suitable for overlay all the time. # Use it for now. - elif media_type == BANG_OLUFSEN_MEDIA_TYPE.TTS: + elif media_type == BangOlufsenMediaType.TTS: await self._client.post_overlay_play( overlay_play_request=OverlayPlayRequest( uri=Uri(location=media_id), ) ) - elif media_type == BANG_OLUFSEN_MEDIA_TYPE.RADIO: + elif media_type == BangOlufsenMediaType.RADIO: await self._client.run_provided_scene( scene_properties=SceneProperties( action_list=[ @@ -587,10 +590,10 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity): ) ) - elif media_type == BANG_OLUFSEN_MEDIA_TYPE.FAVOURITE: + elif media_type == BangOlufsenMediaType.FAVOURITE: await self._client.activate_preset(id=int(media_id)) - elif media_type == BANG_OLUFSEN_MEDIA_TYPE.DEEZER: + elif media_type == BangOlufsenMediaType.DEEZER: try: if media_id == "flow": deezer_id = None diff --git a/homeassistant/components/bang_olufsen/websocket.py b/homeassistant/components/bang_olufsen/websocket.py index 81de5596e31..7415d0f362b 100644 --- a/homeassistant/components/bang_olufsen/websocket.py +++ b/homeassistant/components/bang_olufsen/websocket.py @@ -24,7 +24,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( BANG_OLUFSEN_WEBSOCKET_EVENT, CONNECTION_STATUS, - WEBSOCKET_NOTIFICATION, + WebsocketNotification, ) from .entity import BangOlufsenBase from .util import get_device @@ -93,17 +93,17 @@ class BangOlufsenWebsocket(BangOlufsenBase): ) -> None: """Send notification dispatch.""" if notification.value: - if WEBSOCKET_NOTIFICATION.REMOTE_MENU_CHANGED in notification.value: + if WebsocketNotification.REMOTE_MENU_CHANGED in notification.value: async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.REMOTE_MENU_CHANGED}", + f"{self._unique_id}_{WebsocketNotification.REMOTE_MENU_CHANGED}", ) def on_playback_error_notification(self, notification: PlaybackError) -> None: """Send playback_error dispatch.""" async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_ERROR}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_ERROR}", notification, ) @@ -113,7 +113,7 @@ class BangOlufsenWebsocket(BangOlufsenBase): """Send playback_metadata dispatch.""" async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_METADATA}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_METADATA}", notification, ) @@ -121,7 +121,7 @@ class BangOlufsenWebsocket(BangOlufsenBase): """Send playback_progress dispatch.""" async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_PROGRESS}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_PROGRESS}", notification, ) @@ -129,7 +129,7 @@ class BangOlufsenWebsocket(BangOlufsenBase): """Send playback_state dispatch.""" async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.PLAYBACK_STATE}", + f"{self._unique_id}_{WebsocketNotification.PLAYBACK_STATE}", notification, ) @@ -137,7 +137,7 @@ class BangOlufsenWebsocket(BangOlufsenBase): """Send source_change dispatch.""" async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.SOURCE_CHANGE}", + f"{self._unique_id}_{WebsocketNotification.SOURCE_CHANGE}", notification, ) @@ -145,7 +145,7 @@ class BangOlufsenWebsocket(BangOlufsenBase): """Send volume dispatch.""" async_dispatcher_send( self.hass, - f"{self._unique_id}_{WEBSOCKET_NOTIFICATION.VOLUME}", + f"{self._unique_id}_{WebsocketNotification.VOLUME}", notification, ) From 416d21c548f2a720c03f22fc33ffb45a28c70786 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 11 Mar 2024 04:17:12 +0100 Subject: [PATCH 0686/1691] Bump axis to v53 (#113019) --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 9bfe37da9da..65d18347415 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==52"], + "requirements": ["axis==53"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index 95e522aeea3..751d1a617a9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==52 +axis==53 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6e504161d9e..8d615c7808d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -451,7 +451,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==52 +axis==53 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From e96ef4613cc18c88c0083382a7c9ca78cbe214b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 17:18:49 -1000 Subject: [PATCH 0687/1691] Bump aioesphomeapi to 23.1.1 (#113016) changelog: https://github.com/esphome/aioesphomeapi/compare/v23.1.0...v23.1.1 Fixes cryptic error message the user tried to send a command or execute a service call when the device was disconnected --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 693ecbad2fc..5ebc196fe3d 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -15,7 +15,7 @@ "iot_class": "local_push", "loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"], "requirements": [ - "aioesphomeapi==23.1.0", + "aioesphomeapi==23.1.1", "esphome-dashboard-api==1.2.3", "bleak-esphome==1.0.0" ], diff --git a/requirements_all.txt b/requirements_all.txt index 751d1a617a9..ab8bc56a8f4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -242,7 +242,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==23.1.0 +aioesphomeapi==23.1.1 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8d615c7808d..9bb23b50bdb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -221,7 +221,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==23.1.0 +aioesphomeapi==23.1.1 # homeassistant.components.flo aioflo==2021.11.0 From cddce0ce0d84cbe635aeac194f03289365599cc9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 04:20:37 +0100 Subject: [PATCH 0688/1691] Enable more SIM ruff rules (#113015) * SIM101 SIM103 * SIM107 SIM109 * SIM110 * SIM112 SIM113 * SIM115 * SIM116 * Fix * Fix * Fix --- homeassistant/components/fritz/common.py | 5 +---- .../components/homematicip_cloud/climate.py | 6 +----- homeassistant/components/mqtt/event.py | 3 +-- .../components/philips_js/binary_sensor.py | 5 +---- homeassistant/helpers/intent.py | 6 +----- pylint/plugins/hass_enforce_type_hints.py | 5 +---- pyproject.toml | 8 ++++++++ script/hassfest/config_schema.py | 5 +---- tests/components/litterrobot/test_select.py | 13 +++++-------- tests/components/litterrobot/test_switch.py | 6 ++---- tests/components/media_extractor/test_init.py | 13 ++++++------- tests/components/mqtt/test_common.py | 4 +--- tests/components/template/test_fan.py | 14 +++++++------- tests/components/zha/test_cluster_handlers.py | 2 +- tests/components/zha/test_device_trigger.py | 5 +---- tests/components/zha/test_discover.py | 5 +---- tests/helpers/test_check_config.py | 6 ++---- 17 files changed, 41 insertions(+), 70 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index f3468a0a161..39b3c8fbc20 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -64,10 +64,7 @@ _LOGGER = logging.getLogger(__name__) def _is_tracked(mac: str, current_devices: ValuesView) -> bool: """Check if device is already tracked.""" - for tracked in current_devices: - if mac in tracked: - return True - return False + return any(mac in tracked for tracked in current_devices) def device_filter_out_from_trackers( diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index 9c79e1a0c55..c4304284892 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -305,11 +305,7 @@ class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): @property def _has_switch(self) -> bool: """Return, if a switch is in the hmip heating group.""" - for device in self._device.devices: - if isinstance(device, Switch): - return True - - return False + return any(isinstance(device, Switch) for device in self._device.devices) @property def _has_radiator_thermostat(self) -> bool: diff --git a/homeassistant/components/mqtt/event.py b/homeassistant/components/mqtt/event.py index 2a639e55352..c72791f3284 100644 --- a/homeassistant/components/mqtt/event.py +++ b/homeassistant/components/mqtt/event.py @@ -141,8 +141,7 @@ class MqttEvent(MqttEntity, EventEntity): if ( not payload or payload is PayloadSentinel.DEFAULT - or payload == PAYLOAD_NONE - or payload == PAYLOAD_EMPTY_JSON + or payload in (PAYLOAD_NONE, PAYLOAD_EMPTY_JSON) ): _LOGGER.debug( "Ignoring empty payload '%s' after rendering for topic %s", diff --git a/homeassistant/components/philips_js/binary_sensor.py b/homeassistant/components/philips_js/binary_sensor.py index d1004cfd00b..a21d1416192 100644 --- a/homeassistant/components/philips_js/binary_sensor.py +++ b/homeassistant/components/philips_js/binary_sensor.py @@ -64,10 +64,7 @@ def _check_for_recording_entry(api: PhilipsTV, entry: str, value: str) -> bool: """Return True if at least one specified value is available within entry of list.""" if api.recordings_list is None: return False - for rec in api.recordings_list["recordings"]: - if rec.get(entry) == value: - return True - return False + return any(rec.get(entry) == value for rec in api.recordings_list["recordings"]) class PhilipsTVBinarySensorEntityRecordingType(PhilipsJsEntity, BinarySensorEntity): diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 82385f0cda8..71f9fd90b92 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -197,11 +197,7 @@ def _has_name( if (entity is None) or (not entity.aliases): return False - for alias in entity.aliases: - if name == alias.casefold(): - return True - - return False + return any(name == alias.casefold() for alias in entity.aliases) def _find_area( diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 21438ab48ff..678cbb7a22a 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -3050,10 +3050,7 @@ def _get_named_annotation( def _has_valid_annotations( annotations: list[nodes.NodeNG | None], ) -> bool: - for annotation in annotations: - if annotation is not None: - return True - return False + return any(annotation is not None for annotation in annotations) def _get_module_platform(module_name: str) -> str | None: diff --git a/pyproject.toml b/pyproject.toml index 5d2573ab0ab..6d4bfb9e4e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -627,8 +627,16 @@ select = [ "S604", # call-with-shell-equals-true "S608", # hardcoded-sql-expression "S609", # unix-command-wildcard-injection + "SIM101", # Multiple isinstance calls for {name}, merge into a single call + "SIM103", # Return the condition {condition} directly "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass + "SIM107", # Don't use return in try-except and finally + "SIM109", # Use {replacement} instead of multiple equality comparisons + "SIM110", # Use {replacement} instead of for loop + "SIM112", # Use capitalized environment variable {expected} instead of {actual} + "SIM113", # Use enumerate() for index variable {index} in for loop "SIM114", # Combine if branches using logical or operator + "SIM116", # Use a dictionary instead of consecutive if statements "SIM117", # Merge with-statements that use the same scope "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() "SIM201", # Use {left} != {right} instead of not {left} == {right} diff --git a/script/hassfest/config_schema.py b/script/hassfest/config_schema.py index 9f652691887..0bc1cbebd93 100644 --- a/script/hassfest/config_schema.py +++ b/script/hassfest/config_schema.py @@ -33,10 +33,7 @@ def _has_function( module: ast.Module, _type: ast.AsyncFunctionDef | ast.FunctionDef, name: str ) -> bool: """Test if the module defines a function.""" - for item in module.body: - if type(item) == _type and item.name == name: - return True - return False + return any(type(item) == _type and item.name == name for item in module.body) def _has_import(module: ast.Module, name: str) -> bool: diff --git a/tests/components/litterrobot/test_select.py b/tests/components/litterrobot/test_select.py index 76bba67f932..48ec1bb06a5 100644 --- a/tests/components/litterrobot/test_select.py +++ b/tests/components/litterrobot/test_select.py @@ -37,9 +37,7 @@ async def test_wait_time_select( data = {ATTR_ENTITY_ID: SELECT_ENTITY_ID} - count = 0 - for wait_time in LitterRobot3.VALID_WAIT_TIMES: - count += 1 + for count, wait_time in enumerate(LitterRobot3.VALID_WAIT_TIMES): data[ATTR_OPTION] = wait_time await hass.services.async_call( @@ -49,7 +47,7 @@ async def test_wait_time_select( blocking=True, ) - assert mock_account.robots[0].set_wait_time.call_count == count + assert mock_account.robots[0].set_wait_time.call_count == count + 1 async def test_invalid_wait_time_select(hass: HomeAssistant, mock_account) -> None: @@ -91,9 +89,8 @@ async def test_panel_brightness_select( robot: LitterRobot4 = mock_account_with_litterrobot_4.robots[0] robot.set_panel_brightness = AsyncMock(return_value=True) - count = 0 - for option in select.attributes[ATTR_OPTIONS]: - count += 1 + + for count, option in enumerate(select.attributes[ATTR_OPTIONS]): data[ATTR_OPTION] = option await hass.services.async_call( @@ -103,4 +100,4 @@ async def test_panel_brightness_select( blocking=True, ) - assert robot.set_panel_brightness.call_count == count + assert robot.set_panel_brightness.call_count == count + 1 diff --git a/tests/components/litterrobot/test_switch.py b/tests/components/litterrobot/test_switch.py index feaaa1217b8..d81c02bee49 100644 --- a/tests/components/litterrobot/test_switch.py +++ b/tests/components/litterrobot/test_switch.py @@ -58,13 +58,11 @@ async def test_on_off_commands( data = {ATTR_ENTITY_ID: entity_id} - count = 0 services = ((SERVICE_TURN_ON, STATE_ON, "1"), (SERVICE_TURN_OFF, STATE_OFF, "0")) - for service, new_state, new_value in services: - count += 1 + for count, (service, new_state, new_value) in enumerate(services): await hass.services.async_call(PLATFORM_DOMAIN, service, data, blocking=True) robot._update_data({updated_field: new_value}, partial=True) - assert getattr(robot, robot_command).call_count == count + assert getattr(robot, robot_command).call_count == count + 1 assert (state := hass.states.get(entity_id)) assert state.state == new_state diff --git a/tests/components/media_extractor/test_init.py b/tests/components/media_extractor/test_init.py index 4a979b6aec6..35570e3257d 100644 --- a/tests/components/media_extractor/test_init.py +++ b/tests/components/media_extractor/test_init.py @@ -233,14 +233,13 @@ async def test_cookiefile_detection( if not os.path.exists(cookies_dir): os.makedirs(cookies_dir) - f = open(cookies_file, "w+", encoding="utf-8") - f.write( - """# Netscape HTTP Cookie File + with open(cookies_file, "w+", encoding="utf-8") as f: + f.write( + """# Netscape HTTP Cookie File - .youtube.com TRUE / TRUE 1701708706 GPS 1 - """ - ) - f.close() + .youtube.com TRUE / TRUE 1701708706 GPS 1 + """ + ) await hass.services.async_call( DOMAIN, diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index a25a670fa34..8c023848fc4 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1710,10 +1710,8 @@ async def help_test_publishing_with_custom_encoding( # setup test entities using discovery mqtt_mock = await mqtt_mock_entry() - item: int = 0 - for component_config in setup_config: + for item, component_config in enumerate(setup_config): conf = json.dumps(component_config) - item += 1 async_fire_mqtt_message( hass, f"homeassistant/{domain}/component_{item}/config", conf ) diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 11a0afbaf33..1c4e0d20170 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -404,17 +404,17 @@ async def test_invalid_availability_template_keeps_component_available( async def test_on_off(hass: HomeAssistant, calls) -> None: """Test turn on and turn off.""" await _register_components(hass) - expected_calls = 0 - for func, state, action in [ - (common.async_turn_on, STATE_ON, "turn_on"), - (common.async_turn_off, STATE_OFF, "turn_off"), - ]: + for expected_calls, (func, state, action) in enumerate( + [ + (common.async_turn_on, STATE_ON, "turn_on"), + (common.async_turn_off, STATE_OFF, "turn_off"), + ] + ): await func(hass, _TEST_FAN) assert hass.states.get(_STATE_INPUT_BOOLEAN).state == state _verify(hass, state, 0, None, None, None) - expected_calls += 1 - assert len(calls) == expected_calls + assert len(calls) == expected_calls + 1 assert calls[-1].data["action"] == action assert calls[-1].data["caller"] == _TEST_FAN diff --git a/tests/components/zha/test_cluster_handlers.py b/tests/components/zha/test_cluster_handlers.py index 30d892fb436..fd8ca31fe9f 100644 --- a/tests/components/zha/test_cluster_handlers.py +++ b/tests/components/zha/test_cluster_handlers.py @@ -380,7 +380,7 @@ def test_cluster_handler_registry() -> None: assert cluster_id in all_quirk_ids assert isinstance(cluster_handler_classes, dict) for quirk_id, cluster_handler in cluster_handler_classes.items(): - assert isinstance(quirk_id, NoneType) or isinstance(quirk_id, str) + assert isinstance(quirk_id, (NoneType, str)) assert issubclass(cluster_handler, cluster_handlers.ClusterHandler) assert quirk_id in all_quirk_ids[cluster_id] diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 2698866ebaa..f9141795ef1 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -72,10 +72,7 @@ def _same_lists(list_a, list_b): if len(list_a) != len(list_b): return False - for item in list_a: - if item not in list_b: - return False - return True + return all(item in list_b for item in list_a) @pytest.fixture diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index 760f6da5337..cdf8e137690 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -70,10 +70,7 @@ IGNORE_SUFFIXES = [ def contains_ignored_suffix(unique_id: str) -> bool: """Return true if the unique_id ends with an ignored suffix.""" - for suffix in IGNORE_SUFFIXES: - if suffix.lower() in unique_id.lower(): - return True - return False + return any(suffix.lower() in unique_id.lower() for suffix in IGNORE_SUFFIXES) @patch( diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index fdb61505d5e..09ecaac6638 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -43,11 +43,9 @@ BAD_CORE_CONFIG = "homeassistant:\n unit_system: bad\n\n\n" def log_ha_config(conf): """Log the returned config.""" - cnt = 0 _LOGGER.debug("CONFIG - %s lines - %s errors", len(conf), len(conf.errors)) - for key, val in conf.items(): - _LOGGER.debug("#%s - %s: %s", cnt, key, val) - cnt += 1 + for cnt, (key, val) in enumerate(conf.items()): + _LOGGER.debug("#%s - %s: %s", cnt + 1, key, val) for cnt, err in enumerate(conf.errors): _LOGGER.debug("error[%s] = %s", cnt, err) From b8b8e44454394d661ffb2c8f4a29ef2f0e9c3bfd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 17:23:02 -1000 Subject: [PATCH 0689/1691] Migrate duckdns to use async_run_hass_job (#113012) The code would create a hassjob and than run the wrapped function with async_run_job so it had to work out the job type twice --- homeassistant/components/duckdns/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/duckdns/__init__.py b/homeassistant/components/duckdns/__init__.py index 73dd5960631..557178de571 100644 --- a/homeassistant/components/duckdns/__init__.py +++ b/homeassistant/components/duckdns/__init__.py @@ -142,7 +142,7 @@ def async_track_time_interval_backoff( ) interval_listener_job = HassJob(interval_listener, cancel_on_shutdown=True) - hass.async_run_job(interval_listener, dt_util.utcnow()) + hass.async_run_hass_job(interval_listener_job, dt_util.utcnow()) def remove_listener() -> None: """Remove interval listener.""" From b30c7b47f656497676491aa62a79fa737e6e6424 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 17:24:48 -1000 Subject: [PATCH 0690/1691] Avoid scheduling person updates on the event loop (#113010) These update call the storage collection async_update_item which never suspends so they can finish synchronously without having to be scheduled on the loop when run_immediately, which schedules the task eagerly is set --- homeassistant/components/person/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 77e88c8509a..d75531d3052 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -242,6 +242,7 @@ class PersonStorageCollection(collection.DictStorageCollection): er.EVENT_ENTITY_REGISTRY_UPDATED, self._entity_registry_updated, event_filter=self._entity_registry_filter, + run_immediately=True, ) @callback From db44efc1a370abdd56af84209db993fc5948a741 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Mon, 11 Mar 2024 04:25:04 +0100 Subject: [PATCH 0691/1691] Bump bthome-ble to 3.8.0 (#113008) Bump bthome-ble --- homeassistant/components/bthome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index 764dff3770c..5fb90bb5998 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -20,5 +20,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/bthome", "iot_class": "local_push", - "requirements": ["bthome-ble==3.7.0"] + "requirements": ["bthome-ble==3.8.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index ab8bc56a8f4..3ad10542804 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -618,7 +618,7 @@ brunt==1.2.0 bt-proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==3.7.0 +bthome-ble==3.8.0 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9bb23b50bdb..4b2155ec82c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -526,7 +526,7 @@ brottsplatskartan==1.0.5 brunt==1.2.0 # homeassistant.components.bthome -bthome-ble==3.7.0 +bthome-ble==3.8.0 # homeassistant.components.buienradar buienradar==1.0.5 From a78e389d9b7c83ca21b22c579a59ab32f2c2d773 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 04:26:37 +0100 Subject: [PATCH 0692/1691] Move Microsoft face fixture to integration test (#112993) * Move Microsoft face fixture to integration test * Update tests/components/microsoft_face/test_init.py Co-authored-by: Paulus Schoutsen * Fix --------- Co-authored-by: Paulus Schoutsen --- .../fixtures/create_person.json} | 0 .../fixtures/persongroups.json} | 0 .../microsoft_face/fixtures/persons.json} | 0 tests/components/microsoft_face/test_init.py | 20 +++++++------- .../fixtures/detect.json} | 0 .../fixtures/persongroups.json | 12 +++++++++ .../fixtures/persons.json | 21 +++++++++++++++ .../test_image_processing.py | 8 +++--- .../fixtures/detect.json | 27 +++++++++++++++++++ .../fixtures/identify.json} | 0 .../fixtures/persongroups.json | 12 +++++++++ .../fixtures/persons.json | 21 +++++++++++++++ .../test_image_processing.py | 10 +++---- 13 files changed, 112 insertions(+), 19 deletions(-) rename tests/{fixtures/microsoft_face_create_person.json => components/microsoft_face/fixtures/create_person.json} (100%) rename tests/{fixtures/microsoft_face_persongroups.json => components/microsoft_face/fixtures/persongroups.json} (100%) rename tests/{fixtures/microsoft_face_persons.json => components/microsoft_face/fixtures/persons.json} (100%) rename tests/{fixtures/microsoft_face_detect.json => components/microsoft_face_detect/fixtures/detect.json} (100%) create mode 100644 tests/components/microsoft_face_detect/fixtures/persongroups.json create mode 100644 tests/components/microsoft_face_detect/fixtures/persons.json create mode 100644 tests/components/microsoft_face_identify/fixtures/detect.json rename tests/{fixtures/microsoft_face_identify.json => components/microsoft_face_identify/fixtures/identify.json} (100%) create mode 100644 tests/components/microsoft_face_identify/fixtures/persongroups.json create mode 100644 tests/components/microsoft_face_identify/fixtures/persons.json diff --git a/tests/fixtures/microsoft_face_create_person.json b/tests/components/microsoft_face/fixtures/create_person.json similarity index 100% rename from tests/fixtures/microsoft_face_create_person.json rename to tests/components/microsoft_face/fixtures/create_person.json diff --git a/tests/fixtures/microsoft_face_persongroups.json b/tests/components/microsoft_face/fixtures/persongroups.json similarity index 100% rename from tests/fixtures/microsoft_face_persongroups.json rename to tests/components/microsoft_face/fixtures/persongroups.json diff --git a/tests/fixtures/microsoft_face_persons.json b/tests/components/microsoft_face/fixtures/persons.json similarity index 100% rename from tests/fixtures/microsoft_face_persons.json rename to tests/components/microsoft_face/fixtures/persons.json diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index dd99a1d48aa..63014a095c0 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -134,15 +134,15 @@ async def test_setup_component_test_entities( """Set up component.""" aioclient_mock.get( ENDPOINT_URL.format("persongroups"), - text=load_fixture("microsoft_face_persongroups.json"), + text=load_fixture("persongroups.json", "microsoft_face"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group1/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group2/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face"), ) with assert_setup_component(3, mf.DOMAIN): @@ -202,15 +202,15 @@ async def test_service_person( """Set up component, test person services.""" aioclient_mock.get( ENDPOINT_URL.format("persongroups"), - text=load_fixture("microsoft_face_persongroups.json"), + text=load_fixture("persongroups.json", "microsoft_face"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group1/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group2/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face"), ) with assert_setup_component(3, mf.DOMAIN): @@ -220,7 +220,7 @@ async def test_service_person( aioclient_mock.post( ENDPOINT_URL.format("persongroups/test_group1/persons"), - text=load_fixture("microsoft_face_create_person.json"), + text=load_fixture("create_person.json", "microsoft_face"), ) aioclient_mock.delete( ENDPOINT_URL.format( @@ -274,15 +274,15 @@ async def test_service_face( """Set up component, test person face services.""" aioclient_mock.get( ENDPOINT_URL.format("persongroups"), - text=load_fixture("microsoft_face_persongroups.json"), + text=load_fixture("persongroups.json", "microsoft_face"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group1/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group2/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face"), ) CONFIG["camera"] = {"platform": "demo"} diff --git a/tests/fixtures/microsoft_face_detect.json b/tests/components/microsoft_face_detect/fixtures/detect.json similarity index 100% rename from tests/fixtures/microsoft_face_detect.json rename to tests/components/microsoft_face_detect/fixtures/detect.json diff --git a/tests/components/microsoft_face_detect/fixtures/persongroups.json b/tests/components/microsoft_face_detect/fixtures/persongroups.json new file mode 100644 index 00000000000..5bbb8ceea29 --- /dev/null +++ b/tests/components/microsoft_face_detect/fixtures/persongroups.json @@ -0,0 +1,12 @@ +[ + { + "personGroupId": "test_group1", + "name": "test group1", + "userData": "test" + }, + { + "personGroupId": "test_group2", + "name": "test group2", + "userData": "test" + } +] diff --git a/tests/components/microsoft_face_detect/fixtures/persons.json b/tests/components/microsoft_face_detect/fixtures/persons.json new file mode 100644 index 00000000000..e604bcc2672 --- /dev/null +++ b/tests/components/microsoft_face_detect/fixtures/persons.json @@ -0,0 +1,21 @@ +[ + { + "personId": "25985303-c537-4467-b41d-bdb45cd95ca1", + "name": "Ryan", + "userData": "User-provided data attached to the person", + "persistedFaceIds": [ + "015839fb-fbd9-4f79-ace9-7675fc2f1dd9", + "fce92aed-d578-4d2e-8114-068f8af4492e", + "b64d5e15-8257-4af2-b20a-5a750f8940e7" + ] + }, + { + "personId": "2ae4935b-9659-44c3-977f-61fac20d0538", + "name": "David", + "userData": "User-provided data attached to the person", + "persistedFaceIds": [ + "30ea1073-cc9e-4652-b1e3-d08fb7b95315", + "fbd2a038-dbff-452c-8e79-2ee81b1aa84e" + ] + } +] diff --git a/tests/components/microsoft_face_detect/test_image_processing.py b/tests/components/microsoft_face_detect/test_image_processing.py index b1aa2a00c85..0c0bcb59c0b 100644 --- a/tests/components/microsoft_face_detect/test_image_processing.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -97,15 +97,15 @@ async def test_ms_detect_process_image( """Set up and scan a picture and test plates from event.""" aioclient_mock.get( ENDPOINT_URL.format("persongroups"), - text=load_fixture("microsoft_face_persongroups.json"), + text=load_fixture("persongroups.json", "microsoft_face_detect"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group1/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face_detect"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group2/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face_detect"), ) await async_setup_component(hass, ip.DOMAIN, CONFIG) @@ -127,7 +127,7 @@ async def test_ms_detect_process_image( aioclient_mock.post( ENDPOINT_URL.format("detect"), - text=load_fixture("microsoft_face_detect.json"), + text=load_fixture("detect.json", "microsoft_face_detect"), params={"returnFaceAttributes": "age,gender"}, ) diff --git a/tests/components/microsoft_face_identify/fixtures/detect.json b/tests/components/microsoft_face_identify/fixtures/detect.json new file mode 100644 index 00000000000..c43f4ec494e --- /dev/null +++ b/tests/components/microsoft_face_identify/fixtures/detect.json @@ -0,0 +1,27 @@ +[ + { + "faceId": "c5c24a82-6845-4031-9d5d-978df9175426", + "faceRectangle": { + "width": 78, + "height": 78, + "left": 394, + "top": 54 + }, + "faceAttributes": { + "age": 71.0, + "gender": "male", + "smile": 0.88, + "facialHair": { + "mustache": 0.8, + "beard": 0.1, + "sideburns": 0.02 + }, + "glasses": "sunglasses", + "headPose": { + "roll": 2.1, + "yaw": 3, + "pitch": 0 + } + } + } +] diff --git a/tests/fixtures/microsoft_face_identify.json b/tests/components/microsoft_face_identify/fixtures/identify.json similarity index 100% rename from tests/fixtures/microsoft_face_identify.json rename to tests/components/microsoft_face_identify/fixtures/identify.json diff --git a/tests/components/microsoft_face_identify/fixtures/persongroups.json b/tests/components/microsoft_face_identify/fixtures/persongroups.json new file mode 100644 index 00000000000..5bbb8ceea29 --- /dev/null +++ b/tests/components/microsoft_face_identify/fixtures/persongroups.json @@ -0,0 +1,12 @@ +[ + { + "personGroupId": "test_group1", + "name": "test group1", + "userData": "test" + }, + { + "personGroupId": "test_group2", + "name": "test group2", + "userData": "test" + } +] diff --git a/tests/components/microsoft_face_identify/fixtures/persons.json b/tests/components/microsoft_face_identify/fixtures/persons.json new file mode 100644 index 00000000000..e604bcc2672 --- /dev/null +++ b/tests/components/microsoft_face_identify/fixtures/persons.json @@ -0,0 +1,21 @@ +[ + { + "personId": "25985303-c537-4467-b41d-bdb45cd95ca1", + "name": "Ryan", + "userData": "User-provided data attached to the person", + "persistedFaceIds": [ + "015839fb-fbd9-4f79-ace9-7675fc2f1dd9", + "fce92aed-d578-4d2e-8114-068f8af4492e", + "b64d5e15-8257-4af2-b20a-5a750f8940e7" + ] + }, + { + "personId": "2ae4935b-9659-44c3-977f-61fac20d0538", + "name": "David", + "userData": "User-provided data attached to the person", + "persistedFaceIds": [ + "30ea1073-cc9e-4652-b1e3-d08fb7b95315", + "fbd2a038-dbff-452c-8e79-2ee81b1aa84e" + ] + } +] diff --git a/tests/components/microsoft_face_identify/test_image_processing.py b/tests/components/microsoft_face_identify/test_image_processing.py index 59a93bd9db5..6258448dd05 100644 --- a/tests/components/microsoft_face_identify/test_image_processing.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -99,15 +99,15 @@ async def test_ms_identify_process_image( """Set up and scan a picture and test plates from event.""" aioclient_mock.get( ENDPOINT_URL.format("persongroups"), - text=load_fixture("microsoft_face_persongroups.json"), + text=load_fixture("persongroups.json", "microsoft_face_identify"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group1/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face_identify"), ) aioclient_mock.get( ENDPOINT_URL.format("persongroups/test_group2/persons"), - text=load_fixture("microsoft_face_persons.json"), + text=load_fixture("persons.json", "microsoft_face_identify"), ) await async_setup_component(hass, ip.DOMAIN, CONFIG) @@ -129,11 +129,11 @@ async def test_ms_identify_process_image( aioclient_mock.post( ENDPOINT_URL.format("detect"), - text=load_fixture("microsoft_face_detect.json"), + text=load_fixture("detect.json", "microsoft_face_identify"), ) aioclient_mock.post( ENDPOINT_URL.format("identify"), - text=load_fixture("microsoft_face_identify.json"), + text=load_fixture("identify.json", "microsoft_face_identify"), ) common.async_scan(hass, entity_id="image_processing.test_local") From 812bd4af653916521594c7b3f94375d72222b8d9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 04:27:00 +0100 Subject: [PATCH 0693/1691] Move metoffice fixture to integration test (#112992) * Move metoffice fixture to integration test * Fix * Update tests/components/metoffice/test_config_flow.py Co-authored-by: Paulus Schoutsen * Fix --------- Co-authored-by: Paulus Schoutsen --- tests/{ => components/metoffice}/fixtures/metoffice.json | 0 tests/components/metoffice/test_config_flow.py | 4 ++-- tests/components/metoffice/test_sensor.py | 4 ++-- tests/components/metoffice/test_weather.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename tests/{ => components/metoffice}/fixtures/metoffice.json (100%) diff --git a/tests/fixtures/metoffice.json b/tests/components/metoffice/fixtures/metoffice.json similarity index 100% rename from tests/fixtures/metoffice.json rename to tests/components/metoffice/fixtures/metoffice.json diff --git a/tests/components/metoffice/test_config_flow.py b/tests/components/metoffice/test_config_flow.py index d5749dad035..e4424b0b394 100644 --- a/tests/components/metoffice/test_config_flow.py +++ b/tests/components/metoffice/test_config_flow.py @@ -26,7 +26,7 @@ async def test_form(hass: HomeAssistant, requests_mock: requests_mock.Mocker) -> hass.config.longitude = TEST_LONGITUDE_WAVERTREE # all metoffice test data encapsulated in here - mock_json = json.loads(load_fixture("metoffice.json")) + mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) all_sites = json.dumps(mock_json["all_sites"]) requests_mock.get("/public/data/val/wxfcs/all/json/sitelist/", text=all_sites) @@ -64,7 +64,7 @@ async def test_form_already_configured( hass.config.longitude = TEST_LONGITUDE_WAVERTREE # all metoffice test data encapsulated in here - mock_json = json.loads(load_fixture("metoffice.json")) + mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) all_sites = json.dumps(mock_json["all_sites"]) diff --git a/tests/components/metoffice/test_sensor.py b/tests/components/metoffice/test_sensor.py index 87d3f66aeb5..6bddd1d2596 100644 --- a/tests/components/metoffice/test_sensor.py +++ b/tests/components/metoffice/test_sensor.py @@ -31,7 +31,7 @@ async def test_one_sensor_site_running( ) -> None: """Test the Met Office sensor platform.""" # all metoffice test data encapsulated in here - mock_json = json.loads(load_fixture("metoffice.json")) + mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) all_sites = json.dumps(mock_json["all_sites"]) wavertree_hourly = json.dumps(mock_json["wavertree_hourly"]) wavertree_daily = json.dumps(mock_json["wavertree_daily"]) @@ -80,7 +80,7 @@ async def test_two_sensor_sites_running( """Test we handle two sets of sensors running for two different sites.""" # all metoffice test data encapsulated in here - mock_json = json.loads(load_fixture("metoffice.json")) + mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) all_sites = json.dumps(mock_json["all_sites"]) wavertree_hourly = json.dumps(mock_json["wavertree_hourly"]) wavertree_daily = json.dumps(mock_json["wavertree_daily"]) diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index 85d66bf7686..b0c88063de3 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -48,7 +48,7 @@ def no_sensor(): async def wavertree_data(requests_mock: requests_mock.Mocker) -> dict[str, _Matcher]: """Mock data for the Wavertree location.""" # all metoffice test data encapsulated in here - mock_json = json.loads(load_fixture("metoffice.json")) + mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) all_sites = json.dumps(mock_json["all_sites"]) wavertree_hourly = json.dumps(mock_json["wavertree_hourly"]) wavertree_daily = json.dumps(mock_json["wavertree_daily"]) @@ -247,7 +247,7 @@ async def test_two_weather_sites_running( ) # all metoffice test data encapsulated in here - mock_json = json.loads(load_fixture("metoffice.json")) + mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) kingslynn_hourly = json.dumps(mock_json["kingslynn_hourly"]) kingslynn_daily = json.dumps(mock_json["kingslynn_daily"]) From d528378f5d92a6971bcbc714be9dd14ca0c9a042 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 04:27:20 +0100 Subject: [PATCH 0694/1691] Move london_air fixture to integration test (#112991) * Move london_air fixture to integration test * Fix --- tests/{ => components/london_air}/fixtures/london_air.json | 0 tests/components/london_air/test_sensor.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) rename tests/{ => components/london_air}/fixtures/london_air.json (100%) diff --git a/tests/fixtures/london_air.json b/tests/components/london_air/fixtures/london_air.json similarity index 100% rename from tests/fixtures/london_air.json rename to tests/components/london_air/fixtures/london_air.json diff --git a/tests/components/london_air/test_sensor.py b/tests/components/london_air/test_sensor.py index 0434cba8a37..d87d9257704 100644 --- a/tests/components/london_air/test_sensor.py +++ b/tests/components/london_air/test_sensor.py @@ -18,7 +18,9 @@ async def test_valid_state( ) -> None: """Test for operational london_air sensor with proper attributes.""" requests_mock.get( - URL, text=load_fixture("london_air.json"), status_code=HTTPStatus.OK + URL, + text=load_fixture("london_air.json", "london_air"), + status_code=HTTPStatus.OK, ) assert await async_setup_component(hass, "sensor", VALID_CONFIG) await hass.async_block_till_done() From cede16fc4056e19f2dc1752dfa7e3cb66c8c85eb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 04:27:38 +0100 Subject: [PATCH 0695/1691] Move Feedreader fixture to integration test (#112989) --- tests/{ => components/feedreader}/fixtures/feedreader.xml | 0 tests/{ => components/feedreader}/fixtures/feedreader1.xml | 0 tests/{ => components/feedreader}/fixtures/feedreader2.xml | 0 tests/{ => components/feedreader}/fixtures/feedreader3.xml | 0 tests/{ => components/feedreader}/fixtures/feedreader4.xml | 0 tests/{ => components/feedreader}/fixtures/feedreader5.xml | 0 tests/{ => components/feedreader}/fixtures/feedreader6.xml | 0 tests/components/feedreader/test_init.py | 3 ++- 8 files changed, 2 insertions(+), 1 deletion(-) rename tests/{ => components/feedreader}/fixtures/feedreader.xml (100%) rename tests/{ => components/feedreader}/fixtures/feedreader1.xml (100%) rename tests/{ => components/feedreader}/fixtures/feedreader2.xml (100%) rename tests/{ => components/feedreader}/fixtures/feedreader3.xml (100%) rename tests/{ => components/feedreader}/fixtures/feedreader4.xml (100%) rename tests/{ => components/feedreader}/fixtures/feedreader5.xml (100%) rename tests/{ => components/feedreader}/fixtures/feedreader6.xml (100%) diff --git a/tests/fixtures/feedreader.xml b/tests/components/feedreader/fixtures/feedreader.xml similarity index 100% rename from tests/fixtures/feedreader.xml rename to tests/components/feedreader/fixtures/feedreader.xml diff --git a/tests/fixtures/feedreader1.xml b/tests/components/feedreader/fixtures/feedreader1.xml similarity index 100% rename from tests/fixtures/feedreader1.xml rename to tests/components/feedreader/fixtures/feedreader1.xml diff --git a/tests/fixtures/feedreader2.xml b/tests/components/feedreader/fixtures/feedreader2.xml similarity index 100% rename from tests/fixtures/feedreader2.xml rename to tests/components/feedreader/fixtures/feedreader2.xml diff --git a/tests/fixtures/feedreader3.xml b/tests/components/feedreader/fixtures/feedreader3.xml similarity index 100% rename from tests/fixtures/feedreader3.xml rename to tests/components/feedreader/fixtures/feedreader3.xml diff --git a/tests/fixtures/feedreader4.xml b/tests/components/feedreader/fixtures/feedreader4.xml similarity index 100% rename from tests/fixtures/feedreader4.xml rename to tests/components/feedreader/fixtures/feedreader4.xml diff --git a/tests/fixtures/feedreader5.xml b/tests/components/feedreader/fixtures/feedreader5.xml similarity index 100% rename from tests/fixtures/feedreader5.xml rename to tests/components/feedreader/fixtures/feedreader5.xml diff --git a/tests/fixtures/feedreader6.xml b/tests/components/feedreader/fixtures/feedreader6.xml similarity index 100% rename from tests/fixtures/feedreader6.xml rename to tests/components/feedreader/fixtures/feedreader6.xml diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index b0f2f9e4e72..898e46a7c84 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -15,6 +15,7 @@ from homeassistant.components.feedreader import ( CONF_MAX_ENTRIES, CONF_URLS, DEFAULT_SCAN_INTERVAL, + DOMAIN, EVENT_FEEDREADER, ) from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START @@ -34,7 +35,7 @@ VALID_CONFIG_5 = {feedreader.DOMAIN: {CONF_URLS: [URL], CONF_MAX_ENTRIES: 1}} def load_fixture_bytes(src: str) -> bytes: """Return byte stream of fixture.""" - feed_data = load_fixture(src) + feed_data = load_fixture(src, DOMAIN) raw = bytes(feed_data, "utf-8") return raw From 3387892f591303d27792c563f0f47cc49e1a7bf7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 21:19:49 -1000 Subject: [PATCH 0696/1691] Schedule tasks eagerly when called from hass.add_job (#113014) --- homeassistant/core.py | 28 +++++++++++++++++++++------- tests/common.py | 4 ++-- tests/test_core.py | 34 ++++++++++++++++++---------------- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 8c6a7f230de..056774a2adc 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -524,30 +524,43 @@ class HomeAssistant: if target is None: raise ValueError("Don't call add_job with None") if asyncio.iscoroutine(target): - self.loop.call_soon_threadsafe(self.async_add_job, target) + self.loop.call_soon_threadsafe( + functools.partial(self.async_add_job, target, eager_start=True) + ) return if TYPE_CHECKING: target = cast(Callable[..., Any], target) - self.loop.call_soon_threadsafe(self.async_add_job, target, *args) + self.loop.call_soon_threadsafe( + functools.partial(self.async_add_job, target, *args, eager_start=True) + ) @overload @callback def async_add_job( - self, target: Callable[..., Coroutine[Any, Any, _R]], *args: Any + self, + target: Callable[..., Coroutine[Any, Any, _R]], + *args: Any, + eager_start: bool = False, ) -> asyncio.Future[_R] | None: ... @overload @callback def async_add_job( - self, target: Callable[..., Coroutine[Any, Any, _R] | _R], *args: Any + self, + target: Callable[..., Coroutine[Any, Any, _R] | _R], + *args: Any, + eager_start: bool = False, ) -> asyncio.Future[_R] | None: ... @overload @callback def async_add_job( - self, target: Coroutine[Any, Any, _R], *args: Any + self, + target: Coroutine[Any, Any, _R], + *args: Any, + eager_start: bool = False, ) -> asyncio.Future[_R] | None: ... @@ -556,6 +569,7 @@ class HomeAssistant: self, target: Callable[..., Coroutine[Any, Any, _R] | _R] | Coroutine[Any, Any, _R], *args: Any, + eager_start: bool = False, ) -> asyncio.Future[_R] | None: """Add a job to be executed by the event loop or by an executor. @@ -571,7 +585,7 @@ class HomeAssistant: raise ValueError("Don't call async_add_job with None") if asyncio.iscoroutine(target): - return self.async_create_task(target) + return self.async_create_task(target, eager_start=eager_start) # This code path is performance sensitive and uses # if TYPE_CHECKING to avoid the overhead of constructing @@ -579,7 +593,7 @@ class HomeAssistant: # https://github.com/home-assistant/core/pull/71960 if TYPE_CHECKING: target = cast(Callable[..., Coroutine[Any, Any, _R] | _R], target) - return self.async_add_hass_job(HassJob(target), *args) + return self.async_add_hass_job(HassJob(target), *args, eager_start=eager_start) @overload @callback diff --git a/tests/common.py b/tests/common.py index 0766da158d1..cbc295bf43a 100644 --- a/tests/common.py +++ b/tests/common.py @@ -234,7 +234,7 @@ async def async_test_home_assistant( orig_async_create_task = hass.async_create_task orig_tz = dt_util.DEFAULT_TIME_ZONE - def async_add_job(target, *args): + def async_add_job(target, *args, eager_start: bool = False): """Add job.""" check_target = target while isinstance(check_target, ft.partial): @@ -245,7 +245,7 @@ async def async_test_home_assistant( fut.set_result(target(*args)) return fut - return orig_async_add_job(target, *args) + return orig_async_add_job(target, *args, eager_start=eager_start) def async_add_executor_job(target, *args): """Add executor job.""" diff --git a/tests/test_core.py b/tests/test_core.py index 18e0d352710..1d6f2830925 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -736,6 +736,20 @@ async def test_pending_scheduler(hass: HomeAssistant) -> None: assert len(call_count) == 3 +def test_add_job_pending_tasks_coro(hass: HomeAssistant) -> None: + """Add a coro to pending tasks.""" + + async def test_coro(): + """Test Coro.""" + pass + + for _ in range(2): + hass.add_job(test_coro()) + + # Ensure add_job does not run immediately + assert len(hass._tasks) == 0 + + async def test_async_add_job_pending_tasks_coro(hass: HomeAssistant) -> None: """Add a coro to pending tasks.""" call_count = [] @@ -745,18 +759,12 @@ async def test_async_add_job_pending_tasks_coro(hass: HomeAssistant) -> None: call_count.append("call") for _ in range(2): - hass.add_job(test_coro()) - - async def wait_finish_callback(): - """Wait until all stuff is scheduled.""" - await asyncio.sleep(0) - await asyncio.sleep(0) - - await wait_finish_callback() + hass.async_add_job(test_coro()) assert len(hass._tasks) == 2 await hass.async_block_till_done() assert len(call_count) == 2 + assert len(hass._tasks) == 0 async def test_async_create_task_pending_tasks_coro(hass: HomeAssistant) -> None: @@ -768,18 +776,12 @@ async def test_async_create_task_pending_tasks_coro(hass: HomeAssistant) -> None call_count.append("call") for _ in range(2): - hass.create_task(test_coro()) - - async def wait_finish_callback(): - """Wait until all stuff is scheduled.""" - await asyncio.sleep(0) - await asyncio.sleep(0) - - await wait_finish_callback() + hass.async_create_task(test_coro()) assert len(hass._tasks) == 2 await hass.async_block_till_done() assert len(call_count) == 2 + assert len(hass._tasks) == 0 async def test_async_add_job_pending_tasks_executor(hass: HomeAssistant) -> None: From b914ac3185e8bf2884de2a06acd0d10c6f9da445 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 08:30:22 +0100 Subject: [PATCH 0697/1691] Move openhardwaremonitor fixture to integration tests (#112982) * Move openhardwaremonitor fixture * Update tests/components/openhardwaremonitor/test_sensor.py --------- Co-authored-by: Paulus Schoutsen --- .../openhardwaremonitor}/fixtures/openhardwaremonitor.json | 0 tests/components/openhardwaremonitor/test_sensor.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/{ => components/openhardwaremonitor}/fixtures/openhardwaremonitor.json (100%) diff --git a/tests/fixtures/openhardwaremonitor.json b/tests/components/openhardwaremonitor/fixtures/openhardwaremonitor.json similarity index 100% rename from tests/fixtures/openhardwaremonitor.json rename to tests/components/openhardwaremonitor/fixtures/openhardwaremonitor.json diff --git a/tests/components/openhardwaremonitor/test_sensor.py b/tests/components/openhardwaremonitor/test_sensor.py index 9bf339898a0..944b5487a96 100644 --- a/tests/components/openhardwaremonitor/test_sensor.py +++ b/tests/components/openhardwaremonitor/test_sensor.py @@ -20,7 +20,7 @@ async def test_setup(hass: HomeAssistant, requests_mock: requests_mock.Mocker) - requests_mock.get( "http://localhost:8085/data.json", - text=load_fixture("openhardwaremonitor.json"), + text=load_fixture("openhardwaremonitor.json", "openhardwaremonitor"), ) await async_setup_component(hass, "sensor", config) From 4b84954d17ff6da1a0567bebb4d0e3727de76814 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 08:32:43 +0100 Subject: [PATCH 0698/1691] Remove entity description mixin in V2C (#112960) --- homeassistant/components/v2c/binary_sensor.py | 13 +++---------- homeassistant/components/v2c/number.py | 13 +++---------- homeassistant/components/v2c/sensor.py | 11 +++-------- homeassistant/components/v2c/switch.py | 11 +++-------- 4 files changed, 12 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/v2c/binary_sensor.py b/homeassistant/components/v2c/binary_sensor.py index e0d5306d205..203cc9f3396 100644 --- a/homeassistant/components/v2c/binary_sensor.py +++ b/homeassistant/components/v2c/binary_sensor.py @@ -21,20 +21,13 @@ from .coordinator import V2CUpdateCoordinator from .entity import V2CBaseEntity -@dataclass(frozen=True) -class V2CRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class V2CBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes an EVSE binary sensor entity.""" value_fn: Callable[[Trydan], bool] -@dataclass(frozen=True) -class V2CBinarySensorEntityDescription( - BinarySensorEntityDescription, V2CRequiredKeysMixin -): - """Describes an EVSE binary sensor entity.""" - - TRYDAN_SENSORS = ( V2CBinarySensorEntityDescription( key="connected", diff --git a/homeassistant/components/v2c/number.py b/homeassistant/components/v2c/number.py index e87be9f716a..376509c4780 100644 --- a/homeassistant/components/v2c/number.py +++ b/homeassistant/components/v2c/number.py @@ -25,21 +25,14 @@ MIN_INTENSITY = 6 MAX_INTENSITY = 32 -@dataclass(frozen=True) -class V2CSettingsRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class V2CSettingsNumberEntityDescription(NumberEntityDescription): + """Describes V2C EVSE number entity.""" value_fn: Callable[[TrydanData], int] update_fn: Callable[[Trydan, int], Coroutine[Any, Any, None]] -@dataclass(frozen=True) -class V2CSettingsNumberEntityDescription( - NumberEntityDescription, V2CSettingsRequiredKeysMixin -): - """Describes V2C EVSE number entity.""" - - TRYDAN_NUMBER_SETTINGS = ( V2CSettingsNumberEntityDescription( key="intensity", diff --git a/homeassistant/components/v2c/sensor.py b/homeassistant/components/v2c/sensor.py index b8d3919b1cb..871dd65aa75 100644 --- a/homeassistant/components/v2c/sensor.py +++ b/homeassistant/components/v2c/sensor.py @@ -26,18 +26,13 @@ from .entity import V2CBaseEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class V2CRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class V2CSensorEntityDescription(SensorEntityDescription): + """Describes an EVSE Power sensor entity.""" value_fn: Callable[[TrydanData], float] -@dataclass(frozen=True) -class V2CSensorEntityDescription(SensorEntityDescription, V2CRequiredKeysMixin): - """Describes an EVSE Power sensor entity.""" - - TRYDAN_SENSORS = ( V2CSensorEntityDescription( key="charge_power", diff --git a/homeassistant/components/v2c/switch.py b/homeassistant/components/v2c/switch.py index 1ff86b1677b..0974a712153 100644 --- a/homeassistant/components/v2c/switch.py +++ b/homeassistant/components/v2c/switch.py @@ -28,20 +28,15 @@ from .entity import V2CBaseEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class V2CRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class V2CSwitchEntityDescription(SwitchEntityDescription): + """Describes a V2C EVSE switch entity.""" value_fn: Callable[[TrydanData], bool] turn_on_fn: Callable[[Trydan], Coroutine[Any, Any, Any]] turn_off_fn: Callable[[Trydan], Coroutine[Any, Any, Any]] -@dataclass(frozen=True) -class V2CSwitchEntityDescription(SwitchEntityDescription, V2CRequiredKeysMixin): - """Describes a V2C EVSE switch entity.""" - - TRYDAN_SWITCHES = ( V2CSwitchEntityDescription( key="paused", From 405bf076b24524458384840658ae442eaf5c95b6 Mon Sep 17 00:00:00 2001 From: Yuxin Wang Date: Mon, 11 Mar 2024 03:38:44 -0400 Subject: [PATCH 0699/1691] Remove a redundant check in APCUPSD's config flow (#113032) Remove a redundant check that is impossible to happen in practice --- homeassistant/components/apcupsd/config_flow.py | 3 --- homeassistant/components/apcupsd/strings.json | 3 +-- tests/components/apcupsd/test_config_flow.py | 11 ----------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/homeassistant/components/apcupsd/config_flow.py b/homeassistant/components/apcupsd/config_flow.py index 8d15b0478dd..00f757a1fd7 100644 --- a/homeassistant/components/apcupsd/config_flow.py +++ b/homeassistant/components/apcupsd/config_flow.py @@ -61,9 +61,6 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=_SCHEMA, errors=errors ) - if not data: - return self.async_abort(reason="no_status") - # We _try_ to use the serial number of the UPS as the unique id since this field # is not guaranteed to exist on all APC UPS models. await self.async_set_unique_id(data.serial_no) diff --git a/homeassistant/components/apcupsd/strings.json b/homeassistant/components/apcupsd/strings.json index 15ffb928f00..93102ac1393 100644 --- a/homeassistant/components/apcupsd/strings.json +++ b/homeassistant/components/apcupsd/strings.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "no_status": "No status is reported from host" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" diff --git a/tests/components/apcupsd/test_config_flow.py b/tests/components/apcupsd/test_config_flow.py index 3b9b1ee6e2e..bed0e78ad55 100644 --- a/tests/components/apcupsd/test_config_flow.py +++ b/tests/components/apcupsd/test_config_flow.py @@ -37,17 +37,6 @@ async def test_config_flow_cannot_connect(hass: HomeAssistant) -> None: assert result["errors"]["base"] == "cannot_connect" -async def test_config_flow_no_status(hass: HomeAssistant) -> None: - """Test config flow setup with successful connection but no status is reported.""" - with patch("aioapcaccess.request_status", return_value={}): # Returns no status. - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == FlowResultType.ABORT - assert result["reason"] == "no_status" - - async def test_config_flow_duplicate(hass: HomeAssistant) -> None: """Test duplicate config flow setup.""" # First add an exiting config entry to hass. From 17aa49410b3d8c5db6b5189d5988557e8648138e Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 11 Mar 2024 09:43:47 +0100 Subject: [PATCH 0700/1691] Rename mqtt platform setup method (#113042) * Rename mqtt platform setup method * Format method to one line --- homeassistant/components/mqtt/discovery.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 180f3524dee..218413d79f5 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -144,10 +144,8 @@ async def async_start( # noqa: C901 mqtt_data = get_mqtt_data(hass) platform_setup_lock: dict[str, asyncio.Lock] = {} - async def _async_jit_component_setup( - discovery_payload: MQTTDiscoveryPayload, - ) -> None: - """Perform just in time components set up.""" + async def _async_component_setup(discovery_payload: MQTTDiscoveryPayload) -> None: + """Perform component set up.""" discovery_hash = discovery_payload.discovery_data[ATTR_DISCOVERY_HASH] component, discovery_id = discovery_hash platform_setup_lock.setdefault(component, asyncio.Lock()) @@ -166,7 +164,7 @@ async def async_start( # noqa: C901 mqtt_data.reload_dispatchers.append( async_dispatcher_connect( - hass, MQTT_DISCOVERY_NEW_COMPONENT, _async_jit_component_setup + hass, MQTT_DISCOVERY_NEW_COMPONENT, _async_component_setup ) ) From fd8ee6c85760961d67fa7ee6b39886fe6c17babd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 22:44:21 -1000 Subject: [PATCH 0701/1691] Fix google assistant cloud sync test (#113044) --- tests/components/cloud/test_google_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index dfadf80a601..f10a3cdbac5 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -341,12 +341,13 @@ async def test_sync_google_on_home_assistant_start( config = CloudGoogleConfig( hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] ) - hass.set_state(CoreState.starting) + hass.set_state(CoreState.not_running) with patch.object(config, "async_sync_entities_all") as mock_sync: await config.async_initialize() + await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 0 - hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 1 From 30c3174498bc05f271e241813c33d41cdce83579 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 10 Mar 2024 22:50:09 -1000 Subject: [PATCH 0702/1691] Fix CoreState in cloud google assistant tests (#113045) --- tests/components/cloud/test_google_config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index f10a3cdbac5..bde2d85de46 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -510,7 +510,7 @@ async def test_google_config_migrate_expose_entity_prefs( google_settings_version: int, ) -> None: """Test migrating Google entity config.""" - hass.set_state(CoreState.starting) + hass.set_state(CoreState.not_running) assert await async_setup_component(hass, "homeassistant", {}) hass.states.async_set("light.state_only", "on") @@ -623,7 +623,7 @@ async def test_google_config_migrate_expose_entity_prefs_v2_no_exposed( entity_registry: er.EntityRegistry, ) -> None: """Test migrating Google entity config from v2 to v3 when no entity is exposed.""" - hass.set_state(CoreState.starting) + hass.set_state(CoreState.not_running) assert await async_setup_component(hass, "homeassistant", {}) hass.states.async_set("light.state_only", "on") @@ -670,7 +670,7 @@ async def test_google_config_migrate_expose_entity_prefs_v2_exposed( entity_registry: er.EntityRegistry, ) -> None: """Test migrating Google entity config from v2 to v3 when an entity is exposed.""" - hass.set_state(CoreState.starting) + hass.set_state(CoreState.not_running) assert await async_setup_component(hass, "homeassistant", {}) hass.states.async_set("light.state_only", "on") @@ -717,7 +717,7 @@ async def test_google_config_migrate_expose_entity_prefs_default_none( entity_registry: er.EntityRegistry, ) -> None: """Test migrating Google entity config.""" - hass.set_state(CoreState.starting) + hass.set_state(CoreState.not_running) assert await async_setup_component(hass, "homeassistant", {}) entity_default = entity_registry.async_get_or_create( @@ -754,7 +754,7 @@ async def test_google_config_migrate_expose_entity_prefs_default( entity_registry: er.EntityRegistry, ) -> None: """Test migrating Google entity config.""" - hass.set_state(CoreState.starting) + hass.set_state(CoreState.not_running) assert await async_setup_component(hass, "homeassistant", {}) From 4095de056612c03291c1e33a44ea5ceb8c139179 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 11 Mar 2024 09:52:15 +0100 Subject: [PATCH 0703/1691] Allow Shelly CoAP to honour default network adapter (#110997) * Allow Shelly CoAP to honor default network adapter * apply review comment * 1 more debug log line * adapt code to library changes * test * improve test * one more test --- homeassistant/components/shelly/__init__.py | 3 +- homeassistant/components/shelly/const.py | 1 - homeassistant/components/shelly/manifest.json | 2 +- homeassistant/components/shelly/utils.py | 21 +++++++- tests/components/shelly/test_init.py | 53 ++++++++++++++++++- 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 29550e841d7..cfa95162eed 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -7,7 +7,7 @@ from typing import Any, Final from aioshelly.block_device import BlockDevice, BlockUpdateType from aioshelly.common import ConnectionOptions -from aioshelly.const import RPC_GENERATIONS +from aioshelly.const import DEFAULT_COAP_PORT, RPC_GENERATIONS from aioshelly.exceptions import ( DeviceConnectionError, FirmwareUnsupported, @@ -37,7 +37,6 @@ from .const import ( CONF_COAP_PORT, CONF_SLEEP_PERIOD, DATA_CONFIG_ENTRY, - DEFAULT_COAP_PORT, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID, LOGGER, diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index ebe2dc4bc28..53e827cea72 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -33,7 +33,6 @@ LOGGER: Logger = getLogger(__package__) DATA_CONFIG_ENTRY: Final = "config_entry" CONF_COAP_PORT: Final = "coap_port" -DEFAULT_COAP_PORT: Final = 5683 FIRMWARE_PATTERN: Final = re.compile(r"^(\d{8})") # max light transition time in milliseconds diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 38db658bba9..2bf6b4f0b7d 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74", "@bdraco"], "config_flow": true, - "dependencies": ["bluetooth", "http"], + "dependencies": ["bluetooth", "http", "network"], "documentation": "https://www.home-assistant.io/integrations/shelly", "integration_type": "device", "iot_class": "local_push", diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 4081fe86dd0..85a058cc90c 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -3,12 +3,14 @@ from __future__ import annotations from datetime import datetime, timedelta +from ipaddress import IPv4Address from typing import Any, cast from aiohttp.web import Request, WebSocketResponse from aioshelly.block_device import COAP, Block, BlockDevice from aioshelly.const import ( BLOCK_GENERATIONS, + DEFAULT_COAP_PORT, MODEL_1L, MODEL_DIMMER, MODEL_DIMMER_2, @@ -19,6 +21,7 @@ from aioshelly.const import ( ) from aioshelly.rpc_device import RpcDevice, WsServer +from homeassistant.components import network from homeassistant.components.http import HomeAssistantView from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -36,7 +39,6 @@ from .const import ( BASIC_INPUTS_EVENTS_TYPES, CONF_COAP_PORT, CONF_GEN, - DEFAULT_COAP_PORT, DEVICES_WITHOUT_FIRMWARE_CHANGELOG, DOMAIN, FIRMWARE_UNSUPPORTED_ISSUE_ID, @@ -221,12 +223,27 @@ def get_shbtn_input_triggers() -> list[tuple[str, str]]: async def get_coap_context(hass: HomeAssistant) -> COAP: """Get CoAP context to be used in all Shelly Gen1 devices.""" context = COAP() + + adapters = await network.async_get_adapters(hass) + LOGGER.debug("Network adapters: %s", adapters) + + ipv4: list[IPv4Address] = [] + if not network.async_only_default_interface_enabled(adapters): + for address in await network.async_get_enabled_source_ips(hass): + if address.version == 4 and not ( + address.is_link_local + or address.is_loopback + or address.is_multicast + or address.is_unspecified + ): + ipv4.append(address) + LOGGER.debug("Network IPv4 addresses: %s", ipv4) if DOMAIN in hass.data: port = hass.data[DOMAIN].get(CONF_COAP_PORT, DEFAULT_COAP_PORT) else: port = DEFAULT_COAP_PORT LOGGER.info("Starting CoAP context with UDP port %s", port) - await context.initialize(port) + await context.initialize(port, ipv4) @callback def shutdown_listener(ev: Event) -> None: diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index 6a2d5394c80..700b54f153d 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -1,7 +1,9 @@ """Test cases for the Shelly component.""" -from unittest.mock import AsyncMock, Mock, patch +from ipaddress import IPv4Address +from unittest.mock import AsyncMock, Mock, call, patch +from aioshelly.block_device import COAP from aioshelly.exceptions import ( DeviceConnectionError, FirmwareUnsupported, @@ -49,6 +51,55 @@ async def test_custom_coap_port( assert "Starting CoAP context with UDP port 7632" in caplog.text +async def test_ip_address_with_only_default_interface( + hass: HomeAssistant, mock_block_device: Mock, caplog: pytest.LogCaptureFixture +) -> None: + """Test more local ip addresses with only the default interface..""" + with patch( + "homeassistant.components.network.async_only_default_interface_enabled", + return_value=True, + ), patch( + "homeassistant.components.network.async_get_enabled_source_ips", + return_value=[IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")], + ), patch( + "homeassistant.components.shelly.utils.COAP", + autospec=COAP, + ) as mock_coap_init: + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {"coap_port": 7632}}) + await hass.async_block_till_done() + + await init_integration(hass, 1) + assert "Starting CoAP context with UDP port 7632" in caplog.text + # Make sure COAP.initialize is called with an empty list + # when async_only_default_interface_enabled is True even if + # async_get_enabled_source_ips returns more than one address + assert mock_coap_init.mock_calls[1] == call().initialize(7632, []) + + +async def test_ip_address_without_only_default_interface( + hass: HomeAssistant, mock_block_device: Mock, caplog: pytest.LogCaptureFixture +) -> None: + """Test more local ip addresses without only the default interface..""" + with patch( + "homeassistant.components.network.async_only_default_interface_enabled", + return_value=False, + ), patch( + "homeassistant.components.network.async_get_enabled_source_ips", + return_value=[IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")], + ), patch( + "homeassistant.components.shelly.utils.COAP", + autospec=COAP, + ) as mock_coap_init: + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {"coap_port": 7632}}) + await hass.async_block_till_done() + + await init_integration(hass, 1) + assert "Starting CoAP context with UDP port 7632" in caplog.text + assert mock_coap_init.mock_calls[1] == call().initialize( + 7632, [IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")] + ) + + @pytest.mark.parametrize("gen", [1, 2, 3]) async def test_shared_device_mac( hass: HomeAssistant, From d0f53c2c99e7030dd8dc6dbc2aac72f1d84d6912 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 10:20:08 +0100 Subject: [PATCH 0704/1691] Remove entity description mixin in Vilfo (#112966) --- homeassistant/components/vilfo/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/vilfo/sensor.py b/homeassistant/components/vilfo/sensor.py index 34c862e1a8f..77a7df7a0a8 100644 --- a/homeassistant/components/vilfo/sensor.py +++ b/homeassistant/components/vilfo/sensor.py @@ -25,18 +25,13 @@ from .const import ( ) -@dataclass(frozen=True) -class VilfoRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class VilfoSensorEntityDescription(SensorEntityDescription): + """Describes Vilfo sensor entity.""" api_key: str -@dataclass(frozen=True) -class VilfoSensorEntityDescription(SensorEntityDescription, VilfoRequiredKeysMixin): - """Describes Vilfo sensor entity.""" - - SENSOR_TYPES: tuple[VilfoSensorEntityDescription, ...] = ( VilfoSensorEntityDescription( key=ATTR_LOAD, From a0d50ecdf52b33935691f9cbccb9dab1d768cb52 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 10:53:19 +0100 Subject: [PATCH 0705/1691] Remove entity description mixin in Tado (#112947) --- homeassistant/components/tado/binary_sensor.py | 13 +++---------- homeassistant/components/tado/sensor.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/tado/binary_sensor.py b/homeassistant/components/tado/binary_sensor.py index 65c0b84906c..0e8cbd1d175 100644 --- a/homeassistant/components/tado/binary_sensor.py +++ b/homeassistant/components/tado/binary_sensor.py @@ -34,19 +34,12 @@ from .entity import TadoDeviceEntity, TadoZoneEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class TadoBinarySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TadoBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Tado binary sensor entity.""" state_fn: Callable[[Any], bool] - -@dataclass(frozen=True) -class TadoBinarySensorEntityDescription( - BinarySensorEntityDescription, TadoBinarySensorEntityDescriptionMixin -): - """Describes Tado binary sensor entity.""" - attributes_fn: Callable[[Any], dict[Any, StateType]] | None = None diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index 451a52f350f..f8572ac3bc8 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -37,19 +37,12 @@ from .entity import TadoHomeEntity, TadoZoneEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class TadoSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TadoSensorEntityDescription(SensorEntityDescription): + """Describes Tado sensor entity.""" state_fn: Callable[[Any], StateType] - -@dataclass(frozen=True) -class TadoSensorEntityDescription( - SensorEntityDescription, TadoSensorEntityDescriptionMixin -): - """Describes Tado sensor entity.""" - attributes_fn: Callable[[Any], dict[Any, StateType]] | None = None data_category: str | None = None From 6d440e36ada0a9b88c0a9620d5b1106a5e9e4095 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Mon, 11 Mar 2024 19:54:12 +1000 Subject: [PATCH 0706/1691] Add tests for Aussie Broadband diagnostics (#113049) * Add tests for diag * Fix docstring * Spelling --- .../snapshots/test_diagnostics.ambr | 40 +++++++++++++++++++ .../aussie_broadband/test_diagnostics.py | 22 ++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/components/aussie_broadband/snapshots/test_diagnostics.ambr create mode 100644 tests/components/aussie_broadband/test_diagnostics.py diff --git a/tests/components/aussie_broadband/snapshots/test_diagnostics.ambr b/tests/components/aussie_broadband/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..e419fac09c9 --- /dev/null +++ b/tests/components/aussie_broadband/snapshots/test_diagnostics.ambr @@ -0,0 +1,40 @@ +# serializer version: 1 +# name: test_select_async_setup_entry + dict({ + 'services': list([ + dict({ + 'service': dict({ + 'coordinator': '**REDACTED**', + 'description': '**REDACTED**', + 'name': 'NBN', + 'service_id': '12345678', + 'type': 'NBN', + }), + 'usage': dict({ + }), + }), + dict({ + 'service': dict({ + 'coordinator': '**REDACTED**', + 'description': '**REDACTED**', + 'name': 'Mobile', + 'service_id': '87654321', + 'type': 'PhoneMobile', + }), + 'usage': dict({ + }), + }), + dict({ + 'service': dict({ + 'coordinator': '**REDACTED**', + 'description': '**REDACTED**', + 'name': 'VOIP', + 'service_id': '23456789', + 'type': 'VOIP', + }), + 'usage': dict({ + }), + }), + ]), + }) +# --- diff --git a/tests/components/aussie_broadband/test_diagnostics.py b/tests/components/aussie_broadband/test_diagnostics.py new file mode 100644 index 00000000000..b95e581feff --- /dev/null +++ b/tests/components/aussie_broadband/test_diagnostics.py @@ -0,0 +1,22 @@ +"""Test the Aussie Broadband Diagnostics.""" + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from .common import setup_platform + +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_select_async_setup_entry( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics platform.""" + + entry = await setup_platform(hass, []) + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == snapshot From e87e0d2d410281b0b01d5f561a65584030994dc4 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 11 Mar 2024 10:54:51 +0100 Subject: [PATCH 0707/1691] Bump aioslimproto to 3.0.0 (#113046) --- .../components/slimproto/manifest.json | 2 +- .../components/slimproto/media_player.py | 18 ++++++++++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/slimproto/manifest.json b/homeassistant/components/slimproto/manifest.json index b221db96262..f270e020740 100644 --- a/homeassistant/components/slimproto/manifest.json +++ b/homeassistant/components/slimproto/manifest.json @@ -6,5 +6,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/slimproto", "iot_class": "local_push", - "requirements": ["aioslimproto==2.3.3"] + "requirements": ["aioslimproto==3.0.0"] } diff --git a/homeassistant/components/slimproto/media_player.py b/homeassistant/components/slimproto/media_player.py index 81bce3820ed..f3cbcef2a61 100644 --- a/homeassistant/components/slimproto/media_player.py +++ b/homeassistant/components/slimproto/media_player.py @@ -6,7 +6,7 @@ import asyncio from typing import Any from aioslimproto.client import PlayerState, SlimClient -from aioslimproto.const import EventType, SlimEvent +from aioslimproto.models import EventType, SlimEvent from aioslimproto.server import SlimServer from homeassistant.components import media_source @@ -145,9 +145,23 @@ class SlimProtoPlayer(MediaPlayerEntity): def update_attributes(self) -> None: """Handle player updates.""" self._attr_volume_level = self.player.volume_level / 100 + self._attr_is_volume_muted = self.player.muted self._attr_media_position = self.player.elapsed_seconds self._attr_media_position_updated_at = utcnow() - self._attr_media_content_id = self.player.current_url + if (current_media := self.player.current_media) and ( + metadata := current_media.metadata + ): + self._attr_media_content_id = metadata.get("item_id", current_media.url) + self._attr_media_artist = metadata.get("artist") + self._attr_media_album_name = metadata.get("album") + self._attr_media_title = metadata.get("title") + self._attr_media_image_url = metadata.get("image_url") + else: + self._attr_media_content_id = current_media.url if current_media else None + self._attr_media_artist = None + self._attr_media_album_name = None + self._attr_media_title = None + self._attr_media_image_url = None self._attr_media_content_type = "music" async def async_media_play(self) -> None: diff --git a/requirements_all.txt b/requirements_all.txt index 3ad10542804..441a89d1ff5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -371,7 +371,7 @@ aioshelly==8.1.1 aioskybell==22.7.0 # homeassistant.components.slimproto -aioslimproto==2.3.3 +aioslimproto==3.0.0 # homeassistant.components.steamist aiosteamist==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4b2155ec82c..ad07be13904 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -344,7 +344,7 @@ aioshelly==8.1.1 aioskybell==22.7.0 # homeassistant.components.slimproto -aioslimproto==2.3.3 +aioslimproto==3.0.0 # homeassistant.components.steamist aiosteamist==0.3.2 From 981902dd8ae8a92e88c4db29bd0f372741017d93 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:03:06 +0100 Subject: [PATCH 0708/1691] Remove entity description mixin in Opower (#112913) --- homeassistant/components/opower/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/opower/sensor.py b/homeassistant/components/opower/sensor.py index 1987abcc69d..2d8f1b5ded5 100644 --- a/homeassistant/components/opower/sensor.py +++ b/homeassistant/components/opower/sensor.py @@ -25,18 +25,13 @@ from .const import DOMAIN from .coordinator import OpowerCoordinator -@dataclass(frozen=True) -class OpowerEntityDescriptionMixin: - """Mixin values for required keys.""" +@dataclass(frozen=True, kw_only=True) +class OpowerEntityDescription(SensorEntityDescription): + """Class describing Opower sensors entities.""" value_fn: Callable[[Forecast], str | float] -@dataclass(frozen=True) -class OpowerEntityDescription(SensorEntityDescription, OpowerEntityDescriptionMixin): - """Class describing Opower sensors entities.""" - - # suggested_display_precision=0 for all sensors since # Opower provides 0 decimal points for all these. # (for the statistics in the energy dashboard Opower does provide decimal points) From b45bfdb3ccd6600a46bf6444d414c03c1e90527f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:08:15 +0100 Subject: [PATCH 0709/1691] Bump Wandalen/wretry.action from 1.4.7 to 1.4.8 (#113038) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 29c45ca4820..44d95c03954 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1084,7 +1084,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.4.7 + uses: Wandalen/wretry.action@v1.4.8 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1095,7 +1095,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.4.7 + uses: Wandalen/wretry.action@v1.4.8 with: action: codecov/codecov-action@v3.1.3 with: | From 716a163f5f02d07f80083a131ad7be29095b8f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E9=87=8ESKY?= <87404327+FlyingFeng2021@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:11:32 +0800 Subject: [PATCH 0710/1691] Bump boschshcpy to 0.2.82 (#112890) --- homeassistant/components/bosch_shc/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bosch_shc/manifest.json b/homeassistant/components/bosch_shc/manifest.json index e29865153b3..efae159adc2 100644 --- a/homeassistant/components/bosch_shc/manifest.json +++ b/homeassistant/components/bosch_shc/manifest.json @@ -7,7 +7,7 @@ "documentation": "https://www.home-assistant.io/integrations/bosch_shc", "iot_class": "local_push", "loggers": ["boschshcpy"], - "requirements": ["boschshcpy==0.2.75"], + "requirements": ["boschshcpy==0.2.82"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 441a89d1ff5..3616d5d050d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -593,7 +593,7 @@ bluetooth-data-tools==1.19.0 bond-async==0.2.1 # homeassistant.components.bosch_shc -boschshcpy==0.2.75 +boschshcpy==0.2.82 # homeassistant.components.amazon_polly # homeassistant.components.route53 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ad07be13904..d232d3fdc6b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -508,7 +508,7 @@ bluetooth-data-tools==1.19.0 bond-async==0.2.1 # homeassistant.components.bosch_shc -boschshcpy==0.2.75 +boschshcpy==0.2.82 # homeassistant.components.bring bring-api==0.5.6 From e696afabe70109082da90835a82ed5bf88411d1f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:16:29 +0100 Subject: [PATCH 0711/1691] Remove entity description mixin in WeatherFlow (#112971) --- homeassistant/components/weatherflow/sensor.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/weatherflow/sensor.py b/homeassistant/components/weatherflow/sensor.py index 5bedaa9d711..cacede55c42 100644 --- a/homeassistant/components/weatherflow/sensor.py +++ b/homeassistant/components/weatherflow/sensor.py @@ -47,13 +47,6 @@ from homeassistant.util.unit_system import METRIC_SYSTEM from .const import DOMAIN, LOGGER, format_dispatch_call -@dataclass(frozen=True) -class WeatherFlowSensorRequiredKeysMixin: - """Mixin for required keys.""" - - raw_data_conv_fn: Callable[[WeatherFlowDevice], datetime | StateType] - - def precipitation_raw_conversion_fn(raw_data: Enum): """Parse parse precipitation type.""" if raw_data.name.lower() == "unknown": @@ -61,14 +54,14 @@ def precipitation_raw_conversion_fn(raw_data: Enum): return raw_data.name.lower() -@dataclass(frozen=True) -class WeatherFlowSensorEntityDescription( - SensorEntityDescription, WeatherFlowSensorRequiredKeysMixin -): +@dataclass(frozen=True, kw_only=True) +class WeatherFlowSensorEntityDescription(SensorEntityDescription): """Describes WeatherFlow sensor entity.""" + raw_data_conv_fn: Callable[[WeatherFlowDevice], datetime | StateType] + event_subscriptions: list[str] = field(default_factory=lambda: [EVENT_OBSERVATION]) - imperial_suggested_unit: None | str = None + imperial_suggested_unit: str | None = None def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType: """Return the parsed sensor value.""" From e4b43680e15aaf58f01142e11ee8ee5732d3bfdb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:18:01 +0100 Subject: [PATCH 0712/1691] Remove entity description mixin in Starlink (#112943) --- homeassistant/components/starlink/binary_sensor.py | 13 +++---------- homeassistant/components/starlink/button.py | 13 +++---------- homeassistant/components/starlink/device_tracker.py | 13 +++---------- homeassistant/components/starlink/sensor.py | 13 +++---------- homeassistant/components/starlink/switch.py | 13 +++---------- 5 files changed, 15 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/starlink/binary_sensor.py b/homeassistant/components/starlink/binary_sensor.py index d346c19fec4..e48d28dcc44 100644 --- a/homeassistant/components/starlink/binary_sensor.py +++ b/homeassistant/components/starlink/binary_sensor.py @@ -32,20 +32,13 @@ async def async_setup_entry( ) -@dataclass(frozen=True) -class StarlinkBinarySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class StarlinkBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes a Starlink binary sensor entity.""" value_fn: Callable[[StarlinkData], bool | None] -@dataclass(frozen=True) -class StarlinkBinarySensorEntityDescription( - BinarySensorEntityDescription, StarlinkBinarySensorEntityDescriptionMixin -): - """Describes a Starlink binary sensor entity.""" - - class StarlinkBinarySensorEntity(StarlinkEntity, BinarySensorEntity): """A BinarySensorEntity for Starlink devices. Handles creating unique IDs.""" diff --git a/homeassistant/components/starlink/button.py b/homeassistant/components/starlink/button.py index daf3122a00d..f8f18763d30 100644 --- a/homeassistant/components/starlink/button.py +++ b/homeassistant/components/starlink/button.py @@ -31,20 +31,13 @@ async def async_setup_entry( ) -@dataclass(frozen=True) -class StarlinkButtonEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class StarlinkButtonEntityDescription(ButtonEntityDescription): + """Describes a Starlink button entity.""" press_fn: Callable[[StarlinkUpdateCoordinator], Awaitable[None]] -@dataclass(frozen=True) -class StarlinkButtonEntityDescription( - ButtonEntityDescription, StarlinkButtonEntityDescriptionMixin -): - """Describes a Starlink button entity.""" - - class StarlinkButtonEntity(StarlinkEntity, ButtonEntity): """A ButtonEntity for Starlink devices. Handles creating unique IDs.""" diff --git a/homeassistant/components/starlink/device_tracker.py b/homeassistant/components/starlink/device_tracker.py index f260a7d1c32..84c0a4cac24 100644 --- a/homeassistant/components/starlink/device_tracker.py +++ b/homeassistant/components/starlink/device_tracker.py @@ -26,21 +26,14 @@ async def async_setup_entry( ) -@dataclass(frozen=True) -class StarlinkDeviceTrackerEntityDescriptionMixin: - """Describes a Starlink device tracker.""" +@dataclass(frozen=True, kw_only=True) +class StarlinkDeviceTrackerEntityDescription(EntityDescription): + """Describes a Starlink button entity.""" latitude_fn: Callable[[StarlinkData], float] longitude_fn: Callable[[StarlinkData], float] -@dataclass(frozen=True) -class StarlinkDeviceTrackerEntityDescription( - EntityDescription, StarlinkDeviceTrackerEntityDescriptionMixin -): - """Describes a Starlink button entity.""" - - DEVICE_TRACKERS = [ StarlinkDeviceTrackerEntityDescription( key="device_location", diff --git a/homeassistant/components/starlink/sensor.py b/homeassistant/components/starlink/sensor.py index 3f3b855ca63..21f2400022c 100644 --- a/homeassistant/components/starlink/sensor.py +++ b/homeassistant/components/starlink/sensor.py @@ -41,20 +41,13 @@ async def async_setup_entry( ) -@dataclass(frozen=True) -class StarlinkSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class StarlinkSensorEntityDescription(SensorEntityDescription): + """Describes a Starlink sensor entity.""" value_fn: Callable[[StarlinkData], datetime | StateType] -@dataclass(frozen=True) -class StarlinkSensorEntityDescription( - SensorEntityDescription, StarlinkSensorEntityDescriptionMixin -): - """Describes a Starlink sensor entity.""" - - class StarlinkSensorEntity(StarlinkEntity, SensorEntity): """A SensorEntity for Starlink devices. Handles creating unique IDs.""" diff --git a/homeassistant/components/starlink/switch.py b/homeassistant/components/starlink/switch.py index 551afa8e73c..af773a39f79 100644 --- a/homeassistant/components/starlink/switch.py +++ b/homeassistant/components/starlink/switch.py @@ -31,22 +31,15 @@ async def async_setup_entry( ) -@dataclass(frozen=True) -class StarlinkSwitchEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class StarlinkSwitchEntityDescription(SwitchEntityDescription): + """Describes a Starlink switch entity.""" value_fn: Callable[[StarlinkData], bool | None] turn_on_fn: Callable[[StarlinkUpdateCoordinator], Awaitable[None]] turn_off_fn: Callable[[StarlinkUpdateCoordinator], Awaitable[None]] -@dataclass(frozen=True) -class StarlinkSwitchEntityDescription( - SwitchEntityDescription, StarlinkSwitchEntityDescriptionMixin -): - """Describes a Starlink switch entity.""" - - class StarlinkSwitchEntity(StarlinkEntity, SwitchEntity): """A SwitchEntity for Starlink devices. Handles creating unique IDs.""" From da40e83fd96e23d335ab23cb0840ef50d0caa7c6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:18:34 +0100 Subject: [PATCH 0713/1691] Remove entity description mixin in Schlage (#112934) --- .../components/schlage/binary_sensor.py | 18 +++--------------- homeassistant/components/schlage/switch.py | 18 +++--------------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/schlage/binary_sensor.py b/homeassistant/components/schlage/binary_sensor.py index 5c97a903c72..fae6208b95f 100644 --- a/homeassistant/components/schlage/binary_sensor.py +++ b/homeassistant/components/schlage/binary_sensor.py @@ -20,25 +20,13 @@ from .coordinator import LockData, SchlageDataUpdateCoordinator from .entity import SchlageEntity -@dataclass(frozen=True) -class SchlageBinarySensorEntityDescriptionMixin: - """Mixin for required keys.""" - - # NOTE: This has to be a mixin because these are required keys. - # BinarySensorEntityDescription has attributes with default values, - # which means we can't inherit from it because you haven't have - # non-default arguments follow default arguments in an initializer. +@dataclass(frozen=True, kw_only=True) +class SchlageBinarySensorEntityDescription(BinarySensorEntityDescription): + """Entity description for a Schlage binary_sensor.""" value_fn: Callable[[LockData], bool] -@dataclass(frozen=True) -class SchlageBinarySensorEntityDescription( - BinarySensorEntityDescription, SchlageBinarySensorEntityDescriptionMixin -): - """Entity description for a Schlage binary_sensor.""" - - _DESCRIPTIONS: tuple[SchlageBinarySensorEntityDescription] = ( SchlageBinarySensorEntityDescription( key="keypad_disabled", diff --git a/homeassistant/components/schlage/switch.py b/homeassistant/components/schlage/switch.py index 36c8fa74244..467fbc671bf 100644 --- a/homeassistant/components/schlage/switch.py +++ b/homeassistant/components/schlage/switch.py @@ -24,27 +24,15 @@ from .coordinator import SchlageDataUpdateCoordinator from .entity import SchlageEntity -@dataclass(frozen=True) -class SchlageSwitchEntityDescriptionMixin: - """Mixin for required keys.""" - - # NOTE: This has to be a mixin because these are required keys. - # SwitchEntityDescription has attributes with default values, - # which means we can't inherit from it because you haven't have - # non-default arguments follow default arguments in an initializer. +@dataclass(frozen=True, kw_only=True) +class SchlageSwitchEntityDescription(SwitchEntityDescription): + """Entity description for a Schlage switch.""" on_fn: Callable[[Lock], None] off_fn: Callable[[Lock], None] value_fn: Callable[[Lock], bool] -@dataclass(frozen=True) -class SchlageSwitchEntityDescription( - SwitchEntityDescription, SchlageSwitchEntityDescriptionMixin -): - """Entity description for a Schlage switch.""" - - SWITCHES: tuple[SchlageSwitchEntityDescription, ...] = ( SchlageSwitchEntityDescription( key="beeper", From d95f30611e58fbc904e1e39692d34fd0ac83a30b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:25:05 +0100 Subject: [PATCH 0714/1691] Remove entity description mixin in Kaleidescape (#112896) --- homeassistant/components/kaleidescape/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/kaleidescape/sensor.py b/homeassistant/components/kaleidescape/sensor.py index 1871238177c..5520943e683 100644 --- a/homeassistant/components/kaleidescape/sensor.py +++ b/homeassistant/components/kaleidescape/sensor.py @@ -22,20 +22,13 @@ if TYPE_CHECKING: from homeassistant.helpers.typing import StateType -@dataclass(frozen=True) -class BaseEntityDescriptionMixin: - """Mixin for required descriptor keys.""" +@dataclass(frozen=True, kw_only=True) +class KaleidescapeSensorEntityDescription(SensorEntityDescription): + """Describes Kaleidescape sensor entity.""" value_fn: Callable[[KaleidescapeDevice], StateType] -@dataclass(frozen=True) -class KaleidescapeSensorEntityDescription( - SensorEntityDescription, BaseEntityDescriptionMixin -): - """Describes Kaleidescape sensor entity.""" - - SENSOR_TYPES: tuple[KaleidescapeSensorEntityDescription, ...] = ( KaleidescapeSensorEntityDescription( key="media_location", From d9996d3add1ed42fb3eef62748a02b7118e5b114 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:28:08 +0100 Subject: [PATCH 0715/1691] Remove entity description mixin in Launch Library (#112902) --- homeassistant/components/launch_library/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 4deb444f929..470c2e678cf 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -32,21 +32,14 @@ from .const import DOMAIN DEFAULT_NEXT_LAUNCH_NAME = "Next launch" -@dataclass(frozen=True) -class LaunchLibrarySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class LaunchLibrarySensorEntityDescription(SensorEntityDescription): + """Describes a Next Launch sensor entity.""" value_fn: Callable[[Launch | Event], datetime | int | str | None] attributes_fn: Callable[[Launch | Event], dict[str, Any] | None] -@dataclass(frozen=True) -class LaunchLibrarySensorEntityDescription( - SensorEntityDescription, LaunchLibrarySensorEntityDescriptionMixin -): - """Describes a Next Launch sensor entity.""" - - SENSOR_DESCRIPTIONS: tuple[LaunchLibrarySensorEntityDescription, ...] = ( LaunchLibrarySensorEntityDescription( key="next_launch", From 1fa0ce2f2c47c3b3f7d956914d9a151fab051bff Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:28:26 +0100 Subject: [PATCH 0716/1691] Remove entity description mixin in Kaiterra (#112895) --- homeassistant/components/kaiterra/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/kaiterra/sensor.py b/homeassistant/components/kaiterra/sensor.py index fa15cb9b451..22401f9027a 100644 --- a/homeassistant/components/kaiterra/sensor.py +++ b/homeassistant/components/kaiterra/sensor.py @@ -18,20 +18,13 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import DISPATCHER_KAITERRA, DOMAIN -@dataclass(frozen=True) -class KaiterraSensorRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class KaiterraSensorEntityDescription(SensorEntityDescription): + """Class describing Renault sensor entities.""" suffix: str -@dataclass(frozen=True) -class KaiterraSensorEntityDescription( - SensorEntityDescription, KaiterraSensorRequiredKeysMixin -): - """Class describing Renault sensor entities.""" - - SENSORS = [ KaiterraSensorEntityDescription( suffix="Temperature", From 2349ce1abd4ad8bffe3350a5f2ec2dd150e659f0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:28:30 +0100 Subject: [PATCH 0717/1691] Move yandex transport fixture to integration test (#112988) * Move yandex transport fixture to integration test * Update tests/components/yandex_transport/test_sensor.py * Ran ruff --------- Co-authored-by: Paulus Schoutsen Co-authored-by: Franck Nijhof --- .../yandex_transport/fixtures/bus_reply.json} | 0 .../yandex_transport/fixtures/suburban_reply.json} | 0 .../{test_yandex_transport_sensor.py => test_sensor.py} | 6 ++++-- 3 files changed, 4 insertions(+), 2 deletions(-) rename tests/{fixtures/yandex_transport_bus_reply.json => components/yandex_transport/fixtures/bus_reply.json} (100%) rename tests/{fixtures/yandex_transport_suburban_reply.json => components/yandex_transport/fixtures/suburban_reply.json} (100%) rename tests/components/yandex_transport/{test_yandex_transport_sensor.py => test_sensor.py} (95%) diff --git a/tests/fixtures/yandex_transport_bus_reply.json b/tests/components/yandex_transport/fixtures/bus_reply.json similarity index 100% rename from tests/fixtures/yandex_transport_bus_reply.json rename to tests/components/yandex_transport/fixtures/bus_reply.json diff --git a/tests/fixtures/yandex_transport_suburban_reply.json b/tests/components/yandex_transport/fixtures/suburban_reply.json similarity index 100% rename from tests/fixtures/yandex_transport_suburban_reply.json rename to tests/components/yandex_transport/fixtures/suburban_reply.json diff --git a/tests/components/yandex_transport/test_yandex_transport_sensor.py b/tests/components/yandex_transport/test_sensor.py similarity index 95% rename from tests/components/yandex_transport/test_yandex_transport_sensor.py rename to tests/components/yandex_transport/test_sensor.py index deb4fd1f360..d302ce17a26 100644 --- a/tests/components/yandex_transport/test_yandex_transport_sensor.py +++ b/tests/components/yandex_transport/test_sensor.py @@ -13,8 +13,10 @@ import homeassistant.util.dt as dt_util from tests.common import assert_setup_component, load_fixture -BUS_REPLY = json.loads(load_fixture("yandex_transport_bus_reply.json")) -SUBURBAN_TRAIN_REPLY = json.loads(load_fixture("yandex_transport_suburban_reply.json")) +BUS_REPLY = json.loads(load_fixture("bus_reply.json", "yandex_transport")) +SUBURBAN_TRAIN_REPLY = json.loads( + load_fixture("suburban_reply.json", "yandex_transport") +) @pytest.fixture From 9fd96e8a7835736385e528516ecd691678a8c418 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:28:54 +0100 Subject: [PATCH 0718/1691] Remove entity description mixin in Meater (#112904) --- homeassistant/components/meater/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/meater/sensor.py b/homeassistant/components/meater/sensor.py index 30a10531fad..f719cb0f0e3 100644 --- a/homeassistant/components/meater/sensor.py +++ b/homeassistant/components/meater/sensor.py @@ -28,21 +28,14 @@ from homeassistant.util import dt as dt_util from .const import DOMAIN -@dataclass(frozen=True) -class MeaterSensorEntityDescriptionMixin: - """Mixin for MeaterSensorEntityDescription.""" +@dataclass(frozen=True, kw_only=True) +class MeaterSensorEntityDescription(SensorEntityDescription): + """Describes meater sensor entity.""" available: Callable[[MeaterProbe | None], bool] value: Callable[[MeaterProbe], datetime | float | str | None] -@dataclass(frozen=True) -class MeaterSensorEntityDescription( - SensorEntityDescription, MeaterSensorEntityDescriptionMixin -): - """Describes meater sensor entity.""" - - def _elapsed_time_to_timestamp(probe: MeaterProbe) -> datetime | None: """Convert elapsed time to timestamp.""" if not probe.cook or not hasattr(probe.cook, "time_elapsed"): From 53750acdab15a8faa54b7a9fd5b1ca61fa148a4f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:29:30 +0100 Subject: [PATCH 0719/1691] Remove entity description mixin in Minecraft Server (#112908) --- .../components/minecraft_server/binary_sensor.py | 12 ++---------- homeassistant/components/minecraft_server/sensor.py | 13 +++---------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/minecraft_server/binary_sensor.py b/homeassistant/components/minecraft_server/binary_sensor.py index 707e60dc0db..de514754ef8 100644 --- a/homeassistant/components/minecraft_server/binary_sensor.py +++ b/homeassistant/components/minecraft_server/binary_sensor.py @@ -1,6 +1,5 @@ """The Minecraft Server binary sensor platform.""" -from dataclasses import dataclass from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -18,13 +17,8 @@ from .entity import MinecraftServerEntity KEY_STATUS = "status" -@dataclass(frozen=True) -class MinecraftServerBinarySensorEntityDescription(BinarySensorEntityDescription): - """Class describing Minecraft Server binary sensor entities.""" - - BINARY_SENSOR_DESCRIPTIONS = [ - MinecraftServerBinarySensorEntityDescription( + BinarySensorEntityDescription( key=KEY_STATUS, translation_key=KEY_STATUS, device_class=BinarySensorDeviceClass.CONNECTIVITY, @@ -52,12 +46,10 @@ async def async_setup_entry( class MinecraftServerBinarySensorEntity(MinecraftServerEntity, BinarySensorEntity): """Representation of a Minecraft Server binary sensor base entity.""" - entity_description: MinecraftServerBinarySensorEntityDescription - def __init__( self, coordinator: MinecraftServerCoordinator, - description: MinecraftServerBinarySensorEntityDescription, + description: BinarySensorEntityDescription, config_entry: ConfigEntry, ) -> None: """Initialize binary sensor base entity.""" diff --git a/homeassistant/components/minecraft_server/sensor.py b/homeassistant/components/minecraft_server/sensor.py index ada3e68bab7..4b862f54715 100644 --- a/homeassistant/components/minecraft_server/sensor.py +++ b/homeassistant/components/minecraft_server/sensor.py @@ -32,22 +32,15 @@ UNIT_PLAYERS_MAX = "players" UNIT_PLAYERS_ONLINE = "players" -@dataclass(frozen=True) -class MinecraftServerEntityDescriptionMixin: - """Mixin values for Minecraft Server entities.""" +@dataclass(frozen=True, kw_only=True) +class MinecraftServerSensorEntityDescription(SensorEntityDescription): + """Class describing Minecraft Server sensor entities.""" value_fn: Callable[[MinecraftServerData], StateType] attributes_fn: Callable[[MinecraftServerData], MutableMapping[str, Any]] | None supported_server_types: set[MinecraftServerType] -@dataclass(frozen=True) -class MinecraftServerSensorEntityDescription( - SensorEntityDescription, MinecraftServerEntityDescriptionMixin -): - """Class describing Minecraft Server sensor entities.""" - - def get_extra_state_attributes_players_list( data: MinecraftServerData, ) -> dict[str, list[str]]: From 3b0b729557d3a713935d443e1f134746d1262819 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:41:49 +0100 Subject: [PATCH 0720/1691] Remove YAML configuration from WAQI (#113027) --- homeassistant/components/waqi/__init__.py | 16 --- homeassistant/components/waqi/config_flow.py | 47 +------- homeassistant/components/waqi/sensor.py | 118 +------------------ tests/components/waqi/test_sensor.py | 115 +----------------- 4 files changed, 11 insertions(+), 285 deletions(-) diff --git a/homeassistant/components/waqi/__init__.py b/homeassistant/components/waqi/__init__.py index 387d06da1a1..e9feca75ee7 100644 --- a/homeassistant/components/waqi/__init__.py +++ b/homeassistant/components/waqi/__init__.py @@ -8,7 +8,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.entity_registry as er from .const import DOMAIN from .coordinator import WAQIDataUpdateCoordinator @@ -19,8 +18,6 @@ PLATFORMS: list[Platform] = [Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up World Air Quality Index (WAQI) from a config entry.""" - await _migrate_unique_ids(hass, entry) - client = WAQIClient(session=async_get_clientsession(hass)) client.authenticate(entry.data[CONF_API_KEY]) @@ -39,16 +36,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -async def _migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Migrate pre-config flow unique ids.""" - entity_registry = er.async_get(hass) - registry_entries = er.async_entries_for_config_entry( - entity_registry, entry.entry_id - ) - for reg_entry in registry_entries: - if isinstance(reg_entry.unique_id, int): # type: ignore[unreachable] - entity_registry.async_update_entity( # type: ignore[unreachable] - reg_entry.entity_id, new_unique_id=f"{reg_entry.unique_id}_air_quality" - ) diff --git a/homeassistant/components/waqi/config_flow.py b/homeassistant/components/waqi/config_flow.py index b8c6cdf57a3..068cb1a5020 100644 --- a/homeassistant/components/waqi/config_flow.py +++ b/homeassistant/components/waqi/config_flow.py @@ -20,20 +20,15 @@ from homeassistant.const import ( CONF_LOCATION, CONF_LONGITUDE, CONF_METHOD, - CONF_NAME, ) -from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN -from homeassistant.data_entry_flow import AbortFlow from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.selector import ( LocationSelector, SelectSelector, SelectSelectorConfig, ) -from homeassistant.helpers.typing import ConfigType -from .const import CONF_STATION_NUMBER, DOMAIN, ISSUE_PLACEHOLDER +from .const import CONF_STATION_NUMBER, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -193,43 +188,3 @@ class WAQIConfigFlow(ConfigFlow, domain=DOMAIN): CONF_STATION_NUMBER: measuring_station.station_id, }, ) - - async def async_step_import(self, import_config: ConfigType) -> ConfigFlowResult: - """Handle importing from yaml.""" - await self.async_set_unique_id(str(import_config[CONF_STATION_NUMBER])) - try: - self._abort_if_unique_id_configured() - except AbortFlow as exc: - async_create_issue( - self.hass, - DOMAIN, - "deprecated_yaml_import_issue_already_configured", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - severity=IssueSeverity.ERROR, - translation_key="deprecated_yaml_import_issue_already_configured", - translation_placeholders=ISSUE_PLACEHOLDER, - ) - raise exc - - async_create_issue( - self.hass, - HOMEASSISTANT_DOMAIN, - f"deprecated_yaml_{DOMAIN}", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml", - translation_placeholders={ - "domain": DOMAIN, - "integration_title": "World Air Quality Index", - }, - ) - return self.async_create_entry( - title=import_config[CONF_NAME], - data={ - CONF_API_KEY: import_config[CONF_API_KEY], - CONF_STATION_NUMBER: import_config[CONF_STATION_NUMBER], - }, - ) diff --git a/homeassistant/components/waqi/sensor.py b/homeassistant/components/waqi/sensor.py index 198eaef536b..ce967a9b538 100644 --- a/homeassistant/components/waqi/sensor.py +++ b/homeassistant/components/waqi/sensor.py @@ -7,14 +7,8 @@ from dataclasses import dataclass import logging from typing import Any -from aiowaqi import ( - WAQIAirQuality, - WAQIAuthenticationError, - WAQIClient, - WAQIConnectionError, -) +from aiowaqi import WAQIAirQuality from aiowaqi.models import Pollutant -import voluptuous as vol from homeassistant.components.sensor import ( SensorDeviceClass, @@ -22,28 +16,21 @@ from homeassistant.components.sensor import ( SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_TIME, - CONF_API_KEY, - CONF_NAME, - CONF_TOKEN, PERCENTAGE, UnitOfPressure, UnitOfTemperature, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType +from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_STATION_NUMBER, DOMAIN, ISSUE_PLACEHOLDER +from .const import DOMAIN from .coordinator import WAQIDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -57,102 +44,6 @@ ATTR_PM2_5 = "pm_2_5" ATTR_PRESSURE = "pressure" ATTR_SULFUR_DIOXIDE = "sulfur_dioxide" -ATTRIBUTION = "Data provided by the World Air Quality Index project" - -ATTR_ICON = "mdi:cloud" - -CONF_LOCATIONS = "locations" -CONF_STATIONS = "stations" - -TIMEOUT = 10 - -PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_STATIONS): cv.ensure_list, - vol.Required(CONF_TOKEN): cv.string, - vol.Required(CONF_LOCATIONS): cv.ensure_list, - } -) - - -async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the requested World Air Quality Index locations.""" - - token = config[CONF_TOKEN] - station_filter = config.get(CONF_STATIONS) - locations = config[CONF_LOCATIONS] - - client = WAQIClient(session=async_get_clientsession(hass), request_timeout=TIMEOUT) - client.authenticate(token) - station_count = 0 - try: - for location_name in locations: - stations = await client.search(location_name) - _LOGGER.debug("The following stations were returned: %s", stations) - for station in stations: - station_count = station_count + 1 - if not station_filter or { - station.station_id, - station.station.external_url, - station.station.name, - } & set(station_filter): - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_STATION_NUMBER: station.station_id, - CONF_NAME: station.station.name, - CONF_API_KEY: config[CONF_TOKEN], - }, - ) - ) - except WAQIAuthenticationError as err: - async_create_issue( - hass, - DOMAIN, - "deprecated_yaml_import_issue_invalid_auth", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml_import_issue_invalid_auth", - translation_placeholders=ISSUE_PLACEHOLDER, - ) - _LOGGER.exception("Could not authenticate with WAQI") - raise PlatformNotReady from err - except WAQIConnectionError as err: - async_create_issue( - hass, - DOMAIN, - "deprecated_yaml_import_issue_cannot_connect", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml_import_issue_cannot_connect", - translation_placeholders=ISSUE_PLACEHOLDER, - ) - _LOGGER.exception("Failed to connect to WAQI servers") - raise PlatformNotReady from err - if station_count == 0: - async_create_issue( - hass, - DOMAIN, - "deprecated_yaml_import_issue_none_found", - breaks_in_ha_version="2024.4.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml_import_issue_none_found", - translation_placeholders=ISSUE_PLACEHOLDER, - ) - @dataclass(frozen=True, kw_only=True) class WAQISensorEntityDescription(SensorEntityDescription): @@ -301,6 +192,7 @@ class WaqiSensor(CoordinatorEntity[WAQIDataUpdateCoordinator], SensorEntity): @property def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return old state attributes if the entity is AQI entity.""" + # These are deprecated and will be removed in 2024.5 if self.entity_description.key != "air_quality": return None attrs: dict[str, Any] = {} diff --git a/tests/components/waqi/test_sensor.py b/tests/components/waqi/test_sensor.py index 7b19fbee083..328fe99330e 100644 --- a/tests/components/waqi/test_sensor.py +++ b/tests/components/waqi/test_sensor.py @@ -3,125 +3,20 @@ import json from unittest.mock import patch -from aiowaqi import WAQIAirQuality, WAQIError, WAQISearchResult +from aiowaqi import WAQIAirQuality, WAQIError import pytest from syrupy import SnapshotAssertion from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.components.waqi.const import CONF_STATION_NUMBER, DOMAIN -from homeassistant.components.waqi.sensor import CONF_LOCATIONS, CONF_STATIONS, SENSORS -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntryState -from homeassistant.const import ( - CONF_API_KEY, - CONF_NAME, - CONF_PLATFORM, - CONF_TOKEN, - Platform, -) +from homeassistant.components.waqi.const import DOMAIN +from homeassistant.components.waqi.sensor import SENSORS +from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er, issue_registry as ir +from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, load_fixture -LEGACY_CONFIG = { - Platform.SENSOR: [ - { - CONF_PLATFORM: DOMAIN, - CONF_TOKEN: "asd", - CONF_LOCATIONS: ["utrecht"], - CONF_STATIONS: [6332], - } - ] -} - - -async def test_legacy_migration(hass: HomeAssistant) -> None: - """Test migration from yaml to config flow.""" - search_result_json = json.loads(load_fixture("waqi/search_result.json")) - search_results = [ - WAQISearchResult.from_dict(search_result) - for search_result in search_result_json - ] - with patch( - "aiowaqi.WAQIClient.search", - return_value=search_results, - ), patch( - "aiowaqi.WAQIClient.get_by_station_number", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) - ), - ): - assert await async_setup_component(hass, Platform.SENSOR, LEGACY_CONFIG) - await hass.async_block_till_done() - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - issue_registry = ir.async_get(hass) - assert len(issue_registry.issues) == 1 - - -async def test_legacy_migration_already_imported( - hass: HomeAssistant, mock_config_entry: MockConfigEntry -) -> None: - """Test migration from yaml to config flow after already imported.""" - mock_config_entry.add_to_hass(hass) - with patch( - "aiowaqi.WAQIClient.get_by_station_number", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) - ), - ): - assert await async_setup_component(hass, DOMAIN, {}) - await hass.async_block_till_done() - - state = hass.states.get("sensor.de_jongweg_utrecht_air_quality_index") - assert state.state == "29" - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_STATION_NUMBER: 4584, - CONF_NAME: "xyz", - CONF_API_KEY: "asd", - }, - ) - ) - await hass.async_block_till_done() - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - assert entries[0].state is ConfigEntryState.LOADED - issue_registry = ir.async_get(hass) - assert len(issue_registry.issues) == 1 - - -async def test_sensor_id_migration( - hass: HomeAssistant, mock_config_entry: MockConfigEntry -) -> None: - """Test migrating unique id for original sensor.""" - mock_config_entry.add_to_hass(hass) - entity_registry = er.async_get(hass) - entity_registry.async_get_or_create( - SENSOR_DOMAIN, DOMAIN, 4584, config_entry=mock_config_entry - ) - with patch( - "aiowaqi.WAQIClient.get_by_station_number", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) - ), - ): - assert await async_setup_component(hass, DOMAIN, {}) - await hass.async_block_till_done() - entities = er.async_entries_for_config_entry( - entity_registry, mock_config_entry.entry_id - ) - assert len(entities) == 12 - assert hass.states.get("sensor.waqi_4584") - assert hass.states.get("sensor.de_jongweg_utrecht_air_quality_index") is None - assert entities[0].unique_id == "4584_air_quality" - @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_sensor( From c60aed30122e0713eccbd43ffa09d802b93445ad Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:44:21 +0100 Subject: [PATCH 0721/1691] Bump webmin-xmlrpc to 0.0.2 (#113004) --- homeassistant/components/webmin/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../webmin/fixtures/webmin_update.json | 162 ++++++++------ .../webmin/snapshots/test_diagnostics.ambr | 199 ++++++++++++++---- .../webmin/snapshots/test_sensor.ambr | 12 +- 6 files changed, 269 insertions(+), 110 deletions(-) diff --git a/homeassistant/components/webmin/manifest.json b/homeassistant/components/webmin/manifest.json index a15ca0a1f0d..12a03830cb8 100644 --- a/homeassistant/components/webmin/manifest.json +++ b/homeassistant/components/webmin/manifest.json @@ -7,5 +7,5 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["webmin"], - "requirements": ["webmin-xmlrpc==0.0.1"] + "requirements": ["webmin-xmlrpc==0.0.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3616d5d050d..06b99ce0c64 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2836,7 +2836,7 @@ waterfurnace==1.1.0 weatherflow4py==0.1.14 # homeassistant.components.webmin -webmin-xmlrpc==0.0.1 +webmin-xmlrpc==0.0.2 # homeassistant.components.assist_pipeline webrtc-noise-gain==1.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d232d3fdc6b..95b8400d877 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2177,7 +2177,7 @@ watchdog==2.3.1 weatherflow4py==0.1.14 # homeassistant.components.webmin -webmin-xmlrpc==0.0.1 +webmin-xmlrpc==0.0.2 # homeassistant.components.assist_pipeline webrtc-noise-gain==1.2.3 diff --git a/tests/components/webmin/fixtures/webmin_update.json b/tests/components/webmin/fixtures/webmin_update.json index c74346a925f..7ec77978a93 100644 --- a/tests/components/webmin/fixtures/webmin_update.json +++ b/tests/components/webmin/fixtures/webmin_update.json @@ -1,97 +1,135 @@ { - "load_1m": 0.98, - "load_5m": 1.02, - "load_15m": 1.0, - "mem_total": 32767008, - "mem_free": 26162544, - "swap_total": 1953088, - "swap_free": 1953088, - "total_space": 18104905818112, - "free_space": 8641328926720, - "fs": [ + "disk_total": 18104905818112, + "io": [0, 4], + "load": [ + 1.29, + 1.36, + 1.37, + 3589, + "Intel(R) Core(TM) i7-5820K CPU @ 3.30GHz", + "GenuineIntel", + 15728640, + 12 + ], + "disk_free": 7749321486336, + "kernel": { "os": "Linux", "arch": "x86_64", "version": "6.6.18-1-lts" }, + "disk_fs": [ { - "free": 174511820800, - "dir": "/", - "iused": 391146, - "used": 61225123840, - "type": "ext4", "device": "UUID=00000000-80b6-0000-8a06-000000000000", - "iused_percent": 3, - "used_percent": 26, + "dir": "/", + "ifree": 14927206, "total": 248431161344, + "used_percent": 80, + "type": "ext4", "itotal": 15482880, - "ifree": 15091734 + "iused": 555674, + "free": 49060442112, + "used": 186676502528, + "iused_percent": 4 }, { - "iused": 8877, - "used": 4608079593472, - "type": "ext4", - "dir": "/media/disk1", - "free": 1044483624960, - "used_percent": 82, - "ifree": 183131475, - "itotal": 183140352, - "total": 5952635744256, - "device": "UUID=00000000-2bb2-0000-896c-000000000000", - "iused_percent": 1 - }, - { - "used": 3881508986880, - "type": "ext4", - "iused": 3411401, - "dir": "/media/disk2", - "free": 7422333480960, - "used_percent": 35, "total": 11903838912512, + "used_percent": 38, + "iused": 3542318, + "type": "ext4", "itotal": 366198784, - "ifree": 362787383, "device": "/dev/md127", + "ifree": 362656466, + "dir": "/media/disk2", + "iused_percent": 1, + "free": 7028764823552, + "used": 4275077644288 + }, + { + "dir": "/media/disk1", + "ifree": 183130757, + "device": "UUID=00000000-2bb2-0000-896c-000000000000", + "type": "ext4", + "itotal": 183140352, + "iused": 9595, + "used_percent": 89, + "total": 5952635744256, + "used": 4981066997760, + "free": 671496220672, "iused_percent": 1 } ], - "used_space": 8550813704192, - "uptime": { "days": 3, "minutes": 23, "seconds": 12 }, + "drivetemps": [ + { "temp": 49, "device": "/dev/sda", "failed": "", "errors": "" }, + { "failed": "", "errors": "", "device": "/dev/sdb", "temp": 49 }, + { "device": "/dev/sdc", "temp": 51, "failed": "", "errors": "" }, + { "failed": "", "errors": "", "device": "/dev/sdd", "temp": 51 }, + { "errors": "", "failed": "", "temp": 43, "device": "/dev/sde" }, + { "device": "/dev/sdf", "temp": 40, "errors": "", "failed": "" } + ], + "mem": [32766344, 28530480, 1953088, 1944384, 27845756, ""], + "disk_used": 9442821144576, + "cputemps": [ + { "temp": 51, "core": 0 }, + { "temp": 49, "core": 1 }, + { "core": 2, "temp": 59 }, + { "temp": 51, "core": 3 }, + { "temp": 50, "core": 4 }, + { "temp": 49, "core": 5 } + ], + "procs": 310, + "cpu": [0, 8, 92, 0, 0], + "cpufans": [ + { "rpm": 0, "fan": 1 }, + { "fan": 2, "rpm": 1371 }, + { "rpm": 0, "fan": 3 }, + { "rpm": 927, "fan": 4 }, + { "rpm": 801, "fan": 5 } + ], + "load_1m": 1.29, + "load_5m": 1.36, + "load_15m": 1.37, + "mem_total": 32766344, + "mem_free": 28530480, + "swap_total": 1953088, + "swap_free": 1944384, + "uptime": { "days": 11, "minutes": 1, "seconds": 28 }, "active_interfaces": [ { - "fullname": "lo", + "scope6": ["host"], + "address": "127.0.0.1", + "address6": ["::1"], + "name": "lo", + "broadcast": 0, "up": 1, "index": 0, - "scope6": ["host"], - "netmask": "255.0.0.0", + "fullname": "lo", "netmask6": [128], - "edit": 1, - "broadcast": 0, + "netmask": "255.0.0.0", "mtu": 65536, - "name": "lo", - "address": "127.0.0.1", - "address6": ["::1"] + "edit": 1 }, { - "mtu": 1500, - "fullname": "enp6s0", + "scope6": [], + "address6": [], + "name": "enp6s0", "up": 1, "index": 1, - "ether": "12:34:56:78:9a:bc", - "address6": [], "netmask6": [], + "fullname": "enp6s0", + "mtu": 1500, "edit": 1, - "scope6": [], - "name": "enp6s0" + "ether": "12:34:56:78:9a:bc" }, { + "ether": "12:34:56:78:9a:bd", "edit": 1, - "netmask6": [64], "netmask": "255.255.255.0", - "scope6": ["link"], - "up": 1, - "index": 2, + "netmask6": [64], "fullname": "eno1", - "address6": ["fe80::2:3:4"], - "address": "192.168.1.4", - "name": "eno1", "mtu": 1500, + "index": 2, "broadcast": "192.168.1.255", - "ether": "12:34:56:78:9a:bd" + "up": 1, + "address6": ["fe80::2:3:4"], + "scope6": ["link"], + "name": "eno1", + "address": "192.168.1.4" } ] } diff --git a/tests/components/webmin/snapshots/test_diagnostics.ambr b/tests/components/webmin/snapshots/test_diagnostics.ambr index f227f6332fe..9c666938f56 100644 --- a/tests/components/webmin/snapshots/test_diagnostics.ambr +++ b/tests/components/webmin/snapshots/test_diagnostics.ambr @@ -55,62 +55,183 @@ 'up': 1, }), ]), - 'free_space': 8641328926720, - 'fs': list([ + 'cpu': list([ + 0, + 8, + 92, + 0, + 0, + ]), + 'cpufans': list([ + dict({ + 'fan': 1, + 'rpm': 0, + }), + dict({ + 'fan': 2, + 'rpm': 1371, + }), + dict({ + 'fan': 3, + 'rpm': 0, + }), + dict({ + 'fan': 4, + 'rpm': 927, + }), + dict({ + 'fan': 5, + 'rpm': 801, + }), + ]), + 'cputemps': list([ + dict({ + 'core': 0, + 'temp': 51, + }), + dict({ + 'core': 1, + 'temp': 49, + }), + dict({ + 'core': 2, + 'temp': 59, + }), + dict({ + 'core': 3, + 'temp': 51, + }), + dict({ + 'core': 4, + 'temp': 50, + }), + dict({ + 'core': 5, + 'temp': 49, + }), + ]), + 'disk_free': 7749321486336, + 'disk_fs': list([ dict({ 'device': '**REDACTED**', 'dir': '**REDACTED**', - 'free': 174511820800, - 'ifree': 15091734, + 'free': 49060442112, + 'ifree': 14927206, 'itotal': 15482880, - 'iused': 391146, - 'iused_percent': 3, + 'iused': 555674, + 'iused_percent': 4, 'total': 248431161344, 'type': 'ext4', - 'used': 61225123840, - 'used_percent': 26, + 'used': 186676502528, + 'used_percent': 80, }), dict({ 'device': '**REDACTED**', 'dir': '**REDACTED**', - 'free': 1044483624960, - 'ifree': 183131475, - 'itotal': 183140352, - 'iused': 8877, - 'iused_percent': 1, - 'total': 5952635744256, - 'type': 'ext4', - 'used': 4608079593472, - 'used_percent': 82, - }), - dict({ - 'device': '**REDACTED**', - 'dir': '**REDACTED**', - 'free': 7422333480960, - 'ifree': 362787383, + 'free': 7028764823552, + 'ifree': 362656466, 'itotal': 366198784, - 'iused': 3411401, + 'iused': 3542318, 'iused_percent': 1, 'total': 11903838912512, 'type': 'ext4', - 'used': 3881508986880, - 'used_percent': 35, + 'used': 4275077644288, + 'used_percent': 38, + }), + dict({ + 'device': '**REDACTED**', + 'dir': '**REDACTED**', + 'free': 671496220672, + 'ifree': 183130757, + 'itotal': 183140352, + 'iused': 9595, + 'iused_percent': 1, + 'total': 5952635744256, + 'type': 'ext4', + 'used': 4981066997760, + 'used_percent': 89, }), ]), - 'load_15m': 1.0, - 'load_1m': 0.98, - 'load_5m': 1.02, - 'mem_free': 26162544, - 'mem_total': 32767008, - 'swap_free': 1953088, - 'swap_total': 1953088, - 'total_space': 18104905818112, - 'uptime': dict({ - 'days': 3, - 'minutes': 23, - 'seconds': 12, + 'disk_total': 18104905818112, + 'disk_used': 9442821144576, + 'drivetemps': list([ + dict({ + 'device': '**REDACTED**', + 'errors': '', + 'failed': '', + 'temp': 49, + }), + dict({ + 'device': '**REDACTED**', + 'errors': '', + 'failed': '', + 'temp': 49, + }), + dict({ + 'device': '**REDACTED**', + 'errors': '', + 'failed': '', + 'temp': 51, + }), + dict({ + 'device': '**REDACTED**', + 'errors': '', + 'failed': '', + 'temp': 51, + }), + dict({ + 'device': '**REDACTED**', + 'errors': '', + 'failed': '', + 'temp': 43, + }), + dict({ + 'device': '**REDACTED**', + 'errors': '', + 'failed': '', + 'temp': 40, + }), + ]), + 'io': list([ + 0, + 4, + ]), + 'kernel': dict({ + 'arch': 'x86_64', + 'os': 'Linux', + 'version': '6.6.18-1-lts', + }), + 'load': list([ + 1.29, + 1.36, + 1.37, + 3589, + 'Intel(R) Core(TM) i7-5820K CPU @ 3.30GHz', + 'GenuineIntel', + 15728640, + 12, + ]), + 'load_15m': 1.37, + 'load_1m': 1.29, + 'load_5m': 1.36, + 'mem': list([ + 32766344, + 28530480, + 1953088, + 1944384, + 27845756, + '', + ]), + 'mem_free': 28530480, + 'mem_total': 32766344, + 'procs': 310, + 'swap_free': 1944384, + 'swap_total': 1953088, + 'uptime': dict({ + 'days': 11, + 'minutes': 1, + 'seconds': 28, }), - 'used_space': 8550813704192, }), 'entry': dict({ 'data': dict({ diff --git a/tests/components/webmin/snapshots/test_sensor.ambr b/tests/components/webmin/snapshots/test_sensor.ambr index 285bcdf1f9b..93f8bcc3709 100644 --- a/tests/components/webmin/snapshots/test_sensor.ambr +++ b/tests/components/webmin/snapshots/test_sensor.ambr @@ -44,7 +44,7 @@ 'entity_id': 'sensor.192_168_1_1_load_15m', 'last_changed': , 'last_updated': , - 'state': '1.0', + 'state': '1.37', }) # --- # name: test_sensor[sensor.192_168_1_1_load_1m-entry] @@ -92,7 +92,7 @@ 'entity_id': 'sensor.192_168_1_1_load_1m', 'last_changed': , 'last_updated': , - 'state': '0.98', + 'state': '1.29', }) # --- # name: test_sensor[sensor.192_168_1_1_load_5m-entry] @@ -140,7 +140,7 @@ 'entity_id': 'sensor.192_168_1_1_load_5m', 'last_changed': , 'last_updated': , - 'state': '1.02', + 'state': '1.36', }) # --- # name: test_sensor[sensor.192_168_1_1_memory_free-entry] @@ -196,7 +196,7 @@ 'entity_id': 'sensor.192_168_1_1_memory_free', 'last_changed': , 'last_updated': , - 'state': '24.9505462646484', + 'state': '27.2087860107422', }) # --- # name: test_sensor[sensor.192_168_1_1_memory_total-entry] @@ -252,7 +252,7 @@ 'entity_id': 'sensor.192_168_1_1_memory_total', 'last_changed': , 'last_updated': , - 'state': '31.2490539550781', + 'state': '31.248420715332', }) # --- # name: test_sensor[sensor.192_168_1_1_swap_free-entry] @@ -308,7 +308,7 @@ 'entity_id': 'sensor.192_168_1_1_swap_free', 'last_changed': , 'last_updated': , - 'state': '1.86260986328125', + 'state': '1.85430908203125', }) # --- # name: test_sensor[sensor.192_168_1_1_swap_total-entry] From 53613e69f269e0921789f101be190efc448ca4c7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:44:37 +0100 Subject: [PATCH 0722/1691] Remove entity description mixin in QNAP QSW (#112924) --- homeassistant/components/qnap_qsw/button.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/qnap_qsw/button.py b/homeassistant/components/qnap_qsw/button.py index 1a11a5209c0..091c6786a92 100644 --- a/homeassistant/components/qnap_qsw/button.py +++ b/homeassistant/components/qnap_qsw/button.py @@ -23,18 +23,13 @@ from .coordinator import QswDataCoordinator from .entity import QswDataEntity -@dataclass(frozen=True) -class QswButtonDescriptionMixin: - """Mixin to describe a Button entity.""" +@dataclass(frozen=True, kw_only=True) +class QswButtonDescription(ButtonEntityDescription): + """Class to describe a Button entity.""" press_action: Callable[[QnapQswApi], Awaitable[bool]] -@dataclass(frozen=True) -class QswButtonDescription(ButtonEntityDescription, QswButtonDescriptionMixin): - """Class to describe a Button entity.""" - - BUTTON_TYPES: Final[tuple[QswButtonDescription, ...]] = ( QswButtonDescription( device_class=ButtonDeviceClass.RESTART, From 65f2c74cb09a41b2ae3aaecbdaf2c8b49ecbc7be Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:46:21 +0100 Subject: [PATCH 0723/1691] Remove entity description mixin in Wallbox (#112969) --- homeassistant/components/wallbox/number.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index f09321a0849..8ae4c473299 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -39,22 +39,15 @@ def min_charging_current_value(coordinator: WallboxCoordinator) -> float: return 6 -@dataclass(frozen=True) -class WallboxNumberEntityDescriptionMixin: - """Load entities from different handlers.""" +@dataclass(frozen=True, kw_only=True) +class WallboxNumberEntityDescription(NumberEntityDescription): + """Describes Wallbox number entity.""" max_value_fn: Callable[[WallboxCoordinator], float] min_value_fn: Callable[[WallboxCoordinator], float] set_value_fn: Callable[[WallboxCoordinator], Callable[[float], Awaitable[None]]] -@dataclass(frozen=True) -class WallboxNumberEntityDescription( - NumberEntityDescription, WallboxNumberEntityDescriptionMixin -): - """Describes Wallbox number entity.""" - - NUMBER_TYPES: dict[str, WallboxNumberEntityDescription] = { CHARGER_MAX_CHARGING_CURRENT_KEY: WallboxNumberEntityDescription( key=CHARGER_MAX_CHARGING_CURRENT_KEY, From 830f419a8fec3cbeffc8930021f36d17770b53ad Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:48:27 +0100 Subject: [PATCH 0724/1691] Remove entity description mixin in SABnzbd (#112933) --- homeassistant/components/sabnzbd/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sabnzbd/sensor.py b/homeassistant/components/sabnzbd/sensor.py index 330a0a8c8d1..d5f19b5e718 100644 --- a/homeassistant/components/sabnzbd/sensor.py +++ b/homeassistant/components/sabnzbd/sensor.py @@ -21,18 +21,13 @@ from . import DOMAIN, SIGNAL_SABNZBD_UPDATED from .const import DEFAULT_NAME, KEY_API_DATA -@dataclass(frozen=True) -class SabnzbdRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class SabnzbdSensorEntityDescription(SensorEntityDescription): + """Describes Sabnzbd sensor entity.""" key: str -@dataclass(frozen=True) -class SabnzbdSensorEntityDescription(SensorEntityDescription, SabnzbdRequiredKeysMixin): - """Describes Sabnzbd sensor entity.""" - - SPEED_KEY = "kbpersec" SENSOR_TYPES: tuple[SabnzbdSensorEntityDescription, ...] = ( From 0d68c279851ecf11e7c6a679d2376dd61ec0703d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:48:44 +0100 Subject: [PATCH 0725/1691] Remove entity description mixin in Honeywell Lyric (#112903) --- homeassistant/components/lyric/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 858c6c25bd4..f1e9a8b3c3c 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -42,21 +42,14 @@ LYRIC_SETPOINT_STATUS_NAMES = { } -@dataclass(frozen=True) -class LyricSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class LyricSensorEntityDescription(SensorEntityDescription): + """Class describing Honeywell Lyric sensor entities.""" value_fn: Callable[[LyricDevice], StateType | datetime] suitable_fn: Callable[[LyricDevice], bool] -@dataclass(frozen=True) -class LyricSensorEntityDescription( - SensorEntityDescription, LyricSensorEntityDescriptionMixin -): - """Class describing Honeywell Lyric sensor entities.""" - - DEVICE_SENSORS: list[LyricSensorEntityDescription] = [ LyricSensorEntityDescription( key="indoor_temperature", From a8bde2df3c7a94ed8f1af46c9784b6d7a5cb0124 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:49:44 +0100 Subject: [PATCH 0726/1691] Remove entity description mixin in Z-Wave.js (#112979) --- homeassistant/components/zwave_js/binary_sensor.py | 13 +++---------- homeassistant/components/zwave_js/humidifier.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index dd3ded326b4..8137623379a 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -59,20 +59,13 @@ class NotificationZWaveJSEntityDescription(BinarySensorEntityDescription): states: tuple[str, ...] | None = None -@dataclass(frozen=True) -class PropertyZWaveJSMixin: - """Represent the mixin for property sensor descriptions.""" +@dataclass(frozen=True, kw_only=True) +class PropertyZWaveJSEntityDescription(BinarySensorEntityDescription): + """Represent the entity description for property name sensors.""" on_states: tuple[str, ...] -@dataclass(frozen=True) -class PropertyZWaveJSEntityDescription( - BinarySensorEntityDescription, PropertyZWaveJSMixin -): - """Represent the entity description for property name sensors.""" - - # Mappings for Notification sensors # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] = ( diff --git a/homeassistant/components/zwave_js/humidifier.py b/homeassistant/components/zwave_js/humidifier.py index be9e9a791fc..4030115ab1f 100644 --- a/homeassistant/components/zwave_js/humidifier.py +++ b/homeassistant/components/zwave_js/humidifier.py @@ -35,9 +35,9 @@ from .entity import ZWaveBaseEntity PARALLEL_UPDATES = 0 -@dataclass(frozen=True) -class ZwaveHumidifierEntityDescriptionRequiredKeys: - """A class for humidifier entity description required keys.""" +@dataclass(frozen=True, kw_only=True) +class ZwaveHumidifierEntityDescription(HumidifierEntityDescription): + """A class that describes the humidifier or dehumidifier entity.""" # The "on" control mode for this entity, e.g. HUMIDIFY for humidifier on_mode: HumidityControlMode @@ -49,13 +49,6 @@ class ZwaveHumidifierEntityDescriptionRequiredKeys: setpoint_type: HumidityControlSetpointType -@dataclass(frozen=True) -class ZwaveHumidifierEntityDescription( - HumidifierEntityDescription, ZwaveHumidifierEntityDescriptionRequiredKeys -): - """A class that describes the humidifier or dehumidifier entity.""" - - HUMIDIFIER_ENTITY_DESCRIPTION = ZwaveHumidifierEntityDescription( key="humidifier", device_class=HumidifierDeviceClass.HUMIDIFIER, From 2b8f42be395b3a796c885b06d3c50dddcbc380b6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:51:21 +0100 Subject: [PATCH 0727/1691] Remove entity description mixin in Zamg (#112976) --- homeassistant/components/zamg/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index 4e1a1d49bec..7c7f5fd6c16 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -38,18 +38,13 @@ from .const import ( from .coordinator import ZamgDataUpdateCoordinator -@dataclass(frozen=True) -class ZamgRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class ZamgSensorEntityDescription(SensorEntityDescription): + """Describes Zamg sensor entity.""" para_name: str -@dataclass(frozen=True) -class ZamgSensorEntityDescription(SensorEntityDescription, ZamgRequiredKeysMixin): - """Describes Zamg sensor entity.""" - - SENSOR_TYPES: tuple[ZamgSensorEntityDescription, ...] = ( ZamgSensorEntityDescription( key="pressure", From ea300623f95ec4bc680789422ea69d9a9474c517 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:51:45 +0100 Subject: [PATCH 0728/1691] Remove entity description mixin in Yalexs BLE (#112974) --- homeassistant/components/yalexs_ble/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/yalexs_ble/sensor.py b/homeassistant/components/yalexs_ble/sensor.py index e400111ddf5..1fc0601996e 100644 --- a/homeassistant/components/yalexs_ble/sensor.py +++ b/homeassistant/components/yalexs_ble/sensor.py @@ -28,20 +28,13 @@ from .entity import YALEXSBLEEntity from .models import YaleXSBLEData -@dataclass(frozen=True) -class YaleXSBLERequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class YaleXSBLESensorEntityDescription(SensorEntityDescription): + """Describes Yale Access Bluetooth sensor entity.""" value_fn: Callable[[LockState, LockInfo, ConnectionInfo], int | float | None] -@dataclass(frozen=True) -class YaleXSBLESensorEntityDescription( - SensorEntityDescription, YaleXSBLERequiredKeysMixin -): - """Describes Yale Access Bluetooth sensor entity.""" - - SENSORS: tuple[YaleXSBLESensorEntityDescription, ...] = ( YaleXSBLESensorEntityDescription( key="", # No key for the original RSSI sensor unique id From 85b6d70b049c31f67b1e0e44178a59a9899a2a9b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:52:19 +0100 Subject: [PATCH 0729/1691] Remove entity description mixin in Xiaomi Miio (#112973) --- homeassistant/components/xiaomi_miio/number.py | 9 ++------- homeassistant/components/xiaomi_miio/switch.py | 11 ++--------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/number.py b/homeassistant/components/xiaomi_miio/number.py index 71e92ad04a9..a0ae0ea5078 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -109,17 +109,12 @@ ATTR_OSCILLATION_ANGLE = "angle" ATTR_VOLUME = "volume" -@dataclass(frozen=True) -class XiaomiMiioNumberMixin: +@dataclass(frozen=True, kw_only=True) +class XiaomiMiioNumberDescription(NumberEntityDescription): """A class that describes number entities.""" method: str - -@dataclass(frozen=True) -class XiaomiMiioNumberDescription(NumberEntityDescription, XiaomiMiioNumberMixin): - """A class that describes number entities.""" - available_with_device_off: bool = True diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 85e9e77e120..7720120502f 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -220,21 +220,14 @@ MODEL_TO_FEATURES_MAP = { } -@dataclass(frozen=True) -class XiaomiMiioSwitchRequiredKeyMixin: +@dataclass(frozen=True, kw_only=True) +class XiaomiMiioSwitchDescription(SwitchEntityDescription): """A class that describes switch entities.""" feature: int method_on: str method_off: str - -@dataclass(frozen=True) -class XiaomiMiioSwitchDescription( - SwitchEntityDescription, XiaomiMiioSwitchRequiredKeyMixin -): - """A class that describes switch entities.""" - available_with_device_off: bool = True From 105fca2212a8c6508aa81c7768c2d7a8bc3a45fa Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:52:42 +0100 Subject: [PATCH 0730/1691] Remove entity description mixin in Whirlpool (#112972) --- homeassistant/components/whirlpool/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/whirlpool/sensor.py b/homeassistant/components/whirlpool/sensor.py index ff92a059e96..a710dfb81a8 100644 --- a/homeassistant/components/whirlpool/sensor.py +++ b/homeassistant/components/whirlpool/sensor.py @@ -90,20 +90,13 @@ def washer_state(washer: WasherDryer) -> str | None: return MACHINE_STATE.get(machine_state, None) -@dataclass(frozen=True) -class WhirlpoolSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class WhirlpoolSensorEntityDescription(SensorEntityDescription): + """Describes Whirlpool Washer sensor entity.""" value_fn: Callable -@dataclass(frozen=True) -class WhirlpoolSensorEntityDescription( - SensorEntityDescription, WhirlpoolSensorEntityDescriptionMixin -): - """Describes Whirlpool Washer sensor entity.""" - - SENSORS: tuple[WhirlpoolSensorEntityDescription, ...] = ( WhirlpoolSensorEntityDescription( key="state", From 90769b460dfc7c0b91104da8f0d3c646e3e76494 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:53:18 +0100 Subject: [PATCH 0731/1691] Remove entity description mixin in VeSync (#112965) --- homeassistant/components/vesync/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/vesync/sensor.py b/homeassistant/components/vesync/sensor.py index 87a87f049c2..d594ec5cb82 100644 --- a/homeassistant/components/vesync/sensor.py +++ b/homeassistant/components/vesync/sensor.py @@ -36,19 +36,12 @@ from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_ _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class VeSyncSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class VeSyncSensorEntityDescription(SensorEntityDescription): + """Describe VeSync sensor entity.""" value_fn: Callable[[VeSyncAirBypass | VeSyncOutlet | VeSyncSwitch], StateType] - -@dataclass(frozen=True) -class VeSyncSensorEntityDescription( - SensorEntityDescription, VeSyncSensorEntityDescriptionMixin -): - """Describe VeSync sensor entity.""" - exists_fn: Callable[[VeSyncAirBypass | VeSyncOutlet | VeSyncSwitch], bool] = ( lambda _: True ) From 39bfb2b5baebd1b5e51a93240adf70df450303bc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:53:40 +0100 Subject: [PATCH 0732/1691] Remove entity description mixin in Venstar (#112964) --- homeassistant/components/venstar/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/venstar/sensor.py b/homeassistant/components/venstar/sensor.py index 8b0dbc7a7e6..c06816ad0af 100644 --- a/homeassistant/components/venstar/sensor.py +++ b/homeassistant/components/venstar/sensor.py @@ -66,20 +66,15 @@ SCHEDULE_PARTS: dict[int, str] = { } -@dataclass(frozen=True) -class VenstarSensorTypeMixin: - """Mixin for sensor required keys.""" +@dataclass(frozen=True, kw_only=True) +class VenstarSensorEntityDescription(SensorEntityDescription): + """Base description of a Sensor entity.""" value_fn: Callable[[VenstarDataUpdateCoordinator, str], Any] name_fn: Callable[[str], str] uom_fn: Callable[[Any], str | None] -@dataclass(frozen=True) -class VenstarSensorEntityDescription(SensorEntityDescription, VenstarSensorTypeMixin): - """Base description of a Sensor entity.""" - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, From b5c5db9ca0e8df9f666a2142ab6ff4a247f12c59 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:54:00 +0100 Subject: [PATCH 0733/1691] Remove entity description mixin in Vallox (#112963) --- homeassistant/components/vallox/binary_sensor.py | 13 +++---------- homeassistant/components/vallox/number.py | 11 +++-------- homeassistant/components/vallox/switch.py | 11 +++-------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/vallox/binary_sensor.py b/homeassistant/components/vallox/binary_sensor.py index 9ba81dcd4d1..fbcfa403738 100644 --- a/homeassistant/components/vallox/binary_sensor.py +++ b/homeassistant/components/vallox/binary_sensor.py @@ -42,20 +42,13 @@ class ValloxBinarySensorEntity(ValloxEntity, BinarySensorEntity): return self.coordinator.data.get(self.entity_description.metric_key) == 1 -@dataclass(frozen=True) -class ValloxMetricKeyMixin: - """Dataclass to allow defining metric_key without a default value.""" +@dataclass(frozen=True, kw_only=True) +class ValloxBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Vallox binary sensor entity.""" metric_key: str -@dataclass(frozen=True) -class ValloxBinarySensorEntityDescription( - BinarySensorEntityDescription, ValloxMetricKeyMixin -): - """Describes Vallox binary sensor entity.""" - - BINARY_SENSOR_ENTITIES: tuple[ValloxBinarySensorEntityDescription, ...] = ( ValloxBinarySensorEntityDescription( key="post_heater", diff --git a/homeassistant/components/vallox/number.py b/homeassistant/components/vallox/number.py index c89cdbf17b9..83316a13645 100644 --- a/homeassistant/components/vallox/number.py +++ b/homeassistant/components/vallox/number.py @@ -59,18 +59,13 @@ class ValloxNumberEntity(ValloxEntity, NumberEntity): await self.coordinator.async_request_refresh() -@dataclass(frozen=True) -class ValloxMetricMixin: - """Holds Vallox metric key.""" +@dataclass(frozen=True, kw_only=True) +class ValloxNumberEntityDescription(NumberEntityDescription): + """Describes Vallox number entity.""" metric_key: str -@dataclass(frozen=True) -class ValloxNumberEntityDescription(NumberEntityDescription, ValloxMetricMixin): - """Describes Vallox number entity.""" - - NUMBER_ENTITIES: tuple[ValloxNumberEntityDescription, ...] = ( ValloxNumberEntityDescription( key="supply_air_target_home", diff --git a/homeassistant/components/vallox/switch.py b/homeassistant/components/vallox/switch.py index 8adb8274a64..90e2311bf95 100644 --- a/homeassistant/components/vallox/switch.py +++ b/homeassistant/components/vallox/switch.py @@ -62,18 +62,13 @@ class ValloxSwitchEntity(ValloxEntity, SwitchEntity): await self.coordinator.async_request_refresh() -@dataclass(frozen=True) -class ValloxMetricKeyMixin: - """Dataclass to allow defining metric_key without a default value.""" +@dataclass(frozen=True, kw_only=True) +class ValloxSwitchEntityDescription(SwitchEntityDescription): + """Describes Vallox switch entity.""" metric_key: str -@dataclass(frozen=True) -class ValloxSwitchEntityDescription(SwitchEntityDescription, ValloxMetricKeyMixin): - """Describes Vallox switch entity.""" - - SWITCH_ENTITIES: tuple[ValloxSwitchEntityDescription, ...] = ( ValloxSwitchEntityDescription( key="bypass_locked", From b4dfe455cbbac78193f2400b4ae9bed5dee7e40e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:54:46 +0100 Subject: [PATCH 0734/1691] Remove entity description mixin in Tuya (#112959) --- homeassistant/components/tuya/climate.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index 3f0626ba82c..3be80193beb 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -40,20 +40,13 @@ TUYA_HVAC_TO_HA = { } -@dataclass(frozen=True) -class TuyaClimateSensorDescriptionMixin: - """Define an entity description mixin for climate entities.""" +@dataclass(frozen=True, kw_only=True) +class TuyaClimateEntityDescription(ClimateEntityDescription): + """Describe an Tuya climate entity.""" switch_only_hvac_mode: HVACMode -@dataclass(frozen=True) -class TuyaClimateEntityDescription( - ClimateEntityDescription, TuyaClimateSensorDescriptionMixin -): - """Describe an Tuya climate entity.""" - - CLIMATE_DESCRIPTIONS: dict[str, TuyaClimateEntityDescription] = { # Air conditioner # https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n From d723d5815ca6b83fac1575bc67d5a5d8feffd72c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:55:08 +0100 Subject: [PATCH 0735/1691] Remove entity description mixin in Transmission (#112958) --- homeassistant/components/transmission/switch.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/transmission/switch.py b/homeassistant/components/transmission/switch.py index 1f95fc492fe..8e79d8246e0 100644 --- a/homeassistant/components/transmission/switch.py +++ b/homeassistant/components/transmission/switch.py @@ -18,22 +18,15 @@ from .coordinator import TransmissionDataUpdateCoordinator _LOGGING = logging.getLogger(__name__) -@dataclass(frozen=True) -class TransmissionSwitchEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class TransmissionSwitchEntityDescription(SwitchEntityDescription): + """Entity description class for Transmission switches.""" is_on_func: Callable[[TransmissionDataUpdateCoordinator], bool | None] on_func: Callable[[TransmissionDataUpdateCoordinator], None] off_func: Callable[[TransmissionDataUpdateCoordinator], None] -@dataclass(frozen=True) -class TransmissionSwitchEntityDescription( - SwitchEntityDescription, TransmissionSwitchEntityDescriptionMixin -): - """Entity description class for Transmission switches.""" - - SWITCH_TYPES: tuple[TransmissionSwitchEntityDescription, ...] = ( TransmissionSwitchEntityDescription( key="on_off", From 276b8147d3099a212c5e1f374ee8c80aeb60abac Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:55:33 +0100 Subject: [PATCH 0736/1691] Remove entity description mixin in Tolo (#112951) --- homeassistant/components/tolo/number.py | 13 +++---------- homeassistant/components/tolo/sensor.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/tolo/number.py b/homeassistant/components/tolo/number.py index 257b96edd6e..6a9783aacd4 100644 --- a/homeassistant/components/tolo/number.py +++ b/homeassistant/components/tolo/number.py @@ -19,20 +19,13 @@ from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator from .const import DOMAIN, FAN_TIMER_MAX, POWER_TIMER_MAX, SALT_BATH_TIMER_MAX -@dataclass(frozen=True) -class ToloNumberEntityDescriptionBase: - """Required values when describing TOLO Number entities.""" +@dataclass(frozen=True, kw_only=True) +class ToloNumberEntityDescription(NumberEntityDescription): + """Class describing TOLO Number entities.""" getter: Callable[[SettingsInfo], int | None] setter: Callable[[ToloClient, int | None], Any] - -@dataclass(frozen=True) -class ToloNumberEntityDescription( - NumberEntityDescription, ToloNumberEntityDescriptionBase -): - """Class describing TOLO Number entities.""" - entity_category = EntityCategory.CONFIG native_min_value = 0 native_step = 1 diff --git a/homeassistant/components/tolo/sensor.py b/homeassistant/components/tolo/sensor.py index 5af8abfb117..27aa331c9cf 100644 --- a/homeassistant/components/tolo/sensor.py +++ b/homeassistant/components/tolo/sensor.py @@ -27,20 +27,13 @@ from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator from .const import DOMAIN -@dataclass(frozen=True) -class ToloSensorEntityDescriptionBase: - """Required values when describing TOLO Sensor entities.""" +@dataclass(frozen=True, kw_only=True) +class ToloSensorEntityDescription(SensorEntityDescription): + """Class describing TOLO Sensor entities.""" getter: Callable[[StatusInfo], int | None] availability_checker: Callable[[SettingsInfo, StatusInfo], bool] | None - -@dataclass(frozen=True) -class ToloSensorEntityDescription( - SensorEntityDescription, ToloSensorEntityDescriptionBase -): - """Class describing TOLO Sensor entities.""" - state_class = SensorStateClass.MEASUREMENT From 1bc6277c47ec091fd197882e5d86a6406e1f609e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:56:03 +0100 Subject: [PATCH 0737/1691] Remove entity description mixin in Tautulli (#112949) --- homeassistant/components/tautulli/sensor.py | 26 +++++---------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/tautulli/sensor.py b/homeassistant/components/tautulli/sensor.py index f28aaddde25..f0d274bbe12 100644 --- a/homeassistant/components/tautulli/sensor.py +++ b/homeassistant/components/tautulli/sensor.py @@ -44,20 +44,13 @@ def get_top_stats( return value -@dataclass(frozen=True) -class TautulliSensorEntityMixin: - """Mixin for Tautulli sensor.""" +@dataclass(frozen=True, kw_only=True) +class TautulliSensorEntityDescription(SensorEntityDescription): + """Describes a Tautulli sensor.""" value_fn: Callable[[PyTautulliApiHomeStats, PyTautulliApiActivity, str], StateType] -@dataclass(frozen=True) -class TautulliSensorEntityDescription( - SensorEntityDescription, TautulliSensorEntityMixin -): - """Describes a Tautulli sensor.""" - - SENSOR_TYPES: tuple[TautulliSensorEntityDescription, ...] = ( TautulliSensorEntityDescription( key="watching_count", @@ -145,20 +138,13 @@ SENSOR_TYPES: tuple[TautulliSensorEntityDescription, ...] = ( ) -@dataclass(frozen=True) -class TautulliSessionSensorEntityMixin: - """Mixin for Tautulli session sensor.""" +@dataclass(frozen=True, kw_only=True) +class TautulliSessionSensorEntityDescription(SensorEntityDescription): + """Describes a Tautulli session sensor.""" value_fn: Callable[[PyTautulliApiSession], StateType] -@dataclass(frozen=True) -class TautulliSessionSensorEntityDescription( - SensorEntityDescription, TautulliSessionSensorEntityMixin -): - """Describes a Tautulli session sensor.""" - - SESSION_SENSOR_TYPES: tuple[TautulliSessionSensorEntityDescription, ...] = ( TautulliSessionSensorEntityDescription( key="state", From ff88c46658b673a113c1a8dd4261c4be6ee38d70 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:56:22 +0100 Subject: [PATCH 0738/1691] Remove entity description mixin in Steamist (#112944) --- homeassistant/components/steamist/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/steamist/sensor.py b/homeassistant/components/steamist/sensor.py index 557952767ea..7c24d015513 100644 --- a/homeassistant/components/steamist/sensor.py +++ b/homeassistant/components/steamist/sensor.py @@ -31,20 +31,13 @@ UNIT_MAPPINGS = { } -@dataclass(frozen=True) -class SteamistSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class SteamistSensorEntityDescription(SensorEntityDescription): + """Describes a Steamist sensor entity.""" value_fn: Callable[[SteamistStatus], int | None] -@dataclass(frozen=True) -class SteamistSensorEntityDescription( - SensorEntityDescription, SteamistSensorEntityDescriptionMixin -): - """Describes a Steamist sensor entity.""" - - SENSORS: tuple[SteamistSensorEntityDescription, ...] = ( SteamistSensorEntityDescription( key=_KEY_MINUTES_REMAIN, From f5c873230729d57cbb4d4f39c8911ca21b62fbe3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:56:37 +0100 Subject: [PATCH 0739/1691] Remove entity description mixin in Solaredge (#112942) --- homeassistant/components/solaredge/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 0099812c6fc..5ec65a3b9a5 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -34,20 +34,13 @@ from .coordinator import ( ) -@dataclass(frozen=True) -class SolarEdgeSensorEntityRequiredKeyMixin: - """Sensor entity description with json_key for SolarEdge.""" +@dataclass(frozen=True, kw_only=True) +class SolarEdgeSensorEntityDescription(SensorEntityDescription): + """Sensor entity description for SolarEdge.""" json_key: str -@dataclass(frozen=True) -class SolarEdgeSensorEntityDescription( - SensorEntityDescription, SolarEdgeSensorEntityRequiredKeyMixin -): - """Sensor entity description for SolarEdge.""" - - SENSOR_TYPES = [ SolarEdgeSensorEntityDescription( key="lifetime_energy", From fb23d5e6fbf46097c2809badb345a209f801d246 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:57:01 +0100 Subject: [PATCH 0740/1691] Remove entity description mixin in Smappee (#112941) --- homeassistant/components/smappee/sensor.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index d2f94ed9a75..c984d936b06 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -19,26 +19,21 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -@dataclass(frozen=True) -class SmappeeRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class SmappeeSensorEntityDescription(SensorEntityDescription): + """Describes Smappee sensor entity.""" sensor_id: str -@dataclass(frozen=True) -class SmappeeSensorEntityDescription(SensorEntityDescription, SmappeeRequiredKeysMixin): - """Describes Smappee sensor entity.""" - - -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SmappeePollingSensorEntityDescription(SmappeeSensorEntityDescription): """Describes Smappee sensor entity.""" local_polling: bool = False -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class SmappeeVoltageSensorEntityDescription(SmappeeSensorEntityDescription): """Describes Smappee sensor entity.""" From 37e0a9d9c75218e4ba8de154c0967a0592b56f7c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:57:22 +0100 Subject: [PATCH 0741/1691] Remove entity description mixin in SleepIQ (#112940) --- homeassistant/components/sleepiq/button.py | 13 +++---------- homeassistant/components/sleepiq/number.py | 13 +++---------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/sleepiq/button.py b/homeassistant/components/sleepiq/button.py index f82de232276..94b010066c9 100644 --- a/homeassistant/components/sleepiq/button.py +++ b/homeassistant/components/sleepiq/button.py @@ -18,20 +18,13 @@ from .coordinator import SleepIQData from .entity import SleepIQEntity -@dataclass(frozen=True) -class SleepIQButtonEntityDescriptionMixin: - """Describes a SleepIQ Button entity.""" +@dataclass(frozen=True, kw_only=True) +class SleepIQButtonEntityDescription(ButtonEntityDescription): + """Class to describe a Button entity.""" press_action: Callable[[SleepIQBed], Any] -@dataclass(frozen=True) -class SleepIQButtonEntityDescription( - ButtonEntityDescription, SleepIQButtonEntityDescriptionMixin -): - """Class to describe a Button entity.""" - - ENTITY_DESCRIPTIONS = [ SleepIQButtonEntityDescription( key="calibrate", diff --git a/homeassistant/components/sleepiq/number.py b/homeassistant/components/sleepiq/number.py index f42bed004fc..54912fc2a77 100644 --- a/homeassistant/components/sleepiq/number.py +++ b/homeassistant/components/sleepiq/number.py @@ -31,9 +31,9 @@ from .coordinator import SleepIQData, SleepIQDataUpdateCoordinator from .entity import SleepIQBedEntity, sleeper_for_side -@dataclass(frozen=True) -class SleepIQNumberEntityDescriptionMixin: - """Mixin to describe a SleepIQ number entity.""" +@dataclass(frozen=True, kw_only=True) +class SleepIQNumberEntityDescription(NumberEntityDescription): + """Class to describe a SleepIQ number entity.""" value_fn: Callable[[Any], float] set_value_fn: Callable[[Any, int], Coroutine[None, None, None]] @@ -41,13 +41,6 @@ class SleepIQNumberEntityDescriptionMixin: get_unique_id_fn: Callable[[SleepIQBed, Any], str] -@dataclass(frozen=True) -class SleepIQNumberEntityDescription( - NumberEntityDescription, SleepIQNumberEntityDescriptionMixin -): - """Class to describe a SleepIQ number entity.""" - - async def _async_set_firmness(sleeper: SleepIQSleeper, firmness: int) -> None: await sleeper.set_sleepnumber(firmness) From a1a0738e58147dadc4c787048204308ad3601588 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:57:38 +0100 Subject: [PATCH 0742/1691] Remove entity description mixin in Skybell (#112939) --- homeassistant/components/skybell/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/skybell/sensor.py b/homeassistant/components/skybell/sensor.py index 04fb33d9be3..5f0df77ecfa 100644 --- a/homeassistant/components/skybell/sensor.py +++ b/homeassistant/components/skybell/sensor.py @@ -23,20 +23,13 @@ from homeassistant.helpers.typing import StateType from .entity import DOMAIN, SkybellEntity -@dataclass(frozen=True) -class SkybellSensorEntityDescriptionMixIn: - """Mixin for Skybell sensor.""" +@dataclass(frozen=True, kw_only=True) +class SkybellSensorEntityDescription(SensorEntityDescription): + """Class to describe a Skybell sensor.""" value_fn: Callable[[SkybellDevice], StateType | datetime] -@dataclass(frozen=True) -class SkybellSensorEntityDescription( - SensorEntityDescription, SkybellSensorEntityDescriptionMixIn -): - """Class to describe a Skybell sensor.""" - - SENSOR_TYPES: tuple[SkybellSensorEntityDescription, ...] = ( SkybellSensorEntityDescription( key="chime_level", From 22f6558647e5d1dd08fa9c155d4ba4b6f99450fd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:58:21 +0100 Subject: [PATCH 0743/1691] Remove entity description mixin in Roomba (#112932) --- homeassistant/components/roomba/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index bf76fc1c39f..6e043d237f3 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -27,20 +27,13 @@ from .irobot_base import IRobotEntity from .models import RoombaData -@dataclass(frozen=True) -class RoombaSensorEntityDescriptionMixin: - """Mixin for describing Roomba data.""" +@dataclass(frozen=True, kw_only=True) +class RoombaSensorEntityDescription(SensorEntityDescription): + """Immutable class for describing Roomba data.""" value_fn: Callable[[IRobotEntity], StateType] -@dataclass(frozen=True) -class RoombaSensorEntityDescription( - SensorEntityDescription, RoombaSensorEntityDescriptionMixin -): - """Immutable class for describing Roomba data.""" - - SENSORS: list[RoombaSensorEntityDescription] = [ RoombaSensorEntityDescription( key="battery", From b837a969d819ddaacaa2f6e054d7b1d252fade0e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:59:02 +0100 Subject: [PATCH 0744/1691] Remove entity description mixin in Ring (#112928) --- homeassistant/components/ring/binary_sensor.py | 13 +++---------- homeassistant/components/ring/sensor.py | 11 +++-------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index 1c39e484f97..599753cc1cc 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -20,20 +20,13 @@ from .coordinator import RingNotificationsCoordinator from .entity import RingEntity -@dataclass(frozen=True) -class RingRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class RingBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Ring binary sensor entity.""" category: list[str] -@dataclass(frozen=True) -class RingBinarySensorEntityDescription( - BinarySensorEntityDescription, RingRequiredKeysMixin -): - """Describes Ring binary sensor entity.""" - - BINARY_SENSOR_TYPES: tuple[RingBinarySensorEntityDescription, ...] = ( RingBinarySensorEntityDescription( key="ding", diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 0c9c0196f11..b6b37a8a669 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -143,18 +143,13 @@ class HistoryRingSensor(RingSensor): return attrs -@dataclass(frozen=True) -class RingRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class RingSensorEntityDescription(SensorEntityDescription): + """Describes Ring sensor entity.""" category: list[str] cls: type[RingSensor] - -@dataclass(frozen=True) -class RingSensorEntityDescription(SensorEntityDescription, RingRequiredKeysMixin): - """Describes Ring sensor entity.""" - kind: str | None = None From 3c217d737e89e8dd4e529f835bd01b5a0733bd69 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 11:59:48 +0100 Subject: [PATCH 0745/1691] Remove entity description mixin in Roborock (#112930) --- .../components/roborock/binary_sensor.py | 13 +++---------- homeassistant/components/roborock/button.py | 13 +++---------- homeassistant/components/roborock/number.py | 13 +++---------- homeassistant/components/roborock/select.py | 15 ++++----------- homeassistant/components/roborock/sensor.py | 13 +++---------- homeassistant/components/roborock/switch.py | 13 +++---------- homeassistant/components/roborock/time.py | 11 +++-------- 7 files changed, 22 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/roborock/binary_sensor.py b/homeassistant/components/roborock/binary_sensor.py index 2c903e0bcc9..00716207f7a 100644 --- a/homeassistant/components/roborock/binary_sensor.py +++ b/homeassistant/components/roborock/binary_sensor.py @@ -23,20 +23,13 @@ from .coordinator import RoborockDataUpdateCoordinator from .device import RoborockCoordinatedEntity -@dataclass(frozen=True) -class RoborockBinarySensorDescriptionMixin: - """A class that describes binary sensor entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockBinarySensorDescription(BinarySensorEntityDescription): + """A class that describes Roborock binary sensors.""" value_fn: Callable[[DeviceProp], bool | int | None] -@dataclass(frozen=True) -class RoborockBinarySensorDescription( - BinarySensorEntityDescription, RoborockBinarySensorDescriptionMixin -): - """A class that describes Roborock binary sensors.""" - - BINARY_SENSOR_DESCRIPTIONS = [ RoborockBinarySensorDescription( key="dry_status", diff --git a/homeassistant/components/roborock/button.py b/homeassistant/components/roborock/button.py index 4a37f1b3208..fe6dfabb56c 100644 --- a/homeassistant/components/roborock/button.py +++ b/homeassistant/components/roborock/button.py @@ -18,21 +18,14 @@ from .coordinator import RoborockDataUpdateCoordinator from .device import RoborockEntity -@dataclass(frozen=True) -class RoborockButtonDescriptionMixin: - """Define an entity description mixin for button entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockButtonDescription(ButtonEntityDescription): + """Describes a Roborock button entity.""" command: RoborockCommand param: list | dict | None -@dataclass(frozen=True) -class RoborockButtonDescription( - ButtonEntityDescription, RoborockButtonDescriptionMixin -): - """Describes a Roborock button entity.""" - - CONSUMABLE_BUTTON_DESCRIPTIONS = [ RoborockButtonDescription( key="reset_sensor_consumable", diff --git a/homeassistant/components/roborock/number.py b/homeassistant/components/roborock/number.py index 20f4c76fe75..09030ef8500 100644 --- a/homeassistant/components/roborock/number.py +++ b/homeassistant/components/roborock/number.py @@ -24,9 +24,9 @@ from .device import RoborockEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class RoborockNumberDescriptionMixin: - """Define an entity description mixin for button entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockNumberDescription(NumberEntityDescription): + """Class to describe a Roborock number entity.""" # Gets the status of the switch cache_key: CacheableAttribute @@ -34,13 +34,6 @@ class RoborockNumberDescriptionMixin: update_value: Callable[[AttributeCache, float], Coroutine[Any, Any, dict]] -@dataclass(frozen=True) -class RoborockNumberDescription( - NumberEntityDescription, RoborockNumberDescriptionMixin -): - """Class to describe an Roborock number entity.""" - - NUMBER_DESCRIPTIONS: list[RoborockNumberDescription] = [ RoborockNumberDescription( key="volume", diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index b6e55708371..fa7f4250804 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -19,9 +19,9 @@ from .coordinator import RoborockDataUpdateCoordinator from .device import RoborockCoordinatedEntity -@dataclass(frozen=True) -class RoborockSelectDescriptionMixin: - """Define an entity description mixin for select entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockSelectDescription(SelectEntityDescription): + """Class to describe a Roborock select entity.""" # The command that the select entity will send to the api. api_command: RoborockCommand @@ -29,16 +29,9 @@ class RoborockSelectDescriptionMixin: value_fn: Callable[[Status], str | None] # Gets all options of the select entity. options_lambda: Callable[[Status], list[str] | None] - # Takes the value from the select entiy and converts it for the api. + # Takes the value from the select entity and converts it for the api. parameter_lambda: Callable[[str, Status], list[int]] - -@dataclass(frozen=True) -class RoborockSelectDescription( - SelectEntityDescription, RoborockSelectDescriptionMixin -): - """Class to describe an Roborock select entity.""" - protocol_listener: RoborockDataProtocol | None = None diff --git a/homeassistant/components/roborock/sensor.py b/homeassistant/components/roborock/sensor.py index e1f87ccf603..acee1688cc7 100644 --- a/homeassistant/components/roborock/sensor.py +++ b/homeassistant/components/roborock/sensor.py @@ -37,19 +37,12 @@ from .coordinator import RoborockDataUpdateCoordinator from .device import RoborockCoordinatedEntity -@dataclass(frozen=True) -class RoborockSensorDescriptionMixin: - """A class that describes sensor entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockSensorDescription(SensorEntityDescription): + """A class that describes Roborock sensors.""" value_fn: Callable[[DeviceProp], StateType | datetime.datetime] - -@dataclass(frozen=True) -class RoborockSensorDescription( - SensorEntityDescription, RoborockSensorDescriptionMixin -): - """A class that describes Roborock sensors.""" - protocol_listener: RoborockDataProtocol | None = None diff --git a/homeassistant/components/roborock/switch.py b/homeassistant/components/roborock/switch.py index 6c9c5e18bf0..9c7ca3cdcae 100644 --- a/homeassistant/components/roborock/switch.py +++ b/homeassistant/components/roborock/switch.py @@ -25,9 +25,9 @@ from .device import RoborockEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class RoborockSwitchDescriptionMixin: - """Define an entity description mixin for switch entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockSwitchDescription(SwitchEntityDescription): + """Class to describe a Roborock switch entity.""" # Gets the status of the switch cache_key: CacheableAttribute @@ -37,13 +37,6 @@ class RoborockSwitchDescriptionMixin: attribute: str -@dataclass(frozen=True) -class RoborockSwitchDescription( - SwitchEntityDescription, RoborockSwitchDescriptionMixin -): - """Class to describe an Roborock switch entity.""" - - SWITCH_DESCRIPTIONS: list[RoborockSwitchDescription] = [ RoborockSwitchDescription( cache_key=CacheableAttribute.child_lock_status, diff --git a/homeassistant/components/roborock/time.py b/homeassistant/components/roborock/time.py index e142713213c..9a3cac86425 100644 --- a/homeassistant/components/roborock/time.py +++ b/homeassistant/components/roborock/time.py @@ -26,9 +26,9 @@ from .device import RoborockEntity _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class RoborockTimeDescriptionMixin: - """Define an entity description mixin for time entities.""" +@dataclass(frozen=True, kw_only=True) +class RoborockTimeDescription(TimeEntityDescription): + """Class to describe a Roborock time entity.""" # Gets the status of the switch cache_key: CacheableAttribute @@ -38,11 +38,6 @@ class RoborockTimeDescriptionMixin: get_value: Callable[[AttributeCache], datetime.time] -@dataclass(frozen=True) -class RoborockTimeDescription(TimeEntityDescription, RoborockTimeDescriptionMixin): - """Class to describe an Roborock time entity.""" - - TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [ RoborockTimeDescription( key="dnd_start_time", From 7f3a850ca55c50269d156643a597e4c6ab2b5ba3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:00:16 +0100 Subject: [PATCH 0746/1691] Remove entity description mixin in Meteo-France (#112907) --- homeassistant/components/meteo_france/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/meteo_france/sensor.py b/homeassistant/components/meteo_france/sensor.py index 2587c5d4337..23ea6bb1500 100644 --- a/homeassistant/components/meteo_france/sensor.py +++ b/homeassistant/components/meteo_france/sensor.py @@ -52,20 +52,13 @@ from .const import ( _DataT = TypeVar("_DataT", bound=Rain | Forecast | CurrentPhenomenons) -@dataclass(frozen=True) -class MeteoFranceRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class MeteoFranceSensorEntityDescription(SensorEntityDescription): + """Describes Meteo-France sensor entity.""" data_path: str -@dataclass(frozen=True) -class MeteoFranceSensorEntityDescription( - SensorEntityDescription, MeteoFranceRequiredKeysMixin -): - """Describes Meteo-France sensor entity.""" - - SENSOR_TYPES: tuple[MeteoFranceSensorEntityDescription, ...] = ( MeteoFranceSensorEntityDescription( key="pressure", From 54abc4935f63c9964a38aef090f1c96a48c75b53 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:00:35 +0100 Subject: [PATCH 0747/1691] Remove entity description mixin in Rituals Perfume Genie (#112929) --- .../components/rituals_perfume_genie/switch.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/rituals_perfume_genie/switch.py b/homeassistant/components/rituals_perfume_genie/switch.py index 6b88f4f26c1..b5828f5ca07 100644 --- a/homeassistant/components/rituals_perfume_genie/switch.py +++ b/homeassistant/components/rituals_perfume_genie/switch.py @@ -18,22 +18,15 @@ from .coordinator import RitualsDataUpdateCoordinator from .entity import DiffuserEntity -@dataclass(frozen=True) -class RitualsEntityDescriptionMixin: - """Mixin values for Rituals entities.""" +@dataclass(frozen=True, kw_only=True) +class RitualsSwitchEntityDescription(SwitchEntityDescription): + """Class describing Rituals switch entities.""" is_on_fn: Callable[[Diffuser], bool] turn_on_fn: Callable[[Diffuser], Awaitable[None]] turn_off_fn: Callable[[Diffuser], Awaitable[None]] -@dataclass(frozen=True) -class RitualsSwitchEntityDescription( - SwitchEntityDescription, RitualsEntityDescriptionMixin -): - """Class describing Rituals switch entities.""" - - ENTITY_DESCRIPTIONS = ( RitualsSwitchEntityDescription( key="is_on", From 7730c3bcf3349384ceee30a8755ec25c0d40f519 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:01:01 +0100 Subject: [PATCH 0748/1691] Remove entity description mixin in Renson (#112926) --- homeassistant/components/renson/binary_sensor.py | 13 +++---------- homeassistant/components/renson/button.py | 13 +++---------- homeassistant/components/renson/sensor.py | 13 +++---------- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/renson/binary_sensor.py b/homeassistant/components/renson/binary_sensor.py index 90258a9ae4b..46f832ed15c 100644 --- a/homeassistant/components/renson/binary_sensor.py +++ b/homeassistant/components/renson/binary_sensor.py @@ -31,20 +31,13 @@ from .coordinator import RensonCoordinator from .entity import RensonEntity -@dataclass(frozen=True) -class RensonBinarySensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class RensonBinarySensorEntityDescription(BinarySensorEntityDescription): + """Description of binary sensor.""" field: FieldEnum -@dataclass(frozen=True) -class RensonBinarySensorEntityDescription( - BinarySensorEntityDescription, RensonBinarySensorEntityDescriptionMixin -): - """Description of binary sensor.""" - - BINARY_SENSORS: tuple[RensonBinarySensorEntityDescription, ...] = ( RensonBinarySensorEntityDescription( translation_key="frost_protection_active", diff --git a/homeassistant/components/renson/button.py b/homeassistant/components/renson/button.py index c9a03a5bffb..02278a0d6f6 100644 --- a/homeassistant/components/renson/button.py +++ b/homeassistant/components/renson/button.py @@ -22,20 +22,13 @@ from .const import DOMAIN from .entity import RensonEntity -@dataclass(frozen=True) -class RensonButtonEntityDescriptionMixin: - """Action function called on press.""" +@dataclass(frozen=True, kw_only=True) +class RensonButtonEntityDescription(ButtonEntityDescription): + """Class describing Renson button entity.""" action_fn: Callable[[RensonVentilation], None] -@dataclass(frozen=True) -class RensonButtonEntityDescription( - ButtonEntityDescription, RensonButtonEntityDescriptionMixin -): - """Class describing Renson button entity.""" - - ENTITY_DESCRIPTIONS: tuple[RensonButtonEntityDescription, ...] = ( RensonButtonEntityDescription( key="sync_time", diff --git a/homeassistant/components/renson/sensor.py b/homeassistant/components/renson/sensor.py index 78ece708c4c..1df62e12312 100644 --- a/homeassistant/components/renson/sensor.py +++ b/homeassistant/components/renson/sensor.py @@ -51,21 +51,14 @@ from .coordinator import RensonCoordinator from .entity import RensonEntity -@dataclass(frozen=True) -class RensonSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class RensonSensorEntityDescription(SensorEntityDescription): + """Description of a Renson sensor.""" field: FieldEnum raw_format: bool -@dataclass(frozen=True) -class RensonSensorEntityDescription( - SensorEntityDescription, RensonSensorEntityDescriptionMixin -): - """Description of a Renson sensor.""" - - SENSORS: tuple[RensonSensorEntityDescription, ...] = ( RensonSensorEntityDescription( key="CO2_QUALITY_FIELD", From d29418ebf53d0c0ad8197248e08a8ab8f94a155e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:01:40 +0100 Subject: [PATCH 0749/1691] Remove entity description mixin in Pure Energie (#112923) --- homeassistant/components/pure_energie/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/pure_energie/sensor.py b/homeassistant/components/pure_energie/sensor.py index fa3bed72df4..7f2c36bc4f6 100644 --- a/homeassistant/components/pure_energie/sensor.py +++ b/homeassistant/components/pure_energie/sensor.py @@ -23,20 +23,13 @@ from . import PureEnergieData, PureEnergieDataUpdateCoordinator from .const import DOMAIN -@dataclass(frozen=True) -class PureEnergieSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class PureEnergieSensorEntityDescription(SensorEntityDescription): + """Describes a Pure Energie sensor entity.""" value_fn: Callable[[PureEnergieData], int | float] -@dataclass(frozen=True) -class PureEnergieSensorEntityDescription( - SensorEntityDescription, PureEnergieSensorEntityDescriptionMixin -): - """Describes a Pure Energie sensor entity.""" - - SENSORS: tuple[PureEnergieSensorEntityDescription, ...] = ( PureEnergieSensorEntityDescription( key="power_flow", From d0a17811875557ef1ec97b5ca071eff473ddde01 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:02:26 +0100 Subject: [PATCH 0750/1691] Remove entity description in Point (#112921) --- homeassistant/components/point/sensor.py | 37 +++++------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 101371b0822..f648bb4daf9 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -2,7 +2,6 @@ from __future__ import annotations -from dataclasses import dataclass import logging from homeassistant.components.sensor import ( @@ -24,36 +23,22 @@ from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class MinutPointRequiredKeysMixin: - """Mixin for required keys.""" - - precision: int - - -@dataclass(frozen=True) -class MinutPointSensorEntityDescription( - SensorEntityDescription, MinutPointRequiredKeysMixin -): - """Describes MinutPoint sensor entity.""" - - -SENSOR_TYPES: tuple[MinutPointSensorEntityDescription, ...] = ( - MinutPointSensorEntityDescription( +SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="temperature", - precision=1, + suggested_display_precision=1, device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), - MinutPointSensorEntityDescription( + SensorEntityDescription( key="humidity", - precision=1, + suggested_display_precision=1, device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement=PERCENTAGE, ), - MinutPointSensorEntityDescription( + SensorEntityDescription( key="sound", - precision=1, + suggested_display_precision=1, device_class=SensorDeviceClass.SOUND_PRESSURE, native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A, ), @@ -86,10 +71,8 @@ async def async_setup_entry( class MinutPointSensor(MinutPointEntity, SensorEntity): """The platform class required by Home Assistant.""" - entity_description: MinutPointSensorEntityDescription - def __init__( - self, point_client, device_id, description: MinutPointSensorEntityDescription + self, point_client, device_id, description: SensorEntityDescription ) -> None: """Initialize the sensor.""" super().__init__(point_client, device_id, description.device_class) @@ -100,9 +83,5 @@ class MinutPointSensor(MinutPointEntity, SensorEntity): _LOGGER.debug("Update sensor value for %s", self) if self.is_updated: self._attr_native_value = await self.device.sensor(self.device_class) - if self.native_value is not None: - self._attr_native_value = round( - self.native_value, self.entity_description.precision - ) self._updated = parse_datetime(self.device.last_update) self.async_write_ha_state() From 8391dd39d9cc82130a822ac1a8ba87d766980beb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:02:54 +0100 Subject: [PATCH 0751/1691] Remove entity description mixin in Picnic (#112920) --- homeassistant/components/picnic/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/picnic/sensor.py b/homeassistant/components/picnic/sensor.py index 3c186ccc034..866bd6b56c1 100644 --- a/homeassistant/components/picnic/sensor.py +++ b/homeassistant/components/picnic/sensor.py @@ -45,20 +45,15 @@ from .const import ( from .coordinator import PicnicUpdateCoordinator -@dataclass(frozen=True) -class PicnicRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class PicnicSensorEntityDescription(SensorEntityDescription): + """Describes Picnic sensor entity.""" data_type: Literal[ "cart_data", "slot_data", "next_delivery_data", "last_order_data" ] value_fn: Callable[[Any], StateType | datetime] - -@dataclass(frozen=True) -class PicnicSensorEntityDescription(SensorEntityDescription, PicnicRequiredKeysMixin): - """Describes Picnic sensor entity.""" - entity_registry_enabled_default: bool = False From e08fb68597c5cbf302a2278539b096b8292084e5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:03:13 +0100 Subject: [PATCH 0752/1691] Remove entity description mixin in Pi-hole (#112919) --- homeassistant/components/pi_hole/binary_sensor.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/pi_hole/binary_sensor.py b/homeassistant/components/pi_hole/binary_sensor.py index 4c49cf3a98d..0593d12faa7 100644 --- a/homeassistant/components/pi_hole/binary_sensor.py +++ b/homeassistant/components/pi_hole/binary_sensor.py @@ -22,19 +22,11 @@ from . import PiHoleEntity from .const import DATA_KEY_API, DATA_KEY_COORDINATOR, DOMAIN as PIHOLE_DOMAIN -@dataclass(frozen=True) -class RequiredPiHoleBinaryDescription: - """Represent the required attributes of the PiHole binary description.""" - - state_value: Callable[[Hole], bool] - - -@dataclass(frozen=True) -class PiHoleBinarySensorEntityDescription( - BinarySensorEntityDescription, RequiredPiHoleBinaryDescription -): +@dataclass(frozen=True, kw_only=True) +class PiHoleBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes PiHole binary sensor entity.""" + state_value: Callable[[Hole], bool] extra_value: Callable[[Hole], dict[str, Any] | None] = lambda api: None From b48032e37ea312afb4885ba7135d7b21029529c1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:03:33 +0100 Subject: [PATCH 0753/1691] Remove entity description mixin in PECO (#112915) --- homeassistant/components/peco/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/peco/sensor.py b/homeassistant/components/peco/sensor.py index 60a3a07212d..d08947eb0ec 100644 --- a/homeassistant/components/peco/sensor.py +++ b/homeassistant/components/peco/sensor.py @@ -25,21 +25,14 @@ from . import PECOCoordinatorData from .const import ATTR_CONTENT, CONF_COUNTY, DOMAIN -@dataclass(frozen=True) -class PECOSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class PECOSensorEntityDescription(SensorEntityDescription): + """Description for PECO sensor.""" value_fn: Callable[[PECOCoordinatorData], int | str] attribute_fn: Callable[[PECOCoordinatorData], dict[str, str]] -@dataclass(frozen=True) -class PECOSensorEntityDescription( - SensorEntityDescription, PECOSensorEntityDescriptionMixin -): - """Description for PECO sensor.""" - - PARALLEL_UPDATES: Final = 0 SENSOR_LIST: tuple[PECOSensorEntityDescription, ...] = ( PECOSensorEntityDescription( From f86119aec760b480fbe26c4d2fb806ad9dfea1c6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:03:50 +0100 Subject: [PATCH 0754/1691] Remove entity description mixin in Onvif (#112912) --- homeassistant/components/onvif/switch.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/onvif/switch.py b/homeassistant/components/onvif/switch.py index 004872606d3..02b48d20bef 100644 --- a/homeassistant/components/onvif/switch.py +++ b/homeassistant/components/onvif/switch.py @@ -17,9 +17,9 @@ from .device import ONVIFDevice from .models import Profile -@dataclass(frozen=True) -class ONVIFSwitchEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class ONVIFSwitchEntityDescription(SwitchEntityDescription): + """Describes ONVIF switch entity.""" turn_on_fn: Callable[ [ONVIFDevice], Callable[[Profile, Any], Coroutine[Any, Any, None]] @@ -32,13 +32,6 @@ class ONVIFSwitchEntityDescriptionMixin: supported_fn: Callable[[ONVIFDevice], bool] -@dataclass(frozen=True) -class ONVIFSwitchEntityDescription( - SwitchEntityDescription, ONVIFSwitchEntityDescriptionMixin -): - """Describes ONVIF switch entity.""" - - SWITCHES: tuple[ONVIFSwitchEntityDescription, ...] = ( ONVIFSwitchEntityDescription( key="autofocus", From 14e7e4c8603561d300a9c1f4f6e8671198148ce5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:04:04 +0100 Subject: [PATCH 0755/1691] Remove entity description mixin in Netatmo (#112910) --- homeassistant/components/netatmo/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index f71ffd76cc8..481b0ba86aa 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -71,18 +71,13 @@ SUPPORTED_PUBLIC_SENSOR_TYPES: tuple[str, ...] = ( ) -@dataclass(frozen=True) -class NetatmoRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class NetatmoSensorEntityDescription(SensorEntityDescription): + """Describes Netatmo sensor entity.""" netatmo_name: str -@dataclass(frozen=True) -class NetatmoSensorEntityDescription(SensorEntityDescription, NetatmoRequiredKeysMixin): - """Describes Netatmo sensor entity.""" - - SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = ( NetatmoSensorEntityDescription( key="temperature", From 62817ba3383e3cd16efe613403c620f4a9b0bb45 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:05:55 +0100 Subject: [PATCH 0756/1691] Remove entity description mixin in Melnor (#112906) --- homeassistant/components/melnor/number.py | 13 +++--------- homeassistant/components/melnor/sensor.py | 26 ++++++----------------- homeassistant/components/melnor/switch.py | 13 +++--------- homeassistant/components/melnor/time.py | 13 +++--------- 4 files changed, 15 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/melnor/number.py b/homeassistant/components/melnor/number.py index f68cdd1b29e..33d9fa443b1 100644 --- a/homeassistant/components/melnor/number.py +++ b/homeassistant/components/melnor/number.py @@ -26,21 +26,14 @@ from .models import ( ) -@dataclass(frozen=True) -class MelnorZoneNumberEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class MelnorZoneNumberEntityDescription(NumberEntityDescription): + """Describes Melnor number entity.""" set_num_fn: Callable[[Valve, int], Coroutine[Any, Any, None]] state_fn: Callable[[Valve], Any] -@dataclass(frozen=True) -class MelnorZoneNumberEntityDescription( - NumberEntityDescription, MelnorZoneNumberEntityDescriptionMixin -): - """Describes Melnor number entity.""" - - ZONE_ENTITY_DESCRIPTIONS: list[MelnorZoneNumberEntityDescription] = [ MelnorZoneNumberEntityDescription( entity_category=EntityCategory.CONFIG, diff --git a/homeassistant/components/melnor/sensor.py b/homeassistant/components/melnor/sensor.py index 8765cd538f8..6528773d9d8 100644 --- a/homeassistant/components/melnor/sensor.py +++ b/homeassistant/components/melnor/sensor.py @@ -55,32 +55,18 @@ def next_cycle(valve: Valve) -> datetime | None: return None -@dataclass(frozen=True) -class MelnorSensorEntityDescriptionMixin: - """Mixin for required keys.""" - - state_fn: Callable[[Device], Any] - - -@dataclass(frozen=True) -class MelnorZoneSensorEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class MelnorZoneSensorEntityDescription(SensorEntityDescription): + """Describes Melnor sensor entity.""" state_fn: Callable[[Valve], Any] -@dataclass(frozen=True) -class MelnorZoneSensorEntityDescription( - SensorEntityDescription, MelnorZoneSensorEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class MelnorSensorEntityDescription(SensorEntityDescription): """Describes Melnor sensor entity.""" - -@dataclass(frozen=True) -class MelnorSensorEntityDescription( - SensorEntityDescription, MelnorSensorEntityDescriptionMixin -): - """Describes Melnor sensor entity.""" + state_fn: Callable[[Device], Any] DEVICE_ENTITY_DESCRIPTIONS: list[MelnorSensorEntityDescription] = [ diff --git a/homeassistant/components/melnor/switch.py b/homeassistant/components/melnor/switch.py index 48b9a65e920..f912db1e981 100644 --- a/homeassistant/components/melnor/switch.py +++ b/homeassistant/components/melnor/switch.py @@ -25,21 +25,14 @@ from .models import ( ) -@dataclass(frozen=True) -class MelnorSwitchEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class MelnorSwitchEntityDescription(SwitchEntityDescription): + """Describes Melnor switch entity.""" on_off_fn: Callable[[Valve, bool], Coroutine[Any, Any, None]] state_fn: Callable[[Valve], Any] -@dataclass(frozen=True) -class MelnorSwitchEntityDescription( - SwitchEntityDescription, MelnorSwitchEntityDescriptionMixin -): - """Describes Melnor switch entity.""" - - ZONE_ENTITY_DESCRIPTIONS = [ MelnorSwitchEntityDescription( device_class=SwitchDeviceClass.SWITCH, diff --git a/homeassistant/components/melnor/time.py b/homeassistant/components/melnor/time.py index 36afe2d976d..d2d05f6517f 100644 --- a/homeassistant/components/melnor/time.py +++ b/homeassistant/components/melnor/time.py @@ -23,21 +23,14 @@ from .models import ( ) -@dataclass(frozen=True) -class MelnorZoneTimeEntityDescriptionMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class MelnorZoneTimeEntityDescription(TimeEntityDescription): + """Describes Melnor number entity.""" set_time_fn: Callable[[Valve, time], Coroutine[Any, Any, None]] state_fn: Callable[[Valve], Any] -@dataclass(frozen=True) -class MelnorZoneTimeEntityDescription( - TimeEntityDescription, MelnorZoneTimeEntityDescriptionMixin -): - """Describes Melnor number entity.""" - - ZONE_ENTITY_DESCRIPTIONS: list[MelnorZoneTimeEntityDescription] = [ MelnorZoneTimeEntityDescription( entity_category=EntityCategory.CONFIG, From 324fc513757cd69483d775e57814ab9619038d69 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:06:27 +0100 Subject: [PATCH 0757/1691] Remove entity description mixin in Landisgyr Heat Meter (#112901) --- .../components/landisgyr_heat_meter/sensor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/landisgyr_heat_meter/sensor.py b/homeassistant/components/landisgyr_heat_meter/sensor.py index a06e87f83fa..9078a46f876 100644 --- a/homeassistant/components/landisgyr_heat_meter/sensor.py +++ b/homeassistant/components/landisgyr_heat_meter/sensor.py @@ -40,20 +40,13 @@ from . import DOMAIN _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class HeatMeterSensorEntityDescriptionMixin: - """Mixin for additional Heat Meter sensor description attributes .""" +@dataclass(frozen=True, kw_only=True) +class HeatMeterSensorEntityDescription(SensorEntityDescription): + """Heat Meter sensor description.""" value_fn: Callable[[HeatMeterResponse], StateType | datetime] -@dataclass(frozen=True) -class HeatMeterSensorEntityDescription( - SensorEntityDescription, HeatMeterSensorEntityDescriptionMixin -): - """Heat Meter sensor description.""" - - HEAT_METER_SENSOR_TYPES = ( HeatMeterSensorEntityDescription( key="heat_usage_mwh", From d82ffb4e076d8dd5791a65f6b8b87024da7d9a27 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:06:42 +0100 Subject: [PATCH 0758/1691] Remove entity description mixin in Kraken (#112899) --- homeassistant/components/kraken/sensor.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/kraken/sensor.py b/homeassistant/components/kraken/sensor.py index bcffe394bff..37fee795783 100644 --- a/homeassistant/components/kraken/sensor.py +++ b/homeassistant/components/kraken/sensor.py @@ -33,18 +33,13 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class KrakenRequiredKeysMixin: - """Mixin for required keys.""" +@dataclass(frozen=True, kw_only=True) +class KrakenSensorEntityDescription(SensorEntityDescription): + """Describes Kraken sensor entity.""" value_fn: Callable[[DataUpdateCoordinator[KrakenResponse], str], float | int] -@dataclass(frozen=True) -class KrakenSensorEntityDescription(SensorEntityDescription, KrakenRequiredKeysMixin): - """Describes Kraken sensor entity.""" - - SENSOR_TYPES: tuple[KrakenSensorEntityDescription, ...] = ( KrakenSensorEntityDescription( key="ask", From 067c222bd9a19360f28fdf003b9b9bb010d30db9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:07:13 +0100 Subject: [PATCH 0759/1691] Remove entity description mixin in Kostal Plenticore (#112898) --- .../components/kostal_plenticore/number.py | 13 +++---------- .../components/kostal_plenticore/select.py | 13 +++---------- .../components/kostal_plenticore/sensor.py | 13 +++---------- .../components/kostal_plenticore/switch.py | 13 +++---------- 4 files changed, 12 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/kostal_plenticore/number.py b/homeassistant/components/kostal_plenticore/number.py index d53982535da..2e544a16fec 100644 --- a/homeassistant/components/kostal_plenticore/number.py +++ b/homeassistant/components/kostal_plenticore/number.py @@ -27,9 +27,9 @@ from .helper import PlenticoreDataFormatter, SettingDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class PlenticoreNumberEntityDescriptionMixin: - """Define an entity description mixin for number entities.""" +@dataclass(frozen=True, kw_only=True) +class PlenticoreNumberEntityDescription(NumberEntityDescription): + """Describes a Plenticore number entity.""" module_id: str data_id: str @@ -37,13 +37,6 @@ class PlenticoreNumberEntityDescriptionMixin: fmt_to: str -@dataclass(frozen=True) -class PlenticoreNumberEntityDescription( - NumberEntityDescription, PlenticoreNumberEntityDescriptionMixin -): - """Describes a Plenticore number entity.""" - - NUMBER_SETTINGS_DATA = [ PlenticoreNumberEntityDescription( key="battery_min_soc", diff --git a/homeassistant/components/kostal_plenticore/select.py b/homeassistant/components/kostal_plenticore/select.py index 2f6d9334071..555bb89641b 100644 --- a/homeassistant/components/kostal_plenticore/select.py +++ b/homeassistant/components/kostal_plenticore/select.py @@ -20,20 +20,13 @@ from .helper import Plenticore, SelectDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class PlenticoreRequiredKeysMixin: - """A class that describes required properties for plenticore select entities.""" +@dataclass(frozen=True, kw_only=True) +class PlenticoreSelectEntityDescription(SelectEntityDescription): + """A class that describes plenticore select entities.""" module_id: str -@dataclass(frozen=True) -class PlenticoreSelectEntityDescription( - SelectEntityDescription, PlenticoreRequiredKeysMixin -): - """A class that describes plenticore select entities.""" - - SELECT_SETTINGS_DATA = [ PlenticoreSelectEntityDescription( module_id="devices:local", diff --git a/homeassistant/components/kostal_plenticore/sensor.py b/homeassistant/components/kostal_plenticore/sensor.py index 88714620a46..d6e13ecb5b7 100644 --- a/homeassistant/components/kostal_plenticore/sensor.py +++ b/homeassistant/components/kostal_plenticore/sensor.py @@ -34,21 +34,14 @@ from .helper import PlenticoreDataFormatter, ProcessDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class PlenticoreRequiredKeysMixin: - """A class that describes required properties for plenticore sensor entities.""" +@dataclass(frozen=True, kw_only=True) +class PlenticoreSensorEntityDescription(SensorEntityDescription): + """A class that describes plenticore sensor entities.""" module_id: str formatter: str -@dataclass(frozen=True) -class PlenticoreSensorEntityDescription( - SensorEntityDescription, PlenticoreRequiredKeysMixin -): - """A class that describes plenticore sensor entities.""" - - SENSOR_PROCESS_DATA = [ PlenticoreSensorEntityDescription( module_id="devices:local", diff --git a/homeassistant/components/kostal_plenticore/switch.py b/homeassistant/components/kostal_plenticore/switch.py index f1e75edd5bd..f2ea1a5ef7c 100644 --- a/homeassistant/components/kostal_plenticore/switch.py +++ b/homeassistant/components/kostal_plenticore/switch.py @@ -21,9 +21,9 @@ from .helper import SettingDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class PlenticoreRequiredKeysMixin: - """A class that describes required properties for plenticore switch entities.""" +@dataclass(frozen=True, kw_only=True) +class PlenticoreSwitchEntityDescription(SwitchEntityDescription): + """A class that describes plenticore switch entities.""" module_id: str is_on: str @@ -33,13 +33,6 @@ class PlenticoreRequiredKeysMixin: off_label: str -@dataclass(frozen=True) -class PlenticoreSwitchEntityDescription( - SwitchEntityDescription, PlenticoreRequiredKeysMixin -): - """A class that describes plenticore switch entities.""" - - SWITCH_SETTINGS_DATA = [ PlenticoreSwitchEntityDescription( module_id="devices:local", From 57e6c8e07afee65f57f0c7d1b9ff104d72010aff Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 12:08:42 +0100 Subject: [PATCH 0760/1691] Remove entity description mixin in Juicenet (#112892) --- homeassistant/components/juicenet/number.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/juicenet/number.py b/homeassistant/components/juicenet/number.py index 173ebd45265..383d0d590c4 100644 --- a/homeassistant/components/juicenet/number.py +++ b/homeassistant/components/juicenet/number.py @@ -20,19 +20,11 @@ from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR from .entity import JuiceNetDevice -@dataclass(frozen=True) -class JuiceNetNumberEntityDescriptionMixin: - """Mixin for required keys.""" - - setter_key: str - - -@dataclass(frozen=True) -class JuiceNetNumberEntityDescription( - NumberEntityDescription, JuiceNetNumberEntityDescriptionMixin -): +@dataclass(frozen=True, kw_only=True) +class JuiceNetNumberEntityDescription(NumberEntityDescription): """An entity description for a JuiceNetNumber.""" + setter_key: str native_max_value_key: str | None = None From 7fd5c3ed6120ab6148a79aa310bc990ef5e4db9c Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 11 Mar 2024 12:18:29 +0100 Subject: [PATCH 0761/1691] Use UV instead of PIP in the CI (#113051) --- .github/workflows/ci.yaml | 43 ++++++++++++++++++++++----------------- requirements_test.txt | 1 + 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44d95c03954..142fa100408 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,7 +34,7 @@ on: env: CACHE_VERSION: 5 - PIP_CACHE_VERSION: 4 + UV_CACHE_VERSION: 1 MYPY_CACHE_VERSION: 8 HA_SHORT_VERSION: "2024.4" DEFAULT_PYTHON: "3.12" @@ -56,7 +56,7 @@ env: # - 15.2 is the latest (as of 9 Feb 2023) POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']" PRE_COMMIT_CACHE: ~/.cache/pre-commit - PIP_CACHE: /tmp/pip-cache + UV_CACHE_DIR: /tmp/uv-cache SQLALCHEMY_WARN_20: 1 PYTHONASYNCIODEBUG: 1 HASS_CI: 1 @@ -243,7 +243,8 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install "$(cat requirements_test.txt | grep pre-commit)" + pip install "$(cat requirements_test.txt | grep uv)" + uv pip install "$(cat requirements_test.txt | grep pre-commit)" - name: Restore pre-commit environment from cache id: cache-precommit uses: actions/cache@v4.0.1 @@ -447,10 +448,10 @@ jobs: with: python-version: ${{ matrix.python-version }} check-latest: true - - name: Generate partial pip restore key - id: generate-pip-key + - name: Generate partial uv restore key + id: generate-uv-key run: >- - echo "key=pip-${{ env.PIP_CACHE_VERSION }}-${{ + echo "key=uv-${{ env.UV_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore base Python virtual environment id: cache-venv @@ -461,16 +462,16 @@ jobs: key: >- ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - - name: Restore pip wheel cache + - name: Restore uv wheel cache if: steps.cache-venv.outputs.cache-hit != 'true' uses: actions/cache@v4.0.1 with: - path: ${{ env.PIP_CACHE }} + path: ${{ env.UV_CACHE }} key: >- ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - steps.generate-pip-key.outputs.key }} + steps.generate-uv-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-uv-${{ env.UV_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}- - name: Install additional OS dependencies if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -492,10 +493,11 @@ jobs: python -m venv venv . venv/bin/activate python --version - PIP_CACHE_DIR=$PIP_CACHE pip install -U "pip>=21.3.1" setuptools wheel - PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_all.txt - PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_test.txt - pip install -e . --config-settings editable_mode=compat + pip install "$(cat requirements_test.txt | grep uv)" + uv pip install -U "pip>=21.3.1" setuptools wheel + uv pip install -r requirements_all.txt + uv pip install -r requirements_test.txt + uv pip install -e . --config-settings editable_mode=compat hassfest: name: Check hassfest @@ -723,7 +725,8 @@ jobs: # Ideally this should be part of our dependencies # However this plugin is fairly new and doesn't run correctly # on a non-GitHub environment. - pip install pytest-github-actions-annotate-failures==0.1.3 + pip install "$(cat requirements_test.txt | grep uv)" + uv pip install pytest-github-actions-annotate-failures==0.1.3 - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" @@ -876,14 +879,15 @@ jobs: # Ideally this should be part of our dependencies # However this plugin is fairly new and doesn't run correctly # on a non-GitHub environment. - pip install pytest-github-actions-annotate-failures==0.1.3 + pip install "$(cat requirements_test.txt | grep uv)" + uv pip install pytest-github-actions-annotate-failures==0.1.3 - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Install SQL Python libraries run: | . venv/bin/activate - pip install mysqlclient sqlalchemy_utils + uv pip install mysqlclient sqlalchemy_utils - name: Compile English translations run: | . venv/bin/activate @@ -1003,14 +1007,15 @@ jobs: # Ideally this should be part of our dependencies # However this plugin is fairly new and doesn't run correctly # on a non-GitHub environment. - pip install pytest-github-actions-annotate-failures==0.1.3 + pip install "$(cat requirements_test.txt | grep uv)" + uv pip install pytest-github-actions-annotate-failures==0.1.3 - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - name: Install SQL Python libraries run: | . venv/bin/activate - pip install psycopg2 sqlalchemy_utils + uv pip install psycopg2 sqlalchemy_utils - name: Compile English translations run: | . venv/bin/activate diff --git a/requirements_test.txt b/requirements_test.txt index 7001a60f6bf..03a61613600 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -50,3 +50,4 @@ types-pytz==2023.3.1.1 types-PyYAML==6.0.12.12 types-requests==2.31.0.3 types-xmltodict==0.13.0.3 +uv==0.1.17 \ No newline at end of file From a7a30581fc5dbf7345744e948030667885324354 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 11 Mar 2024 12:35:41 +0100 Subject: [PATCH 0762/1691] Fix adding Hue bridge manually by IP (#113055) --- homeassistant/components/hue/config_flow.py | 2 +- tests/components/hue/test_config_flow.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 9014f2644f5..de2d9363ac7 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -117,7 +117,7 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN): websession=aiohttp_client.async_get_clientsession(self.hass) ) except TimeoutError: - return self.async_abort(reason="discover_timeout") + bridges = [] if bridges: # Find already configured hosts diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 121156fa9dc..7d7ef0eaf38 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -260,8 +260,8 @@ async def test_flow_timeout_discovery(hass: HomeAssistant) -> None: const.DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "abort" - assert result["reason"] == "discover_timeout" + assert result["type"] == "form" + assert result["step_id"] == "manual" async def test_flow_link_unknown_error(hass: HomeAssistant) -> None: From 564c31e8467c4ea0b9bf1ebd6c460601c114b6ed Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Mon, 11 Mar 2024 12:39:07 +0100 Subject: [PATCH 0763/1691] Bump xiaomi-ble to 0.27.0 (#113013) --- homeassistant/components/xiaomi_ble/device_trigger.py | 1 + homeassistant/components/xiaomi_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/device_trigger.py b/homeassistant/components/xiaomi_ble/device_trigger.py index 86749bd8dd3..119424788db 100644 --- a/homeassistant/components/xiaomi_ble/device_trigger.py +++ b/homeassistant/components/xiaomi_ble/device_trigger.py @@ -256,6 +256,7 @@ MODEL_DATA = { "K9BB-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG], "YLAI003": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG], "XMWXKG01LM": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG], + "PTX_YK1_QMIMB": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG], "K9B-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG], "XMWXKG01YL": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG], "K9B-2BTN": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG], diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 603a2dc2c71..8522ff89e45 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.26.1"] + "requirements": ["xiaomi-ble==0.27.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 06b99ce0c64..27994a312a4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2866,7 +2866,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.26.1 +xiaomi-ble==0.27.0 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 95b8400d877..9d7a4e52235 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2204,7 +2204,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.26.1 +xiaomi-ble==0.27.0 # homeassistant.components.knx xknx==2.12.2 From 3c06fbbd8202f42845e8dc186af19a4825cea404 Mon Sep 17 00:00:00 2001 From: Shai Ungar Date: Mon, 11 Mar 2024 13:47:39 +0200 Subject: [PATCH 0764/1691] Add ConfigFlow for seventeentrack integration (#111196) * Add config flow to 17Track * Import config from configuration.yaml * 1. move import to async_setup_platform 2. add USERNAME (email) in title for uniqueness * Add options flow * Add tests * Add CONF_SHOW_ARCHIVED and CONF_SHOW_DELIVERED to data from options * Update homeassistant/components/seventeentrack/__init__.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/__init__.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/config_flow.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/manifest.json Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/config_flow.py Co-authored-by: Christopher Fenner <9592452+CFenner@users.noreply.github.com> * Update homeassistant/components/seventeentrack/__init__.py Co-authored-by: Christopher Fenner <9592452+CFenner@users.noreply.github.com> * Update homeassistant/components/seventeentrack/sensor.py Co-authored-by: Christopher Fenner <9592452+CFenner@users.noreply.github.com> * 1. Added repair issues 2. _async_validate_input inlined 3. added unique id 4. take default scan interval * fix * 1. move async_create_issue to async_setup_platform 2. fix tests 3. black + pylint * combine USER_SCHEMA and OPTIONS_SCHEMA * small fix * remove async_setup * fix tests and add 100% coverage * 1. remove CONFIG_SCHEMA 2. remove error log 3. add issue with more description when import issues happen 4. some linting * Update homeassistant/components/seventeentrack/config_flow.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/sensor.py Co-authored-by: Joost Lekkerkerker * use freezer use AsyncMock fix tests * add test_flow_fails parametrize tests where needed test_import_flow_already_configured - where a unique id already configured (abort flow) * lint * fix rebase issues * some more fix * 17Track revert tests and put them in a different PR * adapt tests to MockConfigEntry * Update tests/components/seventeentrack/test_sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/seventeentrack/sensor.py Co-authored-by: Joost Lekkerkerker * Update tests/components/seventeentrack/__init__.py Co-authored-by: Joost Lekkerkerker * 1. create fixture for config and another with options 2. set options with default values 3. remove CONFIG_SCHEMA * Update tests/components/seventeentrack/conftest.py Co-authored-by: Joost Lekkerkerker * Update tests/components/seventeentrack/conftest.py Co-authored-by: Joost Lekkerkerker * 1. get options from import data and default if not present 2. rename mock_config_entry_no_options -> mock_config_entry_with_default_options * move ACCOUNT_ID to mock_seventeentrack_api.return_value.profile.account_id * Apply suggestions from code review * Update tests/components/seventeentrack/test_config_flow.py --------- Co-authored-by: Joost Lekkerkerker Co-authored-by: Christopher Fenner <9592452+CFenner@users.noreply.github.com> --- CODEOWNERS | 2 + .../components/seventeentrack/__init__.py | 31 +++ .../components/seventeentrack/config_flow.py | 137 ++++++++++++ .../components/seventeentrack/const.py | 39 ++++ .../components/seventeentrack/manifest.json | 4 +- .../components/seventeentrack/sensor.py | 151 +++++++------ .../components/seventeentrack/strings.json | 40 ++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 4 +- tests/components/seventeentrack/__init__.py | 15 +- tests/components/seventeentrack/conftest.py | 105 ++++++--- .../seventeentrack/test_config_flow.py | 208 ++++++++++++++++++ .../components/seventeentrack/test_sensor.py | 120 +++++++--- 13 files changed, 714 insertions(+), 143 deletions(-) create mode 100644 homeassistant/components/seventeentrack/config_flow.py create mode 100644 homeassistant/components/seventeentrack/const.py create mode 100644 homeassistant/components/seventeentrack/strings.json create mode 100644 tests/components/seventeentrack/test_config_flow.py diff --git a/CODEOWNERS b/CODEOWNERS index 15af941984b..1ad49aac17a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1190,6 +1190,8 @@ build.json @home-assistant/supervisor /tests/components/senz/ @milanmeu /homeassistant/components/serial/ @fabaff /homeassistant/components/seven_segments/ @fabaff +/homeassistant/components/seventeentrack/ @shaiu +/tests/components/seventeentrack/ @shaiu /homeassistant/components/sfr_box/ @epenet /tests/components/sfr_box/ @epenet /homeassistant/components/sharkiq/ @JeffResc @funkybunch diff --git a/homeassistant/components/seventeentrack/__init__.py b/homeassistant/components/seventeentrack/__init__.py index 43eebf346f0..183d1bd4068 100644 --- a/homeassistant/components/seventeentrack/__init__.py +++ b/homeassistant/components/seventeentrack/__init__.py @@ -1 +1,32 @@ """The seventeentrack component.""" + +from py17track import Client as SeventeenTrackClient +from py17track.errors import SeventeenTrackError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DOMAIN + +PLATFORMS = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up 17Track from a config entry.""" + + session = async_get_clientsession(hass) + client = SeventeenTrackClient(session=session) + + try: + await client.profile.login(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]) + except SeventeenTrackError as err: + raise ConfigEntryNotReady from err + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = client + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True diff --git a/homeassistant/components/seventeentrack/config_flow.py b/homeassistant/components/seventeentrack/config_flow.py new file mode 100644 index 00000000000..f42ff184f81 --- /dev/null +++ b/homeassistant/components/seventeentrack/config_flow.py @@ -0,0 +1,137 @@ +"""Adds config flow for 17track.net.""" + +from __future__ import annotations + +import logging +from typing import Any + +from py17track import Client as SeventeenTrackClient +from py17track.errors import SeventeenTrackError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.config_entries import ConfigFlowResult +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.schema_config_entry_flow import ( + SchemaFlowFormStep, + SchemaOptionsFlowHandler, +) + +from .const import ( + CONF_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED, + DEFAULT_SHOW_ARCHIVED, + DEFAULT_SHOW_DELIVERED, + DOMAIN, +) + +CONF_SHOW = { + vol.Optional(CONF_SHOW_ARCHIVED, default=DEFAULT_SHOW_ARCHIVED): bool, + vol.Optional(CONF_SHOW_DELIVERED, default=DEFAULT_SHOW_DELIVERED): bool, +} + +_LOGGER = logging.getLogger(__name__) + +OPTIONS_SCHEMA = vol.Schema(CONF_SHOW) +OPTIONS_FLOW = { + "init": SchemaFlowFormStep(OPTIONS_SCHEMA), +} + +USER_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } +) + + +class SeventeenTrackConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """17track config flow.""" + + VERSION = 1 + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> SchemaOptionsFlowHandler: + """Get options flow for this handler.""" + return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a flow initialized by the user.""" + + errors = {} + if user_input: + client = await self._get_client() + + try: + if not await client.profile.login( + user_input[CONF_USERNAME], user_input[CONF_PASSWORD] + ): + errors["base"] = "invalid_auth" + except SeventeenTrackError as err: + _LOGGER.error("There was an error while logging in: %s", err) + errors["base"] = "cannot_connect" + + if not errors: + account_id = client.profile.account_id + await self.async_set_unique_id(account_id) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=user_input[CONF_USERNAME], + data=user_input, + options={ + CONF_SHOW_ARCHIVED: DEFAULT_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED: DEFAULT_SHOW_DELIVERED, + }, + ) + + return self.async_show_form( + step_id="user", + data_schema=USER_SCHEMA, + errors=errors, + ) + + async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult: + """Import 17Track config from configuration.yaml.""" + + client = await self._get_client() + + try: + login_result = await client.profile.login( + import_data[CONF_USERNAME], import_data[CONF_PASSWORD] + ) + except SeventeenTrackError as err: + _LOGGER.error("There was an error while logging in: %s", err) + return self.async_abort(reason="cannot_connect") + + if not login_result: + _LOGGER.error("Invalid username and password provided") + return self.async_abort(reason="invalid_credentials") + + account_id = client.profile.account_id + + await self.async_set_unique_id(account_id) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=import_data[CONF_USERNAME], + data=import_data, + options={ + CONF_SHOW_ARCHIVED: import_data.get( + CONF_SHOW_ARCHIVED, DEFAULT_SHOW_ARCHIVED + ), + CONF_SHOW_DELIVERED: import_data.get( + CONF_SHOW_DELIVERED, DEFAULT_SHOW_DELIVERED + ), + }, + ) + + async def _get_client(self): + session = aiohttp_client.async_get_clientsession(self.hass) + return SeventeenTrackClient(session=session) diff --git a/homeassistant/components/seventeentrack/const.py b/homeassistant/components/seventeentrack/const.py new file mode 100644 index 00000000000..6f8ae1b221c --- /dev/null +++ b/homeassistant/components/seventeentrack/const.py @@ -0,0 +1,39 @@ +"""Constants for the 17track.net component.""" + +from datetime import timedelta + +ATTR_DESTINATION_COUNTRY = "destination_country" +ATTR_INFO_TEXT = "info_text" +ATTR_TIMESTAMP = "timestamp" +ATTR_ORIGIN_COUNTRY = "origin_country" +ATTR_PACKAGES = "packages" +ATTR_PACKAGE_TYPE = "package_type" +ATTR_STATUS = "status" +ATTR_TRACKING_INFO_LANGUAGE = "tracking_info_language" +ATTR_TRACKING_NUMBER = "tracking_number" + +CONF_SHOW_ARCHIVED = "show_archived" +CONF_SHOW_DELIVERED = "show_delivered" + +DEFAULT_SHOW_ARCHIVED = False +DEFAULT_SHOW_DELIVERED = False + +DOMAIN = "seventeentrack" + +DATA_PACKAGES = "package_data" +DATA_SUMMARY = "summary_data" + +ATTRIBUTION = "Data provided by 17track.net" +DEFAULT_SCAN_INTERVAL = timedelta(minutes=10) + +UNIQUE_ID_TEMPLATE = "package_{0}_{1}" +ENTITY_ID_TEMPLATE = "sensor.seventeentrack_package_{0}" + +NOTIFICATION_DELIVERED_ID = "package_delivered_{0}" +NOTIFICATION_DELIVERED_TITLE = "Package {0} delivered" +NOTIFICATION_DELIVERED_MESSAGE = ( + "Package Delivered: {0}
Visit 17.track for more information: " + "https://t.17track.net/track#nums={1}" +) + +VALUE_DELIVERED = "Delivered" diff --git a/homeassistant/components/seventeentrack/manifest.json b/homeassistant/components/seventeentrack/manifest.json index f752f95ff2d..30bdeaa900f 100644 --- a/homeassistant/components/seventeentrack/manifest.json +++ b/homeassistant/components/seventeentrack/manifest.json @@ -1,8 +1,10 @@ { "domain": "seventeentrack", "name": "17TRACK", - "codeowners": [], + "codeowners": ["@shaiu"], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/seventeentrack", + "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["py17track"], "requirements": ["py17track==2021.12.2"] diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 7f9bd5f2505..bdf4b74ac59 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -2,66 +2,53 @@ from __future__ import annotations -from datetime import timedelta import logging -from py17track import Client as SeventeenTrackClient from py17track.errors import SeventeenTrackError +from py17track.package import Package import voluptuous as vol from homeassistant.components import persistent_notification from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_LOCATION, CONF_PASSWORD, - CONF_SCAN_INTERVAL, CONF_USERNAME, ) -from homeassistant.core import HomeAssistant -from homeassistant.helpers import ( - aiohttp_client, - config_validation as cv, - entity, - entity_registry as er, -) +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import config_validation as cv, entity, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.util import Throttle, slugify -_LOGGER = logging.getLogger(__name__) - -ATTR_DESTINATION_COUNTRY = "destination_country" -ATTR_INFO_TEXT = "info_text" -ATTR_TIMESTAMP = "timestamp" -ATTR_ORIGIN_COUNTRY = "origin_country" -ATTR_PACKAGES = "packages" -ATTR_PACKAGE_TYPE = "package_type" -ATTR_STATUS = "status" -ATTR_TRACKING_INFO_LANGUAGE = "tracking_info_language" -ATTR_TRACKING_NUMBER = "tracking_number" - -CONF_SHOW_ARCHIVED = "show_archived" -CONF_SHOW_DELIVERED = "show_delivered" - -DATA_PACKAGES = "package_data" -DATA_SUMMARY = "summary_data" - -ATTRIBUTION = "Data provided by 17track.net" -DEFAULT_SCAN_INTERVAL = timedelta(minutes=10) - -UNIQUE_ID_TEMPLATE = "package_{0}_{1}" -ENTITY_ID_TEMPLATE = "sensor.seventeentrack_package_{0}" - -NOTIFICATION_DELIVERED_ID = "package_delivered_{0}" -NOTIFICATION_DELIVERED_TITLE = "Package {0} delivered" -NOTIFICATION_DELIVERED_MESSAGE = ( - "Package Delivered: {0}
Visit 17.track for more information: " - "https://t.17track.net/track#nums={1}" +from .const import ( + ATTR_DESTINATION_COUNTRY, + ATTR_INFO_TEXT, + ATTR_ORIGIN_COUNTRY, + ATTR_PACKAGE_TYPE, + ATTR_PACKAGES, + ATTR_STATUS, + ATTR_TIMESTAMP, + ATTR_TRACKING_INFO_LANGUAGE, + ATTR_TRACKING_NUMBER, + ATTRIBUTION, + CONF_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED, + DEFAULT_SCAN_INTERVAL, + DOMAIN, + ENTITY_ID_TEMPLATE, + NOTIFICATION_DELIVERED_MESSAGE, + NOTIFICATION_DELIVERED_TITLE, + UNIQUE_ID_TEMPLATE, + VALUE_DELIVERED, ) -VALUE_DELIVERED = "Delivered" +_LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -72,6 +59,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) +ISSUE_PLACEHOLDER = {"url": "/config/integrations/dashboard/add?domain=seventeentrack"} + async def async_setup_platform( hass: HomeAssistant, @@ -79,32 +68,57 @@ async def async_setup_platform( async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Configure the platform and add the sensors.""" + """Initialize 17Track import from config.""" - session = aiohttp_client.async_get_clientsession(hass) - - client = SeventeenTrackClient(session=session) - - try: - login_result = await client.profile.login( - config[CONF_USERNAME], config[CONF_PASSWORD] + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) + if ( + result["type"] == FlowResultType.CREATE_ENTRY + or result["reason"] == "already_configured" + ): + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + is_fixable=False, + breaks_in_ha_version="2024.10.0", + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "17Track", + }, + ) + else: + async_create_issue( + hass, + DOMAIN, + f"deprecated_yaml_import_issue_${result['reason']}", + breaks_in_ha_version="2024.10.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_placeholders=ISSUE_PLACEHOLDER, ) - if not login_result: - _LOGGER.error("Invalid username and password provided") - return - except SeventeenTrackError as err: - _LOGGER.error("There was an error while logging in: %s", err) - return - scan_interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up a 17Track sensor entry.""" + + client = hass.data[DOMAIN][config_entry.entry_id] data = SeventeenTrackData( client, async_add_entities, - scan_interval, - config[CONF_SHOW_ARCHIVED], - config[CONF_SHOW_DELIVERED], + DEFAULT_SCAN_INTERVAL, + config_entry.options[CONF_SHOW_ARCHIVED], + config_entry.options[CONF_SHOW_DELIVERED], str(hass.config.time_zone), ) await data.async_update() @@ -117,7 +131,7 @@ class SeventeenTrackSummarySensor(SensorEntity): _attr_icon = "mdi:package" _attr_native_unit_of_measurement = "packages" - def __init__(self, data, status, initial_state): + def __init__(self, data, status, initial_state) -> None: """Initialize.""" self._attr_extra_state_attributes = {} self._data = data @@ -127,12 +141,12 @@ class SeventeenTrackSummarySensor(SensorEntity): self._attr_unique_id = f"summary_{data.account_id}_{slugify(status)}" @property - def available(self): + def available(self) -> bool: """Return whether the entity is available.""" return self._state is not None @property - def native_value(self): + def native_value(self) -> StateType: """Return the state.""" return self._state @@ -169,7 +183,7 @@ class SeventeenTrackPackageSensor(SensorEntity): _attr_attribution = ATTRIBUTION _attr_icon = "mdi:package" - def __init__(self, data, package): + def __init__(self, data, package) -> None: """Initialize.""" self._attr_extra_state_attributes = { ATTR_DESTINATION_COUNTRY: package.destination_country, @@ -196,14 +210,14 @@ class SeventeenTrackPackageSensor(SensorEntity): return self._data.packages.get(self._tracking_number) is not None @property - def name(self): + def name(self) -> str: """Return the name.""" if not (name := self._friendly_name): name = self._tracking_number return f"Seventeentrack Package: {name}" @property - def native_value(self): + def native_value(self) -> StateType: """Return the state.""" return self._state @@ -278,18 +292,17 @@ class SeventeenTrackData: show_archived, show_delivered, timezone, - ): + ) -> None: """Initialize.""" self._async_add_entities = async_add_entities self._client = client self._scan_interval = scan_interval self._show_archived = show_archived self.account_id = client.profile.account_id - self.packages = {} + self.packages: dict[str, Package] = {} self.show_delivered = show_delivered self.timezone = timezone - self.summary = {} - + self.summary: dict[str, int] = {} self.async_update = Throttle(self._scan_interval)(self._async_update) self.first_update = True diff --git a/homeassistant/components/seventeentrack/strings.json b/homeassistant/components/seventeentrack/strings.json new file mode 100644 index 00000000000..1514509d6c2 --- /dev/null +++ b/homeassistant/components/seventeentrack/strings.json @@ -0,0 +1,40 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + } + }, + "options": { + "step": { + "init": { + "description": "Configure general settings", + "data": { + "show_archived": "Whether sensors should be created for archived packages", + "show_delivered": "Whether sensors should be created for delivered packages" + } + } + } + }, + "issues": { + "deprecated_yaml_import_issue_cannot_connect": { + "title": "The 17Track YAML configuration import cannot connect to server", + "description": "Configuring 17Track using YAML is being removed but there was a connection error importing your YAML configuration.\n\nThings you can try:\nMake sure your home assistant can reach the web.\n\nThen restart Home Assistant to try importing this integration again.\n\nAlternatively, you may remove the 17Track configuration from your YAML configuration entirely, restart Home Assistant, and add the 17Track integration manually." + }, + "deprecated_yaml_import_issue_invalid_credentials": { + "title": "The 17Track YAML configuration import request failed due to invalid credentials", + "description": "Configuring 17Track using YAML is being removed but there were invalid credentials provided while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your 17Track credentials are correct and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the 17Track configuration from your YAML configuration entirely, restart Home Assistant, and add the 17Track integration manually." + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 6fc9d4dee27..a049e2ca108 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -458,6 +458,7 @@ FLOWS = { "sensorpush", "sentry", "senz", + "seventeentrack", "sfr_box", "sharkiq", "shelly", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 99b400119c6..dfa1c5af230 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5271,8 +5271,8 @@ }, "seventeentrack": { "name": "17TRACK", - "integration_type": "hub", - "config_flow": false, + "integration_type": "service", + "config_flow": true, "iot_class": "cloud_polling" }, "sfr_box": { diff --git a/tests/components/seventeentrack/__init__.py b/tests/components/seventeentrack/__init__.py index 61590f2bd3f..4101f34496e 100644 --- a/tests/components/seventeentrack/__init__.py +++ b/tests/components/seventeentrack/__init__.py @@ -6,15 +6,18 @@ from freezegun.api import FrozenDateTimeFactory from homeassistant.components.seventeentrack.sensor import DEFAULT_SCAN_INTERVAL from homeassistant.core import HomeAssistant -from homeassistant.helpers.typing import ConfigType -from homeassistant.setup import async_setup_component -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed -async def init_integration(hass: HomeAssistant, config: ConfigType): - """Set up the seventeentrack integration in Home Assistant.""" - assert await async_setup_component(hass, "sensor", config) +async def init_integration( + hass: HomeAssistant, + config_entry: MockConfigEntry, +) -> None: + """Set up the 17Track integration in Home Assistant.""" + + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/seventeentrack/conftest.py b/tests/components/seventeentrack/conftest.py index 6d37e9f60eb..052d66a4696 100644 --- a/tests/components/seventeentrack/conftest.py +++ b/tests/components/seventeentrack/conftest.py @@ -1,46 +1,23 @@ """Configuration for 17Track tests.""" +from collections.abc import Generator from typing import Optional from unittest.mock import AsyncMock, patch from py17track.package import Package import pytest +from homeassistant.components.seventeentrack.const import ( + DEFAULT_SHOW_ARCHIVED, + DEFAULT_SHOW_DELIVERED, +) from homeassistant.components.seventeentrack.sensor import ( CONF_SHOW_ARCHIVED, CONF_SHOW_DELIVERED, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -VALID_CONFIG_MINIMAL = { - "sensor": { - "platform": "seventeentrack", - CONF_USERNAME: "test", - CONF_PASSWORD: "test", - } -} - -INVALID_CONFIG = {"sensor": {"platform": "seventeentrack", "boom": "test"}} - -VALID_CONFIG_FULL = { - "sensor": { - "platform": "seventeentrack", - CONF_USERNAME: "test", - CONF_PASSWORD: "test", - CONF_SHOW_ARCHIVED: True, - CONF_SHOW_DELIVERED: True, - } -} - -VALID_CONFIG_FULL_NO_DELIVERED = { - "sensor": { - "platform": "seventeentrack", - CONF_USERNAME: "test", - CONF_PASSWORD: "test", - CONF_SHOW_ARCHIVED: False, - CONF_SHOW_DELIVERED: False, - } -} +from tests.common import MockConfigEntry DEFAULT_SUMMARY = { "Not Found": 0, @@ -52,6 +29,8 @@ DEFAULT_SUMMARY = { "Returned": 0, } +ACCOUNT_ID = "1234" + NEW_SUMMARY_DATA = { "Not Found": 1, "In Transit": 1, @@ -62,6 +41,67 @@ NEW_SUMMARY_DATA = { "Returned": 1, } +VALID_CONFIG = { + CONF_USERNAME: "test", + CONF_PASSWORD: "test", +} + +INVALID_CONFIG = {"notusername": "seventeentrack", "notpassword": "test"} + +VALID_OPTIONS = { + CONF_SHOW_ARCHIVED: True, + CONF_SHOW_DELIVERED: True, +} + +NO_DELIVERED_OPTIONS = { + CONF_SHOW_ARCHIVED: False, + CONF_SHOW_DELIVERED: False, +} + +VALID_PLATFORM_CONFIG_FULL = { + "sensor": { + "platform": "seventeentrack", + CONF_USERNAME: "test", + CONF_PASSWORD: "test", + CONF_SHOW_ARCHIVED: True, + CONF_SHOW_DELIVERED: True, + } +} + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.seventeentrack.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + domain="seventeentrack", + data=VALID_CONFIG, + options=VALID_OPTIONS, + unique_id=ACCOUNT_ID, + ) + + +@pytest.fixture +def mock_config_entry_with_default_options() -> MockConfigEntry: + """Return the default mocked config entry.""" + return MockConfigEntry( + domain="seventeentrack", + data=VALID_CONFIG, + options={ + CONF_SHOW_ARCHIVED: DEFAULT_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED: DEFAULT_SHOW_DELIVERED, + }, + unique_id=ACCOUNT_ID, + ) + @pytest.fixture def mock_seventeentrack(): @@ -69,10 +109,15 @@ def mock_seventeentrack(): mock_seventeentrack_api = AsyncMock() with ( patch( - "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient", + "homeassistant.components.seventeentrack.SeventeenTrackClient", + return_value=mock_seventeentrack_api, + ), + patch( + "homeassistant.components.seventeentrack.config_flow.SeventeenTrackClient", return_value=mock_seventeentrack_api, ) as mock_seventeentrack_api, ): + mock_seventeentrack_api.return_value.profile.account_id = ACCOUNT_ID mock_seventeentrack_api.return_value.profile.login.return_value = True mock_seventeentrack_api.return_value.profile.packages.return_value = [] mock_seventeentrack_api.return_value.profile.summary.return_value = ( diff --git a/tests/components/seventeentrack/test_config_flow.py b/tests/components/seventeentrack/test_config_flow.py new file mode 100644 index 00000000000..04e4b50d066 --- /dev/null +++ b/tests/components/seventeentrack/test_config_flow.py @@ -0,0 +1,208 @@ +"""Define tests for the 17Track config flow.""" + +from unittest.mock import AsyncMock + +from py17track.errors import SeventeenTrackError +import pytest + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.seventeentrack import DOMAIN +from homeassistant.components.seventeentrack.const import ( + CONF_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED, +) +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + +ACCOUNT_ID = "1234" + +VALID_CONFIG = { + CONF_USERNAME: "someemail@gmail.com", + CONF_PASSWORD: "edc3eee7330e4fdda04489e3fbc283d0", +} + +VALID_CONFIG_OLD = { + CONF_USERNAME: "someemail@gmail.com", + CONF_PASSWORD: "edc3eee7330e4fdda04489e3fbc283d0", +} + + +async def test_create_entry( + hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_seventeentrack: AsyncMock +) -> None: + """Test that the user step works.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + VALID_CONFIG, + ) + await hass.async_block_till_done() + + assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result2["title"] == "someemail@gmail.com" + assert result2["data"] == { + CONF_PASSWORD: "edc3eee7330e4fdda04489e3fbc283d0", + CONF_USERNAME: "someemail@gmail.com", + } + + +@pytest.mark.parametrize( + ("return_value", "side_effect", "error"), + [ + ( + False, + None, + "invalid_auth", + ), + ( + True, + SeventeenTrackError(), + "cannot_connect", + ), + ], +) +async def test_flow_fails( + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + return_value, + side_effect, + error, +) -> None: + """Test that the user step fails.""" + mock_seventeentrack.return_value.profile.login.return_value = return_value + mock_seventeentrack.return_value.profile.login.side_effect = side_effect + failed_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=VALID_CONFIG, + ) + + assert failed_result["errors"] == {"base": error} + + mock_seventeentrack.return_value.profile.login.return_value = True + mock_seventeentrack.return_value.profile.login.side_effect = None + + result = await hass.config_entries.flow.async_configure( + failed_result["flow_id"], + VALID_CONFIG, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == "someemail@gmail.com" + assert result["data"] == { + CONF_PASSWORD: "edc3eee7330e4fdda04489e3fbc283d0", + CONF_USERNAME: "someemail@gmail.com", + } + + +async def test_import_flow(hass: HomeAssistant, mock_seventeentrack: AsyncMock) -> None: + """Test the import configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=VALID_CONFIG_OLD, + ) + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == "someemail@gmail.com" + assert result["data"][CONF_USERNAME] == "someemail@gmail.com" + assert result["data"][CONF_PASSWORD] == "edc3eee7330e4fdda04489e3fbc283d0" + + +@pytest.mark.parametrize( + ("return_value", "side_effect", "error"), + [ + ( + False, + None, + "invalid_credentials", + ), + ( + True, + SeventeenTrackError(), + "cannot_connect", + ), + ], +) +async def test_import_flow_cannot_connect_error( + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + return_value, + side_effect, + error, +) -> None: + """Test the import configuration flow with error.""" + mock_seventeentrack.return_value.profile.login.return_value = return_value + mock_seventeentrack.return_value.profile.login.side_effect = side_effect + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=VALID_CONFIG_OLD, + ) + + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == error + + +async def test_option_flow(hass: HomeAssistant, mock_seventeentrack: AsyncMock) -> None: + """Test option flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=VALID_CONFIG, + options={ + CONF_SHOW_ARCHIVED: False, + CONF_SHOW_DELIVERED: False, + }, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_SHOW_ARCHIVED: True, CONF_SHOW_DELIVERED: False}, + ) + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["data"][CONF_SHOW_ARCHIVED] + assert not result["data"][CONF_SHOW_DELIVERED] + + +async def test_import_flow_already_configured( + hass: HomeAssistant, mock_seventeentrack: AsyncMock +) -> None: + """Test the import configuration flow with error.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=VALID_CONFIG, + unique_id=ACCOUNT_ID, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + result_aborted = await hass.config_entries.flow.async_configure( + result["flow_id"], + VALID_CONFIG, + ) + await hass.async_block_till_done() + + assert result_aborted["type"] == data_entry_flow.FlowResultType.ABORT + assert result_aborted["reason"] == "already_configured" diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index de345dcf11a..aa7f61ad318 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -8,61 +8,73 @@ from freezegun.api import FrozenDateTimeFactory from py17track.errors import SeventeenTrackError from homeassistant.core import HomeAssistant +from homeassistant.helpers.issue_registry import IssueRegistry +from homeassistant.setup import async_setup_component from . import goto_future, init_integration from .conftest import ( DEFAULT_SUMMARY, - INVALID_CONFIG, NEW_SUMMARY_DATA, - VALID_CONFIG_FULL, - VALID_CONFIG_FULL_NO_DELIVERED, - VALID_CONFIG_MINIMAL, + VALID_PLATFORM_CONFIG_FULL, get_package, ) +from tests.common import MockConfigEntry + async def test_full_valid_config( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure everything starts correctly.""" - await init_integration(hass, VALID_CONFIG_MINIMAL) + await init_integration(hass, mock_config_entry) assert len(hass.states.async_entity_ids()) == len(DEFAULT_SUMMARY.keys()) async def test_valid_config( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure everything starts correctly.""" - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert len(hass.states.async_entity_ids()) == len(DEFAULT_SUMMARY.keys()) -async def test_invalid_config(hass: HomeAssistant) -> None: +async def test_invalid_config( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: """Ensure nothing is created when config is wrong.""" - await init_integration(hass, INVALID_CONFIG) + await init_integration(hass, mock_config_entry) assert not hass.states.async_entity_ids("sensor") async def test_login_exception( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure everything starts correctly.""" mock_seventeentrack.return_value.profile.login.side_effect = SeventeenTrackError( "Error" ) - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert not hass.states.async_entity_ids("sensor") async def test_add_package( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure package is added correctly when user add a new package.""" package = get_package() mock_seventeentrack.return_value.profile.packages.return_value = [package] mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 @@ -82,14 +94,16 @@ async def test_add_package( async def test_add_package_default_friendly_name( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure package is added correctly with default friendly name when user add a new package without his own friendly name.""" package = get_package(friendly_name=None) mock_seventeentrack.return_value.profile.packages.return_value = [package] mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) state_456 = hass.states.get("sensor.seventeentrack_package_456") assert state_456 is not None assert state_456.attributes["friendly_name"] == "Seventeentrack Package: 456" @@ -97,7 +111,10 @@ async def test_add_package_default_friendly_name( async def test_remove_package( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure entity is not there anymore if package is not there.""" package1 = get_package() @@ -115,7 +132,7 @@ async def test_remove_package( ] mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert hass.states.get("sensor.seventeentrack_package_789") is not None @@ -136,7 +153,9 @@ async def test_remove_package( async def test_package_error( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure package is added correctly when user add a new package.""" mock_seventeentrack.return_value.profile.packages.side_effect = SeventeenTrackError( @@ -144,19 +163,22 @@ async def test_package_error( ) mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is None async def test_friendly_name_changed( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test friendly name change.""" package = get_package() mock_seventeentrack.return_value.profile.packages.return_value = [package] mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 @@ -175,7 +197,10 @@ async def test_friendly_name_changed( async def test_delivered_not_shown( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_seventeentrack: AsyncMock, + mock_config_entry_with_default_options: MockConfigEntry, ) -> None: """Ensure delivered packages are not shown.""" package = get_package(status=40) @@ -185,7 +210,7 @@ async def test_delivered_not_shown( with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" ) as persistent_notification_mock: - await init_integration(hass, VALID_CONFIG_FULL_NO_DELIVERED) + await init_integration(hass, mock_config_entry_with_default_options) await goto_future(hass, freezer) assert not hass.states.async_entity_ids() @@ -193,7 +218,9 @@ async def test_delivered_not_shown( async def test_delivered_shown( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure delivered packages are show when user choose to show them.""" package = get_package(status=40) @@ -203,7 +230,7 @@ async def test_delivered_shown( with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" ) as persistent_notification_mock: - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 @@ -211,14 +238,17 @@ async def test_delivered_shown( async def test_becomes_delivered_not_shown_notification( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_seventeentrack: AsyncMock, + mock_config_entry_with_default_options: MockConfigEntry, ) -> None: """Ensure notification is triggered when package becomes delivered.""" package = get_package() mock_seventeentrack.return_value.profile.packages.return_value = [package] mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL_NO_DELIVERED) + await init_integration(hass, mock_config_entry_with_default_options) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 @@ -237,14 +267,17 @@ async def test_becomes_delivered_not_shown_notification( async def test_summary_correctly_updated( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure summary entities are not duplicated.""" package = get_package(status=30) mock_seventeentrack.return_value.profile.packages.return_value = [package] mock_seventeentrack.return_value.profile.summary.return_value = DEFAULT_SUMMARY - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert len(hass.states.async_entity_ids()) == 8 @@ -272,7 +305,9 @@ async def test_summary_correctly_updated( async def test_summary_error( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Test summary empty if error.""" package = get_package(status=30) @@ -281,7 +316,7 @@ async def test_summary_error( "Error" ) - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert len(hass.states.async_entity_ids()) == 1 @@ -291,7 +326,9 @@ async def test_summary_error( async def test_utc_timestamp( - hass: HomeAssistant, mock_seventeentrack: AsyncMock + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure package timestamp is converted correctly from HA-defined time zone to UTC.""" @@ -299,7 +336,7 @@ async def test_utc_timestamp( mock_seventeentrack.return_value.profile.packages.return_value = [package] mock_seventeentrack.return_value.profile.summary.return_value = {} - await init_integration(hass, VALID_CONFIG_FULL) + await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert len(hass.states.async_entity_ids()) == 1 @@ -313,5 +350,18 @@ async def test_non_valid_platform_config( ) -> None: """Test if login fails.""" mock_seventeentrack.return_value.profile.login.return_value = False - await init_integration(hass, VALID_CONFIG_FULL) + assert await async_setup_component(hass, "sensor", VALID_PLATFORM_CONFIG_FULL) + await hass.async_block_till_done() assert len(hass.states.async_entity_ids()) == 0 + + +async def test_full_valid_platform_config( + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + issue_registry: IssueRegistry, +) -> None: + """Ensure everything starts correctly.""" + assert await async_setup_component(hass, "sensor", VALID_PLATFORM_CONFIG_FULL) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids()) == len(DEFAULT_SUMMARY.keys()) + assert len(issue_registry.issues) == 1 From f8d1232598988842791c66c3315bb993e3bd433c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 13:26:44 +0100 Subject: [PATCH 0765/1691] Move HomematicIP Cloud fixture to integration test (#112987) * Move HomematicIP Cloud fixture to integration test * Update tests/components/homematicip_cloud/helper.py * Update tests/components/homematicip_cloud/helper.py --------- Co-authored-by: Paulus Schoutsen --- .../homematicip_cloud}/fixtures/homematicip_cloud.json | 0 tests/components/homematicip_cloud/helper.py | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) rename tests/{ => components/homematicip_cloud}/fixtures/homematicip_cloud.json (100%) diff --git a/tests/fixtures/homematicip_cloud.json b/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json similarity index 100% rename from tests/fixtures/homematicip_cloud.json rename to tests/components/homematicip_cloud/fixtures/homematicip_cloud.json diff --git a/tests/components/homematicip_cloud/helper.py b/tests/components/homematicip_cloud/helper.py index d073cfc4e3d..5bc11b77d32 100644 --- a/tests/components/homematicip_cloud/helper.py +++ b/tests/components/homematicip_cloud/helper.py @@ -28,8 +28,7 @@ from tests.common import load_fixture HAPID = "3014F7110000000000000001" HAPPIN = "5678" AUTH_TOKEN = "1234" -HOME_JSON = "homematicip_cloud.json" -FIXTURE_DATA = load_fixture(HOME_JSON) +FIXTURE_DATA = load_fixture("homematicip_cloud.json", "homematicip_cloud") def get_and_check_entity_basics(hass, mock_hap, entity_id, entity_name, device_model): From 48cb09a4a85f9c90c075381be2d4cc5a0a92b355 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Mon, 11 Mar 2024 12:42:52 +0000 Subject: [PATCH 0766/1691] Prepare ring update service for deprecation (#108781) * Prepare ring update service for deprecation * Update service removal release number --- homeassistant/components/ring/__init__.py | 18 ++++++++++++++ homeassistant/components/ring/strings.json | 13 ++++++++++ tests/components/ring/test_init.py | 28 ++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 5470d997fab..79d90c87f0e 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import APPLICATION_NAME, CONF_TOKEN, __version__ from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import ( DOMAIN, @@ -62,6 +63,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_refresh_all(_: ServiceCall) -> None: """Refresh all ring data.""" + _LOGGER.warning( + "Detected use of service 'ring.update'. " + "This is deprecated and will stop working in Home Assistant 2024.10. " + "Use 'homeassistant.update_entity' instead which updates all ring entities", + ) + async_create_issue( + hass, + DOMAIN, + "deprecated_service_ring_update", + breaks_in_ha_version="2024.10.0", + is_fixable=True, + is_persistent=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_service_ring_update", + ) + for info in hass.data[DOMAIN].values(): await info[RING_DEVICES_COORDINATOR].async_refresh() await info[RING_NOTIFICATIONS_COORDINATOR].async_refresh() diff --git a/homeassistant/components/ring/strings.json b/homeassistant/components/ring/strings.json index 688e3141beb..ee7dbc000f5 100644 --- a/homeassistant/components/ring/strings.json +++ b/homeassistant/components/ring/strings.json @@ -78,5 +78,18 @@ "name": "Update", "description": "Updates the data we have for all your ring devices." } + }, + "issues": { + "deprecated_service_ring_update": { + "title": "Detected use of deprecated service `ring.update`", + "fix_flow": { + "step": { + "confirm": { + "title": "[%key:component::ring::issues::deprecated_service_ring_update::title%]", + "description": "Use `homeassistant.update_entity` instead which will update all ring entities.\n\nPlease replace calls to this service and adjust your automations and scripts and select **submit** to close this issue." + } + } + } + } } } diff --git a/tests/components/ring/test_init.py b/tests/components/ring/test_init.py index 64fca9eac2f..ba5dd03ba9c 100644 --- a/tests/components/ring/test_init.py +++ b/tests/components/ring/test_init.py @@ -11,6 +11,7 @@ import homeassistant.components.ring as ring from homeassistant.components.ring import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers.issue_registry import IssueRegistry from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -238,3 +239,30 @@ async def test_error_on_device_update( record.message for record in caplog.records if record.levelname == "ERROR" ] assert mock_config_entry.entry_id in hass.data[DOMAIN] + + +async def test_issue_deprecated_service_ring_update( + hass: HomeAssistant, + issue_registry: IssueRegistry, + caplog: pytest.LogCaptureFixture, + requests_mock: requests_mock.Mocker, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the issue is raised on deprecated service ring.update.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + _ = await hass.services.async_call(DOMAIN, "update", {}, blocking=True) + + issue = issue_registry.async_get_issue("ring", "deprecated_service_ring_update") + assert issue + assert issue.issue_domain == "ring" + assert issue.issue_id == "deprecated_service_ring_update" + assert issue.translation_key == "deprecated_service_ring_update" + + assert ( + "Detected use of service 'ring.update'. " + "This is deprecated and will stop working in Home Assistant 2024.10. " + "Use 'homeassistant.update_entity' instead which updates all ring entities" + ) in caplog.text From 2792a5f01696ba1c25d3a94c9015e8d55c274115 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 14:23:52 +0100 Subject: [PATCH 0767/1691] Enable even more SIM ruff rules (#113017) * SIM202 SIM211 SIM220 SIM221 SIM222 SIM223 * SIM910 SIM911 * SIM * Update homeassistant/components/mqtt/siren.py Co-authored-by: Jan Bouwhuis * Update homeassistant/components/mqtt/siren.py Co-authored-by: Jan Bouwhuis --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/bloomsky/sensor.py | 8 ++----- homeassistant/components/lifx/util.py | 2 +- homeassistant/components/matter/api.py | 2 +- .../components/modbus/base_platform.py | 6 +++--- homeassistant/components/modbus/validators.py | 8 ++++--- homeassistant/components/mqtt/siren.py | 5 +---- homeassistant/components/whirlpool/sensor.py | 2 +- homeassistant/components/yolink/sensor.py | 2 +- pyproject.toml | 21 ++++--------------- tests/components/lutron_caseta/__init__.py | 2 +- 10 files changed, 20 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index c6a74731d37..1f63b4a7256 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -102,13 +102,9 @@ class BloomSkySensor(SensorEntity): self._attr_name = f"{device['DeviceName']} {sensor_name}" self._attr_unique_id = f"{self._device_id}-{sensor_name}" self._attr_device_class = SENSOR_DEVICE_CLASS.get(sensor_name) - self._attr_native_unit_of_measurement = SENSOR_UNITS_IMPERIAL.get( - sensor_name, None - ) + self._attr_native_unit_of_measurement = SENSOR_UNITS_IMPERIAL.get(sensor_name) if self._bloomsky.is_metric: - self._attr_native_unit_of_measurement = SENSOR_UNITS_METRIC.get( - sensor_name, None - ) + self._attr_native_unit_of_measurement = SENSOR_UNITS_METRIC.get(sensor_name) def update(self) -> None: """Request an update from the BloomSky API.""" diff --git a/homeassistant/components/lifx/util.py b/homeassistant/components/lifx/util.py index 5d41839f61d..bb77c7595d3 100644 --- a/homeassistant/components/lifx/util.py +++ b/homeassistant/components/lifx/util.py @@ -62,7 +62,7 @@ def infrared_brightness_value_to_option(value: int) -> str | None: def infrared_brightness_option_to_value(option: str) -> int | None: """Convert infrared brightness option to value.""" option_values = {v: k for k, v in INFRARED_BRIGHTNESS_VALUES_MAP.items()} - return option_values.get(option, None) + return option_values.get(option) def convert_8_to_16(value: int) -> int: diff --git a/homeassistant/components/matter/api.py b/homeassistant/components/matter/api.py index 94980617c0d..e6a2a6c54d5 100644 --- a/homeassistant/components/matter/api.py +++ b/homeassistant/components/matter/api.py @@ -166,7 +166,7 @@ async def websocket_commission_on_network( ) -> None: """Commission a device already on the network.""" await matter.matter_client.commission_on_network( - msg["pin"], ip_addr=msg.get("ip_addr", None) + msg["pin"], ip_addr=msg.get("ip_addr") ) connection.send_result(msg[ID]) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 24d6ce2b307..82b2b788e78 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -102,7 +102,7 @@ class BasePlatform(Entity): ) self._hub = hub - self._slave = entry.get(CONF_SLAVE, None) or entry.get(CONF_DEVICE_ADDRESS, 0) + self._slave = entry.get(CONF_SLAVE) or entry.get(CONF_DEVICE_ADDRESS, 0) self._address = int(entry[CONF_ADDRESS]) self._input_type = entry[CONF_INPUT_TYPE] self._value: str | None = None @@ -128,7 +128,7 @@ class BasePlatform(Entity): self._min_value = get_optional_numeric_config(CONF_MIN_VALUE) self._max_value = get_optional_numeric_config(CONF_MAX_VALUE) - self._nan_value = entry.get(CONF_NAN_VALUE, None) + self._nan_value = entry.get(CONF_NAN_VALUE) self._zero_suppress = get_optional_numeric_config(CONF_ZERO_SUPPRESS) @abstractmethod @@ -184,7 +184,7 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): self._structure: str = config[CONF_STRUCTURE] self._scale = config[CONF_SCALE] self._offset = config[CONF_OFFSET] - self._slave_count = config.get(CONF_SLAVE_COUNT, None) or config.get( + self._slave_count = config.get(CONF_SLAVE_COUNT) or config.get( CONF_VIRTUAL_COUNT, 0 ) self._slave_size = self._count = config[CONF_COUNT] diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index c484b270afc..2e434933eae 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -118,8 +118,8 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]: data_type = config[CONF_DATA_TYPE] if data_type == "int": data_type = config[CONF_DATA_TYPE] = DataType.INT16 - count = config.get(CONF_COUNT, None) - structure = config.get(CONF_STRUCTURE, None) + count = config.get(CONF_COUNT) + structure = config.get(CONF_STRUCTURE) slave_count = config.get(CONF_SLAVE_COUNT, config.get(CONF_VIRTUAL_COUNT)) validator = DEFAULT_STRUCT_FORMAT[data_type].validate_parm swap_type = config.get(CONF_SWAP) @@ -148,6 +148,8 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]: raise vol.Invalid(error) if config[CONF_DATA_TYPE] == DataType.CUSTOM: + assert isinstance(structure, str) + assert isinstance(count, int) try: size = struct.calcsize(structure) except struct.error as err: @@ -332,7 +334,7 @@ def check_config(config: dict) -> dict: ): if conf_type in entity: addr += f"_{entity[conf_type]}" - inx = entity.get(CONF_SLAVE, None) or entity.get(CONF_DEVICE_ADDRESS, 0) + inx = entity.get(CONF_SLAVE) or entity.get(CONF_DEVICE_ADDRESS, 0) addr += f"_{inx}" loc_addr: set[str] = {addr} diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 2725743735d..5eda05d531f 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -301,10 +301,7 @@ class MqttSiren(MqttEntity, SirenEntity): else {} ) if extra_attributes: - return ( - dict({*self._extra_attributes.items(), *extra_attributes.items()}) - or None - ) + return dict({*self._extra_attributes.items(), *extra_attributes.items()}) return self._extra_attributes or None async def _async_publish( diff --git a/homeassistant/components/whirlpool/sensor.py b/homeassistant/components/whirlpool/sensor.py index a710dfb81a8..d49459cc726 100644 --- a/homeassistant/components/whirlpool/sensor.py +++ b/homeassistant/components/whirlpool/sensor.py @@ -87,7 +87,7 @@ def washer_state(washer: WasherDryer) -> str | None: if func(washer): return cycle_name - return MACHINE_STATE.get(machine_state, None) + return MACHINE_STATE.get(machine_state) @dataclass(frozen=True, kw_only=True) diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index b69e29db031..17aa968840f 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -119,7 +119,7 @@ def cvt_volume(val: int | None) -> str | None: if val is None: return None volume_level = {1: "low", 2: "medium", 3: "high"} - return volume_level.get(val, None) + return volume_level.get(val) SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( diff --git a/pyproject.toml b/pyproject.toml index 6d4bfb9e4e2..bcfcfbc4a4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -627,23 +627,7 @@ select = [ "S604", # call-with-shell-equals-true "S608", # hardcoded-sql-expression "S609", # unix-command-wildcard-injection - "SIM101", # Multiple isinstance calls for {name}, merge into a single call - "SIM103", # Return the condition {condition} directly - "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass - "SIM107", # Don't use return in try-except and finally - "SIM109", # Use {replacement} instead of multiple equality comparisons - "SIM110", # Use {replacement} instead of for loop - "SIM112", # Use capitalized environment variable {expected} instead of {actual} - "SIM113", # Use enumerate() for index variable {index} in for loop - "SIM114", # Combine if branches using logical or operator - "SIM116", # Use a dictionary instead of consecutive if statements - "SIM117", # Merge with-statements that use the same scope - "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() - "SIM201", # Use {left} != {right} instead of not {left} == {right} - "SIM208", # Use {expr} instead of not (not {expr}) - "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a} - "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'. - "SIM401", # Use get from dict with default instead of an if block + "SIM", # flake8-simplify "T100", # Trace found: {name} used "T20", # flake8-print "TID251", # Banned imports @@ -675,6 +659,9 @@ ignore = [ "PLR0915", # Too many statements ({statements} > {max_statements}) "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target + "SIM102", # Use a single if statement instead of nested if statements + "SIM108", # Use ternary operator {contents} instead of if-else-block + "SIM115", # Use context handler for opening files "UP006", # keep type annotation style as is "UP007", # keep type annotation style as is # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 diff --git a/tests/components/lutron_caseta/__init__.py b/tests/components/lutron_caseta/__init__.py index c82a0c78ea1..cc785f71e19 100644 --- a/tests/components/lutron_caseta/__init__.py +++ b/tests/components/lutron_caseta/__init__.py @@ -283,7 +283,7 @@ class MockBridge: :param domain: one of 'light', 'switch', 'cover', 'fan' or 'sensor' :returns list of zero or more of the devices """ - types = _LEAP_DEVICE_TYPES.get(domain, None) + types = _LEAP_DEVICE_TYPES.get(domain) # return immediately if not a supported domain if types is None: From 0a7598e2d495351dec36f3e3e6186889865c3190 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 11 Mar 2024 14:49:52 +0100 Subject: [PATCH 0768/1691] Fix for controlling Hue switch entities (#113064) --- homeassistant/components/hue/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hue/switch.py b/homeassistant/components/hue/switch.py index 022a12f6ffc..b4bc57acf2d 100644 --- a/homeassistant/components/hue/switch.py +++ b/homeassistant/components/hue/switch.py @@ -58,7 +58,7 @@ async def async_setup_entry( event_type: EventType, resource: BehaviorInstance | LightLevel | Motion ) -> None: """Add entity from Hue resource.""" - async_add_entities([switch_class(bridge, api.sensors.motion, resource)]) + async_add_entities([switch_class(bridge, controller, resource)]) # add all current items in controller for item in controller: From ba971890a9f9f84889caf788902bb9ecf8d06321 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Mon, 11 Mar 2024 15:05:31 +0100 Subject: [PATCH 0769/1691] Fix hvac_mode for viessmann devices with heatingCooling mode (#113054) --- homeassistant/components/vicare/climate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index f798f3646fc..4b231443350 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -53,6 +53,7 @@ SERVICE_SET_VICARE_MODE_ATTR_MODE = "vicare_mode" VICARE_MODE_DHW = "dhw" VICARE_MODE_HEATING = "heating" +VICARE_MODE_HEATINGCOOLING = "heatingCooling" VICARE_MODE_DHWANDHEATING = "dhwAndHeating" VICARE_MODE_DHWANDHEATINGCOOLING = "dhwAndHeatingCooling" VICARE_MODE_FORCEDREDUCED = "forcedReduced" @@ -72,6 +73,7 @@ VICARE_TO_HA_HVAC_HEATING: dict[str, HVACMode] = { VICARE_MODE_DHW: HVACMode.OFF, VICARE_MODE_DHWANDHEATINGCOOLING: HVACMode.AUTO, VICARE_MODE_DHWANDHEATING: HVACMode.AUTO, + VICARE_MODE_HEATINGCOOLING: HVACMode.AUTO, VICARE_MODE_HEATING: HVACMode.AUTO, VICARE_MODE_FORCEDNORMAL: HVACMode.HEAT, } From 0eb1adccf810c93368f8632a50a1de096ce60528 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 11 Mar 2024 15:08:00 +0100 Subject: [PATCH 0770/1691] Fix for Hue not applying effect in scene (#113057) --- homeassistant/components/hue/v2/light.py | 3 +++ tests/components/hue/test_light_v2.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/homeassistant/components/hue/v2/light.py b/homeassistant/components/hue/v2/light.py index 9fb883d1930..b908ec83877 100644 --- a/homeassistant/components/hue/v2/light.py +++ b/homeassistant/components/hue/v2/light.py @@ -235,6 +235,9 @@ class HueLight(HueBaseEntity, LightEntity): if transition is None: # a transition is required for timed effect, default to 10 minutes transition = 600000 + # we need to clear color values if an effect is applied + color_temp = None + xy_color = None if flash is not None: await self.async_set_flash(flash) diff --git a/tests/components/hue/test_light_v2.py b/tests/components/hue/test_light_v2.py index 46d22842373..b55da314bac 100644 --- a/tests/components/hue/test_light_v2.py +++ b/tests/components/hue/test_light_v2.py @@ -205,6 +205,28 @@ async def test_light_turn_on_service( ) assert mock_bridge_v2.mock_requests[8]["json"]["timed_effects"]["duration"] == 6000 + # test enabling effect should ignore color temperature + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": test_light_id, "effect": "candle", "color_temp": 500}, + blocking=True, + ) + assert len(mock_bridge_v2.mock_requests) == 10 + assert mock_bridge_v2.mock_requests[9]["json"]["effects"]["effect"] == "candle" + assert "color_temperature" not in mock_bridge_v2.mock_requests[9]["json"] + + # test enabling effect should ignore xy color + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": test_light_id, "effect": "candle", "xy_color": [0.123, 0.123]}, + blocking=True, + ) + assert len(mock_bridge_v2.mock_requests) == 11 + assert mock_bridge_v2.mock_requests[10]["json"]["effects"]["effect"] == "candle" + assert "xy_color" not in mock_bridge_v2.mock_requests[9]["json"] + async def test_light_turn_off_service( hass: HomeAssistant, mock_bridge_v2, v2_resources_test_data From d939c48b4aa292dd66a1369cae0bec9044e08887 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 04:11:33 -1000 Subject: [PATCH 0771/1691] Fix here_travel_time creating many refresh requests at startup (#113041) --- .../components/here_travel_time/__init__.py | 26 ++++++------- .../components/here_travel_time/sensor.py | 6 --- .../here_travel_time/test_sensor.py | 37 +++++++++++-------- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/here_travel_time/__init__.py b/homeassistant/components/here_travel_time/__init__.py index 19ba1f8ebaf..9da1ce491f0 100644 --- a/homeassistant/components/here_travel_time/__init__.py +++ b/homeassistant/components/here_travel_time/__init__.py @@ -5,6 +5,7 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_MODE, Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers.start import async_at_started from homeassistant.util import dt as dt_util from .const import ( @@ -49,22 +50,19 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b departure=departure, ) + cls: type[HERETransitDataUpdateCoordinator] | type[HERERoutingDataUpdateCoordinator] if config_entry.data[CONF_MODE] in {TRAVEL_MODE_PUBLIC, "publicTransportTimeTable"}: - hass.data.setdefault(DOMAIN, {})[ - config_entry.entry_id - ] = HERETransitDataUpdateCoordinator( - hass, - api_key, - here_travel_time_config, - ) + cls = HERETransitDataUpdateCoordinator else: - hass.data.setdefault(DOMAIN, {})[ - config_entry.entry_id - ] = HERERoutingDataUpdateCoordinator( - hass, - api_key, - here_travel_time_config, - ) + cls = HERERoutingDataUpdateCoordinator + + data_coordinator = cls(hass, api_key, here_travel_time_config) + hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = data_coordinator + + async def _async_update_at_start(_: HomeAssistant) -> None: + await data_coordinator.async_refresh() + + config_entry.async_on_unload(async_at_started(hass, _async_update_at_start)) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) return True diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 1fd4f8d94bc..190a8455e83 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -25,7 +25,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.start import async_at_started from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( @@ -141,11 +140,6 @@ class HERETravelTimeSensor( await self._async_restore_state() await super().async_added_to_hass() - async def _update_at_start(_: HomeAssistant) -> None: - await self.async_update() - - self.async_on_remove(async_at_started(self.hass, _update_at_start)) - @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index 6c5cd776ff2..0231ac6428f 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -60,7 +60,7 @@ from homeassistant.const import ( CONF_API_KEY, CONF_MODE, CONF_NAME, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, UnitOfLength, UnitOfTime, ) @@ -122,6 +122,7 @@ async def test_sensor( departure_time, ) -> None: """Test that sensor works.""" + hass.set_state(CoreState.not_running) entry = MockConfigEntry( domain=DOMAIN, unique_id="0123456789", @@ -143,7 +144,7 @@ async def test_sensor( entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() duration = hass.states.get("sensor.test_duration") @@ -201,7 +202,7 @@ async def test_circular_ref( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert "No coordinates found for test.first" in caplog.text @@ -210,6 +211,7 @@ async def test_circular_ref( @pytest.mark.usefixtures("valid_response") async def test_public_transport(hass: HomeAssistant) -> None: """Test that public transport mode is handled.""" + hass.set_state(CoreState.not_running) entry = MockConfigEntry( domain=DOMAIN, unique_id="0123456789", @@ -232,7 +234,7 @@ async def test_public_transport(hass: HomeAssistant) -> None: await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert ( @@ -263,7 +265,7 @@ async def test_no_attribution_response(hass: HomeAssistant) -> None: await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert ( @@ -273,6 +275,7 @@ async def test_no_attribution_response(hass: HomeAssistant) -> None: async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock) -> None: """Test that origin/destination supplied by entities works.""" + hass.set_state(CoreState.not_running) zone_config = { "zone": [ { @@ -309,7 +312,7 @@ async def test_entity_ids(hass: HomeAssistant, valid_response: MagicMock) -> Non await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert hass.states.get("sensor.test_distance").state == "13.682" @@ -348,7 +351,7 @@ async def test_destination_entity_not_found( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert "Could not find entity device_tracker.test" in caplog.text @@ -376,7 +379,7 @@ async def test_origin_entity_not_found( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert "Could not find entity device_tracker.test" in caplog.text @@ -408,7 +411,7 @@ async def test_invalid_destination_entity_state( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert ( @@ -442,7 +445,7 @@ async def test_invalid_origin_entity_state( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert ( @@ -477,7 +480,7 @@ async def test_route_not_found( entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert "Route calculation failed: Couldn't find a route." in caplog.text @@ -635,6 +638,7 @@ async def test_transit_errors( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, exception, expected_message ) -> None: """Test that transit errors are correctly handled.""" + hass.set_state(CoreState.not_running) with patch( "here_transit.HERETransitApi.route", side_effect=exception(), @@ -656,7 +660,7 @@ async def test_transit_errors( entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert expected_message in caplog.text @@ -668,6 +672,7 @@ async def test_routing_rate_limit( freezer: FrozenDateTimeFactory, ) -> None: """Test that rate limiting is applied when encountering HTTP 429.""" + hass.set_state(CoreState.not_running) with patch( "here_routing.HERERoutingApi.route", return_value=RESPONSE, @@ -681,7 +686,7 @@ async def test_routing_rate_limit( entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert hass.states.get("sensor.test_distance").state == "13.682" @@ -716,6 +721,7 @@ async def test_transit_rate_limit( freezer: FrozenDateTimeFactory, ) -> None: """Test that rate limiting is applied when encountering HTTP 429.""" + hass.set_state(CoreState.not_running) with patch( "here_transit.HERETransitApi.route", return_value=TRANSIT_RESPONSE, @@ -737,7 +743,7 @@ async def test_transit_rate_limit( entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert hass.states.get("sensor.test_distance").state == "1.883" @@ -771,6 +777,7 @@ async def test_multiple_sections( hass: HomeAssistant, ) -> None: """Test that multiple sections are handled correctly.""" + hass.set_state(CoreState.not_running) entry = MockConfigEntry( domain=DOMAIN, unique_id="0123456789", @@ -788,7 +795,7 @@ async def test_multiple_sections( entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() duration = hass.states.get("sensor.test_duration") From e13d8200cce5e86ecd6c038db5dc56686d9bda34 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 15:12:24 +0100 Subject: [PATCH 0772/1691] Remove entity description mixin in Github (#112771) --- homeassistant/components/github/sensor.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 2d373de56ea..a0e38862471 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -23,27 +23,16 @@ from .const import DOMAIN from .coordinator import GitHubDataUpdateCoordinator -@dataclass(frozen=True) -class BaseEntityDescriptionMixin: - """Mixin for required GitHub base description keys.""" +@dataclass(frozen=True, kw_only=True) +class GitHubSensorEntityDescription(SensorEntityDescription): + """Describes GitHub issue sensor entity.""" value_fn: Callable[[dict[str, Any]], StateType] - - -@dataclass(frozen=True) -class BaseEntityDescription(SensorEntityDescription): - """Describes GitHub sensor entity default overrides.""" - icon: str = "mdi:github" attr_fn: Callable[[dict[str, Any]], Mapping[str, Any] | None] = lambda data: None avabl_fn: Callable[[dict[str, Any]], bool] = lambda data: True -@dataclass(frozen=True) -class GitHubSensorEntityDescription(BaseEntityDescription, BaseEntityDescriptionMixin): - """Describes GitHub issue sensor entity.""" - - SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( GitHubSensorEntityDescription( key="discussions_count", From 5e2edb681921e3517063a4814f58c36f0caa6d5f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 04:13:41 -1000 Subject: [PATCH 0773/1691] Ensure wemo discovery is run in a background task (#112665) --- homeassistant/components/wemo/__init__.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index f354a5d5e50..fdb94de5f48 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -119,7 +119,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a wemo config entry.""" wemo_data = async_wemo_data(hass) dispatcher = WemoDispatcher(entry) - discovery = WemoDiscovery(hass, dispatcher, wemo_data.static_config) + discovery = WemoDiscovery(hass, dispatcher, wemo_data.static_config, entry) wemo_data.config_entry_data = WemoConfigEntryData( device_coordinators={}, discovery=discovery, @@ -246,6 +246,7 @@ class WemoDiscovery: hass: HomeAssistant, wemo_dispatcher: WemoDispatcher, static_config: Sequence[HostPortTuple], + entry: ConfigEntry, ) -> None: """Initialize the WemoDiscovery.""" self._hass = hass @@ -253,7 +254,8 @@ class WemoDiscovery: self._stop: CALLBACK_TYPE | None = None self._scan_delay = 0 self._static_config = static_config - self._discover_job: HassJob[[datetime], Coroutine[Any, Any, None]] | None = None + self._discover_job: HassJob[[datetime], None] | None = None + self._entry = entry async def async_discover_and_schedule( self, event_time: datetime | None = None @@ -274,13 +276,23 @@ class WemoDiscovery: self.MAX_SECONDS_BETWEEN_SCANS, ) if not self._discover_job: - self._discover_job = HassJob(self.async_discover_and_schedule) + self._discover_job = HassJob(self._async_discover_and_schedule_callback) self._stop = async_call_later( self._hass, self._scan_delay, self._discover_job, ) + @callback + def _async_discover_and_schedule_callback(self, event_time: datetime) -> None: + """Run the periodic background scanning.""" + self._entry.async_create_background_task( + self._hass, + self.async_discover_and_schedule(), + name="wemo_discovery", + eager_start=True, + ) + @callback def async_stop_discovery(self) -> None: """Stop the periodic background scanning.""" From bf40b3311738fe8eb1419852c62a13b398141db1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 15:19:54 +0100 Subject: [PATCH 0774/1691] Remove fixtures from global fixtures folder (#113060) Remove fixtures --- tests/fixtures/Ddwrt_Status_Lan.txt | 18 - tests/fixtures/Ddwrt_Status_Wireless.txt | 13 - tests/fixtures/alpr_stdout.txt | 12 - tests/fixtures/aurora.txt | 529 -------- tests/fixtures/bom_weather.json | 42 - tests/fixtures/coinmarketcap.json | 36 - tests/fixtures/darksky.json | 1475 ---------------------- tests/fixtures/upc_connect.xml | 42 - tests/fixtures/yahoo_finance.json | 85 -- tests/fixtures/yahooweather.json | 149 --- 10 files changed, 2401 deletions(-) delete mode 100644 tests/fixtures/Ddwrt_Status_Lan.txt delete mode 100644 tests/fixtures/Ddwrt_Status_Wireless.txt delete mode 100644 tests/fixtures/alpr_stdout.txt delete mode 100644 tests/fixtures/aurora.txt delete mode 100644 tests/fixtures/bom_weather.json delete mode 100644 tests/fixtures/coinmarketcap.json delete mode 100644 tests/fixtures/darksky.json delete mode 100644 tests/fixtures/upc_connect.xml delete mode 100644 tests/fixtures/yahoo_finance.json delete mode 100644 tests/fixtures/yahooweather.json diff --git a/tests/fixtures/Ddwrt_Status_Lan.txt b/tests/fixtures/Ddwrt_Status_Lan.txt deleted file mode 100644 index b61d92c365e..00000000000 --- a/tests/fixtures/Ddwrt_Status_Lan.txt +++ /dev/null @@ -1,18 +0,0 @@ -{lan_mac::AA:BB:CC:DD:EE:F0} -{lan_ip::192.168.1.1} -{lan_ip_prefix::192.168.1.} -{lan_netmask::255.255.255.0} -{lan_gateway::0.0.0.0} -{lan_dns::8.8.8.8} -{lan_proto::dhcp} -{dhcp_daemon::DNSMasq} -{dhcp_start::100} -{dhcp_num::50} -{dhcp_lease_time::1440} -{dhcp_leases:: 'device_1','192.168.1.113','AA:BB:CC:DD:EE:00','1 day 00:00:00','113','device_2','192.168.1.201','AA:BB:CC:DD:EE:01','Static','201'} -{pptp_leases::} -{pppoe_leases::} -{arp_table:: 'device_1','192.168.1.113','AA:BB:CC:DD:EE:00','13','device_2','192.168.1.201','AA:BB:CC:DD:EE:01','1'} -{uptime:: 12:28:48 up 132 days, 18:02, load average: 0.15, 0.19, 0.21} -{ipinfo:: IP: 192.168.0.108} - diff --git a/tests/fixtures/Ddwrt_Status_Wireless.txt b/tests/fixtures/Ddwrt_Status_Wireless.txt deleted file mode 100644 index 5343fea9904..00000000000 --- a/tests/fixtures/Ddwrt_Status_Wireless.txt +++ /dev/null @@ -1,13 +0,0 @@ -{wl_mac::AA:BB:CC:DD:EE:FF} -{wl_ssid::WIFI_SSD} -{wl_channel::10} -{wl_radio::Radio is On} -{wl_xmit::Auto} -{wl_rate::72 Mbps} -{wl_ack::} -{active_wireless::'AA:BB:CC:DD:EE:00','eth1','3:13:14','72M','24M','HT20','-9','-92','83','1048','AA:BB:CC:DD:EE:01','eth1','10:48:22','72M','72M','HT20','-40','-92','52','664'} -{active_wds::} -{packet_info::SWRXgoodPacket=173673555;SWRXerrorPacket=27;SWTXgoodPacket=311344396;SWTXerrorPacket=3107;} -{uptime:: 12:29:23 up 132 days, 18:03, load average: 0.16, 0.19, 0.20} -{ipinfo:: IP: 192.168.0.108} - diff --git a/tests/fixtures/alpr_stdout.txt b/tests/fixtures/alpr_stdout.txt deleted file mode 100644 index 255b57c5790..00000000000 --- a/tests/fixtures/alpr_stdout.txt +++ /dev/null @@ -1,12 +0,0 @@ - -plate0: top 10 results -- Processing Time = 58.1879ms. - - PE3R2X confidence: 98.9371 - - PE32X confidence: 98.1385 - - PE3R2 confidence: 97.5444 - - PE3R2Y confidence: 86.1448 - - P63R2X confidence: 82.9016 - - FE3R2X confidence: 72.1147 - - PE32 confidence: 66.7458 - - PE32Y confidence: 65.3462 - - P632X confidence: 62.1031 - - P63R2 confidence: 61.5089 diff --git a/tests/fixtures/aurora.txt b/tests/fixtures/aurora.txt deleted file mode 100644 index 22e8d0c2476..00000000000 --- a/tests/fixtures/aurora.txt +++ /dev/null @@ -1,529 +0,0 @@ -#Aurora Specification Tabular Values -# Product: Ovation Aurora Short Term Forecast -# Product Valid At: 2019-11-08 18:55 -# Product Generated At: 2019-11-08 18:25 -# -# Prepared by the U.S. Dept. of Commerce, NOAA, Space Weather Prediction Center. -# Please send comments and suggestions to SWPC.Webmaster@noaa.gov -# -# Missing Data: (n/a) -# Cadence: 5 minutes -# -# Tabular Data is on the following grid -# -# 1024 values covering 0 to 360 degrees in the horizontal (longitude) direction (0.32846715 degrees/value) -# 512 values covering -90 to 90 degrees in the vertical (latitude) direction (0.3515625 degrees/value) -# Values range from 0 (little or no probability of visible aurora) to 100 (high probability of visible aurora) -# - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 - 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 - 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 - 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 - 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 - 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 - 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 - 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 - 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 - 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 - 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 - 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 - 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 - 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 - 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 - 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 - 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 - 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 - 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 - 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 - 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 - 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 - 11 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 - 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 10 10 - 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 9 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 10 10 10 10 10 - 9 9 9 9 9 9 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 10 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 10 10 10 10 10 10 9 - 9 9 9 9 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 - 9 9 8 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 - 9 8 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 5 5 5 5 5 4 4 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 11 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 - 8 8 8 8 7 7 7 7 7 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 12 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 9 9 9 9 9 8 8 - 8 7 7 7 7 7 6 6 6 6 6 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 13 12 12 12 12 11 11 11 11 11 10 10 10 10 9 9 9 9 9 8 8 8 8 - 7 7 7 6 6 6 6 6 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 6 7 7 7 7 8 8 8 8 8 9 9 9 9 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 11 11 10 10 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14 14 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 12 12 12 12 11 11 11 11 10 10 10 10 9 9 9 9 8 8 8 8 7 7 - 7 6 6 6 6 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 13 13 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 13 13 13 13 13 13 13 12 12 12 12 12 12 12 11 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14 14 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 14 14 14 14 14 13 13 13 13 13 12 12 12 12 11 11 11 11 10 10 10 10 9 9 9 9 8 8 8 8 7 7 7 7 - 6 6 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14 14 14 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 14 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 11 11 11 10 10 10 10 9 9 9 9 8 8 8 8 7 7 7 7 6 6 6 - 5 5 5 5 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 8 8 8 8 8 9 9 9 9 10 10 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 14 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 14 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 11 11 11 11 10 10 10 9 9 9 9 8 8 8 8 7 7 7 7 6 6 6 6 5 5 - 4 4 4 4 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 7 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 14 14 14 14 14 15 15 15 15 15 15 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 15 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 15 15 15 15 15 15 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 15 15 15 15 15 15 14 14 14 14 14 13 13 13 13 13 12 12 12 12 11 11 11 10 10 10 10 9 9 9 8 8 8 8 7 7 7 6 6 6 6 6 5 5 5 5 4 4 4 - 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 11 11 11 11 12 12 12 12 13 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 16 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 16 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 16 16 16 16 16 15 15 15 15 15 14 14 14 14 13 13 13 12 12 12 12 11 11 11 10 10 10 9 9 9 8 8 8 7 7 7 7 6 6 6 5 5 5 5 4 4 4 4 4 3 3 3 3 - 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 11 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 17 17 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 17 17 17 17 17 17 16 16 16 16 16 16 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 17 17 17 17 16 16 16 16 16 15 15 15 14 14 14 13 13 13 13 12 12 12 11 11 10 10 10 9 9 9 8 8 7 7 7 7 6 6 6 5 5 5 4 4 4 4 3 3 3 3 3 2 2 2 2 2 - 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 11 11 11 11 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 16 17 17 17 17 17 18 18 18 18 18 18 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 18 18 18 18 18 17 17 17 17 17 17 16 16 16 16 16 15 15 15 15 14 14 14 14 14 13 13 13 13 13 12 12 12 12 11 11 11 11 11 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 18 18 18 18 18 18 18 18 17 17 17 17 17 16 16 16 16 15 15 15 15 14 14 14 13 13 13 12 12 12 11 11 11 10 10 10 9 9 9 8 8 7 7 7 6 6 6 6 5 5 5 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1 1 - 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 11 11 11 11 12 12 12 12 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 16 17 17 17 17 18 18 18 18 18 19 19 19 19 19 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 19 19 19 19 19 19 18 18 18 18 17 17 17 17 16 16 16 16 16 15 15 15 15 14 14 14 14 13 13 13 13 13 12 12 12 12 11 11 11 11 11 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 13 13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 16 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 16 16 16 16 15 15 15 14 14 14 14 13 13 12 12 12 11 11 11 10 10 10 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 5 4 4 4 4 3 3 3 3 2 2 2 2 2 2 1 1 1 1 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 11 11 11 11 12 12 12 12 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 20 20 20 20 20 19 19 19 19 18 18 18 18 17 17 17 17 16 16 16 16 15 15 15 15 15 14 14 14 14 13 13 13 13 12 12 12 12 11 11 11 11 11 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 16 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 16 16 16 16 16 15 15 15 14 14 14 14 13 13 13 12 12 12 11 11 10 10 10 9 9 9 8 8 8 7 7 7 7 6 6 6 5 5 5 5 4 4 4 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 8 8 8 8 9 9 9 9 10 10 10 11 11 11 11 12 12 12 13 13 13 14 14 14 15 15 15 16 16 16 16 17 17 17 18 18 18 19 19 19 20 20 20 20 21 21 21 21 21 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 21 21 21 21 21 21 20 20 20 20 19 19 19 19 18 18 18 18 17 17 17 17 16 16 16 16 15 15 15 15 14 14 14 14 13 13 13 13 12 12 12 12 12 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 17 17 17 16 16 16 16 15 15 15 15 14 14 14 13 13 13 12 12 12 11 11 11 10 10 10 9 9 9 8 8 8 7 7 7 7 6 6 6 6 5 5 5 5 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 1 1 1 1 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 9 9 9 9 10 10 11 11 11 12 12 12 13 13 13 14 14 14 15 15 15 16 16 17 17 17 18 18 18 19 19 19 20 20 20 21 21 21 21 22 22 22 22 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 22 22 22 22 22 22 21 21 21 21 20 20 20 20 20 19 19 19 19 18 18 18 17 17 17 17 16 16 16 16 15 15 15 15 14 14 14 14 13 13 13 13 13 12 12 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 18 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 18 18 18 18 18 17 17 16 16 16 15 15 15 14 14 13 13 13 12 12 12 11 11 11 11 10 10 10 9 9 9 8 8 8 8 7 7 7 6 6 6 6 5 5 5 5 4 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 13 13 13 14 14 15 15 15 16 16 17 17 17 18 18 18 19 19 19 20 20 20 21 21 21 22 22 22 22 23 23 23 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 23 23 23 23 23 22 22 22 22 21 21 21 21 20 20 20 20 20 19 19 19 18 18 18 18 17 17 17 17 16 16 16 16 15 15 15 15 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 15 15 15 15 16 16 16 17 17 17 17 18 18 18 18 18 19 19 19 19 19 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 19 19 19 19 18 18 18 17 17 17 16 16 15 15 15 14 14 13 13 12 12 12 11 11 10 10 10 9 9 9 8 8 8 7 7 7 7 6 6 6 5 5 5 5 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 5 6 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 12 12 12 13 13 14 14 14 15 15 16 16 16 17 17 17 17 18 18 18 19 19 19 20 20 20 21 21 21 22 22 22 23 23 23 23 24 24 24 24 24 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 24 24 24 24 24 23 23 23 22 22 22 22 21 21 21 21 20 20 20 20 20 19 19 19 19 19 18 18 18 17 17 17 16 16 16 16 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 13 12 12 12 12 12 12 12 11 11 11 11 11 11 11 10 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14 14 15 15 15 16 16 16 17 17 17 18 18 18 18 18 19 19 19 19 19 19 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 19 19 19 19 18 18 18 17 17 17 17 16 16 15 15 14 14 13 13 12 12 11 11 11 10 10 9 9 9 8 8 7 7 7 6 6 6 5 5 5 4 4 3 3 3 3 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 12 12 12 13 13 14 14 14 15 15 16 16 16 16 17 17 17 17 18 18 18 19 19 19 20 20 21 21 21 22 22 23 23 23 23 24 24 24 25 25 25 25 26 26 26 26 26 26 26 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 26 26 25 25 25 24 24 24 23 23 23 22 22 22 22 22 21 21 21 21 21 21 21 20 20 20 19 19 19 18 18 18 17 17 17 16 16 16 16 16 15 15 15 15 15 14 14 14 14 14 14 14 13 13 13 13 13 13 13 12 12 12 12 12 12 11 11 11 11 11 10 10 10 10 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 14 15 15 15 15 15 16 16 16 17 17 17 18 18 18 19 19 19 19 19 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 22 22 21 21 21 21 21 20 20 20 20 19 19 19 19 18 18 18 17 17 17 17 16 16 16 15 15 14 14 13 13 12 12 11 11 10 10 9 9 8 8 7 7 7 6 6 5 5 4 4 4 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 13 13 13 14 14 14 15 15 15 15 16 16 16 17 17 17 18 18 19 19 19 20 20 21 21 22 22 22 23 23 23 24 24 24 25 25 25 26 26 26 26 26 27 27 27 27 27 27 28 28 28 27 27 27 27 27 27 27 27 27 27 26 26 26 25 25 25 24 24 24 24 23 23 23 23 23 23 23 22 22 22 22 22 22 21 21 21 20 20 20 19 19 19 18 18 18 17 17 17 17 17 16 16 16 16 16 15 15 15 15 15 15 15 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 11 11 11 11 10 10 10 10 9 9 9 9 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 9 9 10 10 10 10 10 10 10 11 11 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 14 14 14 14 15 15 15 15 15 15 15 16 16 16 16 16 16 17 17 17 18 18 18 19 19 19 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 19 19 19 19 18 18 18 17 17 17 17 16 16 16 16 15 15 15 14 14 13 13 12 12 11 11 10 9 9 9 8 8 7 7 6 6 6 5 5 4 4 3 3 3 2 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 7 7 7 8 8 8 9 9 9 9 10 10 10 11 11 11 11 12 12 12 13 13 14 14 14 15 15 15 16 16 17 17 18 18 19 19 20 20 21 21 22 22 22 23 23 23 24 24 24 25 25 25 26 26 26 26 26 27 27 27 27 27 28 28 28 28 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 25 25 25 25 25 25 24 24 24 24 24 24 24 23 23 23 23 23 22 22 22 21 21 21 20 20 20 20 19 19 19 19 18 18 18 18 17 17 17 17 16 16 16 16 16 16 15 15 15 15 15 14 14 14 14 14 13 13 13 13 12 12 12 11 11 11 11 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 15 15 15 15 16 16 16 16 16 17 17 17 17 17 17 17 17 18 18 18 18 19 19 19 20 20 20 21 21 21 21 21 21 21 21 21 21 21 22 22 22 21 21 21 21 21 21 21 21 21 21 21 20 20 19 19 19 18 18 17 17 16 16 16 16 15 15 15 15 15 14 14 14 14 13 13 12 12 11 11 10 10 9 9 8 8 7 7 7 6 6 6 5 5 4 4 4 3 3 3 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 - 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 6 6 6 7 7 7 7 8 8 8 8 8 9 9 9 9 9 10 10 10 11 11 12 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20 21 21 22 22 23 23 23 24 24 24 25 25 26 26 26 26 26 26 27 27 27 27 27 28 28 28 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 26 26 26 25 25 25 25 25 25 24 24 24 24 23 23 23 23 22 22 22 21 21 21 21 20 20 20 19 19 19 18 18 18 18 18 17 17 17 17 17 16 16 16 16 15 15 15 15 14 14 14 14 13 13 13 13 12 12 12 11 11 11 10 10 10 10 10 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 20 20 20 20 19 19 18 18 17 17 16 16 15 15 14 14 14 14 13 13 13 13 13 13 12 12 12 11 11 10 10 9 9 8 8 8 7 7 6 6 6 5 5 5 4 4 4 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 - 4 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 7 7 7 7 7 8 8 8 8 8 9 9 9 9 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 19 19 20 20 20 21 21 22 22 22 23 23 24 24 24 25 25 25 25 26 26 26 26 26 27 27 27 27 27 27 27 27 27 27 27 27 27 28 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 26 25 25 25 25 25 24 24 24 24 24 23 23 23 23 22 22 22 21 21 21 20 20 20 20 19 19 19 19 18 18 18 18 18 17 17 17 17 16 16 16 15 15 15 15 14 14 14 13 13 13 12 12 12 12 11 11 11 11 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 21 21 21 21 22 22 22 22 23 23 23 23 22 22 22 22 22 22 22 22 22 22 21 21 21 20 20 20 20 19 19 19 18 18 17 17 16 16 15 15 14 14 13 13 12 12 12 12 11 11 11 11 11 10 10 10 9 9 8 8 8 7 7 6 6 6 5 5 5 4 4 4 3 3 3 3 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 4 - 3 3 3 3 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 8 9 9 10 10 11 11 12 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20 20 21 21 22 22 22 23 23 23 24 24 24 25 25 25 25 26 26 26 27 27 27 27 27 27 27 27 27 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 27 27 27 27 27 27 27 27 26 26 26 26 26 26 25 25 25 25 25 25 25 24 24 24 23 23 23 23 22 22 22 22 21 21 21 20 20 20 20 19 19 19 19 18 18 18 18 17 17 17 16 16 16 16 15 15 15 14 14 14 13 13 13 12 12 12 12 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 15 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 19 19 19 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 23 23 23 23 23 23 22 22 22 22 22 22 22 21 21 21 21 20 20 20 20 19 19 19 18 18 18 17 16 16 15 15 14 14 13 13 12 12 11 11 11 10 10 10 9 9 8 8 8 8 7 7 7 6 6 6 5 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 - 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 6 6 6 6 7 7 7 8 8 9 9 9 10 10 11 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 18 19 19 20 20 20 21 21 21 22 22 22 23 23 23 24 24 25 25 25 26 26 26 26 26 27 27 27 27 27 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 27 27 27 27 27 27 27 27 26 26 26 26 26 26 26 26 26 25 25 25 25 24 24 24 24 23 23 23 23 22 22 22 22 21 21 21 20 20 20 20 19 19 19 18 18 18 17 17 17 17 16 16 16 15 15 15 14 14 14 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 13 13 13 13 13 14 14 14 14 15 15 15 15 15 16 16 16 16 16 17 17 17 17 18 18 18 18 18 19 19 19 20 20 20 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 21 21 21 21 20 20 20 19 19 19 19 18 18 18 17 17 16 16 15 15 14 13 13 12 12 11 11 10 10 9 9 8 8 7 7 6 6 6 5 5 5 5 5 5 4 4 4 4 4 3 3 3 2 2 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 - 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 5 5 5 5 6 6 6 7 7 7 8 8 8 9 9 10 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 17 18 18 18 19 19 19 20 20 21 21 21 22 22 23 23 23 24 24 25 25 25 25 26 26 26 26 26 27 27 27 27 27 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 27 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 25 25 25 25 24 24 24 24 23 23 23 22 22 22 21 21 21 21 20 20 20 19 19 19 18 18 18 17 17 17 16 16 16 16 15 15 15 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 13 13 13 13 14 14 14 14 14 15 15 15 15 16 16 16 16 16 17 17 17 17 18 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 21 21 21 20 20 20 19 19 19 18 18 18 17 17 17 16 16 16 15 15 14 14 13 13 12 11 11 10 10 9 9 8 8 7 7 7 6 6 5 5 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 - 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 5 5 5 5 6 6 6 7 7 7 8 8 9 9 9 10 10 11 11 12 12 13 13 14 14 14 15 15 16 16 17 17 17 18 18 18 19 19 19 20 20 21 21 22 22 22 23 23 24 24 25 25 25 25 25 26 26 26 26 27 27 27 27 27 27 27 27 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 27 27 27 27 27 26 26 26 26 26 25 25 25 25 24 24 24 23 23 22 22 22 21 21 21 20 20 20 20 19 19 19 18 18 18 17 17 17 17 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 16 16 16 16 16 17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21 21 22 22 22 22 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 22 22 21 21 21 20 20 19 19 18 18 18 17 17 17 16 16 16 15 15 15 14 14 13 13 12 11 11 10 10 9 9 8 8 7 7 7 6 6 5 5 5 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 6 6 6 7 7 8 8 8 9 9 10 10 11 11 11 12 12 13 13 14 14 15 15 15 16 16 16 17 17 18 18 18 19 19 20 20 21 21 22 22 23 23 24 24 24 24 25 25 25 25 26 26 26 26 26 27 27 27 27 27 27 27 27 28 28 28 28 28 28 28 28 28 28 28 29 29 29 29 29 29 29 29 29 29 29 29 29 29 28 28 28 28 28 28 27 27 27 27 27 26 26 26 25 25 25 24 24 24 23 23 22 22 22 21 21 21 20 20 20 19 19 19 18 18 18 18 18 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 16 16 16 16 16 17 17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 21 22 22 22 22 23 23 23 23 23 24 24 24 24 24 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 22 22 21 21 20 20 19 19 18 18 17 17 16 16 15 15 15 14 14 14 13 13 13 12 11 11 10 10 9 9 8 8 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 3 3 3 4 4 4 4 5 5 5 6 6 6 7 7 7 7 8 8 9 9 9 10 10 10 11 11 12 12 12 13 13 14 14 14 15 15 15 16 16 16 17 17 18 18 19 19 20 20 21 21 22 22 23 23 23 23 23 24 24 24 24 24 25 25 25 25 26 26 26 26 27 27 27 27 27 28 28 28 28 28 28 28 28 28 29 29 29 29 29 29 29 29 29 29 29 29 29 29 28 28 28 28 28 28 28 27 27 27 27 26 26 26 25 25 25 24 24 24 23 23 23 22 22 22 21 21 21 20 20 20 19 19 19 19 19 18 18 18 18 18 18 17 17 17 17 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 16 17 17 17 17 17 17 17 17 18 18 18 18 18 18 19 19 19 19 20 20 20 20 20 21 21 21 21 22 22 22 22 22 23 23 23 23 23 24 24 24 24 24 24 25 25 25 25 24 24 24 24 24 24 23 23 23 23 23 23 23 22 22 22 22 22 21 21 21 20 20 19 19 18 17 17 16 16 15 15 14 14 14 13 13 12 12 12 11 11 11 10 10 9 9 8 8 8 7 7 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 6 6 6 6 7 7 7 7 8 8 8 9 9 9 10 10 10 10 11 11 12 12 12 13 13 14 14 14 15 15 16 16 17 17 18 18 19 19 20 20 21 21 21 21 21 21 22 22 22 22 22 22 23 23 24 24 24 25 25 26 26 26 27 27 27 27 27 28 28 28 28 28 28 28 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 28 28 28 28 28 28 28 28 28 27 27 26 26 26 25 25 25 24 24 24 23 23 23 22 22 22 21 21 21 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 20 20 20 20 20 21 21 21 21 21 22 22 22 22 22 22 23 23 23 23 23 23 24 24 24 24 24 24 25 25 24 24 24 24 24 23 23 23 23 22 22 22 22 21 21 21 21 21 20 20 20 20 20 19 18 18 17 16 16 15 15 14 13 13 12 12 12 11 11 10 10 10 9 9 9 8 8 8 7 7 7 6 6 6 5 5 5 4 4 3 3 3 2 2 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 3 3 3 4 4 4 4 5 5 5 5 6 6 6 6 6 7 7 7 7 7 8 8 8 8 8 9 9 10 10 10 11 11 12 12 13 13 13 14 14 15 15 16 16 17 17 18 19 19 19 19 19 19 20 20 20 20 20 20 20 21 21 22 22 23 24 24 25 25 26 26 26 27 27 27 27 27 27 28 28 28 28 28 28 28 28 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 29 28 28 28 27 27 27 26 26 26 25 25 25 24 24 24 23 23 23 23 22 22 22 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 22 22 22 22 22 22 22 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 23 23 23 22 22 22 21 21 21 20 20 20 20 19 19 19 19 19 18 18 17 17 16 15 15 14 13 13 12 12 11 11 10 10 9 9 9 8 8 7 7 7 6 6 6 6 6 5 5 5 5 4 4 4 3 3 3 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 5 5 5 5 6 6 6 6 6 7 7 7 7 8 8 8 9 9 9 10 10 10 11 11 11 12 12 13 13 14 14 15 15 16 16 17 17 17 17 17 18 18 18 18 18 18 18 18 19 20 20 21 21 22 22 23 23 24 24 25 25 25 25 25 26 26 26 26 26 26 27 27 27 27 27 27 27 27 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 27 27 27 27 26 26 26 25 25 25 25 24 24 24 24 24 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 23 23 23 23 23 23 22 22 22 21 21 20 20 20 19 19 19 18 18 18 18 18 17 17 17 17 16 16 16 15 14 14 13 13 12 12 11 11 10 10 9 9 8 8 8 7 7 6 6 6 5 5 5 5 4 4 4 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 9 9 9 9 10 10 10 11 11 12 12 13 13 14 14 14 15 15 15 16 16 16 16 16 16 16 16 16 16 17 18 18 19 19 20 20 21 22 22 23 23 23 23 23 24 24 24 24 24 25 25 25 25 25 25 26 26 26 26 26 27 27 27 27 27 27 27 27 27 27 27 27 28 28 27 27 27 27 26 26 26 26 25 25 25 25 25 25 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 22 22 22 21 21 21 20 20 19 19 18 18 17 17 17 16 16 16 16 16 15 15 15 15 15 14 14 13 13 12 12 11 11 11 10 10 9 9 8 8 8 7 7 6 6 6 5 5 4 4 4 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 8 9 9 9 10 10 11 11 11 12 12 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 16 16 17 18 18 19 19 20 20 21 21 21 22 22 22 22 22 22 23 23 23 23 23 24 24 24 24 24 25 25 25 25 26 26 26 26 26 26 26 26 27 27 27 27 27 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 19 19 18 18 18 17 17 16 16 15 15 15 14 14 14 14 13 13 13 13 13 12 12 12 11 11 10 10 10 9 9 9 8 8 7 7 7 6 6 5 5 5 4 4 3 3 3 2 2 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 3 3 3 4 4 4 5 5 5 5 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 15 15 16 16 17 17 18 18 19 19 19 19 20 20 20 20 20 20 21 21 21 21 21 22 22 22 22 23 23 23 23 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 20 20 20 20 20 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 17 17 17 16 16 15 15 15 14 14 13 13 13 13 12 12 12 12 11 11 11 10 10 10 9 9 9 8 8 8 8 7 7 7 6 6 6 5 5 5 4 4 4 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 7 8 8 8 8 8 8 8 9 9 9 9 10 10 10 11 11 11 12 12 12 13 13 13 14 14 15 15 16 16 16 17 17 17 17 18 18 18 18 18 18 18 19 19 19 19 20 20 20 20 21 21 21 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 23 23 23 23 23 23 23 23 23 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 20 20 20 20 19 19 19 19 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 16 16 16 16 16 16 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 15 15 15 14 14 14 13 13 13 12 12 12 11 11 11 10 10 10 9 9 9 8 8 8 8 7 7 7 7 6 6 6 6 5 5 5 5 4 4 4 4 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 5 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 9 9 9 10 10 10 11 11 12 12 12 13 13 13 14 14 15 15 15 15 15 16 16 16 16 16 16 16 17 17 17 17 18 18 18 18 19 19 19 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 19 19 19 19 18 18 18 17 17 17 17 17 17 16 16 16 16 16 16 16 15 15 15 15 15 14 14 14 14 14 13 13 13 13 13 14 14 14 14 14 14 14 14 14 13 13 13 13 12 12 12 11 11 11 11 10 10 10 9 9 8 8 8 7 7 7 7 6 6 6 6 6 5 5 5 5 5 4 4 4 4 4 3 3 3 3 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 3 3 3 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10 10 11 11 11 12 12 12 13 13 13 13 14 14 14 14 14 14 14 15 15 15 15 15 16 16 16 16 17 17 17 17 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 17 17 17 16 16 16 15 15 15 15 15 15 14 14 14 14 14 14 14 13 13 13 13 13 12 12 12 12 12 11 11 11 11 11 11 12 12 12 12 12 12 12 12 11 11 11 11 10 10 10 10 9 9 9 8 8 8 8 7 7 7 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 2 2 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 6 6 6 7 7 7 8 8 8 8 9 9 9 10 10 10 10 11 11 11 11 12 12 12 12 12 12 12 13 13 13 13 13 13 14 14 14 14 15 15 15 15 15 15 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 16 16 16 15 15 15 14 14 14 14 13 13 13 13 13 12 12 12 12 12 12 12 11 11 11 11 11 10 10 10 10 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 9 9 9 9 8 8 8 8 7 7 7 7 7 6 6 6 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 7 8 8 8 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 14 14 14 14 13 13 13 12 12 12 11 11 11 11 11 11 10 10 10 10 10 10 10 9 9 9 9 9 8 8 8 8 8 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 5 5 5 5 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 4 4 4 5 5 5 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 9 9 10 10 10 10 10 10 11 11 11 11 11 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 12 12 12 12 11 11 11 11 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 6 6 6 6 6 6 5 5 5 5 5 5 4 4 4 3 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 3 3 3 4 4 5 5 5 5 6 6 6 6 7 7 7 7 8 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 11 11 11 11 11 11 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 10 10 10 10 9 9 9 9 8 8 8 8 8 8 7 7 7 7 7 7 7 7 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 3 3 3 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7 7 8 8 8 8 8 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 8 8 8 8 8 7 7 7 7 7 6 6 6 6 6 6 6 6 5 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 3 3 3 3 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 6 6 6 6 6 5 5 5 5 5 5 5 5 4 4 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 3 3 3 3 4 4 4 3 3 3 3 3 2 2 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 3 3 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 3 3 3 4 4 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 2 3 3 3 4 4 4 4 3 3 3 3 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 2 3 3 4 4 5 5 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 2 3 3 4 4 5 5 5 4 4 4 4 4 4 3 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 2 2 3 3 4 4 4 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 2 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 2 2 2 2 2 2 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 2 2 2 2 1 1 1 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 3 3 3 3 3 3 3 3 3 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 0 0 0 1 1 1 1 2 2 2 2 3 3 3 2 2 2 2 2 1 1 1 1 0 0 1 1 1 1 2 2 2 2 3 3 3 3 3 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 2 2 2 2 3 2 2 2 2 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 2 2 2 3 3 3 3 2 2 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 2 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 - 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 - 3 3 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 - 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 - 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 - 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 - 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 - 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 - 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 - 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 - 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 - 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 - 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 - 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 - 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 - 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 - 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 - 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 - 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 - 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 - 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 - 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 - 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 - 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 - 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 - 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 - 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 - 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 - 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 - 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 27 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 - 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 - 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 25 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 - 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 - 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 - 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 - 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 - 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 - 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 - 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 - 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 - 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 - 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 - 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 - 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/tests/fixtures/bom_weather.json b/tests/fixtures/bom_weather.json deleted file mode 100644 index d40ea6fb21a..00000000000 --- a/tests/fixtures/bom_weather.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "observations": { - "data": [ - { - "wmo": 94767, - "name": "Fake", - "history_product": "IDN00000", - "local_date_time_full": "20180422130000", - "apparent_t": 25.0, - "press": 1021.7, - "weather": "-" - }, - { - "wmo": 94767, - "name": "Fake", - "history_product": "IDN00000", - "local_date_time_full": "20180422130000", - "apparent_t": 22.0, - "press": 1019.7, - "weather": "-" - }, - { - "wmo": 94767, - "name": "Fake", - "history_product": "IDN00000", - "local_date_time_full": "20180422130000", - "apparent_t": 20.0, - "press": 1011.7, - "weather": "Fine" - }, - { - "wmo": 94767, - "name": "Fake", - "history_product": "IDN00000", - "local_date_time_full": "20180422130000", - "apparent_t": 18.0, - "press": 1010.0, - "weather": "-" - } - ] - } -} diff --git a/tests/fixtures/coinmarketcap.json b/tests/fixtures/coinmarketcap.json deleted file mode 100644 index 9484058be4d..00000000000 --- a/tests/fixtures/coinmarketcap.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "cached": false, - "data": { - "id": 1027, - "name": "Ethereum", - "symbol": "ETH", - "website_slug": "ethereum", - "rank": 2, - "circulating_supply": 99619842.0, - "total_supply": 99619842.0, - "max_supply": null, - "quotes": { - "USD": { - "price": 577.019, - "volume_24h": 2839960000.0, - "market_cap": 57482541899.0, - "percent_change_1h": -2.28, - "percent_change_24h": -14.88, - "percent_change_7d": -17.51 - }, - "EUR": { - "price": 493.454724572, - "volume_24h": 2428699712.48, - "market_cap": 49158380042.0, - "percent_change_1h": -2.28, - "percent_change_24h": -14.88, - "percent_change_7d": -17.51 - } - }, - "last_updated": 1527098658 - }, - "metadata": { - "timestamp": 1527098716, - "error": null - } -} diff --git a/tests/fixtures/darksky.json b/tests/fixtures/darksky.json deleted file mode 100644 index 26df6b60210..00000000000 --- a/tests/fixtures/darksky.json +++ /dev/null @@ -1,1475 +0,0 @@ -{ - "alerts": [ - { - "title": "Winter Storm Watch", - "regions": [ - "Burney Basin / Eastern Shasta County", - "Mountains Southwestern Shasta County to Northern Lake County", - "West Slope Northern Sierra Nevada", - "Western Plumas County/Lassen Park" - ], - "severity": "watch", - "time": 1554422400, - "expires": 1554552000, - "description": "...Hazardous mountain travel expected over 6000 feet Thursday through late Friday night... .Snow is expected to begin Thursday afternoon increasing in intensity during the evening hours. Heavy snow and gusty winds are forecast Friday afternoon which will lead to hazardous travel over the mountains. Snow will begin to diminish during the late evening and overnight hours Friday night into Saturday morning. ...WINTER STORM WATCH IN EFFECT FROM THURSDAY AFTERNOON THROUGH LATE FRIDAY NIGHT... * WHAT...Periods of heavy snow possible. Plan on difficult travel conditions, including during the afternoon and evening hours on Friday. Total snow accumulations of 8 to 12 inches, with localized amounts up to 2 and a half feet possible. * WHERE...Western Plumas County/Lassen Park and West Slope Northern Sierra Nevada. * WHEN...From Thursday afternoon through late Friday night. * ADDITIONAL DETAILS...Be prepared for reduced visibilities at times. PRECAUTIONARY/PREPAREDNESS ACTIONS... A Winter Storm Watch means there is potential for significant snow, sleet or ice accumulations that may impact travel. Continue to monitor the latest forecasts.\n", - "uri": "https://alerts.weather.gov/cap/wwacapget.php?x=CA125CF1CFB088.WinterStormWatch.125CF1FC1240CA.STOWSWSTO.1fc288d0ac8e931cb17b5f5be62efff0" - }, - { - "title": "Red Flag Warning", - "regions": ["Guadalupe Mountains", "Southeast Plains"], - "severity": "warning", - "time": 1554300000, - "expires": 1554350400, - "description": "...RED FLAG WARNING IN EFFECT FROM 9 AM CDT /8 AM MDT/ THIS MORNING TO 11 PM CDT /10 PM MDT/ THIS EVENING FOR RELATIVE HUMIDITY OF 15% OR LESS, 20 FT WINDS OF 20 MPH OR MORE, AND HIGH TO EXTREME FIRE DANGER FOR THE GUADALUPE, DAVIS, AND APACHE MOUNTAINS, SOUTHEAST NEW MEXICO PLAINS, REEVES COUNTY AND THE UPPER TRANS PECOS, AND VAN HORN AND THE HIGHWAY 54 CORRIDOR... .Critical fire weather conditions are expected today and this evening across Southeastern New Mexico and areas south as far as the Davis and Apache Mountains. Southwest to west 20 ft winds will increase as an upper-level disturbance passes through the region. Combined with unseasonably warm temperatures, consequent low relative humidity, and cured fuels, potential for fire growth will increase. ...RED FLAG WARNING IN EFFECT FROM 9 AM CDT /8 AM MDT/ THIS MORNING TO 11 PM CDT /10 PM MDT/ THIS EVENING FOR RELATIVE HUMIDITY OF 15% OR LESS, 20 FT WINDS OF 20 MPH OR MORE, AND HIGH TO EXTREME FIRE DANGER... * WIND...Mountains...West 20 to 25 mph increasing to 30 to 40 mph in the afternoon. Plains...Southwest 20 to 25 mph. * HUMIDITY...8% to 15%. * IMPACTS...any fires that develop will likely spread rapidly. Outdoor burning is not recommended.\n", - "uri": "https://alerts.weather.gov/cap/wwacapget.php?x=NM125CF1CDEA3C.RedFlagWarning.125CF1DC5540NM.MAFRFWMAF.085066acdafda6aecf61d10224f5133e" - } - ], - "currently": { - "apparentTemperature": 68.1, - "cloudCover": 0.18, - "dewPoint": 53.23, - "humidity": 0.59, - "icon": "clear-day", - "nearestStormBearing": 115, - "nearestStormDistance": 325, - "ozone": 322.71, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.8, - "summary": "Clear", - "temperature": 68.1, - "time": 1464914163, - "visibility": 9.02, - "windBearing": 271, - "windSpeed": 9.38 - }, - "daily": { - "data": [ - { - "apparentTemperatureMax": 68.56, - "apparentTemperatureMaxTime": 1464915600, - "apparentTemperatureMin": 52.94, - "apparentTemperatureMinTime": 1464872400, - "cloudCover": 0.23, - "dewPoint": 51.98, - "humidity": 0.77, - "icon": "partly-cloudy-day", - "moonPhase": 0.91, - "ozone": 326.1, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1014.84, - "summary": "Partly cloudy in the morning.", - "sunriseTime": 1464871812, - "sunsetTime": 1464924498, - "temperatureMax": 68.56, - "temperatureMaxTime": 1464915600, - "temperatureMin": 52.94, - "temperatureMinTime": 1464872400, - "time": 1464850800, - "visibility": 7.8, - "windBearing": 268, - "windSpeed": 5.59 - }, - { - "apparentTemperatureMax": 75.82, - "apparentTemperatureMaxTime": 1464991200, - "apparentTemperatureMin": 53.45, - "apparentTemperatureMinTime": 1464958800, - "cloudCover": 0.41, - "dewPoint": 53.58, - "humidity": 0.71, - "icon": "partly-cloudy-day", - "moonPhase": 0.95, - "ozone": 319.98, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1015.2, - "summary": "Partly cloudy throughout the day.", - "sunriseTime": 1464958194, - "sunsetTime": 1465010936, - "temperatureMax": 75.82, - "temperatureMaxTime": 1464991200, - "temperatureMin": 53.45, - "temperatureMinTime": 1464958800, - "time": 1464937200, - "visibility": 9.24, - "windBearing": 274, - "windSpeed": 5.92 - }, - { - "apparentTemperatureMax": 72.18, - "apparentTemperatureMaxTime": 1465081200, - "apparentTemperatureMin": 53.06, - "apparentTemperatureMinTime": 1465038000, - "cloudCover": 0.74, - "dewPoint": 54.14, - "humidity": 0.78, - "icon": "partly-cloudy-day", - "moonPhase": 0.98, - "ozone": 324.21, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1013.17, - "summary": "Mostly cloudy throughout the day.", - "sunriseTime": 1465044577, - "sunsetTime": 1465097372, - "temperatureMax": 72.18, - "temperatureMaxTime": 1465081200, - "temperatureMin": 53.06, - "temperatureMinTime": 1465038000, - "time": 1465023600, - "visibility": 7.94, - "windBearing": 255, - "windSpeed": 5.5 - }, - { - "apparentTemperatureMax": 71.76, - "apparentTemperatureMaxTime": 1465171200, - "apparentTemperatureMin": 52.37, - "apparentTemperatureMinTime": 1465131600, - "cloudCover": 0.5, - "dewPoint": 53.42, - "humidity": 0.8, - "icon": "fog", - "moonPhase": 0.03, - "ozone": 325.96, - "precipIntensity": 0.0006, - "precipIntensityMax": 0.0016, - "precipIntensityMaxTime": 1465135200, - "precipProbability": 0.04, - "precipType": "rain", - "pressure": 1011.43, - "summary": "Foggy in the morning.", - "sunriseTime": 1465130962, - "sunsetTime": 1465183808, - "temperatureMax": 71.76, - "temperatureMaxTime": 1465171200, - "temperatureMin": 52.37, - "temperatureMinTime": 1465131600, - "time": 1465110000, - "visibility": 6.86, - "windBearing": 252, - "windSpeed": 7.29 - }, - { - "apparentTemperatureMax": 69.01, - "apparentTemperatureMaxTime": 1465246800, - "apparentTemperatureMin": 54.75, - "apparentTemperatureMinTime": 1465214400, - "cloudCover": 0.09, - "dewPoint": 52.16, - "humidity": 0.74, - "icon": "partly-cloudy-night", - "moonPhase": 0.07, - "ozone": 305.72, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1006.94, - "summary": "Partly cloudy starting in the evening.", - "sunriseTime": 1465217348, - "sunsetTime": 1465270242, - "temperatureMax": 69.01, - "temperatureMaxTime": 1465246800, - "temperatureMin": 54.75, - "temperatureMinTime": 1465214400, - "time": 1465196400, - "windBearing": 222, - "windSpeed": 5.86 - }, - { - "apparentTemperatureMax": 67.78, - "apparentTemperatureMaxTime": 1465333200, - "apparentTemperatureMin": 55.38, - "apparentTemperatureMinTime": 1465300800, - "cloudCover": 0.34, - "dewPoint": 51.41, - "humidity": 0.73, - "icon": "partly-cloudy-day", - "moonPhase": 0.1, - "ozone": 304.57, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1007.88, - "summary": "Partly cloudy throughout the day.", - "sunriseTime": 1465303737, - "sunsetTime": 1465356676, - "temperatureMax": 67.78, - "temperatureMaxTime": 1465333200, - "temperatureMin": 55.38, - "temperatureMinTime": 1465300800, - "time": 1465282800, - "windBearing": 224, - "windSpeed": 6.75 - }, - { - "apparentTemperatureMax": 68.94, - "apparentTemperatureMaxTime": 1465416000, - "apparentTemperatureMin": 55.11, - "apparentTemperatureMinTime": 1465452000, - "cloudCover": 0.45, - "dewPoint": 47.11, - "humidity": 0.63, - "icon": "partly-cloudy-day", - "moonPhase": 0.13, - "ozone": 329.52, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1010.78, - "summary": "Mostly cloudy until afternoon.", - "sunriseTime": 1465390127, - "sunsetTime": 1465443107, - "temperatureMax": 68.94, - "temperatureMaxTime": 1465416000, - "temperatureMin": 55.11, - "temperatureMinTime": 1465452000, - "time": 1465369200, - "windBearing": 263, - "windSpeed": 9.55 - }, - { - "apparentTemperatureMax": 65.67, - "apparentTemperatureMaxTime": 1465506000, - "apparentTemperatureMin": 54, - "apparentTemperatureMinTime": 1465470000, - "cloudCover": 0, - "dewPoint": 44.72, - "humidity": 0.61, - "icon": "clear-day", - "moonPhase": 0.17, - "ozone": 355.02, - "precipIntensity": 0, - "precipIntensityMax": 0, - "precipProbability": 0, - "pressure": 1010.11, - "summary": "Clear throughout the day.", - "sunriseTime": 1465476519, - "sunsetTime": 1465529538, - "temperatureMax": 65.67, - "temperatureMaxTime": 1465506000, - "temperatureMin": 54, - "temperatureMinTime": 1465470000, - "time": 1465455600, - "windBearing": 288, - "windSpeed": 12.21 - } - ], - "icon": "clear-day", - "summary": "No precipitation throughout the week, with temperatures falling to 66°F on Thursday." - }, - "flags": { - "darksky-stations": ["KMUX", "KDAX"], - "isd-stations": [ - "724943-99999", - "745039-99999", - "745065-99999", - "994016-99999", - "998479-99999" - ], - "lamp-stations": [ - "KAPC", - "KCCR", - "KHWD", - "KLVK", - "KNUQ", - "KOAK", - "KPAO", - "KSFO", - "KSQL" - ], - "madis-stations": [ - "AU915", - "C5988", - "C6328", - "C8158", - "C9629", - "D5422", - "D8533", - "E0426", - "E6067", - "E9227", - "FTPC1", - "GGBC1", - "OKXC1", - "PPXC1", - "PXOC1", - "SFOC1" - ], - "sources": [ - "darksky", - "lamp", - "gfs", - "cmc", - "nam", - "rap", - "rtma", - "sref", - "fnmoc", - "isd", - "nwspa", - "madis", - "nearest-precip" - ], - "units": "us" - }, - "hourly": { - "data": [ - { - "apparentTemperature": 67.42, - "cloudCover": 0.19, - "dewPoint": 52.31, - "humidity": 0.58, - "icon": "clear-day", - "ozone": 322.76, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.88, - "summary": "Clear", - "temperature": 67.42, - "time": 1464912000, - "visibility": 9, - "windBearing": 269, - "windSpeed": 8.38 - }, - { - "apparentTemperature": 68.56, - "cloudCover": 0.18, - "dewPoint": 53.84, - "humidity": 0.59, - "icon": "clear-day", - "ozone": 322.68, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.76, - "summary": "Clear", - "temperature": 68.56, - "time": 1464915600, - "visibility": 9.03, - "windBearing": 272, - "windSpeed": 10.05 - }, - { - "apparentTemperature": 67.39, - "cloudCover": 0.15, - "dewPoint": 54.53, - "humidity": 0.63, - "icon": "clear-day", - "ozone": 322.66, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.75, - "summary": "Clear", - "temperature": 67.39, - "time": 1464919200, - "visibility": 9.31, - "windBearing": 274, - "windSpeed": 9.2 - }, - { - "apparentTemperature": 65.48, - "cloudCover": 0.13, - "dewPoint": 53.73, - "humidity": 0.66, - "icon": "clear-day", - "ozone": 322.72, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.86, - "summary": "Clear", - "temperature": 65.48, - "time": 1464922800, - "visibility": 9.41, - "windBearing": 276, - "windSpeed": 8.41 - }, - { - "apparentTemperature": 63.37, - "cloudCover": 0.15, - "dewPoint": 53.05, - "humidity": 0.69, - "icon": "clear-night", - "ozone": 322.89, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.18, - "summary": "Clear", - "temperature": 63.37, - "time": 1464926400, - "visibility": 9.64, - "windBearing": 277, - "windSpeed": 6.64 - }, - { - "apparentTemperature": 61.63, - "cloudCover": 0.18, - "dewPoint": 52.33, - "humidity": 0.72, - "icon": "clear-night", - "ozone": 323.15, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.62, - "summary": "Clear", - "temperature": 61.63, - "time": 1464930000, - "visibility": 9.65, - "windBearing": 280, - "windSpeed": 5.84 - }, - { - "apparentTemperature": 59.39, - "cloudCover": 0.2, - "dewPoint": 51.07, - "humidity": 0.74, - "icon": "clear-night", - "ozone": 323.5, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.9, - "summary": "Clear", - "temperature": 59.39, - "time": 1464933600, - "visibility": 9.62, - "windBearing": 281, - "windSpeed": 5.43 - }, - { - "apparentTemperature": 58.44, - "cloudCover": 0.2, - "dewPoint": 50.88, - "humidity": 0.76, - "icon": "clear-night", - "ozone": 323.99, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.87, - "summary": "Clear", - "temperature": 58.44, - "time": 1464937200, - "visibility": 9.5, - "windBearing": 279, - "windSpeed": 4.98 - }, - { - "apparentTemperature": 57.85, - "cloudCover": 0.2, - "dewPoint": 51.05, - "humidity": 0.78, - "icon": "clear-night", - "ozone": 324.56, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.69, - "summary": "Clear", - "temperature": 57.85, - "time": 1464940800, - "visibility": 9.11, - "windBearing": 278, - "windSpeed": 5.02 - }, - { - "apparentTemperature": 57.28, - "cloudCover": 0.22, - "dewPoint": 51.09, - "humidity": 0.8, - "icon": "clear-night", - "ozone": 325.1, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.52, - "summary": "Clear", - "temperature": 57.28, - "time": 1464944400, - "visibility": 9.05, - "windBearing": 278, - "windSpeed": 5.14 - }, - { - "apparentTemperature": 56.24, - "cloudCover": 0.27, - "dewPoint": 50.63, - "humidity": 0.81, - "icon": "partly-cloudy-night", - "ozone": 325.68, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.37, - "summary": "Partly Cloudy", - "temperature": 56.24, - "time": 1464948000, - "visibility": 8.85, - "windBearing": 278, - "windSpeed": 4.82 - }, - { - "apparentTemperature": 54.93, - "cloudCover": 0.34, - "dewPoint": 49.82, - "humidity": 0.83, - "icon": "partly-cloudy-night", - "ozone": 326.22, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.25, - "summary": "Partly Cloudy", - "temperature": 54.93, - "time": 1464951600, - "visibility": 8.56, - "windBearing": 278, - "windSpeed": 4.45 - }, - { - "apparentTemperature": 54.06, - "cloudCover": 0.4, - "dewPoint": 49.16, - "humidity": 0.83, - "icon": "partly-cloudy-night", - "ozone": 326.31, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.22, - "summary": "Partly Cloudy", - "temperature": 54.06, - "time": 1464955200, - "visibility": 8.12, - "windBearing": 280, - "windSpeed": 4.13 - }, - { - "apparentTemperature": 53.45, - "cloudCover": 0.45, - "dewPoint": 48.47, - "humidity": 0.83, - "icon": "partly-cloudy-day", - "ozone": 325.68, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.4, - "summary": "Partly Cloudy", - "temperature": 53.45, - "time": 1464958800, - "visibility": 7.76, - "windBearing": 280, - "windSpeed": 3.86 - }, - { - "apparentTemperature": 56.05, - "cloudCover": 0.5, - "dewPoint": 50.07, - "humidity": 0.8, - "icon": "partly-cloudy-day", - "ozone": 324.6, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.68, - "summary": "Partly Cloudy", - "temperature": 56.05, - "time": 1464962400, - "visibility": 7.77, - "windBearing": 279, - "windSpeed": 3.61 - }, - { - "apparentTemperature": 59.38, - "cloudCover": 0.52, - "dewPoint": 51.39, - "humidity": 0.75, - "icon": "partly-cloudy-day", - "ozone": 323.51, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.9, - "summary": "Partly Cloudy", - "temperature": 59.38, - "time": 1464966000, - "visibility": 8.18, - "windBearing": 275, - "windSpeed": 4 - }, - { - "apparentTemperature": 62.67, - "cloudCover": 0.51, - "dewPoint": 52.44, - "humidity": 0.69, - "icon": "partly-cloudy-day", - "ozone": 322.57, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.97, - "summary": "Partly Cloudy", - "temperature": 62.67, - "time": 1464969600, - "visibility": 8.4, - "windBearing": 272, - "windSpeed": 4.22 - }, - { - "apparentTemperature": 65.51, - "cloudCover": 0.49, - "dewPoint": 52.62, - "humidity": 0.63, - "icon": "partly-cloudy-day", - "ozone": 321.61, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.96, - "summary": "Partly Cloudy", - "temperature": 65.51, - "time": 1464973200, - "visibility": 8.72, - "windBearing": 271, - "windSpeed": 4.65 - }, - { - "apparentTemperature": 66.98, - "cloudCover": 0.46, - "dewPoint": 53.53, - "humidity": 0.62, - "icon": "partly-cloudy-day", - "ozone": 320.6, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.9, - "summary": "Partly Cloudy", - "temperature": 66.98, - "time": 1464976800, - "visibility": 8.81, - "windBearing": 271, - "windSpeed": 4.93 - }, - { - "apparentTemperature": 69.34, - "cloudCover": 0.4, - "dewPoint": 55.04, - "humidity": 0.6, - "icon": "partly-cloudy-day", - "ozone": 319.39, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.8, - "summary": "Partly Cloudy", - "temperature": 69.34, - "time": 1464980400, - "visibility": 9.05, - "windBearing": 271, - "windSpeed": 5.7 - }, - { - "apparentTemperature": 71.92, - "cloudCover": 0.34, - "dewPoint": 56.61, - "humidity": 0.59, - "icon": "partly-cloudy-day", - "ozone": 318.12, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.65, - "summary": "Partly Cloudy", - "temperature": 71.92, - "time": 1464984000, - "visibility": 9.49, - "windBearing": 272, - "windSpeed": 6.7 - }, - { - "apparentTemperature": 74.45, - "cloudCover": 0.3, - "dewPoint": 58.29, - "humidity": 0.57, - "icon": "partly-cloudy-day", - "ozone": 317.16, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.43, - "summary": "Partly Cloudy", - "temperature": 74.45, - "time": 1464987600, - "visibility": 9.9, - "windBearing": 273, - "windSpeed": 7.6 - }, - { - "apparentTemperature": 75.82, - "cloudCover": 0.3, - "dewPoint": 59.26, - "humidity": 0.57, - "icon": "partly-cloudy-day", - "ozone": 316.77, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1015.09, - "summary": "Partly Cloudy", - "temperature": 75.82, - "time": 1464991200, - "visibility": 10, - "windBearing": 273, - "windSpeed": 8.58 - }, - { - "apparentTemperature": 74.92, - "cloudCover": 0.32, - "dewPoint": 58.55, - "humidity": 0.57, - "icon": "partly-cloudy-day", - "ozone": 316.68, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.71, - "summary": "Partly Cloudy", - "temperature": 74.92, - "time": 1464994800, - "visibility": 10, - "windBearing": 274, - "windSpeed": 9.12 - }, - { - "apparentTemperature": 73.33, - "cloudCover": 0.36, - "dewPoint": 57.88, - "humidity": 0.58, - "icon": "partly-cloudy-day", - "ozone": 316.48, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.4, - "summary": "Partly Cloudy", - "temperature": 73.33, - "time": 1464998400, - "visibility": 10, - "windBearing": 274, - "windSpeed": 9.21 - }, - { - "apparentTemperature": 70.53, - "cloudCover": 0.41, - "dewPoint": 56.98, - "humidity": 0.62, - "icon": "partly-cloudy-day", - "ozone": 316.04, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.22, - "summary": "Partly Cloudy", - "temperature": 70.53, - "time": 1465002000, - "visibility": 10, - "windBearing": 274, - "windSpeed": 8.77 - }, - { - "apparentTemperature": 67.03, - "cloudCover": 0.49, - "dewPoint": 55.91, - "humidity": 0.68, - "icon": "partly-cloudy-day", - "ozone": 315.48, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.11, - "summary": "Partly Cloudy", - "temperature": 67.03, - "time": 1465005600, - "visibility": 10, - "windBearing": 273, - "windSpeed": 7.84 - }, - { - "apparentTemperature": 63.9, - "cloudCover": 0.56, - "dewPoint": 54.93, - "humidity": 0.73, - "icon": "partly-cloudy-day", - "ozone": 314.77, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.1, - "summary": "Partly Cloudy", - "temperature": 63.9, - "time": 1465009200, - "visibility": 10, - "windBearing": 271, - "windSpeed": 7.12 - }, - { - "apparentTemperature": 61.49, - "cloudCover": 0.58, - "dewPoint": 54.23, - "humidity": 0.77, - "icon": "partly-cloudy-night", - "ozone": 313.81, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.29, - "summary": "Partly Cloudy", - "temperature": 61.49, - "time": 1465012800, - "visibility": 10, - "windBearing": 269, - "windSpeed": 6.5 - }, - { - "apparentTemperature": 59.39, - "cloudCover": 0.58, - "dewPoint": 53.62, - "humidity": 0.81, - "icon": "partly-cloudy-night", - "ozone": 312.7, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.54, - "summary": "Partly Cloudy", - "temperature": 59.39, - "time": 1465016400, - "visibility": 10, - "windBearing": 266, - "windSpeed": 5.92 - }, - { - "apparentTemperature": 58.02, - "cloudCover": 0.59, - "dewPoint": 53.35, - "humidity": 0.84, - "icon": "partly-cloudy-night", - "ozone": 311.6, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.63, - "summary": "Partly Cloudy", - "temperature": 58.02, - "time": 1465020000, - "visibility": 10, - "windBearing": 263, - "windSpeed": 5.46 - }, - { - "apparentTemperature": 57.06, - "cloudCover": 0.61, - "dewPoint": 53.07, - "humidity": 0.87, - "icon": "partly-cloudy-night", - "ozone": 310.41, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.43, - "summary": "Mostly Cloudy", - "temperature": 57.06, - "time": 1465023600, - "visibility": 9.01, - "windBearing": 259, - "windSpeed": 5.18 - }, - { - "apparentTemperature": 56.12, - "cloudCover": 0.63, - "dewPoint": 52.56, - "humidity": 0.88, - "icon": "partly-cloudy-night", - "ozone": 309.24, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1014.09, - "summary": "Mostly Cloudy", - "temperature": 56.12, - "time": 1465027200, - "visibility": 7.64, - "windBearing": 256, - "windSpeed": 5.01 - }, - { - "apparentTemperature": 55.14, - "cloudCover": 0.67, - "dewPoint": 51.94, - "humidity": 0.89, - "icon": "partly-cloudy-night", - "ozone": 308.58, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.74, - "summary": "Mostly Cloudy", - "temperature": 55.14, - "time": 1465030800, - "visibility": 6.64, - "windBearing": 253, - "windSpeed": 4.84 - }, - { - "apparentTemperature": 54.01, - "cloudCover": 0.72, - "dewPoint": 51.21, - "humidity": 0.9, - "icon": "partly-cloudy-night", - "ozone": 308.59, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.39, - "summary": "Mostly Cloudy", - "temperature": 54.01, - "time": 1465034400, - "visibility": 6.4, - "windBearing": 254, - "windSpeed": 4.64 - }, - { - "apparentTemperature": 53.06, - "cloudCover": 0.77, - "dewPoint": 50.57, - "humidity": 0.91, - "icon": "partly-cloudy-night", - "ozone": 309.11, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.05, - "summary": "Mostly Cloudy", - "temperature": 53.06, - "time": 1465038000, - "visibility": 6.53, - "windBearing": 256, - "windSpeed": 4.45 - }, - { - "apparentTemperature": 53.73, - "cloudCover": 0.83, - "dewPoint": 51.2, - "humidity": 0.91, - "icon": "partly-cloudy-night", - "ozone": 310.15, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1012.84, - "summary": "Mostly Cloudy", - "temperature": 53.73, - "time": 1465041600, - "visibility": 6.58, - "windBearing": 255, - "windSpeed": 4.23 - }, - { - "apparentTemperature": 54.88, - "cloudCover": 0.89, - "dewPoint": 51.88, - "humidity": 0.9, - "icon": "partly-cloudy-day", - "ozone": 312.09, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1012.85, - "summary": "Mostly Cloudy", - "temperature": 54.88, - "time": 1465045200, - "visibility": 6.28, - "windBearing": 250, - "windSpeed": 3.81 - }, - { - "apparentTemperature": 56.24, - "cloudCover": 0.96, - "dewPoint": 52.42, - "humidity": 0.87, - "icon": "cloudy", - "ozone": 314.55, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013, - "summary": "Overcast", - "temperature": 56.24, - "time": 1465048800, - "visibility": 5.92, - "windBearing": 241, - "windSpeed": 3.23 - }, - { - "apparentTemperature": 57.62, - "cloudCover": 1, - "dewPoint": 52.73, - "humidity": 0.84, - "icon": "cloudy", - "ozone": 316.51, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.16, - "summary": "Overcast", - "temperature": 57.62, - "time": 1465052400, - "visibility": 5.89, - "windBearing": 236, - "windSpeed": 3.11 - }, - { - "apparentTemperature": 59.22, - "cloudCover": 1, - "dewPoint": 52.86, - "humidity": 0.79, - "icon": "cloudy", - "ozone": 317.32, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.31, - "summary": "Overcast", - "temperature": 59.22, - "time": 1465056000, - "visibility": 6.54, - "windBearing": 241, - "windSpeed": 3.59 - }, - { - "apparentTemperature": 61.29, - "cloudCover": 0.99, - "dewPoint": 53.12, - "humidity": 0.75, - "icon": "cloudy", - "ozone": 317.63, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.46, - "summary": "Overcast", - "temperature": 61.29, - "time": 1465059600, - "visibility": 7.52, - "windBearing": 250, - "windSpeed": 4.42 - }, - { - "apparentTemperature": 63.6, - "cloudCover": 0.97, - "dewPoint": 53.81, - "humidity": 0.7, - "icon": "cloudy", - "ozone": 318.31, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.54, - "summary": "Overcast", - "temperature": 63.6, - "time": 1465063200, - "visibility": 8.26, - "windBearing": 256, - "windSpeed": 5.05 - }, - { - "apparentTemperature": 65.59, - "cloudCover": 0.95, - "dewPoint": 54.62, - "humidity": 0.68, - "icon": "cloudy", - "ozone": 319.55, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.54, - "summary": "Overcast", - "temperature": 65.59, - "time": 1465066800, - "visibility": 8.42, - "windBearing": 257, - "windSpeed": 5.53 - }, - { - "apparentTemperature": 67.6, - "cloudCover": 0.91, - "dewPoint": 55.73, - "humidity": 0.66, - "icon": "partly-cloudy-day", - "ozone": 321.16, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.48, - "summary": "Mostly Cloudy", - "temperature": 67.6, - "time": 1465070400, - "visibility": 8.32, - "windBearing": 257, - "windSpeed": 5.95 - }, - { - "apparentTemperature": 69.48, - "cloudCover": 0.87, - "dewPoint": 56.92, - "humidity": 0.64, - "icon": "partly-cloudy-day", - "ozone": 323.49, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.34, - "summary": "Mostly Cloudy", - "temperature": 69.48, - "time": 1465074000, - "visibility": 8.36, - "windBearing": 257, - "windSpeed": 6.37 - }, - { - "apparentTemperature": 71.07, - "cloudCover": 0.85, - "dewPoint": 57.94, - "humidity": 0.63, - "icon": "partly-cloudy-day", - "ozone": 326.97, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1013.09, - "summary": "Mostly Cloudy", - "temperature": 71.07, - "time": 1465077600, - "visibility": 8.75, - "windBearing": 258, - "windSpeed": 6.88 - }, - { - "apparentTemperature": 72.18, - "cloudCover": 0.82, - "dewPoint": 58.77, - "humidity": 0.63, - "icon": "partly-cloudy-day", - "ozone": 331.17, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1012.79, - "summary": "Mostly Cloudy", - "temperature": 72.18, - "time": 1465081200, - "visibility": 9.28, - "windBearing": 260, - "windSpeed": 7.5 - }, - { - "apparentTemperature": 71.38, - "cloudCover": 0.75, - "dewPoint": 58.47, - "humidity": 0.64, - "icon": "partly-cloudy-day", - "ozone": 335.24, - "precipIntensity": 0, - "precipProbability": 0, - "pressure": 1012.58, - "summary": "Mostly Cloudy", - "temperature": 71.38, - "time": 1465084800, - "visibility": 9.67, - "windBearing": 260, - "windSpeed": 7.75 - } - ], - "icon": "partly-cloudy-day", - "summary": "Partly cloudy starting tonight." - }, - "latitude": 37.8267, - "longitude": -122.423, - "minutely": { - "data": [ - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914160 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914220 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914280 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914340 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914400 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914460 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914520 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914580 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914640 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914700 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914760 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914820 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914880 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464914940 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915000 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915060 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915120 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915180 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915240 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915300 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915360 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915420 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915480 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915540 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915600 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915660 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915720 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915780 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915840 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915900 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464915960 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916020 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916080 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916140 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916200 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916260 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916320 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916380 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916440 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916500 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916560 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916620 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916680 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916740 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916800 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916860 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916920 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464916980 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917040 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917100 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917160 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917220 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917280 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917340 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917400 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917460 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917520 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917580 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917640 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917700 - }, - { - "precipIntensity": 0, - "precipProbability": 0, - "time": 1464917760 - } - ], - "icon": "clear-day", - "summary": "Clear for the hour." - }, - "offset": -7, - "timezone": "America/Los_Angeles" -} diff --git a/tests/fixtures/upc_connect.xml b/tests/fixtures/upc_connect.xml deleted file mode 100644 index b8ffc4dd979..00000000000 --- a/tests/fixtures/upc_connect.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Ethernet 1 - 192.168.0.139/24 - 0 - 2 - Unknown - 30:D3:2D:0:69:21 - 2 - 00:00:00:00 - 1000 - - - Ethernet 2 - 192.168.0.134/24 - 1 - 2 - Unknown - 5C:AA:FD:25:32:02 - 2 - 00:00:00:00 - 10 - - - - - HASS - 192.168.0.194/24 - 3 - 3 - Unknown - 70:EE:50:27:A1:38 - 2 - 00:00:00:00 - 39 - - - 3 - upc - diff --git a/tests/fixtures/yahoo_finance.json b/tests/fixtures/yahoo_finance.json deleted file mode 100644 index 5c72abe77a7..00000000000 --- a/tests/fixtures/yahoo_finance.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "symbol": "YHOO", - "Ask": "42.42", - "AverageDailyVolume": "11397600", - "Bid": "42.41", - "AskRealtime": null, - "BidRealtime": null, - "BookValue": "29.83", - "Change_PercentChange": "+0.62 - +1.48%", - "Change": "+0.62", - "Commission": null, - "Currency": "USD", - "ChangeRealtime": null, - "AfterHoursChangeRealtime": null, - "DividendShare": null, - "LastTradeDate": "10/18/2016", - "TradeDate": null, - "EarningsShare": "-5.18", - "ErrorIndicationreturnedforsymbolchangedinvalid": null, - "EPSEstimateCurrentYear": "0.49", - "EPSEstimateNextYear": "0.57", - "EPSEstimateNextQuarter": "0.17", - "DaysLow": "41.86", - "DaysHigh": "42.42", - "YearLow": "26.15", - "YearHigh": "44.92", - "HoldingsGainPercent": null, - "AnnualizedGain": null, - "HoldingsGain": null, - "HoldingsGainPercentRealtime": null, - "HoldingsGainRealtime": null, - "MoreInfo": null, - "OrderBookRealtime": null, - "MarketCapitalization": "40.37B", - "MarketCapRealtime": null, - "EBITDA": "151.08M", - "ChangeFromYearLow": "16.26", - "PercentChangeFromYearLow": "+62.18%", - "LastTradeRealtimeWithTime": null, - "ChangePercentRealtime": null, - "ChangeFromYearHigh": "-2.51", - "PercebtChangeFromYearHigh": "-5.59%", - "LastTradeWithTime": "9:41am - 42.41", - "LastTradePriceOnly": "42.41", - "HighLimit": null, - "LowLimit": null, - "DaysRange": "41.86 - 42.42", - "DaysRangeRealtime": null, - "FiftydayMovingAverage": "43.16", - "TwoHundreddayMovingAverage": "39.26", - "ChangeFromTwoHundreddayMovingAverage": "3.15", - "PercentChangeFromTwoHundreddayMovingAverage": "+8.03%", - "ChangeFromFiftydayMovingAverage": "-0.75", - "PercentChangeFromFiftydayMovingAverage": "-1.74%", - "Name": "Yahoo! Inc.", - "Notes": null, - "Open": "41.69", - "PreviousClose": "41.79", - "PricePaid": null, - "ChangeinPercent": "+1.48%", - "PriceSales": "8.13", - "PriceBook": "1.40", - "ExDividendDate": null, - "PERatio": null, - "DividendPayDate": null, - "PERatioRealtime": null, - "PEGRatio": "-24.57", - "PriceEPSEstimateCurrentYear": "86.55", - "PriceEPSEstimateNextYear": "74.40", - "Symbol": "YHOO", - "SharesOwned": null, - "ShortRatio": "5.05", - "LastTradeTime": "9:41am", - "TickerTrend": null, - "OneyrTargetPrice": "43.64", - "Volume": "946198", - "HoldingsValue": null, - "HoldingsValueRealtime": null, - "YearRange": "26.15 - 44.92", - "DaysValueChange": null, - "DaysValueChangeRealtime": null, - "StockExchange": "NMS", - "DividendYield": null, - "PercentChange": "+1.48%" -} diff --git a/tests/fixtures/yahooweather.json b/tests/fixtures/yahooweather.json deleted file mode 100644 index d345373c149..00000000000 --- a/tests/fixtures/yahooweather.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "query": { - "count": 1, - "created": "2017-11-17T13:40:47Z", - "lang": "en-US", - "results": { - "channel": { - "units": { - "distance": "km", - "pressure": "mb", - "speed": "km/h", - "temperature": "C" - }, - "title": "Yahoo! Weather - San Diego, CA, US", - "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-23511632/", - "description": "Yahoo! Weather for San Diego, CA, US", - "language": "en-us", - "lastBuildDate": "Fri, 17 Nov 2017 05:40 AM PST", - "ttl": "60", - "location": { - "city": "San Diego", - "country": "United States", - "region": " CA" - }, - "wind": { - "chill": "56", - "direction": "0", - "speed": "6.34" - }, - "atmosphere": { - "humidity": "71", - "pressure": "33863.75", - "rising": "0", - "visibility": "22.91" - }, - "astronomy": { - "sunrise": "6:21 am", - "sunset": "4:47 pm" - }, - "image": { - "title": "Yahoo! Weather", - "width": "142", - "height": "18", - "link": "http://weather.yahoo.com", - "url": "http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif" - }, - "item": { - "title": "Conditions for San Diego, CA, US at 05:00 AM PST", - "lat": "32.878101", - "long": "-117.23497", - "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-23511632/", - "pubDate": "Fri, 17 Nov 2017 05:00 AM PST", - "condition": { - "code": "26", - "date": "Fri, 17 Nov 2017 05:00 AM PST", - "temp": "18", - "text": "Cloudy" - }, - "forecast": [ - { - "code": "28", - "date": "17 Nov 2017", - "day": "Fri", - "high": "23", - "low": "16", - "text": "Mostly Cloudy" - }, - { - "code": "30", - "date": "18 Nov 2017", - "day": "Sat", - "high": "22", - "low": "13", - "text": "Partly Cloudy" - }, - { - "code": "30", - "date": "19 Nov 2017", - "day": "Sun", - "high": "22", - "low": "12", - "text": "Partly Cloudy" - }, - { - "code": "28", - "date": "20 Nov 2017", - "day": "Mon", - "high": "21", - "low": "11", - "text": "Mostly Cloudy" - }, - { - "code": "28", - "date": "21 Nov 2017", - "day": "Tue", - "high": "24", - "low": "14", - "text": "Mostly Cloudy" - }, - { - "code": "30", - "date": "22 Nov 2017", - "day": "Wed", - "high": "27", - "low": "15", - "text": "Partly Cloudy" - }, - { - "code": "34", - "date": "23 Nov 2017", - "day": "Thu", - "high": "27", - "low": "15", - "text": "Mostly Sunny" - }, - { - "code": "30", - "date": "24 Nov 2017", - "day": "Fri", - "high": "23", - "low": "16", - "text": "Partly Cloudy" - }, - { - "code": "30", - "date": "25 Nov 2017", - "day": "Sat", - "high": "22", - "low": "15", - "text": "Partly Cloudy" - }, - { - "code": "28", - "date": "26 Nov 2017", - "day": "Sun", - "high": "24", - "low": "13", - "text": "Mostly Cloudy" - } - ], - "description": "\n
\nCurrent Conditions:\n
Cloudy\n
\n
\nForecast:\n
Fri - Mostly Cloudy. High: 23Low: 16\n
Sat - Partly Cloudy. High: 22Low: 13\n
Sun - Partly Cloudy. High: 22Low: 12\n
Mon - Mostly Cloudy. High: 21Low: 11\n
Tue - Mostly Cloudy. High: 24Low: 14\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n
\n]]>", - "guid": { - "isPermaLink": "false" - } - } - } - } - } -} From 690ba103ed4118e906284957dca308a737851d3a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 16:17:44 +0100 Subject: [PATCH 0775/1691] Improve lists in integrations [A] (#113006) * Use list comprehension [A] * Use list comprehension [A] * Update homeassistant/components/aws/notify.py --- .../components/acmeda/config_flow.py | 9 +-- .../components/advantage_air/select.py | 5 +- .../components/advantage_air/switch.py | 8 ++- homeassistant/components/aemet/sensor.py | 24 +++---- homeassistant/components/airly/sensor.py | 16 +++-- homeassistant/components/airtouch5/climate.py | 6 +- .../components/airzone/binary_sensor.py | 50 +++++++------- homeassistant/components/airzone/select.py | 28 ++++---- homeassistant/components/airzone/sensor.py | 64 ++++++++--------- .../components/airzone_cloud/binary_sensor.py | 68 +++++++++---------- .../components/airzone_cloud/sensor.py | 68 +++++++++---------- .../components/alexa/capabilities.py | 9 +-- homeassistant/components/amcrest/__init__.py | 4 +- .../components/analytics/analytics.py | 20 +++--- homeassistant/components/aqualogic/switch.py | 9 ++- .../components/arcam_fmj/device_trigger.py | 28 ++++---- .../aseko_pool_live/binary_sensor.py | 10 +-- .../components/aseko_pool_live/sensor.py | 11 +-- .../components/assist_pipeline/pipeline.py | 10 +-- homeassistant/components/aten_pe/switch.py | 8 +-- homeassistant/components/august/sensor.py | 3 +- .../components/aurora_abb_powerone/sensor.py | 4 +- .../aussie_broadband/diagnostics.py | 11 ++- homeassistant/components/avion/light.py | 32 +++++---- homeassistant/components/aws/__init__.py | 4 +- homeassistant/components/aws/notify.py | 41 ++++++----- homeassistant/components/axis/config_flow.py | 3 +- .../assist_pipeline/test_pipeline.py | 6 +- tests/components/august/mocks.py | 5 +- 29 files changed, 267 insertions(+), 297 deletions(-) diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index c10f2c1b105..5024507a7d3 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -40,12 +40,13 @@ class AcmedaFlowHandler(ConfigFlow, domain=DOMAIN): entry.unique_id for entry in self._async_current_entries() } - hubs: list[aiopulse.Hub] = [] with suppress(TimeoutError): async with timeout(5): - async for hub in aiopulse.Hub.discover(): - if hub.id not in already_configured: - hubs.append(hub) + hubs: list[aiopulse.Hub] = [ + hub + async for hub in aiopulse.Hub.discover() + if hub.id not in already_configured + ] if not hubs: return self.async_abort(reason="no_devices_found") diff --git a/homeassistant/components/advantage_air/select.py b/homeassistant/components/advantage_air/select.py index 013f2cc214d..c3739717ef1 100644 --- a/homeassistant/components/advantage_air/select.py +++ b/homeassistant/components/advantage_air/select.py @@ -21,11 +21,8 @@ async def async_setup_entry( instance: AdvantageAirData = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id] - entities: list[SelectEntity] = [] if aircons := instance.coordinator.data.get("aircons"): - for ac_key in aircons: - entities.append(AdvantageAirMyZone(instance, ac_key)) - async_add_entities(entities) + async_add_entities(AdvantageAirMyZone(instance, ac_key) for ac_key in aircons) class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity): diff --git a/homeassistant/components/advantage_air/switch.py b/homeassistant/components/advantage_air/switch.py index 05cba606bf7..6d21f2e705c 100644 --- a/homeassistant/components/advantage_air/switch.py +++ b/homeassistant/components/advantage_air/switch.py @@ -34,9 +34,11 @@ async def async_setup_entry( if ADVANTAGE_AIR_AUTOFAN_ENABLED in ac_device["info"]: entities.append(AdvantageAirMyFan(instance, ac_key)) if things := instance.coordinator.data.get("myThings"): - for thing in things["things"].values(): - if thing["channelDipState"] == 8: # 8 = Other relay - entities.append(AdvantageAirRelay(instance, thing)) + entities.extend( + AdvantageAirRelay(instance, thing) + for thing in things["things"].values() + if thing["channelDipState"] == 8 # 8 = Other relay + ) async_add_entities(entities) diff --git a/homeassistant/components/aemet/sensor.py b/homeassistant/components/aemet/sensor.py index 2d93d43698b..0952af19d43 100644 --- a/homeassistant/components/aemet/sensor.py +++ b/homeassistant/components/aemet/sensor.py @@ -368,20 +368,16 @@ async def async_setup_entry( name: str = domain_data[ENTRY_NAME] coordinator: WeatherUpdateCoordinator = domain_data[ENTRY_WEATHER_COORDINATOR] - entities: list[AemetSensor] = [] - - for description in FORECAST_SENSORS + WEATHER_SENSORS: - if dict_nested_value(coordinator.data["lib"], description.keys) is not None: - entities.append( - AemetSensor( - name, - coordinator, - description, - config_entry, - ) - ) - - async_add_entities(entities) + async_add_entities( + AemetSensor( + name, + coordinator, + description, + config_entry, + ) + for description in FORECAST_SENSORS + WEATHER_SENSORS + if dict_nested_value(coordinator.data["lib"], description.keys) is not None + ) class AemetSensor(AemetEntity, SensorEntity): diff --git a/homeassistant/components/airly/sensor.py b/homeassistant/components/airly/sensor.py index 7bb759609f4..3d80a0870d8 100644 --- a/homeassistant/components/airly/sensor.py +++ b/homeassistant/components/airly/sensor.py @@ -181,13 +181,15 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][entry.entry_id] - sensors = [] - for description in SENSOR_TYPES: - # When we use the nearest method, we are not sure which sensors are available - if coordinator.data.get(description.key): - sensors.append(AirlySensor(coordinator, name, description)) - - async_add_entities(sensors, False) + async_add_entities( + ( + AirlySensor(coordinator, name, description) + for description in SENSOR_TYPES + # When we use the nearest method, we are not sure which sensors are available + if coordinator.data.get(description.key) + ), + False, + ) class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity): diff --git a/homeassistant/components/airtouch5/climate.py b/homeassistant/components/airtouch5/climate.py index 443f4b71a4a..157e3b7d643 100644 --- a/homeassistant/components/airtouch5/climate.py +++ b/homeassistant/components/airtouch5/climate.py @@ -109,8 +109,10 @@ async def async_setup_entry( entities.append(Airtouch5AC(client, ac)) # Add each zone - for zone in client.zones: - entities.append(Airtouch5Zone(client, zone, zone_to_ac[zone.zone_number])) + entities.extend( + Airtouch5Zone(client, zone, zone_to_ac[zone.zone_number]) + for zone in client.zones + ) async_add_entities(entities) diff --git a/homeassistant/components/airzone/binary_sensor.py b/homeassistant/components/airzone/binary_sensor.py index 374507aeb98..e25751f2a47 100644 --- a/homeassistant/components/airzone/binary_sensor.py +++ b/homeassistant/components/airzone/binary_sensor.py @@ -80,33 +80,31 @@ async def async_setup_entry( """Add Airzone binary sensors from a config_entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - binary_sensors: list[AirzoneBinarySensor] = [] + binary_sensors: list[AirzoneBinarySensor] = [ + AirzoneSystemBinarySensor( + coordinator, + description, + entry, + system_id, + system_data, + ) + for system_id, system_data in coordinator.data[AZD_SYSTEMS].items() + for description in SYSTEM_BINARY_SENSOR_TYPES + if description.key in system_data + ] - for system_id, system_data in coordinator.data[AZD_SYSTEMS].items(): - for description in SYSTEM_BINARY_SENSOR_TYPES: - if description.key in system_data: - binary_sensors.append( - AirzoneSystemBinarySensor( - coordinator, - description, - entry, - system_id, - system_data, - ) - ) - - for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): - for description in ZONE_BINARY_SENSOR_TYPES: - if description.key in zone_data: - binary_sensors.append( - AirzoneZoneBinarySensor( - coordinator, - description, - entry, - system_zone_id, - zone_data, - ) - ) + binary_sensors.extend( + AirzoneZoneBinarySensor( + coordinator, + description, + entry, + system_zone_id, + zone_data, + ) + for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items() + for description in ZONE_BINARY_SENSOR_TYPES + if description.key in zone_data + ) async_add_entities(binary_sensors) diff --git a/homeassistant/components/airzone/select.py b/homeassistant/components/airzone/select.py index fd3dec551dd..6e92394bb05 100644 --- a/homeassistant/components/airzone/select.py +++ b/homeassistant/components/airzone/select.py @@ -84,22 +84,18 @@ async def async_setup_entry( """Add Airzone sensors from a config_entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - entities: list[AirzoneBaseSelect] = [] - - for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): - for description in ZONE_SELECT_TYPES: - if description.key in zone_data: - entities.append( - AirzoneZoneSelect( - coordinator, - description, - entry, - system_zone_id, - zone_data, - ) - ) - - async_add_entities(entities) + async_add_entities( + AirzoneZoneSelect( + coordinator, + description, + entry, + system_zone_id, + zone_data, + ) + for description in ZONE_SELECT_TYPES + for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items() + if description.key in zone_data + ) class AirzoneBaseSelect(AirzoneEntity, SelectEntity): diff --git a/homeassistant/components/airzone/sensor.py b/homeassistant/components/airzone/sensor.py index b4e0c7aa818..e2f9eabc6f6 100644 --- a/homeassistant/components/airzone/sensor.py +++ b/homeassistant/components/airzone/sensor.py @@ -82,44 +82,40 @@ async def async_setup_entry( """Add Airzone sensors from a config_entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - sensors: list[AirzoneSensor] = [] + sensors: list[AirzoneSensor] = [ + AirzoneZoneSensor( + coordinator, + description, + entry, + system_zone_id, + zone_data, + ) + for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items() + for description in ZONE_SENSOR_TYPES + if description.key in zone_data + ] if AZD_HOT_WATER in coordinator.data: - dhw_data = coordinator.data[AZD_HOT_WATER] - for description in HOT_WATER_SENSOR_TYPES: - if description.key in dhw_data: - sensors.append( - AirzoneHotWaterSensor( - coordinator, - description, - entry, - ) - ) + sensors.extend( + AirzoneHotWaterSensor( + coordinator, + description, + entry, + ) + for description in HOT_WATER_SENSOR_TYPES + if description.key in coordinator.data[AZD_HOT_WATER] + ) if AZD_WEBSERVER in coordinator.data: - ws_data = coordinator.data[AZD_WEBSERVER] - for description in WEBSERVER_SENSOR_TYPES: - if description.key in ws_data: - sensors.append( - AirzoneWebServerSensor( - coordinator, - description, - entry, - ) - ) - - for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items(): - for description in ZONE_SENSOR_TYPES: - if description.key in zone_data: - sensors.append( - AirzoneZoneSensor( - coordinator, - description, - entry, - system_zone_id, - zone_data, - ) - ) + sensors.extend( + AirzoneWebServerSensor( + coordinator, + description, + entry, + ) + for description in WEBSERVER_SENSOR_TYPES + if description.key in coordinator.data[AZD_WEBSERVER] + ) async_add_entities(sensors) diff --git a/homeassistant/components/airzone_cloud/binary_sensor.py b/homeassistant/components/airzone_cloud/binary_sensor.py index 8730eb8977e..9266ee3445e 100644 --- a/homeassistant/components/airzone_cloud/binary_sensor.py +++ b/homeassistant/components/airzone_cloud/binary_sensor.py @@ -99,43 +99,41 @@ async def async_setup_entry( """Add Airzone Cloud binary sensors from a config_entry.""" coordinator: AirzoneUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - binary_sensors: list[AirzoneBinarySensor] = [] + binary_sensors: list[AirzoneBinarySensor] = [ + AirzoneAidooBinarySensor( + coordinator, + description, + aidoo_id, + aidoo_data, + ) + for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items() + for description in AIDOO_BINARY_SENSOR_TYPES + if description.key in aidoo_data + ] - for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items(): - for description in AIDOO_BINARY_SENSOR_TYPES: - if description.key in aidoo_data: - binary_sensors.append( - AirzoneAidooBinarySensor( - coordinator, - description, - aidoo_id, - aidoo_data, - ) - ) + binary_sensors.extend( + AirzoneSystemBinarySensor( + coordinator, + description, + system_id, + system_data, + ) + for system_id, system_data in coordinator.data.get(AZD_SYSTEMS, {}).items() + for description in SYSTEM_BINARY_SENSOR_TYPES + if description.key in system_data + ) - for system_id, system_data in coordinator.data.get(AZD_SYSTEMS, {}).items(): - for description in SYSTEM_BINARY_SENSOR_TYPES: - if description.key in system_data: - binary_sensors.append( - AirzoneSystemBinarySensor( - coordinator, - description, - system_id, - system_data, - ) - ) - - for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items(): - for description in ZONE_BINARY_SENSOR_TYPES: - if description.key in zone_data: - binary_sensors.append( - AirzoneZoneBinarySensor( - coordinator, - description, - zone_id, - zone_data, - ) - ) + binary_sensors.extend( + AirzoneZoneBinarySensor( + coordinator, + description, + zone_id, + zone_data, + ) + for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items() + for description in ZONE_BINARY_SENSOR_TYPES + if description.key in zone_data + ) async_add_entities(binary_sensors) diff --git a/homeassistant/components/airzone_cloud/sensor.py b/homeassistant/components/airzone_cloud/sensor.py index 430248d0c64..febbbcc7ef6 100644 --- a/homeassistant/components/airzone_cloud/sensor.py +++ b/homeassistant/components/airzone_cloud/sensor.py @@ -108,46 +108,44 @@ async def async_setup_entry( """Add Airzone Cloud sensors from a config_entry.""" coordinator: AirzoneUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - sensors: list[AirzoneSensor] = [] - # Aidoos - for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items(): - for description in AIDOO_SENSOR_TYPES: - if description.key in aidoo_data: - sensors.append( - AirzoneAidooSensor( - coordinator, - description, - aidoo_id, - aidoo_data, - ) - ) + sensors: list[AirzoneSensor] = [ + AirzoneAidooSensor( + coordinator, + description, + aidoo_id, + aidoo_data, + ) + for aidoo_id, aidoo_data in coordinator.data.get(AZD_AIDOOS, {}).items() + for description in AIDOO_SENSOR_TYPES + if description.key in aidoo_data + ] # WebServers - for ws_id, ws_data in coordinator.data.get(AZD_WEBSERVERS, {}).items(): - for description in WEBSERVER_SENSOR_TYPES: - if description.key in ws_data: - sensors.append( - AirzoneWebServerSensor( - coordinator, - description, - ws_id, - ws_data, - ) - ) + sensors.extend( + AirzoneWebServerSensor( + coordinator, + description, + ws_id, + ws_data, + ) + for ws_id, ws_data in coordinator.data.get(AZD_WEBSERVERS, {}).items() + for description in WEBSERVER_SENSOR_TYPES + if description.key in ws_data + ) # Zones - for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items(): - for description in ZONE_SENSOR_TYPES: - if description.key in zone_data: - sensors.append( - AirzoneZoneSensor( - coordinator, - description, - zone_id, - zone_data, - ) - ) + sensors.extend( + AirzoneZoneSensor( + coordinator, + description, + zone_id, + zone_data, + ) + for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items() + for description in ZONE_SENSOR_TYPES + if description.key in zone_data + ) async_add_entities(sensors) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index d553be2e8cc..1ef1b42b2f8 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1196,11 +1196,12 @@ class AlexaThermostatController(AlexaCapability): if self.entity.domain == water_heater.DOMAIN: return None - supported_modes: list[str] = [] hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES) or [] - for mode in hvac_modes: - if thermostat_mode := API_THERMOSTAT_MODES.get(mode): - supported_modes.append(thermostat_mode) + supported_modes: list[str] = [ + API_THERMOSTAT_MODES[mode] + for mode in hvac_modes + if mode in API_THERMOSTAT_MODES + ] preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES) if preset_modes: diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index 1b205e4a424..6a5481f0d1a 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -442,9 +442,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return entity_ids async def async_service_handler(call: ServiceCall) -> None: - args = [] - for arg in CAMERA_SERVICES[call.service][2]: - args.append(call.data[arg]) + args = [call.data[arg] for arg in CAMERA_SERVICES[call.service][2]] for entity_id in await async_extract_from_service(call): async_dispatcher_send(hass, service_signal(call.service, entity_id), *args) diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index 2528dbd78e8..01c8bf22787 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -193,7 +193,7 @@ class Analytics: system_info = await async_get_system_info(hass) integrations = [] custom_integrations = [] - addons = [] + addons: list[dict[str, Any]] = [] payload: dict = { ATTR_UUID: self.uuid, ATTR_VERSION: HA_VERSION, @@ -267,15 +267,15 @@ class Analytics: for addon in supervisor_info[ATTR_ADDONS] ) ) - for addon in installed_addons: - addons.append( - { - ATTR_SLUG: addon[ATTR_SLUG], - ATTR_PROTECTED: addon[ATTR_PROTECTED], - ATTR_VERSION: addon[ATTR_VERSION], - ATTR_AUTO_UPDATE: addon[ATTR_AUTO_UPDATE], - } - ) + addons.extend( + { + ATTR_SLUG: addon[ATTR_SLUG], + ATTR_PROTECTED: addon[ATTR_PROTECTED], + ATTR_VERSION: addon[ATTR_VERSION], + ATTR_AUTO_UPDATE: addon[ATTR_AUTO_UPDATE], + } + for addon in installed_addons + ) if self.preferences.get(ATTR_USAGE, False): payload[ATTR_CERTIFICATE] = hass.http.ssl_certificate is not None diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py index 1ba268f0f2f..0f1a7e34b3c 100644 --- a/homeassistant/components/aqualogic/switch.py +++ b/homeassistant/components/aqualogic/switch.py @@ -46,13 +46,12 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the switch platform.""" - switches = [] - processor: AquaLogicProcessor = hass.data[DOMAIN] - for switch_type in config[CONF_MONITORED_CONDITIONS]: - switches.append(AquaLogicSwitch(processor, switch_type)) - async_add_entities(switches) + async_add_entities( + AquaLogicSwitch(processor, switch_type) + for switch_type in config[CONF_MONITORED_CONDITIONS] + ) class AquaLogicSwitch(SwitchEntity): diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py index 6147e05f804..16061f3a1e1 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -33,23 +33,19 @@ async def async_get_triggers( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device triggers for Arcam FMJ Receiver control devices.""" - registry = er.async_get(hass) - triggers = [] + entity_registry = er.async_get(hass) - # Get all the integrations entities for this device - for entry in er.async_entries_for_device(registry, device_id): - if entry.domain == "media_player": - triggers.append( - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_ENTITY_ID: entry.id, - CONF_TYPE: "turn_on", - } - ) - - return triggers + return [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.id, + CONF_TYPE: "turn_on", + } + for entry in er.async_entries_for_device(entity_registry, device_id) + if entry.domain == "media_player" + ] async def async_attach_trigger( diff --git a/homeassistant/components/aseko_pool_live/binary_sensor.py b/homeassistant/components/aseko_pool_live/binary_sensor.py index 1013ee66c8e..dbbdff38200 100644 --- a/homeassistant/components/aseko_pool_live/binary_sensor.py +++ b/homeassistant/components/aseko_pool_live/binary_sensor.py @@ -57,11 +57,11 @@ async def async_setup_entry( data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][ config_entry.entry_id ] - entities: list[BinarySensorEntity] = [] - for unit, coordinator in data: - for description in UNIT_BINARY_SENSORS: - entities.append(AsekoUnitBinarySensorEntity(unit, coordinator, description)) - async_add_entities(entities) + async_add_entities( + AsekoUnitBinarySensorEntity(unit, coordinator, description) + for unit, coordinator in data + for description in UNIT_BINARY_SENSORS + ) class AsekoUnitBinarySensorEntity(AsekoEntity, BinarySensorEntity): diff --git a/homeassistant/components/aseko_pool_live/sensor.py b/homeassistant/components/aseko_pool_live/sensor.py index 262c7acd1aa..a4ddea9ad89 100644 --- a/homeassistant/components/aseko_pool_live/sensor.py +++ b/homeassistant/components/aseko_pool_live/sensor.py @@ -27,11 +27,12 @@ async def async_setup_entry( data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][ config_entry.entry_id ] - entities = [] - for unit, coordinator in data: - for variable in unit.variables: - entities.append(VariableSensorEntity(unit, variable, coordinator)) - async_add_entities(entities) + + async_add_entities( + VariableSensorEntity(unit, variable, coordinator) + for unit, coordinator in data + for variable in unit.variables + ) class VariableSensorEntity(AsekoEntity, SensorEntity): diff --git a/homeassistant/components/assist_pipeline/pipeline.py b/homeassistant/components/assist_pipeline/pipeline.py index 5a3f01b7fc1..e55f73ce8a5 100644 --- a/homeassistant/components/assist_pipeline/pipeline.py +++ b/homeassistant/components/assist_pipeline/pipeline.py @@ -765,12 +765,12 @@ class PipelineRun: # spoken, we need to make sure pending audio is forwarded to # speech-to-text so the user does not have to pause before # speaking the voice command. - for chunk_ts in result.queued_audio: - audio_chunks_for_stt.append( - ProcessedAudioChunk( - audio=chunk_ts[0], timestamp_ms=chunk_ts[1], is_speech=False - ) + audio_chunks_for_stt.extend( + ProcessedAudioChunk( + audio=chunk_ts[0], timestamp_ms=chunk_ts[1], is_speech=False ) + for chunk_ts in result.queued_audio + ) wake_word_output = asdict(result) diff --git a/homeassistant/components/aten_pe/switch.py b/homeassistant/components/aten_pe/switch.py index 75c43dca079..1349014d8fb 100644 --- a/homeassistant/components/aten_pe/switch.py +++ b/homeassistant/components/aten_pe/switch.py @@ -80,11 +80,9 @@ async def async_setup_platform( sw_version=sw_version, ) - switches = [] - async for outlet in outlets: - switches.append(AtenSwitch(dev, info, mac, outlet.id, outlet.name)) - - async_add_entities(switches, True) + async_add_entities( + (AtenSwitch(dev, info, mac, outlet.id, outlet.name) for outlet in outlets), True + ) class AtenSwitch(SwitchEntity): diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 8d5270dcfee..6ccdccfce7d 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -151,8 +151,7 @@ async def async_setup_entry( entities.append(keypad_battery_sensor) migrate_unique_id_devices.append(keypad_battery_sensor) - for device in operation_sensors: - entities.append(AugustOperatorSensor(data, device)) + entities.extend(AugustOperatorSensor(data, device) for device in operation_sensors) await _async_migrate_old_unique_ids(hass, migrate_unique_id_devices) diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 9c1a8e2a12c..63e822db44f 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -79,13 +79,11 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up aurora_abb_powerone sensor based on a config entry.""" - entities = [] coordinator = hass.data[DOMAIN][config_entry.entry_id] data = config_entry.data - for sens in SENSOR_TYPES: - entities.append(AuroraSensor(coordinator, data, sens)) + entities = [AuroraSensor(coordinator, data, sens) for sens in SENSOR_TYPES] _LOGGER.debug("async_setup_entry adding %d entities", len(entities)) async_add_entities(entities, True) diff --git a/homeassistant/components/aussie_broadband/diagnostics.py b/homeassistant/components/aussie_broadband/diagnostics.py index 499a739637e..c71cfd090da 100644 --- a/homeassistant/components/aussie_broadband/diagnostics.py +++ b/homeassistant/components/aussie_broadband/diagnostics.py @@ -17,13 +17,12 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - services = [] - for service in hass.data[DOMAIN][config_entry.entry_id]["services"]: - services.append( + return { + "services": [ { "service": async_redact_data(service, TO_REDACT), "usage": async_redact_data(service["coordinator"].data, ["historical"]), } - ) - - return {"services": services} + for service in hass.data[DOMAIN][config_entry.entry_id]["services"] + ] + } diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index 91a63330249..e26676a0169 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -53,21 +53,25 @@ def setup_platform( """Set up an Avion switch.""" avion = importlib.import_module("avion") - lights = [] - if CONF_USERNAME in config and CONF_PASSWORD in config: - devices = avion.get_devices(config[CONF_USERNAME], config[CONF_PASSWORD]) - for device in devices: - lights.append(AvionLight(device)) - - for address, device_config in config[CONF_DEVICES].items(): - device = avion.Avion( - mac=address, - passphrase=device_config[CONF_API_KEY], - name=device_config.get(CONF_NAME), - object_id=device_config.get(CONF_ID), - connect=False, + lights = [ + AvionLight( + avion.Avion( + mac=address, + passphrase=device_config[CONF_API_KEY], + name=device_config.get(CONF_NAME), + object_id=device_config.get(CONF_ID), + connect=False, + ) + ) + for address, device_config in config[CONF_DEVICES].items() + ] + if CONF_USERNAME in config and CONF_PASSWORD in config: + lights.extend( + AvionLight(device) + for device in avion.get_devices( + config[CONF_USERNAME], config[CONF_PASSWORD] + ) ) - lights.append(AvionLight(device)) add_entities(lights) diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index 9335594b925..da84c8985f5 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -129,9 +129,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # validate credentials and create sessions validation = True - tasks = [] - for cred in conf[ATTR_CREDENTIALS]: - tasks.append(_validate_aws_credentials(hass, cred)) + tasks = [_validate_aws_credentials(hass, cred) for cred in conf[ATTR_CREDENTIALS]] if tasks: results = await asyncio.gather(*tasks, return_exceptions=True) for index, result in enumerate(results): diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py index 9fdf0f5b193..054560ca12a 100644 --- a/homeassistant/components/aws/notify.py +++ b/homeassistant/components/aws/notify.py @@ -155,15 +155,14 @@ class AWSLambda(AWSNotify): async with self.session.create_client( self.service, **self.aws_config ) as client: - tasks = [] - for target in kwargs.get(ATTR_TARGET, []): - tasks.append( - client.invoke( - FunctionName=target, - Payload=json_payload, - ClientContext=self.context, - ) + tasks = [ + client.invoke( + FunctionName=target, + Payload=json_payload, + ClientContext=self.context, ) + for target in kwargs.get(ATTR_TARGET, []) + ] if tasks: await asyncio.gather(*tasks) @@ -192,16 +191,15 @@ class AWSSNS(AWSNotify): async with self.session.create_client( self.service, **self.aws_config ) as client: - tasks = [] - for target in kwargs.get(ATTR_TARGET, []): - tasks.append( - client.publish( - TargetArn=target, - Message=message, - Subject=subject, - MessageAttributes=message_attributes, - ) + tasks = [ + client.publish( + TargetArn=target, + Message=message, + Subject=subject, + MessageAttributes=message_attributes, ) + for target in kwargs.get(ATTR_TARGET, []) + ] if tasks: await asyncio.gather(*tasks) @@ -265,7 +263,6 @@ class AWSEventBridge(AWSNotify): async with self.session.create_client( self.service, **self.aws_config ) as client: - tasks = [] entries = [] for target in kwargs.get(ATTR_TARGET, [None]): entry = { @@ -278,10 +275,10 @@ class AWSEventBridge(AWSNotify): entry["EventBusName"] = target entries.append(entry) - for i in range(0, len(entries), 10): - tasks.append( - client.put_events(Entries=entries[i : min(i + 10, len(entries))]) - ) + tasks = [ + client.put_events(Entries=entries[i : min(i + 10, len(entries))]) + for i in range(0, len(entries), 10) + ] if tasks: results = await asyncio.gather(*tasks) diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 66e338f0fdf..0fccd3654cf 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -267,8 +267,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): and profiles.max_groups > 0 ): stream_profiles = [DEFAULT_STREAM_PROFILE] - for profile in vapix.streaming_profiles: - stream_profiles.append(profile.name) + stream_profiles.extend(profile.name for profile in vapix.streaming_profiles) schema[ vol.Optional( diff --git a/tests/components/assist_pipeline/test_pipeline.py b/tests/components/assist_pipeline/test_pipeline.py index 978b3363cd8..40b701588a0 100644 --- a/tests/components/assist_pipeline/test_pipeline.py +++ b/tests/components/assist_pipeline/test_pipeline.py @@ -86,12 +86,12 @@ async def test_load_pipelines(hass: HomeAssistant, init_components) -> None: "wake_word_id": "wakeword_id_3", }, ] - pipeline_ids = [] pipeline_data: PipelineData = hass.data[DOMAIN] store1 = pipeline_data.pipeline_store - for pipeline in pipelines: - pipeline_ids.append((await store1.async_create_item(pipeline)).id) + pipeline_ids = [ + (await store1.async_create_item(pipeline)).id for pipeline in pipelines + ] assert len(store1.data) == 4 # 3 manually created plus a default pipeline assert store1.async_get_preferred_item() == list(store1.data)[0] diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 4e017066e90..df52e900dbd 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -136,10 +136,7 @@ async def _create_august_api_with_devices( # noqa: C901 raise ValueError def _get_base_devices(device_type): - base_devices = [] - for device in device_data[device_type]: - base_devices.append(device["base"]) - return base_devices + return [device["base"] for device in device_data[device_type]] def get_lock_detail_side_effect(access_token, device_id): return _get_device_detail("locks", device_id) From 7fce629fc48fd52cba2d9e6b5c18795abbe3aca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Mar 2024 17:01:19 +0100 Subject: [PATCH 0776/1691] Change airthings pressure sensor device_class to atmospheric pressure (#113005) --- homeassistant/components/airthings/sensor.py | 2 +- homeassistant/components/airthings_ble/sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/airthings/sensor.py b/homeassistant/components/airthings/sensor.py index 6787326be8c..fc91d816aca 100644 --- a/homeassistant/components/airthings/sensor.py +++ b/homeassistant/components/airthings/sensor.py @@ -48,7 +48,7 @@ SENSORS: dict[str, SensorEntityDescription] = { ), "pressure": SensorEntityDescription( key="pressure", - device_class=SensorDeviceClass.PRESSURE, + device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, native_unit_of_measurement=UnitOfPressure.MBAR, ), "battery": SensorEntityDescription( diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index 941ea1d6b45..e281d81827c 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -82,7 +82,7 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { ), "pressure": SensorEntityDescription( key="pressure", - device_class=SensorDeviceClass.PRESSURE, + device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, native_unit_of_measurement=UnitOfPressure.MBAR, state_class=SensorStateClass.MEASUREMENT, ), From 9f3142313669d263cac4da2f18f386662930545c Mon Sep 17 00:00:00 2001 From: Lukas de Boer Date: Mon, 11 Mar 2024 17:30:51 +0100 Subject: [PATCH 0777/1691] Bump rova to 0.4.1 (#113066) --- homeassistant/components/rova/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rova/manifest.json b/homeassistant/components/rova/manifest.json index 03c6ddbb12c..a87ec224122 100644 --- a/homeassistant/components/rova/manifest.json +++ b/homeassistant/components/rova/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/rova", "iot_class": "cloud_polling", "loggers": ["rova"], - "requirements": ["rova==0.4.0"] + "requirements": ["rova==0.4.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 27994a312a4..a25fd2d261f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2463,7 +2463,7 @@ roombapy==1.6.13 roonapi==0.1.6 # homeassistant.components.rova -rova==0.4.0 +rova==0.4.1 # homeassistant.components.rpi_power rpi-bad-power==0.1.0 From 4d77bec68157bf71775a0fb6f9dd9c0bc5b0fc43 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 11 Mar 2024 11:37:00 -0600 Subject: [PATCH 0778/1691] Bump `regenmaschine` to 2024.03.0 (#113074) --- homeassistant/components/rainmachine/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rainmachine/manifest.json b/homeassistant/components/rainmachine/manifest.json index 1c4c78564f6..0d871998eed 100644 --- a/homeassistant/components/rainmachine/manifest.json +++ b/homeassistant/components/rainmachine/manifest.json @@ -10,7 +10,7 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["regenmaschine"], - "requirements": ["regenmaschine==2024.01.0"], + "requirements": ["regenmaschine==2024.03.0"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index a25fd2d261f..837d46b5235 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2421,7 +2421,7 @@ raspyrfm-client==1.2.8 refoss-ha==1.2.0 # homeassistant.components.rainmachine -regenmaschine==2024.01.0 +regenmaschine==2024.03.0 # homeassistant.components.renault renault-api==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9d7a4e52235..2cfe78978d6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1864,7 +1864,7 @@ rapt-ble==0.1.2 refoss-ha==1.2.0 # homeassistant.components.rainmachine -regenmaschine==2024.01.0 +regenmaschine==2024.03.0 # homeassistant.components.renault renault-api==0.2.1 From c75342bd9af877348c450d88ff0ca9fc531046a7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 18:58:35 +0100 Subject: [PATCH 0779/1691] Improve lists in integrations [C-D] (#113072) --- homeassistant/components/canary/camera.py | 30 +++++----- homeassistant/components/canary/sensor.py | 10 ++-- homeassistant/components/cloud/http_api.py | 36 +++++------ homeassistant/components/coinbase/sensor.py | 11 ++-- homeassistant/components/control4/__init__.py | 10 ++-- .../components/crownstone/helpers.py | 21 +++---- homeassistant/components/cups/sensor.py | 6 +- .../components/currencylayer/sensor.py | 8 +-- .../components/danfoss_air/binary_sensor.py | 13 ++-- .../components/danfoss_air/sensor.py | 13 ++-- .../components/danfoss_air/switch.py | 10 ++-- .../components/deconz/config_flow.py | 5 +- homeassistant/components/decora_wifi/light.py | 13 ++-- homeassistant/components/delijn/sensor.py | 12 ++-- .../devolo_home_control/binary_sensor.py | 44 +++++++------- .../components/devolo_home_control/climate.py | 36 ++++++----- .../components/devolo_home_control/cover.py | 25 ++++---- .../devolo_home_control/diagnostics.py | 37 ++++++------ .../components/devolo_home_control/light.py | 25 ++++---- .../components/devolo_home_control/sensor.py | 59 ++++++++++--------- .../components/devolo_home_control/siren.py | 25 ++++---- .../components/devolo_home_control/switch.py | 29 ++++----- .../components/directv/media_player.py | 12 ++-- homeassistant/components/directv/remote.py | 12 ++-- .../dlib_face_detect/image_processing.py | 11 ++-- .../dlib_face_identify/image_processing.py | 19 +++--- .../components/doods/image_processing.py | 22 ++++--- homeassistant/components/dsmr/sensor.py | 4 +- homeassistant/components/dynalite/__init__.py | 9 +-- .../components/dynalite/dynalitebase.py | 4 +- tests/components/climate/test_init.py | 11 ++-- tests/components/cover/test_init.py | 5 +- 32 files changed, 276 insertions(+), 311 deletions(-) diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index f1aac2b17a0..e081d24e06a 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -64,22 +64,22 @@ async def async_setup_entry( ffmpeg_arguments: str = entry.options.get( CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS ) - cameras: list[CanaryCamera] = [] - for location_id, location in coordinator.data["locations"].items(): - for device in location.devices: - if device.is_online: - cameras.append( - CanaryCamera( - hass, - coordinator, - location_id, - device, - ffmpeg_arguments, - ) - ) - - async_add_entities(cameras, True) + async_add_entities( + ( + CanaryCamera( + hass, + coordinator, + location_id, + device, + ffmpeg_arguments, + ) + for location_id, location in coordinator.data["locations"].items() + for device in location.devices + if device.is_online + ), + True, + ) class CanaryCamera(CoordinatorEntity[CanaryDataUpdateCoordinator], Camera): diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 1fcce204d3c..905214e0d1d 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -75,11 +75,11 @@ async def async_setup_entry( for device in location.devices: if device.is_online: device_type = device.device_type - for sensor_type in SENSOR_TYPES: - if device_type.get("name") in sensor_type[4]: - sensors.append( - CanarySensor(coordinator, sensor_type, location, device) - ) + sensors.extend( + CanarySensor(coordinator, sensor_type, location, device) + for sensor_type in SENSOR_TYPES + if device_type.get("name") in sensor_type[4] + ) async_add_entities(sensors, True) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 87e5d00ff8f..a45077c79c4 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -649,16 +649,14 @@ async def google_assistant_list( gconf = await cloud.client.get_google_config() entities = google_helpers.async_get_entities(hass, gconf) - result = [] - - for entity in entities: - result.append( - { - "entity_id": entity.entity_id, - "traits": [trait.name for trait in entity.traits()], - "might_2fa": entity.might_2fa_traits(), - } - ) + result = [ + { + "entity_id": entity.entity_id, + "traits": [trait.name for trait in entity.traits()], + "might_2fa": entity.might_2fa_traits(), + } + for entity in entities + ] connection.send_result(msg["id"], result) @@ -743,16 +741,14 @@ async def alexa_list( alexa_config = await cloud.client.get_alexa_config() entities = alexa_entities.async_get_entities(hass, alexa_config) - result = [] - - for entity in entities: - result.append( - { - "entity_id": entity.entity_id, - "display_categories": entity.default_display_categories(), - "interfaces": [ifc.name() for ifc in entity.interfaces()], - } - ) + result = [ + { + "entity_id": entity.entity_id, + "display_categories": entity.default_display_categories(), + "interfaces": [ifc.name() for ifc in entity.interfaces()], + } + for entity in entities + ] connection.send_result(msg["id"], result) diff --git a/homeassistant/components/coinbase/sensor.py b/homeassistant/components/coinbase/sensor.py index 16fb8006fe9..83c63fa55fb 100644 --- a/homeassistant/components/coinbase/sensor.py +++ b/homeassistant/components/coinbase/sensor.py @@ -85,13 +85,12 @@ async def async_setup_entry( entities.append(AccountSensor(instance, currency)) if CONF_EXCHANGE_RATES in config_entry.options: - rate: str - for rate in config_entry.options[CONF_EXCHANGE_RATES]: - entities.append( - ExchangeRateSensor( - instance, rate, exchange_base_currency, exchange_precision - ) + entities.extend( + ExchangeRateSensor( + instance, rate, exchange_base_currency, exchange_precision ) + for rate in config_entry.options[CONF_EXCHANGE_RATES] + ) async_add_entities(entities) diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index f63d437dd4c..6776483e7e3 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -138,11 +138,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def get_items_of_category(hass: HomeAssistant, entry: ConfigEntry, category: str): """Return a list of all Control4 items with the specified category.""" director_all_items = hass.data[DOMAIN][entry.entry_id][CONF_DIRECTOR_ALL_ITEMS] - return_list = [] - for item in director_all_items: - if "categories" in item and category in item["categories"]: - return_list.append(item) - return return_list + return [ + item + for item in director_all_items + if "categories" in item and category in item["categories"] + ] class Control4Entity(CoordinatorEntity): diff --git a/homeassistant/components/crownstone/helpers.py b/homeassistant/components/crownstone/helpers.py index aeb26b19ac3..0dc86ea5f36 100644 --- a/homeassistant/components/crownstone/helpers.py +++ b/homeassistant/components/crownstone/helpers.py @@ -24,17 +24,18 @@ def list_ports_as_str( if no_usb_option: ports_as_string.append(DONT_USE_USB) - for port in serial_ports: - ports_as_string.append( - usb.human_readable_device_name( - port.device, - port.serial_number, - port.manufacturer, - port.description, - f"{hex(port.vid)[2:]:0>4}".upper() if port.vid else None, - f"{hex(port.pid)[2:]:0>4}".upper() if port.pid else None, - ) + ports_as_string.extend( + usb.human_readable_device_name( + port.device, + port.serial_number, + port.manufacturer, + port.description, + f"{hex(port.vid)[2:]:0>4}".upper() if port.vid else None, + f"{hex(port.pid)[2:]:0>4}".upper() if port.pid else None, ) + for port in serial_ports + ) + ports_as_string.append(MANUAL_PATH) ports_as_string.append(REFRESH_LIST) diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 91c0bb5c4b8..36f108a1c14 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -85,8 +85,10 @@ def setup_platform( dev.append(CupsSensor(data, printer)) if "marker-names" in data.attributes[printer]: - for marker in data.attributes[printer]["marker-names"]: - dev.append(MarkerSensor(data, printer, marker, True)) + dev.extend( + MarkerSensor(data, printer, marker, True) + for marker in data.attributes[printer]["marker-names"] + ) add_entities(dev, True) return diff --git a/homeassistant/components/currencylayer/sensor.py b/homeassistant/components/currencylayer/sensor.py index f5ef455ddfe..2fdf521ad9f 100644 --- a/homeassistant/components/currencylayer/sensor.py +++ b/homeassistant/components/currencylayer/sensor.py @@ -48,12 +48,12 @@ def setup_platform( rest = CurrencylayerData(_RESOURCE, parameters) response = requests.get(_RESOURCE, params=parameters, timeout=10) - sensors = [] - for variable in config[CONF_QUOTE]: - sensors.append(CurrencylayerSensor(rest, base, variable)) if "error" in response.json(): return - add_entities(sensors, True) + add_entities( + (CurrencylayerSensor(rest, base, variable) for variable in config[CONF_QUOTE]), + True, + ) class CurrencylayerSensor(SensorEntity): diff --git a/homeassistant/components/danfoss_air/binary_sensor.py b/homeassistant/components/danfoss_air/binary_sensor.py index c8da17bfddc..358d6ca07ab 100644 --- a/homeassistant/components/danfoss_air/binary_sensor.py +++ b/homeassistant/components/danfoss_air/binary_sensor.py @@ -33,12 +33,13 @@ def setup_platform( ["Danfoss Air Away Mode Active", ReadCommand.away_mode, None], ] - dev = [] - - for sensor in sensors: - dev.append(DanfossAirBinarySensor(data, sensor[0], sensor[1], sensor[2])) - - add_entities(dev, True) + add_entities( + ( + DanfossAirBinarySensor(data, sensor[0], sensor[1], sensor[2]) + for sensor in sensors + ), + True, + ) class DanfossAirBinarySensor(BinarySensorEntity): diff --git a/homeassistant/components/danfoss_air/sensor.py b/homeassistant/components/danfoss_air/sensor.py index 3f1e2b577a6..85b4e89d434 100644 --- a/homeassistant/components/danfoss_air/sensor.py +++ b/homeassistant/components/danfoss_air/sensor.py @@ -97,14 +97,13 @@ def setup_platform( ], ] - dev = [] - - for sensor in sensors: - dev.append( + add_entities( + ( DanfossAir(data, sensor[0], sensor[1], sensor[2], sensor[3], sensor[4]) - ) - - add_entities(dev, True) + for sensor in sensors + ), + True, + ) class DanfossAir(SensorEntity): diff --git a/homeassistant/components/danfoss_air/switch.py b/homeassistant/components/danfoss_air/switch.py index c014f226384..dc3277078b0 100644 --- a/homeassistant/components/danfoss_air/switch.py +++ b/homeassistant/components/danfoss_air/switch.py @@ -47,12 +47,10 @@ def setup_platform( ], ] - dev = [] - - for switch in switches: - dev.append(DanfossAir(data, switch[0], switch[1], switch[2], switch[3])) - - add_entities(dev) + add_entities( + DanfossAir(data, switch[0], switch[1], switch[2], switch[3]) + for switch in switches + ) class DanfossAir(SwitchEntity): diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index 88f4dd3edb7..ba444be1a8f 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -114,10 +114,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN): LOGGER.debug("Discovered deCONZ gateways %s", pformat(self.bridges)) if self.bridges: - hosts = [] - - for bridge in self.bridges: - hosts.append(bridge[CONF_HOST]) + hosts = [bridge[CONF_HOST] for bridge in self.bridges] hosts.append(CONF_MANUAL_INPUT) diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 56488f0712b..798243b5d4b 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -63,17 +63,18 @@ def setup_platform( # Gather all the available devices... perms = session.user.get_residential_permissions() - all_switches = [] + all_switches: list = [] for permission in perms: if permission.residentialAccountId is not None: acct = ResidentialAccount(session, permission.residentialAccountId) - for residence in acct.get_residences(): - for switch in residence.get_iot_switches(): - all_switches.append(switch) + all_switches.extend( + switch + for residence in acct.get_residences() + for switch in residence.get_iot_switches() + ) elif permission.residenceId is not None: residence = Residence(session, permission.residenceId) - for switch in residence.get_iot_switches(): - all_switches.append(switch) + all_switches.extend(residence.get_iot_switches()) add_entities(DecoraWifiLight(sw) for sw in all_switches) except ValueError: diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 5239d6fbd6b..5693a00e857 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -64,9 +64,8 @@ async def async_setup_platform( session = async_get_clientsession(hass) - sensors = [] - for nextpassage in config[CONF_NEXT_DEPARTURE]: - sensors.append( + async_add_entities( + ( DeLijnPublicTransportSensor( Passages( nextpassage[CONF_STOP_ID], @@ -76,9 +75,10 @@ async def async_setup_platform( True, ) ) - ) - - async_add_entities(sensors, True) + for nextpassage in config[CONF_NEXT_DEPARTURE] + ), + True, + ) class DeLijnPublicTransportSensor(SensorEntity): diff --git a/homeassistant/components/devolo_home_control/binary_sensor.py b/homeassistant/components/devolo_home_control/binary_sensor.py index 564d3a583d2..43793a15368 100644 --- a/homeassistant/components/devolo_home_control/binary_sensor.py +++ b/homeassistant/components/devolo_home_control/binary_sensor.py @@ -34,29 +34,27 @@ async def async_setup_entry( entities: list[BinarySensorEntity] = [] for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.binary_sensor_devices: - for binary_sensor in device.binary_sensor_property: - entities.append( - DevoloBinaryDeviceEntity( - homecontrol=gateway, - device_instance=device, - element_uid=binary_sensor, - ) - ) - for device in gateway.devices.values(): - if hasattr(device, "remote_control_property"): - for remote in device.remote_control_property: - for index in range( - 1, device.remote_control_property[remote].key_count + 1 - ): - entities.append( - DevoloRemoteControl( - homecontrol=gateway, - device_instance=device, - element_uid=remote, - key=index, - ) - ) + entities.extend( + DevoloBinaryDeviceEntity( + homecontrol=gateway, + device_instance=device, + element_uid=binary_sensor, + ) + for device in gateway.binary_sensor_devices + for binary_sensor in device.binary_sensor_property + ) + entities.extend( + DevoloRemoteControl( + homecontrol=gateway, + device_instance=device, + element_uid=remote, + key=index, + ) + for device in gateway.devices.values() + if hasattr(device, "remote_control_property") + for remote in device.remote_control_property + for index in range(1, device.remote_control_property[remote].key_count + 1) + ) async_add_entities(entities) diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index 702adc06e1f..f94c7dae15a 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -26,26 +26,24 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Get all cover devices and setup them via config entry.""" - entities = [] - for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.multi_level_switch_devices: - for multi_level_switch in device.multi_level_switch_property: - if device.device_model_uid in ( - "devolo.model.Thermostat:Valve", - "devolo.model.Room:Thermostat", - "devolo.model.Eurotronic:Spirit:Device", - "unk.model.Danfoss:Thermostat", - ): - entities.append( - DevoloClimateDeviceEntity( - homecontrol=gateway, - device_instance=device, - element_uid=multi_level_switch, - ) - ) - - async_add_entities(entities) + async_add_entities( + DevoloClimateDeviceEntity( + homecontrol=gateway, + device_instance=device, + element_uid=multi_level_switch, + ) + for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"] + for device in gateway.multi_level_switch_devices + for multi_level_switch in device.multi_level_switch_property + if device.device_model_uid + in ( + "devolo.model.Thermostat:Valve", + "devolo.model.Room:Thermostat", + "devolo.model.Eurotronic:Spirit:Device", + "unk.model.Danfoss:Thermostat", + ) + ) class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntity): diff --git a/homeassistant/components/devolo_home_control/cover.py b/homeassistant/components/devolo_home_control/cover.py index 3af0de3a509..03aec622645 100644 --- a/homeassistant/components/devolo_home_control/cover.py +++ b/homeassistant/components/devolo_home_control/cover.py @@ -21,21 +21,18 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Get all cover devices and setup them via config entry.""" - entities = [] - for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.multi_level_switch_devices: - for multi_level_switch in device.multi_level_switch_property: - if multi_level_switch.startswith("devolo.Blinds"): - entities.append( - DevoloCoverDeviceEntity( - homecontrol=gateway, - device_instance=device, - element_uid=multi_level_switch, - ) - ) - - async_add_entities(entities) + async_add_entities( + DevoloCoverDeviceEntity( + homecontrol=gateway, + device_instance=device, + element_uid=multi_level_switch, + ) + for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"] + for device in gateway.multi_level_switch_devices + for multi_level_switch in device.multi_level_switch_property + if multi_level_switch.startswith("devolo.Blinds") + ) class DevoloCoverDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, CoverEntity): diff --git a/homeassistant/components/devolo_home_control/diagnostics.py b/homeassistant/components/devolo_home_control/diagnostics.py index 2a7e82b0901..753d04db0a3 100644 --- a/homeassistant/components/devolo_home_control/diagnostics.py +++ b/homeassistant/components/devolo_home_control/diagnostics.py @@ -22,25 +22,24 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" gateways: list[HomeControl] = hass.data[DOMAIN][entry.entry_id]["gateways"] - device_info = [] - for gateway in gateways: - device_info.append( - { - "gateway": { - "local_connection": gateway.gateway.local_connection, - "firmware_version": gateway.gateway.firmware_version, - }, - "devices": [ - { - "device_id": device_id, - "device_model_uid": properties.device_model_uid, - "device_type": properties.device_type, - "name": properties.name, - } - for device_id, properties in gateway.devices.items() - ], - } - ) + device_info = [ + { + "gateway": { + "local_connection": gateway.gateway.local_connection, + "firmware_version": gateway.gateway.firmware_version, + }, + "devices": [ + { + "device_id": device_id, + "device_model_uid": properties.device_model_uid, + "device_type": properties.device_type, + "name": properties.name, + } + for device_id, properties in gateway.devices.items() + ], + } + for gateway in gateways + ] diag_data = { "entry": async_redact_data(entry.as_dict(), TO_REDACT), diff --git a/homeassistant/components/devolo_home_control/light.py b/homeassistant/components/devolo_home_control/light.py index 79e3531741f..36c72ca7f57 100644 --- a/homeassistant/components/devolo_home_control/light.py +++ b/homeassistant/components/devolo_home_control/light.py @@ -20,21 +20,18 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Get all light devices and setup them via config entry.""" - entities = [] - for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.multi_level_switch_devices: - for multi_level_switch in device.multi_level_switch_property.values(): - if multi_level_switch.switch_type == "dimmer": - entities.append( - DevoloLightDeviceEntity( - homecontrol=gateway, - device_instance=device, - element_uid=multi_level_switch.element_uid, - ) - ) - - async_add_entities(entities) + async_add_entities( + DevoloLightDeviceEntity( + homecontrol=gateway, + device_instance=device, + element_uid=multi_level_switch.element_uid, + ) + for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"] + for device in gateway.multi_level_switch_devices + for multi_level_switch in device.multi_level_switch_property.values() + if multi_level_switch.switch_type == "dimmer" + ) class DevoloLightDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, LightEntity): diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index 579dc8f38d6..db630cf3532 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -45,35 +45,36 @@ async def async_setup_entry( entities: list[SensorEntity] = [] for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.multi_level_sensor_devices: - for multi_level_sensor in device.multi_level_sensor_property: - entities.append( - DevoloGenericMultiLevelDeviceEntity( - homecontrol=gateway, - device_instance=device, - element_uid=multi_level_sensor, - ) - ) - for device in gateway.devices.values(): - if hasattr(device, "consumption_property"): - for consumption in device.consumption_property: - for consumption_type in ("current", "total"): - entities.append( - DevoloConsumptionEntity( - homecontrol=gateway, - device_instance=device, - element_uid=consumption, - consumption=consumption_type, - ) - ) - if hasattr(device, "battery_level"): - entities.append( - DevoloBatteryEntity( - homecontrol=gateway, - device_instance=device, - element_uid=f"devolo.BatterySensor:{device.uid}", - ) - ) + entities.extend( + DevoloGenericMultiLevelDeviceEntity( + homecontrol=gateway, + device_instance=device, + element_uid=multi_level_sensor, + ) + for device in gateway.multi_level_sensor_devices + for multi_level_sensor in device.multi_level_sensor_property + ) + entities.extend( + DevoloConsumptionEntity( + homecontrol=gateway, + device_instance=device, + element_uid=consumption, + consumption=consumption_type, + ) + for device in gateway.devices.values() + if hasattr(device, "consumption_property") + for consumption in device.consumption_property + for consumption_type in ("current", "total") + ) + entities.extend( + DevoloBatteryEntity( + homecontrol=gateway, + device_instance=device, + element_uid=f"devolo.BatterySensor:{device.uid}", + ) + for device in gateway.devices.values() + if hasattr(device, "battery_level") + ) async_add_entities(entities) diff --git a/homeassistant/components/devolo_home_control/siren.py b/homeassistant/components/devolo_home_control/siren.py index f0a2c228068..fd015860bbb 100644 --- a/homeassistant/components/devolo_home_control/siren.py +++ b/homeassistant/components/devolo_home_control/siren.py @@ -18,21 +18,18 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Get all binary sensor and multi level sensor devices and setup them via config entry.""" - entities = [] - for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.multi_level_switch_devices: - for multi_level_switch in device.multi_level_switch_property: - if multi_level_switch.startswith("devolo.SirenMultiLevelSwitch"): - entities.append( - DevoloSirenDeviceEntity( - homecontrol=gateway, - device_instance=device, - element_uid=multi_level_switch, - ) - ) - - async_add_entities(entities) + async_add_entities( + DevoloSirenDeviceEntity( + homecontrol=gateway, + device_instance=device, + element_uid=multi_level_switch, + ) + for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"] + for device in gateway.multi_level_switch_devices + for multi_level_switch in device.multi_level_switch_property + if multi_level_switch.startswith("devolo.SirenMultiLevelSwitch") + ) class DevoloSirenDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, SirenEntity): diff --git a/homeassistant/components/devolo_home_control/switch.py b/homeassistant/components/devolo_home_control/switch.py index 1a6f61f64ab..f599d39d0b6 100644 --- a/homeassistant/components/devolo_home_control/switch.py +++ b/homeassistant/components/devolo_home_control/switch.py @@ -20,23 +20,20 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Get all devices and setup the switch devices via config entry.""" - entities = [] - for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]: - for device in gateway.binary_switch_devices: - for binary_switch in device.binary_switch_property: - # Exclude the binary switch which also has multi_level_switches here, - # because those are implemented as light entities now. - if not hasattr(device, "multi_level_switch_property"): - entities.append( - DevoloSwitch( - homecontrol=gateway, - device_instance=device, - element_uid=binary_switch, - ) - ) - - async_add_entities(entities) + async_add_entities( + DevoloSwitch( + homecontrol=gateway, + device_instance=device, + element_uid=binary_switch, + ) + for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"] + for device in gateway.binary_switch_devices + for binary_switch in device.binary_switch_property + # Exclude the binary switch which also has multi_level_switches here, + # because those are implemented as light entities now. + if not hasattr(device, "multi_level_switch_property") + ) class DevoloSwitch(DevoloDeviceEntity, SwitchEntity): diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index 24d21c195a6..736e30b9f96 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -60,18 +60,18 @@ async def async_setup_entry( ) -> None: """Set up the DirecTV config entry.""" dtv = hass.data[DOMAIN][entry.entry_id] - entities = [] - for location in dtv.device.locations: - entities.append( + async_add_entities( + ( DIRECTVMediaPlayer( dtv=dtv, name=str.title(location.name), address=location.address, ) - ) - - async_add_entities(entities, True) + for location in dtv.device.locations + ), + True, + ) class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity): diff --git a/homeassistant/components/directv/remote.py b/homeassistant/components/directv/remote.py index 724984545d2..5a77d90bd3c 100644 --- a/homeassistant/components/directv/remote.py +++ b/homeassistant/components/directv/remote.py @@ -29,18 +29,18 @@ async def async_setup_entry( ) -> None: """Load DirecTV remote based on a config entry.""" dtv = hass.data[DOMAIN][entry.entry_id] - entities = [] - for location in dtv.device.locations: - entities.append( + async_add_entities( + ( DIRECTVRemote( dtv=dtv, name=str.title(location.name), address=location.address, ) - ) - - async_add_entities(entities, True) + for location in dtv.device.locations + ), + True, + ) class DIRECTVRemote(DIRECTVEntity, RemoteEntity): diff --git a/homeassistant/components/dlib_face_detect/image_processing.py b/homeassistant/components/dlib_face_detect/image_processing.py index d2559c0753e..9f6b30dee61 100644 --- a/homeassistant/components/dlib_face_detect/image_processing.py +++ b/homeassistant/components/dlib_face_detect/image_processing.py @@ -24,13 +24,10 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Dlib Face detection platform.""" - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - DlibFaceDetectEntity(camera[CONF_ENTITY_ID], camera.get(CONF_NAME)) - ) - - add_entities(entities) + add_entities( + DlibFaceDetectEntity(camera[CONF_ENTITY_ID], camera.get(CONF_NAME)) + for camera in config[CONF_SOURCE] + ) class DlibFaceDetectEntity(ImageProcessingFaceEntity): diff --git a/homeassistant/components/dlib_face_identify/image_processing.py b/homeassistant/components/dlib_face_identify/image_processing.py index 73cf53db5a2..ac9e69ec9e1 100644 --- a/homeassistant/components/dlib_face_identify/image_processing.py +++ b/homeassistant/components/dlib_face_identify/image_processing.py @@ -38,18 +38,15 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Dlib Face detection platform.""" - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - DlibFaceIdentifyEntity( - camera[CONF_ENTITY_ID], - config[CONF_FACES], - camera.get(CONF_NAME), - config[CONF_CONFIDENCE], - ) + add_entities( + DlibFaceIdentifyEntity( + camera[CONF_ENTITY_ID], + config[CONF_FACES], + camera.get(CONF_NAME), + config[CONF_CONFIDENCE], ) - - add_entities(entities) + for camera in config[CONF_SOURCE] + ) class DlibFaceIdentifyEntity(ImageProcessingFaceEntity): diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index aa9e9535ea9..11985ef4889 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -112,19 +112,17 @@ def setup_platform( ) return - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - Doods( - hass, - camera[CONF_ENTITY_ID], - camera.get(CONF_NAME), - doods, - detector, - config, - ) + add_entities( + Doods( + hass, + camera[CONF_ENTITY_ID], + camera.get(CONF_NAME), + doods, + detector, + config, ) - add_entities(entities) + for camera in config[CONF_SOURCE] + ) class Doods(ImageProcessingEntity): diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 766603f740b..7b2e916529a 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -531,9 +531,7 @@ async def async_setup_entry( add_entities_handler = None if dsmr_version == "5B": - mbus_entities = create_mbus_entities(hass, telegram, entry) - for mbus_entity in mbus_entities: - entities.append(mbus_entity) + entities.extend(create_mbus_entities(hass, telegram, entry)) entities.extend( [ diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index e1f0b277945..46fcfb267d0 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -65,10 +65,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def dynalite_service(service_call: ServiceCall) -> None: data = service_call.data host = data.get(ATTR_HOST, "") - bridges = [] - for cur_bridge in hass.data[DOMAIN].values(): - if not host or cur_bridge.host == host: - bridges.append(cur_bridge) + bridges = [ + bridge + for bridge in hass.data[DOMAIN].values() + if not host or bridge.host == host + ] LOGGER.debug("Selected bridged for service call: %s", bridges) if service_call.service == SERVICE_REQUEST_AREA_PRESET: bridge_attr = "request_area_preset" diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py index 869695d43f7..bfc62609101 100644 --- a/homeassistant/components/dynalite/dynalitebase.py +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -31,9 +31,7 @@ def async_setup_entry_base( @callback def async_add_entities_platform(devices): # assumes it is called with a single platform - added_entities = [] - for device in devices: - added_entities.append(entity_from_device(device, bridge)) + added_entities = [entity_from_device(device, bridge) for device in devices] async_add_entities(added_entities) bridge.register_add_devices(platform, async_add_entities_platform) diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index fcf98876238..3950dea59ed 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -157,11 +157,12 @@ async def test_sync_turn_off(hass: HomeAssistant) -> None: def _create_tuples(enum: Enum, constant_prefix: str) -> list[tuple[Enum, str]]: - result = [] - for enum in enum: - if enum not in [ClimateEntityFeature.TURN_ON, ClimateEntityFeature.TURN_OFF]: - result.append((enum, constant_prefix)) - return result + return [ + (enum_field, constant_prefix) + for enum_field in enum + if enum_field + not in [ClimateEntityFeature.TURN_ON, ClimateEntityFeature.TURN_OFF] + ] @pytest.mark.parametrize( diff --git a/tests/components/cover/test_init.py b/tests/components/cover/test_init.py index e44b0788c9a..5d6b40a171f 100644 --- a/tests/components/cover/test_init.py +++ b/tests/components/cover/test_init.py @@ -140,10 +140,7 @@ def is_closing(hass, ent): def _create_tuples(enum: Enum, constant_prefix: str) -> list[tuple[Enum, str]]: - result = [] - for enum in enum: - result.append((enum, constant_prefix)) - return result + return [(enum_field, constant_prefix) for enum_field in enum] def test_all() -> None: From 1853c2d73a44e72b7db38632ad3fff930326116d Mon Sep 17 00:00:00 2001 From: Alistair Francis Date: Tue, 12 Mar 2024 03:58:43 +1000 Subject: [PATCH 0780/1691] components/gardena_bluetooth: Improve avaliability reliability (#113056) * components/gardena_bluetooth: Improve avaliability reliability The available() function incorrectly returns false even though the device is accessible. The last_update_success property should correctly indicate if the device isn't contactable, so we don't need to call async_address_present(). This is confirmed by seeing that no other users are calling async_address_present() in the available() function. This commit removes the call to async_address_present() to help fix the sensor being unavailable when using a ESPHome BLE proxy. Signed-off-by: Alistair Francis --------- Signed-off-by: Alistair Francis Co-authored-by: Joakim Plate --- .../components/gardena_bluetooth/coordinator.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/homeassistant/components/gardena_bluetooth/coordinator.py b/homeassistant/components/gardena_bluetooth/coordinator.py index 7d939d27eb5..296eff2686e 100644 --- a/homeassistant/components/gardena_bluetooth/coordinator.py +++ b/homeassistant/components/gardena_bluetooth/coordinator.py @@ -13,7 +13,6 @@ from gardena_bluetooth.exceptions import ( ) from gardena_bluetooth.parse import Characteristic, CharacteristicType -from homeassistant.components import bluetooth from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceInfo @@ -118,13 +117,7 @@ class GardenaBluetoothEntity(CoordinatorEntity[Coordinator]): @property def available(self) -> bool: """Return if entity is available.""" - return ( - self.coordinator.last_update_success - and bluetooth.async_address_present( - self.hass, self.coordinator.address, True - ) - and self._attr_available - ) + return self.coordinator.last_update_success and self._attr_available class GardenaBluetoothDescriptorEntity(GardenaBluetoothEntity): From 23ffcaf187c625cd2dd4c2ffc43065692329e8e5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 18:59:57 +0100 Subject: [PATCH 0781/1691] Improve lists in integrations [B] (#113069) --- homeassistant/components/baf/sensor.py | 9 +- .../components/bayesian/binary_sensor.py | 6 +- .../components/blue_current/sensor.py | 12 +-- .../components/bluesound/media_player.py | 19 ++-- homeassistant/components/bluetooth/match.py | 52 +++++----- .../components/bmw_connected_drive/lock.py | 15 +-- .../components/bosch_shc/binary_sensor.py | 57 ++++++----- homeassistant/components/bosch_shc/cover.py | 17 ++-- homeassistant/components/bosch_shc/switch.py | 97 ++++++++++--------- homeassistant/components/brother/sensor.py | 11 +-- 10 files changed, 144 insertions(+), 151 deletions(-) diff --git a/homeassistant/components/baf/sensor.py b/homeassistant/components/baf/sensor.py index 7a7845c2f67..fc052b1e48b 100644 --- a/homeassistant/components/baf/sensor.py +++ b/homeassistant/components/baf/sensor.py @@ -100,10 +100,11 @@ async def async_setup_entry( """Set up BAF fan sensors.""" data: BAFData = hass.data[DOMAIN][entry.entry_id] device = data.device - sensors_descriptions: list[BAFSensorDescription] = [] - for description in DEFINED_ONLY_SENSORS: - if getattr(device, description.key): - sensors_descriptions.append(description) + sensors_descriptions: list[BAFSensorDescription] = [ + description + for description in DEFINED_ONLY_SENSORS + if getattr(device, description.key) + ] if device.has_auto_comfort: sensors_descriptions.extend(AUTO_COMFORT_SENSORS) if device.has_fan: diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index cccb08c5540..b6298040b6b 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -313,9 +313,9 @@ class BayesianBinarySensor(BinarySensorEntity): self.hass, observations, text=f"{self._attr_name}/{entity}" ) - all_template_observations: list[Observation] = [] - for observations in self.observations_by_template.values(): - all_template_observations.append(observations[0]) + all_template_observations: list[Observation] = [ + observations[0] for observations in self.observations_by_template.values() + ] if len(all_template_observations) == 2: raise_mirrored_entries( self.hass, diff --git a/homeassistant/components/blue_current/sensor.py b/homeassistant/components/blue_current/sensor.py index 4d6c82ee530..b544b69d2ff 100644 --- a/homeassistant/components/blue_current/sensor.py +++ b/homeassistant/components/blue_current/sensor.py @@ -217,13 +217,13 @@ async def async_setup_entry( ) -> None: """Set up Blue Current sensors.""" connector: Connector = hass.data[DOMAIN][entry.entry_id] - sensor_list: list[SensorEntity] = [] - for evse_id in connector.charge_points: - for sensor in SENSORS: - sensor_list.append(ChargePointSensor(connector, sensor, evse_id)) + sensor_list: list[SensorEntity] = [ + ChargePointSensor(connector, sensor, evse_id) + for evse_id in connector.charge_points + for sensor in SENSORS + ] - for grid_sensor in GRID_SENSORS: - sensor_list.append(GridSensor(connector, grid_sensor)) + sensor_list.extend(GridSensor(connector, sensor) for sensor in GRID_SENSORS) async_add_entities(sensor_list) diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index a054dadcbc9..357f971f574 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -686,20 +686,15 @@ class BluesoundPlayer(MediaPlayerEntity): if self._status is None or (self.is_grouped and not self.is_master): return None - sources = [] + sources = [source["title"] for source in self._preset_items] - for source in self._preset_items: - sources.append(source["title"]) + sources.extend( + source["title"] + for source in self._services_items + if source["type"] in ("LocalMusic", "RadioService") + ) - for source in [ - x - for x in self._services_items - if x["type"] in ("LocalMusic", "RadioService") - ]: - sources.append(source["title"]) - - for source in self._capture_items: - sources.append(source["title"]) + sources.extend(source["title"] for source in self._capture_items) return sources diff --git a/homeassistant/components/bluetooth/match.py b/homeassistant/components/bluetooth/match.py index 6eba9b65cbb..a5e1159e04e 100644 --- a/homeassistant/components/bluetooth/match.py +++ b/homeassistant/components/bluetooth/match.py @@ -249,39 +249,47 @@ class BluetoothMatcherIndexBase(Generic[_T]): def match(self, service_info: BluetoothServiceInfoBleak) -> list[_T]: """Check for a match.""" - matches = [] + matches: list[_T] = [] if (name := service_info.name) and ( local_name_matchers := self.local_name.get( name[:LOCAL_NAME_MIN_MATCH_LENGTH] ) ): - for matcher in local_name_matchers: - if ble_device_matches(matcher, service_info): - matches.append(matcher) + matches.extend( + matcher + for matcher in local_name_matchers + if ble_device_matches(matcher, service_info) + ) if self.service_data_uuid_set and service_info.service_data: - for service_data_uuid in self.service_data_uuid_set.intersection( - service_info.service_data - ): - for matcher in self.service_data_uuid[service_data_uuid]: - if ble_device_matches(matcher, service_info): - matches.append(matcher) + matches.extend( + matcher + for service_data_uuid in self.service_data_uuid_set.intersection( + service_info.service_data + ) + for matcher in self.service_data_uuid[service_data_uuid] + if ble_device_matches(matcher, service_info) + ) if self.manufacturer_id_set and service_info.manufacturer_data: - for manufacturer_id in self.manufacturer_id_set.intersection( - service_info.manufacturer_data - ): - for matcher in self.manufacturer_id[manufacturer_id]: - if ble_device_matches(matcher, service_info): - matches.append(matcher) + matches.extend( + matcher + for manufacturer_id in self.manufacturer_id_set.intersection( + service_info.manufacturer_data + ) + for matcher in self.manufacturer_id[manufacturer_id] + if ble_device_matches(matcher, service_info) + ) if self.service_uuid_set and service_info.service_uuids: - for service_uuid in self.service_uuid_set.intersection( - service_info.service_uuids - ): - for matcher in self.service_uuid[service_uuid]: - if ble_device_matches(matcher, service_info): - matches.append(matcher) + matches.extend( + matcher + for service_uuid in self.service_uuid_set.intersection( + service_info.service_uuids + ) + for matcher in self.service_uuid[service_uuid] + if ble_device_matches(matcher, service_info) + ) return matches diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index d7898f98bc8..3de41bfb911 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -31,17 +31,10 @@ async def async_setup_entry( """Set up the MyBMW lock from config entry.""" coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - entities: list[BMWLock] = [] - - for vehicle in coordinator.account.vehicles: - if not coordinator.read_only: - entities.append( - BMWLock( - coordinator, - vehicle, - ) - ) - async_add_entities(entities) + if not coordinator.read_only: + async_add_entities( + BMWLock(coordinator, vehicle) for vehicle in coordinator.account.vehicles + ) class BMWLock(BMWBaseEntity, LockEntity): diff --git a/homeassistant/components/bosch_shc/binary_sensor.py b/homeassistant/components/bosch_shc/binary_sensor.py index 9a016e47f9d..342a3e3e417 100644 --- a/homeassistant/components/bosch_shc/binary_sensor.py +++ b/homeassistant/components/bosch_shc/binary_sensor.py @@ -23,39 +23,38 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the SHC binary sensor platform.""" - entities: list[BinarySensorEntity] = [] session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION] - for binary_sensor in ( - session.device_helper.shutter_contacts + session.device_helper.shutter_contacts2 - ): - entities.append( - ShutterContactSensor( - device=binary_sensor, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - ) + entities: list[BinarySensorEntity] = [ + ShutterContactSensor( + device=binary_sensor, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, ) + for binary_sensor in ( + session.device_helper.shutter_contacts + + session.device_helper.shutter_contacts2 + ) + ] - for binary_sensor in ( - session.device_helper.motion_detectors - + session.device_helper.shutter_contacts - + session.device_helper.shutter_contacts2 - + session.device_helper.smoke_detectors - + session.device_helper.thermostats - + session.device_helper.twinguards - + session.device_helper.universal_switches - + session.device_helper.wallthermostats - + session.device_helper.water_leakage_detectors - ): - if binary_sensor.supports_batterylevel: - entities.append( - BatterySensor( - device=binary_sensor, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - ) - ) + entities.extend( + BatterySensor( + device=binary_sensor, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, + ) + for binary_sensor in ( + session.device_helper.motion_detectors + + session.device_helper.shutter_contacts + + session.device_helper.shutter_contacts2 + + session.device_helper.smoke_detectors + + session.device_helper.thermostats + + session.device_helper.twinguards + + session.device_helper.universal_switches + + session.device_helper.wallthermostats + + session.device_helper.water_leakage_detectors + ) + ) async_add_entities(entities) diff --git a/homeassistant/components/bosch_shc/cover.py b/homeassistant/components/bosch_shc/cover.py index c135cdfa186..5377f0c6a8f 100644 --- a/homeassistant/components/bosch_shc/cover.py +++ b/homeassistant/components/bosch_shc/cover.py @@ -25,19 +25,16 @@ async def async_setup_entry( ) -> None: """Set up the SHC cover platform.""" - entities = [] session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION] - for cover in session.device_helper.shutter_controls: - entities.append( - ShutterControlCover( - device=cover, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - ) + async_add_entities( + ShutterControlCover( + device=cover, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, ) - - async_add_entities(entities) + for cover in session.device_helper.shutter_controls + ) class ShutterControlCover(SHCEntity, CoverEntity): diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index 6f815d8f66c..e6ccd2aa9aa 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -84,65 +84,66 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the SHC switch platform.""" - entities: list[SwitchEntity] = [] session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION] - for switch in session.device_helper.smart_plugs: - entities.append( - SHCSwitch( - device=switch, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - description=SWITCH_TYPES["smartplug"], - ) - ) - entities.append( - SHCRoutingSwitch( - device=switch, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - ) + entities: list[SwitchEntity] = [ + SHCSwitch( + device=switch, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["smartplug"], ) + for switch in session.device_helper.smart_plugs + ] - for switch in session.device_helper.light_switches_bsm: - entities.append( - SHCSwitch( - device=switch, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - description=SWITCH_TYPES["lightswitch"], - ) + entities.extend( + SHCRoutingSwitch( + device=switch, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, ) + for switch in session.device_helper.smart_plugs + ) - for switch in session.device_helper.smart_plugs_compact: - entities.append( - SHCSwitch( - device=switch, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - description=SWITCH_TYPES["smartplugcompact"], - ) + entities.extend( + SHCSwitch( + device=switch, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["lightswitch"], ) + for switch in session.device_helper.light_switches_bsm + ) - for switch in session.device_helper.camera_eyes: - entities.append( - SHCSwitch( - device=switch, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - description=SWITCH_TYPES["cameraeyes"], - ) + entities.extend( + SHCSwitch( + device=switch, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["smartplugcompact"], ) + for switch in session.device_helper.smart_plugs_compact + ) - for switch in session.device_helper.camera_360: - entities.append( - SHCSwitch( - device=switch, - parent_id=session.information.unique_id, - entry_id=config_entry.entry_id, - description=SWITCH_TYPES["camera360"], - ) + entities.extend( + SHCSwitch( + device=switch, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["cameraeyes"], ) + for switch in session.device_helper.camera_eyes + ) + + entities.extend( + SHCSwitch( + device=switch, + parent_id=session.information.unique_id, + entry_id=config_entry.entry_id, + description=SWITCH_TYPES["camera360"], + ) + for switch in session.device_helper.camera_360 + ) async_add_entities(entities) diff --git a/homeassistant/components/brother/sensor.py b/homeassistant/components/brother/sensor.py index 12b5bd0fb59..6f56eb680be 100644 --- a/homeassistant/components/brother/sensor.py +++ b/homeassistant/components/brother/sensor.py @@ -339,12 +339,11 @@ async def async_setup_entry( ) entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id) - sensors = [] - - for description in SENSOR_TYPES: - if description.value(coordinator.data) is not None: - sensors.append(BrotherPrinterSensor(coordinator, description)) - async_add_entities(sensors, False) + async_add_entities( + BrotherPrinterSensor(coordinator, description) + for description in SENSOR_TYPES + if description.value(coordinator.data) is not None + ) class BrotherPrinterSensor( From 0b6307fa53df389e1d28c135a7e85105fae2be37 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 11 Mar 2024 19:00:56 +0100 Subject: [PATCH 0782/1691] Add icon translations to Version (#112343) --- homeassistant/components/version/icons.json | 9 +++++++++ homeassistant/components/version/sensor.py | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/version/icons.json diff --git a/homeassistant/components/version/icons.json b/homeassistant/components/version/icons.json new file mode 100644 index 00000000000..e957bc07e3d --- /dev/null +++ b/homeassistant/components/version/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "sensor": { + "version": { + "default": "mdi:package-up" + } + } + } +} diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index e1e930b50a6..6b0565b8cb3 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -32,6 +32,7 @@ async def async_setup_entry( entity_description=SensorEntityDescription( key=str(entry.data[CONF_SOURCE]), name=entity_name, + translation_key="version", ), ) ] @@ -42,8 +43,6 @@ async def async_setup_entry( class VersionSensorEntity(VersionEntity, SensorEntity): """Version sensor entity class.""" - _attr_icon = "mdi:package-up" - @property def native_value(self) -> StateType: """Return the native value of this sensor.""" From ec89886fac7ea2223cbc91b298c8fbdc11372ba8 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 11 Mar 2024 19:04:44 +0100 Subject: [PATCH 0783/1691] Fix colormode attribute on grouped Hue light (#113071) --- homeassistant/components/hue/v2/group.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 32ac29b71f2..db30800a333 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -270,10 +270,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity): self._dynamic_mode_active = lights_in_dynamic_mode > 0 self._attr_supported_color_modes = supported_color_modes # pick a winner for the current colormode - if ( - lights_with_color_temp_support > 0 - and lights_in_colortemp_mode == lights_with_color_temp_support - ): + if lights_with_color_temp_support > 0 and lights_in_colortemp_mode > 0: self._attr_color_mode = ColorMode.COLOR_TEMP elif lights_with_color_support > 0: self._attr_color_mode = ColorMode.XY From 145657dc21e810e25287c22a746e01a15d8ae932 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 11 Mar 2024 14:05:32 -0400 Subject: [PATCH 0784/1691] Only load services.yaml for integrations that have it (#112732) Co-authored-by: J. Nick Koston --- homeassistant/helpers/service.py | 12 ++++++----- homeassistant/loader.py | 5 +++++ tests/helpers/test_service.py | 20 +++++++++++++++++-- tests/test_loader.py | 8 ++++++++ .../test_with_services/__init__.py | 1 + .../test_with_services/manifest.json | 4 ++++ .../test_with_services/services.yaml | 0 7 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 tests/testing_config/custom_components/test_with_services/__init__.py create mode 100644 tests/testing_config/custom_components/test_with_services/manifest.json create mode 100644 tests/testing_config/custom_components/test_with_services/services.yaml diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index d954d7b9682..951f1c24402 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -633,16 +633,18 @@ async def async_get_all_descriptions( ints_or_excs = await async_get_integrations(hass, domains_with_missing_services) integrations: list[Integration] = [] for domain, int_or_exc in ints_or_excs.items(): - if type(int_or_exc) is Integration: # noqa: E721 + if type(int_or_exc) is Integration and int_or_exc.has_services: # noqa: E721 integrations.append(int_or_exc) continue if TYPE_CHECKING: assert isinstance(int_or_exc, Exception) _LOGGER.error("Failed to load integration: %s", domain, exc_info=int_or_exc) - contents = await hass.async_add_executor_job( - _load_services_files, hass, integrations - ) - loaded = dict(zip(domains_with_missing_services, contents)) + + if integrations: + contents = await hass.async_add_executor_job( + _load_services_files, hass, integrations + ) + loaded = dict(zip(domains_with_missing_services, contents)) # Load translations for all service domains translations = await translation.async_get_translations( diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 71f746e322b..8e44ca38d77 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -838,6 +838,11 @@ class Integration: """Return if the integration has translations.""" return "translations" in self._top_level_files + @cached_property + def has_services(self) -> bool: + """Return if the integration has services.""" + return "services.yaml" in self._top_level_files + @property def mqtt(self) -> list[str] | None: """Return Integration MQTT entries.""" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index b0d94bad23b..113c452e719 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -35,6 +35,7 @@ from homeassistant.helpers import ( template, ) import homeassistant.helpers.config_validation as cv +from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component from tests.common import ( @@ -564,14 +565,29 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: """Test async_get_all_descriptions.""" group = hass.components.group group_config = {group.DOMAIN: {}} - await async_setup_component(hass, group.DOMAIN, group_config) - descriptions = await service.async_get_all_descriptions(hass) + assert await async_setup_component(hass, group.DOMAIN, group_config) + assert await async_setup_component(hass, "system_health", {}) + + with patch( + "homeassistant.helpers.service._load_services_files", + side_effect=service._load_services_files, + ) as proxy_load_services_files: + descriptions = await service.async_get_all_descriptions(hass) + + # Test we only load services.yaml for integrations with services.yaml + # And system_health has no services + assert proxy_load_services_files.mock_calls[0][1][1] == [ + await async_get_integration(hass, "group") + ] assert len(descriptions) == 1 assert "description" in descriptions["group"]["reload"] assert "fields" in descriptions["group"]["reload"] + # Does not have services + assert "system_health" not in descriptions + logger = hass.components.logger logger_config = {logger.DOMAIN: {}} diff --git a/tests/test_loader.py b/tests/test_loader.py index b51a9a846b8..a2868976876 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1685,3 +1685,11 @@ async def test_integration_warnings( """Test integration warnings.""" await loader.async_get_integration(hass, "test_package_loaded_loop") assert "configured to to import its code in the event loop" in caplog.text + + +async def test_has_services(hass: HomeAssistant, enable_custom_integrations) -> None: + """Test has_services.""" + integration = await loader.async_get_integration(hass, "test") + assert integration.has_services is False + integration = await loader.async_get_integration(hass, "test_with_services") + assert integration.has_services is True diff --git a/tests/testing_config/custom_components/test_with_services/__init__.py b/tests/testing_config/custom_components/test_with_services/__init__.py new file mode 100644 index 00000000000..e39053682e3 --- /dev/null +++ b/tests/testing_config/custom_components/test_with_services/__init__.py @@ -0,0 +1 @@ +"""Provide a mock integration.""" diff --git a/tests/testing_config/custom_components/test_with_services/manifest.json b/tests/testing_config/custom_components/test_with_services/manifest.json new file mode 100644 index 00000000000..f42d56e2e7d --- /dev/null +++ b/tests/testing_config/custom_components/test_with_services/manifest.json @@ -0,0 +1,4 @@ +{ + "domain": "test_with_services", + "version": "1.0" +} diff --git a/tests/testing_config/custom_components/test_with_services/services.yaml b/tests/testing_config/custom_components/test_with_services/services.yaml new file mode 100644 index 00000000000..e69de29bb2d From ff4e9eb31ecc1973c9130643be6befb46e56cd51 Mon Sep 17 00:00:00 2001 From: Jan Stienstra <65826735+j-stienstra@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:09:18 +0100 Subject: [PATCH 0785/1691] Fix optional Jellyfin RunTimeTicks (#108254) --- homeassistant/components/jellyfin/media_player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/jellyfin/media_player.py b/homeassistant/components/jellyfin/media_player.py index ec1f11de9d7..954ac7af69e 100644 --- a/homeassistant/components/jellyfin/media_player.py +++ b/homeassistant/components/jellyfin/media_player.py @@ -150,7 +150,9 @@ class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity): media_content_type = CONTENT_TYPE_MAP.get(self.now_playing["Type"], None) media_content_id = self.now_playing["Id"] media_title = self.now_playing["Name"] - media_duration = int(self.now_playing["RunTimeTicks"] / 10000000) + + if "RunTimeTicks" in self.now_playing: + media_duration = int(self.now_playing["RunTimeTicks"] / 10000000) if media_content_type == MediaType.EPISODE: media_content_type = MediaType.TVSHOW From d7cc30fd2bf60de008f365fe7df855221eff96f4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 08:46:11 -1000 Subject: [PATCH 0786/1691] Start tasks eagerly in for async_at_start(ed) (#112802) --- homeassistant/helpers/start.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/start.py b/homeassistant/helpers/start.py index 148b416e087..d09e542e2e0 100644 --- a/homeassistant/helpers/start.py +++ b/homeassistant/helpers/start.py @@ -30,7 +30,7 @@ def _async_at_core_state( """ at_start_job = HassJob(at_start_cb) if check_state(hass): - hass.async_run_hass_job(at_start_job, hass) + hass.async_run_hass_job(at_start_job, hass, eager_start=True) return lambda: None unsub: None | CALLBACK_TYPE = None @@ -38,7 +38,7 @@ def _async_at_core_state( @callback def _matched_event(event: Event) -> None: """Call the callback when Home Assistant started.""" - hass.async_run_hass_job(at_start_job, hass) + hass.async_run_hass_job(at_start_job, hass, eager_start=True) nonlocal unsub unsub = None From 89c24b2f75118e257cad5f67e1c1013bc65428c8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 08:58:45 -1000 Subject: [PATCH 0787/1691] Migrate dispatcher to create tasks eagerly (#112845) There were quite a few of these that never had to suspend. --- homeassistant/helpers/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index cb812ef071b..20781eb8084 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -225,4 +225,4 @@ def async_dispatcher_send( if job is None: job = _generate_job(signal, target) target_list[target] = job - hass.async_run_hass_job(job, *args) + hass.async_run_hass_job(job, *args, eager_start=True) From 5e94858821d0857f0bec1005485a7d2bd784f279 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 09:02:35 -1000 Subject: [PATCH 0788/1691] Migrate _async_when_setup to use eager_start (#112872) This one does not make much difference, but its a lot easier to do it now instead of in the future --- homeassistant/setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 186d5d6b75c..45e3b328fc3 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -601,11 +601,14 @@ def _async_when_setup( EVENT_COMPONENT_LOADED, _matched_event, event_filter=_async_is_component_filter, + run_immediately=True, ) ) if start_event: listeners.append( - hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _matched_event) + hass.bus.async_listen( + EVENT_HOMEASSISTANT_START, _matched_event, run_immediately=True + ) ) From eff0aac586df74775952611234efa78725fcbc6b Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:23:49 +0000 Subject: [PATCH 0789/1691] Ensure that the ring integration always raises HomeAssistantError for user actions (#109893) * Wrap library exceptions in HomeAssistantErrors * Remove commented * Update post review * Update post second review --- homeassistant/components/ring/camera.py | 23 +++++------- homeassistant/components/ring/entity.py | 38 +++++++++++++++++--- homeassistant/components/ring/light.py | 10 ++---- homeassistant/components/ring/siren.py | 3 +- homeassistant/components/ring/switch.py | 10 ++---- tests/components/ring/test_light.py | 46 ++++++++++++++++++++++++ tests/components/ring/test_siren.py | 48 +++++++++++++++++++++++++ tests/components/ring/test_switch.py | 46 ++++++++++++++++++++++++ 8 files changed, 189 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index 38b9cae055c..9221430413a 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -8,7 +8,6 @@ import logging from typing import Optional from haffmpeg.camera import CameraMjpeg -import requests from homeassistant.components import ffmpeg from homeassistant.components.camera import Camera @@ -20,7 +19,7 @@ from homeassistant.util import dt as dt_util from .const import DOMAIN, RING_DEVICES, RING_DEVICES_COORDINATOR from .coordinator import RingDataCoordinator -from .entity import RingEntity +from .entity import RingEntity, exception_wrap FORCE_REFRESH_INTERVAL = timedelta(minutes=3) @@ -145,17 +144,11 @@ class RingCam(RingEntity, Camera): if self._last_video_id != self._last_event["id"]: self._image = None - try: - video_url = await self.hass.async_add_executor_job( - self._device.recording_url, self._last_event["id"] - ) - except requests.Timeout: - _LOGGER.warning( - "Time out fetching recording url for camera %s", self.entity_id - ) - video_url = None + self._video_url = await self.hass.async_add_executor_job(self._get_video) - if video_url: - self._last_video_id = self._last_event["id"] - self._video_url = video_url - self._expires_at = FORCE_REFRESH_INTERVAL + utcnow + self._last_video_id = self._last_event["id"] + self._expires_at = FORCE_REFRESH_INTERVAL + utcnow + + @exception_wrap + def _get_video(self) -> str: + return self._device.recording_url(self._last_event["id"]) diff --git a/homeassistant/components/ring/entity.py b/homeassistant/components/ring/entity.py index 94dbb1ba987..e5b17ca135e 100644 --- a/homeassistant/components/ring/entity.py +++ b/homeassistant/components/ring/entity.py @@ -1,10 +1,11 @@ """Base class for Ring entity.""" +from collections.abc import Callable +from typing import Any, Concatenate, ParamSpec, TypeVar -from typing import TypeVar - -from ring_doorbell.generic import RingGeneric +import ring_doorbell from homeassistant.core import callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -19,6 +20,33 @@ _RingCoordinatorT = TypeVar( "_RingCoordinatorT", bound=(RingDataCoordinator | RingNotificationsCoordinator), ) +_T = TypeVar("_T", bound="RingEntity") +_P = ParamSpec("_P") + + +def exception_wrap( + func: Callable[Concatenate[_T, _P], Any], +) -> Callable[Concatenate[_T, _P], Any]: + """Define a wrapper to catch exceptions and raise HomeAssistant errors.""" + + def _wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: + try: + return func(self, *args, **kwargs) + except ring_doorbell.AuthenticationError as err: + self.hass.loop.call_soon_threadsafe( + self.coordinator.config_entry.async_start_reauth, self.hass + ) + raise HomeAssistantError(err) from err + except ring_doorbell.RingTimeout as err: + raise HomeAssistantError( + f"Timeout communicating with API {func}: {err}" + ) from err + except ring_doorbell.RingError as err: + raise HomeAssistantError( + f"Error communicating with API{func}: {err}" + ) from err + + return _wrap class RingEntity(CoordinatorEntity[_RingCoordinatorT]): @@ -30,7 +58,7 @@ class RingEntity(CoordinatorEntity[_RingCoordinatorT]): def __init__( self, - device: RingGeneric, + device: ring_doorbell.RingGeneric, coordinator: _RingCoordinatorT, ) -> None: """Initialize a sensor for Ring device.""" @@ -51,7 +79,7 @@ class RingEntity(CoordinatorEntity[_RingCoordinatorT]): return device_data return None - def _get_coordinator_device(self) -> RingGeneric | None: + def _get_coordinator_device(self) -> ring_doorbell.RingGeneric | None: if (device_data := self._get_coordinator_device_data()) and ( device := device_data.device ): diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index 1d3dde97ae0..36203aed1eb 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -4,7 +4,6 @@ from datetime import timedelta import logging from typing import Any -import requests from ring_doorbell import RingStickUpCam from ring_doorbell.generic import RingGeneric @@ -16,7 +15,7 @@ import homeassistant.util.dt as dt_util from .const import DOMAIN, RING_DEVICES, RING_DEVICES_COORDINATOR from .coordinator import RingDataCoordinator -from .entity import RingEntity +from .entity import RingEntity, exception_wrap _LOGGER = logging.getLogger(__name__) @@ -76,13 +75,10 @@ class RingLight(RingEntity, LightEntity): self._attr_is_on = device.lights == ON_STATE super()._handle_coordinator_update() + @exception_wrap def _set_light(self, new_state): """Update light state, and causes Home Assistant to correctly update.""" - try: - self._device.lights = new_state - except requests.Timeout: - _LOGGER.error("Time out setting %s light to %s", self.entity_id, new_state) - return + self._device.lights = new_state self._attr_is_on = new_state == ON_STATE self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY diff --git a/homeassistant/components/ring/siren.py b/homeassistant/components/ring/siren.py index 60290469fa7..b746889d439 100644 --- a/homeassistant/components/ring/siren.py +++ b/homeassistant/components/ring/siren.py @@ -13,7 +13,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN, RING_DEVICES, RING_DEVICES_COORDINATOR from .coordinator import RingDataCoordinator -from .entity import RingEntity +from .entity import RingEntity, exception_wrap _LOGGER = logging.getLogger(__name__) @@ -49,6 +49,7 @@ class RingChimeSiren(RingEntity, SirenEntity): # Entity class attributes self._attr_unique_id = f"{self._device.id}-siren" + @exception_wrap def turn_on(self, **kwargs: Any) -> None: """Play the test sound on a Ring Chime device.""" tone = kwargs.get(ATTR_TONE) or KIND_DING diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index d2b93cf7154..3c45a1749f8 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -4,7 +4,6 @@ from datetime import timedelta import logging from typing import Any -import requests from ring_doorbell import RingStickUpCam from ring_doorbell.generic import RingGeneric @@ -16,7 +15,7 @@ import homeassistant.util.dt as dt_util from .const import DOMAIN, RING_DEVICES, RING_DEVICES_COORDINATOR from .coordinator import RingDataCoordinator -from .entity import RingEntity +from .entity import RingEntity, exception_wrap _LOGGER = logging.getLogger(__name__) @@ -83,13 +82,10 @@ class SirenSwitch(BaseRingSwitch): self._attr_is_on = device.siren > 0 super()._handle_coordinator_update() + @exception_wrap def _set_switch(self, new_state): """Update switch state, and causes Home Assistant to correctly update.""" - try: - self._device.siren = new_state - except requests.Timeout: - _LOGGER.error("Time out setting %s siren to %s", self.entity_id, new_state) - return + self._device.siren = new_state self._attr_is_on = new_state > 0 self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index fd58e0ffbee..9680c21abfc 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -1,9 +1,14 @@ """The tests for the Ring light platform.""" +from unittest.mock import PropertyMock, patch +import pytest import requests_mock +import ring_doorbell +from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -90,3 +95,44 @@ async def test_updates_work( state = hass.states.get("light.front_light") assert state.state == "on" + + +@pytest.mark.parametrize( + ("exception_type", "reauth_expected"), + [ + (ring_doorbell.AuthenticationError, True), + (ring_doorbell.RingTimeout, False), + (ring_doorbell.RingError, False), + ], + ids=["Authentication", "Timeout", "Other"], +) +async def test_light_errors_when_turned_on( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + exception_type, + reauth_expected, +) -> None: + """Tests the light turns on correctly.""" + await setup_platform(hass, Platform.LIGHT) + config_entry = hass.config_entries.async_entries("ring")[0] + + assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH})) + + with patch.object( + ring_doorbell.RingStickUpCam, "lights", new_callable=PropertyMock + ) as mock_lights: + mock_lights.side_effect = exception_type + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "light", "turn_on", {"entity_id": "light.front_light"}, blocking=True + ) + await hass.async_block_till_done() + assert mock_lights.call_count == 1 + assert ( + any( + flow + for flow in config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}) + if flow["handler"] == "ring" + ) + == reauth_expected + ) diff --git a/tests/components/ring/test_siren.py b/tests/components/ring/test_siren.py index 9a54a739fbd..132c75996a9 100644 --- a/tests/components/ring/test_siren.py +++ b/tests/components/ring/test_siren.py @@ -1,9 +1,14 @@ """The tests for the Ring button platform.""" +from unittest.mock import patch +import pytest import requests_mock +import ring_doorbell +from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from .common import setup_platform @@ -145,3 +150,46 @@ async def test_motion_chime_can_be_played( state = hass.states.get("siren.downstairs_siren") assert state.state == "unknown" + + +@pytest.mark.parametrize( + ("exception_type", "reauth_expected"), + [ + (ring_doorbell.AuthenticationError, True), + (ring_doorbell.RingTimeout, False), + (ring_doorbell.RingError, False), + ], + ids=["Authentication", "Timeout", "Other"], +) +async def test_siren_errors_when_turned_on( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + exception_type, + reauth_expected, +) -> None: + """Tests the siren turns on correctly.""" + await setup_platform(hass, Platform.SIREN) + config_entry = hass.config_entries.async_entries("ring")[0] + + assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH})) + + with patch.object( + ring_doorbell.RingChime, "test_sound", side_effect=exception_type + ) as mock_siren: + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "siren", + "turn_on", + {"entity_id": "siren.downstairs_siren", "tone": "motion"}, + blocking=True, + ) + await hass.async_block_till_done() + assert mock_siren.call_count == 1 + assert ( + any( + flow + for flow in config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}) + if flow["handler"] == "ring" + ) + == reauth_expected + ) diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index 0012db61f97..e8e860cc162 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -1,9 +1,14 @@ """The tests for the Ring switch platform.""" +from unittest.mock import PropertyMock, patch +import pytest import requests_mock +import ring_doorbell +from homeassistant.config_entries import SOURCE_REAUTH from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -97,3 +102,44 @@ async def test_updates_work( state = hass.states.get("switch.front_siren") assert state.state == "on" + + +@pytest.mark.parametrize( + ("exception_type", "reauth_expected"), + [ + (ring_doorbell.AuthenticationError, True), + (ring_doorbell.RingTimeout, False), + (ring_doorbell.RingError, False), + ], + ids=["Authentication", "Timeout", "Other"], +) +async def test_switch_errors_when_turned_on( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + exception_type, + reauth_expected, +) -> None: + """Tests the switch turns on correctly.""" + await setup_platform(hass, Platform.SWITCH) + config_entry = hass.config_entries.async_entries("ring")[0] + + assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH})) + + with patch.object( + ring_doorbell.RingStickUpCam, "siren", new_callable=PropertyMock + ) as mock_switch: + mock_switch.side_effect = exception_type + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.front_siren"}, blocking=True + ) + await hass.async_block_till_done() + assert mock_switch.call_count == 1 + assert ( + any( + flow + for flow in config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}) + if flow["handler"] == "ring" + ) + == reauth_expected + ) From 324266a4e6df754d0ebb7ef1dc36964f0477b785 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 10:48:11 -1000 Subject: [PATCH 0790/1691] Fix race in script stop that could cause async_stop to hang forever (#113089) --- homeassistant/helpers/script.py | 9 ++++++++- tests/helpers/test_script.py | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index da710bd7e24..c246d625b3c 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -392,6 +392,7 @@ class _ScriptRun: self._context = context self._log_exceptions = log_exceptions self._step = -1 + self._started = False self._stop = asyncio.Event() self._stopped = asyncio.Event() self._conversation_response: str | None | UndefinedType = UNDEFINED @@ -420,6 +421,7 @@ class _ScriptRun: async def async_run(self) -> ScriptRunResult | None: """Run script.""" + self._started = True # Push the script to the script execution stack if (script_stack := script_stack_cv.get()) is None: script_stack = [] @@ -501,7 +503,12 @@ class _ScriptRun: async def async_stop(self) -> None: """Stop script run.""" self._stop.set() - await self._stopped.wait() + # If the script was never started + # the stopped event will never be + # set because the script will never + # start running + if self._started: + await self._stopped.wait() def _handle_exception( self, exception: Exception, continue_on_error: bool, log_exceptions: bool diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index b58b03fa9a9..114a90d39fc 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -4344,7 +4344,7 @@ async def test_script_mode_queued_cancel(hass: HomeAssistant) -> None: await task2 assert script_obj.is_running - assert script_obj.runs == 1 + assert script_obj.runs == 2 with pytest.raises(asyncio.CancelledError): task1.cancel() @@ -5621,3 +5621,20 @@ async def test_conversation_response_not_set_subscript_if( "1/if/condition/0": [{"result": {"result": var == 1, "entities": []}}], } assert_action_trace(expected_trace) + + +async def test_stopping_run_before_starting( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test stopping a script run before its started.""" + sequence = cv.SCRIPT_SCHEMA( + [ + {"wait_template": "{{ 'on' == 'off' }}"}, + ] + ) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + # Tested directly because we are checking for a race in the internals + # where the script is stopped before it is started. Previously this + # would hang indefinitely. + run = script._ScriptRun(hass, script_obj, {}, None, True) + await run.async_stop() From e29b012ebaa029bb3199ccddce61d1f096a7ac6e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 11:05:49 -1000 Subject: [PATCH 0791/1691] Fix failing google diagnostics test (#113095) --- tests/components/google/test_diagnostics.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/components/google/test_diagnostics.py b/tests/components/google/test_diagnostics.py index dd25ba152e5..32ed2ab3224 100644 --- a/tests/components/google/test_diagnostics.py +++ b/tests/components/google/test_diagnostics.py @@ -1,6 +1,7 @@ """Tests for diagnostics platform of google calendar.""" from collections.abc import Callable +import time from typing import Any from aiohttp.test_utils import TestClient @@ -16,6 +17,7 @@ from .conftest import TEST_EVENT, ComponentSetup from tests.common import CLIENT_ID, MockConfigEntry, MockUser from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.test_util.aiohttp import AiohttpClientMocker from tests.typing import ClientSessionGenerator @@ -70,8 +72,21 @@ async def test_diagnostics( aiohttp_client: ClientSessionGenerator, socket_enabled: None, snapshot: SnapshotAssertion, + aioclient_mock: AiohttpClientMocker, ) -> None: """Test diagnostics for the calendar.""" + + expires_in = 86400 + expires_at = time.time() + expires_in + aioclient_mock.post( + "https://oauth2.googleapis.com/token", + json={ + "refresh_token": "some-refresh-token", + "access_token": "some-updated-token", + "expires_at": expires_at, + "expires_in": expires_in, + }, + ) mock_events_list_items( [ { From b5761a83c018ba77fcb7f1c50af962138b190a3e Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Tue, 12 Mar 2024 07:17:42 +1000 Subject: [PATCH 0792/1691] Fix location data in Teslemetry (#112866) Fix location data in coordinator --- homeassistant/components/teslemetry/coordinator.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/teslemetry/coordinator.py b/homeassistant/components/teslemetry/coordinator.py index a14ed347193..d8ded1016fa 100644 --- a/homeassistant/components/teslemetry/coordinator.py +++ b/homeassistant/components/teslemetry/coordinator.py @@ -4,6 +4,7 @@ from datetime import timedelta from typing import Any from tesla_fleet_api import EnergySpecific, VehicleSpecific +from tesla_fleet_api.const import VehicleDataEndpoint from tesla_fleet_api.exceptions import TeslaFleetError, VehicleOffline from homeassistant.core import HomeAssistant @@ -13,6 +14,13 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import LOGGER, TeslemetryState SYNC_INTERVAL = 60 +ENDPOINTS = [ + VehicleDataEndpoint.CHARGE_STATE, + VehicleDataEndpoint.CLIMATE_STATE, + VehicleDataEndpoint.DRIVE_STATE, + VehicleDataEndpoint.LOCATION_DATA, + VehicleDataEndpoint.VEHICLE_STATE, +] class TeslemetryDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): @@ -54,7 +62,7 @@ class TeslemetryVehicleDataCoordinator(TeslemetryDataCoordinator): """Update vehicle data using Teslemetry API.""" try: - data = await self.api.vehicle_data() + data = await self.api.vehicle_data(endpoints=ENDPOINTS) except VehicleOffline: self.data["state"] = TeslemetryState.OFFLINE return self.data From 52b69bcfdea97a33a88f339ae79d6fcba2dbe2f3 Mon Sep 17 00:00:00 2001 From: Mike Woudenberg Date: Mon, 11 Mar 2024 22:20:06 +0100 Subject: [PATCH 0793/1691] Update Loqed helper for more logging (#112646) Updates Loqed helper for more logging --- homeassistant/components/loqed/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/loqed/manifest.json b/homeassistant/components/loqed/manifest.json index 7c682b3189d..46ac84b1768 100644 --- a/homeassistant/components/loqed/manifest.json +++ b/homeassistant/components/loqed/manifest.json @@ -7,7 +7,7 @@ "dependencies": ["webhook"], "documentation": "https://www.home-assistant.io/integrations/loqed", "iot_class": "local_push", - "requirements": ["loqedAPI==2.1.8"], + "requirements": ["loqedAPI==2.1.10"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 837d46b5235..56bec2d6de9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1247,7 +1247,7 @@ logi-circle==0.2.3 london-tube-status==0.5 # homeassistant.components.loqed -loqedAPI==2.1.8 +loqedAPI==2.1.10 # homeassistant.components.luftdaten luftdaten==0.7.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2cfe78978d6..cd5151b2a62 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -995,7 +995,7 @@ logi-circle==0.2.3 london-tube-status==0.5 # homeassistant.components.loqed -loqedAPI==2.1.8 +loqedAPI==2.1.10 # homeassistant.components.luftdaten luftdaten==0.7.4 From 77cdecf0f1eb3c79f961261b5a2f458ccf017d76 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 11:30:46 -1000 Subject: [PATCH 0794/1691] Migrate async_run_job to use eager_start for tasks (#113011) --- homeassistant/core.py | 2 +- tests/test_core.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 056774a2adc..ae5c9119738 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -855,7 +855,7 @@ class HomeAssistant: # https://github.com/home-assistant/core/pull/71960 if TYPE_CHECKING: target = cast(Callable[..., Coroutine[Any, Any, _R] | _R], target) - return self.async_run_hass_job(HassJob(target), *args) + return self.async_run_hass_job(HassJob(target), *args, eager_start=True) def block_till_done(self) -> None: """Block until all pending work is done.""" diff --git a/tests/test_core.py b/tests/test_core.py index 1d6f2830925..99490fe4365 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2116,6 +2116,20 @@ async def test_async_functions_with_callback(hass: HomeAssistant) -> None: assert len(runs) == 3 +async def test_async_run_job_starts_tasks_eagerly(hass: HomeAssistant) -> None: + """Test async_run_job starts tasks eagerly.""" + runs = [] + + async def _test(): + runs.append(True) + + task = hass.async_run_job(_test) + # No call to hass.async_block_till_done to ensure the task is run eagerly + assert len(runs) == 1 + assert task.done() + await task + + def test_valid_entity_id() -> None: """Test valid entity ID.""" for invalid in [ From 2061cedadb653999a67c7e00a583b34516480d5e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 12:01:06 -1000 Subject: [PATCH 0795/1691] Bump aiodhcpwatcher to 0.8.1 (#113096) --- homeassistant/components/dhcp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index 142aab52cc8..75d5b15124f 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -14,7 +14,7 @@ ], "quality_scale": "internal", "requirements": [ - "aiodhcpwatcher==0.8.0", + "aiodhcpwatcher==0.8.1", "aiodiscover==1.6.1", "cached_ipaddress==0.3.0" ] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e0dac4e9193..df3da9e332b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ # Automatically generated by gen_requirements_all.py, do not edit -aiodhcpwatcher==0.8.0 +aiodhcpwatcher==0.8.1 aiodiscover==1.6.1 aiohttp-fast-url-dispatcher==0.3.0 aiohttp-zlib-ng==0.3.1 diff --git a/requirements_all.txt b/requirements_all.txt index 56bec2d6de9..d44e6130f3e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ aiobotocore==2.9.1 aiocomelit==0.9.0 # homeassistant.components.dhcp -aiodhcpwatcher==0.8.0 +aiodhcpwatcher==0.8.1 # homeassistant.components.dhcp aiodiscover==1.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd5151b2a62..5a1c5130e51 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,7 +197,7 @@ aiobotocore==2.9.1 aiocomelit==0.9.0 # homeassistant.components.dhcp -aiodhcpwatcher==0.8.0 +aiodhcpwatcher==0.8.1 # homeassistant.components.dhcp aiodiscover==1.6.1 From 281e3922a8aeffc7abde5e3e3e930bbf06a1b89e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 13:02:20 -1000 Subject: [PATCH 0796/1691] Reduce latency to populate initial HKC bluetooth device state (#113103) --- homeassistant/components/homekit_controller/connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index b91203004ad..b7bc40479cf 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -285,6 +285,7 @@ class HKDevice: self.hass.bus.async_listen( EVENT_HOMEASSISTANT_STARTED, self._async_populate_ble_accessory_state, + run_immediately=True, ) ) else: From 487ae1786fee5ac7e5146fab197fe7928e72d361 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 12 Mar 2024 00:41:33 +0100 Subject: [PATCH 0797/1691] Bump axis to v54 (#113091) Co-authored-by: J. Nick Koston --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 65d18347415..f56df16918e 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==53"], + "requirements": ["axis==54"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index d44e6130f3e..ed7bc4ea5b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==53 +axis==54 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5a1c5130e51..56db4174f2f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -451,7 +451,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==53 +axis==54 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From 6ff8d7d2b9b5c5eb3f77409b07eca10eb4e5c4e3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 13:50:04 -1000 Subject: [PATCH 0798/1691] Make sonos ssdp discovery callback a callback function (#113107) --- homeassistant/components/sonos/__init__.py | 3 ++- tests/components/sonos/conftest.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index d16fd6cc6ea..48c4a614ca4 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -493,7 +493,8 @@ class SonosDiscoveryManager: self.hass, f"{SONOS_SPEAKER_ACTIVITY}-{uid}", source ) - async def _async_ssdp_discovered_player( + @callback + def _async_ssdp_discovered_player( self, info: ssdp.SsdpServiceInfo, change: ssdp.SsdpChange ) -> None: uid = info.upnp[ssdp.ATTR_UPNP_UDN] diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index f46023a05de..d9600b9f1e5 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -240,8 +240,8 @@ async def silent_ssdp_scanner(hass): def discover_fixture(soco): """Create a mock soco discover fixture.""" - async def do_callback(hass, callback, *args, **kwargs): - await callback( + def do_callback(hass, callback, *args, **kwargs): + callback( ssdp.SsdpServiceInfo( ssdp_location=f"http://{soco.ip_address}/", ssdp_st="urn:schemas-upnp-org:device:ZonePlayer:1", From 629a045c37993b32dbae72b233fa39fc90f6d48b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 13:50:18 -1000 Subject: [PATCH 0799/1691] Make sonos household_coordinator setup a callback function (#113108) --- homeassistant/components/sonos/household_coordinator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sonos/household_coordinator.py b/homeassistant/components/sonos/household_coordinator.py index b0c0592a642..9fc2703e48d 100644 --- a/homeassistant/components/sonos/household_coordinator.py +++ b/homeassistant/components/sonos/household_coordinator.py @@ -9,7 +9,7 @@ from typing import Any from soco import SoCo -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer from .const import DATA_SONOS @@ -35,7 +35,8 @@ class SonosHouseholdCoordinator: self.update_cache(soco) self.hass.add_job(self._async_setup) - async def _async_setup(self) -> None: + @callback + def _async_setup(self) -> None: """Finish setup in async context.""" self.cache_update_lock = asyncio.Lock() self.async_poll = Debouncer[Coroutine[Any, Any, None]]( From 0c877339ca5502aee5f352c876540035dcce1c45 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 13:50:43 -1000 Subject: [PATCH 0800/1691] Make sonos binary sensor entity creation callback functions (#113109) --- homeassistant/components/sonos/binary_sensor.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/binary_sensor.py b/homeassistant/components/sonos/binary_sensor.py index fbeb196f9fa..b927e970c9b 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -32,12 +32,14 @@ async def async_setup_entry( ) -> None: """Set up Sonos from a config entry.""" - async def _async_create_battery_entity(speaker: SonosSpeaker) -> None: + @callback + def _async_create_battery_entity(speaker: SonosSpeaker) -> None: _LOGGER.debug("Creating battery binary_sensor on %s", speaker.zone_name) entity = SonosPowerEntity(speaker) async_add_entities([entity]) - async def _async_create_mic_entity(speaker: SonosSpeaker) -> None: + @callback + def _async_create_mic_entity(speaker: SonosSpeaker) -> None: _LOGGER.debug("Creating microphone binary_sensor on %s", speaker.zone_name) async_add_entities([SonosMicrophoneSensorEntity(speaker)]) From 53c3e27ed9cc7e753c3dd437c4bc3a3505b5bc4a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 13:51:03 -1000 Subject: [PATCH 0801/1691] Add support for run_immediately to async_listen_once (#113020) --- homeassistant/core.py | 3 ++- tests/test_core.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index ae5c9119738..65f120ae8a2 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1465,6 +1465,7 @@ class EventBus: self, event_type: str, listener: Callable[[Event[Any]], Coroutine[Any, Any, None] | None], + run_immediately: bool = False, ) -> CALLBACK_TYPE: """Listen once for event of a specific type. @@ -1485,7 +1486,7 @@ class EventBus: job_type=HassJobType.Callback, ), None, - False, + run_immediately, ), ) one_time_listener.remove = remove diff --git a/tests/test_core.py b/tests/test_core.py index 99490fe4365..51805e46dba 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1169,6 +1169,21 @@ async def test_eventbus_run_immediately_coro(hass: HomeAssistant) -> None: unsub() +async def test_eventbus_listen_once_run_immediately_coro(hass: HomeAssistant) -> None: + """Test we can call events immediately with a coro.""" + calls = [] + + async def listener(event): + """Mock listener.""" + calls.append(event) + + hass.bus.async_listen_once("test", listener, run_immediately=True) + + hass.bus.async_fire("test", {"event": True}) + # No async_block_till_done here + assert len(calls) == 1 + + async def test_eventbus_unsubscribe_listener(hass: HomeAssistant) -> None: """Test unsubscribe listener from returned function.""" calls = [] From 620433a79dacf442fe0e181b1fa5ab0d29ca7ca8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:05:08 -1000 Subject: [PATCH 0802/1691] Run coroutines as eager tasks in async_run_hass_job (#111683) * Run coroutines as eager tasks in async_run_hass_job Note that this does not change async_add_hass_job Do not merge this. For test run only * Phase out periodic tasks * false by default or some tests will block forever, will need to fix each one manually * kwarg works * kwarg works * kwarg works * fixes * fix more tests * fix more tests * fix lifx * opensky * pvpc_hourly_pricing * adjust more * adjust more * smarttub * adjust more * adjust more * adjust more * adjust more * adjust * no eager executor * zha * qnap_qsw * fix more * fix fix * docs * its a wrapper now * add more coverage * coverage * cover all combos * more fixes * more fixes * more fixes * remaining issues are legit bugs in tests * make tplink test more predictable * more fixes * feedreader * grind out some more * make test race safe * limit first scope to triggers * one more * Start tasks eagerly in for async_at_start(ed) A few of these can avoid being scheduled on the loop during startup * fix cloud * Revert "fix cloud" This reverts commit 5eb3ce695da788bcae649f82c9527c0f9307139c. * fix test to do what start does * flip flag * flip flag * Fix here_travel_time creating many refresh requests at startup - Each entity would try to refresh the coordinator which created many tasks. Move the refresh to a single async_at_started - The tests fired the EVENT_HOMEASSISTANT_START event but the code used async_at_started which only worked because the tests did not set CoreState to not_running * fix azure * remove kw * remove kw * rip * cover * more rips * more rips * more rips --- .../components/azure_event_hub/__init__.py | 2 +- .../homeassistant/triggers/event.py | 1 - .../homeassistant/triggers/homeassistant.py | 1 - .../homeassistant/triggers/numeric_state.py | 1 - .../homeassistant/triggers/state.py | 1 - .../components/homeassistant/triggers/time.py | 1 - .../homeassistant/triggers/time_pattern.py | 1 - homeassistant/components/ssdp/__init__.py | 2 +- homeassistant/core.py | 12 +++----- homeassistant/helpers/debounce.py | 4 +-- homeassistant/helpers/discovery.py | 8 ++--- homeassistant/helpers/dispatcher.py | 2 +- homeassistant/helpers/event.py | 10 +++---- homeassistant/helpers/integration_platform.py | 3 +- homeassistant/helpers/service.py | 6 ++-- homeassistant/helpers/start.py | 4 +-- tests/test_core.py | 30 ++++++++++++------- 17 files changed, 40 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index 668444f9990..0a84ca44141 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -158,7 +158,7 @@ class AzureEventHub: """ logging.getLogger("azure.eventhub").setLevel(logging.WARNING) self._listener_remover = self.hass.bus.async_listen( - MATCH_ALL, self.async_listen + MATCH_ALL, self.async_listen, run_immediately=True ) self._schedule_next_send() diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 90ca131fdff..e045ece12ba 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -144,7 +144,6 @@ async def async_attach_trigger( } }, event.context, - eager_start=True, ) removes = [ diff --git a/homeassistant/components/homeassistant/triggers/homeassistant.py b/homeassistant/components/homeassistant/triggers/homeassistant.py index 600bce910cd..51e3a947a29 100644 --- a/homeassistant/components/homeassistant/triggers/homeassistant.py +++ b/homeassistant/components/homeassistant/triggers/homeassistant.py @@ -56,7 +56,6 @@ async def async_attach_trigger( "description": "Home Assistant starting", } }, - eager_start=True, ) return lambda: None diff --git a/homeassistant/components/homeassistant/triggers/numeric_state.py b/homeassistant/components/homeassistant/triggers/numeric_state.py index aad7cb7d9cb..2575af41401 100644 --- a/homeassistant/components/homeassistant/triggers/numeric_state.py +++ b/homeassistant/components/homeassistant/triggers/numeric_state.py @@ -187,7 +187,6 @@ async def async_attach_trigger( } }, to_s.context, - eager_start=True, ) @callback diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index 724f0d06680..6f3183e2b40 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -184,7 +184,6 @@ async def async_attach_trigger( } }, event.context, - eager_start=True, ) if not time_delta: diff --git a/homeassistant/components/homeassistant/triggers/time.py b/homeassistant/components/homeassistant/triggers/time.py index 6e648c3994f..b1d19d54795 100644 --- a/homeassistant/components/homeassistant/triggers/time.py +++ b/homeassistant/components/homeassistant/triggers/time.py @@ -76,7 +76,6 @@ async def async_attach_trigger( "entity_id": entity_id, } }, - eager_start=True, ) @callback diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index f69968eedbf..df49a79bcb6 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -93,7 +93,6 @@ async def async_attach_trigger( "description": "time pattern", } }, - eager_start=True, ) return async_track_time_change( diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index 72e94996361..5e130a0fc06 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -232,7 +232,7 @@ def _async_process_callbacks( for callback in callbacks: try: hass.async_run_hass_job( - callback, discovery_info, ssdp_change, eager_start=True, background=True + callback, discovery_info, ssdp_change, background=True ) except Exception: # pylint: disable=broad-except _LOGGER.exception("Failed to callback info: %s", discovery_info) diff --git a/homeassistant/core.py b/homeassistant/core.py index 65f120ae8a2..2fe50e4e50b 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -764,7 +764,6 @@ class HomeAssistant: self, hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any, - eager_start: bool = False, background: bool = False, ) -> asyncio.Future[_R] | None: ... @@ -775,7 +774,6 @@ class HomeAssistant: self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any, - eager_start: bool = False, background: bool = False, ) -> asyncio.Future[_R] | None: ... @@ -785,14 +783,12 @@ class HomeAssistant: self, hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any, - eager_start: bool = False, background: bool = False, ) -> asyncio.Future[_R] | None: """Run a HassJob from within the event loop. This method must be run in the event loop. - If eager_start is True, coroutine functions will be scheduled eagerly. If background is True, the task will created as a background task. hassjob: HassJob @@ -809,7 +805,7 @@ class HomeAssistant: return None return self.async_add_hass_job( - hassjob, *args, eager_start=eager_start, background=background + hassjob, *args, eager_start=True, background=background ) @overload @@ -847,7 +843,7 @@ class HomeAssistant: args: parameters for method to call. """ if asyncio.iscoroutine(target): - return self.async_create_task(target) + return self.async_create_task(target, eager_start=True) # This code path is performance sensitive and uses # if TYPE_CHECKING to avoid the overhead of constructing @@ -855,7 +851,7 @@ class HomeAssistant: # https://github.com/home-assistant/core/pull/71960 if TYPE_CHECKING: target = cast(Callable[..., Coroutine[Any, Any, _R] | _R], target) - return self.async_run_hass_job(HassJob(target), *args, eager_start=True) + return self.async_run_hass_job(HassJob(target), *args) def block_till_done(self) -> None: """Block until all pending work is done.""" @@ -1369,7 +1365,7 @@ class EventBus: continue if run_immediately: try: - self._hass.async_run_hass_job(job, event, eager_start=True) + self._hass.async_run_hass_job(job, event) except Exception: # pylint: disable=broad-except _LOGGER.exception("Error running job: %s", job) else: diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 773fff6ebc6..2fe42e19a67 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -109,7 +109,7 @@ class Debouncer(Generic[_R_co]): assert self._job is not None try: - if task := self.hass.async_run_hass_job(self._job, eager_start=True): + if task := self.hass.async_run_hass_job(self._job): await task finally: self._schedule_timer() @@ -130,7 +130,7 @@ class Debouncer(Generic[_R_co]): return try: - if task := self.hass.async_run_hass_job(self._job, eager_start=True): + if task := self.hass.async_run_hass_job(self._job): await task except Exception: # pylint: disable=broad-except self.logger.exception("Unexpected exception from %s", self.function) diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 1f8ba096a69..98419ae6bf2 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -50,9 +50,7 @@ def async_listen( @core.callback def _async_discovery_event_listener(discovered: DiscoveryDict) -> None: """Listen for discovery events.""" - hass.async_run_hass_job( - job, discovered["service"], discovered["discovered"], eager_start=True - ) + hass.async_run_hass_job(job, discovered["service"], discovered["discovered"]) async_dispatcher_connect( hass, @@ -115,9 +113,7 @@ def async_listen_platform( """Listen for platform discovery events.""" if not (platform := discovered["platform"]): return - hass.async_run_hass_job( - job, platform, discovered.get("discovered"), eager_start=True - ) + hass.async_run_hass_job(job, platform, discovered.get("discovered")) return async_dispatcher_connect( hass, diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index 20781eb8084..cb812ef071b 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -225,4 +225,4 @@ def async_dispatcher_send( if job is None: job = _generate_job(signal, target) target_list[target] = job - hass.async_run_hass_job(job, *args, eager_start=True) + hass.async_run_hass_job(job, *args) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index e9da56bf3af..44fc2356c83 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -328,7 +328,7 @@ def _async_dispatch_entity_id_event( return for job in callbacks_list.copy(): try: - hass.async_run_hass_job(job, event, eager_start=True) + hass.async_run_hass_job(job, event) except Exception: # pylint: disable=broad-except _LOGGER.exception( "Error while dispatching event for %s to %s", @@ -1599,7 +1599,7 @@ class _TrackTimeInterval: self._track_job, hass.loop.time() + self.seconds, ) - hass.async_run_hass_job(self._run_job, now, eager_start=True, background=True) + hass.async_run_hass_job(self._run_job, now, background=True) @callback def async_cancel(self) -> None: @@ -1684,7 +1684,7 @@ class SunListener: """Handle solar event.""" self._unsub_sun = None self._listen_next_sun_event() - self.hass.async_run_hass_job(self.job, eager_start=True, background=True) + self.hass.async_run_hass_job(self.job, background=True) @callback def _handle_config_event(self, _event: Any) -> None: @@ -1770,9 +1770,7 @@ class _TrackUTCTimeChange: # time when the timer was scheduled utc_now = time_tracker_utcnow() localized_now = dt_util.as_local(utc_now) if self.local else utc_now - hass.async_run_hass_job( - self.job, localized_now, eager_start=True, background=True - ) + hass.async_run_hass_job(self.job, localized_now, background=True) if TYPE_CHECKING: assert self._pattern_time_change_listener_job is not None self._cancel_callback = async_track_point_in_utc_time( diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index 9d98fa24b72..e142f9c2e5a 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -140,7 +140,6 @@ def _process_integration_platforms( hass, integration.domain, platform, - eager_start=True, ) ) ] @@ -250,7 +249,7 @@ async def _async_process_integration_platforms( continue if future := hass.async_run_hass_job( - process_job, hass, integration.domain, platform, eager_start=True + process_job, hass, integration.domain, platform ): futures.append(future) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 951f1c24402..99c15c3412e 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -969,9 +969,9 @@ async def _handle_entity_call( partial(getattr(entity, func), **data), # type: ignore[arg-type] job_type=entity.get_hassjob_type(func), ) - task = hass.async_run_hass_job(job, eager_start=True) + task = hass.async_run_hass_job(job) else: - task = hass.async_run_hass_job(func, entity, data, eager_start=True) + task = hass.async_run_hass_job(func, entity, data) # Guard because callback functions do not return a task when passed to # async_run_job. @@ -1006,7 +1006,7 @@ async def _async_admin_handler( if not user.is_admin: raise Unauthorized(context=call.context) - result = hass.async_run_hass_job(service_job, call, eager_start=True) + result = hass.async_run_hass_job(service_job, call) if result is not None: await result diff --git a/homeassistant/helpers/start.py b/homeassistant/helpers/start.py index d09e542e2e0..148b416e087 100644 --- a/homeassistant/helpers/start.py +++ b/homeassistant/helpers/start.py @@ -30,7 +30,7 @@ def _async_at_core_state( """ at_start_job = HassJob(at_start_cb) if check_state(hass): - hass.async_run_hass_job(at_start_job, hass, eager_start=True) + hass.async_run_hass_job(at_start_job, hass) return lambda: None unsub: None | CALLBACK_TYPE = None @@ -38,7 +38,7 @@ def _async_at_core_state( @callback def _matched_event(event: Event) -> None: """Call the callback when Home Assistant started.""" - hass.async_run_hass_job(at_start_job, hass, eager_start=True) + hass.async_run_hass_job(at_start_job, hass) nonlocal unsub unsub = None diff --git a/tests/test_core.py b/tests/test_core.py index 51805e46dba..3d72c7481f5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -123,9 +123,7 @@ async def test_async_run_hass_job_eager_start_coro_suspends( async def job_that_suspends(): await asyncio.sleep(0) - task = hass.async_run_hass_job( - ha.HassJob(ha.callback(job_that_suspends)), eager_start=True - ) + task = hass.async_run_hass_job(ha.HassJob(ha.callback(job_that_suspends))) assert not task.done() assert task in hass._tasks await task @@ -169,7 +167,7 @@ async def test_async_add_hass_job_eager_background(hass: HomeAssistant) -> None: await asyncio.sleep(0) task = hass.async_add_hass_job( - ha.HassJob(ha.callback(job_that_suspends)), eager_start=True, background=True + ha.HassJob(ha.callback(job_that_suspends)), background=True ) assert not task.done() assert task in hass._background_tasks @@ -184,7 +182,7 @@ async def test_async_run_hass_job_eager_background(hass: HomeAssistant) -> None: await asyncio.sleep(0) task = hass.async_run_hass_job( - ha.HassJob(ha.callback(job_that_suspends)), eager_start=True, background=True + ha.HassJob(ha.callback(job_that_suspends)), background=True ) assert not task.done() assert task in hass._background_tasks @@ -200,7 +198,6 @@ async def test_async_run_hass_job_background_synchronous(hass: HomeAssistant) -> task = hass.async_run_hass_job( ha.HassJob(ha.callback(job_that_does_not_suspends)), - eager_start=True, background=True, ) assert task.done() @@ -217,7 +214,6 @@ async def test_async_run_hass_job_synchronous(hass: HomeAssistant) -> None: task = hass.async_run_hass_job( ha.HassJob(ha.callback(job_that_does_not_suspends)), - eager_start=True, background=False, ) assert task.done() @@ -393,9 +389,7 @@ async def test_async_run_eager_hass_job_calls_callback() -> None: asyncio.get_running_loop() # ensure we are in the event loop calls.append(1) - ha.HomeAssistant.async_run_hass_job( - hass, ha.HassJob(ha.callback(job)), eager_start=True - ) + ha.HomeAssistant.async_run_hass_job(hass, ha.HassJob(ha.callback(job))) assert len(calls) == 1 @@ -406,7 +400,7 @@ async def test_async_run_eager_hass_job_calls_coro_function() -> None: async def job(): pass - ha.HomeAssistant.async_run_hass_job(hass, ha.HassJob(job), eager_start=True) + ha.HomeAssistant.async_run_hass_job(hass, ha.HassJob(job)) assert len(hass.async_add_hass_job.mock_calls) == 1 @@ -2145,6 +2139,20 @@ async def test_async_run_job_starts_tasks_eagerly(hass: HomeAssistant) -> None: await task +async def test_async_run_job_starts_coro_eagerly(hass: HomeAssistant) -> None: + """Test async_run_job starts coros eagerly.""" + runs = [] + + async def _test(): + runs.append(True) + + task = hass.async_run_job(_test()) + # No call to hass.async_block_till_done to ensure the task is run eagerly + assert len(runs) == 1 + assert task.done() + await task + + def test_valid_entity_id() -> None: """Test valid entity ID.""" for invalid in [ From 5dc44500c3ed75b5639bc861461959773a38d003 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:07:15 -1000 Subject: [PATCH 0803/1691] Make cast async_cast_discovered a callback function (#113111) Nothing was being awaited here and this function is never subclassed --- homeassistant/components/cast/media_player.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 9d041cb7cfb..941b2e0675b 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -242,7 +242,8 @@ class CastDevice: self._status_listener.invalidate() self._status_listener = None - async def _async_cast_discovered(self, discover: ChromecastInfo) -> None: + @callback + def _async_cast_discovered(self, discover: ChromecastInfo) -> None: """Handle discovery of new Chromecast.""" if self._cast_info.uuid != discover.uuid: # Discovered is not our device. From 6e59d1cb29fcb764072e4df03af67c521481340d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:08:02 -1000 Subject: [PATCH 0804/1691] Migrate homekit to use async_at_started (#113102) --- homeassistant/components/homekit/__init__.py | 11 +- tests/components/homekit/test_config_flow.py | 186 ++++++++++--------- tests/components/homekit/test_homekit.py | 12 +- tests/components/homekit/test_init.py | 6 +- 4 files changed, 112 insertions(+), 103 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 376962f376e..3f7b210832d 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -45,13 +45,11 @@ from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, - EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, SERVICE_RELOAD, ) from homeassistant.core import ( CALLBACK_TYPE, - CoreState, HomeAssistant, ServiceCall, State, @@ -75,6 +73,7 @@ from homeassistant.helpers.service import ( async_extract_referenced_entity_ids, async_register_admin_service, ) +from homeassistant.helpers.start import async_at_started from homeassistant.helpers.typing import ConfigType from homeassistant.loader import IntegrationNotFound, async_get_integration @@ -358,10 +357,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) hass.data[DOMAIN][entry.entry_id] = entry_data - if hass.state is CoreState.running: + async def _async_start_homekit(hass: HomeAssistant) -> None: await homekit.async_start() - else: - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, homekit.async_start) + + entry.async_on_unload(async_at_started(hass, _async_start_homekit)) return True @@ -553,7 +552,6 @@ class HomeKit: """Set up bridge and accessory driver.""" assert self.iid_storage is not None persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) - self.driver = HomeDriver( self.hass, self._entry_id, @@ -569,7 +567,6 @@ class HomeKit: loader=get_loader(), iid_storage=self.iid_storage, ) - # If we do not load the mac address will be wrong # as pyhap uses a random one until state is restored if os.path.exists(persist_file): diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index dd389207b8d..fbcc33b9c7d 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -1,6 +1,6 @@ """Test the HomeKit config flow.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch import pytest import voluptuous as vol @@ -428,62 +428,68 @@ async def test_options_flow_devices( demo_config_entry = MockConfigEntry(domain="domain") demo_config_entry.add_to_hass(hass) - assert await async_setup_component(hass, "homeassistant", {}) - assert await async_setup_component(hass, "demo", {"demo": {}}) - assert await async_setup_component(hass, "homekit", {"homekit": {}}) + with patch("homeassistant.components.homekit.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() + assert await async_setup_component(hass, "homekit", {"homekit": {}}) + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, "demo", {"demo": {}}) + assert await async_setup_component(hass, "homekit", {"homekit": {}}) - hass.states.async_set("climate.old", "off") - await hass.async_block_till_done() + hass.states.async_set("climate.old", "off") + await hass.async_block_till_done() - result = await hass.config_entries.options.async_init( - config_entry.entry_id, context={"show_advanced_options": True} - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "init" - - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "domains": ["fan", "vacuum", "climate"], - "include_exclude_mode": "exclude", - }, - ) - - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "exclude" - - entry = entity_registry.async_get("light.ceiling_lights") - assert entry is not None - device_id = entry.device_id - - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "entities": ["climate.old"], - }, - ) - - with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): - result3 = await hass.config_entries.options.async_configure( - result2["flow_id"], - user_input={"devices": [device_id]}, + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": True} ) - assert result3["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert config_entry.options == { - "devices": [device_id], - "mode": "bridge", - "filter": { - "exclude_domains": [], - "exclude_entities": ["climate.old"], - "include_domains": ["fan", "vacuum", "climate"], - "include_entities": [], - }, - } + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "init" - await hass.async_block_till_done() - await hass.config_entries.async_unload(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "domains": ["fan", "vacuum", "climate"], + "include_exclude_mode": "exclude", + }, + ) + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "exclude" + + entry = entity_registry.async_get("light.ceiling_lights") + assert entry is not None + device_id = entry.device_id + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "entities": ["climate.old"], + }, + ) + + with patch( + "homeassistant.components.homekit.async_setup_entry", return_value=True + ): + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], + user_input={"devices": [device_id]}, + ) + + assert result3["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert config_entry.options == { + "devices": [device_id], + "mode": "bridge", + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.old"], + "include_domains": ["fan", "vacuum", "climate"], + "include_entities": [], + }, + } + + await hass.async_block_till_done() + await hass.config_entries.async_unload(config_entry.entry_id) @patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True) @@ -514,49 +520,53 @@ async def test_options_flow_devices_preserved_when_advanced_off( demo_config_entry = MockConfigEntry(domain="domain") demo_config_entry.add_to_hass(hass) - assert await async_setup_component(hass, "homekit", {"homekit": {}}) + with patch("homeassistant.components.homekit.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() + assert await async_setup_component(hass, "homekit", {"homekit": {}}) - hass.states.async_set("climate.old", "off") - await hass.async_block_till_done() + hass.states.async_set("climate.old", "off") + await hass.async_block_till_done() - result = await hass.config_entries.options.async_init( - config_entry.entry_id, context={"show_advanced_options": False} - ) + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": False} + ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "init" + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "init" - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "domains": ["fan", "vacuum", "climate"], - "include_exclude_mode": "exclude", - }, - ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "domains": ["fan", "vacuum", "climate"], + "include_exclude_mode": "exclude", + }, + ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "exclude" + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "exclude" - result2 = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - "entities": ["climate.old"], - }, - ) + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "entities": ["climate.old"], + }, + ) - assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - assert config_entry.options == { - "devices": ["1fabcabcabcabcabcabcabcabcabc"], - "mode": "bridge", - "filter": { - "exclude_domains": [], - "exclude_entities": ["climate.old"], - "include_domains": ["fan", "vacuum", "climate"], - "include_entities": [], - }, - } - await hass.async_block_till_done() - await hass.config_entries.async_unload(config_entry.entry_id) + assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert config_entry.options == { + "devices": ["1fabcabcabcabcabcabcabcabcabc"], + "mode": "bridge", + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.old"], + "include_domains": ["fan", "vacuum", "climate"], + "include_entities": [], + }, + } + await hass.async_block_till_done() + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() async def test_options_flow_include_mode_with_non_existant_entity( diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index da5f8e4f5e5..c1dcd28ce68 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -347,7 +347,7 @@ async def test_homekit_with_single_advertise_ips( ) entry.add_to_hass(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: - mock_driver.async_start = AsyncMock() + hk_driver.async_start = AsyncMock() await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -386,7 +386,7 @@ async def test_homekit_with_many_advertise_ips( ) entry.add_to_hass(hass) with patch(f"{PATH_HOMEKIT}.HomeDriver", return_value=hk_driver) as mock_driver: - mock_driver.async_start = AsyncMock() + hk_driver.async_start = AsyncMock() await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -2043,6 +2043,7 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" ): mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() assert await async_setup_component( hass, "homekit", {"homekit": {CONF_NAME: "reloadable", CONF_PORT: 12345}} ) @@ -2065,16 +2066,15 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: yaml_path = get_fixture_path("configuration.yaml", "homekit") with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), patch( f"{PATH_HOMEKIT}.HomeKit" - ) as mock_homekit2, patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch( + ) as mock_homekit2, patch(f"{PATH_HOMEKIT}.async_show_setup_message"), patch( f"{PATH_HOMEKIT}.get_accessory", - ), patch( + ), patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True), patch( "pyhap.accessory_driver.AccessoryDriver.async_start", ), patch( "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" ): mock_homekit2.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() await hass.services.async_call( "homekit", SERVICE_RELOAD, diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index 068f13c0e54..b56d01bd871 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -1,6 +1,6 @@ """Test HomeKit initialization.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch import pytest @@ -31,7 +31,9 @@ async def test_humanify_homekit_changed_event( ) -> None: """Test humanifying HomeKit changed event.""" hass.config.components.add("recorder") - with patch("homeassistant.components.homekit.HomeKit"): + with patch("homeassistant.components.homekit.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() assert await async_setup_component(hass, "homekit", {"homekit": {}}) assert await async_setup_component(hass, "logbook", {}) await hass.async_block_till_done() From b87036eebe0a43912ac112d43000d6fc533ff078 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:25:15 -1000 Subject: [PATCH 0805/1691] Make restore state started a callback function (#113110) --- homeassistant/helpers/restore_state.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index d0ef3acd426..2d6ffb9ec49 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -144,7 +144,8 @@ class RestoreStateData: """Set up up the instance of this data helper.""" await self.async_load() - async def hass_start(hass: HomeAssistant) -> None: + @callback + def hass_start(hass: HomeAssistant) -> None: """Start the restore state task.""" self.async_setup_dump() From 1536a3981fe873de2f04624582b42c4d16338d32 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:29:39 -1000 Subject: [PATCH 0806/1691] Use run_immediately for starting discovery at the started event (#113112) --- homeassistant/helpers/discovery_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/discovery_flow.py b/homeassistant/helpers/discovery_flow.py index b6633a3f718..e24b405c685 100644 --- a/homeassistant/helpers/discovery_flow.py +++ b/homeassistant/helpers/discovery_flow.py @@ -82,7 +82,9 @@ class FlowDispatcher: @callback def async_setup(self) -> None: """Set up the flow disptcher.""" - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, self._async_start) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, self._async_start, run_immediately=True + ) async def _async_start(self, event: Event) -> None: """Start processing pending flows.""" From e71398d1e0de11e82237667e8c2ccf3b8566232c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 14:45:58 -1000 Subject: [PATCH 0807/1691] Update timezone in supervisor in an eager task (#113113) --- homeassistant/components/hassio/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 802b84cee43..b533f615db6 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -384,7 +384,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: last_timezone = new_timezone await hassio.update_hass_timezone(new_timezone) - hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, push_config) + hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, push_config, run_immediately=True) push_config_task = hass.async_create_task(push_config(None), eager_start=True) # Start listening for problems with supervisor and making issues From 42527862e00a2a95920d5539acdfd25c95b4c773 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:01:30 -1000 Subject: [PATCH 0808/1691] Migrate scene platform to use async_add_executor_job (#113114) --- homeassistant/components/scene/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 5a74e64ca53..ca56f5c8682 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -131,6 +131,6 @@ class Scene(RestoreEntity): async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" - task = self.hass.async_add_job(ft.partial(self.activate, **kwargs)) + task = self.hass.async_add_executor_job(ft.partial(self.activate, **kwargs)) if task: await task From 51f871227ed1e716a38b4d30518964888354253e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:34:32 -1000 Subject: [PATCH 0809/1691] Attach template triggers at start eagerly (#113120) This method calls async_initialize_triggers which is likely to never suspend and the attach can avoid being scheduled on the event loop --- homeassistant/components/template/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/template/coordinator.py b/homeassistant/components/template/coordinator.py index e0beaead2fe..4e86d629990 100644 --- a/homeassistant/components/template/coordinator.py +++ b/homeassistant/components/template/coordinator.py @@ -47,7 +47,7 @@ class TriggerUpdateCoordinator(DataUpdateCoordinator): await self._attach_triggers() else: self._unsub_start = self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, self._attach_triggers + EVENT_HOMEASSISTANT_START, self._attach_triggers, run_immediately=True ) for platform_domain in PLATFORMS: From 17209525cbccbe69c0b6a3285d99e2edc1457eca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:35:53 -1000 Subject: [PATCH 0810/1691] Add run_immediately to the zone core config update async_listen (#113119) Calling async_update_config will never suspend but cannot be changed to a callback function because it would break the collections api --- homeassistant/components/zone/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 4c4b9c7c229..2fda501c447 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -281,7 +281,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Handle core config updated.""" await home_zone.async_update_config(_home_conf(hass)) - hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, core_config_updated) + hass.bus.async_listen( + EVENT_CORE_CONFIG_UPDATE, core_config_updated, run_immediately=True + ) hass.data[DOMAIN] = storage_collection From 427de0052451cf55aca6d9a466f1c10832f819e4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:36:18 -1000 Subject: [PATCH 0811/1691] Remove unnecessary use of async_run_job in script helper (#113118) The function being passed to `async_run` was always a callback --- homeassistant/helpers/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index c246d625b3c..7a1f0bdb8da 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1600,7 +1600,7 @@ class Script: await self.async_stop(update_state=False, spare=run) if started_action: - self._hass.async_run_job(started_action) + started_action() self.last_triggered = utcnow() self._changed() From ebd17687e071edd2c948ca2868e4f2df5fe534da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:36:34 -1000 Subject: [PATCH 0812/1691] Remove unnecessary use of async_add_job in openalpr_cloud (#113116) --- homeassistant/components/openalpr_cloud/image_processing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/openalpr_cloud/image_processing.py b/homeassistant/components/openalpr_cloud/image_processing.py index 2c2807f5364..525edc7da4b 100644 --- a/homeassistant/components/openalpr_cloud/image_processing.py +++ b/homeassistant/components/openalpr_cloud/image_processing.py @@ -142,8 +142,7 @@ class ImageProcessingAlprEntity(ImageProcessingEntity): # Send events for i_plate in new_plates: - self.hass.async_add_job( - self.hass.bus.async_fire, + self.hass.bus.async_fire( EVENT_FOUND_PLATE, { ATTR_PLATE: i_plate, From 17b202d8cfcdf7affd2b7628d9942e49b09fcca4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:37:03 -1000 Subject: [PATCH 0813/1691] Await discovery_function in the DiscoveryFlowHandler instead of wrapping it in a task (#113115) Await discovery_function in the DiscoveryFlowHandler instead of wrapping This function was always a coro so it can be awaited directly instead of wrapping it in add_job --- homeassistant/helpers/config_entry_flow.py | 3 +-- tests/helpers/test_config_entry_flow.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index c4da43a7061..f2247e533a8 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -69,8 +69,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): if not (has_devices := bool(in_progress)): has_devices = await cast( - "asyncio.Future[bool]", - self.hass.async_add_job(self._discovery_function, self.hass), + "asyncio.Future[bool]", self._discovery_function(self.hass) ) if not has_devices: diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index f59b2163591..0735c0a1a80 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -404,6 +404,7 @@ async def test_webhook_create_cloudhook( assert len(mock_delete.mock_calls) == 1 assert result["require_restart"] is False + await hass.async_block_till_done() async def test_webhook_create_cloudhook_aborts_not_connected( From 1949b9936b0fb8b6d0dce031f16bbea3d925a162 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:41:05 -1000 Subject: [PATCH 0814/1691] Simplify automation startup logic (#113122) --- homeassistant/components/automation/__init__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index eb5d6d840ec..5854cad3ef9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -702,7 +702,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): await super().async_will_remove_from_hass() await self.async_disable() - async def _async_enable_automation(self) -> None: + async def _async_enable_automation(self, event: Event) -> None: """Start automation on startup.""" # Don't do anything if no longer enabled or already attached if not self._is_enabled or self._async_detach_triggers is not None: @@ -710,11 +710,6 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): self._async_detach_triggers = await self._async_attach_triggers(True) - @callback - def _async_create_enable_automation_task(self, event: Event) -> None: - """Create a task to enable the automation.""" - self.hass.async_create_task(self._async_enable_automation(), eager_start=True) - async def async_enable(self) -> None: """Enable this automation entity. @@ -732,7 +727,9 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): return self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STARTED, self._async_create_enable_automation_task + EVENT_HOMEASSISTANT_STARTED, + self._async_enable_automation, + run_immediately=True, ) self.async_write_ha_state() From 3f72fae60b3364d9256b2bc00a3cf4be9a0bbd5a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:56:24 -1000 Subject: [PATCH 0815/1691] Migrate remaining call in check_config helper to use async_get_component (#113123) --- homeassistant/helpers/check_config.py | 2 +- tests/helpers/test_check_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 2f716ac8bb1..86083b31b2f 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -192,7 +192,7 @@ async def async_check_ha_config_file( # noqa: C901 continue try: - component = integration.get_component() + component = await integration.async_get_component() except ImportError as ex: result.add_warning(f"Component error: {domain} - {ex}") continue diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index 09ecaac6638..bba9c3f27c4 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -171,7 +171,7 @@ async def test_integration_import_error(hass: HomeAssistant) -> None: # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:"} with patch( - "homeassistant.loader.Integration.get_component", + "homeassistant.loader.Integration.async_get_component", side_effect=ImportError("blablabla"), ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): res = await async_check_ha_config_file(hass) From 52b2522be287e1ffa0ccb9a657e200d164e362f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 15:58:12 -1000 Subject: [PATCH 0816/1691] Import homeassistant trigger platforms in the executor (#113124) --- .../components/homeassistant/trigger.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homeassistant/trigger.py b/homeassistant/components/homeassistant/trigger.py index 3ee73255a4d..74a96fce784 100644 --- a/homeassistant/components/homeassistant/trigger.py +++ b/homeassistant/components/homeassistant/trigger.py @@ -11,16 +11,33 @@ from homeassistant.helpers.trigger import ( ) from homeassistant.helpers.typing import ConfigType +DATA_TRIGGER_PLATFORMS = "homeassistant_trigger_platforms" -def _get_trigger_platform(config: ConfigType) -> TriggerProtocol: - return importlib.import_module(f"..triggers.{config[CONF_PLATFORM]}", __name__) + +def _get_trigger_platform(platform_name: str) -> TriggerProtocol: + """Get trigger platform.""" + return importlib.import_module(f"..triggers.{platform_name}", __name__) + + +async def _async_get_trigger_platform( + hass: HomeAssistant, platform_name: str +) -> TriggerProtocol: + """Get trigger platform from cache or import it.""" + cache: dict[str, TriggerProtocol] = hass.data.setdefault(DATA_TRIGGER_PLATFORMS, {}) + if platform := cache.get(platform_name): + return platform + platform = await hass.async_add_import_executor_job( + _get_trigger_platform, platform_name + ) + cache[platform_name] = platform + return platform async def async_validate_trigger_config( hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" - platform = _get_trigger_platform(config) + platform = await _async_get_trigger_platform(hass, config[CONF_PLATFORM]) if hasattr(platform, "async_validate_trigger_config"): return await platform.async_validate_trigger_config(hass, config) @@ -34,5 +51,5 @@ async def async_attach_trigger( trigger_info: TriggerInfo, ) -> CALLBACK_TYPE: """Attach trigger of specified platform.""" - platform = _get_trigger_platform(config) + platform = await _async_get_trigger_platform(hass, config[CONF_PLATFORM]) return await platform.async_attach_trigger(hass, config, action, trigger_info) From 734a614eb8dbd2a7026c16ea1eaa2d52735bb510 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Mar 2024 16:17:07 -1000 Subject: [PATCH 0817/1691] Switch the reload helper to use async_get_component (#113126) --- homeassistant/helpers/reload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index dcf6a59a71e..d6338af95c7 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -75,7 +75,7 @@ async def _resetup_platform( root_config[platform_domain].append(p_config) - component = integration.get_component() + component = await integration.async_get_component() if hasattr(component, "async_reset_platform"): # If the integration has its own way to reset From 6f19744469a3606bc2e7b62506be0322b3586e02 Mon Sep 17 00:00:00 2001 From: definitio <37266727+definitio@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:06:02 +0300 Subject: [PATCH 0818/1691] Add A6 family code for DS2438 1-Wire sensor (#112844) * Add A6 family code for DS2438 * Add tests * Fix switch * Apply code review suggestion * Add comments --- homeassistant/components/onewire/const.py | 1 + homeassistant/components/onewire/sensor.py | 3 + homeassistant/components/onewire/switch.py | 3 + tests/components/onewire/const.py | 23 + .../onewire/snapshots/test_binary_sensor.ambr | 40 ++ .../onewire/snapshots/test_sensor.ambr | 568 ++++++++++++++++++ .../onewire/snapshots/test_switch.ambr | 83 +++ 7 files changed, 721 insertions(+) diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py index 110e3da0976..a4f3ebe9a78 100644 --- a/homeassistant/components/onewire/const.py +++ b/homeassistant/components/onewire/const.py @@ -28,6 +28,7 @@ DEVICE_SUPPORT = { "3B": (), "42": (), "7E": ("EDS0066", "EDS0068"), + "A6": (), "EF": ("HB_HUB", "HB_MOISTURE_METER", "HobbyBoards_EF"), } diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 2a0f348aa17..d32afce7fa9 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -383,6 +383,9 @@ def get_entities( elif "7E" in family: device_sub_type = "EDS" family = device_type + elif "A6" in family: + # A6 is a secondary family code for DS2438 + family = "26" if family not in get_sensor_types(device_sub_type): continue diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index 87e049a575d..95a712c95bc 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -181,6 +181,9 @@ def get_entities(onewire_hub: OneWireHub) -> list[OneWireSwitch]: if "EF" in family: device_sub_type = "HobbyBoard" family = device_type + elif "A6" in family: + # A6 is a secondary family code for DS2438 + family = "26" if family not in get_sensor_types(device_sub_type): continue diff --git a/tests/components/onewire/const.py b/tests/components/onewire/const.py index c7c16be820b..7fdb6500c37 100644 --- a/tests/components/onewire/const.py +++ b/tests/components/onewire/const.py @@ -224,6 +224,29 @@ MOCK_OWPROXY_DEVICES = { {ATTR_INJECT_READS: b" 29.123"}, ], }, + "A6.111111111111": { + ATTR_INJECT_READS: [ + b"DS2438", # read device type + ], + Platform.SENSOR: [ + {ATTR_INJECT_READS: b" 25.123"}, + {ATTR_INJECT_READS: b" 72.7563"}, + {ATTR_INJECT_READS: b" 73.7563"}, + {ATTR_INJECT_READS: b" 74.7563"}, + {ATTR_INJECT_READS: b" 75.7563"}, + { + ATTR_INJECT_READS: ProtocolError, + }, + {ATTR_INJECT_READS: b" 969.265"}, + {ATTR_INJECT_READS: b" 65.8839"}, + {ATTR_INJECT_READS: b" 2.97"}, + {ATTR_INJECT_READS: b" 4.74"}, + {ATTR_INJECT_READS: b" 0.12"}, + ], + Platform.SWITCH: [ + {ATTR_INJECT_READS: b" 1"}, + ], + }, "EF.111111111111": { ATTR_INJECT_READS: [ b"HobbyBoards_EF", # read type diff --git a/tests/components/onewire/snapshots/test_binary_sensor.ambr b/tests/components/onewire/snapshots/test_binary_sensor.ambr index 2aa415f0345..60744f59c68 100644 --- a/tests/components/onewire/snapshots/test_binary_sensor.ambr +++ b/tests/components/onewire/snapshots/test_binary_sensor.ambr @@ -1235,6 +1235,46 @@ list([ ]) # --- +# name: test_binary_sensors[A6.111111111111] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'onewire', + 'A6.111111111111', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Maxim Integrated', + 'model': 'DS2438', + 'name': 'A6.111111111111', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_binary_sensors[A6.111111111111].1 + list([ + ]) +# --- +# name: test_binary_sensors[A6.111111111111].2 + list([ + ]) +# --- # name: test_binary_sensors[EF.111111111111] list([ DeviceRegistryEntrySnapshot({ diff --git a/tests/components/onewire/snapshots/test_sensor.ambr b/tests/components/onewire/snapshots/test_sensor.ambr index 970d63f4dac..989a5997ca8 100644 --- a/tests/components/onewire/snapshots/test_sensor.ambr +++ b/tests/components/onewire/snapshots/test_sensor.ambr @@ -2347,6 +2347,574 @@ }), ]) # --- +# name: test_sensors[A6.111111111111] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'onewire', + 'A6.111111111111', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Maxim Integrated', + 'model': 'DS2438', + 'name': 'A6.111111111111', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensors[A6.111111111111].1 + list([ + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '/A6.111111111111/temperature', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Humidity', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '/A6.111111111111/humidity', + 'unit_of_measurement': '%', + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_hih3600_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'HIH3600 humidity', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'humidity_hih3600', + 'unique_id': '/A6.111111111111/HIH3600/humidity', + 'unit_of_measurement': '%', + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_hih4000_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'HIH4000 humidity', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'humidity_hih4000', + 'unique_id': '/A6.111111111111/HIH4000/humidity', + 'unit_of_measurement': '%', + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_hih5030_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'HIH5030 humidity', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'humidity_hih5030', + 'unique_id': '/A6.111111111111/HIH5030/humidity', + 'unit_of_measurement': '%', + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_htm1735_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'HTM1735 humidity', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'humidity_htm1735', + 'unique_id': '/A6.111111111111/HTM1735/humidity', + 'unit_of_measurement': '%', + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_pressure', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Pressure', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '/A6.111111111111/B1-R1-A/pressure', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_illuminance', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Illuminance', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '/A6.111111111111/S3-R1-A/illuminance', + 'unit_of_measurement': 'lx', + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_vad_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'VAD voltage', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'voltage_vad', + 'unique_id': '/A6.111111111111/VAD', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_vdd_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'VDD voltage', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'voltage_vdd', + 'unique_id': '/A6.111111111111/VDD', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.a6_111111111111_vis_voltage_difference', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'VIS voltage difference', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'voltage_vis', + 'unique_id': '/A6.111111111111/vis', + 'unit_of_measurement': , + }), + ]) +# --- +# name: test_sensors[A6.111111111111].2 + list([ + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'device_file': '/A6.111111111111/temperature', + 'friendly_name': 'A6.111111111111 Temperature', + 'raw_value': 25.123, + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_temperature', + 'last_changed': , + 'last_updated': , + 'state': '25.1', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'device_file': '/A6.111111111111/humidity', + 'friendly_name': 'A6.111111111111 Humidity', + 'raw_value': 72.7563, + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_humidity', + 'last_changed': , + 'last_updated': , + 'state': '72.8', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'device_file': '/A6.111111111111/HIH3600/humidity', + 'friendly_name': 'A6.111111111111 HIH3600 humidity', + 'raw_value': 73.7563, + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_hih3600_humidity', + 'last_changed': , + 'last_updated': , + 'state': '73.8', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'device_file': '/A6.111111111111/HIH4000/humidity', + 'friendly_name': 'A6.111111111111 HIH4000 humidity', + 'raw_value': 74.7563, + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_hih4000_humidity', + 'last_changed': , + 'last_updated': , + 'state': '74.8', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'device_file': '/A6.111111111111/HIH5030/humidity', + 'friendly_name': 'A6.111111111111 HIH5030 humidity', + 'raw_value': 75.7563, + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_hih5030_humidity', + 'last_changed': , + 'last_updated': , + 'state': '75.8', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'device_file': '/A6.111111111111/HTM1735/humidity', + 'friendly_name': 'A6.111111111111 HTM1735 humidity', + 'raw_value': None, + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_htm1735_humidity', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'pressure', + 'device_file': '/A6.111111111111/B1-R1-A/pressure', + 'friendly_name': 'A6.111111111111 Pressure', + 'raw_value': 969.265, + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_pressure', + 'last_changed': , + 'last_updated': , + 'state': '969.3', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'illuminance', + 'device_file': '/A6.111111111111/S3-R1-A/illuminance', + 'friendly_name': 'A6.111111111111 Illuminance', + 'raw_value': 65.8839, + 'state_class': , + 'unit_of_measurement': 'lx', + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_illuminance', + 'last_changed': , + 'last_updated': , + 'state': '65.9', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'device_file': '/A6.111111111111/VAD', + 'friendly_name': 'A6.111111111111 VAD voltage', + 'raw_value': 2.97, + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_vad_voltage', + 'last_changed': , + 'last_updated': , + 'state': '3.0', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'device_file': '/A6.111111111111/VDD', + 'friendly_name': 'A6.111111111111 VDD voltage', + 'raw_value': 4.74, + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_vdd_voltage', + 'last_changed': , + 'last_updated': , + 'state': '4.7', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'device_file': '/A6.111111111111/vis', + 'friendly_name': 'A6.111111111111 VIS voltage difference', + 'raw_value': 0.12, + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.a6_111111111111_vis_voltage_difference', + 'last_changed': , + 'last_updated': , + 'state': '0.1', + }), + ]) +# --- # name: test_sensors[EF.111111111111] list([ DeviceRegistryEntrySnapshot({ diff --git a/tests/components/onewire/snapshots/test_switch.ambr b/tests/components/onewire/snapshots/test_switch.ambr index 8fbb977948b..fe553ac1bfb 100644 --- a/tests/components/onewire/snapshots/test_switch.ambr +++ b/tests/components/onewire/snapshots/test_switch.ambr @@ -1751,6 +1751,89 @@ list([ ]) # --- +# name: test_switches[A6.111111111111] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'onewire', + 'A6.111111111111', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Maxim Integrated', + 'model': 'DS2438', + 'name': 'A6.111111111111', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_switches[A6.111111111111].1 + list([ + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.a6_111111111111_current_a_d_control', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Current A/D control', + 'platform': 'onewire', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'iad', + 'unique_id': '/A6.111111111111/IAD', + 'unit_of_measurement': None, + }), + ]) +# --- +# name: test_switches[A6.111111111111].2 + list([ + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_file': '/A6.111111111111/IAD', + 'friendly_name': 'A6.111111111111 Current A/D control', + 'raw_value': 1.0, + }), + 'context': , + 'entity_id': 'switch.a6_111111111111_current_a_d_control', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }), + ]) +# --- # name: test_switches[EF.111111111111] list([ DeviceRegistryEntrySnapshot({ From 4f4391bd09874ed740c28c9f5a4420285635b8fc Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Tue, 12 Mar 2024 08:25:35 +0100 Subject: [PATCH 0819/1691] Add missing device info to Husqvarna Automower (#113090) * Add missing DeviceInfo to Husqvarna Automower * add a test * Adress review * Update homeassistant/components/husqvarna_automower/entity.py * fix url --------- Co-authored-by: Joost Lekkerkerker --- .../components/husqvarna_automower/const.py | 1 - .../husqvarna_automower/coordinator.py | 2 ++ .../components/husqvarna_automower/entity.py | 9 +++++- .../snapshots/test_init.ambr | 31 +++++++++++++++++++ .../husqvarna_automower/test_init.py | 21 +++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/components/husqvarna_automower/snapshots/test_init.ambr diff --git a/homeassistant/components/husqvarna_automower/const.py b/homeassistant/components/husqvarna_automower/const.py index ab30bae45f2..5e38b354957 100644 --- a/homeassistant/components/husqvarna_automower/const.py +++ b/homeassistant/components/husqvarna_automower/const.py @@ -2,6 +2,5 @@ DOMAIN = "husqvarna_automower" NAME = "Husqvarna Automower" -HUSQVARNA_URL = "https://developer.husqvarnagroup.cloud/login" OAUTH2_AUTHORIZE = "https://api.authentication.husqvarnagroup.dev/v1/oauth2/authorize" OAUTH2_TOKEN = "https://api.authentication.husqvarnagroup.dev/v1/oauth2/token" diff --git a/homeassistant/components/husqvarna_automower/coordinator.py b/homeassistant/components/husqvarna_automower/coordinator.py index 2188725ed76..eb2c06262cf 100644 --- a/homeassistant/components/husqvarna_automower/coordinator.py +++ b/homeassistant/components/husqvarna_automower/coordinator.py @@ -21,6 +21,8 @@ MAX_WS_RECONNECT_TIME = 600 class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttributes]]): """Class to manage fetching Husqvarna data.""" + config_entry: ConfigEntry + def __init__( self, hass: HomeAssistant, api: AutomowerSession, entry: ConfigEntry ) -> None: diff --git a/homeassistant/components/husqvarna_automower/entity.py b/homeassistant/components/husqvarna_automower/entity.py index 2edce942f0c..c33229a5f64 100644 --- a/homeassistant/components/husqvarna_automower/entity.py +++ b/homeassistant/components/husqvarna_automower/entity.py @@ -3,6 +3,7 @@ import logging from aioautomower.model import MowerAttributes +from aioautomower.utils import structure_token from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -12,6 +13,8 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +HUSQVARNA_URL = "https://developer.husqvarnagroup.cloud" + class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]): """Defining the Automower base Entity.""" @@ -26,11 +29,15 @@ class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]): """Initialize AutomowerEntity.""" super().__init__(coordinator) self.mower_id = mower_id + entry = coordinator.config_entry + structured_token = structure_token(entry.data["token"]["access_token"]) self._attr_device_info = DeviceInfo( + configuration_url=f"{HUSQVARNA_URL}/applications/{structured_token.client_id}", identifiers={(DOMAIN, mower_id)}, - name=self.mower_attributes.system.name, manufacturer="Husqvarna", model=self.mower_attributes.system.model, + name=self.mower_attributes.system.name, + serial_number=self.mower_attributes.system.serial_number, suggested_area="Garden", ) diff --git a/tests/components/husqvarna_automower/snapshots/test_init.ambr b/tests/components/husqvarna_automower/snapshots/test_init.ambr new file mode 100644 index 00000000000..63ca15461e1 --- /dev/null +++ b/tests/components/husqvarna_automower/snapshots/test_init.ambr @@ -0,0 +1,31 @@ +# serializer version: 1 +# name: test_device_info + DeviceRegistryEntrySnapshot({ + 'area_id': 'garden', + 'config_entries': , + 'configuration_url': 'https://developer.husqvarnagroup.cloud/applications/433e5fdf-5129-452c-xxxx-fadce3213042', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'husqvarna_automower', + 'c7233734-b219-4287-a173-08e3643f89f0', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Husqvarna', + 'model': '450XH-TEST', + 'name': 'Test Mower 1', + 'name_by_user': None, + 'serial_number': 123, + 'suggested_area': 'Garden', + 'sw_version': None, + 'via_device_id': None, + }) +# --- diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index 5398178e339..3c97a3b2668 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -8,12 +8,15 @@ from unittest.mock import AsyncMock from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError from freezegun.api import FrozenDateTimeFactory import pytest +from syrupy.assertion import SnapshotAssertion from homeassistant.components.husqvarna_automower.const import DOMAIN, OAUTH2_TOKEN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from . import setup_integration +from .const import TEST_MOWER_ID from tests.common import MockConfigEntry, async_fire_time_changed from tests.test_util.aiohttp import AiohttpClientMocker @@ -109,3 +112,21 @@ async def test_websocket_not_available( assert mock_automower_client.auth.websocket_connect.call_count == 2 assert mock_automower_client.start_listening.call_count == 2 assert mock_config_entry.state == ConfigEntryState.LOADED + + +async def test_device_info( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test select platform.""" + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + reg_device = device_registry.async_get_device( + identifiers={(DOMAIN, TEST_MOWER_ID)}, + ) + assert reg_device == snapshot From 6c35ae06a09aca6380a3c1f6ca92693a64d0fa8e Mon Sep 17 00:00:00 2001 From: slyoldfox Date: Tue, 12 Mar 2024 08:34:21 +0100 Subject: [PATCH 0820/1691] Add scheduled mode to renault charge mode (#105967) Add scheduled to renault charge mode - fixes #105751 --- homeassistant/components/renault/icons.json | 3 ++- homeassistant/components/renault/strings.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/renault/icons.json b/homeassistant/components/renault/icons.json index b8bbcdc91cc..75356fda411 100644 --- a/homeassistant/components/renault/icons.json +++ b/homeassistant/components/renault/icons.json @@ -28,7 +28,8 @@ "charge_mode": { "default": "mdi:calendar-remove", "state": { - "schedule_mode": "mdi:calendar-clock" + "schedule_mode": "mdi:calendar-clock", + "scheduled": "mdi:calendar-clock" } } }, diff --git a/homeassistant/components/renault/strings.json b/homeassistant/components/renault/strings.json index 0b0c3d87822..322f7a207d7 100644 --- a/homeassistant/components/renault/strings.json +++ b/homeassistant/components/renault/strings.json @@ -75,7 +75,8 @@ "state": { "always": "Instant", "always_charging": "[%key:component::renault::entity::select::charge_mode::state::always%]", - "schedule_mode": "Planner" + "schedule_mode": "Planner", + "scheduled": "Scheduled" } } }, From d039bd654b9ba987b2963797d4653671361ae6cd Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 12 Mar 2024 08:47:44 +0100 Subject: [PATCH 0821/1691] Add reconfigure flow to homeworks (#112419) * Add reconfigure flow to homeworks * Fix tests * Use async_update_reload_and_abort helper * Try to fix test shutdown --- .../components/homeworks/config_flow.py | 61 ++++++++ .../components/homeworks/strings.json | 7 + .../components/homeworks/test_config_flow.py | 141 +++++++++++++++++- 3 files changed, 208 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homeworks/config_flow.py b/homeassistant/components/homeworks/config_flow.py index 68f6aa37c27..ec65cef970c 100644 --- a/homeassistant/components/homeworks/config_flow.py +++ b/homeassistant/components/homeworks/config_flow.py @@ -430,6 +430,7 @@ DATA_SCHEMA_ADD_CONTROLLER = vol.Schema( **CONTROLLER_EDIT, } ) +DATA_SCHEMA_EDIT_CONTROLLER = vol.Schema(CONTROLLER_EDIT) DATA_SCHEMA_ADD_LIGHT = vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_LIGHT_NAME): TextSelector(), @@ -643,6 +644,66 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_show_form(step_id="import_finish", data_schema=vol.Schema({})) + async def _validate_edit_controller( + self, user_input: dict[str, Any] + ) -> dict[str, Any]: + """Validate controller setup.""" + user_input[CONF_PORT] = int(user_input[CONF_PORT]) + + our_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + assert our_entry + other_entries = self._async_current_entries() + for entry in other_entries: + if entry.entry_id == our_entry.entry_id: + continue + if ( + user_input[CONF_HOST] == entry.options[CONF_HOST] + and user_input[CONF_PORT] == entry.options[CONF_PORT] + ): + raise SchemaFlowError("duplicated_host_port") + + await _try_connection(user_input) + return user_input + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a reconfigure flow.""" + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + assert entry + + errors = {} + suggested_values = { + CONF_HOST: entry.options[CONF_HOST], + CONF_PORT: entry.options[CONF_PORT], + } + + if user_input: + suggested_values = { + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], + } + try: + await self._validate_edit_controller(user_input) + except SchemaFlowError as err: + errors["base"] = str(err) + else: + new_options = entry.options | { + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], + } + return self.async_update_reload_and_abort( + entry, options=new_options, reason="reconfigure_successful" + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + DATA_SCHEMA_EDIT_CONTROLLER, suggested_values + ), + errors=errors, + ) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: diff --git a/homeassistant/components/homeworks/strings.json b/homeassistant/components/homeworks/strings.json index 2a1ddb44b44..3f1b6d3cc23 100644 --- a/homeassistant/components/homeworks/strings.json +++ b/homeassistant/components/homeworks/strings.json @@ -19,6 +19,13 @@ "name": "[%key:component::homeworks::config::step::user::data_description::name%]" } }, + "reconfigure": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" + }, + "description": "Modify a Lutron Homeworks controller connection settings" + }, "user": { "data": { "host": "[%key:common::config_flow::data::host%]", diff --git a/tests/components/homeworks/test_config_flow.py b/tests/components/homeworks/test_config_flow.py index a50e89dcc5f..00980b2f60c 100644 --- a/tests/components/homeworks/test_config_flow.py +++ b/tests/components/homeworks/test_config_flow.py @@ -18,7 +18,7 @@ from homeassistant.components.homeworks.const import ( DOMAIN, ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_RECONFIGURE, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -261,6 +261,145 @@ async def test_import_flow_controller_id_exists( assert result["errors"] == {"base": "duplicated_controller_id"} +async def test_reconfigure_flow( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test reconfigure flow.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_RECONFIGURE, "entry_id": mock_config_entry.entry_id}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.2", + CONF_PORT: 1234, + }, + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.options == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}, + ], + "host": "192.168.0.2", + "keypads": [ + { + "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], + "name": "Foyer Keypad", + }, + ], + "port": 1234, + } + + +async def test_reconfigure_flow_flow_duplicate( + hass: HomeAssistant, mock_homeworks: MagicMock +) -> None: + """Test reconfigure flow.""" + entry1 = MockConfigEntry( + domain=DOMAIN, + data={}, + options={ + "controller_id": "controller_1", + "host": "192.168.0.1", + "port": 1234, + }, + ) + entry1.add_to_hass(hass) + entry2 = MockConfigEntry( + domain=DOMAIN, + data={}, + options={ + "controller_id": "controller_2", + "host": "192.168.0.2", + "port": 1234, + }, + ) + entry2.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_RECONFIGURE, "entry_id": entry1.entry_id}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.2", + CONF_PORT: 1234, + }, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "reconfigure" + assert result["errors"] == {"base": "duplicated_host_port"} + + +async def test_reconfigure_flow_flow_no_change( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_homeworks: MagicMock +) -> None: + """Test reconfigure flow.""" + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_RECONFIGURE, "entry_id": mock_config_entry.entry_id}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "192.168.0.1", + CONF_PORT: 1234, + }, + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.options == { + "controller_id": "main_controller", + "dimmers": [ + {"addr": "[02:08:01:01]", "name": "Foyer Sconces", "rate": 1.0}, + ], + "host": "192.168.0.1", + "keypads": [ + { + "addr": "[02:08:02:01]", + "buttons": [ + { + "name": "Morning", + "number": 1, + "release_delay": None, + }, + {"name": "Relax", "number": 2, "release_delay": None}, + {"name": "Dim up", "number": 3, "release_delay": 0.2}, + ], + "name": "Foyer Keypad", + } + ], + "port": 1234, + } + + async def test_options_add_light_flow( hass: HomeAssistant, mock_empty_config_entry: MockConfigEntry, From 433d3a510620f0a3e8395b0b837d5050ca941786 Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Tue, 12 Mar 2024 08:55:21 +0100 Subject: [PATCH 0822/1691] bump pytedee_async to 0.2.16 (#113135) --- homeassistant/components/tedee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json index a3e29e1b40f..99e8488e341 100644 --- a/homeassistant/components/tedee/manifest.json +++ b/homeassistant/components/tedee/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/tedee", "iot_class": "local_push", - "requirements": ["pytedee-async==0.2.15"] + "requirements": ["pytedee-async==0.2.16"] } diff --git a/requirements_all.txt b/requirements_all.txt index ed7bc4ea5b9..11015e3a89a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2176,7 +2176,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.15 +pytedee-async==0.2.16 # homeassistant.components.tfiac pytfiac==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 56db4174f2f..3a93c3396a3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1694,7 +1694,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.15 +pytedee-async==0.2.16 # homeassistant.components.motionmount python-MotionMount==0.3.1 From 012291a1f3d9478be4c6d7c149ac59e472154e0e Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 12 Mar 2024 09:07:20 +0100 Subject: [PATCH 0823/1691] Fix google_asssistant sensor state reporting (#112838) * Fix post google_assistant sensor values as float not string * Fix aqi reporting and improve tests * Fix _air_quality_description_for_aqi and test --- .../components/google_assistant/trait.py | 15 ++++--- .../components/google_assistant/test_trait.py | 40 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index bf68ebd758c..dd1e0cb3409 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -2707,10 +2707,9 @@ class SensorStateTrait(_Trait): name = TRAIT_SENSOR_STATE commands: list[str] = [] - def _air_quality_description_for_aqi(self, aqi): - if aqi is None or aqi.isnumeric() is False: + def _air_quality_description_for_aqi(self, aqi: float | None) -> str: + if aqi is None or aqi < 0: return "unknown" - aqi = int(aqi) if aqi <= 50: return "healthy" if aqi <= 100: @@ -2765,11 +2764,17 @@ class SensorStateTrait(_Trait): if device_class is None or data is None: return {} - sensor_data = {"name": data[0], "rawValue": self.state.state} + try: + value = float(self.state.state) + except ValueError: + value = None + if self.state.state == STATE_UNKNOWN: + value = None + sensor_data = {"name": data[0], "rawValue": value} if device_class == sensor.SensorDeviceClass.AQI: sensor_data["currentSensorState"] = self._air_quality_description_for_aqi( - self.state.state + value ) return {"currentSensorStateData": [sensor_data]} diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index b9a68b90a26..a285febe5b3 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,6 +1,7 @@ """Tests for the Google Assistant traits.""" from datetime import datetime, timedelta +from typing import Any from unittest.mock import ANY, patch from freezegun.api import FrozenDateTimeFactory @@ -3926,16 +3927,15 @@ async def test_air_quality_description_for_aqi(hass: HomeAssistant) -> None: BASIC_CONFIG, ) - assert trt._air_quality_description_for_aqi("0") == "healthy" - assert trt._air_quality_description_for_aqi("75") == "moderate" + assert trt._air_quality_description_for_aqi(0) == "healthy" + assert trt._air_quality_description_for_aqi(75) == "moderate" assert ( - trt._air_quality_description_for_aqi("125") == "unhealthy for sensitive groups" + trt._air_quality_description_for_aqi(125.0) == "unhealthy for sensitive groups" ) - assert trt._air_quality_description_for_aqi("175") == "unhealthy" - assert trt._air_quality_description_for_aqi("250") == "very unhealthy" - assert trt._air_quality_description_for_aqi("350") == "hazardous" - assert trt._air_quality_description_for_aqi("-1") == "unknown" - assert trt._air_quality_description_for_aqi("non-numeric") == "unknown" + assert trt._air_quality_description_for_aqi(175) == "unhealthy" + assert trt._air_quality_description_for_aqi(250) == "very unhealthy" + assert trt._air_quality_description_for_aqi(350) == "hazardous" + assert trt._air_quality_description_for_aqi(-1) == "unknown" async def test_null_device_class(hass: HomeAssistant) -> None: @@ -3956,7 +3956,19 @@ async def test_null_device_class(hass: HomeAssistant) -> None: assert trt.query_attributes() == {} -async def test_sensorstate(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + ("value", "published", "aqi"), + [ + (100.0, 100.0, "moderate"), + (10.0, 10.0, "healthy"), + (0, 0.0, "healthy"), + ("", None, "unknown"), + ("unknown", None, "unknown"), + ], +) +async def test_sensorstate( + hass: HomeAssistant, value: Any, published: Any, aqi: Any +) -> None: """Test SensorState trait support for sensor domain.""" sensor_types = { sensor.SensorDeviceClass.AQI: ("AirQuality", "AQI"), @@ -3978,7 +3990,7 @@ async def test_sensorstate(hass: HomeAssistant) -> None: hass, State( "sensor.test", - 100.0, + value, { "device_class": sensor_type, }, @@ -4024,16 +4036,14 @@ async def test_sensorstate(hass: HomeAssistant) -> None: "currentSensorStateData": [ { "name": name, - "currentSensorState": trt._air_quality_description_for_aqi( - trt.state.state - ), - "rawValue": trt.state.state, + "currentSensorState": aqi, + "rawValue": published, }, ] } else: assert trt.query_attributes() == { - "currentSensorStateData": [{"name": name, "rawValue": trt.state.state}] + "currentSensorStateData": [{"name": name, "rawValue": published}] } assert helpers.get_google_type(sensor.DOMAIN, None) is not None From e28d4f0eae3f245e8a9a10561619fe63f85ec3a0 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 12 Mar 2024 09:08:03 +0100 Subject: [PATCH 0824/1691] Validate state_class with last_reset_value_template for mqtt sensors (#113099) --- homeassistant/components/mqtt/sensor.py | 18 ++++++++ tests/components/mqtt/test_sensor.py | 61 +++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index bb63ae5fef0..9ba6308e07c 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -18,6 +18,7 @@ from homeassistant.components.sensor import ( RestoreSensor, SensorDeviceClass, SensorExtraStoredData, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -84,11 +85,27 @@ _PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) + +def validate_sensor_state_class_config(config: ConfigType) -> ConfigType: + """Validate the sensor state class config.""" + if ( + CONF_LAST_RESET_VALUE_TEMPLATE in config + and (state_class := config.get(CONF_STATE_CLASS)) != SensorStateClass.TOTAL + ): + raise vol.Invalid( + f"The option `{CONF_LAST_RESET_VALUE_TEMPLATE}` cannot be used " + f"together with state class `{state_class}`" + ) + + return config + + PLATFORM_SCHEMA_MODERN = vol.All( # Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840 # Removed in HA Core 2023.6.0 cv.removed(CONF_LAST_RESET_TOPIC), _PLATFORM_SCHEMA_BASE, + validate_sensor_state_class_config, ) DISCOVERY_SCHEMA = vol.All( @@ -96,6 +113,7 @@ DISCOVERY_SCHEMA = vol.All( # Removed in HA Core 2023.6.0 cv.removed(CONF_LAST_RESET_TOPIC), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), + validate_sensor_state_class_config, ) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index d88bd3a544a..5ab4b660963 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -1449,6 +1449,7 @@ async def test_entity_name( DEFAULT_CONFIG, ( { + "state_class": "total", "availability_topic": "availability-topic", "json_attributes_topic": "json-attributes-topic", "value_template": "{{ value_json.state }}", @@ -1491,6 +1492,7 @@ async def test_skipped_async_ha_write_state( DEFAULT_CONFIG, ( { + "state_class": "total", "value_template": "{{ value_json.some_var * 1 }}", "last_reset_value_template": "{{ value_json.some_var * 2 }}", }, @@ -1510,3 +1512,62 @@ async def test_value_template_fails( "TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template" in caplog.text ) + + +@pytest.mark.parametrize( + "hass_config", + [ + help_custom_config( + sensor.DOMAIN, + DEFAULT_CONFIG, + ( + { + "state_class": "total_increasing", + "last_reset_value_template": "{{ value_json.last_reset }}", + }, + ), + ), + help_custom_config( + sensor.DOMAIN, + DEFAULT_CONFIG, + ( + { + "state_class": "measurement", + "last_reset_value_template": "{{ value_json.last_reset }}", + }, + ), + ), + help_custom_config( + sensor.DOMAIN, + DEFAULT_CONFIG, + ( + { + "last_reset_value_template": "{{ value_json.last_reset }}", + }, + ), + ), + ], +) +async def test_value_incorrect_state_class_config( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + hass_config: ConfigType, +) -> None: + """Test a sensor config with incorrect state_class config fails from yaml or discovery.""" + await mqtt_mock_entry() + assert ( + "The option `last_reset_value_template` cannot be used together with state class" + in caplog.text + ) + caplog.clear() + + config_payload = hass_config[mqtt.DOMAIN][sensor.DOMAIN][0] + async_fire_mqtt_message( + hass, "homeassistant/sensor/bla/config", json.dumps(config_payload) + ) + await hass.async_block_till_done() + assert ( + "The option `last_reset_value_template` cannot be used together with state class" + in caplog.text + ) From d929efbae0695f7998066926b59e8efa4e92f99a Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:37:48 +0100 Subject: [PATCH 0825/1691] Include pytedee_async logger in tedee integration (#112590) add pytedee logger --- homeassistant/components/tedee/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json index 99e8488e341..1f2a2405a44 100644 --- a/homeassistant/components/tedee/manifest.json +++ b/homeassistant/components/tedee/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/tedee", "iot_class": "local_push", + "loggers": ["pytedee_async"], "requirements": ["pytedee-async==0.2.16"] } From 50ac3c8cfdf9795ef8e26dd8e113b3890a77d074 Mon Sep 17 00:00:00 2001 From: Jevgeni Kiski Date: Tue, 12 Mar 2024 12:32:42 +0200 Subject: [PATCH 0826/1691] Update vallox_websocket_api to 5.1.1 (#113139) Bump vallox-websocket-api==5.1.1 --- homeassistant/components/vallox/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vallox/manifest.json b/homeassistant/components/vallox/manifest.json index 46cb765cc5e..9a57358cd14 100644 --- a/homeassistant/components/vallox/manifest.json +++ b/homeassistant/components/vallox/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/vallox", "iot_class": "local_polling", "loggers": ["vallox_websocket_api"], - "requirements": ["vallox-websocket-api==5.1.0"] + "requirements": ["vallox-websocket-api==5.1.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 11015e3a89a..ce842ce7c35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2784,7 +2784,7 @@ uvcclient==0.11.0 vacuum-map-parser-roborock==0.1.1 # homeassistant.components.vallox -vallox-websocket-api==5.1.0 +vallox-websocket-api==5.1.1 # homeassistant.components.rdw vehicle==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3a93c3396a3..418eddcae4f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2134,7 +2134,7 @@ uvcclient==0.11.0 vacuum-map-parser-roborock==0.1.1 # homeassistant.components.vallox -vallox-websocket-api==5.1.0 +vallox-websocket-api==5.1.1 # homeassistant.components.rdw vehicle==2.2.1 From 46ab4bbf322957bad8365a83f8294d1ae392658e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:52:16 +0100 Subject: [PATCH 0827/1691] Bump Wandalen/wretry.action from 1.4.8 to 1.4.9 (#113132) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 142fa100408..d6a440409cd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1089,7 +1089,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.4.8 + uses: Wandalen/wretry.action@v1.4.9 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1100,7 +1100,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.4.8 + uses: Wandalen/wretry.action@v1.4.9 with: action: codecov/codecov-action@v3.1.3 with: | From b3dedb3efb36eafe06ce48fe94b59e37ce5f9257 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 02:38:21 -1000 Subject: [PATCH 0828/1691] Convert sets to list before passing to the JSON serializer in the registries (#113133) There were a few places we were missing the set to list conversions in the registries. We do this before its cached to avoid the JSON serializer having to fallback to the default method every time since its expensive to switch back from the native code into python context for every set. --- homeassistant/helpers/device_registry.py | 3 +++ homeassistant/helpers/entity_registry.py | 28 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index e823f356f4e..d751efdffe6 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -263,6 +263,9 @@ class DeviceEntry: @property def dict_repr(self) -> dict[str, Any]: """Return a dict representation of the entry.""" + # Convert sets and tuples to lists + # so the JSON serializer does not have to do + # it every time return { "area_id": self.area_id, "configuration_url": self.configuration_url, diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 84d1bd43e2e..413f227c7cc 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -136,11 +136,12 @@ EntityOptionsType = Mapping[str, Mapping[str, Any]] ReadOnlyEntityOptionsType = ReadOnlyDict[str, ReadOnlyDict[str, Any]] DISPLAY_DICT_OPTIONAL = ( - ("ai", "area_id"), - ("lb", "labels"), - ("di", "device_id"), - ("ic", "icon"), - ("tk", "translation_key"), + # key, attr_name, convert_to_list + ("ai", "area_id", False), + ("lb", "labels", True), + ("di", "device_id", False), + ("ic", "icon", False), + ("tk", "translation_key", False), ) @@ -213,9 +214,12 @@ class RegistryEntry: Returns None if there's no data needed for display. """ display_dict: dict[str, Any] = {"ei": self.entity_id, "pl": self.platform} - for key, attr_name in DISPLAY_DICT_OPTIONAL: + for key, attr_name, convert_list in DISPLAY_DICT_OPTIONAL: if (attr_val := getattr(self, attr_name)) is not None: - display_dict[key] = attr_val + # Convert sets and tuples to lists + # so the JSON serializer does not have to do + # it every time + display_dict[key] = list(attr_val) if convert_list else attr_val if (category := self.entity_category) is not None: display_dict["ec"] = ENTITY_CATEGORY_VALUE_TO_INDEX[category] if self.hidden_by is not None: @@ -253,6 +257,9 @@ class RegistryEntry: @cached_property def as_partial_dict(self) -> dict[str, Any]: """Return a partial dict representation of the entry.""" + # Convert sets and tuples to lists + # so the JSON serializer does not have to do + # it every time return { "area_id": self.area_id, "config_entry_id": self.config_entry_id, @@ -264,7 +271,7 @@ class RegistryEntry: "hidden_by": self.hidden_by, "icon": self.icon, "id": self.id, - "labels": self.labels, + "labels": list(self.labels), "name": self.name, "options": self.options, "original_name": self.original_name, @@ -276,9 +283,12 @@ class RegistryEntry: @cached_property def extended_dict(self) -> dict[str, Any]: """Return a extended dict representation of the entry.""" + # Convert sets and tuples to lists + # so the JSON serializer does not have to do + # it every time return { **self.as_partial_dict, - "aliases": self.aliases, + "aliases": list(self.aliases), "capabilities": self.capabilities, "device_class": self.device_class, "original_device_class": self.original_device_class, From 120525e94f232f9a192099b71f05243e80cc7ad9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 02:41:12 -1000 Subject: [PATCH 0829/1691] Add the ability to create Debouncer tasks as background tasks (#113128) * Add the ability to Debouncer tasks in the background This is a more general solution as a followup to https://github.com/home-assistant/core/pull/112652#discussion_r1517159607 * Add the ability to Debouncer tasks in the background This is a more general solution as a followup to https://github.com/home-assistant/core/pull/112652#discussion_r1517159607 * fix --- homeassistant/helpers/debounce.py | 25 +++++++++++++++++------- tests/helpers/test_debounce.py | 32 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/debounce.py b/homeassistant/helpers/debounce.py index 2fe42e19a67..de8f5eb4d53 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -23,6 +23,7 @@ class Debouncer(Generic[_R_co]): cooldown: float, immediate: bool, function: Callable[[], _R_co] | None = None, + background: bool = False, ) -> None: """Initialize debounce. @@ -38,6 +39,7 @@ class Debouncer(Generic[_R_co]): self._timer_task: asyncio.TimerHandle | None = None self._execute_at_end_of_timer: bool = False self._execute_lock = asyncio.Lock() + self._background = background self._job: HassJob[[], _R_co] | None = ( None if function is None @@ -109,7 +111,9 @@ class Debouncer(Generic[_R_co]): assert self._job is not None try: - if task := self.hass.async_run_hass_job(self._job): + if task := self.hass.async_run_hass_job( + self._job, background=self._background + ): await task finally: self._schedule_timer() @@ -130,7 +134,9 @@ class Debouncer(Generic[_R_co]): return try: - if task := self.hass.async_run_hass_job(self._job): + if task := self.hass.async_run_hass_job( + self._job, background=self._background + ): await task except Exception: # pylint: disable=broad-except self.logger.exception("Unexpected exception from %s", self.function) @@ -157,13 +163,18 @@ class Debouncer(Generic[_R_co]): def _on_debounce(self) -> None: """Create job task, but only if pending.""" self._timer_task = None - if self._execute_at_end_of_timer: - self._execute_at_end_of_timer = False + if not self._execute_at_end_of_timer: + return + self._execute_at_end_of_timer = False + name = f"debouncer {self._job} finish cooldown={self.cooldown}, immediate={self.immediate}" + if not self._background: self.hass.async_create_task( - self._handle_timer_finish(), - f"debouncer {self._job} finish cooldown={self.cooldown}, immediate={self.immediate}", - eager_start=True, + self._handle_timer_finish(), name, eager_start=True ) + return + self.hass.async_create_background_task( + self._handle_timer_finish(), name, eager_start=True + ) @callback def _schedule_timer(self) -> None: diff --git a/tests/helpers/test_debounce.py b/tests/helpers/test_debounce.py index 24f243086e5..84b3d19b6d7 100644 --- a/tests/helpers/test_debounce.py +++ b/tests/helpers/test_debounce.py @@ -497,3 +497,35 @@ async def test_shutdown(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) - assert len(calls) == 1 assert debouncer._timer_task is None + + +async def test_background( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test background tasks are created when background is True.""" + calls = [] + + async def _func() -> None: + await asyncio.sleep(0.1) + calls.append(None) + + debouncer = debounce.Debouncer( + hass, _LOGGER, cooldown=0.05, immediate=True, function=_func, background=True + ) + + await debouncer.async_call() + assert len(calls) == 1 + + debouncer.async_schedule_call() + assert len(calls) == 1 + + async_fire_time_changed(hass, utcnow() + timedelta(seconds=1)) + await hass.async_block_till_done(wait_background_tasks=False) + assert len(calls) == 1 + + await hass.async_block_till_done(wait_background_tasks=True) + assert len(calls) == 2 + + async_fire_time_changed(hass, utcnow() + timedelta(seconds=1)) + await hass.async_block_till_done(wait_background_tasks=False) + assert len(calls) == 2 From 556855f54e642ad0301bf02e3cfd9802912a2e29 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 12 Mar 2024 07:50:06 -0500 Subject: [PATCH 0830/1691] Add device_id to sentence trigger and external conversation APIs (#113094) * Add device_id to sentence trigger and external conversation APIs * Remove device_id from external API * Update tests/components/conversation/snapshots/test_init.ambr --------- Co-authored-by: Paulus Schoutsen --- .../components/conversation/default_agent.py | 6 ++- .../components/conversation/trigger.py | 13 ++++-- tests/components/conversation/test_init.py | 9 ++-- tests/components/conversation/test_trigger.py | 46 +++++++++++++++++-- 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index b07919c9d5c..96b0565ebd3 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -53,7 +53,9 @@ _DEFAULT_ERROR_TEXT = "Sorry, I couldn't understand that" _ENTITY_REGISTRY_UPDATE_FIELDS = ["aliases", "name", "original_name"] REGEX_TYPE = type(re.compile("")) -TRIGGER_CALLBACK_TYPE = Callable[[str, RecognizeResult], Awaitable[str | None]] +TRIGGER_CALLBACK_TYPE = Callable[ + [str, RecognizeResult, str | None], Awaitable[str | None] +] METADATA_CUSTOM_SENTENCE = "hass_custom_sentence" METADATA_CUSTOM_FILE = "hass_custom_file" @@ -224,7 +226,7 @@ class DefaultAgent(AbstractConversationAgent): # Gather callback responses in parallel trigger_callbacks = [ self._trigger_sentences[trigger_id].callback( - result.sentence, trigger_result + result.sentence, trigger_result, user_input.device_id ) for trigger_id, trigger_result in result.matched_triggers.items() ] diff --git a/homeassistant/components/conversation/trigger.py b/homeassistant/components/conversation/trigger.py index 082d798a69e..0fadc458352 100644 --- a/homeassistant/components/conversation/trigger.py +++ b/homeassistant/components/conversation/trigger.py @@ -62,7 +62,9 @@ async def async_attach_trigger( job = HassJob(action) - async def call_action(sentence: str, result: RecognizeResult) -> str | None: + async def call_action( + sentence: str, result: RecognizeResult, device_id: str | None + ) -> str | None: """Call action with right context.""" # Add slot values as extra trigger data @@ -70,9 +72,11 @@ async def async_attach_trigger( entity_name: { "name": entity_name, "text": entity.text.strip(), # remove whitespace - "value": entity.value.strip() - if isinstance(entity.value, str) - else entity.value, + "value": ( + entity.value.strip() + if isinstance(entity.value, str) + else entity.value + ), } for entity_name, entity in result.entities.items() } @@ -85,6 +89,7 @@ async def async_attach_trigger( "slots": { # direct access to values entity_name: entity["value"] for entity_name, entity in details.items() }, + "device_id": device_id, } # Wait for the automation to complete diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 7129176adc3..3b9e8e39a82 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -535,9 +535,12 @@ async def test_turn_on_intent( async def test_service_fails(hass: HomeAssistant, init_components) -> None: """Test calling the turn on intent.""" - with pytest.raises(HomeAssistantError), patch( - "homeassistant.components.conversation.async_converse", - side_effect=intent.IntentHandleError, + with ( + pytest.raises(HomeAssistantError), + patch( + "homeassistant.components.conversation.async_converse", + side_effect=intent.IntentHandleError, + ), ): await hass.services.async_call( "conversation", diff --git a/tests/components/conversation/test_trigger.py b/tests/components/conversation/test_trigger.py index f95976a4638..221789b49e0 100644 --- a/tests/components/conversation/test_trigger.py +++ b/tests/components/conversation/test_trigger.py @@ -5,7 +5,8 @@ import logging import pytest import voluptuous as vol -from homeassistant.core import HomeAssistant +from homeassistant.components import conversation +from homeassistant.core import Context, HomeAssistant from homeassistant.helpers import trigger from homeassistant.setup import async_setup_component @@ -51,9 +52,7 @@ async def test_if_fires_on_event(hass: HomeAssistant, calls, setup_comp) -> None service_response = await hass.services.async_call( "conversation", "process", - { - "text": "Ha ha ha", - }, + {"text": "Ha ha ha"}, blocking=True, return_response=True, ) @@ -69,6 +68,7 @@ async def test_if_fires_on_event(hass: HomeAssistant, calls, setup_comp) -> None "sentence": "Ha ha ha", "slots": {}, "details": {}, + "device_id": None, } @@ -160,6 +160,7 @@ async def test_response_same_sentence(hass: HomeAssistant, calls, setup_comp) -> "sentence": "test sentence", "slots": {}, "details": {}, + "device_id": None, } @@ -311,6 +312,7 @@ async def test_same_trigger_multiple_sentences( "sentence": "hello", "slots": {}, "details": {}, + "device_id": None, } @@ -488,4 +490,40 @@ async def test_wildcards(hass: HomeAssistant, calls, setup_comp) -> None: "value": "the beatles", }, }, + "device_id": None, } + + +async def test_trigger_with_device_id(hass: HomeAssistant) -> None: + """Test that a trigger receives a device_id.""" + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, "conversation", {}) + assert await async_setup_component( + hass, + "automation", + { + "automation": { + "trigger": { + "platform": "conversation", + "command": ["test sentence"], + }, + "action": { + "set_conversation_response": "{{ trigger.device_id }}", + }, + } + }, + ) + + agent = await conversation._get_agent_manager(hass).async_get_agent() + assert isinstance(agent, conversation.DefaultAgent) + + result = await agent.async_process( + conversation.ConversationInput( + text="test sentence", + context=Context(), + conversation_id=None, + device_id="my_device", + language=hass.config.language, + ) + ) + assert result.response.speech["plain"]["speech"] == "my_device" From a8d1d9048438ce47f4900ad6f39b3bb603a2b899 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 13:57:19 +0100 Subject: [PATCH 0831/1691] Bump yt-dlp to 2024.03.10 (#109763) --- homeassistant/components/media_extractor/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 111509c1f31..c86099a9ea4 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -7,5 +7,5 @@ "iot_class": "calculated", "loggers": ["yt_dlp"], "quality_scale": "internal", - "requirements": ["yt-dlp==2023.11.16"] + "requirements": ["yt-dlp==2024.03.10"] } diff --git a/requirements_all.txt b/requirements_all.txt index ce842ce7c35..5eb675a6df7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2911,7 +2911,7 @@ youless-api==1.0.1 youtubeaio==1.1.5 # homeassistant.components.media_extractor -yt-dlp==2023.11.16 +yt-dlp==2024.03.10 # homeassistant.components.zamg zamg==0.3.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 418eddcae4f..30d8f19d146 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2243,7 +2243,7 @@ youless-api==1.0.1 youtubeaio==1.1.5 # homeassistant.components.media_extractor -yt-dlp==2023.11.16 +yt-dlp==2024.03.10 # homeassistant.components.zamg zamg==0.3.6 From 42574fe4989593f04ec43f1041ab5a2bb7188a25 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 12 Mar 2024 14:04:42 +0100 Subject: [PATCH 0832/1691] Remove deprecated `hass.components` usage in device_sun_light_trigger (#111881) --- .../device_sun_light_trigger/__init__.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/device_sun_light_trigger/__init__.py b/homeassistant/components/device_sun_light_trigger/__init__.py index f1a3dc7e78b..861a634eda7 100644 --- a/homeassistant/components/device_sun_light_trigger/__init__.py +++ b/homeassistant/components/device_sun_light_trigger/__init__.py @@ -5,11 +5,18 @@ import logging import voluptuous as vol +from homeassistant.components.device_tracker import ( + DOMAIN as DOMAIN_DEVICE_TRACKER, + is_on as device_tracker_is_on, +) +from homeassistant.components.group import get_entity_ids as group_get_entity_ids from homeassistant.components.light import ( ATTR_PROFILE, ATTR_TRANSITION, DOMAIN as DOMAIN_LIGHT, + is_on as light_is_on, ) +from homeassistant.components.person import DOMAIN as DOMAIN_PERSON from homeassistant.const import ( ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, @@ -87,16 +94,16 @@ async def activate_automation( # noqa: C901 ): """Activate the automation.""" logger = logging.getLogger(__name__) - device_tracker = hass.components.device_tracker - group = hass.components.group - light = hass.components.light - person = hass.components.person if device_group is None: - device_entity_ids = hass.states.async_entity_ids(device_tracker.DOMAIN) + device_entity_ids = hass.states.async_entity_ids(DOMAIN_DEVICE_TRACKER) else: - device_entity_ids = group.get_entity_ids(device_group, device_tracker.DOMAIN) - device_entity_ids.extend(group.get_entity_ids(device_group, person.DOMAIN)) + device_entity_ids = group_get_entity_ids( + hass, device_group, DOMAIN_DEVICE_TRACKER + ) + device_entity_ids.extend( + group_get_entity_ids(hass, device_group, DOMAIN_PERSON) + ) if not device_entity_ids: logger.error("No devices found to track") @@ -104,9 +111,9 @@ async def activate_automation( # noqa: C901 # Get the light IDs from the specified group if light_group is None: - light_ids = hass.states.async_entity_ids(light.DOMAIN) + light_ids = hass.states.async_entity_ids(DOMAIN_LIGHT) else: - light_ids = group.get_entity_ids(light_group, light.DOMAIN) + light_ids = group_get_entity_ids(hass, light_group, DOMAIN_LIGHT) if not light_ids: logger.error("No lights found to turn on") @@ -115,12 +122,12 @@ async def activate_automation( # noqa: C901 @callback def anyone_home(): """Test if anyone is home.""" - return any(device_tracker.is_on(dt_id) for dt_id in device_entity_ids) + return any(device_tracker_is_on(hass, dt_id) for dt_id in device_entity_ids) @callback def any_light_on(): """Test if any light on.""" - return any(light.is_on(light_id) for light_id in light_ids) + return any(light_is_on(hass, light_id) for light_id in light_ids) def calc_time_for_light_when_sunset(): """Calculate the time when to start fading lights in when sun sets. @@ -136,7 +143,7 @@ async def activate_automation( # noqa: C901 async def async_turn_on_before_sunset(light_id): """Turn on lights.""" - if not anyone_home() or light.is_on(light_id): + if not anyone_home() or light_is_on(hass, light_id): return await hass.services.async_call( DOMAIN_LIGHT, From 5e530fc42ebfa6a648148480abb193a64ad4221d Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:05:14 +0000 Subject: [PATCH 0833/1691] Add motion detection enable/disable to ring camera platform (#108789) * Add motion detection enable/disable to ring camera platform * Write ha state directly Co-authored-by: J. Nick Koston * Parametrize on off state tests * Add tests for errors on setting motion detection --------- Co-authored-by: J. Nick Koston --- homeassistant/components/ring/camera.py | 30 ++++ tests/components/ring/conftest.py | 7 + tests/components/ring/fixtures/devices.json | 3 + .../ring/fixtures/devices_updated.json | 3 + .../ring/snapshots/test_diagnostics.ambr | 3 + tests/components/ring/test_camera.py | 140 ++++++++++++++++++ 6 files changed, 186 insertions(+) create mode 100644 tests/components/ring/test_camera.py diff --git a/homeassistant/components/ring/camera.py b/homeassistant/components/ring/camera.py index 9221430413a..7cbe3559ab2 100644 --- a/homeassistant/components/ring/camera.py +++ b/homeassistant/components/ring/camera.py @@ -22,6 +22,7 @@ from .coordinator import RingDataCoordinator from .entity import RingEntity, exception_wrap FORCE_REFRESH_INTERVAL = timedelta(minutes=3) +MOTION_DETECTION_CAPABILITY = "motion_detection" _LOGGER = logging.getLogger(__name__) @@ -67,6 +68,8 @@ class RingCam(RingEntity, Camera): self._image = None self._expires_at = dt_util.utcnow() - FORCE_REFRESH_INTERVAL self._attr_unique_id = device.id + if device.has_capability(MOTION_DETECTION_CAPABILITY): + self._attr_motion_detection_enabled = device.motion_detection @callback def _handle_coordinator_update(self): @@ -131,6 +134,13 @@ class RingCam(RingEntity, Camera): async def async_update(self) -> None: """Update camera entity and refresh attributes.""" + if ( + self._device.has_capability(MOTION_DETECTION_CAPABILITY) + and self._attr_motion_detection_enabled != self._device.motion_detection + ): + self._attr_motion_detection_enabled = self._device.motion_detection + self.async_write_ha_state() + if self._last_event is None: return @@ -152,3 +162,23 @@ class RingCam(RingEntity, Camera): @exception_wrap def _get_video(self) -> str: return self._device.recording_url(self._last_event["id"]) + + @exception_wrap + def _set_motion_detection_enabled(self, new_state): + if not self._device.has_capability(MOTION_DETECTION_CAPABILITY): + _LOGGER.error( + "Entity %s does not have motion detection capability", self.entity_id + ) + return + + self._device.motion_detection = new_state + self._attr_motion_detection_enabled = new_state + self.schedule_update_ha_state(False) + + def enable_motion_detection(self) -> None: + """Enable motion detection in the camera.""" + self._set_motion_detection_enabled(True) + + def disable_motion_detection(self) -> None: + """Disable motion detection in camera.""" + self._set_motion_detection_enabled(False) diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 42b2184f289..758643f912e 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -121,4 +121,11 @@ def requests_mock_fixture(): status_code=200, json={"url": "http://127.0.0.1/foo"}, ) + # Mocks the response for setting properties in settings (i.e. motion_detection) + mock.patch( + re.compile( + r"https:\/\/api\.ring\.com\/devices\/v1\/devices\/\d+\/settings" + ), + text="ok", + ) yield mock diff --git a/tests/components/ring/fixtures/devices.json b/tests/components/ring/fixtures/devices.json index aff234f9726..ae7a62e1bae 100644 --- a/tests/components/ring/fixtures/devices.json +++ b/tests/components/ring/fixtures/devices.json @@ -69,6 +69,7 @@ "enable_vod": true, "live_view_preset_profile": "highest", "live_view_presets": ["low", "middle", "high", "highest"], + "motion_detection_enabled": true, "motion_announcement": false, "motion_snooze_preset_profile": "low", "motion_snooze_presets": ["null", "low", "medium", "high"] @@ -133,6 +134,7 @@ }, "live_view_preset_profile": "highest", "live_view_presets": ["low", "middle", "high", "highest"], + "motion_detection_enabled": false, "motion_announcement": false, "motion_snooze_preset_profile": "low", "motion_snooze_presets": ["none", "low", "medium", "high"], @@ -281,6 +283,7 @@ }, "live_view_preset_profile": "highest", "live_view_presets": ["low", "middle", "high", "highest"], + "motion_detection_enabled": true, "motion_announcement": false, "motion_snooze_preset_profile": "low", "motion_snooze_presets": ["none", "low", "medium", "high"], diff --git a/tests/components/ring/fixtures/devices_updated.json b/tests/components/ring/fixtures/devices_updated.json index 5a4584b72db..01ea2ca25f5 100644 --- a/tests/components/ring/fixtures/devices_updated.json +++ b/tests/components/ring/fixtures/devices_updated.json @@ -69,6 +69,7 @@ "enable_vod": true, "live_view_preset_profile": "highest", "live_view_presets": ["low", "middle", "high", "highest"], + "motion_detection_enabled": true, "motion_announcement": false, "motion_snooze_preset_profile": "low", "motion_snooze_presets": ["null", "low", "medium", "high"] @@ -133,6 +134,7 @@ }, "live_view_preset_profile": "highest", "live_view_presets": ["low", "middle", "high", "highest"], + "motion_detection_enabled": true, "motion_announcement": false, "motion_snooze_preset_profile": "low", "motion_snooze_presets": ["none", "low", "medium", "high"], @@ -281,6 +283,7 @@ }, "live_view_preset_profile": "highest", "live_view_presets": ["low", "middle", "high", "highest"], + "motion_detection_enabled": false, "motion_announcement": false, "motion_snooze_preset_profile": "low", "motion_snooze_presets": ["none", "low", "medium", "high"], diff --git a/tests/components/ring/snapshots/test_diagnostics.ambr b/tests/components/ring/snapshots/test_diagnostics.ambr index 64e753ba2b3..2b8f2bac389 100644 --- a/tests/components/ring/snapshots/test_diagnostics.ambr +++ b/tests/components/ring/snapshots/test_diagnostics.ambr @@ -82,6 +82,7 @@ 'highest', ]), 'motion_announcement': False, + 'motion_detection_enabled': True, 'motion_snooze_preset_profile': 'low', 'motion_snooze_presets': list([ 'null', @@ -158,6 +159,7 @@ 'highest', ]), 'motion_announcement': False, + 'motion_detection_enabled': False, 'motion_snooze_preset_profile': 'low', 'motion_snooze_presets': list([ 'none', @@ -398,6 +400,7 @@ 'highest', ]), 'motion_announcement': False, + 'motion_detection_enabled': True, 'motion_snooze_preset_profile': 'low', 'motion_snooze_presets': list([ 'none', diff --git a/tests/components/ring/test_camera.py b/tests/components/ring/test_camera.py new file mode 100644 index 00000000000..5fd05dd8f28 --- /dev/null +++ b/tests/components/ring/test_camera.py @@ -0,0 +1,140 @@ +"""The tests for the Ring switch platform.""" +from unittest.mock import PropertyMock, patch + +import pytest +import requests_mock +import ring_doorbell + +from homeassistant.config_entries import SOURCE_REAUTH +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er + +from .common import setup_platform + +from tests.common import load_fixture + + +async def test_entity_registry( + hass: HomeAssistant, requests_mock: requests_mock.Mocker +) -> None: + """Tests that the devices are registered in the entity registry.""" + await setup_platform(hass, Platform.CAMERA) + entity_registry = er.async_get(hass) + + entry = entity_registry.async_get("camera.front") + assert entry.unique_id == 765432 + + entry = entity_registry.async_get("camera.internal") + assert entry.unique_id == 345678 + + +@pytest.mark.parametrize( + ("entity_name", "expected_state", "friendly_name"), + [ + ("camera.internal", True, "Internal"), + ("camera.front", None, "Front"), + ], + ids=["On", "Off"], +) +async def test_camera_motion_detection_state_reports_correctly( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + entity_name, + expected_state, + friendly_name, +) -> None: + """Tests that the initial state of a device that should be off is correct.""" + await setup_platform(hass, Platform.CAMERA) + + state = hass.states.get(entity_name) + assert state.attributes.get("motion_detection") is expected_state + assert state.attributes.get("friendly_name") == friendly_name + + +async def test_camera_motion_detection_can_be_turned_on( + hass: HomeAssistant, requests_mock: requests_mock.Mocker +) -> None: + """Tests the siren turns on correctly.""" + await setup_platform(hass, Platform.CAMERA) + + state = hass.states.get("camera.front") + assert state.attributes.get("motion_detection") is not True + + await hass.services.async_call( + "camera", + "enable_motion_detection", + {"entity_id": "camera.front"}, + blocking=True, + ) + + await hass.async_block_till_done() + + state = hass.states.get("camera.front") + assert state.attributes.get("motion_detection") is True + + +async def test_updates_work( + hass: HomeAssistant, requests_mock: requests_mock.Mocker +) -> None: + """Tests the update service works correctly.""" + await setup_platform(hass, Platform.CAMERA) + state = hass.states.get("camera.internal") + assert state.attributes.get("motion_detection") is True + # Changes the return to indicate that the switch is now on. + requests_mock.get( + "https://api.ring.com/clients_api/ring_devices", + text=load_fixture("devices_updated.json", "ring"), + ) + + await hass.services.async_call("ring", "update", {}, blocking=True) + + await hass.async_block_till_done() + + state = hass.states.get("camera.internal") + assert state.attributes.get("motion_detection") is not True + + +@pytest.mark.parametrize( + ("exception_type", "reauth_expected"), + [ + (ring_doorbell.AuthenticationError, True), + (ring_doorbell.RingTimeout, False), + (ring_doorbell.RingError, False), + ], + ids=["Authentication", "Timeout", "Other"], +) +async def test_motion_detection_errors_when_turned_on( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + exception_type, + reauth_expected, +) -> None: + """Tests the motion detection errors are handled correctly.""" + await setup_platform(hass, Platform.CAMERA) + config_entry = hass.config_entries.async_entries("ring")[0] + + assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH})) + + with patch.object( + ring_doorbell.RingDoorBell, "motion_detection", new_callable=PropertyMock + ) as mock_motion_detection: + mock_motion_detection.side_effect = exception_type + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "camera", + "enable_motion_detection", + {"entity_id": "camera.front"}, + blocking=True, + ) + await hass.async_block_till_done() + assert mock_motion_detection.call_count == 1 + assert ( + any( + flow + for flow in config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}) + if flow["handler"] == "ring" + ) + == reauth_expected + ) From 70f3da93d47d417fc7b4694937258c924ad3cc11 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 16:10:04 +0100 Subject: [PATCH 0834/1691] Remove entity description mixin in Screenlogic (#112935) * Remove entity description mixin in Screenlogic * Fix --- .../components/screenlogic/binary_sensor.py | 4 +-- .../components/screenlogic/climate.py | 2 +- .../components/screenlogic/entity.py | 28 ++++--------------- homeassistant/components/screenlogic/light.py | 2 +- .../components/screenlogic/number.py | 2 +- .../components/screenlogic/sensor.py | 17 ++++------- .../components/screenlogic/switch.py | 2 +- 7 files changed, 18 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py index ac3fce80102..6f956026c11 100644 --- a/homeassistant/components/screenlogic/binary_sensor.py +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -33,14 +33,14 @@ from .util import cleanup_excluded_entity _LOGGER = logging.getLogger(__name__) -@dataclasses.dataclass(frozen=True) +@dataclasses.dataclass(frozen=True, kw_only=True) class ScreenLogicBinarySensorDescription( BinarySensorEntityDescription, ScreenLogicEntityDescription ): """A class that describes ScreenLogic binary sensor eneites.""" -@dataclasses.dataclass(frozen=True) +@dataclasses.dataclass(frozen=True, kw_only=True) class ScreenLogicPushBinarySensorDescription( ScreenLogicBinarySensorDescription, ScreenLogicPushEntityDescription ): diff --git a/homeassistant/components/screenlogic/climate.py b/homeassistant/components/screenlogic/climate.py index b3b83a66935..e0e61e826b0 100644 --- a/homeassistant/components/screenlogic/climate.py +++ b/homeassistant/components/screenlogic/climate.py @@ -69,7 +69,7 @@ async def async_setup_entry( async_add_entities(entities) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ScreenLogicClimateDescription( ClimateEntityDescription, ScreenLogicPushEntityDescription ): diff --git a/homeassistant/components/screenlogic/entity.py b/homeassistant/components/screenlogic/entity.py index 6d898ec825d..df7fe6fc265 100644 --- a/homeassistant/components/screenlogic/entity.py +++ b/homeassistant/components/screenlogic/entity.py @@ -29,19 +29,11 @@ from .util import generate_unique_id _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) -class ScreenLogicEntityRequiredKeyMixin: - """Mixin for required ScreenLogic entity data_path.""" - - data_root: ScreenLogicDataPath - - -@dataclass(frozen=True) -class ScreenLogicEntityDescription( - EntityDescription, ScreenLogicEntityRequiredKeyMixin -): +@dataclass(frozen=True, kw_only=True) +class ScreenLogicEntityDescription(EntityDescription): """Base class for a ScreenLogic entity description.""" + data_root: ScreenLogicDataPath enabled_lambda: Callable[..., bool] | None = None @@ -104,21 +96,13 @@ class ScreenLogicEntity(CoordinatorEntity[ScreenlogicDataUpdateCoordinator]): raise HomeAssistantError(f"Data not found: {self._data_path}") from ke -@dataclass(frozen=True) -class ScreenLogicPushEntityRequiredKeyMixin: - """Mixin for required key for ScreenLogic push entities.""" +@dataclass(frozen=True, kw_only=True) +class ScreenLogicPushEntityDescription(ScreenLogicEntityDescription): + """Base class for a ScreenLogic push entity description.""" subscription_code: CODE -@dataclass(frozen=True) -class ScreenLogicPushEntityDescription( - ScreenLogicEntityDescription, - ScreenLogicPushEntityRequiredKeyMixin, -): - """Base class for a ScreenLogic push entity description.""" - - class ScreenLogicPushEntity(ScreenLogicEntity): """Base class for all ScreenLogic push entities.""" diff --git a/homeassistant/components/screenlogic/light.py b/homeassistant/components/screenlogic/light.py index 19f27a73115..4def432d97c 100644 --- a/homeassistant/components/screenlogic/light.py +++ b/homeassistant/components/screenlogic/light.py @@ -61,7 +61,7 @@ async def async_setup_entry( async_add_entities(entities) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ScreenLogicLightDescription( LightEntityDescription, ScreenLogicPushEntityDescription ): diff --git a/homeassistant/components/screenlogic/number.py b/homeassistant/components/screenlogic/number.py index d3e433b60e0..71d4e045da6 100644 --- a/homeassistant/components/screenlogic/number.py +++ b/homeassistant/components/screenlogic/number.py @@ -28,7 +28,7 @@ _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 1 -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ScreenLogicNumberDescription( NumberEntityDescription, ScreenLogicEntityDescription, diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index 9fe201ba6bb..a67c61c4c91 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -36,21 +36,16 @@ from .util import cleanup_excluded_entity, get_ha_unit _LOGGER = logging.getLogger(__name__) -@dataclasses.dataclass(frozen=True) -class ScreenLogicSensorMixin: - """Mixin for SecreenLogic sensor entity.""" +@dataclasses.dataclass(frozen=True, kw_only=True) +class ScreenLogicSensorDescription( + SensorEntityDescription, ScreenLogicEntityDescription +): + """Describes a ScreenLogic sensor.""" value_mod: Callable[[int | str], int | str] | None = None -@dataclasses.dataclass(frozen=True) -class ScreenLogicSensorDescription( - ScreenLogicSensorMixin, SensorEntityDescription, ScreenLogicEntityDescription -): - """Describes a ScreenLogic sensor.""" - - -@dataclasses.dataclass(frozen=True) +@dataclasses.dataclass(frozen=True, kw_only=True) class ScreenLogicPushSensorDescription( ScreenLogicSensorDescription, ScreenLogicPushEntityDescription ): diff --git a/homeassistant/components/screenlogic/switch.py b/homeassistant/components/screenlogic/switch.py index f97106fa7bc..fe697567bab 100644 --- a/homeassistant/components/screenlogic/switch.py +++ b/homeassistant/components/screenlogic/switch.py @@ -23,7 +23,7 @@ from .entity import ( _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) +@dataclass(frozen=True, kw_only=True) class ScreenLogicCircuitSwitchDescription( SwitchEntityDescription, ScreenLogicPushEntityDescription ): From 3b1ab6436d0e7f74996dd45ed3b27dcc465e0d8b Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 12 Mar 2024 17:27:44 +0100 Subject: [PATCH 0835/1691] Remove deprecated `hass.components` usage in service tests (#111883) --- tests/helpers/test_service.py | 124 ++++++++++++++++------------------ 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 113c452e719..1a37de217d9 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -13,6 +13,9 @@ import voluptuous as vol from homeassistant import exceptions from homeassistant.auth.permissions import PolicyPermissions import homeassistant.components # noqa: F401 +from homeassistant.components.group import DOMAIN as DOMAIN_GROUP, Group +from homeassistant.components.logger import DOMAIN as DOMAIN_LOGGER +from homeassistant.components.shell_command import DOMAIN as DOMAIN_SHELL_COMMAND from homeassistant.const import ( ATTR_ENTITY_ID, ENTITY_MATCH_ALL, @@ -473,7 +476,7 @@ async def test_extract_entity_ids(hass: HomeAssistant) -> None: assert await async_setup_component(hass, "group", {}) await hass.async_block_till_done() - await hass.components.group.Group.async_create_group( + await Group.async_create_group( hass, "test", created_by_service=False, @@ -563,9 +566,8 @@ async def test_extract_entity_ids_from_devices(hass: HomeAssistant, area_mock) - async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: """Test async_get_all_descriptions.""" - group = hass.components.group - group_config = {group.DOMAIN: {}} - assert await async_setup_component(hass, group.DOMAIN, group_config) + group_config = {DOMAIN_GROUP: {}} + assert await async_setup_component(hass, DOMAIN_GROUP, group_config) assert await async_setup_component(hass, "system_health", {}) with patch( @@ -588,8 +590,7 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: # Does not have services assert "system_health" not in descriptions - logger = hass.components.logger - logger_config = {logger.DOMAIN: {}} + logger_config = {DOMAIN_LOGGER: {}} async def async_get_translations( hass: HomeAssistant, @@ -599,7 +600,7 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: config_flow: bool | None = None, ) -> dict[str, Any]: """Return all backend translations.""" - translation_key_prefix = f"component.{logger.DOMAIN}.services.set_default_level" + translation_key_prefix = f"component.{DOMAIN_LOGGER}.services.set_default_level" return { f"{translation_key_prefix}.name": "Translated name", f"{translation_key_prefix}.description": "Translated description", @@ -612,58 +613,58 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: "homeassistant.helpers.service.translation.async_get_translations", side_effect=async_get_translations, ): - await async_setup_component(hass, logger.DOMAIN, logger_config) + await async_setup_component(hass, DOMAIN_LOGGER, logger_config) descriptions = await service.async_get_all_descriptions(hass) assert len(descriptions) == 2 - assert descriptions[logger.DOMAIN]["set_default_level"]["name"] == "Translated name" + assert descriptions[DOMAIN_LOGGER]["set_default_level"]["name"] == "Translated name" assert ( - descriptions[logger.DOMAIN]["set_default_level"]["description"] + descriptions[DOMAIN_LOGGER]["set_default_level"]["description"] == "Translated description" ) assert ( - descriptions[logger.DOMAIN]["set_default_level"]["fields"]["level"]["name"] + descriptions[DOMAIN_LOGGER]["set_default_level"]["fields"]["level"]["name"] == "Field name" ) assert ( - descriptions[logger.DOMAIN]["set_default_level"]["fields"]["level"][ + descriptions[DOMAIN_LOGGER]["set_default_level"]["fields"]["level"][ "description" ] == "Field description" ) assert ( - descriptions[logger.DOMAIN]["set_default_level"]["fields"]["level"]["example"] + descriptions[DOMAIN_LOGGER]["set_default_level"]["fields"]["level"]["example"] == "Field example" ) - hass.services.async_register(logger.DOMAIN, "new_service", lambda x: None, None) + hass.services.async_register(DOMAIN_LOGGER, "new_service", lambda x: None, None) service.async_set_service_schema( - hass, logger.DOMAIN, "new_service", {"description": "new service"} + hass, DOMAIN_LOGGER, "new_service", {"description": "new service"} ) descriptions = await service.async_get_all_descriptions(hass) - assert "description" in descriptions[logger.DOMAIN]["new_service"] - assert descriptions[logger.DOMAIN]["new_service"]["description"] == "new service" + assert "description" in descriptions[DOMAIN_LOGGER]["new_service"] + assert descriptions[DOMAIN_LOGGER]["new_service"]["description"] == "new service" hass.services.async_register( - logger.DOMAIN, "another_new_service", lambda x: None, None + DOMAIN_LOGGER, "another_new_service", lambda x: None, None ) hass.services.async_register( - logger.DOMAIN, + DOMAIN_LOGGER, "service_with_optional_response", lambda x: None, None, SupportsResponse.OPTIONAL, ) hass.services.async_register( - logger.DOMAIN, + DOMAIN_LOGGER, "service_with_only_response", lambda x: None, None, SupportsResponse.ONLY, ) hass.services.async_register( - logger.DOMAIN, + DOMAIN_LOGGER, "another_service_with_response", lambda x: None, None, @@ -671,22 +672,22 @@ async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: ) service.async_set_service_schema( hass, - logger.DOMAIN, + DOMAIN_LOGGER, "another_service_with_response", {"description": "response service"}, ) descriptions = await service.async_get_all_descriptions(hass) - assert "another_new_service" in descriptions[logger.DOMAIN] - assert "service_with_optional_response" in descriptions[logger.DOMAIN] - assert descriptions[logger.DOMAIN]["service_with_optional_response"][ + assert "another_new_service" in descriptions[DOMAIN_LOGGER] + assert "service_with_optional_response" in descriptions[DOMAIN_LOGGER] + assert descriptions[DOMAIN_LOGGER]["service_with_optional_response"][ "response" ] == {"optional": True} - assert "service_with_only_response" in descriptions[logger.DOMAIN] - assert descriptions[logger.DOMAIN]["service_with_only_response"]["response"] == { + assert "service_with_only_response" in descriptions[DOMAIN_LOGGER] + assert descriptions[DOMAIN_LOGGER]["service_with_only_response"]["response"] == { "optional": False } - assert "another_service_with_response" in descriptions[logger.DOMAIN] - assert descriptions[logger.DOMAIN]["another_service_with_response"]["response"] == { + assert "another_service_with_response" in descriptions[DOMAIN_LOGGER] + assert descriptions[DOMAIN_LOGGER]["another_service_with_response"]["response"] == { "optional": True } @@ -698,9 +699,8 @@ async def test_async_get_all_descriptions_failing_integration( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test async_get_all_descriptions when async_get_integrations returns an exception.""" - group = hass.components.group - group_config = {group.DOMAIN: {}} - await async_setup_component(hass, group.DOMAIN, group_config) + group_config = {DOMAIN_GROUP: {}} + await async_setup_component(hass, DOMAIN_GROUP, group_config) descriptions = await service.async_get_all_descriptions(hass) assert len(descriptions) == 1 @@ -708,9 +708,8 @@ async def test_async_get_all_descriptions_failing_integration( assert "description" in descriptions["group"]["reload"] assert "fields" in descriptions["group"]["reload"] - logger = hass.components.logger - logger_config = {logger.DOMAIN: {}} - await async_setup_component(hass, logger.DOMAIN, logger_config) + logger_config = {DOMAIN_LOGGER: {}} + await async_setup_component(hass, DOMAIN_LOGGER, logger_config) with patch( "homeassistant.helpers.service.async_get_integrations", return_value={"logger": ImportError}, @@ -725,32 +724,32 @@ async def test_async_get_all_descriptions_failing_integration( # Services are empty defaults if the load fails but should # not raise - assert descriptions[logger.DOMAIN]["set_level"] == { + assert descriptions[DOMAIN_LOGGER]["set_level"] == { "description": "", "fields": {}, "name": "", } - hass.services.async_register(logger.DOMAIN, "new_service", lambda x: None, None) + hass.services.async_register(DOMAIN_LOGGER, "new_service", lambda x: None, None) service.async_set_service_schema( - hass, logger.DOMAIN, "new_service", {"description": "new service"} + hass, DOMAIN_LOGGER, "new_service", {"description": "new service"} ) descriptions = await service.async_get_all_descriptions(hass) - assert "description" in descriptions[logger.DOMAIN]["new_service"] - assert descriptions[logger.DOMAIN]["new_service"]["description"] == "new service" + assert "description" in descriptions[DOMAIN_LOGGER]["new_service"] + assert descriptions[DOMAIN_LOGGER]["new_service"]["description"] == "new service" hass.services.async_register( - logger.DOMAIN, "another_new_service", lambda x: None, None + DOMAIN_LOGGER, "another_new_service", lambda x: None, None ) hass.services.async_register( - logger.DOMAIN, + DOMAIN_LOGGER, "service_with_optional_response", lambda x: None, None, SupportsResponse.OPTIONAL, ) hass.services.async_register( - logger.DOMAIN, + DOMAIN_LOGGER, "service_with_only_response", lambda x: None, None, @@ -758,13 +757,13 @@ async def test_async_get_all_descriptions_failing_integration( ) descriptions = await service.async_get_all_descriptions(hass) - assert "another_new_service" in descriptions[logger.DOMAIN] - assert "service_with_optional_response" in descriptions[logger.DOMAIN] - assert descriptions[logger.DOMAIN]["service_with_optional_response"][ + assert "another_new_service" in descriptions[DOMAIN_LOGGER] + assert "service_with_optional_response" in descriptions[DOMAIN_LOGGER] + assert descriptions[DOMAIN_LOGGER]["service_with_optional_response"][ "response" ] == {"optional": True} - assert "service_with_only_response" in descriptions[logger.DOMAIN] - assert descriptions[logger.DOMAIN]["service_with_only_response"]["response"] == { + assert "service_with_only_response" in descriptions[DOMAIN_LOGGER] + assert descriptions[DOMAIN_LOGGER]["service_with_only_response"]["response"] == { "optional": False } @@ -776,9 +775,8 @@ async def test_async_get_all_descriptions_dynamically_created_services( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test async_get_all_descriptions when async_get_integrations when services are dynamic.""" - group = hass.components.group - group_config = {group.DOMAIN: {}} - await async_setup_component(hass, group.DOMAIN, group_config) + group_config = {DOMAIN_GROUP: {}} + await async_setup_component(hass, DOMAIN_GROUP, group_config) descriptions = await service.async_get_all_descriptions(hass) assert len(descriptions) == 1 @@ -786,13 +784,12 @@ async def test_async_get_all_descriptions_dynamically_created_services( assert "description" in descriptions["group"]["reload"] assert "fields" in descriptions["group"]["reload"] - shell_command = hass.components.shell_command - shell_command_config = {shell_command.DOMAIN: {"test_service": "ls /bin"}} - await async_setup_component(hass, shell_command.DOMAIN, shell_command_config) + shell_command_config = {DOMAIN_SHELL_COMMAND: {"test_service": "ls /bin"}} + await async_setup_component(hass, DOMAIN_SHELL_COMMAND, shell_command_config) descriptions = await service.async_get_all_descriptions(hass) assert len(descriptions) == 2 - assert descriptions[shell_command.DOMAIN]["test_service"] == { + assert descriptions[DOMAIN_SHELL_COMMAND]["test_service"] == { "description": "", "fields": {}, "name": "", @@ -804,9 +801,8 @@ async def test_async_get_all_descriptions_new_service_added_while_loading( hass: HomeAssistant, ) -> None: """Test async_get_all_descriptions when a new service is added while loading translations.""" - group = hass.components.group - group_config = {group.DOMAIN: {}} - await async_setup_component(hass, group.DOMAIN, group_config) + group_config = {DOMAIN_GROUP: {}} + await async_setup_component(hass, DOMAIN_GROUP, group_config) descriptions = await service.async_get_all_descriptions(hass) assert len(descriptions) == 1 @@ -814,8 +810,7 @@ async def test_async_get_all_descriptions_new_service_added_while_loading( assert "description" in descriptions["group"]["reload"] assert "fields" in descriptions["group"]["reload"] - logger = hass.components.logger - logger_domain = logger.DOMAIN + logger_domain = DOMAIN_LOGGER logger_config = {logger_domain: {}} translations_called = asyncio.Event() @@ -884,9 +879,8 @@ async def test_register_with_mixed_case(hass: HomeAssistant) -> None: For backwards compatibility, we have historically allowed mixed case, and automatically converted it to lowercase. """ - logger = hass.components.logger - logger_config = {logger.DOMAIN: {}} - await async_setup_component(hass, logger.DOMAIN, logger_config) + logger_config = {DOMAIN_LOGGER: {}} + await async_setup_component(hass, DOMAIN_LOGGER, logger_config) logger_domain_mixed = "LoGgEr" hass.services.async_register( logger_domain_mixed, "NeW_SeRVICE", lambda x: None, None @@ -895,8 +889,8 @@ async def test_register_with_mixed_case(hass: HomeAssistant) -> None: hass, logger_domain_mixed, "NeW_SeRVICE", {"description": "new service"} ) descriptions = await service.async_get_all_descriptions(hass) - assert "description" in descriptions[logger.DOMAIN]["new_service"] - assert descriptions[logger.DOMAIN]["new_service"]["description"] == "new service" + assert "description" in descriptions[DOMAIN_LOGGER]["new_service"] + assert descriptions[DOMAIN_LOGGER]["new_service"]["description"] == "new service" async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -> None: From cd4e8707eaf23e9ce13ba4ef0669da2f65cd16e6 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Tue, 12 Mar 2024 17:38:09 +0100 Subject: [PATCH 0836/1691] Improve discovering upnp/igd device by always using the SSDP-discovery for the Unique Device Name (#111487) * Always use the UDN found in the SSDP discovery, instead of the device description * Ensure existing DeviceEntries are still matched --- homeassistant/components/upnp/__init__.py | 5 +- homeassistant/components/upnp/config_flow.py | 15 +++--- tests/components/upnp/test_config_flow.py | 41 ++++++++++++++- tests/components/upnp/test_init.py | 54 +++++++++++++++++++- 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 893b86cd1b2..f2f3ffd0a1b 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -80,6 +80,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create device. assert discovery_info is not None + assert discovery_info.ssdp_udn assert discovery_info.ssdp_all_locations location = get_preferred_location(discovery_info.ssdp_all_locations) try: @@ -118,7 +119,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if device.serial_number: identifiers.add((IDENTIFIER_SERIAL_NUMBER, device.serial_number)) - connections = {(dr.CONNECTION_UPNP, device.udn)} + connections = {(dr.CONNECTION_UPNP, discovery_info.ssdp_udn)} + if discovery_info.ssdp_udn != device.udn: + connections.add((dr.CONNECTION_UPNP, device.udn)) if device_mac_address: connections.add((dr.CONNECTION_NETWORK_MAC, device_mac_address)) diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index d02877ccd05..a708403b6f2 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -42,7 +42,7 @@ def _friendly_name_from_discovery(discovery_info: ssdp.SsdpServiceInfo) -> str: def _is_complete_discovery(discovery_info: ssdp.SsdpServiceInfo) -> bool: """Test if discovery is complete and usable.""" return bool( - ssdp.ATTR_UPNP_UDN in discovery_info.upnp + discovery_info.ssdp_udn and discovery_info.ssdp_st and discovery_info.ssdp_all_locations and discovery_info.ssdp_usn @@ -80,9 +80,8 @@ class UpnpFlowHandler(ConfigFlow, domain=DOMAIN): VERSION = 1 # Paths: - # - ssdp(discovery_info) --> ssdp_confirm(None) - # --> ssdp_confirm({}) --> create_entry() - # - user(None): scan --> user({...}) --> create_entry() + # 1: ssdp(discovery_info) --> ssdp_confirm(None) --> ssdp_confirm({}) --> create_entry() + # 2: user(None): scan --> user({...}) --> create_entry() @property def _discoveries(self) -> dict[str, SsdpServiceInfo]: @@ -243,9 +242,9 @@ class UpnpFlowHandler(ConfigFlow, domain=DOMAIN): discovery = self._remove_discovery(usn) mac_address = await _async_mac_address_from_discovery(self.hass, discovery) data = { - CONFIG_ENTRY_UDN: discovery.upnp[ssdp.ATTR_UPNP_UDN], + CONFIG_ENTRY_UDN: discovery.ssdp_udn, CONFIG_ENTRY_ST: discovery.ssdp_st, - CONFIG_ENTRY_ORIGINAL_UDN: discovery.upnp[ssdp.ATTR_UPNP_UDN], + CONFIG_ENTRY_ORIGINAL_UDN: discovery.ssdp_udn, CONFIG_ENTRY_MAC_ADDRESS: mac_address, CONFIG_ENTRY_HOST: discovery.ssdp_headers["_host"], CONFIG_ENTRY_LOCATION: get_preferred_location(discovery.ssdp_all_locations), @@ -267,9 +266,9 @@ class UpnpFlowHandler(ConfigFlow, domain=DOMAIN): title = _friendly_name_from_discovery(discovery) mac_address = await _async_mac_address_from_discovery(self.hass, discovery) data = { - CONFIG_ENTRY_UDN: discovery.upnp[ssdp.ATTR_UPNP_UDN], + CONFIG_ENTRY_UDN: discovery.ssdp_udn, CONFIG_ENTRY_ST: discovery.ssdp_st, - CONFIG_ENTRY_ORIGINAL_UDN: discovery.upnp[ssdp.ATTR_UPNP_UDN], + CONFIG_ENTRY_ORIGINAL_UDN: discovery.ssdp_udn, CONFIG_ENTRY_LOCATION: get_preferred_location(discovery.ssdp_all_locations), CONFIG_ENTRY_MAC_ADDRESS: mac_address, CONFIG_ENTRY_HOST: discovery.ssdp_headers["_host"], diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index 7c542e33c9d..67b4e5b10e6 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -1,5 +1,6 @@ """Test UPnP/IGD config flow.""" +import copy from copy import deepcopy from unittest.mock import patch @@ -111,6 +112,7 @@ async def test_flow_ssdp_incomplete_discovery(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_SSDP}, data=ssdp.SsdpServiceInfo( ssdp_usn=TEST_USN, + # ssdp_udn=TEST_UDN, # Not provided. ssdp_st=TEST_ST, ssdp_location=TEST_LOCATION, upnp={ @@ -132,12 +134,12 @@ async def test_flow_ssdp_non_igd_device(hass: HomeAssistant) -> None: context={"source": config_entries.SOURCE_SSDP}, data=ssdp.SsdpServiceInfo( ssdp_usn=TEST_USN, + ssdp_udn=TEST_UDN, ssdp_st=TEST_ST, ssdp_location=TEST_LOCATION, ssdp_all_locations=[TEST_LOCATION], upnp={ ssdp.ATTR_UPNP_DEVICE_TYPE: "urn:schemas-upnp-org:device:WFADevice:1", # Non-IGD - ssdp.ATTR_UPNP_UDN: TEST_UDN, }, ), ) @@ -442,3 +444,40 @@ async def test_flow_user_no_discovery(hass: HomeAssistant) -> None: ) assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "no_devices_found" + + +@pytest.mark.usefixtures( + "ssdp_instant_discovery", + "mock_setup_entry", + "mock_get_source_ip", + "mock_mac_address_from_host", +) +async def test_flow_ssdp_with_mismatched_udn(hass: HomeAssistant) -> None: + """Test config flow: discovered + configured through ssdp, where the UDN differs in the SSDP-discovery vs device description.""" + # Discovered via step ssdp. + test_discovery = copy.deepcopy(TEST_DISCOVERY) + test_discovery.upnp[ssdp.ATTR_UPNP_UDN] = "uuid:another_udn" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data=test_discovery, + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "ssdp_confirm" + + # Confirm via step ssdp_confirm. + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={}, + ) + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == TEST_FRIENDLY_NAME + assert result["data"] == { + CONFIG_ENTRY_ST: TEST_ST, + CONFIG_ENTRY_UDN: TEST_UDN, + CONFIG_ENTRY_ORIGINAL_UDN: TEST_UDN, + CONFIG_ENTRY_LOCATION: TEST_LOCATION, + CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, + CONFIG_ENTRY_HOST: TEST_HOST, + } diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index c46f3187b21..5c59ddd9da7 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -2,10 +2,12 @@ from __future__ import annotations -from unittest.mock import AsyncMock +import copy +from unittest.mock import AsyncMock, MagicMock, patch import pytest +from homeassistant.components import ssdp from homeassistant.components.upnp.const import ( CONFIG_ENTRY_LOCATION, CONFIG_ENTRY_MAC_ADDRESS, @@ -16,7 +18,14 @@ from homeassistant.components.upnp.const import ( ) from homeassistant.core import HomeAssistant -from .conftest import TEST_LOCATION, TEST_MAC_ADDRESS, TEST_ST, TEST_UDN, TEST_USN +from .conftest import ( + TEST_DISCOVERY, + TEST_LOCATION, + TEST_MAC_ADDRESS, + TEST_ST, + TEST_UDN, + TEST_USN, +) from tests.common import MockConfigEntry @@ -95,3 +104,44 @@ async def test_async_setup_entry_multi_location( # Ensure that the IPv4 location is used. mock_async_create_device.assert_called_once_with(TEST_LOCATION) + + +@pytest.mark.usefixtures("mock_get_source_ip", "mock_mac_address_from_host") +async def test_async_setup_udn_mismatch( + hass: HomeAssistant, mock_async_create_device: AsyncMock +) -> None: + """Test async_setup_entry for a device which reports a different UDN from SSDP-discovery and device description.""" + test_discovery = copy.deepcopy(TEST_DISCOVERY) + test_discovery.upnp[ssdp.ATTR_UPNP_UDN] = "uuid:another_udn" + + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=TEST_USN, + data={ + CONFIG_ENTRY_ST: TEST_ST, + CONFIG_ENTRY_UDN: TEST_UDN, + CONFIG_ENTRY_ORIGINAL_UDN: TEST_UDN, + CONFIG_ENTRY_LOCATION: TEST_LOCATION, + CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, + }, + ) + + # Set up device discovery callback. + async def register_callback(hass, callback, match_dict): + """Immediately do callback.""" + await callback(test_discovery, ssdp.SsdpChange.ALIVE) + return MagicMock() + + with patch( + "homeassistant.components.ssdp.async_register_callback", + side_effect=register_callback, + ), patch( + "homeassistant.components.ssdp.async_get_discovery_info_by_st", + return_value=[test_discovery], + ): + # Load config_entry. + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) is True + + # Ensure that the IPv4 location is used. + mock_async_create_device.assert_called_once_with(TEST_LOCATION) From f01095fb66b745f5016a77cc1ca6691dce72ab5c Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 12 Mar 2024 17:41:16 +0100 Subject: [PATCH 0837/1691] Fix availability for GIOS index sensors (#113021) * Fix availability for index sensors * Improve test_availability() --------- Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/gios/sensor.py | 6 +-- tests/components/gios/test_sensor.py | 66 +++++++++++++------------ 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/gios/sensor.py b/homeassistant/components/gios/sensor.py index f097a14c943..c2da9239453 100644 --- a/homeassistant/components/gios/sensor.py +++ b/homeassistant/components/gios/sensor.py @@ -230,11 +230,11 @@ class GiosSensor(CoordinatorEntity[GiosDataUpdateCoordinator], SensorEntity): @property def available(self) -> bool: """Return if entity is available.""" - available = super().available sensor_data = getattr(self.coordinator.data, self.entity_description.key) + available = super().available and bool(sensor_data) # Sometimes the API returns sensor data without indexes - if self.entity_description.subkey: + if self.entity_description.subkey and available: return available and bool(sensor_data.index) - return available and bool(sensor_data) + return available diff --git a/tests/components/gios/test_sensor.py b/tests/components/gios/test_sensor.py index 4f35587d4fe..6714882ad3f 100644 --- a/tests/components/gios/test_sensor.py +++ b/tests/components/gios/test_sensor.py @@ -1,5 +1,6 @@ """Test sensor of GIOS integration.""" +from copy import deepcopy from datetime import timedelta import json from unittest.mock import patch @@ -277,22 +278,24 @@ async def test_availability(hass: HomeAssistant) -> None: async_fire_time_changed(hass, future) await hass.async_block_till_done() - state = hass.states.get("sensor.home_pm2_5") - assert state - assert state.state == STATE_UNAVAILABLE + state = hass.states.get("sensor.home_pm2_5") + assert state + assert state.state == STATE_UNAVAILABLE - state = hass.states.get("sensor.home_pm2_5_index") - assert state - assert state.state == STATE_UNAVAILABLE + state = hass.states.get("sensor.home_pm2_5_index") + assert state + assert state.state == STATE_UNAVAILABLE - state = hass.states.get("sensor.home_air_quality_index") - assert state - assert state.state == STATE_UNAVAILABLE + state = hass.states.get("sensor.home_air_quality_index") + assert state + assert state.state == STATE_UNAVAILABLE + incomplete_sensors = deepcopy(sensors) + incomplete_sensors["pm2.5"] = {} future = utcnow() + timedelta(minutes=120) with patch( "homeassistant.components.gios.Gios._get_all_sensors", - return_value=sensors, + return_value=incomplete_sensors, ), patch( "homeassistant.components.gios.Gios._get_indexes", return_value={}, @@ -300,21 +303,22 @@ async def test_availability(hass: HomeAssistant) -> None: async_fire_time_changed(hass, future) await hass.async_block_till_done() - state = hass.states.get("sensor.home_pm2_5") - assert state - assert state.state == "4" + # There is no PM2.5 data so the state should be unavailable + state = hass.states.get("sensor.home_pm2_5") + assert state + assert state.state == STATE_UNAVAILABLE - # Indexes are empty so the state should be unavailable - state = hass.states.get("sensor.home_air_quality_index") - assert state - assert state.state == STATE_UNAVAILABLE + # Indexes are empty so the state should be unavailable + state = hass.states.get("sensor.home_air_quality_index") + assert state + assert state.state == STATE_UNAVAILABLE - # Indexes are empty so the state should be unavailable - state = hass.states.get("sensor.home_pm2_5_index") - assert state - assert state.state == STATE_UNAVAILABLE + # Indexes are empty so the state should be unavailable + state = hass.states.get("sensor.home_pm2_5_index") + assert state + assert state.state == STATE_UNAVAILABLE - future = utcnow() + timedelta(minutes=180) + future = utcnow() + timedelta(minutes=180) with patch( "homeassistant.components.gios.Gios._get_all_sensors", return_value=sensors ), patch( @@ -324,17 +328,17 @@ async def test_availability(hass: HomeAssistant) -> None: async_fire_time_changed(hass, future) await hass.async_block_till_done() - state = hass.states.get("sensor.home_pm2_5") - assert state - assert state.state == "4" + state = hass.states.get("sensor.home_pm2_5") + assert state + assert state.state == "4" - state = hass.states.get("sensor.home_pm2_5_index") - assert state - assert state.state == "good" + state = hass.states.get("sensor.home_pm2_5_index") + assert state + assert state.state == "good" - state = hass.states.get("sensor.home_air_quality_index") - assert state - assert state.state == "good" + state = hass.states.get("sensor.home_air_quality_index") + assert state + assert state.state == "good" async def test_invalid_indexes(hass: HomeAssistant) -> None: From eb815994003ce54dd17c71c855c24d7841dd930d Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 12 Mar 2024 11:43:25 -0500 Subject: [PATCH 0838/1691] Fix some handle leaks in rainforest_raven (#113035) There were leaks when * The component was shutdown * There was a timeout during the initial device opening Additionally, the device was not closed/reopened when there was a timeout reading regular data. --- .../rainforest_raven/coordinator.py | 30 ++++++++++++------- .../rainforest_raven/test_coordinator.py | 16 ++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/rainforest_raven/coordinator.py b/homeassistant/components/rainforest_raven/coordinator.py index 7f2d3399a47..37e44b12eba 100644 --- a/homeassistant/components/rainforest_raven/coordinator.py +++ b/homeassistant/components/rainforest_raven/coordinator.py @@ -133,16 +133,27 @@ class RAVEnDataCoordinator(DataUpdateCoordinator): ) return None + async def async_shutdown(self) -> None: + """Shutdown the coordinator.""" + await self._cleanup_device() + await super().async_shutdown() + async def _async_update_data(self) -> dict[str, Any]: try: device = await self._get_device() async with asyncio.timeout(5): return await _get_all_data(device, self.entry.data[CONF_MAC]) except RAVEnConnectionError as err: - if self._raven_device: - await self._raven_device.close() - self._raven_device = None + await self._cleanup_device() raise UpdateFailed(f"RAVEnConnectionError: {err}") from err + except TimeoutError: + await self._cleanup_device() + raise + + async def _cleanup_device(self) -> None: + device, self._raven_device = self._raven_device, None + if device is not None: + await device.close() async def _get_device(self) -> RAVEnSerialDevice: if self._raven_device is not None: @@ -150,15 +161,14 @@ class RAVEnDataCoordinator(DataUpdateCoordinator): device = RAVEnSerialDevice(self.entry.data[CONF_DEVICE]) - async with asyncio.timeout(5): - await device.open() - - try: + try: + async with asyncio.timeout(5): + await device.open() await device.synchronize() self._device_info = await device.get_device_info() - except Exception: - await device.close() - raise + except: + await device.close() + raise self._raven_device = device return device diff --git a/tests/components/rainforest_raven/test_coordinator.py b/tests/components/rainforest_raven/test_coordinator.py index 0c716aef6fc..1a5f4d3d3f7 100644 --- a/tests/components/rainforest_raven/test_coordinator.py +++ b/tests/components/rainforest_raven/test_coordinator.py @@ -1,5 +1,8 @@ """Tests for the Rainforest RAVEn data coordinator.""" +import asyncio +import functools + from aioraven.device import RAVEnConnectionError import pytest @@ -84,6 +87,19 @@ async def test_coordinator_device_error_update(hass: HomeAssistant, mock_device) assert coordinator.last_update_success is False +async def test_coordinator_device_timeout_update(hass: HomeAssistant, mock_device): + """Test handling of a device timeout during an update.""" + entry = create_mock_entry() + coordinator = RAVEnDataCoordinator(hass, entry) + + await coordinator.async_config_entry_first_refresh() + assert coordinator.last_update_success is True + + mock_device.get_network_info.side_effect = functools.partial(asyncio.sleep, 10) + await coordinator.async_refresh() + assert coordinator.last_update_success is False + + async def test_coordinator_comm_error(hass: HomeAssistant, mock_device): """Test handling of an error parsing or reading raw device data.""" entry = create_mock_entry() From 18a7f004dbc9bc6a4f41e3ab4464f33c3bbd7266 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 12 Mar 2024 11:45:14 -0500 Subject: [PATCH 0839/1691] Bump intents to 2023.3.12 (#113160) Bump intents --- homeassistant/components/conversation/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index 4e3339d227b..00f645ea0f3 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.6"] + "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.12"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index df3da9e332b..e68407ca389 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -31,7 +31,7 @@ hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240307.0 -home-assistant-intents==2024.3.6 +home-assistant-intents==2024.3.12 httpx==0.27.0 ifaddr==0.2.0 Jinja2==3.1.3 diff --git a/requirements_all.txt b/requirements_all.txt index 5eb675a6df7..3cfa97059c2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ holidays==0.44 home-assistant-frontend==20240307.0 # homeassistant.components.conversation -home-assistant-intents==2024.3.6 +home-assistant-intents==2024.3.12 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 30d8f19d146..68f84892be2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ holidays==0.44 home-assistant-frontend==20240307.0 # homeassistant.components.conversation -home-assistant-intents==2024.3.6 +home-assistant-intents==2024.3.12 # homeassistant.components.home_connect homeconnect==0.7.2 From d33fdd3289b99af3a7ca0feb9c75f92958c26187 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 18:38:57 +0100 Subject: [PATCH 0840/1691] Clean up Abode imports (#113138) --- .../components/abode/alarm_control_panel.py | 12 ++++--- .../components/abode/binary_sensor.py | 22 +++++++----- homeassistant/components/abode/camera.py | 11 +++--- homeassistant/components/abode/cover.py | 8 ++--- homeassistant/components/abode/light.py | 8 ++--- homeassistant/components/abode/lock.py | 8 ++--- homeassistant/components/abode/sensor.py | 34 ++++++++++++------- homeassistant/components/abode/switch.py | 8 ++--- 8 files changed, 64 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index d22fb86537a..333462a4d9f 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -2,10 +2,12 @@ from __future__ import annotations -from jaraco.abode.devices.alarm import Alarm as AbodeAl +from jaraco.abode.devices.alarm import Alarm -import homeassistant.components.alarm_control_panel as alarm -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature +from homeassistant.components.alarm_control_panel import ( + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -29,7 +31,7 @@ async def async_setup_entry( ) -class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity): +class AbodeAlarm(AbodeDevice, AlarmControlPanelEntity): """An alarm_control_panel implementation for Abode.""" _attr_name = None @@ -38,7 +40,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity): AlarmControlPanelEntityFeature.ARM_HOME | AlarmControlPanelEntityFeature.ARM_AWAY ) - _device: AbodeAl + _device: Alarm @property def state(self) -> str | None: diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py index d19b17219a2..4968d5378e1 100644 --- a/homeassistant/components/abode/binary_sensor.py +++ b/homeassistant/components/abode/binary_sensor.py @@ -4,8 +4,14 @@ from __future__ import annotations from typing import cast -from jaraco.abode.devices.sensor import BinarySensor as ABBinarySensor -from jaraco.abode.helpers import constants as CONST +from jaraco.abode.devices.sensor import BinarySensor +from jaraco.abode.helpers.constants import ( + TYPE_CONNECTIVITY, + TYPE_MOISTURE, + TYPE_MOTION, + TYPE_OCCUPANCY, + TYPE_OPENING, +) from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, @@ -27,11 +33,11 @@ async def async_setup_entry( data: AbodeSystem = hass.data[DOMAIN] device_types = [ - CONST.TYPE_CONNECTIVITY, - CONST.TYPE_MOISTURE, - CONST.TYPE_MOTION, - CONST.TYPE_OCCUPANCY, - CONST.TYPE_OPENING, + TYPE_CONNECTIVITY, + TYPE_MOISTURE, + TYPE_MOTION, + TYPE_OCCUPANCY, + TYPE_OPENING, ] async_add_entities( @@ -44,7 +50,7 @@ class AbodeBinarySensor(AbodeDevice, BinarySensorEntity): """A binary sensor implementation for Abode device.""" _attr_name = None - _device: ABBinarySensor + _device: BinarySensor @property def is_on(self) -> bool: diff --git a/homeassistant/components/abode/camera.py b/homeassistant/components/abode/camera.py index 4ccd0725df6..8ffa90a9b82 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -5,9 +5,10 @@ from __future__ import annotations from datetime import timedelta from typing import Any, cast -from jaraco.abode.devices.base import Device as AbodeDev +from jaraco.abode.devices.base import Device from jaraco.abode.devices.camera import Camera as AbodeCam -from jaraco.abode.helpers import constants as CONST, timeline as TIMELINE +from jaraco.abode.helpers import timeline +from jaraco.abode.helpers.constants import TYPE_CAMERA import requests from requests.models import Response @@ -31,8 +32,8 @@ async def async_setup_entry( data: AbodeSystem = hass.data[DOMAIN] async_add_entities( - AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE) - for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA) + AbodeCamera(data, device, timeline.CAPTURE_IMAGE) + for device in data.abode.get_devices(generic_type=TYPE_CAMERA) ) @@ -42,7 +43,7 @@ class AbodeCamera(AbodeDevice, Camera): _device: AbodeCam _attr_name = None - def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None: + def __init__(self, data: AbodeSystem, device: Device, event: Event) -> None: """Initialize the Abode device.""" AbodeDevice.__init__(self, data, device) Camera.__init__(self) diff --git a/homeassistant/components/abode/cover.py b/homeassistant/components/abode/cover.py index 511435e50c3..e3fbb1a5b8f 100644 --- a/homeassistant/components/abode/cover.py +++ b/homeassistant/components/abode/cover.py @@ -2,8 +2,8 @@ from typing import Any -from jaraco.abode.devices.cover import Cover as AbodeCV -from jaraco.abode.helpers import constants as CONST +from jaraco.abode.devices.cover import Cover +from jaraco.abode.helpers.constants import TYPE_COVER from homeassistant.components.cover import CoverEntity from homeassistant.config_entries import ConfigEntry @@ -22,14 +22,14 @@ async def async_setup_entry( async_add_entities( AbodeCover(data, device) - for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER) + for device in data.abode.get_devices(generic_type=TYPE_COVER) ) class AbodeCover(AbodeDevice, CoverEntity): """Representation of an Abode cover.""" - _device: AbodeCV + _device: Cover _attr_name = None @property diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py index f3df1810bcd..188d3c18e40 100644 --- a/homeassistant/components/abode/light.py +++ b/homeassistant/components/abode/light.py @@ -5,8 +5,8 @@ from __future__ import annotations from math import ceil from typing import Any -from jaraco.abode.devices.light import Light as AbodeLT -from jaraco.abode.helpers import constants as CONST +from jaraco.abode.devices.light import Light +from jaraco.abode.helpers.constants import TYPE_LIGHT from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -35,14 +35,14 @@ async def async_setup_entry( async_add_entities( AbodeLight(data, device) - for device in data.abode.get_devices(generic_type=CONST.TYPE_LIGHT) + for device in data.abode.get_devices(generic_type=TYPE_LIGHT) ) class AbodeLight(AbodeDevice, LightEntity): """Representation of an Abode light.""" - _device: AbodeLT + _device: Light _attr_name = None def turn_on(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/abode/lock.py b/homeassistant/components/abode/lock.py index 6458c9abf29..1135d3c3b36 100644 --- a/homeassistant/components/abode/lock.py +++ b/homeassistant/components/abode/lock.py @@ -2,8 +2,8 @@ from typing import Any -from jaraco.abode.devices.lock import Lock as AbodeLK -from jaraco.abode.helpers import constants as CONST +from jaraco.abode.devices.lock import Lock +from jaraco.abode.helpers.constants import TYPE_LOCK from homeassistant.components.lock import LockEntity from homeassistant.config_entries import ConfigEntry @@ -22,14 +22,14 @@ async def async_setup_entry( async_add_entities( AbodeLock(data, device) - for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK) + for device in data.abode.get_devices(generic_type=TYPE_LOCK) ) class AbodeLock(AbodeDevice, LockEntity): """Representation of an Abode lock.""" - _device: AbodeLK + _device: Lock _attr_name = None def lock(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/abode/sensor.py b/homeassistant/components/abode/sensor.py index 405f3db5a67..89e5cf574fb 100644 --- a/homeassistant/components/abode/sensor.py +++ b/homeassistant/components/abode/sensor.py @@ -6,8 +6,16 @@ from collections.abc import Callable from dataclasses import dataclass from typing import cast -from jaraco.abode.devices.sensor import Sensor as AbodeSense -from jaraco.abode.helpers import constants as CONST +from jaraco.abode.devices.sensor import Sensor +from jaraco.abode.helpers.constants import ( + HUMI_STATUS_KEY, + LUX_STATUS_KEY, + STATUSES_KEY, + TEMP_STATUS_KEY, + TYPE_SENSOR, + UNIT_CELSIUS, + UNIT_FAHRENHEIT, +) from homeassistant.components.sensor import ( SensorDeviceClass, @@ -23,8 +31,8 @@ from . import AbodeDevice, AbodeSystem from .const import DOMAIN ABODE_TEMPERATURE_UNIT_HA_UNIT = { - CONST.UNIT_FAHRENHEIT: UnitOfTemperature.FAHRENHEIT, - CONST.UNIT_CELSIUS: UnitOfTemperature.CELSIUS, + UNIT_FAHRENHEIT: UnitOfTemperature.FAHRENHEIT, + UNIT_CELSIUS: UnitOfTemperature.CELSIUS, } @@ -32,13 +40,13 @@ ABODE_TEMPERATURE_UNIT_HA_UNIT = { class AbodeSensorDescription(SensorEntityDescription): """Class describing Abode sensor entities.""" - value_fn: Callable[[AbodeSense], float] - native_unit_of_measurement_fn: Callable[[AbodeSense], str] + value_fn: Callable[[Sensor], float] + native_unit_of_measurement_fn: Callable[[Sensor], str] SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = ( AbodeSensorDescription( - key=CONST.TEMP_STATUS_KEY, + key=TEMP_STATUS_KEY, device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement_fn=lambda device: ABODE_TEMPERATURE_UNIT_HA_UNIT[ device.temp_unit @@ -46,13 +54,13 @@ SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = ( value_fn=lambda device: cast(float, device.temp), ), AbodeSensorDescription( - key=CONST.HUMI_STATUS_KEY, + key=HUMI_STATUS_KEY, device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement_fn=lambda _: PERCENTAGE, value_fn=lambda device: cast(float, device.humidity), ), AbodeSensorDescription( - key=CONST.LUX_STATUS_KEY, + key=LUX_STATUS_KEY, device_class=SensorDeviceClass.ILLUMINANCE, native_unit_of_measurement_fn=lambda _: LIGHT_LUX, value_fn=lambda device: cast(float, device.lux), @@ -69,8 +77,8 @@ async def async_setup_entry( async_add_entities( AbodeSensor(data, device, description) for description in SENSOR_TYPES - for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR) - if description.key in device.get_value(CONST.STATUSES_KEY) + for device in data.abode.get_devices(generic_type=TYPE_SENSOR) + if description.key in device.get_value(STATUSES_KEY) ) @@ -78,12 +86,12 @@ class AbodeSensor(AbodeDevice, SensorEntity): """A sensor implementation for Abode devices.""" entity_description: AbodeSensorDescription - _device: AbodeSense + _device: Sensor def __init__( self, data: AbodeSystem, - device: AbodeSense, + device: Sensor, description: AbodeSensorDescription, ) -> None: """Initialize a sensor for an Abode device.""" diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py index 3d33632e19a..9a33a04e341 100644 --- a/homeassistant/components/abode/switch.py +++ b/homeassistant/components/abode/switch.py @@ -4,8 +4,8 @@ from __future__ import annotations from typing import Any, cast -from jaraco.abode.devices.switch import Switch as AbodeSW -from jaraco.abode.helpers import constants as CONST +from jaraco.abode.devices.switch import Switch +from jaraco.abode.helpers.constants import TYPE_SWITCH, TYPE_VALVE from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -16,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import AbodeAutomation, AbodeDevice, AbodeSystem from .const import DOMAIN -DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE] +DEVICE_TYPES = [TYPE_SWITCH, TYPE_VALVE] async def async_setup_entry( @@ -42,7 +42,7 @@ async def async_setup_entry( class AbodeSwitch(AbodeDevice, SwitchEntity): """Representation of an Abode switch.""" - _device: AbodeSW + _device: Switch _attr_name = None def turn_on(self, **kwargs: Any) -> None: From 643e6096da20d47ede963314e4a2e2544ef58931 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 18:42:43 +0100 Subject: [PATCH 0841/1691] Improve lists in integrations [E-F] (#113075) --- homeassistant/components/ebusd/sensor.py | 12 +++--- homeassistant/components/ecobee/number.py | 17 +++++---- homeassistant/components/econet/climate.py | 14 +++---- homeassistant/components/ecovacs/button.py | 21 +++++----- .../components/ecovacs/lawn_mower.py | 6 +-- homeassistant/components/ecovacs/sensor.py | 21 +++++----- homeassistant/components/ecovacs/util.py | 17 ++++----- homeassistant/components/efergy/sensor.py | 16 ++++---- .../components/environment_canada/weather.py | 24 ++++++------ homeassistant/components/esphome/manager.py | 3 +- homeassistant/components/fail2ban/sensor.py | 5 +-- homeassistant/components/fibaro/event.py | 16 ++++---- homeassistant/components/fibaro/sensor.py | 38 +++++++++---------- homeassistant/components/fivem/coordinator.py | 4 +- homeassistant/components/flo/switch.py | 9 ++--- .../components/forked_daapd/media_player.py | 37 +++++++++--------- .../components/freebox/alarm_control_panel.py | 16 ++++---- .../components/freebox/binary_sensor.py | 11 +++--- .../fritzbox_callmonitor/config_flow.py | 10 ++--- 19 files changed, 140 insertions(+), 157 deletions(-) diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index 7b2120f6799..2eaaddf7e2f 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -38,13 +38,13 @@ def setup_platform( monitored_conditions = discovery_info["monitored_conditions"] name = discovery_info["client_name"] - dev = [] - for condition in monitored_conditions: - dev.append( + add_entities( + ( EbusdSensor(ebusd_api, discovery_info["sensor_types"][condition], name) - ) - - add_entities(dev, True) + for condition in monitored_conditions + ), + True, + ) class EbusdSensor(SensorEntity): diff --git a/homeassistant/components/ecobee/number.py b/homeassistant/components/ecobee/number.py index 974dc3ca132..4c3dd801c41 100644 --- a/homeassistant/components/ecobee/number.py +++ b/homeassistant/components/ecobee/number.py @@ -54,16 +54,17 @@ async def async_setup_entry( ) -> None: """Set up the ecobee thermostat number entity.""" data: EcobeeData = hass.data[DOMAIN] - entities = [] _LOGGER.debug("Adding min time ventilators numbers (if present)") - for index, thermostat in enumerate(data.ecobee.thermostats): - if thermostat["settings"]["ventilatorType"] == "none": - continue - _LOGGER.debug("Adding %s's ventilator min times number", thermostat["name"]) - for numbers in VENTILATOR_NUMBERS: - entities.append(EcobeeVentilatorMinTime(data, index, numbers)) - async_add_entities(entities, True) + async_add_entities( + ( + EcobeeVentilatorMinTime(data, index, numbers) + for index, thermostat in enumerate(data.ecobee.thermostats) + if thermostat["settings"]["ventilatorType"] != "none" + for numbers in VENTILATOR_NUMBERS + ), + True, + ) class EcobeeVentilatorMinTime(EcobeeBaseEntity, NumberEntity): diff --git a/homeassistant/components/econet/climate.py b/homeassistant/components/econet/climate.py index 508ad5bbcb7..f6bd52c9702 100644 --- a/homeassistant/components/econet/climate.py +++ b/homeassistant/components/econet/climate.py @@ -185,17 +185,17 @@ class EcoNetThermostat(EcoNetEntity, ClimateEntity): @property def fan_modes(self): """Return the fan modes.""" - econet_fan_modes = self._econet.fan_modes - fan_list = [] - for mode in econet_fan_modes: + return [ + ECONET_FAN_STATE_TO_HA[mode] + for mode in self._econet.fan_modes # Remove the MEDLO MEDHI once we figure out how to handle it - if mode not in [ + if mode + not in [ ThermostatFanMode.UNKNOWN, ThermostatFanMode.MEDLO, ThermostatFanMode.MEDHI, - ]: - fan_list.append(ECONET_FAN_STATE_TO_HA[mode]) - return fan_list + ] + ] def set_fan_mode(self, fan_mode: str) -> None: """Set the fan mode.""" diff --git a/homeassistant/components/ecovacs/button.py b/homeassistant/components/ecovacs/button.py index 48636d56834..27f729a1ae0 100644 --- a/homeassistant/components/ecovacs/button.py +++ b/homeassistant/components/ecovacs/button.py @@ -74,18 +74,15 @@ async def async_setup_entry( entities: list[EcovacsEntity] = get_supported_entitites( controller, EcovacsButtonEntity, ENTITY_DESCRIPTIONS ) - for device in controller.devices(Capabilities): - lifespan_capability = device.capabilities.life_span - for description in LIFESPAN_ENTITY_DESCRIPTIONS: - if description.component in lifespan_capability.types: - entities.append( - EcovacsResetLifespanButtonEntity( - device, lifespan_capability, description - ) - ) - - if entities: - async_add_entities(entities) + entities.extend( + EcovacsResetLifespanButtonEntity( + device, device.capabilities.life_span, description + ) + for device in controller.devices(Capabilities) + for description in LIFESPAN_ENTITY_DESCRIPTIONS + if description.component in device.capabilities.life_span.types + ) + async_add_entities(entities) class EcovacsButtonEntity( diff --git a/homeassistant/components/ecovacs/lawn_mower.py b/homeassistant/components/ecovacs/lawn_mower.py index d8b42105ae2..1b13d50cc0c 100644 --- a/homeassistant/components/ecovacs/lawn_mower.py +++ b/homeassistant/components/ecovacs/lawn_mower.py @@ -42,10 +42,10 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Ecovacs mowers.""" - mowers: list[EcovacsMower] = [] controller: EcovacsController = hass.data[DOMAIN][config_entry.entry_id] - for device in controller.devices(MowerCapabilities): - mowers.append(EcovacsMower(device)) + mowers: list[EcovacsMower] = [ + EcovacsMower(device) for device in controller.devices(MowerCapabilities) + ] _LOGGER.debug("Adding Ecovacs Mowers to Home Assistant: %s", mowers) async_add_entities(mowers) diff --git a/homeassistant/components/ecovacs/sensor.py b/homeassistant/components/ecovacs/sensor.py index b89a4e3ecc4..92d1b10a614 100644 --- a/homeassistant/components/ecovacs/sensor.py +++ b/homeassistant/components/ecovacs/sensor.py @@ -180,16 +180,17 @@ async def async_setup_entry( entities: list[EcovacsEntity] = get_supported_entitites( controller, EcovacsSensor, ENTITY_DESCRIPTIONS ) - for device in controller.devices(Capabilities): - lifespan_capability = device.capabilities.life_span - for description in LIFESPAN_ENTITY_DESCRIPTIONS: - if description.component in lifespan_capability.types: - entities.append( - EcovacsLifespanSensor(device, lifespan_capability, description) - ) - - if capability := device.capabilities.error: - entities.append(EcovacsErrorSensor(device, capability)) + entities.extend( + EcovacsLifespanSensor(device, device.capabilities.life_span, description) + for device in controller.devices(Capabilities) + for description in LIFESPAN_ENTITY_DESCRIPTIONS + if description.component in device.capabilities.life_span.types + ) + entities.extend( + EcovacsErrorSensor(device, capability) + for device in controller.devices(Capabilities) + if (capability := device.capabilities.error) + ) async_add_entities(entities) diff --git a/homeassistant/components/ecovacs/util.py b/homeassistant/components/ecovacs/util.py index ea0c75b748b..14e69cd4b61 100644 --- a/homeassistant/components/ecovacs/util.py +++ b/homeassistant/components/ecovacs/util.py @@ -31,13 +31,10 @@ def get_supported_entitites( descriptions: tuple[EcovacsCapabilityEntityDescription, ...], ) -> list[EcovacsEntity]: """Return all supported entities for all devices.""" - entities: list[EcovacsEntity] = [] - - for device in controller.devices(Capabilities): - for description in descriptions: - if isinstance(device.capabilities, description.device_capabilities) and ( - capability := description.capability_fn(device.capabilities) - ): - entities.append(entity_class(device, capability, description)) - - return entities + return [ + entity_class(device, capability, description) + for device in controller.devices(Capabilities) + for description in descriptions + if isinstance(device.capabilities, description.device_capabilities) + if (capability := description.capability_fn(device.capabilities)) + ] diff --git a/homeassistant/components/efergy/sensor.py b/homeassistant/components/efergy/sensor.py index 27d1da976b8..59b2799d37b 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -127,15 +127,15 @@ async def async_setup_entry( description, entity_registry_enabled_default=len(api.sids) > 1, ) - for sid in api.sids: - sensors.append( - EfergySensor( - api, - description, - entry.entry_id, - sid=sid, - ) + sensors.extend( + EfergySensor( + api, + description, + entry.entry_id, + sid=sid, ) + for sid in api.sids + ) async_add_entities(sensors, True) diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index 8093603eb2a..750e3172178 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -245,19 +245,17 @@ def get_forecast(ec_data, hourly) -> list[Forecast] | None: ) else: - for hour in ec_data.hourly_forecasts: - forecast_array.append( - { - ATTR_FORECAST_TIME: hour["period"].isoformat(), - ATTR_FORECAST_NATIVE_TEMP: int(hour["temperature"]), - ATTR_FORECAST_CONDITION: icon_code_to_condition( - int(hour["icon_code"]) - ), - ATTR_FORECAST_PRECIPITATION_PROBABILITY: int( - hour["precip_probability"] - ), - } - ) + forecast_array.extend( + { + ATTR_FORECAST_TIME: hour["period"].isoformat(), + ATTR_FORECAST_NATIVE_TEMP: int(hour["temperature"]), + ATTR_FORECAST_CONDITION: icon_code_to_condition(int(hour["icon_code"])), + ATTR_FORECAST_PRECIPITATION_PROBABILITY: int( + hour["precip_probability"] + ), + } + for hour in ec_data.hourly_forecasts + ) return forecast_array diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index 3848171f806..ac33265842f 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -773,8 +773,7 @@ def _setup_services( # New service to_register.append(service) - for service in old_services.values(): - to_unregister.append(service) + to_unregister.extend(old_services.values()) entry_data.services = {serv.key: serv for serv in services} diff --git a/homeassistant/components/fail2ban/sensor.py b/homeassistant/components/fail2ban/sensor.py index 5682c127c41..53490e60c54 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -47,12 +47,9 @@ async def async_setup_platform( jails = config[CONF_JAILS] log_file = config.get(CONF_FILE_PATH, DEFAULT_LOG) - device_list = [] log_parser = BanLogParser(log_file) - for jail in jails: - device_list.append(BanSensor(name, jail, log_parser)) - async_add_entities(device_list, True) + async_add_entities((BanSensor(name, jail, log_parser) for jail in jails), True) class BanSensor(SensorEntity): diff --git a/homeassistant/components/fibaro/event.py b/homeassistant/components/fibaro/event.py index 676889bbe0d..c65e8f143c6 100644 --- a/homeassistant/components/fibaro/event.py +++ b/homeassistant/components/fibaro/event.py @@ -27,13 +27,15 @@ async def async_setup_entry( """Set up the Fibaro event entities.""" controller: FibaroController = hass.data[DOMAIN][entry.entry_id] - entities = [] - for device in controller.fibaro_devices[Platform.EVENT]: - for scene_event in device.central_scene_event: - # Each scene event represents a button on a device - entities.append(FibaroEventEntity(device, scene_event)) - - async_add_entities(entities, True) + # Each scene event represents a button on a device + async_add_entities( + ( + FibaroEventEntity(device, scene_event) + for device in controller.fibaro_devices[Platform.EVENT] + for scene_event in device.central_scene_event + ), + True, + ) class FibaroEventEntity(FibaroDevice, EventEntity): diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index a7c1d93da7b..6e672e9cc97 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -106,29 +106,27 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Fibaro controller devices.""" - entities: list[SensorEntity] = [] controller: FibaroController = hass.data[DOMAIN][entry.entry_id] + entities: list[SensorEntity] = [ + FibaroSensor(device, MAIN_SENSOR_TYPES.get(device.type)) + for device in controller.fibaro_devices[Platform.SENSOR] + ] - for device in controller.fibaro_devices[Platform.SENSOR]: - entity_description = MAIN_SENSOR_TYPES.get(device.type) - - # main sensors are created even if the entity type is not known - entities.append(FibaroSensor(device, entity_description)) - - for platform in ( - Platform.BINARY_SENSOR, - Platform.CLIMATE, - Platform.COVER, - Platform.LIGHT, - Platform.LOCK, - Platform.SENSOR, - Platform.SWITCH, - ): - for device in controller.fibaro_devices[platform]: - for entity_description in ADDITIONAL_SENSOR_TYPES: - if entity_description.key in device.properties: - entities.append(FibaroAdditionalSensor(device, entity_description)) + entities.extend( + FibaroAdditionalSensor(device, entity_description) + for platform in ( + Platform.BINARY_SENSOR, + Platform.CLIMATE, + Platform.COVER, + Platform.LIGHT, + Platform.LOCK, + Platform.SWITCH, + ) + for device in controller.fibaro_devices[platform] + for entity_description in ADDITIONAL_SENSOR_TYPES + if entity_description.key in device.properties + ) async_add_entities(entities, True) diff --git a/homeassistant/components/fivem/coordinator.py b/homeassistant/components/fivem/coordinator.py index c96fa42fb3e..1fdf87fb2b7 100644 --- a/homeassistant/components/fivem/coordinator.py +++ b/homeassistant/components/fivem/coordinator.py @@ -61,9 +61,7 @@ class FiveMDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): except FiveMServerOfflineError as err: raise UpdateFailed from err - players_list: list[str] = [] - for player in server.players: - players_list.append(player.name) + players_list: list[str] = [player.name for player in server.players] players_list.sort() resources_list = server.resources diff --git a/homeassistant/components/flo/switch.py b/homeassistant/components/flo/switch.py index 7b8dfe74c97..41690c28ae4 100644 --- a/homeassistant/components/flo/switch.py +++ b/homeassistant/components/flo/switch.py @@ -34,11 +34,10 @@ async def async_setup_entry( devices: list[FloDeviceDataUpdateCoordinator] = hass.data[FLO_DOMAIN][ config_entry.entry_id ]["devices"] - entities = [] - for device in devices: - if device.device_type != "puck_oem": - entities.append(FloSwitch(device)) - async_add_entities(entities) + + async_add_entities( + [FloSwitch(device) for device in devices if device.device_type != "puck_oem"] + ) platform = entity_platform.async_get_current_platform() diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index faaffaa3fd7..2b6101c9c33 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -106,10 +106,9 @@ async def async_setup_entry( @callback def async_add_zones(api, outputs): - zone_entities = [] - for output in outputs: - zone_entities.append(ForkedDaapdZone(api, output, config_entry.entry_id)) - async_add_entities(zone_entities, False) + async_add_entities( + ForkedDaapdZone(api, output, config_entry.entry_id) for output in outputs + ) remove_add_zones_listener = async_dispatcher_connect( hass, SIGNAL_ADD_ZONES.format(config_entry.entry_id), async_add_zones @@ -433,17 +432,16 @@ class ForkedDaapdMaster(MediaPlayerEntity): # restore state await self.api.set_volume(volume=self._last_volume * 100) if self._last_outputs: - futures: list[asyncio.Task[int]] = [] - for output in self._last_outputs: - futures.append( - asyncio.create_task( - self.api.change_output( - output["id"], - selected=output["selected"], - volume=output["volume"], - ) + futures: list[asyncio.Task[int]] = [ + asyncio.create_task( + self.api.change_output( + output["id"], + selected=output["selected"], + volume=output["volume"], ) ) + for output in self._last_outputs + ] await asyncio.wait(futures) else: # enable all outputs await self.api.set_enabled_outputs( @@ -651,15 +649,14 @@ class ForkedDaapdMaster(MediaPlayerEntity): self._last_outputs = self._outputs if self._outputs: await self.api.set_volume(volume=self._tts_volume * 100) - futures = [] - for output in self._outputs: - futures.append( - asyncio.create_task( - self.api.change_output( - output["id"], selected=True, volume=self._tts_volume * 100 - ) + futures = [ + asyncio.create_task( + self.api.change_output( + output["id"], selected=True, volume=self._tts_volume * 100 ) ) + for output in self._outputs + ] await asyncio.wait(futures) async def _pause_and_wait_for_callback(self): diff --git a/homeassistant/components/freebox/alarm_control_panel.py b/homeassistant/components/freebox/alarm_control_panel.py index 8879963e7f1..4c62b928dff 100644 --- a/homeassistant/components/freebox/alarm_control_panel.py +++ b/homeassistant/components/freebox/alarm_control_panel.py @@ -39,14 +39,14 @@ async def async_setup_entry( """Set up alarm panel.""" router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id] - alarm_entities: list[AlarmControlPanelEntity] = [] - - for node in router.home_devices.values(): - if node["category"] == FreeboxHomeCategory.ALARM: - alarm_entities.append(FreeboxAlarm(hass, router, node)) - - if alarm_entities: - async_add_entities(alarm_entities, True) + async_add_entities( + ( + FreeboxAlarm(hass, router, node) + for node in router.home_devices.values() + if node["category"] == FreeboxHomeCategory.ALARM + ), + True, + ) class FreeboxAlarm(FreeboxHomeEntity, AlarmControlPanelEntity): diff --git a/homeassistant/components/freebox/binary_sensor.py b/homeassistant/components/freebox/binary_sensor.py index 1d149d3336c..a54930753a0 100644 --- a/homeassistant/components/freebox/binary_sensor.py +++ b/homeassistant/components/freebox/binary_sensor.py @@ -53,16 +53,17 @@ async def async_setup_entry( elif node["category"] == FreeboxHomeCategory.DWS: binary_entities.append(FreeboxDwsSensor(hass, router, node)) - for endpoint in node["show_endpoints"]: + binary_entities.extend( + FreeboxCoverSensor(hass, router, node) + for endpoint in node["show_endpoints"] if ( endpoint["name"] == "cover" and endpoint["ep_type"] == "signal" and endpoint.get("value") is not None - ): - binary_entities.append(FreeboxCoverSensor(hass, router, node)) + ) + ) - if binary_entities: - async_add_entities(binary_entities, True) + async_add_entities(binary_entities, True) class FreeboxHomeBinarySensor(FreeboxHomeEntity, BinarySensorEntity): diff --git a/homeassistant/components/fritzbox_callmonitor/config_flow.py b/homeassistant/components/fritzbox_callmonitor/config_flow.py index acb16acbded..ac0d3ea3337 100644 --- a/homeassistant/components/fritzbox_callmonitor/config_flow.py +++ b/homeassistant/components/fritzbox_callmonitor/config_flow.py @@ -128,12 +128,10 @@ class FritzBoxCallMonitorConfigFlow(ConfigFlow, domain=DOMAIN): async def _get_list_of_phonebook_names(self) -> list[str]: """Return list of names for all available phonebooks.""" - phonebook_names: list[str] = [] - - for phonebook_id in self._phonebook_ids: - phonebook_names.append(await self._get_name_of_phonebook(phonebook_id)) - - return phonebook_names + return [ + await self._get_name_of_phonebook(phonebook_id) + for phonebook_id in self._phonebook_ids + ] @staticmethod @callback From 4c2a54746d91354895dda2cb41eaa51543e163fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 07:43:27 -1000 Subject: [PATCH 0842/1691] Make august activity update a background task (#112652) --- homeassistant/components/august/activity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/august/activity.py b/homeassistant/components/august/activity.py index a42a5aa2d67..ae920383e40 100644 --- a/homeassistant/components/august/activity.py +++ b/homeassistant/components/august/activity.py @@ -79,6 +79,7 @@ class ActivityStream(AugustSubscriberMixin): cooldown=ACTIVITY_DEBOUNCE_COOLDOWN, immediate=True, function=partial(self._async_update_house_id, house_id), + background=True, ) update_debounce[house_id] = debouncer update_debounce_jobs[house_id] = HassJob( From c948392ebcb0082a6a53a50dbafccbfd44e12976 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 07:43:43 -1000 Subject: [PATCH 0843/1691] Make Bluetooth active coordinator debouncers run tasks in the background (#113129) --- .../bluetooth/active_update_coordinator.py | 1 + .../bluetooth/active_update_processor.py | 1 + .../test_active_update_coordinator.py | 32 ++++++++++--------- .../bluetooth/test_active_update_processor.py | 31 ++++++++---------- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/bluetooth/active_update_coordinator.py b/homeassistant/components/bluetooth/active_update_coordinator.py index 4673e6adaae..df5701a81a3 100644 --- a/homeassistant/components/bluetooth/active_update_coordinator.py +++ b/homeassistant/components/bluetooth/active_update_coordinator.py @@ -99,6 +99,7 @@ class ActiveBluetoothDataUpdateCoordinator( cooldown=POLL_DEFAULT_COOLDOWN, immediate=POLL_DEFAULT_IMMEDIATE, function=self._async_poll, + background=True, ) else: poll_debouncer.function = self._async_poll diff --git a/homeassistant/components/bluetooth/active_update_processor.py b/homeassistant/components/bluetooth/active_update_processor.py index e028017dd31..be4f6553738 100644 --- a/homeassistant/components/bluetooth/active_update_processor.py +++ b/homeassistant/components/bluetooth/active_update_processor.py @@ -92,6 +92,7 @@ class ActiveBluetoothProcessorCoordinator( cooldown=POLL_DEFAULT_COOLDOWN, immediate=POLL_DEFAULT_IMMEDIATE, function=self._async_poll, + background=True, ) else: poll_debouncer.function = self._async_poll diff --git a/tests/components/bluetooth/test_active_update_coordinator.py b/tests/components/bluetooth/test_active_update_coordinator.py index 995b208ead9..e3178f84336 100644 --- a/tests/components/bluetooth/test_active_update_coordinator.py +++ b/tests/components/bluetooth/test_active_update_coordinator.py @@ -129,7 +129,7 @@ async def test_basic_usage( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data == {"fake": "data"} @@ -175,13 +175,13 @@ async def test_bleak_error_during_polling( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data is None assert coordinator.last_poll_successful is False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO_2.rssi} assert coordinator.data == {"fake": "data"} assert coordinator.last_poll_successful is True @@ -228,13 +228,13 @@ async def test_generic_exception_during_polling( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data is None assert coordinator.last_poll_successful is False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO_2.rssi} assert coordinator.data == {"fake": "data"} assert coordinator.last_poll_successful is True @@ -280,7 +280,7 @@ async def test_polling_debounce( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # We should only get one poll because of the debounce assert coordinator.data == {"poll_count": 1} @@ -316,7 +316,9 @@ async def test_polling_debounce_with_custom_debouncer( mode=BluetoothScanningMode.ACTIVE, needs_poll_method=_needs_poll, poll_method=_poll_method, - poll_debouncer=Debouncer(hass, _LOGGER, cooldown=0.1, immediate=True), + poll_debouncer=Debouncer( + hass, _LOGGER, cooldown=0.1, immediate=True, background=True + ), ) assert coordinator.available is False # no data yet @@ -327,7 +329,7 @@ async def test_polling_debounce_with_custom_debouncer( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # We should only get one poll because of the debounce assert coordinator.data == {"poll_count": 1} @@ -371,25 +373,25 @@ async def test_polling_rejecting_the_first_time( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # First poll is rejected, so no data yet assert coordinator.data is None inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # Data is the same so no poll check assert coordinator.data is None inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO_2.rssi} # Data is different so poll is done assert coordinator.data == {"fake": "data"} inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # Data is different again so poll is done assert coordinator.data == {"fake": "data"} @@ -434,19 +436,19 @@ async def test_no_polling_after_stop_event( assert needs_poll_calls == 0 inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data == {"fake": "data"} assert needs_poll_calls == 1 hass.set_state(CoreState.stopping) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 # Should not generate a poll now inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 cancel() diff --git a/tests/components/bluetooth/test_active_update_processor.py b/tests/components/bluetooth/test_active_update_processor.py index e894ef3dab4..e854233451e 100644 --- a/tests/components/bluetooth/test_active_update_processor.py +++ b/tests/components/bluetooth/test_active_update_processor.py @@ -84,7 +84,7 @@ async def test_basic_usage( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.available is True @@ -127,10 +127,7 @@ async def test_poll_can_be_skipped( needs_poll_method=_poll_needed, poll_method=_poll, poll_debouncer=Debouncer( - hass, - _LOGGER, - cooldown=0, - immediate=True, + hass, _LOGGER, cooldown=0, immediate=True, background=True ), ) assert coordinator.available is False # no data yet @@ -142,19 +139,19 @@ async def test_poll_can_be_skipped( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": True}) flag = False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": None}, True) flag = True inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": True}) cancel() @@ -208,7 +205,7 @@ async def test_bleak_error_and_recover( # First poll fails inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": None}, False) assert ( @@ -219,7 +216,7 @@ async def test_bleak_error_and_recover( # Second poll works flag = False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": False}) cancel() @@ -272,13 +269,13 @@ async def test_poll_failure_and_recover( # First poll fails inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": None}, False) # Second poll works flag = False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": False}) cancel() @@ -329,7 +326,7 @@ async def test_second_poll_needed( # Second poll gets stuck behind first poll inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[1] == call({"testdata": 1}) cancel() @@ -381,7 +378,7 @@ async def test_rate_limit( # Third poll gets stuck behind first poll doesn't get queued inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": 1}) cancel() @@ -425,7 +422,7 @@ async def test_no_polling_after_stop_event( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 assert coordinator.available is True @@ -438,12 +435,12 @@ async def test_no_polling_after_stop_event( assert async_handle_update.mock_calls[1] == call({"testdata": 1}) hass.set_state(CoreState.stopping) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 # Should not generate a poll now that CoreState is stopping inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 cancel() From 0f414d8ac58058dee4ce154e97d9b151e5e90332 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 07:43:59 -1000 Subject: [PATCH 0844/1691] Ensure HKC debounced availability poll does not block startup (#113130) --- homeassistant/components/homekit_controller/connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index b7bc40479cf..883ec4f1a44 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -145,6 +145,7 @@ class HKDevice: cooldown=DEBOUNCE_COOLDOWN, immediate=False, function=self.async_update, + background=True, ) self._availability_callbacks: set[CALLBACK_TYPE] = set() From 5ae207001ff9459f91ed85a63129d71e04b8cdfc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 07:49:17 -1000 Subject: [PATCH 0845/1691] Avoid multiple executor jobs to add wemo devices (#112484) --- homeassistant/components/wemo/wemo_device.py | 29 ++++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/wemo/wemo_device.py b/homeassistant/components/wemo/wemo_device.py index f2bd93fa2b1..fae4f87c239 100644 --- a/homeassistant/components/wemo/wemo_device.py +++ b/homeassistant/components/wemo/wemo_device.py @@ -6,7 +6,7 @@ import asyncio from dataclasses import dataclass, fields from datetime import timedelta import logging -from typing import Literal +from typing import TYPE_CHECKING, Literal from pywemo import Insight, LongPressMixin, WeMoDevice from pywemo.exceptions import ActionException, PyWeMoException @@ -92,7 +92,7 @@ class DeviceCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-en options: Options | None = None - def __init__(self, hass: HomeAssistant, wemo: WeMoDevice, device_id: str) -> None: + def __init__(self, hass: HomeAssistant, wemo: WeMoDevice) -> None: """Initialize DeviceCoordinator.""" super().__init__( hass, @@ -102,11 +102,16 @@ class DeviceCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-en ) self.hass = hass self.wemo = wemo - self.device_id = device_id + self.device_id: str | None = None self.device_info = _create_device_info(wemo) self.supports_long_press = isinstance(wemo, LongPressMixin) self.update_lock = asyncio.Lock() + @callback + def async_setup(self, device_id: str) -> None: + """Set up the device coordinator.""" + self.device_id = device_id + def subscription_callback( self, _device: WeMoDevice, event_type: str, params: str ) -> None: @@ -130,6 +135,9 @@ class DeviceCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-en async def async_shutdown(self) -> None: """Unregister push subscriptions and remove from coordinators dict.""" await super().async_shutdown() + if TYPE_CHECKING: + # mypy doesn't known that the device_id is set in async_setup. + assert self.device_id is not None del _async_coordinators(self.hass)[self.device_id] assert self.options # Always set by async_register_device. if self.options.enable_subscription: @@ -222,7 +230,10 @@ class DeviceCoordinator(DataUpdateCoordinator[None]): # pylint: disable=hass-en async def _async_update_data(self) -> None: """Update WeMo state.""" # No need to poll if the device will push updates. - if not self.should_poll: + # The device_id will not be set until after the first + # update so we should not check should_poll until after + # the device_id is set. + if self.device_id and not self.should_poll: return # If an update is in progress, we don't do anything. @@ -266,15 +277,15 @@ async def async_register_device( hass: HomeAssistant, config_entry: ConfigEntry, wemo: WeMoDevice ) -> DeviceCoordinator: """Register a device with home assistant and enable pywemo event callbacks.""" - # Ensure proper communication with the device and get the initial state. - await hass.async_add_executor_job(wemo.get_state, True) - + device = DeviceCoordinator(hass, wemo) + await device.async_refresh() + if not device.last_update_success and device.last_exception: + raise device.last_exception device_registry = async_get_device_registry(hass) entry = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, **_create_device_info(wemo) ) - - device = DeviceCoordinator(hass, wemo, entry.id) + device.async_setup(device_id=entry.id) _async_coordinators(hass)[entry.id] = device config_entry.async_on_unload( From 21a021944f4bc01116351893310f1c486bd9e959 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:50:57 +0100 Subject: [PATCH 0846/1691] Bump aioautomower to 2024.3.2 (#113162) --- homeassistant/components/husqvarna_automower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json index 525f057c1ff..e51da21c727 100644 --- a/homeassistant/components/husqvarna_automower/manifest.json +++ b/homeassistant/components/husqvarna_automower/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "iot_class": "cloud_push", - "requirements": ["aioautomower==2024.3.0"] + "requirements": ["aioautomower==2024.3.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3cfa97059c2..7673ab02cf5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -203,7 +203,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.3.0 +aioautomower==2024.3.2 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 68f84892be2..b92edb3ea82 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -182,7 +182,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.3.0 +aioautomower==2024.3.2 # homeassistant.components.azure_devops aioazuredevops==1.3.5 From f9b2c3541850515e16b9d02d02b3f618f15ce2e5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 18:52:58 +0100 Subject: [PATCH 0847/1691] Add icon translations to Lametric (#111854) --- homeassistant/components/lametric/button.py | 4 -- homeassistant/components/lametric/icons.json | 45 ++++++++++++++++++++ homeassistant/components/lametric/number.py | 4 +- homeassistant/components/lametric/select.py | 1 - homeassistant/components/lametric/sensor.py | 1 - homeassistant/components/lametric/switch.py | 1 - tests/components/lametric/test_button.py | 5 --- tests/components/lametric/test_number.py | 3 -- tests/components/lametric/test_select.py | 2 - tests/components/lametric/test_sensor.py | 2 - tests/components/lametric/test_switch.py | 2 - 11 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/lametric/icons.json diff --git a/homeassistant/components/lametric/button.py b/homeassistant/components/lametric/button.py index 3f894495f4b..f0a452f2d02 100644 --- a/homeassistant/components/lametric/button.py +++ b/homeassistant/components/lametric/button.py @@ -31,28 +31,24 @@ BUTTONS = [ LaMetricButtonEntityDescription( key="app_next", translation_key="app_next", - icon="mdi:arrow-right-bold", entity_category=EntityCategory.CONFIG, press_fn=lambda api: api.app_next(), ), LaMetricButtonEntityDescription( key="app_previous", translation_key="app_previous", - icon="mdi:arrow-left-bold", entity_category=EntityCategory.CONFIG, press_fn=lambda api: api.app_previous(), ), LaMetricButtonEntityDescription( key="dismiss_current", translation_key="dismiss_current", - icon="mdi:bell-cancel", entity_category=EntityCategory.CONFIG, press_fn=lambda api: api.dismiss_current_notification(), ), LaMetricButtonEntityDescription( key="dismiss_all", translation_key="dismiss_all", - icon="mdi:bell-cancel", entity_category=EntityCategory.CONFIG, press_fn=lambda api: api.dismiss_all_notifications(), ), diff --git a/homeassistant/components/lametric/icons.json b/homeassistant/components/lametric/icons.json new file mode 100644 index 00000000000..7e1841272cf --- /dev/null +++ b/homeassistant/components/lametric/icons.json @@ -0,0 +1,45 @@ +{ + "entity": { + "button": { + "app_next": { + "default": "mdi:arrow-right-bold" + }, + "app_previous": { + "default": "mdi:arrow-left-bold" + }, + "dismiss_current": { + "default": "mdi:bell-cancel" + }, + "dismiss_all": { + "default": "mdi:bell-cancel" + } + }, + "number": { + "brightness": { + "default": "mdi:brightness-6" + }, + "volume": { + "default": "mdi:volume-high" + } + }, + "select": { + "brightness_mode": { + "default": "mdi:brightness-auto" + } + }, + "sensor": { + "rssi": { + "default": "mdi:wifi" + } + }, + "switch": { + "bluetooth": { + "default": "mdi:bluetooth" + } + } + }, + "services": { + "chart": "mdi:chart-areaspline-variant", + "message": "mdi:message" + } +} diff --git a/homeassistant/components/lametric/number.py b/homeassistant/components/lametric/number.py index 704a9e7fbe7..cea9debb04b 100644 --- a/homeassistant/components/lametric/number.py +++ b/homeassistant/components/lametric/number.py @@ -31,8 +31,8 @@ class LaMetricNumberEntityDescription(NumberEntityDescription): NUMBERS = [ LaMetricNumberEntityDescription( key="brightness", + translation_key="brightness", name="Brightness", - icon="mdi:brightness-6", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, @@ -43,8 +43,8 @@ NUMBERS = [ ), LaMetricNumberEntityDescription( key="volume", + translation_key="volume", name="Volume", - icon="mdi:volume-high", entity_category=EntityCategory.CONFIG, native_step=1, native_min_value=0, diff --git a/homeassistant/components/lametric/select.py b/homeassistant/components/lametric/select.py index dd81fd69877..bf9872f2791 100644 --- a/homeassistant/components/lametric/select.py +++ b/homeassistant/components/lametric/select.py @@ -32,7 +32,6 @@ SELECTS = [ LaMetricSelectEntityDescription( key="brightness_mode", translation_key="brightness_mode", - icon="mdi:brightness-auto", entity_category=EntityCategory.CONFIG, options=["auto", "manual"], current_fn=lambda device: device.display.brightness_mode.value, diff --git a/homeassistant/components/lametric/sensor.py b/homeassistant/components/lametric/sensor.py index 3e516789dce..f202a77b530 100644 --- a/homeassistant/components/lametric/sensor.py +++ b/homeassistant/components/lametric/sensor.py @@ -33,7 +33,6 @@ SENSORS = [ LaMetricSensorEntityDescription( key="rssi", translation_key="rssi", - icon="mdi:wifi", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, native_unit_of_measurement=PERCENTAGE, diff --git a/homeassistant/components/lametric/switch.py b/homeassistant/components/lametric/switch.py index d8afa2d076b..9689bb7b802 100644 --- a/homeassistant/components/lametric/switch.py +++ b/homeassistant/components/lametric/switch.py @@ -33,7 +33,6 @@ SWITCHES = [ LaMetricSwitchEntityDescription( key="bluetooth", translation_key="bluetooth", - icon="mdi:bluetooth", entity_category=EntityCategory.CONFIG, available_fn=lambda device: device.bluetooth.available, is_on_fn=lambda device: device.bluetooth.active, diff --git a/tests/components/lametric/test_button.py b/tests/components/lametric/test_button.py index f1ebbc65e44..e755329b93d 100644 --- a/tests/components/lametric/test_button.py +++ b/tests/components/lametric/test_button.py @@ -9,7 +9,6 @@ from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRE from homeassistant.components.lametric.const import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, - ATTR_ICON, STATE_UNAVAILABLE, STATE_UNKNOWN, EntityCategory, @@ -33,7 +32,6 @@ async def test_button_app_next( """Test the LaMetric next app button.""" state = hass.states.get("button.frenck_s_lametric_next_app") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:arrow-right-bold" assert state.state == STATE_UNKNOWN entry = entity_registry.async_get("button.frenck_s_lametric_next_app") @@ -80,7 +78,6 @@ async def test_button_app_previous( """Test the LaMetric previous app button.""" state = hass.states.get("button.frenck_s_lametric_previous_app") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:arrow-left-bold" assert state.state == STATE_UNKNOWN entry = entity_registry.async_get("button.frenck_s_lametric_previous_app") @@ -127,7 +124,6 @@ async def test_button_dismiss_current_notification( """Test the LaMetric dismiss current notification button.""" state = hass.states.get("button.frenck_s_lametric_dismiss_current_notification") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:bell-cancel" assert state.state == STATE_UNKNOWN entry = entity_registry.async_get( @@ -176,7 +172,6 @@ async def test_button_dismiss_all_notifications( """Test the LaMetric dismiss all notifications button.""" state = hass.states.get("button.frenck_s_lametric_dismiss_all_notifications") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:bell-cancel" assert state.state == STATE_UNKNOWN entry = entity_registry.async_get( diff --git a/tests/components/lametric/test_number.py b/tests/components/lametric/test_number.py index 5a1b617c24b..d5466abbd41 100644 --- a/tests/components/lametric/test_number.py +++ b/tests/components/lametric/test_number.py @@ -18,7 +18,6 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, - ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, STATE_UNAVAILABLE, @@ -42,7 +41,6 @@ async def test_brightness( assert state assert state.attributes.get(ATTR_DEVICE_CLASS) is None assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck's LaMetric Brightness" - assert state.attributes.get(ATTR_ICON) == "mdi:brightness-6" assert state.attributes.get(ATTR_MAX) == 100 assert state.attributes.get(ATTR_MIN) == 0 assert state.attributes.get(ATTR_STEP) == 1 @@ -92,7 +90,6 @@ async def test_volume( assert state assert state.attributes.get(ATTR_DEVICE_CLASS) is None assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck's LaMetric Volume" - assert state.attributes.get(ATTR_ICON) == "mdi:volume-high" assert state.attributes.get(ATTR_MAX) == 100 assert state.attributes.get(ATTR_MIN) == 0 assert state.attributes.get(ATTR_STEP) == 1 diff --git a/tests/components/lametric/test_select.py b/tests/components/lametric/test_select.py index 7e528e26905..bd7bc775714 100644 --- a/tests/components/lametric/test_select.py +++ b/tests/components/lametric/test_select.py @@ -14,7 +14,6 @@ from homeassistant.components.select import ( from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, - ATTR_ICON, ATTR_OPTION, STATE_UNAVAILABLE, EntityCategory, @@ -38,7 +37,6 @@ async def test_brightness_mode( assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck's LaMetric Brightness mode" ) - assert state.attributes.get(ATTR_ICON) == "mdi:brightness-auto" assert state.attributes.get(ATTR_OPTIONS) == ["auto", "manual"] assert state.state == BrightnessMode.AUTO diff --git a/tests/components/lametric/test_sensor.py b/tests/components/lametric/test_sensor.py index bb15ebd5aa4..8dff11fb450 100644 --- a/tests/components/lametric/test_sensor.py +++ b/tests/components/lametric/test_sensor.py @@ -7,7 +7,6 @@ from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, - ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, PERCENTAGE, EntityCategory, @@ -30,7 +29,6 @@ async def test_wifi_signal( assert state assert state.attributes.get(ATTR_DEVICE_CLASS) is None assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck's LaMetric Wi-Fi signal" - assert state.attributes.get(ATTR_ICON) == "mdi:wifi" assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.MEASUREMENT assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == PERCENTAGE assert state.state == "21" diff --git a/tests/components/lametric/test_switch.py b/tests/components/lametric/test_switch.py index 7ca5c800121..b81428bb402 100644 --- a/tests/components/lametric/test_switch.py +++ b/tests/components/lametric/test_switch.py @@ -15,7 +15,6 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, - ATTR_ICON, STATE_OFF, STATE_UNAVAILABLE, EntityCategory, @@ -41,7 +40,6 @@ async def test_bluetooth( assert state assert state.attributes.get(ATTR_DEVICE_CLASS) is None assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck's LaMetric Bluetooth" - assert state.attributes.get(ATTR_ICON) == "mdi:bluetooth" assert state.state == STATE_OFF entry = entity_registry.async_get(state.entity_id) From b670066c007372a757d85a3c4f71c2df959c1a43 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 12 Mar 2024 18:54:46 +0100 Subject: [PATCH 0848/1691] Add options update listener for Speedtest.Net (#112108) --- homeassistant/components/speedtestdotnet/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 4ee46478fb9..831e66d1c4e 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -39,6 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b hass.data[DOMAIN] = coordinator await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) + config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) return True @@ -50,3 +51,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> ): hass.data.pop(DOMAIN) return unload_ok + + +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Handle options update.""" + await hass.config_entries.async_reload(config_entry.entry_id) From 2cdf6b9937d1e93bfee866ede062ccae1607466d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 12 Mar 2024 19:25:27 +0100 Subject: [PATCH 0849/1691] Add binary_sensor to homeworks (#112418) * Add binary_sensor to homeworks * Update tests --- .coveragerc | 1 + .../components/homeworks/__init__.py | 23 ++++- .../components/homeworks/binary_sensor.py | 93 +++++++++++++++++++ .../components/homeworks/config_flow.py | 25 +++-- homeassistant/components/homeworks/const.py | 1 + .../components/homeworks/strings.json | 4 + tests/components/homeworks/conftest.py | 4 + .../components/homeworks/test_config_flow.py | 63 +++++++++---- 8 files changed, 183 insertions(+), 31 deletions(-) create mode 100644 homeassistant/components/homeworks/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index a595efd4bcc..c3fd8ba71ff 100644 --- a/.coveragerc +++ b/.coveragerc @@ -546,6 +546,7 @@ omit = homeassistant/components/homematic/sensor.py homeassistant/components/homematic/switch.py homeassistant/components/homeworks/__init__.py + homeassistant/components/homeworks/binary_sensor.py homeassistant/components/homeworks/button.py homeassistant/components/homeworks/light.py homeassistant/components/horizon/media_player.py diff --git a/homeassistant/components/homeworks/__init__.py b/homeassistant/components/homeworks/__init__.py index 3ab833e2b08..83ae12dffba 100644 --- a/homeassistant/components/homeworks/__init__.py +++ b/homeassistant/components/homeworks/__init__.py @@ -21,6 +21,7 @@ from homeassistant.const import ( from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType @@ -37,13 +38,14 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.LIGHT] +PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT] EVENT_BUTTON_PRESS = "homeworks_button_press" EVENT_BUTTON_RELEASE = "homeworks_button_release" DEFAULT_FADE_RATE = 1.0 +KEYPAD_LEDSTATE_POLL_COOLDOWN = 1.0 CV_FADE_RATE = vol.All(vol.Coerce(float), vol.Range(min=0, max=20)) @@ -208,6 +210,13 @@ class HomeworksKeypad: """Register callback that will be used for signals.""" self._addr = addr self._controller = controller + self._debouncer = Debouncer( + hass, + _LOGGER, + cooldown=KEYPAD_LEDSTATE_POLL_COOLDOWN, + immediate=False, + function=self._request_keypad_led_states, + ) self._hass = hass self._name = name self._id = slugify(self._name) @@ -229,3 +238,15 @@ class HomeworksKeypad: return data = {CONF_ID: self._id, CONF_NAME: self._name, "button": values[1]} self._hass.bus.async_fire(event, data) + + def _request_keypad_led_states(self) -> None: + """Query keypad led state.""" + # pylint: disable-next=protected-access + self._controller._send(f"RKLS, {self._addr}") + + async def request_keypad_led_states(self) -> None: + """Query keypad led state. + + Debounced to not storm the controller during setup. + """ + await self._debouncer.async_call() diff --git a/homeassistant/components/homeworks/binary_sensor.py b/homeassistant/components/homeworks/binary_sensor.py new file mode 100644 index 00000000000..a4d315b6821 --- /dev/null +++ b/homeassistant/components/homeworks/binary_sensor.py @@ -0,0 +1,93 @@ +"""Support for Lutron Homeworks binary sensors.""" +from __future__ import annotations + +import logging +from typing import Any + +from pyhomeworks.pyhomeworks import HW_KEYPAD_LED_CHANGED, Homeworks + +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeworksData, HomeworksEntity, HomeworksKeypad +from .const import ( + CONF_ADDR, + CONF_BUTTONS, + CONF_CONTROLLER_ID, + CONF_KEYPADS, + CONF_LED, + CONF_NUMBER, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Homeworks binary sensors.""" + data: HomeworksData = hass.data[DOMAIN][entry.entry_id] + controller = data.controller + controller_id = entry.options[CONF_CONTROLLER_ID] + devs = [] + for keypad in entry.options.get(CONF_KEYPADS, []): + for button in keypad[CONF_BUTTONS]: + if not button[CONF_LED]: + continue + dev = HomeworksBinarySensor( + controller, + data.keypads[keypad[CONF_ADDR]], + controller_id, + keypad[CONF_ADDR], + keypad[CONF_NAME], + button[CONF_NAME], + button[CONF_NUMBER], + ) + devs.append(dev) + async_add_entities(devs, True) + + +class HomeworksBinarySensor(HomeworksEntity, BinarySensorEntity): + """Homeworks Binary Sensor.""" + + _attr_has_entity_name = True + + def __init__( + self, + controller: Homeworks, + keypad: HomeworksKeypad, + controller_id: str, + addr: str, + keypad_name: str, + button_name: str, + led_number: int, + ) -> None: + """Create device with Addr, name, and rate.""" + super().__init__(controller, controller_id, addr, led_number, button_name) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{controller_id}.{addr}")}, name=keypad_name + ) + self._keypad = keypad + + async def async_added_to_hass(self) -> None: + """Call when entity is added to hass.""" + signal = f"homeworks_entity_{self._controller_id}_{self._addr}" + _LOGGER.debug("connecting %s", signal) + self.async_on_remove( + async_dispatcher_connect(self.hass, signal, self._update_callback) + ) + await self._keypad.request_keypad_led_states() + + @callback + def _update_callback(self, msg_type: str, values: list[Any]) -> None: + """Process device specific messages.""" + if msg_type != HW_KEYPAD_LED_CHANGED or len(values[1]) < self._idx: + return + self._attr_is_on = bool(values[1][self._idx - 1]) + self.async_write_ha_state() diff --git a/homeassistant/components/homeworks/config_flow.py b/homeassistant/components/homeworks/config_flow.py index ec65cef970c..b2fe4e0e022 100644 --- a/homeassistant/components/homeworks/config_flow.py +++ b/homeassistant/components/homeworks/config_flow.py @@ -9,6 +9,7 @@ from typing import Any from pyhomeworks.pyhomeworks import Homeworks import voluptuous as vol +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult @@ -44,6 +45,7 @@ from .const import ( CONF_DIMMERS, CONF_INDEX, CONF_KEYPADS, + CONF_LED, CONF_NUMBER, CONF_RATE, CONF_RELEASE_DELAY, @@ -78,6 +80,7 @@ LIGHT_EDIT = { } BUTTON_EDIT = { + vol.Optional(CONF_LED, default=False): selector.BooleanSelector(), vol.Optional(CONF_RELEASE_DELAY, default=0): selector.NumberSelector( selector.NumberSelectorConfig( min=0, @@ -380,16 +383,17 @@ async def validate_remove_button( if str(index) not in removed_indexes: items.append(item) button_number = keypad[CONF_BUTTONS][index][CONF_NUMBER] - if entity_id := entity_registry.async_get_entity_id( - BUTTON_DOMAIN, - DOMAIN, - calculate_unique_id( - handler.options[CONF_CONTROLLER_ID], - keypad[CONF_ADDR], - button_number, - ), - ): - entity_registry.async_remove(entity_id) + for domain in (BINARY_SENSOR_DOMAIN, BUTTON_DOMAIN): + if entity_id := entity_registry.async_get_entity_id( + domain, + DOMAIN, + calculate_unique_id( + handler.options[CONF_CONTROLLER_ID], + keypad[CONF_ADDR], + button_number, + ), + ): + entity_registry.async_remove(entity_id) keypad[CONF_BUTTONS] = items return {} @@ -563,6 +567,7 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN): CONF_ADDR: keypad[CONF_ADDR], CONF_BUTTONS: [ { + CONF_LED: button[CONF_LED], CONF_NAME: button[CONF_NAME], CONF_NUMBER: button[CONF_NUMBER], CONF_RELEASE_DELAY: button[CONF_RELEASE_DELAY], diff --git a/homeassistant/components/homeworks/const.py b/homeassistant/components/homeworks/const.py index a4645f1d357..8baf1b6299d 100644 --- a/homeassistant/components/homeworks/const.py +++ b/homeassistant/components/homeworks/const.py @@ -10,6 +10,7 @@ CONF_CONTROLLER_ID = "controller_id" CONF_DIMMERS = "dimmers" CONF_INDEX = "index" CONF_KEYPADS = "keypads" +CONF_LED = "led" CONF_NUMBER = "number" CONF_RATE = "rate" CONF_RELEASE_DELAY = "release_delay" diff --git a/homeassistant/components/homeworks/strings.json b/homeassistant/components/homeworks/strings.json index 3f1b6d3cc23..03c09e12888 100644 --- a/homeassistant/components/homeworks/strings.json +++ b/homeassistant/components/homeworks/strings.json @@ -60,10 +60,12 @@ "data": { "name": "[%key:common::config_flow::data::name%]", "number": "Number", + "led": "LED", "release_delay": "Release delay" }, "data_description": { "number": "Button number in the range 1 to 24", + "led": "Enable if the button has a scene select indicator", "release_delay": "Time between press and release, set to 0 to only press" }, "title": "[%key:component::homeworks::options::step::init::menu_options::add_keypad%]" @@ -92,9 +94,11 @@ }, "edit_button": { "data": { + "led": "[%key:component::homeworks::options::step::add_button::data::led%]", "release_delay": "[%key:component::homeworks::options::step::add_button::data::release_delay%]" }, "data_description": { + "led": "[%key:component::homeworks::options::step::add_button::data_description::led%]", "release_delay": "[%key:component::homeworks::options::step::add_button::data_description::release_delay%]" }, "title": "[%key:component::homeworks::options::step::edit_keypad::menu_options::select_edit_button%]" diff --git a/tests/components/homeworks/conftest.py b/tests/components/homeworks/conftest.py index d1366f89641..b446ce03c5e 100644 --- a/tests/components/homeworks/conftest.py +++ b/tests/components/homeworks/conftest.py @@ -11,6 +11,7 @@ from homeassistant.components.homeworks.const import ( CONF_CONTROLLER_ID, CONF_DIMMERS, CONF_KEYPADS, + CONF_LED, CONF_NUMBER, CONF_RATE, CONF_RELEASE_DELAY, @@ -47,16 +48,19 @@ def mock_config_entry() -> MockConfigEntry: { CONF_NAME: "Morning", CONF_NUMBER: 1, + CONF_LED: True, CONF_RELEASE_DELAY: None, }, { CONF_NAME: "Relax", CONF_NUMBER: 2, + CONF_LED: True, CONF_RELEASE_DELAY: None, }, { CONF_NAME: "Dim up", CONF_NUMBER: 3, + CONF_LED: False, CONF_RELEASE_DELAY: 0.2, }, ], diff --git a/tests/components/homeworks/test_config_flow.py b/tests/components/homeworks/test_config_flow.py index 00980b2f60c..4bdb5938f1c 100644 --- a/tests/components/homeworks/test_config_flow.py +++ b/tests/components/homeworks/test_config_flow.py @@ -5,6 +5,7 @@ from unittest.mock import ANY, MagicMock import pytest from pytest_unordered import unordered +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.homeworks.const import ( CONF_ADDR, @@ -12,6 +13,7 @@ from homeassistant.components.homeworks.const import ( CONF_DIMMERS, CONF_INDEX, CONF_KEYPADS, + CONF_LED, CONF_NUMBER, CONF_RATE, CONF_RELEASE_DELAY, @@ -163,16 +165,19 @@ async def test_import_flow( { CONF_NAME: "Morning", CONF_NUMBER: 1, + CONF_LED: True, CONF_RELEASE_DELAY: None, }, { CONF_NAME: "Relax", CONF_NUMBER: 2, + CONF_LED: True, CONF_RELEASE_DELAY: None, }, { CONF_NAME: "Dim up", CONF_NUMBER: 3, + CONF_LED: False, CONF_RELEASE_DELAY: 0.2, }, ], @@ -204,12 +209,13 @@ async def test_import_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -295,12 +301,13 @@ async def test_reconfigure_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", }, @@ -386,12 +393,13 @@ async def test_reconfigure_flow_flow_no_change( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -492,12 +500,13 @@ async def test_options_add_remove_light_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -543,12 +552,13 @@ async def test_options_add_remove_light_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -602,12 +612,13 @@ async def test_options_add_remove_keypad_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", }, @@ -750,12 +761,13 @@ async def test_options_edit_light_no_lights_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -801,6 +813,7 @@ async def test_options_add_button_flow( mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) @@ -836,6 +849,7 @@ async def test_options_add_button_flow( CONF_NAME: "Dim down", CONF_NUMBER: 4, CONF_RELEASE_DELAY: 0.2, + CONF_LED: True, }, ) await hass.async_block_till_done() @@ -850,13 +864,15 @@ async def test_options_add_button_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": True, "name": "Morning", "number": 1, "release_delay": None, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, { + "led": True, "name": "Dim down", "number": 4, "release_delay": 0.2, @@ -871,6 +887,7 @@ async def test_options_add_button_flow( await hass.async_block_till_done() # Check the new entities were added + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 3 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 4 @@ -881,6 +898,7 @@ async def test_options_add_button_flow_duplicate( mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) @@ -930,6 +948,7 @@ async def test_options_edit_button_flow( mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) @@ -974,6 +993,7 @@ async def test_options_edit_button_flow( result["flow_id"], user_input={ CONF_RELEASE_DELAY: 0, + CONF_LED: False, }, ) await hass.async_block_till_done() @@ -988,12 +1008,13 @@ async def test_options_edit_button_flow( "addr": "[02:08:02:01]", "buttons": [ { + "led": False, "name": "Morning", "number": 1, "release_delay": 0.0, }, - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -1004,6 +1025,7 @@ async def test_options_edit_button_flow( await hass.async_block_till_done() # Check the new entities were added + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 3 @@ -1061,8 +1083,8 @@ async def test_options_remove_button_flow( { "addr": "[02:08:02:01]", "buttons": [ - {"name": "Relax", "number": 2, "release_delay": None}, - {"name": "Dim up", "number": 3, "release_delay": 0.2}, + {"led": True, "name": "Relax", "number": 2, "release_delay": None}, + {"led": False, "name": "Dim up", "number": 3, "release_delay": 0.2}, ], "name": "Foyer Keypad", } @@ -1073,4 +1095,5 @@ async def test_options_remove_button_flow( await hass.async_block_till_done() # Check the entities were removed + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1 assert len(hass.states.async_entity_ids(BUTTON_DOMAIN)) == 2 From c761b825ecbea37b585a3dd3015dd660135cbddd Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 12 Mar 2024 21:01:20 +0100 Subject: [PATCH 0850/1691] Fix Axis unique ID (#112132) --- homeassistant/components/axis/entity.py | 2 +- homeassistant/components/axis/hub/hub.py | 16 ++++++---------- tests/components/axis/test_hub.py | 4 +++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/axis/entity.py b/homeassistant/components/axis/entity.py index ec827d1bd49..6542b8c55f5 100644 --- a/homeassistant/components/axis/entity.py +++ b/homeassistant/components/axis/entity.py @@ -42,7 +42,7 @@ class AxisEntity(Entity): self.hub = hub self._attr_device_info = DeviceInfo( - identifiers={(AXIS_DOMAIN, hub.unique_id)}, # type: ignore[arg-type] + identifiers={(AXIS_DOMAIN, hub.unique_id)}, serial_number=hub.unique_id, ) diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index b352a514021..4c791ba1809 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -16,7 +16,7 @@ from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.config_entries import ConfigEntry from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_when_setup @@ -38,6 +38,7 @@ class AxisHub: self.available = True self.fw_version = api.vapix.firmware_version self.product_type = api.vapix.product_type + self.unique_id = format_mac(api.vapix.serial_number) self.additional_diagnostics: dict[str, Any] = {} @@ -48,22 +49,17 @@ class AxisHub: hub: AxisHub = hass.data[AXIS_DOMAIN][config_entry.entry_id] return hub - @property - def unique_id(self) -> str | None: - """Return the unique ID (serial number) of this device.""" - return self.config.entry.unique_id - # Signals @property def signal_reachable(self) -> str: """Device specific event to signal a change in connection status.""" - return f"axis_reachable_{self.unique_id}" + return f"axis_reachable_{self.config.entry.entry_id}" @property def signal_new_address(self) -> str: """Device specific event to signal a change in device address.""" - return f"axis_new_address_{self.unique_id}" + return f"axis_new_address_{self.config.entry.entry_id}" # Callbacks @@ -100,8 +96,8 @@ class AxisHub: device_registry.async_get_or_create( config_entry_id=self.config.entry.entry_id, configuration_url=self.api.config.url, - connections={(CONNECTION_NETWORK_MAC, self.unique_id)}, # type: ignore[arg-type] - identifiers={(AXIS_DOMAIN, self.unique_id)}, # type: ignore[arg-type] + connections={(CONNECTION_NETWORK_MAC, self.unique_id)}, + identifiers={(AXIS_DOMAIN, self.unique_id)}, manufacturer=ATTR_MANUFACTURER, model=f"{self.config.model} {self.product_type}", name=self.config.name, diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index f3d0dd3a225..70b71e45b45 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -212,7 +212,9 @@ async def test_shutdown(config) -> None: entry = Mock() entry.data = config - axis_device = axis.hub.AxisHub(hass, entry, Mock()) + mock_api = Mock() + mock_api.vapix.serial_number = FORMATTED_MAC + axis_device = axis.hub.AxisHub(hass, entry, mock_api) await axis_device.shutdown(None) From 78ea9bf681dc323452ef7b8dcf1cd66fc2299a19 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 21:02:37 +0100 Subject: [PATCH 0851/1691] Use enum device class in Dexcom (#112423) Co-authored-by: Franck Nijhof --- homeassistant/components/dexcom/__init__.py | 4 +- homeassistant/components/dexcom/const.py | 13 ------ homeassistant/components/dexcom/icons.json | 12 +++++ homeassistant/components/dexcom/sensor.py | 46 ++++++++++++++------ homeassistant/components/dexcom/strings.json | 11 ++++- 5 files changed, 57 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/dexcom/__init__.py b/homeassistant/components/dexcom/__init__.py index 2053f266eb5..5ff95fae47e 100644 --- a/homeassistant/components/dexcom/__init__.py +++ b/homeassistant/components/dexcom/__init__.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging -from pydexcom import AccountError, Dexcom, SessionError +from pydexcom import AccountError, Dexcom, GlucoseReading, SessionError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME @@ -43,7 +43,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except SessionError as error: raise UpdateFailed(error) from error - coordinator = DataUpdateCoordinator( + coordinator = DataUpdateCoordinator[GlucoseReading]( hass, _LOGGER, name=DOMAIN, diff --git a/homeassistant/components/dexcom/const.py b/homeassistant/components/dexcom/const.py index 25abb1bed26..487a844eb2b 100644 --- a/homeassistant/components/dexcom/const.py +++ b/homeassistant/components/dexcom/const.py @@ -5,19 +5,6 @@ from homeassistant.const import Platform DOMAIN = "dexcom" PLATFORMS = [Platform.SENSOR] -GLUCOSE_TREND_ICON = [ - "mdi:help", - "mdi:arrow-up-thick", - "mdi:arrow-up", - "mdi:arrow-top-right", - "mdi:arrow-right", - "mdi:arrow-bottom-right", - "mdi:arrow-down", - "mdi:arrow-down-thick", - "mdi:help", - "mdi:alert-circle-outline", -] - MMOL_L = "mmol/L" MG_DL = "mg/dL" diff --git a/homeassistant/components/dexcom/icons.json b/homeassistant/components/dexcom/icons.json index 9d0b3534e17..de8355ce861 100644 --- a/homeassistant/components/dexcom/icons.json +++ b/homeassistant/components/dexcom/icons.json @@ -3,6 +3,18 @@ "sensor": { "glucose_value": { "default": "mdi:diabetes" + }, + "glucose_trend": { + "default": "mdi:help", + "state": { + "rising_quickly": "mdi:arrow-up-thick", + "rising": "mdi:arrow-up", + "rising_slightly": "mdi:arrow-top-right", + "steady": "mdi:arrow-right", + "falling_slightly": "mdi:arrow-bottom-right", + "falling": "mdi:arrow-down", + "falling_quickly": "mdi:arrow-down-thick" + } } } } diff --git a/homeassistant/components/dexcom/sensor.py b/homeassistant/components/dexcom/sensor.py index 4c2e46ba06c..10b30f39fcb 100644 --- a/homeassistant/components/dexcom/sensor.py +++ b/homeassistant/components/dexcom/sensor.py @@ -2,7 +2,9 @@ from __future__ import annotations -from homeassistant.components.sensor import SensorEntity +from pydexcom import GlucoseReading + +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_UNIT_OF_MEASUREMENT, CONF_USERNAME from homeassistant.core import HomeAssistant @@ -13,7 +15,17 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, ) -from .const import DOMAIN, GLUCOSE_TREND_ICON, MG_DL +from .const import DOMAIN, MG_DL + +TRENDS = { + 1: "rising_quickly", + 2: "rising", + 3: "rising_slightly", + 4: "steady", + 5: "falling_slightly", + 6: "falling", + 7: "falling_quickly", +} async def async_setup_entry( @@ -35,13 +47,19 @@ async def async_setup_entry( ) -class DexcomSensorEntity(CoordinatorEntity, SensorEntity): +class DexcomSensorEntity( + CoordinatorEntity[DataUpdateCoordinator[GlucoseReading]], SensorEntity +): """Base Dexcom sensor entity.""" _attr_has_entity_name = True def __init__( - self, coordinator: DataUpdateCoordinator, username: str, entry_id: str, key: str + self, + coordinator: DataUpdateCoordinator[GlucoseReading], + username: str, + entry_id: str, + key: str, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) @@ -81,6 +99,8 @@ class DexcomGlucoseTrendSensor(DexcomSensorEntity): """Representation of a Dexcom glucose trend sensor.""" _attr_translation_key = "glucose_trend" + _attr_device_class = SensorDeviceClass.ENUM + _attr_options = list(TRENDS.values()) def __init__( self, coordinator: DataUpdateCoordinator, username: str, entry_id: str @@ -89,15 +109,15 @@ class DexcomGlucoseTrendSensor(DexcomSensorEntity): super().__init__(coordinator, username, entry_id, "trend") @property - def icon(self): - """Return the icon for the frontend.""" - if self.coordinator.data: - return GLUCOSE_TREND_ICON[self.coordinator.data.trend] - return GLUCOSE_TREND_ICON[0] - - @property - def native_value(self): + def native_value(self) -> str | None: """Return the state of the sensor.""" if self.coordinator.data: - return self.coordinator.data.trend_description + return TRENDS.get(self.coordinator.data.trend) return None + + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and ( + self.coordinator.data is None or self.coordinator.data.trend != 9 + ) diff --git a/homeassistant/components/dexcom/strings.json b/homeassistant/components/dexcom/strings.json index 7efc2708bcc..91b5725d918 100644 --- a/homeassistant/components/dexcom/strings.json +++ b/homeassistant/components/dexcom/strings.json @@ -35,7 +35,16 @@ "name": "Glucose value" }, "glucose_trend": { - "name": "Glucose trend" + "name": "Glucose trend", + "state": { + "rising_quickly": "Rising quickly", + "rising": "Rising", + "rising_slightly": "Rising slightly", + "steady": "Steady", + "falling_slightly": "Falling slightly", + "falling": "Falling", + "falling_quickly": "Falling quickly" + } } } } From 1dc3582778d9934897a48141b534537e5463737b Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Wed, 13 Mar 2024 06:06:09 +1000 Subject: [PATCH 0852/1691] Add icons to Teslemetry (#112546) --- .../components/teslemetry/icons.json | 53 ++++++++++++++++++ homeassistant/components/teslemetry/sensor.py | 14 ----- .../teslemetry/snapshots/test_sensor.ambr | 54 +++++++------------ 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/teslemetry/icons.json b/homeassistant/components/teslemetry/icons.json index a4521b52945..4ce852537d7 100644 --- a/homeassistant/components/teslemetry/icons.json +++ b/homeassistant/components/teslemetry/icons.json @@ -13,6 +13,59 @@ } } } + }, + "sensor": { + "battery_power": { + "default": "mdi:home-battery" + }, + "drive_state_active_route_destination": { + "default": "mdi:routes" + }, + "drive_state_active_route_minutes_to_arrival": { + "default": "mdi:routes-clock" + }, + "drive_state_shift_state": { + "default": "mdi:car-shift-pattern", + "state": { + "d": "mdi:alpha-d", + "n": "mdi:alpha-n", + "p": "mdi:alpha-p", + "r": "mdi:alpha-r" + } + }, + "energy_left": { + "default": "mdi:battery" + }, + "generator_power": { + "default": "mdi:generator-stationary" + }, + "grid_power": { + "default": "mdi:transmission-tower" + }, + "grid_services_power": { + "default": "mdi:transmission-tower" + }, + "load_power": { + "default": "mdi:power-plug" + }, + "solar_power": { + "default": "mdi:solar-power" + }, + "total_pack_energy": { + "default": "mdi:battery-high" + }, + "vin": { + "default": "mdi:car-electric" + }, + "wall_connector_fault_state": { + "default": "mdi:ev-station" + }, + "wall_connector_power": { + "default": "mdi:ev-station" + }, + "wall_connector_state": { + "default": "mdi:ev-station" + } } } } diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index d592135ab8e..af7000a3127 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -128,7 +128,6 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( ), TeslemetrySensorEntityDescription( key="drive_state_shift_state", - icon="mdi:car-shift-pattern", options=["p", "d", "r", "n"], device_class=SensorDeviceClass.ENUM, value_fn=lambda x: x.lower() if isinstance(x, str) else x, @@ -243,7 +242,6 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( ), TeslemetrySensorEntityDescription( key="drive_state_active_route_destination", - icon="mdi:map-marker", entity_category=EntityCategory.DIAGNOSTIC, ), ) @@ -256,7 +254,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:solar-power", ), SensorEntityDescription( key="energy_left", @@ -266,7 +263,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY_STORAGE, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:battery", ), SensorEntityDescription( key="total_pack_energy", @@ -276,7 +272,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY_STORAGE, entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:battery-high", entity_registry_enabled_default=False, ), SensorEntityDescription( @@ -293,7 +288,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:home-battery", ), SensorEntityDescription( key="load_power", @@ -302,7 +296,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:power-plug", ), SensorEntityDescription( key="grid_power", @@ -311,7 +304,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:transmission-tower", ), SensorEntityDescription( key="grid_services_power", @@ -320,7 +312,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:transmission-tower", ), SensorEntityDescription( key="generator_power", @@ -329,7 +320,6 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:generator-stationary", entity_registry_enabled_default=False, ), ) @@ -339,13 +329,11 @@ WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( key="wall_connector_state", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - icon="mdi:ev-station", ), SensorEntityDescription( key="wall_connector_fault_state", entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, - icon="mdi:ev-station", ), SensorEntityDescription( key="wall_connector_power", @@ -354,11 +342,9 @@ WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( suggested_unit_of_measurement=UnitOfPower.KILO_WATT, suggested_display_precision=2, device_class=SensorDeviceClass.POWER, - icon="mdi:ev-station", ), SensorEntityDescription( key="vin", - icon="mdi:car-electric", ), ) diff --git a/tests/components/teslemetry/snapshots/test_sensor.ambr b/tests/components/teslemetry/snapshots/test_sensor.ambr index a384f91fc67..64e3a6ee8ab 100644 --- a/tests/components/teslemetry/snapshots/test_sensor.ambr +++ b/tests/components/teslemetry/snapshots/test_sensor.ambr @@ -30,7 +30,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:home-battery', + 'original_icon': None, 'original_name': 'Battery power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -45,7 +45,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Energy Site Battery power', - 'icon': 'mdi:home-battery', 'state_class': , 'unit_of_measurement': , }), @@ -87,7 +86,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:battery', + 'original_icon': None, 'original_name': 'Energy left', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -102,7 +101,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'energy_storage', 'friendly_name': 'Energy Site Energy left', - 'icon': 'mdi:battery', 'state_class': , 'unit_of_measurement': , }), @@ -144,7 +142,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:generator-stationary', + 'original_icon': None, 'original_name': 'Generator power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -159,7 +157,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Energy Site Generator power', - 'icon': 'mdi:generator-stationary', 'state_class': , 'unit_of_measurement': , }), @@ -201,7 +198,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:transmission-tower', + 'original_icon': None, 'original_name': 'Grid power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -216,7 +213,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Energy Site Grid power', - 'icon': 'mdi:transmission-tower', 'state_class': , 'unit_of_measurement': , }), @@ -258,7 +254,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:transmission-tower', + 'original_icon': None, 'original_name': 'Grid services power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -273,7 +269,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Energy Site Grid services power', - 'icon': 'mdi:transmission-tower', 'state_class': , 'unit_of_measurement': , }), @@ -315,7 +310,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:power-plug', + 'original_icon': None, 'original_name': 'Load power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -330,7 +325,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Energy Site Load power', - 'icon': 'mdi:power-plug', 'state_class': , 'unit_of_measurement': , }), @@ -425,7 +419,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:solar-power', + 'original_icon': None, 'original_name': 'Solar power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -440,7 +434,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Energy Site Solar power', - 'icon': 'mdi:solar-power', 'state_class': , 'unit_of_measurement': , }), @@ -482,7 +475,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:battery-high', + 'original_icon': None, 'original_name': 'Total pack energy', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -497,7 +490,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'energy_storage', 'friendly_name': 'Energy Site Total pack energy', - 'icon': 'mdi:battery-high', 'state_class': , 'unit_of_measurement': , }), @@ -893,7 +885,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:map-marker', + 'original_icon': None, 'original_name': 'Destination', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -907,7 +899,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Test Destination', - 'icon': 'mdi:map-marker', }), 'context': , 'entity_id': 'sensor.test_destination', @@ -1317,7 +1308,7 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:car-shift-pattern', + 'original_icon': None, 'original_name': 'Shift state', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -1332,7 +1323,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'enum', 'friendly_name': 'Test Shift state', - 'icon': 'mdi:car-shift-pattern', 'options': list([ 'p', 'd', @@ -1839,7 +1829,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Fault state code', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -1853,7 +1843,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Wall Connector Fault state code', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'sensor.wall_connector_fault_state_code', @@ -1885,7 +1874,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Fault state code', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -1899,7 +1888,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Wall Connector Fault state code', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'sensor.wall_connector_fault_state_code_2', @@ -1939,7 +1927,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -1954,7 +1942,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Wall Connector Power', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -1996,7 +1983,7 @@ }), }), 'original_device_class': , - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'Power', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -2011,7 +1998,6 @@ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Wall Connector Power', - 'icon': 'mdi:ev-station', 'state_class': , 'unit_of_measurement': , }), @@ -2045,7 +2031,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'State code', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -2059,7 +2045,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Wall Connector State code', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'sensor.wall_connector_state_code', @@ -2091,7 +2076,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:ev-station', + 'original_icon': None, 'original_name': 'State code', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -2105,7 +2090,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Wall Connector State code', - 'icon': 'mdi:ev-station', }), 'context': , 'entity_id': 'sensor.wall_connector_state_code_2', @@ -2137,7 +2121,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car-electric', + 'original_icon': None, 'original_name': 'Vehicle', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -2151,7 +2135,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Wall Connector Vehicle', - 'icon': 'mdi:car-electric', }), 'context': , 'entity_id': 'sensor.wall_connector_vehicle', @@ -2183,7 +2166,7 @@ 'options': dict({ }), 'original_device_class': None, - 'original_icon': 'mdi:car-electric', + 'original_icon': None, 'original_name': 'Vehicle', 'platform': 'teslemetry', 'previous_unique_id': None, @@ -2197,7 +2180,6 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Wall Connector Vehicle', - 'icon': 'mdi:car-electric', }), 'context': , 'entity_id': 'sensor.wall_connector_vehicle_2', From 182a1fe3a3f10d0670592db4d1c5906c95ac522b Mon Sep 17 00:00:00 2001 From: mrchi Date: Wed, 13 Mar 2024 04:12:24 +0800 Subject: [PATCH 0853/1691] Bump openwrt-luci-rpc version to 1.1.17 (#112796) --- homeassistant/components/luci/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/luci/manifest.json b/homeassistant/components/luci/manifest.json index 2412aaad0a1..597aad30648 100644 --- a/homeassistant/components/luci/manifest.json +++ b/homeassistant/components/luci/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/luci", "iot_class": "local_polling", "loggers": ["openwrt_luci_rpc"], - "requirements": ["openwrt-luci-rpc==1.1.16"] + "requirements": ["openwrt-luci-rpc==1.1.17"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7673ab02cf5..b21ac85a8e8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1468,7 +1468,7 @@ opensensemap-api==0.2.0 openwebifpy==4.2.4 # homeassistant.components.luci -openwrt-luci-rpc==1.1.16 +openwrt-luci-rpc==1.1.17 # homeassistant.components.ubus openwrt-ubus-rpc==0.0.2 From 848012871fec58c034971e8bc382bdd10bc47747 Mon Sep 17 00:00:00 2001 From: Jeef Date: Tue, 12 Mar 2024 14:13:30 -0600 Subject: [PATCH 0854/1691] Bump weatherflow4py to 0.1.17 (#112661) --- homeassistant/components/weatherflow_cloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/weatherflow_cloud/manifest.json b/homeassistant/components/weatherflow_cloud/manifest.json index 4cb0f5296e5..72a49c0cf19 100644 --- a/homeassistant/components/weatherflow_cloud/manifest.json +++ b/homeassistant/components/weatherflow_cloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "iot_class": "cloud_polling", - "requirements": ["weatherflow4py==0.1.14"] + "requirements": ["weatherflow4py==0.1.17"] } diff --git a/requirements_all.txt b/requirements_all.txt index b21ac85a8e8..0f36748412a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2833,7 +2833,7 @@ watchdog==2.3.1 waterfurnace==1.1.0 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.14 +weatherflow4py==0.1.17 # homeassistant.components.webmin webmin-xmlrpc==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b92edb3ea82..8f9db42c5fc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2174,7 +2174,7 @@ wallbox==0.6.0 watchdog==2.3.1 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.14 +weatherflow4py==0.1.17 # homeassistant.components.webmin webmin-xmlrpc==0.0.2 From 46ce438b6d0013135a13ac5f64997720d334a326 Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:14:27 -0400 Subject: [PATCH 0855/1691] Sonos reduce test time (#111688) --- tests/components/sonos/test_init.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index c073f9f636e..17e04e64749 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -369,7 +369,7 @@ async def test_async_poll_manual_hosts_6( "homeassistant.components.sonos.DISCOVERY_INTERVAL" ) as mock_discovery_interval: # Speed up manual discovery interval so second iteration runs sooner - mock_discovery_interval.total_seconds = Mock(side_effect=[0.5, 60]) + mock_discovery_interval.total_seconds = Mock(side_effect=[0.0, 60]) await _setup_hass(hass) assert "media_player.bedroom" in entity_registry.entities @@ -377,10 +377,6 @@ async def test_async_poll_manual_hosts_6( with caplog.at_level(logging.DEBUG): caplog.clear() - # The discovery events should not fire, wait with a timeout. - with pytest.raises(TimeoutError): - async with asyncio.timeout(1.0): - await speaker_1_activity.event.wait() await hass.async_block_till_done() assert "Activity on Living Room" not in caplog.text assert "Activity on Bedroom" not in caplog.text From b9837a561b36f2671cc07cfb90a43f2e19ac6d43 Mon Sep 17 00:00:00 2001 From: On Freund Date: Tue, 12 Mar 2024 22:20:14 +0200 Subject: [PATCH 0856/1691] Use friendly name for camera media source (#110882) --- .../components/camera/media_source.py | 16 ++++++--- tests/components/camera/conftest.py | 35 +++++++++++++++++++ tests/components/camera/test_media_source.py | 21 +++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/camera/media_source.py b/homeassistant/components/camera/media_source.py index cf896ee0456..4bb6ed5f921 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -13,6 +13,7 @@ from homeassistant.components.media_source.models import ( PlayMedia, ) from homeassistant.components.stream import FORMAT_CONTENT_TYPE, HLS_PROVIDER +from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_component import EntityComponent @@ -26,13 +27,20 @@ async def async_get_media_source(hass: HomeAssistant) -> CameraMediaSource: return CameraMediaSource(hass) -def _media_source_for_camera(camera: Camera, content_type: str) -> BrowseMediaSource: +def _media_source_for_camera( + hass: HomeAssistant, camera: Camera, content_type: str +) -> BrowseMediaSource: + camera_state = hass.states.get(camera.entity_id) + title = camera.name + if camera_state: + title = camera_state.attributes.get(ATTR_FRIENDLY_NAME, camera.name) + return BrowseMediaSource( domain=DOMAIN, identifier=camera.entity_id, media_class=MediaClass.VIDEO, media_content_type=content_type, - title=camera.name, + title=title, thumbnail=f"/api/camera_proxy/{camera.entity_id}", can_play=True, can_expand=False, @@ -90,7 +98,7 @@ class CameraMediaSource(MediaSource): async def _filter_browsable_camera(camera: Camera) -> BrowseMediaSource | None: stream_type = camera.frontend_stream_type if stream_type is None: - return _media_source_for_camera(camera, camera.content_type) + return _media_source_for_camera(self.hass, camera, camera.content_type) if not can_stream_hls: return None @@ -98,7 +106,7 @@ class CameraMediaSource(MediaSource): if stream_type != StreamType.HLS and not (await camera.stream_source()): return None - return _media_source_for_camera(camera, content_type) + return _media_source_for_camera(self.hass, camera, content_type) component: EntityComponent[Camera] = self.hass.data[DOMAIN] results = await asyncio.gather( diff --git a/tests/components/camera/conftest.py b/tests/components/camera/conftest.py index 37c3ba8057c..c1d630de126 100644 --- a/tests/components/camera/conftest.py +++ b/tests/components/camera/conftest.py @@ -8,6 +8,7 @@ from homeassistant.components import camera from homeassistant.components.camera.const import StreamType from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.setup import async_setup_component from .common import WEBRTC_ANSWER @@ -70,3 +71,37 @@ async def mock_camera_web_rtc_fixture(hass): return_value=WEBRTC_ANSWER, ): yield + + +@pytest.fixture(name="mock_camera_with_device") +async def mock_camera_with_device_fixture(): + """Initialize a demo camera platform with a device.""" + dev_info = DeviceInfo( + identifiers={("camera", "test_unique_id")}, + name="Test Camera Device", + ) + + class UniqueIdMock(PropertyMock): + def __get__(self, obj, obj_type=None): + return obj.name + + with patch( + "homeassistant.components.camera.Camera.has_entity_name", + new_callable=PropertyMock(return_value=True), + ), patch( + "homeassistant.components.camera.Camera.unique_id", new=UniqueIdMock() + ), patch( + "homeassistant.components.camera.Camera.device_info", + new_callable=PropertyMock(return_value=dev_info), + ): + yield + + +@pytest.fixture(name="mock_camera_with_no_name") +async def mock_camera_with_no_name_fixture(mock_camera_with_device): + """Initialize a demo camera platform with a device and no name.""" + with patch( + "homeassistant.components.camera.Camera._attr_name", + new_callable=PropertyMock(return_value=None), + ): + yield diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index 5e8fbbd1fb2..bab6d3a236b 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -17,6 +17,26 @@ async def setup_media_source(hass): assert await async_setup_component(hass, "media_source", {}) +async def test_device_with_device( + hass: HomeAssistant, mock_camera_with_device, mock_camera +) -> None: + """Test browsing when camera has a device and a name.""" + item = await media_source.async_browse_media(hass, "media-source://camera") + assert item.not_shown == 2 + assert len(item.children) == 1 + assert item.children[0].title == "Test Camera Device Demo camera without stream" + + +async def test_device_with_no_name( + hass: HomeAssistant, mock_camera_with_no_name, mock_camera +) -> None: + """Test browsing when camera has device and name == None.""" + item = await media_source.async_browse_media(hass, "media-source://camera") + assert item.not_shown == 2 + assert len(item.children) == 1 + assert item.children[0].title == "Test Camera Device Demo camera without stream" + + async def test_browsing_hls(hass: HomeAssistant, mock_camera_hls) -> None: """Test browsing HLS camera media source.""" item = await media_source.async_browse_media(hass, "media-source://camera") @@ -42,6 +62,7 @@ async def test_browsing_mjpeg(hass: HomeAssistant, mock_camera) -> None: assert len(item.children) == 1 assert item.not_shown == 2 assert item.children[0].media_content_type == "image/jpg" + assert item.children[0].title == "Demo camera without stream" async def test_browsing_web_rtc(hass: HomeAssistant, mock_camera_web_rtc) -> None: From 4c82196fc8965f753c362d3dece7c4e4ac6937d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 10:49:31 -1000 Subject: [PATCH 0857/1691] Avoid some event loop overhead for ESPHome listeners (#113173) --- homeassistant/components/esphome/dashboard.py | 2 +- homeassistant/components/esphome/manager.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/dashboard.py b/homeassistant/components/esphome/dashboard.py index 54a593fe0cc..b8a72ac4398 100644 --- a/homeassistant/components/esphome/dashboard.py +++ b/homeassistant/components/esphome/dashboard.py @@ -103,7 +103,7 @@ class ESPHomeDashboardManager: await dashboard.async_shutdown() self._cancel_shutdown = hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, on_hass_stop + EVENT_HOMEASSISTANT_STOP, on_hass_stop, run_immediately=True ) new_data = {"info": {"addon_slug": addon_slug, "host": host, "port": port}} diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index ac33265842f..f89e79aae2b 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -543,11 +543,15 @@ class ESPHomeManager: # "Unable to remove unknown listener # .onetime_listener>" entry_data.cleanup_callbacks.append( - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, self.on_stop) + hass.bus.async_listen( + EVENT_HOMEASSISTANT_STOP, self.on_stop, run_immediately=True + ) ) entry_data.cleanup_callbacks.append( hass.bus.async_listen( - EVENT_LOGGING_CHANGED, self._async_handle_logging_changed + EVENT_LOGGING_CHANGED, + self._async_handle_logging_changed, + run_immediately=True, ) ) From df03e704f89899658e078676df6eba5f12fa36bd Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:49:59 +0100 Subject: [PATCH 0858/1691] Add "language" to strings.json (#112869) --- homeassistant/components/conversation/strings.json | 4 ++-- homeassistant/components/google_translate/strings.json | 2 +- homeassistant/components/google_travel_time/strings.json | 2 +- homeassistant/components/nut/strings.json | 4 +++- homeassistant/components/openweathermap/strings.json | 4 ++-- homeassistant/components/tts/strings.json | 4 ++-- homeassistant/strings.json | 3 ++- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/conversation/strings.json b/homeassistant/components/conversation/strings.json index 255e6cec430..3150623ba65 100644 --- a/homeassistant/components/conversation/strings.json +++ b/homeassistant/components/conversation/strings.json @@ -10,7 +10,7 @@ "description": "Transcribed text input." }, "language": { - "name": "Language", + "name": "[%key:common::config_flow::data::language%]", "description": "Language of text. Defaults to server language." }, "agent_id": { @@ -28,7 +28,7 @@ "description": "Reloads the intent configuration.", "fields": { "language": { - "name": "[%key:component::conversation::services::process::fields::language::name%]", + "name": "[%key:common::config_flow::data::language%]", "description": "Language to clear cached intents for. Defaults to server language." }, "agent_id": { diff --git a/homeassistant/components/google_translate/strings.json b/homeassistant/components/google_translate/strings.json index a83e61f01f9..1ff177e4b45 100644 --- a/homeassistant/components/google_translate/strings.json +++ b/homeassistant/components/google_translate/strings.json @@ -3,7 +3,7 @@ "step": { "user": { "data": { - "language": "Language", + "language": "[%key:common::config_flow::data::language%]", "tld": "TLD" } } diff --git a/homeassistant/components/google_travel_time/strings.json b/homeassistant/components/google_travel_time/strings.json index 3cfcd3cedb3..2c7840b23d8 100644 --- a/homeassistant/components/google_travel_time/strings.json +++ b/homeassistant/components/google_travel_time/strings.json @@ -27,7 +27,7 @@ "description": "You can optionally specify either a Departure Time or Arrival Time. If specifying a departure time, you can enter `now`, a Unix timestamp, or a 24 hour time string like `08:00:00`. If specifying an arrival time, you can use a Unix timestamp or a 24 hour time string like `08:00:00`", "data": { "mode": "Travel Mode", - "language": "Language", + "language": "[%key:common::config_flow::data::language%]", "time_type": "Time Type", "time": "Time", "avoid": "Avoid", diff --git a/homeassistant/components/nut/strings.json b/homeassistant/components/nut/strings.json index 7347744d56f..3c446926fe0 100644 --- a/homeassistant/components/nut/strings.json +++ b/homeassistant/components/nut/strings.json @@ -166,7 +166,9 @@ "ups_delay_reboot": { "name": "UPS reboot delay" }, "ups_delay_shutdown": { "name": "UPS shutdown delay" }, "ups_delay_start": { "name": "Load restart delay" }, - "ups_display_language": { "name": "Language" }, + "ups_display_language": { + "name": "[%key:common::config_flow::data::language%]" + }, "ups_efficiency": { "name": "Efficiency" }, "ups_id": { "name": "System identifier" }, "ups_load": { "name": "Load" }, diff --git a/homeassistant/components/openweathermap/strings.json b/homeassistant/components/openweathermap/strings.json index a29a8952434..c53b685af91 100644 --- a/homeassistant/components/openweathermap/strings.json +++ b/homeassistant/components/openweathermap/strings.json @@ -11,7 +11,7 @@ "user": { "data": { "api_key": "[%key:common::config_flow::data::api_key%]", - "language": "Language", + "language": "[%key:common::config_flow::data::language%]", "latitude": "[%key:common::config_flow::data::latitude%]", "longitude": "[%key:common::config_flow::data::longitude%]", "mode": "[%key:common::config_flow::data::mode%]", @@ -25,7 +25,7 @@ "step": { "init": { "data": { - "language": "Language", + "language": "[%key:common::config_flow::data::language%]", "mode": "[%key:common::config_flow::data::mode%]" } } diff --git a/homeassistant/components/tts/strings.json b/homeassistant/components/tts/strings.json index 2f0208ef8b5..2d1ac379c26 100644 --- a/homeassistant/components/tts/strings.json +++ b/homeassistant/components/tts/strings.json @@ -17,7 +17,7 @@ "description": "Stores this message locally so that when the text is requested again, the output can be produced more quickly." }, "language": { - "name": "Language", + "name": "[%key:common::config_flow::data::language%]", "description": "Language to use for speech generation." }, "options": { @@ -43,7 +43,7 @@ "description": "[%key:component::tts::services::say::fields::cache::description%]" }, "language": { - "name": "[%key:component::tts::services::say::fields::language::name%]", + "name": "[%key:common::config_flow::data::language%]", "description": "[%key:component::tts::services::say::fields::language::description%]" }, "options": { diff --git a/homeassistant/strings.json b/homeassistant/strings.json index cfe1061bce6..2f112d45dc0 100644 --- a/homeassistant/strings.json +++ b/homeassistant/strings.json @@ -96,7 +96,8 @@ "location": "Location", "pin": "PIN code", "mode": "Mode", - "path": "Path" + "path": "Path", + "language": "Language" }, "create_entry": { "authenticated": "Successfully authenticated" From 20647af5aeebcb686cd34ef489c65fb0723eb0aa Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 12 Mar 2024 14:51:13 -0600 Subject: [PATCH 0859/1691] Move Notion coordinator to its own module (#112756) Co-authored-by: Cretezy --- .coveragerc | 1 + homeassistant/components/notion/__init__.py | 140 +-------------- .../components/notion/binary_sensor.py | 4 +- .../components/notion/coordinator.py | 164 ++++++++++++++++++ .../components/notion/diagnostics.py | 5 +- homeassistant/components/notion/sensor.py | 4 +- 6 files changed, 176 insertions(+), 142 deletions(-) create mode 100644 homeassistant/components/notion/coordinator.py diff --git a/.coveragerc b/.coveragerc index c3fd8ba71ff..a60f216af1b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -891,6 +891,7 @@ omit = homeassistant/components/notify_events/notify.py homeassistant/components/notion/__init__.py homeassistant/components/notion/binary_sensor.py + homeassistant/components/notion/coordinator.py homeassistant/components/notion/sensor.py homeassistant/components/notion/util.py homeassistant/components/nsw_fuel_station/sensor.py diff --git a/homeassistant/components/notion/__init__.py b/homeassistant/components/notion/__init__.py index 9b62eb840c0..ca45e3a6d16 100644 --- a/homeassistant/components/notion/__init__.py +++ b/homeassistant/components/notion/__init__.py @@ -2,8 +2,6 @@ from __future__ import annotations -import asyncio -from dataclasses import dataclass, field from datetime import timedelta from typing import Any from uuid import UUID @@ -11,8 +9,6 @@ from uuid import UUID from aionotion.bridge.models import Bridge from aionotion.errors import InvalidCredentialsError, NotionError from aionotion.listener.models import Listener, ListenerKind -from aionotion.sensor.models import Sensor -from aionotion.user.models import UserPreferences from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform @@ -25,11 +21,7 @@ from homeassistant.helpers import ( ) from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import EntityDescription -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, - UpdateFailed, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( CONF_REFRESH_TOKEN, @@ -47,6 +39,7 @@ from .const import ( SENSOR_TEMPERATURE, SENSOR_WINDOW_HINGED, ) +from .coordinator import NotionDataUpdateCoordinator from .util import async_get_client_with_credentials, async_get_client_with_refresh_token PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] @@ -54,11 +47,6 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] ATTR_SYSTEM_MODE = "system_mode" ATTR_SYSTEM_NAME = "system_name" -DATA_BRIDGES = "bridges" -DATA_LISTENERS = "listeners" -DATA_SENSORS = "sensors" -DATA_USER_PREFERENCES = "user_preferences" - DEFAULT_SCAN_INTERVAL = timedelta(minutes=1) CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False) @@ -88,57 +76,6 @@ def is_uuid(value: str) -> bool: return True -@dataclass -class NotionData: - """Define a manager class for Notion data.""" - - hass: HomeAssistant - entry: ConfigEntry - - # Define a dict of bridges, indexed by bridge ID (an integer): - bridges: dict[int, Bridge] = field(default_factory=dict) - - # Define a dict of listeners, indexed by listener UUID (a string): - listeners: dict[str, Listener] = field(default_factory=dict) - - # Define a dict of sensors, indexed by sensor UUID (a string): - sensors: dict[str, Sensor] = field(default_factory=dict) - - # Define a user preferences response object: - user_preferences: UserPreferences | None = field(default=None) - - def update_bridges(self, bridges: list[Bridge]) -> None: - """Update the bridges.""" - for bridge in bridges: - # If a new bridge is discovered, register it: - if bridge.id not in self.bridges: - _async_register_new_bridge(self.hass, self.entry, bridge) - self.bridges[bridge.id] = bridge - - def update_listeners(self, listeners: list[Listener]) -> None: - """Update the listeners.""" - self.listeners = {listener.id: listener for listener in listeners} - - def update_sensors(self, sensors: list[Sensor]) -> None: - """Update the sensors.""" - self.sensors = {sensor.uuid: sensor for sensor in sensors} - - def update_user_preferences(self, user_preferences: UserPreferences) -> None: - """Update the user preferences.""" - self.user_preferences = user_preferences - - def asdict(self) -> dict[str, Any]: - """Represent this dataclass (and its Pydantic contents) as a dict.""" - data: dict[str, Any] = { - DATA_BRIDGES: [item.to_dict() for item in self.bridges.values()], - DATA_LISTENERS: [item.to_dict() for item in self.listeners.values()], - DATA_SENSORS: [item.to_dict() for item in self.sensors.values()], - } - if self.user_preferences: - data[DATA_USER_PREFERENCES] = self.user_preferences.to_dict() - return data - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Notion as a config entry.""" entry_updates: dict[str, Any] = {"data": {**entry.data}} @@ -188,51 +125,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create a callback to save the refresh token when it changes: entry.async_on_unload(client.add_refresh_token_callback(async_save_refresh_token)) - async def async_update() -> NotionData: - """Get the latest data from the Notion API.""" - data = NotionData(hass=hass, entry=entry) - - try: - async with asyncio.TaskGroup() as tg: - bridges = tg.create_task(client.bridge.async_all()) - listeners = tg.create_task(client.listener.async_all()) - sensors = tg.create_task(client.sensor.async_all()) - user_preferences = tg.create_task(client.user.async_preferences()) - except BaseExceptionGroup as err: - result = err.exceptions[0] - if isinstance(result, InvalidCredentialsError): - raise ConfigEntryAuthFailed( - "Invalid username and/or password" - ) from result - if isinstance(result, NotionError): - raise UpdateFailed( - f"There was a Notion error while updating: {result}" - ) from result - if isinstance(result, Exception): - LOGGER.debug( - "There was an unknown error while updating: %s", - result, - exc_info=result, - ) - raise UpdateFailed( - f"There was an unknown error while updating: {result}" - ) from result - if isinstance(result, BaseException): - raise result from None - - data.update_bridges(bridges.result()) - data.update_listeners(listeners.result()) - data.update_sensors(sensors.result()) - data.update_user_preferences(user_preferences.result()) - return data - - coordinator = DataUpdateCoordinator( - hass, - LOGGER, - name=entry.data[CONF_USERNAME], - update_interval=DEFAULT_SCAN_INTERVAL, - update_method=async_update, - ) + coordinator = NotionDataUpdateCoordinator(hass, entry=entry, client=client) await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {}) @@ -282,39 +175,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -@callback -def _async_register_new_bridge( - hass: HomeAssistant, entry: ConfigEntry, bridge: Bridge -) -> None: - """Register a new bridge.""" - if name := bridge.name: - bridge_name = name.capitalize() - else: - bridge_name = str(bridge.id) - - device_registry = dr.async_get(hass) - device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, bridge.hardware_id)}, - manufacturer="Silicon Labs", - model=str(bridge.hardware_revision), - name=bridge_name, - sw_version=bridge.firmware_version.wifi, - ) - - -class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]): +class NotionEntity(CoordinatorEntity[NotionDataUpdateCoordinator]): """Define a base Notion entity.""" _attr_has_entity_name = True def __init__( self, - coordinator: DataUpdateCoordinator[NotionData], + coordinator: NotionDataUpdateCoordinator, listener_id: str, sensor_id: str, bridge_id: int, - system_id: str, description: EntityDescription, ) -> None: """Initialize the entity.""" @@ -338,7 +209,6 @@ class NotionEntity(CoordinatorEntity[DataUpdateCoordinator[NotionData]]): self._bridge_id = bridge_id self._listener_id = listener_id self._sensor_id = sensor_id - self._system_id = system_id self.entity_description = description @property diff --git a/homeassistant/components/notion/binary_sensor.py b/homeassistant/components/notion/binary_sensor.py index 0c56e4fc33e..da50a809689 100644 --- a/homeassistant/components/notion/binary_sensor.py +++ b/homeassistant/components/notion/binary_sensor.py @@ -31,6 +31,7 @@ from .const import ( SENSOR_SMOKE_CO, SENSOR_WINDOW_HINGED, ) +from .coordinator import NotionDataUpdateCoordinator from .model import NotionEntityDescription @@ -110,7 +111,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Notion sensors based on a config entry.""" - coordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: NotionDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( [ @@ -119,7 +120,6 @@ async def async_setup_entry( listener_id, sensor.uuid, sensor.bridge.id, - sensor.system_id, description, ) for listener_id, listener in coordinator.data.listeners.items() diff --git a/homeassistant/components/notion/coordinator.py b/homeassistant/components/notion/coordinator.py new file mode 100644 index 00000000000..c3fd23abc84 --- /dev/null +++ b/homeassistant/components/notion/coordinator.py @@ -0,0 +1,164 @@ +"""Define a Notion data coordinator.""" + +import asyncio +from dataclasses import dataclass, field +from datetime import timedelta +from typing import Any + +from aionotion.bridge.models import Bridge +from aionotion.client import Client +from aionotion.errors import InvalidCredentialsError, NotionError +from aionotion.listener.models import Listener +from aionotion.sensor.models import Sensor +from aionotion.user.models import UserPreferences + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_USERNAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, LOGGER + +DATA_BRIDGES = "bridges" +DATA_LISTENERS = "listeners" +DATA_SENSORS = "sensors" +DATA_USER_PREFERENCES = "user_preferences" + +DEFAULT_SCAN_INTERVAL = timedelta(minutes=1) + + +@callback +def _async_register_new_bridge( + hass: HomeAssistant, entry: ConfigEntry, bridge: Bridge +) -> None: + """Register a new bridge.""" + if name := bridge.name: + bridge_name = name.capitalize() + else: + bridge_name = str(bridge.id) + + device_registry = dr.async_get(hass) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, bridge.hardware_id)}, + manufacturer="Silicon Labs", + model=str(bridge.hardware_revision), + name=bridge_name, + sw_version=bridge.firmware_version.wifi, + ) + + +@dataclass +class NotionData: + """Define a manager class for Notion data.""" + + hass: HomeAssistant + entry: ConfigEntry + + # Define a dict of bridges, indexed by bridge ID (an integer): + bridges: dict[int, Bridge] = field(default_factory=dict) + + # Define a dict of listeners, indexed by listener UUID (a string): + listeners: dict[str, Listener] = field(default_factory=dict) + + # Define a dict of sensors, indexed by sensor UUID (a string): + sensors: dict[str, Sensor] = field(default_factory=dict) + + # Define a user preferences response object: + user_preferences: UserPreferences | None = field(default=None) + + def update_bridges(self, bridges: list[Bridge]) -> None: + """Update the bridges.""" + for bridge in bridges: + # If a new bridge is discovered, register it: + if bridge.id not in self.bridges: + _async_register_new_bridge(self.hass, self.entry, bridge) + self.bridges[bridge.id] = bridge + + def update_listeners(self, listeners: list[Listener]) -> None: + """Update the listeners.""" + self.listeners = {listener.id: listener for listener in listeners} + + def update_sensors(self, sensors: list[Sensor]) -> None: + """Update the sensors.""" + self.sensors = {sensor.uuid: sensor for sensor in sensors} + + def update_user_preferences(self, user_preferences: UserPreferences) -> None: + """Update the user preferences.""" + self.user_preferences = user_preferences + + def asdict(self) -> dict[str, Any]: + """Represent this dataclass (and its Pydantic contents) as a dict.""" + data: dict[str, Any] = { + DATA_BRIDGES: [item.to_dict() for item in self.bridges.values()], + DATA_LISTENERS: [item.to_dict() for item in self.listeners.values()], + DATA_SENSORS: [item.to_dict() for item in self.sensors.values()], + } + if self.user_preferences: + data[DATA_USER_PREFERENCES] = self.user_preferences.to_dict() + return data + + +class NotionDataUpdateCoordinator(DataUpdateCoordinator[NotionData]): + """Define a Notion data coordinator.""" + + config_entry: ConfigEntry + + def __init__( + self, + hass: HomeAssistant, + *, + entry: ConfigEntry, + client: Client, + ) -> None: + """Initialize.""" + super().__init__( + hass, + LOGGER, + name=entry.data[CONF_USERNAME], + update_interval=DEFAULT_SCAN_INTERVAL, + ) + + self._client = client + self._entry = entry + + async def _async_update_data(self) -> NotionData: + """Fetch data from Notion.""" + data = NotionData(hass=self.hass, entry=self._entry) + + try: + async with asyncio.TaskGroup() as tg: + bridges = tg.create_task(self._client.bridge.async_all()) + listeners = tg.create_task(self._client.listener.async_all()) + sensors = tg.create_task(self._client.sensor.async_all()) + user_preferences = tg.create_task(self._client.user.async_preferences()) + except BaseExceptionGroup as err: + result = err.exceptions[0] + if isinstance(result, InvalidCredentialsError): + raise ConfigEntryAuthFailed( + "Invalid username and/or password" + ) from result + if isinstance(result, NotionError): + raise UpdateFailed( + f"There was a Notion error while updating: {result}" + ) from result + if isinstance(result, Exception): + LOGGER.debug( + "There was an unknown error while updating: %s", + result, + exc_info=result, + ) + raise UpdateFailed( + f"There was an unknown error while updating: {result}" + ) from result + if isinstance(result, BaseException): + raise result from None + + data.update_bridges(bridges.result()) + data.update_listeners(listeners.result()) + data.update_sensors(sensors.result()) + data.update_user_preferences(user_preferences.result()) + + return data diff --git a/homeassistant/components/notion/diagnostics.py b/homeassistant/components/notion/diagnostics.py index 77cf9e2a90f..424e5f7d0ac 100644 --- a/homeassistant/components/notion/diagnostics.py +++ b/homeassistant/components/notion/diagnostics.py @@ -8,10 +8,9 @@ from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EMAIL, CONF_UNIQUE_ID, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from . import NotionData from .const import CONF_REFRESH_TOKEN, CONF_USER_UUID, DOMAIN +from .coordinator import NotionDataUpdateCoordinator CONF_DEVICE_KEY = "device_key" CONF_HARDWARE_ID = "hardware_id" @@ -38,7 +37,7 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - coordinator: DataUpdateCoordinator[NotionData] = hass.data[DOMAIN][entry.entry_id] + coordinator: NotionDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] return async_redact_data( { diff --git a/homeassistant/components/notion/sensor.py b/homeassistant/components/notion/sensor.py index 9bb8e3b4bbf..d12dabbbc33 100644 --- a/homeassistant/components/notion/sensor.py +++ b/homeassistant/components/notion/sensor.py @@ -17,6 +17,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import NotionEntity from .const import DOMAIN, SENSOR_MOLD, SENSOR_TEMPERATURE +from .coordinator import NotionDataUpdateCoordinator from .model import NotionEntityDescription @@ -45,7 +46,7 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Notion sensors based on a config entry.""" - coordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: NotionDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( [ @@ -54,7 +55,6 @@ async def async_setup_entry( listener_id, sensor.uuid, sensor.bridge.id, - sensor.system_id, description, ) for listener_id, listener in coordinator.data.listeners.items() From 688395a3e37fdbdb7aa75148bc0e371cac43e6e5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 12 Mar 2024 21:52:06 +0100 Subject: [PATCH 0860/1691] Add icon translations to Bond (#111349) --- homeassistant/components/bond/button.py | 56 ++++++------ homeassistant/components/bond/icons.json | 107 +++++++++++++++++++++++ homeassistant/components/bond/light.py | 2 +- 3 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 homeassistant/components/bond/icons.json diff --git a/homeassistant/components/bond/button.py b/homeassistant/components/bond/button.py index a75e2ad74b8..a8a5a890f2c 100644 --- a/homeassistant/components/bond/button.py +++ b/homeassistant/components/bond/button.py @@ -36,7 +36,7 @@ class BondButtonEntityDescription(ButtonEntityDescription): STOP_BUTTON = BondButtonEntityDescription( key=Action.STOP, name="Stop Actions", - icon="mdi:stop-circle-outline", + translation_key="stop_actions", mutually_exclusive=None, argument=None, ) @@ -46,175 +46,175 @@ BUTTONS: tuple[BondButtonEntityDescription, ...] = ( BondButtonEntityDescription( key=Action.TOGGLE_POWER, name="Toggle Power", - icon="mdi:power-cycle", + translation_key="toggle_power", mutually_exclusive=Action.TURN_ON, argument=None, ), BondButtonEntityDescription( key=Action.TOGGLE_LIGHT, name="Toggle Light", - icon="mdi:lightbulb", + translation_key="toggle_light", mutually_exclusive=Action.TURN_LIGHT_ON, argument=None, ), BondButtonEntityDescription( key=Action.INCREASE_BRIGHTNESS, name="Increase Brightness", - icon="mdi:brightness-7", + translation_key="increase_brightness", mutually_exclusive=Action.SET_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.DECREASE_BRIGHTNESS, name="Decrease Brightness", - icon="mdi:brightness-1", + translation_key="decrease_brightness", mutually_exclusive=Action.SET_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.TOGGLE_UP_LIGHT, name="Toggle Up Light", - icon="mdi:lightbulb", + translation_key="toggle_up_light", mutually_exclusive=Action.TURN_UP_LIGHT_ON, argument=None, ), BondButtonEntityDescription( key=Action.TOGGLE_DOWN_LIGHT, name="Toggle Down Light", - icon="mdi:lightbulb", + translation_key="toggle_down_light", mutually_exclusive=Action.TURN_DOWN_LIGHT_ON, argument=None, ), BondButtonEntityDescription( key=Action.START_DIMMER, name="Start Dimmer", - icon="mdi:brightness-percent", + translation_key="start_dimmer", mutually_exclusive=Action.SET_BRIGHTNESS, argument=None, ), BondButtonEntityDescription( key=Action.START_UP_LIGHT_DIMMER, name="Start Up Light Dimmer", - icon="mdi:brightness-percent", + translation_key="start_up_light_dimmer", mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS, argument=None, ), BondButtonEntityDescription( key=Action.START_DOWN_LIGHT_DIMMER, name="Start Down Light Dimmer", - icon="mdi:brightness-percent", + translation_key="start_down_light_dimmer", mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS, argument=None, ), BondButtonEntityDescription( key=Action.START_INCREASING_BRIGHTNESS, name="Start Increasing Brightness", - icon="mdi:brightness-percent", + translation_key="start_increasing_brightness", mutually_exclusive=Action.SET_BRIGHTNESS, argument=None, ), BondButtonEntityDescription( key=Action.START_DECREASING_BRIGHTNESS, name="Start Decreasing Brightness", - icon="mdi:brightness-percent", + translation_key="start_decreasing_brightness", mutually_exclusive=Action.SET_BRIGHTNESS, argument=None, ), BondButtonEntityDescription( key=Action.INCREASE_UP_LIGHT_BRIGHTNESS, name="Increase Up Light Brightness", - icon="mdi:brightness-percent", + translation_key="increase_up_light_brightness", mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.DECREASE_UP_LIGHT_BRIGHTNESS, name="Decrease Up Light Brightness", - icon="mdi:brightness-percent", + translation_key="decrease_up_light_brightness", mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.INCREASE_DOWN_LIGHT_BRIGHTNESS, name="Increase Down Light Brightness", - icon="mdi:brightness-percent", + translation_key="increase_down_light_brightness", mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.DECREASE_DOWN_LIGHT_BRIGHTNESS, name="Decrease Down Light Brightness", - icon="mdi:brightness-percent", + translation_key="decrease_down_light_brightness", mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.CYCLE_UP_LIGHT_BRIGHTNESS, name="Cycle Up Light Brightness", - icon="mdi:brightness-percent", + translation_key="cycle_up_light_brightness", mutually_exclusive=Action.SET_UP_LIGHT_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.CYCLE_DOWN_LIGHT_BRIGHTNESS, name="Cycle Down Light Brightness", - icon="mdi:brightness-percent", + translation_key="cycle_down_light_brightness", mutually_exclusive=Action.SET_DOWN_LIGHT_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.CYCLE_BRIGHTNESS, name="Cycle Brightness", - icon="mdi:brightness-percent", + translation_key="cycle_brightness", mutually_exclusive=Action.SET_BRIGHTNESS, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.INCREASE_SPEED, name="Increase Speed", - icon="mdi:skew-more", + translation_key="increase_speed", mutually_exclusive=Action.SET_SPEED, argument=1, ), BondButtonEntityDescription( key=Action.DECREASE_SPEED, name="Decrease Speed", - icon="mdi:skew-less", + translation_key="decrease_speed", mutually_exclusive=Action.SET_SPEED, argument=1, ), BondButtonEntityDescription( key=Action.TOGGLE_DIRECTION, name="Toggle Direction", - icon="mdi:directions-fork", + translation_key="toggle_direction", mutually_exclusive=Action.SET_DIRECTION, argument=None, ), BondButtonEntityDescription( key=Action.INCREASE_TEMPERATURE, name="Increase Temperature", - icon="mdi:thermometer-plus", + translation_key="increase_temperature", mutually_exclusive=None, argument=1, ), BondButtonEntityDescription( key=Action.DECREASE_TEMPERATURE, name="Decrease Temperature", - icon="mdi:thermometer-minus", + translation_key="decrease_temperature", mutually_exclusive=None, argument=1, ), BondButtonEntityDescription( key=Action.INCREASE_FLAME, name="Increase Flame", - icon="mdi:fire", + translation_key="increase_flame", mutually_exclusive=None, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.DECREASE_FLAME, name="Decrease Flame", - icon="mdi:fire-off", + translation_key="decrease_flame", mutually_exclusive=None, argument=STEP_SIZE, ), @@ -227,14 +227,14 @@ BUTTONS: tuple[BondButtonEntityDescription, ...] = ( BondButtonEntityDescription( key=Action.INCREASE_POSITION, name="Increase Position", - icon="mdi:plus-box", + translation_key="increase_position", mutually_exclusive=Action.SET_POSITION, argument=STEP_SIZE, ), BondButtonEntityDescription( key=Action.DECREASE_POSITION, name="Decrease Position", - icon="mdi:minus-box", + translation_key="decrease_position", mutually_exclusive=Action.SET_POSITION, argument=STEP_SIZE, ), diff --git a/homeassistant/components/bond/icons.json b/homeassistant/components/bond/icons.json new file mode 100644 index 00000000000..35743d20e65 --- /dev/null +++ b/homeassistant/components/bond/icons.json @@ -0,0 +1,107 @@ +{ + "entity": { + "button": { + "stop_actions": { + "default": "mdi:stop-circle-outline" + }, + "toggle_power": { + "default": "mdi:power-cycle" + }, + "toggle_light": { + "default": "mdi:lightbulb" + }, + "increase_brightness": { + "default": "mdi:brightness-7" + }, + "decrease_brightness": { + "default": "mdi:brightness-1" + }, + "toggle_up_light": { + "default": "mdi:lightbulb" + }, + "toggle_down_light": { + "default": "mdi:lightbulb" + }, + "start_dimmer": { + "default": "mdi:brightness-percent" + }, + "start_up_light_dimmer": { + "default": "mdi:brightness-percent" + }, + "start_down_light_dimmer": { + "default": "mdi:brightness-percent" + }, + "start_increasing_brightness": { + "default": "mdi:brightness-percent" + }, + "start_decreasing_brightness": { + "default": "mdi:brightness-percent" + }, + "increase_up_light_brightness": { + "default": "mdi:brightness-percent" + }, + "decrease_up_light_brightness": { + "default": "mdi:brightness-percent" + }, + "increase_down_light_brightness": { + "default": "mdi:brightness-percent" + }, + "decrease_down_light_brightness": { + "default": "mdi:brightness-percent" + }, + "cycle_up_light_brightness": { + "default": "mdi:brightness-percent" + }, + "cycle_down_light_brightness": { + "default": "mdi:brightness-percent" + }, + "cycle_brightness": { + "default": "mdi:brightness-percent" + }, + "increase_speed": { + "default": "mdi:skew-more" + }, + "decrease_speed": { + "default": "mdi:skew-less" + }, + "toggle_direction": { + "default": "mdi:directions-fork" + }, + "increase_temperature": { + "default": "mdi:thermometer-plus" + }, + "decrease_temperature": { + "default": "mdi:thermometer-minus" + }, + "increase_flame": { + "default": "mdi:fire" + }, + "decrease_flame": { + "default": "mdi:fire-off" + }, + "increase_position": { + "default": "mdi:plus-box" + }, + "decrease_position": { + "default": "mdi:minus-box" + } + }, + "light": { + "fireplace": { + "default": "mdi:fireplace-off", + "state": { + "on": "mdi:fireplace" + } + } + } + }, + "services": { + "set_fan_speed_tracked_state": "mdi:fan", + "set_switch_power_tracked_state": "mdi:toggle-switch-variant", + "set_light_power_tracked_state": "mdi:lightbulb", + "set_light_brightness_tracked_state": "mdi:lightbulb-on", + "start_increasing_brightness": "mdi:brightness-7", + "start_decreasing_brightness": "mdi:brightness-1", + "stop": "mdi:stop" + } +} diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index 9173916d00d..bd1183a3a98 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -274,6 +274,7 @@ class BondFireplace(BondEntity, LightEntity): _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_translation_key = "fireplace" def _apply_state(self) -> None: state = self._device.state @@ -281,7 +282,6 @@ class BondFireplace(BondEntity, LightEntity): flame = state.get("flame") self._attr_is_on = power == 1 self._attr_brightness = round(flame * 255 / 100) if flame else None - self._attr_icon = "mdi:fireplace" if power == 1 else "mdi:fireplace-off" async def async_turn_on(self, **kwargs: Any) -> None: """Turn the fireplace on.""" From 3da07bd1603263a05be6794fa4480b53c4b6db97 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 12 Mar 2024 20:54:03 +0000 Subject: [PATCH 0861/1691] Fix System Bridge media source data URL (#112612) --- homeassistant/components/system_bridge/media_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/system_bridge/media_source.py b/homeassistant/components/system_bridge/media_source.py index b9a7da18386..0b319f4c7c9 100644 --- a/homeassistant/components/system_bridge/media_source.py +++ b/homeassistant/components/system_bridge/media_source.py @@ -14,7 +14,7 @@ from homeassistant.components.media_source.models import ( PlayMedia, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN from homeassistant.core import HomeAssistant from .const import DOMAIN @@ -124,7 +124,7 @@ def _build_base_url( """Build base url for System Bridge media.""" return ( f"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}" - f"/api/media/file/data?apiKey={entry.data[CONF_API_KEY]}" + f"/api/media/file/data?token={entry.data[CONF_TOKEN]}" ) From 5ab7cb6ddde3af6401cd6c9fab7490289f657cf3 Mon Sep 17 00:00:00 2001 From: mattmccormack Date: Wed, 13 Mar 2024 06:55:26 +1000 Subject: [PATCH 0862/1691] Add auto fan mode icon (#110185) --- homeassistant/components/climate/icons.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/climate/icons.json b/homeassistant/components/climate/icons.json index 4317698b257..5afce637ed5 100644 --- a/homeassistant/components/climate/icons.json +++ b/homeassistant/components/climate/icons.json @@ -6,6 +6,7 @@ "fan_mode": { "default": "mdi:circle-medium", "state": { + "auto": "mdi:fan-auto", "diffuse": "mdi:weather-windy", "focus": "mdi:target", "high": "mdi:speedometer", From 1cceaaf19340bbb4a6e67edda9ae7cba6163fe69 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 12:01:39 -1000 Subject: [PATCH 0863/1691] Small improvement to test run time (#113175) --- tests/common.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/common.py b/tests/common.py index cbc295bf43a..6d0e74021e3 100644 --- a/tests/common.py +++ b/tests/common.py @@ -295,7 +295,9 @@ async def async_test_home_assistant( }, ) hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, hass.config_entries._async_shutdown + EVENT_HOMEASSISTANT_STOP, + hass.config_entries._async_shutdown, + run_immediately=True, ) # Load the registries @@ -878,7 +880,9 @@ class MockEntityPlatform(entity_platform.EntityPlatform): def _async_on_stop(_: Event) -> None: self.async_shutdown() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_on_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_on_stop, run_immediately=True + ) class MockToggleEntity(entity.ToggleEntity): From 9ec0e097ef8ef67ae86122a9fb2c68e52d254a5e Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Tue, 12 Mar 2024 23:18:20 +0100 Subject: [PATCH 0864/1691] Remove ZHA IasZone sensor migration (#111893) --- homeassistant/components/zha/binary_sensor.py | 31 ---------- tests/components/zha/test_binary_sensor.py | 61 ------------------- 2 files changed, 92 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 3333bf6bb3e..a3348ad974d 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations import functools -from typing import Any from zigpy.quirks.v2 import BinarySensorMetadata, EntityMetadata import zigpy.types as t @@ -209,36 +208,6 @@ class IASZone(BinarySensor): """Parse the raw attribute into a bool state.""" return BinarySensor.parse(value & 3) # use only bit 0 and 1 for alarm state - # temporary code to migrate old IasZone sensors to update attribute cache state once - # remove in 2024.4.0 - @property - def extra_state_attributes(self) -> dict[str, Any]: - """Return state attributes.""" - return {"migrated_to_cache": True} # writing new state means we're migrated - - # temporary migration code - @callback - def async_restore_last_state(self, last_state): - """Restore previous state.""" - # trigger migration if extra state attribute is not present - if "migrated_to_cache" not in last_state.attributes: - self.migrate_to_zigpy_cache(last_state) - - # temporary migration code - @callback - def migrate_to_zigpy_cache(self, last_state): - """Save old IasZone sensor state to attribute cache.""" - # previous HA versions did not update the attribute cache for IasZone sensors, so do it once here - # a HA state write is triggered shortly afterwards and writes the "migrated_to_cache" extra state attribute - if last_state.state == STATE_ON: - migrated_state = IasZone.ZoneStatus.Alarm_1 - else: - migrated_state = IasZone.ZoneStatus(0) - - self._cluster_handler.cluster.update_attribute( - IasZone.attributes_by_name[self._attribute_name].id, migrated_state - ) - @STRICT_MATCH(cluster_handler_names=CLUSTER_HANDLER_ZONE, models={"WL4200", "WL4200S"}) class SinopeLeakStatus(BinarySensor): diff --git a/tests/components/zha/test_binary_sensor.py b/tests/components/zha/test_binary_sensor.py index 91a800cafd5..18e78ae7e57 100644 --- a/tests/components/zha/test_binary_sensor.py +++ b/tests/components/zha/test_binary_sensor.py @@ -16,7 +16,6 @@ from .common import ( async_test_rejoin, find_entity_id, send_attributes_report, - update_attribute_cache, ) from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE @@ -151,66 +150,6 @@ async def test_binary_sensor( assert hass.states.get(entity_id).state == STATE_OFF -@pytest.mark.parametrize( - "restored_state", - [ - STATE_ON, - STATE_OFF, - ], -) -async def test_binary_sensor_migration_not_migrated( - hass: HomeAssistant, - zigpy_device_mock, - core_rs, - zha_device_restored, - restored_state, -) -> None: - """Test temporary ZHA IasZone binary_sensor migration to zigpy cache.""" - - entity_id = "binary_sensor.fakemanufacturer_fakemodel_ias_zone" - core_rs(entity_id, state=restored_state, attributes={}) # migration sensor state - await async_mock_load_restore_state_from_storage(hass) - - zigpy_device = zigpy_device_mock(DEVICE_IAS) - - zha_device = await zha_device_restored(zigpy_device) - entity_id = find_entity_id(Platform.BINARY_SENSOR, zha_device, hass) - - assert entity_id is not None - assert hass.states.get(entity_id).state == restored_state - - # confirm migration extra state attribute was set to True - assert hass.states.get(entity_id).attributes["migrated_to_cache"] - - -async def test_binary_sensor_migration_already_migrated( - hass: HomeAssistant, - zigpy_device_mock, - core_rs, - zha_device_restored, -) -> None: - """Test temporary ZHA IasZone binary_sensor migration doesn't migrate multiple times.""" - - entity_id = "binary_sensor.fakemanufacturer_fakemodel_ias_zone" - core_rs(entity_id, state=STATE_OFF, attributes={"migrated_to_cache": True}) - await async_mock_load_restore_state_from_storage(hass) - - zigpy_device = zigpy_device_mock(DEVICE_IAS) - - cluster = zigpy_device.endpoints.get(1).ias_zone - cluster.PLUGGED_ATTR_READS = { - "zone_status": security.IasZone.ZoneStatus.Alarm_1, - } - update_attribute_cache(cluster) - - zha_device = await zha_device_restored(zigpy_device) - entity_id = find_entity_id(Platform.BINARY_SENSOR, zha_device, hass) - - assert entity_id is not None - assert hass.states.get(entity_id).state == STATE_ON # matches attribute cache - assert hass.states.get(entity_id).attributes["migrated_to_cache"] - - @pytest.mark.parametrize( "restored_state", [ From e347096ef56d208f98bb80a5b02171f32b78ab4c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 14:19:22 -1000 Subject: [PATCH 0865/1691] Add a task name to the config entry retry tasks (#113188) --- homeassistant/config_entries.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 150c377a6ee..4b2eb81c870 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -640,7 +640,11 @@ class ConfigEntry: # Check again when we fire in case shutdown # has started so we do not block shutdown if not hass.is_stopping: - hass.async_create_task(self.async_setup(hass), eager_start=True) + hass.async_create_task( + self.async_setup(hass), + f"config entry retry {self.domain} {self.title}", + eager_start=True, + ) @callback def async_shutdown(self) -> None: From 45fb5bcb4271a7dc9021ad52bef967ded23c34c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Wed, 13 Mar 2024 02:45:18 +0100 Subject: [PATCH 0866/1691] Fix radon precision for Airthings BLE (#113185) --- homeassistant/components/airthings_ble/sensor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index e281d81827c..d0836e90e08 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -52,12 +52,14 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { key="radon_1day_avg", translation_key="radon_1day_avg", native_unit_of_measurement=VOLUME_BECQUEREL, + suggested_display_precision=0, state_class=SensorStateClass.MEASUREMENT, ), "radon_longterm_avg": SensorEntityDescription( key="radon_longterm_avg", translation_key="radon_longterm_avg", native_unit_of_measurement=VOLUME_BECQUEREL, + suggested_display_precision=0, state_class=SensorStateClass.MEASUREMENT, ), "radon_1day_level": SensorEntityDescription( @@ -170,6 +172,7 @@ async def async_setup_entry( sensors_mapping[key] = dataclasses.replace( val, native_unit_of_measurement=VOLUME_PICOCURIE, + suggested_display_precision=1, ) entities = [] From d2bd68ba30bee9bbb9e278be1162c3f3b5452c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Wed, 13 Mar 2024 02:48:36 +0100 Subject: [PATCH 0867/1691] Bump airthings_ble to 0.7.1 (#113172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ståle Storø Hauknes --- homeassistant/components/airthings_ble/__init__.py | 3 +-- homeassistant/components/airthings_ble/manifest.json | 2 +- homeassistant/components/airthings_ble/sensor.py | 6 ++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/airthings_ble/__init__.py | 9 ++++++--- tests/components/airthings_ble/test_config_flow.py | 8 +++----- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/airthings_ble/__init__.py b/homeassistant/components/airthings_ble/__init__.py index dd05c58ed81..e8a2d492ae2 100644 --- a/homeassistant/components/airthings_ble/__init__.py +++ b/homeassistant/components/airthings_ble/__init__.py @@ -28,7 +28,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) address = entry.unique_id - elevation = hass.config.elevation is_metric = hass.config.units is METRIC_SYSTEM assert address is not None @@ -41,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: f"Could not find Airthings device with address {address}" ) - airthings = AirthingsBluetoothDeviceData(_LOGGER, elevation, is_metric) + airthings = AirthingsBluetoothDeviceData(_LOGGER, is_metric) async def _async_update_method() -> AirthingsDevice: """Get data from Airthings BLE.""" diff --git a/homeassistant/components/airthings_ble/manifest.json b/homeassistant/components/airthings_ble/manifest.json index 97e27793da2..3f7bd02a33e 100644 --- a/homeassistant/components/airthings_ble/manifest.json +++ b/homeassistant/components/airthings_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/airthings_ble", "iot_class": "local_polling", - "requirements": ["airthings-ble==0.6.1"] + "requirements": ["airthings-ble==0.7.1"] } diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index d0836e90e08..73ac7e47ceb 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -17,7 +17,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, - LIGHT_LUX, PERCENTAGE, EntityCategory, Platform, @@ -109,8 +108,7 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { ), "illuminance": SensorEntityDescription( key="illuminance", - device_class=SensorDeviceClass.ILLUMINANCE, - native_unit_of_measurement=LIGHT_LUX, + native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), } @@ -226,7 +224,7 @@ class AirthingsSensor( manufacturer=airthings_device.manufacturer, hw_version=airthings_device.hw_version, sw_version=airthings_device.sw_version, - model=airthings_device.model, + model=airthings_device.model.name, ) @property diff --git a/requirements_all.txt b/requirements_all.txt index 0f36748412a..176bd935625 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -416,7 +416,7 @@ aioymaps==1.2.2 airly==1.1.0 # homeassistant.components.airthings_ble -airthings-ble==0.6.1 +airthings-ble==0.7.1 # homeassistant.components.airthings airthings-cloud==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8f9db42c5fc..3bd4e3436f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -389,7 +389,7 @@ aioymaps==1.2.2 airly==1.1.0 # homeassistant.components.airthings_ble -airthings-ble==0.6.1 +airthings-ble==0.7.1 # homeassistant.components.airthings airthings-cloud==0.2.0 diff --git a/tests/components/airthings_ble/__init__.py b/tests/components/airthings_ble/__init__.py index 0dfc894252e..d60b42eddf2 100644 --- a/tests/components/airthings_ble/__init__.py +++ b/tests/components/airthings_ble/__init__.py @@ -4,7 +4,11 @@ from __future__ import annotations from unittest.mock import patch -from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice +from airthings_ble import ( + AirthingsBluetoothDeviceData, + AirthingsDevice, + AirthingsDeviceType, +) from homeassistant.components.airthings_ble.const import DOMAIN from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak @@ -162,8 +166,7 @@ WAVE_DEVICE_INFO = AirthingsDevice( manufacturer="Airthings AS", hw_version="REV A", sw_version="G-BLE-1.5.3-master+0", - model="Wave Plus", - model_raw="2930", + model=AirthingsDeviceType.WAVE_PLUS, name="Airthings Wave+", identifier="123456", sensors={ diff --git a/tests/components/airthings_ble/test_config_flow.py b/tests/components/airthings_ble/test_config_flow.py index 279e08ef4a9..792ac2ad7ab 100644 --- a/tests/components/airthings_ble/test_config_flow.py +++ b/tests/components/airthings_ble/test_config_flow.py @@ -2,7 +2,7 @@ from unittest.mock import patch -from airthings_ble import AirthingsDevice +from airthings_ble import AirthingsDevice, AirthingsDeviceType from bleak import BleakError from homeassistant.components.airthings_ble.const import DOMAIN @@ -29,8 +29,7 @@ async def test_bluetooth_discovery(hass: HomeAssistant) -> None: with patch_async_ble_device_from_address(WAVE_SERVICE_INFO), patch_airthings_ble( AirthingsDevice( manufacturer="Airthings AS", - model="Wave Plus", - model_raw="2930", + model=AirthingsDeviceType.WAVE_PLUS, name="Airthings Wave Plus", identifier="123456", ) @@ -112,8 +111,7 @@ async def test_user_setup(hass: HomeAssistant) -> None: ), patch_async_ble_device_from_address(WAVE_SERVICE_INFO), patch_airthings_ble( AirthingsDevice( manufacturer="Airthings AS", - model="Wave Plus", - model_raw="2930", + model=AirthingsDeviceType.WAVE_PLUS, name="Airthings Wave Plus", identifier="123456", ) From a2a8a8f1192b59a700dfb1e98a5a720f059d7d10 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:06:25 +1300 Subject: [PATCH 0868/1691] ESPHome: Catch and re-raise client library errors as HomeAssistantErrors (#113026) --- .../components/esphome/alarm_control_panel.py | 14 +++++- homeassistant/components/esphome/button.py | 7 ++- homeassistant/components/esphome/climate.py | 13 +++++- homeassistant/components/esphome/cover.py | 14 +++++- homeassistant/components/esphome/entity.py | 27 +++++++++++- homeassistant/components/esphome/fan.py | 12 ++++- homeassistant/components/esphome/light.py | 9 +++- homeassistant/components/esphome/lock.py | 10 ++++- .../components/esphome/media_player.py | 13 +++++- homeassistant/components/esphome/number.py | 8 +++- homeassistant/components/esphome/select.py | 2 + homeassistant/components/esphome/switch.py | 9 +++- homeassistant/components/esphome/text.py | 8 +++- tests/components/esphome/test_number.py | 44 ++++++++++++++++++- 14 files changed, 176 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/esphome/alarm_control_panel.py b/homeassistant/components/esphome/alarm_control_panel.py index c430934019c..0c483119b22 100644 --- a/homeassistant/components/esphome/alarm_control_panel.py +++ b/homeassistant/components/esphome/alarm_control_panel.py @@ -32,7 +32,12 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) from .enum_mapper import EsphomeEnumMapper _ESPHOME_ACP_STATE_TO_HASS_STATE: EsphomeEnumMapper[ @@ -114,42 +119,49 @@ class EsphomeAlarmControlPanel( """Return the state of the device.""" return _ESPHOME_ACP_STATE_TO_HASS_STATE.from_esphome(self._state.state) + @convert_api_error_ha_error async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" self._client.alarm_control_panel_command( self._key, AlarmControlPanelCommand.DISARM, code ) + @convert_api_error_ha_error async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" self._client.alarm_control_panel_command( self._key, AlarmControlPanelCommand.ARM_HOME, code ) + @convert_api_error_ha_error async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" self._client.alarm_control_panel_command( self._key, AlarmControlPanelCommand.ARM_AWAY, code ) + @convert_api_error_ha_error async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm away command.""" self._client.alarm_control_panel_command( self._key, AlarmControlPanelCommand.ARM_NIGHT, code ) + @convert_api_error_ha_error async def async_alarm_arm_custom_bypass(self, code: str | None = None) -> None: """Send arm away command.""" self._client.alarm_control_panel_command( self._key, AlarmControlPanelCommand.ARM_CUSTOM_BYPASS, code ) + @convert_api_error_ha_error async def async_alarm_arm_vacation(self, code: str | None = None) -> None: """Send arm away command.""" self._client.alarm_control_panel_command( self._key, AlarmControlPanelCommand.ARM_VACATION, code ) + @convert_api_error_ha_error async def async_alarm_trigger(self, code: str | None = None) -> None: """Send alarm trigger command.""" self._client.alarm_control_panel_command( diff --git a/homeassistant/components/esphome/button.py b/homeassistant/components/esphome/button.py index 406d86e9dc5..a825bb9b9b4 100644 --- a/homeassistant/components/esphome/button.py +++ b/homeassistant/components/esphome/button.py @@ -10,7 +10,11 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.enum import try_parse_enum -from .entity import EsphomeEntity, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + platform_async_setup_entry, +) async def async_setup_entry( @@ -53,6 +57,7 @@ class EsphomeButton(EsphomeEntity[ButtonInfo, EntityState], ButtonEntity): self._on_entry_data_changed() self.async_write_ha_state() + @convert_api_error_ha_error async def async_press(self) -> None: """Press the button.""" self._client.button_command(self._key) diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index 5337f9cf933..4225f60af0c 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -56,7 +56,12 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) from .enum_mapper import EsphomeEnumMapper FAN_QUIET = "quiet" @@ -274,6 +279,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti """Return the humidity we try to reach.""" return round(self._state.target_humidity) + @convert_api_error_ha_error async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature (and operation mode if set).""" data: dict[str, Any] = {"key": self._key} @@ -289,16 +295,19 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti data["target_temperature_high"] = kwargs[ATTR_TARGET_TEMP_HIGH] self._client.climate_command(**data) + @convert_api_error_ha_error async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" self._client.climate_command(key=self._key, target_humidity=humidity) + @convert_api_error_ha_error async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target operation mode.""" self._client.climate_command( key=self._key, mode=_CLIMATE_MODES.from_hass(hvac_mode) ) + @convert_api_error_ha_error async def async_set_preset_mode(self, preset_mode: str) -> None: """Set preset mode.""" kwargs: dict[str, Any] = {"key": self._key} @@ -308,6 +317,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti kwargs["preset"] = _PRESETS.from_hass(preset_mode) self._client.climate_command(**kwargs) + @convert_api_error_ha_error async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new fan mode.""" kwargs: dict[str, Any] = {"key": self._key} @@ -317,6 +327,7 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti kwargs["fan_mode"] = _FAN_MODES.from_hass(fan_mode) self._client.climate_command(**kwargs) + @convert_api_error_ha_error async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new swing mode.""" self._client.climate_command( diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 3a1767d50f0..0b845c255a3 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -18,7 +18,12 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.enum import try_parse_enum -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) async def async_setup_entry( @@ -95,30 +100,37 @@ class EsphomeCover(EsphomeEntity[CoverInfo, CoverState], CoverEntity): return None return round(self._state.tilt * 100.0) + @convert_api_error_ha_error async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" self._client.cover_command(key=self._key, position=1.0) + @convert_api_error_ha_error async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" self._client.cover_command(key=self._key, position=0.0) + @convert_api_error_ha_error async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" self._client.cover_command(key=self._key, stop=True) + @convert_api_error_ha_error async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" self._client.cover_command(key=self._key, position=kwargs[ATTR_POSITION] / 100) + @convert_api_error_ha_error async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Open the cover tilt.""" self._client.cover_command(key=self._key, tilt=1.0) + @convert_api_error_ha_error async def async_close_cover_tilt(self, **kwargs: Any) -> None: """Close the cover tilt.""" self._client.cover_command(key=self._key, tilt=0.0) + @convert_api_error_ha_error async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" tilt_position: int = kwargs[ATTR_TILT_POSITION] diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index b4cc54b0bb7..c069f93276b 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -2,12 +2,13 @@ from __future__ import annotations -from collections.abc import Callable +from collections.abc import Awaitable, Callable, Coroutine import functools import math -from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast +from typing import TYPE_CHECKING, Any, Concatenate, Generic, ParamSpec, TypeVar, cast from aioesphomeapi import ( + APIConnectionError, EntityCategory as EsphomeEntityCategory, EntityInfo, EntityState, @@ -18,6 +19,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv import homeassistant.helpers.device_registry as dr @@ -32,6 +34,7 @@ from .entry_data import RuntimeEntryData from .enum_mapper import EsphomeEnumMapper _R = TypeVar("_R") +_P = ParamSpec("_P") _InfoT = TypeVar("_InfoT", bound=EntityInfo) _EntityT = TypeVar("_EntityT", bound="EsphomeEntity[Any,Any]") _StateT = TypeVar("_StateT", bound=EntityState) @@ -140,6 +143,26 @@ def esphome_state_property( return _wrapper +def convert_api_error_ha_error( + func: Callable[Concatenate[_EntityT, _P], Awaitable[None]], +) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]: + """Decorate ESPHome command calls that send commands/make changes to the device. + + A decorator that wraps the passed in function, catches APIConnectionError errors, + and raises a HomeAssistant error instead. + """ + + async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None: + try: + return await func(self, *args, **kwargs) + except APIConnectionError as error: + raise HomeAssistantError( + f"Error communicating with device: {error}" + ) from error + + return handler + + ICON_SCHEMA = vol.Schema(cv.icon) diff --git a/homeassistant/components/esphome/fan.py b/homeassistant/components/esphome/fan.py index 25c81fcb8a8..082de3f7b7d 100644 --- a/homeassistant/components/esphome/fan.py +++ b/homeassistant/components/esphome/fan.py @@ -23,7 +23,12 @@ from homeassistant.util.percentage import ( ranged_value_to_percentage, ) -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) from .enum_mapper import EsphomeEnumMapper ORDERED_NAMED_FAN_SPEEDS = [FanSpeed.LOW, FanSpeed.MEDIUM, FanSpeed.HIGH] @@ -60,6 +65,7 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): """Set the speed percentage of the fan.""" await self._async_set_percentage(percentage) + @convert_api_error_ha_error async def _async_set_percentage(self, percentage: int | None) -> None: if percentage == 0: await self.async_turn_off() @@ -89,20 +95,24 @@ class EsphomeFan(EsphomeEntity[FanInfo, FanState], FanEntity): """Turn on the fan.""" await self._async_set_percentage(percentage) + @convert_api_error_ha_error async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the fan.""" self._client.fan_command(key=self._key, state=False) + @convert_api_error_ha_error async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" self._client.fan_command(key=self._key, oscillating=oscillating) + @convert_api_error_ha_error async def async_set_direction(self, direction: str) -> None: """Set direction of the fan.""" self._client.fan_command( key=self._key, direction=_FAN_DIRECTIONS.from_hass(direction) ) + @convert_api_error_ha_error async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the fan.""" self._client.fan_command(key=self._key, preset_mode=preset_mode) diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index da6d4c7b1fc..bbb4021d58f 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -33,7 +33,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) FLASH_LENGTHS = {FLASH_SHORT: 2, FLASH_LONG: 10} @@ -173,6 +178,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): """Return true if the light is on.""" return self._state.state + @convert_api_error_ha_error async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" data: dict[str, Any] = {"key": self._key, "state": True} @@ -288,6 +294,7 @@ class EsphomeLight(EsphomeEntity[LightInfo, LightState], LightEntity): self._client.light_command(**data) + @convert_api_error_ha_error async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" data: dict[str, Any] = {"key": self._key, "state": False} diff --git a/homeassistant/components/esphome/lock.py b/homeassistant/components/esphome/lock.py index 586a8e7af22..98efdece92e 100644 --- a/homeassistant/components/esphome/lock.py +++ b/homeassistant/components/esphome/lock.py @@ -12,7 +12,12 @@ from homeassistant.const import ATTR_CODE from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) async def async_setup_entry( @@ -70,15 +75,18 @@ class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity): """Return true if the lock is jammed (incomplete locking).""" return self._state.state == LockState.JAMMED + @convert_api_error_ha_error async def async_lock(self, **kwargs: Any) -> None: """Lock the lock.""" self._client.lock_command(self._key, LockCommand.LOCK) + @convert_api_error_ha_error async def async_unlock(self, **kwargs: Any) -> None: """Unlock the lock.""" code = kwargs.get(ATTR_CODE, None) self._client.lock_command(self._key, LockCommand.UNLOCK, code) + @convert_api_error_ha_error async def async_open(self, **kwargs: Any) -> None: """Open the door latch.""" self._client.lock_command(self._key, LockCommand.OPEN) diff --git a/homeassistant/components/esphome/media_player.py b/homeassistant/components/esphome/media_player.py index 60ccc08cad4..c2bfdc5850d 100644 --- a/homeassistant/components/esphome/media_player.py +++ b/homeassistant/components/esphome/media_player.py @@ -26,7 +26,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) from .enum_mapper import EsphomeEnumMapper @@ -95,6 +100,7 @@ class EsphomeMediaPlayer( """Volume level of the media player (0..1).""" return self._state.volume + @convert_api_error_ha_error async def async_play_media( self, media_type: MediaType | str, media_id: str, **kwargs: Any ) -> None: @@ -124,22 +130,27 @@ class EsphomeMediaPlayer( content_filter=lambda item: item.media_content_type.startswith("audio/"), ) + @convert_api_error_ha_error async def async_set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" self._client.media_player_command(self._key, volume=volume) + @convert_api_error_ha_error async def async_media_pause(self) -> None: """Send pause command.""" self._client.media_player_command(self._key, command=MediaPlayerCommand.PAUSE) + @convert_api_error_ha_error async def async_media_play(self) -> None: """Send play command.""" self._client.media_player_command(self._key, command=MediaPlayerCommand.PLAY) + @convert_api_error_ha_error async def async_media_stop(self) -> None: """Send stop command.""" self._client.media_player_command(self._key, command=MediaPlayerCommand.STOP) + @convert_api_error_ha_error async def async_mute_volume(self, mute: bool) -> None: """Mute the volume.""" self._client.media_player_command( diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index c9511ffe5bc..01744dd9998 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -17,7 +17,12 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.enum import try_parse_enum -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) from .enum_mapper import EsphomeEnumMapper @@ -78,6 +83,7 @@ class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity): return None return state.state + @convert_api_error_ha_error async def async_set_native_value(self, value: float) -> None: """Update the current value.""" self._client.number_command(self._key, value) diff --git a/homeassistant/components/esphome/select.py b/homeassistant/components/esphome/select.py index 8f6fa4af6f0..07a9d70e558 100644 --- a/homeassistant/components/esphome/select.py +++ b/homeassistant/components/esphome/select.py @@ -18,6 +18,7 @@ from .domain_data import DomainData from .entity import ( EsphomeAssistEntity, EsphomeEntity, + convert_api_error_ha_error, esphome_state_property, platform_async_setup_entry, ) @@ -66,6 +67,7 @@ class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity): state = self._state return None if state.missing_state else state.state + @convert_api_error_ha_error async def async_select_option(self, option: str) -> None: """Change the selected option.""" self._client.select_command(self._key, option) diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index f42e215eeaa..6fa73058bd2 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -12,7 +12,12 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.enum import try_parse_enum -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) async def async_setup_entry( @@ -48,10 +53,12 @@ class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity): """Return true if the switch is on.""" return self._state.state + @convert_api_error_ha_error async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" self._client.switch_command(self._key, True) + @convert_api_error_ha_error async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" self._client.switch_command(self._key, False) diff --git a/homeassistant/components/esphome/text.py b/homeassistant/components/esphome/text.py index 9cd7cb4c008..7d455e9ec21 100644 --- a/homeassistant/components/esphome/text.py +++ b/homeassistant/components/esphome/text.py @@ -9,7 +9,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from .entity import ( + EsphomeEntity, + convert_api_error_ha_error, + esphome_state_property, + platform_async_setup_entry, +) from .enum_mapper import EsphomeEnumMapper @@ -59,6 +64,7 @@ class EsphomeText(EsphomeEntity[TextInfo, TextState], TextEntity): return None return state.state + @convert_api_error_ha_error async def async_set_value(self, value: str) -> None: """Update the current value.""" self._client.text_command(self._key, value) diff --git a/tests/components/esphome/test_number.py b/tests/components/esphome/test_number.py index dc90d1c1098..557425052f3 100644 --- a/tests/components/esphome/test_number.py +++ b/tests/components/esphome/test_number.py @@ -1,14 +1,16 @@ """Test ESPHome numbers.""" import math -from unittest.mock import call +from unittest.mock import Mock, call from aioesphomeapi import ( APIClient, + APIConnectionError, NumberInfo, NumberMode as ESPHomeNumberMode, NumberState, ) +import pytest from homeassistant.components.number import ( ATTR_VALUE, @@ -17,6 +19,7 @@ from homeassistant.components.number import ( ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError async def test_generic_number_entity( @@ -122,3 +125,42 @@ async def test_generic_number_with_unit_of_measurement_as_empty_string( assert state is not None assert state.state == "42" assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes + + +async def test_generic_number_entity_set_when_disconnected( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic number entity.""" + entity_info = [ + NumberInfo( + object_id="mynumber", + key=1, + name="my number", + unique_id="my_number", + max_value=100, + min_value=0, + step=1, + unit_of_measurement="%", + ) + ] + states = [NumberState(key=1, state=50)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + + mock_client.number_command = Mock(side_effect=APIConnectionError("Not connected")) + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "number.test_mynumber", ATTR_VALUE: 20}, + blocking=True, + ) + mock_client.number_command.reset_mock() From e5ba4dbde94053a90a840041180ce2bb044fb1de Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 12 Mar 2024 23:06:33 -0500 Subject: [PATCH 0869/1691] Properly mark Plex update sensor when current (#113197) --- homeassistant/components/plex/update.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/plex/update.py b/homeassistant/components/plex/update.py index c1f1ffd5b14..7acf4551f33 100644 --- a/homeassistant/components/plex/update.py +++ b/homeassistant/components/plex/update.py @@ -53,6 +53,7 @@ class PlexUpdate(UpdateEntity): self._attr_installed_version = self.plex_server.version try: if (release := self.plex_server.checkForUpdate()) is None: + self._attr_latest_version = self.installed_version return except (requests.exceptions.RequestException, PlexApiException): _LOGGER.debug("Polling update sensor failed, will try again") From 3d7d3d263dd1896acc50fd7aa38642eb5aa8ac69 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:02:52 +1300 Subject: [PATCH 0870/1691] ESPHome: Add Date Entity support (#112475) * ESPHome: Add Date Entity support * Formatting * Add missing state test --- homeassistant/components/esphome/date.py | 46 +++++++++++ .../components/esphome/entry_data.py | 2 + tests/components/esphome/test_date.py | 76 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 homeassistant/components/esphome/date.py create mode 100644 tests/components/esphome/test_date.py diff --git a/homeassistant/components/esphome/date.py b/homeassistant/components/esphome/date.py new file mode 100644 index 00000000000..dbfc7779824 --- /dev/null +++ b/homeassistant/components/esphome/date.py @@ -0,0 +1,46 @@ +"""Support for esphome dates.""" +from __future__ import annotations + +from datetime import date + +from aioesphomeapi import DateInfo, DateState + +from homeassistant.components.date import DateEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up esphome dates based on a config entry.""" + await platform_async_setup_entry( + hass, + entry, + async_add_entities, + info_type=DateInfo, + entity_type=EsphomeDate, + state_type=DateState, + ) + + +class EsphomeDate(EsphomeEntity[DateInfo, DateState], DateEntity): + """A date implementation for esphome.""" + + @property + @esphome_state_property + def native_value(self) -> date | None: + """Return the state of the entity.""" + state = self._state + if state.missing_state: + return None + return date(state.year, state.month, state.day) + + async def async_set_value(self, value: date) -> None: + """Update the current date.""" + self._client.date_command(self._key, value.year, value.month, value.day) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 66ac1ac6c05..a7f3caf20cf 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -19,6 +19,7 @@ from aioesphomeapi import ( CameraState, ClimateInfo, CoverInfo, + DateInfo, DeviceInfo, EntityInfo, EntityState, @@ -63,6 +64,7 @@ INFO_TYPE_TO_PLATFORM: dict[type[EntityInfo], Platform] = { CameraInfo: Platform.CAMERA, ClimateInfo: Platform.CLIMATE, CoverInfo: Platform.COVER, + DateInfo: Platform.DATE, FanInfo: Platform.FAN, LightInfo: Platform.LIGHT, LockInfo: Platform.LOCK, diff --git a/tests/components/esphome/test_date.py b/tests/components/esphome/test_date.py new file mode 100644 index 00000000000..2deb92775fb --- /dev/null +++ b/tests/components/esphome/test_date.py @@ -0,0 +1,76 @@ +"""Test ESPHome dates.""" + +from unittest.mock import call + +from aioesphomeapi import APIClient, DateInfo, DateState + +from homeassistant.components.date import ( + ATTR_DATE, + DOMAIN as DATE_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + + +async def test_generic_date_entity( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic date entity.""" + entity_info = [ + DateInfo( + object_id="mydate", + key=1, + name="my date", + unique_id="my_date", + ) + ] + states = [DateState(key=1, year=2024, month=12, day=31)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + state = hass.states.get("date.test_mydate") + assert state is not None + assert state.state == "2024-12-31" + + await hass.services.async_call( + DATE_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "date.test_mydate", ATTR_DATE: "1999-01-01"}, + blocking=True, + ) + mock_client.date_command.assert_has_calls([call(1, 1999, 1, 1)]) + mock_client.date_command.reset_mock() + + +async def test_generic_date_missing_state( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic date entity with missing state.""" + entity_info = [ + DateInfo( + object_id="mydate", + key=1, + name="my date", + unique_id="my_date", + ) + ] + states = [DateState(key=1, missing_state=True)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + state = hass.states.get("date.test_mydate") + assert state is not None + assert state.state == STATE_UNKNOWN From ca9dea79a4694b4f5f3ed1622589183620a67466 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 23:29:50 -1000 Subject: [PATCH 0871/1691] Bump radios to 0.3.1 (#112850) --- homeassistant/components/radio_browser/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/radio_browser/manifest.json b/homeassistant/components/radio_browser/manifest.json index 3aa94e0d402..4192805ec62 100644 --- a/homeassistant/components/radio_browser/manifest.json +++ b/homeassistant/components/radio_browser/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/radio_browser", "integration_type": "service", "iot_class": "cloud_polling", - "requirements": ["radios==0.2.0"] + "requirements": ["radios==0.3.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 176bd935625..e4fd8291a2e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2403,7 +2403,7 @@ qnapstats==0.4.0 quantum-gateway==0.0.8 # homeassistant.components.radio_browser -radios==0.2.0 +radios==0.3.1 # homeassistant.components.radiotherm radiotherm==2.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3bd4e3436f9..774389dc966 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1852,7 +1852,7 @@ qingping-ble==0.10.0 qnapstats==0.4.0 # homeassistant.components.radio_browser -radios==0.2.0 +radios==0.3.1 # homeassistant.components.radiotherm radiotherm==2.1.0 From 31a0b539bda51d0c7aa0842837077effc1e69814 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:36:17 +0100 Subject: [PATCH 0872/1691] Bump github/codeql-action from 3.24.6 to 3.24.7 (#113207) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a2e04d98754..dea995ed235 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@v4.1.1 - name: Initialize CodeQL - uses: github/codeql-action/init@v3.24.6 + uses: github/codeql-action/init@v3.24.7 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.24.6 + uses: github/codeql-action/analyze@v3.24.7 with: category: "/language:python" From 29772926eca581cb61d454853afe943cf627da68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:36:53 +0100 Subject: [PATCH 0873/1691] Bump Wandalen/wretry.action from 1.4.9 to 1.4.10 (#113206) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d6a440409cd..fdf5ed6d697 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1089,7 +1089,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.4.9 + uses: Wandalen/wretry.action@v1.4.10 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1100,7 +1100,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.4.9 + uses: Wandalen/wretry.action@v1.4.10 with: action: codecov/codecov-action@v3.1.3 with: | From 2005e787bd83e3f2d43acac54b121180ff0b0662 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 13 Mar 2024 04:40:39 -0500 Subject: [PATCH 0874/1691] Bump rokuecp to 0.19.2 (#113198) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 50a4b57fcdd..ce4513fb316 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -11,7 +11,7 @@ "iot_class": "local_polling", "loggers": ["rokuecp"], "quality_scale": "silver", - "requirements": ["rokuecp==0.19.1"], + "requirements": ["rokuecp==0.19.2"], "ssdp": [ { "st": "roku:ecp", diff --git a/requirements_all.txt b/requirements_all.txt index e4fd8291a2e..82f0908fbd8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2451,7 +2451,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.19.1 +rokuecp==0.19.2 # homeassistant.components.romy romy==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 774389dc966..5c14b990fa0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1882,7 +1882,7 @@ rflink==0.0.66 ring-doorbell[listen]==0.8.7 # homeassistant.components.roku -rokuecp==0.19.1 +rokuecp==0.19.2 # homeassistant.components.romy romy==0.0.7 From b1346f3ccdb7f19fe97d94fc69d13afd72e4eb55 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:02:46 +0100 Subject: [PATCH 0875/1691] Update pytest artifact actions to v4 (#110906) --- .github/workflows/ci.yaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fdf5ed6d697..ab8171fdb23 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -800,10 +800,11 @@ jobs: 2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt - name: Upload pytest output if: success() || failure() && (steps.pytest-full.conclusion == 'failure' || steps.pytest-partial.conclusion == 'failure') - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v4.3.1 with: - name: pytest-${{ github.run_number }} + name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }} path: pytest-*.txt + overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@v4.3.1 @@ -927,10 +928,12 @@ jobs: 2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt - name: Upload pytest output if: success() || failure() && steps.pytest-partial.conclusion == 'failure' - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v4.3.1 with: - name: pytest-${{ github.run_number }} + name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ + steps.pytest-partial.outputs.mariadb }} path: pytest-*.txt + overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@v4.3.1 @@ -1056,10 +1059,12 @@ jobs: 2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt - name: Upload pytest output if: success() || failure() && steps.pytest-partial.conclusion == 'failure' - uses: actions/upload-artifact@v3.1.2 + uses: actions/upload-artifact@v4.3.1 with: - name: pytest-${{ github.run_number }} + name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ + steps.pytest-partial.outputs.postgresql }} path: pytest-*.txt + overwrite: true - name: Upload coverage artifact if: needs.info.outputs.skip_coverage != 'true' uses: actions/upload-artifact@v4.3.1 From 488dae43d42cfb3145979ca6815b9fda6e751b54 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 13 Mar 2024 11:04:59 +0100 Subject: [PATCH 0876/1691] Improve lists for MQTT integration (#113184) * Improve lists for MQTT integration * Extra diagnostics tests * Revert changes where the original version was probably faster * Revert change to gather and await in series --- homeassistant/components/mqtt/client.py | 8 ++- homeassistant/components/mqtt/climate.py | 24 ++++---- homeassistant/components/mqtt/debug_info.py | 31 +++++----- .../components/mqtt/device_trigger.py | 16 ++---- homeassistant/components/mqtt/diagnostics.py | 56 ++++++++++--------- homeassistant/components/mqtt/discovery.py | 19 ++++--- homeassistant/components/mqtt/fan.py | 19 +++---- homeassistant/components/mqtt/humidifier.py | 16 +++--- homeassistant/components/mqtt/siren.py | 16 ++++-- homeassistant/components/mqtt/water_heater.py | 27 ++++----- tests/components/mqtt/test_diagnostics.py | 28 +++++++++- 11 files changed, 144 insertions(+), 116 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 1cded579c53..b2fab355c41 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -812,9 +812,11 @@ class MQTT: subscriptions: list[Subscription] = [] if topic in self._simple_subscriptions: subscriptions.extend(self._simple_subscriptions[topic]) - for subscription in self._wildcard_subscriptions: - if subscription.matcher(topic): - subscriptions.append(subscription) + subscriptions.extend( + subscription + for subscription in self._wildcard_subscriptions + if subscription.matcher(topic) + ) return subscriptions @callback diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 2deffaea042..567fe2540fd 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -661,15 +661,12 @@ class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): self._optimistic or CONF_PRESET_MODE_STATE_TOPIC not in config ) - value_templates: dict[str, Template | None] = {} - for key in VALUE_TEMPLATE_KEYS: - value_templates[key] = None - if CONF_VALUE_TEMPLATE in config: - value_templates = { - key: config.get(CONF_VALUE_TEMPLATE) for key in VALUE_TEMPLATE_KEYS - } - for key in VALUE_TEMPLATE_KEYS & config.keys(): - value_templates[key] = config[key] + value_templates: dict[str, Template | None] = { + key: config.get(CONF_VALUE_TEMPLATE) for key in VALUE_TEMPLATE_KEYS + } + value_templates.update( + {key: config[key] for key in VALUE_TEMPLATE_KEYS & config.keys()} + ) self._value_templates = { key: MqttValueTemplate( template, @@ -678,11 +675,10 @@ class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): for key, template in value_templates.items() } - self._command_templates = {} - for key in COMMAND_TEMPLATE_KEYS: - self._command_templates[key] = MqttCommandTemplate( - config.get(key), entity=self - ).async_render + self._command_templates = { + key: MqttCommandTemplate(config.get(key), entity=self).async_render + for key in COMMAND_TEMPLATE_KEYS + } support = ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF if (self._topic[CONF_TEMP_STATE_TOPIC] is not None) or ( diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index a3841c27fa3..7ff93a6bd06 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -239,11 +239,14 @@ def info_for_config_entry(hass: HomeAssistant) -> dict[str, list[Any]]: mqtt_data = get_mqtt_data(hass) mqtt_info: dict[str, list[Any]] = {"entities": [], "triggers": []} - for entity_id in mqtt_data.debug_info_entities: - mqtt_info["entities"].append(_info_for_entity(hass, entity_id)) + mqtt_info["entities"].extend( + _info_for_entity(hass, entity_id) for entity_id in mqtt_data.debug_info_entities + ) - for trigger_key in mqtt_data.debug_info_triggers: - mqtt_info["triggers"].append(_info_for_trigger(hass, trigger_key)) + mqtt_info["triggers"].extend( + _info_for_trigger(hass, trigger_key) + for trigger_key in mqtt_data.debug_info_triggers + ) return mqtt_info @@ -259,16 +262,16 @@ def info_for_device(hass: HomeAssistant, device_id: str) -> dict[str, list[Any]] entries = er.async_entries_for_device( entity_registry, device_id, include_disabled_entities=True ) - for entry in entries: - if entry.entity_id not in mqtt_data.debug_info_entities: - continue + mqtt_info["entities"].extend( + _info_for_entity(hass, entry.entity_id) + for entry in entries + if entry.entity_id in mqtt_data.debug_info_entities + ) - mqtt_info["entities"].append(_info_for_entity(hass, entry.entity_id)) - - for trigger_key, trigger in mqtt_data.debug_info_triggers.items(): - if trigger["device_id"] != device_id: - continue - - mqtt_info["triggers"].append(_info_for_trigger(hass, trigger_key)) + mqtt_info["triggers"].extend( + _info_for_trigger(hass, trigger_key) + for trigger_key, trigger in mqtt_data.debug_info_triggers.items() + if trigger["device_id"] == device_id + ) return mqtt_info diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 8ca5e460419..db94305f9d7 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -353,24 +353,20 @@ async def async_get_triggers( ) -> list[dict[str, str]]: """List device triggers for MQTT devices.""" mqtt_data = get_mqtt_data(hass) - triggers: list[dict[str, str]] = [] if not mqtt_data.device_triggers: - return triggers + return [] - for trig in mqtt_data.device_triggers.values(): - if trig.device_id != device_id or trig.topic is None: - continue - - trigger = { + return [ + { **MQTT_TRIGGER_BASE, "device_id": device_id, "type": trig.type, "subtype": trig.subtype, } - triggers.append(trigger) - - return triggers + for trig in mqtt_data.device_triggers.values() + if trig.device_id == device_id and trig.topic is not None + ] async def async_attach_trigger( diff --git a/homeassistant/components/mqtt/diagnostics.py b/homeassistant/components/mqtt/diagnostics.py index 8b278c1e8d6..9c0f59fe8c3 100644 --- a/homeassistant/components/mqtt/diagnostics.py +++ b/homeassistant/components/mqtt/diagnostics.py @@ -95,36 +95,40 @@ def _async_device_as_dict(hass: HomeAssistant, device: DeviceEntry) -> dict[str, include_disabled_entities=True, ) - for entity_entry in entities: + def _state_dict(entity_entry: er.RegistryEntry) -> dict[str, Any] | None: state = hass.states.get(entity_entry.entity_id) - state_dict = None - if state: - state_dict = dict(state.as_dict()) + if not state: + return None - # The context doesn't provide useful information in this case. - state_dict.pop("context", None) + state_dict = dict(state.as_dict()) - entity_domain = split_entity_id(state.entity_id)[0] + # The context doesn't provide useful information in this case. + state_dict.pop("context", None) - # Retract some sensitive state attributes - if entity_domain == device_tracker.DOMAIN: - state_dict["attributes"] = async_redact_data( - state_dict["attributes"], REDACT_STATE_DEVICE_TRACKER - ) + entity_domain = split_entity_id(state.entity_id)[0] - data["entities"].append( - { - "device_class": entity_entry.device_class, - "disabled_by": entity_entry.disabled_by, - "disabled": entity_entry.disabled, - "entity_category": entity_entry.entity_category, - "entity_id": entity_entry.entity_id, - "icon": entity_entry.icon, - "original_device_class": entity_entry.original_device_class, - "original_icon": entity_entry.original_icon, - "state": state_dict, - "unit_of_measurement": entity_entry.unit_of_measurement, - } - ) + # Retract some sensitive state attributes + if entity_domain == device_tracker.DOMAIN: + state_dict["attributes"] = async_redact_data( + state_dict["attributes"], REDACT_STATE_DEVICE_TRACKER + ) + return state_dict + + data["entities"].extend( + { + "device_class": entity_entry.device_class, + "disabled_by": entity_entry.disabled_by, + "disabled": entity_entry.disabled, + "entity_category": entity_entry.entity_category, + "entity_id": entity_entry.entity_id, + "icon": entity_entry.icon, + "original_device_class": entity_entry.original_device_class, + "original_icon": entity_entry.original_icon, + "state": state_dict, + "unit_of_measurement": entity_entry.unit_of_measurement, + } + for entity_entry in entities + if (state_dict := _state_dict(entity_entry)) is not None + ) return data diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 218413d79f5..0e1b0a799d6 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -403,14 +403,17 @@ async def async_start( # noqa: C901 ): mqtt_data.integration_unsubscribe.pop(key)() - for topic in topics: - key = f"{integration}_{topic}" - mqtt_data.integration_unsubscribe[key] = await mqtt.async_subscribe( - hass, - topic, - functools.partial(async_integration_message_received, integration), - 0, - ) + mqtt_data.integration_unsubscribe.update( + { + f"{integration}_{topic}": await mqtt.async_subscribe( + hass, + topic, + functools.partial(async_integration_message_received, integration), + 0, + ) + for topic in topics + } + ) async def async_stop(hass: HomeAssistant) -> None: diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 848e50e3e2e..0fed4ab666e 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -319,13 +319,11 @@ class MqttFan(MqttEntity, FanEntity): ATTR_PRESET_MODE: config.get(CONF_PRESET_MODE_COMMAND_TEMPLATE), ATTR_OSCILLATING: config.get(CONF_OSCILLATION_COMMAND_TEMPLATE), } - self._command_templates = {} - for key, tpl in command_templates.items(): - self._command_templates[key] = MqttCommandTemplate( - tpl, entity=self - ).async_render + self._command_templates = { + key: MqttCommandTemplate(tpl, entity=self).async_render + for key, tpl in command_templates.items() + } - self._value_templates = {} value_templates: dict[str, Template | None] = { CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE), ATTR_DIRECTION: config.get(CONF_DIRECTION_VALUE_TEMPLATE), @@ -333,11 +331,12 @@ class MqttFan(MqttEntity, FanEntity): ATTR_PRESET_MODE: config.get(CONF_PRESET_MODE_VALUE_TEMPLATE), ATTR_OSCILLATING: config.get(CONF_OSCILLATION_VALUE_TEMPLATE), } - for key, tpl in value_templates.items(): - self._value_templates[key] = MqttValueTemplate( - tpl, - entity=self, + self._value_templates = { + key: MqttValueTemplate( + tpl, entity=self ).async_render_with_possible_json_value + for key, tpl in value_templates.items() + } def _prepare_subscribe_topics(self) -> None: """(Re)Subscribe to topics.""" diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 400b8154c8d..f3b9cf4c4ff 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -254,18 +254,16 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): ) self._optimistic_mode = optimistic or self._topic[CONF_MODE_STATE_TOPIC] is None - self._command_templates = {} command_templates: dict[str, Template | None] = { CONF_STATE: config.get(CONF_COMMAND_TEMPLATE), ATTR_HUMIDITY: config.get(CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE), ATTR_MODE: config.get(CONF_MODE_COMMAND_TEMPLATE), } - for key, tpl in command_templates.items(): - self._command_templates[key] = MqttCommandTemplate( - tpl, entity=self - ).async_render + self._command_templates = { + key: MqttCommandTemplate(tpl, entity=self).async_render + for key, tpl in command_templates.items() + } - self._value_templates = {} value_templates: dict[str, Template | None] = { ATTR_ACTION: config.get(CONF_ACTION_TEMPLATE), ATTR_CURRENT_HUMIDITY: config.get(CONF_CURRENT_HUMIDITY_TEMPLATE), @@ -273,11 +271,13 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): ATTR_HUMIDITY: config.get(CONF_TARGET_HUMIDITY_STATE_TEMPLATE), ATTR_MODE: config.get(CONF_MODE_STATE_TEMPLATE), } - for key, tpl in value_templates.items(): - self._value_templates[key] = MqttValueTemplate( + self._value_templates = { + key: MqttValueTemplate( tpl, entity=self, ).async_render_with_possible_json_value + for key, tpl in value_templates.items() + } def add_subscription( self, diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 5eda05d531f..e360416db7c 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -364,9 +364,13 @@ class MqttSiren(MqttEntity, SirenEntity): def _update(self, data: SirenTurnOnServiceParameters) -> None: """Update the extra siren state attributes.""" - for attribute, support in SUPPORTED_ATTRIBUTES.items(): - if self._attr_supported_features & support and attribute in data: - data_attr = data[attribute] # type: ignore[literal-required] - if self._extra_attributes.get(attribute) == data_attr: - continue - self._extra_attributes[attribute] = data_attr + self._extra_attributes.update( + { + attribute: data_attr + for attribute, support in SUPPORTED_ATTRIBUTES.items() + if self._attr_supported_features & support + and attribute in data + and (data_attr := data[attribute]) # type: ignore[literal-required] + != self._extra_attributes.get(attribute) + } + ) diff --git a/homeassistant/components/mqtt/water_heater.py b/homeassistant/components/mqtt/water_heater.py index f8061873c69..09db5fc33e7 100644 --- a/homeassistant/components/mqtt/water_heater.py +++ b/homeassistant/components/mqtt/water_heater.py @@ -226,28 +226,23 @@ class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity): if self._topic[CONF_MODE_STATE_TOPIC] is None or self._optimistic: self._attr_current_operation = STATE_OFF - value_templates: dict[str, Template | None] = {} - for key in VALUE_TEMPLATE_KEYS: - value_templates[key] = None - if CONF_VALUE_TEMPLATE in config: - value_templates = { - key: config.get(CONF_VALUE_TEMPLATE) for key in VALUE_TEMPLATE_KEYS - } - for key in VALUE_TEMPLATE_KEYS & config.keys(): - value_templates[key] = config[key] + value_templates: dict[str, Template | None] = { + key: config.get(CONF_VALUE_TEMPLATE) for key in VALUE_TEMPLATE_KEYS + } + value_templates.update( + {key: config[key] for key in VALUE_TEMPLATE_KEYS & config.keys()} + ) self._value_templates = { key: MqttValueTemplate( - template, - entity=self, + template, entity=self ).async_render_with_possible_json_value for key, template in value_templates.items() } - self._command_templates = {} - for key in COMMAND_TEMPLATE_KEYS: - self._command_templates[key] = MqttCommandTemplate( - config.get(key), entity=self - ).async_render + self._command_templates = { + key: MqttCommandTemplate(config.get(key), entity=self).async_render + for key in COMMAND_TEMPLATE_KEYS + } support = WaterHeaterEntityFeature(0) if (self._topic[CONF_TEMP_STATE_TOPIC] is not None) or ( diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index 9ca903b88b7..8959ad0f719 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -7,7 +7,7 @@ import pytest from homeassistant.components import mqtt from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import async_fire_mqtt_message from tests.components.diagnostics import ( @@ -25,6 +25,7 @@ default_config = { async def test_entry_diagnostics( hass: HomeAssistant, device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, hass_client: ClientSessionGenerator, mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: @@ -260,3 +261,28 @@ async def test_redact_diagnostics( "mqtt_config": expected_config, "mqtt_debug_info": expected_debug_info, } + + # Disable the entity and remove the state + ent_registry = er.async_get(hass) + device_tracker_entry = er.async_entries_for_device(ent_registry, device_entry.id)[0] + ent_registry.async_update_entity( + device_tracker_entry.entity_id, disabled_by=er.RegistryEntryDisabler.USER + ) + hass.states.async_remove(device_tracker_entry.entity_id) + + # Assert disabled entries are filtered + assert await get_diagnostics_for_device( + hass, hass_client, config_entry, device_entry + ) == { + "connected": True, + "device": { + "id": device_entry.id, + "name": None, + "name_by_user": None, + "disabled": False, + "disabled_by": None, + "entities": [], + }, + "mqtt_config": expected_config, + "mqtt_debug_info": {"entities": [], "triggers": []}, + } From d6f1405874c9b8c4fe598227cd8a6f610856b245 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 00:06:30 -1000 Subject: [PATCH 0877/1691] Migrate one time listeners to use async_run_hass_job (#113179) --- homeassistant/core.py | 12 ++++++------ tests/test_core.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 2fe50e4e50b..3d2df054036 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1264,7 +1264,7 @@ _FilterableJobType = tuple[ @dataclass(slots=True) class _OneTimeListener: hass: HomeAssistant - listener: Callable[[Event], Coroutine[Any, Any, None] | None] + listener_job: HassJob[[Event], Coroutine[Any, Any, None] | None] remove: CALLBACK_TYPE | None = None @callback @@ -1275,14 +1275,14 @@ class _OneTimeListener: return self.remove() self.remove = None - self.hass.async_run_job(self.listener, event) + self.hass.async_run_hass_job(self.listener_job, event) def __repr__(self) -> str: """Return the representation of the listener and source module.""" - module = inspect.getmodule(self.listener) + module = inspect.getmodule(self.listener_job.target) if module: - return f"<_OneTimeListener {module.__name__}:{self.listener}>" - return f"<_OneTimeListener {self.listener}>" + return f"<_OneTimeListener {module.__name__}:{self.listener_job.target}>" + return f"<_OneTimeListener {self.listener_job.target}>" class EventBus: @@ -1472,7 +1472,7 @@ class EventBus: This method must be run in the event loop. """ - one_time_listener = _OneTimeListener(self._hass, listener) + one_time_listener = _OneTimeListener(self._hass, HassJob(listener)) remove = self._async_listen_filterable_job( event_type, ( diff --git a/tests/test_core.py b/tests/test_core.py index 3d72c7481f5..d30ccdd018b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3098,7 +3098,7 @@ def test_one_time_listener_repr(hass: HomeAssistant) -> None: def _listener(event: ha.Event): """Test listener.""" - one_time_listener = ha._OneTimeListener(hass, _listener) + one_time_listener = ha._OneTimeListener(hass, HassJob(_listener)) repr_str = repr(one_time_listener) assert "OneTimeListener" in repr_str assert "test_core" in repr_str From 6ecafbcc2ce4ef5db358d3b46f57c39729a56221 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:26:16 +0100 Subject: [PATCH 0878/1691] Bump actions/checkout from 4.1.1 to 4.1.2 (#113208) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 12 ++++++------ .github/workflows/ci.yaml | 28 ++++++++++++++-------------- .github/workflows/codeql.yml | 2 +- .github/workflows/translations.yml | 2 +- .github/workflows/wheels.yml | 6 +++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 333c31ce841..ef0fbe03b63 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -25,7 +25,7 @@ jobs: publish: ${{ steps.version.outputs.publish }} steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 with: fetch-depth: 0 @@ -57,7 +57,7 @@ jobs: if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@v5.0.0 @@ -99,7 +99,7 @@ jobs: arch: ${{ fromJson(needs.init.outputs.architectures) }} steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Download nightly wheels of frontend if: needs.init.outputs.channel == 'dev' @@ -263,7 +263,7 @@ jobs: - green steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set build additional args run: | @@ -300,7 +300,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Initialize git uses: home-assistant/actions/helpers/git-init@master @@ -338,7 +338,7 @@ jobs: id-token: write steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Install Cosign uses: sigstore/cosign-installer@v3.4.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ab8171fdb23..2a5d4927efa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -89,7 +89,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Generate partial Python venv restore key id: generate_python_cache_key run: >- @@ -222,7 +222,7 @@ jobs: - info steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.0.0 @@ -268,7 +268,7 @@ jobs: - pre-commit steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@v5.0.0 id: python @@ -308,7 +308,7 @@ jobs: - pre-commit steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@v5.0.0 id: python @@ -347,7 +347,7 @@ jobs: - pre-commit steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@v5.0.0 id: python @@ -441,7 +441,7 @@ jobs: python-version: ${{ fromJSON(needs.info.outputs.python_versions) }} steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.0.0 @@ -510,7 +510,7 @@ jobs: - base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.0.0 @@ -542,7 +542,7 @@ jobs: - base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.0.0 @@ -575,7 +575,7 @@ jobs: - base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.0.0 @@ -619,7 +619,7 @@ jobs: - base steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v5.0.0 @@ -701,7 +701,7 @@ jobs: bluez \ ffmpeg - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.0.0 @@ -856,7 +856,7 @@ jobs: ffmpeg \ libmariadb-dev-compat - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.0.0 @@ -986,7 +986,7 @@ jobs: ffmpeg \ postgresql-server-dev-14 - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python uses: actions/setup-python@v5.0.0 @@ -1087,7 +1087,7 @@ jobs: timeout-minutes: 10 steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Download all coverage artifacts uses: actions/download-artifact@v4.1.4 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dea995ed235..58f9e17fd4c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Initialize CodeQL uses: github/codeql-action/init@v3.24.7 diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml index c8e25cc83ea..a8cb65c3216 100644 --- a/.github/workflows/translations.yml +++ b/.github/workflows/translations.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@v5.0.0 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 238ed15f33f..9f127acb57d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -28,7 +28,7 @@ jobs: architectures: ${{ steps.info.outputs.architectures }} steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Get information id: info @@ -88,7 +88,7 @@ jobs: arch: ${{ fromJson(needs.init.outputs.architectures) }} steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Download env_file uses: actions/download-artifact@v4.1.4 @@ -126,7 +126,7 @@ jobs: arch: ${{ fromJson(needs.init.outputs.architectures) }} steps: - name: Checkout the repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.2 - name: Download env_file uses: actions/download-artifact@v4.1.4 From 99eaa07f6fa4ea0d1080cffb952869035933998a Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 13 Mar 2024 11:29:39 +0100 Subject: [PATCH 0879/1691] Add message from Bad Request errors to HassioAPIError (#113144) Co-authored-by: Mike Degatano --- homeassistant/components/hassio/const.py | 1 + homeassistant/components/hassio/handler.py | 21 +++++++++---- .../components/hassio/websocket_api.py | 4 --- tests/components/hassio/test_websocket_api.py | 31 +++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py index 2aa5fdd13b5..0845a98f832 100644 --- a/homeassistant/components/hassio/const.py +++ b/homeassistant/components/hassio/const.py @@ -21,6 +21,7 @@ ATTR_HOMEASSISTANT = "homeassistant" ATTR_HOMEASSISTANT_EXCLUDE_DATABASE = "homeassistant_exclude_database" ATTR_INPUT = "input" ATTR_ISSUES = "issues" +ATTR_MESSAGE = "message" ATTR_METHOD = "method" ATTR_PANELS = "panels" ATTR_PASSWORD = "password" diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index bcfb4a497a0..148f9299954 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -22,7 +22,7 @@ from homeassistant.const import SERVER_PORT from homeassistant.core import HomeAssistant from homeassistant.loader import bind_hass -from .const import ATTR_DISCOVERY, DOMAIN, X_HASS_SOURCE +from .const import ATTR_DISCOVERY, ATTR_MESSAGE, ATTR_RESULT, DOMAIN, X_HASS_SOURCE _P = ParamSpec("_P") @@ -577,7 +577,7 @@ class HassIO: raise HassioAPIError() try: - request = await self.websession.request( + response = await self.websession.request( method, joined_url, json=payload, @@ -590,14 +590,23 @@ class HassIO: timeout=aiohttp.ClientTimeout(total=timeout), ) - if request.status != HTTPStatus.OK: - _LOGGER.error("%s return code %d", command, request.status) + if response.status != HTTPStatus.OK: + error = await response.json(encoding="utf-8") + if error.get(ATTR_RESULT) == "error": + raise HassioAPIError(error.get(ATTR_MESSAGE)) + + _LOGGER.error( + "Request to %s method %s returned with code %d", + command, + method, + response.status, + ) raise HassioAPIError() if return_text: - return await request.text(encoding="utf-8") + return await response.text(encoding="utf-8") - return await request.json(encoding="utf-8") + return await response.json(encoding="utf-8") except TimeoutError: _LOGGER.error("Timeout on %s request", command) diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index 94e83f12107..22315161300 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -22,7 +22,6 @@ from .const import ( ATTR_DATA, ATTR_ENDPOINT, ATTR_METHOD, - ATTR_RESULT, ATTR_SESSION_DATA_USER_ID, ATTR_TIMEOUT, ATTR_WS_EVENT, @@ -132,9 +131,6 @@ async def websocket_supervisor_api( payload=payload, source="core.websocket_api", ) - - if result.get(ATTR_RESULT) == "error": - raise HassioAPIError(result.get("message")) except HassioAPIError as err: _LOGGER.error("Failed to to call %s - %s", msg[ATTR_ENDPOINT], err) connection.send_error( diff --git a/tests/components/hassio/test_websocket_api.py b/tests/components/hassio/test_websocket_api.py index 7152b248336..67252a0bc83 100644 --- a/tests/components/hassio/test_websocket_api.py +++ b/tests/components/hassio/test_websocket_api.py @@ -172,6 +172,7 @@ async def test_websocket_supervisor_api_error( aioclient_mock.get( "http://127.0.0.1/ping", json={"result": "error", "message": "example error"}, + status=400, ) await websocket_client.send_json( @@ -184,9 +185,39 @@ async def test_websocket_supervisor_api_error( ) msg = await websocket_client.receive_json() + assert msg["error"]["code"] == "unknown_error" assert msg["error"]["message"] == "example error" +async def test_websocket_supervisor_api_error_without_msg( + hassio_env, + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + aioclient_mock: AiohttpClientMocker, +) -> None: + """Test Supervisor websocket api error.""" + assert await async_setup_component(hass, "hassio", {}) + websocket_client = await hass_ws_client(hass) + aioclient_mock.get( + "http://127.0.0.1/ping", + json={}, + status=400, + ) + + await websocket_client.send_json( + { + WS_ID: 1, + WS_TYPE: WS_TYPE_API, + ATTR_ENDPOINT: "/ping", + ATTR_METHOD: "get", + } + ) + + msg = await websocket_client.receive_json() + assert msg["error"]["code"] == "unknown_error" + assert msg["error"]["message"] == "" + + async def test_websocket_non_admin_user( hassio_env, hass: HomeAssistant, From 546e5f607f2d9dd7c1fae3df9d733490a8dc2535 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 00:57:11 -1000 Subject: [PATCH 0880/1691] Migrate torque to use async_add_hass_job (#113218) --- homeassistant/components/torque/sensor.py | 25 +++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/torque/sensor.py b/homeassistant/components/torque/sensor.py index 6839aa39ccd..8edf4fe49fc 100644 --- a/homeassistant/components/torque/sensor.py +++ b/homeassistant/components/torque/sensor.py @@ -4,12 +4,13 @@ from __future__ import annotations import re +from aiohttp import web import voluptuous as vol from homeassistant.components.http import KEY_HASS, HomeAssistantView from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_EMAIL, CONF_NAME, DEGREE -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HassJob, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -50,8 +51,8 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Torque platform.""" - vehicle = config.get(CONF_NAME) - email = config.get(CONF_EMAIL) + vehicle: str | None = config.get(CONF_NAME) + email: str | None = config.get(CONF_EMAIL) sensors: dict[int, TorqueSensor] = {} hass.http.register_view( @@ -65,21 +66,27 @@ class TorqueReceiveDataView(HomeAssistantView): url = API_PATH name = "api:torque" - def __init__(self, email, vehicle, sensors, add_entities): + def __init__( + self, + email: str | None, + vehicle: str | None, + sensors: dict[int, TorqueSensor], + add_entities: AddEntitiesCallback, + ) -> None: """Initialize a Torque view.""" self.email = email self.vehicle = vehicle self.sensors = sensors - self.add_entities = add_entities + self.add_entities_job = HassJob(add_entities) @callback - def get(self, request): + def get(self, request: web.Request) -> str | None: """Handle Torque data request.""" - hass = request.app[KEY_HASS] + hass: HomeAssistant = request.app[KEY_HASS] data = request.query if self.email is not None and self.email != data[SENSOR_EMAIL_FIELD]: - return + return None names = {} units = {} @@ -109,7 +116,7 @@ class TorqueReceiveDataView(HomeAssistantView): self.sensors[pid] = TorqueSensor( ENTITY_NAME_FORMAT.format(self.vehicle, name), units.get(pid) ) - hass.async_add_job(self.add_entities, [self.sensors[pid]]) + hass.async_add_hass_job(self.add_entities_job, [self.sensors[pid]]) return "OK!" From bbef3f7f68b6b2e69ec72f2c854867db3a23864e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 00:58:34 -1000 Subject: [PATCH 0881/1691] Only extract traceback once in system_log (#113201) --- .../components/system_log/__init__.py | 36 +++++++++++++------ homeassistant/components/zha/core/gateway.py | 9 +++-- tests/components/system_log/test_init.py | 9 +++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 8a8e8486f93..83a89a277cc 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -63,14 +63,19 @@ SERVICE_WRITE_SCHEMA = vol.Schema( def _figure_out_source( - record: logging.LogRecord, paths_re: re.Pattern[str] + record: logging.LogRecord, + paths_re: re.Pattern[str], + extracted_tb: traceback.StackSummary | None = None, ) -> tuple[str, int]: """Figure out where a log message came from.""" # If a stack trace exists, extract file names from the entire call stack. # The other case is when a regular "log" is made (without an attached # exception). In that case, just use the file where the log was made from. if record.exc_info: - stack = [(x[0], x[1]) for x in traceback.extract_tb(record.exc_info[2])] + stack = [ + (x[0], x[1]) + for x in (extracted_tb or traceback.extract_tb(record.exc_info[2])) + ] for i, (filename, _) in enumerate(stack): # Slice the stack to the first frame that matches # the record pathname. @@ -161,13 +166,19 @@ class LogEntry: "level", "message", "exception", + "extracted_tb", "root_cause", "source", "count", "key", ) - def __init__(self, record: logging.LogRecord, source: tuple[str, int]) -> None: + def __init__( + self, + record: logging.LogRecord, + paths_re: re.Pattern, + figure_out_source: bool = False, + ) -> None: """Initialize a log entry.""" self.first_occurred = self.timestamp = record.created self.name = record.name @@ -176,16 +187,21 @@ class LogEntry: # This must be manually tested when changing the code. self.message = deque([_safe_get_message(record)], maxlen=5) self.exception = "" - self.root_cause = None + self.root_cause: str | None = None + extracted_tb: traceback.StackSummary | None = None if record.exc_info: self.exception = "".join(traceback.format_exception(*record.exc_info)) - _, _, tb = record.exc_info - # Last line of traceback contains the root cause of the exception - if extracted := traceback.extract_tb(tb): + if extracted := traceback.extract_tb(record.exc_info[2]): + # Last line of traceback contains the root cause of the exception + extracted_tb = extracted self.root_cause = str(extracted[-1]) - self.source = source + if figure_out_source: + self.source = _figure_out_source(record, paths_re, extracted_tb) + else: + self.source = (record.pathname, record.lineno) self.count = 1 - self.key = (self.name, source, self.root_cause) + self.extracted_tb = extracted_tb + self.key = (self.name, self.source, self.root_cause) def to_dict(self) -> dict[str, Any]: """Convert object into dict to maintain backward compatibility.""" @@ -259,7 +275,7 @@ class LogErrorHandler(logging.Handler): default upper limit is set to 50 (older entries are discarded) but can be changed if needed. """ - entry = LogEntry(record, _figure_out_source(record, self.paths_re)) + entry = LogEntry(record, self.paths_re, figure_out_source=True) self.records.add_entry(entry) if self.fire_event: self.hass.bus.fire(EVENT_SYSTEM_LOG, entry.to_dict()) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 62112f70584..9556631f45b 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -30,7 +30,7 @@ from zigpy.state import State from zigpy.types.named import EUI64 from homeassistant import __path__ as HOMEASSISTANT_PATH -from homeassistant.components.system_log import LogEntry, _figure_out_source +from homeassistant.components.system_log import LogEntry from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -871,10 +871,9 @@ class LogRelayHandler(logging.Handler): def emit(self, record: LogRecord) -> None: """Relay log message via dispatcher.""" - if record.levelno >= logging.WARN: - entry = LogEntry(record, _figure_out_source(record, self.paths_re)) - else: - entry = LogEntry(record, (record.pathname, record.lineno)) + entry = LogEntry( + record, self.paths_re, figure_out_source=record.levelno >= logging.WARN + ) async_dispatcher_send( self.hass, ZHA_GW_MSG, diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 133b48ea745..22cef15b1ab 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -459,14 +459,19 @@ async def test__figure_out_source(hass: HomeAssistant) -> None: except ValueError as ex: exc_info = (type(ex), ex, ex.__traceback__) mock_record = MagicMock( - pathname="should not hit", + pathname="figure_out_source is False", lineno=5, exc_info=exc_info, ) regex_str = f"({__file__})" + paths_re = re.compile(regex_str) file, line_no = system_log._figure_out_source( mock_record, - re.compile(regex_str), + paths_re, + traceback.extract_tb(exc_info[2]), ) assert file == __file__ assert line_no != 5 + + entry = system_log.LogEntry(mock_record, paths_re, figure_out_source=False) + assert entry.source == ("figure_out_source is False", 5) From fb163278a458ffc47fff7ff61b4c740f7c37be22 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 00:59:36 -1000 Subject: [PATCH 0882/1691] Simplify entity _attr cached_property getter implementation (#113195) --- homeassistant/helpers/entity.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 191882b7afa..f4cc933a6c6 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -11,6 +11,7 @@ from enum import Enum, IntFlag, auto import functools as ft import logging import math +from operator import attrgetter import sys from timeit import default_timer as timer from types import FunctionType @@ -340,16 +341,6 @@ class CachedProperties(type): return _deleter - def getter(name: str) -> Callable[[Any], Any]: - """Create a getter for an _attr_ property.""" - private_attr_name = f"__attr_{name}" - - def _getter(o: Any) -> Any: - """Get an _attr_ property from the backing __attr attribute.""" - return getattr(o, private_attr_name) - - return _getter - def setter(name: str) -> Callable[[Any, Any], None]: """Create a setter for an _attr_ property.""" private_attr_name = f"__attr_{name}" @@ -372,7 +363,9 @@ class CachedProperties(type): def make_property(name: str) -> property: """Help create a property object.""" - return property(fget=getter(name), fset=setter(name), fdel=deleter(name)) + return property( + fget=attrgetter(f"__attr_{name}"), fset=setter(name), fdel=deleter(name) + ) def wrap_attr(cls: CachedProperties, property_name: str) -> None: """Wrap a cached property's corresponding _attr in a property. From a65908becc6905acb511f6dfa2d81e7557621327 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 01:02:07 -1000 Subject: [PATCH 0883/1691] Migrate harmony to use async_run_hass_job (#113178) --- homeassistant/components/harmony/remote.py | 12 +++---- homeassistant/components/harmony/select.py | 11 +++--- .../components/harmony/subscriber.py | 14 ++++---- homeassistant/components/harmony/switch.py | 11 +++--- tests/components/harmony/test_subscriber.py | 36 +++++++++---------- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 90b7f29b17f..37a6811a2b2 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -20,7 +20,7 @@ from homeassistant.components.remote import ( RemoteEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -117,11 +117,11 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): self.async_on_remove( self._data.async_subscribe( HarmonyCallback( - connected=self.async_got_connected, - disconnected=self.async_got_disconnected, - config_updated=self.async_new_config, - activity_starting=self.async_new_activity, - activity_started=self.async_new_activity_finished, + connected=HassJob(self.async_got_connected), + disconnected=HassJob(self.async_got_disconnected), + config_updated=HassJob(self.async_new_config), + activity_starting=HassJob(self.async_new_activity), + activity_started=HassJob(self.async_new_activity_finished), ) ) ) diff --git a/homeassistant/components/harmony/select.py b/homeassistant/components/harmony/select.py index 6338f0c6269..987c7042b99 100644 --- a/homeassistant/components/harmony/select.py +++ b/homeassistant/components/harmony/select.py @@ -7,7 +7,7 @@ import logging from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ACTIVITY_POWER_OFF, DOMAIN, HARMONY_DATA @@ -66,13 +66,14 @@ class HarmonyActivitySelect(HarmonyEntity, SelectEntity): async def async_added_to_hass(self) -> None: """Call when entity is added to hass.""" + activity_update_job = HassJob(self._async_activity_update) self.async_on_remove( self._data.async_subscribe( HarmonyCallback( - connected=self.async_got_connected, - disconnected=self.async_got_disconnected, - activity_starting=self._async_activity_update, - activity_started=self._async_activity_update, + connected=HassJob(self.async_got_connected), + disconnected=HassJob(self.async_got_disconnected), + activity_starting=activity_update_job, + activity_started=activity_update_job, config_updated=None, ) ) diff --git a/homeassistant/components/harmony/subscriber.py b/homeassistant/components/harmony/subscriber.py index 4623e8fa48d..e923df82843 100644 --- a/homeassistant/components/harmony/subscriber.py +++ b/homeassistant/components/harmony/subscriber.py @@ -3,16 +3,15 @@ from __future__ import annotations import asyncio -from collections.abc import Callable import logging from typing import Any, NamedTuple -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback _LOGGER = logging.getLogger(__name__) -NoParamCallback = Callable[[], Any] | None -ActivityCallback = Callable[[tuple], Any] | None +NoParamCallback = HassJob[[], Any] | None +ActivityCallback = HassJob[[tuple], Any] | None class HarmonyCallback(NamedTuple): @@ -89,9 +88,8 @@ class HarmonySubscriberMixin: self, callback_func_name: str, argument: tuple | None = None ) -> None: for subscription in self._subscriptions: - current_callback = getattr(subscription, callback_func_name) - if current_callback: + if current_callback_job := getattr(subscription, callback_func_name): if argument: - self._hass.async_run_job(current_callback, argument) + self._hass.async_run_hass_job(current_callback_job, argument) else: - self._hass.async_run_job(current_callback) + self._hass.async_run_hass_job(current_callback_job) diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index f253e19efbd..df32af36ec5 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -8,7 +8,7 @@ from homeassistant.components.script import scripts_with_entity from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -83,13 +83,14 @@ class HarmonyActivitySwitch(HarmonyEntity, SwitchEntity): async def async_added_to_hass(self) -> None: """Call when entity is added to hass.""" + activity_update_job = HassJob(self._async_activity_update) self.async_on_remove( self._data.async_subscribe( HarmonyCallback( - connected=self.async_got_connected, - disconnected=self.async_got_disconnected, - activity_starting=self._async_activity_update, - activity_started=self._async_activity_update, + connected=HassJob(self.async_got_connected), + disconnected=HassJob(self.async_got_disconnected), + activity_starting=activity_update_job, + activity_started=activity_update_job, config_updated=None, ) ) diff --git a/tests/components/harmony/test_subscriber.py b/tests/components/harmony/test_subscriber.py index 94a8ffbc37a..f1d1866a044 100644 --- a/tests/components/harmony/test_subscriber.py +++ b/tests/components/harmony/test_subscriber.py @@ -7,7 +7,7 @@ from homeassistant.components.harmony.subscriber import ( HarmonyCallback, HarmonySubscriberMixin, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HassJob, HomeAssistant _NO_PARAM_CALLBACKS = { "connected": "_connected", @@ -48,18 +48,18 @@ async def test_async_callbacks(hass: HomeAssistant) -> None: """Ensure we handle async callbacks.""" subscriber = HarmonySubscriberMixin(hass) - callbacks = {k: AsyncMock() for k in _ALL_CALLBACK_NAMES} + callbacks = {k: HassJob(AsyncMock()) for k in _ALL_CALLBACK_NAMES} subscriber.async_subscribe(HarmonyCallback(**callbacks)) _call_all_callbacks(subscriber) await hass.async_block_till_done() for callback_name in _NO_PARAM_CALLBACKS: callback_mock = callbacks[callback_name] - callback_mock.assert_awaited_once() + callback_mock.target.assert_awaited_once() for callback_name in _ACTIVITY_CALLBACKS: callback_mock = callbacks[callback_name] - callback_mock.assert_awaited_once_with(_ACTIVITY_TUPLE) + callback_mock.target.assert_awaited_once_with(_ACTIVITY_TUPLE) async def test_long_async_callbacks(hass: HomeAssistant) -> None: @@ -77,8 +77,8 @@ async def test_long_async_callbacks(hass: HomeAssistant) -> None: async def notifies_when_called(): notifier_event_two.set() - callbacks_one = {k: blocks_until_notified for k in _ALL_CALLBACK_NAMES} - callbacks_two = {k: notifies_when_called for k in _ALL_CALLBACK_NAMES} + callbacks_one = {k: HassJob(blocks_until_notified) for k in _ALL_CALLBACK_NAMES} + callbacks_two = {k: HassJob(notifies_when_called) for k in _ALL_CALLBACK_NAMES} subscriber.async_subscribe(HarmonyCallback(**callbacks_one)) subscriber.async_subscribe(HarmonyCallback(**callbacks_two)) @@ -92,29 +92,29 @@ async def test_callbacks(hass: HomeAssistant) -> None: """Ensure we handle non-async callbacks.""" subscriber = HarmonySubscriberMixin(hass) - callbacks = {k: MagicMock() for k in _ALL_CALLBACK_NAMES} + callbacks = {k: HassJob(MagicMock()) for k in _ALL_CALLBACK_NAMES} subscriber.async_subscribe(HarmonyCallback(**callbacks)) _call_all_callbacks(subscriber) await hass.async_block_till_done() for callback_name in _NO_PARAM_CALLBACKS: callback_mock = callbacks[callback_name] - callback_mock.assert_called_once() + callback_mock.target.assert_called_once() for callback_name in _ACTIVITY_CALLBACKS: callback_mock = callbacks[callback_name] - callback_mock.assert_called_once_with(_ACTIVITY_TUPLE) + callback_mock.target.assert_called_once_with(_ACTIVITY_TUPLE) async def test_subscribe_unsubscribe(hass: HomeAssistant) -> None: """Ensure we handle subscriptions and unsubscriptions correctly.""" subscriber = HarmonySubscriberMixin(hass) - callback_one = {k: MagicMock() for k in _ALL_CALLBACK_NAMES} + callback_one = {k: HassJob(MagicMock()) for k in _ALL_CALLBACK_NAMES} unsub_one = subscriber.async_subscribe(HarmonyCallback(**callback_one)) - callback_two = {k: MagicMock() for k in _ALL_CALLBACK_NAMES} + callback_two = {k: HassJob(MagicMock()) for k in _ALL_CALLBACK_NAMES} _ = subscriber.async_subscribe(HarmonyCallback(**callback_two)) - callback_three = {k: MagicMock() for k in _ALL_CALLBACK_NAMES} + callback_three = {k: HassJob(MagicMock()) for k in _ALL_CALLBACK_NAMES} unsub_three = subscriber.async_subscribe(HarmonyCallback(**callback_three)) unsub_one() @@ -124,14 +124,14 @@ async def test_subscribe_unsubscribe(hass: HomeAssistant) -> None: await hass.async_block_till_done() for callback_name in _NO_PARAM_CALLBACKS: - callback_one[callback_name].assert_not_called() - callback_two[callback_name].assert_called_once() - callback_three[callback_name].assert_not_called() + callback_one[callback_name].target.assert_not_called() + callback_two[callback_name].target.assert_called_once() + callback_three[callback_name].target.assert_not_called() for callback_name in _ACTIVITY_CALLBACKS: - callback_one[callback_name].assert_not_called() - callback_two[callback_name].assert_called_once_with(_ACTIVITY_TUPLE) - callback_three[callback_name].assert_not_called() + callback_one[callback_name].target.assert_not_called() + callback_two[callback_name].target.assert_called_once_with(_ACTIVITY_TUPLE) + callback_three[callback_name].target.assert_not_called() def _call_all_callbacks(subscriber): From 44538ed3c32c9d06544f1a7310621c454389abb3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 01:02:56 -1000 Subject: [PATCH 0884/1691] Ensure apple_tv connect loop is cancelled on shutdown (#113191) --- homeassistant/components/apple_tv/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 7ae2b7575bc..0b9ef14f253 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -247,7 +247,12 @@ class AppleTVManager(DeviceListener): def _start_connect_loop(self) -> None: """Start background connect loop to device.""" if not self._task and self.atv is None and self.is_on: - self._task = asyncio.create_task(self._connect_loop()) + self._task = self.config_entry.async_create_background_task( + self.hass, + self._connect_loop(), + name=f"apple_tv connect loop {self.config_entry.title}", + eager_start=True, + ) else: _LOGGER.debug( "Not starting connect loop (%s, %s)", self.atv is None, self.is_on From 6666f6a8a5f1d500712f7f37ca202af6da2f7fe1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 01:04:27 -1000 Subject: [PATCH 0885/1691] Simplify clearing _attr cached_property in entities (#113136) --- homeassistant/helpers/entity.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index f4cc933a6c6..5f31c049d13 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -332,10 +332,7 @@ class CachedProperties(type): Raises AttributeError if the __attr_ attribute does not exist """ # Invalidate the cache of the cached property - try: # noqa: SIM105 suppress is much slower - delattr(o, name) - except AttributeError: - pass + o.__dict__.pop(name, None) # Delete the __attr_ attribute delattr(o, private_attr_name) @@ -354,10 +351,8 @@ class CachedProperties(type): if getattr(o, private_attr_name, _SENTINEL) == val: return setattr(o, private_attr_name, val) - try: # noqa: SIM105 suppress is much slower - delattr(o, name) - except AttributeError: - pass + # Invalidate the cache of the cached property + o.__dict__.pop(name, None) return _setter From 669dd36daf57169c04dad5171ceb7d3201c1e9f4 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Wed, 13 Mar 2024 11:07:28 +0000 Subject: [PATCH 0886/1691] Add diagnostics platform to IPMA (#105697) --- homeassistant/components/ipma/diagnostics.py | 32 ++++++++++ tests/components/ipma/conftest.py | 38 +++++++----- .../ipma/snapshots/test_diagnostics.ambr | 60 +++++++++++++++++++ tests/components/ipma/test_config_flow.py | 2 +- tests/components/ipma/test_diagnostics.py | 22 +++++++ 5 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 homeassistant/components/ipma/diagnostics.py create mode 100644 tests/components/ipma/snapshots/test_diagnostics.ambr create mode 100644 tests/components/ipma/test_diagnostics.py diff --git a/homeassistant/components/ipma/diagnostics.py b/homeassistant/components/ipma/diagnostics.py new file mode 100644 index 00000000000..30c22d6481a --- /dev/null +++ b/homeassistant/components/ipma/diagnostics.py @@ -0,0 +1,32 @@ +"""Diagnostics support for IPMA.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.core import HomeAssistant + +from .const import DATA_API, DATA_LOCATION, DOMAIN + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + + location = hass.data[DOMAIN][entry.entry_id][DATA_LOCATION] + api = hass.data[DOMAIN][entry.entry_id][DATA_API] + + return { + "location_information": { + "latitude": round(float(entry.data[CONF_LATITUDE]), 3), + "longitude": round(float(entry.data[CONF_LONGITUDE]), 3), + "global_id_local": location.global_id_local, + "id_station": location.id_station, + "name": location.name, + "station": location.station, + }, + "current_weather": await location.observation(api), + "weather_forecast": await location.forecast(api, 1), + } diff --git a/tests/components/ipma/conftest.py b/tests/components/ipma/conftest.py index dda0e69d118..7f3e82a8819 100644 --- a/tests/components/ipma/conftest.py +++ b/tests/components/ipma/conftest.py @@ -1,36 +1,42 @@ """Define test fixtures for IPMA.""" +from unittest.mock import patch + import pytest from homeassistant.components.ipma import DOMAIN from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +from homeassistant.core import HomeAssistant + +from . import MockLocation from tests.common import MockConfigEntry -@pytest.fixture(name="config_entry") -def config_entry_fixture(hass, config): +@pytest.fixture +def config_entry(hass): """Define a config entry fixture.""" entry = MockConfigEntry( domain=DOMAIN, - data=config, + data={ + CONF_NAME: "Home", + CONF_LATITUDE: 0, + CONF_LONGITUDE: 0, + }, ) entry.add_to_hass(hass) return entry -@pytest.fixture(name="config") -def config_fixture(): - """Define a config entry data fixture.""" - return { - CONF_NAME: "Home", - CONF_LATITUDE: 0, - CONF_LONGITUDE: 0, - } +@pytest.fixture +async def init_integration( + hass: HomeAssistant, config_entry: MockConfigEntry +) -> MockConfigEntry: + """Set up the IPMA integration for testing.""" + config_entry.add_to_hass(hass) + with patch("pyipma.location.Location.get", return_value=MockLocation()): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() -@pytest.fixture(name="setup_config_entry") -async def setup_config_entry_fixture(hass, config_entry): - """Define a fixture to set up ipma.""" - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + return config_entry diff --git a/tests/components/ipma/snapshots/test_diagnostics.ambr b/tests/components/ipma/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..c95364b6e4a --- /dev/null +++ b/tests/components/ipma/snapshots/test_diagnostics.ambr @@ -0,0 +1,60 @@ +# serializer version: 1 +# name: test_diagnostics + dict({ + 'current_weather': list([ + 0.0, + 71.0, + 1000.0, + 0.0, + 18.0, + 'NW', + 3.94, + ]), + 'location_information': dict({ + 'global_id_local': 1130600, + 'id_station': 1200545, + 'latitude': 0.0, + 'longitude': 0.0, + 'name': 'HomeTown', + 'station': 'HomeTown Station', + }), + 'weather_forecast': list([ + list([ + '7.7', + '2020-01-15T01:00:00+00:00', + 1, + '86.9', + 12.0, + None, + 80.0, + 10.6, + '2020-01-15T02:51:00', + list([ + 10, + 'Light rain', + 'Chuva fraca ou chuvisco', + ]), + 'S', + '32.7', + ]), + list([ + '5.7', + '2020-01-15T02:00:00+00:00', + 1, + '86.9', + 12.0, + None, + 80.0, + 10.6, + '2020-01-15T02:51:00', + list([ + 1, + 'Clear sky', + 'Céu limpo', + ]), + 'S', + '32.7', + ]), + ]), + }) +# --- diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py index 4ca4abdaa2e..e17c8d011a9 100644 --- a/tests/components/ipma/test_config_flow.py +++ b/tests/components/ipma/test_config_flow.py @@ -92,7 +92,7 @@ async def test_config_flow_failures(hass: HomeAssistant) -> None: } -async def test_flow_entry_already_exists(hass: HomeAssistant, config_entry) -> None: +async def test_flow_entry_already_exists(hass: HomeAssistant, init_integration) -> None: """Test user input for config_entry that already exists. Test when the form should show when user puts existing location diff --git a/tests/components/ipma/test_diagnostics.py b/tests/components/ipma/test_diagnostics.py new file mode 100644 index 00000000000..b7d421a2ee5 --- /dev/null +++ b/tests/components/ipma/test_diagnostics.py @@ -0,0 +1,22 @@ +"""Test IPMA diagnostics.""" + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + init_integration, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics.""" + + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, init_integration) + == snapshot + ) From abc5d6a1b412635fa654bc828d3e5277af4e54d9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 12:17:19 +0100 Subject: [PATCH 0887/1691] Update dsmr reader codeowner (#108152) --- CODEOWNERS | 4 ++-- homeassistant/components/dsmr_reader/manifest.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 1ad49aac17a..9cac303b92d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -315,8 +315,8 @@ build.json @home-assistant/supervisor /tests/components/drop_connect/ @ChandlerSystems @pfrazer /homeassistant/components/dsmr/ @Robbie1221 @frenck /tests/components/dsmr/ @Robbie1221 @frenck -/homeassistant/components/dsmr_reader/ @depl0y @glodenox -/tests/components/dsmr_reader/ @depl0y @glodenox +/homeassistant/components/dsmr_reader/ @sorted-bits @glodenox +/tests/components/dsmr_reader/ @sorted-bits @glodenox /homeassistant/components/duotecno/ @cereal2nd /tests/components/duotecno/ @cereal2nd /homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo diff --git a/homeassistant/components/dsmr_reader/manifest.json b/homeassistant/components/dsmr_reader/manifest.json index cec0dd941fb..35dc21384bd 100644 --- a/homeassistant/components/dsmr_reader/manifest.json +++ b/homeassistant/components/dsmr_reader/manifest.json @@ -1,7 +1,7 @@ { "domain": "dsmr_reader", "name": "DSMR Reader", - "codeowners": ["@depl0y", "@glodenox"], + "codeowners": ["@sorted-bits", "@glodenox"], "config_flow": true, "dependencies": ["mqtt"], "documentation": "https://www.home-assistant.io/integrations/dsmr_reader", From f3984a9d3ee94b92fd5a1a8902532d436ca52353 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 02:53:52 -1000 Subject: [PATCH 0888/1691] Constrain pycountry to >=23.12.11 (#112849) --- homeassistant/package_constraints.txt | 4 ++++ script/gen_requirements_all.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index e68407ca389..9606d51aefd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -183,3 +183,7 @@ pandas==2.1.4 # chacha20poly1305-reuseable==0.12.0 is incompatible with cryptography==42.0.x chacha20poly1305-reuseable>=0.12.1 + +# pycountry<23.12.11 imports setuptools at run time +# https://github.com/pycountry/pycountry/blob/ea69bab36f00df58624a0e490fdad4ccdc14268b/HISTORY.txt#L39 +pycountry>=23.12.11 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index aea5548aa76..8db6aee8a96 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -177,6 +177,10 @@ pandas==2.1.4 # chacha20poly1305-reuseable==0.12.0 is incompatible with cryptography==42.0.x chacha20poly1305-reuseable>=0.12.1 + +# pycountry<23.12.11 imports setuptools at run time +# https://github.com/pycountry/pycountry/blob/ea69bab36f00df58624a0e490fdad4ccdc14268b/HISTORY.txt#L39 +pycountry>=23.12.11 """ GENERATED_MESSAGE = ( From 0ccd813a99dd52106c2afc3d0ad53edfc577179d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 03:46:45 -1000 Subject: [PATCH 0889/1691] Remove HassJob wrapping from the DataUpdateCoordinator (#113192) --- homeassistant/helpers/update_coordinator.py | 23 ++------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 9479c356f24..31d9909ad16 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -18,14 +18,7 @@ from typing_extensions import TypeVar from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import ( - CALLBACK_TYPE, - Event, - HassJob, - HassJobType, - HomeAssistant, - callback, -) +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.exceptions import ( ConfigEntryAuthFailed, ConfigEntryError, @@ -108,18 +101,6 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): ) self._listeners: dict[CALLBACK_TYPE, tuple[CALLBACK_TYPE, object | None]] = {} - job_name = "DataUpdateCoordinator" - type_name = type(self).__name__ - if type_name != job_name: - job_name += f" {type_name}" - job_name += f" {name}" - if entry := self.config_entry: - job_name += f" {entry.title} {entry.domain} {entry.entry_id}" - self._job = HassJob( - self.__wrap_handle_refresh_interval, - job_name, - job_type=HassJobType.Callback, - ) self._unsub_refresh: CALLBACK_TYPE | None = None self._unsub_shutdown: CALLBACK_TYPE | None = None self._request_refresh_task: asyncio.TimerHandle | None = None @@ -251,7 +232,7 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): int(loop.time()) + self._microsecond + self._update_interval_seconds ) self._unsub_refresh = loop.call_at( - next_refresh, hass.async_run_hass_job, self._job + next_refresh, self.__wrap_handle_refresh_interval ).cancel @callback From 081a38a21cdc3bff4c90a82fdd5b102aee585c3f Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Wed, 13 Mar 2024 13:56:20 +0000 Subject: [PATCH 0890/1691] Add processes services to System Bridge (#103564) * Add processes services to System Bridge * Update import and fixes from model updates * Change log level from info to debug for process retrieval * Add exception handling for process not found * Consistency * Change HomeAssistantError to ServiceValidationError * Update homeassistant/components/system_bridge/__init__.py --------- Co-authored-by: Erik Montnemery --- .../components/system_bridge/__init__.py | 83 ++++++++++++++++++- .../components/system_bridge/services.yaml | 24 ++++++ .../components/system_bridge/strings.json | 33 ++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index f68bd97732d..03ef06dc914 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -5,6 +5,7 @@ from __future__ import annotations import asyncio from dataclasses import asdict import logging +from typing import Any from systembridgeconnector.exceptions import ( AuthenticationException, @@ -14,6 +15,7 @@ from systembridgeconnector.exceptions import ( from systembridgeconnector.version import Version from systembridgemodels.keyboard_key import KeyboardKey from systembridgemodels.keyboard_text import KeyboardText +from systembridgemodels.modules.processes import Process from systembridgemodels.open_path import OpenPath from systembridgemodels.open_url import OpenUrl import voluptuous as vol @@ -24,6 +26,7 @@ from homeassistant.const import ( CONF_COMMAND, CONF_ENTITY_ID, CONF_HOST, + CONF_ID, CONF_NAME, CONF_PATH, CONF_PORT, @@ -37,7 +40,11 @@ from homeassistant.core import ( ServiceResponse, SupportsResponse, ) -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.exceptions import ( + ConfigEntryAuthFailed, + ConfigEntryNotReady, + ServiceValidationError, +) from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -64,6 +71,8 @@ CONF_BRIDGE = "bridge" CONF_KEY = "key" CONF_TEXT = "text" +SERVICE_GET_PROCESS_BY_ID = "get_process_by_id" +SERVICE_GET_PROCESSES_BY_NAME = "get_processes_by_name" SERVICE_OPEN_PATH = "open_path" SERVICE_POWER_COMMAND = "power_command" SERVICE_OPEN_URL = "open_url" @@ -202,6 +211,52 @@ async def async_setup_entry( raise vol.Invalid(f"Could not find device {device}") from exception raise vol.Invalid(f"Device {device} does not exist") + async def handle_get_process_by_id(service_call: ServiceCall) -> ServiceResponse: + """Handle the get process by id service call.""" + _LOGGER.debug("Get process by id: %s", service_call.data) + coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ + service_call.data[CONF_BRIDGE] + ] + processes: list[Process] = coordinator.data.processes + + # Find process.id from list, raise ServiceValidationError if not found + try: + return asdict( + next( + process + for process in processes + if process.id == service_call.data[CONF_ID] + ) + ) + except StopIteration as exception: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="process_not_found", + translation_placeholders={"id": service_call.data[CONF_ID]}, + ) from exception + + async def handle_get_processes_by_name( + service_call: ServiceCall, + ) -> ServiceResponse: + """Handle the get process by name service call.""" + _LOGGER.debug("Get process by name: %s", service_call.data) + coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][ + service_call.data[CONF_BRIDGE] + ] + processes: list[Process] = coordinator.data.processes + # Find processes from list + items: list[dict[str, Any]] = [ + asdict(process) + for process in processes + if process.name is not None + and service_call.data[CONF_NAME].lower() in process.name.lower() + ] + + return { + "count": len(items), + "processes": list(items), + } + async def handle_open_path(service_call: ServiceCall) -> ServiceResponse: """Handle the open path service call.""" _LOGGER.debug("Open path: %s", service_call.data) @@ -256,6 +311,32 @@ async def async_setup_entry( ) return asdict(response) + hass.services.async_register( + DOMAIN, + SERVICE_GET_PROCESS_BY_ID, + handle_get_process_by_id, + schema=vol.Schema( + { + vol.Required(CONF_BRIDGE): valid_device, + vol.Required(CONF_ID): cv.positive_int, + }, + ), + supports_response=SupportsResponse.ONLY, + ) + + hass.services.async_register( + DOMAIN, + SERVICE_GET_PROCESSES_BY_NAME, + handle_get_processes_by_name, + schema=vol.Schema( + { + vol.Required(CONF_BRIDGE): valid_device, + vol.Required(CONF_NAME): cv.string, + }, + ), + supports_response=SupportsResponse.ONLY, + ) + hass.services.async_register( DOMAIN, SERVICE_OPEN_PATH, diff --git a/homeassistant/components/system_bridge/services.yaml b/homeassistant/components/system_bridge/services.yaml index 49a7931789e..14f621d99fc 100644 --- a/homeassistant/components/system_bridge/services.yaml +++ b/homeassistant/components/system_bridge/services.yaml @@ -1,3 +1,27 @@ +get_process_by_id: + fields: + bridge: + required: true + selector: + device: + integration: system_bridge + id: + required: true + example: 1234 + selector: + number: +get_processes_by_name: + fields: + bridge: + required: true + selector: + device: + integration: system_bridge + name: + required: true + example: "chrome.exe" + selector: + text: open_path: fields: bridge: diff --git a/homeassistant/components/system_bridge/strings.json b/homeassistant/components/system_bridge/strings.json index 16425da88c4..a13b0319aea 100644 --- a/homeassistant/components/system_bridge/strings.json +++ b/homeassistant/components/system_bridge/strings.json @@ -86,6 +86,11 @@ } } }, + "exceptions": { + "process_not_found": { + "message": "Could not find process with id {id}." + } + }, "issues": { "unsupported_version": { "title": "System Bridge Upgrade Required", @@ -107,6 +112,34 @@ } } }, + "get_process_by_id": { + "name": "Get process by ID", + "description": "Gets a process by the ID.", + "fields": { + "bridge": { + "name": "[%key:component::system_bridge::services::open_path::fields::bridge::name%]", + "description": "[%key:component::system_bridge::services::open_path::fields::bridge::description%]" + }, + "id": { + "name": "ID", + "description": "ID of the process to get." + } + } + }, + "get_processes_by_name": { + "name": "Get processes by name", + "description": "Gets a list of processes by the name.", + "fields": { + "bridge": { + "name": "[%key:component::system_bridge::services::open_path::fields::bridge::name%]", + "description": "[%key:component::system_bridge::services::open_path::fields::bridge::description%]" + }, + "name": { + "name": "Name", + "description": "Name of the process to get." + } + } + }, "open_url": { "name": "Open URL", "description": "Opens a URL on the server using the default application.", From dbb07c98e27110fcaf5ebf9876c2e81ae93bd2ef Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 13 Mar 2024 10:22:07 -0500 Subject: [PATCH 0891/1691] Bump pyipp to 0.15.0 (#113204) update pyipp to 0.15.0 --- homeassistant/components/ipp/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ipp/manifest.json b/homeassistant/components/ipp/manifest.json index 3625a2d867e..5168c5de1fa 100644 --- a/homeassistant/components/ipp/manifest.json +++ b/homeassistant/components/ipp/manifest.json @@ -8,6 +8,6 @@ "iot_class": "local_polling", "loggers": ["deepmerge", "pyipp"], "quality_scale": "platinum", - "requirements": ["pyipp==0.14.5"], + "requirements": ["pyipp==0.15.0"], "zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 82f0908fbd8..8d51e3f0ab8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1872,7 +1872,7 @@ pyintesishome==1.8.0 pyipma==3.0.7 # homeassistant.components.ipp -pyipp==0.14.5 +pyipp==0.15.0 # homeassistant.components.iqvia pyiqvia==2022.04.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c14b990fa0..0d39a14550d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1450,7 +1450,7 @@ pyinsteon==1.5.3 pyipma==3.0.7 # homeassistant.components.ipp -pyipp==0.14.5 +pyipp==0.15.0 # homeassistant.components.iqvia pyiqvia==2022.04.0 From 7e0aac3feb2910147c33392261a04d4e70b18dff Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 16:56:33 +0100 Subject: [PATCH 0892/1691] Improve lists in integrations [I-K] (#113221) --- .../components/iaqualink/binary_sensor.py | 8 +-- homeassistant/components/iaqualink/climate.py | 11 ++-- homeassistant/components/iaqualink/light.py | 7 +- homeassistant/components/iaqualink/sensor.py | 7 +- homeassistant/components/iaqualink/switch.py | 7 +- homeassistant/components/icloud/account.py | 9 +-- .../components/insteon/config_flow.py | 18 ++--- homeassistant/components/insteon/schemas.py | 65 +++++++++---------- homeassistant/components/isy994/button.py | 20 +++--- homeassistant/components/isy994/climate.py | 8 +-- homeassistant/components/isy994/cover.py | 13 ++-- homeassistant/components/isy994/fan.py | 14 ++-- homeassistant/components/isy994/light.py | 11 ++-- homeassistant/components/isy994/lock.py | 13 ++-- homeassistant/components/juicenet/switch.py | 7 +- homeassistant/components/knx/climate.py | 8 +-- homeassistant/components/knx/config_flow.py | 20 +++--- homeassistant/components/knx/notify.py | 23 +++---- tests/components/influxdb/test_sensor.py | 4 +- tests/components/kira/test_remote.py | 3 +- tests/components/kira/test_sensor.py | 3 +- 21 files changed, 136 insertions(+), 143 deletions(-) diff --git a/homeassistant/components/iaqualink/binary_sensor.py b/homeassistant/components/iaqualink/binary_sensor.py index defacb26e29..06dbcf18e4a 100644 --- a/homeassistant/components/iaqualink/binary_sensor.py +++ b/homeassistant/components/iaqualink/binary_sensor.py @@ -25,10 +25,10 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up discovered binary sensors.""" - devs = [] - for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]: - devs.append(HassAqualinkBinarySensor(dev)) - async_add_entities(devs, True) + async_add_entities( + (HassAqualinkBinarySensor(dev) for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]), + True, + ) class HassAqualinkBinarySensor(AqualinkEntity, BinarySensorEntity): diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index 77981c98aa4..29576e9fc10 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -33,10 +33,13 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up discovered switches.""" - devs = [] - for dev in hass.data[AQUALINK_DOMAIN][CLIMATE_DOMAIN]: - devs.append(HassAqualinkThermostat(dev)) - async_add_entities(devs, True) + async_add_entities( + ( + HassAqualinkThermostat(dev) + for dev in hass.data[AQUALINK_DOMAIN][CLIMATE_DOMAIN] + ), + True, + ) class HassAqualinkThermostat(AqualinkEntity, ClimateEntity): diff --git a/homeassistant/components/iaqualink/light.py b/homeassistant/components/iaqualink/light.py index d5e37b9bac6..bce4f2c9855 100644 --- a/homeassistant/components/iaqualink/light.py +++ b/homeassistant/components/iaqualink/light.py @@ -31,10 +31,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up discovered lights.""" - devs = [] - for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]: - devs.append(HassAqualinkLight(dev)) - async_add_entities(devs, True) + async_add_entities( + (HassAqualinkLight(dev) for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]), True + ) class HassAqualinkLight(AqualinkEntity, LightEntity): diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 9859e535c60..8e3983e9c91 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -22,10 +22,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up discovered sensors.""" - devs = [] - for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]: - devs.append(HassAqualinkSensor(dev)) - async_add_entities(devs, True) + async_add_entities( + (HassAqualinkSensor(dev) for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]), True + ) class HassAqualinkSensor(AqualinkEntity, SensorEntity): diff --git a/homeassistant/components/iaqualink/switch.py b/homeassistant/components/iaqualink/switch.py index 65d1c81d7fe..05eed0725e3 100644 --- a/homeassistant/components/iaqualink/switch.py +++ b/homeassistant/components/iaqualink/switch.py @@ -24,10 +24,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up discovered switches.""" - devs = [] - for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]: - devs.append(HassAqualinkSwitch(dev)) - async_add_entities(devs, True) + async_add_entities( + (HassAqualinkSwitch(dev) for dev in hass.data[AQUALINK_DOMAIN][DOMAIN]), True + ) class HassAqualinkSwitch(AqualinkEntity, SwitchEntity): diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index cee80535f55..7e7f4632b59 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -319,11 +319,12 @@ class IcloudAccount: def get_devices_with_name(self, name: str) -> list[Any]: """Get devices by name.""" - result = [] name_slug = slugify(name.replace(" ", "", 99)) - for device in self.devices.values(): - if slugify(device.name.replace(" ", "", 99)) == name_slug: - result.append(device) + result = [ + device + for device in self.devices.values() + if slugify(device.name.replace(" ", "", 99)) == name_slug + ] if not result: raise ValueError(f"No device with name {name}") return result diff --git a/homeassistant/components/insteon/config_flow.py b/homeassistant/components/insteon/config_flow.py index ae4216d3c65..7eac51c600e 100644 --- a/homeassistant/components/insteon/config_flow.py +++ b/homeassistant/components/insteon/config_flow.py @@ -84,10 +84,11 @@ def _remove_override(address, options): new_options = {} if options.get(CONF_X10): new_options[CONF_X10] = options.get(CONF_X10) - new_overrides = [] - for override in options[CONF_OVERRIDE]: - if override[CONF_ADDRESS] != address: - new_overrides.append(override) + new_overrides = [ + override + for override in options[CONF_OVERRIDE] + if override[CONF_ADDRESS] != address + ] if new_overrides: new_options[CONF_OVERRIDE] = new_overrides return new_options @@ -100,13 +101,14 @@ def _remove_x10(device, options): new_options = {} if options.get(CONF_OVERRIDE): new_options[CONF_OVERRIDE] = options.get(CONF_OVERRIDE) - new_x10 = [] - for existing_device in options[CONF_X10]: + new_x10 = [ + existing_device + for existing_device in options[CONF_X10] if ( existing_device[CONF_HOUSECODE].lower() != housecode or existing_device[CONF_UNITCODE] != unitcode - ): - new_x10.append(existing_device) + ) + ] if new_x10: new_options[CONF_X10] = new_x10 return new_options, housecode, unitcode diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 1c1b54b4747..7ac627f7be0 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -103,17 +103,18 @@ def add_device_override(config_data, new_override): except ValueError as err: raise ValueError("Incorrect values") from err - overrides = [] - - for override in config_data.get(CONF_OVERRIDE, []): - if override[CONF_ADDRESS] != address: - overrides.append(override) - - curr_override = {} - curr_override[CONF_ADDRESS] = address - curr_override[CONF_CAT] = cat - curr_override[CONF_SUBCAT] = subcat - overrides.append(curr_override) + overrides = [ + override + for override in config_data.get(CONF_OVERRIDE, []) + if override[CONF_ADDRESS] != address + ] + overrides.append( + { + CONF_ADDRESS: address, + CONF_CAT: cat, + CONF_SUBCAT: subcat, + } + ) new_config = {} if config_data.get(CONF_X10): @@ -124,21 +125,20 @@ def add_device_override(config_data, new_override): def add_x10_device(config_data, new_x10): """Add a new X10 device to X10 device list.""" - x10_devices = [] - for x10_device in config_data.get(CONF_X10, []): - if ( - x10_device[CONF_HOUSECODE] != new_x10[CONF_HOUSECODE] - or x10_device[CONF_UNITCODE] != new_x10[CONF_UNITCODE] - ): - x10_devices.append(x10_device) - - curr_device = {} - curr_device[CONF_HOUSECODE] = new_x10[CONF_HOUSECODE] - curr_device[CONF_UNITCODE] = new_x10[CONF_UNITCODE] - curr_device[CONF_PLATFORM] = new_x10[CONF_PLATFORM] - curr_device[CONF_DIM_STEPS] = new_x10[CONF_DIM_STEPS] - x10_devices.append(curr_device) - + x10_devices = [ + x10_device + for x10_device in config_data.get(CONF_X10, []) + if x10_device[CONF_HOUSECODE] != new_x10[CONF_HOUSECODE] + or x10_device[CONF_UNITCODE] != new_x10[CONF_UNITCODE] + ] + x10_devices.append( + { + CONF_HOUSECODE: new_x10[CONF_HOUSECODE], + CONF_UNITCODE: new_x10[CONF_UNITCODE], + CONF_PLATFORM: new_x10[CONF_PLATFORM], + CONF_DIM_STEPS: new_x10[CONF_DIM_STEPS], + } + ) new_config = {} if config_data.get(CONF_OVERRIDE): new_config[CONF_OVERRIDE] = config_data[CONF_OVERRIDE] @@ -223,17 +223,14 @@ def build_hub_schema( def build_remove_override_schema(data): """Build the schema to remove device overrides in config flow options.""" - selection = [] - for override in data: - selection.append(override[CONF_ADDRESS]) + selection = [override[CONF_ADDRESS] for override in data] return vol.Schema({vol.Required(CONF_ADDRESS): vol.In(selection)}) def build_remove_x10_schema(data): """Build the schema to remove an X10 device in config flow options.""" - selection = [] - for device in data: - housecode = device[CONF_HOUSECODE].upper() - unitcode = device[CONF_UNITCODE] - selection.append(f"Housecode: {housecode}, Unitcode: {unitcode}") + selection = [ + f"Housecode: {device[CONF_HOUSECODE].upper()}, Unitcode: {device[CONF_UNITCODE]}" + for device in data + ] return vol.Schema({vol.Required(CONF_DEVICE): vol.In(selection)}) diff --git a/homeassistant/components/isy994/button.py b/homeassistant/components/isy994/button.py index 9e0a3320f6a..b3b6aa40503 100644 --- a/homeassistant/components/isy994/button.py +++ b/homeassistant/components/isy994/button.py @@ -38,7 +38,15 @@ async def async_setup_entry( ISYNodeQueryButtonEntity | ISYNodeBeepButtonEntity | ISYNetworkResourceButtonEntity - ] = [] + ] = [ + ISYNetworkResourceButtonEntity( + node=node, + name=node.name, + unique_id=isy_data.uid_base(node), + device_info=device_info[CONF_NETWORK], + ) + for node in isy_data.net_resources + ] for node in isy_data.root_nodes[Platform.BUTTON]: entities.append( @@ -61,16 +69,6 @@ async def async_setup_entry( ) ) - for node in isy_data.net_resources: - entities.append( - ISYNetworkResourceButtonEntity( - node=node, - name=node.name, - unique_id=isy_data.uid_base(node), - device_info=device_info[CONF_NETWORK], - ) - ) - # Add entity to query full system entities.append( ISYNodeQueryButtonEntity( diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index ba64fe887bc..d4376b5a3b4 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -64,14 +64,14 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the ISY thermostat platform.""" - entities = [] isy_data: IsyData = hass.data[DOMAIN][entry.entry_id] devices: dict[str, DeviceInfo] = isy_data.devices - for node in isy_data.nodes[Platform.CLIMATE]: - entities.append(ISYThermostatEntity(node, devices.get(node.primary_node))) - async_add_entities(entities) + async_add_entities( + ISYThermostatEntity(node, devices.get(node.primary_node)) + for node in isy_data.nodes[Platform.CLIMATE] + ) class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index 19e8a3da197..b9d7ec44d27 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -27,13 +27,16 @@ async def async_setup_entry( ) -> None: """Set up the ISY cover platform.""" isy_data: IsyData = hass.data[DOMAIN][entry.entry_id] - entities: list[ISYCoverEntity | ISYCoverProgramEntity] = [] devices: dict[str, DeviceInfo] = isy_data.devices - for node in isy_data.nodes[Platform.COVER]: - entities.append(ISYCoverEntity(node, devices.get(node.primary_node))) + entities: list[ISYCoverEntity | ISYCoverProgramEntity] = [ + ISYCoverEntity(node, devices.get(node.primary_node)) + for node in isy_data.nodes[Platform.COVER] + ] - for name, status, actions in isy_data.programs[Platform.COVER]: - entities.append(ISYCoverProgramEntity(name, status, actions)) + entities.extend( + ISYCoverProgramEntity(name, status, actions) + for name, status, actions in isy_data.programs[Platform.COVER] + ) async_add_entities(entities) diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index a7d79c29549..da920540476 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -32,13 +32,15 @@ async def async_setup_entry( """Set up the ISY fan platform.""" isy_data: IsyData = hass.data[DOMAIN][entry.entry_id] devices: dict[str, DeviceInfo] = isy_data.devices - entities: list[ISYFanEntity | ISYFanProgramEntity] = [] + entities: list[ISYFanEntity | ISYFanProgramEntity] = [ + ISYFanEntity(node, devices.get(node.primary_node)) + for node in isy_data.nodes[Platform.FAN] + ] - for node in isy_data.nodes[Platform.FAN]: - entities.append(ISYFanEntity(node, devices.get(node.primary_node))) - - for name, status, actions in isy_data.programs[Platform.FAN]: - entities.append(ISYFanProgramEntity(name, status, actions)) + entities.extend( + ISYFanProgramEntity(name, status, actions) + for name, status, actions in isy_data.programs[Platform.FAN] + ) async_add_entities(entities) diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index ec026a2dc22..69701534840 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -32,13 +32,10 @@ async def async_setup_entry( isy_options = entry.options restore_light_state = isy_options.get(CONF_RESTORE_LIGHT_STATE, False) - entities = [] - for node in isy_data.nodes[Platform.LIGHT]: - entities.append( - ISYLightEntity(node, restore_light_state, devices.get(node.primary_node)) - ) - - async_add_entities(entities) + async_add_entities( + ISYLightEntity(node, restore_light_state, devices.get(node.primary_node)) + for node in isy_data.nodes[Platform.LIGHT] + ) class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py index 73b10990d29..dc2da2a6ee2 100644 --- a/homeassistant/components/isy994/lock.py +++ b/homeassistant/components/isy994/lock.py @@ -53,12 +53,15 @@ async def async_setup_entry( """Set up the ISY lock platform.""" isy_data: IsyData = hass.data[DOMAIN][entry.entry_id] devices: dict[str, DeviceInfo] = isy_data.devices - entities: list[ISYLockEntity | ISYLockProgramEntity] = [] - for node in isy_data.nodes[Platform.LOCK]: - entities.append(ISYLockEntity(node, devices.get(node.primary_node))) + entities: list[ISYLockEntity | ISYLockProgramEntity] = [ + ISYLockEntity(node, devices.get(node.primary_node)) + for node in isy_data.nodes[Platform.LOCK] + ] - for name, status, actions in isy_data.programs[Platform.LOCK]: - entities.append(ISYLockProgramEntity(name, status, actions)) + entities.extend( + ISYLockProgramEntity(name, status, actions) + for name, status, actions in isy_data.programs[Platform.LOCK] + ) async_add_entities(entities) async_setup_lock_services(hass) diff --git a/homeassistant/components/juicenet/switch.py b/homeassistant/components/juicenet/switch.py index a0075fe6c3e..d800ac58c2c 100644 --- a/homeassistant/components/juicenet/switch.py +++ b/homeassistant/components/juicenet/switch.py @@ -17,14 +17,13 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the JuiceNet switches.""" - entities = [] juicenet_data = hass.data[DOMAIN][config_entry.entry_id] api = juicenet_data[JUICENET_API] coordinator = juicenet_data[JUICENET_COORDINATOR] - for device in api.devices: - entities.append(JuiceNetChargeNowSwitch(device, coordinator)) - async_add_entities(entities) + async_add_entities( + JuiceNetChargeNowSwitch(device, coordinator) for device in api.devices + ) class JuiceNetChargeNowSwitch(JuiceNetDevice, SwitchEntity): diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 30b7a3bba74..ce1e4f018b9 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -204,10 +204,10 @@ class KNXClimate(KnxEntity, ClimateEntity): """Return the list of available operation/controller modes.""" ha_controller_modes: list[HVACMode | None] = [] if self._device.mode is not None: - for knx_controller_mode in self._device.mode.controller_modes: - ha_controller_modes.append( - CONTROLLER_MODES.get(knx_controller_mode.value) - ) + ha_controller_modes.extend( + CONTROLLER_MODES.get(knx_controller_mode.value) + for knx_controller_mode in self._device.mode.controller_modes + ) if self._device.supports_on_off: if not ha_controller_modes: diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index bac16798f63..2ef913deee0 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -597,17 +597,17 @@ class KNXCommonFlow(ABC, ConfigEntryBaseFlow): value=CONF_KNX_AUTOMATIC, label=CONF_KNX_AUTOMATIC.capitalize() ) ] - for endpoint in self._tunnel_endpoints: - tunnel_endpoint_options.append( - selector.SelectOptionDict( - value=str(endpoint.individual_address), - label=( - f"{endpoint.individual_address} " - f"{'🔐 ' if endpoint.user_id else ''}" - f"(Data Secure GAs: {len(endpoint.group_addresses)})" - ), - ) + tunnel_endpoint_options.extend( + selector.SelectOptionDict( + value=str(endpoint.individual_address), + label=( + f"{endpoint.individual_address} " + f"{'🔐 ' if endpoint.user_id else ''}" + f"(Data Secure GAs: {len(endpoint.group_addresses)})" + ), ) + for endpoint in self._tunnel_endpoints + ) return self.async_show_form( step_id="knxkeys_tunnel_select", data_schema=vol.Schema( diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index 21f8290586a..74ae86dc5d0 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -28,21 +28,16 @@ async def async_get_service( if platform_config := hass.data[DATA_KNX_CONFIG].get(NotifySchema.PLATFORM): xknx: XKNX = hass.data[DOMAIN].xknx - notification_devices = [] - for device_config in platform_config: - notification_devices.append( - XknxNotification( - xknx, - name=device_config[CONF_NAME], - group_address=device_config[KNX_ADDRESS], - value_type=device_config[CONF_TYPE], - ) + notification_devices = [ + XknxNotification( + xknx, + name=device_config[CONF_NAME], + group_address=device_config[KNX_ADDRESS], + value_type=device_config[CONF_TYPE], ) - return ( - KNXNotificationService(notification_devices) - if notification_devices - else None - ) + for device_config in platform_config + ] + return KNXNotificationService(notification_devices) return None diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index d69bfef1b0a..dc88e04b4cf 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -125,9 +125,7 @@ def _make_v2_resultset(*args): def _make_v2_buckets_resultset(): """Create a mock V2 'buckets()' resultset.""" - records = [] - for name in [DEFAULT_BUCKET, "bucket2"]: - records.append(Record({"name": name})) + records = [Record({"name": name}) for name in [DEFAULT_BUCKET, "bucket2"]] return [Table(records)] diff --git a/tests/components/kira/test_remote.py b/tests/components/kira/test_remote.py index 94d0bb9d818..ff3b28617d3 100644 --- a/tests/components/kira/test_remote.py +++ b/tests/components/kira/test_remote.py @@ -16,8 +16,7 @@ DEVICES = [] def add_entities(devices): """Mock add devices.""" - for device in devices: - DEVICES.append(device) + DEVICES.extend(devices) def test_service_call(hass: HomeAssistant) -> None: diff --git a/tests/components/kira/test_sensor.py b/tests/components/kira/test_sensor.py index d1eae78c788..8619c6953ab 100644 --- a/tests/components/kira/test_sensor.py +++ b/tests/components/kira/test_sensor.py @@ -14,8 +14,7 @@ DEVICES = [] def add_entities(devices): """Mock add devices.""" - for device in devices: - DEVICES.append(device) + DEVICES.extend(devices) @patch("homeassistant.components.kira.sensor.KiraReceiver.schedule_update_ha_state") From d4ae4a9cd0a72c542f3ca8b12815c872bfa604f2 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 13 Mar 2024 17:21:00 +0100 Subject: [PATCH 0893/1691] Deprecate `homeassistant.components.is_on` function (#111891) --- homeassistant/components/__init__.py | 10 ++++++++++ tests/components/homeassistant/test_init.py | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 6a3d12fec5e..030e23628d6 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -12,6 +12,7 @@ from __future__ import annotations import logging from homeassistant.core import HomeAssistant, split_entity_id +from homeassistant.helpers.frame import report from homeassistant.helpers.group import expand_entity_ids _LOGGER = logging.getLogger(__name__) @@ -22,6 +23,15 @@ def is_on(hass: HomeAssistant, entity_id: str | None = None) -> bool: If there is no entity id given we will check all. """ + report( + ( + "uses homeassistant.components.is_on." + " This is deprecated and will stop working in Home Assistant 2024.9, it" + " should be updated to use the function of the platform directly." + ), + error_if_core=True, + ) + if entity_id: entity_ids = expand_entity_ids(hass, [entity_id]) else: diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 2294e7a8f75..faa2f34d33c 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -136,10 +136,11 @@ class TestComponentsCore(unittest.TestCase): def test_is_on(self): """Test is_on method.""" - assert comps.is_on(self.hass, "light.Bowl") - assert not comps.is_on(self.hass, "light.Ceiling") - assert comps.is_on(self.hass) - assert not comps.is_on(self.hass, "non_existing.entity") + with pytest.raises( + RuntimeError, + match="Detected code that uses homeassistant.components.is_on. This is deprecated and will stop working", + ): + assert comps.is_on(self.hass, "light.Bowl") def test_turn_on_without_entities(self): """Test turn_on method without entities.""" From 761933acfee1475e611f70dd99d494bbb633ed95 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 17:25:27 +0100 Subject: [PATCH 0894/1691] Improve lists in integrations [G-H] (#113168) --- .../components/garages_amsterdam/sensor.py | 16 ++--- .../components/greeneye_monitor/sensor.py | 62 ++++++++-------- homeassistant/components/group/__init__.py | 12 ++-- .../components/group/reproduce_state.py | 26 ++++--- homeassistant/components/habitica/sensor.py | 12 ++-- homeassistant/components/hassio/sensor.py | 70 +++++++++---------- homeassistant/components/hassio/update.py | 14 ++-- .../components/haveibeenpwned/sensor.py | 6 +- homeassistant/components/hddtemp/sensor.py | 6 +- .../components/here_travel_time/sensor.py | 17 +++-- .../components/hive/binary_sensor.py | 18 +++-- homeassistant/components/hive/climate.py | 5 +- homeassistant/components/hive/light.py | 8 +-- homeassistant/components/hive/sensor.py | 18 +++-- homeassistant/components/hive/switch.py | 18 +++-- homeassistant/components/hive/water_heater.py | 5 +- homeassistant/components/homekit/__init__.py | 12 ++-- homeassistant/components/homekit/util.py | 5 +- .../homekit_controller/device_trigger.py | 40 +++++------ .../components/homekit_controller/event.py | 28 ++++---- .../components/homematic/__init__.py | 4 +- homeassistant/components/homematic/climate.py | 6 +- homeassistant/components/homematic/lock.py | 6 +- .../homematicip_cloud/binary_sensor.py | 16 ++--- .../components/homematicip_cloud/button.py | 11 ++- .../components/homematicip_cloud/climate.py | 10 +-- .../components/homematicip_cloud/cover.py | 18 ++--- .../components/homematicip_cloud/light.py | 6 +- .../components/homematicip_cloud/switch.py | 34 +++++---- homeassistant/components/honeywell/sensor.py | 13 ++-- homeassistant/components/huawei_lte/sensor.py | 20 +++--- homeassistant/components/hue/event.py | 12 ++-- .../components/hue/v2/device_trigger.py | 50 ++++++------- .../hunterdouglas_powerview/button.py | 25 ++++--- .../hunterdouglas_powerview/number.py | 25 ++++--- .../hunterdouglas_powerview/select.py | 25 ++++--- .../hunterdouglas_powerview/sensor.py | 25 ++++--- .../components/hydrawise/binary_sensor.py | 10 +-- homeassistant/components/hyperion/switch.py | 20 +++--- tests/components/history/test_init.py | 4 +- .../history/test_init_db_schema_30.py | 4 +- .../homekit_controller/test_device_trigger.py | 68 +++++++++--------- tests/components/homematicip_cloud/helper.py | 10 +-- tests/components/humidifier/test_init.py | 5 +- 44 files changed, 392 insertions(+), 433 deletions(-) diff --git a/homeassistant/components/garages_amsterdam/sensor.py b/homeassistant/components/garages_amsterdam/sensor.py index d17a8705275..b6fc950a843 100644 --- a/homeassistant/components/garages_amsterdam/sensor.py +++ b/homeassistant/components/garages_amsterdam/sensor.py @@ -27,17 +27,11 @@ async def async_setup_entry( """Defer sensor setup to the shared sensor module.""" coordinator = await get_coordinator(hass) - entities: list[GaragesAmsterdamSensor] = [] - - for info_type in SENSORS: - if getattr(coordinator.data[config_entry.data["garage_name"]], info_type) != "": - entities.append( - GaragesAmsterdamSensor( - coordinator, config_entry.data["garage_name"], info_type - ) - ) - - async_add_entities(entities) + async_add_entities( + GaragesAmsterdamSensor(coordinator, config_entry.data["garage_name"], info_type) + for info_type in SENSORS + if getattr(coordinator.data[config_entry.data["garage_name"]], info_type) != "" + ) class GaragesAmsterdamSensor(GaragesAmsterdamEntity, SensorEntity): diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index c11e08eaa84..1290fc9459a 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -62,48 +62,46 @@ async def async_setup_platform( None, ) if monitor_config: - entities: list[GEMSensor] = [] - channel_configs = monitor_config[CONF_CHANNELS] - for sensor in channel_configs: - entities.append( - CurrentSensor( - monitor, - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_NET_METERING], - ) + entities: list[GEMSensor] = [ + CurrentSensor( + monitor, + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_NET_METERING], ) + for sensor in channel_configs + ] pulse_counter_configs = monitor_config[CONF_PULSE_COUNTERS] - for sensor in pulse_counter_configs: - entities.append( - PulseCounter( - monitor, - sensor[CONF_NUMBER], - sensor[CONF_NAME], - sensor[CONF_COUNTED_QUANTITY], - sensor[CONF_TIME_UNIT], - sensor[CONF_COUNTED_QUANTITY_PER_PULSE], - ) + entities.extend( + PulseCounter( + monitor, + sensor[CONF_NUMBER], + sensor[CONF_NAME], + sensor[CONF_COUNTED_QUANTITY], + sensor[CONF_TIME_UNIT], + sensor[CONF_COUNTED_QUANTITY_PER_PULSE], ) + for sensor in pulse_counter_configs + ) temperature_sensor_configs = monitor_config[CONF_TEMPERATURE_SENSORS] - for sensor in temperature_sensor_configs[CONF_SENSORS]: - entities.append( - TemperatureSensor( - monitor, - sensor[CONF_NUMBER], - sensor[CONF_NAME], - temperature_sensor_configs[CONF_TEMPERATURE_UNIT], - ) + entities.extend( + TemperatureSensor( + monitor, + sensor[CONF_NUMBER], + sensor[CONF_NAME], + temperature_sensor_configs[CONF_TEMPERATURE_UNIT], ) + for sensor in temperature_sensor_configs[CONF_SENSORS] + ) voltage_sensor_configs = monitor_config[CONF_VOLTAGE_SENSORS] - for sensor in voltage_sensor_configs: - entities.append( - VoltageSensor(monitor, sensor[CONF_NUMBER], sensor[CONF_NAME]) - ) + entities.extend( + VoltageSensor(monitor, sensor[CONF_NUMBER], sensor[CONF_NAME]) + for sensor in voltage_sensor_configs + ) async_add_entities(entities) monitor_configs.remove(monitor_config) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index cc060be88cc..55a4ab4ad17 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -185,13 +185,11 @@ def groups_with_entity(hass: HomeAssistant, entity_id: str) -> list[str]: if DOMAIN not in hass.data: return [] - groups = [] - - for group in hass.data[DOMAIN].entities: - if entity_id in group.tracking: - groups.append(group.entity_id) - - return groups + return [ + group.entity_id + for group in hass.data[DOMAIN].entities + if entity_id in group.tracking + ] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index 3646f957597..eb81ab258e9 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -19,20 +19,18 @@ async def async_reproduce_states( reproduce_options: dict[str, Any] | None = None, ) -> None: """Reproduce component states.""" - states_copy = [] - for state in states: - members = get_entity_ids(hass, state.entity_id) - for member in members: - states_copy.append( - State( - member, - state.state, - state.attributes, - last_changed=state.last_changed, - last_updated=state.last_updated, - context=state.context, - ) - ) + states_copy = [ + State( + member, + state.state, + state.attributes, + last_changed=state.last_changed, + last_updated=state.last_updated, + context=state.context, + ) + for state in states + for member in get_entity_ids(hass, state.entity_id) + ] await async_reproduce_state( hass, states_copy, context=context, reproduce_options=reproduce_options ) diff --git a/homeassistant/components/habitica/sensor.py b/homeassistant/components/habitica/sensor.py index eb37f585899..4d48ec199ec 100644 --- a/homeassistant/components/habitica/sensor.py +++ b/homeassistant/components/habitica/sensor.py @@ -87,14 +87,16 @@ async def async_setup_entry( ) -> None: """Set up the habitica sensors.""" - entities: list[SensorEntity] = [] name = config_entry.data[CONF_NAME] sensor_data = HabitipyData(hass.data[DOMAIN][config_entry.entry_id]) await sensor_data.update() - for sensor_type in SENSORS_TYPES: - entities.append(HabitipySensor(name, sensor_type, sensor_data)) - for task_type in TASKS_TYPES: - entities.append(HabitipyTaskSensor(name, task_type, sensor_data)) + + entities: list[SensorEntity] = [ + HabitipySensor(name, sensor_type, sensor_data) for sensor_type in SENSORS_TYPES + ] + entities.extend( + HabitipyTaskSensor(name, task_type, sensor_data) for task_type in TASKS_TYPES + ) async_add_entities(entities, True) diff --git a/homeassistant/components/hassio/sensor.py b/homeassistant/components/hassio/sensor.py index 5a981d6c96c..039bf483682 100644 --- a/homeassistant/components/hassio/sensor.py +++ b/homeassistant/components/hassio/sensor.py @@ -118,50 +118,48 @@ async def async_setup_entry( entities: list[ HassioOSSensor | HassioAddonSensor | CoreSensor | SupervisorSensor | HostSensor - ] = [] - - for addon in coordinator.data[DATA_KEY_ADDONS].values(): - for entity_description in ADDON_ENTITY_DESCRIPTIONS: - entities.append( - HassioAddonSensor( - addon=addon, - coordinator=coordinator, - entity_description=entity_description, - ) - ) - - for entity_description in CORE_ENTITY_DESCRIPTIONS: - entities.append( - CoreSensor( - coordinator=coordinator, - entity_description=entity_description, - ) + ] = [ + HassioAddonSensor( + addon=addon, + coordinator=coordinator, + entity_description=entity_description, ) + for addon in coordinator.data[DATA_KEY_ADDONS].values() + for entity_description in ADDON_ENTITY_DESCRIPTIONS + ] - for entity_description in SUPERVISOR_ENTITY_DESCRIPTIONS: - entities.append( - SupervisorSensor( - coordinator=coordinator, - entity_description=entity_description, - ) + entities.extend( + CoreSensor( + coordinator=coordinator, + entity_description=entity_description, ) + for entity_description in CORE_ENTITY_DESCRIPTIONS + ) - for entity_description in HOST_ENTITY_DESCRIPTIONS: - entities.append( - HostSensor( - coordinator=coordinator, - entity_description=entity_description, - ) + entities.extend( + SupervisorSensor( + coordinator=coordinator, + entity_description=entity_description, ) + for entity_description in SUPERVISOR_ENTITY_DESCRIPTIONS + ) + + entities.extend( + HostSensor( + coordinator=coordinator, + entity_description=entity_description, + ) + for entity_description in HOST_ENTITY_DESCRIPTIONS + ) if coordinator.is_hass_os: - for entity_description in OS_ENTITY_DESCRIPTIONS: - entities.append( - HassioOSSensor( - coordinator=coordinator, - entity_description=entity_description, - ) + entities.extend( + HassioOSSensor( + coordinator=coordinator, + entity_description=entity_description, ) + for entity_description in OS_ENTITY_DESCRIPTIONS + ) async_add_entities(entities) diff --git a/homeassistant/components/hassio/update.py b/homeassistant/components/hassio/update.py index 6a3bba90234..8e7650a9225 100644 --- a/homeassistant/components/hassio/update.py +++ b/homeassistant/components/hassio/update.py @@ -67,14 +67,14 @@ async def async_setup_entry( ), ] - for addon in coordinator.data[DATA_KEY_ADDONS].values(): - entities.append( - SupervisorAddonUpdateEntity( - addon=addon, - coordinator=coordinator, - entity_description=ENTITY_DESCRIPTION, - ) + entities.extend( + SupervisorAddonUpdateEntity( + addon=addon, + coordinator=coordinator, + entity_description=ENTITY_DESCRIPTION, ) + for addon in coordinator.data[DATA_KEY_ADDONS].values() + ) if coordinator.is_hass_os: entities.append( diff --git a/homeassistant/components/haveibeenpwned/sensor.py b/homeassistant/components/haveibeenpwned/sensor.py index c0a55279b82..9933ba11945 100644 --- a/homeassistant/components/haveibeenpwned/sensor.py +++ b/homeassistant/components/haveibeenpwned/sensor.py @@ -49,11 +49,7 @@ def setup_platform( api_key = config[CONF_API_KEY] data = HaveIBeenPwnedData(emails, api_key) - devices = [] - for email in emails: - devices.append(HaveIBeenPwnedSensor(data, email)) - - add_entities(devices) + add_entities(HaveIBeenPwnedSensor(data, email) for email in emails) class HaveIBeenPwnedSensor(SensorEntity): diff --git a/homeassistant/components/hddtemp/sensor.py b/homeassistant/components/hddtemp/sensor.py index 27a514bd58f..3dda9f44004 100644 --- a/homeassistant/components/hddtemp/sensor.py +++ b/homeassistant/components/hddtemp/sensor.py @@ -66,11 +66,7 @@ def setup_platform( if not disks: disks = [next(iter(hddtemp.data)).split("|")[0]] - dev = [] - for disk in disks: - dev.append(HddTempSensor(name, disk, hddtemp)) - - add_entities(dev, True) + add_entities((HddTempSensor(name, disk, hddtemp) for disk in disks), True) class HddTempSensor(SensorEntity): diff --git a/homeassistant/components/here_travel_time/sensor.py b/homeassistant/components/here_travel_time/sensor.py index 190a8455e83..4d7566ef2e2 100644 --- a/homeassistant/components/here_travel_time/sensor.py +++ b/homeassistant/components/here_travel_time/sensor.py @@ -86,16 +86,15 @@ async def async_setup_entry( name = config_entry.data[CONF_NAME] coordinator = hass.data[DOMAIN][entry_id] - sensors: list[HERETravelTimeSensor] = [] - for sensor_description in sensor_descriptions(config_entry.data[CONF_MODE]): - sensors.append( - HERETravelTimeSensor( - entry_id, - name, - sensor_description, - coordinator, - ) + sensors: list[HERETravelTimeSensor] = [ + HERETravelTimeSensor( + entry_id, + name, + sensor_description, + coordinator, ) + for sensor_description in sensor_descriptions(config_entry.data[CONF_MODE]) + ] sensors.append(OriginSensor(entry_id, name, coordinator)) sensors.append(DestinationSensor(entry_id, name, coordinator)) async_add_entities(sensors) diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 472395afa9e..af1df7d4d62 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -52,13 +52,17 @@ async def async_setup_entry( hive = hass.data[DOMAIN][entry.entry_id] devices = hive.session.deviceList.get("binary_sensor") - entities = [] - if devices: - for description in BINARY_SENSOR_TYPES: - for dev in devices: - if dev["hiveType"] == description.key: - entities.append(HiveBinarySensorEntity(hive, dev, description)) - async_add_entities(entities, True) + if not devices: + return + async_add_entities( + ( + HiveBinarySensorEntity(hive, dev, description) + for dev in devices + for description in BINARY_SENSOR_TYPES + if dev["hiveType"] == description.key + ), + True, + ) class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index 8c674605b11..cb1cc15a5bf 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -59,11 +59,8 @@ async def async_setup_entry( hive = hass.data[DOMAIN][entry.entry_id] devices = hive.session.deviceList.get("climate") - entities = [] if devices: - for dev in devices: - entities.append(HiveClimateEntity(hive, dev)) - async_add_entities(entities, True) + async_add_entities((HiveClimateEntity(hive, dev) for dev in devices), True) platform = entity_platform.async_get_current_platform() diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index ebf9efb3d26..1ce49599262 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -34,11 +34,9 @@ async def async_setup_entry( hive: Hive = hass.data[DOMAIN][entry.entry_id] devices = hive.session.deviceList.get("light") - entities = [] - if devices: - for dev in devices: - entities.append(HiveDeviceLight(hive, dev)) - async_add_entities(entities, True) + if not devices: + return + async_add_entities((HiveDeviceLight(hive, dev) for dev in devices), True) class HiveDeviceLight(HiveEntity, LightEntity): diff --git a/homeassistant/components/hive/sensor.py b/homeassistant/components/hive/sensor.py index e52e608af43..5f750642385 100644 --- a/homeassistant/components/hive/sensor.py +++ b/homeassistant/components/hive/sensor.py @@ -54,13 +54,17 @@ async def async_setup_entry( """Set up Hive thermostat based on a config entry.""" hive = hass.data[DOMAIN][entry.entry_id] devices = hive.session.deviceList.get("sensor") - entities = [] - if devices: - for description in SENSOR_TYPES: - for dev in devices: - if dev["hiveType"] == description.key: - entities.append(HiveSensorEntity(hive, dev, description)) - async_add_entities(entities, True) + if not devices: + return + async_add_entities( + ( + HiveSensorEntity(hive, dev, description) + for dev in devices + for description in SENSOR_TYPES + if dev["hiveType"] == description.key + ), + True, + ) class HiveSensorEntity(HiveEntity, SensorEntity): diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 9f97c8e920b..6bcbfc6345c 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -36,13 +36,17 @@ async def async_setup_entry( hive = hass.data[DOMAIN][entry.entry_id] devices = hive.session.deviceList.get("switch") - entities = [] - if devices: - for description in SWITCH_TYPES: - for dev in devices: - if dev["hiveType"] == description.key: - entities.append(HiveSwitch(hive, dev, description)) - async_add_entities(entities, True) + if not devices: + return + async_add_entities( + ( + HiveSwitch(hive, dev, description) + for dev in devices + for description in SWITCH_TYPES + if dev["hiveType"] == description.key + ), + True, + ) class HiveSwitch(HiveEntity, SwitchEntity): diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index 11f8dddb3bd..127fb80ef18 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -49,11 +49,8 @@ async def async_setup_entry( hive = hass.data[DOMAIN][entry.entry_id] devices = hive.session.deviceList.get("water_heater") - entities = [] if devices: - for dev in devices: - entities.append(HiveWaterHeater(hive, dev)) - async_add_entities(entities, True) + async_add_entities((HiveWaterHeater(hive, dev) for dev in devices), True) platform = entity_platform.async_get_current_platform() diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 3f7b210832d..901dd5ec2fb 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -928,13 +928,15 @@ class HomeKit: connection: tuple[str, str], ) -> None: """Purge bridges that exist from failed pairing or manual resets.""" - devices_to_purge = [] - for entry in dev_reg.devices.values(): - if self._entry_id in entry.config_entries and ( + devices_to_purge = [ + entry.id + for entry in dev_reg.devices.values() + if self._entry_id in entry.config_entries + and ( identifier not in entry.identifiers # type: ignore[comparison-overlap] or connection not in entry.connections - ): - devices_to_purge.append(entry.id) + ) + ] for device_id in devices_to_purge: dev_reg.async_remove_device(device_id) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 2d0eccf9b7c..031cdbbc9bd 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -326,10 +326,7 @@ def validate_media_player_features(state: State, feature_list: str) -> bool: # Auto detected return True - error_list = [] - for feature in feature_list: - if feature not in supported_modes: - error_list.append(feature) + error_list = [feature for feature in feature_list if feature not in supported_modes] if error_list: _LOGGER.error( diff --git a/homeassistant/components/homekit_controller/device_trigger.py b/homeassistant/components/homekit_controller/device_trigger.py index c88928e1c2e..a68241d7fc0 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -160,7 +160,7 @@ def enumerate_stateless_switch_group(service: Service) -> list[dict[str, Any]]: ) ) - results = [] + results: list[dict[str, Any]] = [] for idx, switch in enumerate(switches): char = switch[CharacteristicsTypes.INPUT_EVENT] @@ -168,15 +168,15 @@ def enumerate_stateless_switch_group(service: Service) -> list[dict[str, Any]]: # manufacturer might not - clamp options to what they say. all_values = clamp_enum_to_char(InputEventValues, char) - for event_type in all_values: - results.append( - { - "characteristic": char.iid, - "value": event_type, - "type": f"button{idx + 1}", - "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], - } - ) + results.extend( + { + "characteristic": char.iid, + "value": event_type, + "type": f"button{idx + 1}", + "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], + } + for event_type in all_values + ) return results @@ -188,17 +188,15 @@ def enumerate_doorbell(service: Service) -> list[dict[str, Any]]: # manufacturer might not - clamp options to what they say. all_values = clamp_enum_to_char(InputEventValues, input_event) - results = [] - for event_type in all_values: - results.append( - { - "characteristic": input_event.iid, - "value": event_type, - "type": "doorbell", - "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], - } - ) - return results + return [ + { + "characteristic": input_event.iid, + "value": event_type, + "type": "doorbell", + "subtype": HK_TO_HA_INPUT_EVENT_VALUES[event_type], + } + for event_type in all_values + ] TRIGGER_FINDERS = { diff --git a/homeassistant/components/homekit_controller/event.py b/homeassistant/components/homekit_controller/event.py index 7c9052f984b..890c12c9bab 100644 --- a/homeassistant/components/homekit_controller/event.py +++ b/homeassistant/components/homekit_controller/event.py @@ -118,21 +118,21 @@ async def async_setup_entry( ) ) - for switch in switches: - # The Apple docs say that if we number the buttons ourselves - # We do it in service label index order. `switches` is already in - # that order. - entities.append( - HomeKitEventEntity( - conn, - switch, - EventEntityDescription( - key=f"{service.accessory.aid}_{service.iid}", - device_class=EventDeviceClass.BUTTON, - translation_key="button", - ), - ) + # The Apple docs say that if we number the buttons ourselves + # We do it in service label index order. `switches` is already in + # that order. + entities.extend( + HomeKitEventEntity( + conn, + switch, + EventEntityDescription( + key=f"{service.accessory.aid}_{service.iid}", + device_class=EventDeviceClass.BUTTON, + translation_key="button", + ), ) + for switch in switches + ) elif service.type == ServicesTypes.STATELESS_PROGRAMMABLE_SWITCH: # A stateless switch that has a SERVICE_LABEL_INDEX is part of a group diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py index 6eb01728df0..80345866b1f 100644 --- a/homeassistant/components/homematic/__init__.py +++ b/homeassistant/components/homematic/__init__.py @@ -259,9 +259,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, hass.data[DATA_HOMEMATIC].stop) # Init homematic hubs - entity_hubs = [] - for hub_name in conf[CONF_HOSTS]: - entity_hubs.append(HMHub(hass, homematic, hub_name)) + entity_hubs = [HMHub(hass, homematic, hub_name) for hub_name in conf[CONF_HOSTS]] def _hm_service_virtualkey(service: ServiceCall) -> None: """Service to handle virtualkey servicecalls.""" diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index f0e15b7a055..efdb9324f76 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -113,11 +113,7 @@ class HMThermostat(HMDevice, ClimateEntity): @property def preset_modes(self): """Return a list of available preset modes.""" - preset_modes = [] - for mode in self._hmdevice.ACTIONNODE: - if mode in HM_PRESET_MAP: - preset_modes.append(HM_PRESET_MAP[mode]) - return preset_modes + return [HM_PRESET_MAP[mode] for mode in self._hmdevice.ACTIONNODE] @property def current_humidity(self): diff --git a/homeassistant/components/homematic/lock.py b/homeassistant/components/homematic/lock.py index bcd5c7ce7a0..b79f28f2bc7 100644 --- a/homeassistant/components/homematic/lock.py +++ b/homeassistant/components/homematic/lock.py @@ -23,11 +23,7 @@ def setup_platform( if discovery_info is None: return - devices = [] - for conf in discovery_info[ATTR_DISCOVER_DEVICES]: - devices.append(HMLock(conf)) - - add_entities(devices, True) + add_entities((HMLock(conf) for conf in discovery_info[ATTR_DISCOVER_DEVICES]), True) class HMLock(HMDevice, LockEntity): diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index b3322107bfa..29d8576f060 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -86,15 +86,15 @@ async def async_setup_entry( if isinstance(device, AsyncTiltVibrationSensor): entities.append(HomematicipTiltVibrationSensor(hap, device)) if isinstance(device, AsyncWiredInput32): - for channel in range(1, 33): - entities.append( - HomematicipMultiContactInterface(hap, device, channel=channel) - ) + entities.extend( + HomematicipMultiContactInterface(hap, device, channel=channel) + for channel in range(1, 33) + ) elif isinstance(device, AsyncFullFlushContactInterface6): - for channel in range(1, 7): - entities.append( - HomematicipMultiContactInterface(hap, device, channel=channel) - ) + entities.extend( + HomematicipMultiContactInterface(hap, device, channel=channel) + for channel in range(1, 7) + ) elif isinstance( device, (AsyncContactInterface, AsyncFullFlushContactInterface) ): diff --git a/homeassistant/components/homematicip_cloud/button.py b/homeassistant/components/homematicip_cloud/button.py index 7537c15fc9b..c2707f68a89 100644 --- a/homeassistant/components/homematicip_cloud/button.py +++ b/homeassistant/components/homematicip_cloud/button.py @@ -20,13 +20,12 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP button from a config entry.""" hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id] - entities: list[HomematicipGenericEntity] = [] - for device in hap.home.devices: - if isinstance(device, AsyncWallMountedGarageDoorController): - entities.append(HomematicipGarageDoorControllerButton(hap, device)) - if entities: - async_add_entities(entities) + async_add_entities( + HomematicipGarageDoorControllerButton(hap, device) + for device in hap.home.devices + if isinstance(device, AsyncWallMountedGarageDoorController) + ) class HomematicipGarageDoorControllerButton(HomematicipGenericEntity, ButtonEntity): diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index c4304284892..b0eb2a9edfa 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -51,12 +51,12 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP climate from a config entry.""" hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id] - entities = [] - for device in hap.home.groups: - if isinstance(device, AsyncHeatingGroup): - entities.append(HomematicipHeatingGroup(hap, device)) - async_add_entities(entities) + async_add_entities( + HomematicipHeatingGroup(hap, device) + for device in hap.home.groups + if isinstance(device, AsyncHeatingGroup) + ) class HomematicipHeatingGroup(HomematicipGenericEntity, ClimateEntity): diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index 17e1b7a94f1..b0cff8b6a10 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -41,15 +41,19 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP cover from a config entry.""" hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id] - entities: list[HomematicipGenericEntity] = [] + entities: list[HomematicipGenericEntity] = [ + HomematicipCoverShutterGroup(hap, group) + for group in hap.home.groups + if isinstance(group, AsyncExtendedLinkedShutterGroup) + ] for device in hap.home.devices: if isinstance(device, AsyncBlindModule): entities.append(HomematicipBlindModule(hap, device)) elif isinstance(device, AsyncDinRailBlind4): - for channel in range(1, 5): - entities.append( - HomematicipMultiCoverSlats(hap, device, channel=channel) - ) + entities.extend( + HomematicipMultiCoverSlats(hap, device, channel=channel) + for channel in range(1, 5) + ) elif isinstance(device, AsyncFullFlushBlind): entities.append(HomematicipCoverSlats(hap, device)) elif isinstance(device, AsyncFullFlushShutter): @@ -59,10 +63,6 @@ async def async_setup_entry( ): entities.append(HomematicipGarageDoorModule(hap, device)) - for group in hap.home.groups: - if isinstance(group, AsyncExtendedLinkedShutterGroup): - entities.append(HomematicipCoverShutterGroup(hap, group)) - async_add_entities(entities) diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 6a9bd5a3c4e..17daafc5896 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -56,8 +56,10 @@ async def async_setup_entry( ) ) elif isinstance(device, (AsyncWiredDimmer3, AsyncDinRailDimmer3)): - for channel in range(1, 4): - entities.append(HomematicipMultiDimmer(hap, device, channel=channel)) + entities.extend( + HomematicipMultiDimmer(hap, device, channel=channel) + for channel in range(1, 4) + ) elif isinstance( device, (AsyncDimmer, AsyncPluggableDimmer, AsyncBrandDimmer, AsyncFullFlushDimmer), diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 1fa3007c863..9aa60d45d93 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -39,7 +39,11 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP switch from a config entry.""" hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id] - entities: list[HomematicipGenericEntity] = [] + entities: list[HomematicipGenericEntity] = [ + HomematicipGroupSwitch(hap, group) + for group in hap.home.groups + if isinstance(group, (AsyncExtendedLinkedSwitchingGroup, AsyncSwitchingGroup)) + ] for device in hap.home.devices: if isinstance(device, AsyncBrandSwitchMeasuring): # BrandSwitchMeasuring inherits PlugableSwitchMeasuring @@ -51,13 +55,17 @@ async def async_setup_entry( ): entities.append(HomematicipSwitchMeasuring(hap, device)) elif isinstance(device, AsyncWiredSwitch8): - for channel in range(1, 9): - entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) + entities.extend( + HomematicipMultiSwitch(hap, device, channel=channel) + for channel in range(1, 9) + ) elif isinstance(device, AsyncDinRailSwitch): entities.append(HomematicipMultiSwitch(hap, device, channel=1)) elif isinstance(device, AsyncDinRailSwitch4): - for channel in range(1, 5): - entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) + entities.extend( + HomematicipMultiSwitch(hap, device, channel=channel) + for channel in range(1, 5) + ) elif isinstance( device, ( @@ -68,8 +76,10 @@ async def async_setup_entry( ): entities.append(HomematicipSwitch(hap, device)) elif isinstance(device, AsyncOpenCollector8Module): - for channel in range(1, 9): - entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) + entities.extend( + HomematicipMultiSwitch(hap, device, channel=channel) + for channel in range(1, 9) + ) elif isinstance( device, ( @@ -79,12 +89,10 @@ async def async_setup_entry( AsyncMultiIOBox, ), ): - for channel in range(1, 3): - entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) - - for group in hap.home.groups: - if isinstance(group, (AsyncExtendedLinkedSwitchingGroup, AsyncSwitchingGroup)): - entities.append(HomematicipGroupSwitch(hap, group)) + entities.extend( + HomematicipMultiSwitch(hap, device, channel=channel) + for channel in range(1, 3) + ) async_add_entities(entities) diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py index 030f8be779b..31ed8d646c5 100644 --- a/homeassistant/components/honeywell/sensor.py +++ b/homeassistant/components/honeywell/sensor.py @@ -86,14 +86,13 @@ async def async_setup_entry( ) -> None: """Set up the Honeywell thermostat.""" data: HoneywellData = hass.data[DOMAIN][config_entry.entry_id] - sensors = [] - for device in data.devices.values(): - for description in SENSOR_TYPES: - if getattr(device, description.key) is not None: - sensors.append(HoneywellSensor(device, description)) - - async_add_entities(sensors) + async_add_entities( + HoneywellSensor(device, description) + for device in data.devices.values() + for description in SENSOR_TYPES + if getattr(device, description.key) is not None + ) class HoneywellSensor(SensorEntity): diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index a52aff5fd68..e75fef42ef3 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -674,17 +674,17 @@ async def async_setup_entry( items = filter(key_meta.include.search, items) if key_meta.exclude: items = [x for x in items if not key_meta.exclude.search(x)] - for item in items: - sensors.append( - HuaweiLteSensor( - router, - key, - item, - SENSOR_META[key].descriptions.get( - item, HuaweiSensorEntityDescription(key=item) - ), - ) + sensors.extend( + HuaweiLteSensor( + router, + key, + item, + SENSOR_META[key].descriptions.get( + item, HuaweiSensorEntityDescription(key=item) + ), ) + for item in items + ) async_add_entities(sensors, True) diff --git a/homeassistant/components/hue/event.py b/homeassistant/components/hue/event.py index c12468c0f36..1ba974fa167 100644 --- a/homeassistant/components/hue/event.py +++ b/homeassistant/components/hue/event.py @@ -81,12 +81,12 @@ class HueButtonEventEntity(HueBaseEntity, EventEntity): # fill the event types based on the features the switch supports hue_dev_id = self.controller.get_device(self.resource.id).id model_id = self.bridge.api.devices[hue_dev_id].product_data.product_name - event_types: list[str] = [] - for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get( - model_id, DEFAULT_BUTTON_EVENT_TYPES - ): - event_types.append(event_type.value) - self._attr_event_types = event_types + self._attr_event_types: list[str] = [ + event_type.value + for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get( + model_id, DEFAULT_BUTTON_EVENT_TYPES + ) + ] self._attr_translation_placeholders = { "button_id": self.resource.metadata.control_id } diff --git a/homeassistant/components/hue/v2/device_trigger.py b/homeassistant/components/hue/v2/device_trigger.py index eca7908e930..c35093a9f9c 100644 --- a/homeassistant/components/hue/v2/device_trigger.py +++ b/homeassistant/components/hue/v2/device_trigger.py @@ -90,39 +90,39 @@ def async_get_triggers( # Get Hue device id from device identifier hue_dev_id = get_hue_device_id(device_entry) # extract triggers from all button resources of this Hue device - triggers = [] + triggers: list[dict[str, Any]] = [] model_id = api.devices[hue_dev_id].product_data.product_name for resource in api.devices.get_sensors(hue_dev_id): # button triggers if resource.type == ResourceTypes.BUTTON: - for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get( - model_id, DEFAULT_BUTTON_EVENT_TYPES - ): - triggers.append( - { - CONF_DEVICE_ID: device_entry.id, - CONF_DOMAIN: DOMAIN, - CONF_PLATFORM: "device", - CONF_TYPE: event_type.value, - CONF_SUBTYPE: resource.metadata.control_id, - CONF_UNIQUE_ID: resource.id, - } + triggers.extend( + { + CONF_DEVICE_ID: device_entry.id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: event_type.value, + CONF_SUBTYPE: resource.metadata.control_id, + CONF_UNIQUE_ID: resource.id, + } + for event_type in DEVICE_SPECIFIC_EVENT_TYPES.get( + model_id, DEFAULT_BUTTON_EVENT_TYPES ) + ) # relative_rotary triggers elif resource.type == ResourceTypes.RELATIVE_ROTARY: - for event_type in DEFAULT_ROTARY_EVENT_TYPES: - for sub_type in DEFAULT_ROTARY_EVENT_SUBTYPES: - triggers.append( - { - CONF_DEVICE_ID: device_entry.id, - CONF_DOMAIN: DOMAIN, - CONF_PLATFORM: "device", - CONF_TYPE: event_type.value, - CONF_SUBTYPE: sub_type.value, - CONF_UNIQUE_ID: resource.id, - } - ) + triggers.extend( + { + CONF_DEVICE_ID: device_entry.id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: event_type.value, + CONF_SUBTYPE: sub_type.value, + CONF_UNIQUE_ID: resource.id, + } + for event_type in DEFAULT_ROTARY_EVENT_TYPES + for sub_type in DEFAULT_ROTARY_EVENT_SUBTYPES + ) return triggers diff --git a/homeassistant/components/hunterdouglas_powerview/button.py b/homeassistant/components/hunterdouglas_powerview/button.py index 1ce30e8df81..f7c90f3420b 100644 --- a/homeassistant/components/hunterdouglas_powerview/button.py +++ b/homeassistant/components/hunterdouglas_powerview/button.py @@ -85,19 +85,18 @@ async def async_setup_entry( entities: list[ButtonEntity] = [] for shade in pv_entry.shade_data.values(): room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "") - for description in BUTTONS_SHADE: - if description.create_entity_fn(shade): - entities.append( - PowerviewShadeButton( - pv_entry.coordinator, - pv_entry.device_info, - room_name, - shade, - shade.name, - description, - ) - ) - + entities.extend( + PowerviewShadeButton( + pv_entry.coordinator, + pv_entry.device_info, + room_name, + shade, + shade.name, + description, + ) + for description in BUTTONS_SHADE + if description.create_entity_fn(shade) + ) async_add_entities(entities) diff --git a/homeassistant/components/hunterdouglas_powerview/number.py b/homeassistant/components/hunterdouglas_powerview/number.py index 6b18f663c71..b37331c08df 100644 --- a/homeassistant/components/hunterdouglas_powerview/number.py +++ b/homeassistant/components/hunterdouglas_powerview/number.py @@ -66,19 +66,18 @@ async def async_setup_entry( entities: list[PowerViewNumber] = [] for shade in pv_entry.shade_data.values(): room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "") - for description in NUMBERS: - if description.create_entity_fn(shade): - entities.append( - PowerViewNumber( - pv_entry.coordinator, - pv_entry.device_info, - room_name, - shade, - shade.name, - description, - ) - ) - + entities.extend( + PowerViewNumber( + pv_entry.coordinator, + pv_entry.device_info, + room_name, + shade, + shade.name, + description, + ) + for description in NUMBERS + if description.create_entity_fn(shade) + ) async_add_entities(entities) diff --git a/homeassistant/components/hunterdouglas_powerview/select.py b/homeassistant/components/hunterdouglas_powerview/select.py index c902c2b1a58..66207f6da7c 100644 --- a/homeassistant/components/hunterdouglas_powerview/select.py +++ b/homeassistant/components/hunterdouglas_powerview/select.py @@ -68,19 +68,18 @@ async def async_setup_entry( if not shade.has_battery_info(): continue room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "") - for description in DROPDOWNS: - if description.create_entity_fn(shade): - entities.append( - PowerViewSelect( - pv_entry.coordinator, - pv_entry.device_info, - room_name, - shade, - shade.name, - description, - ) - ) - + entities.extend( + PowerViewSelect( + pv_entry.coordinator, + pv_entry.device_info, + room_name, + shade, + shade.name, + description, + ) + for description in DROPDOWNS + if description.create_entity_fn(shade) + ) async_add_entities(entities) diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py index 02b4ae7c557..c1371a1e848 100644 --- a/homeassistant/components/hunterdouglas_powerview/sensor.py +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -88,19 +88,18 @@ async def async_setup_entry( entities: list[PowerViewSensor] = [] for shade in pv_entry.shade_data.values(): room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "") - for description in SENSORS: - if description.create_entity_fn(shade): - entities.append( - PowerViewSensor( - pv_entry.coordinator, - pv_entry.device_info, - room_name, - shade, - shade.name, - description, - ) - ) - + entities.extend( + PowerViewSensor( + pv_entry.coordinator, + pv_entry.device_info, + room_name, + shade, + shade.name, + description, + ) + for description in SENSORS + if description.create_entity_fn(shade) + ) async_add_entities(entities) diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index 7740334e7fb..e75cf56ac75 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -75,11 +75,11 @@ async def async_setup_entry( entities.append( HydrawiseBinarySensor(coordinator, BINARY_SENSOR_STATUS, controller) ) - for zone in controller.zones: - for description in BINARY_SENSOR_TYPES: - entities.append( - HydrawiseBinarySensor(coordinator, description, controller, zone) - ) + entities.extend( + HydrawiseBinarySensor(coordinator, description, controller, zone) + for zone in controller.zones + for description in BINARY_SENSOR_TYPES + ) async_add_entities(entities) diff --git a/homeassistant/components/hyperion/switch.py b/homeassistant/components/hyperion/switch.py index dbe0bbb5c70..94cbf2aba29 100644 --- a/homeassistant/components/hyperion/switch.py +++ b/homeassistant/components/hyperion/switch.py @@ -100,18 +100,16 @@ async def async_setup_entry( def instance_add(instance_num: int, instance_name: str) -> None: """Add entities for a new Hyperion instance.""" assert server_id - switches = [] - for component in COMPONENT_SWITCHES: - switches.append( - HyperionComponentSwitch( - server_id, - instance_num, - instance_name, - component, - entry_data[CONF_INSTANCE_CLIENTS][instance_num], - ), + async_add_entities( + HyperionComponentSwitch( + server_id, + instance_num, + instance_name, + component, + entry_data[CONF_INSTANCE_CLIENTS][instance_num], ) - async_add_entities(switches) + for component in COMPONENT_SWITCHES + ) @callback def instance_remove(instance_num: int) -> None: diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 8e120728a8b..13574bb2bb2 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -241,9 +241,7 @@ def test_get_significant_states_only(hass_history) -> None: return hass.states.get(entity_id) start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) + points = [start + timedelta(minutes=i) for i in range(1, 4)] states = [] with freeze_time(start) as freezer: diff --git a/tests/components/history/test_init_db_schema_30.py b/tests/components/history/test_init_db_schema_30.py index b0c07cf25af..0bbd913ce2b 100644 --- a/tests/components/history/test_init_db_schema_30.py +++ b/tests/components/history/test_init_db_schema_30.py @@ -257,9 +257,7 @@ def test_get_significant_states_only(legacy_hass_history) -> None: return hass.states.get(entity_id) start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) + points = [start + timedelta(minutes=i) for i in range(1, 4)] states = [] with freeze_time(start) as freezer: diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index 0437958edae..40f565ec88b 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -116,18 +116,18 @@ async def test_enumerate_remote( }, ] - for button in ("button1", "button2", "button3", "button4"): - for subtype in ("single_press", "double_press", "long_press"): - expected.append( - { - "device_id": device.id, - "domain": "homekit_controller", - "platform": "device", - "type": button, - "subtype": subtype, - "metadata": {}, - } - ) + expected.extend( + { + "device_id": device.id, + "domain": "homekit_controller", + "platform": "device", + "type": button, + "subtype": subtype, + "metadata": {}, + } + for button in ("button1", "button2", "button3", "button4") + for subtype in ("single_press", "double_press", "long_press") + ) triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, device.id @@ -167,17 +167,17 @@ async def test_enumerate_button( }, ] - for subtype in ("single_press", "double_press", "long_press"): - expected.append( - { - "device_id": device.id, - "domain": "homekit_controller", - "platform": "device", - "type": "button1", - "subtype": subtype, - "metadata": {}, - } - ) + expected.extend( + { + "device_id": device.id, + "domain": "homekit_controller", + "platform": "device", + "type": "button1", + "subtype": subtype, + "metadata": {}, + } + for subtype in ("single_press", "double_press", "long_press") + ) triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, device.id @@ -217,17 +217,17 @@ async def test_enumerate_doorbell( }, ] - for subtype in ("single_press", "double_press", "long_press"): - expected.append( - { - "device_id": device.id, - "domain": "homekit_controller", - "platform": "device", - "type": "doorbell", - "subtype": subtype, - "metadata": {}, - } - ) + expected.extend( + { + "device_id": device.id, + "domain": "homekit_controller", + "platform": "device", + "type": "doorbell", + "subtype": subtype, + "metadata": {}, + } + for subtype in ("single_press", "double_press", "long_press") + ) triggers = await async_get_device_automations( hass, DeviceAutomationType.TRIGGER, device.id diff --git a/tests/components/homematicip_cloud/helper.py b/tests/components/homematicip_cloud/helper.py index 5bc11b77d32..4632b9107af 100644 --- a/tests/components/homematicip_cloud/helper.py +++ b/tests/components/homematicip_cloud/helper.py @@ -171,15 +171,9 @@ class HomeTemplate(Home): def _generate_mocks(self): """Generate mocks for groups and devices.""" - mock_devices = [] - for device in self.devices: - mock_devices.append(_get_mock(device)) - self.devices = mock_devices + self.devices = [_get_mock(device) for device in self.devices] - mock_groups = [] - for group in self.groups: - mock_groups.append(_get_mock(group)) - self.groups = mock_groups + self.groups = [_get_mock(group) for group in self.groups] def download_configuration(self): """Return the initial json config.""" diff --git a/tests/components/humidifier/test_init.py b/tests/components/humidifier/test_init.py index 33ea6d14a19..b90e7084dd1 100644 --- a/tests/components/humidifier/test_init.py +++ b/tests/components/humidifier/test_init.py @@ -49,10 +49,7 @@ async def test_sync_turn_off(hass: HomeAssistant) -> None: def _create_tuples(enum: Enum, constant_prefix: str) -> list[tuple[Enum, str]]: - result = [] - for enum in enum: - result.append((enum, constant_prefix)) - return result + return [(enum_field, constant_prefix) for enum_field in enum] @pytest.mark.parametrize( From dc7eaee91794f3c2775a735dc59dcbadb73d6dae Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 13 Mar 2024 18:26:19 +0200 Subject: [PATCH 0895/1691] CI: Move pytest-github-actions-annotate-failures to test deps, upgrade (#104604) Move pytest-github-actions-annotate-failures to test deps, upgrade Co-authored-by: Erik Montnemery --- .github/workflows/ci.yaml | 24 ------------------------ requirements_test.txt | 1 + 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a5d4927efa..b012ecb6fd8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -719,14 +719,6 @@ jobs: - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Install Pytest Annotation plugin - run: | - . venv/bin/activate - # Ideally this should be part of our dependencies - # However this plugin is fairly new and doesn't run correctly - # on a non-GitHub environment. - pip install "$(cat requirements_test.txt | grep uv)" - uv pip install pytest-github-actions-annotate-failures==0.1.3 - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" @@ -874,14 +866,6 @@ jobs: - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Install Pytest Annotation plugin - run: | - . venv/bin/activate - # Ideally this should be part of our dependencies - # However this plugin is fairly new and doesn't run correctly - # on a non-GitHub environment. - pip install "$(cat requirements_test.txt | grep uv)" - uv pip install pytest-github-actions-annotate-failures==0.1.3 - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" @@ -1004,14 +988,6 @@ jobs: - name: Register Python problem matcher run: | echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Install Pytest Annotation plugin - run: | - . venv/bin/activate - # Ideally this should be part of our dependencies - # However this plugin is fairly new and doesn't run correctly - # on a non-GitHub environment. - pip install "$(cat requirements_test.txt | grep uv)" - uv pip install pytest-github-actions-annotate-failures==0.1.3 - name: Register pytest slow test problem matcher run: | echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" diff --git a/requirements_test.txt b/requirements_test.txt index 03a61613600..e189526adaf 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -21,6 +21,7 @@ pytest-asyncio==0.23.5 pytest-aiohttp==1.0.5 pytest-cov==4.1.0 pytest-freezer==0.4.8 +pytest-github-actions-annotate-failures==0.2.0 pytest-socket==0.7.0 pytest-test-groups==1.0.3 pytest-sugar==1.0.0 From 9f19e7339d6f0caccfba1f3dc71642323d6777c3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 17:30:30 +0100 Subject: [PATCH 0896/1691] Improve lists in integrations [P-Q] (#113236) --- homeassistant/components/picnic/todo.py | 21 ++++++++----------- .../components/plex/media_browser.py | 20 ++++++++---------- homeassistant/components/plex/sensor.py | 8 ++++--- homeassistant/components/plugwise/number.py | 15 ++++++------- homeassistant/components/plugwise/select.py | 15 ++++++------- .../components/progettihwsw/binary_sensor.py | 17 +++++++-------- .../components/progettihwsw/switch.py | 17 +++++++-------- .../components/pvpc_hourly_pricing/sensor.py | 6 ++++-- .../components/qnap_qsw/binary_sensor.py | 10 ++++----- homeassistant/components/qnap_qsw/sensor.py | 10 ++++----- 10 files changed, 63 insertions(+), 76 deletions(-) diff --git a/homeassistant/components/picnic/todo.py b/homeassistant/components/picnic/todo.py index c645a5ebf83..7fa2bbccd3e 100644 --- a/homeassistant/components/picnic/todo.py +++ b/homeassistant/components/picnic/todo.py @@ -66,18 +66,15 @@ class PicnicCart(TodoListEntity, CoordinatorEntity[PicnicUpdateCoordinator]): _LOGGER.debug(self.coordinator.data["cart_data"]["items"]) - items = [] - for item in self.coordinator.data["cart_data"]["items"]: - for article in item["items"]: - items.append( - TodoItem( - summary=f"{article['name']} ({article['unit_quantity']})", - uid=f"{item['id']}-{article['id']}", - status=TodoItemStatus.NEEDS_ACTION, # We set 'NEEDS_ACTION' so they count as state - ) - ) - - return items + return [ + TodoItem( + summary=f"{article['name']} ({article['unit_quantity']})", + uid=f"{item['id']}-{article['id']}", + status=TodoItemStatus.NEEDS_ACTION, # We set 'NEEDS_ACTION' so they count as state + ) + for item in self.coordinator.data["cart_data"]["items"] + for article in item["items"] + ] async def async_create_todo_item(self, item: TodoItem) -> None: """Add item to shopping cart.""" diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index e5bbb9284ff..9184edeb3bd 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -293,18 +293,16 @@ def generate_plex_uri(server_id, media_id, params=None): def root_payload(hass, is_internal, platform=None): """Return root payload for Plex.""" - children = [] - - for server_id in get_plex_data(hass)[SERVERS]: - children.append( - browse_media( - hass, - is_internal, - "server", - generate_plex_uri(server_id, ""), - platform=platform, - ) + children = [ + browse_media( + hass, + is_internal, + "server", + generate_plex_uri(server_id, ""), + platform=platform, ) + for server_id in get_plex_data(hass)[SERVERS] + ] if len(children) == 1: return children[0] diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 74fab3b1714..30ff8e72f60 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -57,12 +57,14 @@ async def async_setup_entry( """Set up Plex sensor from a config entry.""" server_id = config_entry.data[CONF_SERVER_IDENTIFIER] plexserver = get_plex_server(hass, server_id) - sensors = [PlexSensor(hass, plexserver)] + sensors: list[SensorEntity] = [PlexSensor(hass, plexserver)] def create_library_sensors(): """Create Plex library sensors with sync calls.""" - for library in plexserver.library.sections(): - sensors.append(PlexLibrarySectionSensor(hass, plexserver, library)) + sensors.extend( + PlexLibrarySectionSensor(hass, plexserver, library) + for library in plexserver.library.sections() + ) await hass.async_add_executor_job(create_library_sensors) async_add_entities(sensors) diff --git a/homeassistant/components/plugwise/number.py b/homeassistant/components/plugwise/number.py index 95d09bd845e..2bae113a73e 100644 --- a/homeassistant/components/plugwise/number.py +++ b/homeassistant/components/plugwise/number.py @@ -76,15 +76,12 @@ async def async_setup_entry( config_entry.entry_id ] - entities: list[PlugwiseNumberEntity] = [] - for device_id, device in coordinator.data.devices.items(): - for description in NUMBER_TYPES: - if description.key in device: - entities.append( - PlugwiseNumberEntity(coordinator, device_id, description) - ) - - async_add_entities(entities) + async_add_entities( + PlugwiseNumberEntity(coordinator, device_id, description) + for device_id, device in coordinator.data.devices.items() + for description in NUMBER_TYPES + if description.key in device + ) class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity): diff --git a/homeassistant/components/plugwise/select.py b/homeassistant/components/plugwise/select.py index 21b0794a1e3..10718a818ff 100644 --- a/homeassistant/components/plugwise/select.py +++ b/homeassistant/components/plugwise/select.py @@ -68,15 +68,12 @@ async def async_setup_entry( config_entry.entry_id ] - entities: list[PlugwiseSelectEntity] = [] - for device_id, device in coordinator.data.devices.items(): - for description in SELECT_TYPES: - if description.options_key in device: - entities.append( - PlugwiseSelectEntity(coordinator, device_id, description) - ) - - async_add_entities(entities) + async_add_entities( + PlugwiseSelectEntity(coordinator, device_id, description) + for device_id, device in coordinator.data.devices.items() + for description in SELECT_TYPES + if description.options_key in device + ) class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity): diff --git a/homeassistant/components/progettihwsw/binary_sensor.py b/homeassistant/components/progettihwsw/binary_sensor.py index fd4f478c045..a89b8b3c3f1 100644 --- a/homeassistant/components/progettihwsw/binary_sensor.py +++ b/homeassistant/components/progettihwsw/binary_sensor.py @@ -29,7 +29,6 @@ async def async_setup_entry( """Set up the binary sensors from a config entry.""" board_api = hass.data[DOMAIN][config_entry.entry_id] input_count = config_entry.data["input_count"] - binary_sensors = [] async def async_update_data(): """Fetch data from API endpoint of board.""" @@ -45,16 +44,14 @@ async def async_setup_entry( ) await coordinator.async_refresh() - for i in range(1, int(input_count) + 1): - binary_sensors.append( - ProgettihwswBinarySensor( - coordinator, - f"Input #{i}", - setup_input(board_api, i), - ) + async_add_entities( + ProgettihwswBinarySensor( + coordinator, + f"Input #{i}", + setup_input(board_api, i), ) - - async_add_entities(binary_sensors) + for i in range(1, int(input_count) + 1) + ) class ProgettihwswBinarySensor(CoordinatorEntity, BinarySensorEntity): diff --git a/homeassistant/components/progettihwsw/switch.py b/homeassistant/components/progettihwsw/switch.py index accabcfb32f..88faa35e0a4 100644 --- a/homeassistant/components/progettihwsw/switch.py +++ b/homeassistant/components/progettihwsw/switch.py @@ -30,7 +30,6 @@ async def async_setup_entry( """Set up the switches from a config entry.""" board_api = hass.data[DOMAIN][config_entry.entry_id] relay_count = config_entry.data["relay_count"] - switches = [] async def async_update_data(): """Fetch data from API endpoint of board.""" @@ -46,16 +45,14 @@ async def async_setup_entry( ) await coordinator.async_refresh() - for i in range(1, int(relay_count) + 1): - switches.append( - ProgettihwswSwitch( - coordinator, - f"Relay #{i}", - setup_switch(board_api, i, config_entry.data[f"relay_{str(i)}"]), - ) + async_add_entities( + ProgettihwswSwitch( + coordinator, + f"Relay #{i}", + setup_switch(board_api, i, config_entry.data[f"relay_{str(i)}"]), ) - - async_add_entities(switches) + for i in range(1, int(relay_count) + 1) + ) class ProgettihwswSwitch(CoordinatorEntity, SwitchEntity): diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index 1b97abcddae..246a8b65892 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -154,8 +154,10 @@ async def async_setup_entry( coordinator: ElecPricesDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] sensors = [ElecPriceSensor(coordinator, SENSOR_TYPES[0], entry.unique_id)] if coordinator.api.using_private_api: - for sensor_desc in SENSOR_TYPES[1:]: - sensors.append(ElecPriceSensor(coordinator, sensor_desc, entry.unique_id)) + sensors.extend( + ElecPriceSensor(coordinator, sensor_desc, entry.unique_id) + for sensor_desc in SENSOR_TYPES[1:] + ) async_add_entities(sensors) diff --git a/homeassistant/components/qnap_qsw/binary_sensor.py b/homeassistant/components/qnap_qsw/binary_sensor.py index 907e3d927d3..a9c025b86ce 100644 --- a/homeassistant/components/qnap_qsw/binary_sensor.py +++ b/homeassistant/components/qnap_qsw/binary_sensor.py @@ -83,14 +83,14 @@ async def async_setup_entry( """Add QNAP QSW binary sensors from a config_entry.""" coordinator: QswDataCoordinator = hass.data[DOMAIN][entry.entry_id][QSW_COORD_DATA] - entities: list[QswBinarySensor] = [] - - for description in BINARY_SENSOR_TYPES: + entities: list[QswBinarySensor] = [ + QswBinarySensor(coordinator, description, entry) + for description in BINARY_SENSOR_TYPES if ( description.key in coordinator.data and description.subkey in coordinator.data[description.key] - ): - entities.append(QswBinarySensor(coordinator, description, entry)) + ) + ] for description in LACP_PORT_BINARY_SENSOR_TYPES: if ( diff --git a/homeassistant/components/qnap_qsw/sensor.py b/homeassistant/components/qnap_qsw/sensor.py index f9299c778cb..b64c0aaad82 100644 --- a/homeassistant/components/qnap_qsw/sensor.py +++ b/homeassistant/components/qnap_qsw/sensor.py @@ -288,14 +288,14 @@ async def async_setup_entry( """Add QNAP QSW sensors from a config_entry.""" coordinator: QswDataCoordinator = hass.data[DOMAIN][entry.entry_id][QSW_COORD_DATA] - entities: list[QswSensor] = [] - - for description in SENSOR_TYPES: + entities: list[QswSensor] = [ + QswSensor(coordinator, description, entry) + for description in SENSOR_TYPES if ( description.key in coordinator.data and description.subkey in coordinator.data[description.key] - ): - entities.append(QswSensor(coordinator, description, entry)) + ) + ] for description in LACP_PORT_SENSOR_TYPES: if ( From 8bc3286343b5237dc983faa6f38fcd1f23fe2bc1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 06:33:50 -1000 Subject: [PATCH 0897/1691] Run coordinator shutdown eagerly (#113174) --- homeassistant/helpers/update_coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/update_coordinator.py b/homeassistant/helpers/update_coordinator.py index 31d9909ad16..287e69f7085 100644 --- a/homeassistant/helpers/update_coordinator.py +++ b/homeassistant/helpers/update_coordinator.py @@ -136,7 +136,7 @@ class DataUpdateCoordinator(BaseDataUpdateCoordinatorProtocol, Generic[_DataT]): await self.async_shutdown() self._unsub_shutdown = self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, _on_hass_stop + EVENT_HOMEASSISTANT_STOP, _on_hass_stop, run_immediately=True ) @callback From 3d9a9c3847325c68e0557ded047f20e224e0a502 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 13 Mar 2024 17:38:37 +0100 Subject: [PATCH 0898/1691] Use `single_config_entry` in Accuweather manifest (#111548) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/accuweather/config_flow.py | 5 ----- homeassistant/components/accuweather/manifest.json | 3 ++- homeassistant/components/accuweather/strings.json | 3 --- homeassistant/generated/integrations.json | 3 ++- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/accuweather/config_flow.py b/homeassistant/components/accuweather/config_flow.py index 666171af8df..af7560d963a 100644 --- a/homeassistant/components/accuweather/config_flow.py +++ b/homeassistant/components/accuweather/config_flow.py @@ -41,11 +41,6 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" - # Under the terms of use of the API, one user can use one free API key. Due to - # the small number of requests allowed, we only allow one integration instance. - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - errors = {} if user_input is not None: diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index 2974c36607b..fa651d98efd 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -8,5 +8,6 @@ "iot_class": "cloud_polling", "loggers": ["accuweather"], "quality_scale": "platinum", - "requirements": ["accuweather==2.1.1"] + "requirements": ["accuweather==2.1.1"], + "single_config_entry": true } diff --git a/homeassistant/components/accuweather/strings.json b/homeassistant/components/accuweather/strings.json index 24024ba722f..718f2da6a75 100644 --- a/homeassistant/components/accuweather/strings.json +++ b/homeassistant/components/accuweather/strings.json @@ -17,9 +17,6 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]", "requests_exceeded": "The allowed number of requests to Accuweather API has been exceeded. You have to wait or change API Key." - }, - "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } }, "entity": { diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index dfa1c5af230..cb30532cdec 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -15,7 +15,8 @@ "name": "AccuWeather", "integration_type": "service", "config_flow": true, - "iot_class": "cloud_polling" + "iot_class": "cloud_polling", + "single_config_entry": true }, "acer_projector": { "name": "Acer Projector", From b47fb68214bdbf89b6f081a6808836ebc929f2bc Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 13 Mar 2024 17:50:29 +0100 Subject: [PATCH 0899/1691] Bump `brother` library to version `4.0.2` (#113235) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/brother/__init__.py | 2 +- homeassistant/components/brother/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/brother/__init__.py b/homeassistant/components/brother/__init__.py index d52c7587742..0bd49ed5d7a 100644 --- a/homeassistant/components/brother/__init__.py +++ b/homeassistant/components/brother/__init__.py @@ -34,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: brother = await Brother.create( host, printer_type=printer_type, snmp_engine=snmp_engine ) - except (ConnectionError, SnmpError) as error: + except (ConnectionError, SnmpError, TimeoutError) as error: raise ConfigEntryNotReady from error coordinator = BrotherDataUpdateCoordinator(hass, brother) diff --git a/homeassistant/components/brother/manifest.json b/homeassistant/components/brother/manifest.json index 26317b39ab5..9ca18a95a1e 100644 --- a/homeassistant/components/brother/manifest.json +++ b/homeassistant/components/brother/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_polling", "loggers": ["brother", "pyasn1", "pysmi", "pysnmp"], "quality_scale": "platinum", - "requirements": ["brother==4.0.0"], + "requirements": ["brother==4.0.2"], "zeroconf": [ { "type": "_printer._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 8d51e3f0ab8..d4926f8a691 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -606,7 +606,7 @@ bring-api==0.5.6 broadlink==0.18.3 # homeassistant.components.brother -brother==4.0.0 +brother==4.0.2 # homeassistant.components.brottsplatskartan brottsplatskartan==1.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0d39a14550d..fc383161299 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -517,7 +517,7 @@ bring-api==0.5.6 broadlink==0.18.3 # homeassistant.components.brother -brother==4.0.0 +brother==4.0.2 # homeassistant.components.brottsplatskartan brottsplatskartan==1.0.5 From cdba14acd46c448a298c35c8f1c38aa18581f16b Mon Sep 17 00:00:00 2001 From: FieldofClay <7278759+FieldofClay@users.noreply.github.com> Date: Thu, 14 Mar 2024 03:51:40 +1100 Subject: [PATCH 0900/1691] Ignore AussieBroadband services that don't support usage information (#110253) --- homeassistant/components/aussie_broadband/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index d25a70221cd..13fa919eb9a 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -32,9 +32,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async_get_clientsession(hass), ) + # Ignore services that don't support usage data + ignore_types = FETCH_TYPES + ["Hardware"] + try: await client.login() - services = await client.get_services(drop_types=FETCH_TYPES) + services = await client.get_services(drop_types=ignore_types) except AuthenticationException as exc: raise ConfigEntryAuthFailed() from exc except ClientError as exc: From 96cebdf096230576aecbcc62100b73781b874c66 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 17:54:46 +0100 Subject: [PATCH 0901/1691] Improve lists in integrations [T-U] (#113243) --- homeassistant/components/tami4/sensor.py | 17 +++++----- homeassistant/components/ted5000/sensor.py | 11 +++---- .../components/tellduslive/__init__.py | 5 +-- .../components/tensorflow/image_processing.py | 22 ++++++------- .../components/thermoworks_smoke/sensor.py | 19 ++++++------ .../components/thread/websocket_api.py | 31 +++++++++---------- .../components/tibber/diagnostics.py | 14 +++------ homeassistant/components/tibber/sensor.py | 6 ++-- .../totalconnect/alarm_control_panel.py | 18 +++++------ homeassistant/components/touchline/climate.py | 11 ++++--- homeassistant/components/tplink/switch.py | 6 ++-- homeassistant/components/trace/__init__.py | 8 ++--- .../components/tradfri/diagnostics.py | 7 +++-- .../components/tuya/alarm_control_panel.py | 10 +++--- homeassistant/components/tuya/button.py | 10 +++--- homeassistant/components/tuya/cover.py | 10 +++--- homeassistant/components/tuya/light.py | 10 +++--- homeassistant/components/tuya/number.py | 10 +++--- homeassistant/components/tuya/select.py | 10 +++--- homeassistant/components/tuya/sensor.py | 10 +++--- homeassistant/components/tuya/switch.py | 10 +++--- homeassistant/components/twinkly/light.py | 5 +-- .../components/ukraine_alarm/config_flow.py | 16 ++-------- .../components/unifi/hub/entity_loader.py | 14 ++++----- .../components/unifiprotect/light.py | 11 +++---- .../components/unifiprotect/select.py | 6 ++-- tests/components/unifiprotect/test_migrate.py | 18 ++++++----- .../components/universal/test_media_player.py | 3 +- 28 files changed, 153 insertions(+), 175 deletions(-) diff --git a/homeassistant/components/tami4/sensor.py b/homeassistant/components/tami4/sensor.py index b93342d9144..3772ef0bccb 100644 --- a/homeassistant/components/tami4/sensor.py +++ b/homeassistant/components/tami4/sensor.py @@ -69,17 +69,14 @@ async def async_setup_entry( api: Tami4EdgeAPI = data[API] coordinator: Tami4EdgeWaterQualityCoordinator = data[COORDINATOR] - entities = [] - for entity_description in ENTITY_DESCRIPTIONS: - entities.append( - Tami4EdgeSensorEntity( - coordinator=coordinator, - api=api, - entity_description=entity_description, - ) + async_add_entities( + Tami4EdgeSensorEntity( + coordinator=coordinator, + api=api, + entity_description=entity_description, ) - - async_add_entities(entities) + for entity_description in ENTITY_DESCRIPTIONS + ) class Tami4EdgeSensorEntity( diff --git a/homeassistant/components/ted5000/sensor.py b/homeassistant/components/ted5000/sensor.py index 9dafe945188..99d8991a02e 100644 --- a/homeassistant/components/ted5000/sensor.py +++ b/homeassistant/components/ted5000/sensor.py @@ -78,12 +78,11 @@ def setup_platform( # Get MUT information to create the sensors. gateway.update() - entities = [] - for mtu in gateway.data: - for description in SENSORS: - entities.append(Ted5000Sensor(gateway, name, mtu, description)) - - add_entities(entities) + add_entities( + Ted5000Sensor(gateway, name, mtu, description) + for mtu in gateway.data + for description in SENSORS + ) class Ted5000Sensor(SensorEntity): diff --git a/homeassistant/components/tellduslive/__init__.py b/homeassistant/components/tellduslive/__init__.py index fcabea070b1..92e61edec56 100644 --- a/homeassistant/components/tellduslive/__init__.py +++ b/homeassistant/components/tellduslive/__init__.py @@ -186,8 +186,9 @@ class TelldusLiveClient: self._hass.data[CONFIG_ENTRY_IS_SETUP].add(component) device_ids = [] if device.is_sensor: - for item in device.items: - device_ids.append((device.device_id, item.name, item.scale)) + device_ids.extend( + (device.device_id, item.name, item.scale) for item in device.items + ) else: device_ids.append(device_id) for _id in device_ids: diff --git a/homeassistant/components/tensorflow/image_processing.py b/homeassistant/components/tensorflow/image_processing.py index 3025ffa27c7..632db28ca3a 100644 --- a/homeassistant/components/tensorflow/image_processing.py +++ b/homeassistant/components/tensorflow/image_processing.py @@ -196,20 +196,16 @@ def setup_platform( labels, use_display_name=True ) - entities = [] - - for camera in config[CONF_SOURCE]: - entities.append( - TensorFlowImageProcessor( - hass, - camera[CONF_ENTITY_ID], - camera.get(CONF_NAME), - category_index, - config, - ) + add_entities( + TensorFlowImageProcessor( + hass, + camera[CONF_ENTITY_ID], + camera.get(CONF_NAME), + category_index, + config, ) - - add_entities(entities) + for camera in config[CONF_SOURCE] + ) class TensorFlowImageProcessor(ImageProcessingEntity): diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index 33681d5e574..b2d6613f2e5 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -86,22 +86,21 @@ def setup_platform( try: mgr = thermoworks_smoke.initialize_app(email, password, True, excluded) - - # list of sensor devices - dev = [] - - # get list of registered devices - for serial in mgr.serials(): - for variable in monitored_variables: - dev.append(ThermoworksSmokeSensor(variable, serial, mgr)) - - add_entities(dev, True) except HTTPError as error: msg = f"{error.strerror}" if "EMAIL_NOT_FOUND" in msg or "INVALID_PASSWORD" in msg: _LOGGER.error("Invalid email and password combination") else: _LOGGER.error(msg) + else: + add_entities( + ( + ThermoworksSmokeSensor(variable, serial, mgr) + for serial in mgr.serials() + for variable in monitored_variables + ), + True, + ) class ThermoworksSmokeSensor(SensorEntity): diff --git a/homeassistant/components/thread/websocket_api.py b/homeassistant/components/thread/websocket_api.py index 2ac817c2b75..687c4067caf 100644 --- a/homeassistant/components/thread/websocket_api.py +++ b/homeassistant/components/thread/websocket_api.py @@ -166,23 +166,22 @@ async def ws_list_datasets( """Get a list of thread datasets.""" store = await dataset_store.async_get_store(hass) - result = [] preferred_dataset = store.preferred_dataset - for dataset in store.datasets.values(): - result.append( - { - "channel": dataset.channel, - "created": dataset.created, - "dataset_id": dataset.id, - "extended_pan_id": dataset.extended_pan_id, - "network_name": dataset.network_name, - "pan_id": dataset.pan_id, - "preferred": dataset.id == preferred_dataset, - "preferred_border_agent_id": dataset.preferred_border_agent_id, - "preferred_extended_address": dataset.preferred_extended_address, - "source": dataset.source, - } - ) + result = [ + { + "channel": dataset.channel, + "created": dataset.created, + "dataset_id": dataset.id, + "extended_pan_id": dataset.extended_pan_id, + "network_name": dataset.network_name, + "pan_id": dataset.pan_id, + "preferred": dataset.id == preferred_dataset, + "preferred_border_agent_id": dataset.preferred_border_agent_id, + "preferred_extended_address": dataset.preferred_extended_address, + "source": dataset.source, + } + for dataset in store.datasets.values() + ] connection.send_result(msg["id"], {"datasets": result}) diff --git a/homeassistant/components/tibber/diagnostics.py b/homeassistant/components/tibber/diagnostics.py index f0fc6fec58b..2306aac23e1 100644 --- a/homeassistant/components/tibber/diagnostics.py +++ b/homeassistant/components/tibber/diagnostics.py @@ -18,11 +18,8 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" tibber_connection: tibber.Tibber = hass.data[DOMAIN] - diagnostics_data = {} - - homes = [] - for home in tibber_connection.get_homes(only_active=False): - homes.append( + return { + "homes": [ { "last_data_timestamp": home.last_data_timestamp, "has_active_subscription": home.has_active_subscription, @@ -30,7 +27,6 @@ async def async_get_config_entry_diagnostics( "last_cons_data_timestamp": home.last_cons_data_timestamp, "country": home.country, } - ) - diagnostics_data["homes"] = homes - - return diagnostics_data + for home in tibber_connection.get_homes(only_active=False) + ] + } diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 075b2518bd2..a57c5d3e8dd 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -295,8 +295,10 @@ async def async_setup_entry( entities.append(TibberSensorElPrice(home)) if coordinator is None: coordinator = TibberDataCoordinator(hass, tibber_connection) - for entity_description in SENSORS: - entities.append(TibberDataSensor(home, coordinator, entity_description)) + entities.extend( + TibberDataSensor(home, coordinator, entity_description) + for entity_description in SENSORS + ) if home.has_real_time_consumption: await home.rt_subscribe( diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index 50d889eeba1..3f2c51989f9 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -36,21 +36,21 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up TotalConnect alarm panels based on a config entry.""" - alarms = [] + alarms: list[TotalConnectAlarm] = [] coordinator: TotalConnectDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] for location_id, location in coordinator.client.locations.items(): location_name = location.location_name - for partition_id in location.partitions: - alarms.append( - TotalConnectAlarm( - coordinator=coordinator, - name=location_name, - location_id=location_id, - partition_id=partition_id, - ) + alarms.extend( + TotalConnectAlarm( + coordinator=coordinator, + name=location_name, + location_id=location_id, + partition_id=partition_id, ) + for partition_id in location.partitions + ) async_add_entities(alarms) diff --git a/homeassistant/components/touchline/climate.py b/homeassistant/components/touchline/climate.py index 04251648cc1..83c7839772a 100644 --- a/homeassistant/components/touchline/climate.py +++ b/homeassistant/components/touchline/climate.py @@ -55,10 +55,13 @@ def setup_platform( host = config[CONF_HOST] py_touchline = PyTouchline() number_of_devices = int(py_touchline.get_number_of_devices(host)) - devices = [] - for device_id in range(0, number_of_devices): - devices.append(Touchline(PyTouchline(device_id))) - add_entities(devices, True) + add_entities( + ( + Touchline(PyTouchline(device_id)) + for device_id in range(0, number_of_devices) + ), + True, + ) class Touchline(ClimateEntity): diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 0d5aa9d2224..da3dda9c041 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -37,8 +37,10 @@ async def async_setup_entry( if device.is_strip: # Historically we only add the children if the device is a strip _LOGGER.debug("Initializing strip with %s sockets", len(device.children)) - for child in device.children: - entities.append(SmartPlugSwitchChild(device, parent_coordinator, child)) + entities.extend( + SmartPlugSwitchChild(device, parent_coordinator, child) + for child in device.children + ) elif device.is_plug: entities.append(SmartPlugSwitch(device, parent_coordinator)) diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index 9551c5c7276..d0ec4555376 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -111,13 +111,9 @@ async def async_list_contexts( def _get_debug_traces(hass: HomeAssistant, key: str) -> list[dict[str, Any]]: """Return a serializable list of debug traces for a script or automation.""" - traces: list[dict[str, Any]] = [] - if traces_for_key := _get_data(hass).get(key): - for trace in traces_for_key.values(): - traces.append(trace.as_short_dict()) - - return traces + return [trace.as_short_dict() for trace in traces_for_key.values()] + return [] async def async_list_traces( diff --git a/homeassistant/components/tradfri/diagnostics.py b/homeassistant/components/tradfri/diagnostics.py index 0237382ec39..4d89fd0081f 100644 --- a/homeassistant/components/tradfri/diagnostics.py +++ b/homeassistant/components/tradfri/diagnostics.py @@ -26,9 +26,10 @@ async def async_get_config_entry_diagnostics( ), ) - device_data: list = [] - for coordinator in coordinator_data[COORDINATOR_LIST]: - device_data.append(coordinator.device.device_info.model_number) + device_data: list = [ + coordinator.device.device_info.model_number + for coordinator in coordinator_data[COORDINATOR_LIST] + ] return { "gateway_version": device.sw_version, diff --git a/homeassistant/components/tuya/alarm_control_panel.py b/homeassistant/components/tuya/alarm_control_panel.py index 74392b20148..59075cf00cd 100644 --- a/homeassistant/components/tuya/alarm_control_panel.py +++ b/homeassistant/components/tuya/alarm_control_panel.py @@ -71,11 +71,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := ALARM.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaAlarmEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaAlarmEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) async_discover_device([*hass_data.manager.device_map]) diff --git a/homeassistant/components/tuya/button.py b/homeassistant/components/tuya/button.py index c0363eebe72..a170ddb09e9 100644 --- a/homeassistant/components/tuya/button.py +++ b/homeassistant/components/tuya/button.py @@ -71,11 +71,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := BUTTONS.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaButtonEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaButtonEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 21191ece553..7dc54888ac4 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -155,14 +155,14 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := COVERS.get(device.category): - for description in descriptions: + entities.extend( + TuyaCoverEntity(device, hass_data.manager, description) + for description in descriptions if ( description.key in device.function or description.key in device.status_range - ): - entities.append( - TuyaCoverEntity(device, hass_data.manager, description) - ) + ) + ) async_add_entities(entities) diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 0f769191710..144e3746285 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -417,11 +417,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := LIGHTS.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaLightEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaLightEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index f8d05d0841e..2be7deef89f 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -294,11 +294,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := NUMBERS.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaNumberEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaNumberEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/tuya/select.py b/homeassistant/components/tuya/select.py index 472f11e12b0..6e128bfdcc4 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -332,11 +332,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := SELECTS.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaSelectEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaSelectEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/tuya/sensor.py b/homeassistant/components/tuya/sensor.py index 7669cfa0f32..df11840931d 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -1087,11 +1087,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := SENSORS.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaSensorEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaSensorEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 9b90d391d98..36debaeadde 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -672,11 +672,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := SWITCHES.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaSwitchEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaSwitchEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/twinkly/light.py b/homeassistant/components/twinkly/light.py index 6bfc205b553..2749c9a7764 100644 --- a/homeassistant/components/twinkly/light.py +++ b/homeassistant/components/twinkly/light.py @@ -130,10 +130,7 @@ class TwinklyLight(LightEntity): @property def effect_list(self) -> list[str]: """Return the list of saved effects.""" - effect_list = [] - for movie in self._movies: - effect_list.append(f"{movie['id']} {movie['name']}") - return effect_list + return [f"{movie['id']} {movie['name']}" for movie in self._movies] async def async_added_to_hass(self) -> None: """Device is added to hass.""" diff --git a/homeassistant/components/ukraine_alarm/config_flow.py b/homeassistant/components/ukraine_alarm/config_flow.py index 9b593b77639..bafe6d1fe11 100644 --- a/homeassistant/components/ukraine_alarm/config_flow.py +++ b/homeassistant/components/ukraine_alarm/config_flow.py @@ -133,17 +133,5 @@ def _find(regions, region_id): def _make_regions_object(regions): - regions_list = [] - for region in regions: - regions_list.append( - { - "id": region["regionId"], - "name": region["regionName"], - } - ) - regions_list = sorted(regions_list, key=lambda region: region["name"].lower()) - regions_object = {} - for region in regions_list: - regions_object[region["id"]] = region["name"] - - return regions_object + regions = sorted(regions, key=lambda region: region["regionName"].lower()) + return {region["regionId"]: region["regionName"] for region in regions} diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index af63419d82a..a7a1cb970ec 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -83,14 +83,14 @@ class UnifiEntityLoader: Provide inactive clients to device tracker and switch platform. """ config = self.hub.config - macs: list[str] = [] entity_registry = er.async_get(self.hub.hass) - for entry in async_entries_for_config_entry( - entity_registry, config.entry.entry_id - ): - if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id: - macs.append(entry.unique_id.split("-", 1)[1]) - + macs: list[str] = [ + entry.unique_id.split("-", 1)[1] + for entry in async_entries_for_config_entry( + entity_registry, config.entry.entry_id + ) + if entry.domain == Platform.DEVICE_TRACKER and "-" in entry.unique_id + ] api = self.hub.api for mac in config.option_supported_clients + config.option_block_clients + macs: if mac not in api.clients and mac in api.clients_all: diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index 5a1e5dfd0c4..3ce236b3e23 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -45,12 +45,11 @@ async def async_setup_entry( async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) ) - entities = [] - for device in data.get_by_types({ModelType.LIGHT}): - if device.can_write(data.api.bootstrap.auth_user): - entities.append(ProtectLight(data, device)) - - async_add_entities(entities) + async_add_entities( + ProtectLight(data, device) + for device in data.get_by_types({ModelType.LIGHT}) + if device.can_write(data.api.bootstrap.auth_user) + ) def unifi_brightness_to_hass(value: int) -> int: diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 7d1ca436c78..13bf18eb2a7 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -130,8 +130,10 @@ def _get_doorbell_options(api: ProtectApiClient) -> list[dict[str, Any]]: def _get_paired_camera_options(api: ProtectApiClient) -> list[dict[str, Any]]: options = [{"id": TYPE_EMPTY_VALUE, "name": "Not Paired"}] - for camera in api.bootstrap.cameras.values(): - options.append({"id": camera.id, "name": camera.display_name or camera.type}) + options.extend( + {"id": camera.id, "name": camera.display_name or camera.type} + for camera in api.bootstrap.cameras.values() + ) return options diff --git a/tests/components/unifiprotect/test_migrate.py b/tests/components/unifiprotect/test_migrate.py index 475f6170550..88d66c819bc 100644 --- a/tests/components/unifiprotect/test_migrate.py +++ b/tests/components/unifiprotect/test_migrate.py @@ -52,10 +52,11 @@ async def test_migrate_reboot_button( assert ufp.api.update.called assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - buttons = [] - for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id): - if entity.domain == Platform.BUTTON.value: - buttons.append(entity) + buttons = [ + entity + for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id) + if entity.domain == Platform.BUTTON.value + ] assert len(buttons) == 4 assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device") is None @@ -133,10 +134,11 @@ async def test_migrate_reboot_button_no_device( assert ufp.api.update.called assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - buttons = [] - for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id): - if entity.domain == Platform.BUTTON.value: - buttons.append(entity) + buttons = [ + entity + for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id) + if entity.domain == Platform.BUTTON.value + ] assert len(buttons) == 3 entity = registry.async_get(f"{Platform.BUTTON}.unifiprotect_{light2_id.lower()}") diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index c48316e6f22..9fa55ae6725 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -365,8 +365,7 @@ async def test_platform_setup(hass: HomeAssistant) -> None: def add_entities(new_entities): """Add devices to list.""" - for dev in new_entities: - entities.append(dev) + entities.extend(new_entities) setup_ok = True try: From 7c4747bb02e07a126728dbb8fbf5005257a50c9c Mon Sep 17 00:00:00 2001 From: Em Date: Wed, 13 Mar 2024 18:23:52 +0100 Subject: [PATCH 0902/1691] Parameterize some tests in generic_thermostat (#105643) * test(generic_thermostat): parameterize some tests * refactor: improvements following review --- .../generic_thermostat/test_climate.py | 293 +++++++----------- 1 file changed, 110 insertions(+), 183 deletions(-) diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index c7b6c03a2c5..fdcad219d93 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -755,8 +755,9 @@ async def test_no_state_change_when_operation_mode_off_2( assert len(calls) == 0 -@pytest.fixture -async def setup_comp_4(hass): +async def _setup_thermostat_with_min_cycle_duration( + hass: HomeAssistant, ac_mode: bool, initial_hvac_mode: HVACMode +): """Initialize components.""" hass.config.temperature_unit = UnitOfTemperature.CELSIUS assert await async_setup_component( @@ -770,102 +771,141 @@ async def setup_comp_4(hass): "hot_tolerance": 0.3, "heater": ENT_SWITCH, "target_sensor": ENT_SENSOR, - "ac_mode": True, + "ac_mode": ac_mode, "min_cycle_duration": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVACMode.COOL, + "initial_hvac_mode": initial_hvac_mode, } }, ) await hass.async_block_till_done() -async def test_temp_change_ac_trigger_on_not_long_enough( - hass: HomeAssistant, setup_comp_4 +@pytest.mark.parametrize( + ( + "ac_mode", + "initial_hvac_mode", + "initial_switch_state", + "sensor_temperature", + "target_temperature", + ), + [ + (True, HVACMode.COOL, False, 30, 25), + (True, HVACMode.COOL, True, 25, 30), + (False, HVACMode.HEAT, True, 25, 30), + (False, HVACMode.HEAT, False, 30, 25), + ], +) +async def test_heating_cooling_switch_does_not_toggle_when_within_min_cycle_duration( + hass: HomeAssistant, + ac_mode: bool, + initial_hvac_mode: HVACMode, + initial_switch_state: bool, + sensor_temperature: int, + target_temperature: int, ) -> None: - """Test if temperature change turn ac on.""" - calls = _setup_switch(hass, False) - await common.async_set_temperature(hass, 25) - _setup_sensor(hass, 30) + """Test if heating/cooling does not toggle when inside minimum cycle.""" + # Given + await _setup_thermostat_with_min_cycle_duration(hass, ac_mode, initial_hvac_mode) + calls = _setup_switch(hass, initial_switch_state) + _setup_sensor(hass, sensor_temperature) + + # When + await common.async_set_temperature(hass, target_temperature) await hass.async_block_till_done() + + # Then assert len(calls) == 0 -async def test_temp_change_ac_trigger_on_long_enough( - hass: HomeAssistant, setup_comp_4 +@pytest.mark.parametrize( + ( + "ac_mode", + "initial_hvac_mode", + "initial_switch_state", + "sensor_temperature", + "target_temperature", + "expected_triggered_service_call", + ), + [ + (True, HVACMode.COOL, False, 30, 25, SERVICE_TURN_ON), + (True, HVACMode.COOL, True, 25, 30, SERVICE_TURN_OFF), + (False, HVACMode.HEAT, False, 25, 30, SERVICE_TURN_ON), + (False, HVACMode.HEAT, True, 30, 25, SERVICE_TURN_OFF), + ], +) +async def test_heating_cooling_switch_toggles_when_outside_min_cycle_duration( + hass: HomeAssistant, + ac_mode: bool, + initial_hvac_mode: HVACMode, + initial_switch_state: bool, + sensor_temperature: int, + target_temperature: int, + expected_triggered_service_call: str, ) -> None: - """Test if temperature change turn ac on.""" + """Test if heating/cooling toggles when outside minimum cycle.""" + # Given + await _setup_thermostat_with_min_cycle_duration(hass, ac_mode, initial_hvac_mode) fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) with freeze_time(fake_changed): - calls = _setup_switch(hass, False) - await common.async_set_temperature(hass, 25) - _setup_sensor(hass, 30) + calls = _setup_switch(hass, initial_switch_state) + _setup_sensor(hass, sensor_temperature) + + # When + await common.async_set_temperature(hass, target_temperature) await hass.async_block_till_done() + + # Then assert len(calls) == 1 call = calls[0] assert call.domain == HASS_DOMAIN - assert call.service == SERVICE_TURN_ON + assert call.service == expected_triggered_service_call assert call.data["entity_id"] == ENT_SWITCH -async def test_temp_change_ac_trigger_off_not_long_enough( - hass: HomeAssistant, setup_comp_4 +@pytest.mark.parametrize( + ( + "ac_mode", + "initial_hvac_mode", + "initial_switch_state", + "sensor_temperature", + "target_temperature", + "changed_hvac_mode", + "expected_triggered_service_call", + ), + [ + (True, HVACMode.COOL, False, 30, 25, HVACMode.HEAT, SERVICE_TURN_ON), + (True, HVACMode.COOL, True, 25, 30, HVACMode.OFF, SERVICE_TURN_OFF), + (False, HVACMode.HEAT, False, 25, 30, HVACMode.HEAT, SERVICE_TURN_ON), + (False, HVACMode.HEAT, True, 30, 25, HVACMode.OFF, SERVICE_TURN_OFF), + ], +) +async def test_hvac_mode_change_toggles_heating_cooling_switch_even_when_within_min_cycle_duration( + hass: HomeAssistant, + ac_mode: bool, + initial_hvac_mode: HVACMode, + initial_switch_state: bool, + sensor_temperature: int, + target_temperature: int, + changed_hvac_mode: HVACMode, + expected_triggered_service_call: str, ) -> None: - """Test if temperature change turn ac on.""" - calls = _setup_switch(hass, True) - await common.async_set_temperature(hass, 30) - _setup_sensor(hass, 25) + """Test if mode change toggles heating/cooling despite minimum cycle.""" + # Given + await _setup_thermostat_with_min_cycle_duration(hass, ac_mode, initial_hvac_mode) + calls = _setup_switch(hass, initial_switch_state) + _setup_sensor(hass, sensor_temperature) + + # When + await common.async_set_temperature(hass, target_temperature) await hass.async_block_till_done() + + # Then assert len(calls) == 0 - - -async def test_temp_change_ac_trigger_off_long_enough( - hass: HomeAssistant, setup_comp_4 -) -> None: - """Test if temperature change turn ac on.""" - fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) - with freeze_time(fake_changed): - calls = _setup_switch(hass, True) - await common.async_set_temperature(hass, 30) - _setup_sensor(hass, 25) - await hass.async_block_till_done() - assert len(calls) == 1 - call = calls[0] - assert call.domain == HASS_DOMAIN - assert call.service == SERVICE_TURN_OFF - assert call.data["entity_id"] == ENT_SWITCH - - -async def test_mode_change_ac_trigger_off_not_long_enough( - hass: HomeAssistant, setup_comp_4 -) -> None: - """Test if mode change turns ac off despite minimum cycle.""" - calls = _setup_switch(hass, True) - await common.async_set_temperature(hass, 30) - _setup_sensor(hass, 25) - await hass.async_block_till_done() - assert len(calls) == 0 - await common.async_set_hvac_mode(hass, HVACMode.OFF) + await common.async_set_hvac_mode(hass, changed_hvac_mode) assert len(calls) == 1 call = calls[0] assert call.domain == "homeassistant" - assert call.service == SERVICE_TURN_OFF - assert call.data["entity_id"] == ENT_SWITCH - - -async def test_mode_change_ac_trigger_on_not_long_enough( - hass: HomeAssistant, setup_comp_4 -) -> None: - """Test if mode change turns ac on despite minimum cycle.""" - calls = _setup_switch(hass, False) - await common.async_set_temperature(hass, 25) - _setup_sensor(hass, 30) - await hass.async_block_till_done() - assert len(calls) == 0 - await common.async_set_hvac_mode(hass, HVACMode.HEAT) - assert len(calls) == 1 - call = calls[0] - assert call.domain == "homeassistant" - assert call.service == SERVICE_TURN_ON + assert call.service == expected_triggered_service_call assert call.data["entity_id"] == ENT_SWITCH @@ -983,119 +1023,6 @@ async def test_mode_change_ac_trigger_on_not_long_enough_2( assert call.data["entity_id"] == ENT_SWITCH -@pytest.fixture -async def setup_comp_6(hass): - """Initialize components.""" - hass.config.temperature_unit = UnitOfTemperature.CELSIUS - assert await async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "min_cycle_duration": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVACMode.HEAT, - } - }, - ) - await hass.async_block_till_done() - - -async def test_temp_change_heater_trigger_off_not_long_enough( - hass: HomeAssistant, setup_comp_6 -) -> None: - """Test if temp change doesn't turn heater off because of time.""" - calls = _setup_switch(hass, True) - await common.async_set_temperature(hass, 25) - _setup_sensor(hass, 30) - await hass.async_block_till_done() - assert len(calls) == 0 - - -async def test_temp_change_heater_trigger_on_not_long_enough( - hass: HomeAssistant, setup_comp_6 -) -> None: - """Test if temp change doesn't turn heater on because of time.""" - calls = _setup_switch(hass, False) - await common.async_set_temperature(hass, 30) - _setup_sensor(hass, 25) - await hass.async_block_till_done() - assert len(calls) == 0 - - -async def test_temp_change_heater_trigger_on_long_enough( - hass: HomeAssistant, setup_comp_6 -) -> None: - """Test if temperature change turn heater on after min cycle.""" - fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) - with freeze_time(fake_changed): - calls = _setup_switch(hass, False) - await common.async_set_temperature(hass, 30) - _setup_sensor(hass, 25) - await hass.async_block_till_done() - assert len(calls) == 1 - call = calls[0] - assert call.domain == HASS_DOMAIN - assert call.service == SERVICE_TURN_ON - assert call.data["entity_id"] == ENT_SWITCH - - -async def test_temp_change_heater_trigger_off_long_enough( - hass: HomeAssistant, setup_comp_6 -) -> None: - """Test if temperature change turn heater off after min cycle.""" - fake_changed = datetime.datetime(1970, 11, 11, 11, 11, 11, tzinfo=dt_util.UTC) - with freeze_time(fake_changed): - calls = _setup_switch(hass, True) - await common.async_set_temperature(hass, 25) - _setup_sensor(hass, 30) - await hass.async_block_till_done() - assert len(calls) == 1 - call = calls[0] - assert call.domain == HASS_DOMAIN - assert call.service == SERVICE_TURN_OFF - assert call.data["entity_id"] == ENT_SWITCH - - -async def test_mode_change_heater_trigger_off_not_long_enough( - hass: HomeAssistant, setup_comp_6 -) -> None: - """Test if mode change turns heater off despite minimum cycle.""" - calls = _setup_switch(hass, True) - await common.async_set_temperature(hass, 25) - _setup_sensor(hass, 30) - await hass.async_block_till_done() - assert len(calls) == 0 - await common.async_set_hvac_mode(hass, HVACMode.OFF) - assert len(calls) == 1 - call = calls[0] - assert call.domain == "homeassistant" - assert call.service == SERVICE_TURN_OFF - assert call.data["entity_id"] == ENT_SWITCH - - -async def test_mode_change_heater_trigger_on_not_long_enough( - hass: HomeAssistant, setup_comp_6 -) -> None: - """Test if mode change turns heater on despite minimum cycle.""" - calls = _setup_switch(hass, False) - await common.async_set_temperature(hass, 30) - _setup_sensor(hass, 25) - await hass.async_block_till_done() - assert len(calls) == 0 - await common.async_set_hvac_mode(hass, HVACMode.HEAT) - assert len(calls) == 1 - call = calls[0] - assert call.domain == "homeassistant" - assert call.service == SERVICE_TURN_ON - assert call.data["entity_id"] == ENT_SWITCH - - @pytest.fixture async def setup_comp_7(hass): """Initialize components.""" From b34302e51bf0a6fac5ac00d0dbaa8d87a317f006 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 19:06:16 +0100 Subject: [PATCH 0903/1691] Add icon translations to Roku (#112214) * Add icon translations to Roku * Add icon translations to Roku * Fix * Fix --- .../components/roku/binary_sensor.py | 4 -- homeassistant/components/roku/icons.json | 37 +++++++++++++++++++ homeassistant/components/roku/select.py | 2 - homeassistant/components/roku/sensor.py | 2 - tests/components/roku/test_binary_sensor.py | 15 +------- tests/components/roku/test_select.py | 4 +- tests/components/roku/test_sensor.py | 5 --- 7 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 homeassistant/components/roku/icons.json diff --git a/homeassistant/components/roku/binary_sensor.py b/homeassistant/components/roku/binary_sensor.py index 17d44a0a160..0f5f29f63f6 100644 --- a/homeassistant/components/roku/binary_sensor.py +++ b/homeassistant/components/roku/binary_sensor.py @@ -31,27 +31,23 @@ BINARY_SENSORS: tuple[RokuBinarySensorEntityDescription, ...] = ( RokuBinarySensorEntityDescription( key="headphones_connected", translation_key="headphones_connected", - icon="mdi:headphones", value_fn=lambda device: device.info.headphones_connected, ), RokuBinarySensorEntityDescription( key="supports_airplay", translation_key="supports_airplay", - icon="mdi:cast-variant", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.info.supports_airplay, ), RokuBinarySensorEntityDescription( key="supports_ethernet", translation_key="supports_ethernet", - icon="mdi:ethernet", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.info.ethernet_support, ), RokuBinarySensorEntityDescription( key="supports_find_remote", translation_key="supports_find_remote", - icon="mdi:remote", entity_category=EntityCategory.DIAGNOSTIC, value_fn=lambda device: device.info.supports_find_remote, ), diff --git a/homeassistant/components/roku/icons.json b/homeassistant/components/roku/icons.json new file mode 100644 index 00000000000..02e5d1e5698 --- /dev/null +++ b/homeassistant/components/roku/icons.json @@ -0,0 +1,37 @@ +{ + "entity": { + "binary_sensor": { + "headphones_connected": { + "default": "mdi:headphones" + }, + "supports_airplay": { + "default": "mdi:cast-variant" + }, + "supports_ethernet": { + "default": "mdi:ethernet" + }, + "supports_find_remote": { + "default": "mdi:remote" + } + }, + "select": { + "application": { + "default": "mdi:application" + }, + "channel": { + "default": "mdi:television" + } + }, + "sensor": { + "active_app": { + "default": "mdi:application" + }, + "active_app_id": { + "default": "mdi:application-cog" + } + } + }, + "services": { + "search": "mdi:magnify" + } +} diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py index 4c900579739..2c9e64ed044 100644 --- a/homeassistant/components/roku/select.py +++ b/homeassistant/components/roku/select.py @@ -90,7 +90,6 @@ ENTITIES: tuple[RokuSelectEntityDescription, ...] = ( RokuSelectEntityDescription( key="application", translation_key="application", - icon="mdi:application", set_fn=_launch_application, value_fn=_get_application_name, options_fn=_get_applications, @@ -101,7 +100,6 @@ ENTITIES: tuple[RokuSelectEntityDescription, ...] = ( CHANNEL_ENTITY = RokuSelectEntityDescription( key="channel", translation_key="channel", - icon="mdi:television", set_fn=_tune_channel, value_fn=_get_channel_name, options_fn=_get_channels, diff --git a/homeassistant/components/roku/sensor.py b/homeassistant/components/roku/sensor.py index 1b8b429ef16..ed134cc4c2a 100644 --- a/homeassistant/components/roku/sensor.py +++ b/homeassistant/components/roku/sensor.py @@ -30,14 +30,12 @@ SENSORS: tuple[RokuSensorEntityDescription, ...] = ( key="active_app", translation_key="active_app", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:application", value_fn=lambda device: device.app.name if device.app else None, ), RokuSensorEntityDescription( key="active_app_id", translation_key="active_app_id", entity_category=EntityCategory.DIAGNOSTIC, - icon="mdi:application-cog", value_fn=lambda device: device.app.app_id if device.app else None, ), ) diff --git a/tests/components/roku/test_binary_sensor.py b/tests/components/roku/test_binary_sensor.py index 89e559bbabd..076e16ebad0 100644 --- a/tests/components/roku/test_binary_sensor.py +++ b/tests/components/roku/test_binary_sensor.py @@ -7,12 +7,7 @@ from rokuecp import Device as RokuDevice from homeassistant.components.binary_sensor import STATE_OFF, STATE_ON from homeassistant.components.roku.const import DOMAIN -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_FRIENDLY_NAME, - ATTR_ICON, - EntityCategory, -) +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -36,7 +31,6 @@ async def test_roku_binary_sensors( assert entry.entity_category is None assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Roku 3 Headphones connected" - assert state.attributes.get(ATTR_ICON) == "mdi:headphones" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.my_roku_3_supports_airplay") @@ -47,7 +41,6 @@ async def test_roku_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Roku 3 Supports AirPlay" - assert state.attributes.get(ATTR_ICON) == "mdi:cast-variant" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.my_roku_3_supports_ethernet") @@ -58,7 +51,6 @@ async def test_roku_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_ON assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Roku 3 Supports ethernet" - assert state.attributes.get(ATTR_ICON) == "mdi:ethernet" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.my_roku_3_supports_find_remote") @@ -69,7 +61,6 @@ async def test_roku_binary_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_OFF assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Roku 3 Supports find remote" - assert state.attributes.get(ATTR_ICON) == "mdi:remote" assert ATTR_DEVICE_CLASS not in state.attributes assert entry.device_id @@ -113,7 +104,6 @@ async def test_rokutv_binary_sensors( state.attributes.get(ATTR_FRIENDLY_NAME) == '58" Onn Roku TV Headphones connected' ) - assert state.attributes.get(ATTR_ICON) == "mdi:headphones" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.58_onn_roku_tv_supports_airplay") @@ -126,7 +116,6 @@ async def test_rokutv_binary_sensors( assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == '58" Onn Roku TV Supports AirPlay' ) - assert state.attributes.get(ATTR_ICON) == "mdi:cast-variant" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.58_onn_roku_tv_supports_ethernet") @@ -139,7 +128,6 @@ async def test_rokutv_binary_sensors( assert ( state.attributes.get(ATTR_FRIENDLY_NAME) == '58" Onn Roku TV Supports ethernet' ) - assert state.attributes.get(ATTR_ICON) == "mdi:ethernet" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("binary_sensor.58_onn_roku_tv_supports_find_remote") @@ -155,7 +143,6 @@ async def test_rokutv_binary_sensors( state.attributes.get(ATTR_FRIENDLY_NAME) == '58" Onn Roku TV Supports find remote' ) - assert state.attributes.get(ATTR_ICON) == "mdi:remote" assert ATTR_DEVICE_CLASS not in state.attributes assert entry.device_id diff --git a/tests/components/roku/test_select.py b/tests/components/roku/test_select.py index edef070ee21..a226ae7c191 100644 --- a/tests/components/roku/test_select.py +++ b/tests/components/roku/test_select.py @@ -18,7 +18,7 @@ from homeassistant.components.select import ( ATTR_OPTIONS, DOMAIN as SELECT_DOMAIN, ) -from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON, SERVICE_SELECT_OPTION +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_SELECT_OPTION from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er @@ -50,7 +50,6 @@ async def test_application_state( state = hass.states.get("select.my_roku_3_application") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:application" assert state.attributes.get(ATTR_OPTIONS) == [ "Home", "Amazon Video on Demand", @@ -175,7 +174,6 @@ async def test_channel_state( state = hass.states.get("select.58_onn_roku_tv_channel") assert state - assert state.attributes.get(ATTR_ICON) == "mdi:television" assert state.attributes.get(ATTR_OPTIONS) == [ "99.1", "QVC (1.3)", diff --git a/tests/components/roku/test_sensor.py b/tests/components/roku/test_sensor.py index e0aeb22126c..2d431e7f5dc 100644 --- a/tests/components/roku/test_sensor.py +++ b/tests/components/roku/test_sensor.py @@ -8,7 +8,6 @@ from homeassistant.components.roku.const import DOMAIN from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, - ATTR_ICON, STATE_UNKNOWN, EntityCategory, ) @@ -36,7 +35,6 @@ async def test_roku_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == "Roku" assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Roku 3 Active app" - assert state.attributes.get(ATTR_ICON) == "mdi:application" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("sensor.my_roku_3_active_app_id") @@ -47,7 +45,6 @@ async def test_roku_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == STATE_UNKNOWN assert state.attributes.get(ATTR_FRIENDLY_NAME) == "My Roku 3 Active app ID" - assert state.attributes.get(ATTR_ICON) == "mdi:application-cog" assert ATTR_DEVICE_CLASS not in state.attributes assert entry.device_id @@ -85,7 +82,6 @@ async def test_rokutv_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == "Antenna TV" assert state.attributes.get(ATTR_FRIENDLY_NAME) == '58" Onn Roku TV Active app' - assert state.attributes.get(ATTR_ICON) == "mdi:application" assert ATTR_DEVICE_CLASS not in state.attributes state = hass.states.get("sensor.58_onn_roku_tv_active_app_id") @@ -96,7 +92,6 @@ async def test_rokutv_sensors( assert entry.entity_category == EntityCategory.DIAGNOSTIC assert state.state == "tvinput.dtv" assert state.attributes.get(ATTR_FRIENDLY_NAME) == '58" Onn Roku TV Active app ID' - assert state.attributes.get(ATTR_ICON) == "mdi:application-cog" assert ATTR_DEVICE_CLASS not in state.attributes assert entry.device_id From 41215aa95410c25d84b6cfec4a0da93be3ca45b1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 08:29:21 -1000 Subject: [PATCH 0904/1691] Remove remaining async_add_job calls in core (#113217) --- homeassistant/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 3d2df054036..ae02961c6a4 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -525,13 +525,15 @@ class HomeAssistant: raise ValueError("Don't call add_job with None") if asyncio.iscoroutine(target): self.loop.call_soon_threadsafe( - functools.partial(self.async_add_job, target, eager_start=True) + functools.partial(self.async_create_task, target, eager_start=True) ) return if TYPE_CHECKING: target = cast(Callable[..., Any], target) self.loop.call_soon_threadsafe( - functools.partial(self.async_add_job, target, *args, eager_start=True) + functools.partial( + self.async_add_hass_job, HassJob(target), *args, eager_start=True + ) ) @overload From 49fc59548a18588d4112af3c2dd3b0bfa6470267 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 19:38:04 +0100 Subject: [PATCH 0905/1691] Improve lists in integrations [V-W] (#113252) --- homeassistant/components/vasttrafik/sensor.py | 12 ++++----- homeassistant/components/velbus/light.py | 9 +++---- homeassistant/components/velbus/switch.py | 5 +--- homeassistant/components/velux/cover.py | 10 +++---- homeassistant/components/venstar/sensor.py | 12 ++++----- homeassistant/components/vera/__init__.py | 6 +---- homeassistant/components/vera/common.py | 4 +-- homeassistant/components/vesync/sensor.py | 15 ++++++----- .../components/vicare/diagnostics.py | 15 ++++++----- .../components/volvooncall/binary_sensor.py | 26 ++++++++----------- .../components/volvooncall/device_tracker.py | 26 ++++++++----------- homeassistant/components/volvooncall/lock.py | 26 ++++++++----------- .../components/volvooncall/sensor.py | 26 ++++++++----------- .../components/volvooncall/switch.py | 26 ++++++++----------- .../components/vulcan/config_flow.py | 23 +++++++--------- .../components/waterfurnace/sensor.py | 5 +--- homeassistant/components/wled/number.py | 5 ++-- .../components/workday/config_flow.py | 4 +-- tests/components/venstar/util.py | 15 +++++------ tests/components/vesync/common.py | 9 ++++--- tests/components/vultr/test_switch.py | 3 +-- .../components/websocket_api/test_commands.py | 4 +-- tests/components/wsdot/test_sensor.py | 3 +-- 23 files changed, 126 insertions(+), 163 deletions(-) diff --git a/homeassistant/components/vasttrafik/sensor.py b/homeassistant/components/vasttrafik/sensor.py index a065d6e1f6d..611f571336c 100644 --- a/homeassistant/components/vasttrafik/sensor.py +++ b/homeassistant/components/vasttrafik/sensor.py @@ -65,10 +65,8 @@ def setup_platform( ) -> None: """Set up the departure sensor.""" planner = vasttrafik.JournyPlanner(config.get(CONF_KEY), config.get(CONF_SECRET)) - sensors = [] - - for departure in config[CONF_DEPARTURES]: - sensors.append( + add_entities( + ( VasttrafikDepartureSensor( planner, departure.get(CONF_NAME), @@ -77,8 +75,10 @@ def setup_platform( departure.get(CONF_LINES), departure.get(CONF_DELAY), ) - ) - add_entities(sensors, True) + for departure in config[CONF_DEPARTURES] + ), + True, + ) class VasttrafikDepartureSensor(SensorEntity): diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index dd740e7e850..7145576be6a 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -38,11 +38,10 @@ async def async_setup_entry( """Set up Velbus switch based on config_entry.""" await hass.data[DOMAIN][entry.entry_id]["tsk"] cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - entities: list[Entity] = [] - for channel in cntrl.get_all("light"): - entities.append(VelbusLight(channel)) - for channel in cntrl.get_all("led"): - entities.append(VelbusButtonLight(channel)) + entities: list[Entity] = [ + VelbusLight(channel) for channel in cntrl.get_all("light") + ] + entities.extend(VelbusButtonLight(channel) for channel in cntrl.get_all("led")) async_add_entities(entities) diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index ebb281753bf..1e6014b8d90 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -21,10 +21,7 @@ async def async_setup_entry( """Set up Velbus switch based on config_entry.""" await hass.data[DOMAIN][entry.entry_id]["tsk"] cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - entities = [] - for channel in cntrl.get_all("switch"): - entities.append(VelbusSwitch(channel)) - async_add_entities(entities) + async_add_entities(VelbusSwitch(channel) for channel in cntrl.get_all("switch")) class VelbusSwitch(VelbusEntity, SwitchEntity): diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 6f5f453911f..c8688e4d186 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -27,12 +27,12 @@ async def async_setup_entry( hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up cover(s) for Velux platform.""" - entities = [] module = hass.data[DOMAIN][config.entry_id] - for node in module.pyvlx.nodes: - if isinstance(node, OpeningDevice): - entities.append(VeluxCover(node)) - async_add_entities(entities) + async_add_entities( + VeluxCover(node) + for node in module.pyvlx.nodes + if isinstance(node, OpeningDevice) + ) class VeluxCover(VeluxEntity, CoverEntity): diff --git a/homeassistant/components/venstar/sensor.py b/homeassistant/components/venstar/sensor.py index c06816ad0af..24b4b2f8b16 100644 --- a/homeassistant/components/venstar/sensor.py +++ b/homeassistant/components/venstar/sensor.py @@ -96,13 +96,11 @@ async def async_setup_entry( ) runtimes = coordinator.runtimes[-1] - for sensor_name in runtimes: - if sensor_name in RUNTIME_DEVICES: - entities.append( - VenstarSensor( - coordinator, config_entry, RUNTIME_ENTITY, sensor_name - ) - ) + entities.extend( + VenstarSensor(coordinator, config_entry, RUNTIME_ENTITY, sensor_name) + for sensor_name in runtimes + if sensor_name in RUNTIME_DEVICES + ) for description in INFO_ENTITIES: try: diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 3111e878820..acbb89f4367 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -129,14 +129,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if device_type is not None: vera_devices[device_type].append(device) - vera_scenes = [] - for scene in all_scenes: - vera_scenes.append(scene) - controller_data = ControllerData( controller=controller, devices=vera_devices, - scenes=vera_scenes, + scenes=all_scenes, config_entry=entry, ) diff --git a/homeassistant/components/vera/common.py b/homeassistant/components/vera/common.py index 4309a0d43f3..76adeeab1d2 100644 --- a/homeassistant/components/vera/common.py +++ b/homeassistant/components/vera/common.py @@ -27,9 +27,7 @@ class ControllerData(NamedTuple): def get_configured_platforms(controller_data: ControllerData) -> set[Platform]: """Get configured platforms for a controller.""" - platforms: list[Platform] = [] - for platform in controller_data.devices: - platforms.append(platform) + platforms: list[Platform] = list(controller_data.devices) if controller_data.scenes: platforms.append(Platform.SCENE) diff --git a/homeassistant/components/vesync/sensor.py b/homeassistant/components/vesync/sensor.py index d594ec5cb82..81f42f4c2ee 100644 --- a/homeassistant/components/vesync/sensor.py +++ b/homeassistant/components/vesync/sensor.py @@ -194,12 +194,15 @@ async def async_setup_entry( @callback def _setup_entities(devices, async_add_entities): """Check if device is online and add entity.""" - entities = [] - for dev in devices: - for description in SENSORS: - if description.exists_fn(dev): - entities.append(VeSyncSensorEntity(dev, description)) - async_add_entities(entities, update_before_add=True) + async_add_entities( + ( + VeSyncSensorEntity(dev, description) + for dev in devices + for description in SENSORS + if description.exists_fn(dev) + ), + update_before_add=True, + ) class VeSyncSensorEntity(VeSyncBaseEntity, SensorEntity): diff --git a/homeassistant/components/vicare/diagnostics.py b/homeassistant/components/vicare/diagnostics.py index b8cec966199..9182e96509f 100644 --- a/homeassistant/components/vicare/diagnostics.py +++ b/homeassistant/components/vicare/diagnostics.py @@ -19,12 +19,15 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - data = [] - for device in hass.data[DOMAIN][entry.entry_id][DEVICE_LIST]: - data.append( - json.loads(await hass.async_add_executor_job(device.config.dump_secure)) - ) + + def dump_devices() -> list[dict[str, Any]]: + """Dump devices.""" + return [ + json.loads(device.config.dump_secure()) + for device in hass.data[DOMAIN][entry.entry_id][DEVICE_LIST] + ] + return { "entry": async_redact_data(entry.as_dict(), TO_REDACT), - "data": data, + "data": await hass.async_add_executor_job(dump_devices), } diff --git a/homeassistant/components/volvooncall/binary_sensor.py b/homeassistant/components/volvooncall/binary_sensor.py index 390779407fa..604dc2313bf 100644 --- a/homeassistant/components/volvooncall/binary_sensor.py +++ b/homeassistant/components/volvooncall/binary_sensor.py @@ -32,21 +32,17 @@ async def async_setup_entry( @callback def async_discover_device(instruments: list[Instrument]) -> None: """Discover and add a discovered Volvo On Call binary sensor.""" - entities: list[VolvoSensor] = [] - - for instrument in instruments: - if instrument.component == "binary_sensor": - entities.append( - VolvoSensor( - coordinator, - instrument.vehicle.vin, - instrument.component, - instrument.attr, - instrument.slug_attr, - ) - ) - - async_add_entities(entities) + async_add_entities( + VolvoSensor( + coordinator, + instrument.vehicle.vin, + instrument.component, + instrument.attr, + instrument.slug_attr, + ) + for instrument in instruments + if instrument.component == "binary_sensor" + ) async_discover_device([*volvo_data.instruments]) diff --git a/homeassistant/components/volvooncall/device_tracker.py b/homeassistant/components/volvooncall/device_tracker.py index 039679fa413..51c2f08130b 100644 --- a/homeassistant/components/volvooncall/device_tracker.py +++ b/homeassistant/components/volvooncall/device_tracker.py @@ -26,21 +26,17 @@ async def async_setup_entry( @callback def async_discover_device(instruments: list[Instrument]) -> None: """Discover and add a discovered Volvo On Call device tracker.""" - entities: list[VolvoTrackerEntity] = [] - - for instrument in instruments: - if instrument.component == "device_tracker": - entities.append( - VolvoTrackerEntity( - instrument.vehicle.vin, - instrument.component, - instrument.attr, - instrument.slug_attr, - coordinator, - ) - ) - - async_add_entities(entities) + async_add_entities( + VolvoTrackerEntity( + instrument.vehicle.vin, + instrument.component, + instrument.attr, + instrument.slug_attr, + coordinator, + ) + for instrument in instruments + if instrument.component == "device_tracker" + ) async_discover_device([*volvo_data.instruments]) diff --git a/homeassistant/components/volvooncall/lock.py b/homeassistant/components/volvooncall/lock.py index a48b5dc6b65..cccd64bce05 100644 --- a/homeassistant/components/volvooncall/lock.py +++ b/homeassistant/components/volvooncall/lock.py @@ -28,21 +28,17 @@ async def async_setup_entry( @callback def async_discover_device(instruments: list[Instrument]) -> None: """Discover and add a discovered Volvo On Call lock.""" - entities: list[VolvoLock] = [] - - for instrument in instruments: - if instrument.component == "lock": - entities.append( - VolvoLock( - coordinator, - instrument.vehicle.vin, - instrument.component, - instrument.attr, - instrument.slug_attr, - ) - ) - - async_add_entities(entities) + async_add_entities( + VolvoLock( + coordinator, + instrument.vehicle.vin, + instrument.component, + instrument.attr, + instrument.slug_attr, + ) + for instrument in instruments + if instrument.component == "lock" + ) async_discover_device([*volvo_data.instruments]) diff --git a/homeassistant/components/volvooncall/sensor.py b/homeassistant/components/volvooncall/sensor.py index d8acaf34d67..a46c8671929 100644 --- a/homeassistant/components/volvooncall/sensor.py +++ b/homeassistant/components/volvooncall/sensor.py @@ -26,21 +26,17 @@ async def async_setup_entry( @callback def async_discover_device(instruments: list[Instrument]) -> None: """Discover and add a discovered Volvo On Call sensor.""" - entities: list[VolvoSensor] = [] - - for instrument in instruments: - if instrument.component == "sensor": - entities.append( - VolvoSensor( - coordinator, - instrument.vehicle.vin, - instrument.component, - instrument.attr, - instrument.slug_attr, - ) - ) - - async_add_entities(entities) + async_add_entities( + VolvoSensor( + coordinator, + instrument.vehicle.vin, + instrument.component, + instrument.attr, + instrument.slug_attr, + ) + for instrument in instruments + if instrument.component == "sensor" + ) async_discover_device([*volvo_data.instruments]) diff --git a/homeassistant/components/volvooncall/switch.py b/homeassistant/components/volvooncall/switch.py index 571aa88757a..23bc452ef66 100644 --- a/homeassistant/components/volvooncall/switch.py +++ b/homeassistant/components/volvooncall/switch.py @@ -28,21 +28,17 @@ async def async_setup_entry( @callback def async_discover_device(instruments: list[Instrument]) -> None: """Discover and add a discovered Volvo On Call switch.""" - entities: list[VolvoSwitch] = [] - - for instrument in instruments: - if instrument.component == "switch": - entities.append( - VolvoSwitch( - coordinator, - instrument.vehicle.vin, - instrument.component, - instrument.attr, - instrument.slug_attr, - ) - ) - - async_add_entities(entities) + async_add_entities( + VolvoSwitch( + coordinator, + instrument.vehicle.vin, + instrument.component, + instrument.attr, + instrument.slug_attr, + ) + for instrument in instruments + if instrument.component == "switch" + ) async_discover_device([*volvo_data.instruments]) diff --git a/homeassistant/components/vulcan/config_flow.py b/homeassistant/components/vulcan/config_flow.py index b761527e660..15749a7bf3d 100644 --- a/homeassistant/components/vulcan/config_flow.py +++ b/homeassistant/components/vulcan/config_flow.py @@ -190,9 +190,7 @@ class VulcanFlowHandler(ConfigFlow, domain=DOMAIN): async def async_step_add_next_config_entry(self, user_input=None): """Flow initialized when user is adding next entry of that integration.""" - existing_entries = [] - for entry in self.hass.config_entries.async_entries(DOMAIN): - existing_entries.append(entry) + existing_entries = self.hass.config_entries.async_entries(DOMAIN) errors = {} @@ -205,13 +203,14 @@ class VulcanFlowHandler(ConfigFlow, domain=DOMAIN): account = Account.load(existing_entries[0].data["account"]) client = Vulcan(keystore, account, async_get_clientsession(self.hass)) students = await client.get_students() - new_students = [] - existing_entry_ids = [] - for entry in self.hass.config_entries.async_entries(DOMAIN): - existing_entry_ids.append(entry.data["student_id"]) - for student in students: - if str(student.pupil.id) not in existing_entry_ids: - new_students.append(student) + existing_entry_ids = [ + entry.data["student_id"] for entry in existing_entries + ] + new_students = [ + student + for student in students + if str(student.pupil.id) not in existing_entry_ids + ] if not new_students: return self.async_abort(reason="all_student_already_configured") if len(new_students) == 1: @@ -277,9 +276,7 @@ class VulcanFlowHandler(ConfigFlow, domain=DOMAIN): keystore = credentials["keystore"] client = Vulcan(keystore, account, async_get_clientsession(self.hass)) students = await client.get_students() - existing_entries = [] - for entry in self.hass.config_entries.async_entries(DOMAIN): - existing_entries.append(entry) + existing_entries = self.hass.config_entries.async_entries(DOMAIN) matching_entries = False for student in students: for entry in existing_entries: diff --git a/homeassistant/components/waterfurnace/sensor.py b/homeassistant/components/waterfurnace/sensor.py index 46f76a58eb5..1e03ad88cc8 100644 --- a/homeassistant/components/waterfurnace/sensor.py +++ b/homeassistant/components/waterfurnace/sensor.py @@ -104,12 +104,9 @@ def setup_platform( if discovery_info is None: return - sensors = [] client = hass.data[WF_DOMAIN] - for description in SENSORS: - sensors.append(WaterFurnaceSensor(client, description)) - add_entities(sensors) + add_entities(WaterFurnaceSensor(client, description) for description in SENSORS) class WaterFurnaceSensor(SensorEntity): diff --git a/homeassistant/components/wled/number.py b/homeassistant/components/wled/number.py index 4dcdc493e53..e6142c1cea6 100644 --- a/homeassistant/components/wled/number.py +++ b/homeassistant/components/wled/number.py @@ -140,7 +140,8 @@ def async_update_segments( # Process new segments, add them to Home Assistant for segment_id in segment_ids - current_ids: current_ids.add(segment_id) - for desc in NUMBERS: - new_entities.append(WLEDNumber(coordinator, segment_id, desc)) + new_entities.extend( + WLEDNumber(coordinator, segment_id, desc) for desc in NUMBERS + ) async_add_entities(new_entities) diff --git a/homeassistant/components/workday/config_flow.py b/homeassistant/components/workday/config_flow.py index a1a5ed37a14..a66a9c51588 100644 --- a/homeassistant/components/workday/config_flow.py +++ b/homeassistant/components/workday/config_flow.py @@ -66,9 +66,7 @@ def add_province_and_language_to_schema( _country = country_holidays(country=country) if country_default_language := (_country.default_language): selectable_languages = _country.supported_languages - new_selectable_languages = [] - for lang in selectable_languages: - new_selectable_languages.append(lang[:2]) + new_selectable_languages = [lang[:2] for lang in selectable_languages] language_schema = { vol.Optional( CONF_LANGUAGE, default=country_default_language diff --git a/tests/components/venstar/util.py b/tests/components/venstar/util.py index 23e480272d0..369d3332135 100644 --- a/tests/components/venstar/util.py +++ b/tests/components/venstar/util.py @@ -47,14 +47,13 @@ async def async_init_integration( skip_setup: bool = False, ): """Set up the venstar integration in Home Assistant.""" - platform_config = [] - for model in TEST_MODELS: - platform_config.append( - { - CONF_PLATFORM: "venstar", - CONF_HOST: f"venstar-{model}.localdomain", - } - ) + platform_config = [ + { + CONF_PLATFORM: "venstar", + CONF_HOST: f"venstar-{model}.localdomain", + } + for model in TEST_MODELS + ] config = {DOMAIN: platform_config} await async_setup_component(hass, DOMAIN, config) diff --git a/tests/components/vesync/common.py b/tests/components/vesync/common.py index 2e5a973c143..23c57177ddd 100644 --- a/tests/components/vesync/common.py +++ b/tests/components/vesync/common.py @@ -49,10 +49,11 @@ def mock_devices_response( requests_mock: requests_mock.Mocker, device_name: str ) -> None: """Build a response for the Helpers.call_api method.""" - device_list = [] - for device in ALL_DEVICES["result"]["list"]: - if device["deviceName"] == device_name: - device_list.append(device) + device_list = [ + device + for device in ALL_DEVICES["result"]["list"] + if device["deviceName"] == device_name + ] requests_mock.post( "https://smartapi.vesync.com/cloud/v1/deviceManaged/devices", diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index a8968682aef..9f98447af41 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -141,8 +141,7 @@ def test_invalid_switches(hass: HomeAssistant) -> None: def add_entities(devices, action): """Mock add devices.""" - for device in devices: - hass_devices.append(device) + hass_devices.extend(devices) bad_conf = {} # No subscription diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 5e4eff5da53..7eee2261a26 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -684,9 +684,7 @@ async def test_get_states( assert msg["type"] == const.TYPE_RESULT assert msg["success"] - states = [] - for state in hass.states.async_all(): - states.append(state.as_dict()) + states = [state.as_dict() for state in hass.states.async_all()] assert msg["result"] == states diff --git a/tests/components/wsdot/test_sensor.py b/tests/components/wsdot/test_sensor.py index 4a912340078..9f5ec92a5b6 100644 --- a/tests/components/wsdot/test_sensor.py +++ b/tests/components/wsdot/test_sensor.py @@ -46,8 +46,7 @@ async def test_setup(hass: HomeAssistant, requests_mock: requests_mock.Mocker) - for entity in new_entities: entity.update() - for entity in new_entities: - entities.append(entity) + entities.extend(new_entities) uri = re.compile(RESOURCE + "*") requests_mock.get(uri, text=load_fixture("wsdot/wsdot.json")) From 3e85b2ed124ce45ea407c9c1f5f83d4cf322c02b Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 13 Mar 2024 19:46:38 +0100 Subject: [PATCH 0906/1691] Use async_update_reload_and_abort helper in tailwind (#110885) --- homeassistant/components/tailwind/config_flow.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/tailwind/config_flow.py b/homeassistant/components/tailwind/config_flow.py index 5005a0fb7f0..7204e9c9202 100644 --- a/homeassistant/components/tailwind/config_flow.py +++ b/homeassistant/components/tailwind/config_flow.py @@ -213,14 +213,13 @@ class TailwindFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="unsupported_firmware") if self.reauth_entry: - self.hass.config_entries.async_update_entry( + return self.async_update_reload_and_abort( self.reauth_entry, - data={CONF_HOST: host, CONF_TOKEN: token}, + data={ + CONF_HOST: host, + CONF_TOKEN: token, + }, ) - self.hass.async_create_task( - self.hass.config_entries.async_reload(self.reauth_entry.entry_id) - ) - return self.async_abort(reason="reauth_successful") await self.async_set_unique_id( format_mac(status.mac_address), raise_on_progress=False From a136638719f39526d7c287cca654ae3b73772664 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 19:54:52 +0100 Subject: [PATCH 0907/1691] Rework Melissa tests (#113241) --- homeassistant/components/melissa/__init__.py | 4 +- homeassistant/components/melissa/climate.py | 2 +- tests/components/melissa/__init__.py | 10 + tests/components/melissa/conftest.py | 47 +++ .../melissa/snapshots/test_climate.ambr | 34 ++ tests/components/melissa/test_climate.py | 382 ++---------------- tests/components/melissa/test_init.py | 20 +- 7 files changed, 128 insertions(+), 371 deletions(-) create mode 100644 tests/components/melissa/conftest.py create mode 100644 tests/components/melissa/snapshots/test_climate.ambr diff --git a/homeassistant/components/melissa/__init__.py b/homeassistant/components/melissa/__init__.py index 93e26231005..9a9268e73d1 100644 --- a/homeassistant/components/melissa/__init__.py +++ b/homeassistant/components/melissa/__init__.py @@ -1,6 +1,6 @@ """Support for Melissa climate.""" -import melissa +from melissa import AsyncMelissa import voluptuous as vol from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform @@ -31,7 +31,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) - api = melissa.AsyncMelissa(username=username, password=password) + api = AsyncMelissa(username=username, password=password) await api.async_connect() hass.data[DATA_MELISSA] = api diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index af59046c58b..73b04ff8c1d 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -50,7 +50,7 @@ async def async_setup_platform( if device["type"] == "melissa": all_devices.append(MelissaClimate(api, device["serial_number"], device)) - async_add_entities(all_devices) + async_add_entities(all_devices, True) class MelissaClimate(ClimateEntity): diff --git a/tests/components/melissa/__init__.py b/tests/components/melissa/__init__.py index c4caf0fe671..24b278b3c3a 100644 --- a/tests/components/melissa/__init__.py +++ b/tests/components/melissa/__init__.py @@ -1 +1,11 @@ """Tests for the melissa component.""" +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +VALID_CONFIG = {"melissa": {"username": "********", "password": "********"}} + + +async def setup_integration(hass: HomeAssistant) -> None: + """Set up the melissa integration in Home Assistant.""" + assert await async_setup_component(hass, "melissa", VALID_CONFIG) + await hass.async_block_till_done() diff --git a/tests/components/melissa/conftest.py b/tests/components/melissa/conftest.py new file mode 100644 index 00000000000..6a6781263b5 --- /dev/null +++ b/tests/components/melissa/conftest.py @@ -0,0 +1,47 @@ +"""Melissa conftest.""" + +from unittest.mock import AsyncMock, patch + +import pytest + +from tests.common import load_json_object_fixture + + +@pytest.fixture +async def mock_melissa(): + """Mock the Melissa API.""" + with patch( + "homeassistant.components.melissa.AsyncMelissa", autospec=True + ) as mock_client: + mock_client.return_value.async_connect = AsyncMock() + mock_client.return_value.async_fetch_devices.return_value = ( + load_json_object_fixture("fetch_devices.json", "melissa") + ) + mock_client.return_value.async_status.return_value = load_json_object_fixture( + "status.json", "melissa" + ) + mock_client.return_value.async_cur_settings.return_value = ( + load_json_object_fixture("cur_settings.json", "melissa") + ) + + mock_client.return_value.STATE_OFF = 0 + mock_client.return_value.STATE_ON = 1 + mock_client.return_value.STATE_IDLE = 2 + + mock_client.return_value.MODE_AUTO = 0 + mock_client.return_value.MODE_FAN = 1 + mock_client.return_value.MODE_HEAT = 2 + mock_client.return_value.MODE_COOL = 3 + mock_client.return_value.MODE_DRY = 4 + + mock_client.return_value.FAN_AUTO = 0 + mock_client.return_value.FAN_LOW = 1 + mock_client.return_value.FAN_MEDIUM = 2 + mock_client.return_value.FAN_HIGH = 3 + + mock_client.return_value.STATE = "state" + mock_client.return_value.MODE = "mode" + mock_client.return_value.FAN = "fan" + mock_client.return_value.TEMP = "temp" + mock_client.return_value.HUMIDITY = "humidity" + yield mock_client diff --git a/tests/components/melissa/snapshots/test_climate.ambr b/tests/components/melissa/snapshots/test_climate.ambr new file mode 100644 index 00000000000..40e757d1561 --- /dev/null +++ b/tests/components/melissa/snapshots/test_climate.ambr @@ -0,0 +1,34 @@ +# serializer version: 1 +# name: test_setup_platform + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_humidity': 18.7, + 'current_temperature': 27.4, + 'fan_mode': 'low', + 'fan_modes': list([ + 'auto', + 'high', + 'medium', + 'low', + ]), + 'friendly_name': 'Melissa 12345678', + 'hvac_modes': list([ + , + , + , + , + , + ]), + 'max_temp': 30, + 'min_temp': 16, + 'supported_features': , + 'target_temp_step': 1, + 'temperature': 16, + }), + 'context': , + 'entity_id': 'climate.melissa_12345678', + 'last_changed': , + 'last_updated': , + 'state': 'heat', + }) +# --- diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index 7903744ff13..ff59f925961 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -1,368 +1,46 @@ """Test for Melissa climate component.""" -import json -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock + +from syrupy import SnapshotAssertion from homeassistant.components.climate import ( - FAN_HIGH, - FAN_LOW, - FAN_MEDIUM, - ClimateEntityFeature, - HVACMode, + DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, ) -from homeassistant.components.melissa import DATA_MELISSA, climate as melissa -from homeassistant.components.melissa.climate import MelissaClimate -from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE from homeassistant.core import HomeAssistant +import homeassistant.helpers.entity_registry as er -from tests.common import load_fixture - -_SERIAL = "12345678" +from tests.components.melissa import setup_integration -def melissa_mock(): - """Use this to mock the melissa api.""" - api = Mock() - api.async_fetch_devices = AsyncMock( - return_value=json.loads(load_fixture("fetch_devices.json", "melissa")) - ) - api.async_status = AsyncMock( - return_value=json.loads(load_fixture("status.json", "melissa")) - ) - api.async_cur_settings = AsyncMock( - return_value=json.loads(load_fixture("cur_settings.json", "melissa")) - ) - - api.async_send = AsyncMock(return_value=True) - - api.STATE_OFF = 0 - api.STATE_ON = 1 - api.STATE_IDLE = 2 - - api.MODE_AUTO = 0 - api.MODE_FAN = 1 - api.MODE_HEAT = 2 - api.MODE_COOL = 3 - api.MODE_DRY = 4 - - api.FAN_AUTO = 0 - api.FAN_LOW = 1 - api.FAN_MEDIUM = 2 - api.FAN_HIGH = 3 - - api.STATE = "state" - api.MODE = "mode" - api.FAN = "fan" - api.TEMP = "temp" - return api - - -async def test_setup_platform(hass: HomeAssistant) -> None: +async def test_setup_platform( + hass: HomeAssistant, mock_melissa, snapshot: SnapshotAssertion +) -> None: """Test setup_platform.""" - with patch( - "homeassistant.components.melissa.climate.MelissaClimate" - ) as mocked_thermostat: - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = mocked_thermostat(api, device["serial_number"], device) - thermostats = [thermostat] + await setup_integration(hass) - hass.data[DATA_MELISSA] = api - - config = {} - add_entities = Mock() - discovery_info = {} - - await melissa.async_setup_platform(hass, config, add_entities, discovery_info) - add_entities.assert_called_once_with(thermostats) + assert hass.states.get("climate.melissa_12345678") == snapshot -async def test_get_name(hass: HomeAssistant) -> None: - """Test name property.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.name == "Melissa 12345678" +async def test_actions( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + mock_melissa: AsyncMock, +) -> None: + """Test that the switch can be turned on and off.""" + await setup_integration(hass) + entity_id = "climate.melissa_12345678" -async def test_current_fan_mode(hass: HomeAssistant) -> None: - """Test current_fan_mode property.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.fan_mode == FAN_LOW + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 25}, + blocking=True, + ) + await hass.async_block_till_done() - thermostat._cur_settings = None - assert thermostat.fan_mode is None - - -async def test_current_temperature(hass: HomeAssistant) -> None: - """Test current temperature.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.current_temperature == 27.4 - - -async def test_current_temperature_no_data(hass: HomeAssistant) -> None: - """Test current temperature without data.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - thermostat._data = None - assert thermostat.current_temperature is None - - -async def test_target_temperature_step(hass: HomeAssistant) -> None: - """Test current target_temperature_step.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.target_temperature_step == 1 - - -async def test_current_operation(hass: HomeAssistant) -> None: - """Test current operation.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.state == HVACMode.HEAT - - thermostat._cur_settings = None - assert thermostat.hvac_action is None - - -async def test_operation_list(hass: HomeAssistant) -> None: - """Test the operation list.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert [ - HVACMode.HEAT, - HVACMode.COOL, - HVACMode.DRY, - HVACMode.FAN_ONLY, - HVACMode.OFF, - ] == thermostat.hvac_modes - - -async def test_fan_modes(hass: HomeAssistant) -> None: - """Test the fan list.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert ["auto", FAN_HIGH, FAN_MEDIUM, FAN_LOW] == thermostat.fan_modes - - -async def test_target_temperature(hass: HomeAssistant) -> None: - """Test target temperature.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.target_temperature == 16 - - thermostat._cur_settings = None - assert thermostat.target_temperature is None - - -async def test_state(hass: HomeAssistant) -> None: - """Test state.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.state == HVACMode.HEAT - - thermostat._cur_settings = None - assert thermostat.state is None - - -async def test_temperature_unit(hass: HomeAssistant) -> None: - """Test temperature unit.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.temperature_unit == UnitOfTemperature.CELSIUS - - -async def test_min_temp(hass: HomeAssistant) -> None: - """Test min temp.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.min_temp == 16 - - -async def test_max_temp(hass: HomeAssistant) -> None: - """Test max temp.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.max_temp == 30 - - -async def test_supported_features(hass: HomeAssistant) -> None: - """Test supported_features property.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - features = ( - ClimateEntityFeature.TARGET_TEMPERATURE - | ClimateEntityFeature.FAN_MODE - | ClimateEntityFeature.TURN_OFF - | ClimateEntityFeature.TURN_ON - ) - assert thermostat.supported_features == features - - -async def test_set_temperature(hass: HomeAssistant) -> None: - """Test set_temperature.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - await thermostat.async_set_temperature(**{ATTR_TEMPERATURE: 25}) - assert thermostat.target_temperature == 25 - - -async def test_fan_mode(hass: HomeAssistant) -> None: - """Test set_fan_mode.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - await hass.async_block_till_done() - await thermostat.async_set_fan_mode(FAN_HIGH) - await hass.async_block_till_done() - assert thermostat.fan_mode == FAN_HIGH - - -async def test_set_operation_mode(hass: HomeAssistant) -> None: - """Test set_operation_mode.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - await hass.async_block_till_done() - await thermostat.async_set_hvac_mode(HVACMode.COOL) - await hass.async_block_till_done() - assert thermostat.hvac_mode == HVACMode.COOL - - -async def test_send(hass: HomeAssistant) -> None: - """Test send.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - await hass.async_block_till_done() - await thermostat.async_send({"fan": api.FAN_MEDIUM}) - await hass.async_block_till_done() - assert thermostat.fan_mode == FAN_MEDIUM - api.async_send.return_value = AsyncMock(return_value=False) - thermostat._cur_settings = None - await thermostat.async_send({"fan": api.FAN_LOW}) - await hass.async_block_till_done() - assert thermostat.fan_mode != FAN_LOW - assert thermostat._cur_settings is None - - -async def test_update(hass: HomeAssistant) -> None: - """Test update.""" - with patch( - "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning, patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - await thermostat.async_update() - assert thermostat.fan_mode == FAN_LOW - assert thermostat.state == HVACMode.HEAT - api.async_status = AsyncMock(side_effect=KeyError("boom")) - await thermostat.async_update() - mocked_warning.assert_called_once_with( - "Unable to update entity %s", thermostat.entity_id - ) - - -async def test_melissa_op_to_hass(hass: HomeAssistant) -> None: - """Test for translate melissa operations to hass.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.melissa_op_to_hass(1) == HVACMode.FAN_ONLY - assert thermostat.melissa_op_to_hass(2) == HVACMode.HEAT - assert thermostat.melissa_op_to_hass(3) == HVACMode.COOL - assert thermostat.melissa_op_to_hass(4) == HVACMode.DRY - assert thermostat.melissa_op_to_hass(5) is None - - -async def test_melissa_fan_to_hass(hass: HomeAssistant) -> None: - """Test for translate melissa fan state to hass.""" - with patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.melissa_fan_to_hass(0) == "auto" - assert thermostat.melissa_fan_to_hass(1) == FAN_LOW - assert thermostat.melissa_fan_to_hass(2) == FAN_MEDIUM - assert thermostat.melissa_fan_to_hass(3) == FAN_HIGH - assert thermostat.melissa_fan_to_hass(4) is None - - -async def test_hass_mode_to_melissa(hass: HomeAssistant) -> None: - """Test for hass operations to melssa.""" - with patch( - "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning, patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.hass_mode_to_melissa(HVACMode.FAN_ONLY) == 1 - assert thermostat.hass_mode_to_melissa(HVACMode.HEAT) == 2 - assert thermostat.hass_mode_to_melissa(HVACMode.COOL) == 3 - assert thermostat.hass_mode_to_melissa(HVACMode.DRY) == 4 - thermostat.hass_mode_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s mode", "test" - ) - - -async def test_hass_fan_to_melissa(hass: HomeAssistant) -> None: - """Test for translate melissa states to hass.""" - with patch( - "homeassistant.components.melissa.climate._LOGGER.warning" - ) as mocked_warning, patch("homeassistant.components.melissa"): - api = melissa_mock() - device = (await api.async_fetch_devices())[_SERIAL] - thermostat = MelissaClimate(api, _SERIAL, device) - assert thermostat.hass_fan_to_melissa("auto") == 0 - assert thermostat.hass_fan_to_melissa(FAN_LOW) == 1 - assert thermostat.hass_fan_to_melissa(FAN_MEDIUM) == 2 - assert thermostat.hass_fan_to_melissa(FAN_HIGH) == 3 - thermostat.hass_fan_to_melissa("test") - mocked_warning.assert_called_once_with( - "Melissa have no setting for %s fan mode", "test" - ) + assert len(mock_melissa.return_value.async_send.mock_calls) == 2 diff --git a/tests/components/melissa/test_init.py b/tests/components/melissa/test_init.py index 8f1e0c7a43c..6ad86ba4595 100644 --- a/tests/components/melissa/test_init.py +++ b/tests/components/melissa/test_init.py @@ -1,25 +1,13 @@ """The test for the Melissa Climate component.""" -from unittest.mock import AsyncMock, patch -from homeassistant.components import melissa from homeassistant.core import HomeAssistant -VALID_CONFIG = {"melissa": {"username": "********", "password": "********"}} +from tests.components.melissa import setup_integration -async def test_setup(hass: HomeAssistant) -> None: +async def test_setup(hass: HomeAssistant, mock_melissa) -> None: """Test setting up the Melissa component.""" - with patch("melissa.AsyncMelissa") as mocked_melissa, patch.object( - melissa, "async_load_platform" - ): - mocked_melissa.return_value.async_connect = AsyncMock() - await melissa.async_setup(hass, VALID_CONFIG) + await setup_integration(hass) - mocked_melissa.assert_called_with(username="********", password="********") - - assert melissa.DATA_MELISSA in hass.data - assert isinstance( - hass.data[melissa.DATA_MELISSA], - type(mocked_melissa.return_value), - ) + mock_melissa.assert_called_with(username="********", password="********") From 64b42a36510f7dc01a27ee417845ab9c45247efb Mon Sep 17 00:00:00 2001 From: Jonny Bergdahl <128166901+jonnybergdahl@users.noreply.github.com> Date: Wed, 13 Mar 2024 20:16:00 +0100 Subject: [PATCH 0908/1691] Fix Twitch auth token refresh (#112833) * Fix for expired token * Add auth token refresh. * Eliminate extra auth call * Fixed mock client --------- Co-authored-by: Jonny Bergdahl --- homeassistant/components/twitch/__init__.py | 7 +++-- homeassistant/components/twitch/const.py | 2 ++ homeassistant/components/twitch/sensor.py | 29 ++++++++++++++++----- tests/components/twitch/__init__.py | 1 + 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/twitch/__init__.py b/homeassistant/components/twitch/__init__.py index 5882b1da74d..60c9dcabb36 100644 --- a/homeassistant/components/twitch/__init__.py +++ b/homeassistant/components/twitch/__init__.py @@ -17,7 +17,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( async_get_config_entry_implementation, ) -from .const import DOMAIN, OAUTH_SCOPES, PLATFORMS +from .const import CLIENT, DOMAIN, OAUTH_SCOPES, PLATFORMS, SESSION async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -46,7 +46,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: client.auto_refresh_auth = False await client.set_user_authentication(access_token, scope=OAUTH_SCOPES) - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = client + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + CLIENT: client, + SESSION: session, + } await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/twitch/const.py b/homeassistant/components/twitch/const.py index fc7c2f73487..b46bf8113b4 100644 --- a/homeassistant/components/twitch/const.py +++ b/homeassistant/components/twitch/const.py @@ -17,5 +17,7 @@ CONF_REFRESH_TOKEN = "refresh_token" DOMAIN = "twitch" CONF_CHANNELS = "channels" +CLIENT = "client" +SESSION = "session" OAUTH_SCOPES = [AuthScope.USER_READ_SUBSCRIPTIONS, AuthScope.USER_READ_FOLLOWS] diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 9a936981f24..1107513080a 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -20,12 +20,13 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_TOKEN from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from .const import CONF_CHANNELS, DOMAIN, LOGGER, OAUTH_SCOPES +from .const import CLIENT, CONF_CHANNELS, DOMAIN, LOGGER, OAUTH_SCOPES, SESSION PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -50,6 +51,8 @@ ATTR_VIEWS = "views" STATE_OFFLINE = "offline" STATE_STREAMING = "streaming" +PARALLEL_UPDATES = 1 + def chunk_list(lst: list, chunk_size: int) -> list[list]: """Split a list into chunks of chunk_size.""" @@ -96,7 +99,8 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Initialize entries.""" - client = hass.data[DOMAIN][entry.entry_id] + client = hass.data[DOMAIN][entry.entry_id][CLIENT] + session = hass.data[DOMAIN][entry.entry_id][SESSION] channels = entry.options[CONF_CHANNELS] @@ -106,7 +110,7 @@ async def async_setup_entry( for chunk in chunk_list(channels, 100): entities.extend( [ - TwitchSensor(channel, client) + TwitchSensor(channel, session, client) async for channel in client.get_users(logins=chunk) ] ) @@ -119,8 +123,11 @@ class TwitchSensor(SensorEntity): _attr_translation_key = "channel" - def __init__(self, channel: TwitchUser, client: Twitch) -> None: + def __init__( + self, channel: TwitchUser, session: OAuth2Session, client: Twitch + ) -> None: """Initialize the sensor.""" + self._session = session self._client = client self._channel = channel self._enable_user_auth = client.has_required_auth(AuthType.USER, OAUTH_SCOPES) @@ -129,9 +136,17 @@ class TwitchSensor(SensorEntity): async def async_update(self) -> None: """Update device state.""" - followers = (await self._client.get_channel_followers(self._channel.id)).total + await self._session.async_ensure_token_valid() + await self._client.set_user_authentication( + self._session.token["access_token"], + OAUTH_SCOPES, + self._session.token["refresh_token"], + False, + ) + followers = await self._client.get_channel_followers(self._channel.id) + self._attr_extra_state_attributes = { - ATTR_FOLLOWING: followers, + ATTR_FOLLOWING: followers.total, ATTR_VIEWS: self._channel.view_count, } if self._enable_user_auth: @@ -165,7 +180,7 @@ class TwitchSensor(SensorEntity): self._attr_extra_state_attributes[ATTR_SUBSCRIPTION] = True self._attr_extra_state_attributes[ATTR_SUBSCRIPTION_GIFTED] = sub.is_gift except TwitchResourceNotFound: - LOGGER.debug("User is not subscribed") + LOGGER.debug("User is not subscribed to %s", self._channel.display_name) except TwitchAPIException as exc: LOGGER.error("Error response on check_user_subscription: %s", exc) diff --git a/tests/components/twitch/__init__.py b/tests/components/twitch/__init__.py index ba49390ebdf..3fa7d96337e 100644 --- a/tests/components/twitch/__init__.py +++ b/tests/components/twitch/__init__.py @@ -153,6 +153,7 @@ class TwitchMock: self, token: str, scope: list[AuthScope], + refresh_token: str | None = None, validate: bool = True, ) -> None: """Set user authentication.""" From 4547131bbc4b27d1b5a1efc449e3de6dbecce2ae Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 20:32:12 +0100 Subject: [PATCH 0909/1691] Improve lists in integrations [X-Z] (#113253) --- homeassistant/components/xbox/browse_media.py | 22 ++++----- homeassistant/components/xiaomi_aqara/lock.py | 10 ++-- .../components/xiaomi_miio/device_tracker.py | 8 +--- .../components/xiaomi_miio/select.py | 29 +++++------ .../components/xiaomi_miio/switch.py | 25 +++++----- homeassistant/components/xs1/switch.py | 14 +++--- .../yale_smart_alarm/binary_sensor.py | 11 +++-- .../yamaha_musiccast/media_player.py | 14 +++--- .../components/yamaha_musiccast/number.py | 20 ++++---- .../components/yamaha_musiccast/select.py | 22 ++++----- .../components/yamaha_musiccast/switch.py | 20 ++++---- .../components/yolink/binary_sensor.py | 18 ++++--- .../components/yolink/device_trigger.py | 20 ++++---- homeassistant/components/yolink/number.py | 22 ++++----- homeassistant/components/yolink/sensor.py | 22 ++++----- homeassistant/components/yolink/siren.py | 7 ++- homeassistant/components/yolink/switch.py | 16 +++---- homeassistant/components/zabbix/__init__.py | 4 +- homeassistant/components/zeroconf/__init__.py | 8 ++-- homeassistant/components/zha/core/gateway.py | 10 ++-- homeassistant/components/zha/core/group.py | 24 ++++------ homeassistant/components/zha/websocket_api.py | 48 +++++++++---------- homeassistant/components/zoneminder/switch.py | 8 ++-- homeassistant/components/zwave_js/cover.py | 11 ++--- .../components/zwave_js/device_action.py | 18 +++---- .../components/zwave_js/triggers/event.py | 15 +++--- .../zwave_js/triggers/value_updated.py | 14 +++--- 27 files changed, 219 insertions(+), 241 deletions(-) diff --git a/homeassistant/components/xbox/browse_media.py b/homeassistant/components/xbox/browse_media.py index 7dcfa90dcec..060712b5f9f 100644 --- a/homeassistant/components/xbox/browse_media.py +++ b/homeassistant/components/xbox/browse_media.py @@ -109,18 +109,18 @@ async def build_item_response( content_types = sorted( {app.content_type for app in apps.result if app.content_type in TYPE_MAP} ) - for c_type in content_types: - children.append( - BrowseMedia( - media_class=MediaClass.DIRECTORY, - media_content_id=c_type, - media_content_type=TYPE_MAP[c_type].type, - title=f"{c_type}s", - can_play=False, - can_expand=True, - children_media_class=TYPE_MAP[c_type].cls, - ) + children.extend( + BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=c_type, + media_content_type=TYPE_MAP[c_type].type, + title=f"{c_type}s", + can_play=False, + can_expand=True, + children_media_class=TYPE_MAP[c_type].cls, ) + for c_type in content_types + ) return library_info diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index ae6cf2c4e8f..90afbe15911 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -28,12 +28,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Perform the setup for Xiaomi devices.""" - entities = [] gateway = hass.data[DOMAIN][GATEWAYS_KEY][config_entry.entry_id] - for device in gateway.devices["lock"]: - if device["model"] == "lock.aq1": - entities.append(XiaomiAqaraLock(device, "Lock", gateway, config_entry)) - async_add_entities(entities) + async_add_entities( + XiaomiAqaraLock(device, "Lock", gateway, config_entry) + for device in gateway.devices["lock"] + if device["model"] == "lock.aq1" + ) class XiaomiAqaraLock(LockEntity, XiaomiDevice): diff --git a/homeassistant/components/xiaomi_miio/device_tracker.py b/homeassistant/components/xiaomi_miio/device_tracker.py index af023e5e999..ba73ccc57f0 100644 --- a/homeassistant/components/xiaomi_miio/device_tracker.py +++ b/homeassistant/components/xiaomi_miio/device_tracker.py @@ -62,18 +62,14 @@ class XiaomiMiioDeviceScanner(DeviceScanner): async def async_scan_devices(self): """Scan for devices and return a list containing found device IDs.""" - devices = [] try: station_info = await self.hass.async_add_executor_job(self.device.status) _LOGGER.debug("Got new station info: %s", station_info) - - for device in station_info.associated_stations: - devices.append(device["mac"]) - except DeviceException as ex: _LOGGER.error("Unable to fetch the state: %s", ex) + return [] - return devices + return [device["mac"] for device in station_info.associated_stations] async def async_get_device_name(self, device): """Return None. diff --git a/homeassistant/components/xiaomi_miio/select.py b/homeassistant/components/xiaomi_miio/select.py index d87037cdd2d..bef39535176 100644 --- a/homeassistant/components/xiaomi_miio/select.py +++ b/homeassistant/components/xiaomi_miio/select.py @@ -211,27 +211,24 @@ async def async_setup_entry( if model not in MODEL_TO_ATTR_MAP: return - entities = [] unique_id = config_entry.unique_id device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR] attributes = MODEL_TO_ATTR_MAP[model] - for description in SELECTOR_TYPES: - for attribute in attributes: - if description.key == attribute.attr_name: - entities.append( - XiaomiGenericSelector( - device, - config_entry, - f"{description.key}_{unique_id}", - coordinator, - description, - attribute.enum_class, - ) - ) - - async_add_entities(entities) + async_add_entities( + XiaomiGenericSelector( + device, + config_entry, + f"{description.key}_{unique_id}", + coordinator, + description, + attribute.enum_class, + ) + for description in SELECTOR_TYPES + for attribute in attributes + if description.key == attribute.attr_name + ) class XiaomiSelector(XiaomiCoordinatedMiioEntity, SelectEntity): diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 7720120502f..02517d00c57 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -349,7 +349,6 @@ async def async_setup_entry( async def async_setup_coordinated_entry(hass, config_entry, async_add_entities): """Set up the coordinated switch from a config entry.""" - entities = [] model = config_entry.data[CONF_MODEL] unique_id = config_entry.unique_id device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] @@ -371,19 +370,17 @@ async def async_setup_coordinated_entry(hass, config_entry, async_add_entities): elif model in MODELS_PURIFIER_MIOT: device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT - for description in SWITCH_TYPES: - if description.feature & device_features: - entities.append( - XiaomiGenericCoordinatedSwitch( - device, - config_entry, - f"{description.key}_{unique_id}", - coordinator, - description, - ) - ) - - async_add_entities(entities) + async_add_entities( + XiaomiGenericCoordinatedSwitch( + device, + config_entry, + f"{description.key}_{unique_id}", + coordinator, + description, + ) + for description in SWITCH_TYPES + if description.feature & device_features + ) async def async_setup_other_entry(hass, config_entry, async_add_entities): diff --git a/homeassistant/components/xs1/switch.py b/homeassistant/components/xs1/switch.py index 1a2113973e3..c2af652d6ad 100644 --- a/homeassistant/components/xs1/switch.py +++ b/homeassistant/components/xs1/switch.py @@ -23,14 +23,12 @@ def setup_platform( """Set up the XS1 switch platform.""" actuators = hass.data[COMPONENT_DOMAIN][ACTUATORS] - switch_entities = [] - for actuator in actuators: - if (actuator.type() == ActuatorType.SWITCH) or ( - actuator.type() == ActuatorType.DIMMER - ): - switch_entities.append(XS1SwitchEntity(actuator)) - - add_entities(switch_entities) + add_entities( + XS1SwitchEntity(actuator) + for actuator in actuators + if (actuator.type() == ActuatorType.SWITCH) + or (actuator.type() == ActuatorType.DIMMER) + ) class XS1SwitchEntity(XS1DeviceEntity, SwitchEntity): diff --git a/homeassistant/components/yale_smart_alarm/binary_sensor.py b/homeassistant/components/yale_smart_alarm/binary_sensor.py index 87101418a14..67fe1d74293 100644 --- a/homeassistant/components/yale_smart_alarm/binary_sensor.py +++ b/homeassistant/components/yale_smart_alarm/binary_sensor.py @@ -52,11 +52,12 @@ async def async_setup_entry( coordinator: YaleDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ COORDINATOR ] - sensors: list[YaleDoorSensor | YaleProblemSensor] = [] - for data in coordinator.data["door_windows"]: - sensors.append(YaleDoorSensor(coordinator, data)) - for description in SENSOR_TYPES: - sensors.append(YaleProblemSensor(coordinator, description)) + sensors: list[YaleDoorSensor | YaleProblemSensor] = [ + YaleDoorSensor(coordinator, data) for data in coordinator.data["door_windows"] + ] + sensors.extend( + YaleProblemSensor(coordinator, description) for description in SENSOR_TYPES + ) async_add_entities(sensors) diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index 399d645ff20..d03e4666ab2 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -901,13 +901,13 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity): return _LOGGER.debug("%s updates his group members", self.entity_id) - client_ips_for_removal = [] - for expected_client_ip in self.coordinator.data.group_client_list: - if expected_client_ip not in [ - entity.ip_address for entity in self.musiccast_group - ]: - # The client is no longer part of the group. Prepare removal. - client_ips_for_removal.append(expected_client_ip) + client_ips_for_removal = [ + expected_client_ip + for expected_client_ip in self.coordinator.data.group_client_list + # The client is no longer part of the group. Prepare removal. + if expected_client_ip + not in [entity.ip_address for entity in self.musiccast_group] + ] if client_ips_for_removal: _LOGGER.debug( diff --git a/homeassistant/components/yamaha_musiccast/number.py b/homeassistant/components/yamaha_musiccast/number.py index f820a31c4be..a5a591379c6 100644 --- a/homeassistant/components/yamaha_musiccast/number.py +++ b/homeassistant/components/yamaha_musiccast/number.py @@ -20,16 +20,18 @@ async def async_setup_entry( """Set up MusicCast number entities based on a config entry.""" coordinator: MusicCastDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - number_entities = [] + number_entities = [ + NumberCapability(coordinator, capability) + for capability in coordinator.data.capabilities + if isinstance(capability, NumberSetter) + ] - for capability in coordinator.data.capabilities: - if isinstance(capability, NumberSetter): - number_entities.append(NumberCapability(coordinator, capability)) - - for zone, data in coordinator.data.zones.items(): - for capability in data.capabilities: - if isinstance(capability, NumberSetter): - number_entities.append(NumberCapability(coordinator, capability, zone)) + number_entities.extend( + NumberCapability(coordinator, capability, zone) + for zone, data in coordinator.data.zones.items() + for capability in data.capabilities + if isinstance(capability, NumberSetter) + ) async_add_entities(number_entities) diff --git a/homeassistant/components/yamaha_musiccast/select.py b/homeassistant/components/yamaha_musiccast/select.py index c139dfb71fc..b068b956e1b 100644 --- a/homeassistant/components/yamaha_musiccast/select.py +++ b/homeassistant/components/yamaha_musiccast/select.py @@ -21,18 +21,18 @@ async def async_setup_entry( """Set up MusicCast select entities based on a config entry.""" coordinator: MusicCastDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - select_entities = [] + select_entities = [ + SelectableCapability(coordinator, capability) + for capability in coordinator.data.capabilities + if isinstance(capability, OptionSetter) + ] - for capability in coordinator.data.capabilities: - if isinstance(capability, OptionSetter): - select_entities.append(SelectableCapability(coordinator, capability)) - - for zone, data in coordinator.data.zones.items(): - for capability in data.capabilities: - if isinstance(capability, OptionSetter): - select_entities.append( - SelectableCapability(coordinator, capability, zone) - ) + select_entities.extend( + SelectableCapability(coordinator, capability, zone) + for zone, data in coordinator.data.zones.items() + for capability in data.capabilities + if isinstance(capability, OptionSetter) + ) async_add_entities(select_entities) diff --git a/homeassistant/components/yamaha_musiccast/switch.py b/homeassistant/components/yamaha_musiccast/switch.py index 4402ddc1c38..2ae8388027a 100644 --- a/homeassistant/components/yamaha_musiccast/switch.py +++ b/homeassistant/components/yamaha_musiccast/switch.py @@ -20,16 +20,18 @@ async def async_setup_entry( """Set up MusicCast sensor based on a config entry.""" coordinator: MusicCastDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - switch_entities = [] + switch_entities = [ + SwitchCapability(coordinator, capability) + for capability in coordinator.data.capabilities + if isinstance(capability, BinarySetter) + ] - for capability in coordinator.data.capabilities: - if isinstance(capability, BinarySetter): - switch_entities.append(SwitchCapability(coordinator, capability)) - - for zone, data in coordinator.data.zones.items(): - for capability in data.capabilities: - if isinstance(capability, BinarySetter): - switch_entities.append(SwitchCapability(coordinator, capability, zone)) + switch_entities.extend( + SwitchCapability(coordinator, capability, zone) + for zone, data in coordinator.data.zones.items() + for capability in data.capabilities + if isinstance(capability, BinarySetter) + ) async_add_entities(switch_entities) diff --git a/homeassistant/components/yolink/binary_sensor.py b/homeassistant/components/yolink/binary_sensor.py index ba28089546f..07a1fb07cc0 100644 --- a/homeassistant/components/yolink/binary_sensor.py +++ b/homeassistant/components/yolink/binary_sensor.py @@ -99,16 +99,14 @@ async def async_setup_entry( for device_coordinator in device_coordinators.values() if device_coordinator.device.device_type in SENSOR_DEVICE_TYPE ] - entities = [] - for binary_sensor_device_coordinator in binary_sensor_device_coordinators: - for description in SENSOR_TYPES: - if description.exists_fn(binary_sensor_device_coordinator.device): - entities.append( - YoLinkBinarySensorEntity( - config_entry, binary_sensor_device_coordinator, description - ) - ) - async_add_entities(entities) + async_add_entities( + YoLinkBinarySensorEntity( + config_entry, binary_sensor_device_coordinator, description + ) + for binary_sensor_device_coordinator in binary_sensor_device_coordinators + for description in SENSOR_TYPES + if description.exists_fn(binary_sensor_device_coordinator.device) + ) class YoLinkBinarySensorEntity(YoLinkEntity, BinarySensorEntity): diff --git a/homeassistant/components/yolink/device_trigger.py b/homeassistant/components/yolink/device_trigger.py index d3176e146d2..b7f83623be5 100644 --- a/homeassistant/components/yolink/device_trigger.py +++ b/homeassistant/components/yolink/device_trigger.py @@ -55,17 +55,15 @@ async def async_get_triggers( if not registry_device or registry_device.model != ATTR_DEVICE_SMART_REMOTER: return [] - triggers = [] - for trigger in DEVICE_TRIGGER_TYPES[ATTR_DEVICE_SMART_REMOTER]: - triggers.append( - { - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_PLATFORM: "device", - CONF_TYPE: trigger, - } - ) - return triggers + return [ + { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: trigger, + } + for trigger in DEVICE_TRIGGER_TYPES[ATTR_DEVICE_SMART_REMOTER] + ] async def async_attach_trigger( diff --git a/homeassistant/components/yolink/number.py b/homeassistant/components/yolink/number.py index d1a52e6e46b..7b7b582382b 100644 --- a/homeassistant/components/yolink/number.py +++ b/homeassistant/components/yolink/number.py @@ -75,18 +75,16 @@ async def async_setup_entry( for device_coordinator in device_coordinators.values() if device_coordinator.device.device_type in NUMBER_TYPE_CONF_SUPPORT_DEVICES ] - entities = [] - for config_device_coordinator in config_device_coordinators: - for description in DEVICE_CONFIG_DESCRIPTIONS: - if description.exists_fn(config_device_coordinator.device): - entities.append( - YoLinkNumberTypeConfigEntity( - config_entry, - config_device_coordinator, - description, - ) - ) - async_add_entities(entities) + async_add_entities( + YoLinkNumberTypeConfigEntity( + config_entry, + config_device_coordinator, + description, + ) + for config_device_coordinator in config_device_coordinators + for description in DEVICE_CONFIG_DESCRIPTIONS + if description.exists_fn(config_device_coordinator.device) + ) class YoLinkNumberTypeConfigEntity(YoLinkEntity, NumberEntity): diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index 17aa968840f..e1635465bc1 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -217,18 +217,16 @@ async def async_setup_entry( for device_coordinator in device_coordinators.values() if device_coordinator.device.device_type in SENSOR_DEVICE_TYPE ] - entities = [] - for sensor_device_coordinator in sensor_device_coordinators: - for description in SENSOR_TYPES: - if description.exists_fn(sensor_device_coordinator.device): - entities.append( - YoLinkSensorEntity( - config_entry, - sensor_device_coordinator, - description, - ) - ) - async_add_entities(entities) + async_add_entities( + YoLinkSensorEntity( + config_entry, + sensor_device_coordinator, + description, + ) + for sensor_device_coordinator in sensor_device_coordinators + for description in SENSOR_TYPES + if description.exists_fn(sensor_device_coordinator.device) + ) class YoLinkSensorEntity(YoLinkEntity, SensorEntity): diff --git a/homeassistant/components/yolink/siren.py b/homeassistant/components/yolink/siren.py index eca6a958108..b44baecf7b8 100644 --- a/homeassistant/components/yolink/siren.py +++ b/homeassistant/components/yolink/siren.py @@ -64,7 +64,12 @@ async def async_setup_entry( config_entry, siren_device_coordinator, description ) ) - async_add_entities(entities) + async_add_entities( + YoLinkSirenEntity(config_entry, siren_device_coordinator, description) + for siren_device_coordinator in siren_device_coordinators + for description in DEVICE_TYPES + if description.exists_fn(siren_device_coordinator.device) + ) class YoLinkSirenEntity(YoLinkEntity, SirenEntity): diff --git a/homeassistant/components/yolink/switch.py b/homeassistant/components/yolink/switch.py index 9e1b8eba9db..7a24ec1bd13 100644 --- a/homeassistant/components/yolink/switch.py +++ b/homeassistant/components/yolink/switch.py @@ -114,16 +114,12 @@ async def async_setup_entry( for device_coordinator in device_coordinators.values() if device_coordinator.device.device_type in DEVICE_TYPE ] - entities = [] - for switch_device_coordinator in switch_device_coordinators: - for description in DEVICE_TYPES: - if description.exists_fn(switch_device_coordinator.device): - entities.append( - YoLinkSwitchEntity( - config_entry, switch_device_coordinator, description - ) - ) - async_add_entities(entities) + async_add_entities( + YoLinkSwitchEntity(config_entry, switch_device_coordinator, description) + for switch_device_coordinator in switch_device_coordinators + for description in DEVICE_TYPES + if description.exists_fn(switch_device_coordinator.device) + ) class YoLinkSwitchEntity(YoLinkEntity, SwitchEntity): diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index edc0e28cce2..58d3c1fd3f2 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -140,9 +140,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: float_keys_count = len(float_keys) float_keys.update(floats) if len(float_keys) != float_keys_count: - floats_discovery = [] - for float_key in float_keys: - floats_discovery.append({"{#KEY}": float_key}) + floats_discovery = [{"{#KEY}": float_key} for float_key in float_keys] metric = ZabbixMetric( publish_states_host, "homeassistant.floats_discovery", diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 775353a29f6..e740b46c9eb 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -363,9 +363,11 @@ class ZeroconfDiscovery: # We want to make sure we know about other HomeAssistant # instances as soon as possible to avoid name conflicts # so we always browse for ZEROCONF_TYPE - for hk_type in (ZEROCONF_TYPE, *HOMEKIT_TYPES): - if hk_type not in self.zeroconf_types: - types.append(hk_type) + types.extend( + hk_type + for hk_type in (ZEROCONF_TYPE, *HOMEKIT_TYPES) + if hk_type not in self.zeroconf_types + ) _LOGGER.debug("Starting Zeroconf browser for: %s", types) self.async_service_browser = AsyncServiceBrowser( self.zeroconf, types, handlers=[self.async_service_update] diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 9556631f45b..55714bfef15 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -452,9 +452,9 @@ class ZHAGateway: self, device: ZHADevice, entity_refs: list[EntityReference] | None ) -> None: if entity_refs is not None: - remove_tasks: list[asyncio.Future[Any]] = [] - for entity_ref in entity_refs: - remove_tasks.append(entity_ref.remove_future) + remove_tasks: list[asyncio.Future[Any]] = [ + entity_ref.remove_future for entity_ref in entity_refs + ] if remove_tasks: await asyncio.wait(remove_tasks) @@ -783,9 +783,7 @@ class ZHAGateway: _LOGGER.debug("Group: 0x%04x could not be found", group_id) return if group.members: - tasks = [] - for member in group.members: - tasks.append(member.async_remove_from_group()) + tasks = [member.async_remove_from_group() for member in group.members] if tasks: await asyncio.gather(*tasks) self.application_controller.groups.pop(group_id) diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index f57f5df1861..0925a449301 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -176,13 +176,12 @@ class ZHAGroup(LogMixin): async def async_add_members(self, members: list[GroupMember]) -> None: """Add members to this group.""" if len(members) > 1: - tasks = [] - for member in members: - tasks.append( - self._zha_gateway.devices[member.ieee].async_add_endpoint_to_group( - member.endpoint_id, self.group_id - ) + tasks = [ + self._zha_gateway.devices[member.ieee].async_add_endpoint_to_group( + member.endpoint_id, self.group_id ) + for member in members + ] await asyncio.gather(*tasks) else: await self._zha_gateway.devices[ @@ -192,15 +191,12 @@ class ZHAGroup(LogMixin): async def async_remove_members(self, members: list[GroupMember]) -> None: """Remove members from this group.""" if len(members) > 1: - tasks = [] - for member in members: - tasks.append( - self._zha_gateway.devices[ - member.ieee - ].async_remove_endpoint_from_group( - member.endpoint_id, self.group_id - ) + tasks = [ + self._zha_gateway.devices[member.ieee].async_remove_endpoint_from_group( + member.endpoint_id, self.group_id ) + for member in members + ] await asyncio.gather(*tasks) else: await self._zha_gateway.devices[ diff --git a/homeassistant/components/zha/websocket_api.py b/homeassistant/components/zha/websocket_api.py index 38cce07550a..fe9c5680e27 100644 --- a/homeassistant/components/zha/websocket_api.py +++ b/homeassistant/components/zha/websocket_api.py @@ -388,30 +388,30 @@ async def websocket_get_groupable_devices( zha_gateway = get_zha_gateway(hass) devices = [device for device in zha_gateway.devices.values() if device.is_groupable] - groupable_devices = [] + groupable_devices: list[dict[str, Any]] = [] for device in devices: entity_refs = zha_gateway.device_registry[device.ieee] - for ep_id in device.async_get_groupable_endpoints(): - groupable_devices.append( - { - "endpoint_id": ep_id, - "entities": [ - { - "name": _get_entity_name(zha_gateway, entity_ref), - "original_name": _get_entity_original_name( - zha_gateway, entity_ref - ), - } - for entity_ref in entity_refs - if list(entity_ref.cluster_handlers.values())[ - 0 - ].cluster.endpoint.endpoint_id - == ep_id - ], - "device": device.zha_device_info, - } - ) + groupable_devices.extend( + { + "endpoint_id": ep_id, + "entities": [ + { + "name": _get_entity_name(zha_gateway, entity_ref), + "original_name": _get_entity_original_name( + zha_gateway, entity_ref + ), + } + for entity_ref in entity_refs + if list(entity_ref.cluster_handlers.values())[ + 0 + ].cluster.endpoint.endpoint_id + == ep_id + ], + "device": device.zha_device_info, + } + for ep_id in device.async_get_groupable_endpoints() + ) connection.send_result(msg[ID], groupable_devices) @@ -521,9 +521,9 @@ async def websocket_remove_groups( group_ids: list[int] = msg[GROUP_IDS] if len(group_ids) > 1: - tasks = [] - for group_id in group_ids: - tasks.append(zha_gateway.async_remove_zigpy_group(group_id)) + tasks = [ + zha_gateway.async_remove_zigpy_group(group_id) for group_id in group_ids + ] await asyncio.gather(*tasks) else: await zha_gateway.async_remove_zigpy_group(group_ids[0]) diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index fa91478f29c..48cbe58a876 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -40,16 +40,16 @@ def setup_platform( on_state = MonitorState(config.get(CONF_COMMAND_ON)) off_state = MonitorState(config.get(CONF_COMMAND_OFF)) - switches = [] + switches: list[ZMSwitchMonitors] = [] zm_client: ZoneMinder for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): if not (monitors := zm_client.get_monitors()): raise PlatformNotReady( "Switch could not fetch any monitors from ZoneMinder" ) - - for monitor in monitors: - switches.append(ZMSwitchMonitors(monitor, on_state, off_state)) + switches.extend( + ZMSwitchMonitors(monitor, on_state, off_state) for monitor in monitors + ) add_entities(switches) diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 8c6aea0ff75..f0ef1913bbb 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -379,12 +379,11 @@ class ZWaveWindowCovering(CoverPositionMixin, CoverTiltMixin): assert self._attr_supported_features self._attr_supported_features ^= set_position_feature - additional_info: list[str] = [] - for value in (self._current_position_value, self._current_tilt_value): - if value and value.property_key_name: - additional_info.append( - value.property_key_name.removesuffix(f" {NO_POSITION_SUFFIX}") - ) + additional_info: list[str] = [ + value.property_key_name.removesuffix(f" {NO_POSITION_SUFFIX}") + for value in (self._current_position_value, self._current_tilt_value) + if value and value.property_key_name + ] self._attr_name = self.generate_name(additional_info=additional_info) self._attr_device_class = CoverDeviceClass.WINDOW diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index 8ed2c07def3..bec9c8e55ab 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -238,15 +238,15 @@ async def async_get_actions( CONF_SUBTYPE: f"Endpoint {endpoint} (All)", } ) - for meter_type in endpoint_data[ATTR_METER_TYPE]: - actions.append( - { - **base_action, - CONF_TYPE: SERVICE_RESET_METER, - ATTR_METER_TYPE: meter_type, - CONF_SUBTYPE: f"Endpoint {endpoint} ({meter_type.name})", - } - ) + actions.extend( + { + **base_action, + CONF_TYPE: SERVICE_RESET_METER, + ATTR_METER_TYPE: meter_type, + CONF_SUBTYPE: f"Endpoint {endpoint} ({meter_type.name})", + } + for meter_type in endpoint_data[ATTR_METER_TYPE] + ) return actions diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py index 61c53630354..28717370d20 100644 --- a/homeassistant/components/zwave_js/triggers/event.py +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -239,15 +239,14 @@ async def async_attach_trigger( unsubs.append( node.on(event_name, functools.partial(async_on_event, device=device)) ) - - for driver in drivers: - unsubs.append( - async_dispatcher_connect( - hass, - f"{DOMAIN}_{driver.controller.home_id}_connected_to_server", - _create_zwave_listeners, - ) + unsubs.extend( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{driver.controller.home_id}_connected_to_server", + _create_zwave_listeners, ) + for driver in drivers + ) _create_zwave_listeners() diff --git a/homeassistant/components/zwave_js/triggers/value_updated.py b/homeassistant/components/zwave_js/triggers/value_updated.py index ef10af1cec0..4814eba0757 100644 --- a/homeassistant/components/zwave_js/triggers/value_updated.py +++ b/homeassistant/components/zwave_js/triggers/value_updated.py @@ -194,14 +194,14 @@ async def async_attach_trigger( ) ) - for driver in drivers: - unsubs.append( - async_dispatcher_connect( - hass, - f"{DOMAIN}_{driver.controller.home_id}_connected_to_server", - _create_zwave_listeners, - ) + unsubs.extend( + async_dispatcher_connect( + hass, + f"{DOMAIN}_{driver.controller.home_id}_connected_to_server", + _create_zwave_listeners, ) + for driver in drivers + ) _create_zwave_listeners() From 595d07f1c684cac83f4993917c92a6bfed59d478 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 21:28:21 +0100 Subject: [PATCH 0910/1691] Improve lists in integrations [L-M] (#113227) * Improve lists in integrations [L-M] * Update homeassistant/components/mailbox/__init__.py Co-authored-by: Jan Bouwhuis * Fix --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/lamarzocco/number.py | 13 ++++---- .../components/landisgyr_heat_meter/sensor.py | 9 +++--- homeassistant/components/lcn/binary_sensor.py | 13 +++----- homeassistant/components/lcn/climate.py | 13 +++----- homeassistant/components/lcn/cover.py | 11 +++---- homeassistant/components/lcn/light.py | 11 +++---- homeassistant/components/lcn/scene.py | 11 +++---- homeassistant/components/lcn/sensor.py | 11 +++---- homeassistant/components/lcn/switch.py | 12 +++---- .../components/lg_soundbar/media_player.py | 20 ++++++------ homeassistant/components/lifx/coordinator.py | 3 +- homeassistant/components/logbook/helpers.py | 8 ++--- homeassistant/components/london_air/sensor.py | 15 +++++---- .../components/london_underground/sensor.py | 8 ++--- .../components/luci/device_tracker.py | 19 +++++------- .../components/lupusec/binary_sensor.py | 7 ++--- homeassistant/components/lupusec/switch.py | 7 ++--- .../lutron_caseta/device_trigger.py | 26 +++++++--------- homeassistant/components/lyric/climate.py | 31 +++++++++---------- homeassistant/components/lyric/sensor.py | 13 +++++++- homeassistant/components/mailbox/__init__.py | 10 +++--- homeassistant/components/matrix/__init__.py | 18 +++++------ homeassistant/components/maxcube/climate.py | 12 +++---- homeassistant/components/melissa/climate.py | 15 ++++----- homeassistant/components/melnor/models.py | 7 +++-- .../microsoft_face_detect/image_processing.py | 13 +++----- .../image_processing.py | 21 ++++++------- .../components/minecraft_server/api.py | 3 +- .../components/modbus/binary_sensor.py | 7 ++--- homeassistant/components/modbus/sensor.py | 7 ++--- homeassistant/components/mvglive/sensor.py | 11 ++++--- homeassistant/components/mystrom/sensor.py | 11 +++---- homeassistant/components/myuplink/update.py | 20 +++++------- tests/components/mqtt/test_discovery.py | 16 +++++----- 34 files changed, 200 insertions(+), 232 deletions(-) diff --git a/homeassistant/components/lamarzocco/number.py b/homeassistant/components/lamarzocco/number.py index 05f937f48f6..88a06a0c9d0 100644 --- a/homeassistant/components/lamarzocco/number.py +++ b/homeassistant/components/lamarzocco/number.py @@ -208,20 +208,19 @@ async def async_setup_entry( """Set up number entities.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities( + entities: list[NumberEntity] = [ LaMarzoccoNumberEntity(coordinator, description) for description in ENTITIES if description.supported_fn(coordinator) - ) + ] - entities: list[LaMarzoccoKeyNumberEntity] = [] for description in KEY_ENTITIES: if description.supported_fn(coordinator): num_keys = KEYS_PER_MODEL[coordinator.lm.model_name] - for key in range(min(num_keys, 1), num_keys + 1): - entities.append( - LaMarzoccoKeyNumberEntity(coordinator, description, key) - ) + entities.extend( + LaMarzoccoKeyNumberEntity(coordinator, description, key) + for key in range(min(num_keys, 1), num_keys + 1) + ) async_add_entities(entities) diff --git a/homeassistant/components/landisgyr_heat_meter/sensor.py b/homeassistant/components/landisgyr_heat_meter/sensor.py index 9078a46f876..dd76d3e53cc 100644 --- a/homeassistant/components/landisgyr_heat_meter/sensor.py +++ b/homeassistant/components/landisgyr_heat_meter/sensor.py @@ -286,11 +286,10 @@ async def async_setup_entry( name="Landis+Gyr Heat Meter", ) - sensors = [] - for description in HEAT_METER_SENSOR_TYPES: - sensors.append(HeatMeterSensor(coordinator, description, device)) - - async_add_entities(sensors) + async_add_entities( + HeatMeterSensor(coordinator, description, device) + for description in HEAT_METER_SENSOR_TYPES + ) class HeatMeterSensor( diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py index 2670777d9b3..35836e4653e 100644 --- a/homeassistant/components/lcn/binary_sensor.py +++ b/homeassistant/components/lcn/binary_sensor.py @@ -43,15 +43,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up LCN switch entities from a config entry.""" - entities = [] - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_BINARY_SENSOR: - entities.append( - create_lcn_binary_sensor_entity(hass, entity_config, config_entry) - ) - - async_add_entities(entities) + async_add_entities( + create_lcn_binary_sensor_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_BINARY_SENSOR + ) class LcnRegulatorLockSensor(LcnEntity, BinarySensorEntity): diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index abe0080811d..c03061618f7 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -56,15 +56,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up LCN switch entities from a config entry.""" - entities = [] - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_CLIMATE: - entities.append( - create_lcn_climate_entity(hass, entity_config, config_entry) - ) - - async_add_entities(entities) + async_add_entities( + create_lcn_climate_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_CLIMATE + ) class LcnClimate(LcnEntity, ClimateEntity): diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 6738add28e4..edc60a202a1 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -40,13 +40,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up LCN cover entities from a config entry.""" - entities = [] - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_COVER: - entities.append(create_lcn_cover_entity(hass, entity_config, config_entry)) - - async_add_entities(entities) + async_add_entities( + create_lcn_cover_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_COVER + ) class LcnOutputsCover(LcnEntity, CoverEntity): diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 3f1467c74c1..584161a0829 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -53,13 +53,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up LCN light entities from a config entry.""" - entities = [] - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_LIGHT: - entities.append(create_lcn_light_entity(hass, entity_config, config_entry)) - - async_add_entities(entities) + async_add_entities( + create_lcn_light_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_LIGHT + ) class LcnOutputLight(LcnEntity, LightEntity): diff --git a/homeassistant/components/lcn/scene.py b/homeassistant/components/lcn/scene.py index fe8040bc291..7e476987c53 100644 --- a/homeassistant/components/lcn/scene.py +++ b/homeassistant/components/lcn/scene.py @@ -43,13 +43,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up LCN switch entities from a config entry.""" - entities = [] - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_SCENE: - entities.append(create_lcn_scene_entity(hass, entity_config, config_entry)) - - async_add_entities(entities) + async_add_entities( + create_lcn_scene_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_SCENE + ) class LcnScene(LcnEntity, Scene): diff --git a/homeassistant/components/lcn/sensor.py b/homeassistant/components/lcn/sensor.py index 38f76e8d2d1..32b97ab8317 100644 --- a/homeassistant/components/lcn/sensor.py +++ b/homeassistant/components/lcn/sensor.py @@ -56,13 +56,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up LCN switch entities from a config entry.""" - entities = [] - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_SENSOR: - entities.append(create_lcn_sensor_entity(hass, entity_config, config_entry)) - - async_add_entities(entities) + async_add_entities( + create_lcn_sensor_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_SENSOR + ) class LcnVariableSensor(LcnEntity, SensorEntity): diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index 05e8d8d3937..b82394ced0d 100644 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -41,13 +41,11 @@ async def async_setup_entry( ) -> None: """Set up LCN switch entities from a config entry.""" - entities = [] - - for entity_config in config_entry.data[CONF_ENTITIES]: - if entity_config[CONF_DOMAIN] == DOMAIN_SWITCH: - entities.append(create_lcn_switch_entity(hass, entity_config, config_entry)) - - async_add_entities(entities) + async_add_entities( + create_lcn_switch_entity(hass, entity_config, config_entry) + for entity_config in config_entry.data[CONF_ENTITIES] + if entity_config[CONF_DOMAIN] == DOMAIN_SWITCH + ) class LcnOutputSwitch(LcnEntity, SwitchEntity): diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index 962f67f714d..61baed1198b 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -166,11 +166,11 @@ class LGDevice(MediaPlayerEntity): @property def sound_mode_list(self): """Return the available sound modes.""" - modes = [] - for equaliser in self._equalisers: - if equaliser < len(temescal.equalisers): - modes.append(temescal.equalisers[equaliser]) - return sorted(modes) + return sorted( + temescal.equalisers[equaliser] + for equaliser in self._equalisers + if equaliser < len(temescal.equalisers) + ) @property def source(self): @@ -182,11 +182,11 @@ class LGDevice(MediaPlayerEntity): @property def source_list(self): """List of available input sources.""" - sources = [] - for function in self._functions: - if function < len(temescal.functions): - sources.append(temescal.functions[function]) - return sorted(sources) + return sorted( + temescal.functions[function] + for function in self._functions + if function < len(temescal.functions) + ) def set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" diff --git a/homeassistant/components/lifx/coordinator.py b/homeassistant/components/lifx/coordinator.py index 2b15b65255f..63912cbb820 100644 --- a/homeassistant/components/lifx/coordinator.py +++ b/homeassistant/components/lifx/coordinator.py @@ -381,8 +381,7 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]): # pad the color list with blanks if necessary if len(colors) < 82: - for _ in range(82 - len(colors)): - colors.append((0, 0, 0, 0)) + colors.extend([(0, 0, 0, 0) for _ in range(82 - len(colors))]) await async_execute_lifx( partial( diff --git a/homeassistant/components/logbook/helpers.py b/homeassistant/components/logbook/helpers.py index e3288452fb7..1731fcaddd9 100644 --- a/homeassistant/components/logbook/helpers.py +++ b/homeassistant/components/logbook/helpers.py @@ -176,10 +176,10 @@ def async_subscribe_events( event_forwarder = event_forwarder_filtered( target, entities_filter, entity_ids, device_ids ) - for event_type in event_types: - subscriptions.append( - hass.bus.async_listen(event_type, event_forwarder, run_immediately=True) - ) + subscriptions.extend( + hass.bus.async_listen(event_type, event_forwarder, run_immediately=True) + for event_type in event_types + ) if device_ids and not entity_ids: # No entities to subscribe to but we are filtering diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index 39debb5ba4c..12d52b9a20a 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -72,11 +72,8 @@ def setup_platform( """Set up the London Air sensor.""" data = APIData() data.update() - sensors = [] - for name in config[CONF_LOCATIONS]: - sensors.append(AirSensor(name, data)) - add_entities(sensors, True) + add_entities((AirSensor(name, data) for name in config[CONF_LOCATIONS]), True) class APIData: @@ -141,14 +138,16 @@ class AirSensor(SensorEntity): def update(self) -> None: """Update the sensor.""" - sites_status = [] + sites_status: list = [] self._api_data.update() if self._api_data.data: self._site_data = self._api_data.data[self._name] self._updated = self._site_data[0]["updated"] - for site in self._site_data: - if site["pollutants_status"] != "no_species_data": - sites_status.append(site["pollutants_status"]) + sites_status.extend( + site["pollutants_status"] + for site in self._site_data + if site["pollutants_status"] != "no_species_data" + ) if sites_status: self._state = max(set(sites_status), key=sites_status.count) diff --git a/homeassistant/components/london_underground/sensor.py b/homeassistant/components/london_underground/sensor.py index 03bdc1c395f..e5735aa7fba 100644 --- a/homeassistant/components/london_underground/sensor.py +++ b/homeassistant/components/london_underground/sensor.py @@ -45,11 +45,9 @@ async def async_setup_platform( if not coordinator.last_update_success: raise PlatformNotReady - sensors = [] - for line in config[CONF_LINE]: - sensors.append(LondonTubeSensor(coordinator, line)) - - async_add_entities(sensors) + async_add_entities( + LondonTubeSensor(coordinator, line) for line in config[CONF_LINE] + ) class LondonTubeSensor(CoordinatorEntity[LondonTubeCoordinator], SensorEntity): diff --git a/homeassistant/components/luci/device_tracker.py b/homeassistant/components/luci/device_tracker.py index 528596d53c5..d62c1b07b5c 100644 --- a/homeassistant/components/luci/device_tracker.py +++ b/homeassistant/components/luci/device_tracker.py @@ -96,14 +96,11 @@ class LuciDeviceScanner(DeviceScanner): _LOGGER.debug("Luci get_all_connected_devices returned: %s", result) - last_results = [] - for device in result: - if ( - not hasattr(self.router.router.owrt_version, "release") - or not self.router.router.owrt_version.release - or self.router.router.owrt_version.release[0] < 19 - or device.reachable - ): - last_results.append(device) - - self.last_results = last_results + self.last_results = [ + device + for device in result + if not hasattr(self.router.router.owrt_version, "release") + or not self.router.router.owrt_version.release + or self.router.router.owrt_version.release[0] < 19 + or device.reachable + ] diff --git a/homeassistant/components/lupusec/binary_sensor.py b/homeassistant/components/lupusec/binary_sensor.py index e046d356327..b2413e2b462 100644 --- a/homeassistant/components/lupusec/binary_sensor.py +++ b/homeassistant/components/lupusec/binary_sensor.py @@ -35,13 +35,12 @@ async def async_setup_entry( device_types = CONST.TYPE_OPENING + CONST.TYPE_SENSOR - sensors = [] partial_func = partial(data.get_devices, generic_type=device_types) devices = await hass.async_add_executor_job(partial_func) - for device in devices: - sensors.append(LupusecBinarySensor(device, config_entry.entry_id)) - async_add_entities(sensors) + async_add_entities( + LupusecBinarySensor(device, config_entry.entry_id) for device in devices + ) class LupusecBinarySensor(LupusecBaseSensor, BinarySensorEntity): diff --git a/homeassistant/components/lupusec/switch.py b/homeassistant/components/lupusec/switch.py index 3fa62235b9a..23f3c927880 100644 --- a/homeassistant/components/lupusec/switch.py +++ b/homeassistant/components/lupusec/switch.py @@ -30,13 +30,12 @@ async def async_setup_entry( device_types = CONST.TYPE_SWITCH - switches = [] partial_func = partial(data.get_devices, generic_type=device_types) devices = await hass.async_add_executor_job(partial_func) - for device in devices: - switches.append(LupusecSwitch(device, config_entry.entry_id)) - async_add_entities(switches) + async_add_entities( + LupusecSwitch(device, config_entry.entry_id) for device in devices + ) class LupusecSwitch(LupusecBaseSensor, SwitchEntity): diff --git a/homeassistant/components/lutron_caseta/device_trigger.py b/homeassistant/components/lutron_caseta/device_trigger.py index fd5d2ad222d..86b82e64127 100644 --- a/homeassistant/components/lutron_caseta/device_trigger.py +++ b/homeassistant/components/lutron_caseta/device_trigger.py @@ -379,8 +379,6 @@ async def async_get_triggers( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device triggers for lutron caseta devices.""" - triggers = [] - # Check if device is a valid keypad. Return empty if not. if not (data := get_lutron_data_by_dr_id(hass, device_id)) or not ( keypad := data.keypad_data.dr_device_id_to_keypad.get(device_id) @@ -395,19 +393,17 @@ async def async_get_triggers( keypad_button_names_to_leap[keypad["lutron_device_id"]], ) - for trigger in SUPPORTED_INPUTS_EVENTS_TYPES: - for subtype in valid_buttons: - triggers.append( - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: trigger, - CONF_SUBTYPE: subtype, - } - ) - - return triggers + return [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: trigger, + CONF_SUBTYPE: subtype, + } + for trigger in SUPPORTED_INPUTS_EVENTS_TYPES + for subtype in valid_buttons + ] async def async_attach_trigger( diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 3bba2f677b0..f8ae978c2fd 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -126,23 +126,22 @@ async def async_setup_entry( """Set up the Honeywell Lyric climate platform based on a config entry.""" coordinator: DataUpdateCoordinator[Lyric] = hass.data[DOMAIN][entry.entry_id] - entities = [] - - for location in coordinator.data.locations: - for device in location.devices: - entities.append( - LyricClimate( - coordinator, - ClimateEntityDescription( - key=f"{device.macID}_thermostat", - name=device.name, - ), - location, - device, - ) + async_add_entities( + ( + LyricClimate( + coordinator, + ClimateEntityDescription( + key=f"{device.macID}_thermostat", + name=device.name, + ), + location, + device, ) - - async_add_entities(entities, True) + for location in coordinator.data.locations + for device in location.devices + ), + True, + ) platform = entity_platform.async_get_current_platform() diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index f1e9a8b3c3c..1072ca6c2ba 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -149,7 +149,18 @@ async def async_setup_entry( ) ) - async_add_entities(entities) + async_add_entities( + LyricSensor( + coordinator, + device_sensor, + location, + device, + ) + for location in coordinator.data.locations + for device in location.devices + for device_sensor in DEVICE_SENSORS + if device_sensor.suitable_fn(device) + ) class LyricSensor(LyricDeviceEntity, SensorEntity): diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index ef9a36ca20c..4d585413519 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -232,16 +232,16 @@ class MailboxPlatformsView(MailboxView): async def get(self, request: web.Request) -> web.Response: """Retrieve list of platforms.""" - platforms: list[dict[str, Any]] = [] - for mailbox in self.mailboxes: - platforms.append( + return self.json( + [ { "name": mailbox.name, "has_media": mailbox.has_media, "can_delete": mailbox.can_delete, } - ) - return self.json(platforms) + for mailbox in self.mailboxes + ] + ) class MailboxMessageView(MailboxView): diff --git a/homeassistant/components/matrix/__init__.py b/homeassistant/components/matrix/__init__.py index 5c7d66b7a0b..b8f1ec08fe0 100644 --- a/homeassistant/components/matrix/__init__.py +++ b/homeassistant/components/matrix/__init__.py @@ -432,18 +432,16 @@ class MatrixBot: self, target_rooms: Sequence[RoomAnyID], message_type: str, content: dict ) -> None: """Wrap _handle_room_send for multiple target_rooms.""" - _tasks = [] - for target_room in target_rooms: - _tasks.append( - self.hass.async_create_task( - self._handle_room_send( - target_room=target_room, - message_type=message_type, - content=content, - ) + await asyncio.wait( + self.hass.async_create_task( + self._handle_room_send( + target_room=target_room, + message_type=message_type, + content=content, ) ) - await asyncio.wait(_tasks) + for target_room in target_rooms + ) async def _send_image( self, image_path: str, target_rooms: Sequence[RoomAnyID] diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 1b7dc6b901d..b14efbbe073 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -54,13 +54,13 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Iterate through all MAX! Devices and add thermostats.""" - devices = [] - for handler in hass.data[DATA_KEY].values(): - for device in handler.cube.devices: - if device.is_thermostat() or device.is_wallthermostat(): - devices.append(MaxCubeClimate(handler, device)) - add_entities(devices) + add_entities( + MaxCubeClimate(handler, device) + for handler in hass.data[DATA_KEY].values() + for device in handler.cube.devices + if device.is_thermostat() or device.is_wallthermostat() + ) class MaxCubeClimate(ClimateEntity): diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index 73b04ff8c1d..fcb0820a6f0 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -44,13 +44,14 @@ async def async_setup_platform( api = hass.data[DATA_MELISSA] devices = (await api.async_fetch_devices()).values() - all_devices = [] - - for device in devices: - if device["type"] == "melissa": - all_devices.append(MelissaClimate(api, device["serial_number"], device)) - - async_add_entities(all_devices, True) + async_add_entities( + ( + MelissaClimate(api, device["serial_number"], device) + for device in devices + if device["type"] == "melissa" + ), + True, + ) class MelissaClimate(ClimateEntity): diff --git a/homeassistant/components/melnor/models.py b/homeassistant/components/melnor/models.py index beb8b42a4a3..ffcccccb789 100644 --- a/homeassistant/components/melnor/models.py +++ b/homeassistant/components/melnor/models.py @@ -117,14 +117,15 @@ def get_entities_for_valves( ], ) -> list[CoordinatorEntity[MelnorDataUpdateCoordinator]]: """Get descriptions for valves.""" - entities = [] + entities: list[CoordinatorEntity[MelnorDataUpdateCoordinator]] = [] # This device may not have 4 valves total, but the library will only expose the right number of valves for i in range(1, 5): valve = coordinator.data[f"zone{i}"] if valve is not None: - for description in descriptions: - entities.append(function(valve, description)) + entities.extend( + function(valve, description) for description in descriptions + ) return entities diff --git a/homeassistant/components/microsoft_face_detect/image_processing.py b/homeassistant/components/microsoft_face_detect/image_processing.py index 536557bdddb..ef8a4f5df4b 100644 --- a/homeassistant/components/microsoft_face_detect/image_processing.py +++ b/homeassistant/components/microsoft_face_detect/image_processing.py @@ -56,15 +56,12 @@ async def async_setup_platform( api = hass.data[DATA_MICROSOFT_FACE] attributes = config[CONF_ATTRIBUTES] - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - MicrosoftFaceDetectEntity( - camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME) - ) + async_add_entities( + MicrosoftFaceDetectEntity( + camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME) ) - - async_add_entities(entities) + for camera in config[CONF_SOURCE] + ) class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity): diff --git a/homeassistant/components/microsoft_face_identify/image_processing.py b/homeassistant/components/microsoft_face_identify/image_processing.py index 7903df22996..d1af1d4a827 100644 --- a/homeassistant/components/microsoft_face_identify/image_processing.py +++ b/homeassistant/components/microsoft_face_identify/image_processing.py @@ -38,19 +38,16 @@ async def async_setup_platform( face_group = config[CONF_GROUP] confidence = config[CONF_CONFIDENCE] - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - MicrosoftFaceIdentifyEntity( - camera[CONF_ENTITY_ID], - api, - face_group, - confidence, - camera.get(CONF_NAME), - ) + async_add_entities( + MicrosoftFaceIdentifyEntity( + camera[CONF_ENTITY_ID], + api, + face_group, + confidence, + camera.get(CONF_NAME), ) - - async_add_entities(entities) + for camera in config[CONF_SOURCE] + ) class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): diff --git a/homeassistant/components/minecraft_server/api.py b/homeassistant/components/minecraft_server/api.py index 7b0d7475f84..3155d83a736 100644 --- a/homeassistant/components/minecraft_server/api.py +++ b/homeassistant/components/minecraft_server/api.py @@ -140,8 +140,7 @@ class MinecraftServer: players_list: list[str] = [] if players := status_response.players.sample: - for player in players: - players_list.append(player.name) + players_list.extend(player.name for player in players) players_list.sort() return MinecraftServerData( diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 0de7a87f219..23192244332 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -93,10 +93,9 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity): name=name, ) - slaves: list[SlaveSensor] = [] - for idx in range(0, slave_count): - slaves.append(SlaveSensor(self._coordinator, idx, entry)) - return slaves + return [ + SlaveSensor(self._coordinator, idx, entry) for idx in range(0, slave_count) + ] async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index b21a001c935..f74c4bf4e1b 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -94,10 +94,9 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity): name=name, ) - slaves: list[SlaveSensor] = [] - for idx in range(0, slave_count): - slaves.append(SlaveSensor(self._coordinator, idx, entry)) - return slaves + return [ + SlaveSensor(self._coordinator, idx, entry) for idx in range(0, slave_count) + ] async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" diff --git a/homeassistant/components/mvglive/sensor.py b/homeassistant/components/mvglive/sensor.py index 39d77edbfa7..6aefa83d4bb 100644 --- a/homeassistant/components/mvglive/sensor.py +++ b/homeassistant/components/mvglive/sensor.py @@ -71,9 +71,8 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the MVGLive sensor.""" - sensors = [] - for nextdeparture in config[CONF_NEXT_DEPARTURE]: - sensors.append( + add_entities( + ( MVGLiveSensor( nextdeparture.get(CONF_STATION), nextdeparture.get(CONF_DESTINATIONS), @@ -84,8 +83,10 @@ def setup_platform( nextdeparture.get(CONF_NUMBER), nextdeparture.get(CONF_NAME), ) - ) - add_entities(sensors, True) + for nextdeparture in config[CONF_NEXT_DEPARTURE] + ), + True, + ) class MVGLiveSensor(SensorEntity): diff --git a/homeassistant/components/mystrom/sensor.py b/homeassistant/components/mystrom/sensor.py index f7805cd9012..5c096c87993 100644 --- a/homeassistant/components/mystrom/sensor.py +++ b/homeassistant/components/mystrom/sensor.py @@ -52,13 +52,12 @@ async def async_setup_entry( ) -> None: """Set up the myStrom entities.""" device: MyStromSwitch = hass.data[DOMAIN][entry.entry_id].device - sensors = [] - for description in SENSOR_TYPES: - if description.value_fn(device) is not None: - sensors.append(MyStromSwitchSensor(device, entry.title, description)) - - async_add_entities(sensors) + async_add_entities( + MyStromSwitchSensor(device, entry.title, description) + for description in SENSOR_TYPES + if description.value_fn(device) is not None + ) class MyStromSwitchSensor(SensorEntity): diff --git a/homeassistant/components/myuplink/update.py b/homeassistant/components/myuplink/update.py index 2b779e83386..6a38741a562 100644 --- a/homeassistant/components/myuplink/update.py +++ b/homeassistant/components/myuplink/update.py @@ -25,21 +25,17 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up update entity.""" - entities: list[UpdateEntity] = [] coordinator: MyUplinkDataCoordinator = hass.data[DOMAIN][config_entry.entry_id] - # Setup update entities - for device_id in coordinator.data.devices: - entities.append( - MyUplinkDeviceUpdate( - coordinator=coordinator, - device_id=device_id, - entity_description=UPDATE_DESCRIPTION, - unique_id_suffix="upd", - ) + async_add_entities( + MyUplinkDeviceUpdate( + coordinator=coordinator, + device_id=device_id, + entity_description=UPDATE_DESCRIPTION, + unique_id_suffix="upd", ) - - async_add_entities(entities) + for device_id in coordinator.data.devices + ) class MyUplinkDeviceUpdate(MyUplinkEntity, UpdateEntity): diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index e1fbb95791e..3d3baf1307c 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1399,15 +1399,13 @@ async def test_missing_discover_abbreviations( continue with open(fil, encoding="utf-8") as file: matches = re.findall(regex, file.read()) - for match in matches: - if ( - match[1] not in ABBREVIATIONS.values() - and match[1] not in DEVICE_ABBREVIATIONS.values() - and match[0] not in ABBREVIATIONS_WHITE_LIST - ): - missing.append( - f"{fil}: no abbreviation for {match[1]} ({match[0]})" - ) + missing.extend( + f"{fil}: no abbreviation for {match[1]} ({match[0]})" + for match in matches + if match[1] not in ABBREVIATIONS.values() + and match[1] not in DEVICE_ABBREVIATIONS.values() + and match[0] not in ABBREVIATIONS_WHITE_LIST + ) assert not missing From e6a692f354866dbffa35daa0c36d224fa7c4917e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 21:51:38 +0100 Subject: [PATCH 0911/1691] Improve lists in integrations [N-O] (#113231) --- homeassistant/components/nam/sensor.py | 11 +++-- homeassistant/components/neato/camera.py | 9 +++-- homeassistant/components/neato/sensor.py | 4 +- homeassistant/components/neato/switch.py | 10 ++--- homeassistant/components/neato/vacuum.py | 7 ++-- .../nederlandse_spoorwegen/sensor.py | 3 +- homeassistant/components/nest/camera.py | 14 +++---- homeassistant/components/nest/climate.py | 25 ++++++------ homeassistant/components/nest/config_flow.py | 9 +++-- homeassistant/components/nest/media_source.py | 7 ++-- .../components/netatmo/device_trigger.py | 24 +++++------ homeassistant/components/netgear/sensor.py | 40 ++++++------------- homeassistant/components/netgear/switch.py | 11 ++--- homeassistant/components/network/__init__.py | 6 +-- .../components/nextdns/binary_sensor.py | 8 ++-- homeassistant/components/nextdns/switch.py | 8 ++-- .../components/nina/binary_sensor.py | 14 +++---- homeassistant/components/obihai/sensor.py | 16 ++++---- homeassistant/components/octoprint/sensor.py | 18 ++++----- .../openalpr_cloud/image_processing.py | 13 +++--- .../components/opencv/image_processing.py | 19 ++++----- .../components/opentherm_gw/binary_sensor.py | 28 +++++-------- .../components/opentherm_gw/sensor.py | 34 ++++++---------- homeassistant/components/opower/sensor.py | 18 ++++----- .../components/osoenergy/water_heater.py | 8 ++-- .../components/overkiz/alarm_control_panel.py | 26 ++++++------ .../components/overkiz/binary_sensor.py | 18 ++++----- homeassistant/components/overkiz/button.py | 18 ++++----- homeassistant/components/overkiz/number.py | 18 ++++----- homeassistant/components/overkiz/select.py | 18 ++++----- homeassistant/components/overkiz/sensor.py | 18 ++++----- homeassistant/components/overkiz/switch.py | 26 ++++++------ .../components/owntracks/__init__.py | 24 +++++------ .../components/netatmo/test_device_trigger.py | 24 +++++------ tests/components/onewire/__init__.py | 10 +++-- 35 files changed, 254 insertions(+), 310 deletions(-) diff --git a/homeassistant/components/nam/sensor.py b/homeassistant/components/nam/sensor.py index 848c3396668..a098f48e434 100644 --- a/homeassistant/components/nam/sensor.py +++ b/homeassistant/components/nam/sensor.py @@ -367,12 +367,11 @@ async def async_setup_entry( ) ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) - sensors: list[NAMSensor] = [] - for description in SENSORS: - if getattr(coordinator.data, description.key) is not None: - sensors.append(NAMSensor(coordinator, description)) - - async_add_entities(sensors, False) + async_add_entities( + NAMSensor(coordinator, description) + for description in SENSORS + if getattr(coordinator.data, description.key) is not None + ) class NAMSensor(CoordinatorEntity[NAMDataUpdateCoordinator], SensorEntity): diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 7aeadb0698d..e4d5f81f33a 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -29,12 +29,13 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Neato camera with config entry.""" - dev = [] neato: NeatoHub = hass.data[NEATO_LOGIN] mapdata: dict[str, Any] | None = hass.data.get(NEATO_MAP_DATA) - for robot in hass.data[NEATO_ROBOTS]: - if "maps" in robot.traits: - dev.append(NeatoCleaningMap(neato, robot, mapdata)) + dev = [ + NeatoCleaningMap(neato, robot, mapdata) + for robot in hass.data[NEATO_ROBOTS] + if "maps" in robot.traits + ] if not dev: return diff --git a/homeassistant/components/neato/sensor.py b/homeassistant/components/neato/sensor.py index f781c49457e..c247cc48493 100644 --- a/homeassistant/components/neato/sensor.py +++ b/homeassistant/components/neato/sensor.py @@ -30,10 +30,8 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Neato sensor using config entry.""" - dev = [] neato: NeatoHub = hass.data[NEATO_LOGIN] - for robot in hass.data[NEATO_ROBOTS]: - dev.append(NeatoSensor(neato, robot)) + dev = [NeatoSensor(neato, robot) for robot in hass.data[NEATO_ROBOTS]] if not dev: return diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py index 34a0b13a870..25da1c41df1 100644 --- a/homeassistant/components/neato/switch.py +++ b/homeassistant/components/neato/switch.py @@ -32,12 +32,12 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Neato switch with config entry.""" - dev = [] neato: NeatoHub = hass.data[NEATO_LOGIN] - - for robot in hass.data[NEATO_ROBOTS]: - for type_name in SWITCH_TYPES: - dev.append(NeatoConnectedSwitch(neato, robot, type_name)) + dev = [ + NeatoConnectedSwitch(neato, robot, type_name) + for robot in hass.data[NEATO_ROBOTS] + for type_name in SWITCH_TYPES + ] if not dev: return diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 6f1c7c2f5d7..b750b121f58 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -64,12 +64,13 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Neato vacuum with config entry.""" - dev = [] neato: NeatoHub = hass.data[NEATO_LOGIN] mapdata: dict[str, Any] | None = hass.data.get(NEATO_MAP_DATA) persistent_maps: dict[str, Any] | None = hass.data.get(NEATO_PERSISTENT_MAPS) - for robot in hass.data[NEATO_ROBOTS]: - dev.append(NeatoConnectedVacuum(neato, robot, mapdata, persistent_maps)) + dev = [ + NeatoConnectedVacuum(neato, robot, mapdata, persistent_maps) + for robot in hass.data[NEATO_ROBOTS] + ] if not dev: return diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index 1e8a7c30b9d..d99e4c62646 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -135,8 +135,7 @@ class NSDepartureSensor(SensorEntity): if self._trips[0].trip_parts: route = [self._trips[0].departure] - for k in self._trips[0].trip_parts: - route.append(k.destination) + route.extend(k.destination for k in self._trips[0].trip_parts) # Static attributes attributes = { diff --git a/homeassistant/components/nest/camera.py b/homeassistant/components/nest/camera.py index f0b3ccd477d..e87c9ccbbe7 100644 --- a/homeassistant/components/nest/camera.py +++ b/homeassistant/components/nest/camera.py @@ -48,14 +48,12 @@ async def async_setup_entry( device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][ DATA_DEVICE_MANAGER ] - entities = [] - for device in device_manager.devices.values(): - if ( - CameraImageTrait.NAME in device.traits - or CameraLiveStreamTrait.NAME in device.traits - ): - entities.append(NestCamera(device)) - async_add_entities(entities) + async_add_entities( + NestCamera(device) + for device in device_manager.devices.values() + if CameraImageTrait.NAME in device.traits + or CameraLiveStreamTrait.NAME in device.traits + ) class NestCamera(Camera): diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index 5e9dcc01e97..411389f9fb2 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -86,11 +86,12 @@ async def async_setup_entry( device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][ DATA_DEVICE_MANAGER ] - entities = [] - for device in device_manager.devices.values(): - if ThermostatHvacTrait.NAME in device.traits: - entities.append(ThermostatEntity(device)) - async_add_entities(entities) + + async_add_entities( + ThermostatEntity(device) + for device in device_manager.devices.values() + if ThermostatHvacTrait.NAME in device.traits + ) class ThermostatEntity(ClimateEntity): @@ -217,13 +218,13 @@ class ThermostatEntity(ClimateEntity): @property def preset_modes(self) -> list[str]: """Return the available presets.""" - modes = [] - if ThermostatEcoTrait.NAME in self._device.traits: - trait = self._device.traits[ThermostatEcoTrait.NAME] - for mode in trait.available_modes: - if mode in PRESET_MODE_MAP: - modes.append(PRESET_MODE_MAP[mode]) - return modes + if ThermostatEcoTrait.NAME not in self._device.traits: + return [] + return [ + PRESET_MODE_MAP[mode] + for mode in self._device.traits[ThermostatEcoTrait.NAME].available_modes + if mode in PRESET_MODE_MAP + ] @property def fan_mode(self) -> str: diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index e3854b06038..092d6fe1f22 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -71,10 +71,11 @@ def _generate_subscription_id(cloud_project_id: str) -> str: def generate_config_title(structures: Iterable[Structure]) -> str | None: """Pick a user friendly config title based on the Google Home name(s).""" - names: list[str] = [] - for structure in structures: - if (trait := structure.traits.get(InfoTrait.NAME)) and trait.custom_name: - names.append(trait.custom_name) + names: list[str] = [ + trait.custom_name + for structure in structures + if (trait := structure.traits.get(InfoTrait.NAME)) and trait.custom_name + ] if not names: return None return ", ".join(names) diff --git a/homeassistant/components/nest/media_source.py b/homeassistant/components/nest/media_source.py index ba2faaeaae5..d48006c449d 100644 --- a/homeassistant/components/nest/media_source.py +++ b/homeassistant/components/nest/media_source.py @@ -490,9 +490,10 @@ def _browse_clip_preview( event_id: MediaId, device: Device, event: ClipPreviewSession ) -> BrowseMediaSource: """Build a BrowseMediaSource for a specific clip preview event.""" - types = [] - for event_type in event.event_types: - types.append(MEDIA_SOURCE_EVENT_TITLE_MAP.get(event_type, "Event")) + types = [ + MEDIA_SOURCE_EVENT_TITLE_MAP.get(event_type, "Event") + for event_type in event.event_types + ] return BrowseMediaSource( domain=DOMAIN, identifier=event_id.identifier, diff --git a/homeassistant/components/netatmo/device_trigger.py b/homeassistant/components/netatmo/device_trigger.py index efc2978a43b..686df2ef2cb 100644 --- a/homeassistant/components/netatmo/device_trigger.py +++ b/homeassistant/components/netatmo/device_trigger.py @@ -96,7 +96,7 @@ async def async_get_triggers( """List device triggers for Netatmo devices.""" registry = er.async_get(hass) device_registry = dr.async_get(hass) - triggers = [] + triggers: list[dict[str, str]] = [] for entry in er.async_entries_for_device(registry, device_id): if ( @@ -106,17 +106,17 @@ async def async_get_triggers( for trigger in DEVICES.get(device.model, []): if trigger in SUBTYPES: - for subtype in SUBTYPES[trigger]: - triggers.append( - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_ENTITY_ID: entry.id, - CONF_TYPE: trigger, - CONF_SUBTYPE: subtype, - } - ) + triggers.extend( + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.id, + CONF_TYPE: trigger, + CONF_SUBTYPE: subtype, + } + for subtype in SUBTYPES[trigger] + ) else: triggers.append( { diff --git a/homeassistant/components/netgear/sensor.py b/homeassistant/components/netgear/sensor.py index d569a50d7e1..72087dd28db 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -280,30 +280,16 @@ async def async_setup_entry( coordinator_utilization = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_UTIL] coordinator_link = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR_LINK] - # Router entities - router_entities = [] - - for description in SENSOR_TRAFFIC_TYPES: - router_entities.append( - NetgearRouterSensorEntity(coordinator_traffic, router, description) + async_add_entities( + NetgearRouterSensorEntity(coordinator, router, description) + for (coordinator, descriptions) in ( + (coordinator_traffic, SENSOR_TRAFFIC_TYPES), + (coordinator_speed, SENSOR_SPEED_TYPES), + (coordinator_utilization, SENSOR_UTILIZATION), + (coordinator_link, SENSOR_LINK_TYPES), ) - - for description in SENSOR_SPEED_TYPES: - router_entities.append( - NetgearRouterSensorEntity(coordinator_speed, router, description) - ) - - for description in SENSOR_UTILIZATION: - router_entities.append( - NetgearRouterSensorEntity(coordinator_utilization, router, description) - ) - - for description in SENSOR_LINK_TYPES: - router_entities.append( - NetgearRouterSensorEntity(coordinator_link, router, description) - ) - - async_add_entities(router_entities) + for description in descriptions + ) # Entities per network device tracked = set() @@ -317,17 +303,15 @@ async def async_setup_entry( if not coordinator.data: return - new_entities = [] + new_entities: list[NetgearSensorEntity] = [] for mac, device in router.devices.items(): if mac in tracked: continue new_entities.extend( - [ - NetgearSensorEntity(coordinator, router, device, attribute) - for attribute in sensors - ] + NetgearSensorEntity(coordinator, router, device, attribute) + for attribute in sensors ) tracked.add(mac) diff --git a/homeassistant/components/netgear/switch.py b/homeassistant/components/netgear/switch.py index e39d4030f3f..85f214d784a 100644 --- a/homeassistant/components/netgear/switch.py +++ b/homeassistant/components/netgear/switch.py @@ -104,13 +104,10 @@ async def async_setup_entry( """Set up switches for Netgear component.""" router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER] - # Router entities - router_entities = [] - - for description in ROUTER_SWITCH_TYPES: - router_entities.append(NetgearRouterSwitchEntity(router, description)) - - async_add_entities(router_entities) + async_add_entities( + NetgearRouterSwitchEntity(router, description) + for description in ROUTER_SWITCH_TYPES + ) # Entities per network device coordinator = hass.data[DOMAIN][entry.entry_id][KEY_COORDINATOR] diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index 0ec6f71814f..517ab7e7246 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -131,10 +131,8 @@ async def async_get_announce_addresses(hass: HomeAssistant) -> list[str]: for adapter in adapters: if not adapter["enabled"]: continue - for ips in adapter["ipv4"]: - addresses.append(str(IPv4Address(ips["address"]))) - for ips in adapter["ipv6"]: - addresses.append(str(IPv6Address(ips["address"]))) + addresses.extend(str(IPv4Address(ips["address"])) for ips in adapter["ipv4"]) + addresses.extend(str(IPv6Address(ips["address"])) for ips in adapter["ipv6"]) # Puts the default IPv4 address first in the list to preserve compatibility, # because some mDNS implementations ignores anything but the first announced diff --git a/homeassistant/components/nextdns/binary_sensor.py b/homeassistant/components/nextdns/binary_sensor.py index bf230b937c2..f6860586808 100644 --- a/homeassistant/components/nextdns/binary_sensor.py +++ b/homeassistant/components/nextdns/binary_sensor.py @@ -68,11 +68,9 @@ async def async_setup_entry( ATTR_CONNECTION ] - sensors: list[NextDnsBinarySensor] = [] - for description in SENSORS: - sensors.append(NextDnsBinarySensor(coordinator, description)) - - async_add_entities(sensors) + async_add_entities( + NextDnsBinarySensor(coordinator, description) for description in SENSORS + ) class NextDnsBinarySensor( diff --git a/homeassistant/components/nextdns/switch.py b/homeassistant/components/nextdns/switch.py index f9a5dbde4b7..81bf8b4e8c6 100644 --- a/homeassistant/components/nextdns/switch.py +++ b/homeassistant/components/nextdns/switch.py @@ -538,11 +538,9 @@ async def async_setup_entry( ATTR_SETTINGS ] - switches: list[NextDnsSwitch] = [] - for description in SWITCHES: - switches.append(NextDnsSwitch(coordinator, description)) - - async_add_entities(switches) + async_add_entities( + NextDnsSwitch(coordinator, description) for description in SWITCHES + ) class NextDnsSwitch(CoordinatorEntity[NextDnsSettingsUpdateCoordinator], SwitchEntity): diff --git a/homeassistant/components/nina/binary_sensor.py b/homeassistant/components/nina/binary_sensor.py index bcc1eb2a4b6..8104a52683c 100644 --- a/homeassistant/components/nina/binary_sensor.py +++ b/homeassistant/components/nina/binary_sensor.py @@ -44,15 +44,11 @@ async def async_setup_entry( regions: dict[str, str] = config_entry.data[CONF_REGIONS] message_slots: int = config_entry.data[CONF_MESSAGE_SLOTS] - entities: list[NINAMessage] = [] - - for ent in coordinator.data: - for i in range(0, message_slots): - entities.append( - NINAMessage(coordinator, ent, regions[ent], i + 1, config_entry) - ) - - async_add_entities(entities) + async_add_entities( + NINAMessage(coordinator, ent, regions[ent], i + 1, config_entry) + for ent in coordinator.data + for i in range(0, message_slots) + ) class NINAMessage(CoordinatorEntity[NINADataUpdateCoordinator], BinarySensorEntity): diff --git a/homeassistant/components/obihai/sensor.py b/homeassistant/components/obihai/sensor.py index cc70296028f..91920b4c32d 100644 --- a/homeassistant/components/obihai/sensor.py +++ b/homeassistant/components/obihai/sensor.py @@ -24,16 +24,16 @@ async def async_setup_entry( requester: ObihaiConnection = hass.data[DOMAIN][entry.entry_id] - sensors = [] - for key in requester.services: - sensors.append(ObihaiServiceSensors(requester, key)) + sensors = [ObihaiServiceSensors(requester, key) for key in requester.services] + + sensors.extend( + ObihaiServiceSensors(requester, key) for key in requester.call_direction + ) if requester.line_services is not None: - for key in requester.line_services: - sensors.append(ObihaiServiceSensors(requester, key)) - - for key in requester.call_direction: - sensors.append(ObihaiServiceSensors(requester, key)) + sensors.extend( + ObihaiServiceSensors(requester, key) for key in requester.line_services + ) async_add_entities(sensors, update_before_add=True) diff --git a/homeassistant/components/octoprint/sensor.py b/homeassistant/components/octoprint/sensor.py index eb54cadd023..fb5f292d669 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -55,7 +55,7 @@ async def async_setup_entry( if not coordinator.data["printer"]: return - new_tools = [] + new_tools: list[OctoPrintTemperatureSensor] = [] for tool in [ tool for tool in coordinator.data["printer"].temperatures @@ -63,15 +63,15 @@ async def async_setup_entry( ]: assert device_id is not None known_tools.add(tool.name) - for temp_type in ("actual", "target"): - new_tools.append( - OctoPrintTemperatureSensor( - coordinator, - tool.name, - temp_type, - device_id, - ) + new_tools.extend( + OctoPrintTemperatureSensor( + coordinator, + tool.name, + temp_type, + device_id, ) + for temp_type in ("actual", "target") + ) async_add_entities(new_tools) config_entry.async_on_unload(coordinator.async_add_listener(async_add_tool_sensors)) diff --git a/homeassistant/components/openalpr_cloud/image_processing.py b/homeassistant/components/openalpr_cloud/image_processing.py index 525edc7da4b..2a8fe328c7d 100644 --- a/homeassistant/components/openalpr_cloud/image_processing.py +++ b/homeassistant/components/openalpr_cloud/image_processing.py @@ -80,15 +80,12 @@ async def async_setup_platform( "country": config[CONF_REGION], } - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - OpenAlprCloudEntity( - camera[CONF_ENTITY_ID], params, confidence, camera.get(CONF_NAME) - ) + async_add_entities( + OpenAlprCloudEntity( + camera[CONF_ENTITY_ID], params, confidence, camera.get(CONF_NAME) ) - - async_add_entities(entities) + for camera in config[CONF_SOURCE] + ) class ImageProcessingAlprEntity(ImageProcessingEntity): diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py index 760c473243d..e3dbe10a1a0 100644 --- a/homeassistant/components/opencv/image_processing.py +++ b/homeassistant/components/opencv/image_processing.py @@ -109,23 +109,20 @@ def setup_platform( ) return - entities = [] if CONF_CLASSIFIER not in config: dest_path = hass.config.path(DEFAULT_CLASSIFIER_PATH) _get_default_classifier(dest_path) config[CONF_CLASSIFIER] = {"Face": dest_path} - for camera in config[CONF_SOURCE]: - entities.append( - OpenCVImageProcessor( - hass, - camera[CONF_ENTITY_ID], - camera.get(CONF_NAME), - config[CONF_CLASSIFIER], - ) + add_entities( + OpenCVImageProcessor( + hass, + camera[CONF_ENTITY_ID], + camera.get(CONF_NAME), + config[CONF_CLASSIFIER], ) - - add_entities(entities) + for camera in config[CONF_SOURCE] + ) class OpenCVImageProcessor(ImageProcessingEntity): diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index de949dafbaf..ad8d09afa89 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -28,25 +28,19 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the OpenTherm Gateway binary sensors.""" - sensors = [] gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] - for var, info in BINARY_SENSOR_INFO.items(): - device_class = info[0] - friendly_name_format = info[1] - status_sources = info[2] - for source in status_sources: - sensors.append( - OpenThermBinarySensor( - gw_dev, - var, - source, - device_class, - friendly_name_format, - ) - ) - - async_add_entities(sensors) + async_add_entities( + OpenThermBinarySensor( + gw_dev, + var, + source, + info[0], + info[1], + ) + for var, info in BINARY_SENSOR_INFO.items() + for source in info[2] + ) class OpenThermBinarySensor(BinarySensorEntity): diff --git a/homeassistant/components/opentherm_gw/sensor.py b/homeassistant/components/opentherm_gw/sensor.py index bf393794450..9171292c21b 100644 --- a/homeassistant/components/opentherm_gw/sensor.py +++ b/homeassistant/components/opentherm_gw/sensor.py @@ -23,29 +23,21 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the OpenTherm Gateway sensors.""" - sensors = [] gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]] - for var, info in SENSOR_INFO.items(): - device_class = info[0] - unit = info[1] - friendly_name_format = info[2] - suggested_display_precision = info[3] - status_sources = info[4] - for source in status_sources: - sensors.append( - OpenThermSensor( - gw_dev, - var, - source, - device_class, - unit, - friendly_name_format, - suggested_display_precision, - ) - ) - - async_add_entities(sensors) + async_add_entities( + OpenThermSensor( + gw_dev, + var, + source, + info[0], + info[1], + info[2], + info[3], + ) + for var, info in SENSOR_INFO.items() + for source in info[4] + ) class OpenThermSensor(SensorEntity): diff --git a/homeassistant/components/opower/sensor.py b/homeassistant/components/opower/sensor.py index 2d8f1b5ded5..9f467dce1c6 100644 --- a/homeassistant/components/opower/sensor.py +++ b/homeassistant/components/opower/sensor.py @@ -187,16 +187,16 @@ async def async_setup_entry( and forecast.unit_of_measure in [UnitOfMeasure.THERM, UnitOfMeasure.CCF] ): sensors = GAS_SENSORS - for sensor in sensors: - entities.append( - OpowerSensor( - coordinator, - sensor, - forecast.account.utility_account_id, - device, - device_id, - ) + entities.extend( + OpowerSensor( + coordinator, + sensor, + forecast.account.utility_account_id, + device, + device_id, ) + for sensor in sensors + ) async_add_entities(entities) diff --git a/homeassistant/components/osoenergy/water_heater.py b/homeassistant/components/osoenergy/water_heater.py index 146bdaa3a60..eaf54a9f9a4 100644 --- a/homeassistant/components/osoenergy/water_heater.py +++ b/homeassistant/components/osoenergy/water_heater.py @@ -45,11 +45,9 @@ async def async_setup_entry( """Set up OSO Energy heater based on a config entry.""" osoenergy = hass.data[DOMAIN][entry.entry_id] devices = osoenergy.session.device_list.get("water_heater") - entities = [] - if devices: - for dev in devices: - entities.append(OSOEnergyWaterHeater(osoenergy, dev)) - async_add_entities(entities, True) + if not devices: + return + async_add_entities((OSOEnergyWaterHeater(osoenergy, dev) for dev in devices), True) class OSOEnergyWaterHeater( diff --git a/homeassistant/components/overkiz/alarm_control_panel.py b/homeassistant/components/overkiz/alarm_control_panel.py index 1f088874d43..72c99982a1b 100644 --- a/homeassistant/components/overkiz/alarm_control_panel.py +++ b/homeassistant/components/overkiz/alarm_control_panel.py @@ -221,21 +221,19 @@ async def async_setup_entry( ) -> None: """Set up the Overkiz alarm control panel from a config entry.""" data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] - entities: list[OverkizAlarmControlPanel] = [] - for device in data.platforms[Platform.ALARM_CONTROL_PANEL]: - if description := SUPPORTED_DEVICES.get(device.widget) or SUPPORTED_DEVICES.get( - device.ui_class - ): - entities.append( - OverkizAlarmControlPanel( - device.device_url, - data.coordinator, - description, - ) - ) - - async_add_entities(entities) + async_add_entities( + OverkizAlarmControlPanel( + device.device_url, + data.coordinator, + description, + ) + for device in data.platforms[Platform.ALARM_CONTROL_PANEL] + if ( + description := SUPPORTED_DEVICES.get(device.widget) + or SUPPORTED_DEVICES.get(device.ui_class) + ) + ) class OverkizAlarmControlPanel(OverkizDescriptiveEntity, AlarmControlPanelEntity): diff --git a/homeassistant/components/overkiz/binary_sensor.py b/homeassistant/components/overkiz/binary_sensor.py index b4365a66e06..871a70b3e0a 100644 --- a/homeassistant/components/overkiz/binary_sensor.py +++ b/homeassistant/components/overkiz/binary_sensor.py @@ -128,15 +128,15 @@ async def async_setup_entry( ): continue - for state in device.definition.states: - if description := SUPPORTED_STATES.get(state.qualified_name): - entities.append( - OverkizBinarySensor( - device.device_url, - data.coordinator, - description, - ) - ) + entities.extend( + OverkizBinarySensor( + device.device_url, + data.coordinator, + description, + ) + for state in device.definition.states + if (description := SUPPORTED_STATES.get(state.qualified_name)) + ) async_add_entities(entities) diff --git a/homeassistant/components/overkiz/button.py b/homeassistant/components/overkiz/button.py index 3e0321b5055..5a1116aeeb5 100644 --- a/homeassistant/components/overkiz/button.py +++ b/homeassistant/components/overkiz/button.py @@ -95,15 +95,15 @@ async def async_setup_entry( ): continue - for command in device.definition.commands: - if description := SUPPORTED_COMMANDS.get(command.command_name): - entities.append( - OverkizButton( - device.device_url, - data.coordinator, - description, - ) - ) + entities.extend( + OverkizButton( + device.device_url, + data.coordinator, + description, + ) + for command in device.definition.commands + if (description := SUPPORTED_COMMANDS.get(command.command_name)) + ) async_add_entities(entities) diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index e9664527335..76a3e5d932b 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -183,15 +183,15 @@ async def async_setup_entry( ): continue - for state in device.definition.states: - if description := SUPPORTED_STATES.get(state.qualified_name): - entities.append( - OverkizNumber( - device.device_url, - data.coordinator, - description, - ) - ) + entities.extend( + OverkizNumber( + device.device_url, + data.coordinator, + description, + ) + for state in device.definition.states + if (description := SUPPORTED_STATES.get(state.qualified_name)) + ) async_add_entities(entities) diff --git a/homeassistant/components/overkiz/select.py b/homeassistant/components/overkiz/select.py index 155c72a7f7a..83cdc9c4f2b 100644 --- a/homeassistant/components/overkiz/select.py +++ b/homeassistant/components/overkiz/select.py @@ -143,15 +143,15 @@ async def async_setup_entry( ): continue - for state in device.definition.states: - if description := SUPPORTED_STATES.get(state.qualified_name): - entities.append( - OverkizSelect( - device.device_url, - data.coordinator, - description, - ) - ) + entities.extend( + OverkizSelect( + device.device_url, + data.coordinator, + description, + ) + for state in device.definition.states + if (description := SUPPORTED_STATES.get(state.qualified_name)) + ) async_add_entities(entities) diff --git a/homeassistant/components/overkiz/sensor.py b/homeassistant/components/overkiz/sensor.py index b8693784395..2b0a222f96f 100644 --- a/homeassistant/components/overkiz/sensor.py +++ b/homeassistant/components/overkiz/sensor.py @@ -459,15 +459,15 @@ async def async_setup_entry( ): continue - for state in device.definition.states: - if description := SUPPORTED_STATES.get(state.qualified_name): - entities.append( - OverkizStateSensor( - device.device_url, - data.coordinator, - description, - ) - ) + entities.extend( + OverkizStateSensor( + device.device_url, + data.coordinator, + description, + ) + for state in device.definition.states + if (description := SUPPORTED_STATES.get(state.qualified_name)) + ) async_add_entities(entities) diff --git a/homeassistant/components/overkiz/switch.py b/homeassistant/components/overkiz/switch.py index 5d1fe0493ac..ac3ea351559 100644 --- a/homeassistant/components/overkiz/switch.py +++ b/homeassistant/components/overkiz/switch.py @@ -116,21 +116,19 @@ async def async_setup_entry( ) -> None: """Set up the Overkiz switch from a config entry.""" data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] - entities: list[OverkizSwitch] = [] - for device in data.platforms[Platform.SWITCH]: - if description := SUPPORTED_DEVICES.get(device.widget) or SUPPORTED_DEVICES.get( - device.ui_class - ): - entities.append( - OverkizSwitch( - device.device_url, - data.coordinator, - description, - ) - ) - - async_add_entities(entities) + async_add_entities( + OverkizSwitch( + device.device_url, + data.coordinator, + description, + ) + for device in data.platforms[Platform.SWITCH] + if ( + description := SUPPORTED_DEVICES.get(device.widget) + or SUPPORTED_DEVICES.get(device.ui_class) + ) + ) class OverkizSwitch(OverkizDescriptiveEntity, SwitchEntity): diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index dbc4df293c0..ffc50065c5a 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -187,19 +187,17 @@ async def handle_webhook(hass, webhook_id, request): async_dispatcher_send(hass, DOMAIN, hass, context, message) - response = [] - - for person in hass.states.async_all("person"): - if "latitude" in person.attributes and "longitude" in person.attributes: - response.append( - { - "_type": "location", - "lat": person.attributes["latitude"], - "lon": person.attributes["longitude"], - "tid": "".join(p[0] for p in person.name.split(" ")[:2]), - "tst": int(person.last_updated.timestamp()), - } - ) + response = [ + { + "_type": "location", + "lat": person.attributes["latitude"], + "lon": person.attributes["longitude"], + "tid": "".join(p[0] for p in person.name.split(" ")[:2]), + "tst": int(person.last_updated.timestamp()), + } + for person in hass.states.async_all("person") + if "latitude" in person.attributes and "longitude" in person.attributes + ] if message["_type"] == "encrypted" and context.secret: return json_response( diff --git a/tests/components/netatmo/test_device_trigger.py b/tests/components/netatmo/test_device_trigger.py index 65d7274cdc8..f7c31d7681c 100644 --- a/tests/components/netatmo/test_device_trigger.py +++ b/tests/components/netatmo/test_device_trigger.py @@ -63,18 +63,18 @@ async def test_get_triggers( expected_triggers = [] for event_type in event_types: if event_type in SUBTYPES: - for subtype in SUBTYPES[event_type]: - expected_triggers.append( - { - "platform": "device", - "domain": NETATMO_DOMAIN, - "type": event_type, - "subtype": subtype, - "device_id": device_entry.id, - "entity_id": entity_entry.id, - "metadata": {"secondary": False}, - } - ) + expected_triggers.extend( + { + "platform": "device", + "domain": NETATMO_DOMAIN, + "type": event_type, + "subtype": subtype, + "device_id": device_entry.id, + "entity_id": entity_entry.id, + "metadata": {"secondary": False}, + } + for subtype in SUBTYPES[event_type] + ) else: expected_triggers.append( { diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py index 2929ed3ed50..7a9dc01117d 100644 --- a/tests/components/onewire/__init__.py +++ b/tests/components/onewire/__init__.py @@ -102,7 +102,9 @@ def _setup_owproxy_mock_device_reads( device_sensors = mock_device.get(platform, []) if platform is Platform.SENSOR and device_id.startswith("12"): # We need to check if there is TAI8570 plugged in - for expected_sensor in device_sensors: - sub_read_side_effect.append(expected_sensor[ATTR_INJECT_READS]) - for expected_sensor in device_sensors: - sub_read_side_effect.append(expected_sensor[ATTR_INJECT_READS]) + sub_read_side_effect.extend( + expected_sensor[ATTR_INJECT_READS] for expected_sensor in device_sensors + ) + sub_read_side_effect.extend( + expected_sensor[ATTR_INJECT_READS] for expected_sensor in device_sensors + ) From 77917506bb184611c7181a40fbf5c266c9ab2bac Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 21:55:00 +0100 Subject: [PATCH 0912/1691] Improve lists in integrations [R-S] (#113233) * Improve lists in integrations [R-S] * Fix * Fix --- homeassistant/components/rachio/device.py | 9 +- homeassistant/components/rachio/switch.py | 11 +- .../components/raincloud/binary_sensor.py | 6 +- homeassistant/components/raincloud/sensor.py | 6 +- homeassistant/components/raincloud/switch.py | 15 +- .../components/recorder/migration.py | 23 +- .../components/reolink/media_source.py | 27 +- .../components/rfxtrx/device_action.py | 26 +- .../components/rfxtrx/device_trigger.py | 25 +- homeassistant/components/rfxtrx/sensor.py | 19 +- homeassistant/components/ring/diagnostics.py | 9 +- homeassistant/components/ring/light.py | 11 +- homeassistant/components/ring/siren.py | 8 +- homeassistant/components/ring/switch.py | 11 +- homeassistant/components/roborock/__init__.py | 10 +- homeassistant/components/roku/select.py | 14 +- homeassistant/components/roomba/braava.py | 10 +- .../components/russound_rnet/media_player.py | 4 +- homeassistant/components/saj/sensor.py | 12 +- .../components/schlage/binary_sensor.py | 20 +- homeassistant/components/schlage/switch.py | 20 +- .../components/screenlogic/binary_sensor.py | 21 +- .../components/screenlogic/climate.py | 23 +- .../components/screenlogic/sensor.py | 9 +- homeassistant/components/sense/sensor.py | 6 +- homeassistant/components/sensibo/climate.py | 4 +- .../seven_segments/image_processing.py | 13 +- homeassistant/components/shelly/utils.py | 14 +- homeassistant/components/sigfox/sensor.py | 8 +- .../components/simplisafe/binary_sensor.py | 6 +- homeassistant/components/simplisafe/lock.py | 7 +- homeassistant/components/simplisafe/sensor.py | 10 +- homeassistant/components/sleepiq/number.py | 50 ++-- homeassistant/components/sma/sensor.py | 21 +- homeassistant/components/smappee/switch.py | 22 +- homeassistant/components/smartthings/fan.py | 6 +- homeassistant/components/sms/sensor.py | 17 +- .../components/speedtestdotnet/coordinator.py | 7 +- .../components/squeezebox/media_player.py | 16 +- homeassistant/components/starline/button.py | 12 +- .../components/starline/device_tracker.py | 10 +- .../components/starlingbank/sensor.py | 12 +- .../components/subaru/device_tracker.py | 10 +- homeassistant/components/surepetcare/lock.py | 19 +- .../swiss_hydrological_data/sensor.py | 13 +- homeassistant/components/syncthru/sensor.py | 18 +- .../components/synology_dsm/media_source.py | 24 +- .../components/system_bridge/binary_sensor.py | 19 +- .../components/system_bridge/media_source.py | 31 +- .../components/system_bridge/sensor.py | 276 +++++++++--------- .../components/systemmonitor/binary_sensor.py | 25 +- tests/components/recorder/test_history.py | 4 +- .../recorder/test_history_db_schema_30.py | 4 +- .../recorder/test_history_db_schema_32.py | 4 +- .../recorder/test_migration_from_schema_32.py | 49 ++-- tests/components/reolink/test_config_flow.py | 23 +- tests/components/statistics/test_sensor.py | 21 +- tests/components/steam_online/__init__.py | 8 +- tests/components/stream/test_hls.py | 8 +- tests/components/stream/test_ll_hls.py | 8 +- 60 files changed, 543 insertions(+), 611 deletions(-) diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py index a7d1c991453..dea91fcc6fd 100644 --- a/homeassistant/components/rachio/device.py +++ b/homeassistant/components/rachio/device.py @@ -245,10 +245,11 @@ class RachioIro: _deinit_webhooks(None) # Choose which events to listen for and get their IDs - event_types = [] - for event_type in self.rachio.notification.get_webhook_event_type()[1]: - if event_type[KEY_NAME] in LISTEN_EVENT_TYPES: - event_types.append({"id": event_type[KEY_ID]}) + event_types = [ + {"id": event_type[KEY_ID]} + for event_type in self.rachio.notification.get_webhook_event_type()[1] + if event_type[KEY_NAME] in LISTEN_EVENT_TYPES + ] # Register to listen to these events from the device url = self.rachio.webhook_url diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index ebe96152d70..8b8d10248e0 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -169,10 +169,13 @@ def _create_entities(hass: HomeAssistant, config_entry: ConfigEntry) -> list[Ent schedules = controller.list_schedules() flex_schedules = controller.list_flex_schedules() current_schedule = controller.current_schedule - for zone in zones: - entities.append(RachioZone(person, controller, zone, current_schedule)) - for sched in schedules + flex_schedules: - entities.append(RachioSchedule(person, controller, sched, current_schedule)) + entities.extend( + RachioZone(person, controller, zone, current_schedule) for zone in zones + ) + entities.extend( + RachioSchedule(person, controller, schedule, current_schedule) + for schedule in schedules + flex_schedules + ) return entities diff --git a/homeassistant/components/raincloud/binary_sensor.py b/homeassistant/components/raincloud/binary_sensor.py index fb949f69d91..8530323dad1 100644 --- a/homeassistant/components/raincloud/binary_sensor.py +++ b/homeassistant/components/raincloud/binary_sensor.py @@ -45,8 +45,10 @@ def setup_platform( else: # create a sensor for each zone managed by faucet - for zone in raincloud.controller.faucet.zones: - sensors.append(RainCloudBinarySensor(zone, sensor_type)) + sensors.extend( + RainCloudBinarySensor(zone, sensor_type) + for zone in raincloud.controller.faucet.zones + ) add_entities(sensors, True) diff --git a/homeassistant/components/raincloud/sensor.py b/homeassistant/components/raincloud/sensor.py index 3da30609f63..34cd3f213ed 100644 --- a/homeassistant/components/raincloud/sensor.py +++ b/homeassistant/components/raincloud/sensor.py @@ -48,8 +48,10 @@ def setup_platform( sensors.append(RainCloudSensor(raincloud.controller.faucet, sensor_type)) else: # create a sensor for each zone managed by a faucet - for zone in raincloud.controller.faucet.zones: - sensors.append(RainCloudSensor(zone, sensor_type)) + sensors.extend( + RainCloudSensor(zone, sensor_type) + for zone in raincloud.controller.faucet.zones + ) add_entities(sensors, True) diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py index 957720305de..d901f862133 100644 --- a/homeassistant/components/raincloud/switch.py +++ b/homeassistant/components/raincloud/switch.py @@ -47,13 +47,14 @@ def setup_platform( raincloud = hass.data[DATA_RAINCLOUD].data default_watering_timer = config[CONF_WATERING_TIME] - sensors = [] - for sensor_type in config[CONF_MONITORED_CONDITIONS]: - # create a sensor for each zone managed by faucet - for zone in raincloud.controller.faucet.zones: - sensors.append(RainCloudSwitch(default_watering_timer, zone, sensor_type)) - - add_entities(sensors, True) + add_entities( + ( + RainCloudSwitch(default_watering_timer, zone, sensor_type) + for zone in raincloud.controller.faucet.zones + for sensor_type in config[CONF_MONITORED_CONDITIONS] + ), + True, + ) class RainCloudSwitch(RainCloudEntity, SwitchEntity): diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index b57070198a5..491366c4983 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -512,21 +512,20 @@ def _update_states_table_with_foreign_key_options( ) -> None: """Add the options to foreign key constraints.""" inspector = sqlalchemy.inspect(engine) - alters = [] - for foreign_key in inspector.get_foreign_keys(TABLE_STATES): - if foreign_key["name"] and ( + alters = [ + { + "old_fk": ForeignKeyConstraint((), (), name=foreign_key["name"]), + "columns": foreign_key["constrained_columns"], + } + for foreign_key in inspector.get_foreign_keys(TABLE_STATES) + if foreign_key["name"] + and ( # MySQL/MariaDB will have empty options not foreign_key.get("options") - or # Postgres will have ondelete set to None - foreign_key.get("options", {}).get("ondelete") is None - ): - alters.append( - { - "old_fk": ForeignKeyConstraint((), (), name=foreign_key["name"]), - "columns": foreign_key["constrained_columns"], - } - ) + or foreign_key.get("options", {}).get("ondelete") is None + ) + ] if not alters: return diff --git a/homeassistant/components/reolink/media_source.py b/homeassistant/components/reolink/media_source.py index 2a1eee9e97d..84c844a0f92 100644 --- a/homeassistant/components/reolink/media_source.py +++ b/homeassistant/components/reolink/media_source.py @@ -243,7 +243,6 @@ class ReolinkVODMediaSource(MediaSource): start = now - dt.timedelta(days=31) end = now - children: list[BrowseMediaSource] = [] if _LOGGER.isEnabledFor(logging.DEBUG): _LOGGER.debug( "Requesting recording days of %s from %s to %s", @@ -254,19 +253,19 @@ class ReolinkVODMediaSource(MediaSource): statuses, _ = await host.api.request_vod_files( channel, start, end, status_only=True, stream=stream ) - for status in statuses: - for day in status.days: - children.append( - BrowseMediaSource( - domain=DOMAIN, - identifier=f"DAY|{config_entry_id}|{channel}|{stream}|{status.year}|{status.month}|{day}", - media_class=MediaClass.DIRECTORY, - media_content_type=MediaType.PLAYLIST, - title=f"{status.year}/{status.month}/{day}", - can_play=False, - can_expand=True, - ) - ) + children: list[BrowseMediaSource] = [ + BrowseMediaSource( + domain=DOMAIN, + identifier=f"DAY|{config_entry_id}|{channel}|{stream}|{status.year}|{status.month}|{day}", + media_class=MediaClass.DIRECTORY, + media_content_type=MediaType.PLAYLIST, + title=f"{status.year}/{status.month}/{day}", + can_play=False, + can_expand=True, + ) + for status in statuses + for day in status.days + ] return BrowseMediaSource( domain=DOMAIN, diff --git a/homeassistant/components/rfxtrx/device_action.py b/homeassistant/components/rfxtrx/device_action.py index e706153ace0..65cf1a11911 100644 --- a/homeassistant/components/rfxtrx/device_action.py +++ b/homeassistant/components/rfxtrx/device_action.py @@ -51,21 +51,17 @@ async def async_get_actions( except ValueError: return [] - actions = [] - for action_type in ACTION_TYPES: - if hasattr(device, action_type): - data: dict[int, str] = getattr(device, ACTION_SELECTION[action_type], {}) - for value in data.values(): - actions.append( - { - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: action_type, - CONF_SUBTYPE: value, - } - ) - - return actions + return [ + { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: action_type, + CONF_SUBTYPE: value, + } + for action_type in ACTION_TYPES + if hasattr(device, action_type) + for value in getattr(device, ACTION_SELECTION[action_type], {}).values() + ] def _get_commands( diff --git a/homeassistant/components/rfxtrx/device_trigger.py b/homeassistant/components/rfxtrx/device_trigger.py index 3064832a397..9e42cfa3919 100644 --- a/homeassistant/components/rfxtrx/device_trigger.py +++ b/homeassistant/components/rfxtrx/device_trigger.py @@ -51,20 +51,17 @@ async def async_get_triggers( """List device triggers for RFXCOM RFXtrx devices.""" device = async_get_device_object(hass, device_id) - triggers = [] - for conf_type in TRIGGER_TYPES: - data: dict[int, str] = getattr(device, TRIGGER_SELECTION[conf_type], {}) - for command in data.values(): - triggers.append( - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: conf_type, - CONF_SUBTYPE: command, - } - ) - return triggers + return [ + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: conf_type, + CONF_SUBTYPE: command, + } + for conf_type in TRIGGER_TYPES + for command in getattr(device, TRIGGER_SELECTION[conf_type], {}).values() + ] async def async_validate_trigger_config( diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 0bb98c44896..f421b6da7ef 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -255,18 +255,15 @@ async def async_setup_entry( device_id: DeviceTuple, entity_info: dict[str, Any], ) -> list[Entity]: - entities: list[Entity] = [] - for data_type in set(event.values) & set(SENSOR_TYPES_DICT): - entities.append( - RfxtrxSensor( - event.device, - device_id, - SENSOR_TYPES_DICT[data_type], - event=event if auto else None, - ) + return [ + RfxtrxSensor( + event.device, + device_id, + SENSOR_TYPES_DICT[data_type], + event=event if auto else None, ) - - return entities + for data_type in set(event.values) & set(SENSOR_TYPES_DICT) + ] await async_setup_platform_entry( hass, config_entry, async_add_entities, _supported, _constructor diff --git a/homeassistant/components/ring/diagnostics.py b/homeassistant/components/ring/diagnostics.py index 0f9c91415d7..497d87a086b 100644 --- a/homeassistant/components/ring/diagnostics.py +++ b/homeassistant/components/ring/diagnostics.py @@ -34,10 +34,11 @@ async def async_get_config_entry_diagnostics( ) -> dict[str, Any]: """Return diagnostics for a config entry.""" ring: ring_doorbell.Ring = hass.data[DOMAIN][entry.entry_id]["api"] - devices_raw = [] - for device_type in ring.devices_data: - for device_id in ring.devices_data[device_type]: - devices_raw.append(ring.devices_data[device_type][device_id]) + devices_raw = [ + ring.devices_data[device_type][device_id] + for device_type in ring.devices_data + for device_id in ring.devices_data[device_type] + ] return async_redact_data( {"device_data": devices_raw}, TO_REDACT, diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index 36203aed1eb..abc3284bcb2 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -41,13 +41,12 @@ async def async_setup_entry( devices_coordinator: RingDataCoordinator = hass.data[DOMAIN][config_entry.entry_id][ RING_DEVICES_COORDINATOR ] - lights = [] - for device in devices["stickup_cams"]: - if device.has_capability("light"): - lights.append(RingLight(device, devices_coordinator)) - - async_add_entities(lights) + async_add_entities( + RingLight(device, devices_coordinator) + for device in devices["stickup_cams"] + if device.has_capability("light") + ) class RingLight(RingEntity, LightEntity): diff --git a/homeassistant/components/ring/siren.py b/homeassistant/components/ring/siren.py index b746889d439..aa3c50f275a 100644 --- a/homeassistant/components/ring/siren.py +++ b/homeassistant/components/ring/siren.py @@ -28,12 +28,10 @@ async def async_setup_entry( coordinator: RingDataCoordinator = hass.data[DOMAIN][config_entry.entry_id][ RING_DEVICES_COORDINATOR ] - sirens = [] - for device in devices["chimes"]: - sirens.append(RingChimeSiren(device, coordinator)) - - async_add_entities(sirens) + async_add_entities( + RingChimeSiren(device, coordinator) for device in devices["chimes"] + ) class RingChimeSiren(RingEntity, SirenEntity): diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index 3c45a1749f8..86b4b6b0b72 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -38,13 +38,12 @@ async def async_setup_entry( coordinator: RingDataCoordinator = hass.data[DOMAIN][config_entry.entry_id][ RING_DEVICES_COORDINATOR ] - switches = [] - for device in devices["stickup_cams"]: - if device.has_capability("siren"): - switches.append(SirenSwitch(device, coordinator)) - - async_add_entities(switches) + async_add_entities( + SirenSwitch(device, coordinator) + for device in devices["stickup_cams"] + if device.has_capability("siren") + ) class BaseRingSwitch(RingEntity, SwitchEntity): diff --git a/homeassistant/components/roborock/__init__.py b/homeassistant/components/roborock/__init__.py index 55f25e6bdd2..c01d1fc7c9b 100644 --- a/homeassistant/components/roborock/__init__.py +++ b/homeassistant/components/roborock/__init__.py @@ -87,12 +87,10 @@ def build_setup_functions( product_info: dict[str, HomeDataProduct], ) -> list[Coroutine[Any, Any, RoborockDataUpdateCoordinator | None]]: """Create a list of setup functions that can later be called asynchronously.""" - setup_functions = [] - for device in device_map.values(): - setup_functions.append( - setup_device(hass, user_data, device, product_info[device.product_id]) - ) - return setup_functions + return [ + setup_device(hass, user_data, device, product_info[device.product_id]) + for device in device_map.values() + ] async def setup_device( diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py index 2c9e64ed044..366215a6684 100644 --- a/homeassistant/components/roku/select.py +++ b/homeassistant/components/roku/select.py @@ -115,15 +115,13 @@ async def async_setup_entry( coordinator: RokuDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] device: RokuDevice = coordinator.data - entities: list[RokuSelectEntity] = [] - - for description in ENTITIES: - entities.append( - RokuSelectEntity( - coordinator=coordinator, - description=description, - ) + entities: list[RokuSelectEntity] = [ + RokuSelectEntity( + coordinator=coordinator, + description=description, ) + for description in ENTITIES + ] if len(device.channels) > 0: entities.append( diff --git a/homeassistant/components/roomba/braava.py b/homeassistant/components/roomba/braava.py index da9007f8b2c..37411680d0b 100644 --- a/homeassistant/components/roomba/braava.py +++ b/homeassistant/components/roomba/braava.py @@ -37,11 +37,11 @@ class BraavaJet(IRobotVacuum): super().__init__(roomba, blid) # Initialize fan speed list - speed_list = [] - for behavior in BRAAVA_MOP_BEHAVIORS: - for spray in BRAAVA_SPRAY_AMOUNT: - speed_list.append(f"{behavior}-{spray}") - self._attr_fan_speed_list = speed_list + self._attr_fan_speed_list = [ + f"{behavior}-{spray}" + for behavior in BRAAVA_MOP_BEHAVIORS + for spray in BRAAVA_SPRAY_AMOUNT + ] @property def fan_speed(self): diff --git a/homeassistant/components/russound_rnet/media_player.py b/homeassistant/components/russound_rnet/media_player.py index f9419f4be4f..3b061d5a503 100644 --- a/homeassistant/components/russound_rnet/media_player.py +++ b/homeassistant/components/russound_rnet/media_player.py @@ -58,9 +58,7 @@ def setup_platform( russ = russound.Russound(host, port) russ.connect() - sources = [] - for source in config[CONF_SOURCES]: - sources.append(source["name"]) + sources = [source["name"] for source in config[CONF_SOURCES]] if russ.is_connected(): for zone_id, extra in config[CONF_ZONES].items(): diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index d420d499c4b..75b56c98ac3 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -79,7 +79,7 @@ async def async_setup_platform( sensor_def = pysaj.Sensors(wifi) # Use all sensors by default - hass_sensors = [] + hass_sensors: list[SAJsensor] = [] kwargs = {} if wifi: @@ -103,11 +103,11 @@ async def async_setup_platform( if not done: raise PlatformNotReady - for sensor in sensor_def: - if sensor.enabled: - hass_sensors.append( - SAJsensor(saj.serialnumber, sensor, inverter_name=config.get(CONF_NAME)) - ) + hass_sensors.extend( + SAJsensor(saj.serialnumber, sensor, inverter_name=config.get(CONF_NAME)) + for sensor in sensor_def + if sensor.enabled + ) async_add_entities(hass_sensors) diff --git a/homeassistant/components/schlage/binary_sensor.py b/homeassistant/components/schlage/binary_sensor.py index fae6208b95f..a141403bdf4 100644 --- a/homeassistant/components/schlage/binary_sensor.py +++ b/homeassistant/components/schlage/binary_sensor.py @@ -45,17 +45,15 @@ async def async_setup_entry( ) -> None: """Set up binary_sensors based on a config entry.""" coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - entities = [] - for device_id in coordinator.data.locks: - for description in _DESCRIPTIONS: - entities.append( - SchlageBinarySensor( - coordinator=coordinator, - description=description, - device_id=device_id, - ) - ) - async_add_entities(entities) + async_add_entities( + SchlageBinarySensor( + coordinator=coordinator, + description=description, + device_id=device_id, + ) + for device_id in coordinator.data.locks + for description in _DESCRIPTIONS + ) class SchlageBinarySensor(SchlageEntity, BinarySensorEntity): diff --git a/homeassistant/components/schlage/switch.py b/homeassistant/components/schlage/switch.py index 467fbc671bf..53771768ccd 100644 --- a/homeassistant/components/schlage/switch.py +++ b/homeassistant/components/schlage/switch.py @@ -62,17 +62,15 @@ async def async_setup_entry( ) -> None: """Set up switches based on a config entry.""" coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - entities = [] - for device_id in coordinator.data.locks: - for description in SWITCHES: - entities.append( - SchlageSwitch( - coordinator=coordinator, - description=description, - device_id=device_id, - ) - ) - async_add_entities(entities) + async_add_entities( + SchlageSwitch( + coordinator=coordinator, + description=description, + device_id=device_id, + ) + for device_id in coordinator.data.locks + for description in SWITCHES + ) class SchlageSwitch(SchlageEntity, SwitchEntity): diff --git a/homeassistant/components/screenlogic/binary_sensor.py b/homeassistant/components/screenlogic/binary_sensor.py index 6f956026c11..1277ea7e1d4 100644 --- a/homeassistant/components/screenlogic/binary_sensor.py +++ b/homeassistant/components/screenlogic/binary_sensor.py @@ -175,32 +175,31 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" - entities: list[ScreenLogicBinarySensor] = [] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[SL_DOMAIN][ config_entry.entry_id ] gateway = coordinator.gateway - for core_sensor_description in SUPPORTED_CORE_SENSORS: + entities: list[ScreenLogicBinarySensor] = [ + ScreenLogicPushBinarySensor(coordinator, core_sensor_description) + for core_sensor_description in SUPPORTED_CORE_SENSORS if ( gateway.get_data( *core_sensor_description.data_root, core_sensor_description.key ) is not None - ): - entities.append( - ScreenLogicPushBinarySensor(coordinator, core_sensor_description) - ) + ) + ] for p_index, p_data in gateway.get_data(DEVICE.PUMP).items(): if not p_data or not p_data.get(VALUE.DATA): continue - for proto_pump_sensor_description in SUPPORTED_PUMP_SENSORS: - entities.append( - ScreenLogicPumpBinarySensor( - coordinator, copy(proto_pump_sensor_description), p_index - ) + entities.extend( + ScreenLogicPumpBinarySensor( + coordinator, copy(proto_pump_sensor_description), p_index ) + for proto_pump_sensor_description in SUPPORTED_PUMP_SENSORS + ) chem_sensor_description: ScreenLogicPushBinarySensorDescription for chem_sensor_description in SUPPORTED_INTELLICHEM_SENSORS: diff --git a/homeassistant/components/screenlogic/climate.py b/homeassistant/components/screenlogic/climate.py index e0e61e826b0..8e89cb2eb03 100644 --- a/homeassistant/components/screenlogic/climate.py +++ b/homeassistant/components/screenlogic/climate.py @@ -47,26 +47,23 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" - entities = [] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[SL_DOMAIN][ config_entry.entry_id ] gateway = coordinator.gateway - for body_index in gateway.get_data(DEVICE.BODY): - entities.append( - ScreenLogicClimate( - coordinator, - ScreenLogicClimateDescription( - subscription_code=CODE.STATUS_CHANGED, - data_root=(DEVICE.BODY,), - key=body_index, - ), - ) + async_add_entities( + ScreenLogicClimate( + coordinator, + ScreenLogicClimateDescription( + subscription_code=CODE.STATUS_CHANGED, + data_root=(DEVICE.BODY,), + key=body_index, + ), ) - - async_add_entities(entities) + for body_index in gateway.get_data(DEVICE.BODY) + ) @dataclass(frozen=True, kw_only=True) diff --git a/homeassistant/components/screenlogic/sensor.py b/homeassistant/components/screenlogic/sensor.py index a67c61c4c91..e4fc86a6b5f 100644 --- a/homeassistant/components/screenlogic/sensor.py +++ b/homeassistant/components/screenlogic/sensor.py @@ -227,20 +227,21 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" - entities: list[ScreenLogicSensor] = [] coordinator: ScreenlogicDataUpdateCoordinator = hass.data[SL_DOMAIN][ config_entry.entry_id ] gateway = coordinator.gateway - for core_sensor_description in SUPPORTED_CORE_SENSORS: + entities: list[ScreenLogicSensor] = [ + ScreenLogicPushSensor(coordinator, core_sensor_description) + for core_sensor_description in SUPPORTED_CORE_SENSORS if ( gateway.get_data( *core_sensor_description.data_root, core_sensor_description.key ) is not None - ): - entities.append(ScreenLogicPushSensor(coordinator, core_sensor_description)) + ) + ] for pump_index, pump_data in gateway.get_data(DEVICE.PUMP).items(): if not pump_data or not pump_data.get(VALUE.DATA): diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 1adfe3ecbd3..18155daaf1f 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -127,8 +127,10 @@ async def async_setup_entry( ) ) - for i in range(len(data.active_voltage)): - entities.append(SenseVoltageSensor(data, i, sense_monitor_id)) + entities.extend( + SenseVoltageSensor(data, i, sense_monitor_id) + for i in range(len(data.active_voltage)) + ) for type_id, typ in TRENDS_SENSOR_TYPES.items(): for variant_id, variant_name in TREND_SENSOR_VARIANTS: diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index f2a2d3f1827..d952fdb9e56 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -230,11 +230,9 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): @property def hvac_modes(self) -> list[HVACMode]: """Return the list of available hvac operation modes.""" - hvac_modes = [] if TYPE_CHECKING: assert self.device_data.hvac_modes - for mode in self.device_data.hvac_modes: - hvac_modes.append(SENSIBO_TO_HA[mode]) + hvac_modes = [SENSIBO_TO_HA[mode] for mode in self.device_data.hvac_modes] return hvac_modes if hvac_modes else [HVACMode.OFF] @property diff --git a/homeassistant/components/seven_segments/image_processing.py b/homeassistant/components/seven_segments/image_processing.py index deb25da5b09..9024a2d4ed4 100644 --- a/homeassistant/components/seven_segments/image_processing.py +++ b/homeassistant/components/seven_segments/image_processing.py @@ -57,15 +57,12 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the Seven segments OCR platform.""" - entities = [] - for camera in config[CONF_SOURCE]: - entities.append( - ImageProcessingSsocr( - hass, camera[CONF_ENTITY_ID], config, camera.get(CONF_NAME) - ) + async_add_entities( + ImageProcessingSsocr( + hass, camera[CONF_ENTITY_ID], config, camera.get(CONF_NAME) ) - - async_add_entities(entities) + for camera in config[CONF_SOURCE] + ) class ImageProcessingSsocr(ImageProcessingEntity): diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 85a058cc90c..101306818a0 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -188,8 +188,6 @@ def get_block_input_triggers( if not is_block_momentary_input(device.settings, block, True): return [] - triggers = [] - if block.type == "device" or get_number_of_channels(device, block) == 1: subtype = "button" else: @@ -203,20 +201,12 @@ def get_block_input_triggers( else: trigger_types = BASIC_INPUTS_EVENTS_TYPES - for trigger_type in trigger_types: - triggers.append((trigger_type, subtype)) - - return triggers + return [(trigger_type, subtype) for trigger_type in trigger_types] def get_shbtn_input_triggers() -> list[tuple[str, str]]: """Return list of input triggers for SHBTN models.""" - triggers = [] - - for trigger_type in SHBTN_INPUTS_EVENTS_TYPES: - triggers.append((trigger_type, "button")) - - return triggers + return [(trigger_type, "button") for trigger_type in SHBTN_INPUTS_EVENTS_TYPES] @singleton.singleton("shelly_coap") diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 87acc78e748..b7da9ef4fa1 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -52,10 +52,7 @@ def setup_platform( auth = sigfox.auth devices = sigfox.devices - sensors = [] - for device in devices: - sensors.append(SigfoxDevice(device, auth, name)) - add_entities(sensors, True) + add_entities((SigfoxDevice(device, auth, name) for device in devices), True) def epoch_to_datetime(epoch_time): @@ -105,8 +102,7 @@ class SigfoxAPI: url = urljoin(API_URL, location_url) response = requests.get(url, auth=self._auth, timeout=10) devices_data = json.loads(response.text)["data"] - for device in devices_data: - devices.append(device["id"]) + devices.extend(device["id"] for device in devices_data) return devices @property diff --git a/homeassistant/components/simplisafe/binary_sensor.py b/homeassistant/components/simplisafe/binary_sensor.py index b3aa1e3281d..3f56149a9f8 100644 --- a/homeassistant/components/simplisafe/binary_sensor.py +++ b/homeassistant/components/simplisafe/binary_sensor.py @@ -79,8 +79,10 @@ async def async_setup_entry( if sensor.type in SUPPORTED_BATTERY_SENSOR_TYPES: sensors.append(BatteryBinarySensor(simplisafe, system, sensor)) - for lock in system.locks.values(): - sensors.append(BatteryBinarySensor(simplisafe, system, lock)) + sensors.extend( + BatteryBinarySensor(simplisafe, system, lock) + for lock in system.locks.values() + ) async_add_entities(sensors) diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index b6bb07fd100..680fc0f4c0f 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -34,15 +34,16 @@ async def async_setup_entry( ) -> None: """Set up SimpliSafe locks based on a config entry.""" simplisafe = hass.data[DOMAIN][entry.entry_id] - locks = [] + locks: list[SimpliSafeLock] = [] for system in simplisafe.systems.values(): if system.version == 2: LOGGER.info("Skipping lock setup for V2 system: %s", system.system_id) continue - for lock in system.locks.values(): - locks.append(SimpliSafeLock(simplisafe, system, lock)) + locks.extend( + SimpliSafeLock(simplisafe, system, lock) for lock in system.locks.values() + ) async_add_entities(locks) diff --git a/homeassistant/components/simplisafe/sensor.py b/homeassistant/components/simplisafe/sensor.py index bd04720f9ba..fbccfc4b2f9 100644 --- a/homeassistant/components/simplisafe/sensor.py +++ b/homeassistant/components/simplisafe/sensor.py @@ -25,16 +25,18 @@ async def async_setup_entry( ) -> None: """Set up SimpliSafe freeze sensors based on a config entry.""" simplisafe = hass.data[DOMAIN][entry.entry_id] - sensors = [] + sensors: list[SimplisafeFreezeSensor] = [] for system in simplisafe.systems.values(): if system.version == 2: LOGGER.info("Skipping sensor setup for V2 system: %s", system.system_id) continue - for sensor in system.sensors.values(): - if sensor.type == DeviceTypes.TEMPERATURE: - sensors.append(SimplisafeFreezeSensor(simplisafe, system, sensor)) + sensors.extend( + SimplisafeFreezeSensor(simplisafe, system, sensor) + for sensor in system.sensors.values() + if sensor.type == DeviceTypes.TEMPERATURE + ) async_add_entities(sensors) diff --git a/homeassistant/components/sleepiq/number.py b/homeassistant/components/sleepiq/number.py index 54912fc2a77..905ceab18bd 100644 --- a/homeassistant/components/sleepiq/number.py +++ b/homeassistant/components/sleepiq/number.py @@ -143,35 +143,35 @@ async def async_setup_entry( """Set up the SleepIQ bed sensors.""" data: SleepIQData = hass.data[DOMAIN][entry.entry_id] - entities = [] + entities: list[SleepIQNumberEntity] = [] for bed in data.client.beds.values(): - for sleeper in bed.sleepers: - entities.append( - SleepIQNumberEntity( - data.data_coordinator, - bed, - sleeper, - NUMBER_DESCRIPTIONS[FIRMNESS], - ) + entities.extend( + SleepIQNumberEntity( + data.data_coordinator, + bed, + sleeper, + NUMBER_DESCRIPTIONS[FIRMNESS], ) - for actuator in bed.foundation.actuators: - entities.append( - SleepIQNumberEntity( - data.data_coordinator, - bed, - actuator, - NUMBER_DESCRIPTIONS[ACTUATOR], - ) + for sleeper in bed.sleepers + ) + entities.extend( + SleepIQNumberEntity( + data.data_coordinator, + bed, + actuator, + NUMBER_DESCRIPTIONS[ACTUATOR], ) - for foot_warmer in bed.foundation.foot_warmers: - entities.append( - SleepIQNumberEntity( - data.data_coordinator, - bed, - foot_warmer, - NUMBER_DESCRIPTIONS[FOOT_WARMING_TIMER], - ) + for actuator in bed.foundation.actuators + ) + entities.extend( + SleepIQNumberEntity( + data.data_coordinator, + bed, + foot_warmer, + NUMBER_DESCRIPTIONS[FOOT_WARMING_TIMER], ) + for foot_warmer in bed.foundation.foot_warmers + ) async_add_entities(entities) diff --git a/homeassistant/components/sma/sensor.py b/homeassistant/components/sma/sensor.py index 0d4aad6050a..9d580a76d9e 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -844,19 +844,16 @@ async def async_setup_entry( if TYPE_CHECKING: assert config_entry.unique_id - entities = [] - for sensor in used_sensors: - entities.append( - SMAsensor( - coordinator, - config_entry.unique_id, - SENSOR_ENTITIES.get(sensor.name), - device_info, - sensor, - ) + async_add_entities( + SMAsensor( + coordinator, + config_entry.unique_id, + SENSOR_ENTITIES.get(sensor.name), + device_info, + sensor, ) - - async_add_entities(entities) + for sensor in used_sensors + ) class SMAsensor(CoordinatorEntity, SensorEntity): diff --git a/homeassistant/components/smappee/switch.py b/homeassistant/components/smappee/switch.py index 678b7fa8229..1bc5d159145 100644 --- a/homeassistant/components/smappee/switch.py +++ b/homeassistant/components/smappee/switch.py @@ -36,18 +36,18 @@ async def async_setup_entry( ) ) elif actuator.type == "INFINITY_OUTPUT_MODULE": - for option in actuator.state_options: - entities.append( - SmappeeActuator( - smappee_base, - service_location, - actuator.name, - actuator_id, - actuator.type, - actuator.serialnumber, - actuator_state_option=option, - ) + entities.extend( + SmappeeActuator( + smappee_base, + service_location, + actuator.name, + actuator_id, + actuator.type, + actuator.serialnumber, + actuator_state_option=option, ) + for option in actuator.state_options + ) async_add_entities(entities, True) diff --git a/homeassistant/components/smartthings/fan.py b/homeassistant/components/smartthings/fan.py index 7430b48dbd3..215986dfb0d 100644 --- a/homeassistant/components/smartthings/fan.py +++ b/homeassistant/components/smartthings/fan.py @@ -59,9 +59,9 @@ def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: supported = [Capability.switch] - for capability in optional: - if capability in capabilities: - supported.append(capability) + supported.extend( + capability for capability in optional if capability in capabilities + ) return supported diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index 7b1aa540728..821200f68b1 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -85,15 +85,14 @@ async def async_setup_entry( network_coordinator = sms_data[NETWORK_COORDINATOR] gateway = sms_data[GATEWAY] unique_id = str(await gateway.get_imei_async()) - entities = [] - for description in SIGNAL_SENSORS: - entities.append( - DeviceSensor(signal_coordinator, description, unique_id, gateway) - ) - for description in NETWORK_SENSORS: - entities.append( - DeviceSensor(network_coordinator, description, unique_id, gateway) - ) + entities = [ + DeviceSensor(signal_coordinator, description, unique_id, gateway) + for description in SIGNAL_SENSORS + ] + entities.extend( + DeviceSensor(network_coordinator, description, unique_id, gateway) + for description in NETWORK_SENSORS + ) async_add_entities(entities, True) diff --git a/homeassistant/components/speedtestdotnet/coordinator.py b/homeassistant/components/speedtestdotnet/coordinator.py index e07bb94342d..299652ba0bd 100644 --- a/homeassistant/components/speedtestdotnet/coordinator.py +++ b/homeassistant/components/speedtestdotnet/coordinator.py @@ -38,10 +38,9 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator[dict[str, Any]]): def update_servers(self) -> None: """Update list of test servers.""" test_servers = self.api.get_servers() - test_servers_list = [] - for servers in test_servers.values(): - for server in servers: - test_servers_list.append(server) + test_servers_list = [ + server for servers in test_servers.values() for server in servers + ] for server in sorted( test_servers_list, key=lambda server: ( diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 191981b781d..f0b474064c2 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -395,11 +395,11 @@ class SqueezeBoxEntity(MediaPlayerEntity): player_ids = { p.unique_id: p.entity_id for p in self.hass.data[DOMAIN][KNOWN_PLAYERS] } - sync_group = [] - for player in self._player.sync_group: - if player in player_ids: - sync_group.append(player_ids[player]) - return sync_group + return [ + player_ids[player] + for player in self._player.sync_group + if player in player_ids + ] @property def sync_group(self): @@ -550,8 +550,7 @@ class SqueezeBoxEntity(MediaPlayerEntity): """ all_params = [command] if parameters: - for parameter in parameters: - all_params.append(parameter) + all_params.extend(parameters) await self._player.async_query(*all_params) async def async_call_query(self, command, parameters=None): @@ -562,8 +561,7 @@ class SqueezeBoxEntity(MediaPlayerEntity): """ all_params = [command] if parameters: - for parameter in parameters: - all_params.append(parameter) + all_params.extend(parameters) self._query_result = await self._player.async_query(*all_params) _LOGGER.debug("call_query got result %s", self._query_result) diff --git a/homeassistant/components/starline/button.py b/homeassistant/components/starline/button.py index fa2b96ca58c..ea1a27adc15 100644 --- a/homeassistant/components/starline/button.py +++ b/homeassistant/components/starline/button.py @@ -24,12 +24,12 @@ async def async_setup_entry( ) -> None: """Set up the StarLine button.""" account: StarlineAccount = hass.data[DOMAIN][entry.entry_id] - entities = [] - for device in account.api.devices.values(): - if device.support_state: - for description in BUTTON_TYPES: - entities.append(StarlineButton(account, device, description)) - async_add_entities(entities) + async_add_entities( + StarlineButton(account, device, description) + for device in account.api.devices.values() + if device.support_state + for description in BUTTON_TYPES + ) class StarlineButton(StarlineEntity, ButtonEntity): diff --git a/homeassistant/components/starline/device_tracker.py b/homeassistant/components/starline/device_tracker.py index 9b57b335d8b..11b0d433787 100644 --- a/homeassistant/components/starline/device_tracker.py +++ b/homeassistant/components/starline/device_tracker.py @@ -16,11 +16,11 @@ async def async_setup_entry( ) -> None: """Set up StarLine entry.""" account: StarlineAccount = hass.data[DOMAIN][entry.entry_id] - entities = [] - for device in account.api.devices.values(): - if device.support_position: - entities.append(StarlineDeviceTracker(account, device)) - async_add_entities(entities) + async_add_entities( + StarlineDeviceTracker(account, device) + for device in account.api.devices.values() + if device.support_position + ) class StarlineDeviceTracker(StarlineEntity, TrackerEntity, RestoreEntity): diff --git a/homeassistant/components/starlingbank/sensor.py b/homeassistant/components/starlingbank/sensor.py index 2950c60a6ff..f6b11a41102 100644 --- a/homeassistant/components/starlingbank/sensor.py +++ b/homeassistant/components/starlingbank/sensor.py @@ -54,18 +54,18 @@ def setup_platform( ) -> None: """Set up the Sterling Bank sensor platform.""" - sensors = [] + sensors: list[StarlingBalanceSensor] = [] for account in config[CONF_ACCOUNTS]: try: starling_account = StarlingAccount( account[CONF_ACCESS_TOKEN], sandbox=account[CONF_SANDBOX] ) - for balance_type in account[CONF_BALANCE_TYPES]: - sensors.append( - StarlingBalanceSensor( - starling_account, account[CONF_NAME], balance_type - ) + sensors.extend( + StarlingBalanceSensor( + starling_account, account[CONF_NAME], balance_type ) + for balance_type in account[CONF_BALANCE_TYPES] + ) except requests.exceptions.HTTPError as error: _LOGGER.error( "Unable to set up Starling account '%s': %s", account[CONF_NAME], error diff --git a/homeassistant/components/subaru/device_tracker.py b/homeassistant/components/subaru/device_tracker.py index 8611c338a8b..5d25056312e 100644 --- a/homeassistant/components/subaru/device_tracker.py +++ b/homeassistant/components/subaru/device_tracker.py @@ -36,11 +36,11 @@ async def async_setup_entry( entry: dict = hass.data[DOMAIN][config_entry.entry_id] coordinator: DataUpdateCoordinator = entry[ENTRY_COORDINATOR] vehicle_info: dict = entry[ENTRY_VEHICLES] - entities: list[SubaruDeviceTracker] = [] - for vehicle in vehicle_info.values(): - if vehicle[VEHICLE_HAS_REMOTE_SERVICE]: - entities.append(SubaruDeviceTracker(vehicle, coordinator)) - async_add_entities(entities) + async_add_entities( + SubaruDeviceTracker(vehicle, coordinator) + for vehicle in vehicle_info.values() + if vehicle[VEHICLE_HAS_REMOTE_SERVICE] + ) class SubaruDeviceTracker( diff --git a/homeassistant/components/surepetcare/lock.py b/homeassistant/components/surepetcare/lock.py index 1f33457214e..b933cc40637 100644 --- a/homeassistant/components/surepetcare/lock.py +++ b/homeassistant/components/surepetcare/lock.py @@ -23,25 +23,18 @@ async def async_setup_entry( ) -> None: """Set up Sure PetCare locks on a config entry.""" - entities: list[SurePetcareLock] = [] - coordinator: SurePetcareDataCoordinator = hass.data[DOMAIN][entry.entry_id] - for surepy_entity in coordinator.data.values(): - if surepy_entity.type not in [ - EntityType.CAT_FLAP, - EntityType.PET_FLAP, - ]: - continue - + async_add_entities( + SurePetcareLock(surepy_entity.id, coordinator, lock_state) + for surepy_entity in coordinator.data.values() + if surepy_entity.type in [EntityType.CAT_FLAP, EntityType.PET_FLAP] for lock_state in ( LockState.LOCKED_IN, LockState.LOCKED_OUT, LockState.LOCKED_ALL, - ): - entities.append(SurePetcareLock(surepy_entity.id, coordinator, lock_state)) - - async_add_entities(entities) + ) + ) class SurePetcareLock(SurePetcareEntity, LockEntity): diff --git a/homeassistant/components/swiss_hydrological_data/sensor.py b/homeassistant/components/swiss_hydrological_data/sensor.py index f8346d7368d..e74d1f66046 100644 --- a/homeassistant/components/swiss_hydrological_data/sensor.py +++ b/homeassistant/components/swiss_hydrological_data/sensor.py @@ -73,12 +73,13 @@ def setup_platform( _LOGGER.error("The station doesn't exists: %s", station) return - entities = [] - - for condition in monitored_conditions: - entities.append(SwissHydrologicalDataSensor(hydro_data, station, condition)) - - add_entities(entities, True) + add_entities( + ( + SwissHydrologicalDataSensor(hydro_data, station, condition) + for condition in monitored_conditions + ), + True, + ) class SwissHydrologicalDataSensor(SensorEntity): diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index 703c283182b..412fd128577 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -62,15 +62,15 @@ async def async_setup_entry( SyncThruMainSensor(coordinator, name), SyncThruActiveAlertSensor(coordinator, name), ] - - for key in supp_toner: - entities.append(SyncThruTonerSensor(coordinator, name, key)) - for key in supp_drum: - entities.append(SyncThruDrumSensor(coordinator, name, key)) - for key in supp_tray: - entities.append(SyncThruInputTraySensor(coordinator, name, key)) - for int_key in supp_output_tray: - entities.append(SyncThruOutputTraySensor(coordinator, name, int_key)) + entities.extend(SyncThruTonerSensor(coordinator, name, key) for key in supp_toner) + entities.extend(SyncThruDrumSensor(coordinator, name, key) for key in supp_drum) + entities.extend( + SyncThruInputTraySensor(coordinator, name, key) for key in supp_tray + ) + entities.extend( + SyncThruOutputTraySensor(coordinator, name, int_key) + for int_key in supp_output_tray + ) async_add_entities(entities) diff --git a/homeassistant/components/synology_dsm/media_source.py b/homeassistant/components/synology_dsm/media_source.py index 69c87f4d0a1..ca6554209b8 100644 --- a/homeassistant/components/synology_dsm/media_source.py +++ b/homeassistant/components/synology_dsm/media_source.py @@ -91,20 +91,18 @@ class SynologyPhotosMediaSource(MediaSource): ) -> list[BrowseMediaSource]: """Handle browsing different diskstations.""" if not item.identifier: - ret = [] - for entry in self.entries: - ret.append( - BrowseMediaSource( - domain=DOMAIN, - identifier=entry.unique_id, - media_class=MediaClass.DIRECTORY, - media_content_type=MediaClass.IMAGE, - title=f"{entry.title} - {entry.unique_id}", - can_play=False, - can_expand=True, - ) + return [ + BrowseMediaSource( + domain=DOMAIN, + identifier=entry.unique_id, + media_class=MediaClass.DIRECTORY, + media_content_type=MediaClass.IMAGE, + title=f"{entry.title} - {entry.unique_id}", + can_play=False, + can_expand=True, ) - return ret + for entry in self.entries + ] identifier = SynologyPhotosMediaSourceIdentifier(item.identifier) diskstation: SynologyDSMData = self.hass.data[DOMAIN][identifier.unique_id] diff --git a/homeassistant/components/system_bridge/binary_sensor.py b/homeassistant/components/system_bridge/binary_sensor.py index 36de4c72c3d..d5cb34cd42a 100644 --- a/homeassistant/components/system_bridge/binary_sensor.py +++ b/homeassistant/components/system_bridge/binary_sensor.py @@ -50,23 +50,20 @@ async def async_setup_entry( """Set up System Bridge binary sensor based on a config entry.""" coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - entities = [] - for description in BASE_BINARY_SENSOR_TYPES: - entities.append( - SystemBridgeBinarySensor(coordinator, description, entry.data[CONF_PORT]) - ) + entities = [ + SystemBridgeBinarySensor(coordinator, description, entry.data[CONF_PORT]) + for description in BASE_BINARY_SENSOR_TYPES + ] if ( coordinator.data.battery and coordinator.data.battery.percentage and coordinator.data.battery.percentage > -1 ): - for description in BATTERY_BINARY_SENSOR_TYPES: - entities.append( - SystemBridgeBinarySensor( - coordinator, description, entry.data[CONF_PORT] - ) - ) + entities.extend( + SystemBridgeBinarySensor(coordinator, description, entry.data[CONF_PORT]) + for description in BATTERY_BINARY_SENSOR_TYPES + ) async_add_entities(entities) diff --git a/homeassistant/components/system_bridge/media_source.py b/homeassistant/components/system_bridge/media_source.py index 0b319f4c7c9..54aa3cffaae 100644 --- a/homeassistant/components/system_bridge/media_source.py +++ b/homeassistant/components/system_bridge/media_source.py @@ -88,22 +88,21 @@ class SystemBridgeSource(MediaSource): def _build_bridges(self) -> BrowseMediaSource: """Build bridges for System Bridge media.""" - children = [] - for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.entry_id is not None: - children.append( - BrowseMediaSource( - domain=DOMAIN, - identifier=entry.entry_id, - media_class=MediaClass.DIRECTORY, - media_content_type="", - title=entry.title, - can_play=False, - can_expand=True, - children=[], - children_media_class=MediaClass.DIRECTORY, - ) - ) + children = [ + BrowseMediaSource( + domain=DOMAIN, + identifier=entry.entry_id, + media_class=MediaClass.DIRECTORY, + media_content_type="", + title=entry.title, + can_play=False, + can_expand=True, + children=[], + children_media_class=MediaClass.DIRECTORY, + ) + for entry in self.hass.config_entries.async_entries(DOMAIN) + if entry.entry_id is not None + ] return BrowseMediaSource( domain=DOMAIN, diff --git a/homeassistant/components/system_bridge/sensor.py b/homeassistant/components/system_bridge/sensor.py index 56cd9bcb13f..94c73a2ac05 100644 --- a/homeassistant/components/system_bridge/sensor.py +++ b/homeassistant/components/system_bridge/sensor.py @@ -364,45 +364,44 @@ async def async_setup_entry( """Set up System Bridge sensor based on a config entry.""" coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - entities = [] - for description in BASE_SENSOR_TYPES: - entities.append( - SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT]) - ) + entities = [ + SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT]) + for description in BASE_SENSOR_TYPES + ] for index_device, device in enumerate(coordinator.data.disks.devices): if device.partitions is None: continue - for index_partition, partition in enumerate(device.partitions): - entities.append( - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"filesystem_{partition.mount_point.replace(':', '')}", - name=f"{partition.mount_point} space used", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PERCENTAGE, - icon="mdi:harddisk", - value=( - lambda data, - dk=index_device, - pk=index_partition: partition_usage(data, dk, pk) - ), + entities.extend( + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"filesystem_{partition.mount_point.replace(':', '')}", + name=f"{partition.mount_point} space used", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + icon="mdi:harddisk", + value=( + lambda data, + dk=index_device, + pk=index_partition: partition_usage(data, dk, pk) ), - entry.data[CONF_PORT], - ) + ), + entry.data[CONF_PORT], ) + for index_partition, partition in enumerate(device.partitions) + ) if ( coordinator.data.battery and coordinator.data.battery.percentage and coordinator.data.battery.percentage > -1 ): - for description in BATTERY_SENSOR_TYPES: - entities.append( - SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT]) - ) + entities.extend( + SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT]) + for description in BATTERY_SENSOR_TYPES + ) entities.append( SystemBridgeSensor( @@ -466,127 +465,128 @@ async def async_setup_entry( ] for index, gpu in enumerate(coordinator.data.gpus): - entities = [ - *entities, - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_core_clock_speed", - name=f"{gpu.name} clock speed", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ, - device_class=SensorDeviceClass.FREQUENCY, - icon="mdi:speedometer", - value=lambda data, k=index: gpu_core_clock_speed(data, k), + entities.extend( + [ + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_core_clock_speed", + name=f"{gpu.name} clock speed", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ, + device_class=SensorDeviceClass.FREQUENCY, + icon="mdi:speedometer", + value=lambda data, k=index: gpu_core_clock_speed(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_memory_clock_speed", - name=f"{gpu.name} memory clock speed", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ, - device_class=SensorDeviceClass.FREQUENCY, - icon="mdi:speedometer", - value=lambda data, k=index: gpu_memory_clock_speed(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_memory_clock_speed", + name=f"{gpu.name} memory clock speed", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ, + device_class=SensorDeviceClass.FREQUENCY, + icon="mdi:speedometer", + value=lambda data, k=index: gpu_memory_clock_speed(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_memory_free", - name=f"{gpu.name} memory free", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfInformation.MEGABYTES, - device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", - value=lambda data, k=index: gpu_memory_free(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_memory_free", + name=f"{gpu.name} memory free", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfInformation.MEGABYTES, + device_class=SensorDeviceClass.DATA_SIZE, + icon="mdi:memory", + value=lambda data, k=index: gpu_memory_free(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_memory_used_percentage", - name=f"{gpu.name} memory used %", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", - value=lambda data, k=index: gpu_memory_used_percentage(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_memory_used_percentage", + name=f"{gpu.name} memory used %", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + icon="mdi:memory", + value=lambda data, k=index: gpu_memory_used_percentage(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_memory_used", - name=f"{gpu.name} memory used", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfInformation.MEGABYTES, - device_class=SensorDeviceClass.DATA_SIZE, - icon="mdi:memory", - value=lambda data, k=index: gpu_memory_used(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_memory_used", + name=f"{gpu.name} memory used", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfInformation.MEGABYTES, + device_class=SensorDeviceClass.DATA_SIZE, + icon="mdi:memory", + value=lambda data, k=index: gpu_memory_used(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_fan_speed", - name=f"{gpu.name} fan speed", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=REVOLUTIONS_PER_MINUTE, - icon="mdi:fan", - value=lambda data, k=index: gpu_fan_speed(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_fan_speed", + name=f"{gpu.name} fan speed", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=REVOLUTIONS_PER_MINUTE, + icon="mdi:fan", + value=lambda data, k=index: gpu_fan_speed(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_power_usage", - name=f"{gpu.name} power usage", - entity_registry_enabled_default=False, - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfPower.WATT, - value=lambda data, k=index: gpu_power_usage(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_power_usage", + name=f"{gpu.name} power usage", + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.WATT, + value=lambda data, k=index: gpu_power_usage(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_temperature", - name=f"{gpu.name} temperature", - entity_registry_enabled_default=False, - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - value=lambda data, k=index: gpu_temperature(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_temperature", + name=f"{gpu.name} temperature", + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value=lambda data, k=index: gpu_temperature(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - SystemBridgeSensor( - coordinator, - SystemBridgeSensorEntityDescription( - key=f"gpu_{gpu.id}_usage_percentage", - name=f"{gpu.name} usage %", - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=PERCENTAGE, - icon="mdi:percent", - value=lambda data, k=index: gpu_usage_percentage(data, k), + SystemBridgeSensor( + coordinator, + SystemBridgeSensorEntityDescription( + key=f"gpu_{gpu.id}_usage_percentage", + name=f"{gpu.name} usage %", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + icon="mdi:percent", + value=lambda data, k=index: gpu_usage_percentage(data, k), + ), + entry.data[CONF_PORT], ), - entry.data[CONF_PORT], - ), - ] + ] + ) if coordinator.data.cpu.per_cpu is not None: for cpu in coordinator.data.cpu.per_cpu: diff --git a/homeassistant/components/systemmonitor/binary_sensor.py b/homeassistant/components/systemmonitor/binary_sensor.py index 97dd0c97f93..9efd6f3b4e0 100644 --- a/homeassistant/components/systemmonitor/binary_sensor.py +++ b/homeassistant/components/systemmonitor/binary_sensor.py @@ -92,21 +92,20 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up System Montor binary sensors based on a config entry.""" - entities: list[SystemMonitorSensor] = [] coordinator: SystemMonitorCoordinator = hass.data[DOMAIN_COORDINATOR] - for sensor_description in SENSOR_TYPES: - _entry = entry.options.get(BINARY_SENSOR_DOMAIN, {}) - for argument in _entry.get(CONF_PROCESS, []): - entities.append( - SystemMonitorSensor( - coordinator, - sensor_description, - entry.entry_id, - argument, - ) - ) - async_add_entities(entities) + async_add_entities( + SystemMonitorSensor( + coordinator, + sensor_description, + entry.entry_id, + argument, + ) + for sensor_description in SENSOR_TYPES + for argument in entry.options.get(BINARY_SENSOR_DOMAIN, {}).get( + CONF_PROCESS, [] + ) + ) class SystemMonitorSensor( diff --git a/tests/components/recorder/test_history.py b/tests/components/recorder/test_history.py index 432239849aa..40290924d11 100644 --- a/tests/components/recorder/test_history.py +++ b/tests/components/recorder/test_history.py @@ -658,9 +658,7 @@ def test_get_significant_states_only( return hass.states.get(entity_id) start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) + points = [start + timedelta(minutes=i) for i in range(1, 4)] states = [] with freeze_time(start) as freezer: diff --git a/tests/components/recorder/test_history_db_schema_30.py b/tests/components/recorder/test_history_db_schema_30.py index 3df1aa6278e..ed4418deb22 100644 --- a/tests/components/recorder/test_history_db_schema_30.py +++ b/tests/components/recorder/test_history_db_schema_30.py @@ -517,9 +517,7 @@ def test_get_significant_states_only( return hass.states.get(entity_id) start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) + points = [start + timedelta(minutes=i) for i in range(1, 4)] states = [] with freeze_time(start) as freezer: diff --git a/tests/components/recorder/test_history_db_schema_32.py b/tests/components/recorder/test_history_db_schema_32.py index 904c232d3f7..70f8c636d0e 100644 --- a/tests/components/recorder/test_history_db_schema_32.py +++ b/tests/components/recorder/test_history_db_schema_32.py @@ -507,9 +507,7 @@ def test_get_significant_states_only( return hass.states.get(entity_id) start = dt_util.utcnow() - timedelta(minutes=4) - points = [] - for i in range(1, 4): - points.append(start + timedelta(minutes=i)) + points = [start + timedelta(minutes=i) for i in range(1, 4)] states = [] with freeze_time(start) as freezer: diff --git a/tests/components/recorder/test_migration_from_schema_32.py b/tests/components/recorder/test_migration_from_schema_32.py index cd9969206f5..2e9a71a2a50 100644 --- a/tests/components/recorder/test_migration_from_schema_32.py +++ b/tests/components/recorder/test_migration_from_schema_32.py @@ -914,18 +914,17 @@ async def test_stats_timestamp_conversion_is_reentrant( def _get_all_short_term_stats() -> list[dict[str, Any]]: with session_scope(hass=hass) as session: - results = [] - for result in ( - session.query(old_db_schema.StatisticsShortTerm) - .where(old_db_schema.StatisticsShortTerm.metadata_id == 1000) - .all() - ): - results.append( - { - field.name: getattr(result, field.name) - for field in old_db_schema.StatisticsShortTerm.__table__.c - } + results = [ + { + field.name: getattr(result, field.name) + for field in old_db_schema.StatisticsShortTerm.__table__.c + } + for result in ( + session.query(old_db_schema.StatisticsShortTerm) + .where(old_db_schema.StatisticsShortTerm.metadata_id == 1000) + .all() ) + ] return sorted(results, key=lambda row: row["start_ts"]) # Do not optimize this block, its intentionally written to interleave @@ -1099,14 +1098,12 @@ async def test_stats_timestamp_with_one_by_one( def _get_all_stats(table: old_db_schema.StatisticsBase) -> list[dict[str, Any]]: """Get all stats from a table.""" with session_scope(hass=hass) as session: - results = [] - for result in session.query(table).where(table.metadata_id == 1000).all(): - results.append( - { - field.name: getattr(result, field.name) - for field in table.__table__.c - } - ) + results = [ + {field.name: getattr(result, field.name) for field in table.__table__.c} + for result in session.query(table) + .where(table.metadata_id == 1000) + .all() + ] return sorted(results, key=lambda row: row["start_ts"]) def _insert_and_do_migration(): @@ -1326,14 +1323,12 @@ async def test_stats_timestamp_with_one_by_one_removes_duplicates( def _get_all_stats(table: old_db_schema.StatisticsBase) -> list[dict[str, Any]]: """Get all stats from a table.""" with session_scope(hass=hass) as session: - results = [] - for result in session.query(table).where(table.metadata_id == 1000).all(): - results.append( - { - field.name: getattr(result, field.name) - for field in table.__table__.c - } - ) + results = [ + {field.name: getattr(result, field.name) for field in table.__table__.c} + for result in session.query(table) + .where(table.metadata_id == 1000) + .all() + ] return sorted(results, key=lambda row: row["start_ts"]) def _insert_and_do_migration(): diff --git a/tests/components/reolink/test_config_flow.py b/tests/components/reolink/test_config_flow.py index 34f617fb071..e8818c9e560 100644 --- a/tests/components/reolink/test_config_flow.py +++ b/tests/components/reolink/test_config_flow.py @@ -474,19 +474,18 @@ async def test_dhcp_ip_update( const.DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp_data ) - expected_calls = [] - for host in host_call_list: - expected_calls.append( - call( - host, - TEST_USERNAME, - TEST_PASSWORD, - port=TEST_PORT, - use_https=TEST_USE_HTTPS, - protocol=DEFAULT_PROTOCOL, - timeout=DEFAULT_TIMEOUT, - ) + expected_calls = [ + call( + host, + TEST_USERNAME, + TEST_PASSWORD, + port=TEST_PORT, + use_https=TEST_USE_HTTPS, + protocol=DEFAULT_PROTOCOL, + timeout=DEFAULT_TIMEOUT, ) + for host in host_call_list + ] assert reolink_connect_class.call_args_list == expected_calls diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index c0420e93f70..d2891cf7b87 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -1292,17 +1292,16 @@ async def test_state_characteristics(hass: HomeAssistant) -> None: "unit": "%", }, ) - sensors_config = [] - for characteristic in characteristics: - sensors_config.append( - { - "platform": "statistics", - "name": f"test_{characteristic['source_sensor_domain']}_{characteristic['name']}", - "entity_id": f"{characteristic['source_sensor_domain']}.test_monitored", - "state_characteristic": characteristic["name"], - "max_age": {"minutes": 8}, # 9 values spaces by one minute - } - ) + sensors_config = [ + { + "platform": "statistics", + "name": f"test_{characteristic['source_sensor_domain']}_{characteristic['name']}", + "entity_id": f"{characteristic['source_sensor_domain']}.test_monitored", + "state_characteristic": characteristic["name"], + "max_age": {"minutes": 8}, # 9 values spaces by one minute + } + for characteristic in characteristics + ] with freeze_time(current_time) as freezer: assert await async_setup_component( diff --git a/tests/components/steam_online/__init__.py b/tests/components/steam_online/__init__.py index 83fdfdda5de..46273a61816 100644 --- a/tests/components/steam_online/__init__.py +++ b/tests/components/steam_online/__init__.py @@ -69,10 +69,10 @@ class MockedInterface(dict): def GetFriendList(self, steamid: str) -> dict: """Get friend list.""" fake_friends = [{"steamid": ACCOUNT_2}] - for _i in range(0, 4): - fake_friends.append( - {"steamid": "".join(random.choices(string.digits, k=len(ACCOUNT_1)))} - ) + fake_friends.extend( + {"steamid": "".join(random.choices(string.digits, k=len(ACCOUNT_1)))} + for _ in range(0, 4) + ) return {"friendslist": {"friends": fake_friends}} def GetPlayerSummaries(self, steamids: str | list[str]) -> dict: diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 8390a1ac049..02fbfc96ab7 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -399,9 +399,7 @@ async def test_hls_max_segments( # Only NUM_PLAYLIST_SEGMENTS are returned in the playlist. start = MAX_SEGMENTS + 1 - NUM_PLAYLIST_SEGMENTS - segments = [] - for sequence in range(start, MAX_SEGMENTS + 1): - segments.append(make_segment(sequence)) + segments = [make_segment(sequence) for sequence in range(start, MAX_SEGMENTS + 1)] assert await resp.text() == make_playlist(sequence=start, segments=segments) # Fetch the actual segments with a fake byte payload @@ -497,9 +495,7 @@ async def test_hls_max_segments_discontinuity( # EXT-X-DISCONTINUITY tag to be omitted and EXT-X-DISCONTINUITY-SEQUENCE # returned instead. start = MAX_SEGMENTS + 1 - NUM_PLAYLIST_SEGMENTS - segments = [] - for sequence in range(start, MAX_SEGMENTS + 1): - segments.append(make_segment(sequence)) + segments = [make_segment(sequence) for sequence in range(start, MAX_SEGMENTS + 1)] assert await resp.text() == make_playlist( sequence=start, discontinuity_sequence=1, diff --git a/tests/components/stream/test_ll_hls.py b/tests/components/stream/test_ll_hls.py index 02fac835bdc..39d66f0141d 100644 --- a/tests/components/stream/test_ll_hls.py +++ b/tests/components/stream/test_ll_hls.py @@ -96,10 +96,10 @@ def make_segment_with_parts( response = [] if discontinuity: response.append("#EXT-X-DISCONTINUITY") - for i in range(num_parts): - response.append( - f'#EXT-X-PART:DURATION={TEST_PART_DURATION:.3f},URI="./segment/{segment}.{i}.m4s"{",INDEPENDENT=YES" if i%independent_period==0 else ""}' - ) + response.extend( + f'#EXT-X-PART:DURATION={TEST_PART_DURATION:.3f},URI="./segment/{segment}.{i}.m4s"{",INDEPENDENT=YES" if i%independent_period==0 else ""}' + for i in range(num_parts) + ) response.extend( [ "#EXT-X-PROGRAM-DATE-TIME:" From 932e073feeaf7f0521e9fd769cc5937ecf261d37 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 13 Mar 2024 22:49:49 +0100 Subject: [PATCH 0913/1691] Split out deCONZ config model (#112851) * Add separate deCONZ config class * Use config in get_deconz_api --- homeassistant/components/deconz/__init__.py | 2 +- .../components/deconz/hub/__init__.py | 1 + homeassistant/components/deconz/hub/api.py | 16 ++-- homeassistant/components/deconz/hub/config.py | 57 +++++++++++++++ homeassistant/components/deconz/hub/hub.py | 73 ++++++------------- tests/components/deconz/test_gateway.py | 12 +-- 6 files changed, 94 insertions(+), 67 deletions(-) create mode 100644 homeassistant/components/deconz/hub/config.py diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 140fbe12d82..776ab8e830f 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -37,7 +37,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b await async_update_master_gateway(hass, config_entry) try: - api = await get_deconz_api(hass, config_entry.data) + api = await get_deconz_api(hass, config_entry) except CannotConnect as err: raise ConfigEntryNotReady from err except AuthenticationRequired as err: diff --git a/homeassistant/components/deconz/hub/__init__.py b/homeassistant/components/deconz/hub/__init__.py index a99a578a89d..b3b70ef2207 100644 --- a/homeassistant/components/deconz/hub/__init__.py +++ b/homeassistant/components/deconz/hub/__init__.py @@ -1,4 +1,5 @@ """Internal functionality not part of HA infrastructure.""" from .api import get_deconz_api # noqa: F401 +from .config import DeconzConfig # noqa: F401 from .hub import DeconzHub, get_gateway_from_config_entry # noqa: F401 diff --git a/homeassistant/components/deconz/hub/api.py b/homeassistant/components/deconz/hub/api.py index 33c49e189d5..71551ead6e1 100644 --- a/homeassistant/components/deconz/hub/api.py +++ b/homeassistant/components/deconz/hub/api.py @@ -3,37 +3,35 @@ from __future__ import annotations import asyncio -from types import MappingProxyType -from typing import Any from pydeconz import DeconzSession, errors -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client from ..const import LOGGER from ..errors import AuthenticationRequired, CannotConnect +from .config import DeconzConfig async def get_deconz_api( - hass: HomeAssistant, config: MappingProxyType[str, Any] + hass: HomeAssistant, config_entry: ConfigEntry ) -> DeconzSession: """Create a gateway object and verify configuration.""" session = aiohttp_client.async_get_clientsession(hass) - api = DeconzSession( - session, config[CONF_HOST], config[CONF_PORT], config[CONF_API_KEY] - ) + config = DeconzConfig.from_config_entry(config_entry) + api = DeconzSession(session, config.host, config.port, config.api_key) try: async with asyncio.timeout(10): await api.refresh_state() return api except errors.Unauthorized as err: - LOGGER.warning("Invalid key for deCONZ at %s", config[CONF_HOST]) + LOGGER.warning("Invalid key for deCONZ at %s", config.host) raise AuthenticationRequired from err except (TimeoutError, errors.RequestError, errors.ResponseError) as err: - LOGGER.error("Error connecting to deCONZ gateway at %s", config[CONF_HOST]) + LOGGER.error("Error connecting to deCONZ gateway at %s", config.host) raise CannotConnect from err diff --git a/homeassistant/components/deconz/hub/config.py b/homeassistant/components/deconz/hub/config.py new file mode 100644 index 00000000000..06d2dc10542 --- /dev/null +++ b/homeassistant/components/deconz/hub/config.py @@ -0,0 +1,57 @@ +"""deCONZ config entry abstraction.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Self + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT + +from ..const import ( + CONF_ALLOW_CLIP_SENSOR, + CONF_ALLOW_DECONZ_GROUPS, + CONF_ALLOW_NEW_DEVICES, + DEFAULT_ALLOW_CLIP_SENSOR, + DEFAULT_ALLOW_DECONZ_GROUPS, + DEFAULT_ALLOW_NEW_DEVICES, +) + + +@dataclass +class DeconzConfig: + """Represent a deCONZ config entry.""" + + entry: ConfigEntry + + host: str + port: int + api_key: str + + allow_clip_sensor: bool + allow_deconz_groups: bool + allow_new_devices: bool + + @classmethod + def from_config_entry(cls, config_entry: ConfigEntry) -> Self: + """Create object from config entry.""" + config = config_entry.data + options = config_entry.options + return cls( + entry=config_entry, + host=config[CONF_HOST], + port=config[CONF_PORT], + api_key=config[CONF_API_KEY], + allow_clip_sensor=options.get( + CONF_ALLOW_CLIP_SENSOR, + DEFAULT_ALLOW_CLIP_SENSOR, + ), + allow_deconz_groups=options.get( + CONF_ALLOW_DECONZ_GROUPS, + DEFAULT_ALLOW_DECONZ_GROUPS, + ), + allow_new_devices=options.get( + CONF_ALLOW_NEW_DEVICES, + DEFAULT_ALLOW_NEW_DEVICES, + ), + ) diff --git a/homeassistant/components/deconz/hub/hub.py b/homeassistant/components/deconz/hub/hub.py index 5c422f845bd..c79ae90aa1a 100644 --- a/homeassistant/components/deconz/hub/hub.py +++ b/homeassistant/components/deconz/hub/hub.py @@ -12,24 +12,18 @@ from pydeconz.interfaces.groups import GroupHandler from pydeconz.models.event import EventType from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from ..const import ( - CONF_ALLOW_CLIP_SENSOR, - CONF_ALLOW_DECONZ_GROUPS, - CONF_ALLOW_NEW_DEVICES, CONF_MASTER_GATEWAY, - DEFAULT_ALLOW_CLIP_SENSOR, - DEFAULT_ALLOW_DECONZ_GROUPS, - DEFAULT_ALLOW_NEW_DEVICES, DOMAIN as DECONZ_DOMAIN, HASSIO_CONFIGURATION_URL, PLATFORMS, ) +from .config import DeconzConfig if TYPE_CHECKING: from ..deconz_event import ( @@ -77,6 +71,7 @@ class DeconzHub: ) -> None: """Initialize the system.""" self.hass = hass + self.config = DeconzConfig.from_config_entry(config_entry) self.config_entry = config_entry self.api = api @@ -99,26 +94,11 @@ class DeconzHub: self.deconz_groups: set[tuple[Callable[[EventType, str], None], str]] = set() self.ignored_devices: set[tuple[Callable[[EventType, str], None], str]] = set() - self.option_allow_clip_sensor = self.config_entry.options.get( - CONF_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR - ) - self.option_allow_deconz_groups = config_entry.options.get( - CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS - ) - self.option_allow_new_devices = config_entry.options.get( - CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES - ) - @property def bridgeid(self) -> str: """Return the unique identifier of the gateway.""" return cast(str, self.config_entry.unique_id) - @property - def host(self) -> str: - """Return the host of the gateway.""" - return cast(str, self.config_entry.data[CONF_HOST]) - @property def master(self) -> bool: """Gateway which is used with deCONZ services without defining id.""" @@ -143,7 +123,7 @@ class DeconzHub: """ if ( not initializing - and not self.option_allow_new_devices + and not self.config.allow_new_devices and not self.ignore_state_updates ): self.ignored_devices.add((async_add_device, device_id)) @@ -151,14 +131,14 @@ class DeconzHub: if isinstance(deconz_device_interface, GroupHandler): self.deconz_groups.add((async_add_device, device_id)) - if not self.option_allow_deconz_groups: + if not self.config.allow_deconz_groups: return if isinstance(deconz_device_interface, SENSORS): device = deconz_device_interface[device_id] if device.type.startswith("CLIP") and not always_ignore_clip_sensors: self.clip_sensors.add((async_add_device, device_id)) - if not self.option_allow_clip_sensor: + if not self.config.allow_clip_sensor: return add_device_callback(EventType.ADDED, device_id) @@ -205,7 +185,7 @@ class DeconzHub: ) # Gateway service - configuration_url = f"http://{self.host}:{self.config_entry.data[CONF_PORT]}" + configuration_url = f"http://{self.config.host}:{self.config.port}" if self.config_entry.source == SOURCE_HASSIO: configuration_url = HASSIO_CONFIGURATION_URL device_registry.async_get_or_create( @@ -222,7 +202,7 @@ class DeconzHub: @staticmethod async def async_config_entry_updated( - hass: HomeAssistant, entry: ConfigEntry + hass: HomeAssistant, config_entry: ConfigEntry ) -> None: """Handle signals of config entry being updated. @@ -231,32 +211,29 @@ class DeconzHub: Causes for this is either discovery updating host address or config entry options changing. """ - if entry.entry_id not in hass.data[DECONZ_DOMAIN]: + if config_entry.entry_id not in hass.data[DECONZ_DOMAIN]: # A race condition can occur if multiple config entries are # unloaded in parallel return - gateway = get_gateway_from_config_entry(hass, entry) - - if gateway.api.host != gateway.host: + gateway = get_gateway_from_config_entry(hass, config_entry) + previous_config = gateway.config + gateway.config = DeconzConfig.from_config_entry(config_entry) + if previous_config.host != gateway.config.host: gateway.api.close() - gateway.api.host = gateway.host + gateway.api.host = gateway.config.host gateway.api.start() return - await gateway.options_updated() + await gateway.options_updated(previous_config) - async def options_updated(self) -> None: + async def options_updated(self, previous_config: DeconzConfig) -> None: """Manage entities affected by config entry options.""" deconz_ids = [] # Allow CLIP sensors - option_allow_clip_sensor = self.config_entry.options.get( - CONF_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR - ) - if option_allow_clip_sensor != self.option_allow_clip_sensor: - self.option_allow_clip_sensor = option_allow_clip_sensor - if option_allow_clip_sensor: + if self.config.allow_clip_sensor != previous_config.allow_clip_sensor: + if self.config.allow_clip_sensor: for add_device, device_id in self.clip_sensors: add_device(EventType.ADDED, device_id) else: @@ -268,12 +245,8 @@ class DeconzHub: # Allow Groups - option_allow_deconz_groups = self.config_entry.options.get( - CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS - ) - if option_allow_deconz_groups != self.option_allow_deconz_groups: - self.option_allow_deconz_groups = option_allow_deconz_groups - if option_allow_deconz_groups: + if self.config.allow_deconz_groups != previous_config.allow_deconz_groups: + if self.config.allow_deconz_groups: for add_device, device_id in self.deconz_groups: add_device(EventType.ADDED, device_id) else: @@ -281,12 +254,8 @@ class DeconzHub: # Allow adding new devices - option_allow_new_devices = self.config_entry.options.get( - CONF_ALLOW_NEW_DEVICES, DEFAULT_ALLOW_NEW_DEVICES - ) - if option_allow_new_devices != self.option_allow_new_devices: - self.option_allow_new_devices = option_allow_new_devices - if option_allow_new_devices: + if self.config.allow_new_devices != previous_config.allow_new_devices: + if self.config.allow_new_devices: self.load_ignored_devices() # Remove entities based on above categories diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index aec39dc5b8b..a1b28ada799 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -152,9 +152,9 @@ async def test_gateway_setup( gateway = get_gateway_from_config_entry(hass, config_entry) assert gateway.bridgeid == BRIDGEID assert gateway.master is True - assert gateway.option_allow_clip_sensor is False - assert gateway.option_allow_deconz_groups is True - assert gateway.option_allow_new_devices is True + assert gateway.config.allow_clip_sensor is False + assert gateway.config.allow_deconz_groups is True + assert gateway.config.allow_new_devices is True assert len(gateway.deconz_ids) == 0 assert len(hass.states.async_all()) == 0 @@ -290,8 +290,9 @@ async def test_reset_after_successful_setup( async def test_get_deconz_api(hass: HomeAssistant) -> None: """Successful call.""" + config_entry = MockConfigEntry(domain=DECONZ_DOMAIN, data=ENTRY_CONFIG) with patch("pydeconz.DeconzSession.refresh_state", return_value=True): - assert await get_deconz_api(hass, ENTRY_CONFIG) + assert await get_deconz_api(hass, config_entry) @pytest.mark.parametrize( @@ -307,8 +308,9 @@ async def test_get_deconz_api_fails( hass: HomeAssistant, side_effect, raised_exception ) -> None: """Failed call.""" + config_entry = MockConfigEntry(domain=DECONZ_DOMAIN, data=ENTRY_CONFIG) with patch( "pydeconz.DeconzSession.refresh_state", side_effect=side_effect, ), pytest.raises(raised_exception): - assert await get_deconz_api(hass, ENTRY_CONFIG) + assert await get_deconz_api(hass, config_entry) From daaadd16e190dc814c7f0180d18996d9595cf5a6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 23:06:00 +0100 Subject: [PATCH 0914/1691] Add service icons to Bluesound (#113281) --- homeassistant/components/bluesound/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/bluesound/icons.json diff --git a/homeassistant/components/bluesound/icons.json b/homeassistant/components/bluesound/icons.json new file mode 100644 index 00000000000..8c886f12dfd --- /dev/null +++ b/homeassistant/components/bluesound/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "join": "mdi:link-variant", + "unjoin": "mdi:link-variant-off", + "set_sleep_timer": "mdi:sleep", + "clear_sleep_timer": "mdi:sleep-off" + } +} From bf02befe4a5f37b26102548588350b2bcbbe9658 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 13 Mar 2024 23:06:33 +0100 Subject: [PATCH 0915/1691] Add service icons to Browser (#113283) --- homeassistant/components/browser/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/browser/icons.json diff --git a/homeassistant/components/browser/icons.json b/homeassistant/components/browser/icons.json new file mode 100644 index 00000000000..7c971009fd7 --- /dev/null +++ b/homeassistant/components/browser/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "browse_url": "mdi:web" + } +} From 1abb448106a2df8c3c41e59262f4985ef9634459 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 01:07:52 +0100 Subject: [PATCH 0916/1691] Add service icons to Command line (#113285) --- homeassistant/components/command_line/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/command_line/icons.json diff --git a/homeassistant/components/command_line/icons.json b/homeassistant/components/command_line/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/command_line/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From e0828f1efc47f0567da7857c0aa1c984046277cc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 15:12:13 -1000 Subject: [PATCH 0917/1691] Ensure apple_tv setup retries later on timeout (#113367) --- homeassistant/components/apple_tv/__init__.py | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index 0b9ef14f253..cd1a1c59127 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -54,6 +54,25 @@ SIGNAL_DISCONNECTED = "apple_tv_disconnected" PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE] +AUTH_EXCEPTIONS = ( + exceptions.AuthenticationError, + exceptions.InvalidCredentialsError, + exceptions.NoCredentialsError, +) +CONNECTION_TIMEOUT_EXCEPTIONS = ( + asyncio.CancelledError, + TimeoutError, + exceptions.ConnectionLostError, + exceptions.ConnectionFailedError, +) +DEVICE_EXCEPTIONS = ( + exceptions.ProtocolError, + exceptions.NoServiceError, + exceptions.PairingError, + exceptions.BackOffError, + exceptions.DeviceIdMissingError, +) + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a config entry for Apple TV.""" @@ -64,27 +83,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await manager.async_first_connect() - except ( - exceptions.AuthenticationError, - exceptions.InvalidCredentialsError, - exceptions.NoCredentialsError, - ) as ex: + except AUTH_EXCEPTIONS as ex: raise ConfigEntryAuthFailed( f"{address}: Authentication failed, try reconfiguring device: {ex}" ) from ex - except ( - asyncio.CancelledError, - exceptions.ConnectionLostError, - exceptions.ConnectionFailedError, - ) as ex: + except CONNECTION_TIMEOUT_EXCEPTIONS as ex: raise ConfigEntryNotReady(f"{address}: {ex}") from ex - except ( - exceptions.ProtocolError, - exceptions.NoServiceError, - exceptions.PairingError, - exceptions.BackOffError, - exceptions.DeviceIdMissingError, - ) as ex: + except DEVICE_EXCEPTIONS as ex: _LOGGER.debug( "Error setting up apple_tv at %s: %s", address, ex, exc_info=ex ) From 9e645e1b00ece7bd8c0de41d7c1c803fd3f79a7e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 16:46:40 -1000 Subject: [PATCH 0918/1691] Bump aiodns to 3.1.1 (#113371) --- homeassistant/components/dnsip/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dnsip/manifest.json b/homeassistant/components/dnsip/manifest.json index e90e4eb2c72..17c0677e4d9 100644 --- a/homeassistant/components/dnsip/manifest.json +++ b/homeassistant/components/dnsip/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dnsip", "iot_class": "cloud_polling", - "requirements": ["aiodns==3.0.0"] + "requirements": ["aiodns==3.1.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index d4926f8a691..f88ec6b61f2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -224,7 +224,7 @@ aiodhcpwatcher==0.8.1 aiodiscover==1.6.1 # homeassistant.components.dnsip -aiodns==3.0.0 +aiodns==3.1.1 # homeassistant.components.eafm aioeafm==0.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc383161299..23be0c89669 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -203,7 +203,7 @@ aiodhcpwatcher==0.8.1 aiodiscover==1.6.1 # homeassistant.components.dnsip -aiodns==3.0.0 +aiodns==3.1.1 # homeassistant.components.eafm aioeafm==0.1.2 From 3d1a65a1c32fde285e0ee10f43d5f5371e833c07 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 17:13:22 -1000 Subject: [PATCH 0919/1691] Bump bluetooth-auto-recovery to 1.4.0 (#113368) --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 1c58a56b47c..62296ddd8b8 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -17,7 +17,7 @@ "bleak==0.21.1", "bleak-retry-connector==3.4.0", "bluetooth-adapters==0.18.0", - "bluetooth-auto-recovery==1.3.0", + "bluetooth-auto-recovery==1.4.0", "bluetooth-data-tools==1.19.0", "dbus-fast==2.21.1", "habluetooth==2.4.2" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9606d51aefd..8dae6c4ed07 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -16,7 +16,7 @@ bcrypt==4.1.2 bleak-retry-connector==3.4.0 bleak==0.21.1 bluetooth-adapters==0.18.0 -bluetooth-auto-recovery==1.3.0 +bluetooth-auto-recovery==1.4.0 bluetooth-data-tools==1.19.0 cached_ipaddress==0.3.0 certifi>=2021.5.30 diff --git a/requirements_all.txt b/requirements_all.txt index f88ec6b61f2..3781416e9a5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -581,7 +581,7 @@ bluemaestro-ble==0.2.3 bluetooth-adapters==0.18.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==1.3.0 +bluetooth-auto-recovery==1.4.0 # homeassistant.components.bluetooth # homeassistant.components.ld2410_ble diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23be0c89669..8bd5a80c190 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -496,7 +496,7 @@ bluemaestro-ble==0.2.3 bluetooth-adapters==0.18.0 # homeassistant.components.bluetooth -bluetooth-auto-recovery==1.3.0 +bluetooth-auto-recovery==1.4.0 # homeassistant.components.bluetooth # homeassistant.components.ld2410_ble From 6338c8d86e900acdf85d5f0fe0ff6a1ec01a08e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 17:24:33 -1000 Subject: [PATCH 0920/1691] Bump aiodiscover to 2.0.0 (#113337) --- homeassistant/components/dhcp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index 75d5b15124f..10f63402c94 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -15,7 +15,7 @@ "quality_scale": "internal", "requirements": [ "aiodhcpwatcher==0.8.1", - "aiodiscover==1.6.1", + "aiodiscover==2.0.0", "cached_ipaddress==0.3.0" ] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8dae6c4ed07..3adc3527379 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,7 +1,7 @@ # Automatically generated by gen_requirements_all.py, do not edit aiodhcpwatcher==0.8.1 -aiodiscover==1.6.1 +aiodiscover==2.0.0 aiohttp-fast-url-dispatcher==0.3.0 aiohttp-zlib-ng==0.3.1 aiohttp==3.9.3 diff --git a/requirements_all.txt b/requirements_all.txt index 3781416e9a5..68bc2489459 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,7 +221,7 @@ aiocomelit==0.9.0 aiodhcpwatcher==0.8.1 # homeassistant.components.dhcp -aiodiscover==1.6.1 +aiodiscover==2.0.0 # homeassistant.components.dnsip aiodns==3.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8bd5a80c190..1999c8c0ac3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -200,7 +200,7 @@ aiocomelit==0.9.0 aiodhcpwatcher==0.8.1 # homeassistant.components.dhcp -aiodiscover==1.6.1 +aiodiscover==2.0.0 # homeassistant.components.dnsip aiodns==3.1.1 From d1d28dbfb816569c04db59122c07b95ee5cfb655 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:01:57 +0100 Subject: [PATCH 0921/1691] Fix uv cache dir env [ci] (#113312) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b012ecb6fd8..2bca52fc638 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -466,7 +466,7 @@ jobs: if: steps.cache-venv.outputs.cache-hit != 'true' uses: actions/cache@v4.0.1 with: - path: ${{ env.UV_CACHE }} + path: ${{ env.UV_CACHE_DIR }} key: >- ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ steps.generate-uv-key.outputs.key }} From 9940f51b95b7ba3423cd6079bd83ecf325bca7c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:13:40 -1000 Subject: [PATCH 0922/1691] Avoid pre-importing config_flows if the integration does not support migration (#113369) * Avoid pre-importing config_flows if the integration does support migration Currently we pre-import the config flow module if it exists since setting up the config entry required comparing the versions found in the config_flow.py. We can avoid the pre-import if the integration does not support async_migrate_entry which means we avoid loading many config flows in memory at startup. * cover * fix missing block * do not call directly * its too fast now, the test gets more along * Update homeassistant/loader.py --- homeassistant/config_entries.py | 22 +++++-- homeassistant/loader.py | 23 ++++++-- tests/components/jellyfin/test_init.py | 1 + tests/components/sonarr/test_init.py | 4 +- .../unifiprotect/test_config_flow.py | 57 +++++++++++++------ tests/test_config_entries.py | 3 + tests/test_loader.py | 21 +++---- 7 files changed, 89 insertions(+), 42 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 4b2eb81c870..b7623e9c8d7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -349,6 +349,9 @@ class ConfigEntry: # Supports remove device self.supports_remove_device: bool | None = None + # Supports migrate + self.supports_migrate: bool | None = None + # Supports options self._supports_options: bool | None = None @@ -491,6 +494,7 @@ class ConfigEntry: self.supports_remove_device = await support_remove_from_device( hass, self.domain ) + try: component = await integration.async_get_component() except ImportError as err: @@ -506,7 +510,12 @@ class ConfigEntry: ) return - if domain_is_integration: + if self.supports_migrate is None: + self.supports_migrate = hasattr(component, "async_migrate_entry") + + if domain_is_integration and self.supports_migrate: + # Avoid loading the config_flow module unless we need to check + # the version to see if we need to migrate try: await integration.async_get_platforms(("config_flow",)) except ImportError as err: @@ -787,11 +796,7 @@ class ConfigEntry: if same_major_version and self.minor_version == handler.MINOR_VERSION: return True - if not (integration := self._integration_for_domain): - integration = await loader.async_get_integration(hass, self.domain) - component = await integration.async_get_component() - supports_migrate = hasattr(component, "async_migrate_entry") - if not supports_migrate: + if not self.supports_migrate: if same_major_version: return True _LOGGER.error( @@ -801,6 +806,11 @@ class ConfigEntry: ) return False + if not (integration := self._integration_for_domain): + integration = await loader.async_get_integration(hass, self.domain) + + component = await integration.async_get_component() + try: result = await component.async_migrate_entry(hass, self) if not isinstance(result, bool): diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 8e44ca38d77..9c961fd7f67 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -961,10 +961,7 @@ class Integration: # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. - load_executor = self.import_executor and ( - self.pkg_path not in sys.modules - or (self.config_flow and f"{self.pkg_path}.config_flow" not in sys.modules) - ) + load_executor = self.import_executor and self.pkg_path not in sys.modules if not load_executor: comp = self._get_component() if debug: @@ -1051,6 +1048,12 @@ class Integration: if preload_platforms: for platform_name in self.platforms_exists(self._platforms_to_preload): + if ( + platform_name == "config_flow" + and not async_config_flow_needs_preload(cache[domain]) + ): + continue + with suppress(ImportError): self.get_platform(platform_name) @@ -1684,3 +1687,15 @@ def async_suggest_report_issue( ) return f"create a bug report at {issue_tracker}" + + +@callback +def async_config_flow_needs_preload(component: ComponentProtocol) -> bool: + """Test if a config_flow for a component needs to be preloaded. + + Currently we need to preload a the config flow if the integration + has a config flow and the component has an async_migrate_entry method + because it means that config_entries will always have to load + it to check if it needs to be migrated. + """ + return hasattr(component, "async_migrate_entry") diff --git a/tests/components/jellyfin/test_init.py b/tests/components/jellyfin/test_init.py index 9bfe37f9874..6e6a0f7219b 100644 --- a/tests/components/jellyfin/test_init.py +++ b/tests/components/jellyfin/test_init.py @@ -67,6 +67,7 @@ async def test_invalid_auth( mock_config_entry.add_to_hass(hass) assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py index 9f512c11074..e663139d33c 100644 --- a/tests/components/sonarr/test_init.py +++ b/tests/components/sonarr/test_init.py @@ -105,7 +105,9 @@ async def test_migrate_config_entry(hass: HomeAssistant) -> None: assert entry.version == 1 assert not entry.unique_id - await entry.async_migrate(hass) + with patch("homeassistant.components.sonarr.async_setup_entry", return_value=True): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() assert entry.data == { CONF_API_KEY: "MOCK_API_KEY", diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 6dfbb907097..a5fbdb8493f 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -219,25 +219,31 @@ async def test_form_reauth_auth( ) mock_config.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - ) - assert result["type"] == FlowResultType.FORM - assert not result["errors"] - flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) - assert flows[0]["context"]["title_placeholders"] == { - "ip_address": "1.1.1.1", - "name": "Mock Title", - } - with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", side_effect=NotAuthorized, + ), patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": mock_config.entry_id, + }, + ) + assert result["type"] == FlowResultType.FORM + assert not result["errors"] + flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) + assert flows[0]["context"]["title_placeholders"] == { + "ip_address": "1.1.1.1", + "name": "Mock Title", + } + result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -257,7 +263,10 @@ async def test_form_reauth_auth( ), patch( "homeassistant.components.unifiprotect.async_setup", return_value=True, - ) as mock_setup: + ) as mock_setup, patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -768,6 +777,20 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa ) mock_config.add_to_hass(hass) + with patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup: + await hass.config_entries.async_setup(mock_config.entry_id) + await hass.async_block_till_done() + + assert mock_config.state == config_entries.ConfigEntryState.LOADED + assert len(mock_setup_entry.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 1 + other_ip_dict = UNIFI_DISCOVERY_DICT.copy() other_ip_dict["source_ip"] = "127.0.0.2" other_ip_dict["direct_connect_domain"] = "nomatchsameip.ui.direct" @@ -823,7 +846,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa "verify_ssl": True, } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup.mock_calls) == 0 async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_no_result( diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 4ada764a9ba..18022ae3a21 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -117,6 +117,7 @@ async def test_call_setup_entry(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 assert entry.state is config_entries.ConfigEntryState.LOADED assert entry.supports_unload + assert entry.supports_migrate async def test_call_setup_entry_without_reload_support(hass: HomeAssistant) -> None: @@ -146,6 +147,7 @@ async def test_call_setup_entry_without_reload_support(hass: HomeAssistant) -> N assert len(mock_setup_entry.mock_calls) == 1 assert entry.state is config_entries.ConfigEntryState.LOADED assert not entry.supports_unload + assert entry.supports_migrate @pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (1, 2), (2, 2)]) @@ -289,6 +291,7 @@ async def test_call_async_migrate_entry_failure_not_supported( ) entry.add_to_hass(hass) assert not entry.supports_unload + entry.supports_migrate = True mock_setup_entry = AsyncMock(return_value=True) diff --git a/tests/test_loader.py b/tests/test_loader.py index a2868976876..5bdf93758b0 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1204,31 +1204,21 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert "test_package_loaded_executor" not in hass.config.components assert "test_package_loaded_executor.config_flow" not in hass.config.components - config_flow_module_name = f"{integration.pkg_path}.config_flow" - module_mock = MagicMock(__file__="__init__.py") - config_flow_module_mock = MagicMock(__file__="config_flow.py") + module_mock = object() def import_module(name: str) -> Any: if name == integration.pkg_path: return module_mock - if name == config_flow_module_name: - return config_flow_module_mock raise ImportError - modules_without_config_flow = { - k: v for k, v in sys.modules.items() if k != config_flow_module_name - } with patch.dict( "sys.modules", - {**modules_without_config_flow, integration.pkg_path: module_mock}, + {**sys.modules, integration.pkg_path: module_mock}, clear=True, ), patch("homeassistant.loader.importlib.import_module", import_module): module = await integration.async_get_component() - # The config flow is missing so we should load - # in the executor - assert "loaded_executor=True" in caplog.text - assert "loaded_executor=False" not in caplog.text + assert "loaded_executor=False" in caplog.text assert module is module_mock caplog.clear() @@ -1236,7 +1226,6 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( "sys.modules", { integration.pkg_path: module_mock, - config_flow_module_name: config_flow_module_mock, }, ), patch("homeassistant.loader.importlib.import_module", import_module): module = await integration.async_get_component() @@ -1246,6 +1235,10 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert "loaded_executor" not in caplog.text assert module is module_mock + # The integration does not implement async_migrate_entry so it + # should not be preloaded + assert integration.get_platform_cached("config_flow") is None + async def test_async_get_component_concurrent_loads( hass: HomeAssistant, From 870caf90c11c091873c9d0d0521962c7758dd1e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:14:13 -1000 Subject: [PATCH 0923/1691] Bump ha-ffmpeg to 3.2.0 (#113297) No longer needs to use the executor to run subprocesses changelog: https://github.com/home-assistant-libs/ha-ffmpeg/compare/3.1.0...3.2.0 --- homeassistant/components/ffmpeg/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ffmpeg/manifest.json b/homeassistant/components/ffmpeg/manifest.json index 65cafc195de..8cd7b1f504d 100644 --- a/homeassistant/components/ffmpeg/manifest.json +++ b/homeassistant/components/ffmpeg/manifest.json @@ -3,5 +3,5 @@ "name": "FFmpeg", "codeowners": [], "documentation": "https://www.home-assistant.io/integrations/ffmpeg", - "requirements": ["ha-ffmpeg==3.1.0"] + "requirements": ["ha-ffmpeg==3.2.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 3adc3527379..dc69875e11e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -25,7 +25,7 @@ cryptography==42.0.5 dbus-fast==2.21.1 fnv-hash-fast==0.5.0 ha-av==10.1.1 -ha-ffmpeg==3.1.0 +ha-ffmpeg==3.2.0 habluetooth==2.4.2 hass-nabucasa==0.78.0 hassil==1.6.1 diff --git a/requirements_all.txt b/requirements_all.txt index 68bc2489459..e04598e7aea 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1016,7 +1016,7 @@ h2==4.1.0 ha-av==10.1.1 # homeassistant.components.ffmpeg -ha-ffmpeg==3.1.0 +ha-ffmpeg==3.2.0 # homeassistant.components.iotawatt ha-iotawattpy==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1999c8c0ac3..233f3be1b60 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -827,7 +827,7 @@ h2==4.1.0 ha-av==10.1.1 # homeassistant.components.ffmpeg -ha-ffmpeg==3.1.0 +ha-ffmpeg==3.2.0 # homeassistant.components.iotawatt ha-iotawattpy==0.1.1 From 9ef0a8cb95addd21fc967882ba6fd6c1aa46819b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:15:19 -1000 Subject: [PATCH 0924/1691] Add default_config to the bootstrap pre-imports (#113263) This does not make default_config a requirement, it only preloads the python code for the integration so it does not have to be loaded when the import executor is busy. While its a tiny init file, it always ends up at the end of the line and delays startup --- homeassistant/bootstrap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index f1ccfcbdb89..416b26f01d7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -32,6 +32,7 @@ from .components import ( api as api_pre_import, # noqa: F401 auth as auth_pre_import, # noqa: F401 config as config_pre_import, # noqa: F401 + default_config as default_config_pre_import, # noqa: F401 device_automation as device_automation_pre_import, # noqa: F401 diagnostics as diagnostics_pre_import, # noqa: F401 file_upload as file_upload_pre_import, # noqa: F401 From 7f37732e715137ac6c00a741fdf01571571c29d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:22:15 -1000 Subject: [PATCH 0925/1691] Migrate solax away from using async_add_job (#113257) The plan is to deprecate `async_add_job` to reduce the number of job APIs we have to maintain. See #113179 for additional history. `async_add_job` was not being used as expected here --- homeassistant/components/solax/sensor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/solax/sensor.py b/homeassistant/components/solax/sensor.py index dc3b7e522a3..ccd1a8c96c9 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -103,8 +103,12 @@ async def async_setup_entry( serial = resp.serial_number version = resp.version endpoint = RealTimeDataEndpoint(hass, api) - hass.async_add_job(endpoint.async_refresh) - async_track_time_interval(hass, endpoint.async_refresh, SCAN_INTERVAL) + entry.async_create_background_task( + hass, endpoint.async_refresh(), f"solax {entry.title} initial refresh" + ) + entry.async_on_unload( + async_track_time_interval(hass, endpoint.async_refresh, SCAN_INTERVAL) + ) devices = [] for sensor, (idx, measurement) in api.inverter.sensor_map().items(): description = SENSOR_DESCRIPTIONS[(measurement.unit, measurement.is_monotonic)] From 9d1c683a706e092bba51954dc774b6aa99be8dc0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:22:40 -1000 Subject: [PATCH 0926/1691] Remove async_add_job calls from configurator (#113256) The plan is to deprecate `async_add_job` to reduce the number of job APIs we have to maintain. See #113179 for additional history. This one got the smallest change possible since its likely to go away as well at some point --- homeassistant/components/configurator/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/configurator/__init__.py b/homeassistant/components/configurator/__init__.py index e49b6b25d6c..0579df90dc9 100644 --- a/homeassistant/components/configurator/__init__.py +++ b/homeassistant/components/configurator/__init__.py @@ -15,7 +15,12 @@ import functools as ft from typing import Any from homeassistant.const import ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME -from homeassistant.core import HomeAssistant, ServiceCall, callback as async_callback +from homeassistant.core import ( + HassJob, + HomeAssistant, + ServiceCall, + callback as async_callback, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_call_later @@ -245,7 +250,9 @@ class Configurator: # field validation goes here? if callback and ( - job := self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {})) + job := self.hass.async_add_hass_job( + HassJob(callback), call.data.get(ATTR_FIELDS, {}) + ) ): await job From 4f113f256fbadd3ce2c4e584e4b2644e5972c639 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:23:25 -1000 Subject: [PATCH 0927/1691] Migrate script integration to create eager tasks (#113189) Along with #113183 the script execution may be able to synchronously without having to be scheduled on the event loop --- homeassistant/components/script/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 12abf51a925..2be2ce7b062 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -56,6 +56,7 @@ from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.trace import trace_get, trace_path from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from homeassistant.util.async_ import create_eager_task from homeassistant.util.dt import parse_datetime from .config import ScriptConfig @@ -227,7 +228,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await asyncio.wait( [ - asyncio.create_task(script_entity.async_turn_off()) + create_eager_task(script_entity.async_turn_off()) for script_entity in script_entities ] ) @@ -576,7 +577,7 @@ class ScriptEntity(BaseScriptEntity, RestoreEntity): script_stack_cv.set([]) self._changed.clear() - self.hass.async_create_task(coro) + self.hass.async_create_task(coro, eager_start=True) # Wait for first state change so we can guarantee that # it is written to the State Machine before we return. await self._changed.wait() From cfe14bca8f590d5b26f6dce05760b72949370e54 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:26:33 -1000 Subject: [PATCH 0928/1691] Add a helper to import modules from the event loop (#113169) * Add a helper to import modules in the event loop Replaces the one used for triggers with a more generic helper that can be reused and uses a future to avoid importing concurrently * Add a helper to import modules in the event loop Replaces the one used for triggers with a more generic helper that can be reused and uses a future to avoid importing concurrently * coverage * make sure we do not retry * coverage --- .../components/homeassistant/trigger.py | 20 ++--- homeassistant/helpers/importlib.py | 65 +++++++++++++++ tests/helpers/test_importlib.py | 81 +++++++++++++++++++ 3 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 homeassistant/helpers/importlib.py create mode 100644 tests/helpers/test_importlib.py diff --git a/homeassistant/components/homeassistant/trigger.py b/homeassistant/components/homeassistant/trigger.py index 74a96fce784..495cd07502a 100644 --- a/homeassistant/components/homeassistant/trigger.py +++ b/homeassistant/components/homeassistant/trigger.py @@ -1,9 +1,10 @@ """Home Assistant trigger dispatcher.""" -import importlib +from typing import cast from homeassistant.const import CONF_PLATFORM from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.importlib import async_import_module from homeassistant.helpers.trigger import ( TriggerActionType, TriggerInfo, @@ -11,26 +12,15 @@ from homeassistant.helpers.trigger import ( ) from homeassistant.helpers.typing import ConfigType -DATA_TRIGGER_PLATFORMS = "homeassistant_trigger_platforms" - - -def _get_trigger_platform(platform_name: str) -> TriggerProtocol: - """Get trigger platform.""" - return importlib.import_module(f"..triggers.{platform_name}", __name__) - async def _async_get_trigger_platform( hass: HomeAssistant, platform_name: str ) -> TriggerProtocol: """Get trigger platform from cache or import it.""" - cache: dict[str, TriggerProtocol] = hass.data.setdefault(DATA_TRIGGER_PLATFORMS, {}) - if platform := cache.get(platform_name): - return platform - platform = await hass.async_add_import_executor_job( - _get_trigger_platform, platform_name + platform = await async_import_module( + hass, f"homeassistant.components.homeassistant.triggers.{platform_name}" ) - cache[platform_name] = platform - return platform + return cast(TriggerProtocol, platform) async def async_validate_trigger_config( diff --git a/homeassistant/helpers/importlib.py b/homeassistant/helpers/importlib.py new file mode 100644 index 00000000000..00af75f6d8e --- /dev/null +++ b/homeassistant/helpers/importlib.py @@ -0,0 +1,65 @@ +"""Helper to import modules from asyncio.""" + +from __future__ import annotations + +import asyncio +from contextlib import suppress +import importlib +import logging +import sys +from types import ModuleType + +from homeassistant.core import HomeAssistant + +_LOGGER = logging.getLogger(__name__) + +DATA_IMPORT_CACHE = "import_cache" +DATA_IMPORT_FUTURES = "import_futures" +DATA_IMPORT_FAILURES = "import_failures" + + +def _get_module(cache: dict[str, ModuleType], name: str) -> ModuleType: + """Get a module.""" + cache[name] = importlib.import_module(name) + return cache[name] + + +async def async_import_module(hass: HomeAssistant, name: str) -> ModuleType: + """Import a module or return it from the cache.""" + cache: dict[str, ModuleType] = hass.data.setdefault(DATA_IMPORT_CACHE, {}) + if module := cache.get(name): + return module + + failure_cache: dict[str, BaseException] = hass.data.setdefault( + DATA_IMPORT_FAILURES, {} + ) + if exception := failure_cache.get(name): + raise exception + + import_futures: dict[str, asyncio.Future[ModuleType]] + import_futures = hass.data.setdefault(DATA_IMPORT_FUTURES, {}) + + if future := import_futures.get(name): + return await future + + if name in sys.modules: + return _get_module(cache, name) + + import_future = hass.loop.create_future() + import_futures[name] = import_future + try: + module = await hass.async_add_import_executor_job(_get_module, cache, name) + import_future.set_result(module) + except BaseException as ex: + failure_cache[name] = ex + import_future.set_exception(ex) + with suppress(BaseException): + # Set the exception retrieved flag on the future since + # it will never be retrieved unless there + # are concurrent calls + import_future.result() + raise + finally: + del import_futures[name] + + return module diff --git a/tests/helpers/test_importlib.py b/tests/helpers/test_importlib.py new file mode 100644 index 00000000000..7f89018ded2 --- /dev/null +++ b/tests/helpers/test_importlib.py @@ -0,0 +1,81 @@ +"""Tests for the importlib helper.""" + +import time +from typing import Any +from unittest.mock import patch + +import pytest + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import importlib + +from tests.common import MockModule + + +async def test_async_import_module(hass: HomeAssistant) -> None: + """Test importing a module.""" + mock_module = MockModule() + with patch( + "homeassistant.helpers.importlib.importlib.import_module", + return_value=mock_module, + ): + module = await importlib.async_import_module(hass, "test.module") + + assert module is mock_module + + +async def test_async_import_module_on_helper(hass: HomeAssistant) -> None: + """Test importing the importlib helper.""" + module = await importlib.async_import_module( + hass, "homeassistant.helpers.importlib" + ) + assert module is importlib + module = await importlib.async_import_module( + hass, "homeassistant.helpers.importlib" + ) + assert module is importlib + + +async def test_async_import_module_failures(hass: HomeAssistant) -> None: + """Test importing a module fails.""" + with patch( + "homeassistant.helpers.importlib.importlib.import_module", + side_effect=ImportError, + ), pytest.raises(ImportError): + await importlib.async_import_module(hass, "test.module") + + mock_module = MockModule() + # The failure should be cached + with pytest.raises(ImportError), patch( + "homeassistant.helpers.importlib.importlib.import_module", + return_value=mock_module, + ): + await importlib.async_import_module(hass, "test.module") + + +@pytest.mark.parametrize("eager_start", [True, False]) +async def test_async_import_module_concurrency( + hass: HomeAssistant, eager_start: bool +) -> None: + """Test importing a module with concurrency.""" + mock_module = MockModule() + + def _mock_import(name: str, *args: Any) -> MockModule: + time.sleep(0.1) + return mock_module + + with patch( + "homeassistant.helpers.importlib.importlib.import_module", + _mock_import, + ): + task1 = hass.async_create_task( + importlib.async_import_module(hass, "test.module"), eager_start=eager_start + ) + task2 = hass.async_create_task( + importlib.async_import_module(hass, "test.module"), eager_start=eager_start + ) + module1 = await task1 + module2 = await task2 + + assert module1 is mock_module + assert module2 is mock_module From 4f326df088d02f8c1a30126b0ec90897f10afebe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 18:34:37 -1000 Subject: [PATCH 0929/1691] Remove async_add_job calls from qwikswitch (#113258) --- homeassistant/components/qwikswitch/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/qwikswitch/__init__.py b/homeassistant/components/qwikswitch/__init__.py index 8c753da7cc2..eea110a02d7 100644 --- a/homeassistant/components/qwikswitch/__init__.py +++ b/homeassistant/components/qwikswitch/__init__.py @@ -225,7 +225,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async_dispatcher_send(hass, qspacket[QS_ID], qspacket) # Update all ha_objects - hass.async_add_job(qsusb.update_from_devices) + hass.async_create_task(qsusb.update_from_devices()) @callback def async_start(_): From 4ed3ea3b02c42e64ef7cd74cc3ee0cd9f2944a3f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 06:10:48 +0100 Subject: [PATCH 0930/1691] Add service icons to REST command (#113347) --- homeassistant/components/rest_command/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/rest_command/icons.json diff --git a/homeassistant/components/rest_command/icons.json b/homeassistant/components/rest_command/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/rest_command/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From c3b5e819c5805cbcb186a6baa16a144fef2f838b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 19:18:15 -1000 Subject: [PATCH 0931/1691] Fix group loading too late resulting in incorrect state (#113262) --- homeassistant/bootstrap.py | 1 + homeassistant/components/air_quality/__init__.py | 2 ++ .../components/alarm_control_panel/__init__.py | 1 + homeassistant/components/binary_sensor/__init__.py | 2 ++ homeassistant/components/climate/__init__.py | 1 + homeassistant/components/cover/__init__.py | 2 ++ homeassistant/components/device_tracker/__init__.py | 1 + homeassistant/components/fan/__init__.py | 2 ++ homeassistant/components/group/__init__.py | 10 +++++++--- homeassistant/components/group/config_flow.py | 8 +++++--- homeassistant/components/group/const.py | 2 ++ homeassistant/components/humidifier/__init__.py | 1 + homeassistant/components/light/__init__.py | 2 ++ homeassistant/components/lock/__init__.py | 2 ++ homeassistant/components/media_player/__init__.py | 1 + homeassistant/components/person/__init__.py | 2 ++ homeassistant/components/plant/__init__.py | 1 + homeassistant/components/remote/__init__.py | 2 ++ homeassistant/components/sensor/__init__.py | 1 + homeassistant/components/switch/__init__.py | 1 + homeassistant/components/vacuum/__init__.py | 13 ++++++++----- homeassistant/components/vacuum/const.py | 8 ++++++++ homeassistant/components/vacuum/group.py | 2 +- homeassistant/components/water_heater/__init__.py | 2 ++ homeassistant/components/water_heater/const.py | 8 ++++++++ homeassistant/components/water_heater/group.py | 2 +- homeassistant/components/weather/__init__.py | 1 + 27 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/vacuum/const.py create mode 100644 homeassistant/components/water_heater/const.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 416b26f01d7..294a5551496 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -36,6 +36,7 @@ from .components import ( device_automation as device_automation_pre_import, # noqa: F401 diagnostics as diagnostics_pre_import, # noqa: F401 file_upload as file_upload_pre_import, # noqa: F401 + group as group_pre_import, # noqa: F401 history as history_pre_import, # noqa: F401 http, # not named pre_import since it has requirements image_upload as image_upload_import, # noqa: F401 - not named pre_import since it has requirements diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index b7104d46152..a25874f0c2f 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -17,6 +17,8 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType, StateType +from . import group as group_pre_import # noqa: F401 + _LOGGER: Final = logging.getLogger(__name__) ATTR_AQI: Final = "air_quality_index" diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index a4f950d4db2..37a2fa6a315 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -33,6 +33,7 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType +from . import group as group_pre_import # noqa: F401 from .const import ( # noqa: F401 _DEPRECATED_FORMAT_NUMBER, _DEPRECATED_FORMAT_TEXT, diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 4fd99c309bc..b9f612257b6 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -28,6 +28,8 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index c4faed875e9..7265b5192e8 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -42,6 +42,7 @@ from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType from homeassistant.util.unit_conversion import TemperatureConverter +from . import group as group_pre_import # noqa: F401 from .const import ( # noqa: F401 _DEPRECATED_HVAC_MODE_AUTO, _DEPRECATED_HVAC_MODE_COOL, diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 3261ee90f29..aed0680bad3 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -44,6 +44,8 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 4081e669b00..e16712acf95 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -14,6 +14,7 @@ from homeassistant.helpers.deprecation import ( from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 from .config_entry import ( # noqa: F401 ScannerEntity, TrackerEntity, diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index aeb3a6c89df..d39e38f8e25 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -40,6 +40,8 @@ from homeassistant.util.percentage import ( ranged_value_to_percentage, ) +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 55a4ab4ad17..778c4da9c9f 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -52,9 +52,11 @@ from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from .const import CONF_HIDE_MEMBERS +from .const import ( + CONF_HIDE_MEMBERS, + DOMAIN, # noqa: F401 +) -DOMAIN = "group" GROUP_ORDER = "group_order" ENTITY_ID_FORMAT = DOMAIN + ".{}" @@ -241,7 +243,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[REG_KEY] = GroupIntegrationRegistry() - await async_process_integration_platforms(hass, DOMAIN, _process_group_platform) + await async_process_integration_platforms( + hass, DOMAIN, _process_group_platform, wait_for_platforms=True + ) await _async_process_config(hass, config) diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index 71243ffbd74..237eb570417 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable, Coroutine, Mapping from functools import partial -from typing import Any, cast +from typing import TYPE_CHECKING, Any, cast import voluptuous as vol @@ -22,9 +22,11 @@ from homeassistant.helpers.schema_config_entry_flow import ( entity_selector_without_own_entities, ) -from . import DOMAIN, GroupEntity +if TYPE_CHECKING: + from . import GroupEntity + from .binary_sensor import CONF_ALL, async_create_preview_binary_sensor -from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC +from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC, DOMAIN from .cover import async_create_preview_cover from .event import async_create_preview_event from .fan import async_create_preview_fan diff --git a/homeassistant/components/group/const.py b/homeassistant/components/group/const.py index 3ef280b2770..e64358181ca 100644 --- a/homeassistant/components/group/const.py +++ b/homeassistant/components/group/const.py @@ -2,3 +2,5 @@ CONF_HIDE_MEMBERS = "hide_members" CONF_IGNORE_NON_NUMERIC = "ignore_non_numeric" + +DOMAIN = "group" diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 97f78aba21c..5dba7d8e32c 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -34,6 +34,7 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 from .const import ( # noqa: F401 _DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER, _DEPRECATED_DEVICE_CLASS_HUMIDIFIER, diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 0a41ca2a84e..610dc662369 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -34,6 +34,8 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass import homeassistant.util.color as color_util +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index f17c81aed20..da342410308 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -42,6 +42,8 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType, StateType +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 4bb026ffc0d..cc768232287 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -63,6 +63,7 @@ from homeassistant.helpers.network import get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 from .browse_media import BrowseMedia, async_process_play_media_url # noqa: F401 from .const import ( # noqa: F401 ATTR_APP_ID, diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index d75531d3052..08fff3a652e 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -57,6 +57,8 @@ from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 + _LOGGER = logging.getLogger(__name__) ATTR_SOURCE = "source" diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index efa0b45d25c..076f93faf7b 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -32,6 +32,7 @@ from homeassistant.helpers.event import ( from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util +from . import group as group_pre_import # noqa: F401 from .const import ( ATTR_DICT_OF_UNITS_OF_MEASUREMENT, ATTR_MAX_BRIGHTNESS_HISTORY, diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index fffbc913fe8..e8f100a1e8c 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -37,6 +37,8 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index e56037e3cee..0b2eb911cf2 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -70,6 +70,7 @@ from homeassistant.helpers.typing import UNDEFINED, ConfigType, StateType, Undef from homeassistant.util import dt as dt_util from homeassistant.util.enum import try_parse_enum +from . import group as group_pre_import # noqa: F401 from .const import ( # noqa: F401 _DEPRECATED_STATE_CLASS_MEASUREMENT, _DEPRECATED_STATE_CLASS_TOTAL, diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 86c67248eea..e8fcaf1223e 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -33,6 +33,7 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 from .const import DOMAIN if TYPE_CHECKING: diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index cdac8c553ff..1be2c6c6796 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -35,6 +35,14 @@ from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +from . import group as group_pre_import # noqa: F401 +from .const import ( # noqa: F401 + STATE_CLEANING, + STATE_DOCKED, + STATE_ERROR, + STATE_RETURNING, +) + if TYPE_CHECKING: from functools import cached_property else: @@ -64,11 +72,6 @@ SERVICE_PAUSE = "pause" SERVICE_STOP = "stop" -STATE_CLEANING = "cleaning" -STATE_DOCKED = "docked" -STATE_RETURNING = "returning" -STATE_ERROR = "error" - STATES = [STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR] DEFAULT_NAME = "Vacuum cleaner robot" diff --git a/homeassistant/components/vacuum/const.py b/homeassistant/components/vacuum/const.py new file mode 100644 index 00000000000..f623d313b1a --- /dev/null +++ b/homeassistant/components/vacuum/const.py @@ -0,0 +1,8 @@ +"""Support for vacuum cleaner robots (botvacs).""" + +STATE_CLEANING = "cleaning" +STATE_DOCKED = "docked" +STATE_RETURNING = "returning" +STATE_ERROR = "error" + +STATES = [STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR] diff --git a/homeassistant/components/vacuum/group.py b/homeassistant/components/vacuum/group.py index 71aecfbabc5..e856bd460c0 100644 --- a/homeassistant/components/vacuum/group.py +++ b/homeassistant/components/vacuum/group.py @@ -4,7 +4,7 @@ from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback -from . import STATE_CLEANING, STATE_ERROR, STATE_RETURNING +from .const import STATE_CLEANING, STATE_ERROR, STATE_RETURNING @callback diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index dad3bd8c48b..5bd414ee53e 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -42,6 +42,8 @@ from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType from homeassistant.util.unit_conversion import TemperatureConverter +from . import group as group_pre_import # noqa: F401 + if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/water_heater/const.py b/homeassistant/components/water_heater/const.py new file mode 100644 index 00000000000..5bf0816348c --- /dev/null +++ b/homeassistant/components/water_heater/const.py @@ -0,0 +1,8 @@ +"""Support for water heater devices.""" + +STATE_ECO = "eco" +STATE_ELECTRIC = "electric" +STATE_PERFORMANCE = "performance" +STATE_HIGH_DEMAND = "high_demand" +STATE_HEAT_PUMP = "heat_pump" +STATE_GAS = "gas" diff --git a/homeassistant/components/water_heater/group.py b/homeassistant/components/water_heater/group.py index 16387d5abd7..7ae13131210 100644 --- a/homeassistant/components/water_heater/group.py +++ b/homeassistant/components/water_heater/group.py @@ -4,7 +4,7 @@ from homeassistant.components.group import GroupIntegrationRegistry from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, callback -from . import ( +from .const import ( STATE_ECO, STATE_ELECTRIC, STATE_GAS, diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 5c4c53079ba..76dc0508545 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -61,6 +61,7 @@ from homeassistant.util.dt import utcnow from homeassistant.util.json import JsonValueType from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM +from . import group as group_pre_import # noqa: F401 from .const import ( # noqa: F401 ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_CLOUD_COVERAGE, From c1f5c7c4b7bfcc03d45492c9015bb10cd0cdd240 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 19:33:33 -1000 Subject: [PATCH 0932/1691] Remove usage of async_add_job in tests (#113259) --- tests/components/alert/test_init.py | 2 +- tests/components/counter/common.py | 6 +++--- tests/components/device_tracker/common.py | 2 +- tests/components/ffmpeg/test_binary_sensor.py | 4 ++-- tests/components/ffmpeg/test_init.py | 6 +++--- tests/components/group/common.py | 6 +++--- tests/components/group/test_init.py | 2 ++ tests/components/homematicip_cloud/test_hap.py | 2 +- tests/components/image_processing/common.py | 2 +- tests/components/mqtt/test_util.py | 10 ++++------ tests/components/python_script/test_init.py | 2 +- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/components/alert/test_init.py b/tests/components/alert/test_init.py index 8dfbb437646..7c4030b56da 100644 --- a/tests/components/alert/test_init.py +++ b/tests/components/alert/test_init.py @@ -342,6 +342,6 @@ async def test_done_message_state_tracker_reset_on_cancel(hass: HomeAssistant) - entity._cancel = lambda *args: None assert entity._send_done_message is False entity._send_done_message = True - hass.async_add_job(entity.end_alerting) + await entity.end_alerting() await hass.async_block_till_done() assert entity._send_done_message is False diff --git a/tests/components/counter/common.py b/tests/components/counter/common.py index 3cd7dd3c300..72d183a7b0f 100644 --- a/tests/components/counter/common.py +++ b/tests/components/counter/common.py @@ -19,7 +19,7 @@ from homeassistant.loader import bind_hass @bind_hass def async_increment(hass, entity_id): """Increment a counter.""" - hass.async_add_job( + hass.create_task( hass.services.async_call(DOMAIN, SERVICE_INCREMENT, {ATTR_ENTITY_ID: entity_id}) ) @@ -28,7 +28,7 @@ def async_increment(hass, entity_id): @bind_hass def async_decrement(hass, entity_id): """Decrement a counter.""" - hass.async_add_job( + hass.create_task( hass.services.async_call(DOMAIN, SERVICE_DECREMENT, {ATTR_ENTITY_ID: entity_id}) ) @@ -37,6 +37,6 @@ def async_decrement(hass, entity_id): @bind_hass def async_reset(hass, entity_id): """Reset a counter.""" - hass.async_add_job( + hass.create_task( hass.services.async_call(DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id}) ) diff --git a/tests/components/device_tracker/common.py b/tests/components/device_tracker/common.py index 499c3ac6cda..6e2c7b253c3 100644 --- a/tests/components/device_tracker/common.py +++ b/tests/components/device_tracker/common.py @@ -50,4 +50,4 @@ def async_see( } if attributes: data[ATTR_ATTRIBUTES] = attributes - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SEE, data)) + hass.create_task(hass.services.async_call(DOMAIN, SERVICE_SEE, data)) diff --git a/tests/components/ffmpeg/test_binary_sensor.py b/tests/components/ffmpeg/test_binary_sensor.py index 72d6fee5e7f..8b1a5115f86 100644 --- a/tests/components/ffmpeg/test_binary_sensor.py +++ b/tests/components/ffmpeg/test_binary_sensor.py @@ -65,7 +65,7 @@ async def test_noise_setup_component_start_callback(mock_ffmpeg, hass: HomeAssis entity = hass.states.get("binary_sensor.ffmpeg_noise") assert entity.state == "off" - hass.async_add_job(mock_ffmpeg.call_args[0][1], True) + mock_ffmpeg.call_args[0][1](True) await hass.async_block_till_done() entity = hass.states.get("binary_sensor.ffmpeg_noise") @@ -121,7 +121,7 @@ async def test_motion_setup_component_start_callback(mock_ffmpeg, hass: HomeAssi entity = hass.states.get("binary_sensor.ffmpeg_motion") assert entity.state == "off" - hass.async_add_job(mock_ffmpeg.call_args[0][1], True) + mock_ffmpeg.call_args[0][1](True) await hass.async_block_till_done() entity = hass.states.get("binary_sensor.ffmpeg_motion") diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 5ead1bcf942..e945a26e05b 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -27,7 +27,7 @@ def async_start(hass, entity_id=None): This is a legacy helper method. Do not use it for new tests. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_START, data)) + hass.create_task(hass.services.async_call(DOMAIN, SERVICE_START, data)) @callback @@ -37,7 +37,7 @@ def async_stop(hass, entity_id=None): This is a legacy helper method. Do not use it for new tests. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_STOP, data)) + hass.create_task(hass.services.async_call(DOMAIN, SERVICE_STOP, data)) @callback @@ -47,7 +47,7 @@ def async_restart(hass, entity_id=None): This is a legacy helper method. Do not use it for new tests. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RESTART, data)) + hass.create_task(hass.services.async_call(DOMAIN, SERVICE_RESTART, data)) class MockFFmpegDev(ffmpeg.FFmpegBase): diff --git a/tests/components/group/common.py b/tests/components/group/common.py index d24deb2f34f..395fc990930 100644 --- a/tests/components/group/common.py +++ b/tests/components/group/common.py @@ -27,7 +27,7 @@ def reload(hass): @bind_hass def async_reload(hass): """Reload the automation from config.""" - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RELOAD)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_RELOAD)) @bind_hass @@ -74,7 +74,7 @@ def async_set_group( if value is not None } - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SET, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_SET, data)) @callback @@ -82,4 +82,4 @@ def async_set_group( def async_remove(hass, object_id): """Remove a user group.""" data = {ATTR_OBJECT_ID: object_id} - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_REMOVE, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_REMOVE, data)) diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index c609c705f21..66b744cddc9 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -1232,6 +1232,8 @@ async def test_group_vacuum_on(hass: HomeAssistant) -> None: async def test_device_tracker_not_home(hass: HomeAssistant) -> None: """Test group of device_tracker not_home.""" + await async_setup_component(hass, "device_tracker", {}) + await hass.async_block_till_done() hass.states.async_set("device_tracker.one", "not_home") hass.states.async_set("device_tracker.two", "not_home") hass.states.async_set("device_tracker.three", "not_home") diff --git a/tests/components/homematicip_cloud/test_hap.py b/tests/components/homematicip_cloud/test_hap.py index de47360770f..72bdb5cc0fc 100644 --- a/tests/components/homematicip_cloud/test_hap.py +++ b/tests/components/homematicip_cloud/test_hap.py @@ -101,7 +101,7 @@ async def test_hap_setup_connection_error() -> None: ): assert not await hap.async_setup() - assert not hass.async_add_job.mock_calls + assert not hass.async_add_hass_job.mock_calls assert not hass.config_entries.flow.async_init.mock_calls diff --git a/tests/components/image_processing/common.py b/tests/components/image_processing/common.py index d0354696e8a..4b3a008c6cd 100644 --- a/tests/components/image_processing/common.py +++ b/tests/components/image_processing/common.py @@ -21,4 +21,4 @@ def scan(hass, entity_id=ENTITY_MATCH_ALL): def async_scan(hass, entity_id=ENTITY_MATCH_ALL): """Force process of all cameras or given entity.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SCAN, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_SCAN, data)) diff --git a/tests/components/mqtt/test_util.py b/tests/components/mqtt/test_util.py index 0b8d7002319..b07dfc1f642 100644 --- a/tests/components/mqtt/test_util.py +++ b/tests/components/mqtt/test_util.py @@ -159,10 +159,8 @@ async def test_waiting_for_client_not_loaded( unsubs.append(await mqtt.async_subscribe(hass, "test_topic", lambda msg: None)) # Simulate some integration waiting for the client to become available - hass.async_add_job(_async_just_in_time_subscribe) - hass.async_add_job(_async_just_in_time_subscribe) - hass.async_add_job(_async_just_in_time_subscribe) - hass.async_add_job(_async_just_in_time_subscribe) + for _ in range(4): + hass.async_create_task(_async_just_in_time_subscribe()) assert entry.state == ConfigEntryState.NOT_LOADED assert await hass.config_entries.async_setup(entry.entry_id) @@ -210,7 +208,7 @@ async def test_waiting_for_client_entry_fails( async def _async_just_in_time_subscribe() -> Callable[[], None]: assert not await mqtt.async_wait_for_mqtt_client(hass) - hass.async_add_job(_async_just_in_time_subscribe) + hass.async_create_task(_async_just_in_time_subscribe()) assert entry.state == ConfigEntryState.NOT_LOADED with patch( "homeassistant.components.mqtt.async_setup_entry", @@ -238,7 +236,7 @@ async def test_waiting_for_client_setup_fails( async def _async_just_in_time_subscribe() -> Callable[[], None]: assert not await mqtt.async_wait_for_mqtt_client(hass) - hass.async_add_job(_async_just_in_time_subscribe) + hass.async_create_task(_async_just_in_time_subscribe()) assert entry.state == ConfigEntryState.NOT_LOADED # Simulate MQTT setup fails before the client would become available diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index 81a98f5828a..ced279b2f9a 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -250,7 +250,7 @@ for index, value in enumerate(["earth", "mars"]): hass.states.set('hello.{}'.format(index), value) """ - hass.async_add_job(execute, hass, "test.py", source, {}) + await hass.async_add_executor_job(execute, hass, "test.py", source, {}) await hass.async_block_till_done() assert hass.states.is_state("hello.0", "earth") From fe99d800543fc170766b63ca62851eb5e384be3e Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 14 Mar 2024 00:44:07 -0500 Subject: [PATCH 0933/1691] Add diagnostics for IPP (#113205) --- homeassistant/components/ipp/diagnostics.py | 28 +++++ .../ipp/snapshots/test_diagnostics.ambr | 100 ++++++++++++++++++ tests/components/ipp/test_diagnostics.py | 22 ++++ 3 files changed, 150 insertions(+) create mode 100644 homeassistant/components/ipp/diagnostics.py create mode 100644 tests/components/ipp/snapshots/test_diagnostics.ambr create mode 100644 tests/components/ipp/test_diagnostics.py diff --git a/homeassistant/components/ipp/diagnostics.py b/homeassistant/components/ipp/diagnostics.py new file mode 100644 index 00000000000..67b84183977 --- /dev/null +++ b/homeassistant/components/ipp/diagnostics.py @@ -0,0 +1,28 @@ +"""Diagnostics support for Internet Printing Protocol (IPP).""" + +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import IPPDataUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: IPPDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + return { + "entry": { + "data": { + **config_entry.data, + }, + "unique_id": config_entry.unique_id, + }, + "data": coordinator.data.as_dict(), + } diff --git a/tests/components/ipp/snapshots/test_diagnostics.ambr b/tests/components/ipp/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..98d0055c982 --- /dev/null +++ b/tests/components/ipp/snapshots/test_diagnostics.ambr @@ -0,0 +1,100 @@ +# serializer version: 1 +# name: test_diagnostics + dict({ + 'data': dict({ + 'info': dict({ + 'command_set': 'ESCPL2,BDC,D4,D4PX,ESCPR7,END4,GENEP,URF', + 'location': None, + 'manufacturer': 'TEST', + 'model': 'HA-1000 Series', + 'more_info': 'http://192.168.1.31:80/PRESENTATION/BONJOUR', + 'name': 'Test HA-1000 Series', + 'printer_info': 'Test HA-1000 Series', + 'printer_name': 'Test Printer', + 'printer_uri_supported': list([ + 'ipps://192.168.1.31:631/ipp/print', + 'ipp://192.168.1.31:631/ipp/print', + ]), + 'serial': '555534593035345555', + 'uptime': 30, + 'uuid': 'cfe92100-67c4-11d4-a45f-f8d027761251', + 'version': '20.23.06HA', + }), + 'markers': list([ + dict({ + 'color': '#000000', + 'high_level': 100, + 'level': 58, + 'low_level': 10, + 'marker_id': 0, + 'marker_type': 'ink-cartridge', + 'name': 'Black ink', + }), + dict({ + 'color': '#00FFFF', + 'high_level': 100, + 'level': 91, + 'low_level': 10, + 'marker_id': 2, + 'marker_type': 'ink-cartridge', + 'name': 'Cyan ink', + }), + dict({ + 'color': '#FF00FF', + 'high_level': 100, + 'level': 73, + 'low_level': 10, + 'marker_id': 4, + 'marker_type': 'ink-cartridge', + 'name': 'Magenta ink', + }), + dict({ + 'color': '#000000', + 'high_level': 100, + 'level': 98, + 'low_level': 10, + 'marker_id': 1, + 'marker_type': 'ink-cartridge', + 'name': 'Photo black ink', + }), + dict({ + 'color': '#FFFF00', + 'high_level': 100, + 'level': 95, + 'low_level': 10, + 'marker_id': 3, + 'marker_type': 'ink-cartridge', + 'name': 'Yellow ink', + }), + ]), + 'state': dict({ + 'message': None, + 'printer_state': 'idle', + 'reasons': None, + }), + 'uris': list([ + dict({ + 'authentication': None, + 'security': 'tls', + 'uri': 'ipps://192.168.1.31:631/ipp/print', + }), + dict({ + 'authentication': None, + 'security': None, + 'uri': 'ipp://192.168.1.31:631/ipp/print', + }), + ]), + }), + 'entry': dict({ + 'data': dict({ + 'base_path': '/ipp/print', + 'host': '192.168.1.31', + 'port': 631, + 'ssl': False, + 'uuid': 'cfe92100-67c4-11d4-a45f-f8d027761251', + 'verify_ssl': True, + }), + 'unique_id': 'cfe92100-67c4-11d4-a45f-f8d027761251', + }), + }) +# --- diff --git a/tests/components/ipp/test_diagnostics.py b/tests/components/ipp/test_diagnostics.py new file mode 100644 index 00000000000..08446601e69 --- /dev/null +++ b/tests/components/ipp/test_diagnostics.py @@ -0,0 +1,22 @@ +"""Tests for the diagnostics data provided by the Internet Printing Protocol (IPP) integration.""" + +from syrupy import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + init_integration: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics for config entry.""" + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, init_integration) + == snapshot + ) From 34b1f848c1483a968ffacfcbcef4e8927515ccc1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 07:04:39 +0100 Subject: [PATCH 0934/1691] Add service icons to Unifi (#113360) --- homeassistant/components/unifi/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/unifi/icons.json diff --git a/homeassistant/components/unifi/icons.json b/homeassistant/components/unifi/icons.json new file mode 100644 index 00000000000..2d5017a3187 --- /dev/null +++ b/homeassistant/components/unifi/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "reconnect_client": "mdi:sync", + "remove_clients": "mdi:delete" + } +} From 33c75bfd368c6d03641ccc6a78692e6f8891ff4b Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:29:11 +0100 Subject: [PATCH 0935/1691] Add loggers to Husqvarna Automower (#113381) --- homeassistant/components/husqvarna_automower/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json index e51da21c727..49a554f2e0a 100644 --- a/homeassistant/components/husqvarna_automower/manifest.json +++ b/homeassistant/components/husqvarna_automower/manifest.json @@ -6,5 +6,6 @@ "dependencies": ["application_credentials"], "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "iot_class": "cloud_push", + "loggers": ["aioautomower"], "requirements": ["aioautomower==2024.3.2"] } From 438215f220dda3c96c132b1969e041bcda487337 Mon Sep 17 00:00:00 2001 From: tronikos Date: Thu, 14 Mar 2024 01:46:44 -0700 Subject: [PATCH 0936/1691] Bump opower to 0.4.0 (#113390) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 418f2a5723b..ab1bff8a5e9 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", "loggers": ["opower"], - "requirements": ["opower==0.3.1"] + "requirements": ["opower==0.4.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index e04598e7aea..5cdbce2a594 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1474,7 +1474,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.3.1 +opower==0.4.0 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 233f3be1b60..0d3a3410c96 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1165,7 +1165,7 @@ openerz-api==0.3.0 openhomedevice==2.2.0 # homeassistant.components.opower -opower==0.3.1 +opower==0.4.0 # homeassistant.components.oralb oralb-ble==0.17.6 From bc6917552f9b8532fabcad24f4deb7745507806e Mon Sep 17 00:00:00 2001 From: tronikos Date: Thu, 14 Mar 2024 01:47:09 -0700 Subject: [PATCH 0937/1691] Bump gassist-text to 0.0.11 (#113386) Co-authored-by: J. Nick Koston --- homeassistant/components/google_assistant_sdk/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google_assistant_sdk/manifest.json b/homeassistant/components/google_assistant_sdk/manifest.json index d52b7c18c41..b6281e2a4f0 100644 --- a/homeassistant/components/google_assistant_sdk/manifest.json +++ b/homeassistant/components/google_assistant_sdk/manifest.json @@ -8,5 +8,5 @@ "integration_type": "service", "iot_class": "cloud_polling", "quality_scale": "platinum", - "requirements": ["gassist-text==0.0.10"] + "requirements": ["gassist-text==0.0.11"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5cdbce2a594..ad00d983451 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -902,7 +902,7 @@ gTTS==2.2.4 gardena-bluetooth==1.4.1 # homeassistant.components.google_assistant_sdk -gassist-text==0.0.10 +gassist-text==0.0.11 # homeassistant.components.google gcal-sync==6.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0d3a3410c96..484f685611a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -734,7 +734,7 @@ gTTS==2.2.4 gardena-bluetooth==1.4.1 # homeassistant.components.google_assistant_sdk -gassist-text==0.0.10 +gassist-text==0.0.11 # homeassistant.components.google gcal-sync==6.0.3 From e9e5712c31d20ee078ae10ae14308850352e6829 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:48:46 +0100 Subject: [PATCH 0938/1691] Bump docker/login-action from 3.0.0 to 3.1.0 (#113384) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index ef0fbe03b63..770abf025d2 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -200,7 +200,7 @@ jobs: echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE - name: Login to GitHub Container Registry - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -277,7 +277,7 @@ jobs: fi - name: Login to GitHub Container Registry - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -346,13 +346,13 @@ jobs: cosign-release: "v2.0.2" - name: Login to DockerHub - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} From fd9cc3a53e386058dc9c8bda13bf4392375bc1c0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 09:50:53 +0100 Subject: [PATCH 0939/1691] Add service icons to Debug py (#113287) --- homeassistant/components/debugpy/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/debugpy/icons.json diff --git a/homeassistant/components/debugpy/icons.json b/homeassistant/components/debugpy/icons.json new file mode 100644 index 00000000000..b3bb4dde23a --- /dev/null +++ b/homeassistant/components/debugpy/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "start": "mdi:play" + } +} From de726d0e6eb8ab2ab68a10c0b282c3aef45fa34f Mon Sep 17 00:00:00 2001 From: tronikos Date: Thu, 14 Mar 2024 01:55:37 -0700 Subject: [PATCH 0940/1691] Add virtual integration Sacramento Municipal Utility District (SMUD) (#113389) smud --- homeassistant/components/smud/__init__.py | 1 + homeassistant/components/smud/manifest.json | 6 ++++++ homeassistant/generated/integrations.json | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 homeassistant/components/smud/__init__.py create mode 100644 homeassistant/components/smud/manifest.json diff --git a/homeassistant/components/smud/__init__.py b/homeassistant/components/smud/__init__.py new file mode 100644 index 00000000000..ce33399ad45 --- /dev/null +++ b/homeassistant/components/smud/__init__.py @@ -0,0 +1 @@ +"""Virtual integration: Sacramento Municipal Utility District (SMUD).""" diff --git a/homeassistant/components/smud/manifest.json b/homeassistant/components/smud/manifest.json new file mode 100644 index 00000000000..7b9b5d9d397 --- /dev/null +++ b/homeassistant/components/smud/manifest.json @@ -0,0 +1,6 @@ +{ + "domain": "smud", + "name": "Sacramento Municipal Utility District (SMUD)", + "integration_type": "virtual", + "supported_by": "opower" +} diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index cb30532cdec..a84a78324cf 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5486,6 +5486,11 @@ "config_flow": false, "iot_class": "cloud_push" }, + "smud": { + "name": "Sacramento Municipal Utility District (SMUD)", + "integration_type": "virtual", + "supported_by": "opower" + }, "snapcast": { "name": "Snapcast", "integration_type": "hub", From 8a98fb7cfd9d351c94dc16f2436eedaef59df840 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:21:38 +0100 Subject: [PATCH 0941/1691] Add service icons to Filter (#113295) --- homeassistant/components/filter/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/filter/icons.json diff --git a/homeassistant/components/filter/icons.json b/homeassistant/components/filter/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/filter/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 05172d8e4d77e6070ec271dc315f9b15ee68a89c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:22:20 +0100 Subject: [PATCH 0942/1691] Improve loops and lists (#113269) * Enable PERF * Enable PERF rule * Enable PERF rule * Don't enable flag yet --- homeassistant/auth/permissions/merge.py | 5 +-- .../components/ambiclimate/climate.py | 16 ++++---- homeassistant/components/aws/notify.py | 15 ++++---- homeassistant/components/ecovacs/vacuum.py | 6 +-- homeassistant/components/glances/sensor.py | 38 +++++++++---------- .../components/google_assistant_sdk/notify.py | 8 ++-- homeassistant/components/kwb/sensor.py | 14 +++---- homeassistant/components/lyric/sensor.py | 15 -------- homeassistant/components/nextdns/sensor.py | 11 ++---- .../components/qrcode/image_processing.py | 9 ++--- .../components/recorder/migration.py | 9 +++-- homeassistant/components/shelly/utils.py | 11 ++++-- homeassistant/components/sigfox/sensor.py | 5 +-- homeassistant/components/sleepiq/select.py | 16 ++++---- .../components/synology_dsm/media_source.py | 22 +++++------ homeassistant/components/tuya/siren.py | 10 ++--- homeassistant/components/yolink/siren.py | 9 ----- homeassistant/components/zha/core/group.py | 11 +++--- homeassistant/helpers/collection.py | 8 ++-- homeassistant/helpers/entityfilter.py | 7 ++-- homeassistant/helpers/http.py | 3 +- homeassistant/util/frozen_dataclass_compat.py | 4 +- script/gen_requirements_all.py | 3 +- script/hassfest/bluetooth.py | 3 +- script/hassfest/dhcp.py | 3 +- script/hassfest/usb.py | 14 +++---- tests/helpers/test_entity.py | 6 +-- tests/test_config_entries.py | 6 +-- tests/test_const.py | 5 +-- tests/util/test_executor.py | 5 +-- 30 files changed, 125 insertions(+), 172 deletions(-) diff --git a/homeassistant/auth/permissions/merge.py b/homeassistant/auth/permissions/merge.py index 841959b7f31..d0d43e2f088 100644 --- a/homeassistant/auth/permissions/merge.py +++ b/homeassistant/auth/permissions/merge.py @@ -58,10 +58,7 @@ def _merge_policies(sources: list[CategoryType]) -> CategoryType: continue seen.add(key) - key_sources = [] - for src in sources: - if isinstance(src, dict): - key_sources.append(src.get(key)) + key_sources = [src.get(key) for src in sources if isinstance(src, dict)] policy[key] = _merge_policies(key_sources) diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index 2e81cbd3595..e9554b08724 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -98,16 +98,16 @@ async def async_setup_entry( _LOGGER.error("No devices found") return - tasks = [] - for heater in data_connection.get_devices(): - tasks.append(asyncio.create_task(heater.update_device_info())) + tasks = [ + asyncio.create_task(heater.update_device_info()) + for heater in data_connection.get_devices() + ] await asyncio.wait(tasks) - devs = [] - for heater in data_connection.get_devices(): - devs.append(AmbiclimateEntity(heater, store)) - - async_add_entities(devs, True) + async_add_entities( + (AmbiclimateEntity(heater, store) for heater in data_connection.get_devices()), + True, + ) async def send_comfort_feedback(service: ServiceCall) -> None: """Send comfort feedback.""" diff --git a/homeassistant/components/aws/notify.py b/homeassistant/components/aws/notify.py index 054560ca12a..47d66900eb0 100644 --- a/homeassistant/components/aws/notify.py +++ b/homeassistant/components/aws/notify.py @@ -230,15 +230,14 @@ class AWSSQS(AWSNotify): async with self.session.create_client( self.service, **self.aws_config ) as client: - tasks = [] - for target in kwargs.get(ATTR_TARGET, []): - tasks.append( - client.send_message( - QueueUrl=target, - MessageBody=json_body, - MessageAttributes=message_attributes, - ) + tasks = [ + client.send_message( + QueueUrl=target, + MessageBody=json_body, + MessageAttributes=message_attributes, ) + for target in kwargs.get(ATTR_TARGET, []) + ] if tasks: await asyncio.gather(*tasks) diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 523f301db28..b70f6a6344e 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -46,13 +46,13 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Ecovacs vacuums.""" - vacuums: list[EcovacsVacuum | EcovacsLegacyVacuum] = [] controller: EcovacsController = hass.data[DOMAIN][config_entry.entry_id] + vacuums: list[EcovacsVacuum | EcovacsLegacyVacuum] = [ + EcovacsVacuum(device) for device in controller.devices(VacuumCapabilities) + ] for device in controller.legacy_devices: await hass.async_add_executor_job(device.connect_and_wait_until_ready) vacuums.append(EcovacsLegacyVacuum(device)) - for device in controller.devices(VacuumCapabilities): - vacuums.append(EcovacsVacuum(device)) _LOGGER.debug("Adding Ecovacs Vacuums to Home Assistant: %s", vacuums) async_add_entities(vacuums) diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 7591bf954d3..7db06a08496 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -223,29 +223,29 @@ async def async_setup_entry( """Set up the Glances sensors.""" coordinator: GlancesDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] - entities = [] + entities: list[GlancesSensor] = [] for sensor_type, sensors in coordinator.data.items(): if sensor_type in ["fs", "sensors", "raid"]: - for sensor_label, params in sensors.items(): - for param in params: - if sensor_description := SENSOR_TYPES.get((sensor_type, param)): - entities.append( - GlancesSensor( - coordinator, - sensor_description, - sensor_label, - ) - ) + entities.extend( + GlancesSensor( + coordinator, + sensor_description, + sensor_label, + ) + for sensor_label, params in sensors.items() + for param in params + if (sensor_description := SENSOR_TYPES.get((sensor_type, param))) + ) else: - for sensor in sensors: - if sensor_description := SENSOR_TYPES.get((sensor_type, sensor)): - entities.append( - GlancesSensor( - coordinator, - sensor_description, - ) - ) + entities.extend( + GlancesSensor( + coordinator, + sensor_description, + ) + for sensor in sensors + if (sensor_description := SENSOR_TYPES.get((sensor_type, sensor))) + ) async_add_entities(entities) diff --git a/homeassistant/components/google_assistant_sdk/notify.py b/homeassistant/components/google_assistant_sdk/notify.py index 5592f22ffeb..3f01cef2ebc 100644 --- a/homeassistant/components/google_assistant_sdk/notify.py +++ b/homeassistant/components/google_assistant_sdk/notify.py @@ -66,8 +66,8 @@ class BroadcastNotificationService(BaseNotificationService): if not targets: commands.append(broadcast_commands(language_code)[0].format(message)) else: - for target in targets: - commands.append( - broadcast_commands(language_code)[1].format(message, target) - ) + commands.extend( + broadcast_commands(language_code)[1].format(message, target) + for target in targets + ) await async_send_text_commands(self.hass, commands) diff --git a/homeassistant/components/kwb/sensor.py b/homeassistant/components/kwb/sensor.py index 950c0ed7d76..e55b90cf89f 100644 --- a/homeassistant/components/kwb/sensor.py +++ b/homeassistant/components/kwb/sensor.py @@ -72,16 +72,14 @@ def setup_platform( easyfire.run_thread() - sensors = [] - for sensor in easyfire.get_sensors(): - if (sensor.sensor_type != kwb.PROP_SENSOR_RAW) or ( - sensor.sensor_type == kwb.PROP_SENSOR_RAW and raw - ): - sensors.append(KWBSensor(easyfire, sensor, client_name)) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: easyfire.stop_thread()) - add_entities(sensors) + add_entities( + KWBSensor(easyfire, sensor, client_name) + for sensor in easyfire.get_sensors() + if (sensor.sensor_type != kwb.PROP_SENSOR_RAW) + or (sensor.sensor_type == kwb.PROP_SENSOR_RAW and raw) + ) class KWBSensor(SensorEntity): diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 1072ca6c2ba..276336e02cc 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -134,21 +134,6 @@ async def async_setup_entry( """Set up the Honeywell Lyric sensor platform based on a config entry.""" coordinator: DataUpdateCoordinator[Lyric] = hass.data[DOMAIN][entry.entry_id] - entities = [] - - for location in coordinator.data.locations: - for device in location.devices: - for device_sensor in DEVICE_SENSORS: - if device_sensor.suitable_fn(device): - entities.append( - LyricSensor( - coordinator, - device_sensor, - location, - device, - ) - ) - async_add_entities( LyricSensor( coordinator, diff --git a/homeassistant/components/nextdns/sensor.py b/homeassistant/components/nextdns/sensor.py index 94441f8f658..4357179cbdb 100644 --- a/homeassistant/components/nextdns/sensor.py +++ b/homeassistant/components/nextdns/sensor.py @@ -311,15 +311,12 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Add a NextDNS entities from a config_entry.""" - sensors: list[NextDnsSensor] = [] coordinators = hass.data[DOMAIN][entry.entry_id] - for description in SENSORS: - sensors.append( - NextDnsSensor(coordinators[description.coordinator_type], description) - ) - - async_add_entities(sensors) + async_add_entities( + NextDnsSensor(coordinators[description.coordinator_type], description) + for description in SENSORS + ) class NextDnsSensor( diff --git a/homeassistant/components/qrcode/image_processing.py b/homeassistant/components/qrcode/image_processing.py index aacd3c0c38f..bec0cea8c2f 100644 --- a/homeassistant/components/qrcode/image_processing.py +++ b/homeassistant/components/qrcode/image_processing.py @@ -21,11 +21,10 @@ def setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the QR code image processing platform.""" - entities = [] - for camera in config[CONF_SOURCE]: - entities.append(QrEntity(camera[CONF_ENTITY_ID], camera.get(CONF_NAME))) - - add_entities(entities) + add_entities( + QrEntity(camera[CONF_ENTITY_ID], camera.get(CONF_NAME)) + for camera in config[CONF_SOURCE] + ) class QrEntity(ImageProcessingEntity): diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 491366c4983..2d8a2976219 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -556,10 +556,11 @@ def _drop_foreign_key_constraints( ) -> None: """Drop foreign key constraints for a table on specific columns.""" inspector = sqlalchemy.inspect(engine) - drops = [] - for foreign_key in inspector.get_foreign_keys(table): - if foreign_key["name"] and foreign_key["constrained_columns"] == columns: - drops.append(ForeignKeyConstraint((), (), name=foreign_key["name"])) + drops = [ + ForeignKeyConstraint((), (), name=foreign_key["name"]) + for foreign_key in inspector.get_foreign_keys(table) + if foreign_key["name"] and foreign_key["constrained_columns"] == columns + ] # Bind the ForeignKeyConstraints to the table old_table = Table(table, MetaData(), *drops) # noqa: F841 diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 101306818a0..291fe0cc4ea 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -219,14 +219,17 @@ async def get_coap_context(hass: HomeAssistant) -> COAP: ipv4: list[IPv4Address] = [] if not network.async_only_default_interface_enabled(adapters): - for address in await network.async_get_enabled_source_ips(hass): - if address.version == 4 and not ( + ipv4.extend( + address + for address in await network.async_get_enabled_source_ips(hass) + if address.version == 4 + and not ( address.is_link_local or address.is_loopback or address.is_multicast or address.is_unspecified - ): - ipv4.append(address) + ) + ) LOGGER.debug("Network IPv4 addresses: %s", ipv4) if DOMAIN in hass.data: port = hass.data[DOMAIN].get(CONF_COAP_PORT, DEFAULT_COAP_PORT) diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index b7da9ef4fa1..fbda6fece21 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -89,10 +89,7 @@ class SigfoxAPI: """Get a list of device types.""" url = urljoin(API_URL, "devicetypes") response = requests.get(url, auth=self._auth, timeout=10) - device_types = [] - for device in json.loads(response.text)["data"]: - device_types.append(device["id"]) - return device_types + return [device["id"] for device in json.loads(response.text)["data"]] def get_devices(self, device_types): """Get the device_id of each device registered.""" diff --git a/homeassistant/components/sleepiq/select.py b/homeassistant/components/sleepiq/select.py index 74bcd89cc4d..0a09aa4d657 100644 --- a/homeassistant/components/sleepiq/select.py +++ b/homeassistant/components/sleepiq/select.py @@ -29,14 +29,14 @@ async def async_setup_entry( data: SleepIQData = hass.data[DOMAIN][entry.entry_id] entities: list[SleepIQBedEntity] = [] for bed in data.client.beds.values(): - for preset in bed.foundation.presets: - entities.append(SleepIQSelectEntity(data.data_coordinator, bed, preset)) - for foot_warmer in bed.foundation.foot_warmers: - entities.append( - SleepIQFootWarmingTempSelectEntity( - data.data_coordinator, bed, foot_warmer - ) - ) + entities.extend( + SleepIQSelectEntity(data.data_coordinator, bed, preset) + for preset in bed.foundation.presets + ) + entities.extend( + SleepIQFootWarmingTempSelectEntity(data.data_coordinator, bed, foot_warmer) + for foot_warmer in bed.foundation.foot_warmers + ) async_add_entities(entities) diff --git a/homeassistant/components/synology_dsm/media_source.py b/homeassistant/components/synology_dsm/media_source.py index ca6554209b8..6f53116a39c 100644 --- a/homeassistant/components/synology_dsm/media_source.py +++ b/homeassistant/components/synology_dsm/media_source.py @@ -124,18 +124,18 @@ class SynologyPhotosMediaSource(MediaSource): can_expand=True, ) ] - for album in albums: - ret.append( - BrowseMediaSource( - domain=DOMAIN, - identifier=f"{item.identifier}/{album.album_id}", - media_class=MediaClass.DIRECTORY, - media_content_type=MediaClass.IMAGE, - title=album.name, - can_play=False, - can_expand=True, - ) + ret.extend( + BrowseMediaSource( + domain=DOMAIN, + identifier=f"{item.identifier}/{album.album_id}", + media_class=MediaClass.DIRECTORY, + media_content_type=MediaClass.IMAGE, + title=album.name, + can_play=False, + can_expand=True, ) + for album in albums + ) return ret diff --git a/homeassistant/components/tuya/siren.py b/homeassistant/components/tuya/siren.py index 1f1707a1fc7..04473e44e22 100644 --- a/homeassistant/components/tuya/siren.py +++ b/homeassistant/components/tuya/siren.py @@ -60,11 +60,11 @@ async def async_setup_entry( for device_id in device_ids: device = hass_data.manager.device_map[device_id] if descriptions := SIRENS.get(device.category): - for description in descriptions: - if description.key in device.status: - entities.append( - TuyaSirenEntity(device, hass_data.manager, description) - ) + entities.extend( + TuyaSirenEntity(device, hass_data.manager, description) + for description in descriptions + if description.key in device.status + ) async_add_entities(entities) diff --git a/homeassistant/components/yolink/siren.py b/homeassistant/components/yolink/siren.py index b44baecf7b8..9e02f50bb70 100644 --- a/homeassistant/components/yolink/siren.py +++ b/homeassistant/components/yolink/siren.py @@ -55,15 +55,6 @@ async def async_setup_entry( for device_coordinator in device_coordinators.values() if device_coordinator.device.device_type in DEVICE_TYPE ] - entities = [] - for siren_device_coordinator in siren_device_coordinators: - for description in DEVICE_TYPES: - if description.exists_fn(siren_device_coordinator.device): - entities.append( - YoLinkSirenEntity( - config_entry, siren_device_coordinator, description - ) - ) async_add_entities( YoLinkSirenEntity(config_entry, siren_device_coordinator, description) for siren_device_coordinator in siren_device_coordinators diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index 0925a449301..7b5a6e91516 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -206,12 +206,11 @@ class ZHAGroup(LogMixin): @property def member_entity_ids(self) -> list[str]: """Return the ZHA entity ids for all entities for the members of this group.""" - all_entity_ids: list[str] = [] - for member in self.members: - entity_references = member.associated_entities - for entity_reference in entity_references: - all_entity_ids.append(entity_reference["entity_id"]) - return all_entity_ids + return [ + entity_reference["entity_id"] + for member in self.members + for entity_reference in member.associated_entities + ] def get_domain_entity_ids(self, domain: str) -> list[str]: """Return entity ids from the entity domain for this group.""" diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 451f96379ac..6e833e338db 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -221,10 +221,10 @@ class YamlCollection(ObservableCollection[dict]): self.data[item_id] = item change_sets.append(CollectionChangeSet(event, item_id, item)) - for item_id in old_ids: - change_sets.append( - CollectionChangeSet(CHANGE_REMOVED, item_id, self.data.pop(item_id)) - ) + change_sets.extend( + CollectionChangeSet(CHANGE_REMOVED, item_id, self.data.pop(item_id)) + for item_id in old_ids + ) if change_sets: await self.notify_changes(change_sets) diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index cd1960fde12..837c5e2bc1d 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -142,10 +142,9 @@ def _convert_globs_to_pattern(globs: list[str] | None) -> re.Pattern[str] | None if globs is None: return None - translated_patterns: list[str] = [] - for glob in set(globs): - if pattern := fnmatch.translate(glob): - translated_patterns.append(pattern) + translated_patterns: list[str] = [ + pattern for glob in set(globs) if (pattern := fnmatch.translate(glob)) + ] if not translated_patterns: return None diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index 7bcb1bedd20..f3b2b175997 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -175,8 +175,7 @@ class HomeAssistantView: handler = request_handler_factory(hass, self, handler) - for url in urls: - routes.append(router.add_route(method, url, handler)) + routes.extend(router.add_route(method, url, handler) for url in urls) # Use `get` because CORS middleware is not be loaded in emulated_hue if self.cors_allowed: diff --git a/homeassistant/util/frozen_dataclass_compat.py b/homeassistant/util/frozen_dataclass_compat.py index 2bbcadb4ccf..68db3cd6832 100644 --- a/homeassistant/util/frozen_dataclass_compat.py +++ b/homeassistant/util/frozen_dataclass_compat.py @@ -57,9 +57,7 @@ class FrozenOrThawed(type): def _make_dataclass(cls, name: str, bases: tuple[type, ...], kw_only: bool) -> None: class_fields = _class_fields(cls, kw_only) - dataclass_bases = [] - for base in bases: - dataclass_bases.append(getattr(base, "_dataclass", base)) + dataclass_bases = [getattr(base, "_dataclass", base) for base in bases] cls._dataclass = dataclasses.make_dataclass( name, class_fields, bases=tuple(dataclass_bases), frozen=True ) diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 8db6aee8a96..8cf8f33c542 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -347,8 +347,7 @@ def generate_requirements_list(reqs: dict[str, list[str]]) -> str: """Generate a pip file based on requirements.""" output = [] for pkg, requirements in sorted(reqs.items(), key=itemgetter(0)): - for req in sorted(requirements): - output.append(f"\n# {req}") + output.extend(f"\n# {req}" for req in sorted(requirements)) if comment_requirement(pkg): output.append(f"\n# {pkg}\n") diff --git a/script/hassfest/bluetooth.py b/script/hassfest/bluetooth.py index 9f11c36360e..d724905f9cd 100644 --- a/script/hassfest/bluetooth.py +++ b/script/hassfest/bluetooth.py @@ -16,8 +16,7 @@ def generate_and_validate(integrations: dict[str, Integration]) -> str: if not match_types: continue - for entry in match_types: - match_list.append({"domain": domain, **entry}) + match_list.extend({"domain": domain, **entry} for entry in match_types) return format_python_namespace( {"BLUETOOTH": match_list}, diff --git a/script/hassfest/dhcp.py b/script/hassfest/dhcp.py index bc4c78544c4..67543a772fc 100644 --- a/script/hassfest/dhcp.py +++ b/script/hassfest/dhcp.py @@ -16,8 +16,7 @@ def generate_and_validate(integrations: dict[str, Integration]) -> str: if not match_types: continue - for entry in match_types: - match_list.append({"domain": domain, **entry}) + match_list.extend({"domain": domain, **entry} for entry in match_types) return format_python_namespace( {"DHCP": match_list}, diff --git a/script/hassfest/usb.py b/script/hassfest/usb.py index 0c7998f4fc2..84cafc973ad 100644 --- a/script/hassfest/usb.py +++ b/script/hassfest/usb.py @@ -16,13 +16,13 @@ def generate_and_validate(integrations: dict[str, Integration]) -> str: if not match_types: continue - for entry in match_types: - match_list.append( - { - "domain": domain, - **{k: v for k, v in entry.items() if k != "known_devices"}, - } - ) + match_list.extend( + { + "domain": domain, + **{k: v for k, v in entry.items() if k != "known_devices"}, + } + for entry in match_types + ) return format_python_namespace({"USB": match_list}) diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index ec281bf4c0d..2d4127b2763 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -2397,9 +2397,9 @@ async def test_cached_entity_property_class_attribute(hass: HomeAssistant) -> No EntityWithClassAttribute4, ) - entities: list[tuple[entity.Entity, entity.Entity]] = [] - for cls in classes: - entities.append((cls(), cls())) + entities: list[tuple[entity.Entity, entity.Entity]] = [ + (cls(), cls()) for cls in classes + ] for ent in entities: assert getattr(ent[0], property) == values[0] diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 18022ae3a21..d961b71e96d 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -3915,9 +3915,9 @@ async def test_entry_reload_concurrency( ), ) mock_platform(hass, "comp.config_flow", None) - tasks = [] - for _ in range(15): - tasks.append(asyncio.create_task(manager.async_reload(entry.entry_id))) + tasks = [ + asyncio.create_task(manager.async_reload(entry.entry_id)) for _ in range(15) + ] await asyncio.gather(*tasks) assert entry.state is config_entries.ConfigEntryState.LOADED assert loaded == 1 diff --git a/tests/test_const.py b/tests/test_const.py index 5b7cf851fcc..63b01388dd7 100644 --- a/tests/test_const.py +++ b/tests/test_const.py @@ -17,10 +17,7 @@ from tests.common import ( def _create_tuples( value: Enum | list[Enum], constant_prefix: str ) -> list[tuple[Enum, str]]: - result = [] - for enum in value: - result.append((enum, constant_prefix)) - return result + return [(enum, constant_prefix) for enum in value] def test_all() -> None: diff --git a/tests/util/test_executor.py b/tests/util/test_executor.py index d7731a44b7d..0730c16b68d 100644 --- a/tests/util/test_executor.py +++ b/tests/util/test_executor.py @@ -21,10 +21,7 @@ async def test_executor_shutdown_can_interrupt_threads( while True: time.sleep(0.1) - sleep_futures = [] - - for _ in range(100): - sleep_futures.append(iexecutor.submit(_loop_sleep_in_executor)) + sleep_futures = [iexecutor.submit(_loop_sleep_in_executor) for _ in range(100)] iexecutor.shutdown() From 7ae852e5edf8cf22a9f20a10b3e069f8cfa5bce1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:43:03 +0100 Subject: [PATCH 0943/1691] Enable ISC ruff rule (#113270) --- homeassistant/components/actiontec/const.py | 6 +++--- homeassistant/components/aruba/device_tracker.py | 4 ++-- homeassistant/components/kostal_plenticore/helper.py | 6 ++++-- homeassistant/components/picnic/config_flow.py | 2 +- homeassistant/components/xiaomi/device_tracker.py | 2 +- pyproject.toml | 2 +- tests/components/hddtemp/test_sensor.py | 6 +++--- tests/components/html5/test_notify.py | 8 +++++--- tests/components/imap/const.py | 6 +++--- tests/components/tts/test_init.py | 2 +- 10 files changed, 24 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/actiontec/const.py b/homeassistant/components/actiontec/const.py index af28b0abb41..beff47f9811 100644 --- a/homeassistant/components/actiontec/const.py +++ b/homeassistant/components/actiontec/const.py @@ -9,7 +9,7 @@ from typing import Final LEASES_REGEX: Final[re.Pattern[str]] = re.compile( r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})" - + r"\smac:\s(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))" - + r"\svalid\sfor:\s(?P(-?\d+))" - + r"\ssec" + r"\smac:\s(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))" + r"\svalid\sfor:\s(?P(-?\d+))" + r"\ssec" ) diff --git a/homeassistant/components/aruba/device_tracker.py b/homeassistant/components/aruba/device_tracker.py index 35053233428..dd94a5975f0 100644 --- a/homeassistant/components/aruba/device_tracker.py +++ b/homeassistant/components/aruba/device_tracker.py @@ -23,8 +23,8 @@ _LOGGER = logging.getLogger(__name__) _DEVICES_REGEX = re.compile( r"(?P([^\s]+)?)\s+" - + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+" - + r"(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))\s+" + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+" + r"(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))\s+" ) PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/kostal_plenticore/helper.py b/homeassistant/components/kostal_plenticore/helper.py index d9117625d19..4a4e6539f03 100644 --- a/homeassistant/components/kostal_plenticore/helper.py +++ b/homeassistant/components/kostal_plenticore/helper.py @@ -101,8 +101,10 @@ class Plenticore: manufacturer="Kostal", model=f"{prod1} {prod2}", name=settings["scb:network"][hostname_id], - sw_version=f'IOC: {device_local["Properties:VersionIOC"]}' - + f' MC: {device_local["Properties:VersionMC"]}', + sw_version=( + f'IOC: {device_local["Properties:VersionIOC"]}' + f' MC: {device_local["Properties:VersionMC"]}' + ), ) return True diff --git a/homeassistant/components/picnic/config_flow.py b/homeassistant/components/picnic/config_flow.py index 1900f39306d..9712286b554 100644 --- a/homeassistant/components/picnic/config_flow.py +++ b/homeassistant/components/picnic/config_flow.py @@ -68,7 +68,7 @@ async def validate_input(hass: HomeAssistant, data): # Return the validation result address = ( f'{user_data["address"]["street"]} {user_data["address"]["house_number"]}' - + f'{user_data["address"]["house_number_ext"]}' + f'{user_data["address"]["house_number_ext"]}' ) return auth_token, { "title": address, diff --git a/homeassistant/components/xiaomi/device_tracker.py b/homeassistant/components/xiaomi/device_tracker.py index f50d67a01b4..76227d89e94 100644 --- a/homeassistant/components/xiaomi/device_tracker.py +++ b/homeassistant/components/xiaomi/device_tracker.py @@ -168,7 +168,7 @@ def _get_token(host, username, password): except KeyError: error_message = ( "Xiaomi token cannot be refreshed, response from " - + "url: [%s] \nwith parameter: [%s] \nwas: [%s]" + "url: [%s] \nwith parameter: [%s] \nwas: [%s]" ) _LOGGER.exception(error_message, url, data, result) return diff --git a/pyproject.toml b/pyproject.toml index bcfcfbc4a4b..e6ee50f1a3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -594,6 +594,7 @@ select = [ "F", # pyflakes/autoflake "G", # flake8-logging-format "I", # isort + "ISC", # flake8-implicit-str-concat "ICN001", # import concentions; {name} should be imported as {asname} "N804", # First argument of a class method should be named cls "N805", # First argument of a method should be named self @@ -681,7 +682,6 @@ ignore = [ "COM812", "COM819", "ISC001", - "ISC002", # Disabled because ruff does not understand type of __all__ generated by a function "PLE0605", diff --git a/tests/components/hddtemp/test_sensor.py b/tests/components/hddtemp/test_sensor.py index 170f30eae37..eac6d4c4053 100644 --- a/tests/components/hddtemp/test_sensor.py +++ b/tests/components/hddtemp/test_sensor.py @@ -67,9 +67,9 @@ class TelnetMock: self.timeout = timeout self.sample_data = bytes( "|/dev/sda1|WDC WD30EZRX-12DC0B0|29|C|" - + "|/dev/sdb1|WDC WD15EADS-11P7B2|32|C|" - + "|/dev/sdc1|WDC WD20EARX-22MMMB0|29|C|" - + "|/dev/sdd1|WDC WD15EARS-00Z5B1|89|F|", + "|/dev/sdb1|WDC WD15EADS-11P7B2|32|C|" + "|/dev/sdc1|WDC WD20EARX-22MMMB0|29|C|" + "|/dev/sdd1|WDC WD15EARS-00Z5B1|89|F|", "ascii", ) diff --git a/tests/components/html5/test_notify.py b/tests/components/html5/test_notify.py index 1d87603fe67..6763708cc38 100644 --- a/tests/components/html5/test_notify.py +++ b/tests/components/html5/test_notify.py @@ -17,9 +17,11 @@ CONFIG_FILE = "file.conf" VAPID_CONF = { "platform": "html5", - "vapid_pub_key": "BJMA2gDZEkHaXRhf1fhY_" - + "QbKbhVIHlSJXI0bFyo0eJXnUPOjdgycCAbj-2bMKMKNKs" - + "_rM8JoSnyKGCXAY2dbONI", + "vapid_pub_key": ( + "BJMA2gDZEkHaXRhf1fhY_" + "QbKbhVIHlSJXI0bFyo0eJXnUPOjdgycCAbj-2bMKMKNKs" + "_rM8JoSnyKGCXAY2dbONI" + ), "vapid_prv_key": "ZwPgwKpESGuGLMZYU39vKgrekrWzCijo-LsBM3CZ9-c", "vapid_email": "someone@example.com", } diff --git a/tests/components/imap/const.py b/tests/components/imap/const.py index 8ec5e258059..677eea7a473 100644 --- a/tests/components/imap/const.py +++ b/tests/components/imap/const.py @@ -101,7 +101,7 @@ TEST_CONTENT_HTML_BASE64 = ( TEST_CONTENT_MULTIPART = ( b"\r\nThis is a multi-part message in MIME format.\r\n" - + b"\r\n--Mark=_100584970350292485166\r\n" + b"\r\n--Mark=_100584970350292485166\r\n" + TEST_CONTENT_TEXT_PLAIN + b"\r\n--Mark=_100584970350292485166\r\n" + TEST_CONTENT_HTML @@ -110,7 +110,7 @@ TEST_CONTENT_MULTIPART = ( TEST_CONTENT_MULTIPART_BASE64 = ( b"\r\nThis is a multi-part message in MIME format.\r\n" - + b"\r\n--Mark=_100584970350292485166\r\n" + b"\r\n--Mark=_100584970350292485166\r\n" + TEST_CONTENT_TEXT_BASE64 + b"\r\n--Mark=_100584970350292485166\r\n" + TEST_CONTENT_HTML_BASE64 @@ -119,7 +119,7 @@ TEST_CONTENT_MULTIPART_BASE64 = ( TEST_CONTENT_MULTIPART_BASE64_INVALID = ( b"\r\nThis is a multi-part message in MIME format.\r\n" - + b"\r\n--Mark=_100584970350292485166\r\n" + b"\r\n--Mark=_100584970350292485166\r\n" + TEST_CONTENT_TEXT_BASE64_INVALID + b"\r\n--Mark=_100584970350292485166\r\n" + TEST_CONTENT_HTML_BASE64 diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index cbf18fe8771..aae61e69a54 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -1308,7 +1308,7 @@ async def test_tags_with_wave() -> None: # below data represents an empty wav file tts_data = bytes.fromhex( "52 49 46 46 24 00 00 00 57 41 56 45 66 6d 74 20 10 00 00 00 01 00 02 00" - + "22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 00 00 00" + "22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 00 00 00" ) tagged_data = ORIG_WRITE_TAGS( From 9eea7864116808b93ee3406d0415512e58d68cdd Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 14 Mar 2024 10:43:55 +0100 Subject: [PATCH 0944/1691] Remove old migration from lovelace (#113388) --- .../components/lovelace/dashboard.py | 17 ------ tests/components/lovelace/test_dashboard.py | 55 ------------------- 2 files changed, 72 deletions(-) diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index 984c0d14734..98c03e20d87 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -232,23 +232,6 @@ class DashboardsCollection(collection.DictStorageCollection): storage.Store(hass, DASHBOARDS_STORAGE_VERSION, DASHBOARDS_STORAGE_KEY), ) - async def _async_load_data(self) -> collection.SerializedStorageCollection | None: - """Load the data.""" - if (data := await self.store.async_load()) is None: - return data - - updated = False - - for item in data["items"] or []: - if "-" not in item[CONF_URL_PATH]: - updated = True - item[CONF_URL_PATH] = f"lovelace-{item[CONF_URL_PATH]}" - - if updated: - await self.store.async_save(data) - - return data - async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" if "-" not in data[CONF_URL_PATH]: diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py index 0fca21a9a45..a85770d8ae4 100644 --- a/tests/components/lovelace/test_dashboard.py +++ b/tests/components/lovelace/test_dashboard.py @@ -441,61 +441,6 @@ async def test_storage_dashboards( assert dashboard.CONFIG_STORAGE_KEY.format(dashboard_id) not in hass_storage -async def test_storage_dashboard_migrate( - hass: HomeAssistant, hass_ws_client, hass_storage: dict[str, Any] -) -> None: - """Test changing url path from storage config.""" - hass_storage[dashboard.DASHBOARDS_STORAGE_KEY] = { - "key": "lovelace_dashboards", - "version": 1, - "data": { - "items": [ - { - "icon": "mdi:tools", - "id": "tools", - "mode": "storage", - "require_admin": True, - "show_in_sidebar": True, - "title": "Tools", - "url_path": "tools", - }, - { - "icon": "mdi:tools", - "id": "tools2", - "mode": "storage", - "require_admin": True, - "show_in_sidebar": True, - "title": "Tools", - "url_path": "dashboard-tools", - }, - ] - }, - } - - assert await async_setup_component(hass, "lovelace", {}) - - client = await hass_ws_client(hass) - - # Fetch data - await client.send_json({"id": 5, "type": "lovelace/dashboards/list"}) - response = await client.receive_json() - assert response["success"] - without_hyphen, with_hyphen = response["result"] - - assert without_hyphen["icon"] == "mdi:tools" - assert without_hyphen["id"] == "tools" - assert without_hyphen["mode"] == "storage" - assert without_hyphen["require_admin"] - assert without_hyphen["show_in_sidebar"] - assert without_hyphen["title"] == "Tools" - assert without_hyphen["url_path"] == "lovelace-tools" - - assert ( - with_hyphen - == hass_storage[dashboard.DASHBOARDS_STORAGE_KEY]["data"]["items"][1] - ) - - async def test_websocket_list_dashboards( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: From 4aec48d3587529439b19d014def2d2255a80ba46 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:44:17 +0000 Subject: [PATCH 0945/1691] Update ring integration imports (#113397) Update ring imports for patching where library is created --- homeassistant/components/ring/__init__.py | 6 +++--- homeassistant/components/ring/config_flow.py | 8 ++++---- homeassistant/components/ring/coordinator.py | 17 ++++++++--------- homeassistant/components/ring/diagnostics.py | 4 ++-- homeassistant/components/ring/entity.py | 12 ++++++------ homeassistant/components/ring/light.py | 3 +-- homeassistant/components/ring/sensor.py | 2 +- homeassistant/components/ring/siren.py | 2 +- homeassistant/components/ring/switch.py | 3 +-- tests/components/ring/conftest.py | 12 +++--------- 10 files changed, 30 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/ring/__init__.py b/homeassistant/components/ring/__init__.py index 79d90c87f0e..e3697d4fccc 100644 --- a/homeassistant/components/ring/__init__.py +++ b/homeassistant/components/ring/__init__.py @@ -5,7 +5,7 @@ from __future__ import annotations from functools import partial import logging -import ring_doorbell +from ring_doorbell import Auth, Ring from homeassistant.config_entries import ConfigEntry from homeassistant.const import APPLICATION_NAME, CONF_TOKEN, __version__ @@ -39,10 +39,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) ) - auth = ring_doorbell.Auth( + auth = Auth( f"{APPLICATION_NAME}/{__version__}", entry.data[CONF_TOKEN], token_updater ) - ring = ring_doorbell.Ring(auth) + ring = Ring(auth) devices_coordinator = RingDataCoordinator(hass, ring) notifications_coordinator = RingNotificationsCoordinator(hass, ring) diff --git a/homeassistant/components/ring/config_flow.py b/homeassistant/components/ring/config_flow.py index f5557c48183..6d4f28eb311 100644 --- a/homeassistant/components/ring/config_flow.py +++ b/homeassistant/components/ring/config_flow.py @@ -4,7 +4,7 @@ from collections.abc import Mapping import logging from typing import Any -import ring_doorbell +from ring_doorbell import Auth, AuthenticationError, Requires2FAError import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult @@ -31,7 +31,7 @@ STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) async def validate_input(hass: HomeAssistant, data): """Validate the user input allows us to connect.""" - auth = ring_doorbell.Auth(f"{APPLICATION_NAME}/{ha_version}") + auth = Auth(f"{APPLICATION_NAME}/{ha_version}") try: token = await hass.async_add_executor_job( @@ -40,9 +40,9 @@ async def validate_input(hass: HomeAssistant, data): data[CONF_PASSWORD], data.get(CONF_2FA), ) - except ring_doorbell.Requires2FAError as err: + except Requires2FAError as err: raise Require2FA from err - except ring_doorbell.AuthenticationError as err: + except AuthenticationError as err: raise InvalidAuth from err return token diff --git a/homeassistant/components/ring/coordinator.py b/homeassistant/components/ring/coordinator.py index bccfccf2808..fdb6fc1f296 100644 --- a/homeassistant/components/ring/coordinator.py +++ b/homeassistant/components/ring/coordinator.py @@ -6,8 +6,7 @@ from dataclasses import dataclass import logging from typing import Any, Optional -import ring_doorbell -from ring_doorbell.generic import RingGeneric +from ring_doorbell import AuthenticationError, Ring, RingError, RingGeneric, RingTimeout from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed @@ -23,15 +22,15 @@ async def _call_api( ): try: return await hass.async_add_executor_job(target, *args) - except ring_doorbell.AuthenticationError as err: + except AuthenticationError as err: # Raising ConfigEntryAuthFailed will cancel future updates # and start a config flow with SOURCE_REAUTH (async_step_reauth) raise ConfigEntryAuthFailed from err - except ring_doorbell.RingTimeout as err: + except RingTimeout as err: raise UpdateFailed( f"Timeout communicating with API{msg_suffix}: {err}" ) from err - except ring_doorbell.RingError as err: + except RingError as err: raise UpdateFailed(f"Error communicating with API{msg_suffix}: {err}") from err @@ -49,7 +48,7 @@ class RingDataCoordinator(DataUpdateCoordinator[dict[int, RingDeviceData]]): def __init__( self, hass: HomeAssistant, - ring_api: ring_doorbell.Ring, + ring_api: Ring, ) -> None: """Initialize my coordinator.""" super().__init__( @@ -58,7 +57,7 @@ class RingDataCoordinator(DataUpdateCoordinator[dict[int, RingDeviceData]]): logger=_LOGGER, update_interval=SCAN_INTERVAL, ) - self.ring_api: ring_doorbell.Ring = ring_api + self.ring_api: Ring = ring_api self.first_call: bool = True async def _async_update_data(self): @@ -105,7 +104,7 @@ class RingDataCoordinator(DataUpdateCoordinator[dict[int, RingDeviceData]]): class RingNotificationsCoordinator(DataUpdateCoordinator[None]): """Global notifications coordinator.""" - def __init__(self, hass: HomeAssistant, ring_api: ring_doorbell.Ring) -> None: + def __init__(self, hass: HomeAssistant, ring_api: Ring) -> None: """Initialize my coordinator.""" super().__init__( hass, @@ -113,7 +112,7 @@ class RingNotificationsCoordinator(DataUpdateCoordinator[None]): name="active dings", update_interval=NOTIFICATIONS_SCAN_INTERVAL, ) - self.ring_api: ring_doorbell.Ring = ring_api + self.ring_api: Ring = ring_api async def _async_update_data(self): """Fetch data from API endpoint.""" diff --git a/homeassistant/components/ring/diagnostics.py b/homeassistant/components/ring/diagnostics.py index 497d87a086b..5295629979a 100644 --- a/homeassistant/components/ring/diagnostics.py +++ b/homeassistant/components/ring/diagnostics.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import Any -import ring_doorbell +from ring_doorbell import Ring from homeassistant.components.diagnostics import async_redact_data from homeassistant.config_entries import ConfigEntry @@ -33,7 +33,7 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - ring: ring_doorbell.Ring = hass.data[DOMAIN][entry.entry_id]["api"] + ring: Ring = hass.data[DOMAIN][entry.entry_id]["api"] devices_raw = [ ring.devices_data[device_type][device_id] for device_type in ring.devices_data diff --git a/homeassistant/components/ring/entity.py b/homeassistant/components/ring/entity.py index e5b17ca135e..0c738090218 100644 --- a/homeassistant/components/ring/entity.py +++ b/homeassistant/components/ring/entity.py @@ -2,7 +2,7 @@ from collections.abc import Callable from typing import Any, Concatenate, ParamSpec, TypeVar -import ring_doorbell +from ring_doorbell import AuthenticationError, RingError, RingGeneric, RingTimeout from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError @@ -32,16 +32,16 @@ def exception_wrap( def _wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: try: return func(self, *args, **kwargs) - except ring_doorbell.AuthenticationError as err: + except AuthenticationError as err: self.hass.loop.call_soon_threadsafe( self.coordinator.config_entry.async_start_reauth, self.hass ) raise HomeAssistantError(err) from err - except ring_doorbell.RingTimeout as err: + except RingTimeout as err: raise HomeAssistantError( f"Timeout communicating with API {func}: {err}" ) from err - except ring_doorbell.RingError as err: + except RingError as err: raise HomeAssistantError( f"Error communicating with API{func}: {err}" ) from err @@ -58,7 +58,7 @@ class RingEntity(CoordinatorEntity[_RingCoordinatorT]): def __init__( self, - device: ring_doorbell.RingGeneric, + device: RingGeneric, coordinator: _RingCoordinatorT, ) -> None: """Initialize a sensor for Ring device.""" @@ -79,7 +79,7 @@ class RingEntity(CoordinatorEntity[_RingCoordinatorT]): return device_data return None - def _get_coordinator_device(self) -> ring_doorbell.RingGeneric | None: + def _get_coordinator_device(self) -> RingGeneric | None: if (device_data := self._get_coordinator_device_data()) and ( device := device_data.device ): diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index abc3284bcb2..31e22c2084c 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -4,8 +4,7 @@ from datetime import timedelta import logging from typing import Any -from ring_doorbell import RingStickUpCam -from ring_doorbell.generic import RingGeneric +from ring_doorbell import RingGeneric, RingStickUpCam from homeassistant.components.light import ColorMode, LightEntity from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index b6b37a8a669..874d3664ab7 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -5,7 +5,7 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any -from ring_doorbell.generic import RingGeneric +from ring_doorbell import RingGeneric from homeassistant.components.sensor import ( SensorDeviceClass, diff --git a/homeassistant/components/ring/siren.py b/homeassistant/components/ring/siren.py index aa3c50f275a..4e53ab8a006 100644 --- a/homeassistant/components/ring/siren.py +++ b/homeassistant/components/ring/siren.py @@ -3,8 +3,8 @@ import logging from typing import Any +from ring_doorbell import RingGeneric from ring_doorbell.const import CHIME_TEST_SOUND_KINDS, KIND_DING -from ring_doorbell.generic import RingGeneric from homeassistant.components.siren import ATTR_TONE, SirenEntity, SirenEntityFeature from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index 86b4b6b0b72..15aa0a787bb 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -4,8 +4,7 @@ from datetime import timedelta import logging from typing import Any -from ring_doorbell import RingStickUpCam -from ring_doorbell.generic import RingGeneric +from ring_doorbell import RingGeneric, RingStickUpCam from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 758643f912e..833d84265a6 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -27,20 +27,15 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_ring_auth(): """Mock ring_doorbell.Auth.""" - with patch("ring_doorbell.Auth", autospec=True) as mock_ring_auth: + with patch( + "homeassistant.components.ring.config_flow.Auth", autospec=True + ) as mock_ring_auth: mock_ring_auth.return_value.fetch_token.return_value = { "access_token": "mock-token" } yield mock_ring_auth.return_value -@pytest.fixture -def mock_ring(): - """Mock ring_doorbell.Ring.""" - with patch("ring_doorbell.Ring", autospec=True) as mock_ring: - yield mock_ring.return_value - - @pytest.fixture def mock_config_entry() -> MockConfigEntry: """Mock ConfigEntry.""" @@ -60,7 +55,6 @@ async def mock_added_config_entry( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_ring_auth: Mock, - mock_ring: Mock, ) -> MockConfigEntry: """Mock ConfigEntry that's been added to HA.""" mock_config_entry.add_to_hass(hass) From 4341b21a61f9994a426f0dcf82c50b941ae721fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 23:44:36 -1000 Subject: [PATCH 0946/1691] Migrate auth to use async_import_module to avoid blocking I/O in the event loop (#113387) --- homeassistant/auth/mfa_modules/__init__.py | 4 ++-- homeassistant/auth/providers/__init__.py | 6 ++++-- tests/auth/providers/test_homeassistant.py | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 0cfb7508dbf..fd4072ea88a 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -import importlib import logging import types from typing import Any @@ -15,6 +14,7 @@ from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.importlib import async_import_module from homeassistant.util.decorator import Registry MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry() @@ -149,7 +149,7 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.Modul module_path = f"homeassistant.auth.mfa_modules.{module_name}" try: - module = importlib.import_module(module_path) + module = await async_import_module(hass, module_path) except ImportError as err: _LOGGER.error("Unable to load mfa module %s: %s", module_name, err) raise HomeAssistantError( diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 0a3d697eeea..63028f54d2e 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Mapping -import importlib import logging import types from typing import Any @@ -15,6 +14,7 @@ from homeassistant import data_entry_flow, requirements from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.importlib import async_import_module from homeassistant.util import dt as dt_util from homeassistant.util.decorator import Registry @@ -157,7 +157,9 @@ async def load_auth_provider_module( ) -> types.ModuleType: """Load an auth provider.""" try: - module = importlib.import_module(f"homeassistant.auth.providers.{provider}") + module = await async_import_module( + hass, f"homeassistant.auth.providers.{provider}" + ) except ImportError as err: _LOGGER.error("Unable to load auth provider %s: %s", provider, err) raise HomeAssistantError( diff --git a/tests/auth/providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py index 22d4ba26845..dc5c255579c 100644 --- a/tests/auth/providers/test_homeassistant.py +++ b/tests/auth/providers/test_homeassistant.py @@ -41,6 +41,7 @@ async def test_validating_password_invalid_user(data, hass: HomeAssistant) -> No async def test_not_allow_set_id() -> None: """Test we are not allowed to set an ID in config.""" hass = Mock() + hass.data = {} with pytest.raises(vol.Invalid): await auth_provider_from_config( hass, None, {"type": "homeassistant", "id": "invalid"} From c122e32d20d6e4b518dc4732fe9216ecbffb5be8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 23:45:11 -1000 Subject: [PATCH 0947/1691] Fix telegram_bot doing blocking I/O in the event loop to import platforms (#113383) --- .../components/telegram_bot/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index ba9cd88eacc..7534f56ef4d 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio -import importlib import io from ipaddress import ip_network import logging @@ -41,6 +40,7 @@ from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType +from homeassistant.loader import async_get_loaded_integration _LOGGER = logging.getLogger(__name__) @@ -355,15 +355,21 @@ async def load_data( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Telegram bot component.""" - if not config[DOMAIN]: + domain_config: list[dict[str, Any]] = config[DOMAIN] + + if not domain_config: return False - for p_config in config[DOMAIN]: + platforms = await async_get_loaded_integration(hass, DOMAIN).async_get_platforms( + {p_config[CONF_PLATFORM] for p_config in domain_config} + ) + + for p_config in domain_config: # Each platform config gets its own bot bot = initialize_bot(p_config) - p_type = p_config.get(CONF_PLATFORM) + p_type: str = p_config[CONF_PLATFORM] - platform = importlib.import_module(f".{p_config[CONF_PLATFORM]}", __name__) + platform = platforms[p_type] _LOGGER.info("Setting up %s.%s", DOMAIN, p_type) try: From c466008fb4910d7e8d37de8e094936fbd45673c8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 23:45:59 -1000 Subject: [PATCH 0948/1691] Migrate sun to use single_config_entry (#113370) --- homeassistant/components/sun/__init__.py | 16 ++++++++++------ homeassistant/components/sun/config_flow.py | 3 --- homeassistant/components/sun/manifest.json | 3 ++- homeassistant/components/sun/strings.json | 3 --- homeassistant/generated/integrations.json | 3 ++- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index 0c494b7a268..a4964c94009 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -84,13 +84,17 @@ CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Track the state of the sun.""" - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=config, + if not hass.config_entries.async_entries(DOMAIN): + # We avoid creating an import flow if its already + # setup since it will have to import the config_flow + # module. + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) ) - ) return True diff --git a/homeassistant/components/sun/config_flow.py b/homeassistant/components/sun/config_flow.py index 399373c8cb4..30b64c60b9f 100644 --- a/homeassistant/components/sun/config_flow.py +++ b/homeassistant/components/sun/config_flow.py @@ -18,9 +18,6 @@ class SunConfigFlow(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - if user_input is not None: return self.async_create_entry(title=DEFAULT_NAME, data={}) diff --git a/homeassistant/components/sun/manifest.json b/homeassistant/components/sun/manifest.json index 2fdcaafe114..f6b4ae1976b 100644 --- a/homeassistant/components/sun/manifest.json +++ b/homeassistant/components/sun/manifest.json @@ -5,5 +5,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sun", "iot_class": "calculated", - "quality_scale": "internal" + "quality_scale": "internal", + "single_config_entry": true } diff --git a/homeassistant/components/sun/strings.json b/homeassistant/components/sun/strings.json index eb538eedf09..7c7accd8cc6 100644 --- a/homeassistant/components/sun/strings.json +++ b/homeassistant/components/sun/strings.json @@ -5,9 +5,6 @@ "user": { "description": "[%key:common::config_flow::description::confirm_setup%]" } - }, - "abort": { - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } }, "entity_component": { diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index a84a78324cf..e745c52db69 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5743,7 +5743,8 @@ "sun": { "integration_type": "hub", "config_flow": true, - "iot_class": "calculated" + "iot_class": "calculated", + "single_config_entry": true }, "sunweg": { "name": "Sun WEG", From 972efada7593c02b89dab1129f5315d68aef5ea6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:46:52 +0100 Subject: [PATCH 0949/1691] Add service icons to Zoneminder (#113366) --- homeassistant/components/zoneminder/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/zoneminder/icons.json diff --git a/homeassistant/components/zoneminder/icons.json b/homeassistant/components/zoneminder/icons.json new file mode 100644 index 00000000000..8ca180d7399 --- /dev/null +++ b/homeassistant/components/zoneminder/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_run_state": "mdi:cog" + } +} From 6832611550fd9440a128b00fb43e3ad4c33e1dcb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 13 Mar 2024 23:47:18 -1000 Subject: [PATCH 0950/1691] Fix smartthings doing blocking I/O in the event loop to import platforms (#113382) --- homeassistant/components/smartthings/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 6f213936447..8136806cd0b 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -27,6 +27,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType +from homeassistant.loader import async_get_loaded_integration from .config_flow import SmartThingsFlowHandler # noqa: F401 from .const import ( @@ -104,6 +105,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api = SmartThings(async_get_clientsession(hass), entry.data[CONF_ACCESS_TOKEN]) + # Ensure platform modules are loaded since the DeviceBroker will + # import them below and we want them to be cached ahead of time + # so the integration does not do blocking I/O in the event loop + # to import the modules. + await async_get_loaded_integration(hass, DOMAIN).async_get_platforms(PLATFORMS) + remove_entry = False try: # See if the app is already setup. This occurs when there are From 6d903300bea31cf909ad6d0e0db1090d1c628f02 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Thu, 14 Mar 2024 05:53:55 -0400 Subject: [PATCH 0951/1691] Apply suggestion failures fail supervisor repair (#113372) --- homeassistant/components/hassio/handler.py | 5 +- homeassistant/components/hassio/repairs.py | 11 ++-- tests/components/hassio/test_issues.py | 3 +- tests/components/hassio/test_repairs.py | 72 ++++++++++++++++++++++ 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 148f9299954..db0a5ac0528 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -263,10 +263,7 @@ async def async_update_core( @bind_hass @_api_bool async def async_apply_suggestion(hass: HomeAssistant, suggestion_uuid: str) -> dict: - """Apply a suggestion from supervisor's resolution center. - - The caller of the function should handle HassioAPIError. - """ + """Apply a suggestion from supervisor's resolution center.""" hassio: HassIO = hass.data[DOMAIN] command = f"/resolution/suggestion/{suggestion_uuid}" return await hassio.send_command(command, timeout=None) diff --git a/homeassistant/components/hassio/repairs.py b/homeassistant/components/hassio/repairs.py index 42fe5148be7..8458d7eaac2 100644 --- a/homeassistant/components/hassio/repairs.py +++ b/homeassistant/components/hassio/repairs.py @@ -19,7 +19,7 @@ from .const import ( PLACEHOLDER_KEY_REFERENCE, SupervisorIssueContext, ) -from .handler import HassioAPIError, async_apply_suggestion +from .handler import async_apply_suggestion from .issues import Issue, Suggestion SUGGESTION_CONFIRMATION_REQUIRED = {"system_execute_reboot"} @@ -110,12 +110,9 @@ class SupervisorIssueRepairFlow(RepairsFlow): if not confirmed and suggestion.key in SUGGESTION_CONFIRMATION_REQUIRED: return self._async_form_for_suggestion(suggestion) - try: - await async_apply_suggestion(self.hass, suggestion.uuid) - except HassioAPIError: - return self.async_abort(reason="apply_suggestion_fail") - - return self.async_create_entry(data={}) + if await async_apply_suggestion(self.hass, suggestion.uuid): + return self.async_create_entry(data={}) + return self.async_abort(reason="apply_suggestion_fail") @staticmethod def _async_step( diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 4daa6ab0f58..9a715f385ee 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -41,6 +41,7 @@ def mock_resolution_info( unsupported: list[str] | None = None, unhealthy: list[str] | None = None, issues: list[dict[str, str]] | None = None, + suggestion_result: str = "ok", ): """Mock resolution/info endpoint with unsupported/unhealthy reasons and/or issues.""" aioclient_mock.get( @@ -77,7 +78,7 @@ def mock_resolution_info( for suggestion in suggestions: aioclient_mock.post( f"http://127.0.0.1/resolution/suggestion/{suggestion['uuid']}", - json={"result": "ok"}, + json={"result": suggestion_result}, ) diff --git a/tests/components/hassio/test_repairs.py b/tests/components/hassio/test_repairs.py index 6a9287e0331..d387968da46 100644 --- a/tests/components/hassio/test_repairs.py +++ b/tests/components/hassio/test_repairs.py @@ -396,6 +396,78 @@ async def test_supervisor_issue_repair_flow_skip_confirmation( ) +async def test_mount_failed_repair_flow_error( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + hass_client: ClientSessionGenerator, + issue_registry: ir.IssueRegistry, + all_setup_requests, +) -> None: + """Test repair flow fails when repair fails to apply.""" + mock_resolution_info( + aioclient_mock, + issues=[ + { + "uuid": "1234", + "type": "mount_failed", + "context": "mount", + "reference": "backup_share", + "suggestions": [ + { + "uuid": "1235", + "type": "execute_reload", + "context": "mount", + "reference": "backup_share", + }, + { + "uuid": "1236", + "type": "execute_remove", + "context": "mount", + "reference": "backup_share", + }, + ], + }, + ], + suggestion_result=False, + ) + + assert await async_setup_component(hass, "hassio", {}) + + repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234") + assert repair_issue + + client = await hass_client() + + resp = await client.post( + "/api/repairs/issues/fix", + json={"handler": "hassio", "issue_id": repair_issue.issue_id}, + ) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + flow_id = data["flow_id"] + + resp = await client.post( + f"/api/repairs/issues/fix/{flow_id}", + json={"next_step_id": "mount_execute_reload"}, + ) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "type": "abort", + "flow_id": flow_id, + "handler": "hassio", + "reason": "apply_suggestion_fail", + "result": None, + "description_placeholders": None, + } + + assert issue_registry.async_get_issue(domain="hassio", issue_id="1234") + + async def test_mount_failed_repair_flow( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, From 5a25349cf7759534b43a73d144df470e6728b16f Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Thu, 14 Mar 2024 05:55:04 -0400 Subject: [PATCH 0952/1691] Supervisor issues update retries on failure (#113373) --- homeassistant/components/hassio/issues.py | 12 +++- tests/components/hassio/test_issues.py | 78 ++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hassio/issues.py b/homeassistant/components/hassio/issues.py index c47ea315089..0d78c71d4af 100644 --- a/homeassistant/components/hassio/issues.py +++ b/homeassistant/components/hassio/issues.py @@ -4,11 +4,13 @@ from __future__ import annotations import asyncio from dataclasses import dataclass, field +from datetime import datetime import logging from typing import Any, NotRequired, TypedDict -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.issue_registry import ( IssueSeverity, async_create_issue, @@ -36,6 +38,7 @@ from .const import ( EVENT_SUPPORTED_CHANGED, ISSUE_KEY_SYSTEM_DOCKER_CONFIG, PLACEHOLDER_KEY_REFERENCE, + REQUEST_REFRESH_DELAY, UPDATE_KEY_SUPERVISOR, SupervisorIssueContext, ) @@ -303,12 +306,17 @@ class SupervisorIssues: self._hass, EVENT_SUPERVISOR_EVENT, self._supervisor_events_to_issues ) - async def update(self) -> None: + async def update(self, _: datetime | None = None) -> None: """Update issues from Supervisor resolution center.""" try: data = await self._client.get_resolution_info() except HassioAPIError as err: _LOGGER.error("Failed to update supervisor issues: %r", err) + async_call_later( + self._hass, + REQUEST_REFRESH_DELAY, + HassJob(self.update, cancel_on_shutdown=True), + ) return self.unhealthy_reasons = set(data[ATTR_UNHEALTHY]) self.unsupported_reasons = set(data[ATTR_UNSUPPORTED]) diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 9a715f385ee..6b0b2e5fdc4 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -2,6 +2,8 @@ from __future__ import annotations +import asyncio +from http import HTTPStatus import os from typing import Any from unittest.mock import ANY, patch @@ -14,7 +16,7 @@ from homeassistant.setup import async_setup_component from .test_init import MOCK_ENVIRON -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.test_util.aiohttp import AiohttpClientMocker, AiohttpClientMockResponse from tests.typing import WebSocketGenerator @@ -530,6 +532,80 @@ async def test_supervisor_issues( ) +async def test_supervisor_issues_initial_failure( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test issues manager retries after initial update failure.""" + responses = [ + AiohttpClientMockResponse( + method="get", + url="http://127.0.0.1/resolution/info", + status=HTTPStatus.BAD_REQUEST, + json={ + "result": "error", + "message": "System is not ready with state: setup", + }, + ), + AiohttpClientMockResponse( + method="get", + url="http://127.0.0.1/resolution/info", + status=HTTPStatus.OK, + json={ + "result": "ok", + "data": { + "unsupported": [], + "unhealthy": [], + "suggestions": [], + "issues": [ + { + "uuid": "1234", + "type": "reboot_required", + "context": "system", + "reference": None, + }, + ], + "checks": [ + {"enabled": True, "slug": "supervisor_trust"}, + {"enabled": True, "slug": "free_space"}, + ], + }, + }, + ), + ] + + async def mock_responses(*args): + nonlocal responses + return responses.pop(0) + + aioclient_mock.get( + "http://127.0.0.1/resolution/info", + side_effect=mock_responses, + ) + aioclient_mock.get( + "http://127.0.0.1/resolution/issue/1234/suggestions", + json={"result": "ok", "data": {"suggestions": []}}, + ) + + with patch("homeassistant.components.hassio.issues.REQUEST_REFRESH_DELAY", new=0.1): + result = await async_setup_component(hass, "hassio", {}) + assert result + + client = await hass_ws_client(hass) + + await client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 0 + + await asyncio.sleep(0.1) + await client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 1 + + async def test_supervisor_issues_add_remove( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, From fc85b4f12386edbcec0d5f9bdc70ccec57bfe51d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:59:01 +0100 Subject: [PATCH 0953/1691] Add service icons to Wake on LAN (#113362) --- homeassistant/components/wake_on_lan/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/wake_on_lan/icons.json diff --git a/homeassistant/components/wake_on_lan/icons.json b/homeassistant/components/wake_on_lan/icons.json new file mode 100644 index 00000000000..6426c478157 --- /dev/null +++ b/homeassistant/components/wake_on_lan/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "send_magic_packet": "mdi:cube-send" + } +} From ca916bcc98b605a69ff3ef20e5fe3b3cf1b4f16f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:59:28 +0100 Subject: [PATCH 0954/1691] Add service icons to System Log (#113355) --- homeassistant/components/system_log/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/system_log/icons.json diff --git a/homeassistant/components/system_log/icons.json b/homeassistant/components/system_log/icons.json new file mode 100644 index 00000000000..436a6c34808 --- /dev/null +++ b/homeassistant/components/system_log/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "clear": "mdi:delete", + "write": "mdi:pencil" + } +} From 73af5f5be11bcf785e58d8affbd53dc034e34b13 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 10:59:49 +0100 Subject: [PATCH 0955/1691] Add service icons to Statistics (#113353) --- homeassistant/components/statistics/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/statistics/icons.json diff --git a/homeassistant/components/statistics/icons.json b/homeassistant/components/statistics/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/statistics/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From cd902c917c15713bd43833e8c4c4cf8162abfd5b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:00:27 +0100 Subject: [PATCH 0956/1691] Add service icons to Universal (#113361) --- homeassistant/components/universal/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/universal/icons.json diff --git a/homeassistant/components/universal/icons.json b/homeassistant/components/universal/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/universal/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 246017b3fa6e1cdfe93aebcfc7c2f7bdb64642e5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:03:13 +0100 Subject: [PATCH 0957/1691] Add service icons to Alert (#113274) --- homeassistant/components/alert/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/alert/icons.json diff --git a/homeassistant/components/alert/icons.json b/homeassistant/components/alert/icons.json new file mode 100644 index 00000000000..7f5258706d2 --- /dev/null +++ b/homeassistant/components/alert/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "toggle": "mdi:bell-ring", + "turn_off": "mdi:bell-off", + "turn_on": "mdi:bell-alert" + } +} From 4c3943e264b8423c559248696f06c4c8038a65e4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:35:43 +0100 Subject: [PATCH 0958/1691] Add service icons to System Bridge (#113354) --- homeassistant/components/system_bridge/icons.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 homeassistant/components/system_bridge/icons.json diff --git a/homeassistant/components/system_bridge/icons.json b/homeassistant/components/system_bridge/icons.json new file mode 100644 index 00000000000..cc648889f0b --- /dev/null +++ b/homeassistant/components/system_bridge/icons.json @@ -0,0 +1,11 @@ +{ + "services": { + "get_process_by_id": "mdi:console", + "get_processes_by_name": "mdi:console", + "open_path": "mdi:folder-open", + "open_url": "mdi:web", + "send_keypress": "mdi:keyboard", + "send_text": "mdi:keyboard", + "power_command": "mdi:power" + } +} From 11e69f6baa722a14e38ce3687e2f3f2f344d42d5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:40:22 +0100 Subject: [PATCH 0959/1691] Add service icons to Timer (#113358) --- homeassistant/components/timer/icons.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 homeassistant/components/timer/icons.json diff --git a/homeassistant/components/timer/icons.json b/homeassistant/components/timer/icons.json new file mode 100644 index 00000000000..4cad5c119bd --- /dev/null +++ b/homeassistant/components/timer/icons.json @@ -0,0 +1,10 @@ +{ + "services": { + "start": "mdi:start", + "pause": "mdi:pause", + "cancel": "mdi:cancel", + "finish": "mdi:check", + "change": "mdi:pencil", + "reload": "mdi:reload" + } +} From eeaf8ddd690d237ec69b2c9f3c27c34fd671d3a0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:40:46 +0100 Subject: [PATCH 0960/1691] Add service icons to SMTP (#113351) --- homeassistant/components/smtp/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/smtp/icons.json diff --git a/homeassistant/components/smtp/icons.json b/homeassistant/components/smtp/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/smtp/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 4f120ea223f08ddd33154c7137b03afccedd2676 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:41:09 +0100 Subject: [PATCH 0961/1691] Add service icons to Schedule (#113350) --- homeassistant/components/schedule/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/schedule/icons.json diff --git a/homeassistant/components/schedule/icons.json b/homeassistant/components/schedule/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/schedule/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 374695bfa64618a06bd60e1c33bb725693e0f36b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:41:43 +0100 Subject: [PATCH 0962/1691] Add service icons to Recorder (#113344) --- homeassistant/components/recorder/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/recorder/icons.json diff --git a/homeassistant/components/recorder/icons.json b/homeassistant/components/recorder/icons.json new file mode 100644 index 00000000000..1090401abd5 --- /dev/null +++ b/homeassistant/components/recorder/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "purge": "mdi:database-sync", + "purge_entities": "mdi:database-sync", + "disable": "mdi:database-off", + "enable": "mdi:database" + } +} From 1c56c7b136b143ab92411a9002e0e4b19deed3ce Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:42:12 +0100 Subject: [PATCH 0963/1691] Add service icons to Snips (#113352) --- homeassistant/components/snips/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/snips/icons.json diff --git a/homeassistant/components/snips/icons.json b/homeassistant/components/snips/icons.json new file mode 100644 index 00000000000..0d465465fe4 --- /dev/null +++ b/homeassistant/components/snips/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "feedback_off": "mdi:message-alert", + "feedback_on": "mdi:message-alert", + "say": "mdi:chat", + "say_action": "mdi:account-voice" + } +} From 77a67191de9cf8ee1c28d52034783da640035abe Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 11:42:40 +0100 Subject: [PATCH 0964/1691] Add service icons to Ombi (#113339) --- homeassistant/components/ombi/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/ombi/icons.json diff --git a/homeassistant/components/ombi/icons.json b/homeassistant/components/ombi/icons.json new file mode 100644 index 00000000000..4b3e32a1e13 --- /dev/null +++ b/homeassistant/components/ombi/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "submit_movie_request": "mdi:movie-roll", + "submit_tv_request": "mdi:television-classic", + "submit_music_request": "mdi:music" + } +} From 7696973932f2fec801325d783ba8f715b925fdde Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 14 Mar 2024 12:54:01 +0100 Subject: [PATCH 0965/1691] Move modbus check_config to setup, to have access to hass (#112828) --- homeassistant/components/modbus/__init__.py | 2 - homeassistant/components/modbus/modbus.py | 3 + homeassistant/components/modbus/validators.py | 185 +++++++++--------- tests/components/modbus/test_init.py | 20 +- 4 files changed, 109 insertions(+), 101 deletions(-) diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index d008f5d167c..eb1f2ec0f60 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -132,7 +132,6 @@ from .const import ( # noqa: F401 ) from .modbus import ModbusHub, async_modbus_setup from .validators import ( - check_config, check_hvac_target_temp_registers, duplicate_fan_mode_validator, hvac_fixedsize_reglist_validator, @@ -418,7 +417,6 @@ CONFIG_SCHEMA = vol.Schema( [ vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA), ], - check_config, ), }, extra=vol.ALLOW_EXTRA, diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 7138341d445..d08e8b80577 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -69,6 +69,7 @@ from .const import ( TCP, UDP, ) +from .validators import check_config _LOGGER = logging.getLogger(__name__) @@ -127,6 +128,8 @@ async def async_modbus_setup( await async_setup_reload_service(hass, DOMAIN, [DOMAIN]) + if config[DOMAIN]: + config[DOMAIN] = check_config(hass, config[DOMAIN]) if DOMAIN in hass.data and config[DOMAIN] == []: hubs = hass.data[DOMAIN] for name in hubs: diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 2e434933eae..51c8a08079b 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -24,6 +24,7 @@ from homeassistant.const import ( CONF_TIMEOUT, CONF_TYPE, ) +from homeassistant.core import HomeAssistant from .const import ( CONF_DATA_TYPE, @@ -270,7 +271,99 @@ def register_int_list_validator(value: Any) -> Any: ) -def check_config(config: dict) -> dict: +def validate_modbus( + hass: HomeAssistant, + hosts: set[str], + hub_names: set[str], + hub: dict, + hub_name_inx: int, +) -> bool: + """Validate modbus entries.""" + host: str = ( + hub[CONF_PORT] + if hub[CONF_TYPE] == SERIAL + else f"{hub[CONF_HOST]}_{hub[CONF_PORT]}" + ) + if CONF_NAME not in hub: + hub[CONF_NAME] = ( + DEFAULT_HUB if not hub_name_inx else f"{DEFAULT_HUB}_{hub_name_inx}" + ) + hub_name_inx += 1 + err = f"Modbus host/port {host} is missing name, added {hub[CONF_NAME]}!" + _LOGGER.warning(err) + name = hub[CONF_NAME] + if host in hosts or name in hub_names: + err = f"Modbus {name} host/port {host} is duplicate, not loaded!" + _LOGGER.warning(err) + return False + hosts.add(host) + hub_names.add(name) + return True + + +def validate_entity( + hass: HomeAssistant, + hub_name: str, + component: str, + entity: dict, + minimum_scan_interval: int, + ent_names: set[str], + ent_addr: set[str], +) -> bool: + """Validate entity.""" + name = f"{component}.{entity[CONF_NAME]}" + addr = f"{hub_name}{entity[CONF_ADDRESS]}" + scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + if 0 < scan_interval < 5: + _LOGGER.warning( + ( + "%s %s scan_interval(%d) is lower than 5 seconds, " + "which may cause Home Assistant stability issues" + ), + hub_name, + name, + scan_interval, + ) + entity[CONF_SCAN_INTERVAL] = scan_interval + minimum_scan_interval = min(scan_interval, minimum_scan_interval) + for conf_type in ( + CONF_INPUT_TYPE, + CONF_WRITE_TYPE, + CONF_COMMAND_ON, + CONF_COMMAND_OFF, + ): + if conf_type in entity: + addr += f"_{entity[conf_type]}" + inx = entity.get(CONF_SLAVE) or entity.get(CONF_DEVICE_ADDRESS, 0) + addr += f"_{inx}" + loc_addr: set[str] = {addr} + + if CONF_TARGET_TEMP in entity: + loc_addr.add(f"{hub_name}{entity[CONF_TARGET_TEMP]}_{inx}") + if CONF_HVAC_MODE_REGISTER in entity: + loc_addr.add(f"{hub_name}{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}") + if CONF_FAN_MODE_REGISTER in entity: + loc_addr.add(f"{hub_name}{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}") + + dup_addrs = ent_addr.intersection(loc_addr) + if len(dup_addrs) > 0: + for addr in dup_addrs: + err = ( + f"Modbus {hub_name}/{name} address {addr} is duplicate, second" + " entry not loaded!" + ) + _LOGGER.warning(err) + return False + if name in ent_names: + err = f"Modbus {hub_name}/{name} is duplicate, second entry not loaded!" + _LOGGER.warning(err) + return False + ent_names.add(name) + ent_addr.update(loc_addr) + return True + + +def check_config(hass: HomeAssistant, config: dict) -> dict: """Do final config check.""" hosts: set[str] = set() hub_names: set[str] = set() @@ -279,97 +372,10 @@ def check_config(config: dict) -> dict: ent_names: set[str] = set() ent_addr: set[str] = set() - def validate_modbus(hub: dict, hub_name_inx: int) -> bool: - """Validate modbus entries.""" - host: str = ( - hub[CONF_PORT] - if hub[CONF_TYPE] == SERIAL - else f"{hub[CONF_HOST]}_{hub[CONF_PORT]}" - ) - if CONF_NAME not in hub: - hub[CONF_NAME] = ( - DEFAULT_HUB if not hub_name_inx else f"{DEFAULT_HUB}_{hub_name_inx}" - ) - hub_name_inx += 1 - err = f"Modbus host/port {host} is missing name, added {hub[CONF_NAME]}!" - _LOGGER.warning(err) - name = hub[CONF_NAME] - if host in hosts or name in hub_names: - err = f"Modbus {name} host/port {host} is duplicate, not loaded!" - _LOGGER.warning(err) - return False - hosts.add(host) - hub_names.add(name) - return True - - def validate_entity( - hub_name: str, - component: str, - entity: dict, - minimum_scan_interval: int, - ent_names: set, - ent_addr: set, - ) -> bool: - """Validate entity.""" - name = f"{component}.{entity[CONF_NAME]}" - addr = f"{hub_name}{entity[CONF_ADDRESS]}" - scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) - if 0 < scan_interval < 5: - _LOGGER.warning( - ( - "%s %s scan_interval(%d) is lower than 5 seconds, " - "which may cause Home Assistant stability issues" - ), - hub_name, - name, - scan_interval, - ) - entity[CONF_SCAN_INTERVAL] = scan_interval - minimum_scan_interval = min(scan_interval, minimum_scan_interval) - for conf_type in ( - CONF_INPUT_TYPE, - CONF_WRITE_TYPE, - CONF_COMMAND_ON, - CONF_COMMAND_OFF, - ): - if conf_type in entity: - addr += f"_{entity[conf_type]}" - inx = entity.get(CONF_SLAVE) or entity.get(CONF_DEVICE_ADDRESS, 0) - addr += f"_{inx}" - loc_addr: set[str] = {addr} - - if CONF_TARGET_TEMP in entity: - loc_addr.add(f"{hub_name}{entity[CONF_TARGET_TEMP]}_{inx}") - if CONF_HVAC_MODE_REGISTER in entity: - loc_addr.add( - f"{hub_name}{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}" - ) - if CONF_FAN_MODE_REGISTER in entity: - loc_addr.add( - f"{hub_name}{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}" - ) - - dup_addrs = ent_addr.intersection(loc_addr) - if len(dup_addrs) > 0: - for addr in dup_addrs: - err = ( - f"Modbus {hub_name}/{name} address {addr} is duplicate, second" - " entry not loaded!" - ) - _LOGGER.warning(err) - return False - if name in ent_names: - err = f"Modbus {hub_name}/{name} is duplicate, second entry not loaded!" - _LOGGER.warning(err) - return False - ent_names.add(name) - ent_addr.update(loc_addr) - return True - hub_inx = 0 while hub_inx < len(config): hub = config[hub_inx] - if not validate_modbus(hub, hub_name_inx): + if not validate_modbus(hass, hosts, hub_names, hub, hub_name_inx): del config[hub_inx] continue minimum_scan_interval = 9999 @@ -382,6 +388,7 @@ def check_config(config: dict) -> dict: entities = hub[conf_key] while entity_inx < len(entities): if not validate_entity( + hass, hub[CONF_NAME], component, entities[entity_inx], diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index aaf2b335b90..3e0e94f4076 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -423,9 +423,9 @@ async def test_exception_struct_validator(do_config) -> None: ], ], ) -async def test_check_config(do_config) -> None: +async def test_check_config(hass: HomeAssistant, do_config) -> None: """Test duplicate modbus validator.""" - check_config(do_config) + check_config(hass, do_config) assert len(do_config) == 1 @@ -476,9 +476,9 @@ async def test_check_config(do_config) -> None: ], ], ) -async def test_check_config_sensor(do_config) -> None: +async def test_check_config_sensor(hass: HomeAssistant, do_config) -> None: """Test duplicate entity validator.""" - check_config(do_config) + check_config(hass, do_config) assert len(do_config[0][CONF_SENSORS]) == 1 @@ -691,9 +691,9 @@ async def test_check_config_sensor(do_config) -> None: ], ], ) -async def test_check_config_climate(do_config) -> None: +async def test_check_config_climate(hass: HomeAssistant, do_config) -> None: """Test duplicate entity validator.""" - check_config(do_config) + check_config(hass, do_config) assert len(do_config[0][CONF_CLIMATES]) == 1 @@ -913,9 +913,9 @@ async def test_duplicate_fan_mode_validator(do_config) -> None: ), ], ) -async def test_duplicate_addresses(do_config, sensor_cnt) -> None: +async def test_duplicate_addresses(hass: HomeAssistant, do_config, sensor_cnt) -> None: """Test duplicate entity validator.""" - check_config(do_config) + check_config(hass, do_config) use_inx = len(do_config) - 1 assert len(do_config[use_inx][CONF_SENSORS]) == sensor_cnt @@ -948,9 +948,9 @@ async def test_duplicate_addresses(do_config, sensor_cnt) -> None: ], ], ) -async def test_no_duplicate_names(do_config) -> None: +async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: """Test duplicate entity validator.""" - check_config(do_config) + check_config(hass, do_config) assert len(do_config[0][CONF_SENSORS]) == 1 assert len(do_config[0][CONF_BINARY_SENSORS]) == 1 From a6594f886060f4fa005dafbc9f7550242a98170b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 14 Mar 2024 12:54:43 +0100 Subject: [PATCH 0966/1691] Bump hass-nabucasa from 0.78.0 to 0.79.0 (#113405) --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index e816516fd7a..ac9f47935d4 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -8,5 +8,5 @@ "integration_type": "system", "iot_class": "cloud_push", "loggers": ["hass_nabucasa"], - "requirements": ["hass-nabucasa==0.78.0"] + "requirements": ["hass-nabucasa==0.79.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index dc69875e11e..7747d78f88f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -27,7 +27,7 @@ fnv-hash-fast==0.5.0 ha-av==10.1.1 ha-ffmpeg==3.2.0 habluetooth==2.4.2 -hass-nabucasa==0.78.0 +hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240307.0 diff --git a/pyproject.toml b/pyproject.toml index e6ee50f1a3f..8ccfd9e8aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "fnv-hash-fast==0.5.0", # hass-nabucasa is imported by helpers which don't depend on the cloud # integration - "hass-nabucasa==0.78.0", + "hass-nabucasa==0.79.0", # When bumping httpx, please check the version pins of # httpcore, anyio, and h11 in gen_requirements_all "httpx==0.27.0", diff --git a/requirements.txt b/requirements.txt index 03f3ec987fb..82ff6ba3ce4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ bcrypt==4.1.2 certifi>=2021.5.30 ciso8601==2.3.1 fnv-hash-fast==0.5.0 -hass-nabucasa==0.78.0 +hass-nabucasa==0.79.0 httpx==0.27.0 home-assistant-bluetooth==1.12.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index ad00d983451..79dfd705988 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1031,7 +1031,7 @@ habitipy==0.2.0 habluetooth==2.4.2 # homeassistant.components.cloud -hass-nabucasa==0.78.0 +hass-nabucasa==0.79.0 # homeassistant.components.splunk hass-splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 484f685611a..9650ff49963 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -842,7 +842,7 @@ habitipy==0.2.0 habluetooth==2.4.2 # homeassistant.components.cloud -hass-nabucasa==0.78.0 +hass-nabucasa==0.79.0 # homeassistant.components.conversation hassil==1.6.1 From 064f96f8fa713f27eb51d9965ab14baa713e93d2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 14 Mar 2024 12:55:31 +0100 Subject: [PATCH 0967/1691] Add floor support to areas WebSocket API (#113402) --- homeassistant/components/config/area_registry.py | 3 +++ tests/components/config/test_area_registry.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index 435277c5192..acddf7bb200 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -40,6 +40,7 @@ def websocket_list_areas( { vol.Required("type"): "config/area_registry/create", vol.Optional("aliases"): list, + vol.Optional("floor_id"): str, vol.Optional("icon"): str, vol.Required("name"): str, vol.Optional("picture"): vol.Any(str, None), @@ -100,6 +101,7 @@ def websocket_delete_area( vol.Required("type"): "config/area_registry/update", vol.Optional("aliases"): list, vol.Required("area_id"): str, + vol.Optional("floor_id"): vol.Any(str, None), vol.Optional("icon"): vol.Any(str, None), vol.Optional("name"): str, vol.Optional("picture"): vol.Any(str, None), @@ -137,6 +139,7 @@ def _entry_dict(entry: AreaEntry) -> dict[str, Any]: return { "aliases": list(entry.aliases), "area_id": entry.id, + "floor_id": entry.floor_id, "icon": entry.icon, "name": entry.name, "picture": entry.picture, diff --git a/tests/components/config/test_area_registry.py b/tests/components/config/test_area_registry.py index fb9c5fd453b..8bedd1a0a29 100644 --- a/tests/components/config/test_area_registry.py +++ b/tests/components/config/test_area_registry.py @@ -30,6 +30,7 @@ async def test_list_areas( aliases={"alias_1", "alias_2"}, icon="mdi:garage", picture="/image/example.png", + floor_id="first_floor", ) await client.send_json_auto_id({"type": "config/area_registry/list"}) @@ -39,6 +40,7 @@ async def test_list_areas( { "aliases": [], "area_id": area1.id, + "floor_id": None, "icon": None, "name": "mock 1", "picture": None, @@ -46,6 +48,7 @@ async def test_list_areas( { "aliases": unordered(["alias_1", "alias_2"]), "area_id": area2.id, + "floor_id": "first_floor", "icon": "mdi:garage", "name": "mock 2", "picture": "/image/example.png", @@ -67,6 +70,7 @@ async def test_create_area( assert msg["result"] == { "aliases": [], "area_id": ANY, + "floor_id": None, "icon": None, "name": "mock", "picture": None, @@ -77,6 +81,7 @@ async def test_create_area( await client.send_json_auto_id( { "aliases": ["alias_1", "alias_2"], + "floor_id": "first_floor", "icon": "mdi:garage", "name": "mock 2", "picture": "/image/example.png", @@ -89,6 +94,7 @@ async def test_create_area( assert msg["result"] == { "aliases": unordered(["alias_1", "alias_2"]), "area_id": ANY, + "floor_id": "first_floor", "icon": "mdi:garage", "name": "mock 2", "picture": "/image/example.png", @@ -158,6 +164,7 @@ async def test_update_area( { "aliases": ["alias_1", "alias_2"], "area_id": area.id, + "floor_id": "first_floor", "icon": "mdi:garage", "name": "mock 2", "picture": "/image/example.png", @@ -170,6 +177,7 @@ async def test_update_area( assert msg["result"] == { "aliases": unordered(["alias_1", "alias_2"]), "area_id": area.id, + "floor_id": "first_floor", "icon": "mdi:garage", "name": "mock 2", "picture": "/image/example.png", @@ -180,6 +188,7 @@ async def test_update_area( { "aliases": ["alias_1", "alias_1"], "area_id": area.id, + "floor_id": None, "icon": None, "picture": None, "type": "config/area_registry/update", @@ -191,6 +200,7 @@ async def test_update_area( assert msg["result"] == { "aliases": ["alias_1"], "area_id": area.id, + "floor_id": None, "icon": None, "name": "mock 2", "picture": None, From ffe68107698462e460ba1acf882c8735aef6b191 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:05:43 +0100 Subject: [PATCH 0968/1691] Add service icons to Yamaha (#113365) --- homeassistant/components/yamaha/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/yamaha/icons.json diff --git a/homeassistant/components/yamaha/icons.json b/homeassistant/components/yamaha/icons.json new file mode 100644 index 00000000000..f7075508b0d --- /dev/null +++ b/homeassistant/components/yamaha/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "enable_output": "mdi:audio-input-stereo-minijack", + "menu_cursor": "mdi:cursor-default", + "select_scene": "mdi:palette" + } +} From 5f48083ecab4a3d665eda229f84519191e01b707 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:11:55 +0100 Subject: [PATCH 0969/1691] Add service icons to Wemo (#113363) --- homeassistant/components/wemo/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/wemo/icons.json diff --git a/homeassistant/components/wemo/icons.json b/homeassistant/components/wemo/icons.json new file mode 100644 index 00000000000..c5ddf5912d6 --- /dev/null +++ b/homeassistant/components/wemo/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_humidity": "mdi:water-percent", + "reset_filter_life": "mdi:refresh" + } +} From 13a2db03486d402baab3ce681c25200415b5c229 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:12:03 +0100 Subject: [PATCH 0970/1691] Add service icons to Telegram bot (#113357) --- .../components/telegram_bot/icons.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 homeassistant/components/telegram_bot/icons.json diff --git a/homeassistant/components/telegram_bot/icons.json b/homeassistant/components/telegram_bot/icons.json new file mode 100644 index 00000000000..f410d387435 --- /dev/null +++ b/homeassistant/components/telegram_bot/icons.json @@ -0,0 +1,18 @@ +{ + "services": { + "send_message": "mdi:send", + "send_photo": "mdi:camera", + "send_sticker": "mdi:sticker", + "send_animation": "mdi:animation", + "send_video": "mdi:video", + "send_voice": "mdi:microphone", + "send_document": "mdi:file-document", + "send_location": "mdi:map-marker", + "send_poll": "mdi:poll", + "edit_message": "mdi:pencil", + "edit_caption": "mdi:pencil", + "edit_replymarkup": "mdi:pencil", + "answer_callback_query": "mdi:check", + "delete_message": "mdi:delete" + } +} From ca1c2475744737a77df65edddaa2ad20d0c40677 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:12:20 +0100 Subject: [PATCH 0971/1691] Add service icons to Toon (#113359) --- homeassistant/components/toon/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/toon/icons.json diff --git a/homeassistant/components/toon/icons.json b/homeassistant/components/toon/icons.json new file mode 100644 index 00000000000..650bf0b6d19 --- /dev/null +++ b/homeassistant/components/toon/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "update": "mdi:update" + } +} From 2a2f95ce8e98a8718d03b83f7ad91899cd5bb666 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:12:43 +0100 Subject: [PATCH 0972/1691] Add service icons to Telegram (#113356) --- homeassistant/components/telegram/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/telegram/icons.json diff --git a/homeassistant/components/telegram/icons.json b/homeassistant/components/telegram/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/telegram/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From b8d232ab7f0062085f0cc04f47f19c5d95387835 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:12:51 +0100 Subject: [PATCH 0973/1691] Add service icons to Route53 (#113349) --- homeassistant/components/route53/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/route53/icons.json diff --git a/homeassistant/components/route53/icons.json b/homeassistant/components/route53/icons.json new file mode 100644 index 00000000000..30a854991f0 --- /dev/null +++ b/homeassistant/components/route53/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "update_records": "mdi:database-refresh" + } +} From 98c250b9101817b29e7ec26ef65ccbde0f6a1c42 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:14:15 +0100 Subject: [PATCH 0974/1691] Add service icons to RFLink (#113348) * Add service icons to RFLink * Update homeassistant/components/rflink/icons.json --- homeassistant/components/rflink/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/rflink/icons.json diff --git a/homeassistant/components/rflink/icons.json b/homeassistant/components/rflink/icons.json new file mode 100644 index 00000000000..988b048eee7 --- /dev/null +++ b/homeassistant/components/rflink/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "send_command": "mdi:send" + } +} From bfe6d0873acec67d3a1dfbcf99b12c3d75d77733 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:14:48 +0100 Subject: [PATCH 0975/1691] Add service icons to Remember the milk (#113345) --- homeassistant/components/remember_the_milk/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/remember_the_milk/icons.json diff --git a/homeassistant/components/remember_the_milk/icons.json b/homeassistant/components/remember_the_milk/icons.json new file mode 100644 index 00000000000..3ca17113fb8 --- /dev/null +++ b/homeassistant/components/remember_the_milk/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "create_task": "mdi:check", + "complete_task": "mdi:check-all" + } +} From 8c13b817ea8b43bc8518ef3fbf694567c8beb446 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:15:11 +0100 Subject: [PATCH 0976/1691] Add service icons to Xiaomi Miio (#113364) --- .../components/xiaomi_miio/icons.json | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 homeassistant/components/xiaomi_miio/icons.json diff --git a/homeassistant/components/xiaomi_miio/icons.json b/homeassistant/components/xiaomi_miio/icons.json new file mode 100644 index 00000000000..a9daaf9a61c --- /dev/null +++ b/homeassistant/components/xiaomi_miio/icons.json @@ -0,0 +1,28 @@ +{ + "services": { + "fan_reset_filter": "mdi:refresh", + "fan_set_extra_features": "mdi:cog", + "light_set_scene": "mdi:palette", + "light_set_delayed_turn_off": "mdi:timer", + "light_reminder_on": "mdi:alarm", + "light_reminder_off": "mdi:alarm-off", + "light_night_light_mode_on": "mdi:weather-night", + "light_night_light_mode_off": "mdi:weather-sunny", + "light_eyecare_mode_on": "mdi:eye", + "light_eyecare_mode_off": "mdi:eye-off", + "remote_learn_command": "mdi:remote", + "remote_set_led_on": "mdi:led-on", + "remote_set_led_off": "mdi:led-off", + "switch_set_wifi_led_on": "mdi:wifi", + "switch_set_wifi_led_off": "mdi:wifi-off", + "switch_set_power_price": "mdi:currency-usd", + "switch_set_power_mode": "mdi:power", + "vacuum_remote_control_start": "mdi:start", + "vacuum_remote_control_stop": "mdi:stop", + "vacuum_remote_control_move": "mdi:remote", + "vacuum_remote_control_move_step": "mdi:remote", + "vacuum_clean_zone": "mdi:map-marker", + "vacuum_goto": "mdi:map-marker", + "vacuum_clean_segment": "mdi:map-marker" + } +} From 821a235c594e01554093a891e7ca48d32b9524e1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:15:25 +0100 Subject: [PATCH 0977/1691] Add service icons to QVR Pro (#113343) --- homeassistant/components/qvr_pro/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/qvr_pro/icons.json diff --git a/homeassistant/components/qvr_pro/icons.json b/homeassistant/components/qvr_pro/icons.json new file mode 100644 index 00000000000..556a8d40752 --- /dev/null +++ b/homeassistant/components/qvr_pro/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "start_record": "mdi:record-rec", + "stop_record": "mdi:stop" + } +} From 415402e7dcb36053729f9f4704292a6d9d9fc3c6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:16:12 +0100 Subject: [PATCH 0978/1691] Add service icons to Python Scripts (#113342) --- homeassistant/components/python_script/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/python_script/icons.json diff --git a/homeassistant/components/python_script/icons.json b/homeassistant/components/python_script/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/python_script/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 2f16774f66e5ca553ce7b5c332231efa24003bcd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:16:58 +0100 Subject: [PATCH 0979/1691] Add service icons to Pilight (#113341) --- homeassistant/components/pilight/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/pilight/icons.json diff --git a/homeassistant/components/pilight/icons.json b/homeassistant/components/pilight/icons.json new file mode 100644 index 00000000000..c1b8e741e45 --- /dev/null +++ b/homeassistant/components/pilight/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "send": "mdi:send" + } +} From 353b1cd08fcbe370ae94f8b83447ee626f846000 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:18:04 +0100 Subject: [PATCH 0980/1691] Add service icons to nx584 (#113338) --- homeassistant/components/nx584/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/nx584/icons.json diff --git a/homeassistant/components/nx584/icons.json b/homeassistant/components/nx584/icons.json new file mode 100644 index 00000000000..76e5ae82e09 --- /dev/null +++ b/homeassistant/components/nx584/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "bypass_zone": "mdi:wrench", + "unbypass_zone": "mdi:wrench" + } +} From d58ab859197b411b16e6d063dd7b0f37f0fa5f12 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:20:28 +0100 Subject: [PATCH 0981/1691] Add service icons to Nissan Leaf (#113336) --- homeassistant/components/nissan_leaf/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/nissan_leaf/icons.json diff --git a/homeassistant/components/nissan_leaf/icons.json b/homeassistant/components/nissan_leaf/icons.json new file mode 100644 index 00000000000..5da03ed5f1a --- /dev/null +++ b/homeassistant/components/nissan_leaf/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "start_charge": "mdi:flash", + "update": "mdi:update" + } +} From bac5a4d86707dd73fbb39ff6b371773416e9968d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:20:36 +0100 Subject: [PATCH 0982/1691] Add service icons to Netatmo (#113335) --- homeassistant/components/netatmo/icons.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 homeassistant/components/netatmo/icons.json diff --git a/homeassistant/components/netatmo/icons.json b/homeassistant/components/netatmo/icons.json new file mode 100644 index 00000000000..c585a9c7587 --- /dev/null +++ b/homeassistant/components/netatmo/icons.json @@ -0,0 +1,14 @@ +{ + "services": { + "set_camera_light": "mdi:led-on", + "set_schedule": "mdi:calendar-clock", + "set_preset_mode_with_end_datetime": "mdi:calendar-clock", + "set_temperature_with_end_datetime": "mdi:thermometer", + "set_temperature_with_time_period": "mdi:thermometer", + "clear_temperature_setting": "mdi:thermometer", + "set_persons_home": "mdi:home", + "set_person_away": "mdi:walk", + "register_webhook": "mdi:link-variant", + "unregister_webhook": "mdi:link-variant-off" + } +} From 6442d13a71e76646dd9d1e3b0ce7d7bbb6c32bd5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:25:27 +0100 Subject: [PATCH 0983/1691] Add service icons to Media Extractor (#113329) --- homeassistant/components/media_extractor/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/media_extractor/icons.json diff --git a/homeassistant/components/media_extractor/icons.json b/homeassistant/components/media_extractor/icons.json new file mode 100644 index 00000000000..71b65e7c4a6 --- /dev/null +++ b/homeassistant/components/media_extractor/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "play_media": "mdi:play" + } +} From e303a6dae9412805fa8f432315a5b4f2d0b54507 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:25:37 +0100 Subject: [PATCH 0984/1691] Add service icons to Microsoft Face (#113330) --- homeassistant/components/microsoft_face/icons.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 homeassistant/components/microsoft_face/icons.json diff --git a/homeassistant/components/microsoft_face/icons.json b/homeassistant/components/microsoft_face/icons.json new file mode 100644 index 00000000000..826e390197a --- /dev/null +++ b/homeassistant/components/microsoft_face/icons.json @@ -0,0 +1,10 @@ +{ + "services": { + "create_group": "mdi:account-multiple-plus", + "create_person": "mdi:account-plus", + "delete_group": "mdi:account-multiple-remove", + "delete_person": "mdi:account-remove", + "face_person": "mdi:face-man", + "train_group": "mdi:account-multiple-check" + } +} From 75b4cd2733e65f59e3d52efd22bbd6456b7f6169 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:25:45 +0100 Subject: [PATCH 0985/1691] Add service icons to Minio (#113331) --- homeassistant/components/minio/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/minio/icons.json diff --git a/homeassistant/components/minio/icons.json b/homeassistant/components/minio/icons.json new file mode 100644 index 00000000000..16deb1a168d --- /dev/null +++ b/homeassistant/components/minio/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "get": "mdi:cloud-download", + "put": "mdi:cloud-upload", + "remove": "mdi:delete" + } +} From bb97063c487c3ed2abd02581282477f3c36dd24c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:25:54 +0100 Subject: [PATCH 0986/1691] Add service icons to Ness alarm (#113333) --- homeassistant/components/ness_alarm/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/ness_alarm/icons.json diff --git a/homeassistant/components/ness_alarm/icons.json b/homeassistant/components/ness_alarm/icons.json new file mode 100644 index 00000000000..ea17fd2b299 --- /dev/null +++ b/homeassistant/components/ness_alarm/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "aux": "mdi:audio-input-stereo-minijack", + "panic": "mdi:fire" + } +} From 03e6e2577a74af54ace841405f7cb64d05b11fab Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:27:38 +0100 Subject: [PATCH 0987/1691] Add service icons to Matrix (#113328) * Add service icons to Matrix * Update homeassistant/components/matrix/icons.json --- homeassistant/components/matrix/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/matrix/icons.json diff --git a/homeassistant/components/matrix/icons.json b/homeassistant/components/matrix/icons.json new file mode 100644 index 00000000000..4fc56ebe0ff --- /dev/null +++ b/homeassistant/components/matrix/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "send_message": "mdi:matrix" + } +} From f9c1ed42a94bc2073bf126e4c9e255ceb89a6f77 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:29:10 +0100 Subject: [PATCH 0988/1691] Add service icons to Lovelace (#113327) --- homeassistant/components/lovelace/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/lovelace/icons.json diff --git a/homeassistant/components/lovelace/icons.json b/homeassistant/components/lovelace/icons.json new file mode 100644 index 00000000000..fe0a0e114ae --- /dev/null +++ b/homeassistant/components/lovelace/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload_resources": "mdi:reload" + } +} From c94fb8f660c8b6e3f02f6cce05e462120c3a4423 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:29:22 +0100 Subject: [PATCH 0989/1691] Add service icons to Logi circle (#113326) --- homeassistant/components/logi_circle/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/logi_circle/icons.json diff --git a/homeassistant/components/logi_circle/icons.json b/homeassistant/components/logi_circle/icons.json new file mode 100644 index 00000000000..9289746d375 --- /dev/null +++ b/homeassistant/components/logi_circle/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "set_config": "mdi:cog", + "livestream_snapshot": "mdi:camera", + "livestream_record": "mdi:record-rec" + } +} From 246ec0476f11fb9c1fd1b1798af89ccb1a62d5a5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:32:30 +0100 Subject: [PATCH 0990/1691] Add service icons to Local file (#113323) --- homeassistant/components/local_file/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/local_file/icons.json diff --git a/homeassistant/components/local_file/icons.json b/homeassistant/components/local_file/icons.json new file mode 100644 index 00000000000..c9c92fa86c8 --- /dev/null +++ b/homeassistant/components/local_file/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "update_file_path": "mdi:cog" + } +} From ca34fb3fbb9b8d9828824f74a0371b5fa775dedc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:33:01 +0100 Subject: [PATCH 0991/1691] Add service icons to Keyboard (#113322) --- homeassistant/components/keyboard/icons.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 homeassistant/components/keyboard/icons.json diff --git a/homeassistant/components/keyboard/icons.json b/homeassistant/components/keyboard/icons.json new file mode 100644 index 00000000000..8186b2684dd --- /dev/null +++ b/homeassistant/components/keyboard/icons.json @@ -0,0 +1,10 @@ +{ + "services": { + "volume_up": "mdi:volume-high", + "volume_down": "mdi:volume-low", + "volume_mute": "mdi:volume-off", + "media_play_pause": "mdi:play-pause", + "media_next_track": "mdi:skip-next", + "media_prev_track": "mdi:skip-previous" + } +} From 58f216fc89971f89612a2904535481152ac335b3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:33:53 +0100 Subject: [PATCH 0992/1691] Add service icons to Kef (#113321) --- homeassistant/components/kef/icons.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 homeassistant/components/kef/icons.json diff --git a/homeassistant/components/kef/icons.json b/homeassistant/components/kef/icons.json new file mode 100644 index 00000000000..eeb6dd099ce --- /dev/null +++ b/homeassistant/components/kef/icons.json @@ -0,0 +1,12 @@ +{ + "services": { + "update_dsp": "mdi:update", + "set_mode": "mdi:cog", + "set_desk_db": "mdi:volume-high", + "set_wall_db": "mdi:volume-high", + "set_treble_db": "mdi:volume-high", + "set_high_hz": "mdi:sine-wave", + "set_low_hz": "mdi:cosine-wave", + "set_sub_db": "mdi:volume-high" + } +} From 557e38915997f5cfb70719b0bc94b5807b6dfdad Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:34:42 +0100 Subject: [PATCH 0993/1691] Add service icons to Keba (#113320) --- homeassistant/components/keba/icons.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 homeassistant/components/keba/icons.json diff --git a/homeassistant/components/keba/icons.json b/homeassistant/components/keba/icons.json new file mode 100644 index 00000000000..7f64bf7fb34 --- /dev/null +++ b/homeassistant/components/keba/icons.json @@ -0,0 +1,12 @@ +{ + "services": { + "request_data": "mdi:database-arrow-down", + "authorize": "mdi:lock", + "deauthorize": "mdi:lock-open", + "set_energy": "mdi:flash", + "set_current": "mdi:flash", + "enable": "mdi:flash", + "disable": "mdi:fash-off", + "set_failsafe": "mdi:message-alert" + } +} From be36626910a9e5245122c4826233c1c9aa76f064 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:35:14 +0100 Subject: [PATCH 0994/1691] Add service icons to Iperf3 (#113319) --- homeassistant/components/iperf3/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/iperf3/icons.json diff --git a/homeassistant/components/iperf3/icons.json b/homeassistant/components/iperf3/icons.json new file mode 100644 index 00000000000..3ef7e301ed6 --- /dev/null +++ b/homeassistant/components/iperf3/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "speedtest": "mdi:speedometer" + } +} From d79a8393de10906dfbd32ddfaace609d3c655141 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:35:43 +0100 Subject: [PATCH 0995/1691] Add service icons to Intent Script (#113318) --- homeassistant/components/intent_script/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/intent_script/icons.json diff --git a/homeassistant/components/intent_script/icons.json b/homeassistant/components/intent_script/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/intent_script/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 645a108e782c4e01ca0848e722d90d39a7b38c9f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:36:03 +0100 Subject: [PATCH 0996/1691] Add service icons to Input Text (#113317) --- homeassistant/components/input_text/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/input_text/icons.json diff --git a/homeassistant/components/input_text/icons.json b/homeassistant/components/input_text/icons.json new file mode 100644 index 00000000000..0190e4ffba2 --- /dev/null +++ b/homeassistant/components/input_text/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_value": "mdi:form-textbox", + "reload": "mdi:reload" + } +} From b6906d963b758feeb53033b23f6ac3e60b438ba1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:36:30 +0100 Subject: [PATCH 0997/1691] Add service icons to Input Select (#113316) --- homeassistant/components/input_select/icons.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 homeassistant/components/input_select/icons.json diff --git a/homeassistant/components/input_select/icons.json b/homeassistant/components/input_select/icons.json new file mode 100644 index 00000000000..894b6be60dd --- /dev/null +++ b/homeassistant/components/input_select/icons.json @@ -0,0 +1,11 @@ +{ + "services": { + "select_next": "mdi:skip", + "select_option": "mdi:check", + "select_previous": "mdi:skip-previous", + "select_first": "mdi:skip-backward", + "select_last": "mdi:skip-forward", + "set_options": "mdi:cog", + "reload": "mdi:reload" + } +} From 5573dbc5a7d77ba03c6298667fa2d556bfea3703 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:38:47 +0100 Subject: [PATCH 0998/1691] Add service icons to Input button (#113313) --- homeassistant/components/input_button/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/input_button/icons.json diff --git a/homeassistant/components/input_button/icons.json b/homeassistant/components/input_button/icons.json new file mode 100644 index 00000000000..226b8ede110 --- /dev/null +++ b/homeassistant/components/input_button/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "press": "mdi:gesture-tap-button", + "reload": "mdi:reload" + } +} From 41be0487c6614b71bb0910b6adaa585b382aab0a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:38:56 +0100 Subject: [PATCH 0999/1691] Add service icons to Input Number (#113315) --- homeassistant/components/input_number/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/input_number/icons.json diff --git a/homeassistant/components/input_number/icons.json b/homeassistant/components/input_number/icons.json new file mode 100644 index 00000000000..d1423838491 --- /dev/null +++ b/homeassistant/components/input_number/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "decrement": "mdi:minus", + "increment": "mdi:plus", + "set_value": "mdi:numeric", + "reload": "mdi:reload" + } +} From 95f269ef709cfc5c1c7d7c3722a095fcc31d853f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:39:09 +0100 Subject: [PATCH 1000/1691] Add service icons to IHC (#113311) --- homeassistant/components/ihc/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/ihc/icons.json diff --git a/homeassistant/components/ihc/icons.json b/homeassistant/components/ihc/icons.json new file mode 100644 index 00000000000..73aab5f80d8 --- /dev/null +++ b/homeassistant/components/ihc/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "set_runtime_value_bool": "mdi:toggle-switch", + "set_runtime_value_int": "mdi:numeric", + "set_runtime_value_float": "mdi:numeric", + "pulse": "mdi:pulse" + } +} From 223e63b2d3fd287a05b592f4408971455141f554 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:39:41 +0100 Subject: [PATCH 1001/1691] Add service icons to HTML5 (#113309) --- homeassistant/components/html5/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/html5/icons.json diff --git a/homeassistant/components/html5/icons.json b/homeassistant/components/html5/icons.json new file mode 100644 index 00000000000..c3d6e27efda --- /dev/null +++ b/homeassistant/components/html5/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "dismiss": "mdi:bell-off" + } +} From 3326fe6357b529da8eb58ddfa5cd1297f28beac3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:40:24 +0100 Subject: [PATCH 1002/1691] Add service icons to Homematic (#113308) --- homeassistant/components/homematic/icons.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 homeassistant/components/homematic/icons.json diff --git a/homeassistant/components/homematic/icons.json b/homeassistant/components/homematic/icons.json new file mode 100644 index 00000000000..998c9a385ba --- /dev/null +++ b/homeassistant/components/homematic/icons.json @@ -0,0 +1,10 @@ +{ + "services": { + "virtualkey": "mdi:keyboard", + "set_variable_value": "mdi:console", + "set_device_value": "mdi:television", + "reconnect": "mdi:wifi-refresh", + "set_install_mode": "mdi:cog", + "put_paramset": "mdi:cog" + } +} From d88bebc255b95b33aa39ac94a01a20d2d7bd1796 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:43:17 +0100 Subject: [PATCH 1003/1691] Add service icons to Home Assistant (#113307) --- .../components/homeassistant/icons.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 homeassistant/components/homeassistant/icons.json diff --git a/homeassistant/components/homeassistant/icons.json b/homeassistant/components/homeassistant/icons.json new file mode 100644 index 00000000000..ec4d5729918 --- /dev/null +++ b/homeassistant/components/homeassistant/icons.json @@ -0,0 +1,17 @@ +{ + "services": { + "check_config": "mdi:receipt-text-check", + "reload_core_config": "mdi:receipt-text-send", + "restart": "mdi:restart", + "set_location": "mdi:map-marker", + "stop": "mdi:stop", + "toggle": "mdi:toggle-switch", + "turn_on": "mdi:power-on", + "turn_off": "mdi:power-off", + "update_entity": "mdi:update", + "reload_custom_templates": "mdi:palette-swatch", + "reload_config_entry": "mdi:reload", + "save_persistent_states": "mdi:content-save", + "reload_all": "mdi:reload" + } +} From 46ec25b2ef82c572a14a268b6ca19e06f3360b61 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:44:52 +0100 Subject: [PATCH 1004/1691] Add service icons to Habitica (#113303) --- homeassistant/components/habitica/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/habitica/icons.json diff --git a/homeassistant/components/habitica/icons.json b/homeassistant/components/habitica/icons.json new file mode 100644 index 00000000000..4e5831c4e82 --- /dev/null +++ b/homeassistant/components/habitica/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "api_call": "mdi:console" + } +} From 4d98267cd9e4ebcf86b5318d369facd16fc7b384 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:45:03 +0100 Subject: [PATCH 1005/1691] Add service icons to Geniushub (#113301) --- homeassistant/components/geniushub/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/geniushub/icons.json diff --git a/homeassistant/components/geniushub/icons.json b/homeassistant/components/geniushub/icons.json new file mode 100644 index 00000000000..41697b419a8 --- /dev/null +++ b/homeassistant/components/geniushub/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "set_zone_mode": "mdi:auto-mode", + "set_zone_override": "mdi:thermometer-lines", + "set_switch_override": "mdi:toggle-switch-variant" + } +} From 8fa08c2fd8f75c7f7fdbaad85ba1addd4fbfd938 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:46:00 +0100 Subject: [PATCH 1006/1691] Add service icons to Generic Thermostat (#113300) --- homeassistant/components/generic_thermostat/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/generic_thermostat/icons.json diff --git a/homeassistant/components/generic_thermostat/icons.json b/homeassistant/components/generic_thermostat/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/generic_thermostat/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From dcd56e4ba7c8ab239d07dcf4b9e4f7c8a887c682 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:46:13 +0100 Subject: [PATCH 1007/1691] Add service icons to Frontend (#113299) --- homeassistant/components/frontend/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/frontend/icons.json diff --git a/homeassistant/components/frontend/icons.json b/homeassistant/components/frontend/icons.json new file mode 100644 index 00000000000..9fbe4d5b9b0 --- /dev/null +++ b/homeassistant/components/frontend/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_theme": "mdi:palette-swatch", + "reload_themes": "mdi:reload" + } +} From 0eda6e86c58dda4108f253154a67a373f0129ccc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:46:33 +0100 Subject: [PATCH 1008/1691] Add service icons to Freebox (#113298) --- homeassistant/components/freebox/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/freebox/icons.json diff --git a/homeassistant/components/freebox/icons.json b/homeassistant/components/freebox/icons.json new file mode 100644 index 00000000000..81361d2c990 --- /dev/null +++ b/homeassistant/components/freebox/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reboot": "mdi:restart" + } +} From 5e0465343197b3d56abdef2417cd20d12fab5ce9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:47:11 +0100 Subject: [PATCH 1009/1691] Add service icons to Ffmpeg (#113294) --- homeassistant/components/ffmpeg/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/ffmpeg/icons.json diff --git a/homeassistant/components/ffmpeg/icons.json b/homeassistant/components/ffmpeg/icons.json new file mode 100644 index 00000000000..3017b7dc0da --- /dev/null +++ b/homeassistant/components/ffmpeg/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "restart": "mdi:restart", + "start": "mdi:start", + "stop": "mdi:stop" + } +} From ed91ccbfd483ea68ba48f37681f4020f530fb399 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:48:18 +0100 Subject: [PATCH 1010/1691] Add service icons to Evohome (#113293) --- homeassistant/components/evohome/icons.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 homeassistant/components/evohome/icons.json diff --git a/homeassistant/components/evohome/icons.json b/homeassistant/components/evohome/icons.json new file mode 100644 index 00000000000..b396b226c25 --- /dev/null +++ b/homeassistant/components/evohome/icons.json @@ -0,0 +1,9 @@ +{ + "services": { + "set_system_mode": "mdi:pencil", + "reset_system": "mdi:reset", + "refresh_system": "mdi:refresh", + "set_zone_override": "mdi:motion-sensor", + "clear_zone_override": "mdi:motion-sensor-off" + } +} From 423a4dfc33fbb447164d4ce0b67031104c7bd408 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:48:31 +0100 Subject: [PATCH 1011/1691] Add service icons to Foursquare (#113296) --- homeassistant/components/foursquare/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/foursquare/icons.json diff --git a/homeassistant/components/foursquare/icons.json b/homeassistant/components/foursquare/icons.json new file mode 100644 index 00000000000..cf60ed9f247 --- /dev/null +++ b/homeassistant/components/foursquare/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "checkin": "mdi:map-marker" + } +} From 141bdf33df4b5d226646856853c1c3cd486e3020 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:48:58 +0100 Subject: [PATCH 1012/1691] Add service icons to Envisalink (#113292) --- homeassistant/components/envisalink/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/envisalink/icons.json diff --git a/homeassistant/components/envisalink/icons.json b/homeassistant/components/envisalink/icons.json new file mode 100644 index 00000000000..55084ede935 --- /dev/null +++ b/homeassistant/components/envisalink/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "alarm_keypress": "mdi:alarm-panel", + "invoke_custom_function": "mdi:terminal" + } +} From 064491b91506da99d2034837846259e0653eb8e9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:49:18 +0100 Subject: [PATCH 1013/1691] Add service icons to HDMI CEC (#113304) * Add service icons to HDMI CEC * Update homeassistant/components/hdmi_cec/icons.json Co-authored-by: Jan Bouwhuis --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/hdmi_cec/icons.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 homeassistant/components/hdmi_cec/icons.json diff --git a/homeassistant/components/hdmi_cec/icons.json b/homeassistant/components/hdmi_cec/icons.json new file mode 100644 index 00000000000..0bfcb98eea2 --- /dev/null +++ b/homeassistant/components/hdmi_cec/icons.json @@ -0,0 +1,10 @@ +{ + "services": { + "power_on": "mdi:power", + "select_device": "mdi:television", + "send_command": "mdi:console", + "standby": "mdi:power-standby", + "update": "mdi:update", + "volume": "mdi:volume-high" + } +} From 02a07a3d15ab70d4bcac44eec34d4430192f97a7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:51:24 +0100 Subject: [PATCH 1014/1691] Add service icons to Ebusd (#113291) --- homeassistant/components/ebusd/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/ebusd/icons.json diff --git a/homeassistant/components/ebusd/icons.json b/homeassistant/components/ebusd/icons.json new file mode 100644 index 00000000000..642be37a43b --- /dev/null +++ b/homeassistant/components/ebusd/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "write": "mdi:pencil" + } +} From 40566c3b682b97cc72ea0b04ca15eb464476a24e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:51:59 +0100 Subject: [PATCH 1015/1691] Add service icons to Downloader (#113289) --- homeassistant/components/downloader/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/downloader/icons.json diff --git a/homeassistant/components/downloader/icons.json b/homeassistant/components/downloader/icons.json new file mode 100644 index 00000000000..2a78df93ca7 --- /dev/null +++ b/homeassistant/components/downloader/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "download_file": "mdi:download" + } +} From c1b05ce93d6cfecb4577af0509b93ace7600e3b8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:52:10 +0100 Subject: [PATCH 1016/1691] Add service icons to Dominos (#113288) --- homeassistant/components/dominos/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/dominos/icons.json diff --git a/homeassistant/components/dominos/icons.json b/homeassistant/components/dominos/icons.json new file mode 100644 index 00000000000..d88bfb2542f --- /dev/null +++ b/homeassistant/components/dominos/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "order": "mdi:pizza" + } +} From a3a1647256474398209f31505d00099ee7f355c1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:52:27 +0100 Subject: [PATCH 1017/1691] Add service icons to Logbook (#113324) --- homeassistant/components/logbook/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/logbook/icons.json diff --git a/homeassistant/components/logbook/icons.json b/homeassistant/components/logbook/icons.json new file mode 100644 index 00000000000..cd2cde8600c --- /dev/null +++ b/homeassistant/components/logbook/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "log": "mdi:file-document" + } +} From 43d4f10582809ed4206e4692bd9323991dea9b59 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:52:45 +0100 Subject: [PATCH 1018/1691] Add service icons to Logger (#113325) * Add service icons to Logger * Update homeassistant/components/logger/icons.json Co-authored-by: Jan Bouwhuis --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/logger/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/logger/icons.json diff --git a/homeassistant/components/logger/icons.json b/homeassistant/components/logger/icons.json new file mode 100644 index 00000000000..305dd3ece91 --- /dev/null +++ b/homeassistant/components/logger/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_default_level": "mdi:cog-outline", + "set_level": "mdi:cog-outline" + } +} From 85473b80ca0f578513f06bffdd261e3065554245 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:52:59 +0100 Subject: [PATCH 1019/1691] Add service icons to Input Datetime (#113314) --- homeassistant/components/input_datetime/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/input_datetime/icons.json diff --git a/homeassistant/components/input_datetime/icons.json b/homeassistant/components/input_datetime/icons.json new file mode 100644 index 00000000000..de899023cf2 --- /dev/null +++ b/homeassistant/components/input_datetime/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "set_datetime": "mdi:calendar-clock", + "reload": "mdi:reload" + } +} From 500dd9330a42514bab88f9fd1cb75f2f46c2c962 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:53:14 +0100 Subject: [PATCH 1020/1691] Add service icons to Conversation (#113286) --- homeassistant/components/conversation/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/conversation/icons.json diff --git a/homeassistant/components/conversation/icons.json b/homeassistant/components/conversation/icons.json new file mode 100644 index 00000000000..b39a1603b15 --- /dev/null +++ b/homeassistant/components/conversation/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "process": "mdi:message-processing", + "reload": "mdi:reload" + } +} From 816d984cac50d661b50c20e52f5b1d2dd856c404 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:53:35 +0100 Subject: [PATCH 1021/1691] Add service icons to DuckDNS (#113290) * Add service icons to DuckDNS * Update homeassistant/components/duckdns/icons.json Co-authored-by: Jan Bouwhuis --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/duckdns/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/duckdns/icons.json diff --git a/homeassistant/components/duckdns/icons.json b/homeassistant/components/duckdns/icons.json new file mode 100644 index 00000000000..79ec18d13ff --- /dev/null +++ b/homeassistant/components/duckdns/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_txt": "mdi:text-box-edit-outline" + } +} From 1fd447f65b07cc300ea323c92ecd2e39c1c9a77e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:54:45 +0100 Subject: [PATCH 1022/1691] Add service icons to Agent DVR (#113273) --- homeassistant/components/agent_dvr/icons.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 homeassistant/components/agent_dvr/icons.json diff --git a/homeassistant/components/agent_dvr/icons.json b/homeassistant/components/agent_dvr/icons.json new file mode 100644 index 00000000000..6550d01641e --- /dev/null +++ b/homeassistant/components/agent_dvr/icons.json @@ -0,0 +1,9 @@ +{ + "services": { + "start_recording": "mdi:record-rec", + "stop_recording": "mdi:stop", + "enable_alerts": "mdi:bell-alert", + "disable_alerts": "mdi:bell-off", + "snapshot": "mdi:camera" + } +} From 6a1913b372cca418a4b582ff33de01b9b5cd4f32 Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Thu, 14 Mar 2024 13:55:16 +0100 Subject: [PATCH 1023/1691] Bump tololib to 1.1.0 (#113268) * upgrade tololib dependency to v1.0.0 * use latest available patch version * fixed tolo tests * fixed test cases --- homeassistant/components/tolo/__init__.py | 24 +++++++++----------- homeassistant/components/tolo/button.py | 2 +- homeassistant/components/tolo/climate.py | 24 ++++++++++---------- homeassistant/components/tolo/config_flow.py | 11 ++++----- homeassistant/components/tolo/const.py | 10 -------- homeassistant/components/tolo/manifest.json | 2 +- homeassistant/components/tolo/number.py | 13 +++++++---- homeassistant/components/tolo/select.py | 2 +- homeassistant/components/tolo/sensor.py | 6 ++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tolo/test_config_flow.py | 12 +++++----- 12 files changed, 50 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/tolo/__init__.py b/homeassistant/components/tolo/__init__.py index 2fc41fac3af..165e5804d61 100644 --- a/homeassistant/components/tolo/__init__.py +++ b/homeassistant/components/tolo/__init__.py @@ -6,9 +6,7 @@ from datetime import timedelta import logging from typing import NamedTuple -from tololib import ToloClient -from tololib.errors import ResponseTimedOutError -from tololib.message_info import SettingsInfo, StatusInfo +from tololib import ToloClient, ToloSettings, ToloStatus from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform @@ -59,8 +57,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class ToloSaunaData(NamedTuple): """Compound class for reflecting full state (status and info) of a TOLO Sauna.""" - status: StatusInfo - settings: SettingsInfo + status: ToloStatus + settings: ToloSettings class ToloSaunaUpdateCoordinator(DataUpdateCoordinator[ToloSaunaData]): # pylint: disable=hass-enforce-coordinator-module @@ -68,7 +66,11 @@ class ToloSaunaUpdateCoordinator(DataUpdateCoordinator[ToloSaunaData]): # pylin def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize ToloSaunaUpdateCoordinator.""" - self.client = ToloClient(entry.data[CONF_HOST]) + self.client = ToloClient( + address=entry.data[CONF_HOST], + retry_timeout=DEFAULT_RETRY_TIMEOUT, + retry_count=DEFAULT_RETRY_COUNT, + ) super().__init__( hass=hass, logger=_LOGGER, @@ -81,13 +83,9 @@ class ToloSaunaUpdateCoordinator(DataUpdateCoordinator[ToloSaunaData]): # pylin def _get_tolo_sauna_data(self) -> ToloSaunaData: try: - status = self.client.get_status_info( - resend_timeout=DEFAULT_RETRY_TIMEOUT, retries=DEFAULT_RETRY_COUNT - ) - settings = self.client.get_settings_info( - resend_timeout=DEFAULT_RETRY_TIMEOUT, retries=DEFAULT_RETRY_COUNT - ) - except ResponseTimedOutError as error: + status = self.client.get_status() + settings = self.client.get_settings() + except TimeoutError as error: raise UpdateFailed("communication timeout") from error return ToloSaunaData(status, settings) diff --git a/homeassistant/components/tolo/button.py b/homeassistant/components/tolo/button.py index 82daa79d1c1..9a8ac67b9fe 100644 --- a/homeassistant/components/tolo/button.py +++ b/homeassistant/components/tolo/button.py @@ -1,6 +1,6 @@ """TOLO Sauna Button controls.""" -from tololib.const import LampMode +from tololib import LampMode from homeassistant.components.button import ButtonEntity from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/tolo/climate.py b/homeassistant/components/tolo/climate.py index bbe7bf57202..2994d97d54a 100644 --- a/homeassistant/components/tolo/climate.py +++ b/homeassistant/components/tolo/climate.py @@ -4,7 +4,13 @@ from __future__ import annotations from typing import Any -from tololib.const import Calefaction +from tololib import ( + TARGET_HUMIDITY_MAX, + TARGET_HUMIDITY_MIN, + TARGET_TEMPERATURE_MAX, + TARGET_TEMPERATURE_MIN, + Calefaction, +) from homeassistant.components.climate import ( FAN_OFF, @@ -20,13 +26,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator -from .const import ( - DEFAULT_MAX_HUMIDITY, - DEFAULT_MAX_TEMP, - DEFAULT_MIN_HUMIDITY, - DEFAULT_MIN_TEMP, - DOMAIN, -) +from .const import DOMAIN async def async_setup_entry( @@ -44,10 +44,10 @@ class SaunaClimate(ToloSaunaCoordinatorEntity, ClimateEntity): _attr_fan_modes = [FAN_ON, FAN_OFF] _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT, HVACMode.DRY] - _attr_max_humidity = DEFAULT_MAX_HUMIDITY - _attr_max_temp = DEFAULT_MAX_TEMP - _attr_min_humidity = DEFAULT_MIN_HUMIDITY - _attr_min_temp = DEFAULT_MIN_TEMP + _attr_max_humidity = TARGET_HUMIDITY_MAX + _attr_max_temp = TARGET_TEMPERATURE_MAX + _attr_min_humidity = TARGET_HUMIDITY_MIN + _attr_min_temp = TARGET_TEMPERATURE_MIN _attr_name = None _attr_precision = PRECISION_WHOLE _attr_supported_features = ( diff --git a/homeassistant/components/tolo/config_flow.py b/homeassistant/components/tolo/config_flow.py index ed15030b372..5cf91bdc3a8 100644 --- a/homeassistant/components/tolo/config_flow.py +++ b/homeassistant/components/tolo/config_flow.py @@ -5,8 +5,7 @@ from __future__ import annotations import logging from typing import Any -from tololib import ToloClient -from tololib.errors import ResponseTimedOutError +from tololib import ToloClient, ToloCommunicationError import voluptuous as vol from homeassistant.components import dhcp @@ -14,7 +13,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST from homeassistant.helpers.device_registry import format_mac -from .const import DEFAULT_NAME, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_TIMEOUT, DOMAIN +from .const import DEFAULT_NAME, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -30,10 +29,8 @@ class ToloSaunaConfigFlow(ConfigFlow, domain=DOMAIN): def _check_device_availability(host: str) -> bool: client = ToloClient(host) try: - result = client.get_status_info( - resend_timeout=DEFAULT_RETRY_TIMEOUT, retries=DEFAULT_RETRY_COUNT - ) - except ResponseTimedOutError: + result = client.get_status() + except ToloCommunicationError: return False return result is not None diff --git a/homeassistant/components/tolo/const.py b/homeassistant/components/tolo/const.py index 77bee92d521..16fcbd04bb7 100644 --- a/homeassistant/components/tolo/const.py +++ b/homeassistant/components/tolo/const.py @@ -5,13 +5,3 @@ DEFAULT_NAME = "TOLO Sauna" DEFAULT_RETRY_TIMEOUT = 1 DEFAULT_RETRY_COUNT = 3 - -DEFAULT_MAX_TEMP = 60 -DEFAULT_MIN_TEMP = 20 - -DEFAULT_MAX_HUMIDITY = 99 -DEFAULT_MIN_HUMIDITY = 60 - -POWER_TIMER_MAX = 60 -SALT_BATH_TIMER_MAX = 60 -FAN_TIMER_MAX = 60 diff --git a/homeassistant/components/tolo/manifest.json b/homeassistant/components/tolo/manifest.json index 57a63e55cf3..14125a857f6 100644 --- a/homeassistant/components/tolo/manifest.json +++ b/homeassistant/components/tolo/manifest.json @@ -11,5 +11,5 @@ "documentation": "https://www.home-assistant.io/integrations/tolo", "iot_class": "local_polling", "loggers": ["tololib"], - "requirements": ["tololib==0.1.0b4"] + "requirements": ["tololib==1.1.0"] } diff --git a/homeassistant/components/tolo/number.py b/homeassistant/components/tolo/number.py index 6a9783aacd4..2d2c20715fa 100644 --- a/homeassistant/components/tolo/number.py +++ b/homeassistant/components/tolo/number.py @@ -6,8 +6,13 @@ from collections.abc import Callable from dataclasses import dataclass from typing import Any -from tololib import ToloClient -from tololib.message_info import SettingsInfo +from tololib import ( + FAN_TIMER_MAX, + POWER_TIMER_MAX, + SALT_BATH_TIMER_MAX, + ToloClient, + ToloSettings, +) from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.config_entries import ConfigEntry @@ -16,14 +21,14 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator -from .const import DOMAIN, FAN_TIMER_MAX, POWER_TIMER_MAX, SALT_BATH_TIMER_MAX +from .const import DOMAIN @dataclass(frozen=True, kw_only=True) class ToloNumberEntityDescription(NumberEntityDescription): """Class describing TOLO Number entities.""" - getter: Callable[[SettingsInfo], int | None] + getter: Callable[[ToloSettings], int | None] setter: Callable[[ToloClient, int | None], Any] entity_category = EntityCategory.CONFIG diff --git a/homeassistant/components/tolo/select.py b/homeassistant/components/tolo/select.py index 26480aa3527..8d574f9dff5 100644 --- a/homeassistant/components/tolo/select.py +++ b/homeassistant/components/tolo/select.py @@ -2,7 +2,7 @@ from __future__ import annotations -from tololib.const import LampMode +from tololib import LampMode from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry diff --git a/homeassistant/components/tolo/sensor.py b/homeassistant/components/tolo/sensor.py index 27aa331c9cf..bee01cc283f 100644 --- a/homeassistant/components/tolo/sensor.py +++ b/homeassistant/components/tolo/sensor.py @@ -5,7 +5,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from tololib.message_info import SettingsInfo, StatusInfo +from tololib import ToloSettings, ToloStatus from homeassistant.components.sensor import ( SensorDeviceClass, @@ -31,8 +31,8 @@ from .const import DOMAIN class ToloSensorEntityDescription(SensorEntityDescription): """Class describing TOLO Sensor entities.""" - getter: Callable[[StatusInfo], int | None] - availability_checker: Callable[[SettingsInfo, StatusInfo], bool] | None + getter: Callable[[ToloStatus], int | None] + availability_checker: Callable[[ToloSettings, ToloStatus], bool] | None state_class = SensorStateClass.MEASUREMENT diff --git a/requirements_all.txt b/requirements_all.txt index 79dfd705988..3c4faaad46c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2716,7 +2716,7 @@ tmb==0.0.4 todoist-api-python==2.1.2 # homeassistant.components.tolo -tololib==0.1.0b4 +tololib==1.1.0 # homeassistant.components.toon toonapi==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9650ff49963..ada8b08ee1f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2075,7 +2075,7 @@ tilt-ble==0.2.3 todoist-api-python==2.1.2 # homeassistant.components.tolo -tololib==0.1.0b4 +tololib==1.1.0 # homeassistant.components.toon toonapi==0.3.0 diff --git a/tests/components/tolo/test_config_flow.py b/tests/components/tolo/test_config_flow.py index ff704bcd5cd..711ded3880b 100644 --- a/tests/components/tolo/test_config_flow.py +++ b/tests/components/tolo/test_config_flow.py @@ -3,7 +3,7 @@ from unittest.mock import Mock, patch import pytest -from tololib.errors import ResponseTimedOutError +from tololib import ToloCommunicationError from homeassistant.components import dhcp from homeassistant.components.tolo.const import DOMAIN @@ -38,7 +38,7 @@ def coordinator_toloclient() -> Mock: async def test_user_with_timed_out_host(hass: HomeAssistant, toloclient: Mock) -> None: """Test a user initiated config flow with provided host which times out.""" - toloclient().get_status_info.side_effect = ResponseTimedOutError() + toloclient().get_status.side_effect = ToloCommunicationError result = await hass.config_entries.flow.async_init( DOMAIN, @@ -62,7 +62,7 @@ async def test_user_walkthrough( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - toloclient().get_status_info.side_effect = lambda *args, **kwargs: None + toloclient().get_status.side_effect = lambda *args, **kwargs: None result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -73,7 +73,7 @@ async def test_user_walkthrough( assert result2["step_id"] == "user" assert result2["errors"] == {"base": "cannot_connect"} - toloclient().get_status_info.side_effect = lambda *args, **kwargs: object() + toloclient().get_status.side_effect = lambda *args, **kwargs: object() result3 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -89,7 +89,7 @@ async def test_dhcp( hass: HomeAssistant, toloclient: Mock, coordinator_toloclient: Mock ) -> None: """Test starting a flow from discovery.""" - toloclient().get_status_info.side_effect = lambda *args, **kwargs: object() + toloclient().get_status.side_effect = lambda *args, **kwargs: object() result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_DHCP}, data=MOCK_DHCP_DATA @@ -110,7 +110,7 @@ async def test_dhcp( async def test_dhcp_invalid_device(hass: HomeAssistant, toloclient: Mock) -> None: """Test starting a flow from discovery.""" - toloclient().get_status_info.side_effect = lambda *args, **kwargs: None + toloclient().get_status.side_effect = lambda *args, **kwargs: None result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_DHCP}, data=MOCK_DHCP_DATA From 645e780445fadedaf95fd2b64e25193b3583916a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:55:47 +0100 Subject: [PATCH 1024/1691] Add service icons to Advantage air (#113272) --- homeassistant/components/advantage_air/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/advantage_air/icons.json diff --git a/homeassistant/components/advantage_air/icons.json b/homeassistant/components/advantage_air/icons.json new file mode 100644 index 00000000000..a4168f440cf --- /dev/null +++ b/homeassistant/components/advantage_air/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_time_to": "mdi:timer-cog" + } +} From 09be817f768f52cd5ca4fcb869bf15730b23a15d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 13:56:02 +0100 Subject: [PATCH 1025/1691] Add service icons to Channels (#113284) * Add service icons to Channels * Update homeassistant/components/channels/icons.json Co-authored-by: Jan Bouwhuis --------- Co-authored-by: Jan Bouwhuis --- homeassistant/components/channels/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/channels/icons.json diff --git a/homeassistant/components/channels/icons.json b/homeassistant/components/channels/icons.json new file mode 100644 index 00000000000..cbbda1ef623 --- /dev/null +++ b/homeassistant/components/channels/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "seek_forward": "mdi:skip-forward", + "seek_backward": "mdi:skip-backward", + "seek_by": "mdi:timer-check-outline" + } +} From 20c2bac32e5fbce5b12f8c6d99af2c97eba1e3b9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:00:50 +0100 Subject: [PATCH 1026/1691] Add service icons to Modbus (#113332) --- homeassistant/components/modbus/icons.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 homeassistant/components/modbus/icons.json diff --git a/homeassistant/components/modbus/icons.json b/homeassistant/components/modbus/icons.json new file mode 100644 index 00000000000..eeaeff6403b --- /dev/null +++ b/homeassistant/components/modbus/icons.json @@ -0,0 +1,9 @@ +{ + "services": { + "reload": "mdi:reload", + "write_coil": "mdi:pencil", + "write_register": "mdi:database-edit", + "stop": "mdi:stop", + "restart": "mdi:restart" + } +} From d10a244eaa937cfad17638c850c879a2abd35d08 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:00:57 +0100 Subject: [PATCH 1027/1691] Add service icons to Backup (#113277) --- homeassistant/components/backup/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/backup/icons.json diff --git a/homeassistant/components/backup/icons.json b/homeassistant/components/backup/icons.json new file mode 100644 index 00000000000..cba4fb22831 --- /dev/null +++ b/homeassistant/components/backup/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "create": "mdi:cloud-upload" + } +} From 265ecc141d9936520fad1d280a67416da94324a9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:01:04 +0100 Subject: [PATCH 1028/1691] Add service icons to Bayesian (#113278) --- homeassistant/components/bayesian/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/bayesian/icons.json diff --git a/homeassistant/components/bayesian/icons.json b/homeassistant/components/bayesian/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/bayesian/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 467a6f3ed038fae4687fece8f4cc712cf28e5a43 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:01:13 +0100 Subject: [PATCH 1029/1691] Add service icons to Bluetooth Tracker (#113282) --- homeassistant/components/bluetooth_tracker/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/bluetooth_tracker/icons.json diff --git a/homeassistant/components/bluetooth_tracker/icons.json b/homeassistant/components/bluetooth_tracker/icons.json new file mode 100644 index 00000000000..650bf0b6d19 --- /dev/null +++ b/homeassistant/components/bluetooth_tracker/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "update": "mdi:update" + } +} From d81053a69548a67322fb8f96a88c2341ee3f35f2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:01:20 +0100 Subject: [PATCH 1030/1691] Add service icons to History Stats (#113306) --- homeassistant/components/history_stats/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/history_stats/icons.json diff --git a/homeassistant/components/history_stats/icons.json b/homeassistant/components/history_stats/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/history_stats/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 774b2800bff632d9f24a25bfdbf369590f64fc0b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:01:30 +0100 Subject: [PATCH 1031/1691] Add service icons to Group (#113302) --- homeassistant/components/group/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/group/icons.json diff --git a/homeassistant/components/group/icons.json b/homeassistant/components/group/icons.json new file mode 100644 index 00000000000..8cca94e08e1 --- /dev/null +++ b/homeassistant/components/group/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "reload": "mdi:reload", + "set": "mdi:home-group-plus", + "remove": "mdi:home-group-remove" + } +} From 1191032b081390c5034a527a135981e17511a681 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:01:41 +0100 Subject: [PATCH 1032/1691] Add service icons to REST (#113346) --- homeassistant/components/rest/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/rest/icons.json diff --git a/homeassistant/components/rest/icons.json b/homeassistant/components/rest/icons.json new file mode 100644 index 00000000000..a03163179cb --- /dev/null +++ b/homeassistant/components/rest/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "reload": "mdi:reload" + } +} From 21a2871014fe0d24c08d31daaf4d1396134bdb99 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:01:51 +0100 Subject: [PATCH 1033/1691] Add service icons to Amcrest (#113276) --- homeassistant/components/amcrest/icons.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 homeassistant/components/amcrest/icons.json diff --git a/homeassistant/components/amcrest/icons.json b/homeassistant/components/amcrest/icons.json new file mode 100644 index 00000000000..efba49d6b56 --- /dev/null +++ b/homeassistant/components/amcrest/icons.json @@ -0,0 +1,15 @@ +{ + "services": { + "enable_recording": "mdi:record-rec", + "disable_recording": "mdi:stop", + "enable_audio": "mdi:volume-high", + "disable_audio": "mdi:volume-off", + "enable_motion_recording": "mdi:motion-sensor", + "disable_motion_recording": "mdi:motion-sensor-off", + "goto_preset": "mdi:pan", + "set_color_bw": "mdi:palette", + "start_tour": "mdi:panorama", + "stop_tour": "mdi:panorama-outline", + "ptz_control": "mdi:pan" + } +} From 020b75d5de4a72fe917d8f9c49e616d545effbdb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:02:01 +0100 Subject: [PATCH 1034/1691] Add service icons to Blackbird (#113280) --- homeassistant/components/blackbird/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/blackbird/icons.json diff --git a/homeassistant/components/blackbird/icons.json b/homeassistant/components/blackbird/icons.json new file mode 100644 index 00000000000..f080fb5f857 --- /dev/null +++ b/homeassistant/components/blackbird/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "set_all_zones": "mdi:home-sound-in" + } +} From fef2d7ddd4b56babdaded10ec0810804b6db62a9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:02:15 +0100 Subject: [PATCH 1035/1691] Add service icons to Persistent Notification (#113340) --- .../components/persistent_notification/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 homeassistant/components/persistent_notification/icons.json diff --git a/homeassistant/components/persistent_notification/icons.json b/homeassistant/components/persistent_notification/icons.json new file mode 100644 index 00000000000..9c782bd7b21 --- /dev/null +++ b/homeassistant/components/persistent_notification/icons.json @@ -0,0 +1,7 @@ +{ + "services": { + "create": "mdi:message-badge", + "dismiss": "mdi:bell-off", + "dismiss_all": "mdi:notification-clear-all" + } +} From a16ea3d7bdd57bbd0fe3402ac4f2999c451dcc7b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 14 Mar 2024 14:04:41 +0100 Subject: [PATCH 1036/1691] Deprecate the map integration (#113215) * Deprecate the map integration * Revert changes in DashboardsCollection._async_load_data * Add option to allow single word in dashboard URL * Update tests * Translate title * Add icon * Improve test coverage --- .coveragerc | 1 - homeassistant/components/lovelace/__init__.py | 35 +++++- homeassistant/components/lovelace/const.py | 3 + .../components/lovelace/dashboard.py | 9 +- .../components/lovelace/manifest.json | 1 + homeassistant/components/map/__init__.py | 46 ++++++- homeassistant/components/map/manifest.json | 2 +- .../components/onboarding/__init__.py | 53 ++++++-- .../components/onboarding/strings.json | 3 + homeassistant/components/onboarding/views.py | 15 ++- script/hassfest/translations.py | 5 +- tests/components/lovelace/test_cast.py | 16 ++- tests/components/lovelace/test_dashboard.py | 18 ++- tests/components/lovelace/test_init.py | 98 +++++++++++++++ .../components/lovelace/test_system_health.py | 18 ++- tests/components/map/__init__.py | 1 + tests/components/map/test_init.py | 116 ++++++++++++++++++ tests/components/onboarding/test_init.py | 13 +- tests/components/onboarding/test_views.py | 63 +++++++++- 19 files changed, 480 insertions(+), 36 deletions(-) create mode 100644 tests/components/lovelace/test_init.py create mode 100644 tests/components/map/__init__.py create mode 100644 tests/components/map/test_init.py diff --git a/.coveragerc b/.coveragerc index a60f216af1b..54d543dfbf3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -747,7 +747,6 @@ omit = homeassistant/components/lyric/climate.py homeassistant/components/lyric/sensor.py homeassistant/components/mailgun/notify.py - homeassistant/components/map/* homeassistant/components/mastodon/notify.py homeassistant/components/matrix/__init__.py homeassistant/components/matrix/notify.py diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 73be119880f..60d03717be0 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components import frontend, websocket_api +from homeassistant.components import frontend, onboarding, websocket_api from homeassistant.config import ( async_hass_config_yaml, async_process_component_and_handle_errors, @@ -14,11 +14,13 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import collection, config_validation as cv from homeassistant.helpers.service import async_register_admin_service +from homeassistant.helpers.translation import async_get_translations from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_integration from . import dashboard, resources, websocket from .const import ( # noqa: F401 + CONF_ALLOW_SINGLE_WORD, CONF_ICON, CONF_REQUIRE_ADMIN, CONF_SHOW_IN_SIDEBAR, @@ -201,6 +203,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Process storage dashboards dashboards_collection = dashboard.DashboardsCollection(hass) + # This can be removed when the map integration is removed + hass.data[DOMAIN]["dashboards_collection"] = dashboards_collection + dashboards_collection.async_add_listener(storage_dashboard_changed) await dashboards_collection.async_load() @@ -212,6 +217,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: STORAGE_DASHBOARD_UPDATE_FIELDS, ).async_setup(hass, create_list=False) + def create_map_dashboard(): + hass.async_create_task(_create_map_dashboard(hass)) + + if not onboarding.async_is_onboarded(hass): + onboarding.async_add_listener(hass, create_map_dashboard) + return True @@ -249,3 +260,25 @@ def _register_panel(hass, url_path, mode, config, update): kwargs["sidebar_icon"] = config.get(CONF_ICON, DEFAULT_ICON) frontend.async_register_built_in_panel(hass, DOMAIN, **kwargs) + + +async def _create_map_dashboard(hass: HomeAssistant): + translations = await async_get_translations( + hass, hass.config.language, "dashboard", {onboarding.DOMAIN} + ) + title = translations["component.onboarding.dashboard.map.title"] + + dashboards_collection: dashboard.DashboardsCollection = hass.data[DOMAIN][ + "dashboards_collection" + ] + await dashboards_collection.async_create_item( + { + CONF_ALLOW_SINGLE_WORD: True, + CONF_ICON: "mdi:map", + CONF_TITLE: title, + CONF_URL_PATH: "map", + } + ) + + map_store: dashboard.LovelaceStorage = hass.data[DOMAIN]["dashboards"]["map"] + await map_store.async_save({"strategy": {"type": "map"}}) diff --git a/homeassistant/components/lovelace/const.py b/homeassistant/components/lovelace/const.py index 0f1e818e8bd..538bd49d72c 100644 --- a/homeassistant/components/lovelace/const.py +++ b/homeassistant/components/lovelace/const.py @@ -24,6 +24,7 @@ MODE_STORAGE = "storage" MODE_AUTO = "auto-gen" LOVELACE_CONFIG_FILE = "ui-lovelace.yaml" +CONF_ALLOW_SINGLE_WORD = "allow_single_word" CONF_URL_PATH = "url_path" CONF_RESOURCE_TYPE_WS = "res_type" @@ -75,6 +76,8 @@ STORAGE_DASHBOARD_CREATE_FIELDS = { # For now we write "storage" as all modes. # In future we can adjust this to be other modes. vol.Optional(CONF_MODE, default=MODE_STORAGE): MODE_STORAGE, + # Set to allow adding dashboard without hyphen + vol.Optional(CONF_ALLOW_SINGLE_WORD): bool, } STORAGE_DASHBOARD_UPDATE_FIELDS = DASHBOARD_BASE_UPDATE_FIELDS diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index 98c03e20d87..17116a011a4 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -18,6 +18,7 @@ from homeassistant.helpers import collection, storage from homeassistant.util.yaml import Secrets, load_yaml_dict from .const import ( + CONF_ALLOW_SINGLE_WORD, CONF_ICON, CONF_URL_PATH, DOMAIN, @@ -234,10 +235,14 @@ class DashboardsCollection(collection.DictStorageCollection): async def _process_create_data(self, data: dict) -> dict: """Validate the config is valid.""" - if "-" not in data[CONF_URL_PATH]: + url_path = data[CONF_URL_PATH] + + allow_single_word = data.pop(CONF_ALLOW_SINGLE_WORD, False) + + if not allow_single_word and "-" not in url_path: raise vol.Invalid("Url path needs to contain a hyphen (-)") - if data[CONF_URL_PATH] in self.hass.data[DATA_PANELS]: + if url_path in self.hass.data[DATA_PANELS]: raise vol.Invalid("Panel url path needs to be unique") return self.CREATE_SCHEMA(data) diff --git a/homeassistant/components/lovelace/manifest.json b/homeassistant/components/lovelace/manifest.json index 9dcffdb3b6c..ed55142ee77 100644 --- a/homeassistant/components/lovelace/manifest.json +++ b/homeassistant/components/lovelace/manifest.json @@ -2,6 +2,7 @@ "domain": "lovelace", "name": "Dashboards", "codeowners": ["@home-assistant/frontend"], + "dependencies": ["onboarding"], "documentation": "https://www.home-assistant.io/integrations/lovelace", "integration_type": "system", "quality_scale": "internal" diff --git a/homeassistant/components/map/__init__.py b/homeassistant/components/map/__init__.py index a78199ad660..beddde5df1d 100644 --- a/homeassistant/components/map/__init__.py +++ b/homeassistant/components/map/__init__.py @@ -1,16 +1,52 @@ """Support for showing device locations.""" - -from homeassistant.components import frontend -from homeassistant.core import HomeAssistant +from homeassistant.components import onboarding +from homeassistant.components.lovelace import _create_map_dashboard +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType DOMAIN = "map" CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) +STORAGE_KEY = DOMAIN +STORAGE_VERSION_MAJOR = 1 + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Register the built-in map panel.""" - frontend.async_register_built_in_panel(hass, "map", "map", "hass:tooltip-account") + """Create a map panel.""" + + if DOMAIN in config: + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2024.10.0", + is_fixable=False, + is_persistent=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "map", + }, + ) + + store: Store[dict[str, bool]] = Store( + hass, + STORAGE_VERSION_MAJOR, + STORAGE_KEY, + ) + data = await store.async_load() + if data: + return True + + if onboarding.async_is_onboarded(hass): + await _create_map_dashboard(hass) + + await store.async_save({"migrated": True}) + return True diff --git a/homeassistant/components/map/manifest.json b/homeassistant/components/map/manifest.json index b617aa3e5fa..6a0333c862a 100644 --- a/homeassistant/components/map/manifest.json +++ b/homeassistant/components/map/manifest.json @@ -2,7 +2,7 @@ "domain": "map", "name": "Map", "codeowners": [], - "dependencies": ["frontend"], + "dependencies": ["frontend", "lovelace"], "documentation": "https://www.home-assistant.io/integrations/map", "integration_type": "system", "quality_scale": "internal" diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py index 61576c831bf..c11bd79c377 100644 --- a/homeassistant/components/onboarding/__init__.py +++ b/homeassistant/components/onboarding/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from collections.abc import Callable +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv @@ -26,15 +28,30 @@ STORAGE_VERSION = 4 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) -class OnboadingStorage(Store[dict[str, list[str]]]): +@dataclass +class OnboardingData: + """Container for onboarding data.""" + + listeners: list[Callable[[], None]] + onboarded: bool + steps: OnboardingStoreData + + +class OnboardingStoreData(TypedDict): + """Onboarding store data.""" + + done: list[str] + + +class OnboardingStorage(Store[OnboardingStoreData]): """Store onboarding data.""" async def _async_migrate_func( self, old_major_version: int, old_minor_version: int, - old_data: dict[str, list[str]], - ) -> dict[str, list[str]]: + old_data: OnboardingStoreData, + ) -> OnboardingStoreData: """Migrate to the new version.""" # From version 1 -> 2, we automatically mark the integration step done if old_major_version < 2: @@ -50,21 +67,37 @@ class OnboadingStorage(Store[dict[str, list[str]]]): @callback def async_is_onboarded(hass: HomeAssistant) -> bool: """Return if Home Assistant has been onboarded.""" - data = hass.data.get(DOMAIN) - return data is None or data is True + data: OnboardingData | None = hass.data.get(DOMAIN) + return data is None or data.onboarded is True @bind_hass @callback def async_is_user_onboarded(hass: HomeAssistant) -> bool: """Return if a user has been created as part of onboarding.""" - return async_is_onboarded(hass) or STEP_USER in hass.data[DOMAIN]["done"] + return async_is_onboarded(hass) or STEP_USER in hass.data[DOMAIN].steps["done"] + + +@callback +def async_add_listener(hass: HomeAssistant, listener: Callable[[], None]) -> None: + """Add a listener to be called when onboarding is complete.""" + data: OnboardingData | None = hass.data.get(DOMAIN) + + if not data: + # Onboarding not active + return + + if data.onboarded: + listener() + return + + data.listeners.append(listener) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the onboarding component.""" - store = OnboadingStorage(hass, STORAGE_VERSION, STORAGE_KEY, private=True) - data: dict[str, list[str]] | None + store = OnboardingStorage(hass, STORAGE_VERSION, STORAGE_KEY, private=True) + data: OnboardingStoreData | None if (data := await store.async_load()) is None: data = {"done": []} @@ -88,7 +121,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if set(data["done"]) == set(STEPS): return True - hass.data[DOMAIN] = data + hass.data[DOMAIN] = OnboardingData([], False, data) await views.async_setup(hass, data, store) diff --git a/homeassistant/components/onboarding/strings.json b/homeassistant/components/onboarding/strings.json index 9e3806927d2..bc8bb6e54ff 100644 --- a/homeassistant/components/onboarding/strings.json +++ b/homeassistant/components/onboarding/strings.json @@ -3,5 +3,8 @@ "living_room": "Living Room", "bedroom": "Bedroom", "kitchen": "Kitchen" + }, + "dashboard": { + "map": { "title": "Map" } } } diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index bcc4bd26a58..bb4d84d1b50 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -26,7 +26,7 @@ from homeassistant.setup import async_setup_component from homeassistant.util.async_ import create_eager_task if TYPE_CHECKING: - from . import OnboadingStorage + from . import OnboardingData, OnboardingStorage, OnboardingStoreData from .const import ( DEFAULT_AREAS, @@ -40,7 +40,7 @@ from .const import ( async def async_setup( - hass: HomeAssistant, data: dict[str, list[str]], store: OnboadingStorage + hass: HomeAssistant, data: OnboardingStoreData, store: OnboardingStorage ) -> None: """Set up the onboarding view.""" hass.http.register_view(OnboardingView(data, store)) @@ -58,7 +58,7 @@ class OnboardingView(HomeAssistantView): url = "/api/onboarding" name = "api:onboarding" - def __init__(self, data: dict[str, list[str]], store: OnboadingStorage) -> None: + def __init__(self, data: OnboardingStoreData, store: OnboardingStorage) -> None: """Initialize the onboarding view.""" self._store = store self._data = data @@ -77,7 +77,7 @@ class InstallationTypeOnboardingView(HomeAssistantView): url = "/api/onboarding/installation_type" name = "api:onboarding:installation_type" - def __init__(self, data: dict[str, list[str]]) -> None: + def __init__(self, data: OnboardingStoreData) -> None: """Initialize the onboarding installation type view.""" self._data = data @@ -96,7 +96,7 @@ class _BaseOnboardingView(HomeAssistantView): step: str - def __init__(self, data: dict[str, list[str]], store: OnboadingStorage) -> None: + def __init__(self, data: OnboardingStoreData, store: OnboardingStorage) -> None: """Initialize the onboarding view.""" self._store = store self._data = data @@ -113,7 +113,10 @@ class _BaseOnboardingView(HomeAssistantView): await self._store.async_save(self._data) if set(self._data["done"]) == set(STEPS): - hass.data[DOMAIN] = True + data: OnboardingData = hass.data[DOMAIN] + data.onboarded = True + for listener in data.listeners: + listener() class UserOnboardingView(_BaseOnboardingView): diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index f595776febe..724f65eafb6 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -441,7 +441,10 @@ def gen_platform_strings_schema(config: Config, integration: Integration) -> vol ONBOARDING_SCHEMA = vol.Schema( - {vol.Required("area"): {str: translation_value_validator}} + { + vol.Required("area"): {str: translation_value_validator}, + vol.Required("dashboard"): {str: {"title": translation_value_validator}}, + } ) diff --git a/tests/components/lovelace/test_cast.py b/tests/components/lovelace/test_cast.py index 12cf4a84095..5fdce538d4c 100644 --- a/tests/components/lovelace/test_cast.py +++ b/tests/components/lovelace/test_cast.py @@ -1,7 +1,8 @@ """Test the Lovelace Cast platform.""" +from collections.abc import Generator from time import time -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest @@ -15,6 +16,19 @@ from homeassistant.setup import async_setup_component from tests.common import async_mock_service +@pytest.fixture(autouse=True) +def mock_onboarding_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding. + + Enabled to prevent creating default dashboards during test execution. + """ + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=True, + ) as mock_onboarding: + yield mock_onboarding + + @pytest.fixture async def mock_https_url(hass): """Mock valid URL.""" diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py index a85770d8ae4..d0ac6e98835 100644 --- a/tests/components/lovelace/test_dashboard.py +++ b/tests/components/lovelace/test_dashboard.py @@ -1,7 +1,8 @@ """Test the Lovelace initialization.""" +from collections.abc import Generator from typing import Any -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest @@ -14,6 +15,19 @@ from tests.common import assert_setup_component, async_capture_events from tests.typing import WebSocketGenerator +@pytest.fixture(autouse=True) +def mock_onboarding_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding. + + Enabled to prevent creating default dashboards during test execution. + """ + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=True, + ) as mock_onboarding: + yield mock_onboarding + + async def test_lovelace_from_storage( hass: HomeAssistant, hass_ws_client, hass_storage: dict[str, Any] ) -> None: @@ -277,7 +291,7 @@ async def test_dashboard_from_yaml( async def test_wrong_key_dashboard_from_yaml(hass: HomeAssistant) -> None: """Test we don't load lovelace dashboard without hyphen config from yaml.""" - with assert_setup_component(0): + with assert_setup_component(0, "lovelace"): assert not await async_setup_component( hass, "lovelace", diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py new file mode 100644 index 00000000000..a88745e4500 --- /dev/null +++ b/tests/components/lovelace/test_init.py @@ -0,0 +1,98 @@ +"""Test the Lovelace initialization.""" + +from collections.abc import Generator +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.typing import WebSocketGenerator + + +@pytest.fixture +def mock_onboarding_not_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding.""" + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding: + yield mock_onboarding + + +@pytest.fixture +def mock_onboarding_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding.""" + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=True, + ) as mock_onboarding: + yield mock_onboarding + + +@pytest.fixture +def mock_add_onboarding_listener() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding.""" + with patch( + "homeassistant.components.onboarding.async_add_listener", + ) as mock_add_onboarding_listener: + yield mock_add_onboarding_listener + + +async def test_create_dashboards_when_onboarded( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + hass_storage: dict[str, Any], + mock_onboarding_done, +) -> None: + """Test we don't create dashboards when onboarded.""" + client = await hass_ws_client(hass) + + assert await async_setup_component(hass, "lovelace", {}) + + # List dashboards + await client.send_json_auto_id({"type": "lovelace/dashboards/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] + + +async def test_create_dashboards_when_not_onboarded( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + hass_storage: dict[str, Any], + mock_add_onboarding_listener, + mock_onboarding_not_done, +) -> None: + """Test we automatically create dashboards when not onboarded.""" + client = await hass_ws_client(hass) + + assert await async_setup_component(hass, "lovelace", {}) + + # Call onboarding listener + mock_add_onboarding_listener.mock_calls[0][1][1]() + await hass.async_block_till_done() + + # List dashboards + await client.send_json_auto_id({"type": "lovelace/dashboards/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [ + { + "icon": "mdi:map", + "id": "map", + "mode": "storage", + "require_admin": False, + "show_in_sidebar": True, + "title": "Map", + "url_path": "map", + } + ] + + # List map dashboard config + await client.send_json_auto_id({"type": "lovelace/config", "url_path": "map"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == {"strategy": {"type": "map"}} diff --git a/tests/components/lovelace/test_system_health.py b/tests/components/lovelace/test_system_health.py index 11cb8776e62..9bd8543004c 100644 --- a/tests/components/lovelace/test_system_health.py +++ b/tests/components/lovelace/test_system_health.py @@ -1,7 +1,10 @@ """Tests for Lovelace system health.""" +from collections.abc import Generator from typing import Any -from unittest.mock import patch +from unittest.mock import MagicMock, patch + +import pytest from homeassistant.components.lovelace import dashboard from homeassistant.core import HomeAssistant @@ -10,6 +13,19 @@ from homeassistant.setup import async_setup_component from tests.common import get_system_health_info +@pytest.fixture(autouse=True) +def mock_onboarding_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding. + + Enabled to prevent creating default dashboards during test execution. + """ + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=True, + ) as mock_onboarding: + yield mock_onboarding + + async def test_system_health_info_autogen(hass: HomeAssistant) -> None: """Test system health info endpoint.""" assert await async_setup_component(hass, "lovelace", {}) diff --git a/tests/components/map/__init__.py b/tests/components/map/__init__.py new file mode 100644 index 00000000000..142afc0d5c9 --- /dev/null +++ b/tests/components/map/__init__.py @@ -0,0 +1 @@ +"""Tests for Map.""" diff --git a/tests/components/map/test_init.py b/tests/components/map/test_init.py new file mode 100644 index 00000000000..6d79afefab3 --- /dev/null +++ b/tests/components/map/test_init.py @@ -0,0 +1,116 @@ +"""Test the Map initialization.""" + +from collections.abc import Generator +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.components.map import DOMAIN +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.helpers import issue_registry as ir +from homeassistant.setup import async_setup_component + +from tests.common import MockModule, mock_integration + + +@pytest.fixture +def mock_onboarding_not_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding.""" + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding: + yield mock_onboarding + + +@pytest.fixture +def mock_onboarding_done() -> Generator[MagicMock, None, None]: + """Mock that Home Assistant is currently onboarding.""" + with patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=True, + ) as mock_onboarding: + yield mock_onboarding + + +@pytest.fixture +def mock_create_map_dashboard() -> Generator[MagicMock, None, None]: + """Mock the create map dashboard function.""" + with patch( + "homeassistant.components.map._create_map_dashboard", + ) as mock_create_map_dashboard: + yield mock_create_map_dashboard + + +async def test_create_dashboards_when_onboarded( + hass: HomeAssistant, + hass_storage: dict[str, Any], + mock_onboarding_done, + mock_create_map_dashboard, +) -> None: + """Test we create map dashboard when onboarded.""" + # Mock the lovelace integration to prevent it from creating a map dashboard + mock_integration(hass, MockModule("lovelace")) + + assert await async_setup_component(hass, DOMAIN, {}) + + mock_create_map_dashboard.assert_called_once() + assert hass_storage[DOMAIN]["data"] == {"migrated": True} + + +async def test_create_dashboards_once_when_onboarded( + hass: HomeAssistant, + hass_storage: dict[str, Any], + mock_onboarding_done, + mock_create_map_dashboard, +) -> None: + """Test we create map dashboard once when onboarded.""" + hass_storage[DOMAIN] = { + "version": 1, + "minor_version": 1, + "key": "map", + "data": {"migrated": True}, + } + + # Mock the lovelace integration to prevent it from creating a map dashboard + mock_integration(hass, MockModule("lovelace")) + + assert await async_setup_component(hass, DOMAIN, {}) + + mock_create_map_dashboard.assert_not_called() + assert hass_storage[DOMAIN]["data"] == {"migrated": True} + + +async def test_create_dashboards_when_not_onboarded( + hass: HomeAssistant, + hass_storage: dict[str, Any], + mock_onboarding_not_done, + mock_create_map_dashboard, +) -> None: + """Test we do not create map dashboard when not onboarded.""" + # Mock the lovelace integration to prevent it from creating a map dashboard + mock_integration(hass, MockModule("lovelace")) + + assert await async_setup_component(hass, DOMAIN, {}) + + mock_create_map_dashboard.assert_not_called() + assert hass_storage[DOMAIN]["data"] == {"migrated": True} + + +async def test_create_issue_when_not_manually_configured(hass: HomeAssistant) -> None: + """Test creating issue registry issues.""" + assert await async_setup_component(hass, DOMAIN, {}) + + issue_registry = ir.async_get(hass) + assert not issue_registry.async_get_issue( + HOMEASSISTANT_DOMAIN, "deprecated_yaml_map" + ) + + +async def test_create_issue_when_manually_configured(hass: HomeAssistant) -> None: + """Test creating issue registry issues.""" + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + + issue_registry = ir.async_get(hass) + assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, "deprecated_yaml_map") diff --git a/tests/components/onboarding/test_init.py b/tests/components/onboarding/test_init.py index 69063057387..f6941098b18 100644 --- a/tests/components/onboarding/test_init.py +++ b/tests/components/onboarding/test_init.py @@ -48,10 +48,10 @@ async def test_is_onboarded() -> None: assert onboarding.async_is_onboarded(hass) - hass.data[onboarding.DOMAIN] = True + hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], True, {"done": []}) assert onboarding.async_is_onboarded(hass) - hass.data[onboarding.DOMAIN] = {"done": []} + hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], False, {"done": []}) assert not onboarding.async_is_onboarded(hass) @@ -62,10 +62,15 @@ async def test_is_user_onboarded() -> None: assert onboarding.async_is_user_onboarded(hass) - hass.data[onboarding.DOMAIN] = True + hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], True, {"done": []}) assert onboarding.async_is_user_onboarded(hass) - hass.data[onboarding.DOMAIN] = {"done": []} + hass.data[onboarding.DOMAIN] = onboarding.OnboardingData( + [], False, {"done": ["user"]} + ) + assert onboarding.async_is_user_onboarded(hass) + + hass.data[onboarding.DOMAIN] = onboarding.OnboardingData([], False, {"done": []}) assert not onboarding.async_is_user_onboarded(hass) diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 406faff0dac..272d0e99773 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -4,7 +4,7 @@ import asyncio from http import HTTPStatus import os from typing import Any -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest @@ -655,3 +655,64 @@ async def test_onboarding_installation_type_after_done( resp = await client.get("/api/onboarding/installation_type") assert resp.status == 401 + + +async def test_complete_onboarding( + hass: HomeAssistant, hass_client: ClientSessionGenerator +) -> None: + """Test completing onboarding calls listeners.""" + listener_1 = Mock() + onboarding.async_add_listener(hass, listener_1) + listener_1.assert_not_called() + + assert await async_setup_component(hass, "onboarding", {}) + await hass.async_block_till_done() + + listener_2 = Mock() + onboarding.async_add_listener(hass, listener_2) + listener_2.assert_not_called() + + client = await hass_client() + + assert not onboarding.async_is_onboarded(hass) + + # Complete the user step + resp = await client.post( + "/api/onboarding/users", + json={ + "client_id": CLIENT_ID, + "name": "Test Name", + "username": "test-user", + "password": "test-pass", + "language": "en", + }, + ) + assert resp.status == 200 + assert not onboarding.async_is_onboarded(hass) + listener_2.assert_not_called() + + # Complete the core config step + resp = await client.post("/api/onboarding/core_config") + assert resp.status == 200 + assert not onboarding.async_is_onboarded(hass) + listener_2.assert_not_called() + + # Complete the integration step + resp = await client.post( + "/api/onboarding/integration", + json={"client_id": CLIENT_ID, "redirect_uri": CLIENT_REDIRECT_URI}, + ) + assert resp.status == 200 + assert not onboarding.async_is_onboarded(hass) + listener_2.assert_not_called() + + # Complete the analytics step + resp = await client.post("/api/onboarding/analytics") + assert resp.status == 200 + assert onboarding.async_is_onboarded(hass) + listener_1.assert_not_called() # Registered before the integration was setup + listener_2.assert_called_once_with() + + listener_3 = Mock() + onboarding.async_add_listener(hass, listener_3) + listener_3.assert_called_once_with() From 606ee3c3792c4f1892b200655c97bc03980f04f0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:10:02 +0100 Subject: [PATCH 1037/1691] Enable PERF ruff rules (#113408) --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ccfd9e8aba..f258a0aa2f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -599,9 +599,7 @@ select = [ "N804", # First argument of a class method should be named cls "N805", # First argument of a method should be named self "N815", # Variable {name} in class scope should not be mixedCase - "PERF101", # Do not cast an iterable to list before iterating over it - "PERF102", # When using only the {subset} of a dict use the {subset}() method - "PERF203", # try-except within a loop incurs performance overhead + "PERF", # Perflint "PGH004", # Use specific rule codes when using noqa "PLC0414", # Useless import alias. Import alias does not rename original package. "PLC", # pylint From 70286b38ece4b508e1ff3274a46e95dc78759115 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 14:49:20 +0100 Subject: [PATCH 1038/1691] Fix icon mistakes (#113420) --- homeassistant/components/envisalink/icons.json | 2 +- homeassistant/components/evohome/icons.json | 2 +- homeassistant/components/ps4/icons.json | 2 +- homeassistant/components/squeezebox/icons.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/envisalink/icons.json b/homeassistant/components/envisalink/icons.json index 55084ede935..20696067f76 100644 --- a/homeassistant/components/envisalink/icons.json +++ b/homeassistant/components/envisalink/icons.json @@ -1,6 +1,6 @@ { "services": { "alarm_keypress": "mdi:alarm-panel", - "invoke_custom_function": "mdi:terminal" + "invoke_custom_function": "mdi:console" } } diff --git a/homeassistant/components/evohome/icons.json b/homeassistant/components/evohome/icons.json index b396b226c25..cd0005e2546 100644 --- a/homeassistant/components/evohome/icons.json +++ b/homeassistant/components/evohome/icons.json @@ -1,7 +1,7 @@ { "services": { "set_system_mode": "mdi:pencil", - "reset_system": "mdi:reset", + "reset_system": "mdi:refresh", "refresh_system": "mdi:refresh", "set_zone_override": "mdi:motion-sensor", "clear_zone_override": "mdi:motion-sensor-off" diff --git a/homeassistant/components/ps4/icons.json b/homeassistant/components/ps4/icons.json index 165099070a9..8da5909213b 100644 --- a/homeassistant/components/ps4/icons.json +++ b/homeassistant/components/ps4/icons.json @@ -7,6 +7,6 @@ } }, "services": { - "send_command": "mdi:terminal" + "send_command": "mdi:console" } } diff --git a/homeassistant/components/squeezebox/icons.json b/homeassistant/components/squeezebox/icons.json index d90e5dad84d..d58f0d5634d 100644 --- a/homeassistant/components/squeezebox/icons.json +++ b/homeassistant/components/squeezebox/icons.json @@ -1,6 +1,6 @@ { "services": { - "call_method": "mdi:terminal", + "call_method": "mdi:console", "call_query": "mdi:database", "sync": "mdi:sync", "unsync": "mdi:sync-off" From 796d0381ccd8fca22caa554b078de05518571f80 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 16:48:13 +0100 Subject: [PATCH 1039/1691] Add service icons to Utility meter (#113419) * Add service icons to Utility meter * Add service icons to Utility meter * Update homeassistant/components/utility_meter/icons.json Co-authored-by: Diogo Gomes * Update homeassistant/components/utility_meter/icons.json Co-authored-by: Diogo Gomes --------- Co-authored-by: Diogo Gomes --- homeassistant/components/utility_meter/icons.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/utility_meter/icons.json b/homeassistant/components/utility_meter/icons.json index 7260fbfbe96..3c447b4a810 100644 --- a/homeassistant/components/utility_meter/icons.json +++ b/homeassistant/components/utility_meter/icons.json @@ -10,5 +10,9 @@ "default": "mdi:clock-outline" } } + }, + "services": { + "reset": "mdi:numeric-0-box-outline", + "calibrate": "mdi:auto-fix" } } From 0316681615d292abb9be5f3b4865a3149e4a7934 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 16:49:17 +0100 Subject: [PATCH 1040/1691] Add service icons to Demo (#113414) --- homeassistant/components/demo/icons.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/demo/icons.json b/homeassistant/components/demo/icons.json index 9c746c633d4..d9e1d405490 100644 --- a/homeassistant/components/demo/icons.json +++ b/homeassistant/components/demo/icons.json @@ -73,5 +73,8 @@ "default": "mdi:air-conditioner" } } + }, + "services": { + "randomize_device_tracker_data": "mdi:dice-multiple" } } From 0867ace44a6b7dfce3d5279d903a009b9e676044 Mon Sep 17 00:00:00 2001 From: Jeef Date: Thu, 14 Mar 2024 09:55:25 -0600 Subject: [PATCH 1041/1691] Bump weatherflow_cloud backing lib v0.2.13 (#113181) * bump backing lib - and switch to rest models * ci fix --- homeassistant/components/weatherflow_cloud/coordinator.py | 2 +- homeassistant/components/weatherflow_cloud/manifest.json | 2 +- homeassistant/components/weatherflow_cloud/weather.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/weatherflow_cloud/coordinator.py b/homeassistant/components/weatherflow_cloud/coordinator.py index 56554b555b0..600a5e105c9 100644 --- a/homeassistant/components/weatherflow_cloud/coordinator.py +++ b/homeassistant/components/weatherflow_cloud/coordinator.py @@ -4,7 +4,7 @@ from datetime import timedelta from aiohttp import ClientResponseError from weatherflow4py.api import WeatherFlowRestAPI -from weatherflow4py.models.unified import WeatherFlowData +from weatherflow4py.models.rest.unified import WeatherFlowData from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed diff --git a/homeassistant/components/weatherflow_cloud/manifest.json b/homeassistant/components/weatherflow_cloud/manifest.json index 72a49c0cf19..ec5bd0ffd8a 100644 --- a/homeassistant/components/weatherflow_cloud/manifest.json +++ b/homeassistant/components/weatherflow_cloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "iot_class": "cloud_polling", - "requirements": ["weatherflow4py==0.1.17"] + "requirements": ["weatherflow4py==0.2.13"] } diff --git a/homeassistant/components/weatherflow_cloud/weather.py b/homeassistant/components/weatherflow_cloud/weather.py index 0d754eebb64..8d40fab52cb 100644 --- a/homeassistant/components/weatherflow_cloud/weather.py +++ b/homeassistant/components/weatherflow_cloud/weather.py @@ -2,7 +2,7 @@ from __future__ import annotations -from weatherflow4py.models.unified import WeatherFlowData +from weatherflow4py.models.rest.unified import WeatherFlowData from homeassistant.components.weather import ( Forecast, diff --git a/requirements_all.txt b/requirements_all.txt index 3c4faaad46c..71580e8c804 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2833,7 +2833,7 @@ watchdog==2.3.1 waterfurnace==1.1.0 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.17 +weatherflow4py==0.2.13 # homeassistant.components.webmin webmin-xmlrpc==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ada8b08ee1f..9b80eb8a119 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2174,7 +2174,7 @@ wallbox==0.6.0 watchdog==2.3.1 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.1.17 +weatherflow4py==0.2.13 # homeassistant.components.webmin webmin-xmlrpc==0.0.2 From 8a8546579c54c152abc80c1959d7cc0130b022ea Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 17:28:17 +0100 Subject: [PATCH 1042/1691] Add service icons to Scene (#113418) --- homeassistant/components/scene/icons.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/components/scene/icons.json b/homeassistant/components/scene/icons.json index 3ab7264b357..563c0f31ddc 100644 --- a/homeassistant/components/scene/icons.json +++ b/homeassistant/components/scene/icons.json @@ -3,5 +3,12 @@ "_": { "default": "mdi:palette" } + }, + "services": { + "turn_on": "mdi:power", + "reload": "mdi:reload", + "apply": "mdi:check", + "create": "mdi:plus", + "delete": "mdi:delete" } } From f1374503c37f2a450ba247cdb5299f2395cda21c Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Thu, 14 Mar 2024 17:51:24 +0100 Subject: [PATCH 1043/1691] Bump aioautomower to 2024.3.3 (#113430) --- .../husqvarna_automower/manifest.json | 2 +- .../components/husqvarna_automower/sensor.py | 7 +++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../husqvarna_automower/test_sensor.py | 30 +++++++++++++++---- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json index 49a554f2e0a..ed013f2e0c2 100644 --- a/homeassistant/components/husqvarna_automower/manifest.json +++ b/homeassistant/components/husqvarna_automower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "iot_class": "cloud_push", "loggers": ["aioautomower"], - "requirements": ["aioautomower==2024.3.2"] + "requirements": ["aioautomower==2024.3.3"] } diff --git a/homeassistant/components/husqvarna_automower/sensor.py b/homeassistant/components/husqvarna_automower/sensor.py index 40f676df541..857d3039e22 100644 --- a/homeassistant/components/husqvarna_automower/sensor.py +++ b/homeassistant/components/husqvarna_automower/sensor.py @@ -70,6 +70,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, suggested_unit_of_measurement=UnitOfTime.HOURS, + exists_fn=lambda data: data.statistics.total_charging_time is not None, value_fn=lambda data: data.statistics.total_charging_time, ), AutomowerSensorEntityDescription( @@ -80,6 +81,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, suggested_unit_of_measurement=UnitOfTime.HOURS, + exists_fn=lambda data: data.statistics.total_cutting_time is not None, value_fn=lambda data: data.statistics.total_cutting_time, ), AutomowerSensorEntityDescription( @@ -90,6 +92,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, suggested_unit_of_measurement=UnitOfTime.HOURS, + exists_fn=lambda data: data.statistics.total_running_time is not None, value_fn=lambda data: data.statistics.total_running_time, ), AutomowerSensorEntityDescription( @@ -100,6 +103,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, suggested_unit_of_measurement=UnitOfTime.HOURS, + exists_fn=lambda data: data.statistics.total_searching_time is not None, value_fn=lambda data: data.statistics.total_searching_time, ), AutomowerSensorEntityDescription( @@ -107,6 +111,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( translation_key="number_of_charging_cycles", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL, + exists_fn=lambda data: data.statistics.number_of_charging_cycles is not None, value_fn=lambda data: data.statistics.number_of_charging_cycles, ), AutomowerSensorEntityDescription( @@ -114,6 +119,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( translation_key="number_of_collisions", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.TOTAL, + exists_fn=lambda data: data.statistics.number_of_collisions is not None, value_fn=lambda data: data.statistics.number_of_collisions, ), AutomowerSensorEntityDescription( @@ -124,6 +130,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=UnitOfLength.METERS, suggested_unit_of_measurement=UnitOfLength.KILOMETERS, + exists_fn=lambda data: data.statistics.total_drive_distance is not None, value_fn=lambda data: data.statistics.total_drive_distance, ), AutomowerSensorEntityDescription( diff --git a/requirements_all.txt b/requirements_all.txt index 71580e8c804..24c87de9198 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -203,7 +203,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.3.2 +aioautomower==2024.3.3 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b80eb8a119..9094e7efd0d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -182,7 +182,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.3.2 +aioautomower==2024.3.3 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/tests/components/husqvarna_automower/test_sensor.py b/tests/components/husqvarna_automower/test_sensor.py index dd12e0942c7..bc464b2ce78 100644 --- a/tests/components/husqvarna_automower/test_sensor.py +++ b/tests/components/husqvarna_automower/test_sensor.py @@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, patch from aioautomower.model import MowerModes from aioautomower.utils import mower_list_to_dictionary_dataclass from freezegun.api import FrozenDateTimeFactory +import pytest from syrupy import SnapshotAssertion from homeassistant.components.husqvarna_automower.const import DOMAIN @@ -60,17 +61,36 @@ async def test_cutting_blade_usage_time_sensor( assert state is not None assert state.state == "0.034" - entry = hass.config_entries.async_entries(DOMAIN)[0] - await hass.config_entries.async_remove(entry.entry_id) - await hass.async_block_till_done() + +@pytest.mark.parametrize( + ("sensor_to_test"), + [ + ("cutting_blade_usage_time"), + ("number_of_charging_cycles"), + ("number_of_collisions"), + ("total_charging_time"), + ("total_cutting_time"), + ("total_running_time"), + ("total_searching_time"), + ("total_drive_distance"), + ], +) +async def test_statistics_not_available( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + sensor_to_test: str, +) -> None: + """Test if this sensor is only added, if data is available.""" + values = mower_list_to_dictionary_dataclass( load_json_value_fixture("mower.json", DOMAIN) ) - delattr(values[TEST_MOWER_ID].statistics, "cutting_blade_usage_time") + delattr(values[TEST_MOWER_ID].statistics, sensor_to_test) mock_automower_client.get_status.return_value = values await setup_integration(hass, mock_config_entry) - state = hass.states.get("sensor.test_mower_1_cutting_blade_usage_time") + state = hass.states.get(f"sensor.test_mower_1_{sensor_to_test}") assert state is None From 02521c9da33d4897b5a0e4dd26831c5f2a3017e3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 17:54:55 +0100 Subject: [PATCH 1044/1691] Add service icons to ADS (#113412) --- homeassistant/components/ads/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/ads/icons.json diff --git a/homeassistant/components/ads/icons.json b/homeassistant/components/ads/icons.json new file mode 100644 index 00000000000..5ab8041fe9b --- /dev/null +++ b/homeassistant/components/ads/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "write_data_by_name": "mdi:pencil" + } +} From 2bf6170a6bfc70f972c5ee622b05942cbefd4181 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 14 Mar 2024 13:34:45 -0400 Subject: [PATCH 1045/1691] Add UniFi Protect service to remove privacy zones (#111292) --- .../components/unifiprotect/services.py | 62 ++++++++++++++++++- .../components/unifiprotect/services.yaml | 13 ++++ .../components/unifiprotect/strings.json | 14 +++++ .../components/unifiprotect/test_services.py | 58 ++++++++++++++++- 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/unifiprotect/services.py b/homeassistant/components/unifiprotect/services.py index da11dbd7bc4..8c62664f55b 100644 --- a/homeassistant/components/unifiprotect/services.py +++ b/homeassistant/components/unifiprotect/services.py @@ -8,15 +8,15 @@ from typing import Any, cast from pydantic import ValidationError from pyunifiprotect.api import ProtectApiClient -from pyunifiprotect.data import Chime +from pyunifiprotect.data import Camera, Chime from pyunifiprotect.exceptions import ClientError import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ATTR_DEVICE_ID, Platform +from homeassistant.const import ATTR_DEVICE_ID, ATTR_NAME, Platform from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import ( config_validation as cv, device_registry as dr, @@ -30,6 +30,8 @@ from .data import async_ufp_instance_for_config_entry_ids SERVICE_ADD_DOORBELL_TEXT = "add_doorbell_text" SERVICE_REMOVE_DOORBELL_TEXT = "remove_doorbell_text" +SERVICE_SET_PRIVACY_ZONE = "set_privacy_zone" +SERVICE_REMOVE_PRIVACY_ZONE = "remove_privacy_zone" SERVICE_SET_DEFAULT_DOORBELL_TEXT = "set_default_doorbell_text" SERVICE_SET_CHIME_PAIRED = "set_chime_paired_doorbells" @@ -38,6 +40,7 @@ ALL_GLOBAL_SERIVCES = [ SERVICE_REMOVE_DOORBELL_TEXT, SERVICE_SET_DEFAULT_DOORBELL_TEXT, SERVICE_SET_CHIME_PAIRED, + SERVICE_REMOVE_PRIVACY_ZONE, ] DOORBELL_TEXT_SCHEMA = vol.All( @@ -60,6 +63,16 @@ CHIME_PAIRED_SCHEMA = vol.All( cv.has_at_least_one_key(ATTR_DEVICE_ID), ) +REMOVE_PRIVACY_ZONE_SCHEMA = vol.All( + vol.Schema( + { + **cv.ENTITY_SERVICE_FIELDS, + vol.Required(ATTR_NAME): cv.string, + }, + ), + cv.has_at_least_one_key(ATTR_DEVICE_ID), +) + @callback def _async_get_ufp_instance(hass: HomeAssistant, device_id: str) -> ProtectApiClient: @@ -77,6 +90,21 @@ def _async_get_ufp_instance(hass: HomeAssistant, device_id: str) -> ProtectApiCl raise HomeAssistantError(f"No device found for device id: {device_id}") +@callback +def _async_get_ufp_camera(hass: HomeAssistant, call: ServiceCall) -> Camera: + ref = async_extract_referenced_entity_ids(hass, call) + entity_registry = er.async_get(hass) + + entity_id = ref.indirectly_referenced.pop() + camera_entity = entity_registry.async_get(entity_id) + assert camera_entity is not None + assert camera_entity.device_id is not None + camera_mac = _async_unique_id_to_mac(camera_entity.unique_id) + + instance = _async_get_ufp_instance(hass, camera_entity.device_id) + return cast(Camera, instance.bootstrap.get_device_from_mac(camera_mac)) + + @callback def _async_get_protect_from_call( hass: HomeAssistant, call: ServiceCall @@ -123,6 +151,29 @@ async def set_default_doorbell_text(hass: HomeAssistant, call: ServiceCall) -> N await _async_service_call_nvr(hass, call, "set_default_doorbell_message", message) +async def remove_privacy_zone(hass: HomeAssistant, call: ServiceCall) -> None: + """Remove privacy zone from camera.""" + + name: str = call.data[ATTR_NAME] + camera = _async_get_ufp_camera(hass, call) + + remove_index: int | None = None + for index, zone in enumerate(camera.privacy_zones): + if zone.name == name: + remove_index = index + break + + if remove_index is None: + raise ServiceValidationError( + f"Could not find privacy zone with name {name} on camera {camera.display_name}." + ) + + def remove_zone() -> None: + camera.privacy_zones.pop(remove_index) + + await camera.queue_update(remove_zone) + + @callback def _async_unique_id_to_mac(unique_id: str) -> str: """Extract the MAC address from the registry entry unique id.""" @@ -190,6 +241,11 @@ def async_setup_services(hass: HomeAssistant) -> None: functools.partial(set_chime_paired_doorbells, hass), CHIME_PAIRED_SCHEMA, ), + ( + SERVICE_REMOVE_PRIVACY_ZONE, + functools.partial(remove_privacy_zone, hass), + REMOVE_PRIVACY_ZONE_SCHEMA, + ), ] for name, method, schema in services: if hass.services.has_service(DOMAIN, name): diff --git a/homeassistant/components/unifiprotect/services.yaml b/homeassistant/components/unifiprotect/services.yaml index 6998f540471..e747b9e7240 100644 --- a/homeassistant/components/unifiprotect/services.yaml +++ b/homeassistant/components/unifiprotect/services.yaml @@ -52,3 +52,16 @@ set_chime_paired_doorbells: integration: unifiprotect domain: binary_sensor device_class: occupancy +remove_privacy_zone: + fields: + device_id: + required: true + selector: + device: + integration: unifiprotect + entity: + domain: camera + name: + required: true + selector: + text: diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index eccf5829332..97e68388dd9 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -157,6 +157,20 @@ "description": "The doorbells to link to the chime." } } + }, + "remove_privacy_zone": { + "name": "Remove camera privacy zone", + "description": "Use to remove a privacy zone from a camera.", + "fields": { + "device_id": { + "name": "Camera", + "description": "Camera you want to remove privacy zone from." + }, + "name": { + "name": "Privacy Zone Name", + "description": "The name of the zone to remove." + } + } } } } diff --git a/tests/components/unifiprotect/test_services.py b/tests/components/unifiprotect/test_services.py index fd8226e425a..508a143c522 100644 --- a/tests/components/unifiprotect/test_services.py +++ b/tests/components/unifiprotect/test_services.py @@ -5,17 +5,19 @@ from __future__ import annotations from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Chime, Light, ModelType +from pyunifiprotect.data import Camera, Chime, Color, Light, ModelType +from pyunifiprotect.data.devices import CameraZone from pyunifiprotect.exceptions import BadRequest from homeassistant.components.unifiprotect.const import ATTR_MESSAGE, DOMAIN from homeassistant.components.unifiprotect.services import ( SERVICE_ADD_DOORBELL_TEXT, SERVICE_REMOVE_DOORBELL_TEXT, + SERVICE_REMOVE_PRIVACY_ZONE, SERVICE_SET_CHIME_PAIRED, SERVICE_SET_DEFAULT_DOORBELL_TEXT, ) -from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID +from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, ATTR_NAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -177,3 +179,55 @@ async def test_set_chime_paired_doorbells( ufp.api.update_device.assert_called_once_with( ModelType.CHIME, chime.id, {"cameraIds": sorted([camera1.id, camera2.id])} ) + + +async def test_remove_privacy_zone_no_zone( + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, +) -> None: + """Test remove_privacy_zone service.""" + + ufp.api.update_device = AsyncMock() + doorbell.privacy_zones = [] + + await init_entry(hass, ufp, [doorbell]) + + registry = er.async_get(hass) + camera_entry = registry.async_get("binary_sensor.test_camera_doorbell") + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_REMOVE_PRIVACY_ZONE, + {ATTR_DEVICE_ID: camera_entry.device_id, ATTR_NAME: "Testing"}, + blocking=True, + ) + ufp.api.update_device.assert_not_called() + + +async def test_remove_privacy_zone( + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, +) -> None: + """Test remove_privacy_zone service.""" + + ufp.api.update_device = AsyncMock() + doorbell.privacy_zones = [ + CameraZone(id=0, name="Testing", color=Color("red"), points=[(0, 0), (1, 1)]) + ] + + await init_entry(hass, ufp, [doorbell]) + + registry = er.async_get(hass) + camera_entry = registry.async_get("binary_sensor.test_camera_doorbell") + + await hass.services.async_call( + DOMAIN, + SERVICE_REMOVE_PRIVACY_ZONE, + {ATTR_DEVICE_ID: camera_entry.device_id, ATTR_NAME: "Testing"}, + blocking=True, + ) + ufp.api.update_device.assert_called() + assert not len(doorbell.privacy_zones) From 20626947db0bbd0c14f89f8386cd3e69f83cbc33 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 18:43:09 +0100 Subject: [PATCH 1046/1691] Add icon translations to Unifi protect (#112332) --- homeassistant/components/unifiprotect/icons.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 homeassistant/components/unifiprotect/icons.json diff --git a/homeassistant/components/unifiprotect/icons.json b/homeassistant/components/unifiprotect/icons.json new file mode 100644 index 00000000000..e4c8801c888 --- /dev/null +++ b/homeassistant/components/unifiprotect/icons.json @@ -0,0 +1,8 @@ +{ + "services": { + "add_doorbell_text": "mdi:message-plus", + "remove_doorbell_text": "mdi:message-minus", + "set_default_doorbell_text": "mdi:message-processing", + "set_chime_paired_doorbells": "mdi:bell-cog" + } +} From 2aadd643edb1f6da283322b35120d5a989b2445b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 14 Mar 2024 19:02:23 +0100 Subject: [PATCH 1047/1691] Add floors to service target (#110850) --- homeassistant/const.py | 3 + homeassistant/helpers/config_validation.py | 7 ++ homeassistant/helpers/service.py | 45 ++++++++--- tests/helpers/test_service.py | 91 +++++++++++++++++++--- 4 files changed, 125 insertions(+), 21 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6331b5d46ce..04561f489dd 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -510,6 +510,9 @@ ATTR_AREA_ID: Final = "area_id" # Contains one string, the device ID ATTR_DEVICE_ID: Final = "device_id" +# Contains one string or a list of strings, each being an floor id +ATTR_FLOOR_ID: Final = "floor_id" + # String with a friendly name for the entity ATTR_FRIENDLY_NAME: Final = "friendly_name" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index c545862f48a..8f9e0d5353f 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -29,6 +29,7 @@ from homeassistant.const import ( ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, + ATTR_FLOOR_ID, CONF_ABOVE, CONF_ALIAS, CONF_ATTRIBUTE, @@ -1216,6 +1217,9 @@ ENTITY_SERVICE_FIELDS = { vol.Optional(ATTR_AREA_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), + vol.Optional(ATTR_FLOOR_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), } TARGET_SERVICE_FIELDS = { @@ -1233,6 +1237,9 @@ TARGET_SERVICE_FIELDS = { vol.Optional(ATTR_AREA_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), + vol.Optional(ATTR_FLOOR_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), } diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 99c15c3412e..b58e0831ccb 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -18,6 +18,7 @@ from homeassistant.const import ( ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, + ATTR_FLOOR_ID, CONF_ENTITY_ID, CONF_SERVICE, CONF_SERVICE_DATA, @@ -53,6 +54,7 @@ from . import ( config_validation as cv, device_registry, entity_registry, + floor_registry, template, translation, ) @@ -194,7 +196,7 @@ class ServiceParams(TypedDict): class ServiceTargetSelector: """Class to hold a target selector for a service.""" - __slots__ = ("entity_ids", "device_ids", "area_ids") + __slots__ = ("entity_ids", "device_ids", "area_ids", "floor_ids") def __init__(self, service_call: ServiceCall) -> None: """Extract ids from service call data.""" @@ -202,6 +204,7 @@ class ServiceTargetSelector: entity_ids: str | list | None = service_call_data.get(ATTR_ENTITY_ID) device_ids: str | list | None = service_call_data.get(ATTR_DEVICE_ID) area_ids: str | list | None = service_call_data.get(ATTR_AREA_ID) + floor_ids: str | list | None = service_call_data.get(ATTR_FLOOR_ID) self.entity_ids = ( set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() @@ -210,11 +213,16 @@ class ServiceTargetSelector: set(cv.ensure_list(device_ids)) if _has_match(device_ids) else set() ) self.area_ids = set(cv.ensure_list(area_ids)) if _has_match(area_ids) else set() + self.floor_ids = ( + set(cv.ensure_list(floor_ids)) if _has_match(floor_ids) else set() + ) @property def has_any_selector(self) -> bool: """Determine if any selectors are present.""" - return bool(self.entity_ids or self.device_ids or self.area_ids) + return bool( + self.entity_ids or self.device_ids or self.area_ids or self.floor_ids + ) @dataclasses.dataclass(slots=True) @@ -224,21 +232,24 @@ class SelectedEntities: # Entities that were explicitly mentioned. referenced: set[str] = dataclasses.field(default_factory=set) - # Entities that were referenced via device/area ID. + # Entities that were referenced via device/area/floor ID. # Should not trigger a warning when they don't exist. indirectly_referenced: set[str] = dataclasses.field(default_factory=set) # Referenced items that could not be found. missing_devices: set[str] = dataclasses.field(default_factory=set) missing_areas: set[str] = dataclasses.field(default_factory=set) + missing_floors: set[str] = dataclasses.field(default_factory=set) # Referenced devices referenced_devices: set[str] = dataclasses.field(default_factory=set) + referenced_areas: set[str] = dataclasses.field(default_factory=set) def log_missing(self, missing_entities: set[str]) -> None: """Log about missing items.""" parts = [] for label, items in ( + ("floors", self.missing_floors), ("areas", self.missing_areas), ("devices", self.missing_devices), ("entities", missing_entities), @@ -472,37 +483,49 @@ def async_extract_referenced_entity_ids( selected.referenced.update(entity_ids) - if not selector.device_ids and not selector.area_ids: + if not selector.device_ids and not selector.area_ids and not selector.floor_ids: return selected ent_reg = entity_registry.async_get(hass) dev_reg = device_registry.async_get(hass) area_reg = area_registry.async_get(hass) + floor_reg = floor_registry.async_get(hass) - for device_id in selector.device_ids: - if device_id not in dev_reg.devices: - selected.missing_devices.add(device_id) + for floor_id in selector.floor_ids: + if floor_id not in floor_reg.floors: + selected.missing_floors.add(floor_id) for area_id in selector.area_ids: if area_id not in area_reg.areas: selected.missing_areas.add(area_id) + for device_id in selector.device_ids: + if device_id not in dev_reg.devices: + selected.missing_devices.add(device_id) + + # Find areas for targeted floors + if selector.floor_ids: + for area_entry in area_reg.areas.values(): + if area_entry.id and area_entry.floor_id in selector.floor_ids: + selected.referenced_areas.add(area_entry.id) + # Find devices for targeted areas selected.referenced_devices.update(selector.device_ids) - if selector.area_ids: + selected.referenced_areas.update(selector.area_ids) + if selected.referenced_areas: for device_entry in dev_reg.devices.values(): - if device_entry.area_id in selector.area_ids: + if device_entry.area_id in selected.referenced_areas: selected.referenced_devices.add(device_entry.id) - if not selector.area_ids and not selected.referenced_devices: + if not selected.referenced_areas and not selected.referenced_devices: return selected entities = ent_reg.entities # Add indirectly referenced by area selected.indirectly_referenced.update( entry.entity_id - for area_id in selector.area_ids + for area_id in selected.referenced_areas # The entity's area matches a targeted area for entry in entities.get_entries_for_area_id(area_id) # Do not add entities which are hidden or which are config diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 1a37de217d9..2209e60a37d 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -32,6 +32,7 @@ from homeassistant.core import ( SupportsResponse, ) from homeassistant.helpers import ( + area_registry as ar, device_registry as dr, entity_registry as er, service, @@ -45,6 +46,7 @@ from tests.common import ( MockEntity, MockUser, async_mock_service, + mock_area_registry, mock_device_registry, mock_registry, ) @@ -102,12 +104,38 @@ def mock_entities(hass: HomeAssistant) -> dict[str, MockEntity]: @pytest.fixture -def area_mock(hass): - """Mock including area info.""" +def floor_area_mock(hass: HomeAssistant) -> None: + """Mock including floor and area info.""" hass.states.async_set("light.Bowl", STATE_ON) hass.states.async_set("light.Ceiling", STATE_OFF) hass.states.async_set("light.Kitchen", STATE_OFF) + area_in_floor = ar.AreaEntry( + id="test-area", + name="Test area", + aliases={}, + normalized_name="test-area", + floor_id="test-floor", + icon=None, + picture=None, + ) + area_in_floor_a = ar.AreaEntry( + id="area-a", + name="Area A", + aliases={}, + normalized_name="area-a", + floor_id="floor-a", + icon=None, + picture=None, + ) + mock_area_registry( + hass, + { + area_in_floor.id: area_in_floor, + area_in_floor_a.id: area_in_floor_a, + }, + ) + device_in_area = dr.DeviceEntry(area_id="test-area") device_no_area = dr.DeviceEntry(id="device-no-area-id") device_diff_area = dr.DeviceEntry(area_id="diff-area") @@ -264,7 +292,11 @@ async def test_service_call(hass: HomeAssistant) -> None: "effect": {"value": "{{ 'complex' }}", "simple": "simple"}, }, "data_template": {"list": ["{{ 'list' }}", "2"]}, - "target": {"area_id": "test-area-id", "entity_id": "will.be_overridden"}, + "target": { + "area_id": "test-area-id", + "entity_id": "will.be_overridden", + "floor_id": "test-floor-id", + }, } await service.async_call_from_config(hass, config) @@ -279,6 +311,7 @@ async def test_service_call(hass: HomeAssistant) -> None: "list": ["list", "2"], "entity_id": ["hello.world"], "area_id": ["test-area-id"], + "floor_id": ["test-floor-id"], } config = { @@ -287,6 +320,7 @@ async def test_service_call(hass: HomeAssistant) -> None: "area_id": ["area-42", "{{ 'area-51' }}"], "device_id": ["abcdef", "{{ 'fedcba' }}"], "entity_id": ["light.static", "{{ 'light.dynamic' }}"], + "floor_id": ["floor-first", "{{ 'floor-second' }}"], }, } @@ -297,6 +331,7 @@ async def test_service_call(hass: HomeAssistant) -> None: "area_id": ["area-42", "area-51"], "device_id": ["abcdef", "fedcba"], "entity_id": ["light.static", "light.dynamic"], + "floor_id": ["floor-first", "floor-second"], } config = { @@ -510,7 +545,9 @@ async def test_extract_entity_ids(hass: HomeAssistant) -> None: ) -async def test_extract_entity_ids_from_area(hass: HomeAssistant, area_mock) -> None: +async def test_extract_entity_ids_from_area( + hass: HomeAssistant, floor_area_mock +) -> None: """Test extract_entity_ids method with areas.""" call = ServiceCall("light", "turn_on", {"area_id": "own-area"}) @@ -541,7 +578,9 @@ async def test_extract_entity_ids_from_area(hass: HomeAssistant, area_mock) -> N ) -async def test_extract_entity_ids_from_devices(hass: HomeAssistant, area_mock) -> None: +async def test_extract_entity_ids_from_devices( + hass: HomeAssistant, floor_area_mock +) -> None: """Test extract_entity_ids method with devices.""" assert await service.async_extract_entity_ids( hass, ServiceCall("light", "turn_on", {"device_id": "device-no-area-id"}) @@ -564,6 +603,32 @@ async def test_extract_entity_ids_from_devices(hass: HomeAssistant, area_mock) - ) +@pytest.mark.usefixtures("floor_area_mock") +async def test_extract_entity_ids_from_floor(hass: HomeAssistant) -> None: + """Test extract_entity_ids method with floors.""" + call = ServiceCall("light", "turn_on", {"floor_id": "test-floor"}) + + assert { + "light.in_area", + "light.assigned_to_area", + } == await service.async_extract_entity_ids(hass, call) + + call = ServiceCall("light", "turn_on", {"floor_id": ["test-floor", "floor-a"]}) + + assert { + "light.in_area", + "light.assigned_to_area", + "light.in_area_a", + } == await service.async_extract_entity_ids(hass, call) + + assert ( + await service.async_extract_entity_ids( + hass, ServiceCall("light", "turn_on", {"floor_id": ENTITY_MATCH_NONE}) + ) + == set() + ) + + async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: """Test async_get_all_descriptions.""" group_config = {DOMAIN_GROUP: {}} @@ -1476,7 +1541,9 @@ async def test_extract_from_service_filter_out_non_existing_entities( ] -async def test_extract_from_service_area_id(hass: HomeAssistant, area_mock) -> None: +async def test_extract_from_service_area_id( + hass: HomeAssistant, floor_area_mock +) -> None: """Test the extraction using area ID as reference.""" entities = [ MockEntity(name="in_area", entity_id="light.in_area"), @@ -1522,12 +1589,14 @@ async def test_entity_service_call_warn_referenced( "area_id": "non-existent-area", "entity_id": "non.existent", "device_id": "non-existent-device", + "floor_id": "non-existent-floor", }, ) await service.entity_service_call(hass, {}, "", call) assert ( - "Referenced areas non-existent-area, devices non-existent-device, " - "entities non.existent are missing or not currently available" + "Referenced floors non-existent-floor, areas non-existent-area, " + "devices non-existent-device, entities non.existent are missing " + "or not currently available" ) in caplog.text @@ -1542,13 +1611,15 @@ async def test_async_extract_entities_warn_referenced( "area_id": "non-existent-area", "entity_id": "non.existent", "device_id": "non-existent-device", + "floor_id": "non-existent-floor", }, ) extracted = await service.async_extract_entities(hass, {}, call) assert len(extracted) == 0 assert ( - "Referenced areas non-existent-area, devices non-existent-device, " - "entities non.existent are missing or not currently available" + "Referenced floors non-existent-floor, areas non-existent-area, " + "devices non-existent-device, entities non.existent are missing " + "or not currently available" ) in caplog.text From 7359d66d32ea2785f884bd4251df26333a62e1d1 Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Thu, 14 Mar 2024 19:10:29 +0100 Subject: [PATCH 1048/1691] add switch platform for tolo (#113440) * upgrade tololib dependency to v1.0.0 * add switch to enable/disable aroma therapy * aroma therapy and salt bath switch * Update homeassistant/components/tolo/strings.json Co-authored-by: Joost Lekkerkerker * removed key from specific property list, it's required by default --------- Co-authored-by: Joost Lekkerkerker --- .coveragerc | 1 + homeassistant/components/tolo/__init__.py | 1 + homeassistant/components/tolo/icons.json | 5 ++ homeassistant/components/tolo/strings.json | 8 +++ homeassistant/components/tolo/switch.py | 83 ++++++++++++++++++++++ 5 files changed, 98 insertions(+) create mode 100644 homeassistant/components/tolo/switch.py diff --git a/.coveragerc b/.coveragerc index 54d543dfbf3..aff0979ca1d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1429,6 +1429,7 @@ omit = homeassistant/components/tolo/number.py homeassistant/components/tolo/select.py homeassistant/components/tolo/sensor.py + homeassistant/components/tolo/switch.py homeassistant/components/toon/__init__.py homeassistant/components/toon/binary_sensor.py homeassistant/components/toon/climate.py diff --git a/homeassistant/components/tolo/__init__.py b/homeassistant/components/tolo/__init__.py index 165e5804d61..5fdcdea6c30 100644 --- a/homeassistant/components/tolo/__init__.py +++ b/homeassistant/components/tolo/__init__.py @@ -29,6 +29,7 @@ PLATFORMS = [ Platform.NUMBER, Platform.SELECT, Platform.SENSOR, + Platform.SWITCH, ] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/tolo/icons.json b/homeassistant/components/tolo/icons.json index 011df5788b2..7aeec065b41 100644 --- a/homeassistant/components/tolo/icons.json +++ b/homeassistant/components/tolo/icons.json @@ -42,6 +42,11 @@ "fan_timer_remaining": { "default": "mdi:fan-auto" } + }, + "switch": { + "aroma_therapy_on": { + "default": "mdi:scent" + } } } } diff --git a/homeassistant/components/tolo/strings.json b/homeassistant/components/tolo/strings.json index f48e26c5276..deab432425f 100644 --- a/homeassistant/components/tolo/strings.json +++ b/homeassistant/components/tolo/strings.json @@ -79,6 +79,14 @@ "fan_timer_remaining": { "name": "Fan timer" } + }, + "switch": { + "aroma_therapy_on": { + "name": "Aroma therapy" + }, + "salt_bath_on": { + "name": "Salt bath" + } } } } diff --git a/homeassistant/components/tolo/switch.py b/homeassistant/components/tolo/switch.py new file mode 100644 index 00000000000..b90f548ee76 --- /dev/null +++ b/homeassistant/components/tolo/switch.py @@ -0,0 +1,83 @@ +"""TOLO Sauna switch controls.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from tololib import ToloClient, ToloStatus + +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator +from .const import DOMAIN + + +@dataclass(frozen=True, kw_only=True) +class ToloSwitchEntityDescription(SwitchEntityDescription): + """Class describing TOLO switch entities.""" + + getter: Callable[[ToloStatus], bool] + setter: Callable[[ToloClient, bool], bool] + + +SWITCHES = ( + ToloSwitchEntityDescription( + key="aroma_therapy_on", + translation_key="aroma_therapy_on", + getter=lambda status: status.aroma_therapy_on, + setter=lambda client, value: client.set_aroma_therapy_on(value), + ), + ToloSwitchEntityDescription( + key="salt_bath_on", + translation_key="salt_bath_on", + getter=lambda status: status.salt_bath_on, + setter=lambda client, value: client.set_salt_bath_on(value), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up switch controls for TOLO Sauna.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + ToloSwitchEntity(coordinator, entry, description) for description in SWITCHES + ) + + +class ToloSwitchEntity(ToloSaunaCoordinatorEntity, SwitchEntity): + """TOLO switch entity.""" + + entity_description: ToloSwitchEntityDescription + + def __init__( + self, + coordinator: ToloSaunaUpdateCoordinator, + entry: ConfigEntry, + entity_description: ToloSwitchEntityDescription, + ) -> None: + """Initialize TOLO switch entity.""" + super().__init__(coordinator, entry) + self.entity_description = entity_description + self._attr_unique_id = f"{entry.entry_id}_{entity_description.key}" + + @property + def is_on(self) -> bool: + """Return if the switch is currently on.""" + return self.entity_description.getter(self.coordinator.data.status) + + def turn_on(self, **kwargs: Any) -> None: + """Turn the switch on.""" + self.entity_description.setter(self.coordinator.client, True) + + def turn_off(self, **kwargs: Any) -> None: + """Turn the switch off.""" + self.entity_description.setter(self.coordinator.client, False) From 36123717be1ded7c2c9e12c373b6d8faf81b9355 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 19:10:52 +0100 Subject: [PATCH 1049/1691] Allow entity platforms without entity_component in icon validation (#113422) * Allow entity platforms without entity_component * Rename variable --------- Co-authored-by: jbouwh --- script/hassfest/icons.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/script/hassfest/icons.py b/script/hassfest/icons.py index 17f7d6075ce..61745dba828 100644 --- a/script/hassfest/icons.py +++ b/script/hassfest/icons.py @@ -47,7 +47,7 @@ def ensure_not_same_as_default(value: dict) -> dict: return value -def icon_schema(integration_type: str) -> vol.Schema: +def icon_schema(integration_type: str, no_entity_platforms: bool) -> vol.Schema: """Create a icon schema.""" state_validator = cv.schema_with_slug_keys( @@ -78,7 +78,7 @@ def icon_schema(integration_type: str) -> vol.Schema: ) if integration_type in ("entity", "helper", "system"): - if integration_type != "entity": + if integration_type != "entity" or no_entity_platforms: field = vol.Optional("entity_component") else: field = vol.Required("entity_component") @@ -126,7 +126,9 @@ def validate_icon_file(config: Config, integration: Integration) -> None: # noq integration.add_error("icons", f"Invalid JSON in {name}: {err}") return - schema = icon_schema(integration.integration_type) + no_entity_platforms = name in ("notify", "image_processing") + + schema = icon_schema(integration.integration_type, no_entity_platforms) try: schema(icons) From e68c27ec125d97aa942b8dc7b29496cd660779eb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 19:25:22 +0100 Subject: [PATCH 1050/1691] Add service icons to Fan (#113415) --- homeassistant/components/fan/icons.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/fan/icons.json b/homeassistant/components/fan/icons.json index f962d1e7c1a..60edbce5f01 100644 --- a/homeassistant/components/fan/icons.json +++ b/homeassistant/components/fan/icons.json @@ -23,6 +23,7 @@ "decrease_speed": "mdi:fan-minus", "increase_speed": "mdi:fan-plus", "oscillate": "mdi:arrow-oscillating", + "set_direction": "mdi:rotate-3d-variant", "set_percentage": "mdi:fan", "set_preset_mode": "mdi:fan-auto", "toggle": "mdi:fan", From 87767a58fc65d806449ab7dcbae94af3266fa50a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 19:25:33 +0100 Subject: [PATCH 1051/1691] Fix alarm control panel icons (#113413) --- homeassistant/components/alarm_control_panel/icons.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/icons.json b/homeassistant/components/alarm_control_panel/icons.json index 62a9eee2915..915448a9962 100644 --- a/homeassistant/components/alarm_control_panel/icons.json +++ b/homeassistant/components/alarm_control_panel/icons.json @@ -18,9 +18,9 @@ "alarm_arm_away": "mdi:shield-lock", "alarm_arm_home": "mdi:shield-home", "alarm_arm_night": "mdi:shield-moon", - "alarm_custom_bypass": "mdi:security", + "alarm_arm_custom_bypass": "mdi:security", "alarm_disarm": "mdi:shield-off", "alarm_trigger": "mdi:bell-ring", - "arlam_arm_vacation": "mdi:shield-airplane" + "alarm_arm_vacation": "mdi:shield-airplane" } } From 5c59b4d8462d07f9477cbf440b4d6474929ccdd4 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 19:26:01 +0100 Subject: [PATCH 1052/1691] Add service icons to Abode (#113411) --- homeassistant/components/abode/icons.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/abode/icons.json b/homeassistant/components/abode/icons.json index 89cee031818..00175628d9a 100644 --- a/homeassistant/components/abode/icons.json +++ b/homeassistant/components/abode/icons.json @@ -5,5 +5,10 @@ "default": "mdi:robot" } } + }, + "services": { + "capture_image": "mdi:camera", + "change_setting": "mdi:cog", + "trigger_automation": "mdi:play" } } From 2b3f0a9459bf05c67ba56c9c13a6648d1dc98417 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 19:28:54 +0100 Subject: [PATCH 1053/1691] Add icon translations to ZHA (#112368) --- homeassistant/components/zha/binary_sensor.py | 1 - homeassistant/components/zha/icons.json | 178 ++++++++++++++++++ homeassistant/components/zha/number.py | 27 --- homeassistant/components/zha/select.py | 2 - homeassistant/components/zha/sensor.py | 11 -- homeassistant/components/zha/switch.py | 10 - 6 files changed, 178 insertions(+), 51 deletions(-) create mode 100644 homeassistant/components/zha/icons.json diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index a3348ad974d..195b0a1dcf5 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -327,5 +327,4 @@ class AqaraE1CurtainMotorOpenedByHandBinarySensor(BinarySensor): _unique_id_suffix = "hand_open" _attribute_name = "hand_open" _attr_translation_key = "hand_open" - _attr_icon = "mdi:hand-wave" _attr_entity_category = EntityCategory.DIAGNOSTIC diff --git a/homeassistant/components/zha/icons.json b/homeassistant/components/zha/icons.json new file mode 100644 index 00000000000..9b060e8105a --- /dev/null +++ b/homeassistant/components/zha/icons.json @@ -0,0 +1,178 @@ +{ + "entity": { + "binary_sensor": { + "hand_open": { + "default": "mdi:hand-wave" + } + }, + "number": { + "timer_duration": { + "default": "mdi:timer" + }, + "filter_life_time": { + "default": "mdi:timer" + }, + "dimming_speed_up_remote": { + "default": "mdi:speedometer" + }, + "button_delay": { + "default": "mdi:speedometer" + }, + "dimming_speed_up_local": { + "default": "mdi:speedometer" + }, + "ramp_rate_off_to_on_local": { + "default": "mdi:speedometer" + }, + "ramp_rate_off_to_on_remote": { + "default": "mdi:speedometer" + }, + "dimming_speed_down_remote": { + "default": "mdi:speedometer" + }, + "dimming_speed_down_local": { + "default": "mdi:speedometer" + }, + "ramp_rate_on_to_off_local": { + "default": "mdi:speedometer" + }, + "ramp_rate_on_to_off_remote": { + "default": "mdi:speedometer" + }, + "minimum_level": { + "default": "mdi:brightness-percent" + }, + "maximum_level": { + "default": "mdi:brightness-percent" + }, + "auto_off_timer": { + "default": "mdi:timer" + }, + "quick_start_time": { + "default": "mdi:speedometer" + }, + "load_level_indicator_timeout": { + "default": "mdi:timer" + }, + "led_color_when_on": { + "default": "mdi:palette" + }, + "led_color_when_off": { + "default": "mdi:palette" + }, + "led_intensity_when_on": { + "default": "mdi:brightness-percent" + }, + "led_intensity_when_off": { + "default": "mdi:brightness-percent" + }, + "double_tap_up_level": { + "default": "mdi:brightness-percent" + }, + "double_tap_down_level": { + "default": "mdi:brightness-percent" + }, + "serving_size": { + "default": "mdi:counter" + }, + "portion_weight": { + "default": "mdi:weight-gram" + }, + "away_preset_temperature": { + "default": "mdi:temperature-celsius" + }, + "local_temperature_calibration": { + "default": "mdi:temperature-celsius" + }, + "presence_detection_timeout": { + "default": "mdi:timer-edit" + } + }, + "select": { + "feeding_mode": { + "default": "mdi:wrench-clock" + }, + "keypad_lockout": { + "default": "mdi:lock" + } + }, + "sensor": { + "timer_time_left": { + "default": "mdi:timer" + }, + "device_run_time": { + "default": "mdi:timer" + }, + "filter_run_time": { + "default": "mdi:timer" + }, + "last_feeding_source": { + "default": "mdi:devices" + }, + "last_feeding_size": { + "default": "mdi:counter" + }, + "portions_dispensed_today": { + "default": "mdi:counter" + }, + "weight_dispensed_today": { + "default": "mdi:weight-gram" + }, + "smoke_density": { + "default": "mdi:google-circles-communities" + }, + "last_illumination_state": { + "default": "mdi:theme-light-dark" + }, + "pi_heating_demand": { + "default": "mdi:radiator" + }, + "setpoint_change_source": { + "default": "mdi:thermostat" + }, + "hooks_state": { + "default": "mdi:hook" + } + }, + "switch": { + "led_indicator": { + "default": "mdi:led-on" + }, + "child_lock": { + "default": "mdi:account-lock" + }, + "heartbeat_indicator": { + "default": "mdi:heart-flash" + }, + "linkage_alarm": { + "default": "mdi:shield-link-variant" + }, + "buzzer_manual_mute": { + "default": "mdi:volume-off" + }, + "buzzer_manual_alarm": { + "default": "mdi:bullhorn" + }, + "inverted": { + "default": "mdi:arrow-up-down" + }, + "hooks_locked": { + "default": "mdi:lock" + } + } + }, + "services": { + "permit": "mdi:cellphone-link", + "remove": "mdi:cellphone-remove", + "reconfigure_device": "mdi:cellphone-cog", + "set_zigbee_cluster_attribute": "mdi:cog", + "issue_zigbee_cluster_command": "mdi:console", + "issue_zigbee_group_command": "mdi:console", + "warning_device_squawk": "mdi:alert", + "warning_device_warn": "mdi:alert", + "clear_lock_user_code": "mdi:lock-remove", + "enable_lock_user_code": "mdi:lock", + "disable_lock_user_code": "mdi:lock-off", + "set_lock_user_code": "mdi:lock" + } +} diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index ef8f8287e1e..3ae261cb572 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -597,7 +597,6 @@ class TimerDurationMinutes(ZHANumberConfigurationEntity): _unique_id_suffix = "timer_duration" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[14] _attr_native_min_value: float = 0x00 _attr_native_max_value: float = 0x257 _attr_native_unit_of_measurement: str | None = UNITS[72] @@ -612,7 +611,6 @@ class FilterLifeTime(ZHANumberConfigurationEntity): _unique_id_suffix = "filter_life_time" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[14] _attr_native_min_value: float = 0x00 _attr_native_max_value: float = 0xFFFFFFFF _attr_native_unit_of_measurement: str | None = UNITS[72] @@ -643,7 +641,6 @@ class InovelliRemoteDimmingUpSpeed(ZHANumberConfigurationEntity): _unique_id_suffix = "dimming_speed_up_remote" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 126 _attribute_name = "dimming_speed_up_remote" @@ -657,7 +654,6 @@ class InovelliButtonDelay(ZHANumberConfigurationEntity): _unique_id_suffix = "button_delay" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 9 _attribute_name = "button_delay" @@ -671,7 +667,6 @@ class InovelliLocalDimmingUpSpeed(ZHANumberConfigurationEntity): _unique_id_suffix = "dimming_speed_up_local" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "dimming_speed_up_local" @@ -685,7 +680,6 @@ class InovelliLocalRampRateOffToOn(ZHANumberConfigurationEntity): _unique_id_suffix = "ramp_rate_off_to_on_local" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "ramp_rate_off_to_on_local" @@ -699,7 +693,6 @@ class InovelliRemoteDimmingSpeedOffToOn(ZHANumberConfigurationEntity): _unique_id_suffix = "ramp_rate_off_to_on_remote" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "ramp_rate_off_to_on_remote" @@ -713,7 +706,6 @@ class InovelliRemoteDimmingDownSpeed(ZHANumberConfigurationEntity): _unique_id_suffix = "dimming_speed_down_remote" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "dimming_speed_down_remote" @@ -727,7 +719,6 @@ class InovelliLocalDimmingDownSpeed(ZHANumberConfigurationEntity): _unique_id_suffix = "dimming_speed_down_local" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "dimming_speed_down_local" @@ -741,7 +732,6 @@ class InovelliLocalRampRateOnToOff(ZHANumberConfigurationEntity): _unique_id_suffix = "ramp_rate_on_to_off_local" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "ramp_rate_on_to_off_local" @@ -755,7 +745,6 @@ class InovelliRemoteDimmingSpeedOnToOff(ZHANumberConfigurationEntity): _unique_id_suffix = "ramp_rate_on_to_off_remote" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 127 _attribute_name = "ramp_rate_on_to_off_remote" @@ -769,7 +758,6 @@ class InovelliMinimumLoadDimmingLevel(ZHANumberConfigurationEntity): _unique_id_suffix = "minimum_level" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[16] _attr_native_min_value: float = 1 _attr_native_max_value: float = 254 _attribute_name = "minimum_level" @@ -783,7 +771,6 @@ class InovelliMaximumLoadDimmingLevel(ZHANumberConfigurationEntity): _unique_id_suffix = "maximum_level" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[16] _attr_native_min_value: float = 2 _attr_native_max_value: float = 255 _attribute_name = "maximum_level" @@ -797,7 +784,6 @@ class InovelliAutoShutoffTimer(ZHANumberConfigurationEntity): _unique_id_suffix = "auto_off_timer" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[14] _attr_native_min_value: float = 0 _attr_native_max_value: float = 32767 _attribute_name = "auto_off_timer" @@ -813,7 +799,6 @@ class InovelliQuickStartTime(ZHANumberConfigurationEntity): _unique_id_suffix = "quick_start_time" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[3] _attr_native_min_value: float = 0 _attr_native_max_value: float = 10 _attribute_name = "quick_start_time" @@ -827,7 +812,6 @@ class InovelliLoadLevelIndicatorTimeout(ZHANumberConfigurationEntity): _unique_id_suffix = "load_level_indicator_timeout" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[14] _attr_native_min_value: float = 0 _attr_native_max_value: float = 11 _attribute_name = "load_level_indicator_timeout" @@ -841,7 +825,6 @@ class InovelliDefaultAllLEDOnColor(ZHANumberConfigurationEntity): _unique_id_suffix = "led_color_when_on" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[15] _attr_native_min_value: float = 0 _attr_native_max_value: float = 255 _attribute_name = "led_color_when_on" @@ -855,7 +838,6 @@ class InovelliDefaultAllLEDOffColor(ZHANumberConfigurationEntity): _unique_id_suffix = "led_color_when_off" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[15] _attr_native_min_value: float = 0 _attr_native_max_value: float = 255 _attribute_name = "led_color_when_off" @@ -869,7 +851,6 @@ class InovelliDefaultAllLEDOnIntensity(ZHANumberConfigurationEntity): _unique_id_suffix = "led_intensity_when_on" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[16] _attr_native_min_value: float = 0 _attr_native_max_value: float = 100 _attribute_name = "led_intensity_when_on" @@ -883,7 +864,6 @@ class InovelliDefaultAllLEDOffIntensity(ZHANumberConfigurationEntity): _unique_id_suffix = "led_intensity_when_off" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[16] _attr_native_min_value: float = 0 _attr_native_max_value: float = 100 _attribute_name = "led_intensity_when_off" @@ -897,7 +877,6 @@ class InovelliDoubleTapUpLevel(ZHANumberConfigurationEntity): _unique_id_suffix = "double_tap_up_level" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[16] _attr_native_min_value: float = 2 _attr_native_max_value: float = 254 _attribute_name = "double_tap_up_level" @@ -911,7 +890,6 @@ class InovelliDoubleTapDownLevel(ZHANumberConfigurationEntity): _unique_id_suffix = "double_tap_down_level" _attr_entity_category = EntityCategory.CONFIG - _attr_icon: str = ICONS[16] _attr_native_min_value: float = 0 _attr_native_max_value: float = 254 _attribute_name = "double_tap_down_level" @@ -933,7 +911,6 @@ class AqaraPetFeederServingSize(ZHANumberConfigurationEntity): _attr_translation_key: str = "serving_size" _attr_mode: NumberMode = NumberMode.BOX - _attr_icon: str = "mdi:counter" @CONFIG_DIAGNOSTIC_MATCH( @@ -952,7 +929,6 @@ class AqaraPetFeederPortionWeight(ZHANumberConfigurationEntity): _attr_mode: NumberMode = NumberMode.BOX _attr_native_unit_of_measurement: str = UnitOfMass.GRAMS - _attr_icon: str = "mdi:weight-gram" @CONFIG_DIAGNOSTIC_MATCH( @@ -972,7 +948,6 @@ class AqaraThermostatAwayTemp(ZHANumberConfigurationEntity): _attr_mode: NumberMode = NumberMode.SLIDER _attr_native_unit_of_measurement: str = UnitOfTemperature.CELSIUS - _attr_icon: str = ICONS[0] @CONFIG_DIAGNOSTIC_MATCH( @@ -993,7 +968,6 @@ class ThermostatLocalTempCalibration(ZHANumberConfigurationEntity): _attr_mode: NumberMode = NumberMode.SLIDER _attr_native_unit_of_measurement: str = UnitOfTemperature.CELSIUS - _attr_icon: str = ICONS[0] @CONFIG_DIAGNOSTIC_MATCH( @@ -1025,7 +999,6 @@ class SonoffPresenceSenorTimeout(ZHANumberConfigurationEntity): _attr_translation_key: str = "presence_detection_timeout" _attr_mode: NumberMode = NumberMode.BOX - _attr_icon: str = "mdi:timer-edit" # pylint: disable-next=hass-invalid-inheritance # needs fixing diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index c629a16a7c6..c3c62e6173d 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -625,7 +625,6 @@ class AqaraPetFeederMode(ZCLEnumSelectEntity): _attribute_name = "feeding_mode" _enum = AqaraFeedingMode _attr_translation_key: str = "feeding_mode" - _attr_icon: str = "mdi:wrench-clock" class AqaraThermostatPresetMode(types.enum8): @@ -690,4 +689,3 @@ class KeypadLockout(ZCLEnumSelectEntity): _attribute_name: str = "keypad_lockout" _enum = KeypadLockoutEnum _attr_translation_key: str = "keypad_lockout" - _attr_icon: str = "mdi:lock" diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 1bb6b355f6e..1a11cd99593 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -1280,7 +1280,6 @@ class TimeLeft(Sensor): _attribute_name = "timer_time_left" _unique_id_suffix = "time_left" _attr_device_class: SensorDeviceClass = SensorDeviceClass.DURATION - _attr_icon = "mdi:timer" _attr_translation_key: str = "timer_time_left" _attr_native_unit_of_measurement = UnitOfTime.MINUTES @@ -1293,7 +1292,6 @@ class IkeaDeviceRunTime(Sensor): _attribute_name = "device_run_time" _unique_id_suffix = "device_run_time" _attr_device_class: SensorDeviceClass = SensorDeviceClass.DURATION - _attr_icon = "mdi:timer" _attr_translation_key: str = "device_run_time" _attr_native_unit_of_measurement = UnitOfTime.MINUTES _attr_entity_category: EntityCategory = EntityCategory.DIAGNOSTIC @@ -1307,7 +1305,6 @@ class IkeaFilterRunTime(Sensor): _attribute_name = "filter_run_time" _unique_id_suffix = "filter_run_time" _attr_device_class: SensorDeviceClass = SensorDeviceClass.DURATION - _attr_icon = "mdi:timer" _attr_translation_key: str = "filter_run_time" _attr_native_unit_of_measurement = UnitOfTime.MINUTES _attr_entity_category: EntityCategory = EntityCategory.DIAGNOSTIC @@ -1328,7 +1325,6 @@ class AqaraPetFeederLastFeedingSource(EnumSensor): _attribute_name = "last_feeding_source" _unique_id_suffix = "last_feeding_source" _attr_translation_key: str = "last_feeding_source" - _attr_icon = "mdi:devices" _enum = AqaraFeedingSource @@ -1340,7 +1336,6 @@ class AqaraPetFeederLastFeedingSize(Sensor): _attribute_name = "last_feeding_size" _unique_id_suffix = "last_feeding_size" _attr_translation_key: str = "last_feeding_size" - _attr_icon: str = "mdi:counter" @MULTI_MATCH(cluster_handler_names="opple_cluster", models={"aqara.feeder.acn001"}) @@ -1352,7 +1347,6 @@ class AqaraPetFeederPortionsDispensed(Sensor): _unique_id_suffix = "portions_dispensed" _attr_translation_key: str = "portions_dispensed_today" _attr_state_class: SensorStateClass = SensorStateClass.TOTAL_INCREASING - _attr_icon: str = "mdi:counter" @MULTI_MATCH(cluster_handler_names="opple_cluster", models={"aqara.feeder.acn001"}) @@ -1365,7 +1359,6 @@ class AqaraPetFeederWeightDispensed(Sensor): _attr_translation_key: str = "weight_dispensed_today" _attr_native_unit_of_measurement = UnitOfMass.GRAMS _attr_state_class: SensorStateClass = SensorStateClass.TOTAL_INCREASING - _attr_icon: str = "mdi:weight-gram" @MULTI_MATCH(cluster_handler_names="opple_cluster", models={"lumi.sensor_smoke.acn03"}) @@ -1378,7 +1371,6 @@ class AqaraSmokeDensityDbm(Sensor): _attr_translation_key: str = "smoke_density" _attr_native_unit_of_measurement = "dB/m" _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT - _attr_icon: str = "mdi:google-circles-communities" _attr_suggested_display_precision: int = 3 @@ -1397,7 +1389,6 @@ class SonoffPresenceSenorIlluminationStatus(EnumSensor): _attribute_name = "last_illumination_state" _unique_id_suffix = "last_illumination" _attr_translation_key: str = "last_illumination_state" - _attr_icon: str = "mdi:theme-light-dark" _enum = SonoffIlluminationStates @@ -1412,7 +1403,6 @@ class PiHeatingDemand(Sensor): _unique_id_suffix = "pi_heating_demand" _attribute_name = "pi_heating_demand" _attr_translation_key: str = "pi_heating_demand" - _attr_icon: str = "mdi:radiator" _attr_native_unit_of_measurement = PERCENTAGE _decimals = 0 _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT @@ -1438,7 +1428,6 @@ class SetpointChangeSource(EnumSensor): _unique_id_suffix = "setpoint_change_source" _attribute_name = "setpoint_change_source" _attr_translation_key: str = "setpoint_change_source" - _attr_icon: str = "mdi:thermostat" _attr_entity_category = EntityCategory.DIAGNOSTIC _enum = SetpointChangeSourceEnum diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 726b73c4675..0561efbb2f2 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -513,7 +513,6 @@ class AqaraPetFeederLEDIndicator(ZHASwitchConfigurationEntity): _attribute_name = "disable_led_indicator" _attr_translation_key = "led_indicator" _force_inverted = True - _attr_icon: str = "mdi:led-on" @CONFIG_DIAGNOSTIC_MATCH( @@ -525,7 +524,6 @@ class AqaraPetFeederChildLock(ZHASwitchConfigurationEntity): _unique_id_suffix = "child_lock" _attribute_name = "child_lock" _attr_translation_key = "child_lock" - _attr_icon: str = "mdi:account-lock" @CONFIG_DIAGNOSTIC_MATCH( @@ -537,7 +535,6 @@ class TuyaChildLockSwitch(ZHASwitchConfigurationEntity): _unique_id_suffix = "child_lock" _attribute_name = "child_lock" _attr_translation_key = "child_lock" - _attr_icon: str = "mdi:account-lock" @CONFIG_DIAGNOSTIC_MATCH( @@ -571,7 +568,6 @@ class AqaraThermostatChildLock(ZHASwitchConfigurationEntity): _unique_id_suffix = "child_lock" _attribute_name = "child_lock" _attr_translation_key = "child_lock" - _attr_icon: str = "mdi:account-lock" @CONFIG_DIAGNOSTIC_MATCH( @@ -583,7 +579,6 @@ class AqaraHeartbeatIndicator(ZHASwitchConfigurationEntity): _unique_id_suffix = "heartbeat_indicator" _attribute_name = "heartbeat_indicator" _attr_translation_key = "heartbeat_indicator" - _attr_icon: str = "mdi:heart-flash" @CONFIG_DIAGNOSTIC_MATCH( @@ -595,7 +590,6 @@ class AqaraLinkageAlarm(ZHASwitchConfigurationEntity): _unique_id_suffix = "linkage_alarm" _attribute_name = "linkage_alarm" _attr_translation_key = "linkage_alarm" - _attr_icon: str = "mdi:shield-link-variant" @CONFIG_DIAGNOSTIC_MATCH( @@ -607,7 +601,6 @@ class AqaraBuzzerManualMute(ZHASwitchConfigurationEntity): _unique_id_suffix = "buzzer_manual_mute" _attribute_name = "buzzer_manual_mute" _attr_translation_key = "buzzer_manual_mute" - _attr_icon: str = "mdi:volume-off" @CONFIG_DIAGNOSTIC_MATCH( @@ -619,7 +612,6 @@ class AqaraBuzzerManualAlarm(ZHASwitchConfigurationEntity): _unique_id_suffix = "buzzer_manual_alarm" _attribute_name = "buzzer_manual_alarm" _attr_translation_key = "buzzer_manual_alarm" - _attr_icon: str = "mdi:bullhorn" @CONFIG_DIAGNOSTIC_MATCH(cluster_handler_names=CLUSTER_HANDLER_COVER) @@ -632,7 +624,6 @@ class WindowCoveringInversionSwitch(ZHASwitchConfigurationEntity): _unique_id_suffix = "inverted" _attribute_name = WindowCovering.AttributeDefs.config_status.name _attr_translation_key = "inverted" - _attr_icon: str = "mdi:arrow-up-down" @classmethod def create_entity( @@ -726,4 +717,3 @@ class AqaraE1CurtainMotorHooksLockedSwitch(ZHASwitchConfigurationEntity): _unique_id_suffix = "hooks_lock" _attribute_name = "hooks_lock" _attr_translation_key = "hooks_locked" - _attr_icon: str = "mdi:lock" From aaac879c839420a08b774f3954043b2545e8c8b0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 08:34:44 -1000 Subject: [PATCH 1054/1691] Fix calling sync api in counter/ffmpeg/device_tracker tests (#113441) --- tests/components/counter/common.py | 6 +++--- tests/components/device_tracker/common.py | 2 +- tests/components/ffmpeg/test_init.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/counter/common.py b/tests/components/counter/common.py index 72d183a7b0f..b5156c1a432 100644 --- a/tests/components/counter/common.py +++ b/tests/components/counter/common.py @@ -19,7 +19,7 @@ from homeassistant.loader import bind_hass @bind_hass def async_increment(hass, entity_id): """Increment a counter.""" - hass.create_task( + hass.async_create_task( hass.services.async_call(DOMAIN, SERVICE_INCREMENT, {ATTR_ENTITY_ID: entity_id}) ) @@ -28,7 +28,7 @@ def async_increment(hass, entity_id): @bind_hass def async_decrement(hass, entity_id): """Decrement a counter.""" - hass.create_task( + hass.async_create_task( hass.services.async_call(DOMAIN, SERVICE_DECREMENT, {ATTR_ENTITY_ID: entity_id}) ) @@ -37,6 +37,6 @@ def async_decrement(hass, entity_id): @bind_hass def async_reset(hass, entity_id): """Reset a counter.""" - hass.create_task( + hass.async_create_task( hass.services.async_call(DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id}) ) diff --git a/tests/components/device_tracker/common.py b/tests/components/device_tracker/common.py index 6e2c7b253c3..973eb7d8820 100644 --- a/tests/components/device_tracker/common.py +++ b/tests/components/device_tracker/common.py @@ -50,4 +50,4 @@ def async_see( } if attributes: data[ATTR_ATTRIBUTES] = attributes - hass.create_task(hass.services.async_call(DOMAIN, SERVICE_SEE, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_SEE, data)) diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index e945a26e05b..23d00d7e46b 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -27,7 +27,7 @@ def async_start(hass, entity_id=None): This is a legacy helper method. Do not use it for new tests. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.create_task(hass.services.async_call(DOMAIN, SERVICE_START, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_START, data)) @callback @@ -37,7 +37,7 @@ def async_stop(hass, entity_id=None): This is a legacy helper method. Do not use it for new tests. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.create_task(hass.services.async_call(DOMAIN, SERVICE_STOP, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_STOP, data)) @callback @@ -47,7 +47,7 @@ def async_restart(hass, entity_id=None): This is a legacy helper method. Do not use it for new tests. """ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} - hass.create_task(hass.services.async_call(DOMAIN, SERVICE_RESTART, data)) + hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_RESTART, data)) class MockFFmpegDev(ffmpeg.FFmpegBase): From 5512e8b7891eeaf2d61d7d6ebc840b6c187d65fb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 09:06:55 -1000 Subject: [PATCH 1055/1691] Deprecate async_run_job and async_add_job (#113260) --- homeassistant/core.py | 22 ++++++++++++++++++++++ tests/test_core.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/homeassistant/core.py b/homeassistant/core.py index ae02961c6a4..629206e75f1 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -583,6 +583,17 @@ class HomeAssistant: target: target to call. args: parameters for method to call. """ + # late import to avoid circular imports + from .helpers import frame # pylint: disable=import-outside-toplevel + + frame.report( + "calls `async_add_job`, which is deprecated and will be removed in Home " + "Assistant 2025.4; Please review " + "https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job" + " for replacement options", + error_if_core=False, + ) + if target is None: raise ValueError("Don't call async_add_job with None") @@ -844,6 +855,17 @@ class HomeAssistant: target: target to call. args: parameters for method to call. """ + # late import to avoid circular imports + from .helpers import frame # pylint: disable=import-outside-toplevel + + frame.report( + "calls `async_run_job`, which is deprecated and will be removed in Home " + "Assistant 2025.4; Please review " + "https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job" + " for replacement options", + error_if_core=False, + ) + if asyncio.iscoroutine(target): return self.async_create_task(target, eager_start=True) diff --git a/tests/test_core.py b/tests/test_core.py index d30ccdd018b..abebcef7121 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3119,3 +3119,37 @@ async def test_async_add_import_executor_job(hass: HomeAssistant) -> None: assert await future is evt assert hass.import_executor._max_workers == 1 + + +async def test_async_run_job_deprecated( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test async_run_job warns about its deprecation.""" + + async def _test(): + pass + + hass.async_run_job(_test) + assert ( + "Detected code that calls `async_run_job`, which is deprecated " + "and will be removed in Home Assistant 2025.4; Please review " + "https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job" + " for replacement options" + ) in caplog.text + + +async def test_async_add_job_deprecated( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test async_add_job warns about its deprecation.""" + + async def _test(): + pass + + hass.async_add_job(_test) + assert ( + "Detected code that calls `async_add_job`, which is deprecated " + "and will be removed in Home Assistant 2025.4; Please review " + "https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job" + " for replacement options" + ) in caplog.text From 1ada10299ae5a935869fa37ebe5980a91d4db0ab Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 14 Mar 2024 15:07:54 -0400 Subject: [PATCH 1056/1691] Check for EA release channel for UniFi Protect (#113432) Co-authored-by: J. Nick Koston --- homeassistant/components/unifiprotect/__init__.py | 13 +++++++------ homeassistant/components/unifiprotect/const.py | 2 +- homeassistant/components/unifiprotect/repairs.py | 5 +++-- .../components/unifiprotect/strings.json | 12 ++++++------ tests/components/unifiprotect/test_config_flow.py | 2 +- tests/components/unifiprotect/test_repairs.py | 15 ++++++++++----- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 45d80b85c58..15784dbdeaf 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -7,6 +7,7 @@ import logging from aiohttp.client_exceptions import ServerDisconnectedError from pyunifiprotect.data import Bootstrap +from pyunifiprotect.data.types import FirmwareReleaseChannel from pyunifiprotect.exceptions import ClientError, NotAuthorized # Import the test_util.anonymize module from the pyunifiprotect package @@ -112,19 +113,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop) ) - if ( - not entry.options.get(CONF_ALLOW_EA, False) - and await nvr_info.get_is_prerelease() + if not entry.options.get(CONF_ALLOW_EA, False) and ( + await nvr_info.get_is_prerelease() + or nvr_info.release_channel != FirmwareReleaseChannel.RELEASE ): ir.async_create_issue( hass, DOMAIN, - "ea_warning", + "ea_channel_warning", is_fixable=True, is_persistent=True, learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access", severity=IssueSeverity.WARNING, - translation_key="ea_warning", + translation_key="ea_channel_warning", translation_placeholders={"version": str(nvr_info.version)}, data={"entry_id": entry.entry_id}, ) @@ -150,7 +151,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "version": str(nvr_info.version), }, ) - ir.async_delete_issue(hass, DOMAIN, "ea_warning") + ir.async_delete_issue(hass, DOMAIN, "ea_channel_warning") _LOGGER.exception("Error setting up UniFi Protect integration: %s", err) raise diff --git a/homeassistant/components/unifiprotect/const.py b/homeassistant/components/unifiprotect/const.py index 2982ca29c4a..39be5f0e7cb 100644 --- a/homeassistant/components/unifiprotect/const.py +++ b/homeassistant/components/unifiprotect/const.py @@ -24,7 +24,7 @@ CONF_DISABLE_RTSP = "disable_rtsp" CONF_ALL_UPDATES = "all_updates" CONF_OVERRIDE_CHOST = "override_connection_host" CONF_MAX_MEDIA = "max_media" -CONF_ALLOW_EA = "allow_ea" +CONF_ALLOW_EA = "allow_ea_channel" CONFIG_OPTIONS = [ CONF_ALL_UPDATES, diff --git a/homeassistant/components/unifiprotect/repairs.py b/homeassistant/components/unifiprotect/repairs.py index ddc0a257c14..254984da515 100644 --- a/homeassistant/components/unifiprotect/repairs.py +++ b/homeassistant/components/unifiprotect/repairs.py @@ -6,6 +6,7 @@ import logging from typing import cast from pyunifiprotect import ProtectApiClient +from pyunifiprotect.data.types import FirmwareReleaseChannel import voluptuous as vol from homeassistant import data_entry_flow @@ -68,7 +69,7 @@ class EAConfirm(ProtectRepair): ) nvr = await self._api.get_nvr() - if await nvr.get_is_prerelease(): + if nvr.release_channel != FirmwareReleaseChannel.RELEASE: return await self.async_step_confirm() await self.hass.config_entries.async_reload(self._entry.entry_id) return self.async_create_entry(data={}) @@ -124,7 +125,7 @@ async def async_create_fix_flow( data: dict[str, str | int | float | None] | None, ) -> RepairsFlow: """Create flow.""" - if data is not None and issue_id == "ea_warning": + if data is not None and issue_id == "ea_channel_warning": entry_id = cast(str, data["entry_id"]) if (entry := hass.config_entries.async_get_entry(entry_id)) is not None: api = async_create_api_client(hass, entry) diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index 97e68388dd9..b783bdf1a2c 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -61,16 +61,16 @@ } }, "issues": { - "ea_warning": { - "title": "UniFi Protect v{version} is an Early Access version", + "ea_channel_warning": { + "title": "UniFi Protect Early Access enabled", "fix_flow": { "step": { "start": { - "title": "v{version} is an Early Access version", - "description": "You are using v{version} of UniFi Protect which is an Early Access version. [Early Access versions are not supported by Home Assistant](https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access) and it is recommended to go back to a stable release as soon as possible.\n\nBy submitting this form you have either [downgraded UniFi Protect](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) or you agree to run an unsupported version of UniFi Protect." + "title": "UniFi Protect Early Access enabled", + "description": "You are either running an Early Access version of UniFi Protect (v{version}) or opt-ed into a release channel that is not the Official Release Channel. [Home Assistant does not support Early Access versions](https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access), so you should immediately switch to the Official Release Channel. Accidentally upgrading to an Early Access version can break your UniFi Protect integration.\n\nBy submitting this form, you have switched back to the Official Release Channel or agree to run an unsupported version of UniFi Protect, which may break your Home Assistant integration at any time." }, "confirm": { - "title": "[%key:component::unifiprotect::issues::ea_warning::fix_flow::step::start::title%]", + "title": "[%key:component::unifiprotect::issues::ea_channel_warning::fix_flow::step::start::title%]", "description": "Are you sure you want to run unsupported versions of UniFi Protect? This may cause your Home Assistant integration to break." } } @@ -78,7 +78,7 @@ }, "ea_setup_failed": { "title": "Setup error using Early Access version", - "description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}" + "description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please restore a backup of a stable release of UniFi Protect to continue using the integration.\n\nError: {error}" }, "cloud_user": { "title": "Ubiquiti Cloud Users are not Supported", diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index a5fbdb8493f..f4ff4752baf 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -328,7 +328,7 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) - "disable_rtsp": True, "override_connection_host": True, "max_media": 1000, - "allow_ea": False, + "allow_ea_channel": False, } await hass.config_entries.async_unload(mock_config.entry_id) diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index 41307029a0f..b75b025be11 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -46,12 +46,14 @@ async def test_ea_warning_ignore( assert len(msg["result"]["issues"]) > 0 issue = None for i in msg["result"]["issues"]: - if i["issue_id"] == "ea_warning": + if i["issue_id"] == "ea_channel_warning": issue = i assert issue is not None url = RepairsFlowIndexView.url - resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "ea_warning"}) + resp = await client.post( + url, json={"handler": DOMAIN, "issue_id": "ea_channel_warning"} + ) assert resp.status == HTTPStatus.OK data = await resp.json() @@ -104,12 +106,14 @@ async def test_ea_warning_fix( assert len(msg["result"]["issues"]) > 0 issue = None for i in msg["result"]["issues"]: - if i["issue_id"] == "ea_warning": + if i["issue_id"] == "ea_channel_warning": issue = i assert issue is not None url = RepairsFlowIndexView.url - resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "ea_warning"}) + resp = await client.post( + url, json={"handler": DOMAIN, "issue_id": "ea_channel_warning"} + ) assert resp.status == HTTPStatus.OK data = await resp.json() @@ -122,8 +126,9 @@ async def test_ea_warning_fix( new_nvr = copy(ufp.api.bootstrap.nvr) new_nvr.version = Version("2.2.6") + new_nvr.release_channel = "release" mock_msg = Mock() - mock_msg.changed_data = {"version": "2.2.6"} + mock_msg.changed_data = {"version": "2.2.6", "releaseChannel": "release"} mock_msg.new_obj = new_nvr ufp.api.bootstrap.nvr = new_nvr From 3f2a51bcff733473d91c2f5b976349461a0c69c2 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 20:45:21 +0100 Subject: [PATCH 1057/1691] Add service icons to Climate (#113409) --- homeassistant/components/climate/icons.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/climate/icons.json b/homeassistant/components/climate/icons.json index 5afce637ed5..91a306f1da4 100644 --- a/homeassistant/components/climate/icons.json +++ b/homeassistant/components/climate/icons.json @@ -58,6 +58,12 @@ "set_fan_mode": "mdi:fan", "set_humidity": "mdi:water-percent", "set_swing_mode": "mdi:arrow-oscillating", - "set_temperature": "mdi:thermometer" + "set_temperature": "mdi:thermometer", + "set_aux_heat": "mdi:radiator", + "set_preset_mode": "mdi:sofa", + "set_hvac_mode": "mdi:hvac", + "turn_on": "mdi:power-on", + "turn_off": "mdi:power-off", + "toggle": "mdi:toggle-switch" } } From 566cbc71a52fcf3c09daf13dc69c72c19d00387b Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Thu, 14 Mar 2024 21:03:41 +0100 Subject: [PATCH 1058/1691] Add aroma therapy select entity for tolo integration (#113442) * add select entity to allow aroma therapy slot selection * improved translation readability --- homeassistant/components/tolo/select.py | 72 +++++++++++++++++----- homeassistant/components/tolo/strings.json | 7 +++ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/tolo/select.py b/homeassistant/components/tolo/select.py index 8d574f9dff5..f88152d6fa8 100644 --- a/homeassistant/components/tolo/select.py +++ b/homeassistant/components/tolo/select.py @@ -2,9 +2,12 @@ from __future__ import annotations -from tololib import LampMode +from collections.abc import Callable +from dataclasses import dataclass -from homeassistant.components.select import SelectEntity +from tololib import AromaTherapySlot, LampMode, ToloClient, ToloSettings + +from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant @@ -14,6 +17,37 @@ from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator from .const import DOMAIN +@dataclass(frozen=True, kw_only=True) +class ToloSelectEntityDescription(SelectEntityDescription): + """Class describing TOLO select entities.""" + + options: list[str] + getter: Callable[[ToloSettings], str] + setter: Callable[[ToloClient, str], bool] + + +SELECTS = ( + ToloSelectEntityDescription( + key="lamp_mode", + translation_key="lamp_mode", + options=[lamp_mode.name.lower() for lamp_mode in LampMode], + getter=lambda settings: settings.lamp_mode.name.lower(), + setter=lambda client, option: client.set_lamp_mode(LampMode[option.upper()]), + ), + ToloSelectEntityDescription( + key="aroma_therapy_slot", + translation_key="aroma_therapy_slot", + options=[ + aroma_therapy_slot.name.lower() for aroma_therapy_slot in AromaTherapySlot + ], + getter=lambda settings: settings.aroma_therapy_slot.name.lower(), + setter=lambda client, option: client.set_aroma_therapy_slot( + AromaTherapySlot[option.upper()] + ), + ), +) + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -21,29 +55,39 @@ async def async_setup_entry( ) -> None: """Set up select entities for TOLO Sauna.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([ToloLampModeSelect(coordinator, entry)]) + async_add_entities( + ToloSelectEntity(coordinator, entry, description) for description in SELECTS + ) -class ToloLampModeSelect(ToloSaunaCoordinatorEntity, SelectEntity): - """TOLO Sauna lamp mode select.""" +class ToloSelectEntity(ToloSaunaCoordinatorEntity, SelectEntity): + """TOLO select entity.""" _attr_entity_category = EntityCategory.CONFIG - _attr_options = [lamp_mode.name.lower() for lamp_mode in LampMode] - _attr_translation_key = "lamp_mode" + + entity_description: ToloSelectEntityDescription def __init__( - self, coordinator: ToloSaunaUpdateCoordinator, entry: ConfigEntry + self, + coordinator: ToloSaunaUpdateCoordinator, + entry: ConfigEntry, + entity_description: ToloSelectEntityDescription, ) -> None: - """Initialize lamp mode select entity.""" + """Initialize TOLO select entity.""" super().__init__(coordinator, entry) + self.entity_description = entity_description + self._attr_unique_id = f"{entry.entry_id}_{entity_description.key}" - self._attr_unique_id = f"{entry.entry_id}_lamp_mode" + @property + def options(self) -> list[str]: + """Return available select options.""" + return self.entity_description.options @property def current_option(self) -> str: - """Return current lamp mode.""" - return self.coordinator.data.settings.lamp_mode.name.lower() + """Return current select option.""" + return self.entity_description.getter(self.coordinator.data.settings) def select_option(self, option: str) -> None: - """Select lamp mode.""" - self.coordinator.client.set_lamp_mode(LampMode[option.upper()]) + """Select a select option.""" + self.entity_description.setter(self.coordinator.client, option) diff --git a/homeassistant/components/tolo/strings.json b/homeassistant/components/tolo/strings.json index deab432425f..c55498b8d92 100644 --- a/homeassistant/components/tolo/strings.json +++ b/homeassistant/components/tolo/strings.json @@ -61,6 +61,13 @@ "automatic": "Automatic", "manual": "Manual" } + }, + "aroma_therapy_slot": { + "name": "Aroma therapy slot", + "state": { + "a": "Slot A", + "b": "Slot B" + } } }, "sensor": { From 3ed7a7166d65c9ed39cb35c61d18354cbe96d047 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 14 Mar 2024 22:11:44 +0100 Subject: [PATCH 1059/1691] Revert "Remove unused test helper mock_area_registry" (#113453) --- tests/common.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/common.py b/tests/common.py index 6d0e74021e3..834354fa673 100644 --- a/tests/common.py +++ b/tests/common.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +from collections import OrderedDict from collections.abc import AsyncGenerator, Generator, Mapping, Sequence from contextlib import asynccontextmanager, contextmanager from datetime import UTC, datetime, timedelta @@ -620,6 +621,27 @@ def mock_registry( return registry +def mock_area_registry( + hass: HomeAssistant, mock_entries: dict[str, ar.AreaEntry] | None = None +) -> ar.AreaRegistry: + """Mock the Area Registry. + + This should only be used if you need to mock/re-stage a clean mocked + area registry in your current hass object. It can be useful to, + for example, pre-load the registry with items. + + This mock will thus replace the existing registry in the running hass. + + If you just need to access the existing registry, use the `area_registry` + fixture instead. + """ + registry = ar.AreaRegistry(hass) + registry.areas = mock_entries or OrderedDict() + + hass.data[ar.DATA_REGISTRY] = registry + return registry + + def mock_device_registry( hass: HomeAssistant, mock_entries: dict[str, dr.DeviceEntry] | None = None, From b1c636c8868c38f4f8ef63e18b99e953267cf130 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 14 Mar 2024 22:51:18 +0100 Subject: [PATCH 1060/1691] Fix hassfest icons check for notify and image_processing (#113446) --- script/hassfest/icons.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/script/hassfest/icons.py b/script/hassfest/icons.py index 61745dba828..8b0fd6d3b91 100644 --- a/script/hassfest/icons.py +++ b/script/hassfest/icons.py @@ -47,7 +47,7 @@ def ensure_not_same_as_default(value: dict) -> dict: return value -def icon_schema(integration_type: str, no_entity_platforms: bool) -> vol.Schema: +def icon_schema(integration_type: str, no_entity_platform: bool) -> vol.Schema: """Create a icon schema.""" state_validator = cv.schema_with_slug_keys( @@ -78,7 +78,7 @@ def icon_schema(integration_type: str, no_entity_platforms: bool) -> vol.Schema: ) if integration_type in ("entity", "helper", "system"): - if integration_type != "entity" or no_entity_platforms: + if integration_type != "entity" or no_entity_platform: field = vol.Optional("entity_component") else: field = vol.Required("entity_component") @@ -126,9 +126,9 @@ def validate_icon_file(config: Config, integration: Integration) -> None: # noq integration.add_error("icons", f"Invalid JSON in {name}: {err}") return - no_entity_platforms = name in ("notify", "image_processing") + no_entity_platform = integration.domain in ("notify", "image_processing") - schema = icon_schema(integration.integration_type, no_entity_platforms) + schema = icon_schema(integration.integration_type, no_entity_platform) try: schema(icons) From 221893c1d7fe301c1f4c4a624675aee69175e904 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 14 Mar 2024 23:07:07 +0100 Subject: [PATCH 1061/1691] Add aliases support to floor registry WebSocket API (#113401) --- homeassistant/components/config/floor_registry.py | 11 +++++++++++ tests/components/config/test_floor_registry.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/homeassistant/components/config/floor_registry.py b/homeassistant/components/config/floor_registry.py index 893f97c567d..081ec59e268 100644 --- a/homeassistant/components/config/floor_registry.py +++ b/homeassistant/components/config/floor_registry.py @@ -41,6 +41,7 @@ def websocket_list_floors( { vol.Required("type"): "config/floor_registry/create", vol.Required("name"): str, + vol.Optional("aliases"): list, vol.Optional("icon"): vol.Any(str, None), vol.Optional("level"): int, } @@ -57,6 +58,10 @@ def websocket_create_floor( data.pop("type") data.pop("id") + if "aliases" in data: + # Convert aliases to a set + data["aliases"] = set(data["aliases"]) + try: entry = registry.async_create(**data) except ValueError as err: @@ -91,6 +96,7 @@ def websocket_delete_floor( { vol.Required("type"): "config/floor_registry/update", vol.Required("floor_id"): str, + vol.Optional("aliases"): list, vol.Optional("icon"): vol.Any(str, None), vol.Optional("level"): int, vol.Optional("name"): str, @@ -108,6 +114,10 @@ def websocket_update_floor( data.pop("type") data.pop("id") + if "aliases" in data: + # Convert aliases to a set + data["aliases"] = set(data["aliases"]) + try: entry = registry.async_update(**data) except ValueError as err: @@ -120,6 +130,7 @@ def websocket_update_floor( def _entry_dict(entry: FloorEntry) -> dict[str, Any]: """Convert entry to API format.""" return { + "aliases": list(entry.aliases), "floor_id": entry.floor_id, "icon": entry.icon, "level": entry.level, diff --git a/tests/components/config/test_floor_registry.py b/tests/components/config/test_floor_registry.py index a3866c88ffa..781c6181118 100644 --- a/tests/components/config/test_floor_registry.py +++ b/tests/components/config/test_floor_registry.py @@ -1,6 +1,7 @@ """Test floor registry API.""" import pytest +from pytest_unordered import unordered from homeassistant.components.config import floor_registry from homeassistant.core import HomeAssistant @@ -26,6 +27,7 @@ async def test_list_floors( floor_registry.async_create("First floor") floor_registry.async_create( name="Second floor", + aliases={"top floor", "attic"}, icon="mdi:home-floor-2", level=2, ) @@ -38,12 +40,14 @@ async def test_list_floors( assert len(msg["result"]) == len(floor_registry.floors) assert msg["result"][0] == { + "aliases": [], "icon": None, "floor_id": "first_floor", "name": "First floor", "level": 0, } assert msg["result"][1] == { + "aliases": unordered(["top floor", "attic"]), "icon": "mdi:home-floor-2", "floor_id": "second_floor", "name": "Second floor", @@ -64,6 +68,7 @@ async def test_create_floor( assert len(floor_registry.floors) == 1 assert msg["result"] == { + "aliases": [], "icon": None, "floor_id": "first_floor", "name": "First floor", @@ -74,6 +79,7 @@ async def test_create_floor( { "name": "Second floor", "type": "config/floor_registry/create", + "aliases": ["top floor", "attic"], "icon": "mdi:home-floor-2", "level": 2, } @@ -83,6 +89,7 @@ async def test_create_floor( assert len(floor_registry.floors) == 2 assert msg["result"] == { + "aliases": unordered(["top floor", "attic"]), "icon": "mdi:home-floor-2", "floor_id": "second_floor", "name": "Second floor", @@ -165,6 +172,7 @@ async def test_update_floor( { "floor_id": floor.floor_id, "name": "Second floor", + "aliases": ["top floor", "attic"], "icon": "mdi:home-floor-2", "type": "config/floor_registry/update", "level": 2, @@ -175,6 +183,7 @@ async def test_update_floor( assert len(floor_registry.floors) == 1 assert msg["result"] == { + "aliases": unordered(["top floor", "attic"]), "icon": "mdi:home-floor-2", "floor_id": floor.floor_id, "name": "Second floor", @@ -185,6 +194,7 @@ async def test_update_floor( { "floor_id": floor.floor_id, "name": "First floor", + "aliases": [], "icon": None, "level": 1, "type": "config/floor_registry/update", @@ -195,6 +205,7 @@ async def test_update_floor( assert len(floor_registry.floors) == 1 assert msg["result"] == { + "aliases": [], "icon": None, "floor_id": floor.floor_id, "name": "First floor", From 28ef898775170736a842a45ae5a300f716c48b12 Mon Sep 17 00:00:00 2001 From: wilburCforce <109390391+wilburCforce@users.noreply.github.com> Date: Thu, 14 Mar 2024 17:07:16 -0500 Subject: [PATCH 1062/1691] Support lutron transition time and flash for lights (#109185) * support transition time for lights * bug fix and support for FLASH * updated to flash() to match changes coming in pylutron * bumped pylutron version in anticipation of next release * Update manifest.json * Update requirements_all.txt * Update requirements_test_all.txt * Update requirements_test_all.txt * nits and code improves --- homeassistant/components/lutron/light.py | 35 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index 103bcb1e39d..f4088d687cd 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -9,7 +9,14 @@ from typing import Any from pylutron import Output from homeassistant.components.automation import automations_with_entity -from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_FLASH, + ATTR_TRANSITION, + ColorMode, + LightEntity, + LightEntityFeature, +) from homeassistant.components.script import scripts_with_entity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -101,6 +108,7 @@ class LutronLight(LutronDevice, LightEntity): _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.FLASH _lutron_device: Output _prev_brightness: int | None = None _attr_name = None @@ -123,14 +131,20 @@ class LutronLight(LutronDevice, LightEntity): severity=IssueSeverity.WARNING, translation_key="deprecated_light_fan_on", ) - if ATTR_BRIGHTNESS in kwargs and self._lutron_device.is_dimmable: - brightness = kwargs[ATTR_BRIGHTNESS] - elif self._prev_brightness == 0: - brightness = 255 / 2 + if flash := kwargs.get(ATTR_FLASH): + self._lutron_device.flash(0.5 if flash == "short" else 1.5) else: - brightness = self._prev_brightness - self._prev_brightness = brightness - self._lutron_device.level = to_lutron_level(brightness) + if ATTR_BRIGHTNESS in kwargs and self._lutron_device.is_dimmable: + brightness = kwargs[ATTR_BRIGHTNESS] + elif self._prev_brightness == 0: + brightness = 255 / 2 + else: + brightness = self._prev_brightness + self._prev_brightness = brightness + args = {"new_level": brightness} + if ATTR_TRANSITION in kwargs: + args["fade_time_seconds"] = kwargs[ATTR_TRANSITION] + self._lutron_device.set_level(**args) def turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" @@ -145,7 +159,10 @@ class LutronLight(LutronDevice, LightEntity): severity=IssueSeverity.WARNING, translation_key="deprecated_light_fan_off", ) - self._lutron_device.level = 0 + args = {"new_level": 0} + if ATTR_TRANSITION in kwargs: + args["fade_time_seconds"] = kwargs[ATTR_TRANSITION] + self._lutron_device.set_level(**args) @property def extra_state_attributes(self) -> Mapping[str, Any] | None: From 7cba34b2e6b53621e6188ff2b473a3553e72bdcd Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 14 Mar 2024 23:19:52 +0100 Subject: [PATCH 1063/1691] Change modbus integration to use async library calls (#113450) --- homeassistant/components/modbus/modbus.py | 46 ++++++++++------------- tests/components/modbus/conftest.py | 14 ++++--- tests/components/modbus/test_cover.py | 2 +- tests/components/modbus/test_init.py | 4 +- 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index d08e8b80577..f4947223662 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -8,8 +8,11 @@ from collections.abc import Callable import logging from typing import Any -from pymodbus.client import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient -from pymodbus.client.base import ModbusBaseClient +from pymodbus.client import ( + AsyncModbusSerialClient, + AsyncModbusTcpClient, + AsyncModbusUdpClient, +) from pymodbus.exceptions import ModbusException from pymodbus.pdu import ModbusResponse from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer, ModbusSocketFramer @@ -275,7 +278,7 @@ class ModbusHub: else: client_config[CONF_RETRIES] = 3 # generic configuration - self._client: ModbusBaseClient | None = None + self._client: AsyncModbusSerialClient | AsyncModbusTcpClient | AsyncModbusUdpClient | None = None self._async_cancel_listener: Callable[[], None] | None = None self._in_error = False self._lock = asyncio.Lock() @@ -285,10 +288,10 @@ class ModbusHub: self._config_delay = client_config[CONF_DELAY] self._pb_request: dict[str, RunEntry] = {} self._pb_class = { - SERIAL: ModbusSerialClient, - TCP: ModbusTcpClient, - UDP: ModbusUdpClient, - RTUOVERTCP: ModbusTcpClient, + SERIAL: AsyncModbusSerialClient, + TCP: AsyncModbusTcpClient, + UDP: AsyncModbusUdpClient, + RTUOVERTCP: AsyncModbusTcpClient, } self._pb_params = { "port": client_config[CONF_PORT], @@ -336,9 +339,14 @@ class ModbusHub: async def async_pb_connect(self) -> None: """Connect to device, async.""" async with self._lock: - if not await self.hass.async_add_executor_job(self.pb_connect): - err = f"{self.name} connect failed, retry in pymodbus" + try: + await self._client.connect() # type: ignore[union-attr] + except ModbusException as exception_error: + err = f"{self.name} connect failed, retry in pymodbus ({str(exception_error)})" self._log_error(err, error_state=False) + return + message = f"modbus {self.name} communication open" + _LOGGER.info(message) async def async_setup(self) -> bool: """Set up pymodbus client.""" @@ -392,26 +400,14 @@ class ModbusHub: message = f"modbus {self.name} communication closed" _LOGGER.warning(message) - def pb_connect(self) -> bool: - """Connect client.""" - try: - self._client.connect() # type: ignore[union-attr] - except ModbusException as exception_error: - self._log_error(str(exception_error), error_state=False) - return False - - message = f"modbus {self.name} communication open" - _LOGGER.info(message) - return True - - def pb_call( + async def low_level_pb_call( self, slave: int | None, address: int, value: int | list[int], use_call: str ) -> ModbusResponse | None: """Call sync. pymodbus.""" kwargs = {"slave": slave} if slave else {} entry = self._pb_request[use_call] try: - result: ModbusResponse = entry.func(address, value, **kwargs) + result: ModbusResponse = await entry.func(address, value, **kwargs) except ModbusException as exception_error: error = ( f"Error: device: {slave} address: {address} -> {str(exception_error)}" @@ -448,9 +444,7 @@ class ModbusHub: async with self._lock: if not self._client: return None - result = await self.hass.async_add_executor_job( - self.pb_call, unit, address, value, use_call - ) + result = await self.low_level_pb_call(unit, address, value, use_call) if self._msg_wait: # small delay until next request/response await asyncio.sleep(self._msg_wait) diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index e470f30fe82..2a81fa3d4fb 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -50,17 +50,18 @@ class ReadResult: @pytest.fixture(name="mock_pymodbus") def mock_pymodbus_fixture(): """Mock pymodbus.""" - mock_pb = mock.MagicMock() + mock_pb = mock.AsyncMock() + mock_pb.close = mock.MagicMock() with mock.patch( - "homeassistant.components.modbus.modbus.ModbusTcpClient", + "homeassistant.components.modbus.modbus.AsyncModbusTcpClient", return_value=mock_pb, autospec=True, ), mock.patch( - "homeassistant.components.modbus.modbus.ModbusSerialClient", + "homeassistant.components.modbus.modbus.AsyncModbusSerialClient", return_value=mock_pb, autospec=True, ), mock.patch( - "homeassistant.components.modbus.modbus.ModbusUdpClient", + "homeassistant.components.modbus.modbus.AsyncModbusUdpClient", return_value=mock_pb, autospec=True, ): @@ -118,9 +119,10 @@ async def mock_modbus_fixture( } ] } - mock_pb = mock.MagicMock() + mock_pb = mock.AsyncMock() + mock_pb.close = mock.MagicMock() with mock.patch( - "homeassistant.components.modbus.modbus.ModbusTcpClient", + "homeassistant.components.modbus.modbus.AsyncModbusTcpClient", return_value=mock_pb, autospec=True, ): diff --git a/tests/components/modbus/test_cover.py b/tests/components/modbus/test_cover.py index 32c79fb4dff..fa9e617d96d 100644 --- a/tests/components/modbus/test_cover.py +++ b/tests/components/modbus/test_cover.py @@ -270,7 +270,7 @@ async def test_service_cover_move(hass: HomeAssistant, mock_modbus, mock_ha) -> ) assert hass.states.get(ENTITY_ID).state == STATE_CLOSED - mock_modbus.reset() + await mock_modbus.reset() mock_modbus.read_holding_registers.side_effect = ModbusException("fail write_") await hass.services.async_call( "cover", "close_cover", {"entity_id": ENTITY_ID}, blocking=True diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 3e0e94f4076..e55b9f4232d 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -1329,7 +1329,7 @@ async def test_pymodbus_constructor_fail( ] } with mock.patch( - "homeassistant.components.modbus.modbus.ModbusTcpClient", autospec=True + "homeassistant.components.modbus.modbus.AsyncModbusTcpClient", autospec=True ) as mock_pb: caplog.set_level(logging.ERROR) mock_pb.side_effect = ModbusException("test no class") @@ -1529,7 +1529,7 @@ async def test_stop_restart( async def test_write_no_client(hass: HomeAssistant, mock_modbus) -> None: """Run test for service stop and write without client.""" - mock_modbus.reset() + await mock_modbus.reset() data = { ATTR_HUB: TEST_MODBUS_NAME, } From cd52f0f243694a6d6dc10349bc452d3a5432aac5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 23:48:53 +0100 Subject: [PATCH 1064/1691] Add service icons to Notify (#113417) Co-authored-by: Jan Bouwhuis --- homeassistant/components/notify/icons.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 homeassistant/components/notify/icons.json diff --git a/homeassistant/components/notify/icons.json b/homeassistant/components/notify/icons.json new file mode 100644 index 00000000000..88577bc2356 --- /dev/null +++ b/homeassistant/components/notify/icons.json @@ -0,0 +1,6 @@ +{ + "services": { + "notify": "mdi:bell-ring", + "persistent_notification": "mdi:bell-badge" + } +} From ba2d382eb6ca9d6d867f851ee22e27ec596f40cc Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 14 Mar 2024 23:49:05 +0100 Subject: [PATCH 1065/1691] Add service icons to Image processing (#113416) Co-authored-by: Jan Bouwhuis --- homeassistant/components/image_processing/icons.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 homeassistant/components/image_processing/icons.json diff --git a/homeassistant/components/image_processing/icons.json b/homeassistant/components/image_processing/icons.json new file mode 100644 index 00000000000..b19d29c186d --- /dev/null +++ b/homeassistant/components/image_processing/icons.json @@ -0,0 +1,5 @@ +{ + "services": { + "scan": "mdi:qrcode-scan" + } +} From 5a62be571db87f8eceb77d4c292397973b22839e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 13:09:58 -1000 Subject: [PATCH 1066/1691] Bump aiodhcpwatcher to 0.8.2 (#113466) --- homeassistant/components/dhcp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index 10f63402c94..dfe44d0022f 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -14,7 +14,7 @@ ], "quality_scale": "internal", "requirements": [ - "aiodhcpwatcher==0.8.1", + "aiodhcpwatcher==0.8.2", "aiodiscover==2.0.0", "cached_ipaddress==0.3.0" ] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7747d78f88f..11acc893b79 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ # Automatically generated by gen_requirements_all.py, do not edit -aiodhcpwatcher==0.8.1 +aiodhcpwatcher==0.8.2 aiodiscover==2.0.0 aiohttp-fast-url-dispatcher==0.3.0 aiohttp-zlib-ng==0.3.1 diff --git a/requirements_all.txt b/requirements_all.txt index 24c87de9198..0c92b1c7b83 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ aiobotocore==2.9.1 aiocomelit==0.9.0 # homeassistant.components.dhcp -aiodhcpwatcher==0.8.1 +aiodhcpwatcher==0.8.2 # homeassistant.components.dhcp aiodiscover==2.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9094e7efd0d..0a352f07999 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,7 +197,7 @@ aiobotocore==2.9.1 aiocomelit==0.9.0 # homeassistant.components.dhcp -aiodhcpwatcher==0.8.1 +aiodhcpwatcher==0.8.2 # homeassistant.components.dhcp aiodiscover==2.0.0 From 7cdec9aeebbfcac67819e55fb9b3aaa9e6b8651a Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Thu, 14 Mar 2024 19:42:13 -0400 Subject: [PATCH 1067/1691] Bump aiooncue to 0.3.7 (#113451) --- homeassistant/components/oncue/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/oncue/manifest.json b/homeassistant/components/oncue/manifest.json index 24414e4efb8..b4c425a1645 100644 --- a/homeassistant/components/oncue/manifest.json +++ b/homeassistant/components/oncue/manifest.json @@ -12,5 +12,5 @@ "documentation": "https://www.home-assistant.io/integrations/oncue", "iot_class": "cloud_polling", "loggers": ["aiooncue"], - "requirements": ["aiooncue==0.3.5"] + "requirements": ["aiooncue==0.3.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 0c92b1c7b83..1555c4e1601 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -315,7 +315,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.oncue -aiooncue==0.3.5 +aiooncue==0.3.7 # homeassistant.components.openexchangerates aioopenexchangerates==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0a352f07999..c3f9f98a7b0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -288,7 +288,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.oncue -aiooncue==0.3.5 +aiooncue==0.3.7 # homeassistant.components.openexchangerates aioopenexchangerates==0.4.0 From f95d649f4496035824864e34828401bf7fe825a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Strandberg?= Date: Fri, 15 Mar 2024 00:47:10 +0100 Subject: [PATCH 1068/1691] Add icon translations to myuplink (#111466) --- .../components/myuplink/binary_sensor.py | 2 +- homeassistant/components/myuplink/icons.json | 45 +++++++++++++++++++ homeassistant/components/myuplink/number.py | 4 +- homeassistant/components/myuplink/sensor.py | 10 ++--- homeassistant/components/myuplink/switch.py | 6 ++- tests/components/myuplink/test_number.py | 1 - tests/components/myuplink/test_switch.py | 1 - 7 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/myuplink/icons.json diff --git a/homeassistant/components/myuplink/binary_sensor.py b/homeassistant/components/myuplink/binary_sensor.py index b5ade88a002..38b8c9c5fd3 100644 --- a/homeassistant/components/myuplink/binary_sensor.py +++ b/homeassistant/components/myuplink/binary_sensor.py @@ -20,7 +20,7 @@ CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, BinarySensorEntityDescription]] "NIBEF": { "43161": BinarySensorEntityDescription( key="elect_add", - icon="mdi:electric-switch", + translation_key="elect_add", ), }, } diff --git a/homeassistant/components/myuplink/icons.json b/homeassistant/components/myuplink/icons.json new file mode 100644 index 00000000000..580b83b1b15 --- /dev/null +++ b/homeassistant/components/myuplink/icons.json @@ -0,0 +1,45 @@ +{ + "entity": { + "binary_sensor": { + "elect_add": { + "default": "mdi:electric-switch", + "state": { + "on": "mdi:electric-switch-closed" + } + } + }, + "number": { + "degree_minutes": { + "default": "mdi:thermometer-lines" + } + }, + "sensor": { + "airflow": { + "default": "mdi:weather-windy" + }, + "elect_add": { + "default": "mdi:heat-wave" + }, + "fan_mode": { + "default": "mdi:fan" + }, + "priority": { + "default": "mdi:priority-high" + }, + "status_compressor": { + "default": "mdi:heat-pump-outline" + } + }, + "switch": { + "boost_ventilation": { + "default": "mdi:fan-plus" + }, + "temporary_lux": { + "default": "mdi:water-alert-outline", + "state": { + "on": "mdi:water-alert" + } + } + } + } +} diff --git a/homeassistant/components/myuplink/number.py b/homeassistant/components/myuplink/number.py index 55662fb57fb..4e947a440dd 100644 --- a/homeassistant/components/myuplink/number.py +++ b/homeassistant/components/myuplink/number.py @@ -18,7 +18,7 @@ from .helpers import find_matching_platform DEVICE_POINT_UNIT_DESCRIPTIONS: dict[str, NumberEntityDescription] = { "DM": NumberEntityDescription( key="degree_minutes", - icon="mdi:thermometer-lines", + translation_key="degree_minutes", native_unit_of_measurement="DM", ), } @@ -27,7 +27,7 @@ CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, NumberEntityDescription]] = { "NIBEF": { "40940": NumberEntityDescription( key="degree_minutes", - icon="mdi:thermometer-lines", + translation_key="degree_minutes", native_unit_of_measurement="DM", ), }, diff --git a/homeassistant/components/myuplink/sensor.py b/homeassistant/components/myuplink/sensor.py index 1e4bfed1a20..a7afa8f28d8 100644 --- a/homeassistant/components/myuplink/sensor.py +++ b/homeassistant/components/myuplink/sensor.py @@ -81,10 +81,10 @@ DEVICE_POINT_UNIT_DESCRIPTIONS: dict[str, SensorEntityDescription] = { ), "m3/h": SensorEntityDescription( key="airflow", + translation_key="airflow", device_class=SensorDeviceClass.VOLUME_FLOW_RATE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, - icon="mdi:weather-windy", ), "s": SensorEntityDescription( key="seconds", @@ -101,22 +101,22 @@ CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, SensorEntityDescription]] = { "NIBEF": { "43108": SensorEntityDescription( key="fan_mode", - icon="mdi:fan", + translation_key="fan_mode", ), "43427": SensorEntityDescription( key="status_compressor", + translation_key="status_compressor", device_class=SensorDeviceClass.ENUM, - icon="mdi:heat-pump-outline", ), "49993": SensorEntityDescription( key="elect_add", + translation_key="elect_add", device_class=SensorDeviceClass.ENUM, - icon="mdi:heat-wave", ), "49994": SensorEntityDescription( key="priority", + translation_key="priority", device_class=SensorDeviceClass.ENUM, - icon="mdi:priority-high", ), }, "NIBE": {}, diff --git a/homeassistant/components/myuplink/switch.py b/homeassistant/components/myuplink/switch.py index 310c6417133..5f6bee2262e 100644 --- a/homeassistant/components/myuplink/switch.py +++ b/homeassistant/components/myuplink/switch.py @@ -21,7 +21,11 @@ CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, SwitchEntityDescription]] = { "NIBEF": { "50004": SwitchEntityDescription( key="temporary_lux", - icon="mdi:water-alert-outline", + translation_key="temporary_lux", + ), + "50005": SwitchEntityDescription( + key="boost_ventilation", + translation_key="boost_ventilation", ), }, } diff --git a/tests/components/myuplink/test_number.py b/tests/components/myuplink/test_number.py index 09f361f0ec6..2bd2ca61496 100644 --- a/tests/components/myuplink/test_number.py +++ b/tests/components/myuplink/test_number.py @@ -42,7 +42,6 @@ async def test_attributes( assert state.state == "-875.0" assert state.attributes == { "friendly_name": ENTITY_FRIENDLY_NAME, - "icon": "mdi:thermometer-lines", "min": -3000, "max": 3000, "mode": "auto", diff --git a/tests/components/myuplink/test_switch.py b/tests/components/myuplink/test_switch.py index 06855fd91da..e60aa4ebc4b 100644 --- a/tests/components/myuplink/test_switch.py +++ b/tests/components/myuplink/test_switch.py @@ -47,7 +47,6 @@ async def test_attributes( assert state.state == STATE_OFF assert state.attributes == { "friendly_name": ENTITY_FRIENDLY_NAME, - "icon": "mdi:water-alert-outline", } From 28836be3ebe5c7b119f851ce8ebd70075d8e23b6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 13:53:11 -1000 Subject: [PATCH 1069/1691] Construct storage data in the executor to avoid blocking the event loop (#113465) Construct storage data in the executor Constructing storage data can be expensive for large files and can block the event loop. While ideally we optimize the construction of the data, there are some places we cannot make it any faster. To avoid blocking the loop, the construction of the data is now done in the executor by running the data_func in the executor. 2024-03-14 11:28:20.178 WARNING (MainThread) [asyncio] Executing took 0.159 seconds There is some risk that the data_func is not thread-safe and needs to be run in the event loop, but I could not find any cases in our existing code where it would be a problem --- homeassistant/helpers/storage.py | 7 +++---- tests/common.py | 6 +++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 2af21b070da..cdbea143e18 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -374,10 +374,6 @@ class Store(Generic[_T]): return data = self._data - - if "data_func" in data: - data["data"] = data.pop("data_func")() - self._data = None if self._read_only: @@ -395,6 +391,9 @@ class Store(Generic[_T]): """Write the data.""" os.makedirs(os.path.dirname(path), exist_ok=True) + if "data_func" in data: + data["data"] = data.pop("data_func")() + _LOGGER.debug("Writing data for %s to %s", self.key, path) json_helper.save_json( path, diff --git a/tests/common.py b/tests/common.py index 834354fa673..2e6b30d680b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1356,6 +1356,10 @@ def mock_storage( # To ensure that the data can be serialized _LOGGER.debug("Writing data to %s: %s", store.key, data_to_write) raise_contains_mocks(data_to_write) + + if "data_func" in data_to_write: + data_to_write["data"] = data_to_write.pop("data_func")() + encoder = store._encoder if encoder and encoder is not JSONEncoder: # If they pass a custom encoder that is not the @@ -1363,7 +1367,7 @@ def mock_storage( dump = ft.partial(json.dumps, cls=store._encoder) else: dump = _orjson_default_encoder - data[store.key] = json.loads(dump(data_to_write)) + data[store.key] = json_loads(dump(data_to_write)) async def mock_remove(store: storage.Store) -> None: """Remove data.""" From 5b80eb4c3d50ea2528edd23395045b3d068e90cd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 13:54:43 -1000 Subject: [PATCH 1070/1691] Reduce overhead to save the larger registries (#113462) We save the device and entity registry to disk quite often, and the cost of serializing them to the storage can block the event loop for >100ms. Add a cache to reduce the change the loop is blocked at an inopportune time at run time. The first write after startup will still be a little slow but we do have to serialize the bulk of it at least once as there is no way to avoid this ``` 2024-03-14 11:28:19.765 WARNING (MainThread) [homeassistant.helpers.storage] Writing data with data_func: core.device_registry 2024-03-14 11:28:20.020 WARNING (MainThread) [homeassistant.helpers.storage] Writing data with data_func: core.entity_registry 2024-03-14 11:28:20.178 WARNING (MainThread) [asyncio] Executing took 0.159 seconds ``` --- homeassistant/helpers/device_registry.py | 90 ++++++++++--------- homeassistant/helpers/entity_registry.py | 106 +++++++++++++---------- 2 files changed, 110 insertions(+), 86 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index d751efdffe6..4cc7ad83c32 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -31,7 +31,7 @@ from .deprecation import ( dir_with_deprecated_constants, ) from .frame import report -from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes +from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes, json_fragment from .registry import BaseRegistry from .typing import UNDEFINED, UndefinedType @@ -301,8 +301,35 @@ class DeviceEntry: ) return None + @cached_property + def as_storage_fragment(self) -> json_fragment: + """Return a json fragment for storage.""" + return json_fragment( + json_bytes( + { + "area_id": self.area_id, + "config_entries": list(self.config_entries), + "configuration_url": self.configuration_url, + "connections": list(self.connections), + "disabled_by": self.disabled_by, + "entry_type": self.entry_type, + "hw_version": self.hw_version, + "id": self.id, + "identifiers": list(self.identifiers), + "labels": list(self.labels), + "manufacturer": self.manufacturer, + "model": self.model, + "name_by_user": self.name_by_user, + "name": self.name, + "serial_number": self.serial_number, + "sw_version": self.sw_version, + "via_device_id": self.via_device_id, + } + ) + ) -@attr.s(slots=True, frozen=True) + +@attr.s(frozen=True) class DeletedDeviceEntry: """Deleted Device Registry Entry.""" @@ -328,6 +355,21 @@ class DeletedDeviceEntry: is_new=True, ) + @cached_property + def as_storage_fragment(self) -> json_fragment: + """Return a json fragment for storage.""" + return json_fragment( + json_bytes( + { + "config_entries": list(self.config_entries), + "connections": list(self.connections), + "identifiers": list(self.identifiers), + "id": self.id, + "orphaned_timestamp": self.orphaned_timestamp, + } + ) + ) + @lru_cache(maxsize=512) def format_mac(mac: str) -> str: @@ -904,44 +946,14 @@ class DeviceRegistry(BaseRegistry): self._device_data = devices.data @callback - def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: + def _data_to_save(self) -> dict[str, Any]: """Return data of device registry to store in a file.""" - data: dict[str, list[dict[str, Any]]] = {} - - data["devices"] = [ - { - "area_id": entry.area_id, - "config_entries": list(entry.config_entries), - "configuration_url": entry.configuration_url, - "connections": list(entry.connections), - "disabled_by": entry.disabled_by, - "entry_type": entry.entry_type, - "hw_version": entry.hw_version, - "id": entry.id, - "identifiers": list(entry.identifiers), - "labels": list(entry.labels), - "manufacturer": entry.manufacturer, - "model": entry.model, - "name_by_user": entry.name_by_user, - "name": entry.name, - "serial_number": entry.serial_number, - "sw_version": entry.sw_version, - "via_device_id": entry.via_device_id, - } - for entry in self.devices.values() - ] - data["deleted_devices"] = [ - { - "config_entries": list(entry.config_entries), - "connections": list(entry.connections), - "identifiers": list(entry.identifiers), - "id": entry.id, - "orphaned_timestamp": entry.orphaned_timestamp, - } - for entry in self.deleted_devices.values() - ] - - return data + return { + "devices": [entry.as_storage_fragment for entry in self.devices.values()], + "deleted_devices": [ + entry.as_storage_fragment for entry in self.deleted_devices.values() + ], + } @callback def async_clear_config_entry(self, config_entry_id: str) -> None: diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 413f227c7cc..3c437faf18c 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -52,7 +52,7 @@ from homeassistant.util.read_only_dict import ReadOnlyDict from . import device_registry as dr, storage from .device_registry import EVENT_DEVICE_REGISTRY_UPDATED -from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes +from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes, json_fragment from .registry import BaseRegistry from .typing import UNDEFINED, UndefinedType @@ -311,6 +311,41 @@ class RegistryEntry: ) return None + @cached_property + def as_storage_fragment(self) -> json_fragment: + """Return a json fragment for storage.""" + return json_fragment( + json_bytes( + { + "aliases": list(self.aliases), + "area_id": self.area_id, + "capabilities": self.capabilities, + "config_entry_id": self.config_entry_id, + "device_class": self.device_class, + "device_id": self.device_id, + "disabled_by": self.disabled_by, + "entity_category": self.entity_category, + "entity_id": self.entity_id, + "hidden_by": self.hidden_by, + "icon": self.icon, + "id": self.id, + "has_entity_name": self.has_entity_name, + "labels": list(self.labels), + "name": self.name, + "options": self.options, + "original_device_class": self.original_device_class, + "original_icon": self.original_icon, + "original_name": self.original_name, + "platform": self.platform, + "supported_features": self.supported_features, + "translation_key": self.translation_key, + "unique_id": self.unique_id, + "previous_unique_id": self.previous_unique_id, + "unit_of_measurement": self.unit_of_measurement, + } + ) + ) + @callback def write_unavailable_state(self, hass: HomeAssistant) -> None: """Write the unavailable state to the state machine.""" @@ -340,7 +375,7 @@ class RegistryEntry: hass.states.async_set(self.entity_id, STATE_UNAVAILABLE, attrs) -@attr.s(slots=True, frozen=True) +@attr.s(frozen=True) class DeletedRegistryEntry: """Deleted Entity Registry Entry.""" @@ -357,6 +392,22 @@ class DeletedRegistryEntry: """Compute domain value.""" return split_entity_id(self.entity_id)[0] + @cached_property + def as_storage_fragment(self) -> json_fragment: + """Return a json fragment for storage.""" + return json_fragment( + json_bytes( + { + "config_entry_id": self.config_entry_id, + "entity_id": self.entity_id, + "id": self.id, + "orphaned_timestamp": self.orphaned_timestamp, + "platform": self.platform, + "unique_id": self.unique_id, + } + ) + ) + class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]): """Store entity registry data.""" @@ -1197,51 +1248,12 @@ class EntityRegistry(BaseRegistry): @callback def _data_to_save(self) -> dict[str, Any]: """Return data of entity registry to store in a file.""" - data: dict[str, Any] = {} - - data["entities"] = [ - { - "aliases": list(entry.aliases), - "area_id": entry.area_id, - "capabilities": entry.capabilities, - "config_entry_id": entry.config_entry_id, - "device_class": entry.device_class, - "device_id": entry.device_id, - "disabled_by": entry.disabled_by, - "entity_category": entry.entity_category, - "entity_id": entry.entity_id, - "hidden_by": entry.hidden_by, - "icon": entry.icon, - "id": entry.id, - "has_entity_name": entry.has_entity_name, - "labels": list(entry.labels), - "name": entry.name, - "options": entry.options, - "original_device_class": entry.original_device_class, - "original_icon": entry.original_icon, - "original_name": entry.original_name, - "platform": entry.platform, - "supported_features": entry.supported_features, - "translation_key": entry.translation_key, - "unique_id": entry.unique_id, - "previous_unique_id": entry.previous_unique_id, - "unit_of_measurement": entry.unit_of_measurement, - } - for entry in self.entities.values() - ] - data["deleted_entities"] = [ - { - "config_entry_id": entry.config_entry_id, - "entity_id": entry.entity_id, - "id": entry.id, - "orphaned_timestamp": entry.orphaned_timestamp, - "platform": entry.platform, - "unique_id": entry.unique_id, - } - for entry in self.deleted_entities.values() - ] - - return data + return { + "entities": [entry.as_storage_fragment for entry in self.entities.values()], + "deleted_entities": [ + entry.as_storage_fragment for entry in self.deleted_entities.values() + ], + } @callback def async_clear_label_id(self, label_id: str) -> None: From 4fb127e5af1fa8a90e581bbc687254b8a40cbe36 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 13:58:11 -1000 Subject: [PATCH 1071/1691] Avoid creating another ssl context in cert_expiry (#113467) Creating an ssl context does blocking I/O as it has to read the certs from disk --- homeassistant/components/cert_expiry/helper.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/cert_expiry/helper.py b/homeassistant/components/cert_expiry/helper.py index f3e0688c015..b35687dc933 100644 --- a/homeassistant/components/cert_expiry/helper.py +++ b/homeassistant/components/cert_expiry/helper.py @@ -2,13 +2,13 @@ import asyncio import datetime -from functools import cache import socket import ssl from typing import Any from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util +from homeassistant.util.ssl import get_default_context from .const import TIMEOUT from .errors import ( @@ -19,12 +19,6 @@ from .errors import ( ) -@cache -def _get_default_ssl_context() -> ssl.SSLContext: - """Return the default SSL context.""" - return ssl.create_default_context() - - async def async_get_cert( hass: HomeAssistant, host: str, @@ -36,7 +30,7 @@ async def async_get_cert( asyncio.Protocol, host, port, - ssl=_get_default_ssl_context(), + ssl=get_default_context(), happy_eyeballs_delay=0.25, server_hostname=host, ) From e293afe46e74d635e95b06473abcf606899b34b2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 14:16:19 -1000 Subject: [PATCH 1072/1691] Bump aiodhcpwatcher to 1.0.0 (#113469) --- homeassistant/components/dhcp/__init__.py | 37 +++++++++++---------- homeassistant/components/dhcp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/dhcp/test_init.py | 4 +-- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 45d3ff77c3a..5325283eb0a 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -from abc import ABC, abstractmethod import asyncio from collections.abc import Callable from dataclasses import dataclass @@ -132,18 +131,26 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # For the passive classes we need to start listening # for state changes and connect the dispatchers before # everything else starts up or we will miss events - for passive_cls in (DeviceTrackerRegisteredWatcher, DeviceTrackerWatcher): - passive_watcher = passive_cls(hass, address_data, integration_matchers) - passive_watcher.async_start() - watchers.append(passive_watcher) + device_watcher = DeviceTrackerWatcher(hass, address_data, integration_matchers) + device_watcher.async_start() + watchers.append(device_watcher) + + device_tracker_registered_watcher = DeviceTrackerRegisteredWatcher( + hass, address_data, integration_matchers + ) + device_tracker_registered_watcher.async_start() + watchers.append(device_tracker_registered_watcher) async def _async_initialize(event: Event) -> None: await aiodhcpwatcher.async_init() - for active_cls in (DHCPWatcher, NetworkWatcher): - active_watcher = active_cls(hass, address_data, integration_matchers) - active_watcher.async_start() - watchers.append(active_watcher) + network_watcher = NetworkWatcher(hass, address_data, integration_matchers) + network_watcher.async_start() + watchers.append(network_watcher) + + dhcp_watcher = DHCPWatcher(hass, address_data, integration_matchers) + await dhcp_watcher.async_start() + watchers.append(dhcp_watcher) @callback def _async_stop(event: Event) -> None: @@ -156,7 +163,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -class WatcherBase(ABC): +class WatcherBase: """Base class for dhcp and device tracker watching.""" def __init__( @@ -180,11 +187,6 @@ class WatcherBase(ABC): self._unsub() self._unsub = None - @abstractmethod - @callback - def async_start(self) -> None: - """Start the watcher.""" - @callback def async_process_client( self, ip_address: str, hostname: str, unformatted_mac_address: str @@ -403,10 +405,9 @@ class DHCPWatcher(WatcherBase): response.ip_address, response.hostname, response.mac_address ) - @callback - def async_start(self) -> None: + async def async_start(self) -> None: """Start watching for dhcp packets.""" - self._unsub = aiodhcpwatcher.start(self._async_process_dhcp_request) + self._unsub = await aiodhcpwatcher.async_start(self._async_process_dhcp_request) @lru_cache(maxsize=4096, typed=True) diff --git a/homeassistant/components/dhcp/manifest.json b/homeassistant/components/dhcp/manifest.json index dfe44d0022f..0d77b997e82 100644 --- a/homeassistant/components/dhcp/manifest.json +++ b/homeassistant/components/dhcp/manifest.json @@ -14,7 +14,7 @@ ], "quality_scale": "internal", "requirements": [ - "aiodhcpwatcher==0.8.2", + "aiodhcpwatcher==1.0.0", "aiodiscover==2.0.0", "cached_ipaddress==0.3.0" ] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 11acc893b79..49b7b032dcb 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -1,6 +1,6 @@ # Automatically generated by gen_requirements_all.py, do not edit -aiodhcpwatcher==0.8.2 +aiodhcpwatcher==1.0.0 aiodiscover==2.0.0 aiohttp-fast-url-dispatcher==0.3.0 aiohttp-zlib-ng==0.3.1 diff --git a/requirements_all.txt b/requirements_all.txt index 1555c4e1601..1c15c49d8cb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ aiobotocore==2.9.1 aiocomelit==0.9.0 # homeassistant.components.dhcp -aiodhcpwatcher==0.8.2 +aiodhcpwatcher==1.0.0 # homeassistant.components.dhcp aiodiscover==2.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c3f9f98a7b0..33614520ee1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,7 +197,7 @@ aiobotocore==2.9.1 aiocomelit==0.9.0 # homeassistant.components.dhcp -aiodhcpwatcher==0.8.2 +aiodhcpwatcher==1.0.0 # homeassistant.components.dhcp aiodiscover==2.0.0 diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index ea0b64e4219..2ba651307ed 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -145,8 +145,8 @@ async def _async_get_handle_dhcp_packet( {}, integration_matchers, ) - with patch("aiodhcpwatcher.start"): - dhcp_watcher.async_start() + with patch("aiodhcpwatcher.async_start"): + await dhcp_watcher.async_start() def _async_handle_dhcp_request(request: aiodhcpwatcher.DHCPRequest) -> None: dhcp_watcher._async_process_dhcp_request(request) From 09934d44c4bd01d28faca5d5dde38441230d9f61 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 14:28:27 -1000 Subject: [PATCH 1073/1691] Reduce script overhead by avoiding creation of many tasks (#113183) * Reduce script overhead by avoiding creation of many tasks * no eager stop * reduce * make sure wait being cancelled is handled * make sure wait being cancelled is handled * make sure wait being cancelled is handled * preen * preen * result already raises cancelled error, remove redundant code * no need to raise it into the future * will never set an exception * Simplify long action script implementation * comment * preen * dry * dry * preen * dry * preen * no need to access protected * no need to access protected * dry * name * dry * dry * dry * dry * reduce name changes * drop one more task * stale comment * stale comment --- homeassistant/helpers/script.py | 288 ++++++++++++++++---------------- 1 file changed, 147 insertions(+), 141 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 7a1f0bdb8da..0f625086235 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import AsyncGenerator, Callable, Mapping, Sequence -from contextlib import asynccontextmanager, suppress +from contextlib import asynccontextmanager from contextvars import ContextVar from copy import copy from dataclasses import dataclass @@ -15,6 +15,7 @@ import logging from types import MappingProxyType from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, cast +import async_interrupt import voluptuous as vol from homeassistant import exceptions @@ -157,6 +158,16 @@ SCRIPT_DEBUG_CONTINUE_ALL = "script_debug_continue_all" script_stack_cv: ContextVar[list[int] | None] = ContextVar("script_stack", default=None) +class ScriptStoppedError(Exception): + """Error to indicate that the script has been stopped.""" + + +def _set_result_unless_done(future: asyncio.Future[None]) -> None: + """Set result of future unless it is done.""" + if not future.done(): + future.set_result(None) + + def action_trace_append(variables, path): """Append a TraceElement to trace[path].""" trace_element = TraceElement(variables, path) @@ -168,7 +179,7 @@ def action_trace_append(variables, path): async def trace_action( hass: HomeAssistant, script_run: _ScriptRun, - stop: asyncio.Event, + stop: asyncio.Future[None], variables: dict[str, Any], ) -> AsyncGenerator[TraceElement, None]: """Trace action execution.""" @@ -199,13 +210,13 @@ async def trace_action( ): async_dispatcher_send(hass, SCRIPT_BREAKPOINT_HIT, key, run_id, path) - done = asyncio.Event() + done = hass.loop.create_future() @callback def async_continue_stop(command=None): if command == "stop": - stop.set() - done.set() + _set_result_unless_done(stop) + _set_result_unless_done(done) signal = SCRIPT_DEBUG_CONTINUE_STOP.format(key, run_id) remove_signal1 = async_dispatcher_connect(hass, signal, async_continue_stop) @@ -213,10 +224,7 @@ async def trace_action( hass, SCRIPT_DEBUG_CONTINUE_ALL, async_continue_stop ) - tasks = [hass.async_create_task(flag.wait()) for flag in (stop, done)] - await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) - for task in tasks: - task.cancel() + await asyncio.wait([stop, done], return_when=asyncio.FIRST_COMPLETED) remove_signal1() remove_signal2() @@ -393,12 +401,12 @@ class _ScriptRun: self._log_exceptions = log_exceptions self._step = -1 self._started = False - self._stop = asyncio.Event() + self._stop = hass.loop.create_future() self._stopped = asyncio.Event() self._conversation_response: str | None | UndefinedType = UNDEFINED def _changed(self) -> None: - if not self._stop.is_set(): + if not self._stop.done(): self._script._changed() # pylint: disable=protected-access async def _async_get_condition(self, config): @@ -432,7 +440,7 @@ class _ScriptRun: try: self._log("Running %s", self._script.running_description) for self._step, self._action in enumerate(self._script.sequence): - if self._stop.is_set(): + if self._stop.done(): script_execution_set("cancelled") break await self._async_step(log_exceptions=False) @@ -471,7 +479,7 @@ class _ScriptRun: async with trace_action( self._hass, self, self._stop, self._variables ) as trace_element: - if self._stop.is_set(): + if self._stop.done(): return action = cv.determine_script_action(self._action) @@ -483,8 +491,8 @@ class _ScriptRun: trace_set_result(enabled=False) return + handler = f"_async_{action}_step" try: - handler = f"_async_{action}_step" await getattr(self, handler)() except Exception as ex: # pylint: disable=broad-except self._handle_exception( @@ -502,7 +510,7 @@ class _ScriptRun: async def async_stop(self) -> None: """Stop script run.""" - self._stop.set() + _set_result_unless_done(self._stop) # If the script was never started # the stopped event will never be # set because the script will never @@ -576,9 +584,9 @@ class _ScriptRun: level=level, ) - def _get_pos_time_period_template(self, key): + def _get_pos_time_period_template(self, key: str) -> timedelta: try: - return cv.positive_time_period( + return cv.positive_time_period( # type: ignore[no-any-return] template.render_complex(self._action[key], self._variables) ) except (exceptions.TemplateError, vol.Invalid) as ex: @@ -593,26 +601,34 @@ class _ScriptRun: async def _async_delay_step(self): """Handle delay.""" - delay = self._get_pos_time_period_template(CONF_DELAY) + delay_delta = self._get_pos_time_period_template(CONF_DELAY) - self._step_log(f"delay {delay}") + self._step_log(f"delay {delay_delta}") - delay = delay.total_seconds() + delay = delay_delta.total_seconds() self._changed() trace_set_result(delay=delay, done=False) + futures, timeout_handle, timeout_future = self._async_futures_with_timeout( + delay + ) + try: - async with asyncio.timeout(delay): - await self._stop.wait() - except TimeoutError: - trace_set_result(delay=delay, done=True) + await asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED) + finally: + if timeout_future.done(): + trace_set_result(delay=delay, done=True) + else: + timeout_handle.cancel() + + def _get_timeout_seconds_from_action(self) -> float | None: + """Get the timeout from the action.""" + if CONF_TIMEOUT in self._action: + return self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() + return None async def _async_wait_template_step(self): """Handle a wait template.""" - if CONF_TIMEOUT in self._action: - timeout = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() - else: - timeout = None - + timeout = self._get_timeout_seconds_from_action() self._step_log("wait template", timeout) self._variables["wait"] = {"remaining": timeout, "completed": False} @@ -626,74 +642,47 @@ class _ScriptRun: self._variables["wait"]["completed"] = True return + futures, timeout_handle, timeout_future = self._async_futures_with_timeout( + timeout + ) + done = self._hass.loop.create_future() + futures.append(done) + @callback def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" - # pylint: disable=protected-access - wait_var = self._variables["wait"] - if to_context and to_context._when: - wait_var["remaining"] = to_context._when - self._hass.loop.time() - else: - wait_var["remaining"] = timeout - wait_var["completed"] = True - done.set() + self._async_set_remaining_time_var(timeout_handle) + self._variables["wait"]["completed"] = True + _set_result_unless_done(done) - to_context = None unsub = async_track_template( self._hass, wait_template, async_script_wait, self._variables ) - self._changed() - done = asyncio.Event() - tasks = [ - self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) - ] - try: - async with asyncio.timeout(timeout) as to_context: - await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) - except TimeoutError as ex: - self._variables["wait"]["remaining"] = 0.0 - if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): - self._log(_TIMEOUT_MSG) - trace_set_result(wait=self._variables["wait"], timeout=True) - raise _AbortScript from ex - finally: - for task in tasks: - task.cancel() - unsub() + await self._async_wait_with_optional_timeout( + futures, timeout_handle, timeout_future, unsub + ) + + def _async_set_remaining_time_var( + self, timeout_handle: asyncio.TimerHandle | None + ) -> None: + """Set the remaining time variable for a wait step.""" + wait_var = self._variables["wait"] + if timeout_handle: + wait_var["remaining"] = timeout_handle.when() - self._hass.loop.time() + else: + wait_var["remaining"] = None async def _async_run_long_action(self, long_task: asyncio.Task[_T]) -> _T | None: """Run a long task while monitoring for stop request.""" - - async def async_cancel_long_task() -> None: - # Stop long task and wait for it to finish. - long_task.cancel() - with suppress(Exception): - await long_task - - # Wait for long task while monitoring for a stop request. - stop_task = self._hass.async_create_task(self._stop.wait()) try: - await asyncio.wait( - {long_task, stop_task}, return_when=asyncio.FIRST_COMPLETED - ) - # If our task is cancelled, then cancel long task, too. Note that if long task - # is cancelled otherwise the CancelledError exception will not be raised to - # here due to the call to asyncio.wait(). Rather we'll check for that below. - except asyncio.CancelledError: - await async_cancel_long_task() - raise - finally: - stop_task.cancel() - - if long_task.cancelled(): - raise asyncio.CancelledError - if long_task.done(): - # Propagate any exceptions that occurred. - return long_task.result() - # Stopped before long task completed, so cancel it. - await async_cancel_long_task() - return None + async with async_interrupt.interrupt(self._stop, ScriptStoppedError, None): + # if stop is set, interrupt will cancel inside the context + # manager which will cancel long_task, and raise + # ScriptStoppedError outside the context manager + return await long_task + except ScriptStoppedError as ex: + raise asyncio.CancelledError from ex async def _async_call_service_step(self): """Call the service specified in the action.""" @@ -735,8 +724,9 @@ class _ScriptRun: blocking=True, context=self._context, return_response=return_response, - ) - ), + ), + eager_start=True, + ) ) if response_variable: self._variables[response_variable] = response_data @@ -866,7 +856,7 @@ class _ScriptRun: for iteration in range(1, count + 1): set_repeat_var(iteration, count) await async_run_sequence(iteration, extra_msg) - if self._stop.is_set(): + if self._stop.done(): break elif CONF_FOR_EACH in repeat: @@ -894,7 +884,7 @@ class _ScriptRun: for iteration, item in enumerate(items, 1): set_repeat_var(iteration, count, item) extra_msg = f" of {count} with item: {repr(item)}" - if self._stop.is_set(): + if self._stop.done(): break await async_run_sequence(iteration, extra_msg) @@ -905,7 +895,7 @@ class _ScriptRun: for iteration in itertools.count(1): set_repeat_var(iteration) try: - if self._stop.is_set(): + if self._stop.done(): break if not self._test_conditions(conditions, "while"): break @@ -923,7 +913,7 @@ class _ScriptRun: set_repeat_var(iteration) await async_run_sequence(iteration) try: - if self._stop.is_set(): + if self._stop.done(): break if self._test_conditions(conditions, "until") in [True, None]: break @@ -983,12 +973,35 @@ class _ScriptRun: with trace_path("else"): await self._async_run_script(if_data["if_else"]) + def _async_futures_with_timeout( + self, + timeout: float | None, + ) -> tuple[ + list[asyncio.Future[None]], + asyncio.TimerHandle | None, + asyncio.Future[None] | None, + ]: + """Return a list of futures to wait for. + + The list will contain the stop future. + + If timeout is set, a timeout future and handle will be created + and will be added to the list of futures. + """ + timeout_handle: asyncio.TimerHandle | None = None + timeout_future: asyncio.Future[None] | None = None + futures: list[asyncio.Future[None]] = [self._stop] + if timeout: + timeout_future = self._hass.loop.create_future() + timeout_handle = self._hass.loop.call_later( + timeout, _set_result_unless_done, timeout_future + ) + futures.append(timeout_future) + return futures, timeout_handle, timeout_future + async def _async_wait_for_trigger_step(self): """Wait for a trigger event.""" - if CONF_TIMEOUT in self._action: - timeout = self._get_pos_time_period_template(CONF_TIMEOUT).total_seconds() - else: - timeout = None + timeout = self._get_timeout_seconds_from_action() self._step_log("wait for trigger", timeout) @@ -996,22 +1009,20 @@ class _ScriptRun: self._variables["wait"] = {"remaining": timeout, "trigger": None} trace_set_result(wait=self._variables["wait"]) - done = asyncio.Event() + futures, timeout_handle, timeout_future = self._async_futures_with_timeout( + timeout + ) + done = self._hass.loop.create_future() + futures.append(done) async def async_done(variables, context=None): - # pylint: disable=protected-access - wait_var = self._variables["wait"] - if to_context and to_context._when: - wait_var["remaining"] = to_context._when - self._hass.loop.time() - else: - wait_var["remaining"] = timeout - wait_var["trigger"] = variables["trigger"] - done.set() + self._async_set_remaining_time_var(timeout_handle) + self._variables["wait"]["trigger"] = variables["trigger"] + _set_result_unless_done(done) def log_cb(level, msg, **kwargs): self._log(msg, level=level, **kwargs) - to_context = None remove_triggers = await async_initialize_triggers( self._hass, self._action[CONF_WAIT_FOR_TRIGGER], @@ -1023,24 +1034,31 @@ class _ScriptRun: ) if not remove_triggers: return - self._changed() - tasks = [ - self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) - ] + await self._async_wait_with_optional_timeout( + futures, timeout_handle, timeout_future, remove_triggers + ) + + async def _async_wait_with_optional_timeout( + self, + futures: list[asyncio.Future[None]], + timeout_handle: asyncio.TimerHandle | None, + timeout_future: asyncio.Future[None] | None, + unsub: Callable[[], None], + ) -> None: try: - async with asyncio.timeout(timeout) as to_context: - await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) - except TimeoutError as ex: - self._variables["wait"]["remaining"] = 0.0 - if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): - self._log(_TIMEOUT_MSG) - trace_set_result(wait=self._variables["wait"], timeout=True) - raise _AbortScript from ex + await asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED) + if timeout_future and timeout_future.done(): + self._variables["wait"]["remaining"] = 0.0 + if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): + self._log(_TIMEOUT_MSG) + trace_set_result(wait=self._variables["wait"], timeout=True) + raise _AbortScript from TimeoutError() finally: - for task in tasks: - task.cancel() - remove_triggers() + if timeout_future and not timeout_future.done() and timeout_handle: + timeout_handle.cancel() + + unsub() async def _async_variables_step(self): """Set a variable value.""" @@ -1107,7 +1125,7 @@ class _ScriptRun: """Execute a script.""" result = await self._async_run_long_action( self._hass.async_create_task( - script.async_run(self._variables, self._context) + script.async_run(self._variables, self._context), eager_start=True ) ) if result and result.conversation_response is not UNDEFINED: @@ -1123,29 +1141,17 @@ class _QueuedScriptRun(_ScriptRun): """Run script.""" # Wait for previous run, if any, to finish by attempting to acquire the script's # shared lock. At the same time monitor if we've been told to stop. - lock_task = self._hass.async_create_task( - self._script._queue_lck.acquire() # pylint: disable=protected-access - ) - stop_task = self._hass.async_create_task(self._stop.wait()) try: - await asyncio.wait( - {lock_task, stop_task}, return_when=asyncio.FIRST_COMPLETED - ) - except asyncio.CancelledError: + async with async_interrupt.interrupt(self._stop, ScriptStoppedError, None): + await self._script._queue_lck.acquire() # pylint: disable=protected-access + except ScriptStoppedError as ex: + # If we've been told to stop, then just finish up. self._finish() - raise - else: - self.lock_acquired = lock_task.done() and not lock_task.cancelled() - finally: - lock_task.cancel() - stop_task.cancel() + raise asyncio.CancelledError from ex - # If we've been told to stop, then just finish up. Otherwise, we've acquired the - # lock so we can go ahead and start the run. - if self._stop.is_set(): - self._finish() - else: - await super().async_run() + self.lock_acquired = True + # We've acquired the lock so we can go ahead and start the run. + await super().async_run() def _finish(self) -> None: if self.lock_acquired: From 052d7d1e197b0c509a17a5fe86f7b274810a7080 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 14:56:57 -1000 Subject: [PATCH 1074/1691] Fix scene integration doing blocking I/O in the event loop to import platforms (#113391) --- homeassistant/components/homeassistant/__init__.py | 5 +++++ homeassistant/components/scene/__init__.py | 12 +++++------- tests/components/scene/test_init.py | 12 ++++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index b14194c425f..6d32f175a8a 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -35,6 +35,11 @@ from homeassistant.helpers.service import ( from homeassistant.helpers.template import async_load_custom_templates from homeassistant.helpers.typing import ConfigType +# The scene integration will do a late import of scene +# so we want to make sure its loaded with the component +# so its already in memory when its imported so the import +# does not do blocking I/O in the event loop. +from . import scene as scene_pre_import # noqa: F401 from .const import ( DATA_EXPOSED_ENTITIES, DATA_STOP_HANDLER, diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index ca56f5c8682..ee1937b65c9 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -32,15 +32,13 @@ def _hass_domain_validator(config: dict[str, Any]) -> dict[str, Any]: def _platform_validator(config: dict[str, Any]) -> dict[str, Any]: """Validate it is a valid platform.""" + platform_name = config[CONF_PLATFORM] try: - platform = importlib.import_module(f".{config[CONF_PLATFORM]}", __name__) + platform = importlib.import_module( + f"homeassistant.components.{platform_name}.scene" + ) except ImportError: - try: - platform = importlib.import_module( - f"homeassistant.components.{config[CONF_PLATFORM]}.scene" - ) - except ImportError: - raise vol.Invalid("Invalid platform specified") from None + raise vol.Invalid("Invalid platform specified") from None if not hasattr(platform, "PLATFORM_SCHEMA"): return config diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 82f29328850..c797c26f35a 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -262,3 +262,15 @@ async def turn_off_lights(hass, entity_ids): blocking=True, ) await hass.async_block_till_done() + + +async def test_invalid_platform( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test invalid platform.""" + await async_setup_component( + hass, scene.DOMAIN, {scene.DOMAIN: {"platform": "does_not_exist"}} + ) + await hass.async_block_till_done() + assert "Invalid platform specified" in caplog.text + assert "does_not_exist" in caplog.text From 3528cc86d718da0961e3d7c0969c3be3fdbfaccb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 15:11:09 -1000 Subject: [PATCH 1075/1691] Fix delayed registry check to only using the short delay at running (#113471) --- homeassistant/helpers/registry.py | 6 ++---- tests/helpers/test_registry.py | 29 ++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/homeassistant/helpers/registry.py b/homeassistant/helpers/registry.py index 83079af6228..d5b1035531a 100644 --- a/homeassistant/helpers/registry.py +++ b/homeassistant/helpers/registry.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from .storage import Store SAVE_DELAY = 10 -SAVE_DELAY_STARTING = 300 +SAVE_DELAY_LONG = 180 class BaseRegistry(ABC): @@ -25,9 +25,7 @@ class BaseRegistry(ABC): """Schedule saving the registry.""" # Schedule the save past startup to avoid writing # the file while the system is starting. - delay = ( - SAVE_DELAY_STARTING if self.hass.state is CoreState.starting else SAVE_DELAY - ) + delay = SAVE_DELAY if self.hass.state is CoreState.running else SAVE_DELAY_LONG self._store.async_delay_save(self._data_to_save, delay) @callback diff --git a/tests/helpers/test_registry.py b/tests/helpers/test_registry.py index 40e3d0cbb28..2cfa3be26c7 100644 --- a/tests/helpers/test_registry.py +++ b/tests/helpers/test_registry.py @@ -3,10 +3,11 @@ from typing import Any from freezegun.api import FrozenDateTimeFactory +import pytest from homeassistant.core import CoreState, HomeAssistant from homeassistant.helpers import storage -from homeassistant.helpers.registry import SAVE_DELAY, SAVE_DELAY_STARTING, BaseRegistry +from homeassistant.helpers.registry import SAVE_DELAY, SAVE_DELAY_LONG, BaseRegistry from tests.common import async_fire_time_changed @@ -26,12 +27,30 @@ class SampleRegistry(BaseRegistry): return None +@pytest.mark.parametrize( + "long_delay_state", + ( + CoreState.not_running, + CoreState.starting, + CoreState.stopped, + CoreState.final_write, + ), +) async def test_async_schedule_save( - hass: HomeAssistant, freezer: FrozenDateTimeFactory, hass_storage: dict[str, Any] + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + long_delay_state: CoreState, + hass_storage: dict[str, Any], ) -> None: - """Test saving the registry.""" + """Test saving the registry. + + If CoreState is not running, it should save with long delay. + + Storage will always save at final write if there is a + write pending so we should not schedule a save in that case. + """ registry = SampleRegistry(hass) - hass.set_state(CoreState.starting) + hass.set_state(long_delay_state) registry.async_schedule_save() freezer.tick(SAVE_DELAY) @@ -39,7 +58,7 @@ async def test_async_schedule_save( await hass.async_block_till_done() assert registry.save_calls == 0 - freezer.tick(SAVE_DELAY_STARTING) + freezer.tick(SAVE_DELAY_LONG) async_fire_time_changed(hass) await hass.async_block_till_done() assert registry.save_calls == 1 From 92e73312ea101ef9f3029d33c22b5cf4534c8944 Mon Sep 17 00:00:00 2001 From: Lex Li <425130+lextm@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:19:18 -0400 Subject: [PATCH 1076/1691] Bump `pysnmp-lextudio` to version `6.0.11` (#113463) --- homeassistant/components/snmp/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/snmp/manifest.json b/homeassistant/components/snmp/manifest.json index cd6c1dd9152..c4aa82f2a74 100644 --- a/homeassistant/components/snmp/manifest.json +++ b/homeassistant/components/snmp/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/snmp", "iot_class": "local_polling", "loggers": ["pyasn1", "pysmi", "pysnmp"], - "requirements": ["pysnmp-lextudio==6.0.9"] + "requirements": ["pysnmp-lextudio==6.0.11"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1c15c49d8cb..3ec9eefa6e9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2149,7 +2149,7 @@ pysmartthings==0.7.8 pysml==0.0.12 # homeassistant.components.snmp -pysnmp-lextudio==6.0.9 +pysnmp-lextudio==6.0.11 # homeassistant.components.snooz pysnooz==0.8.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33614520ee1..5f83e4007c5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1670,7 +1670,7 @@ pysmartthings==0.7.8 pysml==0.0.12 # homeassistant.components.snmp -pysnmp-lextudio==6.0.9 +pysnmp-lextudio==6.0.11 # homeassistant.components.snooz pysnooz==0.8.6 From bdede0e0daa9d518e0781081c0409efbf4d86c8f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Mar 2024 16:53:26 -1000 Subject: [PATCH 1077/1691] Start script runs eagerly (#113190) --- homeassistant/helpers/script.py | 3 ++- tests/components/fan/common.py | 8 ++++++++ .../homeassistant/triggers/test_numeric_state.py | 10 ++++++++-- tests/components/homeassistant/triggers/test_state.py | 10 ++++++++-- tests/components/template/test_fan.py | 1 + tests/helpers/test_script.py | 9 ++++++--- 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 0f625086235..99d950d4d76 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -77,6 +77,7 @@ from homeassistant.core import ( callback, ) from homeassistant.util import slugify +from homeassistant.util.async_ import create_eager_task from homeassistant.util.dt import utcnow from . import condition, config_validation as cv, service, template @@ -1611,7 +1612,7 @@ class Script: self._changed() try: - return await asyncio.shield(run.async_run()) + return await asyncio.shield(create_eager_task(run.async_run())) except asyncio.CancelledError: await run.async_stop() self._changed() diff --git a/tests/components/fan/common.py b/tests/components/fan/common.py index 40fab746e8d..7955a91bc0a 100644 --- a/tests/components/fan/common.py +++ b/tests/components/fan/common.py @@ -44,6 +44,7 @@ async def async_turn_on( } await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data, blocking=True) + await hass.async_block_till_done() async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL) -> None: @@ -51,6 +52,7 @@ async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL) -> None: data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data, blocking=True) + await hass.async_block_till_done() async def async_oscillate( @@ -67,6 +69,7 @@ async def async_oscillate( } await hass.services.async_call(DOMAIN, SERVICE_OSCILLATE, data, blocking=True) + await hass.async_block_till_done() async def async_set_preset_mode( @@ -80,6 +83,7 @@ async def async_set_preset_mode( } await hass.services.async_call(DOMAIN, SERVICE_SET_PRESET_MODE, data, blocking=True) + await hass.async_block_till_done() async def async_set_percentage( @@ -93,6 +97,7 @@ async def async_set_percentage( } await hass.services.async_call(DOMAIN, SERVICE_SET_PERCENTAGE, data, blocking=True) + await hass.async_block_till_done() async def async_increase_speed( @@ -109,6 +114,7 @@ async def async_increase_speed( } await hass.services.async_call(DOMAIN, SERVICE_INCREASE_SPEED, data, blocking=True) + await hass.async_block_till_done() async def async_decrease_speed( @@ -125,6 +131,7 @@ async def async_decrease_speed( } await hass.services.async_call(DOMAIN, SERVICE_DECREASE_SPEED, data, blocking=True) + await hass.async_block_till_done() async def async_set_direction( @@ -138,3 +145,4 @@ async def async_set_direction( } await hass.services.async_call(DOMAIN, SERVICE_SET_DIRECTION, data, blocking=True) + await hass.async_block_till_done() diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 9357d29ac38..7c33e0d81a7 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -1207,7 +1207,10 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop( "below": below, "for": {"seconds": 5}, }, - "action": {"service": "test.automation"}, + "action": [ + {"delay": "0.0001"}, + {"service": "test.automation"}, + ], } }, ) @@ -1833,7 +1836,10 @@ async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( "attribute": "test-measurement", "for": 5, }, - "action": {"service": "test.automation"}, + "action": [ + {"delay": "0.0001"}, + {"service": "test.automation"}, + ], } }, ) diff --git a/tests/components/homeassistant/triggers/test_state.py b/tests/components/homeassistant/triggers/test_state.py index 0045888f971..9d1d60031e0 100644 --- a/tests/components/homeassistant/triggers/test_state.py +++ b/tests/components/homeassistant/triggers/test_state.py @@ -666,7 +666,10 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop( "to": "world", "for": {"seconds": 5}, }, - "action": {"service": "test.automation"}, + "action": [ + {"delay": "0.0001"}, + {"service": "test.automation"}, + ], } }, ) @@ -1624,7 +1627,10 @@ async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( "attribute": "name", "for": 5, }, - "action": {"service": "test.automation"}, + "action": [ + {"delay": "0.0001"}, + {"service": "test.automation"}, + ], } }, ) diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 1c4e0d20170..773c67c39db 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -428,6 +428,7 @@ async def test_set_invalid_direction_from_initial_stage( await common.async_turn_on(hass, _TEST_FAN) await common.async_set_direction(hass, _TEST_FAN, "invalid") + assert hass.states.get(_DIRECTION_INPUT_SELECT).state == "" _verify(hass, STATE_ON, 0, None, None, None) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 114a90d39fc..6fed4863dfe 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -3441,7 +3441,8 @@ async def test_parallel_loop( script_obj = script.Script(hass, sequence, "Test Name", "test_domain") hass.async_create_task( - script_obj.async_run(MappingProxyType({"what": "world"}), Context()) + script_obj.async_run(MappingProxyType({"what": "world"}), Context()), + eager_start=True, ) await hass.async_block_till_done() @@ -3456,7 +3457,6 @@ async def test_parallel_loop( expected_trace = { "0": [{"variables": {"what": "world"}}], "0/parallel/0/sequence/0": [{}], - "0/parallel/1/sequence/0": [{}], "0/parallel/0/sequence/0/repeat/sequence/0": [ { "variables": { @@ -3492,6 +3492,7 @@ async def test_parallel_loop( "result": {"event": "loop1", "event_data": {"hello1": "loop1_c"}}, }, ], + "0/parallel/1/sequence/0": [{}], "0/parallel/1/sequence/0/repeat/sequence/0": [ { "variables": { @@ -4118,7 +4119,9 @@ async def test_max_exceeded( ) hass.states.async_set("switch.test", "on") for _ in range(max_runs + 1): - hass.async_create_task(script_obj.async_run(context=Context())) + hass.async_create_task( + script_obj.async_run(context=Context()), eager_start=True + ) hass.states.async_set("switch.test", "off") await hass.async_block_till_done() if max_exceeded is None: From dd9fdac51eb026f3d612761d9151da268c62e0d2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 15 Mar 2024 09:25:10 +0100 Subject: [PATCH 1078/1691] Remove unused tts fixture (#113480) --- tests/components/tts/test_init.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index aae61e69a54..681b14eecf0 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -43,12 +43,6 @@ from tests.typing import ClientSessionGenerator, WebSocketGenerator ORIG_WRITE_TAGS = tts.SpeechManager.write_tags -@pytest.fixture -async def setup_tts(hass: HomeAssistant, mock_tts: None) -> None: - """Mock TTS.""" - assert await async_setup_component(hass, tts.DOMAIN, {"tts": {"platform": "test"}}) - - class DefaultEntity(tts.TextToSpeechEntity): """Test entity.""" From 2b2b5a2b027bdce13c18867b592cfcf74ab28af8 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 Mar 2024 09:38:47 +0100 Subject: [PATCH 1079/1691] Bump axis to v55 (#113479) --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index f56df16918e..c1471d370a5 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==54"], + "requirements": ["axis==55"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index 3ec9eefa6e9..068ddefd470 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==54 +axis==55 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5f83e4007c5..98567b2d672 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -451,7 +451,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==54 +axis==55 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From 4df2398b9f95a043108f49f4ba0235ae282110db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 00:02:10 -1000 Subject: [PATCH 1080/1691] Move loading of ha-av to the executor (#113485) --- homeassistant/components/stream/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 3c36a78f13c..4959f51a0db 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -220,7 +220,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: logging.getLogger(logging_namespace).setLevel(logging.ERROR) # This will load av so we run it in the executor - await hass.async_add_import_executor_job(set_pyav_logging, debug_enabled) + await hass.async_add_executor_job(set_pyav_logging, debug_enabled) # Keep import here so that we can import stream integration without installing reqs # pylint: disable-next=import-outside-toplevel From c69495b64f7c06ae5519c002a42901174dfc252e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 00:13:44 -1000 Subject: [PATCH 1081/1691] Remove supports_encryption check from mobile_app (#113490) helpers already ports nacl at top level so this check can never be False ``` >>> import sys >>> from nacl.secret import SecretBox >>> assert nacl in sys.modules >>> ``` --- homeassistant/components/mobile_app/helpers.py | 10 ---------- homeassistant/components/mobile_app/http_api.py | 3 +-- homeassistant/components/mobile_app/webhook.py | 9 --------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index e30974123df..13d50b7984f 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -143,16 +143,6 @@ def error_response( ) -def supports_encryption() -> bool: - """Test if we support encryption.""" - try: - import nacl # noqa: F401 pylint: disable=import-outside-toplevel - - return True - except OSError: - return False - - def safe_registration(registration: dict) -> dict: """Return a registration without sensitive values.""" # Sensitive values: webhook_id, secret, cloudhook_url diff --git a/homeassistant/components/mobile_app/http_api.py b/homeassistant/components/mobile_app/http_api.py index 8c071fe0342..f4786b2914c 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -35,7 +35,6 @@ from .const import ( DOMAIN, SCHEMA_APP_DATA, ) -from .helpers import supports_encryption from .util import async_create_cloud_hook @@ -77,7 +76,7 @@ class RegistrationsView(HomeAssistantView): data[CONF_WEBHOOK_ID] = webhook_id - if data[ATTR_SUPPORTS_ENCRYPTION] and supports_encryption(): + if data[ATTR_SUPPORTS_ENCRYPTION]: data[CONF_SECRET] = secrets.token_hex(SecretBox.KEY_SIZE) data[CONF_USER_ID] = request["hass_user"].id diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 0a55a5337b3..3cef5ae645a 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -100,7 +100,6 @@ from .const import ( DATA_DEVICES, DOMAIN, ERR_ENCRYPTION_ALREADY_ENABLED, - ERR_ENCRYPTION_NOT_AVAILABLE, ERR_ENCRYPTION_REQUIRED, ERR_INVALID_FORMAT, ERR_SENSOR_NOT_REGISTERED, @@ -115,7 +114,6 @@ from .helpers import ( error_response, registration_context, safe_registration, - supports_encryption, webhook_response, ) @@ -484,13 +482,6 @@ async def webhook_enable_encryption( ERR_ENCRYPTION_ALREADY_ENABLED, "Encryption already enabled" ) - if not supports_encryption(): - _LOGGER.warning( - "Unable to enable encryption for %s because libsodium is unavailable!", - config_entry.data[ATTR_DEVICE_NAME], - ) - return error_response(ERR_ENCRYPTION_NOT_AVAILABLE, "Encryption is unavailable") - secret = secrets.token_hex(SecretBox.KEY_SIZE) update_data = { From 99e29b75ccc9fa747f3a60d8d39cdac025375f21 Mon Sep 17 00:00:00 2001 From: On Freund Date: Fri, 15 Mar 2024 12:42:53 +0200 Subject: [PATCH 1082/1691] Revert setting communication delay in Risco init (#113497) --- homeassistant/components/risco/__init__.py | 34 +++++----------------- tests/components/risco/conftest.py | 10 ------- tests/components/risco/test_init.py | 22 -------------- 3 files changed, 8 insertions(+), 58 deletions(-) delete mode 100644 tests/components/risco/test_init.py diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index 2c55b474e00..02e51bcc4ea 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -38,12 +38,10 @@ from homeassistant.helpers.storage import Store from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( - CONF_COMMUNICATION_DELAY, DATA_COORDINATOR, DEFAULT_SCAN_INTERVAL, DOMAIN, EVENTS_COORDINATOR, - MAX_COMMUNICATION_DELAY, TYPE_LOCAL, ) @@ -86,31 +84,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data = entry.data - comm_delay = initial_delay = data.get(CONF_COMMUNICATION_DELAY, 0) + risco = RiscoLocal(data[CONF_HOST], data[CONF_PORT], data[CONF_PIN]) - while True: - risco = RiscoLocal( - data[CONF_HOST], - data[CONF_PORT], - data[CONF_PIN], - communication_delay=comm_delay, - ) - try: - await risco.connect() - except CannotConnectError as error: - if comm_delay >= MAX_COMMUNICATION_DELAY: - raise ConfigEntryNotReady() from error - comm_delay += 1 - except UnauthorizedError: - _LOGGER.exception("Failed to login to Risco cloud") - return False - else: - break - - if comm_delay > initial_delay: - new_data = data.copy() - new_data[CONF_COMMUNICATION_DELAY] = comm_delay - hass.config_entries.async_update_entry(entry, data=new_data) + try: + await risco.connect() + except CannotConnectError as error: + raise ConfigEntryNotReady() from error + except UnauthorizedError: + _LOGGER.exception("Failed to login to Risco cloud") + return False async def _error(error: Exception) -> None: _LOGGER.error("Error in Risco library: %s", error) diff --git a/tests/components/risco/conftest.py b/tests/components/risco/conftest.py index 6e86e04be7d..a27225fce84 100644 --- a/tests/components/risco/conftest.py +++ b/tests/components/risco/conftest.py @@ -172,16 +172,6 @@ def connect_with_error(exception): yield -@pytest.fixture -def connect_with_single_error(exception): - """Fixture to simulate error on connect.""" - with patch( - "homeassistant.components.risco.RiscoLocal.connect", - side_effect=[exception, None], - ): - yield - - @pytest.fixture async def setup_risco_local(hass, local_config_entry): """Set up a local Risco integration for testing.""" diff --git a/tests/components/risco/test_init.py b/tests/components/risco/test_init.py deleted file mode 100644 index 18378c5c9c4..00000000000 --- a/tests/components/risco/test_init.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Tests for the Risco initialization.""" - -import pytest - -from homeassistant.components.risco import CannotConnectError -from homeassistant.components.risco.const import CONF_COMMUNICATION_DELAY -from homeassistant.core import HomeAssistant - - -@pytest.mark.parametrize("exception", [CannotConnectError]) -async def test_single_error_on_connect( - hass: HomeAssistant, connect_with_single_error, local_config_entry -) -> None: - """Test single error on connect to validate communication delay update from 0 (default) to 1.""" - expected_data = { - **local_config_entry.data, - **{"type": "local", CONF_COMMUNICATION_DELAY: 1}, - } - - await hass.config_entries.async_setup(local_config_entry.entry_id) - await hass.async_block_till_done() - assert local_config_entry.data == expected_data From 3e3cf45a5dc717e9c15842815e339c6b0f165156 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Fri, 15 Mar 2024 13:00:08 +0200 Subject: [PATCH 1083/1691] Bump croniter to 2.0.2 (#113494) --- homeassistant/components/utility_meter/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/utility_meter/manifest.json b/homeassistant/components/utility_meter/manifest.json index 11aaf5307c8..25e803e6a2d 100644 --- a/homeassistant/components/utility_meter/manifest.json +++ b/homeassistant/components/utility_meter/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["croniter"], "quality_scale": "internal", - "requirements": ["croniter==1.0.6"] + "requirements": ["croniter==2.0.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 068ddefd470..635c0deb245 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -666,7 +666,7 @@ connect-box==0.2.8 construct==2.10.68 # homeassistant.components.utility_meter -croniter==1.0.6 +croniter==2.0.2 # homeassistant.components.crownstone crownstone-cloud==1.4.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 98567b2d672..eef277af206 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -550,7 +550,7 @@ colorthief==0.2.1 construct==2.10.68 # homeassistant.components.utility_meter -croniter==1.0.6 +croniter==2.0.2 # homeassistant.components.crownstone crownstone-cloud==1.4.9 From 103b8b4dc52e45083267312b94452d7e6a600bab Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 15 Mar 2024 12:01:11 +0100 Subject: [PATCH 1084/1691] Improve State docstring (#113501) --- homeassistant/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 629206e75f1..7f10d8f6df5 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1540,8 +1540,8 @@ class State: entity_id: the entity that is represented. state: the state of the entity attributes: extra information on entity and state - last_changed: last time the state was changed, not the attributes. - last_updated: last time this object was updated. + last_changed: last time the state was changed. + last_updated: last time the state or attributes were changed. context: Context in which it was created domain: Domain of this state. object_id: Object id of this state. From 45195d2ea975e194c8affec4393766cd5134d865 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 01:03:44 -1000 Subject: [PATCH 1085/1691] Avoid multiple context switches to setup a sonos speaker (#113378) --- homeassistant/components/sonos/speaker.py | 69 ++++++++++++----------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index ebc981790fa..2d92fe2f741 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -31,7 +31,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, dispatcher_send, ) -from homeassistant.helpers.event import async_track_time_interval, track_time_interval +from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import dt as dt_util from .alarms import SonosAlarms @@ -127,7 +127,6 @@ class SonosSpeaker: zone_group_state_sub.callback = self.async_dispatch_event self._subscriptions.append(zone_group_state_sub) self._subscription_lock: asyncio.Lock | None = None - self._event_dispatchers: dict[str, Callable] = {} self._last_activity: float = NEVER_TIME self._last_event_cache: dict[str, Any] = {} self.activity_stats: ActivityStatistics = ActivityStatistics(self.zone_name) @@ -179,13 +178,22 @@ class SonosSpeaker: self.snapshot_group: list[SonosSpeaker] = [] self._group_members_missing: set[str] = set() - async def async_setup(self, entry: ConfigEntry) -> None: + async def async_setup( + self, entry: ConfigEntry, has_battery: bool, dispatches: list[tuple[Any, ...]] + ) -> None: """Complete setup in async context.""" + # Battery events can be infrequent, polling is still necessary + if has_battery: + self._battery_poll_timer = async_track_time_interval( + self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL + ) + self.websocket = SonosWebsocket( self.soco.ip_address, player_id=self.soco.uid, session=async_get_clientsession(self.hass), ) + dispatch_pairs: tuple[tuple[str, Callable[..., Any]], ...] = ( (SONOS_CHECK_ACTIVITY, self.async_check_activity), (SONOS_SPEAKER_ADDED, self.async_update_group_for_uid), @@ -203,6 +211,11 @@ class SonosSpeaker: ) ) + for dispatch in dispatches: + async_dispatcher_send(self.hass, *dispatch) + + await self.async_subscribe() + def setup(self, entry: ConfigEntry) -> None: """Run initial setup of the speaker.""" self.media.play_mode = self.soco.play_mode @@ -211,53 +224,34 @@ class SonosSpeaker: if self.is_coordinator: self.media.poll_media() - future = asyncio.run_coroutine_threadsafe( - self.async_setup(entry), self.hass.loop - ) - future.result(timeout=10) - - dispatcher_send(self.hass, SONOS_CREATE_LEVELS, self) + dispatches: list[tuple[Any, ...]] = [(SONOS_CREATE_LEVELS, self)] if audio_format := self.soco.soundbar_audio_input_format: - dispatcher_send( - self.hass, SONOS_CREATE_AUDIO_FORMAT_SENSOR, self, audio_format - ) + dispatches.append((SONOS_CREATE_AUDIO_FORMAT_SENSOR, self, audio_format)) + has_battery = False try: self.battery_info = self.fetch_battery_info() except SonosUpdateError: _LOGGER.debug("No battery available for %s", self.zone_name) else: - # Battery events can be infrequent, polling is still necessary - self._battery_poll_timer = track_time_interval( - self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL - ) + has_battery = True dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self) if (mic_enabled := self.soco.mic_enabled) is not None: self.mic_enabled = mic_enabled - dispatcher_send(self.hass, SONOS_CREATE_MIC_SENSOR, self) + dispatches.append((SONOS_CREATE_MIC_SENSOR, self)) if new_alarms := [ alarm.alarm_id for alarm in self.alarms if alarm.zone.uid == self.soco.uid ]: - dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms) + dispatches.append((SONOS_CREATE_ALARM, self, new_alarms)) - dispatcher_send(self.hass, SONOS_CREATE_SWITCHES, self) + dispatches.append((SONOS_CREATE_SWITCHES, self)) + dispatches.append((SONOS_CREATE_MEDIA_PLAYER, self)) + dispatches.append((SONOS_SPEAKER_ADDED, self.soco.uid)) - self._event_dispatchers = { - "AlarmClock": self.async_dispatch_alarms, - "AVTransport": self.async_dispatch_media_update, - "ContentDirectory": self.async_dispatch_favorites, - "DeviceProperties": self.async_dispatch_device_properties, - "RenderingControl": self.async_update_volume, - "ZoneGroupTopology": self.async_update_groups, - } - - dispatcher_send(self.hass, SONOS_CREATE_MEDIA_PLAYER, self) - dispatcher_send(self.hass, SONOS_SPEAKER_ADDED, self.soco.uid) - - self.hass.create_task(self.async_subscribe()) + self.hass.create_task(self.async_setup(entry, has_battery, dispatches)) # # Entity management @@ -448,7 +442,7 @@ class SonosSpeaker: # Save most recently processed event variables for cache and diagnostics self._last_event_cache[event.service.service_type] = event.variables dispatcher = self._event_dispatchers[event.service.service_type] - dispatcher(event) + dispatcher(self, event) @callback def async_dispatch_alarms(self, event: SonosEvent) -> None: @@ -1145,3 +1139,12 @@ class SonosSpeaker: """Update information about current volume settings.""" self.volume = self.soco.volume self.muted = self.soco.mute + + _event_dispatchers = { + "AlarmClock": async_dispatch_alarms, + "AVTransport": async_dispatch_media_update, + "ContentDirectory": async_dispatch_favorites, + "DeviceProperties": async_dispatch_device_properties, + "RenderingControl": async_update_volume, + "ZoneGroupTopology": async_update_groups, + } From 309f5543369a904d91a6505b57486ca58b53e910 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:04:33 +0100 Subject: [PATCH 1086/1691] Address late review on adding an error sensor to Husqvarna Automower (#113242) --- homeassistant/components/husqvarna_automower/coordinator.py | 2 -- homeassistant/components/husqvarna_automower/entity.py | 6 ------ .../components/husqvarna_automower/snapshots/test_init.ambr | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/coordinator.py b/homeassistant/components/husqvarna_automower/coordinator.py index eb2c06262cf..2188725ed76 100644 --- a/homeassistant/components/husqvarna_automower/coordinator.py +++ b/homeassistant/components/husqvarna_automower/coordinator.py @@ -21,8 +21,6 @@ MAX_WS_RECONNECT_TIME = 600 class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttributes]]): """Class to manage fetching Husqvarna data.""" - config_entry: ConfigEntry - def __init__( self, hass: HomeAssistant, api: AutomowerSession, entry: ConfigEntry ) -> None: diff --git a/homeassistant/components/husqvarna_automower/entity.py b/homeassistant/components/husqvarna_automower/entity.py index c33229a5f64..4d20d2d677b 100644 --- a/homeassistant/components/husqvarna_automower/entity.py +++ b/homeassistant/components/husqvarna_automower/entity.py @@ -3,7 +3,6 @@ import logging from aioautomower.model import MowerAttributes -from aioautomower.utils import structure_token from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -13,8 +12,6 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -HUSQVARNA_URL = "https://developer.husqvarnagroup.cloud" - class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]): """Defining the Automower base Entity.""" @@ -29,10 +26,7 @@ class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]): """Initialize AutomowerEntity.""" super().__init__(coordinator) self.mower_id = mower_id - entry = coordinator.config_entry - structured_token = structure_token(entry.data["token"]["access_token"]) self._attr_device_info = DeviceInfo( - configuration_url=f"{HUSQVARNA_URL}/applications/{structured_token.client_id}", identifiers={(DOMAIN, mower_id)}, manufacturer="Husqvarna", model=self.mower_attributes.system.model, diff --git a/tests/components/husqvarna_automower/snapshots/test_init.ambr b/tests/components/husqvarna_automower/snapshots/test_init.ambr index 63ca15461e1..c3a7191b4b9 100644 --- a/tests/components/husqvarna_automower/snapshots/test_init.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_init.ambr @@ -3,7 +3,7 @@ DeviceRegistryEntrySnapshot({ 'area_id': 'garden', 'config_entries': , - 'configuration_url': 'https://developer.husqvarnagroup.cloud/applications/433e5fdf-5129-452c-xxxx-fadce3213042', + 'configuration_url': None, 'connections': set({ }), 'disabled_by': None, From ffe9b7801f6ba37d3c58874d625b9f86b9d580d9 Mon Sep 17 00:00:00 2001 From: slyoldfox Date: Fri, 15 Mar 2024 12:05:10 +0100 Subject: [PATCH 1087/1691] Add missing mystrom sensor (#113225) Co-authored-by: TheJulianJES Co-authored-by: Franck Nijhof --- homeassistant/components/mystrom/sensor.py | 7 +++++++ homeassistant/components/mystrom/strings.json | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/homeassistant/components/mystrom/sensor.py b/homeassistant/components/mystrom/sensor.py index 5c096c87993..2c35d35dad6 100644 --- a/homeassistant/components/mystrom/sensor.py +++ b/homeassistant/components/mystrom/sensor.py @@ -30,6 +30,13 @@ class MyStromSwitchSensorEntityDescription(SensorEntityDescription): SENSOR_TYPES: tuple[MyStromSwitchSensorEntityDescription, ...] = ( + MyStromSwitchSensorEntityDescription( + key="avg_consumption", + translation_key="avg_consumption", + device_class=SensorDeviceClass.POWER, + native_unit_of_measurement=UnitOfPower.WATT, + value_fn=lambda device: device.consumedWs, + ), MyStromSwitchSensorEntityDescription( key="consumption", device_class=SensorDeviceClass.POWER, diff --git a/homeassistant/components/mystrom/strings.json b/homeassistant/components/mystrom/strings.json index 9ebd1c36df0..80d0866f6f4 100644 --- a/homeassistant/components/mystrom/strings.json +++ b/homeassistant/components/mystrom/strings.json @@ -17,5 +17,12 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "entity": { + "sensor": { + "avg_consumption": { + "name": "Average consumption" + } + } } } From e41133e9f05f3061f19a6810f31ee366bf8e0402 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 Mar 2024 12:05:45 +0100 Subject: [PATCH 1088/1691] Remove old update unique id function from deCONZ binary sensor (#112536) --- .../components/deconz/binary_sensor.py | 28 +------------------ tests/components/deconz/test_binary_sensor.py | 22 +-------------- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 754e5ab427f..3629ff7283e 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -28,12 +28,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -import homeassistant.helpers.entity_registry as er -from .const import ATTR_DARK, ATTR_ON, DOMAIN as DECONZ_DOMAIN +from .const import ATTR_DARK, ATTR_ON from .deconz_device import DeconzDevice from .hub import DeconzHub, get_gateway_from_config_entry -from .util import serial_from_unique_id _SensorDeviceT = TypeVar("_SensorDeviceT", bound=PydeconzSensorBase) @@ -164,29 +162,6 @@ ENTITY_DESCRIPTIONS: tuple[DeconzBinarySensorDescription, ...] = ( ) -@callback -def async_update_unique_id( - hass: HomeAssistant, unique_id: str, description: DeconzBinarySensorDescription -) -> None: - """Update unique ID to always have a suffix. - - Introduced with release 2022.7. - """ - ent_reg = er.async_get(hass) - - new_unique_id = f"{unique_id}-{description.key}" - if ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, new_unique_id): - return - - if description.old_unique_id_suffix: - unique_id = ( - f"{serial_from_unique_id(unique_id)}-{description.old_unique_id_suffix}" - ) - - if entity_id := ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, unique_id): - ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -207,7 +182,6 @@ async def async_setup_entry( and not isinstance(sensor, description.instance_check) ) or description.value_fn(sensor) is None: continue - async_update_unique_id(hass, sensor.unique_id, description) async_add_entities([DeconzBinarySensor(sensor, gateway, description)]) gateway.register_platform_add_device_callback( diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index ee5a30f2fab..9fd57926f44 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -4,7 +4,7 @@ from unittest.mock import patch import pytest -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDeviceClass +from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.deconz.const import ( CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_NEW_DEVICES, @@ -69,7 +69,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.alarm_10", "unique_id": "00:15:8d:00:02:b5:d1:80-01-0500-alarm", - "old_unique_id": "00:15:8d:00:02:b5:d1:80-01-0500", "state": STATE_OFF, "entity_category": None, "device_class": BinarySensorDeviceClass.SAFETY, @@ -111,7 +110,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.cave_co", "unique_id": "00:15:8d:00:02:a5:21:24-01-0101-carbon_monoxide", - "old_unique_id": "00:15:8d:00:02:a5:21:24-01-0101", "state": STATE_OFF, "entity_category": None, "device_class": BinarySensorDeviceClass.CO, @@ -147,7 +145,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.sensor_kitchen_smoke", "unique_id": "00:15:8d:00:01:d9:3e:7c-01-0500-fire", - "old_unique_id": "00:15:8d:00:01:d9:3e:7c-01-0500", "state": STATE_OFF, "entity_category": None, "device_class": BinarySensorDeviceClass.SMOKE, @@ -184,7 +181,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.sensor_kitchen_smoke_test_mode", "unique_id": "00:15:8d:00:01:d9:3e:7c-01-0500-in_test_mode", - "old_unique_id": "00:15:8d:00:01:d9:3e:7c-test mode", "state": STATE_OFF, "entity_category": EntityCategory.DIAGNOSTIC, "device_class": BinarySensorDeviceClass.SMOKE, @@ -217,7 +213,6 @@ TEST_DATA = [ "device_count": 2, "entity_id": "binary_sensor.kitchen_switch", "unique_id": "kitchen-switch-flag", - "old_unique_id": "kitchen-switch", "state": STATE_ON, "entity_category": None, "device_class": None, @@ -255,7 +250,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.back_door", "unique_id": "00:15:8d:00:02:2b:96:b4-01-0006-open", - "old_unique_id": "00:15:8d:00:02:2b:96:b4-01-0006", "state": STATE_OFF, "entity_category": None, "device_class": BinarySensorDeviceClass.OPENING, @@ -302,7 +296,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.motion_sensor_4", "unique_id": "00:17:88:01:03:28:8c:9b-02-0406-presence", - "old_unique_id": "00:17:88:01:03:28:8c:9b-02-0406", "state": STATE_OFF, "entity_category": None, "device_class": BinarySensorDeviceClass.MOTION, @@ -344,7 +337,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.water2", "unique_id": "00:15:8d:00:02:2f:07:db-01-0500-water", - "old_unique_id": "00:15:8d:00:02:2f:07:db-01-0500", "state": STATE_OFF, "entity_category": None, "device_class": BinarySensorDeviceClass.MOISTURE, @@ -390,7 +382,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.vibration_1", "unique_id": "00:15:8d:00:02:a5:21:24-01-0101-vibration", - "old_unique_id": "00:15:8d:00:02:a5:21:24-01-0101", "state": STATE_ON, "entity_category": None, "device_class": BinarySensorDeviceClass.VIBRATION, @@ -429,7 +420,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.presence_sensor_tampered", "unique_id": "00:00:00:00:00:00:00:00-00-tampered", - "old_unique_id": "00:00:00:00:00:00:00:00-tampered", "state": STATE_OFF, "entity_category": EntityCategory.DIAGNOSTIC, "device_class": BinarySensorDeviceClass.TAMPER, @@ -463,7 +453,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "binary_sensor.presence_sensor_low_battery", "unique_id": "00:00:00:00:00:00:00:00-00-low_battery", - "old_unique_id": "00:00:00:00:00:00:00:00-low battery", "state": STATE_OFF, "entity_category": EntityCategory.DIAGNOSTIC, "device_class": BinarySensorDeviceClass.BATTERY, @@ -489,15 +478,6 @@ async def test_binary_sensors( expected, ) -> None: """Test successful creation of binary sensor entities.""" - - # Create entity entry to migrate to new unique ID - entity_registry.async_get_or_create( - DOMAIN, - DECONZ_DOMAIN, - expected["old_unique_id"], - suggested_object_id=expected["entity_id"].replace(DOMAIN, ""), - ) - with patch.dict(DECONZ_WEB_REQUEST, {"sensors": {"1": sensor_data}}): config_entry = await setup_deconz_integration( hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} From 86607d2bbbb131aad246ff5cef5bf1908a5956bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Strandberg?= Date: Fri, 15 Mar 2024 12:11:13 +0100 Subject: [PATCH 1089/1691] Create more relevant names for myuplink DeviceInfo (#111502) --- homeassistant/components/myuplink/__init__.py | 24 +++++++++++-------- .../components/myuplink/fixtures/systems.json | 2 +- .../components/myuplink/test_binary_sensor.py | 4 ++-- tests/components/myuplink/test_number.py | 4 ++-- tests/components/myuplink/test_sensor.py | 4 ++-- tests/components/myuplink/test_switch.py | 4 ++-- tests/components/myuplink/test_update.py | 2 +- 7 files changed, 24 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/myuplink/__init__.py b/homeassistant/components/myuplink/__init__.py index 4e3305cf713..42bb9007789 100644 --- a/homeassistant/components/myuplink/__init__.py +++ b/homeassistant/components/myuplink/__init__.py @@ -5,7 +5,7 @@ from __future__ import annotations from http import HTTPStatus from aiohttp import ClientError, ClientResponseError -from myuplink import MyUplinkAPI +from myuplink import MyUplinkAPI, get_manufacturer, get_model, get_system_name from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -83,12 +83,16 @@ def create_devices( """Update all devices.""" device_registry = dr.async_get(hass) - for device_id, device in coordinator.data.devices.items(): - device_registry.async_get_or_create( - config_entry_id=config_entry.entry_id, - identifiers={(DOMAIN, device_id)}, - name=device.productName, - manufacturer=device.productName.split(" ")[0], - model=device.productName, - sw_version=device.firmwareCurrent, - ) + for system in coordinator.data.systems: + devices_in_system = [x.id for x in system.devices] + for device_id, device in coordinator.data.devices.items(): + if device_id in devices_in_system: + device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + identifiers={(DOMAIN, device_id)}, + name=get_system_name(system), + manufacturer=get_manufacturer(device), + model=get_model(device), + sw_version=device.firmwareCurrent, + serial_number=device.product_serial_number, + ) diff --git a/tests/components/myuplink/fixtures/systems.json b/tests/components/myuplink/fixtures/systems.json index 151e9f55a8d..c009816a88f 100644 --- a/tests/components/myuplink/fixtures/systems.json +++ b/tests/components/myuplink/fixtures/systems.json @@ -5,7 +5,7 @@ "systems": [ { "systemId": "123456-7890-1234", - "name": "Gotham City", + "name": "Batcave", "securityLevel": "admin", "hasAlarm": false, "country": "Sweden", diff --git a/tests/components/myuplink/test_binary_sensor.py b/tests/components/myuplink/test_binary_sensor.py index 24bfe49985d..19eb4a4f292 100644 --- a/tests/components/myuplink/test_binary_sensor.py +++ b/tests/components/myuplink/test_binary_sensor.py @@ -17,9 +17,9 @@ async def test_sensor_states( """Test sensor state.""" await setup_integration(hass, mock_config_entry) - state = hass.states.get("binary_sensor.f730_cu_3x400v_pump_heating_medium_gp1") + state = hass.states.get("binary_sensor.gotham_city_pump_heating_medium_gp1") assert state is not None assert state.state == "on" assert state.attributes == { - "friendly_name": "F730 CU 3x400V Pump: Heating medium (GP1)", + "friendly_name": "Gotham City Pump: Heating medium (GP1)", } diff --git a/tests/components/myuplink/test_number.py b/tests/components/myuplink/test_number.py index 2bd2ca61496..41dc0dae164 100644 --- a/tests/components/myuplink/test_number.py +++ b/tests/components/myuplink/test_number.py @@ -14,8 +14,8 @@ from homeassistant.helpers import entity_registry as er TEST_PLATFORM = Platform.NUMBER pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) -ENTITY_ID = "number.f730_cu_3x400v_degree_minutes" -ENTITY_FRIENDLY_NAME = "F730 CU 3x400V Degree minutes" +ENTITY_ID = "number.gotham_city_degree_minutes" +ENTITY_FRIENDLY_NAME = "Gotham City Degree minutes" ENTITY_UID = "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-40940" diff --git a/tests/components/myuplink/test_sensor.py b/tests/components/myuplink/test_sensor.py index eb8c0f82bbd..8fecb787122 100644 --- a/tests/components/myuplink/test_sensor.py +++ b/tests/components/myuplink/test_sensor.py @@ -17,11 +17,11 @@ async def test_sensor_states( """Test sensor state.""" await setup_integration(hass, mock_config_entry) - state = hass.states.get("sensor.f730_cu_3x400v_average_outdoor_temp_bt1") + state = hass.states.get("sensor.gotham_city_average_outdoor_temp_bt1") assert state is not None assert state.state == "-12.2" assert state.attributes == { - "friendly_name": "F730 CU 3x400V Average outdoor temp (BT1)", + "friendly_name": "Gotham City Average outdoor temp (BT1)", "device_class": "temperature", "state_class": "measurement", "unit_of_measurement": "°C", diff --git a/tests/components/myuplink/test_switch.py b/tests/components/myuplink/test_switch.py index e60aa4ebc4b..71c7f41c214 100644 --- a/tests/components/myuplink/test_switch.py +++ b/tests/components/myuplink/test_switch.py @@ -19,8 +19,8 @@ from homeassistant.helpers import entity_registry as er TEST_PLATFORM = Platform.SWITCH pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) -ENTITY_ID = "switch.f730_cu_3x400v_temporary_lux" -ENTITY_FRIENDLY_NAME = "F730 CU 3x400V Tempo­rary lux" +ENTITY_ID = "switch.gotham_city_temporary_lux" +ENTITY_FRIENDLY_NAME = "Gotham City Tempo\xadrary lux" ENTITY_UID = "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-50004" diff --git a/tests/components/myuplink/test_update.py b/tests/components/myuplink/test_update.py index f25993e8ef6..82f6ac17f69 100644 --- a/tests/components/myuplink/test_update.py +++ b/tests/components/myuplink/test_update.py @@ -17,6 +17,6 @@ async def test_update_states( """Test update state.""" await setup_integration(hass, mock_config_entry) - state = hass.states.get("update.f730_cu_3x400v_firmware") + state = hass.states.get("update.gotham_city_firmware") assert state is not None assert state.state == "off" From 13cd6eb00e7314a9c137b3443af8f05fefbead6b Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:12:03 -0300 Subject: [PATCH 1090/1691] Add icon translations to Moon (#111386) --- homeassistant/components/moon/icons.json | 19 +++++++++++++++++++ homeassistant/components/moon/sensor.py | 13 ------------- tests/components/moon/test_sensor.py | 23 ++++++++++------------- 3 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 homeassistant/components/moon/icons.json diff --git a/homeassistant/components/moon/icons.json b/homeassistant/components/moon/icons.json new file mode 100644 index 00000000000..77c578c8f0d --- /dev/null +++ b/homeassistant/components/moon/icons.json @@ -0,0 +1,19 @@ +{ + "entity": { + "sensor": { + "phase": { + "default": "mdi:weather-night", + "state": { + "first_quarter": "mdi:moon-first-quarter", + "full_moon": "mdi:moon-full", + "last_quarter": "mdi:moon-last-quarter", + "new_moon": "mdi:moon-new", + "waning_crescent": "mdi:moon-waning-crescent", + "waning_gibbous": "mdi:moon-waning-gibbous", + "waxing_crescent": "mdi:moon-waxing-crescent", + "waxing_gibbous": "mdi:moon-waxing-gibbous" + } + } + } + } +} diff --git a/homeassistant/components/moon/sensor.py b/homeassistant/components/moon/sensor.py index cef1dbffbb3..1e2674a24bf 100644 --- a/homeassistant/components/moon/sensor.py +++ b/homeassistant/components/moon/sensor.py @@ -22,17 +22,6 @@ STATE_WANING_GIBBOUS = "waning_gibbous" STATE_WAXING_CRESCENT = "waxing_crescent" STATE_WAXING_GIBBOUS = "waxing_gibbous" -MOON_ICONS = { - STATE_FIRST_QUARTER: "mdi:moon-first-quarter", - STATE_FULL_MOON: "mdi:moon-full", - STATE_LAST_QUARTER: "mdi:moon-last-quarter", - STATE_NEW_MOON: "mdi:moon-new", - STATE_WANING_CRESCENT: "mdi:moon-waning-crescent", - STATE_WANING_GIBBOUS: "mdi:moon-waning-gibbous", - STATE_WAXING_CRESCENT: "mdi:moon-waxing-crescent", - STATE_WAXING_GIBBOUS: "mdi:moon-waxing-gibbous", -} - async def async_setup_entry( hass: HomeAssistant, @@ -90,5 +79,3 @@ class MoonSensorEntity(SensorEntity): self._attr_native_value = STATE_LAST_QUARTER else: self._attr_native_value = STATE_WANING_CRESCENT - - self._attr_icon = MOON_ICONS.get(self._attr_native_value) diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py index b3d5eb03428..8dd50bfa99d 100644 --- a/tests/components/moon/test_sensor.py +++ b/tests/components/moon/test_sensor.py @@ -7,7 +7,6 @@ from unittest.mock import patch import pytest from homeassistant.components.moon.sensor import ( - MOON_ICONS, STATE_FIRST_QUARTER, STATE_FULL_MOON, STATE_LAST_QUARTER, @@ -18,7 +17,7 @@ from homeassistant.components.moon.sensor import ( STATE_WAXING_GIBBOUS, ) from homeassistant.components.sensor import ATTR_OPTIONS, SensorDeviceClass -from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -26,16 +25,16 @@ from tests.common import MockConfigEntry @pytest.mark.parametrize( - ("moon_value", "native_value", "icon"), + ("moon_value", "native_value"), [ - (0, STATE_NEW_MOON, MOON_ICONS[STATE_NEW_MOON]), - (5, STATE_WAXING_CRESCENT, MOON_ICONS[STATE_WAXING_CRESCENT]), - (7, STATE_FIRST_QUARTER, MOON_ICONS[STATE_FIRST_QUARTER]), - (12, STATE_WAXING_GIBBOUS, MOON_ICONS[STATE_WAXING_GIBBOUS]), - (14.3, STATE_FULL_MOON, MOON_ICONS[STATE_FULL_MOON]), - (20.1, STATE_WANING_GIBBOUS, MOON_ICONS[STATE_WANING_GIBBOUS]), - (20.8, STATE_LAST_QUARTER, MOON_ICONS[STATE_LAST_QUARTER]), - (23, STATE_WANING_CRESCENT, MOON_ICONS[STATE_WANING_CRESCENT]), + (0, STATE_NEW_MOON), + (5, STATE_WAXING_CRESCENT), + (7, STATE_FIRST_QUARTER), + (12, STATE_WAXING_GIBBOUS), + (14.3, STATE_FULL_MOON), + (20.1, STATE_WANING_GIBBOUS), + (20.8, STATE_LAST_QUARTER), + (23, STATE_WANING_CRESCENT), ], ) async def test_moon_day( @@ -45,7 +44,6 @@ async def test_moon_day( mock_config_entry: MockConfigEntry, moon_value: float, native_value: str, - icon: str, ) -> None: """Test the Moon sensor.""" mock_config_entry.add_to_hass(hass) @@ -59,7 +57,6 @@ async def test_moon_day( state = hass.states.get("sensor.moon_phase") assert state assert state.state == native_value - assert state.attributes[ATTR_ICON] == icon assert state.attributes[ATTR_FRIENDLY_NAME] == "Moon Phase" assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM assert state.attributes[ATTR_OPTIONS] == [ From 7bcfa94b122cbf80056e8ccd4e1dcc8700675647 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 15 Mar 2024 12:20:32 +0100 Subject: [PATCH 1091/1691] Use `single_config_entry` in KNX manifest (#112526) --- homeassistant/components/knx/config_flow.py | 2 -- homeassistant/components/knx/manifest.json | 3 ++- homeassistant/components/knx/strings.json | 4 ---- homeassistant/generated/integrations.json | 3 ++- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 2ef913deee0..af1eee89af7 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -756,8 +756,6 @@ class KNXConfigFlow(KNXCommonFlow, ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult: """Handle a flow initialized by the user.""" - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") return await self.async_step_connection_type() diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 290b560dad5..99c150a8346 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -14,5 +14,6 @@ "xknx==2.12.2", "xknxproject==3.7.0", "knx-frontend==2024.1.20.105944" - ] + ], + "single_config_entry": true } diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index 5f5a2263eac..39b96dddf8f 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -108,10 +108,6 @@ } } }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" - }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_backbone_key": "Invalid backbone key. 32 hexadecimal numbers expected.", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index e745c52db69..7ed8c4accd1 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3023,7 +3023,8 @@ "name": "KNX", "integration_type": "hub", "config_flow": true, - "iot_class": "local_push" + "iot_class": "local_push", + "single_config_entry": true }, "kodi": { "name": "Kodi", From e0b1531afa34f322c89973ed28e4375ce094f7dd Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 15 Mar 2024 13:23:30 +0200 Subject: [PATCH 1092/1691] Remove OpenCV integration (#113455) --- .coveragerc | 1 - homeassistant/components/opencv/__init__.py | 1 - .../components/opencv/image_processing.py | 196 ------------------ homeassistant/components/opencv/manifest.json | 8 - homeassistant/generated/integrations.json | 6 - requirements_all.txt | 4 - requirements_test_all.txt | 1 - script/gen_requirements_all.py | 1 - 8 files changed, 218 deletions(-) delete mode 100644 homeassistant/components/opencv/__init__.py delete mode 100644 homeassistant/components/opencv/image_processing.py delete mode 100644 homeassistant/components/opencv/manifest.json diff --git a/.coveragerc b/.coveragerc index aff0979ca1d..792cf9ddfe0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -926,7 +926,6 @@ omit = homeassistant/components/onvif/sensor.py homeassistant/components/onvif/util.py homeassistant/components/open_meteo/weather.py - homeassistant/components/opencv/* homeassistant/components/openevse/sensor.py homeassistant/components/openexchangerates/__init__.py homeassistant/components/openexchangerates/coordinator.py diff --git a/homeassistant/components/opencv/__init__.py b/homeassistant/components/opencv/__init__.py deleted file mode 100644 index 0e4a755b2b9..00000000000 --- a/homeassistant/components/opencv/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The opencv component.""" diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py deleted file mode 100644 index e3dbe10a1a0..00000000000 --- a/homeassistant/components/opencv/image_processing.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Support for OpenCV classification on images.""" - -from __future__ import annotations - -from datetime import timedelta -import logging - -import numpy as np -import requests -import voluptuous as vol - -from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, - ImageProcessingEntity, -) -from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE -from homeassistant.core import HomeAssistant, split_entity_id -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -try: - # Verify that the OpenCV python package is pre-installed - import cv2 - - CV2_IMPORTED = True -except ImportError: - CV2_IMPORTED = False - - -_LOGGER = logging.getLogger(__name__) - -ATTR_MATCHES = "matches" -ATTR_TOTAL_MATCHES = "total_matches" - -CASCADE_URL = ( - "https://raw.githubusercontent.com/opencv/opencv/master/data/" - "lbpcascades/lbpcascade_frontalface.xml" -) - -CONF_CLASSIFIER = "classifier" -CONF_FILE = "file" -CONF_MIN_SIZE = "min_size" -CONF_NEIGHBORS = "neighbors" -CONF_SCALE = "scale" - -DEFAULT_CLASSIFIER_PATH = "lbp_frontalface.xml" -DEFAULT_MIN_SIZE = (30, 30) -DEFAULT_NEIGHBORS = 4 -DEFAULT_SCALE = 1.1 -DEFAULT_TIMEOUT = 10 - -SCAN_INTERVAL = timedelta(seconds=2) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_CLASSIFIER): { - cv.string: vol.Any( - cv.isfile, - vol.Schema( - { - vol.Required(CONF_FILE): cv.isfile, - vol.Optional(CONF_SCALE, DEFAULT_SCALE): float, - vol.Optional( - CONF_NEIGHBORS, DEFAULT_NEIGHBORS - ): cv.positive_int, - vol.Optional(CONF_MIN_SIZE, DEFAULT_MIN_SIZE): vol.Schema( - vol.All(vol.Coerce(tuple), vol.ExactSequence([int, int])) - ), - } - ), - ) - } - } -) - - -def _create_processor_from_config(hass, camera_entity, config): - """Create an OpenCV processor from configuration.""" - classifier_config = config.get(CONF_CLASSIFIER) - name = f"{config[CONF_NAME]} {split_entity_id(camera_entity)[1].replace('_', ' ')}" - - processor = OpenCVImageProcessor(hass, camera_entity, name, classifier_config) - - return processor - - -def _get_default_classifier(dest_path): - """Download the default OpenCV classifier.""" - _LOGGER.info("Downloading default classifier") - req = requests.get(CASCADE_URL, stream=True, timeout=10) - with open(dest_path, "wb") as fil: - for chunk in req.iter_content(chunk_size=1024): - if chunk: # filter out keep-alive new chunks - fil.write(chunk) - - -def setup_platform( - hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, -) -> None: - """Set up the OpenCV image processing platform.""" - if not CV2_IMPORTED: - _LOGGER.error( - "No OpenCV library found! Install or compile for your system " - "following instructions here: https://opencv.org/?s=releases" - ) - return - - if CONF_CLASSIFIER not in config: - dest_path = hass.config.path(DEFAULT_CLASSIFIER_PATH) - _get_default_classifier(dest_path) - config[CONF_CLASSIFIER] = {"Face": dest_path} - - add_entities( - OpenCVImageProcessor( - hass, - camera[CONF_ENTITY_ID], - camera.get(CONF_NAME), - config[CONF_CLASSIFIER], - ) - for camera in config[CONF_SOURCE] - ) - - -class OpenCVImageProcessor(ImageProcessingEntity): - """Representation of an OpenCV image processor.""" - - def __init__(self, hass, camera_entity, name, classifiers): - """Initialize the OpenCV entity.""" - self.hass = hass - self._camera_entity = camera_entity - if name: - self._name = name - else: - self._name = f"OpenCV {split_entity_id(camera_entity)[1]}" - self._classifiers = classifiers - self._matches = {} - self._total_matches = 0 - self._last_image = None - - @property - def camera_entity(self): - """Return camera entity id from process pictures.""" - return self._camera_entity - - @property - def name(self): - """Return the name of the image processor.""" - return self._name - - @property - def state(self): - """Return the state of the entity.""" - return self._total_matches - - @property - def extra_state_attributes(self): - """Return device specific state attributes.""" - return {ATTR_MATCHES: self._matches, ATTR_TOTAL_MATCHES: self._total_matches} - - def process_image(self, image): - """Process the image.""" - cv_image = cv2.imdecode(np.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) - - matches = {} - total_matches = 0 - - for name, classifier in self._classifiers.items(): - scale = DEFAULT_SCALE - neighbors = DEFAULT_NEIGHBORS - min_size = DEFAULT_MIN_SIZE - if isinstance(classifier, dict): - path = classifier[CONF_FILE] - scale = classifier.get(CONF_SCALE, scale) - neighbors = classifier.get(CONF_NEIGHBORS, neighbors) - min_size = classifier.get(CONF_MIN_SIZE, min_size) - else: - path = classifier - - cascade = cv2.CascadeClassifier(path) - - detections = cascade.detectMultiScale( - cv_image, scaleFactor=scale, minNeighbors=neighbors, minSize=min_size - ) - regions = [] - for x, y, w, h in detections: - regions.append((int(x), int(y), int(w), int(h))) - total_matches += 1 - - matches[name] = regions - - self._matches = matches - self._total_matches = total_matches diff --git a/homeassistant/components/opencv/manifest.json b/homeassistant/components/opencv/manifest.json deleted file mode 100644 index 3c484385934..00000000000 --- a/homeassistant/components/opencv/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "opencv", - "name": "OpenCV", - "codeowners": [], - "documentation": "https://www.home-assistant.io/integrations/opencv", - "iot_class": "local_push", - "requirements": ["numpy==1.26.0", "opencv-python-headless==4.6.0.66"] -} diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 7ed8c4accd1..1de015ac780 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -4183,12 +4183,6 @@ "config_flow": false, "iot_class": "cloud_push" }, - "opencv": { - "name": "OpenCV", - "integration_type": "hub", - "config_flow": false, - "iot_class": "local_push" - }, "openerz": { "name": "Open ERZ", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index 635c0deb245..6ce453c11b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1407,7 +1407,6 @@ numato-gpio==0.12.0 # homeassistant.components.compensation # homeassistant.components.iqvia -# homeassistant.components.opencv # homeassistant.components.stream # homeassistant.components.tensorflow # homeassistant.components.trend @@ -1449,9 +1448,6 @@ open-meteo==0.3.1 # homeassistant.components.openai_conversation openai==1.3.8 -# homeassistant.components.opencv -# opencv-python-headless==4.6.0.66 - # homeassistant.components.openerz openerz-api==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eef277af206..8832c9b8d88 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1125,7 +1125,6 @@ numato-gpio==0.12.0 # homeassistant.components.compensation # homeassistant.components.iqvia -# homeassistant.components.opencv # homeassistant.components.stream # homeassistant.components.tensorflow # homeassistant.components.trend diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 8cf8f33c542..4cab1f167c7 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -29,7 +29,6 @@ COMMENT_REQUIREMENTS = ( "decora-wifi", "evdev", "face-recognition", - "opencv-python-headless", "pybluez", "pycocotools", "pycups", From 9cc0006b926efd0ae28ac9101b1ae210cbc69c11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 01:23:53 -1000 Subject: [PATCH 1093/1691] Ensure TurboJPEG is imported in the executor (#113504) The import was too late and it eneded up being imported in the event loop --- homeassistant/components/camera/img_util.py | 23 ++++++++++---------- tests/components/camera/test_img_util.py | 24 +++++++++++++++------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/camera/img_util.py b/homeassistant/components/camera/img_util.py index bf07d6a2f1f..b9b607d5edf 100644 --- a/homeassistant/components/camera/img_util.py +++ b/homeassistant/components/camera/img_util.py @@ -2,20 +2,27 @@ from __future__ import annotations +from contextlib import suppress import logging from typing import TYPE_CHECKING, Literal, cast +with suppress(Exception): # pylint: disable=broad-except + # TurboJPEG imports numpy which may or may not work so + # we have to guard the import here. We still want + # to import it at top level so it gets loaded + # in the import executor and not in the event loop. + from turbojpeg import TurboJPEG + + +if TYPE_CHECKING: + from . import Image + SUPPORTED_SCALING_FACTORS = [(7, 8), (3, 4), (5, 8), (1, 2), (3, 8), (1, 4), (1, 8)] _LOGGER = logging.getLogger(__name__) JPEG_QUALITY = 75 -if TYPE_CHECKING: - from turbojpeg import TurboJPEG - - from . import Image - def find_supported_scaling_factor( current_width: int, current_height: int, target_width: int, target_height: int @@ -90,12 +97,6 @@ class TurboJPEGSingleton: def __init__(self) -> None: """Try to create TurboJPEG only once.""" try: - # TurboJPEG checks for libturbojpeg - # when its created, but it imports - # numpy which may or may not work so - # we have to guard the import here. - from turbojpeg import TurboJPEG # pylint: disable=import-outside-toplevel - TurboJPEGSingleton.__instance = TurboJPEG() except Exception: # pylint: disable=broad-except _LOGGER.exception( diff --git a/tests/components/camera/test_img_util.py b/tests/components/camera/test_img_util.py index 4fb863d414d..5c3d3712f38 100644 --- a/tests/components/camera/test_img_util.py +++ b/tests/components/camera/test_img_util.py @@ -38,25 +38,33 @@ def test_scale_jpeg_camera_image() -> None: camera_image = Image("image/jpeg", EMPTY_16_12_JPEG) turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) - with patch("turbojpeg.TurboJPEG", return_value=False): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=False + ): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) turbo_jpeg.decode_header.side_effect = OSError - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == EMPTY_16_12_JPEG turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=8, second_height=6 ) - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() jpeg_bytes = scale_jpeg_camera_image(camera_image, 8, 6) @@ -65,7 +73,9 @@ def test_scale_jpeg_camera_image() -> None: turbo_jpeg = mock_turbo_jpeg( first_width=640, first_height=480, second_width=640, second_height=480 ) - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() jpeg_bytes = scale_jpeg_camera_image(camera_image, 320, 480) @@ -75,7 +85,9 @@ def test_scale_jpeg_camera_image() -> None: def test_turbojpeg_load_failure() -> None: """Handle libjpegturbo not being installed.""" _clear_turbojpeg_singleton() - with patch("turbojpeg.TurboJPEG", side_effect=Exception): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", side_effect=Exception + ): TurboJPEGSingleton() assert TurboJPEGSingleton.instance() is False From 30d1f70468235bd7ff8d163d43dddecde0b49f87 Mon Sep 17 00:00:00 2001 From: Sebastian Noack Date: Fri, 15 Mar 2024 07:52:21 -0400 Subject: [PATCH 1094/1691] Use on state icons by default in Balboa spa integration (#111911) --- homeassistant/components/balboa/icons.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/balboa/icons.json b/homeassistant/components/balboa/icons.json index fb1b6d01ed4..7261f71bd00 100644 --- a/homeassistant/components/balboa/icons.json +++ b/homeassistant/components/balboa/icons.json @@ -2,21 +2,21 @@ "entity": { "binary_sensor": { "filter_1": { - "default": "mdi:sync-off", + "default": "mdi:sync", "state": { - "on": "mdi:sync" + "off": "mdi:sync-off" } }, "filter_2": { - "default": "mdi:sync-off", + "default": "mdi:sync", "state": { - "on": "mdi:sync" + "off": "mdi:sync-off" } }, "circ_pump": { - "default": "mdi:pump-off", + "default": "mdi:pump", "state": { - "on": "mdi:pump" + "off": "mdi:pump-off" } } }, From 360f7dea7549dafc1e775161df9a8ad34b538f9e Mon Sep 17 00:00:00 2001 From: cosimomeli Date: Fri, 15 Mar 2024 12:59:36 +0100 Subject: [PATCH 1095/1691] Add Ring Intercom support (#109819) * Add button entity * Add support for Ring intercom ("other" device type) * description * format * - Tests - Fallback when intercom devices arent inside response * Fix ring button * Update library * Fix button after merge * Move names to strings.json * Remove button entity_category * Add wifi sensors to other * Add last_ sensors to other * Fix tests * Add button test * Add new sensors tests * Revert "Add last_ sensors to other" This reverts commit 5c03bba5a10130489bb33897a1952ca426bd725a. * Update library * Revert "Revert "Add last_ sensors to other"" This reverts commit 27631978d0d940a3fcbece761357d97c02bcca55. * Fix tests * Remove default list for other Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> * Copy mock to conftest * Fix history test * Change time skip * Remove button * Fix history test --------- Co-authored-by: Martin Pham Co-authored-by: Steven B. <51370195+sdb9696@users.noreply.github.com> --- .../components/ring/binary_sensor.py | 4 +- homeassistant/components/ring/icons.json | 9 ++ homeassistant/components/ring/sensor.py | 42 ++++++- homeassistant/components/ring/strings.json | 9 ++ tests/components/ring/conftest.py | 11 +- tests/components/ring/fixtures/devices.json | 87 +++++++++++++ .../{doorbots.json => doorbot_history.json} | 0 tests/components/ring/fixtures/groups.json | 24 ++++ .../ring/fixtures/intercom_history.json | 116 ++++++++++++++++++ .../ring/snapshots/test_diagnostics.ambr | 85 +++++++++++++ tests/components/ring/test_binary_sensor.py | 10 +- tests/components/ring/test_sensor.py | 30 ++++- 12 files changed, 412 insertions(+), 15 deletions(-) rename tests/components/ring/fixtures/{doorbots.json => doorbot_history.json} (100%) create mode 100644 tests/components/ring/fixtures/groups.json create mode 100644 tests/components/ring/fixtures/intercom_history.json diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index 599753cc1cc..19daebf9ce1 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -31,7 +31,7 @@ BINARY_SENSOR_TYPES: tuple[RingBinarySensorEntityDescription, ...] = ( RingBinarySensorEntityDescription( key="ding", translation_key="ding", - category=["doorbots", "authorized_doorbots"], + category=["doorbots", "authorized_doorbots", "other"], device_class=BinarySensorDeviceClass.OCCUPANCY, ), RingBinarySensorEntityDescription( @@ -56,7 +56,7 @@ async def async_setup_entry( entities = [ RingBinarySensor(ring, device, notifications_coordinator, description) - for device_type in ("doorbots", "authorized_doorbots", "stickup_cams") + for device_type in ("doorbots", "authorized_doorbots", "stickup_cams", "other") for description in BINARY_SENSOR_TYPES if device_type in description.category for device in devices[device_type] diff --git a/homeassistant/components/ring/icons.json b/homeassistant/components/ring/icons.json index 07b42db1516..9ce0de6bebc 100644 --- a/homeassistant/components/ring/icons.json +++ b/homeassistant/components/ring/icons.json @@ -13,6 +13,15 @@ "volume": { "default": "mdi:bell-ring" }, + "doorbell_volume": { + "default": "mdi:bell-ring" + }, + "mic_volume": { + "default": "mdi:microphone" + }, + "voice_volume": { + "default": "mdi:account-voice" + }, "wifi_signal_category": { "default": "mdi:wifi" }, diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 874d3664ab7..9ba677e7e5b 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -40,7 +40,13 @@ async def async_setup_entry( entities = [ description.cls(device, devices_coordinator, description) - for device_type in ("chimes", "doorbots", "authorized_doorbots", "stickup_cams") + for device_type in ( + "chimes", + "doorbots", + "authorized_doorbots", + "stickup_cams", + "other", + ) for description in SENSOR_TYPES if device_type in description.category for device in devices[device_type] @@ -72,6 +78,12 @@ class RingSensor(RingEntity, SensorEntity): sensor_type = self.entity_description.key if sensor_type == "volume": return self._device.volume + if sensor_type == "doorbell_volume": + return self._device.doorbell_volume + if sensor_type == "mic_volume": + return self._device.mic_volume + if sensor_type == "voice_volume": + return self._device.voice_volume if sensor_type == "battery": return self._device.battery_life @@ -156,7 +168,7 @@ class RingSensorEntityDescription(SensorEntityDescription): SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( RingSensorEntityDescription( key="battery", - category=["doorbots", "authorized_doorbots", "stickup_cams"], + category=["doorbots", "authorized_doorbots", "stickup_cams", "other"], native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, @@ -166,14 +178,14 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( RingSensorEntityDescription( key="last_activity", translation_key="last_activity", - category=["doorbots", "authorized_doorbots", "stickup_cams"], + category=["doorbots", "authorized_doorbots", "stickup_cams", "other"], device_class=SensorDeviceClass.TIMESTAMP, cls=HistoryRingSensor, ), RingSensorEntityDescription( key="last_ding", translation_key="last_ding", - category=["doorbots", "authorized_doorbots"], + category=["doorbots", "authorized_doorbots", "other"], kind="ding", device_class=SensorDeviceClass.TIMESTAMP, cls=HistoryRingSensor, @@ -192,17 +204,35 @@ SENSOR_TYPES: tuple[RingSensorEntityDescription, ...] = ( category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], cls=RingSensor, ), + RingSensorEntityDescription( + key="doorbell_volume", + translation_key="doorbell_volume", + category=["other"], + cls=RingSensor, + ), + RingSensorEntityDescription( + key="mic_volume", + translation_key="mic_volume", + category=["other"], + cls=RingSensor, + ), + RingSensorEntityDescription( + key="voice_volume", + translation_key="voice_volume", + category=["other"], + cls=RingSensor, + ), RingSensorEntityDescription( key="wifi_signal_category", translation_key="wifi_signal_category", - category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], + category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams", "other"], entity_category=EntityCategory.DIAGNOSTIC, cls=HealthDataRingSensor, ), RingSensorEntityDescription( key="wifi_signal_strength", translation_key="wifi_signal_strength", - category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], + category=["chimes", "doorbots", "authorized_doorbots", "stickup_cams", "other"], native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, device_class=SensorDeviceClass.SIGNAL_STRENGTH, entity_category=EntityCategory.DIAGNOSTIC, diff --git a/homeassistant/components/ring/strings.json b/homeassistant/components/ring/strings.json index ee7dbc000f5..de8b5112ec9 100644 --- a/homeassistant/components/ring/strings.json +++ b/homeassistant/components/ring/strings.json @@ -60,6 +60,15 @@ "volume": { "name": "Volume" }, + "doorbell_volume": { + "name": "Doorbell volume" + }, + "mic_volume": { + "name": "Mic volume" + }, + "voice_volume": { + "name": "Voice volume" + }, "wifi_signal_category": { "name": "Wi-Fi signal category" }, diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 833d84265a6..106a824f1d5 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -96,7 +96,7 @@ def requests_mock_fixture(): re.compile( r"https:\/\/api\.ring\.com\/clients_api\/doorbots\/\d+\/history" ), - text=load_fixture("doorbots.json", "ring"), + text=load_fixture("doorbot_history.json", "ring"), ) # Mocks the response for getting the health of a device mock.get( @@ -115,6 +115,15 @@ def requests_mock_fixture(): status_code=200, json={"url": "http://127.0.0.1/foo"}, ) + mock.get( + "https://api.ring.com/groups/v1/locations/mock-location-id/groups", + text=load_fixture("groups.json", "ring"), + ) + # Mocks the response for getting the history of the intercom + mock.get( + "https://api.ring.com/clients_api/doorbots/185036587/history", + text=load_fixture("intercom_history.json", "ring"), + ) # Mocks the response for setting properties in settings (i.e. motion_detection) mock.patch( re.compile( diff --git a/tests/components/ring/fixtures/devices.json b/tests/components/ring/fixtures/devices.json index ae7a62e1bae..8deee7ec413 100644 --- a/tests/components/ring/fixtures/devices.json +++ b/tests/components/ring/fixtures/devices.json @@ -378,5 +378,92 @@ "subscribed_motions": true, "time_zone": "America/New_York" } + ], + "other": [ + { + "id": 185036587, + "kind": "intercom_handset_audio", + "description": "Ingress", + "location_id": "mock-location-id", + "schema_id": null, + "is_sidewalk_gateway": false, + "created_at": "2023-12-01T18:05:25Z", + "deactivated_at": null, + "owner": { + "id": 762490876, + "first_name": "", + "last_name": "", + "email": "" + }, + "device_id": "124ba1b3fe1a", + "time_zone": "Europe/Rome", + "firmware_version": "Up to Date", + "owned": true, + "ring_net_id": null, + "settings": { + "features_confirmed": 5, + "show_recordings": true, + "recording_ttl": 180, + "recording_enabled": false, + "keep_alive": null, + "keep_alive_auto": 45.0, + "doorbell_volume": 8, + "enable_chime": 1, + "theft_alarm_enable": 0, + "use_cached_domain": 1, + "use_server_ip": 0, + "server_domain": "fw.ring.com", + "server_ip": null, + "enable_log": 1, + "forced_keep_alive": null, + "mic_volume": 11, + "chime_settings": { + "enable": true, + "type": 2, + "duration": 10 + }, + "intercom_settings": { + "ring_to_open": false, + "predecessor": "{\"make\":\"Comelit\",\"model\":\"2738W\",\"wires\":2}", + "config": "{\"intercom_type\": 2, \"number_of_wires\": 2, \"autounlock_enabled\": false, \"speaker_gain\": [-49, -35, -25, -21, -16, -9, -6, -3, 0, 3, 6, 9], \"digital\": {\"audio_amp\": 0, \"chg_en\": false, \"fast_chg\": false, \"bypass\": false, \"idle_lvl\": 32, \"ext_audio\": false, \"ext_audio_term\": 0, \"off_hk_tm\": 0, \"unlk_ka\": false, \"unlock\": {\"cap_tm\": 1000, \"rpl_tm\": 1500, \"gain\": 1000, \"cmp_thr\": 4000, \"lvl\": 25000, \"thr\": 30, \"thr2\": 0, \"offln\": false, \"ac\": true, \"bias\": \"h\", \"tx_pin\": \"TXD2\", \"ack\": 0, \"prot\": \"Comelit_SB2\", \"ask\": {\"f\": 25000, \"b\": 333}}, \"ring\": {\"cap_tm\": 40, \"rpl_tm\": 200, \"gain\": 2000, \"cmp_thr\": 4500, \"lvl\": 28000, \"thr\": 30, \"thr2\": 0, \"offln\": false, \"ac\": true, \"bias\": \"m\", \"tx_pin\": \"TXD2\", \"ack\": 0, \"prot\": \"Comelit_SB2\", \"ask\": {\"f\": 25000, \"b\": 333}}, \"hook_off\": {\"cap_tm\": 1000, \"rpl_tm\": 1500, \"gain\": 1000, \"cmp_thr\": 4000, \"lvl\": 25000, \"thr\": 30, \"thr2\": 0, \"offln\": false, \"ac\": true, \"bias\": \"h\", \"tx_pin\": \"TXD2\", \"ack\": 0, \"prot\": \"Comelit_SB2\", \"ask\": {\"f\": 25000, \"b\": 333}}, \"hook_on\": {\"cap_tm\": 1000, \"rpl_tm\": 1500, \"gain\": 1000, \"cmp_thr\": 4000, \"lvl\": 25000, \"thr\": 30, \"thr2\": 0, \"offln\": false, \"ac\": true, \"bias\": \"h\", \"tx_pin\": \"TXD2\", \"ack\": 0, \"prot\": \"Comelit_SB2\", \"ask\": {\"f\": 25000, \"b\": 333}}}}", + "intercom_type": "DF", + "replication": 1, + "unlock_mode": 0 + }, + "voice_volume": 11 + }, + "alerts": { + "connection": "online", + "ota_status": "timeout" + }, + "function": { + "name": null + }, + "subscribed": false, + "battery_life": "52", + "features": { + "cfes_eligible": false, + "motion_zone_recommendation": false, + "motions_enabled": true, + "show_recordings": true, + "show_vod_settings": true, + "rich_notifications_eligible": false, + "show_offline_motion_events": false, + "sheila_camera_eligible": null, + "sheila_camera_processing_eligible": null, + "dynamic_network_switching_eligible": false, + "chime_auto_detect_capable": false, + "missing_key_delivery_address": false, + "show_24x7_lite": false, + "recording_24x7_eligible": null + }, + "metadata": { + "ethernet": false, + "legacy_fw_migrated": true, + "imported_from_amazon": false, + "is_sidewalk_gateway": false, + "key_access_point_associated": true + } + } ] } diff --git a/tests/components/ring/fixtures/doorbots.json b/tests/components/ring/fixtures/doorbot_history.json similarity index 100% rename from tests/components/ring/fixtures/doorbots.json rename to tests/components/ring/fixtures/doorbot_history.json diff --git a/tests/components/ring/fixtures/groups.json b/tests/components/ring/fixtures/groups.json new file mode 100644 index 00000000000..399aaac1641 --- /dev/null +++ b/tests/components/ring/fixtures/groups.json @@ -0,0 +1,24 @@ +{ + "device_groups": [ + { + "device_group_id": "mock-group-id", + "location_id": "mock-location-id", + "name": "Landscape", + "devices": [ + { + "doorbot_id": 12345678, + "location_id": "mock-location-id", + "type": "beams_ct200_transformer", + "mac_address": null, + "hardware_id": "1234567890", + "name": "Mock Transformer", + "deleted_at": null + } + ], + "created_at": "2020-11-03T22:07:05Z", + "updated_at": "2020-11-19T03:52:59Z", + "deleted_at": null, + "external_id": "12345678-1234-5678-90ab-1234567890ab" + } + ] +} diff --git a/tests/components/ring/fixtures/intercom_history.json b/tests/components/ring/fixtures/intercom_history.json new file mode 100644 index 00000000000..fccd87b9227 --- /dev/null +++ b/tests/components/ring/fixtures/intercom_history.json @@ -0,0 +1,116 @@ +[ + { + "id": 7330963245622279024, + "created_at": "2024-02-02T11:21:24.000Z", + "answered": false, + "events": [], + "kind": "ding", + "favorite": false, + "snapshot_url": "", + "recording": { + "status": "ready" + }, + "duration": 40.0, + "cv_properties": { + "person_detected": null, + "stream_broken": false, + "detection_type": null, + "cv_triggers": null, + "detection_types": null, + "security_alerts": null + }, + "properties": { + "is_alexa": false, + "is_sidewalk": false, + "is_autoreply": false + }, + "doorbot": { + "id": 185036587, + "description": "Ingresso", + "type": "intercom_handset_audio" + }, + "device_placement": null, + "geolocation": null, + "last_location": null, + "siren": null, + "is_e2ee": false, + "had_subscription": false, + "owner_id": "762490876" + }, + { + "id": 7323267080901445808, + "created_at": "2024-01-12T17:36:28.000Z", + "answered": true, + "events": [], + "kind": "on_demand", + "favorite": false, + "snapshot_url": "", + "recording": { + "status": "ready" + }, + "duration": 13.0, + "cv_properties": { + "person_detected": null, + "stream_broken": false, + "detection_type": null, + "cv_triggers": null, + "detection_types": null, + "security_alerts": null + }, + "properties": { + "is_alexa": false, + "is_sidewalk": false, + "is_autoreply": false + }, + "doorbot": { + "id": 185036587, + "description": "Ingresso", + "type": "intercom_handset_audio" + }, + "device_placement": null, + "geolocation": null, + "last_location": null, + "siren": null, + "is_e2ee": false, + "had_subscription": false, + "owner_id": "762490876" + }, + { + "id": 7307399027047288688, + "created_at": "2023-12-01T18:44:28.000Z", + "answered": true, + "events": [], + "kind": "on_demand", + "favorite": false, + "snapshot_url": "", + "recording": { + "status": "ready" + }, + "duration": 43.0, + "cv_properties": { + "person_detected": null, + "stream_broken": false, + "detection_type": null, + "cv_triggers": null, + "detection_types": null, + "security_alerts": null + }, + "properties": { + "is_alexa": false, + "is_sidewalk": false, + "is_autoreply": false + }, + "doorbot": { + "id": 185036587, + "description": "Ingresso", + "type": "intercom_handset_audio" + }, + "device_placement": null, + "geolocation": null, + "last_location": null, + "siren": null, + "is_e2ee": false, + "had_subscription": false, + "owner_id": "762490876" + } +] diff --git a/tests/components/ring/snapshots/test_diagnostics.ambr b/tests/components/ring/snapshots/test_diagnostics.ambr index 2b8f2bac389..1a9d8898af6 100644 --- a/tests/components/ring/snapshots/test_diagnostics.ambr +++ b/tests/components/ring/snapshots/test_diagnostics.ambr @@ -577,6 +577,91 @@ 'subscribed_motions': True, 'time_zone': 'America/New_York', }), + dict({ + 'alerts': dict({ + 'connection': 'online', + 'ota_status': 'timeout', + }), + 'battery_life': '52', + 'created_at': '2023-12-01T18:05:25Z', + 'deactivated_at': None, + 'description': '**REDACTED**', + 'device_id': '**REDACTED**', + 'features': dict({ + 'cfes_eligible': False, + 'chime_auto_detect_capable': False, + 'dynamic_network_switching_eligible': False, + 'missing_key_delivery_address': False, + 'motion_zone_recommendation': False, + 'motions_enabled': True, + 'recording_24x7_eligible': None, + 'rich_notifications_eligible': False, + 'sheila_camera_eligible': None, + 'sheila_camera_processing_eligible': None, + 'show_24x7_lite': False, + 'show_offline_motion_events': False, + 'show_recordings': True, + 'show_vod_settings': True, + }), + 'firmware_version': 'Up to Date', + 'function': dict({ + 'name': None, + }), + 'id': '**REDACTED**', + 'is_sidewalk_gateway': False, + 'kind': 'intercom_handset_audio', + 'location_id': '**REDACTED**', + 'metadata': dict({ + 'ethernet': False, + 'imported_from_amazon': False, + 'is_sidewalk_gateway': False, + 'key_access_point_associated': True, + 'legacy_fw_migrated': True, + }), + 'owned': True, + 'owner': dict({ + 'email': '', + 'first_name': '', + 'id': '**REDACTED**', + 'last_name': '', + }), + 'ring_net_id': None, + 'schema_id': None, + 'settings': dict({ + 'chime_settings': dict({ + 'duration': 10, + 'enable': True, + 'type': 2, + }), + 'doorbell_volume': 8, + 'enable_chime': 1, + 'enable_log': 1, + 'features_confirmed': 5, + 'forced_keep_alive': None, + 'intercom_settings': dict({ + 'config': '{"intercom_type": 2, "number_of_wires": 2, "autounlock_enabled": false, "speaker_gain": [-49, -35, -25, -21, -16, -9, -6, -3, 0, 3, 6, 9], "digital": {"audio_amp": 0, "chg_en": false, "fast_chg": false, "bypass": false, "idle_lvl": 32, "ext_audio": false, "ext_audio_term": 0, "off_hk_tm": 0, "unlk_ka": false, "unlock": {"cap_tm": 1000, "rpl_tm": 1500, "gain": 1000, "cmp_thr": 4000, "lvl": 25000, "thr": 30, "thr2": 0, "offln": false, "ac": true, "bias": "h", "tx_pin": "TXD2", "ack": 0, "prot": "Comelit_SB2", "ask": {"f": 25000, "b": 333}}, "ring": {"cap_tm": 40, "rpl_tm": 200, "gain": 2000, "cmp_thr": 4500, "lvl": 28000, "thr": 30, "thr2": 0, "offln": false, "ac": true, "bias": "m", "tx_pin": "TXD2", "ack": 0, "prot": "Comelit_SB2", "ask": {"f": 25000, "b": 333}}, "hook_off": {"cap_tm": 1000, "rpl_tm": 1500, "gain": 1000, "cmp_thr": 4000, "lvl": 25000, "thr": 30, "thr2": 0, "offln": false, "ac": true, "bias": "h", "tx_pin": "TXD2", "ack": 0, "prot": "Comelit_SB2", "ask": {"f": 25000, "b": 333}}, "hook_on": {"cap_tm": 1000, "rpl_tm": 1500, "gain": 1000, "cmp_thr": 4000, "lvl": 25000, "thr": 30, "thr2": 0, "offln": false, "ac": true, "bias": "h", "tx_pin": "TXD2", "ack": 0, "prot": "Comelit_SB2", "ask": {"f": 25000, "b": 333}}}}', + 'intercom_type': 'DF', + 'predecessor': '{"make":"Comelit","model":"2738W","wires":2}', + 'replication': 1, + 'ring_to_open': False, + 'unlock_mode': 0, + }), + 'keep_alive': None, + 'keep_alive_auto': 45.0, + 'mic_volume': 11, + 'recording_enabled': False, + 'recording_ttl': 180, + 'server_domain': 'fw.ring.com', + 'server_ip': None, + 'show_recordings': True, + 'theft_alarm_enable': 0, + 'use_cached_domain': 1, + 'use_server_ip': 0, + 'voice_volume': 11, + }), + 'subscribed': False, + 'time_zone': 'Europe/Rome', + }), ]), }) # --- diff --git a/tests/components/ring/test_binary_sensor.py b/tests/components/ring/test_binary_sensor.py index 8738594fa05..ba73de05c9b 100644 --- a/tests/components/ring/test_binary_sensor.py +++ b/tests/components/ring/test_binary_sensor.py @@ -33,6 +33,10 @@ async def test_binary_sensor( assert motion_state.state == "on" assert motion_state.attributes["device_class"] == "motion" - ding_state = hass.states.get("binary_sensor.front_door_ding") - assert ding_state is not None - assert ding_state.state == "off" + front_ding_state = hass.states.get("binary_sensor.front_door_ding") + assert front_ding_state is not None + assert front_ding_state.state == "off" + + ingress_ding_state = hass.states.get("binary_sensor.ingress_ding") + assert ingress_ding_state is not None + assert ingress_ding_state.state == "off" diff --git a/tests/components/ring/test_sensor.py b/tests/components/ring/test_sensor.py index 0299e626670..aadea6f0ba1 100644 --- a/tests/components/ring/test_sensor.py +++ b/tests/components/ring/test_sensor.py @@ -40,13 +40,19 @@ async def test_sensor(hass: HomeAssistant, requests_mock: requests_mock.Mocker) assert downstairs_volume_state is not None assert downstairs_volume_state.state == "2" - front_door_last_activity_state = hass.states.get("sensor.front_door_last_activity") - assert front_door_last_activity_state is not None - downstairs_wifi_signal_strength_state = hass.states.get( "sensor.downstairs_wifi_signal_strength" ) + ingress_mic_volume_state = hass.states.get("sensor.ingress_mic_volume") + assert ingress_mic_volume_state.state == "11" + + ingress_doorbell_volume_state = hass.states.get("sensor.ingress_doorbell_volume") + assert ingress_doorbell_volume_state.state == "8" + + ingress_voice_volume_state = hass.states.get("sensor.ingress_voice_volume") + assert ingress_voice_volume_state.state == "11" + if not WIFI_ENABLED: return @@ -66,6 +72,24 @@ async def test_sensor(hass: HomeAssistant, requests_mock: requests_mock.Mocker) assert front_door_wifi_signal_strength_state.state == "-58" +async def test_history( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + requests_mock: requests_mock.Mocker, +) -> None: + """Test history derived sensors.""" + await setup_platform(hass, Platform.SENSOR) + freezer.tick(SCAN_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done(True) + + front_door_last_activity_state = hass.states.get("sensor.front_door_last_activity") + assert front_door_last_activity_state.state == "2017-03-05T15:03:40+00:00" + + ingress_last_activity_state = hass.states.get("sensor.ingress_last_activity") + assert ingress_last_activity_state.state == "unknown" + + async def test_only_chime_devices( hass: HomeAssistant, requests_mock: requests_mock.Mocker, From 436c83e8a70f0eb18f27a559c65d414ee7abe1e2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 15 Mar 2024 13:22:06 +0100 Subject: [PATCH 1096/1691] Add theme color support to labels (#113404) --- .../components/config/label_registry.py | 37 +++++++++++++++- .../components/config/test_label_registry.py | 43 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/config/label_registry.py b/homeassistant/components/config/label_registry.py index d1bb74393af..1d5d526016d 100644 --- a/homeassistant/components/config/label_registry.py +++ b/homeassistant/components/config/label_registry.py @@ -10,6 +10,35 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.label_registry import LabelEntry, async_get +SUPPORTED_LABEL_THEME_COLORS = { + "primary", + "accent", + "disabled", + "red", + "pink", + "purple", + "deep-purple", + "indigo", + "blue", + "light-blue", + "cyan", + "teal", + "green", + "light-green", + "lime", + "yellow", + "amber", + "orange", + "deep-orange", + "brown", + "light-grey", + "grey", + "dark-grey", + "blue-grey", + "black", + "white", +} + @callback def async_setup(hass: HomeAssistant) -> bool: @@ -42,7 +71,9 @@ def websocket_list_labels( { vol.Required("type"): "config/label_registry/create", vol.Required("name"): str, - vol.Optional("color"): vol.Any(cv.color_hex, None), + vol.Optional("color"): vol.Any( + cv.color_hex, vol.In(SUPPORTED_LABEL_THEME_COLORS), None + ), vol.Optional("description"): vol.Any(str, None), vol.Optional("icon"): vol.Any(cv.icon, None), } @@ -93,7 +124,9 @@ def websocket_delete_label( { vol.Required("type"): "config/label_registry/update", vol.Required("label_id"): str, - vol.Optional("color"): vol.Any(cv.color_hex, None), + vol.Optional("color"): vol.Any( + cv.color_hex, vol.In(SUPPORTED_LABEL_THEME_COLORS), None + ), vol.Optional("description"): vol.Any(str, None), vol.Optional("icon"): vol.Any(cv.icon, None), vol.Optional("name"): str, diff --git a/tests/components/config/test_label_registry.py b/tests/components/config/test_label_registry.py index aa082a3de38..040b3bfe28a 100644 --- a/tests/components/config/test_label_registry.py +++ b/tests/components/config/test_label_registry.py @@ -99,6 +99,27 @@ async def test_create_label( "name": "MOCKERY", } + await client.send_json_auto_id( + { + "name": "MAGIC", + "type": "config/label_registry/create", + "color": "indigo", + "description": "This is the third label", + "icon": "mdi:three", + } + ) + + msg = await client.receive_json() + + assert len(label_registry.labels) == 3 + assert msg["result"] == { + "color": "indigo", + "description": "This is the third label", + "icon": "mdi:three", + "label_id": "magic", + "name": "MAGIC", + } + async def test_create_label_with_name_already_in_use( client: MockHAClientWebSocket, @@ -210,6 +231,28 @@ async def test_update_label( "name": "UPDATED AGAIN", } + await client.send_json_auto_id( + { + "label_id": label.label_id, + "name": "UPDATED YET AGAIN", + "icon": None, + "color": "primary", + "description": None, + "type": "config/label_registry/update", + } + ) + + msg = await client.receive_json() + + assert len(label_registry.labels) == 1 + assert msg["result"] == { + "color": "primary", + "description": None, + "icon": None, + "label_id": "mock", + "name": "UPDATED YET AGAIN", + } + async def test_update_with_name_already_in_use( client: MockHAClientWebSocket, From 0e2775667d7cea10c9009ab265b2afe0d81c6ded Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 15 Mar 2024 13:25:16 +0100 Subject: [PATCH 1097/1691] Add category registry (#110897) * Add category registry * Add entity registry support * Update homeassistant/components/config/entity_registry.py Co-authored-by: Martin Hjelmare * Use ulid instead * Add tests for adding same name in different scopes * Handle keyerror on update * Lookup tweak * Omit categories from entity registry snapshots * Use base registry * Update snapshots * Update snapshots --------- Co-authored-by: Martin Hjelmare --- homeassistant/bootstrap.py | 2 + homeassistant/components/config/__init__.py | 2 + .../components/config/category_registry.py | 134 ++++ .../components/config/entity_registry.py | 22 + homeassistant/helpers/category_registry.py | 208 ++++++ homeassistant/helpers/entity_registry.py | 64 +- pyproject.toml | 1 + tests/common.py | 2 + .../config/test_category_registry.py | 380 ++++++++++ .../components/config/test_entity_registry.py | 144 +++- .../snapshots/test_diagnostics.ambr | 140 ++++ .../snapshots/test_init.ambr | 662 ++++++++++++++++++ tests/conftest.py | 7 + tests/helpers/test_category_registry.py | 395 +++++++++++ tests/helpers/test_entity_registry.py | 78 ++- tests/syrupy.py | 4 +- 16 files changed, 2232 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/config/category_registry.py create mode 100644 homeassistant/helpers/category_registry.py create mode 100644 tests/components/config/test_category_registry.py create mode 100644 tests/helpers/test_category_registry.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 294a5551496..1d6fdcf36d4 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -62,6 +62,7 @@ from .const import ( from .exceptions import HomeAssistantError from .helpers import ( area_registry, + category_registry, config_validation as cv, device_registry, entity, @@ -342,6 +343,7 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None: template.async_setup(hass) await asyncio.gather( create_eager_task(area_registry.async_load(hass)), + create_eager_task(category_registry.async_load(hass)), create_eager_task(device_registry.async_load(hass)), create_eager_task(entity_registry.async_load(hass)), create_eager_task(floor_registry.async_load(hass)), diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index b536433cf1b..d71a00ce3bd 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -14,6 +14,7 @@ from . import ( auth, auth_provider_homeassistant, automation, + category_registry, config_entries, core, device_registry, @@ -30,6 +31,7 @@ SECTIONS = ( auth, auth_provider_homeassistant, automation, + category_registry, config_entries, core, device_registry, diff --git a/homeassistant/components/config/category_registry.py b/homeassistant/components/config/category_registry.py new file mode 100644 index 00000000000..045243a355b --- /dev/null +++ b/homeassistant/components/config/category_registry.py @@ -0,0 +1,134 @@ +"""Websocket API to interact with the category registry.""" +from typing import Any + +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import category_registry as cr, config_validation as cv + + +@callback +def async_setup(hass: HomeAssistant) -> bool: + """Register the category registry WS commands.""" + websocket_api.async_register_command(hass, websocket_list_categories) + websocket_api.async_register_command(hass, websocket_create_category) + websocket_api.async_register_command(hass, websocket_delete_category) + websocket_api.async_register_command(hass, websocket_update_category) + return True + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/category_registry/list", + vol.Required("scope"): str, + } +) +@callback +def websocket_list_categories( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle list categories command.""" + category_registry = cr.async_get(hass) + connection.send_result( + msg["id"], + [ + _entry_dict(entry) + for entry in category_registry.async_list_categories(scope=msg["scope"]) + ], + ) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/category_registry/create", + vol.Required("scope"): str, + vol.Required("name"): str, + vol.Optional("icon"): vol.Any(cv.icon, None), + } +) +@websocket_api.require_admin +@callback +def websocket_create_category( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Create category command.""" + category_registry = cr.async_get(hass) + + data = dict(msg) + data.pop("type") + data.pop("id") + + try: + entry = category_registry.async_create(**data) + except ValueError as err: + connection.send_error(msg["id"], "invalid_info", str(err)) + else: + connection.send_result(msg["id"], _entry_dict(entry)) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/category_registry/delete", + vol.Required("scope"): str, + vol.Required("category_id"): str, + } +) +@websocket_api.require_admin +@callback +def websocket_delete_category( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Delete category command.""" + category_registry = cr.async_get(hass) + + try: + category_registry.async_delete( + scope=msg["scope"], category_id=msg["category_id"] + ) + except KeyError: + connection.send_error(msg["id"], "invalid_info", "Category ID doesn't exist") + else: + connection.send_result(msg["id"]) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/category_registry/update", + vol.Required("scope"): str, + vol.Required("category_id"): str, + vol.Optional("name"): str, + vol.Optional("icon"): vol.Any(cv.icon, None), + } +) +@websocket_api.require_admin +@callback +def websocket_update_category( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle update category websocket command.""" + category_registry = cr.async_get(hass) + + data = dict(msg) + data.pop("type") + data.pop("id") + + try: + entry = category_registry.async_update(**data) + except ValueError as err: + connection.send_error(msg["id"], "invalid_info", str(err)) + except KeyError: + connection.send_error(msg["id"], "invalid_info", "Category ID doesn't exist") + else: + connection.send_result(msg["id"], _entry_dict(entry)) + + +@callback +def _entry_dict(entry: cr.CategoryEntry) -> dict[str, Any]: + """Convert entry to API format.""" + return { + "category_id": entry.category_id, + "icon": entry.icon, + "name": entry.name, + } diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 8a172b17921..2d8fe1a3645 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -153,6 +153,16 @@ def websocket_get_entities( # If passed in, we update value. Passing None will remove old value. vol.Optional("aliases"): list, vol.Optional("area_id"): vol.Any(str, None), + # Categories is a mapping of key/value (scope/category_id) pairs. + # If passed in, we update/adjust only the provided scope(s). + # Other category scopes in the entity, are left as is. + # + # Categorized items such as entities + # can only be in 1 category ID per scope at a time. + # Therefore, passing in a category ID will either add or move + # the entity to that specific category. Passing in None will + # remove the entity from the category. + vol.Optional("categories"): cv.schema_with_slug_keys(vol.Any(str, None)), vol.Optional("device_class"): vol.Any(str, None), vol.Optional("icon"): vol.Any(str, None), vol.Optional("name"): vol.Any(str, None), @@ -227,6 +237,18 @@ def websocket_update_entity( ) return + # Update the categories if provided + if "categories" in msg: + categories = entity_entry.categories.copy() + for scope, category_id in msg["categories"].items(): + if scope in categories and category_id is None: + # Remove the category from the scope as it was unset + del categories[scope] + elif category_id is not None: + # Add or update the category for the given scope + categories[scope] = category_id + changes["categories"] = categories + try: if changes: entity_entry = registry.async_update_entity(entity_id, **changes) diff --git a/homeassistant/helpers/category_registry.py b/homeassistant/helpers/category_registry.py new file mode 100644 index 00000000000..686ddaa69c8 --- /dev/null +++ b/homeassistant/helpers/category_registry.py @@ -0,0 +1,208 @@ +"""Provide a way to categorize things within a defined scope.""" +from __future__ import annotations + +from collections.abc import Iterable +import dataclasses +from dataclasses import dataclass, field +from typing import Literal, TypedDict, cast + +from homeassistant.core import HomeAssistant, callback +from homeassistant.util.ulid import ulid_now + +from .registry import BaseRegistry +from .typing import UNDEFINED, EventType, UndefinedType + +DATA_REGISTRY = "category_registry" +EVENT_CATEGORY_REGISTRY_UPDATED = "category_registry_updated" +STORAGE_KEY = "core.category_registry" +STORAGE_VERSION_MAJOR = 1 + + +class EventCategoryRegistryUpdatedData(TypedDict): + """Event data for when the category registry is updated.""" + + action: Literal["create", "remove", "update"] + scope: str + category_id: str + + +EventCategoryRegistryUpdated = EventType[EventCategoryRegistryUpdatedData] + + +@dataclass(slots=True, kw_only=True, frozen=True) +class CategoryEntry: + """Category registry entry.""" + + category_id: str = field(default_factory=ulid_now) + icon: str | None = None + name: str + + +class CategoryRegistry(BaseRegistry): + """Class to hold a registry of categories by scope.""" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the category registry.""" + self.hass = hass + self.categories: dict[str, dict[str, CategoryEntry]] = {} + self._store = hass.helpers.storage.Store( + STORAGE_VERSION_MAJOR, + STORAGE_KEY, + atomic_writes=True, + ) + + @callback + def async_get_category( + self, *, scope: str, category_id: str + ) -> CategoryEntry | None: + """Get category by ID.""" + if scope not in self.categories: + return None + return self.categories[scope].get(category_id) + + @callback + def async_list_categories(self, *, scope: str) -> Iterable[CategoryEntry]: + """Get all categories.""" + if scope not in self.categories: + return [] + return self.categories[scope].values() + + @callback + def async_create( + self, + *, + name: str, + scope: str, + icon: str | None = None, + ) -> CategoryEntry: + """Create a new category.""" + self._async_ensure_name_is_available(scope, name) + category = CategoryEntry( + icon=icon, + name=name, + ) + + if scope not in self.categories: + self.categories[scope] = {} + + self.categories[scope][category.category_id] = category + + self.async_schedule_save() + self.hass.bus.async_fire( + EVENT_CATEGORY_REGISTRY_UPDATED, + EventCategoryRegistryUpdatedData( + action="create", scope=scope, category_id=category.category_id + ), + ) + return category + + @callback + def async_delete(self, *, scope: str, category_id: str) -> None: + """Delete category.""" + del self.categories[scope][category_id] + self.hass.bus.async_fire( + EVENT_CATEGORY_REGISTRY_UPDATED, + EventCategoryRegistryUpdatedData( + action="remove", + scope=scope, + category_id=category_id, + ), + ) + self.async_schedule_save() + + @callback + def async_update( + self, + *, + scope: str, + category_id: str, + icon: str | None | UndefinedType = UNDEFINED, + name: str | UndefinedType = UNDEFINED, + ) -> CategoryEntry: + """Update name or icon of the category.""" + old = self.categories[scope][category_id] + changes = {} + + if icon is not UNDEFINED and icon != old.icon: + changes["icon"] = icon + + if name is not UNDEFINED and name != old.name: + changes["name"] = name + self._async_ensure_name_is_available(scope, name, category_id) + + if not changes: + return old + + new = self.categories[scope][category_id] = dataclasses.replace(old, **changes) # type: ignore[arg-type] + + self.async_schedule_save() + self.hass.bus.async_fire( + EVENT_CATEGORY_REGISTRY_UPDATED, + EventCategoryRegistryUpdatedData( + action="update", scope=scope, category_id=category_id + ), + ) + + return new + + async def async_load(self) -> None: + """Load the category registry.""" + data = await self._store.async_load() + category_entries: dict[str, dict[str, CategoryEntry]] = {} + + if data is not None: + for scope, categories in data["categories"].items(): + category_entries[scope] = { + category["category_id"]: CategoryEntry( + category_id=category["category_id"], + icon=category["icon"], + name=category["name"], + ) + for category in categories + } + + self.categories = category_entries + + @callback + def _data_to_save(self) -> dict[str, dict[str, list[dict[str, str | None]]]]: + """Return data of category registry to store in a file.""" + return { + "categories": { + scope: [ + { + "category_id": entry.category_id, + "icon": entry.icon, + "name": entry.name, + } + for entry in entries.values() + ] + for scope, entries in self.categories.items() + } + } + + @callback + def _async_ensure_name_is_available( + self, scope: str, name: str, category_id: str | None = None + ) -> None: + """Ensure name is available within the scope.""" + if scope not in self.categories: + return + for category in self.categories[scope].values(): + if ( + category.name.casefold() == name.casefold() + and category.category_id != category_id + ): + raise ValueError(f"The name '{name}' is already in use") + + +@callback +def async_get(hass: HomeAssistant) -> CategoryRegistry: + """Get category registry.""" + return cast(CategoryRegistry, hass.data[DATA_REGISTRY]) + + +async def async_load(hass: HomeAssistant) -> None: + """Load category registry.""" + assert DATA_REGISTRY not in hass.data + hass.data[DATA_REGISTRY] = CategoryRegistry(hass) + await hass.data[DATA_REGISTRY].async_load() diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 3c437faf18c..d0ac52fe9fc 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -67,7 +67,7 @@ EVENT_ENTITY_REGISTRY_UPDATED = "entity_registry_updated" _LOGGER = logging.getLogger(__name__) STORAGE_VERSION_MAJOR = 1 -STORAGE_VERSION_MINOR = 13 +STORAGE_VERSION_MINOR = 14 STORAGE_KEY = "core.entity_registry" CLEANUP_INTERVAL = 3600 * 24 @@ -164,6 +164,7 @@ class RegistryEntry: previous_unique_id: str | None = attr.ib(default=None) aliases: set[str] = attr.ib(factory=set) area_id: str | None = attr.ib(default=None) + categories: dict[str, str] = attr.ib(factory=dict) capabilities: Mapping[str, Any] | None = attr.ib(default=None) config_entry_id: str | None = attr.ib(default=None) device_class: str | None = attr.ib(default=None) @@ -262,6 +263,7 @@ class RegistryEntry: # it every time return { "area_id": self.area_id, + "categories": self.categories, "config_entry_id": self.config_entry_id, "device_id": self.device_id, "disabled_by": self.disabled_by, @@ -319,6 +321,7 @@ class RegistryEntry: { "aliases": list(self.aliases), "area_id": self.area_id, + "categories": self.categories, "capabilities": self.capabilities, "config_entry_id": self.config_entry_id, "device_class": self.device_class, @@ -498,6 +501,11 @@ class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]): for entity in data["entities"]: entity["labels"] = [] + if old_major_version == 1 and old_minor_version < 14: + # Version 1.14 adds categories + for entity in data["entities"]: + entity["categories"] = {} + if old_major_version > 1: raise NotImplementedError return data @@ -952,6 +960,7 @@ class EntityRegistry(BaseRegistry): *, aliases: set[str] | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED, + categories: dict[str, str] | UndefinedType = UNDEFINED, capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED, @@ -1003,6 +1012,7 @@ class EntityRegistry(BaseRegistry): for attr_name, value in ( ("aliases", aliases), ("area_id", area_id), + ("categories", categories), ("capabilities", capabilities), ("config_entry_id", config_entry_id), ("device_class", device_class), @@ -1081,6 +1091,7 @@ class EntityRegistry(BaseRegistry): *, aliases: set[str] | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED, + categories: dict[str, str] | UndefinedType = UNDEFINED, capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED, @@ -1106,6 +1117,7 @@ class EntityRegistry(BaseRegistry): entity_id, aliases=aliases, area_id=area_id, + categories=categories, capabilities=capabilities, config_entry_id=config_entry_id, device_class=device_class, @@ -1196,6 +1208,7 @@ class EntityRegistry(BaseRegistry): entities[entity["entity_id"]] = RegistryEntry( aliases=set(entity["aliases"]), area_id=entity["area_id"], + categories=entity["categories"], capabilities=entity["capabilities"], config_entry_id=entity["config_entry_id"], device_class=entity["device_class"], @@ -1255,6 +1268,17 @@ class EntityRegistry(BaseRegistry): ], } + @callback + def async_clear_category_id(self, scope: str, category_id: str) -> None: + """Clear category id from registry entries.""" + for entity_id, entry in self.entities.items(): + if ( + existing_category_id := entry.categories.get(scope) + ) and category_id == existing_category_id: + categories = entry.categories.copy() + del categories[scope] + self.async_update_entity(entity_id, categories=categories) + @callback def async_clear_label_id(self, label_id: str) -> None: """Clear label from registry entries.""" @@ -1344,6 +1368,21 @@ def async_entries_for_label( return [entry for entry in registry.entities.values() if label_id in entry.labels] +@callback +def async_entries_for_category( + registry: EntityRegistry, scope: str, category_id: str +) -> list[RegistryEntry]: + """Return entries that match a category in a scope.""" + return [ + entry + for entry in registry.entities.values() + if ( + (existing_category_id := entry.categories.get(scope)) + and category_id == existing_category_id + ) + ] + + @callback def async_entries_for_config_entry( registry: EntityRegistry, config_entry_id: str @@ -1386,13 +1425,13 @@ def async_config_entry_disabled_by_changed( def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: """Clean up device registry when entities removed.""" # pylint: disable-next=import-outside-toplevel - from . import event, label_registry as lr + from . import category_registry as cr, event, label_registry as lr @callback - def _label_removed_from_registry_filter( - event: lr.EventLabelRegistryUpdated, + def _removed_from_registry_filter( + event: lr.EventLabelRegistryUpdated | cr.EventCategoryRegistryUpdated, ) -> bool: - """Filter all except for the remove action from label registry events.""" + """Filter all except for the remove action from registry events.""" return event.data["action"] == "remove" @callback @@ -1402,10 +1441,23 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: hass.bus.async_listen( event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, - event_filter=_label_removed_from_registry_filter, + event_filter=_removed_from_registry_filter, listener=_handle_label_registry_update, ) + @callback + def _handle_category_registry_update( + event: cr.EventCategoryRegistryUpdated, + ) -> None: + """Update entity that have a category that has been removed.""" + registry.async_clear_category_id(event.data["scope"], event.data["category_id"]) + + hass.bus.async_listen( + event_type=cr.EVENT_CATEGORY_REGISTRY_UPDATED, + event_filter=_removed_from_registry_filter, + listener=_handle_category_registry_update, + ) + @callback def cleanup(_: datetime) -> None: """Clean up entity registry.""" diff --git a/pyproject.toml b/pyproject.toml index f258a0aa2f7..bc5c3c53b85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -688,6 +688,7 @@ ignore = [ [tool.ruff.lint.flake8-import-conventions.extend-aliases] voluptuous = "vol" "homeassistant.helpers.area_registry" = "ar" +"homeassistant.helpers.category_registry" = "cr" "homeassistant.helpers.config_validation" = "cv" "homeassistant.helpers.device_registry" = "dr" "homeassistant.helpers.entity_registry" = "er" diff --git a/tests/common.py b/tests/common.py index 2e6b30d680b..2b1db405de7 100644 --- a/tests/common.py +++ b/tests/common.py @@ -59,6 +59,7 @@ from homeassistant.core import ( ) from homeassistant.helpers import ( area_registry as ar, + category_registry as cr, device_registry as dr, entity, entity_platform, @@ -334,6 +335,7 @@ async def async_test_home_assistant( "homeassistant.helpers.restore_state.start.async_at_start", ): await ar.async_load(hass) + await cr.async_load(hass) await dr.async_load(hass) await er.async_load(hass) await fr.async_load(hass) diff --git a/tests/components/config/test_category_registry.py b/tests/components/config/test_category_registry.py new file mode 100644 index 00000000000..fd762d80c3b --- /dev/null +++ b/tests/components/config/test_category_registry.py @@ -0,0 +1,380 @@ +"""Test category registry API.""" +import pytest + +from homeassistant.components.config import category_registry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import category_registry as cr + +from tests.common import ANY +from tests.typing import MockHAClientWebSocket, WebSocketGenerator + + +@pytest.fixture(name="client") +async def client_fixture( + hass: HomeAssistant, hass_ws_client: WebSocketGenerator +) -> MockHAClientWebSocket: + """Fixture that can interact with the config manager API.""" + category_registry.async_setup(hass) + return await hass_ws_client(hass) + + +async def test_list_categories( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test list entries.""" + category1 = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + category2 = category_registry.async_create( + scope="automation", + name="Something else", + icon="mdi:home", + ) + category3 = category_registry.async_create( + scope="zone", + name="Grocery stores", + icon="mdi:store", + ) + + assert len(category_registry.categories) == 2 + assert len(category_registry.categories["automation"]) == 2 + assert len(category_registry.categories["zone"]) == 1 + + await client.send_json_auto_id( + {"type": "config/category_registry/list", "scope": "automation"} + ) + + msg = await client.receive_json() + + assert len(msg["result"]) == 2 + assert msg["result"][0] == { + "category_id": category1.category_id, + "name": "Energy saving", + "icon": "mdi:leaf", + } + assert msg["result"][1] == { + "category_id": category2.category_id, + "name": "Something else", + "icon": "mdi:home", + } + + await client.send_json_auto_id( + {"type": "config/category_registry/list", "scope": "zone"} + ) + + msg = await client.receive_json() + + assert len(msg["result"]) == 1 + assert msg["result"][0] == { + "category_id": category3.category_id, + "name": "Grocery stores", + "icon": "mdi:store", + } + + +async def test_create_category( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test create entry.""" + await client.send_json_auto_id( + { + "type": "config/category_registry/create", + "scope": "automation", + "name": "Energy saving", + "icon": "mdi:leaf", + } + ) + + msg = await client.receive_json() + + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + assert msg["result"] == { + "icon": "mdi:leaf", + "category_id": ANY, + "name": "Energy saving", + } + + await client.send_json_auto_id( + { + "scope": "automation", + "name": "Something else", + "type": "config/category_registry/create", + } + ) + + msg = await client.receive_json() + + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 2 + + assert msg["result"] == { + "icon": None, + "category_id": ANY, + "name": "Something else", + } + + # Test adding the same one again in a different scope + await client.send_json_auto_id( + { + "type": "config/category_registry/create", + "scope": "script", + "name": "Energy saving", + "icon": "mdi:leaf", + } + ) + + msg = await client.receive_json() + + assert len(category_registry.categories) == 2 + assert len(category_registry.categories["automation"]) == 2 + assert len(category_registry.categories["script"]) == 1 + + assert msg["result"] == { + "icon": "mdi:leaf", + "category_id": ANY, + "name": "Energy saving", + } + + +async def test_create_category_with_name_already_in_use( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test create entry that should fail.""" + category_registry.async_create( + scope="automation", + name="Energy saving", + ) + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "automation", + "name": "ENERGY SAVING", + "type": "config/category_registry/create", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "The name 'ENERGY SAVING' is already in use" + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + +async def test_delete_category( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test delete entry.""" + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "automation", + "category_id": category.category_id, + "type": "config/category_registry/delete", + } + ) + + msg = await client.receive_json() + + assert msg["success"] + assert len(category_registry.categories) == 1 + assert not category_registry.categories["automation"] + + +async def test_delete_non_existing_category( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test delete entry that should fail.""" + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "automation", + "category_id": "idkfa", + "type": "config/category_registry/delete", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "Category ID doesn't exist" + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "bullshizzle", + "category_id": category.category_id, + "type": "config/category_registry/delete", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "Category ID doesn't exist" + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + +async def test_update_category( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test update entry.""" + category = category_registry.async_create( + scope="automation", + name="Energy saving", + ) + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "automation", + "category_id": category.category_id, + "name": "ENERGY SAVING", + "icon": "mdi:left", + "type": "config/category_registry/update", + } + ) + + msg = await client.receive_json() + + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + assert msg["result"] == { + "icon": "mdi:left", + "category_id": category.category_id, + "name": "ENERGY SAVING", + } + + await client.send_json_auto_id( + { + "scope": "automation", + "category_id": category.category_id, + "name": "Energy saving", + "icon": None, + "type": "config/category_registry/update", + } + ) + + msg = await client.receive_json() + + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + assert msg["result"] == { + "icon": None, + "category_id": category.category_id, + "name": "Energy saving", + } + + +async def test_update_with_name_already_in_use( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test update entry.""" + category_registry.async_create( + scope="automation", + name="Energy saving", + ) + category = category_registry.async_create( + scope="automation", + name="Something else", + ) + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 2 + + await client.send_json_auto_id( + { + "scope": "automation", + "category_id": category.category_id, + "name": "ENERGY SAVING", + "type": "config/category_registry/update", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "The name 'ENERGY SAVING' is already in use" + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 2 + + +async def test_update_non_existing_category( + client: MockHAClientWebSocket, + category_registry: cr.CategoryRegistry, +) -> None: + """Test update entry that should fail.""" + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "automation", + "category_id": "idkfa", + "name": "New category name", + "type": "config/category_registry/update", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "Category ID doesn't exist" + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await client.send_json_auto_id( + { + "scope": "bullshizzle", + "category_id": category.category_id, + "name": "New category name", + "type": "config/category_registry/update", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "Category ID doesn't exist" + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 36e3b8b46ff..30a7106c0dd 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -61,6 +61,7 @@ async def test_list_entities( assert msg["result"] == [ { "area_id": None, + "categories": {}, "config_entry_id": None, "device_id": None, "disabled_by": None, @@ -80,6 +81,7 @@ async def test_list_entities( }, { "area_id": None, + "categories": {}, "config_entry_id": None, "device_id": None, "disabled_by": None, @@ -126,6 +128,7 @@ async def test_list_entities( assert msg["result"] == [ { "area_id": None, + "categories": {}, "config_entry_id": None, "device_id": None, "disabled_by": None, @@ -349,6 +352,7 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) -> "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": None, "device_class": None, "device_id": None, @@ -382,6 +386,7 @@ async def test_get_entity(hass: HomeAssistant, client: MockHAClientWebSocket) -> "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": None, "device_class": None, "device_id": None, @@ -440,6 +445,7 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket) "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": None, "device_class": None, "device_id": None, @@ -464,6 +470,7 @@ async def test_get_entities(hass: HomeAssistant, client: MockHAClientWebSocket) "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": None, "device_class": None, "device_id": None, @@ -514,13 +521,14 @@ async def test_update_entity( assert state.name == "before update" assert state.attributes[ATTR_ICON] == "icon:before update" - # UPDATE AREA, DEVICE_CLASS, HIDDEN_BY, ICON AND NAME + # Update area, categories, device_class, hidden_by, icon, labels & name await client.send_json_auto_id( { "type": "config/entity_registry/update", "entity_id": "test_domain.world", "aliases": ["alias_1", "alias_2"], "area_id": "mock-area-id", + "categories": {"scope1": "id", "scope2": "id"}, "device_class": "custom_device_class", "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", @@ -535,6 +543,7 @@ async def test_update_entity( "aliases": unordered(["alias_1", "alias_2"]), "area_id": "mock-area-id", "capabilities": None, + "categories": {"scope1": "id", "scope2": "id"}, "config_entry_id": None, "device_class": "custom_device_class", "device_id": None, @@ -561,7 +570,7 @@ async def test_update_entity( assert state.name == "after update" assert state.attributes[ATTR_ICON] == "icon:after update" - # UPDATE HIDDEN_BY TO ILLEGAL VALUE + # Update hidden_by to illegal value await client.send_json_auto_id( { "type": "config/entity_registry/update", @@ -575,7 +584,7 @@ async def test_update_entity( assert registry.entities["test_domain.world"].hidden_by is RegistryEntryHider.USER - # UPDATE DISABLED_BY TO USER + # Update disabled_by to user await client.send_json_auto_id( { "type": "config/entity_registry/update", @@ -592,7 +601,7 @@ async def test_update_entity( registry.entities["test_domain.world"].disabled_by is RegistryEntryDisabler.USER ) - # UPDATE DISABLED_BY TO NONE + # Update disabled_by to None await client.send_json_auto_id( { "type": "config/entity_registry/update", @@ -608,6 +617,7 @@ async def test_update_entity( "aliases": unordered(["alias_1", "alias_2"]), "area_id": "mock-area-id", "capabilities": None, + "categories": {"scope1": "id", "scope2": "id"}, "config_entry_id": None, "device_class": "custom_device_class", "device_id": None, @@ -631,7 +641,7 @@ async def test_update_entity( "require_restart": True, } - # UPDATE ENTITY OPTION + # Update entity option await client.send_json_auto_id( { "type": "config/entity_registry/update", @@ -648,6 +658,127 @@ async def test_update_entity( "aliases": unordered(["alias_1", "alias_2"]), "area_id": "mock-area-id", "capabilities": None, + "categories": {"scope1": "id", "scope2": "id"}, + "config_entry_id": None, + "device_class": "custom_device_class", + "device_id": None, + "disabled_by": None, + "entity_category": None, + "entity_id": "test_domain.world", + "has_entity_name": False, + "hidden_by": "user", # We exchange strings over the WS API, not enums + "icon": "icon:after update", + "id": ANY, + "labels": [], + "name": "after update", + "options": {"sensor": {"unit_of_measurement": "beard_second"}}, + "original_device_class": None, + "original_icon": None, + "original_name": None, + "platform": "test_platform", + "translation_key": None, + "unique_id": "1234", + }, + } + + # Add a category to the entity + await client.send_json_auto_id( + { + "type": "config/entity_registry/update", + "entity_id": "test_domain.world", + "categories": {"scope3": "id"}, + } + ) + + msg = await client.receive_json() + assert msg["success"] + + assert msg["result"] == { + "entity_entry": { + "aliases": unordered(["alias_1", "alias_2"]), + "area_id": "mock-area-id", + "capabilities": None, + "categories": {"scope1": "id", "scope2": "id", "scope3": "id"}, + "config_entry_id": None, + "device_class": "custom_device_class", + "device_id": None, + "disabled_by": None, + "entity_category": None, + "entity_id": "test_domain.world", + "has_entity_name": False, + "hidden_by": "user", # We exchange strings over the WS API, not enums + "icon": "icon:after update", + "id": ANY, + "labels": [], + "name": "after update", + "options": {"sensor": {"unit_of_measurement": "beard_second"}}, + "original_device_class": None, + "original_icon": None, + "original_name": None, + "platform": "test_platform", + "translation_key": None, + "unique_id": "1234", + }, + } + + # Move the entity to a different category + await client.send_json_auto_id( + { + "type": "config/entity_registry/update", + "entity_id": "test_domain.world", + "categories": {"scope3": "other_id"}, + } + ) + + msg = await client.receive_json() + assert msg["success"] + + assert msg["result"] == { + "entity_entry": { + "aliases": unordered(["alias_1", "alias_2"]), + "area_id": "mock-area-id", + "capabilities": None, + "categories": {"scope1": "id", "scope2": "id", "scope3": "other_id"}, + "config_entry_id": None, + "device_class": "custom_device_class", + "device_id": None, + "disabled_by": None, + "entity_category": None, + "entity_id": "test_domain.world", + "has_entity_name": False, + "hidden_by": "user", # We exchange strings over the WS API, not enums + "icon": "icon:after update", + "id": ANY, + "labels": [], + "name": "after update", + "options": {"sensor": {"unit_of_measurement": "beard_second"}}, + "original_device_class": None, + "original_icon": None, + "original_name": None, + "platform": "test_platform", + "translation_key": None, + "unique_id": "1234", + }, + } + + # Move the entity to a different category + await client.send_json_auto_id( + { + "type": "config/entity_registry/update", + "entity_id": "test_domain.world", + "categories": {"scope2": None}, + } + ) + + msg = await client.receive_json() + assert msg["success"] + + assert msg["result"] == { + "entity_entry": { + "aliases": unordered(["alias_1", "alias_2"]), + "area_id": "mock-area-id", + "capabilities": None, + "categories": {"scope1": "id", "scope3": "other_id"}, "config_entry_id": None, "device_class": "custom_device_class", "device_id": None, @@ -702,6 +833,7 @@ async def test_update_entity_require_restart( "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": config_entry.entry_id, "device_class": None, "device_id": None, @@ -815,6 +947,7 @@ async def test_update_entity_no_changes( "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": None, "device_class": None, "device_id": None, @@ -904,6 +1037,7 @@ async def test_update_entity_id( "aliases": [], "area_id": None, "capabilities": None, + "categories": {}, "config_entry_id": None, "device_class": None, "device_id": None, diff --git a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr index 18ad22921ed..0b6e9aebecd 100644 --- a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr +++ b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr @@ -61,6 +61,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -111,6 +113,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -159,6 +163,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -208,6 +214,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -258,6 +266,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -308,6 +318,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -356,6 +368,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -405,6 +419,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -455,6 +471,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -505,6 +523,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -553,6 +573,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -602,6 +624,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -652,6 +676,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -702,6 +728,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -750,6 +778,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -799,6 +829,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -849,6 +881,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -899,6 +933,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -947,6 +983,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -996,6 +1034,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1046,6 +1086,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1096,6 +1138,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1144,6 +1188,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1193,6 +1239,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1243,6 +1291,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1293,6 +1343,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1341,6 +1393,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1390,6 +1444,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1440,6 +1496,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1490,6 +1548,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1538,6 +1598,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1587,6 +1649,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1637,6 +1701,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1687,6 +1753,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1737,6 +1805,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -1787,6 +1857,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -1821,6 +1893,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -1862,6 +1936,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -1894,6 +1970,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -1928,6 +2006,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -1965,6 +2045,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2002,6 +2084,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2039,6 +2123,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2073,6 +2159,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2114,6 +2202,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2146,6 +2236,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2180,6 +2272,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2217,6 +2311,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2254,6 +2350,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2291,6 +2389,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2325,6 +2425,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2366,6 +2468,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2398,6 +2502,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2432,6 +2538,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2469,6 +2577,8 @@ 'capabilities': dict({ 'state_class': 'total_increasing', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2506,6 +2616,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2543,6 +2655,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2577,6 +2691,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2618,6 +2734,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2650,6 +2768,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2688,6 +2808,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2720,6 +2842,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2758,6 +2882,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2790,6 +2916,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2828,6 +2956,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2860,6 +2990,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2898,6 +3030,8 @@ 'check-wiring', ]), }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2930,6 +3064,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', @@ -2996,6 +3132,8 @@ 'capabilities': dict({ 'state_class': 'measurement', }), + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': None, @@ -3038,6 +3176,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', 'device_class': None, 'disabled_by': 'integration', diff --git a/tests/components/homekit_controller/snapshots/test_init.ambr b/tests/components/homekit_controller/snapshots/test_init.ambr index 94e7dd7bc17..10f62920d8e 100644 --- a/tests/components/homekit_controller/snapshots/test_init.ambr +++ b/tests/components/homekit_controller/snapshots/test_init.ambr @@ -37,6 +37,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -78,6 +80,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -126,6 +130,8 @@ 'manual', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -174,6 +180,8 @@ 'purifying', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -220,6 +228,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -262,6 +272,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -304,6 +316,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -354,6 +368,8 @@ 'sleepy', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -411,6 +427,8 @@ 'router', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -459,6 +477,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -497,6 +517,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -535,6 +557,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -609,6 +633,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -680,6 +706,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -719,6 +747,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -758,6 +788,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -799,6 +831,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -841,6 +875,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -911,6 +947,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -950,6 +988,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -989,6 +1029,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1030,6 +1072,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1072,6 +1116,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1142,6 +1188,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1181,6 +1229,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1220,6 +1270,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1261,6 +1313,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1303,6 +1357,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1377,6 +1433,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1419,6 +1477,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1463,6 +1523,8 @@ 'mode': , 'step': 1, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1505,6 +1567,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1575,6 +1639,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1614,6 +1680,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1655,6 +1723,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1733,6 +1803,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1775,6 +1847,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1823,6 +1897,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1882,6 +1958,8 @@ 'mode': , 'step': 1, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1924,6 +2002,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -1998,6 +2078,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2039,6 +2121,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2117,6 +2201,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2156,6 +2242,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2195,6 +2283,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2243,6 +2333,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2299,6 +2391,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2341,6 +2435,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2385,6 +2481,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2428,6 +2526,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2469,6 +2569,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2507,6 +2609,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2581,6 +2685,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2622,6 +2728,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2665,6 +2773,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2708,6 +2818,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2751,6 +2863,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2794,6 +2908,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2837,6 +2953,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2878,6 +2996,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2917,6 +3037,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -2992,6 +3114,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3031,6 +3155,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3072,6 +3198,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3145,6 +3273,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3184,6 +3314,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3223,6 +3355,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3261,6 +3395,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3311,6 +3447,8 @@ 'min_humidity': 20, 'min_temp': 7.2, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3373,6 +3511,8 @@ 'away', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3421,6 +3561,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3465,6 +3607,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3508,6 +3652,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3581,6 +3727,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3620,6 +3768,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3661,6 +3811,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3734,6 +3886,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3773,6 +3927,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3814,6 +3970,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3891,6 +4049,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3930,6 +4090,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -3969,6 +4131,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4007,6 +4171,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4057,6 +4223,8 @@ 'min_humidity': 20, 'min_temp': 7.2, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4119,6 +4287,8 @@ 'away', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4167,6 +4337,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4211,6 +4383,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4254,6 +4428,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4331,6 +4507,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4370,6 +4548,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4441,6 +4621,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4491,6 +4673,8 @@ 'min_humidity': 20, 'min_temp': 7.2, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4552,6 +4736,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4596,6 +4782,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4639,6 +4827,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4712,6 +4902,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4751,6 +4943,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4792,6 +4986,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4865,6 +5061,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4904,6 +5102,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -4945,6 +5145,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5022,6 +5224,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5061,6 +5265,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5100,6 +5306,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5138,6 +5346,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5192,6 +5402,8 @@ 'min_humidity': 20, 'min_temp': 7.2, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5259,6 +5471,8 @@ 'away', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5307,6 +5521,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5351,6 +5567,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5394,6 +5612,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5471,6 +5691,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5510,6 +5732,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5549,6 +5773,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5590,6 +5816,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5633,6 +5861,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5674,6 +5904,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5748,6 +5980,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5792,6 +6026,8 @@ 'mode': , 'step': 1, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5839,6 +6075,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5883,6 +6121,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5926,6 +6166,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -5970,6 +6212,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6013,6 +6257,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6090,6 +6336,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6131,6 +6379,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6174,6 +6424,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6217,6 +6469,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6260,6 +6514,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6301,6 +6557,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6340,6 +6598,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6414,6 +6674,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6453,6 +6715,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6491,6 +6755,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6532,6 +6798,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6607,6 +6875,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6646,6 +6916,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6720,6 +6992,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6759,6 +7033,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6801,6 +7077,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6875,6 +7153,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6946,6 +7226,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -6985,6 +7267,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7027,6 +7311,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7105,6 +7391,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7146,6 +7434,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7221,6 +7511,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7292,6 +7584,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7333,6 +7627,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7413,6 +7709,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7462,6 +7760,8 @@ 'min_temp': 7, 'target_temp_step': 1.0, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7514,6 +7814,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7563,6 +7865,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7607,6 +7911,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7650,6 +7956,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7723,6 +8031,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7798,6 +8108,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7869,6 +8181,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7912,6 +8226,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -7958,6 +8274,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8036,6 +8354,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8075,6 +8395,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8117,6 +8439,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8191,6 +8515,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8262,6 +8588,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8301,6 +8629,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8343,6 +8673,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8421,6 +8753,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8462,6 +8796,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8537,6 +8873,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8608,6 +8946,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8649,6 +8989,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8730,6 +9072,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8801,6 +9145,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8842,6 +9188,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8923,6 +9271,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -8976,6 +9326,8 @@ ]), 'target_temp_step': 1.0, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9033,6 +9385,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9082,6 +9436,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9126,6 +9482,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9169,6 +9527,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9242,6 +9602,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9317,6 +9679,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9388,6 +9752,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9434,6 +9800,8 @@ 'max_humidity': 100, 'min_humidity': 0, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9485,6 +9853,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9562,6 +9932,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9633,6 +10005,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9679,6 +10053,8 @@ 'max_humidity': 80, 'min_humidity': 20, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9730,6 +10106,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9807,6 +10185,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9878,6 +10258,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9926,6 +10308,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -9982,6 +10366,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10060,6 +10446,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10115,6 +10503,8 @@ 'min_temp': 18, 'target_temp_step': 0.5, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10175,6 +10565,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10252,6 +10644,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10299,6 +10693,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10384,6 +10780,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10431,6 +10829,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10516,6 +10916,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10563,6 +10965,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10648,6 +11052,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10695,6 +11101,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10780,6 +11188,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10827,6 +11237,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10922,6 +11334,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -10969,6 +11383,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11064,6 +11480,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11107,6 +11525,8 @@ 'single_press', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11154,6 +11574,8 @@ 'single_press', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11201,6 +11623,8 @@ 'single_press', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11248,6 +11672,8 @@ 'single_press', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11293,6 +11719,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11367,6 +11795,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11410,6 +11840,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11486,6 +11918,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11529,6 +11963,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11605,6 +12041,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11648,6 +12086,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11724,6 +12164,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11767,6 +12209,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11843,6 +12287,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11886,6 +12332,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -11962,6 +12410,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12005,6 +12455,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12081,6 +12533,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12124,6 +12578,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12200,6 +12656,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12275,6 +12733,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12323,6 +12783,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12413,6 +12875,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12454,6 +12918,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12495,6 +12961,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12570,6 +13038,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12611,6 +13081,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12652,6 +13124,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12690,6 +13164,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12764,6 +13240,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12812,6 +13290,8 @@ 'max_temp': 37, 'min_temp': 4.5, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12870,6 +13350,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12914,6 +13396,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -12957,6 +13441,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13034,6 +13520,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13083,6 +13571,8 @@ 'HDMI 4', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13133,6 +13623,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13207,6 +13699,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13248,6 +13742,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13323,6 +13819,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13398,6 +13896,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13437,6 +13937,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13475,6 +13977,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13513,6 +14017,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13551,6 +14057,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13589,6 +14097,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13663,6 +14173,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13706,6 +14218,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13786,6 +14300,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13834,6 +14350,8 @@ 'max_temp': 35, 'min_temp': 7, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13889,6 +14407,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13938,6 +14458,8 @@ 'fahrenheit', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -13982,6 +14504,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14025,6 +14549,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14102,6 +14628,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14150,6 +14678,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14223,6 +14753,8 @@ 'sleepy', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14280,6 +14812,8 @@ 'router', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14364,6 +14898,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14403,6 +14939,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14442,6 +14980,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14487,6 +15027,8 @@ 'long_press', ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14532,6 +15074,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14570,6 +15114,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14644,6 +15190,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14683,6 +15231,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14722,6 +15272,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14797,6 +15349,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14838,6 +15392,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14880,6 +15436,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14923,6 +15481,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -14966,6 +15526,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15009,6 +15571,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15086,6 +15650,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15125,6 +15691,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15166,6 +15734,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15207,6 +15777,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15248,6 +15820,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15289,6 +15863,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15330,6 +15906,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15371,6 +15949,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15412,6 +15992,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15489,6 +16071,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15528,6 +16112,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15570,6 +16156,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15644,6 +16232,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15715,6 +16305,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15754,6 +16346,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15796,6 +16390,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15874,6 +16470,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15913,6 +16511,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -15955,6 +16555,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16029,6 +16631,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16068,6 +16672,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16110,6 +16716,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16184,6 +16792,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16223,6 +16833,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16265,6 +16877,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16339,6 +16953,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16410,6 +17026,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16449,6 +17067,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16491,6 +17111,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16569,6 +17191,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16608,6 +17232,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16683,6 +17309,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16724,6 +17352,8 @@ 'capabilities': dict({ 'preset_modes': None, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16772,6 +17402,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16852,6 +17484,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16923,6 +17557,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -16964,6 +17600,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17007,6 +17645,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17050,6 +17690,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17123,6 +17765,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17162,6 +17806,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17239,6 +17885,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17285,6 +17933,8 @@ 'max_humidity': 100, 'min_humidity': 0, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17343,6 +17993,8 @@ , ]), }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17412,6 +18064,8 @@ 'mode': , 'step': 1, }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17456,6 +18110,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17533,6 +18189,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17574,6 +18232,8 @@ 'capabilities': dict({ 'state_class': , }), + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, @@ -17615,6 +18275,8 @@ ]), 'area_id': None, 'capabilities': None, + 'categories': dict({ + }), 'config_entry_id': 'TestData', 'device_class': None, 'disabled_by': None, diff --git a/tests/conftest.py b/tests/conftest.py index 40bd4157957..1f8ecc407d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,6 +53,7 @@ from homeassistant.const import HASSIO_USER_NAME from homeassistant.core import CoreState, HassJob, HomeAssistant from homeassistant.helpers import ( area_registry as ar, + category_registry as cr, config_entry_oauth2_flow, device_registry as dr, entity_registry as er, @@ -1618,6 +1619,12 @@ def mock_bluetooth( """Mock out bluetooth from starting.""" +@pytest.fixture +def category_registry(hass: HomeAssistant) -> cr.CategoryRegistry: + """Return the category registry from the current hass instance.""" + return cr.async_get(hass) + + @pytest.fixture def area_registry(hass: HomeAssistant) -> ar.AreaRegistry: """Return the area registry from the current hass instance.""" diff --git a/tests/helpers/test_category_registry.py b/tests/helpers/test_category_registry.py new file mode 100644 index 00000000000..d204ec21e39 --- /dev/null +++ b/tests/helpers/test_category_registry.py @@ -0,0 +1,395 @@ +"""Tests for the category registry.""" +import re +from typing import Any + +import pytest + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import category_registry as cr + +from tests.common import async_capture_events, flush_store + + +async def test_list_categories_for_scope( + category_registry: cr.CategoryRegistry, +) -> None: + """Make sure that we can read categories for scope.""" + categories = category_registry.async_list_categories(scope="automation") + assert len(list(categories)) == len( + category_registry.categories.get("automation", {}) + ) + + +async def test_create_category( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make sure that we can create new categories.""" + update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED) + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + + assert category.category_id + assert category.name == "Energy saving" + assert category.icon == "mdi:leaf" + + assert len(category_registry.categories) == 1 + assert len(category_registry.categories["automation"]) == 1 + + await hass.async_block_till_done() + + assert len(update_events) == 1 + assert update_events[0].data == { + "action": "create", + "scope": "automation", + "category_id": category.category_id, + } + + +async def test_create_category_with_name_already_in_use( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make sure that we can't create a category with the same name within a scope.""" + update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED) + category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + + with pytest.raises( + ValueError, + match=re.escape("The name 'ENERGY SAVING' is already in use"), + ): + category_registry.async_create( + scope="automation", + name="ENERGY SAVING", + icon="mdi:leaf", + ) + + await hass.async_block_till_done() + + assert len(category_registry.categories["automation"]) == 1 + assert len(update_events) == 1 + + +async def test_create_category_with_duplicate_name_in_other_scopes( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make we can create the same category in multiple scopes.""" + update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED) + category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + category_registry.async_create( + scope="script", + name="Energy saving", + icon="mdi:leaf", + ) + + await hass.async_block_till_done() + + assert len(category_registry.categories["script"]) == 1 + assert len(category_registry.categories["automation"]) == 1 + assert len(update_events) == 2 + + +async def test_delete_category( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make sure that we can delete a category.""" + update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED) + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + + assert len(category_registry.categories["automation"]) == 1 + + category_registry.async_delete(scope="automation", category_id=category.category_id) + + assert not category_registry.categories["automation"] + + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0].data == { + "action": "create", + "scope": "automation", + "category_id": category.category_id, + } + assert update_events[1].data == { + "action": "remove", + "scope": "automation", + "category_id": category.category_id, + } + + +async def test_delete_non_existing_category( + category_registry: cr.CategoryRegistry, +) -> None: + """Make sure that we can't delete a category that doesn't exist.""" + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + + with pytest.raises(KeyError): + category_registry.async_delete(scope="automation", category_id="") + + with pytest.raises(KeyError): + category_registry.async_delete(scope="", category_id=category.category_id) + + assert len(category_registry.categories["automation"]) == 1 + + +async def test_update_category( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make sure that we can update categories.""" + update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED) + category = category_registry.async_create( + scope="automation", + name="Energy saving", + ) + + assert len(category_registry.categories["automation"]) == 1 + assert category.category_id + assert category.name == "Energy saving" + assert category.icon is None + + updated_category = category_registry.async_update( + scope="automation", + category_id=category.category_id, + name="ENERGY SAVING", + icon="mdi:leaf", + ) + + assert updated_category != category + assert updated_category.category_id == category.category_id + assert updated_category.name == "ENERGY SAVING" + assert updated_category.icon == "mdi:leaf" + + assert len(category_registry.categories["automation"]) == 1 + + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0].data == { + "action": "create", + "scope": "automation", + "category_id": category.category_id, + } + assert update_events[1].data == { + "action": "update", + "scope": "automation", + "category_id": category.category_id, + } + + +async def test_update_category_with_same_data( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make sure that we can reapply the same data to a category and it won't update.""" + update_events = async_capture_events(hass, cr.EVENT_CATEGORY_REGISTRY_UPDATED) + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + + updated_category = category_registry.async_update( + scope="automation", + category_id=category.category_id, + name="Energy saving", + icon="mdi:leaf", + ) + assert category == updated_category + + await hass.async_block_till_done() + + # No update event + assert len(update_events) == 1 + assert update_events[0].data == { + "action": "create", + "scope": "automation", + "category_id": category.category_id, + } + + +async def test_update_category_with_same_name_change_case( + category_registry: cr.CategoryRegistry, +) -> None: + """Make sure that we can reapply the same name with a different case to a category.""" + category = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + + updated_category = category_registry.async_update( + scope="automation", + category_id=category.category_id, + name="ENERGY SAVING", + ) + + assert updated_category.category_id == category.category_id + assert updated_category.name == "ENERGY SAVING" + assert updated_category.icon == "mdi:leaf" + assert len(category_registry.categories["automation"]) == 1 + + +async def test_update_category_with_name_already_in_use( + category_registry: cr.CategoryRegistry, +) -> None: + """Make sure that we can't update a category with a name already in use.""" + category1 = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + category2 = category_registry.async_create( + scope="automation", + name="Something else", + icon="mdi:leaf", + ) + + with pytest.raises( + ValueError, + match=re.escape("The name 'ENERGY SAVING' is already in use"), + ): + category_registry.async_update( + scope="automation", + category_id=category2.category_id, + name="ENERGY SAVING", + ) + + assert category1.name == "Energy saving" + assert category2.name == "Something else" + assert len(category_registry.categories["automation"]) == 2 + + +async def test_load_categories( + hass: HomeAssistant, category_registry: cr.CategoryRegistry +) -> None: + """Make sure that we can load/save data correctly.""" + category1 = category_registry.async_create( + scope="automation", + name="Energy saving", + icon="mdi:leaf", + ) + category2 = category_registry.async_create( + scope="automation", + name="Something else", + icon="mdi:leaf", + ) + category3 = category_registry.async_create( + scope="zone", + name="Grocery stores", + icon="mdi:store", + ) + + assert len(category_registry.categories) == 2 + assert len(category_registry.categories["automation"]) == 2 + assert len(category_registry.categories["zone"]) == 1 + + registry2 = cr.CategoryRegistry(hass) + await flush_store(category_registry._store) + await registry2.async_load() + + assert len(registry2.categories) == 2 + assert len(registry2.categories["automation"]) == 2 + assert len(registry2.categories["zone"]) == 1 + assert list(category_registry.categories) == list(registry2.categories) + assert list(category_registry.categories["automation"]) == list( + registry2.categories["automation"] + ) + assert list(category_registry.categories["zone"]) == list( + registry2.categories["zone"] + ) + + category1_registry2 = registry2.async_get_category( + scope="automation", category_id=category1.category_id + ) + assert category1_registry2.category_id == category1.category_id + assert category1_registry2.name == category1.name + assert category1_registry2.icon == category1.icon + + category2_registry2 = registry2.async_get_category( + scope="automation", category_id=category2.category_id + ) + assert category2_registry2.category_id == category2.category_id + assert category2_registry2.name == category2.name + assert category2_registry2.icon == category2.icon + + category3_registry2 = registry2.async_get_category( + scope="zone", category_id=category3.category_id + ) + assert category3_registry2.category_id == category3.category_id + assert category3_registry2.name == category3.name + assert category3_registry2.icon == category3.icon + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_loading_categories_from_storage( + hass: HomeAssistant, hass_storage: Any +) -> None: + """Test loading stored categories on start.""" + hass_storage[cr.STORAGE_KEY] = { + "version": cr.STORAGE_VERSION_MAJOR, + "data": { + "categories": { + "automation": [ + { + "category_id": "uuid1", + "name": "Energy saving", + "icon": "mdi:leaf", + }, + { + "category_id": "uuid2", + "name": "Something else", + "icon": None, + }, + ], + "zone": [ + { + "category_id": "uuid3", + "name": "Grocery stores", + "icon": "mdi:store", + }, + ], + } + }, + } + + await cr.async_load(hass) + category_registry = cr.async_get(hass) + + assert len(category_registry.categories) == 2 + assert len(category_registry.categories["automation"]) == 2 + assert len(category_registry.categories["zone"]) == 1 + + category1 = category_registry.async_get_category( + scope="automation", category_id="uuid1" + ) + assert category1.category_id == "uuid1" + assert category1.name == "Energy saving" + assert category1.icon == "mdi:leaf" + + category2 = category_registry.async_get_category( + scope="automation", category_id="uuid2" + ) + assert category2.category_id == "uuid2" + assert category2.name == "Something else" + assert category2.icon is None + + category3 = category_registry.async_get_category(scope="zone", category_id="uuid3") + assert category3.category_id == "uuid3" + assert category3.name == "Grocery stores" + assert category3.icon == "mdi:store" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 15703af8103..b029933ebbe 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -280,7 +280,9 @@ async def test_loading_saving_data( orig_entry2.entity_id, "light", {"minimum_brightness": 20} ) entity_registry.async_update_entity( - orig_entry2.entity_id, labels={"label1", "label2"} + orig_entry2.entity_id, + categories={"scope", "id"}, + labels={"label1", "label2"}, ) orig_entry2 = entity_registry.async_get(orig_entry2.entity_id) orig_entry3 = entity_registry.async_get_or_create("light", "hue", "ABCD") @@ -310,6 +312,7 @@ async def test_loading_saving_data( assert orig_entry4 == new_entry4 assert new_entry2.area_id == "mock-area-id" + assert new_entry2.categories == {"scope", "id"} assert new_entry2.capabilities == {"max": 100} assert new_entry2.config_entry_id == mock_config.entry_id assert new_entry2.device_class == "user-class" @@ -1847,3 +1850,76 @@ async def test_entries_for_label(entity_registry: er.EntityRegistry) -> None: assert not er.async_entries_for_label(entity_registry, "unknown") assert not er.async_entries_for_label(entity_registry, "") + + +async def test_removing_categories(entity_registry: er.EntityRegistry) -> None: + """Make sure we can clear categories.""" + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="5678", + ) + entry = entity_registry.async_update_entity( + entry.entity_id, categories={"scope1": "id", "scope2": "id"} + ) + + entity_registry.async_clear_category_id("scope1", "id") + entry_cleared_scope1 = entity_registry.async_get(entry.entity_id) + + entity_registry.async_clear_category_id("scope2", "id") + entry_cleared_scope2 = entity_registry.async_get(entry.entity_id) + + assert entry_cleared_scope1 + assert entry_cleared_scope2 + assert entry != entry_cleared_scope1 + assert entry != entry_cleared_scope2 + assert entry_cleared_scope1 != entry_cleared_scope2 + assert entry.categories == {"scope1": "id", "scope2": "id"} + assert entry_cleared_scope1.categories == {"scope2": "id"} + assert not entry_cleared_scope2.categories + + +async def test_entries_for_category(entity_registry: er.EntityRegistry) -> None: + """Test getting entity entries by category.""" + entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="000", + ) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="123", + ) + category_1 = entity_registry.async_update_entity( + entry.entity_id, categories={"scope1": "id"} + ) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="456", + ) + category_2 = entity_registry.async_update_entity( + entry.entity_id, categories={"scope2": "id"} + ) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="789", + ) + category_1_and_2 = entity_registry.async_update_entity( + entry.entity_id, categories={"scope1": "id", "scope2": "id"} + ) + + entries = er.async_entries_for_category(entity_registry, "scope1", "id") + assert len(entries) == 2 + assert entries == [category_1, category_1_and_2] + + entries = er.async_entries_for_category(entity_registry, "scope2", "id") + assert len(entries) == 2 + assert entries == [category_2, category_1_and_2] + + assert not er.async_entries_for_category(entity_registry, "unknown", "id") + assert not er.async_entries_for_category(entity_registry, "", "id") + assert not er.async_entries_for_category(entity_registry, "scope1", "unknown") + assert not er.async_entries_for_category(entity_registry, "scope1", "") diff --git a/tests/syrupy.py b/tests/syrupy.py index 348a618e90e..d489724a940 100644 --- a/tests/syrupy.py +++ b/tests/syrupy.py @@ -166,7 +166,7 @@ class HomeAssistantSnapshotSerializer(AmberDataSerializer): cls, data: er.RegistryEntry ) -> SerializableData: """Prepare a Home Assistant entity registry entry for serialization.""" - return EntityRegistryEntrySnapshot( + serialized = EntityRegistryEntrySnapshot( attrs.asdict(data) | { "config_entry_id": ANY, @@ -175,6 +175,8 @@ class HomeAssistantSnapshotSerializer(AmberDataSerializer): "options": {k: dict(v) for k, v in data.options.items()}, } ) + serialized.pop("categories") + return serialized @classmethod def _serializable_flow_result(cls, data: FlowResult) -> SerializableData: From 4181c62ec0d40dafd9498f32856a81366f281ca4 Mon Sep 17 00:00:00 2001 From: On Freund Date: Fri, 15 Mar 2024 14:38:39 +0200 Subject: [PATCH 1098/1691] Bump pyrisco to 0.5.10 (#113505) --- homeassistant/components/risco/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/risco/manifest.json b/homeassistant/components/risco/manifest.json index ca28af3d8e5..b5d8c4442fd 100644 --- a/homeassistant/components/risco/manifest.json +++ b/homeassistant/components/risco/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_push", "loggers": ["pyrisco"], "quality_scale": "platinum", - "requirements": ["pyrisco==0.5.8"] + "requirements": ["pyrisco==0.5.10"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6ce453c11b4..60f95eb9dc3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2080,7 +2080,7 @@ pyrecswitch==1.0.2 pyrepetierng==0.1.0 # homeassistant.components.risco -pyrisco==0.5.8 +pyrisco==0.5.10 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8832c9b8d88..89540c8e37f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1613,7 +1613,7 @@ pyqwikswitch==0.93 pyrainbird==4.0.2 # homeassistant.components.risco -pyrisco==0.5.8 +pyrisco==0.5.10 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 From 1c938f642285c8fc9cdb43f5d829a63eddd2e961 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 15 Mar 2024 13:44:29 +0100 Subject: [PATCH 1099/1691] create issues for modbus config errors (#113431) --- homeassistant/components/modbus/strings.json | 16 ++++ homeassistant/components/modbus/validators.py | 87 +++++++++++++++---- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/modbus/strings.json b/homeassistant/components/modbus/strings.json index 4f7bb1c60e3..d4f649ac1e6 100644 --- a/homeassistant/components/modbus/strings.json +++ b/homeassistant/components/modbus/strings.json @@ -77,6 +77,22 @@ "deprecated_retries": { "title": "`{config_key}` configuration key is being removed", "description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3." + }, + "missing_modbus_name": { + "title": "Modbus entry with host `{sub_2}` missing name", + "description": "Please add `{sub_1}` key to the {integration} entry with host `{sub_2}` in your configuration.yaml file and restart Home Assistant to fix this issue\n\n. `{sub_1}: {sub_3}` have been added." + }, + "duplicate_modbus_entry": { + "title": "Modbus {sub_2} host/port {sub_1} is duplicate, second entry not loaded.", + "description": "Please update {sub_2} and/or {sub_1} for the entry in your configuration.yaml file and restart Home Assistant to fix this issue." + }, + "duplicate_entity_entry": { + "title": "Modbus {sub_1} address {sub_2} is duplicate, second entry not loaded.", + "description": "An address can only be associated with on entity, Please correct the entry in your configuration.yaml file and restart Home Assistant to fix this issue." + }, + "duplicate_entity_name": { + "title": "Modbus {sub_1} is duplicate, second entry not loaded.", + "description": "A entity name must be unique, Please correct the entry in your configuration.yaml file and restart Home Assistant to fix this issue." } } } diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 51c8a08079b..7ca64a03eed 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -25,6 +25,7 @@ from homeassistant.const import ( CONF_TYPE, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import ( CONF_DATA_TYPE, @@ -44,6 +45,7 @@ from .const import ( CONF_WRITE_TYPE, DEFAULT_HUB, DEFAULT_SCAN_INTERVAL, + MODBUS_DOMAIN as DOMAIN, PLATFORMS, SERIAL, DataType, @@ -112,6 +114,29 @@ DEFAULT_STRUCT_FORMAT = { } +def modbus_create_issue( + hass: HomeAssistant, key: str, subs: list[str], err: str +) -> None: + """Create issue modbus style.""" + async_create_issue( + hass, + DOMAIN, + key, + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key=key, + translation_placeholders={ + "sub_1": subs[0], + "sub_2": subs[1], + "sub_3": subs[2], + "integration": DOMAIN, + }, + issue_domain=DOMAIN, + learn_more_url="https://www.home-assistant.io/integrations/modbus", + ) + _LOGGER.warning(err) + + def struct_validator(config: dict[str, Any]) -> dict[str, Any]: """Sensor schema validator.""" @@ -289,12 +314,28 @@ def validate_modbus( DEFAULT_HUB if not hub_name_inx else f"{DEFAULT_HUB}_{hub_name_inx}" ) hub_name_inx += 1 - err = f"Modbus host/port {host} is missing name, added {hub[CONF_NAME]}!" - _LOGGER.warning(err) + modbus_create_issue( + hass, + "missing_modbus_name", + [ + "name", + host, + hub[CONF_NAME], + ], + f"Modbus host/port {host} is missing name, added {hub[CONF_NAME]}!", + ) name = hub[CONF_NAME] if host in hosts or name in hub_names: - err = f"Modbus {name} host/port {host} is duplicate, not loaded!" - _LOGGER.warning(err) + modbus_create_issue( + hass, + "duplicate_modbus_entry", + [ + host, + hub[CONF_NAME], + "", + ], + f"Modbus {name} host/port {host} is duplicate, not loaded!", + ) return False hosts.add(host) hub_names.add(name) @@ -315,15 +356,11 @@ def validate_entity( addr = f"{hub_name}{entity[CONF_ADDRESS]}" scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) if 0 < scan_interval < 5: - _LOGGER.warning( - ( - "%s %s scan_interval(%d) is lower than 5 seconds, " - "which may cause Home Assistant stability issues" - ), - hub_name, - name, - scan_interval, + err = ( + f"{hub_name} {name} scan_interval is lower than 5 seconds, " + "which may cause Home Assistant stability issues" ) + _LOGGER.warning(err) entity[CONF_SCAN_INTERVAL] = scan_interval minimum_scan_interval = min(scan_interval, minimum_scan_interval) for conf_type in ( @@ -337,7 +374,6 @@ def validate_entity( inx = entity.get(CONF_SLAVE) or entity.get(CONF_DEVICE_ADDRESS, 0) addr += f"_{inx}" loc_addr: set[str] = {addr} - if CONF_TARGET_TEMP in entity: loc_addr.add(f"{hub_name}{entity[CONF_TARGET_TEMP]}_{inx}") if CONF_HVAC_MODE_REGISTER in entity: @@ -348,15 +384,28 @@ def validate_entity( dup_addrs = ent_addr.intersection(loc_addr) if len(dup_addrs) > 0: for addr in dup_addrs: - err = ( - f"Modbus {hub_name}/{name} address {addr} is duplicate, second" - " entry not loaded!" + modbus_create_issue( + hass, + "duplicate_entity_entry", + [ + f"{hub_name}/{name}", + addr, + "", + ], + f"Modbus {hub_name}/{name} address {addr} is duplicate, second entry not loaded!", ) - _LOGGER.warning(err) return False if name in ent_names: - err = f"Modbus {hub_name}/{name} is duplicate, second entry not loaded!" - _LOGGER.warning(err) + modbus_create_issue( + hass, + "duplicate_entity_name", + [ + f"{hub_name}/{name}", + "", + "", + ], + f"Modbus {hub_name}/{name} is duplicate, second entry not loaded!", + ) return False ent_names.add(name) ent_addr.update(loc_addr) From c69ab425c5edb4f7cd56404a6a6e96d4b7320e14 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 03:05:42 -1000 Subject: [PATCH 1100/1691] Speed up ffmpeg setup (#113496) * Speed up ffmpeg setup Only check for old versions of ffmpeg if is_official_image is False * Speed up ffmpeg setup Only check for old versions of ffmpeg if is_official_image is False * Speed up ffmpeg setup Only check for old versions of ffmpeg if is_official_image is False * Speed up ffmpeg setup Only check for old versions of ffmpeg if is_official_image is False * Speed up ffmpeg setup Only check for old versions of ffmpeg if is_official_image is False * Speed up ffmpeg setup Only check for old versions of ffmpeg if is_official_image is False * adjust * adjust * twea * Update homeassistant/components/ffmpeg/__init__.py * forgot about the mock in conftest for comps --- homeassistant/components/ffmpeg/__init__.py | 39 ++++++++++++++------- tests/components/ffmpeg/test_init.py | 38 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index f16aed7b607..ec19df5fb6f 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio import re -from typing import Generic, TypeVar +from typing import TYPE_CHECKING, Generic, TypeVar from haffmpeg.core import HAFFmpeg from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame @@ -24,9 +24,16 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.entity import Entity +from homeassistant.helpers.system_info import is_official_image from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +if TYPE_CHECKING: + from functools import cached_property +else: + from homeassistant.backports.functools import cached_property + + _HAFFmpegT = TypeVar("_HAFFmpegT", bound=HAFFmpeg) DOMAIN = "ffmpeg" @@ -49,6 +56,12 @@ CONF_OUTPUT = "output" DEFAULT_BINARY = "ffmpeg" +# Currently we only care if the version is < 3 +# because we use a different content-type +# It is only important to update this version if the +# content-type changes again in the future +OFFICIAL_IMAGE_VERSION = "6.0" + CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( @@ -142,26 +155,28 @@ class FFmpegManager: self._version: str | None = None self._major_version: int | None = None - @property + @cached_property def binary(self) -> str: """Return ffmpeg binary from config.""" return self._bin async def async_get_version(self) -> tuple[str | None, int | None]: """Return ffmpeg version.""" - - ffversion = FFVersion(self._bin) - self._version = await ffversion.get_version() - - self._major_version = None - if self._version is not None: - result = re.search(r"(\d+)\.", self._version) - if result is not None: - self._major_version = int(result.group(1)) + if self._version is None: + if is_official_image(): + self._version = OFFICIAL_IMAGE_VERSION + self._major_version = int(self._version.split(".")[0]) + elif ( + (version := await FFVersion(self._bin).get_version()) + and (result := re.search(r"(\d+)\.", version)) + and (major_version := int(result.group(1))) + ): + self._version = version + self._major_version = major_version return self._version, self._major_version - @property + @cached_property def ffmpeg_stream_content_type(self) -> str: """Return HTTP content type for ffmpeg stream.""" if self._major_version is not None and self._major_version > 3: diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 23d00d7e46b..2d79e593d2c 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -8,6 +8,7 @@ from homeassistant.components.ffmpeg import ( SERVICE_RESTART, SERVICE_START, SERVICE_STOP, + get_ffmpeg_manager, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -251,3 +252,40 @@ async def test_async_get_image_with_extra_cmd_width_height(hass: HomeAssistant) assert get_image_mock.call_args_list == [ call("rtsp://fake", output_format="mjpeg", extra_cmd="-vf any -s 640x480") ] + + +async def test_modern_ffmpeg( + hass: HomeAssistant, +) -> None: + """Test modern ffmpeg uses the new ffmpeg content type.""" + with assert_setup_component(1): + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + + manager = get_ffmpeg_manager(hass) + assert "ffmpeg" in manager.ffmpeg_stream_content_type + + +async def test_legacy_ffmpeg( + hass: HomeAssistant, +) -> None: + """Test legacy ffmpeg uses the old ffserver content type.""" + with assert_setup_component(1), patch( + "homeassistant.components.ffmpeg.FFVersion.get_version", return_value="3.0" + ), patch("homeassistant.components.ffmpeg.is_official_image", return_value=False): + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + + manager = get_ffmpeg_manager(hass) + assert "ffserver" in manager.ffmpeg_stream_content_type + + +async def test_ffmpeg_using_official_image( + hass: HomeAssistant, +) -> None: + """Test ffmpeg using official image is the new ffmpeg content type.""" + with assert_setup_component(1), patch( + "homeassistant.components.ffmpeg.is_official_image", return_value=True + ): + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + + manager = get_ffmpeg_manager(hass) + assert "ffmpeg" in manager.ffmpeg_stream_content_type From eb1f37ea9b8b213361fbb294fb8c819adda67fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Strandberg?= Date: Fri, 15 Mar 2024 14:06:10 +0100 Subject: [PATCH 1101/1691] Filter out irrelevant entities on SMO20 devices in myuplink (#113493) --- homeassistant/components/myuplink/helpers.py | 16 + homeassistant/components/myuplink/number.py | 4 +- homeassistant/components/myuplink/sensor.py | 4 +- homeassistant/components/myuplink/switch.py | 4 +- .../fixtures/device_points_nibe_smo20.json | 13470 ++++++++++++++++ tests/components/myuplink/test_number.py | 19 + tests/components/myuplink/test_switch.py | 19 + 7 files changed, 13533 insertions(+), 3 deletions(-) create mode 100644 tests/components/myuplink/fixtures/device_points_nibe_smo20.json diff --git a/homeassistant/components/myuplink/helpers.py b/homeassistant/components/myuplink/helpers.py index 8b16dacfd34..abe039605d3 100644 --- a/homeassistant/components/myuplink/helpers.py +++ b/homeassistant/components/myuplink/helpers.py @@ -31,3 +31,19 @@ def find_matching_platform( return Platform.SENSOR return Platform.SENSOR + + +def skip_entity(model: str, device_point: DevicePoint) -> bool: + """Check if entity should be skipped for this device model.""" + if model == "SMO 20": + if len(device_point.smart_home_categories) > 0 or device_point.parameter_id in ( + "40940", + "47011", + "47015", + "47028", + "47032", + "50004", + ): + return False + return True + return False diff --git a/homeassistant/components/myuplink/number.py b/homeassistant/components/myuplink/number.py index 4e947a440dd..89d6658d368 100644 --- a/homeassistant/components/myuplink/number.py +++ b/homeassistant/components/myuplink/number.py @@ -13,7 +13,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import MyUplinkDataCoordinator from .const import DOMAIN from .entity import MyUplinkEntity -from .helpers import find_matching_platform +from .helpers import find_matching_platform, skip_entity DEVICE_POINT_UNIT_DESCRIPTIONS: dict[str, NumberEntityDescription] = { "DM": NumberEntityDescription( @@ -65,6 +65,8 @@ async def async_setup_entry( # Setup device point number entities for device_id, point_data in coordinator.data.points.items(): for point_id, device_point in point_data.items(): + if skip_entity(device_point.category, device_point): + continue description = get_description(device_point) if find_matching_platform(device_point, description) == Platform.NUMBER: entities.append( diff --git a/homeassistant/components/myuplink/sensor.py b/homeassistant/components/myuplink/sensor.py index a7afa8f28d8..6cde6b6b071 100644 --- a/homeassistant/components/myuplink/sensor.py +++ b/homeassistant/components/myuplink/sensor.py @@ -27,7 +27,7 @@ from homeassistant.helpers.typing import StateType from . import MyUplinkDataCoordinator from .const import DOMAIN from .entity import MyUplinkEntity -from .helpers import find_matching_platform +from .helpers import find_matching_platform, skip_entity DEVICE_POINT_UNIT_DESCRIPTIONS: dict[str, SensorEntityDescription] = { "°C": SensorEntityDescription( @@ -155,6 +155,8 @@ async def async_setup_entry( # Setup device point sensors for device_id, point_data in coordinator.data.points.items(): for point_id, device_point in point_data.items(): + if skip_entity(device_point.category, device_point): + continue if find_matching_platform(device_point) == Platform.SENSOR: description = get_description(device_point) entity_class = MyUplinkDevicePointSensor diff --git a/homeassistant/components/myuplink/switch.py b/homeassistant/components/myuplink/switch.py index 5f6bee2262e..d26695f4cbe 100644 --- a/homeassistant/components/myuplink/switch.py +++ b/homeassistant/components/myuplink/switch.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import MyUplinkDataCoordinator from .const import DOMAIN from .entity import MyUplinkEntity -from .helpers import find_matching_platform +from .helpers import find_matching_platform, skip_entity CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, SwitchEntityDescription]] = { "NIBEF": { @@ -58,6 +58,8 @@ async def async_setup_entry( # Setup device point switches for device_id, point_data in coordinator.data.points.items(): for point_id, device_point in point_data.items(): + if skip_entity(device_point.category, device_point): + continue if find_matching_platform(device_point) == Platform.SWITCH: description = get_description(device_point) diff --git a/tests/components/myuplink/fixtures/device_points_nibe_smo20.json b/tests/components/myuplink/fixtures/device_points_nibe_smo20.json new file mode 100644 index 00000000000..b64869c236c --- /dev/null +++ b/tests/components/myuplink/fixtures/device_points_nibe_smo20.json @@ -0,0 +1,13470 @@ +[ + { + "category": "SMO 20", + "parameterId": "30200", + "parameterName": "heat pump", + "parameterUnit": "(EB101)", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "1(EB101)", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40004", + "parameterName": "Current outd temp (BT1)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:35:17+00:00", + "value": 11.7, + "strVal": "11.7°C", + "smartHomeCategories": ["sh-outdoorTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40014", + "parameterName": "Hot water char­ging (BT6)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:39:12+00:00", + "value": 55.5, + "strVal": "55.5°C", + "smartHomeCategories": ["sh-hwTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40047", + "parameterName": "Supply line (BT61)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": -32768, + "strVal": "-32768°C", + "smartHomeCategories": ["sh-supplyTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40048", + "parameterName": "Return line (BT62)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": -32768, + "strVal": "-32768°C", + "smartHomeCategories": ["sh-returnTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40067", + "parameterName": "Average outdoor temp (BT1)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:27:45+00:00", + "value": 9.7, + "strVal": "9.7°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40071", + "parameterName": "Exter­nal supply line (BT25)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:39:06+00:00", + "value": 31.3, + "strVal": "31.3°C", + "smartHomeCategories": ["sh-supplyTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40145", + "parameterName": "Oil temp­erature (EP15-BT29)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40146", + "parameterName": "Oil temp­erature (BT29)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40152", + "parameterName": "Exter­nal return line (BT71)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:40:02+00:00", + "value": 30, + "strVal": "30°C", + "smartHomeCategories": ["sh-returnTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40755", + "parameterName": "time factor:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-08T14:17:41+00:00", + "value": 6, + "strVal": "6hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "40782", + "parameterName": "Re­quested com­pressor freq (EB101)", + "parameterUnit": "Hz", + "writable": false, + "timestamp": "2024-03-15T09:31:33+00:00", + "value": 40, + "strVal": "40Hz", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458, + "strVal": "-458", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "current value", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5DM", + "smartHomeCategories": [], + "minValue": -30000, + "maxValue": 30000, + "stepValue": 100, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "40940", + "parameterName": "Degree minutes", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:30:01+00:00", + "value": -458.5, + "strVal": "-458.5", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41002", + "parameterName": "Fan speed (EB101)", + "parameterUnit": "rpm", + "writable": false, + "timestamp": "2024-03-15T09:32:17+00:00", + "value": 319, + "strVal": "319rpm", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41011", + "parameterName": "EEV-ssh-act (EB101)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:37:46+00:00", + "value": 10.2, + "strVal": "10.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41017", + "parameterName": "EEV posi­tion (EB101)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:34:19+00:00", + "value": 96, + "strVal": "96", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41019", + "parameterName": "EVI-ssh-act (EB101)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41025", + "parameterName": "EVI posi­tion (EB101)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41162", + "parameterName": "Low pres­sure (EB101-EP14-BP8)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:35:13+00:00", + "value": 4.2, + "strVal": "4.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41163", + "parameterName": "High pres­sure (EB101-EP14-BP9)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:38:36+00:00", + "value": 64.2, + "strVal": "64.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41164", + "parameterName": "Injec­tion (EB101-BT81)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": -32768, + "strVal": "-32768°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "41167", + "parameterName": "Evap­orator (EB101-BT84)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:38:43+00:00", + "value": 8.5, + "strVal": "8.5°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "41929", + "parameterName": "Mode (Smart Price Adaption)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:00:01+00:00", + "value": 1, + "strVal": "Low", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Unknown", + "icon": "" + }, + { + "value": "1", + "text": "Low", + "icon": "" + }, + { + "value": "2", + "text": "Normal", + "icon": "" + }, + { + "value": "3", + "text": "High", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43009", + "parameterName": "Calcu­lated supply climate system 1", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:35:44+00:00", + "value": 42.1, + "strVal": "42.1°C", + "smartHomeCategories": ["sh-supplyTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 36.9, + "strVal": "36.9", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43081", + "parameterName": "Time factor add heat", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:37:40+00:00", + "value": 37, + "strVal": "37", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43109", + "parameterName": "Current hot water mode", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:29:25+00:00", + "value": 2, + "strVal": "2", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "43161", + "parameterName": "Extern. adjust­ment climate system 1", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "Off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Off", + "icon": "" + }, + { + "value": "1", + "text": "On", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44014", + "parameterName": "Version (EB101)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 10880, + "strVal": "10880", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44032", + "parameterName": "Slave (EB101)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 17, + "strVal": "17", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44055", + "parameterName": "Return line (EB101-BT3)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:39:52+00:00", + "value": 56.7, + "strVal": "56.7°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44058", + "parameterName": "Supply line (EB101-BT12)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:40:33+00:00", + "value": 64.8, + "strVal": "64.8°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44059", + "parameterName": "Dis­charge (EB101-BT14)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:40:31+00:00", + "value": 81.5, + "strVal": "81.5°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44060", + "parameterName": "Liquid line (EB101-BT15)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:39:31+00:00", + "value": 56.2, + "strVal": "56.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44061", + "parameterName": "Suction gas (EB101-BT17)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:40:48+00:00", + "value": 15.2, + "strVal": "15.2°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44064", + "parameterName": "Status com­pressor (EB101)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 60, + "strVal": "Operating", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "20", + "text": "Off", + "icon": "" + }, + { + "value": "40", + "text": "Starting", + "icon": "" + }, + { + "value": "60", + "text": "Operating", + "icon": "" + }, + { + "value": "100", + "text": "Stopping", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44069", + "parameterName": "No. of starts (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44069", + "parameterName": "number of starts:", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:20:02+00:00", + "value": 1859, + "strVal": "1859", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44071", + "parameterName": "Oper. time (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44071", + "parameterName": "total operating time:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T07:55:32+00:00", + "value": 2694, + "strVal": "2694hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44073", + "parameterName": "Oper. time hot water (EB101-EP14)", + "parameterUnit": "h", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114h", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44073", + "parameterName": "- of which hot water:", + "parameterUnit": "hrs", + "writable": false, + "timestamp": "2024-03-15T03:30:27+00:00", + "value": 114, + "strVal": "114hrs", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44362", + "parameterName": "Outd temp­erature (EB101-BT28)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:19:30+00:00", + "value": 11.6, + "strVal": "11.6°C", + "smartHomeCategories": ["sh-outdoorTemp"], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44363", + "parameterName": "Evap­orator (EB101-BT16)", + "parameterUnit": "°C", + "writable": false, + "timestamp": "2024-03-15T09:39:44+00:00", + "value": 5, + "strVal": "5°C", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44396", + "parameterName": "Heating medium pump speed (GP1)", + "parameterUnit": "%", + "writable": false, + "timestamp": "2024-03-15T09:38:24+00:00", + "value": 11, + "strVal": "11%", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44700", + "parameterName": "Low pres­sure (EB101-BP8)", + "parameterUnit": "bar", + "writable": false, + "timestamp": "2024-03-15T09:23:55+00:00", + "value": 4, + "strVal": "4bar", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44701", + "parameterName": "Current com­pressor fre­quency (EB101)", + "parameterUnit": "Hz", + "writable": false, + "timestamp": "2024-03-15T09:32:20+00:00", + "value": 40, + "strVal": "40Hz", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44702", + "parameterName": "Prot. mode, com­pressor (EB101)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-12T02:32:25+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "44703", + "parameterName": "Defrost­ing (EB101-EP14)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T06:10:59+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44896", + "parameterName": "Heating offset (Smart Price Adaption)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:00:01+00:00", + "value": 0.2, + "strVal": "0.2", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44898", + "parameterName": "Pool offset (Smart Price Adaption)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44899", + "parameterName": "Cooling offset (Smart Price Adaption)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "44908", + "parameterName": "Status (Smart Price Adaption)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-02-14T08:39:28+00:00", + "value": 30, + "strVal": "30", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "45862", + "parameterName": "Continuous fan de-icing", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47007", + "parameterName": "heating curve", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 9, + "strVal": "9", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 15, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47011", + "parameterName": "Heating offset climate system 1", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:29:34+00:00", + "value": 5, + "strVal": "5", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47015", + "parameterName": "climate system", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 25, + "strVal": "25°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 700, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47020", + "parameterName": "flow line temp. at 30 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 15, + "strVal": "15°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47021", + "parameterName": "flow line temp. at 20 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 15, + "strVal": "15°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47022", + "parameterName": "flow line temp. at 10 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 26, + "strVal": "26°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47023", + "parameterName": "flow line temp. at 0 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 32, + "strVal": "32°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47024", + "parameterName": "flow line temp. at -10 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 35, + "strVal": "35°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47025", + "parameterName": "flow line temp. at -20 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 40, + "strVal": "40°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47026", + "parameterName": "flow line temp. at -30 °C", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 45, + "strVal": "45°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 80, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47027", + "parameterName": "outdoor temp. point", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": -40, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47028", + "parameterName": "change in curve", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-03-07T09:37:01+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47032", + "parameterName": "climate system", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47043", + "parameterName": "start temp. lux", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-03-09T16:50:12+00:00", + "value": 56, + "strVal": "56°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 700, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47044", + "parameterName": "start temp. normal", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 46, + "strVal": "46°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 600, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47045", + "parameterName": "start temp. economy", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 43, + "strVal": "43°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 550, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47046", + "parameterName": "stop temp. per. increase", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 55, + "strVal": "55°C", + "smartHomeCategories": [], + "minValue": 550, + "maxValue": 700, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47047", + "parameterName": "stop temp. lux", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-03-09T16:50:32+00:00", + "value": 59, + "strVal": "59°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 700, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47048", + "parameterName": "stop temp. normal", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 50, + "strVal": "50°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 650, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47049", + "parameterName": "stop temp. economy", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 47, + "strVal": "47°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 600, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47050", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47051", + "parameterName": "period", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 14, + "strVal": "14days", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 90, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47054", + "parameterName": "operating time", + "parameterUnit": "min", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 10, + "strVal": "10min", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 60, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47055", + "parameterName": "downtime", + "parameterUnit": "min", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 20, + "strVal": "20min", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 60, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47056", + "parameterName": "length of period 3", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47057", + "parameterName": "length of period 2", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47058", + "parameterName": "length of period 1", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 20700, + "strVal": "20700", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47059", + "parameterName": "length of period 3", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47060", + "parameterName": "length of period 2", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47061", + "parameterName": "length of period 1", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47137", + "parameterName": "op. mode", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "auto", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "auto", + "icon": "" + }, + { + "value": "1", + "text": "manual", + "icon": "" + }, + { + "value": "2", + "text": "add. heat only", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47206", + "parameterName": "start compressor", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": -60, + "strVal": "-60DM", + "smartHomeCategories": [], + "minValue": -1000, + "maxValue": -30, + "stepValue": 10, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47209", + "parameterName": "diff. between additional steps", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 100, + "strVal": "100DM", + "smartHomeCategories": [], + "minValue": 10, + "maxValue": 1000, + "stepValue": 10, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47214", + "parameterName": "fuse size", + "parameterUnit": "A", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 16, + "strVal": "16A", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 400, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47266", + "parameterName": "speed 4", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 90, + "strVal": "90%", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47267", + "parameterName": "speed 3", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 70, + "strVal": "70%", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47268", + "parameterName": "speed 2", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 25, + "strVal": "25%", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47269", + "parameterName": "speed 1", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0%", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47270", + "parameterName": "normal", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 60, + "strVal": "60%", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47275", + "parameterName": "months btwn filter alarms", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 3, + "strVal": "3", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 24, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47276", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47277", + "parameterName": "length of period 7", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 2, + "strVal": "2days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47278", + "parameterName": "length of period 6", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 2, + "strVal": "2days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47279", + "parameterName": "length of period 5", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 2, + "strVal": "2days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47280", + "parameterName": "length of period 4", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 3, + "strVal": "3days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47281", + "parameterName": "length of period 3", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 2, + "strVal": "2days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47282", + "parameterName": "length of period 2", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 2, + "strVal": "2days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47283", + "parameterName": "length of period 1", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 2, + "strVal": "2days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47284", + "parameterName": "temp. period 7", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 20, + "strVal": "20°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47285", + "parameterName": "temp. period 6", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 30, + "strVal": "30°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47286", + "parameterName": "temp. period 5", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 40, + "strVal": "40°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47287", + "parameterName": "temp. period 4", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 45, + "strVal": "45°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47288", + "parameterName": "temp. period 3", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 40, + "strVal": "40°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47289", + "parameterName": "temp. period 2", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 30, + "strVal": "30°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47290", + "parameterName": "temp. period 1", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 20, + "strVal": "20°C", + "smartHomeCategories": [], + "minValue": 15, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47347", + "parameterName": "max. tank temperature", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 95, + "strVal": "95°C", + "smartHomeCategories": [], + "minValue": 5, + "maxValue": 110, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47348", + "parameterName": "max. solar collector temp.", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 125, + "strVal": "125°C", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 200, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47375", + "parameterName": "stop heating", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 18, + "strVal": "18°C", + "smartHomeCategories": [], + "minValue": -200, + "maxValue": 400, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47376", + "parameterName": "stop additional heat", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 5, + "strVal": "5°C", + "smartHomeCategories": [], + "minValue": -250, + "maxValue": 400, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47377", + "parameterName": "filtering time", + "parameterUnit": "hrs", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 6, + "strVal": "6hrs", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 48, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47378", + "parameterName": "max diff compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 6, + "strVal": "6°C", + "smartHomeCategories": [], + "minValue": 10, + "maxValue": 250, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47379", + "parameterName": "max diff addition", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 3, + "strVal": "3°C", + "smartHomeCategories": [], + "minValue": 10, + "maxValue": 240, + "stepValue": 10, + "enumValues": [], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47388", + "parameterName": "decrease room temp", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47389", + "parameterName": "deactivate hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47394", + "parameterName": "control room sensor syst", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47413", + "parameterName": "hot water", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 70, + "strVal": "70%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47414", + "parameterName": "heating", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 70, + "strVal": "70%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47416", + "parameterName": "economy", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 30, + "strVal": "30%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47544", + "parameterName": "start delta-T", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 8, + "strVal": "8°C", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 40, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47545", + "parameterName": "stop delta-T", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 4, + "strVal": "4°C", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 40, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47550", + "parameterName": "solar panel cooling", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47613", + "parameterName": "max step", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 2, + "strVal": "2", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 7, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47614", + "parameterName": "binary stepping", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47635", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47637", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47638", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47639", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47640", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47641", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47642", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47643", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47644", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47645", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47646", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47647", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47648", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47649", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47650", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47651", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47652", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47653", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47654", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47655", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47656", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47657", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47658", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47659", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47660", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47669", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47671", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47672", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47673", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47674", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47675", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47676", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47677", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47678", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47679", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47680", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47681", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47682", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47683", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47684", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47685", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47686", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47687", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47688", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47689", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47690", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47691", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47692", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47693", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47694", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "economy", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "-1", + "text": "off", + "icon": "" + }, + { + "value": "0", + "text": "economy", + "icon": "" + }, + { + "value": "1", + "text": "normal", + "icon": "" + }, + { + "value": "2", + "text": "luxury", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47703", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47705", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47706", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47707", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47708", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47709", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47710", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47711", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47712", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47713", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47714", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47715", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47716", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47717", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47718", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47719", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47720", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47721", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47722", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47723", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47724", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47725", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47726", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47727", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47728", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47737", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47739", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47740", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47741", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47742", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47743", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47744", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47745", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47746", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47747", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47748", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47749", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47750", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47751", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47752", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47753", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47754", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47755", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47756", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47757", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47758", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:47+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47759", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47760", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47761", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 1, + "strVal": "speed 1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47762", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "speed 1", + "icon": "" + }, + { + "value": "2", + "text": "speed 2", + "icon": "" + }, + { + "value": "3", + "text": "speed 3", + "icon": "" + }, + { + "value": "4", + "text": "speed 4", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47771", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47772", + "parameterName": "system", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "1", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47773", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47774", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47775", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47776", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47777", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47778", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47779", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47780", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47781", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47782", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47783", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47784", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47785", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47786", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47787", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47788", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47789", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47790", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47791", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47792", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47793", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47794", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47795", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47796", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47805", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47806", + "parameterName": "system", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "1", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47807", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47808", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47809", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47810", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47811", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47812", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47813", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47814", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47815", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47816", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47817", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47818", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47819", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47820", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47821", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47822", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47823", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47824", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47825", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47826", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47827", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47828", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47829", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47830", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47839", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47840", + "parameterName": "system", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "1", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47841", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47842", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47843", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47844", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47845", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47846", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47847", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47848", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47849", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47850", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47851", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47852", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47853", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47854", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47855", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47856", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47857", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47858", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47859", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47860", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47861", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47862", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47863", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47864", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": -10, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47907", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47909", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47910", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47911", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47912", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47913", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47914", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47915", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47916", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47917", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47918", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47919", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47920", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47921", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47922", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47923", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47924", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47925", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47926", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47927", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47928", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47929", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47930", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47931", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47932", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47941", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47943", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47944", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47945", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47946", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47947", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47948", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47949", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47950", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47951", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47952", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47953", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47954", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47955", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47956", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47957", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47958", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47959", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47960", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47961", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47962", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47963", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47964", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47965", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47966", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "off", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47975", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47977", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47978", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47979", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47980", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47981", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47982", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47983", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47984", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47985", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47986", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47987", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47988", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47989", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47990", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47991", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47992", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47993", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47994", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47995", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47996", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47997", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47998", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "47999", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48000", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48009", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48011", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48012", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48013", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48014", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48015", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48016", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48017", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48018", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48019", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48020", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48021", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48022", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48023", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48024", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48025", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48026", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48027", + "parameterName": "sun", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48028", + "parameterName": "sat", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48029", + "parameterName": "fri", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48030", + "parameterName": "thur", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48031", + "parameterName": "wed", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48032", + "parameterName": "tues", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48033", + "parameterName": "mon", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 2, + "strVal": "Add. heat", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48034", + "parameterName": "all", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "1", + "text": "Com­pressor", + "icon": "" + }, + { + "value": "2", + "text": "Add. heat", + "icon": "" + }, + { + "value": "3", + "text": "Com­pressor and Addi­tional Heat", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48072", + "parameterName": "start diff additional heat", + "parameterUnit": "DM", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 400, + "strVal": "400DM", + "smartHomeCategories": [], + "minValue": 100, + "maxValue": 2000, + "stepValue": 10, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48132", + "parameterName": "temporary lux", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:29:25+00:00", + "value": 4, + "strVal": "one time increase", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + }, + { + "value": "1", + "text": "3 hrs", + "icon": "" + }, + { + "value": "2", + "text": "6 hrs", + "icon": "" + }, + { + "value": "3", + "text": "12 hrs", + "icon": "" + }, + { + "value": "4", + "text": "one time increase", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48200", + "parameterName": "freeze protection", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48228", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48229", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48230", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48231", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48232", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48233", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48234", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48235", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 0, + "strVal": "intermittent", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "intermittent", + "icon": "" + }, + { + "value": "1", + "text": "auto", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48279", + "parameterName": "positioning", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 1, + "strVal": "after QN10", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "before QN10", + "icon": "" + }, + { + "value": "1", + "text": "after QN10", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48280", + "parameterName": "add. heat in tank", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48281", + "parameterName": "charge method", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 1, + "strVal": "delta temp", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "target temp", + "icon": "" + }, + { + "value": "1", + "text": "delta temp", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48356", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48357", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48358", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48359", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48360", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48361", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48362", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48363", + "parameterName": "silent mode permitted", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48375", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48376", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48377", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48378", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48379", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48380", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48381", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48382", + "parameterName": "heating", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:01+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48391", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48392", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48393", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48394", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48395", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48396", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48397", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48398", + "parameterName": "hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48442", + "parameterName": "activated", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:38:42+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48443", + "parameterName": "affect room temperature", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:38:42+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48444", + "parameterName": "- degree of effect", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:38:42+00:00", + "value": 3, + "strVal": "3", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 10, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48445", + "parameterName": "affect hot water", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:38:42+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48460", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48461", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48462", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48463", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48464", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48465", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48466", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48467", + "parameterName": "max. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 80, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48566", + "parameterName": "- degree of effect", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:38:42+00:00", + "value": 2, + "strVal": "2", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 4, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48642", + "parameterName": "start delta-T", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 20, + "strVal": "20°C", + "smartHomeCategories": [], + "minValue": 2, + "maxValue": 40, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48643", + "parameterName": "stop delta-T", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 10, + "strVal": "10°C", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 38, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48644", + "parameterName": "max. tank temperature", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 85, + "strVal": "85°C", + "smartHomeCategories": [], + "minValue": 50, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48645", + "parameterName": "tank cooling", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48659", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48660", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48665", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48711", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48712", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48713", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48714", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48715", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48716", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48717", + "parameterName": "stop temp compressor", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": -25, + "strVal": "-25°C", + "smartHomeCategories": [], + "minValue": -25, + "maxValue": -2, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48758", + "parameterName": "Relay ctrl", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "Always on", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Temp", + "icon": "" + }, + { + "value": "0", + "text": "Watch", + "icon": "" + }, + { + "value": "0", + "text": "W+T", + "icon": "" + }, + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Ext. ctrl", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48759", + "parameterName": "Relay ctrl", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "Always on", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Temp", + "icon": "" + }, + { + "value": "0", + "text": "Watch", + "icon": "" + }, + { + "value": "0", + "text": "W+T", + "icon": "" + }, + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Ext. ctrl", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48760", + "parameterName": "Relay ctrl", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "Always on", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Temp", + "icon": "" + }, + { + "value": "0", + "text": "Watch", + "icon": "" + }, + { + "value": "0", + "text": "W+T", + "icon": "" + }, + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Ext. ctrl", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48761", + "parameterName": "Relay ctrl", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "Always on", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Temp", + "icon": "" + }, + { + "value": "0", + "text": "Watch", + "icon": "" + }, + { + "value": "0", + "text": "W+T", + "icon": "" + }, + { + "value": "0", + "text": "Always on", + "icon": "" + }, + { + "value": "0", + "text": "Ext. ctrl", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48762", + "parameterName": "Start time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48763", + "parameterName": "Start time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48764", + "parameterName": "Start time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48765", + "parameterName": "Start time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48766", + "parameterName": "Stop time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48767", + "parameterName": "Stop time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48768", + "parameterName": "Stop time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48769", + "parameterName": "Stop time (h)", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48770", + "parameterName": "Switch temp.", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:46+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": -40, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48771", + "parameterName": "Switch temp.", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": -40, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48772", + "parameterName": "Switch temp.", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": -40, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48773", + "parameterName": "Switch temp.", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0°C", + "smartHomeCategories": [], + "minValue": -40, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48774", + "parameterName": "Use input", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48775", + "parameterName": "Use input", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48776", + "parameterName": "Use input", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48777", + "parameterName": "Use input", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48826", + "parameterName": "limit RH in the room, syst.", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48891", + "parameterName": "adjusting ventilation", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 2147483647, + "maxValue": 0, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48941", + "parameterName": "defrost more often", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "48943", + "parameterName": "temp. passive / active", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2024-02-29T12:08:21+00:00", + "value": 4, + "strVal": "4°C", + "smartHomeCategories": [], + "minValue": -99, + "maxValue": 99, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49190", + "parameterName": "Alarm at min temp", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49230", + "parameterName": "activ. imm heat in heat mode", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49383", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49384", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49385", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49386", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49387", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49388", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49390", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49391", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49392", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49393", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49394", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49395", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49396", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49397", + "parameterName": "blockFreq", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49563", + "parameterName": "QM41 motion interval", + "parameterUnit": "days", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 14, + "strVal": "14days", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 30, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49564", + "parameterName": "fire alarm temperature BT20", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 38, + "strVal": "38°C", + "smartHomeCategories": [], + "minValue": 38, + "maxValue": 70, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49566", + "parameterName": "set point value BP12", + "parameterUnit": "Pa", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 150, + "strVal": "150Pa", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 400, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49567", + "parameterName": "min. fan speed GQ2", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 10, + "strVal": "10%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49568", + "parameterName": "max. fan speed GQ2", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 100, + "strVal": "100%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49569", + "parameterName": "filt. alarm press.", + "parameterUnit": "Pa", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 150, + "strVal": "150Pa", + "smartHomeCategories": [], + "minValue": 100, + "maxValue": 300, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49570", + "parameterName": "months btwn filter alarms", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 12, + "strVal": "12", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 24, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49571", + "parameterName": "runtime damper QM41", + "parameterUnit": "sec", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 150, + "strVal": "150sec", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 300, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49572", + "parameterName": "outd temp compensation", + "parameterUnit": "Pa", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0Pa", + "smartHomeCategories": [], + "minValue": -100, + "maxValue": 0, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49573", + "parameterName": "type of fire alarm", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 1, + "strVal": "1", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 3, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49574", + "parameterName": "fan speed during fire alarm", + "parameterUnit": "%", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 70, + "strVal": "70%", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 100, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49580", + "parameterName": "start manual motion QM41", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 2147483647, + "maxValue": 0, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49584", + "parameterName": "kc parameter", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 10, + "strVal": "10", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 32000, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49610", + "parameterName": "climate system", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 4, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49728", + "parameterName": "man. sel. power at DOT", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 1, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49740", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49741", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49742", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49743", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49744", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49745", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49746", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49747", + "parameterName": "start manual defrosting", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49756", + "parameterName": "fan de-icing", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:03+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 2147483647, + "maxValue": 0, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49797", + "parameterName": "temp. cond. heater EB14", + "parameterUnit": "°C", + "writable": true, + "timestamp": "2023-11-22T07:03:45+00:00", + "value": 4, + "strVal": "4°C", + "smartHomeCategories": [], + "minValue": -20, + "maxValue": 20, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49866", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49867", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49868", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49869", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49870", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49871", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49872", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 1, + "strVal": "1%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49873", + "parameterName": "min. allowed speed", + "parameterUnit": "%", + "writable": true, + "timestamp": "2024-02-14T08:36:02+00:00", + "value": 15, + "strVal": "15%", + "smartHomeCategories": [], + "minValue": 1, + "maxValue": 50, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49909", + "parameterName": "start time", + "parameterUnit": "", + "writable": true, + "timestamp": "2023-11-22T07:03:44+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 23, + "stepValue": 1, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49912", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49913", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49914", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49915", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49916", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49917", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49918", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49919", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49920", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49921", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49922", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49923", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49924", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49925", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49926", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:05+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49927", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49946", + "parameterName": "Start Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49947", + "parameterName": "Start Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49948", + "parameterName": "Start Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49949", + "parameterName": "Start Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49950", + "parameterName": "Start Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49951", + "parameterName": "Start Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49952", + "parameterName": "Start Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49953", + "parameterName": "Start all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49954", + "parameterName": "Finish Sunday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49955", + "parameterName": "Finish Saturday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49956", + "parameterName": "Finish Friday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49957", + "parameterName": "Finish Thursday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49958", + "parameterName": "Finish Wednesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49959", + "parameterName": "Finish Tuesday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49960", + "parameterName": "Finish Monday", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49961", + "parameterName": "End all days", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-02-14T08:36:06+00:00", + "value": 0, + "strVal": "0", + "smartHomeCategories": [], + "minValue": 0, + "maxValue": 86400, + "stepValue": 900, + "enumValues": [], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "49994", + "parameterName": "Prior­ity", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:29:26+00:00", + "value": 20, + "strVal": "Hot water", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "10", + "text": "Off", + "icon": "" + }, + { + "value": "20", + "text": "Hot water", + "icon": "" + }, + { + "value": "30", + "text": "Heating", + "icon": "" + }, + { + "value": "40", + "text": "Pool", + "icon": "" + }, + { + "value": "41", + "text": "Pool 2", + "icon": "" + }, + { + "value": "50", + "text": "Trans­fer", + "icon": "" + }, + { + "value": "60", + "text": "Cooling", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "Slave 1 (EB101)", + "parameterId": "49996", + "parameterName": "Charge pump (GP12)", + "parameterUnit": "", + "writable": false, + "timestamp": "2024-03-15T09:19:24+00:00", + "value": 1, + "strVal": "On", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "Off", + "icon": "" + }, + { + "value": "1", + "text": "On", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "50004", + "parameterName": "Tempo­rary lux", + "parameterUnit": "", + "writable": true, + "timestamp": "2024-03-15T09:29:25+00:00", + "value": 1, + "strVal": "on", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + }, + { + "value": "1", + "text": "on", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "50096", + "parameterName": "status:", + "parameterUnit": "min", + "writable": false, + "timestamp": "2024-03-15T09:34:48+00:00", + "value": 15004, + "strVal": "15004min", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "15000", + "text": "starts", + "icon": "" + }, + { + "value": "15001", + "text": "runs", + "icon": "" + }, + { + "value": "15003", + "text": "off", + "icon": "" + }, + { + "value": "15004", + "text": "hot water", + "icon": "" + }, + { + "value": "15005", + "text": "heating", + "icon": "" + }, + { + "value": "15006", + "text": "pool", + "icon": "" + }, + { + "value": "10480", + "text": "incomp. hp", + "icon": "" + }, + { + "value": "10065", + "text": "Comm.fault In", + "icon": "" + }, + { + "value": "10066", + "text": "Communication fault with PCA Input.", + "icon": "" + }, + { + "value": "10067", + "text": "Communication fault with PCA Input.", + "icon": "" + }, + { + "value": "10068", + "text": "Com.flt Base", + "icon": "" + }, + { + "value": "10069", + "text": "Communication fault with PCA Base.", + "icon": "" + }, + { + "value": "10070", + "text": "Communication fault with PCA Base.", + "icon": "" + }, + { + "value": "10162", + "text": "Sens flt:BT18", + "icon": "" + }, + { + "value": "15971", + "text": "incomp. inv.", + "icon": "" + }, + { + "value": "10177", + "text": "Sens flt:BT14", + "icon": "" + }, + { + "value": "10071", + "text": "HP alarm", + "icon": "" + }, + { + "value": "10072", + "text": "High pressure alarm", + "icon": "" + }, + { + "value": "10073", + "text": "High pressure alarm", + "icon": "" + }, + { + "value": "10074", + "text": "LP alarm", + "icon": "" + }, + { + "value": "10075", + "text": "Low pressure alarm", + "icon": "" + }, + { + "value": "10076", + "text": "Low pressure alarm", + "icon": "" + }, + { + "value": "10077", + "text": "Comm.flt MC", + "icon": "" + }, + { + "value": "10078", + "text": "Comm.flt with PCA Motor Controller", + "icon": "" + }, + { + "value": "10079", + "text": "Communication fault with PCA Motor Controller.", + "icon": "" + }, + { + "value": "10080", + "text": "MP alarm", + "icon": "" + }, + { + "value": "10081", + "text": "Motor protection alarm", + "icon": "" + }, + { + "value": "10082", + "text": "Motor protection alarm", + "icon": "" + }, + { + "value": "10083", + "text": "Sensor flt:BT1", + "icon": "" + }, + { + "value": "10084", + "text": "Sensor fault: BT1 outdoor sensor", + "icon": "" + }, + { + "value": "10085", + "text": "Sensor fault: BT1 outdoor sensor", + "icon": "" + }, + { + "value": "10086", + "text": "Sensor flt:BT2", + "icon": "" + }, + { + "value": "10087", + "text": "Sensor fault: BT2 flow line sensor 1", + "icon": "" + }, + { + "value": "10088", + "text": "Sensor fault: BT2 flow line sensor 1", + "icon": "" + }, + { + "value": "10089", + "text": "Sens flt:BT12", + "icon": "" + }, + { + "value": "10090", + "text": "Sensor fault: BT12 condenser out", + "icon": "" + }, + { + "value": "10091", + "text": "Sensor fault: BT12 condenser out", + "icon": "" + }, + { + "value": "10092", + "text": "Sensor flt:BT3", + "icon": "" + }, + { + "value": "10095", + "text": "Sensor flt:BT6", + "icon": "" + }, + { + "value": "10154", + "text": "internal electrical addition", + "icon": "" + }, + { + "value": "10183", + "text": "L exh.temp", + "icon": "" + }, + { + "value": "10203", + "text": "Sensor flt:BT7", + "icon": "" + }, + { + "value": "10187", + "text": "Defrosting", + "icon": "" + }, + { + "value": "15117", + "text": "blocked", + "icon": "" + }, + { + "value": "16528", + "text": "Inverter fault", + "icon": "" + }, + { + "value": "10068", + "text": "Com.flt Base", + "icon": "" + }, + { + "value": "10311", + "text": "Inverter I", + "icon": "" + }, + { + "value": "10314", + "text": "Inverter II", + "icon": "" + }, + { + "value": "10317", + "text": "Inverter III", + "icon": "" + }, + { + "value": "10110", + "text": "Hot gas alarm", + "icon": "" + }, + { + "value": "10116", + "text": "TB alarm", + "icon": "" + }, + { + "value": "10122", + "text": "Hi cond. out", + "icon": "" + }, + { + "value": "10276", + "text": "Err: BT63", + "icon": "" + }, + { + "value": "10293", + "text": "serial no", + "icon": "" + }, + { + "value": "10296", + "text": "software", + "icon": "" + }, + { + "value": "11176", + "text": "comm.err hp", + "icon": "" + }, + { + "value": "15117", + "text": "blocked", + "icon": "" + }, + { + "value": "15629", + "text": "ext control", + "icon": "" + }, + { + "value": "15966", + "text": "Pres.alarm", + "icon": "" + }, + { + "value": "15971", + "text": "incomp. inv.", + "icon": "" + }, + { + "value": "16537", + "text": "Soft-start flt", + "icon": "" + }, + { + "value": "16659", + "text": "Acc. block.", + "icon": "" + }, + { + "value": "15468", + "text": "initiating", + "icon": "" + }, + { + "value": "11165", + "text": "pool 2", + "icon": "" + }, + { + "value": "11174", + "text": "com.err.slave", + "icon": "" + }, + { + "value": "15985", + "text": "Com.flt GP12", + "icon": "" + }, + { + "value": "16541", + "text": "not docked", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "50113", + "parameterName": "status:", + "parameterUnit": "kW", + "writable": false, + "timestamp": "2024-03-12T16:16:30+00:00", + "value": 1512, + "strVal": "1512kW", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "15000", + "text": "starts", + "icon": "" + }, + { + "value": "15001", + "text": "runs", + "icon": "" + }, + { + "value": "15003", + "text": "off", + "icon": "" + }, + { + "value": "15004", + "text": "hot water", + "icon": "" + }, + { + "value": "15005", + "text": "heating", + "icon": "" + }, + { + "value": "15006", + "text": "pool", + "icon": "" + }, + { + "value": "10480", + "text": "incomp. hp", + "icon": "" + }, + { + "value": "10065", + "text": "Comm.fault In", + "icon": "" + }, + { + "value": "10066", + "text": "Communication fault with PCA Input.", + "icon": "" + }, + { + "value": "10067", + "text": "Communication fault with PCA Input.", + "icon": "" + }, + { + "value": "10068", + "text": "Com.flt Base", + "icon": "" + }, + { + "value": "10069", + "text": "Communication fault with PCA Base.", + "icon": "" + }, + { + "value": "10070", + "text": "Communication fault with PCA Base.", + "icon": "" + }, + { + "value": "10162", + "text": "Sens flt:BT18", + "icon": "" + }, + { + "value": "15971", + "text": "incomp. inv.", + "icon": "" + }, + { + "value": "10177", + "text": "Sens flt:BT14", + "icon": "" + }, + { + "value": "10071", + "text": "HP alarm", + "icon": "" + }, + { + "value": "10072", + "text": "High pressure alarm", + "icon": "" + }, + { + "value": "10073", + "text": "High pressure alarm", + "icon": "" + }, + { + "value": "10074", + "text": "LP alarm", + "icon": "" + }, + { + "value": "10075", + "text": "Low pressure alarm", + "icon": "" + }, + { + "value": "10076", + "text": "Low pressure alarm", + "icon": "" + }, + { + "value": "10077", + "text": "Comm.flt MC", + "icon": "" + }, + { + "value": "10078", + "text": "Comm.flt with PCA Motor Controller", + "icon": "" + }, + { + "value": "10079", + "text": "Communication fault with PCA Motor Controller.", + "icon": "" + }, + { + "value": "10080", + "text": "MP alarm", + "icon": "" + }, + { + "value": "10081", + "text": "Motor protection alarm", + "icon": "" + }, + { + "value": "10082", + "text": "Motor protection alarm", + "icon": "" + }, + { + "value": "10083", + "text": "Sensor flt:BT1", + "icon": "" + }, + { + "value": "10084", + "text": "Sensor fault: BT1 outdoor sensor", + "icon": "" + }, + { + "value": "10085", + "text": "Sensor fault: BT1 outdoor sensor", + "icon": "" + }, + { + "value": "10086", + "text": "Sensor flt:BT2", + "icon": "" + }, + { + "value": "10087", + "text": "Sensor fault: BT2 flow line sensor 1", + "icon": "" + }, + { + "value": "10088", + "text": "Sensor fault: BT2 flow line sensor 1", + "icon": "" + }, + { + "value": "10089", + "text": "Sens flt:BT12", + "icon": "" + }, + { + "value": "10090", + "text": "Sensor fault: BT12 condenser out", + "icon": "" + }, + { + "value": "10091", + "text": "Sensor fault: BT12 condenser out", + "icon": "" + }, + { + "value": "10092", + "text": "Sensor flt:BT3", + "icon": "" + }, + { + "value": "10095", + "text": "Sensor flt:BT6", + "icon": "" + }, + { + "value": "10154", + "text": "internal electrical addition", + "icon": "" + }, + { + "value": "10183", + "text": "L exh.temp", + "icon": "" + }, + { + "value": "10203", + "text": "Sensor flt:BT7", + "icon": "" + }, + { + "value": "10187", + "text": "Defrosting", + "icon": "" + }, + { + "value": "15117", + "text": "blocked", + "icon": "" + }, + { + "value": "16528", + "text": "Inverter fault", + "icon": "" + }, + { + "value": "10068", + "text": "Com.flt Base", + "icon": "" + }, + { + "value": "10311", + "text": "Inverter I", + "icon": "" + }, + { + "value": "10314", + "text": "Inverter II", + "icon": "" + }, + { + "value": "10317", + "text": "Inverter III", + "icon": "" + }, + { + "value": "10110", + "text": "Hot gas alarm", + "icon": "" + }, + { + "value": "10116", + "text": "TB alarm", + "icon": "" + }, + { + "value": "10122", + "text": "Hi cond. out", + "icon": "" + }, + { + "value": "10276", + "text": "Err: BT63", + "icon": "" + }, + { + "value": "10293", + "text": "serial no", + "icon": "" + }, + { + "value": "10296", + "text": "software", + "icon": "" + }, + { + "value": "11176", + "text": "comm.err hp", + "icon": "" + }, + { + "value": "15117", + "text": "blocked", + "icon": "" + }, + { + "value": "15629", + "text": "ext control", + "icon": "" + }, + { + "value": "15966", + "text": "Pres.alarm", + "icon": "" + }, + { + "value": "15971", + "text": "incomp. inv.", + "icon": "" + }, + { + "value": "16537", + "text": "Soft-start flt", + "icon": "" + }, + { + "value": "16659", + "text": "Acc. block.", + "icon": "" + }, + { + "value": "15468", + "text": "initiating", + "icon": "" + }, + { + "value": "11165", + "text": "pool 2", + "icon": "" + }, + { + "value": "11174", + "text": "com.err.slave", + "icon": "" + }, + { + "value": "15985", + "text": "Com.flt GP12", + "icon": "" + }, + { + "value": "16541", + "text": "not docked", + "icon": "" + } + ], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "SMO 20", + "parameterId": "50114", + "parameterName": "add. heat in tank", + "parameterUnit": "min", + "writable": false, + "timestamp": "2024-03-08T14:20:01+00:00", + "value": 0, + "strVal": "0min", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1, + "enumValues": [ + { + "value": "0", + "text": "off", + "icon": "" + }, + { + "value": "1", + "text": "on", + "icon": "" + } + ], + "scaleValue": "1", + "zoneId": null + } +] diff --git a/tests/components/myuplink/test_number.py b/tests/components/myuplink/test_number.py index 41dc0dae164..2f898b1db9e 100644 --- a/tests/components/myuplink/test_number.py +++ b/tests/components/myuplink/test_number.py @@ -5,12 +5,15 @@ from unittest.mock import MagicMock from aiohttp import ClientError import pytest +from homeassistant.components.myuplink.const import DOMAIN from homeassistant.components.number import SERVICE_SET_VALUE from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from tests.common import load_fixture + TEST_PLATFORM = Platform.NUMBER pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) @@ -84,3 +87,19 @@ async def test_api_failure( ) await hass.async_block_till_done() mock_myuplink_client.async_set_device_points.assert_called_once() + + +@pytest.mark.parametrize( + "load_device_points_file", + [load_fixture("device_points_nibe_smo20.json", DOMAIN)], +) +async def test_entity_registry_smo20( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_myuplink_client: MagicMock, + setup_platform: None, +) -> None: + """Test that the entities are registered in the entity registry.""" + + entry = entity_registry.async_get("number.f730_cu_3x400v_change_in_curve") + assert entry.unique_id == "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-47028" diff --git a/tests/components/myuplink/test_switch.py b/tests/components/myuplink/test_switch.py index 71c7f41c214..7635cc43a05 100644 --- a/tests/components/myuplink/test_switch.py +++ b/tests/components/myuplink/test_switch.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock from aiohttp import ClientError import pytest +from homeassistant.components.myuplink.const import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -16,6 +17,8 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er +from tests.common import load_fixture + TEST_PLATFORM = Platform.SWITCH pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) @@ -94,3 +97,19 @@ async def test_api_failure( ) await hass.async_block_till_done() mock_myuplink_client.async_set_device_points.assert_called_once() + + +@pytest.mark.parametrize( + "load_device_points_file", + [load_fixture("device_points_nibe_smo20.json", DOMAIN)], +) +async def test_entity_registry_smo20( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_myuplink_client: MagicMock, + setup_platform: None, +) -> None: + """Test that the entities are registered in the entity registry.""" + + entry = entity_registry.async_get(ENTITY_ID) + assert entry.unique_id == ENTITY_UID From 4b4258881b77f4adbb62ae3685dc0bf7b81ef5bd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 14:40:40 +0100 Subject: [PATCH 1102/1691] Remove Sonos migrations (#113506) --- homeassistant/components/sonos/switch.py | 94 +----------------------- 1 file changed, 1 insertion(+), 93 deletions(-) diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index 53e06687615..c3ba89057de 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -11,7 +11,7 @@ from soco.exceptions import SoCoSlaveException, SoCoUPnPException from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TIME, EntityCategory, Platform +from homeassistant.const import ATTR_TIME, EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -91,9 +91,6 @@ async def async_setup_entry( """Set up Sonos from a config entry.""" async def _async_create_alarms(speaker: SonosSpeaker, alarm_ids: list[str]) -> None: - async_migrate_alarm_unique_ids( - hass, config_entry, speaker.household_id, alarm_ids - ) entities = [] created_alarms = ( hass.data[DATA_SONOS].alarms[speaker.household_id].created_alarm_ids @@ -123,10 +120,6 @@ async def async_setup_entry( available_soco_attributes, speaker ) for feature_type in available_features: - if feature_type == ATTR_SPEECH_ENHANCEMENT: - async_migrate_speech_enhancement_entity_unique_id( - hass, config_entry, speaker - ) _LOGGER.debug( "Creating %s switch on %s", feature_type, @@ -354,88 +347,3 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity): """Handle turn on/off of alarm switch.""" self.alarm.enabled = turn_on self.alarm.save() - - -@callback -def async_migrate_alarm_unique_ids( - hass: HomeAssistant, - config_entry: ConfigEntry, - household_id: str, - alarm_ids: list[str], -) -> None: - """Migrate alarm switch unique_ids in the entity registry to the new format.""" - entity_registry = er.async_get(hass) - registry_entries = er.async_entries_for_config_entry( - entity_registry, config_entry.entry_id - ) - - alarm_entries = [ - (entry.unique_id, entry) - for entry in registry_entries - if entry.domain == Platform.SWITCH and entry.original_icon == "mdi:alarm" - ] - - for old_unique_id, alarm_entry in alarm_entries: - if ":" in old_unique_id: - continue - - entry_alarm_id = old_unique_id.split("-")[-1] - if entry_alarm_id in alarm_ids: - new_unique_id = f"alarm-{household_id}:{entry_alarm_id}" - _LOGGER.debug( - "Migrating unique_id for %s from %s to %s", - alarm_entry.entity_id, - old_unique_id, - new_unique_id, - ) - entity_registry.async_update_entity( - alarm_entry.entity_id, new_unique_id=new_unique_id - ) - - -@callback -def async_migrate_speech_enhancement_entity_unique_id( - hass: HomeAssistant, - config_entry: ConfigEntry, - speaker: SonosSpeaker, -) -> None: - """Migrate Speech Enhancement switch entity unique_id.""" - entity_registry = er.async_get(hass) - registry_entries = er.async_entries_for_config_entry( - entity_registry, config_entry.entry_id - ) - - speech_enhancement_entries = [ - entry - for entry in registry_entries - if entry.domain == Platform.SWITCH - and entry.original_icon == FEATURE_ICONS[ATTR_SPEECH_ENHANCEMENT] - and entry.unique_id.startswith(speaker.soco.uid) - ] - - if len(speech_enhancement_entries) > 1: - _LOGGER.warning( - ( - "Migration of Speech Enhancement switches on %s failed," - " manual cleanup required: %s" - ), - speaker.zone_name, - [e.entity_id for e in speech_enhancement_entries], - ) - return - - if len(speech_enhancement_entries) == 1: - old_entry = speech_enhancement_entries[0] - if old_entry.unique_id.endswith("dialog_level"): - return - - new_unique_id = f"{speaker.soco.uid}-{ATTR_SPEECH_ENHANCEMENT}" - _LOGGER.debug( - "Migrating unique_id for %s from %s to %s", - old_entry.entity_id, - old_entry.unique_id, - new_unique_id, - ) - entity_registry.async_update_entity( - old_entry.entity_id, new_unique_id=new_unique_id - ) From 1ff049cc66846b239b0ad797832ac4fcb6ffdd20 Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Fri, 15 Mar 2024 14:42:07 +0100 Subject: [PATCH 1103/1691] Add diagnostics platform to Fastdotcom (#111525) --- .../components/fastdotcom/__init__.py | 4 +- .../components/fastdotcom/coordinator.py | 2 +- .../components/fastdotcom/diagnostics.py | 24 +++++++++++ homeassistant/components/fastdotcom/sensor.py | 10 ++--- .../components/fastdotcom/services.py | 6 +-- .../snapshots/test_diagnostics.ambr | 6 +++ .../components/fastdotcom/test_diagnostics.py | 43 +++++++++++++++++++ 7 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/fastdotcom/diagnostics.py create mode 100644 tests/components/fastdotcom/snapshots/test_diagnostics.ambr create mode 100644 tests/components/fastdotcom/test_diagnostics.py diff --git a/homeassistant/components/fastdotcom/__init__.py b/homeassistant/components/fastdotcom/__init__.py index 9cd99c0c58b..12bd355b82b 100644 --- a/homeassistant/components/fastdotcom/__init__.py +++ b/homeassistant/components/fastdotcom/__init__.py @@ -14,7 +14,7 @@ from homeassistant.helpers.start import async_at_started from homeassistant.helpers.typing import ConfigType from .const import CONF_MANUAL, DEFAULT_INTERVAL, DOMAIN, PLATFORMS -from .coordinator import FastdotcomDataUpdateCoordindator +from .coordinator import FastdotcomDataUpdateCoordinator from .services import async_setup_services _LOGGER = logging.getLogger(__name__) @@ -50,7 +50,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Fast.com from a config entry.""" - coordinator = FastdotcomDataUpdateCoordindator(hass) + coordinator = FastdotcomDataUpdateCoordinator(hass) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups( diff --git a/homeassistant/components/fastdotcom/coordinator.py b/homeassistant/components/fastdotcom/coordinator.py index 42e60069507..75ac55b8314 100644 --- a/homeassistant/components/fastdotcom/coordinator.py +++ b/homeassistant/components/fastdotcom/coordinator.py @@ -12,7 +12,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import DEFAULT_INTERVAL, DOMAIN, LOGGER -class FastdotcomDataUpdateCoordindator(DataUpdateCoordinator[float]): +class FastdotcomDataUpdateCoordinator(DataUpdateCoordinator[float]): """Class to manage fetching Fast.com data API.""" def __init__(self, hass: HomeAssistant) -> None: diff --git a/homeassistant/components/fastdotcom/diagnostics.py b/homeassistant/components/fastdotcom/diagnostics.py new file mode 100644 index 00000000000..d7383ef0c6a --- /dev/null +++ b/homeassistant/components/fastdotcom/diagnostics.py @@ -0,0 +1,24 @@ +"""Diagnostics support for Fast.com.""" + +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import FastdotcomDataUpdateCoordinator + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for the config entry.""" + coordinator: FastdotcomDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ] + + return { + "coordinator_data": coordinator.data, + } diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index 52f9c6f0a2c..721290e8c0d 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import FastdotcomDataUpdateCoordindator +from .coordinator import FastdotcomDataUpdateCoordinator async def async_setup_entry( @@ -24,13 +24,11 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Fast.com sensor.""" - coordinator: FastdotcomDataUpdateCoordindator = hass.data[DOMAIN][entry.entry_id] + coordinator: FastdotcomDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities([SpeedtestSensor(entry.entry_id, coordinator)]) -class SpeedtestSensor( - CoordinatorEntity[FastdotcomDataUpdateCoordindator], SensorEntity -): +class SpeedtestSensor(CoordinatorEntity[FastdotcomDataUpdateCoordinator], SensorEntity): """Implementation of a Fast.com sensor.""" _attr_translation_key = "download" @@ -41,7 +39,7 @@ class SpeedtestSensor( _attr_has_entity_name = True def __init__( - self, entry_id: str, coordinator: FastdotcomDataUpdateCoordindator + self, entry_id: str, coordinator: FastdotcomDataUpdateCoordinator ) -> None: """Initialize the sensor.""" super().__init__(coordinator) diff --git a/homeassistant/components/fastdotcom/services.py b/homeassistant/components/fastdotcom/services.py index a901915e11f..5939a667342 100644 --- a/homeassistant/components/fastdotcom/services.py +++ b/homeassistant/components/fastdotcom/services.py @@ -8,14 +8,14 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import issue_registry as ir from .const import DOMAIN, SERVICE_NAME -from .coordinator import FastdotcomDataUpdateCoordindator +from .coordinator import FastdotcomDataUpdateCoordinator def async_setup_services(hass: HomeAssistant) -> None: """Set up the service for the Fastdotcom integration.""" @callback - def collect_coordinator() -> FastdotcomDataUpdateCoordindator: + def collect_coordinator() -> FastdotcomDataUpdateCoordinator: """Collect the coordinator Fastdotcom.""" config_entries = hass.config_entries.async_entries(DOMAIN) if not config_entries: @@ -24,7 +24,7 @@ def async_setup_services(hass: HomeAssistant) -> None: for config_entry in config_entries: if config_entry.state != ConfigEntryState.LOADED: raise HomeAssistantError(f"{config_entry.title} is not loaded") - coordinator: FastdotcomDataUpdateCoordindator = hass.data[DOMAIN][ + coordinator: FastdotcomDataUpdateCoordinator = hass.data[DOMAIN][ config_entry.entry_id ] break diff --git a/tests/components/fastdotcom/snapshots/test_diagnostics.ambr b/tests/components/fastdotcom/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..d2b4ca6c55a --- /dev/null +++ b/tests/components/fastdotcom/snapshots/test_diagnostics.ambr @@ -0,0 +1,6 @@ +# serializer version: 1 +# name: test_get_config_entry_diagnostics + dict({ + 'coordinator_data': 50.3, + }) +# --- diff --git a/tests/components/fastdotcom/test_diagnostics.py b/tests/components/fastdotcom/test_diagnostics.py new file mode 100644 index 00000000000..7ea644665c7 --- /dev/null +++ b/tests/components/fastdotcom/test_diagnostics.py @@ -0,0 +1,43 @@ +"""Test the Fast.com component diagnostics.""" + +from unittest.mock import patch + +from syrupy import SnapshotAssertion + +from homeassistant.components.fastdotcom.const import DEFAULT_NAME, DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_get_config_entry_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test if get_config_entry_diagnostics returns the correct data.""" + config_entry = MockConfigEntry( + version=1, + domain=DOMAIN, + title=DEFAULT_NAME, + source=SOURCE_USER, + options={}, + entry_id="TEST_ENTRY_ID", + unique_id="UNIQUE_TEST_ID", + minor_version=1, + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.fastdotcom.coordinator.fast_com", return_value=50.3 + ): + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + == snapshot + ) From a9fb34d9c205ce6551c0b35c62b9e9f290e39029 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 15 Mar 2024 15:16:06 +0100 Subject: [PATCH 1104/1691] Remove modbus create_issue from init (#113510) Create_issue only in check_config. --- .../components/modbus/base_platform.py | 22 ---------- homeassistant/components/modbus/modbus.py | 22 ---------- homeassistant/components/modbus/validators.py | 41 +++++++++++++++++++ 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 82b2b788e78..6d547e5ea38 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -29,7 +29,6 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity, ToggleEntity from homeassistant.helpers.event import async_call_later, async_track_time_interval -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.restore_state import RestoreEntity from .const import ( @@ -46,7 +45,6 @@ from .const import ( CONF_DATA_TYPE, CONF_DEVICE_ADDRESS, CONF_INPUT_TYPE, - CONF_LAZY_ERROR, CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_NAN_VALUE, @@ -63,7 +61,6 @@ from .const import ( CONF_VIRTUAL_COUNT, CONF_WRITE_TYPE, CONF_ZERO_SUPPRESS, - MODBUS_DOMAIN, SIGNAL_START_ENTITY, SIGNAL_STOP_ENTITY, DataType, @@ -82,25 +79,6 @@ class BasePlatform(Entity): ) -> None: """Initialize the Modbus binary sensor.""" - if CONF_LAZY_ERROR in entry: - async_create_issue( - hass, - MODBUS_DOMAIN, - "removed_lazy_error_count", - breaks_in_ha_version="2024.7.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="removed_lazy_error_count", - translation_placeholders={ - "config_key": "lazy_error_count", - "integration": MODBUS_DOMAIN, - "url": "https://www.home-assistant.io/integrations/modbus", - }, - ) - _LOGGER.warning( - "`lazy_error_count`: is deprecated and will be removed in version 2024.7" - ) - self._hub = hub self._slave = entry.get(CONF_SLAVE) or entry.get(CONF_DEVICE_ADDRESS, 0) self._address = int(entry[CONF_ADDRESS]) diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index f4947223662..28249ce72ad 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -34,7 +34,6 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.typing import ConfigType @@ -56,7 +55,6 @@ from .const import ( CONF_BYTESIZE, CONF_MSG_WAIT, CONF_PARITY, - CONF_RETRIES, CONF_STOPBITS, DEFAULT_HUB, MODBUS_DOMAIN as DOMAIN, @@ -257,26 +255,6 @@ class ModbusHub: def __init__(self, hass: HomeAssistant, client_config: dict[str, Any]) -> None: """Initialize the Modbus hub.""" - if CONF_RETRIES in client_config: - async_create_issue( - hass, - DOMAIN, - "deprecated_retries", - breaks_in_ha_version="2024.7.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_retries", - translation_placeholders={ - "config_key": "retries", - "integration": DOMAIN, - "url": "https://www.home-assistant.io/integrations/modbus", - }, - ) - _LOGGER.warning( - "`retries`: is deprecated and will be removed in version 2024.7" - ) - else: - client_config[CONF_RETRIES] = 3 # generic configuration self._client: AsyncModbusSerialClient | AsyncModbusTcpClient | AsyncModbusUdpClient | None = None self._async_cancel_listener: Callable[[], None] | None = None diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index 7ca64a03eed..a91ac9be6b4 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -35,6 +35,8 @@ from .const import ( CONF_HVAC_MODE_REGISTER, CONF_HVAC_ONOFF_REGISTER, CONF_INPUT_TYPE, + CONF_LAZY_ERROR, + CONF_RETRIES, CONF_SLAVE_COUNT, CONF_SWAP, CONF_SWAP_BYTE, @@ -304,6 +306,27 @@ def validate_modbus( hub_name_inx: int, ) -> bool: """Validate modbus entries.""" + if CONF_RETRIES in hub: + async_create_issue( + hass, + DOMAIN, + "deprecated_retries", + breaks_in_ha_version="2024.7.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_retries", + translation_placeholders={ + "config_key": "retries", + "integration": DOMAIN, + "url": "https://www.home-assistant.io/integrations/modbus", + }, + ) + _LOGGER.warning( + "`retries`: is deprecated and will be removed in version 2024.7" + ) + else: + hub[CONF_RETRIES] = 3 + host: str = ( hub[CONF_PORT] if hub[CONF_TYPE] == SERIAL @@ -352,6 +375,24 @@ def validate_entity( ent_addr: set[str], ) -> bool: """Validate entity.""" + if CONF_LAZY_ERROR in entity: + async_create_issue( + hass, + DOMAIN, + "removed_lazy_error_count", + breaks_in_ha_version="2024.7.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="removed_lazy_error_count", + translation_placeholders={ + "config_key": "lazy_error_count", + "integration": DOMAIN, + "url": "https://www.home-assistant.io/integrations/modbus", + }, + ) + _LOGGER.warning( + "`lazy_error_count`: is deprecated and will be removed in version 2024.7" + ) name = f"{component}.{entity[CONF_NAME]}" addr = f"{hub_name}{entity[CONF_ADDRESS]}" scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) From 4107cd6ad8e543e2e07c18487a486f515de08e75 Mon Sep 17 00:00:00 2001 From: cosimomeli Date: Fri, 15 Mar 2024 15:31:51 +0100 Subject: [PATCH 1105/1691] Add Ring Intercom open door button (#113514) * Add button * Make Ruff happy * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker * Fix doc string * Format * Update tests/components/ring/test_button.py --------- Co-authored-by: Joost Lekkerkerker --- homeassistant/components/ring/button.py | 57 ++++++++++++++++++++++ homeassistant/components/ring/const.py | 1 + homeassistant/components/ring/icons.json | 3 ++ homeassistant/components/ring/strings.json | 5 ++ tests/components/ring/conftest.py | 11 +++++ tests/components/ring/test_button.py | 42 ++++++++++++++++ 6 files changed, 119 insertions(+) create mode 100644 homeassistant/components/ring/button.py create mode 100644 tests/components/ring/test_button.py diff --git a/homeassistant/components/ring/button.py b/homeassistant/components/ring/button.py new file mode 100644 index 00000000000..343c0d68257 --- /dev/null +++ b/homeassistant/components/ring/button.py @@ -0,0 +1,57 @@ +"""Component providing support for Ring buttons.""" + +from __future__ import annotations + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, RING_DEVICES, RING_DEVICES_COORDINATOR +from .coordinator import RingDataCoordinator +from .entity import RingEntity, exception_wrap + +BUTTON_DESCRIPTION = ButtonEntityDescription( + key="open_door", translation_key="open_door" +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Create the buttons for the Ring devices.""" + devices = hass.data[DOMAIN][config_entry.entry_id][RING_DEVICES] + devices_coordinator: RingDataCoordinator = hass.data[DOMAIN][config_entry.entry_id][ + RING_DEVICES_COORDINATOR + ] + + async_add_entities( + RingDoorButton(device, devices_coordinator, BUTTON_DESCRIPTION) + for device in devices["other"] + if device.has_capability("open") + ) + + +class RingDoorButton(RingEntity, ButtonEntity): + """Creates a button to open the ring intercom door.""" + + def __init__( + self, + device, + coordinator, + description: ButtonEntityDescription, + ) -> None: + """Initialize the button.""" + super().__init__( + device, + coordinator, + ) + self.entity_description = description + self._attr_unique_id = f"{device.id}-{description.key}" + + @exception_wrap + def press(self) -> None: + """Open the door.""" + self._device.open_door() diff --git a/homeassistant/components/ring/const.py b/homeassistant/components/ring/const.py index 24c584016d5..23f378a38be 100644 --- a/homeassistant/components/ring/const.py +++ b/homeassistant/components/ring/const.py @@ -16,6 +16,7 @@ DEFAULT_ENTITY_NAMESPACE = "ring" PLATFORMS = [ Platform.BINARY_SENSOR, + Platform.BUTTON, Platform.CAMERA, Platform.LIGHT, Platform.SENSOR, diff --git a/homeassistant/components/ring/icons.json b/homeassistant/components/ring/icons.json index 9ce0de6bebc..9dd31fd0fd1 100644 --- a/homeassistant/components/ring/icons.json +++ b/homeassistant/components/ring/icons.json @@ -22,6 +22,9 @@ "voice_volume": { "default": "mdi:account-voice" }, + "open_door": { + "default": "mdi:door-closed-lock" + }, "wifi_signal_category": { "default": "mdi:wifi" }, diff --git a/homeassistant/components/ring/strings.json b/homeassistant/components/ring/strings.json index de8b5112ec9..142c533fcfc 100644 --- a/homeassistant/components/ring/strings.json +++ b/homeassistant/components/ring/strings.json @@ -37,6 +37,11 @@ "name": "Ding" } }, + "button": { + "open_door": { + "name": "Open door" + } + }, "light": { "light": { "name": "[%key:component::light::title%]" diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 106a824f1d5..70c067af887 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -131,4 +131,15 @@ def requests_mock_fixture(): ), text="ok", ) + # Mocks the open door command for intercom devices + mock.put( + "https://api.ring.com/commands/v1/devices/185036587/device_rpc", + status_code=200, + text="{}", + ) + # Mocks the response for getting the history of the intercom + mock.get( + "https://api.ring.com/clients_api/doorbots/185036587/history", + text=load_fixture("intercom_history.json", "ring"), + ) yield mock diff --git a/tests/components/ring/test_button.py b/tests/components/ring/test_button.py new file mode 100644 index 00000000000..6f0c29b1fcc --- /dev/null +++ b/tests/components/ring/test_button.py @@ -0,0 +1,42 @@ +"""The tests for the Ring button platform.""" + +import requests_mock + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .common import setup_platform + + +async def test_entity_registry( + hass: HomeAssistant, + requests_mock: requests_mock.Mocker, + entity_registry: er.EntityRegistry, +) -> None: + """Tests that the devices are registered in the entity registry.""" + await setup_platform(hass, Platform.BUTTON) + + entry = entity_registry.async_get("button.ingress_open_door") + assert entry.unique_id == "185036587-open_door" + + +async def test_button_opens_door( + hass: HomeAssistant, requests_mock: requests_mock.Mocker +) -> None: + """Tests the door open button works correctly.""" + await setup_platform(hass, Platform.BUTTON) + + # Mocks the response for opening door + mock = requests_mock.put( + "https://api.ring.com/commands/v1/devices/185036587/device_rpc", + status_code=200, + text="{}", + ) + + await hass.services.async_call( + "button", "press", {"entity_id": "button.ingress_open_door"}, blocking=True + ) + + await hass.async_block_till_done() + assert mock.called_once From 823481063ecf60675f37d9bf7713f4148f939e89 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 16:34:13 +0100 Subject: [PATCH 1106/1691] Add icon translations to Sonos (#112235) --- .../components/sonos/binary_sensor.py | 1 - homeassistant/components/sonos/icons.json | 55 +++++++++++++++++++ homeassistant/components/sonos/sensor.py | 3 +- homeassistant/components/sonos/switch.py | 13 ----- 4 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/sonos/icons.json diff --git a/homeassistant/components/sonos/binary_sensor.py b/homeassistant/components/sonos/binary_sensor.py index b927e970c9b..2c1e8af9961 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -93,7 +93,6 @@ class SonosMicrophoneSensorEntity(SonosEntity, BinarySensorEntity): """Representation of a Sonos microphone sensor entity.""" _attr_entity_category = EntityCategory.DIAGNOSTIC - _attr_icon = "mdi:microphone" _attr_translation_key = "microphone" def __init__(self, speaker: SonosSpeaker) -> None: diff --git a/homeassistant/components/sonos/icons.json b/homeassistant/components/sonos/icons.json new file mode 100644 index 00000000000..e2545358ba6 --- /dev/null +++ b/homeassistant/components/sonos/icons.json @@ -0,0 +1,55 @@ +{ + "entity": { + "binary_sensor": { + "microphone": { + "default": "mdi:microphone" + } + }, + "sensor": { + "audio_input_format": { + "default": "mdi:import" + }, + "favorites": { + "default": "mdi:star" + } + }, + "switch": { + "loudness": { + "default": "mdi:bullhorn-variant" + }, + "surround_mode": { + "default": "mdi:music-note-plus" + }, + "night_mode": { + "default": "mdi:chat-sleep" + }, + "dialog_level": { + "default": "mdi:ear-hearing" + }, + "cross_fade": { + "default": "mdi:swap-horizontal" + }, + "status_light": { + "default": "mdi:led-on" + }, + "sub_enabled": { + "default": "mdi:dog" + }, + "surround_enabled": { + "default": "mdi:surround-sound" + }, + "buttons_enabled": { + "default": "mdi:gesture-tap" + } + } + }, + "services": { + "snapshot": "mdi:camera", + "restore": "mdi:camera-retake", + "set_sleep_timer": "mdi:alarm", + "clear_sleep_timer": "mdi:alarm-off", + "play_queue": "mdi:play", + "remove_from_queue": "mdi:playlist-remove", + "update_alarm": "mdi:alarm" + } +} diff --git a/homeassistant/components/sonos/sensor.py b/homeassistant/components/sonos/sensor.py index 25cbcf752ea..a089c09b33c 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -106,7 +106,6 @@ class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity): """Representation of a Sonos audio import format sensor entity.""" _attr_entity_category = EntityCategory.DIAGNOSTIC - _attr_icon = "mdi:import" _attr_translation_key = "audio_input_format" _attr_should_poll = True @@ -135,8 +134,8 @@ class SonosFavoritesEntity(SensorEntity): """Representation of a Sonos favorites info entity.""" _attr_entity_registry_enabled_default = False - _attr_icon = "mdi:star" _attr_name = "Sonos favorites" + _attr_translation_key = "favorites" _attr_native_unit_of_measurement = "items" _attr_should_poll = False diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index c3ba89057de..4bf5487b1a6 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -68,18 +68,6 @@ POLL_REQUIRED = ( ATTR_STATUS_LIGHT, ) -FEATURE_ICONS = { - ATTR_LOUDNESS: "mdi:bullhorn-variant", - ATTR_MUSIC_PLAYBACK_FULL_VOLUME: "mdi:music-note-plus", - ATTR_NIGHT_SOUND: "mdi:chat-sleep", - ATTR_SPEECH_ENHANCEMENT: "mdi:ear-hearing", - ATTR_CROSSFADE: "mdi:swap-horizontal", - ATTR_STATUS_LIGHT: "mdi:led-on", - ATTR_SUB_ENABLED: "mdi:dog", - ATTR_SURROUND_ENABLED: "mdi:surround-sound", - ATTR_TOUCH_CONTROLS: "mdi:gesture-tap", -} - WEEKEND_DAYS = (0, 6) @@ -147,7 +135,6 @@ class SonosSwitchEntity(SonosPollingEntity, SwitchEntity): self._attr_entity_category = EntityCategory.CONFIG self._attr_translation_key = feature_type self._attr_unique_id = f"{speaker.soco.uid}-{feature_type}" - self._attr_icon = FEATURE_ICONS.get(feature_type) if feature_type in POLL_REQUIRED: self._attr_entity_registry_enabled_default = False From 38164c705e7faa3b87fc2117b2e4dd8884aff8b5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 16:40:48 +0100 Subject: [PATCH 1107/1691] Add missing icon for Unifi protect service (#113508) --- homeassistant/components/unifiprotect/icons.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/icons.json b/homeassistant/components/unifiprotect/icons.json index e4c8801c888..b357a892ff4 100644 --- a/homeassistant/components/unifiprotect/icons.json +++ b/homeassistant/components/unifiprotect/icons.json @@ -3,6 +3,7 @@ "add_doorbell_text": "mdi:message-plus", "remove_doorbell_text": "mdi:message-minus", "set_default_doorbell_text": "mdi:message-processing", - "set_chime_paired_doorbells": "mdi:bell-cog" + "set_chime_paired_doorbells": "mdi:bell-cog", + "remove_privacy_zone": "mdi:eye-minus" } } From 6ede1c543f73c60268f6c1664af9bb4e84a7f24d Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Fri, 15 Mar 2024 13:06:14 -0400 Subject: [PATCH 1108/1691] Protect SupervisorIssues.update method (#113425) --- homeassistant/components/hassio/issues.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/hassio/issues.py b/homeassistant/components/hassio/issues.py index 0d78c71d4af..0bb28a3ceef 100644 --- a/homeassistant/components/hassio/issues.py +++ b/homeassistant/components/hassio/issues.py @@ -300,13 +300,13 @@ class SupervisorIssues: async def setup(self) -> None: """Create supervisor events listener.""" - await self.update() + await self._update() async_dispatcher_connect( self._hass, EVENT_SUPERVISOR_EVENT, self._supervisor_events_to_issues ) - async def update(self, _: datetime | None = None) -> None: + async def _update(self, _: datetime | None = None) -> None: """Update issues from Supervisor resolution center.""" try: data = await self._client.get_resolution_info() @@ -315,7 +315,7 @@ class SupervisorIssues: async_call_later( self._hass, REQUEST_REFRESH_DELAY, - HassJob(self.update, cancel_on_shutdown=True), + HassJob(self._update, cancel_on_shutdown=True), ) return self.unhealthy_reasons = set(data[ATTR_UNHEALTHY]) @@ -342,7 +342,7 @@ class SupervisorIssues: event[ATTR_WS_EVENT] == EVENT_SUPERVISOR_UPDATE and event.get(ATTR_UPDATE_KEY) == UPDATE_KEY_SUPERVISOR ): - self._hass.async_create_task(self.update()) + self._hass.async_create_task(self._update()) elif event[ATTR_WS_EVENT] == EVENT_HEALTH_CHANGED: self.unhealthy_reasons = ( From a73553581bc30f13e242d2faaae0dba0086d6134 Mon Sep 17 00:00:00 2001 From: Drew C Date: Fri, 15 Mar 2024 13:10:58 -0400 Subject: [PATCH 1109/1691] Bump opower to 0.4.1 (#113509) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index ab1bff8a5e9..bc6f8796d50 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", "loggers": ["opower"], - "requirements": ["opower==0.4.0"] + "requirements": ["opower==0.4.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 60f95eb9dc3..4ee5604ad47 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1470,7 +1470,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.4.0 +opower==0.4.1 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 89540c8e37f..18baa476413 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1164,7 +1164,7 @@ openerz-api==0.3.0 openhomedevice==2.2.0 # homeassistant.components.opower -opower==0.4.0 +opower==0.4.1 # homeassistant.components.oralb oralb-ble==0.17.6 From cfc2f17f35d5353b062ed9d55efb8558103f488f Mon Sep 17 00:00:00 2001 From: soonsouth <163404563+soonsouth@users.noreply.github.com> Date: Sat, 16 Mar 2024 01:11:12 +0800 Subject: [PATCH 1110/1691] Remove repetitive words in comments and docstrings (#113529) chore: remove repetitive words Signed-off-by: soonsouth --- homeassistant/components/esphome/voice_assistant.py | 2 +- tests/components/bluetooth/test_manager.py | 2 +- tests/components/light/test_init.py | 12 ++++++------ tests/components/mqtt/test_image.py | 2 +- tests/components/recorder/test_statistics.py | 2 +- tests/components/reddit/__init__.py | 2 +- .../zwave_js/fixtures/leviton_zw4sf_state.json | 2 +- tests/helpers/test_config_validation.py | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/esphome/voice_assistant.py b/homeassistant/components/esphome/voice_assistant.py index 15b580a0601..f856cc27179 100644 --- a/homeassistant/components/esphome/voice_assistant.py +++ b/homeassistant/components/esphome/voice_assistant.py @@ -95,7 +95,7 @@ class VoiceAssistantUDPServer(asyncio.DatagramProtocol): @property def is_running(self) -> bool: - """True if the the UDP server is started and hasn't been asked to stop.""" + """True if the UDP server is started and hasn't been asked to stop.""" return self.started and (not self.stop_requested) async def start_server(self) -> int: diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index 87fbdfb0936..b99d47cdba1 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -597,7 +597,7 @@ async def test_connectable_advertisement_can_be_retrieved_with_best_path_is_non_ ) -> None: """Test we can still get a connectable BLEDevice when the best path is non-connectable. - In this case the the device is closer to a non-connectable scanner, but the + In this case the device is closer to a non-connectable scanner, but the at least one connectable scanner has the device in range. """ diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 2c6c84cc35c..2cc23f71f22 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1613,7 +1613,7 @@ async def test_light_service_call_color_conversion( _, data = entity5.last_call("turn_on") assert data == {"brightness": 255, "rgbw_color": (0, 0, 0, 255)} _, data = entity6.last_call("turn_on") - # The midpoint of the the white channels is warm, compensated by adding green + blue + # The midpoint of the white channels is warm, compensated by adding green + blue assert data == {"brightness": 255, "rgbww_color": (0, 76, 141, 255, 255)} _, data = entity7.last_call("turn_on") assert data == {"brightness": 255, "color_temp_kelvin": 5962, "color_temp": 167} @@ -1686,7 +1686,7 @@ async def test_light_service_call_color_conversion( _, data = entity5.last_call("turn_on") assert data == {"brightness": 128, "rgbw_color": (0, 0, 0, 255)} _, data = entity6.last_call("turn_on") - # The midpoint the the white channels is warm, compensated by adding green + blue + # The midpoint the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)} _, data = entity7.last_call("turn_on") assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167} @@ -1759,7 +1759,7 @@ async def test_light_service_call_color_conversion( _, data = entity5.last_call("turn_on") assert data == {"brightness": 128, "rgbw_color": (1, 0, 0, 255)} _, data = entity6.last_call("turn_on") - # The midpoint the the white channels is warm, compensated by adding green + blue + # The midpoint the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)} _, data = entity7.last_call("turn_on") assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167} @@ -1796,7 +1796,7 @@ async def test_light_service_call_color_conversion( _, data = entity5.last_call("turn_on") assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 64)} _, data = entity6.last_call("turn_on") - # The midpoint the the white channels is warm, compensated by adding green + blue + # The midpoint the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (128, 0, 30, 117, 117)} _, data = entity7.last_call("turn_on") assert data == {"brightness": 128, "color_temp_kelvin": 3011, "color_temp": 332} @@ -1833,7 +1833,7 @@ async def test_light_service_call_color_conversion( _, data = entity5.last_call("turn_on") assert data == {"brightness": 128, "rgbw_color": (255, 255, 255, 255)} _, data = entity6.last_call("turn_on") - # The midpoint the the white channels is warm, compensated by adding green + blue + # The midpoint the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)} _, data = entity7.last_call("turn_on") assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167} @@ -1904,7 +1904,7 @@ async def test_light_service_call_color_conversion( _, data = entity4.last_call("turn_on") assert data == {"brightness": 128, "hs_color": (27.429, 27.451)} _, data = entity5.last_call("turn_on") - # The midpoint the the white channels is warm, compensated by decreasing green + blue + # The midpoint the white channels is warm, compensated by decreasing green + blue assert data == {"brightness": 128, "rgbw_color": (96, 44, 0, 255)} _, data = entity6.last_call("turn_on") assert data == {"brightness": 128, "rgbww_color": (255, 255, 255, 255, 255)} diff --git a/tests/components/mqtt/test_image.py b/tests/components/mqtt/test_image.py index 2f1676c0c03..79e6cf1d281 100644 --- a/tests/components/mqtt/test_image.py +++ b/tests/components/mqtt/test_image.py @@ -449,7 +449,7 @@ async def test_image_from_url_fails( state = hass.states.get("image.test") - # The image failed to load, the the last image update is registered + # The image failed to load, the last image update is registered # but _last_image was set to `None` assert state.state == "2023-04-01T00:00:00+00:00" client = await hass_client_no_auth() diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 2b320cffccc..27f2955b1a5 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -456,7 +456,7 @@ def test_rename_entity_collision( ) -> None: """Test statistics is migrated when entity_id is changed. - This test relies on the the safeguard in the statistics_meta_manager + This test relies on the safeguard in the statistics_meta_manager and should not hit the filter_unique_constraint_integrity_error safeguard. """ hass = hass_recorder() diff --git a/tests/components/reddit/__init__.py b/tests/components/reddit/__init__.py index 67e0db82f42..d45d10130bd 100644 --- a/tests/components/reddit/__init__.py +++ b/tests/components/reddit/__init__.py @@ -1 +1 @@ -"""Tests for the the Reddit component.""" +"""Tests for the Reddit component.""" diff --git a/tests/components/zwave_js/fixtures/leviton_zw4sf_state.json b/tests/components/zwave_js/fixtures/leviton_zw4sf_state.json index 28b59a0b844..70af9c90a34 100644 --- a/tests/components/zwave_js/fixtures/leviton_zw4sf_state.json +++ b/tests/components/zwave_js/fixtures/leviton_zw4sf_state.json @@ -35,7 +35,7 @@ }, "metadata": { "inclusion": "Classic Inclusion To A Z-Wave Network\nFor older controllers Classic Inclusion is supported. Depending on the age of the controller the controller will need to be 3 to 35 feet from the device when including.\n1. To enter programming mode, hold the button for 7 seconds. The status light will turn amber, release and the status light will blink.\n2. Follow the Z-Wave controller instructions to enter inclusion mode.\n3. Tap the top or the paddle of the paddle one time. The status light will quickly flash green.\n4. The Z-Wave controller will confirm successful inclusion to the network", - "exclusion": "Exclusion From A Z-Wave Network\nWhen removing an fan speed controller from a Z-Wave network,\nbest practice is to use the exclusion command found in the Z-Wave\ncontroller.\n1. To enter programming mode, hold the button for 7 seconds. The\nstatus light will turn amber, release and the status light will blink.\n2. Follow Z-Wave controller directions to enter exclusion mode\n3. Tap the the top of the paddle 1 time. The status light will quickly\nflash green.\n4. The Z-Wave controller will remove the device from the network", + "exclusion": "Exclusion From A Z-Wave Network\nWhen removing an fan speed controller from a Z-Wave network,\nbest practice is to use the exclusion command found in the Z-Wave\ncontroller.\n1. To enter programming mode, hold the button for 7 seconds. The\nstatus light will turn amber, release and the status light will blink.\n2. Follow Z-Wave controller directions to enter exclusion mode\n3. Tap the top of the paddle 1 time. The status light will quickly\nflash green.\n4. The Z-Wave controller will remove the device from the network", "reset": "Factory Default\nWhen removing a fan speed controller from a network it is best\npractice to use the exclusion process. In situations where a device\nneeds to be returned to factory default follow the following steps. A\nreset should only be used when a controller is\ninoperable or missing.\n1. Hold the top of the paddle for 7 seconds, the status light will turn amber.\nContinue holding the top paddle for another 7 seconds (total of 14 seconds).\nThe status light will quickly flash red/ amber.\n2. Release the top of the paddle and the device will reset", "manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=product_documents/3832/Draft%20ZW4SF%203-25-20.pdf" } diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 6481b8e134d..b2e5c82e88a 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1591,7 +1591,7 @@ def test_config_entry_only_schema_cant_find_module() -> None: def test_config_entry_only_schema_no_hass( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: - """Test if the the hass context is not set in our context.""" + """Test if the hass context is not set in our context.""" with patch( "homeassistant.helpers.config_validation.async_get_hass", side_effect=HomeAssistantError, From 98132d1cd3b31f366aa66fb048190a1354cebd3d Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:13:35 +0100 Subject: [PATCH 1111/1691] Add Fyta integration (#110816) * Initial commit for fyta integration * Update __init__.py Delete BinarySensor for first PR * Update __init__.py Rewind wrongful deletion of comma * Delete homeassistant/components/fyta/binary_sensor.py Delete binary_sensor for first pr of integration * Update manifest.json Updated requirement to new version of fyta_cli 0.2.1, where bug in import of modules has been resolved. * Update requirements_test_all.txt adjust to updated manifest * Update requirements_all.txt adjust to updated manifest * Update test_config_flow.py * Update config_flow.py update file to correct error with _entry attribute * Fyta integration - update initial PR based on review in initial PR #110816 (#2) * adjustments to pass test for config_flow * backport of changes in intitial PR to dev * update text_config_flow * changes based on review in initial PR #110816 * Update homeassistant/components/fyta/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/config_flow.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/config_flow.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/coordinator.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/config_flow.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/strings.json Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Update homeassistant/components/fyta/strings.json Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Update homeassistant/components/fyta/manifest.json Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Adjustments based on PR-commet of Feb 19 (#3) * add test for config_flow.validate_input * update based on pr review * update based on pr review * further refinings based on PR review * Update tests/components/fyta/test_config_flow.py Co-authored-by: Joost Lekkerkerker * Update test_config_flow.py Update tests based on PR comment * Update homeassistant/components/fyta/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/sensor.py Co-authored-by: Joost Lekkerkerker * add handling and test for duplicate entry * Update homeassistant/components/fyta/coordinator.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/sensor.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/entity.py Co-authored-by: Joost Lekkerkerker * Update test_config_flow.py parametrize test for exceptions * Update config_flow.py Move _async_abort_entries_match, add arguments * Update coordinator.py * Update typing in coordinator.py * Update coordinator.py update typing * Update coordinator.py corrected typo * Update coordinator.py * Update entity.py * Update sensor.py * Update icons.json * Update homeassistant/components/fyta/entity.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/entity.py Co-authored-by: Joost Lekkerkerker * Update entity.py * Update test_config_flow.py * Update config_flow.py (change FlowResult to ConfigFlowResult) * Update config_flow.py * Update homeassistant/components/fyta/config_flow.py Co-authored-by: Robert Resch * Update homeassistant/components/fyta/config_flow.py Co-authored-by: Robert Resch * Update homeassistant/components/fyta/coordinator.py Co-authored-by: Robert Resch * Update coordinator.py * Update config_flow.py (typing FlowResult -> ConfigFlowResult) * Update config_flow.py * Aktualisieren von config_flow.py * remove coordinator entities * Update strings.json remove plant_number * Update icons.json remove plant_number * Update manifest.json Update requirement to latest fyta_cli version * Update requirements_all.txt * Update requirements_test_all.txt * Update homeassistant/components/fyta/sensor.py * Update homeassistant/components/fyta/sensor.py * Update homeassistant/components/fyta/coordinator.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/coordinator.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/coordinator.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/entity.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/strings.json Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/fyta/strings.json Co-authored-by: Joost Lekkerkerker * Update tests/components/fyta/test_config_flow.py Co-authored-by: Joost Lekkerkerker * Update tests/components/fyta/test_config_flow.py Co-authored-by: Joost Lekkerkerker * move test-helpers into conftest.py, adjust import of coordinator.py --------- Co-authored-by: Joost Lekkerkerker Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> Co-authored-by: Robert Resch --- .coveragerc | 4 + CODEOWNERS | 2 + homeassistant/components/fyta/__init__.py | 48 +++++++ homeassistant/components/fyta/config_flow.py | 64 +++++++++ homeassistant/components/fyta/const.py | 2 + homeassistant/components/fyta/coordinator.py | 55 +++++++ homeassistant/components/fyta/entity.py | 47 ++++++ homeassistant/components/fyta/icons.json | 27 ++++ homeassistant/components/fyta/manifest.json | 10 ++ homeassistant/components/fyta/sensor.py | 143 +++++++++++++++++++ homeassistant/components/fyta/strings.json | 83 +++++++++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/fyta/__init__.py | 1 + tests/components/fyta/conftest.py | 32 +++++ tests/components/fyta/test_config_flow.py | 121 ++++++++++++++++ 18 files changed, 652 insertions(+) create mode 100644 homeassistant/components/fyta/__init__.py create mode 100644 homeassistant/components/fyta/config_flow.py create mode 100644 homeassistant/components/fyta/const.py create mode 100644 homeassistant/components/fyta/coordinator.py create mode 100644 homeassistant/components/fyta/entity.py create mode 100644 homeassistant/components/fyta/icons.json create mode 100644 homeassistant/components/fyta/manifest.json create mode 100644 homeassistant/components/fyta/sensor.py create mode 100644 homeassistant/components/fyta/strings.json create mode 100644 tests/components/fyta/__init__.py create mode 100644 tests/components/fyta/conftest.py create mode 100644 tests/components/fyta/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 792cf9ddfe0..7a66f88ed87 100644 --- a/.coveragerc +++ b/.coveragerc @@ -461,6 +461,10 @@ omit = homeassistant/components/frontier_silicon/browse_media.py homeassistant/components/frontier_silicon/media_player.py homeassistant/components/futurenow/light.py + homeassistant/components/fyta/__init__.py + homeassistant/components/fyta/coordinator.py + homeassistant/components/fyta/entity.py + homeassistant/components/fyta/sensor.py homeassistant/components/garadget/cover.py homeassistant/components/garages_amsterdam/__init__.py homeassistant/components/garages_amsterdam/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 9cac303b92d..b81fade1402 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -453,6 +453,8 @@ build.json @home-assistant/supervisor /tests/components/frontier_silicon/ @wlcrs /homeassistant/components/fully_kiosk/ @cgarwood /tests/components/fully_kiosk/ @cgarwood +/homeassistant/components/fyta/ @dontinelli +/tests/components/fyta/ @dontinelli /homeassistant/components/garages_amsterdam/ @klaasnicolaas /tests/components/garages_amsterdam/ @klaasnicolaas /homeassistant/components/gardena_bluetooth/ @elupus diff --git a/homeassistant/components/fyta/__init__.py b/homeassistant/components/fyta/__init__.py new file mode 100644 index 00000000000..34399ce23ee --- /dev/null +++ b/homeassistant/components/fyta/__init__.py @@ -0,0 +1,48 @@ +"""Initialization of FYTA integration.""" +from __future__ import annotations + +import logging + +from fyta_cli.fyta_connector import FytaConnector + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import FytaCoordinator + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = [ + Platform.SENSOR, +] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up the Fyta integration.""" + + username = entry.data[CONF_USERNAME] + password = entry.data[CONF_PASSWORD] + + fyta = FytaConnector(username, password) + + coordinator = FytaCoordinator(hass, fyta) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Fyta entity.""" + + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/fyta/config_flow.py b/homeassistant/components/fyta/config_flow.py new file mode 100644 index 00000000000..698686a41a7 --- /dev/null +++ b/homeassistant/components/fyta/config_flow.py @@ -0,0 +1,64 @@ +"""Config flow for FYTA integration.""" +from __future__ import annotations + +import logging +from typing import Any + +from fyta_cli.fyta_connector import FytaConnector +from fyta_cli.fyta_exceptions import ( + FytaAuthentificationError, + FytaConnectionError, + FytaPasswordError, +) +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} +) + + +class FytaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Fyta.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Handle the initial step.""" + + errors = {} + if user_input: + self._async_abort_entries_match({CONF_USERNAME: user_input[CONF_USERNAME]}) + + fyta = FytaConnector(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + + try: + await fyta.login() + except FytaConnectionError: + errors["base"] = "cannot_connect" + except FytaAuthentificationError: + errors["base"] = "invalid_auth" + except FytaPasswordError: + errors["base"] = "invalid_auth" + errors[CONF_PASSWORD] = "password_error" + except Exception: # pylint: disable=broad-except + errors["base"] = "unknown" + else: + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + finally: + await fyta.client.close() + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/fyta/const.py b/homeassistant/components/fyta/const.py new file mode 100644 index 00000000000..86c3121089d --- /dev/null +++ b/homeassistant/components/fyta/const.py @@ -0,0 +1,2 @@ +"""Const for fyta integration.""" +DOMAIN = "fyta" diff --git a/homeassistant/components/fyta/coordinator.py b/homeassistant/components/fyta/coordinator.py new file mode 100644 index 00000000000..c132ee75e72 --- /dev/null +++ b/homeassistant/components/fyta/coordinator.py @@ -0,0 +1,55 @@ +"""Coordinator for FYTA integration.""" + +from datetime import datetime, timedelta +import logging +from typing import Any + +from fyta_cli.fyta_connector import FytaConnector +from fyta_cli.fyta_exceptions import ( + FytaAuthentificationError, + FytaConnectionError, + FytaPasswordError, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + + +class FytaCoordinator(DataUpdateCoordinator[dict[int, dict[str, Any]]]): + """Fyta custom coordinator.""" + + config_entry: ConfigEntry + + def __init__(self, hass: HomeAssistant, fyta: FytaConnector) -> None: + """Initialize my coordinator.""" + super().__init__( + hass, + _LOGGER, + name="FYTA Coordinator", + update_interval=timedelta(seconds=60), + ) + self.fyta = fyta + + async def _async_update_data( + self, + ) -> dict[int, dict[str, Any]]: + """Fetch data from API endpoint.""" + + if self.fyta.expiration is None or self.fyta.expiration < datetime.now(): + await self.renew_authentication() + + return await self.fyta.update_all_plants() + + async def renew_authentication(self) -> None: + """Renew access token for FYTA API.""" + + try: + await self.fyta.login() + except FytaConnectionError as ex: + raise ConfigEntryNotReady from ex + except (FytaAuthentificationError, FytaPasswordError) as ex: + raise ConfigEntryError from ex diff --git a/homeassistant/components/fyta/entity.py b/homeassistant/components/fyta/entity.py new file mode 100644 index 00000000000..a0bcf0a0084 --- /dev/null +++ b/homeassistant/components/fyta/entity.py @@ -0,0 +1,47 @@ +"""Entities for FYTA integration.""" +from typing import Any + +from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import FytaCoordinator + + +class FytaPlantEntity(CoordinatorEntity[FytaCoordinator]): + """Base Fyta Plant entity.""" + + _attr_has_entity_name = True + + def __init__( + self, + coordinator: FytaCoordinator, + entry: ConfigEntry, + description: SensorEntityDescription, + plant_id: int, + ) -> None: + """Initialize the Fyta sensor.""" + super().__init__(coordinator) + + self.plant_id = plant_id + self._attr_unique_id = f"{entry.entry_id}-{plant_id}-{description.key}" + self._attr_device_info = DeviceInfo( + manufacturer="Fyta", + model="Plant", + identifiers={(DOMAIN, f"{entry.entry_id}-{plant_id}")}, + name=self.plant.get("name"), + sw_version=self.plant.get("sw_version"), + ) + self.entity_description = description + + @property + def plant(self) -> dict[str, Any]: + """Get plant data.""" + return self.coordinator.data[self.plant_id] + + @property + def available(self) -> bool: + """Test if entity is available.""" + return super().available and self.plant_id in self.coordinator.data diff --git a/homeassistant/components/fyta/icons.json b/homeassistant/components/fyta/icons.json new file mode 100644 index 00000000000..b96eeb15e62 --- /dev/null +++ b/homeassistant/components/fyta/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "sensor": { + "status": { + "default": "mdi:flower" + }, + "temperature_status": { + "default": "mdi:thermometer-lines" + }, + "light_status": { + "default": "mdi:sun-clock-outline" + }, + "moisture_status": { + "default": "mdi:water-percent-alert" + }, + "salinity_status": { + "default": "mdi:sprout-outline" + }, + "light": { + "default": "mdi:weather-sunny" + }, + "salinity": { + "default": "mdi:sprout-outline" + } + } + } +} diff --git a/homeassistant/components/fyta/manifest.json b/homeassistant/components/fyta/manifest.json new file mode 100644 index 00000000000..a93a76a9e1d --- /dev/null +++ b/homeassistant/components/fyta/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "fyta", + "name": "FYTA", + "codeowners": ["@dontinelli"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/fyta", + "integration_type": "hub", + "iot_class": "cloud_polling", + "requirements": ["fyta_cli==0.3.3"] +} diff --git a/homeassistant/components/fyta/sensor.py b/homeassistant/components/fyta/sensor.py new file mode 100644 index 00000000000..ae1c1bec272 --- /dev/null +++ b/homeassistant/components/fyta/sensor.py @@ -0,0 +1,143 @@ +"""Summary data from Fyta.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime +from typing import Final + +from fyta_cli.fyta_connector import PLANT_STATUS + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import FytaCoordinator +from .entity import FytaPlantEntity + + +@dataclass(frozen=True) +class FytaSensorEntityDescription(SensorEntityDescription): + """Describes Fyta sensor entity.""" + + value_fn: Callable[[str | int | float | datetime], str | int | float | datetime] = ( + lambda value: value + ) + + +PLANT_STATUS_LIST: list[str] = ["too_low", "low", "perfect", "high", "too_high"] + +SENSORS: Final[list[FytaSensorEntityDescription]] = [ + FytaSensorEntityDescription( + key="scientific_name", + translation_key="scientific_name", + ), + FytaSensorEntityDescription( + key="status", + translation_key="plant_status", + device_class=SensorDeviceClass.ENUM, + options=PLANT_STATUS_LIST, + value_fn=lambda value: PLANT_STATUS[value], + ), + FytaSensorEntityDescription( + key="temperature_status", + translation_key="temperature_status", + device_class=SensorDeviceClass.ENUM, + options=PLANT_STATUS_LIST, + value_fn=lambda value: PLANT_STATUS[value], + ), + FytaSensorEntityDescription( + key="light_status", + translation_key="light_status", + device_class=SensorDeviceClass.ENUM, + options=PLANT_STATUS_LIST, + value_fn=lambda value: PLANT_STATUS[value], + ), + FytaSensorEntityDescription( + key="moisture_status", + translation_key="moisture_status", + device_class=SensorDeviceClass.ENUM, + options=PLANT_STATUS_LIST, + value_fn=lambda value: PLANT_STATUS[value], + ), + FytaSensorEntityDescription( + key="salinity_status", + translation_key="salinity_status", + device_class=SensorDeviceClass.ENUM, + options=PLANT_STATUS_LIST, + value_fn=lambda value: PLANT_STATUS[value], + ), + FytaSensorEntityDescription( + key="temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + FytaSensorEntityDescription( + key="light", + translation_key="light", + native_unit_of_measurement="mol/d", + state_class=SensorStateClass.MEASUREMENT, + ), + FytaSensorEntityDescription( + key="moisture", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.MOISTURE, + state_class=SensorStateClass.MEASUREMENT, + ), + FytaSensorEntityDescription( + key="salinity", + translation_key="salinity", + native_unit_of_measurement="mS/cm", + state_class=SensorStateClass.MEASUREMENT, + ), + FytaSensorEntityDescription( + key="ph", + device_class=SensorDeviceClass.PH, + state_class=SensorStateClass.MEASUREMENT, + ), + FytaSensorEntityDescription( + key="battery_level", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the FYTA sensors.""" + coordinator: FytaCoordinator = hass.data[DOMAIN][entry.entry_id] + + plant_entities = [ + FytaPlantSensor(coordinator, entry, sensor, plant_id) + for plant_id in coordinator.fyta.plant_list + for sensor in SENSORS + if sensor.key in coordinator.data[plant_id] + ] + + async_add_entities(plant_entities) + + +class FytaPlantSensor(FytaPlantEntity, SensorEntity): + """Represents a Fyta sensor.""" + + entity_description: FytaSensorEntityDescription + + @property + def native_value(self) -> str | int | float | datetime: + """Return the state for this sensor.""" + + val = self.plant[self.entity_description.key] + return self.entity_description.value_fn(val) diff --git a/homeassistant/components/fyta/strings.json b/homeassistant/components/fyta/strings.json new file mode 100644 index 00000000000..6d4fe68a86c --- /dev/null +++ b/homeassistant/components/fyta/strings.json @@ -0,0 +1,83 @@ +{ + "config": { + "step": { + "user": { + "title": "Credentials for FYTA API", + "description": "Provide username and password to connect to the FYTA server", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "password_error": "Invalid password", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "entity": { + "sensor": { + "scientific_name": { + "name": "Scientific name" + }, + "plant_status": { + "name": "Plant state", + "state": { + "too_low": "Too low", + "low": "Low", + "perfect": "Perfect", + "high": "High", + "too_high": "Too high" + } + }, + "temperature_status": { + "name": "Temperature state", + "state": { + "too_low": "[%key:component::fyta::entity::sensor::plant_status::state::too_low%]", + "low": "[%key:component::fyta::entity::sensor::plant_status::state::low%]", + "perfect": "[%key:component::fyta::entity::sensor::plant_status::state::perfect%]", + "high": "[%key:component::fyta::entity::sensor::plant_status::state::high%]", + "too_high": "[%key:component::fyta::entity::sensor::plant_status::state::too_high%]" + } + }, + "light_status": { + "name": "Light state", + "state": { + "too_low": "[%key:component::fyta::entity::sensor::plant_status::state::too_low%]", + "low": "[%key:component::fyta::entity::sensor::plant_status::state::low%]", + "perfect": "[%key:component::fyta::entity::sensor::plant_status::state::perfect%]", + "high": "[%key:component::fyta::entity::sensor::plant_status::state::high%]", + "too_high": "[%key:component::fyta::entity::sensor::plant_status::state::too_high%]" + } + }, + "moisture_status": { + "name": "Moisture state", + "state": { + "too_low": "[%key:component::fyta::entity::sensor::plant_status::state::too_low%]", + "low": "[%key:component::fyta::entity::sensor::plant_status::state::low%]", + "perfect": "[%key:component::fyta::entity::sensor::plant_status::state::perfect%]", + "high": "[%key:component::fyta::entity::sensor::plant_status::state::high%]", + "too_high": "[%key:component::fyta::entity::sensor::plant_status::state::too_high%]" + } + }, + "salinity_status": { + "name": "Salinity state", + "state": { + "too_low": "[%key:component::fyta::entity::sensor::plant_status::state::too_low%]", + "low": "[%key:component::fyta::entity::sensor::plant_status::state::low%]", + "perfect": "[%key:component::fyta::entity::sensor::plant_status::state::perfect%]", + "high": "[%key:component::fyta::entity::sensor::plant_status::state::high%]", + "too_high": "[%key:component::fyta::entity::sensor::plant_status::state::too_high%]" + } + }, + "light": { + "name": "Light" + }, + "salinity": { + "name": "Salinity" + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a049e2ca108..957a7696055 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -179,6 +179,7 @@ FLOWS = { "fronius", "frontier_silicon", "fully_kiosk", + "fyta", "garages_amsterdam", "gardena_bluetooth", "gdacs", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 1de015ac780..2c82810e072 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2049,6 +2049,12 @@ "config_flow": false, "iot_class": "local_polling" }, + "fyta": { + "name": "FYTA", + "integration_type": "hub", + "config_flow": true, + "iot_class": "cloud_polling" + }, "garadget": { "name": "Garadget", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index 4ee5604ad47..7948214c64c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -895,6 +895,9 @@ freesms==0.2.0 # homeassistant.components.fritzbox_callmonitor fritzconnection[qr]==1.13.2 +# homeassistant.components.fyta +fyta_cli==0.3.3 + # homeassistant.components.google_translate gTTS==2.2.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 18baa476413..1b75ae9aaa2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -727,6 +727,9 @@ freebox-api==1.1.0 # homeassistant.components.fritzbox_callmonitor fritzconnection[qr]==1.13.2 +# homeassistant.components.fyta +fyta_cli==0.3.3 + # homeassistant.components.google_translate gTTS==2.2.4 diff --git a/tests/components/fyta/__init__.py b/tests/components/fyta/__init__.py new file mode 100644 index 00000000000..cdc2cf63b0d --- /dev/null +++ b/tests/components/fyta/__init__.py @@ -0,0 +1 @@ +"""Tests for the Fyta integration.""" diff --git a/tests/components/fyta/conftest.py b/tests/components/fyta/conftest.py new file mode 100644 index 00000000000..40ab4925a47 --- /dev/null +++ b/tests/components/fyta/conftest.py @@ -0,0 +1,32 @@ +"""Test helpers.""" +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + +from .test_config_flow import ACCESS_TOKEN, EXPIRATION + + +@pytest.fixture +def mock_fyta(): + """Build a fixture for the Fyta API that connects successfully and returns one device.""" + + mock_fyta_api = AsyncMock() + with patch( + "homeassistant.components.fyta.config_flow.FytaConnector", + return_value=mock_fyta_api, + ) as mock_fyta_api: + mock_fyta_api.return_value.login.return_value = { + "access_token": ACCESS_TOKEN, + "expiration": EXPIRATION, + } + yield mock_fyta_api + + +@pytest.fixture +def mock_setup_entry() -> Generator[AsyncMock, None, None]: + """Override async_setup_entry.""" + with patch( + "homeassistant.components.fyta.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry diff --git a/tests/components/fyta/test_config_flow.py b/tests/components/fyta/test_config_flow.py new file mode 100644 index 00000000000..1fdb9b6109c --- /dev/null +++ b/tests/components/fyta/test_config_flow.py @@ -0,0 +1,121 @@ +"""Test the fyta config flow.""" +from datetime import datetime +from unittest.mock import AsyncMock + +from fyta_cli.fyta_exceptions import ( + FytaAuthentificationError, + FytaConnectionError, + FytaPasswordError, +) +import pytest + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.fyta.const import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +USERNAME = "fyta_user" +PASSWORD = "fyta_pass" +ACCESS_TOKEN = "123xyz" +EXPIRATION = datetime.now() + + +async def test_user_flow( + hass: HomeAssistant, mock_fyta: AsyncMock, mock_setup_entry +) -> None: + """Test we get the form.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + await hass.async_block_till_done() + + assert result2["type"] == "create_entry" + assert result2["title"] == USERNAME + assert result2["data"] == {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + ("exception", "error"), + [ + (FytaConnectionError, {"base": "cannot_connect"}), + (FytaAuthentificationError, {"base": "invalid_auth"}), + (FytaPasswordError, {"base": "invalid_auth", CONF_PASSWORD: "password_error"}), + (Exception, {"base": "unknown"}), + ], +) +async def test_form_exceptions( + hass: HomeAssistant, + exception: Exception, + error: dict[str, str], + mock_fyta: AsyncMock, + mock_setup_entry, +) -> None: + """Test we can handle Form exceptions.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_fyta.return_value.login.side_effect = exception + + # tests with connection error + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == error + + mock_fyta.return_value.login.side_effect = None + + # tests with all information provided + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == USERNAME + assert result["data"][CONF_USERNAME] == USERNAME + assert result["data"][CONF_PASSWORD] == PASSWORD + + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_duplicate_entry(hass: HomeAssistant, mock_fyta: AsyncMock) -> None: + """Test duplicate setup handling.""" + entry = MockConfigEntry( + domain=DOMAIN, + title=USERNAME, + data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "already_configured" From 066594b25ba4098a7519cacb806b4a49dc915b62 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 18:19:14 +0100 Subject: [PATCH 1112/1691] Use Volume Flow Rate device class in BTHome (#113530) --- homeassistant/components/bthome/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/bthome/sensor.py b/homeassistant/components/bthome/sensor.py index c3848ac976f..179979707b2 100644 --- a/homeassistant/components/bthome/sensor.py +++ b/homeassistant/components/bthome/sensor.py @@ -334,6 +334,7 @@ SENSOR_DESCRIPTIONS = { Units.VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, ): SensorEntityDescription( key=f"{BTHomeSensorDeviceClass.VOLUME_FLOW_RATE}_{Units.VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR}", + device_class=SensorDeviceClass.VOLUME_FLOW_RATE, native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, state_class=SensorStateClass.MEASUREMENT, ), From 66aa2c038a634cbfe4391aa557831d7ae53c307c Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:22:17 +0100 Subject: [PATCH 1113/1691] Freeze timezone in bmw_connected_drive tests (#113533) Freeze timezone in bmw_connected_drive --- .../bmw_connected_drive/test_diagnostics.py | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/tests/components/bmw_connected_drive/test_diagnostics.py b/tests/components/bmw_connected_drive/test_diagnostics.py index 5b22fdcf71d..2f58bc0e4a0 100644 --- a/tests/components/bmw_connected_drive/test_diagnostics.py +++ b/tests/components/bmw_connected_drive/test_diagnostics.py @@ -1,8 +1,6 @@ """Test BMW diagnostics.""" import datetime -import os -import time import pytest from syrupy.assertion import SnapshotAssertion @@ -20,7 +18,7 @@ from tests.components.diagnostics import ( from tests.typing import ClientSessionGenerator -@pytest.mark.freeze_time(datetime.datetime(2022, 7, 10, 11)) +@pytest.mark.freeze_time(datetime.datetime(2022, 7, 10, 11, tzinfo=datetime.UTC)) async def test_config_entry_diagnostics( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -29,10 +27,6 @@ async def test_config_entry_diagnostics( ) -> None: """Test config entry diagnostics.""" - # Make sure that local timezone for test is UTC - os.environ["TZ"] = "UTC" - time.tzset() - mock_config_entry = await setup_mocked_integration(hass) diagnostics = await get_diagnostics_for_config_entry( @@ -42,7 +36,7 @@ async def test_config_entry_diagnostics( assert diagnostics == snapshot -@pytest.mark.freeze_time(datetime.datetime(2022, 7, 10, 11)) +@pytest.mark.freeze_time(datetime.datetime(2022, 7, 10, 11, tzinfo=datetime.UTC)) async def test_device_diagnostics( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -52,10 +46,6 @@ async def test_device_diagnostics( ) -> None: """Test device diagnostics.""" - # Make sure that local timezone for test is UTC - os.environ["TZ"] = "UTC" - time.tzset() - mock_config_entry = await setup_mocked_integration(hass) reg_device = device_registry.async_get_device( @@ -70,7 +60,7 @@ async def test_device_diagnostics( assert diagnostics == snapshot -@pytest.mark.freeze_time(datetime.datetime(2022, 7, 10, 11)) +@pytest.mark.freeze_time(datetime.datetime(2022, 7, 10, 11, tzinfo=datetime.UTC)) async def test_device_diagnostics_vehicle_not_found( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -80,10 +70,6 @@ async def test_device_diagnostics_vehicle_not_found( ) -> None: """Test device diagnostics when the vehicle cannot be found.""" - # Make sure that local timezone for test is UTC - os.environ["TZ"] = "UTC" - time.tzset() - mock_config_entry = await setup_mocked_integration(hass) reg_device = device_registry.async_get_device( From be7c4295dcfedadf8ac5fe7562c509e1377f2b2d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 18:26:28 +0100 Subject: [PATCH 1114/1691] Fix MyUplink tests (#113534) --- tests/components/myuplink/conftest.py | 19 ++++++++++--------- tests/components/myuplink/test_number.py | 7 ++----- tests/components/myuplink/test_switch.py | 5 +---- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/components/myuplink/conftest.py b/tests/components/myuplink/conftest.py index 87f8b37726b..e08dc4255be 100644 --- a/tests/components/myuplink/conftest.py +++ b/tests/components/myuplink/conftest.py @@ -6,6 +6,7 @@ from typing import Any from unittest.mock import MagicMock, patch from myuplink import Device, DevicePoint, System +import orjson import pytest from homeassistant.components.application_credentials import ( @@ -107,22 +108,22 @@ def system_fixture(load_systems_file: str) -> list[System]: # Fixture group for device points API endpoint. -@pytest.fixture(scope="session") +@pytest.fixture def load_device_points_file() -> str: """Load fixture file for device-points endpoint.""" - return load_fixture("device_points_nibe_f730.json", DOMAIN) + return "device_points_nibe_f730.json" @pytest.fixture -def load_device_points_jv_file(): +def load_device_points_jv_file(load_device_points_file) -> str: """Load fixture file for device_points.""" - return json_loads(load_device_points_file) + return load_fixture(load_device_points_file, DOMAIN) @pytest.fixture -def device_points_fixture(load_device_points_file: str) -> list[DevicePoint]: - """Fixture for devce_points.""" - data = json_loads(load_device_points_file) +def device_points_fixture(load_device_points_jv_file: str) -> list[DevicePoint]: + """Fixture for device_points.""" + data = orjson.loads(load_device_points_jv_file) return [DevicePoint(point_data) for point_data in data] @@ -130,7 +131,7 @@ def device_points_fixture(load_device_points_file: str) -> list[DevicePoint]: def mock_myuplink_client( load_device_file, device_fixture, - load_device_points_file, + load_device_points_jv_file, device_points_fixture, system_fixture, load_systems_jv_file, @@ -150,7 +151,7 @@ def mock_myuplink_client( client.async_get_device_json.return_value = load_device_file client.async_get_device_points.return_value = device_points_fixture - client.async_get_device_points_json.return_value = load_device_points_file + client.async_get_device_points_json.return_value = load_device_points_jv_file yield client diff --git a/tests/components/myuplink/test_number.py b/tests/components/myuplink/test_number.py index 2f898b1db9e..899b2302b3c 100644 --- a/tests/components/myuplink/test_number.py +++ b/tests/components/myuplink/test_number.py @@ -5,15 +5,12 @@ from unittest.mock import MagicMock from aiohttp import ClientError import pytest -from homeassistant.components.myuplink.const import DOMAIN from homeassistant.components.number import SERVICE_SET_VALUE from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from tests.common import load_fixture - TEST_PLATFORM = Platform.NUMBER pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) @@ -91,7 +88,7 @@ async def test_api_failure( @pytest.mark.parametrize( "load_device_points_file", - [load_fixture("device_points_nibe_smo20.json", DOMAIN)], + ["device_points_nibe_smo20.json"], ) async def test_entity_registry_smo20( hass: HomeAssistant, @@ -101,5 +98,5 @@ async def test_entity_registry_smo20( ) -> None: """Test that the entities are registered in the entity registry.""" - entry = entity_registry.async_get("number.f730_cu_3x400v_change_in_curve") + entry = entity_registry.async_get("number.gotham_city_change_in_curve") assert entry.unique_id == "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-47028" diff --git a/tests/components/myuplink/test_switch.py b/tests/components/myuplink/test_switch.py index 7635cc43a05..efbc2c88371 100644 --- a/tests/components/myuplink/test_switch.py +++ b/tests/components/myuplink/test_switch.py @@ -5,7 +5,6 @@ from unittest.mock import MagicMock from aiohttp import ClientError import pytest -from homeassistant.components.myuplink.const import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -17,8 +16,6 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er -from tests.common import load_fixture - TEST_PLATFORM = Platform.SWITCH pytestmark = pytest.mark.parametrize("platforms", [(TEST_PLATFORM,)]) @@ -101,7 +98,7 @@ async def test_api_failure( @pytest.mark.parametrize( "load_device_points_file", - [load_fixture("device_points_nibe_smo20.json", DOMAIN)], + ["device_points_nibe_smo20.json"], ) async def test_entity_registry_smo20( hass: HomeAssistant, From 5b5ff92a05544d83288ad0a57b1cabc366ae7968 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 Mar 2024 18:58:49 +0100 Subject: [PATCH 1115/1691] Support configuring Axis to use HTTPS (#113271) --- homeassistant/components/axis/config_flow.py | 34 ++++++++++++-------- homeassistant/components/axis/hub/api.py | 9 +++++- tests/components/axis/test_config_flow.py | 19 +++++++++-- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 0fccd3654cf..38b5f5f7422 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -25,6 +25,7 @@ from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_PROTOCOL, CONF_USERNAME, ) from homeassistant.core import callback @@ -42,7 +43,9 @@ from .errors import AuthenticationRequired, CannotConnect from .hub import AxisHub, get_axis_api AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"} -DEFAULT_PORT = 80 +DEFAULT_PORT = 443 +DEFAULT_PROTOCOL = "https" +PROTOCOL_CHOICES = ["https", "http"] class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): @@ -74,11 +77,19 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): try: api = await get_axis_api(self.hass, MappingProxyType(user_input)) + except AuthenticationRequired: + errors["base"] = "invalid_auth" + + except CannotConnect: + errors["base"] = "cannot_connect" + + else: serial = api.vapix.serial_number await self.async_set_unique_id(format_mac(serial)) self._abort_if_unique_id_configured( updates={ + CONF_PROTOCOL: user_input[CONF_PROTOCOL], CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT], CONF_USERNAME: user_input[CONF_USERNAME], @@ -87,6 +98,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): ) self.config = { + CONF_PROTOCOL: user_input[CONF_PROTOCOL], CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT], CONF_USERNAME: user_input[CONF_USERNAME], @@ -96,13 +108,8 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): return await self._create_entry(serial) - except AuthenticationRequired: - errors["base"] = "invalid_auth" - - except CannotConnect: - errors["base"] = "cannot_connect" - data = self.discovery_schema or { + vol.Required(CONF_PROTOCOL): vol.In(PROTOCOL_CHOICES), vol.Required(CONF_HOST): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, @@ -149,6 +156,9 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): } self.discovery_schema = { + vol.Required( + CONF_PROTOCOL, default=entry_data.get(CONF_PROTOCOL, DEFAULT_PROTOCOL) + ): str, vol.Required(CONF_HOST, default=entry_data[CONF_HOST]): str, vol.Required(CONF_USERNAME, default=entry_data[CONF_USERNAME]): str, vol.Required(CONF_PASSWORD): str, @@ -166,7 +176,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): CONF_HOST: discovery_info.ip, CONF_MAC: format_mac(discovery_info.macaddress), CONF_NAME: discovery_info.hostname, - CONF_PORT: DEFAULT_PORT, + CONF_PORT: 80, } ) @@ -210,10 +220,7 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): await self.async_set_unique_id(discovery_info[CONF_MAC]) self._abort_if_unique_id_configured( - updates={ - CONF_HOST: discovery_info[CONF_HOST], - CONF_PORT: discovery_info[CONF_PORT], - } + updates={CONF_HOST: discovery_info[CONF_HOST]} ) self.context.update( @@ -227,10 +234,11 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): ) self.discovery_schema = { + vol.Required(CONF_PROTOCOL): vol.In(PROTOCOL_CHOICES), vol.Required(CONF_HOST, default=discovery_info[CONF_HOST]): str, vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_PORT, default=discovery_info[CONF_PORT]): int, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, } return await self.async_step_user() diff --git a/homeassistant/components/axis/hub/api.py b/homeassistant/components/axis/hub/api.py index e29219edbc2..0a784dd2c14 100644 --- a/homeassistant/components/axis/hub/api.py +++ b/homeassistant/components/axis/hub/api.py @@ -7,7 +7,13 @@ from typing import Any import axis from axis.configuration import Configuration -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.httpx_client import get_async_client @@ -29,6 +35,7 @@ async def get_axis_api( port=config[CONF_PORT], username=config[CONF_USERNAME], password=config[CONF_PASSWORD], + web_proto=config.get(CONF_PROTOCOL, "http"), ) ) diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 76b2d8e2f5c..e0514250a98 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -28,6 +28,7 @@ from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_PROTOCOL, CONF_USERNAME, ) from homeassistant.core import HomeAssistant @@ -65,6 +66,7 @@ async def test_flow_manual_configuration( result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -75,6 +77,7 @@ async def test_flow_manual_configuration( assert result["type"] == FlowResultType.CREATE_ENTRY assert result["title"] == f"M1065-LW - {MAC}" assert result["data"] == { + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -101,6 +104,7 @@ async def test_manual_configuration_update_configuration( result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "http", CONF_HOST: "2.3.4.5", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -130,6 +134,7 @@ async def test_flow_fails_faulty_credentials(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -156,6 +161,7 @@ async def test_flow_fails_cannot_connect(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -191,6 +197,7 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model( result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -201,6 +208,7 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model( assert result["type"] == FlowResultType.CREATE_ENTRY assert result["title"] == f"M1065-LW - {MAC}" assert result["data"] == { + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -233,17 +241,20 @@ async def test_reauth_flow_update_configuration( result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "https", CONF_HOST: "2.3.4.5", CONF_USERNAME: "user2", CONF_PASSWORD: "pass2", - CONF_PORT: 80, + CONF_PORT: 443, }, ) await hass.async_block_till_done() assert result["type"] == FlowResultType.ABORT assert result["reason"] == "already_configured" + assert mock_config_entry.data[CONF_PROTOCOL] == "https" assert mock_config_entry.data[CONF_HOST] == "2.3.4.5" + assert mock_config_entry.data[CONF_PORT] == 443 assert mock_config_entry.data[CONF_USERNAME] == "user2" assert mock_config_entry.data[CONF_PASSWORD] == "pass2" @@ -334,6 +345,7 @@ async def test_discovery_flow( result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -344,6 +356,7 @@ async def test_discovery_flow( assert result["type"] == FlowResultType.CREATE_ENTRY assert result["title"] == f"M1065-LW - {MAC}" assert result["data"] == { + CONF_PROTOCOL: "http", CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", @@ -430,7 +443,7 @@ async def test_discovered_device_already_configured( "presentationURL": "http://2.3.4.5:8080/", }, ), - 8080, + 80, ), ( SOURCE_ZEROCONF, @@ -443,7 +456,7 @@ async def test_discovered_device_already_configured( properties={"macaddress": MAC}, type="mock_type", ), - 8080, + 80, ), ], ) From b6a06f49b776da87be70c9ff9d336057dc6741d9 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 19:12:04 +0100 Subject: [PATCH 1116/1691] Use Volume Flow Rate device class in DROP connect (#113528) --- homeassistant/components/drop_connect/sensor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/drop_connect/sensor.py b/homeassistant/components/drop_connect/sensor.py index 249922a4b3c..0806737254e 100644 --- a/homeassistant/components/drop_connect/sensor.py +++ b/homeassistant/components/drop_connect/sensor.py @@ -20,6 +20,7 @@ from homeassistant.const import ( UnitOfPressure, UnitOfTemperature, UnitOfVolume, + UnitOfVolumeFlowRate, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -70,7 +71,8 @@ SENSORS: list[DROPSensorEntityDescription] = [ DROPSensorEntityDescription( key=CURRENT_FLOW_RATE, translation_key=CURRENT_FLOW_RATE, - native_unit_of_measurement="gpm", + device_class=SensorDeviceClass.VOLUME_FLOW_RATE, + native_unit_of_measurement=UnitOfVolumeFlowRate.GALLONS_PER_MINUTE, suggested_display_precision=1, value_fn=lambda device: device.drop_api.current_flow_rate(), state_class=SensorStateClass.MEASUREMENT, @@ -78,7 +80,8 @@ SENSORS: list[DROPSensorEntityDescription] = [ DROPSensorEntityDescription( key=PEAK_FLOW_RATE, translation_key=PEAK_FLOW_RATE, - native_unit_of_measurement="gpm", + device_class=SensorDeviceClass.VOLUME_FLOW_RATE, + native_unit_of_measurement=UnitOfVolumeFlowRate.GALLONS_PER_MINUTE, suggested_display_precision=1, value_fn=lambda device: device.drop_api.peak_flow_rate(), state_class=SensorStateClass.MEASUREMENT, From 422d4ea5b3ff00ac61dc84442087eb31427b96d0 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 19:12:38 +0100 Subject: [PATCH 1117/1691] Use Volume Flow Rate device class in Opentherm gw (#113531) --- homeassistant/components/opentherm_gw/const.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py index 65a684de224..74b856b4eaf 100644 --- a/homeassistant/components/opentherm_gw/const.py +++ b/homeassistant/components/opentherm_gw/const.py @@ -12,7 +12,7 @@ from homeassistant.const import ( UnitOfPressure, UnitOfTemperature, UnitOfTime, - UnitOfVolume, + UnitOfVolumeFlowRate, ) ATTR_GW_ID = "gateway_id" @@ -310,8 +310,8 @@ SENSOR_INFO: dict[str, list] = { [gw_vars.BOILER, gw_vars.THERMOSTAT], ], gw_vars.DATA_DHW_FLOW_RATE: [ - None, - f"{UnitOfVolume.LITERS}/{UnitOfTime.MINUTES}", + SensorDeviceClass.VOLUME_FLOW_RATE, + UnitOfVolumeFlowRate.LITERS_PER_MINUTE, "Hot Water Flow Rate {}", SENSOR_FLOAT_SUGGESTED_DISPLAY_PRECISION, [gw_vars.BOILER, gw_vars.THERMOSTAT], From 02d4bf007d7f37cb16f031e722184cc71706c416 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 15 Mar 2024 19:49:29 +0100 Subject: [PATCH 1118/1691] Do not allow modbus config without entities (#113516) --- homeassistant/components/modbus/modbus.py | 2 + homeassistant/components/modbus/strings.json | 4 + homeassistant/components/modbus/validators.py | 16 ++- tests/components/modbus/test_init.py | 117 ++++++++++++++++++ 4 files changed, 135 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 28249ce72ad..d955c6656f8 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -131,6 +131,8 @@ async def async_modbus_setup( if config[DOMAIN]: config[DOMAIN] = check_config(hass, config[DOMAIN]) + if not config[DOMAIN]: + return False if DOMAIN in hass.data and config[DOMAIN] == []: hubs = hass.data[DOMAIN] for name in hubs: diff --git a/homeassistant/components/modbus/strings.json b/homeassistant/components/modbus/strings.json index d4f649ac1e6..fd93185b891 100644 --- a/homeassistant/components/modbus/strings.json +++ b/homeassistant/components/modbus/strings.json @@ -93,6 +93,10 @@ "duplicate_entity_name": { "title": "Modbus {sub_1} is duplicate, second entry not loaded.", "description": "A entity name must be unique, Please correct the entry in your configuration.yaml file and restart Home Assistant to fix this issue." + }, + "no_entities": { + "title": "Modbus {sub_1} contain no entities, entry not loaded.", + "description": "Please add at least one entity to Modbus {sub_1} in your configuration.yaml file and restart Home Assistant to fix this issue." } } } diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index a91ac9be6b4..7de2ecbe604 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -490,10 +490,18 @@ def check_config(hass: HomeAssistant, config: dict) -> dict: else: entity_inx += 1 if no_entities: - err = f"Modbus {hub[CONF_NAME]} contain no entities, this will cause instability, please add at least one entity!" - _LOGGER.warning(err) - # Ensure timeout is not started/handled. - hub[CONF_TIMEOUT] = -1 + modbus_create_issue( + hass, + "no_entities", + [ + hub[CONF_NAME], + "", + "", + ], + f"Modbus {hub[CONF_NAME]} contain no entities, causing instability, entry not loaded", + ) + del config[hub_inx] + continue if hub[CONF_TIMEOUT] >= minimum_scan_interval: hub[CONF_TIMEOUT] = minimum_scan_interval - 1 _LOGGER.warning( diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index e55b9f4232d..44bf089f0d5 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -962,22 +962,46 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_RETRIES: 3, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: TCP, @@ -986,11 +1010,23 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_NAME: TEST_MODBUS_NAME, CONF_TIMEOUT: 30, CONF_DELAY: 10, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: UDP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: UDP, @@ -999,11 +1035,23 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_NAME: TEST_MODBUS_NAME, CONF_TIMEOUT: 30, CONF_DELAY: 10, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: RTUOVERTCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: RTUOVERTCP, @@ -1012,6 +1060,12 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_NAME: TEST_MODBUS_NAME, CONF_TIMEOUT: 30, CONF_DELAY: 10, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: SERIAL, @@ -1022,6 +1076,12 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_PARITY: "E", CONF_STOPBITS: 1, CONF_MSG_WAIT: 100, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: SERIAL, @@ -1034,12 +1094,24 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_NAME: TEST_MODBUS_NAME, CONF_TIMEOUT: 30, CONF_DELAY: 10, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_DELAY: 5, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, [ { @@ -1047,12 +1119,24 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_NAME: TEST_MODBUS_NAME, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, CONF_NAME: f"{TEST_MODBUS_NAME} 2", + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, { CONF_TYPE: SERIAL, @@ -1063,6 +1147,12 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: CONF_PARITY: "E", CONF_STOPBITS: 1, CONF_NAME: f"{TEST_MODBUS_NAME} 3", + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], }, ], { @@ -1351,6 +1441,12 @@ async def test_pymodbus_close_fail( CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], } ] } @@ -1373,6 +1469,12 @@ async def test_pymodbus_connect_fail( CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, CONF_PORT: TEST_PORT_TCP, + CONF_SENSORS: [ + { + CONF_NAME: "dummy", + CONF_ADDRESS: 9999, + } + ], } ] } @@ -1603,3 +1705,18 @@ async def test_integration_setup_failed( ) await hass.services.async_call(DOMAIN, SERVICE_RELOAD, blocking=True) await hass.async_block_till_done() + + +async def test_no_entities(hass: HomeAssistant) -> None: + """Run test for failing pymodbus constructor.""" + config = { + DOMAIN: [ + { + CONF_NAME: TEST_MODBUS_NAME, + CONF_TYPE: TCP, + CONF_HOST: TEST_MODBUS_HOST, + CONF_PORT: TEST_PORT_TCP, + } + ] + } + assert await async_setup_component(hass, DOMAIN, config) is False From 29f07260f916c276c3d4509b6ba84bf5f55da610 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Fri, 15 Mar 2024 13:56:13 -0500 Subject: [PATCH 1119/1691] Use single_config_entry in jellyfin manifest (#113202) --- homeassistant/components/jellyfin/config_flow.py | 3 --- homeassistant/components/jellyfin/manifest.json | 3 ++- homeassistant/components/jellyfin/strings.json | 1 - homeassistant/generated/integrations.json | 3 ++- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/jellyfin/config_flow.py b/homeassistant/components/jellyfin/config_flow.py index 5ccff4ffdb3..c6e447d18e8 100644 --- a/homeassistant/components/jellyfin/config_flow.py +++ b/homeassistant/components/jellyfin/config_flow.py @@ -51,9 +51,6 @@ class JellyfinConfigFlow(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a user defined configuration.""" - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - errors: dict[str, str] = {} if user_input is not None: diff --git a/homeassistant/components/jellyfin/manifest.json b/homeassistant/components/jellyfin/manifest.json index 990449364a7..19358cff17c 100644 --- a/homeassistant/components/jellyfin/manifest.json +++ b/homeassistant/components/jellyfin/manifest.json @@ -7,5 +7,6 @@ "integration_type": "service", "iot_class": "local_polling", "loggers": ["jellyfin_apiclient_python"], - "requirements": ["jellyfin-apiclient-python==1.9.2"] + "requirements": ["jellyfin-apiclient-python==1.9.2"], + "single_config_entry": true } diff --git a/homeassistant/components/jellyfin/strings.json b/homeassistant/components/jellyfin/strings.json index 3e8965da785..3e4c8066b77 100644 --- a/homeassistant/components/jellyfin/strings.json +++ b/homeassistant/components/jellyfin/strings.json @@ -23,7 +23,6 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", - "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 2c82810e072..4ce6ce6283c 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2904,7 +2904,8 @@ "name": "Jellyfin", "integration_type": "service", "config_flow": true, - "iot_class": "local_polling" + "iot_class": "local_polling", + "single_config_entry": true }, "jewish_calendar": { "name": "Jewish Calendar", From b7f7bed46cf38c1fbe1906c64715b12d98630cdf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 09:05:18 -1000 Subject: [PATCH 1120/1691] Import and create pyudev for usb in the executor (#113478) --- homeassistant/components/usb/__init__.py | 34 +++++++++++++++++------- tests/components/hassio/test_issues.py | 1 + tests/components/usb/test_init.py | 4 +-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/usb/__init__.py b/homeassistant/components/usb/__init__.py index f91603ce158..959a8f5894c 100644 --- a/homeassistant/components/usb/__init__.py +++ b/homeassistant/components/usb/__init__.py @@ -35,7 +35,7 @@ from .models import USBDevice from .utils import usb_device_from_port if TYPE_CHECKING: - from pyudev import Device + from pyudev import Device, MonitorObserver _LOGGER = logging.getLogger(__name__) @@ -228,6 +228,25 @@ class USBDiscovery: if info.get("docker"): return + if not ( + observer := await self.hass.async_add_executor_job( + self._get_monitor_observer + ) + ): + return + + def _stop_observer(event: Event) -> None: + observer.stop() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_observer) + self.observer_active = True + + def _get_monitor_observer(self) -> MonitorObserver | None: + """Get the monitor observer. + + This runs in the executor because the import + does blocking I/O. + """ from pyudev import ( # pylint: disable=import-outside-toplevel Context, Monitor, @@ -237,7 +256,7 @@ class USBDiscovery: try: context = Context() except (ImportError, OSError): - return + return None monitor = Monitor.from_netlink(context) try: @@ -246,17 +265,14 @@ class USBDiscovery: _LOGGER.debug( "Unable to setup pyudev filtering; This is expected on WSL: %s", ex ) - return + return None + observer = MonitorObserver( monitor, callback=self._device_discovered, name="usb-observer" ) + observer.start() - - def _stop_observer(event: Event) -> None: - observer.stop() - - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_observer) - self.observer_active = True + return observer def _device_discovered(self, device: Device) -> None: """Call when the observer discovers a new usb tty device.""" diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 6b0b2e5fdc4..6ff3df65908 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -590,6 +590,7 @@ async def test_supervisor_issues_initial_failure( with patch("homeassistant.components.hassio.issues.REQUEST_REFRESH_DELAY", new=0.1): result = await async_setup_component(hass, "hassio", {}) + await hass.async_block_till_done() assert result client = await hass_ws_client(hass) diff --git a/tests/components/usb/test_init.py b/tests/components/usb/test_init.py index dd6d9fff874..6d04516a26c 100644 --- a/tests/components/usb/test_init.py +++ b/tests/components/usb/test_init.py @@ -85,7 +85,7 @@ async def test_observer_discovery( def _create_mock_monitor_observer(monitor, callback, name): nonlocal mock_observer - hass.async_create_task(_mock_monitor_observer_callback(callback)) + hass.create_task(_mock_monitor_observer_callback(callback)) mock_observer = MagicMock() return mock_observer @@ -107,7 +107,7 @@ async def test_observer_discovery( hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() - assert mock_observer.mock_calls == [call.start(), call.stop()] + assert mock_observer.mock_calls == [call.start(), call.__bool__(), call.stop()] @pytest.mark.skipif( From e90388afd1c7ba3a851976d70f34e2acfcf4cec2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 Mar 2024 20:06:09 +0100 Subject: [PATCH 1121/1691] Remove old update of sensor unique id in deCONZ (#113527) --- homeassistant/components/deconz/sensor.py | 29 +---------------------- tests/components/deconz/test_sensor.py | 27 +-------------------- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 7f5495bdd98..8ea589ac7e0 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -48,14 +48,12 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -import homeassistant.helpers.entity_registry as er from homeassistant.helpers.typing import StateType import homeassistant.util.dt as dt_util -from .const import ATTR_DARK, ATTR_ON, DOMAIN as DECONZ_DOMAIN +from .const import ATTR_DARK, ATTR_ON from .deconz_device import DeconzDevice from .hub import DeconzHub, get_gateway_from_config_entry -from .util import serial_from_unique_id PROVIDES_EXTRA_ATTRIBUTES = ( "battery", @@ -291,29 +289,6 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = ( ) -@callback -def async_update_unique_id( - hass: HomeAssistant, unique_id: str, description: DeconzSensorDescription -) -> None: - """Update unique ID to always have a suffix. - - Introduced with release 2022.9. - """ - ent_reg = er.async_get(hass) - - new_unique_id = f"{unique_id}-{description.key}" - if ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, new_unique_id): - return - - if description.old_unique_id_suffix: - unique_id = ( - f"{serial_from_unique_id(unique_id)}-{description.old_unique_id_suffix}" - ) - - if entity_id := ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, unique_id): - ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -357,7 +332,6 @@ async def async_setup_entry( continue known_device_entities[description.key].add(unique_id) if no_sensor_data and description.key == "battery": - async_update_unique_id(hass, sensor.unique_id, description) DeconzBatteryTracker( sensor_id, gateway, description, async_add_entities ) @@ -366,7 +340,6 @@ async def async_setup_entry( if no_sensor_data: continue - async_update_unique_id(hass, sensor.unique_id, description) entities.append(DeconzSensor(sensor, gateway, description)) async_add_entities(entities) diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index 4cc1d349ef3..4950928f2e6 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -5,10 +5,7 @@ from unittest.mock import patch import pytest -from homeassistant.components.deconz.const import ( - CONF_ALLOW_CLIP_SENSOR, - DOMAIN as DECONZ_DOMAIN, -) +from homeassistant.components.deconz.const import CONF_ALLOW_CLIP_SENSOR from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, SensorDeviceClass, @@ -68,7 +65,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.bosch_air_quality_sensor", "unique_id": "00:12:4b:00:14:4d:00:07-02-fdef-air_quality", - "old_unique_id": "00:12:4b:00:14:4d:00:07-02-fdef", "state": "poor", "entity_category": None, "device_class": None, @@ -106,7 +102,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.bosch_air_quality_sensor_ppb", "unique_id": "00:12:4b:00:14:4d:00:07-02-fdef-air_quality_ppb", - "old_unique_id": "00:12:4b:00:14:4d:00:07-ppb", "state": "809", "entity_category": None, "device_class": None, @@ -265,7 +260,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.fyrtur_block_out_roller_blind_battery", "unique_id": "00:0d:6f:ff:fe:01:23:45-01-0001-battery", - "old_unique_id": "00:0d:6f:ff:fe:01:23:45-battery", "state": "100", "entity_category": EntityCategory.DIAGNOSTIC, "device_class": SensorDeviceClass.BATTERY, @@ -302,7 +296,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.consumption_15", "unique_id": "00:0d:6f:00:0b:7a:64:29-01-0702-consumption", - "old_unique_id": "00:0d:6f:00:0b:7a:64:29-01-0702", "state": "11.342", "entity_category": None, "device_class": SensorDeviceClass.ENERGY, @@ -384,7 +377,6 @@ TEST_DATA = [ "device_count": 2, "entity_id": "sensor.fsm_state_motion_stair", "unique_id": "fsm-state-1520195376277-status", - "old_unique_id": "fsm-state-1520195376277", "state": "0", "entity_category": None, "device_class": None, @@ -423,7 +415,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.mi_temperature_1", "unique_id": "00:15:8d:00:02:45:dc:53-01-0405-humidity", - "old_unique_id": "00:15:8d:00:02:45:dc:53-01-0405", "state": "35.55", "entity_category": None, "device_class": SensorDeviceClass.HUMIDITY, @@ -513,7 +504,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.motion_sensor_4", "unique_id": "00:17:88:01:03:28:8c:9b-02-0400-light_level", - "old_unique_id": "00:17:88:01:03:28:8c:9b-02-0400", "state": "5.0", "entity_category": None, "device_class": SensorDeviceClass.ILLUMINANCE, @@ -605,7 +595,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.power_16", "unique_id": "00:0d:6f:00:0b:7a:64:29-01-0b04-power", - "old_unique_id": "00:0d:6f:00:0b:7a:64:29-01-0b04", "state": "64", "entity_category": None, "device_class": SensorDeviceClass.POWER, @@ -648,7 +637,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.mi_temperature_1", "unique_id": "00:15:8d:00:02:45:dc:53-01-0403-pressure", - "old_unique_id": "00:15:8d:00:02:45:dc:53-01-0403", "state": "1010", "entity_category": None, "device_class": SensorDeviceClass.PRESSURE, @@ -690,7 +678,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.mi_temperature_1", "unique_id": "00:15:8d:00:02:45:dc:53-01-0402-temperature", - "old_unique_id": "00:15:8d:00:02:45:dc:53-01-0402", "state": "21.82", "entity_category": None, "device_class": SensorDeviceClass.TEMPERATURE, @@ -737,7 +724,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.etrv_sejour", "unique_id": "cc:cc:cc:ff:fe:38:4d:b3-01-000a-last_set", - "old_unique_id": "cc:cc:cc:ff:fe:38:4d:b3-01-000a", "state": "2020-11-19T08:07:08+00:00", "entity_category": None, "device_class": SensorDeviceClass.TIMESTAMP, @@ -777,7 +763,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.alarm_10_temperature", "unique_id": "00:15:8d:00:02:b5:d1:80-01-0500-internal_temperature", - "old_unique_id": "00:15:8d:00:02:b5:d1:80-temperature", "state": "26.0", "entity_category": None, "device_class": SensorDeviceClass.TEMPERATURE, @@ -819,7 +804,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "sensor.dimmer_switch_3_battery", "unique_id": "00:17:88:01:02:0e:32:a3-02-fc00-battery", - "old_unique_id": "00:17:88:01:02:0e:32:a3-battery", "state": "90", "entity_category": EntityCategory.DIAGNOSTIC, "device_class": SensorDeviceClass.BATTERY, @@ -851,15 +835,6 @@ async def test_sensors( ) -> None: """Test successful creation of sensor entities.""" - # Create entity entry to migrate to new unique ID - if "old_unique_id" in expected: - entity_registry.async_get_or_create( - SENSOR_DOMAIN, - DECONZ_DOMAIN, - expected["old_unique_id"], - suggested_object_id=expected["entity_id"].replace("sensor.", ""), - ) - with patch.dict(DECONZ_WEB_REQUEST, {"sensors": {"1": sensor_data}}): config_entry = await setup_deconz_integration( hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: True} From 9c2c7f1a451b8056604225e33f588d174591265a Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 Mar 2024 20:08:18 +0100 Subject: [PATCH 1122/1691] Remove old update of number unique id in deCONZ (#113524) --- homeassistant/components/deconz/number.py | 24 ----------------------- tests/components/deconz/test_number.py | 11 ----------- 2 files changed, 35 deletions(-) diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index a829831b511..6464a99fd7a 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -21,12 +21,9 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -import homeassistant.helpers.entity_registry as er -from .const import DOMAIN as DECONZ_DOMAIN from .deconz_device import DeconzDevice from .hub import DeconzHub, get_gateway_from_config_entry -from .util import serial_from_unique_id T = TypeVar("T", Presence, PydeconzSensorBase) @@ -70,25 +67,6 @@ ENTITY_DESCRIPTIONS: tuple[DeconzNumberDescription, ...] = ( ) -@callback -def async_update_unique_id( - hass: HomeAssistant, unique_id: str, description: DeconzNumberDescription -) -> None: - """Update unique ID base to be on full unique ID rather than device serial. - - Introduced with release 2022.11. - """ - ent_reg = er.async_get(hass) - - new_unique_id = f"{unique_id}-{description.key}" - if ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, new_unique_id): - return - - unique_id = f"{serial_from_unique_id(unique_id)}-{description.key}" - if entity_id := ent_reg.async_get_entity_id(DOMAIN, DECONZ_DOMAIN, unique_id): - ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) - - async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -109,8 +87,6 @@ async def async_setup_entry( or description.value_fn(sensor) is None ): continue - if description.key == "delay": - async_update_unique_id(hass, sensor.unique_id, description) async_add_entities([DeconzNumber(sensor, gateway, description)]) gateway.register_platform_add_device_callback( diff --git a/tests/components/deconz/test_number.py b/tests/components/deconz/test_number.py index 19d1cdf2bea..3f86182e032 100644 --- a/tests/components/deconz/test_number.py +++ b/tests/components/deconz/test_number.py @@ -4,7 +4,6 @@ from unittest.mock import patch import pytest -from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.number import ( ATTR_VALUE, DOMAIN as NUMBER_DOMAIN, @@ -50,7 +49,6 @@ TEST_DATA = [ "device_count": 3, "entity_id": "number.presence_sensor_delay", "unique_id": "00:00:00:00:00:00:00:00-00-delay", - "old_unique_id": "00:00:00:00:00:00:00:00-delay", "state": "0", "entity_category": EntityCategory.CONFIG, "attributes": { @@ -120,15 +118,6 @@ async def test_number_entities( ) -> None: """Test successful creation of number entities.""" - # Create entity entry to migrate to new unique ID - if "old_unique_id" in expected: - entity_registry.async_get_or_create( - NUMBER_DOMAIN, - DECONZ_DOMAIN, - expected["old_unique_id"], - suggested_object_id=expected["entity_id"].replace("number.", ""), - ) - with patch.dict(DECONZ_WEB_REQUEST, {"sensors": {"0": sensor_data}}): config_entry = await setup_deconz_integration(hass, aioclient_mock) From 3ba29c361a26d4f67502f31ae24a3a88f412f7f2 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 Mar 2024 20:09:44 +0100 Subject: [PATCH 1123/1691] Remove old update of group unique id in deCONZ (#112533) --- homeassistant/components/deconz/__init__.py | 46 +---------- tests/components/deconz/test_init.py | 87 +-------------------- 2 files changed, 4 insertions(+), 129 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 776ab8e830f..ae9bd0db0fd 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -2,21 +2,13 @@ from __future__ import annotations -from typing import cast - from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_API_KEY, - CONF_HOST, - CONF_PORT, - EVENT_HOMEASSISTANT_STOP, -) -from homeassistant.core import HomeAssistant, callback +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -import homeassistant.helpers.entity_registry as er from .config_flow import get_master_gateway -from .const import CONF_GROUP_ID_BASE, CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS +from .const import CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect from .hub import DeconzHub, get_deconz_api @@ -31,8 +23,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b """ hass.data.setdefault(DOMAIN, {}) - await async_update_group_unique_id(hass, config_entry) - if not config_entry.options: await async_update_master_gateway(hass, config_entry) @@ -98,33 +88,3 @@ async def async_update_master_gateway( options = {**config_entry.options, CONF_MASTER_GATEWAY: master} hass.config_entries.async_update_entry(config_entry, options=options) - - -async def async_update_group_unique_id( - hass: HomeAssistant, config_entry: ConfigEntry -) -> None: - """Update unique ID entities based on deCONZ groups.""" - if not (group_id_base := config_entry.data.get(CONF_GROUP_ID_BASE)): - return - - old_unique_id = cast(str, group_id_base) - new_unique_id = cast(str, config_entry.unique_id) - - @callback - def update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None: - """Update unique ID of entity entry.""" - if f"{old_unique_id}-" not in entity_entry.unique_id: - return None - return { - "new_unique_id": entity_entry.unique_id.replace( - old_unique_id, new_unique_id - ) - } - - await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id) - data = { - CONF_API_KEY: config_entry.data[CONF_API_KEY], - CONF_HOST: config_entry.data[CONF_HOST], - CONF_PORT: config_entry.data[CONF_PORT], - } - hass.config_entries.async_update_entry(config_entry, data=data) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 64b1594faea..3405a1973c0 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -7,21 +7,13 @@ from homeassistant.components.deconz import ( DeconzHub, async_setup_entry, async_unload_entry, - async_update_group_unique_id, -) -from homeassistant.components.deconz.const import ( - CONF_GROUP_ID_BASE, - DOMAIN as DECONZ_DOMAIN, ) +from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.errors import AuthenticationRequired, CannotConnect -from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration -from tests.common import MockConfigEntry from tests.test_util.aiohttp import AiohttpClientMocker ENTRY1_HOST = "1.2.3.4" @@ -158,80 +150,3 @@ async def test_unload_entry_multiple_gateways_parallel( ) assert len(hass.data[DECONZ_DOMAIN]) == 0 - - -async def test_update_group_unique_id( - hass: HomeAssistant, entity_registry: er.EntityRegistry -) -> None: - """Test successful migration of entry data.""" - old_unique_id = "123" - new_unique_id = "1234" - entry = MockConfigEntry( - domain=DECONZ_DOMAIN, - unique_id=new_unique_id, - data={ - CONF_API_KEY: "1", - CONF_HOST: "2", - CONF_GROUP_ID_BASE: old_unique_id, - CONF_PORT: "3", - }, - ) - entry.add_to_hass(hass) - - # Create entity entry to migrate to new unique ID - entity_registry.async_get_or_create( - LIGHT_DOMAIN, - DECONZ_DOMAIN, - f"{old_unique_id}-OLD", - suggested_object_id="old", - config_entry=entry, - ) - # Create entity entry with new unique ID - entity_registry.async_get_or_create( - LIGHT_DOMAIN, - DECONZ_DOMAIN, - f"{new_unique_id}-NEW", - suggested_object_id="new", - config_entry=entry, - ) - - await async_update_group_unique_id(hass, entry) - - assert entry.data == {CONF_API_KEY: "1", CONF_HOST: "2", CONF_PORT: "3"} - assert ( - entity_registry.async_get(f"{LIGHT_DOMAIN}.old").unique_id - == f"{new_unique_id}-OLD" - ) - assert ( - entity_registry.async_get(f"{LIGHT_DOMAIN}.new").unique_id - == f"{new_unique_id}-NEW" - ) - - -async def test_update_group_unique_id_no_legacy_group_id( - hass: HomeAssistant, entity_registry: er.EntityRegistry -) -> None: - """Test migration doesn't trigger without old legacy group id in entry data.""" - old_unique_id = "123" - new_unique_id = "1234" - entry = MockConfigEntry( - domain=DECONZ_DOMAIN, - unique_id=new_unique_id, - data={}, - ) - - # Create entity entry to migrate to new unique ID - entity_registry.async_get_or_create( - LIGHT_DOMAIN, - DECONZ_DOMAIN, - f"{old_unique_id}-OLD", - suggested_object_id="old", - config_entry=entry, - ) - - await async_update_group_unique_id(hass, entry) - - assert ( - entity_registry.async_get(f"{LIGHT_DOMAIN}.old").unique_id - == f"{old_unique_id}-OLD" - ) From 397bde4652bafefd611243455cfc0c578814ed32 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 15 Mar 2024 21:20:39 +0100 Subject: [PATCH 1124/1691] Add missing continue in legacy recorder history function (#113535) Co-authored-by: Jan Bouwhuis --- homeassistant/components/recorder/history/legacy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/recorder/history/legacy.py b/homeassistant/components/recorder/history/legacy.py index 6a66599e11b..ad9505e1af2 100644 --- a/homeassistant/components/recorder/history/legacy.py +++ b/homeassistant/components/recorder/history/legacy.py @@ -814,6 +814,7 @@ def _sorted_states_to_dict( } ) prev_state = state + continue for row in group: if (state := row[state_idx]) != prev_state: From b261f124d63ed668feb003afb2a7d59a12cc316c Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Fri, 15 Mar 2024 21:59:30 +0100 Subject: [PATCH 1125/1691] fix mqtt siren test asserts (#113550) --- tests/components/mqtt/test_siren.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/mqtt/test_siren.py b/tests/components/mqtt/test_siren.py index c2cb39cca0a..77bec4accfb 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -832,7 +832,7 @@ async def test_command_templates( mqtt_mock.async_publish.assert_any_call( "test-topic", "CMD: ON, DURATION: 22, TONE: ping, VOLUME: 0.88", 0, False ) - mqtt_mock.async_publish.call_count == 1 + assert mqtt_mock.async_publish.call_count == 1 mqtt_mock.reset_mock() await async_turn_off( hass, @@ -841,7 +841,7 @@ async def test_command_templates( mqtt_mock.async_publish.assert_any_call( "test-topic", "CMD: OFF, DURATION: , TONE: , VOLUME:", 0, False ) - mqtt_mock.async_publish.call_count == 1 + assert mqtt_mock.async_publish.call_count == 1 mqtt_mock.reset_mock() await async_turn_on( @@ -862,7 +862,7 @@ async def test_command_templates( entity_id="siren.milk", ) mqtt_mock.async_publish.assert_any_call("test-topic", "CMD_OFF: OFF", 0, False) - mqtt_mock.async_publish.call_count == 1 + assert mqtt_mock.async_publish.call_count == 2 mqtt_mock.reset_mock() From d1209934776d343d9e16959126f47373608066c9 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:02:18 +0100 Subject: [PATCH 1126/1691] fix homekit test asserts (#113549) --- tests/components/homekit/test_type_lights.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index fce523262b6..edbec9e1a2b 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -1376,13 +1376,13 @@ async def test_light_min_max_mireds(hass: HomeAssistant, hk_driver, events) -> N ATTR_SUPPORTED_COLOR_MODES: [ColorMode.COLOR_TEMP], ATTR_BRIGHTNESS: 255, ATTR_MAX_MIREDS: 500.5, - ATTR_MIN_MIREDS: 100.5, + ATTR_MIN_MIREDS: 153.5, }, ) await hass.async_block_till_done() acc = Light(hass, hk_driver, "Light", entity_id, 1, None) - acc.char_color_temp.properties["maxValue"] == 500 - acc.char_color_temp.properties["minValue"] == 100 + assert acc.char_color_temp.properties["maxValue"] == 500 + assert acc.char_color_temp.properties["minValue"] == 153 async def test_light_set_brightness_and_color_temp( From 77a94ea515067361041e825d6b2e43627286b2d0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 12:01:49 -1000 Subject: [PATCH 1127/1691] Speed up loading sun (#113544) * Speed up loading sun * Speed up loading sun * Speed up loading sun * adjust * tweak --- homeassistant/components/sun/__init__.py | 306 ++--------------------- homeassistant/components/sun/const.py | 15 ++ homeassistant/components/sun/entity.py | 296 ++++++++++++++++++++++ homeassistant/components/sun/sensor.py | 2 +- tests/components/sun/test_init.py | 43 ++-- tests/components/sun/test_recorder.py | 4 +- 6 files changed, 351 insertions(+), 315 deletions(-) create mode 100644 homeassistant/components/sun/entity.py diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index a4964c94009..6308594f4bd 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -2,82 +2,24 @@ from __future__ import annotations -from datetime import datetime, timedelta -import logging -from typing import Any - -from astral.location import Elevation, Location - from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - EVENT_CORE_CONFIG_UPDATE, - SUN_EVENT_SUNRISE, - SUN_EVENT_SUNSET, - Platform, -) -from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback -from homeassistant.helpers import config_validation as cv, event -from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.sun import ( - get_astral_location, - get_location_astral_event_next, -) +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import ConfigType -from homeassistant.util import dt as dt_util - -from .const import DOMAIN, SIGNAL_EVENTS_CHANGED, SIGNAL_POSITION_CHANGED - -_LOGGER = logging.getLogger(__name__) - -ENTITY_ID = "sun.sun" - -STATE_ABOVE_HORIZON = "above_horizon" -STATE_BELOW_HORIZON = "below_horizon" - -STATE_ATTR_AZIMUTH = "azimuth" -STATE_ATTR_ELEVATION = "elevation" -STATE_ATTR_RISING = "rising" -STATE_ATTR_NEXT_DAWN = "next_dawn" -STATE_ATTR_NEXT_DUSK = "next_dusk" -STATE_ATTR_NEXT_MIDNIGHT = "next_midnight" -STATE_ATTR_NEXT_NOON = "next_noon" -STATE_ATTR_NEXT_RISING = "next_rising" -STATE_ATTR_NEXT_SETTING = "next_setting" - -# The algorithm used here is somewhat complicated. It aims to cut down -# the number of sensor updates over the day. It's documented best in -# the PR for the change, see the Discussion section of: -# https://github.com/home-assistant/core/pull/23832 - - -# As documented in wikipedia: https://en.wikipedia.org/wiki/Twilight -# sun is: -# < -18° of horizon - all stars visible -PHASE_NIGHT = "night" -# 18°-12° - some stars not visible -PHASE_ASTRONOMICAL_TWILIGHT = "astronomical_twilight" -# 12°-6° - horizon visible -PHASE_NAUTICAL_TWILIGHT = "nautical_twilight" -# 6°-0° - objects visible -PHASE_TWILIGHT = "twilight" -# 0°-10° above horizon, sun low on horizon -PHASE_SMALL_DAY = "small_day" -# > 10° above horizon -PHASE_DAY = "day" - -# 4 mins is one degree of arc change of the sun on its circle. -# During the night and the middle of the day we don't update -# that much since it's not important. -_PHASE_UPDATES = { - PHASE_NIGHT: timedelta(minutes=4 * 5), - PHASE_ASTRONOMICAL_TWILIGHT: timedelta(minutes=4 * 2), - PHASE_NAUTICAL_TWILIGHT: timedelta(minutes=4 * 2), - PHASE_TWILIGHT: timedelta(minutes=4), - PHASE_SMALL_DAY: timedelta(minutes=2), - PHASE_DAY: timedelta(minutes=4), -} +# The sensor platform is pre-imported here to ensure +# it gets loaded when the base component is loaded +# as we will always load it and we do not want to have +# to wait for the import executor when its busy later +# in the startup process. +from . import sensor as sensor_pre_import # noqa: F401 +from .const import ( # noqa: F401 # noqa: F401 + DOMAIN, + STATE_ABOVE_HORIZON, + STATE_BELOW_HORIZON, +) +from .entity import Sun CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) @@ -114,221 +56,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: sun.remove_listeners() hass.states.async_remove(sun.entity_id) return unload_ok - - -class Sun(Entity): - """Representation of the Sun.""" - - _unrecorded_attributes = frozenset( - { - STATE_ATTR_AZIMUTH, - STATE_ATTR_ELEVATION, - STATE_ATTR_RISING, - STATE_ATTR_NEXT_DAWN, - STATE_ATTR_NEXT_DUSK, - STATE_ATTR_NEXT_MIDNIGHT, - STATE_ATTR_NEXT_NOON, - STATE_ATTR_NEXT_RISING, - STATE_ATTR_NEXT_SETTING, - } - ) - - _attr_name = "Sun" - entity_id = ENTITY_ID - # This entity is legacy and does not have a platform. - # We can't fix this easily without breaking changes. - _no_platform_reported = True - - location: Location - elevation: Elevation - next_rising: datetime - next_setting: datetime - next_dawn: datetime - next_dusk: datetime - next_midnight: datetime - next_noon: datetime - solar_elevation: float - solar_azimuth: float - rising: bool - _next_change: datetime - - def __init__(self, hass: HomeAssistant) -> None: - """Initialize the sun.""" - self.hass = hass - self.phase: str | None = None - - # This is normally done by async_internal_added_to_hass which is not called - # for sun because sun has no platform - self._state_info = { - "unrecorded_attributes": self._Entity__combined_unrecorded_attributes # type: ignore[attr-defined] - } - - self._config_listener: CALLBACK_TYPE | None = None - self._update_events_listener: CALLBACK_TYPE | None = None - self._update_sun_position_listener: CALLBACK_TYPE | None = None - self._config_listener = self.hass.bus.async_listen( - EVENT_CORE_CONFIG_UPDATE, self.update_location - ) - self.update_location(initial=True) - - @callback - def update_location(self, _: Event | None = None, initial: bool = False) -> None: - """Update location.""" - location, elevation = get_astral_location(self.hass) - if not initial and location == self.location: - return - self.location = location - self.elevation = elevation - if self._update_events_listener: - self._update_events_listener() - self.update_events() - - @callback - def remove_listeners(self) -> None: - """Remove listeners.""" - if self._config_listener: - self._config_listener() - if self._update_events_listener: - self._update_events_listener() - if self._update_sun_position_listener: - self._update_sun_position_listener() - - @property - def state(self) -> str: - """Return the state of the sun.""" - # 0.8333 is the same value as astral uses - if self.solar_elevation > -0.833: - return STATE_ABOVE_HORIZON - - return STATE_BELOW_HORIZON - - @property - def extra_state_attributes(self) -> dict[str, Any]: - """Return the state attributes of the sun.""" - return { - STATE_ATTR_NEXT_DAWN: self.next_dawn.isoformat(), - STATE_ATTR_NEXT_DUSK: self.next_dusk.isoformat(), - STATE_ATTR_NEXT_MIDNIGHT: self.next_midnight.isoformat(), - STATE_ATTR_NEXT_NOON: self.next_noon.isoformat(), - STATE_ATTR_NEXT_RISING: self.next_rising.isoformat(), - STATE_ATTR_NEXT_SETTING: self.next_setting.isoformat(), - STATE_ATTR_ELEVATION: self.solar_elevation, - STATE_ATTR_AZIMUTH: self.solar_azimuth, - STATE_ATTR_RISING: self.rising, - } - - def _check_event( - self, utc_point_in_time: datetime, sun_event: str, before: str | None - ) -> datetime: - next_utc = get_location_astral_event_next( - self.location, self.elevation, sun_event, utc_point_in_time - ) - if next_utc < self._next_change: - self._next_change = next_utc - self.phase = before - return next_utc - - @callback - def update_events(self, now: datetime | None = None) -> None: - """Update the attributes containing solar events.""" - # Grab current time in case system clock changed since last time we ran. - utc_point_in_time = dt_util.utcnow() - self._next_change = utc_point_in_time + timedelta(days=400) - - # Work our way around the solar cycle, figure out the next - # phase. Some of these are stored. - self.location.solar_depression = "astronomical" - self._check_event(utc_point_in_time, "dawn", PHASE_NIGHT) - self.location.solar_depression = "nautical" - self._check_event(utc_point_in_time, "dawn", PHASE_ASTRONOMICAL_TWILIGHT) - self.location.solar_depression = "civil" - self.next_dawn = self._check_event( - utc_point_in_time, "dawn", PHASE_NAUTICAL_TWILIGHT - ) - self.next_rising = self._check_event( - utc_point_in_time, SUN_EVENT_SUNRISE, PHASE_TWILIGHT - ) - self.location.solar_depression = -10 - self._check_event(utc_point_in_time, "dawn", PHASE_SMALL_DAY) - self.next_noon = self._check_event(utc_point_in_time, "noon", None) - self._check_event(utc_point_in_time, "dusk", PHASE_DAY) - self.next_setting = self._check_event( - utc_point_in_time, SUN_EVENT_SUNSET, PHASE_SMALL_DAY - ) - self.location.solar_depression = "civil" - self.next_dusk = self._check_event(utc_point_in_time, "dusk", PHASE_TWILIGHT) - self.location.solar_depression = "nautical" - self._check_event(utc_point_in_time, "dusk", PHASE_NAUTICAL_TWILIGHT) - self.location.solar_depression = "astronomical" - self._check_event(utc_point_in_time, "dusk", PHASE_ASTRONOMICAL_TWILIGHT) - self.next_midnight = self._check_event(utc_point_in_time, "midnight", None) - self.location.solar_depression = "civil" - - # if the event was solar midday or midnight, phase will now - # be None. Solar noon doesn't always happen when the sun is - # even in the day at the poles, so we can't rely on it. - # Need to calculate phase if next is noon or midnight - if self.phase is None: - elevation = self.location.solar_elevation(self._next_change, self.elevation) - if elevation >= 10: - self.phase = PHASE_DAY - elif elevation >= 0: - self.phase = PHASE_SMALL_DAY - elif elevation >= -6: - self.phase = PHASE_TWILIGHT - elif elevation >= -12: - self.phase = PHASE_NAUTICAL_TWILIGHT - elif elevation >= -18: - self.phase = PHASE_ASTRONOMICAL_TWILIGHT - else: - self.phase = PHASE_NIGHT - - self.rising = self.next_noon < self.next_midnight - - _LOGGER.debug( - "sun phase_update@%s: phase=%s", utc_point_in_time.isoformat(), self.phase - ) - if self._update_sun_position_listener: - self._update_sun_position_listener() - self.update_sun_position() - async_dispatcher_send(self.hass, SIGNAL_EVENTS_CHANGED) - - # Set timer for the next solar event - self._update_events_listener = event.async_track_point_in_utc_time( - self.hass, self.update_events, self._next_change - ) - _LOGGER.debug("next time: %s", self._next_change.isoformat()) - - @callback - def update_sun_position(self, now: datetime | None = None) -> None: - """Calculate the position of the sun.""" - # Grab current time in case system clock changed since last time we ran. - utc_point_in_time = dt_util.utcnow() - self.solar_azimuth = round( - self.location.solar_azimuth(utc_point_in_time, self.elevation), 2 - ) - self.solar_elevation = round( - self.location.solar_elevation(utc_point_in_time, self.elevation), 2 - ) - - _LOGGER.debug( - "sun position_update@%s: elevation=%s azimuth=%s", - utc_point_in_time.isoformat(), - self.solar_elevation, - self.solar_azimuth, - ) - self.async_write_ha_state() - - async_dispatcher_send(self.hass, SIGNAL_POSITION_CHANGED) - - # Next update as per the current phase - assert self.phase - delta = _PHASE_UPDATES[self.phase] - # if the next update is within 1.25 of the next - # position update just drop it - if utc_point_in_time + delta * 1.25 > self._next_change: - self._update_sun_position_listener = None - return - self._update_sun_position_listener = event.async_track_point_in_utc_time( - self.hass, self.update_sun_position, utc_point_in_time + delta - ) diff --git a/homeassistant/components/sun/const.py b/homeassistant/components/sun/const.py index df7b0d43465..949bd4e2fbb 100644 --- a/homeassistant/components/sun/const.py +++ b/homeassistant/components/sun/const.py @@ -8,3 +8,18 @@ DEFAULT_NAME: Final = "Sun" SIGNAL_POSITION_CHANGED = f"{DOMAIN}_position_changed" SIGNAL_EVENTS_CHANGED = f"{DOMAIN}_events_changed" + + +STATE_ABOVE_HORIZON = "above_horizon" +STATE_BELOW_HORIZON = "below_horizon" + + +STATE_ATTR_AZIMUTH = "azimuth" +STATE_ATTR_ELEVATION = "elevation" +STATE_ATTR_RISING = "rising" +STATE_ATTR_NEXT_DAWN = "next_dawn" +STATE_ATTR_NEXT_DUSK = "next_dusk" +STATE_ATTR_NEXT_MIDNIGHT = "next_midnight" +STATE_ATTR_NEXT_NOON = "next_noon" +STATE_ATTR_NEXT_RISING = "next_rising" +STATE_ATTR_NEXT_SETTING = "next_setting" diff --git a/homeassistant/components/sun/entity.py b/homeassistant/components/sun/entity.py new file mode 100644 index 00000000000..739784697e0 --- /dev/null +++ b/homeassistant/components/sun/entity.py @@ -0,0 +1,296 @@ +"""Support for functionality to keep track of the sun.""" + +from __future__ import annotations + +from datetime import datetime, timedelta +import logging +from typing import Any + +from astral.location import Elevation, Location + +from homeassistant.const import ( + EVENT_CORE_CONFIG_UPDATE, + SUN_EVENT_SUNRISE, + SUN_EVENT_SUNSET, +) +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback +from homeassistant.helpers import event +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.sun import ( + get_astral_location, + get_location_astral_event_next, +) +from homeassistant.util import dt as dt_util + +from .const import ( + SIGNAL_EVENTS_CHANGED, + SIGNAL_POSITION_CHANGED, + STATE_ABOVE_HORIZON, + STATE_BELOW_HORIZON, +) + +_LOGGER = logging.getLogger(__name__) + +ENTITY_ID = "sun.sun" + +STATE_ATTR_AZIMUTH = "azimuth" +STATE_ATTR_ELEVATION = "elevation" +STATE_ATTR_RISING = "rising" +STATE_ATTR_NEXT_DAWN = "next_dawn" +STATE_ATTR_NEXT_DUSK = "next_dusk" +STATE_ATTR_NEXT_MIDNIGHT = "next_midnight" +STATE_ATTR_NEXT_NOON = "next_noon" +STATE_ATTR_NEXT_RISING = "next_rising" +STATE_ATTR_NEXT_SETTING = "next_setting" + +# The algorithm used here is somewhat complicated. It aims to cut down +# the number of sensor updates over the day. It's documented best in +# the PR for the change, see the Discussion section of: +# https://github.com/home-assistant/core/pull/23832 + + +# As documented in wikipedia: https://en.wikipedia.org/wiki/Twilight +# sun is: +# < -18° of horizon - all stars visible +PHASE_NIGHT = "night" +# 18°-12° - some stars not visible +PHASE_ASTRONOMICAL_TWILIGHT = "astronomical_twilight" +# 12°-6° - horizon visible +PHASE_NAUTICAL_TWILIGHT = "nautical_twilight" +# 6°-0° - objects visible +PHASE_TWILIGHT = "twilight" +# 0°-10° above horizon, sun low on horizon +PHASE_SMALL_DAY = "small_day" +# > 10° above horizon +PHASE_DAY = "day" + +# 4 mins is one degree of arc change of the sun on its circle. +# During the night and the middle of the day we don't update +# that much since it's not important. +_PHASE_UPDATES = { + PHASE_NIGHT: timedelta(minutes=4 * 5), + PHASE_ASTRONOMICAL_TWILIGHT: timedelta(minutes=4 * 2), + PHASE_NAUTICAL_TWILIGHT: timedelta(minutes=4 * 2), + PHASE_TWILIGHT: timedelta(minutes=4), + PHASE_SMALL_DAY: timedelta(minutes=2), + PHASE_DAY: timedelta(minutes=4), +} + + +class Sun(Entity): + """Representation of the Sun.""" + + _unrecorded_attributes = frozenset( + { + STATE_ATTR_AZIMUTH, + STATE_ATTR_ELEVATION, + STATE_ATTR_RISING, + STATE_ATTR_NEXT_DAWN, + STATE_ATTR_NEXT_DUSK, + STATE_ATTR_NEXT_MIDNIGHT, + STATE_ATTR_NEXT_NOON, + STATE_ATTR_NEXT_RISING, + STATE_ATTR_NEXT_SETTING, + } + ) + + _attr_name = "Sun" + entity_id = ENTITY_ID + # This entity is legacy and does not have a platform. + # We can't fix this easily without breaking changes. + _no_platform_reported = True + + location: Location + elevation: Elevation + next_rising: datetime + next_setting: datetime + next_dawn: datetime + next_dusk: datetime + next_midnight: datetime + next_noon: datetime + solar_elevation: float + solar_azimuth: float + rising: bool + _next_change: datetime + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the sun.""" + self.hass = hass + self.phase: str | None = None + + # This is normally done by async_internal_added_to_hass which is not called + # for sun because sun has no platform + self._state_info = { + "unrecorded_attributes": self._Entity__combined_unrecorded_attributes # type: ignore[attr-defined] + } + + self._config_listener: CALLBACK_TYPE | None = None + self._update_events_listener: CALLBACK_TYPE | None = None + self._update_sun_position_listener: CALLBACK_TYPE | None = None + self._config_listener = self.hass.bus.async_listen( + EVENT_CORE_CONFIG_UPDATE, self.update_location + ) + self.update_location(initial=True) + + @callback + def update_location(self, _: Event | None = None, initial: bool = False) -> None: + """Update location.""" + location, elevation = get_astral_location(self.hass) + if not initial and location == self.location: + return + self.location = location + self.elevation = elevation + if self._update_events_listener: + self._update_events_listener() + self.update_events() + + @callback + def remove_listeners(self) -> None: + """Remove listeners.""" + if self._config_listener: + self._config_listener() + if self._update_events_listener: + self._update_events_listener() + if self._update_sun_position_listener: + self._update_sun_position_listener() + + @property + def state(self) -> str: + """Return the state of the sun.""" + # 0.8333 is the same value as astral uses + if self.solar_elevation > -0.833: + return STATE_ABOVE_HORIZON + + return STATE_BELOW_HORIZON + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the state attributes of the sun.""" + return { + STATE_ATTR_NEXT_DAWN: self.next_dawn.isoformat(), + STATE_ATTR_NEXT_DUSK: self.next_dusk.isoformat(), + STATE_ATTR_NEXT_MIDNIGHT: self.next_midnight.isoformat(), + STATE_ATTR_NEXT_NOON: self.next_noon.isoformat(), + STATE_ATTR_NEXT_RISING: self.next_rising.isoformat(), + STATE_ATTR_NEXT_SETTING: self.next_setting.isoformat(), + STATE_ATTR_ELEVATION: self.solar_elevation, + STATE_ATTR_AZIMUTH: self.solar_azimuth, + STATE_ATTR_RISING: self.rising, + } + + def _check_event( + self, utc_point_in_time: datetime, sun_event: str, before: str | None + ) -> datetime: + next_utc = get_location_astral_event_next( + self.location, self.elevation, sun_event, utc_point_in_time + ) + if next_utc < self._next_change: + self._next_change = next_utc + self.phase = before + return next_utc + + @callback + def update_events(self, now: datetime | None = None) -> None: + """Update the attributes containing solar events.""" + # Grab current time in case system clock changed since last time we ran. + utc_point_in_time = dt_util.utcnow() + self._next_change = utc_point_in_time + timedelta(days=400) + + # Work our way around the solar cycle, figure out the next + # phase. Some of these are stored. + self.location.solar_depression = "astronomical" + self._check_event(utc_point_in_time, "dawn", PHASE_NIGHT) + self.location.solar_depression = "nautical" + self._check_event(utc_point_in_time, "dawn", PHASE_ASTRONOMICAL_TWILIGHT) + self.location.solar_depression = "civil" + self.next_dawn = self._check_event( + utc_point_in_time, "dawn", PHASE_NAUTICAL_TWILIGHT + ) + self.next_rising = self._check_event( + utc_point_in_time, SUN_EVENT_SUNRISE, PHASE_TWILIGHT + ) + self.location.solar_depression = -10 + self._check_event(utc_point_in_time, "dawn", PHASE_SMALL_DAY) + self.next_noon = self._check_event(utc_point_in_time, "noon", None) + self._check_event(utc_point_in_time, "dusk", PHASE_DAY) + self.next_setting = self._check_event( + utc_point_in_time, SUN_EVENT_SUNSET, PHASE_SMALL_DAY + ) + self.location.solar_depression = "civil" + self.next_dusk = self._check_event(utc_point_in_time, "dusk", PHASE_TWILIGHT) + self.location.solar_depression = "nautical" + self._check_event(utc_point_in_time, "dusk", PHASE_NAUTICAL_TWILIGHT) + self.location.solar_depression = "astronomical" + self._check_event(utc_point_in_time, "dusk", PHASE_ASTRONOMICAL_TWILIGHT) + self.next_midnight = self._check_event(utc_point_in_time, "midnight", None) + self.location.solar_depression = "civil" + + # if the event was solar midday or midnight, phase will now + # be None. Solar noon doesn't always happen when the sun is + # even in the day at the poles, so we can't rely on it. + # Need to calculate phase if next is noon or midnight + if self.phase is None: + elevation = self.location.solar_elevation(self._next_change, self.elevation) + if elevation >= 10: + self.phase = PHASE_DAY + elif elevation >= 0: + self.phase = PHASE_SMALL_DAY + elif elevation >= -6: + self.phase = PHASE_TWILIGHT + elif elevation >= -12: + self.phase = PHASE_NAUTICAL_TWILIGHT + elif elevation >= -18: + self.phase = PHASE_ASTRONOMICAL_TWILIGHT + else: + self.phase = PHASE_NIGHT + + self.rising = self.next_noon < self.next_midnight + + _LOGGER.debug( + "sun phase_update@%s: phase=%s", utc_point_in_time.isoformat(), self.phase + ) + if self._update_sun_position_listener: + self._update_sun_position_listener() + self.update_sun_position() + async_dispatcher_send(self.hass, SIGNAL_EVENTS_CHANGED) + + # Set timer for the next solar event + self._update_events_listener = event.async_track_point_in_utc_time( + self.hass, self.update_events, self._next_change + ) + _LOGGER.debug("next time: %s", self._next_change.isoformat()) + + @callback + def update_sun_position(self, now: datetime | None = None) -> None: + """Calculate the position of the sun.""" + # Grab current time in case system clock changed since last time we ran. + utc_point_in_time = dt_util.utcnow() + self.solar_azimuth = round( + self.location.solar_azimuth(utc_point_in_time, self.elevation), 2 + ) + self.solar_elevation = round( + self.location.solar_elevation(utc_point_in_time, self.elevation), 2 + ) + + _LOGGER.debug( + "sun position_update@%s: elevation=%s azimuth=%s", + utc_point_in_time.isoformat(), + self.solar_elevation, + self.solar_azimuth, + ) + self.async_write_ha_state() + + async_dispatcher_send(self.hass, SIGNAL_POSITION_CHANGED) + + # Next update as per the current phase + assert self.phase + delta = _PHASE_UPDATES[self.phase] + # if the next update is within 1.25 of the next + # position update just drop it + if utc_point_in_time + delta * 1.25 > self._next_change: + self._update_sun_position_listener = None + return + self._update_sun_position_listener = event.async_track_point_in_utc_time( + self.hass, self.update_sun_position, utc_point_in_time + delta + ) diff --git a/homeassistant/components/sun/sensor.py b/homeassistant/components/sun/sensor.py index 1abb1a6f23d..018ba4fa994 100644 --- a/homeassistant/components/sun/sensor.py +++ b/homeassistant/components/sun/sensor.py @@ -21,8 +21,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from . import Sun from .const import DOMAIN, SIGNAL_EVENTS_CHANGED, SIGNAL_POSITION_CHANGED +from .entity import Sun ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".sun_{}" diff --git a/tests/components/sun/test_init.py b/tests/components/sun/test_init.py index b97596f74e8..48a214274c9 100644 --- a/tests/components/sun/test_init.py +++ b/tests/components/sun/test_init.py @@ -7,6 +7,7 @@ from freezegun import freeze_time import pytest from homeassistant.components import sun +from homeassistant.components.sun import entity from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.core import HomeAssistant, callback from homeassistant.setup import async_setup_component @@ -22,7 +23,7 @@ async def test_setting_rising(hass: HomeAssistant) -> None: await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}}) await hass.async_block_till_done() - state = hass.states.get(sun.ENTITY_ID) + state = hass.states.get(entity.ENTITY_ID) from astral import LocationInfo import astral.sun @@ -88,22 +89,22 @@ async def test_setting_rising(hass: HomeAssistant) -> None: mod += 1 assert next_dawn == dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_DAWN] + state.attributes[entity.STATE_ATTR_NEXT_DAWN] ) assert next_dusk == dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_DUSK] + state.attributes[entity.STATE_ATTR_NEXT_DUSK] ) assert next_midnight == dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_MIDNIGHT] + state.attributes[entity.STATE_ATTR_NEXT_MIDNIGHT] ) assert next_noon == dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_NOON] + state.attributes[entity.STATE_ATTR_NEXT_NOON] ) assert next_rising == dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_RISING] + state.attributes[entity.STATE_ATTR_NEXT_RISING] ) assert next_setting == dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_SETTING] + state.attributes[entity.STATE_ATTR_NEXT_SETTING] ) @@ -118,29 +119,29 @@ async def test_state_change( await hass.async_block_till_done() test_time = dt_util.parse_datetime( - hass.states.get(sun.ENTITY_ID).attributes[sun.STATE_ATTR_NEXT_RISING] + hass.states.get(entity.ENTITY_ID).attributes[entity.STATE_ATTR_NEXT_RISING] ) assert test_time is not None - assert hass.states.get(sun.ENTITY_ID).state == sun.STATE_BELOW_HORIZON + assert hass.states.get(entity.ENTITY_ID).state == sun.STATE_BELOW_HORIZON patched_time = test_time + timedelta(seconds=5) with freeze_time(patched_time): async_fire_time_changed(hass, patched_time) await hass.async_block_till_done() - assert hass.states.get(sun.ENTITY_ID).state == sun.STATE_ABOVE_HORIZON + assert hass.states.get(entity.ENTITY_ID).state == sun.STATE_ABOVE_HORIZON # Update core configuration with patch("homeassistant.helpers.condition.dt_util.utcnow", return_value=now): await hass.config.async_update(longitude=hass.config.longitude + 90) await hass.async_block_till_done() - assert hass.states.get(sun.ENTITY_ID).state == sun.STATE_ABOVE_HORIZON + assert hass.states.get(entity.ENTITY_ID).state == sun.STATE_ABOVE_HORIZON # Test listeners are not duplicated after a core configuration change test_time = dt_util.parse_datetime( - hass.states.get(sun.ENTITY_ID).attributes[sun.STATE_ATTR_NEXT_DUSK] + hass.states.get(entity.ENTITY_ID).attributes[entity.STATE_ATTR_NEXT_DUSK] ) assert test_time is not None @@ -155,7 +156,7 @@ async def test_state_change( # Called once by time listener, once from Sun.update_events assert caplog.text.count("sun position_update") == 2 - assert hass.states.get(sun.ENTITY_ID).state == sun.STATE_BELOW_HORIZON + assert hass.states.get(entity.ENTITY_ID).state == sun.STATE_BELOW_HORIZON async def test_norway_in_june(hass: HomeAssistant) -> None: @@ -168,14 +169,14 @@ async def test_norway_in_june(hass: HomeAssistant) -> None: with patch("homeassistant.helpers.condition.dt_util.utcnow", return_value=june): assert await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}}) - state = hass.states.get(sun.ENTITY_ID) + state = hass.states.get(entity.ENTITY_ID) assert state is not None assert dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_RISING] + state.attributes[entity.STATE_ATTR_NEXT_RISING] ) == datetime(2016, 7, 24, 22, 59, 45, 689645, tzinfo=dt_util.UTC) assert dt_util.parse_datetime( - state.attributes[sun.STATE_ATTR_NEXT_SETTING] + state.attributes[entity.STATE_ATTR_NEXT_SETTING] ) == datetime(2016, 7, 25, 22, 17, 13, 503932, tzinfo=dt_util.UTC) assert state.state == sun.STATE_ABOVE_HORIZON @@ -223,25 +224,25 @@ async def test_setup_and_remove_config_entry(hass: HomeAssistant) -> None: await hass.async_block_till_done() # Check the platform is setup correctly - state = hass.states.get("sun.sun") + state = hass.states.get(entity.ENTITY_ID) assert state is not None test_time = dt_util.parse_datetime( - hass.states.get(sun.ENTITY_ID).attributes[sun.STATE_ATTR_NEXT_RISING] + hass.states.get(entity.ENTITY_ID).attributes[entity.STATE_ATTR_NEXT_RISING] ) assert test_time is not None - assert hass.states.get(sun.ENTITY_ID).state == sun.STATE_BELOW_HORIZON + assert hass.states.get(entity.ENTITY_ID).state == sun.STATE_BELOW_HORIZON # Remove the config entry assert await hass.config_entries.async_remove(config_entry.entry_id) await hass.async_block_till_done() # Check the state is removed, and does not reappear - assert hass.states.get("sun.sun") is None + assert hass.states.get(entity.ENTITY_ID) is None patched_time = test_time + timedelta(seconds=5) with freeze_time(patched_time): async_fire_time_changed(hass, patched_time) await hass.async_block_till_done() - assert hass.states.get("sun.sun") is None + assert hass.states.get(entity.ENTITY_ID) is None diff --git a/tests/components/sun/test_recorder.py b/tests/components/sun/test_recorder.py index 3392884e20e..15c15552c40 100644 --- a/tests/components/sun/test_recorder.py +++ b/tests/components/sun/test_recorder.py @@ -6,8 +6,8 @@ from datetime import timedelta from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.history import get_significant_states -from homeassistant.components.sun import ( - DOMAIN, +from homeassistant.components.sun import DOMAIN +from homeassistant.components.sun.entity import ( STATE_ATTR_AZIMUTH, STATE_ATTR_ELEVATION, STATE_ATTR_NEXT_DAWN, From e8de1a7031d6f231494d9a11156f7cc0f63c4a91 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 15 Mar 2024 23:15:36 +0100 Subject: [PATCH 1128/1691] =?UTF-8?q?Revert=20"Avoid=20pre-importing=20con?= =?UTF-8?q?fig=5Fflows=20if=20the=20integration=20does=20not=20=E2=80=A6?= =?UTF-8?q?=20(#113553)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Avoid pre-importing config_flows if the integration does not support …" This reverts commit 9940f51b95b7ba3423cd6079bd83ecf325bca7c2. --- homeassistant/config_entries.py | 22 ++----- homeassistant/loader.py | 23 ++------ tests/components/jellyfin/test_init.py | 1 - tests/components/sonarr/test_init.py | 4 +- .../unifiprotect/test_config_flow.py | 57 ++++++------------- tests/test_config_entries.py | 3 - tests/test_loader.py | 21 ++++--- 7 files changed, 42 insertions(+), 89 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b7623e9c8d7..4b2eb81c870 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -349,9 +349,6 @@ class ConfigEntry: # Supports remove device self.supports_remove_device: bool | None = None - # Supports migrate - self.supports_migrate: bool | None = None - # Supports options self._supports_options: bool | None = None @@ -494,7 +491,6 @@ class ConfigEntry: self.supports_remove_device = await support_remove_from_device( hass, self.domain ) - try: component = await integration.async_get_component() except ImportError as err: @@ -510,12 +506,7 @@ class ConfigEntry: ) return - if self.supports_migrate is None: - self.supports_migrate = hasattr(component, "async_migrate_entry") - - if domain_is_integration and self.supports_migrate: - # Avoid loading the config_flow module unless we need to check - # the version to see if we need to migrate + if domain_is_integration: try: await integration.async_get_platforms(("config_flow",)) except ImportError as err: @@ -796,7 +787,11 @@ class ConfigEntry: if same_major_version and self.minor_version == handler.MINOR_VERSION: return True - if not self.supports_migrate: + if not (integration := self._integration_for_domain): + integration = await loader.async_get_integration(hass, self.domain) + component = await integration.async_get_component() + supports_migrate = hasattr(component, "async_migrate_entry") + if not supports_migrate: if same_major_version: return True _LOGGER.error( @@ -806,11 +801,6 @@ class ConfigEntry: ) return False - if not (integration := self._integration_for_domain): - integration = await loader.async_get_integration(hass, self.domain) - - component = await integration.async_get_component() - try: result = await component.async_migrate_entry(hass, self) if not isinstance(result, bool): diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 9c961fd7f67..8e44ca38d77 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -961,7 +961,10 @@ class Integration: # Some integrations fail on import because they call functions incorrectly. # So we do it before validating config to catch these errors. - load_executor = self.import_executor and self.pkg_path not in sys.modules + load_executor = self.import_executor and ( + self.pkg_path not in sys.modules + or (self.config_flow and f"{self.pkg_path}.config_flow" not in sys.modules) + ) if not load_executor: comp = self._get_component() if debug: @@ -1048,12 +1051,6 @@ class Integration: if preload_platforms: for platform_name in self.platforms_exists(self._platforms_to_preload): - if ( - platform_name == "config_flow" - and not async_config_flow_needs_preload(cache[domain]) - ): - continue - with suppress(ImportError): self.get_platform(platform_name) @@ -1687,15 +1684,3 @@ def async_suggest_report_issue( ) return f"create a bug report at {issue_tracker}" - - -@callback -def async_config_flow_needs_preload(component: ComponentProtocol) -> bool: - """Test if a config_flow for a component needs to be preloaded. - - Currently we need to preload a the config flow if the integration - has a config flow and the component has an async_migrate_entry method - because it means that config_entries will always have to load - it to check if it needs to be migrated. - """ - return hasattr(component, "async_migrate_entry") diff --git a/tests/components/jellyfin/test_init.py b/tests/components/jellyfin/test_init.py index 6e6a0f7219b..9bfe37f9874 100644 --- a/tests/components/jellyfin/test_init.py +++ b/tests/components/jellyfin/test_init.py @@ -67,7 +67,6 @@ async def test_invalid_auth( mock_config_entry.add_to_hass(hass) assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py index e663139d33c..9f512c11074 100644 --- a/tests/components/sonarr/test_init.py +++ b/tests/components/sonarr/test_init.py @@ -105,9 +105,7 @@ async def test_migrate_config_entry(hass: HomeAssistant) -> None: assert entry.version == 1 assert not entry.unique_id - with patch("homeassistant.components.sonarr.async_setup_entry", return_value=True): - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() + await entry.async_migrate(hass) assert entry.data == { CONF_API_KEY: "MOCK_API_KEY", diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index f4ff4752baf..be315b72d17 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -219,31 +219,25 @@ async def test_form_reauth_auth( ) mock_config.add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": mock_config.entry_id, + }, + ) + assert result["type"] == FlowResultType.FORM + assert not result["errors"] + flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) + assert flows[0]["context"]["title_placeholders"] == { + "ip_address": "1.1.1.1", + "name": "Mock Title", + } + with patch( "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", side_effect=NotAuthorized, - ), patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": mock_config.entry_id, - }, - ) - assert result["type"] == FlowResultType.FORM - assert not result["errors"] - flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) - assert flows[0]["context"]["title_placeholders"] == { - "ip_address": "1.1.1.1", - "name": "Mock Title", - } - result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -263,10 +257,7 @@ async def test_form_reauth_auth( ), patch( "homeassistant.components.unifiprotect.async_setup", return_value=True, - ) as mock_setup, patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ): + ) as mock_setup: result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -777,20 +768,6 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa ) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: - await hass.config_entries.async_setup(mock_config.entry_id) - await hass.async_block_till_done() - - assert mock_config.state == config_entries.ConfigEntryState.LOADED - assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_setup.mock_calls) == 1 - other_ip_dict = UNIFI_DISCOVERY_DICT.copy() other_ip_dict["source_ip"] = "127.0.0.2" other_ip_dict["direct_connect_domain"] = "nomatchsameip.ui.direct" @@ -846,7 +823,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa "verify_ssl": True, } assert len(mock_setup_entry.mock_calls) == 1 - assert len(mock_setup.mock_calls) == 0 + assert len(mock_setup.mock_calls) == 1 async def test_discovered_by_unifi_discovery_direct_connect_on_different_interface_resolver_no_result( diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index d961b71e96d..5ce49308725 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -117,7 +117,6 @@ async def test_call_setup_entry(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 assert entry.state is config_entries.ConfigEntryState.LOADED assert entry.supports_unload - assert entry.supports_migrate async def test_call_setup_entry_without_reload_support(hass: HomeAssistant) -> None: @@ -147,7 +146,6 @@ async def test_call_setup_entry_without_reload_support(hass: HomeAssistant) -> N assert len(mock_setup_entry.mock_calls) == 1 assert entry.state is config_entries.ConfigEntryState.LOADED assert not entry.supports_unload - assert entry.supports_migrate @pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (1, 2), (2, 2)]) @@ -291,7 +289,6 @@ async def test_call_async_migrate_entry_failure_not_supported( ) entry.add_to_hass(hass) assert not entry.supports_unload - entry.supports_migrate = True mock_setup_entry = AsyncMock(return_value=True) diff --git a/tests/test_loader.py b/tests/test_loader.py index 5bdf93758b0..a2868976876 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1204,21 +1204,31 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert "test_package_loaded_executor" not in hass.config.components assert "test_package_loaded_executor.config_flow" not in hass.config.components - module_mock = object() + config_flow_module_name = f"{integration.pkg_path}.config_flow" + module_mock = MagicMock(__file__="__init__.py") + config_flow_module_mock = MagicMock(__file__="config_flow.py") def import_module(name: str) -> Any: if name == integration.pkg_path: return module_mock + if name == config_flow_module_name: + return config_flow_module_mock raise ImportError + modules_without_config_flow = { + k: v for k, v in sys.modules.items() if k != config_flow_module_name + } with patch.dict( "sys.modules", - {**sys.modules, integration.pkg_path: module_mock}, + {**modules_without_config_flow, integration.pkg_path: module_mock}, clear=True, ), patch("homeassistant.loader.importlib.import_module", import_module): module = await integration.async_get_component() - assert "loaded_executor=False" in caplog.text + # The config flow is missing so we should load + # in the executor + assert "loaded_executor=True" in caplog.text + assert "loaded_executor=False" not in caplog.text assert module is module_mock caplog.clear() @@ -1226,6 +1236,7 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( "sys.modules", { integration.pkg_path: module_mock, + config_flow_module_name: config_flow_module_mock, }, ), patch("homeassistant.loader.importlib.import_module", import_module): module = await integration.async_get_component() @@ -1235,10 +1246,6 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert "loaded_executor" not in caplog.text assert module is module_mock - # The integration does not implement async_migrate_entry so it - # should not be preloaded - assert integration.get_platform_cached("config_flow") is None - async def test_async_get_component_concurrent_loads( hass: HomeAssistant, From 03bb791080ed7486a0a5feeda66b125d55d2624c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 12:31:55 -1000 Subject: [PATCH 1129/1691] Add missing async_block_till_done to jellyfin test_invalid_auth test (#113556) This fix was reverted out in #113553 --- tests/components/jellyfin/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/jellyfin/test_init.py b/tests/components/jellyfin/test_init.py index 9bfe37f9874..6e6a0f7219b 100644 --- a/tests/components/jellyfin/test_init.py +++ b/tests/components/jellyfin/test_init.py @@ -67,6 +67,7 @@ async def test_invalid_auth( mock_config_entry.add_to_hass(hass) assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 From 88f04bb3b483adfee4923bc8fe6a924880f3e0c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 12:33:48 -1000 Subject: [PATCH 1130/1691] Avoid calling entry.async_migrate in sonarr tests (#113557) We should setup the the config entry instead of calling the migrator directly This fix was reverted out in #113553 --- tests/components/sonarr/test_init.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py index 9f512c11074..e663139d33c 100644 --- a/tests/components/sonarr/test_init.py +++ b/tests/components/sonarr/test_init.py @@ -105,7 +105,9 @@ async def test_migrate_config_entry(hass: HomeAssistant) -> None: assert entry.version == 1 assert not entry.unique_id - await entry.async_migrate(hass) + with patch("homeassistant.components.sonarr.async_setup_entry", return_value=True): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() assert entry.data == { CONF_API_KEY: "MOCK_API_KEY", From 2b049753321d75fe5262733c016ef004c0059938 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Fri, 15 Mar 2024 23:42:04 +0100 Subject: [PATCH 1131/1691] Remove danielperna84 as codeowner (#109662) * Remove danielperna84 as codeowner * Update CODEOWNERS --- CODEOWNERS | 4 ++-- homeassistant/components/homematic/manifest.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b81fade1402..abeddd439ab 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -570,8 +570,8 @@ build.json @home-assistant/supervisor /tests/components/homekit/ @bdraco /homeassistant/components/homekit_controller/ @Jc2k @bdraco /tests/components/homekit_controller/ @Jc2k @bdraco -/homeassistant/components/homematic/ @pvizeli @danielperna84 -/tests/components/homematic/ @pvizeli @danielperna84 +/homeassistant/components/homematic/ @pvizeli +/tests/components/homematic/ @pvizeli /homeassistant/components/homewizard/ @DCSBL /tests/components/homewizard/ @DCSBL /homeassistant/components/honeywell/ @rdfurman @mkmer diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json index 063f48d74b4..9c67a5da0b2 100644 --- a/homeassistant/components/homematic/manifest.json +++ b/homeassistant/components/homematic/manifest.json @@ -1,7 +1,7 @@ { "domain": "homematic", "name": "Homematic", - "codeowners": ["@pvizeli", "@danielperna84"], + "codeowners": ["@pvizeli"], "documentation": "https://www.home-assistant.io/integrations/homematic", "iot_class": "local_push", "loggers": ["pyhomematic"], From 53a76fc792da194ae83478d345c3842428a9c84f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 15 Mar 2024 23:48:47 +0100 Subject: [PATCH 1132/1691] Fix Airthings BLE illuminance sensor name (#113560) --- homeassistant/components/airthings_ble/sensor.py | 1 + homeassistant/components/airthings_ble/strings.json | 3 +++ 2 files changed, 4 insertions(+) diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index 73ac7e47ceb..8031b802eae 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -108,6 +108,7 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { ), "illuminance": SensorEntityDescription( key="illuminance", + translation_key="illuminance", native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, ), diff --git a/homeassistant/components/airthings_ble/strings.json b/homeassistant/components/airthings_ble/strings.json index b7343377a2b..6f17b9a317e 100644 --- a/homeassistant/components/airthings_ble/strings.json +++ b/homeassistant/components/airthings_ble/strings.json @@ -33,6 +33,9 @@ }, "radon_longterm_level": { "name": "Radon longterm level" + }, + "illuminance": { + "name": "[%key:component::sensor::entity_component::illuminance::name%]" } } } From b96bceadfa5cab6ce6fe55b600097053d74ba4d1 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Fri, 15 Mar 2024 23:53:16 +0100 Subject: [PATCH 1133/1691] Add translations for devolo Home Network exceptions (#105755) * Add translations for devolo Home Network exceptions * Add translations for ConfigEntryAuthFailed * Update homeassistant/components/devolo_home_network/switch.py Co-authored-by: Erik Montnemery --------- Co-authored-by: Erik Montnemery --- .../components/devolo_home_network/__init__.py | 9 +++++++-- .../components/devolo_home_network/button.py | 10 ++++++++-- .../components/devolo_home_network/strings.json | 14 ++++++++++++++ .../components/devolo_home_network/switch.py | 10 ++++++++-- .../components/devolo_home_network/update.py | 10 ++++++++-- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/devolo_home_network/__init__.py b/homeassistant/components/devolo_home_network/__init__.py index 137eee62b22..08d0e340b16 100644 --- a/homeassistant/components/devolo_home_network/__init__.py +++ b/homeassistant/components/devolo_home_network/__init__.py @@ -69,7 +69,10 @@ async def async_setup_entry( # noqa: C901 ) except DeviceNotFound as err: raise ConfigEntryNotReady( - f"Unable to connect to {entry.data[CONF_IP_ADDRESS]}" + f"Unable to connect to {entry.data[CONF_IP_ADDRESS]}", + translation_domain=DOMAIN, + translation_key="connection_failed", + translation_placeholders={"ip_address": entry.data[CONF_IP_ADDRESS]}, ) from err hass.data[DOMAIN][entry.entry_id] = {"device": device} @@ -101,7 +104,9 @@ async def async_setup_entry( # noqa: C901 except DeviceUnavailable as err: raise UpdateFailed(err) from err except DevicePasswordProtected as err: - raise ConfigEntryAuthFailed(err) from err + raise ConfigEntryAuthFailed( + err, translation_domain=DOMAIN, translation_key="password_wrong" + ) from err async def async_update_led_status() -> bool: """Fetch data from API endpoint.""" diff --git a/homeassistant/components/devolo_home_network/button.py b/homeassistant/components/devolo_home_network/button.py index e9210b7ccc1..13e688c6fc5 100644 --- a/homeassistant/components/devolo_home_network/button.py +++ b/homeassistant/components/devolo_home_network/button.py @@ -117,9 +117,15 @@ class DevoloButtonEntity(DevoloEntity, ButtonEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password" + f"Device {self.entry.title} require re-authenticatication to set or change the password", + translation_domain=DOMAIN, + translation_key="password_protected", + translation_placeholders={"title": self.entry.title}, ) from ex except DeviceUnavailable as ex: raise HomeAssistantError( - f"Device {self.entry.title} did not respond" + f"Device {self.entry.title} did not respond", + translation_domain=DOMAIN, + translation_key="no_response", + translation_placeholders={"title": self.entry.title}, ) from ex diff --git a/homeassistant/components/devolo_home_network/strings.json b/homeassistant/components/devolo_home_network/strings.json index 1362417c125..9d86b127d77 100644 --- a/homeassistant/components/devolo_home_network/strings.json +++ b/homeassistant/components/devolo_home_network/strings.json @@ -78,5 +78,19 @@ "name": "Enable LEDs" } } + }, + "exceptions": { + "connection_failed": { + "message": "Unable to connect to {ip_address}" + }, + "no_response": { + "message": "Device {title} did not respond" + }, + "password_protected": { + "message": "Device {title} requires re-authenticatication to set or change the password" + }, + "password_wrong": { + "message": "The used password is wrong" + } } } diff --git a/homeassistant/components/devolo_home_network/switch.py b/homeassistant/components/devolo_home_network/switch.py index ffb6ae8098a..950b362d7f5 100644 --- a/homeassistant/components/devolo_home_network/switch.py +++ b/homeassistant/components/devolo_home_network/switch.py @@ -109,7 +109,10 @@ class DevoloSwitchEntity(DevoloCoordinatorEntity[_DataT], SwitchEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password" + f"Device {self.entry.title} require re-authenticatication to set or change the password", + translation_domain=DOMAIN, + translation_key="password_protected", + translation_placeholders={"title": self.entry.title}, ) from ex except DeviceUnavailable: pass # The coordinator will handle this @@ -122,7 +125,10 @@ class DevoloSwitchEntity(DevoloCoordinatorEntity[_DataT], SwitchEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password" + f"Device {self.entry.title} require re-authenticatication to set or change the password", + translation_domain=DOMAIN, + translation_key="password_protected", + translation_placeholders={"title": self.entry.title}, ) from ex except DeviceUnavailable: pass # The coordinator will handle this diff --git a/homeassistant/components/devolo_home_network/update.py b/homeassistant/components/devolo_home_network/update.py index f94cf13ef5c..0793254c761 100644 --- a/homeassistant/components/devolo_home_network/update.py +++ b/homeassistant/components/devolo_home_network/update.py @@ -117,9 +117,15 @@ class DevoloUpdateEntity(DevoloCoordinatorEntity, UpdateEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authentication to set or change the password" + f"Device {self.entry.title} require re-authenticatication to set or change the password", + translation_domain=DOMAIN, + translation_key="password_protected", + translation_placeholders={"title": self.entry.title}, ) from ex except DeviceUnavailable as ex: raise HomeAssistantError( - f"Device {self.entry.title} did not respond" + f"Device {self.entry.title} did not respond", + translation_domain=DOMAIN, + translation_key="no_response", + translation_placeholders={"title": self.entry.title}, ) from ex From 51ece8b1efc36c5a44673f3b2b5ac68409d80f42 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 13:09:20 -1000 Subject: [PATCH 1134/1691] Restore group config_flow pre-import (#113558) --- homeassistant/bootstrap.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 1d6fdcf36d4..1a417a7fbac 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -51,6 +51,13 @@ from .components import ( webhook as webhook_pre_import, # noqa: F401 websocket_api as websocket_api_pre_import, # noqa: F401 ) + +# Ensure group config_flow is imported so it does not need the import +# executor since config_flows are preloaded when the component is loaded. +# Even though group is pre-imported above we would have still had to wait +# for the config flow to be imported when the import executor is the most +# busy. +from .components.group import config_flow as group_config_flow # noqa: F401 from .components.sensor import recorder as sensor_recorder # noqa: F401 from .const import ( FORMAT_DATETIME, From b644c03fa743d0a022607a650facf0e75102aad7 Mon Sep 17 00:00:00 2001 From: On Freund Date: Sat, 16 Mar 2024 02:00:33 +0200 Subject: [PATCH 1135/1691] Send keep-alive frames in image proxy stream (#113542) --- homeassistant/components/image/__init__.py | 18 +++++++++++++ tests/components/image/test_init.py | 30 +++++++++++++++++----- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index d3b627f8292..b73da5fbfd6 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -339,25 +339,43 @@ async def async_get_still_stream( return True event = asyncio.Event() + timed_out = False @callback def _async_image_state_update(_event: Event[EventStateChangedData]) -> None: """Write image to stream.""" event.set() + @callback + def _async_timeout_reached() -> None: + """Handle timeout.""" + nonlocal timed_out + timed_out = True + event.set() + hass = request.app[KEY_HASS] + loop = hass.loop remove = async_track_state_change_event( hass, image_entity.entity_id, _async_image_state_update, ) + timeout_handle = None try: while True: if not await _write_frame(): return response + # Ensure that an image is sent at least every 55 seconds + # Otherwise some devices go blank + timeout_handle = loop.call_later(55, _async_timeout_reached) await event.wait() event.clear() + if not timed_out: + timeout_handle.cancel() + timed_out = False finally: + if timeout_handle: + timeout_handle.cancel() remove() diff --git a/tests/components/image/test_init.py b/tests/components/image/test_init.py index 75816e1350f..717e82a652d 100644 --- a/tests/components/image/test_init.py +++ b/tests/components/image/test_init.py @@ -1,11 +1,12 @@ """The tests for the image component.""" -import datetime +from datetime import datetime from http import HTTPStatus import ssl from unittest.mock import MagicMock, patch from aiohttp import hdrs +from freezegun.api import FrozenDateTimeFactory import httpx import pytest import respx @@ -24,7 +25,12 @@ from .conftest import ( MockURLImageEntity, ) -from tests.common import MockModule, mock_integration, mock_platform +from tests.common import ( + MockModule, + async_fire_time_changed, + mock_integration, + mock_platform, +) from tests.typing import ClientSessionGenerator @@ -292,7 +298,9 @@ async def test_fetch_image_url_wrong_content_type( async def test_image_stream( - hass: HomeAssistant, hass_client: ClientSessionGenerator + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + freezer: FrozenDateTimeFactory, ) -> None: """Test image stream.""" @@ -323,18 +331,26 @@ async def test_image_stream( assert not resp.closed assert resp.status == HTTPStatus.OK - mock_image.image_last_updated = datetime.datetime.now() + mock_image.image_last_updated = datetime.now() mock_image.async_write_ha_state() # Two blocks to ensure the frame is written await hass.async_block_till_done() await hass.async_block_till_done() + with patch.object(mock_image, "async_image", return_value=b"") as mock: + # Simulate a "keep alive" frame + freezer.tick(55) + async_fire_time_changed(hass) + # Two blocks to ensure the frame is written + await hass.async_block_till_done() + await hass.async_block_till_done() + mock.assert_called_once() + with patch.object(mock_image, "async_image", return_value=None): - mock_image.image_last_updated = datetime.datetime.now() - mock_image.async_write_ha_state() + freezer.tick(55) + async_fire_time_changed(hass) # Two blocks to ensure the frame is written await hass.async_block_till_done() await hass.async_block_till_done() await close_future - assert resp.closed From af06e03b71f90422e41dc1ad817b9b47d3088ada Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 14:49:35 -1000 Subject: [PATCH 1136/1691] Add profiler set_asyncio_debug service (#113447) * Add profiler set_asyncio_debug service Currently when a user has a problem with there event loop being blocked the simplest way to enable asyncio debug is to add `debugpy:` to `configuration.yaml`, however this approach slows the system which makes the report less useful and harder to track down the problem. We need a lightweight way to enable debug mode so users can report problems with the event loop being blocked, and we have a better chance of finding the source without side effects * logging * logging * logging * comments * fix * icon * only if enabled * coverage --- homeassistant/components/profiler/__init__.py | 24 ++++++++++ homeassistant/components/profiler/icons.json | 3 +- .../components/profiler/services.yaml | 6 +++ .../components/profiler/strings.json | 10 ++++ tests/components/profiler/test_init.py | 48 +++++++++++++++++++ 5 files changed, 90 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py index da70e587b88..30385a1c267 100644 --- a/homeassistant/components/profiler/__init__.py +++ b/homeassistant/components/profiler/__init__.py @@ -36,6 +36,7 @@ SERVICE_DUMP_LOG_OBJECTS = "dump_log_objects" SERVICE_LRU_STATS = "lru_stats" SERVICE_LOG_THREAD_FRAMES = "log_thread_frames" SERVICE_LOG_EVENT_LOOP_SCHEDULED = "log_event_loop_scheduled" +SERVICE_SET_ASYNCIO_DEBUG = "set_asyncio_debug" _LRU_CACHE_WRAPPER_OBJECT = _lru_cache_wrapper.__name__ _SQLALCHEMY_LRU_OBJECT = "LRUCache" @@ -57,12 +58,14 @@ SERVICES = ( SERVICE_LRU_STATS, SERVICE_LOG_THREAD_FRAMES, SERVICE_LOG_EVENT_LOOP_SCHEDULED, + SERVICE_SET_ASYNCIO_DEBUG, ) DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) DEFAULT_MAX_OBJECTS = 5 +CONF_ENABLED = "enabled" CONF_SECONDS = "seconds" CONF_MAX_OBJECTS = "max_objects" @@ -254,6 +257,19 @@ async def async_setup_entry( # noqa: C901 arepr.maxstring = original_maxstring arepr.maxother = original_maxother + async def _async_asyncio_debug(call: ServiceCall) -> None: + """Enable or disable asyncio debug.""" + enabled = call.data[CONF_ENABLED] + # Always log this at critical level so we know when + # it's been changed when reviewing logs + _LOGGER.critical("Setting asyncio debug to %s", enabled) + # Make sure the logger is set to at least INFO or + # we won't see the messages + base_logger = logging.getLogger() + if enabled and base_logger.getEffectiveLevel() > logging.INFO: + base_logger.setLevel(logging.INFO) + hass.loop.set_debug(enabled) + async_register_admin_service( hass, DOMAIN, @@ -348,6 +364,14 @@ async def async_setup_entry( # noqa: C901 _async_dump_scheduled, ) + async_register_admin_service( + hass, + DOMAIN, + SERVICE_SET_ASYNCIO_DEBUG, + _async_asyncio_debug, + schema=vol.Schema({vol.Optional(CONF_ENABLED, default=True): cv.boolean}), + ) + return True diff --git a/homeassistant/components/profiler/icons.json b/homeassistant/components/profiler/icons.json index d6c7ee21257..9a8c0e85f0d 100644 --- a/homeassistant/components/profiler/icons.json +++ b/homeassistant/components/profiler/icons.json @@ -9,6 +9,7 @@ "stop_log_object_sources": "mdi:stop", "lru_stats": "mdi:chart-areaspline", "log_thread_frames": "mdi:format-list-bulleted", - "log_event_loop_scheduled": "mdi:calendar-clock" + "log_event_loop_scheduled": "mdi:calendar-clock", + "set_asyncio_debug": "mdi:bug-check" } } diff --git a/homeassistant/components/profiler/services.yaml b/homeassistant/components/profiler/services.yaml index 311325fa404..6842b2f45f2 100644 --- a/homeassistant/components/profiler/services.yaml +++ b/homeassistant/components/profiler/services.yaml @@ -53,3 +53,9 @@ stop_log_object_sources: lru_stats: log_thread_frames: log_event_loop_scheduled: +set_asyncio_debug: + fields: + enabled: + default: true + selector: + boolean: diff --git a/homeassistant/components/profiler/strings.json b/homeassistant/components/profiler/strings.json index a14324a9082..980550a1a4a 100644 --- a/homeassistant/components/profiler/strings.json +++ b/homeassistant/components/profiler/strings.json @@ -83,6 +83,16 @@ "log_event_loop_scheduled": { "name": "Log event loop scheduled", "description": "Logs what is scheduled in the event loop." + }, + "set_asyncio_debug": { + "name": "Set asyncio debug", + "description": "Enable or disable asyncio debug.", + "fields": { + "enabled": { + "name": "Enabled", + "description": "Whether to enable or disable asyncio debug." + } + } } } } diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 84db1b07106..8bb17e79b3f 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -2,6 +2,7 @@ from datetime import timedelta from functools import lru_cache +import logging import os from pathlib import Path from unittest.mock import patch @@ -12,12 +13,14 @@ import pytest from homeassistant.components.profiler import ( _LRU_CACHE_WRAPPER_OBJECT, _SQLALCHEMY_LRU_OBJECT, + CONF_ENABLED, CONF_SECONDS, SERVICE_DUMP_LOG_OBJECTS, SERVICE_LOG_EVENT_LOOP_SCHEDULED, SERVICE_LOG_THREAD_FRAMES, SERVICE_LRU_STATS, SERVICE_MEMORY, + SERVICE_SET_ASYNCIO_DEBUG, SERVICE_START, SERVICE_START_LOG_OBJECT_SOURCES, SERVICE_START_LOG_OBJECTS, @@ -368,3 +371,48 @@ async def test_log_object_sources( await hass.services.async_call( DOMAIN, SERVICE_STOP_LOG_OBJECT_SOURCES, {}, blocking=True ) + + +async def test_set_asyncio_debug( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test setting asyncio debug.""" + + entry = MockConfigEntry(domain=DOMAIN) + entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.services.has_service(DOMAIN, SERVICE_SET_ASYNCIO_DEBUG) + + hass.loop.set_debug(False) + original_level = logging.getLogger().getEffectiveLevel() + logging.getLogger().setLevel(logging.WARNING) + + await hass.services.async_call( + DOMAIN, SERVICE_SET_ASYNCIO_DEBUG, {CONF_ENABLED: False}, blocking=True + ) + # Ensure logging level is only increased if we enable + assert logging.getLogger().getEffectiveLevel() == logging.WARNING + + await hass.services.async_call(DOMAIN, SERVICE_SET_ASYNCIO_DEBUG, {}, blocking=True) + assert hass.loop.get_debug() is True + + # Ensure logging is at least at INFO level + assert logging.getLogger().getEffectiveLevel() == logging.INFO + + await hass.services.async_call( + DOMAIN, SERVICE_SET_ASYNCIO_DEBUG, {CONF_ENABLED: False}, blocking=True + ) + assert hass.loop.get_debug() is False + + await hass.services.async_call( + DOMAIN, SERVICE_SET_ASYNCIO_DEBUG, {CONF_ENABLED: True}, blocking=True + ) + assert hass.loop.get_debug() is True + + logging.getLogger().setLevel(original_level) + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() From b26928878f037987a261afd831575deee725ab3a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 14:51:21 -1000 Subject: [PATCH 1137/1691] Remove group integration platforms that use the default states (#113562) Remove group integration platforms that use the default There is no need to register platforms that use the defaults as the group code already uses STATE_ON/STATE_OFF when there are no on/off states in the GroupIntegrationRegistry --- homeassistant/components/air_quality/group.py | 8 ++++++-- .../components/alarm_control_panel/group.py | 9 +++++++-- homeassistant/components/binary_sensor/__init__.py | 2 -- homeassistant/components/binary_sensor/group.py | 13 ------------- homeassistant/components/climate/group.py | 8 ++++++-- homeassistant/components/cover/group.py | 8 ++++++-- homeassistant/components/device_tracker/group.py | 8 ++++++-- homeassistant/components/fan/__init__.py | 2 -- homeassistant/components/fan/group.py | 13 ------------- homeassistant/components/humidifier/__init__.py | 1 - homeassistant/components/humidifier/group.py | 13 ------------- homeassistant/components/light/__init__.py | 2 -- homeassistant/components/light/group.py | 13 ------------- homeassistant/components/lock/group.py | 8 ++++++-- homeassistant/components/media_player/group.py | 8 ++++++-- homeassistant/components/person/group.py | 8 ++++++-- homeassistant/components/plant/group.py | 8 ++++++-- homeassistant/components/remote/__init__.py | 2 -- homeassistant/components/remote/group.py | 13 ------------- homeassistant/components/sensor/group.py | 8 ++++++-- homeassistant/components/switch/__init__.py | 1 - homeassistant/components/switch/group.py | 13 ------------- homeassistant/components/vacuum/group.py | 7 +++++-- homeassistant/components/water_heater/group.py | 7 +++++-- homeassistant/components/weather/group.py | 8 ++++++-- 25 files changed, 77 insertions(+), 114 deletions(-) delete mode 100644 homeassistant/components/binary_sensor/group.py delete mode 100644 homeassistant/components/fan/group.py delete mode 100644 homeassistant/components/humidifier/group.py delete mode 100644 homeassistant/components/light/group.py delete mode 100644 homeassistant/components/remote/group.py delete mode 100644 homeassistant/components/switch/group.py diff --git a/homeassistant/components/air_quality/group.py b/homeassistant/components/air_quality/group.py index 162457d336f..13a70cc4b6b 100644 --- a/homeassistant/components/air_quality/group.py +++ b/homeassistant/components/air_quality/group.py @@ -1,12 +1,16 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.exclude_domain() diff --git a/homeassistant/components/alarm_control_panel/group.py b/homeassistant/components/alarm_control_panel/group.py index 69a5e6d367e..3ef1ff96cda 100644 --- a/homeassistant/components/alarm_control_panel/group.py +++ b/homeassistant/components/alarm_control_panel/group.py @@ -1,6 +1,8 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry + +from typing import TYPE_CHECKING + from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, @@ -12,10 +14,13 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states( diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index b9f612257b6..4fd99c309bc 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -28,8 +28,6 @@ from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType -from . import group as group_pre_import # noqa: F401 - if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/binary_sensor/group.py b/homeassistant/components/binary_sensor/group.py deleted file mode 100644 index e1a0b18dd08..00000000000 --- a/homeassistant/components/binary_sensor/group.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Describe group states.""" - -from homeassistant.components.group import GroupIntegrationRegistry -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant, callback - - -@callback -def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry -) -> None: - """Describe group on off states.""" - registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/climate/group.py b/homeassistant/components/climate/group.py index 45d6a9b5fcb..f0b7a748740 100644 --- a/homeassistant/components/climate/group.py +++ b/homeassistant/components/climate/group.py @@ -1,15 +1,19 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, callback from .const import HVAC_MODES, HVACMode +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states( diff --git a/homeassistant/components/cover/group.py b/homeassistant/components/cover/group.py index 78335286d10..a4b682b84ff 100644 --- a/homeassistant/components/cover/group.py +++ b/homeassistant/components/cover/group.py @@ -1,13 +1,17 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_CLOSED, STATE_OPEN from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" # On means open, Off means closed diff --git a/homeassistant/components/device_tracker/group.py b/homeassistant/components/device_tracker/group.py index 97e9556eaeb..e1b93696aa9 100644 --- a/homeassistant/components/device_tracker/group.py +++ b/homeassistant/components/device_tracker/group.py @@ -1,13 +1,17 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states({STATE_HOME}, STATE_NOT_HOME) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index d39e38f8e25..aeb3a6c89df 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -40,8 +40,6 @@ from homeassistant.util.percentage import ( ranged_value_to_percentage, ) -from . import group as group_pre_import # noqa: F401 - if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/fan/group.py b/homeassistant/components/fan/group.py deleted file mode 100644 index e1a0b18dd08..00000000000 --- a/homeassistant/components/fan/group.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Describe group states.""" - -from homeassistant.components.group import GroupIntegrationRegistry -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant, callback - - -@callback -def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry -) -> None: - """Describe group on off states.""" - registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 5dba7d8e32c..97f78aba21c 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -34,7 +34,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from . import group as group_pre_import # noqa: F401 from .const import ( # noqa: F401 _DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER, _DEPRECATED_DEVICE_CLASS_HUMIDIFIER, diff --git a/homeassistant/components/humidifier/group.py b/homeassistant/components/humidifier/group.py deleted file mode 100644 index e1a0b18dd08..00000000000 --- a/homeassistant/components/humidifier/group.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Describe group states.""" - -from homeassistant.components.group import GroupIntegrationRegistry -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant, callback - - -@callback -def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry -) -> None: - """Describe group on off states.""" - registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 610dc662369..0a41ca2a84e 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -34,8 +34,6 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass import homeassistant.util.color as color_util -from . import group as group_pre_import # noqa: F401 - if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/light/group.py b/homeassistant/components/light/group.py deleted file mode 100644 index e1a0b18dd08..00000000000 --- a/homeassistant/components/light/group.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Describe group states.""" - -from homeassistant.components.group import GroupIntegrationRegistry -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant, callback - - -@callback -def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry -) -> None: - """Describe group on off states.""" - registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/lock/group.py b/homeassistant/components/lock/group.py index 03e576af307..99109e852f6 100644 --- a/homeassistant/components/lock/group.py +++ b/homeassistant/components/lock/group.py @@ -1,13 +1,17 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states({STATE_UNLOCKED}, STATE_LOCKED) diff --git a/homeassistant/components/media_player/group.py b/homeassistant/components/media_player/group.py index 2194f71edf4..f4d465922af 100644 --- a/homeassistant/components/media_player/group.py +++ b/homeassistant/components/media_player/group.py @@ -1,6 +1,7 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import ( STATE_IDLE, STATE_OFF, @@ -10,10 +11,13 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states( diff --git a/homeassistant/components/person/group.py b/homeassistant/components/person/group.py index 97e9556eaeb..e1b93696aa9 100644 --- a/homeassistant/components/person/group.py +++ b/homeassistant/components/person/group.py @@ -1,13 +1,17 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states({STATE_HOME}, STATE_NOT_HOME) diff --git a/homeassistant/components/plant/group.py b/homeassistant/components/plant/group.py index 0a29f748a76..96d4166fe1f 100644 --- a/homeassistant/components/plant/group.py +++ b/homeassistant/components/plant/group.py @@ -1,13 +1,17 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_OK, STATE_PROBLEM from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states({STATE_PROBLEM}, STATE_OK) diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index e8f100a1e8c..fffbc913fe8 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -37,8 +37,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from . import group as group_pre_import # noqa: F401 - if TYPE_CHECKING: from functools import cached_property else: diff --git a/homeassistant/components/remote/group.py b/homeassistant/components/remote/group.py deleted file mode 100644 index e1a0b18dd08..00000000000 --- a/homeassistant/components/remote/group.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Describe group states.""" - -from homeassistant.components.group import GroupIntegrationRegistry -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant, callback - - -@callback -def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry -) -> None: - """Describe group on off states.""" - registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/sensor/group.py b/homeassistant/components/sensor/group.py index 162457d336f..13a70cc4b6b 100644 --- a/homeassistant/components/sensor/group.py +++ b/homeassistant/components/sensor/group.py @@ -1,12 +1,16 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.exclude_domain() diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index e8fcaf1223e..86c67248eea 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -33,7 +33,6 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from . import group as group_pre_import # noqa: F401 from .const import DOMAIN if TYPE_CHECKING: diff --git a/homeassistant/components/switch/group.py b/homeassistant/components/switch/group.py deleted file mode 100644 index e1a0b18dd08..00000000000 --- a/homeassistant/components/switch/group.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Describe group states.""" - -from homeassistant.components.group import GroupIntegrationRegistry -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant, callback - - -@callback -def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry -) -> None: - """Describe group on off states.""" - registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/vacuum/group.py b/homeassistant/components/vacuum/group.py index e856bd460c0..3e874ec22e7 100644 --- a/homeassistant/components/vacuum/group.py +++ b/homeassistant/components/vacuum/group.py @@ -1,15 +1,18 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry from .const import STATE_CLEANING, STATE_ERROR, STATE_RETURNING @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states( diff --git a/homeassistant/components/water_heater/group.py b/homeassistant/components/water_heater/group.py index 7ae13131210..72347c8a442 100644 --- a/homeassistant/components/water_heater/group.py +++ b/homeassistant/components/water_heater/group.py @@ -1,9 +1,12 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.const import STATE_OFF from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry from .const import ( STATE_ECO, STATE_ELECTRIC, @@ -16,7 +19,7 @@ from .const import ( @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.on_off_states( diff --git a/homeassistant/components/weather/group.py b/homeassistant/components/weather/group.py index 162457d336f..13a70cc4b6b 100644 --- a/homeassistant/components/weather/group.py +++ b/homeassistant/components/weather/group.py @@ -1,12 +1,16 @@ """Describe group states.""" -from homeassistant.components.group import GroupIntegrationRegistry +from typing import TYPE_CHECKING + from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from homeassistant.components.group import GroupIntegrationRegistry + @callback def async_describe_on_off_states( - hass: HomeAssistant, registry: GroupIntegrationRegistry + hass: HomeAssistant, registry: "GroupIntegrationRegistry" ) -> None: """Describe group on off states.""" registry.exclude_domain() From 6e84dbde35dfac8c5fbc74919fb3cc3cf312d8c1 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 16 Mar 2024 02:10:24 +0100 Subject: [PATCH 1138/1691] Fix lingering hassio issues test (#113569) --- tests/components/hassio/test_issues.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 6ff3df65908..2da9d30549d 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -2,12 +2,13 @@ from __future__ import annotations -import asyncio +from datetime import timedelta from http import HTTPStatus import os from typing import Any from unittest.mock import ANY, patch +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN @@ -536,6 +537,7 @@ async def test_supervisor_issues_initial_failure( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, hass_ws_client: WebSocketGenerator, + freezer: FrozenDateTimeFactory, ) -> None: """Test issues manager retries after initial update failure.""" responses = [ @@ -600,7 +602,8 @@ async def test_supervisor_issues_initial_failure( assert msg["success"] assert len(msg["result"]["issues"]) == 0 - await asyncio.sleep(0.1) + freezer.tick(timedelta(milliseconds=200)) + await hass.async_block_till_done() await client.send_json({"id": 2, "type": "repairs/list_issues"}) msg = await client.receive_json() assert msg["success"] From bb667abd514c0246ca0bc40a7a7319f05494c566 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 15:45:43 -1000 Subject: [PATCH 1139/1691] Cleanup some circular imports in group (#113554) --- homeassistant/components/group/__init__.py | 545 +----------------- .../components/group/binary_sensor.py | 2 +- homeassistant/components/group/config_flow.py | 6 +- homeassistant/components/group/const.py | 13 + homeassistant/components/group/cover.py | 2 +- homeassistant/components/group/entity.py | 477 +++++++++++++++ homeassistant/components/group/event.py | 2 +- homeassistant/components/group/fan.py | 2 +- homeassistant/components/group/light.py | 2 +- homeassistant/components/group/lock.py | 2 +- homeassistant/components/group/registry.py | 68 +++ homeassistant/components/group/sensor.py | 4 +- homeassistant/components/group/switch.py | 2 +- 13 files changed, 588 insertions(+), 539 deletions(-) create mode 100644 homeassistant/components/group/entity.py create mode 100644 homeassistant/components/group/registry.py diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 778c4da9c9f..120c2d18290 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -2,74 +2,53 @@ from __future__ import annotations -from abc import abstractmethod import asyncio -from collections.abc import Callable, Collection, Mapping -from contextvars import ContextVar +from collections.abc import Collection import logging -from typing import Any, Protocol +from typing import Any import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ATTR_ASSUMED_STATE, - ATTR_ENTITY_ID, + ATTR_ENTITY_ID, # noqa: F401 ATTR_ICON, ATTR_NAME, CONF_ENTITIES, CONF_ICON, CONF_NAME, SERVICE_RELOAD, - STATE_OFF, - STATE_ON, Platform, ) -from homeassistant.core import ( - CALLBACK_TYPE, - Event, - HomeAssistant, - ServiceCall, - State, - callback, - split_entity_id, -) -from homeassistant.helpers import config_validation as cv, entity_registry as er, start -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.event import ( - EventStateChangedData, - async_track_state_change_event, -) from homeassistant.helpers.group import ( expand_entity_ids as _expand_entity_ids, get_entity_ids as _get_entity_ids, ) -from homeassistant.helpers.integration_platform import ( - async_process_integration_platforms, -) from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from .const import ( +from .const import ( # noqa: F401 + ATTR_ADD_ENTITIES, + ATTR_ALL, + ATTR_AUTO, + ATTR_ENTITIES, + ATTR_OBJECT_ID, + ATTR_ORDER, + ATTR_REMOVE_ENTITIES, CONF_HIDE_MEMBERS, DOMAIN, # noqa: F401 + GROUP_ORDER, + REG_KEY, ) - -GROUP_ORDER = "group_order" - -ENTITY_ID_FORMAT = DOMAIN + ".{}" +from .entity import Group, async_get_component +from .registry import GroupIntegrationRegistry, async_setup as async_setup_registry CONF_ALL = "all" -ATTR_ADD_ENTITIES = "add_entities" -ATTR_REMOVE_ENTITIES = "remove_entities" -ATTR_AUTO = "auto" -ATTR_ENTITIES = "entities" -ATTR_OBJECT_ID = "object_id" -ATTR_ORDER = "order" -ATTR_ALL = "all" SERVICE_SET = "set" SERVICE_REMOVE = "remove" @@ -86,23 +65,8 @@ PLATFORMS = [ Platform.SWITCH, ] -REG_KEY = f"{DOMAIN}_registry" - -ENTITY_PREFIX = f"{DOMAIN}." - _LOGGER = logging.getLogger(__name__) -current_domain: ContextVar[str] = ContextVar("current_domain") - - -class GroupProtocol(Protocol): - """Define the format of group platforms.""" - - def async_describe_on_off_states( - self, hass: HomeAssistant, registry: GroupIntegrationRegistry - ) -> None: - """Describe group on off states.""" - def _conf_preprocess(value: Any) -> dict[str, Any]: """Preprocess alternative configuration formats.""" @@ -129,36 +93,6 @@ CONFIG_SCHEMA = vol.Schema( ) -def _async_get_component(hass: HomeAssistant) -> EntityComponent[Group]: - if (component := hass.data.get(DOMAIN)) is None: - component = hass.data[DOMAIN] = EntityComponent[Group](_LOGGER, DOMAIN, hass) - return component - - -class GroupIntegrationRegistry: - """Class to hold a registry of integrations.""" - - on_off_mapping: dict[str, str] = {STATE_ON: STATE_OFF} - off_on_mapping: dict[str, str] = {STATE_OFF: STATE_ON} - on_states_by_domain: dict[str, set] = {} - exclude_domains: set = set() - - def exclude_domain(self) -> None: - """Exclude the current domain.""" - self.exclude_domains.add(current_domain.get()) - - def on_off_states(self, on_states: set, off_state: str) -> None: - """Register on and off states for the current domain.""" - for on_state in on_states: - if on_state not in self.on_off_mapping: - self.on_off_mapping[on_state] = off_state - - if len(on_states) == 1 and off_state not in self.off_on_mapping: - self.off_on_mapping[off_state] = list(on_states)[0] - - self.on_states_by_domain[current_domain.get()] = set(on_states) - - @bind_hass def is_on(hass: HomeAssistant, entity_id: str) -> bool: """Test if the group state is in its ON-state.""" @@ -241,11 +175,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: component: EntityComponent[Group] = hass.data[DOMAIN] - hass.data[REG_KEY] = GroupIntegrationRegistry() - - await async_process_integration_platforms( - hass, DOMAIN, _process_group_platform, wait_for_platforms=True - ) + await async_setup_registry(hass) await _async_process_config(hass, config) @@ -387,16 +317,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -@callback -def _process_group_platform( - hass: HomeAssistant, domain: str, platform: GroupProtocol -) -> None: - """Process a group platform.""" - current_domain.set(domain) - registry: GroupIntegrationRegistry = hass.data[REG_KEY] - platform.async_describe_on_off_states(hass, registry) - - async def _async_process_config(hass: HomeAssistant, config: ConfigType) -> None: """Process group configuration.""" hass.data.setdefault(GROUP_ORDER, 0) @@ -434,431 +354,4 @@ async def _async_process_config(hass: HomeAssistant, config: ConfigType) -> None hass.data[GROUP_ORDER] += 1 # If called before the platform async_setup is called (test cases) - await _async_get_component(hass).async_add_entities(entities) - - -class GroupEntity(Entity): - """Representation of a Group of entities.""" - - _unrecorded_attributes = frozenset({ATTR_ENTITY_ID}) - - _attr_should_poll = False - _entity_ids: list[str] - - @callback - def async_start_preview( - self, - preview_callback: Callable[[str, Mapping[str, Any]], None], - ) -> CALLBACK_TYPE: - """Render a preview.""" - - for entity_id in self._entity_ids: - if (state := self.hass.states.get(entity_id)) is None: - continue - self.async_update_supported_features(entity_id, state) - - @callback - def async_state_changed_listener( - event: Event[EventStateChangedData] | None, - ) -> None: - """Handle child updates.""" - self.async_update_group_state() - if event: - self.async_update_supported_features( - event.data["entity_id"], event.data["new_state"] - ) - calculated_state = self._async_calculate_state() - preview_callback(calculated_state.state, calculated_state.attributes) - - async_state_changed_listener(None) - return async_track_state_change_event( - self.hass, self._entity_ids, async_state_changed_listener - ) - - async def async_added_to_hass(self) -> None: - """Register listeners.""" - for entity_id in self._entity_ids: - if (state := self.hass.states.get(entity_id)) is None: - continue - self.async_update_supported_features(entity_id, state) - - @callback - def async_state_changed_listener( - event: Event[EventStateChangedData], - ) -> None: - """Handle child updates.""" - self.async_set_context(event.context) - self.async_update_supported_features( - event.data["entity_id"], event.data["new_state"] - ) - self.async_defer_or_update_ha_state() - - self.async_on_remove( - async_track_state_change_event( - self.hass, self._entity_ids, async_state_changed_listener - ) - ) - self.async_on_remove(start.async_at_start(self.hass, self._update_at_start)) - - @callback - def _update_at_start(self, _: HomeAssistant) -> None: - """Update the group state at start.""" - self.async_update_group_state() - self.async_write_ha_state() - - @callback - def async_defer_or_update_ha_state(self) -> None: - """Only update once at start.""" - if not self.hass.is_running: - return - - self.async_update_group_state() - self.async_write_ha_state() - - @abstractmethod - @callback - def async_update_group_state(self) -> None: - """Abstract method to update the entity.""" - - @callback - def async_update_supported_features( - self, - entity_id: str, - new_state: State | None, - ) -> None: - """Update dictionaries with supported features.""" - - -class Group(Entity): - """Track a group of entity ids.""" - - _unrecorded_attributes = frozenset({ATTR_ENTITY_ID, ATTR_ORDER, ATTR_AUTO}) - - _attr_should_poll = False - tracking: tuple[str, ...] - trackable: tuple[str, ...] - - def __init__( - self, - hass: HomeAssistant, - name: str, - *, - created_by_service: bool, - entity_ids: Collection[str] | None, - icon: str | None, - mode: bool | None, - order: int | None, - ) -> None: - """Initialize a group. - - This Object has factory function for creation. - """ - self.hass = hass - self._name = name - self._state: str | None = None - self._icon = icon - self._set_tracked(entity_ids) - self._on_off: dict[str, bool] = {} - self._assumed: dict[str, bool] = {} - self._on_states: set[str] = set() - self.created_by_service = created_by_service - self.mode = any - if mode: - self.mode = all - self._order = order - self._assumed_state = False - self._async_unsub_state_changed: CALLBACK_TYPE | None = None - - @staticmethod - @callback - def async_create_group_entity( - hass: HomeAssistant, - name: str, - *, - created_by_service: bool, - entity_ids: Collection[str] | None, - icon: str | None, - mode: bool | None, - object_id: str | None, - order: int | None, - ) -> Group: - """Create a group entity.""" - if order is None: - hass.data.setdefault(GROUP_ORDER, 0) - order = hass.data[GROUP_ORDER] - # Keep track of the group order without iterating - # every state in the state machine every time - # we setup a new group - hass.data[GROUP_ORDER] += 1 - - group = Group( - hass, - name, - created_by_service=created_by_service, - entity_ids=entity_ids, - icon=icon, - mode=mode, - order=order, - ) - - group.entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, object_id or name, hass=hass - ) - - return group - - @staticmethod - async def async_create_group( - hass: HomeAssistant, - name: str, - *, - created_by_service: bool, - entity_ids: Collection[str] | None, - icon: str | None, - mode: bool | None, - object_id: str | None, - order: int | None, - ) -> Group: - """Initialize a group. - - This method must be run in the event loop. - """ - group = Group.async_create_group_entity( - hass, - name, - created_by_service=created_by_service, - entity_ids=entity_ids, - icon=icon, - mode=mode, - object_id=object_id, - order=order, - ) - - # If called before the platform async_setup is called (test cases) - await _async_get_component(hass).async_add_entities([group]) - return group - - @property - def name(self) -> str: - """Return the name of the group.""" - return self._name - - @name.setter - def name(self, value: str) -> None: - """Set Group name.""" - self._name = value - - @property - def state(self) -> str | None: - """Return the state of the group.""" - return self._state - - @property - def icon(self) -> str | None: - """Return the icon of the group.""" - return self._icon - - @icon.setter - def icon(self, value: str | None) -> None: - """Set Icon for group.""" - self._icon = value - - @property - def extra_state_attributes(self) -> dict[str, Any]: - """Return the state attributes for the group.""" - data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order} - if self.created_by_service: - data[ATTR_AUTO] = True - - return data - - @property - def assumed_state(self) -> bool: - """Test if any member has an assumed state.""" - return self._assumed_state - - def update_tracked_entity_ids(self, entity_ids: Collection[str] | None) -> None: - """Update the member entity IDs.""" - asyncio.run_coroutine_threadsafe( - self.async_update_tracked_entity_ids(entity_ids), self.hass.loop - ).result() - - async def async_update_tracked_entity_ids( - self, entity_ids: Collection[str] | None - ) -> None: - """Update the member entity IDs. - - This method must be run in the event loop. - """ - self._async_stop() - self._set_tracked(entity_ids) - self._reset_tracked_state() - self._async_start() - - def _set_tracked(self, entity_ids: Collection[str] | None) -> None: - """Tuple of entities to be tracked.""" - # tracking are the entities we want to track - # trackable are the entities we actually watch - - if not entity_ids: - self.tracking = () - self.trackable = () - return - - registry: GroupIntegrationRegistry = self.hass.data[REG_KEY] - excluded_domains = registry.exclude_domains - - tracking: list[str] = [] - trackable: list[str] = [] - for ent_id in entity_ids: - ent_id_lower = ent_id.lower() - domain = split_entity_id(ent_id_lower)[0] - tracking.append(ent_id_lower) - if domain not in excluded_domains: - trackable.append(ent_id_lower) - - self.trackable = tuple(trackable) - self.tracking = tuple(tracking) - - @callback - def _async_start(self, _: HomeAssistant | None = None) -> None: - """Start tracking members and write state.""" - self._reset_tracked_state() - self._async_start_tracking() - self.async_write_ha_state() - - @callback - def _async_start_tracking(self) -> None: - """Start tracking members. - - This method must be run in the event loop. - """ - if self.trackable and self._async_unsub_state_changed is None: - self._async_unsub_state_changed = async_track_state_change_event( - self.hass, self.trackable, self._async_state_changed_listener - ) - - self._async_update_group_state() - - @callback - def _async_stop(self) -> None: - """Unregister the group from Home Assistant. - - This method must be run in the event loop. - """ - if self._async_unsub_state_changed: - self._async_unsub_state_changed() - self._async_unsub_state_changed = None - - @callback - def async_update_group_state(self) -> None: - """Query all members and determine current group state.""" - self._state = None - self._async_update_group_state() - - async def async_added_to_hass(self) -> None: - """Handle addition to Home Assistant.""" - self.async_on_remove(start.async_at_start(self.hass, self._async_start)) - - async def async_will_remove_from_hass(self) -> None: - """Handle removal from Home Assistant.""" - self._async_stop() - - async def _async_state_changed_listener( - self, event: Event[EventStateChangedData] - ) -> None: - """Respond to a member state changing. - - This method must be run in the event loop. - """ - # removed - if self._async_unsub_state_changed is None: - return - - self.async_set_context(event.context) - - if (new_state := event.data["new_state"]) is None: - # The state was removed from the state machine - self._reset_tracked_state() - - self._async_update_group_state(new_state) - self.async_write_ha_state() - - def _reset_tracked_state(self) -> None: - """Reset tracked state.""" - self._on_off = {} - self._assumed = {} - self._on_states = set() - - for entity_id in self.trackable: - if (state := self.hass.states.get(entity_id)) is not None: - self._see_state(state) - - def _see_state(self, new_state: State) -> None: - """Keep track of the state.""" - entity_id = new_state.entity_id - domain = new_state.domain - state = new_state.state - registry: GroupIntegrationRegistry = self.hass.data[REG_KEY] - self._assumed[entity_id] = bool(new_state.attributes.get(ATTR_ASSUMED_STATE)) - - if domain not in registry.on_states_by_domain: - # Handle the group of a group case - if state in registry.on_off_mapping: - self._on_states.add(state) - elif state in registry.off_on_mapping: - self._on_states.add(registry.off_on_mapping[state]) - self._on_off[entity_id] = state in registry.on_off_mapping - else: - entity_on_state = registry.on_states_by_domain[domain] - if domain in registry.on_states_by_domain: - self._on_states.update(entity_on_state) - self._on_off[entity_id] = state in entity_on_state - - @callback - def _async_update_group_state(self, tr_state: State | None = None) -> None: - """Update group state. - - Optionally you can provide the only state changed since last update - allowing this method to take shortcuts. - - This method must be run in the event loop. - """ - # To store current states of group entities. Might not be needed. - if tr_state: - self._see_state(tr_state) - - if not self._on_off: - return - - if ( - tr_state is None - or self._assumed_state - and not tr_state.attributes.get(ATTR_ASSUMED_STATE) - ): - self._assumed_state = self.mode(self._assumed.values()) - - elif tr_state.attributes.get(ATTR_ASSUMED_STATE): - self._assumed_state = True - - num_on_states = len(self._on_states) - # If all the entity domains we are tracking - # have the same on state we use this state - # and its hass.data[REG_KEY].on_off_mapping to off - if num_on_states == 1: - on_state = list(self._on_states)[0] - # If we do not have an on state for any domains - # we use None (which will be STATE_UNKNOWN) - elif num_on_states == 0: - self._state = None - return - # If the entity domains have more than one - # on state, we use STATE_ON/STATE_OFF - else: - on_state = STATE_ON - group_is_on = self.mode(self._on_off.values()) - if group_is_on: - self._state = on_state - else: - registry: GroupIntegrationRegistry = self.hass.data[REG_KEY] - self._state = registry.on_off_mapping[on_state] + await async_get_component(hass).async_add_entities(entities) diff --git a/homeassistant/components/group/binary_sensor.py b/homeassistant/components/group/binary_sensor.py index 16665e8970f..3fbadfb156c 100644 --- a/homeassistant/components/group/binary_sensor.py +++ b/homeassistant/components/group/binary_sensor.py @@ -29,7 +29,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity DEFAULT_NAME = "Binary Sensor Group" diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index 237eb570417..f3e2405d86a 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable, Coroutine, Mapping from functools import partial -from typing import TYPE_CHECKING, Any, cast +from typing import Any, cast import voluptuous as vol @@ -22,12 +22,10 @@ from homeassistant.helpers.schema_config_entry_flow import ( entity_selector_without_own_entities, ) -if TYPE_CHECKING: - from . import GroupEntity - from .binary_sensor import CONF_ALL, async_create_preview_binary_sensor from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC, DOMAIN from .cover import async_create_preview_cover +from .entity import GroupEntity from .event import async_create_preview_event from .fan import async_create_preview_fan from .light import async_create_preview_light diff --git a/homeassistant/components/group/const.py b/homeassistant/components/group/const.py index e64358181ca..0fdd429269f 100644 --- a/homeassistant/components/group/const.py +++ b/homeassistant/components/group/const.py @@ -4,3 +4,16 @@ CONF_HIDE_MEMBERS = "hide_members" CONF_IGNORE_NON_NUMERIC = "ignore_non_numeric" DOMAIN = "group" + +REG_KEY = f"{DOMAIN}_registry" + +GROUP_ORDER = "group_order" + + +ATTR_ADD_ENTITIES = "add_entities" +ATTR_REMOVE_ENTITIES = "remove_entities" +ATTR_AUTO = "auto" +ATTR_ENTITIES = "entities" +ATTR_OBJECT_ID = "object_id" +ATTR_ORDER = "order" +ATTR_ALL = "all" diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index 8d521314f96..02e5ebbc7cd 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -43,7 +43,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity from .util import reduce_attribute KEY_OPEN_CLOSE = "open_close" diff --git a/homeassistant/components/group/entity.py b/homeassistant/components/group/entity.py new file mode 100644 index 00000000000..3df068f5e23 --- /dev/null +++ b/homeassistant/components/group/entity.py @@ -0,0 +1,477 @@ +"""Provide entity classes for group entities.""" + +from __future__ import annotations + +from abc import abstractmethod +import asyncio +from collections.abc import Callable, Collection, Mapping +import logging +from typing import Any + +from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_ON +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + HomeAssistant, + State, + callback, + split_entity_id, +) +from homeassistant.helpers import start +from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.helpers.event import ( + EventStateChangedData, + async_track_state_change_event, +) + +from .const import ( + ATTR_AUTO, + ATTR_ORDER, + DOMAIN, # noqa: F401 + GROUP_ORDER, + REG_KEY, +) +from .registry import GroupIntegrationRegistry + +ENTITY_ID_FORMAT = DOMAIN + ".{}" + +_PACKAGE_LOGGER = logging.getLogger(__package__) + +_LOGGER = logging.getLogger(__name__) + + +class GroupEntity(Entity): + """Representation of a Group of entities.""" + + _unrecorded_attributes = frozenset({ATTR_ENTITY_ID}) + + _attr_should_poll = False + _entity_ids: list[str] + + @callback + def async_start_preview( + self, + preview_callback: Callable[[str, Mapping[str, Any]], None], + ) -> CALLBACK_TYPE: + """Render a preview.""" + + for entity_id in self._entity_ids: + if (state := self.hass.states.get(entity_id)) is None: + continue + self.async_update_supported_features(entity_id, state) + + @callback + def async_state_changed_listener( + event: Event[EventStateChangedData] | None, + ) -> None: + """Handle child updates.""" + self.async_update_group_state() + if event: + self.async_update_supported_features( + event.data["entity_id"], event.data["new_state"] + ) + calculated_state = self._async_calculate_state() + preview_callback(calculated_state.state, calculated_state.attributes) + + async_state_changed_listener(None) + return async_track_state_change_event( + self.hass, self._entity_ids, async_state_changed_listener + ) + + async def async_added_to_hass(self) -> None: + """Register listeners.""" + for entity_id in self._entity_ids: + if (state := self.hass.states.get(entity_id)) is None: + continue + self.async_update_supported_features(entity_id, state) + + @callback + def async_state_changed_listener( + event: Event[EventStateChangedData], + ) -> None: + """Handle child updates.""" + self.async_set_context(event.context) + self.async_update_supported_features( + event.data["entity_id"], event.data["new_state"] + ) + self.async_defer_or_update_ha_state() + + self.async_on_remove( + async_track_state_change_event( + self.hass, self._entity_ids, async_state_changed_listener + ) + ) + self.async_on_remove(start.async_at_start(self.hass, self._update_at_start)) + + @callback + def _update_at_start(self, _: HomeAssistant) -> None: + """Update the group state at start.""" + self.async_update_group_state() + self.async_write_ha_state() + + @callback + def async_defer_or_update_ha_state(self) -> None: + """Only update once at start.""" + if not self.hass.is_running: + return + + self.async_update_group_state() + self.async_write_ha_state() + + @abstractmethod + @callback + def async_update_group_state(self) -> None: + """Abstract method to update the entity.""" + + @callback + def async_update_supported_features( + self, + entity_id: str, + new_state: State | None, + ) -> None: + """Update dictionaries with supported features.""" + + +class Group(Entity): + """Track a group of entity ids.""" + + _unrecorded_attributes = frozenset({ATTR_ENTITY_ID, ATTR_ORDER, ATTR_AUTO}) + + _attr_should_poll = False + tracking: tuple[str, ...] + trackable: tuple[str, ...] + + def __init__( + self, + hass: HomeAssistant, + name: str, + *, + created_by_service: bool, + entity_ids: Collection[str] | None, + icon: str | None, + mode: bool | None, + order: int | None, + ) -> None: + """Initialize a group. + + This Object has factory function for creation. + """ + self.hass = hass + self._name = name + self._state: str | None = None + self._icon = icon + self._set_tracked(entity_ids) + self._on_off: dict[str, bool] = {} + self._assumed: dict[str, bool] = {} + self._on_states: set[str] = set() + self.created_by_service = created_by_service + self.mode = any + if mode: + self.mode = all + self._order = order + self._assumed_state = False + self._async_unsub_state_changed: CALLBACK_TYPE | None = None + + @staticmethod + @callback + def async_create_group_entity( + hass: HomeAssistant, + name: str, + *, + created_by_service: bool, + entity_ids: Collection[str] | None, + icon: str | None, + mode: bool | None, + object_id: str | None, + order: int | None, + ) -> Group: + """Create a group entity.""" + if order is None: + hass.data.setdefault(GROUP_ORDER, 0) + order = hass.data[GROUP_ORDER] + # Keep track of the group order without iterating + # every state in the state machine every time + # we setup a new group + hass.data[GROUP_ORDER] += 1 + + group = Group( + hass, + name, + created_by_service=created_by_service, + entity_ids=entity_ids, + icon=icon, + mode=mode, + order=order, + ) + + group.entity_id = async_generate_entity_id( + ENTITY_ID_FORMAT, object_id or name, hass=hass + ) + + return group + + @staticmethod + async def async_create_group( + hass: HomeAssistant, + name: str, + *, + created_by_service: bool, + entity_ids: Collection[str] | None, + icon: str | None, + mode: bool | None, + object_id: str | None, + order: int | None, + ) -> Group: + """Initialize a group. + + This method must be run in the event loop. + """ + group = Group.async_create_group_entity( + hass, + name, + created_by_service=created_by_service, + entity_ids=entity_ids, + icon=icon, + mode=mode, + object_id=object_id, + order=order, + ) + + # If called before the platform async_setup is called (test cases) + await async_get_component(hass).async_add_entities([group]) + return group + + @property + def name(self) -> str: + """Return the name of the group.""" + return self._name + + @name.setter + def name(self, value: str) -> None: + """Set Group name.""" + self._name = value + + @property + def state(self) -> str | None: + """Return the state of the group.""" + return self._state + + @property + def icon(self) -> str | None: + """Return the icon of the group.""" + return self._icon + + @icon.setter + def icon(self, value: str | None) -> None: + """Set Icon for group.""" + self._icon = value + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Return the state attributes for the group.""" + data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order} + if self.created_by_service: + data[ATTR_AUTO] = True + + return data + + @property + def assumed_state(self) -> bool: + """Test if any member has an assumed state.""" + return self._assumed_state + + def update_tracked_entity_ids(self, entity_ids: Collection[str] | None) -> None: + """Update the member entity IDs.""" + asyncio.run_coroutine_threadsafe( + self.async_update_tracked_entity_ids(entity_ids), self.hass.loop + ).result() + + async def async_update_tracked_entity_ids( + self, entity_ids: Collection[str] | None + ) -> None: + """Update the member entity IDs. + + This method must be run in the event loop. + """ + self._async_stop() + self._set_tracked(entity_ids) + self._reset_tracked_state() + self._async_start() + + def _set_tracked(self, entity_ids: Collection[str] | None) -> None: + """Tuple of entities to be tracked.""" + # tracking are the entities we want to track + # trackable are the entities we actually watch + + if not entity_ids: + self.tracking = () + self.trackable = () + return + + registry: GroupIntegrationRegistry = self.hass.data[REG_KEY] + excluded_domains = registry.exclude_domains + + tracking: list[str] = [] + trackable: list[str] = [] + for ent_id in entity_ids: + ent_id_lower = ent_id.lower() + domain = split_entity_id(ent_id_lower)[0] + tracking.append(ent_id_lower) + if domain not in excluded_domains: + trackable.append(ent_id_lower) + + self.trackable = tuple(trackable) + self.tracking = tuple(tracking) + + @callback + def _async_start(self, _: HomeAssistant | None = None) -> None: + """Start tracking members and write state.""" + self._reset_tracked_state() + self._async_start_tracking() + self.async_write_ha_state() + + @callback + def _async_start_tracking(self) -> None: + """Start tracking members. + + This method must be run in the event loop. + """ + if self.trackable and self._async_unsub_state_changed is None: + self._async_unsub_state_changed = async_track_state_change_event( + self.hass, self.trackable, self._async_state_changed_listener + ) + + self._async_update_group_state() + + @callback + def _async_stop(self) -> None: + """Unregister the group from Home Assistant. + + This method must be run in the event loop. + """ + if self._async_unsub_state_changed: + self._async_unsub_state_changed() + self._async_unsub_state_changed = None + + @callback + def async_update_group_state(self) -> None: + """Query all members and determine current group state.""" + self._state = None + self._async_update_group_state() + + async def async_added_to_hass(self) -> None: + """Handle addition to Home Assistant.""" + self.async_on_remove(start.async_at_start(self.hass, self._async_start)) + + async def async_will_remove_from_hass(self) -> None: + """Handle removal from Home Assistant.""" + self._async_stop() + + async def _async_state_changed_listener( + self, event: Event[EventStateChangedData] + ) -> None: + """Respond to a member state changing. + + This method must be run in the event loop. + """ + # removed + if self._async_unsub_state_changed is None: + return + + self.async_set_context(event.context) + + if (new_state := event.data["new_state"]) is None: + # The state was removed from the state machine + self._reset_tracked_state() + + self._async_update_group_state(new_state) + self.async_write_ha_state() + + def _reset_tracked_state(self) -> None: + """Reset tracked state.""" + self._on_off = {} + self._assumed = {} + self._on_states = set() + + for entity_id in self.trackable: + if (state := self.hass.states.get(entity_id)) is not None: + self._see_state(state) + + def _see_state(self, new_state: State) -> None: + """Keep track of the state.""" + entity_id = new_state.entity_id + domain = new_state.domain + state = new_state.state + registry: GroupIntegrationRegistry = self.hass.data[REG_KEY] + self._assumed[entity_id] = bool(new_state.attributes.get(ATTR_ASSUMED_STATE)) + + if domain not in registry.on_states_by_domain: + # Handle the group of a group case + if state in registry.on_off_mapping: + self._on_states.add(state) + elif state in registry.off_on_mapping: + self._on_states.add(registry.off_on_mapping[state]) + self._on_off[entity_id] = state in registry.on_off_mapping + else: + entity_on_state = registry.on_states_by_domain[domain] + if domain in registry.on_states_by_domain: + self._on_states.update(entity_on_state) + self._on_off[entity_id] = state in entity_on_state + + @callback + def _async_update_group_state(self, tr_state: State | None = None) -> None: + """Update group state. + + Optionally you can provide the only state changed since last update + allowing this method to take shortcuts. + + This method must be run in the event loop. + """ + # To store current states of group entities. Might not be needed. + if tr_state: + self._see_state(tr_state) + + if not self._on_off: + return + + if ( + tr_state is None + or self._assumed_state + and not tr_state.attributes.get(ATTR_ASSUMED_STATE) + ): + self._assumed_state = self.mode(self._assumed.values()) + + elif tr_state.attributes.get(ATTR_ASSUMED_STATE): + self._assumed_state = True + + num_on_states = len(self._on_states) + # If all the entity domains we are tracking + # have the same on state we use this state + # and its hass.data[REG_KEY].on_off_mapping to off + if num_on_states == 1: + on_state = list(self._on_states)[0] + # If we do not have an on state for any domains + # we use None (which will be STATE_UNKNOWN) + elif num_on_states == 0: + self._state = None + return + # If the entity domains have more than one + # on state, we use STATE_ON/STATE_OFF + else: + on_state = STATE_ON + group_is_on = self.mode(self._on_off.values()) + if group_is_on: + self._state = on_state + else: + registry: GroupIntegrationRegistry = self.hass.data[REG_KEY] + self._state = registry.on_off_mapping[on_state] + + +def async_get_component(hass: HomeAssistant) -> EntityComponent[Group]: + """Get the group entity component.""" + if (component := hass.data.get(DOMAIN)) is None: + component = hass.data[DOMAIN] = EntityComponent[Group]( + _PACKAGE_LOGGER, DOMAIN, hass + ) + return component diff --git a/homeassistant/components/group/event.py b/homeassistant/components/group/event.py index 8095a0e89c1..61ddb3e0645 100644 --- a/homeassistant/components/group/event.py +++ b/homeassistant/components/group/event.py @@ -34,7 +34,7 @@ from homeassistant.helpers.event import ( ) from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity DEFAULT_NAME = "Event group" diff --git a/homeassistant/components/group/fan.py b/homeassistant/components/group/fan.py index c8add0b6724..b70a4ff1531 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -40,7 +40,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity from .util import attribute_equal, most_frequent_attribute, reduce_attribute SUPPORTED_FLAGS = { diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index d014ca5d618..9adced828c7 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -51,7 +51,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity from .util import find_state_attributes, mean_tuple, reduce_attribute DEFAULT_NAME = "Light Group" diff --git a/homeassistant/components/group/lock.py b/homeassistant/components/group/lock.py index 08c2c053b0e..b0cf36bd6b1 100644 --- a/homeassistant/components/group/lock.py +++ b/homeassistant/components/group/lock.py @@ -34,7 +34,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity DEFAULT_NAME = "Lock Group" diff --git a/homeassistant/components/group/registry.py b/homeassistant/components/group/registry.py new file mode 100644 index 00000000000..1441d39d331 --- /dev/null +++ b/homeassistant/components/group/registry.py @@ -0,0 +1,68 @@ +"""Provide the functionality to group entities.""" + +from __future__ import annotations + +from contextvars import ContextVar +from typing import Protocol + +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.integration_platform import ( + async_process_integration_platforms, +) + +from .const import DOMAIN, REG_KEY + +current_domain: ContextVar[str] = ContextVar("current_domain") + + +async def async_setup(hass: HomeAssistant) -> None: + """Set up the Group integration registry of integration platforms.""" + hass.data[REG_KEY] = GroupIntegrationRegistry() + + await async_process_integration_platforms( + hass, DOMAIN, _process_group_platform, wait_for_platforms=True + ) + + +class GroupProtocol(Protocol): + """Define the format of group platforms.""" + + def async_describe_on_off_states( + self, hass: HomeAssistant, registry: GroupIntegrationRegistry + ) -> None: + """Describe group on off states.""" + + +@callback +def _process_group_platform( + hass: HomeAssistant, domain: str, platform: GroupProtocol +) -> None: + """Process a group platform.""" + current_domain.set(domain) + registry: GroupIntegrationRegistry = hass.data[REG_KEY] + platform.async_describe_on_off_states(hass, registry) + + +class GroupIntegrationRegistry: + """Class to hold a registry of integrations.""" + + on_off_mapping: dict[str, str] = {STATE_ON: STATE_OFF} + off_on_mapping: dict[str, str] = {STATE_OFF: STATE_ON} + on_states_by_domain: dict[str, set] = {} + exclude_domains: set = set() + + def exclude_domain(self) -> None: + """Exclude the current domain.""" + self.exclude_domains.add(current_domain.get()) + + def on_off_states(self, on_states: set, off_state: str) -> None: + """Register on and off states for the current domain.""" + for on_state in on_states: + if on_state not in self.on_off_mapping: + self.on_off_mapping[on_state] = off_state + + if len(on_states) == 1 and off_state not in self.off_on_mapping: + self.off_on_mapping[off_state] = list(on_states)[0] + + self.on_states_by_domain[current_domain.get()] = set(on_states) diff --git a/homeassistant/components/group/sensor.py b/homeassistant/components/group/sensor.py index 7334831211d..5de668c7bb0 100644 --- a/homeassistant/components/group/sensor.py +++ b/homeassistant/components/group/sensor.py @@ -52,8 +52,8 @@ from homeassistant.helpers.issue_registry import ( ) from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType -from . import DOMAIN as GROUP_DOMAIN, GroupEntity -from .const import CONF_IGNORE_NON_NUMERIC +from .const import CONF_IGNORE_NON_NUMERIC, DOMAIN as GROUP_DOMAIN +from .entity import GroupEntity DEFAULT_NAME = "Sensor Group" diff --git a/homeassistant/components/group/switch.py b/homeassistant/components/group/switch.py index ec70f137b33..7be6b188e72 100644 --- a/homeassistant/components/group/switch.py +++ b/homeassistant/components/group/switch.py @@ -25,7 +25,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import GroupEntity +from .entity import GroupEntity DEFAULT_NAME = "Switch Group" CONF_ALL = "all" From 27e844e3bf987150e91f8070010824199aaf2b73 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 04:48:37 +0100 Subject: [PATCH 1140/1691] Add ruff B005, B015, B032 and fix occurrences (#113543) Co-authored-by: Jan Bouwhuis Co-authored-by: J. Nick Koston --- .../components/fronius/config_flow.py | 2 +- homeassistant/components/roomba/config_flow.py | 2 +- pyproject.toml | 3 +++ tests/components/alexa/test_state_report.py | 2 +- tests/components/bluetooth/test_wrappers.py | 2 +- .../esphome/bluetooth/test_client.py | 2 +- .../components/evil_genius_labs/test_light.py | 12 ++++++------ tests/components/google/test_init.py | 2 +- .../test_silabs_multiprotocol_addon.py | 18 ++++++++++++------ .../media_player/test_browse_media.py | 2 +- tests/components/recorder/test_init.py | 2 +- tests/components/withings/test_init.py | 2 +- tests/components/zha/test_gateway.py | 2 +- tests/components/zha/test_websocket_api.py | 2 +- tests/helpers/test_aiohttp_client.py | 4 ++-- 15 files changed, 34 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/fronius/config_flow.py b/homeassistant/components/fronius/config_flow.py index 22bfc4a67f4..2b46d226b7a 100644 --- a/homeassistant/components/fronius/config_flow.py +++ b/homeassistant/components/fronius/config_flow.py @@ -115,7 +115,7 @@ class FroniusConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Handle a flow initiated by the DHCP client.""" for entry in self._async_current_entries(include_ignore=False): - if entry.data[CONF_HOST].lstrip("http://").rstrip("/").lower() in ( + if entry.data[CONF_HOST].removeprefix("http://").rstrip("/").lower() in ( discovery_info.ip, discovery_info.hostname, ): diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 08973b415ee..7b834421135 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -95,7 +95,7 @@ class RoombaConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Handle zeroconf discovery.""" return await self._async_step_discovery( - discovery_info.host, discovery_info.hostname.lower().rstrip(".local.") + discovery_info.host, discovery_info.hostname.lower().removesuffix(".local.") ) async def async_step_dhcp( diff --git a/pyproject.toml b/pyproject.toml index bc5c3c53b85..6cfd01a7c7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -580,10 +580,13 @@ filterwarnings = [ [tool.ruff.lint] select = [ "B002", # Python does not support the unary prefix increment + "B005", # Using .strip() with multi-character strings is misleading "B007", # Loop control variable {name} not used within loop body "B014", # Exception handler with duplicate exception + "B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. "B023", # Function definition does not bind loop variable {name} "B026", # Star-arg unpacking after a keyword argument is strongly discouraged + "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)? "B904", # Use raise from to specify exception cause "C", # complexity "COM818", # Trailing comma on bare tuple prohibited diff --git a/tests/components/alexa/test_state_report.py b/tests/components/alexa/test_state_report.py index c851b7991ac..6bd7caccc38 100644 --- a/tests/components/alexa/test_state_report.py +++ b/tests/components/alexa/test_state_report.py @@ -410,7 +410,7 @@ async def test_report_state_number( } if unit: - state["unit_of_measurement"]: unit + state["unit_of_measurement"] = unit hass.states.async_set( f"{domain}.test_{domain}", diff --git a/tests/components/bluetooth/test_wrappers.py b/tests/components/bluetooth/test_wrappers.py index 9a191cdbf90..1fc42ebf187 100644 --- a/tests/components/bluetooth/test_wrappers.py +++ b/tests/components/bluetooth/test_wrappers.py @@ -244,7 +244,7 @@ async def test_test_switch_adapters_when_out_of_slots( ) as allocate_slot_mock: ble_device = hci0_device_advs["00:00:00:00:00:03"][0] client = bleak.BleakClient(ble_device) - await client.connect() is True + assert await client.connect() is True assert release_slot_mock.call_count == 0 cancel_hci0() diff --git a/tests/components/esphome/bluetooth/test_client.py b/tests/components/esphome/bluetooth/test_client.py index 2898ae2bda2..98993be37d0 100644 --- a/tests/components/esphome/bluetooth/test_client.py +++ b/tests/components/esphome/bluetooth/test_client.py @@ -55,4 +55,4 @@ async def test_client_usage_while_not_connected(client_data: ESPHomeClientData) with pytest.raises( BleakError, match=f"{ESP_NAME}.*{ESP_MAC_ADDRESS}.*not connected" ): - await client.write_gatt_char("test", b"test") is False + assert await client.write_gatt_char("test", b"test") is False diff --git a/tests/components/evil_genius_labs/test_light.py b/tests/components/evil_genius_labs/test_light.py index cbc68ccf2d7..e0ecb5415a7 100644 --- a/tests/components/evil_genius_labs/test_light.py +++ b/tests/components/evil_genius_labs/test_light.py @@ -46,11 +46,11 @@ async def test_turn_on_color(hass: HomeAssistant, setup_evil_genius_labs) -> Non ) assert len(mock_set_path_value.mock_calls) == 2 - mock_set_path_value.mock_calls[0][1] == ("brightness", 100) - mock_set_path_value.mock_calls[1][1] == ("power", 1) + assert mock_set_path_value.mock_calls[0][1] == ("brightness", 100) + assert mock_set_path_value.mock_calls[1][1] == ("power", 1) assert len(mock_set_rgb_color.mock_calls) == 1 - mock_set_rgb_color.mock_calls[0][1] == (10, 20, 30) + assert mock_set_rgb_color.mock_calls[0][1] == (10, 20, 30) @pytest.mark.parametrize("platforms", [("light",)]) @@ -68,8 +68,8 @@ async def test_turn_on_effect(hass: HomeAssistant, setup_evil_genius_labs) -> No ) assert len(mock_set_path_value.mock_calls) == 2 - mock_set_path_value.mock_calls[0][1] == ("pattern", 4) - mock_set_path_value.mock_calls[1][1] == ("power", 1) + assert mock_set_path_value.mock_calls[0][1] == ("pattern", 4) + assert mock_set_path_value.mock_calls[1][1] == ("power", 1) @pytest.mark.parametrize("platforms", [("light",)]) @@ -86,4 +86,4 @@ async def test_turn_off(hass: HomeAssistant, setup_evil_genius_labs) -> None: ) assert len(mock_set_path_value.mock_calls) == 1 - mock_set_path_value.mock_calls[0][1] == ("power", 0) + assert mock_set_path_value.mock_calls[0][1] == ("power", 0) diff --git a/tests/components/google/test_init.py b/tests/components/google/test_init.py index c51ecf593e3..319f6be5012 100644 --- a/tests/components/google/test_init.py +++ b/tests/components/google/test_init.py @@ -816,7 +816,7 @@ async def test_calendar_yaml_update( assert await component_setup() mock_calendars_yaml().read.assert_called() - mock_calendars_yaml().write.called is expect_write_calls + assert mock_calendars_yaml().write.called is expect_write_calls state = hass.states.get(TEST_API_ENTITY) assert state diff --git a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py index 6d1efc79df4..6221ac3ecb5 100644 --- a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py +++ b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py @@ -1719,9 +1719,12 @@ async def test_multi_pan_addon_using_device_not_running( "state": "not_running", } - await silabs_multiprotocol_addon.multi_pan_addon_using_device( - hass, "/dev/ttyAMA1" - ) is False + assert ( + await silabs_multiprotocol_addon.multi_pan_addon_using_device( + hass, "/dev/ttyAMA1" + ) + is False + ) @pytest.mark.parametrize( @@ -1750,6 +1753,9 @@ async def test_multi_pan_addon_using_device( "state": "running", } - await silabs_multiprotocol_addon.multi_pan_addon_using_device( - hass, "/dev/ttyAMA1" - ) is expected_result + assert ( + await silabs_multiprotocol_addon.multi_pan_addon_using_device( + hass, "/dev/ttyAMA1" + ) + is expected_result + ) diff --git a/tests/components/media_player/test_browse_media.py b/tests/components/media_player/test_browse_media.py index 8c5554793a6..e3c5614d815 100644 --- a/tests/components/media_player/test_browse_media.py +++ b/tests/components/media_player/test_browse_media.py @@ -88,7 +88,7 @@ async def test_process_play_media_url(hass: HomeAssistant, mock_sign_path) -> No ) # Not changing a URL which is not absolute and does not start with / - async_process_play_media_url(hass, "hello") == "hello" + assert async_process_play_media_url(hass, "hello") == "hello" async def test_process_play_media_url_for_addon( diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 96814da5171..238f6420c78 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -543,7 +543,7 @@ def test_saving_state_with_commit_interval_zero( ) -> None: """Test saving a state with a commit interval of zero.""" hass = hass_recorder({"commit_interval": 0}) - get_instance(hass).commit_interval == 0 + assert get_instance(hass).commit_interval == 0 entity_id = "test.recorder" state = "restoring_from_db" diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index 4caf83bef91..e5fa2dcb9fd 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -412,7 +412,7 @@ async def test_setup_with_cloud( for config_entry in hass.config_entries.async_entries("withings"): await hass.config_entries.async_remove(config_entry.entry_id) - fake_delete_cloudhook.call_count == 2 + assert fake_delete_cloudhook.call_count == 2 await hass.async_block_till_done() assert not hass.config_entries.async_entries(DOMAIN) diff --git a/tests/components/zha/test_gateway.py b/tests/components/zha/test_gateway.py index 5ae1cc0e7e5..182cc2c4752 100644 --- a/tests/components/zha/test_gateway.py +++ b/tests/components/zha/test_gateway.py @@ -264,7 +264,7 @@ async def test_gateway_initialize_bellows_thread( ) as mock_new: await zha_gateway.async_initialize() - mock_new.mock_calls[-1].kwargs["config"]["use_thread"] is thread_state + assert mock_new.mock_calls[-1].kwargs["config"]["use_thread"] is thread_state await zha_gateway.shutdown() diff --git a/tests/components/zha/test_websocket_api.py b/tests/components/zha/test_websocket_api.py index 041329123f8..73dc63258b6 100644 --- a/tests/components/zha/test_websocket_api.py +++ b/tests/components/zha/test_websocket_api.py @@ -938,7 +938,7 @@ async def test_websocket_change_channel( assert msg["type"] == const.TYPE_RESULT assert msg["success"] - change_channel_mock.mock_calls == [call(ANY, new_channel)] + change_channel_mock.assert_has_calls([call(ANY, new_channel)]) @pytest.mark.parametrize( diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 7ec4e996629..73339e9fe3c 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -288,8 +288,8 @@ async def test_sending_named_tuple( session = client.async_create_clientsession(hass) resp = await session.post("http://127.0.0.1/rgb", json={"rgb": RGBColor(4, 3, 2)}) assert resp.status == 200 - await resp.json() == {"rgb": RGBColor(4, 3, 2)} - aioclient_mock.mock_calls[0][2]["rgb"] == RGBColor(4, 3, 2) + assert await resp.json() == {"rgb": [4, 3, 2]} + assert aioclient_mock.mock_calls[0][2]["rgb"] == RGBColor(4, 3, 2) async def test_client_session_immutable_headers(hass: HomeAssistant) -> None: From a5cde8a61e432687a6206ed5f49272bafab800c7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 16 Mar 2024 05:01:46 +0100 Subject: [PATCH 1141/1691] Improve typing of State.as_compressed_state (#113540) --- homeassistant/const.py | 10 +++++----- homeassistant/core.py | 27 ++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 04561f489dd..28409ba0907 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1456,11 +1456,11 @@ _DEPRECATED_DATA_RATE_GIBIBYTES_PER_SECOND: Final = DeprecatedConstantEnum( # States -COMPRESSED_STATE_STATE = "s" -COMPRESSED_STATE_ATTRIBUTES = "a" -COMPRESSED_STATE_CONTEXT = "c" -COMPRESSED_STATE_LAST_CHANGED = "lc" -COMPRESSED_STATE_LAST_UPDATED = "lu" +COMPRESSED_STATE_STATE: Final = "s" +COMPRESSED_STATE_ATTRIBUTES: Final = "a" +COMPRESSED_STATE_CONTEXT: Final = "c" +COMPRESSED_STATE_LAST_CHANGED: Final = "lc" +COMPRESSED_STATE_LAST_UPDATED: Final = "lu" # #### SERVICES #### SERVICE_TURN_ON: Final = "turn_on" diff --git a/homeassistant/core.py b/homeassistant/core.py index 7f10d8f6df5..a6262c56c81 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -31,7 +31,18 @@ import re import threading import time from time import monotonic -from typing import TYPE_CHECKING, Any, Generic, Literal, ParamSpec, Self, cast, overload +from typing import ( + TYPE_CHECKING, + Any, + Generic, + Literal, + NotRequired, + ParamSpec, + Self, + TypedDict, + cast, + overload, +) from urllib.parse import urlparse from typing_extensions import TypeVar @@ -1534,6 +1545,16 @@ class EventBus: ) +class CompressedState(TypedDict): + """Compressed dict of a state.""" + + s: str # COMPRESSED_STATE_STATE + a: ReadOnlyDict[str, Any] # COMPRESSED_STATE_ATTRIBUTES + c: str | dict[str, Any] # COMPRESSED_STATE_CONTEXT + lc: float # COMPRESSED_STATE_LAST_CHANGED + lu: NotRequired[float] # COMPRESSED_STATE_LAST_UPDATED + + class State: """Object to represent a state within the state machine. @@ -1663,7 +1684,7 @@ class State: return json_fragment(self.as_dict_json) @cached_property - def as_compressed_state(self) -> dict[str, Any]: + def as_compressed_state(self) -> CompressedState: """Build a compressed dict of a state for adds. Omits the lu (last_updated) if it matches (lc) last_changed. @@ -1678,7 +1699,7 @@ class State: # to avoid callers outside of this module # from misusing it by mistake. context = state_context._as_dict # pylint: disable=protected-access - compressed_state = { + compressed_state: CompressedState = { COMPRESSED_STATE_STATE: self.state, COMPRESSED_STATE_ATTRIBUTES: self.attributes, COMPRESSED_STATE_CONTEXT: context, From 702488062b1aa773cd2101d0f6bac48489a30f0d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 18:44:10 -1000 Subject: [PATCH 1142/1691] Move group config flow pre-import to its init (#113564) --- homeassistant/bootstrap.py | 7 ------- homeassistant/components/group/__init__.py | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 1a417a7fbac..1d6fdcf36d4 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -51,13 +51,6 @@ from .components import ( webhook as webhook_pre_import, # noqa: F401 websocket_api as websocket_api_pre_import, # noqa: F401 ) - -# Ensure group config_flow is imported so it does not need the import -# executor since config_flows are preloaded when the component is loaded. -# Even though group is pre-imported above we would have still had to wait -# for the config flow to be imported when the import executor is the most -# busy. -from .components.group import config_flow as group_config_flow # noqa: F401 from .components.sensor import recorder as sensor_recorder # noqa: F401 from .const import ( FORMAT_DATETIME, diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 120c2d18290..330eafa110c 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -31,6 +31,12 @@ from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass +# Ensure group config_flow is imported so it does not need the import +# executor since config_flows are preloaded when the component is loaded. +# Even though group is pre-imported above we would have still had to wait +# for the config flow to be imported when the import executor is the most +# busy. +from . import config_flow as config_flow_pre_import # noqa: F401 from .const import ( # noqa: F401 ATTR_ADD_ENTITIES, ATTR_ALL, From eb90c9a54815379fa641f37c62b899991404c9ae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 22:38:06 -1000 Subject: [PATCH 1143/1691] Reduce bottlenecks in bootstrap by ordering the setup of integrations (#113570) --- homeassistant/bootstrap.py | 75 +++++++++++++---- homeassistant/config.py | 13 ++- tests/test_bootstrap.py | 162 +++++++++++++++++++++++++++++++++++++ tests/test_config.py | 13 ++- 4 files changed, 236 insertions(+), 27 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 1d6fdcf36d4..89e36e7e557 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -5,9 +5,11 @@ from __future__ import annotations import asyncio import contextlib from datetime import timedelta +from functools import partial +from itertools import chain import logging import logging.handlers -from operator import itemgetter +from operator import contains, itemgetter import os import platform import sys @@ -94,6 +96,9 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +SETUP_ORDER_SORT_KEY = partial(contains, BASE_PLATFORMS) + + ERROR_LOG_FILENAME = "home-assistant.log" # hass.data key for logging information. @@ -664,13 +669,18 @@ async def async_setup_multi_components( """Set up multiple domains. Log on failure.""" # Avoid creating tasks for domains that were setup in a previous stage domains_not_yet_setup = domains - hass.config.components + # Create setup tasks for base platforms first since everything will have + # to wait to be imported, and the sooner we can get the base platforms + # loaded the sooner we can start loading the rest of the integrations. futures = { domain: hass.async_create_task( async_setup_component(hass, domain, config), f"setup component {domain}", eager_start=True, ) - for domain in domains_not_yet_setup + for domain in sorted( + domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=True + ) } results = await asyncio.gather(*futures.values(), return_exceptions=True) for idx, domain in enumerate(futures): @@ -687,29 +697,53 @@ async def _async_resolve_domains_to_setup( hass: core.HomeAssistant, config: dict[str, Any] ) -> tuple[set[str], dict[str, loader.Integration]]: """Resolve all dependencies and return list of domains to set up.""" - base_platforms_loaded = False domains_to_setup = _get_domains(hass, config) needed_requirements: set[str] = set() platform_integrations = conf_util.extract_platform_integrations( config, BASE_PLATFORMS ) + # Ensure base platforms that have platform integrations are added to + # to `domains_to_setup so they can be setup first instead of + # discovering them when later when a config entry setup task + # notices its needed and there is already a long line to use + # the import executor. + # + # For example if we have + # sensor: + # - platform: template + # + # `template` has to be loaded to validate the config for sensor + # so we want to start loading `sensor` as soon as we know + # it will be needed. The more platforms under `sensor:`, the longer + # it will take to finish setup for `sensor` because each of these + # platforms has to be imported before we can validate the config. + # + # Thankfully we are migrating away from the platform pattern + # so this will be less of a problem in the future. + domains_to_setup.update(platform_integrations) + + # Load manifests for base platforms and platform based integrations + # that are defined under base platforms right away since we do not require + # the manifest to list them as dependencies and we want to avoid the lock + # contention when multiple integrations try to load them at once + additional_manifests_to_load = { + *BASE_PLATFORMS, + *chain.from_iterable(platform_integrations.values()), + } + + translations_to_load = {*domains_to_setup, *additional_manifests_to_load} # Resolve all dependencies so we know all integrations - # that will have to be loaded and start rightaway + # that will have to be loaded and start right-away integration_cache: dict[str, loader.Integration] = {} to_resolve: set[str] = domains_to_setup - while to_resolve: + while to_resolve or additional_manifests_to_load: old_to_resolve: set[str] = to_resolve to_resolve = set() - if not base_platforms_loaded: - # Load base platforms right away since - # we do not require the manifest to list - # them as dependencies and we want - # to avoid the lock contention when multiple - # integrations try to resolve them at once - base_platforms_loaded = True - to_get = {*old_to_resolve, *BASE_PLATFORMS, *platform_integrations} + if additional_manifests_to_load: + to_get = {*old_to_resolve, *additional_manifests_to_load} + additional_manifests_to_load.clear() else: to_get = old_to_resolve @@ -722,6 +756,17 @@ async def _async_resolve_domains_to_setup( continue integration_cache[domain] = itg needed_requirements.update(itg.requirements) + + # Make sure manifests for dependencies are loaded in the next + # loop to try to group as many as manifest loads in a single + # call to avoid the creating one-off executor jobs later in + # the setup process + additional_manifests_to_load.update( + dep + for dep in chain(itg.dependencies, itg.after_dependencies) + if dep not in integration_cache + ) + if domain not in old_to_resolve: continue @@ -781,9 +826,7 @@ async def _async_resolve_domains_to_setup( # wait for the translation load lock, loading will be done by the # time it gets to it. hass.async_create_background_task( - translation.async_load_integrations( - hass, {*BASE_PLATFORMS, *platform_integrations, *domains_to_setup} - ), + translation.async_load_integrations(hass, translations_to_load), "load translations", eager_start=True, ) diff --git a/homeassistant/config.py b/homeassistant/config.py index 92ed7556445..46f213759b8 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1391,9 +1391,14 @@ def config_per_platform( yield platform, item -def extract_platform_integrations(config: ConfigType, domains: set[str]) -> set[str]: - """Find all the platforms in a configuration.""" - platform_integrations: set[str] = set() +def extract_platform_integrations( + config: ConfigType, domains: set[str] +) -> dict[str, set[str]]: + """Find all the platforms in a configuration. + + Returns a dictionary with domain as key and a set of platforms as value. + """ + platform_integrations: dict[str, set[str]] = {} for key, domain_config in config.items(): try: domain = cv.domain_key(key) @@ -1411,7 +1416,7 @@ def extract_platform_integrations(config: ConfigType, domains: set[str]) -> set[ except AttributeError: continue if platform: - platform_integrations.add(platform) + platform_integrations.setdefault(domain, set()).add(platform) return platform_integrations diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 4f3bcb74d7d..996126ad1b6 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -222,6 +222,93 @@ async def test_setup_after_deps_in_stage_1_ignored(hass: HomeAssistant) -> None: assert order == ["cloud", "an_after_dep", "normal_integration"] +@pytest.mark.parametrize("load_registries", [False]) +async def test_setup_after_deps_manifests_are_loaded_even_if_not_setup( + hass: HomeAssistant, +) -> None: + """Ensure we preload manifests for after deps even if they are not setup. + + Its important that we preload the after dep manifests even if they are not setup + since we will always have to check their requirements since any integration + that lists an after dep may import it and we have to ensure requirements are + up to date before the after dep can be imported. + """ + # This test relies on this + assert "cloud" in bootstrap.STAGE_1_INTEGRATIONS + order = [] + + def gen_domain_setup(domain): + async def async_setup(hass, config): + order.append(domain) + return True + + return async_setup + + mock_integration( + hass, + MockModule( + domain="normal_integration", + async_setup=gen_domain_setup("normal_integration"), + partial_manifest={"after_dependencies": ["an_after_dep"]}, + ), + ) + mock_integration( + hass, + MockModule( + domain="an_after_dep", + async_setup=gen_domain_setup("an_after_dep"), + partial_manifest={"after_dependencies": ["an_after_dep_of_after_dep"]}, + ), + ) + mock_integration( + hass, + MockModule( + domain="an_after_dep_of_after_dep", + async_setup=gen_domain_setup("an_after_dep_of_after_dep"), + partial_manifest={ + "after_dependencies": ["an_after_dep_of_after_dep_of_after_dep"] + }, + ), + ) + mock_integration( + hass, + MockModule( + domain="an_after_dep_of_after_dep_of_after_dep", + async_setup=gen_domain_setup("an_after_dep_of_after_dep_of_after_dep"), + ), + ) + mock_integration( + hass, + MockModule( + domain="cloud", + async_setup=gen_domain_setup("cloud"), + partial_manifest={"after_dependencies": ["normal_integration"]}, + ), + ) + + await bootstrap._async_set_up_integrations( + hass, {"cloud": {}, "normal_integration": {}} + ) + + assert "normal_integration" in hass.config.components + assert "cloud" in hass.config.components + assert "an_after_dep" not in hass.config.components + assert "an_after_dep_of_after_dep" not in hass.config.components + assert "an_after_dep_of_after_dep_of_after_dep" not in hass.config.components + assert order == ["cloud", "normal_integration"] + assert loader.async_get_loaded_integration(hass, "an_after_dep") is not None + assert ( + loader.async_get_loaded_integration(hass, "an_after_dep_of_after_dep") + is not None + ) + assert ( + loader.async_get_loaded_integration( + hass, "an_after_dep_of_after_dep_of_after_dep" + ) + is not None + ) + + @pytest.mark.parametrize("load_registries", [False]) async def test_setup_frontend_before_recorder(hass: HomeAssistant) -> None: """Test frontend is setup before recorder.""" @@ -1193,3 +1280,78 @@ async def test_cancellation_does_not_leak_upward_from_async_setup_entry( assert "test_package" in hass.config.components assert "test_package_raises_cancelled_error_config_entry" in hass.config.components + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_setup_does_base_platforms_first(hass: HomeAssistant) -> None: + """Test setup does base platforms first. + + Its important that base platforms are setup before other integrations + in stage1/2 since they are the foundation for other integrations and + almost every integration has to wait for them to be setup. + """ + order = [] + + def gen_domain_setup(domain): + async def async_setup(hass, config): + order.append(domain) + return True + + return async_setup + + mock_integration( + hass, MockModule(domain="sensor", async_setup=gen_domain_setup("sensor")) + ) + mock_integration( + hass, + MockModule( + domain="binary_sensor", async_setup=gen_domain_setup("binary_sensor") + ), + ) + mock_integration( + hass, MockModule(domain="root", async_setup=gen_domain_setup("root")) + ) + mock_integration( + hass, + MockModule( + domain="first_dep", + async_setup=gen_domain_setup("first_dep"), + partial_manifest={"after_dependencies": ["root"]}, + ), + ) + mock_integration( + hass, + MockModule( + domain="second_dep", + async_setup=gen_domain_setup("second_dep"), + partial_manifest={"after_dependencies": ["first_dep"]}, + ), + ) + + with patch( + "homeassistant.components.logger.async_setup", gen_domain_setup("logger") + ): + await bootstrap._async_set_up_integrations( + hass, + { + "root": {}, + "first_dep": {}, + "second_dep": {}, + "sensor": {}, + "logger": {}, + "binary_sensor": {}, + }, + ) + + assert "binary_sensor" in hass.config.components + assert "sensor" in hass.config.components + assert "root" in hass.config.components + assert "first_dep" in hass.config.components + assert "second_dep" in hass.config.components + + assert order[0] == "logger" + # base platforms (sensor/binary_sensor) should be setup before other integrations + # but after logger integrations. The order of base platforms is not guaranteed, + # only that they are setup before other integrations. + assert set(order[1:3]) == {"sensor", "binary_sensor"} + assert order[3:] == ["root", "first_dep", "second_dep"] diff --git a/tests/test_config.py b/tests/test_config.py index 7a7f83b5e6b..af4b653e4f6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2361,13 +2361,12 @@ def test_extract_platform_integrations() -> None: ] ) assert config_util.extract_platform_integrations(config, {"zone"}) == { - "hello", - "hello 2", + "zone": {"hello", "hello 2"} } - assert config_util.extract_platform_integrations(config, {"zonex"}) == set() - assert config_util.extract_platform_integrations(config, {"zoney"}) == set() + assert config_util.extract_platform_integrations(config, {"zonex"}) == {} + assert config_util.extract_platform_integrations(config, {"zoney"}) == {} assert config_util.extract_platform_integrations( config, {"zone", "not_valid", "notzone"} - ) == {"hello", "hello 2", "nothello"} - assert config_util.extract_platform_integrations(config, {"zoneq"}) == set() - assert config_util.extract_platform_integrations(config, {"zoneempty"}) == set() + ) == {"zone": {"hello 2", "hello"}, "notzone": {"nothello"}} + assert config_util.extract_platform_integrations(config, {"zoneq"}) == {} + assert config_util.extract_platform_integrations(config, {"zoneempty"}) == {} From dd3101e16174505f081d60b212e6711506b9d4b7 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 11:01:24 +0100 Subject: [PATCH 1144/1691] Fix unifiprotect tests for ruff B018 (#113584) --- tests/components/unifiprotect/test_init.py | 1 - tests/components/unifiprotect/test_views.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 35477f1e56d..681f22b327e 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -70,7 +70,6 @@ async def test_setup_multiple( nvr = bootstrap.nvr nvr._api = ufp.api nvr.mac = "A1E00C826983" - nvr.id ufp.api.get_nvr = AsyncMock(return_value=nvr) with patch( diff --git a/tests/components/unifiprotect/test_views.py b/tests/components/unifiprotect/test_views.py index 2952509fea9..fb24b399124 100644 --- a/tests/components/unifiprotect/test_views.py +++ b/tests/components/unifiprotect/test_views.py @@ -37,7 +37,7 @@ async def test_thumbnail_bad_nvr_id( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 404 - ufp.api.get_event_thumbnail.assert_not_called + ufp.api.get_event_thumbnail.assert_not_called() @pytest.mark.parametrize(("width", "height"), [("test", None), (None, "test")]) @@ -62,7 +62,7 @@ async def test_thumbnail_bad_params( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 400 - ufp.api.get_event_thumbnail.assert_not_called + ufp.api.get_event_thumbnail.assert_not_called() async def test_thumbnail_bad_event( @@ -259,7 +259,7 @@ async def test_video_bad_nvr_id( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 404 - ufp.api.request.assert_not_called + ufp.api.request.assert_not_called() async def test_video_bad_camera_id( @@ -293,7 +293,7 @@ async def test_video_bad_camera_id( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 404 - ufp.api.request.assert_not_called + ufp.api.request.assert_not_called() async def test_video_bad_camera_perms( @@ -329,7 +329,7 @@ async def test_video_bad_camera_perms( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 403 - ufp.api.request.assert_not_called + ufp.api.request.assert_not_called() @pytest.mark.parametrize(("start", "end"), [("test", None), (None, "test")]) @@ -369,7 +369,7 @@ async def test_video_bad_params( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 400 - ufp.api.request.assert_not_called + ufp.api.request.assert_not_called() async def test_video_bad_video( @@ -403,7 +403,7 @@ async def test_video_bad_video( response = cast(ClientResponse, await http_client.get(url)) assert response.status == 404 - ufp.api.request.assert_called_once + ufp.api.request.assert_called_once() async def test_video( @@ -446,7 +446,7 @@ async def test_video( assert await response.content.read() == b"testtest" assert response.status == 200 - ufp.api.request.assert_called_once + ufp.api.request.assert_called_once() async def test_video_entity_id( @@ -490,4 +490,4 @@ async def test_video_entity_id( assert await response.content.read() == b"testtest" assert response.status == 200 - ufp.api.request.assert_called_once + ufp.api.request.assert_called_once() From 470ef554d788bea1328fa950ddf7e6e21e301149 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 11:03:25 +0100 Subject: [PATCH 1145/1691] Fix freedompro tests for ruff B018 (#113583) --- .../freedompro/test_binary_sensor.py | 5 ++-- tests/components/freedompro/test_climate.py | 24 ++++++++++------- tests/components/freedompro/test_cover.py | 14 ++++------ tests/components/freedompro/test_fan.py | 20 +++++++------- tests/components/freedompro/test_init.py | 8 ++++-- tests/components/freedompro/test_light.py | 27 ++++++++++++------- tests/components/freedompro/test_lock.py | 15 ++++++----- tests/components/freedompro/test_sensor.py | 5 ++-- tests/components/freedompro/test_switch.py | 17 +++++++----- 9 files changed, 75 insertions(+), 60 deletions(-) diff --git a/tests/components/freedompro/test_binary_sensor.py b/tests/components/freedompro/test_binary_sensor.py index 30bb1a58117..f3bfeb68c6c 100644 --- a/tests/components/freedompro/test_binary_sensor.py +++ b/tests/components/freedompro/test_binary_sensor.py @@ -12,7 +12,7 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed @pytest.mark.parametrize( @@ -48,14 +48,13 @@ async def test_binary_sensor_get_state( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, - init_integration, + init_integration: MockConfigEntry, entity_id: str, uid: str, name: str, model: str, ) -> None: """Test states of the binary_sensor.""" - init_integration device = device_registry.async_get_device(identifiers={("freedompro", uid)}) assert device is not None diff --git a/tests/components/freedompro/test_climate.py b/tests/components/freedompro/test_climate.py index 0195f6bde94..9a8f0c5030c 100644 --- a/tests/components/freedompro/test_climate.py +++ b/tests/components/freedompro/test_climate.py @@ -24,7 +24,7 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed uid = "3WRRJR6RCZQZSND8VP0YTO3YXCSOFPKBMW8T51TU-LQ*TWMYQKL3UVED4HSIIB9GXJWJZBQCXG-9VE-N2IUAIWI" @@ -33,7 +33,7 @@ async def test_climate_get_state( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, - init_integration, + init_integration: MockConfigEntry, ) -> None: """Test states of the climate.""" device = device_registry.async_get_device(identifiers={("freedompro", uid)}) @@ -88,10 +88,11 @@ async def test_climate_get_state( async def test_climate_set_off( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set off climate.""" - init_integration entity_id = "climate.thermostat" state = hass.states.get(entity_id) @@ -119,10 +120,11 @@ async def test_climate_set_off( async def test_climate_set_unsupported_hvac_mode( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set unsupported hvac mode climate.""" - init_integration entity_id = "climate.thermostat" state = hass.states.get(entity_id) @@ -143,10 +145,11 @@ async def test_climate_set_unsupported_hvac_mode( async def test_climate_set_temperature( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set temperature climate.""" - init_integration entity_id = "climate.thermostat" state = hass.states.get(entity_id) @@ -189,10 +192,11 @@ async def test_climate_set_temperature( async def test_climate_set_temperature_unsupported_hvac_mode( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set temperature climate unsupported hvac mode.""" - init_integration entity_id = "climate.thermostat" state = hass.states.get(entity_id) diff --git a/tests/components/freedompro/test_cover.py b/tests/components/freedompro/test_cover.py index 9eef2b0b95b..ba48da1d1d4 100644 --- a/tests/components/freedompro/test_cover.py +++ b/tests/components/freedompro/test_cover.py @@ -21,7 +21,7 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed @pytest.mark.parametrize( @@ -39,14 +39,13 @@ async def test_cover_get_state( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, - init_integration, + init_integration: MockConfigEntry, entity_id: str, uid: str, name: str, model: str, ) -> None: """Test states of the cover.""" - init_integration device = device_registry.async_get_device(identifiers={("freedompro", uid)}) assert device is not None @@ -98,14 +97,13 @@ async def test_cover_get_state( async def test_cover_set_position( hass: HomeAssistant, entity_registry: er.EntityRegistry, - init_integration, + init_integration: MockConfigEntry, entity_id: str, uid: str, name: str, model: str, ) -> None: """Test set position of the cover.""" - init_integration state = hass.states.get(entity_id) assert state @@ -153,14 +151,13 @@ async def test_cover_set_position( async def test_cover_close( hass: HomeAssistant, entity_registry: er.EntityRegistry, - init_integration, + init_integration: MockConfigEntry, entity_id: str, uid: str, name: str, model: str, ) -> None: """Test close cover.""" - init_integration states_response = get_states_response_for_uid(uid) states_response[0]["state"]["position"] = 100 @@ -216,14 +213,13 @@ async def test_cover_close( async def test_cover_open( hass: HomeAssistant, entity_registry: er.EntityRegistry, - init_integration, + init_integration: MockConfigEntry, entity_id: str, uid: str, name: str, model: str, ) -> None: """Test open cover.""" - init_integration state = hass.states.get(entity_id) assert state diff --git a/tests/components/freedompro/test_fan.py b/tests/components/freedompro/test_fan.py index ea9b94ffc41..fd1a7fb4399 100644 --- a/tests/components/freedompro/test_fan.py +++ b/tests/components/freedompro/test_fan.py @@ -17,7 +17,7 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed uid = "3WRRJR6RCZQZSND8VP0YTO3YXCSOFPKBMW8T51TU-LQ*ILYH1E3DWZOVMNEUIMDYMNLOW-LFRQFDPWWJOVHVDOS" @@ -26,10 +26,9 @@ async def test_fan_get_state( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, - init_integration, + init_integration: MockConfigEntry, ) -> None: """Test states of the fan.""" - init_integration device = device_registry.async_get_device(identifiers={("freedompro", uid)}) assert device is not None @@ -72,10 +71,11 @@ async def test_fan_get_state( async def test_fan_set_off( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test turn off the fan.""" - init_integration entity_id = "fan.bedroom" @@ -126,10 +126,11 @@ async def test_fan_set_off( async def test_fan_set_on( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test turn on the fan.""" - init_integration entity_id = "fan.bedroom" state = hass.states.get(entity_id) @@ -167,10 +168,11 @@ async def test_fan_set_on( async def test_fan_set_percent( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test turn on the fan.""" - init_integration entity_id = "fan.bedroom" state = hass.states.get(entity_id) diff --git a/tests/components/freedompro/test_init.py b/tests/components/freedompro/test_init.py index 18bf6d41ae2..58f9c493582 100644 --- a/tests/components/freedompro/test_init.py +++ b/tests/components/freedompro/test_init.py @@ -14,7 +14,9 @@ LOGGER = logging.getLogger(__name__) ENTITY_ID = f"{DOMAIN}.fake_name" -async def test_async_setup_entry(hass: HomeAssistant, init_integration) -> None: +async def test_async_setup_entry( + hass: HomeAssistant, init_integration: MockConfigEntry +) -> None: """Test a successful setup entry.""" entry = init_integration assert entry is not None @@ -44,7 +46,9 @@ async def test_config_not_ready(hass: HomeAssistant) -> None: assert entry.state == ConfigEntryState.SETUP_RETRY -async def test_unload_entry(hass: HomeAssistant, init_integration) -> None: +async def test_unload_entry( + hass: HomeAssistant, init_integration: MockConfigEntry +) -> None: """Test successful unload of entry.""" entry = init_integration diff --git a/tests/components/freedompro/test_light.py b/tests/components/freedompro/test_light.py index d0270f40843..05439adf764 100644 --- a/tests/components/freedompro/test_light.py +++ b/tests/components/freedompro/test_light.py @@ -14,6 +14,8 @@ from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, STATE_OFF, STA from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from tests.common import MockConfigEntry + @pytest.fixture(autouse=True) def mock_freedompro_put_state(): @@ -23,10 +25,11 @@ def mock_freedompro_put_state(): async def test_light_get_state( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test states of the light.""" - init_integration entity_id = "light.lightbulb" state = hass.states.get(entity_id) @@ -43,10 +46,11 @@ async def test_light_get_state( async def test_light_set_on( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set on of the light.""" - init_integration entity_id = "light.lightbulb" state = hass.states.get(entity_id) @@ -74,10 +78,11 @@ async def test_light_set_on( async def test_light_set_off( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set off of the light.""" - init_integration entity_id = "light.bedroomlight" state = hass.states.get(entity_id) @@ -105,10 +110,11 @@ async def test_light_set_off( async def test_light_set_brightness( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set brightness of the light.""" - init_integration entity_id = "light.lightbulb" state = hass.states.get(entity_id) @@ -137,10 +143,11 @@ async def test_light_set_brightness( async def test_light_set_hue( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set brightness of the light.""" - init_integration entity_id = "light.lightbulb" state = hass.states.get(entity_id) diff --git a/tests/components/freedompro/test_lock.py b/tests/components/freedompro/test_lock.py index ad5b53dd51d..94f5609ee47 100644 --- a/tests/components/freedompro/test_lock.py +++ b/tests/components/freedompro/test_lock.py @@ -16,7 +16,7 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed uid = "2WRRJR6RCZQZSND8VP0YTO3YXCSOFPKBMW8T51TU-LQ*2VAS3HTWINNZ5N6HVEIPDJ6NX85P2-AM-GSYWUCNPU0" @@ -25,10 +25,9 @@ async def test_lock_get_state( hass: HomeAssistant, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, - init_integration, + init_integration: MockConfigEntry, ) -> None: """Test states of the lock.""" - init_integration device = device_registry.async_get_device(identifiers={("freedompro", uid)}) assert device is not None @@ -68,10 +67,11 @@ async def test_lock_get_state( async def test_lock_set_unlock( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set on of the lock.""" - init_integration entity_id = "lock.lock" @@ -117,10 +117,11 @@ async def test_lock_set_unlock( async def test_lock_set_lock( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set on of the lock.""" - init_integration entity_id = "lock.lock" state = hass.states.get(entity_id) diff --git a/tests/components/freedompro/test_sensor.py b/tests/components/freedompro/test_sensor.py index 1deb895ad3d..c9075299238 100644 --- a/tests/components/freedompro/test_sensor.py +++ b/tests/components/freedompro/test_sensor.py @@ -11,7 +11,7 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed @pytest.mark.parametrize( @@ -37,13 +37,12 @@ from tests.common import async_fire_time_changed async def test_sensor_get_state( hass: HomeAssistant, entity_registry: er.EntityRegistry, - init_integration, + init_integration: MockConfigEntry, entity_id: str, uid: str, name: str, ) -> None: """Test states of the sensor.""" - init_integration state = hass.states.get(entity_id) assert state diff --git a/tests/components/freedompro/test_switch.py b/tests/components/freedompro/test_switch.py index e3ab02de55b..831218550a4 100644 --- a/tests/components/freedompro/test_switch.py +++ b/tests/components/freedompro/test_switch.py @@ -12,16 +12,17 @@ from homeassistant.util.dt import utcnow from .conftest import get_states_response_for_uid -from tests.common import async_fire_time_changed +from tests.common import MockConfigEntry, async_fire_time_changed uid = "3WRRJR6RCZQZSND8VP0YTO3YXCSOFPKBMW8T51TU-LQ*1JKU1MVWHQL-Z9SCUS85VFXMRGNDCDNDDUVVDKBU31W" async def test_switch_get_state( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test states of the switch.""" - init_integration entity_id = "switch.irrigation_switch" state = hass.states.get(entity_id) @@ -54,10 +55,11 @@ async def test_switch_get_state( async def test_switch_set_off( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set off of the switch.""" - init_integration entity_id = "switch.irrigation_switch" @@ -105,10 +107,11 @@ async def test_switch_set_off( async def test_switch_set_on( - hass: HomeAssistant, entity_registry: er.EntityRegistry, init_integration + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + init_integration: MockConfigEntry, ) -> None: """Test set on of the switch.""" - init_integration entity_id = "switch.irrigation_switch" state = hass.states.get(entity_id) From 219cb7a788b470117b662ab3dc294f84ebee0eb0 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sat, 16 Mar 2024 20:54:37 +1000 Subject: [PATCH 1146/1691] Add additional sensors to Teslemetry (#112555) * Add more sensors * Fix coverage * Dont do this rename yet * Fix case * Update snapshot * Add icons * Remove unused icons * Update snapshot * Remove last_value logic from TimeSensor * Apply suggestions from code review Co-authored-by: G Johansson * Update constant case * Remove useless test * Add refresh test back * Add assertion to post coordinator refresh --------- Co-authored-by: G Johansson --- homeassistant/components/teslemetry/entity.py | 5 + .../components/teslemetry/icons.json | 9 + homeassistant/components/teslemetry/sensor.py | 123 +- .../components/teslemetry/strings.json | 168 +- tests/components/teslemetry/__init__.py | 15 + tests/components/teslemetry/const.py | 1 + .../teslemetry/fixtures/vehicle_data_alt.json | 279 +++ .../teslemetry/snapshots/test_sensor.ambr | 1830 ++++++++++++----- tests/components/teslemetry/test_sensor.py | 16 +- 9 files changed, 1788 insertions(+), 658 deletions(-) create mode 100644 tests/components/teslemetry/fixtures/vehicle_data_alt.json diff --git a/homeassistant/components/teslemetry/entity.py b/homeassistant/components/teslemetry/entity.py index c8f650c01aa..eda3d26f341 100644 --- a/homeassistant/components/teslemetry/entity.py +++ b/homeassistant/components/teslemetry/entity.py @@ -48,6 +48,11 @@ class TeslemetryVehicleEntity(CoordinatorEntity[TeslemetryVehicleDataCoordinator serial_number=vehicle.vin, ) + @property + def _value(self) -> Any | None: + """Return a specific value from coordinator data.""" + return self.coordinator.data.get(self.key) + async def wake_up_if_asleep(self) -> None: """Wake up the vehicle if its asleep.""" async with self._wakelock: diff --git a/homeassistant/components/teslemetry/icons.json b/homeassistant/components/teslemetry/icons.json index 4ce852537d7..b3b61831b0e 100644 --- a/homeassistant/components/teslemetry/icons.json +++ b/homeassistant/components/teslemetry/icons.json @@ -18,6 +18,15 @@ "battery_power": { "default": "mdi:home-battery" }, + "charge_state_charging_state": { + "default": "mdi:ev-station", + "state": { + "disconnected": "mdi:connection", + "no_power": "mdi:power-plug-off-outline", + "starting": "mdi:play-circle", + "stopped": "mdi:stop-circle" + } + }, "drive_state_active_route_destination": { "default": "mdi:routes" }, diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index af7000a3127..7e6a35d03e1 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -6,6 +6,7 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta from itertools import chain +from typing import cast from homeassistant.components.sensor import ( SensorDeviceClass, @@ -27,10 +28,11 @@ from homeassistant.const import ( UnitOfTemperature, UnitOfTime, ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util import dt as dt_util +from homeassistant.util.variance import ignore_variance from .const import DOMAIN from .entity import ( @@ -40,13 +42,16 @@ from .entity import ( ) from .models import TeslemetryEnergyData, TeslemetryVehicleData +CHARGE_STATES = { + "Starting": "starting", + "Charging": "charging", + "Stopped": "stopped", + "Complete": "complete", + "Disconnected": "disconnected", + "NoPower": "no_power", +} -@callback -def minutes_to_datetime(value: StateType) -> datetime | None: - """Convert relative minutes into absolute datetime.""" - if isinstance(value, (int, float)) and value > 0: - return dt_util.now() + timedelta(minutes=value) - return None +SHIFT_STATES = {"P": "p", "D": "d", "R": "r", "N": "n"} @dataclass(frozen=True, kw_only=True) @@ -57,11 +62,24 @@ class TeslemetrySensorEntityDescription(SensorEntityDescription): VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( + TeslemetrySensorEntityDescription( + key="charge_state_charging_state", + options=list(CHARGE_STATES.values()), + device_class=SensorDeviceClass.ENUM, + value_fn=lambda value: CHARGE_STATES.get(cast(str, value)), + ), + TeslemetrySensorEntityDescription( + key="charge_state_battery_level", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.BATTERY, + ), TeslemetrySensorEntityDescription( key="charge_state_usable_battery_level", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, + entity_registry_enabled_default=False, ), TeslemetrySensorEntityDescription( key="charge_state_charge_energy_added", @@ -96,13 +114,16 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR, device_class=SensorDeviceClass.SPEED, entity_category=EntityCategory.DIAGNOSTIC, + ), + TeslemetrySensorEntityDescription( + key="charge_state_conn_charge_cable", + entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, ), TeslemetrySensorEntityDescription( - key="charge_state_minutes_to_full_charge", - device_class=SensorDeviceClass.TIMESTAMP, + key="charge_state_fast_charger_type", entity_category=EntityCategory.DIAGNOSTIC, - value_fn=minutes_to_datetime, + entity_registry_enabled_default=False, ), TeslemetrySensorEntityDescription( key="charge_state_battery_range", @@ -111,12 +132,29 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( device_class=SensorDeviceClass.DISTANCE, suggested_display_precision=1, ), + TeslemetrySensorEntityDescription( + key="charge_state_est_battery_range", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfLength.MILES, + device_class=SensorDeviceClass.DISTANCE, + suggested_display_precision=1, + entity_registry_enabled_default=False, + ), + TeslemetrySensorEntityDescription( + key="charge_state_ideal_battery_range", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfLength.MILES, + device_class=SensorDeviceClass.DISTANCE, + suggested_display_precision=1, + entity_registry_enabled_default=False, + ), TeslemetrySensorEntityDescription( key="drive_state_speed", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR, device_class=SensorDeviceClass.SPEED, entity_registry_enabled_default=False, + value_fn=lambda value: value or 0, ), TeslemetrySensorEntityDescription( key="drive_state_power", @@ -125,12 +163,13 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( device_class=SensorDeviceClass.POWER, entity_category=EntityCategory.DIAGNOSTIC, entity_registry_enabled_default=False, + value_fn=lambda value: value or 0, ), TeslemetrySensorEntityDescription( key="drive_state_shift_state", - options=["p", "d", "r", "n"], + options=list(SHIFT_STATES.values()), device_class=SensorDeviceClass.ENUM, - value_fn=lambda x: x.lower() if isinstance(x, str) else x, + value_fn=lambda x: SHIFT_STATES.get(str(x), "p"), entity_registry_enabled_default=False, ), TeslemetrySensorEntityDescription( @@ -235,14 +274,27 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfLength.MILES, device_class=SensorDeviceClass.DISTANCE, ), - TeslemetrySensorEntityDescription( +) + + +@dataclass(frozen=True, kw_only=True) +class TeslemetryTimeEntityDescription(SensorEntityDescription): + """Describes Teslemetry Sensor entity.""" + + variance: int + + +VEHICLE_TIME_DESCRIPTIONS: tuple[TeslemetryTimeEntityDescription, ...] = ( + TeslemetryTimeEntityDescription( + key="charge_state_minutes_to_full_charge", + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + variance=4, + ), + TeslemetryTimeEntityDescription( key="drive_state_active_route_minutes_to_arrival", device_class=SensorDeviceClass.TIMESTAMP, - value_fn=minutes_to_datetime, - ), - TeslemetrySensorEntityDescription( - key="drive_state_active_route_destination", - entity_category=EntityCategory.DIAGNOSTIC, + variance=1, ), ) @@ -322,6 +374,7 @@ ENERGY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( device_class=SensorDeviceClass.POWER, entity_registry_enabled_default=False, ), + SensorEntityDescription(key="island_status", device_class=SensorDeviceClass.ENUM), ) WALL_CONNECTOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( @@ -362,7 +415,12 @@ async def async_setup_entry( for vehicle in data.vehicles for description in VEHICLE_DESCRIPTIONS ), - ( # Add energy sites + ( # Add vehicles time sensors + TeslemetryVehicleTimeSensorEntity(vehicle, description) + for vehicle in data.vehicles + for description in VEHICLE_TIME_DESCRIPTIONS + ), + ( # Add energy site live TeslemetryEnergySensorEntity(energysite, description) for energysite in data.energysites for description in ENERGY_DESCRIPTIONS @@ -390,17 +448,36 @@ class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity): ) -> None: """Initialize the sensor.""" super().__init__(vehicle, description.key) + + +class TeslemetryVehicleTimeSensorEntity(TeslemetryVehicleEntity, SensorEntity): + """Base class for Teslemetry vehicle metric sensors.""" + + entity_description: TeslemetryTimeEntityDescription + + def __init__( + self, + data: TeslemetryVehicleData, + description: TeslemetryTimeEntityDescription, + ) -> None: + """Initialize the sensor.""" self.entity_description = description + self._get_timestamp = ignore_variance( + func=lambda value: dt_util.now() + timedelta(minutes=value), + ignored_variance=timedelta(minutes=description.variance), + ) + + super().__init__(data, description.key) @property - def native_value(self) -> StateType | datetime: + def native_value(self) -> datetime | None: """Return the state of the sensor.""" - return self.entity_description.value_fn(self.get()) + return self._get_timestamp(self._value) @property def available(self) -> bool: - """Return if sensor is available.""" - return super().available and self.get() is not None + """Return the avaliability of the sensor.""" + return isinstance(self._value, int | float) and self._value > 0 class TeslemetryEnergySensorEntity(TeslemetryEnergyEntity, SensorEntity): diff --git a/homeassistant/components/teslemetry/strings.json b/homeassistant/components/teslemetry/strings.json index 2e89503bbe9..fa4419fbfcb 100644 --- a/homeassistant/components/teslemetry/strings.json +++ b/homeassistant/components/teslemetry/strings.json @@ -32,32 +32,85 @@ } }, "sensor": { - "charge_state_usable_battery_level": { - "name": "Battery level" + "battery_power": { + "name": "Battery power" + }, + "charge_state_battery_range": { + "name": "Battery range" + }, + "charge_state_est_battery_range": { + "name": "Estimate battery range" + }, + "charge_state_ideal_battery_range": { + "name": "Ideal battery range" }, "charge_state_charge_energy_added": { "name": "Charge energy added" }, + "charge_state_charge_rate": { + "name": "Charge rate" + }, + "charge_state_charger_actual_current": { + "name": "Charger current" + }, "charge_state_charger_power": { "name": "Charger power" }, "charge_state_charger_voltage": { "name": "Charger voltage" }, - "charge_state_charger_actual_current": { - "name": "Charger current" + "charge_state_conn_charge_cable": { + "name": "Charge cable" }, - "charge_state_charge_rate": { - "name": "Charge rate" + "charge_state_fast_charger_type": { + "name": "Fast charger type" }, - "charge_state_battery_range": { - "name": "Battery range" + "charge_state_charging_state": { + "name": "Charging", + "state": { + "starting": "Starting", + "charging": "Charging", + "disconnected": "Disconnected", + "stopped": "Stopped", + "complete": "Complete", + "no_power": "No power" + } }, "charge_state_minutes_to_full_charge": { "name": "Time to full charge" }, - "drive_state_speed": { - "name": "Speed" + "charge_state_battery_level": { + "name": "Battery level" + }, + "charge_state_usable_battery_level": { + "name": "Usable battery level" + }, + "climate_state_driver_temp_setting": { + "name": "Driver temperature setting" + }, + "climate_state_inside_temp": { + "name": "Inside temperature" + }, + "climate_state_outside_temp": { + "name": "Outside temperature" + }, + "climate_state_passenger_temp_setting": { + "name": "Passenger temperature setting" + }, + "drive_state_active_route_destination": { + "name": "Destination" + }, + "drive_state_active_route_energy_at_arrival": { + "name": "State of charge at arrival" + }, + "drive_state_active_route_miles_to_arrival": { + "name": "Distance to arrival" + }, + "drive_state_active_route_minutes_to_arrival": { + "name": "Time to arrival" + }, + "drive_state_active_route_traffic_minutes_delay": { + "name": "Traffic delay" }, "drive_state_power": { "name": "Power" @@ -65,12 +118,39 @@ "drive_state_shift_state": { "name": "Shift state", "state": { - "p": "Park", "d": "Drive", - "r": "Reverse", - "n": "Neutral" + "n": "Neutral", + "p": "Park", + "r": "Reverse" } }, + "drive_state_speed": { + "name": "Speed" + }, + "energy_left": { + "name": "Energy left" + }, + "generator_power": { + "name": "Generator power" + }, + "grid_power": { + "name": "Grid power" + }, + "grid_services_power": { + "name": "Grid services power" + }, + "load_power": { + "name": "Load power" + }, + "percentage_charged": { + "name": "Percentage charged" + }, + "solar_power": { + "name": "Solar power" + }, + "total_pack_energy": { + "name": "Total pack energy" + }, "vehicle_state_odometer": { "name": "Odometer" }, @@ -86,62 +166,8 @@ "vehicle_state_tpms_pressure_rr": { "name": "Tire pressure rear right" }, - "climate_state_inside_temp": { - "name": "Inside temperature" - }, - "climate_state_outside_temp": { - "name": "Outside temperature" - }, - "climate_state_driver_temp_setting": { - "name": "Driver temperature setting" - }, - "climate_state_passenger_temp_setting": { - "name": "Passenger temperature setting" - }, - "drive_state_active_route_traffic_minutes_delay": { - "name": "Traffic delay" - }, - "drive_state_active_route_energy_at_arrival": { - "name": "State of charge at arrival" - }, - "drive_state_active_route_miles_to_arrival": { - "name": "Distance to arrival" - }, - "drive_state_active_route_minutes_to_arrival": { - "name": "Time to arrival" - }, - "drive_state_active_route_destination": { - "name": "Destination" - }, - "solar_power": { - "name": "Solar power" - }, - "energy_left": { - "name": "Energy left" - }, - "total_pack_energy": { - "name": "Total pack energy" - }, - "percentage_charged": { - "name": "Percentage charged" - }, - "battery_power": { - "name": "Battery power" - }, - "load_power": { - "name": "Load power" - }, - "grid_power": { - "name": "Grid power" - }, - "grid_services_power": { - "name": "Grid services power" - }, - "generator_power": { - "name": "Generator power" - }, - "wall_connector_state": { - "name": "State code" + "vin": { + "name": "Vehicle" }, "wall_connector_fault_state": { "name": "Fault state code" @@ -149,8 +175,8 @@ "wall_connector_power": { "name": "Power" }, - "vin": { - "name": "Vehicle" + "wall_connector_state": { + "name": "State code" } } } diff --git a/tests/components/teslemetry/__init__.py b/tests/components/teslemetry/__init__.py index eae58127d1d..ac3a2904c27 100644 --- a/tests/components/teslemetry/__init__.py +++ b/tests/components/teslemetry/__init__.py @@ -48,3 +48,18 @@ def assert_entities( assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") assert (state := hass.states.get(entity_entry.entity_id)) assert state == snapshot(name=f"{entity_entry.entity_id}-state") + + +def assert_entities_alt( + hass: HomeAssistant, + entry_id: str, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test that all entities match their alt snapshot.""" + entity_entries = er.async_entries_for_config_entry(entity_registry, entry_id) + + assert entity_entries + for entity_entry in entity_entries: + assert (state := hass.states.get(entity_entry.entity_id)) + assert state == snapshot(name=f"{entity_entry.entity_id}-statealt") diff --git a/tests/components/teslemetry/const.py b/tests/components/teslemetry/const.py index 90419d43bbb..776cc231a5c 100644 --- a/tests/components/teslemetry/const.py +++ b/tests/components/teslemetry/const.py @@ -12,6 +12,7 @@ WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None} PRODUCTS = load_json_object_fixture("products.json", DOMAIN) VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN) +VEHICLE_DATA_ALT = load_json_object_fixture("vehicle_data_alt.json", DOMAIN) LIVE_STATUS = load_json_object_fixture("live_status.json", DOMAIN) RESPONSE_OK = {"response": {}, "error": None} diff --git a/tests/components/teslemetry/fixtures/vehicle_data_alt.json b/tests/components/teslemetry/fixtures/vehicle_data_alt.json new file mode 100644 index 00000000000..13d11073fb1 --- /dev/null +++ b/tests/components/teslemetry/fixtures/vehicle_data_alt.json @@ -0,0 +1,279 @@ +{ + "response": { + "id": 1234, + "user_id": 1234, + "vehicle_id": 1234, + "vin": "VINVINVIN", + "color": null, + "access_type": "OWNER", + "granular_access": { + "hide_private": false + }, + "tokens": ["abc", "def"], + "state": "online", + "in_service": false, + "id_s": "1234", + "calendar_enabled": true, + "api_version": 71, + "backseat_token": null, + "backseat_token_updated_at": null, + "ble_autopair_enrolled": false, + "charge_state": { + "battery_heater_on": false, + "battery_level": 77, + "battery_range": 266.87, + "charge_amps": 16, + "charge_current_request": 16, + "charge_current_request_max": 16, + "charge_enable_request": true, + "charge_energy_added": 0, + "charge_limit_soc": 80, + "charge_limit_soc_max": 100, + "charge_limit_soc_min": 50, + "charge_limit_soc_std": 80, + "charge_miles_added_ideal": 0, + "charge_miles_added_rated": 0, + "charge_port_cold_weather_mode": false, + "charge_port_color": "", + "charge_port_door_open": true, + "charge_port_latch": "Engaged", + "charge_rate": 0, + "charger_actual_current": 0, + "charger_phases": null, + "charger_pilot_current": 16, + "charger_power": 0, + "charger_voltage": 2, + "charging_state": "Stopped", + "conn_charge_cable": "IEC", + "est_battery_range": 275.04, + "fast_charger_brand": "", + "fast_charger_present": false, + "fast_charger_type": "ACSingleWireCAN", + "ideal_battery_range": 266.87, + "max_range_charge_counter": 0, + "minutes_to_full_charge": "bad value", + "not_enough_power_to_heat": null, + "off_peak_charging_enabled": false, + "off_peak_charging_times": "all_week", + "off_peak_hours_end_time": 900, + "preconditioning_enabled": false, + "preconditioning_times": "all_week", + "scheduled_charging_mode": "Off", + "scheduled_charging_pending": false, + "scheduled_charging_start_time": null, + "scheduled_charging_start_time_app": 600, + "scheduled_departure_time": 1704837600, + "scheduled_departure_time_minutes": 480, + "supercharger_session_trip_planner": false, + "time_to_full_charge": null, + "timestamp": null, + "trip_charging": false, + "usable_battery_level": 77, + "user_charge_enable_request": null + }, + "climate_state": { + "allow_cabin_overheat_protection": true, + "auto_seat_climate_left": false, + "auto_seat_climate_right": false, + "auto_steering_wheel_heat": false, + "battery_heater": false, + "battery_heater_no_power": null, + "cabin_overheat_protection": "Off", + "cabin_overheat_protection_actively_cooling": false, + "climate_keeper_mode": "off", + "cop_activation_temperature": "High", + "defrost_mode": 0, + "driver_temp_setting": 22, + "fan_status": 0, + "hvac_auto_request": "On", + "inside_temp": 29.8, + "is_auto_conditioning_on": false, + "is_climate_on": false, + "is_front_defroster_on": false, + "is_preconditioning": false, + "is_rear_defroster_on": false, + "left_temp_direction": 251, + "max_avail_temp": 28, + "min_avail_temp": 15, + "outside_temp": 30, + "passenger_temp_setting": 22, + "remote_heater_control_enabled": false, + "right_temp_direction": 251, + "seat_heater_left": 0, + "seat_heater_rear_center": 0, + "seat_heater_rear_left": 0, + "seat_heater_rear_right": 0, + "seat_heater_right": 0, + "side_mirror_heaters": false, + "steering_wheel_heat_level": 0, + "steering_wheel_heater": false, + "supports_fan_only_cabin_overheat_protection": true, + "timestamp": 1705707520649, + "wiper_blade_heater": false + }, + "drive_state": { + "active_route_latitude": 30.2226265, + "active_route_longitude": -97.6236871, + "active_route_miles_to_arrival": 0, + "active_route_minutes_to_arrival": 0, + "active_route_traffic_minutes_delay": 0, + "gps_as_of": 1701129612, + "heading": 185, + "latitude": -30.222626, + "longitude": -97.6236871, + "native_latitude": -30.222626, + "native_location_supported": 1, + "native_longitude": -97.6236871, + "native_type": "wgs", + "power": -7, + "shift_state": null, + "speed": null, + "timestamp": 1705707520649 + }, + "gui_settings": { + "gui_24_hour_time": false, + "gui_charge_rate_units": "kW", + "gui_distance_units": "km/hr", + "gui_range_display": "Rated", + "gui_temperature_units": "C", + "gui_tirepressure_units": "Psi", + "show_range_units": false, + "timestamp": 1705707520649 + }, + "vehicle_config": { + "aux_park_lamps": "Eu", + "badge_version": 1, + "can_accept_navigation_requests": true, + "can_actuate_trunks": true, + "car_special_type": "base", + "car_type": "model3", + "charge_port_type": "CCS", + "cop_user_set_temp_supported": false, + "dashcam_clip_save_supported": true, + "default_charge_to_max": false, + "driver_assist": "TeslaAP3", + "ece_restrictions": false, + "efficiency_package": "M32021", + "eu_vehicle": true, + "exterior_color": "DeepBlue", + "exterior_trim": "Black", + "exterior_trim_override": "", + "has_air_suspension": false, + "has_ludicrous_mode": false, + "has_seat_cooling": false, + "headlamp_type": "Global", + "interior_trim_type": "White2", + "key_version": 2, + "motorized_charge_port": true, + "paint_color_override": "0,9,25,0.7,0.04", + "performance_package": "Base", + "plg": true, + "pws": true, + "rear_drive_unit": "PM216MOSFET", + "rear_seat_heaters": 1, + "rear_seat_type": 0, + "rhd": true, + "roof_color": "RoofColorGlass", + "seat_type": null, + "spoiler_type": "None", + "sun_roof_installed": null, + "supports_qr_pairing": false, + "third_row_seats": "None", + "timestamp": 1705707520649, + "trim_badging": "74d", + "use_range_badging": true, + "utc_offset": 36000, + "webcam_selfie_supported": true, + "webcam_supported": true, + "wheel_type": "Pinwheel18CapKit" + }, + "vehicle_state": { + "api_version": 71, + "autopark_state_v2": "unavailable", + "calendar_supported": true, + "car_version": "2023.44.30.8 06f534d46010", + "center_display_state": 0, + "dashcam_clip_save_available": true, + "dashcam_state": "Recording", + "df": 0, + "dr": 0, + "fd_window": 0, + "feature_bitmask": "fbdffbff,187f", + "fp_window": 0, + "ft": 0, + "is_user_present": false, + "locked": false, + "media_info": { + "audio_volume": 2.6667, + "audio_volume_increment": 0.333333, + "audio_volume_max": 10.333333, + "media_playback_status": "Stopped", + "now_playing_album": "", + "now_playing_artist": "", + "now_playing_duration": 0, + "now_playing_elapsed": 0, + "now_playing_source": "Spotify", + "now_playing_station": "", + "now_playing_title": "" + }, + "media_state": { + "remote_control_enabled": true + }, + "notifications_supported": true, + "odometer": 6481.019282, + "parsed_calendar_supported": true, + "pf": 0, + "pr": 0, + "rd_window": 0, + "remote_start": false, + "remote_start_enabled": true, + "remote_start_supported": true, + "rp_window": 0, + "rt": 0, + "santa_mode": 0, + "sentry_mode": false, + "sentry_mode_available": true, + "service_mode": false, + "service_mode_plus": false, + "software_update": { + "download_perc": 0, + "expected_duration_sec": 2700, + "install_perc": 1, + "status": "", + "version": " " + }, + "speed_limit_mode": { + "active": false, + "current_limit_mph": 69, + "max_limit_mph": 120, + "min_limit_mph": 50, + "pin_code_set": true + }, + "timestamp": 1705707520649, + "tpms_hard_warning_fl": false, + "tpms_hard_warning_fr": false, + "tpms_hard_warning_rl": false, + "tpms_hard_warning_rr": false, + "tpms_last_seen_pressure_time_fl": 1705700812, + "tpms_last_seen_pressure_time_fr": 1705700793, + "tpms_last_seen_pressure_time_rl": 1705700794, + "tpms_last_seen_pressure_time_rr": 1705700823, + "tpms_pressure_fl": 2.775, + "tpms_pressure_fr": 2.8, + "tpms_pressure_rl": 2.775, + "tpms_pressure_rr": 2.775, + "tpms_rcp_front_value": 2.9, + "tpms_rcp_rear_value": 2.9, + "tpms_soft_warning_fl": false, + "tpms_soft_warning_fr": false, + "tpms_soft_warning_rl": false, + "tpms_soft_warning_rr": false, + "valet_mode": false, + "valet_pin_needed": false, + "vehicle_name": "Test", + "vehicle_self_test_progress": 0, + "vehicle_self_test_requested": false, + "webcam_available": true + } + } +} diff --git a/tests/components/teslemetry/snapshots/test_sensor.ambr b/tests/components/teslemetry/snapshots/test_sensor.ambr index 64e3a6ee8ab..ec87e5e09e1 100644 --- a/tests/components/teslemetry/snapshots/test_sensor.ambr +++ b/tests/components/teslemetry/snapshots/test_sensor.ambr @@ -55,6 +55,21 @@ 'state': '5.06', }) # --- +# name: test_sensors[sensor.energy_site_battery_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Battery power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_battery_power', + 'last_changed': , + 'last_updated': , + 'state': '5.06', + }) +# --- # name: test_sensors[sensor.energy_site_energy_left-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -111,6 +126,21 @@ 'state': '38.8964736842105', }) # --- +# name: test_sensors[sensor.energy_site_energy_left-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy_storage', + 'friendly_name': 'Energy Site Energy left', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_energy_left', + 'last_changed': , + 'last_updated': , + 'state': '38.8964736842105', + }) +# --- # name: test_sensors[sensor.energy_site_generator_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -167,6 +197,21 @@ 'state': '0.0', }) # --- +# name: test_sensors[sensor.energy_site_generator_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Generator power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_generator_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_sensors[sensor.energy_site_grid_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -223,6 +268,21 @@ 'state': '0.0', }) # --- +# name: test_sensors[sensor.energy_site_grid_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Grid power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_grid_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_sensors[sensor.energy_site_grid_services_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -279,6 +339,21 @@ 'state': '0.0', }) # --- +# name: test_sensors[sensor.energy_site_grid_services_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Grid services power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_grid_services_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_sensors[sensor.energy_site_load_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -335,6 +410,80 @@ 'state': '6.245', }) # --- +# name: test_sensors[sensor.energy_site_load_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Load power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_load_power', + 'last_changed': , + 'last_updated': , + 'state': '6.245', + }) +# --- +# name: test_sensors[sensor.energy_site_none-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.energy_site_none', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'island_status', + 'unique_id': '123456-island_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.energy_site_none-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Energy Site None', + }), + 'context': , + 'entity_id': 'sensor.energy_site_none', + 'last_changed': , + 'last_updated': , + 'state': 'on_grid', + }) +# --- +# name: test_sensors[sensor.energy_site_none-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Energy Site None', + }), + 'context': , + 'entity_id': 'sensor.energy_site_none', + 'last_changed': , + 'last_updated': , + 'state': 'on_grid', + }) +# --- # name: test_sensors[sensor.energy_site_percentage_charged-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -388,6 +537,21 @@ 'state': '95.5053740373966', }) # --- +# name: test_sensors[sensor.energy_site_percentage_charged-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Energy Site Percentage charged', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.energy_site_percentage_charged', + 'last_changed': , + 'last_updated': , + 'state': '95.5053740373966', + }) +# --- # name: test_sensors[sensor.energy_site_solar_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -444,6 +608,21 @@ 'state': '1.185', }) # --- +# name: test_sensors[sensor.energy_site_solar_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Energy Site Solar power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.energy_site_solar_power', + 'last_changed': , + 'last_updated': , + 'state': '1.185', + }) +# --- # name: test_sensors[sensor.energy_site_total_pack_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -500,369 +679,22 @@ 'state': '40.727', }) # --- -# name: test_sensors[sensor.test_battery_level-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_battery_level', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Battery level', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_usable_battery_level', - 'unique_id': 'VINVINVIN-charge_state_usable_battery_level', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[sensor.test_battery_level-state] +# name: test_sensors[sensor.energy_site_total_pack_energy-statealt] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'battery', - 'friendly_name': 'Test Battery level', + 'device_class': 'energy_storage', + 'friendly_name': 'Energy Site Total pack energy', 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.test_battery_level', - 'last_changed': , - 'last_updated': , - 'state': '77', - }) -# --- -# name: test_sensors[sensor.test_battery_range-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_battery_range', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Battery range', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_battery_range', - 'unique_id': 'VINVINVIN-charge_state_battery_range', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.test_battery_range-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'distance', - 'friendly_name': 'Test Battery range', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_battery_range', - 'last_changed': , - 'last_updated': , - 'state': '429.48563328', - }) -# --- -# name: test_sensors[sensor.test_charge_energy_added-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_charge_energy_added', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Charge energy added', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_charge_energy_added', - 'unique_id': 'VINVINVIN-charge_state_charge_energy_added', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.test_charge_energy_added-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test Charge energy added', - 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_charge_energy_added', + 'entity_id': 'sensor.energy_site_total_pack_energy', 'last_changed': , 'last_updated': , - 'state': '0', + 'state': '40.727', }) # --- -# name: test_sensors[sensor.test_charge_rate-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_charge_rate', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Charge rate', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_charge_rate', - 'unique_id': 'VINVINVIN-charge_state_charge_rate', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.test_charge_rate-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'speed', - 'friendly_name': 'Test Charge rate', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_charge_rate', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[sensor.test_charger_current-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_charger_current', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Charger current', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_charger_actual_current', - 'unique_id': 'VINVINVIN-charge_state_charger_actual_current', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.test_charger_current-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test Charger current', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_charger_current', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[sensor.test_charger_power-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_charger_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Charger power', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_charger_power', - 'unique_id': 'VINVINVIN-charge_state_charger_power', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.test_charger_power-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Test Charger power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_charger_power', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[sensor.test_charger_voltage-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_charger_voltage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Charger voltage', - 'platform': 'teslemetry', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'charge_state_charger_voltage', - 'unique_id': 'VINVINVIN-charge_state_charger_voltage', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[sensor.test_charger_voltage-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Test Charger voltage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_charger_voltage', - 'last_changed': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_sensors[sensor.test_destination-entry] +# name: test_sensors[sensor.test_battery_level-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -873,8 +705,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.test_destination', + 'entity_category': None, + 'entity_id': 'sensor.test_battery_level', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -886,25 +718,493 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Destination', + 'original_name': 'Battery level', 'platform': 'teslemetry', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': 'drive_state_active_route_destination', - 'unique_id': 'VINVINVIN-drive_state_active_route_destination', + 'translation_key': 'charge_state_battery_level', + 'unique_id': 'VINVINVIN-charge_state_battery_level', 'unit_of_measurement': None, }) # --- -# name: test_sensors[sensor.test_destination-state] +# name: test_sensors[sensor.test_battery_level-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Test Destination', + 'friendly_name': 'Test Battery level', }), 'context': , - 'entity_id': 'sensor.test_destination', + 'entity_id': 'sensor.test_battery_level', 'last_changed': , 'last_updated': , - 'state': 'unavailable', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_battery_level-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Battery level', + }), + 'context': , + 'entity_id': 'sensor.test_battery_level', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_battery_range-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_battery_range', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Battery range', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_battery_range', + 'unique_id': 'VINVINVIN-charge_state_battery_range', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_battery_range-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Battery range', + }), + 'context': , + 'entity_id': 'sensor.test_battery_range', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_battery_range-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Battery range', + }), + 'context': , + 'entity_id': 'sensor.test_battery_range', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charge_cable-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charge_cable', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charge cable', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_conn_charge_cable', + 'unique_id': 'VINVINVIN-charge_state_conn_charge_cable', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charge_cable-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charge cable', + }), + 'context': , + 'entity_id': 'sensor.test_charge_cable', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charge_cable-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charge cable', + }), + 'context': , + 'entity_id': 'sensor.test_charge_cable', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charge_energy_added-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charge_energy_added', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charge energy added', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charge_energy_added', + 'unique_id': 'VINVINVIN-charge_state_charge_energy_added', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charge_energy_added-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charge energy added', + }), + 'context': , + 'entity_id': 'sensor.test_charge_energy_added', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charge_energy_added-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charge energy added', + }), + 'context': , + 'entity_id': 'sensor.test_charge_energy_added', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charge_rate-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charge_rate', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charge rate', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charge_rate', + 'unique_id': 'VINVINVIN-charge_state_charge_rate', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charge_rate-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charge rate', + }), + 'context': , + 'entity_id': 'sensor.test_charge_rate', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charge_rate-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charge rate', + }), + 'context': , + 'entity_id': 'sensor.test_charge_rate', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charger_current-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charger_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charger current', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charger_actual_current', + 'unique_id': 'VINVINVIN-charge_state_charger_actual_current', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charger_current-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charger current', + }), + 'context': , + 'entity_id': 'sensor.test_charger_current', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charger_current-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charger current', + }), + 'context': , + 'entity_id': 'sensor.test_charger_current', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charger_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charger_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charger power', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charger_power', + 'unique_id': 'VINVINVIN-charge_state_charger_power', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charger_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charger power', + }), + 'context': , + 'entity_id': 'sensor.test_charger_power', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charger_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charger power', + }), + 'context': , + 'entity_id': 'sensor.test_charger_power', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charger_voltage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charger_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charger voltage', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charger_voltage', + 'unique_id': 'VINVINVIN-charge_state_charger_voltage', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charger_voltage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charger voltage', + }), + 'context': , + 'entity_id': 'sensor.test_charger_voltage', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charger_voltage-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charger voltage', + }), + 'context': , + 'entity_id': 'sensor.test_charger_voltage', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charging-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_charging', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Charging', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_charging_state', + 'unique_id': 'VINVINVIN-charge_state_charging_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_charging-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charging', + }), + 'context': , + 'entity_id': 'sensor.test_charging', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_charging-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Charging', + }), + 'context': , + 'entity_id': 'sensor.test_charging', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_distance_to_arrival-entry] @@ -912,9 +1212,7 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , @@ -930,11 +1228,8 @@ }), 'name': None, 'options': dict({ - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Distance to arrival', 'platform': 'teslemetry', @@ -942,22 +1237,31 @@ 'supported_features': 0, 'translation_key': 'drive_state_active_route_miles_to_arrival', 'unique_id': 'VINVINVIN-drive_state_active_route_miles_to_arrival', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_distance_to_arrival-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'distance', 'friendly_name': 'Test Distance to arrival', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_distance_to_arrival', 'last_changed': , 'last_updated': , - 'state': '0.063555', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_distance_to_arrival-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Distance to arrival', + }), + 'context': , + 'entity_id': 'sensor.test_distance_to_arrival', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_driver_temperature_setting-entry] @@ -965,15 +1269,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_driver_temperature_setting', 'has_entity_name': True, 'hidden_by': None, @@ -983,11 +1285,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Driver temperature setting', 'platform': 'teslemetry', @@ -995,22 +1294,202 @@ 'supported_features': 0, 'translation_key': 'climate_state_driver_temp_setting', 'unique_id': 'VINVINVIN-climate_state_driver_temp_setting', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_driver_temperature_setting-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', 'friendly_name': 'Test Driver temperature setting', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_driver_temperature_setting', 'last_changed': , 'last_updated': , - 'state': '22', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_driver_temperature_setting-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Driver temperature setting', + }), + 'context': , + 'entity_id': 'sensor.test_driver_temperature_setting', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_estimate_battery_range-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_estimate_battery_range', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Estimate battery range', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_est_battery_range', + 'unique_id': 'VINVINVIN-charge_state_est_battery_range', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_estimate_battery_range-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Estimate battery range', + }), + 'context': , + 'entity_id': 'sensor.test_estimate_battery_range', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_estimate_battery_range-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Estimate battery range', + }), + 'context': , + 'entity_id': 'sensor.test_estimate_battery_range', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_fast_charger_type-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_fast_charger_type', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fast charger type', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_fast_charger_type', + 'unique_id': 'VINVINVIN-charge_state_fast_charger_type', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_fast_charger_type-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Fast charger type', + }), + 'context': , + 'entity_id': 'sensor.test_fast_charger_type', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_fast_charger_type-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Fast charger type', + }), + 'context': , + 'entity_id': 'sensor.test_fast_charger_type', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_ideal_battery_range-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_ideal_battery_range', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Ideal battery range', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_ideal_battery_range', + 'unique_id': 'VINVINVIN-charge_state_ideal_battery_range', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_ideal_battery_range-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Ideal battery range', + }), + 'context': , + 'entity_id': 'sensor.test_ideal_battery_range', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_ideal_battery_range-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Ideal battery range', + }), + 'context': , + 'entity_id': 'sensor.test_ideal_battery_range', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_inside_temperature-entry] @@ -1018,9 +1497,7 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , @@ -1036,11 +1513,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Inside temperature', 'platform': 'teslemetry', @@ -1048,22 +1522,31 @@ 'supported_features': 0, 'translation_key': 'climate_state_inside_temp', 'unique_id': 'VINVINVIN-climate_state_inside_temp', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_inside_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', 'friendly_name': 'Test Inside temperature', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_inside_temperature', 'last_changed': , 'last_updated': , - 'state': '29.8', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_inside_temperature-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Inside temperature', + }), + 'context': , + 'entity_id': 'sensor.test_inside_temperature', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_odometer-entry] @@ -1071,15 +1554,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_odometer', 'has_entity_name': True, 'hidden_by': None, @@ -1089,14 +1570,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Odometer', 'platform': 'teslemetry', @@ -1104,22 +1579,31 @@ 'supported_features': 0, 'translation_key': 'vehicle_state_odometer', 'unique_id': 'VINVINVIN-vehicle_state_odometer', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_odometer-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'distance', 'friendly_name': 'Test Odometer', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_odometer', 'last_changed': , 'last_updated': , - 'state': '10430.189495371', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_odometer-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Odometer', + }), + 'context': , + 'entity_id': 'sensor.test_odometer', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_outside_temperature-entry] @@ -1127,9 +1611,7 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , @@ -1145,11 +1627,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Outside temperature', 'platform': 'teslemetry', @@ -1157,22 +1636,31 @@ 'supported_features': 0, 'translation_key': 'climate_state_outside_temp', 'unique_id': 'VINVINVIN-climate_state_outside_temp', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_outside_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', 'friendly_name': 'Test Outside temperature', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_outside_temperature', 'last_changed': , 'last_updated': , - 'state': '30', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_outside_temperature-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Outside temperature', + }), + 'context': , + 'entity_id': 'sensor.test_outside_temperature', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_passenger_temperature_setting-entry] @@ -1180,15 +1668,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_passenger_temperature_setting', 'has_entity_name': True, 'hidden_by': None, @@ -1198,11 +1684,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Passenger temperature setting', 'platform': 'teslemetry', @@ -1210,22 +1693,31 @@ 'supported_features': 0, 'translation_key': 'climate_state_passenger_temp_setting', 'unique_id': 'VINVINVIN-climate_state_passenger_temp_setting', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_passenger_temperature_setting-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', 'friendly_name': 'Test Passenger temperature setting', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_passenger_temperature_setting', 'last_changed': , 'last_updated': , - 'state': '22', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_passenger_temperature_setting-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Passenger temperature setting', + }), + 'context': , + 'entity_id': 'sensor.test_passenger_temperature_setting', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_power-entry] @@ -1233,15 +1725,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_power', 'has_entity_name': True, 'hidden_by': None, @@ -1252,7 +1742,7 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Power', 'platform': 'teslemetry', @@ -1260,22 +1750,31 @@ 'supported_features': 0, 'translation_key': 'drive_state_power', 'unique_id': 'VINVINVIN-drive_state_power', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', 'friendly_name': 'Test Power', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_power', 'last_changed': , 'last_updated': , - 'state': '-7', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Power', + }), + 'context': , + 'entity_id': 'sensor.test_power', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_shift_state-entry] @@ -1283,14 +1782,7 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'options': list([ - 'p', - 'd', - 'r', - 'n', - ]), - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , @@ -1307,7 +1799,7 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Shift state', 'platform': 'teslemetry', @@ -1321,20 +1813,25 @@ # name: test_sensors[sensor.test_shift_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', 'friendly_name': 'Test Shift state', - 'options': list([ - 'p', - 'd', - 'r', - 'n', - ]), }), 'context': , 'entity_id': 'sensor.test_shift_state', 'last_changed': , 'last_updated': , - 'state': 'unavailable', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_shift_state-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Shift state', + }), + 'context': , + 'entity_id': 'sensor.test_shift_state', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_speed-entry] @@ -1342,9 +1839,7 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , @@ -1360,11 +1855,8 @@ }), 'name': None, 'options': dict({ - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Speed', 'platform': 'teslemetry', @@ -1372,22 +1864,31 @@ 'supported_features': 0, 'translation_key': 'drive_state_speed', 'unique_id': 'VINVINVIN-drive_state_speed', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_speed-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'speed', 'friendly_name': 'Test Speed', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_speed', 'last_changed': , 'last_updated': , - 'state': 'unavailable', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_speed-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Speed', + }), + 'context': , + 'entity_id': 'sensor.test_speed', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_state_of_charge_at_arrival-entry] @@ -1395,15 +1896,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_state_of_charge_at_arrival', 'has_entity_name': True, 'hidden_by': None, @@ -1414,7 +1913,7 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'State of charge at arrival', 'platform': 'teslemetry', @@ -1422,22 +1921,31 @@ 'supported_features': 0, 'translation_key': 'drive_state_active_route_energy_at_arrival', 'unique_id': 'VINVINVIN-drive_state_active_route_energy_at_arrival', - 'unit_of_measurement': '%', + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_state_of_charge_at_arrival-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'battery', 'friendly_name': 'Test State of charge at arrival', - 'state_class': , - 'unit_of_measurement': '%', }), 'context': , 'entity_id': 'sensor.test_state_of_charge_at_arrival', 'last_changed': , 'last_updated': , - 'state': 'unavailable', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_state_of_charge_at_arrival-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test State of charge at arrival', + }), + 'context': , + 'entity_id': 'sensor.test_state_of_charge_at_arrival', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_time_to_arrival-entry] @@ -1486,6 +1994,19 @@ 'state': '2024-01-01T00:00:06+00:00', }) # --- +# name: test_sensors[sensor.test_time_to_arrival-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Test Time to arrival', + }), + 'context': , + 'entity_id': 'sensor.test_time_to_arrival', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- # name: test_sensors[sensor.test_time_to_full_charge-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1529,7 +2050,20 @@ 'entity_id': 'sensor.test_time_to_full_charge', 'last_changed': , 'last_updated': , - 'state': 'unknown', + 'state': 'unavailable', + }) +# --- +# name: test_sensors[sensor.test_time_to_full_charge-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Test Time to full charge', + }), + 'context': , + 'entity_id': 'sensor.test_time_to_full_charge', + 'last_changed': , + 'last_updated': , + 'state': 'unavailable', }) # --- # name: test_sensors[sensor.test_tire_pressure_front_left-entry] @@ -1537,15 +2071,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_tire_pressure_front_left', 'has_entity_name': True, 'hidden_by': None, @@ -1555,14 +2087,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Tire pressure front left', 'platform': 'teslemetry', @@ -1570,22 +2096,31 @@ 'supported_features': 0, 'translation_key': 'vehicle_state_tpms_pressure_fl', 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_fl', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_tire_pressure_front_left-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'pressure', 'friendly_name': 'Test Tire pressure front left', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_tire_pressure_front_left', 'last_changed': , 'last_updated': , - 'state': '40.2479739314961', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_front_left-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Tire pressure front left', + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_front_left', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_tire_pressure_front_right-entry] @@ -1593,15 +2128,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_tire_pressure_front_right', 'has_entity_name': True, 'hidden_by': None, @@ -1611,14 +2144,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Tire pressure front right', 'platform': 'teslemetry', @@ -1626,22 +2153,31 @@ 'supported_features': 0, 'translation_key': 'vehicle_state_tpms_pressure_fr', 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_fr', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_tire_pressure_front_right-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'pressure', 'friendly_name': 'Test Tire pressure front right', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_tire_pressure_front_right', 'last_changed': , 'last_updated': , - 'state': '40.6105682912393', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_front_right-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Tire pressure front right', + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_front_right', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_tire_pressure_rear_left-entry] @@ -1649,15 +2185,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_tire_pressure_rear_left', 'has_entity_name': True, 'hidden_by': None, @@ -1667,14 +2201,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Tire pressure rear left', 'platform': 'teslemetry', @@ -1682,22 +2210,31 @@ 'supported_features': 0, 'translation_key': 'vehicle_state_tpms_pressure_rl', 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_rl', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_tire_pressure_rear_left-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'pressure', 'friendly_name': 'Test Tire pressure rear left', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_left', 'last_changed': , 'last_updated': , - 'state': '40.2479739314961', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_rear_left-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Tire pressure rear left', + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_rear_left', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_tire_pressure_rear_right-entry] @@ -1705,15 +2242,13 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , + 'entity_category': None, 'entity_id': 'sensor.test_tire_pressure_rear_right', 'has_entity_name': True, 'hidden_by': None, @@ -1723,14 +2258,8 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Tire pressure rear right', 'platform': 'teslemetry', @@ -1738,22 +2267,31 @@ 'supported_features': 0, 'translation_key': 'vehicle_state_tpms_pressure_rr', 'unique_id': 'VINVINVIN-vehicle_state_tpms_pressure_rr', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_tire_pressure_rear_right-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'pressure', 'friendly_name': 'Test Tire pressure rear right', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_right', 'last_changed': , 'last_updated': , - 'state': '40.2479739314961', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_tire_pressure_rear_right-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Tire pressure rear right', + }), + 'context': , + 'entity_id': 'sensor.test_tire_pressure_rear_right', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.test_traffic_delay-entry] @@ -1761,9 +2299,7 @@ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'device_class': None, 'device_id': , @@ -1780,7 +2316,7 @@ 'name': None, 'options': dict({ }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, 'original_name': 'Traffic delay', 'platform': 'teslemetry', @@ -1788,22 +2324,88 @@ 'supported_features': 0, 'translation_key': 'drive_state_active_route_traffic_minutes_delay', 'unique_id': 'VINVINVIN-drive_state_active_route_traffic_minutes_delay', - 'unit_of_measurement': , + 'unit_of_measurement': None, }) # --- # name: test_sensors[sensor.test_traffic_delay-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'duration', 'friendly_name': 'Test Traffic delay', - 'state_class': , - 'unit_of_measurement': , }), 'context': , 'entity_id': 'sensor.test_traffic_delay', 'last_changed': , 'last_updated': , - 'state': '0', + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_traffic_delay-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Traffic delay', + }), + 'context': , + 'entity_id': 'sensor.test_traffic_delay', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_usable_battery_level-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_usable_battery_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Usable battery level', + 'platform': 'teslemetry', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'charge_state_usable_battery_level', + 'unique_id': 'VINVINVIN-charge_state_usable_battery_level', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[sensor.test_usable_battery_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Usable battery level', + }), + 'context': , + 'entity_id': 'sensor.test_usable_battery_level', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensors[sensor.test_usable_battery_level-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Usable battery level', + }), + 'context': , + 'entity_id': 'sensor.test_usable_battery_level', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', }) # --- # name: test_sensors[sensor.wall_connector_fault_state_code-entry] @@ -1851,6 +2453,18 @@ 'state': '2', }) # --- +# name: test_sensors[sensor.wall_connector_fault_state_code-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Fault state code', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_fault_state_code', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- # name: test_sensors[sensor.wall_connector_fault_state_code_2-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1896,6 +2510,18 @@ 'state': '2', }) # --- +# name: test_sensors[sensor.wall_connector_fault_state_code_2-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Fault state code', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_fault_state_code_2', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- # name: test_sensors[sensor.wall_connector_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1952,6 +2578,21 @@ 'state': '0.0', }) # --- +# name: test_sensors[sensor.wall_connector_power-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Wall Connector Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.wall_connector_power', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_sensors[sensor.wall_connector_power_2-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2008,6 +2649,21 @@ 'state': '0.0', }) # --- +# name: test_sensors[sensor.wall_connector_power_2-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Wall Connector Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.wall_connector_power_2', + 'last_changed': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_sensors[sensor.wall_connector_state_code-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2053,6 +2709,18 @@ 'state': '2', }) # --- +# name: test_sensors[sensor.wall_connector_state_code-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector State code', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_state_code', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- # name: test_sensors[sensor.wall_connector_state_code_2-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2098,6 +2766,18 @@ 'state': '2', }) # --- +# name: test_sensors[sensor.wall_connector_state_code_2-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector State code', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_state_code_2', + 'last_changed': , + 'last_updated': , + 'state': '2', + }) +# --- # name: test_sensors[sensor.wall_connector_vehicle-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2143,6 +2823,18 @@ 'state': 'unknown', }) # --- +# name: test_sensors[sensor.wall_connector_vehicle-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Vehicle', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_vehicle', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_sensors[sensor.wall_connector_vehicle_2-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2188,3 +2880,15 @@ 'state': 'unknown', }) # --- +# name: test_sensors[sensor.wall_connector_vehicle_2-statealt] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Wall Connector Vehicle', + }), + 'context': , + 'entity_id': 'sensor.wall_connector_vehicle_2', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py index 3380c0086db..59b3cf08bce 100644 --- a/tests/components/teslemetry/test_sensor.py +++ b/tests/components/teslemetry/test_sensor.py @@ -1,14 +1,19 @@ """Test the Teslemetry sensor platform.""" +from datetime import timedelta from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion +from homeassistant.components.teslemetry.coordinator import SYNC_INTERVAL from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from . import assert_entities, setup_platform +from . import assert_entities, assert_entities_alt, setup_platform +from .const import VEHICLE_DATA_ALT + +from tests.common import async_fire_time_changed @pytest.mark.usefixtures("entity_registry_enabled_by_default") @@ -17,6 +22,7 @@ async def test_sensors( snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry, freezer: FrozenDateTimeFactory, + mock_vehicle_data, ) -> None: """Tests that the sensor entities are correct.""" @@ -25,3 +31,11 @@ async def test_sensors( entry = await setup_platform(hass, [Platform.SENSOR]) assert_entities(hass, entry.entry_id, entity_registry, snapshot) + + # Coordinator refresh + mock_vehicle_data.return_value = VEHICLE_DATA_ALT + freezer.tick(timedelta(seconds=SYNC_INTERVAL)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert_entities_alt(hass, entry.entry_id, entity_registry, snapshot) From 1ffc514528edfb962453e3c48c43cbbd3d8ca919 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <9592452+CFenner@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:38:54 +0100 Subject: [PATCH 1147/1691] Add battery discharge sensor to ViCare integration (#113502) Co-authored-by: jan iversen --- homeassistant/components/vicare/sensor.py | 39 ++++++++++++++++++++ homeassistant/components/vicare/strings.json | 15 ++++++++ 2 files changed, 54 insertions(+) diff --git a/homeassistant/components/vicare/sensor.py b/homeassistant/components/vicare/sensor.py index e37476286c1..41266f8bde7 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -620,6 +620,45 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( options=["charge", "discharge", "standby"], value_getter=lambda api: api.getElectricalEnergySystemOperationState(), ), + ViCareSensorEntityDescription( + key="ess_discharge_today", + translation_key="ess_discharge_today", + state_class=SensorStateClass.TOTAL_INCREASING, + value_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedCurrentDay(), + unit_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedUnit(), + ), + ViCareSensorEntityDescription( + key="ess_discharge_this_week", + translation_key="ess_discharge_this_week", + state_class=SensorStateClass.TOTAL_INCREASING, + value_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedCurrentWeek(), + unit_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedUnit(), + entity_registry_enabled_default=False, + ), + ViCareSensorEntityDescription( + key="ess_discharge_this_month", + translation_key="ess_discharge_this_month", + state_class=SensorStateClass.TOTAL_INCREASING, + value_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedCurrentMonth(), + unit_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedUnit(), + entity_registry_enabled_default=False, + ), + ViCareSensorEntityDescription( + key="ess_discharge_this_year", + translation_key="ess_discharge_this_year", + state_class=SensorStateClass.TOTAL_INCREASING, + value_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedCurrentYear(), + unit_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedUnit(), + entity_registry_enabled_default=False, + ), + ViCareSensorEntityDescription( + key="ess_discharge_total", + translation_key="ess_discharge_total", + state_class=SensorStateClass.TOTAL_INCREASING, + value_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedLifeCycle(), + unit_getter=lambda api: api.getElectricalEnergySystemTransferDischargeCumulatedUnit(), + entity_registry_enabled_default=False, + ), ViCareSensorEntityDescription( key="pcc_transfer_power_exchange", translation_key="pcc_transfer_power_exchange", diff --git a/homeassistant/components/vicare/strings.json b/homeassistant/components/vicare/strings.json index 0541be9631f..5a69cae4d29 100644 --- a/homeassistant/components/vicare/strings.json +++ b/homeassistant/components/vicare/strings.json @@ -286,6 +286,21 @@ "standby": "Standby" } }, + "ess_discharge_today": { + "name": "Battery discharge today" + }, + "ess_discharge_this_week": { + "name": "Battery discharge this week" + }, + "ess_discharge_this_month": { + "name": "Battery discharge this month" + }, + "ess_discharge_this_year": { + "name": "Battery discharge this year" + }, + "ess_discharge_total": { + "name": "Battery discharge total" + }, "pcc_current_power_exchange": { "name": "Grid power exchange" }, From a7fd1c278ce72efb2ebeb7cb523f74fd87588523 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 16 Mar 2024 16:18:13 +0200 Subject: [PATCH 1148/1691] Shelly config flow test wait for tasks to finish (#113588) --- tests/components/shelly/test_config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index c3c1fc36c2e..1a680bcfc68 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -1092,6 +1092,7 @@ async def test_zeroconf_sleeping_device_not_triggers_refresh( await hass.async_block_till_done() mock_rpc_device.mock_update() + await hass.async_block_till_done() assert "online, resuming setup" in caplog.text From d17e3974555d9fe38e19e8b6539a67232bb91a7f Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 16 Mar 2024 16:18:41 +0200 Subject: [PATCH 1149/1691] Ignore Shelly block update with cfgChanged None (#113587) --- homeassistant/components/shelly/coordinator.py | 2 +- tests/components/shelly/test_coordinator.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 6d2170bf941..22f81e21b6c 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -217,7 +217,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]): # Check for input events and config change cfg_changed = 0 for block in self.device.blocks: - if block.type == "device": + if block.type == "device" and block.cfgChanged is not None: cfg_changed = block.cfgChanged # Shelly TRV sends information about changing the configuration for no diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py index 4c169998104..c16f78b83ff 100644 --- a/tests/components/shelly/test_coordinator.py +++ b/tests/components/shelly/test_coordinator.py @@ -68,6 +68,18 @@ async def test_block_reload_on_cfg_change( mock_block_device.mock_update() await hass.async_block_till_done() + # Make sure cfgChanged with None is ignored + monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "cfgChanged", None) + mock_block_device.mock_update() + await hass.async_block_till_done() + + # Wait for debouncer + freezer.tick(timedelta(seconds=ENTRY_RELOAD_COOLDOWN)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get("switch.test_name_channel_1") is not None + # Generate config change from switch to light monkeypatch.setitem( mock_block_device.settings["relays"][RELAY_BLOCK_ID], "appliance_type", "light" From ef0c17749fe837e13673c396f3ecf36289774dfa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 04:40:50 -1000 Subject: [PATCH 1150/1691] Use async_create_task in recorder init tests (#113586) --- tests/components/recorder/test_init.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 238f6420c78..65f5869b6ca 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -142,7 +142,7 @@ async def test_shutdown_before_startup_finishes( hass.set_state(CoreState.not_running) recorder_helper.async_initialize_recorder(hass) - hass.create_task(async_setup_recorder_instance(hass, config)) + hass.async_create_task(async_setup_recorder_instance(hass, config)) await recorder_helper.async_wait_recorder(hass) instance = get_instance(hass) @@ -172,7 +172,7 @@ async def test_canceled_before_startup_finishes( """Test recorder shuts down when its startup future is canceled out from under it.""" hass.set_state(CoreState.not_running) recorder_helper.async_initialize_recorder(hass) - hass.create_task(async_setup_recorder_instance(hass)) + hass.async_create_task(async_setup_recorder_instance(hass)) await recorder_helper.async_wait_recorder(hass) instance = get_instance(hass) @@ -224,7 +224,7 @@ async def test_state_gets_saved_when_set_before_start_event( hass.set_state(CoreState.not_running) recorder_helper.async_initialize_recorder(hass) - hass.create_task(async_setup_recorder_instance(hass)) + hass.async_create_task(async_setup_recorder_instance(hass)) await recorder_helper.async_wait_recorder(hass) entity_id = "test.recorder" @@ -2509,7 +2509,7 @@ async def test_commit_before_commits_pending_writes( } recorder_helper.async_initialize_recorder(hass) - hass.create_task(async_setup_recorder_instance(hass, config)) + hass.async_create_task(async_setup_recorder_instance(hass, config)) await recorder_helper.async_wait_recorder(hass) instance = get_instance(hass) assert instance.commit_interval == 60 From 6191b2556370271f6208bddf96b4824757827240 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sat, 16 Mar 2024 16:01:48 +0100 Subject: [PATCH 1151/1691] Catch `TimeoutError` in `Brother` config flow (#113593) * Catch TimeoutError in Brother config flow * Update tests * Remove unnecessary parentheses --------- Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/brother/config_flow.py | 4 ++-- tests/components/brother/test_config_flow.py | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 9660b02c453..346141cd197 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -59,7 +59,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=title, data=user_input) except InvalidHost: errors[CONF_HOST] = "wrong_host" - except ConnectionError: + except (ConnectionError, TimeoutError): errors["base"] = "cannot_connect" except SnmpError: errors["base"] = "snmp_error" @@ -89,7 +89,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN): await self.brother.async_update() except UnsupportedModelError: return self.async_abort(reason="unsupported_model") - except (ConnectionError, SnmpError): + except (ConnectionError, SnmpError, TimeoutError): return self.async_abort(reason="cannot_connect") # Check if already configured diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index f0c55549fd7..dc8e5c4d079 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -94,10 +94,11 @@ async def test_invalid_hostname(hass: HomeAssistant) -> None: assert result["errors"] == {CONF_HOST: "wrong_host"} -async def test_connection_error(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("exc", [ConnectionError, TimeoutError]) +async def test_connection_error(hass: HomeAssistant, exc: Exception) -> None: """Test connection to host error.""" with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=ConnectionError() + "brother.Brother._get_data", side_effect=exc ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG @@ -148,10 +149,11 @@ async def test_device_exists_abort(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" -async def test_zeroconf_snmp_error(hass: HomeAssistant) -> None: - """Test we abort zeroconf flow on SNMP error.""" +@pytest.mark.parametrize("exc", [ConnectionError, TimeoutError, SnmpError("error")]) +async def test_zeroconf_exception(hass: HomeAssistant, exc: Exception) -> None: + """Test we abort zeroconf flow on exception.""" with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=SnmpError("error") + "brother.Brother._get_data", side_effect=exc ): result = await hass.config_entries.flow.async_init( DOMAIN, From 2a5c85a020e22022a82ac9a8508be5e0eaa03b12 Mon Sep 17 00:00:00 2001 From: Massimo Savazzi Date: Sat, 16 Mar 2024 16:52:51 +0100 Subject: [PATCH 1152/1691] Add JVC Projector Sensors (#108949) * Add JVC Projector Sensors - Power Status, Input * Removed commented line, removed name in icons.json * fixed icons.json file * fixed tests * Update homeassistant/components/jvc_projector/sensor.py Co-authored-by: Joost Lekkerkerker * Fixed as requested * Fixed code as requested * added fixes * Fixed sensor creation * fixed const * fixed icons * Added test for both sensors * Added ha state stest * fixed commented line and removed useless ones * Changed time FAST - SLOW to be more responsive * Rolled back to previous values 6/60 * Update sensor.py removed off * Update icons.json * Removed the Input Sensor entity * Update tests/components/jvc_projector/test_sensor.py Co-authored-by: Martin Hjelmare * Update tests/components/jvc_projector/test_sensor.py Co-authored-by: Martin Hjelmare * Update tests/components/jvc_projector/test_sensor.py Co-authored-by: Martin Hjelmare * Update tests/components/jvc_projector/test_sensor.py Co-authored-by: Martin Hjelmare * Update tests/components/jvc_projector/test_sensor.py Co-authored-by: Martin Hjelmare * Update tests/components/jvc_projector/test_sensor.py Co-authored-by: Martin Hjelmare * Updated unique id of sensor Co-authored-by: Steve Easley * Added translation and string for Power Status * Update homeassistant/components/jvc_projector/strings.json Co-authored-by: Steve Easley * Update homeassistant/components/jvc_projector/strings.json Co-authored-by: Steve Easley * Update strings.json * Update strings.json added missing , --------- Co-authored-by: Joost Lekkerkerker Co-authored-by: Martin Hjelmare Co-authored-by: Steve Easley --- .../components/jvc_projector/__init__.py | 2 +- .../components/jvc_projector/icons.json | 11 ++++ .../components/jvc_projector/sensor.py | 65 +++++++++++++++++++ .../components/jvc_projector/strings.json | 12 ++++ tests/components/jvc_projector/conftest.py | 2 +- .../jvc_projector/test_coordinator.py | 4 +- tests/components/jvc_projector/test_sensor.py | 24 +++++++ 7 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/jvc_projector/sensor.py create mode 100644 tests/components/jvc_projector/test_sensor.py diff --git a/homeassistant/components/jvc_projector/__init__.py b/homeassistant/components/jvc_projector/__init__.py index 33af1d315f7..28e4cc995bb 100644 --- a/homeassistant/components/jvc_projector/__init__.py +++ b/homeassistant/components/jvc_projector/__init__.py @@ -18,7 +18,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DOMAIN from .coordinator import JvcProjectorDataUpdateCoordinator -PLATFORMS = [Platform.BINARY_SENSOR, Platform.REMOTE] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.REMOTE, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/jvc_projector/icons.json b/homeassistant/components/jvc_projector/icons.json index 94e2ec41cf6..c70ded78cb4 100644 --- a/homeassistant/components/jvc_projector/icons.json +++ b/homeassistant/components/jvc_projector/icons.json @@ -7,6 +7,17 @@ "on": "mdi:projector" } } + }, + "sensor": { + "jvc_power_status": { + "default": "mdi:power-plug-off", + "state": { + "on": "mdi:power-plug", + "warming": "mdi:heat-wave", + "cooling": "mdi:snowflake", + "error": "mdi:alert-circle" + } + } } } } diff --git a/homeassistant/components/jvc_projector/sensor.py b/homeassistant/components/jvc_projector/sensor.py new file mode 100644 index 00000000000..9be04b367e6 --- /dev/null +++ b/homeassistant/components/jvc_projector/sensor.py @@ -0,0 +1,65 @@ +"""Sensor platform for JVC Projector integration.""" + +from __future__ import annotations + +from jvcprojector import const + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import JvcProjectorDataUpdateCoordinator +from .const import DOMAIN +from .entity import JvcProjectorEntity + +JVC_SENSORS = ( + SensorEntityDescription( + key="power", + translation_key="jvc_power_status", + device_class=SensorDeviceClass.ENUM, + entity_category=EntityCategory.DIAGNOSTIC, + options=[ + const.STANDBY, + const.ON, + const.WARMING, + const.COOLING, + const.ERROR, + ], + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the JVC Projector platform from a config entry.""" + coordinator: JvcProjectorDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + JvcSensor(coordinator, description) for description in JVC_SENSORS + ) + + +class JvcSensor(JvcProjectorEntity, SensorEntity): + """The entity class for JVC Projector integration.""" + + def __init__( + self, + coordinator: JvcProjectorDataUpdateCoordinator, + description: SensorEntityDescription, + ) -> None: + """Initialize the JVC Projector sensor.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.unique_id}_{description.key}" + + @property + def native_value(self) -> str | None: + """Return the native value.""" + return self.coordinator.data[self.entity_description.key] diff --git a/homeassistant/components/jvc_projector/strings.json b/homeassistant/components/jvc_projector/strings.json index 06efdc8f9aa..9991fa1cf67 100644 --- a/homeassistant/components/jvc_projector/strings.json +++ b/homeassistant/components/jvc_projector/strings.json @@ -37,6 +37,18 @@ "jvc_power": { "name": "[%key:component::sensor::entity_component::power::name%]" } + }, + "sensor": { + "jvc_power_status": { + "name": "Power status", + "state": { + "standby": "[%key:common::state::standby%]", + "on": "[%key:common::state::on%]", + "warming": "Warming", + "cooling": "Cooling", + "error": "Error" + } + } } } } diff --git a/tests/components/jvc_projector/conftest.py b/tests/components/jvc_projector/conftest.py index 091aad9e849..10603e8ae39 100644 --- a/tests/components/jvc_projector/conftest.py +++ b/tests/components/jvc_projector/conftest.py @@ -27,7 +27,7 @@ def fixture_mock_device(request) -> Generator[None, AsyncMock, None]: device.port = MOCK_PORT device.mac = MOCK_MAC device.model = MOCK_MODEL - device.get_state.return_value = {"power": "standby"} + device.get_state.return_value = {"power": "standby", "input": "hdmi1"} yield device diff --git a/tests/components/jvc_projector/test_coordinator.py b/tests/components/jvc_projector/test_coordinator.py index cfda3728eb0..24297348653 100644 --- a/tests/components/jvc_projector/test_coordinator.py +++ b/tests/components/jvc_projector/test_coordinator.py @@ -23,7 +23,7 @@ async def test_coordinator_update( mock_integration: MockConfigEntry, ) -> None: """Test coordinator update runs.""" - mock_device.get_state.return_value = {"power": "standby"} + mock_device.get_state.return_value = {"power": "standby", "input": "hdmi1"} async_fire_time_changed( hass, utcnow() + timedelta(seconds=INTERVAL_SLOW.seconds + 1) ) @@ -65,7 +65,7 @@ async def test_coordinator_device_on( mock_config_entry: MockConfigEntry, ) -> None: """Test coordinator changes update interval when device is on.""" - mock_device.get_state.return_value = {"power": "on"} + mock_device.get_state.return_value = {"power": "on", "input": "hdmi1"} mock_config_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/jvc_projector/test_sensor.py b/tests/components/jvc_projector/test_sensor.py new file mode 100644 index 00000000000..1827363e5ad --- /dev/null +++ b/tests/components/jvc_projector/test_sensor.py @@ -0,0 +1,24 @@ +"""Tests for the JVC Projector binary sensor device.""" + +from unittest.mock import MagicMock + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry + +POWER_ID = "sensor.jvc_projector_power_status" + + +async def test_entity_state( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_device: MagicMock, + mock_integration: MockConfigEntry, +) -> None: + """Tests entity state is registered.""" + state = hass.states.get(POWER_ID) + assert state + assert entity_registry.async_get(state.entity_id) + + assert state.state == "standby" From ccd2e989c3a56c71b8820dd292e3833cbd46e047 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:37:20 +0100 Subject: [PATCH 1153/1691] Enable ruff RUF005 and fix occurrences (#113589) --- .../components/advantage_air/climate.py | 2 +- .../components/android_ip_webcam/sensor.py | 2 +- .../components/androidtv/config_flow.py | 2 +- homeassistant/components/api/__init__.py | 2 +- .../components/aussie_broadband/__init__.py | 2 +- homeassistant/components/demo/media_player.py | 4 +--- .../components/generic_thermostat/climate.py | 2 +- .../components/google_assistant/helpers.py | 2 +- homeassistant/components/harmony/select.py | 2 +- homeassistant/components/hdmi_cec/__init__.py | 2 +- .../components/homekit_controller/climate.py | 6 +++-- .../homekit_controller/humidifier.py | 5 ++-- homeassistant/components/isy994/const.py | 20 +++++++++------- .../components/konnected/config_flow.py | 11 +++++---- homeassistant/components/ld2410_ble/sensor.py | 24 +++++++++---------- .../components/light/device_action.py | 8 +++++-- homeassistant/components/mqtt/config_flow.py | 2 +- homeassistant/components/network/__init__.py | 2 +- homeassistant/components/omnilogic/common.py | 2 +- homeassistant/components/person/__init__.py | 2 +- .../components/proximity/coordinator.py | 6 +++-- homeassistant/components/roku/media_player.py | 9 ++++--- homeassistant/components/roku/select.py | 2 +- homeassistant/components/sense/sensor.py | 3 ++- .../seven_segments/image_processing.py | 16 ++++++------- .../components/shell_command/__init__.py | 2 +- homeassistant/components/smartthings/cover.py | 3 ++- homeassistant/components/sonos/speaker.py | 2 +- homeassistant/components/version/const.py | 3 ++- homeassistant/components/workday/const.py | 2 +- homeassistant/components/x10/light.py | 2 +- .../components/xiaomi_aqara/light.py | 2 +- .../yamaha_musiccast/media_player.py | 2 +- .../zha/core/cluster_handlers/__init__.py | 4 ++-- homeassistant/components/zha/core/device.py | 2 +- homeassistant/components/zha/core/group.py | 4 ++-- homeassistant/components/zha/entity.py | 2 +- homeassistant/config.py | 2 +- homeassistant/helpers/http.py | 2 +- homeassistant/helpers/script.py | 2 +- pyproject.toml | 1 + script/gen_requirements_all.py | 20 +++++++++------- script/hassfest/manifest.py | 11 +++++++-- tests/components/anova/conftest.py | 2 +- tests/components/demo/test_fan.py | 7 +++--- tests/components/derivative/test_sensor.py | 6 +---- .../components/device_automation/test_init.py | 8 +++---- tests/components/filter/test_sensor.py | 6 ++--- tests/components/http/test_ban.py | 2 +- tests/components/onewire/__init__.py | 2 +- tests/components/stream/test_recorder.py | 2 +- tests/components/vizio/const.py | 2 +- tests/components/vizio/test_media_player.py | 4 ++-- tests/components/vulcan/test_config_flow.py | 16 ++++++++----- tests/components/wyoming/test_satellite.py | 2 +- tests/components/yeelight/test_light.py | 7 +++--- tests/conftest.py | 2 +- tests/test_main.py | 2 +- 58 files changed, 150 insertions(+), 128 deletions(-) diff --git a/homeassistant/components/advantage_air/climate.py b/homeassistant/components/advantage_air/climate.py index b20171fa603..49b8224a902 100644 --- a/homeassistant/components/advantage_air/climate.py +++ b/homeassistant/components/advantage_air/climate.py @@ -58,7 +58,7 @@ HVAC_MODES = [ HVACMode.FAN_ONLY, HVACMode.DRY, ] -HVAC_MODES_MYAUTO = HVAC_MODES + [HVACMode.HEAT_COOL] +HVAC_MODES_MYAUTO = [*HVAC_MODES, HVACMode.HEAT_COOL] SUPPORTED_FEATURES = ( ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.TURN_OFF diff --git a/homeassistant/components/android_ip_webcam/sensor.py b/homeassistant/components/android_ip_webcam/sensor.py index eb00dd5dbf7..7ccb0661a6c 100644 --- a/homeassistant/components/android_ip_webcam/sensor.py +++ b/homeassistant/components/android_ip_webcam/sensor.py @@ -132,7 +132,7 @@ async def async_setup_entry( sensor for sensor in SENSOR_TYPES if sensor.key - in coordinator.cam.enabled_sensors + ["audio_connections", "video_connections"] + in [*coordinator.cam.enabled_sensors, "audio_connections", "video_connections"] ] async_add_entities( IPWebcamSensor(coordinator, description) for description in sensor_types diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index 765ee5def16..20396b20bb9 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -235,7 +235,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry): apps = [SelectOptionDict(value=APPS_NEW_ID, label="Add new")] + [ SelectOptionDict(value=k, label=v) for k, v in apps_list.items() ] - rules = [RULES_NEW_ID] + list(self._state_det_rules) + rules = [RULES_NEW_ID, *self._state_det_rules] options = self.options data_schema = vol.Schema( diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index a427957de15..25d404cbd15 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -137,7 +137,7 @@ class APIEventStream(HomeAssistantView): restrict: list[str] | None = None if restrict_str := request.query.get("restrict"): - restrict = restrict_str.split(",") + [EVENT_HOMEASSISTANT_STOP] + restrict = [*restrict_str.split(","), EVENT_HOMEASSISTANT_STOP] async def forward_events(event: Event) -> None: """Forward events to the open request.""" diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index 13fa919eb9a..72fa824bb5b 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -33,7 +33,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) # Ignore services that don't support usage data - ignore_types = FETCH_TYPES + ["Hardware"] + ignore_types = [*FETCH_TYPES, "Hardware"] try: await client.login() diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index a31d4a29c0e..8ce77bcd615 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -323,9 +323,7 @@ class DemoMusicPlayer(AbstractDemoPlayer): def join_players(self, group_members: list[str]) -> None: """Join `group_members` as a player group with the current player.""" - self._attr_group_members = [ - self.entity_id, - ] + group_members + self._attr_group_members = [self.entity_id, *group_members] self.schedule_update_ha_state() def unjoin_player(self) -> None: diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 0c63f7d2d15..0ee96232d4c 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -236,7 +236,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): ) if len(presets): self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE - self._attr_preset_modes = [PRESET_NONE] + list(presets.keys()) + self._attr_preset_modes = [PRESET_NONE, *presets.keys()] else: self._attr_preset_modes = [PRESET_NONE] self._presets = presets diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 480a7e73d8b..4d0cedc5311 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -625,7 +625,7 @@ class GoogleEntity: if (config_aliases := entity_config.get(CONF_ALIASES, [])) or ( entity_entry and entity_entry.aliases ): - device["name"]["nicknames"] = [name] + config_aliases + device["name"]["nicknames"] = [name, *config_aliases] if entity_entry: device["name"]["nicknames"].extend(entity_entry.aliases) diff --git a/homeassistant/components/harmony/select.py b/homeassistant/components/harmony/select.py index 987c7042b99..ca653e03ccc 100644 --- a/homeassistant/components/harmony/select.py +++ b/homeassistant/components/harmony/select.py @@ -47,7 +47,7 @@ class HarmonyActivitySelect(HarmonyEntity, SelectEntity): @property def options(self) -> list[str]: """Return a set of selectable options.""" - return [TRANSLATABLE_POWER_OFF] + sorted(self._data.activity_names) + return [TRANSLATABLE_POWER_OFF, *sorted(self._data.activity_names)] @property def current_option(self) -> str | None: diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 07a45cf8480..7902fa1c33c 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -172,7 +172,7 @@ def parse_mapping(mapping, parents=None): if isinstance(addr, (str,)) and isinstance(val, (str,)): yield (addr, PhysicalAddress(val)) else: - cur = parents + [addr] + cur = [*parents, addr] if isinstance(val, dict): yield from parse_mapping(val, cur) elif isinstance(val, str): diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 7fe11b8f875..dda181b31ff 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -201,7 +201,8 @@ class HomeKitHeaterCoolerEntity(HomeKitBaseClimateEntity): def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" - return super().get_characteristic_types() + [ + return [ + *super().get_characteristic_types(), CharacteristicsTypes.ACTIVE, CharacteristicsTypes.CURRENT_HEATER_COOLER_STATE, CharacteristicsTypes.TARGET_HEATER_COOLER_STATE, @@ -479,7 +480,8 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity): def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" - return super().get_characteristic_types() + [ + return [ + *super().get_characteristic_types(), CharacteristicsTypes.HEATING_COOLING_CURRENT, CharacteristicsTypes.HEATING_COOLING_TARGET, CharacteristicsTypes.TEMPERATURE_COOLING_THRESHOLD, diff --git a/homeassistant/components/homekit_controller/humidifier.py b/homeassistant/components/homekit_controller/humidifier.py index fef8fed33ad..fecba147a71 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -155,8 +155,9 @@ class HomeKitDehumidifier(HomeKitBaseHumidifier): def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity cares about.""" - return super().get_characteristic_types() + [ - CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD + return [ + *super().get_characteristic_types(), + CharacteristicsTypes.RELATIVE_HUMIDITY_DEHUMIDIFIER_THRESHOLD, ] @property diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index fae05421f63..daa474b736b 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -214,19 +214,21 @@ NODE_FILTERS: dict[Platform, dict[str, list[str]]] = { "7.13.", TYPE_CATEGORY_SAFETY, ], # Does a startswith() match; include the dot - FILTER_ZWAVE_CAT: (["104", "112", "138"] + list(map(str, range(148, 180)))), + FILTER_ZWAVE_CAT: (["104", "112", "138", *map(str, range(148, 180))]), }, Platform.SENSOR: { # This is just a more-readable way of including MOST uoms between 1-100 # (Remember that range() is non-inclusive of the stop value) FILTER_UOM: ( - ["1"] - + list(map(str, range(3, 11))) - + list(map(str, range(12, 51))) - + list(map(str, range(52, 66))) - + list(map(str, range(69, 78))) - + ["79"] - + list(map(str, range(82, 97))) + [ + "1", + *map(str, range(3, 11)), + *map(str, range(12, 51)), + *map(str, range(52, 66)), + *map(str, range(69, 78)), + "79", + *map(str, range(82, 97)), + ] ), FILTER_STATES: [], FILTER_NODE_DEF_ID: [ @@ -238,7 +240,7 @@ NODE_FILTERS: dict[Platform, dict[str, list[str]]] = { "RemoteLinc2_ADV", ], FILTER_INSTEON_TYPE: ["0.16.", "0.17.", "0.18.", "9.0.", "9.7."], - FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 186)))), + FILTER_ZWAVE_CAT: (["118", "143", *map(str, range(180, 186))]), }, Platform.LOCK: { FILTER_UOM: ["11"], diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index e2b460042f1..29f4fbe2a49 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -570,9 +570,10 @@ class OptionsFlowHandler(OptionsFlow): if user_input is not None: zone = {"zone": self.active_cfg} zone.update(user_input) - self.new_opt[CONF_BINARY_SENSORS] = self.new_opt.get( - CONF_BINARY_SENSORS, [] - ) + [zone] + self.new_opt[CONF_BINARY_SENSORS] = [ + *self.new_opt.get(CONF_BINARY_SENSORS, []), + zone, + ] self.io_cfg.pop(self.active_cfg) self.active_cfg = None @@ -645,7 +646,7 @@ class OptionsFlowHandler(OptionsFlow): if user_input is not None: zone = {"zone": self.active_cfg} zone.update(user_input) - self.new_opt[CONF_SENSORS] = self.new_opt.get(CONF_SENSORS, []) + [zone] + self.new_opt[CONF_SENSORS] = [*self.new_opt.get(CONF_SENSORS, []), zone] self.io_cfg.pop(self.active_cfg) self.active_cfg = None @@ -714,7 +715,7 @@ class OptionsFlowHandler(OptionsFlow): zone = {"zone": self.active_cfg} zone.update(user_input) del zone[CONF_MORE_STATES] - self.new_opt[CONF_SWITCHES] = self.new_opt.get(CONF_SWITCHES, []) + [zone] + self.new_opt[CONF_SWITCHES] = [*self.new_opt.get(CONF_SWITCHES, []), zone] # iterate through multiple switch states if self.current_states: diff --git a/homeassistant/components/ld2410_ble/sensor.py b/homeassistant/components/ld2410_ble/sensor.py index 933a2151a17..d3a2a6e599c 100644 --- a/homeassistant/components/ld2410_ble/sensor.py +++ b/homeassistant/components/ld2410_ble/sensor.py @@ -106,19 +106,17 @@ STATIC_ENERGY_GATES = [ for i in range(0, 9) ] -SENSOR_DESCRIPTIONS = ( - [ - MOVING_TARGET_DISTANCE_DESCRIPTION, - STATIC_TARGET_DISTANCE_DESCRIPTION, - MOVING_TARGET_ENERGY_DESCRIPTION, - STATIC_TARGET_ENERGY_DESCRIPTION, - DETECTION_DISTANCE_DESCRIPTION, - MAX_MOTION_GATES_DESCRIPTION, - MAX_STATIC_GATES_DESCRIPTION, - ] - + MOTION_ENERGY_GATES - + STATIC_ENERGY_GATES -) +SENSOR_DESCRIPTIONS = [ + MOVING_TARGET_DISTANCE_DESCRIPTION, + STATIC_TARGET_DISTANCE_DESCRIPTION, + MOVING_TARGET_ENERGY_DESCRIPTION, + STATIC_TARGET_ENERGY_DESCRIPTION, + DETECTION_DISTANCE_DESCRIPTION, + MAX_MOTION_GATES_DESCRIPTION, + MAX_STATIC_GATES_DESCRIPTION, + *MOTION_ENERGY_GATES, + *STATIC_ENERGY_GATES, +] async def async_setup_entry( diff --git a/homeassistant/components/light/device_action.py b/homeassistant/components/light/device_action.py index 50dbf42b677..dbdf7200a7b 100644 --- a/homeassistant/components/light/device_action.py +++ b/homeassistant/components/light/device_action.py @@ -47,8 +47,12 @@ _ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( vol.Required(ATTR_ENTITY_ID): cv.entity_id_or_uuid, vol.Required(CONF_DOMAIN): DOMAIN, vol.Required(CONF_TYPE): vol.In( - toggle_entity.DEVICE_ACTION_TYPES - + [TYPE_BRIGHTNESS_INCREASE, TYPE_BRIGHTNESS_DECREASE, TYPE_FLASH] + [ + *toggle_entity.DEVICE_ACTION_TYPES, + TYPE_BRIGHTNESS_INCREASE, + TYPE_BRIGHTNESS_DECREASE, + TYPE_FLASH, + ] ), vol.Optional(ATTR_BRIGHTNESS_PCT): VALID_BRIGHTNESS_PCT, vol.Optional(ATTR_FLASH): VALID_FLASH, diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 3889af78591..b6fcae4d5ec 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -572,7 +572,7 @@ async def async_get_broker_settings( ) schema = vol.Schema({cv.string: cv.template}) schema(validated_user_input[CONF_WS_HEADERS]) - except JSON_DECODE_EXCEPTIONS + (vol.MultipleInvalid,): + except (*JSON_DECODE_EXCEPTIONS, vol.MultipleInvalid): errors["base"] = "bad_ws_headers" return False return True diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index 517ab7e7246..10046f75127 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -140,7 +140,7 @@ async def async_get_announce_addresses(hass: HomeAssistant) -> list[str]: if default_ip := await async_get_source_ip(hass, target_ip=MDNS_TARGET_IP): if default_ip in addresses: addresses.remove(default_ip) - return [default_ip] + list(addresses) + return [default_ip, *addresses] return list(addresses) diff --git a/homeassistant/components/omnilogic/common.py b/homeassistant/components/omnilogic/common.py index 3fbd53d20f2..adc87e7be26 100644 --- a/homeassistant/components/omnilogic/common.py +++ b/homeassistant/components/omnilogic/common.py @@ -60,7 +60,7 @@ class OmniLogicUpdateCoordinator(DataUpdateCoordinator[dict[tuple, dict[str, Any if "systemId" in item: system_id = item["systemId"] - current_id = current_id + (item_kind, system_id) + current_id = (*current_id, item_kind, system_id) data[current_id] = item for kind in ALL_ITEM_KINDS: diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 08fff3a652e..3b2273f5033 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -134,7 +134,7 @@ async def async_add_user_device_tracker( await coll.async_update_item( person[CONF_ID], - {CONF_DEVICE_TRACKERS: device_trackers + [device_tracker_entity_id]}, + {CONF_DEVICE_TRACKERS: [*device_trackers, device_tracker_entity_id]}, ) break diff --git a/homeassistant/components/proximity/coordinator.py b/homeassistant/components/proximity/coordinator.py index d5f4a33d31e..829e9aaf4a1 100644 --- a/homeassistant/components/proximity/coordinator.py +++ b/homeassistant/components/proximity/coordinator.py @@ -124,8 +124,10 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): **self.config_entry.data, CONF_TRACKED_ENTITIES: [ tracked_entity - for tracked_entity in self.tracked_entities - + [new_tracked_entity_id] + for tracked_entity in ( + *self.tracked_entities, + new_tracked_entity_id, + ) if tracked_entity != old_tracked_entity_id ], }, diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 4418f708a7f..92361909219 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -255,9 +255,12 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): @property def source_list(self) -> list[str]: """List of available input sources.""" - return ["Home"] + sorted( - app.name for app in self.coordinator.data.apps if app.name is not None - ) + return [ + "Home", + *sorted( + app.name for app in self.coordinator.data.apps if app.name is not None + ), + ] @roku_exception_handler() async def search(self, keyword: str) -> None: diff --git a/homeassistant/components/roku/select.py b/homeassistant/components/roku/select.py index 366215a6684..5f3b9d4049b 100644 --- a/homeassistant/components/roku/select.py +++ b/homeassistant/components/roku/select.py @@ -30,7 +30,7 @@ def _get_application_name(device: RokuDevice) -> str | None: def _get_applications(device: RokuDevice) -> list[str]: - return ["Home"] + sorted(app.name for app in device.apps if app.name is not None) + return ["Home", *sorted(app.name for app in device.apps if app.name is not None)] def _get_channel_name(device: RokuDevice) -> str | None: diff --git a/homeassistant/components/sense/sensor.py b/homeassistant/components/sense/sensor.py index 18155daaf1f..199bae43701 100644 --- a/homeassistant/components/sense/sensor.py +++ b/homeassistant/components/sense/sensor.py @@ -70,7 +70,8 @@ TRENDS_SENSOR_TYPES = { SENSOR_VARIANTS = [(PRODUCTION_ID, PRODUCTION_NAME), (CONSUMPTION_ID, CONSUMPTION_NAME)] # Trend production/consumption variants -TREND_SENSOR_VARIANTS = SENSOR_VARIANTS + [ +TREND_SENSOR_VARIANTS = [ + *SENSOR_VARIANTS, (PRODUCTION_PCT_ID, PRODUCTION_PCT_NAME), (NET_PRODUCTION_ID, NET_PRODUCTION_NAME), (FROM_GRID_ID, FROM_GRID_NAME), diff --git a/homeassistant/components/seven_segments/image_processing.py b/homeassistant/components/seven_segments/image_processing.py index 9024a2d4ed4..622ceb761a0 100644 --- a/homeassistant/components/seven_segments/image_processing.py +++ b/homeassistant/components/seven_segments/image_processing.py @@ -96,14 +96,14 @@ class ImageProcessingSsocr(ImageProcessingEntity): threshold = ["-t", str(config[CONF_THRESHOLD])] extra_arguments = config[CONF_EXTRA_ARGUMENTS].split(" ") - self._command = ( - [config[CONF_SSOCR_BIN]] - + crop - + digits - + threshold - + rotate - + extra_arguments - ) + self._command = [ + config[CONF_SSOCR_BIN], + *crop, + *digits, + *threshold, + *rotate, + *extra_arguments, + ] self._command.append(self.filepath) @property diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index 98b08d975ff..91cb48e9988 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -77,7 +77,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: else: # Template used. Break into list and use create_subprocess_exec # (which uses shell=False) for security - shlexed_cmd = [prog] + shlex.split(rendered_args) + shlexed_cmd = [prog, *shlex.split(rendered_args)] create_process = asyncio.create_subprocess_exec( *shlexed_cmd, diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index b1e260b5962..276a68176b4 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -63,7 +63,8 @@ def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: # Must have one of the min_required if any(capability in capabilities for capability in min_required): # Return all capabilities supported/consumed - return min_required + [ + return [ + *min_required, Capability.battery, Capability.switch_level, Capability.window_shade_level, diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 2d92fe2f741..ebb2738c641 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -837,7 +837,7 @@ class SonosSpeaker: if p.uid != coordinator_uid and p.is_visible ] - return [coordinator_uid] + joined_uids + return [coordinator_uid, *joined_uids] async def _async_extract_group(event: SonosEvent | None) -> list[str]: """Extract group layout from a topology event.""" diff --git a/homeassistant/components/version/const.py b/homeassistant/components/version/const.py index 56a1c88c5f2..c0a5062bedb 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -92,7 +92,8 @@ VERSION_SOURCE_MAP: Final[dict[str, str]] = { VERSION_SOURCE_PYPI: "pypi", } -VALID_SOURCES: Final[list[str]] = HA_VERSION_SOURCES + [ +VALID_SOURCES: Final[list[str]] = [ + *HA_VERSION_SOURCES, "hassio", # Kept to not break existing configurations "docker", # Kept to not break existing configurations ] diff --git a/homeassistant/components/workday/const.py b/homeassistant/components/workday/const.py index 847c3809822..6a46f1e824b 100644 --- a/homeassistant/components/workday/const.py +++ b/homeassistant/components/workday/const.py @@ -8,7 +8,7 @@ from homeassistant.const import WEEKDAYS, Platform LOGGER = logging.getLogger(__package__) -ALLOWED_DAYS = WEEKDAYS + ["holiday"] +ALLOWED_DAYS = [*WEEKDAYS, "holiday"] DOMAIN = "workday" PLATFORMS = [Platform.BINARY_SENSOR] diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py index 5fbbd4f2ca6..8f105d9c695 100644 --- a/homeassistant/components/x10/light.py +++ b/homeassistant/components/x10/light.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def x10_command(command): """Execute X10 command and check output.""" - return check_output(["heyu"] + command.split(" "), stderr=STDOUT) + return check_output(["heyu", *command.split(" ")], stderr=STDOUT) def get_unit_status(code): diff --git a/homeassistant/components/xiaomi_aqara/light.py b/homeassistant/components/xiaomi_aqara/light.py index 49dae825de7..fc19a22eb5f 100644 --- a/homeassistant/components/xiaomi_aqara/light.py +++ b/homeassistant/components/xiaomi_aqara/light.py @@ -106,7 +106,7 @@ class XiaomiGatewayLight(XiaomiDevice, LightEntity): self._brightness = int(100 * kwargs[ATTR_BRIGHTNESS] / 255) rgb = color_util.color_hs_to_RGB(*self._hs) - rgba = (self._brightness,) + rgb + rgba = (self._brightness, *rgb) rgbhex = binascii.hexlify(struct.pack("BBBB", *rgba)).decode("ASCII") rgbhex = int(rgbhex, 16) diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index d03e4666ab2..04c365d9996 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -676,7 +676,7 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity): return [self] entities = self.get_all_mc_entities() clients = [entity for entity in entities if entity.is_part_of_group(self)] - return [self] + clients + return [self, *clients] @property def musiccast_zone_entity(self) -> MusicCastMediaPlayer: diff --git a/homeassistant/components/zha/core/cluster_handlers/__init__.py b/homeassistant/components/zha/core/cluster_handlers/__init__.py index 9e483f1700a..ff880ed3b69 100644 --- a/homeassistant/components/zha/core/cluster_handlers/__init__.py +++ b/homeassistant/components/zha/core/cluster_handlers/__init__.py @@ -556,7 +556,7 @@ class ClusterHandler(LogMixin): def log(self, level, msg, *args, **kwargs): """Log a message.""" msg = f"[%s:%s]: {msg}" - args = (self._endpoint.device.nwk, self._id) + args + args = (self._endpoint.device.nwk, self._id, *args) _LOGGER.log(level, msg, *args, **kwargs) def __getattr__(self, name): @@ -620,7 +620,7 @@ class ZDOClusterHandler(LogMixin): def log(self, level, msg, *args, **kwargs): """Log a message.""" msg = f"[%s:ZDO](%s): {msg}" - args = (self._zha_device.nwk, self._zha_device.model) + args + args = (self._zha_device.nwk, self._zha_device.model, *args) _LOGGER.log(level, msg, *args, **kwargs) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index fe19e1662d0..e6d9f3e66b5 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -1005,5 +1005,5 @@ class ZHADevice(LogMixin): def log(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None: """Log a message.""" msg = f"[%s](%s): {msg}" - args = (self.nwk, self.model) + args + args = (self.nwk, self.model, *args) _LOGGER.log(level, msg, *args, **kwargs) diff --git a/homeassistant/components/zha/core/group.py b/homeassistant/components/zha/core/group.py index 7b5a6e91516..a6156ab63b7 100644 --- a/homeassistant/components/zha/core/group.py +++ b/homeassistant/components/zha/core/group.py @@ -131,7 +131,7 @@ class ZHAGroupMember(LogMixin): def log(self, level: int, msg: str, *args: Any, **kwargs) -> None: """Log a message.""" msg = f"[%s](%s): {msg}" - args = (f"0x{self._zha_group.group_id:04x}", self.endpoint_id) + args + args = (f"0x{self._zha_group.group_id:04x}", self.endpoint_id, *args) _LOGGER.log(level, msg, *args, **kwargs) @@ -242,5 +242,5 @@ class ZHAGroup(LogMixin): def log(self, level: int, msg: str, *args: Any, **kwargs) -> None: """Log a message.""" msg = f"[%s](%s): {msg}" - args = (self.name, self.group_id) + args + args = (self.name, self.group_id, *args) _LOGGER.log(level, msg, *args, **kwargs) diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 5b9a11cb621..842450f9279 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -140,7 +140,7 @@ class BaseZhaEntity(LogMixin, entity.Entity): def log(self, level: int, msg: str, *args, **kwargs): """Log a message.""" msg = f"%s: {msg}" - args = (self.entity_id,) + args + args = (self.entity_id, *args) _LOGGER.log(level, msg, *args, **kwargs) diff --git a/homeassistant/config.py b/homeassistant/config.py index 46f213759b8..0cc79a37ca5 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -506,7 +506,7 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> dict: await merge_packages_config(hass, config, core_config.get(CONF_PACKAGES, {})) except vol.Invalid as exc: suffix = "" - if annotation := find_annotation(config, [CONF_CORE, CONF_PACKAGES] + exc.path): + if annotation := find_annotation(config, [CONF_CORE, CONF_PACKAGES, *exc.path]): suffix = f" at {_relpath(hass, annotation[0])}, line {annotation[1]}" _LOGGER.error( "Invalid package configuration '%s'%s: %s", CONF_PACKAGES, suffix, exc diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index f3b2b175997..018558a8761 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -166,7 +166,7 @@ class HomeAssistantView: ) -> None: """Register the view with a router.""" assert self.url is not None, "No url set for view" - urls = [self.url] + self.extra_urls + urls = [self.url, *self.extra_urls] routes: list[AbstractRoute] = [] for method in ("get", "post", "delete", "put", "patch", "head", "options"): diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 99d950d4d76..e38642768df 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -130,7 +130,7 @@ CONF_MAX = "max" DEFAULT_MAX = 10 CONF_MAX_EXCEEDED = "max_exceeded" -_MAX_EXCEEDED_CHOICES = list(LOGSEVERITY) + ["SILENT"] +_MAX_EXCEEDED_CHOICES = [*LOGSEVERITY, "SILENT"] DEFAULT_MAX_EXCEEDED = "WARNING" ATTR_CUR = "current" diff --git a/pyproject.toml b/pyproject.toml index 6cfd01a7c7e..7d0ff3f3e35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -610,6 +610,7 @@ select = [ "PLR", # pylint "PLW", # pylint "Q000", # Double quotes found but single quotes preferred + "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task "S102", # Use of exec detected "S103", # bad-file-permissions diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 4cab1f167c7..ddc197ed7a8 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -433,15 +433,17 @@ def gather_constraints() -> str: return ( GENERATED_MESSAGE + "\n".join( - sorted( - { - *core_requirements(), - *gather_recursive_requirements("default_config"), - *gather_recursive_requirements("mqtt"), - }, - key=str.lower, - ) - + [""] + [ + *sorted( + { + *core_requirements(), + *gather_recursive_requirements("default_config"), + *gather_recursive_requirements("mqtt"), + }, + key=str.lower, + ), + "", + ] ) + CONSTRAINT_BASE ) diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index 19860f3c4f6..0c7f48b9af3 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -398,8 +398,15 @@ def validate(integrations: dict[str, Integration], config: Config) -> None: manifests_resorted.append(integration.manifest_path) if config.action == "generate" and manifests_resorted: subprocess.run( - ["pre-commit", "run", "--hook-stage", "manual", "prettier", "--files"] - + manifests_resorted, + [ + "pre-commit", + "run", + "--hook-stage", + "manual", + "prettier", + "--files", + *manifests_resorted, + ], stdout=subprocess.DEVNULL, check=True, ) diff --git a/tests/components/anova/conftest.py b/tests/components/anova/conftest.py index cca58996031..88b5ae1fd41 100644 --- a/tests/components/anova/conftest.py +++ b/tests/components/anova/conftest.py @@ -25,7 +25,7 @@ async def anova_api( async def get_devices_side_effect(): if not api_mock.existing_devices: api_mock.existing_devices = [] - api_mock.existing_devices = api_mock.existing_devices + [new_device] + api_mock.existing_devices = [*api_mock.existing_devices, new_device] return [new_device] api_mock.authenticate.side_effect = authenticate_side_effect diff --git a/tests/components/demo/test_fan.py b/tests/components/demo/test_fan.py index a5a0b731cb8..bd42ae3a953 100644 --- a/tests/components/demo/test_fan.py +++ b/tests/components/demo/test_fan.py @@ -25,13 +25,12 @@ from homeassistant.setup import async_setup_component FULL_FAN_ENTITY_IDS = ["fan.living_room_fan", "fan.percentage_full_fan"] FANS_WITH_PRESET_MODE_ONLY = ["fan.preset_only_limited_fan"] -LIMITED_AND_FULL_FAN_ENTITY_IDS = FULL_FAN_ENTITY_IDS + [ +LIMITED_AND_FULL_FAN_ENTITY_IDS = [ + *FULL_FAN_ENTITY_IDS, "fan.ceiling_fan", "fan.percentage_limited_fan", ] -FANS_WITH_PRESET_MODES = FULL_FAN_ENTITY_IDS + [ - "fan.percentage_limited_fan", -] +FANS_WITH_PRESET_MODES = [*FULL_FAN_ENTITY_IDS, "fan.percentage_limited_fan"] PERCENTAGE_MODEL_FANS = ["fan.percentage_full_fan", "fan.percentage_limited_fan"] diff --git a/tests/components/derivative/test_sensor.py b/tests/components/derivative/test_sensor.py index ae566151065..e4f57437d24 100644 --- a/tests/components/derivative/test_sensor.py +++ b/tests/components/derivative/test_sensor.py @@ -238,11 +238,7 @@ async def test_double_signal_after_delay(hass: HomeAssistant) -> None: # The old algorithm would produce extreme values if, after a delay longer than the time window # there would be two signals, a large spike would be produced. Check explicitly for this situation time_window = 60 - times = [*range(time_window * 10)] - times = times + [ - time_window * 20, - time_window * 20 + 0.01, - ] + times = [*range(time_window * 10), time_window * 20, time_window * 20 + 0.01] # just apply sine as some sort of temperature change and make sure the change after the delay is very small temperature_values = [sin(x) for x in times] diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 772737b4137..7de8c5c9adc 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -1276,11 +1276,9 @@ BAD_AUTOMATIONS = [ ), ] -BAD_TRIGGERS = BAD_CONDITIONS = BAD_AUTOMATIONS + [ - ( - {"domain": "light"}, - "required key not provided @ data{path}['device_id']", - ) +BAD_TRIGGERS = BAD_CONDITIONS = [ + *BAD_AUTOMATIONS, + ({"domain": "light"}, "required key not provided @ data{path}['device_id']"), ] diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index d0588b7ea9a..9a29d593b3c 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -384,7 +384,7 @@ def test_initial_outlier(values: list[State]) -> None: """Test issue #13363.""" filt = OutlierFilter(window_size=3, precision=2, entity=None, radius=4.0) out = State("sensor.test_monitored", "4000") - for state in [out] + values: + for state in [out, *values]: filtered = filt.filter_state(state) assert filtered.state == 21 @@ -393,7 +393,7 @@ def test_unknown_state_outlier(values: list[State]) -> None: """Test issue #32395.""" filt = OutlierFilter(window_size=3, precision=2, entity=None, radius=4.0) out = State("sensor.test_monitored", "unknown") - for state in [out] + values + [out]: + for state in [out, *values, out]: try: filtered = filt.filter_state(state) except ValueError: @@ -413,7 +413,7 @@ def test_lowpass(values: list[State]) -> None: """Test if lowpass filter works.""" filt = LowPassFilter(window_size=10, precision=2, entity=None, time_constant=10) out = State("sensor.test_monitored", "unknown") - for state in [out] + values + [out]: + for state in [out, *values, out]: try: filtered = filt.filter_state(state) except ValueError: diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index 103e0484fb0..ee8f8c80864 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -30,7 +30,7 @@ from tests.typing import ClientSessionGenerator SUPERVISOR_IP = "1.2.3.4" BANNED_IPS = ["200.201.202.203", "100.64.0.2"] -BANNED_IPS_WITH_SUPERVISOR = BANNED_IPS + [SUPERVISOR_IP] +BANNED_IPS_WITH_SUPERVISOR = [*BANNED_IPS, SUPERVISOR_IP] @pytest.fixture(name="hassio_env") diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py index 7a9dc01117d..ed15cac94be 100644 --- a/tests/components/onewire/__init__.py +++ b/tests/components/onewire/__init__.py @@ -32,7 +32,7 @@ def setup_owproxy_mock_devices( ) # Ensure enough read side effect - dir_side_effect = [main_dir_return_value] + sub_dir_side_effect + dir_side_effect = [main_dir_return_value, *sub_dir_side_effect] read_side_effect = ( main_read_side_effect + sub_read_side_effect diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 29442620a10..9ab9b7a680a 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -109,7 +109,7 @@ async def test_record_path_not_allowed(hass: HomeAssistant, h264_video) -> None: def add_parts_to_segment(segment, source): """Add relevant part data to segment for testing recorder.""" - moof_locs = list(find_box(source.getbuffer(), b"moof")) + [len(source.getbuffer())] + moof_locs = [*find_box(source.getbuffer(), b"moof"), len(source.getbuffer())] segment.init = source.getbuffer()[: moof_locs[0]].tobytes() segment.parts = [ Part( diff --git a/tests/components/vizio/const.py b/tests/components/vizio/const.py index 0054b47c536..1f35cc16385 100644 --- a/tests/components/vizio/const.py +++ b/tests/components/vizio/const.py @@ -84,7 +84,7 @@ APP_LIST = [ }, ] APP_NAME_LIST = [app["name"] for app in APP_LIST] -INPUT_LIST_WITH_APPS = INPUT_LIST + ["CAST"] +INPUT_LIST_WITH_APPS = [*INPUT_LIST, "CAST"] CUSTOM_CONFIG = {CONF_APP_ID: "test", CONF_MESSAGE: None, CONF_NAME_SPACE: 10} ADDITIONAL_APP_CONFIG = { "name": CURRENT_APP, diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 2f585b9e8be..218c16eb939 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -564,7 +564,7 @@ async def test_setup_with_apps_include( hass, MOCK_TV_WITH_INCLUDE_CONFIG, CURRENT_APP_CONFIG ): attr = hass.states.get(ENTITY_ID).attributes - _assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + [CURRENT_APP]), attr) + _assert_source_list_with_apps([*INPUT_LIST_WITH_APPS, CURRENT_APP], attr) assert CURRENT_APP in attr[ATTR_INPUT_SOURCE_LIST] assert attr[ATTR_INPUT_SOURCE] == CURRENT_APP assert attr["app_name"] == CURRENT_APP @@ -582,7 +582,7 @@ async def test_setup_with_apps_exclude( hass, MOCK_TV_WITH_EXCLUDE_CONFIG, CURRENT_APP_CONFIG ): attr = hass.states.get(ENTITY_ID).attributes - _assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + [CURRENT_APP]), attr) + _assert_source_list_with_apps([*INPUT_LIST_WITH_APPS, CURRENT_APP], attr) assert CURRENT_APP in attr[ATTR_INPUT_SOURCE_LIST] assert attr[ATTR_INPUT_SOURCE] == CURRENT_APP assert attr["app_name"] == CURRENT_APP diff --git a/tests/components/vulcan/test_config_flow.py b/tests/components/vulcan/test_config_flow.py index 97dd47d7ea4..b0b928cfde2 100644 --- a/tests/components/vulcan/test_config_flow.py +++ b/tests/components/vulcan/test_config_flow.py @@ -88,8 +88,10 @@ async def test_config_flow_auth_success_with_multiple_students( mock_account.return_value = fake_account mock_student.return_value = [ Student.load(student) - for student in [load_fixture("fake_student_1.json", "vulcan")] - + [load_fixture("fake_student_2.json", "vulcan")] + for student in [ + load_fixture("fake_student_1.json", "vulcan"), + load_fixture("fake_student_2.json", "vulcan"), + ] ] result = await hass.config_entries.flow.async_init( const.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -379,8 +381,9 @@ async def test_multiple_config_entries_using_saved_credentials_2( ) -> None: """Test a successful config flow for multiple config entries using saved credentials (different situation).""" mock_student.return_value = [ - Student.load(load_fixture("fake_student_1.json", "vulcan")) - ] + [Student.load(load_fixture("fake_student_2.json", "vulcan"))] + Student.load(load_fixture("fake_student_1.json", "vulcan")), + Student.load(load_fixture("fake_student_2.json", "vulcan")), + ] MockConfigEntry( domain=const.DOMAIN, unique_id="123456", @@ -477,8 +480,9 @@ async def test_multiple_config_entries_using_saved_credentials_4( ) -> None: """Test a successful config flow for multiple config entries using saved credentials (different situation).""" mock_student.return_value = [ - Student.load(load_fixture("fake_student_1.json", "vulcan")) - ] + [Student.load(load_fixture("fake_student_2.json", "vulcan"))] + Student.load(load_fixture("fake_student_1.json", "vulcan")), + Student.load(load_fixture("fake_student_2.json", "vulcan")), + ] MockConfigEntry( entry_id="456", domain=const.DOMAIN, diff --git a/tests/components/wyoming/test_satellite.py b/tests/components/wyoming/test_satellite.py index 5cbbfd0a8c3..76f442ef246 100644 --- a/tests/components/wyoming/test_satellite.py +++ b/tests/components/wyoming/test_satellite.py @@ -169,7 +169,7 @@ class SatelliteAsyncTcpClient(MockAsyncTcpClient): def inject_event(self, event: Event) -> None: """Put an event in as the next response.""" - self.responses = [event] + self.responses + self.responses = [event, *self.responses] async def test_satellite_pipeline(hass: HomeAssistant) -> None: diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index d3d2ba52124..4fed4f602e4 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -1418,9 +1418,10 @@ async def test_effects(hass: HomeAssistant) -> None: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - assert hass.states.get(ENTITY_LIGHT).attributes.get( - "effect_list" - ) == YEELIGHT_COLOR_EFFECT_LIST + ["mock_effect"] + assert hass.states.get(ENTITY_LIGHT).attributes.get("effect_list") == [ + *YEELIGHT_COLOR_EFFECT_LIST, + "mock_effect", + ] async def _async_test_effect(name, target=None, called=True): async_mocked_start_flow = AsyncMock() diff --git a/tests/conftest.py b/tests/conftest.py index 1f8ecc407d7..1159f66a698 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -993,7 +993,7 @@ async def _mqtt_mock_entry( nonlocal mock_mqtt_instance nonlocal real_mqtt_instance real_mqtt_instance = real_mqtt(*args, **kwargs) - spec = dir(real_mqtt_instance) + ["_mqttc"] + spec = [*dir(real_mqtt_instance), "_mqttc"] mock_mqtt_instance = MqttMockHAClient( return_value=real_mqtt_instance, spec_set=spec, diff --git a/tests/test_main.py b/tests/test_main.py index 32371ae4e6f..080787311a0 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -69,7 +69,7 @@ def test_skip_pip_mutually_exclusive(mock_exit) -> None: """Test --skip-pip and --skip-pip-package are mutually exclusive.""" def parse_args(*args): - with patch("sys.argv", ["python"] + list(args)): + with patch("sys.argv", ["python", *args]): return main.get_arguments() args = parse_args("--skip-pip") From 00361f5293e42b2ae2cbf0889f5f24ee0f01b373 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 16 Mar 2024 18:41:00 +0100 Subject: [PATCH 1154/1691] Remove unnecessary method in UniFi entity loader (#113597) --- .../components/unifi/hub/entity_loader.py | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index a7a1cb970ec..5ae3e8f789c 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -6,7 +6,6 @@ Make sure expected clients are available for platforms. from __future__ import annotations import asyncio -from collections.abc import Iterable from datetime import timedelta from functools import partial from typing import TYPE_CHECKING @@ -143,42 +142,36 @@ class UnifiEntityLoader: """Subscribe to UniFi API handlers and create entities.""" @callback - def async_load_entities(descriptions: Iterable[UnifiEntityDescription]) -> None: - """Load and subscribe to UniFi endpoints.""" - - @callback - def _add_unifi_entities() -> None: - """Add UniFi entity.""" - async_add_entities( - unifi_platform_entity(obj_id, self.hub, description) - for description in descriptions - for obj_id in description.api_handler_fn(self.hub.api) - if self._should_add_entity(description, obj_id) - ) - - _add_unifi_entities() - - @callback - def _create_unifi_entity( - description: UnifiEntityDescription, event: ItemEvent, obj_id: str - ) -> None: - """Create new UniFi entity on event.""" - if self._should_add_entity(description, obj_id): - async_add_entities( - [unifi_platform_entity(obj_id, self.hub, description)] - ) - - for description in descriptions: - description.api_handler_fn(self.hub.api).subscribe( - partial(_create_unifi_entity, description), ItemEvent.ADDED - ) - - self.hub.config.entry.async_on_unload( - async_dispatcher_connect( - self.hub.hass, - self.hub.signal_options_update, - _add_unifi_entities, - ) + def _add_unifi_entities() -> None: + """Add UniFi entity.""" + async_add_entities( + unifi_platform_entity(obj_id, self.hub, description) + for description in descriptions + for obj_id in description.api_handler_fn(self.hub.api) + if self._should_add_entity(description, obj_id) ) - async_load_entities(descriptions) + _add_unifi_entities() + + @callback + def _create_unifi_entity( + description: UnifiEntityDescription, event: ItemEvent, obj_id: str + ) -> None: + """Create new UniFi entity on event.""" + if self._should_add_entity(description, obj_id): + async_add_entities( + [unifi_platform_entity(obj_id, self.hub, description)] + ) + + for description in descriptions: + description.api_handler_fn(self.hub.api).subscribe( + partial(_create_unifi_entity, description), ItemEvent.ADDED + ) + + self.hub.config.entry.async_on_unload( + async_dispatcher_connect( + self.hub.hass, + self.hub.signal_options_update, + _add_unifi_entities, + ) + ) From c57dcacadedea3f3b4bc022139bf1bba7b8da83c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 16 Mar 2024 20:47:54 +0100 Subject: [PATCH 1155/1691] Axis use entity description in switch platform (#113595) * Draft * Make a generic register platform --- homeassistant/components/axis/switch.py | 68 ++++++++++++++++++++----- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index d7072bb877f..8364d1317f9 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -1,11 +1,19 @@ """Support for Axis switches.""" +from collections.abc import Callable, Iterable +from dataclasses import dataclass +from functools import partial from typing import Any from axis.models.event import Event, EventOperation, EventTopic -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -13,34 +21,68 @@ from .entity import AxisEventEntity from .hub import AxisHub +@dataclass(frozen=True, kw_only=True) +class AxisSwitchDescription(SwitchEntityDescription): + """Axis switch entity description.""" + + event_topic: EventTopic + """Event topic that provides state updates.""" + name_fn: Callable[[AxisHub, Event], str] + """Function providing the corresponding name to the event ID.""" + supported_fn: Callable[[AxisHub, Event], bool] + """Function validating if event is supported.""" + + +ENTITY_DESCRIPTIONS = ( + AxisSwitchDescription( + key="Relay state control", + device_class=SwitchDeviceClass.OUTLET, + entity_category=EntityCategory.CONFIG, + event_topic=EventTopic.RELAY, + supported_fn=lambda hub, event: isinstance(int(event.id), int), + name_fn=lambda hub, event: hub.api.vapix.ports[event.id].name, + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up a Axis switch.""" + """Set up the Axis switch platform.""" hub = AxisHub.get_hub(hass, config_entry) @callback - def async_create_entity(event: Event) -> None: - """Create Axis switch entity.""" - async_add_entities([AxisSwitch(event, hub)]) + def register_platform(descriptions: Iterable[AxisSwitchDescription]) -> None: + """Register entity platform to create entities on event initialized signal.""" - hub.api.event.subscribe( - async_create_entity, - topic_filter=EventTopic.RELAY, - operation_filter=EventOperation.INITIALIZED, - ) + @callback + def create_entity(description: AxisSwitchDescription, event: Event) -> None: + """Create Axis entity.""" + if description.supported_fn(hub, event): + async_add_entities([AxisSwitch(hub, description, event)]) + + for description in descriptions: + hub.api.event.subscribe( + partial(create_entity, description), + topic_filter=description.event_topic, + operation_filter=EventOperation.INITIALIZED, + ) + + register_platform(ENTITY_DESCRIPTIONS) class AxisSwitch(AxisEventEntity, SwitchEntity): """Representation of a Axis switch.""" - def __init__(self, event: Event, hub: AxisHub) -> None: + def __init__( + self, hub: AxisHub, description: AxisSwitchDescription, event: Event + ) -> None: """Initialize the Axis switch.""" super().__init__(event, hub) - if event.id and hub.api.vapix.ports[event.id].name: - self._attr_name = hub.api.vapix.ports[event.id].name + self.entity_description = description + self._attr_name = description.name_fn(hub, event) or self._attr_name self._attr_is_on = event.is_tripped @callback From 6ee273a5484067814109a3f06814906add498325 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 20:48:37 +0100 Subject: [PATCH 1156/1691] Clean up unneeded ruff noqa directives (#113616) --- homeassistant/components/command_line/utils.py | 4 ++-- homeassistant/components/devolo_home_network/__init__.py | 4 +--- homeassistant/components/esphome/entry_data.py | 2 +- homeassistant/components/event/__init__.py | 2 +- homeassistant/components/google_assistant/__init__.py | 2 +- homeassistant/components/group/__init__.py | 2 +- homeassistant/components/group/entity.py | 8 +------- homeassistant/components/hassio/__init__.py | 4 ++-- homeassistant/components/hassio/websocket_api.py | 2 +- homeassistant/components/iaqualink/__init__.py | 4 +--- homeassistant/components/image/__init__.py | 2 +- homeassistant/components/influxdb/__init__.py | 2 +- homeassistant/components/isy994/services.py | 2 +- homeassistant/components/juicenet/device.py | 2 +- homeassistant/components/logbook/models.py | 2 +- homeassistant/components/logbook/processor.py | 2 +- homeassistant/components/matter/event.py | 2 +- homeassistant/components/modbus/__init__.py | 2 +- homeassistant/components/mqtt/climate.py | 4 ++-- homeassistant/components/recorder/core.py | 4 ++-- homeassistant/components/recorder/history/modern.py | 2 +- homeassistant/components/recorder/statistics.py | 2 +- homeassistant/components/systemmonitor/sensor.py | 2 +- homeassistant/components/vacuum/__init__.py | 7 +------ homeassistant/components/weather/__init__.py | 4 ++-- homeassistant/components/websocket_api/const.py | 2 +- homeassistant/components/wemo/wemo_device.py | 2 +- homeassistant/config.py | 2 +- homeassistant/core.py | 4 ++-- homeassistant/helpers/condition.py | 2 +- homeassistant/helpers/config_validation.py | 2 +- homeassistant/helpers/deprecation.py | 2 +- homeassistant/helpers/service.py | 2 +- homeassistant/loader.py | 4 ++-- homeassistant/scripts/check_config.py | 2 +- homeassistant/util/yaml/loader.py | 2 +- pyproject.toml | 1 + script/hassfest/icons.py | 2 +- tests/components/august/mocks.py | 2 +- tests/components/config/test_automation.py | 2 +- tests/components/config/test_script.py | 2 +- tests/components/tellduslive/test_config_flow.py | 1 - 42 files changed, 48 insertions(+), 63 deletions(-) diff --git a/homeassistant/components/command_line/utils.py b/homeassistant/components/command_line/utils.py index faebccae477..067efc08e97 100644 --- a/homeassistant/components/command_line/utils.py +++ b/homeassistant/components/command_line/utils.py @@ -19,7 +19,7 @@ async def async_call_shell_with_timeout( """ try: _LOGGER.debug("Running command: %s", command) - proc = await asyncio.create_subprocess_shell( # noqa: S602 # shell by design + proc = await asyncio.create_subprocess_shell( # shell by design command, close_fds=False, # required for posix_spawn ) @@ -43,7 +43,7 @@ async def async_call_shell_with_timeout( async def async_check_output_or_log(command: str, timeout: int) -> str | None: """Run a shell command with a timeout and return the output.""" try: - proc = await asyncio.create_subprocess_shell( # noqa: S602 # shell by design + proc = await asyncio.create_subprocess_shell( # shell by design command, close_fds=False, # required for posix_spawn stdout=asyncio.subprocess.PIPE, diff --git a/homeassistant/components/devolo_home_network/__init__.py b/homeassistant/components/devolo_home_network/__init__.py index 08d0e340b16..d96312be4e6 100644 --- a/homeassistant/components/devolo_home_network/__init__.py +++ b/homeassistant/components/devolo_home_network/__init__.py @@ -49,9 +49,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -async def async_setup_entry( # noqa: C901 - hass: HomeAssistant, entry: ConfigEntry -) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up devolo Home Network from a config entry.""" hass.data.setdefault(DOMAIN, {}) zeroconf_instance = await zeroconf.async_get_async_instance(hass) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index a7f3caf20cf..39bca5031fa 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -347,7 +347,7 @@ class RuntimeEntryData: and subscription_key not in stale_state and state_type is not CameraState and not ( - state_type is SensorState # noqa: E721 + state_type is SensorState and (platform_info := self.info.get(SensorInfo)) and (entity_info := platform_info.get(state.key)) and (cast(SensorInfo, entity_info)).force_update diff --git a/homeassistant/components/event/__init__.py b/homeassistant/components/event/__init__.py index 5a7e8bdda30..fc9add8c62e 100644 --- a/homeassistant/components/event/__init__.py +++ b/homeassistant/components/event/__init__.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Self, final from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.config_validation import ( # noqa: F401 +from homeassistant.helpers.config_validation import ( PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py index eb71361672a..273e46040b7 100644 --- a/homeassistant/components/google_assistant/__init__.py +++ b/homeassistant/components/google_assistant/__init__.py @@ -29,7 +29,7 @@ from .const import ( # noqa: F401 DEFAULT_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSED_DOMAINS, DOMAIN, - EVENT_QUERY_RECEIVED, # noqa: F401 + EVENT_QUERY_RECEIVED, SERVICE_REQUEST_SYNC, SOURCE_CLOUD, ) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 330eafa110c..8758bf19312 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -46,7 +46,7 @@ from .const import ( # noqa: F401 ATTR_ORDER, ATTR_REMOVE_ENTITIES, CONF_HIDE_MEMBERS, - DOMAIN, # noqa: F401 + DOMAIN, GROUP_ORDER, REG_KEY, ) diff --git a/homeassistant/components/group/entity.py b/homeassistant/components/group/entity.py index 3df068f5e23..24c10fd2e7b 100644 --- a/homeassistant/components/group/entity.py +++ b/homeassistant/components/group/entity.py @@ -25,13 +25,7 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) -from .const import ( - ATTR_AUTO, - ATTR_ORDER, - DOMAIN, # noqa: F401 - GROUP_ORDER, - REG_KEY, -) +from .const import ATTR_AUTO, ATTR_ORDER, DOMAIN, GROUP_ORDER, REG_KEY from .registry import GroupIntegrationRegistry ENTITY_ID_FORMAT = DOMAIN + ".{}" diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index b533f615db6..90b155aff15 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -83,14 +83,14 @@ from .const import ( from .data import ( HassioDataUpdateCoordinator, get_addons_changelogs, # noqa: F401 - get_addons_info, # noqa: F401 + get_addons_info, get_addons_stats, # noqa: F401 get_core_info, # noqa: F401 get_core_stats, # noqa: F401 get_host_info, # noqa: F401 get_info, # noqa: F401 get_issues_info, # noqa: F401 - get_os_info, # noqa: F401 + get_os_info, get_store, # noqa: F401 get_supervisor_info, # noqa: F401 get_supervisor_stats, # noqa: F401 diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index 22315161300..6313ebd5eaf 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -46,7 +46,7 @@ WS_NO_ADMIN_ENDPOINTS = re.compile( r"^(?:" r"|/ingress/(session|validate_session)" r"|/addons/[^/]+/info" - r")$" # noqa: ISC001 + r")$" ) # fmt: on diff --git a/homeassistant/components/iaqualink/__init__.py b/homeassistant/components/iaqualink/__init__.py index 6468347b001..33697dfb2cc 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -56,9 +56,7 @@ PLATFORMS = [ ] -async def async_setup_entry( # noqa: C901 - hass: HomeAssistant, entry: ConfigEntry -) -> bool: +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Aqualink from a config entry.""" username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index b73da5fbfd6..35869f31792 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -33,7 +33,7 @@ from homeassistant.helpers.event import ( from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType -from .const import DOMAIN, IMAGE_TIMEOUT # noqa: F401 +from .const import DOMAIN, IMAGE_TIMEOUT if TYPE_CHECKING: from functools import cached_property diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index daa49a4ba20..5c40dac1c9d 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -551,7 +551,7 @@ class InfluxThread(threading.Thread): if item is None: self.shutdown = True - elif type(item) is tuple: # noqa: E721 + elif type(item) is tuple: timestamp, event = item age = time.monotonic() - timestamp diff --git a/homeassistant/components/isy994/services.py b/homeassistant/components/isy994/services.py index 9159627c9eb..fedf7f8e902 100644 --- a/homeassistant/components/isy994/services.py +++ b/homeassistant/components/isy994/services.py @@ -131,7 +131,7 @@ def async_get_entities(hass: HomeAssistant) -> dict[str, Entity]: @callback -def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901 +def async_setup_services(hass: HomeAssistant) -> None: """Create and register services for the ISY integration.""" existing_services = hass.services.async_services_for_domain(DOMAIN) if existing_services and SERVICE_SEND_PROGRAM_COMMAND in existing_services: diff --git a/homeassistant/components/juicenet/device.py b/homeassistant/components/juicenet/device.py index 86e1c92e4da..daec88c2a94 100644 --- a/homeassistant/components/juicenet/device.py +++ b/homeassistant/components/juicenet/device.py @@ -10,7 +10,7 @@ class JuiceNetApi: self._devices = [] async def setup(self): - """JuiceNet device setup.""" # noqa: D403 + """JuiceNet device setup.""" self._devices = await self.api.get_devices() @property diff --git a/homeassistant/components/logbook/models.py b/homeassistant/components/logbook/models.py index 7cbafd43d53..1073c6b0d3a 100644 --- a/homeassistant/components/logbook/models.py +++ b/homeassistant/components/logbook/models.py @@ -51,7 +51,7 @@ class LazyEventPartialState: self._event_data_cache = event_data_cache # We need to explicitly check for the row is EventAsRow as the unhappy path # to fetch row.data for Row is very expensive - if type(row) is EventAsRow: # noqa: E721 + if type(row) is EventAsRow: # If its an EventAsRow we can avoid the whole # json decode process as we already have the data self.data = row.data diff --git a/homeassistant/components/logbook/processor.py b/homeassistant/components/logbook/processor.py index 29e135fbf62..2180a63b74f 100644 --- a/homeassistant/components/logbook/processor.py +++ b/homeassistant/components/logbook/processor.py @@ -428,7 +428,7 @@ class EventCache: def get(self, row: EventAsRow | Row) -> LazyEventPartialState: """Get the event from the row.""" - if type(row) is EventAsRow: # noqa: E721 - this is never subclassed + if type(row) is EventAsRow: # - this is never subclassed return LazyEventPartialState(row, self._event_data_cache) if event := self.event_cache.get(row): return event diff --git a/homeassistant/components/matter/event.py b/homeassistant/components/matter/event.py index 1b55b700085..ea48beef782 100644 --- a/homeassistant/components/matter/event.py +++ b/homeassistant/components/matter/event.py @@ -105,7 +105,7 @@ class MatterEventEntity(MatterEntity, EventEntity): """Call when Node attribute(s) changed.""" @callback - def _on_matter_node_event( # noqa: F821 + def _on_matter_node_event( self, event: EventType, data: MatterNodeEvent, diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index eb1f2ec0f60..94a84d3440d 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -51,7 +51,7 @@ from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from .const import ( # noqa: F401 +from .const import ( CALL_TYPE_COIL, CALL_TYPE_DISCRETE, CALL_TYPE_REGISTER_HOLDING, diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 567fe2540fd..cb1274c7665 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -456,7 +456,7 @@ class MqttTemperatureControlEntity(MqttEntity, ABC): except ValueError: _LOGGER.error("Could not parse %s from %s", template_name, payload) - def prepare_subscribe_topics( # noqa: C901 + def prepare_subscribe_topics( self, topics: dict[str, dict[str, Any]], ) -> None: @@ -714,7 +714,7 @@ class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): self._attr_supported_features = support - def _prepare_subscribe_topics(self) -> None: # noqa: C901 + def _prepare_subscribe_topics(self) -> None: """(Re)Subscribe to topics.""" topics: dict[str, dict[str, Any]] = {} diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 664fb59644f..fdb593ff27b 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -887,7 +887,7 @@ class Recorder(threading.Thread): for task_or_event in startup_task_or_events: # Event is never subclassed so we can # use a fast type check - if type(task_or_event) is Event: # noqa: E721 + if type(task_or_event) is Event: event_ = task_or_event if event_.event_type == EVENT_STATE_CHANGED: state_change_events.append(event_) @@ -918,7 +918,7 @@ class Recorder(threading.Thread): # is an Event so we can process it directly # and since its never subclassed, we can # use a fast type check - if type(task) is Event: # noqa: E721 + if type(task) is Event: self._process_one_event(task) return # If its not an event, commit everything diff --git a/homeassistant/components/recorder/history/modern.py b/homeassistant/components/recorder/history/modern.py index b35e1315cbe..124a6a43869 100644 --- a/homeassistant/components/recorder/history/modern.py +++ b/homeassistant/components/recorder/history/modern.py @@ -758,7 +758,7 @@ def _sorted_states_to_dict( _utc_from_timestamp = dt_util.utc_from_timestamp ent_results.extend( { - attr_state: (prev_state := state), # noqa: F841 + attr_state: (prev_state := state), attr_time: _utc_from_timestamp(row[last_updated_ts_idx]).isoformat(), } for row in group diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index ffe3136f841..f840fdbd7b6 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -2039,7 +2039,7 @@ def _fast_build_sum_list( ] -def _sorted_statistics_to_dict( # noqa: C901 +def _sorted_statistics_to_dict( hass: HomeAssistant, session: Session, stats: Sequence[Row[Any]], diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index fb04ac815b9..a8b0d6bd146 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -494,7 +494,7 @@ async def async_setup_platform( ) -async def async_setup_entry( # noqa: C901 +async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up System Montor sensors based on a config entry.""" diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 1be2c6c6796..61a31add655 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -36,12 +36,7 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from . import group as group_pre_import # noqa: F401 -from .const import ( # noqa: F401 - STATE_CLEANING, - STATE_DOCKED, - STATE_ERROR, - STATE_RETURNING, -) +from .const import STATE_CLEANING, STATE_DOCKED, STATE_ERROR, STATE_RETURNING if TYPE_CHECKING: from functools import cached_property diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 76dc0508545..59e01f03e67 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -62,7 +62,7 @@ from homeassistant.util.json import JsonValueType from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from . import group as group_pre_import # noqa: F401 -from .const import ( # noqa: F401 +from .const import ( ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_DEW_POINT, @@ -675,7 +675,7 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A @final @property - def state_attributes(self) -> dict[str, Any]: # noqa: C901 + def state_attributes(self) -> dict[str, Any]: """Return the state attributes, converted. Attributes are configured from native units to user-configured units. diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index c5de49e4033..25d3ff8dcb3 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Final from homeassistant.core import HomeAssistant if TYPE_CHECKING: - from .connection import ActiveConnection # noqa: F401 + from .connection import ActiveConnection WebSocketCommandHandler = Callable[ diff --git a/homeassistant/components/wemo/wemo_device.py b/homeassistant/components/wemo/wemo_device.py index fae4f87c239..2a4185a7640 100644 --- a/homeassistant/components/wemo/wemo_device.py +++ b/homeassistant/components/wemo/wemo_device.py @@ -36,7 +36,7 @@ from .models import async_wemo_data _LOGGER = logging.getLogger(__name__) # Literal values must match options.error keys from strings.json. -ErrorStringKey = Literal["long_press_requires_subscription"] # noqa: F821 +ErrorStringKey = Literal["long_press_requires_subscription"] # Literal values must match options.step.init.data keys from strings.json. OptionsFieldKey = Literal["enable_subscription", "enable_long_press"] diff --git a/homeassistant/config.py b/homeassistant/config.py index 0cc79a37ca5..a95da351a54 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1434,7 +1434,7 @@ def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: return domain_configs -async def async_process_component_config( # noqa: C901 +async def async_process_component_config( hass: HomeAssistant, config: ConfigType, integration: Integration, diff --git a/homeassistant/core.py b/homeassistant/core.py index a6262c56c81..c9760194a92 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1136,7 +1136,7 @@ class HomeAssistant: if ( not handle.cancelled() and (args := handle._args) # pylint: disable=protected-access - and type(job := args[0]) is HassJob # noqa: E721 + and type(job := args[0]) is HassJob and job.cancel_on_shutdown ): handle.cancel() @@ -1595,7 +1595,7 @@ class State: # State only creates and expects a ReadOnlyDict so # there is no need to check for subclassing with # isinstance here so we can use the faster type check. - if type(attributes) is not ReadOnlyDict: # noqa: E721 + if type(attributes) is not ReadOnlyDict: self.attributes = ReadOnlyDict(attributes or {}) else: self.attributes = attributes diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index ab0da9f4478..e906148efdb 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -362,7 +362,7 @@ def numeric_state( ).result() -def async_numeric_state( # noqa: C901 +def async_numeric_state( hass: HomeAssistant, entity: None | str | State, below: float | str | None = None, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 8f9e0d5353f..fbe98ccd387 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -625,7 +625,7 @@ def string(value: Any) -> str: # This is expected to be the most common case, so check it first. if ( type(value) is str # noqa: E721 - or type(value) is NodeStrClass # noqa: E721 + or type(value) is NodeStrClass or isinstance(value, str) ): return value diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 3266c69db20..6e70bbc7635 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -278,7 +278,7 @@ def check_if_deprecated_constant(name: str, module_globals: dict[str, Any]) -> A # specifies that __getattr__ should raise AttributeError if the attribute is not # found. # https://peps.python.org/pep-0562/#specification - raise AttributeError(msg) # noqa: TRY004 + raise AttributeError(msg) _print_deprecation_warning_internal( name, diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index b58e0831ccb..df8284c0b4c 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -656,7 +656,7 @@ async def async_get_all_descriptions( ints_or_excs = await async_get_integrations(hass, domains_with_missing_services) integrations: list[Integration] = [] for domain, int_or_exc in ints_or_excs.items(): - if type(int_or_exc) is Integration and int_or_exc.has_services: # noqa: E721 + if type(int_or_exc) is Integration and int_or_exc.has_services: integrations.append(int_or_exc) continue if TYPE_CHECKING: diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 8e44ca38d77..fe825e84bdf 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1299,7 +1299,7 @@ def async_get_loaded_integration(hass: HomeAssistant, domain: str) -> Integratio cache = cast(dict[str, Integration | asyncio.Future[None]], cache) int_or_fut = cache.get(domain, _UNDEF) # Integration is never subclassed, so we can check for type - if type(int_or_fut) is Integration: # noqa: E721 + if type(int_or_fut) is Integration: return int_or_fut raise IntegrationNotLoaded(domain) @@ -1326,7 +1326,7 @@ async def async_get_integrations( for domain in domains: int_or_fut = cache.get(domain, _UNDEF) # Integration is never subclassed, so we can check for type - if type(int_or_fut) is Integration: # noqa: E721 + if type(int_or_fut) is Integration: results[domain] = int_or_fut elif int_or_fut is not _UNDEF: in_progress[domain] = cast(asyncio.Future[None], int_or_fut) diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index f688a96b11d..d38e24a24da 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -176,7 +176,7 @@ def check(config_dir, secrets=False): "secrets": OrderedDict(), # secret cache and secrets loaded "except": OrderedDict(), # critical exceptions raised (with config) "warn": OrderedDict(), # non critical exceptions raised (with config) - #'components' is a HomeAssistantConfig # noqa: E265 + #'components' is a HomeAssistantConfig "secret_cache": {}, } diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 2a2883ed544..0713fd83ee8 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -319,7 +319,7 @@ def _add_reference( # type: ignore[no-untyped-def] obj = NodeListClass(obj) if isinstance(obj, str): obj = NodeStrClass(obj) - try: # noqa: SIM105 suppress is much slower + try: # suppress is much slower setattr(obj, "__config_file__", loader.get_name) setattr(obj, "__line__", node.start_mark.line + 1) except AttributeError: diff --git a/pyproject.toml b/pyproject.toml index 7d0ff3f3e35..7bc7931f18b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -612,6 +612,7 @@ select = [ "Q000", # Double quotes found but single quotes preferred "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task + # "RUF100", # Unused `noqa` directive; temporarily every now and then to clean them up "S102", # Use of exec detected "S103", # bad-file-permissions "S108", # hardcoded-temp-file diff --git a/script/hassfest/icons.py b/script/hassfest/icons.py index 8b0fd6d3b91..b7ba2fbb402 100644 --- a/script/hassfest/icons.py +++ b/script/hassfest/icons.py @@ -112,7 +112,7 @@ def icon_schema(integration_type: str, no_entity_platform: bool) -> vol.Schema: return schema -def validate_icon_file(config: Config, integration: Integration) -> None: # noqa: C901 +def validate_icon_file(config: Config, integration: Integration) -> None: """Validate icon file for integration.""" icons_file = integration.path / "icons.json" if not icons_file.is_file(): diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index df52e900dbd..b819565d4bf 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -98,7 +98,7 @@ async def _create_august_with_devices( return entry -async def _create_august_api_with_devices( # noqa: C901 +async def _create_august_api_with_devices( hass, devices, api_call_side_effects=None, diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 616aae58bdd..6d28a66fdc3 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -27,7 +27,7 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None: async def setup_automation( hass, automation_config, - stub_blueprint_populate, # noqa: F811 + stub_blueprint_populate, ): """Set up automation integration.""" assert await async_setup_component( diff --git a/tests/components/config/test_script.py b/tests/components/config/test_script.py index 745ee708875..cca4f07f8e0 100644 --- a/tests/components/config/test_script.py +++ b/tests/components/config/test_script.py @@ -24,7 +24,7 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None: @pytest.fixture(autouse=True) -async def setup_script(hass, script_config, stub_blueprint_populate): # noqa: F811 +async def setup_script(hass, script_config, stub_blueprint_populate): """Set up script integration.""" assert await async_setup_component(hass, "script", {"script": script_config}) diff --git a/tests/components/tellduslive/test_config_flow.py b/tests/components/tellduslive/test_config_flow.py index 17d29b383fb..cf107477897 100644 --- a/tests/components/tellduslive/test_config_flow.py +++ b/tests/components/tellduslive/test_config_flow.py @@ -1,4 +1,3 @@ -# flake8: noqa pylint: skip-file """Tests for the TelldusLive config flow.""" from unittest.mock import Mock, patch From 4be9d3e7f607d74171ce226aedeeaf3dfb8958bc Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sat, 16 Mar 2024 20:49:25 +0100 Subject: [PATCH 1157/1691] Remove deprecated `hass.components` from mysensors (#113611) --- homeassistant/components/mysensors/gateway.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 3bdd884079e..177dbbe4dc9 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -17,6 +17,8 @@ from homeassistant.components.mqtt import ( DOMAIN as MQTT_DOMAIN, ReceiveMessage as MQTTReceiveMessage, ReceivePayloadType, + async_publish, + async_subscribe, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE, EVENT_HOMEASSISTANT_STOP @@ -171,13 +173,10 @@ async def _get_gateway( # Naive check that doesn't consider config entry state. if MQTT_DOMAIN not in hass.config.components: return None - mqtt = hass.components.mqtt def pub_callback(topic: str, payload: str, qos: int, retain: bool) -> None: """Call MQTT publish function.""" - hass.async_create_task( - mqtt.async_publish(hass, topic, payload, qos, retain) - ) + hass.async_create_task(async_publish(hass, topic, payload, qos, retain)) def sub_callback( topic: str, sub_cb: Callable[[str, ReceivePayloadType, int], None], qos: int @@ -189,7 +188,7 @@ async def _get_gateway( """Call callback.""" sub_cb(msg.topic, msg.payload, msg.qos) - hass.async_create_task(mqtt.async_subscribe(topic, internal_callback, qos)) + hass.async_create_task(async_subscribe(hass, topic, internal_callback, qos)) gateway = mysensors.AsyncMQTTGateway( pub_callback, From 5dccd8204ccdfeabe368619f64ab18e3310a9888 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 16 Mar 2024 21:16:18 +0100 Subject: [PATCH 1158/1691] Freeze time on profile test (#113618) --- tests/components/profiler/test_init.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 8bb17e79b3f..2574f71a57b 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -7,6 +7,7 @@ import os from pathlib import Path from unittest.mock import patch +from freezegun.api import FrozenDateTimeFactory from lru import LRU import pytest @@ -99,7 +100,9 @@ async def test_memory_usage(hass: HomeAssistant, tmp_path: Path) -> None: async def test_object_growth_logging( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, ) -> None: """Test we can setup and the service and we can dump objects to the log.""" From 4174d88ad757ffc7aa360f30f7237b7c6922052d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 10:19:07 -1000 Subject: [PATCH 1159/1691] Add a guard to handle unhashable platforms in config (#113607) Someone might set the platform to [fitbit] instead of fitbit I have not seen anyone do this, but its good to guard against it --- homeassistant/config.py | 4 ++-- tests/test_config.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index a95da351a54..f536b2b2913 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -3,7 +3,7 @@ from __future__ import annotations from collections import OrderedDict -from collections.abc import Callable, Iterable, Sequence +from collections.abc import Callable, Hashable, Iterable, Sequence from contextlib import suppress from dataclasses import dataclass from enum import StrEnum @@ -1415,7 +1415,7 @@ def extract_platform_integrations( platform = item.get(CONF_PLATFORM) except AttributeError: continue - if platform: + if platform and isinstance(platform, Hashable): platform_integrations.setdefault(domain, set()).add(platform) return platform_integrations diff --git a/tests/test_config.py b/tests/test_config.py index af4b653e4f6..9d1cb9e3ce7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2348,6 +2348,7 @@ def test_extract_platform_integrations() -> None: [ (b"zone", {"platform": "not str"}), ("zone", {"platform": "hello"}), + ("switch", {"platform": ["un", "hash", "able"]}), ("zonex", []), ("zoney", ""), ("notzone", {"platform": "nothello"}), @@ -2363,6 +2364,7 @@ def test_extract_platform_integrations() -> None: assert config_util.extract_platform_integrations(config, {"zone"}) == { "zone": {"hello", "hello 2"} } + assert config_util.extract_platform_integrations(config, {"switch"}) == {} assert config_util.extract_platform_integrations(config, {"zonex"}) == {} assert config_util.extract_platform_integrations(config, {"zoney"}) == {} assert config_util.extract_platform_integrations( From bb12d2e865a5b55a38ed89ef7fbcb4c1327ced6f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 10:20:06 -1000 Subject: [PATCH 1160/1691] Avoid creating tasks in homeassistant_alerts when the debouncer will not fire (#113580) --- .../homeassistant_alerts/__init__.py | 9 +- .../homeassistant_alerts/test_init.py | 104 +++++++++--------- 2 files changed, 58 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/homeassistant_alerts/__init__.py b/homeassistant/components/homeassistant_alerts/__init__.py index 6e6aaa0838e..338d8679b19 100644 --- a/homeassistant/components/homeassistant_alerts/__init__.py +++ b/homeassistant/components/homeassistant_alerts/__init__.py @@ -98,11 +98,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: function=coordinator.async_refresh, ) - async def _component_loaded(_: Event) -> None: - await refresh_debouncer.async_call() + @callback + def _component_loaded(_: Event) -> None: + refresh_debouncer.async_schedule_call() await coordinator.async_refresh() - hass.bus.async_listen(EVENT_COMPONENT_LOADED, _component_loaded) + hass.bus.async_listen( + EVENT_COMPONENT_LOADED, _component_loaded, run_immediately=True + ) async_at_start(hass, initial_refresh) diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index d21d5135db5..beaef50caf7 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -323,33 +323,33 @@ async def test_alerts_refreshed_on_component_load( ): assert await async_setup_component(hass, DOMAIN, {}) - client = await hass_ws_client(hass) + client = await hass_ws_client(hass) - await client.send_json({"id": 1, "type": "repairs/list_issues"}) - msg = await client.receive_json() - assert msg["success"] - assert msg["result"] == { - "issues": [ - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "homeassistant_alerts", - "ignored": False, - "is_fixable": False, - "issue_id": f"{alert}.markdown_{integration}", - "issue_domain": integration, - "learn_more_url": None, - "severity": "warning", - "translation_key": "alert", - "translation_placeholders": { - "title": f"Title for {alert}", - "description": f"Content for {alert}", - }, - } - for alert, integration in initial_alerts - ] - } + await client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] == { + "issues": [ + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": "homeassistant_alerts", + "ignored": False, + "is_fixable": False, + "issue_id": f"{alert}.markdown_{integration}", + "issue_domain": integration, + "learn_more_url": None, + "severity": "warning", + "translation_key": "alert", + "translation_placeholders": { + "title": f"Title for {alert}", + "description": f"Content for {alert}", + }, + } + for alert, integration in initial_alerts + ] + } with patch( "homeassistant.components.homeassistant_alerts.__version__", @@ -368,33 +368,33 @@ async def test_alerts_refreshed_on_component_load( freezer.tick(COMPONENT_LOADED_COOLDOWN + 1) await hass.async_block_till_done() - client = await hass_ws_client(hass) + client = await hass_ws_client(hass) - await client.send_json({"id": 2, "type": "repairs/list_issues"}) - msg = await client.receive_json() - assert msg["success"] - assert msg["result"] == { - "issues": [ - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "homeassistant_alerts", - "ignored": False, - "is_fixable": False, - "issue_id": f"{alert}.markdown_{integration}", - "issue_domain": integration, - "learn_more_url": None, - "severity": "warning", - "translation_key": "alert", - "translation_placeholders": { - "title": f"Title for {alert}", - "description": f"Content for {alert}", - }, - } - for alert, integration in late_alerts - ] - } + await client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] == { + "issues": [ + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": "homeassistant_alerts", + "ignored": False, + "is_fixable": False, + "issue_id": f"{alert}.markdown_{integration}", + "issue_domain": integration, + "learn_more_url": None, + "severity": "warning", + "translation_key": "alert", + "translation_placeholders": { + "title": f"Title for {alert}", + "description": f"Content for {alert}", + }, + } + for alert, integration in late_alerts + ] + } @pytest.mark.parametrize( From 7d58be1a6ac68cc71de540829132aa2a1e2d2209 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 10:57:10 -1000 Subject: [PATCH 1161/1691] Gather loading platforms in async_process_component_config (#113573) --- homeassistant/config.py | 141 +++++++++++++++++++++++++++++----------- tests/test_config.py | 79 ++++++++++++++++++++++ 2 files changed, 181 insertions(+), 39 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index f536b2b2913..8c2935c8b4c 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -2,12 +2,13 @@ from __future__ import annotations +import asyncio from collections import OrderedDict from collections.abc import Callable, Hashable, Iterable, Sequence from contextlib import suppress from dataclasses import dataclass from enum import StrEnum -from functools import reduce +from functools import partial, reduce import logging import operator import os @@ -65,6 +66,7 @@ from .helpers.entity_values import EntityValues from .helpers.typing import ConfigType from .loader import ComponentProtocol, Integration, IntegrationNotFound from .requirements import RequirementsNotFound, async_get_integration_with_requirements +from .util.async_ import create_eager_task from .util.package import is_docker_env from .util.unit_system import get_unit_system, validate_unit_system from .util.yaml import SECRET_YAML, Secrets, YamlTypeError, load_yaml_dict @@ -1434,6 +1436,67 @@ def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]: return domain_configs +@dataclass(slots=True) +class _PlatformIntegration: + """Class to hold platform integration information.""" + + path: str # integration.platform; ex: filter.sensor + name: str # integration; ex: filter + integration: Integration # + config: ConfigType # un-validated config + validated_config: ConfigType # component validated config + + +async def _async_load_and_validate_platform_integration( + domain: str, + integration_docs: str | None, + config_exceptions: list[ConfigExceptionInfo], + p_integration: _PlatformIntegration, +) -> ConfigType | None: + """Load a platform integration and validate its config.""" + try: + platform = await p_integration.integration.async_get_platform(domain) + except LOAD_EXCEPTIONS as exc: + exc_info = ConfigExceptionInfo( + exc, + ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC, + p_integration.path, + p_integration.config, + integration_docs, + ) + config_exceptions.append(exc_info) + return None + + # If the platform does not have a config schema + # the top level component validated schema will be used + if not hasattr(platform, "PLATFORM_SCHEMA"): + return p_integration.validated_config + + # Validate platform specific schema + try: + return platform.PLATFORM_SCHEMA(p_integration.config) # type: ignore[no-any-return] + except vol.Invalid as exc: + exc_info = ConfigExceptionInfo( + exc, + ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR, + p_integration.path, + p_integration.config, + p_integration.integration.documentation, + ) + config_exceptions.append(exc_info) + except Exception as exc: # pylint: disable=broad-except + exc_info = ConfigExceptionInfo( + exc, + ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR, + p_integration.name, + p_integration.config, + p_integration.integration.documentation, + ) + config_exceptions.append(exc_info) + + return None + + async def async_process_component_config( hass: HomeAssistant, config: ConfigType, @@ -1548,6 +1611,7 @@ async def async_process_component_config( if component_platform_schema is None: return IntegrationConfigInfo(config, []) + platform_integrations_to_load: list[_PlatformIntegration] = [] platforms: list[ConfigType] = [] for p_name, p_config in config_per_platform(config, domain): # Validate component specific platform schema @@ -1595,45 +1659,44 @@ async def async_process_component_config( config_exceptions.append(exc_info) continue - try: - platform = await p_integration.async_get_platform(domain) - except LOAD_EXCEPTIONS as exc: - exc_info = ConfigExceptionInfo( - exc, - ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC, - platform_path, - p_config, - integration_docs, + platform_integration = _PlatformIntegration( + platform_path, p_name, p_integration, p_config, p_validated + ) + platform_integrations_to_load.append(platform_integration) + + # + # Since bootstrap will order base platform (ie sensor) integrations + # first, we eagerly gather importing the platforms that need to be + # validated for the base platform since everything that uses the + # base platform has to wait for it to finish. + # + # For example if `hue` where to load first and than called + # `async_forward_entry_setup` for the `sensor` platform it would have to + # wait for the sensor platform to finish loading before it could continue. + # Since the base `sensor` platform must also import all of its platform + # integrations to do validation before it can finish setup, its important + # that the platform integrations are imported first so we do not waste + # time importing `hue` first when we could have been importing the platforms + # that the base `sensor` platform need to load to do validation and allow + # all integrations that need the base `sensor` platform to proceed with setup. + # + if platform_integrations_to_load: + async_load_and_validate = partial( + _async_load_and_validate_platform_integration, + domain, + integration_docs, + config_exceptions, + ) + platforms.extend( + validated_config + for validated_config in await asyncio.gather( + *( + create_eager_task(async_load_and_validate(p_integration)) + for p_integration in platform_integrations_to_load + ) ) - config_exceptions.append(exc_info) - continue - - # Validate platform specific schema - if hasattr(platform, "PLATFORM_SCHEMA"): - try: - p_validated = platform.PLATFORM_SCHEMA(p_config) - except vol.Invalid as exc: - exc_info = ConfigExceptionInfo( - exc, - ConfigErrorTranslationKey.PLATFORM_CONFIG_VALIDATION_ERR, - platform_path, - p_config, - p_integration.documentation, - ) - config_exceptions.append(exc_info) - continue - except Exception as exc: # pylint: disable=broad-except - exc_info = ConfigExceptionInfo( - exc, - ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR, - p_name, - p_config, - p_integration.documentation, - ) - config_exceptions.append(exc_info) - continue - - platforms.append(p_validated) + if validated_config is not None + ) # Create a copy of the configuration with all config for current # component removed and add validated config back in. diff --git a/tests/test_config.py b/tests/test_config.py index 9d1cb9e3ce7..13bb1519876 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,5 +1,6 @@ """Test config utils.""" +import asyncio from collections import OrderedDict import contextlib import copy @@ -15,6 +16,7 @@ import voluptuous as vol from voluptuous import Invalid, MultipleInvalid import yaml +from homeassistant import config, loader import homeassistant.config as config_util from homeassistant.const import ( ATTR_ASSUMED_STATE, @@ -2372,3 +2374,80 @@ def test_extract_platform_integrations() -> None: ) == {"zone": {"hello 2", "hello"}, "notzone": {"nothello"}} assert config_util.extract_platform_integrations(config, {"zoneq"}) == {} assert config_util.extract_platform_integrations(config, {"zoneempty"}) == {} + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_loading_platforms_gathers(hass: HomeAssistant) -> None: + """Test loading platform integrations gathers.""" + + mock_integration( + hass, + MockModule( + domain="platform_int", + ), + ) + mock_integration( + hass, + MockModule( + domain="platform_int2", + ), + ) + + # Its important that we do not mock the platforms with mock_platform + # as the loader is smart enough to know they are already loaded and + # will not create an executor job to load them. We are testing in + # what order the executor jobs happen here as we want to make + # sure the platform integrations are at the front of the line + light_integration = await loader.async_get_integration(hass, "light") + sensor_integration = await loader.async_get_integration(hass, "sensor") + + order: list[tuple[str, str]] = [] + + def _load_platform(self, platform: str) -> MockModule: + order.append((self.domain, platform)) + return MockModule() + + # We need to patch what runs in the executor so we are counting + # the order that jobs are scheduled in th executor + with patch( + "homeassistant.loader.Integration._load_platform", + _load_platform, + ): + light_task = hass.async_create_task( + config.async_process_component_config( + hass, + { + "light": [ + {"platform": "platform_int"}, + {"platform": "platform_int2"}, + ] + }, + light_integration, + ), + eager_start=True, + ) + sensor_task = hass.async_create_task( + config.async_process_component_config( + hass, + { + "sensor": [ + {"platform": "platform_int"}, + {"platform": "platform_int2"}, + ] + }, + sensor_integration, + ), + eager_start=True, + ) + + await asyncio.gather(light_task, sensor_task) + + # Should be called in order so that + # all the light platforms are imported + # before the sensor platforms + assert order == [ + ("platform_int", "light"), + ("platform_int2", "light"), + ("platform_int", "sensor"), + ("platform_int2", "sensor"), + ] From cbe2a5883bd0a6b57a07f826c9afd7031231487d Mon Sep 17 00:00:00 2001 From: Antoni Czaplicki <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Sat, 16 Mar 2024 21:59:24 +0100 Subject: [PATCH 1162/1691] Fix vulcan calendar offset (#113604) * Fix offset on vulcan calendar * Combine date, time and zone into one command in vulcan calendar --- homeassistant/components/vulcan/calendar.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/vulcan/calendar.py b/homeassistant/components/vulcan/calendar.py index a0ccaefdb15..e068a772345 100644 --- a/homeassistant/components/vulcan/calendar.py +++ b/homeassistant/components/vulcan/calendar.py @@ -111,11 +111,11 @@ class VulcanCalendarEntity(CalendarEntity): event_list = [] for item in events: event = CalendarEvent( - start=datetime.combine(item["date"], item["time"].from_).astimezone( - ZoneInfo("Europe/Warsaw") + start=datetime.combine( + item["date"], item["time"].from_, ZoneInfo("Europe/Warsaw") ), - end=datetime.combine(item["date"], item["time"].to).astimezone( - ZoneInfo("Europe/Warsaw") + end=datetime.combine( + item["date"], item["time"].to, ZoneInfo("Europe/Warsaw") ), summary=item["lesson"], location=item["room"], @@ -165,10 +165,10 @@ class VulcanCalendarEntity(CalendarEntity): ) self._event = CalendarEvent( start=datetime.combine( - new_event["date"], new_event["time"].from_ - ).astimezone(ZoneInfo("Europe/Warsaw")), - end=datetime.combine(new_event["date"], new_event["time"].to).astimezone( - ZoneInfo("Europe/Warsaw") + new_event["date"], new_event["time"].from_, ZoneInfo("Europe/Warsaw") + ), + end=datetime.combine( + new_event["date"], new_event["time"].to, ZoneInfo("Europe/Warsaw") ), summary=new_event["lesson"], location=new_event["room"], From 0b9c9aff62b2945ea3bc3420efd6d89662fad882 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:04:58 +0100 Subject: [PATCH 1163/1691] Add ruff rules PIE790, PIE794, PIE807, PIE810 (#113617) --- homeassistant/components/alexa/intent.py | 1 - homeassistant/components/buienradar/sensor.py | 10 ++-------- homeassistant/components/cast/helpers.py | 2 +- homeassistant/components/cast/media_player.py | 6 +----- homeassistant/components/discovergy/sensor.py | 2 +- homeassistant/components/elkm1/config_flow.py | 2 +- homeassistant/components/google_domains/__init__.py | 2 +- homeassistant/components/iaqualink/switch.py | 2 +- homeassistant/components/isy994/config_flow.py | 2 +- homeassistant/components/kodi/notify.py | 2 +- homeassistant/components/no_ip/__init__.py | 2 +- homeassistant/components/upnp/device.py | 2 +- homeassistant/components/uptimerobot/__init__.py | 2 +- homeassistant/components/uptimerobot/config_flow.py | 2 +- homeassistant/components/webhook/__init__.py | 1 - homeassistant/components/xiaomi_aqara/config_flow.py | 4 +--- homeassistant/components/xiaomi_miio/__init__.py | 6 ++---- homeassistant/components/xiaomi_miio/sensor.py | 6 ++---- pyproject.toml | 5 +++++ tests/components/homekit/test_homekit.py | 2 +- tests/components/recorder/db_schema_16.py | 6 +++--- .../recorder/db_schema_23_with_newer_columns.py | 2 +- tests/components/twitch/__init__.py | 2 -- tests/test_core.py | 1 - .../config_flow.py | 2 -- 25 files changed, 29 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index 03c6ac640e3..8d266e4a634 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -50,7 +50,6 @@ async def async_setup_intents(hass: HomeAssistant) -> None: Right now this module does not expose any, but the intent component breaks without it. """ - pass # pylint: disable=unnecessary-pass class UnknownRequest(HomeAssistantError): diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index d0847c111fb..fb15aa49001 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -774,13 +774,7 @@ class BrSensor(SensorEntity): self._measured = data.get(MEASURED) sensor_type = self.entity_description.key - if ( - sensor_type.endswith("_1d") - or sensor_type.endswith("_2d") - or sensor_type.endswith("_3d") - or sensor_type.endswith("_4d") - or sensor_type.endswith("_5d") - ): + if sensor_type.endswith(("_1d", "_2d", "_3d", "_4d", "_5d")): # update forecasting sensors: fcday = 0 if sensor_type.endswith("_2d"): @@ -793,7 +787,7 @@ class BrSensor(SensorEntity): fcday = 4 # update weather symbol & status text - if sensor_type.startswith(SYMBOL) or sensor_type.startswith(CONDITION): + if sensor_type.startswith((SYMBOL, CONDITION)): try: condition = data.get(FORECAST)[fcday].get(CONDITION) except IndexError: diff --git a/homeassistant/components/cast/helpers.py b/homeassistant/components/cast/helpers.py index eff6bf18e8b..2d4e1a9dbfa 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -360,7 +360,7 @@ async def parse_pls(hass, url): async def parse_playlist(hass, url): """Parse an m3u or pls playlist.""" - if url.endswith(".m3u") or url.endswith(".m3u8"): + if url.endswith((".m3u", ".m3u8")): playlist = await parse_m3u(hass, url) else: playlist = await parse_pls(hass, url) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 941b2e0675b..037e154eb96 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -685,11 +685,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): "hlsVideoSegmentFormat": "fmp4", }, } - elif ( - media_id.endswith(".m3u") - or media_id.endswith(".m3u8") - or media_id.endswith(".pls") - ): + elif media_id.endswith((".m3u", ".m3u8", ".pls")): try: playlist = await parse_playlist(self.hass, media_id) _LOGGER.debug( diff --git a/homeassistant/components/discovergy/sensor.py b/homeassistant/components/discovergy/sensor.py index cb9f8c3bf1d..0a820917821 100644 --- a/homeassistant/components/discovergy/sensor.py +++ b/homeassistant/components/discovergy/sensor.py @@ -43,7 +43,7 @@ class DiscovergySensorEntityDescription(SensorEntityDescription): value_fn: Callable[[Reading, str, int], datetime | float | None] = field( default=_get_and_scale ) - alternative_keys: list[str] = field(default_factory=lambda: []) + alternative_keys: list[str] = field(default_factory=list) scale: int = field(default_factory=lambda: 1000) diff --git a/homeassistant/components/elkm1/config_flow.py b/homeassistant/components/elkm1/config_flow.py index 6e4874f7366..5991c502ef6 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -78,7 +78,7 @@ async def validate_input(data: dict[str, str], mac: str | None) -> dict[str, str prefix = data[CONF_PREFIX] url = _make_url_from_data(data) - requires_password = url.startswith("elks://") or url.startswith("elksv1_2") + requires_password = url.startswith(("elks://", "elksv1_2")) if requires_password and (not userid or not password): raise InvalidAuth diff --git a/homeassistant/components/google_domains/__init__.py b/homeassistant/components/google_domains/__init__.py index d6537c5e135..a4dcef62964 100644 --- a/homeassistant/components/google_domains/__init__.py +++ b/homeassistant/components/google_domains/__init__.py @@ -73,7 +73,7 @@ async def _update_google_domains(hass, session, domain, user, password, timeout) resp = await session.get(url, params=params) body = await resp.text() - if body.startswith("good") or body.startswith("nochg"): + if body.startswith(("good", "nochg")): return True _LOGGER.warning("Updating Google Domains failed: %s => %s", domain, body) diff --git a/homeassistant/components/iaqualink/switch.py b/homeassistant/components/iaqualink/switch.py index 05eed0725e3..e681879855b 100644 --- a/homeassistant/components/iaqualink/switch.py +++ b/homeassistant/components/iaqualink/switch.py @@ -40,7 +40,7 @@ class HassAqualinkSwitch(AqualinkEntity, SwitchEntity): self._attr_icon = "mdi:robot-vacuum" elif name == "Waterfall" or name.endswith("Dscnt"): self._attr_icon = "mdi:fountain" - elif name.endswith("Pump") or name.endswith("Blower"): + elif name.endswith(("Pump", "Blower")): self._attr_icon = "mdi:fan" if name.endswith("Heater"): self._attr_icon = "mdi:radiator" diff --git a/homeassistant/components/isy994/config_flow.py b/homeassistant/components/isy994/config_flow.py index c66e8af4022..639e591746d 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -213,7 +213,7 @@ class Isy994ConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Handle a discovered ISY/IoX device via dhcp.""" friendly_name = discovery_info.hostname - if friendly_name.startswith("polisy") or friendly_name.startswith("eisy"): + if friendly_name.startswith(("polisy", "eisy")): url = f"http://{discovery_info.ip}:8080" else: url = f"http://{discovery_info.ip}" diff --git a/homeassistant/components/kodi/notify.py b/homeassistant/components/kodi/notify.py index 7d65321a807..05b5ff56be4 100644 --- a/homeassistant/components/kodi/notify.py +++ b/homeassistant/components/kodi/notify.py @@ -60,7 +60,7 @@ async def async_get_service( port: int = config[CONF_PORT] encryption = config.get(CONF_PROXY_SSL) - if host.startswith("http://") or host.startswith("https://"): + if host.startswith(("http://", "https://")): host = host[host.index("://") + 3 :] _LOGGER.warning( "Kodi host name should no longer contain http:// See updated " diff --git a/homeassistant/components/no_ip/__init__.py b/homeassistant/components/no_ip/__init__.py index d9084719cbd..9680464c9fa 100644 --- a/homeassistant/components/no_ip/__init__.py +++ b/homeassistant/components/no_ip/__init__.py @@ -104,7 +104,7 @@ async def _update_no_ip( resp = await session.get(url, params=params, headers=headers) body = await resp.text() - if body.startswith("good") or body.startswith("nochg"): + if body.startswith(("good", "nochg")): _LOGGER.debug("Updating NO-IP success: %s", domain) return True diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 9e8aaea198b..04d8b3414bd 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -38,7 +38,7 @@ def get_preferred_location(locations: set[str]) -> str: """Get the preferred location (an IPv4 location) from a set of locations.""" # Prefer IPv4 over IPv6. for location in locations: - if location.startswith("http://[") or location.startswith("https://["): + if location.startswith(("http://[", "https://[")): continue return location diff --git a/homeassistant/components/uptimerobot/__init__.py b/homeassistant/components/uptimerobot/__init__.py index 75fa5eeabcc..afff0c8fe03 100644 --- a/homeassistant/components/uptimerobot/__init__.py +++ b/homeassistant/components/uptimerobot/__init__.py @@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up UptimeRobot from a config entry.""" hass.data.setdefault(DOMAIN, {}) key: str = entry.data[CONF_API_KEY] - if key.startswith("ur") or key.startswith("m"): + if key.startswith(("ur", "m")): raise ConfigEntryAuthFailed( "Wrong API key type detected, use the 'main' API key" ) diff --git a/homeassistant/components/uptimerobot/config_flow.py b/homeassistant/components/uptimerobot/config_flow.py index 0afee9cfacb..feb747c6b9e 100644 --- a/homeassistant/components/uptimerobot/config_flow.py +++ b/homeassistant/components/uptimerobot/config_flow.py @@ -36,7 +36,7 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN): errors: dict[str, str] = {} response: UptimeRobotApiResponse | UptimeRobotApiError | None = None key: str = data[CONF_API_KEY] - if key.startswith("ur") or key.startswith("m"): + if key.startswith(("ur", "m")): LOGGER.error("Wrong API key type detected, use the 'main' API key") errors["base"] = "not_main_key" return errors, None diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 081147cc7f0..f2d1416e2c9 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -210,7 +210,6 @@ class WebhookView(HomeAssistantView): head = _handle post = _handle put = _handle - get = _handle @websocket_api.websocket_command( diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index ca1a5851f7e..7021b27e187 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -158,9 +158,7 @@ class XiaomiAqaraFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="not_xiaomi_aqara") # Check if the discovered device is an xiaomi aqara gateway. - if not ( - name.startswith(ZEROCONF_GATEWAY) or name.startswith(ZEROCONF_ACPARTNER) - ): + if not (name.startswith((ZEROCONF_GATEWAY, ZEROCONF_ACPARTNER))): _LOGGER.debug( ( "Xiaomi device '%s' discovered with host %s, not identified as" diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index aa9bbe5dcc6..f21698f33c3 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -340,10 +340,8 @@ async def async_create_miio_device_and_coordinator( device = AirFreshA1(host, token, lazy_discover=lazy_discover) elif model == MODEL_AIRFRESH_T2017: device = AirFreshT2017(host, token, lazy_discover=lazy_discover) - elif ( - model in MODELS_VACUUM - or model.startswith(ROBOROCK_GENERIC) - or model.startswith(ROCKROBO_GENERIC) + elif model in MODELS_VACUUM or model.startswith( + (ROBOROCK_GENERIC, ROCKROBO_GENERIC) ): # TODO: add lazy_discover as argument when python-miio add support # pylint: disable=fixme device = RoborockVacuum(host, token) diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index a7b3ff176bd..9f70ef6bb17 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -831,10 +831,8 @@ async def async_setup_entry( sensors = PURIFIER_MIIO_SENSORS elif model in MODELS_PURIFIER_MIOT: sensors = PURIFIER_MIOT_SENSORS - elif ( - model in MODELS_VACUUM - or model.startswith(ROBOROCK_GENERIC) - or model.startswith(ROCKROBO_GENERIC) + elif model in MODELS_VACUUM or model.startswith( + (ROBOROCK_GENERIC, ROCKROBO_GENERIC) ): return _setup_vacuum_sensors(hass, config_entry, async_add_entities) diff --git a/pyproject.toml b/pyproject.toml index 7bc7931f18b..04aca768267 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -276,6 +276,7 @@ disable = [ "unidiomatic-typecheck", # E721 "unnecessary-direct-lambda-call", # PLC3002 "unnecessary-lambda-assignment", # PLC3001 + "unnecessary-pass", # PIE790 "unneeded-not", # SIM208 "useless-import-alias", # PLC0414 "wrong-import-order", # I001 @@ -604,6 +605,10 @@ select = [ "N815", # Variable {name} in class scope should not be mixedCase "PERF", # Perflint "PGH004", # Use specific rule codes when using noqa + "PIE790", # Unnecessary pass statement + "PIE794", # Class field is defined multiple times + "PIE807", # Prefer list/dict over useless lambda + "PIE810", # Call startswith/endswith once with a tuple "PLC0414", # Useless import alias. Import alias does not rename original package. "PLC", # pylint "PLE", # pylint diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index c1dcd28ce68..a6748749e1a 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1297,7 +1297,7 @@ async def test_homekit_reset_accessories_not_bridged( acc_mock = MagicMock() acc_mock.entity_id = entity_id acc_mock.stop = AsyncMock() - acc_mock.to_HAP = lambda: {} + acc_mock.to_HAP = dict aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id) homekit.bridge.accessories = {aid: acc_mock} diff --git a/tests/components/recorder/db_schema_16.py b/tests/components/recorder/db_schema_16.py index 8c491b82c39..4d48400e370 100644 --- a/tests/components/recorder/db_schema_16.py +++ b/tests/components/recorder/db_schema_16.py @@ -84,7 +84,7 @@ class Events(Base): # type: ignore context_user_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) context_parent_id = Column(String(MAX_LENGTH_EVENT_CONTEXT_ID), index=True) - __table_args__ = ( + __table_args__ = ( # noqa: PIE794 # Used for fetching events at a specific time # see logbook Index("ix_events_event_type_time_fired", "event_type", "time_fired"), @@ -156,7 +156,7 @@ class States(Base): # type: ignore event = relationship("Events", uselist=False) old_state = relationship("States", remote_side=[state_id]) - __table_args__ = ( + __table_args__ = ( # noqa: PIE794 # Used for fetching the state of entities at a specific time # (get_states in history.py) Index("ix_states_entity_id_last_updated", "entity_id", "last_updated"), @@ -237,7 +237,7 @@ class Statistics(Base): # type: ignore state = Column(Float()) sum = Column(Float()) - __table_args__ = ( + __table_args__ = ( # noqa: PIE794 # Used for fetching statistics for a certain entity at a specific time Index("ix_statistics_statistic_id_start", "statistic_id", "start"), ) diff --git a/tests/components/recorder/db_schema_23_with_newer_columns.py b/tests/components/recorder/db_schema_23_with_newer_columns.py index f3a2dc39859..92c3f7a75ff 100644 --- a/tests/components/recorder/db_schema_23_with_newer_columns.py +++ b/tests/components/recorder/db_schema_23_with_newer_columns.py @@ -254,7 +254,7 @@ class States(Base): # type: ignore TIMESTAMP_TYPE, default=time.time ) # *** Not originally in v23, only added for recorder to startup ok last_updated = Column(DATETIME_TYPE, default=dt_util.utcnow, index=True) - last_updated_ts = Column( + last_updated_ts = Column( # noqa: PIE794 TIMESTAMP_TYPE, default=time.time, index=True ) # *** Not originally in v23, only added for recorder to startup ok created = Column(DATETIME_TYPE, default=dt_util.utcnow) diff --git a/tests/components/twitch/__init__.py b/tests/components/twitch/__init__.py index 3fa7d96337e..bb4530eacf8 100644 --- a/tests/components/twitch/__init__.py +++ b/tests/components/twitch/__init__.py @@ -123,7 +123,6 @@ class TwitchMock: async def _noop(self): """Fake function to create task.""" - pass async def get_users( self, user_ids: list[str] | None = None, logins: list[str] | None = None @@ -157,7 +156,6 @@ class TwitchMock: validate: bool = True, ) -> None: """Set user authentication.""" - pass async def get_followed_channels( self, user_id: str, broadcaster_id: str | None = None diff --git a/tests/test_core.py b/tests/test_core.py index abebcef7121..da4a87702f5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -735,7 +735,6 @@ def test_add_job_pending_tasks_coro(hass: HomeAssistant) -> None: async def test_coro(): """Test Coro.""" - pass for _ in range(2): hass.add_job(test_coro()) diff --git a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py index 7277bac343d..e2a7ef9281f 100644 --- a/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py +++ b/tests/testing_config/custom_components/test_package_raises_cancelled_error_config_entry/config_flow.py @@ -9,8 +9,6 @@ class MockConfigFlow( ): """Mock config flow.""" - pass - async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" From c58bba55bf30273f16d9cb30c07107542dcc1bad Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sat, 16 Mar 2024 22:19:29 +0100 Subject: [PATCH 1164/1691] Remove deprecated `hass.components` from legacy device tracker platform (#113612) --- homeassistant/components/device_tracker/legacy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 45d971529d1..081d5e2ca67 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -15,6 +15,7 @@ import voluptuous as vol from homeassistant import util from homeassistant.backports.functools import cached_property from homeassistant.components import zone +from homeassistant.components.zone import ENTITY_ID_HOME from homeassistant.config import ( async_log_schema_error, config_per_platform, @@ -481,7 +482,7 @@ def async_setup_scanner_platform( }, } - zone_home = hass.states.get(hass.components.zone.ENTITY_ID_HOME) + zone_home = hass.states.get(ENTITY_ID_HOME) if zone_home is not None: kwargs["gps"] = [ zone_home.attributes[ATTR_LATITUDE], From 86ccb99f4c1dde657f1ac48cb3d9917746c523fc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 11:51:56 -1000 Subject: [PATCH 1165/1691] Fix race in removing modified devices from the entity registry (#113623) --- homeassistant/helpers/entity_registry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index d0ac52fe9fc..9f666e86c69 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -638,7 +638,9 @@ class EntityRegistry(BaseRegistry): minor_version=STORAGE_VERSION_MINOR, ) self.hass.bus.async_listen( - EVENT_DEVICE_REGISTRY_UPDATED, self.async_device_modified + EVENT_DEVICE_REGISTRY_UPDATED, + self.async_device_modified, + run_immediately=True, ) @callback From 2bc4a5067ddf1712d611683cc81e25e7f8bde04f Mon Sep 17 00:00:00 2001 From: Lex Li <425130+lextm@users.noreply.github.com> Date: Sat, 16 Mar 2024 17:56:21 -0400 Subject: [PATCH 1166/1691] snmp: Better sensor support to resolve previous issues (#113624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Christian Kühnel Co-authored-by: jan iversen --- homeassistant/components/snmp/sensor.py | 47 ++++++++++- tests/components/snmp/test_float_sensor.py | 79 +++++++++++++++++++ ...{test_sensor.py => test_integer_sensor.py} | 10 +-- tests/components/snmp/test_string_sensor.py | 73 +++++++++++++++++ 4 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 tests/components/snmp/test_float_sensor.py rename tests/components/snmp/{test_sensor.py => test_integer_sensor.py} (91%) create mode 100644 tests/components/snmp/test_string_sensor.py diff --git a/homeassistant/components/snmp/sensor.py b/homeassistant/components/snmp/sensor.py index 02e466d2bc4..f55cd07effb 100644 --- a/homeassistant/components/snmp/sensor.py +++ b/homeassistant/components/snmp/sensor.py @@ -4,7 +4,9 @@ from __future__ import annotations from datetime import timedelta import logging +from struct import unpack +from pyasn1.codec.ber import decoder from pysnmp.error import PySnmpError import pysnmp.hlapi.asyncio as hlapi from pysnmp.hlapi.asyncio import ( @@ -18,6 +20,8 @@ from pysnmp.hlapi.asyncio import ( UsmUserData, getCmd, ) +from pysnmp.proto.rfc1902 import Opaque +from pysnmp.proto.rfc1905 import NoSuchObject import voluptuous as vol from homeassistant.components.sensor import CONF_STATE_CLASS, PLATFORM_SCHEMA @@ -165,7 +169,10 @@ async def async_setup_platform( errindication, _, _, _ = get_result if errindication and not accept_errors: - _LOGGER.error("Please check the details in the configuration file") + _LOGGER.error( + "Please check the details in the configuration file: %s", + errindication, + ) return name = config.get(CONF_NAME, Template(DEFAULT_NAME, hass)) @@ -248,10 +255,44 @@ class SnmpData: _LOGGER.error( "SNMP error: %s at %s", errstatus.prettyPrint(), - errindex and restable[-1][int(errindex) - 1] or "?", + restable[-1][int(errindex) - 1] if errindex else "?", ) elif (errindication or errstatus) and self._accept_errors: self.value = self._default_value else: for resrow in restable: - self.value = resrow[-1].prettyPrint() + self.value = self._decode_value(resrow[-1]) + + def _decode_value(self, value): + """Decode the different results we could get into strings.""" + + _LOGGER.debug( + "SNMP OID %s received type=%s and data %s", + self._baseoid, + type(value), + bytes(value), + ) + if isinstance(value, NoSuchObject): + _LOGGER.error( + "SNMP error for OID %s: No Such Object currently exists at this OID", + self._baseoid, + ) + return self._default_value + + if isinstance(value, Opaque): + # Float data type is not supported by the pyasn1 library, + # so we need to decode this type ourselves based on: + # https://tools.ietf.org/html/draft-perkins-opaque-01 + if bytes(value).startswith(b"\x9f\x78"): + return str(unpack("!f", bytes(value)[3:])[0]) + # Otherwise Opaque types should be asn1 encoded + try: + decoded_value, _ = decoder.decode(bytes(value)) + return str(decoded_value) + # pylint: disable=broad-except + except Exception as decode_exception: + _LOGGER.error( + "SNMP error in decoding opaque type: %s", decode_exception + ) + return self._default_value + return str(value) diff --git a/tests/components/snmp/test_float_sensor.py b/tests/components/snmp/test_float_sensor.py new file mode 100644 index 00000000000..0e11ee03968 --- /dev/null +++ b/tests/components/snmp/test_float_sensor.py @@ -0,0 +1,79 @@ +"""SNMP sensor tests.""" + +from unittest.mock import patch + +from pysnmp.proto.rfc1902 import Opaque +import pytest + +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + + +@pytest.fixture(autouse=True) +def hlapi_mock(): + """Mock out 3rd party API.""" + mock_data = Opaque(value=b"\x9fx\x04=\xa4\x00\x00") + with patch( + "homeassistant.components.snmp.sensor.getCmd", + return_value=(None, None, None, [[mock_data]]), + ): + yield + + +async def test_basic_config(hass: HomeAssistant) -> None: + """Test basic entity configuration.""" + + config = { + SENSOR_DOMAIN: { + "platform": "snmp", + "host": "192.168.1.32", + "baseoid": "1.3.6.1.4.1.2021.10.1.3.1", + }, + } + + assert await async_setup_component(hass, SENSOR_DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.snmp") + assert state.state == "0.080078125" + assert state.attributes == {"friendly_name": "SNMP"} + + +async def test_entity_config(hass: HomeAssistant) -> None: + """Test entity configuration.""" + + config = { + SENSOR_DOMAIN: { + # SNMP configuration + "platform": "snmp", + "host": "192.168.1.32", + "baseoid": "1.3.6.1.4.1.2021.10.1.3.1", + # Entity configuration + "icon": "{{'mdi:one_two_three'}}", + "picture": "{{'blabla.png'}}", + "device_class": "temperature", + "name": "{{'SNMP' + ' ' + 'Sensor'}}", + "state_class": "measurement", + "unique_id": "very_unique", + "unit_of_measurement": "°C", + }, + } + + assert await async_setup_component(hass, SENSOR_DOMAIN, config) + await hass.async_block_till_done() + + entity_registry = er.async_get(hass) + assert entity_registry.async_get("sensor.snmp_sensor").unique_id == "very_unique" + + state = hass.states.get("sensor.snmp_sensor") + assert state.state == "0.080078125" + assert state.attributes == { + "device_class": "temperature", + "entity_picture": "blabla.png", + "friendly_name": "SNMP Sensor", + "icon": "mdi:one_two_three", + "state_class": "measurement", + "unit_of_measurement": "°C", + } diff --git a/tests/components/snmp/test_sensor.py b/tests/components/snmp/test_integer_sensor.py similarity index 91% rename from tests/components/snmp/test_sensor.py rename to tests/components/snmp/test_integer_sensor.py index d6637946da8..0ea9ac4d434 100644 --- a/tests/components/snmp/test_sensor.py +++ b/tests/components/snmp/test_integer_sensor.py @@ -1,7 +1,8 @@ """SNMP sensor tests.""" -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import patch +from pysnmp.hlapi import Integer32 import pytest from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -13,8 +14,7 @@ from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) def hlapi_mock(): """Mock out 3rd party API.""" - mock_data = MagicMock() - mock_data.prettyPrint = Mock(return_value="13.5") + mock_data = Integer32(13) with patch( "homeassistant.components.snmp.sensor.getCmd", return_value=(None, None, None, [[mock_data]]), @@ -37,7 +37,7 @@ async def test_basic_config(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get("sensor.snmp") - assert state.state == "13.5" + assert state.state == "13" assert state.attributes == {"friendly_name": "SNMP"} @@ -68,7 +68,7 @@ async def test_entity_config(hass: HomeAssistant) -> None: assert entity_registry.async_get("sensor.snmp_sensor").unique_id == "very_unique" state = hass.states.get("sensor.snmp_sensor") - assert state.state == "13.5" + assert state.state == "13" assert state.attributes == { "device_class": "temperature", "entity_picture": "blabla.png", diff --git a/tests/components/snmp/test_string_sensor.py b/tests/components/snmp/test_string_sensor.py new file mode 100644 index 00000000000..536b819b711 --- /dev/null +++ b/tests/components/snmp/test_string_sensor.py @@ -0,0 +1,73 @@ +"""SNMP sensor tests.""" + +from unittest.mock import patch + +from pysnmp.proto.rfc1902 import OctetString +import pytest + +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + + +@pytest.fixture(autouse=True) +def hlapi_mock(): + """Mock out 3rd party API.""" + mock_data = OctetString("98F") + with patch( + "homeassistant.components.snmp.sensor.getCmd", + return_value=(None, None, None, [[mock_data]]), + ): + yield + + +async def test_basic_config(hass: HomeAssistant) -> None: + """Test basic entity configuration.""" + + config = { + SENSOR_DOMAIN: { + "platform": "snmp", + "host": "192.168.1.32", + "baseoid": "1.3.6.1.4.1.2021.10.1.3.1", + }, + } + + assert await async_setup_component(hass, SENSOR_DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.snmp") + assert state.state == "98F" + assert state.attributes == {"friendly_name": "SNMP"} + + +async def test_entity_config(hass: HomeAssistant) -> None: + """Test entity configuration.""" + + config = { + SENSOR_DOMAIN: { + # SNMP configuration + "platform": "snmp", + "host": "192.168.1.32", + "baseoid": "1.3.6.1.4.1.2021.10.1.3.1", + # Entity configuration + "icon": "{{'mdi:one_two_three'}}", + "picture": "{{'blabla.png'}}", + "name": "{{'SNMP' + ' ' + 'Sensor'}}", + "unique_id": "very_unique", + }, + } + + assert await async_setup_component(hass, SENSOR_DOMAIN, config) + await hass.async_block_till_done() + + entity_registry = er.async_get(hass) + assert entity_registry.async_get("sensor.snmp_sensor").unique_id == "very_unique" + + state = hass.states.get("sensor.snmp_sensor") + assert state.state == "98F" + assert state.attributes == { + "entity_picture": "blabla.png", + "friendly_name": "SNMP Sensor", + "icon": "mdi:one_two_three", + } From 554aefed423d672c431ff21c78b50c596573cce5 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 16 Mar 2024 22:56:48 +0100 Subject: [PATCH 1167/1691] Generate HomeAssistantError message from English translations (#113305) * Fetch exception message from translation cache * Improve tests * Return translation key without path, cleanup * Fetch translations when string variant is requested * Move import * revert changes ConfigValidationError * mypy * Remove _str__ method instead * Type _message for mqtt template exception classes * Revert changes made to test_config.py * Undo changes TemplateError * Follow up comments and test coverage --- homeassistant/components/climate/__init__.py | 2 - homeassistant/components/mqtt/models.py | 4 ++ homeassistant/exceptions.py | 38 +++++++++++-- homeassistant/helpers/translation.py | 32 ++++++++++- tests/components/climate/test_init.py | 16 +++--- tests/test_exceptions.py | 57 ++++++++++++++++++++ 6 files changed, 136 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 7265b5192e8..2382c535413 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -647,8 +647,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): elif mode_type == "fan": translation_key = "not_valid_fan_mode" raise ServiceValidationError( - f"The {mode_type}_mode {mode} is not a valid {mode_type}_mode:" - f" {modes_str}", translation_domain=DOMAIN, translation_key=translation_key, translation_placeholders={ diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 9c961a3b543..6e6ae784eec 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -116,6 +116,8 @@ class MqttOriginInfo(TypedDict, total=False): class MqttCommandTemplateException(ServiceValidationError): """Handle MqttCommandTemplate exceptions.""" + _message: str + def __init__( self, *args: object, @@ -227,6 +229,8 @@ class MqttCommandTemplate: class MqttValueTemplateException(TemplateError): """Handle MqttValueTemplate exceptions.""" + _message: str + def __init__( self, *args: object, diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index a5999510f9f..409fa3450bd 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -13,6 +13,9 @@ if TYPE_CHECKING: class HomeAssistantError(Exception): """General Home Assistant exception occurred.""" + _message: str | None = None + generate_message: bool = False + def __init__( self, *args: object, @@ -21,11 +24,42 @@ class HomeAssistantError(Exception): translation_placeholders: dict[str, str] | None = None, ) -> None: """Initialize exception.""" + if not args and translation_key and translation_domain: + self.generate_message = True + args = (translation_key,) + super().__init__(*args) self.translation_domain = translation_domain self.translation_key = translation_key self.translation_placeholders = translation_placeholders + def __str__(self) -> str: + """Return exception message. + + If no message was passed to `__init__`, the exception message is generated from + the translation_key. The message will be in English, regardless of the configured + language. + """ + + if self._message: + return self._message + + if not self.generate_message: + self._message = super().__str__() + return self._message + + if TYPE_CHECKING: + assert self.translation_key is not None + assert self.translation_domain is not None + + # pylint: disable-next=import-outside-toplevel + from .helpers.translation import async_get_exception_message + + self._message = async_get_exception_message( + self.translation_domain, self.translation_key, self.translation_placeholders + ) + return self._message + class ConfigValidationError(HomeAssistantError, ExceptionGroup[Exception]): """A validation exception occurred when validating the configuration.""" @@ -47,10 +81,6 @@ class ConfigValidationError(HomeAssistantError, ExceptionGroup[Exception]): ) self._message = message - def __str__(self) -> str: - """Return exception message string.""" - return self._message - class ServiceValidationError(HomeAssistantError): """A validation exception occurred when calling a service.""" diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 641d200afe3..841226ac584 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import Iterable, Mapping +from contextlib import suppress import logging import string from typing import Any @@ -13,7 +14,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, async_get_hass, callback from homeassistant.loader import ( Integration, async_get_config_flows, @@ -528,6 +529,35 @@ def async_translations_loaded(hass: HomeAssistant, components: set[str]) -> bool ) +@callback +def async_get_exception_message( + translation_domain: str, + translation_key: str, + translation_placeholders: dict[str, str] | None = None, +) -> str: + """Return a translated exception message. + + Defaults to English, requires translations to already be cached. + """ + language = "en" + hass = async_get_hass() + localize_key = ( + f"component.{translation_domain}.exceptions.{translation_key}.message" + ) + translations = async_get_cached_translations(hass, language, "exceptions") + if localize_key in translations: + if message := translations[localize_key]: + message = message.rstrip(".") + if not translation_placeholders: + return message + with suppress(KeyError): + message = message.format(**translation_placeholders) + return message + + # We return the translation key when was not found in the cache + return translation_key + + @callback def async_translate_state( hass: HomeAssistant, diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 3950dea59ed..65b7287f549 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -300,7 +300,7 @@ async def test_preset_mode_validation( with pytest.raises( ServiceValidationError, - match="The preset_mode invalid is not a valid preset_mode: home, away", + match="Preset mode invalid is not valid. Valid preset modes are: home, away", ) as exc: await hass.services.async_call( DOMAIN, @@ -313,13 +313,13 @@ async def test_preset_mode_validation( ) assert ( str(exc.value) - == "The preset_mode invalid is not a valid preset_mode: home, away" + == "Preset mode invalid is not valid. Valid preset modes are: home, away" ) assert exc.value.translation_key == "not_valid_preset_mode" with pytest.raises( ServiceValidationError, - match="The swing_mode invalid is not a valid swing_mode: auto, off", + match="Swing mode invalid is not valid. Valid swing modes are: auto, off", ) as exc: await hass.services.async_call( DOMAIN, @@ -331,13 +331,14 @@ async def test_preset_mode_validation( blocking=True, ) assert ( - str(exc.value) == "The swing_mode invalid is not a valid swing_mode: auto, off" + str(exc.value) + == "Swing mode invalid is not valid. Valid swing modes are: auto, off" ) assert exc.value.translation_key == "not_valid_swing_mode" with pytest.raises( ServiceValidationError, - match="The fan_mode invalid is not a valid fan_mode: auto, off", + match="Fan mode invalid is not valid. Valid fan modes are: auto, off", ) as exc: await hass.services.async_call( DOMAIN, @@ -348,7 +349,10 @@ async def test_preset_mode_validation( }, blocking=True, ) - assert str(exc.value) == "The fan_mode invalid is not a valid fan_mode: auto, off" + assert ( + str(exc.value) + == "Fan mode invalid is not valid. Valid fan modes are: auto, off" + ) assert exc.value.translation_key == "not_valid_fan_mode" diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3f18b3527b7..f5b60627ce2 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -2,12 +2,17 @@ from __future__ import annotations +from typing import Any +from unittest.mock import patch + import pytest +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ( ConditionErrorContainer, ConditionErrorIndex, ConditionErrorMessage, + HomeAssistantError, TemplateError, ) @@ -62,3 +67,55 @@ def test_template_message(arg: str | Exception, expected: str) -> None: """Ensure we can create TemplateError.""" template_error = TemplateError(arg) assert str(template_error) == expected + + +@pytest.mark.parametrize( + ("exception_args", "exception_kwargs", "args_base_class", "message"), + [ + ((), {}, (), ""), + (("bla",), {}, ("bla",), "bla"), + ((None,), {}, (None,), "None"), + ((type_error_bla := TypeError("bla"),), {}, (type_error_bla,), "bla"), + ( + (), + {"translation_domain": "test", "translation_key": "test"}, + ("test",), + "test", + ), + ( + (), + {"translation_domain": "test", "translation_key": "bla"}, + ("bla",), + "{bla} from cache", + ), + ( + (), + { + "translation_domain": "test", + "translation_key": "bla", + "translation_placeholders": {"bla": "Bla"}, + }, + ("bla",), + "Bla from cache", + ), + ], +) +async def test_home_assistant_error( + hass: HomeAssistant, + exception_args: tuple[Any,], + exception_kwargs: dict[str, Any], + args_base_class: tuple[Any], + message: str, +) -> None: + """Test edge cases with HomeAssistantError.""" + + with patch( + "homeassistant.helpers.translation.async_get_cached_translations", + return_value={"component.test.exceptions.bla.message": "{bla} from cache"}, + ): + with pytest.raises(HomeAssistantError) as exc: + raise HomeAssistantError(*exception_args, **exception_kwargs) + assert exc.value.args == args_base_class + assert str(exc.value) == message + # Get string of exception again from the cache + assert str(exc.value) == message From ab9b64729a0c9b0402f7f4d8b6300f06c3ce62f4 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 16 Mar 2024 23:02:52 +0100 Subject: [PATCH 1168/1691] Bump axis to v56 (#113608) --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index c1471d370a5..346afc4b4fe 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==55"], + "requirements": ["axis==56"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index 7948214c64c..f93cd2ea95f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==55 +axis==56 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1b75ae9aaa2..037cbe18bda 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -451,7 +451,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==55 +axis==56 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From a9da9ee443d4e23cd531d876913314909b159b30 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 16 Mar 2024 15:03:26 -0700 Subject: [PATCH 1169/1691] Bump ical to 7.0.3 to fix local-todo persisted with invalid DTSTART values (#113526) --- homeassistant/components/google/manifest.json | 2 +- .../components/local_calendar/manifest.json | 2 +- .../components/local_todo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../local_todo/snapshots/test_todo.ambr | 10 ++++++++ tests/components/local_todo/test_todo.py | 23 +++++++++++++++++++ 7 files changed, 38 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index a08daee8961..ec9fb7018d6 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/calendar.google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==7.0.1"] + "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==7.0.3"] } diff --git a/homeassistant/components/local_calendar/manifest.json b/homeassistant/components/local_calendar/manifest.json index 25ec9f2ccc6..1c13970503d 100644 --- a/homeassistant/components/local_calendar/manifest.json +++ b/homeassistant/components/local_calendar/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/local_calendar", "iot_class": "local_polling", "loggers": ["ical"], - "requirements": ["ical==7.0.1"] + "requirements": ["ical==7.0.3"] } diff --git a/homeassistant/components/local_todo/manifest.json b/homeassistant/components/local_todo/manifest.json index 81f0f9dc199..3bcb8af9f43 100644 --- a/homeassistant/components/local_todo/manifest.json +++ b/homeassistant/components/local_todo/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/local_todo", "iot_class": "local_polling", - "requirements": ["ical==7.0.1"] + "requirements": ["ical==7.0.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index f93cd2ea95f..f166683b2b7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1115,7 +1115,7 @@ ibmiotf==0.3.4 # homeassistant.components.google # homeassistant.components.local_calendar # homeassistant.components.local_todo -ical==7.0.1 +ical==7.0.3 # homeassistant.components.ping icmplib==3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 037cbe18bda..fc29b120069 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -905,7 +905,7 @@ ibeacon-ble==1.2.0 # homeassistant.components.google # homeassistant.components.local_calendar # homeassistant.components.local_todo -ical==7.0.1 +ical==7.0.3 # homeassistant.components.ping icmplib==3.0 diff --git a/tests/components/local_todo/snapshots/test_todo.ambr b/tests/components/local_todo/snapshots/test_todo.ambr index db4403f301c..15a44ff8c27 100644 --- a/tests/components/local_todo/snapshots/test_todo.ambr +++ b/tests/components/local_todo/snapshots/test_todo.ambr @@ -22,6 +22,16 @@ list([ ]) # --- +# name: test_parse_existing_ics[invalid_dtstart_tzname] + list([ + dict({ + 'due': '2023-10-24T11:30:00', + 'status': 'needs_action', + 'summary': 'Task', + 'uid': '077cb7f2-6c89-11ee-b2a9-0242ac110002', + }), + ]) +# --- # name: test_parse_existing_ics[migrate_legacy_due] list([ dict({ diff --git a/tests/components/local_todo/test_todo.py b/tests/components/local_todo/test_todo.py index 231f56b0afb..760b0260dbb 100644 --- a/tests/components/local_todo/test_todo.py +++ b/tests/components/local_todo/test_todo.py @@ -671,6 +671,28 @@ async def test_move_item_previous_unknown( ), "1", ), + ( + textwrap.dedent( + """\ + BEGIN:VCALENDAR + PRODID:-//homeassistant.io//local_todo 2.0//EN + VERSION:2.0 + BEGIN:VTODO + DTSTAMP:20231024T014011 + UID:077cb7f2-6c89-11ee-b2a9-0242ac110002 + CREATED:20231017T010348 + LAST-MODIFIED:20231024T014011 + SEQUENCE:1 + STATUS:NEEDS-ACTION + SUMMARY:Task + DUE:20231024T113000 + DTSTART;TZID=CST:20231024T113000 + END:VTODO + END:VCALENDAR + """ + ), + "1", + ), ], ids=( "empty", @@ -679,6 +701,7 @@ async def test_move_item_previous_unknown( "needs_action", "migrate_legacy_due", "due", + "invalid_dtstart_tzname", ), ) async def test_parse_existing_ics( From 0725ff34b164bbb0d9e734abdfe156a5d403fadf Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sat, 16 Mar 2024 18:26:56 -0400 Subject: [PATCH 1170/1691] Bump pyunifiprotect to 5.0.1 (#113630) --- homeassistant/components/unifiprotect/manifest.json | 2 +- homeassistant/components/unifiprotect/select.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 37b810495a1..ab4dfb67743 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -41,7 +41,7 @@ "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], "quality_scale": "platinum", - "requirements": ["pyunifiprotect==4.23.3", "unifi-discovery==1.1.8"], + "requirements": ["pyunifiprotect==5.0.1", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 13bf18eb2a7..e6e0762f478 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -46,6 +46,7 @@ INFRARED_MODES = [ {"id": IRLEDMode.AUTO.value, "name": "Auto"}, {"id": IRLEDMode.ON.value, "name": "Always Enable"}, {"id": IRLEDMode.AUTO_NO_LED.value, "name": "Auto (Filter Only, no LED's)"}, + {"id": IRLEDMode.CUSTOM.value, "name": "Auto (Custom Lux)"}, {"id": IRLEDMode.OFF.value, "name": "Always Disable"}, ] diff --git a/requirements_all.txt b/requirements_all.txt index f166683b2b7..cc084afa0b7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2333,7 +2333,7 @@ pytrydan==0.4.0 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.23.3 +pyunifiprotect==5.0.1 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc29b120069..84ffcf7d15f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1800,7 +1800,7 @@ pytrydan==0.4.0 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==4.23.3 +pyunifiprotect==5.0.1 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From d0352ed91d7ecef639c5264219bb5cdf35ef55fc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 16 Mar 2024 23:37:24 +0100 Subject: [PATCH 1171/1691] Fix missing context when running script from template entity (#113523) Co-authored-by: J. Nick Koston --- homeassistant/components/template/coordinator.py | 7 +++++-- tests/components/template/test_sensor.py | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/coordinator.py b/homeassistant/components/template/coordinator.py index 4e86d629990..3319afa01c2 100644 --- a/homeassistant/components/template/coordinator.py +++ b/homeassistant/components/template/coordinator.py @@ -4,7 +4,7 @@ from collections.abc import Callable import logging from homeassistant.const import EVENT_HOMEASSISTANT_START -from homeassistant.core import CoreState, callback +from homeassistant.core import Context, CoreState, callback from homeassistant.helpers import discovery, trigger as trigger_helper from homeassistant.helpers.script import Script from homeassistant.helpers.typing import ConfigType @@ -91,7 +91,10 @@ class TriggerUpdateCoordinator(DataUpdateCoordinator): ) async def _handle_triggered_with_script(self, run_variables, context=None): - if script_result := await self._script.async_run(run_variables, context): + # Create a context referring to the trigger context. + trigger_context_id = None if context is None else context.id + script_context = Context(parent_id=trigger_context_id) + if script_result := await self._script.async_run(run_variables, script_context): run_variables = script_result.variables self._handle_triggered(run_variables, context) diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 8d497a9af73..462f1350656 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -30,6 +30,7 @@ import homeassistant.util.dt as dt_util from tests.common import ( MockConfigEntry, assert_setup_component, + async_capture_events, async_fire_time_changed, mock_restore_cache_with_extra_data, ) @@ -1849,6 +1850,7 @@ async def test_trigger_entity_restore_state( "my_variable": "{{ trigger.event.data.beer + 1 }}" }, }, + {"event": "test_event2", "event_data": {"hello": "world"}}, ], "sensor": [ { @@ -1865,6 +1867,10 @@ async def test_trigger_action( hass: HomeAssistant, start_ha, entity_registry: er.EntityRegistry ) -> None: """Test trigger entity with an action works.""" + event = "test_event2" + context = Context() + events = async_capture_events(hass, event) + state = hass.states.get("sensor.hello_name") assert state is not None assert state.state == STATE_UNKNOWN @@ -1876,3 +1882,6 @@ async def test_trigger_action( state = hass.states.get("sensor.hello_name") assert state.state == "3" assert state.context is context + + assert len(events) == 1 + assert events[0].context.parent_id == context.id From fe9cc6705c5f3bd160c46ba1ecd1c8162f2dd162 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:45:18 +0100 Subject: [PATCH 1172/1691] Add ruff rule PIE804 (#113620) Co-authored-by: J. Nick Koston --- homeassistant/components/github/__init__.py | 2 +- .../components/github/config_flow.py | 10 ++++---- .../components/github/diagnostics.py | 2 +- homeassistant/components/iqvia/__init__.py | 2 +- pyproject.toml | 1 + .../components/aladdin_connect/test_sensor.py | 10 ++++---- tests/components/apcupsd/test_sensor.py | 2 +- tests/components/apprise/test_notify.py | 6 ++--- tests/components/cloud/test_http_api.py | 24 +++++++++---------- tests/components/fronius/__init__.py | 2 +- tests/components/hue/test_sensor_v2.py | 2 +- tests/components/lifx/test_sensor.py | 4 ++-- tests/components/nam/test_sensor.py | 2 +- .../components/onewire/test_binary_sensor.py | 2 +- tests/components/onewire/test_sensor.py | 2 +- tests/components/onewire/test_switch.py | 2 +- tests/components/plex/test_media_search.py | 2 +- tests/components/renault/test_sensor.py | 2 +- tests/components/sfr_box/test_sensor.py | 2 +- .../signal_messenger/test_notify.py | 6 ++--- tests/components/switcher_kis/test_sensor.py | 4 +--- tests/components/tomorrowio/test_sensor.py | 4 +--- tests/components/tomorrowio/test_weather.py | 4 +--- tests/components/xiaomi_miio/test_vacuum.py | 16 ++----------- .../components/zwave_js/test_binary_sensor.py | 6 ++--- .../zwave_js/test_device_trigger.py | 8 +++---- tests/components/zwave_js/test_discovery.py | 4 +--- tests/components/zwave_js/test_number.py | 2 +- tests/components/zwave_js/test_select.py | 4 +--- tests/components/zwave_js/test_sensor.py | 8 +++---- tests/components/zwave_js/test_switch.py | 4 +--- tests/test_config.py | 4 ++-- 32 files changed, 64 insertions(+), 91 deletions(-) diff --git a/homeassistant/components/github/__init__.py b/homeassistant/components/github/__init__.py index 20706c1acbc..20df559b819 100644 --- a/homeassistant/components/github/__init__.py +++ b/homeassistant/components/github/__init__.py @@ -26,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: client = GitHubAPI( token=entry.data[CONF_ACCESS_TOKEN], session=async_get_clientsession(hass), - **{"client_name": SERVER_SOFTWARE}, + client_name=SERVER_SOFTWARE, ) repositories: list[str] = entry.options[CONF_REPOSITORIES] diff --git a/homeassistant/components/github/config_flow.py b/homeassistant/components/github/config_flow.py index 2d9440928e3..25d8782618f 100644 --- a/homeassistant/components/github/config_flow.py +++ b/homeassistant/components/github/config_flow.py @@ -38,12 +38,12 @@ async def get_repositories(hass: HomeAssistant, access_token: str) -> list[str]: repositories = set() async def _get_starred_repositories() -> None: - response = await client.user.starred(**{"params": {"per_page": 100}}) + response = await client.user.starred(params={"per_page": 100}) if not response.is_last_page: results = await asyncio.gather( *( client.user.starred( - **{"params": {"per_page": 100, "page": page_number}}, + params={"per_page": 100, "page": page_number}, ) for page_number in range( response.next_page_number, response.last_page_number + 1 @@ -56,12 +56,12 @@ async def get_repositories(hass: HomeAssistant, access_token: str) -> list[str]: repositories.update(response.data) async def _get_personal_repositories() -> None: - response = await client.user.repos(**{"params": {"per_page": 100}}) + response = await client.user.repos(params={"per_page": 100}) if not response.is_last_page: results = await asyncio.gather( *( client.user.repos( - **{"params": {"per_page": 100, "page": page_number}}, + params={"per_page": 100, "page": page_number}, ) for page_number in range( response.next_page_number, response.last_page_number + 1 @@ -137,7 +137,7 @@ class GitHubConfigFlow(ConfigFlow, domain=DOMAIN): self._device = GitHubDeviceAPI( client_id=CLIENT_ID, session=async_get_clientsession(self.hass), - **{"client_name": SERVER_SOFTWARE}, + client_name=SERVER_SOFTWARE, ) try: diff --git a/homeassistant/components/github/diagnostics.py b/homeassistant/components/github/diagnostics.py index 37313be3690..df1e4b4a4cf 100644 --- a/homeassistant/components/github/diagnostics.py +++ b/homeassistant/components/github/diagnostics.py @@ -27,7 +27,7 @@ async def async_get_config_entry_diagnostics( client = GitHubAPI( token=config_entry.data[CONF_ACCESS_TOKEN], session=async_get_clientsession(hass), - **{"client_name": SERVER_SOFTWARE}, + client_name=SERVER_SOFTWARE, ) try: diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index 8f85d98c576..27d7d46f677 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -47,7 +47,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not entry.unique_id: # If the config entry doesn't already have a unique ID, set one: hass.config_entries.async_update_entry( - entry, **{"unique_id": entry.data[CONF_ZIP_CODE]} + entry, unique_id=entry.data[CONF_ZIP_CODE] ) websession = aiohttp_client.async_get_clientsession(hass) diff --git a/pyproject.toml b/pyproject.toml index 04aca768267..c10562c0fa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -605,6 +605,7 @@ select = [ "N815", # Variable {name} in class scope should not be mixedCase "PERF", # Perflint "PGH004", # Use specific rule codes when using noqa + "PIE804", # Unnecessary dict kwargs "PIE790", # Unnecessary pass statement "PIE794", # Class field is defined multiple times "PIE807", # Prefer list/dict over useless lambda diff --git a/tests/components/aladdin_connect/test_sensor.py b/tests/components/aladdin_connect/test_sensor.py index c6fac4cf79e..9c229e2ac5e 100644 --- a/tests/components/aladdin_connect/test_sensor.py +++ b/tests/components/aladdin_connect/test_sensor.py @@ -53,7 +53,7 @@ async def test_sensors( assert entry.disabled assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION update_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) await hass.async_block_till_done() assert update_entry != entry @@ -75,7 +75,7 @@ async def test_sensors( assert entry.disabled assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION update_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) await hass.async_block_till_done() assert update_entry != entry @@ -84,7 +84,7 @@ async def test_sensors( assert state is None update_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) await hass.async_block_till_done() async_fire_time_changed( @@ -135,7 +135,7 @@ async def test_sensors_model_01( assert entry.disabled assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION update_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) await hass.async_block_till_done() assert update_entry != entry @@ -144,7 +144,7 @@ async def test_sensors_model_01( assert state is None update_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) await hass.async_block_till_done() async_fire_time_changed( diff --git a/tests/components/apcupsd/test_sensor.py b/tests/components/apcupsd/test_sensor.py index 77987e455c7..0c7d174a5e8 100644 --- a/tests/components/apcupsd/test_sensor.py +++ b/tests/components/apcupsd/test_sensor.py @@ -134,7 +134,7 @@ async def test_sensor_disabled( # Test enabling entity. updated_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) assert updated_entry != entry diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index ef7f878a234..c2b11ccb043 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -121,7 +121,7 @@ async def test_apprise_notification(hass: HomeAssistant) -> None: # Validate calls were made under the hood correctly obj.add.assert_called_once_with(config[BASE_COMPONENT]["url"]) obj.notify.assert_called_once_with( - **{"body": data["message"], "title": data["title"], "tag": None} + body=data["message"], title=data["title"], tag=None ) @@ -162,7 +162,7 @@ async def test_apprise_multiple_notification(hass: HomeAssistant) -> None: # Validate 2 calls were made under the hood assert obj.add.call_count == 2 obj.notify.assert_called_once_with( - **{"body": data["message"], "title": data["title"], "tag": None} + body=data["message"], title=data["title"], tag=None ) @@ -204,5 +204,5 @@ async def test_apprise_notification_with_target( # Validate calls were made under the hood correctly apprise_obj.notify.assert_called_once_with( - **{"body": data["message"], "title": data["title"], "tag": data["target"]} + body=data["message"], title=data["title"], tag=data["target"] ) diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 389361693ed..0ff9fd79c75 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -467,19 +467,17 @@ async def test_register_view_with_location( with patch( "homeassistant.components.cloud.http_api.async_detect_location_info", return_value=LocationInfo( - **{ - "country_code": "XX", - "zip_code": "12345", - "region_code": "GH", - "ip": "1.2.3.4", - "city": "Gotham", - "region_name": "Gotham", - "time_zone": "Earth/Gotham", - "currency": "XXX", - "latitude": "12.34567", - "longitude": "12.34567", - "use_metric": True, - } + country_code="XX", + zip_code="12345", + region_code="GH", + ip="1.2.3.4", + city="Gotham", + region_name="Gotham", + time_zone="Earth/Gotham", + currency="XXX", + latitude="12.34567", + longitude="12.34567", + use_metric=True, ), ): req = await cloud_client.post( diff --git a/tests/components/fronius/__init__.py b/tests/components/fronius/__init__.py index 5f0e86c292d..3757abab928 100644 --- a/tests/components/fronius/__init__.py +++ b/tests/components/fronius/__init__.py @@ -121,7 +121,7 @@ async def enable_all_entities(hass, freezer, config_entry_id, time_till_next_upd for entry in entities if entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION ]: - registry.async_update_entity(entry.entity_id, **{"disabled_by": None}) + registry.async_update_entity(entry.entity_id, disabled_by=None) await hass.async_block_till_done() freezer.tick(time_till_next_update) async_fire_time_changed(hass) diff --git a/tests/components/hue/test_sensor_v2.py b/tests/components/hue/test_sensor_v2.py index ef13ba15235..4c1f8defc95 100644 --- a/tests/components/hue/test_sensor_v2.py +++ b/tests/components/hue/test_sensor_v2.py @@ -86,7 +86,7 @@ async def test_enable_sensor( # enable the entity updated_entry = entity_registry.async_update_entity( - entity_entry.entity_id, **{"disabled_by": None} + entity_entry.entity_id, disabled_by=None ) assert updated_entry != entity_entry assert updated_entry.disabled is False diff --git a/tests/components/lifx/test_sensor.py b/tests/components/lifx/test_sensor.py index 91000350f89..5a94963ca18 100644 --- a/tests/components/lifx/test_sensor.py +++ b/tests/components/lifx/test_sensor.py @@ -60,7 +60,7 @@ async def test_rssi_sensor( # Test enabling entity updated_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) with _patch_discovery(device=bulb), _patch_config_flow_try_connect( @@ -112,7 +112,7 @@ async def test_rssi_sensor_old_firmware( # Test enabling entity updated_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) with _patch_discovery(device=bulb), _patch_config_flow_try_connect( diff --git a/tests/components/nam/test_sensor.py b/tests/components/nam/test_sensor.py index 5a8f67c4378..44bf6552042 100644 --- a/tests/components/nam/test_sensor.py +++ b/tests/components/nam/test_sensor.py @@ -488,7 +488,7 @@ async def test_sensor_disabled( # Test enabling entity updated_entry = entity_registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} + entry.entity_id, disabled_by=None ) assert updated_entry != entry diff --git a/tests/components/onewire/test_binary_sensor.py b/tests/components/onewire/test_binary_sensor.py index 5fd4ab2cee6..26b1ed5aed7 100644 --- a/tests/components/onewire/test_binary_sensor.py +++ b/tests/components/onewire/test_binary_sensor.py @@ -50,7 +50,7 @@ async def test_binary_sensors( setup_owproxy_mock_devices(owproxy, Platform.BINARY_SENSOR, [device_id]) # Some entities are disabled, enable them and reload before checking states for ent in entity_entries: - entity_registry.async_update_entity(ent.entity_id, **{"disabled_by": None}) + entity_registry.async_update_entity(ent.entity_id, disabled_by=None) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py index 856c3ec4dea..848489c837f 100644 --- a/tests/components/onewire/test_sensor.py +++ b/tests/components/onewire/test_sensor.py @@ -54,7 +54,7 @@ async def test_sensors( setup_owproxy_mock_devices(owproxy, Platform.SENSOR, [device_id]) # Some entities are disabled, enable them and reload before checking states for ent in entity_entries: - entity_registry.async_update_entity(ent.entity_id, **{"disabled_by": None}) + entity_registry.async_update_entity(ent.entity_id, disabled_by=None) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/onewire/test_switch.py b/tests/components/onewire/test_switch.py index a367ace3b02..c6d84d38848 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -57,7 +57,7 @@ async def test_switches( setup_owproxy_mock_devices(owproxy, Platform.SWITCH, [device_id]) # Some entities are disabled, enable them and reload before checking states for ent in entity_entries: - entity_registry.async_update_entity(ent.entity_id, **{"disabled_by": None}) + entity_registry.async_update_entity(ent.entity_id, disabled_by=None) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/plex/test_media_search.py b/tests/components/plex/test_media_search.py index da5b89c18eb..517fdd2f689 100644 --- a/tests/components/plex/test_media_search.py +++ b/tests/components/plex/test_media_search.py @@ -247,7 +247,7 @@ async def test_media_lookups( }, True, ) - search.assert_called_with(**{"title": "Movie 1", "libtype": None}) + search.assert_called_with(title="Movie 1", libtype=None) with pytest.raises(MediaNotFound) as excinfo: payload = '{"title": "Movie 1"}' diff --git a/tests/components/renault/test_sensor.py b/tests/components/renault/test_sensor.py index 984e974ce3b..bd94aa8d8e1 100644 --- a/tests/components/renault/test_sensor.py +++ b/tests/components/renault/test_sensor.py @@ -50,7 +50,7 @@ async def test_sensors( # Some entities are disabled, enable them and reload before checking states for ent in entity_entries: - entity_registry.async_update_entity(ent.entity_id, **{"disabled_by": None}) + entity_registry.async_update_entity(ent.entity_id, disabled_by=None) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/sfr_box/test_sensor.py b/tests/components/sfr_box/test_sensor.py index 9d806f39be7..afdcf87b9db 100644 --- a/tests/components/sfr_box/test_sensor.py +++ b/tests/components/sfr_box/test_sensor.py @@ -46,7 +46,7 @@ async def test_sensors( # Some entities are disabled, enable them and reload before checking states for ent in entity_entries: - entity_registry.async_update_entity(ent.entity_id, **{"disabled_by": None}) + entity_registry.async_update_entity(ent.entity_id, disabled_by=None) await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/signal_messenger/test_notify.py b/tests/components/signal_messenger/test_notify.py index d1f590ab5bc..87b51bdef7c 100644 --- a/tests/components/signal_messenger/test_notify.py +++ b/tests/components/signal_messenger/test_notify.py @@ -92,7 +92,7 @@ def test_send_message_with_bad_data_throws_vol_error( logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" ), pytest.raises(vol.Invalid) as exc: data = {"test": "test"} - signal_notification_service.send_message(MESSAGE, **{"data": data}) + signal_notification_service.send_message(MESSAGE, data=data) assert "Sending signal message" in caplog.text assert "extra keys not allowed" in str(exc.value) @@ -112,7 +112,7 @@ def test_send_message_with_attachment( ) as temp_file: temp_file.write("attachment_data") data = {"attachments": [temp_file.name]} - signal_notification_service.send_message(MESSAGE, **{"data": data}) + signal_notification_service.send_message(MESSAGE, data=data) assert "Sending signal message" in caplog.text assert signal_requests_mock.called @@ -131,7 +131,7 @@ def test_send_message_with_attachment_as_url( logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" ): data = {"urls": [URL_ATTACHMENT]} - signal_notification_service.send_message(MESSAGE, **{"data": data}) + signal_notification_service.send_message(MESSAGE, data=data) assert "Sending signal message" in caplog.text assert signal_requests_mock.called diff --git a/tests/components/switcher_kis/test_sensor.py b/tests/components/switcher_kis/test_sensor.py index 99bc0aa3a69..f61cdd5a010 100644 --- a/tests/components/switcher_kis/test_sensor.py +++ b/tests/components/switcher_kis/test_sensor.py @@ -66,9 +66,7 @@ async def test_sensor_disabled(hass: HomeAssistant, mock_bridge) -> None: assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION # Test enabling entity - updated_entry = registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} - ) + updated_entry = registry.async_update_entity(entry.entity_id, disabled_by=None) assert updated_entry != entry assert updated_entry.disabled is False diff --git a/tests/components/tomorrowio/test_sensor.py b/tests/components/tomorrowio/test_sensor.py index 0b5b989b91a..b0e2fba3123 100644 --- a/tests/components/tomorrowio/test_sensor.py +++ b/tests/components/tomorrowio/test_sensor.py @@ -105,9 +105,7 @@ def _enable_entity(hass: HomeAssistant, entity_name: str) -> None: """Enable disabled entity.""" ent_reg = async_get(hass) entry = ent_reg.async_get(entity_name) - updated_entry = ent_reg.async_update_entity( - entry.entity_id, **{"disabled_by": None} - ) + updated_entry = ent_reg.async_update_entity(entry.entity_id, disabled_by=None) assert updated_entry != entry assert updated_entry.disabled is False diff --git a/tests/components/tomorrowio/test_weather.py b/tests/components/tomorrowio/test_weather.py index dbb94dcd89b..a87c0363b7a 100644 --- a/tests/components/tomorrowio/test_weather.py +++ b/tests/components/tomorrowio/test_weather.py @@ -68,9 +68,7 @@ def _enable_entity(hass: HomeAssistant, entity_name: str) -> None: """Enable disabled entity.""" ent_reg = async_get(hass) entry = ent_reg.async_get(entity_name) - updated_entry = ent_reg.async_update_entity( - entry.entity_id, **{"disabled_by": None} - ) + updated_entry = ent_reg.async_update_entity(entry.entity_id, disabled_by=None) assert updated_entry != entry assert updated_entry.disabled is False diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index a020b4bf8e9..c5345386777 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -372,13 +372,7 @@ async def test_xiaomi_vacuum_services( "velocity": -0.1, }, "manual_control", - mock.call( - **{ - "duration": 1000, - "rotation": -40, - "velocity": -0.1, - } - ), + mock.call(duration=1000, rotation=-40, velocity=-0.1), ), ( SERVICE_STOP_REMOTE_CONTROL, @@ -397,13 +391,7 @@ async def test_xiaomi_vacuum_services( "velocity": 0.1, }, "manual_control_once", - mock.call( - **{ - "duration": 2000, - "rotation": 120, - "velocity": 0.1, - } - ), + mock.call(duration=2000, rotation=120, velocity=0.1), ), ( SERVICE_CLEAN_ZONE, diff --git a/tests/components/zwave_js/test_binary_sensor.py b/tests/components/zwave_js/test_binary_sensor.py index 61ceb82263e..3f78e23a50c 100644 --- a/tests/components/zwave_js/test_binary_sensor.py +++ b/tests/components/zwave_js/test_binary_sensor.py @@ -119,9 +119,7 @@ async def test_disabled_legacy_sensor( assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION # Test enabling legacy entity - updated_entry = registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} - ) + updated_entry = registry.async_update_entity(entry.entity_id, disabled_by=None) assert updated_entry != entry assert updated_entry.disabled is False @@ -274,7 +272,7 @@ async def test_config_parameter_binary_sensor( assert entity_entry.entity_category == EntityCategory.DIAGNOSTIC updated_entry = ent_reg.async_update_entity( - binary_sensor_entity_id, **{"disabled_by": None} + binary_sensor_entity_id, disabled_by=None ) assert updated_entry != entity_entry assert updated_entry.disabled is False diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index 0835ef01162..93402219b6c 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -341,7 +341,7 @@ async def test_get_node_status_triggers( entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg ) - entity = ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) + entity = ent_reg.async_update_entity(entity_id, disabled_by=None) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() @@ -373,7 +373,7 @@ async def test_if_node_status_change_fires( entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg ) - entity = ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) + entity = ent_reg.async_update_entity(entity_id, disabled_by=None) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() @@ -452,7 +452,7 @@ async def test_if_node_status_change_fires_legacy( entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg ) - ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) + ent_reg.async_update_entity(entity_id, disabled_by=None) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() @@ -530,7 +530,7 @@ async def test_get_trigger_capabilities_node_status( entity_id = async_get_node_status_sensor_entity_id( hass, device.id, ent_reg, dev_reg ) - ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) + ent_reg.async_update_entity(entity_id, disabled_by=None) await hass.config_entries.async_reload(integration.entry_id) await hass.async_block_till_done() diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index 2b597339e8f..fe231707629 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -153,9 +153,7 @@ async def test_merten_507801_disabled_enitites( assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION # Test enabling entity - updated_entry = registry.async_update_entity( - entry.entity_id, **{"disabled_by": None} - ) + updated_entry = registry.async_update_entity(entry.entity_id, disabled_by=None) assert updated_entry != entry assert updated_entry.disabled is False diff --git a/tests/components/zwave_js/test_number.py b/tests/components/zwave_js/test_number.py index e05346b442a..38a582762cb 100644 --- a/tests/components/zwave_js/test_number.py +++ b/tests/components/zwave_js/test_number.py @@ -232,7 +232,7 @@ async def test_config_parameter_number( assert entity_entry.entity_category == EntityCategory.CONFIG for entity_id in (number_entity_id, number_with_states_entity_id): - updated_entry = ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) + updated_entry = ent_reg.async_update_entity(entity_id, disabled_by=None) assert updated_entry != entity_entry assert updated_entry.disabled is False diff --git a/tests/components/zwave_js/test_select.py b/tests/components/zwave_js/test_select.py index ee2d14cfcba..f1a1f8796d0 100644 --- a/tests/components/zwave_js/test_select.py +++ b/tests/components/zwave_js/test_select.py @@ -308,9 +308,7 @@ async def test_config_parameter_select( assert entity_entry.disabled assert entity_entry.entity_category == EntityCategory.CONFIG - updated_entry = ent_reg.async_update_entity( - select_entity_id, **{"disabled_by": None} - ) + updated_entry = ent_reg.async_update_entity(select_entity_id, disabled_by=None) assert updated_entry != entity_entry assert updated_entry.disabled is False diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index 2ae4dc7686a..417b57aaaaa 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -222,7 +222,7 @@ async def test_disabled_notification_sensor( # Test enabling entity updated_entry = ent_reg.async_update_entity( - entity_entry.entity_id, **{"disabled_by": None} + entity_entry.entity_id, disabled_by=None ) assert updated_entry != entity_entry assert updated_entry.disabled is False @@ -278,7 +278,7 @@ async def test_config_parameter_sensor( assert entity_entry.entity_category == EntityCategory.DIAGNOSTIC for entity_id in (sensor_entity_id, sensor_with_states_entity_id): - updated_entry = ent_reg.async_update_entity(entity_id, **{"disabled_by": None}) + updated_entry = ent_reg.async_update_entity(entity_id, disabled_by=None) assert updated_entry != entity_entry assert updated_entry.disabled is False @@ -295,7 +295,7 @@ async def test_config_parameter_sensor( assert state.state == "C-Wire" updated_entry = ent_reg.async_update_entity( - entity_entry.entity_id, **{"disabled_by": None} + entity_entry.entity_id, disabled_by=None ) assert updated_entry != entity_entry assert updated_entry.disabled is False @@ -753,7 +753,7 @@ async def test_statistics_sensors_no_last_seen( assert entry.disabled assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION - ent_reg.async_update_entity(entry.entity_id, **{"disabled_by": None}) + ent_reg.async_update_entity(entry.entity_id, disabled_by=None) # reload integration and check if entity is correctly there await hass.config_entries.async_reload(integration.entry_id) diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index e9ffdef55cb..5a5ad0821eb 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -228,9 +228,7 @@ async def test_config_parameter_switch( assert entity_entry assert entity_entry.disabled - updated_entry = ent_reg.async_update_entity( - switch_entity_id, **{"disabled_by": None} - ) + updated_entry = ent_reg.async_update_entity(switch_entity_id, disabled_by=None) assert updated_entry != entity_entry assert updated_entry.disabled is False assert entity_entry.entity_category == EntityCategory.CONFIG diff --git a/tests/test_config.py b/tests/test_config.py index 13bb1519876..56e63cc40c7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1971,7 +1971,7 @@ async def test_core_store_historic_currency( assert issue assert issue.translation_placeholders == {"currency": "LTT"} - await hass.config.async_update(**{"currency": "EUR"}) + await hass.config.async_update(currency="EUR") issue = issue_registry.async_get_issue("homeassistant", issue_id) assert not issue @@ -2027,7 +2027,7 @@ async def test_core_store_no_country( issue = issue_registry.async_get_issue("homeassistant", issue_id) assert issue - await hass.config.async_update(**{"country": "SE"}) + await hass.config.async_update(country="SE") issue = issue_registry.async_get_issue("homeassistant", issue_id) assert not issue From 11c570ea7b23d750e662efef502df0a0366cd649 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:45:55 +0100 Subject: [PATCH 1173/1691] Remove ignore for ruff PLC0208 (#113537) --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c10562c0fa2..cb2539e6f24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -661,8 +661,6 @@ ignore = [ # https://github.com/astral-sh/ruff/issues/7491 # "PLC1901", # Lots of false positives - # False positives https://github.com/astral-sh/ruff/issues/5386 - "PLC0208", # Use a sequence type instead of a `set` when iterating over values "PLR0911", # Too many return statements ({returns} > {max_returns}) "PLR0912", # Too many branches ({branches} > {max_branches}) "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) From 73f11064d76cfcc220c6729e42154c91d5678861 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sat, 16 Mar 2024 23:47:59 +0100 Subject: [PATCH 1174/1691] Use `mock_platform` for button entity component tests instead of `hass.components` (#113627) --- tests/components/button/conftest.py | 50 +++++++++++++++++++ tests/components/button/const.py | 3 ++ tests/components/button/test_init.py | 19 ++----- .../custom_components/test/button.py | 47 ----------------- 4 files changed, 58 insertions(+), 61 deletions(-) create mode 100644 tests/components/button/conftest.py create mode 100644 tests/components/button/const.py delete mode 100644 tests/testing_config/custom_components/test/button.py diff --git a/tests/components/button/conftest.py b/tests/components/button/conftest.py new file mode 100644 index 00000000000..fa7b153def5 --- /dev/null +++ b/tests/components/button/conftest.py @@ -0,0 +1,50 @@ +"""Fixtures for the button entity component tests.""" +import logging + +import pytest + +from homeassistant.components.button import DOMAIN, ButtonEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import TEST_DOMAIN + +from tests.common import MockEntity, MockPlatform, mock_platform + +_LOGGER = logging.getLogger(__name__) + + +class MockButtonEntity(MockEntity, ButtonEntity): + """Mock Button class.""" + + def press(self) -> None: + """Press the button.""" + _LOGGER.info("The button has been pressed") + + +@pytest.fixture +async def setup_platform(hass: HomeAssistant) -> None: + """Set up the button entity platform.""" + + async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, + ) -> None: + """Set up test button platform.""" + async_add_entities( + [ + MockButtonEntity( + name="button 1", + unique_id="unique_button_1", + ), + ] + ) + + mock_platform( + hass, + f"{TEST_DOMAIN}.{DOMAIN}", + MockPlatform(async_setup_platform=async_setup_platform), + ) diff --git a/tests/components/button/const.py b/tests/components/button/const.py new file mode 100644 index 00000000000..9cb21e53550 --- /dev/null +++ b/tests/components/button/const.py @@ -0,0 +1,3 @@ +"""Constants for the button entity component tests.""" + +TEST_DOMAIN = "test" diff --git a/tests/components/button/test_init.py b/tests/components/button/test_init.py index 5290d145d69..caddfe93814 100644 --- a/tests/components/button/test_init.py +++ b/tests/components/button/test_init.py @@ -1,5 +1,4 @@ """The tests for the Button component.""" - from collections.abc import Generator from datetime import timedelta from unittest.mock import MagicMock @@ -26,6 +25,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util +from .const import TEST_DOMAIN + from tests.common import ( MockConfigEntry, MockModule, @@ -36,8 +37,6 @@ from tests.common import ( mock_restore_cache, ) -TEST_DOMAIN = "test" - async def test_button(hass: HomeAssistant) -> None: """Test getting data from the mocked button entity.""" @@ -60,11 +59,9 @@ async def test_custom_integration( caplog: pytest.LogCaptureFixture, enable_custom_integrations: None, freezer: FrozenDateTimeFactory, + setup_platform: None, ) -> None: """Test we integration.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -98,14 +95,11 @@ async def test_custom_integration( async def test_restore_state( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, enable_custom_integrations: None, setup_platform: None ) -> None: """Test we restore state integration.""" mock_restore_cache(hass, (State("button.button_1", "2021-01-01T23:59:59+00:00"),)) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -113,14 +107,11 @@ async def test_restore_state( async def test_restore_state_does_not_restore_unavailable( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, enable_custom_integrations: None, setup_platform: None ) -> None: """Test we restore state integration except for unavailable.""" mock_restore_cache(hass, (State("button.button_1", STATE_UNAVAILABLE),)) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/testing_config/custom_components/test/button.py b/tests/testing_config/custom_components/test/button.py deleted file mode 100644 index 8daf3a741a1..00000000000 --- a/tests/testing_config/custom_components/test/button.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Provide a mock button platform. - -Call init before using it in your tests to ensure clean test data. -""" - -import logging - -from homeassistant.components.button import ButtonEntity - -from tests.common import MockEntity - -UNIQUE_BUTTON_1 = "unique_button_1" - -ENTITIES = [] - -_LOGGER = logging.getLogger(__name__) - - -class MockButtonEntity(MockEntity, ButtonEntity): - """Mock Button class.""" - - def press(self) -> None: - """Press the button.""" - _LOGGER.info("The button has been pressed") - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - [] - if empty - else [ - MockButtonEntity( - name="button 1", - unique_id="unique_button_1", - ), - ] - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(ENTITIES) From e703baba0a644fcfd89391c8d1a6b44b0f7ad2eb Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sat, 16 Mar 2024 19:17:23 -0400 Subject: [PATCH 1175/1691] Add new fields from UniFi Protect v3 (#113631) --- .../components/unifiprotect/number.py | 14 ++++++++++ .../components/unifiprotect/select.py | 17 +++++++++++ .../components/unifiprotect/switch.py | 1 + tests/components/unifiprotect/test_number.py | 14 ++++++---- tests/components/unifiprotect/test_select.py | 28 +++++++++++-------- tests/components/unifiprotect/test_switch.py | 21 ++++++++------ 6 files changed, 70 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 89d8552f3ab..49c629ac42f 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -120,6 +120,20 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = ( ufp_set_method="set_chime_duration", ufp_perm=PermRequired.WRITE, ), + ProtectNumberEntityDescription( + key="icr_lux", + name="Infrared Custom Lux Trigger", + icon="mdi:white-balance-sunny", + entity_category=EntityCategory.CONFIG, + ufp_min=1, + ufp_max=30, + ufp_step=1, + ufp_required_field="feature_flags.has_led_ir", + ufp_value="icr_lux_display", + ufp_set_method="set_icr_custom_lux", + ufp_enabled="is_ir_led_slider_enabled", + ufp_perm=PermRequired.WRITE, + ), ) LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = ( diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index e6e0762f478..6ba90948fca 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -42,6 +42,12 @@ from .utils import async_dispatch_id as _ufpd, async_get_light_motion_current _LOGGER = logging.getLogger(__name__) _KEY_LIGHT_MOTION = "light_motion" +HDR_MODES = [ + {"id": "always", "name": "Always On"}, + {"id": "off", "name": "Always Off"}, + {"id": "auto", "name": "Auto"}, +] + INFRARED_MODES = [ {"id": IRLEDMode.AUTO.value, "name": "Auto"}, {"id": IRLEDMode.ON.value, "name": "Always Enable"}, @@ -228,6 +234,17 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = ( ufp_set_method="set_chime_type", ufp_perm=PermRequired.WRITE, ), + ProtectSelectEntityDescription( + key="hdr_mode", + name="HDR Mode", + icon="mdi:brightness-7", + entity_category=EntityCategory.CONFIG, + ufp_required_field="feature_flags.has_hdr", + ufp_options=HDR_MODES, + ufp_value="hdr_mode_display", + ufp_set_method="set_hdr_mode", + ufp_perm=PermRequired.WRITE, + ), ) LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = ( diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 148170d00c0..bd7cfa4d2a2 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -74,6 +74,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( name="HDR Mode", icon="mdi:brightness-7", entity_category=EntityCategory.CONFIG, + entity_registry_enabled_default=False, ufp_required_field="feature_flags.has_hdr", ufp_value="hdr_mode", ufp_set_method="set_hdr", diff --git a/tests/components/unifiprotect/test_number.py b/tests/components/unifiprotect/test_number.py index 15dec4b0c74..5eeb5308d62 100644 --- a/tests/components/unifiprotect/test_number.py +++ b/tests/components/unifiprotect/test_number.py @@ -6,7 +6,7 @@ from datetime import timedelta from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Doorlock, Light +from pyunifiprotect.data import Camera, Doorlock, IRLEDMode, Light from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.number import ( @@ -35,11 +35,11 @@ async def test_number_sensor_camera_remove( """Test removing and re-adding a camera device.""" await init_entry(hass, ufp, [camera, unadopted_camera]) - assert_entity_counts(hass, Platform.NUMBER, 3, 3) + assert_entity_counts(hass, Platform.NUMBER, 4, 4) await remove_entities(hass, ufp, [camera, unadopted_camera]) assert_entity_counts(hass, Platform.NUMBER, 0, 0) await adopt_devices(hass, ufp, [camera, unadopted_camera]) - assert_entity_counts(hass, Platform.NUMBER, 3, 3) + assert_entity_counts(hass, Platform.NUMBER, 4, 4) async def test_number_sensor_light_remove( @@ -99,8 +99,11 @@ async def test_number_setup_camera_all( camera.feature_flags.has_chime = True camera.chime_duration = timedelta(seconds=1) + camera.feature_flags.has_led_ir = True + camera.isp_settings.icr_custom_value = 1 + camera.isp_settings.ir_led_mode = IRLEDMode.CUSTOM await init_entry(hass, ufp, [camera]) - assert_entity_counts(hass, Platform.NUMBER, 4, 4) + assert_entity_counts(hass, Platform.NUMBER, 5, 5) entity_registry = er.async_get(hass) @@ -128,6 +131,7 @@ async def test_number_setup_camera_none( camera.feature_flags.has_mic = False # has_wdr is an the inverse of has HDR camera.feature_flags.has_hdr = True + camera.feature_flags.has_led_ir = False await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.NUMBER, 0, 0) @@ -199,7 +203,7 @@ async def test_number_camera_simple( """Tests all simple numbers for cameras.""" await init_entry(hass, ufp, [camera]) - assert_entity_counts(hass, Platform.NUMBER, 3, 3) + assert_entity_counts(hass, Platform.NUMBER, 4, 4) assert description.ufp_set_method is not None diff --git a/tests/components/unifiprotect/test_select.py b/tests/components/unifiprotect/test_select.py index 6987e526e34..7c6e449be5e 100644 --- a/tests/components/unifiprotect/test_select.py +++ b/tests/components/unifiprotect/test_select.py @@ -48,11 +48,11 @@ async def test_select_camera_remove( ufp.api.bootstrap.nvr.system_info.ustorage = None await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) await remove_entities(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.SELECT, 0, 0) await adopt_devices(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) async def test_select_light_remove( @@ -142,10 +142,16 @@ async def test_select_setup_camera_all( """Test select entity setup for camera devices (all features).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) entity_registry = er.async_get(hass) - expected_values = ("Always", "Auto", "Default Message (Welcome)", "None") + expected_values = ( + "Always", + "Auto", + "Default Message (Welcome)", + "None", + "Always Off", + ) for index, description in enumerate(CAMERA_SELECTS): unique_id, entity_id = ids_from_device_description( @@ -233,7 +239,7 @@ async def test_select_update_doorbell_settings( """Test select entity update (new Doorbell Message).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) expected_length = len(ufp.api.bootstrap.nvr.doorbell_settings.all_messages) + 1 @@ -279,7 +285,7 @@ async def test_select_update_doorbell_message( """Test select entity update (change doorbell message).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) _, entity_id = ids_from_device_description( Platform.SELECT, doorbell, CAMERA_SELECTS[2] @@ -372,7 +378,7 @@ async def test_select_set_option_camera_recording( """Test Recording Mode select.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) _, entity_id = ids_from_device_description( Platform.SELECT, doorbell, CAMERA_SELECTS[0] @@ -397,7 +403,7 @@ async def test_select_set_option_camera_ir( """Test Infrared Mode select.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) _, entity_id = ids_from_device_description( Platform.SELECT, doorbell, CAMERA_SELECTS[1] @@ -422,7 +428,7 @@ async def test_select_set_option_camera_doorbell_custom( """Test Doorbell Text select (user defined message).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) _, entity_id = ids_from_device_description( Platform.SELECT, doorbell, CAMERA_SELECTS[2] @@ -449,7 +455,7 @@ async def test_select_set_option_camera_doorbell_unifi( """Test Doorbell Text select (unifi message).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) _, entity_id = ids_from_device_description( Platform.SELECT, doorbell, CAMERA_SELECTS[2] @@ -491,7 +497,7 @@ async def test_select_set_option_camera_doorbell_default( """Test Doorbell Text select (default message).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SELECT, 4, 4) + assert_entity_counts(hass, Platform.SELECT, 5, 5) _, entity_id = ids_from_device_description( Platform.SELECT, doorbell, CAMERA_SELECTS[2] diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index 1ad3baf5db1..562eec8c5d0 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -38,13 +38,16 @@ CAMERA_SWITCHES_BASIC = [ and d.name != "SSH Enabled" and d.name != "Color Night Vision" and d.name != "Tracking: Person" + and d.name != "HDR Mode" ) or d.name == "Detections: Motion" or d.name == "Detections: Person" or d.name == "Detections: Vehicle" ] CAMERA_SWITCHES_NO_EXTRA = [ - d for d in CAMERA_SWITCHES_BASIC if d.name not in ("High FPS", "Privacy Mode") + d + for d in CAMERA_SWITCHES_BASIC + if d.name not in ("High FPS", "Privacy Mode", "HDR Mode") ] @@ -55,11 +58,11 @@ async def test_switch_camera_remove( ufp.api.bootstrap.nvr.system_info.ustorage = None await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) await remove_entities(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.SWITCH, 2, 2) await adopt_devices(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) async def test_switch_light_remove( @@ -171,7 +174,7 @@ async def test_switch_setup_camera_all( """Test switch entity setup for camera devices (all enabled feature flags).""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) entity_registry = er.async_get(hass) @@ -294,7 +297,7 @@ async def test_switch_camera_ssh( """Tests SSH switch for cameras.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) description = CAMERA_SWITCHES[0] @@ -327,7 +330,7 @@ async def test_switch_camera_simple( """Tests all simple switches for cameras.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) assert description.ufp_set_method is not None @@ -356,7 +359,7 @@ async def test_switch_camera_highfps( """Tests High FPS switch for cameras.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) description = CAMERA_SWITCHES[3] @@ -387,7 +390,7 @@ async def test_switch_camera_privacy( previous_record = doorbell.recording_settings.mode = RecordingMode.DETECTIONS await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) description = PRIVACY_MODE_SWITCH @@ -439,7 +442,7 @@ async def test_switch_camera_privacy_already_on( doorbell.add_privacy_zone() await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SWITCH, 15, 14) + assert_entity_counts(hass, Platform.SWITCH, 15, 13) description = PRIVACY_MODE_SWITCH From 44cac3c90a2210302595fd0eca12c6f609d216a7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 13:34:27 -1000 Subject: [PATCH 1176/1691] Run apple_tv shutdown eagerly at the stop event (#113637) The shutdown can be done without having to schedule a task on the event loop --- homeassistant/components/apple_tv/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/__init__.py b/homeassistant/components/apple_tv/__init__.py index cd1a1c59127..9a72a89c876 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -102,7 +102,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await manager.disconnect() entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, on_hass_stop, run_immediately=True + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) From 796f4deac2287750eac33d251d945eb7ba662ddd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 13:36:02 -1000 Subject: [PATCH 1177/1691] Run shelly coordinator shutdown immediately at the stop event (#113633) There is no need to use a call_soon here as we want to shutdown right away --- homeassistant/components/shelly/coordinator.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 22f81e21b6c..ebc92a6c6e0 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -179,7 +179,9 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]): self.async_add_listener(self._async_device_updates_handler) ) entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop, run_immediately=True + ) ) @callback @@ -408,7 +410,9 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): self._input_event_listeners: list[Callable[[dict[str, Any]], None]] = [] entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self._handle_ha_stop, run_immediately=True + ) ) entry.async_on_unload(entry.add_update_listener(self._async_update_listener)) From 6e3e2d1693e68aa82420c88e2cd164bdb4e9e4f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 13:39:31 -1000 Subject: [PATCH 1178/1691] Shutdown config entry manager immediately at the stop event (#113632) --- homeassistant/config_entries.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 4b2eb81c870..3b4787120dd 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1591,7 +1591,9 @@ class ConfigEntries: old_conf_migrate_func=_old_conf_migrator, ) - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_shutdown) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self._async_shutdown, run_immediately=True + ) if config is None: self._entries = ConfigEntryItems(self.hass) From 2f39187628416e5f152282760e2128a8b53eea22 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 13:39:48 -1000 Subject: [PATCH 1179/1691] Shutdown homekit eagerly at the stop event (#113639) --- homeassistant/components/homekit/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 901dd5ec2fb..ebbb1b9420d 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -349,7 +349,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.async_on_unload(entry.add_update_listener(_async_update_listener)) entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, homekit.async_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, homekit.async_stop, run_immediately=True + ) ) entry_data = HomeKitEntryData( From cede6af4962fcb2fc05f4054952fb2d5a3929f1b Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 17 Mar 2024 00:45:03 +0100 Subject: [PATCH 1180/1691] Consolidate ruff PL and Q rules (#113555) --- pyproject.toml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cb2539e6f24..aeff05426ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -605,17 +605,12 @@ select = [ "N815", # Variable {name} in class scope should not be mixedCase "PERF", # Perflint "PGH004", # Use specific rule codes when using noqa + "PL", # pylint "PIE804", # Unnecessary dict kwargs "PIE790", # Unnecessary pass statement "PIE794", # Class field is defined multiple times "PIE807", # Prefer list/dict over useless lambda "PIE810", # Call startswith/endswith once with a tuple - "PLC0414", # Useless import alias. Import alias does not rename original package. - "PLC", # pylint - "PLE", # pylint - "PLR", # pylint - "PLW", # pylint - "Q000", # Double quotes found but single quotes preferred "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task # "RUF100", # Unused `noqa` directive; temporarily every now and then to clean them up @@ -682,10 +677,7 @@ ignore = [ "E117", "D206", "D300", - "Q000", - "Q001", - "Q002", - "Q003", + "Q", "COM812", "COM819", "ISC001", From 513da0f71ef43185001c545e39dd0fca94eaab35 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 13:52:01 -1000 Subject: [PATCH 1181/1691] Run functions from ssdp listeners immediately (#113634) --- homeassistant/components/ssdp/__init__.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index 5e130a0fc06..75ffac772f6 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -392,7 +392,9 @@ class Scanner: await self._async_start_ssdp_listeners() - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self.async_stop, run_immediately=True + ) self._cancel_scan = async_track_time_interval( self.hass, self.async_scan, SCAN_INTERVAL, name="SSDP scanner" ) @@ -752,9 +754,13 @@ class Server: async def async_start(self) -> None: """Start the server.""" bus = self.hass.bus - bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) bus.async_listen_once( - EVENT_HOMEASSISTANT_STARTED, self._async_start_upnp_servers + EVENT_HOMEASSISTANT_STOP, self.async_stop, run_immediately=True + ) + bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, + self._async_start_upnp_servers, + run_immediately=True, ) async def _async_get_instance_udn(self) -> str: From fa68c5633cd87480432e037f45b34b7595645607 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 14:42:27 -1000 Subject: [PATCH 1182/1691] Fix zeroconf shutting down too early (#113638) --- homeassistant/components/zeroconf/__init__.py | 16 +++++++++++++--- tests/components/zeroconf/test_init.py | 6 ++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index e740b46c9eb..66c41c19474 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -24,7 +24,11 @@ from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo from homeassistant import config_entries from homeassistant.components import network -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, __version__ +from homeassistant.const import ( + EVENT_HOMEASSISTANT_CLOSE, + EVENT_HOMEASSISTANT_STOP, + __version__, +) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.data_entry_flow import BaseServiceInfo from homeassistant.helpers import discovery_flow, instance_id @@ -163,7 +167,11 @@ async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaAsyncZero """Stop Zeroconf.""" await aio_zc.ha_async_close() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_zeroconf) + # Wait to the close event to shutdown zeroconf to give + # integrations time to send a good bye message + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_CLOSE, _async_stop_zeroconf, run_immediately=True + ) hass.data[DOMAIN] = aio_zc return aio_zc @@ -240,7 +248,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def _async_zeroconf_hass_stop(_event: Event) -> None: await discovery.async_stop() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_zeroconf_hass_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_zeroconf_hass_stop, run_immediately=True + ) async_when_setup_or_start(hass, "frontend", _async_zeroconf_hass_start) return True diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index b156690d290..da817c84448 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -15,6 +15,7 @@ from zeroconf.asyncio import AsyncServiceInfo from homeassistant.components import zeroconf from homeassistant.const import ( EVENT_COMPONENT_LOADED, + EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, @@ -935,6 +936,11 @@ async def test_get_instance(hass: HomeAssistant, mock_async_zeroconf: None) -> N ) hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() + assert len(mock_async_zeroconf.ha_async_close.mock_calls) == 0 + # Only shutdown at the close event so integrations have time + # to send out their goodbyes + hass.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE) + await hass.async_block_till_done() assert len(mock_async_zeroconf.ha_async_close.mock_calls) == 1 From 91fa612301259ed6069004a0b72b47ae93d17095 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 14:42:36 -1000 Subject: [PATCH 1183/1691] Run entity component shutdown immediately (#113635) --- homeassistant/helpers/entity_component.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index fe2d78c02ed..dc9093b362b 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -120,7 +120,9 @@ class EntityComponent(Generic[_EntityT]): Note: this is only required if the integration never calls `setup` or `async_setup`. """ - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._async_shutdown) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self._async_shutdown, run_immediately=True + ) def setup(self, config: ConfigType) -> None: """Set up a full entity component. From 6a6f3d46a9248f4eabe8c982a10124849f29cf41 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 14:43:49 -1000 Subject: [PATCH 1184/1691] Create config entry async_on_unload tasks eagerly (#113626) --- homeassistant/config_entries.py | 2 +- tests/components/pi_hole/test_config_flow.py | 1 + tests/test_config_entries.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 3b4787120dd..cb854bcf0ad 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -857,7 +857,7 @@ class ConfigEntry: if self._on_unload is not None: while self._on_unload: if job := self._on_unload.pop()(): - self.async_create_task(hass, job) + self.async_create_task(hass, job, eager_start=True) if not self._tasks and not self._background_tasks: return diff --git a/tests/components/pi_hole/test_config_flow.py b/tests/components/pi_hole/test_config_flow.py index 0ab8b53744c..350b8b899d8 100644 --- a/tests/components/pi_hole/test_config_flow.py +++ b/tests/components/pi_hole/test_config_flow.py @@ -113,6 +113,7 @@ async def test_flow_reauth(hass: HomeAssistant) -> None: entry.add_to_hass(hass) with _patch_init_hole(mocked_hole), _patch_config_flow_hole(mocked_hole): assert not await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 5ce49308725..9e6e93cfe9e 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -4334,10 +4334,10 @@ async def test_task_tracking(hass: HomeAssistant) -> None: hass.loop.call_soon(event.set) await entry._async_process_on_unload(hass) assert results == [ - "on_unload", "background", "background", "normal", + "on_unload", ] From 26c1b7e72e880982595cee6f0079f82c5fe0ce92 Mon Sep 17 00:00:00 2001 From: luar123 <49960470+luar123@users.noreply.github.com> Date: Sun, 17 Mar 2024 02:16:26 +0100 Subject: [PATCH 1185/1691] Bump snapcast to 2.3.6 (#113606) --- homeassistant/components/snapcast/manifest.json | 2 +- homeassistant/components/snapcast/server.py | 6 +++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json index f59283bb5f6..ea40f45d8cc 100644 --- a/homeassistant/components/snapcast/manifest.json +++ b/homeassistant/components/snapcast/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/snapcast", "iot_class": "local_push", "loggers": ["construct", "snapcast"], - "requirements": ["snapcast==2.3.3"] + "requirements": ["snapcast==2.3.6"] } diff --git a/homeassistant/components/snapcast/server.py b/homeassistant/components/snapcast/server.py index 761a8aa4712..4714156c4c2 100644 --- a/homeassistant/components/snapcast/server.py +++ b/homeassistant/components/snapcast/server.py @@ -66,7 +66,7 @@ class HomeAssistantSnapcast: self.server.set_on_connect_callback(None) self.server.set_on_disconnect_callback(None) self.server.set_new_client_callback(None) - await self.server.stop() + self.server.stop() def on_update(self) -> None: """Update all entities. @@ -100,8 +100,8 @@ class HomeAssistantSnapcast: ] del_entities.extend([x for x in self.clients if x not in clients]) - _LOGGER.debug("New clients: %s", str(new_clients)) - _LOGGER.debug("New groups: %s", str(new_groups)) + _LOGGER.debug("New clients: %s", str([c.name for c in new_clients])) + _LOGGER.debug("New groups: %s", str([g.name for g in new_groups])) _LOGGER.debug("Delete: %s", str(del_entities)) ent_reg = er.async_get(self.hass) diff --git a/requirements_all.txt b/requirements_all.txt index cc084afa0b7..fb9fe8b6fb1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2556,7 +2556,7 @@ smart-meter-texas==0.4.7 smhi-pkg==1.0.16 # homeassistant.components.snapcast -snapcast==2.3.3 +snapcast==2.3.6 # homeassistant.components.sonos soco==0.30.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 84ffcf7d15f..fd47e4d1431 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1960,7 +1960,7 @@ smart-meter-texas==0.4.7 smhi-pkg==1.0.16 # homeassistant.components.snapcast -snapcast==2.3.3 +snapcast==2.3.6 # homeassistant.components.sonos soco==0.30.2 From 885abe2fda712c23b4c74d0bebe0b287269e38a7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 16:10:57 -1000 Subject: [PATCH 1186/1691] Cleanup sonos shutdown process (#113654) --- homeassistant/components/sonos/__init__.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 48c4a614ca4..028c412cd75 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -35,6 +35,7 @@ from homeassistant.helpers import ( from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_call_later, async_track_time_interval from homeassistant.helpers.typing import ConfigType +from homeassistant.util.async_ import create_eager_task from .alarms import SonosAlarms from .const import ( @@ -320,11 +321,15 @@ class SonosDiscoveryManager: zgs.total_requests, ) await asyncio.gather( - *(speaker.async_offline() for speaker in self.data.discovered.values()) + *( + create_eager_task(speaker.async_offline()) + for speaker in self.data.discovered.values() + ) ) if events_asyncio.event_listener: await events_asyncio.event_listener.async_stop() + @callback def _stop_manual_heartbeat(self, event: Event | None = None) -> None: if self.data.hosts_heartbeat: self.data.hosts_heartbeat() @@ -569,14 +574,18 @@ class SonosDiscoveryManager: await self.hass.config_entries.async_forward_entry_setups(self.entry, PLATFORMS) self.entry.async_on_unload( self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, self._async_stop_event_listener + EVENT_HOMEASSISTANT_STOP, + self._async_stop_event_listener, + run_immediately=True, ) ) _LOGGER.debug("Adding discovery job") if self.hosts: self.entry.async_on_unload( self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, self._stop_manual_heartbeat + EVENT_HOMEASSISTANT_STOP, + self._stop_manual_heartbeat, + run_immediately=True, ) ) await self.async_poll_manual_hosts() From 0a26829ffce89ee93aa04b6456e0b099c8f09204 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sat, 16 Mar 2024 22:15:18 -0400 Subject: [PATCH 1187/1691] Bump pyunifiprotect to 5.0.2 (#113651) --- homeassistant/components/unifiprotect/data.py | 27 ++----------------- .../components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 0e11c28e09d..2a4f83b668e 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -20,6 +20,7 @@ from pyunifiprotect.data import ( WSSubscriptionMessage, ) from pyunifiprotect.exceptions import ClientError, NotAuthorized +from pyunifiprotect.utils import log_event from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback @@ -42,11 +43,6 @@ from .utils import async_dispatch_id as _ufpd, async_get_devices_by_type _LOGGER = logging.getLogger(__name__) ProtectDeviceType = ProtectAdoptableDeviceModel | NVR -SMART_EVENTS = { - EventType.SMART_DETECT, - EventType.SMART_AUDIO_DETECT, - EventType.SMART_DETECT_LINE, -} @callback @@ -231,26 +227,7 @@ class ProtectData: # trigger updates for camera that the event references elif isinstance(obj, Event): # type: ignore[unreachable] if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug("event WS msg: %s", obj.dict()) - if obj.type in SMART_EVENTS: - if obj.camera is not None: - if obj.end is None: - _LOGGER.debug( - "%s (%s): New smart detection started for %s (%s)", - obj.camera.name, - obj.camera.mac, - obj.smart_detect_types, - obj.id, - ) - else: - _LOGGER.debug( - "%s (%s): Smart detection ended for %s (%s)", - obj.camera.name, - obj.camera.mac, - obj.smart_detect_types, - obj.id, - ) - + log_event(obj) if obj.type is EventType.DEVICE_ADOPTED: if obj.metadata is not None and obj.metadata.device_id is not None: device = self.api.bootstrap.get_device_from_id( diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index ab4dfb67743..7cfb0ddcc9e 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -41,7 +41,7 @@ "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], "quality_scale": "platinum", - "requirements": ["pyunifiprotect==5.0.1", "unifi-discovery==1.1.8"], + "requirements": ["pyunifiprotect==5.0.2", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index fb9fe8b6fb1..62fbfc4d4aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2333,7 +2333,7 @@ pytrydan==0.4.0 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==5.0.1 +pyunifiprotect==5.0.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd47e4d1431..1cb95ab2b9e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1800,7 +1800,7 @@ pytrydan==0.4.0 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==5.0.1 +pyunifiprotect==5.0.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From 43652a4ace733465fbbe9448ce324851db9dd04b Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sat, 16 Mar 2024 22:15:32 -0400 Subject: [PATCH 1188/1691] Deprecate UniFi Protect HDR switch and package sensor (#113636) Co-authored-by: J. Nick Koston --- .../components/unifiprotect/binary_sensor.py | 1 + .../components/unifiprotect/migrate.py | 236 ++++++----- .../components/unifiprotect/strings.json | 8 + .../unifiprotect/fixtures/sample_nvr.json | 4 +- tests/components/unifiprotect/test_migrate.py | 371 +++++++++--------- tests/components/unifiprotect/test_repairs.py | 6 +- 6 files changed, 305 insertions(+), 321 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 66c1a0a4e33..f779fc7a1ad 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -458,6 +458,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( name="Package Detected", icon="mdi:package-variant-closed", ufp_value="is_package_currently_detected", + entity_registry_enabled_default=False, ufp_required_field="can_detect_package", ufp_enabled="is_package_detection_on", ufp_event_obj="last_package_detect_event", diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index 601220bc4f0..1fbf8bab8e2 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -2,19 +2,103 @@ from __future__ import annotations +from itertools import chain import logging from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import NVR, Bootstrap, ProtectAdoptableDeviceModel +from pyunifiprotect.data import Bootstrap +from typing_extensions import TypedDict +from homeassistant.components.automation import automations_with_entity +from homeassistant.components.script import scripts_with_entity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er, issue_registry as ir +from homeassistant.helpers.issue_registry import IssueSeverity + +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +class EntityRef(TypedDict): + """Entity ref parameter variable.""" + + id: str + platform: Platform + + +class EntityUsage(TypedDict): + """Entity usages response variable.""" + + automations: dict[str, list[str]] + scripts: dict[str, list[str]] + + +@callback +def check_if_used( + hass: HomeAssistant, entry: ConfigEntry, entities: dict[str, EntityRef] +) -> dict[str, EntityUsage]: + """Check for usages of entities and return them.""" + + entity_registry = er.async_get(hass) + refs: dict[str, EntityUsage] = { + ref: {"automations": {}, "scripts": {}} for ref in entities + } + + for entity in er.async_entries_for_config_entry(entity_registry, entry.entry_id): + for ref_id, ref in entities.items(): + if ( + entity.domain == ref["platform"] + and entity.disabled_by is None + and ref["id"] in entity.unique_id + ): + entity_automations = automations_with_entity(hass, entity.entity_id) + entity_scripts = scripts_with_entity(hass, entity.entity_id) + if entity_automations: + refs[ref_id]["automations"][entity.entity_id] = entity_automations + if entity_scripts: + refs[ref_id]["scripts"][entity.entity_id] = entity_scripts + + return refs + + +@callback +def create_repair_if_used( + hass: HomeAssistant, + entry: ConfigEntry, + breaks_in: str, + entities: dict[str, EntityRef], +) -> None: + """Create repairs for used entities that are deprecated.""" + + usages = check_if_used(hass, entry, entities) + for ref_id, refs in usages.items(): + issue_id = f"deprecate_{ref_id}" + automations = refs["automations"] + scripts = refs["scripts"] + if automations or scripts: + items = sorted( + set(chain.from_iterable(chain(automations.values(), scripts.values()))) + ) + ir.async_create_issue( + hass, + DOMAIN, + issue_id, + is_fixable=False, + breaks_in_ha_version=breaks_in, + severity=IssueSeverity.WARNING, + translation_key=issue_id, + translation_placeholders={ + "items": "* `" + "`\n* `".join(items) + "`\n" + }, + ) + else: + _LOGGER.debug("No found usages of %s", ref_id) + ir.async_delete_issue(hass, DOMAIN, issue_id) + + async def async_migrate_data( hass: HomeAssistant, entry: ConfigEntry, @@ -23,132 +107,32 @@ async def async_migrate_data( ) -> None: """Run all valid UniFi Protect data migrations.""" - _LOGGER.debug("Start Migrate: async_migrate_buttons") - await async_migrate_buttons(hass, entry, protect, bootstrap) - _LOGGER.debug("Completed Migrate: async_migrate_buttons") - - _LOGGER.debug("Start Migrate: async_migrate_device_ids") - await async_migrate_device_ids(hass, entry, protect, bootstrap) - _LOGGER.debug("Completed Migrate: async_migrate_device_ids") + _LOGGER.debug("Start Migrate: async_deprecate_hdr_package") + async_deprecate_hdr_package(hass, entry) + _LOGGER.debug("Completed Migrate: async_deprecate_hdr_package") -async def async_migrate_buttons( - hass: HomeAssistant, - entry: ConfigEntry, - protect: ProtectApiClient, - bootstrap: Bootstrap, -) -> None: - """Migrate existing Reboot button unique IDs from {device_id} to {deivce_id}_reboot. +@callback +def async_deprecate_hdr_package(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Check for usages of hdr_mode switch and package sensor and raise repair if it is used. - This allows for additional types of buttons that are outside of just a reboot button. + UniFi Protect v3.0.22 changed how HDR works so it is no longer a simple on/off toggle. There is + Always On, Always Off and Auto. So it has been migrated to a select. The old switch is now deprecated. - Added in 2022.6.0. + Additionally, the Package sensor is no longer functional due to how events work so a repair to notify users. + + Added in 2024.4.0 """ - registry = er.async_get(hass) - to_migrate = [] - for entity in er.async_entries_for_config_entry(registry, entry.entry_id): - if entity.domain == Platform.BUTTON and "_" not in entity.unique_id: - _LOGGER.debug("Button %s needs migration", entity.entity_id) - to_migrate.append(entity) - - if len(to_migrate) == 0: - _LOGGER.debug("No button entities need migration") - return - - count = 0 - for button in to_migrate: - device = bootstrap.get_device_from_id(button.unique_id) - if device is None: - continue - - new_unique_id = f"{device.id}_reboot" - _LOGGER.debug( - "Migrating entity %s (old unique_id: %s, new unique_id: %s)", - button.entity_id, - button.unique_id, - new_unique_id, - ) - try: - registry.async_update_entity(button.entity_id, new_unique_id=new_unique_id) - except ValueError: - _LOGGER.warning( - "Could not migrate entity %s (old unique_id: %s, new unique_id: %s)", - button.entity_id, - button.unique_id, - new_unique_id, - ) - else: - count += 1 - - if count < len(to_migrate): - _LOGGER.warning("Failed to migate %s reboot buttons", len(to_migrate) - count) - - -async def async_migrate_device_ids( - hass: HomeAssistant, - entry: ConfigEntry, - protect: ProtectApiClient, - bootstrap: Bootstrap, -) -> None: - """Migrate unique IDs from {device_id}_{name} format to {mac}_{name} format. - - This makes devices persist better with in HA. Anything a device is unadopted/readopted or - the Protect instance has to rebuild the disk array, the device IDs of Protect devices - can change. This causes a ton of orphaned entities and loss of historical data. MAC - addresses are the one persistent identifier a device has that does not change. - - Added in 2022.7.0. - """ - - registry = er.async_get(hass) - to_migrate = [] - for entity in er.async_entries_for_config_entry(registry, entry.entry_id): - parts = entity.unique_id.split("_") - # device ID = 24 characters, MAC = 12 - if len(parts[0]) == 24: - _LOGGER.debug("Entity %s needs migration", entity.entity_id) - to_migrate.append(entity) - - if len(to_migrate) == 0: - _LOGGER.debug("No entities need migration to MAC address ID") - return - - count = 0 - for entity in to_migrate: - parts = entity.unique_id.split("_") - if parts[0] == bootstrap.nvr.id: - device: NVR | ProtectAdoptableDeviceModel | None = bootstrap.nvr - else: - device = bootstrap.get_device_from_id(parts[0]) - - if device is None: - continue - - new_unique_id = device.mac - if len(parts) > 1: - new_unique_id = f"{device.mac}_{'_'.join(parts[1:])}" - _LOGGER.debug( - "Migrating entity %s (old unique_id: %s, new unique_id: %s)", - entity.entity_id, - entity.unique_id, - new_unique_id, - ) - try: - registry.async_update_entity(entity.entity_id, new_unique_id=new_unique_id) - except ValueError as err: - _LOGGER.warning( - ( - "Could not migrate entity %s (old unique_id: %s, new unique_id:" - " %s): %s" - ), - entity.entity_id, - entity.unique_id, - new_unique_id, - err, - ) - else: - count += 1 - - if count < len(to_migrate): - _LOGGER.warning("Failed to migrate %s entities", len(to_migrate) - count) + create_repair_if_used( + hass, + entry, + "2024.10.0", + { + "hdr_switch": {"id": "hdr_mode", "platform": Platform.SWITCH}, + "package_sensor": { + "id": "smart_obj_package", + "platform": Platform.BINARY_SENSOR, + }, + }, + ) diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index b783bdf1a2c..0b01c8f220c 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -90,6 +90,14 @@ } } } + }, + "deprecate_hdr_switch": { + "title": "HDR Mode Switch Deprecated", + "description": "UniFi Protect v3 added a new state for HDR (auto). As a result, the HDR Mode Switch has been replaced with an HDR Mode Select, and it is deprecated.\n\nBelow are the detected automations or scripts that use one or more of the deprecated entities:\n{items}\nThe above list may be incomplete and it does not include any template usages inside of dashboards. Please update any templates, automations or scripts accordingly." + }, + "deprecate_package_sensor": { + "title": "Package Event Sensor Deprecated", + "description": "The package event sensor never tripped because of the way events are reported in UniFi Protect. As a result, the sensor is deprecated and will be removed.\n\nBelow are the detected automations or scripts that use one or more of the deprecated entities:\n{items}\nThe above list may be incomplete and it does not include any template usages inside of dashboards. Please update any templates, automations or scripts accordingly." } }, "entity": { diff --git a/tests/components/unifiprotect/fixtures/sample_nvr.json b/tests/components/unifiprotect/fixtures/sample_nvr.json index 8777e3ce945..13e93a8c2e7 100644 --- a/tests/components/unifiprotect/fixtures/sample_nvr.json +++ b/tests/components/unifiprotect/fixtures/sample_nvr.json @@ -5,7 +5,7 @@ "canAutoUpdate": true, "isStatsGatheringEnabled": true, "timezone": "America/New_York", - "version": "1.21.0-beta.2", + "version": "2.2.6", "ucoreVersion": "2.3.26", "firmwareVersion": "2.3.10", "uiVersion": null, @@ -40,7 +40,7 @@ "enableStatsReporting": false, "isSshEnabled": false, "errorCode": null, - "releaseChannel": "beta", + "releaseChannel": "release", "ssoChannel": null, "hosts": ["192.168.216.198"], "enableBridgeAutoAdoption": true, diff --git a/tests/components/unifiprotect/test_migrate.py b/tests/components/unifiprotect/test_migrate.py index 88d66c819bc..7e736c39e6a 100644 --- a/tests/components/unifiprotect/test_migrate.py +++ b/tests/components/unifiprotect/test_migrate.py @@ -2,238 +2,225 @@ from __future__ import annotations -from unittest.mock import AsyncMock +from unittest.mock import patch -from pyunifiprotect.data import Light -from pyunifiprotect.exceptions import NvrError +from pyunifiprotect.data import Camera +from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN +from homeassistant.components.repairs.issue_handler import ( + async_process_repairs_platforms, +) +from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN from homeassistant.components.unifiprotect.const import DOMAIN -from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import Platform +from homeassistant.const import SERVICE_RELOAD, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component -from .utils import ( - MockUFPFixture, - generate_random_ids, - init_entry, - regenerate_device_ids, -) +from .utils import MockUFPFixture, init_entry + +from tests.typing import WebSocketGenerator -async def test_migrate_reboot_button( - hass: HomeAssistant, ufp: MockUFPFixture, light: Light -) -> None: - """Test migrating unique ID of reboot button.""" +async def test_deprecated_entity( + hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera +): + """Test Deprecate entity repair does not exist by default (new installs).""" - light1 = light.copy() - light1.name = "Test Light 1" - regenerate_device_ids(light1) + await init_entry(hass, ufp, [doorbell]) - light2 = light.copy() - light2.name = "Test Light 2" - regenerate_device_ids(light2) + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_hdr_switch": + issue = i + assert issue is None + + +async def test_deprecated_entity_no_automations( + hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera +): + """Test Deprecate entity repair exists for existing installs.""" registry = er.async_get(hass) registry.async_get_or_create( - Platform.BUTTON, DOMAIN, light1.id, config_entry=ufp.entry - ) - registry.async_get_or_create( - Platform.BUTTON, + Platform.SWITCH, DOMAIN, - f"{light2.mac}_reboot", + f"{doorbell.mac}_hdr_mode", config_entry=ufp.entry, ) - ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) - await init_entry(hass, ufp, [light1, light2], regenerate_ids=False) + await init_entry(hass, ufp, [doorbell]) - assert ufp.entry.state == ConfigEntryState.LOADED - assert ufp.api.update.called - assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) - buttons = [ - entity - for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id) - if entity.domain == Platform.BUTTON.value - ] - assert len(buttons) == 4 + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() - assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device") is None - assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device_2") is None - light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_{light1.id.lower()}") - assert light is not None - assert light.unique_id == f"{light1.mac}_reboot" + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_hdr_switch": + issue = i + assert issue is None - assert registry.async_get(f"{Platform.BUTTON}.test_light_2_reboot_device") is None - assert registry.async_get(f"{Platform.BUTTON}.test_light_2_reboot_device_2") is None - light = registry.async_get( - f"{Platform.BUTTON}.unifiprotect_{light2.mac.lower()}_reboot" + +async def _load_automation(hass: HomeAssistant, entity_id: str): + assert await async_setup_component( + hass, + AUTOMATION_DOMAIN, + { + AUTOMATION_DOMAIN: [ + { + "alias": "test1", + "trigger": [ + {"platform": "state", "entity_id": entity_id}, + { + "platform": "event", + "event_type": "state_changed", + "event_data": {"entity_id": entity_id}, + }, + ], + "condition": { + "condition": "state", + "entity_id": entity_id, + "state": "on", + }, + "action": [ + { + "service": "test.script", + "data": {"entity_id": entity_id}, + }, + ], + }, + ] + }, ) - assert light is not None - assert light.unique_id == f"{light2.mac}_reboot" -async def test_migrate_nvr_mac( - hass: HomeAssistant, ufp: MockUFPFixture, light: Light +async def test_deprecate_entity_automation( + hass: HomeAssistant, + ufp: MockUFPFixture, + hass_ws_client: WebSocketGenerator, + doorbell: Camera, ) -> None: - """Test migrating unique ID of NVR to use MAC address.""" - - light1 = light.copy() - light1.name = "Test Light 1" - regenerate_device_ids(light1) - - light2 = light.copy() - light2.name = "Test Light 2" - regenerate_device_ids(light2) - - nvr = ufp.api.bootstrap.nvr - regenerate_device_ids(nvr) - registry = er.async_get(hass) - registry.async_get_or_create( - Platform.SENSOR, - DOMAIN, - f"{nvr.id}_storage_utilization", - config_entry=ufp.entry, - ) - - ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) - await init_entry(hass, ufp, [light1, light2], regenerate_ids=False) - - assert ufp.entry.state == ConfigEntryState.LOADED - assert ufp.api.update.called - assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - - assert registry.async_get(f"{Platform.SENSOR}.{DOMAIN}_storage_utilization") is None - assert ( - registry.async_get(f"{Platform.SENSOR}.{DOMAIN}_storage_utilization_2") is None - ) - sensor = registry.async_get( - f"{Platform.SENSOR}.{DOMAIN}_{nvr.id}_storage_utilization" - ) - assert sensor is not None - assert sensor.unique_id == f"{nvr.mac}_storage_utilization" - - -async def test_migrate_reboot_button_no_device( - hass: HomeAssistant, ufp: MockUFPFixture, light: Light -) -> None: - """Test migrating unique ID of reboot button if UniFi Protect device ID changed.""" - - light2_id, _ = generate_random_ids() + """Test Deprecate entity repair exists for existing installs.""" registry = er.async_get(hass) - registry.async_get_or_create( - Platform.BUTTON, DOMAIN, light2_id, config_entry=ufp.entry + entry = registry.async_get_or_create( + Platform.SWITCH, + DOMAIN, + f"{doorbell.mac}_hdr_mode", + config_entry=ufp.entry, + ) + await _load_automation(hass, entry.entity_id) + await init_entry(hass, ufp, [doorbell]) + + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_hdr_switch": + issue = i + assert issue is not None + + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={AUTOMATION_DOMAIN: []}, + ): + await hass.services.async_call(AUTOMATION_DOMAIN, SERVICE_RELOAD, blocking=True) + + await hass.config_entries.async_reload(ufp.entry.entry_id) + await hass.async_block_till_done() + + await ws_client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_hdr_switch": + issue = i + assert issue is None + + +async def _load_script(hass: HomeAssistant, entity_id: str): + assert await async_setup_component( + hass, + SCRIPT_DOMAIN, + { + SCRIPT_DOMAIN: { + "test": { + "sequence": { + "service": "test.script", + "data": {"entity_id": entity_id}, + } + } + }, + }, ) - ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) - await init_entry(hass, ufp, [light], regenerate_ids=False) - assert ufp.entry.state == ConfigEntryState.LOADED - assert ufp.api.update.called - assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - - buttons = [ - entity - for entity in er.async_entries_for_config_entry(registry, ufp.entry.entry_id) - if entity.domain == Platform.BUTTON.value - ] - assert len(buttons) == 3 - - entity = registry.async_get(f"{Platform.BUTTON}.unifiprotect_{light2_id.lower()}") - assert entity is not None - assert entity.unique_id == light2_id - - -async def test_migrate_reboot_button_fail( - hass: HomeAssistant, ufp: MockUFPFixture, light: Light +async def test_deprecate_entity_script( + hass: HomeAssistant, + ufp: MockUFPFixture, + hass_ws_client: WebSocketGenerator, + doorbell: Camera, ) -> None: - """Test migrating unique ID of reboot button.""" + """Test Deprecate entity repair exists for existing installs.""" registry = er.async_get(hass) - registry.async_get_or_create( - Platform.BUTTON, + entry = registry.async_get_or_create( + Platform.SWITCH, DOMAIN, - light.id, + f"{doorbell.mac}_hdr_mode", config_entry=ufp.entry, - suggested_object_id=light.display_name, - ) - registry.async_get_or_create( - Platform.BUTTON, - DOMAIN, - f"{light.id}_reboot", - config_entry=ufp.entry, - suggested_object_id=light.display_name, ) + await _load_script(hass, entry.entity_id) + await init_entry(hass, ufp, [doorbell]) - ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) - await init_entry(hass, ufp, [light], regenerate_ids=False) + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) - assert ufp.entry.state == ConfigEntryState.LOADED - assert ufp.api.update.called - assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() - entity = registry.async_get(f"{Platform.BUTTON}.test_light") - assert entity is not None - assert entity.unique_id == f"{light.mac}" + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_hdr_switch": + issue = i + assert issue is not None + with patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value={SCRIPT_DOMAIN: {}}, + ): + await hass.services.async_call(SCRIPT_DOMAIN, SERVICE_RELOAD, blocking=True) -async def test_migrate_device_mac_button_fail( - hass: HomeAssistant, ufp: MockUFPFixture, light: Light -) -> None: - """Test migrating unique ID to MAC format.""" + await hass.config_entries.async_reload(ufp.entry.entry_id) + await hass.async_block_till_done() - registry = er.async_get(hass) - registry.async_get_or_create( - Platform.BUTTON, - DOMAIN, - f"{light.id}_reboot", - config_entry=ufp.entry, - suggested_object_id=light.display_name, - ) - registry.async_get_or_create( - Platform.BUTTON, - DOMAIN, - f"{light.mac}_reboot", - config_entry=ufp.entry, - suggested_object_id=light.display_name, - ) + await ws_client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() - ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap) - await init_entry(hass, ufp, [light], regenerate_ids=False) - - assert ufp.entry.state == ConfigEntryState.LOADED - assert ufp.api.update.called - assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac - - entity = registry.async_get(f"{Platform.BUTTON}.test_light") - assert entity is not None - assert entity.unique_id == f"{light.id}_reboot" - - -async def test_migrate_device_mac_bootstrap_fail( - hass: HomeAssistant, ufp: MockUFPFixture, light: Light -) -> None: - """Test migrating with a network error.""" - - registry = er.async_get(hass) - registry.async_get_or_create( - Platform.BUTTON, - DOMAIN, - f"{light.id}_reboot", - config_entry=ufp.entry, - suggested_object_id=light.name, - ) - registry.async_get_or_create( - Platform.BUTTON, - DOMAIN, - f"{light.mac}_reboot", - config_entry=ufp.entry, - suggested_object_id=light.name, - ) - - ufp.api.get_bootstrap = AsyncMock(side_effect=NvrError) - await init_entry(hass, ufp, [light], regenerate_ids=False) - - assert ufp.entry.state == ConfigEntryState.SETUP_RETRY + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_hdr_switch": + issue = i + assert issue is None diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index b75b025be11..6ec0b3fe6ca 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -32,6 +32,8 @@ async def test_ea_warning_ignore( ) -> None: """Test EA warning is created if using prerelease version of Protect.""" + ufp.api.bootstrap.nvr.release_channel = "beta" + ufp.api.bootstrap.nvr.version = Version("1.21.0-beta.2") version = ufp.api.bootstrap.nvr.version assert version.is_prerelease await init_entry(hass, ufp, []) @@ -92,6 +94,8 @@ async def test_ea_warning_fix( ) -> None: """Test EA warning is created if using prerelease version of Protect.""" + ufp.api.bootstrap.nvr.release_channel = "beta" + ufp.api.bootstrap.nvr.version = Version("1.21.0-beta.2") version = ufp.api.bootstrap.nvr.version assert version.is_prerelease await init_entry(hass, ufp, []) @@ -125,8 +129,8 @@ async def test_ea_warning_fix( assert data["step_id"] == "start" new_nvr = copy(ufp.api.bootstrap.nvr) - new_nvr.version = Version("2.2.6") new_nvr.release_channel = "release" + new_nvr.version = Version("2.2.6") mock_msg = Mock() mock_msg.changed_data = {"version": "2.2.6", "releaseChannel": "release"} mock_msg.new_obj = new_nvr From 69564b1a17775a63020fda5c71a47c27fba31aaf Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 17 Mar 2024 03:31:30 +0100 Subject: [PATCH 1189/1691] Add ruff rule PIE800 (#113619) Co-authored-by: J. Nick Koston --- .../components/accuweather/__init__.py | 2 +- homeassistant/components/anova/__init__.py | 2 +- .../components/gogogate2/__init__.py | 2 +- homeassistant/components/isy994/const.py | 14 +++---- homeassistant/components/risco/config_flow.py | 4 +- homeassistant/components/stream/worker.py | 28 ++++++------- homeassistant/components/vera/config_flow.py | 10 ++--- homeassistant/components/zha/websocket_api.py | 2 +- pyproject.toml | 1 + tests/components/cast/test_media_player.py | 2 +- tests/components/energy/test_sensor.py | 14 +++---- tests/components/risco/test_config_flow.py | 3 +- tests/components/roborock/test_vacuum.py | 2 +- tests/components/sensor/test_recorder.py | 40 +++++++++---------- tests/components/vera/test_init.py | 2 +- 15 files changed, 63 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index 1f7e72681e6..25cff4ed4fc 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -135,4 +135,4 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): ) as error: raise UpdateFailed(error) from error _LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining) - return {**current, **{ATTR_FORECAST: forecast}} + return {**current, ATTR_FORECAST: forecast} diff --git a/homeassistant/components/anova/__init__.py b/homeassistant/components/anova/__init__.py index 653b556089c..9b0f649dad9 100644 --- a/homeassistant/components/anova/__init__.py +++ b/homeassistant/components/anova/__init__.py @@ -56,7 +56,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry, data={ **entry.data, - **{CONF_DEVICES: serialize_device_list(devices)}, + CONF_DEVICES: serialize_device_list(devices), }, ) coordinators = [AnovaCoordinator(hass, device) for device in devices] diff --git a/homeassistant/components/gogogate2/__init__.py b/homeassistant/components/gogogate2/__init__.py index ece2f6bbbc8..ceb07c99849 100644 --- a/homeassistant/components/gogogate2/__init__.py +++ b/homeassistant/components/gogogate2/__init__.py @@ -18,7 +18,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if CONF_DEVICE not in entry.data: config_updates = { **entry.data, - **{CONF_DEVICE: DEVICE_TYPE_GOGOGATE2}, + CONF_DEVICE: DEVICE_TYPE_GOGOGATE2, } if config_updates: diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index daa474b736b..85ecafd6490 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -596,14 +596,12 @@ UOM_TO_STATES = { 4: "highly polluted", }, UOM_BARRIER: { # Barrier Status - **{ - 0: STATE_CLOSED, - 100: STATE_OPEN, - 101: STATE_UNKNOWN, - 102: "stopped", - 103: STATE_CLOSING, - 104: STATE_OPENING, - }, + 0: STATE_CLOSED, + 100: STATE_OPEN, + 101: STATE_UNKNOWN, + 102: "stopped", + 103: STATE_CLOSING, + 104: STATE_OPENING, **{ b: f"{b} %" for a, b in enumerate(list(range(1, 100))) }, # 1-99 are percentage open diff --git a/homeassistant/components/risco/config_flow.py b/homeassistant/components/risco/config_flow.py index bd703ca5a6d..0f13721856c 100644 --- a/homeassistant/components/risco/config_flow.py +++ b/homeassistant/components/risco/config_flow.py @@ -206,8 +206,8 @@ class RiscoConfigFlow(ConfigFlow, domain=DOMAIN): title=info["title"], data={ **user_input, - **{CONF_TYPE: TYPE_LOCAL}, - **{CONF_COMMUNICATION_DELAY: info["comm_delay"]}, + CONF_TYPE: TYPE_LOCAL, + CONF_COMMUNICATION_DELAY: info["comm_delay"], }, ) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 1bea660c95d..e86179a91eb 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -161,21 +161,19 @@ class StreamMuxer: mode="w", format=SEGMENT_CONTAINER_FORMAT, container_options={ - **{ - # Removed skip_sidx - see: - # https://github.com/home-assistant/core/pull/39970 - # "cmaf" flag replaces several of the movflags used, - # but too recent to use for now - "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov", - # Sometimes the first segment begins with negative timestamps, - # and this setting just - # adjusts the timestamps in the output from that segment to start - # from 0. Helps from having to make some adjustments - # in test_durations - "avoid_negative_ts": "make_non_negative", - "fragment_index": str(sequence + 1), - "video_track_timescale": str(int(1 / input_vstream.time_base)), - }, + # Removed skip_sidx - see: + # https://github.com/home-assistant/core/pull/39970 + # "cmaf" flag replaces several of the movflags used, + # but too recent to use for now + "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov", + # Sometimes the first segment begins with negative timestamps, + # and this setting just + # adjusts the timestamps in the output from that segment to start + # from 0. Helps from having to make some adjustments + # in test_durations + "avoid_negative_ts": "make_non_negative", + "fragment_index": str(sequence + 1), + "video_track_timescale": str(int(1 / input_vstream.time_base)), # Only do extra fragmenting if we are using ll_hls # Let ffmpeg do the work using frag_duration # Fragment durations may exceed the 15% allowed variance but it seems ok diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 7c6a4376cd8..fcb1e5f013e 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -116,15 +116,15 @@ class VeraFlowHandler(ConfigFlow, domain=DOMAIN): { **user_input, **options_data(user_input), - **{CONF_SOURCE: SOURCE_USER}, - **{CONF_LEGACY_UNIQUE_ID: False}, + CONF_SOURCE: SOURCE_USER, + CONF_LEGACY_UNIQUE_ID: False, } ) return self.async_show_form( step_id="user", data_schema=vol.Schema( - {**{vol.Required(CONF_CONTROLLER): str}, **options_schema()} + {vol.Required(CONF_CONTROLLER): str, **options_schema()} ), ) @@ -148,8 +148,8 @@ class VeraFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_finish( { **config, - **{CONF_SOURCE: SOURCE_IMPORT}, - **{CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id}, + CONF_SOURCE: SOURCE_IMPORT, + CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id, } ) diff --git a/homeassistant/components/zha/websocket_api.py b/homeassistant/components/zha/websocket_api.py index fe9c5680e27..f9a92acdd4c 100644 --- a/homeassistant/components/zha/websocket_api.py +++ b/homeassistant/components/zha/websocket_api.py @@ -1098,7 +1098,7 @@ async def websocket_update_zha_configuration( """Update the ZHA configuration.""" zha_gateway = get_zha_gateway(hass) options = zha_gateway.config_entry.options - data_to_save = {**options, **{CUSTOM_CONFIGURATION: msg["data"]}} + data_to_save = {**options, CUSTOM_CONFIGURATION: msg["data"]} for section, schema in ZHA_CONFIG_SCHEMAS.items(): for entry in schema.schema: diff --git a/pyproject.toml b/pyproject.toml index aeff05426ae..c743e3507ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -605,6 +605,7 @@ select = [ "N815", # Variable {name} in class scope should not be mixedCase "PERF", # Perflint "PGH004", # Use specific rule codes when using noqa + "PIE800", # Unnecessary dictionary unpacking operators "PL", # pylint "PIE804", # Unnecessary dict kwargs "PIE790", # Unnecessary pass statement diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 6b658ece853..ce69c6778f4 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -116,7 +116,7 @@ async def async_setup_cast(hass, config=None): """Set up the cast platform.""" if config is None: config = {} - data = {**{"ignore_cec": [], "known_hosts": [], "uuid": []}, **config} + data = {"ignore_cec": [], "known_hosts": [], "uuid": [], **config} with patch( "homeassistant.helpers.entity_platform.EntityPlatform._async_schedule_add_entities_for_entry" ) as add_entities: diff --git a/tests/components/energy/test_sensor.py b/tests/components/energy/test_sensor.py index c51a228f7f0..5b06d01c1b8 100644 --- a/tests/components/energy/test_sensor.py +++ b/tests/components/energy/test_sensor.py @@ -425,7 +425,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, initial_energy, - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) hass.states.async_set("sensor.energy_price", "1") @@ -444,7 +444,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, "0", - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) await hass.async_block_till_done() @@ -466,7 +466,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, "10", - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) @@ -493,7 +493,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, "14.5", - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) @@ -511,7 +511,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, "14", - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) @@ -524,7 +524,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, "4", - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) @@ -537,7 +537,7 @@ async def test_cost_sensor_price_entity_total( hass.states.async_set( usage_sensor_entity_id, "10", - {**energy_attributes, **{"last_reset": last_reset}}, + {**energy_attributes, "last_reset": last_reset}, ) await hass.async_block_till_done() state = hass.states.get(cost_sensor_entity_id) diff --git a/tests/components/risco/test_config_flow.py b/tests/components/risco/test_config_flow.py index 4775cfca903..d4dd4e3fac5 100644 --- a/tests/components/risco/test_config_flow.py +++ b/tests/components/risco/test_config_flow.py @@ -249,7 +249,8 @@ async def test_local_form(hass: HomeAssistant) -> None: expected_data = { **TEST_LOCAL_DATA, - **{"type": "local", CONF_COMMUNICATION_DELAY: 0}, + "type": "local", + CONF_COMMUNICATION_DELAY: 0, } assert result3["type"] == FlowResultType.CREATE_ENTRY assert result3["title"] == TEST_SITE_NAME diff --git a/tests/components/roborock/test_vacuum.py b/tests/components/roborock/test_vacuum.py index 97a76803b8f..4b683da88fc 100644 --- a/tests/components/roborock/test_vacuum.py +++ b/tests/components/roborock/test_vacuum.py @@ -140,7 +140,7 @@ async def test_failed_user_command( setup_entry: MockConfigEntry, ) -> None: """Test that when a user sends an invalid command, we raise HomeAssistantError.""" - data = {ATTR_ENTITY_ID: ENTITY_ID, **{"command": "fake_command"}} + data = {ATTR_ENTITY_ID: ENTITY_ID, "command": "fake_command"} with patch( "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_command", side_effect=RoborockException(), diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 83cfd8c2e83..40b38b2e57a 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -4140,14 +4140,14 @@ async def test_validate_unit_change_convertible( # No statistics, unit in state matching device class - empty response hass.states.async_set( - "sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit}} + "sensor.test", 10, attributes={**attributes, "unit_of_measurement": unit} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) # No statistics, unit in state not matching device class - empty response hass.states.async_set( - "sensor.test", 11, attributes={**attributes, **{"unit_of_measurement": "dogs"}} + "sensor.test", 11, attributes={**attributes, "unit_of_measurement": "dogs"} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4156,7 +4156,7 @@ async def test_validate_unit_change_convertible( await async_recorder_block_till_done(hass) do_adhoc_statistics(hass, start=now) hass.states.async_set( - "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}} + "sensor.test", 12, attributes={**attributes, "unit_of_measurement": "dogs"} ) await async_recorder_block_till_done(hass) expected = { @@ -4176,7 +4176,7 @@ async def test_validate_unit_change_convertible( # Valid state - empty response hass.states.async_set( - "sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit}} + "sensor.test", 13, attributes={**attributes, "unit_of_measurement": unit} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4188,7 +4188,7 @@ async def test_validate_unit_change_convertible( # Valid state in compatible unit - empty response hass.states.async_set( - "sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit2}} + "sensor.test", 13, attributes={**attributes, "unit_of_measurement": unit2} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4263,7 +4263,7 @@ async def test_validate_statistics_unit_ignore_device_class( do_adhoc_statistics(hass, start=now) await async_recorder_block_till_done(hass) hass.states.async_set( - "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}} + "sensor.test", 12, attributes={**attributes, "unit_of_measurement": "dogs"} ) await hass.async_block_till_done() await assert_validation_result(client, {}) @@ -4350,14 +4350,14 @@ async def test_validate_statistics_unit_change_no_device_class( # No statistics, sensor state set - empty response hass.states.async_set( - "sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit}} + "sensor.test", 10, attributes={**attributes, "unit_of_measurement": unit} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) # No statistics, sensor state set to an incompatible unit - empty response hass.states.async_set( - "sensor.test", 11, attributes={**attributes, **{"unit_of_measurement": "dogs"}} + "sensor.test", 11, attributes={**attributes, "unit_of_measurement": "dogs"} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4366,7 +4366,7 @@ async def test_validate_statistics_unit_change_no_device_class( await async_recorder_block_till_done(hass) do_adhoc_statistics(hass, start=now) hass.states.async_set( - "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": "dogs"}} + "sensor.test", 12, attributes={**attributes, "unit_of_measurement": "dogs"} ) await async_recorder_block_till_done(hass) expected = { @@ -4386,7 +4386,7 @@ async def test_validate_statistics_unit_change_no_device_class( # Valid state - empty response hass.states.async_set( - "sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit}} + "sensor.test", 13, attributes={**attributes, "unit_of_measurement": unit} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4398,7 +4398,7 @@ async def test_validate_statistics_unit_change_no_device_class( # Valid state in compatible unit - empty response hass.states.async_set( - "sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit2}} + "sensor.test", 13, attributes={**attributes, "unit_of_measurement": unit2} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4739,13 +4739,13 @@ async def test_validate_statistics_unit_change_no_conversion( # No statistics, original unit - empty response hass.states.async_set( - "sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit1}} + "sensor.test", 10, attributes={**attributes, "unit_of_measurement": unit1} ) await assert_validation_result(client, {}) # No statistics, changed unit - empty response hass.states.async_set( - "sensor.test", 11, attributes={**attributes, **{"unit_of_measurement": unit2}} + "sensor.test", 11, attributes={**attributes, "unit_of_measurement": unit2} ) await assert_validation_result(client, {}) @@ -4757,7 +4757,7 @@ async def test_validate_statistics_unit_change_no_conversion( # No statistics, original unit - empty response hass.states.async_set( - "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": unit1}} + "sensor.test", 12, attributes={**attributes, "unit_of_measurement": unit1} ) await assert_validation_result(client, {}) @@ -4772,7 +4772,7 @@ async def test_validate_statistics_unit_change_no_conversion( # Change unit - expect error hass.states.async_set( - "sensor.test", 13, attributes={**attributes, **{"unit_of_measurement": unit2}} + "sensor.test", 13, attributes={**attributes, "unit_of_measurement": unit2} ) await async_recorder_block_till_done(hass) expected = { @@ -4792,7 +4792,7 @@ async def test_validate_statistics_unit_change_no_conversion( # Original unit - empty response hass.states.async_set( - "sensor.test", 14, attributes={**attributes, **{"unit_of_measurement": unit1}} + "sensor.test", 14, attributes={**attributes, "unit_of_measurement": unit1} ) await async_recorder_block_till_done(hass) await assert_validation_result(client, {}) @@ -4874,7 +4874,7 @@ async def test_validate_statistics_unit_change_equivalent_units( # No statistics, original unit - empty response hass.states.async_set( - "sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit1}} + "sensor.test", 10, attributes={**attributes, "unit_of_measurement": unit1} ) await assert_validation_result(client, {}) @@ -4888,7 +4888,7 @@ async def test_validate_statistics_unit_change_equivalent_units( # Units changed to an equivalent unit - empty response hass.states.async_set( - "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": unit2}} + "sensor.test", 12, attributes={**attributes, "unit_of_measurement": unit2} ) await assert_validation_result(client, {}) @@ -4960,7 +4960,7 @@ async def test_validate_statistics_unit_change_equivalent_units_2( # No statistics, original unit - empty response hass.states.async_set( - "sensor.test", 10, attributes={**attributes, **{"unit_of_measurement": unit1}} + "sensor.test", 10, attributes={**attributes, "unit_of_measurement": unit1} ) await assert_validation_result(client, {}) @@ -4974,7 +4974,7 @@ async def test_validate_statistics_unit_change_equivalent_units_2( # Units changed to an equivalent unit which is not known by the unit converters hass.states.async_set( - "sensor.test", 12, attributes={**attributes, **{"unit_of_measurement": unit2}} + "sensor.test", 12, attributes={**attributes, "unit_of_measurement": unit2} ) expected = { "sensor.test": [ diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index 585d6d6f60a..666af780283 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -229,7 +229,7 @@ async def test_exclude_and_light_ids( controller_config=new_simple_controller_config( config_source=ConfigSource.CONFIG_ENTRY, devices=(vera_device1, vera_device2, vera_device3, vera_device4), - config={**{CONF_CONTROLLER: "http://127.0.0.1:123"}, **options}, + config={CONF_CONTROLLER: "http://127.0.0.1:123", **options}, ), ) From ba7ec4ac165fc0dbe70158a700ab1da22684e935 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 17:04:04 -1000 Subject: [PATCH 1190/1691] Eagerly shutdown unifiprotect at the stop event (#113655) --- homeassistant/components/unifiprotect/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index 15784dbdeaf..71c887cd870 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -110,7 +110,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service entry.async_on_unload(entry.add_update_listener(_async_options_updated)) entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, data_service.async_stop, run_immediately=True + ) ) if not entry.options.get(CONF_ALLOW_EA, False) and ( From 4d430520a0d33220221a8b64a20b78f1794f9922 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 17:06:31 -1000 Subject: [PATCH 1191/1691] Run yalexs_ble shutdown with run_immediately (#113653) --- homeassistant/components/yalexs_ble/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/yalexs_ble/__init__.py b/homeassistant/components/yalexs_ble/__init__.py index 8c9c5176003..f608da6cd60 100644 --- a/homeassistant/components/yalexs_ble/__init__.py +++ b/homeassistant/components/yalexs_ble/__init__.py @@ -127,7 +127,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(_async_update_listener)) entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_shutdown, run_immediately=True + ) ) return True From 309fcb5c30223935fff73a6bf5d26cd354356cb0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 17:15:46 -1000 Subject: [PATCH 1192/1691] Eagerly shutdown homekit_controller at the stop event (#113650) --- homeassistant/components/homekit_controller/__init__.py | 7 +++++-- homeassistant/components/homekit_controller/utils.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 9244a9f95d8..218094ddaf5 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -24,6 +24,7 @@ from homeassistant.core import Event, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.typing import ConfigType +from homeassistant.util.async_ import create_eager_task from .config_flow import normalize_hkid from .connection import HKDevice @@ -80,12 +81,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def _async_stop_homekit_controller(event: Event) -> None: await asyncio.gather( *( - connection.async_unload() + create_eager_task(connection.async_unload()) for connection in hass.data[KNOWN_DEVICES].values() ) ) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller, run_immediately=True + ) return True diff --git a/homeassistant/components/homekit_controller/utils.py b/homeassistant/components/homekit_controller/utils.py index 2f94f5bac92..40879a533f4 100644 --- a/homeassistant/components/homekit_controller/utils.py +++ b/homeassistant/components/homekit_controller/utils.py @@ -77,7 +77,9 @@ async def async_get_controller(hass: HomeAssistant) -> Controller: # Right now _async_stop_homekit_controller is only called on HA exiting # So we don't have to worry about leaking a callback here. - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_stop_homekit_controller, run_immediately=True + ) await controller.async_start() From 6df3e9d7c0d8b0aa686fc0bd306d41b85b4d9774 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 17:21:03 -1000 Subject: [PATCH 1193/1691] Run bond shutdown listener with run_immediately (#113657) --- homeassistant/components/bond/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/bond/__init__.py b/homeassistant/components/bond/__init__.py index 9ecfedee570..216a4b501f2 100644 --- a/homeassistant/components/bond/__init__.py +++ b/homeassistant/components/bond/__init__.py @@ -68,7 +68,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry.async_on_unload(_async_stop_event) entry.async_on_unload( - hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, _async_stop_event) + hass.bus.async_listen( + EVENT_HOMEASSISTANT_STOP, _async_stop_event, run_immediately=True + ) ) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = BondData(hub, bpup_subs) From 1f9c8694a8d8fad9c0323b5001377cc22c790acb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 19:02:57 -1000 Subject: [PATCH 1194/1691] Save bluetooth passive data eagerly at the stop event (#113648) --- .../components/bluetooth/passive_update_processor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth/passive_update_processor.py b/homeassistant/components/bluetooth/passive_update_processor.py index b0b490a70f2..4e2de196da4 100644 --- a/homeassistant/components/bluetooth/passive_update_processor.py +++ b/homeassistant/components/bluetooth/passive_update_processor.py @@ -272,7 +272,9 @@ async def async_setup(hass: HomeAssistant) -> None: await _async_save_processor_data(None) hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, _async_save_processor_data_at_stop + EVENT_HOMEASSISTANT_STOP, + _async_save_processor_data_at_stop, + run_immediately=True, ) From 5e8265d8a44c2c8fc73412faf4af8bcf9e70a2c9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 Mar 2024 19:03:14 -1000 Subject: [PATCH 1195/1691] Add run_immediately to bluetooth listeners (#113659) --- homeassistant/components/bluetooth/__init__.py | 8 ++++++-- homeassistant/components/bluetooth/manager.py | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 5a59858093c..1e091ec32cc 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -166,7 +166,9 @@ async def _async_start_adapter_discovery( """Shutdown debouncer.""" discovery_debouncer.async_shutdown() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown_debouncer) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_shutdown_debouncer, run_immediately=True + ) async def _async_call_debouncer(now: datetime.datetime) -> None: """Call the debouncer at a later time.""" @@ -197,7 +199,9 @@ async def _async_start_adapter_discovery( cancel = usb.async_register_scan_request_callback(hass, _async_trigger_discovery) hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, hass_callback(lambda event: cancel()) + EVENT_HOMEASSISTANT_STOP, + hass_callback(lambda event: cancel()), + run_immediately=True, ) diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py index 2eb07c5133f..3a240e9f01e 100644 --- a/homeassistant/components/bluetooth/manager.py +++ b/homeassistant/components/bluetooth/manager.py @@ -135,9 +135,11 @@ class HomeAssistantBluetoothManager(BluetoothManager): self._bluetooth_adapters, self.storage ) self._cancel_logging_listener = self.hass.bus.async_listen( - EVENT_LOGGING_CHANGED, self._async_logging_changed + EVENT_LOGGING_CHANGED, self._async_logging_changed, run_immediately=True + ) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self.async_stop, run_immediately=True ) - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) seen: set[str] = set() for address, service_info in itertools.chain( self._connectable_history.items(), self._all_history.items() From 8f6c4f8b333c6c89676b227ce35682c94fdb6b8f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 17 Mar 2024 09:45:49 +0100 Subject: [PATCH 1196/1691] Add tests of homeworks binary sensor (#113577) --- .coveragerc | 1 - .../snapshots/test_binary_sensor.ambr | 40 +++++++++++ .../homeworks/test_binary_sensor.py | 69 +++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 tests/components/homeworks/snapshots/test_binary_sensor.ambr create mode 100644 tests/components/homeworks/test_binary_sensor.py diff --git a/.coveragerc b/.coveragerc index 7a66f88ed87..e7de89daffb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -550,7 +550,6 @@ omit = homeassistant/components/homematic/sensor.py homeassistant/components/homematic/switch.py homeassistant/components/homeworks/__init__.py - homeassistant/components/homeworks/binary_sensor.py homeassistant/components/homeworks/button.py homeassistant/components/homeworks/light.py homeassistant/components/horizon/media_player.py diff --git a/tests/components/homeworks/snapshots/test_binary_sensor.ambr b/tests/components/homeworks/snapshots/test_binary_sensor.ambr new file mode 100644 index 00000000000..effa4315861 --- /dev/null +++ b/tests/components/homeworks/snapshots/test_binary_sensor.ambr @@ -0,0 +1,40 @@ +# serializer version: 1 +# name: test_binary_sensor_attributes_state_update + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Foyer Keypad Morning', + 'homeworks_address': '[02:08:02:01]', + }), + 'context': , + 'entity_id': 'binary_sensor.foyer_keypad_morning', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_binary_sensor_attributes_state_update.1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Foyer Keypad Morning', + 'homeworks_address': '[02:08:02:01]', + }), + 'context': , + 'entity_id': 'binary_sensor.foyer_keypad_morning', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_binary_sensor_attributes_state_update.2 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Foyer Keypad Morning', + 'homeworks_address': '[02:08:02:01]', + }), + 'context': , + 'entity_id': 'binary_sensor.foyer_keypad_morning', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/homeworks/test_binary_sensor.py b/tests/components/homeworks/test_binary_sensor.py new file mode 100644 index 00000000000..cc9e580c23c --- /dev/null +++ b/tests/components/homeworks/test_binary_sensor.py @@ -0,0 +1,69 @@ +"""Tests for the Lutron Homeworks Series 4 and 8 binary sensor.""" +from unittest.mock import ANY, MagicMock + +from freezegun.api import FrozenDateTimeFactory +from pyhomeworks.pyhomeworks import HW_KEYPAD_LED_CHANGED +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.homeworks import KEYPAD_LEDSTATE_POLL_COOLDOWN +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_binary_sensor_attributes_state_update( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, +) -> None: + """Test Homeworks binary sensor state changes.""" + entity_id = "binary_sensor.foyer_keypad_morning" + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_homeworks.assert_called_once_with("192.168.0.1", 1234, ANY) + hw_callback = mock_homeworks.mock_calls[0][1][2] + + assert entity_id in hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN) + + state = hass.states.get(entity_id) + assert state.state == STATE_UNKNOWN + assert state == snapshot + + freezer.tick(KEYPAD_LEDSTATE_POLL_COOLDOWN + 1) + async_fire_time_changed(hass) + await hass.async_block_till_done() + assert len(mock_controller._send.mock_calls) == 1 + assert mock_controller._send.mock_calls[0][1] == ("RKLS, [02:08:02:01]",) + + hw_callback( + HW_KEYPAD_LED_CHANGED, + [ + "[02:08:02:01]", + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + ) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state == snapshot + + hw_callback( + HW_KEYPAD_LED_CHANGED, + [ + "[02:08:02:01]", + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + ) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + assert state == snapshot From 1a70dbfd94e2d37fa504cc051d7f5b8fddc2e5ba Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 17 Mar 2024 09:46:36 +0100 Subject: [PATCH 1197/1691] Add tests of homeworks light (#113579) --- .coveragerc | 1 - .../homeworks/snapshots/test_light.ambr | 39 ++++++ tests/components/homeworks/test_light.py | 124 ++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 tests/components/homeworks/snapshots/test_light.ambr create mode 100644 tests/components/homeworks/test_light.py diff --git a/.coveragerc b/.coveragerc index e7de89daffb..dcc51b83cc5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -551,7 +551,6 @@ omit = homeassistant/components/homematic/switch.py homeassistant/components/homeworks/__init__.py homeassistant/components/homeworks/button.py - homeassistant/components/homeworks/light.py homeassistant/components/horizon/media_player.py homeassistant/components/hp_ilo/sensor.py homeassistant/components/huawei_lte/__init__.py diff --git a/tests/components/homeworks/snapshots/test_light.ambr b/tests/components/homeworks/snapshots/test_light.ambr new file mode 100644 index 00000000000..49f7f561bc0 --- /dev/null +++ b/tests/components/homeworks/snapshots/test_light.ambr @@ -0,0 +1,39 @@ +# serializer version: 1 +# name: test_light_attributes_state_update + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': None, + 'color_mode': None, + 'friendly_name': 'Foyer Sconces', + 'homeworks_address': '[02:08:01:01]', + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.foyer_sconces', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_light_attributes_state_update.1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'brightness': 127, + 'color_mode': , + 'friendly_name': 'Foyer Sconces', + 'homeworks_address': '[02:08:01:01]', + 'supported_color_modes': list([ + , + ]), + 'supported_features': , + }), + 'context': , + 'entity_id': 'light.foyer_sconces', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/homeworks/test_light.py b/tests/components/homeworks/test_light.py new file mode 100644 index 00000000000..dda88f2a784 --- /dev/null +++ b/tests/components/homeworks/test_light.py @@ -0,0 +1,124 @@ +"""Tests for the Lutron Homeworks Series 4 and 8 light.""" +from unittest.mock import ANY, MagicMock + +from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED +import pytest +from pytest_unordered import unordered +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_light_attributes_state_update( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, + snapshot: SnapshotAssertion, +) -> None: + """Test Homeworks light state changes.""" + entity_id = "light.foyer_sconces" + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_homeworks.assert_called_once_with("192.168.0.1", 1234, ANY) + hw_callback = mock_homeworks.mock_calls[0][1][2] + + assert len(mock_controller.request_dimmer_level.mock_calls) == 1 + assert mock_controller.request_dimmer_level.mock_calls[0][1] == ("[02:08:01:01]",) + + assert hass.states.async_entity_ids("light") == unordered([entity_id]) + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + assert state == snapshot + + hw_callback(HW_LIGHT_CHANGED, ["[02:08:01:01]", 50]) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state == snapshot + + +async def test_light_service_calls( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test Homeworks light service call.""" + entity_id = "light.foyer_sconces" + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.async_entity_ids("light") == unordered([entity_id]) + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + mock_controller.fade_dim.assert_called_with(0.0, 1.0, 0, "[02:08:01:01]") + + # The light's brightness is unknown, turning it on should set it to max + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + mock_controller.fade_dim.assert_called_with(100.0, 1.0, 0, "[02:08:01:01]") + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 127}, + blocking=True, + ) + mock_controller.fade_dim.assert_called_with( + pytest.approx(49.8, abs=0.1), 1.0, 0, "[02:08:01:01]" + ) + + +async def test_light_restore_brightness( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test Homeworks light service call.""" + entity_id = "light.foyer_sconces" + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_homeworks.assert_called_once_with("192.168.0.1", 1234, ANY) + hw_callback = mock_homeworks.mock_calls[0][1][2] + + assert hass.states.async_entity_ids("light") == unordered([entity_id]) + + hw_callback(HW_LIGHT_CHANGED, ["[02:08:01:01]", 50]) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 127 + + await hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + mock_controller.fade_dim.assert_called_with( + pytest.approx(49.8, abs=0.1), 1.0, 0, "[02:08:01:01]" + ) From d5fd005db8e5f1c5429140aa1636c1f117c92981 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 17 Mar 2024 09:56:26 +0100 Subject: [PATCH 1198/1691] Add ruff rule PIE808 (#113621) --- homeassistant/components/accuweather/__init__.py | 2 +- homeassistant/components/ddwrt/device_tracker.py | 2 +- homeassistant/components/demo/mailbox.py | 2 +- homeassistant/components/ecobee/climate.py | 2 +- homeassistant/components/ecobee/weather.py | 2 +- homeassistant/components/enphase_envoy/sensor.py | 8 ++++---- homeassistant/components/hdmi_cec/__init__.py | 2 +- homeassistant/components/homekit/__init__.py | 2 +- homeassistant/components/iammeter/sensor.py | 4 ++-- homeassistant/components/insteon/schemas.py | 2 +- homeassistant/components/ld2410_ble/sensor.py | 4 ++-- homeassistant/components/lifx/light.py | 2 +- homeassistant/components/modbus/base_platform.py | 2 +- homeassistant/components/modbus/binary_sensor.py | 2 +- homeassistant/components/modbus/sensor.py | 2 +- homeassistant/components/nina/binary_sensor.py | 2 +- homeassistant/components/nina/config_flow.py | 2 +- homeassistant/components/openhardwaremonitor/sensor.py | 2 +- homeassistant/components/recorder/util.py | 2 +- homeassistant/components/roomba/irobot_base.py | 2 +- homeassistant/components/stream/fmp4utils.py | 2 +- homeassistant/components/syncthru/sensor.py | 2 +- homeassistant/components/touchline/climate.py | 5 +---- homeassistant/components/zwave_js/entity.py | 2 +- pyproject.toml | 7 +------ tests/components/greeneye_monitor/common.py | 6 +++--- tests/components/homekit/test_aidmanager.py | 8 ++++---- tests/components/hue/test_light_v2.py | 4 ++-- tests/components/local_todo/test_todo.py | 2 +- tests/components/mailbox/test_init.py | 2 +- tests/components/modbus/test_sensor.py | 4 ++-- tests/components/mqtt/test_common.py | 2 +- tests/components/mqtt/test_init.py | 4 ++-- tests/components/mqtt/test_light_json.py | 2 +- tests/components/nest/test_media_source.py | 6 +++--- tests/components/owntracks/test_device_tracker.py | 2 +- tests/components/recorder/test_migrate.py | 2 +- tests/components/recorder/test_websocket_api.py | 6 +++--- tests/components/shopping_list/test_todo.py | 2 +- tests/components/steam_online/__init__.py | 2 +- tests/components/stream/test_worker.py | 4 ++-- tests/components/zwave_js/test_migrate.py | 4 ++-- 42 files changed, 61 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index 25cff4ed4fc..26e0c1331be 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -52,7 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Remove ozone sensors from registry if they exist ent_reg = er.async_get(hass) - for day in range(0, 5): + for day in range(5): unique_id = f"{coordinator.location_key}-ozone-{day}" if entity_id := ent_reg.async_get_entity_id(SENSOR_PLATFORM, DOMAIN, unique_id): _LOGGER.debug("Removing ozone sensor entity %s", entity_id) diff --git a/homeassistant/components/ddwrt/device_tracker.py b/homeassistant/components/ddwrt/device_tracker.py index 39a7d1ebc0c..21786a292f4 100644 --- a/homeassistant/components/ddwrt/device_tracker.py +++ b/homeassistant/components/ddwrt/device_tracker.py @@ -98,7 +98,7 @@ class DdWrtDeviceScanner(DeviceScanner): elements = cleaned_str.split(",") num_clients = int(len(elements) / 5) self.mac2name = {} - for idx in range(0, num_clients): + for idx in range(num_clients): # The data is a single array # every 5 elements represents one host, the MAC # is the third element and the name is the first. diff --git a/homeassistant/components/demo/mailbox.py b/homeassistant/components/demo/mailbox.py index ca9cbf0bd85..3ec80e47118 100644 --- a/homeassistant/components/demo/mailbox.py +++ b/homeassistant/components/demo/mailbox.py @@ -34,7 +34,7 @@ class DemoMailbox(Mailbox): super().__init__(hass, name) self._messages: dict[str, dict[str, Any]] = {} txt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - for idx in range(0, 10): + for idx in range(10): msgtime = int( dt_util.as_timestamp(dt_util.utcnow()) - 3600 * 24 * (10 - idx) ) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index ae076d42e1d..cfb73153f45 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -709,7 +709,7 @@ class Thermostat(ClimateEntity): def set_humidity(self, humidity: int) -> None: """Set the humidity level.""" - if humidity not in range(0, 101): + if humidity not in range(101): raise ValueError( f"Invalid set_humidity value (must be in range 0-100): {humidity}" ) diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 53b6b0bb345..743ee12ddc7 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -172,7 +172,7 @@ class EcobeeWeather(WeatherEntity): forecasts: list[Forecast] = [] date = dt_util.utcnow() - for day in range(0, 5): + for day in range(5): forecast = _process_forecast(self.weather["forecasts"][day]) if forecast is None: continue diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 842c50c6d9d..1b64b3a9b9f 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -151,7 +151,7 @@ PRODUCTION_PHASE_SENSORS = { ) for sensor in list(PRODUCTION_SENSORS) ] - for phase in range(0, 3) + for phase in range(3) } @@ -221,7 +221,7 @@ CONSUMPTION_PHASE_SENSORS = { ) for sensor in list(CONSUMPTION_SENSORS) ] - for phase in range(0, 3) + for phase in range(3) } @@ -326,7 +326,7 @@ CT_NET_CONSUMPTION_PHASE_SENSORS = { ) for sensor in list(CT_NET_CONSUMPTION_SENSORS) ] - for phase in range(0, 3) + for phase in range(3) } CT_PRODUCTION_SENSORS = ( @@ -361,7 +361,7 @@ CT_PRODUCTION_PHASE_SENSORS = { ) for sensor in list(CT_PRODUCTION_SENSORS) ] - for phase in range(0, 3) + for phase in range(3) } diff --git a/homeassistant/components/hdmi_cec/__init__.py b/homeassistant/components/hdmi_cec/__init__.py index 7902fa1c33c..43a649ba01a 100644 --- a/homeassistant/components/hdmi_cec/__init__.py +++ b/homeassistant/components/hdmi_cec/__init__.py @@ -253,7 +253,7 @@ def setup(hass: HomeAssistant, base_config: ConfigType) -> bool: # noqa: C901 hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) else: att = 1 if att == "" else int(att) - for _ in range(0, att): + for _ in range(att): hdmi_network.send_command(KeyPressCommand(cmd, dst=ADDR_AUDIOSYSTEM)) hdmi_network.send_command(KeyReleaseCommand(dst=ADDR_AUDIOSYSTEM)) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index ebbb1b9420d..8fc96cff954 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -384,7 +384,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await homekit.async_stop() logged_shutdown_wait = False - for _ in range(0, SHUTDOWN_TIMEOUT): + for _ in range(SHUTDOWN_TIMEOUT): if async_port_is_available(entry.data[CONF_PORT]): break diff --git a/homeassistant/components/iammeter/sensor.py b/homeassistant/components/iammeter/sensor.py index 2a5d3ed7a81..a3922b06980 100644 --- a/homeassistant/components/iammeter/sensor.py +++ b/homeassistant/components/iammeter/sensor.py @@ -75,8 +75,8 @@ def _migrate_to_new_unique_id( phase_list = ["A", "B", "C", "NET"] id_phase_range = 1 if model == DEVICE_3080 else 4 id_name_range = 5 if model == DEVICE_3080 else 7 - for row in range(0, id_phase_range): - for idx in range(0, id_name_range): + for row in range(id_phase_range): + for idx in range(id_name_range): old_unique_id = f"{serial_number}-{row}-{idx}" new_unique_id = ( f"{serial_number}_{name_list[idx]}" diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 7ac627f7be0..e277281c240 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -79,7 +79,7 @@ ADD_DEFAULT_LINKS_SCHEMA = vol.Schema({vol.Required(CONF_ENTITY_ID): cv.entity_i def normalize_byte_entry_to_int(entry: int | bytes | str): """Format a hex entry value.""" if isinstance(entry, int): - if entry in range(0, 256): + if entry in range(256): return entry raise ValueError("Must be single byte") if isinstance(entry, str): diff --git a/homeassistant/components/ld2410_ble/sensor.py b/homeassistant/components/ld2410_ble/sensor.py index d3a2a6e599c..6daa1397161 100644 --- a/homeassistant/components/ld2410_ble/sensor.py +++ b/homeassistant/components/ld2410_ble/sensor.py @@ -92,7 +92,7 @@ MOTION_ENERGY_GATES = [ entity_registry_enabled_default=False, native_unit_of_measurement="Target Energy", ) - for i in range(0, 9) + for i in range(9) ] STATIC_ENERGY_GATES = [ @@ -103,7 +103,7 @@ STATIC_ENERGY_GATES = [ entity_registry_enabled_default=False, native_unit_of_measurement="Target Energy", ) - for i in range(0, 9) + for i in range(9) ] SENSOR_DESCRIPTIONS = [ diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index d1ee5c836eb..90632f82d9e 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -418,7 +418,7 @@ class LIFXMultiZone(LIFXColor): await super().set_color(hsbk, kwargs, duration) return - zones = list(range(0, num_zones)) + zones = list(range(num_zones)) else: zones = [x for x in set(zones) if x < num_zones] diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 6d547e5ea38..c64ef95c163 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -185,7 +185,7 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): """Do swap as needed.""" if slave_count: swapped = [] - for i in range(0, self._slave_count + 1): + for i in range(self._slave_count + 1): inx = i * self._slave_size inx2 = inx + self._slave_size swapped.extend(self._swap_registers(registers[inx:inx2], 0)) diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 23192244332..314877b7927 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -94,7 +94,7 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity): ) return [ - SlaveSensor(self._coordinator, idx, entry) for idx in range(0, slave_count) + SlaveSensor(self._coordinator, idx, entry) for idx in range(slave_count) ] async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index f74c4bf4e1b..6c6e1ef1830 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -95,7 +95,7 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity): ) return [ - SlaveSensor(self._coordinator, idx, entry) for idx in range(0, slave_count) + SlaveSensor(self._coordinator, idx, entry) for idx in range(slave_count) ] async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/nina/binary_sensor.py b/homeassistant/components/nina/binary_sensor.py index 8104a52683c..397ced0f5d3 100644 --- a/homeassistant/components/nina/binary_sensor.py +++ b/homeassistant/components/nina/binary_sensor.py @@ -47,7 +47,7 @@ async def async_setup_entry( async_add_entities( NINAMessage(coordinator, ent, regions[ent], i + 1, config_entry) for ent in coordinator.data - for i in range(0, message_slots) + for i in range(message_slots) ) diff --git a/homeassistant/components/nina/config_flow.py b/homeassistant/components/nina/config_flow.py index 8b5bcb72252..07c3f6fe9a1 100644 --- a/homeassistant/components/nina/config_flow.py +++ b/homeassistant/components/nina/config_flow.py @@ -225,7 +225,7 @@ class OptionsFlowHandler(OptionsFlow): removed_entities_slots = [ f"{region}-{slot_id}" for region in self.data[CONF_REGIONS] - for slot_id in range(0, self.data[CONF_MESSAGE_SLOTS] + 1) + for slot_id in range(self.data[CONF_MESSAGE_SLOTS] + 1) if slot_id > user_input[CONF_MESSAGE_SLOTS] ] diff --git a/homeassistant/components/openhardwaremonitor/sensor.py b/homeassistant/components/openhardwaremonitor/sensor.py index a68b9b11678..4e15ca3dd57 100644 --- a/homeassistant/components/openhardwaremonitor/sensor.py +++ b/homeassistant/components/openhardwaremonitor/sensor.py @@ -170,7 +170,7 @@ class OpenHardwareMonitorData: result = devices.copy() if json[OHM_CHILDREN]: - for child_index in range(0, len(json[OHM_CHILDREN])): + for child_index in range(len(json[OHM_CHILDREN])): child_path = path.copy() child_path.append(child_index) diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index 0b9ec0cf68c..e92e287d11a 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -160,7 +160,7 @@ def execute( This method also retries a few times in the case of stale connections. """ debug = _LOGGER.isEnabledFor(logging.DEBUG) - for tryno in range(0, RETRIES): + for tryno in range(RETRIES): try: if debug: timer_start = time.perf_counter() diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 73ef5a813b1..4850dc0b7e9 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -251,7 +251,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): """Set the vacuum cleaner to return to the dock.""" if self.state == STATE_CLEANING: await self.async_pause() - for _ in range(0, 10): + for _ in range(10): if self.state == STATE_PAUSED: break await asyncio.sleep(1) diff --git a/homeassistant/components/stream/fmp4utils.py b/homeassistant/components/stream/fmp4utils.py index 4175b91fd11..e611e07cd71 100644 --- a/homeassistant/components/stream/fmp4utils.py +++ b/homeassistant/components/stream/fmp4utils.py @@ -92,7 +92,7 @@ def get_codec_string(mp4_bytes: bytes) -> str: stsd_box[112:116], byteorder="big" ) reverse = 0 - for i in range(0, 32): + for i in range(32): reverse |= general_profile_compatibility & 1 if i == 31: break diff --git a/homeassistant/components/syncthru/sensor.py b/homeassistant/components/syncthru/sensor.py index 412fd128577..df2ffd99803 100644 --- a/homeassistant/components/syncthru/sensor.py +++ b/homeassistant/components/syncthru/sensor.py @@ -22,7 +22,7 @@ COLORS = ["black", "cyan", "magenta", "yellow"] DRUM_COLORS = COLORS TONER_COLORS = COLORS TRAYS = range(1, 6) -OUTPUT_TRAYS = range(0, 6) +OUTPUT_TRAYS = range(6) DEFAULT_MONITORED_CONDITIONS = [] DEFAULT_MONITORED_CONDITIONS.extend([f"toner_{key}" for key in TONER_COLORS]) DEFAULT_MONITORED_CONDITIONS.extend([f"drum_{key}" for key in DRUM_COLORS]) diff --git a/homeassistant/components/touchline/climate.py b/homeassistant/components/touchline/climate.py index 83c7839772a..5b1c52534c5 100644 --- a/homeassistant/components/touchline/climate.py +++ b/homeassistant/components/touchline/climate.py @@ -56,10 +56,7 @@ def setup_platform( py_touchline = PyTouchline() number_of_devices = int(py_touchline.get_number_of_devices(host)) add_entities( - ( - Touchline(PyTouchline(device_id)) - for device_id in range(0, number_of_devices) - ), + (Touchline(PyTouchline(device_id)) for device_id in range(number_of_devices)), True, ) diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 0f594161bb9..4a6f87cc032 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -203,7 +203,7 @@ class ZWaveBaseEntity(Entity): property_key=primary_value.property_key, ) in self.info.node.values - for endpoint_idx in range(0, primary_value.endpoint) + for endpoint_idx in range(primary_value.endpoint) ): name += f" ({primary_value.endpoint})" diff --git a/pyproject.toml b/pyproject.toml index c743e3507ba..0bd8e592d86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -605,13 +605,8 @@ select = [ "N815", # Variable {name} in class scope should not be mixedCase "PERF", # Perflint "PGH004", # Use specific rule codes when using noqa - "PIE800", # Unnecessary dictionary unpacking operators + "PIE", # flake8-pie "PL", # pylint - "PIE804", # Unnecessary dict kwargs - "PIE790", # Unnecessary pass statement - "PIE794", # Class field is defined multiple times - "PIE807", # Prefer list/dict over useless lambda - "PIE810", # Call startswith/endswith once with a tuple "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task # "RUF100", # Unused `noqa` directive; temporarily every now and then to clean them up diff --git a/tests/components/greeneye_monitor/common.py b/tests/components/greeneye_monitor/common.py index 40562096b53..e4eeca47e60 100644 --- a/tests/components/greeneye_monitor/common.py +++ b/tests/components/greeneye_monitor/common.py @@ -236,9 +236,9 @@ def mock_monitor(serial_number: int) -> MagicMock: monitor = mock_with_listeners() monitor.serial_number = serial_number monitor.voltage_sensor = mock_voltage_sensor() - monitor.pulse_counters = [mock_pulse_counter() for i in range(0, 4)] - monitor.temperature_sensors = [mock_temperature_sensor() for i in range(0, 8)] - monitor.channels = [mock_channel() for i in range(0, 32)] + monitor.pulse_counters = [mock_pulse_counter() for i in range(4)] + monitor.temperature_sensors = [mock_temperature_sensor() for i in range(8)] + monitor.channels = [mock_channel() for i in range(32)] return monitor diff --git a/tests/components/homekit/test_aidmanager.py b/tests/components/homekit/test_aidmanager.py index 9cd618e307c..6dbac422f07 100644 --- a/tests/components/homekit/test_aidmanager.py +++ b/tests/components/homekit/test_aidmanager.py @@ -49,7 +49,7 @@ async def test_aid_generation( aid_storage = AccessoryAidStorage(hass, config_entry) await aid_storage.async_initialize() - for _ in range(0, 2): + for _ in range(2): assert ( aid_storage.get_or_allocate_aid_for_entity_id(light_ent.entity_id) == 1953095294 @@ -72,7 +72,7 @@ async def test_aid_generation( aid_storage.delete_aid(get_system_unique_id(remote_ent, remote_ent.unique_id)) aid_storage.delete_aid("non-existent-one") - for _ in range(0, 2): + for _ in range(2): assert ( aid_storage.get_or_allocate_aid_for_entity_id(light_ent.entity_id) == 1953095294 @@ -112,7 +112,7 @@ async def test_no_aid_collision( seen_aids = set() - for unique_id in range(0, 202): + for unique_id in range(202): ent = entity_registry.async_get_or_create( "light", "device", unique_id, device_id=device_entry.id ) @@ -141,7 +141,7 @@ async def test_aid_generation_no_unique_ids_handles_collision( seen_aids = set() collisions = [] - for light_id in range(0, 220): + for light_id in range(220): entity_id = f"light.light{light_id}" hass.states.async_set(entity_id, "on") expected_aid = fnv1a_32(entity_id.encode("utf-8")) diff --git a/tests/components/hue/test_light_v2.py b/tests/components/hue/test_light_v2.py index b55da314bac..d8d0f4b6e66 100644 --- a/tests/components/hue/test_light_v2.py +++ b/tests/components/hue/test_light_v2.py @@ -550,7 +550,7 @@ async def test_grouped_lights( # PUT request should have been sent to ALL group lights with correct params assert len(mock_bridge_v2.mock_requests) == 3 - for index in range(0, 3): + for index in range(3): assert ( mock_bridge_v2.mock_requests[index]["json"]["identify"]["action"] == "identify" @@ -588,7 +588,7 @@ async def test_grouped_lights( # PUT request should have been sent to ALL group lights with correct params assert len(mock_bridge_v2.mock_requests) == 3 - for index in range(0, 3): + for index in range(3): assert ( mock_bridge_v2.mock_requests[index]["json"]["identify"]["action"] == "identify" diff --git a/tests/components/local_todo/test_todo.py b/tests/components/local_todo/test_todo.py index 760b0260dbb..3074cdcf88f 100644 --- a/tests/components/local_todo/test_todo.py +++ b/tests/components/local_todo/test_todo.py @@ -185,7 +185,7 @@ async def test_bulk_remove( ws_get_items: Callable[[], Awaitable[dict[str, str]]], ) -> None: """Test removing multiple todo items.""" - for i in range(0, 5): + for i in range(5): await hass.services.async_call( TODO_DOMAIN, "add_item", diff --git a/tests/components/mailbox/test_init.py b/tests/components/mailbox/test_init.py index 1af7f2e06b9..8b83f2b0ec7 100644 --- a/tests/components/mailbox/test_init.py +++ b/tests/components/mailbox/test_init.py @@ -49,7 +49,7 @@ class TestMailbox(mailbox.Mailbox): """Initialize Test mailbox.""" super().__init__(hass, name) self._messages: dict[str, dict[str, Any]] = {} - for idx in range(0, 10): + for idx in range(10): msg = _create_message(idx) msgsha = msg["sha"] self._messages[msgsha] = msg diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index 1c6b332d391..23347cc56bb 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -910,7 +910,7 @@ async def test_virtual_sensor( hass: HomeAssistant, entity_registry: er.EntityRegistry, mock_do_cycle, expected ) -> None: """Run test for sensor.""" - for i in range(0, len(expected)): + for i in range(len(expected)): entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") unique_id = f"{SLAVE_UNIQUE_ID}" if i: @@ -1080,7 +1080,7 @@ async def test_virtual_swap_sensor( hass: HomeAssistant, mock_do_cycle, expected ) -> None: """Run test for sensor.""" - for i in range(0, len(expected)): + for i in range(len(expected)): entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") if i: entity_id = f"{entity_id}_{i}" diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 8c023848fc4..6d9f56c531e 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1327,7 +1327,7 @@ async def help_test_entity_debug_info_max_messages( start_dt = datetime(2019, 1, 1, 0, 0, 0, tzinfo=dt_util.UTC) with freeze_time(start_dt): - for i in range(0, debug_info.STORED_MESSAGES + 1): + for i in range(debug_info.STORED_MESSAGES + 1): async_fire_mqtt_message(hass, "test-topic", f"{i}") debug_info_data = debug_info.info_for_device(hass, device.id) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 0f3069ef1a1..0c3764ce8a8 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -4130,7 +4130,7 @@ async def test_multi_platform_discovery( }, } for platform, config in entity_configs.items(): - for set_number in range(0, 2): + for set_number in range(2): set_config = deepcopy(config) set_config["name"] = f"test_{set_number}" topic = f"homeassistant/{platform}/bla_{set_number}/config" @@ -4139,7 +4139,7 @@ async def test_multi_platform_discovery( topic = f"homeassistant/{platform}/bla/config" async_fire_mqtt_message(hass, topic, json.dumps(config)) await hass.async_block_till_done() - for set_number in range(0, 2): + for set_number in range(2): for platform in entity_configs: entity_id = f"{platform}.test_{set_number}" state = hass.states.get(entity_id) diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 8adbf05e46c..ff1b308ef70 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -983,7 +983,7 @@ async def test_controlling_the_state_with_legacy_color_handling( assert state.attributes.get("xy_color") is None assert not state.attributes.get(ATTR_ASSUMED_STATE) - for _ in range(0, 2): + for _ in range(2): # Returned state after the light was turned on # Receiving legacy color mode: rgb. async_fire_mqtt_message( diff --git a/tests/components/nest/test_media_source.py b/tests/components/nest/test_media_source.py index 88bef222c89..4810c8e2ff5 100644 --- a/tests/components/nest/test_media_source.py +++ b/tests/components/nest/test_media_source.py @@ -1053,7 +1053,7 @@ async def test_multiple_devices( assert len(browse.children) == 0 # Send events for device #1 - for i in range(0, 5): + for i in range(5): auth.responses = [ aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT), @@ -1078,7 +1078,7 @@ async def test_multiple_devices( assert len(browse.children) == 0 # Send events for device #2 - for i in range(0, 3): + for i in range(3): auth.responses = [ aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE), aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT), @@ -1340,7 +1340,7 @@ async def test_camera_event_media_eviction( assert len(browse.children) == 0 event_timestamp = dt_util.now() - for i in range(0, 7): + for i in range(7): auth.responses = [aiohttp.web.Response(body=f"image-bytes-{i}".encode())] ts = event_timestamp + datetime.timedelta(seconds=i) await subscriber.async_receive_event( diff --git a/tests/components/owntracks/test_device_tracker.py b/tests/components/owntracks/test_device_tracker.py index d855c318fb7..4462722bb53 100644 --- a/tests/components/owntracks/test_device_tracker.py +++ b/tests/components/owntracks/test_device_tracker.py @@ -964,7 +964,7 @@ async def test_mobile_exit_move_beacon(hass: HomeAssistant, context) -> None: async def test_mobile_multiple_async_enter_exit(hass: HomeAssistant, context) -> None: """Test the multiple entering.""" # Test race condition - for _ in range(0, 20): + for _ in range(20): async_fire_mqtt_message( hass, EVENT_TOPIC, json.dumps(MOBILE_BEACON_ENTER_EVENT_MESSAGE) ) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index 600570acf66..141ffc31cc0 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -72,7 +72,7 @@ async def test_schema_update_calls(recorder_db_url: str, hass: HomeAssistant) -> update.assert_has_calls( [ call(instance, hass, engine, session_maker, version + 1, 0) - for version in range(0, db_schema.SCHEMA_VERSION) + for version in range(db_schema.SCHEMA_VERSION) ] ) diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 2ca386734bb..01514385dd2 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -242,7 +242,7 @@ async def test_statistic_during_period( "min": -76 + i * 2, "sum": i, } - for i in range(0, 39) + for i in range(39) ] imported_stats = [] slice_end = 12 - offset @@ -255,7 +255,7 @@ async def test_statistic_during_period( "sum": imported_stats_5min[slice_end - 1]["sum"], } ) - for i in range(0, 2): + for i in range(2): slice_start = i * 12 + (12 - offset) slice_end = (i + 1) * 12 + (12 - offset) assert imported_stats_5min[slice_start]["start"].minute == 0 @@ -664,7 +664,7 @@ async def test_statistic_during_period_hole( "min": -76 + i * 2, "sum": i, } - for i in range(0, 6) + for i in range(6) ] imported_metadata = { diff --git a/tests/components/shopping_list/test_todo.py b/tests/components/shopping_list/test_todo.py index 373c449497c..fb6f61d4edf 100644 --- a/tests/components/shopping_list/test_todo.py +++ b/tests/components/shopping_list/test_todo.py @@ -166,7 +166,7 @@ async def test_bulk_remove( ) -> None: """Test removing a todo item.""" - for _i in range(0, 5): + for _i in range(5): await hass.services.async_call( TODO_DOMAIN, "add_item", diff --git a/tests/components/steam_online/__init__.py b/tests/components/steam_online/__init__.py index 46273a61816..c7d67509489 100644 --- a/tests/components/steam_online/__init__.py +++ b/tests/components/steam_online/__init__.py @@ -71,7 +71,7 @@ class MockedInterface(dict): fake_friends = [{"steamid": ACCOUNT_2}] fake_friends.extend( {"steamid": "".join(random.choices(string.digits, k=len(ACCOUNT_1)))} - for _ in range(0, 4) + for _ in range(4) ) return {"friendslist": {"friends": fake_friends}} diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index cf61368e12b..92221ebb88d 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -460,7 +460,7 @@ async def test_skip_initial_bad_packets(hass: HomeAssistant) -> None: num_packets = LONGER_TEST_SEQUENCE_LENGTH packets = list(PacketSequence(num_packets)) num_bad_packets = MAX_MISSING_DTS - 1 - for i in range(0, num_bad_packets): + for i in range(num_bad_packets): packets[i].dts = None decoded_stream = await async_decode_stream(hass, packets) @@ -490,7 +490,7 @@ async def test_too_many_initial_bad_packets_fails(hass: HomeAssistant) -> None: num_packets = LONGER_TEST_SEQUENCE_LENGTH packets = list(PacketSequence(num_packets)) num_bad_packets = MAX_MISSING_DTS + 1 - for i in range(0, num_bad_packets): + for i in range(num_bad_packets): packets[i].dts = None py_av = MockPyAv() diff --git a/tests/components/zwave_js/test_migrate.py b/tests/components/zwave_js/test_migrate.py index b5e88a745ef..41fa507a3a0 100644 --- a/tests/components/zwave_js/test_migrate.py +++ b/tests/components/zwave_js/test_migrate.py @@ -278,7 +278,7 @@ async def test_different_endpoint_migration_status_sensor( assert entity_entry.unique_id == old_unique_id # Do this twice to make sure re-interview doesn't do anything weird - for _ in range(0, 2): + for _ in range(2): # Add a ready node, unique ID should be migrated event = {"node": node} driver.controller.emit("node added", event) @@ -387,7 +387,7 @@ async def test_old_entity_migration_notification_binary_sensor( assert entity_entry.unique_id == old_unique_id # Do this twice to make sure re-interview doesn't do anything weird - for _ in range(0, 2): + for _ in range(2): # Add a ready node, unique ID should be migrated event = {"node": node} driver.controller.emit("node added", event) From 398c38b50dbc4b38a6ce8aa544c1392e6639f9af Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 17 Mar 2024 10:06:57 +0100 Subject: [PATCH 1199/1691] Add tests of homeworks button (#113578) --- .coveragerc | 1 - tests/components/homeworks/test_button.py | 57 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/components/homeworks/test_button.py diff --git a/.coveragerc b/.coveragerc index dcc51b83cc5..4971581a2bc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -550,7 +550,6 @@ omit = homeassistant/components/homematic/sensor.py homeassistant/components/homematic/switch.py homeassistant/components/homeworks/__init__.py - homeassistant/components/homeworks/button.py homeassistant/components/horizon/media_player.py homeassistant/components/hp_ilo/sensor.py homeassistant/components/huawei_lte/__init__.py diff --git a/tests/components/homeworks/test_button.py b/tests/components/homeworks/test_button.py new file mode 100644 index 00000000000..6f0250d980a --- /dev/null +++ b/tests/components/homeworks/test_button.py @@ -0,0 +1,57 @@ +"""Tests for the Lutron Homeworks Series 4 and 8 button.""" +from unittest.mock import MagicMock + +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def test_button_service_calls( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test Homeworks button service call.""" + entity_id = "button.foyer_keypad_morning" + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert entity_id in hass.states.async_entity_ids(BUTTON_DOMAIN) + + mock_controller._send.reset_mock() + await hass.services.async_call( + BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert len(mock_controller._send.mock_calls) == 1 + assert mock_controller._send.mock_calls[0][1] == ("KBP, [02:08:02:01], 1",) + + +async def test_button_service_calls_delay( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test Homeworks button service call.""" + entity_id = "button.foyer_keypad_dim_up" + mock_controller = MagicMock() + mock_homeworks.return_value = mock_controller + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert entity_id in hass.states.async_entity_ids(BUTTON_DOMAIN) + + mock_controller._send.reset_mock() + await hass.services.async_call( + BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True + ) + assert len(mock_controller._send.mock_calls) == 2 + assert mock_controller._send.mock_calls[0][1] == ("KBP, [02:08:02:01], 3",) + assert mock_controller._send.mock_calls[1][1] == ("KBR, [02:08:02:01], 3",) From 7b20641651612e2b30bb2139bf5340558b9b94fc Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 17 Mar 2024 10:22:08 +0100 Subject: [PATCH 1200/1691] Add tests of homeworks __init__ (#113581) --- .coveragerc | 1 - tests/components/homeworks/test_init.py | 115 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 tests/components/homeworks/test_init.py diff --git a/.coveragerc b/.coveragerc index 4971581a2bc..adf3c7b6eeb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -549,7 +549,6 @@ omit = homeassistant/components/homematic/notify.py homeassistant/components/homematic/sensor.py homeassistant/components/homematic/switch.py - homeassistant/components/homeworks/__init__.py homeassistant/components/horizon/media_player.py homeassistant/components/hp_ilo/sensor.py homeassistant/components/huawei_lte/__init__.py diff --git a/tests/components/homeworks/test_init.py b/tests/components/homeworks/test_init.py new file mode 100644 index 00000000000..a3eb864c9da --- /dev/null +++ b/tests/components/homeworks/test_init.py @@ -0,0 +1,115 @@ +"""Tests for the Lutron Homeworks Series 4 and 8 integration.""" +from unittest.mock import ANY, MagicMock + +from pyhomeworks.pyhomeworks import HW_BUTTON_PRESSED, HW_BUTTON_RELEASED + +from homeassistant.components.homeworks import EVENT_BUTTON_PRESS, EVENT_BUTTON_RELEASE +from homeassistant.components.homeworks.const import CONF_DIMMERS, CONF_KEYPADS, DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry, async_capture_events + + +async def test_import( + hass: HomeAssistant, + mock_homeworks: MagicMock, +) -> None: + """Test the Homeworks YAML import.""" + await async_setup_component( + hass, + DOMAIN, + { + DOMAIN: { + CONF_HOST: "192.168.0.1", + CONF_PORT: 1234, + CONF_DIMMERS: [], + CONF_KEYPADS: [], + } + }, + ) + await hass.async_block_till_done() + + assert len(hass.config_entries.flow.async_progress()) == 1 + assert hass.config_entries.flow.async_progress()[0]["context"]["source"] == "import" + + +async def test_load_unload_config_entry( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test the Homeworks configuration entry loading/unloading.""" + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + mock_homeworks.assert_called_once_with("192.168.0.1", 1234, ANY) + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert not hass.data.get(DOMAIN) + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + + +async def test_config_entry_not_ready( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test the Homeworks configuration entry not ready.""" + mock_homeworks.side_effect = ConnectionError + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_homeworks.assert_called_once() + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_keypad_events( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_homeworks: MagicMock, +) -> None: + """Test Homeworks keypad events.""" + release_events = async_capture_events(hass, EVENT_BUTTON_RELEASE) + press_events = async_capture_events(hass, EVENT_BUTTON_PRESS) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_homeworks.assert_called_once_with("192.168.0.1", 1234, ANY) + hw_callback = mock_homeworks.mock_calls[0][1][2] + + hw_callback(HW_BUTTON_PRESSED, ["[02:08:02:01]", 1]) + await hass.async_block_till_done() + assert len(press_events) == 1 + assert len(release_events) == 0 + assert press_events[0].data == { + "id": "foyer_keypad", + "name": "Foyer Keypad", + "button": 1, + } + assert press_events[0].event_type == "homeworks_button_press" + + hw_callback(HW_BUTTON_RELEASED, ["[02:08:02:01]", 1]) + await hass.async_block_till_done() + assert len(press_events) == 1 + assert len(release_events) == 1 + assert release_events[0].data == { + "id": "foyer_keypad", + "name": "Foyer Keypad", + "button": 1, + } + assert release_events[0].event_type == "homeworks_button_release" + + hw_callback("unsupported", ["[02:08:02:01]", 1]) + await hass.async_block_till_done() + assert len(press_events) == 1 + assert len(release_events) == 1 From 6113b99ddd6885c887571ea141ac218281221252 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 17 Mar 2024 10:58:14 +0100 Subject: [PATCH 1201/1691] Replace pylint pointless-statement with ruff B018 (#113582) * Replace pylint pointless-statement with ruff B018 * fix occurrences of B018 * disable pylint expression-not-assigned as well --------- Co-authored-by: J. Nick Koston --- homeassistant/bootstrap.py | 2 +- homeassistant/components/auth/indieauth.py | 2 +- homeassistant/components/lutron/cover.py | 2 +- homeassistant/components/lutron/fan.py | 2 +- homeassistant/components/lutron/light.py | 2 +- homeassistant/components/lutron/switch.py | 4 ++-- pyproject.toml | 5 +++-- tests/components/event/test_init.py | 2 +- tests/components/history/test_websocket_api.py | 1 - tests/components/kira/test_sensor.py | 2 +- tests/components/valve/test_init.py | 2 +- tests/test_core.py | 2 +- tests/test_loader.py | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 89e36e7e557..85ee15e3aa0 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -340,7 +340,7 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None: asyncio event loop. By primeing the cache of uname we can avoid the blocking call in the event loop. """ - platform.uname().processor # pylint: disable=expression-not-assigned + _ = platform.uname().processor # Load the registries and cache the result of platform.uname().processor translation.async_setup(hass) diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index 9284c232d38..2c90d33b1d8 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -183,7 +183,7 @@ def _parse_client_id(client_id: str) -> ParseResult: # MAY contain a port try: # parts raises ValueError when port cannot be parsed as int - parts.port + _ = parts.port except ValueError as ex: raise ValueError("Client ID contains invalid port") from ex diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index 247ead3ccec..2f80798aee4 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -70,7 +70,7 @@ class LutronCover(LutronDevice, CoverEntity): def _request_state(self) -> None: """Request the state from the device.""" - self._lutron_device.level # pylint: disable=pointless-statement + _ = self._lutron_device.level def _update_attrs(self) -> None: """Update the state attributes.""" diff --git a/homeassistant/components/lutron/fan.py b/homeassistant/components/lutron/fan.py index 07e0bb444b2..c350e70b222 100644 --- a/homeassistant/components/lutron/fan.py +++ b/homeassistant/components/lutron/fan.py @@ -79,7 +79,7 @@ class LutronFan(LutronDevice, FanEntity): def _request_state(self) -> None: """Request the state from the device.""" - self._lutron_device.level # pylint: disable=pointless-statement + _ = self._lutron_device.level def _update_attrs(self) -> None: """Update the state attributes.""" diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index f4088d687cd..18b5edd1039 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -171,7 +171,7 @@ class LutronLight(LutronDevice, LightEntity): def _request_state(self) -> None: """Request the state from the device.""" - self._lutron_device.level # pylint: disable=pointless-statement + _ = self._lutron_device.level def _update_attrs(self) -> None: """Update the state attributes.""" diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 9ffce0aa530..c8b93dd7398 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -61,7 +61,7 @@ class LutronSwitch(LutronDevice, SwitchEntity): def _request_state(self) -> None: """Request the state from the device.""" - self._lutron_device.level # pylint: disable=pointless-statement + _ = self._lutron_device.level def _update_attrs(self) -> None: """Update the state attributes.""" @@ -105,7 +105,7 @@ class LutronLed(LutronKeypad, SwitchEntity): def _request_state(self) -> None: """Request the state from the device.""" - self._lutron_device.state # pylint: disable=pointless-statement + _ = self._lutron_device.state def _update_attrs(self) -> None: """Update the state attributes.""" diff --git a/pyproject.toml b/pyproject.toml index 0bd8e592d86..5c6fbd2c1e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -231,7 +231,7 @@ disable = [ "duplicate-value", # F "eval-used", # S307 "exec-used", # S102 - # "expression-not-assigned", # B018, ruff catches new occurrences, needs more work + "expression-not-assigned", # B018 "f-string-without-interpolation", # F541 "forgotten-debug-statement", # T100 "format-string-without-interpolation", # F @@ -248,7 +248,7 @@ disable = [ "misplaced-future", # F404 "named-expr-without-context", # PLW0131 "nested-min-max", # PLW3301 - # "pointless-statement", # B018, ruff catches new occurrences, needs more work + "pointless-statement", # B018 "raise-missing-from", # B904 # "redefined-builtin", # A001, ruff is way more stricter, needs work "try-except-raise", # TRY302 @@ -585,6 +585,7 @@ select = [ "B007", # Loop control variable {name} not used within loop body "B014", # Exception handler with duplicate exception "B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. + "B018", # Found useless attribute access. Either assign it to a variable or remove it. "B023", # Function definition does not bind loop variable {name} "B026", # Star-arg unpacking after a keyword argument is strongly discouraged "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)? diff --git a/tests/components/event/test_init.py b/tests/components/event/test_init.py index 41bdac5e6bd..25cb5af3e08 100644 --- a/tests/components/event/test_init.py +++ b/tests/components/event/test_init.py @@ -49,7 +49,7 @@ async def test_event() -> None: # No event types defined, should raise with pytest.raises(AttributeError): - event.event_types + _ = event.event_types # Test retrieving data from entity description event.entity_description = EventEntityDescription( diff --git a/tests/components/history/test_websocket_api.py b/tests/components/history/test_websocket_api.py index 72fd9420a61..154e29a0989 100644 --- a/tests/components/history/test_websocket_api.py +++ b/tests/components/history/test_websocket_api.py @@ -1835,7 +1835,6 @@ async def test_history_stream_historical_only_with_start_time_state_past( await async_setup_component(hass, "sensor", {}) hass.states.async_set("sensor.one", "first", attributes={"any": "attr"}) - hass.states.get("sensor.one").last_updated await async_recorder_block_till_done(hass) await asyncio.sleep(0.00002) diff --git a/tests/components/kira/test_sensor.py b/tests/components/kira/test_sensor.py index 8619c6953ab..fe0fc95a918 100644 --- a/tests/components/kira/test_sensor.py +++ b/tests/components/kira/test_sensor.py @@ -39,7 +39,7 @@ def test_kira_sensor_callback( codeTuple = (codeName, deviceName) sensor._update_callback(codeTuple) - mock_schedule_update_ha_state.assert_called + mock_schedule_update_ha_state.assert_called() assert sensor.state == codeName assert sensor.extra_state_attributes == {kira.CONF_DEVICE: deviceName} diff --git a/tests/components/valve/test_init.py b/tests/components/valve/test_init.py index 314819c4cc8..23071718724 100644 --- a/tests/components/valve/test_init.py +++ b/tests/components/valve/test_init.py @@ -298,7 +298,7 @@ async def test_valve_report_position(hass: HomeAssistant) -> None: default_valve.hass = hass with pytest.raises(ValueError): - default_valve.reports_position + _ = default_valve.reports_position second_valve = MockValveEntity(reports_position=True) second_valve.hass = hass diff --git a/tests/test_core.py b/tests/test_core.py index da4a87702f5..78229c4b445 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2422,7 +2422,7 @@ async def test_hassjob_forbid_coroutine() -> None: coro = bla() with pytest.raises(ValueError): - ha.HassJob(coro).job_type + _ = ha.HassJob(coro).job_type # To avoid warning about unawaited coro await coro diff --git a/tests/test_loader.py b/tests/test_loader.py index a2868976876..ae21d75a6e1 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -72,7 +72,7 @@ def test_component_loader_non_existing(hass: HomeAssistant) -> None: """Test loading components.""" components = loader.Components(hass) with pytest.raises(ImportError): - components.non_existing + _ = components.non_existing async def test_component_wrapper(hass: HomeAssistant) -> None: From 0643ff1cfe1057617cb6fda410084022f99d9252 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 17 Mar 2024 13:44:32 +0100 Subject: [PATCH 1202/1691] Improve debug logging in Tankerkoenig (#113674) --- .../components/tankerkoenig/coordinator.py | 34 +++++++-- .../tankerkoenig/test_coordinator.py | 75 ++++++++++++++++++- 2 files changed, 100 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/tankerkoenig/coordinator.py b/homeassistant/components/tankerkoenig/coordinator.py index f6c021cb74e..447099d2dca 100644 --- a/homeassistant/components/tankerkoenig/coordinator.py +++ b/homeassistant/components/tankerkoenig/coordinator.py @@ -62,8 +62,18 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): try: station = await self._tankerkoenig.station_details(station_id) except TankerkoenigInvalidKeyError as err: + _LOGGER.debug( + "invalid key error occur during setup of station %s %s", + station_id, + err, + ) raise ConfigEntryAuthFailed(err) from err except TankerkoenigConnectionError as err: + _LOGGER.debug( + "connection error occur during setup of station %s %s", + station_id, + err, + ) raise ConfigEntryNotReady(err) from err except TankerkoenigError as err: _LOGGER.error("Error when adding station %s %s", station_id, err) @@ -86,17 +96,27 @@ class TankerkoenigDataUpdateCoordinator(DataUpdateCoordinator): # The API seems to only return at most 10 results, so split the list in chunks of 10 # and merge it together. for index in range(ceil(len(station_ids) / 10)): + stations = station_ids[index * 10 : (index + 1) * 10] try: - data = await self._tankerkoenig.prices( - station_ids[index * 10 : (index + 1) * 10] - ) + data = await self._tankerkoenig.prices(stations) except TankerkoenigInvalidKeyError as err: + _LOGGER.debug( + "invalid key error occur during update of stations %s %s", + stations, + err, + ) raise ConfigEntryAuthFailed(err) from err + except TankerkoenigRateLimitError as err: + _LOGGER.warning( + "API rate limit reached, consider to increase polling interval" + ) + raise UpdateFailed(err) from err except (TankerkoenigError, TankerkoenigConnectionError) as err: - if isinstance(err, TankerkoenigRateLimitError): - _LOGGER.warning( - "API rate limit reached, consider to increase polling interval" - ) + _LOGGER.debug( + "error occur during update of stations %s %s", + stations, + err, + ) raise UpdateFailed(err) from err prices.update(data) diff --git a/tests/components/tankerkoenig/test_coordinator.py b/tests/components/tankerkoenig/test_coordinator.py index e1604c77819..5a33cb95dd9 100644 --- a/tests/components/tankerkoenig/test_coordinator.py +++ b/tests/components/tankerkoenig/test_coordinator.py @@ -5,13 +5,19 @@ from __future__ import annotations from datetime import timedelta from unittest.mock import AsyncMock -from aiotankerkoenig.exceptions import TankerkoenigRateLimitError +from aiotankerkoenig.exceptions import ( + TankerkoenigConnectionError, + TankerkoenigError, + TankerkoenigInvalidKeyError, + TankerkoenigRateLimitError, +) import pytest -from homeassistant.components.tankerkoenig.const import DEFAULT_SCAN_INTERVAL +from homeassistant.components.tankerkoenig.const import DEFAULT_SCAN_INTERVAL, DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from tests.common import MockConfigEntry, async_fire_time_changed @@ -50,3 +56,68 @@ async def test_rate_limit( state = hass.states.get("binary_sensor.station_somewhere_street_1_status") assert state assert state.state == "on" + + +@pytest.mark.parametrize( + ("exception", "expected_log"), + [ + ( + TankerkoenigInvalidKeyError, + "invalid key error occur during update of stations", + ), + ( + TankerkoenigRateLimitError, + "API rate limit reached, consider to increase polling interval", + ), + (TankerkoenigConnectionError, "error occur during update of stations"), + (TankerkoenigError, "error occur during update of stations"), + ], +) +@pytest.mark.usefixtures("setup_integration") +async def test_update_exception_logging( + hass: HomeAssistant, + config_entry: MockConfigEntry, + tankerkoenig: AsyncMock, + caplog: pytest.LogCaptureFixture, + exception: None, + expected_log: str, +) -> None: + """Test log messages about exceptions during update.""" + tankerkoenig.prices.side_effect = exception + async_fire_time_changed( + hass, dt_util.utcnow() + timedelta(minutes=DEFAULT_SCAN_INTERVAL) + ) + await hass.async_block_till_done() + assert expected_log in caplog.text + state = hass.states.get("binary_sensor.station_somewhere_street_1_status") + assert state + assert state.state == STATE_UNAVAILABLE + + +@pytest.mark.parametrize( + ("exception", "expected_log"), + [ + ( + TankerkoenigInvalidKeyError, + "invalid key error occur during setup of station", + ), + (TankerkoenigConnectionError, "connection error occur during setup of station"), + (TankerkoenigError, "Error when adding station"), + ], +) +async def test_setup_exception_logging( + hass: HomeAssistant, + config_entry: MockConfigEntry, + tankerkoenig: AsyncMock, + caplog: pytest.LogCaptureFixture, + exception: None, + expected_log: str, +) -> None: + """Test log messages about exceptions during setup.""" + config_entry.add_to_hass(hass) + tankerkoenig.station_details.side_effect = exception + + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + assert expected_log in caplog.text From b8e18627465581955736e5c015205309944ad569 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 17 Mar 2024 15:59:29 +0100 Subject: [PATCH 1203/1691] Remove deprecated `hass.components` from image_processing platform (#113613) * Remove deprecated `hass.components` from image_processing platform * Add test and change log * D'oh.. use updated error text --- .../components/image_processing/__init__.py | 13 ++++++----- .../components/image_processing/test_init.py | 22 +++++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index b75cb445895..ed366c0bdfc 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -10,7 +10,7 @@ from typing import Any, Final, TypedDict, final import voluptuous as vol -from homeassistant.components.camera import Image +from homeassistant.components.camera import async_get_image from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, @@ -176,13 +176,16 @@ class ImageProcessingEntity(Entity): This method is a coroutine. """ - camera = self.hass.components.camera + if self.camera_entity is None: + _LOGGER.error( + "No camera entity id was set by the image processing entity", + ) + return try: - image: Image = await camera.async_get_image( - self.camera_entity, timeout=self.timeout + image = await async_get_image( + self.hass, self.camera_entity, timeout=self.timeout ) - except HomeAssistantError as err: _LOGGER.error("Error on receive image from entity: %s", err) return diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 88aedc548ce..cf92047b49a 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -1,5 +1,4 @@ """The tests for the image_processing component.""" - from unittest.mock import PropertyMock, patch import pytest @@ -103,7 +102,7 @@ async def test_get_image_from_camera( @patch( - "homeassistant.components.camera.async_get_image", + "homeassistant.components.image_processing.async_get_image", side_effect=HomeAssistantError(), ) async def test_get_image_without_exists_camera( @@ -180,3 +179,22 @@ async def test_face_event_call_no_confidence( assert event_data[0]["confidence"] == 98.34 assert event_data[0]["gender"] == "male" assert event_data[0]["entity_id"] == "image_processing.demo_face" + + +async def test_update_missing_camera( + hass: HomeAssistant, + aiohttp_unused_port_factory, + enable_custom_integrations: None, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test when entity does not set camera.""" + await setup_image_processing(hass, aiohttp_unused_port_factory) + + with patch( + "custom_components.test.image_processing.TestImageProcessing.camera_entity", + new_callable=PropertyMock(return_value=None), + ): + common.async_scan(hass, entity_id="image_processing.test") + await hass.async_block_till_done() + + assert "No camera entity id was set by the image processing entity" in caplog.text From 929bcb92e2f0add2bd9f42cd1a243ffdea56ed6f Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 17 Mar 2024 15:59:49 +0100 Subject: [PATCH 1204/1691] Add Ruff LOG rules (#113677) * Add Ruff LOG rules * Address review comment * Update const.py --- homeassistant/components/logger/const.py | 2 +- homeassistant/components/system_log/__init__.py | 2 +- homeassistant/components/zha/core/gateway.py | 2 +- pyproject.toml | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/logger/const.py b/homeassistant/components/logger/const.py index 483d1e8a097..bc026d196b7 100644 --- a/homeassistant/components/logger/const.py +++ b/homeassistant/components/logger/const.py @@ -21,7 +21,7 @@ LOGSEVERITY = { LOGSEVERITY_FATAL: logging.FATAL, LOGSEVERITY_ERROR: logging.ERROR, LOGSEVERITY_WARNING: logging.WARNING, - LOGSEVERITY_WARN: logging.WARN, + LOGSEVERITY_WARN: logging.WARNING, LOGSEVERITY_INFO: logging.INFO, LOGSEVERITY_DEBUG: logging.DEBUG, LOGSEVERITY_NOTSET: logging.NOTSET, diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 83a89a277cc..47351b196ba 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -294,7 +294,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: handler = LogErrorHandler( hass, conf[CONF_MAX_ENTRIES], conf[CONF_FIRE_EVENT], paths_re ) - handler.setLevel(logging.WARN) + handler.setLevel(logging.WARNING) hass.data[DOMAIN] = handler diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 55714bfef15..6706aef91f3 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -870,7 +870,7 @@ class LogRelayHandler(logging.Handler): def emit(self, record: LogRecord) -> None: """Relay log message via dispatcher.""" entry = LogEntry( - record, self.paths_re, figure_out_source=record.levelno >= logging.WARN + record, self.paths_re, figure_out_source=record.levelno >= logging.WARNING ) async_dispatcher_send( self.hass, diff --git a/pyproject.toml b/pyproject.toml index 5c6fbd2c1e7..56e5656a1d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -601,6 +601,7 @@ select = [ "I", # isort "ISC", # flake8-implicit-str-concat "ICN001", # import concentions; {name} should be imported as {asname} + "LOG", # flake8-logging "N804", # First argument of a class method should be named cls "N805", # First argument of a method should be named self "N815", # Variable {name} in class scope should not be mixedCase From 81ab29a38e713d413ae3c4f69ee3d0c29c573e46 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 17 Mar 2024 16:02:45 +0100 Subject: [PATCH 1205/1691] Freeze time on zwave_js update test (#113625) Freeze time on jwave_js update test --- tests/components/zwave_js/test_update.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/zwave_js/test_update.py b/tests/components/zwave_js/test_update.py index 64c72ddc1fa..c5cfba18569 100644 --- a/tests/components/zwave_js/test_update.py +++ b/tests/components/zwave_js/test_update.py @@ -3,6 +3,7 @@ import asyncio from datetime import timedelta +from freezegun.api import FrozenDateTimeFactory import pytest from zwave_js_server.event import Event from zwave_js_server.exceptions import FailedZWaveCommand @@ -629,6 +630,7 @@ async def test_update_entity_delay( ge_in_wall_dimmer_switch, zen_31, hass_ws_client: WebSocketGenerator, + freezer: FrozenDateTimeFactory, ) -> None: """Test update occurs on a delay after HA starts.""" client.async_send_command.reset_mock() From 295b4203a117d8abe5657d69373768117e4cb92c Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 17 Mar 2024 16:04:40 +0100 Subject: [PATCH 1206/1691] Re-ignore Ruff rule PLC1901 (#113675) Re-ignore PLC1901 --- pyproject.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 56e5656a1d7..4703c6178e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -649,11 +649,7 @@ ignore = [ "E501", # line too long "E731", # do not assign a lambda expression, use a def - # Ignore ignored, as the rule is now back in preview/nursery, which cannot - # be ignored anymore without warnings. - # https://github.com/astral-sh/ruff/issues/7491 - # "PLC1901", # Lots of false positives - + "PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives "PLR0911", # Too many return statements ({returns} > {max_returns}) "PLR0912", # Too many branches ({branches} > {max_branches}) "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) From 75a489deb9e828525c056b6939e83ac9d2e21445 Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 17 Mar 2024 15:06:57 +0000 Subject: [PATCH 1207/1691] Add WAN port connection control to TP-Link Omada gateways (#111431) * Add gateway connection switches * Add unit tests * tplink_omada code review feedback * Rebase and move icons and strings into json --- .../components/tplink_omada/__init__.py | 4 + .../components/tplink_omada/binary_sensor.py | 139 ++- .../components/tplink_omada/icons.json | 14 + .../components/tplink_omada/strings.json | 27 + .../components/tplink_omada/switch.py | 155 ++- tests/components/tplink_omada/conftest.py | 16 +- .../tplink_omada/fixtures/devices.json | 41 + .../fixtures/gateway-TL-ER7212PC.json | 1040 +++++++++++++++++ .../fixtures/switch-TL-SG3210XHP-M2.json | 4 +- .../switch-ports-TL-SG3210XHP-M2.json | 2 +- .../tplink_omada/snapshots/test_switch.ambr | 57 +- tests/components/tplink_omada/test_switch.py | 158 ++- 12 files changed, 1565 insertions(+), 92 deletions(-) create mode 100644 tests/components/tplink_omada/fixtures/devices.json create mode 100644 tests/components/tplink_omada/fixtures/gateway-TL-ER7212PC.json diff --git a/homeassistant/components/tplink_omada/__init__.py b/homeassistant/components/tplink_omada/__init__.py index f6efeea7678..fa022fcac77 100644 --- a/homeassistant/components/tplink_omada/__init__.py +++ b/homeassistant/components/tplink_omada/__init__.py @@ -47,6 +47,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: site_client = await client.get_site_client(OmadaSite("", entry.data[CONF_SITE])) controller = OmadaSiteController(hass, site_client) + gateway_coordinator = await controller.get_gateway_coordinator() + if gateway_coordinator: + await gateway_coordinator.async_config_entry_first_refresh() + hass.data[DOMAIN][entry.entry_id] = controller await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/tplink_omada/binary_sensor.py b/homeassistant/components/tplink_omada/binary_sensor.py index 7b2191f7832..7bee6159dd7 100644 --- a/homeassistant/components/tplink_omada/binary_sensor.py +++ b/homeassistant/components/tplink_omada/binary_sensor.py @@ -2,19 +2,21 @@ from __future__ import annotations -from collections.abc import Callable, Generator +from collections.abc import Callable +from dataclasses import dataclass -from attr import dataclass -from tplink_omada_client.definitions import GatewayPortMode, LinkStatus +from tplink_omada_client.definitions import GatewayPortMode, LinkStatus, PoEMode from tplink_omada_client.devices import ( OmadaDevice, OmadaGateway, + OmadaGatewayPortConfig, OmadaGatewayPortStatus, ) from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, + BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -32,94 +34,103 @@ async def async_setup_entry( ) -> None: """Set up binary sensors.""" controller: OmadaSiteController = hass.data[DOMAIN][config_entry.entry_id] - omada_client = controller.omada_client gateway_coordinator = await controller.get_gateway_coordinator() if not gateway_coordinator: return - gateway = await omada_client.get_gateway(gateway_coordinator.mac) - - async_add_entities( - get_gateway_port_status_sensors(gateway, hass, gateway_coordinator) - ) - - await gateway_coordinator.async_request_refresh() - - -def get_gateway_port_status_sensors( - gateway: OmadaGateway, hass: HomeAssistant, coordinator: OmadaGatewayCoordinator -) -> Generator[BinarySensorEntity, None, None]: - """Generate binary sensors for gateway ports.""" - for port in gateway.port_status: - if port.mode == GatewayPortMode.WAN: - yield OmadaGatewayPortBinarySensor( - coordinator, - gateway, - GatewayPortBinarySensorConfig( - port_number=port.port_number, - id_suffix="wan_link", - name_suffix="Internet Link", - device_class=BinarySensorDeviceClass.CONNECTIVITY, - update_func=lambda p: p.wan_connected, - ), - ) - if port.mode == GatewayPortMode.LAN: - yield OmadaGatewayPortBinarySensor( - coordinator, - gateway, - GatewayPortBinarySensorConfig( - port_number=port.port_number, - id_suffix="lan_status", - name_suffix="LAN Status", - device_class=BinarySensorDeviceClass.CONNECTIVITY, - update_func=lambda p: p.link_status == LinkStatus.LINK_UP, - ), + entities: list[OmadaDeviceEntity] = [] + for gateway in gateway_coordinator.data.values(): + entities.extend( + OmadaGatewayPortBinarySensor( + gateway_coordinator, gateway, p.port_number, desc ) + for p in gateway.port_configs + for desc in GATEWAY_PORT_SENSORS + if desc.exists_func(p) + ) + + async_add_entities(entities) -@dataclass -class GatewayPortBinarySensorConfig: - """Config for a binary status derived from a gateway port.""" +@dataclass(frozen=True, kw_only=True) +class GatewayPortBinarySensorEntityDescription(BinarySensorEntityDescription): + """Entity description for a binary status derived from a gateway port.""" - port_number: int - id_suffix: str - name_suffix: str - device_class: BinarySensorDeviceClass + exists_func: Callable[[OmadaGatewayPortConfig], bool] = lambda _: True update_func: Callable[[OmadaGatewayPortStatus], bool] +GATEWAY_PORT_SENSORS: list[GatewayPortBinarySensorEntityDescription] = [ + GatewayPortBinarySensorEntityDescription( + key="wan_link", + translation_key="wan_link", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + exists_func=lambda p: p.port_status.mode == GatewayPortMode.WAN, + update_func=lambda p: p.wan_connected, + ), + GatewayPortBinarySensorEntityDescription( + key="online_detection", + translation_key="online_detection", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + exists_func=lambda p: p.port_status.mode == GatewayPortMode.WAN, + update_func=lambda p: p.online_detection, + ), + GatewayPortBinarySensorEntityDescription( + key="lan_status", + translation_key="lan_status", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + exists_func=lambda p: p.port_status.mode == GatewayPortMode.LAN, + update_func=lambda p: p.link_status == LinkStatus.LINK_UP, + ), + GatewayPortBinarySensorEntityDescription( + key="poe_delivery", + translation_key="poe_delivery", + device_class=BinarySensorDeviceClass.POWER, + exists_func=lambda p: ( + p.port_status.mode == GatewayPortMode.LAN and p.poe_mode == PoEMode.ENABLED + ), + update_func=lambda p: p.poe_active, + ), +] + + class OmadaGatewayPortBinarySensor(OmadaDeviceEntity[OmadaGateway], BinarySensorEntity): """Binary status of a property on an internet gateway.""" + entity_description: GatewayPortBinarySensorEntityDescription _attr_has_entity_name = True def __init__( self, coordinator: OmadaGatewayCoordinator, device: OmadaDevice, - config: GatewayPortBinarySensorConfig, + port_number: int, + entity_description: GatewayPortBinarySensorEntityDescription, ) -> None: """Initialize the gateway port binary sensor.""" super().__init__(coordinator, device) - self._config = config - self._attr_unique_id = f"{device.mac}_{config.port_number}_{config.id_suffix}" - self._attr_device_class = config.device_class + self.entity_description = entity_description + self._port_number = port_number + self._attr_unique_id = f"{device.mac}_{port_number}_{entity_description.key}" + self._attr_translation_placeholders = {"port_name": f"{port_number}"} - self._attr_name = f"Port {config.port_number} {config.name_suffix}" + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self._do_update() + + def _do_update(self) -> None: + gateway = self.coordinator.data[self.device.mac] + + port = next( + p for p in gateway.port_status if p.port_number == self._port_number + ) + if port: + self._attr_is_on = self.entity_description.update_func(port) @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" - gateway = self.coordinator.data[self.device.mac] - - port = next( - p for p in gateway.port_status if p.port_number == self._config.port_number - ) - if port: - self._attr_is_on = self._config.update_func(port) - self._attr_available = True - else: - self._attr_available = False - + self._do_update() self.async_write_ha_state() diff --git a/homeassistant/components/tplink_omada/icons.json b/homeassistant/components/tplink_omada/icons.json index 38efc9068be..d0c407a9326 100644 --- a/homeassistant/components/tplink_omada/icons.json +++ b/homeassistant/components/tplink_omada/icons.json @@ -3,6 +3,20 @@ "switch": { "poe_control": { "default": "mdi:ethernet" + }, + "wan_connect_ipv4": { + "default": "mdi:wan" + }, + "wan_connect_ipv6": { + "default": "mdi:wan" + } + }, + "binary_sensor": { + "online_detection": { + "default": "mdi:cloud-check", + "state": { + "off": "mdi:cloud-cancel" + } } } } diff --git a/homeassistant/components/tplink_omada/strings.json b/homeassistant/components/tplink_omada/strings.json index 04fa6d162d3..49873b7d088 100644 --- a/homeassistant/components/tplink_omada/strings.json +++ b/homeassistant/components/tplink_omada/strings.json @@ -39,5 +39,32 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } + }, + "entity": { + "switch": { + "poe_control": { + "name": "Port {port_name} PoE" + }, + "wan_connect_ipv4": { + "name": "Port {port_name} Internet Connected" + }, + "wan_connect_ipv6": { + "name": "Port {port_name} Internet Connected (IPv6)" + } + }, + "binary_sensor": { + "wan_link": { + "name": "Port {port_name} Internet Link" + }, + "online_detection": { + "name": "Port {port_name} Online Detection" + }, + "lan_status": { + "name": "Port {port_name} LAN Status" + }, + "poe_delivery": { + "name": "Port {port_name} PoE Delivery" + } + } } } diff --git a/homeassistant/components/tplink_omada/switch.py b/homeassistant/components/tplink_omada/switch.py index ba65f397bd0..30974e829e2 100644 --- a/homeassistant/components/tplink_omada/switch.py +++ b/homeassistant/components/tplink_omada/switch.py @@ -2,20 +2,33 @@ from __future__ import annotations +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from functools import partial from typing import Any -from tplink_omada_client import SwitchPortOverrides -from tplink_omada_client.definitions import PoEMode -from tplink_omada_client.devices import OmadaSwitch, OmadaSwitchPortDetails +from tplink_omada_client import OmadaSiteClient, SwitchPortOverrides +from tplink_omada_client.definitions import GatewayPortMode, PoEMode +from tplink_omada_client.devices import ( + OmadaDevice, + OmadaGateway, + OmadaGatewayPortStatus, + OmadaSwitch, + OmadaSwitchPortDetails, +) -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN -from .controller import OmadaSiteController, OmadaSwitchPortCoordinator +from .controller import ( + OmadaGatewayCoordinator, + OmadaSiteController, + OmadaSwitchPortCoordinator, +) from .entity import OmadaDeviceEntity @@ -44,15 +57,69 @@ async def async_setup_entry( OmadaNetworkSwitchPortPoEControl(coordinator, switch, port_id) ) + gateway_coordinator = await controller.get_gateway_coordinator() + if gateway_coordinator: + for gateway in gateway_coordinator.data.values(): + entities.extend( + OmadaGatewayPortSwitchEntity( + gateway_coordinator, gateway, p.port_number, desc + ) + for p in gateway.port_status + for desc in GATEWAY_PORT_SWITCHES + if desc.exists_func(p) + ) + async_add_entities(entities) +@dataclass(frozen=True, kw_only=True) +class GatewayPortSwitchEntityDescription(SwitchEntityDescription): + """Entity description for a toggle switch derived from a gateway port.""" + + exists_func: Callable[[OmadaGatewayPortStatus], bool] = lambda _: True + set_func: Callable[ + [OmadaSiteClient, OmadaDevice, OmadaGatewayPortStatus, bool], + Awaitable[OmadaGatewayPortStatus], + ] + update_func: Callable[[OmadaGatewayPortStatus], bool] + + +def _wan_connect_disconnect( + client: OmadaSiteClient, + device: OmadaDevice, + port: OmadaGatewayPortStatus, + enable: bool, + ipv6: bool, +) -> Awaitable[OmadaGatewayPortStatus]: + return client.set_gateway_wan_port_connect_state( + port.port_number, enable, device, ipv6=ipv6 + ) + + +GATEWAY_PORT_SWITCHES: list[GatewayPortSwitchEntityDescription] = [ + GatewayPortSwitchEntityDescription( + key="wan_connect_ipv4", + translation_key="wan_connect_ipv4", + exists_func=lambda p: p.mode == GatewayPortMode.WAN, + set_func=partial(_wan_connect_disconnect, ipv6=False), + update_func=lambda p: p.wan_connected, + ), + GatewayPortSwitchEntityDescription( + key="wan_connect_ipv6", + translation_key="wan_connect_ipv6", + exists_func=lambda p: p.mode == GatewayPortMode.WAN and p.wan_ipv6_enabled, + set_func=partial(_wan_connect_disconnect, ipv6=True), + update_func=lambda p: p.ipv6_wan_connected, + ), +] + + def get_port_base_name(port: OmadaSwitchPortDetails) -> str: """Get display name for a switch port.""" if port.name == f"Port{port.port}": - return f"Port {port.port}" - return f"Port {port.port} ({port.name})" + return f"{port.port}" + return f"{port.port} ({port.name})" class OmadaNetworkSwitchPortPoEControl( @@ -76,8 +143,9 @@ class OmadaNetworkSwitchPortPoEControl( self.port_details = coordinator.data[port_id] self.omada_client = coordinator.omada_client self._attr_unique_id = f"{device.mac}_{port_id}_poe" - - self._attr_name = f"{get_port_base_name(self.port_details)} PoE" + self._attr_translation_placeholders = { + "port_name": get_port_base_name(self.port_details) + } async def async_added_to_hass(self) -> None: """When entity is added to hass.""" @@ -109,3 +177,72 @@ class OmadaNetworkSwitchPortPoEControl( """Handle updated data from the coordinator.""" self.port_details = self.coordinator.data[self.port_id] self._refresh_state() + + +class OmadaGatewayPortSwitchEntity(OmadaDeviceEntity[OmadaGateway], SwitchEntity): + """Generic toggle switch on a Gateway entity.""" + + _attr_has_entity_name = True + _port_details: OmadaGatewayPortStatus | None = None + entity_description: GatewayPortSwitchEntityDescription + + def __init__( + self, + coordinator: OmadaGatewayCoordinator, + device: OmadaGateway, + port_number: int, + entity_description: GatewayPortSwitchEntityDescription, + ) -> None: + """Initialize the toggle switch.""" + super().__init__(coordinator, device) + self.entity_description = entity_description + self._port_number = port_number + self._attr_unique_id = f"{device.mac}_{port_number}_{entity_description.key}" + self._attr_translation_placeholders = {"port_name": f"{port_number}"} + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self._do_update() + + async def _async_turn_on_off(self, enable: bool) -> None: + if self._port_details: + self._port_details = await self.entity_description.set_func( + self.coordinator.omada_client, self.device, self._port_details, enable + ) + self._attr_is_on = enable + # Refresh to make sure the requested changes stuck + await self.coordinator.async_request_refresh() + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + await self._async_turn_on_off(True) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + await self._async_turn_on_off(False) + + @property + def available(self) -> bool: + """Return true if entity is available.""" + return bool( + super().available + and self._port_details + and self.entity_description.exists_func(self._port_details) + ) + + def _do_update(self) -> None: + gateway = self.coordinator.data[self.device.mac] + + port = next( + p for p in gateway.port_status if p.port_number == self._port_number + ) + if port: + self._port_details = port + self._attr_is_on = self.entity_description.update_func(port) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._do_update() + self.async_write_ha_state() diff --git a/tests/components/tplink_omada/conftest.py b/tests/components/tplink_omada/conftest.py index ce7fc880c8e..afedaa2df3c 100644 --- a/tests/components/tplink_omada/conftest.py +++ b/tests/components/tplink_omada/conftest.py @@ -5,7 +5,12 @@ import json from unittest.mock import AsyncMock, MagicMock, patch import pytest -from tplink_omada_client.devices import OmadaSwitch, OmadaSwitchPortDetails +from tplink_omada_client.devices import ( + OmadaGateway, + OmadaListDevice, + OmadaSwitch, + OmadaSwitchPortDetails, +) from homeassistant.components.tplink_omada.config_flow import CONF_SITE from homeassistant.components.tplink_omada.const import DOMAIN @@ -45,10 +50,19 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: def mock_omada_site_client() -> Generator[AsyncMock, None, None]: """Mock Omada site client.""" site_client = AsyncMock() + + gateway_data = json.loads(load_fixture("gateway-TL-ER7212PC.json", DOMAIN)) + gateway = OmadaGateway(gateway_data) + site_client.get_gateway.return_value = gateway + switch1_data = json.loads(load_fixture("switch-TL-SG3210XHP-M2.json", DOMAIN)) switch1 = OmadaSwitch(switch1_data) site_client.get_switches.return_value = [switch1] + devices_data = json.loads(load_fixture("devices.json", DOMAIN)) + devices = [OmadaListDevice(d) for d in devices_data] + site_client.get_devices.return_value = devices + switch1_ports_data = json.loads( load_fixture("switch-ports-TL-SG3210XHP-M2.json", DOMAIN) ) diff --git a/tests/components/tplink_omada/fixtures/devices.json b/tests/components/tplink_omada/fixtures/devices.json new file mode 100644 index 00000000000..d92fd5f7d66 --- /dev/null +++ b/tests/components/tplink_omada/fixtures/devices.json @@ -0,0 +1,41 @@ +[ + { + "type": "gateway", + "mac": "AA-BB-CC-DD-EE-FF", + "name": "Test Router", + "model": "ER7212PC", + "compoundModel": "ER7212PC v1.0", + "showModel": "ER7212PC v1.0", + "firmwareVersion": "1.1.1 Build 20230901 Rel.55651", + "version": "1.1.1", + "hwVersion": "ER7212PC v1.0", + "uptime": "32day(s) 5h 39m 27s", + "uptimeLong": 2785167, + "cpuUtil": 16, + "memUtil": 47, + "status": 14, + "statusCategory": 1, + "site": "Test", + "needUpgrade": false + }, + { + "type": "switch", + "mac": "54-AF-97-00-00-01", + "name": "Test PoE Switch", + "model": "TL-SG3210XHP-M2", + "modelVersion": "1.0", + "compoundModel": "TL-SG3210XHP-M2 v1.0", + "showModel": "TL-SG3210XHP-M2 v1.0", + "firmwareVersion": "1.0.12 Build 20230203 Rel.36545", + "version": "1.0.12", + "hwVersion": "1.0", + "uptime": "1day(s) 8h 27m 26s", + "uptimeLong": 116846, + "cpuUtil": 10, + "memUtil": 20, + "status": 14, + "statusCategory": 1, + "needUpgrade": false, + "fwDownload": false + } +] diff --git a/tests/components/tplink_omada/fixtures/gateway-TL-ER7212PC.json b/tests/components/tplink_omada/fixtures/gateway-TL-ER7212PC.json new file mode 100644 index 00000000000..fba595e7109 --- /dev/null +++ b/tests/components/tplink_omada/fixtures/gateway-TL-ER7212PC.json @@ -0,0 +1,1040 @@ +{ + "type": "gateway", + "mac": "AA-BB-CC-DD-EE-FF", + "name": "Test Router", + "model": "ER7212PC", + "modelVersion": "1.0", + "compoundModel": "ER7212PC v1.0", + "showModel": "ER7212PC v1.0", + "firmwareVersion": "1.1.1 Build 20230901 Rel.55651", + "version": "1.1.1", + "hwVersion": "ER7212PC v1.0", + "status": 14, + "statusCategory": 1, + "site": "Test", + "omadacId": "XXXXX", + "compatible": 0, + "sn": "XXXX", + "addedInAdvanced": false, + "portNum": 12, + "ledSetting": 2, + "snmpSeting": { + "location": "", + "contact": "" + }, + "iptvSetting": { + "igmpEnable": true, + "igmpVersion": "2" + }, + "supportHwOffload": false, + "supportPoe": true, + "hwOffloadEnable": true, + "poeSettings": [ + { + "portId": 5, + "portName": "LAN5", + "enable": true + }, + { + "portId": 6, + "portName": "LAN6", + "enable": true + }, + { + "portId": 7, + "portName": "LAN7", + "enable": true + }, + { + "portId": 8, + "portName": "LAN8", + "enable": true + }, + { + "portId": 9, + "portName": "LAN9", + "enable": true + }, + { + "portId": 10, + "portName": "LAN10", + "enable": true + }, + { + "portId": 11, + "portName": "LAN11", + "enable": true + }, + { + "portId": 12, + "portName": "LAN12", + "enable": true + } + ], + "lldpEnable": false, + "echoServer": "0.0.0.0", + "ip": "192.168.0.1", + "uptime": "5day(s) 3h 29m 49s", + "uptimeLong": 444589, + "cpuUtil": 9, + "memUtil": 86, + "lastSeen": 1704219948802, + "portStats": [ + { + "port": 1, + "name": "SFP WAN/LAN1", + "type": 1, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 2, + "name": "SFP WAN/LAN2", + "type": 1, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 3, + "name": "WAN3", + "type": 0, + "mode": -1, + "status": 0, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "mirroredPorts": [] + }, + { + "port": 4, + "name": "WAN/LAN4", + "type": 1, + "mode": 0, + "poe": false, + "status": 1, + "internetState": 1, + "ip": "XX.XX.XX.XX", + "speed": 3, + "duplex": 2, + "rx": 39901007520, + "rxPkt": 33051930, + "rxPktRate": 25, + "rxRate": 18, + "tx": 8891933646, + "txPkt": 12195464, + "txPktRate": 22, + "txRate": 3, + "proto": "dhcp", + "wanIpv6Comptent": 1, + "wanPortIpv6Config": { + "enable": 0, + "addr": "", + "gateway": "", + "priDns": "", + "sndDns": "", + "internetState": 0 + }, + "wanPortIpv4Config": { + "ip": "140.100.128.10", + "gateway": "140.100.128.1", + "gateway2": "0.0.0.0", + "priDns": "8.8.8.8", + "sndDns": "8.8.4.4", + "priDns2": "0.0.0.0", + "sndDns2": "0.0.0.0" + }, + "mirroredPorts": [] + }, + { + "port": 5, + "name": "LAN5", + "type": 2, + "mode": 1, + "poe": true, + "status": 1, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 3, + "duplex": 2, + "rx": 4622709923, + "rxPkt": 8985877, + "rxPktRate": 21, + "rxRate": 4, + "tx": 38465362622, + "txPkt": 30836050, + "txPktRate": 25, + "txRate": 17, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 6, + "name": "LAN6", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 7, + "name": "LAN7", + "type": 2, + "mode": 1, + "poe": false, + "status": 1, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 2, + "duplex": 2, + "rx": 5477288, + "rxPkt": 52166, + "rxPktRate": 0, + "rxRate": 0, + "tx": 66036305, + "txPkt": 319810, + "txPktRate": 1, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 8, + "name": "LAN8", + "type": 2, + "mode": 1, + "poe": false, + "status": 1, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 3, + "duplex": 2, + "rx": 6105639661, + "rxPkt": 6200831, + "rxPktRate": 4, + "rxRate": 0, + "tx": 3258101551, + "txPkt": 4719927, + "txPktRate": 4, + "txRate": 1, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 9, + "name": "LAN9", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 10, + "name": "LAN10", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 11, + "name": "LAN11", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + }, + { + "port": 12, + "name": "LAN12", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + ], + "lanClientStats": [ + { + "lanName": "LAN", + "vlan": 1, + "ip": "192.168.0.1", + "rx": 3365832108, + "tx": 19893930205, + "clientNum": 3, + "lanPortIpv6Config": { + "addr": "XXXXX" + } + } + ], + "needUpgrade": false, + "download": 39901007520, + "upload": 8891933646, + "networkComptent": 1, + "portConfigs": [ + { + "port": 1, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 1, + "name": "SFP WAN/LAN1", + "type": 1, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 2, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 2, + "name": "SFP WAN/LAN2", + "type": 1, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 3, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "portStat": { + "port": 3, + "name": "WAN3", + "type": 0, + "mode": -1, + "status": 0, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "mirroredPorts": [] + } + }, + { + "port": 4, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "portStat": { + "port": 4, + "name": "WAN/LAN4", + "type": 1, + "mode": 0, + "poe": false, + "status": 1, + "internetState": 1, + "ip": "XX.XX.XX.XX", + "speed": 3, + "duplex": 2, + "rx": 39901007520, + "rxPkt": 33051930, + "rxPktRate": 25, + "rxRate": 18, + "tx": 8891933646, + "txPkt": 12195464, + "txPktRate": 22, + "txRate": 3, + "proto": "dhcp", + "wanIpv6Comptent": 1, + "wanPortIpv6Config": { + "enable": 0, + "addr": "", + "gateway": "", + "priDns": "", + "sndDns": "", + "internetState": 0 + }, + "wanPortIpv4Config": { + "ip": "XX.XX.XX.XX", + "gateway": "XX.XX.XX.XXX", + "gateway2": "0.0.0.0", + "priDns": "212.27.40.240", + "sndDns": "212.27.40.241", + "priDns2": "0.0.0.0", + "sndDns2": "0.0.0.0" + }, + "mirroredPorts": [] + } + }, + { + "port": 5, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 5, + "name": "LAN5", + "type": 2, + "mode": 1, + "poe": true, + "status": 1, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 3, + "duplex": 2, + "rx": 4622709923, + "rxPkt": 8985877, + "rxPktRate": 21, + "rxRate": 4, + "tx": 38465362622, + "txPkt": 30836050, + "txPktRate": 25, + "txRate": 17, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 6, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 6, + "name": "LAN6", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 7, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 7, + "name": "LAN7", + "type": 2, + "mode": 1, + "poe": false, + "status": 1, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 2, + "duplex": 2, + "rx": 5477288, + "rxPkt": 52166, + "rxPktRate": 0, + "rxRate": 0, + "tx": 66036305, + "txPkt": 319810, + "txPktRate": 1, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 8, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 8, + "name": "LAN8", + "type": 2, + "mode": 1, + "poe": false, + "status": 1, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 3, + "duplex": 2, + "rx": 6105639661, + "rxPkt": 6200831, + "rxPktRate": 4, + "rxRate": 0, + "tx": 3258101551, + "txPkt": 4719927, + "txPktRate": 4, + "txRate": 1, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 9, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 9, + "name": "LAN9", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 10, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 10, + "name": "LAN10", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 11, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 11, + "name": "LAN11", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + }, + { + "port": 12, + "linkSpeed": 0, + "duplex": 0, + "portCap": [ + { + "linkSpeed": 2, + "duplex": 1 + }, + { + "linkSpeed": 2, + "duplex": 2 + }, + { + "linkSpeed": 1, + "duplex": 1 + }, + { + "linkSpeed": 1, + "duplex": 2 + }, + { + "linkSpeed": 0, + "duplex": 0 + }, + { + "linkSpeed": 3, + "duplex": 2 + } + ], + "mirrorEnable": false, + "pvid": 1, + "availablePvids": [1], + "portStat": { + "port": 12, + "name": "LAN12", + "type": 2, + "mode": 1, + "poe": false, + "status": 0, + "internetState": 0, + "ip": "192.168.0.1", + "speed": 1, + "duplex": 1, + "rx": 0, + "rxPkt": 0, + "rxPktRate": 0, + "rxRate": 0, + "tx": 0, + "txPkt": 0, + "txPktRate": 0, + "txRate": 0, + "wanIpv6Comptent": 1, + "mirroredPorts": [] + } + } + ], + "supportSpeedDuplex": true, + "supportMirror": true, + "supportPvid": true, + "unsupportedPorts": [], + "combinedGateway": true, + "speeds": [1, 2, 3], + "poeRemain": 105.699997, + "poeRemainPercent": 96.090904, + "multiChipGateway": true, + "multiChipInfos": [ + [1, 3, 5, 6, 7, 8], + [2, 4, 9, 10, 11, 12] + ] +} diff --git a/tests/components/tplink_omada/fixtures/switch-TL-SG3210XHP-M2.json b/tests/components/tplink_omada/fixtures/switch-TL-SG3210XHP-M2.json index 2e3f21406b0..163a1758333 100644 --- a/tests/components/tplink_omada/fixtures/switch-TL-SG3210XHP-M2.json +++ b/tests/components/tplink_omada/fixtures/switch-TL-SG3210XHP-M2.json @@ -11,7 +11,7 @@ "hwVersion": "1.0", "status": 14, "statusCategory": 1, - "site": "000000000000000000000000", + "site": "Test", "omadacId": "00000000000000000000000000000000", "compatible": 0, "sn": "Y220000000001", @@ -124,7 +124,7 @@ { "id": "000000000000000000000002", "port": 2, - "name": "Port2", + "name": "Renamed Port", "disable": false, "type": 1, "maxSpeed": 4, diff --git a/tests/components/tplink_omada/fixtures/switch-ports-TL-SG3210XHP-M2.json b/tests/components/tplink_omada/fixtures/switch-ports-TL-SG3210XHP-M2.json index b079b2d2fb7..2c505ca7c13 100644 --- a/tests/components/tplink_omada/fixtures/switch-ports-TL-SG3210XHP-M2.json +++ b/tests/components/tplink_omada/fixtures/switch-ports-TL-SG3210XHP-M2.json @@ -108,7 +108,7 @@ "switchId": "640934810000000000000000", "switchMac": "54-AF-97-00-00-01", "site": "000000000000000000000000", - "name": "Port2", + "name": "Renamed Port", "disable": false, "type": 1, "maxSpeed": 4, diff --git a/tests/components/tplink_omada/snapshots/test_switch.ambr b/tests/components/tplink_omada/snapshots/test_switch.ambr index 78698e7ef87..872c9b8eeff 100644 --- a/tests/components/tplink_omada/snapshots/test_switch.ambr +++ b/tests/components/tplink_omada/snapshots/test_switch.ambr @@ -1,4 +1,53 @@ # serializer version: 1 +# name: test_gateway_api_fail_disables_switch_entities + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Router Port 4 Internet Connected', + }), + 'context': , + 'entity_id': 'switch.test_router_port_4_internet_connected', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_gateway_connect_ipv4_switch + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Router Port 4 Internet Connected', + }), + 'context': , + 'entity_id': 'switch.test_router_port_4_internet_connected', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_gateway_disappear_disables_switches + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Router Port 4 Internet Connected', + 'icon': 'mdi:ethernet', + }), + 'context': , + 'entity_id': 'switch.test_router_port_4_internet_connected', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_gateway_port_change_disables_switch_entities + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Router Port 4 Internet Connected', + }), + 'context': , + 'entity_id': 'switch.test_router_port_4_internet_connected', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_poe_switches StateSnapshot({ 'attributes': ReadOnlyDict({ @@ -182,10 +231,10 @@ # name: test_poe_switches.2 StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Test PoE Switch Port 2 PoE', + 'friendly_name': 'Test PoE Switch Port 2 (Renamed Port) PoE', }), 'context': , - 'entity_id': 'switch.test_poe_switch_port_2_poe', + 'entity_id': 'switch.test_poe_switch_port_2_renamed_port_poe', 'last_changed': , 'last_updated': , 'state': 'on', @@ -203,7 +252,7 @@ 'disabled_by': None, 'domain': 'switch', 'entity_category': , - 'entity_id': 'switch.test_poe_switch_port_2_poe', + 'entity_id': 'switch.test_poe_switch_port_2_renamed_port_poe', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -215,7 +264,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Port 2 PoE', + 'original_name': 'Port 2 (Renamed Port) PoE', 'platform': 'tplink_omada', 'previous_unique_id': None, 'supported_features': 0, diff --git a/tests/components/tplink_omada/test_switch.py b/tests/components/tplink_omada/test_switch.py index 0a7d6840295..aa5cd5d33c4 100644 --- a/tests/components/tplink_omada/test_switch.py +++ b/tests/components/tplink_omada/test_switch.py @@ -1,18 +1,30 @@ """Tests for TP-Link Omada switch entities.""" - +from datetime import timedelta +from typing import Any from unittest.mock import MagicMock from syrupy.assertion import SnapshotAssertion from tplink_omada_client import SwitchPortOverrides from tplink_omada_client.definitions import PoEMode -from tplink_omada_client.devices import OmadaSwitch, OmadaSwitchPortDetails +from tplink_omada_client.devices import ( + OmadaGateway, + OmadaGatewayPortStatus, + OmadaSwitch, + OmadaSwitchPortDetails, +) +from tplink_omada_client.exceptions import InvalidDevice from homeassistant.components import switch +from homeassistant.components.tplink_omada.controller import POLL_GATEWAY from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from homeassistant.util.dt import utcnow -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_fire_time_changed + +UPDATE_INTERVAL = timedelta(seconds=10) +POLL_INTERVAL = timedelta(seconds=POLL_GATEWAY + 10) async def test_poe_switches( @@ -23,15 +35,124 @@ async def test_poe_switches( ) -> None: """Test PoE switch.""" poe_switch_mac = "54-AF-97-00-00-01" - for i in range(1, 9): - await _test_poe_switch( - hass, - mock_omada_site_client, - f"switch.test_poe_switch_port_{i}_poe", - poe_switch_mac, - i, - snapshot, + await _test_poe_switch( + hass, + mock_omada_site_client, + "switch.test_poe_switch_port_1_poe", + poe_switch_mac, + 1, + snapshot, + ) + + await _test_poe_switch( + hass, + mock_omada_site_client, + "switch.test_poe_switch_port_2_renamed_port_poe", + poe_switch_mac, + 2, + snapshot, + ) + + +async def test_gateway_connect_ipv4_switch( + hass: HomeAssistant, + mock_omada_site_client: MagicMock, + init_integration: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test gateway connected switches.""" + gateway_mac = "AA-BB-CC-DD-EE-FF" + + entity_id = "switch.test_router_port_4_internet_connected" + entity = hass.states.get(entity_id) + assert entity == snapshot + + test_gateway = await mock_omada_site_client.get_gateway(gateway_mac) + port_status = test_gateway.port_status[3] + assert port_status.port_number == 4 + + mock_omada_site_client.set_gateway_wan_port_connect_state.reset_mock() + mock_omada_site_client.set_gateway_wan_port_connect_state.return_value = ( + _get_updated_gateway_port_status( + mock_omada_site_client, test_gateway, 3, "internetState", 0 ) + ) + await call_service(hass, "turn_off", entity_id) + mock_omada_site_client.set_gateway_wan_port_connect_state.assert_called_once_with( + 4, False, test_gateway, ipv6=False + ) + + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(entity_id) + assert entity.state == "off" + + mock_omada_site_client.set_gateway_wan_port_connect_state.reset_mock() + mock_omada_site_client.set_gateway_wan_port_connect_state.return_value = ( + _get_updated_gateway_port_status( + mock_omada_site_client, test_gateway, 3, "internetState", 1 + ) + ) + await call_service(hass, "turn_on", entity_id) + mock_omada_site_client.set_gateway_wan_port_connect_state.assert_called_once_with( + 4, True, test_gateway, ipv6=False + ) + + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(entity_id) + assert entity.state == "on" + + +async def test_gateway_api_fail_disables_switch_entities( + hass: HomeAssistant, + mock_omada_site_client: MagicMock, + init_integration: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test gateway connected switches.""" + entity_id = "switch.test_router_port_4_internet_connected" + entity = hass.states.get(entity_id) + assert entity == snapshot + assert entity.state == "on" + + mock_omada_site_client.get_gateway.reset_mock() + mock_omada_site_client.get_gateway.side_effect = InvalidDevice("Expected error") + + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(entity_id) + assert entity.state == "unavailable" + + +async def test_gateway_port_change_disables_switch_entities( + hass: HomeAssistant, + mock_omada_site_client: MagicMock, + init_integration: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test gateway connected switch reconfigure.""" + + gateway_mac = "AA-BB-CC-DD-EE-FF" + test_gateway = await mock_omada_site_client.get_gateway(gateway_mac) + + entity_id = "switch.test_router_port_4_internet_connected" + entity = hass.states.get(entity_id) + assert entity == snapshot + assert entity.state == "on" + + mock_omada_site_client.get_gateway.reset_mock() + # Set Port 4 to LAN mode + _get_updated_gateway_port_status(mock_omada_site_client, test_gateway, 3, "mode", 1) + + async_fire_time_changed(hass, utcnow() + POLL_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(entity_id) + assert entity.state == "unavailable" async def _test_poe_switch( @@ -94,6 +215,7 @@ async def _test_poe_switch( True, **mock_omada_site_client.update_switch_port.call_args.kwargs, ) + await hass.async_block_till_done() entity = hass.states.get(entity_id) assert entity.state == "on" @@ -116,6 +238,20 @@ async def _update_port_details( return OmadaSwitchPortDetails(raw_data) +def _get_updated_gateway_port_status( + mock_omada_site_client: MagicMock, + gateway: OmadaGateway, + port: int, + key: str, + value: Any, +) -> OmadaGatewayPortStatus: + gateway_data = gateway.raw_data.copy() + gateway_data["portStats"][port][key] = value + mock_omada_site_client.get_gateway.reset_mock() + mock_omada_site_client.get_gateway.return_value = OmadaGateway(gateway_data) + return OmadaGatewayPortStatus(gateway_data["portStats"][port]) + + def call_service(hass: HomeAssistant, service: str, entity_id: str): """Call any service on entity.""" return hass.services.async_call( From 25c4ab070b4e771bba0f1edc72cbd87220c12051 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 17 Mar 2024 17:32:16 +0100 Subject: [PATCH 1208/1691] Use `mock_platform` for event entity component tests instead of `hass.components` (#113667) --- tests/components/event/conftest.py | 52 +++++++++++++++++++ tests/components/event/const.py | 3 ++ tests/components/event/test_init.py | 24 +++------ .../custom_components/test/event.py | 43 --------------- 4 files changed, 61 insertions(+), 61 deletions(-) create mode 100644 tests/components/event/conftest.py create mode 100644 tests/components/event/const.py delete mode 100644 tests/testing_config/custom_components/test/event.py diff --git a/tests/components/event/conftest.py b/tests/components/event/conftest.py new file mode 100644 index 00000000000..38c59af117b --- /dev/null +++ b/tests/components/event/conftest.py @@ -0,0 +1,52 @@ +"""Fixtures for the event entity component tests.""" +import logging + +import pytest + +from homeassistant.components.event import DOMAIN, EventEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from .const import TEST_DOMAIN + +from tests.common import MockEntity, MockPlatform, mock_platform + +_LOGGER = logging.getLogger(__name__) + + +class MockEventEntity(MockEntity, EventEntity): + """Mock EventEntity class.""" + + @property + def event_types(self) -> list[str]: + """Return a list of possible events.""" + return self._handle("event_types") + + +@pytest.fixture +async def mock_event_platform(hass: HomeAssistant) -> None: + """Mock the event entity platform.""" + + async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, + ) -> None: + """Set up test event platform.""" + async_add_entities( + [ + MockEventEntity( + name="doorbell", + unique_id="unique_doorbell", + event_types=["short_press", "long_press"], + ), + ] + ) + + mock_platform( + hass, + f"{TEST_DOMAIN}.{DOMAIN}", + MockPlatform(async_setup_platform=async_setup_platform), + ) diff --git a/tests/components/event/const.py b/tests/components/event/const.py new file mode 100644 index 00000000000..323f32fa04b --- /dev/null +++ b/tests/components/event/const.py @@ -0,0 +1,3 @@ +"""Constants for the event entity component tests.""" + +TEST_DOMAIN = "test" diff --git a/tests/components/event/test_init.py b/tests/components/event/test_init.py index 25cb5af3e08..fd3cf0eaf9b 100644 --- a/tests/components/event/test_init.py +++ b/tests/components/event/test_init.py @@ -22,6 +22,8 @@ from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util +from .const import TEST_DOMAIN + from tests.common import ( MockConfigEntry, MockModule, @@ -34,8 +36,6 @@ from tests.common import ( mock_restore_cache_with_extra_data, ) -TEST_DOMAIN = "test" - async def test_event() -> None: """Test the event entity.""" @@ -96,7 +96,7 @@ async def test_event() -> None: event._trigger_event("unknown_event") -@pytest.mark.usefixtures("enable_custom_integrations") +@pytest.mark.usefixtures("enable_custom_integrations", "mock_event_platform") async def test_restore_state(hass: HomeAssistant) -> None: """Test we restore state integration.""" mock_restore_cache_with_extra_data( @@ -128,9 +128,6 @@ async def test_restore_state(hass: HomeAssistant) -> None: ), ) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -142,7 +139,7 @@ async def test_restore_state(hass: HomeAssistant) -> None: assert state.attributes["hello"] == "world" -@pytest.mark.usefixtures("enable_custom_integrations") +@pytest.mark.usefixtures("enable_custom_integrations", "mock_event_platform") async def test_invalid_extra_restore_state(hass: HomeAssistant) -> None: """Test we restore state integration.""" mock_restore_cache_with_extra_data( @@ -163,9 +160,6 @@ async def test_invalid_extra_restore_state(hass: HomeAssistant) -> None: ), ) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -177,7 +171,7 @@ async def test_invalid_extra_restore_state(hass: HomeAssistant) -> None: assert "hello" not in state.attributes -@pytest.mark.usefixtures("enable_custom_integrations") +@pytest.mark.usefixtures("enable_custom_integrations", "mock_event_platform") async def test_no_extra_restore_state(hass: HomeAssistant) -> None: """Test we restore state integration.""" mock_restore_cache( @@ -198,9 +192,6 @@ async def test_no_extra_restore_state(hass: HomeAssistant) -> None: ), ) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -212,7 +203,7 @@ async def test_no_extra_restore_state(hass: HomeAssistant) -> None: assert "hello" not in state.attributes -@pytest.mark.usefixtures("enable_custom_integrations") +@pytest.mark.usefixtures("enable_custom_integrations", "mock_event_platform") async def test_saving_state(hass: HomeAssistant, hass_storage: dict[str, Any]) -> None: """Test we restore state integration.""" restore_data = {"last_event_type": "double_press", "last_event_attributes": None} @@ -230,9 +221,6 @@ async def test_saving_state(hass: HomeAssistant, hass_storage: dict[str, Any]) - ), ) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/testing_config/custom_components/test/event.py b/tests/testing_config/custom_components/test/event.py deleted file mode 100644 index 7dee8390148..00000000000 --- a/tests/testing_config/custom_components/test/event.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Provide a mock event platform. - -Call init before using it in your tests to ensure clean test data. -""" - -from homeassistant.components.event import EventEntity - -from tests.common import MockEntity - -ENTITIES = [] - - -class MockEventEntity(MockEntity, EventEntity): - """Mock EventEntity class.""" - - @property - def event_types(self) -> list[str]: - """Return a list of possible events.""" - return self._handle("event_types") - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - [] - if empty - else [ - MockEventEntity( - name="doorbell", - unique_id="unique_doorbell", - event_types=["short_press", "long_press"], - ), - ] - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(ENTITIES) From d9bc09e93a0c701e900bc78168a579dfe0318aa6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 06:42:25 -1000 Subject: [PATCH 1209/1691] Migrate stream listeners to use run_immediately and eager tasks (#113660) None of these need to a call_soon and can shutdown a bit faster --- homeassistant/components/stream/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 4959f51a0db..b092e6e8aa6 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -35,6 +35,7 @@ from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType +from homeassistant.util.async_ import create_eager_task from .const import ( ATTR_ENDPOINTS, @@ -212,7 +213,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # Only pass through PyAV log messages if stream logging is above DEBUG cancel_logging_listener = hass.bus.async_listen( - EVENT_LOGGING_CHANGED, update_pyav_logging + EVENT_LOGGING_CHANGED, update_pyav_logging, run_immediately=True ) # libav.mp4 and libav.swscaler have a few unimportant messages that are logged # at logging.WARNING. Set those Logger levels to logging.ERROR @@ -256,14 +257,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: for stream in hass.data[DOMAIN][ATTR_STREAMS]: stream.dynamic_stream_settings.preload_stream = False if awaitables := [ - asyncio.create_task(stream.stop()) + create_eager_task(stream.stop()) for stream in hass.data[DOMAIN][ATTR_STREAMS] ]: await asyncio.wait(awaitables) _LOGGER.debug("Stopped stream workers") cancel_logging_listener() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown, run_immediately=True) return True From 681705394d6533550845148796ca74bc268f52cc Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 17 Mar 2024 17:42:48 +0100 Subject: [PATCH 1210/1691] Remove deprecated `hass.components` from network helper function (#113615) * Remove deprecated `hass.components` from network helper function * Remove deprecated use of `hass.components` in alexa camera tests --- homeassistant/helpers/network.py | 12 ++++++++--- tests/components/alexa/test_smart_home.py | 10 ++++----- tests/helpers/test_network.py | 25 +++++++++-------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 510639b5506..ed6339f9996 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -5,7 +5,6 @@ from __future__ import annotations from collections.abc import Callable from contextlib import suppress from ipaddress import ip_address -from typing import cast from hass_nabucasa import remote import yarl @@ -299,9 +298,16 @@ def _get_external_url( def _get_cloud_url(hass: HomeAssistant, require_current_request: bool = False) -> str: """Get external Home Assistant Cloud URL of this instance.""" if "cloud" in hass.config.components: + # Local import to avoid circular dependencies + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.cloud import ( + CloudNotAvailable, + async_remote_ui_url, + ) + try: - cloud_url = yarl.URL(cast(str, hass.components.cloud.async_remote_ui_url())) - except hass.components.cloud.CloudNotAvailable as err: + cloud_url = yarl.URL(async_remote_ui_url(hass)) + except CloudNotAvailable as err: raise NoURLAvailableError from err if not require_current_request or cloud_url.host == _get_request_host(): diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 5204b32821c..b7e6a5e53ac 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -5464,9 +5464,8 @@ async def test_camera_discovery(hass: HomeAssistant, mock_stream: None) -> None: ) hass.config.components.add("cloud") - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", return_value="https://example.nabu.casa", ): appliance = await discovery_test(device, hass) @@ -5495,9 +5494,8 @@ async def test_camera_discovery_without_stream(hass: HomeAssistant) -> None: ) hass.config.components.add("cloud") - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", return_value="https://example.nabu.casa", ): appliance = await discovery_test(device, hass) diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index cbffdc88e19..3c0793290d0 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -362,9 +362,8 @@ async def test_get_cloud_url(hass: HomeAssistant) -> None: assert hass.config.external_url is None hass.config.components.add("cloud") - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", return_value="https://example.nabu.casa", ): assert _get_cloud_url(hass) == "https://example.nabu.casa" @@ -387,9 +386,8 @@ async def test_get_cloud_url(hass: HomeAssistant) -> None: ), pytest.raises(NoURLAvailableError): _get_cloud_url(hass, require_current_request=True) - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", side_effect=cloud.CloudNotAvailable, ), pytest.raises(NoURLAvailableError): _get_cloud_url(hass) @@ -410,9 +408,8 @@ async def test_get_external_url_cloud_fallback(hass: HomeAssistant) -> None: # Add Cloud to the previous test hass.config.components.add("cloud") - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", return_value="https://example.nabu.casa", ): assert _get_external_url(hass, allow_cloud=False) == "http://1.1.1.1:8123" @@ -436,9 +433,8 @@ async def test_get_external_url_cloud_fallback(hass: HomeAssistant) -> None: # Add Cloud to the previous test hass.config.components.add("cloud") - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", return_value="https://example.nabu.casa", ): assert _get_external_url(hass, allow_cloud=False) == "https://example.com" @@ -710,9 +706,8 @@ async def test_is_hass_url(hass: HomeAssistant) -> None: assert is_hass_url(hass, "http://example.com:443") is False assert is_hass_url(hass, "http://example.com") is False - with patch.object( - hass.components.cloud, - "async_remote_ui_url", + with patch( + "homeassistant.components.cloud.async_remote_ui_url", return_value="https://example.nabu.casa", ): assert is_hass_url(hass, "https://example.nabu.casa") is False From 324c922c0d681e03d5c8fdf6cbb56dac8f432d08 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 17 Mar 2024 17:48:58 +0100 Subject: [PATCH 1211/1691] Remove deprecated `hass.components` from withings webhook tests (#113687) * Remove deprecated `hass.components` from withings webhook tests * Use patch.object --- tests/components/withings/test_init.py | 36 ++++++++++++-------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index e5fa2dcb9fd..c7a9d5cde4b 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -16,6 +16,7 @@ import pytest import voluptuous as vol from homeassistant import config_entries +from homeassistant.components import cloud from homeassistant.components.cloud import CloudNotAvailable from homeassistant.components.webhook import async_generate_url from homeassistant.components.withings import CONFIG_SCHEMA, async_setup @@ -298,9 +299,7 @@ async def test_setup_with_cloudhook( "homeassistant.components.cloud.async_is_logged_in", return_value=True ), patch( "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( + ), patch.object(cloud, "async_active_subscription", return_value=True), patch( "homeassistant.components.cloud.async_create_cloudhook", return_value="https://hooks.nabu.casa/ABCD", ) as fake_create_cloudhook, patch( @@ -311,7 +310,8 @@ async def test_setup_with_cloudhook( "homeassistant.components.withings.webhook_generate_url" ): await setup_integration(hass, cloudhook_config_entry) - assert hass.components.cloud.async_active_subscription() is True + + assert cloud.async_active_subscription(hass) is True assert ( hass.config_entries.async_entries(DOMAIN)[0].data["cloudhook_url"] @@ -342,9 +342,7 @@ async def test_removing_entry_with_cloud_unavailable( "homeassistant.components.cloud.async_is_logged_in", return_value=True ), patch( "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( + ), patch.object(cloud, "async_active_subscription", return_value=True), patch( "homeassistant.components.cloud.async_create_cloudhook", return_value="https://hooks.nabu.casa/ABCD", ), patch( @@ -356,7 +354,8 @@ async def test_removing_entry_with_cloud_unavailable( "homeassistant.components.withings.webhook_generate_url", ): await setup_integration(hass, cloudhook_config_entry) - assert hass.components.cloud.async_active_subscription() is True + + assert cloud.async_active_subscription(hass) is True await hass.async_block_till_done() assert hass.config_entries.async_entries(DOMAIN) @@ -380,10 +379,8 @@ async def test_setup_with_cloud( with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch.object(cloud, "async_is_connected", return_value=True), patch.object( + cloud, "async_active_subscription", return_value=True ), patch( "homeassistant.components.cloud.async_create_cloudhook", return_value="https://hooks.nabu.casa/ABCD", @@ -397,8 +394,8 @@ async def test_setup_with_cloud( await setup_integration(hass, webhook_config_entry) await prepare_webhook_setup(hass, freezer) - assert hass.components.cloud.async_active_subscription() is True - assert hass.components.cloud.async_is_connected() is True + assert cloud.async_active_subscription(hass) is True + assert cloud.async_is_connected(hass) is True fake_create_cloudhook.assert_called_once() fake_delete_cloudhook.assert_called_once() @@ -460,10 +457,8 @@ async def test_cloud_disconnect( with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch.object(cloud, "async_is_connected", return_value=True), patch.object( + cloud, "async_active_subscription", return_value=True ), patch( "homeassistant.components.cloud.async_create_cloudhook", return_value="https://hooks.nabu.casa/ABCD", @@ -476,8 +471,9 @@ async def test_cloud_disconnect( ): await setup_integration(hass, webhook_config_entry) await prepare_webhook_setup(hass, freezer) - assert hass.components.cloud.async_active_subscription() is True - assert hass.components.cloud.async_is_connected() is True + + assert cloud.async_active_subscription(hass) is True + assert cloud.async_is_connected(hass) is True await hass.async_block_till_done() From 412fd3dc057a2a74a60e6f38e445556f56531635 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 08:15:14 -1000 Subject: [PATCH 1212/1691] Add run_immediately to usb start/stop listeners (#113658) --- homeassistant/components/usb/__init__.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/usb/__init__.py b/homeassistant/components/usb/__init__.py index 959a8f5894c..48697c98ae7 100644 --- a/homeassistant/components/usb/__init__.py +++ b/homeassistant/components/usb/__init__.py @@ -207,8 +207,12 @@ class USBDiscovery: async def async_setup(self) -> None: """Set up USB Discovery.""" await self._async_start_monitor() - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, self.async_start) - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, self.async_start, run_immediately=True + ) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self.async_stop, run_immediately=True + ) async def async_start(self, event: Event) -> None: """Start USB Discovery and run a manual scan.""" @@ -238,7 +242,9 @@ class USBDiscovery: def _stop_observer(event: Event) -> None: observer.stop() - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_observer) + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _stop_observer, run_immediately=True + ) self.observer_active = True def _get_monitor_observer(self) -> MonitorObserver | None: From dbbd6fff01d8b1feafbd84da3c1396fbb2cbfc6c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 08:15:28 -1000 Subject: [PATCH 1213/1691] Move ESPHome disconnects to the close event (#113652) --- homeassistant/components/esphome/manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/esphome/manager.py b/homeassistant/components/esphome/manager.py index f89e79aae2b..dc95952194e 100644 --- a/homeassistant/components/esphome/manager.py +++ b/homeassistant/components/esphome/manager.py @@ -30,7 +30,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_DEVICE_ID, CONF_MODE, - EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_CLOSE, EVENT_LOGGING_CHANGED, ) from homeassistant.core import Event, HomeAssistant, ServiceCall, State, callback @@ -542,9 +542,12 @@ class ESPHomeManager: # the callback twice when shutting down Home Assistant. # "Unable to remove unknown listener # .onetime_listener>" + # We only close the connection at the last possible moment + # when the CLOSE event is fired so anything using a Bluetooth + # proxy has a chance to shut down properly. entry_data.cleanup_callbacks.append( hass.bus.async_listen( - EVENT_HOMEASSISTANT_STOP, self.on_stop, run_immediately=True + EVENT_HOMEASSISTANT_CLOSE, self.on_stop, run_immediately=True ) ) entry_data.cleanup_callbacks.append( From 091199d24a744bd2614e7b8dc115738a2095e563 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 08:15:41 -1000 Subject: [PATCH 1214/1691] Run entity registry write_unavailable_states immediately at start (#113647) --- homeassistant/helpers/entity_registry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 9f666e86c69..277a93ef4b4 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -1518,7 +1518,9 @@ def _async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) - entry.write_unavailable_state(hass) - hass.bus.async_listen(EVENT_HOMEASSISTANT_START, _write_unavailable_states) + hass.bus.async_listen( + EVENT_HOMEASSISTANT_START, _write_unavailable_states, run_immediately=True + ) async def async_migrate_entries( From 93497dde8bc832eb05153609fa6ee733284eb512 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 08:15:55 -1000 Subject: [PATCH 1215/1691] Run registry cleanup listeners immediately (#113646) --- homeassistant/helpers/device_registry.py | 6 +++++- homeassistant/helpers/entity_registry.py | 5 ++++- .../components/dlna_dmr/test_media_player.py | 21 +++++++------------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 4cc7ad83c32..0e6a1367b2b 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1191,6 +1191,7 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, _async_entity_registry_changed, event_filter=entity_registry_changed_filter, + run_immediately=True, ) return @@ -1200,10 +1201,13 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, _async_entity_registry_changed, event_filter=entity_registry_changed_filter, + run_immediately=True, ) await debounced_cleanup.async_call() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, startup_clean) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, startup_clean, run_immediately=True + ) @callback def _on_homeassistant_stop(event: Event) -> None: diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 277a93ef4b4..9d0d8b8191c 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -1476,7 +1476,9 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: """Cancel cleanup.""" cancel() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _on_homeassistant_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _on_homeassistant_stop, run_immediately=True + ) @callback @@ -1502,6 +1504,7 @@ def _async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) - EVENT_ENTITY_REGISTRY_UPDATED, cleanup_restored_states, event_filter=cleanup_restored_states_filter, + run_immediately=True, ) if hass.is_running: diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 4eb4780add3..6b64ef6f347 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -266,9 +266,8 @@ async def test_setup_entry_no_options( domain_data_mock.async_release_event_notifier.assert_awaited_once() dmr_device_mock.async_unsubscribe_services.assert_awaited_once() assert dmr_device_mock.on_event is None - mock_state = hass.states.get(mock_entity_id) - assert mock_state is not None - assert mock_state.state == ha_const.STATE_UNAVAILABLE + # Entity should be removed by the cleanup + assert hass.states.get(mock_entity_id) is None @pytest.mark.parametrize( @@ -345,9 +344,8 @@ async def test_setup_entry_with_options( domain_data_mock.async_release_event_notifier.assert_awaited_once() dmr_device_mock.async_unsubscribe_services.assert_awaited_once() assert dmr_device_mock.on_event is None - mock_state = hass.states.get(mock_entity_id) - assert mock_state is not None - assert mock_state.state == ha_const.STATE_UNAVAILABLE + # Entity should be removed by the cleanup + assert hass.states.get(mock_entity_id) is None async def test_setup_entry_mac_address( @@ -1384,10 +1382,8 @@ async def test_unavailable_device( # Check event notifiers are not released domain_data_mock.async_release_event_notifier.assert_not_called() - # Confirm the entity is still unavailable - mock_state = hass.states.get(mock_entity_id) - assert mock_state is not None - assert mock_state.state == ha_const.STATE_UNAVAILABLE + # Entity should be removed by the cleanup + assert hass.states.get(mock_entity_id) is None @pytest.mark.parametrize( @@ -1477,9 +1473,8 @@ async def test_become_available( domain_data_mock.async_release_event_notifier.assert_awaited_once() dmr_device_mock.async_unsubscribe_services.assert_awaited_once() assert dmr_device_mock.on_event is None - mock_state = hass.states.get(mock_entity_id) - assert mock_state is not None - assert mock_state.state == ha_const.STATE_UNAVAILABLE + # Entity should be removed by the cleanup + assert hass.states.get(mock_entity_id) is None @pytest.mark.parametrize( From 68320b1278173b52253eae32cb20867501bee326 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 08:16:11 -1000 Subject: [PATCH 1216/1691] Migrate registry for labels and categories to run_immediately (#113645) --- homeassistant/helpers/area_registry.py | 2 ++ homeassistant/helpers/device_registry.py | 1 + homeassistant/helpers/entity_registry.py | 2 ++ 3 files changed, 5 insertions(+) diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 5a14eb3ad16..7b65a53d34c 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -331,6 +331,7 @@ class AreaRegistry(BaseRegistry): event_type=fr.EVENT_FLOOR_REGISTRY_UPDATED, event_filter=_removed_from_registry_filter, listener=_handle_floor_registry_update, + run_immediately=True, ) @callback @@ -347,6 +348,7 @@ class AreaRegistry(BaseRegistry): event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, event_filter=_removed_from_registry_filter, listener=_handle_label_registry_update, + run_immediately=True, ) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 0e6a1367b2b..13aeab9ebf2 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1158,6 +1158,7 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, event_filter=_label_removed_from_registry_filter, listener=_handle_label_registry_update, + run_immediately=True, ) @callback diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 9d0d8b8191c..637734c16ae 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -1445,6 +1445,7 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: event_type=lr.EVENT_LABEL_REGISTRY_UPDATED, event_filter=_removed_from_registry_filter, listener=_handle_label_registry_update, + run_immediately=True, ) @callback @@ -1458,6 +1459,7 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: event_type=cr.EVENT_CATEGORY_REGISTRY_UPDATED, event_filter=_removed_from_registry_filter, listener=_handle_category_registry_update, + run_immediately=True, ) @callback From 4d75940cd21d183414c290c6c778bb75936a0763 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 08:16:26 -1000 Subject: [PATCH 1217/1691] Start and stop template cache watcher to run_immediately (#113644) --- homeassistant/helpers/template.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 62652e15be9..20140c28ba1 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -209,8 +209,12 @@ def async_setup(hass: HomeAssistant) -> bool: cancel = async_track_time_interval( hass, _async_adjust_lru_sizes, timedelta(minutes=10) ) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_adjust_lru_sizes) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, callback(lambda _: cancel())) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, _async_adjust_lru_sizes, run_immediately=True + ) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, callback(lambda _: cancel()), run_immediately=True + ) return True From d8f74571a1d93e2223676bbc38b3404c8ebcaaad Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 17 Mar 2024 21:21:42 +0100 Subject: [PATCH 1218/1691] Add removal condition to Shelly battery sensor (#113703) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/shelly/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ec5f31dae8a..ad25c83a19b 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -942,6 +942,7 @@ RPC_SENSORS: Final = { device_class=SensorDeviceClass.BATTERY, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, + removal_condition=lambda _config, status, key: (status[key]["battery"] is None), ), "voltmeter": RpcSensorDescription( key="voltmeter", From 685553d17d55cfa1ee9406f8aa454a355f37022b Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 17 Mar 2024 21:23:46 +0100 Subject: [PATCH 1219/1691] Cache late imported async_get_exception_message for HomeAssistantError (#113683) * Cache late imported async_get_exception_message for HomeAssistantError * Use a dict to store the function cache * Update homeassistant/exceptions.py Co-authored-by: J. Nick Koston --------- Co-authored-by: J. Nick Koston --- homeassistant/exceptions.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 409fa3450bd..b9e1ca2e2bd 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import Generator, Sequence +from collections.abc import Callable, Generator, Sequence from dataclasses import dataclass from typing import TYPE_CHECKING @@ -10,6 +10,25 @@ if TYPE_CHECKING: from .core import Context +_function_cache: dict[str, Callable[[str, str, dict[str, str] | None], str]] = {} + + +def import_async_get_exception_message() -> ( + Callable[[str, str, dict[str, str] | None], str] +): + """Return a method that can fetch a translated exception message. + + Defaults to English, requires translations to already be cached. + """ + + # pylint: disable-next=import-outside-toplevel + from .helpers.translation import ( + async_get_exception_message as async_get_exception_message_import, + ) + + return async_get_exception_message_import + + class HomeAssistantError(Exception): """General Home Assistant exception occurred.""" @@ -52,10 +71,12 @@ class HomeAssistantError(Exception): assert self.translation_key is not None assert self.translation_domain is not None - # pylint: disable-next=import-outside-toplevel - from .helpers.translation import async_get_exception_message + if "async_get_exception_message" not in _function_cache: + _function_cache[ + "async_get_exception_message" + ] = import_async_get_exception_message() - self._message = async_get_exception_message( + self._message = _function_cache["async_get_exception_message"]( self.translation_domain, self.translation_key, self.translation_placeholders ) return self._message From 82a60fe8adb104d30098ded5705238d96b64afb9 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Mon, 18 Mar 2024 00:40:38 +0100 Subject: [PATCH 1220/1691] Enable Ruff RSE (#113695) --- homeassistant/components/adguard/entity.py | 2 +- .../components/air_quality/__init__.py | 2 +- .../components/airtouch5/__init__.py | 2 +- .../components/airvisual_pro/__init__.py | 2 +- homeassistant/components/airzone/entity.py | 2 +- .../alarm_control_panel/__init__.py | 14 +++---- homeassistant/components/alexa/resources.py | 2 +- homeassistant/components/api/__init__.py | 2 +- .../components/apple_tv/config_flow.py | 4 +- .../components/aprilaire/__init__.py | 2 +- .../components/aussie_broadband/__init__.py | 4 +- homeassistant/components/auth/indieauth.py | 2 +- .../components/bluesound/media_player.py | 4 +- .../components/brother/config_flow.py | 2 +- homeassistant/components/brunt/__init__.py | 2 +- homeassistant/components/button/__init__.py | 2 +- homeassistant/components/calendar/__init__.py | 10 ++--- homeassistant/components/camera/__init__.py | 26 ++++++------- homeassistant/components/climate/__init__.py | 16 ++++---- .../components/coolmaster/__init__.py | 2 +- homeassistant/components/cover/__init__.py | 4 +- homeassistant/components/cups/sensor.py | 4 +- homeassistant/components/date/__init__.py | 2 +- homeassistant/components/datetime/__init__.py | 2 +- .../components/device_tracker/legacy.py | 6 +-- .../components/directv/media_player.py | 4 +- .../components/dunehd/config_flow.py | 4 +- homeassistant/components/elmax/__init__.py | 2 +- homeassistant/components/elmax/config_flow.py | 2 +- .../components/epsonworkforce/sensor.py | 2 +- homeassistant/components/event/__init__.py | 2 +- homeassistant/components/fan/__init__.py | 10 ++--- homeassistant/components/ffmpeg/__init__.py | 2 +- .../components/file_upload/__init__.py | 4 +- homeassistant/components/filter/sensor.py | 2 +- .../components/flick_electric/config_flow.py | 4 +- .../components/freedompro/coordinator.py | 2 +- homeassistant/components/fritz/common.py | 10 ++--- homeassistant/components/fritz/switch.py | 4 +- .../components/generic/config_flow.py | 4 +- .../components/google_travel_time/helpers.py | 6 +-- .../components/habitica/config_flow.py | 2 +- homeassistant/components/hassio/auth.py | 8 ++-- homeassistant/components/hassio/discovery.py | 2 +- homeassistant/components/hassio/handler.py | 6 +-- homeassistant/components/hassio/http.py | 2 +- homeassistant/components/hassio/ingress.py | 8 ++-- .../components/hassio/websocket_api.py | 2 +- .../components/hdmi_cec/media_player.py | 10 ++--- homeassistant/components/hive/__init__.py | 2 +- homeassistant/components/homekit/__init__.py | 4 +- .../components/homekit/accessories.py | 2 +- homeassistant/components/http/ban.py | 2 +- homeassistant/components/http/static.py | 4 +- .../components/humidifier/__init__.py | 4 +- .../components/icloud/config_flow.py | 2 +- homeassistant/components/image/__init__.py | 10 ++--- .../components/image_processing/__init__.py | 2 +- .../components/image_upload/__init__.py | 2 +- homeassistant/components/imap/coordinator.py | 4 +- homeassistant/components/influxdb/sensor.py | 2 +- homeassistant/components/iqvia/__init__.py | 2 +- .../components/justnimbus/__init__.py | 2 +- homeassistant/components/kef/media_player.py | 2 +- homeassistant/components/kodi/config_flow.py | 2 +- .../components/lawn_mower/__init__.py | 6 +-- homeassistant/components/litejet/light.py | 8 ++-- homeassistant/components/litejet/scene.py | 2 +- homeassistant/components/litejet/switch.py | 4 +- homeassistant/components/lock/__init__.py | 6 +-- homeassistant/components/mailbox/__init__.py | 8 ++-- .../components/media_extractor/__init__.py | 6 +-- .../components/media_player/__init__.py | 38 +++++++++---------- .../components/media_source/local_source.py | 18 ++++----- homeassistant/components/met/coordinator.py | 2 +- .../components/metoffice/__init__.py | 2 +- .../components/metoffice/config_flow.py | 2 +- .../components/mobile_app/webhook.py | 2 +- homeassistant/components/mystrom/__init__.py | 4 +- .../nederlandse_spoorwegen/sensor.py | 2 +- homeassistant/components/notify/legacy.py | 2 +- homeassistant/components/number/__init__.py | 4 +- homeassistant/components/onboarding/views.py | 2 +- homeassistant/components/onewire/__init__.py | 2 +- homeassistant/components/onvif/__init__.py | 2 +- .../components/osoenergy/__init__.py | 2 +- homeassistant/components/proxy/camera.py | 8 ++-- homeassistant/components/remote/__init__.py | 6 +-- homeassistant/components/renault/__init__.py | 6 +-- homeassistant/components/rflink/__init__.py | 2 +- homeassistant/components/risco/__init__.py | 2 +- homeassistant/components/scene/__init__.py | 2 +- .../components/screenlogic/entity.py | 2 +- .../components/screenlogic/number.py | 2 +- homeassistant/components/select/__init__.py | 4 +- homeassistant/components/sfr_box/__init__.py | 4 +- .../components/sfr_box/coordinator.py | 2 +- homeassistant/components/sharkiq/vacuum.py | 4 +- homeassistant/components/sisyphus/light.py | 2 +- .../components/sisyphus/media_player.py | 2 +- .../components/sonos/household_coordinator.py | 4 +- homeassistant/components/stream/core.py | 4 +- homeassistant/components/stt/__init__.py | 8 ++-- .../components/synology_dsm/media_source.py | 6 +-- homeassistant/components/text/__init__.py | 2 +- homeassistant/components/tibber/sensor.py | 4 +- homeassistant/components/time/__init__.py | 2 +- homeassistant/components/todo/__init__.py | 8 ++-- homeassistant/components/tts/__init__.py | 2 +- homeassistant/components/tts/legacy.py | 2 +- homeassistant/components/unifi/entity.py | 2 +- .../components/universal/media_player.py | 2 +- homeassistant/components/update/__init__.py | 4 +- homeassistant/components/vacuum/__init__.py | 16 ++++---- homeassistant/components/valve/__init__.py | 8 ++-- .../components/vlc_telnet/__init__.py | 2 +- .../components/water_heater/__init__.py | 12 +++--- .../components/websocket_api/decorators.py | 2 +- .../components/xiaomi_aqara/__init__.py | 2 +- .../components/xiaomi_miio/__init__.py | 4 +- .../components/yamaha_musiccast/__init__.py | 2 +- .../zha/repairs/wrong_silabs_firmware.py | 2 +- homeassistant/components/zwave_me/__init__.py | 2 +- homeassistant/exceptions.py | 2 +- homeassistant/helpers/aiohttp_client.py | 4 +- homeassistant/helpers/device_registry.py | 2 +- homeassistant/helpers/entity.py | 4 +- homeassistant/helpers/http.py | 8 ++-- homeassistant/helpers/intent.py | 4 +- homeassistant/util/language.py | 2 +- pyproject.toml | 1 + tests/components/anova/conftest.py | 4 +- .../assist_pipeline/test_websocket.py | 2 +- tests/components/conversation/test_init.py | 4 +- tests/components/coolmaster/conftest.py | 2 +- tests/components/lifx/test_config_flow.py | 2 +- tests/components/lifx/test_init.py | 2 +- tests/components/netatmo/test_camera.py | 2 +- tests/components/network/test_init.py | 2 +- tests/components/nibe_heatpump/__init__.py | 2 +- tests/components/nibe_heatpump/conftest.py | 2 +- .../nsw_fuel_station/test_sensor.py | 2 +- tests/components/nx584/test_binary_sensor.py | 4 +- .../owntracks/test_device_tracker.py | 2 +- tests/components/recorder/test_util.py | 2 +- tests/components/sonos/test_init.py | 2 +- tests/components/stream/test_ll_hls.py | 2 +- tests/components/twinkly/__init__.py | 12 +++--- tests/components/twitch/__init__.py | 8 ++-- tests/components/uvc/test_camera.py | 2 +- tests/components/websocket_api/test_http.py | 4 +- tests/components/ws66i/test_media_player.py | 2 +- .../helpers/test_config_entry_oauth2_flow.py | 2 +- tests/test_util/aiohttp.py | 2 +- tests/util/test_timeout.py | 4 +- 155 files changed, 325 insertions(+), 324 deletions(-) diff --git a/homeassistant/components/adguard/entity.py b/homeassistant/components/adguard/entity.py index 74f69442f81..8cb71a861e8 100644 --- a/homeassistant/components/adguard/entity.py +++ b/homeassistant/components/adguard/entity.py @@ -44,7 +44,7 @@ class AdGuardHomeEntity(Entity): async def _adguard_update(self) -> None: """Update AdGuard Home entity.""" - raise NotImplementedError() + raise NotImplementedError @property def device_info(self) -> DeviceInfo: diff --git a/homeassistant/components/air_quality/__init__.py b/homeassistant/components/air_quality/__init__.py index a25874f0c2f..f23f87019b9 100644 --- a/homeassistant/components/air_quality/__init__.py +++ b/homeassistant/components/air_quality/__init__.py @@ -83,7 +83,7 @@ class AirQualityEntity(Entity): @property def particulate_matter_2_5(self) -> StateType: """Return the particulate matter 2.5 level.""" - raise NotImplementedError() + raise NotImplementedError @property def particulate_matter_10(self) -> StateType: diff --git a/homeassistant/components/airtouch5/__init__.py b/homeassistant/components/airtouch5/__init__.py index 8518d8a442e..b8b9a3f765a 100644 --- a/homeassistant/components/airtouch5/__init__.py +++ b/homeassistant/components/airtouch5/__init__.py @@ -27,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await client.connect_and_stay_connected() except TimeoutError as t: - raise ConfigEntryNotReady() from t + raise ConfigEntryNotReady from t # Store an API object for your platforms to access hass.data[DOMAIN][entry.entry_id] = client diff --git a/homeassistant/components/airvisual_pro/__init__.py b/homeassistant/components/airvisual_pro/__init__.py index 74c90b9ed02..88f05d28145 100644 --- a/homeassistant/components/airvisual_pro/__init__.py +++ b/homeassistant/components/airvisual_pro/__init__.py @@ -54,7 +54,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await node.async_connect() except NodeProError as err: - raise ConfigEntryNotReady() from err + raise ConfigEntryNotReady from err reload_task: asyncio.Task | None = None diff --git a/homeassistant/components/airzone/entity.py b/homeassistant/components/airzone/entity.py index 7d2c883f484..b360db61897 100644 --- a/homeassistant/components/airzone/entity.py +++ b/homeassistant/components/airzone/entity.py @@ -44,7 +44,7 @@ class AirzoneEntity(CoordinatorEntity[AirzoneUpdateCoordinator]): def get_airzone_value(self, key: str) -> Any: """Return Airzone entity value by key.""" - raise NotImplementedError() + raise NotImplementedError class AirzoneSystemEntity(AirzoneEntity): diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 37a2fa6a315..63c095ea6ce 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -173,7 +173,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" @@ -181,7 +181,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" @@ -189,7 +189,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" @@ -197,7 +197,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" @@ -205,7 +205,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_arm_vacation(self, code: str | None = None) -> None: """Send arm vacation command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_arm_vacation(self, code: str | None = None) -> None: """Send arm vacation command.""" @@ -213,7 +213,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_trigger(self, code: str | None = None) -> None: """Send alarm trigger command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_trigger(self, code: str | None = None) -> None: """Send alarm trigger command.""" @@ -221,7 +221,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A def alarm_arm_custom_bypass(self, code: str | None = None) -> None: """Send arm custom bypass command.""" - raise NotImplementedError() + raise NotImplementedError async def async_alarm_arm_custom_bypass(self, code: str | None = None) -> None: """Send arm custom bypass command.""" diff --git a/homeassistant/components/alexa/resources.py b/homeassistant/components/alexa/resources.py index b75e5597b6f..4bc63f6ccae 100644 --- a/homeassistant/components/alexa/resources.py +++ b/homeassistant/components/alexa/resources.py @@ -224,7 +224,7 @@ class AlexaCapabilityResource: Return ModeResources, PresetResources friendlyNames serialized. """ - raise NotImplementedError() + raise NotImplementedError def serialize_labels(self, resources: list[str]) -> dict[str, list[dict[str, Any]]]: """Return serialized labels for an API response. diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 25d404cbd15..82aaefe1288 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -413,7 +413,7 @@ class APIDomainServicesView(HomeAssistantView): ) ) except (vol.Invalid, ServiceNotFound) as ex: - raise HTTPBadRequest() from ex + raise HTTPBadRequest from ex finally: cancel_listen() diff --git a/homeassistant/components/apple_tv/config_flow.py b/homeassistant/components/apple_tv/config_flow.py index dea4763a04d..19cbb24d8a2 100644 --- a/homeassistant/components/apple_tv/config_flow.py +++ b/homeassistant/components/apple_tv/config_flow.py @@ -341,7 +341,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN): self.hass, self.scan_filter, self.hass.loop ) if not self.atv: - raise DeviceNotFound() + raise DeviceNotFound # Protocols supported by the device are prospects for pairing self.protocols_to_pair = deque( @@ -384,7 +384,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN): self.hass.config_entries.async_reload(entry.entry_id) ) if not allow_exist: - raise DeviceAlreadyConfigured() + raise DeviceAlreadyConfigured async def async_step_confirm( self, user_input: dict[str, str] | None = None diff --git a/homeassistant/components/aprilaire/__init__.py b/homeassistant/components/aprilaire/__init__.py index b5aeea2a55c..4fa5cdac68d 100644 --- a/homeassistant/components/aprilaire/__init__.py +++ b/homeassistant/components/aprilaire/__init__.py @@ -51,7 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator.stop_listen() - raise ConfigEntryNotReady() + raise ConfigEntryNotReady await coordinator.wait_for_ready(ready_callback) diff --git a/homeassistant/components/aussie_broadband/__init__.py b/homeassistant/components/aussie_broadband/__init__.py index 72fa824bb5b..1fc7e47ebde 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -39,9 +39,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await client.login() services = await client.get_services(drop_types=ignore_types) except AuthenticationException as exc: - raise ConfigEntryAuthFailed() from exc + raise ConfigEntryAuthFailed from exc except ClientError as exc: - raise ConfigEntryNotReady() from exc + raise ConfigEntryNotReady from exc # Create an appropriate refresh function def update_data_factory(service_id): diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index 2c90d33b1d8..0bbfcc01fc7 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -158,7 +158,7 @@ def _parse_client_id(client_id: str) -> ParseResult: # Client identifier URLs # MUST have either an https or http scheme if parts.scheme not in ("http", "https"): - raise ValueError() + raise ValueError # MUST contain a path component # Handled by url canonicalization. diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index 357f971f574..9377557d025 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -366,7 +366,7 @@ class BluesoundPlayer(MediaPlayerEntity): data = None elif response.status == 595: _LOGGER.info("Status 595 returned, treating as timeout") - raise BluesoundPlayer._TimeoutException() + raise BluesoundPlayer._TimeoutException else: _LOGGER.error("Error %s on %s", response.status, url) return None @@ -432,7 +432,7 @@ class BluesoundPlayer(MediaPlayerEntity): self.async_write_ha_state() elif response.status == 595: _LOGGER.info("Status 595 returned, treating as timeout") - raise BluesoundPlayer._TimeoutException() + raise BluesoundPlayer._TimeoutException else: _LOGGER.error( "Error %s on %s. Trying one more time", response.status, url diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 346141cd197..ca2f1ae5a39 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -43,7 +43,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN): if user_input is not None: try: if not is_host_valid(user_input[CONF_HOST]): - raise InvalidHost() + raise InvalidHost snmp_engine = get_snmp_engine(self.hass) diff --git a/homeassistant/components/brunt/__init__.py b/homeassistant/components/brunt/__init__.py index ec3ecd0ce6c..bec281d1902 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -51,7 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise UpdateFailed(f"Error communicating with API: {err}") from err except ClientResponseError as err: if err.status == 403: - raise ConfigEntryAuthFailed() from err + raise ConfigEntryAuthFailed from err if err.status == 401: _LOGGER.warning("Device not found, will reload Brunt integration") await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/button/__init__.py b/homeassistant/components/button/__init__.py index 0385e6b7f98..6826f681d4d 100644 --- a/homeassistant/components/button/__init__.py +++ b/homeassistant/components/button/__init__.py @@ -148,7 +148,7 @@ class ButtonEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_ def press(self) -> None: """Press the button.""" - raise NotImplementedError() + raise NotImplementedError async def async_press(self) -> None: """Press the button.""" diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index cb0256e8346..47ea10b71b6 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -503,7 +503,7 @@ class CalendarEntity(Entity): @property def event(self) -> CalendarEvent | None: """Return the next upcoming event.""" - raise NotImplementedError() + raise NotImplementedError @final @property @@ -599,11 +599,11 @@ class CalendarEntity(Entity): end_date: datetime.datetime, ) -> list[CalendarEvent]: """Return calendar events within a datetime range.""" - raise NotImplementedError() + raise NotImplementedError async def async_create_event(self, **kwargs: Any) -> None: """Add a new event to calendar.""" - raise NotImplementedError() + raise NotImplementedError async def async_delete_event( self, @@ -612,7 +612,7 @@ class CalendarEntity(Entity): recurrence_range: str | None = None, ) -> None: """Delete an event on the calendar.""" - raise NotImplementedError() + raise NotImplementedError async def async_update_event( self, @@ -622,7 +622,7 @@ class CalendarEntity(Entity): recurrence_range: str | None = None, ) -> None: """Delete an event on the calendar.""" - raise NotImplementedError() + raise NotImplementedError class CalendarEventView(http.HomeAssistantView): diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index c91c8c403ca..9671288e7ee 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -651,7 +651,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return bytes of camera image.""" - raise NotImplementedError() + raise NotImplementedError async def async_camera_image( self, width: int | None = None, height: int | None = None @@ -696,7 +696,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_off(self) -> None: """Turn off camera.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_off(self) -> None: """Turn off camera.""" @@ -704,7 +704,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_on(self) -> None: """Turn off camera.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_on(self) -> None: """Turn off camera.""" @@ -712,7 +712,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def enable_motion_detection(self) -> None: """Enable motion detection in the camera.""" - raise NotImplementedError() + raise NotImplementedError async def async_enable_motion_detection(self) -> None: """Call the job and enable motion detection.""" @@ -720,7 +720,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def disable_motion_detection(self) -> None: """Disable motion detection in camera.""" - raise NotImplementedError() + raise NotImplementedError async def async_disable_motion_detection(self) -> None: """Call the job and disable motion detection.""" @@ -797,7 +797,7 @@ class CameraView(HomeAssistantView): async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse: """Start a GET request.""" if (camera := self.component.get_entity(entity_id)) is None: - raise web.HTTPNotFound() + raise web.HTTPNotFound authenticated = ( request[KEY_AUTHENTICATED] @@ -808,19 +808,19 @@ class CameraView(HomeAssistantView): # Attempt with invalid bearer token, raise unauthorized # so ban middleware can handle it. if hdrs.AUTHORIZATION in request.headers: - raise web.HTTPUnauthorized() + raise web.HTTPUnauthorized # Invalid sigAuth or camera access token - raise web.HTTPForbidden() + raise web.HTTPForbidden if not camera.is_on: _LOGGER.debug("Camera is off") - raise web.HTTPServiceUnavailable() + raise web.HTTPServiceUnavailable return await self.handle(request, camera) async def handle(self, request: web.Request, camera: Camera) -> web.StreamResponse: """Handle the camera request.""" - raise NotImplementedError() + raise NotImplementedError class CameraImageView(CameraView): @@ -841,7 +841,7 @@ class CameraImageView(CameraView): int(height) if height else None, ) except (HomeAssistantError, ValueError) as ex: - raise web.HTTPInternalServerError() from ex + raise web.HTTPInternalServerError from ex return web.Response(body=image.content, content_type=image.content_type) @@ -861,7 +861,7 @@ class CameraMjpegStream(CameraView): stream = None _LOGGER.debug("Error while writing MJPEG stream to transport") if stream is None: - raise web.HTTPBadGateway() + raise web.HTTPBadGateway return stream try: @@ -871,7 +871,7 @@ class CameraMjpegStream(CameraView): raise ValueError(f"Stream interval must be > {MIN_STREAM_INTERVAL}") return await camera.handle_async_still_stream(request, interval) except ValueError as err: - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err @websocket_api.websocket_command( diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 2382c535413..d96dd163f41 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -657,7 +657,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -667,7 +667,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_humidity(self, humidity: int) -> None: """Set new target humidity.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" @@ -681,7 +681,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_fan_mode(self, fan_mode: str) -> None: """Set new target fan mode.""" @@ -689,7 +689,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" @@ -703,7 +703,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" @@ -717,7 +717,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" @@ -725,7 +725,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_aux_heat_on(self) -> None: """Turn auxiliary heater on.""" @@ -733,7 +733,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_aux_heat_off(self) -> None: """Turn auxiliary heater off.""" diff --git a/homeassistant/components/coolmaster/__init__.py b/homeassistant/components/coolmaster/__init__.py index 513d8b3c5a9..1f3f5a66380 100644 --- a/homeassistant/components/coolmaster/__init__.py +++ b/homeassistant/components/coolmaster/__init__.py @@ -37,7 +37,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not info: raise ConfigEntryNotReady except OSError as error: - raise ConfigEntryNotReady() from error + raise ConfigEntryNotReady from error coordinator = CoolmasterDataUpdateCoordinator(hass, coolmaster) hass.data.setdefault(DOMAIN, {}) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index aed0680bad3..1eac6844703 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -381,7 +381,7 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def open_cover(self, **kwargs: Any) -> None: """Open the cover.""" - raise NotImplementedError() + raise NotImplementedError async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" @@ -389,7 +389,7 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def close_cover(self, **kwargs: Any) -> None: """Close cover.""" - raise NotImplementedError() + raise NotImplementedError async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index 36f108a1c14..647deee79a6 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -74,7 +74,7 @@ def setup_platform( data.update() if data.available is False: _LOGGER.error("Unable to connect to CUPS server: %s:%s", host, port) - raise PlatformNotReady() + raise PlatformNotReady assert data.printers is not None dev: list[SensorEntity] = [] @@ -97,7 +97,7 @@ def setup_platform( data.update() if data.available is False: _LOGGER.error("Unable to connect to IPP printer: %s:%s", host, port) - raise PlatformNotReady() + raise PlatformNotReady dev = [] for printer in printers: diff --git a/homeassistant/components/date/__init__.py b/homeassistant/components/date/__init__.py index 5f569500c19..3cb6ad3a77d 100644 --- a/homeassistant/components/date/__init__.py +++ b/homeassistant/components/date/__init__.py @@ -110,7 +110,7 @@ class DateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_value(self, value: date) -> None: """Change the date.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_value(self, value: date) -> None: """Change the date.""" diff --git a/homeassistant/components/datetime/__init__.py b/homeassistant/components/datetime/__init__.py index 15829e14de5..420cf27b5aa 100644 --- a/homeassistant/components/datetime/__init__.py +++ b/homeassistant/components/datetime/__init__.py @@ -126,7 +126,7 @@ class DateTimeEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_value(self, value: datetime) -> None: """Change the date/time.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_value(self, value: datetime) -> None: """Change the date/time.""" diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 081d5e2ca67..9ca9567b896 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -940,7 +940,7 @@ class DeviceScanner: def scan_devices(self) -> list[str]: """Scan for devices.""" - raise NotImplementedError() + raise NotImplementedError async def async_scan_devices(self) -> list[str]: """Scan for devices.""" @@ -951,7 +951,7 @@ class DeviceScanner: def get_device_name(self, device: str) -> str | None: """Get the name of a device.""" - raise NotImplementedError() + raise NotImplementedError async def async_get_device_name(self, device: str) -> str | None: """Get the name of a device.""" @@ -962,7 +962,7 @@ class DeviceScanner: def get_extra_attributes(self, device: str) -> dict: """Get the extra attributes of a device.""" - raise NotImplementedError() + raise NotImplementedError async def async_get_extra_attributes(self, device: str) -> dict: """Get the extra attributes of a device.""" diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index 736e30b9f96..6c4a40598de 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -278,7 +278,7 @@ class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity): async def async_turn_on(self) -> None: """Turn on the receiver.""" if self._is_client: - raise NotImplementedError() + raise NotImplementedError _LOGGER.debug("Turn on %s", self.name) await self.dtv.remote("poweron", self._address) @@ -286,7 +286,7 @@ class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity): async def async_turn_off(self) -> None: """Turn off the receiver.""" if self._is_client: - raise NotImplementedError() + raise NotImplementedError _LOGGER.debug("Turn off %s", self.name) await self.dtv.remote("poweroff", self._address) diff --git a/homeassistant/components/dunehd/config_flow.py b/homeassistant/components/dunehd/config_flow.py index 43e919ee42c..8a0f3eec4a0 100644 --- a/homeassistant/components/dunehd/config_flow.py +++ b/homeassistant/components/dunehd/config_flow.py @@ -25,7 +25,7 @@ class DuneHDConfigFlow(ConfigFlow, domain=DOMAIN): player = DuneHDPlayer(host) state = await self.hass.async_add_executor_job(player.update_state) if not state: - raise CannotConnect() + raise CannotConnect async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -39,7 +39,7 @@ class DuneHDConfigFlow(ConfigFlow, domain=DOMAIN): try: if self.host_already_configured(host): - raise AlreadyConfigured() + raise AlreadyConfigured await self.init_device(host) except CannotConnect: errors[CONF_HOST] = "cannot_connect" diff --git a/homeassistant/components/elmax/__init__.py b/homeassistant/components/elmax/__init__.py index ce8ce68d953..518bf1e932b 100644 --- a/homeassistant/components/elmax/__init__.py +++ b/homeassistant/components/elmax/__init__.py @@ -95,7 +95,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: client, panel = await _load_elmax_panel_client(entry) except ElmaxBadLoginError as err: - raise ConfigEntryAuthFailed() from err + raise ConfigEntryAuthFailed from err # Create the API client object and attempt a login, so that we immediately know # if there is something wrong with user credentials diff --git a/homeassistant/components/elmax/config_flow.py b/homeassistant/components/elmax/config_flow.py index d7c6e74ed14..666f4e75fcd 100644 --- a/homeassistant/components/elmax/config_flow.py +++ b/homeassistant/components/elmax/config_flow.py @@ -424,7 +424,7 @@ class ElmaxConfigFlow(ConfigFlow, domain=DOMAIN): if p.hash == self._entry.data[CONF_ELMAX_PANEL_ID] ] if len(panels) < 1: - raise NoOnlinePanelsError() + raise NoOnlinePanelsError # Verify the pin is still valid. await client.get_panel_status( diff --git a/homeassistant/components/epsonworkforce/sensor.py b/homeassistant/components/epsonworkforce/sensor.py index f8b11fc31f4..dea611a3c3a 100644 --- a/homeassistant/components/epsonworkforce/sensor.py +++ b/homeassistant/components/epsonworkforce/sensor.py @@ -81,7 +81,7 @@ def setup_platform( api = EpsonPrinterAPI(host) if not api.available: - raise PlatformNotReady() + raise PlatformNotReady sensors = [ EpsonPrinterCartridge(api, description) diff --git a/homeassistant/components/event/__init__.py b/homeassistant/components/event/__init__.py index fc9add8c62e..2ad0e6d950b 100644 --- a/homeassistant/components/event/__init__.py +++ b/homeassistant/components/event/__init__.py @@ -147,7 +147,7 @@ class EventEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_) and self.entity_description.event_types is not None ): return self.entity_description.event_types - raise AttributeError() + raise AttributeError @final def _trigger_event( diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index aeb3a6c89df..185b9631198 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -238,7 +238,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" @@ -277,7 +277,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - raise NotImplementedError() + raise NotImplementedError @final async def async_handle_set_preset_mode_service(self, preset_mode: str) -> None: @@ -307,7 +307,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_direction(self, direction: str) -> None: """Set the direction of the fan.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_direction(self, direction: str) -> None: """Set the direction of the fan.""" @@ -320,7 +320,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): **kwargs: Any, ) -> None: """Turn on the fan.""" - raise NotImplementedError() + raise NotImplementedError @final async def async_handle_turn_on_service( @@ -352,7 +352,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" - raise NotImplementedError() + raise NotImplementedError async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index ec19df5fb6f..2045b6bb06b 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -229,7 +229,7 @@ class FFmpegBase(Entity, Generic[_HAFFmpegT]): This method is a coroutine. """ - raise NotImplementedError() + raise NotImplementedError async def _async_stop_ffmpeg(self, entity_ids: list[str] | None) -> None: """Stop a FFmpeg process. diff --git a/homeassistant/components/file_upload/__init__.py b/homeassistant/components/file_upload/__init__.py index 94c3c39ce92..bb8c34fb28a 100644 --- a/homeassistant/components/file_upload/__init__.py +++ b/homeassistant/components/file_upload/__init__.py @@ -203,13 +203,13 @@ class FileUploadView(HomeAssistantView): hass = request.app[KEY_HASS] if DOMAIN not in hass.data: - raise web.HTTPNotFound() + raise web.HTTPNotFound file_id = data["file_id"] file_upload_data: FileUploadData = hass.data[DOMAIN] if file_upload_data.files.pop(file_id, None) is None: - raise web.HTTPNotFound() + raise web.HTTPNotFound await hass.async_add_executor_job( lambda: shutil.rmtree(file_upload_data.file_dir(file_id)) diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index 4289444c6f4..5ae300f6ec4 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -469,7 +469,7 @@ class Filter: def _filter_state(self, new_state: FilterState) -> FilterState: """Implement filter.""" - raise NotImplementedError() + raise NotImplementedError def filter_state(self, new_state: _State) -> _State: """Implement a common interface for filters.""" diff --git a/homeassistant/components/flick_electric/config_flow.py b/homeassistant/components/flick_electric/config_flow.py index efe8a5725f7..41b58431977 100644 --- a/homeassistant/components/flick_electric/config_flow.py +++ b/homeassistant/components/flick_electric/config_flow.py @@ -49,9 +49,9 @@ class FlickConfigFlow(ConfigFlow, domain=DOMAIN): async with asyncio.timeout(60): token = await auth.async_get_access_token() except TimeoutError as err: - raise CannotConnect() from err + raise CannotConnect from err except AuthException as err: - raise InvalidAuth() from err + raise InvalidAuth from err return token is not None diff --git a/homeassistant/components/freedompro/coordinator.py b/homeassistant/components/freedompro/coordinator.py index c5f8ea990dc..ad76a9aaa65 100644 --- a/homeassistant/components/freedompro/coordinator.py +++ b/homeassistant/components/freedompro/coordinator.py @@ -36,7 +36,7 @@ class FreedomproDataUpdateCoordinator(DataUpdateCoordinator[list[dict[str, Any]] if result["state"]: self._devices = result["devices"] else: - raise UpdateFailed() + raise UpdateFailed result = await get_states( aiohttp_client.async_get_clientsession(self._hass), self._api_key diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 39b3c8fbc20..8e773e74c75 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -342,21 +342,21 @@ class FritzBoxTools( def unique_id(self) -> str: """Return unique id.""" if not self._unique_id: - raise ClassSetupMissing() + raise ClassSetupMissing return self._unique_id @property def model(self) -> str: """Return device model.""" if not self._model: - raise ClassSetupMissing() + raise ClassSetupMissing return self._model @property def current_firmware(self) -> str: """Return current SW version.""" if not self._current_firmware: - raise ClassSetupMissing() + raise ClassSetupMissing return self._current_firmware @property @@ -378,7 +378,7 @@ class FritzBoxTools( def mac(self) -> str: """Return device Mac address.""" if not self._unique_id: - raise ClassSetupMissing() + raise ClassSetupMissing return dr.format_mac(self._unique_id) @property @@ -966,7 +966,7 @@ class FritzDeviceBase(update_coordinator.CoordinatorEntity[AvmWrapper]): async def async_process_update(self) -> None: """Update device.""" - raise NotImplementedError() + raise NotImplementedError async def async_on_demand_update(self) -> None: """Update state.""" diff --git a/homeassistant/components/fritz/switch.py b/homeassistant/components/fritz/switch.py index 26926c09223..913d0165247 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -289,7 +289,7 @@ class FritzBoxBaseCoordinatorSwitch(CoordinatorEntity[AvmWrapper], SwitchEntity) @property def data(self) -> dict[str, Any]: """Return entity data from coordinator data.""" - raise NotImplementedError() + raise NotImplementedError @property def available(self) -> bool: @@ -298,7 +298,7 @@ class FritzBoxBaseCoordinatorSwitch(CoordinatorEntity[AvmWrapper], SwitchEntity) async def _async_handle_turn_on_off(self, turn_on: bool) -> None: """Handle switch state change request.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_on(self, **kwargs: Any) -> None: """Turn on switch.""" diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 0964e7f2b29..621b4dd299b 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -480,12 +480,12 @@ class CameraImagePreview(HomeAssistantView): flow = self.hass.config_entries.options.async_get(flow_id) except UnknownFlow as exc: _LOGGER.warning("Unknown flow while getting image preview") - raise web.HTTPNotFound() from exc + raise web.HTTPNotFound from exc user_input = flow["context"]["preview_cam"] camera = GenericCamera(self.hass, user_input, flow_id, "preview") if not camera.is_on: _LOGGER.debug("Camera is off") - raise web.HTTPServiceUnavailable() + raise web.HTTPServiceUnavailable image = await _async_get_image( camera, CAMERA_IMAGE_TIMEOUT, diff --git a/homeassistant/components/google_travel_time/helpers.py b/homeassistant/components/google_travel_time/helpers.py index d36adb21c18..baceffecc73 100644 --- a/homeassistant/components/google_travel_time/helpers.py +++ b/homeassistant/components/google_travel_time/helpers.py @@ -30,13 +30,13 @@ def validate_config_entry( _LOGGER.error("Request denied: %s", api_error.message) raise InvalidApiKeyException from api_error _LOGGER.error("Unknown error: %s", api_error.message) - raise UnknownException() from api_error + raise UnknownException from api_error except TransportError as transport_error: _LOGGER.error("Unknown error: %s", transport_error) - raise UnknownException() from transport_error + raise UnknownException from transport_error except Timeout as timeout_error: _LOGGER.error("Timeout error") - raise TimeoutError() from timeout_error + raise TimeoutError from timeout_error class InvalidApiKeyException(Exception): diff --git a/homeassistant/components/habitica/config_flow.py b/homeassistant/components/habitica/config_flow.py index 18bdb4b45ce..9ee2aef40ba 100644 --- a/homeassistant/components/habitica/config_flow.py +++ b/homeassistant/components/habitica/config_flow.py @@ -46,7 +46,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, CONF_API_USER: data[CONF_API_USER], } except ClientResponseError as ex: - raise InvalidAuth() from ex + raise InvalidAuth from ex class HabiticaConfigFlow(ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py index 52016b20659..6ca89ee24be 100644 --- a/homeassistant/components/hassio/auth.py +++ b/homeassistant/components/hassio/auth.py @@ -48,12 +48,12 @@ class HassIOBaseAuth(HomeAssistantView): hassio_ip ): _LOGGER.error("Invalid auth request from %s", request.remote) - raise HTTPUnauthorized() + raise HTTPUnauthorized # Check caller token if request[KEY_HASS_USER].id != self.user.id: _LOGGER.error("Invalid auth request from %s", request[KEY_HASS_USER].name) - raise HTTPUnauthorized() + raise HTTPUnauthorized class HassIOAuth(HassIOBaseAuth): @@ -82,7 +82,7 @@ class HassIOAuth(HassIOBaseAuth): data[ATTR_USERNAME], data[ATTR_PASSWORD] ) except auth_ha.InvalidAuth: - raise HTTPNotFound() from None + raise HTTPNotFound from None return web.Response(status=HTTPStatus.OK) @@ -112,6 +112,6 @@ class HassIOPasswordReset(HassIOBaseAuth): data[ATTR_USERNAME], data[ATTR_PASSWORD] ) except auth_ha.InvalidUser as err: - raise HTTPNotFound() from err + raise HTTPNotFound from err return web.Response(status=HTTPStatus.OK) diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py index 173583c9891..66be8267d53 100644 --- a/homeassistant/components/hassio/discovery.py +++ b/homeassistant/components/hassio/discovery.py @@ -78,7 +78,7 @@ class HassIODiscovery(HomeAssistantView): data = await self.hassio.get_discovery_message(uuid) except HassioAPIError as err: _LOGGER.error("Can't read discovery data: %s", err) - raise HTTPServiceUnavailable() from None + raise HTTPServiceUnavailable from None await self.async_process_new(data) return web.Response() diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index db0a5ac0528..ff34aa06cf3 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -571,7 +571,7 @@ class HassIO: # such as ../../../../etc/passwd if url != str(joined_url): _LOGGER.error("Invalid request %s", command) - raise HassioAPIError() + raise HassioAPIError try: response = await self.websession.request( @@ -598,7 +598,7 @@ class HassIO: method, response.status, ) - raise HassioAPIError() + raise HassioAPIError if return_text: return await response.text(encoding="utf-8") @@ -611,4 +611,4 @@ class HassIO: except aiohttp.ClientError as err: _LOGGER.error("Client error on %s request %s", command, err) - raise HassioAPIError() + raise HassioAPIError diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index edbcf93d7e5..0b92ada6c70 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -196,7 +196,7 @@ class HassIOView(HomeAssistantView): except TimeoutError: _LOGGER.error("Client timeout error on API request %s", path) - raise HTTPBadGateway() + raise HTTPBadGateway get = _handle post = _handle diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index d325fb10f35..6d6faa6fe75 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -77,10 +77,10 @@ class HassIOIngress(HomeAssistantView): try: target_url = URL(url) except ValueError as err: - raise HTTPBadRequest() from err + raise HTTPBadRequest from err if not target_url.path.startswith(base_path): - raise HTTPBadRequest() + raise HTTPBadRequest return target_url @@ -99,7 +99,7 @@ class HassIOIngress(HomeAssistantView): except aiohttp.ClientError as err: _LOGGER.debug("Ingress error with %s / %s: %s", token, path, err) - raise HTTPBadGateway() from None + raise HTTPBadGateway from None get = _handle post = _handle @@ -247,7 +247,7 @@ def _init_header(request: web.Request, token: str) -> CIMultiDict | dict[str, st assert request.transport if (peername := request.transport.get_extra_info("peername")) is None: _LOGGER.error("Can't set forward_for header, missing peername") - raise HTTPBadRequest() + raise HTTPBadRequest headers[hdrs.X_FORWARDED_FOR] = _forwarded_for_header(forward_for, peername[0]) diff --git a/homeassistant/components/hassio/websocket_api.py b/homeassistant/components/hassio/websocket_api.py index 6313ebd5eaf..03ca424035c 100644 --- a/homeassistant/components/hassio/websocket_api.py +++ b/homeassistant/components/hassio/websocket_api.py @@ -112,7 +112,7 @@ async def websocket_supervisor_api( if not connection.user.is_admin and not WS_NO_ADMIN_ENDPOINTS.match( msg[ATTR_ENDPOINT] ): - raise Unauthorized() + raise Unauthorized supervisor: HassIO = hass.data[DOMAIN] command = msg[ATTR_ENDPOINT] diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index 4cd95a6c382..e86a1f5be70 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -95,7 +95,7 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity): def clear_playlist(self) -> None: """Clear players playlist.""" - raise NotImplementedError() + raise NotImplementedError def turn_off(self) -> None: """Turn device off.""" @@ -111,7 +111,7 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity): self, media_type: MediaType | str, media_id: str, **kwargs: Any ) -> None: """Not supported.""" - raise NotImplementedError() + raise NotImplementedError def media_next_track(self) -> None: """Skip to next track.""" @@ -119,11 +119,11 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity): def media_seek(self, position: float) -> None: """Not supported.""" - raise NotImplementedError() + raise NotImplementedError def set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" - raise NotImplementedError() + raise NotImplementedError def media_pause(self) -> None: """Pause playback.""" @@ -132,7 +132,7 @@ class CecPlayerEntity(CecEntity, MediaPlayerEntity): def select_source(self, source: str) -> None: """Not supported.""" - raise NotImplementedError() + raise NotImplementedError def media_play(self) -> None: """Start playback.""" diff --git a/homeassistant/components/hive/__init__.py b/homeassistant/components/hive/__init__.py index 834054dd91c..fb2733223eb 100644 --- a/homeassistant/components/hive/__init__.py +++ b/homeassistant/components/hive/__init__.py @@ -90,7 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: devices = await hive.session.startSession(hive_config) except HTTPException as error: _LOGGER.error("Could not connect to the internet: %s", error) - raise ConfigEntryNotReady() from error + raise ConfigEntryNotReady from error except HiveReauthRequired as err: raise ConfigEntryAuthFailed from err diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 8fc96cff954..a5c08a3bbd4 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -1163,7 +1163,7 @@ class HomeKitPairingQRView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Retrieve the pairing QRCode image.""" if not request.query_string: - raise Unauthorized() + raise Unauthorized entry_id, secret = request.query_string.split("-") hass = request.app[KEY_HASS] domain_data: dict[str, HomeKitEntryData] = hass.data[DOMAIN] @@ -1173,7 +1173,7 @@ class HomeKitPairingQRView(HomeAssistantView): or not entry_data.pairing_qr_secret or secret != entry_data.pairing_qr_secret ): - raise Unauthorized() + raise Unauthorized return web.Response( body=entry_data.pairing_qr, content_type="image/svg+xml", diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 7c8eb973911..f39bf32744a 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -588,7 +588,7 @@ class HomeAccessory(Accessory): # type: ignore[misc] Overridden by accessory types. """ - raise NotImplementedError() + raise NotImplementedError @ha_callback def async_call_service( diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index 5a0f40cdeab..eec951fb907 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -82,7 +82,7 @@ async def ban_middleware( # Verify if IP is not banned ip_address_ = ip_address(request.remote) # type: ignore[arg-type] if ip_address_ in ip_bans_lookup: - raise HTTPForbidden() + raise HTTPForbidden try: return await handler(request) diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index bdc7b08ee4a..fd6cd742ce4 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -52,14 +52,14 @@ class CachingStaticResource(StaticResource): filepath = await hass.async_add_executor_job(_get_file_path, *key) except (ValueError, FileNotFoundError) as error: # relatively safe - raise HTTPNotFound() from error + raise HTTPNotFound from error except HTTPForbidden: # forbidden raise except Exception as error: # perm error or other kind! request.app.logger.exception(error) - raise HTTPNotFound() from error + raise HTTPNotFound from error content_type: str | None = None if filepath is not None: diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index 97f78aba21c..bf3ce16fa92 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -248,7 +248,7 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT def set_humidity(self, humidity: int) -> None: """Set new target humidity.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" @@ -256,7 +256,7 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT def set_mode(self, mode: str) -> None: """Set new mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_mode(self, mode: str) -> None: """Set new mode.""" diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index caec770743b..36fe880ec79 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -141,7 +141,7 @@ class IcloudFlowHandler(ConfigFlow, domain=DOMAIN): getattr, self.api, "devices" ) if not devices: - raise PyiCloudNoDevicesException() + raise PyiCloudNoDevicesException except (PyiCloudServiceNotActivatedException, PyiCloudNoDevicesException): _LOGGER.error("No device found in the iCloud account: %s", self._username) self.api = None diff --git a/homeassistant/components/image/__init__.py b/homeassistant/components/image/__init__.py index 35869f31792..47767a004cb 100644 --- a/homeassistant/components/image/__init__.py +++ b/homeassistant/components/image/__init__.py @@ -190,7 +190,7 @@ class ImageEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def image(self) -> bytes | None: """Return bytes of image.""" - raise NotImplementedError() + raise NotImplementedError async def _fetch_url(self, url: str) -> httpx.Response | None: """Fetch a URL.""" @@ -278,7 +278,7 @@ class ImageView(HomeAssistantView): async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse: """Start a GET request.""" if (image_entity := self.component.get_entity(entity_id)) is None: - raise web.HTTPNotFound() + raise web.HTTPNotFound authenticated = ( request[KEY_AUTHENTICATED] @@ -289,9 +289,9 @@ class ImageView(HomeAssistantView): # Attempt with invalid bearer token, raise unauthorized # so ban middleware can handle it. if hdrs.AUTHORIZATION in request.headers: - raise web.HTTPUnauthorized() + raise web.HTTPUnauthorized # Invalid sigAuth or image entity access token - raise web.HTTPForbidden() + raise web.HTTPForbidden return await self.handle(request, image_entity) @@ -302,7 +302,7 @@ class ImageView(HomeAssistantView): try: image = await _async_get_image(image_entity, IMAGE_TIMEOUT) except (HomeAssistantError, ValueError) as ex: - raise web.HTTPInternalServerError() from ex + raise web.HTTPInternalServerError from ex return web.Response(body=image.content, content_type=image.content_type) diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index ed366c0bdfc..2c1d0f9304c 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -165,7 +165,7 @@ class ImageProcessingEntity(Entity): def process_image(self, image: bytes) -> None: """Process image.""" - raise NotImplementedError() + raise NotImplementedError async def async_process_image(self, image: bytes) -> None: """Process image.""" diff --git a/homeassistant/components/image_upload/__init__.py b/homeassistant/components/image_upload/__init__.py index 4bde9730674..19763e65fa5 100644 --- a/homeassistant/components/image_upload/__init__.py +++ b/homeassistant/components/image_upload/__init__.py @@ -199,7 +199,7 @@ class ImageServeView(HomeAssistantView): image_info = self.image_collection.data.get(image_id) if image_info is None: - raise web.HTTPNotFound() + raise web.HTTPNotFound hass = request.app[KEY_HASS] target_file = self.image_folder / image_id / f"{width}x{height}" diff --git a/homeassistant/components/imap/coordinator.py b/homeassistant/components/imap/coordinator.py index 2881f9f515e..78b52e06db3 100644 --- a/homeassistant/components/imap/coordinator.py +++ b/homeassistant/components/imap/coordinator.py @@ -406,7 +406,7 @@ class ImapPollingDataUpdateCoordinator(ImapDataUpdateCoordinator): ) as ex: await self._cleanup() self.async_set_update_error(ex) - raise UpdateFailed() from ex + raise UpdateFailed from ex except InvalidFolder as ex: _LOGGER.warning("Selected mailbox folder is invalid") await self._cleanup() @@ -423,7 +423,7 @@ class ImapPollingDataUpdateCoordinator(ImapDataUpdateCoordinator): ) self.config_entry.async_start_reauth(self.hass) self.async_set_update_error(ex) - raise ConfigEntryAuthFailed() from ex + raise ConfigEntryAuthFailed from ex class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator): diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index b9ce68e198c..03b6acb204c 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -166,7 +166,7 @@ def setup_platform( influx = get_influx_connection(config, test_read=True) except ConnectionError as exc: _LOGGER.error(exc) - raise PlatformNotReady() from exc + raise PlatformNotReady from exc entities = [] if CONF_QUERIES_FLUX in config: diff --git a/homeassistant/components/iqvia/__init__.py b/homeassistant/components/iqvia/__init__.py index 27d7d46f677..eef7f929cab 100644 --- a/homeassistant/components/iqvia/__init__.py +++ b/homeassistant/components/iqvia/__init__.py @@ -92,7 +92,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # The IQVIA API can be selectively flaky, meaning that any number of the setup # API calls could fail. We only retry integration setup if *all* of the initial # API calls fail: - raise ConfigEntryNotReady() + raise ConfigEntryNotReady # Once we've successfully authenticated, we re-enable client request retries: client.enable_request_retries() diff --git a/homeassistant/components/justnimbus/__init__.py b/homeassistant/components/justnimbus/__init__.py index e4be25b8959..101a2086962 100644 --- a/homeassistant/components/justnimbus/__init__.py +++ b/homeassistant/components/justnimbus/__init__.py @@ -15,7 +15,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if "zip_code" in entry.data: coordinator = JustNimbusCoordinator(hass=hass, entry=entry) else: - raise ConfigEntryAuthFailed() + raise ConfigEntryAuthFailed await coordinator.async_config_entry_first_refresh() hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index 03f3468dc15..d7d33dabd44 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -270,7 +270,7 @@ class KefMediaPlayer(MediaPlayerEntity): async def async_turn_on(self) -> None: """Turn the media player on.""" if not self._supports_on: - raise NotImplementedError() + raise NotImplementedError await self._speaker.turn_on() async def async_volume_up(self) -> None: diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index 30cac5dbdd4..1d9d1ca4f7c 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -78,7 +78,7 @@ async def validate_ws(hass: HomeAssistant, data): await kwc.connect() if not kwc.connected: _LOGGER.warning("Cannot connect to %s:%s over WebSocket", host, ws_port) - raise WSCannotConnect() + raise WSCannotConnect kodi = Kodi(kwc) await kodi.ping() except CannotConnectError as error: diff --git a/homeassistant/components/lawn_mower/__init__.py b/homeassistant/components/lawn_mower/__init__.py index 5be457ab88f..f28bd1308b0 100644 --- a/homeassistant/components/lawn_mower/__init__.py +++ b/homeassistant/components/lawn_mower/__init__.py @@ -108,7 +108,7 @@ class LawnMowerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def start_mowing(self) -> None: """Start or resume mowing.""" - raise NotImplementedError() + raise NotImplementedError async def async_start_mowing(self) -> None: """Start or resume mowing.""" @@ -116,7 +116,7 @@ class LawnMowerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def dock(self) -> None: """Dock the mower.""" - raise NotImplementedError() + raise NotImplementedError async def async_dock(self) -> None: """Dock the mower.""" @@ -124,7 +124,7 @@ class LawnMowerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def pause(self) -> None: """Pause the lawn mower.""" - raise NotImplementedError() + raise NotImplementedError async def async_pause(self) -> None: """Pause the lawn mower.""" diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index b387c6f8890..f2b9af9adb4 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -97,7 +97,7 @@ class LiteJetLight(LightEntity): try: await self._lj.activate_load(self._index) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc return # If either attribute is specified then Home Assistant must @@ -109,7 +109,7 @@ class LiteJetLight(LightEntity): try: await self._lj.activate_load_at(self._index, brightness, int(transition)) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" @@ -117,7 +117,7 @@ class LiteJetLight(LightEntity): try: await self._lj.activate_load_at(self._index, 0, kwargs[ATTR_TRANSITION]) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc return # If transition attribute is not specified then the simple @@ -126,7 +126,7 @@ class LiteJetLight(LightEntity): try: await self._lj.deactivate_load(self._index) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc async def async_update(self) -> None: """Retrieve the light's brightness from the LiteJet system.""" diff --git a/homeassistant/components/litejet/scene.py b/homeassistant/components/litejet/scene.py index ab1e0687710..712e223aa3e 100644 --- a/homeassistant/components/litejet/scene.py +++ b/homeassistant/components/litejet/scene.py @@ -77,4 +77,4 @@ class LiteJetScene(Scene): try: await self._lj.activate_scene(self._index) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index cf294be8054..28f751f3ec1 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -92,11 +92,11 @@ class LiteJetSwitch(SwitchEntity): try: await self._lj.press_switch(self._index) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc async def async_turn_off(self, **kwargs: Any) -> None: """Release the switch.""" try: await self._lj.release_switch(self._index) except LiteJetError as exc: - raise HomeAssistantError() from exc + raise HomeAssistantError from exc diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index da342410308..e8b7a013523 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -219,7 +219,7 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def lock(self, **kwargs: Any) -> None: """Lock the lock.""" - raise NotImplementedError() + raise NotImplementedError async def async_lock(self, **kwargs: Any) -> None: """Lock the lock.""" @@ -232,7 +232,7 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def unlock(self, **kwargs: Any) -> None: """Unlock the lock.""" - raise NotImplementedError() + raise NotImplementedError async def async_unlock(self, **kwargs: Any) -> None: """Unlock the lock.""" @@ -245,7 +245,7 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def open(self, **kwargs: Any) -> None: """Open the door latch.""" - raise NotImplementedError() + raise NotImplementedError async def async_open(self, **kwargs: Any) -> None: """Open the door latch.""" diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index 4d585413519..b000f1eadcb 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -180,7 +180,7 @@ class Mailbox: @property def media_type(self) -> str: """Return the supported media type.""" - raise NotImplementedError() + raise NotImplementedError @property def can_delete(self) -> bool: @@ -194,15 +194,15 @@ class Mailbox: async def async_get_media(self, msgid: str) -> bytes: """Return the media blob for the msgid.""" - raise NotImplementedError() + raise NotImplementedError async def async_get_messages(self) -> list[dict[str, Any]]: """Return a list of the current messages.""" - raise NotImplementedError() + raise NotImplementedError async def async_delete(self, msgid: str) -> bool: """Delete the specified messages.""" - raise NotImplementedError() + raise NotImplementedError class StreamError(Exception): diff --git a/homeassistant/components/media_extractor/__init__.py b/homeassistant/components/media_extractor/__init__.py index f6b153b2ef8..888265e8d3c 100644 --- a/homeassistant/components/media_extractor/__init__.py +++ b/homeassistant/components/media_extractor/__init__.py @@ -127,7 +127,7 @@ class MediaExtractor: all_media = ydl.extract_info(self.get_media_url(), process=False) except DownloadError as err: # This exception will be logged by youtube-dl itself - raise MEDownloadException() from err + raise MEDownloadException from err if "entries" in all_media: _LOGGER.warning("Playlists are not supported, looking for the first video") @@ -136,7 +136,7 @@ class MediaExtractor: selected_media = entries[0] else: _LOGGER.error("Playlist is empty") - raise MEDownloadException() + raise MEDownloadException else: selected_media = all_media @@ -147,7 +147,7 @@ class MediaExtractor: requested_stream = ydl.process_ie_result(selected_media, download=False) except (ExtractorError, DownloadError) as err: _LOGGER.error("Could not extract stream for the query: %s", query) - raise MEQueryException() from err + raise MEQueryException from err if "formats" in requested_stream: if requested_stream["extractor"] == "youtube": diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index cc768232287..6535aea3e52 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -783,7 +783,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_on(self) -> None: """Turn the media player on.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_on(self) -> None: """Turn the media player on.""" @@ -791,7 +791,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_off(self) -> None: """Turn the media player off.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_off(self) -> None: """Turn the media player off.""" @@ -799,7 +799,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def mute_volume(self, mute: bool) -> None: """Mute the volume.""" - raise NotImplementedError() + raise NotImplementedError async def async_mute_volume(self, mute: bool) -> None: """Mute the volume.""" @@ -807,7 +807,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" @@ -815,7 +815,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def media_play(self) -> None: """Send play command.""" - raise NotImplementedError() + raise NotImplementedError async def async_media_play(self) -> None: """Send play command.""" @@ -823,7 +823,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def media_pause(self) -> None: """Send pause command.""" - raise NotImplementedError() + raise NotImplementedError async def async_media_pause(self) -> None: """Send pause command.""" @@ -831,7 +831,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def media_stop(self) -> None: """Send stop command.""" - raise NotImplementedError() + raise NotImplementedError async def async_media_stop(self) -> None: """Send stop command.""" @@ -839,7 +839,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def media_previous_track(self) -> None: """Send previous track command.""" - raise NotImplementedError() + raise NotImplementedError async def async_media_previous_track(self) -> None: """Send previous track command.""" @@ -847,7 +847,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def media_next_track(self) -> None: """Send next track command.""" - raise NotImplementedError() + raise NotImplementedError async def async_media_next_track(self) -> None: """Send next track command.""" @@ -855,7 +855,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def media_seek(self, position: float) -> None: """Send seek command.""" - raise NotImplementedError() + raise NotImplementedError async def async_media_seek(self, position: float) -> None: """Send seek command.""" @@ -865,7 +865,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): self, media_type: MediaType | str, media_id: str, **kwargs: Any ) -> None: """Play a piece of media.""" - raise NotImplementedError() + raise NotImplementedError async def async_play_media( self, media_type: MediaType | str, media_id: str, **kwargs: Any @@ -877,7 +877,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def select_source(self, source: str) -> None: """Select input source.""" - raise NotImplementedError() + raise NotImplementedError async def async_select_source(self, source: str) -> None: """Select input source.""" @@ -885,7 +885,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def select_sound_mode(self, sound_mode: str) -> None: """Select sound mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_select_sound_mode(self, sound_mode: str) -> None: """Select sound mode.""" @@ -893,7 +893,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def clear_playlist(self) -> None: """Clear players playlist.""" - raise NotImplementedError() + raise NotImplementedError async def async_clear_playlist(self) -> None: """Clear players playlist.""" @@ -901,7 +901,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_shuffle(self, shuffle: bool) -> None: """Enable/disable shuffle mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_shuffle(self, shuffle: bool) -> None: """Enable/disable shuffle mode.""" @@ -909,7 +909,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_repeat(self, repeat: RepeatMode) -> None: """Set repeat mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_repeat(self, repeat: RepeatMode) -> None: """Set repeat mode.""" @@ -1135,11 +1135,11 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): The BrowseMedia instance will be used by the "media_player/browse_media" websocket command. """ - raise NotImplementedError() + raise NotImplementedError def join_players(self, group_members: list[str]) -> None: """Join `group_members` as a player group with the current player.""" - raise NotImplementedError() + raise NotImplementedError async def async_join_players(self, group_members: list[str]) -> None: """Join `group_members` as a player group with the current player.""" @@ -1147,7 +1147,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def unjoin_player(self) -> None: """Remove this player from any group.""" - raise NotImplementedError() + raise NotImplementedError async def async_unjoin_player(self) -> None: """Remove this player from any group.""" diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 8d739d8d027..8a67ae4a5b4 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -219,21 +219,21 @@ class LocalMediaView(http.HomeAssistantView): try: raise_if_invalid_path(location) except ValueError as err: - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err if source_dir_id not in self.hass.config.media_dirs: - raise web.HTTPNotFound() + raise web.HTTPNotFound media_path = self.source.async_full_path(source_dir_id, location) # Check that the file exists if not media_path.is_file(): - raise web.HTTPNotFound() + raise web.HTTPNotFound # Check that it's a media file mime_type, _ = mimetypes.guess_type(str(media_path)) if not mime_type or mime_type.split("/")[0] not in MEDIA_MIME_TYPES: - raise web.HTTPNotFound() + raise web.HTTPNotFound return web.FileResponse(media_path) @@ -265,19 +265,19 @@ class UploadMediaView(http.HomeAssistantView): data = self.schema(dict(await request.post())) except vol.Invalid as err: LOGGER.error("Received invalid upload data: %s", err) - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err try: item = MediaSourceItem.from_uri(self.hass, data["media_content_id"], None) except ValueError as err: LOGGER.error("Received invalid upload data: %s", err) - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err try: source_dir_id, location = self.source.async_parse_identifier(item) except Unresolvable as err: LOGGER.error("Invalid local source ID") - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err uploaded_file: FileField = data["file"] @@ -289,7 +289,7 @@ class UploadMediaView(http.HomeAssistantView): raise_if_invalid_filename(uploaded_file.filename) except ValueError as err: LOGGER.error("Invalid filename") - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err try: await self.hass.async_add_executor_job( @@ -299,7 +299,7 @@ class UploadMediaView(http.HomeAssistantView): ) except ValueError as err: LOGGER.error("Moving upload failed: %s", err) - raise web.HTTPBadRequest() from err + raise web.HTTPBadRequest from err return self.json( {"media_content_id": f"{data['media_content_id']}/{uploaded_file.filename}"} diff --git a/homeassistant/components/met/coordinator.py b/homeassistant/components/met/coordinator.py index e5a4dbf754c..ef73e1b52ab 100644 --- a/homeassistant/components/met/coordinator.py +++ b/homeassistant/components/met/coordinator.py @@ -78,7 +78,7 @@ class MetWeatherData: """Fetch data from API - (current weather and forecast).""" resp = await self._weather_data.fetching_data() if not resp: - raise CannotConnect() + raise CannotConnect self.current_weather_data = self._weather_data.get_current_weather() time_zone = dt_util.DEFAULT_TIME_ZONE self.daily_forecast = self._weather_data.get_forecast(time_zone, False, 0) diff --git a/homeassistant/components/metoffice/__init__.py b/homeassistant/components/metoffice/__init__.py index cdc6c2c7500..18fc121d5d3 100644 --- a/homeassistant/components/metoffice/__init__.py +++ b/homeassistant/components/metoffice/__init__.py @@ -94,7 +94,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: fetch_site, connection, latitude, longitude ) if site is None: - raise ConfigEntryNotReady() + raise ConfigEntryNotReady async def async_update_3hourly() -> MetOfficeData: return await hass.async_add_executor_job( diff --git a/homeassistant/components/metoffice/config_flow.py b/homeassistant/components/metoffice/config_flow.py index 196d24ff68a..8b3c10cd460 100644 --- a/homeassistant/components/metoffice/config_flow.py +++ b/homeassistant/components/metoffice/config_flow.py @@ -36,7 +36,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, ) if site is None: - raise CannotConnect() + raise CannotConnect return {"site_name": site.name} diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 3cef5ae645a..3e16c21261b 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -296,7 +296,7 @@ async def webhook_call_service( config_entry.data[ATTR_DEVICE_NAME], ex, ) - raise HTTPBadRequest() from ex + raise HTTPBadRequest from ex return empty_okay_response() diff --git a/homeassistant/components/mystrom/__init__.py b/homeassistant/components/mystrom/__init__.py index 3c6a5805ddb..09cd7b42da0 100644 --- a/homeassistant/components/mystrom/__init__.py +++ b/homeassistant/components/mystrom/__init__.py @@ -30,7 +30,7 @@ async def _async_get_device_state( await device.get_state() except MyStromConnectionError as err: _LOGGER.error("No route to myStrom plug: %s", ip_address) - raise ConfigEntryNotReady() from err + raise ConfigEntryNotReady from err def _get_mystrom_bulb(host: str, mac: str) -> MyStromBulb: @@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: info = await pymystrom.get_device_info(host) except MyStromConnectionError as err: _LOGGER.error("No route to myStrom plug: %s", host) - raise ConfigEntryNotReady() from err + raise ConfigEntryNotReady from err info.setdefault("type", 101) diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index d99e4c62646..55727289181 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -64,7 +64,7 @@ def setup_platform( requests.exceptions.HTTPError, ) as error: _LOGGER.error("Could not connect to the internet: %s", error) - raise PlatformNotReady() from error + raise PlatformNotReady from error except RequestParametersError as error: _LOGGER.error("Could not fetch stations, please check configuration: %s", error) return diff --git a/homeassistant/components/notify/legacy.py b/homeassistant/components/notify/legacy.py index 664e3a6d10d..6b29df504d9 100644 --- a/homeassistant/components/notify/legacy.py +++ b/homeassistant/components/notify/legacy.py @@ -232,7 +232,7 @@ class BaseNotificationService: kwargs can contain ATTR_TITLE to specify a title. """ - raise NotImplementedError() + raise NotImplementedError async def async_send_message(self, message: str, **kwargs: Any) -> None: """Send a message. diff --git a/homeassistant/components/number/__init__.py b/homeassistant/components/number/__init__.py index 5d21109f3fe..8c55bbc2cba 100644 --- a/homeassistant/components/number/__init__.py +++ b/homeassistant/components/number/__init__.py @@ -394,7 +394,7 @@ class NumberEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_native_value(self, value: float) -> None: """Set new value.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_native_value(self, value: float) -> None: """Set new value.""" @@ -403,7 +403,7 @@ class NumberEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): @final def set_value(self, value: float) -> None: """Set new value.""" - raise NotImplementedError() + raise NotImplementedError @final async def async_set_value(self, value: float) -> None: diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index bb4d84d1b50..1ecfc10d974 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -84,7 +84,7 @@ class InstallationTypeOnboardingView(HomeAssistantView): async def get(self, request: web.Request) -> web.Response: """Return the onboarding status.""" if self._data["done"]: - raise HTTPUnauthorized() + raise HTTPUnauthorized hass = request.app[KEY_HASS] info = await async_get_system_info(hass) diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index 95082c9d9e2..72119915246 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -26,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: CannotConnect, # Failed to connect to the server protocol.OwnetError, # Connected to server, but failed to list the devices ) as exc: - raise ConfigEntryNotReady() from exc + raise ConfigEntryNotReady from exc hass.data[DOMAIN][entry.entry_id] = onewire_hub diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 5c97abee77a..02e7e28ea18 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -87,7 +87,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady(f"Setup was unexpectedly canceled: {err}") from err if not device.available: - raise ConfigEntryNotReady() + raise ConfigEntryNotReady hass.data[DOMAIN][entry.unique_id] = device diff --git a/homeassistant/components/osoenergy/__init__.py b/homeassistant/components/osoenergy/__init__.py index 269fd6614b7..48ea01e8bb8 100644 --- a/homeassistant/components/osoenergy/__init__.py +++ b/homeassistant/components/osoenergy/__init__.py @@ -45,7 +45,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: devices: Any = await osoenergy.session.start_session(osoenergy_config) except HTTPException as error: - raise ConfigEntryNotReady() from error + raise ConfigEntryNotReady from error except OSOEnergyReauthRequired as err: raise ConfigEntryAuthFailed from err diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index c50dc819f9a..3a93c7a2d36 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -78,16 +78,16 @@ async def async_setup_platform( def _precheck_image(image, opts): """Perform some pre-checks on the given image.""" if not opts: - raise ValueError() + raise ValueError try: img = Image.open(io.BytesIO(image)) except OSError as err: _LOGGER.warning("Failed to open image") - raise ValueError() from err + raise ValueError from err imgfmt = str(img.format) if imgfmt not in ("PNG", "JPEG"): _LOGGER.warning("Image is of unsupported type: %s", imgfmt) - raise ValueError() + raise ValueError if img.mode != "RGB": img = img.convert("RGB") return img @@ -292,7 +292,7 @@ class ProxyCamera(Camera): if not image: return None except HomeAssistantError as err: - raise asyncio.CancelledError() from err + raise asyncio.CancelledError from err if self._mode == MODE_RESIZE: job = _resize_image diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index fffbc913fe8..2b88c51e936 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -235,7 +235,7 @@ class RemoteEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_) def send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send commands to a device.""" - raise NotImplementedError() + raise NotImplementedError async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send commands to a device.""" @@ -245,7 +245,7 @@ class RemoteEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_) def learn_command(self, **kwargs: Any) -> None: """Learn a command from a device.""" - raise NotImplementedError() + raise NotImplementedError async def async_learn_command(self, **kwargs: Any) -> None: """Learn a command from a device.""" @@ -253,7 +253,7 @@ class RemoteEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_) def delete_command(self, **kwargs: Any) -> None: """Delete commands from the database.""" - raise NotImplementedError() + raise NotImplementedError async def async_delete_command(self, **kwargs: Any) -> None: """Delete commands from the database.""" diff --git a/homeassistant/components/renault/__init__.py b/homeassistant/components/renault/__init__.py index f2a02be7b49..62425d9c20e 100644 --- a/homeassistant/components/renault/__init__.py +++ b/homeassistant/components/renault/__init__.py @@ -21,16 +21,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b config_entry.data[CONF_USERNAME], config_entry.data[CONF_PASSWORD] ) except (aiohttp.ClientConnectionError, GigyaException) as exc: - raise ConfigEntryNotReady() from exc + raise ConfigEntryNotReady from exc if not login_success: - raise ConfigEntryAuthFailed() + raise ConfigEntryAuthFailed hass.data.setdefault(DOMAIN, {}) try: await renault_hub.async_initialise(config_entry) except aiohttp.ClientError as exc: - raise ConfigEntryNotReady() from exc + raise ConfigEntryNotReady from exc hass.data[DOMAIN][config_entry.entry_id] = renault_hub diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index ee2e1cb9fff..74b7d4aa4c0 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -377,7 +377,7 @@ class RflinkDevice(Entity): def _handle_event(self, event): """Platform specific event handler.""" - raise NotImplementedError() + raise NotImplementedError @property def name(self): diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index 02e51bcc4ea..309cad7610e 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -89,7 +89,7 @@ async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> b try: await risco.connect() except CannotConnectError as error: - raise ConfigEntryNotReady() from error + raise ConfigEntryNotReady from error except UnauthorizedError: _LOGGER.exception("Failed to login to Risco cloud") return False diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index ee1937b65c9..1baf25735fa 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -125,7 +125,7 @@ class Scene(RestoreEntity): def activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" - raise NotImplementedError() + raise NotImplementedError async def async_activate(self, **kwargs: Any) -> None: """Activate scene. Try to get entities into requested state.""" diff --git a/homeassistant/components/screenlogic/entity.py b/homeassistant/components/screenlogic/entity.py index df7fe6fc265..0f7530b7289 100644 --- a/homeassistant/components/screenlogic/entity.py +++ b/homeassistant/components/screenlogic/entity.py @@ -159,7 +159,7 @@ class ScreenLogicSwitchingEntity(ScreenLogicEntity): await self._async_set_state(ON_OFF.OFF) async def _async_set_state(self, state: ON_OFF) -> None: - raise NotImplementedError() + raise NotImplementedError class ScreenLogicCircuitEntity(ScreenLogicSwitchingEntity, ScreenLogicPushEntity): diff --git a/homeassistant/components/screenlogic/number.py b/homeassistant/components/screenlogic/number.py index 71d4e045da6..76640339040 100644 --- a/homeassistant/components/screenlogic/number.py +++ b/homeassistant/components/screenlogic/number.py @@ -112,7 +112,7 @@ class ScreenLogicNumber(ScreenLogicEntity, NumberEntity): async def async_set_native_value(self, value: float) -> None: """Update the current value.""" - raise NotImplementedError() + raise NotImplementedError class ScreenLogicSCGNumber(ScreenLogicNumber): diff --git a/homeassistant/components/select/__init__.py b/homeassistant/components/select/__init__.py index 00963f4d069..1594b9e2195 100644 --- a/homeassistant/components/select/__init__.py +++ b/homeassistant/components/select/__init__.py @@ -164,7 +164,7 @@ class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): and self.entity_description.options is not None ): return self.entity_description.options - raise AttributeError() + raise AttributeError @cached_property def current_option(self) -> str | None: @@ -197,7 +197,7 @@ class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def select_option(self, option: str) -> None: """Change the selected option.""" - raise NotImplementedError() + raise NotImplementedError async def async_select_option(self, option: str) -> None: """Change the selected option.""" diff --git a/homeassistant/components/sfr_box/__init__.py b/homeassistant/components/sfr_box/__init__.py index 7e8ea102576..dade1af0e52 100644 --- a/homeassistant/components/sfr_box/__init__.py +++ b/homeassistant/components/sfr_box/__init__.py @@ -29,9 +29,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: try: await box.authenticate(username=username, password=password) except SFRBoxAuthenticationError as err: - raise ConfigEntryAuthFailed() from err + raise ConfigEntryAuthFailed from err except SFRBoxError as err: - raise ConfigEntryNotReady() from err + raise ConfigEntryNotReady from err platforms = PLATFORMS_WITH_AUTH data = DomainData( diff --git a/homeassistant/components/sfr_box/coordinator.py b/homeassistant/components/sfr_box/coordinator.py index 29ab1d7a2c6..08698edd74a 100644 --- a/homeassistant/components/sfr_box/coordinator.py +++ b/homeassistant/components/sfr_box/coordinator.py @@ -37,4 +37,4 @@ class SFRDataUpdateCoordinator(DataUpdateCoordinator[_T]): try: return await self._method(self.box) except SFRBoxError as err: - raise UpdateFailed() from err + raise UpdateFailed from err diff --git a/homeassistant/components/sharkiq/vacuum.py b/homeassistant/components/sharkiq/vacuum.py index f24a21b7f8e..658d446b9cb 100644 --- a/homeassistant/components/sharkiq/vacuum.py +++ b/homeassistant/components/sharkiq/vacuum.py @@ -99,7 +99,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum def clean_spot(self, **kwargs: Any) -> None: """Clean a spot. Not yet implemented.""" - raise NotImplementedError() + raise NotImplementedError def send_command( self, @@ -108,7 +108,7 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum **kwargs: Any, ) -> None: """Send a command to the vacuum. Not yet implemented.""" - raise NotImplementedError() + raise NotImplementedError @property def is_online(self) -> bool: diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py index aad18461b6e..eff0fb378a3 100644 --- a/homeassistant/components/sisyphus/light.py +++ b/homeassistant/components/sisyphus/light.py @@ -33,7 +33,7 @@ async def async_setup_platform( table_holder = hass.data[DATA_SISYPHUS][host] table = await table_holder.get_table() except aiohttp.ClientError as err: - raise PlatformNotReady() from err + raise PlatformNotReady from err add_entities([SisyphusLight(table_holder.name, table)], update_before_add=True) diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index 8fb1aa50223..3884a83928a 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -35,7 +35,7 @@ async def async_setup_platform( table_holder = hass.data[DATA_SISYPHUS][host] table = await table_holder.get_table() except aiohttp.ClientError as err: - raise PlatformNotReady() from err + raise PlatformNotReady from err add_entities([SisyphusPlayer(table_holder.name, host, table)], True) diff --git a/homeassistant/components/sonos/household_coordinator.py b/homeassistant/components/sonos/household_coordinator.py index 9fc2703e48d..8fcecdf4d5e 100644 --- a/homeassistant/components/sonos/household_coordinator.py +++ b/homeassistant/components/sonos/household_coordinator.py @@ -75,8 +75,8 @@ class SonosHouseholdCoordinator: self, soco: SoCo, update_id: int | None = None ) -> None: """Update the cache and update entities.""" - raise NotImplementedError() + raise NotImplementedError def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool: """Update the cache of the household-level feature and return if cache has changed.""" - raise NotImplementedError() + raise NotImplementedError diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 4ff5f79ebd8..68c08a4f072 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -389,7 +389,7 @@ class StreamView(HomeAssistantView): ) if not stream: - raise web.HTTPNotFound() + raise web.HTTPNotFound # Start worker if not already started await stream.start() @@ -400,7 +400,7 @@ class StreamView(HomeAssistantView): self, request: web.Request, stream: Stream, sequence: str, part_num: str ) -> web.StreamResponse: """Handle the stream request.""" - raise NotImplementedError() + raise NotImplementedError TRANSFORM_IMAGE_FUNCTION = ( diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index 811913e1494..c3d39d4337e 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -257,7 +257,7 @@ class SpeechToTextView(HomeAssistantView): not (provider_entity := async_get_speech_to_text_entity(hass, provider)) and provider not in self.providers ): - raise HTTPNotFound() + raise HTTPNotFound # Get metadata try: @@ -270,7 +270,7 @@ class SpeechToTextView(HomeAssistantView): # Check format if not stt_provider.check_metadata(metadata): - raise HTTPUnsupportedMediaType() + raise HTTPUnsupportedMediaType # Process audio stream result = await stt_provider.async_process_audio_stream( @@ -279,7 +279,7 @@ class SpeechToTextView(HomeAssistantView): else: # Check format if not provider_entity.check_metadata(metadata): - raise HTTPUnsupportedMediaType() + raise HTTPUnsupportedMediaType # Process audio stream result = await provider_entity.internal_async_process_audio_stream( @@ -296,7 +296,7 @@ class SpeechToTextView(HomeAssistantView): not (provider_entity := async_get_speech_to_text_entity(hass, provider)) and provider not in self.providers ): - raise HTTPNotFound() + raise HTTPNotFound if not provider_entity: stt_provider = self._get_provider(hass, provider) diff --git a/homeassistant/components/synology_dsm/media_source.py b/homeassistant/components/synology_dsm/media_source.py index 6f53116a39c..9a393813c3e 100644 --- a/homeassistant/components/synology_dsm/media_source.py +++ b/homeassistant/components/synology_dsm/media_source.py @@ -212,18 +212,18 @@ class SynologyDsmMediaView(http.HomeAssistantView): ) -> web.Response: """Start a GET request.""" if not self.hass.data.get(DOMAIN): - raise web.HTTPNotFound() + raise web.HTTPNotFound # location: {cache_key}/{filename} cache_key, file_name = location.split("/") image_id = cache_key.split("_")[0] mime_type, _ = mimetypes.guess_type(file_name) if not isinstance(mime_type, str): - raise web.HTTPNotFound() + raise web.HTTPNotFound diskstation: SynologyDSMData = self.hass.data[DOMAIN][source_dir_id] item = SynoPhotosItem(image_id, "", "", "", cache_key, "") try: image = await diskstation.api.photos.download_item(item) except SynologyDSMException as exc: - raise web.HTTPNotFound() from exc + raise web.HTTPNotFound from exc return web.Response(body=image, content_type=mime_type) diff --git a/homeassistant/components/text/__init__.py b/homeassistant/components/text/__init__.py index c69cf8fcbdd..cf29910cc34 100644 --- a/homeassistant/components/text/__init__.py +++ b/homeassistant/components/text/__init__.py @@ -237,7 +237,7 @@ class TextEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_value(self, value: str) -> None: """Change the value.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_value(self, value: str) -> None: """Change the value.""" diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index a57c5d3e8dd..da2fd881a54 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -286,10 +286,10 @@ async def async_setup_entry( await home.update_info() except TimeoutError as err: _LOGGER.error("Timeout connecting to Tibber home: %s ", err) - raise PlatformNotReady() from err + raise PlatformNotReady from err except aiohttp.ClientError as err: _LOGGER.error("Error connecting to Tibber home: %s ", err) - raise PlatformNotReady() from err + raise PlatformNotReady from err if home.has_active_subscription: entities.append(TibberSensorElPrice(home)) diff --git a/homeassistant/components/time/__init__.py b/homeassistant/components/time/__init__.py index 867aada2e8f..2e87aaac28d 100644 --- a/homeassistant/components/time/__init__.py +++ b/homeassistant/components/time/__init__.py @@ -110,7 +110,7 @@ class TimeEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_value(self, value: time) -> None: """Change the time.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_value(self, value: time) -> None: """Change the time.""" diff --git a/homeassistant/components/todo/__init__.py b/homeassistant/components/todo/__init__.py index afcb8e28f74..0b8dd756289 100644 --- a/homeassistant/components/todo/__init__.py +++ b/homeassistant/components/todo/__init__.py @@ -261,15 +261,15 @@ class TodoListEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): async def async_create_todo_item(self, item: TodoItem) -> None: """Add an item to the To-do list.""" - raise NotImplementedError() + raise NotImplementedError async def async_update_todo_item(self, item: TodoItem) -> None: """Update an item in the To-do list.""" - raise NotImplementedError() + raise NotImplementedError async def async_delete_todo_items(self, uids: list[str]) -> None: """Delete an item in the To-do list.""" - raise NotImplementedError() + raise NotImplementedError async def async_move_todo_item( self, uid: str, previous_uid: str | None = None @@ -280,7 +280,7 @@ class TodoListEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): in the list after the specified by `previous_uid` or `None` for the first position in the To-do list. """ - raise NotImplementedError() + raise NotImplementedError @final @callback diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index ca1ed0d6027..f445309fffe 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -457,7 +457,7 @@ class TextToSpeechEntity(RestoreEntity): self, message: str, language: str, options: dict[str, Any] ) -> TtsAudioType: """Load tts audio file from the engine.""" - raise NotImplementedError() + raise NotImplementedError async def async_get_tts_audio( self, message: str, language: str, options: dict[str, Any] diff --git a/homeassistant/components/tts/legacy.py b/homeassistant/components/tts/legacy.py index 9d70139ff74..0214dcd13e9 100644 --- a/homeassistant/components/tts/legacy.py +++ b/homeassistant/components/tts/legacy.py @@ -231,7 +231,7 @@ class Provider: self, message: str, language: str, options: dict[str, Any] ) -> TtsAudioType: """Load tts audio file from provider.""" - raise NotImplementedError() + raise NotImplementedError async def async_get_tts_audio( self, message: str, language: str, options: dict[str, Any] diff --git a/homeassistant/components/unifi/entity.py b/homeassistant/components/unifi/entity.py index 649c9fc0c13..e162b32ba42 100644 --- a/homeassistant/components/unifi/entity.py +++ b/homeassistant/components/unifi/entity.py @@ -270,4 +270,4 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]): Perform additional action updating platform entity child class state. """ - raise NotImplementedError() + raise NotImplementedError diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 54b5d337605..90036e5d47c 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -658,7 +658,7 @@ class UniversalMediaPlayer(MediaPlayerEntity): component: EntityComponent[MediaPlayerEntity] = self.hass.data[DOMAIN] if entity_id and (entity := component.get_entity(entity_id)): return await entity.async_browse_media(media_content_type, media_content_id) - raise NotImplementedError() + raise NotImplementedError async def async_update(self) -> None: """Update state in HA.""" diff --git a/homeassistant/components/update/__init__.py b/homeassistant/components/update/__init__.py index d52216974aa..f274da6f412 100644 --- a/homeassistant/components/update/__init__.py +++ b/homeassistant/components/update/__init__.py @@ -374,7 +374,7 @@ class UpdateEntity( The backup parameter indicates a backup should be taken before installing the update. """ - raise NotImplementedError() + raise NotImplementedError async def async_release_notes(self) -> str | None: """Return full release notes. @@ -390,7 +390,7 @@ class UpdateEntity( This is suitable for a long changelog that does not fit in the release_summary property. The returned string can contain markdown. """ - raise NotImplementedError() + raise NotImplementedError @property @final diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 61a31add655..bdf690ed63f 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -293,7 +293,7 @@ class StateVacuumEntity( def stop(self, **kwargs: Any) -> None: """Stop the vacuum cleaner.""" - raise NotImplementedError() + raise NotImplementedError async def async_stop(self, **kwargs: Any) -> None: """Stop the vacuum cleaner. @@ -304,7 +304,7 @@ class StateVacuumEntity( def return_to_base(self, **kwargs: Any) -> None: """Set the vacuum cleaner to return to the dock.""" - raise NotImplementedError() + raise NotImplementedError async def async_return_to_base(self, **kwargs: Any) -> None: """Set the vacuum cleaner to return to the dock. @@ -315,7 +315,7 @@ class StateVacuumEntity( def clean_spot(self, **kwargs: Any) -> None: """Perform a spot clean-up.""" - raise NotImplementedError() + raise NotImplementedError async def async_clean_spot(self, **kwargs: Any) -> None: """Perform a spot clean-up. @@ -326,7 +326,7 @@ class StateVacuumEntity( def locate(self, **kwargs: Any) -> None: """Locate the vacuum cleaner.""" - raise NotImplementedError() + raise NotImplementedError async def async_locate(self, **kwargs: Any) -> None: """Locate the vacuum cleaner. @@ -337,7 +337,7 @@ class StateVacuumEntity( def set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set fan speed.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set fan speed. @@ -355,7 +355,7 @@ class StateVacuumEntity( **kwargs: Any, ) -> None: """Send a command to a vacuum cleaner.""" - raise NotImplementedError() + raise NotImplementedError async def async_send_command( self, @@ -373,7 +373,7 @@ class StateVacuumEntity( def start(self) -> None: """Start or resume the cleaning task.""" - raise NotImplementedError() + raise NotImplementedError async def async_start(self) -> None: """Start or resume the cleaning task. @@ -384,7 +384,7 @@ class StateVacuumEntity( def pause(self) -> None: """Pause the cleaning task.""" - raise NotImplementedError() + raise NotImplementedError async def async_pause(self) -> None: """Pause the cleaning task. diff --git a/homeassistant/components/valve/__init__.py b/homeassistant/components/valve/__init__.py index f2d2fcf4760..0363ef55832 100644 --- a/homeassistant/components/valve/__init__.py +++ b/homeassistant/components/valve/__init__.py @@ -215,7 +215,7 @@ class ValveEntity(Entity): def open_valve(self) -> None: """Open the valve.""" - raise NotImplementedError() + raise NotImplementedError async def async_open_valve(self) -> None: """Open the valve.""" @@ -230,7 +230,7 @@ class ValveEntity(Entity): def close_valve(self) -> None: """Close valve.""" - raise NotImplementedError() + raise NotImplementedError async def async_close_valve(self) -> None: """Close valve.""" @@ -257,7 +257,7 @@ class ValveEntity(Entity): def set_valve_position(self, position: int) -> None: """Move the valve to a specific position.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_valve_position(self, position: int) -> None: """Move the valve to a specific position.""" @@ -265,7 +265,7 @@ class ValveEntity(Entity): def stop_valve(self) -> None: """Stop the valve.""" - raise NotImplementedError() + raise NotImplementedError async def async_stop_valve(self) -> None: """Stop the valve.""" diff --git a/homeassistant/components/vlc_telnet/__init__.py b/homeassistant/components/vlc_telnet/__init__.py index 659a5d29d1f..67c45c5dbdf 100644 --- a/homeassistant/components/vlc_telnet/__init__.py +++ b/homeassistant/components/vlc_telnet/__init__.py @@ -36,7 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await vlc.login() except AuthError as err: await disconnect_vlc(vlc) - raise ConfigEntryAuthFailed() from err + raise ConfigEntryAuthFailed from err domain_data = hass.data.setdefault(DOMAIN, {}) domain_data[entry.entry_id] = {DATA_VLC: vlc, DATA_AVAILABLE: available} diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 5bd414ee53e..370bb5c293c 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -331,7 +331,7 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -341,7 +341,7 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_on(self, **kwargs: Any) -> None: """Turn the water heater on.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_on(self, **kwargs: Any) -> None: """Turn the water heater on.""" @@ -349,7 +349,7 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_off(self, **kwargs: Any) -> None: """Turn the water heater off.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_off(self, **kwargs: Any) -> None: """Turn the water heater off.""" @@ -357,7 +357,7 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def set_operation_mode(self, operation_mode: str) -> None: """Set new target operation mode.""" - raise NotImplementedError() + raise NotImplementedError async def async_set_operation_mode(self, operation_mode: str) -> None: """Set new target operation mode.""" @@ -395,7 +395,7 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_away_mode_on(self) -> None: """Turn away mode on.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_away_mode_on(self) -> None: """Turn away mode on.""" @@ -403,7 +403,7 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def turn_away_mode_off(self) -> None: """Turn away mode off.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_away_mode_off(self) -> None: """Turn away mode off.""" diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index 4de83530642..51643752a0f 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -63,7 +63,7 @@ def require_admin(func: const.WebSocketCommandHandler) -> const.WebSocketCommand user = connection.user if user is None or not user.is_admin: - raise Unauthorized() + raise Unauthorized func(hass, connection, msg) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index f39daa9b594..ee7948a237e 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -388,7 +388,7 @@ class XiaomiDevice(Entity): def parse_data(self, data, raw_data): """Parse data sent by gateway.""" - raise NotImplementedError() + raise NotImplementedError def _add_gateway_to_schema(hass, schema): diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index f21698f33c3..5384bd93a7e 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -410,9 +410,9 @@ async def async_setup_gateway_entry(hass: HomeAssistant, entry: ConfigEntry) -> try: await gateway.async_connect_gateway(host, token) except AuthException as error: - raise ConfigEntryAuthFailed() from error + raise ConfigEntryAuthFailed from error except SetupException as error: - raise ConfigEntryNotReady() from error + raise ConfigEntryNotReady from error gateway_info = gateway.gateway_info device_registry = dr.async_get(hass) diff --git a/homeassistant/components/yamaha_musiccast/__init__.py b/homeassistant/components/yamaha_musiccast/__init__.py index 9c7e6d07b82..667b411e6c4 100644 --- a/homeassistant/components/yamaha_musiccast/__init__.py +++ b/homeassistant/components/yamaha_musiccast/__init__.py @@ -120,7 +120,7 @@ class MusicCastDataUpdateCoordinator(DataUpdateCoordinator[MusicCastData]): # p try: await self.musiccast.fetch() except MusicCastConnectionException as exception: - raise UpdateFailed() from exception + raise UpdateFailed from exception return self.musiccast.data diff --git a/homeassistant/components/zha/repairs/wrong_silabs_firmware.py b/homeassistant/components/zha/repairs/wrong_silabs_firmware.py index 3326f5685df..5b1f85e1a29 100644 --- a/homeassistant/components/zha/repairs/wrong_silabs_firmware.py +++ b/homeassistant/components/zha/repairs/wrong_silabs_firmware.py @@ -101,7 +101,7 @@ async def warn_on_wrong_silabs_firmware(hass: HomeAssistant, device: str) -> boo if app_type == ApplicationType.EZSP: # If connecting fails but we somehow probe EZSP (e.g. stuck in bootloader), # reconnect, it should work - raise AlreadyRunningEZSP() + raise AlreadyRunningEZSP hardware_type = _detect_radio_hardware(hass, device) ir.async_create_issue( diff --git a/homeassistant/components/zwave_me/__init__.py b/homeassistant/components/zwave_me/__init__.py index dae065b5829..66f02246792 100644 --- a/homeassistant/components/zwave_me/__init__.py +++ b/homeassistant/components/zwave_me/__init__.py @@ -28,7 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: registry = dr.async_get(hass) controller.remove_stale_devices(registry) return True - raise ConfigEntryNotReady() + raise ConfigEntryNotReady async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index b9e1ca2e2bd..63b95b570e7 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -139,7 +139,7 @@ class ConditionError(HomeAssistantError): def output(self, indent: int) -> Generator[str, None, None]: """Yield an indented representation.""" - raise NotImplementedError() + raise NotImplementedError def __str__(self) -> str: """Return string representation.""" diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 17784d12aa2..15437b00183 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -191,11 +191,11 @@ async def async_aiohttp_proxy_web( except TimeoutError as err: # Timeout trying to start the web request - raise HTTPGatewayTimeout() from err + raise HTTPGatewayTimeout from err except aiohttp.ClientError as err: # Something went wrong with the connection - raise HTTPBadGateway() from err + raise HTTPBadGateway from err try: return await async_aiohttp_proxy_stream( diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 13aeab9ebf2..ba4c393a2d9 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -755,7 +755,7 @@ class DeviceRegistry(BaseRegistry): config_entries = old.config_entries if merge_identifiers is not UNDEFINED and new_identifiers is not UNDEFINED: - raise HomeAssistantError() + raise HomeAssistantError if isinstance(disabled_by, str) and not isinstance( disabled_by, DeviceEntryDisabler diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 5f31c049d13..a2adee96500 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -1648,7 +1648,7 @@ class ToggleEntity( def turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" @@ -1656,7 +1656,7 @@ class ToggleEntity( def turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - raise NotImplementedError() + raise NotImplementedError async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" diff --git a/homeassistant/helpers/http.py b/homeassistant/helpers/http.py index 018558a8761..a464056fc07 100644 --- a/homeassistant/helpers/http.py +++ b/homeassistant/helpers/http.py @@ -58,7 +58,7 @@ def request_handler_factory( authenticated = request.get(KEY_AUTHENTICATED, False) if view.requires_auth and not authenticated: - raise HTTPUnauthorized() + raise HTTPUnauthorized if _LOGGER.isEnabledFor(logging.DEBUG): _LOGGER.debug( @@ -74,11 +74,11 @@ def request_handler_factory( else: result = handler(request, **request.match_info) except vol.Invalid as err: - raise HTTPBadRequest() from err + raise HTTPBadRequest from err except exceptions.ServiceNotFound as err: - raise HTTPInternalServerError() from err + raise HTTPInternalServerError from err except exceptions.Unauthorized as err: - raise HTTPUnauthorized() from err + raise HTTPUnauthorized from err if isinstance(result, web.StreamResponse): # The method handler returned a ready-made Response, how nice of it diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 71f9fd90b92..63214cb135b 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -375,7 +375,7 @@ class IntentHandler: async def async_handle(self, intent_obj: Intent) -> IntentResponse: """Handle the intent.""" - raise NotImplementedError() + raise NotImplementedError def __repr__(self) -> str: """Represent a string of an intent handler.""" @@ -439,7 +439,7 @@ class DynamicServiceIntentHandler(IntentHandler): self, intent_obj: Intent, state: State ) -> tuple[str, str]: """Get the domain and service name to call.""" - raise NotImplementedError() + raise NotImplementedError async def async_handle(self, intent_obj: Intent) -> IntentResponse: """Handle the hass intent.""" diff --git a/homeassistant/util/language.py b/homeassistant/util/language.py index 38fa22730a6..8644f8014b6 100644 --- a/homeassistant/util/language.py +++ b/homeassistant/util/language.py @@ -139,7 +139,7 @@ class Dialect: region_idx = pref_regions.index(dialect.region) else: # Can't happen, but mypy is not smart enough - raise ValueError() + raise ValueError # More preferred regions are at the front. # Add 1 to boost above a weak match where no regions are set. diff --git a/pyproject.toml b/pyproject.toml index 4703c6178e4..218127ccff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -609,6 +609,7 @@ select = [ "PGH004", # Use specific rule codes when using noqa "PIE", # flake8-pie "PL", # pylint + "RSE", # flake8-raise "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task # "RUF100", # Unused `noqa` directive; temporarily every now and then to clean them up diff --git a/tests/components/anova/conftest.py b/tests/components/anova/conftest.py index 88b5ae1fd41..3e904bb1415 100644 --- a/tests/components/anova/conftest.py +++ b/tests/components/anova/conftest.py @@ -51,7 +51,7 @@ async def anova_api_no_devices( api_mock.jwt = "my_test_jwt" async def get_devices_side_effect(): - raise NoDevicesFound() + raise NoDevicesFound api_mock.authenticate.side_effect = authenticate_side_effect api_mock.get_devices.side_effect = get_devices_side_effect @@ -73,7 +73,7 @@ async def anova_api_wrong_login( api_mock = AsyncMock() async def authenticate_side_effect(): - raise InvalidLogin() + raise InvalidLogin api_mock.authenticate.side_effect = authenticate_side_effect diff --git a/tests/components/assist_pipeline/test_websocket.py b/tests/components/assist_pipeline/test_websocket.py index 91f07fc08c0..612bf760bd0 100644 --- a/tests/components/assist_pipeline/test_websocket.py +++ b/tests/components/assist_pipeline/test_websocket.py @@ -2392,7 +2392,7 @@ async def test_device_capture_queue_full( def put_nowait(self, item): if item is not None: - raise asyncio.QueueFull() + raise asyncio.QueueFull super().put_nowait(item) diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 3b9e8e39a82..1e445431c24 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -601,7 +601,7 @@ async def test_http_api_handle_failure( # Raise an error during intent handling def async_handle_error(*args, **kwargs): - raise intent.IntentHandleError() + raise intent.IntentHandleError with patch("homeassistant.helpers.intent.async_handle", new=async_handle_error): resp = await client.post( @@ -629,7 +629,7 @@ async def test_http_api_unexpected_failure( # Raise an "unexpected" error during intent handling def async_handle_error(*args, **kwargs): - raise intent.IntentUnexpectedError() + raise intent.IntentUnexpectedError with patch("homeassistant.helpers.intent.async_handle", new=async_handle_error): resp = await client.post( diff --git a/tests/components/coolmaster/conftest.py b/tests/components/coolmaster/conftest.py index e2c4b050514..7ddf1fd5942 100644 --- a/tests/components/coolmaster/conftest.py +++ b/tests/components/coolmaster/conftest.py @@ -72,7 +72,7 @@ class CoolMasterNetUnitMock: async def set_swing(self, value: str | None) -> CoolMasterNetUnitMock: """Set the swing mode.""" if value == "": - raise ValueError() + raise ValueError self._attributes["swing"] = value return CoolMasterNetUnitMock(self.unit_id, self._attributes) diff --git a/tests/components/lifx/test_config_flow.py b/tests/components/lifx/test_config_flow.py index 8381a07cb7f..78e852e2cab 100644 --- a/tests/components/lifx/test_config_flow.py +++ b/tests/components/lifx/test_config_flow.py @@ -285,7 +285,7 @@ async def test_manual_dns_error(hass: HomeAssistant) -> None: async def async_setup(self): """Mock setup.""" - raise socket.gaierror() + raise socket.gaierror def async_stop(self): """Mock teardown.""" diff --git a/tests/components/lifx/test_init.py b/tests/components/lifx/test_init.py index c79e4bb3856..01003e6e7f1 100644 --- a/tests/components/lifx/test_init.py +++ b/tests/components/lifx/test_init.py @@ -139,7 +139,7 @@ async def test_dns_error_at_startup(hass: HomeAssistant) -> None: async def async_setup(self): """Mock setup.""" - raise socket.gaierror() + raise socket.gaierror def async_stop(self): """Mock teardown.""" diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index 12641438a3a..771bd62de1c 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -541,7 +541,7 @@ async def test_camera_image_raises_exception( endpoint = kwargs["endpoint"].split("/")[-1] if "snapshot_720.jpg" in endpoint: - raise pyatmo.ApiError() + raise pyatmo.ApiError return await fake_post_request(*args, **kwargs) diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index 2e0d2eb585b..7aea83a84e2 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -40,7 +40,7 @@ def _mock_cond_socket(sockname): """Return addr if it matches the mock sockname.""" if self._addr == sockname: return [sockname] - raise AttributeError() + raise AttributeError return CondMockSock() diff --git a/tests/components/nibe_heatpump/__init__.py b/tests/components/nibe_heatpump/__init__.py index 3c3db391ba8..d7c1fa5ebad 100644 --- a/tests/components/nibe_heatpump/__init__.py +++ b/tests/components/nibe_heatpump/__init__.py @@ -41,7 +41,7 @@ class MockConnection(Connection): async def read_coil(self, coil: Coil, timeout: float = 0) -> CoilData: """Read of coils.""" if (data := self.coils.get(coil.address, None)) is None: - raise ReadException() + raise ReadException return CoilData(coil, data) async def write_coil(self, coil_data: CoilData, timeout: float = 10.0) -> None: diff --git a/tests/components/nibe_heatpump/conftest.py b/tests/components/nibe_heatpump/conftest.py index 2d88e1b0fd2..010a79980c4 100644 --- a/tests/components/nibe_heatpump/conftest.py +++ b/tests/components/nibe_heatpump/conftest.py @@ -68,7 +68,7 @@ async def fixture_coils(mock_connection: MockConnection): def get_coil_by_address(self, address): coils_data = get_coil_by_address_original(self, address) if coils_data.address not in mock_connection.coils: - raise CoilNotFoundException() + raise CoilNotFoundException return coils_data with patch.object(HeatPump, "get_coils", new=get_coils), patch.object( diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 3e3a5aa9001..898d5757870 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -90,7 +90,7 @@ async def test_setup(get_fuel_prices, hass: HomeAssistant) -> None: def raise_fuel_check_error(): """Raise fuel check error for testing error cases.""" - raise FuelCheckError() + raise FuelCheckError @patch( diff --git a/tests/components/nx584/test_binary_sensor.py b/tests/components/nx584/test_binary_sensor.py index 5b8adada1a2..5c57feb471b 100644 --- a/tests/components/nx584/test_binary_sensor.py +++ b/tests/components/nx584/test_binary_sensor.py @@ -249,8 +249,8 @@ def test_nx584_watcher_run_retries_failures(mock_sleep) -> None: """Fake runner.""" if empty_me: empty_me.pop() - raise requests.exceptions.ConnectionError() - raise StopMe() + raise requests.exceptions.ConnectionError + raise StopMe watcher = nx584.NX584Watcher(None, {}) with mock.patch.object(watcher, "_run") as mock_inner: diff --git a/tests/components/owntracks/test_device_tracker.py b/tests/components/owntracks/test_device_tracker.py index 4462722bb53..a36d03e973c 100644 --- a/tests/components/owntracks/test_device_tracker.py +++ b/tests/components/owntracks/test_device_tracker.py @@ -1383,7 +1383,7 @@ def mock_cipher(): (mkey, plaintext) = pickle.loads(base64.b64decode(ciphertext)) if key != mkey: - raise ValueError() + raise ValueError return plaintext return len(TEST_SECRET_KEY), mock_decrypt diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 5f8225e8fe6..c199d980f69 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -60,7 +60,7 @@ def test_recorder_bad_execute(hass_recorder: Callable[..., HomeAssistant]) -> No def to_native(validate_entity_id=True): """Raise exception.""" - raise SQLAlchemyError() + raise SQLAlchemyError mck1 = MagicMock() mck1.to_native = to_native diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index 17e04e64749..964984e777a 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -158,7 +158,7 @@ async def test_async_poll_manual_hosts_warnings( class _MockSoCoOsError(MockSoCo): @property def visible_zones(self): - raise OSError() + raise OSError class _MockSoCoVisibleZones(MockSoCo): diff --git a/tests/components/stream/test_ll_hls.py b/tests/components/stream/test_ll_hls.py index 39d66f0141d..4cf3909dd0d 100644 --- a/tests/components/stream/test_ll_hls.py +++ b/tests/components/stream/test_ll_hls.py @@ -397,7 +397,7 @@ async def test_ll_hls_playlist_bad_msn_part( """Test some playlist requests with invalid _HLS_msn/_HLS_part.""" async def _handler_bad_request(request): - raise web.HTTPBadRequest() + raise web.HTTPBadRequest await async_setup_component( hass, diff --git a/tests/components/twinkly/__init__.py b/tests/components/twinkly/__init__.py index c77dd0ac963..f322004962a 100644 --- a/tests/components/twinkly/__init__.py +++ b/tests/components/twinkly/__init__.py @@ -42,38 +42,38 @@ class ClientMock: async def get_details(self): """Get the mocked device info.""" if self.is_offline: - raise ClientConnectionError() + raise ClientConnectionError return self.device_info async def is_on(self) -> bool: """Get the mocked on/off state.""" if self.is_offline: - raise ClientConnectionError() + raise ClientConnectionError return self.state async def turn_on(self) -> None: """Set the mocked on state.""" if self.is_offline: - raise ClientConnectionError() + raise ClientConnectionError self.state = True self.mode = self.default_mode async def turn_off(self) -> None: """Set the mocked off state.""" if self.is_offline: - raise ClientConnectionError() + raise ClientConnectionError self.state = False async def get_brightness(self) -> int: """Get the mocked brightness.""" if self.is_offline: - raise ClientConnectionError() + raise ClientConnectionError return self.brightness async def set_brightness(self, brightness: int) -> None: """Set the mocked brightness.""" if self.is_offline: - raise ClientConnectionError() + raise ClientConnectionError self.brightness = {"mode": "enabled", "value": brightness} def change_name(self, new_name: str) -> None: diff --git a/tests/components/twitch/__init__.py b/tests/components/twitch/__init__.py index bb4530eacf8..d37c386f0a3 100644 --- a/tests/components/twitch/__init__.py +++ b/tests/components/twitch/__init__.py @@ -198,7 +198,7 @@ class TwitchUnauthorizedMock(TwitchMock): def __await__(self): """Add async capabilities to the mock.""" - raise TwitchAuthorizationException() + raise TwitchAuthorizationException class TwitchMissingScopeMock(TwitchMock): @@ -208,7 +208,7 @@ class TwitchMissingScopeMock(TwitchMock): self, token: str, scope: list[AuthScope], validate: bool = True ) -> None: """Set user authentication.""" - raise MissingScopeException() + raise MissingScopeException class TwitchInvalidTokenMock(TwitchMock): @@ -218,7 +218,7 @@ class TwitchInvalidTokenMock(TwitchMock): self, token: str, scope: list[AuthScope], validate: bool = True ) -> None: """Set user authentication.""" - raise InvalidTokenException() + raise InvalidTokenException class TwitchInvalidUserMock(TwitchMock): @@ -243,4 +243,4 @@ class TwitchAPIExceptionMock(TwitchMock): self, broadcaster_id: str, user_id: str ) -> UserSubscriptionMock: """Check if the user is subscribed.""" - raise TwitchAPIException() + raise TwitchAPIException diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index ccc8b75021f..cbbd27b81df 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -472,7 +472,7 @@ async def test_login_tries_both_addrs_and_caches( """Mock get snapshots.""" try: snapshots.pop(0) - raise camera.CameraAuthError() + raise camera.CameraAuthError except IndexError: pass return "test_image" diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index 465c4aea749..6c2646f369e 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -86,7 +86,7 @@ async def test_cleanup_on_cancellation( @callback def _raise(): - raise ValueError() + raise ValueError connection.subscriptions[msg_id] = _raise connection.send_result(msg_id) @@ -104,7 +104,7 @@ async def test_cleanup_on_cancellation( def cancel_in_handler( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: - raise asyncio.CancelledError() + raise asyncio.CancelledError async_register_command(hass, cancel_in_handler) diff --git a/tests/components/ws66i/test_media_player.py b/tests/components/ws66i/test_media_player.py index 08ab646ce28..eec6bf191f7 100644 --- a/tests/components/ws66i/test_media_player.py +++ b/tests/components/ws66i/test_media_player.py @@ -86,7 +86,7 @@ class MockWs66i: def open(self): """Open socket. Do nothing.""" if self.fail_open is True: - raise ConnectionError() + raise ConnectionError def close(self): """Close socket. Do nothing.""" diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index f91cb3d2f35..6ad8af5d698 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -90,7 +90,7 @@ class MockOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implementa async def _async_refresh_token(self, token: dict) -> dict: """Refresh a token.""" - raise NotImplementedError() + raise NotImplementedError def test_inherit_enforces_domain_set() -> None: diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index df232e51aee..742b111143f 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -335,7 +335,7 @@ class MockLongPollSideEffect: async def __call__(self, method, url, data): """Fetch the next response from the queue or wait until the queue has items.""" if self.stopping: - raise ClientError() + raise ClientError await self.semaphore.acquire() kwargs = self.response_list.pop(0) return AiohttpClientMockResponse(method=method, url=url, **kwargs) diff --git a/tests/util/test_timeout.py b/tests/util/test_timeout.py index 99430cc0361..02a06632ff6 100644 --- a/tests/util/test_timeout.py +++ b/tests/util/test_timeout.py @@ -315,7 +315,7 @@ async def test_simple_zone_timeout_freeze_without_timeout_exeption() -> None: async with timeout.async_timeout(0.1): with suppress(RuntimeError): async with timeout.async_freeze("test"): - raise RuntimeError() + raise RuntimeError await asyncio.sleep(0.4) @@ -328,6 +328,6 @@ async def test_simple_zone_timeout_zone_with_timeout_exeption() -> None: async with timeout.async_timeout(0.1): with suppress(RuntimeError): async with timeout.async_timeout(0.3, "test"): - raise RuntimeError() + raise RuntimeError await asyncio.sleep(0.3) From 0d279ccd13fee63fc3053063f4d9e4dfab87e52a Mon Sep 17 00:00:00 2001 From: Johnny Willemsen Date: Mon, 18 Mar 2024 00:47:11 +0100 Subject: [PATCH 1221/1691] Improve scaffold test_config_flow (#113229) Update test_config_flow.py Use DOMAIN constant --- .../templates/config_flow_oauth2/tests/test_config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py index d0000b800df..6e3a2047c6e 100644 --- a/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_oauth2/tests/test_config_flow.py @@ -42,7 +42,7 @@ async def test_full_flow( ) -> None: """Check full flow.""" result = await hass.config_entries.flow.async_init( - "NEW_DOMAIN", context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": config_entries.SOURCE_USER} ) state = config_entry_oauth2_flow._encode_jwt( hass, From 9a6804b5d722979cc7f5f33a35844456330e7308 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 14:28:29 -1000 Subject: [PATCH 1222/1691] Adjust config flow pre-import comment in group (#113702) --- homeassistant/components/group/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 8758bf19312..7657201da4d 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -31,11 +31,14 @@ from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -# Ensure group config_flow is imported so it does not need the import -# executor since config_flows are preloaded when the component is loaded. -# Even though group is pre-imported above we would have still had to wait -# for the config flow to be imported when the import executor is the most -# busy. +# +# Below we ensure the config_flow is imported so it does not need the import +# executor later. +# +# Since group is pre-imported, the loader will not get a chance to pre-import +# the config flow as there is no run time import of the group component in the +# executor. +# from . import config_flow as config_flow_pre_import # noqa: F401 from .const import ( # noqa: F401 ATTR_ADD_ENTITIES, From fcdb7039f9b26ee09c43d94d5f59b0378f9e478c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 17:38:48 -1000 Subject: [PATCH 1223/1691] Migrate isy994 listeners to use run_immediately (#113661) --- homeassistant/components/isy994/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 0c238182849..db72cc45a30 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -166,7 +166,9 @@ async def async_setup_entry( entry.async_on_unload(entry.add_update_listener(_async_update_listener)) entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_auto_update) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_stop_auto_update, run_immediately=True + ) ) # Register Integration-wide Services: From e23ce42d3ac40f5f71a83ab5efc63456c35df20f Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Mon, 18 Mar 2024 06:40:03 +0100 Subject: [PATCH 1224/1691] Bump xiaomi-ble to 0.27.1 (#113686) --- homeassistant/components/xiaomi_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 8522ff89e45..84447e211bb 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.27.0"] + "requirements": ["xiaomi-ble==0.27.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 62fbfc4d4aa..730c3d2d327 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2865,7 +2865,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.27.0 +xiaomi-ble==0.27.1 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1cb95ab2b9e..694e3fc7d08 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2206,7 +2206,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.27.0 +xiaomi-ble==0.27.1 # homeassistant.components.knx xknx==2.12.2 From 20897e0a3a81270bf3ca8429b04a799a0d106f87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 08:28:10 +0100 Subject: [PATCH 1225/1691] Bump dawidd6/action-download-artifact from 3.1.2 to 3.1.3 (#113720) --- .github/workflows/builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 770abf025d2..c4117611676 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -103,7 +103,7 @@ jobs: - name: Download nightly wheels of frontend if: needs.init.outputs.channel == 'dev' - uses: dawidd6/action-download-artifact@v3.1.2 + uses: dawidd6/action-download-artifact@v3.1.3 with: github_token: ${{secrets.GITHUB_TOKEN}} repo: home-assistant/frontend @@ -114,7 +114,7 @@ jobs: - name: Download nightly wheels of intents if: needs.init.outputs.channel == 'dev' - uses: dawidd6/action-download-artifact@v3.1.2 + uses: dawidd6/action-download-artifact@v3.1.3 with: github_token: ${{secrets.GITHUB_TOKEN}} repo: home-assistant/intents-package From 3844ade572410fedaaa54fe127f5286ad5da39cc Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:12:46 +0100 Subject: [PATCH 1226/1691] Fix unknown values in onewire (#113731) * Fix unknown values in onewire * Update tests --- homeassistant/components/onewire/binary_sensor.py | 4 +++- homeassistant/components/onewire/switch.py | 6 ++++-- tests/components/onewire/const.py | 8 ++++++-- .../components/onewire/snapshots/test_binary_sensor.ambr | 4 ++-- tests/components/onewire/snapshots/test_switch.ambr | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/onewire/binary_sensor.py b/homeassistant/components/onewire/binary_sensor.py index 0f98be0d892..fea78fd3760 100644 --- a/homeassistant/components/onewire/binary_sensor.py +++ b/homeassistant/components/onewire/binary_sensor.py @@ -144,6 +144,8 @@ class OneWireBinarySensor(OneWireEntity, BinarySensorEntity): entity_description: OneWireBinarySensorEntityDescription @property - def is_on(self) -> bool: + def is_on(self) -> bool | None: """Return true if sensor is on.""" + if self._state is None: + return None return bool(self._state) diff --git a/homeassistant/components/onewire/switch.py b/homeassistant/components/onewire/switch.py index 95a712c95bc..cdf1315394e 100644 --- a/homeassistant/components/onewire/switch.py +++ b/homeassistant/components/onewire/switch.py @@ -208,8 +208,10 @@ class OneWireSwitch(OneWireEntity, SwitchEntity): entity_description: OneWireSwitchEntityDescription @property - def is_on(self) -> bool: - """Return true if sensor is on.""" + def is_on(self) -> bool | None: + """Return true if switch is on.""" + if self._state is None: + return None return bool(self._state) def turn_on(self, **kwargs: Any) -> None: diff --git a/tests/components/onewire/const.py b/tests/components/onewire/const.py index 7fdb6500c37..e5f8ac575e9 100644 --- a/tests/components/onewire/const.py +++ b/tests/components/onewire/const.py @@ -156,7 +156,9 @@ MOCK_OWPROXY_DEVICES = { {ATTR_INJECT_READS: b" 1"}, {ATTR_INJECT_READS: b" 0"}, {ATTR_INJECT_READS: b" 0"}, - {ATTR_INJECT_READS: b" 0"}, + { + ATTR_INJECT_READS: ProtocolError, + }, {ATTR_INJECT_READS: b" 0"}, {ATTR_INJECT_READS: b" 0"}, {ATTR_INJECT_READS: b" 0"}, @@ -166,7 +168,9 @@ MOCK_OWPROXY_DEVICES = { {ATTR_INJECT_READS: b" 1"}, {ATTR_INJECT_READS: b" 0"}, {ATTR_INJECT_READS: b" 1"}, - {ATTR_INJECT_READS: b" 0"}, + { + ATTR_INJECT_READS: ProtocolError, + }, {ATTR_INJECT_READS: b" 1"}, {ATTR_INJECT_READS: b" 0"}, {ATTR_INJECT_READS: b" 1"}, diff --git a/tests/components/onewire/snapshots/test_binary_sensor.ambr b/tests/components/onewire/snapshots/test_binary_sensor.ambr index 60744f59c68..afdec245ea0 100644 --- a/tests/components/onewire/snapshots/test_binary_sensor.ambr +++ b/tests/components/onewire/snapshots/test_binary_sensor.ambr @@ -851,13 +851,13 @@ 'attributes': ReadOnlyDict({ 'device_file': '/29.111111111111/sensed.3', 'friendly_name': '29.111111111111 Sensed 3', - 'raw_value': 0.0, + 'raw_value': None, }), 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_3', 'last_changed': , 'last_updated': , - 'state': 'off', + 'state': 'unknown', }), StateSnapshot({ 'attributes': ReadOnlyDict({ diff --git a/tests/components/onewire/snapshots/test_switch.ambr b/tests/components/onewire/snapshots/test_switch.ambr index fe553ac1bfb..cd3e9708d95 100644 --- a/tests/components/onewire/snapshots/test_switch.ambr +++ b/tests/components/onewire/snapshots/test_switch.ambr @@ -1271,13 +1271,13 @@ 'attributes': ReadOnlyDict({ 'device_file': '/29.111111111111/PIO.3', 'friendly_name': '29.111111111111 Programmed input-output 3', - 'raw_value': 0.0, + 'raw_value': None, }), 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_3', 'last_changed': , 'last_updated': , - 'state': 'off', + 'state': 'unknown', }), StateSnapshot({ 'attributes': ReadOnlyDict({ From 8a144d16f58e452c100bcd8acb602176eaaf5d16 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 Mar 2024 23:39:59 -1000 Subject: [PATCH 1227/1691] Move legacy device_tracker setup to a tracked task (#113715) * Move legacy device_tracker setup to a tracked task Legacy platforms are now loaded in a tracked task which allows anything waiting on device_tracker (such as a config entry that uses the device_tracker platform) to proceed. This also allows us to remove the workaround of adding device_tracker to hass.config.components in its setup * tweak * tweak * fix tests --- .../components/device_tracker/__init__.py | 9 ---- .../components/device_tracker/legacy.py | 42 +++++++++++++------ .../test_device_tracker.py | 4 ++ tests/components/device_tracker/test_init.py | 1 + .../components/meraki/test_device_tracker.py | 9 ++-- .../mqtt_json/test_device_tracker.py | 14 +++++++ 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index e16712acf95..346459e324e 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -69,16 +69,7 @@ def is_on(hass: HomeAssistant, entity_id: str) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the device tracker.""" - - # We need to add the component here break the deadlock - # when setting up integrations from config entries as - # they would otherwise wait for the device tracker to be - # setup and thus the config entries would not be able to - # setup their platforms. - hass.config.components.add(DOMAIN) - await async_setup_legacy_integration(hass, config) - return True diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 9ca9567b896..344443cd878 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -55,6 +55,7 @@ from homeassistant.setup import ( async_start_setup, ) from homeassistant.util import dt as dt_util +from homeassistant.util.async_ import create_eager_task from homeassistant.util.yaml import dump from .const import ( @@ -203,10 +204,37 @@ async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> No """Set up the legacy integration.""" tracker = await get_tracker(hass, config) + async def async_see_service(call: ServiceCall) -> None: + """Service to see a device.""" + # Temp workaround for iOS, introduced in 0.65 + data = dict(call.data) + data.pop("hostname", None) + data.pop("battery_status", None) + await tracker.async_see(**data) + + hass.services.async_register( + DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA + ) + + # + # The platforms load in a non-awaited tracked task + # to ensure device tracker setup can continue and config + # entry integrations are not waiting for legacy device + # tracker platforms to be set up. + # + hass.async_create_task( + _async_setup_legacy_integration(hass, config, tracker), eager_start=True + ) + + +async def _async_setup_legacy_integration( + hass: HomeAssistant, config: ConfigType, tracker: DeviceTracker +) -> None: + """Set up the legacy integration.""" legacy_platforms = await async_extract_config(hass, config) setup_tasks = [ - asyncio.create_task(legacy_platform.async_setup_legacy(hass, tracker)) + create_eager_task(legacy_platform.async_setup_legacy(hass, tracker)) for legacy_platform in legacy_platforms ] @@ -231,18 +259,6 @@ async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> No hass, tracker.async_update_stale, second=range(0, 60, 5) ) - async def async_see_service(call: ServiceCall) -> None: - """Service to see a device.""" - # Temp workaround for iOS, introduced in 0.65 - data = dict(call.data) - data.pop("hostname", None) - data.pop("battery_status", None) - await tracker.async_see(**data) - - hass.services.async_register( - DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA - ) - # restore await tracker.async_setup_tracked_device() diff --git a/tests/components/bluetooth_le_tracker/test_device_tracker.py b/tests/components/bluetooth_le_tracker/test_device_tracker.py index cbf823e4cee..627f2ffadcc 100644 --- a/tests/components/bluetooth_le_tracker/test_device_tracker.py +++ b/tests/components/bluetooth_le_tracker/test_device_tracker.py @@ -103,6 +103,7 @@ async def test_do_not_see_device_if_time_not_updated( CONF_CONSIDER_HOME: timedelta(minutes=10), } result = await async_setup_component(hass, DOMAIN, {DOMAIN: config}) + await hass.async_block_till_done() assert result # Tick until device seen enough times for to be registered for tracking @@ -246,6 +247,7 @@ async def test_preserve_new_tracked_device_name( CONF_TRACK_NEW: True, } assert await async_setup_component(hass, DOMAIN, {DOMAIN: config}) + await hass.async_block_till_done() # Seen once here; return without name when seen subsequent times device = BluetoothServiceInfoBleak( @@ -315,6 +317,7 @@ async def test_tracking_battery_times_out( CONF_TRACK_NEW: True, } result = await async_setup_component(hass, DOMAIN, {DOMAIN: config}) + await hass.async_block_till_done() assert result # Tick until device seen enough times for to be registered for tracking @@ -449,6 +452,7 @@ async def test_tracking_battery_successful( CONF_TRACK_NEW: True, } result = await async_setup_component(hass, DOMAIN, {DOMAIN: config}) + await hass.async_block_till_done() assert result # Tick until device seen enough times for to be registered for tracking diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 520e13e4c83..3166354260b 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -310,6 +310,7 @@ async def test_entity_attributes( with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() attrs = hass.states.get(entity_id).attributes diff --git a/tests/components/meraki/test_device_tracker.py b/tests/components/meraki/test_device_tracker.py index 02b9ba06b72..d0cd2cf8c5a 100644 --- a/tests/components/meraki/test_device_tracker.py +++ b/tests/components/meraki/test_device_tracker.py @@ -21,8 +21,9 @@ from homeassistant.setup import async_setup_component def meraki_client(event_loop, hass, hass_client): """Meraki mock client.""" loop = event_loop - assert loop.run_until_complete( - async_setup_component( + + async def setup_and_wait(): + result = await async_setup_component( hass, device_tracker.DOMAIN, { @@ -33,8 +34,10 @@ def meraki_client(event_loop, hass, hass_client): } }, ) - ) + await hass.async_block_till_done() + return result + assert loop.run_until_complete(setup_and_wait()) return loop.run_until_complete(hass_client()) diff --git a/tests/components/mqtt_json/test_device_tracker.py b/tests/components/mqtt_json/test_device_tracker.py index 91f40e2e4df..f150f5c86c9 100644 --- a/tests/components/mqtt_json/test_device_tracker.py +++ b/tests/components/mqtt_json/test_device_tracker.py @@ -61,6 +61,8 @@ async def test_setup_fails_without_mqtt_being_setup( DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}}, ) + await hass.async_block_till_done() + assert "MQTT integration is not available" in caplog.text @@ -83,6 +85,7 @@ async def test_ensure_device_tracker_platform_validation(hass: HomeAssistant) -> DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}}, ) + await hass.async_block_till_done() assert mock_sp.call_count == 1 @@ -97,6 +100,7 @@ async def test_json_message(hass: HomeAssistant) -> None: DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}}, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() state = hass.states.get("device_tracker.zanzito") @@ -117,6 +121,7 @@ async def test_non_json_message( DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}}, ) + await hass.async_block_till_done() caplog.set_level(logging.ERROR) caplog.clear() @@ -138,6 +143,7 @@ async def test_incomplete_message( DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}}, ) + await hass.async_block_till_done() caplog.set_level(logging.ERROR) caplog.clear() @@ -161,6 +167,8 @@ async def test_single_level_wildcard_topic(hass: HomeAssistant) -> None: DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}}, ) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() state = hass.states.get("device_tracker.zanzito") @@ -180,6 +188,8 @@ async def test_multi_level_wildcard_topic(hass: HomeAssistant) -> None: DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}}, ) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() state = hass.states.get("device_tracker.zanzito") @@ -200,6 +210,8 @@ async def test_single_level_wildcard_topic_not_matching(hass: HomeAssistant) -> DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}}, ) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() assert hass.states.get(entity_id) is None @@ -218,6 +230,8 @@ async def test_multi_level_wildcard_topic_not_matching(hass: HomeAssistant) -> N DT_DOMAIN, {DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}}, ) + await hass.async_block_till_done() + async_fire_mqtt_message(hass, topic, location) await hass.async_block_till_done() assert hass.states.get(entity_id) is None From 264e023ab4c3bdc4d55885701cb8dc8739272ae3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 02:07:00 -1000 Subject: [PATCH 1228/1691] Run august stop listener with run_immediately (#113729) There is no need for a call_soon here --- homeassistant/components/august/subscriber.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/august/subscriber.py b/homeassistant/components/august/subscriber.py index 2ba1454a114..e800b5cb604 100644 --- a/homeassistant/components/august/subscriber.py +++ b/homeassistant/components/august/subscriber.py @@ -66,7 +66,9 @@ class AugustSubscriberMixin: self._unsub_interval() self._stop_interval = self._hass.bus.async_listen( - EVENT_HOMEASSISTANT_STOP, _async_cancel_update_interval + EVENT_HOMEASSISTANT_STOP, + _async_cancel_update_interval, + run_immediately=True, ) @callback From 10f2d8b4b12d1b64f56e28b327212056a55875b2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 02:09:21 -1000 Subject: [PATCH 1229/1691] Move legacy notify setup to use tracked tasks (#113716) * Move legacy notify setup to a tracked task * fix test * fix test * comment --- homeassistant/components/notify/__init__.py | 23 +++++++-------------- tests/components/file/test_notify.py | 2 ++ tests/components/tts/test_notify.py | 7 +++++++ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 2aa110649f2..e7390a49676 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -2,8 +2,6 @@ from __future__ import annotations -import asyncio - import voluptuous as vol import homeassistant.components.persistent_notification as pn @@ -43,19 +41,14 @@ PLATFORM_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the notify services.""" - platform_setups = async_setup_legacy(hass, config) - - # We need to add the component here break the deadlock - # when setting up integrations from config entries as - # they would otherwise wait for notify to be - # setup and thus the config entries would not be able to - # setup their platforms, but we need to do it after - # the dispatcher is connected so we don't miss integrations - # that are registered before the dispatcher is connected - hass.config.components.add(DOMAIN) - - if platform_setups: - await asyncio.wait([asyncio.create_task(setup) for setup in platform_setups]) + for setup in async_setup_legacy(hass, config): + # Tasks are created as tracked tasks to ensure startup + # waits for them to finish, but we explicitly do not + # want to wait for them to finish here because we want + # any config entries that use notify as a base platform + # to be able to start with out having to wait for the + # legacy platforms to finish setting up. + hass.async_create_task(setup, eager_start=True) async def persistent_notification(service: ServiceCall) -> None: """Send notification via the built-in persistent_notify integration.""" diff --git a/tests/components/file/test_notify.py b/tests/components/file/test_notify.py index 071c68caea3..093be77c08f 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -20,6 +20,7 @@ async def test_bad_config(hass: HomeAssistant) -> None: config = {notify.DOMAIN: {"name": "test", "platform": "file"}} with assert_setup_component(0) as handle_config: assert await async_setup_component(hass, notify.DOMAIN, config) + await hass.async_block_till_done() assert not handle_config[notify.DOMAIN] @@ -49,6 +50,7 @@ async def test_notify_file( } }, ) + await hass.async_block_till_done() assert handle_config[notify.DOMAIN] freezer.move_to(dt_util.utcnow()) diff --git a/tests/components/tts/test_notify.py b/tests/components/tts/test_notify.py index acefca21f6c..07ba2f2f3f5 100644 --- a/tests/components/tts/test_notify.py +++ b/tests/components/tts/test_notify.py @@ -49,6 +49,7 @@ async def test_setup_legacy_platform(hass: HomeAssistant) -> None: } with assert_setup_component(1, notify.DOMAIN): assert await async_setup_component(hass, notify.DOMAIN, config) + await hass.async_block_till_done() assert hass.services.has_service(notify.DOMAIN, "tts_test") @@ -65,6 +66,7 @@ async def test_setup_platform(hass: HomeAssistant) -> None: } with assert_setup_component(1, notify.DOMAIN): assert await async_setup_component(hass, notify.DOMAIN, config) + await hass.async_block_till_done() assert hass.services.has_service(notify.DOMAIN, "tts_test") @@ -80,6 +82,7 @@ async def test_setup_platform_missing_key(hass: HomeAssistant) -> None: } with assert_setup_component(0, notify.DOMAIN): assert await async_setup_component(hass, notify.DOMAIN, config) + await hass.async_block_till_done() assert not hass.services.has_service(notify.DOMAIN, "tts_test") @@ -107,6 +110,8 @@ async def test_setup_legacy_service(hass: HomeAssistant) -> None: with assert_setup_component(1, notify.DOMAIN): assert await async_setup_component(hass, notify.DOMAIN, config) + await hass.async_block_till_done() + await hass.services.async_call( notify.DOMAIN, "tts_test", @@ -142,6 +147,8 @@ async def test_setup_service( with assert_setup_component(1, notify.DOMAIN): assert await async_setup_component(hass, notify.DOMAIN, config) + await hass.async_block_till_done() + await hass.services.async_call( notify.DOMAIN, "tts_test", From 719d373bd70f7092e50b158c160e7fb40d1ebc25 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 02:10:12 -1000 Subject: [PATCH 1230/1691] Move legacy stt setup to use tracked tasks (#113718) * Move legacy stt setup to use tracked tasks * comment --- homeassistant/components/stt/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stt/__init__.py b/homeassistant/components/stt/__init__.py index c3d39d4337e..676d8b8aa76 100644 --- a/homeassistant/components/stt/__init__.py +++ b/homeassistant/components/stt/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations from abc import abstractmethod -import asyncio from collections.abc import AsyncIterable from dataclasses import asdict import logging @@ -127,8 +126,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: component.register_shutdown() platform_setups = async_setup_legacy(hass, config) - if platform_setups: - await asyncio.wait([asyncio.create_task(setup) for setup in platform_setups]) + for setup in platform_setups: + # Tasks are created as tracked tasks to ensure startup + # waits for them to finish, but we explicitly do not + # want to wait for them to finish here because we want + # any config entries that use stt as a base platform + # to be able to start with out having to wait for the + # legacy platforms to finish setting up. + hass.async_create_task(setup, eager_start=True) hass.http.register_view(SpeechToTextView(hass.data[DATA_PROVIDERS])) return True From 8f33bad4ef533a57e509b5932e66cf17bd192a8d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 02:11:22 -1000 Subject: [PATCH 1231/1691] Move legacy tts setup to use tracked tasks (#113717) * Move legacy tts setup to a tracked task * comment * fix test * fix test * fix more tests * tweak --- homeassistant/components/tts/__init__.py | 12 +++++++++--- tests/components/cloud/test_tts.py | 2 ++ tests/components/microsoft/test_tts.py | 7 +++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index f445309fffe..82055d542ff 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -319,9 +319,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: platform_setups = await async_setup_legacy(hass, config) - if platform_setups: - await asyncio.wait([asyncio.create_task(setup) for setup in platform_setups]) - component.async_register_entity_service( "speak", { @@ -345,6 +342,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: schema=SCHEMA_SERVICE_CLEAR_CACHE, ) + for setup in platform_setups: + # Tasks are created as tracked tasks to ensure startup + # waits for them to finish, but we explicitly do not + # want to wait for them to finish here because we want + # any config entries that use tts as a base platform + # to be able to start with out having to wait for the + # legacy platforms to finish setting up. + hass.async_create_task(setup, eager_start=True) + return True diff --git a/tests/components/cloud/test_tts.py b/tests/components/cloud/test_tts.py index 2fd42012c77..3797a9784e1 100644 --- a/tests/components/cloud/test_tts.py +++ b/tests/components/cloud/test_tts.py @@ -101,6 +101,7 @@ async def test_prefs_default_voice( """Test cloud provider uses the preferences.""" assert await async_setup_component(hass, "homeassistant", {}) assert await async_setup_component(hass, TTS_DOMAIN, {TTS_DOMAIN: platform_config}) + await hass.async_block_till_done() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -108,6 +109,7 @@ async def test_prefs_default_voice( on_start_callback = cloud.register_on_start.call_args[0][0] await on_start_callback() + await hass.async_block_till_done() engine = get_engine_instance(hass, engine_id) diff --git a/tests/components/microsoft/test_tts.py b/tests/components/microsoft/test_tts.py index cd4ccabcf0d..c395dc82419 100644 --- a/tests/components/microsoft/test_tts.py +++ b/tests/components/microsoft/test_tts.py @@ -61,6 +61,7 @@ async def test_service_say( await async_setup_component( hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "microsoft", "api_key": ""}} ) + await hass.async_block_till_done() await hass.services.async_call( tts.DOMAIN, @@ -110,6 +111,7 @@ async def test_service_say_en_gb_config( } }, ) + await hass.async_block_till_done() await hass.services.async_call( tts.DOMAIN, @@ -151,6 +153,7 @@ async def test_service_say_en_gb_service( tts.DOMAIN, {tts.DOMAIN: {"platform": "microsoft", "api_key": ""}}, ) + await hass.async_block_till_done() await hass.services.async_call( tts.DOMAIN, @@ -201,6 +204,7 @@ async def test_service_say_fa_ir_config( } }, ) + await hass.async_block_till_done() await hass.services.async_call( tts.DOMAIN, @@ -246,6 +250,7 @@ async def test_service_say_fa_ir_service( } await async_setup_component(hass, tts.DOMAIN, config) + await hass.async_block_till_done() await hass.services.async_call( tts.DOMAIN, @@ -303,6 +308,7 @@ async def test_invalid_language(hass: HomeAssistant, mock_tts, calls) -> None: tts.DOMAIN, {tts.DOMAIN: {"platform": "microsoft", "api_key": "", "language": "en"}}, ) + await hass.async_block_till_done() with pytest.raises(ServiceNotFound): await hass.services.async_call( @@ -327,6 +333,7 @@ async def test_service_say_error( await async_setup_component( hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "microsoft", "api_key": ""}} ) + await hass.async_block_till_done() await hass.services.async_call( tts.DOMAIN, From 727581eea3f95faca3d8d1ca40e465e601ed3ade Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:26:14 +0100 Subject: [PATCH 1232/1691] Add first batch of Ruff PT rules (#113665) * Add first batch of Ruff PT rules * fix weather test * Fix pilight test * Update test_intent.py * Update pilight test_init.py * Update test_init.py --- homeassistant/components/recorder/util.py | 4 +-- pyproject.toml | 10 +++++++ .../device_action/tests/test_device_action.py | 2 +- script/version_bump.py | 2 +- .../components/blackbird/test_media_player.py | 2 +- tests/components/dsmr/test_init.py | 1 - tests/components/humidifier/test_intent.py | 14 +++------- tests/components/modbus/test_init.py | 27 ------------------- tests/components/modbus/test_sensor.py | 9 ------- tests/components/pilight/test_init.py | 6 ++--- .../risco/test_alarm_control_panel.py | 7 ++--- tests/components/risco/test_binary_sensor.py | 15 ++++++----- tests/components/risco/test_sensor.py | 7 ++--- tests/components/risco/test_switch.py | 7 ++--- tests/components/rympro/test_config_flow.py | 23 ++++++++-------- tests/components/template/test_fan.py | 14 ---------- tests/components/weather/test_init.py | 8 +++--- tests/components/zha/test_cluster_handlers.py | 1 - tests/helpers/test_entity_registry.py | 3 +-- 19 files changed, 60 insertions(+), 102 deletions(-) diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index e92e287d11a..770dc91353c 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -645,7 +645,7 @@ def retryable_database_job( return job(instance, *args, **kwargs) except OperationalError as err: if _is_retryable_error(instance, err): - assert isinstance(err.orig, BaseException) + assert isinstance(err.orig, BaseException) # noqa: PT017 _LOGGER.info( "%s; %s not completed, retrying", err.orig.args[1], description ) @@ -691,7 +691,7 @@ def database_job_retry_wrapper( instance, err ): raise - assert isinstance(err.orig, BaseException) + assert isinstance(err.orig, BaseException) # noqa: PT017 _LOGGER.info( "%s; %s failed, retrying", err.orig.args[1], description ) diff --git a/pyproject.toml b/pyproject.toml index 218127ccff0..8c79824a1f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -609,6 +609,7 @@ select = [ "PGH004", # Use specific rule codes when using noqa "PIE", # flake8-pie "PL", # pylint + "PT", # flake8-pytest-style "RSE", # flake8-raise "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task @@ -679,6 +680,15 @@ ignore = [ # Disabled because ruff does not understand type of __all__ generated by a function "PLE0605", + + # temporarily disabled + "PT004", + "PT007", + "PT011", + "PT018", + "PT012", + "PT023", + "PT019" ] [tool.ruff.lint.flake8-import-conventions.extend-aliases] diff --git a/script/scaffold/templates/device_action/tests/test_device_action.py b/script/scaffold/templates/device_action/tests/test_device_action.py index cd7adfbc844..f8260a62282 100644 --- a/script/scaffold/templates/device_action/tests/test_device_action.py +++ b/script/scaffold/templates/device_action/tests/test_device_action.py @@ -49,7 +49,7 @@ async def test_get_actions( @pytest.mark.parametrize( - "hidden_by,entity_category", + ("hidden_by", "entity_category"), ( (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), diff --git a/script/version_bump.py b/script/version_bump.py index 50f749d6983..da864b45176 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -90,7 +90,7 @@ def bump_version(version, bump_type): to_change["dev"] = ("dev", dt_util.utcnow().strftime("%Y%m%d")) else: - assert False, f"Unsupported type: {bump_type}" + raise ValueError(f"Unsupported type: {bump_type}") temp = Version("0") temp._version = version._version._replace(**to_change) diff --git a/tests/components/blackbird/test_media_player.py b/tests/components/blackbird/test_media_player.py index 5e6ece7d792..3b0465ef208 100644 --- a/tests/components/blackbird/test_media_player.py +++ b/tests/components/blackbird/test_media_player.py @@ -176,7 +176,7 @@ async def setup_blackbird(hass, mock_blackbird): """Set up blackbird.""" with mock.patch( "homeassistant.components.blackbird.media_player.get_blackbird", - new=lambda *a: mock_blackbird, + return_value=mock_blackbird, ): await hass.async_add_executor_job( setup_platform, diff --git a/tests/components/dsmr/test_init.py b/tests/components/dsmr/test_init.py index f87c17f6a19..11487b6b87b 100644 --- a/tests/components/dsmr/test_init.py +++ b/tests/components/dsmr/test_init.py @@ -78,7 +78,6 @@ from tests.common import MockConfigEntry ("5B", "1234_Max_current_per_phase", "1234_belgium_max_current_per_phase"), ("5L", "1234_Energy_Consumption_(total)", "1234_electricity_imported_total"), ("5L", "1234_Energy_Production_(total)", "1234_electricity_exported_total"), - ("5L", "1234_Energy_Production_(total)", "1234_electricity_exported_total"), ("5", "1234_Gas_Consumption", "1234_hourly_gas_meter_reading"), ("5B", "1234_Gas_Consumption", "1234_belgium_5min_gas_meter_reading"), ("2.2", "1234_Gas_Consumption", "1234_gas_meter_reading"), diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py index 20e23b14605..9ee72baf73b 100644 --- a/tests/components/humidifier/test_intent.py +++ b/tests/components/humidifier/test_intent.py @@ -175,17 +175,14 @@ async def test_intent_set_mode_tests_feature(hass: HomeAssistant) -> None: mode_calls = async_mock_service(hass, DOMAIN, SERVICE_SET_MODE) await intent.async_setup_intents(hass) - try: + with pytest.raises(IntentHandleError) as excinfo: await async_handle( hass, "test", intent.INTENT_MODE, {"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}}, ) - pytest.fail("handling intent should have raised") - except IntentHandleError as err: - assert str(err) == "Entity bedroom humidifier does not support modes" - + assert str(excinfo.value) == "Entity bedroom humidifier does not support modes" assert len(mode_calls) == 0 @@ -207,15 +204,12 @@ async def test_intent_set_unknown_mode( mode_calls = async_mock_service(hass, DOMAIN, SERVICE_SET_MODE) await intent.async_setup_intents(hass) - try: + with pytest.raises(IntentHandleError) as excinfo: await async_handle( hass, "test", intent.INTENT_MODE, {"name": {"value": "Bedroom humidifier"}, "mode": {"value": "eco"}}, ) - pytest.fail("handling intent should have raised") - except IntentHandleError as err: - assert str(err) == "Entity bedroom humidifier does not support eco mode" - + assert str(excinfo.value) == "Entity bedroom humidifier does not support eco mode" assert len(mode_calls) == 0 diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 44bf089f0d5..b93519c1d05 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -329,11 +329,6 @@ async def test_ok_struct_validator(do_config) -> None: CONF_VIRTUAL_COUNT: 2, CONF_DATA_TYPE: DataType.INT32, }, - { - CONF_NAME: TEST_ENTITY_NAME, - CONF_DATA_TYPE: DataType.INT16, - CONF_SWAP: CONF_SWAP_WORD, - }, { CONF_NAME: TEST_ENTITY_NAME, CONF_DATA_TYPE: DataType.INT16, @@ -981,28 +976,6 @@ async def test_no_duplicate_names(hass: HomeAssistant, do_config) -> None: } ], }, - { - CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST, - CONF_PORT: TEST_PORT_TCP, - CONF_SENSORS: [ - { - CONF_NAME: "dummy", - CONF_ADDRESS: 9999, - } - ], - }, - { - CONF_TYPE: TCP, - CONF_HOST: TEST_MODBUS_HOST, - CONF_PORT: TEST_PORT_TCP, - CONF_SENSORS: [ - { - CONF_NAME: "dummy", - CONF_ADDRESS: 9999, - } - ], - }, { CONF_TYPE: TCP, CONF_HOST: TEST_MODBUS_HOST, diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index 23347cc56bb..524acc0dabb 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -116,15 +116,6 @@ SLAVE_UNIQUE_ID = "ground_floor_sensor" } ] }, - { - CONF_SENSORS: [ - { - CONF_NAME: TEST_ENTITY_NAME, - CONF_ADDRESS: 51, - CONF_DATA_TYPE: DataType.INT16, - } - ] - }, { CONF_SENSORS: [ { diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index 9cbbadfbbc8..e5ff016b5f3 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -5,6 +5,7 @@ import logging import socket from unittest.mock import patch +import pytest from voluptuous import MultipleInvalid from homeassistant.components import pilight @@ -104,7 +105,7 @@ async def test_send_code_no_protocol(hass: HomeAssistant) -> None: assert await async_setup_component(hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) # Call without protocol info, should raise an error - try: + with pytest.raises(MultipleInvalid) as excinfo: await hass.services.async_call( pilight.DOMAIN, pilight.SERVICE_NAME, @@ -112,8 +113,7 @@ async def test_send_code_no_protocol(hass: HomeAssistant) -> None: blocking=True, ) await hass.async_block_till_done() - except MultipleInvalid as error: - assert "required key not provided @ data['protocol']" in str(error) + assert "required key not provided @ data['protocol']" in str(excinfo.value) @patch("homeassistant.components.pilight._LOGGER.error") diff --git a/tests/components/risco/test_alarm_control_panel.py b/tests/components/risco/test_alarm_control_panel.py index 5fc4f40da99..ee8b844c167 100644 --- a/tests/components/risco/test_alarm_control_panel.py +++ b/tests/components/risco/test_alarm_control_panel.py @@ -512,7 +512,8 @@ async def _check_local_state( @pytest.fixture -def _mock_partition_handler(): +def mock_partition_handler(): + """Create a mock for add_partition_handler.""" with patch( "homeassistant.components.risco.RiscoLocal.add_partition_handler" ) as mock: @@ -523,11 +524,11 @@ def _mock_partition_handler(): async def test_local_states( hass: HomeAssistant, two_part_local_alarm, - _mock_partition_handler, + mock_partition_handler, setup_risco_local, ) -> None: """Test the various alarm states.""" - callback = _mock_partition_handler.call_args.args[0] + callback = mock_partition_handler.call_args.args[0] assert callback is not None diff --git a/tests/components/risco/test_binary_sensor.py b/tests/components/risco/test_binary_sensor.py index ce49c1696fd..22f71ead28d 100644 --- a/tests/components/risco/test_binary_sensor.py +++ b/tests/components/risco/test_binary_sensor.py @@ -134,16 +134,17 @@ async def _check_local_state( @pytest.fixture -def _mock_zone_handler(): +def mock_zone_handler(): + """Create a mock for add_zone_handler.""" with patch("homeassistant.components.risco.RiscoLocal.add_zone_handler") as mock: yield mock async def test_local_states( - hass: HomeAssistant, two_zone_local, _mock_zone_handler, setup_risco_local + hass: HomeAssistant, two_zone_local, mock_zone_handler, setup_risco_local ) -> None: """Test the various zone states.""" - callback = _mock_zone_handler.call_args.args[0] + callback = mock_zone_handler.call_args.args[0] assert callback is not None @@ -162,10 +163,10 @@ async def test_local_states( async def test_alarmed_local_states( - hass: HomeAssistant, two_zone_local, _mock_zone_handler, setup_risco_local + hass: HomeAssistant, two_zone_local, mock_zone_handler, setup_risco_local ) -> None: """Test the various zone alarmed states.""" - callback = _mock_zone_handler.call_args.args[0] + callback = mock_zone_handler.call_args.args[0] assert callback is not None @@ -184,10 +185,10 @@ async def test_alarmed_local_states( async def test_armed_local_states( - hass: HomeAssistant, two_zone_local, _mock_zone_handler, setup_risco_local + hass: HomeAssistant, two_zone_local, mock_zone_handler, setup_risco_local ) -> None: """Test the various zone armed states.""" - callback = _mock_zone_handler.call_args.args[0] + callback = mock_zone_handler.call_args.args[0] assert callback is not None diff --git a/tests/components/risco/test_sensor.py b/tests/components/risco/test_sensor.py index a172eb3d650..be909254d70 100644 --- a/tests/components/risco/test_sensor.py +++ b/tests/components/risco/test_sensor.py @@ -163,7 +163,8 @@ def _set_utc_time_zone(hass): @pytest.fixture -def _save_mock(): +def save_mock(): + """Create a mock for async_save.""" with patch( "homeassistant.components.risco.Store.async_save", ) as save_mock: @@ -175,7 +176,7 @@ async def test_cloud_setup( hass: HomeAssistant, two_zone_cloud, _set_utc_time_zone, - _save_mock, + save_mock, setup_risco_cloud, ) -> None: """Test entity setup.""" @@ -183,7 +184,7 @@ async def test_cloud_setup( for id in ENTITY_IDS.values(): assert registry.async_is_registered(id) - _save_mock.assert_awaited_once_with({LAST_EVENT_TIMESTAMP_KEY: TEST_EVENTS[0].time}) + save_mock.assert_awaited_once_with({LAST_EVENT_TIMESTAMP_KEY: TEST_EVENTS[0].time}) for category, entity_id in ENTITY_IDS.items(): _check_state(hass, category, entity_id) diff --git a/tests/components/risco/test_switch.py b/tests/components/risco/test_switch.py index 1d575f4b75b..100796b9ea1 100644 --- a/tests/components/risco/test_switch.py +++ b/tests/components/risco/test_switch.py @@ -124,16 +124,17 @@ async def _check_local_state(hass, zones, bypassed, entity_id, zone_id, callback @pytest.fixture -def _mock_zone_handler(): +def mock_zone_handler(): + """Create a mock for add_zone_handler.""" with patch("homeassistant.components.risco.RiscoLocal.add_zone_handler") as mock: yield mock async def test_local_states( - hass: HomeAssistant, two_zone_local, _mock_zone_handler, setup_risco_local + hass: HomeAssistant, two_zone_local, mock_zone_handler, setup_risco_local ) -> None: """Test the various alarm states.""" - callback = _mock_zone_handler.call_args.args[0] + callback = mock_zone_handler.call_args.args[0] assert callback is not None diff --git a/tests/components/rympro/test_config_flow.py b/tests/components/rympro/test_config_flow.py index 811234dd559..e61384107cb 100644 --- a/tests/components/rympro/test_config_flow.py +++ b/tests/components/rympro/test_config_flow.py @@ -25,7 +25,8 @@ TEST_DATA = { @pytest.fixture -def _config_entry(hass): +def config_entry(hass: HomeAssistant) -> MockConfigEntry: + """Create a mock config entry.""" config_entry = MockConfigEntry( domain=DOMAIN, data=TEST_DATA, @@ -122,7 +123,7 @@ async def test_login_error(hass: HomeAssistant, exception, error) -> None: assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_already_exists(hass: HomeAssistant, _config_entry) -> None: +async def test_form_already_exists(hass: HomeAssistant, config_entry) -> None: """Test that a flow with an existing account aborts.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -148,16 +149,16 @@ async def test_form_already_exists(hass: HomeAssistant, _config_entry) -> None: assert result2["reason"] == "already_configured" -async def test_form_reauth(hass: HomeAssistant, _config_entry) -> None: +async def test_form_reauth(hass: HomeAssistant, config_entry) -> None: """Test reauthentication.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={ "source": config_entries.SOURCE_REAUTH, - "entry_id": _config_entry.entry_id, + "entry_id": config_entry.entry_id, }, - data=_config_entry.data, + data=config_entry.data, ) assert result["type"] == "form" assert result["errors"] is None @@ -183,20 +184,20 @@ async def test_form_reauth(hass: HomeAssistant, _config_entry) -> None: assert result2["type"] == "abort" assert result2["reason"] == "reauth_successful" - assert _config_entry.data[CONF_PASSWORD] == "new_password" + assert config_entry.data[CONF_PASSWORD] == "new_password" assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_reauth_with_new_account(hass: HomeAssistant, _config_entry) -> None: +async def test_form_reauth_with_new_account(hass: HomeAssistant, config_entry) -> None: """Test reauthentication with new account.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={ "source": config_entries.SOURCE_REAUTH, - "entry_id": _config_entry.entry_id, + "entry_id": config_entry.entry_id, }, - data=_config_entry.data, + data=config_entry.data, ) assert result["type"] == "form" assert result["errors"] is None @@ -222,6 +223,6 @@ async def test_form_reauth_with_new_account(hass: HomeAssistant, _config_entry) assert result2["type"] == "abort" assert result2["reason"] == "reauth_successful" - assert _config_entry.data[CONF_UNIQUE_ID] == "new-account-number" - assert _config_entry.unique_id == "new-account-number" + assert config_entry.data[CONF_UNIQUE_ID] == "new-account-number" + assert config_entry.unique_id == "new-account-number" assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index 773c67c39db..93520b0f621 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -105,20 +105,6 @@ async def test_missing_optional_config(hass: HomeAssistant, start_ha) -> None: }, } }, - { - DOMAIN: { - "platform": "template", - "fans": { - "platform": "template", - "fans": { - "test_fan": { - "value_template": "{{ 'on' }}", - "turn_on": {"service": "script.fan_on"}, - } - }, - }, - } - }, ], ) async def test_wrong_template_config(hass: HomeAssistant, start_ha) -> None: diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index cf29c8d62ab..c6f7e6112db 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -257,10 +257,12 @@ async def test_temperature_no_unit( ) -@pytest.mark.parametrize("native_unit", (UnitOfPressure.INHG, UnitOfPressure.INHG)) @pytest.mark.parametrize( - ("state_unit", "unit_system"), - ((UnitOfPressure.HPA, METRIC_SYSTEM), (UnitOfPressure.INHG, US_CUSTOMARY_SYSTEM)), + ("state_unit", "unit_system", "native_unit"), + ( + (UnitOfPressure.HPA, METRIC_SYSTEM, UnitOfPressure.INHG), + (UnitOfPressure.INHG, US_CUSTOMARY_SYSTEM, UnitOfPressure.INHG), + ), ) async def test_pressure( hass: HomeAssistant, diff --git a/tests/components/zha/test_cluster_handlers.py b/tests/components/zha/test_cluster_handlers.py index fd8ca31fe9f..c081faab010 100644 --- a/tests/components/zha/test_cluster_handlers.py +++ b/tests/components/zha/test_cluster_handlers.py @@ -148,7 +148,6 @@ async def poll_control_device(zha_device_restored, zigpy_device_mock): (zigpy.zcl.clusters.general.AnalogInput.cluster_id, 1, {"present_value"}), (zigpy.zcl.clusters.general.AnalogOutput.cluster_id, 1, {"present_value"}), (zigpy.zcl.clusters.general.AnalogValue.cluster_id, 1, {"present_value"}), - (zigpy.zcl.clusters.general.AnalogOutput.cluster_id, 1, {"present_value"}), (zigpy.zcl.clusters.general.BinaryOutput.cluster_id, 1, {"present_value"}), (zigpy.zcl.clusters.general.BinaryValue.cluster_id, 1, {"present_value"}), (zigpy.zcl.clusters.general.MultistateInput.cluster_id, 1, {"present_value"}), diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index b029933ebbe..0974d3bc9de 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -1775,8 +1775,7 @@ async def test_async_migrate_entry_delete_other( entity_registry.async_remove(entry2.entity_id) return None if entity_entry == entry2: - # We should not get here - pytest.fail() + pytest.fail("We should not get here") return None entries = set() From ec7aecef84957f11846350c7f0d53c13fb4b2248 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 18 Mar 2024 14:39:41 +0100 Subject: [PATCH 1233/1691] Get HomeAssistantError message from translation cache only (#113688) Get HomeAssistant error message from translation cache only --- .../components/devolo_home_network/button.py | 2 -- .../components/devolo_home_network/switch.py | 2 -- .../components/devolo_home_network/update.py | 2 -- .../components/homewizard/helpers.py | 2 -- .../components/rest_command/__init__.py | 16 +++++---- .../components/rest_command/strings.json | 6 ++-- homeassistant/components/roborock/device.py | 1 - homeassistant/components/roborock/image.py | 1 - homeassistant/components/sensibo/climate.py | 6 ---- homeassistant/components/sensibo/entity.py | 2 -- homeassistant/components/sensibo/select.py | 2 -- homeassistant/components/sensibo/switch.py | 2 -- homeassistant/components/tailwind/cover.py | 6 ---- homeassistant/components/tailwind/number.py | 1 - homeassistant/components/tessie/entity.py | 1 - .../yale_smart_alarm/alarm_control_panel.py | 3 -- .../components/yale_smart_alarm/lock.py | 2 -- tests/components/rest_command/test_init.py | 33 ++++++++++--------- tests/components/tailwind/test_cover.py | 24 ++++++++++---- tests/components/tailwind/test_number.py | 6 +++- 20 files changed, 53 insertions(+), 67 deletions(-) diff --git a/homeassistant/components/devolo_home_network/button.py b/homeassistant/components/devolo_home_network/button.py index 13e688c6fc5..1dcdc007189 100644 --- a/homeassistant/components/devolo_home_network/button.py +++ b/homeassistant/components/devolo_home_network/button.py @@ -117,14 +117,12 @@ class DevoloButtonEntity(DevoloEntity, ButtonEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password", translation_domain=DOMAIN, translation_key="password_protected", translation_placeholders={"title": self.entry.title}, ) from ex except DeviceUnavailable as ex: raise HomeAssistantError( - f"Device {self.entry.title} did not respond", translation_domain=DOMAIN, translation_key="no_response", translation_placeholders={"title": self.entry.title}, diff --git a/homeassistant/components/devolo_home_network/switch.py b/homeassistant/components/devolo_home_network/switch.py index 950b362d7f5..2a9775257a8 100644 --- a/homeassistant/components/devolo_home_network/switch.py +++ b/homeassistant/components/devolo_home_network/switch.py @@ -109,7 +109,6 @@ class DevoloSwitchEntity(DevoloCoordinatorEntity[_DataT], SwitchEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password", translation_domain=DOMAIN, translation_key="password_protected", translation_placeholders={"title": self.entry.title}, @@ -125,7 +124,6 @@ class DevoloSwitchEntity(DevoloCoordinatorEntity[_DataT], SwitchEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password", translation_domain=DOMAIN, translation_key="password_protected", translation_placeholders={"title": self.entry.title}, diff --git a/homeassistant/components/devolo_home_network/update.py b/homeassistant/components/devolo_home_network/update.py index 0793254c761..75fc1b7b99c 100644 --- a/homeassistant/components/devolo_home_network/update.py +++ b/homeassistant/components/devolo_home_network/update.py @@ -117,14 +117,12 @@ class DevoloUpdateEntity(DevoloCoordinatorEntity, UpdateEntity): except DevicePasswordProtected as ex: self.entry.async_start_reauth(self.hass) raise HomeAssistantError( - f"Device {self.entry.title} require re-authenticatication to set or change the password", translation_domain=DOMAIN, translation_key="password_protected", translation_placeholders={"title": self.entry.title}, ) from ex except DeviceUnavailable as ex: raise HomeAssistantError( - f"Device {self.entry.title} did not respond", translation_domain=DOMAIN, translation_key="no_response", translation_placeholders={"title": self.entry.title}, diff --git a/homeassistant/components/homewizard/helpers.py b/homeassistant/components/homewizard/helpers.py index a69e09204e7..a3eda4ad565 100644 --- a/homeassistant/components/homewizard/helpers.py +++ b/homeassistant/components/homewizard/helpers.py @@ -33,7 +33,6 @@ def homewizard_exception_handler( await func(self, *args, **kwargs) except RequestError as ex: raise HomeAssistantError( - "An error occurred while communicating with HomeWizard device", translation_domain=DOMAIN, translation_key="communication_error", ) from ex @@ -42,7 +41,6 @@ def homewizard_exception_handler( self.coordinator.config_entry.entry_id ) raise HomeAssistantError( - "The local API of the HomeWizard device is disabled", translation_domain=DOMAIN, translation_key="api_disabled", ) from ex diff --git a/homeassistant/components/rest_command/__init__.py b/homeassistant/components/rest_command/__init__.py index 81a57e432d2..c43e23cf068 100644 --- a/homeassistant/components/rest_command/__init__.py +++ b/homeassistant/components/rest_command/__init__.py @@ -173,33 +173,37 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _content = await response.text() except (JSONDecodeError, AttributeError) as err: raise HomeAssistantError( - f"Response of '{request_url}' could not be decoded as JSON", translation_domain=DOMAIN, translation_key="decoding_error", - translation_placeholders={"decoding_type": "json"}, + translation_placeholders={ + "request_url": request_url, + "decoding_type": "JSON", + }, ) from err except UnicodeDecodeError as err: raise HomeAssistantError( - f"Response of '{request_url}' could not be decoded as text", translation_domain=DOMAIN, translation_key="decoding_error", - translation_placeholders={"decoding_type": "text"}, + translation_placeholders={ + "request_url": request_url, + "decoding_type": "text", + }, ) from err return {"content": _content, "status": response.status} except TimeoutError as err: raise HomeAssistantError( - f"Timeout when calling resource '{request_url}'", translation_domain=DOMAIN, translation_key="timeout", + translation_placeholders={"request_url": request_url}, ) from err except aiohttp.ClientError as err: raise HomeAssistantError( - f"Client error occurred when calling resource '{request_url}'", translation_domain=DOMAIN, translation_key="client_error", + translation_placeholders={"request_url": request_url}, ) from err # register services diff --git a/homeassistant/components/rest_command/strings.json b/homeassistant/components/rest_command/strings.json index 8a48cddace3..2d658ad8b20 100644 --- a/homeassistant/components/rest_command/strings.json +++ b/homeassistant/components/rest_command/strings.json @@ -7,13 +7,13 @@ }, "exceptions": { "timeout": { - "message": "Timeout while waiting for response from the server" + "message": "Timeout when calling resource '{request_url}'" }, "client_error": { - "message": "An error occurred while requesting the resource" + "message": "Client error occurred when calling resource '{request_url}'" }, "decoding_error": { - "message": "The response from the server could not be decoded as {decoding_type}" + "message": "The response of '{request_url}' could not be decoded as {decoding_type}" } } } diff --git a/homeassistant/components/roborock/device.py b/homeassistant/components/roborock/device.py index 01f26df56f3..7affaa396e6 100644 --- a/homeassistant/components/roborock/device.py +++ b/homeassistant/components/roborock/device.py @@ -58,7 +58,6 @@ class RoborockEntity(Entity): else: command_name = command raise HomeAssistantError( - f"Error while calling {command}", translation_domain=DOMAIN, translation_key="command_failed", translation_placeholders={ diff --git a/homeassistant/components/roborock/image.py b/homeassistant/components/roborock/image.py index f62a0aac1e4..3367f1b3017 100644 --- a/homeassistant/components/roborock/image.py +++ b/homeassistant/components/roborock/image.py @@ -105,7 +105,6 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity): parsed_map = self.parser.parse(map_bytes) if parsed_map.image is None: raise HomeAssistantError( - "Something went wrong creating the map", translation_domain=DOMAIN, translation_key="map_failure", ) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index d952fdb9e56..f7661a3ee80 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -314,14 +314,12 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): """Set new target temperature.""" if "targetTemperature" not in self.device_data.active_features: raise HomeAssistantError( - "Current mode doesn't support setting Target Temperature", translation_domain=DOMAIN, translation_key="no_target_temperature_in_features", ) if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: raise ServiceValidationError( - "No target temperature provided", translation_domain=DOMAIN, translation_key="no_target_temperature", ) @@ -341,13 +339,11 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): """Set new target fan mode.""" if "fanLevel" not in self.device_data.active_features: raise HomeAssistantError( - "Current mode doesn't support setting Fanlevel", translation_domain=DOMAIN, translation_key="no_fan_level_in_features", ) if fan_mode not in AVAILABLE_FAN_MODES: raise HomeAssistantError( - f"Climate fan mode {fan_mode} is not supported by the integration, please open an issue", translation_domain=DOMAIN, translation_key="fan_mode_not_supported", translation_placeholders={"fan_mode": fan_mode}, @@ -393,13 +389,11 @@ class SensiboClimate(SensiboDeviceBaseEntity, ClimateEntity): """Set new target swing operation.""" if "swing" not in self.device_data.active_features: raise HomeAssistantError( - "Current mode doesn't support setting Swing", translation_domain=DOMAIN, translation_key="no_swing_in_features", ) if swing_mode not in AVAILABLE_SWING_MODES: raise HomeAssistantError( - f"Climate swing mode {swing_mode} is not supported by the integration, please open an issue", translation_domain=DOMAIN, translation_key="swing_not_supported", translation_placeholders={"swing_mode": swing_mode}, diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py index 4c22ef8366a..97ef4dffca7 100644 --- a/homeassistant/components/sensibo/entity.py +++ b/homeassistant/components/sensibo/entity.py @@ -34,7 +34,6 @@ def async_handle_api_call( res = await function(entity, *args, **kwargs) except SENSIBO_ERRORS as err: raise HomeAssistantError( - str(err), translation_domain=DOMAIN, translation_key="service_raised", translation_placeholders={"error": str(err), "name": entity.name}, @@ -43,7 +42,6 @@ def async_handle_api_call( LOGGER.debug("Result %s for entity %s with arguments %s", res, entity, kwargs) if res is not True: raise HomeAssistantError( - f"Could not execute service for {entity.name}", translation_domain=DOMAIN, translation_key="service_result_not_true", translation_placeholders={"name": entity.name}, diff --git a/homeassistant/components/sensibo/select.py b/homeassistant/components/sensibo/select.py index f3f8f55fc8e..798d4735b16 100644 --- a/homeassistant/components/sensibo/select.py +++ b/homeassistant/components/sensibo/select.py @@ -100,8 +100,6 @@ class SensiboSelect(SensiboDeviceBaseEntity, SelectEntity): if self.entity_description.key not in self.device_data.active_features: hvac_mode = self.device_data.hvac_mode if self.device_data.hvac_mode else "" raise HomeAssistantError( - f"Current mode {self.device_data.hvac_mode} doesn't support setting" - f" {self.entity_description.name}", translation_domain=DOMAIN, translation_key="select_option_not_available", translation_placeholders={ diff --git a/homeassistant/components/sensibo/switch.py b/homeassistant/components/sensibo/switch.py index b4e3dbf13fd..a8ebd63fa43 100644 --- a/homeassistant/components/sensibo/switch.py +++ b/homeassistant/components/sensibo/switch.py @@ -175,8 +175,6 @@ class SensiboDeviceSwitch(SensiboDeviceBaseEntity, SwitchEntity): """Make service call to api for setting Climate React.""" if self.device_data.smart_type is None: raise HomeAssistantError( - "Use Sensibo Enable Climate React Service once to enable switch or the" - " Sensibo app", translation_domain=DOMAIN, translation_key="climate_react_not_available", ) diff --git a/homeassistant/components/tailwind/cover.py b/homeassistant/components/tailwind/cover.py index f5dba47c37e..f54902dac4a 100644 --- a/homeassistant/components/tailwind/cover.py +++ b/homeassistant/components/tailwind/cover.py @@ -71,19 +71,16 @@ class TailwindDoorCoverEntity(TailwindDoorEntity, CoverEntity): ) except TailwindDoorDisabledError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="door_disabled", ) from exc except TailwindDoorLockedOutError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="door_locked_out", ) from exc except TailwindError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="communication_error", ) from exc @@ -106,19 +103,16 @@ class TailwindDoorCoverEntity(TailwindDoorEntity, CoverEntity): ) except TailwindDoorDisabledError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="door_disabled", ) from exc except TailwindDoorLockedOutError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="door_locked_out", ) from exc except TailwindError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="communication_error", ) from exc diff --git a/homeassistant/components/tailwind/number.py b/homeassistant/components/tailwind/number.py index f05415e34c6..63c01cf7e73 100644 --- a/homeassistant/components/tailwind/number.py +++ b/homeassistant/components/tailwind/number.py @@ -77,7 +77,6 @@ class TailwindNumberEntity(TailwindEntity, NumberEntity): await self.entity_description.set_value_fn(self.coordinator.tailwind, value) except TailwindError as exc: raise HomeAssistantError( - str(exc), translation_domain=DOMAIN, translation_key="communication_error", ) from exc diff --git a/homeassistant/components/tessie/entity.py b/homeassistant/components/tessie/entity.py index 718a7050953..e11a99348ed 100644 --- a/homeassistant/components/tessie/entity.py +++ b/homeassistant/components/tessie/entity.py @@ -69,7 +69,6 @@ class TessieEntity(CoordinatorEntity[TessieStateUpdateCoordinator]): name: str = getattr(self, "name", self.entity_id) reason: str = response.get("reason", "unknown") raise HomeAssistantError( - reason.replace("_", " "), translation_domain=DOMAIN, translation_key=reason.replace(" ", "_"), translation_placeholders={"name": name}, diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py index e4729622a64..7cfa6ffe4b9 100644 --- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py +++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py @@ -83,8 +83,6 @@ class YaleAlarmDevice(YaleAlarmEntity, AlarmControlPanelEntity): ) except YALE_ALL_ERRORS as error: raise HomeAssistantError( - f"Could not set alarm for {self.coordinator.entry.data[CONF_NAME]}:" - f" {error}", translation_domain=DOMAIN, translation_key="set_alarm", translation_placeholders={ @@ -98,7 +96,6 @@ class YaleAlarmDevice(YaleAlarmEntity, AlarmControlPanelEntity): self.async_write_ha_state() return raise HomeAssistantError( - "Could not change alarm, check system ready for arming", translation_domain=DOMAIN, translation_key="could_not_change_alarm", ) diff --git a/homeassistant/components/yale_smart_alarm/lock.py b/homeassistant/components/yale_smart_alarm/lock.py index 8acfecbf034..7a7b3aa4af4 100644 --- a/homeassistant/components/yale_smart_alarm/lock.py +++ b/homeassistant/components/yale_smart_alarm/lock.py @@ -80,7 +80,6 @@ class YaleDoorlock(YaleEntity, LockEntity): ) except YALE_ALL_ERRORS as error: raise HomeAssistantError( - f"Could not set lock for {self.lock_name}: {error}", translation_domain=DOMAIN, translation_key="set_lock", translation_placeholders={ @@ -94,7 +93,6 @@ class YaleDoorlock(YaleEntity, LockEntity): self.async_write_ha_state() return raise HomeAssistantError( - "Could not set lock, check system ready for lock", translation_domain=DOMAIN, translation_key="could_not_change_lock", ) diff --git a/tests/components/rest_command/test_init.py b/tests/components/rest_command/test_init.py index dc71333370a..567391a4b32 100644 --- a/tests/components/rest_command/test_init.py +++ b/tests/components/rest_command/test_init.py @@ -66,11 +66,9 @@ async def test_rest_command_timeout( aioclient_mock.get(TEST_URL, exc=TimeoutError()) - with pytest.raises( - HomeAssistantError, - match=r"^Timeout when calling resource 'https://example.com/'$", - ): + with pytest.raises(HomeAssistantError) as exc: await hass.services.async_call(DOMAIN, "get_test", {}, blocking=True) + assert str(exc.value) == "Timeout when calling resource 'https://example.com/'" assert len(aioclient_mock.mock_calls) == 1 @@ -85,12 +83,13 @@ async def test_rest_command_aiohttp_error( aioclient_mock.get(TEST_URL, exc=aiohttp.ClientError()) - with pytest.raises( - HomeAssistantError, - match=r"^Client error occurred when calling resource 'https://example.com/'$", - ): + with pytest.raises(HomeAssistantError) as exc: await hass.services.async_call(DOMAIN, "get_test", {}, blocking=True) + assert ( + str(exc.value) + == "Client error occurred when calling resource 'https://example.com/'" + ) assert len(aioclient_mock.mock_calls) == 1 @@ -336,13 +335,14 @@ async def test_rest_command_get_response_malformed_json( assert not response # Throws error when requesting response - with pytest.raises( - HomeAssistantError, - match=r"^Response of 'https://example.com/' could not be decoded as JSON$", - ): + with pytest.raises(HomeAssistantError) as exc: await hass.services.async_call( DOMAIN, "get_test", {}, blocking=True, return_response=True ) + assert ( + str(exc.value) + == "The response of 'https://example.com/' could not be decoded as JSON" + ) async def test_rest_command_get_response_none( @@ -369,12 +369,13 @@ async def test_rest_command_get_response_none( assert not response # Throws Decode error when requesting response - with pytest.raises( - HomeAssistantError, - match=r"^Response of 'https://example.com/' could not be decoded as text$", - ): + with pytest.raises(HomeAssistantError) as exc: response = await hass.services.async_call( DOMAIN, "get_test", {}, blocking=True, return_response=True ) + assert ( + str(exc.value) + == "The response of 'https://example.com/' could not be decoded as text" + ) assert not response diff --git a/tests/components/tailwind/test_cover.py b/tests/components/tailwind/test_cover.py index 5e77ed19af3..8ccb8947624 100644 --- a/tests/components/tailwind/test_cover.py +++ b/tests/components/tailwind/test_cover.py @@ -86,7 +86,7 @@ async def test_cover_operations( # Test door disabled error handling mock_tailwind.operate.side_effect = TailwindDoorDisabledError("Door disabled") - with pytest.raises(HomeAssistantError, match="Door disabled") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( COVER_DOMAIN, SERVICE_OPEN_COVER, @@ -95,11 +95,12 @@ async def test_cover_operations( }, blocking=True, ) + assert str(excinfo.value) == "The door is disabled and cannot be operated" assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "door_disabled" - with pytest.raises(HomeAssistantError, match="Door disabled") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( COVER_DOMAIN, SERVICE_CLOSE_COVER, @@ -109,13 +110,14 @@ async def test_cover_operations( blocking=True, ) + assert str(excinfo.value) == "The door is disabled and cannot be operated" assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "door_disabled" # Test door locked out error handling mock_tailwind.operate.side_effect = TailwindDoorLockedOutError("Door locked out") - with pytest.raises(HomeAssistantError, match="Door locked out") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( COVER_DOMAIN, SERVICE_OPEN_COVER, @@ -125,10 +127,11 @@ async def test_cover_operations( blocking=True, ) + assert str(excinfo.value) == "The door is locked out and cannot be operated" assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "door_locked_out" - with pytest.raises(HomeAssistantError, match="Door locked out") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( COVER_DOMAIN, SERVICE_CLOSE_COVER, @@ -138,13 +141,14 @@ async def test_cover_operations( blocking=True, ) + assert str(excinfo.value) == "The door is locked out and cannot be operated" assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "door_locked_out" # Test door error handling mock_tailwind.operate.side_effect = TailwindError("Some error") - with pytest.raises(HomeAssistantError, match="Some error") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( COVER_DOMAIN, SERVICE_OPEN_COVER, @@ -154,10 +158,14 @@ async def test_cover_operations( blocking=True, ) + assert ( + str(excinfo.value) + == "An error occurred while communicating with the Tailwind device" + ) assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "communication_error" - with pytest.raises(HomeAssistantError, match="Some error") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( COVER_DOMAIN, SERVICE_CLOSE_COVER, @@ -167,5 +175,9 @@ async def test_cover_operations( blocking=True, ) + assert ( + str(excinfo.value) + == "An error occurred while communicating with the Tailwind device" + ) assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "communication_error" diff --git a/tests/components/tailwind/test_number.py b/tests/components/tailwind/test_number.py index 84d22eebd85..4e6736fc831 100644 --- a/tests/components/tailwind/test_number.py +++ b/tests/components/tailwind/test_number.py @@ -52,7 +52,7 @@ async def test_number_entities( # Test error handling mock_tailwind.status_led.side_effect = TailwindError("Some error") - with pytest.raises(HomeAssistantError, match="Some error") as excinfo: + with pytest.raises(HomeAssistantError) as excinfo: await hass.services.async_call( number.DOMAIN, SERVICE_SET_VALUE, @@ -63,5 +63,9 @@ async def test_number_entities( blocking=True, ) + assert ( + str(excinfo.value) + == "An error occurred while communicating with the Tailwind device" + ) assert excinfo.value.translation_domain == DOMAIN assert excinfo.value.translation_key == "communication_error" From 3dc8df2403aec7129b4841a380b1b058da20506c Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 18 Mar 2024 14:42:21 +0100 Subject: [PATCH 1234/1691] Get ServiceValidationError message from translation cache only (#113704) * Get ServiceValidationError message from translation cache only * Remove message for NotValidPresetModeError --- homeassistant/components/bsblan/climate.py | 1 - homeassistant/components/easyenergy/services.py | 2 -- homeassistant/components/ecovacs/vacuum.py | 3 --- homeassistant/components/energyzero/services.py | 3 --- homeassistant/components/fan/__init__.py | 2 -- homeassistant/components/homeassistant/scene.py | 2 -- homeassistant/components/lock/__init__.py | 1 - homeassistant/components/mqtt/__init__.py | 3 --- homeassistant/components/select/__init__.py | 1 - homeassistant/components/smtp/notify.py | 4 ---- homeassistant/components/tessie/lock.py | 1 - homeassistant/components/todo/__init__.py | 3 --- homeassistant/components/water_heater/__init__.py | 5 ----- homeassistant/components/yolink/services.py | 1 - tests/components/energyzero/test_services.py | 9 ++++++--- tests/components/mqtt/test_init.py | 4 ++-- tests/components/todo/test_init.py | 10 ++++++---- tests/components/water_heater/test_init.py | 4 ++-- 18 files changed, 16 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/bsblan/climate.py b/homeassistant/components/bsblan/climate.py index eaa412978c0..1b300e1e738 100644 --- a/homeassistant/components/bsblan/climate.py +++ b/homeassistant/components/bsblan/climate.py @@ -150,7 +150,6 @@ class BSBLANClimate( await self.async_set_data(preset_mode=preset_mode) else: raise ServiceValidationError( - "Can't set preset mode when hvac mode is not auto", translation_domain=DOMAIN, translation_key="set_preset_mode_error", translation_placeholders={"preset_mode": preset_mode}, diff --git a/homeassistant/components/easyenergy/services.py b/homeassistant/components/easyenergy/services.py index 402f3195dc7..5b80cfafd08 100644 --- a/homeassistant/components/easyenergy/services.py +++ b/homeassistant/components/easyenergy/services.py @@ -95,7 +95,6 @@ def __get_coordinator( if not entry: raise ServiceValidationError( - f"Invalid config entry: {entry_id}", translation_domain=DOMAIN, translation_key="invalid_config_entry", translation_placeholders={ @@ -104,7 +103,6 @@ def __get_coordinator( ) if entry.state != ConfigEntryState.LOADED: raise ServiceValidationError( - f"{entry.title} is not loaded", translation_domain=DOMAIN, translation_key="unloaded_config_entry", translation_placeholders={ diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index b70f6a6344e..d5016ab683d 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -337,7 +337,6 @@ class EcovacsVacuum( params = {} elif isinstance(params, list): raise ServiceValidationError( - "Params must be a dict!", translation_domain=DOMAIN, translation_key="vacuum_send_command_params_dict", ) @@ -345,7 +344,6 @@ class EcovacsVacuum( if command in ["spot_area", "custom_area"]: if params is None: raise ServiceValidationError( - f"Params are required for {command}!", translation_domain=DOMAIN, translation_key="vacuum_send_command_params_required", translation_placeholders={"command": command}, @@ -354,7 +352,6 @@ class EcovacsVacuum( info = self._device.device_info name = info.get("nick", info["name"]) raise ServiceValidationError( - f"Vacuum {name} does not support area capability!", translation_domain=DOMAIN, translation_key="vacuum_send_command_area_not_supported", translation_placeholders={"name": name}, diff --git a/homeassistant/components/energyzero/services.py b/homeassistant/components/energyzero/services.py index d04912ca99e..d98699c5c08 100644 --- a/homeassistant/components/energyzero/services.py +++ b/homeassistant/components/energyzero/services.py @@ -62,7 +62,6 @@ def __get_date(date_input: str | None) -> date | datetime: return value raise ServiceValidationError( - "Invalid datetime provided.", translation_domain=DOMAIN, translation_key="invalid_date", translation_placeholders={ @@ -93,7 +92,6 @@ def __get_coordinator( if not entry: raise ServiceValidationError( - f"Invalid config entry: {entry_id}", translation_domain=DOMAIN, translation_key="invalid_config_entry", translation_placeholders={ @@ -102,7 +100,6 @@ def __get_coordinator( ) if entry.state != ConfigEntryState.LOADED: raise ServiceValidationError( - f"{entry.title} is not loaded", translation_domain=DOMAIN, translation_key="unloaded_config_entry", translation_placeholders={ diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index 185b9631198..b62f207bde8 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -297,8 +297,6 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if not preset_modes or preset_mode not in preset_modes: preset_modes_str: str = ", ".join(preset_modes or []) raise NotValidPresetModeError( - f"The preset_mode {preset_mode} is not a valid preset_mode:" - f" {preset_modes}", translation_placeholders={ "preset_mode": preset_mode, "preset_modes": preset_modes_str, diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 9d03b35e079..1c4fee23198 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -282,7 +282,6 @@ async def async_setup_platform( scene = platform.entities.get(entity_id) if scene is None: raise ServiceValidationError( - f"{entity_id} is not a valid scene entity_id", translation_domain=SCENE_DOMAIN, translation_key="entity_not_scene", translation_placeholders={ @@ -292,7 +291,6 @@ async def async_setup_platform( assert isinstance(scene, HomeAssistantScene) if not scene.from_service: raise ServiceValidationError( - f"The scene {entity_id} is not created with service `scene.create`", translation_domain=SCENE_DOMAIN, translation_key="entity_not_dynamically_created", translation_placeholders={ diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index e8b7a013523..b2cd28324cb 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -156,7 +156,6 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if TYPE_CHECKING: assert self.code_format raise ServiceValidationError( - f"The code for {self.entity_id} doesn't match pattern {self.code_format}", translation_domain=DOMAIN, translation_key="add_default_code", translation_placeholders={ diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 3b34ef79291..8e866776a41 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -331,8 +331,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except vol.Invalid as err: err_str = str(err) raise ServiceValidationError( - f"Unable to publish: topic template '{msg_topic_template}' produced an " - f"invalid topic '{rendered_topic}' after rendering ({err_str})", translation_domain=DOMAIN, translation_key="invalid_publish_topic", translation_placeholders={ @@ -405,7 +403,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) except ConfigValidationError as ex: raise ServiceValidationError( - str(ex), translation_domain=ex.translation_domain, translation_key=ex.translation_key, translation_placeholders=ex.translation_placeholders, diff --git a/homeassistant/components/select/__init__.py b/homeassistant/components/select/__init__.py index 1594b9e2195..0c54dfc0aac 100644 --- a/homeassistant/components/select/__init__.py +++ b/homeassistant/components/select/__init__.py @@ -179,7 +179,6 @@ class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if not options or option not in options: friendly_options: str = ", ".join(options or []) raise ServiceValidationError( - f"Option {option} is not valid for {self.entity_id}", translation_domain=DOMAIN, translation_key="not_valid_option", translation_placeholders={ diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py index a82675a2f07..68be378f367 100644 --- a/homeassistant/components/smtp/notify.py +++ b/homeassistant/components/smtp/notify.py @@ -264,10 +264,6 @@ def _attach_file(hass, atch_name, content_id=""): file_name = os.path.basename(atch_name) url = "https://www.home-assistant.io/docs/configuration/basic/" raise ServiceValidationError( - f"Cannot send email with attachment '{file_name}' " - f"from directory '{file_path}' which is not secure to load data from. " - f"Only folders added to `{allow_list}` are accessible. " - f"See {url} for more information.", translation_domain=DOMAIN, translation_key="remote_path_not_allowed", translation_placeholders={ diff --git a/homeassistant/components/tessie/lock.py b/homeassistant/components/tessie/lock.py index ccd613af2e3..09402055ee8 100644 --- a/homeassistant/components/tessie/lock.py +++ b/homeassistant/components/tessie/lock.py @@ -112,7 +112,6 @@ class TessieCableLockEntity(TessieEntity, LockEntity): async def async_lock(self, **kwargs: Any) -> None: """Charge cable Lock cannot be manually locked.""" raise ServiceValidationError( - "Insert cable to lock", translation_domain=DOMAIN, translation_key="no_cable", ) diff --git a/homeassistant/components/todo/__init__.py b/homeassistant/components/todo/__init__.py index 0b8dd756289..74ee99b811f 100644 --- a/homeassistant/components/todo/__init__.py +++ b/homeassistant/components/todo/__init__.py @@ -107,7 +107,6 @@ def _validate_supported_features( continue if not supported_features or not supported_features & desc.required_feature: raise ServiceValidationError( - f"Entity does not support setting field '{desc.service_field}'", translation_domain=DOMAIN, translation_key="update_field_not_supported", translation_placeholders={"service_field": desc.service_field}, @@ -485,7 +484,6 @@ async def _async_update_todo_item(entity: TodoListEntity, call: ServiceCall) -> found = _find_by_uid_or_summary(item, entity.todo_items) if not found: raise ServiceValidationError( - f"Unable to find To-do item '{item}'", translation_domain=DOMAIN, translation_key="item_not_found", translation_placeholders={"item": item}, @@ -518,7 +516,6 @@ async def _async_remove_todo_items(entity: TodoListEntity, call: ServiceCall) -> found = _find_by_uid_or_summary(item, entity.todo_items) if not found or not found.uid: raise ServiceValidationError( - f"Unable to find To-do item '{item}'", translation_domain=DOMAIN, translation_key="item_not_found", translation_placeholders={"item": item}, diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 370bb5c293c..167acb85914 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -368,8 +368,6 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """Handle a set target operation mode service call.""" if self.operation_list is None: raise ServiceValidationError( - f"Operation mode {operation_mode} not valid for " - f"entity {self.entity_id}. The operation list is not defined", translation_domain=DOMAIN, translation_key="operation_list_not_defined", translation_placeholders={ @@ -380,9 +378,6 @@ class WaterHeaterEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if operation_mode not in self.operation_list: operation_list = ", ".join(self.operation_list) raise ServiceValidationError( - f"Operation mode {operation_mode} not valid for " - f"entity {self.entity_id}. Valid " - f"operation modes are: {operation_list}", translation_domain=DOMAIN, translation_key="not_valid_operation_mode", translation_placeholders={ diff --git a/homeassistant/components/yolink/services.py b/homeassistant/components/yolink/services.py index e41e3dce260..a011d493dc9 100644 --- a/homeassistant/components/yolink/services.py +++ b/homeassistant/components/yolink/services.py @@ -36,7 +36,6 @@ def async_register_services(hass: HomeAssistant) -> None: break if entry is None or entry.state == ConfigEntryState.NOT_LOADED: raise ServiceValidationError( - "Config entry not found or not loaded!", translation_domain=DOMAIN, translation_key="invalid_config_entry", ) diff --git a/tests/components/energyzero/test_services.py b/tests/components/energyzero/test_services.py index c0b54729e03..38929d7007a 100644 --- a/tests/components/energyzero/test_services.py +++ b/tests/components/energyzero/test_services.py @@ -1,5 +1,7 @@ """Tests for the services provided by the EnergyZero integration.""" +import re + import pytest from syrupy.assertion import SnapshotAssertion import voluptuous as vol @@ -101,7 +103,7 @@ def config_entry_data( "start": "incorrect date", }, ServiceValidationError, - "Invalid datetime provided.", + "Invalid date provided. Got incorrect date", ), ( {"config_entry": True}, @@ -110,7 +112,7 @@ def config_entry_data( "end": "incorrect date", }, ServiceValidationError, - "Invalid datetime provided.", + "Invalid date provided. Got incorrect date", ), ], indirect=["config_entry_data"], @@ -125,7 +127,7 @@ async def test_service_validation( ) -> None: """Test the EnergyZero Service validation.""" - with pytest.raises(error, match=error_message): + with pytest.raises(error) as exc: await hass.services.async_call( DOMAIN, service, @@ -133,6 +135,7 @@ async def test_service_validation( blocking=True, return_response=True, ) + assert re.match(error_message, str(exc.value)) @pytest.mark.usefixtures("init_integration") diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 0c3764ce8a8..3459e6fc058 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -550,8 +550,8 @@ async def test_service_call_with_template_topic_renders_invalid_topic( blocking=True, ) assert str(exc.value) == ( - "Unable to publish: topic template 'test/{{ '+' if True else 'topic' }}/topic' " - "produced an invalid topic 'test/+/topic' after rendering " + "Unable to publish: topic template `test/{{ '+' if True else 'topic' }}/topic` " + "produced an invalid topic `test/+/topic` after rendering " "(Wildcards cannot be used in topic names)" ) assert not mqtt_mock.async_publish.called diff --git a/tests/components/todo/test_init.py b/tests/components/todo/test_init.py index 5a8f6183cbb..2b8bb858177 100644 --- a/tests/components/todo/test_init.py +++ b/tests/components/todo/test_init.py @@ -348,12 +348,12 @@ async def test_add_item_service_raises( ( {"item": "Submit forms", "description": "Submit tax forms"}, ServiceValidationError, - "does not support setting field 'description'", + "does not support setting field: description", ), ( {"item": "Submit forms", "due_date": "2023-11-17"}, ServiceValidationError, - "does not support setting field 'due_date'", + "does not support setting field: due_date", ), ( { @@ -361,7 +361,7 @@ async def test_add_item_service_raises( "due_datetime": f"2023-11-17T17:00:00{TEST_OFFSET}", }, ServiceValidationError, - "does not support setting field 'due_datetime'", + "does not support setting field: due_datetime", ), ], ) @@ -376,7 +376,7 @@ async def test_add_item_service_invalid_input( await create_mock_platform(hass, [test_entity]) - with pytest.raises(expected_exception, match=expected_error): + with pytest.raises(expected_exception) as exc: await hass.services.async_call( DOMAIN, "add_item", @@ -385,6 +385,8 @@ async def test_add_item_service_invalid_input( blocking=True, ) + assert expected_error in str(exc.value) + @pytest.mark.parametrize( ("supported_entity_feature", "item_data", "expected_item"), diff --git a/tests/components/water_heater/test_init.py b/tests/components/water_heater/test_init.py index f6f48e65480..f883cf47b19 100644 --- a/tests/components/water_heater/test_init.py +++ b/tests/components/water_heater/test_init.py @@ -175,7 +175,7 @@ async def test_operation_mode_validation( DOMAIN, SERVICE_SET_OPERATION_MODE, data, blocking=True ) assert ( - str(exc.value) == "Operation mode test not valid for entity water_heater.test. " + str(exc.value) == "Operation mode test is not valid for water_heater.test. " "The operation list is not defined" ) assert exc.value.translation_domain == DOMAIN @@ -191,7 +191,7 @@ async def test_operation_mode_validation( DOMAIN, SERVICE_SET_OPERATION_MODE, data, blocking=True ) assert ( - str(exc.value) == "Operation mode test not valid for entity water_heater.test. " + str(exc.value) == "Operation mode test is not valid for water_heater.test. " "Valid operation modes are: gas, eco" ) assert exc.value.translation_domain == DOMAIN From 40ce2011beaac02e345390ece9556d148e1141b7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 04:16:20 -1000 Subject: [PATCH 1235/1691] Run dhcp listeners with run_immediately (#113726) Neither of these need a call_soon --- homeassistant/components/dhcp/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 5325283eb0a..050bc7a74b2 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -157,9 +157,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: for watcher in watchers: watcher.async_stop() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_stop, run_immediately=True + ) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_initialize) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, _async_initialize, run_immediately=True + ) return True From 7dc64a03ef234b6952b5e5d49f3935612fa213bb Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Mon, 18 Mar 2024 10:34:08 -0400 Subject: [PATCH 1236/1691] Bump apprise to 1.7.4 (#113629) Co-authored-by: jan iversen --- homeassistant/components/apprise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index dd630ccc872..0c0e816f088 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/apprise", "iot_class": "cloud_push", "loggers": ["apprise"], - "requirements": ["apprise==1.7.2"] + "requirements": ["apprise==1.7.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index 730c3d2d327..e58c319fb19 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -455,7 +455,7 @@ anthemav==1.4.1 apple_weatherkit==1.1.2 # homeassistant.components.apprise -apprise==1.7.2 +apprise==1.7.4 # homeassistant.components.aprs aprslib==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 694e3fc7d08..a33b277cc9d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -419,7 +419,7 @@ anthemav==1.4.1 apple_weatherkit==1.1.2 # homeassistant.components.apprise -apprise==1.7.2 +apprise==1.7.4 # homeassistant.components.aprs aprslib==0.7.0 From e882d47cded0d2fde27c9547b993ab09e0743df5 Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Mon, 18 Mar 2024 16:16:24 +0100 Subject: [PATCH 1237/1691] Add Downloader config flow, including tests (#98722) * Adding base line, including tests * Adding validatge input and expanding tests * Updating manifest * Minor patch * Revert minor patch, wrong nesting * Adding proper translations * Including abort message * Update homeassistant/components/downloader/config_flow.py Co-authored-by: Erik Montnemery * Rename exception class * Refactor import * Update strings * Apply suggestions from code review * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Update homeassistant/components/downloader/__init__.py Co-authored-by: Erik Montnemery * Reverting back filename and fix typing * Reverting back mutex/lock * Upgrade version * Adding typing * Removing coroutine * Removing unload entry (for now) * Removing comment * Change type * Putting download back in setup_entry * Revert back code --------- Co-authored-by: Erik Montnemery --- .coveragerc | 2 +- CODEOWNERS | 2 + .../components/downloader/__init__.py | 92 ++++++++++++------ .../components/downloader/config_flow.py | 69 ++++++++++++++ homeassistant/components/downloader/const.py | 19 ++++ .../components/downloader/manifest.json | 3 +- .../components/downloader/strings.json | 23 +++++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 2 +- tests/components/downloader/__init__.py | 1 + .../components/downloader/test_config_flow.py | 94 +++++++++++++++++++ 11 files changed, 277 insertions(+), 31 deletions(-) create mode 100644 homeassistant/components/downloader/config_flow.py create mode 100644 homeassistant/components/downloader/const.py create mode 100644 tests/components/downloader/__init__.py create mode 100644 tests/components/downloader/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index adf3c7b6eeb..80412c14a65 100644 --- a/.coveragerc +++ b/.coveragerc @@ -250,7 +250,7 @@ omit = homeassistant/components/dormakaba_dkey/lock.py homeassistant/components/dormakaba_dkey/sensor.py homeassistant/components/dovado/* - homeassistant/components/downloader/* + homeassistant/components/downloader/__init__.py homeassistant/components/dsmr_reader/__init__.py homeassistant/components/dsmr_reader/definitions.py homeassistant/components/dsmr_reader/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index abeddd439ab..440ddd45cfa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -309,6 +309,8 @@ build.json @home-assistant/supervisor /tests/components/doorbird/ @oblogic7 @bdraco @flacjacket /homeassistant/components/dormakaba_dkey/ @emontnemery /tests/components/dormakaba_dkey/ @emontnemery +/homeassistant/components/downloader/ @erwindouna +/tests/components/downloader/ @erwindouna /homeassistant/components/dremel_3d_printer/ @tkdrob /tests/components/dremel_3d_printer/ @tkdrob /homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer diff --git a/homeassistant/components/downloader/__init__.py b/homeassistant/components/downloader/__init__.py index 5d7f1594be0..94d243e2cf2 100644 --- a/homeassistant/components/downloader/__init__.py +++ b/homeassistant/components/downloader/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations from http import HTTPStatus -import logging import os import re import threading @@ -11,33 +10,26 @@ import threading import requests import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.data_entry_flow import FlowResultType import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.service import async_register_admin_service from homeassistant.helpers.typing import ConfigType from homeassistant.util import raise_if_invalid_filename, raise_if_invalid_path -_LOGGER = logging.getLogger(__name__) - -ATTR_FILENAME = "filename" -ATTR_SUBDIR = "subdir" -ATTR_URL = "url" -ATTR_OVERWRITE = "overwrite" - -CONF_DOWNLOAD_DIR = "download_dir" - -DOMAIN = "downloader" -DOWNLOAD_FAILED_EVENT = "download_failed" -DOWNLOAD_COMPLETED_EVENT = "download_completed" - -SERVICE_DOWNLOAD_FILE = "download_file" - -SERVICE_DOWNLOAD_FILE_SCHEMA = vol.Schema( - { - vol.Required(ATTR_URL): cv.url, - vol.Optional(ATTR_SUBDIR): cv.string, - vol.Optional(ATTR_FILENAME): cv.string, - vol.Optional(ATTR_OVERWRITE, default=False): cv.boolean, - } +from .const import ( + _LOGGER, + ATTR_FILENAME, + ATTR_OVERWRITE, + ATTR_SUBDIR, + ATTR_URL, + CONF_DOWNLOAD_DIR, + DOMAIN, + DOWNLOAD_COMPLETED_EVENT, + DOWNLOAD_FAILED_EVENT, + SERVICE_DOWNLOAD_FILE, ) CONFIG_SCHEMA = vol.Schema( @@ -46,9 +38,46 @@ CONFIG_SCHEMA = vol.Schema( ) -def setup(hass: HomeAssistant, config: ConfigType) -> bool: +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Downloader component, via the YAML file.""" + if DOMAIN not in config: + return True + + import_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_DOWNLOAD_DIR: config[DOMAIN][CONF_DOWNLOAD_DIR], + }, + ) + + translation_key = "deprecated_yaml" + if ( + import_result["type"] == FlowResultType.ABORT + and import_result["reason"] == "import_failed" + ): + translation_key = "import_failed" + + async_create_issue( + hass, + DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2024.9.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key=translation_key, + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Downloader", + }, + ) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Listen for download events to download files.""" - download_path = config[DOMAIN][CONF_DOWNLOAD_DIR] + download_path = entry.data[CONF_DOWNLOAD_DIR] # If path is relative, we assume relative to Home Assistant config dir if not os.path.isabs(download_path): @@ -58,7 +87,6 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.error( "Download path %s does not exist. File Downloader not active", download_path ) - return False def download_file(service: ServiceCall) -> None: @@ -169,11 +197,19 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: threading.Thread(target=do_download).start() - hass.services.register( + async_register_admin_service( + hass, DOMAIN, SERVICE_DOWNLOAD_FILE, download_file, - schema=SERVICE_DOWNLOAD_FILE_SCHEMA, + schema=vol.Schema( + { + vol.Optional(ATTR_FILENAME): cv.string, + vol.Optional(ATTR_SUBDIR): cv.string, + vol.Required(ATTR_URL): cv.url, + vol.Optional(ATTR_OVERWRITE, default=False): cv.boolean, + } + ), ) return True diff --git a/homeassistant/components/downloader/config_flow.py b/homeassistant/components/downloader/config_flow.py new file mode 100644 index 00000000000..70557d30e19 --- /dev/null +++ b/homeassistant/components/downloader/config_flow.py @@ -0,0 +1,69 @@ +"""Config flow for Downloader integration.""" +from __future__ import annotations + +import os +from typing import Any + +import voluptuous as vol + +from homeassistant import exceptions +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.helpers import config_validation as cv + +from .const import _LOGGER, CONF_DOWNLOAD_DIR, DEFAULT_NAME, DOMAIN + + +class DownloaderConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Downloader.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the initial step.""" + errors: dict[str, str] = {} + + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + if user_input is not None: + try: + await self._validate_input(user_input) + except DirectoryDoesNotExist: + errors["base"] = "cannot_connect" + else: + return self.async_create_entry(title=DEFAULT_NAME, data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_DOWNLOAD_DIR): cv.string, + } + ), + errors=errors, + ) + + async def async_step_import( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a flow initiated by configuration file.""" + + return await self.async_step_user(user_input) + + async def _validate_input(self, user_input: dict[str, Any]) -> None: + """Validate the user input if the directory exists.""" + if not os.path.isabs(user_input[CONF_DOWNLOAD_DIR]): + download_path = self.hass.config.path(user_input[CONF_DOWNLOAD_DIR]) + + if not os.path.isdir(download_path): + _LOGGER.error( + "Download path %s does not exist. File Downloader not active", + download_path, + ) + raise DirectoryDoesNotExist + + +class DirectoryDoesNotExist(exceptions.HomeAssistantError): + """Error to indicate the specified download directory does not exist.""" diff --git a/homeassistant/components/downloader/const.py b/homeassistant/components/downloader/const.py new file mode 100644 index 00000000000..047dff2b0cc --- /dev/null +++ b/homeassistant/components/downloader/const.py @@ -0,0 +1,19 @@ +"""Constants for the Downloader component.""" +import logging + +_LOGGER = logging.getLogger(__package__) + +DOMAIN = "downloader" +DEFAULT_NAME = "Downloader" +CONF_DOWNLOAD_DIR = "download_dir" +ATTR_FILENAME = "filename" +ATTR_SUBDIR = "subdir" +ATTR_URL = "url" +ATTR_OVERWRITE = "overwrite" + +CONF_DOWNLOAD_DIR = "download_dir" + +DOWNLOAD_FAILED_EVENT = "download_failed" +DOWNLOAD_COMPLETED_EVENT = "download_completed" + +SERVICE_DOWNLOAD_FILE = "download_file" diff --git a/homeassistant/components/downloader/manifest.json b/homeassistant/components/downloader/manifest.json index 5e4f0f5fde9..876404be889 100644 --- a/homeassistant/components/downloader/manifest.json +++ b/homeassistant/components/downloader/manifest.json @@ -1,7 +1,8 @@ { "domain": "downloader", "name": "Downloader", - "codeowners": [], + "codeowners": ["@erwindouna"], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/downloader", "quality_scale": "internal" } diff --git a/homeassistant/components/downloader/strings.json b/homeassistant/components/downloader/strings.json index c81b9f0ea39..77dd0abd9d3 100644 --- a/homeassistant/components/downloader/strings.json +++ b/homeassistant/components/downloader/strings.json @@ -1,4 +1,17 @@ { + "config": { + "step": { + "user": { + "description": "Select a location to get to store downloads. The setup will check if the directory exists." + } + }, + "error": { + "cannot_connect": "The directory could not be reached. Please check your settings." + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + }, "services": { "download_file": { "name": "Download file", @@ -22,5 +35,15 @@ } } } + }, + "issues": { + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour configuration is already imported.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + }, + "import_failed": { + "title": "The {integration_title} failed to import", + "description": "The {integration_title} integration failed to import.\n\nPlease check the logs for more details." + } } } diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 957a7696055..638f9497db9 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -118,6 +118,7 @@ FLOWS = { "dnsip", "doorbird", "dormakaba_dkey", + "downloader", "dremel_3d_printer", "drop_connect", "dsmr", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 4ce6ce6283c..424861e0f58 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -1298,7 +1298,7 @@ "downloader": { "name": "Downloader", "integration_type": "hub", - "config_flow": false + "config_flow": true }, "dremel_3d_printer": { "name": "Dremel 3D Printer", diff --git a/tests/components/downloader/__init__.py b/tests/components/downloader/__init__.py new file mode 100644 index 00000000000..abf11631bb9 --- /dev/null +++ b/tests/components/downloader/__init__.py @@ -0,0 +1 @@ +"""Tests for the downloader component.""" diff --git a/tests/components/downloader/test_config_flow.py b/tests/components/downloader/test_config_flow.py new file mode 100644 index 00000000000..597ecb333bf --- /dev/null +++ b/tests/components/downloader/test_config_flow.py @@ -0,0 +1,94 @@ +"""Test the Downloader config flow.""" +from unittest.mock import patch + +import pytest + +from homeassistant import config_entries +from homeassistant.components.downloader.config_flow import DirectoryDoesNotExist +from homeassistant.components.downloader.const import CONF_DOWNLOAD_DIR, DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + +CONFIG = {CONF_DOWNLOAD_DIR: "download_dir"} + + +async def test_user_form(hass: HomeAssistant) -> None: + """Test the full user configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + with patch( + "homeassistant.components.downloader.async_setup_entry", + return_value=True, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=CONFIG, + ) + assert result["type"] == FlowResultType.FORM + + with patch( + "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", + side_effect=DirectoryDoesNotExist, + ): + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "cannot_connect"} + + with patch( + "homeassistant.components.downloader.async_setup_entry", return_value=True + ), patch( + "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", + return_value=None, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input=CONFIG, + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Downloader" + assert result["data"] == {"download_dir": "download_dir"} + + +@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT]) +async def test_single_instance_allowed( + hass: HomeAssistant, + source: str, +) -> None: + """Test we abort if already setup.""" + mock_config_entry = MockConfigEntry(domain=DOMAIN) + + mock_config_entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": source} + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "single_instance_allowed" + + +async def test_import_flow_success(hass: HomeAssistant) -> None: + """Test import flow.""" + with patch( + "homeassistant.components.downloader.async_setup_entry", return_value=True + ), patch( + "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", + return_value=None, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={}, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Downloader" + assert result["data"] == {} + assert result["options"] == {} From 34b0ff40f3d438dc5788ba214a0a7e956e3050de Mon Sep 17 00:00:00 2001 From: Jack Boswell Date: Tue, 19 Mar 2024 04:18:32 +1300 Subject: [PATCH 1238/1691] Allow configuring Starlink sleep schedule (#103057) * Expose sleep config getters and setters * Add a switch for toggling sleep schedule * Add Time platform * Add frozen to dataclasses * Update tests * Add starlink time to coveragerc * No more mixin * Update time.py * Update time.py * Run data collectors asynchronously * Fix timezone handling --- .coveragerc | 1 + homeassistant/components/starlink/__init__.py | 1 + .../components/starlink/coordinator.py | 63 ++++++++++-- .../components/starlink/strings.json | 11 +++ homeassistant/components/starlink/switch.py | 14 ++- homeassistant/components/starlink/time.py | 98 +++++++++++++++++++ .../starlink/fixtures/sleep_data_success.json | 1 + tests/components/starlink/patchers.py | 5 + .../starlink/snapshots/test_diagnostics.ambr | 5 + tests/components/starlink/test_diagnostics.py | 8 +- tests/components/starlink/test_init.py | 10 +- 11 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 homeassistant/components/starlink/time.py create mode 100644 tests/components/starlink/fixtures/sleep_data_success.json diff --git a/.coveragerc b/.coveragerc index 80412c14a65..e2d8b9b5cef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1284,6 +1284,7 @@ omit = homeassistant/components/starlink/device_tracker.py homeassistant/components/starlink/sensor.py homeassistant/components/starlink/switch.py + homeassistant/components/starlink/time.py homeassistant/components/starline/__init__.py homeassistant/components/starline/account.py homeassistant/components/starline/binary_sensor.py diff --git a/homeassistant/components/starlink/__init__.py b/homeassistant/components/starlink/__init__.py index fc6863e7935..17081a7491e 100644 --- a/homeassistant/components/starlink/__init__.py +++ b/homeassistant/components/starlink/__init__.py @@ -15,6 +15,7 @@ PLATFORMS = [ Platform.DEVICE_TRACKER, Platform.SENSOR, Platform.SWITCH, + Platform.TIME, ] diff --git a/homeassistant/components/starlink/coordinator.py b/homeassistant/components/starlink/coordinator.py index 069c9969de7..9c597fbb033 100644 --- a/homeassistant/components/starlink/coordinator.py +++ b/homeassistant/components/starlink/coordinator.py @@ -6,6 +6,7 @@ import asyncio from dataclasses import dataclass from datetime import timedelta import logging +from zoneinfo import ZoneInfo from starlink_grpc import ( AlertDict, @@ -14,8 +15,10 @@ from starlink_grpc import ( LocationDict, ObstructionDict, StatusDict, + get_sleep_config, location_data, reboot, + set_sleep_config, set_stow_state, status_data, ) @@ -32,6 +35,7 @@ class StarlinkData: """Contains data pulled from the Starlink system.""" location: LocationDict + sleep: tuple[int, int, bool] status: StatusDict obstruction: ObstructionDict alert: AlertDict @@ -43,7 +47,7 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]): def __init__(self, hass: HomeAssistant, name: str, url: str) -> None: """Initialize an UpdateCoordinator for a group of sensors.""" self.channel_context = ChannelContext(target=url) - + self.timezone = ZoneInfo(hass.config.time_zone) super().__init__( hass, _LOGGER, @@ -54,13 +58,16 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]): async def _async_update_data(self) -> StarlinkData: async with asyncio.timeout(4): try: - status = await self.hass.async_add_executor_job( - status_data, self.channel_context + status, location, sleep = await asyncio.gather( + self.hass.async_add_executor_job(status_data, self.channel_context), + self.hass.async_add_executor_job( + location_data, self.channel_context + ), + self.hass.async_add_executor_job( + get_sleep_config, self.channel_context + ), ) - location = await self.hass.async_add_executor_job( - location_data, self.channel_context - ) - return StarlinkData(location, *status) + return StarlinkData(location, sleep, *status) except GrpcError as exc: raise UpdateFailed from exc @@ -81,3 +88,45 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]): await self.hass.async_add_executor_job(reboot, self.channel_context) except GrpcError as exc: raise HomeAssistantError from exc + + async def async_set_sleep_schedule_enabled(self, sleep_schedule: bool) -> None: + """Set whether Starlink system uses the configured sleep schedule.""" + async with asyncio.timeout(4): + try: + await self.hass.async_add_executor_job( + set_sleep_config, + self.data.sleep[0], + self.data.sleep[1], + sleep_schedule, + self.channel_context, + ) + except GrpcError as exc: + raise HomeAssistantError from exc + + async def async_set_sleep_start(self, start: int) -> None: + """Set Starlink system sleep schedule start time.""" + async with asyncio.timeout(4): + try: + await self.hass.async_add_executor_job( + set_sleep_config, + start, + self.data.sleep[1], + self.data.sleep[2], + self.channel_context, + ) + except GrpcError as exc: + raise HomeAssistantError from exc + + async def async_set_sleep_duration(self, end: int) -> None: + """Set Starlink system sleep schedule end time.""" + async with asyncio.timeout(4): + try: + await self.hass.async_add_executor_job( + set_sleep_config, + self.data.sleep[0], + end, + self.data.sleep[2], + self.channel_context, + ) + except GrpcError as exc: + raise HomeAssistantError from exc diff --git a/homeassistant/components/starlink/strings.json b/homeassistant/components/starlink/strings.json index bc6807e8ba7..36a4f176e70 100644 --- a/homeassistant/components/starlink/strings.json +++ b/homeassistant/components/starlink/strings.json @@ -75,6 +75,17 @@ "switch": { "stowed": { "name": "Stowed" + }, + "sleep_schedule": { + "name": "Sleep schedule" + } + }, + "time": { + "sleep_start": { + "name": "Sleep start" + }, + "sleep_end": { + "name": "Sleep end" } } } diff --git a/homeassistant/components/starlink/switch.py b/homeassistant/components/starlink/switch.py index af773a39f79..3534748127e 100644 --- a/homeassistant/components/starlink/switch.py +++ b/homeassistant/components/starlink/switch.py @@ -67,5 +67,17 @@ SWITCHES = [ value_fn=lambda data: data.status["state"] == "STOWED", turn_on_fn=lambda coordinator: coordinator.async_stow_starlink(True), turn_off_fn=lambda coordinator: coordinator.async_stow_starlink(False), - ) + ), + StarlinkSwitchEntityDescription( + key="sleep_schedule", + translation_key="sleep_schedule", + device_class=SwitchDeviceClass.SWITCH, + value_fn=lambda data: data.sleep[2], + turn_on_fn=lambda coordinator: coordinator.async_set_sleep_schedule_enabled( + True + ), + turn_off_fn=lambda coordinator: coordinator.async_set_sleep_schedule_enabled( + False + ), + ), ] diff --git a/homeassistant/components/starlink/time.py b/homeassistant/components/starlink/time.py new file mode 100644 index 00000000000..4d9e2d06675 --- /dev/null +++ b/homeassistant/components/starlink/time.py @@ -0,0 +1,98 @@ +"""Contains time pickers exposed by the Starlink integration.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from datetime import UTC, datetime, time, tzinfo +import math + +from homeassistant.components.time import TimeEntity, TimeEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import StarlinkData, StarlinkUpdateCoordinator +from .entity import StarlinkEntity + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up all time entities for this entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + StarlinkTimeEntity(coordinator, description) for description in TIMES + ) + + +@dataclass(frozen=True, kw_only=True) +class StarlinkTimeEntityDescription(TimeEntityDescription): + """Describes a Starlink time entity.""" + + value_fn: Callable[[StarlinkData, tzinfo], time | None] + update_fn: Callable[[StarlinkUpdateCoordinator, time], Awaitable[None]] + available_fn: Callable[[StarlinkData], bool] + + +class StarlinkTimeEntity(StarlinkEntity, TimeEntity): + """A TimeEntity for Starlink devices. Handles creating unique IDs.""" + + entity_description: StarlinkTimeEntityDescription + + @property + def native_value(self) -> time | None: + """Return the value reported by the time.""" + return self.entity_description.value_fn( + self.coordinator.data, self.coordinator.timezone + ) + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self.entity_description.available_fn(self.coordinator.data) + + async def async_set_value(self, value: time) -> None: + """Change the time.""" + return await self.entity_description.update_fn(self.coordinator, value) + + +def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time: + hour = math.floor(utc_minutes / 60) + minute = utc_minutes % 60 + utc = datetime.now(UTC).replace(hour=hour, minute=minute, second=0, microsecond=0) + return utc.astimezone(timezone).time() + + +def _time_to_utc_minutes(t: time, timezone: tzinfo) -> int: + zoned_time = datetime.now(timezone).replace( + hour=t.hour, minute=t.minute, second=0, microsecond=0 + ) + utc_time = zoned_time.astimezone(UTC).time() + return (utc_time.hour * 60) + utc_time.minute + + +TIMES = [ + StarlinkTimeEntityDescription( + key="sleep_start", + translation_key="sleep_start", + value_fn=lambda data, timezone: _utc_minutes_to_time(data.sleep[0], timezone), + update_fn=lambda coordinator, time: coordinator.async_set_sleep_start( + _time_to_utc_minutes(time, coordinator.timezone) + ), + available_fn=lambda data: data.sleep[2], + ), + StarlinkTimeEntityDescription( + key="sleep_end", + translation_key="sleep_end", + value_fn=lambda data, timezone: _utc_minutes_to_time( + data.sleep[0] + data.sleep[1], timezone + ), + update_fn=lambda coordinator, time: coordinator.async_set_sleep_duration( + _time_to_utc_minutes(time, coordinator.timezone) + ), + available_fn=lambda data: data.sleep[2], + ), +] diff --git a/tests/components/starlink/fixtures/sleep_data_success.json b/tests/components/starlink/fixtures/sleep_data_success.json new file mode 100644 index 00000000000..99942688adc --- /dev/null +++ b/tests/components/starlink/fixtures/sleep_data_success.json @@ -0,0 +1 @@ +[0, 1, false] diff --git a/tests/components/starlink/patchers.py b/tests/components/starlink/patchers.py index 85e7a77c78c..f8179f07bed 100644 --- a/tests/components/starlink/patchers.py +++ b/tests/components/starlink/patchers.py @@ -19,6 +19,11 @@ LOCATION_DATA_SUCCESS_PATCHER = patch( return_value=json.loads(load_fixture("location_data_success.json", "starlink")), ) +SLEEP_DATA_SUCCESS_PATCHER = patch( + "homeassistant.components.starlink.coordinator.get_sleep_config", + return_value=json.loads(load_fixture("sleep_data_success.json", "starlink")), +) + DEVICE_FOUND_PATCHER = patch( "homeassistant.components.starlink.config_flow.get_id", return_value="some-valid-id" ) diff --git a/tests/components/starlink/snapshots/test_diagnostics.ambr b/tests/components/starlink/snapshots/test_diagnostics.ambr index 3bb7f235017..4c85ad84ca7 100644 --- a/tests/components/starlink/snapshots/test_diagnostics.ambr +++ b/tests/components/starlink/snapshots/test_diagnostics.ambr @@ -52,6 +52,11 @@ None, ]), }), + 'sleep': list([ + 0, + 1, + False, + ]), 'status': dict({ 'alerts': 0, 'currently_obstructed': False, diff --git a/tests/components/starlink/test_diagnostics.py b/tests/components/starlink/test_diagnostics.py index 2c11c19d4c2..22e1d6e84be 100644 --- a/tests/components/starlink/test_diagnostics.py +++ b/tests/components/starlink/test_diagnostics.py @@ -6,7 +6,11 @@ from homeassistant.components.starlink.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant -from .patchers import LOCATION_DATA_SUCCESS_PATCHER, STATUS_DATA_SUCCESS_PATCHER +from .patchers import ( + LOCATION_DATA_SUCCESS_PATCHER, + SLEEP_DATA_SUCCESS_PATCHER, + STATUS_DATA_SUCCESS_PATCHER, +) from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -24,7 +28,7 @@ async def test_diagnostics( data={CONF_IP_ADDRESS: "1.2.3.4:0000"}, ) - with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER: + with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER, SLEEP_DATA_SUCCESS_PATCHER: entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/starlink/test_init.py b/tests/components/starlink/test_init.py index 53d58a852cd..4fdb6afc3ed 100644 --- a/tests/components/starlink/test_init.py +++ b/tests/components/starlink/test_init.py @@ -5,7 +5,11 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant -from .patchers import LOCATION_DATA_SUCCESS_PATCHER, STATUS_DATA_SUCCESS_PATCHER +from .patchers import ( + LOCATION_DATA_SUCCESS_PATCHER, + SLEEP_DATA_SUCCESS_PATCHER, + STATUS_DATA_SUCCESS_PATCHER, +) from tests.common import MockConfigEntry @@ -17,7 +21,7 @@ async def test_successful_entry(hass: HomeAssistant) -> None: data={CONF_IP_ADDRESS: "1.2.3.4:0000"}, ) - with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER: + with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER, SLEEP_DATA_SUCCESS_PATCHER: entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -34,7 +38,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None: data={CONF_IP_ADDRESS: "1.2.3.4:0000"}, ) - with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER: + with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER, SLEEP_DATA_SUCCESS_PATCHER: entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) From 7065625d2800554aed472e17e6188414562f5071 Mon Sep 17 00:00:00 2001 From: Xitee <59659167+Xitee1@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:20:22 +0100 Subject: [PATCH 1239/1691] Add additional buttons to OctoPrint (#103139) * Add 3 new buttons - System shutdown button - System reboot button - Octoprint restart button * Enable buttons by default * Add tests * Fix tests * Remove accidentally committed unused code * Add RESTART device class to RestartOctoprint and RebootSystem buttons * Apply suggestions to octoprint test_button * Freeze time for OctoPrint button tests * Make new button base class to prevent implementing the availability check multiple times --- homeassistant/components/octoprint/button.py | 98 +++++++++++++++++++- tests/components/octoprint/test_button.py | 80 ++++++++++++++++ 2 files changed, 173 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/octoprint/button.py b/homeassistant/components/octoprint/button.py index b7c49df7f4c..2a2e5015303 100644 --- a/homeassistant/components/octoprint/button.py +++ b/homeassistant/components/octoprint/button.py @@ -2,7 +2,7 @@ from pyoctoprintapi import OctoprintClient, OctoprintPrinterInfo -from homeassistant.components.button import ButtonEntity +from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -31,11 +31,16 @@ async def async_setup_entry( OctoprintResumeJobButton(coordinator, device_id, client), OctoprintPauseJobButton(coordinator, device_id, client), OctoprintStopJobButton(coordinator, device_id, client), + OctoprintShutdownSystemButton(coordinator, device_id, client), + OctoprintRebootSystemButton(coordinator, device_id, client), + OctoprintRestartOctoprintButton(coordinator, device_id, client), ] ) -class OctoprintButton(CoordinatorEntity[OctoprintDataUpdateCoordinator], ButtonEntity): +class OctoprintPrinterButton( + CoordinatorEntity[OctoprintDataUpdateCoordinator], ButtonEntity +): """Represent an OctoPrint binary sensor.""" client: OctoprintClient @@ -61,7 +66,35 @@ class OctoprintButton(CoordinatorEntity[OctoprintDataUpdateCoordinator], ButtonE return self.coordinator.last_update_success and self.coordinator.data["printer"] -class OctoprintPauseJobButton(OctoprintButton): +class OctoprintSystemButton( + CoordinatorEntity[OctoprintDataUpdateCoordinator], ButtonEntity +): + """Represent an OctoPrint binary sensor.""" + + client: OctoprintClient + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + button_type: str, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator) + self.client = client + self._device_id = device_id + self._attr_name = f"OctoPrint {button_type}" + self._attr_unique_id = f"{button_type}-{device_id}" + self._attr_device_info = coordinator.device_info + + @property + def available(self) -> bool: + """Return if entity is available.""" + return self.coordinator.last_update_success + + +class OctoprintPauseJobButton(OctoprintPrinterButton): """Pause the active job.""" def __init__( @@ -83,7 +116,7 @@ class OctoprintPauseJobButton(OctoprintButton): raise InvalidPrinterState("Printer is not printing") -class OctoprintResumeJobButton(OctoprintButton): +class OctoprintResumeJobButton(OctoprintPrinterButton): """Resume the active job.""" def __init__( @@ -105,7 +138,7 @@ class OctoprintResumeJobButton(OctoprintButton): raise InvalidPrinterState("Printer is not currently paused") -class OctoprintStopJobButton(OctoprintButton): +class OctoprintStopJobButton(OctoprintPrinterButton): """Resume the active job.""" def __init__( @@ -125,5 +158,60 @@ class OctoprintStopJobButton(OctoprintButton): await self.client.cancel_job() +class OctoprintShutdownSystemButton(OctoprintSystemButton): + """Shutdown the system.""" + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator, "Shutdown System", device_id, client) + + async def async_press(self) -> None: + """Handle the button press.""" + await self.client.shutdown() + + +class OctoprintRebootSystemButton(OctoprintSystemButton): + """Reboot the system.""" + + _attr_device_class = ButtonDeviceClass.RESTART + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator, "Reboot System", device_id, client) + + async def async_press(self) -> None: + """Handle the button press.""" + await self.client.reboot_system() + + +class OctoprintRestartOctoprintButton(OctoprintSystemButton): + """Restart Octoprint.""" + + _attr_device_class = ButtonDeviceClass.RESTART + + def __init__( + self, + coordinator: OctoprintDataUpdateCoordinator, + device_id: str, + client: OctoprintClient, + ) -> None: + """Initialize a new OctoPrint button.""" + super().__init__(coordinator, "Restart Octoprint", device_id, client) + + async def async_press(self) -> None: + """Handle the button press.""" + await self.client.restart() + + class InvalidPrinterState(HomeAssistantError): """Service attempted in invalid state.""" diff --git a/tests/components/octoprint/test_button.py b/tests/components/octoprint/test_button.py index c511227f1b9..39e8fa5886c 100644 --- a/tests/components/octoprint/test_button.py +++ b/tests/components/octoprint/test_button.py @@ -2,6 +2,7 @@ from unittest.mock import patch +from freezegun import freeze_time from pyoctoprintapi import OctoprintPrinterInfo import pytest @@ -193,3 +194,82 @@ async def test_stop_job(hass: HomeAssistant) -> None: ) assert len(stop_command.mock_calls) == 0 + + +@freeze_time("2023-01-01 00:00") +async def test_shutdown_system(hass: HomeAssistant) -> None: + """Test the shutdown system button.""" + await init_integration(hass, BUTTON_DOMAIN) + + entity_id = "button.octoprint_shutdown_system" + + # Test shutting down the system + with patch( + "homeassistant.components.octoprint.coordinator.OctoprintClient.shutdown" + ) as shutdown_command: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + + assert len(shutdown_command.mock_calls) == 1 + + state = hass.states.get(entity_id) + assert state + assert state.state == "2023-01-01T00:00:00+00:00" + + +@freeze_time("2023-01-01 00:00") +async def test_reboot_system(hass: HomeAssistant) -> None: + """Test the reboot system button.""" + await init_integration(hass, BUTTON_DOMAIN) + + entity_id = "button.octoprint_reboot_system" + + # Test rebooting the system + with patch( + "homeassistant.components.octoprint.coordinator.OctoprintClient.reboot_system" + ) as reboot_command: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: entity_id, + }, + blocking=True, + ) + + assert len(reboot_command.mock_calls) == 1 + + state = hass.states.get(entity_id) + assert state + assert state.state == "2023-01-01T00:00:00+00:00" + + +@freeze_time("2023-01-01 00:00") +async def test_restart_octoprint(hass: HomeAssistant) -> None: + """Test the restart octoprint button.""" + await init_integration(hass, BUTTON_DOMAIN) + + entity_id = "button.octoprint_restart_octoprint" + + # Test restarting octoprint + with patch( + "homeassistant.components.octoprint.coordinator.OctoprintClient.restart" + ) as restart_command: + await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: entity_id, + }, + blocking=True, + ) + + assert len(restart_command.mock_calls) == 1 + + state = hass.states.get(entity_id) + assert state + assert state.state == "2023-01-01T00:00:00+00:00" From 8918eb6922ece896c93a14161680d5e695c8dee4 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 18 Mar 2024 15:21:52 +0000 Subject: [PATCH 1240/1691] Add camera in use and pending reboot binary sensors to System Bridge (#104095) * Add binary sensors * Fix * Fix * Fix translations * Add icons * Fix attr * Update string * fix name * Remove unnessasary check * Remove unrelated changes * Remove unrelated strings * Apply suggestions from code review Co-authored-by: Erik Montnemery * Update camera_in_use function and value_fn for binary sensors --------- Co-authored-by: Erik Montnemery --- .../components/system_bridge/binary_sensor.py | 28 ++++++++++++++++--- .../components/system_bridge/strings.json | 8 ++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/system_bridge/binary_sensor.py b/homeassistant/components/system_bridge/binary_sensor.py index d5cb34cd42a..019b1df4639 100644 --- a/homeassistant/components/system_bridge/binary_sensor.py +++ b/homeassistant/components/system_bridge/binary_sensor.py @@ -17,6 +17,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN from .coordinator import SystemBridgeDataUpdateCoordinator +from .data import SystemBridgeData from .entity import SystemBridgeEntity @@ -24,14 +25,33 @@ from .entity import SystemBridgeEntity class SystemBridgeBinarySensorEntityDescription(BinarySensorEntityDescription): """Class describing System Bridge binary sensor entities.""" - value: Callable = round + value_fn: Callable = round + + +def camera_in_use(data: SystemBridgeData) -> bool | None: + """Return if any camera is in use.""" + if data.system.camera_usage is not None: + return len(data.system.camera_usage) > 0 + return None BASE_BINARY_SENSOR_TYPES: tuple[SystemBridgeBinarySensorEntityDescription, ...] = ( + SystemBridgeBinarySensorEntityDescription( + key="camera_in_use", + translation_key="camera_in_use", + icon="mdi:webcam", + value_fn=camera_in_use, + ), + SystemBridgeBinarySensorEntityDescription( + key="pending_reboot", + translation_key="pending_reboot", + icon="mdi:restart", + value_fn=lambda data: data.system.pending_reboot, + ), SystemBridgeBinarySensorEntityDescription( key="version_available", device_class=BinarySensorDeviceClass.UPDATE, - value=lambda data: data.system.version_newer_available, + value_fn=lambda data: data.system.version_newer_available, ), ) @@ -39,7 +59,7 @@ BATTERY_BINARY_SENSOR_TYPES: tuple[SystemBridgeBinarySensorEntityDescription, .. SystemBridgeBinarySensorEntityDescription( key="battery_is_charging", device_class=BinarySensorDeviceClass.BATTERY_CHARGING, - value=lambda data: data.battery.is_charging, + value_fn=lambda data: data.battery.is_charging, ), ) @@ -90,4 +110,4 @@ class SystemBridgeBinarySensor(SystemBridgeEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Return the boolean state of the binary sensor.""" - return self.entity_description.value(self.coordinator.data) + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/system_bridge/strings.json b/homeassistant/components/system_bridge/strings.json index a13b0319aea..98a1fe4c08d 100644 --- a/homeassistant/components/system_bridge/strings.json +++ b/homeassistant/components/system_bridge/strings.json @@ -30,6 +30,14 @@ } }, "entity": { + "binary_sensor": { + "camera_in_use": { + "name": "Camera in use" + }, + "pending_reboot": { + "name": "Pending reboot" + } + }, "media_player": { "media": { "name": "Media" From eaf86ee1ea39f86de62ebfcd0ca15103d4af6c5b Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:45:49 +0100 Subject: [PATCH 1241/1691] Log cannot connect exception in Tedee config flow (#113740) log exception --- homeassistant/components/tedee/config_flow.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tedee/config_flow.py b/homeassistant/components/tedee/config_flow.py index fe8681a7fe4..8465b332539 100644 --- a/homeassistant/components/tedee/config_flow.py +++ b/homeassistant/components/tedee/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Tedee integration.""" from collections.abc import Mapping +import logging from typing import Any from pytedee_async import ( @@ -18,6 +19,8 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN, NAME +_LOGGER = logging.getLogger(__name__) + class TedeeConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Tedee.""" @@ -47,7 +50,8 @@ class TedeeConfigFlow(ConfigFlow, domain=DOMAIN): errors[CONF_LOCAL_ACCESS_TOKEN] = "invalid_api_key" except TedeeClientException: errors[CONF_HOST] = "invalid_host" - except TedeeDataUpdateException: + except TedeeDataUpdateException as exc: + _LOGGER.error("Error during local bridge discovery: %s", exc) errors["base"] = "cannot_connect" else: if self.reauth_entry: From aba5dcb63e89462bc3ecd4d076bd65b91833b884 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 18 Mar 2024 16:49:39 +0100 Subject: [PATCH 1242/1691] Patch Discovery timeout in govee light local (#113692) --- homeassistant/components/govee_light_local/config_flow.py | 4 +++- tests/components/govee_light_local/test_config_flow.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/govee_light_local/config_flow.py b/homeassistant/components/govee_light_local/config_flow.py index 8058668f0ca..bcf9886f6b4 100644 --- a/homeassistant/components/govee_light_local/config_flow.py +++ b/homeassistant/components/govee_light_local/config_flow.py @@ -20,6 +20,8 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +DISCOVERY_TIMEOUT = 5 + async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" @@ -41,7 +43,7 @@ async def _async_has_devices(hass: HomeAssistant) -> bool: await controller.start() try: - async with asyncio.timeout(delay=5): + async with asyncio.timeout(delay=DISCOVERY_TIMEOUT): while not controller.devices: await asyncio.sleep(delay=1) except TimeoutError: diff --git a/tests/components/govee_light_local/test_config_flow.py b/tests/components/govee_light_local/test_config_flow.py index 2b527c867f9..b76d13a82fc 100644 --- a/tests/components/govee_light_local/test_config_flow.py +++ b/tests/components/govee_light_local/test_config_flow.py @@ -1,5 +1,4 @@ """Test Govee light local config flow.""" - from unittest.mock import AsyncMock, patch from govee_local_api import GoveeDevice @@ -21,6 +20,9 @@ async def test_creating_entry_has_no_devices( with patch( "homeassistant.components.govee_light_local.config_flow.GoveeController", return_value=mock_govee_api, + ), patch( + "homeassistant.components.govee_light_local.config_flow.DISCOVERY_TIMEOUT", + 0, ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} From 815d120645623dd9701ca492a1c40971b91d1e2a Mon Sep 17 00:00:00 2001 From: Max Holland Date: Mon, 18 Mar 2024 15:59:49 +0000 Subject: [PATCH 1243/1691] Add support for Tuya dimmer module (#113741) --- homeassistant/components/tuya/light.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 144e3746285..d898e837d8e 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -380,6 +380,10 @@ LIGHTS["cz"] = LIGHTS["kg"] # https://developer.tuya.com/en/docs/iot/s?id=K9gf7o5prgf7s LIGHTS["pc"] = LIGHTS["kg"] +# Dimmer (duplicate of `tgq`) +# https://developer.tuya.com/en/docs/iot/tgq?id=Kaof8ke9il4k4 +LIGHTS["tdq"] = LIGHTS["tgq"] + @dataclass class ColorData: From 1ed8232b02dcf903a4fa7e896cda2b6036e4d69d Mon Sep 17 00:00:00 2001 From: Gido Date: Mon, 18 Mar 2024 18:00:54 +0100 Subject: [PATCH 1244/1691] Add config flow to Rova (#113596) * Add Config Flow for Rova component * Add tests for Rova config flow * Fix data type * Add rova to requirements for tests * Removed seperate function for area check and global variable * Add unique name and id to rova entities * Add support for multiple rova entries * Fix correct error after connection timeout or http error * Revert SENSOR_TYPES update * Add existing rova configuration from yaml as new entity * Add tests for import configuration.yaml flow * Cleanup code * Update valid rova area check in config flow * Changed abort keys and messages * Updated using self.add_suggested_values_to_schema * Update to pass tests * Added missing strings * Update sensor unique_ids * Fix service name formatting * Update tests for Rova entry * Update tests to recover after error * Update test name * Apply suggestions from code review --------- Co-authored-by: Joost Lekkerkerker --- .coveragerc | 1 + homeassistant/components/rova/__init__.py | 45 ++++ homeassistant/components/rova/config_flow.py | 91 +++++++ homeassistant/components/rova/const.py | 4 + homeassistant/components/rova/manifest.json | 1 + homeassistant/components/rova/sensor.py | 98 +++++-- homeassistant/components/rova/strings.json | 33 +++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 2 +- requirements_test_all.txt | 3 + tests/components/rova/__init__.py | 1 + tests/components/rova/conftest.py | 18 ++ tests/components/rova/test_config_flow.py | 269 +++++++++++++++++++ 13 files changed, 539 insertions(+), 28 deletions(-) create mode 100644 homeassistant/components/rova/config_flow.py create mode 100644 homeassistant/components/rova/strings.json create mode 100644 tests/components/rova/__init__.py create mode 100644 tests/components/rova/conftest.py create mode 100644 tests/components/rova/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index e2d8b9b5cef..cc7b1b98d2a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1145,6 +1145,7 @@ omit = homeassistant/components/roon/media_player.py homeassistant/components/roon/server.py homeassistant/components/route53/* + homeassistant/components/rova/__init__.py homeassistant/components/rova/sensor.py homeassistant/components/rpi_camera/* homeassistant/components/rtorrent/sensor.py diff --git a/homeassistant/components/rova/__init__.py b/homeassistant/components/rova/__init__.py index 411ea6c7239..d7ed140dddc 100644 --- a/homeassistant/components/rova/__init__.py +++ b/homeassistant/components/rova/__init__.py @@ -1 +1,46 @@ """The rova component.""" + +from __future__ import annotations + +from requests.exceptions import ConnectTimeout, HTTPError +from rova.rova import Rova + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady + +from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, DOMAIN + +PLATFORMS: list[Platform] = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up ROVA from a config entry.""" + + api = Rova( + entry.data[CONF_ZIP_CODE], + entry.data[CONF_HOUSE_NUMBER], + entry.data[CONF_HOUSE_NUMBER_SUFFIX], + ) + + try: + rova_area = await hass.async_add_executor_job(api.is_rova_area) + except (ConnectTimeout, HTTPError) as ex: + raise ConfigEntryNotReady from ex + + if not rova_area: + raise ConfigEntryError("Rova does not collect garbage in this area") + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = api + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload ROVA config entry.""" + + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/rova/config_flow.py b/homeassistant/components/rova/config_flow.py new file mode 100644 index 00000000000..d618681783e --- /dev/null +++ b/homeassistant/components/rova/config_flow.py @@ -0,0 +1,91 @@ +"""Config flow for the Rova platform.""" + +from typing import Any + +from requests.exceptions import ConnectTimeout, HTTPError +from rova.rova import Rova +import voluptuous as vol + +from homeassistant import config_entries + +from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, DOMAIN + + +class RovaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle Rova config flow.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.ConfigFlowResult: + """Step when user initializes a integration.""" + errors: dict[str, str] = {} + + if user_input is not None: + # generate unique name for rova integration + zip_code = user_input[CONF_ZIP_CODE] + number = user_input[CONF_HOUSE_NUMBER] + suffix = user_input[CONF_HOUSE_NUMBER_SUFFIX] + + await self.async_set_unique_id(f"{zip_code}{number}{suffix}".strip()) + self._abort_if_unique_id_configured() + + api = Rova(zip_code, number, suffix) + + try: + if not await self.hass.async_add_executor_job(api.is_rova_area): + errors = {"base": "invalid_rova_area"} + except (ConnectTimeout, HTTPError): + errors = {"base": "cannot_connect"} + + if not errors: + return self.async_create_entry( + title=f"{zip_code} {number} {suffix}".strip(), + data=user_input, + ) + + return self.async_show_form( + step_id="user", + data_schema=self.add_suggested_values_to_schema( + vol.Schema( + { + vol.Required(CONF_ZIP_CODE): str, + vol.Required(CONF_HOUSE_NUMBER): str, + vol.Optional(CONF_HOUSE_NUMBER_SUFFIX, default=""): str, + } + ), + user_input, + ), + errors=errors, + ) + + async def async_step_import( + self, user_input: dict[str, Any] + ) -> config_entries.ConfigFlowResult: + """Import the yaml config.""" + zip_code = user_input[CONF_ZIP_CODE] + number = user_input[CONF_HOUSE_NUMBER] + suffix = user_input[CONF_HOUSE_NUMBER_SUFFIX] + + await self.async_set_unique_id(f"{zip_code}{number}{suffix}".strip()) + self._abort_if_unique_id_configured() + + api = Rova(zip_code, number, suffix) + + try: + result = await self.hass.async_add_executor_job(api.is_rova_area) + + if result: + return self.async_create_entry( + title=f"{zip_code} {number} {suffix}".strip(), + data={ + CONF_ZIP_CODE: zip_code, + CONF_HOUSE_NUMBER: number, + CONF_HOUSE_NUMBER_SUFFIX: suffix, + }, + ) + return self.async_abort(reason="invalid_rova_area") + + except (ConnectTimeout, HTTPError): + return self.async_abort(reason="cannot_connect") diff --git a/homeassistant/components/rova/const.py b/homeassistant/components/rova/const.py index a0b233dabca..fa815b922ea 100644 --- a/homeassistant/components/rova/const.py +++ b/homeassistant/components/rova/const.py @@ -4,6 +4,10 @@ import logging LOGGER = logging.getLogger(__package__) +DEFAULT_NAME = "Rova" + CONF_ZIP_CODE = "zip_code" CONF_HOUSE_NUMBER = "house_number" CONF_HOUSE_NUMBER_SUFFIX = "house_number_suffix" + +DOMAIN = "rova" diff --git a/homeassistant/components/rova/manifest.json b/homeassistant/components/rova/manifest.json index a87ec224122..b867cac8e7a 100644 --- a/homeassistant/components/rova/manifest.json +++ b/homeassistant/components/rova/manifest.json @@ -2,6 +2,7 @@ "domain": "rova", "name": "ROVA", "codeowners": [], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/rova", "iot_class": "cloud_polling", "loggers": ["rova"], diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index 9f9c2238ba5..352540da1f2 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import datetime, timedelta +from typing import Any from requests.exceptions import ConnectTimeout, HTTPError from rova.rova import Rova @@ -14,21 +15,31 @@ from homeassistant.components.sensor import ( SensorEntity, SensorEntityDescription, ) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME -from homeassistant.core import HomeAssistant +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.data_entry_flow import FlowResultType import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import Throttle from homeassistant.util.dt import get_time_zone -from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, LOGGER +from .const import ( + CONF_HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX, + CONF_ZIP_CODE, + DOMAIN, + LOGGER, +) + +ISSUE_PLACEHOLDER = {"url": "/config/integrations/dashboard/add?domain=rova"} UPDATE_DELAY = timedelta(hours=12) SCAN_INTERVAL = timedelta(hours=12) - -SENSOR_TYPES: dict[str, SensorEntityDescription] = { +SENSOR_TYPES = { "bio": SensorEntityDescription( key="gft", name="bio", @@ -64,39 +75,71 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform( +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Create the Rova data service and sensors.""" + """Set up the rova sensor platform through yaml configuration.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + if ( + result["type"] == FlowResultType.CREATE_ENTRY + or result["reason"] == "already_configured" + ): + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{DOMAIN}", + breaks_in_ha_version="2024.10.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "Rova", + }, + ) + else: + async_create_issue( + hass, + DOMAIN, + f"deprecated_yaml_import_issue_${result['reason']}", + breaks_in_ha_version="2024.10.0", + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_placeholders=ISSUE_PLACEHOLDER, + ) - zip_code = config[CONF_ZIP_CODE] - house_number = config[CONF_HOUSE_NUMBER] - house_number_suffix = config[CONF_HOUSE_NUMBER_SUFFIX] - platform_name = config[CONF_NAME] - # Create new Rova object to retrieve data - api = Rova(zip_code, house_number, house_number_suffix) - - try: - if not api.is_rova_area(): - LOGGER.error("ROVA does not collect garbage in this area") - return - except (ConnectTimeout, HTTPError): - LOGGER.error("Could not retrieve details from ROVA API") - return +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Rova entry.""" + # get api from hass + api: Rova = hass.data[DOMAIN][entry.entry_id] # Create rova data service which will retrieve and update the data. data_service = RovaData(api) + # generate unique name for rova integration + name = f"{entry.data[CONF_ZIP_CODE]}{entry.data[CONF_HOUSE_NUMBER]}{entry.data[CONF_HOUSE_NUMBER_SUFFIX]}" + # Create a new sensor for each garbage type. entities = [ - RovaSensor(platform_name, SENSOR_TYPES[sensor_key], data_service) - for sensor_key in config[CONF_MONITORED_CONDITIONS] + RovaSensor(name, description, data_service) + for key, description in SENSOR_TYPES.items() ] - add_entities(entities, True) + async_add_entities(entities, True) class RovaSensor(SensorEntity): @@ -109,7 +152,8 @@ class RovaSensor(SensorEntity): self.entity_description = description self.data_service = data_service - self._attr_name = f"{platform_name}_{description.name}" + self._attr_name = f"{platform_name}_{description.key}" + self._attr_unique_id = f"{platform_name}_{description.key}" self._attr_device_class = SensorDeviceClass.TIMESTAMP def update(self) -> None: @@ -123,10 +167,10 @@ class RovaSensor(SensorEntity): class RovaData: """Get and update the latest data from the Rova API.""" - def __init__(self, api): + def __init__(self, api) -> None: """Initialize the data object.""" self.api = api - self.data = {} + self.data: dict[str, Any] = {} @Throttle(UPDATE_DELAY) def update(self): diff --git a/homeassistant/components/rova/strings.json b/homeassistant/components/rova/strings.json new file mode 100644 index 00000000000..8b57c2e5f62 --- /dev/null +++ b/homeassistant/components/rova/strings.json @@ -0,0 +1,33 @@ +{ + "config": { + "step": { + "user": { + "title": "Provide your address details", + "data": { + "zip_code": "Your zip code", + "house_number": "Your house number", + "house_number_suffix": "A suffix for your house number" + } + } + }, + "error": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "invalid_rova_area": "Rova does not collect at this address" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "cannot_connect": "Could not connect to the Rova API", + "invalid_rova_area": "Rova does not collect at this address" + } + }, + "issues": { + "deprecated_yaml_import_issue_cannot_connect": { + "title": "The Rova YAML configuration import failed", + "description": "Configuring Rova using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Rova works and restart Home Assistant to try again or remove the Rova YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." + }, + "deprecated_yaml_import_issue_invalid_rova_area": { + "title": "The Rova YAML configuration import failed", + "description": "There was an error when trying to import your Rova YAML configuration.\n\nRova does not collect at this address.\n\nEnsure the imported configuration is correct and remove the Rova YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 638f9497db9..fb7b0151bea 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -441,6 +441,7 @@ FLOWS = { "romy", "roomba", "roon", + "rova", "rpi_power", "rtsp_to_webrtc", "ruckus_unleashed", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 424861e0f58..044c76dc03b 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -5042,7 +5042,7 @@ "rova": { "name": "ROVA", "integration_type": "hub", - "config_flow": false, + "config_flow": true, "iot_class": "cloud_polling" }, "rss_feed_template": { diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a33b277cc9d..505c96d6ad9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1895,6 +1895,9 @@ roombapy==1.6.13 # homeassistant.components.roon roonapi==0.1.6 +# homeassistant.components.rova +rova==0.4.1 + # homeassistant.components.rpi_power rpi-bad-power==0.1.0 diff --git a/tests/components/rova/__init__.py b/tests/components/rova/__init__.py new file mode 100644 index 00000000000..631d37c09df --- /dev/null +++ b/tests/components/rova/__init__.py @@ -0,0 +1 @@ +"""Tests for the Rova component.""" diff --git a/tests/components/rova/conftest.py b/tests/components/rova/conftest.py new file mode 100644 index 00000000000..99dcd29fdf3 --- /dev/null +++ b/tests/components/rova/conftest.py @@ -0,0 +1,18 @@ +"""Common fixtures for Rova tests.""" + +from unittest.mock import MagicMock, patch + +import pytest + + +@pytest.fixture +def mock_rova(): + """Mock a successful Rova API.""" + api = MagicMock() + + with patch( + "homeassistant.components.rova.config_flow.Rova", + return_value=api, + ) as api, patch("homeassistant.components.rova.Rova", return_value=api): + api.is_rova_area.return_value = True + yield api diff --git a/tests/components/rova/test_config_flow.py b/tests/components/rova/test_config_flow.py new file mode 100644 index 00000000000..b5d590f7891 --- /dev/null +++ b/tests/components/rova/test_config_flow.py @@ -0,0 +1,269 @@ +"""Tests for the Rova config flow.""" +from unittest.mock import MagicMock + +import pytest +from requests.exceptions import ConnectTimeout, HTTPError + +from homeassistant import data_entry_flow +from homeassistant.components.rova.const import ( + CONF_HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX, + CONF_ZIP_CODE, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +ZIP_CODE = "7991AD" +HOUSE_NUMBER = "10" +HOUSE_NUMBER_SUFFIX = "a" + + +async def test_user(hass: HomeAssistant, mock_rova: MagicMock) -> None: + """Test user config.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + assert result.get("type") == data_entry_flow.FlowResultType.FORM + assert result.get("step_id") == "user" + + # test with all information provided + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + assert result.get("type") == data_entry_flow.FlowResultType.CREATE_ENTRY + + data = result.get("data") + assert data + assert data[CONF_ZIP_CODE] == ZIP_CODE + assert data[CONF_HOUSE_NUMBER] == HOUSE_NUMBER + assert data[CONF_HOUSE_NUMBER_SUFFIX] == HOUSE_NUMBER_SUFFIX + + +async def test_abort_if_not_rova_area( + hass: HomeAssistant, mock_rova: MagicMock +) -> None: + """Test we abort if rova does not collect at the given address.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + # test with area where rova does not collect + mock_rova.return_value.is_rova_area.return_value = False + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result.get("type") == data_entry_flow.FlowResultType.FORM + assert result.get("errors") == {"base": "invalid_rova_area"} + + # now reset the return value and test if we can recover + mock_rova.return_value.is_rova_area.return_value = True + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"{ZIP_CODE} {HOUSE_NUMBER} {HOUSE_NUMBER_SUFFIX}" + assert result["data"] == { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + } + + +async def test_abort_if_already_setup(hass: HomeAssistant) -> None: + """Test we abort if rova is already setup.""" + MockConfigEntry( + domain=DOMAIN, + unique_id=f"{ZIP_CODE}{HOUSE_NUMBER}{HOUSE_NUMBER_SUFFIX}", + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +@pytest.mark.parametrize( + ("exception", "error"), + [ + (ConnectTimeout(), "cannot_connect"), + (HTTPError(), "cannot_connect"), + ], +) +async def test_abort_if_api_throws_exception( + hass: HomeAssistant, exception: Exception, error: str, mock_rova: MagicMock +) -> None: + """Test different exceptions for the Rova entity.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + ) + + # test with exception + mock_rova.return_value.is_rova_area.side_effect = exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + assert result.get("type") == data_entry_flow.FlowResultType.FORM + assert result.get("errors") == {"base": error} + + # now reset the side effect to see if we can recover + mock_rova.return_value.is_rova_area.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"{ZIP_CODE} {HOUSE_NUMBER} {HOUSE_NUMBER_SUFFIX}" + assert result["data"] == { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + } + + +async def test_import(hass: HomeAssistant, mock_rova: MagicMock) -> None: + """Test import flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"{ZIP_CODE} {HOUSE_NUMBER} {HOUSE_NUMBER_SUFFIX}" + assert result["data"] == { + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + } + + +async def test_import_already_configured( + hass: HomeAssistant, mock_rova: MagicMock +) -> None: + """Test we abort import flow when entry is already configured.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=f"{ZIP_CODE}{HOUSE_NUMBER}{HOUSE_NUMBER_SUFFIX}", + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +async def test_import_if_not_rova_area( + hass: HomeAssistant, mock_rova: MagicMock +) -> None: + """Test we abort if rova does not collect at the given address.""" + + # test with area where rova does not collect + mock_rova.return_value.is_rova_area.return_value = False + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result.get("type") == data_entry_flow.FlowResultType.ABORT + assert result.get("reason") == "invalid_rova_area" + + +@pytest.mark.parametrize( + ("exception", "error"), + [ + (ConnectTimeout(), "cannot_connect"), + (HTTPError(), "cannot_connect"), + ], +) +async def test_import_connection_errors( + hass: HomeAssistant, exception: Exception, error: str, mock_rova: MagicMock +) -> None: + """Test import connection errors flow.""" + + # test with HTTPError + mock_rova.return_value.is_rova_area.side_effect = exception + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_ZIP_CODE: ZIP_CODE, + CONF_HOUSE_NUMBER: HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX: HOUSE_NUMBER_SUFFIX, + }, + ) + + assert result.get("type") == data_entry_flow.FlowResultType.ABORT + assert result.get("reason") == error From cb348ddbdb18ca508fc3f2e32792d5bf6030d15e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 18 Mar 2024 19:26:29 +0100 Subject: [PATCH 1245/1691] Axis use entity description light platform (#113602) * Axis use entity description in light platform * Clean up some old code * Update homeassistant/components/axis/light.py Co-authored-by: Joost Lekkerkerker --------- Co-authored-by: Joost Lekkerkerker --- homeassistant/components/axis/light.py | 110 +++++++++++++++++-------- 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/axis/light.py b/homeassistant/components/axis/light.py index 2fe4278ef06..ae7200398bd 100644 --- a/homeassistant/components/axis/light.py +++ b/homeassistant/components/axis/light.py @@ -1,75 +1,114 @@ """Support for Axis lights.""" +from collections.abc import Callable, Iterable +from dataclasses import dataclass +from functools import partial from typing import Any from axis.models.event import Event, EventOperation, EventTopic -from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ColorMode, + LightEntity, + LightEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import AxisEventEntity +from .entity import TOPIC_TO_EVENT_TYPE, AxisEventEntity from .hub import AxisHub +@callback +def light_name_fn(hub: AxisHub, event: Event) -> str: + """Provide Axis light entity name.""" + event_type = TOPIC_TO_EVENT_TYPE[event.topic_base] + light_id = f"led{event.id}" + light_type = hub.api.vapix.light_control[light_id].light_type + return f"{light_type} {event_type} {event.id}" + + +@dataclass(frozen=True, kw_only=True) +class AxisLightDescription(LightEntityDescription): + """Axis light entity description.""" + + event_topic: EventTopic + """Event topic that provides state updates.""" + name_fn: Callable[[AxisHub, Event], str] + """Function providing the corresponding name to the event ID.""" + supported_fn: Callable[[AxisHub, Event], bool] + """Function validating if event is supported.""" + + +ENTITY_DESCRIPTIONS = ( + AxisLightDescription( + key="Light state control", + event_topic=EventTopic.LIGHT_STATUS, + name_fn=light_name_fn, + supported_fn=lambda hub, event: len(hub.api.vapix.light_control) > 0, + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up a Axis light.""" + """Set up the Axis light platform.""" hub = AxisHub.get_hub(hass, config_entry) - if hub.api.vapix.light_control is None or len(hub.api.vapix.light_control) == 0: - return - @callback - def async_create_entity(event: Event) -> None: - """Create Axis light entity.""" - async_add_entities([AxisLight(event, hub)]) + def register_platform(descriptions: Iterable[AxisLightDescription]) -> None: + """Register entity platform to create entities on event initialized signal.""" - hub.api.event.subscribe( - async_create_entity, - topic_filter=EventTopic.LIGHT_STATUS, - operation_filter=EventOperation.INITIALIZED, - ) + @callback + def create_entity(description: AxisLightDescription, event: Event) -> None: + """Create Axis entity.""" + if description.supported_fn(hub, event): + async_add_entities([AxisLight(hub, description, event)]) + + for description in descriptions: + hub.api.event.subscribe( + partial(create_entity, description), + topic_filter=description.event_topic, + operation_filter=EventOperation.INITIALIZED, + ) + + register_platform(ENTITY_DESCRIPTIONS) class AxisLight(AxisEventEntity, LightEntity): - """Representation of a light Axis event.""" + """Representation of an Axis light.""" _attr_should_poll = True + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} - def __init__(self, event: Event, hub: AxisHub) -> None: + def __init__( + self, hub: AxisHub, description: AxisLightDescription, event: Event + ) -> None: """Initialize the Axis light.""" super().__init__(event, hub) - - self._light_id = f"led{event.id}" - - self.current_intensity = 0 - self.max_intensity = 0 - - light_type = hub.api.vapix.light_control[self._light_id].light_type - self._attr_name = f"{light_type} {self._event_type} {event.id}" + self.entity_description = description + self._attr_name = description.name_fn(hub, event) self._attr_is_on = event.is_tripped - self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} - self._attr_color_mode = ColorMode.BRIGHTNESS + self._light_id = f"led{event.id}" + self.current_intensity = 0 + self.max_intensity = 0 async def async_added_to_hass(self) -> None: """Subscribe lights events.""" await super().async_added_to_hass() - - current_intensity = ( + self.current_intensity = ( await self.hub.api.vapix.light_control.get_current_intensity(self._light_id) ) - self.current_intensity = current_intensity - - max_intensity = await self.hub.api.vapix.light_control.get_valid_intensity( - self._light_id - ) - self.max_intensity = max_intensity.high + self.max_intensity = ( + await self.hub.api.vapix.light_control.get_valid_intensity(self._light_id) + ).high @callback def async_event_callback(self, event: Event) -> None: @@ -100,7 +139,6 @@ class AxisLight(AxisEventEntity, LightEntity): async def async_update(self) -> None: """Update brightness.""" - current_intensity = ( + self.current_intensity = ( await self.hub.api.vapix.light_control.get_current_intensity(self._light_id) ) - self.current_intensity = current_intensity From f73f93913f3141c3348663c44832ad4ba17932c9 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 18 Mar 2024 19:31:13 +0100 Subject: [PATCH 1246/1691] Remove deprecated `hass.components` from netatmo webhook tests (#113691) --- tests/components/netatmo/test_init.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index b55d501e2c2..4e82d7f1b59 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -10,6 +10,7 @@ import pytest from syrupy import SnapshotAssertion from homeassistant import config_entries +from homeassistant.components import cloud from homeassistant.components.netatmo import DOMAIN from homeassistant.const import CONF_WEBHOOK_ID, Platform from homeassistant.core import CoreState, HomeAssistant @@ -200,10 +201,8 @@ async def test_setup_with_cloud( with patch( "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True + ), patch.object(cloud, "async_is_connected", return_value=True), patch.object( + cloud, "async_active_subscription", return_value=True ), patch( "homeassistant.components.cloud.async_create_cloudhook", return_value="https://hooks.nabu.casa/ABCD", @@ -222,8 +221,8 @@ async def test_setup_with_cloud( assert await async_setup_component( hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} ) - assert hass.components.cloud.async_active_subscription() is True - assert hass.components.cloud.async_is_connected() is True + assert cloud.async_active_subscription(hass) is True + assert cloud.async_is_connected(hass) is True fake_create_cloudhook.assert_called_once() assert ( @@ -268,9 +267,7 @@ async def test_setup_with_cloudhook(hass: HomeAssistant) -> None: "homeassistant.components.cloud.async_is_logged_in", return_value=True ), patch( "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( + ), patch.object(cloud, "async_active_subscription", return_value=True), patch( "homeassistant.components.cloud.async_create_cloudhook", return_value="https://hooks.nabu.casa/ABCD", ) as fake_create_cloudhook, patch( @@ -288,7 +285,7 @@ async def test_setup_with_cloudhook(hass: HomeAssistant) -> None: mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() assert await async_setup_component(hass, "netatmo", {}) - assert hass.components.cloud.async_active_subscription() is True + assert cloud.async_active_subscription(hass) is True assert ( hass.config_entries.async_entries("netatmo")[0].data["cloudhook_url"] From 51b8ffc69d2744f171e2132573bafabd0f8494c2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 18 Mar 2024 21:19:27 +0100 Subject: [PATCH 1247/1691] Add WebSocket support for handling labels on device registry (#113758) --- .../components/config/device_registry.py | 5 +++ homeassistant/helpers/device_registry.py | 1 + .../components/config/test_device_registry.py | 43 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index d7a9dc1a66d..f2b0035d060 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -69,6 +69,7 @@ def websocket_list_devices( # We only allow setting disabled_by user via API. # No Enum support like this in voluptuous, use .value vol.Optional("disabled_by"): vol.Any(DeviceEntryDisabler.USER.value, None), + vol.Optional("labels"): [str], vol.Optional("name_by_user"): vol.Any(str, None), } ) @@ -87,6 +88,10 @@ def websocket_update_device( if msg.get("disabled_by") is not None: msg["disabled_by"] = DeviceEntryDisabler(msg["disabled_by"]) + if "labels" in msg: + # Convert labels to a set + msg["labels"] = set(msg["labels"]) + entry = cast(DeviceEntry, registry.async_update_device(**msg)) connection.send_message(websocket_api.result_message(msg_id, entry.dict_repr)) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index ba4c393a2d9..2f421034919 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -276,6 +276,7 @@ class DeviceEntry: "hw_version": self.hw_version, "id": self.id, "identifiers": list(self.identifiers), + "labels": list(self.labels), "manufacturer": self.manufacturer, "model": self.model, "name_by_user": self.name_by_user, diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index 19c30a43858..bfb1ebdb191 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -1,6 +1,7 @@ """Test device_registry API.""" import pytest +from pytest_unordered import unordered from homeassistant.components.config import device_registry from homeassistant.core import HomeAssistant @@ -64,6 +65,7 @@ async def test_list_devices( "entry_type": None, "hw_version": None, "identifiers": [["bridgeid", "0123"]], + "labels": [], "manufacturer": "manufacturer", "model": "model", "name_by_user": None, @@ -81,6 +83,7 @@ async def test_list_devices( "entry_type": dr.DeviceEntryType.SERVICE, "hw_version": None, "identifiers": [["bridgeid", "1234"]], + "labels": [], "manufacturer": "manufacturer", "model": "model", "name_by_user": None, @@ -111,6 +114,7 @@ async def test_list_devices( "hw_version": None, "id": device1.id, "identifiers": [["bridgeid", "0123"]], + "labels": [], "manufacturer": "manufacturer", "model": "model", "name_by_user": None, @@ -180,6 +184,45 @@ async def test_update_device( assert isinstance(device.disabled_by, (dr.DeviceEntryDisabler, type(None))) +async def test_update_device_labels( + hass: HomeAssistant, + client: MockHAClientWebSocket, + device_registry: dr.DeviceRegistry, +) -> None: + """Test update entry labels.""" + entry = MockConfigEntry(title=None) + entry.add_to_hass(hass) + device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections={("ethernet", "12:34:56:78:90:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + + assert not device.labels + + await client.send_json_auto_id( + { + "type": "config/device_registry/update", + "device_id": device.id, + "labels": ["label1", "label2"], + } + ) + + msg = await client.receive_json() + await hass.async_block_till_done() + assert len(device_registry.devices) == 1 + + device = device_registry.async_get_device( + identifiers={("bridgeid", "0123")}, + connections={("ethernet", "12:34:56:78:90:AB:CD:EF")}, + ) + + assert msg["result"]["labels"] == unordered(["label1", "label2"]) + assert device.labels == {"label1", "label2"} + + async def test_remove_config_entry_from_device( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, From e20cc4f8b9f3803626f796af8e0aa945746a801d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 18 Mar 2024 22:03:37 +0100 Subject: [PATCH 1248/1691] Add WebSocket support for assigning labels to entities (#113757) --- homeassistant/components/config/entity_registry.py | 5 +++++ tests/components/config/test_entity_registry.py | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 2d8fe1a3645..7cdec324340 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -165,6 +165,7 @@ def websocket_get_entities( vol.Optional("categories"): cv.schema_with_slug_keys(vol.Any(str, None)), vol.Optional("device_class"): vol.Any(str, None), vol.Optional("icon"): vol.Any(str, None), + vol.Optional("labels"): [str], vol.Optional("name"): vol.Any(str, None), vol.Optional("new_entity_id"): str, # We only allow setting disabled_by user via API. @@ -224,6 +225,10 @@ def websocket_update_entity( # Convert aliases to a set changes["aliases"] = set(msg["aliases"]) + if "labels" in msg: + # Convert labels to a set + changes["labels"] = set(msg["labels"]) + if "disabled_by" in msg and msg["disabled_by"] is None: # Don't allow enabling an entity of a disabled device if entity_entry.device_id: diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 30a7106c0dd..d61d9d7f892 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -532,6 +532,7 @@ async def test_update_entity( "device_class": "custom_device_class", "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", + "labels": ["label1", "label2"], "name": "after update", } ) @@ -554,7 +555,7 @@ async def test_update_entity( "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, - "labels": [], + "labels": unordered(["label1", "label2"]), "name": "after update", "options": {}, "original_device_class": None, @@ -628,7 +629,7 @@ async def test_update_entity( "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, - "labels": [], + "labels": unordered(["label1", "label2"]), "name": "after update", "options": {}, "original_device_class": None, @@ -669,7 +670,7 @@ async def test_update_entity( "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, - "labels": [], + "labels": unordered(["label1", "label2"]), "name": "after update", "options": {"sensor": {"unit_of_measurement": "beard_second"}}, "original_device_class": None, @@ -709,7 +710,7 @@ async def test_update_entity( "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, - "labels": [], + "labels": unordered(["label1", "label2"]), "name": "after update", "options": {"sensor": {"unit_of_measurement": "beard_second"}}, "original_device_class": None, @@ -749,7 +750,7 @@ async def test_update_entity( "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, - "labels": [], + "labels": unordered(["label1", "label2"]), "name": "after update", "options": {"sensor": {"unit_of_measurement": "beard_second"}}, "original_device_class": None, @@ -789,7 +790,7 @@ async def test_update_entity( "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, - "labels": [], + "labels": unordered(["label1", "label2"]), "name": "after update", "options": {"sensor": {"unit_of_measurement": "beard_second"}}, "original_device_class": None, From 506240be10ff79a095a20f090d27d57413e3230e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 18 Mar 2024 22:08:06 +0100 Subject: [PATCH 1249/1691] Streamline naming in deCONZ integration (#111977) --- homeassistant/components/deconz/__init__.py | 38 +++++----- .../components/deconz/alarm_control_panel.py | 36 +++++----- .../components/deconz/binary_sensor.py | 18 ++--- homeassistant/components/deconz/button.py | 30 ++++---- homeassistant/components/deconz/climate.py | 30 ++++---- .../components/deconz/config_flow.py | 8 +-- homeassistant/components/deconz/cover.py | 32 ++++----- .../components/deconz/deconz_device.py | 38 +++++----- .../components/deconz/deconz_event.py | 65 ++++++++--------- .../components/deconz/device_trigger.py | 6 +- .../components/deconz/diagnostics.py | 26 ++++--- homeassistant/components/deconz/fan.py | 24 +++---- .../components/deconz/hub/__init__.py | 3 +- homeassistant/components/deconz/hub/hub.py | 30 ++++---- homeassistant/components/deconz/light.py | 44 ++++++------ homeassistant/components/deconz/lock.py | 30 ++++---- homeassistant/components/deconz/number.py | 20 +++--- homeassistant/components/deconz/scene.py | 16 ++--- homeassistant/components/deconz/select.py | 24 +++---- homeassistant/components/deconz/sensor.py | 30 ++++---- homeassistant/components/deconz/services.py | 70 +++++++++---------- homeassistant/components/deconz/siren.py | 18 ++--- homeassistant/components/deconz/switch.py | 18 ++--- tests/components/deconz/test_gateway.py | 13 ++-- 24 files changed, 325 insertions(+), 342 deletions(-) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index ae9bd0db0fd..4952cb3dafc 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -7,7 +7,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from .config_flow import get_master_gateway +from .config_flow import get_master_hub from .const import CONF_MASTER_GATEWAY, DOMAIN, PLATFORMS from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect @@ -24,7 +24,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b hass.data.setdefault(DOMAIN, {}) if not config_entry.options: - await async_update_master_gateway(hass, config_entry) + await async_update_master_hub(hass, config_entry) try: api = await get_deconz_api(hass, config_entry) @@ -36,20 +36,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not hass.data[DOMAIN]: async_setup_services(hass) - gateway = hass.data[DOMAIN][config_entry.entry_id] = DeconzHub( - hass, config_entry, api - ) - await gateway.async_update_device_registry() + hub = hass.data[DOMAIN][config_entry.entry_id] = DeconzHub(hass, config_entry, api) + await hub.async_update_device_registry() - config_entry.add_update_listener(gateway.async_config_entry_updated) + config_entry.add_update_listener(hub.async_config_entry_updated) - await async_setup_events(gateway) + await async_setup_events(hub) await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) api.start() config_entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, hub.shutdown) ) return True @@ -57,31 +55,31 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload deCONZ config entry.""" - gateway: DeconzHub = hass.data[DOMAIN].pop(config_entry.entry_id) - async_unload_events(gateway) + hub: DeconzHub = hass.data[DOMAIN].pop(config_entry.entry_id) + async_unload_events(hub) if not hass.data[DOMAIN]: async_unload_services(hass) - elif gateway.master: - await async_update_master_gateway(hass, config_entry) - new_master_gateway = next(iter(hass.data[DOMAIN].values())) - await async_update_master_gateway(hass, new_master_gateway.config_entry) + elif hub.master: + await async_update_master_hub(hass, config_entry) + new_master_hub = next(iter(hass.data[DOMAIN].values())) + await async_update_master_hub(hass, new_master_hub.config_entry) - return await gateway.async_reset() + return await hub.async_reset() -async def async_update_master_gateway( +async def async_update_master_hub( hass: HomeAssistant, config_entry: ConfigEntry ) -> None: - """Update master gateway boolean. + """Update master hub boolean. Called by setup_entry and unload_entry. Makes sure there is always one master available. """ try: - master_gateway = get_master_gateway(hass) - master = master_gateway.config_entry == config_entry + master_hub = get_master_hub(hass) + master = master_hub.config_entry == config_entry except ValueError: master = True diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index 764bcecc17f..ae230c783f9 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -29,7 +29,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub DECONZ_TO_ALARM_STATE = { AncillaryControlPanel.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, @@ -45,9 +45,9 @@ DECONZ_TO_ALARM_STATE = { } -def get_alarm_system_id_for_unique_id(gateway: DeconzHub, unique_id: str) -> str | None: +def get_alarm_system_id_for_unique_id(hub: DeconzHub, unique_id: str) -> str | None: """Retrieve alarm system ID the unique ID is registered to.""" - for alarm_system in gateway.api.alarm_systems.values(): + for alarm_system in hub.api.alarm_systems.values(): if unique_id in alarm_system.devices: return alarm_system.resource_id return None @@ -59,23 +59,19 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ alarm control panel devices.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_sensor(_: EventType, sensor_id: str) -> None: """Add alarm control panel devices from deCONZ.""" - sensor = gateway.api.sensors.ancillary_control[sensor_id] - if alarm_system_id := get_alarm_system_id_for_unique_id( - gateway, sensor.unique_id - ): - async_add_entities( - [DeconzAlarmControlPanel(sensor, gateway, alarm_system_id)] - ) + sensor = hub.api.sensors.ancillary_control[sensor_id] + if alarm_system_id := get_alarm_system_id_for_unique_id(hub, sensor.unique_id): + async_add_entities([DeconzAlarmControlPanel(sensor, hub, alarm_system_id)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors.ancillary_control, + hub.api.sensors.ancillary_control, ) @@ -95,11 +91,11 @@ class DeconzAlarmControlPanel(DeconzDevice[AncillaryControl], AlarmControlPanelE def __init__( self, device: AncillaryControl, - gateway: DeconzHub, + hub: DeconzHub, alarm_system_id: str, ) -> None: """Set up alarm control panel device.""" - super().__init__(device, gateway) + super().__init__(device, hub) self.alarm_system_id = alarm_system_id @callback @@ -118,27 +114,27 @@ class DeconzAlarmControlPanel(DeconzDevice[AncillaryControl], AlarmControlPanelE async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" if code: - await self.gateway.api.alarm_systems.arm( + await self.hub.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.AWAY, code ) async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" if code: - await self.gateway.api.alarm_systems.arm( + await self.hub.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.STAY, code ) async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" if code: - await self.gateway.api.alarm_systems.arm( + await self.hub.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.NIGHT, code ) async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" if code: - await self.gateway.api.alarm_systems.arm( + await self.hub.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.DISARM, code ) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 3629ff7283e..eaa89c6eb9c 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -31,7 +31,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_DARK, ATTR_ON from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub _SensorDeviceT = TypeVar("_SensorDeviceT", bound=PydeconzSensorBase) @@ -168,13 +168,13 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ binary sensor.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_sensor(_: EventType, sensor_id: str) -> None: """Add sensor from deCONZ.""" - sensor = gateway.api.sensors[sensor_id] + sensor = hub.api.sensors[sensor_id] for description in ENTITY_DESCRIPTIONS: if ( @@ -182,11 +182,11 @@ async def async_setup_entry( and not isinstance(sensor, description.instance_check) ) or description.value_fn(sensor) is None: continue - async_add_entities([DeconzBinarySensor(sensor, gateway, description)]) + async_add_entities([DeconzBinarySensor(sensor, hub, description)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors, + hub.api.sensors, ) @@ -199,7 +199,7 @@ class DeconzBinarySensor(DeconzDevice[SensorResources], BinarySensorEntity): def __init__( self, device: SensorResources, - gateway: DeconzHub, + hub: DeconzHub, description: DeconzBinarySensorDescription, ) -> None: """Initialize deCONZ binary sensor.""" @@ -208,7 +208,7 @@ class DeconzBinarySensor(DeconzDevice[SensorResources], BinarySensorEntity): self._update_key = description.update_key if description.name_suffix: self._name_suffix = description.name_suffix - super().__init__(device, gateway) + super().__init__(device, hub) if ( self.entity_description.key in PROVIDES_EXTRA_ATTRIBUTES diff --git a/homeassistant/components/deconz/button.py b/homeassistant/components/deconz/button.py index 64801880583..a915ca56a33 100644 --- a/homeassistant/components/deconz/button.py +++ b/homeassistant/components/deconz/button.py @@ -20,7 +20,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice, DeconzSceneMixin -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub @dataclass(frozen=True, kw_only=True) @@ -50,33 +50,33 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ button entity.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_scene(_: EventType, scene_id: str) -> None: """Add scene button from deCONZ.""" - scene = gateway.api.scenes[scene_id] + scene = hub.api.scenes[scene_id] async_add_entities( - DeconzSceneButton(scene, gateway, description) + DeconzSceneButton(scene, hub, description) for description in ENTITY_DESCRIPTIONS.get(PydeconzScene, []) ) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_scene, - gateway.api.scenes, + hub.api.scenes, ) @callback def async_add_presence_sensor(_: EventType, sensor_id: str) -> None: """Add presence sensor reset button from deCONZ.""" - sensor = gateway.api.sensors.presence[sensor_id] + sensor = hub.api.sensors.presence[sensor_id] if sensor.presence_event is not None: - async_add_entities([DeconzPresenceResetButton(sensor, gateway)]) + async_add_entities([DeconzPresenceResetButton(sensor, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_presence_sensor, - gateway.api.sensors.presence, + hub.api.sensors.presence, ) @@ -88,19 +88,19 @@ class DeconzSceneButton(DeconzSceneMixin, ButtonEntity): def __init__( self, device: PydeconzScene, - gateway: DeconzHub, + hub: DeconzHub, description: DeconzButtonDescription, ) -> None: """Initialize deCONZ number entity.""" self.entity_description: DeconzButtonDescription = description - super().__init__(device, gateway) + super().__init__(device, hub) self._attr_name = f"{self._attr_name} {description.suffix}" async def async_press(self) -> None: """Store light states into scene.""" async_button_fn = getattr( - self.gateway.api.scenes, + self.hub.api.scenes, self.entity_description.button_fn, ) await async_button_fn(self._device.group_id, self._device.id) @@ -123,7 +123,7 @@ class DeconzPresenceResetButton(DeconzDevice[Presence], ButtonEntity): async def async_press(self) -> None: """Store reset presence state.""" - await self.gateway.api.sensors.presence.set_config( + await self.hub.api.sensors.presence.set_config( id=self._device.resource_id, reset_presence=True, ) diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 931995196cc..45a50d44e36 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -35,7 +35,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_LOCKED, ATTR_OFFSET, ATTR_VALVE from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub DECONZ_FAN_SMART = "smart" @@ -80,18 +80,18 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ climate devices.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_climate(_: EventType, climate_id: str) -> None: """Add climate from deCONZ.""" - climate = gateway.api.sensors.thermostat[climate_id] - async_add_entities([DeconzThermostat(climate, gateway)]) + climate = hub.api.sensors.thermostat[climate_id] + async_add_entities([DeconzThermostat(climate, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_climate, - gateway.api.sensors.thermostat, + hub.api.sensors.thermostat, ) @@ -103,9 +103,9 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): _attr_temperature_unit = UnitOfTemperature.CELSIUS _enable_turn_on_off_backwards_compatibility = False - def __init__(self, device: Thermostat, gateway: DeconzHub) -> None: + def __init__(self, device: Thermostat, hub: DeconzHub) -> None: """Set up thermostat device.""" - super().__init__(device, gateway) + super().__init__(device, hub) self._attr_hvac_modes = [ HVACMode.HEAT, @@ -149,7 +149,7 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): if fan_mode not in FAN_MODE_TO_DECONZ: raise ValueError(f"Unsupported fan mode {fan_mode}") - await self.gateway.api.sensors.thermostat.set_config( + await self.hub.api.sensors.thermostat.set_config( id=self._device.resource_id, fan_mode=FAN_MODE_TO_DECONZ[fan_mode], ) @@ -169,12 +169,12 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): raise ValueError(f"Unsupported HVAC mode {hvac_mode}") if len(self._attr_hvac_modes) == 2: # Only allow turn on and off thermostat - await self.gateway.api.sensors.thermostat.set_config( + await self.hub.api.sensors.thermostat.set_config( id=self._device.resource_id, on=hvac_mode != HVACMode.OFF, ) else: - await self.gateway.api.sensors.thermostat.set_config( + await self.hub.api.sensors.thermostat.set_config( id=self._device.resource_id, mode=HVAC_MODE_TO_DECONZ[hvac_mode], ) @@ -208,7 +208,7 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): if preset_mode not in PRESET_MODE_TO_DECONZ: raise ValueError(f"Unsupported preset mode {preset_mode}") - await self.gateway.api.sensors.thermostat.set_config( + await self.hub.api.sensors.thermostat.set_config( id=self._device.resource_id, preset=PRESET_MODE_TO_DECONZ[preset_mode], ) @@ -237,12 +237,12 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity): raise ValueError(f"Expected attribute {ATTR_TEMPERATURE}") if self._device.mode == ThermostatMode.COOL: - await self.gateway.api.sensors.thermostat.set_config( + await self.hub.api.sensors.thermostat.set_config( id=self._device.resource_id, cooling_setpoint=kwargs[ATTR_TEMPERATURE] * 100, ) else: - await self.gateway.api.sensors.thermostat.set_config( + await self.hub.api.sensors.thermostat.set_config( id=self._device.resource_id, heating_setpoint=kwargs[ATTR_TEMPERATURE] * 100, ) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index ba444be1a8f..d017e2c5c65 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -52,11 +52,11 @@ CONF_MANUAL_INPUT = "Manually define gateway" @callback -def get_master_gateway(hass: HomeAssistant) -> DeconzHub: +def get_master_hub(hass: HomeAssistant) -> DeconzHub: """Return the gateway which is marked as master.""" - for gateway in hass.data[DOMAIN].values(): - if gateway.master: - return cast(DeconzHub, gateway) + for hub in hass.data[DOMAIN].values(): + if hub.master: + return cast(DeconzHub, hub) raise ValueError diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index bccdac1fc4c..b83c62c3367 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -22,7 +22,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub DECONZ_TYPE_TO_DEVICE_CLASS = { ResourceType.LEVEL_CONTROLLABLE_OUTPUT.value: CoverDeviceClass.DAMPER, @@ -37,17 +37,17 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up covers for deCONZ component.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_cover(_: EventType, cover_id: str) -> None: """Add cover from deCONZ.""" - async_add_entities([DeconzCover(cover_id, gateway)]) + async_add_entities([DeconzCover(cover_id, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_cover, - gateway.api.lights.covers, + hub.api.lights.covers, ) @@ -56,9 +56,9 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): TYPE = DOMAIN - def __init__(self, cover_id: str, gateway: DeconzHub) -> None: + def __init__(self, cover_id: str, hub: DeconzHub) -> None: """Set up cover device.""" - super().__init__(cover := gateway.api.lights.covers[cover_id], gateway) + super().__init__(cover := hub.api.lights.covers[cover_id], hub) self._attr_supported_features = ( CoverEntityFeature.OPEN @@ -92,7 +92,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" position = 100 - cast(int, kwargs[ATTR_POSITION]) - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, lift=position, legacy_mode=self.legacy_mode, @@ -100,7 +100,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_open_cover(self, **kwargs: Any) -> None: """Open cover.""" - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.OPEN, legacy_mode=self.legacy_mode, @@ -108,7 +108,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.CLOSE, legacy_mode=self.legacy_mode, @@ -116,7 +116,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_stop_cover(self, **kwargs: Any) -> None: """Stop cover.""" - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.STOP, legacy_mode=self.legacy_mode, @@ -132,7 +132,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Tilt the cover to a specific position.""" position = 100 - cast(int, kwargs[ATTR_TILT_POSITION]) - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, tilt=position, legacy_mode=self.legacy_mode, @@ -140,7 +140,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Open cover tilt.""" - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, tilt=0, legacy_mode=self.legacy_mode, @@ -148,7 +148,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_close_cover_tilt(self, **kwargs: Any) -> None: """Close cover tilt.""" - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, tilt=100, legacy_mode=self.legacy_mode, @@ -156,7 +156,7 @@ class DeconzCover(DeconzDevice[Cover], CoverEntity): async def async_stop_cover_tilt(self, **kwargs: Any) -> None: """Stop cover tilt.""" - await self.gateway.api.lights.covers.set_state( + await self.hub.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.STOP, legacy_mode=self.legacy_mode, diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index 90f6a5a251f..0ddabbcfccc 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -33,11 +33,11 @@ class DeconzBase(Generic[_DeviceT]): def __init__( self, device: _DeviceT, - gateway: DeconzHub, + hub: DeconzHub, ) -> None: """Set up device and add update callback to get data from websocket.""" self._device: _DeviceT = device - self.gateway = gateway + self.hub = hub @property def unique_id(self) -> str: @@ -67,7 +67,7 @@ class DeconzBase(Generic[_DeviceT]): model=self._device.model_id, name=self._device.name, sw_version=self._device.software_version, - via_device=(DECONZ_DOMAIN, self.gateway.api.config.bridge_id), + via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id), ) @@ -85,11 +85,11 @@ class DeconzDevice(DeconzBase[_DeviceT], Entity): def __init__( self, device: _DeviceT, - gateway: DeconzHub, + hub: DeconzHub, ) -> None: """Set up device and add update callback to get data from websocket.""" - super().__init__(device, gateway) - self.gateway.entities[self.TYPE].add(self.unique_id) + super().__init__(device, hub) + self.hub.entities[self.TYPE].add(self.unique_id) self._attr_name = self._device.name if self._name_suffix is not None: @@ -103,11 +103,11 @@ class DeconzDevice(DeconzBase[_DeviceT], Entity): async def async_added_to_hass(self) -> None: """Subscribe to device events.""" self._device.register_callback(self.async_update_callback) - self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id + self.hub.deconz_ids[self.entity_id] = self._device.deconz_id self.async_on_remove( async_dispatcher_connect( self.hass, - self.gateway.signal_reachable, + self.hub.signal_reachable, self.async_update_connection_state, ) ) @@ -115,8 +115,8 @@ class DeconzDevice(DeconzBase[_DeviceT], Entity): async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" self._device.remove_callback(self.async_update_callback) - del self.gateway.deconz_ids[self.entity_id] - self.gateway.entities[self.TYPE].remove(self.unique_id) + del self.hub.deconz_ids[self.entity_id] + self.hub.entities[self.TYPE].remove(self.unique_id) @callback def async_update_connection_state(self) -> None: @@ -126,7 +126,7 @@ class DeconzDevice(DeconzBase[_DeviceT], Entity): @callback def async_update_callback(self) -> None: """Update the device's state.""" - if self.gateway.ignore_state_updates: + if self.hub.ignore_state_updates: return if self._update_keys is not None and not self._device.changed_keys.intersection( @@ -140,8 +140,8 @@ class DeconzDevice(DeconzBase[_DeviceT], Entity): def available(self) -> bool: """Return True if device is available.""" if isinstance(self._device, PydeconzScene): - return self.gateway.available - return self.gateway.available and self._device.reachable # type: ignore[union-attr] + return self.hub.available + return self.hub.available and self._device.reachable # type: ignore[union-attr] class DeconzSceneMixin(DeconzDevice[PydeconzScene]): @@ -152,23 +152,23 @@ class DeconzSceneMixin(DeconzDevice[PydeconzScene]): def __init__( self, device: PydeconzScene, - gateway: DeconzHub, + hub: DeconzHub, ) -> None: """Set up a scene.""" - super().__init__(device, gateway) + super().__init__(device, hub) - self.group = self.gateway.api.groups[device.group_id] + self.group = self.hub.api.groups[device.group_id] self._attr_name = device.name self._group_identifier = self.get_parent_identifier() def get_device_identifier(self) -> str: """Describe a unique identifier for this scene.""" - return f"{self.gateway.bridgeid}{self._device.deconz_id}" + return f"{self.hub.bridgeid}{self._device.deconz_id}" def get_parent_identifier(self) -> str: """Describe a unique identifier for group this scene belongs to.""" - return f"{self.gateway.bridgeid}-{self.group.deconz_id}" + return f"{self.hub.bridgeid}-{self.group.deconz_id}" @property def unique_id(self) -> str: @@ -183,5 +183,5 @@ class DeconzSceneMixin(DeconzDevice[PydeconzScene]): manufacturer="Dresden Elektronik", model="deCONZ group", name=self.group.name, - via_device=(DECONZ_DOMAIN, self.gateway.api.config.bridge_id), + via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id), ) diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 70ee964f2f5..601190eaab7 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -55,57 +55,57 @@ RELATIVE_ROTARY_DECONZ_TO_EVENT = { } -async def async_setup_events(gateway: DeconzHub) -> None: +async def async_setup_events(hub: DeconzHub) -> None: """Set up the deCONZ events.""" @callback def async_add_sensor(_: EventType, sensor_id: str) -> None: """Create DeconzEvent.""" new_event: DeconzAlarmEvent | DeconzEvent | DeconzPresenceEvent | DeconzRelativeRotaryEvent - sensor = gateway.api.sensors[sensor_id] + sensor = hub.api.sensors[sensor_id] if isinstance(sensor, Switch): - new_event = DeconzEvent(sensor, gateway) + new_event = DeconzEvent(sensor, hub) elif isinstance(sensor, AncillaryControl): - new_event = DeconzAlarmEvent(sensor, gateway) + new_event = DeconzAlarmEvent(sensor, hub) elif isinstance(sensor, Presence): if sensor.presence_event is None: return - new_event = DeconzPresenceEvent(sensor, gateway) + new_event = DeconzPresenceEvent(sensor, hub) elif isinstance(sensor, RelativeRotary): - new_event = DeconzRelativeRotaryEvent(sensor, gateway) + new_event = DeconzRelativeRotaryEvent(sensor, hub) - gateway.hass.async_create_task(new_event.async_update_device_registry()) - gateway.events.append(new_event) + hub.hass.async_create_task(new_event.async_update_device_registry()) + hub.events.append(new_event) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors.switch, + hub.api.sensors.switch, ) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors.ancillary_control, + hub.api.sensors.ancillary_control, ) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors.presence, + hub.api.sensors.presence, ) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors.relative_rotary, + hub.api.sensors.relative_rotary, ) @callback -def async_unload_events(gateway: DeconzHub) -> None: +def async_unload_events(hub: DeconzHub) -> None: """Unload all deCONZ events.""" - for event in gateway.events: + for event in hub.events: event.async_will_remove_from_hass() - gateway.events.clear() + hub.events.clear() class DeconzEventBase(DeconzBase): @@ -118,10 +118,10 @@ class DeconzEventBase(DeconzBase): def __init__( self, device: AncillaryControl | Presence | RelativeRotary | Switch, - gateway: DeconzHub, + hub: DeconzHub, ) -> None: """Register callback that will be used for signals.""" - super().__init__(device, gateway) + super().__init__(device, hub) self._unsubscribe = device.subscribe(self.async_update_callback) @@ -145,10 +145,10 @@ class DeconzEventBase(DeconzBase): if not self.device_info: return - device_registry = dr.async_get(self.gateway.hass) + device_registry = dr.async_get(self.hub.hass) entry = device_registry.async_get_or_create( - config_entry_id=self.gateway.config_entry.entry_id, **self.device_info + config_entry_id=self.hub.config_entry.entry_id, **self.device_info ) self.device_id = entry.id @@ -165,10 +165,7 @@ class DeconzEvent(DeconzEventBase): @callback def async_update_callback(self) -> None: """Fire the event if reason is that state is updated.""" - if ( - self.gateway.ignore_state_updates - or "state" not in self._device.changed_keys - ): + if self.hub.ignore_state_updates or "state" not in self._device.changed_keys: return data: dict[str, Any] = { @@ -189,7 +186,7 @@ class DeconzEvent(DeconzEventBase): if self._device.xy is not None: data[CONF_XY] = self._device.xy - self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) + self.hub.hass.bus.async_fire(CONF_DECONZ_EVENT, data) class DeconzAlarmEvent(DeconzEventBase): @@ -201,7 +198,7 @@ class DeconzAlarmEvent(DeconzEventBase): def async_update_callback(self) -> None: """Fire the event if reason is new action is updated.""" if ( - self.gateway.ignore_state_updates + self.hub.ignore_state_updates or "action" not in self._device.changed_keys or self._device.action not in SUPPORTED_DECONZ_ALARM_EVENTS ): @@ -214,7 +211,7 @@ class DeconzAlarmEvent(DeconzEventBase): CONF_EVENT: self._device.action.value, } - self.gateway.hass.bus.async_fire(CONF_DECONZ_ALARM_EVENT, data) + self.hub.hass.bus.async_fire(CONF_DECONZ_ALARM_EVENT, data) class DeconzPresenceEvent(DeconzEventBase): @@ -226,7 +223,7 @@ class DeconzPresenceEvent(DeconzEventBase): def async_update_callback(self) -> None: """Fire the event if reason is new action is updated.""" if ( - self.gateway.ignore_state_updates + self.hub.ignore_state_updates or "presenceevent" not in self._device.changed_keys or self._device.presence_event not in SUPPORTED_DECONZ_PRESENCE_EVENTS ): @@ -239,7 +236,7 @@ class DeconzPresenceEvent(DeconzEventBase): CONF_EVENT: self._device.presence_event.value, } - self.gateway.hass.bus.async_fire(CONF_DECONZ_PRESENCE_EVENT, data) + self.hub.hass.bus.async_fire(CONF_DECONZ_PRESENCE_EVENT, data) class DeconzRelativeRotaryEvent(DeconzEventBase): @@ -251,7 +248,7 @@ class DeconzRelativeRotaryEvent(DeconzEventBase): def async_update_callback(self) -> None: """Fire the event if reason is new action is updated.""" if ( - self.gateway.ignore_state_updates + self.hub.ignore_state_updates or "rotaryevent" not in self._device.changed_keys ): return @@ -265,4 +262,4 @@ class DeconzRelativeRotaryEvent(DeconzEventBase): ATTR_DURATION: self._device.expected_event_duration, } - self.gateway.hass.bus.async_fire(CONF_DECONZ_RELATIVE_ROTARY_EVENT, data) + self.hub.hass.bus.async_fire(CONF_DECONZ_RELATIVE_ROTARY_EVENT, data) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index 27c82ab0be5..5e16d85ec4d 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -656,9 +656,9 @@ def _get_deconz_event_from_device( device: dr.DeviceEntry, ) -> DeconzAlarmEvent | DeconzEvent | DeconzPresenceEvent | DeconzRelativeRotaryEvent: """Resolve deconz event from device.""" - gateways: dict[str, DeconzHub] = hass.data.get(DOMAIN, {}) - for gateway in gateways.values(): - for deconz_event in gateway.events: + hubs: dict[str, DeconzHub] = hass.data.get(DOMAIN, {}) + for hub in hubs.values(): + for deconz_event in hub.events: if device.id == deconz_event.device_id: return deconz_event diff --git a/homeassistant/components/deconz/diagnostics.py b/homeassistant/components/deconz/diagnostics.py index 3200ea162e4..fcd5dec120f 100644 --- a/homeassistant/components/deconz/diagnostics.py +++ b/homeassistant/components/deconz/diagnostics.py @@ -9,7 +9,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_UNIQUE_ID from homeassistant.core import HomeAssistant -from .hub import get_gateway_from_config_entry +from .hub import DeconzHub REDACT_CONFIG = {CONF_API_KEY, CONF_UNIQUE_ID} REDACT_DECONZ_CONFIG = {"bridgeid", "mac", "panid"} @@ -19,29 +19,27 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict[str, Any]: """Return diagnostics for a config entry.""" - gateway = get_gateway_from_config_entry(hass, config_entry) + hub = DeconzHub.get_hub(hass, config_entry) diag: dict[str, Any] = {} diag["config"] = async_redact_data(config_entry.as_dict(), REDACT_CONFIG) - diag["deconz_config"] = async_redact_data( - gateway.api.config.raw, REDACT_DECONZ_CONFIG - ) + diag["deconz_config"] = async_redact_data(hub.api.config.raw, REDACT_DECONZ_CONFIG) diag["websocket_state"] = ( - gateway.api.websocket.state.value if gateway.api.websocket else "Unknown" + hub.api.websocket.state.value if hub.api.websocket else "Unknown" ) - diag["deconz_ids"] = gateway.deconz_ids - diag["entities"] = gateway.entities + diag["deconz_ids"] = hub.deconz_ids + diag["entities"] = hub.entities diag["events"] = { event.serial: { "event_id": event.event_id, "event_type": type(event).__name__, } - for event in gateway.events + for event in hub.events } - diag["alarm_systems"] = {k: v.raw for k, v in gateway.api.alarm_systems.items()} - diag["groups"] = {k: v.raw for k, v in gateway.api.groups.items()} - diag["lights"] = {k: v.raw for k, v in gateway.api.lights.items()} - diag["scenes"] = {k: v.raw for k, v in gateway.api.scenes.items()} - diag["sensors"] = {k: v.raw for k, v in gateway.api.sensors.items()} + diag["alarm_systems"] = {k: v.raw for k, v in hub.api.alarm_systems.items()} + diag["groups"] = {k: v.raw for k, v in hub.api.groups.items()} + diag["lights"] = {k: v.raw for k, v in hub.api.lights.items()} + diag["scenes"] = {k: v.raw for k, v in hub.api.scenes.items()} + diag["sensors"] = {k: v.raw for k, v in hub.api.sensors.items()} return diag diff --git a/homeassistant/components/deconz/fan.py b/homeassistant/components/deconz/fan.py index 45b1a83878f..ee5456aab4e 100644 --- a/homeassistant/components/deconz/fan.py +++ b/homeassistant/components/deconz/fan.py @@ -17,7 +17,7 @@ from homeassistant.util.percentage import ( ) from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub ORDERED_NAMED_FAN_SPEEDS: list[LightFanSpeed] = [ LightFanSpeed.PERCENT_25, @@ -33,20 +33,20 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up fans for deCONZ component.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_fan(_: EventType, fan_id: str) -> None: """Add fan from deCONZ.""" - fan = gateway.api.lights.lights[fan_id] + fan = hub.api.lights.lights[fan_id] if not fan.supports_fan_speed: return - async_add_entities([DeconzFan(fan, gateway)]) + async_add_entities([DeconzFan(fan, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_fan, - gateway.api.lights.lights, + hub.api.lights.lights, ) @@ -58,9 +58,9 @@ class DeconzFan(DeconzDevice[Light], FanEntity): _attr_supported_features = FanEntityFeature.SET_SPEED - def __init__(self, device: Light, gateway: DeconzHub) -> None: + def __init__(self, device: Light, hub: DeconzHub) -> None: """Set up fan.""" - super().__init__(device, gateway) + super().__init__(device, hub) _attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS) if device.fan_speed in ORDERED_NAMED_FAN_SPEEDS: self._default_on_speed = device.fan_speed @@ -92,7 +92,7 @@ class DeconzFan(DeconzDevice[Light], FanEntity): """Set the speed percentage of the fan.""" if percentage == 0: return await self.async_turn_off() - await self.gateway.api.lights.lights.set_state( + await self.hub.api.lights.lights.set_state( id=self._device.resource_id, fan_speed=percentage_to_ordered_list_item( ORDERED_NAMED_FAN_SPEEDS, percentage @@ -109,14 +109,14 @@ class DeconzFan(DeconzDevice[Light], FanEntity): if percentage is not None: await self.async_set_percentage(percentage) return - await self.gateway.api.lights.lights.set_state( + await self.hub.api.lights.lights.set_state( id=self._device.resource_id, fan_speed=self._default_on_speed, ) async def async_turn_off(self, **kwargs: Any) -> None: """Turn off fan.""" - await self.gateway.api.lights.lights.set_state( + await self.hub.api.lights.lights.set_state( id=self._device.resource_id, fan_speed=LightFanSpeed.OFF, ) diff --git a/homeassistant/components/deconz/hub/__init__.py b/homeassistant/components/deconz/hub/__init__.py index b3b70ef2207..e484bd5bb59 100644 --- a/homeassistant/components/deconz/hub/__init__.py +++ b/homeassistant/components/deconz/hub/__init__.py @@ -1,5 +1,4 @@ """Internal functionality not part of HA infrastructure.""" from .api import get_deconz_api # noqa: F401 -from .config import DeconzConfig # noqa: F401 -from .hub import DeconzHub, get_gateway_from_config_entry # noqa: F401 +from .hub import DeconzHub # noqa: F401 diff --git a/homeassistant/components/deconz/hub/hub.py b/homeassistant/components/deconz/hub/hub.py index c79ae90aa1a..ff958bbda50 100644 --- a/homeassistant/components/deconz/hub/hub.py +++ b/homeassistant/components/deconz/hub/hub.py @@ -94,6 +94,12 @@ class DeconzHub: self.deconz_groups: set[tuple[Callable[[EventType, str], None], str]] = set() self.ignored_devices: set[tuple[Callable[[EventType, str], None], str]] = set() + @callback + @staticmethod + def get_hub(hass: HomeAssistant, config_entry: ConfigEntry) -> DeconzHub: + """Return hub with a matching config entry ID.""" + return cast(DeconzHub, hass.data[DECONZ_DOMAIN][config_entry.entry_id]) + @property def bridgeid(self) -> str: """Return the unique identifier of the gateway.""" @@ -215,16 +221,16 @@ class DeconzHub: # A race condition can occur if multiple config entries are # unloaded in parallel return - gateway = get_gateway_from_config_entry(hass, config_entry) - previous_config = gateway.config - gateway.config = DeconzConfig.from_config_entry(config_entry) - if previous_config.host != gateway.config.host: - gateway.api.close() - gateway.api.host = gateway.config.host - gateway.api.start() + hub = DeconzHub.get_hub(hass, config_entry) + previous_config = hub.config + hub.config = DeconzConfig.from_config_entry(config_entry) + if previous_config.host != hub.config.host: + hub.api.close() + hub.api.host = hub.config.host + hub.api.start() return - await gateway.options_updated(previous_config) + await hub.options_updated(previous_config) async def options_updated(self, previous_config: DeconzConfig) -> None: """Manage entities affected by config entry options.""" @@ -292,11 +298,3 @@ class DeconzHub: self.deconz_ids = {} return True - - -@callback -def get_gateway_from_config_entry( - hass: HomeAssistant, config_entry: ConfigEntry -) -> DeconzHub: - """Return gateway with a matching config entry ID.""" - return cast(DeconzHub, hass.data[DECONZ_DOMAIN][config_entry.entry_id]) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 4565e72e9f5..fc5388d2b33 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -36,7 +36,7 @@ from homeassistant.util.color import color_hs_to_xy from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub DECONZ_GROUP = "is_deconz_group" EFFECT_TO_DECONZ = { @@ -111,13 +111,13 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ lights and groups from a config entry.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() entity_registry = er.async_get(hass) # On/Off Output should be switch not light 2022.5 - for light in gateway.api.lights.lights.values(): + for light in hub.api.lights.lights.values(): if light.type == ResourceType.ON_OFF_OUTPUT.value and ( entity_id := entity_registry.async_get_entity_id( DOMAIN, DECONZ_DOMAIN, light.unique_id @@ -128,15 +128,15 @@ async def async_setup_entry( @callback def async_add_light(_: EventType, light_id: str) -> None: """Add light from deCONZ.""" - light = gateway.api.lights.lights[light_id] + light = hub.api.lights.lights[light_id] if light.type in POWER_PLUGS: return - async_add_entities([DeconzLight(light, gateway)]) + async_add_entities([DeconzLight(light, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_light, - gateway.api.lights.lights, + hub.api.lights.lights, ) @callback @@ -145,20 +145,20 @@ async def async_setup_entry( Update group states based on its sum of related lights. """ - if (group := gateway.api.groups[group_id]) and not group.lights: + if (group := hub.api.groups[group_id]) and not group.lights: return first = True for light_id in group.lights: - if (light := gateway.api.lights.lights.get(light_id)) and light.reachable: + if (light := hub.api.lights.lights.get(light_id)) and light.reachable: group.update_color_state(light, update_all_attributes=first) first = False - async_add_entities([DeconzGroup(group, gateway)]) + async_add_entities([DeconzGroup(group, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_group, - gateway.api.groups, + hub.api.groups, ) @@ -168,15 +168,15 @@ class DeconzBaseLight(DeconzDevice[_LightDeviceT], LightEntity): TYPE = DOMAIN _attr_color_mode = ColorMode.UNKNOWN - def __init__(self, device: _LightDeviceT, gateway: DeconzHub) -> None: + def __init__(self, device: _LightDeviceT, hub: DeconzHub) -> None: """Set up light.""" - super().__init__(device, gateway) + super().__init__(device, hub) self.api: GroupHandler | LightHandler if isinstance(self._device, Light): - self.api = self.gateway.api.lights.lights + self.api = self.hub.api.lights.lights elif isinstance(self._device, Group): - self.api = self.gateway.api.groups + self.api = self.hub.api.groups self._attr_supported_color_modes: set[ColorMode] = set() @@ -324,7 +324,7 @@ class DeconzLight(DeconzBaseLight[Light]): super().async_update_callback() if self._device.reachable and "attr" not in self._device.changed_keys: - for group in self.gateway.api.groups.values(): + for group in self.hub.api.groups.values(): if self._device.resource_id in group.lights: group.update_color_state(self._device) @@ -334,10 +334,10 @@ class DeconzGroup(DeconzBaseLight[Group]): _attr_has_entity_name = True - def __init__(self, device: Group, gateway: DeconzHub) -> None: + def __init__(self, device: Group, hub: DeconzHub) -> None: """Set up group and create an unique id.""" - self._unique_id = f"{gateway.bridgeid}-{device.deconz_id}" - super().__init__(device, gateway) + self._unique_id = f"{hub.bridgeid}-{device.deconz_id}" + super().__init__(device, hub) self._attr_name = None @@ -354,7 +354,7 @@ class DeconzGroup(DeconzBaseLight[Group]): manufacturer="Dresden Elektronik", model="deCONZ group", name=self._device.name, - via_device=(DECONZ_DOMAIN, self.gateway.api.config.bridge_id), + via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id), ) @property diff --git a/homeassistant/components/deconz/lock.py b/homeassistant/components/deconz/lock.py index c5fdcd43625..8729d7de793 100644 --- a/homeassistant/components/deconz/lock.py +++ b/homeassistant/components/deconz/lock.py @@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .hub import get_gateway_from_config_entry +from .hub import DeconzHub async def async_setup_entry( @@ -23,29 +23,29 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up locks for deCONZ component.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_lock_from_light(_: EventType, lock_id: str) -> None: """Add lock from deCONZ.""" - lock = gateway.api.lights.locks[lock_id] - async_add_entities([DeconzLock(lock, gateway)]) + lock = hub.api.lights.locks[lock_id] + async_add_entities([DeconzLock(lock, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_lock_from_light, - gateway.api.lights.locks, + hub.api.lights.locks, ) @callback def async_add_lock_from_sensor(_: EventType, lock_id: str) -> None: """Add lock from deCONZ.""" - lock = gateway.api.sensors.door_lock[lock_id] - async_add_entities([DeconzLock(lock, gateway)]) + lock = hub.api.sensors.door_lock[lock_id] + async_add_entities([DeconzLock(lock, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_lock_from_sensor, - gateway.api.sensors.door_lock, + hub.api.sensors.door_lock, always_ignore_clip_sensors=True, ) @@ -63,12 +63,12 @@ class DeconzLock(DeconzDevice[DoorLock | Lock], LockEntity): async def async_lock(self, **kwargs: Any) -> None: """Lock the lock.""" if isinstance(self._device, DoorLock): - await self.gateway.api.sensors.door_lock.set_config( + await self.hub.api.sensors.door_lock.set_config( id=self._device.resource_id, lock=True, ) else: - await self.gateway.api.lights.locks.set_state( + await self.hub.api.lights.locks.set_state( id=self._device.resource_id, lock=True, ) @@ -76,12 +76,12 @@ class DeconzLock(DeconzDevice[DoorLock | Lock], LockEntity): async def async_unlock(self, **kwargs: Any) -> None: """Unlock the lock.""" if isinstance(self._device, DoorLock): - await self.gateway.api.sensors.door_lock.set_config( + await self.hub.api.sensors.door_lock.set_config( id=self._device.resource_id, lock=False, ) else: - await self.gateway.api.lights.locks.set_state( + await self.hub.api.lights.locks.set_state( id=self._device.resource_id, lock=False, ) diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index 6464a99fd7a..03c25668820 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -23,7 +23,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub T = TypeVar("T", Presence, PydeconzSensorBase) @@ -73,13 +73,13 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ number entity.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_sensor(_: EventType, sensor_id: str) -> None: """Add sensor from deCONZ.""" - sensor = gateway.api.sensors.presence[sensor_id] + sensor = hub.api.sensors.presence[sensor_id] for description in ENTITY_DESCRIPTIONS: if ( @@ -87,11 +87,11 @@ async def async_setup_entry( or description.value_fn(sensor) is None ): continue - async_add_entities([DeconzNumber(sensor, gateway, description)]) + async_add_entities([DeconzNumber(sensor, hub, description)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors.presence, + hub.api.sensors.presence, always_ignore_clip_sensors=True, ) @@ -105,7 +105,7 @@ class DeconzNumber(DeconzDevice[SensorResources], NumberEntity): def __init__( self, device: SensorResources, - gateway: DeconzHub, + hub: DeconzHub, description: DeconzNumberDescription, ) -> None: """Initialize deCONZ number entity.""" @@ -113,7 +113,7 @@ class DeconzNumber(DeconzDevice[SensorResources], NumberEntity): self.unique_id_suffix = description.key self._name_suffix = description.name_suffix self._update_key = description.update_key - super().__init__(device, gateway) + super().__init__(device, hub) @property def native_value(self) -> float | None: @@ -123,7 +123,7 @@ class DeconzNumber(DeconzDevice[SensorResources], NumberEntity): async def async_set_native_value(self, value: float) -> None: """Set sensor config.""" await self.entity_description.set_fn( - self.gateway.api, + self.hub.api, self._device.resource_id, int(value), ) diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index 75e0a5ff44d..f121c3107b0 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzSceneMixin -from .hub import get_gateway_from_config_entry +from .hub import DeconzHub async def async_setup_entry( @@ -21,18 +21,18 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up scenes for deCONZ integration.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_scene(_: EventType, scene_id: str) -> None: """Add scene from deCONZ.""" - scene = gateway.api.scenes[scene_id] - async_add_entities([DeconzScene(scene, gateway)]) + scene = hub.api.scenes[scene_id] + async_add_entities([DeconzScene(scene, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_scene, - gateway.api.scenes, + hub.api.scenes, ) @@ -43,7 +43,7 @@ class DeconzScene(DeconzSceneMixin, Scene): async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" - await self.gateway.api.scenes.recall( + await self.hub.api.scenes.recall( self._device.group_id, self._device.id, ) diff --git a/homeassistant/components/deconz/select.py b/homeassistant/components/deconz/select.py index 7720df7c8cd..dad3ba9d78d 100644 --- a/homeassistant/components/deconz/select.py +++ b/homeassistant/components/deconz/select.py @@ -17,7 +17,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .hub import get_gateway_from_config_entry +from .hub import DeconzHub SENSITIVITY_TO_DECONZ = { "High": PresenceConfigSensitivity.HIGH.value, @@ -33,25 +33,25 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ button entity.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_presence_sensor(_: EventType, sensor_id: str) -> None: """Add presence select entity from deCONZ.""" - sensor = gateway.api.sensors.presence[sensor_id] + sensor = hub.api.sensors.presence[sensor_id] if sensor.presence_event is not None: async_add_entities( [ - DeconzPresenceDeviceModeSelect(sensor, gateway), - DeconzPresenceSensitivitySelect(sensor, gateway), - DeconzPresenceTriggerDistanceSelect(sensor, gateway), + DeconzPresenceDeviceModeSelect(sensor, hub), + DeconzPresenceSensitivitySelect(sensor, hub), + DeconzPresenceTriggerDistanceSelect(sensor, hub), ] ) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_presence_sensor, - gateway.api.sensors.presence, + hub.api.sensors.presence, ) @@ -79,7 +79,7 @@ class DeconzPresenceDeviceModeSelect(DeconzDevice[Presence], SelectEntity): async def async_select_option(self, option: str) -> None: """Change the selected option.""" - await self.gateway.api.sensors.presence.set_config( + await self.hub.api.sensors.presence.set_config( id=self._device.resource_id, device_mode=PresenceConfigDeviceMode(option), ) @@ -106,7 +106,7 @@ class DeconzPresenceSensitivitySelect(DeconzDevice[Presence], SelectEntity): async def async_select_option(self, option: str) -> None: """Change the selected option.""" - await self.gateway.api.sensors.presence.set_config( + await self.hub.api.sensors.presence.set_config( id=self._device.resource_id, sensitivity=SENSITIVITY_TO_DECONZ[option], ) @@ -137,7 +137,7 @@ class DeconzPresenceTriggerDistanceSelect(DeconzDevice[Presence], SelectEntity): async def async_select_option(self, option: str) -> None: """Change the selected option.""" - await self.gateway.api.sensors.presence.set_config( + await self.hub.api.sensors.presence.set_config( id=self._device.resource_id, trigger_distance=PresenceConfigTriggerDistance(option), ) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 8ea589ac7e0..750019dc680 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -53,7 +53,7 @@ import homeassistant.util.dt as dt_util from .const import ATTR_DARK, ATTR_ON from .deconz_device import DeconzDevice -from .hub import DeconzHub, get_gateway_from_config_entry +from .hub import DeconzHub PROVIDES_EXTRA_ATTRIBUTES = ( "battery", @@ -295,8 +295,8 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the deCONZ sensors.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() known_device_entities: dict[str, set[str]] = { description.key: set() @@ -307,7 +307,7 @@ async def async_setup_entry( @callback def async_add_sensor(_: EventType, sensor_id: str) -> None: """Add sensor from deCONZ.""" - sensor = gateway.api.sensors[sensor_id] + sensor = hub.api.sensors[sensor_id] entities: list[DeconzSensor] = [] for description in ENTITY_DESCRIPTIONS: @@ -333,20 +333,20 @@ async def async_setup_entry( known_device_entities[description.key].add(unique_id) if no_sensor_data and description.key == "battery": DeconzBatteryTracker( - sensor_id, gateway, description, async_add_entities + sensor_id, hub, description, async_add_entities ) continue if no_sensor_data: continue - entities.append(DeconzSensor(sensor, gateway, description)) + entities.append(DeconzSensor(sensor, hub, description)) async_add_entities(entities) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_sensor, - gateway.api.sensors, + hub.api.sensors, ) @@ -359,7 +359,7 @@ class DeconzSensor(DeconzDevice[SensorResources], SensorEntity): def __init__( self, device: SensorResources, - gateway: DeconzHub, + hub: DeconzHub, description: DeconzSensorDescription, ) -> None: """Initialize deCONZ sensor.""" @@ -368,7 +368,7 @@ class DeconzSensor(DeconzDevice[SensorResources], SensorEntity): self._update_key = description.update_key if description.name_suffix: self._name_suffix = description.name_suffix - super().__init__(device, gateway) + super().__init__(device, hub) if ( self.entity_description.key in PROVIDES_EXTRA_ATTRIBUTES @@ -413,7 +413,7 @@ class DeconzSensor(DeconzDevice[SensorResources], SensorEntity): attr[ATTR_VOLTAGE] = self._device.voltage elif isinstance(self._device, Switch): - for event in self.gateway.events: + for event in self.hub.events: if self._device == event.device: attr[ATTR_EVENT_ID] = event.event_id @@ -426,13 +426,13 @@ class DeconzBatteryTracker: def __init__( self, sensor_id: str, - gateway: DeconzHub, + hub: DeconzHub, description: DeconzSensorDescription, async_add_entities: AddEntitiesCallback, ) -> None: """Set up tracker.""" - self.sensor = gateway.api.sensors[sensor_id] - self.gateway = gateway + self.sensor = hub.api.sensors[sensor_id] + self.hub = hub self.description = description self.async_add_entities = async_add_entities self.unsubscribe = self.sensor.subscribe(self.async_update_callback) @@ -443,5 +443,5 @@ class DeconzBatteryTracker: if self.description.update_key in self.sensor.changed_keys: self.unsubscribe() self.async_add_entities( - [DeconzSensor(self.sensor, self.gateway, self.description)] + [DeconzSensor(self.sensor, self.hub, self.description)] ) diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index d27c2a67615..91f36bb871e 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity_registry import ( ) from homeassistant.util.read_only_dict import ReadOnlyDict -from .config_flow import get_master_gateway +from .config_flow import get_master_hub from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER from .hub import DeconzHub @@ -66,33 +66,33 @@ def async_setup_services(hass: HomeAssistant) -> None: service_data = service_call.data if CONF_BRIDGE_ID in service_data: - found_gateway = False + found_hub = False bridge_id = normalize_bridge_id(service_data[CONF_BRIDGE_ID]) - for possible_gateway in hass.data[DOMAIN].values(): - if possible_gateway.bridgeid == bridge_id: - gateway = possible_gateway - found_gateway = True + for possible_hub in hass.data[DOMAIN].values(): + if possible_hub.bridgeid == bridge_id: + hub = possible_hub + found_hub = True break - if not found_gateway: + if not found_hub: LOGGER.error("Could not find the gateway %s", bridge_id) return else: try: - gateway = get_master_gateway(hass) + hub = get_master_hub(hass) except ValueError: LOGGER.error("No master gateway available") return if service == SERVICE_CONFIGURE_DEVICE: - await async_configure_service(gateway, service_data) + await async_configure_service(hub, service_data) elif service == SERVICE_DEVICE_REFRESH: - await async_refresh_devices_service(gateway) + await async_refresh_devices_service(hub) elif service == SERVICE_REMOVE_ORPHANED_ENTRIES: - await async_remove_orphaned_entries_service(gateway) + await async_remove_orphaned_entries_service(hub) for service in SUPPORTED_SERVICES: hass.services.async_register( @@ -110,7 +110,7 @@ def async_unload_services(hass: HomeAssistant) -> None: hass.services.async_remove(DOMAIN, service) -async def async_configure_service(gateway: DeconzHub, data: ReadOnlyDict) -> None: +async def async_configure_service(hub: DeconzHub, data: ReadOnlyDict) -> None: """Set attribute of device in deCONZ. Entity is used to resolve to a device path (e.g. '/lights/1'). @@ -132,61 +132,61 @@ async def async_configure_service(gateway: DeconzHub, data: ReadOnlyDict) -> Non if entity_id: try: - field = gateway.deconz_ids[entity_id] + field + field = hub.deconz_ids[entity_id] + field except KeyError: LOGGER.error("Could not find the entity %s", entity_id) return - await gateway.api.request("put", field, json=data) + await hub.api.request("put", field, json=data) -async def async_refresh_devices_service(gateway: DeconzHub) -> None: +async def async_refresh_devices_service(hub: DeconzHub) -> None: """Refresh available devices from deCONZ.""" - gateway.ignore_state_updates = True - await gateway.api.refresh_state() - gateway.load_ignored_devices() - gateway.ignore_state_updates = False + hub.ignore_state_updates = True + await hub.api.refresh_state() + hub.load_ignored_devices() + hub.ignore_state_updates = False -async def async_remove_orphaned_entries_service(gateway: DeconzHub) -> None: +async def async_remove_orphaned_entries_service(hub: DeconzHub) -> None: """Remove orphaned deCONZ entries from device and entity registries.""" - device_registry = dr.async_get(gateway.hass) - entity_registry = er.async_get(gateway.hass) + device_registry = dr.async_get(hub.hass) + entity_registry = er.async_get(hub.hass) entity_entries = async_entries_for_config_entry( - entity_registry, gateway.config_entry.entry_id + entity_registry, hub.config_entry.entry_id ) entities_to_be_removed = [] devices_to_be_removed = [ entry.id for entry in device_registry.devices.values() - if gateway.config_entry.entry_id in entry.config_entries + if hub.config_entry.entry_id in entry.config_entries ] # Don't remove the Gateway host entry - if gateway.api.config.mac: - gateway_host = device_registry.async_get_device( - connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)}, + if hub.api.config.mac: + hub_host = device_registry.async_get_device( + connections={(CONNECTION_NETWORK_MAC, hub.api.config.mac)}, ) - if gateway_host and gateway_host.id in devices_to_be_removed: - devices_to_be_removed.remove(gateway_host.id) + if hub_host and hub_host.id in devices_to_be_removed: + devices_to_be_removed.remove(hub_host.id) # Don't remove the Gateway service entry - gateway_service = device_registry.async_get_device( - identifiers={(DOMAIN, gateway.api.config.bridge_id)} + hub_service = device_registry.async_get_device( + identifiers={(DOMAIN, hub.api.config.bridge_id)} ) - if gateway_service and gateway_service.id in devices_to_be_removed: - devices_to_be_removed.remove(gateway_service.id) + if hub_service and hub_service.id in devices_to_be_removed: + devices_to_be_removed.remove(hub_service.id) # Don't remove devices belonging to available events - for event in gateway.events: + for event in hub.events: if event.device_id in devices_to_be_removed: devices_to_be_removed.remove(event.device_id) for entry in entity_entries: # Don't remove available entities - if entry.unique_id in gateway.entities[entry.domain]: + if entry.unique_id in hub.entities[entry.domain]: # Don't remove devices with available entities if entry.device_id in devices_to_be_removed: devices_to_be_removed.remove(entry.device_id) diff --git a/homeassistant/components/deconz/siren.py b/homeassistant/components/deconz/siren.py index e054dc8b473..deb1c98f151 100644 --- a/homeassistant/components/deconz/siren.py +++ b/homeassistant/components/deconz/siren.py @@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice -from .hub import get_gateway_from_config_entry +from .hub import DeconzHub async def async_setup_entry( @@ -27,18 +27,18 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up sirens for deCONZ component.""" - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_siren(_: EventType, siren_id: str) -> None: """Add siren from deCONZ.""" - siren = gateway.api.lights.sirens[siren_id] - async_add_entities([DeconzSiren(siren, gateway)]) + siren = hub.api.lights.sirens[siren_id] + async_add_entities([DeconzSiren(siren, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_siren, - gateway.api.lights.sirens, + hub.api.lights.sirens, ) @@ -61,7 +61,7 @@ class DeconzSiren(DeconzDevice[Siren], SirenEntity): """Turn on siren.""" if (duration := kwargs.get(ATTR_DURATION)) is not None: duration *= 10 - await self.gateway.api.lights.sirens.set_state( + await self.hub.api.lights.sirens.set_state( id=self._device.resource_id, on=True, duration=duration, @@ -69,7 +69,7 @@ class DeconzSiren(DeconzDevice[Siren], SirenEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn off siren.""" - await self.gateway.api.lights.sirens.set_state( + await self.hub.api.lights.sirens.set_state( id=self._device.resource_id, on=False, ) diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index 214f3f17897..e176d9c7710 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -14,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import POWER_PLUGS from .deconz_device import DeconzDevice -from .hub import get_gateway_from_config_entry +from .hub import DeconzHub async def async_setup_entry( @@ -26,20 +26,20 @@ async def async_setup_entry( Switches are based on the same device class as lights in deCONZ. """ - gateway = get_gateway_from_config_entry(hass, config_entry) - gateway.entities[DOMAIN] = set() + hub = DeconzHub.get_hub(hass, config_entry) + hub.entities[DOMAIN] = set() @callback def async_add_switch(_: EventType, switch_id: str) -> None: """Add switch from deCONZ.""" - switch = gateway.api.lights.lights[switch_id] + switch = hub.api.lights.lights[switch_id] if switch.type not in POWER_PLUGS: return - async_add_entities([DeconzPowerPlug(switch, gateway)]) + async_add_entities([DeconzPowerPlug(switch, hub)]) - gateway.register_platform_add_device_callback( + hub.register_platform_add_device_callback( async_add_switch, - gateway.api.lights.lights, + hub.api.lights.lights, ) @@ -55,14 +55,14 @@ class DeconzPowerPlug(DeconzDevice[Light], SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Turn on switch.""" - await self.gateway.api.lights.lights.set_state( + await self.hub.api.lights.lights.set_state( id=self._device.resource_id, on=True, ) async def async_turn_off(self, **kwargs: Any) -> None: """Turn off switch.""" - await self.gateway.api.lights.lights.set_state( + await self.hub.api.lights.lights.set_state( id=self._device.resource_id, on=False, ) diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index a1b28ada799..d984354adca 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -18,10 +18,7 @@ from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.components.deconz.config_flow import DECONZ_MANUFACTURERURL from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.errors import AuthenticationRequired, CannotConnect -from homeassistant.components.deconz.hub import ( - get_deconz_api, - get_gateway_from_config_entry, -) +from homeassistant.components.deconz.hub import DeconzHub, get_deconz_api from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN @@ -149,7 +146,7 @@ async def test_gateway_setup( return_value=True, ) as forward_entry_setup: config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + gateway = DeconzHub.get_hub(hass, config_entry) assert gateway.bridgeid == BRIDGEID assert gateway.master is True assert gateway.config.allow_clip_sensor is False @@ -201,7 +198,7 @@ async def test_gateway_device_configuration_url_when_addon( config_entry = await setup_deconz_integration( hass, aioclient_mock, source=SOURCE_HASSIO ) - gateway = get_gateway_from_config_entry(hass, config_entry) + gateway = DeconzHub.get_hub(hass, config_entry) gateway_entry = device_registry.async_get_device( identifiers={(DECONZ_DOMAIN, gateway.bridgeid)} @@ -248,7 +245,7 @@ async def test_update_address( ) -> None: """Make sure that connection status triggers a dispatcher send.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + gateway = DeconzHub.get_hub(hass, config_entry) assert gateway.api.host == "1.2.3.4" with patch( @@ -280,7 +277,7 @@ async def test_reset_after_successful_setup( ) -> None: """Make sure that connection status triggers a dispatcher send.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) - gateway = get_gateway_from_config_entry(hass, config_entry) + gateway = DeconzHub.get_hub(hass, config_entry) result = await gateway.async_reset() await hass.async_block_till_done() From 541d4b78ac6e9009f3d4b67dec13305ed0578495 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 18 Mar 2024 22:17:13 +0100 Subject: [PATCH 1250/1691] Add WebSocket support for handling labels on areas registry (#113755) --- homeassistant/components/config/area_registry.py | 11 +++++++++++ tests/components/config/test_area_registry.py | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/homeassistant/components/config/area_registry.py b/homeassistant/components/config/area_registry.py index acddf7bb200..a499ab84784 100644 --- a/homeassistant/components/config/area_registry.py +++ b/homeassistant/components/config/area_registry.py @@ -42,6 +42,7 @@ def websocket_list_areas( vol.Optional("aliases"): list, vol.Optional("floor_id"): str, vol.Optional("icon"): str, + vol.Optional("labels"): [str], vol.Required("name"): str, vol.Optional("picture"): vol.Any(str, None), } @@ -64,6 +65,10 @@ def websocket_create_area( # Convert aliases to a set data["aliases"] = set(data["aliases"]) + if "labels" in data: + # Convert labels to a set + data["labels"] = set(data["labels"]) + try: entry = registry.async_create(**data) except ValueError as err: @@ -103,6 +108,7 @@ def websocket_delete_area( vol.Required("area_id"): str, vol.Optional("floor_id"): vol.Any(str, None), vol.Optional("icon"): vol.Any(str, None), + vol.Optional("labels"): [str], vol.Optional("name"): str, vol.Optional("picture"): vol.Any(str, None), } @@ -125,6 +131,10 @@ def websocket_update_area( # Convert aliases to a set data["aliases"] = set(data["aliases"]) + if "labels" in data: + # Convert labels to a set + data["labels"] = set(data["labels"]) + try: entry = registry.async_update(**data) except ValueError as err: @@ -141,6 +151,7 @@ def _entry_dict(entry: AreaEntry) -> dict[str, Any]: "area_id": entry.id, "floor_id": entry.floor_id, "icon": entry.icon, + "labels": list(entry.labels), "name": entry.name, "picture": entry.picture, } diff --git a/tests/components/config/test_area_registry.py b/tests/components/config/test_area_registry.py index 8bedd1a0a29..fb59725fd29 100644 --- a/tests/components/config/test_area_registry.py +++ b/tests/components/config/test_area_registry.py @@ -31,6 +31,7 @@ async def test_list_areas( icon="mdi:garage", picture="/image/example.png", floor_id="first_floor", + labels={"label_1", "label_2"}, ) await client.send_json_auto_id({"type": "config/area_registry/list"}) @@ -42,6 +43,7 @@ async def test_list_areas( "area_id": area1.id, "floor_id": None, "icon": None, + "labels": [], "name": "mock 1", "picture": None, }, @@ -50,6 +52,7 @@ async def test_list_areas( "area_id": area2.id, "floor_id": "first_floor", "icon": "mdi:garage", + "labels": unordered(["label_1", "label_2"]), "name": "mock 2", "picture": "/image/example.png", }, @@ -72,6 +75,7 @@ async def test_create_area( "area_id": ANY, "floor_id": None, "icon": None, + "labels": [], "name": "mock", "picture": None, } @@ -83,6 +87,7 @@ async def test_create_area( "aliases": ["alias_1", "alias_2"], "floor_id": "first_floor", "icon": "mdi:garage", + "labels": ["label_1", "label_2"], "name": "mock 2", "picture": "/image/example.png", "type": "config/area_registry/create", @@ -96,6 +101,7 @@ async def test_create_area( "area_id": ANY, "floor_id": "first_floor", "icon": "mdi:garage", + "labels": unordered(["label_1", "label_2"]), "name": "mock 2", "picture": "/image/example.png", } @@ -166,6 +172,7 @@ async def test_update_area( "area_id": area.id, "floor_id": "first_floor", "icon": "mdi:garage", + "labels": ["label_1", "label_2"], "name": "mock 2", "picture": "/image/example.png", "type": "config/area_registry/update", @@ -179,6 +186,7 @@ async def test_update_area( "area_id": area.id, "floor_id": "first_floor", "icon": "mdi:garage", + "labels": unordered(["label_1", "label_2"]), "name": "mock 2", "picture": "/image/example.png", } @@ -190,6 +198,7 @@ async def test_update_area( "area_id": area.id, "floor_id": None, "icon": None, + "labels": [], "picture": None, "type": "config/area_registry/update", } @@ -202,6 +211,7 @@ async def test_update_area( "area_id": area.id, "floor_id": None, "icon": None, + "labels": [], "name": "mock 2", "picture": None, } From 167e66d45c27e295b6468119beadea563f68d2df Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 18 Mar 2024 22:32:23 +0100 Subject: [PATCH 1251/1691] Add labels to service target (#113753) --- homeassistant/const.py | 3 + homeassistant/helpers/config_validation.py | 7 + homeassistant/helpers/service.py | 50 ++++- tests/helpers/test_service.py | 205 ++++++++++++++++++++- 4 files changed, 256 insertions(+), 9 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 28409ba0907..ac934dcc0d6 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -513,6 +513,9 @@ ATTR_DEVICE_ID: Final = "device_id" # Contains one string or a list of strings, each being an floor id ATTR_FLOOR_ID: Final = "floor_id" +# Contains one string or a list of strings, each being an label id +ATTR_LABEL_ID: Final = "label_id" + # String with a friendly name for the entity ATTR_FRIENDLY_NAME: Final = "friendly_name" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index fbe98ccd387..bf666cf2e03 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -30,6 +30,7 @@ from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ENTITY_ID, ATTR_FLOOR_ID, + ATTR_LABEL_ID, CONF_ABOVE, CONF_ALIAS, CONF_ATTRIBUTE, @@ -1220,6 +1221,9 @@ ENTITY_SERVICE_FIELDS = { vol.Optional(ATTR_FLOOR_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), + vol.Optional(ATTR_LABEL_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), } TARGET_SERVICE_FIELDS = { @@ -1240,6 +1244,9 @@ TARGET_SERVICE_FIELDS = { vol.Optional(ATTR_FLOOR_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), + vol.Optional(ATTR_LABEL_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), } diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index df8284c0b4c..34cee93d971 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -19,6 +19,7 @@ from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ENTITY_ID, ATTR_FLOOR_ID, + ATTR_LABEL_ID, CONF_ENTITY_ID, CONF_SERVICE, CONF_SERVICE_DATA, @@ -55,6 +56,7 @@ from . import ( device_registry, entity_registry, floor_registry, + label_registry, template, translation, ) @@ -196,7 +198,7 @@ class ServiceParams(TypedDict): class ServiceTargetSelector: """Class to hold a target selector for a service.""" - __slots__ = ("entity_ids", "device_ids", "area_ids", "floor_ids") + __slots__ = ("entity_ids", "device_ids", "area_ids", "floor_ids", "label_ids") def __init__(self, service_call: ServiceCall) -> None: """Extract ids from service call data.""" @@ -205,6 +207,7 @@ class ServiceTargetSelector: device_ids: str | list | None = service_call_data.get(ATTR_DEVICE_ID) area_ids: str | list | None = service_call_data.get(ATTR_AREA_ID) floor_ids: str | list | None = service_call_data.get(ATTR_FLOOR_ID) + label_ids: str | list | None = service_call_data.get(ATTR_LABEL_ID) self.entity_ids = ( set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() @@ -216,12 +219,19 @@ class ServiceTargetSelector: self.floor_ids = ( set(cv.ensure_list(floor_ids)) if _has_match(floor_ids) else set() ) + self.label_ids = ( + set(cv.ensure_list(label_ids)) if _has_match(label_ids) else set() + ) @property def has_any_selector(self) -> bool: """Determine if any selectors are present.""" return bool( - self.entity_ids or self.device_ids or self.area_ids or self.floor_ids + self.entity_ids + or self.device_ids + or self.area_ids + or self.floor_ids + or self.label_ids ) @@ -232,7 +242,7 @@ class SelectedEntities: # Entities that were explicitly mentioned. referenced: set[str] = dataclasses.field(default_factory=set) - # Entities that were referenced via device/area/floor ID. + # Entities that were referenced via device/area/floor/label ID. # Should not trigger a warning when they don't exist. indirectly_referenced: set[str] = dataclasses.field(default_factory=set) @@ -240,6 +250,7 @@ class SelectedEntities: missing_devices: set[str] = dataclasses.field(default_factory=set) missing_areas: set[str] = dataclasses.field(default_factory=set) missing_floors: set[str] = dataclasses.field(default_factory=set) + missing_labels: set[str] = dataclasses.field(default_factory=set) # Referenced devices referenced_devices: set[str] = dataclasses.field(default_factory=set) @@ -253,6 +264,7 @@ class SelectedEntities: ("areas", self.missing_areas), ("devices", self.missing_devices), ("entities", missing_entities), + ("labels", self.missing_labels), ): if items: parts.append(f"{label} {', '.join(sorted(items))}") @@ -467,7 +479,7 @@ def _has_match(ids: str | list[str] | None) -> TypeGuard[str | list[str]]: @bind_hass -def async_extract_referenced_entity_ids( +def async_extract_referenced_entity_ids( # noqa: C901 hass: HomeAssistant, service_call: ServiceCall, expand_group: bool = True ) -> SelectedEntities: """Extract referenced entity IDs from a service call.""" @@ -483,13 +495,19 @@ def async_extract_referenced_entity_ids( selected.referenced.update(entity_ids) - if not selector.device_ids and not selector.area_ids and not selector.floor_ids: + if ( + not selector.device_ids + and not selector.area_ids + and not selector.floor_ids + and not selector.label_ids + ): return selected ent_reg = entity_registry.async_get(hass) dev_reg = device_registry.async_get(hass) area_reg = area_registry.async_get(hass) floor_reg = floor_registry.async_get(hass) + label_reg = label_registry.async_get(hass) for floor_id in selector.floor_ids: if floor_id not in floor_reg.floors: @@ -503,6 +521,28 @@ def async_extract_referenced_entity_ids( if device_id not in dev_reg.devices: selected.missing_devices.add(device_id) + for label_id in selector.label_ids: + if label_id not in label_reg.labels: + selected.missing_labels.add(label_id) + + # Find areas, devices & entities for targeted labels + if selector.label_ids: + for area_entry in area_reg.areas.values(): + if area_entry.labels.intersection(selector.label_ids): + selected.referenced_areas.add(area_entry.id) + + for device_entry in dev_reg.devices.values(): + if device_entry.labels.intersection(selector.label_ids): + selected.referenced_devices.add(device_entry.id) + + for entity_entry in ent_reg.entities.values(): + if ( + entity_entry.entity_category is None + and entity_entry.hidden_by is None + and entity_entry.labels.intersection(selector.label_ids) + ): + selected.indirectly_referenced.add(entity_entry.entity_id) + # Find areas for targeted floors if selector.floor_ids: for area_entry in area_reg.areas.values(): diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 2209e60a37d..4f889478460 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -266,6 +266,120 @@ def floor_area_mock(hass: HomeAssistant) -> None: ) +@pytest.fixture +def label_mock(hass: HomeAssistant) -> None: + """Mock including label info.""" + hass.states.async_set("light.bowl", STATE_ON) + hass.states.async_set("light.ceiling", STATE_OFF) + hass.states.async_set("light.kitchen", STATE_OFF) + + area_with_labels = ar.AreaEntry( + id="area-with-labels", + name="Area with labels", + aliases={}, + normalized_name="with_labels", + floor_id=None, + icon=None, + labels={"label_area"}, + picture=None, + ) + area_without_labels = ar.AreaEntry( + id="area-no-labels", + name="Area without labels", + aliases={}, + normalized_name="without_labels", + floor_id=None, + icon=None, + labels=set(), + picture=None, + ) + mock_area_registry( + hass, + { + area_with_labels.id: area_with_labels, + area_without_labels.id: area_without_labels, + }, + ) + + device_has_label1 = dr.DeviceEntry(labels={"label1"}) + device_has_label2 = dr.DeviceEntry(labels={"label2"}) + device_has_labels = dr.DeviceEntry( + labels={"label1", "label2"}, area_id=area_with_labels.id + ) + device_no_labels = dr.DeviceEntry( + id="device-no-labels", area_id=area_without_labels.id + ) + + mock_device_registry( + hass, + { + device_has_label1.id: device_has_label1, + device_has_label2.id: device_has_label2, + device_has_labels.id: device_has_labels, + device_no_labels.id: device_no_labels, + }, + ) + + entity_with_my_label = er.RegistryEntry( + entity_id="light.with_my_label", + unique_id="with_my_label", + platform="test", + labels={"my-label"}, + ) + hidden_entity_with_my_label = er.RegistryEntry( + entity_id="light.hidden_with_my_label", + unique_id="hidden_with_my_label", + platform="test", + labels={"my-label"}, + hidden_by=er.RegistryEntryHider.USER, + ) + config_entity_with_my_label = er.RegistryEntry( + entity_id="light.config_with_my_label", + unique_id="config_with_my_label", + platform="test", + labels={"my-label"}, + entity_category=EntityCategory.CONFIG, + ) + entity_with_label1_from_device = er.RegistryEntry( + entity_id="light.with_label1_from_device", + unique_id="with_label1_from_device", + platform="test", + device_id=device_has_label1.id, + ) + entity_with_label1_and_label2_from_device = er.RegistryEntry( + entity_id="light.with_label1_and_label2_from_device", + unique_id="with_label1_and_label2_from_device", + platform="test", + labels={"label1"}, + device_id=device_has_label2.id, + ) + entity_with_labels_from_device = er.RegistryEntry( + entity_id="light.with_labels_from_device", + unique_id="with_labels_from_device", + platform="test", + device_id=device_has_labels.id, + ) + entity_with_no_labels = er.RegistryEntry( + entity_id="light.no_labels", + unique_id="no_labels", + platform="test", + device_id=device_no_labels.id, + ) + + mock_registry( + hass, + { + config_entity_with_my_label.entity_id: config_entity_with_my_label, + entity_with_label1_and_label2_from_device.entity_id: entity_with_label1_and_label2_from_device, + entity_with_label1_from_device.entity_id: entity_with_label1_from_device, + entity_with_labels_from_device.entity_id: entity_with_labels_from_device, + entity_with_my_label.entity_id: entity_with_my_label, + entity_with_no_labels.entity_id: entity_with_no_labels, + hidden_entity_with_my_label.entity_id: hidden_entity_with_my_label, + }, + ) + + async def test_call_from_config(hass: HomeAssistant) -> None: """Test the sync wrapper of service.async_call_from_config.""" calls = async_mock_service(hass, "test_domain", "test_service") @@ -629,6 +743,44 @@ async def test_extract_entity_ids_from_floor(hass: HomeAssistant) -> None: ) +@pytest.mark.usefixtures("label_mock") +async def test_extract_entity_ids_from_labels(hass: HomeAssistant) -> None: + """Test extract_entity_ids method with labels.""" + call = ServiceCall("light", "turn_on", {"label_id": "my-label"}) + + assert { + "light.with_my_label", + } == await service.async_extract_entity_ids(hass, call) + + call = ServiceCall("light", "turn_on", {"label_id": "label1"}) + + assert { + "light.with_label1_from_device", + "light.with_labels_from_device", + "light.with_label1_and_label2_from_device", + } == await service.async_extract_entity_ids(hass, call) + + call = ServiceCall("light", "turn_on", {"label_id": ["label2"]}) + + assert { + "light.with_labels_from_device", + "light.with_label1_and_label2_from_device", + } == await service.async_extract_entity_ids(hass, call) + + call = ServiceCall("light", "turn_on", {"label_id": ["label_area"]}) + + assert { + "light.with_labels_from_device", + } == await service.async_extract_entity_ids(hass, call) + + assert ( + await service.async_extract_entity_ids( + hass, ServiceCall("light", "turn_on", {"label_id": ENTITY_MATCH_NONE}) + ) + == set() + ) + + async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: """Test async_get_all_descriptions.""" group_config = {DOMAIN_GROUP: {}} @@ -1578,6 +1730,49 @@ async def test_extract_from_service_area_id( ] +@pytest.mark.usefixtures("label_mock") +async def test_extract_from_service_label_id(hass: HomeAssistant) -> None: + """Test the extraction using label ID as reference.""" + entities = [ + MockEntity(name="with_my_label", entity_id="light.with_my_label"), + MockEntity(name="no_labels", entity_id="light.no_labels"), + MockEntity( + name="with_labels_from_device", entity_id="light.with_labels_from_device" + ), + ] + + call = ServiceCall("light", "turn_on", {"label_id": "label_area"}) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 1 + assert extracted[0].entity_id == "light.with_labels_from_device" + + call = ServiceCall("light", "turn_on", {"label_id": "my-label"}) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 1 + assert extracted[0].entity_id == "light.with_my_label" + + call = ServiceCall("light", "turn_on", {"label_id": ["my-label", "label1"]}) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 2 + assert sorted(ent.entity_id for ent in extracted) == [ + "light.with_labels_from_device", + "light.with_my_label", + ] + + call = ServiceCall( + "light", + "turn_on", + {"label_id": ["my-label", "label1"], "device_id": "device-no-labels"}, + ) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 3 + assert sorted(ent.entity_id for ent in extracted) == [ + "light.no_labels", + "light.with_labels_from_device", + "light.with_my_label", + ] + + async def test_entity_service_call_warn_referenced( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: @@ -1590,13 +1785,14 @@ async def test_entity_service_call_warn_referenced( "entity_id": "non.existent", "device_id": "non-existent-device", "floor_id": "non-existent-floor", + "label_id": "non-existent-label", }, ) await service.entity_service_call(hass, {}, "", call) assert ( "Referenced floors non-existent-floor, areas non-existent-area, " - "devices non-existent-device, entities non.existent are missing " - "or not currently available" + "devices non-existent-device, entities non.existent, " + "labels non-existent-label are missing or not currently available" ) in caplog.text @@ -1612,14 +1808,15 @@ async def test_async_extract_entities_warn_referenced( "entity_id": "non.existent", "device_id": "non-existent-device", "floor_id": "non-existent-floor", + "label_id": "non-existent-label", }, ) extracted = await service.async_extract_entities(hass, {}, call) assert len(extracted) == 0 assert ( "Referenced floors non-existent-floor, areas non-existent-area, " - "devices non-existent-device, entities non.existent are missing " - "or not currently available" + "devices non-existent-device, entities non.existent, " + "labels non-existent-label are missing or not currently available" ) in caplog.text From 9be5f3531f43aa27ac098ae72c33fd18bd481c37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 11:59:44 -1000 Subject: [PATCH 1252/1691] Run websocket shutdown listener with run_immediately (#113727) --- homeassistant/components/websocket_api/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index fc75b46ddbd..83d68ee21ea 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -292,7 +292,7 @@ class WebSocketHandler: self._handle_task = asyncio.current_task() unsub_stop = hass.bus.async_listen( - EVENT_HOMEASSISTANT_STOP, self._async_handle_hass_stop + EVENT_HOMEASSISTANT_STOP, self._async_handle_hass_stop, run_immediately=True ) writer = wsock._writer # pylint: disable=protected-access From c615b528405cfb6bb36a0028c65ee615adeaa45c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 15:45:34 -1000 Subject: [PATCH 1253/1691] Refactor integration startup time to show wall clock time (#113707) * Refactor setup time tracking to exclude time waiting on other operations We now exclude the import time and th time waiting on base platforms to setup from the setup times * tweak * tweak * tweak * tweak * adjust * fixes * fixes * preen * preen * tweak * tweak * adjust * tweak * reduce * do not count integrtion platforms against their parent integration * handle legacy tts platforms * stt as well * one more wait * use the same pattern in all the legacy * fix tts and stt legacy * fix * fix * reduce * preen * entity comp does not wait for platforms * scene blocks as well * fix test * test fixes * coverage * coverage * coverage * fix test * Update tests/test_setup.py Co-authored-by: Martin Hjelmare * Update tests/test_setup.py Co-authored-by: Martin Hjelmare * Update homeassistant/setup.py Co-authored-by: Martin Hjelmare * strip * strip WAIT_PLATFORM_INTEGRATION * strip WAIT_PLATFORM_INTEGRATION * strip WAIT_PLATFORM_INTEGRATION * strip WAIT_PLATFORM_INTEGRATION * remove complexity * Apply suggestions from code review * no longer works that way * fixes * fixes * fixes --------- Co-authored-by: Martin Hjelmare --- homeassistant/bootstrap.py | 33 +-- .../components/device_tracker/legacy.py | 8 +- homeassistant/components/http/__init__.py | 8 +- homeassistant/components/notify/legacy.py | 13 +- homeassistant/components/scene/__init__.py | 5 +- homeassistant/components/stream/__init__.py | 4 +- homeassistant/components/stt/legacy.py | 22 +- homeassistant/components/tts/legacy.py | 36 ++- .../components/websocket_api/commands.py | 5 +- homeassistant/config_entries.py | 27 +- homeassistant/helpers/entity_platform.py | 143 +++++----- homeassistant/loader.py | 7 + homeassistant/setup.py | 180 ++++++++++--- tests/components/config/test_scene.py | 1 + tests/components/homeassistant/test_scene.py | 2 + tests/components/scene/test_init.py | 1 + .../components/websocket_api/test_commands.py | 17 +- tests/test_bootstrap.py | 5 +- tests/test_loader.py | 3 + tests/test_setup.py | 254 ++++++++++++++++-- 20 files changed, 598 insertions(+), 176 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 85ee15e3aa0..f98c029832e 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -3,8 +3,8 @@ from __future__ import annotations import asyncio +from collections import defaultdict import contextlib -from datetime import timedelta from functools import partial from itertools import chain import logging @@ -82,7 +82,7 @@ from .helpers.typing import ConfigType from .setup import ( BASE_PLATFORMS, DATA_SETUP_STARTED, - DATA_SETUP_TIME, + async_get_setup_timings, async_notify_setup_error, async_set_domains_to_be_loaded, async_setup_component, @@ -597,7 +597,9 @@ class _WatchPendingSetups: """Periodic log and dispatch of setups that are pending.""" def __init__( - self, hass: core.HomeAssistant, setup_started: dict[str, float] + self, + hass: core.HomeAssistant, + setup_started: dict[tuple[str, str | None], float], ) -> None: """Initialize the WatchPendingSetups class.""" self._hass = hass @@ -612,10 +614,11 @@ class _WatchPendingSetups: now = monotonic() self._duration_count += SLOW_STARTUP_CHECK_INTERVAL - remaining_with_setup_started = { - domain: (now - start_time) - for domain, start_time in self._setup_started.items() - } + remaining_with_setup_started: defaultdict[str, float] = defaultdict(float) + for integration_group, start_time in self._setup_started.items(): + domain, _ = integration_group + remaining_with_setup_started[domain] += now - start_time + if remaining_with_setup_started: _LOGGER.debug("Integration remaining: %s", remaining_with_setup_started) elif waiting_tasks := self._hass._active_tasks: # pylint: disable=protected-access @@ -629,7 +632,7 @@ class _WatchPendingSetups: # once we take over LOG_SLOW_STARTUP_INTERVAL (60s) to start up _LOGGER.warning( "Waiting on integrations to complete setup: %s", - ", ".join(self._setup_started), + self._setup_started, ) _LOGGER.debug("Running timeout Zones: %s", self._hass.timeout.zones) @@ -838,10 +841,8 @@ async def _async_set_up_integrations( hass: core.HomeAssistant, config: dict[str, Any] ) -> None: """Set up all the integrations.""" - setup_started: dict[str, float] = {} + setup_started: dict[tuple[str, str | None], float] = {} hass.data[DATA_SETUP_STARTED] = setup_started - setup_time: dict[str, timedelta] = hass.data.setdefault(DATA_SETUP_TIME, {}) - watcher = _WatchPendingSetups(hass, setup_started) watcher.async_start() @@ -934,7 +935,9 @@ async def _async_set_up_integrations( watcher.async_stop() - _LOGGER.debug( - "Integration setup times: %s", - dict(sorted(setup_time.items(), key=itemgetter(1))), - ) + if _LOGGER.isEnabledFor(logging.DEBUG): + setup_time = async_get_setup_timings(hass) + _LOGGER.debug( + "Integration setup times: %s", + dict(sorted(setup_time.items(), key=itemgetter(1), reverse=True)), + ) diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 344443cd878..72fc4ef717b 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -50,6 +50,7 @@ from homeassistant.helpers.event import ( from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType, GPSType, StateType from homeassistant.setup import ( + SetupPhases, async_notify_setup_error, async_prepare_setup_platform, async_start_setup, @@ -307,7 +308,12 @@ class DeviceTrackerPlatform: assert self.type == PLATFORM_TYPE_LEGACY full_name = f"{self.name}.{DOMAIN}" LOGGER.info("Setting up %s", full_name) - with async_start_setup(hass, [full_name]): + with async_start_setup( + hass, + integration=self.name, + group=str(id(self.config)), + phase=SetupPhases.PLATFORM_SETUP, + ): try: scanner = None setup: bool | None = None diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index b01d80c9997..c9f8c21e0a3 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -43,7 +43,11 @@ from homeassistant.helpers.http import ( from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass -from homeassistant.setup import async_start_setup, async_when_setup_or_start +from homeassistant.setup import ( + SetupPhases, + async_start_setup, + async_when_setup_or_start, +) from homeassistant.util import dt as dt_util, ssl as ssl_util from homeassistant.util.async_ import create_eager_task from homeassistant.util.json import json_loads @@ -218,7 +222,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def start_server(*_: Any) -> None: """Start the server.""" - with async_start_setup(hass, ["http"]): + with async_start_setup(hass, integration="http", phase=SetupPhases.SETUP): hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server) # We already checked it's not None. assert conf is not None diff --git a/homeassistant/components/notify/legacy.py b/homeassistant/components/notify/legacy.py index 6b29df504d9..2f6984e36f1 100644 --- a/homeassistant/components/notify/legacy.py +++ b/homeassistant/components/notify/legacy.py @@ -16,7 +16,11 @@ from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.template import Template from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import async_get_integration, bind_hass -from homeassistant.setup import async_prepare_setup_platform, async_start_setup +from homeassistant.setup import ( + SetupPhases, + async_prepare_setup_platform, + async_start_setup, +) from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml_dict @@ -84,7 +88,12 @@ def async_setup_legacy( full_name = f"{DOMAIN}.{integration_name}" LOGGER.info("Setting up %s", full_name) - with async_start_setup(hass, [full_name]): + with async_start_setup( + hass, + integration=integration_name, + group=str(id(p_config)), + phase=SetupPhases.PLATFORM_SETUP, + ): notify_service: BaseNotificationService | None = None try: if hasattr(platform, "async_get_service"): diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index 1baf25735fa..5a7df164e1f 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -66,7 +66,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await component.async_setup(config) # Ensure Home Assistant platform always loaded. - await component.async_setup_platform(HA_DOMAIN, {"platform": HA_DOMAIN, STATES: []}) + hass.async_create_task( + component.async_setup_platform(HA_DOMAIN, {"platform": HA_DOMAIN, STATES: []}), + eager_start=True, + ) component.async_register_entity_service( SERVICE_TURN_ON, {ATTR_TRANSITION: vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))}, diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index b092e6e8aa6..c1822d596ec 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -35,6 +35,7 @@ from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType +from homeassistant.setup import SetupPhases, async_pause_setup from homeassistant.util.async_ import create_eager_task from .const import ( @@ -221,7 +222,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: logging.getLogger(logging_namespace).setLevel(logging.ERROR) # This will load av so we run it in the executor - await hass.async_add_executor_job(set_pyav_logging, debug_enabled) + with async_pause_setup(hass, SetupPhases.WAIT_IMPORT_PACKAGES): + await hass.async_add_executor_job(set_pyav_logging, debug_enabled) # Keep import here so that we can import stream integration without installing reqs # pylint: disable-next=import-outside-toplevel diff --git a/homeassistant/components/stt/legacy.py b/homeassistant/components/stt/legacy.py index fc7738418f6..997835ef9f8 100644 --- a/homeassistant/components/stt/legacy.py +++ b/homeassistant/components/stt/legacy.py @@ -11,7 +11,11 @@ from homeassistant.config import config_per_platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import discovery from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.setup import async_prepare_setup_platform +from homeassistant.setup import ( + SetupPhases, + async_prepare_setup_platform, + async_start_setup, +) from .const import ( DATA_PROVIDERS, @@ -68,12 +72,20 @@ def async_setup_legacy( return try: - provider = await platform.async_get_engine(hass, p_config, discovery_info) + with async_start_setup( + hass, + integration=p_type, + group=str(id(p_config)), + phase=SetupPhases.PLATFORM_SETUP, + ): + provider = await platform.async_get_engine( + hass, p_config, discovery_info + ) - provider.name = p_type - provider.hass = hass + provider.name = p_type + provider.hass = hass - providers[provider.name] = provider + providers[provider.name] = provider except Exception: # pylint: disable=broad-except _LOGGER.exception("Error setting up platform: %s", p_type) return diff --git a/homeassistant/components/tts/legacy.py b/homeassistant/components/tts/legacy.py index 0214dcd13e9..88249ed107b 100644 --- a/homeassistant/components/tts/legacy.py +++ b/homeassistant/components/tts/legacy.py @@ -31,7 +31,11 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.service import async_set_service_schema from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.setup import async_prepare_setup_platform +from homeassistant.setup import ( + SetupPhases, + async_prepare_setup_platform, + async_start_setup, +) from homeassistant.util.yaml import load_yaml_dict from .const import ( @@ -124,20 +128,26 @@ async def async_setup_legacy( return try: - if hasattr(platform, "async_get_engine"): - provider = await platform.async_get_engine( - hass, p_config, discovery_info - ) - else: - provider = await hass.async_add_executor_job( - platform.get_engine, hass, p_config, discovery_info - ) + with async_start_setup( + hass, + integration=p_type, + group=str(id(p_config)), + phase=SetupPhases.PLATFORM_SETUP, + ): + if hasattr(platform, "async_get_engine"): + provider = await platform.async_get_engine( + hass, p_config, discovery_info + ) + else: + provider = await hass.async_add_executor_job( + platform.get_engine, hass, p_config, discovery_info + ) - if provider is None: - _LOGGER.error("Error setting up platform: %s", p_type) - return + if provider is None: + _LOGGER.error("Error setting up platform: %s", p_type) + return - tts.async_register_legacy_engine(p_type, provider, p_config) + tts.async_register_legacy_engine(p_type, provider, p_config) except Exception: # pylint: disable=broad-except _LOGGER.exception("Error setting up platform: %s", p_type) return diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index dcdbae98689..191ea1ea996 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -55,7 +55,7 @@ from homeassistant.loader import ( async_get_integration_descriptions, async_get_integrations, ) -from homeassistant.setup import DATA_SETUP_TIME, async_get_loaded_integrations +from homeassistant.setup import async_get_loaded_integrations, async_get_setup_timings from homeassistant.util.json import format_unserializable_data from . import const, decorators, messages @@ -539,12 +539,11 @@ def handle_integration_setup_info( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle integrations command.""" - setup_time: dict[str, float] = hass.data[DATA_SETUP_TIME] connection.send_result( msg["id"], [ {"domain": integration, "seconds": seconds} - for integration, seconds in setup_time.items() + for integration, seconds in async_get_setup_timings(hass).items() ], ) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index cb854bcf0ad..b73c7b25e41 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -57,7 +57,14 @@ from .helpers.frame import report from .helpers.json import json_bytes, json_fragment from .helpers.typing import UNDEFINED, ConfigType, DiscoveryInfoType, UndefinedType from .loader import async_suggest_report_issue -from .setup import DATA_SETUP_DONE, async_process_deps_reqs, async_setup_component +from .setup import ( + DATA_SETUP_DONE, + SetupPhases, + async_pause_setup, + async_process_deps_reqs, + async_setup_component, + async_start_setup, +) from .util import uuid as uuid_util from .util.async_ import create_eager_task from .util.decorator import Registry @@ -529,10 +536,17 @@ class ConfigEntry: self._async_set_state(hass, ConfigEntryState.MIGRATION_ERROR, None) return + setup_phase = SetupPhases.CONFIG_ENTRY_SETUP + else: + setup_phase = SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP + error_reason = None try: - result = await component.async_setup_entry(hass, self) + with async_start_setup( + hass, integration=self.domain, group=self.entry_id, phase=setup_phase + ): + result = await component.async_setup_entry(hass, self) if not isinstance(result, bool): _LOGGER.error( # type: ignore[unreachable] @@ -1838,7 +1852,9 @@ class ConfigEntries: ) -> None: """Forward the setup of an entry to platforms.""" integration = await loader.async_get_integration(self.hass, entry.domain) - await integration.async_get_platforms(platforms) + if not integration.platforms_are_loaded(platforms): + with async_pause_setup(self.hass, SetupPhases.WAIT_IMPORT_PLATFORMS): + await integration.async_get_platforms(platforms) await asyncio.gather( *( create_eager_task( @@ -1860,7 +1876,10 @@ class ConfigEntries: """ # Setup Component if not set up yet if domain not in self.hass.config.components: - result = await async_setup_component(self.hass, domain, self._hass_config) + with async_pause_setup(self.hass, SetupPhases.WAIT_BASE_PLATFORM_SETUP): + result = await async_setup_component( + self.hass, domain, self._hass_config + ) if not result: return False diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index cc829cc9bd2..b0e094f97b4 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -32,7 +32,7 @@ from homeassistant.core import ( ) from homeassistant.exceptions import HomeAssistantError, PlatformNotReady from homeassistant.generated import languages -from homeassistant.setup import async_start_setup +from homeassistant.setup import SetupPhases, async_start_setup from homeassistant.util.async_ import create_eager_task from . import ( @@ -284,7 +284,13 @@ class EntityPlatform: discovery_info, ) - await self._async_setup_platform(async_create_setup_awaitable) + with async_start_setup( + hass, + integration=self.platform_name, + group=str(id(platform_config)), + phase=SetupPhases.PLATFORM_SETUP, + ): + await self._async_setup_platform(async_create_setup_awaitable) @callback def async_shutdown(self) -> None: @@ -341,81 +347,78 @@ class EntityPlatform: self.platform_name, SLOW_SETUP_WARNING, ) - with async_start_setup(hass, [full_name]): - try: - awaitable = async_create_setup_awaitable() - if asyncio.iscoroutine(awaitable): - awaitable = create_eager_task(awaitable) + try: + awaitable = async_create_setup_awaitable() + if asyncio.iscoroutine(awaitable): + awaitable = create_eager_task(awaitable) - async with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, self.domain): - await asyncio.shield(awaitable) + async with hass.timeout.async_timeout(SLOW_SETUP_MAX_WAIT, self.domain): + await asyncio.shield(awaitable) - # Block till all entities are done - while self._tasks: - # Await all tasks even if they are done - # to ensure exceptions are propagated - pending = self._tasks.copy() - self._tasks.clear() - await asyncio.gather(*pending) + # Block till all entities are done + while self._tasks: + # Await all tasks even if they are done + # to ensure exceptions are propagated + pending = self._tasks.copy() + self._tasks.clear() + await asyncio.gather(*pending) - hass.config.components.add(full_name) - self._setup_complete = True - return True - except PlatformNotReady as ex: - tries += 1 - wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME - message = str(ex) - ready_message = f"ready yet: {message}" if message else "ready yet" - if tries == 1: - logger.warning( - "Platform %s not %s; Retrying in background in %d seconds", - self.platform_name, - ready_message, - wait_time, - ) - else: - logger.debug( - "Platform %s not %s; Retrying in %d seconds", - self.platform_name, - ready_message, - wait_time, - ) - - async def setup_again(*_args: Any) -> None: - """Run setup again.""" - self._async_cancel_retry_setup = None - await self._async_setup_platform( - async_create_setup_awaitable, tries - ) - - if hass.state is CoreState.running: - self._async_cancel_retry_setup = async_call_later( - hass, wait_time, setup_again - ) - else: - self._async_cancel_retry_setup = hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STARTED, setup_again - ) - return False - except TimeoutError: - logger.error( - ( - "Setup of platform %s is taking longer than %s seconds." - " Startup will proceed without waiting any longer." - ), + hass.config.components.add(full_name) + self._setup_complete = True + return True + except PlatformNotReady as ex: + tries += 1 + wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME + message = str(ex) + ready_message = f"ready yet: {message}" if message else "ready yet" + if tries == 1: + logger.warning( + "Platform %s not %s; Retrying in background in %d seconds", self.platform_name, - SLOW_SETUP_MAX_WAIT, + ready_message, + wait_time, ) - return False - except Exception: # pylint: disable=broad-except - logger.exception( - "Error while setting up %s platform for %s", + else: + logger.debug( + "Platform %s not %s; Retrying in %d seconds", self.platform_name, - self.domain, + ready_message, + wait_time, ) - return False - finally: - warn_task.cancel() + + async def setup_again(*_args: Any) -> None: + """Run setup again.""" + self._async_cancel_retry_setup = None + await self._async_setup_platform(async_create_setup_awaitable, tries) + + if hass.state is CoreState.running: + self._async_cancel_retry_setup = async_call_later( + hass, wait_time, setup_again + ) + else: + self._async_cancel_retry_setup = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, setup_again + ) + return False + except TimeoutError: + logger.error( + ( + "Setup of platform %s is taking longer than %s seconds." + " Startup will proceed without waiting any longer." + ), + self.platform_name, + SLOW_SETUP_MAX_WAIT, + ) + return False + except Exception: # pylint: disable=broad-except + logger.exception( + "Error while setting up %s platform for %s", + self.platform_name, + self.domain, + ) + return False + finally: + warn_task.cancel() async def _async_get_translations( self, language: str, category: str, integration: str diff --git a/homeassistant/loader.py b/homeassistant/loader.py index fe825e84bdf..74250a3a150 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1172,6 +1172,13 @@ class Integration: raise self._missing_platforms_cache[full_name] return None + def platforms_are_loaded(self, platform_names: Iterable[str]) -> bool: + """Check if a platforms are loaded for an integration.""" + return all( + f"{self.domain}.{platform_name}" in self._cache + for platform_name in platform_names + ) + def get_platform_cached(self, platform_name: str) -> ModuleType | None: """Return a platform for an integration from cache.""" return self._cache.get(f"{self.domain}.{platform_name}") # type: ignore[return-value] diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 45e3b328fc3..f8a123ab797 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -3,8 +3,11 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable, Generator, Iterable +from collections import defaultdict +from collections.abc import Awaitable, Callable, Generator, Mapping import contextlib +import contextvars +from enum import StrEnum import logging.handlers import time from timeit import default_timer as timer @@ -29,9 +32,13 @@ from .exceptions import DependencyError, HomeAssistantError from .helpers import translation from .helpers.issue_registry import IssueSeverity, async_create_issue from .helpers.typing import ConfigType -from .util import ensure_unique_string from .util.async_ import create_eager_task +current_setup_group: contextvars.ContextVar[ + tuple[str, str | None] | None +] = contextvars.ContextVar("current_setup_group", default=None) + + _LOGGER = logging.getLogger(__name__) ATTR_COMPONENT: Final = "component" @@ -54,12 +61,12 @@ DATA_SETUP = "setup_tasks" # is finished, regardless of if the setup was successful or not. DATA_SETUP_DONE = "setup_done" -# DATA_SETUP_STARTED is a dict [str, float], indicating when an attempt +# DATA_SETUP_STARTED is a dict [tuple[str, str | None], float], indicating when an attempt # to setup a component started. DATA_SETUP_STARTED = "setup_started" -# DATA_SETUP_TIME is a dict [str, timedelta], indicating how time was spent -# setting up a component. +# DATA_SETUP_TIME is a defaultdict[str, defaultdict[str | None, defaultdict[SetupPhases, float]]] +# indicating how time was spent setting up a component and each group (config entry). DATA_SETUP_TIME = "setup_time" DATA_DEPS_REQS = "deps_reqs_processed" @@ -358,7 +365,7 @@ async def _async_setup_component( # noqa: C901 translation.async_load_integrations(hass, integration_set) ) - with async_start_setup(hass, integration_set): + with async_start_setup(hass, integration=domain, phase=SetupPhases.SETUP): if hasattr(component, "PLATFORM_SCHEMA"): # Entity components have their own warning warn_task = None @@ -430,18 +437,18 @@ async def _async_setup_component( # noqa: C901 # call to avoid a deadlock when forwarding platforms hass.config.components.add(domain) - if entries := hass.config_entries.async_entries( - domain, include_ignore=False, include_disabled=False - ): - await asyncio.gather( - *( - create_eager_task( - entry.async_setup(hass, integration=integration), - name=f"config entry setup {entry.title} {entry.domain} {entry.entry_id}", - ) - for entry in entries + if entries := hass.config_entries.async_entries( + domain, include_ignore=False, include_disabled=False + ): + await asyncio.gather( + *( + create_eager_task( + entry.async_setup(hass, integration=integration), + name=f"config entry setup {entry.title} {entry.domain} {entry.entry_id}", ) + for entry in entries ) + ) # Cleanup if domain in hass.data[DATA_SETUP]: @@ -626,27 +633,134 @@ def async_get_loaded_integrations(hass: core.HomeAssistant) -> set[str]: return integrations +class SetupPhases(StrEnum): + """Constants for setup time measurements.""" + + SETUP = "setup" + """Set up of a component in __init__.py.""" + CONFIG_ENTRY_SETUP = "config_entry_setup" + """Set up of a config entry in __init__.py.""" + PLATFORM_SETUP = "platform_setup" + """Set up of a platform integration. + + ex async_setup_platform or setup_platform or + a legacy platform like device_tracker.legacy + """ + CONFIG_ENTRY_PLATFORM_SETUP = "config_entry_platform_setup" + """Set up of a platform in a config entry after the config entry is setup. + + This is only for platforms that are not awaited in async_setup_entry. + """ + WAIT_BASE_PLATFORM_SETUP = "wait_base_component" + """Wait time for the base component to be setup.""" + WAIT_IMPORT_PLATFORMS = "wait_import_platforms" + """Wait time for the platforms to import.""" + WAIT_IMPORT_PACKAGES = "wait_import_packages" + """Wait time for the packages to import.""" + + +@contextlib.contextmanager +def async_pause_setup( + hass: core.HomeAssistant, phase: SetupPhases +) -> Generator[None, None, None]: + """Keep track of time we are blocked waiting for other operations. + + We want to count the time we wait for importing and + setting up the base components so we can subtract it + from the total setup time. + """ + if not (running := current_setup_group.get()): + # This means we are likely in a late platform setup + # that is running in a task so we do not want + # to subtract out the time later as nothing is waiting + # for the code inside the context manager to finish. + yield + return + + started = time.monotonic() + try: + yield + finally: + time_taken = time.monotonic() - started + integration, group = running + # Add negative time for the time we waited + _setup_times(hass)[integration][group][phase] = -time_taken + + +def _setup_times( + hass: core.HomeAssistant, +) -> defaultdict[str, defaultdict[str | None, defaultdict[SetupPhases, float]]]: + """Return the setup timings default dict.""" + if DATA_SETUP_TIME not in hass.data: + hass.data[DATA_SETUP_TIME] = defaultdict( + lambda: defaultdict(lambda: defaultdict(float)) + ) + return hass.data[DATA_SETUP_TIME] # type: ignore[no-any-return] + + @contextlib.contextmanager def async_start_setup( - hass: core.HomeAssistant, components: Iterable[str] + hass: core.HomeAssistant, + integration: str, + phase: SetupPhases, + group: str | None = None, ) -> Generator[None, None, None]: - """Keep track of when setup starts and finishes.""" + """Keep track of when setup starts and finishes. + + :param hass: Home Assistant instance + :param integration: The integration that is being setup + :param phase: The phase of setup + :param group: The group (config entry/platform instance) that is being setup + + A group is a group of setups that run in parallel. + + """ + if hass.is_stopping or hass.state is core.CoreState.running: + # Don't track setup times when we are shutting down or already running + # as we present the timings as "Integration startup time", and we + # don't want to add all the setup retry times to that. + yield + return + + setup_started: dict[tuple[str, str | None], float] setup_started = hass.data.setdefault(DATA_SETUP_STARTED, {}) + current = (integration, group) + if current in setup_started: + # We are already inside another async_start_setup, this like means we + # are setting up a platform inside async_setup_entry so we should not + # record this as a new setup + yield + return + started = time.monotonic() - unique_components: dict[str, str] = {} - for domain in components: - unique = ensure_unique_string(domain, setup_started) - unique_components[unique] = domain - setup_started[unique] = started + current_setup_group.set(current) + setup_started[current] = started - yield + try: + yield + finally: + time_taken = time.monotonic() - started + del setup_started[current] + _setup_times(hass)[integration][group][phase] = time_taken - setup_time: dict[str, float] = hass.data.setdefault(DATA_SETUP_TIME, {}) - time_taken = time.monotonic() - started - for unique, domain in unique_components.items(): - del setup_started[unique] - integration = domain.partition(".")[0] - if integration in setup_time: - setup_time[integration] += time_taken - else: - setup_time[integration] = time_taken + +@callback +def async_get_setup_timings(hass: core.HomeAssistant) -> dict[str, float]: + """Return timing data for each integration.""" + setup_time = _setup_times(hass) + domain_timings: dict[str, float] = {} + top_level_timings: Mapping[SetupPhases, float] + for domain, timings in setup_time.items(): + top_level_timings = timings.get(None, {}) + total_top_level = sum(top_level_timings.values()) + # Groups (config entries/platform instance) are setup in parallel so we + # take the max of the group timings and add it to the top level + group_totals = { + group: sum(group_timings.values()) + for group, group_timings in timings.items() + if group is not None + } + group_max = max(group_totals.values(), default=0) + domain_timings[domain] = total_top_level + group_max + + return domain_timings diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index b270ac26a9b..dfb7cdad8c6 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -19,6 +19,7 @@ from tests.typing import ClientSessionGenerator async def setup_scene(hass, scene_config): """Set up scene integration.""" assert await async_setup_component(hass, "scene", {"scene": scene_config}) + await hass.async_block_till_done() @pytest.mark.parametrize("scene_config", ({},)) diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 033d414d4a9..a1a532db162 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -18,6 +18,7 @@ from tests.common import async_capture_events, async_mock_service async def test_reload_config_service(hass: HomeAssistant) -> None: """Test the reload config service.""" assert await async_setup_component(hass, "scene", {}) + await hass.async_block_till_done() test_reloaded_event = async_capture_events(hass, EVENT_SCENE_RELOADED) @@ -175,6 +176,7 @@ async def test_delete_service( "scene", {"scene": {"name": "hallo_2", "entities": {"light.kitchen": "on"}}}, ) + await hass.async_block_till_done() await hass.services.async_call( "scene", diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index c797c26f35a..660be91dca2 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -228,6 +228,7 @@ async def activate(hass, entity_id=ENTITY_MATCH_ALL): async def test_services_registered(hass: HomeAssistant) -> None: """Test we register services with empty config.""" assert await async_setup_component(hass, "scene", {}) + await hass.async_block_till_done() assert hass.services.has_service("scene", "reload") assert hass.services.has_service("scene", "turn_on") assert hass.services.has_service("scene", "apply") diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 7eee2261a26..5f10451799b 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -23,7 +23,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.loader import async_get_integration -from homeassistant.setup import DATA_SETUP_TIME, async_setup_component +from homeassistant.setup import async_setup_component from homeassistant.util.json import json_loads from tests.common import ( @@ -2491,13 +2491,16 @@ async def test_integration_setup_info( hass_admin_user: MockUser, ) -> None: """Test subscribe/unsubscribe bootstrap_integrations.""" - hass.data[DATA_SETUP_TIME] = { - "august": 12.5, - "isy994": 12.8, - } - await websocket_client.send_json({"id": 7, "type": "integration/setup_info"}) + with patch( + "homeassistant.components.websocket_api.commands.async_get_setup_timings", + return_value={ + "august": 12.5, + "isy994": 12.8, + }, + ): + await websocket_client.send_json({"id": 7, "type": "integration/setup_info"}) + msg = await websocket_client.receive_json() - msg = await websocket_client.receive_json() assert msg["id"] == 7 assert msg["type"] == const.TYPE_RESULT assert msg["success"] diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 996126ad1b6..4dc322c5a29 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -14,7 +14,7 @@ from homeassistant import bootstrap, loader, runner import homeassistant.config as config_util from homeassistant.config_entries import HANDLERS, ConfigEntry from homeassistant.const import SIGNAL_BOOTSTRAP_INTEGRATIONS -from homeassistant.core import HomeAssistant, async_get_hass, callback +from homeassistant.core import CoreState, HomeAssistant, async_get_hass, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import ConfigType @@ -869,6 +869,9 @@ async def test_empty_integrations_list_is_only_sent_at_the_end_of_bootstrap( hass: HomeAssistant, ) -> None: """Test empty integrations list is only sent at the end of bootstrap.""" + # setup times only tracked when not running + hass.set_state(CoreState.not_running) + order = [] def gen_domain_setup(domain): diff --git a/tests/test_loader.py b/tests/test_loader.py index ae21d75a6e1..c8a8905cdef 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1520,6 +1520,9 @@ async def test_platforms_exists( assert integration.platforms_exists(["group"]) == ["group"] + assert integration.platforms_are_loaded(["group"]) is True + assert integration.platforms_are_loaded(["other"]) is False + async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( hass: HomeAssistant, diff --git a/tests/test_setup.py b/tests/test_setup.py index c54c02fd880..28366a50a82 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -2,14 +2,14 @@ import asyncio import threading -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import ANY, AsyncMock, Mock, patch import pytest import voluptuous as vol from homeassistant import config_entries, loader, setup from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import CoreState, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import discovery, translation from homeassistant.helpers.config_validation import ( @@ -727,26 +727,244 @@ async def test_integration_only_setup_entry(hass: HomeAssistant) -> None: assert await setup.async_setup_component(hass, "test_integration_only_entry", {}) -async def test_async_start_setup(hass: HomeAssistant) -> None: - """Test setup started context manager keeps track of setup times.""" - with setup.async_start_setup(hass, ["august"]): - assert isinstance(hass.data[setup.DATA_SETUP_STARTED]["august"], float) - with setup.async_start_setup(hass, ["august"]): - assert isinstance(hass.data[setup.DATA_SETUP_STARTED]["august_2"], float) +async def test_async_start_setup_running(hass: HomeAssistant) -> None: + """Test setup started context manager does nothing when running.""" + assert hass.state is CoreState.running + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) - assert "august" not in hass.data[setup.DATA_SETUP_STARTED] - assert isinstance(hass.data[setup.DATA_SETUP_TIME]["august"], float) - assert "august_2" not in hass.data[setup.DATA_SETUP_TIME] + with setup.async_start_setup( + hass, integration="august", phase=setup.SetupPhases.SETUP + ): + assert not setup_started -async def test_async_start_setup_platforms(hass: HomeAssistant) -> None: - """Test setup started context manager keeps track of setup times for platforms.""" - with setup.async_start_setup(hass, ["august.sensor"]): - assert isinstance(hass.data[setup.DATA_SETUP_STARTED]["august.sensor"], float) +async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None: + """Test setup started keeps track of setup times with a config entry.""" + hass.set_state(CoreState.not_running) + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) + setup_time = setup._setup_times(hass) - assert "august" not in hass.data[setup.DATA_SETUP_STARTED] - assert isinstance(hass.data[setup.DATA_SETUP_TIME]["august"], float) - assert "sensor" not in hass.data[setup.DATA_SETUP_TIME] + with setup.async_start_setup( + hass, integration="august", phase=setup.SetupPhases.SETUP + ): + assert isinstance(setup_started[("august", None)], float) + + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id")], float) + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id")], float) + + # CONFIG_ENTRY_PLATFORM_SETUP inside of CONFIG_ENTRY_SETUP should not be tracked + assert setup_time["august"] == { + None: {setup.SetupPhases.SETUP: ANY}, + "entry_id": {setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY}, + } + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id")], float) + # Platforms outside of CONFIG_ENTRY_SETUP should be tracked + # This simulates a late platform forward + assert setup_time["august"] == { + None: {setup.SetupPhases.SETUP: ANY}, + "entry_id": { + setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY, + setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: ANY, + }, + } + + with setup.async_start_setup( + hass, + integration="august", + group="entry_id2", + phase=setup.SetupPhases.CONFIG_ENTRY_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id2")], float) + # We wrap places where we wait for other components + # or the import of a module with async_freeze_setup + # so we can subtract the time waited from the total setup time + with setup.async_pause_setup(hass, setup.SetupPhases.WAIT_BASE_PLATFORM_SETUP): + await asyncio.sleep(0) + + # Wait time should be added if freeze_setup is used + assert setup_time["august"] == { + None: {setup.SetupPhases.SETUP: ANY}, + "entry_id": { + setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY, + setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: ANY, + }, + "entry_id2": { + setup.SetupPhases.CONFIG_ENTRY_SETUP: ANY, + setup.SetupPhases.WAIT_BASE_PLATFORM_SETUP: ANY, + }, + } + + +async def test_async_start_setup_top_level_yaml(hass: HomeAssistant) -> None: + """Test setup started context manager keeps track of setup times with modern yaml.""" + hass.set_state(CoreState.not_running) + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) + setup_time = setup._setup_times(hass) + + with setup.async_start_setup( + hass, integration="command_line", phase=setup.SetupPhases.SETUP + ): + assert isinstance(setup_started[("command_line", None)], float) + + assert setup_time["command_line"] == { + None: {setup.SetupPhases.SETUP: ANY}, + } + + +async def test_async_start_setup_platform_integration(hass: HomeAssistant) -> None: + """Test setup started keeps track of setup times a platform integration.""" + hass.set_state(CoreState.not_running) + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) + setup_time = setup._setup_times(hass) + + with setup.async_start_setup( + hass, integration="sensor", phase=setup.SetupPhases.SETUP + ): + assert isinstance(setup_started[("sensor", None)], float) + + # Platform integration setups happen in another task + with setup.async_start_setup( + hass, + integration="filter", + group="123456", + phase=setup.SetupPhases.PLATFORM_SETUP, + ): + assert isinstance(setup_started[("filter", "123456")], float) + + assert setup_time["sensor"] == { + None: { + setup.SetupPhases.SETUP: ANY, + }, + } + assert setup_time["filter"] == { + "123456": { + setup.SetupPhases.PLATFORM_SETUP: ANY, + }, + } + + +async def test_async_start_setup_legacy_platform_integration( + hass: HomeAssistant, +) -> None: + """Test setup started keeps track of setup times for a legacy platform integration.""" + hass.set_state(CoreState.not_running) + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) + setup_time = setup._setup_times(hass) + + with setup.async_start_setup( + hass, integration="notify", phase=setup.SetupPhases.SETUP + ): + assert isinstance(setup_started[("notify", None)], float) + + with setup.async_start_setup( + hass, + integration="legacy_notify_integration", + group="123456", + phase=setup.SetupPhases.PLATFORM_SETUP, + ): + assert isinstance(setup_started[("legacy_notify_integration", "123456")], float) + + assert setup_time["notify"] == { + None: { + setup.SetupPhases.SETUP: ANY, + }, + } + assert setup_time["legacy_notify_integration"] == { + "123456": { + setup.SetupPhases.PLATFORM_SETUP: ANY, + }, + } + + +async def test_async_start_setup_simple_integration_end_to_end( + hass: HomeAssistant, +) -> None: + """Test end to end timings for a simple integration with no platforms.""" + hass.set_state(CoreState.not_running) + mock_integration( + hass, + MockModule( + "test_integration_no_platforms", + setup=False, + async_setup_entry=AsyncMock(return_value=True), + ), + ) + assert await setup.async_setup_component(hass, "test_integration_no_platforms", {}) + await hass.async_block_till_done() + assert setup.async_get_setup_timings(hass) == { + "test_integration_no_platforms": ANY, + } + + +async def test_async_get_setup_timings(hass) -> None: + """Test we can get the setup timings from the setup time data.""" + setup_time = setup._setup_times(hass) + # Mock setup time data + setup_time.update( + { + "august": { + None: {setup.SetupPhases.SETUP: 1}, + "entry_id": { + setup.SetupPhases.CONFIG_ENTRY_SETUP: 1, + setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: 4, + }, + "entry_id2": { + setup.SetupPhases.CONFIG_ENTRY_SETUP: 7, + setup.SetupPhases.WAIT_BASE_PLATFORM_SETUP: -5, + }, + }, + "notify": { + None: { + setup.SetupPhases.SETUP: 2, + }, + }, + "legacy_notify_integration": { + "123456": { + setup.SetupPhases.PLATFORM_SETUP: 3, + }, + }, + "sensor": { + None: { + setup.SetupPhases.SETUP: 1, + }, + }, + "filter": { + "123456": { + setup.SetupPhases.PLATFORM_SETUP: 2, + }, + }, + } + ) + assert setup.async_get_setup_timings(hass) == { + "august": 6, + "notify": 2, + "legacy_notify_integration": 3, + "sensor": 1, + "filter": 2, + } async def test_setup_config_entry_from_yaml( From e3ae66ca64659dfd80314568c5a52a031256412c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 19:57:14 -1000 Subject: [PATCH 1254/1691] Bump PyMetno to 0.12.0 (#113777) changelog: https://github.com/Danielhiversen/pyMetno/releases/tag/0.12.0 --- homeassistant/components/met/manifest.json | 2 +- homeassistant/components/norway_air/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/met/manifest.json b/homeassistant/components/met/manifest.json index a3190109cac..e900c5a012a 100644 --- a/homeassistant/components/met/manifest.json +++ b/homeassistant/components/met/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/met", "iot_class": "cloud_polling", "loggers": ["metno"], - "requirements": ["PyMetno==0.11.0"] + "requirements": ["PyMetno==0.12.0"] } diff --git a/homeassistant/components/norway_air/manifest.json b/homeassistant/components/norway_air/manifest.json index 84af1313cf5..f787f647db8 100644 --- a/homeassistant/components/norway_air/manifest.json +++ b/homeassistant/components/norway_air/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/norway_air", "iot_class": "cloud_polling", "loggers": ["metno"], - "requirements": ["PyMetno==0.11.0"] + "requirements": ["PyMetno==0.12.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index e58c319fb19..6640155c623 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -73,7 +73,7 @@ PyMetEireann==2021.8.0 # homeassistant.components.met # homeassistant.components.norway_air -PyMetno==0.11.0 +PyMetno==0.12.0 # homeassistant.components.keymitt_ble PyMicroBot==0.0.17 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 505c96d6ad9..34df50d9eb9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -61,7 +61,7 @@ PyMetEireann==2021.8.0 # homeassistant.components.met # homeassistant.components.norway_air -PyMetno==0.11.0 +PyMetno==0.12.0 # homeassistant.components.keymitt_ble PyMicroBot==0.0.17 From 8b9a8a33f4e4436c01f99473d42b90b33fb03b2f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:00:51 -1000 Subject: [PATCH 1255/1691] Start live history/logbook with eager tasks (#113779) --- homeassistant/components/history/websocket_api.py | 5 +++-- homeassistant/components/logbook/websocket_api.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/history/websocket_api.py b/homeassistant/components/history/websocket_api.py index 1f1c5064c5a..6eb1fec3416 100644 --- a/homeassistant/components/history/websocket_api.py +++ b/homeassistant/components/history/websocket_api.py @@ -36,6 +36,7 @@ from homeassistant.helpers.event import ( async_track_state_change_event, ) from homeassistant.helpers.json import json_bytes +from homeassistant.util.async_ import create_eager_task import homeassistant.util.dt as dt_util from .const import EVENT_COALESCE_TIME, MAX_PENDING_HISTORY_STATES @@ -536,7 +537,7 @@ async def ws_stream( # Unsubscribe happened while sending historical states return - live_stream.task = asyncio.create_task( + live_stream.task = create_eager_task( _async_events_consumer( subscriptions_setup_complete_time, connection, @@ -546,7 +547,7 @@ async def ws_stream( ) ) - live_stream.wait_sync_task = asyncio.create_task( + live_stream.wait_sync_task = create_eager_task( get_instance(hass).async_block_till_done() ) await live_stream.wait_sync_task diff --git a/homeassistant/components/logbook/websocket_api.py b/homeassistant/components/logbook/websocket_api.py index 4e4732c0d0e..6d976a50240 100644 --- a/homeassistant/components/logbook/websocket_api.py +++ b/homeassistant/components/logbook/websocket_api.py @@ -18,6 +18,7 @@ from homeassistant.components.websocket_api.connection import ActiveConnection from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.json import json_bytes +from homeassistant.util.async_ import create_eager_task import homeassistant.util.dt as dt_util from .const import DOMAIN @@ -395,7 +396,7 @@ async def ws_event_stream( # Unsubscribe happened while sending historical events return - live_stream.task = asyncio.create_task( + live_stream.task = create_eager_task( _async_events_consumer( subscriptions_setup_complete_time, connection, @@ -405,7 +406,7 @@ async def ws_event_stream( ) ) - live_stream.wait_sync_task = asyncio.create_task( + live_stream.wait_sync_task = create_eager_task( get_instance(hass).async_block_till_done() ) await live_stream.wait_sync_task From f680065664221355fe0646778feaac8d4cf40b37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:15:06 +0100 Subject: [PATCH 1256/1691] Bump dawidd6/action-download-artifact from 3.1.3 to 3.1.4 (#113788) --- .github/workflows/builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index c4117611676..9e8e763b347 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -103,7 +103,7 @@ jobs: - name: Download nightly wheels of frontend if: needs.init.outputs.channel == 'dev' - uses: dawidd6/action-download-artifact@v3.1.3 + uses: dawidd6/action-download-artifact@v3.1.4 with: github_token: ${{secrets.GITHUB_TOKEN}} repo: home-assistant/frontend @@ -114,7 +114,7 @@ jobs: - name: Download nightly wheels of intents if: needs.init.outputs.channel == 'dev' - uses: dawidd6/action-download-artifact@v3.1.3 + uses: dawidd6/action-download-artifact@v3.1.4 with: github_token: ${{secrets.GITHUB_TOKEN}} repo: home-assistant/intents-package From ed9b5d843c194a683c564fbadb22039bcf4528e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:27:32 +0100 Subject: [PATCH 1257/1691] Bump github/codeql-action from 3.24.7 to 3.24.8 (#113789) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 58f9e17fd4c..c9d97aaadcb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@v4.1.2 - name: Initialize CodeQL - uses: github/codeql-action/init@v3.24.7 + uses: github/codeql-action/init@v3.24.8 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.24.7 + uses: github/codeql-action/analyze@v3.24.8 with: category: "/language:python" From 85e13bdb8794781889bd0f57f98eb969cd4c75ee Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 19 Mar 2024 08:29:29 +0100 Subject: [PATCH 1258/1691] Require an icon for a service (#112373) --- script/hassfest/services.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 8489a3a9d68..34f9b906fb5 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -139,6 +139,13 @@ def validate_services(config: Config, integration: Integration) -> None: ) return + icons_file = integration.path / "icons.json" + icons = {} + if icons_file.is_file(): + with contextlib.suppress(ValueError): + icons = json.loads(icons_file.read_text()) + service_icons = icons.get("services", {}) + # Try loading translation strings if integration.core: strings_file = integration.path / "strings.json" @@ -155,9 +162,17 @@ def validate_services(config: Config, integration: Integration) -> None: if not integration.core: error_msg_suffix = f"and is not {error_msg_suffix}" - # For each service in the integration, check if the description if set, - # if not, check if it's in the strings file. If not, add an error. + # For each service in the integration: + # 1. Check if the service description is set, if not, + # check if it's in the strings file else add an error. + # 2. Check if the service has an icon set in icons.json. + # raise an error if not., for service_name, service_schema in services.items(): + if service_name not in service_icons: + integration.add_error( + "services", + f"Service {service_name} has no icon in icons.json.", + ) if service_schema is None: continue if "name" not in service_schema: From 2582172ad1d1f67e1f4b7f9bdc2741f5825695d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:41:39 -1000 Subject: [PATCH 1259/1691] Create tasks eagerly with core create_task (#113781) --- homeassistant/core.py | 4 +++- tests/components/stream/test_hls.py | 13 ++++++++++--- tests/components/stream/test_recorder.py | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index c9760194a92..a2c14814c99 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -701,7 +701,9 @@ class HomeAssistant: target: target to call. """ - self.loop.call_soon_threadsafe(self.async_create_task, target, name) + self.loop.call_soon_threadsafe( + functools.partial(self.async_create_task, target, name, eager_start=True) + ) @callback def async_create_task( diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 02fbfc96ab7..23ba2e4ab34 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -297,6 +297,9 @@ async def test_stream_retries( stream.set_update_callback(update_callback) + open_future1 = hass.loop.create_future() + open_future2 = hass.loop.create_future() + futures = [open_future2, open_future1] cur_time = 0 def time_side_effect(): @@ -308,18 +311,22 @@ async def test_stream_retries( cur_time += 40 return cur_time + def av_open_side_effect(*args, **kwargs): + hass.loop.call_soon_threadsafe(futures.pop().set_result, None) + raise av.error.InvalidDataError(-2, "error") + with patch("av.open") as av_open, patch( "homeassistant.components.stream.time" ) as mock_time, patch( "homeassistant.components.stream.STREAM_RESTART_INCREMENT", 0 ): - av_open.side_effect = av.error.InvalidDataError(-2, "error") + av_open.side_effect = av_open_side_effect mock_time.time.side_effect = time_side_effect # Request stream. Enable retries which are disabled by default in tests. should_retry.return_value = True await stream.start() - stream._thread.join() - stream._thread = None + await open_future1 + await open_future2 assert av_open.call_count == 2 await hass.async_block_till_done() diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 9ab9b7a680a..35b8c2442f4 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -237,6 +237,7 @@ async def test_record_stream_audio( # Fire the IdleTimer future = dt_util.utcnow() + timedelta(seconds=30) async_fire_time_changed(hass, future) + await hass.async_block_till_done() await make_recording From ae55e8e1baa95097189216c6d8156b16435fcd9c Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 19 Mar 2024 08:42:07 +0100 Subject: [PATCH 1260/1691] Align Comelit climate code to humidifier (#113747) --- homeassistant/components/comelit/climate.py | 34 ++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/comelit/climate.py b/homeassistant/components/comelit/climate.py index d92067651b6..0b88367c0fa 100644 --- a/homeassistant/components/comelit/climate.py +++ b/homeassistant/components/comelit/climate.py @@ -25,7 +25,7 @@ from .const import DOMAIN from .coordinator import ComelitSerialBridge -class ClimaMode(StrEnum): +class ClimaComelitMode(StrEnum): """Serial Bridge clima modes.""" AUTO = "A" @@ -34,8 +34,8 @@ class ClimaMode(StrEnum): UPPER = "U" -class ClimaAction(StrEnum): - """Serial Bridge clima actions.""" +class ClimaComelitCommand(StrEnum): + """Serial Bridge clima commands.""" OFF = "off" ON = "on" @@ -45,28 +45,28 @@ class ClimaAction(StrEnum): API_STATUS: dict[str, dict[str, Any]] = { - ClimaMode.OFF: { + ClimaComelitMode.OFF: { "action": "off", "hvac_mode": HVACMode.OFF, "hvac_action": HVACAction.OFF, }, - ClimaMode.LOWER: { + ClimaComelitMode.LOWER: { "action": "lower", "hvac_mode": HVACMode.COOL, "hvac_action": HVACAction.COOLING, }, - ClimaMode.UPPER: { + ClimaComelitMode.UPPER: { "action": "upper", "hvac_mode": HVACMode.HEAT, "hvac_action": HVACAction.HEATING, }, } -MODE_TO_ACTION: dict[HVACMode, ClimaAction] = { - HVACMode.OFF: ClimaAction.OFF, - HVACMode.AUTO: ClimaAction.AUTO, - HVACMode.COOL: ClimaAction.MANUAL, - HVACMode.HEAT: ClimaAction.MANUAL, +MODE_TO_ACTION: dict[HVACMode, ClimaComelitCommand] = { + HVACMode.OFF: ClimaComelitCommand.OFF, + HVACMode.AUTO: ClimaComelitCommand.AUTO, + HVACMode.COOL: ClimaComelitCommand.MANUAL, + HVACMode.HEAT: ClimaComelitCommand.MANUAL, } @@ -139,7 +139,7 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity @property def _api_automatic(self) -> bool: """Return device in automatic/manual mode.""" - return self._clima[3] == ClimaMode.AUTO + return self._clima[3] == ClimaComelitMode.AUTO @property def target_temperature(self) -> float: @@ -155,7 +155,7 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity def hvac_mode(self) -> HVACMode | None: """HVAC current mode.""" - if self._api_mode == ClimaMode.OFF: + if self._api_mode == ClimaComelitMode.OFF: return HVACMode.OFF if self._api_automatic: @@ -170,7 +170,7 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity def hvac_action(self) -> HVACAction | None: """HVAC current action.""" - if self._api_mode == ClimaMode.OFF: + if self._api_mode == ClimaComelitMode.OFF: return HVACAction.OFF if not self._api_active: @@ -189,10 +189,10 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity return await self.coordinator.api.set_clima_status( - self._device.index, ClimaAction.MANUAL + self._device.index, ClimaComelitCommand.MANUAL ) await self.coordinator.api.set_clima_status( - self._device.index, ClimaAction.SET, target_temp + self._device.index, ClimaComelitCommand.SET, target_temp ) async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: @@ -200,7 +200,7 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity if hvac_mode != HVACMode.OFF: await self.coordinator.api.set_clima_status( - self._device.index, ClimaAction.ON + self._device.index, ClimaComelitCommand.ON ) await self.coordinator.api.set_clima_status( self._device.index, MODE_TO_ACTION[hvac_mode] From d740e4c3d76707246765312385cd53410f38b0ad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:42:49 -1000 Subject: [PATCH 1261/1691] Migrate restore_state shutdown to use run_immediately (#113786) --- homeassistant/helpers/restore_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 2d6ffb9ec49..7979247c8b0 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -253,7 +253,7 @@ class RestoreStateData: # Dump states when stopping hass self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, _async_dump_states_at_stop + EVENT_HOMEASSISTANT_STOP, _async_dump_states_at_stop, run_immediately=True ) @callback From 2f88460b6832b85505e8deb3924af13eda5b18a2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:43:14 -1000 Subject: [PATCH 1262/1691] Use async api in template weather to create tasks (#113784) --- homeassistant/components/template/weather.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 3d84eb1d07a..b572f0ecbb8 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -433,7 +433,9 @@ class WeatherTemplate(TemplateEntity, WeatherEntity): """Save template result and trigger forecast listener.""" attr_result = None if isinstance(result, TemplateError) else result setattr(self, f"_forecast_{forecast_type}", attr_result) - self.hass.create_task(self.async_update_listeners([forecast_type])) + self.hass.async_create_task( + self.async_update_listeners([forecast_type]), eager_start=True + ) @callback def _validate_forecast( From 3bb0d044529fc0f406308e48a49227626418d743 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:43:27 -1000 Subject: [PATCH 1263/1691] Use async api in yamaha_musiccast to create tasks (#113785) --- homeassistant/components/yamaha_musiccast/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index 04c365d9996..3edf524c8ad 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -927,4 +927,4 @@ class MusicCastMediaPlayer(MusicCastDeviceEntity, MediaPlayerEntity): @callback def async_schedule_check_client_list(self): """Schedule async_check_client_list.""" - self.hass.create_task(self.async_check_client_list()) + self.hass.async_create_task(self.async_check_client_list(), eager_start=True) From 31a9c9451ee5db9c35420f075553fcfa8bc69ee7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:43:49 -1000 Subject: [PATCH 1264/1691] Use async api in generic_thermostat to create tasks (#113783) --- homeassistant/components/generic_thermostat/climate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 0ee96232d4c..42fd2ef6f41 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -279,7 +279,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity): STATE_UNAVAILABLE, STATE_UNKNOWN, ): - self.hass.create_task(self._check_switch_initial_state()) + self.hass.async_create_task( + self._check_switch_initial_state(), eager_start=True + ) if self.hass.state is CoreState.running: _async_startup() @@ -443,7 +445,9 @@ class GenericThermostat(ClimateEntity, RestoreEntity): if new_state is None: return if old_state is None: - self.hass.create_task(self._check_switch_initial_state()) + self.hass.async_create_task( + self._check_switch_initial_state(), eager_start=True + ) self.async_write_ha_state() @callback From f51ac30b5a6a30f1227b384d1a1f45d0b3edb6c9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:44:13 -1000 Subject: [PATCH 1265/1691] Call async task creation api in apple_tv (#113782) --- homeassistant/components/apple_tv/media_player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 8d10509d615..3f64d10f9ac 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -153,7 +153,9 @@ class AppleTvMediaPlayer( atv.audio.listener = self if atv.features.in_state(FeatureState.Available, FeatureName.AppList): - self.hass.create_task(self._update_app_list()) + self.manager.config_entry.async_create_task( + self.hass, self._update_app_list(), eager_start=True + ) async def _update_app_list(self) -> None: _LOGGER.debug("Updating app list") From 00f94feec9f1e6fc75fd8ca609fac2cc131f978f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 Mar 2024 21:44:39 -1000 Subject: [PATCH 1266/1691] Start energy load platform task eagerly (#113778) --- homeassistant/components/energy/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/energy/__init__.py b/homeassistant/components/energy/__init__.py index 63d5df20050..fe2d3b0da14 100644 --- a/homeassistant/components/energy/__init__.py +++ b/homeassistant/components/energy/__init__.py @@ -29,7 +29,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: frontend.async_register_built_in_panel(hass, DOMAIN, DOMAIN, "mdi:lightning-bolt") hass.async_create_task( - discovery.async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) + discovery.async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, config), + eager_start=True, ) hass.data[DOMAIN] = { "cost_sensors": {}, From 089a3ab6d71e2920c2e5e79afd66ec1c7d3cab29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Mind=C3=AAllo=20de=20Andrade?= Date: Tue, 19 Mar 2024 04:50:11 -0300 Subject: [PATCH 1267/1691] Bump sunweg to 2.1.1 (#113767) --- homeassistant/components/sunweg/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sunweg/conftest.py | 20 +++++++++++++++++++ tests/components/sunweg/test_init.py | 16 +++++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sunweg/manifest.json b/homeassistant/components/sunweg/manifest.json index b681ecc6d5f..3e41d331e8c 100644 --- a/homeassistant/components/sunweg/manifest.json +++ b/homeassistant/components/sunweg/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/sunweg/", "iot_class": "cloud_polling", "loggers": ["sunweg"], - "requirements": ["sunweg==2.1.0"] + "requirements": ["sunweg==2.1.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6640155c623..256055c801c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2631,7 +2631,7 @@ subarulink==0.7.9 sunwatcher==0.2.1 # homeassistant.components.sunweg -sunweg==2.1.0 +sunweg==2.1.1 # homeassistant.components.surepetcare surepy==0.9.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 34df50d9eb9..d2cc004fc32 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2032,7 +2032,7 @@ subarulink==0.7.9 sunwatcher==0.2.1 # homeassistant.components.sunweg -sunweg==2.1.0 +sunweg==2.1.1 # homeassistant.components.surepetcare surepy==0.9.0 diff --git a/tests/components/sunweg/conftest.py b/tests/components/sunweg/conftest.py index 68c4cab86c5..db94b9cc5c8 100644 --- a/tests/components/sunweg/conftest.py +++ b/tests/components/sunweg/conftest.py @@ -68,3 +68,23 @@ def plant_fixture(inverter_fixture) -> Plant: ) plant.inverters.append(inverter_fixture) return plant + + +@pytest.fixture +def plant_fixture_alternative(inverter_fixture) -> Plant: + """Define Plant fixture.""" + plant = Plant( + 123456, + "Plant #123", + 29.5, + 0.5, + 0, + 12.786912, + 24.0, + "kWh", + 332.2, + 0.012296, + None, + ) + plant.inverters.append(inverter_fixture) + return plant diff --git a/tests/components/sunweg/test_init.py b/tests/components/sunweg/test_init.py index 0295e778f9c..5707fd93b99 100644 --- a/tests/components/sunweg/test_init.py +++ b/tests/components/sunweg/test_init.py @@ -76,6 +76,22 @@ async def test_sunwegdata_update_success(plant_fixture) -> None: assert len(data.data.inverters) == 1 +async def test_sunwegdata_update_success_alternative(plant_fixture_alternative) -> None: + """Test SunWEGData success on update.""" + api = MagicMock() + api.plant = MagicMock(return_value=plant_fixture_alternative) + api.complete_inverter = MagicMock() + data = SunWEGData(api, 0) + data.update() + assert data.data.id == plant_fixture_alternative.id + assert data.data.name == plant_fixture_alternative.name + assert data.data.kwh_per_kwp == plant_fixture_alternative.kwh_per_kwp + assert data.data.last_update == plant_fixture_alternative.last_update + assert data.data.performance_rate == plant_fixture_alternative.performance_rate + assert data.data.saving == plant_fixture_alternative.saving + assert len(data.data.inverters) == 1 + + async def test_sunwegdata_get_api_value_none(plant_fixture) -> None: """Test SunWEGData none return on get_api_value.""" api = MagicMock() From 00ec7f11f0536431a45de2470b2d60e33ef5eda9 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Tue, 19 Mar 2024 09:01:07 +0100 Subject: [PATCH 1268/1691] Enable Ruff rule PT007 (#113764) Co-authored-by: Franck Nijhof --- pyproject.toml | 3 +- .../tests/test_config_flow.py | 4 +- .../config_flow_helper/tests/test_init.py | 2 +- .../device_action/tests/test_device_action.py | 4 +- tests/components/airly/test_init.py | 2 +- .../alarm_control_panel/test_device_action.py | 4 +- .../test_device_condition.py | 4 +- .../test_device_trigger.py | 4 +- tests/components/apcupsd/test_init.py | 10 +- tests/components/automation/test_init.py | 24 +-- tests/components/backup/test_websocket.py | 28 ++-- .../binary_sensor/test_device_condition.py | 4 +- .../binary_sensor/test_device_trigger.py | 4 +- tests/components/blueprint/test_importer.py | 4 +- tests/components/blueprint/test_schemas.py | 12 +- .../blueprint/test_websocket_api.py | 8 +- tests/components/button/test_device_action.py | 4 +- .../components/button/test_device_trigger.py | 4 +- tests/components/cast/test_helpers.py | 16 +- tests/components/cast/test_media_player.py | 6 +- .../components/climate/test_device_action.py | 4 +- .../climate/test_device_condition.py | 4 +- .../components/climate/test_device_trigger.py | 4 +- tests/components/cloudflare/test_init.py | 2 +- tests/components/config/test_automation.py | 18 +-- .../components/config/test_config_entries.py | 4 +- .../components/config/test_device_registry.py | 14 +- tests/components/config/test_scene.py | 12 +- tests/components/config/test_script.py | 18 +-- tests/components/conversation/test_init.py | 6 +- tests/components/cover/test_device_action.py | 4 +- .../components/cover/test_device_condition.py | 4 +- tests/components/cover/test_device_trigger.py | 4 +- tests/components/demo/test_sensor.py | 4 +- .../components/derivative/test_config_flow.py | 4 +- tests/components/derivative/test_init.py | 2 +- .../device_tracker/test_device_condition.py | 4 +- .../device_tracker/test_device_trigger.py | 4 +- .../devolo_home_network/test_button.py | 16 +- .../devolo_home_network/test_config_flow.py | 2 +- .../devolo_home_network/test_init.py | 10 +- .../devolo_home_network/test_sensor.py | 12 +- .../devolo_home_network/test_switch.py | 8 +- .../components/dlna_dmr/test_media_player.py | 10 +- .../dormakaba_dkey/test_config_flow.py | 8 +- .../dremel_3d_printer/test_button.py | 4 +- tests/components/elvia/test_config_flow.py | 4 +- tests/components/energy/test_sensor.py | 6 +- tests/components/energy/test_validate.py | 4 +- .../components/esphome/test_binary_sensor.py | 2 +- tests/components/fan/test_device_action.py | 4 +- tests/components/fan/test_device_condition.py | 4 +- tests/components/fan/test_device_trigger.py | 4 +- tests/components/filter/test_sensor.py | 2 +- .../components/forecast_solar/test_sensor.py | 4 +- .../google_assistant/test_helpers.py | 2 +- .../components/google_assistant/test_trait.py | 12 +- tests/components/google_mail/test_services.py | 4 +- tests/components/gree/test_climate.py | 28 ++-- tests/components/group/test_config_flow.py | 26 +-- tests/components/group/test_init.py | 12 +- tests/components/hassio/test_handler.py | 6 +- .../here_travel_time/test_config_flow.py | 2 +- .../components/history/test_websocket_api.py | 2 +- .../triggers/test_numeric_state.py | 124 +++++++-------- .../homeassistant_alerts/test_init.py | 16 +- .../homeassistant_sky_connect/test_init.py | 2 +- .../homeassistant_yellow/test_init.py | 2 +- tests/components/homekit/test_type_lights.py | 24 +-- tests/components/http/test_static.py | 4 +- .../components/huawei_lte/test_config_flow.py | 18 +-- .../huawei_lte/test_device_tracker.py | 4 +- tests/components/huawei_lte/test_sensor.py | 4 +- .../humidifier/test_device_action.py | 4 +- .../humidifier/test_device_condition.py | 4 +- .../humidifier/test_device_trigger.py | 4 +- tests/components/humidifier/test_intent.py | 2 +- .../components/improv_ble/test_config_flow.py | 26 +-- .../integration/test_config_flow.py | 4 +- tests/components/integration/test_init.py | 2 +- .../components/justnimbus/test_config_flow.py | 4 +- tests/components/light/test_device_action.py | 4 +- .../components/light/test_device_condition.py | 4 +- tests/components/light/test_device_trigger.py | 4 +- tests/components/light/test_init.py | 10 +- .../components/light/test_reproduce_state.py | 8 +- tests/components/litterrobot/test_init.py | 4 +- tests/components/lock/test_device_action.py | 4 +- .../components/lock/test_device_condition.py | 4 +- tests/components/lock/test_device_trigger.py | 4 +- tests/components/lovelace/test_dashboard.py | 2 +- .../lutron_caseta/test_config_flow.py | 2 +- .../media_player/test_device_condition.py | 4 +- .../media_player/test_device_trigger.py | 4 +- tests/components/media_player/test_init.py | 4 +- tests/components/min_max/test_config_flow.py | 4 +- tests/components/min_max/test_init.py | 2 +- tests/components/mobile_app/test_init.py | 2 +- tests/components/mobile_app/test_sensor.py | 8 +- tests/components/mobile_app/test_webhook.py | 4 +- tests/components/modbus/test_init.py | 18 +-- .../netgear_lte/test_config_flow.py | 2 +- tests/components/nextbus/test_config_flow.py | 4 +- tests/components/nextbus/test_sensor.py | 8 +- tests/components/nextbus/test_util.py | 8 +- .../nibe_heatpump/test_config_flow.py | 20 +-- tests/components/number/test_device_action.py | 4 +- tests/components/oncue/test_sensor.py | 8 +- tests/components/p1_monitor/test_sensor.py | 2 +- tests/components/panel_iframe/test_init.py | 4 +- tests/components/ping/test_config_flow.py | 4 +- .../components/powerwall/test_config_flow.py | 2 +- tests/components/prusalink/test_button.py | 8 +- tests/components/random/test_config_flow.py | 8 +- .../auto_repairs/events/test_schema.py | 2 +- .../auto_repairs/states/test_schema.py | 2 +- .../auto_repairs/statistics/test_schema.py | 4 +- .../recorder/auto_repairs/test_schema.py | 2 +- tests/components/recorder/test_init.py | 16 +- tests/components/recorder/test_purge.py | 10 +- .../recorder/test_purge_v32_schema.py | 4 +- tests/components/recorder/test_statistics.py | 6 +- .../components/recorder/test_websocket_api.py | 22 +-- tests/components/remote/test_device_action.py | 4 +- .../remote/test_device_condition.py | 4 +- .../components/remote/test_device_trigger.py | 4 +- tests/components/repairs/test_init.py | 2 +- .../components/repairs/test_websocket_api.py | 4 +- tests/components/rest/test_sensor.py | 4 +- tests/components/rfxtrx/test_binary_sensor.py | 2 +- tests/components/rfxtrx/test_device_action.py | 22 +-- .../components/rfxtrx/test_device_trigger.py | 4 +- tests/components/rfxtrx/test_light.py | 2 +- tests/components/rfxtrx/test_sensor.py | 2 +- tests/components/schedule/test_init.py | 16 +- tests/components/script/test_init.py | 26 +-- tests/components/select/test_device_action.py | 8 +- .../select/test_device_condition.py | 4 +- .../components/select/test_device_trigger.py | 4 +- .../sensor/test_device_condition.py | 8 +- .../components/sensor/test_device_trigger.py | 8 +- tests/components/sensor/test_init.py | 14 +- tests/components/sonarr/test_sensor.py | 4 +- tests/components/stt/test_init.py | 4 +- tests/components/switch/test_device_action.py | 4 +- .../switch/test_device_condition.py | 4 +- .../components/switch/test_device_trigger.py | 4 +- .../switch_as_x/test_config_flow.py | 4 +- tests/components/switch_as_x/test_init.py | 8 +- tests/components/tasmota/test_cover.py | 2 +- .../components/technove/test_binary_sensor.py | 2 +- tests/components/technove/test_sensor.py | 4 +- tests/components/template/test_config_flow.py | 16 +- tests/components/text/test_device_action.py | 4 +- tests/components/thread/test_discovery.py | 2 +- .../components/threshold/test_config_flow.py | 2 +- tests/components/threshold/test_init.py | 2 +- tests/components/tod/test_binary_sensor.py | 14 +- tests/components/tod/test_config_flow.py | 2 +- tests/components/todo/test_init.py | 32 ++-- .../traccar_server/test_config_flow.py | 8 +- tests/components/tts/test_init.py | 8 +- .../components/update/test_device_trigger.py | 4 +- .../utility_meter/test_config_flow.py | 2 +- tests/components/utility_meter/test_init.py | 8 +- tests/components/utility_meter/test_sensor.py | 52 +++--- tests/components/vacuum/test_device_action.py | 4 +- .../vacuum/test_device_condition.py | 4 +- .../components/vacuum/test_device_trigger.py | 4 +- .../water_heater/test_device_action.py | 4 +- tests/components/weather/test_init.py | 66 ++++---- .../components/websocket_api/test_commands.py | 10 +- tests/components/whois/test_sensor.py | 12 +- tests/components/withings/test_init.py | 14 +- tests/components/wled/test_sensor.py | 4 +- .../components/zeversolar/test_config_flow.py | 4 +- tests/components/zha/test_climate.py | 24 +-- tests/components/zha/test_config_flow.py | 2 +- tests/components/zha/test_device.py | 4 +- tests/components/zha/test_fan.py | 12 +- tests/components/zha/test_init.py | 6 +- tests/components/zha/test_number.py | 6 +- tests/components/zha/test_registries.py | 4 +- tests/components/zha/test_sensor.py | 20 +-- tests/components/zha/test_websocket_api.py | 8 +- tests/helpers/test_config_validation.py | 4 +- tests/helpers/test_device_registry.py | 8 +- tests/helpers/test_entity.py | 42 ++--- tests/helpers/test_entity_platform.py | 24 +-- tests/helpers/test_integration_platform.py | 2 +- tests/helpers/test_json.py | 2 +- tests/helpers/test_registry.py | 4 +- .../helpers/test_schema_config_entry_flow.py | 4 +- tests/helpers/test_script.py | 4 +- tests/helpers/test_selector.py | 150 +++++++++--------- tests/helpers/test_service.py | 2 +- tests/helpers/test_singleton.py | 4 +- tests/helpers/test_template.py | 8 +- tests/helpers/test_translation.py | 4 +- tests/test_config.py | 4 +- tests/test_config_entries.py | 28 ++-- tests/test_core.py | 4 +- tests/test_data_entry_flow.py | 2 +- tests/util/test_unit_system.py | 16 +- 204 files changed, 908 insertions(+), 921 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8c79824a1f6..17fb6307710 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -658,6 +658,7 @@ ignore = [ "PLR0915", # Too many statements ({statements} > {max_statements}) "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target + "PT004", # Fixture {fixture} does not return anything, add leading underscore "SIM102", # Use a single if statement instead of nested if statements "SIM108", # Use ternary operator {contents} instead of if-else-block "SIM115", # Use context handler for opening files @@ -682,8 +683,6 @@ ignore = [ "PLE0605", # temporarily disabled - "PT004", - "PT007", "PT011", "PT018", "PT012", diff --git a/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py b/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py index 085ae289f65..809902fa0dd 100644 --- a/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow_helper/tests/test_config_flow.py @@ -14,7 +14,7 @@ from tests.common import MockConfigEntry pytestmark = pytest.mark.usefixtures("mock_setup_entry") -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_config_flow( hass: HomeAssistant, mock_setup_entry: AsyncMock, platform ) -> None: @@ -62,7 +62,7 @@ def get_suggested(schema, key): raise Exception -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_options(hass: HomeAssistant, platform) -> None: """Test reconfiguring.""" input_sensor_1_entity_id = "sensor.input1" diff --git a/script/scaffold/templates/config_flow_helper/tests/test_init.py b/script/scaffold/templates/config_flow_helper/tests/test_init.py index 9f6951189ac..73ac28da059 100644 --- a/script/scaffold/templates/config_flow_helper/tests/test_init.py +++ b/script/scaffold/templates/config_flow_helper/tests/test_init.py @@ -9,7 +9,7 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_setup_and_remove_config_entry( hass: HomeAssistant, platform: str, diff --git a/script/scaffold/templates/device_action/tests/test_device_action.py b/script/scaffold/templates/device_action/tests/test_device_action.py index f8260a62282..2ecf3e95f9a 100644 --- a/script/scaffold/templates/device_action/tests/test_device_action.py +++ b/script/scaffold/templates/device_action/tests/test_device_action.py @@ -50,12 +50,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/airly/test_init.py b/tests/components/airly/test_init.py index 74051737197..6fc26110186 100644 --- a/tests/components/airly/test_init.py +++ b/tests/components/airly/test_init.py @@ -196,7 +196,7 @@ async def test_unload_entry( assert not hass.data.get(DOMAIN) -@pytest.mark.parametrize("old_identifier", ((DOMAIN, 123, 456), (DOMAIN, "123", "456"))) +@pytest.mark.parametrize("old_identifier", [(DOMAIN, 123, 456), (DOMAIN, "123", "456")]) async def test_migrate_device_entry( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index ff999a0a273..81aa9c903ba 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -132,12 +132,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/alarm_control_panel/test_device_condition.py b/tests/components/alarm_control_panel/test_device_condition.py index 72ea9530fbd..d95574b7c9f 100644 --- a/tests/components/alarm_control_panel/test_device_condition.py +++ b/tests/components/alarm_control_panel/test_device_condition.py @@ -139,12 +139,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 76883516957..be241ef241e 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -131,12 +131,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/apcupsd/test_init.py b/tests/components/apcupsd/test_init.py index 4a579cdb899..723ec164eae 100644 --- a/tests/components/apcupsd/test_init.py +++ b/tests/components/apcupsd/test_init.py @@ -21,7 +21,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed @pytest.mark.parametrize( "status", - ( + [ # Contains "SERIALNO" and "UPSNAME" fields. # We should create devices for the entities and prefix their IDs with "MyUPS". MOCK_STATUS, @@ -31,7 +31,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed # Does not contain either "SERIALNO" field. # We should _not_ create devices for the entities and their IDs will not have prefixes. MOCK_MINIMAL_STATUS, - ), + ], ) async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> None: """Test a successful setup entry.""" @@ -53,7 +53,7 @@ async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> No @pytest.mark.parametrize( "status", - ( + [ # We should not create device entries if SERIALNO is not reported. MOCK_MINIMAL_STATUS, # We should set the device name to be the friendly UPSNAME field if available. @@ -62,7 +62,7 @@ async def test_async_setup_entry(hass: HomeAssistant, status: OrderedDict) -> No MOCK_MINIMAL_STATUS | {"SERIALNO": "XXXX"}, # We should create all fields of the device entry if they are available. MOCK_STATUS, - ), + ], ) async def test_device_entry( hass: HomeAssistant, status: OrderedDict, device_registry: dr.DeviceRegistry @@ -139,7 +139,7 @@ async def test_multiple_integrations_different_devices(hass: HomeAssistant) -> N @pytest.mark.parametrize( "error", - (OSError(), asyncio.IncompleteReadError(partial=b"", expected=0)), + [OSError(), asyncio.IncompleteReadError(partial=b"", expected=0)], ) async def test_connection_error(hass: HomeAssistant, error: Exception) -> None: """Test connection error during integration setup.""" diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index e099c25f58f..70aae2a66c8 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -754,7 +754,7 @@ async def test_automation_stops(hass: HomeAssistant, calls, service) -> None: assert len(calls) == (1 if service == "turn_off_no_stop" else 0) -@pytest.mark.parametrize("extra_config", ({}, {"id": "sun"})) +@pytest.mark.parametrize("extra_config", [{}, {"id": "sun"}]) async def test_reload_unchanged_does_not_stop( hass: HomeAssistant, calls, extra_config ) -> None: @@ -962,7 +962,7 @@ async def test_reload_identical_automations_without_id( @pytest.mark.parametrize( "automation_config", - ( + [ { "trigger": {"platform": "event", "event_type": "test_event"}, "action": [{"service": "test.automation"}], @@ -1029,7 +1029,7 @@ async def test_reload_identical_automations_without_id( }, }, }, - ), + ], ) async def test_reload_unchanged_automation( hass: HomeAssistant, calls, automation_config @@ -1065,7 +1065,7 @@ async def test_reload_unchanged_automation( assert len(calls) == 2 -@pytest.mark.parametrize("extra_config", ({}, {"id": "sun"})) +@pytest.mark.parametrize("extra_config", [{}, {"id": "sun"}]) async def test_reload_automation_when_blueprint_changes( hass: HomeAssistant, calls, extra_config ) -> None: @@ -1362,7 +1362,7 @@ async def test_automation_not_trigger_on_bootstrap(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("broken_config", "problem", "details"), - ( + [ ( {}, "could not be validated", @@ -1403,7 +1403,7 @@ async def test_automation_not_trigger_on_bootstrap(hass: HomeAssistant) -> None: "failed to setup actions", "Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.", ), - ), + ], ) async def test_automation_bad_config_validation( hass: HomeAssistant, @@ -2141,7 +2141,7 @@ async def test_blueprint_automation(hass: HomeAssistant, calls) -> None: @pytest.mark.parametrize( ("blueprint_inputs", "problem", "details"), - ( + [ ( # No input {}, @@ -2167,7 +2167,7 @@ async def test_blueprint_automation(hass: HomeAssistant, calls) -> None: " data['action'][0]['service']" ), ), - ), + ], ) async def test_blueprint_automation_bad_config( hass: HomeAssistant, @@ -2350,21 +2350,21 @@ async def test_trigger_condition_explicit_id(hass: HomeAssistant, calls) -> None @pytest.mark.parametrize( ("automation_mode", "automation_runs"), - ( + [ (SCRIPT_MODE_PARALLEL, 2), (SCRIPT_MODE_QUEUED, 2), (SCRIPT_MODE_RESTART, 2), (SCRIPT_MODE_SINGLE, 1), - ), + ], ) @pytest.mark.parametrize( ("script_mode", "script_warning_msg"), - ( + [ (SCRIPT_MODE_PARALLEL, "script1: Maximum number of runs exceeded"), (SCRIPT_MODE_QUEUED, "script1: Disallowed recursion detected"), (SCRIPT_MODE_RESTART, "script1: Disallowed recursion detected"), (SCRIPT_MODE_SINGLE, "script1: Already running"), - ), + ], ) @pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True]) async def test_recursive_automation_starting_script( diff --git a/tests/components/backup/test_websocket.py b/tests/components/backup/test_websocket.py index 18c5ff355c5..79d682c69fe 100644 --- a/tests/components/backup/test_websocket.py +++ b/tests/components/backup/test_websocket.py @@ -27,10 +27,10 @@ def sync_access_token_proxy( @pytest.mark.parametrize( "with_hassio", - ( + [ pytest.param(True, id="with_hassio"), pytest.param(False, id="without_hassio"), - ), + ], ) async def test_info( hass: HomeAssistant, @@ -54,10 +54,10 @@ async def test_info( @pytest.mark.parametrize( "with_hassio", - ( + [ pytest.param(True, id="with_hassio"), pytest.param(False, id="without_hassio"), - ), + ], ) async def test_remove( hass: HomeAssistant, @@ -80,10 +80,10 @@ async def test_remove( @pytest.mark.parametrize( "with_hassio", - ( + [ pytest.param(True, id="with_hassio"), pytest.param(False, id="without_hassio"), - ), + ], ) async def test_generate( hass: HomeAssistant, @@ -111,10 +111,10 @@ async def test_generate( ) @pytest.mark.parametrize( ("with_hassio"), - ( + [ pytest.param(True, id="with_hassio"), pytest.param(False, id="without_hassio"), - ), + ], ) async def test_backup_end( hass: HomeAssistant, @@ -145,10 +145,10 @@ async def test_backup_end( ) @pytest.mark.parametrize( ("with_hassio"), - ( + [ pytest.param(True, id="with_hassio"), pytest.param(False, id="without_hassio"), - ), + ], ) async def test_backup_start( hass: HomeAssistant, @@ -174,11 +174,11 @@ async def test_backup_start( @pytest.mark.parametrize( "exception", - ( + [ TimeoutError(), HomeAssistantError("Boom"), Exception("Boom"), - ), + ], ) async def test_backup_end_excepion( hass: HomeAssistant, @@ -203,11 +203,11 @@ async def test_backup_end_excepion( @pytest.mark.parametrize( "exception", - ( + [ TimeoutError(), HomeAssistantError("Boom"), Exception("Boom"), - ), + ], ) async def test_backup_start_excepion( hass: HomeAssistant, diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index 23907586c01..904e62ef18d 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -82,12 +82,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index 66fa8d35a92..ae78620b939 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -82,12 +82,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/blueprint/test_importer.py b/tests/components/blueprint/test_importer.py index 78a70a57fe2..76f3ff36d05 100644 --- a/tests/components/blueprint/test_importer.py +++ b/tests/components/blueprint/test_importer.py @@ -119,10 +119,10 @@ async def test_fetch_blueprint_from_community_url( @pytest.mark.parametrize( "url", - ( + [ "https://raw.githubusercontent.com/balloob/home-assistant-config/main/blueprints/automation/motion_light.yaml", "https://github.com/balloob/home-assistant-config/blob/main/blueprints/automation/motion_light.yaml", - ), + ], ) async def test_fetch_blueprint_from_github_url( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, url: str diff --git a/tests/components/blueprint/test_schemas.py b/tests/components/blueprint/test_schemas.py index 0afe9e780f5..0440a759f2f 100644 --- a/tests/components/blueprint/test_schemas.py +++ b/tests/components/blueprint/test_schemas.py @@ -12,7 +12,7 @@ _LOGGER = logging.getLogger(__name__) @pytest.mark.parametrize( "blueprint", - ( + [ # Test allow extra { "trigger": "Test allow extra", @@ -52,7 +52,7 @@ _LOGGER = logging.getLogger(__name__) }, } }, - ), + ], ) def test_blueprint_schema(blueprint) -> None: """Test different schemas.""" @@ -65,7 +65,7 @@ def test_blueprint_schema(blueprint) -> None: @pytest.mark.parametrize( "blueprint", - ( + [ # no domain {"blueprint": {}}, # non existing key in blueprint @@ -94,7 +94,7 @@ def test_blueprint_schema(blueprint) -> None: }, } }, - ), + ], ) def test_blueprint_schema_invalid(blueprint) -> None: """Test different schemas.""" @@ -104,11 +104,11 @@ def test_blueprint_schema_invalid(blueprint) -> None: @pytest.mark.parametrize( "bp_instance", - ( + [ {"path": "hello.yaml"}, {"path": "hello.yaml", "input": {}}, {"path": "hello.yaml", "input": {"hello": None}}, - ), + ], ) def test_blueprint_instance_fields(bp_instance) -> None: """Test blueprint instance fields.""" diff --git a/tests/components/blueprint/test_websocket_api.py b/tests/components/blueprint/test_websocket_api.py index a5ed448aeca..93d97dfd036 100644 --- a/tests/components/blueprint/test_websocket_api.py +++ b/tests/components/blueprint/test_websocket_api.py @@ -400,7 +400,7 @@ async def test_delete_non_exist_file_blueprint( @pytest.mark.parametrize( "automation_config", - ( + [ { "automation": { "use_blueprint": { @@ -413,7 +413,7 @@ async def test_delete_non_exist_file_blueprint( } } }, - ), + ], ) async def test_delete_blueprint_in_use_by_automation( hass: HomeAssistant, @@ -446,7 +446,7 @@ async def test_delete_blueprint_in_use_by_automation( @pytest.mark.parametrize( "script_config", - ( + [ { "script": { "test_script": { @@ -459,7 +459,7 @@ async def test_delete_blueprint_in_use_by_automation( } } }, - ), + ], ) async def test_delete_blueprint_in_use_by_script( hass: HomeAssistant, diff --git a/tests/components/button/test_device_action.py b/tests/components/button/test_device_action.py index 3e05052f978..f0d34e25e37 100644 --- a/tests/components/button/test_device_action.py +++ b/tests/components/button/test_device_action.py @@ -50,12 +50,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/button/test_device_trigger.py b/tests/components/button/test_device_trigger.py index ba40fb30d08..034b8ed7e6e 100644 --- a/tests/components/button/test_device_trigger.py +++ b/tests/components/button/test_device_trigger.py @@ -59,12 +59,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass, diff --git a/tests/components/cast/test_helpers.py b/tests/components/cast/test_helpers.py index d19c4f212d0..84914db2b3a 100644 --- a/tests/components/cast/test_helpers.py +++ b/tests/components/cast/test_helpers.py @@ -17,7 +17,7 @@ from tests.test_util.aiohttp import AiohttpClientMocker @pytest.mark.parametrize( ("url", "fixture", "content_type"), - ( + [ ( "http://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_low/ak/bbc_radio_fourfm.m3u8", "bbc_radio_fourfm.m3u8", @@ -33,7 +33,7 @@ from tests.test_util.aiohttp import AiohttpClientMocker "rthkaudio2.m3u8", None, ), - ), + ], ) async def test_hls_playlist_supported( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, url, fixture, content_type @@ -47,7 +47,7 @@ async def test_hls_playlist_supported( @pytest.mark.parametrize( ("url", "fixture", "content_type", "expected_playlist"), - ( + [ ( "https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u", "209-hi-mp3.m3u", @@ -96,7 +96,7 @@ async def test_hls_playlist_supported( ) ], ), - ), + ], ) async def test_parse_playlist( hass: HomeAssistant, @@ -115,7 +115,7 @@ async def test_parse_playlist( @pytest.mark.parametrize( ("url", "fixture"), - ( + [ ("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid_entries.pls"), ("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid_file.pls"), ("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid_version.pls"), @@ -126,7 +126,7 @@ async def test_parse_playlist( ("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_no_version.pls"), ("https://sverigesradio.se/209-hi-mp3.m3u", "209-hi-mp3_bad_url.m3u"), ("https://sverigesradio.se/209-hi-mp3.m3u", "empty.m3u"), - ), + ], ) async def test_parse_bad_playlist( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, url, fixture @@ -139,10 +139,10 @@ async def test_parse_bad_playlist( @pytest.mark.parametrize( ("url", "exc"), - ( + [ ("http://sverigesradio.se/164-hi-aac.pls", TimeoutError), ("http://sverigesradio.se/164-hi-aac.pls", client_exceptions.ClientError), - ), + ], ) async def test_parse_http_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, url, exc diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index ce69c6778f4..7756a2aacbd 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -754,7 +754,7 @@ async def test_entity_availability(hass: HomeAssistant) -> None: assert state.state == "unavailable" -@pytest.mark.parametrize(("port", "entry_type"), ((8009, None), (12345, None))) +@pytest.mark.parametrize(("port", "entry_type"), [(8009, None), (12345, None)]) async def test_device_registry( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, @@ -1261,7 +1261,7 @@ async def test_entity_play_media_sign_URL(hass: HomeAssistant, quick_play_mock) @pytest.mark.parametrize( ("url", "fixture", "playlist_item"), - ( + [ # Test title is extracted from m3u playlist ( "https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u", @@ -1307,7 +1307,7 @@ async def test_entity_play_media_sign_URL(hass: HomeAssistant, quick_play_mock) "media_type": "audio", }, ), - ), + ], ) async def test_entity_play_media_playlist( hass: HomeAssistant, diff --git a/tests/components/climate/test_device_action.py b/tests/components/climate/test_device_action.py index f15ee3817e7..3ee5a9b8edd 100644 --- a/tests/components/climate/test_device_action.py +++ b/tests/components/climate/test_device_action.py @@ -97,12 +97,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/climate/test_device_condition.py b/tests/components/climate/test_device_condition.py index 0d6767c374c..d9345a0516c 100644 --- a/tests/components/climate/test_device_condition.py +++ b/tests/components/climate/test_device_condition.py @@ -101,12 +101,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/climate/test_device_trigger.py b/tests/components/climate/test_device_trigger.py index 13426ba02a2..7dbe106bd4f 100644 --- a/tests/components/climate/test_device_trigger.py +++ b/tests/components/climate/test_device_trigger.py @@ -88,12 +88,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/cloudflare/test_init.py b/tests/components/cloudflare/test_init.py index 128c865e744..e070be749dd 100644 --- a/tests/components/cloudflare/test_init.py +++ b/tests/components/cloudflare/test_init.py @@ -39,7 +39,7 @@ async def test_unload_entry(hass: HomeAssistant, cfupdate) -> None: @pytest.mark.parametrize( "side_effect", - (pycfdns.ComunicationException(),), + [pycfdns.ComunicationException()], ) async def test_async_setup_raises_entry_not_ready( hass: HomeAssistant, cfupdate, side_effect diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 6d28a66fdc3..80f68b96fe1 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -35,7 +35,7 @@ async def setup_automation( ) -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) async def test_get_automation_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -58,7 +58,7 @@ async def test_get_automation_config( assert result == {"id": "moon"} -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) async def test_update_automation_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -97,7 +97,7 @@ async def test_update_automation_config( assert new_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) @pytest.mark.parametrize( ("updated_config", "validation_error"), [ @@ -179,7 +179,7 @@ async def test_update_automation_config_with_error( assert validation_error not in caplog.text -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) @pytest.mark.parametrize( ("updated_config", "validation_error"), [ @@ -236,7 +236,7 @@ async def test_update_automation_config_with_blueprint_substitution_error( assert validation_error not in caplog.text -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) async def test_update_remove_key_automation_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -275,7 +275,7 @@ async def test_update_remove_key_automation_config( assert new_data[1] == {"id": "moon", "trigger": [], "condition": [], "action": []} -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) async def test_bad_formatted_automations( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -323,7 +323,7 @@ async def test_bad_formatted_automations( @pytest.mark.parametrize( "automation_config", - ( + [ [ { "id": "sun", @@ -336,7 +336,7 @@ async def test_bad_formatted_automations( "action": {"service": "test.automation"}, }, ], - ), + ], ) async def test_delete_automation( hass: HomeAssistant, @@ -378,7 +378,7 @@ async def test_delete_automation( assert len(entity_registry.entities) == 1 -@pytest.mark.parametrize("automation_config", ({},)) +@pytest.mark.parametrize("automation_config", [{}]) async def test_api_calls_require_admin( hass: HomeAssistant, hass_client: ClientSessionGenerator, diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index a373587e1ab..b4ef32b864c 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -329,11 +329,11 @@ async def test_reload_entry_in_setup_retry( @pytest.mark.parametrize( ("type_filter", "result"), - ( + [ (None, {"hello", "another", "world"}), ("integration", {"hello", "another"}), ("helper", {"world"}), - ), + ], ) async def test_available_flows( hass: HomeAssistant, client, type_filter, result diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index bfb1ebdb191..f88ae42b98a 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -132,13 +132,13 @@ async def test_list_devices( @pytest.mark.parametrize( ("payload_key", "payload_value"), [ - ["area_id", "12345A"], - ["area_id", None], - ["disabled_by", dr.DeviceEntryDisabler.USER], - ["disabled_by", "user"], - ["disabled_by", None], - ["name_by_user", "Test Friendly Name"], - ["name_by_user", None], + ("area_id", "12345A"), + ("area_id", None), + ("disabled_by", dr.DeviceEntryDisabler.USER), + ("disabled_by", "user"), + ("disabled_by", None), + ("name_by_user", "Test Friendly Name"), + ("name_by_user", None), ], ) async def test_update_device( diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index dfb7cdad8c6..6ca42e7f56d 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -22,7 +22,7 @@ async def setup_scene(hass, scene_config): await hass.async_block_till_done() -@pytest.mark.parametrize("scene_config", ({},)) +@pytest.mark.parametrize("scene_config", [{}]) async def test_create_scene( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -69,7 +69,7 @@ async def test_create_scene( ] -@pytest.mark.parametrize("scene_config", ({},)) +@pytest.mark.parametrize("scene_config", [{}]) async def test_update_scene( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -117,7 +117,7 @@ async def test_update_scene( ] -@pytest.mark.parametrize("scene_config", ({},)) +@pytest.mark.parametrize("scene_config", [{}]) async def test_bad_formatted_scene( hass: HomeAssistant, hass_client: ClientSessionGenerator, @@ -177,12 +177,12 @@ async def test_bad_formatted_scene( @pytest.mark.parametrize( "scene_config", - ( + [ [ {"id": "light_on", "name": "Light on", "entities": {}}, {"id": "light_off", "name": "Light off", "entities": {}}, ], - ), + ], ) async def test_delete_scene( hass: HomeAssistant, @@ -226,7 +226,7 @@ async def test_delete_scene( assert len(entity_registry.entities) == 1 -@pytest.mark.parametrize("scene_config", ({},)) +@pytest.mark.parametrize("scene_config", [{}]) async def test_api_calls_require_admin( hass: HomeAssistant, hass_client: ClientSessionGenerator, diff --git a/tests/components/config/test_script.py b/tests/components/config/test_script.py index cca4f07f8e0..3c1970a9bca 100644 --- a/tests/components/config/test_script.py +++ b/tests/components/config/test_script.py @@ -29,7 +29,7 @@ async def setup_script(hass, script_config, stub_blueprint_populate): assert await async_setup_component(hass, "script", {"script": script_config}) -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) async def test_get_script_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: @@ -52,7 +52,7 @@ async def test_get_script_config( assert result == {"alias": "Moon"} -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) async def test_update_script_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: @@ -88,7 +88,7 @@ async def test_update_script_config( assert new_data["moon"] == {"alias": "Moon updated", "sequence": []} -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) async def test_invalid_object_id( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: @@ -122,7 +122,7 @@ async def test_invalid_object_id( assert new_data == {} -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) @pytest.mark.parametrize( ("updated_config", "validation_error"), [ @@ -182,7 +182,7 @@ async def test_update_script_config_with_error( assert validation_error not in caplog.text -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) @pytest.mark.parametrize( ("updated_config", "validation_error"), [ @@ -237,7 +237,7 @@ async def test_update_script_config_with_blueprint_substitution_error( assert validation_error not in caplog.text -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) async def test_update_remove_key_script_config( hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_config_store ) -> None: @@ -275,12 +275,12 @@ async def test_update_remove_key_script_config( @pytest.mark.parametrize( "script_config", - ( + [ { "one": {"alias": "Light on", "sequence": []}, "two": {"alias": "Light off", "sequence": []}, }, - ), + ], ) async def test_delete_script( hass: HomeAssistant, @@ -320,7 +320,7 @@ async def test_delete_script( assert len(entity_registry.entities) == 1 -@pytest.mark.parametrize("script_config", ({},)) +@pytest.mark.parametrize("script_config", [{}]) async def test_api_calls_require_admin( hass: HomeAssistant, hass_client: ClientSessionGenerator, diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 1e445431c24..fd48fe450dc 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -502,8 +502,8 @@ async def test_http_processing_intent_conversion_not_expose_new( @pytest.mark.parametrize("agent_id", AGENT_ID_OPTIONS) -@pytest.mark.parametrize("sentence", ("turn on kitchen", "turn kitchen on")) -@pytest.mark.parametrize("conversation_id", ("my_new_conversation", None)) +@pytest.mark.parametrize("sentence", ["turn on kitchen", "turn kitchen on"]) +@pytest.mark.parametrize("conversation_id", ["my_new_conversation", None]) async def test_turn_on_intent( hass: HomeAssistant, init_components, conversation_id, sentence, agent_id, snapshot ) -> None: @@ -550,7 +550,7 @@ async def test_service_fails(hass: HomeAssistant, init_components) -> None: ) -@pytest.mark.parametrize("sentence", ("turn off kitchen", "turn kitchen off")) +@pytest.mark.parametrize("sentence", ["turn off kitchen", "turn kitchen off"]) async def test_turn_off_intent(hass: HomeAssistant, init_components, sentence) -> None: """Test calling the turn on intent.""" hass.states.async_set("light.kitchen", "on") diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index 814d70e5c43..089b5b9052a 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -94,12 +94,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index dc92b209e57..781dbfb107a 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -123,12 +123,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index be91ee38be9..33ddef80d07 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -124,12 +124,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/demo/test_sensor.py b/tests/components/demo/test_sensor.py index 57650f3fce4..b017035caf1 100644 --- a/tests/components/demo/test_sensor.py +++ b/tests/components/demo/test_sensor.py @@ -26,7 +26,7 @@ async def sensor_only() -> None: yield -@pytest.mark.parametrize(("entity_id", "delta"), (("sensor.total_energy_kwh", 0.5),)) +@pytest.mark.parametrize(("entity_id", "delta"), [("sensor.total_energy_kwh", 0.5)]) async def test_energy_sensor( hass: HomeAssistant, entity_id, delta, freezer: FrozenDateTimeFactory ) -> None: @@ -47,7 +47,7 @@ async def test_energy_sensor( assert state.state == str(delta) -@pytest.mark.parametrize(("entity_id", "delta"), (("sensor.total_energy_kwh", 0.5),)) +@pytest.mark.parametrize(("entity_id", "delta"), [("sensor.total_energy_kwh", 0.5)]) async def test_restore_state( hass: HomeAssistant, entity_id, delta, freezer: FrozenDateTimeFactory ) -> None: diff --git a/tests/components/derivative/test_config_flow.py b/tests/components/derivative/test_config_flow.py index 89bf5bb201e..9002a201f85 100644 --- a/tests/components/derivative/test_config_flow.py +++ b/tests/components/derivative/test_config_flow.py @@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_config_flow(hass: HomeAssistant, platform) -> None: """Test the config flow.""" input_sensor_entity_id = "sensor.input" @@ -74,7 +74,7 @@ def get_suggested(schema, key): raise Exception -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_options(hass: HomeAssistant, platform) -> None: """Test reconfiguring.""" # Setup the config entry diff --git a/tests/components/derivative/test_init.py b/tests/components/derivative/test_init.py index 8d387c4ecab..34fe385032b 100644 --- a/tests/components/derivative/test_init.py +++ b/tests/components/derivative/test_init.py @@ -9,7 +9,7 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_setup_and_remove_config_entry( hass: HomeAssistant, entity_registry: er.EntityRegistry, diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index c5abff2e517..431840d2f57 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -64,12 +64,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/device_tracker/test_device_trigger.py b/tests/components/device_tracker/test_device_trigger.py index 217f7641a58..1bbe2394d8e 100644 --- a/tests/components/device_tracker/test_device_trigger.py +++ b/tests/components/device_tracker/test_device_trigger.py @@ -96,12 +96,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/devolo_home_network/test_button.py b/tests/components/devolo_home_network/test_button.py index a46007e4c42..1097c0271cb 100644 --- a/tests/components/devolo_home_network/test_button.py +++ b/tests/components/devolo_home_network/test_button.py @@ -40,26 +40,26 @@ async def test_button_setup(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("name", "api_name", "trigger_method"), [ - [ + ( "identify_device_with_a_blinking_led", "plcnet", "async_identify_device_start", - ], - [ + ), + ( "start_plc_pairing", "plcnet", "async_pair_device", - ], - [ + ), + ( "restart_device", "device", "async_restart", - ], - [ + ), + ( "start_wps", "device", "async_start_wps", - ], + ), ], ) @pytest.mark.freeze_time("2023-01-13 12:00:00+00:00") diff --git a/tests/components/devolo_home_network/test_config_flow.py b/tests/components/devolo_home_network/test_config_flow.py index 77b147e0e48..5d23037df54 100644 --- a/tests/components/devolo_home_network/test_config_flow.py +++ b/tests/components/devolo_home_network/test_config_flow.py @@ -64,7 +64,7 @@ async def test_form(hass: HomeAssistant, info: dict[str, Any]) -> None: @pytest.mark.parametrize( ("exception_type", "expected_error"), - [[DeviceNotFound(IP), "cannot_connect"], [Exception, "unknown"]], + [(DeviceNotFound(IP), "cannot_connect"), (Exception, "unknown")], ) async def test_form_error(hass: HomeAssistant, exception_type, expected_error) -> None: """Test we handle errors.""" diff --git a/tests/components/devolo_home_network/test_init.py b/tests/components/devolo_home_network/test_init.py index bbf83957d6f..60dfbe4596f 100644 --- a/tests/components/devolo_home_network/test_init.py +++ b/tests/components/devolo_home_network/test_init.py @@ -95,15 +95,15 @@ async def test_hass_stop(hass: HomeAssistant, mock_device: MockDevice) -> None: @pytest.mark.parametrize( ("device", "expected_platforms"), [ - [ + ( "mock_device", (BINARY_SENSOR, BUTTON, DEVICE_TRACKER, IMAGE, SENSOR, SWITCH, UPDATE), - ], - [ + ), + ( "mock_repeater_device", (BUTTON, DEVICE_TRACKER, IMAGE, SENSOR, SWITCH, UPDATE), - ], - ["mock_nonwifi_device", (BINARY_SENSOR, BUTTON, SENSOR, SWITCH, UPDATE)], + ), + ("mock_nonwifi_device", (BINARY_SENSOR, BUTTON, SENSOR, SWITCH, UPDATE)), ], ) async def test_platforms( diff --git a/tests/components/devolo_home_network/test_sensor.py b/tests/components/devolo_home_network/test_sensor.py index f82ec732d3e..5b5e05a40d1 100644 --- a/tests/components/devolo_home_network/test_sensor.py +++ b/tests/components/devolo_home_network/test_sensor.py @@ -66,21 +66,21 @@ async def test_sensor_setup(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("name", "get_method", "interval"), [ - [ + ( "connected_wifi_clients", "async_get_wifi_connected_station", SHORT_UPDATE_INTERVAL, - ], - [ + ), + ( "neighboring_wifi_networks", "async_get_wifi_neighbor_access_points", LONG_UPDATE_INTERVAL, - ], - [ + ), + ( "connected_plc_devices", "async_get_network_overview", LONG_UPDATE_INTERVAL, - ], + ), ], ) @pytest.mark.usefixtures("entity_registry_enabled_by_default") diff --git a/tests/components/devolo_home_network/test_switch.py b/tests/components/devolo_home_network/test_switch.py index a2e87ed1b25..0fe5bea5c52 100644 --- a/tests/components/devolo_home_network/test_switch.py +++ b/tests/components/devolo_home_network/test_switch.py @@ -247,8 +247,8 @@ async def test_update_enable_leds( @pytest.mark.parametrize( ("name", "get_method", "update_interval"), [ - ["enable_guest_wifi", "async_get_wifi_guest_access", SHORT_UPDATE_INTERVAL], - ["enable_leds", "async_get_led_setting", SHORT_UPDATE_INTERVAL], + ("enable_guest_wifi", "async_get_wifi_guest_access", SHORT_UPDATE_INTERVAL), + ("enable_leds", "async_get_led_setting", SHORT_UPDATE_INTERVAL), ], ) async def test_device_failure( @@ -284,8 +284,8 @@ async def test_device_failure( @pytest.mark.parametrize( ("name", "set_method"), [ - ["enable_guest_wifi", "async_set_wifi_guest_access"], - ["enable_leds", "async_set_led_setting"], + ("enable_guest_wifi", "async_set_wifi_guest_access"), + ("enable_leds", "async_set_led_setting"), ], ) async def test_auth_failed( diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 6b64ef6f347..18f4a7164c0 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -272,7 +272,7 @@ async def test_setup_entry_no_options( @pytest.mark.parametrize( "core_state", - (CoreState.not_running, CoreState.running), + [CoreState.not_running, CoreState.running], ) async def test_setup_entry_with_options( hass: HomeAssistant, @@ -1261,7 +1261,7 @@ async def test_playback_update_state( @pytest.mark.parametrize( "core_state", - (CoreState.not_running, CoreState.running), + [CoreState.not_running, CoreState.running], ) async def test_unavailable_device( hass: HomeAssistant, @@ -1388,7 +1388,7 @@ async def test_unavailable_device( @pytest.mark.parametrize( "core_state", - (CoreState.not_running, CoreState.running), + [CoreState.not_running, CoreState.running], ) async def test_become_available( hass: HomeAssistant, @@ -1479,7 +1479,7 @@ async def test_become_available( @pytest.mark.parametrize( "core_state", - (CoreState.not_running, CoreState.running), + [CoreState.not_running, CoreState.running], ) async def test_alive_but_gone( hass: HomeAssistant, @@ -2329,7 +2329,7 @@ async def test_config_update_mac_address( @pytest.mark.parametrize( "core_state", - (CoreState.not_running, CoreState.running), + [CoreState.not_running, CoreState.running], ) async def test_connections_restored( hass: HomeAssistant, diff --git a/tests/components/dormakaba_dkey/test_config_flow.py b/tests/components/dormakaba_dkey/test_config_flow.py index 9b070071210..bcb13cd788e 100644 --- a/tests/components/dormakaba_dkey/test_config_flow.py +++ b/tests/components/dormakaba_dkey/test_config_flow.py @@ -222,10 +222,10 @@ async def test_bluetooth_step_already_in_progress(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("exc", "error"), - ( + [ (BleakError, "cannot_connect"), (Exception, "unknown"), - ), + ], ) async def test_bluetooth_step_cannot_connect(hass: HomeAssistant, exc, error) -> None: """Test bluetooth step and we cannot connect.""" @@ -261,10 +261,10 @@ async def test_bluetooth_step_cannot_connect(hass: HomeAssistant, exc, error) -> @pytest.mark.parametrize( ("exc", "error"), - ( + [ (dkey_errors.InvalidActivationCode, "invalid_code"), (dkey_errors.WrongActivationCode, "wrong_code"), - ), + ], ) async def test_bluetooth_step_cannot_associate(hass: HomeAssistant, exc, error) -> None: """Test bluetooth step and we cannot associate.""" diff --git a/tests/components/dremel_3d_printer/test_button.py b/tests/components/dremel_3d_printer/test_button.py index e0f76caf0ed..e0ea0cf9eaf 100644 --- a/tests/components/dremel_3d_printer/test_button.py +++ b/tests/components/dremel_3d_printer/test_button.py @@ -16,11 +16,11 @@ from tests.common import MockConfigEntry @pytest.mark.parametrize( ("button", "function"), - ( + [ ("cancel", "stop"), ("pause", "pause"), ("resume", "resume"), - ), + ], ) async def test_buttons( hass: HomeAssistant, diff --git a/tests/components/elvia/test_config_flow.py b/tests/components/elvia/test_config_flow.py index 470037e5dd6..2fda29217c4 100644 --- a/tests/components/elvia/test_config_flow.py +++ b/tests/components/elvia/test_config_flow.py @@ -198,12 +198,12 @@ async def test_abort_when_metering_point_id_exist( @pytest.mark.parametrize( ("side_effect", "base_error"), - ( + [ (ElviaError.ElviaException("Boom"), "unknown"), (ElviaError.AuthError("Boom", 403, {}, ""), "invalid_auth"), (ElviaError.ElviaServerException("Boom", 500, {}, ""), "unknown"), (ElviaError.ElviaClientException("Boom"), "unknown"), - ), + ], ) async def test_form_exceptions( recorder_mock: Recorder, diff --git a/tests/components/energy/test_sensor.py b/tests/components/energy/test_sensor.py index 5b06d01c1b8..192cf6abea4 100644 --- a/tests/components/energy/test_sensor.py +++ b/tests/components/energy/test_sensor.py @@ -988,7 +988,7 @@ async def test_cost_sensor_handle_late_price_sensor( @pytest.mark.parametrize( "unit", - (UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS), + [UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS], ) async def test_cost_sensor_handle_gas( setup_integration, hass: HomeAssistant, hass_storage: dict[str, Any], unit @@ -1086,12 +1086,12 @@ async def test_cost_sensor_handle_gas_kwh( @pytest.mark.parametrize( ("unit_system", "usage_unit", "growth"), - ( + [ # 1 cubic foot = 7.47 gl, 100 ft3 growth @ 0.5/ft3: (US_CUSTOMARY_SYSTEM, UnitOfVolume.CUBIC_FEET, 374.025974025974), (US_CUSTOMARY_SYSTEM, UnitOfVolume.GALLONS, 50.0), (METRIC_SYSTEM, UnitOfVolume.CUBIC_METERS, 50.0), - ), + ], ) async def test_cost_sensor_handle_water( setup_integration, diff --git a/tests/components/energy/test_validate.py b/tests/components/energy/test_validate.py index afefe7810c9..7a328e77d76 100644 --- a/tests/components/energy/test_validate.py +++ b/tests/components/energy/test_validate.py @@ -690,7 +690,7 @@ async def test_validation_grid_auto_cost_entity_errors( @pytest.mark.parametrize( ("state", "unit", "expected"), - ( + [ ( "123,123.12", "$/kWh", @@ -711,7 +711,7 @@ async def test_validation_grid_auto_cost_entity_errors( }, }, ), - ), + ], ) async def test_validation_grid_price_errors( hass: HomeAssistant, mock_energy_manager, mock_get_metadata, state, unit, expected diff --git a/tests/components/esphome/test_binary_sensor.py b/tests/components/esphome/test_binary_sensor.py index 81bebe45c08..3da8a54ff34 100644 --- a/tests/components/esphome/test_binary_sensor.py +++ b/tests/components/esphome/test_binary_sensor.py @@ -45,7 +45,7 @@ async def test_assist_in_progress( @pytest.mark.parametrize( - "binary_state", ((True, STATE_ON), (False, STATE_OFF), (None, STATE_UNKNOWN)) + "binary_state", [(True, STATE_ON), (False, STATE_OFF), (None, STATE_UNKNOWN)] ) async def test_binary_sensor_generic_entity( hass: HomeAssistant, diff --git a/tests/components/fan/test_device_action.py b/tests/components/fan/test_device_action.py index 49d6f441b1e..c08e0617700 100644 --- a/tests/components/fan/test_device_action.py +++ b/tests/components/fan/test_device_action.py @@ -58,12 +58,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/fan/test_device_condition.py b/tests/components/fan/test_device_condition.py index 18744155f91..afd237d1974 100644 --- a/tests/components/fan/test_device_condition.py +++ b/tests/components/fan/test_device_condition.py @@ -64,12 +64,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/fan/test_device_trigger.py b/tests/components/fan/test_device_trigger.py index cf0325f133d..92b6443f241 100644 --- a/tests/components/fan/test_device_trigger.py +++ b/tests/components/fan/test_device_trigger.py @@ -69,12 +69,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 9a29d593b3c..66914bf32a6 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -92,7 +92,7 @@ async def test_chain( assert state.state == "18.05" -@pytest.mark.parametrize("missing", (True, False)) +@pytest.mark.parametrize("missing", [True, False]) async def test_chain_history( recorder_mock: Recorder, hass: HomeAssistant, diff --git a/tests/components/forecast_solar/test_sensor.py b/tests/components/forecast_solar/test_sensor.py index a09bb94aa1b..f78ca894acb 100644 --- a/tests/components/forecast_solar/test_sensor.py +++ b/tests/components/forecast_solar/test_sensor.py @@ -167,11 +167,11 @@ async def test_sensors( @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.power_production_next_12hours", "sensor.power_production_next_24hours", "sensor.power_production_next_hour", - ), + ], ) async def test_disabled_by_default( hass: HomeAssistant, diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 2455e1938fd..3f7fd91fed2 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -404,7 +404,7 @@ async def test_config_local_sdk_allow_min_version( ) not in caplog.text -@pytest.mark.parametrize("version", (None, "2.1.4")) +@pytest.mark.parametrize("version", [None, "2.1.4"]) async def test_config_local_sdk_warn_version( hass: HomeAssistant, hass_client: ClientSessionGenerator, diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index a285febe5b3..0ed4d960edc 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -3306,11 +3306,11 @@ async def test_openclose_cover_valve_no_position( @pytest.mark.parametrize( "device_class", - ( + [ cover.CoverDeviceClass.DOOR, cover.CoverDeviceClass.GARAGE, cover.CoverDeviceClass.GATE, - ), + ], ) async def test_openclose_cover_secure(hass: HomeAssistant, device_class) -> None: """Test OpenClose trait support for cover domain.""" @@ -3372,13 +3372,13 @@ async def test_openclose_cover_secure(hass: HomeAssistant, device_class) -> None @pytest.mark.parametrize( "device_class", - ( + [ binary_sensor.BinarySensorDeviceClass.DOOR, binary_sensor.BinarySensorDeviceClass.GARAGE_DOOR, binary_sensor.BinarySensorDeviceClass.LOCK, binary_sensor.BinarySensorDeviceClass.OPENING, binary_sensor.BinarySensorDeviceClass.WINDOW, - ), + ], ) async def test_openclose_binary_sensor(hass: HomeAssistant, device_class) -> None: """Test OpenClose trait support for binary_sensor domain.""" @@ -3816,7 +3816,7 @@ async def test_transport_control( @pytest.mark.parametrize( "state", - ( + [ STATE_OFF, STATE_IDLE, STATE_PLAYING, @@ -3825,7 +3825,7 @@ async def test_transport_control( STATE_STANDBY, STATE_UNAVAILABLE, STATE_UNKNOWN, - ), + ], ) async def test_media_state(hass: HomeAssistant, state) -> None: """Test the MediaStateTrait.""" diff --git a/tests/components/google_mail/test_services.py b/tests/components/google_mail/test_services.py index ca82564dae9..c8679de75e4 100644 --- a/tests/components/google_mail/test_services.py +++ b/tests/components/google_mail/test_services.py @@ -62,10 +62,10 @@ async def test_set_vacation( @pytest.mark.parametrize( ("side_effect"), - ( + [ (RefreshError,), (ClientResponseError("", (), status=400),), - ), + ], ) async def test_reauth_trigger( hass: HomeAssistant, diff --git a/tests/components/gree/test_climate.py b/tests/components/gree/test_climate.py index 2b62be2b16d..0bd767e4f35 100644 --- a/tests/components/gree/test_climate.py +++ b/tests/components/gree/test_climate.py @@ -514,7 +514,7 @@ async def test_update_target_temperature( @pytest.mark.parametrize( - "preset", (PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE) + "preset", [PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE] ) async def test_send_preset_mode( hass: HomeAssistant, discovery, device, mock_now, preset @@ -554,7 +554,7 @@ async def test_send_invalid_preset_mode( @pytest.mark.parametrize( - "preset", (PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE) + "preset", [PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE] ) async def test_send_preset_mode_device_timeout( hass: HomeAssistant, discovery, device, mock_now, preset @@ -577,7 +577,7 @@ async def test_send_preset_mode_device_timeout( @pytest.mark.parametrize( - "preset", (PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE) + "preset", [PRESET_AWAY, PRESET_ECO, PRESET_SLEEP, PRESET_BOOST, PRESET_NONE] ) async def test_update_preset_mode( hass: HomeAssistant, discovery, device, mock_now, preset @@ -597,14 +597,14 @@ async def test_update_preset_mode( @pytest.mark.parametrize( "hvac_mode", - ( + [ HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.FAN_ONLY, HVACMode.HEAT, - ), + ], ) async def test_send_hvac_mode( hass: HomeAssistant, discovery, device, mock_now, hvac_mode @@ -626,7 +626,7 @@ async def test_send_hvac_mode( @pytest.mark.parametrize( "hvac_mode", - (HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.FAN_ONLY, HVACMode.HEAT), + [HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.FAN_ONLY, HVACMode.HEAT], ) async def test_send_hvac_mode_device_timeout( hass: HomeAssistant, discovery, device, mock_now, hvac_mode @@ -650,14 +650,14 @@ async def test_send_hvac_mode_device_timeout( @pytest.mark.parametrize( "hvac_mode", - ( + [ HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.DRY, HVACMode.FAN_ONLY, HVACMode.HEAT, - ), + ], ) async def test_update_hvac_mode( hass: HomeAssistant, discovery, device, mock_now, hvac_mode @@ -675,7 +675,7 @@ async def test_update_hvac_mode( @pytest.mark.parametrize( "fan_mode", - (FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH), + [FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH], ) async def test_send_fan_mode( hass: HomeAssistant, discovery, device, mock_now, fan_mode @@ -716,7 +716,7 @@ async def test_send_invalid_fan_mode( @pytest.mark.parametrize( "fan_mode", - (FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH), + [FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH], ) async def test_send_fan_mode_device_timeout( hass: HomeAssistant, discovery, device, mock_now, fan_mode @@ -740,7 +740,7 @@ async def test_send_fan_mode_device_timeout( @pytest.mark.parametrize( "fan_mode", - (FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH), + [FAN_AUTO, FAN_LOW, FAN_MEDIUM_LOW, FAN_MEDIUM, FAN_MEDIUM_HIGH, FAN_HIGH], ) async def test_update_fan_mode( hass: HomeAssistant, discovery, device, mock_now, fan_mode @@ -756,7 +756,7 @@ async def test_update_fan_mode( @pytest.mark.parametrize( - "swing_mode", (SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL) + "swing_mode", [SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL] ) async def test_send_swing_mode( hass: HomeAssistant, discovery, device, mock_now, swing_mode @@ -796,7 +796,7 @@ async def test_send_invalid_swing_mode( @pytest.mark.parametrize( - "swing_mode", (SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL) + "swing_mode", [SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL] ) async def test_send_swing_mode_device_timeout( hass: HomeAssistant, discovery, device, mock_now, swing_mode @@ -819,7 +819,7 @@ async def test_send_swing_mode_device_timeout( @pytest.mark.parametrize( - "swing_mode", (SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL) + "swing_mode", [SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL] ) async def test_update_swing_mode( hass: HomeAssistant, discovery, device, mock_now, swing_mode diff --git a/tests/components/group/test_config_flow.py b/tests/components/group/test_config_flow.py index 706d1304916..623bcb5c1ee 100644 --- a/tests/components/group/test_config_flow.py +++ b/tests/components/group/test_config_flow.py @@ -26,7 +26,7 @@ from tests.typing import WebSocketGenerator "extra_options", "extra_attrs", ), - ( + [ ("binary_sensor", "on", "on", {}, {}, {"all": False}, {}), ("binary_sensor", "on", "on", {}, {"all": True}, {"all": True}, {}), ("cover", "open", "open", {}, {}, {}, {}), @@ -56,7 +56,7 @@ from tests.typing import WebSocketGenerator {}, ), ("switch", "on", "on", {}, {}, {}, {}), - ), + ], ) async def test_config_flow( hass: HomeAssistant, @@ -129,11 +129,11 @@ async def test_config_flow( @pytest.mark.parametrize( - ("hide_members", "hidden_by"), ((False, None), (True, "integration")) + ("hide_members", "hidden_by"), [(False, None), (True, "integration")] ) @pytest.mark.parametrize( ("group_type", "extra_input"), - ( + [ ("binary_sensor", {"all": False}), ("cover", {}), ("event", {}), @@ -142,7 +142,7 @@ async def test_config_flow( ("lock", {}), ("media_player", {}), ("switch", {}), - ), + ], ) async def test_config_flow_hides_members( hass: HomeAssistant, @@ -210,7 +210,7 @@ def get_suggested(schema, key): @pytest.mark.parametrize( ("group_type", "member_state", "extra_options", "options_options"), - ( + [ ("binary_sensor", "on", {"all": False}, {}), ("cover", "open", {}, {}), ("event", "2021-01-01T23:59:59.123+00:00", {}, {}), @@ -225,7 +225,7 @@ def get_suggested(schema, key): {"ignore_non_numeric": False, "type": "sum"}, ), ("switch", "on", {"all": False}, {}), - ), + ], ) async def test_options( hass: HomeAssistant, group_type, member_state, extra_options, options_options @@ -316,7 +316,7 @@ async def test_options( @pytest.mark.parametrize( ("group_type", "extra_options", "extra_options_after", "advanced"), - ( + [ ("light", {"all": False}, {"all": False}, False), ("light", {"all": True}, {"all": True}, False), ("light", {"all": False}, {"all": False}, True), @@ -325,7 +325,7 @@ async def test_options( ("switch", {"all": True}, {"all": True}, False), ("switch", {"all": False}, {"all": False}, True), ("switch", {"all": True}, {"all": False}, True), - ), + ], ) async def test_all_options( hass: HomeAssistant, group_type, extra_options, extra_options_after, advanced @@ -387,14 +387,14 @@ async def test_all_options( @pytest.mark.parametrize( ("hide_members", "hidden_by_initial", "hidden_by"), - ( + [ (False, er.RegistryEntryHider.INTEGRATION, None), (True, None, er.RegistryEntryHider.INTEGRATION), - ), + ], ) @pytest.mark.parametrize( ("group_type", "extra_input"), - ( + [ ("binary_sensor", {"all": False}), ("cover", {}), ("event", {}), @@ -403,7 +403,7 @@ async def test_all_options( ("lock", {}), ("media_player", {}), ("switch", {}), - ), + ], ) async def test_options_flow_hides_members( hass: HomeAssistant, diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 66b744cddc9..45846123a80 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -1626,7 +1626,7 @@ async def test_plant_group(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("group_type", "member_state", "extra_options"), - ( + [ ("binary_sensor", "on", {"all": False}), ("cover", "open", {}), ("fan", "on", {}), @@ -1642,7 +1642,7 @@ async def test_plant_group(hass: HomeAssistant) -> None: "state_class": "measurement", }, ), - ), + ], ) async def test_setup_and_remove_config_entry( hass: HomeAssistant, @@ -1689,24 +1689,24 @@ async def test_setup_and_remove_config_entry( @pytest.mark.parametrize( ("hide_members", "hidden_by_initial", "hidden_by"), - ( + [ (False, er.RegistryEntryHider.INTEGRATION, er.RegistryEntryHider.INTEGRATION), (False, None, None), (False, er.RegistryEntryHider.USER, er.RegistryEntryHider.USER), (True, er.RegistryEntryHider.INTEGRATION, None), (True, None, None), (True, er.RegistryEntryHider.USER, er.RegistryEntryHider.USER), - ), + ], ) @pytest.mark.parametrize( ("group_type", "extra_options"), - ( + [ ("binary_sensor", {"all": False}), ("cover", {}), ("fan", {}), ("light", {"all": False}), ("media_player", {}), - ), + ], ) async def test_unhide_members_on_remove( hass: HomeAssistant, diff --git a/tests/components/hassio/test_handler.py b/tests/components/hassio/test_handler.py index 42e0a174952..337a0dd864f 100644 --- a/tests/components/hassio/test_handler.py +++ b/tests/components/hassio/test_handler.py @@ -315,9 +315,9 @@ async def test_api_ingress_panels( @pytest.mark.parametrize( ("api_call", "method", "payload"), [ - ["retrieve_discovery_messages", "GET", None], - ["refresh_updates", "POST", None], - ["update_diagnostics", "POST", True], + ("retrieve_discovery_messages", "GET", None), + ("refresh_updates", "POST", None), + ("update_diagnostics", "POST", True), ], ) async def test_api_headers( diff --git a/tests/components/here_travel_time/test_config_flow.py b/tests/components/here_travel_time/test_config_flow.py index 4d3797c66a0..51b12978856 100644 --- a/tests/components/here_travel_time/test_config_flow.py +++ b/tests/components/here_travel_time/test_config_flow.py @@ -116,7 +116,7 @@ async def origin_step_result_fixture( @pytest.mark.parametrize( "menu_options", - (["origin_coordinates", "origin_entity"],), + [["origin_coordinates", "origin_entity"]], ) @pytest.mark.usefixtures("valid_response") async def test_step_user(hass: HomeAssistant, menu_options) -> None: diff --git a/tests/components/history/test_websocket_api.py b/tests/components/history/test_websocket_api.py index 154e29a0989..70e2eb9470a 100644 --- a/tests/components/history/test_websocket_api.py +++ b/tests/components/history/test_websocket_api.py @@ -1332,7 +1332,7 @@ async def test_history_stream_live_with_future_end_time( ) == listeners_without_writes(init_listeners) -@pytest.mark.parametrize("include_start_time_state", (True, False)) +@pytest.mark.parametrize("include_start_time_state", [True, False]) async def test_history_stream_before_history_starts( recorder_mock: Recorder, hass: HomeAssistant, diff --git a/tests/components/homeassistant/triggers/test_numeric_state.py b/tests/components/homeassistant/triggers/test_numeric_state.py index 7c33e0d81a7..65c2863d0d7 100644 --- a/tests/components/homeassistant/triggers/test_numeric_state.py +++ b/tests/components/homeassistant/triggers/test_numeric_state.py @@ -60,7 +60,7 @@ async def setup_comp(hass): @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_not_fires_on_entity_removal( hass: HomeAssistant, calls, below @@ -90,7 +90,7 @@ async def test_if_not_fires_on_entity_removal( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_fires_on_entity_change_below( hass: HomeAssistant, calls, below @@ -139,7 +139,7 @@ async def test_if_fires_on_entity_change_below( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_fires_on_entity_change_below_uuid( hass: HomeAssistant, entity_registry: er.EntityRegistry, calls, below @@ -193,7 +193,7 @@ async def test_if_fires_on_entity_change_below_uuid( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_fires_on_entity_change_over_to_below( hass: HomeAssistant, calls, below @@ -224,7 +224,7 @@ async def test_if_fires_on_entity_change_over_to_below( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_fires_on_entities_change_over_to_below( hass: HomeAssistant, calls, below @@ -259,7 +259,7 @@ async def test_if_fires_on_entities_change_over_to_below( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_not_fires_on_entity_change_below_to_below( hass: HomeAssistant, calls, below @@ -302,7 +302,7 @@ async def test_if_not_fires_on_entity_change_below_to_below( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_not_below_fires_on_entity_change_to_equal( hass: HomeAssistant, calls, below @@ -333,7 +333,7 @@ async def test_if_not_below_fires_on_entity_change_to_equal( @pytest.mark.parametrize( - "below", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "below", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_not_fires_on_initial_entity_below( hass: HomeAssistant, calls, below @@ -364,7 +364,7 @@ async def test_if_not_fires_on_initial_entity_below( @pytest.mark.parametrize( - "above", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "above", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_not_fires_on_initial_entity_above( hass: HomeAssistant, calls, above @@ -395,7 +395,7 @@ async def test_if_not_fires_on_initial_entity_above( @pytest.mark.parametrize( - "above", (10, "input_number.value_10", "number.value_10", "sensor.value_10") + "above", [10, "input_number.value_10", "number.value_10", "sensor.value_10"] ) async def test_if_fires_on_entity_change_above( hass: HomeAssistant, calls, above @@ -448,7 +448,7 @@ async def test_if_fires_on_entity_unavailable_at_startup( assert len(calls) == 0 -@pytest.mark.parametrize("above", (10, "input_number.value_10")) +@pytest.mark.parametrize("above", [10, "input_number.value_10"]) async def test_if_fires_on_entity_change_below_to_above( hass: HomeAssistant, calls, above ) -> None: @@ -478,7 +478,7 @@ async def test_if_fires_on_entity_change_below_to_above( assert len(calls) == 1 -@pytest.mark.parametrize("above", (10, "input_number.value_10")) +@pytest.mark.parametrize("above", [10, "input_number.value_10"]) async def test_if_not_fires_on_entity_change_above_to_above( hass: HomeAssistant, calls, above ) -> None: @@ -513,7 +513,7 @@ async def test_if_not_fires_on_entity_change_above_to_above( assert len(calls) == 1 -@pytest.mark.parametrize("above", (10, "input_number.value_10")) +@pytest.mark.parametrize("above", [10, "input_number.value_10"]) async def test_if_not_above_fires_on_entity_change_to_equal( hass: HomeAssistant, calls, above ) -> None: @@ -545,12 +545,12 @@ async def test_if_not_above_fires_on_entity_change_to_equal( @pytest.mark.parametrize( ("above", "below"), - ( + [ (5, 10), (5, "input_number.value_10"), ("input_number.value_5", 10), ("input_number.value_5", "input_number.value_10"), - ), + ], ) async def test_if_fires_on_entity_change_below_range( hass: HomeAssistant, calls, above, below @@ -582,12 +582,12 @@ async def test_if_fires_on_entity_change_below_range( @pytest.mark.parametrize( ("above", "below"), - ( + [ (5, 10), (5, "input_number.value_10"), ("input_number.value_5", 10), ("input_number.value_5", "input_number.value_10"), - ), + ], ) async def test_if_fires_on_entity_change_below_above_range( hass: HomeAssistant, calls, above, below @@ -616,12 +616,12 @@ async def test_if_fires_on_entity_change_below_above_range( @pytest.mark.parametrize( ("above", "below"), - ( + [ (5, 10), (5, "input_number.value_10"), ("input_number.value_5", 10), ("input_number.value_5", "input_number.value_10"), - ), + ], ) async def test_if_fires_on_entity_change_over_to_below_range( hass: HomeAssistant, calls, above, below @@ -654,12 +654,12 @@ async def test_if_fires_on_entity_change_over_to_below_range( @pytest.mark.parametrize( ("above", "below"), - ( + [ (5, 10), (5, "input_number.value_10"), ("input_number.value_5", 10), ("input_number.value_5", "input_number.value_10"), - ), + ], ) async def test_if_fires_on_entity_change_over_to_below_above_range( hass: HomeAssistant, calls, above, below @@ -690,7 +690,7 @@ async def test_if_fires_on_entity_change_over_to_below_above_range( assert len(calls) == 0 -@pytest.mark.parametrize("below", (100, "input_number.value_100")) +@pytest.mark.parametrize("below", [100, "input_number.value_100"]) async def test_if_not_fires_if_entity_not_match( hass: HomeAssistant, calls, below ) -> None: @@ -745,7 +745,7 @@ async def test_if_not_fires_and_warns_if_below_entity_unknown( assert caplog.record_tuples[0][1] == logging.WARNING -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_if_fires_on_entity_change_below_with_attribute( hass: HomeAssistant, calls, below ) -> None: @@ -773,7 +773,7 @@ async def test_if_fires_on_entity_change_below_with_attribute( assert len(calls) == 1 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_if_not_fires_on_entity_change_not_below_with_attribute( hass: HomeAssistant, calls, below ) -> None: @@ -798,7 +798,7 @@ async def test_if_not_fires_on_entity_change_not_below_with_attribute( assert len(calls) == 0 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_if_fires_on_attribute_change_with_attribute_below( hass: HomeAssistant, calls, below ) -> None: @@ -827,7 +827,7 @@ async def test_if_fires_on_attribute_change_with_attribute_below( assert len(calls) == 1 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_if_not_fires_on_attribute_change_with_attribute_not_below( hass: HomeAssistant, calls, below ) -> None: @@ -853,7 +853,7 @@ async def test_if_not_fires_on_attribute_change_with_attribute_not_below( assert len(calls) == 0 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_if_not_fires_on_entity_change_with_attribute_below( hass: HomeAssistant, calls, below ) -> None: @@ -879,7 +879,7 @@ async def test_if_not_fires_on_entity_change_with_attribute_below( assert len(calls) == 0 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_if_not_fires_on_entity_change_with_not_attribute_below( hass: HomeAssistant, calls, below ) -> None: @@ -905,7 +905,7 @@ async def test_if_not_fires_on_entity_change_with_not_attribute_below( assert len(calls) == 0 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr( hass: HomeAssistant, calls, below ) -> None: @@ -937,7 +937,7 @@ async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr( assert len(calls) == 1 -@pytest.mark.parametrize("below", (10, "input_number.value_10")) +@pytest.mark.parametrize("below", [10, "input_number.value_10"]) async def test_template_list(hass: HomeAssistant, calls, below) -> None: """Test template list.""" hass.states.async_set("test.entity", "entity", {"test_attribute": [11, 15, 11]}) @@ -963,7 +963,7 @@ async def test_template_list(hass: HomeAssistant, calls, below) -> None: assert len(calls) == 1 -@pytest.mark.parametrize("below", (10.0, "input_number.value_10")) +@pytest.mark.parametrize("below", [10.0, "input_number.value_10"]) async def test_template_string(hass: HomeAssistant, calls, below) -> None: """Test template string.""" assert await async_setup_component( @@ -1036,12 +1036,12 @@ async def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_action(hass: HomeAssistant, calls, above, below) -> None: """Test if action.""" @@ -1084,12 +1084,12 @@ async def test_if_action(hass: HomeAssistant, calls, above, below) -> None: @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fails_setup_bad_for(hass: HomeAssistant, calls, above, below) -> None: """Test for setup failure for bad for.""" @@ -1140,12 +1140,12 @@ async def test_if_fails_setup_for_without_above_below( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_not_fires_on_entity_change_with_for( hass: HomeAssistant, freezer: FrozenDateTimeFactory, calls, above, below @@ -1180,12 +1180,12 @@ async def test_if_not_fires_on_entity_change_with_for( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_not_fires_on_entities_change_with_for_after_stop( hass: HomeAssistant, calls, above, below @@ -1241,12 +1241,12 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_entity_change_with_for_attribute_change( hass: HomeAssistant, freezer: FrozenDateTimeFactory, calls, above, below @@ -1287,12 +1287,12 @@ async def test_if_fires_on_entity_change_with_for_attribute_change( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_entity_change_with_for( hass: HomeAssistant, calls, above, below @@ -1325,7 +1325,7 @@ async def test_if_fires_on_entity_change_with_for( assert len(calls) == 1 -@pytest.mark.parametrize("above", (10, "input_number.value_10")) +@pytest.mark.parametrize("above", [10, "input_number.value_10"]) async def test_wait_template_with_trigger(hass: HomeAssistant, calls, above) -> None: """Test using wait template with 'trigger.entity_id'.""" hass.states.async_set("test.entity", "0") @@ -1368,12 +1368,12 @@ async def test_wait_template_with_trigger(hass: HomeAssistant, calls, above) -> @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_entities_change_no_overlap( hass: HomeAssistant, freezer: FrozenDateTimeFactory, calls, above, below @@ -1423,12 +1423,12 @@ async def test_if_fires_on_entities_change_no_overlap( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_entities_change_overlap( hass: HomeAssistant, freezer: FrozenDateTimeFactory, calls, above, below @@ -1489,12 +1489,12 @@ async def test_if_fires_on_entities_change_overlap( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_change_with_for_template_1( hass: HomeAssistant, calls, above, below @@ -1530,12 +1530,12 @@ async def test_if_fires_on_change_with_for_template_1( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_change_with_for_template_2( hass: HomeAssistant, calls, above, below @@ -1571,12 +1571,12 @@ async def test_if_fires_on_change_with_for_template_2( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_change_with_for_template_3( hass: HomeAssistant, calls, above, below @@ -1650,12 +1650,12 @@ async def test_if_not_fires_on_error_with_for_template( @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_invalid_for_template(hass: HomeAssistant, calls, above, below) -> None: """Test for invalid for template.""" @@ -1687,12 +1687,12 @@ async def test_invalid_for_template(hass: HomeAssistant, calls, above, below) -> @pytest.mark.parametrize( ("above", "below"), - ( + [ (8, 12), (8, "input_number.value_12"), ("input_number.value_8", 12), ("input_number.value_8", "input_number.value_12"), - ), + ], ) async def test_if_fires_on_entities_change_overlap_for_template( hass: HomeAssistant, freezer: FrozenDateTimeFactory, calls, above, below @@ -1788,7 +1788,7 @@ async def test_schema_unacceptable_entities(hass: HomeAssistant) -> None: ) -@pytest.mark.parametrize("above", (3, "input_number.value_3")) +@pytest.mark.parametrize("above", [3, "input_number.value_3"]) async def test_attribute_if_fires_on_entity_change_with_both_filters( hass: HomeAssistant, calls, above ) -> None: @@ -1817,7 +1817,7 @@ async def test_attribute_if_fires_on_entity_change_with_both_filters( assert len(calls) == 1 -@pytest.mark.parametrize("above", (3, "input_number.value_3")) +@pytest.mark.parametrize("above", [3, "input_number.value_3"]) async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( hass: HomeAssistant, calls, above ) -> None: @@ -1855,7 +1855,7 @@ async def test_attribute_if_not_fires_on_entities_change_with_for_after_stop( @pytest.mark.parametrize( ("above", "below"), - ((8, 12),), + [(8, 12)], ) async def test_variables_priority( hass: HomeAssistant, freezer: FrozenDateTimeFactory, calls, above, below @@ -1912,7 +1912,7 @@ async def test_variables_priority( assert calls[0].data["some"] == "test.entity_1 - 0:00:05" -@pytest.mark.parametrize("multiplier", (1, 5)) +@pytest.mark.parametrize("multiplier", [1, 5]) async def test_template_variable(hass: HomeAssistant, calls, multiplier) -> None: """Test template variable.""" hass.states.async_set("test.entity", "entity", {"test_attribute": [11, 15, 11]}) diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index beaef50caf7..7e41169d3d1 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -42,7 +42,7 @@ async def setup_repairs(hass): @pytest.mark.parametrize( ("ha_version", "supervisor_info", "expected_alerts"), - ( + [ ( "2022.7.0", {"version": "2022.11.0"}, @@ -93,7 +93,7 @@ async def setup_repairs(hass): ("sochain", "sochain"), ], ), - ), + ], ) async def test_alerts( hass: HomeAssistant, @@ -182,7 +182,7 @@ async def test_alerts( "initial_alerts", "late_alerts", ), - ( + [ ( "2022.7.0", {"version": "2022.11.0"}, @@ -282,7 +282,7 @@ async def test_alerts( ("sochain", "sochain"), ], ), - ), + ], ) async def test_alerts_refreshed_on_component_load( hass: HomeAssistant, @@ -399,7 +399,7 @@ async def test_alerts_refreshed_on_component_load( @pytest.mark.parametrize( ("ha_version", "fixture", "expected_alerts"), - ( + [ ( "2022.7.0", "alerts_no_integrations.json", @@ -415,7 +415,7 @@ async def test_alerts_refreshed_on_component_load( ("hikvision", "hikvision"), ], ), - ), + ], ) async def test_bad_alerts( hass: HomeAssistant, @@ -503,7 +503,7 @@ async def test_no_alerts( @pytest.mark.parametrize( ("ha_version", "fixture_1", "expected_alerts_1", "fixture_2", "expected_alerts_2"), - ( + [ ( "2022.7.0", "alerts_1.json", @@ -564,7 +564,7 @@ async def test_no_alerts( ("sochain", "sochain"), ], ), - ), + ], ) async def test_alerts_change( hass: HomeAssistant, diff --git a/tests/components/homeassistant_sky_connect/test_init.py b/tests/components/homeassistant_sky_connect/test_init.py index a8602b2f65c..fd70d1fb696 100644 --- a/tests/components/homeassistant_sky_connect/test_init.py +++ b/tests/components/homeassistant_sky_connect/test_init.py @@ -53,7 +53,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: @pytest.mark.parametrize( - ("onboarded", "num_entries", "num_flows"), ((False, 1, 0), (True, 0, 1)) + ("onboarded", "num_entries", "num_flows"), [(False, 1, 0), (True, 0, 1)] ) async def test_setup_entry( mock_zha_config_flow_setup, diff --git a/tests/components/homeassistant_yellow/test_init.py b/tests/components/homeassistant_yellow/test_init.py index de6696a108e..c3a9819c14b 100644 --- a/tests/components/homeassistant_yellow/test_init.py +++ b/tests/components/homeassistant_yellow/test_init.py @@ -16,7 +16,7 @@ from tests.common import MockConfigEntry, MockModule, mock_integration @pytest.mark.parametrize( - ("onboarded", "num_entries", "num_flows"), ((False, 1, 0), (True, 0, 1)) + ("onboarded", "num_entries", "num_flows"), [(False, 1, 0), (True, 0, 1)] ) async def test_setup_entry( hass: HomeAssistant, onboarded, num_entries, num_flows, addon_store_info diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index edbec9e1a2b..8d2978fb0bd 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -615,7 +615,7 @@ async def test_light_restore( @pytest.mark.parametrize( ("supported_color_modes", "state_props", "turn_on_props_with_brightness"), [ - [ + ( [ColorMode.COLOR_TEMP, ColorMode.RGBW], { ATTR_RGBW_COLOR: (128, 50, 0, 255), @@ -625,8 +625,8 @@ async def test_light_restore( ATTR_COLOR_MODE: ColorMode.RGBW, }, {ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25}, - ], - [ + ), + ( [ColorMode.COLOR_TEMP, ColorMode.RGBWW], { ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), @@ -636,7 +636,7 @@ async def test_light_restore( ATTR_COLOR_MODE: ColorMode.RGBWW, }, {ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25}, - ], + ), ], ) async def test_light_rgb_with_color_temp( @@ -735,7 +735,7 @@ async def test_light_rgb_with_color_temp( @pytest.mark.parametrize( ("supported_color_modes", "state_props", "turn_on_props_with_brightness"), [ - [ + ( [ColorMode.RGBW], { ATTR_RGBW_COLOR: (128, 50, 0, 255), @@ -745,8 +745,8 @@ async def test_light_rgb_with_color_temp( ATTR_COLOR_MODE: ColorMode.RGBW, }, {ATTR_RGBW_COLOR: (0, 0, 0, 191)}, - ], - [ + ), + ( [ColorMode.RGBWW], { ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), @@ -756,7 +756,7 @@ async def test_light_rgb_with_color_temp( ATTR_COLOR_MODE: ColorMode.RGBWW, }, {ATTR_RGBWW_COLOR: (0, 0, 0, 165, 26)}, - ], + ), ], ) async def test_light_rgbwx_with_color_temp_and_brightness( @@ -932,7 +932,7 @@ async def test_light_rgb_or_w_lights( @pytest.mark.parametrize( ("supported_color_modes", "state_props"), [ - [ + ( [ColorMode.COLOR_TEMP, ColorMode.RGBW], { ATTR_RGBW_COLOR: (128, 50, 0, 255), @@ -941,8 +941,8 @@ async def test_light_rgb_or_w_lights( ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: ColorMode.RGBW, }, - ], - [ + ), + ( [ColorMode.COLOR_TEMP, ColorMode.RGBWW], { ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), @@ -951,7 +951,7 @@ async def test_light_rgb_or_w_lights( ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: ColorMode.RGBWW, }, - ], + ), ], ) async def test_light_rgb_with_white_switch_to_temp( diff --git a/tests/components/http/test_static.py b/tests/components/http/test_static.py index d129f6641a5..e3cf2f50c15 100644 --- a/tests/components/http/test_static.py +++ b/tests/components/http/test_static.py @@ -30,11 +30,11 @@ async def mock_http_client(hass: HomeAssistant, aiohttp_client: ClientSessionGen @pytest.mark.parametrize( ("url", "canonical_url"), - ( + [ ("//a", "//a"), ("///a", "///a"), ("/c:\\a\\b", "/c:%5Ca%5Cb"), - ), + ], ) async def test_static_path_blocks_anchors( hass: HomeAssistant, diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index 997de3c879e..cc4a32a7df5 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -102,7 +102,7 @@ async def test_already_configured( @pytest.mark.parametrize( ("exception", "errors", "data_patch"), - ( + [ (ConnectionError(), {CONF_URL: "unknown"}, {}), (requests.exceptions.SSLError(), {CONF_URL: "ssl_error_try_plain"}, {}), ( @@ -110,7 +110,7 @@ async def test_already_configured( {CONF_URL: "ssl_error_try_unverified"}, {CONF_VERIFY_SSL: True}, ), - ), + ], ) async def test_connection_errors( hass: HomeAssistant, @@ -158,7 +158,7 @@ def login_requests_mock(requests_mock): @pytest.mark.parametrize( ("request_outcome", "fixture_override", "errors"), - ( + [ ( { "text": f"{LoginErrorEnum.USERNAME_WRONG}", @@ -202,7 +202,7 @@ def login_requests_mock(requests_mock): {}, {CONF_URL: "connection_timeout"}, ), - ), + ], ) async def test_login_error( hass: HomeAssistant, login_requests_mock, request_outcome, fixture_override, errors @@ -224,7 +224,7 @@ async def test_login_error( assert result["errors"] == errors -@pytest.mark.parametrize("scheme", ("http", "https")) +@pytest.mark.parametrize("scheme", ["http", "https"]) async def test_success(hass: HomeAssistant, login_requests_mock, scheme: str) -> None: """Test successful flow provides entry creation data.""" user_input = { @@ -257,7 +257,7 @@ async def test_success(hass: HomeAssistant, login_requests_mock, scheme: str) -> @pytest.mark.parametrize( ("requests_mock_request_kwargs", "upnp_data", "expected_result"), - ( + [ ( { "method": ANY, @@ -304,7 +304,7 @@ async def test_success(hass: HomeAssistant, login_requests_mock, scheme: str) -> "reason": "unsupported_device", }, ), - ), + ], ) async def test_ssdp( hass: HomeAssistant, @@ -346,7 +346,7 @@ async def test_ssdp( @pytest.mark.parametrize( ("login_response_text", "expected_result", "expected_entry_data"), - ( + [ ( "OK", { @@ -364,7 +364,7 @@ async def test_ssdp( }, {**FIXTURE_USER_INPUT, CONF_PASSWORD: "invalid-password"}, ), - ), + ], ) async def test_reauth( hass: HomeAssistant, diff --git a/tests/components/huawei_lte/test_device_tracker.py b/tests/components/huawei_lte/test_device_tracker.py index 094fd6e88b7..56eb594fca0 100644 --- a/tests/components/huawei_lte/test_device_tracker.py +++ b/tests/components/huawei_lte/test_device_tracker.py @@ -7,13 +7,13 @@ from homeassistant.components.huawei_lte import device_tracker @pytest.mark.parametrize( ("value", "expected"), - ( + [ ("HTTP", "http"), ("ID", "id"), ("IPAddress", "ip_address"), ("HTTPResponse", "http_response"), ("foo_bar", "foo_bar"), - ), + ], ) def test_better_snakecase(value, expected) -> None: """Test that better snakecase works better.""" diff --git a/tests/components/huawei_lte/test_sensor.py b/tests/components/huawei_lte/test_sensor.py index 74f8b7c7b49..4d5acaf2d31 100644 --- a/tests/components/huawei_lte/test_sensor.py +++ b/tests/components/huawei_lte/test_sensor.py @@ -11,11 +11,11 @@ from homeassistant.const import ( @pytest.mark.parametrize( ("value", "expected"), - ( + [ ("-71 dBm", (-71, SIGNAL_STRENGTH_DECIBELS_MILLIWATT)), ("15dB", (15, SIGNAL_STRENGTH_DECIBELS)), (">=-51dBm", (-51, SIGNAL_STRENGTH_DECIBELS_MILLIWATT)), - ), + ], ) def test_format_default(value, expected) -> None: """Test that default formatter copes with expected values.""" diff --git a/tests/components/humidifier/test_device_action.py b/tests/components/humidifier/test_device_action.py index e8ac6006451..13c41fd8369 100644 --- a/tests/components/humidifier/test_device_action.py +++ b/tests/components/humidifier/test_device_action.py @@ -95,12 +95,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/humidifier/test_device_condition.py b/tests/components/humidifier/test_device_condition.py index 7864a119393..fa17d1bb732 100644 --- a/tests/components/humidifier/test_device_condition.py +++ b/tests/components/humidifier/test_device_condition.py @@ -103,12 +103,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/humidifier/test_device_trigger.py b/tests/components/humidifier/test_device_trigger.py index f6fcd450768..3e05f6b02d1 100644 --- a/tests/components/humidifier/test_device_trigger.py +++ b/tests/components/humidifier/test_device_trigger.py @@ -103,12 +103,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/humidifier/test_intent.py b/tests/components/humidifier/test_intent.py index 9ee72baf73b..936369f8aa7 100644 --- a/tests/components/humidifier/test_intent.py +++ b/tests/components/humidifier/test_intent.py @@ -186,7 +186,7 @@ async def test_intent_set_mode_tests_feature(hass: HomeAssistant) -> None: assert len(mode_calls) == 0 -@pytest.mark.parametrize("available_modes", (["home", "away"], None)) +@pytest.mark.parametrize("available_modes", [["home", "away"], None]) async def test_intent_set_unknown_mode( hass: HomeAssistant, available_modes: list[str] | None ) -> None: diff --git a/tests/components/improv_ble/test_config_flow.py b/tests/components/improv_ble/test_config_flow.py index a64a03a139a..81efd10cc3d 100644 --- a/tests/components/improv_ble/test_config_flow.py +++ b/tests/components/improv_ble/test_config_flow.py @@ -363,11 +363,11 @@ async def test_bluetooth_step_already_in_progress(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("exc", "error"), - ( + [ (BleakError, "cannot_connect"), (Exception, "unknown"), (improv_ble_errors.CharacteristicMissingError, "characteristic_missing"), - ), + ], ) async def test_can_identify_fails(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" @@ -398,11 +398,11 @@ async def test_can_identify_fails(hass: HomeAssistant, exc, error) -> None: @pytest.mark.parametrize( ("exc", "error"), - ( + [ (BleakError, "cannot_connect"), (Exception, "unknown"), (improv_ble_errors.CharacteristicMissingError, "characteristic_missing"), - ), + ], ) async def test_identify_fails(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" @@ -441,11 +441,11 @@ async def test_identify_fails(hass: HomeAssistant, exc, error) -> None: @pytest.mark.parametrize( ("exc", "error"), - ( + [ (BleakError, "cannot_connect"), (Exception, "unknown"), (improv_ble_errors.CharacteristicMissingError, "characteristic_missing"), - ), + ], ) async def test_need_authorization_fails(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" @@ -485,11 +485,11 @@ async def test_need_authorization_fails(hass: HomeAssistant, exc, error) -> None @pytest.mark.parametrize( ("exc", "error"), - ( + [ (BleakError, "cannot_connect"), (Exception, "unknown"), (improv_ble_errors.CharacteristicMissingError, "characteristic_missing"), - ), + ], ) async def test_authorize_fails(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" @@ -577,12 +577,12 @@ async def _test_provision_error(hass: HomeAssistant, exc) -> None: @pytest.mark.parametrize( ("exc", "error"), - ( + [ (BleakError, "cannot_connect"), (Exception, "unknown"), (improv_ble_errors.CharacteristicMissingError, "characteristic_missing"), (improv_ble_errors.ProvisioningFailed(Error.UNKNOWN_ERROR), "unknown"), - ), + ], ) async def test_provision_fails(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" @@ -595,7 +595,7 @@ async def test_provision_fails(hass: HomeAssistant, exc, error) -> None: @pytest.mark.parametrize( ("exc", "error"), - ((improv_ble_errors.ProvisioningFailed(Error.NOT_AUTHORIZED), "unknown"),), + [(improv_ble_errors.ProvisioningFailed(Error.NOT_AUTHORIZED), "unknown")], ) async def test_provision_not_authorized(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" @@ -619,12 +619,12 @@ async def test_provision_not_authorized(hass: HomeAssistant, exc, error) -> None @pytest.mark.parametrize( ("exc", "error"), - ( + [ ( improv_ble_errors.ProvisioningFailed(Error.UNABLE_TO_CONNECT), "unable_to_connect", ), - ), + ], ) async def test_provision_retry(hass: HomeAssistant, exc, error) -> None: """Test bluetooth flow with error.""" diff --git a/tests/components/integration/test_config_flow.py b/tests/components/integration/test_config_flow.py index 3ccd192c4f1..4f811e98de2 100644 --- a/tests/components/integration/test_config_flow.py +++ b/tests/components/integration/test_config_flow.py @@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_config_flow(hass: HomeAssistant, platform) -> None: """Test the config flow.""" input_sensor_entity_id = "sensor.input" @@ -74,7 +74,7 @@ def get_suggested(schema, key): raise Exception -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_options(hass: HomeAssistant, platform) -> None: """Test reconfiguring.""" # Setup the config entry diff --git a/tests/components/integration/test_init.py b/tests/components/integration/test_init.py index 9f9decaef45..f92a4a67585 100644 --- a/tests/components/integration/test_init.py +++ b/tests/components/integration/test_init.py @@ -9,7 +9,7 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_setup_and_remove_config_entry( hass: HomeAssistant, entity_registry: er.EntityRegistry, diff --git a/tests/components/justnimbus/test_config_flow.py b/tests/components/justnimbus/test_config_flow.py index 8140168751c..2b2caeed929 100644 --- a/tests/components/justnimbus/test_config_flow.py +++ b/tests/components/justnimbus/test_config_flow.py @@ -28,7 +28,7 @@ async def test_form(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("side_effect", "errors"), - ( + [ ( InvalidClientID(client_id="test_id"), {"base": "invalid_auth"}, @@ -41,7 +41,7 @@ async def test_form(hass: HomeAssistant) -> None: RuntimeError, {"base": "unknown"}, ), - ), + ], ) async def test_form_errors( hass: HomeAssistant, diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index 6fd83d9c29a..3f8ed8adbb6 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -83,12 +83,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 5f47829f949..489e2bf39d0 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -69,12 +69,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index 415a70a0661..ff692432d31 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -69,12 +69,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 2cc23f71f22..dfb446beec3 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -438,7 +438,7 @@ async def test_services( @pytest.mark.parametrize( ("profile_name", "last_call", "expected_data"), - ( + [ ( "test", "turn_on", @@ -501,7 +501,7 @@ async def test_services( light.ATTR_TRANSITION: 5.3, }, ), - ), + ], ) async def test_light_profiles( hass: HomeAssistant, @@ -591,7 +591,7 @@ async def test_default_profiles_group( "expected_params_state_was_off", "expected_params_state_was_on", ), - ( + [ ( # No turn on params, should apply profile {}, @@ -774,7 +774,7 @@ async def test_default_profiles_group( light.ATTR_TRANSITION: 1, }, ), - ), + ], ) async def test_default_profiles_light( hass: HomeAssistant, @@ -1128,7 +1128,7 @@ invalid_no_brightness_no_color_no_transition,,, assert invalid_profile_name not in profiles.data -@pytest.mark.parametrize("light_state", (STATE_ON, STATE_OFF)) +@pytest.mark.parametrize("light_state", [STATE_ON, STATE_OFF]) async def test_light_backwards_compatibility_supported_color_modes( hass: HomeAssistant, light_state, enable_custom_integrations: None ) -> None: diff --git a/tests/components/light/test_reproduce_state.py b/tests/components/light/test_reproduce_state.py index ed02ad891e2..aa698129915 100644 --- a/tests/components/light/test_reproduce_state.py +++ b/tests/components/light/test_reproduce_state.py @@ -127,7 +127,7 @@ async def test_reproducing_states( @pytest.mark.parametrize( "color_mode", - ( + [ light.ColorMode.COLOR_TEMP, light.ColorMode.BRIGHTNESS, light.ColorMode.HS, @@ -138,7 +138,7 @@ async def test_reproducing_states( light.ColorMode.UNKNOWN, light.ColorMode.WHITE, light.ColorMode.XY, - ), + ], ) async def test_filter_color_modes( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, color_mode @@ -195,7 +195,7 @@ async def test_filter_color_modes( @pytest.mark.parametrize( "saved_state", - ( + [ NONE_BRIGHTNESS, NONE_EFFECT, NONE_COLOR_TEMP, @@ -204,7 +204,7 @@ async def test_filter_color_modes( NONE_RGBW_COLOR, NONE_RGBWW_COLOR, NONE_XY_COLOR, - ), + ], ) async def test_filter_none(hass: HomeAssistant, saved_state) -> None: """Test filtering of parameters which are None.""" diff --git a/tests/components/litterrobot/test_init.py b/tests/components/litterrobot/test_init.py index ca68ea0d843..60f359f08f0 100644 --- a/tests/components/litterrobot/test_init.py +++ b/tests/components/litterrobot/test_init.py @@ -47,10 +47,10 @@ async def test_unload_entry(hass: HomeAssistant, mock_account: MagicMock) -> Non @pytest.mark.parametrize( ("side_effect", "expected_state"), - ( + [ (LitterRobotLoginException, ConfigEntryState.SETUP_ERROR), (LitterRobotException, ConfigEntryState.SETUP_RETRY), - ), + ], ) async def test_entry_not_setup( hass: HomeAssistant, diff --git a/tests/components/lock/test_device_action.py b/tests/components/lock/test_device_action.py index 0356bc206cc..3396324284b 100644 --- a/tests/components/lock/test_device_action.py +++ b/tests/components/lock/test_device_action.py @@ -90,12 +90,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/lock/test_device_condition.py b/tests/components/lock/test_device_condition.py index 31aa9aab143..71e1b6ac48e 100644 --- a/tests/components/lock/test_device_condition.py +++ b/tests/components/lock/test_device_condition.py @@ -77,12 +77,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/lock/test_device_trigger.py b/tests/components/lock/test_device_trigger.py index f4e0a7a38a3..a45fd7527b5 100644 --- a/tests/components/lock/test_device_trigger.py +++ b/tests/components/lock/test_device_trigger.py @@ -76,12 +76,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py index d0ac6e98835..47c4981ba2a 100644 --- a/tests/components/lovelace/test_dashboard.py +++ b/tests/components/lovelace/test_dashboard.py @@ -181,7 +181,7 @@ async def test_lovelace_from_yaml( assert len(events) == 1 -@pytest.mark.parametrize("url_path", ("test-panel", "test-panel-no-sidebar")) +@pytest.mark.parametrize("url_path", ["test-panel", "test-panel-no-sidebar"]) async def test_dashboard_from_yaml( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, url_path ) -> None: diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index f2014ce70ca..5531796c20a 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -473,7 +473,7 @@ async def test_zeroconf_not_lutron_device(hass: HomeAssistant) -> None: @pytest.mark.parametrize( - "source", (config_entries.SOURCE_ZEROCONF, config_entries.SOURCE_HOMEKIT) + "source", [config_entries.SOURCE_ZEROCONF, config_entries.SOURCE_HOMEKIT] ) async def test_zeroconf(hass: HomeAssistant, source, tmp_path: Path) -> None: """Test starting a flow from discovery.""" diff --git a/tests/components/media_player/test_device_condition.py b/tests/components/media_player/test_device_condition.py index 8e085152954..3d020b01c3d 100644 --- a/tests/components/media_player/test_device_condition.py +++ b/tests/components/media_player/test_device_condition.py @@ -79,12 +79,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/media_player/test_device_trigger.py b/tests/components/media_player/test_device_trigger.py index 9639a13968f..3f347918f3d 100644 --- a/tests/components/media_player/test_device_trigger.py +++ b/tests/components/media_player/test_device_trigger.py @@ -87,12 +87,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index bc92c591d27..436a9e3d05f 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -240,14 +240,14 @@ async def test_group_members_available_when_off(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("input", "expected"), - ( + [ (True, MediaPlayerEnqueue.ADD), (False, MediaPlayerEnqueue.PLAY), ("play", MediaPlayerEnqueue.PLAY), ("next", MediaPlayerEnqueue.NEXT), ("add", MediaPlayerEnqueue.ADD), ("replace", MediaPlayerEnqueue.REPLACE), - ), + ], ) async def test_enqueue_rewrite(hass: HomeAssistant, input, expected) -> None: """Test that group_members are still available when media_player is off.""" diff --git a/tests/components/min_max/test_config_flow.py b/tests/components/min_max/test_config_flow.py index ba9862f92b4..367c4cd717d 100644 --- a/tests/components/min_max/test_config_flow.py +++ b/tests/components/min_max/test_config_flow.py @@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_config_flow(hass: HomeAssistant, platform: str) -> None: """Test the config flow.""" input_sensors = ["sensor.input_one", "sensor.input_two"] @@ -66,7 +66,7 @@ def get_suggested(schema, key): raise Exception -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_options(hass: HomeAssistant, platform: str) -> None: """Test reconfiguring.""" hass.states.async_set("sensor.input_one", "10") diff --git a/tests/components/min_max/test_init.py b/tests/components/min_max/test_init.py index f6c67e60326..0e180c3135c 100644 --- a/tests/components/min_max/test_init.py +++ b/tests/components/min_max/test_init.py @@ -9,7 +9,7 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_setup_and_remove_config_entry( hass: HomeAssistant, entity_registry: er.EntityRegistry, diff --git a/tests/components/mobile_app/test_init.py b/tests/components/mobile_app/test_init.py index 5e44fbd1c03..c6c724cc20b 100644 --- a/tests/components/mobile_app/test_init.py +++ b/tests/components/mobile_app/test_init.py @@ -120,7 +120,7 @@ async def test_create_cloud_hook_on_setup( await _test_create_cloud_hook(hass, hass_admin_user, {}, True, additional_steps) -@pytest.mark.parametrize("exception", (CloudNotAvailable, ValueError)) +@pytest.mark.parametrize("exception", [CloudNotAvailable, ValueError]) async def test_remove_cloudhook( hass: HomeAssistant, hass_admin_user: MockUser, diff --git a/tests/components/mobile_app/test_sensor.py b/tests/components/mobile_app/test_sensor.py index 5ff006a1773..a7fb0ffc183 100644 --- a/tests/components/mobile_app/test_sensor.py +++ b/tests/components/mobile_app/test_sensor.py @@ -19,10 +19,10 @@ from homeassistant.util.unit_system import METRIC_SYSTEM, US_CUSTOMARY_SYSTEM @pytest.mark.parametrize( ("unit_system", "state_unit", "state1", "state2"), - ( + [ (METRIC_SYSTEM, UnitOfTemperature.CELSIUS, "100", "123"), (US_CUSTOMARY_SYSTEM, UnitOfTemperature.FAHRENHEIT, "212", "253"), - ), + ], ) async def test_sensor( hass: HomeAssistant, @@ -128,7 +128,7 @@ async def test_sensor( @pytest.mark.parametrize( ("unique_id", "unit_system", "state_unit", "state1", "state2"), - ( + [ ("battery_temperature", METRIC_SYSTEM, UnitOfTemperature.CELSIUS, "100", "123"), ( "battery_temperature", @@ -145,7 +145,7 @@ async def test_sensor( "212", "123", ), - ), + ], ) async def test_sensor_migration( hass: HomeAssistant, diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index c43b3c7ffb8..4a5f472221f 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -348,13 +348,13 @@ async def test_webhook_returns_error_incorrect_json( @pytest.mark.parametrize( ("msg", "generate_response"), - ( + [ (RENDER_TEMPLATE, lambda hass: {"one": "Hello world"}), ( {"type": "get_zones", "data": {}}, lambda hass: [hass.states.get("zone.home").as_dict()], ), - ), + ], ) async def test_webhook_handle_decryption( hass: HomeAssistant, webhook_client, create_registrations, msg, generate_response diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index b93519c1d05..598fcd7bea7 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -1328,24 +1328,24 @@ async def mock_modbus_read_pymodbus_fixture( @pytest.mark.parametrize( ("do_domain", "do_group", "do_type", "do_scan_interval"), [ - [SENSOR_DOMAIN, CONF_SENSORS, CALL_TYPE_REGISTER_HOLDING, 10], - [SENSOR_DOMAIN, CONF_SENSORS, CALL_TYPE_REGISTER_INPUT, 10], - [BINARY_SENSOR_DOMAIN, CONF_BINARY_SENSORS, CALL_TYPE_DISCRETE, 10], - [BINARY_SENSOR_DOMAIN, CONF_BINARY_SENSORS, CALL_TYPE_COIL, 1], + (SENSOR_DOMAIN, CONF_SENSORS, CALL_TYPE_REGISTER_HOLDING, 10), + (SENSOR_DOMAIN, CONF_SENSORS, CALL_TYPE_REGISTER_INPUT, 10), + (BINARY_SENSOR_DOMAIN, CONF_BINARY_SENSORS, CALL_TYPE_DISCRETE, 10), + (BINARY_SENSOR_DOMAIN, CONF_BINARY_SENSORS, CALL_TYPE_COIL, 1), ], ) @pytest.mark.parametrize( ("do_return", "do_exception", "do_expect_state", "do_expect_value"), [ - [ReadResult([1]), None, STATE_ON, "1"], - [IllegalFunctionRequest(0x99), None, STATE_UNAVAILABLE, STATE_UNAVAILABLE], - [ExceptionResponse(0x99), None, STATE_UNAVAILABLE, STATE_UNAVAILABLE], - [ + (ReadResult([1]), None, STATE_ON, "1"), + (IllegalFunctionRequest(0x99), None, STATE_UNAVAILABLE, STATE_UNAVAILABLE), + (ExceptionResponse(0x99), None, STATE_UNAVAILABLE, STATE_UNAVAILABLE), + ( ReadResult([1]), ModbusException("fail read_"), STATE_UNAVAILABLE, STATE_UNAVAILABLE, - ], + ), ], ) async def test_pb_read( diff --git a/tests/components/netgear_lte/test_config_flow.py b/tests/components/netgear_lte/test_config_flow.py index 08ae2ce0889..71fe8ddb774 100644 --- a/tests/components/netgear_lte/test_config_flow.py +++ b/tests/components/netgear_lte/test_config_flow.py @@ -40,7 +40,7 @@ async def test_flow_user_form(hass: HomeAssistant, connection: None) -> None: assert result["context"]["unique_id"] == "FFFFFFFFFFFFF" -@pytest.mark.parametrize("source", (SOURCE_USER, SOURCE_IMPORT)) +@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT]) async def test_flow_already_configured( hass: HomeAssistant, setup_integration: None, source: str ) -> None: diff --git a/tests/components/nextbus/test_config_flow.py b/tests/components/nextbus/test_config_flow.py index 6bb8d9e2bfe..466ecb9df61 100644 --- a/tests/components/nextbus/test_config_flow.py +++ b/tests/components/nextbus/test_config_flow.py @@ -70,11 +70,11 @@ async def test_import_config( @pytest.mark.parametrize( ("override", "expected_reason"), - ( + [ ({CONF_AGENCY: "not muni"}, "invalid_agency"), ({CONF_ROUTE: "not F"}, "invalid_route"), ({CONF_STOP: "not 5650"}, "invalid_stop"), - ), + ], ) async def test_import_config_invalid( hass: HomeAssistant, diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index 42c4c5bdb44..ece40b36fb1 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -274,10 +274,10 @@ async def test_direction_list( @pytest.mark.parametrize( "client_exception", - ( + [ NextBusHTTPError("failed", HTTPError("url", 500, "error", MagicMock(), None)), NextBusFormatError("failed"), - ), + ], ) async def test_prediction_exceptions( hass: HomeAssistant, @@ -312,10 +312,10 @@ async def test_custom_name( @pytest.mark.parametrize( "prediction_results", - ( + [ {}, {"Error": "Failed"}, - ), + ], ) async def test_no_predictions( hass: HomeAssistant, diff --git a/tests/components/nextbus/test_util.py b/tests/components/nextbus/test_util.py index 2d19c1fe777..dfe54249814 100644 --- a/tests/components/nextbus/test_util.py +++ b/tests/components/nextbus/test_util.py @@ -9,11 +9,11 @@ from homeassistant.components.nextbus.util import listify, maybe_first @pytest.mark.parametrize( ("input", "expected"), - ( + [ ("foo", ["foo"]), (["foo"], ["foo"]), (None, []), - ), + ], ) def test_listify(input: Any, expected: list[Any]) -> None: """Test input listification.""" @@ -22,13 +22,13 @@ def test_listify(input: Any, expected: list[Any]) -> None: @pytest.mark.parametrize( ("input", "expected"), - ( + [ ([], []), (None, None), ("test", "test"), (["test"], "test"), (["test", "second"], "test"), - ), + ], ) def test_maybe_first(input: list[Any] | None, expected: Any) -> None: """Test maybe getting the first thing from a list.""" diff --git a/tests/components/nibe_heatpump/test_config_flow.py b/tests/components/nibe_heatpump/test_config_flow.py index 2e615aa55f6..b4c0b223998 100644 --- a/tests/components/nibe_heatpump/test_config_flow.py +++ b/tests/components/nibe_heatpump/test_config_flow.py @@ -146,10 +146,10 @@ async def test_nibegw_address_inuse(hass: HomeAssistant, mock_connection: Mock) @pytest.mark.parametrize( ("connection_type", "data"), - ( + [ ("nibegw", MOCK_FLOW_NIBEGW_USERDATA), ("modbus", MOCK_FLOW_MODBUS_USERDATA), - ), + ], ) async def test_read_timeout( hass: HomeAssistant, mock_connection: Mock, connection_type: str, data: dict @@ -167,10 +167,10 @@ async def test_read_timeout( @pytest.mark.parametrize( ("connection_type", "data"), - ( + [ ("nibegw", MOCK_FLOW_NIBEGW_USERDATA), ("modbus", MOCK_FLOW_MODBUS_USERDATA), - ), + ], ) async def test_write_timeout( hass: HomeAssistant, mock_connection: Mock, connection_type: str, data: dict @@ -188,10 +188,10 @@ async def test_write_timeout( @pytest.mark.parametrize( ("connection_type", "data"), - ( + [ ("nibegw", MOCK_FLOW_NIBEGW_USERDATA), ("modbus", MOCK_FLOW_MODBUS_USERDATA), - ), + ], ) async def test_unexpected_exception( hass: HomeAssistant, mock_connection: Mock, connection_type: str, data: dict @@ -209,10 +209,10 @@ async def test_unexpected_exception( @pytest.mark.parametrize( ("connection_type", "data"), - ( + [ ("nibegw", MOCK_FLOW_NIBEGW_USERDATA), ("modbus", MOCK_FLOW_MODBUS_USERDATA), - ), + ], ) async def test_nibegw_invalid_host( hass: HomeAssistant, mock_connection: Mock, connection_type: str, data: dict @@ -233,10 +233,10 @@ async def test_nibegw_invalid_host( @pytest.mark.parametrize( ("connection_type", "data"), - ( + [ ("nibegw", MOCK_FLOW_NIBEGW_USERDATA), ("modbus", MOCK_FLOW_MODBUS_USERDATA), - ), + ], ) async def test_model_missing_coil( hass: HomeAssistant, mock_connection: Mock, connection_type: str, data: dict diff --git a/tests/components/number/test_device_action.py b/tests/components/number/test_device_action.py index 17546218a14..0c34f1bf53c 100644 --- a/tests/components/number/test_device_action.py +++ b/tests/components/number/test_device_action.py @@ -62,12 +62,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/oncue/test_sensor.py b/tests/components/oncue/test_sensor.py index 1544d015c89..13f5a8b944d 100644 --- a/tests/components/oncue/test_sensor.py +++ b/tests/components/oncue/test_sensor.py @@ -25,8 +25,8 @@ from tests.common import MockConfigEntry @pytest.mark.parametrize( ("patcher", "connections"), [ - [_patch_login_and_data, {("mac", "c9:24:22:6f:14:00")}], - [_patch_login_and_data_offline_device, set()], + (_patch_login_and_data, {("mac", "c9:24:22:6f:14:00")}), + (_patch_login_and_data_offline_device, set()), ], ) async def test_sensors(hass: HomeAssistant, patcher, connections) -> None: @@ -152,8 +152,8 @@ async def test_sensors(hass: HomeAssistant, patcher, connections) -> None: @pytest.mark.parametrize( ("patcher", "connections"), [ - [_patch_login_and_data_unavailable_device, set()], - [_patch_login_and_data_unavailable, {("mac", "c9:24:22:6f:14:00")}], + (_patch_login_and_data_unavailable_device, set()), + (_patch_login_and_data_unavailable, {("mac", "c9:24:22:6f:14:00")}), ], ) async def test_sensors_unavailable(hass: HomeAssistant, patcher, connections) -> None: diff --git a/tests/components/p1_monitor/test_sensor.py b/tests/components/p1_monitor/test_sensor.py index d280a4da39f..e1ea53ba6cc 100644 --- a/tests/components/p1_monitor/test_sensor.py +++ b/tests/components/p1_monitor/test_sensor.py @@ -239,7 +239,7 @@ async def test_no_watermeter( @pytest.mark.parametrize( "entity_id", - ("sensor.smartmeter_gas_consumption",), + ["sensor.smartmeter_gas_consumption"], ) async def test_smartmeter_disabled_by_default( hass: HomeAssistant, init_integration: MockConfigEntry, entity_id: str diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index 147ab879ed6..a16d87f1838 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -9,10 +9,10 @@ from homeassistant.setup import async_setup_component @pytest.mark.parametrize( "config_to_try", - ( + [ {"invalid space": {"url": "https://home-assistant.io"}}, {"router": {"url": "not-a-url"}}, - ), + ], ) async def test_wrong_config(hass: HomeAssistant, config_to_try) -> None: """Test setup with wrong configuration.""" diff --git a/tests/components/ping/test_config_flow.py b/tests/components/ping/test_config_flow.py index 50b05abd409..541bdca8b1e 100644 --- a/tests/components/ping/test_config_flow.py +++ b/tests/components/ping/test_config_flow.py @@ -17,7 +17,7 @@ from tests.common import MockConfigEntry @pytest.mark.parametrize( ("host", "expected_title"), - (("192.618.178.1", "192.618.178.1"),), + [("192.618.178.1", "192.618.178.1")], ) @pytest.mark.usefixtures("patch_setup") async def test_form(hass: HomeAssistant, host, expected_title) -> None: @@ -49,7 +49,7 @@ async def test_form(hass: HomeAssistant, host, expected_title) -> None: @pytest.mark.parametrize( ("host", "count", "expected_title"), - (("192.618.178.1", 10, "192.618.178.1"),), + [("192.618.178.1", 10, "192.618.178.1")], ) @pytest.mark.usefixtures("patch_setup") async def test_options(hass: HomeAssistant, host, count, expected_title) -> None: diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index 5b9e27460fd..1b78a4a7212 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -60,7 +60,7 @@ async def test_form_source_user(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 -@pytest.mark.parametrize("exc", (PowerwallUnreachableError, TimeoutError)) +@pytest.mark.parametrize("exc", [PowerwallUnreachableError, TimeoutError]) async def test_form_cannot_connect(hass: HomeAssistant, exc: Exception) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/prusalink/test_button.py b/tests/components/prusalink/test_button.py index da790a2036b..e99cf9c2180 100644 --- a/tests/components/prusalink/test_button.py +++ b/tests/components/prusalink/test_button.py @@ -22,10 +22,10 @@ def setup_button_platform_only(): @pytest.mark.parametrize( ("object_id", "method"), - ( + [ ("mock_title_cancel_job", "cancel_job"), ("mock_title_pause_job", "pause_job"), - ), + ], ) async def test_button_pause_cancel( hass: HomeAssistant, @@ -68,10 +68,10 @@ async def test_button_pause_cancel( @pytest.mark.parametrize( ("object_id", "method"), - ( + [ ("mock_title_cancel_job", "cancel_job"), ("mock_title_resume_job", "resume_job"), - ), + ], ) async def test_button_resume_cancel( hass: HomeAssistant, diff --git a/tests/components/random/test_config_flow.py b/tests/components/random/test_config_flow.py index 261843df78e..4ffa59da4e4 100644 --- a/tests/components/random/test_config_flow.py +++ b/tests/components/random/test_config_flow.py @@ -23,7 +23,7 @@ from tests.common import MockConfigEntry "extra_input", "extra_options", ), - ( + [ ( "binary_sensor", {}, @@ -47,7 +47,7 @@ from tests.common import MockConfigEntry {}, {"minimum": 0, "maximum": 20}, ), - ), + ], ) async def test_config_flow( hass: HomeAssistant, @@ -135,7 +135,7 @@ async def test_wrong_uom( "extra_options", "options_options", ), - ( + [ ( "sensor", { @@ -151,7 +151,7 @@ async def test_wrong_uom( "unit_of_measurement": UnitOfPower.WATT, }, ), - ), + ], ) async def test_options( hass: HomeAssistant, diff --git a/tests/components/recorder/auto_repairs/events/test_schema.py b/tests/components/recorder/auto_repairs/events/test_schema.py index 1dc9fb1f560..a09da8a688a 100644 --- a/tests/components/recorder/auto_repairs/events/test_schema.py +++ b/tests/components/recorder/auto_repairs/events/test_schema.py @@ -12,7 +12,7 @@ from tests.typing import RecorderInstanceGenerator @pytest.mark.parametrize("enable_schema_validation", [True]) -@pytest.mark.parametrize("db_engine", ("mysql", "postgresql")) +@pytest.mark.parametrize("db_engine", ["mysql", "postgresql"]) async def test_validate_db_schema_fix_float_issue( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, diff --git a/tests/components/recorder/auto_repairs/states/test_schema.py b/tests/components/recorder/auto_repairs/states/test_schema.py index f3d733c7c45..75d968f19d1 100644 --- a/tests/components/recorder/auto_repairs/states/test_schema.py +++ b/tests/components/recorder/auto_repairs/states/test_schema.py @@ -12,7 +12,7 @@ from tests.typing import RecorderInstanceGenerator @pytest.mark.parametrize("enable_schema_validation", [True]) -@pytest.mark.parametrize("db_engine", ("mysql", "postgresql")) +@pytest.mark.parametrize("db_engine", ["mysql", "postgresql"]) async def test_validate_db_schema_fix_float_issue( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, diff --git a/tests/components/recorder/auto_repairs/statistics/test_schema.py b/tests/components/recorder/auto_repairs/statistics/test_schema.py index 032cd57ce49..c5abb013d99 100644 --- a/tests/components/recorder/auto_repairs/statistics/test_schema.py +++ b/tests/components/recorder/auto_repairs/statistics/test_schema.py @@ -42,8 +42,8 @@ async def test_validate_db_schema_fix_utf8_issue( @pytest.mark.parametrize("enable_schema_validation", [True]) -@pytest.mark.parametrize("table", ("statistics_short_term", "statistics")) -@pytest.mark.parametrize("db_engine", ("mysql", "postgresql")) +@pytest.mark.parametrize("table", ["statistics_short_term", "statistics"]) +@pytest.mark.parametrize("db_engine", ["mysql", "postgresql"]) async def test_validate_db_schema_fix_float_issue( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, diff --git a/tests/components/recorder/auto_repairs/test_schema.py b/tests/components/recorder/auto_repairs/test_schema.py index 83fb64dca6c..14c74e2614e 100644 --- a/tests/components/recorder/auto_repairs/test_schema.py +++ b/tests/components/recorder/auto_repairs/test_schema.py @@ -23,7 +23,7 @@ from tests.typing import RecorderInstanceGenerator @pytest.mark.parametrize("enable_schema_validation", [True]) -@pytest.mark.parametrize("db_engine", ("mysql", "postgresql")) +@pytest.mark.parametrize("db_engine", ["mysql", "postgresql"]) async def test_validate_db_schema( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 65f5869b6ca..f28e379a0c4 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -274,11 +274,11 @@ async def test_saving_state(recorder_mock: Recorder, hass: HomeAssistant) -> Non @pytest.mark.parametrize( ("dialect_name", "expected_attributes"), - ( + [ (SupportedDialect.MYSQL, {"test_attr": 5, "test_attr_10": "silly\0stuff"}), (SupportedDialect.POSTGRESQL, {"test_attr": 5, "test_attr_10": "silly"}), (SupportedDialect.SQLITE, {"test_attr": 5, "test_attr_10": "silly\0stuff"}), - ), + ], ) async def test_saving_state_with_nul( recorder_mock: Recorder, hass: HomeAssistant, dialect_name, expected_attributes @@ -2116,14 +2116,14 @@ async def test_async_block_till_done( @pytest.mark.parametrize( ("db_url", "echo"), - ( + [ ("sqlite://blabla", None), ("mariadb://blabla", False), ("mysql://blabla", False), ("mariadb+pymysql://blabla", False), ("mysql+pymysql://blabla", False), ("postgresql://blabla", False), - ), + ], ) async def test_disable_echo( hass: HomeAssistant, db_url, echo, caplog: pytest.LogCaptureFixture @@ -2148,7 +2148,7 @@ async def test_disable_echo( @pytest.mark.parametrize( ("config_url", "expected_connect_args"), - ( + [ ( "mariadb://user:password@SERVER_IP/DB_NAME", {"charset": "utf8mb4"}, @@ -2181,7 +2181,7 @@ async def test_disable_echo( "sqlite://blabla", {}, ), - ), + ], ) async def test_mysql_missing_utf8mb4( hass: HomeAssistant, config_url, expected_connect_args @@ -2209,11 +2209,11 @@ async def test_mysql_missing_utf8mb4( @pytest.mark.parametrize( "config_url", - ( + [ "mysql://user:password@SERVER_IP/DB_NAME", "mysql://user:password@SERVER_IP/DB_NAME?charset=utf8mb4", "mysql://user:password@SERVER_IP/DB_NAME?blah=bleh&charset=other", - ), + ], ) async def test_connect_args_priority(hass: HomeAssistant, config_url) -> None: """Test connect_args has priority over URL query.""" diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index 8225fae3d5b..a58dd2e91dd 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -381,7 +381,7 @@ async def test_purge_old_statistics_runs( assert statistics_runs.count() == 1 -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_method( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, @@ -506,7 +506,7 @@ async def test_purge_method( ) -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_edge_case( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, @@ -749,7 +749,7 @@ async def test_purge_cutoff_date( assert state_attributes.count() == 0 -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_filtered_states( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, @@ -944,7 +944,7 @@ async def test_purge_filtered_states( assert session.query(StateAttributes).count() == 0 -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_filtered_states_to_empty( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, @@ -998,7 +998,7 @@ async def test_purge_filtered_states_to_empty( await async_wait_purge_done(hass) -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_without_state_attributes_filtered_states_to_empty( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, diff --git a/tests/components/recorder/test_purge_v32_schema.py b/tests/components/recorder/test_purge_v32_schema.py index e8f9130165f..cc210f2d780 100644 --- a/tests/components/recorder/test_purge_v32_schema.py +++ b/tests/components/recorder/test_purge_v32_schema.py @@ -349,7 +349,7 @@ async def test_purge_old_statistics_runs( assert statistics_runs.count() == 1 -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_method( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, @@ -469,7 +469,7 @@ async def test_purge_method( ) -@pytest.mark.parametrize("use_sqlite", (True, False), indirect=True) +@pytest.mark.parametrize("use_sqlite", [True, False], indirect=True) async def test_purge_edge_case( async_setup_recorder_instance: RecorderInstanceGenerator, hass: HomeAssistant, diff --git a/tests/components/recorder/test_statistics.py b/tests/components/recorder/test_statistics.py index 27f2955b1a5..d469db8831e 100644 --- a/tests/components/recorder/test_statistics.py +++ b/tests/components/recorder/test_statistics.py @@ -682,13 +682,13 @@ def test_statistics_duplicated( caplog.clear() -@pytest.mark.parametrize("last_reset_str", ("2022-01-01T00:00:00+02:00", None)) +@pytest.mark.parametrize("last_reset_str", ["2022-01-01T00:00:00+02:00", None]) @pytest.mark.parametrize( ("source", "statistic_id", "import_fn"), - ( + [ ("test", "test:total_energy_import", async_add_external_statistics), ("recorder", "sensor.total_energy_import", async_import_statistics), - ), + ], ) async def test_import_statistics( recorder_mock: Recorder, diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 01514385dd2..08217d6003c 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -216,7 +216,7 @@ async def test_statistics_during_period( @pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 7, 25, tzinfo=datetime.UTC)) -@pytest.mark.parametrize("offset", (0, 1, 2)) +@pytest.mark.parametrize("offset", [0, 1, 2]) async def test_statistic_during_period( recorder_mock: Recorder, hass: HomeAssistant, @@ -797,7 +797,7 @@ async def test_statistic_during_period_hole( @pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 7, 25, tzinfo=datetime.UTC)) @pytest.mark.parametrize( ("calendar_period", "start_time", "end_time"), - ( + [ ( {"period": "hour"}, "2022-10-21T07:00:00+00:00", @@ -848,7 +848,7 @@ async def test_statistic_during_period_hole( "2021-01-01T08:00:00+00:00", "2022-01-01T08:00:00+00:00", ), - ), + ], ) async def test_statistic_during_period_calendar( recorder_mock: Recorder, @@ -2393,10 +2393,10 @@ async def test_get_statistics_metadata( @pytest.mark.parametrize( ("source", "statistic_id"), - ( + [ ("test", "test:total_energy_import"), ("recorder", "sensor.total_energy_import"), - ), + ], ) async def test_import_statistics( recorder_mock: Recorder, @@ -2607,10 +2607,10 @@ async def test_import_statistics( @pytest.mark.parametrize( ("source", "statistic_id"), - ( + [ ("test", "test:total_energy_import"), ("recorder", "sensor.total_energy_import"), - ), + ], ) async def test_adjust_sum_statistics_energy( recorder_mock: Recorder, @@ -2800,10 +2800,10 @@ async def test_adjust_sum_statistics_energy( @pytest.mark.parametrize( ("source", "statistic_id"), - ( + [ ("test", "test:total_gas"), ("recorder", "sensor.total_gas"), - ), + ], ) async def test_adjust_sum_statistics_gas( recorder_mock: Recorder, @@ -3000,14 +3000,14 @@ async def test_adjust_sum_statistics_gas( "valid_units", "invalid_units", ), - ( + [ ("kWh", "kWh", "energy", 1, ("Wh", "kWh", "MWh"), ("ft³", "m³", "cats", None)), ("MWh", "MWh", "energy", 1, ("Wh", "kWh", "MWh"), ("ft³", "m³", "cats", None)), ("m³", "m³", "volume", 1, ("ft³", "m³"), ("Wh", "kWh", "MWh", "cats", None)), ("ft³", "ft³", "volume", 1, ("ft³", "m³"), ("Wh", "kWh", "MWh", "cats", None)), ("dogs", "dogs", None, 1, ("dogs",), ("cats", None)), (None, None, "unitless", 1, (None,), ("cats",)), - ), + ], ) async def test_adjust_sum_statistics_errors( recorder_mock: Recorder, diff --git a/tests/components/remote/test_device_action.py b/tests/components/remote/test_device_action.py index ef8364c050e..09c68843872 100644 --- a/tests/components/remote/test_device_action.py +++ b/tests/components/remote/test_device_action.py @@ -63,12 +63,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/remote/test_device_condition.py b/tests/components/remote/test_device_condition.py index 2d21e6e25ed..e7826f4952c 100644 --- a/tests/components/remote/test_device_condition.py +++ b/tests/components/remote/test_device_condition.py @@ -69,12 +69,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/remote/test_device_trigger.py b/tests/components/remote/test_device_trigger.py index f87a5484f4a..b77d971e9a6 100644 --- a/tests/components/remote/test_device_trigger.py +++ b/tests/components/remote/test_device_trigger.py @@ -69,12 +69,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/repairs/test_init.py b/tests/components/repairs/test_init.py index d4bc2777c8a..ec34409eb74 100644 --- a/tests/components/repairs/test_init.py +++ b/tests/components/repairs/test_init.py @@ -125,7 +125,7 @@ async def test_create_update_issue( ) -@pytest.mark.parametrize("ha_version", ("2022.9.cat", "In the future: 2023.1.1")) +@pytest.mark.parametrize("ha_version", ["2022.9.cat", "In the future: 2023.1.1"]) async def test_create_issue_invalid_version( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, ha_version ) -> None: diff --git a/tests/components/repairs/test_websocket_api.py b/tests/components/repairs/test_websocket_api.py index 859bdb15805..846b25ae8c2 100644 --- a/tests/components/repairs/test_websocket_api.py +++ b/tests/components/repairs/test_websocket_api.py @@ -270,10 +270,10 @@ async def test_fix_non_existing_issue( @pytest.mark.parametrize( ("domain", "step", "description_placeholders"), - ( + [ ("fake_integration", "custom_step", None), ("fake_integration_default_handler", "confirm", {"abc": "123"}), - ), + ], ) async def test_fix_issue( hass: HomeAssistant, diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index 81684b6301e..3de386be214 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -162,11 +162,11 @@ async def test_setup_encoding(hass: HomeAssistant) -> None: @respx.mock @pytest.mark.parametrize( ("ssl_cipher_list", "ssl_cipher_list_expected"), - ( + [ ("python_default", SSLCipherList.PYTHON_DEFAULT), ("intermediate", SSLCipherList.INTERMEDIATE), ("modern", SSLCipherList.MODERN), - ), + ], ) async def test_setup_ssl_ciphers( hass: HomeAssistant, ssl_cipher_list: str, ssl_cipher_list_expected: SSLCipherList diff --git a/tests/components/rfxtrx/test_binary_sensor.py b/tests/components/rfxtrx/test_binary_sensor.py index d71cd040c87..8f212b6e976 100644 --- a/tests/components/rfxtrx/test_binary_sensor.py +++ b/tests/components/rfxtrx/test_binary_sensor.py @@ -98,7 +98,7 @@ async def test_pt2262_unconfigured(hass: HomeAssistant, rfxtrx) -> None: @pytest.mark.parametrize( ("state", "event"), - [["on", "0b1100cd0213c7f230010f71"], ["off", "0b1100cd0213c7f230000f71"]], + [("on", "0b1100cd0213c7f230010f71"), ("off", "0b1100cd0213c7f230000f71")], ) async def test_state_restore(hass: HomeAssistant, rfxtrx, state, event) -> None: """State restoration.""" diff --git a/tests/components/rfxtrx/test_device_action.py b/tests/components/rfxtrx/test_device_action.py index 07a972a9148..a717fcf35d6 100644 --- a/tests/components/rfxtrx/test_device_action.py +++ b/tests/components/rfxtrx/test_device_action.py @@ -67,15 +67,15 @@ def _get_expected_actions(data): @pytest.mark.parametrize( ("device", "expected"), [ - [ + ( DEVICE_LIGHTING_1, list(_get_expected_actions(RFXtrx.lowlevel.Lighting1.COMMANDS)), - ], - [ + ), + ( DEVICE_BLINDS_1, list(_get_expected_actions(RFXtrx.lowlevel.RollerTrol.COMMANDS)), - ], - [DEVICE_TEMPHUM_1, []], + ), + (DEVICE_TEMPHUM_1, []), ], ) async def test_get_actions( @@ -115,21 +115,21 @@ async def test_get_actions( @pytest.mark.parametrize( ("device", "config", "expected"), [ - [ + ( DEVICE_LIGHTING_1, {"type": "send_command", "subtype": "On"}, "0710000045050100", - ], - [ + ), + ( DEVICE_LIGHTING_1, {"type": "send_command", "subtype": "Off"}, "0710000045050000", - ], - [ + ), + ( DEVICE_BLINDS_1, {"type": "send_command", "subtype": "Stop"}, "09190000009ba8010200", - ], + ), ], ) async def test_action( diff --git a/tests/components/rfxtrx/test_device_trigger.py b/tests/components/rfxtrx/test_device_trigger.py index 63ac5b1ab89..7d24ec3ff6a 100644 --- a/tests/components/rfxtrx/test_device_trigger.py +++ b/tests/components/rfxtrx/test_device_trigger.py @@ -61,7 +61,7 @@ async def setup_entry(hass, devices): @pytest.mark.parametrize( ("event", "expected"), [ - [ + ( EVENT_LIGHTING_1, [ {"type": "command", "subtype": subtype} @@ -76,7 +76,7 @@ async def setup_entry(hass, devices): "Illegal command", ] ], - ] + ) ], ) async def test_get_triggers( diff --git a/tests/components/rfxtrx/test_light.py b/tests/components/rfxtrx/test_light.py index 05862e47630..c95df855da5 100644 --- a/tests/components/rfxtrx/test_light.py +++ b/tests/components/rfxtrx/test_light.py @@ -91,7 +91,7 @@ async def test_one_light(hass: HomeAssistant, rfxtrx) -> None: @pytest.mark.parametrize( - ("state", "brightness"), [["on", 100], ["on", 50], ["off", None]] + ("state", "brightness"), [("on", 100), ("on", 50), ("off", None)] ) async def test_state_restore(hass: HomeAssistant, rfxtrx, state, brightness) -> None: """State restoration.""" diff --git a/tests/components/rfxtrx/test_sensor.py b/tests/components/rfxtrx/test_sensor.py index 566237156a6..4336798768f 100644 --- a/tests/components/rfxtrx/test_sensor.py +++ b/tests/components/rfxtrx/test_sensor.py @@ -52,7 +52,7 @@ async def test_one_sensor(hass: HomeAssistant, rfxtrx) -> None: @pytest.mark.parametrize( ("state", "event"), - [["18.4", "0a520801070100b81b0279"], ["17.9", "0a52085e070100b31b0279"]], + [("18.4", "0a520801070100b81b0279"), ("17.9", "0a52085e070100b31b0279")], ) async def test_state_restore(hass: HomeAssistant, rfxtrx, state, event) -> None: """State restoration.""" diff --git a/tests/components/schedule/test_init.py b/tests/components/schedule/test_init.py index 9ee16806287..ddb98cee39d 100644 --- a/tests/components/schedule/test_init.py +++ b/tests/components/schedule/test_init.py @@ -118,7 +118,7 @@ async def test_invalid_config(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("schedule", "error"), - ( + [ ( [ {CONF_FROM: "00:00:00", CONF_TO: "23:59:59"}, @@ -153,7 +153,7 @@ async def test_invalid_config(hass: HomeAssistant) -> None: ], "Invalid time range, from 06:00:00 is after 05:00:00", ), - ), + ], ) async def test_invalid_schedules( hass: HomeAssistant, @@ -419,10 +419,10 @@ async def test_non_adjacent_within_day( @pytest.mark.parametrize( "schedule", - ( + [ {CONF_FROM: "00:00:00", CONF_TO: "24:00"}, {CONF_FROM: "00:00:00", CONF_TO: "24:00:00"}, - ), + ], ) async def test_to_midnight( hass: HomeAssistant, @@ -595,11 +595,11 @@ async def test_ws_delete( @pytest.mark.freeze_time("2022-08-10 20:10:00-07:00") @pytest.mark.parametrize( ("to", "next_event", "saved_to"), - ( + [ ("23:59:59", "2022-08-10T23:59:59-07:00", "23:59:59"), ("24:00", "2022-08-11T00:00:00-07:00", "24:00:00"), ("24:00:00", "2022-08-11T00:00:00-07:00", "24:00:00"), - ), + ], ) async def test_update( hass: HomeAssistant, @@ -665,11 +665,11 @@ async def test_update( @pytest.mark.freeze_time("2022-08-11 8:52:00-07:00") @pytest.mark.parametrize( ("to", "next_event", "saved_to"), - ( + [ ("14:00:00", "2022-08-15T14:00:00-07:00", "14:00:00"), ("24:00", "2022-08-16T00:00:00-07:00", "24:00:00"), ("24:00:00", "2022-08-16T00:00:00-07:00", "24:00:00"), - ), + ], ) async def test_ws_create( hass: HomeAssistant, diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 8affd8b7cf9..b4758d2d547 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -196,7 +196,7 @@ async def test_setup_with_invalid_configs( @pytest.mark.parametrize( ("object_id", "broken_config", "problem", "details"), - ( + [ ( "Bad Script", {}, @@ -212,7 +212,7 @@ async def test_setup_with_invalid_configs( "reload, toggle, turn_off, turn_on. Got 'turn_on'" ), ), - ), + ], ) async def test_bad_config_validation_critical( hass: HomeAssistant, @@ -252,7 +252,7 @@ async def test_bad_config_validation_critical( @pytest.mark.parametrize( ("object_id", "broken_config", "problem", "details"), - ( + [ ( "bad_script", {}, @@ -272,7 +272,7 @@ async def test_bad_config_validation_critical( "failed to setup actions", "Unknown entity registry entry abcdabcdabcdabcdabcdabcdabcdabcd.", ), - ), + ], ) async def test_bad_config_validation( hass: HomeAssistant, @@ -424,7 +424,7 @@ async def test_reload_unchanged_does_not_stop(hass: HomeAssistant, calls) -> Non @pytest.mark.parametrize( "script_config", - ( + [ { "test": { "sequence": [{"service": "test.script"}], @@ -458,7 +458,7 @@ async def test_reload_unchanged_does_not_stop(hass: HomeAssistant, calls) -> Non } } }, - ), + ], ) async def test_reload_unchanged_script( hass: HomeAssistant, calls, script_config @@ -1182,12 +1182,12 @@ async def test_script_restore_last_triggered(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("script_mode", "warning_msg"), - ( + [ (SCRIPT_MODE_PARALLEL, "Maximum number of runs exceeded"), (SCRIPT_MODE_QUEUED, "Disallowed recursion detected"), (SCRIPT_MODE_RESTART, "Disallowed recursion detected"), (SCRIPT_MODE_SINGLE, "Already running"), - ), + ], ) async def test_recursive_script( hass: HomeAssistant, script_mode, warning_msg, caplog: pytest.LogCaptureFixture @@ -1232,12 +1232,12 @@ async def test_recursive_script( @pytest.mark.parametrize( ("script_mode", "warning_msg"), - ( + [ (SCRIPT_MODE_PARALLEL, "Maximum number of runs exceeded"), (SCRIPT_MODE_QUEUED, "Disallowed recursion detected"), (SCRIPT_MODE_RESTART, "Disallowed recursion detected"), (SCRIPT_MODE_SINGLE, "Already running"), - ), + ], ) async def test_recursive_script_indirect( hass: HomeAssistant, script_mode, warning_msg, caplog: pytest.LogCaptureFixture @@ -1539,7 +1539,7 @@ async def test_blueprint_automation(hass: HomeAssistant, calls) -> None: @pytest.mark.parametrize( ("blueprint_inputs", "problem", "details"), - ( + [ ( # No input {}, @@ -1562,7 +1562,7 @@ async def test_blueprint_automation(hass: HomeAssistant, calls) -> None: "Blueprint 'Call service' generated invalid script", "value should be a string for dictionary value @ data['sequence'][0]['service']", ), - ), + ], ) async def test_blueprint_script_bad_config( hass: HomeAssistant, @@ -1621,7 +1621,7 @@ async def test_blueprint_script_fails_substitution( ) -@pytest.mark.parametrize("response", ({"value": 5}, '{"value": 5}')) +@pytest.mark.parametrize("response", [{"value": 5}, '{"value": 5}']) async def test_responses(hass: HomeAssistant, response: Any) -> None: """Test we can get responses.""" mock_restore_cache(hass, ()) diff --git a/tests/components/select/test_device_action.py b/tests/components/select/test_device_action.py index 8245dfe7d56..c83e2585d5b 100644 --- a/tests/components/select/test_device_action.py +++ b/tests/components/select/test_device_action.py @@ -63,12 +63,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, @@ -115,7 +115,7 @@ async def test_get_actions_hidden_auxiliary( assert actions == unordered(expected_actions) -@pytest.mark.parametrize("action_type", ("select_first", "select_last")) +@pytest.mark.parametrize("action_type", ["select_first", "select_last"]) async def test_action_select_first_last( hass: HomeAssistant, device_registry: dr.DeviceRegistry, @@ -164,7 +164,7 @@ async def test_action_select_first_last( assert select_calls[0].data == {"entity_id": entry.entity_id} -@pytest.mark.parametrize("action_type", ("select_first", "select_last")) +@pytest.mark.parametrize("action_type", ["select_first", "select_last"]) async def test_action_select_first_last_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, diff --git a/tests/components/select/test_device_condition.py b/tests/components/select/test_device_condition.py index c744a6409bf..526ad678c19 100644 --- a/tests/components/select/test_device_condition.py +++ b/tests/components/select/test_device_condition.py @@ -67,12 +67,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/select/test_device_trigger.py b/tests/components/select/test_device_trigger.py index bd40f975d3e..e587e125e11 100644 --- a/tests/components/select/test_device_trigger.py +++ b/tests/components/select/test_device_trigger.py @@ -67,12 +67,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 31f234109e5..7263154c1dc 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -131,12 +131,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, @@ -225,13 +225,13 @@ async def test_get_conditions_no_state( @pytest.mark.parametrize( ("state_class", "unit", "condition_types"), - ( + [ (SensorStateClass.MEASUREMENT, None, ["is_value"]), (SensorStateClass.TOTAL, None, ["is_value"]), (SensorStateClass.TOTAL_INCREASING, None, ["is_value"]), (SensorStateClass.MEASUREMENT, "dogs", ["is_value"]), (None, None, []), - ), + ], ) async def test_get_conditions_no_unit_or_stateclass( hass: HomeAssistant, diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index c97f34c2c39..4193adc9299 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -133,12 +133,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, @@ -182,13 +182,13 @@ async def test_get_triggers_hidden_auxiliary( @pytest.mark.parametrize( ("state_class", "unit", "trigger_types"), - ( + [ (SensorStateClass.MEASUREMENT, None, ["value"]), (SensorStateClass.TOTAL, None, ["value"]), (SensorStateClass.TOTAL_INCREASING, None, ["value"]), (SensorStateClass.MEASUREMENT, "dogs", ["value"]), (None, None, []), - ), + ], ) async def test_get_triggers_no_unit_or_stateclass( hass: HomeAssistant, diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 909552bb2ed..59df07bb0b9 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -130,7 +130,7 @@ async def test_temperature_conversion( assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == state_unit -@pytest.mark.parametrize("device_class", (None, SensorDeviceClass.PRESSURE)) +@pytest.mark.parametrize("device_class", [None, SensorDeviceClass.PRESSURE]) async def test_temperature_conversion_wrong_device_class( hass: HomeAssistant, device_class, enable_custom_integrations: None ) -> None: @@ -154,7 +154,7 @@ async def test_temperature_conversion_wrong_device_class( assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.FAHRENHEIT -@pytest.mark.parametrize("state_class", ("measurement", "total_increasing")) +@pytest.mark.parametrize("state_class", ["measurement", "total_increasing"]) async def test_deprecated_last_reset( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, @@ -1785,11 +1785,11 @@ async def test_invalid_enumeration_entity_without_device_class( @pytest.mark.parametrize( "device_class", - ( + [ SensorDeviceClass.DATE, SensorDeviceClass.ENUM, SensorDeviceClass.TIMESTAMP, - ), + ], ) async def test_non_numeric_device_class_with_unit_of_measurement( hass: HomeAssistant, @@ -1819,7 +1819,7 @@ async def test_non_numeric_device_class_with_unit_of_measurement( @pytest.mark.parametrize( "device_class", - ( + [ SensorDeviceClass.APPARENT_POWER, SensorDeviceClass.AQI, SensorDeviceClass.ATMOSPHERIC_PRESSURE, @@ -1864,7 +1864,7 @@ async def test_non_numeric_device_class_with_unit_of_measurement( SensorDeviceClass.WATER, SensorDeviceClass.WEIGHT, SensorDeviceClass.WIND_SPEED, - ), + ], ) async def test_device_classes_with_invalid_unit_of_measurement( hass: HomeAssistant, @@ -1952,7 +1952,7 @@ async def test_non_numeric_validation_error( @pytest.mark.parametrize( - ("device_class", "state_class", "unit", "precision"), ((None, None, None, 1),) + ("device_class", "state_class", "unit", "precision"), [(None, None, None, 1)] ) @pytest.mark.parametrize( ("native_value", "expected"), diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 2304a9388ea..3641ae95de8 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -90,13 +90,13 @@ async def test_sensors( @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.sonarr_commands", "sensor.sonarr_disk_space", "sensor.sonarr_queue", "sensor.sonarr_shows", "sensor.sonarr_wanted", - ), + ], ) async def test_disabled_by_default_sensors( hass: HomeAssistant, diff --git a/tests/components/stt/test_init.py b/tests/components/stt/test_init.py index 4230b7bf33d..a06c635bcfd 100644 --- a/tests/components/stt/test_init.py +++ b/tests/components/stt/test_init.py @@ -300,7 +300,7 @@ async def test_stream_audio( ) @pytest.mark.parametrize( ("header", "status", "error"), - ( + [ (None, 400, "Missing X-Speech-Content header"), ( ( @@ -331,7 +331,7 @@ async def test_stream_audio( 400, "Missing language in X-Speech-Content header", ), - ), + ], ) async def test_metadata_errors( hass: HomeAssistant, diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index a4e0472b2e7..c35f7261afc 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -64,12 +64,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index 7127e840aeb..d69d8a547aa 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -69,12 +69,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 0e6eff9bd27..874210a32bc 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -69,12 +69,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/switch_as_x/test_config_flow.py b/tests/components/switch_as_x/test_config_flow.py index 7603e0d1235..59b7d7fadcd 100644 --- a/tests/components/switch_as_x/test_config_flow.py +++ b/tests/components/switch_as_x/test_config_flow.py @@ -67,10 +67,10 @@ async def test_config_flow( @pytest.mark.parametrize( ("hidden_by_before", "hidden_by_after"), - ( + [ (er.RegistryEntryHider.USER, er.RegistryEntryHider.USER), (None, er.RegistryEntryHider.INTEGRATION), - ), + ], ) @pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST) async def test_config_flow_registered_entity( diff --git a/tests/components/switch_as_x/test_init.py b/tests/components/switch_as_x/test_init.py index 3fe6ede151f..c74b14cc91c 100644 --- a/tests/components/switch_as_x/test_init.py +++ b/tests/components/switch_as_x/test_init.py @@ -70,14 +70,14 @@ async def test_config_entry_unregistered_uuid( @pytest.mark.parametrize( ("target_domain", "state_on", "state_off"), - ( + [ (Platform.COVER, STATE_OPEN, STATE_CLOSED), (Platform.FAN, STATE_ON, STATE_OFF), (Platform.LIGHT, STATE_ON, STATE_OFF), (Platform.LOCK, STATE_UNLOCKED, STATE_LOCKED), (Platform.SIREN, STATE_ON, STATE_OFF), (Platform.VALVE, STATE_OPEN, STATE_CLOSED), - ), + ], ) async def test_entity_registry_events( hass: HomeAssistant, target_domain: str, state_on: str, state_off: str @@ -407,10 +407,10 @@ async def test_setup_and_remove_config_entry( @pytest.mark.parametrize( ("hidden_by_before", "hidden_by_after"), - ( + [ (er.RegistryEntryHider.USER, er.RegistryEntryHider.USER), (er.RegistryEntryHider.INTEGRATION, None), - ), + ], ) @pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST) async def test_reset_hidden_by( diff --git a/tests/components/tasmota/test_cover.py b/tests/components/tasmota/test_cover.py index a28739d59be..7da3cdbd1ec 100644 --- a/tests/components/tasmota/test_cover.py +++ b/tests/components/tasmota/test_cover.py @@ -297,7 +297,7 @@ async def test_controlling_state_via_mqtt_tilt( assert state.attributes["current_position"] == 100 -@pytest.mark.parametrize("tilt", ("", ',"Tilt":0')) +@pytest.mark.parametrize("tilt", ["", ',"Tilt":0']) async def test_controlling_state_via_mqtt_inverted( hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota, tilt ) -> None: diff --git a/tests/components/technove/test_binary_sensor.py b/tests/components/technove/test_binary_sensor.py index faa29e4e047..0ee4f3f3db7 100644 --- a/tests/components/technove/test_binary_sensor.py +++ b/tests/components/technove/test_binary_sensor.py @@ -43,7 +43,7 @@ async def test_sensors( @pytest.mark.parametrize( "entity_id", - ("binary_sensor.technove_station_static_ip",), + ["binary_sensor.technove_station_static_ip"], ) @pytest.mark.usefixtures("init_integration") async def test_disabled_by_default_binary_sensors( diff --git a/tests/components/technove/test_sensor.py b/tests/components/technove/test_sensor.py index b1d63933bc1..9cf80a659eb 100644 --- a/tests/components/technove/test_sensor.py +++ b/tests/components/technove/test_sensor.py @@ -45,10 +45,10 @@ async def test_sensors( @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.technove_station_signal_strength", "sensor.technove_station_wi_fi_network_name", - ), + ], ) @pytest.mark.usefixtures("init_integration") async def test_disabled_by_default_sensors( diff --git a/tests/components/template/test_config_flow.py b/tests/components/template/test_config_flow.py index 4950c4ce819..30d6942750c 100644 --- a/tests/components/template/test_config_flow.py +++ b/tests/components/template/test_config_flow.py @@ -26,7 +26,7 @@ from tests.typing import WebSocketGenerator "extra_options", "extra_attrs", ), - ( + [ ( "binary_sensor", "{{ states('binary_sensor.one') == 'on' or states('binary_sensor.two') == 'on' }}", @@ -47,7 +47,7 @@ from tests.typing import WebSocketGenerator {}, {}, ), - ), + ], ) async def test_config_flow( hass: HomeAssistant, @@ -142,7 +142,7 @@ def get_suggested(schema, key): "extra_options", "options_options", ), - ( + [ ( "binary_sensor", "{{ states('binary_sensor.one') == 'on' or states('binary_sensor.two') == 'on' }}", @@ -161,7 +161,7 @@ def get_suggested(schema, key): {}, {}, ), - ), + ], ) async def test_options( hass: HomeAssistant, @@ -261,7 +261,7 @@ async def test_options( "extra_attributes", "listeners", ), - ( + [ ( "binary_sensor", "{{ states.binary_sensor.one.state == 'on' or states.binary_sensor.two.state == 'on' }}", @@ -280,7 +280,7 @@ async def test_options( [{}, {}], [["one", "two"], ["one", "two"]], ), - ), + ], ) async def test_config_flow_preview( hass: HomeAssistant, @@ -644,13 +644,13 @@ async def test_config_flow_preview_template_error( "state_template", "extra_user_input", ), - ( + [ ( "sensor", "{{ states('sensor.one') }}", {"unit_of_measurement": "°C"}, ), - ), + ], ) async def test_config_flow_preview_bad_state( hass: HomeAssistant, diff --git a/tests/components/text/test_device_action.py b/tests/components/text/test_device_action.py index e2357fc7794..6a0e0958558 100644 --- a/tests/components/text/test_device_action.py +++ b/tests/components/text/test_device_action.py @@ -62,12 +62,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/thread/test_discovery.py b/tests/components/thread/test_discovery.py index 12eddb0b92a..bdfd0390b9a 100644 --- a/tests/components/thread/test_discovery.py +++ b/tests/components/thread/test_discovery.py @@ -194,7 +194,7 @@ async def test_discover_routers_unconfigured( @pytest.mark.parametrize( - "data", (ROUTER_DISCOVERY_HASS_BAD_DATA, ROUTER_DISCOVERY_HASS_MISSING_DATA) + "data", [ROUTER_DISCOVERY_HASS_BAD_DATA, ROUTER_DISCOVERY_HASS_MISSING_DATA] ) async def test_discover_routers_bad_or_missing_optional_data( hass: HomeAssistant, mock_async_zeroconf: None, data diff --git a/tests/components/threshold/test_config_flow.py b/tests/components/threshold/test_config_flow.py index ca61be795d6..726fa04cef0 100644 --- a/tests/components/threshold/test_config_flow.py +++ b/tests/components/threshold/test_config_flow.py @@ -61,7 +61,7 @@ async def test_config_flow(hass: HomeAssistant) -> None: assert config_entry.title == "My threshold sensor" -@pytest.mark.parametrize(("extra_input_data", "error"), (({}, "need_lower_upper"),)) +@pytest.mark.parametrize(("extra_input_data", "error"), [({}, "need_lower_upper")]) async def test_fail(hass: HomeAssistant, extra_input_data, error) -> None: """Test not providing lower or upper limit fails.""" input_sensor = "sensor.input" diff --git a/tests/components/threshold/test_init.py b/tests/components/threshold/test_init.py index a580ec50a9d..86b580c47f5 100644 --- a/tests/components/threshold/test_init.py +++ b/tests/components/threshold/test_init.py @@ -9,7 +9,7 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("binary_sensor",)) +@pytest.mark.parametrize("platform", ["binary_sensor"]) async def test_setup_and_remove_config_entry( hass: HomeAssistant, platform: str, diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index 65c768a2a64..1a2e1ad9849 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -823,7 +823,7 @@ async def test_dst6( @pytest.mark.freeze_time("2019-01-10 18:43:00") -@pytest.mark.parametrize("hass_time_zone", ("UTC",)) +@pytest.mark.parametrize("hass_time_zone", ["UTC"]) async def test_simple_before_after_does_not_loop_utc_not_in_range( hass: HomeAssistant, ) -> None: @@ -849,7 +849,7 @@ async def test_simple_before_after_does_not_loop_utc_not_in_range( @pytest.mark.freeze_time("2019-01-10 22:43:00") -@pytest.mark.parametrize("hass_time_zone", ("UTC",)) +@pytest.mark.parametrize("hass_time_zone", ["UTC"]) async def test_simple_before_after_does_not_loop_utc_in_range( hass: HomeAssistant, ) -> None: @@ -875,7 +875,7 @@ async def test_simple_before_after_does_not_loop_utc_in_range( @pytest.mark.freeze_time("2019-01-11 06:00:00") -@pytest.mark.parametrize("hass_time_zone", ("UTC",)) +@pytest.mark.parametrize("hass_time_zone", ["UTC"]) async def test_simple_before_after_does_not_loop_utc_fire_at_before( hass: HomeAssistant, ) -> None: @@ -901,7 +901,7 @@ async def test_simple_before_after_does_not_loop_utc_fire_at_before( @pytest.mark.freeze_time("2019-01-10 22:00:00") -@pytest.mark.parametrize("hass_time_zone", ("UTC",)) +@pytest.mark.parametrize("hass_time_zone", ["UTC"]) async def test_simple_before_after_does_not_loop_utc_fire_at_after( hass: HomeAssistant, ) -> None: @@ -927,7 +927,7 @@ async def test_simple_before_after_does_not_loop_utc_fire_at_after( @pytest.mark.freeze_time("2019-01-10 22:00:00") -@pytest.mark.parametrize("hass_time_zone", ("UTC",)) +@pytest.mark.parametrize("hass_time_zone", ["UTC"]) async def test_simple_before_after_does_not_loop_utc_both_before_now( hass: HomeAssistant, ) -> None: @@ -953,7 +953,7 @@ async def test_simple_before_after_does_not_loop_utc_both_before_now( @pytest.mark.freeze_time("2019-01-10 17:43:00+01:00") -@pytest.mark.parametrize("hass_time_zone", ("Europe/Berlin",)) +@pytest.mark.parametrize("hass_time_zone", ["Europe/Berlin"]) async def test_simple_before_after_does_not_loop_berlin_not_in_range( hass: HomeAssistant, ) -> None: @@ -979,7 +979,7 @@ async def test_simple_before_after_does_not_loop_berlin_not_in_range( @pytest.mark.freeze_time("2019-01-11 00:43:00+01:00") -@pytest.mark.parametrize("hass_time_zone", ("Europe/Berlin",)) +@pytest.mark.parametrize("hass_time_zone", ["Europe/Berlin"]) async def test_simple_before_after_does_not_loop_berlin_in_range( hass: HomeAssistant, ) -> None: diff --git a/tests/components/tod/test_config_flow.py b/tests/components/tod/test_config_flow.py index b18e8e316e3..c56accf103c 100644 --- a/tests/components/tod/test_config_flow.py +++ b/tests/components/tod/test_config_flow.py @@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_config_flow(hass: HomeAssistant, platform) -> None: """Test the config flow.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/todo/test_init.py b/tests/components/todo/test_init.py index 2b8bb858177..4e52c2fff70 100644 --- a/tests/components/todo/test_init.py +++ b/tests/components/todo/test_init.py @@ -227,23 +227,11 @@ async def test_list_todo_items( [ ({}, [ITEM_1, ITEM_2]), ( - [ - {"status": [TodoItemStatus.COMPLETED, TodoItemStatus.NEEDS_ACTION]}, - [ITEM_1, ITEM_2], - ] - ), - ( - [ - {"status": [TodoItemStatus.NEEDS_ACTION]}, - [ITEM_1], - ] - ), - ( - [ - {"status": [TodoItemStatus.COMPLETED]}, - [ITEM_2], - ] + {"status": [TodoItemStatus.COMPLETED, TodoItemStatus.NEEDS_ACTION]}, + [ITEM_1, ITEM_2], ), + ({"status": [TodoItemStatus.NEEDS_ACTION]}, [ITEM_1]), + ({"status": [TodoItemStatus.COMPLETED]}, [ITEM_2]), ], ) async def test_get_items_service( @@ -390,7 +378,7 @@ async def test_add_item_service_invalid_input( @pytest.mark.parametrize( ("supported_entity_feature", "item_data", "expected_item"), - ( + [ ( TodoListEntityFeature.SET_DUE_DATE_ON_ITEM, {"item": "New item", "due_date": "2023-11-13"}, @@ -436,7 +424,7 @@ async def test_add_item_service_invalid_input( description="Submit revised draft", ), ), - ), + ], ) async def test_add_item_service_extended_fields( hass: HomeAssistant, @@ -695,7 +683,7 @@ async def test_update_todo_item_field_unsupported( @pytest.mark.parametrize( ("supported_entity_feature", "update_data", "expected_update"), - ( + [ ( TodoListEntityFeature.SET_DUE_DATE_ON_ITEM, {"due_date": "2023-11-13"}, @@ -726,7 +714,7 @@ async def test_update_todo_item_field_unsupported( description="Submit revised draft", ), ), - ), + ], ) async def test_update_todo_item_extended_fields( hass: HomeAssistant, @@ -756,7 +744,7 @@ async def test_update_todo_item_extended_fields( @pytest.mark.parametrize( ("test_entity_items", "update_data", "expected_update"), - ( + [ ( [TodoItem(uid="1", summary="Summary", description="description")], {"description": "Submit revised draft"}, @@ -804,7 +792,7 @@ async def test_update_todo_item_extended_fields( {"due_datetime": None}, TodoItem(uid="1", summary="Summary"), ), - ), + ], ids=[ "overwrite_description", "overwrite_empty_description", diff --git a/tests/components/traccar_server/test_config_flow.py b/tests/components/traccar_server/test_config_flow.py index 055f155a894..c412830066d 100644 --- a/tests/components/traccar_server/test_config_flow.py +++ b/tests/components/traccar_server/test_config_flow.py @@ -67,10 +67,10 @@ async def test_form( @pytest.mark.parametrize( ("side_effect", "error"), - ( + [ (TraccarException, "cannot_connect"), (Exception, "unknown"), - ), + ], ) async def test_form_cannot_connect( hass: HomeAssistant, @@ -154,7 +154,7 @@ async def test_options( @pytest.mark.parametrize( ("imported", "data", "options"), - ( + [ ( { CONF_HOST: "1.1.1.1", @@ -223,7 +223,7 @@ async def test_options( CONF_MAX_ACCURACY: 0, }, ), - ), + ], ) async def test_import_from_yaml( hass: HomeAssistant, diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 681b14eecf0..5626f91b2a3 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -1327,12 +1327,12 @@ async def test_tags_with_wave() -> None: ) @pytest.mark.parametrize( ("engine", "language", "options", "cache", "result_query"), - ( + [ (None, None, None, None, ""), (None, "de_DE", None, None, "language=de_DE"), (None, "de_DE", {"voice": "henk"}, None, "language=de_DE&voice=henk"), (None, "de_DE", None, True, "cache=true&language=de_DE"), - ), + ], ) async def test_generate_media_source_id( hass: HomeAssistant, @@ -1367,11 +1367,11 @@ async def test_generate_media_source_id( ) @pytest.mark.parametrize( ("engine", "language", "options"), - ( + [ ("not-loaded-engine", None, None), (None, "unsupported-language", None), (None, None, {"option": "not-supported"}), - ), + ], ) async def test_generate_media_source_id_invalid_options( hass: HomeAssistant, diff --git a/tests/components/update/test_device_trigger.py b/tests/components/update/test_device_trigger.py index ddb98c91ea2..5a22bcec912 100644 --- a/tests/components/update/test_device_trigger.py +++ b/tests/components/update/test_device_trigger.py @@ -68,12 +68,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (er.RegistryEntryHider.INTEGRATION, None), (er.RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/utility_meter/test_config_flow.py b/tests/components/utility_meter/test_config_flow.py index 7a05564b5c4..1bf1c02385d 100644 --- a/tests/components/utility_meter/test_config_flow.py +++ b/tests/components/utility_meter/test_config_flow.py @@ -13,7 +13,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry -@pytest.mark.parametrize("platform", ("sensor",)) +@pytest.mark.parametrize("platform", ["sensor"]) async def test_config_flow(hass: HomeAssistant, platform) -> None: """Test the config flow.""" input_sensor_entity_id = "sensor.input" diff --git a/tests/components/utility_meter/test_init.py b/tests/components/utility_meter/test_init.py index d39d2fb6ed7..a89cbe352a0 100644 --- a/tests/components/utility_meter/test_init.py +++ b/tests/components/utility_meter/test_init.py @@ -62,10 +62,10 @@ async def test_restore_state(hass: HomeAssistant) -> None: @pytest.mark.parametrize( "meter", - ( + [ ["select.energy_bill"], "select.energy_bill", - ), + ], ) async def test_services(hass: HomeAssistant, meter) -> None: """Test energy sensor reset service.""" @@ -385,7 +385,7 @@ async def test_setup_missing_discovery(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("tariffs", "expected_entities"), - ( + [ ( [], ["sensor.electricity_meter"], @@ -398,7 +398,7 @@ async def test_setup_missing_discovery(hass: HomeAssistant) -> None: "select.electricity_meter", ], ), - ), + ], ) async def test_setup_and_remove_config_entry( hass: HomeAssistant, tariffs: str, expected_entities: list[str] diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 3ec1946f3de..c250a66b87a 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -61,7 +61,7 @@ def set_utc(hass: HomeAssistant): @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -86,7 +86,7 @@ def set_utc(hass: HomeAssistant): "tariffs": ["onpeak", "midpeak", "offpeak"], }, ), - ), + ], ) async def test_state(hass: HomeAssistant, yaml_config, config_entry_config) -> None: """Test utility sensor state.""" @@ -235,7 +235,7 @@ async def test_state(hass: HomeAssistant, yaml_config, config_entry_config) -> N @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -261,7 +261,7 @@ async def test_state(hass: HomeAssistant, yaml_config, config_entry_config) -> N "always_available": True, }, ), - ), + ], ) async def test_state_always_available( hass: HomeAssistant, yaml_config, config_entry_config @@ -335,7 +335,7 @@ async def test_state_always_available( @pytest.mark.parametrize( "yaml_config", - ( + [ ( { "utility_meter": { @@ -347,7 +347,7 @@ async def test_state_always_available( }, None, ), - ), + ], ) async def test_not_unique_tariffs(hass: HomeAssistant, yaml_config) -> None: """Test utility sensor state initializtion.""" @@ -356,7 +356,7 @@ async def test_not_unique_tariffs(hass: HomeAssistant, yaml_config) -> None: @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -381,7 +381,7 @@ async def test_not_unique_tariffs(hass: HomeAssistant, yaml_config) -> None: "tariffs": ["onpeak", "midpeak", "offpeak"], }, ), - ), + ], ) async def test_init(hass: HomeAssistant, yaml_config, config_entry_config) -> None: """Test utility sensor state initializtion.""" @@ -456,7 +456,7 @@ async def test_unique_id( @pytest.mark.parametrize( ("yaml_config", "entity_id", "name"), - ( + [ ( { "utility_meter": { @@ -493,7 +493,7 @@ async def test_unique_id( "sensor.energy_bill", "energy_bill", ), - ), + ], ) async def test_entity_name(hass: HomeAssistant, yaml_config, entity_id, name) -> None: """Test utility sensor state initializtion.""" @@ -511,7 +511,7 @@ async def test_entity_name(hass: HomeAssistant, yaml_config, entity_id, name) -> @pytest.mark.parametrize( ("yaml_config", "config_entry_configs"), - ( + [ ( { "utility_meter": { @@ -551,7 +551,7 @@ async def test_entity_name(hass: HomeAssistant, yaml_config, entity_id, name) -> }, ], ), - ), + ], ) async def test_device_class( hass: HomeAssistant, yaml_config, config_entry_configs @@ -604,7 +604,7 @@ async def test_device_class( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -629,7 +629,7 @@ async def test_device_class( "tariffs": ["onpeak", "midpeak", "offpeak", "superpeak"], }, ), - ), + ], ) async def test_restore_state( hass: HomeAssistant, yaml_config, config_entry_config @@ -775,7 +775,7 @@ async def test_restore_state( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -799,7 +799,7 @@ async def test_restore_state( "tariffs": [], }, ), - ), + ], ) async def test_service_reset_no_tariffs( hass: HomeAssistant, yaml_config, config_entry_config @@ -866,7 +866,7 @@ async def test_service_reset_no_tariffs( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -891,7 +891,7 @@ async def test_service_reset_no_tariffs( "tariffs": [], }, ), - ), + ], ) async def test_net_consumption( hass: HomeAssistant, yaml_config, config_entry_config @@ -938,7 +938,7 @@ async def test_net_consumption( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -962,7 +962,7 @@ async def test_net_consumption( "tariffs": [], }, ), - ), + ], ) async def test_non_net_consumption( hass: HomeAssistant, @@ -1023,7 +1023,7 @@ async def test_non_net_consumption( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -1048,7 +1048,7 @@ async def test_non_net_consumption( "tariffs": [], }, ), - ), + ], ) async def test_delta_values( hass: HomeAssistant, @@ -1135,7 +1135,7 @@ async def test_delta_values( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -1160,7 +1160,7 @@ async def test_delta_values( "tariffs": [], }, ), - ), + ], ) async def test_non_periodically_resetting( hass: HomeAssistant, yaml_config, config_entry_config @@ -1267,7 +1267,7 @@ async def test_non_periodically_resetting( @pytest.mark.parametrize( ("yaml_config", "config_entry_config"), - ( + [ ( { "utility_meter": { @@ -1293,7 +1293,7 @@ async def test_non_periodically_resetting( "tariffs": ["low", "high"], }, ), - ), + ], ) async def test_non_periodically_resetting_meter_with_tariffs( hass: HomeAssistant, yaml_config, config_entry_config diff --git a/tests/components/vacuum/test_device_action.py b/tests/components/vacuum/test_device_action.py index 6b7b56ccf18..fe5b2814a33 100644 --- a/tests/components/vacuum/test_device_action.py +++ b/tests/components/vacuum/test_device_action.py @@ -57,12 +57,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/vacuum/test_device_condition.py b/tests/components/vacuum/test_device_condition.py index 029765afd77..f8d1368a163 100644 --- a/tests/components/vacuum/test_device_condition.py +++ b/tests/components/vacuum/test_device_condition.py @@ -69,12 +69,12 @@ async def test_get_conditions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_conditions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/vacuum/test_device_trigger.py b/tests/components/vacuum/test_device_trigger.py index cb84f5697c1..831d6807b8c 100644 --- a/tests/components/vacuum/test_device_trigger.py +++ b/tests/components/vacuum/test_device_trigger.py @@ -69,12 +69,12 @@ async def test_get_triggers( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_triggers_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/water_heater/test_device_action.py b/tests/components/water_heater/test_device_action.py index 1a3645af80b..d456fa7be71 100644 --- a/tests/components/water_heater/test_device_action.py +++ b/tests/components/water_heater/test_device_action.py @@ -57,12 +57,12 @@ async def test_get_actions( @pytest.mark.parametrize( ("hidden_by", "entity_category"), - ( + [ (RegistryEntryHider.INTEGRATION, None), (RegistryEntryHider.USER, None), (None, EntityCategory.CONFIG), (None, EntityCategory.DIAGNOSTIC), - ), + ], ) async def test_get_actions_hidden_auxiliary( hass: HomeAssistant, diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index c6f7e6112db..3dcb78e3257 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -110,14 +110,14 @@ class MockWeatherEntity(WeatherEntity): @pytest.mark.parametrize( - "native_unit", (UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS) + "native_unit", [UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS] ) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfTemperature.CELSIUS, METRIC_SYSTEM), (UnitOfTemperature.FAHRENHEIT, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_temperature( hass: HomeAssistant, @@ -190,13 +190,13 @@ async def test_temperature( ) -@pytest.mark.parametrize("native_unit", (None,)) +@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfTemperature.CELSIUS, METRIC_SYSTEM), (UnitOfTemperature.FAHRENHEIT, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_temperature_no_unit( hass: HomeAssistant, @@ -259,10 +259,10 @@ async def test_temperature_no_unit( @pytest.mark.parametrize( ("state_unit", "unit_system", "native_unit"), - ( + [ (UnitOfPressure.HPA, METRIC_SYSTEM, UnitOfPressure.INHG), (UnitOfPressure.INHG, US_CUSTOMARY_SYSTEM, UnitOfPressure.INHG), - ), + ], ) async def test_pressure( hass: HomeAssistant, @@ -298,10 +298,10 @@ async def test_pressure( assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2) -@pytest.mark.parametrize("native_unit", (None,)) +@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ((UnitOfPressure.HPA, METRIC_SYSTEM), (UnitOfPressure.INHG, US_CUSTOMARY_SYSTEM)), + [(UnitOfPressure.HPA, METRIC_SYSTEM), (UnitOfPressure.INHG, US_CUSTOMARY_SYSTEM)], ) async def test_pressure_no_unit( hass: HomeAssistant, @@ -339,18 +339,18 @@ async def test_pressure_no_unit( @pytest.mark.parametrize( "native_unit", - ( + [ UnitOfSpeed.MILES_PER_HOUR, UnitOfSpeed.KILOMETERS_PER_HOUR, UnitOfSpeed.METERS_PER_SECOND, - ), + ], ) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfSpeed.KILOMETERS_PER_HOUR, METRIC_SYSTEM), (UnitOfSpeed.MILES_PER_HOUR, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_wind_speed( hass: HomeAssistant, @@ -390,18 +390,18 @@ async def test_wind_speed( @pytest.mark.parametrize( "native_unit", - ( + [ UnitOfSpeed.MILES_PER_HOUR, UnitOfSpeed.KILOMETERS_PER_HOUR, UnitOfSpeed.METERS_PER_SECOND, - ), + ], ) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfSpeed.KILOMETERS_PER_HOUR, METRIC_SYSTEM), (UnitOfSpeed.MILES_PER_HOUR, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_wind_gust_speed( hass: HomeAssistant, @@ -442,13 +442,13 @@ async def test_wind_gust_speed( ) -@pytest.mark.parametrize("native_unit", (None,)) +@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfSpeed.KILOMETERS_PER_HOUR, METRIC_SYSTEM), (UnitOfSpeed.MILES_PER_HOUR, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_wind_speed_no_unit( hass: HomeAssistant, @@ -486,13 +486,13 @@ async def test_wind_speed_no_unit( ) -@pytest.mark.parametrize("native_unit", (UnitOfLength.MILES, UnitOfLength.KILOMETERS)) +@pytest.mark.parametrize("native_unit", [UnitOfLength.MILES, UnitOfLength.KILOMETERS]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfLength.KILOMETERS, METRIC_SYSTEM), (UnitOfLength.MILES, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_visibility( hass: HomeAssistant, @@ -525,13 +525,13 @@ async def test_visibility( ) -@pytest.mark.parametrize("native_unit", (None,)) +@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfLength.KILOMETERS, METRIC_SYSTEM), (UnitOfLength.MILES, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_visibility_no_unit( hass: HomeAssistant, @@ -564,13 +564,13 @@ async def test_visibility_no_unit( ) -@pytest.mark.parametrize("native_unit", (UnitOfLength.INCHES, UnitOfLength.MILLIMETERS)) +@pytest.mark.parametrize("native_unit", [UnitOfLength.INCHES, UnitOfLength.MILLIMETERS]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfLength.MILLIMETERS, METRIC_SYSTEM), (UnitOfLength.INCHES, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_precipitation( hass: HomeAssistant, @@ -608,13 +608,13 @@ async def test_precipitation( ) -@pytest.mark.parametrize("native_unit", (None,)) +@pytest.mark.parametrize("native_unit", [None]) @pytest.mark.parametrize( ("state_unit", "unit_system"), - ( + [ (UnitOfLength.MILLIMETERS, METRIC_SYSTEM), (UnitOfLength.INCHES, US_CUSTOMARY_SYSTEM), - ), + ], ) async def test_precipitation_no_unit( hass: HomeAssistant, diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 5f10451799b..52d0e86d828 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -206,7 +206,7 @@ async def test_return_response_error(hass: HomeAssistant, websocket_client) -> N assert msg["error"]["code"] == "unknown_error" -@pytest.mark.parametrize("command", ("call_service", "call_service_action")) +@pytest.mark.parametrize("command", ["call_service", "call_service_action"]) async def test_call_service_blocking( hass: HomeAssistant, websocket_client: MockHAClientWebSocket, command ) -> None: @@ -2512,7 +2512,7 @@ async def test_integration_setup_info( @pytest.mark.parametrize( ("key", "config"), - ( + [ ("trigger", {"platform": "event", "event_type": "hello"}), ("trigger", [{"platform": "event", "event_type": "hello"}]), ( @@ -2525,7 +2525,7 @@ async def test_integration_setup_info( ), ("action", {"service": "domain_test.test_service"}), ("action", [{"service": "domain_test.test_service"}]), - ), + ], ) async def test_validate_config_works( websocket_client: MockHAClientWebSocket, key, config @@ -2542,7 +2542,7 @@ async def test_validate_config_works( @pytest.mark.parametrize( ("key", "config", "error"), - ( + [ ( "trigger", {"platform": "non_existing", "event_type": "hello"}, @@ -2566,7 +2566,7 @@ async def test_validate_config_works( {"non_existing": "domain_test.test_service"}, "Unable to determine action @ data[0]", ), - ), + ], ) async def test_validate_config_invalid( websocket_client: MockHAClientWebSocket, key, config, error diff --git a/tests/components/whois/test_sensor.py b/tests/components/whois/test_sensor.py index b9619d6cfe0..d58cc342745 100644 --- a/tests/components/whois/test_sensor.py +++ b/tests/components/whois/test_sensor.py @@ -22,7 +22,7 @@ pytestmark = [ @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.home_assistant_io_admin", "sensor.home_assistant_io_created", "sensor.home_assistant_io_days_until_expiration", @@ -32,7 +32,7 @@ pytestmark = [ "sensor.home_assistant_io_registrant", "sensor.home_assistant_io_registrar", "sensor.home_assistant_io_reseller", - ), + ], ) async def test_whois_sensors( hass: HomeAssistant, @@ -67,13 +67,13 @@ async def test_whois_sensors_missing_some_attrs( @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.home_assistant_io_admin", "sensor.home_assistant_io_owner", "sensor.home_assistant_io_registrant", "sensor.home_assistant_io_registrar", "sensor.home_assistant_io_reseller", - ), + ], ) async def test_disabled_by_default_sensors( hass: HomeAssistant, entity_id: str, entity_registry: er.EntityRegistry @@ -88,7 +88,7 @@ async def test_disabled_by_default_sensors( @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.home_assistant_io_admin", "sensor.home_assistant_io_created", "sensor.home_assistant_io_days_until_expiration", @@ -98,7 +98,7 @@ async def test_disabled_by_default_sensors( "sensor.home_assistant_io_registrant", "sensor.home_assistant_io_registrar", "sensor.home_assistant_io_reseller", - ), + ], ) async def test_no_data( hass: HomeAssistant, mock_whois: MagicMock, entity_id: str diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index c7a9d5cde4b..c8bebe854eb 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -495,15 +495,15 @@ async def test_cloud_disconnect( @pytest.mark.parametrize( ("body", "expected_code"), [ - [{"userid": 0, "appli": NotificationCategory.WEIGHT.value}, 0], # Success - [{"userid": None, "appli": 1}, 0], # Success, we ignore the user_id. - [{}, 12], # No request body. - [{"userid": "GG"}, 20], # appli not provided. - [{"userid": 0}, 20], # appli not provided. - [ + ({"userid": 0, "appli": NotificationCategory.WEIGHT.value}, 0), # Success + ({"userid": None, "appli": 1}, 0), # Success, we ignore the user_id. + ({}, 12), # No request body. + ({"userid": "GG"}, 20), # appli not provided. + ({"userid": 0}, 20), # appli not provided. + ( {"userid": 11, "appli": NotificationCategory.WEIGHT.value}, 0, - ], # Success, we ignore the user_id + ), # Success, we ignore the user_id ], ) async def test_webhook_post( diff --git a/tests/components/wled/test_sensor.py b/tests/components/wled/test_sensor.py index d72e6581549..319622e7cb3 100644 --- a/tests/components/wled/test_sensor.py +++ b/tests/components/wled/test_sensor.py @@ -123,14 +123,14 @@ async def test_sensors( @pytest.mark.parametrize( "entity_id", - ( + [ "sensor.wled_rgb_light_uptime", "sensor.wled_rgb_light_free_memory", "sensor.wled_rgb_light_wi_fi_signal", "sensor.wled_rgb_light_wi_fi_rssi", "sensor.wled_rgb_light_wi_fi_channel", "sensor.wled_rgb_light_wi_fi_bssid", - ), + ], ) @pytest.mark.usefixtures("init_integration") async def test_disabled_by_default_sensors( diff --git a/tests/components/zeversolar/test_config_flow.py b/tests/components/zeversolar/test_config_flow.py index 57b7fefefb4..5e841c9b313 100644 --- a/tests/components/zeversolar/test_config_flow.py +++ b/tests/components/zeversolar/test_config_flow.py @@ -31,7 +31,7 @@ async def test_form(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("side_effect", "errors"), - ( + [ ( ZeverSolarHTTPNotFound, {"base": "invalid_host"}, @@ -48,7 +48,7 @@ async def test_form(hass: HomeAssistant) -> None: RuntimeError, {"base": "unknown"}, ), - ), + ], ) async def test_form_errors( hass: HomeAssistant, diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index 744bbcb9f12..16563f62e06 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -523,14 +523,14 @@ async def test_climate_hvac_action_pi_demand( @pytest.mark.parametrize( ("sys_mode", "hvac_mode"), - ( + [ (Thermostat.SystemMode.Auto, HVACMode.HEAT_COOL), (Thermostat.SystemMode.Cool, HVACMode.COOL), (Thermostat.SystemMode.Heat, HVACMode.HEAT), (Thermostat.SystemMode.Pre_cooling, HVACMode.COOL), (Thermostat.SystemMode.Fan_only, HVACMode.FAN_ONLY), (Thermostat.SystemMode.Dry, HVACMode.DRY), - ), + ], ) async def test_hvac_mode( hass: HomeAssistant, device_climate, sys_mode, hvac_mode @@ -560,7 +560,7 @@ async def test_hvac_mode( @pytest.mark.parametrize( ("seq_of_op", "modes"), - ( + [ (0xFF, {HVACMode.OFF}), (0x00, {HVACMode.OFF, HVACMode.COOL}), (0x01, {HVACMode.OFF, HVACMode.COOL}), @@ -568,7 +568,7 @@ async def test_hvac_mode( (0x03, {HVACMode.OFF, HVACMode.HEAT}), (0x04, {HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL}), (0x05, {HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL}), - ), + ], ) async def test_hvac_modes( hass: HomeAssistant, device_climate_mock, seq_of_op, modes @@ -585,12 +585,12 @@ async def test_hvac_modes( @pytest.mark.parametrize( ("sys_mode", "preset", "target_temp"), - ( + [ (Thermostat.SystemMode.Heat, None, 22), (Thermostat.SystemMode.Heat, PRESET_AWAY, 16), (Thermostat.SystemMode.Cool, None, 25), (Thermostat.SystemMode.Cool, PRESET_AWAY, 27), - ), + ], ) async def test_target_temperature( hass: HomeAssistant, @@ -628,11 +628,11 @@ async def test_target_temperature( @pytest.mark.parametrize( ("preset", "unoccupied", "target_temp"), - ( + [ (None, 1800, 17), (PRESET_AWAY, 1800, 18), (PRESET_AWAY, None, None), - ), + ], ) async def test_target_temperature_high( hass: HomeAssistant, device_climate_mock, preset, unoccupied, target_temp @@ -664,11 +664,11 @@ async def test_target_temperature_high( @pytest.mark.parametrize( ("preset", "unoccupied", "target_temp"), - ( + [ (None, 1600, 21), (PRESET_AWAY, 1600, 16), (PRESET_AWAY, None, None), - ), + ], ) async def test_target_temperature_low( hass: HomeAssistant, device_climate_mock, preset, unoccupied, target_temp @@ -700,14 +700,14 @@ async def test_target_temperature_low( @pytest.mark.parametrize( ("hvac_mode", "sys_mode"), - ( + [ (HVACMode.AUTO, None), (HVACMode.COOL, Thermostat.SystemMode.Cool), (HVACMode.DRY, None), (HVACMode.FAN_ONLY, None), (HVACMode.HEAT, Thermostat.SystemMode.Heat), (HVACMode.HEAT_COOL, Thermostat.SystemMode.Auto), - ), + ], ) async def test_set_hvac_mode( hass: HomeAssistant, device_climate, hvac_mode, sys_mode diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 9ec3cc9f497..d1c4c6cb507 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -999,7 +999,7 @@ async def test_hardware_already_setup(hass: HomeAssistant) -> None: @pytest.mark.parametrize( - "data", (None, {}, {"radio_type": "best_radio"}, {"radio_type": "efr32"}) + "data", [None, {}, {"radio_type": "best_radio"}, {"radio_type": "efr32"}] ) async def test_hardware_invalid_data(hass: HomeAssistant, data) -> None: """Test onboarding flow -- invalid data.""" diff --git a/tests/components/zha/test_device.py b/tests/components/zha/test_device.py index 94994250167..48eecdd87d4 100644 --- a/tests/components/zha/test_device.py +++ b/tests/components/zha/test_device.py @@ -267,7 +267,7 @@ async def test_ota_sw_version(hass: HomeAssistant, ota_zha_device) -> None: @pytest.mark.parametrize( ("device", "last_seen_delta", "is_available"), - ( + [ ("zigpy_device", 0, True), ( "zigpy_device", @@ -305,7 +305,7 @@ async def test_ota_sw_version(hass: HomeAssistant, ota_zha_device) -> None: CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY + 2, False, ), - ), + ], ) async def test_device_restore_availability( hass: HomeAssistant, diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 5da333afdcd..5ed7c7bfeed 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -464,13 +464,13 @@ async def test_zha_group_fan_entity_failure_state( @pytest.mark.parametrize( ("plug_read", "expected_state", "expected_percentage"), - ( + [ (None, STATE_OFF, None), ({"fan_mode": 0}, STATE_OFF, 0), ({"fan_mode": 1}, STATE_ON, 33), ({"fan_mode": 2}, STATE_ON, 66), ({"fan_mode": 3}, STATE_ON, 100), - ), + ], ) async def test_fan_init( hass: HomeAssistant, @@ -645,7 +645,7 @@ async def test_fan_ikea( "ikea_expected_percentage", "ikea_preset_mode", ), - ( + [ (None, STATE_OFF, None, None), ({"fan_mode": 0}, STATE_OFF, 0, None), ({"fan_mode": 1}, STATE_ON, 10, PRESET_MODE_AUTO), @@ -658,7 +658,7 @@ async def test_fan_ikea( ({"fan_mode": 40}, STATE_ON, 80, "Speed 4"), ({"fan_mode": 45}, STATE_ON, 90, "Speed 4.5"), ({"fan_mode": 50}, STATE_ON, 100, "Speed 5"), - ), + ], ) async def test_fan_ikea_init( hass: HomeAssistant, @@ -828,7 +828,7 @@ async def test_fan_kof( @pytest.mark.parametrize( ("plug_read", "expected_state", "expected_percentage", "expected_preset"), - ( + [ (None, STATE_OFF, None, None), ({"fan_mode": 0}, STATE_OFF, 0, None), ({"fan_mode": 1}, STATE_ON, 25, None), @@ -836,7 +836,7 @@ async def test_fan_kof( ({"fan_mode": 3}, STATE_ON, 75, None), ({"fan_mode": 4}, STATE_ON, 100, None), ({"fan_mode": 6}, STATE_ON, None, PRESET_MODE_SMART), - ), + ], ) async def test_fan_kof_init( hass: HomeAssistant, diff --git a/tests/components/zha/test_init.py b/tests/components/zha/test_init.py index f516dca7094..99d6a78924b 100644 --- a/tests/components/zha/test_init.py +++ b/tests/components/zha/test_init.py @@ -52,7 +52,7 @@ def config_entry_v1(hass): ) -@pytest.mark.parametrize("config", ({}, {DOMAIN: {}})) +@pytest.mark.parametrize("config", [{}, {DOMAIN: {}}]) @patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) async def test_migration_from_v1_no_baudrate( hass: HomeAssistant, config_entry_v1, config @@ -106,12 +106,12 @@ async def test_migration_from_v1_wrong_baudrate( ) @pytest.mark.parametrize( "zha_config", - ( + [ {}, {CONF_USB_PATH: "str"}, {CONF_RADIO_TYPE: "ezsp"}, {CONF_RADIO_TYPE: "ezsp", CONF_USB_PATH: "str"}, - ), + ], ) async def test_config_depreciation(hass: HomeAssistant, zha_config) -> None: """Test config option depreciation.""" diff --git a/tests/components/zha/test_number.py b/tests/components/zha/test_number.py index 4a6482183ab..a9fb3dd9509 100644 --- a/tests/components/zha/test_number.py +++ b/tests/components/zha/test_number.py @@ -192,14 +192,14 @@ async def test_number( @pytest.mark.parametrize( ("attr", "initial_value", "new_value"), - ( + [ ("on_off_transition_time", 20, 5), ("on_level", 255, 50), ("on_transition_time", 5, 1), ("off_transition_time", 5, 1), ("default_move_rate", 1, 5), ("start_up_current_level", 254, 125), - ), + ], ) async def test_level_control_number( hass: HomeAssistant, @@ -324,7 +324,7 @@ async def test_level_control_number( @pytest.mark.parametrize( ("attr", "initial_value", "new_value"), - (("start_up_color_temperature", 500, 350),), + [("start_up_color_temperature", 500, 350)], ) async def test_color_number( hass: HomeAssistant, diff --git a/tests/components/zha/test_registries.py b/tests/components/zha/test_registries.py index 317f47f1eea..29020aa4313 100644 --- a/tests/components/zha/test_registries.py +++ b/tests/components/zha/test_registries.py @@ -395,14 +395,14 @@ def entity_registry(): @pytest.mark.parametrize( ("manufacturer", "model", "quirk_id", "match_name"), - ( + [ ("random manufacturer", "random model", "random.class", "OnOff"), ("random manufacturer", MODEL, "random.class", "OnOffModel"), (MANUFACTURER, "random model", "random.class", "OnOffManufacturer"), ("random manufacturer", "random model", QUIRK_ID, "OnOffQuirk"), (MANUFACTURER, MODEL, "random.class", "OnOffModelManufacturer"), (MANUFACTURER, "some model", "random.class", "OnOffMultimodel"), - ), + ], ) def test_weighted_match( cluster_handler, diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index b97ca1b8927..f656ddea71c 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -378,7 +378,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id): "unsupported_attrs", "initial_sensor_state", ), - ( + [ ( measurement.RelativeHumidity.cluster_id, "humidity", @@ -563,7 +563,7 @@ async def async_test_pi_heating_demand(hass, cluster, entity_id): None, STATE_UNKNOWN, ), - ), + ], ) async def test_sensor( hass: HomeAssistant, @@ -808,7 +808,7 @@ async def test_electrical_measurement_init( @pytest.mark.parametrize( ("cluster_id", "unsupported_attributes", "entity_ids", "missing_entity_ids"), - ( + [ ( homeautomation.ElectricalMeasurement.cluster_id, {"apparent_power", "rms_voltage", "rms_current"}, @@ -877,7 +877,7 @@ async def test_electrical_measurement_init( }, {}, ), - ), + ], ) async def test_unsupported_attributes_sensor( hass: HomeAssistant, @@ -919,7 +919,7 @@ async def test_unsupported_attributes_sensor( @pytest.mark.parametrize( ("raw_uom", "raw_value", "expected_state", "expected_uom"), - ( + [ ( 1, 12320, @@ -1004,7 +1004,7 @@ async def test_unsupported_attributes_sensor( "5.01", UnitOfVolume.LITERS, ), - ), + ], ) async def test_se_summation_uom( hass: HomeAssistant, @@ -1052,7 +1052,7 @@ async def test_se_summation_uom( @pytest.mark.parametrize( ("raw_measurement_type", "expected_type"), - ( + [ (1, "ACTIVE_MEASUREMENT"), (8, "PHASE_A_MEASUREMENT"), (9, "ACTIVE_MEASUREMENT, PHASE_A_MEASUREMENT"), @@ -1063,7 +1063,7 @@ async def test_se_summation_uom( " PHASE_A_MEASUREMENT" ), ), - ), + ], ) async def test_elec_measurement_sensor_type( hass: HomeAssistant, @@ -1127,7 +1127,7 @@ async def test_elec_measurement_sensor_polling( @pytest.mark.parametrize( "supported_attributes", - ( + [ set(), { "active_power", @@ -1152,7 +1152,7 @@ async def test_elec_measurement_sensor_polling( "rms_voltage", "rms_voltage_max", }, - ), + ], ) async def test_elec_measurement_skip_unsupported_attribute( hass: HomeAssistant, diff --git a/tests/components/zha/test_websocket_api.py b/tests/components/zha/test_websocket_api.py index 73dc63258b6..9cd475a7bf8 100644 --- a/tests/components/zha/test_websocket_api.py +++ b/tests/components/zha/test_websocket_api.py @@ -499,7 +499,7 @@ async def app_controller( @pytest.mark.parametrize( ("params", "duration", "node"), - ( + [ ({}, 60, None), ({ATTR_DURATION: 30}, 30, None), ( @@ -512,7 +512,7 @@ async def app_controller( 60, zigpy.types.EUI64.convert("aa:bb:cc:dd:aa:bb:cc:d1"), ), - ), + ], ) async def test_permit_ha12( hass: HomeAssistant, @@ -743,7 +743,7 @@ async def test_ws_permit_with_install_code_fail( @pytest.mark.parametrize( ("params", "duration", "node"), - ( + [ ({}, 60, None), ({ATTR_DURATION: 30}, 30, None), ( @@ -756,7 +756,7 @@ async def test_ws_permit_with_install_code_fail( 60, zigpy.types.EUI64.convert("aa:bb:cc:dd:aa:bb:cc:d1"), ), - ), + ], ) async def test_ws_permit_ha12( app_controller: ControllerApplication, zha_client, params, duration, node diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index b2e5c82e88a..c10e82627eb 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1397,7 +1397,7 @@ def test_key_value_schemas_with_default() -> None: @pytest.mark.parametrize( ("config", "error"), - ( + [ ({"delay": "{{ invalid"}, "should be format 'HH:MM'"), ({"wait_template": "{{ invalid"}, "invalid template"), ({"condition": "invalid"}, "Unexpected value for condition: 'invalid'"), @@ -1432,7 +1432,7 @@ def test_key_value_schemas_with_default() -> None: }, "not allowed to add a response to an error stop action", ), - ), + ], ) def test_script(caplog: pytest.LogCaptureFixture, config: dict, error: str) -> None: """Test script validation is user friendly.""" diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 7f7398ec3de..4438ba93861 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -2304,7 +2304,7 @@ async def test_entries_for_label( "placeholders", "expected_device_name", ), - ( + [ (None, None, None, "Device Bla"), ( "test_device", @@ -2334,7 +2334,7 @@ async def test_entries_for_label( {"placeholder": "special"}, "English dev special", ), - ), + ], ) async def test_device_name_translation_placeholders( hass: HomeAssistant, @@ -2381,7 +2381,7 @@ async def test_device_name_translation_placeholders( "expectation", "expected_error", ), - ( + [ ( "test_device", { @@ -2426,7 +2426,7 @@ async def test_device_name_translation_placeholders( "not match the name '{placeholder} English dev'" ), ), - ), + ], ) async def test_device_name_translation_placeholders_errors( hass: HomeAssistant, diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 2d4127b2763..dab986a7284 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -933,10 +933,10 @@ async def test_entity_category_property(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("value", "expected"), - ( + [ ("config", entity.EntityCategory.CONFIG), ("diagnostic", entity.EntityCategory.DIAGNOSTIC), - ), + ], ) def test_entity_category_schema(value, expected) -> None: """Test entity category schema.""" @@ -946,7 +946,7 @@ def test_entity_category_schema(value, expected) -> None: assert isinstance(result, entity.EntityCategory) -@pytest.mark.parametrize("value", (None, "non_existing")) +@pytest.mark.parametrize("value", [None, "non_existing"]) def test_entity_category_schema_error(value) -> None: """Test entity category schema.""" schema = vol.Schema(entity.ENTITY_CATEGORIES_SCHEMA) @@ -1007,14 +1007,14 @@ async def _test_friendly_name( "device_name", "expected_friendly_name", ), - ( + [ (False, "Entity Blu", "Device Bla", "Entity Blu"), (False, None, "Device Bla", None), (True, "Entity Blu", "Device Bla", "Device Bla Entity Blu"), (True, None, "Device Bla", "Device Bla"), (True, "Entity Blu", UNDEFINED, "Entity Blu"), (True, "Entity Blu", None, "Mock Title Entity Blu"), - ), + ], ) async def test_friendly_name_attr( hass: HomeAssistant, @@ -1044,14 +1044,14 @@ async def test_friendly_name_attr( @pytest.mark.parametrize( ("has_entity_name", "entity_name", "expected_friendly_name"), - ( + [ (False, "Entity Blu", "Entity Blu"), (False, None, None), (False, UNDEFINED, None), (True, "Entity Blu", "Device Bla Entity Blu"), (True, None, "Device Bla"), (True, UNDEFINED, "Device Bla None"), - ), + ], ) async def test_friendly_name_description( hass: HomeAssistant, @@ -1081,14 +1081,14 @@ async def test_friendly_name_description( @pytest.mark.parametrize( ("has_entity_name", "entity_name", "expected_friendly_name"), - ( + [ (False, "Entity Blu", "Entity Blu"), (False, None, None), (False, UNDEFINED, None), (True, "Entity Blu", "Device Bla Entity Blu"), (True, None, "Device Bla"), (True, UNDEFINED, "Device Bla English cls"), - ), + ], ) async def test_friendly_name_description_device_class_name( hass: HomeAssistant, @@ -1150,7 +1150,7 @@ async def test_friendly_name_description_device_class_name( "placeholders", "expected_friendly_name", ), - ( + [ (False, None, None, None, "Entity Blu"), (True, None, None, None, "Device Bla Entity Blu"), ( @@ -1186,7 +1186,7 @@ async def test_friendly_name_description_device_class_name( {"placeholder": "special"}, "Device Bla English ent special", ), - ), + ], ) async def test_entity_name_translation_placeholders( hass: HomeAssistant, @@ -1239,7 +1239,7 @@ async def test_entity_name_translation_placeholders( "release_channel", "expected_error", ), - ( + [ ( "test_entity", { @@ -1279,7 +1279,7 @@ async def test_entity_name_translation_placeholders( "not match the name '{placeholder} English ent'" ), ), - ), + ], ) async def test_entity_name_translation_placeholder_errors( hass: HomeAssistant, @@ -1341,14 +1341,14 @@ async def test_entity_name_translation_placeholder_errors( @pytest.mark.parametrize( ("has_entity_name", "entity_name", "expected_friendly_name"), - ( + [ (False, "Entity Blu", "Entity Blu"), (False, None, None), (False, UNDEFINED, None), (True, "Entity Blu", "Device Bla Entity Blu"), (True, None, "Device Bla"), (True, UNDEFINED, "Device Bla None"), - ), + ], ) async def test_friendly_name_property( hass: HomeAssistant, @@ -1377,7 +1377,7 @@ async def test_friendly_name_property( @pytest.mark.parametrize( ("has_entity_name", "entity_name", "expected_friendly_name"), - ( + [ (False, "Entity Blu", "Entity Blu"), (False, None, None), (False, UNDEFINED, None), @@ -1385,7 +1385,7 @@ async def test_friendly_name_property( (True, None, "Device Bla"), # Won't use the device class name because the entity overrides the name property (True, UNDEFINED, "Device Bla None"), - ), + ], ) async def test_friendly_name_property_device_class_name( hass: HomeAssistant, @@ -1438,10 +1438,10 @@ async def test_friendly_name_property_device_class_name( @pytest.mark.parametrize( ("has_entity_name", "expected_friendly_name"), - ( + [ (False, None), (True, "Device Bla English cls"), - ), + ], ) async def test_friendly_name_device_class_name( hass: HomeAssistant, @@ -1497,7 +1497,7 @@ async def test_friendly_name_device_class_name( "expected_friendly_name2", "expected_friendly_name3", ), - ( + [ ( "Entity Blu", "Device Bla Entity Blu", @@ -1510,7 +1510,7 @@ async def test_friendly_name_device_class_name( "Device Bla2", "New Device", ), - ), + ], ) async def test_friendly_name_updated( hass: HomeAssistant, diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 2486626f82f..efea66c8d77 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1711,7 +1711,7 @@ async def test_register_entity_service_limited_to_matching_platforms( } -@pytest.mark.parametrize("update_before_add", (True, False)) +@pytest.mark.parametrize("update_before_add", [True, False]) async def test_invalid_entity_id( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, update_before_add: bool ) -> None: @@ -1738,7 +1738,7 @@ class MockBlockingEntity(MockEntity): await asyncio.sleep(1000) -@pytest.mark.parametrize("update_before_add", (True, False)) +@pytest.mark.parametrize("update_before_add", [True, False]) async def test_setup_entry_with_entities_that_block_forever( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, @@ -1784,7 +1784,7 @@ class MockCancellingEntity(MockEntity): raise asyncio.CancelledError -@pytest.mark.parametrize("update_before_add", (True, False)) +@pytest.mark.parametrize("update_before_add", [True, False]) async def test_cancellation_is_not_blocked( hass: HomeAssistant, update_before_add: bool, @@ -1813,7 +1813,7 @@ async def test_cancellation_is_not_blocked( assert full_name not in hass.config.components -@pytest.mark.parametrize("update_before_add", (True, False)) +@pytest.mark.parametrize("update_before_add", [True, False]) async def test_two_platforms_add_same_entity( hass: HomeAssistant, update_before_add: bool ) -> None: @@ -1868,14 +1868,14 @@ class SlowEntity(MockEntity): @pytest.mark.parametrize( ("has_entity_name", "entity_name", "expected_entity_id"), - ( + [ (False, "Entity Blu", "test_domain.entity_blu"), (False, None, "test_domain.test_qwer"), # Set to _ (True, "Entity Blu", "test_domain.device_bla_entity_blu"), (True, None, "test_domain.device_bla"), - ), + ], ) -@pytest.mark.parametrize("update_before_add", (True, False)) +@pytest.mark.parametrize("update_before_add", [True, False]) async def test_entity_name_influences_entity_id( hass: HomeAssistant, entity_registry: er.EntityRegistry, @@ -1921,15 +1921,15 @@ async def test_entity_name_influences_entity_id( @pytest.mark.parametrize( ("language", "has_entity_name", "expected_entity_id"), - ( + [ ("en", False, "test_domain.test_qwer"), # Set to _ ("en", True, "test_domain.device_bla_english_name"), ("sv", True, "test_domain.device_bla_swedish_name"), # Chinese uses english for entity_id ("cn", True, "test_domain.device_bla_english_name"), - ), + ], ) -@pytest.mark.parametrize("update_before_add", (True, False)) +@pytest.mark.parametrize("update_before_add", [True, False]) async def test_translated_entity_name_influences_entity_id( hass: HomeAssistant, entity_registry: er.EntityRegistry, @@ -1998,7 +1998,7 @@ async def test_translated_entity_name_influences_entity_id( @pytest.mark.parametrize( ("language", "has_entity_name", "device_class", "expected_entity_id"), - ( + [ ("en", False, None, "test_domain.test_qwer"), # Set to _ ( "en", @@ -2010,7 +2010,7 @@ async def test_translated_entity_name_influences_entity_id( ("sv", True, "test_class", "test_domain.device_bla_swedish_cls"), # Chinese uses english for entity_id ("cn", True, "test_class", "test_domain.device_bla_english_cls"), - ), + ], ) async def test_translated_device_class_name_influences_entity_id( hass: HomeAssistant, diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index c9f25e796bb..2eeae2ff3e0 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -190,7 +190,7 @@ async def _process_platform_coro( @pytest.mark.no_fail_on_log_exception @pytest.mark.parametrize( - "process_platform", (_process_platform_callback, _process_platform_coro) + "process_platform", [_process_platform_callback, _process_platform_coro] ) async def test_process_integration_platforms_non_compliant( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, process_platform: Callable diff --git a/tests/helpers/test_json.py b/tests/helpers/test_json.py index 192db706974..57269963164 100644 --- a/tests/helpers/test_json.py +++ b/tests/helpers/test_json.py @@ -34,7 +34,7 @@ TEST_JSON_A = {"a": 1, "B": "two"} TEST_JSON_B = {"a": "one", "B": 2} -@pytest.mark.parametrize("encoder", (DefaultHASSJSONEncoder, ExtendedJSONEncoder)) +@pytest.mark.parametrize("encoder", [DefaultHASSJSONEncoder, ExtendedJSONEncoder]) def test_json_encoder(hass: HomeAssistant, encoder: type[json.JSONEncoder]) -> None: """Test the JSON encoders.""" ha_json_enc = encoder() diff --git a/tests/helpers/test_registry.py b/tests/helpers/test_registry.py index 2cfa3be26c7..46b04b05fe3 100644 --- a/tests/helpers/test_registry.py +++ b/tests/helpers/test_registry.py @@ -29,12 +29,12 @@ class SampleRegistry(BaseRegistry): @pytest.mark.parametrize( "long_delay_state", - ( + [ CoreState.not_running, CoreState.starting, CoreState.stopped, CoreState.final_write, - ), + ], ) async def test_async_schedule_save( hass: HomeAssistant, diff --git a/tests/helpers/test_schema_config_entry_flow.py b/tests/helpers/test_schema_config_entry_flow.py index a23b0311b86..4db56a91c11 100644 --- a/tests/helpers/test_schema_config_entry_flow.py +++ b/tests/helpers/test_schema_config_entry_flow.py @@ -104,7 +104,7 @@ async def test_name(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> assert wrapped_entity_config_entry_title(hass, entry.id) == "Custom Name" -@pytest.mark.parametrize("marker", (vol.Required, vol.Optional)) +@pytest.mark.parametrize("marker", [vol.Required, vol.Optional]) async def test_config_flow_advanced_option( hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker ) -> None: @@ -199,7 +199,7 @@ async def test_config_flow_advanced_option( assert isinstance(option, str) -@pytest.mark.parametrize("marker", (vol.Required, vol.Optional)) +@pytest.mark.parametrize("marker", [vol.Required, vol.Optional]) async def test_options_flow_advanced_option( hass: HomeAssistant, manager: data_entry_flow.FlowManager, marker ) -> None: diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 6fed4863dfe..aab84592725 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -5020,10 +5020,10 @@ async def test_stop_action( @pytest.mark.parametrize( ("error", "error_dict", "logmsg", "script_execution"), - ( + [ (True, {"error": "In the name of love"}, "Error", "aborted"), (False, {}, "Stop", "finished"), - ), + ], ) async def test_stop_action_subscript( hass: HomeAssistant, diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index a01ee4ef69e..d04882d6802 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -13,10 +13,10 @@ FAKE_UUID = "a266a680b608c32770e6c45bfe6b8411" @pytest.mark.parametrize( "schema", - ( + [ {"device": None}, {"entity": None}, - ), + ], ) def test_valid_base_schema(schema) -> None: """Test base schema validation.""" @@ -25,14 +25,14 @@ def test_valid_base_schema(schema) -> None: @pytest.mark.parametrize( "schema", - ( + [ None, "not_a_dict", {}, {"non_existing": {}}, # Two keys {"device": {}, "entity": {}}, - ), + ], ) def test_invalid_base_schema(schema) -> None: """Test base schema validation.""" @@ -79,7 +79,7 @@ def _test_selector( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ (None, ("abc123",), (None,)), ({}, ("abc123",), (None,)), ({"integration": "zha"}, ("abc123",), (None,)), @@ -147,7 +147,7 @@ def _test_selector( ("abc123",), (None,), ), - ), + ], ) def test_device_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test device selector.""" @@ -156,7 +156,7 @@ def test_device_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ({}, ("sensor.abc123", FAKE_UUID), (None, "abc123")), ({"integration": "zha"}, ("sensor.abc123", FAKE_UUID), (None, "abc123")), ({"domain": "light"}, ("light.abc123", FAKE_UUID), (None, "sensor.abc123")), @@ -266,7 +266,7 @@ def test_device_selector_schema(schema, valid_selections, invalid_selections) -> ("light.abc123", "blah.blah", FAKE_UUID), (None,), ), - ), + ], ) def test_entity_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test entity selector.""" @@ -275,7 +275,7 @@ def test_entity_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( "schema", - ( + [ # Feature should be string specifying an enum member, not an int {"filter": [{"supported_features": [1]}]}, # Invalid feature @@ -284,7 +284,7 @@ def test_entity_selector_schema(schema, valid_selections, invalid_selections) -> {"filter": [{"supported_features": ["blah.FooEntityFeature.blah"]}]}, # Unknown feature enum member {"filter": [{"supported_features": ["light.LightEntityFeature.blah"]}]}, - ), + ], ) def test_entity_selector_schema_error(schema) -> None: """Test number selector.""" @@ -294,7 +294,7 @@ def test_entity_selector_schema_error(schema) -> None: @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ({}, ("abc123",), (None,)), ({"entity": {}}, ("abc123",), (None,)), ({"entity": {"domain": "light"}}, ("abc123",), (None,)), @@ -352,7 +352,7 @@ def test_entity_selector_schema_error(schema) -> None: ((["abc123", "def456"],)), (None, "abc123", ["abc123", None]), ), - ), + ], ) def test_area_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test area selector.""" @@ -361,13 +361,13 @@ def test_area_selector_schema(schema, valid_selections, invalid_selections) -> N @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("23ouih2iu23ou2", "2j4hp3uy4p87wyrpiuhk34"), (None, True, 1), ), - ), + ], ) def test_assist_pipeline_selector_schema( schema, valid_selections, invalid_selections @@ -378,7 +378,7 @@ def test_assist_pipeline_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"min": 10, "max": 50}, ( @@ -398,7 +398,7 @@ def test_assist_pipeline_selector_schema( ({"mode": "box"}, (10,), ()), ({"mode": "box", "step": "any"}, (), ()), ({"mode": "slider", "min": 0, "max": 1, "step": "any"}, (), ()), - ), + ], ) def test_number_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test number selector.""" @@ -407,10 +407,10 @@ def test_number_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( "schema", - ( + [ {}, # Must have mandatory fields {"mode": "slider"}, # Must have min+max in slider mode - ), + ], ) def test_number_selector_schema_error(schema) -> None: """Test number selector.""" @@ -420,7 +420,7 @@ def test_number_selector_schema_error(schema) -> None: @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, ("abc123",), (None,)),), + [({}, ("abc123",), (None,))], ) def test_addon_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test add-on selector.""" @@ -429,7 +429,7 @@ def test_addon_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, ("abc123", "/backup"), (None, "abc@123", "abc 123", "")),), + [({}, ("abc123", "/backup"), (None, "abc@123", "abc 123", ""))], ) def test_backup_location_selector_schema( schema, valid_selections, invalid_selections @@ -440,7 +440,7 @@ def test_backup_location_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, (1, "one", None), ()),), # Everything can be coerced to bool + [({}, (1, "one", None), ())], # Everything can be coerced to bool ) def test_boolean_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test boolean selector.""" @@ -455,7 +455,7 @@ def test_boolean_selector_schema(schema, valid_selections, invalid_selections) - @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("6b68b250388cbe0d620c92dd3acc93ec", "76f2e8f9a6491a1b580b3a8967c27ddd"), @@ -466,7 +466,7 @@ def test_boolean_selector_schema(schema, valid_selections, invalid_selections) - ("6b68b250388cbe0d620c92dd3acc93ec", "76f2e8f9a6491a1b580b3a8967c27ddd"), (None, True, 1), ), - ), + ], ) def test_config_entry_selector_schema( schema, valid_selections, invalid_selections @@ -477,7 +477,7 @@ def test_config_entry_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("NL", "DE"), @@ -488,7 +488,7 @@ def test_config_entry_selector_schema( ("NL", "DE"), (None, True, 1, "sv", "en"), ), - ), + ], ) def test_country_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test country selector.""" @@ -497,7 +497,7 @@ def test_country_selector_schema(schema, valid_selections, invalid_selections) - @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, ("00:00:00",), ("blah", None)),), + [({}, ("00:00:00",), ("blah", None))], ) def test_time_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test time selector.""" @@ -506,13 +506,13 @@ def test_time_selector_schema(schema, valid_selections, invalid_selections) -> N @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"entity_id": "sensor.abc"}, ("on", "armed"), (None, True, 1), ), - ), + ], ) def test_state_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test state selector.""" @@ -521,7 +521,7 @@ def test_state_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ({}, ({"entity_id": ["sensor.abc123"]},), ("abc123", None)), ({"entity": {}}, (), ()), ({"entity": {"domain": "light"}}, (), ()), @@ -566,7 +566,7 @@ def test_state_selector_schema(schema, valid_selections, invalid_selections) -> (), (), ), - ), + ], ) def test_target_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test target selector.""" @@ -575,7 +575,7 @@ def test_target_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, ("abc123",), ()),), + [({}, ("abc123",), ())], ) def test_action_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test action sequence selector.""" @@ -584,7 +584,7 @@ def test_action_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, ("abc123",), ()),), + [({}, ("abc123",), ())], ) def test_object_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test object selector.""" @@ -593,7 +593,7 @@ def test_object_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ({}, ("abc123",), (None,)), ({"multiline": True}, (), ()), ({"multiline": False, "type": "email"}, (), ()), @@ -603,7 +603,7 @@ def test_object_selector_schema(schema, valid_selections, invalid_selections) -> (["abc123", "def456"],), ("abc123", None, ["abc123", None]), ), - ), + ], ) def test_text_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test text selector.""" @@ -612,7 +612,7 @@ def test_text_selector_schema(schema, valid_selections, invalid_selections) -> N @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"options": ["red", "green", "blue"]}, ("red", "green", "blue"), @@ -681,7 +681,7 @@ def test_text_selector_schema(schema, valid_selections, invalid_selections) -> N ("red", "blue"), (0, None, ["red"]), ), - ), + ], ) def test_select_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test select selector.""" @@ -690,14 +690,14 @@ def test_select_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( "schema", - ( + [ {}, # Must have options {"options": {"hello": "World"}}, # Options must be a list # Options must be strings or value / label pairs {"options": [{"hello": "World"}]}, # Options must all be of the same type {"options": ["red", {"value": "green", "label": "Emerald Green"}]}, - ), + ], ) def test_select_selector_schema_error(schema) -> None: """Test select selector.""" @@ -707,7 +707,7 @@ def test_select_selector_schema_error(schema) -> None: @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"entity_id": "sensor.abc"}, ("friendly_name", "device_class"), @@ -718,7 +718,7 @@ def test_select_selector_schema_error(schema) -> None: ("device_class", "state_class"), (None,), ), - ), + ], ) def test_attribute_selector_schema( schema, valid_selections, invalid_selections @@ -729,7 +729,7 @@ def test_attribute_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ( @@ -743,7 +743,7 @@ def test_attribute_selector_schema( ({"seconds": 10}, {"days": 10}), (None, {}), ), - ), + ], ) def test_duration_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test duration selector.""" @@ -752,13 +752,13 @@ def test_duration_selector_schema(schema, valid_selections, invalid_selections) @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("mdi:abc",), (None,), ), - ), + ], ) def test_icon_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test icon selector.""" @@ -767,7 +767,7 @@ def test_icon_selector_schema(schema, valid_selections, invalid_selections) -> N @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("abc",), @@ -778,7 +778,7 @@ def test_icon_selector_schema(schema, valid_selections, invalid_selections) -> N ("abc",), (None,), ), - ), + ], ) def test_theme_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test theme selector.""" @@ -787,7 +787,7 @@ def test_theme_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ( @@ -805,7 +805,7 @@ def test_theme_selector_schema(schema, valid_selections, invalid_selections) -> ), (None, "abc", {}), ), - ), + ], ) def test_media_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test media selector.""" @@ -826,7 +826,7 @@ def test_media_selector_schema(schema, valid_selections, invalid_selections) -> @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("nl", "fr"), @@ -837,7 +837,7 @@ def test_media_selector_schema(schema, valid_selections, invalid_selections) -> ("nl", "fr"), (None, True, 1, "de", "en"), ), - ), + ], ) def test_language_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test language selector.""" @@ -846,7 +846,7 @@ def test_language_selector_schema(schema, valid_selections, invalid_selections) @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ( @@ -873,7 +873,7 @@ def test_language_selector_schema(schema, valid_selections, invalid_selections) {"longitude": 1.0}, ), ), - ), + ], ) def test_location_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test location selector.""" @@ -883,13 +883,13 @@ def test_location_selector_schema(schema, valid_selections, invalid_selections) @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ([0, 0, 0], [255, 255, 255], [0.0, 0.0, 0.0], [255.0, 255.0, 255.0]), (None, "abc", [0, 0, "nil"], (255, 255, 255)), ), - ), + ], ) def test_rgb_color_selector_schema( schema, valid_selections, invalid_selections @@ -901,7 +901,7 @@ def test_rgb_color_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, (100, 100.0), @@ -922,7 +922,7 @@ def test_rgb_color_selector_schema( (1000, 2000), (999, 2001), ), - ), + ], ) def test_color_tempselector_schema( schema, valid_selections, invalid_selections @@ -934,13 +934,13 @@ def test_color_tempselector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("2022-03-24",), (None, "abc", "00:00", "2022-03-24 00:00", "2022-03-32"), ), - ), + ], ) def test_date_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test date selector.""" @@ -950,13 +950,13 @@ def test_date_selector_schema(schema, valid_selections, invalid_selections) -> N @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("2022-03-24 00:00", "2022-03-24"), (None, "abc", "00:00", "2022-03-24 24:01"), ), - ), + ], ) def test_datetime_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test datetime selector.""" @@ -966,7 +966,7 @@ def test_datetime_selector_schema(schema, valid_selections, invalid_selections) @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - (({}, ("abc123", "{{ now() }}"), (None, "{{ incomplete }", "{% if True %}Hi!")),), + [({}, ("abc123", "{{ now() }}"), (None, "{{ incomplete }", "{% if True %}Hi!"))], ) def test_template_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test template selector.""" @@ -975,13 +975,13 @@ def test_template_selector_schema(schema, valid_selections, invalid_selections) @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"accept": "image/*"}, ("0182a1b99dbc5ae24aecd90c346605fa",), (None, "not-a-uuid", "abcd", 1), ), - ), + ], ) def test_file_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test file selector.""" @@ -991,7 +991,7 @@ def test_file_selector_schema(schema, valid_selections, invalid_selections) -> N @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"value": True, "label": "Blah"}, (True, 1), @@ -1022,7 +1022,7 @@ def test_file_selector_schema(schema, valid_selections, invalid_selections) -> N ("dog",), (None, False, True, 0, 1, "abc", "def"), ), - ), + ], ) def test_constant_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test constant selector.""" @@ -1031,11 +1031,11 @@ def test_constant_selector_schema(schema, valid_selections, invalid_selections) @pytest.mark.parametrize( "schema", - ( + [ {}, # Value is mandatory {"value": []}, # Value must be str, int or bool {"value": 123, "label": 123}, # Label must be str - ), + ], ) def test_constant_selector_schema_error(schema) -> None: """Test constant selector.""" @@ -1045,7 +1045,7 @@ def test_constant_selector_schema_error(schema) -> None: @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ("home_assistant", "2j4hp3uy4p87wyrpiuhk34"), @@ -1056,7 +1056,7 @@ def test_constant_selector_schema_error(schema) -> None: ("home_assistant", "2j4hp3uy4p87wyrpiuhk34"), (None, True, 1), ), - ), + ], ) def test_conversation_agent_selector_schema( schema, valid_selections, invalid_selections @@ -1067,7 +1067,7 @@ def test_conversation_agent_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ( @@ -1082,7 +1082,7 @@ def test_conversation_agent_selector_schema( ), ("abc"), ), - ), + ], ) def test_condition_selector_schema( schema, valid_selections, invalid_selections @@ -1093,7 +1093,7 @@ def test_condition_selector_schema( @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {}, ( @@ -1108,7 +1108,7 @@ def test_condition_selector_schema( ), ("abc"), ), - ), + ], ) def test_trigger_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test trigger sequence selector.""" @@ -1117,7 +1117,7 @@ def test_trigger_selector_schema(schema, valid_selections, invalid_selections) - @pytest.mark.parametrize( ("schema", "valid_selections", "invalid_selections"), - ( + [ ( {"data": "test", "scale": 5}, ("test",), @@ -1137,7 +1137,7 @@ def test_trigger_selector_schema(schema, valid_selections, invalid_selections) - ("test",), (True, 1, []), ), - ), + ], ) def test_qr_code_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test QR code selector.""" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 4f889478460..cf099b3bc23 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -601,7 +601,7 @@ async def test_service_call_entry_id( assert dict(calls[0].data) == {"entity_id": ["hello.world"]} -@pytest.mark.parametrize("target", ("all", "none")) +@pytest.mark.parametrize("target", ["all", "none"]) async def test_service_call_all_none(hass: HomeAssistant, target) -> None: """Test service call targeting all.""" calls = async_mock_service(hass, "test_domain", "test_service") diff --git a/tests/helpers/test_singleton.py b/tests/helpers/test_singleton.py index 2278683f95e..dcda1e2db3a 100644 --- a/tests/helpers/test_singleton.py +++ b/tests/helpers/test_singleton.py @@ -13,7 +13,7 @@ def mock_hass(): return Mock(data={}) -@pytest.mark.parametrize("result", (object(), {}, [])) +@pytest.mark.parametrize("result", [object(), {}, []]) async def test_singleton_async(mock_hass, result) -> None: """Test singleton with async function.""" @@ -29,7 +29,7 @@ async def test_singleton_async(mock_hass, result) -> None: assert mock_hass.data["test_key"] is result1 -@pytest.mark.parametrize("result", (object(), {}, [])) +@pytest.mark.parametrize("result", [object(), {}, []]) def test_singleton(mock_hass, result) -> None: """Test singleton with function.""" diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 47230ef2e67..9fb73c524bf 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -1140,14 +1140,14 @@ def test_timestamp_local(hass: HomeAssistant) -> None: @pytest.mark.parametrize( "input", - ( + [ "2021-06-03 13:00:00.000000+00:00", "1986-07-09T12:00:00Z", "2016-10-19 15:22:05.588122+0100", "2016-10-19", "2021-01-01 00:00:01", "invalid", - ), + ], ) def test_as_datetime(hass: HomeAssistant, input) -> None: """Test converting a timestamp string to a date object.""" @@ -1466,11 +1466,11 @@ def test_max(hass: HomeAssistant) -> None: @pytest.mark.parametrize( "attribute", - ( + [ "a", "b", "c", - ), + ], ) def test_min_max_attribute(hass: HomeAssistant, attribute) -> None: """Test the min and max filters with attribute.""" diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 45c43144da7..669fb4bed24 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -112,7 +112,7 @@ def test__load_translations_files_by_language( @pytest.mark.parametrize( ("language", "expected_translation", "expected_errors"), - ( + [ ( "en", { @@ -154,7 +154,7 @@ def test__load_translations_files_by_language( "component.test.entity.switch.outlet.name", ], ), - ), + ], ) async def test_load_translations_files_invalid_localized_placeholders( hass: HomeAssistant, diff --git a/tests/test_config.py b/tests/test_config.py index 56e63cc40c7..404cf829a83 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -865,7 +865,7 @@ async def test_loading_configuration(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("minor_version", "users", "user_data", "default_language"), - ( + [ (2, (), {}, "en"), (2, ({"is_owner": True},), {}, "en"), ( @@ -894,7 +894,7 @@ async def test_loading_configuration(hass: HomeAssistant) -> None: {"user1": {"language": {"language": "sv"}}}, "en", ), - ), + ], ) async def test_language_default( hass: HomeAssistant, diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 9e6e93cfe9e..544396c237a 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1455,13 +1455,13 @@ async def test_entry_setup_succeed( @pytest.mark.parametrize( "state", - ( + [ config_entries.ConfigEntryState.LOADED, config_entries.ConfigEntryState.SETUP_ERROR, config_entries.ConfigEntryState.MIGRATION_ERROR, config_entries.ConfigEntryState.SETUP_RETRY, config_entries.ConfigEntryState.FAILED_UNLOAD, - ), + ], ) async def test_entry_setup_invalid_state( hass: HomeAssistant, @@ -1506,11 +1506,11 @@ async def test_entry_unload_succeed( @pytest.mark.parametrize( "state", - ( + [ config_entries.ConfigEntryState.NOT_LOADED, config_entries.ConfigEntryState.SETUP_ERROR, config_entries.ConfigEntryState.SETUP_RETRY, - ), + ], ) async def test_entry_unload_failed_to_load( hass: HomeAssistant, @@ -1532,10 +1532,10 @@ async def test_entry_unload_failed_to_load( @pytest.mark.parametrize( "state", - ( + [ config_entries.ConfigEntryState.MIGRATION_ERROR, config_entries.ConfigEntryState.FAILED_UNLOAD, - ), + ], ) async def test_entry_unload_invalid_state( hass: HomeAssistant, @@ -1588,11 +1588,11 @@ async def test_entry_reload_succeed( @pytest.mark.parametrize( "state", - ( + [ config_entries.ConfigEntryState.NOT_LOADED, config_entries.ConfigEntryState.SETUP_ERROR, config_entries.ConfigEntryState.SETUP_RETRY, - ), + ], ) async def test_entry_reload_not_loaded( hass: HomeAssistant, @@ -1627,10 +1627,10 @@ async def test_entry_reload_not_loaded( @pytest.mark.parametrize( "state", - ( + [ config_entries.ConfigEntryState.MIGRATION_ERROR, config_entries.ConfigEntryState.FAILED_UNLOAD, - ), + ], ) async def test_entry_reload_error( hass: HomeAssistant, @@ -2928,7 +2928,7 @@ async def test_async_setup_update_entry(hass: HomeAssistant) -> None: @pytest.mark.parametrize( "discovery_source", - ( + [ (config_entries.SOURCE_BLUETOOTH, BaseServiceInfo()), (config_entries.SOURCE_DISCOVERY, {}), (config_entries.SOURCE_SSDP, BaseServiceInfo()), @@ -2940,7 +2940,7 @@ async def test_async_setup_update_entry(hass: HomeAssistant) -> None: config_entries.SOURCE_HASSIO, HassioServiceInfo(config={}, name="Test", slug="test", uuid="1234"), ), - ), + ], ) async def test_flow_with_default_discovery( hass: HomeAssistant, @@ -4836,7 +4836,7 @@ async def test_directly_mutating_blocked( @pytest.mark.parametrize( "field", - ( + [ "data", "options", "title", @@ -4844,7 +4844,7 @@ async def test_directly_mutating_blocked( "pref_disable_polling", "minor_version", "version", - ), + ], ) async def test_report_direct_mutation_of_config_entry( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, field: str diff --git a/tests/test_core.py b/tests/test_core.py index 78229c4b445..0db2ba562ee 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -2865,7 +2865,7 @@ async def test_state_changed_events_to_not_leak_contexts(hass: HomeAssistant) -> assert len(_get_by_type("homeassistant.core.Context")) == init_count -@pytest.mark.parametrize("eager_start", (True, False)) +@pytest.mark.parametrize("eager_start", [True, False]) async def test_background_task(hass: HomeAssistant, eager_start: bool) -> None: """Test background tasks being quit.""" result = asyncio.Future() @@ -2936,7 +2936,7 @@ async def test_shutdown_does_not_block_on_shielded_tasks( sleep_task.cancel() -@pytest.mark.parametrize("eager_start", (True, False)) +@pytest.mark.parametrize("eager_start", [True, False]) async def test_cancellable_hassjob(hass: HomeAssistant, eager_start: bool) -> None: """Simulate a shutdown, ensure cancellable jobs are cancelled.""" job = MagicMock() diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index 710a637e941..c18eae4dd19 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -908,7 +908,7 @@ async def test_abort_raises_unknown_flow_if_not_in_progress(manager) -> None: @pytest.mark.parametrize( "menu_options", - (["target1", "target2"], {"target1": "Target 1", "target2": "Target 2"}), + [["target1", "target2"], {"target1": "Target 1", "target2": "Target 2"}], ) async def test_show_menu(hass: HomeAssistant, manager, menu_options) -> None: """Test show menu.""" diff --git a/tests/util/test_unit_system.py b/tests/util/test_unit_system.py index 6749627519b..0fa11762490 100644 --- a/tests/util/test_unit_system.py +++ b/tests/util/test_unit_system.py @@ -337,7 +337,7 @@ def test_get_unit_system_invalid(key: str) -> None: @pytest.mark.parametrize( ("device_class", "original_unit", "state_unit"), - ( + [ # Test atmospheric pressure ( SensorDeviceClass.ATMOSPHERIC_PRESSURE, @@ -477,7 +477,7 @@ def test_get_unit_system_invalid(key: str) -> None: UnitOfSpeed.KILOMETERS_PER_HOUR, ), (SensorDeviceClass.WIND_SPEED, "very_fast", None), - ), + ], ) def test_get_metric_converted_unit_( device_class: SensorDeviceClass, @@ -537,7 +537,7 @@ UNCONVERTED_UNITS_METRIC_SYSTEM = { @pytest.mark.parametrize( "device_class", - ( + [ SensorDeviceClass.ATMOSPHERIC_PRESSURE, SensorDeviceClass.DISTANCE, SensorDeviceClass.GAS, @@ -547,7 +547,7 @@ UNCONVERTED_UNITS_METRIC_SYSTEM = { SensorDeviceClass.SPEED, SensorDeviceClass.VOLUME, SensorDeviceClass.WATER, - ), + ], ) def test_metric_converted_units(device_class: SensorDeviceClass) -> None: """Test unit conversion rules are in place for all units.""" @@ -565,7 +565,7 @@ def test_metric_converted_units(device_class: SensorDeviceClass) -> None: @pytest.mark.parametrize( ("device_class", "original_unit", "state_unit"), - ( + [ # Test atmospheric pressure ( SensorDeviceClass.ATMOSPHERIC_PRESSURE, @@ -697,7 +697,7 @@ def test_metric_converted_units(device_class: SensorDeviceClass) -> None: (SensorDeviceClass.WIND_SPEED, UnitOfSpeed.KNOTS, None), (SensorDeviceClass.WIND_SPEED, UnitOfSpeed.MILES_PER_HOUR, None), (SensorDeviceClass.WIND_SPEED, "very_fast", None), - ), + ], ) def test_get_us_converted_unit( device_class: SensorDeviceClass, @@ -748,7 +748,7 @@ UNCONVERTED_UNITS_US_SYSTEM = { @pytest.mark.parametrize( "device_class", - ( + [ SensorDeviceClass.ATMOSPHERIC_PRESSURE, SensorDeviceClass.DISTANCE, SensorDeviceClass.GAS, @@ -758,7 +758,7 @@ UNCONVERTED_UNITS_US_SYSTEM = { SensorDeviceClass.SPEED, SensorDeviceClass.VOLUME, SensorDeviceClass.WATER, - ), + ], ) def test_imperial_converted_units(device_class: SensorDeviceClass) -> None: """Test unit conversion rules are in place for all units.""" From c2538d5176c6e00cd57ed2d49df11fe7aa34b4c7 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 19 Mar 2024 09:37:36 +0100 Subject: [PATCH 1269/1691] Bump pymodbus v3.6.6 (#113796) --- homeassistant/components/modbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index 6b072457144..14faad789fe 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -6,5 +6,5 @@ "iot_class": "local_polling", "loggers": ["pymodbus"], "quality_scale": "gold", - "requirements": ["pymodbus==3.6.5"] + "requirements": ["pymodbus==3.6.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 256055c801c..3d1f62ad058 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1964,7 +1964,7 @@ pymitv==1.4.3 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.6.5 +pymodbus==3.6.6 # homeassistant.components.monoprice pymonoprice==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d2cc004fc32..f7cd00ed91f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1524,7 +1524,7 @@ pymeteoclimatic==0.1.0 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.6.5 +pymodbus==3.6.6 # homeassistant.components.monoprice pymonoprice==0.4 From d0b4210de5db623fd916d3dd2f1f090842cc6541 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 19 Mar 2024 10:07:13 +0100 Subject: [PATCH 1270/1691] Update Home Assistant base image to 2024.03.0 (#113797) --- build.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.yaml b/build.yaml index f6ffac3bd1d..044358b1f9d 100644 --- a/build.yaml +++ b/build.yaml @@ -1,10 +1,10 @@ image: ghcr.io/home-assistant/{arch}-homeassistant build_from: - aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.02.1 - armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.02.1 - armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.02.1 - amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.02.1 - i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.02.1 + aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.03.0 + armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.03.0 + armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.03.0 + amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.03.0 + i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.03.0 codenotary: signer: notary@home-assistant.io base_image: notary@home-assistant.io From 438178093643bd07c055414ad1986ac68eecedf4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 19 Mar 2024 10:22:58 +0100 Subject: [PATCH 1271/1691] Add WS command cloud/remove_data (#109821) --- homeassistant/components/cloud/http_api.py | 28 ++++++++++++++++ homeassistant/components/cloud/prefs.py | 4 +++ tests/components/cloud/test_http_api.py | 39 ++++++++++++++++++++++ tests/components/cloud/test_prefs.py | 30 +++++++++++++++-- 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index a45077c79c4..cb13cd75944 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -71,6 +71,7 @@ _CLOUD_ERRORS: dict[type[Exception], tuple[HTTPStatus, str]] = { @callback def async_setup(hass: HomeAssistant) -> None: """Initialize the HTTP API.""" + websocket_api.async_register_command(hass, websocket_cloud_remove_data) websocket_api.async_register_command(hass, websocket_cloud_status) websocket_api.async_register_command(hass, websocket_subscription) websocket_api.async_register_command(hass, websocket_update_prefs) @@ -329,6 +330,33 @@ class CloudForgotPasswordView(HomeAssistantView): return self.json_message("ok") +@websocket_api.require_admin +@websocket_api.websocket_command({vol.Required("type"): "cloud/remove_data"}) +@websocket_api.async_response +async def websocket_cloud_remove_data( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], +) -> None: + """Handle request for account info. + + Async friendly. + """ + cloud: Cloud[CloudClient] = hass.data[DOMAIN] + if cloud.is_logged_in: + connection.send_message( + websocket_api.error_message( + msg["id"], "logged_in", "Can't remove data when logged in." + ) + ) + return + + await cloud.remove_data() + await cloud.client.prefs.async_erase_config() + + connection.send_message(websocket_api.result_message(msg["id"])) + + @websocket_api.websocket_command({vol.Required("type"): "cloud/status"}) @websocket_api.async_response async def websocket_cloud_status( diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 7c27aa0f130..0a0989ed4aa 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -204,6 +204,10 @@ class CloudPreferences: return True + async def async_erase_config(self) -> None: + """Erase the configuration.""" + await self._save_prefs(self._empty_config("")) + def as_dict(self) -> dict[str, Any]: """Return dictionary version.""" return { diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 0ff9fd79c75..f7b88fab6c3 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -697,6 +697,45 @@ async def test_resend_confirm_view_unknown_error( assert req.status == HTTPStatus.BAD_GATEWAY +async def test_websocket_remove_data( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, +) -> None: + """Test removing cloud data.""" + cloud.id_token = None + client = await hass_ws_client(hass) + + with patch.object(cloud.client.prefs, "async_erase_config") as mock_erase_config: + await client.send_json_auto_id({"type": "cloud/remove_data"}) + response = await client.receive_json() + + assert response["success"] + cloud.remove_data.assert_awaited_once_with() + mock_erase_config.assert_awaited_once_with() + + +async def test_websocket_remove_data_logged_in( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, +) -> None: + """Test removing cloud data.""" + cloud.iot.state = STATE_CONNECTED + client = await hass_ws_client(hass) + + await client.send_json_auto_id({"type": "cloud/remove_data"}) + response = await client.receive_json() + + assert not response["success"] + assert response["error"] == { + "code": "logged_in", + "message": "Can't remove data when logged in.", + } + + async def test_websocket_status( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, diff --git a/tests/components/cloud/test_prefs.py b/tests/components/cloud/test_prefs.py index e7d7a174135..86e86a71583 100644 --- a/tests/components/cloud/test_prefs.py +++ b/tests/components/cloud/test_prefs.py @@ -1,7 +1,7 @@ """Test Cloud preferences.""" from typing import Any -from unittest.mock import patch +from unittest.mock import ANY, patch import pytest @@ -26,8 +26,34 @@ async def test_set_username(hass: HomeAssistant) -> None: assert prefs.google_enabled +async def test_erase_config(hass: HomeAssistant) -> None: + """Test erasing config.""" + prefs = CloudPreferences(hass) + await prefs.async_initialize() + assert prefs._prefs == { + **prefs._empty_config(""), + "google_local_webhook_id": ANY, + "instance_id": ANY, + } + + await prefs.async_update(google_enabled=False) + assert prefs._prefs == { + **prefs._empty_config(""), + "google_enabled": False, + "google_local_webhook_id": ANY, + "instance_id": ANY, + } + + await prefs.async_erase_config() + assert prefs._prefs == { + **prefs._empty_config(""), + "google_local_webhook_id": ANY, + "instance_id": ANY, + } + + async def test_set_username_migration(hass: HomeAssistant) -> None: - """Test we not clear config if we had no username.""" + """Test we do not clear config if we had no username.""" prefs = CloudPreferences(hass) with patch.object(prefs, "_empty_config", return_value=prefs._empty_config(None)): From 18ef76a018c96af3bbdb77cd6cb3710b278c4518 Mon Sep 17 00:00:00 2001 From: Nalin Mahajan Date: Tue, 19 Mar 2024 05:44:52 -0500 Subject: [PATCH 1272/1691] Add Room Audio Control to Control4 Integration (#87821) * Add control4 room based media player (#13) * update attribute names (#14) * change to data class and set off to idle (#15) Co-authored-by: nalin29 --------- Co-authored-by: nalin29 --- .coveragerc | 1 + homeassistant/components/control4/__init__.py | 12 +- homeassistant/components/control4/const.py | 1 + homeassistant/components/control4/light.py | 12 +- .../components/control4/media_player.py | 391 ++++++++++++++++++ 5 files changed, 408 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/control4/media_player.py diff --git a/.coveragerc b/.coveragerc index cc7b1b98d2a..2d16ab0575e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -202,6 +202,7 @@ omit = homeassistant/components/control4/__init__.py homeassistant/components/control4/director_utils.py homeassistant/components/control4/light.py + homeassistant/components/control4/media_player.py homeassistant/components/coolmaster/coordinator.py homeassistant/components/cppm_tracker/device_tracker.py homeassistant/components/crownstone/__init__.py diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index 6776483e7e3..b8d195fcb05 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations import json import logging +from typing import Any from aiohttp import client_exceptions from pyControl4.account import C4Account @@ -36,13 +37,14 @@ from .const import ( CONF_DIRECTOR_ALL_ITEMS, CONF_DIRECTOR_MODEL, CONF_DIRECTOR_SW_VERSION, + CONF_UI_CONFIGURATION, DEFAULT_SCAN_INTERVAL, DOMAIN, ) _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.LIGHT] +PLATFORMS = [Platform.LIGHT, Platform.MEDIA_PLAYER] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -105,6 +107,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: director_all_items = json.loads(director_all_items) entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items + entry_data[CONF_UI_CONFIGURATION] = json.loads(await director.getUiConfiguration()) + # Load options from config entry entry_data[CONF_SCAN_INTERVAL] = entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL @@ -145,14 +149,14 @@ async def get_items_of_category(hass: HomeAssistant, entry: ConfigEntry, categor ] -class Control4Entity(CoordinatorEntity): +class Control4Entity(CoordinatorEntity[Any]): """Base entity for Control4.""" def __init__( self, entry_data: dict, - coordinator: DataUpdateCoordinator, - name: str, + coordinator: DataUpdateCoordinator[Any], + name: str | None, idx: int, device_name: str | None, device_manufacturer: str | None, diff --git a/homeassistant/components/control4/const.py b/homeassistant/components/control4/const.py index 677610a1618..f8d939e1ac5 100644 --- a/homeassistant/components/control4/const.py +++ b/homeassistant/components/control4/const.py @@ -10,6 +10,7 @@ CONF_DIRECTOR = "director" CONF_DIRECTOR_SW_VERSION = "director_sw_version" CONF_DIRECTOR_MODEL = "director_model" CONF_DIRECTOR_ALL_ITEMS = "director_all_items" +CONF_UI_CONFIGURATION = "ui_configuration" CONF_CONTROLLER_UNIQUE_ID = "controller_unique_id" CONF_CONFIG_LISTENER = "config_listener" diff --git a/homeassistant/components/control4/light.py b/homeassistant/components/control4/light.py index 8f7940e5cc3..d7cfd44dc43 100644 --- a/homeassistant/components/control4/light.py +++ b/homeassistant/components/control4/light.py @@ -45,7 +45,7 @@ async def async_setup_entry( scan_interval, ) - async def async_update_data_non_dimmer(): + async def async_update_data_non_dimmer() -> dict[int, dict[str, Any]]: """Fetch data from Control4 director for non-dimmer lights.""" try: return await update_variables_for_config_entry( @@ -54,7 +54,7 @@ async def async_setup_entry( except C4Exception as err: raise UpdateFailed(f"Error communicating with API: {err}") from err - async def async_update_data_dimmer(): + async def async_update_data_dimmer() -> dict[int, dict[str, Any]]: """Fetch data from Control4 director for dimmer lights.""" try: return await update_variables_for_config_entry( @@ -63,14 +63,14 @@ async def async_setup_entry( except C4Exception as err: raise UpdateFailed(f"Error communicating with API: {err}") from err - non_dimmer_coordinator = DataUpdateCoordinator( + non_dimmer_coordinator = DataUpdateCoordinator[dict[int, dict[str, Any]]]( hass, _LOGGER, name="light", update_method=async_update_data_non_dimmer, update_interval=timedelta(seconds=scan_interval), ) - dimmer_coordinator = DataUpdateCoordinator( + dimmer_coordinator = DataUpdateCoordinator[dict[int, dict[str, Any]]]( hass, _LOGGER, name="light", @@ -149,10 +149,12 @@ async def async_setup_entry( class Control4Light(Control4Entity, LightEntity): """Control4 light entity.""" + _attr_has_entity_name = True + def __init__( self, entry_data: dict, - coordinator: DataUpdateCoordinator, + coordinator: DataUpdateCoordinator[dict[int, dict[str, Any]]], name: str, idx: int, device_name: str | None, diff --git a/homeassistant/components/control4/media_player.py b/homeassistant/components/control4/media_player.py new file mode 100644 index 00000000000..c65004260af --- /dev/null +++ b/homeassistant/components/control4/media_player.py @@ -0,0 +1,391 @@ +"""Platform for Control4 Rooms Media Players.""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import timedelta +import enum +import logging +from typing import Any + +from pyControl4.error_handling import C4Exception +from pyControl4.room import C4Room + +from homeassistant.components.media_player import ( + MediaPlayerDeviceClass, + MediaPlayerEntity, + MediaPlayerEntityFeature, + MediaPlayerState, + MediaType, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_SCAN_INTERVAL +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from . import Control4Entity +from .const import CONF_DIRECTOR, CONF_DIRECTOR_ALL_ITEMS, CONF_UI_CONFIGURATION, DOMAIN +from .director_utils import update_variables_for_config_entry + +_LOGGER = logging.getLogger(__name__) + +CONTROL4_POWER_STATE = "POWER_STATE" +CONTROL4_VOLUME_STATE = "CURRENT_VOLUME" +CONTROL4_MUTED_STATE = "IS_MUTED" +CONTROL4_CURRENT_VIDEO_DEVICE = "CURRENT_VIDEO_DEVICE" +CONTROL4_PLAYING = "PLAYING" +CONTROL4_PAUSED = "PAUSED" +CONTROL4_STOPPED = "STOPPED" +CONTROL4_MEDIA_INFO = "CURRENT MEDIA INFO" + +CONTROL4_PARENT_ID = "parentId" + +VARIABLES_OF_INTEREST = { + CONTROL4_POWER_STATE, + CONTROL4_VOLUME_STATE, + CONTROL4_MUTED_STATE, + CONTROL4_CURRENT_VIDEO_DEVICE, + CONTROL4_MEDIA_INFO, + CONTROL4_PLAYING, + CONTROL4_PAUSED, + CONTROL4_STOPPED, +} + + +class _SourceType(enum.Enum): + AUDIO = 1 + VIDEO = 2 + + +@dataclass +class _RoomSource: + """Class for Room Source.""" + + source_type: set[_SourceType] + idx: int + name: str + + +async def get_rooms(hass: HomeAssistant, entry: ConfigEntry): + """Return a list of all Control4 rooms.""" + director_all_items = hass.data[DOMAIN][entry.entry_id][CONF_DIRECTOR_ALL_ITEMS] + return [ + item + for item in director_all_items + if "typeName" in item and item["typeName"] == "room" + ] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Control4 rooms from a config entry.""" + all_rooms = await get_rooms(hass, entry) + if not all_rooms: + return + + entry_data = hass.data[DOMAIN][entry.entry_id] + scan_interval = entry_data[CONF_SCAN_INTERVAL] + _LOGGER.debug("Scan interval = %s", scan_interval) + + async def async_update_data() -> dict[int, dict[str, Any]]: + """Fetch data from Control4 director.""" + try: + return await update_variables_for_config_entry( + hass, entry, VARIABLES_OF_INTEREST + ) + except C4Exception as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err + + coordinator = DataUpdateCoordinator[dict[int, dict[str, Any]]]( + hass, + _LOGGER, + name="room", + update_method=async_update_data, + update_interval=timedelta(seconds=scan_interval), + ) + + # Fetch initial data so we have data when entities subscribe + await coordinator.async_refresh() + + items_by_id = { + item["id"]: item + for item in hass.data[DOMAIN][entry.entry_id][CONF_DIRECTOR_ALL_ITEMS] + } + item_to_parent_map = { + k: item["parentId"] + for k, item in items_by_id.items() + if "parentId" in item and k > 1 + } + + ui_config = entry_data[CONF_UI_CONFIGURATION] + + entity_list = [] + for room in all_rooms: + room_id = room["id"] + + sources: dict[int, _RoomSource] = {} + for exp in ui_config["experiences"]: + if room_id == exp["room_id"]: + exp_type = exp["type"] + if exp_type not in ("listen", "watch"): + continue + + dev_type = ( + _SourceType.AUDIO if exp_type == "listen" else _SourceType.VIDEO + ) + for source in exp["sources"]["source"]: + dev_id = source["id"] + name = items_by_id.get(dev_id, {}).get( + "name", f"Unknown Device - {dev_id}" + ) + if dev_id in sources: + sources[dev_id].source_type.add(dev_type) + else: + sources[dev_id] = _RoomSource( + source_type={dev_type}, idx=dev_id, name=name + ) + + try: + hidden = room["roomHidden"] + entity_list.append( + Control4Room( + entry_data, + coordinator, + room["name"], + room_id, + item_to_parent_map, + sources, + hidden, + ) + ) + except KeyError: + _LOGGER.exception( + "Unknown device properties received from Control4: %s", + room, + ) + continue + + async_add_entities(entity_list, True) + + +class Control4Room(Control4Entity, MediaPlayerEntity): + """Control4 Room entity.""" + + _attr_has_entity_name = True + + def __init__( + self, + entry_data: dict, + coordinator: DataUpdateCoordinator[dict[int, dict[str, Any]]], + name: str, + room_id: int, + id_to_parent: dict[int, int], + sources: dict[int, _RoomSource], + room_hidden: bool, + ) -> None: + """Initialize Control4 room entity.""" + super().__init__( + entry_data, + coordinator, + None, + room_id, + device_name=name, + device_manufacturer=None, + device_model=None, + device_id=room_id, + ) + self._attr_entity_registry_enabled_default = not room_hidden + self._id_to_parent = id_to_parent + self._sources = sources + self._attr_supported_features = ( + MediaPlayerEntityFeature.PLAY + | MediaPlayerEntityFeature.PAUSE + | MediaPlayerEntityFeature.STOP + | MediaPlayerEntityFeature.VOLUME_MUTE + | MediaPlayerEntityFeature.VOLUME_SET + | MediaPlayerEntityFeature.VOLUME_STEP + | MediaPlayerEntityFeature.TURN_OFF + | MediaPlayerEntityFeature.SELECT_SOURCE + ) + + def _create_api_object(self): + """Create a pyControl4 device object. + + This exists so the director token used is always the latest one, without needing to re-init the entire entity. + """ + return C4Room(self.entry_data[CONF_DIRECTOR], self._idx) + + def _get_device_from_variable(self, var: str) -> int | None: + current_device = self.coordinator.data[self._idx][var] + if current_device == 0: + return None + + return current_device + + def _get_current_video_device_id(self) -> int | None: + return self._get_device_from_variable(CONTROL4_CURRENT_VIDEO_DEVICE) + + def _get_current_playing_device_id(self) -> int | None: + media_info = self._get_media_info() + if media_info: + if "medSrcDev" in media_info: + return media_info["medSrcDev"] + if "deviceid" in media_info: + return media_info["deviceid"] + return 0 + + def _get_media_info(self) -> dict | None: + """Get the Media Info Dictionary if populated.""" + media_info = self.coordinator.data[self._idx][CONTROL4_MEDIA_INFO] + if "mediainfo" in media_info: + return media_info["mediainfo"] + return None + + def _get_current_source_state(self) -> str | None: + current_source = self._get_current_playing_device_id() + while current_source: + current_data = self.coordinator.data.get(current_source, None) + if current_data: + if current_data.get(CONTROL4_PLAYING, None): + return MediaPlayerState.PLAYING + if current_data.get(CONTROL4_PAUSED, None): + return MediaPlayerState.PAUSED + if current_data.get(CONTROL4_STOPPED, None): + return MediaPlayerState.ON + current_source = self._id_to_parent.get(current_source, None) + return None + + @property + def device_class(self) -> MediaPlayerDeviceClass | None: + """Return the class of this entity.""" + for avail_source in self._sources.values(): + if _SourceType.VIDEO in avail_source.source_type: + return MediaPlayerDeviceClass.TV + return MediaPlayerDeviceClass.SPEAKER + + @property + def state(self): + """Return whether this room is on or idle.""" + + if source_state := self._get_current_source_state(): + return source_state + + if self.coordinator.data[self._idx][CONTROL4_POWER_STATE]: + return MediaPlayerState.ON + + return MediaPlayerState.IDLE + + @property + def source(self): + """Get the current source.""" + current_source = self._get_current_playing_device_id() + if not current_source or current_source not in self._sources: + return None + return self._sources[current_source].name + + @property + def media_title(self) -> str | None: + """Get the Media Title.""" + media_info = self._get_media_info() + if not media_info: + return None + if "title" in media_info: + return media_info["title"] + current_source = self._get_current_playing_device_id() + if not current_source or current_source not in self._sources: + return None + return self._sources[current_source].name + + @property + def media_content_type(self): + """Get current content type if available.""" + current_source = self._get_current_playing_device_id() + if not current_source: + return None + if current_source == self._get_current_video_device_id(): + return MediaType.VIDEO + return MediaType.MUSIC + + async def async_media_play_pause(self): + """If possible, toggle the current play/pause state. + + Not every source supports play/pause. + Unfortunately MediaPlayer capabilities are not dynamic, + so we must determine if play/pause is supported here + """ + if self._get_current_source_state(): + await super().async_media_play_pause() + + @property + def source_list(self) -> list[str]: + """Get the available source.""" + return [x.name for x in self._sources.values()] + + @property + def volume_level(self): + """Get the volume level.""" + return self.coordinator.data[self._idx][CONTROL4_VOLUME_STATE] / 100 + + @property + def is_volume_muted(self): + """Check if the volume is muted.""" + return bool(self.coordinator.data[self._idx][CONTROL4_MUTED_STATE]) + + async def async_select_source(self, source): + """Select a new source.""" + for avail_source in self._sources.values(): + if avail_source.name == source: + audio_only = _SourceType.VIDEO not in avail_source.source_type + if audio_only: + await self._create_api_object().setAudioSource(avail_source.idx) + else: + await self._create_api_object().setVideoAndAudioSource( + avail_source.idx + ) + break + + await self.coordinator.async_request_refresh() + + async def async_turn_off(self): + """Turn off the room.""" + await self._create_api_object().setRoomOff() + await self.coordinator.async_request_refresh() + + async def async_mute_volume(self, mute): + """Mute the room.""" + if mute: + await self._create_api_object().setMuteOn() + else: + await self._create_api_object().setMuteOff() + await self.coordinator.async_request_refresh() + + async def async_set_volume_level(self, volume): + """Set room volume, 0-1 scale.""" + await self._create_api_object().setVolume(int(volume * 100)) + await self.coordinator.async_request_refresh() + + async def async_volume_up(self): + """Increase the volume by 1.""" + await self._create_api_object().setIncrementVolume() + await self.coordinator.async_request_refresh() + + async def async_volume_down(self): + """Decrease the volume by 1.""" + await self._create_api_object().setDecrementVolume() + await self.coordinator.async_request_refresh() + + async def async_media_pause(self): + """Issue a pause command.""" + await self._create_api_object().setPause() + await self.coordinator.async_request_refresh() + + async def async_media_play(self): + """Issue a play command.""" + await self._create_api_object().setPlay() + await self.coordinator.async_request_refresh() + + async def async_media_stop(self): + """Issue a stop command.""" + await self._create_api_object().setStop() + await self.coordinator.async_request_refresh() From 5230a8a2100eff7599a09c77e9dbe5c0acd410ee Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 19 Mar 2024 11:49:15 +0100 Subject: [PATCH 1273/1691] Simplify UV install in CI (#113803) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2bca52fc638..73443306a49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -243,7 +243,7 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install "$(cat requirements_test.txt | grep uv)" + pip install "$(grep '^uv' < requirements_test.txt)" uv pip install "$(cat requirements_test.txt | grep pre-commit)" - name: Restore pre-commit environment from cache id: cache-precommit @@ -493,7 +493,7 @@ jobs: python -m venv venv . venv/bin/activate python --version - pip install "$(cat requirements_test.txt | grep uv)" + pip install "$(grep '^uv' < requirements_test.txt)" uv pip install -U "pip>=21.3.1" setuptools wheel uv pip install -r requirements_all.txt uv pip install -r requirements_test.txt From 318b6e3a8b444d155fa3407deb8960492e68b635 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:21:32 +0000 Subject: [PATCH 1274/1691] Allow retries on communication exceptions for Aurora ABB Powerone solar inverter (#104492) * Allow retries on SerialException, AuroraError * Add test to verify that retry is occuring * Fix tests and indents * Only log to info level for normal on/offline * Review comment: don't log warning, debug and raise UpdateFailed * Fix tests --- .../aurora_abb_powerone/__init__.py | 77 +++++++++++-------- .../aurora_abb_powerone/test_sensor.py | 30 +++++++- 2 files changed, 71 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/aurora_abb_powerone/__init__.py b/homeassistant/components/aurora_abb_powerone/__init__.py index c7400f31727..05e5819e36e 100644 --- a/homeassistant/components/aurora_abb_powerone/__init__.py +++ b/homeassistant/components/aurora_abb_powerone/__init__.py @@ -11,13 +11,15 @@ # sudo chmod 777 /dev/ttyUSB0 import logging +from time import sleep from aurorapy.client import AuroraError, AuroraSerialClient, AuroraTimeoutError +from serial import SerialException from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_PORT, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, SCAN_INTERVAL @@ -69,38 +71,49 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): """ data: dict[str, float] = {} self.available_prev = self.available - try: - self.client.connect() + retries: int = 3 + while retries > 0: + try: + self.client.connect() - # read ADC channel 3 (grid power output) - power_watts = self.client.measure(3, True) - temperature_c = self.client.measure(21) - energy_wh = self.client.cumulated_energy(5) - [alarm, *_] = self.client.alarms() - except AuroraTimeoutError: - self.available = False - _LOGGER.debug("No response from inverter (could be dark)") - except AuroraError as error: - self.available = False - raise error - else: - data["instantaneouspower"] = round(power_watts, 1) - data["temp"] = round(temperature_c, 1) - data["totalenergy"] = round(energy_wh / 1000, 2) - data["alarm"] = alarm - self.available = True - - finally: - if self.available != self.available_prev: - if self.available: - _LOGGER.info("Communication with %s back online", self.name) - else: - _LOGGER.warning( - "Communication with %s lost", - self.name, - ) - if self.client.serline.isOpen(): - self.client.close() + # read ADC channel 3 (grid power output) + power_watts = self.client.measure(3, True) + temperature_c = self.client.measure(21) + energy_wh = self.client.cumulated_energy(5) + [alarm, *_] = self.client.alarms() + except AuroraTimeoutError: + self.available = False + _LOGGER.debug("No response from inverter (could be dark)") + retries = 0 + except (SerialException, AuroraError) as error: + self.available = False + retries -= 1 + if retries <= 0: + raise UpdateFailed(error) from error + _LOGGER.debug( + "Exception: %s occurred, %d retries remaining", + repr(error), + retries, + ) + sleep(1) + else: + data["instantaneouspower"] = round(power_watts, 1) + data["temp"] = round(temperature_c, 1) + data["totalenergy"] = round(energy_wh / 1000, 2) + data["alarm"] = alarm + self.available = True + retries = 0 + finally: + if self.available != self.available_prev: + if self.available: + _LOGGER.info("Communication with %s back online", self.name) + else: + _LOGGER.info( + "Communication with %s lost", + self.name, + ) + if self.client.serline.isOpen(): + self.client.close() return data diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index d1e8bbb015a..32e96df7fac 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -4,6 +4,7 @@ from unittest.mock import patch from aurorapy.client import AuroraError, AuroraTimeoutError from freezegun.api import FrozenDateTimeFactory +import pytest from homeassistant.components.aurora_abb_powerone.const import ( ATTR_DEVICE_NAME, @@ -171,18 +172,39 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) assert power.state == "unknown" # should this be 'available'? -async def test_sensor_unknown_error(hass: HomeAssistant) -> None: +async def test_sensor_unknown_error( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, +) -> None: """Test other comms error is handled correctly.""" mock_entry = _mock_config_entry() + # sun is up + with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( + "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns + ), patch( + "aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"] + ), patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=_simulated_returns, + ): + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( "aurorapy.client.AuroraSerialClient.measure", side_effect=AuroraError("another error"), ), patch( "aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"] ), patch("serial.Serial.isOpen", return_value=True): - mock_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_entry.entry_id) + freezer.tick(SCAN_INTERVAL * 2) + async_fire_time_changed(hass) await hass.async_block_till_done() + assert ( + "Exception: AuroraError('another error') occurred, 2 retries remaining" + in caplog.text + ) power = hass.states.get("sensor.mydevicename_power_output") - assert power is None + assert power.state == "unavailable" From 6106a66ed8043e7a828a55035d7fefd3ecf493cf Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Tue, 19 Mar 2024 13:15:57 +0100 Subject: [PATCH 1275/1691] Bump bthome-ble to 3.8.1 (#113800) --- homeassistant/components/bthome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index 5fb90bb5998..7c90c6f3bbc 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -20,5 +20,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/bthome", "iot_class": "local_push", - "requirements": ["bthome-ble==3.8.0"] + "requirements": ["bthome-ble==3.8.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3d1f62ad058..700016c66bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -618,7 +618,7 @@ brunt==1.2.0 bt-proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==3.8.0 +bthome-ble==3.8.1 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f7cd00ed91f..926ff05c009 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -526,7 +526,7 @@ brottsplatskartan==1.0.5 brunt==1.2.0 # homeassistant.components.bthome -bthome-ble==3.8.0 +bthome-ble==3.8.1 # homeassistant.components.buienradar buienradar==1.0.5 From 44211dc761e4f5b5681bd2da145f2c3cf1b81743 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 19 Mar 2024 14:02:50 +0100 Subject: [PATCH 1276/1691] Migrate Harmony to has entity name (#104737) * Migrate Harmony to has entity name * Fix tests --------- Co-authored-by: J. Nick Koston --- homeassistant/components/harmony/data.py | 19 ++--- homeassistant/components/harmony/entity.py | 7 +- homeassistant/components/harmony/remote.py | 26 ++++--- homeassistant/components/harmony/select.py | 9 +-- homeassistant/components/harmony/strings.json | 1 + homeassistant/components/harmony/switch.py | 17 ++--- tests/components/harmony/conftest.py | 22 +++++- tests/components/harmony/test_remote.py | 70 +++++++++++-------- tests/components/harmony/test_select.py | 48 +++++-------- tests/components/harmony/test_switch.py | 15 ++-- 10 files changed, 120 insertions(+), 114 deletions(-) diff --git a/homeassistant/components/harmony/data.py b/homeassistant/components/harmony/data.py index 75678391e7b..992eaf52326 100644 --- a/homeassistant/components/harmony/data.py +++ b/homeassistant/components/harmony/data.py @@ -29,7 +29,7 @@ class HarmonyData(HarmonySubscriberMixin): ) -> None: """Initialize a data object.""" super().__init__(hass) - self._name = name + self.name = name self._unique_id = unique_id self._available = False self._address = address @@ -60,11 +60,6 @@ class HarmonyData(HarmonySubscriberMixin): return devices - @property - def name(self) -> str: - """Return the Harmony device's name.""" - return self._name - @property def unique_id(self): """Return the Harmony device's unique_id.""" @@ -105,7 +100,7 @@ class HarmonyData(HarmonySubscriberMixin): async def connect(self) -> None: """Connect to the Harmony Hub.""" - _LOGGER.debug("%s: Connecting", self._name) + _LOGGER.debug("%s: Connecting", self.name) callbacks = { "config_updated": self._config_updated, @@ -124,27 +119,27 @@ class HarmonyData(HarmonySubscriberMixin): except (TimeoutError, aioexc.TimeOut) as err: await self._client.close() raise ConfigEntryNotReady( - f"{self._name}: Connection timed-out to {self._address}:8088" + f"{self.name}: Connection timed-out to {self._address}:8088" ) from err except (ValueError, AttributeError) as err: await self._client.close() raise ConfigEntryNotReady( - f"{self._name}: Error {err} while connected HUB at:" + f"{self.name}: Error {err} while connected HUB at:" f" {self._address}:8088" ) from err if not connected: await self._client.close() raise ConfigEntryNotReady( - f"{self._name}: Unable to connect to HUB at: {self._address}:8088" + f"{self.name}: Unable to connect to HUB at: {self._address}:8088" ) async def shutdown(self) -> None: """Close connection on shutdown.""" - _LOGGER.debug("%s: Closing Harmony Hub", self._name) + _LOGGER.debug("%s: Closing Harmony Hub", self.name) try: await self._client.close() except aioexc.TimeOut: - _LOGGER.warning("%s: Disconnect timed-out", self._name) + _LOGGER.warning("%s: Disconnect timed-out", self.name) async def async_start_activity(self, activity: str) -> None: """Start an activity from the Harmony device.""" diff --git a/homeassistant/components/harmony/entity.py b/homeassistant/components/harmony/entity.py index f87d30a0e40..99b5744e0ed 100644 --- a/homeassistant/components/harmony/entity.py +++ b/homeassistant/components/harmony/entity.py @@ -19,11 +19,12 @@ TIME_MARK_DISCONNECTED = 10 class HarmonyEntity(Entity): """Base entity for Harmony with connection state handling.""" + _attr_has_entity_name = True + def __init__(self, data: HarmonyData) -> None: """Initialize the Harmony base entity.""" super().__init__() self._unsub_mark_disconnected: Callable[[], None] | None = None - self._name = data.name self._data = data self._attr_should_poll = False @@ -34,14 +35,14 @@ class HarmonyEntity(Entity): async def async_got_connected(self, _: str | None = None) -> None: """Notification that we're connected to the HUB.""" - _LOGGER.debug("%s: connected to the HUB", self._name) + _LOGGER.debug("%s: connected to the HUB", self._data.name) self.async_write_ha_state() self._clear_disconnection_delay() async def async_got_disconnected(self, _: str | None = None) -> None: """Notification that we're disconnected from the HUB.""" - _LOGGER.debug("%s: disconnected from the HUB", self._name) + _LOGGER.debug("%s: disconnected from the HUB", self._data.name) # We're going to wait for 10 seconds before announcing we're # unavailable, this to allow a reconnection to happen. self._unsub_mark_disconnected = async_call_later( diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 37a6811a2b2..c6b2e9be718 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -87,6 +87,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): """Remote representation used to control a Harmony device.""" _attr_supported_features = RemoteEntityFeature.ACTIVITY + _attr_name = None def __init__( self, data: HarmonyData, activity: str | None, delay_secs: float, out_path: str @@ -103,7 +104,6 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): self._config_path = out_path self._attr_unique_id = data.unique_id self._attr_device_info = self._data.device_info(DOMAIN) - self._attr_name = data.name async def _async_update_options(self, data: dict[str, Any]) -> None: """Change options when the options flow does.""" @@ -136,7 +136,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): """Complete the initialization.""" await super().async_added_to_hass() - _LOGGER.debug("%s: Harmony Hub added", self.name) + _LOGGER.debug("%s: Harmony Hub added", self._data.name) self.async_on_remove(self._clear_disconnection_delay) self._setup_callbacks() @@ -193,7 +193,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): def async_new_activity(self, activity_info: tuple) -> None: """Call for updating the current activity.""" activity_id, activity_name = activity_info - _LOGGER.debug("%s: activity reported as: %s", self.name, activity_name) + _LOGGER.debug("%s: activity reported as: %s", self._data.name, activity_name) self._current_activity = activity_name if self._is_initial_update: self._is_initial_update = False @@ -209,13 +209,13 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): async def async_new_config(self, _: dict | None = None) -> None: """Call for updating the current activity.""" - _LOGGER.debug("%s: configuration has been updated", self.name) + _LOGGER.debug("%s: configuration has been updated", self._data.name) self.async_new_activity(self._data.current_activity) await self.hass.async_add_executor_job(self.write_config_file) async def async_turn_on(self, **kwargs: Any) -> None: """Start an activity from the Harmony device.""" - _LOGGER.debug("%s: Turn On", self.name) + _LOGGER.debug("%s: Turn On", self._data.name) activity = kwargs.get(ATTR_ACTIVITY, self.default_activity) @@ -228,7 +228,9 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): if activity: await self._data.async_start_activity(activity) else: - _LOGGER.error("%s: No activity specified with turn_on service", self.name) + _LOGGER.error( + "%s: No activity specified with turn_on service", self._data.name + ) async def async_turn_off(self, **kwargs: Any) -> None: """Start the PowerOff activity.""" @@ -236,9 +238,9 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: """Send a list of commands to one device.""" - _LOGGER.debug("%s: Send Command", self.name) + _LOGGER.debug("%s: Send Command", self._data.name) if (device := kwargs.get(ATTR_DEVICE)) is None: - _LOGGER.error("%s: Missing required argument: device", self.name) + _LOGGER.error("%s: Missing required argument: device", self._data.name) return num_repeats = kwargs[ATTR_NUM_REPEATS] @@ -263,10 +265,12 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): This is a handy way for users to figure out the available commands for automations. """ _LOGGER.debug( - "%s: Writing hub configuration to file: %s", self.name, self._config_path + "%s: Writing hub configuration to file: %s", + self._data.name, + self._config_path, ) if (json_config := self._data.json_config) is None: - _LOGGER.warning("%s: No configuration received from hub", self.name) + _LOGGER.warning("%s: No configuration received from hub", self._data.name) return try: @@ -275,7 +279,7 @@ class HarmonyRemote(HarmonyEntity, RemoteEntity, RestoreEntity): except OSError as exc: _LOGGER.error( "%s: Unable to write HUB configuration to %s: %s", - self.name, + self._data.name, self._config_path, exc, ) diff --git a/homeassistant/components/harmony/select.py b/homeassistant/components/harmony/select.py index ca653e03ccc..0bb8f462419 100644 --- a/homeassistant/components/harmony/select.py +++ b/homeassistant/components/harmony/select.py @@ -6,7 +6,6 @@ import logging from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -25,10 +24,7 @@ async def async_setup_entry( ) -> None: """Set up harmony activities select.""" data: HarmonyData = hass.data[DOMAIN][entry.entry_id][HARMONY_DATA] - _LOGGER.debug("creating select for %s hub activities", entry.data[CONF_NAME]) - async_add_entities( - [HarmonyActivitySelect(f"{entry.data[CONF_NAME]} Activities", data)] - ) + async_add_entities([HarmonyActivitySelect(data)]) class HarmonyActivitySelect(HarmonyEntity, SelectEntity): @@ -36,13 +32,12 @@ class HarmonyActivitySelect(HarmonyEntity, SelectEntity): _attr_translation_key = "activities" - def __init__(self, name: str, data: HarmonyData) -> None: + def __init__(self, data: HarmonyData) -> None: """Initialize HarmonyActivitySelect class.""" super().__init__(data=data) self._data = data self._attr_unique_id = self._data.unique_id self._attr_device_info = self._data.device_info(DOMAIN) - self._attr_name = name @property def options(self) -> list[str]: diff --git a/homeassistant/components/harmony/strings.json b/homeassistant/components/harmony/strings.json index f6862ca3c83..444097395c9 100644 --- a/homeassistant/components/harmony/strings.json +++ b/homeassistant/components/harmony/strings.json @@ -39,6 +39,7 @@ "entity": { "select": { "activities": { + "name": "Activities", "state": { "power_off": "Power Off" } diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index df32af36ec5..0cb07e5cb1e 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -7,7 +7,6 @@ from homeassistant.components.automation import automations_with_entity from homeassistant.components.script import scripts_with_entity from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue @@ -25,28 +24,22 @@ async def async_setup_entry( ) -> None: """Set up harmony activity switches.""" data: HarmonyData = hass.data[DOMAIN][entry.entry_id][HARMONY_DATA] - activities = data.activities - switches = [] - for activity in activities: - _LOGGER.debug("creating switch for activity: %s", activity) - name = f"{entry.data[CONF_NAME]} {activity['label']}" - switches.append(HarmonyActivitySwitch(name, activity, data)) - - async_add_entities(switches, True) + async_add_entities( + (HarmonyActivitySwitch(activity, data) for activity in data.activities), True + ) class HarmonyActivitySwitch(HarmonyEntity, SwitchEntity): """Switch representation of a Harmony activity.""" - def __init__(self, name: str, activity: dict, data: HarmonyData) -> None: + def __init__(self, activity: dict, data: HarmonyData) -> None: """Initialize HarmonyActivitySwitch class.""" super().__init__(data=data) - self._activity_name = activity["label"] + self._activity_name = self._attr_name = activity["label"] self._activity_id = activity["id"] self._attr_entity_registry_enabled_default = False self._attr_unique_id = f"activity_{self._activity_id}" - self._attr_name = name self._attr_device_info = self._data.device_info(DOMAIN) @property diff --git a/tests/components/harmony/conftest.py b/tests/components/harmony/conftest.py index c7594848990..97449749667 100644 --- a/tests/components/harmony/conftest.py +++ b/tests/components/harmony/conftest.py @@ -5,9 +5,17 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from aioharmony.const import ClientCallbackType import pytest -from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF +from homeassistant.components.harmony.const import ACTIVITY_POWER_OFF, DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME -from .const import NILE_TV_ACTIVITY_ID, PLAY_MUSIC_ACTIVITY_ID, WATCH_TV_ACTIVITY_ID +from .const import ( + HUB_NAME, + NILE_TV_ACTIVITY_ID, + PLAY_MUSIC_ACTIVITY_ID, + WATCH_TV_ACTIVITY_ID, +) + +from tests.common import MockConfigEntry ACTIVITIES_TO_IDS = { ACTIVITY_POWER_OFF: -1, @@ -166,3 +174,13 @@ def mock_write_config(): "homeassistant.components.harmony.remote.HarmonyRemote.write_config_file", ) as mock: yield mock + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Return mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + unique_id="123", + data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME}, + ) diff --git a/tests/components/harmony/test_remote.py b/tests/components/harmony/test_remote.py index 14b8bdd63cf..c0ec2235b84 100644 --- a/tests/components/harmony/test_remote.py +++ b/tests/components/harmony/test_remote.py @@ -43,15 +43,16 @@ STOP_COMMAND = "Stop" async def test_connection_state_changes( - harmony_client, mock_hc, hass: HomeAssistant, mock_write_config + harmony_client, + mock_hc, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure connection changes are reflected in the remote state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV @@ -82,14 +83,13 @@ async def test_connection_state_changes( assert hass.states.is_state(ENTITY_REMOTE, STATE_ON) -async def test_remote_toggles(mock_hc, hass: HomeAssistant, mock_write_config) -> None: +async def test_remote_toggles( + mock_hc, hass: HomeAssistant, mock_write_config, mock_config_entry: MockConfigEntry +) -> None: """Ensure calls to the remote also updates the switches.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start remote with Watch TV default activity @@ -151,15 +151,16 @@ async def test_remote_toggles(mock_hc, hass: HomeAssistant, mock_write_config) - async def test_async_send_command( - mock_hc, harmony_client, hass: HomeAssistant, mock_write_config + mock_hc, + harmony_client, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure calls to send remote commands properly propagate to devices.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() send_commands_mock = harmony_client.send_commands @@ -285,11 +286,16 @@ async def test_async_send_command( async def test_async_send_command_custom_delay( - mock_hc, harmony_client, hass: HomeAssistant, mock_write_config + mock_hc, + harmony_client, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure calls to send remote commands properly propagate to devices with custom delays.""" entry = MockConfigEntry( domain=DOMAIN, + unique_id="123", data={ CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME, @@ -327,15 +333,16 @@ async def test_async_send_command_custom_delay( async def test_change_channel( - mock_hc, harmony_client, hass: HomeAssistant, mock_write_config + mock_hc, + harmony_client, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Test change channel commands.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() change_channel_mock = harmony_client.change_channel @@ -353,15 +360,16 @@ async def test_change_channel( async def test_sync( - mock_hc, harmony_client, mock_write_config, hass: HomeAssistant + mock_hc, + harmony_client, + mock_write_config, + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, ) -> None: """Test the sync command.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() sync_mock = harmony_client.sync diff --git a/tests/components/harmony/test_select.py b/tests/components/harmony/test_select.py index da699be5372..2568feb1412 100644 --- a/tests/components/harmony/test_select.py +++ b/tests/components/harmony/test_select.py @@ -2,38 +2,31 @@ from datetime import timedelta -from homeassistant.components.harmony.const import DOMAIN from homeassistant.components.select import ( ATTR_OPTION, DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, ) -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_HOST, - CONF_NAME, - STATE_OFF, - STATE_ON, - STATE_UNAVAILABLE, -) +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.util import utcnow -from .const import ENTITY_REMOTE, ENTITY_SELECT, HUB_NAME +from .const import ENTITY_REMOTE, ENTITY_SELECT from tests.common import MockConfigEntry, async_fire_time_changed async def test_connection_state_changes( - harmony_client, mock_hc, hass: HomeAssistant, mock_write_config + harmony_client, + mock_hc, + hass: HomeAssistant, + mock_write_config, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure connection changes are reflected in the switch states.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV @@ -56,14 +49,13 @@ async def test_connection_state_changes( assert hass.states.is_state(ENTITY_SELECT, "Watch TV") -async def test_options(mock_hc, hass: HomeAssistant, mock_write_config) -> None: +async def test_options( + mock_hc, hass: HomeAssistant, mock_write_config, mock_config_entry: MockConfigEntry +) -> None: """Ensure calls to the switch modify the harmony state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # assert we have all options @@ -76,14 +68,12 @@ async def test_options(mock_hc, hass: HomeAssistant, mock_write_config) -> None: ] -async def test_select_option(mock_hc, hass: HomeAssistant, mock_write_config) -> None: +async def test_select_option( + mock_hc, hass: HomeAssistant, mock_write_config, mock_config_entry: MockConfigEntry +) -> None: """Ensure calls to the switch modify the harmony state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV diff --git a/tests/components/harmony/test_switch.py b/tests/components/harmony/test_switch.py index b5603938972..01f9287ae57 100644 --- a/tests/components/harmony/test_switch.py +++ b/tests/components/harmony/test_switch.py @@ -90,21 +90,22 @@ async def test_connection_state_changes( async def test_switch_toggles( - mock_hc, hass: HomeAssistant, mock_write_config, entity_registry: er.EntityRegistry + mock_hc, + hass: HomeAssistant, + mock_write_config, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, ) -> None: """Ensure calls to the switch modify the harmony state.""" - entry = MockConfigEntry( - domain=DOMAIN, data={CONF_HOST: "192.0.2.0", CONF_NAME: HUB_NAME} - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() # enable switch entities entity_registry.async_update_entity(ENTITY_WATCH_TV, disabled_by=None) entity_registry.async_update_entity(ENTITY_PLAY_MUSIC, disabled_by=None) - await hass.config_entries.async_reload(entry.entry_id) + await hass.config_entries.async_reload(mock_config_entry.entry_id) await hass.async_block_till_done() # mocks start with current activity == Watch TV From 38d0854b70f814e4f977a41a3b4511d55f47be9d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 19 Mar 2024 14:18:53 +0100 Subject: [PATCH 1277/1691] Find referenced floors in automations & scripts (#113802) --- .../components/automation/__init__.py | 27 +++++ homeassistant/components/script/__init__.py | 27 +++++ homeassistant/helpers/script.py | 37 +++++-- tests/components/automation/test_init.py | 25 +++++ tests/components/script/test_init.py | 25 +++++ tests/helpers/test_script.py | 101 ++++++++++++++++++ 6 files changed, 232 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 5854cad3ef9..be20ee17181 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -233,6 +233,18 @@ def areas_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: return _x_in_automation(hass, entity_id, "referenced_areas") +@callback +def automations_with_floor(hass: HomeAssistant, floor_id: str) -> list[str]: + """Return all automations that reference the floor.""" + return _automations_with_x(hass, floor_id, "referenced_floors") + + +@callback +def floors_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: + """Return all floors in an automation.""" + return _x_in_automation(hass, entity_id, "referenced_floors") + + @callback def automations_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list[str]: """Return all automations that reference the blueprint.""" @@ -341,6 +353,11 @@ class BaseAutomationEntity(ToggleEntity, ABC): return {CONF_ID: self.unique_id} return None + @cached_property + @abstractmethod + def referenced_floors(self) -> set[str]: + """Return a set of referenced floors.""" + @cached_property @abstractmethod def referenced_areas(self) -> set[str]: @@ -396,6 +413,11 @@ class UnavailableAutomationEntity(BaseAutomationEntity): """Return the name of the entity.""" return self._name + @cached_property + def referenced_floors(self) -> set[str]: + """Return a set of referenced floors.""" + return set() + @cached_property def referenced_areas(self) -> set[str]: """Return a set of referenced areas.""" @@ -483,6 +505,11 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): """Return True if entity is on.""" return self._async_detach_triggers is not None or self._is_enabled + @property + def referenced_floors(self) -> set[str]: + """Return a set of referenced floors.""" + return self.action_script.referenced_floors + @cached_property def referenced_areas(self) -> set[str]: """Return a set of referenced areas.""" diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 2be2ce7b062..c742c42309f 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -158,6 +158,18 @@ def areas_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: return _x_in_script(hass, entity_id, "referenced_areas") +@callback +def scripts_with_floor(hass: HomeAssistant, floor_id: str) -> list[str]: + """Return all scripts that reference the floor.""" + return _scripts_with_x(hass, floor_id, "referenced_floors") + + +@callback +def floors_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: + """Return all floors in a script.""" + return _x_in_script(hass, entity_id, "referenced_floors") + + @callback def scripts_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list[str]: """Return all scripts that reference the blueprint.""" @@ -389,6 +401,11 @@ class BaseScriptEntity(ToggleEntity, ABC): raw_config: ConfigType | None + @cached_property + @abstractmethod + def referenced_floors(self) -> set[str]: + """Return a set of referenced floors.""" + @cached_property @abstractmethod def referenced_areas(self) -> set[str]: @@ -434,6 +451,11 @@ class UnavailableScriptEntity(BaseScriptEntity): """Return the name of the entity.""" return self._name + @cached_property + def referenced_floors(self) -> set[str]: + """Return a set of referenced floors.""" + return set() + @cached_property def referenced_areas(self) -> set[str]: """Return a set of referenced areas.""" @@ -517,6 +539,11 @@ class ScriptEntity(BaseScriptEntity, RestoreEntity): """Return true if script is on.""" return self.script.is_running + @cached_property + def referenced_floors(self) -> set[str]: + """Return a set of referenced floors.""" + return self.script.referenced_floors + @cached_property def referenced_areas(self) -> set[str]: """Return a set of referenced areas.""" diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index e38642768df..d5d3534f793 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -13,7 +13,7 @@ from functools import partial import itertools import logging from types import MappingProxyType -from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, cast +from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar, cast import async_interrupt import voluptuous as vol @@ -26,6 +26,7 @@ from homeassistant.const import ( ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, + ATTR_FLOOR_ID, CONF_ALIAS, CONF_CHOOSE, CONF_CONDITION, @@ -1380,17 +1381,27 @@ class Script: """Return true if the current mode support max.""" return self.script_mode in (SCRIPT_MODE_PARALLEL, SCRIPT_MODE_QUEUED) + @cached_property + def referenced_floors(self) -> set[str]: + """Return a set of referenced fooors.""" + referenced_floors: set[str] = set() + Script._find_referenced_target(ATTR_FLOOR_ID, referenced_floors, self.sequence) + return referenced_floors + @cached_property def referenced_areas(self) -> set[str]: """Return a set of referenced areas.""" referenced_areas: set[str] = set() - Script._find_referenced_areas(referenced_areas, self.sequence) + Script._find_referenced_target(ATTR_AREA_ID, referenced_areas, self.sequence) return referenced_areas @staticmethod - def _find_referenced_areas( - referenced: set[str], sequence: Sequence[dict[str, Any]] + def _find_referenced_target( + target: Literal["area_id", "floor_id"], + referenced: set[str], + sequence: Sequence[dict[str, Any]], ) -> None: + """Find referenced target in a sequence.""" for step in sequence: action = cv.determine_script_action(step) @@ -1400,22 +1411,28 @@ class Script: step.get(CONF_SERVICE_DATA), step.get(CONF_SERVICE_DATA_TEMPLATE), ): - _referenced_extract_ids(data, ATTR_AREA_ID, referenced) + _referenced_extract_ids(data, target, referenced) elif action == cv.SCRIPT_ACTION_CHOOSE: for choice in step[CONF_CHOOSE]: - Script._find_referenced_areas(referenced, choice[CONF_SEQUENCE]) + Script._find_referenced_target( + target, referenced, choice[CONF_SEQUENCE] + ) if CONF_DEFAULT in step: - Script._find_referenced_areas(referenced, step[CONF_DEFAULT]) + Script._find_referenced_target( + target, referenced, step[CONF_DEFAULT] + ) elif action == cv.SCRIPT_ACTION_IF: - Script._find_referenced_areas(referenced, step[CONF_THEN]) + Script._find_referenced_target(target, referenced, step[CONF_THEN]) if CONF_ELSE in step: - Script._find_referenced_areas(referenced, step[CONF_ELSE]) + Script._find_referenced_target(target, referenced, step[CONF_ELSE]) elif action == cv.SCRIPT_ACTION_PARALLEL: for script in step[CONF_PARALLEL]: - Script._find_referenced_areas(referenced, script[CONF_SEQUENCE]) + Script._find_referenced_target( + target, referenced, script[CONF_SEQUENCE] + ) @cached_property def referenced_devices(self) -> set[str]: diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 70aae2a66c8..7ee829c7bc9 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1561,6 +1561,8 @@ async def test_extraction_functions_not_setup(hass: HomeAssistant) -> None: assert automation.devices_in_automation(hass, "automation.test") == [] assert automation.automations_with_entity(hass, "light.in_both") == [] assert automation.entities_in_automation(hass, "automation.test") == [] + assert automation.automations_with_floor(hass, "floor-in-both") == [] + assert automation.floors_in_automation(hass, "automation.test") == [] async def test_extraction_functions_unknown_automation(hass: HomeAssistant) -> None: @@ -1570,6 +1572,7 @@ async def test_extraction_functions_unknown_automation(hass: HomeAssistant) -> N assert automation.blueprint_in_automation(hass, "automation.unknown") is None assert automation.devices_in_automation(hass, "automation.unknown") == [] assert automation.entities_in_automation(hass, "automation.unknown") == [] + assert automation.floors_in_automation(hass, "automation.unknown") == [] async def test_extraction_functions_unavailable_automation(hass: HomeAssistant) -> None: @@ -1595,6 +1598,8 @@ async def test_extraction_functions_unavailable_automation(hass: HomeAssistant) assert automation.devices_in_automation(hass, entity_id) == [] assert automation.automations_with_entity(hass, "light.in_both") == [] assert automation.entities_in_automation(hass, entity_id) == [] + assert automation.automations_with_floor(hass, "floor-in-both") == [] + assert automation.floors_in_automation(hass, entity_id) == [] async def test_extraction_functions( @@ -1694,6 +1699,10 @@ async def test_extraction_functions( "service": "test.test", "target": {"area_id": "area-in-both"}, }, + { + "service": "test.test", + "target": {"floor_id": "floor-in-both"}, + }, ], }, { @@ -1813,6 +1822,14 @@ async def test_extraction_functions( "service": "test.test", "target": {"area_id": "area-in-last"}, }, + { + "service": "test.test", + "target": {"floor_id": "floor-in-both"}, + }, + { + "service": "test.test", + "target": {"floor_id": "floor-in-last"}, + }, ], }, ] @@ -1855,6 +1872,14 @@ async def test_extraction_functions( "area-in-both", "area-in-last", } + assert set(automation.automations_with_floor(hass, "floor-in-both")) == { + "automation.test1", + "automation.test3", + } + assert set(automation.floors_in_automation(hass, "automation.test3")) == { + "floor-in-both", + "floor-in-last", + } assert automation.blueprint_in_automation(hass, "automation.test3") is None diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index b4758d2d547..95fc3338333 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -684,11 +684,14 @@ async def test_extraction_functions_not_setup(hass: HomeAssistant) -> None: assert script.devices_in_script(hass, "script.test") == [] assert script.scripts_with_entity(hass, "light.in_both") == [] assert script.entities_in_script(hass, "script.test") == [] + assert script.scripts_with_floor(hass, "floor-in-both") == [] + assert script.floors_in_script(hass, "script.test") == [] async def test_extraction_functions_unknown_script(hass: HomeAssistant) -> None: """Test extraction functions for an unknown script.""" assert await async_setup_component(hass, DOMAIN, {}) + assert script.floors_in_script(hass, "script.unknown") == [] assert script.areas_in_script(hass, "script.unknown") == [] assert script.blueprint_in_script(hass, "script.unknown") is None assert script.devices_in_script(hass, "script.unknown") == [] @@ -712,6 +715,8 @@ async def test_extraction_functions_unavailable_script(hass: HomeAssistant) -> N assert script.devices_in_script(hass, entity_id) == [] assert script.scripts_with_entity(hass, "light.in_both") == [] assert script.entities_in_script(hass, entity_id) == [] + assert script.scripts_with_floor(hass, "floor-in-both") == [] + assert script.floors_in_script(hass, entity_id) == [] async def test_extraction_functions( @@ -756,6 +761,10 @@ async def test_extraction_functions( "service": "test.test", "target": {"area_id": "area-in-both"}, }, + { + "service": "test.test", + "target": {"floor_id": "floor-in-both"}, + }, ] }, "test2": { @@ -804,6 +813,14 @@ async def test_extraction_functions( "service": "test.test", "target": {"area_id": "area-in-last"}, }, + { + "service": "test.test", + "target": {"floor_id": "floor-in-both"}, + }, + { + "service": "test.test", + "target": {"floor_id": "floor-in-last"}, + }, ], }, } @@ -835,6 +852,14 @@ async def test_extraction_functions( "area-in-both", "area-in-last", } + assert set(script.scripts_with_floor(hass, "floor-in-both")) == { + "script.test1", + "script.test3", + } + assert set(script.floors_in_script(hass, "script.test3")) == { + "floor-in-both", + "floor-in-last", + } assert script.blueprint_in_script(hass, "script.test3") is None diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index aab84592725..4d20add4f3e 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -3695,6 +3695,107 @@ async def test_propagate_error_service_exception(hass: HomeAssistant) -> None: assert_action_trace(expected_trace, expected_script_execution="error") +async def test_referenced_floors(hass: HomeAssistant) -> None: + """Test referenced floors.""" + script_obj = script.Script( + hass, + cv.SCRIPT_SCHEMA( + [ + { + "service": "test.script", + "data": {"floor_id": "floor_service_not_list"}, + }, + { + "service": "test.script", + "data": {"floor_id": ["floor_service_list"]}, + }, + { + "service": "test.script", + "data": {"floor_id": "{{ 'floor_service_template' }}"}, + }, + { + "service": "test.script", + "target": {"floor_id": "floor_in_target"}, + }, + { + "service": "test.script", + "data_template": {"floor_id": "floor_in_data_template"}, + }, + {"service": "test.script", "data": {"without": "floor_id"}}, + { + "choose": [ + { + "conditions": "{{ true == false }}", + "sequence": [ + { + "service": "test.script", + "data": {"floor_id": "floor_choice_1_seq"}, + } + ], + }, + { + "conditions": "{{ true == false }}", + "sequence": [ + { + "service": "test.script", + "data": {"floor_id": "floor_choice_2_seq"}, + } + ], + }, + ], + "default": [ + { + "service": "test.script", + "data": {"floor_id": "floor_default_seq"}, + } + ], + }, + {"event": "test_event"}, + {"delay": "{{ delay_period }}"}, + { + "if": [], + "then": [ + { + "service": "test.script", + "data": {"floor_id": "floor_if_then"}, + } + ], + "else": [ + { + "service": "test.script", + "data": {"floor_id": "floor_if_else"}, + } + ], + }, + { + "parallel": [ + { + "service": "test.script", + "data": {"floor_id": "floor_parallel"}, + } + ], + }, + ] + ), + "Test Name", + "test_domain", + ) + assert script_obj.referenced_floors == { + "floor_choice_1_seq", + "floor_choice_2_seq", + "floor_default_seq", + "floor_in_data_template", + "floor_in_target", + "floor_service_list", + "floor_service_not_list", + "floor_if_then", + "floor_if_else", + "floor_parallel", + } + # Test we cache results. + assert script_obj.referenced_floors is script_obj.referenced_floors + + async def test_referenced_areas(hass: HomeAssistant) -> None: """Test referenced areas.""" script_obj = script.Script( From 3a8494cb88c1df720f0a12403254d236baa460c0 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:30:30 +0100 Subject: [PATCH 1278/1691] Add binary sensor platform for Husqvarna Automower (#113248) * Add binary sensor platform for Husqvarna Automower * revert changes in sensor.py * use == instead of is * remove the parantheses * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker * Docstring --------- Co-authored-by: Joost Lekkerkerker --- .../husqvarna_automower/__init__.py | 7 +- .../husqvarna_automower/binary_sensor.py | 82 ++++++ .../components/husqvarna_automower/icons.json | 8 + .../husqvarna_automower/strings.json | 8 + .../snapshots/test_binary_sensor.ambr | 273 ++++++++++++++++++ .../husqvarna_automower/test_binary_sensor.py | 83 ++++++ 6 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/husqvarna_automower/binary_sensor.py create mode 100644 tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr create mode 100644 tests/components/husqvarna_automower/test_binary_sensor.py diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index 473c67c4ea8..2145b3a134e 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -17,7 +17,12 @@ from .coordinator import AutomowerDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.LAWN_MOWER, Platform.SENSOR, Platform.SWITCH] +PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.LAWN_MOWER, + Platform.SENSOR, + Platform.SWITCH, +] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/husqvarna_automower/binary_sensor.py b/homeassistant/components/husqvarna_automower/binary_sensor.py new file mode 100644 index 00000000000..e8e64e7ffc7 --- /dev/null +++ b/homeassistant/components/husqvarna_automower/binary_sensor.py @@ -0,0 +1,82 @@ +"""Creates the binary sensor entities for the mower.""" + +from collections.abc import Callable +from dataclasses import dataclass +import logging + +from aioautomower.model import MowerActivities, MowerAttributes + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import AutomowerDataUpdateCoordinator +from .entity import AutomowerBaseEntity + +_LOGGER = logging.getLogger(__name__) + + +@dataclass(frozen=True, kw_only=True) +class AutomowerBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Automower binary sensor entity.""" + + value_fn: Callable[[MowerAttributes], bool] + + +BINARY_SENSOR_TYPES: tuple[AutomowerBinarySensorEntityDescription, ...] = ( + AutomowerBinarySensorEntityDescription( + key="battery_charging", + value_fn=lambda data: data.mower.activity == MowerActivities.CHARGING, + device_class=BinarySensorDeviceClass.BATTERY_CHARGING, + ), + AutomowerBinarySensorEntityDescription( + key="leaving_dock", + translation_key="leaving_dock", + value_fn=lambda data: data.mower.activity == MowerActivities.LEAVING, + ), + AutomowerBinarySensorEntityDescription( + key="returning_to_dock", + translation_key="returning_to_dock", + value_fn=lambda data: data.mower.activity == MowerActivities.GOING_HOME, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up binary sensor platform.""" + coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + AutomowerBinarySensorEntity(mower_id, coordinator, description) + for mower_id in coordinator.data + for description in BINARY_SENSOR_TYPES + ) + + +class AutomowerBinarySensorEntity(AutomowerBaseEntity, BinarySensorEntity): + """Defining the Automower Sensors with AutomowerBinarySensorEntityDescription.""" + + entity_description: AutomowerBinarySensorEntityDescription + + def __init__( + self, + mower_id: str, + coordinator: AutomowerDataUpdateCoordinator, + description: AutomowerBinarySensorEntityDescription, + ) -> None: + """Set up AutomowerSensors.""" + super().__init__(mower_id, coordinator) + self.entity_description = description + self._attr_unique_id = f"{mower_id}_{description.key}" + + @property + def is_on(self) -> bool: + """Return the state of the binary sensor.""" + return self.entity_description.value_fn(self.mower_attributes) diff --git a/homeassistant/components/husqvarna_automower/icons.json b/homeassistant/components/husqvarna_automower/icons.json index a0dd316b535..a3e2bd6bb8b 100644 --- a/homeassistant/components/husqvarna_automower/icons.json +++ b/homeassistant/components/husqvarna_automower/icons.json @@ -1,5 +1,13 @@ { "entity": { + "binary_sensor": { + "leaving_dock": { + "default": "mdi:debug-step-out" + }, + "returning_to_dock": { + "default": "mdi:debug-step-into" + } + }, "sensor": { "number_of_charging_cycles": { "default": "mdi:battery-sync-outline" diff --git a/homeassistant/components/husqvarna_automower/strings.json b/homeassistant/components/husqvarna_automower/strings.json index 2d42172506d..4280ea097e8 100644 --- a/homeassistant/components/husqvarna_automower/strings.json +++ b/homeassistant/components/husqvarna_automower/strings.json @@ -29,6 +29,14 @@ } }, "entity": { + "binary_sensor": { + "leaving_dock": { + "name": "Leaving dock" + }, + "returning_to_dock": { + "name": "Returning to dock" + } + }, "switch": { "enable_schedule": { "name": "Enable schedule" diff --git a/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr b/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr new file mode 100644 index 00000000000..f586cca1ba5 --- /dev/null +++ b/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr @@ -0,0 +1,273 @@ +# serializer version: 1 +# name: test_sensor[binary_sensor.test_mower_1_charging-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.test_mower_1_charging', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charging', + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_battery_charging', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[binary_sensor.test_mower_1_charging-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery_charging', + 'friendly_name': 'Test Mower 1 Charging', + }), + 'context': , + 'entity_id': 'binary_sensor.test_mower_1_charging', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_sensor[binary_sensor.test_mower_1_leaving_dock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.test_mower_1_leaving_dock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Leaving dock', + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'leaving_dock', + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_leaving_dock', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[binary_sensor.test_mower_1_leaving_dock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Mower 1 Leaving dock', + }), + 'context': , + 'entity_id': 'binary_sensor.test_mower_1_leaving_dock', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_sensor[binary_sensor.test_mower_1_returning_to_dock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.test_mower_1_returning_to_dock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Returning to dock', + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'returning_to_dock', + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_returning_to_dock', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[binary_sensor.test_mower_1_returning_to_dock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Mower 1 Returning to dock', + }), + 'context': , + 'entity_id': 'binary_sensor.test_mower_1_returning_to_dock', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_snapshot_binary_sensor[binary_sensor.test_mower_1_charging-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.test_mower_1_charging', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Charging', + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_battery_charging', + 'unit_of_measurement': None, + }) +# --- +# name: test_snapshot_binary_sensor[binary_sensor.test_mower_1_charging-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery_charging', + 'friendly_name': 'Test Mower 1 Charging', + }), + 'context': , + 'entity_id': 'binary_sensor.test_mower_1_charging', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_snapshot_binary_sensor[binary_sensor.test_mower_1_leaving_dock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.test_mower_1_leaving_dock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Leaving dock', + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'leaving_dock', + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_leaving_dock', + 'unit_of_measurement': None, + }) +# --- +# name: test_snapshot_binary_sensor[binary_sensor.test_mower_1_leaving_dock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Mower 1 Leaving dock', + }), + 'context': , + 'entity_id': 'binary_sensor.test_mower_1_leaving_dock', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_snapshot_binary_sensor[binary_sensor.test_mower_1_returning_to_dock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.test_mower_1_returning_to_dock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Returning to dock', + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'returning_to_dock', + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0_returning_to_dock', + 'unit_of_measurement': None, + }) +# --- +# name: test_snapshot_binary_sensor[binary_sensor.test_mower_1_returning_to_dock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Mower 1 Returning to dock', + }), + 'context': , + 'entity_id': 'binary_sensor.test_mower_1_returning_to_dock', + 'last_changed': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/husqvarna_automower/test_binary_sensor.py b/tests/components/husqvarna_automower/test_binary_sensor.py new file mode 100644 index 00000000000..425636ba915 --- /dev/null +++ b/tests/components/husqvarna_automower/test_binary_sensor.py @@ -0,0 +1,83 @@ +"""Tests for binary sensor platform.""" + +from datetime import timedelta +from unittest.mock import AsyncMock, patch + +from aioautomower.model import MowerActivities +from aioautomower.utils import mower_list_to_dictionary_dataclass +from freezegun.api import FrozenDateTimeFactory +from syrupy import SnapshotAssertion + +from homeassistant.components.husqvarna_automower.const import DOMAIN +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration +from .const import TEST_MOWER_ID + +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + load_json_value_fixture, +) + + +async def test_binary_sensor_states( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test binary sensor states.""" + values = mower_list_to_dictionary_dataclass( + load_json_value_fixture("mower.json", DOMAIN) + ) + await setup_integration(hass, mock_config_entry) + state = hass.states.get("binary_sensor.test_mower_1_charging") + assert state is not None + assert state.state == "off" + state = hass.states.get("binary_sensor.test_mower_1_leaving_dock") + assert state is not None + assert state.state == "off" + state = hass.states.get("binary_sensor.test_mower_1_returning_to_dock") + assert state is not None + assert state.state == "off" + + for activity, entity in [ + (MowerActivities.CHARGING, "test_mower_1_charging"), + (MowerActivities.LEAVING, "test_mower_1_leaving_dock"), + (MowerActivities.GOING_HOME, "test_mower_1_returning_to_dock"), + ]: + values[TEST_MOWER_ID].mower.activity = activity + mock_automower_client.get_status.return_value = values + freezer.tick(timedelta(minutes=5)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get(f"binary_sensor.{entity}") + assert state.state == "on" + + +async def test_snapshot_binary_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test states of the binary sensors.""" + with patch( + "homeassistant.components.husqvarna_automower.PLATFORMS", + [Platform.BINARY_SENSOR], + ): + await setup_integration(hass, mock_config_entry) + entity_entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + + assert entity_entries + for entity_entry in entity_entries: + assert hass.states.get(entity_entry.entity_id) == snapshot( + name=f"{entity_entry.entity_id}-state" + ) + assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") From ea443af557d97f634c97f20708040f407f26e52c Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:46:35 +0100 Subject: [PATCH 1279/1691] Add device_tracker platform for husqvarna_automower (#111403) * Add device_tracker platform for husqvarna_automower * ruff * Add snapshot test * State test * Fix description * ruff * Optimize some docstrings * Update homeassistant/components/husqvarna_automower/device_tracker.py Co-authored-by: Joost Lekkerkerker * Adress review --------- Co-authored-by: Joost Lekkerkerker --- .../husqvarna_automower/__init__.py | 1 + .../husqvarna_automower/device_tracker.py | 52 +++++++++++++++++++ .../snapshots/test_device_tracker.ambr | 50 ++++++++++++++++++ .../test_device_tracker.py | 38 ++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 homeassistant/components/husqvarna_automower/device_tracker.py create mode 100644 tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr create mode 100644 tests/components/husqvarna_automower/test_device_tracker.py diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index 2145b3a134e..a6c9e46dbed 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -19,6 +19,7 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, + Platform.DEVICE_TRACKER, Platform.LAWN_MOWER, Platform.SENSOR, Platform.SWITCH, diff --git a/homeassistant/components/husqvarna_automower/device_tracker.py b/homeassistant/components/husqvarna_automower/device_tracker.py new file mode 100644 index 00000000000..a32fd8758bd --- /dev/null +++ b/homeassistant/components/husqvarna_automower/device_tracker.py @@ -0,0 +1,52 @@ +"""Creates the device tracker entity for the mower.""" + +from homeassistant.components.device_tracker import SourceType, TrackerEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import AutomowerDataUpdateCoordinator +from .entity import AutomowerBaseEntity + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up device tracker platform.""" + coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + AutomowerDeviceTrackerEntity(mower_id, coordinator) + for mower_id in coordinator.data + if coordinator.data[mower_id].capabilities.position + ) + + +class AutomowerDeviceTrackerEntity(AutomowerBaseEntity, TrackerEntity): + """Defining the AutomowerDeviceTrackerEntity.""" + + _attr_name = None + + def __init__( + self, + mower_id: str, + coordinator: AutomowerDataUpdateCoordinator, + ) -> None: + """Initialize AutomowerDeviceTracker.""" + super().__init__(mower_id, coordinator) + self._attr_unique_id = mower_id + + @property + def source_type(self) -> SourceType: + """Return the source type of the device.""" + return SourceType.GPS + + @property + def latitude(self) -> float: + """Return latitude value of the device.""" + return self.mower_attributes.positions[0].latitude + + @property + def longitude(self) -> float: + """Return longitude value of the device.""" + return self.mower_attributes.positions[0].longitude diff --git a/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr b/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr new file mode 100644 index 00000000000..636b6599380 --- /dev/null +++ b/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr @@ -0,0 +1,50 @@ +# serializer version: 1 +# name: test_device_tracker_snapshot[device_tracker.test_mower_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'device_tracker', + 'entity_category': , + 'entity_id': 'device_tracker.test_mower_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'husqvarna_automower', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'c7233734-b219-4287-a173-08e3643f89f0', + 'unit_of_measurement': None, + }) +# --- +# name: test_device_tracker_snapshot[device_tracker.test_mower_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Mower 1', + 'gps_accuracy': 0, + 'latitude': 35.5402913, + 'longitude': -82.5527055, + 'source_type': , + }), + 'context': , + 'entity_id': 'device_tracker.test_mower_1', + 'last_changed': , + 'last_updated': , + 'state': 'not_home', + }) +# --- diff --git a/tests/components/husqvarna_automower/test_device_tracker.py b/tests/components/husqvarna_automower/test_device_tracker.py new file mode 100644 index 00000000000..d9cab0d5074 --- /dev/null +++ b/tests/components/husqvarna_automower/test_device_tracker.py @@ -0,0 +1,38 @@ +"""Tests for the device tracker platform.""" + +from unittest.mock import AsyncMock, patch + +from syrupy import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_integration + +from tests.common import MockConfigEntry + + +async def test_device_tracker_snapshot( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test device tracker with a snapshot.""" + with patch( + "homeassistant.components.husqvarna_automower.PLATFORMS", + [Platform.DEVICE_TRACKER], + ): + await setup_integration(hass, mock_config_entry) + entity_entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + + assert entity_entries + for entity_entry in entity_entries: + assert hass.states.get(entity_entry.entity_id) == snapshot( + name=f"{entity_entry.entity_id}-state" + ) + assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") From 670bd97777537fe51df38a3bae52d7249ed87b63 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 19 Mar 2024 16:28:37 +0100 Subject: [PATCH 1280/1691] Find referenced labels in automations & scripts (#113812) --- .../components/automation/__init__.py | 27 +++++ homeassistant/components/script/__init__.py | 27 +++++ homeassistant/helpers/script.py | 10 +- tests/components/automation/test_init.py | 25 +++++ tests/components/script/test_init.py | 25 +++++ tests/helpers/test_script.py | 104 ++++++++++++++++++ 6 files changed, 217 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index be20ee17181..7b1b142719a 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -245,6 +245,18 @@ def floors_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: return _x_in_automation(hass, entity_id, "referenced_floors") +@callback +def automations_with_label(hass: HomeAssistant, label_id: str) -> list[str]: + """Return all automations that reference the label.""" + return _automations_with_x(hass, label_id, "referenced_labels") + + +@callback +def labels_in_automation(hass: HomeAssistant, entity_id: str) -> list[str]: + """Return all labels in an automation.""" + return _x_in_automation(hass, entity_id, "referenced_labels") + + @callback def automations_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list[str]: """Return all automations that reference the blueprint.""" @@ -353,6 +365,11 @@ class BaseAutomationEntity(ToggleEntity, ABC): return {CONF_ID: self.unique_id} return None + @cached_property + @abstractmethod + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + @cached_property @abstractmethod def referenced_floors(self) -> set[str]: @@ -413,6 +430,11 @@ class UnavailableAutomationEntity(BaseAutomationEntity): """Return the name of the entity.""" return self._name + @cached_property + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + return set() + @cached_property def referenced_floors(self) -> set[str]: """Return a set of referenced floors.""" @@ -505,6 +527,11 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): """Return True if entity is on.""" return self._async_detach_triggers is not None or self._is_enabled + @property + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + return self.action_script.referenced_labels + @property def referenced_floors(self) -> set[str]: """Return a set of referenced floors.""" diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index c742c42309f..954100c3a71 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -170,6 +170,18 @@ def floors_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: return _x_in_script(hass, entity_id, "referenced_floors") +@callback +def scripts_with_label(hass: HomeAssistant, label_id: str) -> list[str]: + """Return all scripts that reference the label.""" + return _scripts_with_x(hass, label_id, "referenced_labels") + + +@callback +def labels_in_script(hass: HomeAssistant, entity_id: str) -> list[str]: + """Return all labels in a script.""" + return _x_in_script(hass, entity_id, "referenced_labels") + + @callback def scripts_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list[str]: """Return all scripts that reference the blueprint.""" @@ -401,6 +413,11 @@ class BaseScriptEntity(ToggleEntity, ABC): raw_config: ConfigType | None + @cached_property + @abstractmethod + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + @cached_property @abstractmethod def referenced_floors(self) -> set[str]: @@ -451,6 +468,11 @@ class UnavailableScriptEntity(BaseScriptEntity): """Return the name of the entity.""" return self._name + @cached_property + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + return set() + @cached_property def referenced_floors(self) -> set[str]: """Return a set of referenced floors.""" @@ -539,6 +561,11 @@ class ScriptEntity(BaseScriptEntity, RestoreEntity): """Return true if script is on.""" return self.script.is_running + @cached_property + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + return self.script.referenced_labels + @cached_property def referenced_floors(self) -> set[str]: """Return a set of referenced floors.""" diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index d5d3534f793..4f9c1d113ea 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -27,6 +27,7 @@ from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ENTITY_ID, ATTR_FLOOR_ID, + ATTR_LABEL_ID, CONF_ALIAS, CONF_CHOOSE, CONF_CONDITION, @@ -1381,6 +1382,13 @@ class Script: """Return true if the current mode support max.""" return self.script_mode in (SCRIPT_MODE_PARALLEL, SCRIPT_MODE_QUEUED) + @cached_property + def referenced_labels(self) -> set[str]: + """Return a set of referenced labels.""" + referenced_labels: set[str] = set() + Script._find_referenced_target(ATTR_LABEL_ID, referenced_labels, self.sequence) + return referenced_labels + @cached_property def referenced_floors(self) -> set[str]: """Return a set of referenced fooors.""" @@ -1397,7 +1405,7 @@ class Script: @staticmethod def _find_referenced_target( - target: Literal["area_id", "floor_id"], + target: Literal["area_id", "floor_id", "label_id"], referenced: set[str], sequence: Sequence[dict[str, Any]], ) -> None: diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 7ee829c7bc9..234fc912118 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1563,6 +1563,8 @@ async def test_extraction_functions_not_setup(hass: HomeAssistant) -> None: assert automation.entities_in_automation(hass, "automation.test") == [] assert automation.automations_with_floor(hass, "floor-in-both") == [] assert automation.floors_in_automation(hass, "automation.test") == [] + assert automation.automations_with_label(hass, "label-in-both") == [] + assert automation.labels_in_automation(hass, "automation.test") == [] async def test_extraction_functions_unknown_automation(hass: HomeAssistant) -> None: @@ -1573,6 +1575,7 @@ async def test_extraction_functions_unknown_automation(hass: HomeAssistant) -> N assert automation.devices_in_automation(hass, "automation.unknown") == [] assert automation.entities_in_automation(hass, "automation.unknown") == [] assert automation.floors_in_automation(hass, "automation.unknown") == [] + assert automation.labels_in_automation(hass, "automation.unknown") == [] async def test_extraction_functions_unavailable_automation(hass: HomeAssistant) -> None: @@ -1600,6 +1603,8 @@ async def test_extraction_functions_unavailable_automation(hass: HomeAssistant) assert automation.entities_in_automation(hass, entity_id) == [] assert automation.automations_with_floor(hass, "floor-in-both") == [] assert automation.floors_in_automation(hass, entity_id) == [] + assert automation.automations_with_label(hass, "label-in-both") == [] + assert automation.labels_in_automation(hass, entity_id) == [] async def test_extraction_functions( @@ -1703,6 +1708,10 @@ async def test_extraction_functions( "service": "test.test", "target": {"floor_id": "floor-in-both"}, }, + { + "service": "test.test", + "target": {"label_id": "label-in-both"}, + }, ], }, { @@ -1830,6 +1839,14 @@ async def test_extraction_functions( "service": "test.test", "target": {"floor_id": "floor-in-last"}, }, + { + "service": "test.test", + "target": {"label_id": "label-in-both"}, + }, + { + "service": "test.test", + "target": {"label_id": "label-in-last"}, + }, ], }, ] @@ -1880,6 +1897,14 @@ async def test_extraction_functions( "floor-in-both", "floor-in-last", } + assert set(automation.automations_with_label(hass, "label-in-both")) == { + "automation.test1", + "automation.test3", + } + assert set(automation.labels_in_automation(hass, "automation.test3")) == { + "label-in-both", + "label-in-last", + } assert automation.blueprint_in_automation(hass, "automation.test3") is None diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 95fc3338333..02587d7bc98 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -686,11 +686,14 @@ async def test_extraction_functions_not_setup(hass: HomeAssistant) -> None: assert script.entities_in_script(hass, "script.test") == [] assert script.scripts_with_floor(hass, "floor-in-both") == [] assert script.floors_in_script(hass, "script.test") == [] + assert script.scripts_with_label(hass, "label-in-both") == [] + assert script.labels_in_script(hass, "script.test") == [] async def test_extraction_functions_unknown_script(hass: HomeAssistant) -> None: """Test extraction functions for an unknown script.""" assert await async_setup_component(hass, DOMAIN, {}) + assert script.labels_in_script(hass, "script.unknown") == [] assert script.floors_in_script(hass, "script.unknown") == [] assert script.areas_in_script(hass, "script.unknown") == [] assert script.blueprint_in_script(hass, "script.unknown") is None @@ -717,6 +720,8 @@ async def test_extraction_functions_unavailable_script(hass: HomeAssistant) -> N assert script.entities_in_script(hass, entity_id) == [] assert script.scripts_with_floor(hass, "floor-in-both") == [] assert script.floors_in_script(hass, entity_id) == [] + assert script.scripts_with_label(hass, "label-in-both") == [] + assert script.labels_in_script(hass, entity_id) == [] async def test_extraction_functions( @@ -765,6 +770,10 @@ async def test_extraction_functions( "service": "test.test", "target": {"floor_id": "floor-in-both"}, }, + { + "service": "test.test", + "target": {"label_id": "label-in-both"}, + }, ] }, "test2": { @@ -821,6 +830,14 @@ async def test_extraction_functions( "service": "test.test", "target": {"floor_id": "floor-in-last"}, }, + { + "service": "test.test", + "target": {"label_id": "label-in-both"}, + }, + { + "service": "test.test", + "target": {"label_id": "label-in-last"}, + }, ], }, } @@ -860,6 +877,14 @@ async def test_extraction_functions( "floor-in-both", "floor-in-last", } + assert set(script.scripts_with_label(hass, "label-in-both")) == { + "script.test1", + "script.test3", + } + assert set(script.labels_in_script(hass, "script.test3")) == { + "label-in-both", + "label-in-last", + } assert script.blueprint_in_script(hass, "script.test3") is None diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 4d20add4f3e..0e658f35702 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -3695,6 +3695,110 @@ async def test_propagate_error_service_exception(hass: HomeAssistant) -> None: assert_action_trace(expected_trace, expected_script_execution="error") +async def test_referenced_labels(hass: HomeAssistant) -> None: + """Test referenced labels.""" + script_obj = script.Script( + hass, + cv.SCRIPT_SCHEMA( + [ + { + "service": "test.script", + "data": {"label_id": "label_service_not_list"}, + }, + { + "service": "test.script", + "data": { + "label_id": ["label_service_list_1", "label_service_list_2"] + }, + }, + { + "service": "test.script", + "data": {"label_id": "{{ 'label_service_template' }}"}, + }, + { + "service": "test.script", + "target": {"label_id": "label_in_target"}, + }, + { + "service": "test.script", + "data_template": {"label_id": "label_in_data_template"}, + }, + {"service": "test.script", "data": {"without": "label_id"}}, + { + "choose": [ + { + "conditions": "{{ true == false }}", + "sequence": [ + { + "service": "test.script", + "data": {"label_id": "label_choice_1_seq"}, + } + ], + }, + { + "conditions": "{{ true == false }}", + "sequence": [ + { + "service": "test.script", + "data": {"label_id": "label_choice_2_seq"}, + } + ], + }, + ], + "default": [ + { + "service": "test.script", + "data": {"label_id": "label_default_seq"}, + } + ], + }, + {"event": "test_event"}, + {"delay": "{{ delay_period }}"}, + { + "if": [], + "then": [ + { + "service": "test.script", + "data": {"label_id": "label_if_then"}, + } + ], + "else": [ + { + "service": "test.script", + "data": {"label_id": "label_if_else"}, + } + ], + }, + { + "parallel": [ + { + "service": "test.script", + "data": {"label_id": "label_parallel"}, + } + ], + }, + ] + ), + "Test Name", + "test_domain", + ) + assert script_obj.referenced_labels == { + "label_choice_1_seq", + "label_choice_2_seq", + "label_default_seq", + "label_in_data_template", + "label_in_target", + "label_service_list_1", + "label_service_list_2", + "label_service_not_list", + "label_if_then", + "label_if_else", + "label_parallel", + } + # Test we cache results. + assert script_obj.referenced_labels is script_obj.referenced_labels + + async def test_referenced_floors(hass: HomeAssistant) -> None: """Test referenced floors.""" script_obj = script.Script( From 0fa395556db8ba97ec95d103c9a232f7ab964432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85ke=20Strandberg?= Date: Tue, 19 Mar 2024 17:27:16 +0100 Subject: [PATCH 1281/1691] Revert get_model from myuplink lib (#113811) --- homeassistant/components/myuplink/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/myuplink/__init__.py b/homeassistant/components/myuplink/__init__.py index 42bb9007789..5dee46b24cf 100644 --- a/homeassistant/components/myuplink/__init__.py +++ b/homeassistant/components/myuplink/__init__.py @@ -5,7 +5,7 @@ from __future__ import annotations from http import HTTPStatus from aiohttp import ClientError, ClientResponseError -from myuplink import MyUplinkAPI, get_manufacturer, get_model, get_system_name +from myuplink import MyUplinkAPI, get_manufacturer, get_system_name from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform @@ -92,7 +92,7 @@ def create_devices( identifiers={(DOMAIN, device_id)}, name=get_system_name(system), manufacturer=get_manufacturer(device), - model=get_model(device), + model=device.productName, sw_version=device.firmwareCurrent, serial_number=device.product_serial_number, ) From b35b4e8bfd54d97cefaddd9a4e6b9b91e7567ac7 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 19 Mar 2024 18:24:36 +0100 Subject: [PATCH 1282/1691] Do not patch `asyncio.timeout` in govee light local test (#113819) --- homeassistant/components/govee_light_local/__init__.py | 4 ++-- homeassistant/components/govee_light_local/config_flow.py | 3 +-- homeassistant/components/govee_light_local/const.py | 1 + tests/components/govee_light_local/test_light.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/govee_light_local/__init__.py b/homeassistant/components/govee_light_local/__init__.py index 5a791b9e3d1..d2537fb5c9b 100644 --- a/homeassistant/components/govee_light_local/__init__.py +++ b/homeassistant/components/govee_light_local/__init__.py @@ -9,7 +9,7 @@ from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from .const import DOMAIN +from .const import DISCOVERY_TIMEOUT, DOMAIN from .coordinator import GoveeLocalApiCoordinator PLATFORMS: list[Platform] = [Platform.LIGHT] @@ -26,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() try: - async with asyncio.timeout(delay=5): + async with asyncio.timeout(delay=DISCOVERY_TIMEOUT): while not coordinator.devices: await asyncio.sleep(delay=1) except TimeoutError as ex: diff --git a/homeassistant/components/govee_light_local/config_flow.py b/homeassistant/components/govee_light_local/config_flow.py index bcf9886f6b4..d31bfed0579 100644 --- a/homeassistant/components/govee_light_local/config_flow.py +++ b/homeassistant/components/govee_light_local/config_flow.py @@ -15,13 +15,12 @@ from .const import ( CONF_LISTENING_PORT_DEFAULT, CONF_MULTICAST_ADDRESS_DEFAULT, CONF_TARGET_PORT_DEFAULT, + DISCOVERY_TIMEOUT, DOMAIN, ) _LOGGER = logging.getLogger(__name__) -DISCOVERY_TIMEOUT = 5 - async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" diff --git a/homeassistant/components/govee_light_local/const.py b/homeassistant/components/govee_light_local/const.py index d9410c9c05e..a90a1ff1ff1 100644 --- a/homeassistant/components/govee_light_local/const.py +++ b/homeassistant/components/govee_light_local/const.py @@ -11,3 +11,4 @@ CONF_LISTENING_PORT_DEFAULT = 4002 CONF_DISCOVERY_INTERVAL_DEFAULT = 60 SCAN_INTERVAL = timedelta(seconds=30) +DISCOVERY_TIMEOUT = 5 diff --git a/tests/components/govee_light_local/test_light.py b/tests/components/govee_light_local/test_light.py index 66f471df267..3bc9da77fe5 100644 --- a/tests/components/govee_light_local/test_light.py +++ b/tests/components/govee_light_local/test_light.py @@ -131,8 +131,8 @@ async def test_light_setup_retry( entry.add_to_hass(hass) with patch( - "homeassistant.components.govee_light_local.asyncio.timeout", - side_effect=TimeoutError, + "homeassistant.components.govee_light_local.DISCOVERY_TIMEOUT", + 0, ): await hass.config_entries.async_setup(entry.entry_id) assert entry.state is ConfigEntryState.SETUP_RETRY From 15c0422837d52783533fa6afe569b85ef3538e69 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 09:17:12 -1000 Subject: [PATCH 1283/1691] Fix flakey profiler object growth tests (#113825) --- tests/components/profiler/test_init.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 2574f71a57b..8847e779b9e 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -9,6 +9,7 @@ from unittest.mock import patch from freezegun.api import FrozenDateTimeFactory from lru import LRU +import objgraph import pytest from homeassistant.components.profiler import ( @@ -115,30 +116,31 @@ async def test_object_growth_logging( assert hass.services.has_service(DOMAIN, SERVICE_START_LOG_OBJECTS) assert hass.services.has_service(DOMAIN, SERVICE_STOP_LOG_OBJECTS) - with patch("objgraph.growth"): + with patch.object(objgraph, "growth"): await hass.services.async_call( - DOMAIN, SERVICE_START_LOG_OBJECTS, {CONF_SCAN_INTERVAL: 10}, blocking=True + DOMAIN, SERVICE_START_LOG_OBJECTS, {CONF_SCAN_INTERVAL: 1}, blocking=True ) with pytest.raises(HomeAssistantError, match="Object logging already started"): await hass.services.async_call( DOMAIN, SERVICE_START_LOG_OBJECTS, - {CONF_SCAN_INTERVAL: 10}, + {CONF_SCAN_INTERVAL: 1}, blocking=True, ) assert "Growth" in caplog.text + await hass.async_block_till_done(wait_background_tasks=True) caplog.clear() - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=11)) - await hass.async_block_till_done() + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=2)) + await hass.async_block_till_done(wait_background_tasks=True) assert "Growth" in caplog.text await hass.services.async_call(DOMAIN, SERVICE_STOP_LOG_OBJECTS, {}, blocking=True) caplog.clear() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=21)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Growth" not in caplog.text with pytest.raises(HomeAssistantError, match="Object logging not running"): @@ -146,17 +148,18 @@ async def test_object_growth_logging( DOMAIN, SERVICE_STOP_LOG_OBJECTS, {}, blocking=True ) - with patch("objgraph.growth"): + with patch.object(objgraph, "growth"): await hass.services.async_call( DOMAIN, SERVICE_START_LOG_OBJECTS, {CONF_SCAN_INTERVAL: 10}, blocking=True ) + await hass.async_block_till_done(wait_background_tasks=True) caplog.clear() assert await hass.config_entries.async_unload(entry.entry_id) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Growth" not in caplog.text From 32c64855b2538b08701dbbf9158a12c7dab0ae46 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 09:17:23 -1000 Subject: [PATCH 1284/1691] Fix duplicate events in live logbook (#113828) --- homeassistant/components/logbook/websocket_api.py | 5 ++++- tests/components/logbook/test_websocket_api.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/logbook/websocket_api.py b/homeassistant/components/logbook/websocket_api.py index 6d976a50240..b7555d91241 100644 --- a/homeassistant/components/logbook/websocket_api.py +++ b/homeassistant/components/logbook/websocket_api.py @@ -423,7 +423,10 @@ async def ws_event_stream( hass, connection, msg_id, - last_event_time or start_time, + # Add one microsecond so we are outside the window of + # the last event we got from the database since otherwise + # we could fetch the same event twice + (last_event_time or start_time) + timedelta(microseconds=1), subscriptions_setup_complete_time, messages.event_message, event_processor, diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py index d17ed34cda4..f3689c379c5 100644 --- a/tests/components/logbook/test_websocket_api.py +++ b/tests/components/logbook/test_websocket_api.py @@ -2285,7 +2285,7 @@ async def test_live_stream_with_one_second_commit_interval( hass.bus.async_fire("mock_event", {"device_id": device.id, "message": "7"}) - while len(recieved_rows) != 7: + while len(recieved_rows) < 7: msg = await asyncio.wait_for(websocket_client.receive_json(), 2.5) assert msg["id"] == 7 assert msg["type"] == "event" From f96bb6754bc61e9c455cbc1d3c0cb9077c8f4363 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 19 Mar 2024 21:32:27 +0100 Subject: [PATCH 1285/1691] Remove deprecated `hass.components` from http test (#113823) --- tests/components/http/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 4ed2e56f1a5..35678c08d38 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -470,7 +470,7 @@ async def test_storing_config( async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200)) await hass.async_block_till_done() - restored = await hass.components.http.async_get_last_config() + restored = await http.async_get_last_config(hass) restored["trusted_proxies"][0] = ip_network(restored["trusted_proxies"][0]) assert restored == http.HTTP_SCHEMA(config) From ff03c9db196071546755c721bca9e3f4c3e9dcf9 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 19 Mar 2024 21:33:11 +0100 Subject: [PATCH 1286/1691] Remove deprecated `hass.components` from cloud client test (#113820) --- tests/components/cloud/test_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index 748d3de673b..6f1552527c4 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -8,6 +8,7 @@ from aiohttp import web from hass_nabucasa.client import RemoteActivationNotAllowed import pytest +from homeassistant.components import webhook from homeassistant.components.cloud import DOMAIN from homeassistant.components.cloud.client import ( VALID_REPAIR_TRANSLATION_KEYS, @@ -206,7 +207,7 @@ async def test_webhook_msg( received.append(request) return web.json_response({"from": "handler"}) - hass.components.webhook.async_register("test", "Test", "mock-webhook-id", handler) + webhook.async_register(hass, "test", "Test", "mock-webhook-id", handler) response = await cloud.client.async_webhook_message( { From c52ee2a898f35dc577971c7283fef13512cc9fc3 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 19 Mar 2024 21:33:40 +0100 Subject: [PATCH 1287/1691] Remove deprecated `hass.components` from person test (#113822) --- tests/components/person/test_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 6485f0ae7ac..b00a0ff1a6b 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -349,8 +349,8 @@ async def test_create_person_during_run(hass: HomeAssistant) -> None: hass.states.async_set(DEVICE_TRACKER, "home") await hass.async_block_till_done() - await hass.components.person.async_create_person( - "tracked person", device_trackers=[DEVICE_TRACKER] + await person.async_create_person( + hass, "tracked person", device_trackers=[DEVICE_TRACKER] ) await hass.async_block_till_done() From 879e5bc9612d2dbc1645c3d20baa62851641643d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 10:38:27 -1000 Subject: [PATCH 1288/1691] Only wait for import flows in setup of there is a config flow (#113780) --- homeassistant/setup.py | 16 ++++++++++------ tests/components/cloud/test_http_api.py | 3 +++ tests/test_bootstrap.py | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index f8a123ab797..522e7bd94ac 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -427,15 +427,19 @@ async def _async_setup_component( # noqa: C901 ) return False - # Flush out async_setup calling create_task. Fragile but covered by test. + if load_translations_task: + await load_translations_task + + if integration.platforms_exists(("config_flow",)): + # If the integration has a config_flow, flush out async_setup calling create_task + # with an asyncio.sleep(0) so we can wait for import flows. + # Fragile but covered by test. await asyncio.sleep(0) await hass.config_entries.flow.async_wait_import_flow_initialized(domain) - if load_translations_task: - await load_translations_task - # Add to components before the entry.async_setup - # call to avoid a deadlock when forwarding platforms - hass.config.components.add(domain) + # Add to components before the entry.async_setup + # call to avoid a deadlock when forwarding platforms + hass.config.components.add(domain) if entries := hass.config_entries.async_entries( domain, include_ignore=False, include_disabled=False diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index f7b88fab6c3..269b7b5d0c5 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -926,6 +926,8 @@ async def test_websocket_update_preferences_alexa_report_state( client = await hass_ws_client(hass) with patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_sync_entities" + ), patch( ( "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" ".async_get_access_token" @@ -941,6 +943,7 @@ async def test_websocket_update_preferences_alexa_report_state( response = await client.receive_json() set_authorized_mock.assert_called_once_with(True) + await hass.async_block_till_done() assert response["success"] diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 4dc322c5a29..fee058c4b52 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1121,6 +1121,7 @@ async def test_bootstrap_dependencies( # We patch the _import platform method to avoid loading the platform module # to avoid depending on non core components in the tests. mqtt_integration._import_platform = Mock() + mqtt_integration.platforms_exists = Mock(return_value=True) integrations = { "mqtt": { From 6b5518b2bf8f610245920b9d043fde72b02b0f50 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 10:44:04 -1000 Subject: [PATCH 1289/1691] Fix template sensor test relying on event bus debug logging (#113842) --- tests/components/template/test_sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 462f1350656..8ce5596a446 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -476,7 +476,11 @@ async def test_invalid_attribute_template( await hass.async_block_till_done() await async_update_entity(hass, "sensor.invalid_template") assert "TemplateError" in caplog_setup_text - assert "test_attribute" in caplog.text + assert ( + "Template variable error: 'None' has no attribute 'attributes' when rendering" + in caplog.text + ) + assert hass.states.get("sensor.invalid_template").state == "startup" @pytest.mark.parametrize(("count", "domain"), [(1, sensor.DOMAIN)]) From 658bef5447fe65739488de113caf488baeba392d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 11:01:23 -1000 Subject: [PATCH 1290/1691] Migrate samsungtv stop to use run_immediately (#113834) There is no need for a call_soon here --- homeassistant/components/samsungtv/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 9dcb2f9f57e..68a58710c19 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -149,7 +149,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await bridge.async_close_remote() entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_bridge) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, stop_bridge, run_immediately=True + ) ) await _async_update_ssdp_locations(hass, entry) From bf2155300879866cbbbac4e15c2db78ef1be294b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 19 Mar 2024 22:37:29 +0100 Subject: [PATCH 1291/1691] Fix startup race in cast (#113843) --- homeassistant/components/cast/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 28df9e4d7ef..b41dc9ddb41 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -25,9 +25,9 @@ PLATFORMS = [Platform.MEDIA_PLAYER] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Cast from a config entry.""" + hass.data[DOMAIN] = {"cast_platform": {}, "unknown_models": {}} await home_assistant_cast.async_setup_ha_cast(hass, entry) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - hass.data[DOMAIN] = {"cast_platform": {}, "unknown_models": {}} await async_process_integration_platforms(hass, DOMAIN, _register_cast_platform) return True From 8a9f69b22a64830b84a0c23a1c08ac72c28137b2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 19 Mar 2024 22:41:10 +0100 Subject: [PATCH 1292/1691] Bump pychromecast to 14.0.1 (#113841) --- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index d02bcd3558a..1d06ae23ca2 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -14,6 +14,6 @@ "documentation": "https://www.home-assistant.io/integrations/cast", "iot_class": "local_polling", "loggers": ["casttube", "pychromecast"], - "requirements": ["PyChromecast==14.0.0"], + "requirements": ["PyChromecast==14.0.1"], "zeroconf": ["_googlecast._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 700016c66bc..12197aca771 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -54,7 +54,7 @@ ProgettiHWSW==0.1.3 # PyBluez==0.22 # homeassistant.components.cast -PyChromecast==14.0.0 +PyChromecast==14.0.1 # homeassistant.components.flick_electric PyFlick==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 926ff05c009..bd1fa81f91f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -45,7 +45,7 @@ PlexAPI==4.15.10 ProgettiHWSW==0.1.3 # homeassistant.components.cast -PyChromecast==14.0.0 +PyChromecast==14.0.1 # homeassistant.components.flick_electric PyFlick==0.0.2 From ec3db0a6aac9690c229c1fdcf7d2ffdf318e661c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 13:58:59 -1000 Subject: [PATCH 1293/1691] Migrate camera listeners to use run_immediately (#113840) None of these need a call_soon --- homeassistant/components/camera/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 9671288e7ee..bfeab601352 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -417,7 +417,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: stream.add_provider("hls") await stream.start() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, preload_stream) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, preload_stream, run_immediately=True + ) @callback def update_tokens(t: datetime) -> None: @@ -435,7 +437,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Unsubscribe track time interval timer.""" unsub() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unsub_track_time_interval) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, unsub_track_time_interval, run_immediately=True + ) component.async_register_entity_service( SERVICE_ENABLE_MOTION, {}, "async_enable_motion_detection" From b26f0bc488c55656845bcfad8296564746dc803d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 13:59:37 -1000 Subject: [PATCH 1294/1691] Only create one executor job to shutdown wemo (#113836) Currently we created two but the work can be done in a single job --- homeassistant/components/wemo/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index fdb94de5f48..5cf0566278c 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -92,11 +92,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: discovery_responder = pywemo.ssdp.DiscoveryResponder(registry.port) await hass.async_add_executor_job(discovery_responder.start) - async def _on_hass_stop(_: Event) -> None: - await hass.async_add_executor_job(discovery_responder.stop) - await hass.async_add_executor_job(registry.stop) + def _on_hass_stop(_: Event) -> None: + discovery_responder.stop() + registry.stop() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _on_hass_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _on_hass_stop, run_immediately=True + ) yaml_config = config.get(DOMAIN, {}) hass.data[DOMAIN] = WemoData( From 7b67a486bd22f9c8bb7c18145beefea00128c4c8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 14:07:57 -1000 Subject: [PATCH 1295/1691] Migrate legacy device_tracker shutdown to use run_immediately (#113835) A call_soon is not needed here --- homeassistant/components/device_tracker/legacy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 72fc4ef717b..0f0d842c595 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -271,7 +271,9 @@ async def _async_setup_legacy_integration( """ cancel_update_stale() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _on_hass_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _on_hass_stop, run_immediately=True + ) @attr.s From 685468d84515295f4c101e1e79b99d183718f4f0 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 20 Mar 2024 01:09:48 +0100 Subject: [PATCH 1296/1691] Use `mock_platform` for light entity component tests instead of `hass.components` (#113845) * Use `mock_platform` for light entity component tests instead of `hass.components` * Move pytest_plugins to top * Fix comment --- .../components/light/test_device_condition.py | 9 +- tests/components/light/test_init.py | 362 +++++++++--------- tests/conftest.py | 5 +- tests/fixtures/pytest/__init__.py | 1 + tests/fixtures/pytest/light.py | 115 ++++++ 5 files changed, 307 insertions(+), 185 deletions(-) create mode 100644 tests/fixtures/pytest/__init__.py create mode 100644 tests/fixtures/pytest/light.py diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 489e2bf39d0..2dd06589966 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -22,6 +22,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) +from tests.fixtures.pytest.light import SetupLightPlatformCallable @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -323,7 +324,7 @@ async def test_if_fires_on_for_condition( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + setup_light_platform: SetupLightPlatformCallable, ) -> None: """Test for firing if condition is on with delay.""" config_entry = MockConfigEntry(domain="test", data={}) @@ -342,14 +343,10 @@ async def test_if_fires_on_for_condition( point2 = point1 + timedelta(seconds=10) point3 = point2 + timedelta(seconds=10) - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_light_platform() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() - ent1, ent2, ent3 = platform.ENTITIES - with freeze_time(point1) as freezer: assert await async_setup_component( hass, diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index dfb446beec3..57bd312421c 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1,5 +1,4 @@ """The tests for the Light component.""" - from unittest.mock import MagicMock, mock_open, patch import pytest @@ -23,6 +22,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.color as color_util from tests.common import MockEntityPlatform, MockUser, async_mock_service +from tests.fixtures.pytest.light import MockLight, SetupLightPlatformCallable orig_Profiles = light.Profiles @@ -108,18 +108,20 @@ async def test_methods(hass: HomeAssistant) -> None: async def test_services( - hass: HomeAssistant, mock_light_profiles, enable_custom_integrations: None + hass: HomeAssistant, + mock_light_profiles, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the provided services.""" - platform = getattr(hass.components, "test.light") + setup_light_platform() - platform.init() assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1, ent2, ent3 = platform.ENTITIES + ent1, ent2, ent3 = mock_light_entities ent1.supported_color_modes = [light.ColorMode.HS] ent3.supported_color_modes = [light.ColorMode.HS] ent1.supported_features = light.LightEntityFeature.TRANSITION @@ -509,11 +511,11 @@ async def test_light_profiles( profile_name, expected_data, last_call, - enable_custom_integrations: None, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test light profiles.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform() profile_mock_data = { "test": (0.4, 0.6, 100, 0), @@ -533,7 +535,7 @@ async def test_light_profiles( ) await hass.async_block_till_done() - ent1, _, _ = platform.ENTITIES + ent1, _, _ = mock_light_entities ent1.supported_color_modes = [light.ColorMode.HS] ent1.supported_features = light.LightEntityFeature.TRANSITION @@ -556,11 +558,13 @@ async def test_light_profiles( async def test_default_profiles_group( - hass: HomeAssistant, mock_light_profiles, enable_custom_integrations: None + hass: HomeAssistant, + mock_light_profiles, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test default turn-on light profile for all lights.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform() assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -570,7 +574,7 @@ async def test_default_profiles_group( profile = light.Profile("group.all_lights.default", 0.4, 0.6, 99, 2) mock_light_profiles[profile.name] = profile - ent, _, _ = platform.ENTITIES + ent, _, _ = mock_light_entities ent.supported_color_modes = [light.ColorMode.HS] ent.supported_features = light.LightEntityFeature.TRANSITION await hass.services.async_call( @@ -780,13 +784,13 @@ async def test_default_profiles_light( hass: HomeAssistant, mock_light_profiles, extra_call_params, - enable_custom_integrations: None, expected_params_state_was_off, expected_params_state_was_on, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test default turn-on light profile for a specific light.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform() assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -798,7 +802,7 @@ async def test_default_profiles_light( profile = light.Profile("light.ceiling_2.default", 0.6, 0.6, 100, 3) mock_light_profiles[profile.name] = profile - dev = next(filter(lambda x: x.entity_id == "light.ceiling_2", platform.ENTITIES)) + dev = next(filter(lambda x: x.entity_id == "light.ceiling_2", mock_light_entities)) dev.supported_color_modes = { light.ColorMode.COLOR_TEMP, light.ColorMode.HS, @@ -850,11 +854,13 @@ async def test_default_profiles_light( async def test_light_context( - hass: HomeAssistant, hass_admin_user: MockUser, enable_custom_integrations: None + hass: HomeAssistant, + hass_admin_user: MockUser, + setup_light_platform: SetupLightPlatformCallable, ) -> None: """Test that light context works.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform() + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -876,11 +882,13 @@ async def test_light_context( async def test_light_turn_on_auth( - hass: HomeAssistant, hass_read_only_user: MockUser, enable_custom_integrations: None + hass: HomeAssistant, + hass_read_only_user: MockUser, + setup_light_platform: SetupLightPlatformCallable, ) -> None: """Test that light context works.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform() + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -900,20 +908,23 @@ async def test_light_turn_on_auth( async def test_light_brightness_step( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test that light context works.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) - platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON)) - entity0 = platform.ENTITIES[0] + entities = [ + MockLight("Test_0", STATE_ON), + MockLight("Test_1", STATE_ON), + ] + + setup_light_platform(entities) + + entity0 = entities[0] entity0.supported_features = light.SUPPORT_BRIGHTNESS # Set color modes to none to trigger backwards compatibility in LightEntity entity0.supported_color_modes = None entity0.color_mode = None entity0.brightness = 100 - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_features = light.SUPPORT_BRIGHTNESS # Set color modes to none to trigger backwards compatibility in LightEntity entity1.supported_color_modes = None @@ -970,12 +981,15 @@ async def test_light_brightness_step( async def test_light_brightness_pct_conversion( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + enable_custom_integrations: None, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test that light brightness percent conversion.""" - platform = getattr(hass.components, "test.light") - platform.init() - entity = platform.ENTITIES[0] + setup_light_platform() + + entity = mock_light_entities[0] entity.supported_features = light.SUPPORT_BRIGHTNESS # Set color modes to none to trigger backwards compatibility in LightEntity entity.supported_color_modes = None @@ -1130,39 +1144,38 @@ invalid_no_brightness_no_color_no_transition,,, @pytest.mark.parametrize("light_state", [STATE_ON, STATE_OFF]) async def test_light_backwards_compatibility_supported_color_modes( - hass: HomeAssistant, light_state, enable_custom_integrations: None + hass: HomeAssistant, light_state, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test supported_color_modes if not implemented by the entity.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_0", light_state), + MockLight("Test_1", light_state), + MockLight("Test_2", light_state), + MockLight("Test_3", light_state), + MockLight("Test_4", light_state), + ] - platform.ENTITIES.append(platform.MockLight("Test_0", light_state)) - platform.ENTITIES.append(platform.MockLight("Test_1", light_state)) - platform.ENTITIES.append(platform.MockLight("Test_2", light_state)) - platform.ENTITIES.append(platform.MockLight("Test_3", light_state)) - platform.ENTITIES.append(platform.MockLight("Test_4", light_state)) + entity0 = entities[0] - entity0 = platform.ENTITIES[0] - - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_features = light.SUPPORT_BRIGHTNESS # Set color modes to none to trigger backwards compatibility in LightEntity entity1.supported_color_modes = None entity1.color_mode = None - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP # Set color modes to none to trigger backwards compatibility in LightEntity entity2.supported_color_modes = None entity2.color_mode = None - entity3 = platform.ENTITIES[3] + entity3 = entities[3] entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR # Set color modes to none to trigger backwards compatibility in LightEntity entity3.supported_color_modes = None entity3.color_mode = None - entity4 = platform.ENTITIES[4] + entity4 = entities[4] entity4.supported_features = ( light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP ) @@ -1170,6 +1183,8 @@ async def test_light_backwards_compatibility_supported_color_modes( entity4.supported_color_modes = None entity4.color_mode = None + setup_light_platform(entities) + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1213,42 +1228,41 @@ async def test_light_backwards_compatibility_supported_color_modes( async def test_light_backwards_compatibility_color_mode( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color_mode if not implemented by the entity.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_0", STATE_ON), + MockLight("Test_1", STATE_ON), + MockLight("Test_2", STATE_ON), + MockLight("Test_3", STATE_ON), + MockLight("Test_4", STATE_ON), + ] - platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_2", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_3", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_4", STATE_ON)) + entity0 = entities[0] - entity0 = platform.ENTITIES[0] - - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_features = light.SUPPORT_BRIGHTNESS # Set color modes to none to trigger backwards compatibility in LightEntity entity1.supported_color_modes = None entity1.color_mode = None entity1.brightness = 100 - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP # Set color modes to none to trigger backwards compatibility in LightEntity entity2.supported_color_modes = None entity2.color_mode = None entity2.color_temp_kelvin = 10000 - entity3 = platform.ENTITIES[3] + entity3 = entities[3] entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR # Set color modes to none to trigger backwards compatibility in LightEntity entity3.supported_color_modes = None entity3.color_mode = None entity3.hs_color = (240, 100) - entity4 = platform.ENTITIES[4] + entity4 = entities[4] entity4.supported_features = ( light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP ) @@ -1258,6 +1272,8 @@ async def test_light_backwards_compatibility_color_mode( entity4.hs_color = (240, 100) entity4.color_temp_kelvin = 10000 + setup_light_platform(entities) + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1290,17 +1306,14 @@ async def test_light_backwards_compatibility_color_mode( async def test_light_service_call_rgbw( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test rgbw functionality in service calls.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) - - platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = MockLight("Test_rgbw", STATE_ON) entity0.supported_color_modes = {light.ColorMode.RGBW} + setup_light_platform([entity0]) + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1323,24 +1336,24 @@ async def test_light_service_call_rgbw( async def test_light_state_off( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test rgbw color conversion in state updates.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_onoff", STATE_OFF), + MockLight("Test_brightness", STATE_OFF), + MockLight("Test_ct", STATE_OFF), + MockLight("Test_rgbw", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_onoff", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("Test_brightness", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("Test_ct", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.ONOFF} - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {light.ColorMode.BRIGHTNESS} - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {light.ColorMode.COLOR_TEMP} - entity3 = platform.ENTITIES[3] + entity3 = entities[3] entity3.supported_color_modes = {light.ColorMode.RGBW} assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) @@ -1396,15 +1409,12 @@ async def test_light_state_off( async def test_light_state_rgbw( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test rgbw color conversion in state updates.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entity0 = MockLight("Test_rgbw", STATE_ON) + setup_light_platform([entity0]) - platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) - - entity0 = platform.ENTITIES[0] entity0.brightness = 255 entity0.supported_color_modes = {light.ColorMode.RGBW} entity0.color_mode = light.ColorMode.RGBW @@ -1432,15 +1442,12 @@ async def test_light_state_rgbw( async def test_light_state_rgbww( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test rgbww color conversion in state updates.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entity0 = MockLight("Test_rgbww", STATE_ON) + setup_light_platform([entity0]) - platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON)) - - entity0 = platform.ENTITIES[0] entity0.supported_color_modes = {light.ColorMode.RGBWW} entity0.color_mode = light.ColorMode.RGBWW entity0.hs_color = "Invalid" # Should be ignored @@ -1468,50 +1475,50 @@ async def test_light_state_rgbww( async def test_light_service_call_color_conversion( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color conversion in service calls.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_hs", STATE_ON), + MockLight("Test_rgb", STATE_ON), + MockLight("Test_xy", STATE_ON), + MockLight("Test_all", STATE_ON), + MockLight("Test_legacy", STATE_ON), + MockLight("Test_rgbw", STATE_ON), + MockLight("Test_rgbww", STATE_ON), + MockLight("Test_temperature", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_all", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_temperature", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {light.ColorMode.RGB} - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {light.ColorMode.XY} - entity3 = platform.ENTITIES[3] + entity3 = entities[3] entity3.supported_color_modes = { light.ColorMode.HS, light.ColorMode.RGB, light.ColorMode.XY, } - entity4 = platform.ENTITIES[4] + entity4 = entities[4] entity4.supported_features = light.SUPPORT_COLOR # Set color modes to none to trigger backwards compatibility in LightEntity entity4.supported_color_modes = None entity4.color_mode = None - entity5 = platform.ENTITIES[5] + entity5 = entities[5] entity5.supported_color_modes = {light.ColorMode.RGBW} - entity6 = platform.ENTITIES[6] + entity6 = entities[6] entity6.supported_color_modes = {light.ColorMode.RGBWW} - entity7 = platform.ENTITIES[7] + entity7 = entities[7] entity7.supported_color_modes = {light.ColorMode.COLOR_TEMP} assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) @@ -1913,46 +1920,46 @@ async def test_light_service_call_color_conversion( async def test_light_service_call_color_conversion_named_tuple( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test a named tuple (RGBColor) is handled correctly.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_hs", STATE_ON), + MockLight("Test_rgb", STATE_ON), + MockLight("Test_xy", STATE_ON), + MockLight("Test_all", STATE_ON), + MockLight("Test_legacy", STATE_ON), + MockLight("Test_rgbw", STATE_ON), + MockLight("Test_rgbww", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_all", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {light.ColorMode.RGB} - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {light.ColorMode.XY} - entity3 = platform.ENTITIES[3] + entity3 = entities[3] entity3.supported_color_modes = { light.ColorMode.HS, light.ColorMode.RGB, light.ColorMode.XY, } - entity4 = platform.ENTITIES[4] + entity4 = entities[4] entity4.supported_features = light.SUPPORT_COLOR # Set color modes to none to trigger backwards compatibility in LightEntity entity4.supported_color_modes = None entity4.color_mode = None - entity5 = platform.ENTITIES[5] + entity5 = entities[5] entity5.supported_color_modes = {light.ColorMode.RGBW} - entity6 = platform.ENTITIES[6] + entity6 = entities[6] entity6.supported_color_modes = {light.ColorMode.RGBWW} assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) @@ -1993,23 +2000,23 @@ async def test_light_service_call_color_conversion_named_tuple( async def test_light_service_call_color_temp_emulation( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color conversion in service calls.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_hs_ct", STATE_ON), + MockLight("Test_hs", STATE_ON), + MockLight("Test_hs_white", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_hs_ct", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_hs_white", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.COLOR_TEMP, light.ColorMode.HS} - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {light.ColorMode.HS} - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {light.ColorMode.HS, light.ColorMode.WHITE} assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) @@ -2053,22 +2060,22 @@ async def test_light_service_call_color_temp_emulation( async def test_light_service_call_color_temp_conversion( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color temp conversion in service calls.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_rgbww_ct", STATE_ON), + MockLight("Test_rgbww", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_rgbww_ct", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = { light.ColorMode.COLOR_TEMP, light.ColorMode.RGBWW, } - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {light.ColorMode.RGBWW} assert entity1.min_mireds == 153 assert entity1.max_mireds == 500 @@ -2186,16 +2193,16 @@ async def test_light_service_call_color_temp_conversion( async def test_light_mired_color_temp_conversion( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color temp conversion from K to legacy mired.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_rgbww_ct", STATE_ON), + MockLight("Test_rgbww", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_rgbww_ct", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = { light.ColorMode.COLOR_TEMP, } @@ -2234,15 +2241,12 @@ async def test_light_mired_color_temp_conversion( async def test_light_service_call_white_mode( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color_mode white in service calls.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) - - platform.ENTITIES.append(platform.MockLight("Test_white", STATE_ON)) - entity0 = platform.ENTITIES[0] + entity0 = MockLight("Test_white", STATE_ON) entity0.supported_color_modes = {light.ColorMode.HS, light.ColorMode.WHITE} + setup_light_platform([entity0]) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -2338,39 +2342,39 @@ async def test_light_service_call_white_mode( async def test_light_state_color_conversion( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color conversion in state updates.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("Test_hs", STATE_ON), + MockLight("Test_rgb", STATE_ON), + MockLight("Test_xy", STATE_ON), + MockLight("Test_legacy", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} entity0.color_mode = light.ColorMode.HS entity0.hs_color = (240, 100) entity0.rgb_color = "Invalid" # Should be ignored entity0.xy_color = "Invalid" # Should be ignored - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {light.ColorMode.RGB} entity1.color_mode = light.ColorMode.RGB entity1.hs_color = "Invalid" # Should be ignored entity1.rgb_color = (128, 0, 0) entity1.xy_color = "Invalid" # Should be ignored - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {light.ColorMode.XY} entity2.color_mode = light.ColorMode.XY entity2.hs_color = "Invalid" # Should be ignored entity2.rgb_color = "Invalid" # Should be ignored entity2.xy_color = (0.1, 0.8) - entity3 = platform.ENTITIES[3] + entity3 = entities[3] entity3.hs_color = (240, 100) entity3.supported_features = light.SUPPORT_COLOR # Set color modes to none to trigger backwards compatibility in LightEntity @@ -2406,18 +2410,20 @@ async def test_light_state_color_conversion( async def test_services_filter_parameters( - hass: HomeAssistant, mock_light_profiles, enable_custom_integrations: None + hass: HomeAssistant, + mock_light_profiles, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test turn_on and turn_off filters unsupported parameters.""" - platform = getattr(hass.components, "test.light") + setup_light_platform() - platform.init() assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1, _, _ = platform.ENTITIES + ent1, _, _ = mock_light_entities # turn off the light by setting brightness to 0, this should work even if the light # doesn't support brightness diff --git a/tests/conftest.py b/tests/conftest.py index 1159f66a698..e21a5e3d92b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -105,7 +105,6 @@ from .test_util.aiohttp import ( # noqa: E402, isort:skip mock_aiohttp_client, ) - _LOGGER = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -115,6 +114,10 @@ asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False)) # Disable fixtures overriding our beautiful policy asyncio.set_event_loop_policy = lambda policy: None +pytest_plugins = [ + "tests.fixtures.pytest.light", +] + def pytest_addoption(parser: pytest.Parser) -> None: """Register custom pytest options.""" diff --git a/tests/fixtures/pytest/__init__.py b/tests/fixtures/pytest/__init__.py new file mode 100644 index 00000000000..f792ac8b29e --- /dev/null +++ b/tests/fixtures/pytest/__init__.py @@ -0,0 +1 @@ +"""Fixtures for tests.""" diff --git a/tests/fixtures/pytest/light.py b/tests/fixtures/pytest/light.py new file mode 100644 index 00000000000..e81d2d9ba94 --- /dev/null +++ b/tests/fixtures/pytest/light.py @@ -0,0 +1,115 @@ +"""Fixtures for the light entity component tests.""" +from collections.abc import Callable + +import pytest + +from homeassistant.components.light import DOMAIN, ColorMode, LightEntity +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType + +from tests.common import MockPlatform, MockToggleEntity, mock_platform + +TURN_ON_ARG_TO_COLOR_MODE = { + "hs_color": ColorMode.HS, + "xy_color": ColorMode.XY, + "rgb_color": ColorMode.RGB, + "rgbw_color": ColorMode.RGBW, + "rgbww_color": ColorMode.RGBWW, + "color_temp_kelvin": ColorMode.COLOR_TEMP, +} + + +class MockLight(MockToggleEntity, LightEntity): + """Mock light class.""" + + _attr_max_color_temp_kelvin = 6500 + _attr_min_color_temp_kelvin = 2000 + supported_features = 0 + + brightness = None + color_temp_kelvin = None + hs_color = None + rgb_color = None + rgbw_color = None + rgbww_color = None + xy_color = None + + def __init__( + self, + name, + state, + unique_id=None, + supported_color_modes: set[ColorMode] | None = None, + ): + """Initialize the mock light.""" + super().__init__(name, state, unique_id) + if supported_color_modes is None: + supported_color_modes = {ColorMode.ONOFF} + self._attr_supported_color_modes = supported_color_modes + color_mode = ColorMode.UNKNOWN + if len(supported_color_modes) == 1: + color_mode = next(iter(supported_color_modes)) + self._attr_color_mode = color_mode + + def turn_on(self, **kwargs): + """Turn the entity on.""" + super().turn_on(**kwargs) + for key, value in kwargs.items(): + if key in [ + "brightness", + "hs_color", + "xy_color", + "rgb_color", + "rgbw_color", + "rgbww_color", + "color_temp_kelvin", + ]: + setattr(self, key, value) + if key == "white": + setattr(self, "brightness", value) + if key in TURN_ON_ARG_TO_COLOR_MODE: + self._attr_color_mode = TURN_ON_ARG_TO_COLOR_MODE[key] + + +SetupLightPlatformCallable = Callable[[list[MockLight] | None], None] + + +@pytest.fixture +async def mock_light_entities() -> list[MockLight]: + """Return mocked light entities.""" + return [ + MockLight("Ceiling", STATE_ON), + MockLight("Ceiling", STATE_OFF), + MockLight(None, STATE_OFF), + ] + + +@pytest.fixture +async def setup_light_platform( + hass: HomeAssistant, mock_light_entities: list[MockLight] +) -> SetupLightPlatformCallable: + """Set up the mock light entity platform.""" + + def _setup(entities: list[MockLight] | None = None) -> None: + """Set up the mock light entity platform.""" + + async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, + ) -> None: + """Set up test light platform.""" + async_add_entities( + entities if entities is not None else mock_light_entities + ) + + mock_platform( + hass, + f"test.{DOMAIN}", + MockPlatform(async_setup_platform=async_setup_platform), + ) + + return _setup From 9add4aea70414d331b7f825d360e56c696084466 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 20 Mar 2024 01:13:05 +0100 Subject: [PATCH 1297/1691] Remove deprecated `hass.components` from vilfo config flow tests (#113821) --- tests/components/vilfo/test_config_flow.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index c8bae610e73..7077c4f2acc 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -5,6 +5,7 @@ from unittest.mock import Mock, patch import vilfo from homeassistant import config_entries, data_entry_flow +from homeassistant.components.vilfo import config_flow from homeassistant.components.vilfo.const import DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC from homeassistant.core import HomeAssistant @@ -173,9 +174,7 @@ async def test_validate_input_returns_data(hass: HomeAssistant) -> None: ), patch( "vilfo.Client.resolve_firmware_version", return_value=firmware_version ), patch("vilfo.Client.resolve_mac_address", return_value=None): - result = await hass.components.vilfo.config_flow.validate_input( - hass, data=mock_data - ) + result = await config_flow.validate_input(hass, data=mock_data) assert result["title"] == mock_data["host"] assert result[CONF_HOST] == mock_data["host"] @@ -187,15 +186,9 @@ async def test_validate_input_returns_data(hass: HomeAssistant) -> None: ), patch( "vilfo.Client.resolve_firmware_version", return_value=firmware_version ), patch("vilfo.Client.resolve_mac_address", return_value=mock_mac): - result2 = await hass.components.vilfo.config_flow.validate_input( - hass, data=mock_data - ) - result3 = await hass.components.vilfo.config_flow.validate_input( - hass, data=mock_data_with_ip - ) - result4 = await hass.components.vilfo.config_flow.validate_input( - hass, data=mock_data_with_ipv6 - ) + result2 = await config_flow.validate_input(hass, data=mock_data) + result3 = await config_flow.validate_input(hass, data=mock_data_with_ip) + result4 = await config_flow.validate_input(hass, data=mock_data_with_ipv6) assert result2["title"] == mock_data["host"] assert result2[CONF_HOST] == mock_data["host"] From 9a38f0de0b83189ca18b68bd8ebd5ae7daf8e4d7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 20 Mar 2024 01:16:21 +0100 Subject: [PATCH 1298/1691] Update actions/cache to 4.0.2 (#113817) --- .github/workflows/ci.yaml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 73443306a49..b771ecb340f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -231,7 +231,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v4.0.1 + uses: actions/cache@v4.0.2 with: path: venv key: >- @@ -247,7 +247,7 @@ jobs: uv pip install "$(cat requirements_test.txt | grep pre-commit)" - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache@v4.0.1 + uses: actions/cache@v4.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} lookup-only: true @@ -277,7 +277,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -286,7 +286,7 @@ jobs: needs.info.outputs.pre-commit_cache_key }} - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -317,7 +317,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -326,7 +326,7 @@ jobs: needs.info.outputs.pre-commit_cache_key }} - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -356,7 +356,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -365,7 +365,7 @@ jobs: needs.info.outputs.pre-commit_cache_key }} - name: Restore pre-commit environment from cache id: cache-precommit - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: ${{ env.PRE_COMMIT_CACHE }} fail-on-cache-miss: true @@ -455,7 +455,7 @@ jobs: env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache@v4.0.1 + uses: actions/cache@v4.0.2 with: path: venv lookup-only: true @@ -464,7 +464,7 @@ jobs: needs.info.outputs.python_cache_key }} - name: Restore uv wheel cache if: steps.cache-venv.outputs.cache-hit != 'true' - uses: actions/cache@v4.0.1 + uses: actions/cache@v4.0.2 with: path: ${{ env.UV_CACHE_DIR }} key: >- @@ -519,7 +519,7 @@ jobs: check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -551,7 +551,7 @@ jobs: check-latest: true - name: Restore base Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -584,7 +584,7 @@ jobs: check-latest: true - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -635,7 +635,7 @@ jobs: env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -643,7 +643,7 @@ jobs: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ needs.info.outputs.python_cache_key }} - name: Restore mypy cache - uses: actions/cache@v4.0.1 + uses: actions/cache@v4.0.2 with: path: .mypy_cache key: >- @@ -710,7 +710,7 @@ jobs: check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -857,7 +857,7 @@ jobs: check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true @@ -979,7 +979,7 @@ jobs: check-latest: true - name: Restore full Python ${{ matrix.python-version }} virtual environment id: cache-venv - uses: actions/cache/restore@v4.0.1 + uses: actions/cache/restore@v4.0.2 with: path: venv fail-on-cache-miss: true From 02c1088596ca47f3fff971324ef3f7c3dd80ce28 Mon Sep 17 00:00:00 2001 From: Jessica Smith <8505845+NodeJSmith@users.noreply.github.com> Date: Tue, 19 Mar 2024 20:02:45 -0500 Subject: [PATCH 1299/1691] Upgrade whirlpool integration to add shared appliances and allow brand selection (#111687) * update to 1.18.5 and add Brand to config, required for getting shared appliances * update version to 0.18.6 * start fixing tests * fix typo * check for falsy values instead of explicit None * move CONF_BRAND from global constants to whirlpool constants * add test for no brand, fix __init__ import * add brand to string.json * add brand to re-auth * add title/description, add brand info to description * add reauth strings * pass already initialized data dict Co-authored-by: Martin Hjelmare * remove trailing comma * Update strings again * fix reauth tests to add brand --------- Co-authored-by: Martin Hjelmare --- .../components/whirlpool/__init__.py | 6 +- .../components/whirlpool/config_flow.py | 30 ++++----- homeassistant/components/whirlpool/const.py | 9 ++- .../components/whirlpool/manifest.json | 2 +- homeassistant/components/whirlpool/sensor.py | 2 +- .../components/whirlpool/strings.json | 20 +++++- homeassistant/components/whirlpool/util.py | 8 --- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/whirlpool/__init__.py | 8 ++- tests/components/whirlpool/conftest.py | 15 ++++- .../components/whirlpool/test_config_flow.py | 65 ++++++++++--------- tests/components/whirlpool/test_init.py | 32 ++++++++- 13 files changed, 130 insertions(+), 71 deletions(-) delete mode 100644 homeassistant/components/whirlpool/util.py diff --git a/homeassistant/components/whirlpool/__init__.py b/homeassistant/components/whirlpool/__init__.py index 919f9b9be89..36f8fbec59d 100644 --- a/homeassistant/components/whirlpool/__init__.py +++ b/homeassistant/components/whirlpool/__init__.py @@ -14,8 +14,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import CONF_REGIONS_MAP, DOMAIN -from .util import get_brand_for_region +from .const import CONF_BRAND, CONF_BRANDS_MAP, CONF_REGIONS_MAP, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -28,8 +27,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: session = async_get_clientsession(hass) region = CONF_REGIONS_MAP[entry.data.get(CONF_REGION, "EU")] - brand = get_brand_for_region(region) + brand = CONF_BRANDS_MAP[entry.data.get(CONF_BRAND, "Whirlpool")] backend_selector = BackendSelector(brand, region) + auth = Auth( backend_selector, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], session ) diff --git a/homeassistant/components/whirlpool/config_flow.py b/homeassistant/components/whirlpool/config_flow.py index e8ab765feba..5e1cb102d77 100644 --- a/homeassistant/components/whirlpool/config_flow.py +++ b/homeassistant/components/whirlpool/config_flow.py @@ -18,8 +18,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import CONF_REGIONS_MAP, DOMAIN -from .util import get_brand_for_region +from .const import CONF_BRAND, CONF_BRANDS_MAP, CONF_REGIONS_MAP, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -29,10 +28,16 @@ STEP_USER_DATA_SCHEMA = vol.Schema( vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(CONF_REGION): vol.In(list(CONF_REGIONS_MAP)), + vol.Required(CONF_BRAND): vol.In(list(CONF_BRANDS_MAP)), } ) -REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) +REAUTH_SCHEMA = vol.Schema( + { + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_BRAND): vol.In(list(CONF_BRANDS_MAP)), + } +) async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: @@ -42,7 +47,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, """ session = async_get_clientsession(hass) region = CONF_REGIONS_MAP[data[CONF_REGION]] - brand = get_brand_for_region(region) + brand = CONF_BRANDS_MAP[data[CONF_BRAND]] backend_selector = BackendSelector(brand, region) auth = Auth(backend_selector, data[CONF_USERNAME], data[CONF_PASSWORD], session) try: @@ -55,7 +60,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, appliances_manager = AppliancesManager(backend_selector, auth, session) await appliances_manager.fetch_appliances() - if appliances_manager.aircons is None and appliances_manager.washer_dryers is None: + + if not appliances_manager.aircons and not appliances_manager.washer_dryers: raise NoAppliances return {"title": data[CONF_USERNAME]} @@ -84,10 +90,8 @@ class WhirlpoolConfigFlow(ConfigFlow, domain=DOMAIN): if user_input: assert self.entry is not None password = user_input[CONF_PASSWORD] - data = { - **self.entry.data, - CONF_PASSWORD: password, - } + brand = user_input[CONF_BRAND] + data = {**self.entry.data, CONF_PASSWORD: password, CONF_BRAND: brand} try: await validate_input(self.hass, data) @@ -96,13 +100,7 @@ class WhirlpoolConfigFlow(ConfigFlow, domain=DOMAIN): except (CannotConnect, TimeoutError): errors["base"] = "cannot_connect" else: - self.hass.config_entries.async_update_entry( - self.entry, - data={ - **self.entry.data, - CONF_PASSWORD: password, - }, - ) + self.hass.config_entries.async_update_entry(self.entry, data=data) await self.hass.config_entries.async_reload(self.entry.entry_id) return self.async_abort(reason="reauth_successful") diff --git a/homeassistant/components/whirlpool/const.py b/homeassistant/components/whirlpool/const.py index b472c7f9156..63a58f54c1d 100644 --- a/homeassistant/components/whirlpool/const.py +++ b/homeassistant/components/whirlpool/const.py @@ -1,10 +1,17 @@ """Constants for the Whirlpool Appliances integration.""" -from whirlpool.backendselector import Region +from whirlpool.backendselector import Brand, Region DOMAIN = "whirlpool" +CONF_BRAND = "brand" CONF_REGIONS_MAP = { "EU": Region.EU, "US": Region.US, } + +CONF_BRANDS_MAP = { + "Whirlpool": Brand.Whirlpool, + "Maytag": Brand.Maytag, + "KitchenAid": Brand.KitchenAid, +} diff --git a/homeassistant/components/whirlpool/manifest.json b/homeassistant/components/whirlpool/manifest.json index 4c3ce680323..0c46580ceeb 100644 --- a/homeassistant/components/whirlpool/manifest.json +++ b/homeassistant/components/whirlpool/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["whirlpool"], - "requirements": ["whirlpool-sixth-sense==0.18.4"] + "requirements": ["whirlpool-sixth-sense==0.18.6"] } diff --git a/homeassistant/components/whirlpool/sensor.py b/homeassistant/components/whirlpool/sensor.py index d49459cc726..8c74f01298e 100644 --- a/homeassistant/components/whirlpool/sensor.py +++ b/homeassistant/components/whirlpool/sensor.py @@ -206,7 +206,7 @@ class WasherDryerClass(SensorEntity): self._wd.register_attr_callback(self.async_write_ha_state) async def async_will_remove_from_hass(self) -> None: - """Close Whrilpool Appliance sockets before removing.""" + """Close Whirlpool Appliance sockets before removing.""" self._wd.unregister_attr_callback(self.async_write_ha_state) @property diff --git a/homeassistant/components/whirlpool/strings.json b/homeassistant/components/whirlpool/strings.json index a24e42304d0..b1658947263 100644 --- a/homeassistant/components/whirlpool/strings.json +++ b/homeassistant/components/whirlpool/strings.json @@ -2,9 +2,27 @@ "config": { "step": { "user": { + "title": "Configure your Whirlpool account", "data": { "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "password": "[%key:common::config_flow::data::password%]", + "region": "Region", + "brand": "Brand" + }, + "data_description": { + "brand": "Please choose the brand of the mobile app you use, or the brand of the appliances in your account" + } + }, + "reauth_confirm": { + "title": "Correct your Whirlpool account credentials", + "description": "For 'brand', please choose the brand of the mobile app you use, or the brand of the appliances in your account", + "data": { + "password": "[%key:common::config_flow::data::password%]", + "region": "Region", + "brand": "Brand" + }, + "data_description": { + "brand": "Please choose the brand of the mobile app you use, or the brand of the appliances in your account" } } }, diff --git a/homeassistant/components/whirlpool/util.py b/homeassistant/components/whirlpool/util.py deleted file mode 100644 index 55b094f76ac..00000000000 --- a/homeassistant/components/whirlpool/util.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Utility functions for the Whirlpool Sixth Sense integration.""" - -from whirlpool.backendselector import Brand, Region - - -def get_brand_for_region(region: Region) -> Brand: - """Get the correct brand for each region.""" - return Brand.Maytag if region == Region.US else Brand.Whirlpool diff --git a/requirements_all.txt b/requirements_all.txt index 12197aca771..b10ec3b082d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2841,7 +2841,7 @@ webmin-xmlrpc==0.0.2 webrtc-noise-gain==1.2.3 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.18.4 +whirlpool-sixth-sense==0.18.6 # homeassistant.components.whois whois==0.9.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bd1fa81f91f..b9c44739a32 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2188,7 +2188,7 @@ webmin-xmlrpc==0.0.2 webrtc-noise-gain==1.2.3 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.18.4 +whirlpool-sixth-sense==0.18.6 # homeassistant.components.whois whois==0.9.27 diff --git a/tests/components/whirlpool/__init__.py b/tests/components/whirlpool/__init__.py index eda7541ca56..ac52031a0fc 100644 --- a/tests/components/whirlpool/__init__.py +++ b/tests/components/whirlpool/__init__.py @@ -1,13 +1,14 @@ """Tests for the Whirlpool Sixth Sense integration.""" - -from homeassistant.components.whirlpool.const import DOMAIN +from homeassistant.components.whirlpool.const import CONF_BRAND, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry -async def init_integration(hass: HomeAssistant, region: str = "EU") -> MockConfigEntry: +async def init_integration( + hass: HomeAssistant, region: str = "EU", brand: str = "Whirlpool" +) -> MockConfigEntry: """Set up the Whirlpool integration in Home Assistant.""" entry = MockConfigEntry( domain=DOMAIN, @@ -15,6 +16,7 @@ async def init_integration(hass: HomeAssistant, region: str = "EU") -> MockConfi CONF_USERNAME: "nobody", CONF_PASSWORD: "qwerty", CONF_REGION: region, + CONF_BRAND: brand, }, ) diff --git a/tests/components/whirlpool/conftest.py b/tests/components/whirlpool/conftest.py index 7af14f9991a..e386012265c 100644 --- a/tests/components/whirlpool/conftest.py +++ b/tests/components/whirlpool/conftest.py @@ -16,13 +16,26 @@ MOCK_SAID4 = "said4" @pytest.fixture( name="region", - params=[("EU", Region.EU, Brand.Whirlpool), ("US", Region.US, Brand.Maytag)], + params=[("EU", Region.EU), ("US", Region.US)], ) def fixture_region(request): """Return a region for input.""" return request.param +@pytest.fixture( + name="brand", + params=[ + ("Whirlpool", Brand.Whirlpool), + ("KitchenAid", Brand.KitchenAid), + ("Maytag", Brand.Maytag), + ], +) +def fixture_brand(request): + """Return a brand for input.""" + return request.param + + @pytest.fixture(name="mock_auth_api") def fixture_mock_auth_api(): """Set up Auth fixture.""" diff --git a/tests/components/whirlpool/test_config_flow.py b/tests/components/whirlpool/test_config_flow.py index 5d2375d2668..d164bff6a61 100644 --- a/tests/components/whirlpool/test_config_flow.py +++ b/tests/components/whirlpool/test_config_flow.py @@ -6,7 +6,7 @@ import aiohttp from aiohttp.client_exceptions import ClientConnectionError from homeassistant import config_entries -from homeassistant.components.whirlpool.const import DOMAIN +from homeassistant.components.whirlpool.const import CONF_BRAND, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -19,10 +19,7 @@ CONFIG_INPUT = { } -async def test_form( - hass: HomeAssistant, - region, -) -> None: +async def test_form(hass: HomeAssistant, region, brand) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -48,7 +45,7 @@ async def test_form( ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - CONFIG_INPUT | {"region": region[0]}, + CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, ) await hass.async_block_till_done() @@ -58,12 +55,13 @@ async def test_form( "username": "test-username", "password": "test-password", "region": region[0], + "brand": brand[0], } assert len(mock_setup_entry.mock_calls) == 1 - mock_backend_selector.assert_called_once_with(region[2], region[1]) + mock_backend_selector.assert_called_once_with(brand[1], region[1]) -async def test_form_invalid_auth(hass: HomeAssistant, region) -> None: +async def test_form_invalid_auth(hass: HomeAssistant, region, brand) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -74,16 +72,13 @@ async def test_form_invalid_auth(hass: HomeAssistant, region) -> None: ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - CONFIG_INPUT - | { - "region": region[0], - }, + CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "invalid_auth"} -async def test_form_cannot_connect(hass: HomeAssistant, region) -> None: +async def test_form_cannot_connect(hass: HomeAssistant, region, brand) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -97,13 +92,14 @@ async def test_form_cannot_connect(hass: HomeAssistant, region) -> None: CONFIG_INPUT | { "region": region[0], + "brand": brand[0], }, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} -async def test_form_auth_timeout(hass: HomeAssistant, region) -> None: +async def test_form_auth_timeout(hass: HomeAssistant, region, brand) -> None: """Test we handle auth timeout error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -117,13 +113,14 @@ async def test_form_auth_timeout(hass: HomeAssistant, region) -> None: CONFIG_INPUT | { "region": region[0], + "brand": brand[0], }, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} -async def test_form_generic_auth_exception(hass: HomeAssistant, region) -> None: +async def test_form_generic_auth_exception(hass: HomeAssistant, region, brand) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -137,17 +134,18 @@ async def test_form_generic_auth_exception(hass: HomeAssistant, region) -> None: CONFIG_INPUT | { "region": region[0], + "brand": brand[0], }, ) assert result2["type"] == "form" assert result2["errors"] == {"base": "unknown"} -async def test_form_already_configured(hass: HomeAssistant, region) -> None: +async def test_form_already_configured(hass: HomeAssistant, region, brand) -> None: """Test we handle cannot connect error.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data=CONFIG_INPUT | {"region": region[0]}, + data=CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -174,6 +172,7 @@ async def test_form_already_configured(hass: HomeAssistant, region) -> None: CONFIG_INPUT | { "region": region[0], + "brand": brand[0], }, ) await hass.async_block_till_done() @@ -182,8 +181,8 @@ async def test_form_already_configured(hass: HomeAssistant, region) -> None: assert result2["reason"] == "already_configured" -async def test_no_appliances_flow(hass: HomeAssistant, region) -> None: - """Test we get and error with no appliances.""" +async def test_no_appliances_flow(hass: HomeAssistant, region, brand) -> None: + """Test we get an error with no appliances.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -200,7 +199,7 @@ async def test_no_appliances_flow(hass: HomeAssistant, region) -> None: ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - CONFIG_INPUT | {"region": region[0]}, + CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, ) await hass.async_block_till_done() @@ -208,11 +207,11 @@ async def test_no_appliances_flow(hass: HomeAssistant, region) -> None: assert result2["errors"] == {"base": "no_appliances"} -async def test_reauth_flow(hass: HomeAssistant, region) -> None: +async def test_reauth_flow(hass: HomeAssistant, region, brand) -> None: """Test a successful reauth flow.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data=CONFIG_INPUT | {"region": region[0]}, + data=CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -224,7 +223,7 @@ async def test_reauth_flow(hass: HomeAssistant, region) -> None: "unique_id": mock_entry.unique_id, "entry_id": mock_entry.entry_id, }, - data=CONFIG_INPUT | {"region": region[0]}, + data=CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, ) assert result["step_id"] == "reauth_confirm" @@ -246,7 +245,7 @@ async def test_reauth_flow(hass: HomeAssistant, region) -> None: ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_PASSWORD: "new-password"}, + {CONF_PASSWORD: "new-password", CONF_BRAND: brand[0]}, ) await hass.async_block_till_done() @@ -256,15 +255,16 @@ async def test_reauth_flow(hass: HomeAssistant, region) -> None: CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password", "region": region[0], + "brand": brand[0], } -async def test_reauth_flow_auth_error(hass: HomeAssistant, region) -> None: +async def test_reauth_flow_auth_error(hass: HomeAssistant, region, brand) -> None: """Test an authorization error reauth flow.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data=CONFIG_INPUT | {"region": region[0]}, + data=CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -280,6 +280,7 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant, region) -> None: CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password", "region": region[0], + "brand": brand[0], }, ) @@ -295,7 +296,7 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant, region) -> None: ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_PASSWORD: "new-password"}, + {CONF_PASSWORD: "new-password", CONF_BRAND: brand[0]}, ) await hass.async_block_till_done() @@ -303,12 +304,14 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant, region) -> None: assert result2["errors"] == {"base": "invalid_auth"} -async def test_reauth_flow_connnection_error(hass: HomeAssistant, region) -> None: +async def test_reauth_flow_connnection_error( + hass: HomeAssistant, region, brand +) -> None: """Test a connection error reauth flow.""" mock_entry = MockConfigEntry( domain=DOMAIN, - data=CONFIG_INPUT | {"region": region[0]}, + data=CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, unique_id="test-username", ) mock_entry.add_to_hass(hass) @@ -320,7 +323,7 @@ async def test_reauth_flow_connnection_error(hass: HomeAssistant, region) -> Non "unique_id": mock_entry.unique_id, "entry_id": mock_entry.entry_id, }, - data=CONFIG_INPUT | {"region": region[0]}, + data=CONFIG_INPUT | {"region": region[0], "brand": brand[0]}, ) assert result["step_id"] == "reauth_confirm" @@ -339,7 +342,7 @@ async def test_reauth_flow_connnection_error(hass: HomeAssistant, region) -> Non ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {CONF_PASSWORD: "new-password"}, + {CONF_PASSWORD: "new-password", CONF_BRAND: brand[0]}, ) await hass.async_block_till_done() diff --git a/tests/components/whirlpool/test_init.py b/tests/components/whirlpool/test_init.py index b5c8cf3d481..f9d28e78a06 100644 --- a/tests/components/whirlpool/test_init.py +++ b/tests/components/whirlpool/test_init.py @@ -7,7 +7,7 @@ from whirlpool.backendselector import Brand, Region from homeassistant.components.whirlpool.const import DOMAIN from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant from . import init_integration, init_integration_with_entry @@ -19,13 +19,14 @@ async def test_setup( hass: HomeAssistant, mock_backend_selector_api: MagicMock, region, + brand, mock_aircon_api_instances: MagicMock, ) -> None: """Test setup.""" - entry = await init_integration(hass, region[0]) + entry = await init_integration(hass, region[0], brand[0]) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert entry.state is ConfigEntryState.LOADED - mock_backend_selector_api.assert_called_once_with(region[2], region[1]) + mock_backend_selector_api.assert_called_once_with(brand[1], region[1]) async def test_setup_region_fallback( @@ -51,6 +52,31 @@ async def test_setup_region_fallback( mock_backend_selector_api.assert_called_once_with(Brand.Whirlpool, Region.EU) +async def test_setup_brand_fallback( + hass: HomeAssistant, + region, + mock_backend_selector_api: MagicMock, + mock_aircon_api_instances: MagicMock, +) -> None: + """Test setup when no brand is available on the ConfigEntry. + + This can happen after a version update, since the brand was not selected or stored in the earlier versions. + """ + + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_USERNAME: "nobody", + CONF_PASSWORD: "qwerty", + CONF_REGION: region[0], + }, + ) + entry = await init_integration_with_entry(hass, entry) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.state is ConfigEntryState.LOADED + mock_backend_selector_api.assert_called_once_with(Brand.Whirlpool, region[1]) + + async def test_setup_http_exception( hass: HomeAssistant, mock_auth_api: MagicMock, From 417b491b78fe01a2dff46607cb92cffb9e44bf30 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 15:13:34 -1000 Subject: [PATCH 1300/1691] Reduce overhead to write dlna_dmr state (#113776) * Reduce overhead to write dlna_dmr state - Only update supported_features once per state write cycle - Use a dict lookup for state * useless dispatch * fix tests * remove unreachable code --- .../components/dlna_dmr/media_player.py | 45 ++++++++++--------- .../components/dlna_dmr/test_media_player.py | 2 + 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index 7bad837d328..69b9c0ffdb7 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -57,6 +57,17 @@ _R = TypeVar("_R") _P = ParamSpec("_P") +_TRANSPORT_STATE_TO_MEDIA_PLAYER_STATE = { + TransportState.PLAYING: MediaPlayerState.PLAYING, + TransportState.TRANSITIONING: MediaPlayerState.PLAYING, + TransportState.PAUSED_PLAYBACK: MediaPlayerState.PAUSED, + TransportState.PAUSED_RECORDING: MediaPlayerState.PAUSED, + # Unable to map this state to anything reasonable, so it's "Unknown" + TransportState.VENDOR_DEFINED: None, + None: MediaPlayerState.ON, +} + + def catch_request_errors( func: Callable[Concatenate[_DlnaDmrEntityT, _P], Awaitable[_R]], ) -> Callable[Concatenate[_DlnaDmrEntityT, _P], Coroutine[Any, Any, _R | None]]: @@ -186,6 +197,7 @@ class DlnaDmrEntity(MediaPlayerEntity): self._updated_registry: bool = False self._config_entry = config_entry self._attr_device_info = dr.DeviceInfo(connections={(dr.CONNECTION_UPNP, udn)}) + self._attr_supported_features = self._supported_features() async def async_added_to_hass(self) -> None: """Handle addition.""" @@ -345,6 +357,11 @@ class DlnaDmrEntity(MediaPlayerEntity): # Device was de/re-connected, state might have changed self.async_write_ha_state() + def async_write_ha_state(self) -> None: + """Write the state.""" + self._attr_supported_features = self._supported_features() + super().async_write_ha_state() + async def _device_connect(self, location: str) -> None: """Connect to the device now that it's available.""" _LOGGER.debug("Connecting to device at %s", location) @@ -491,6 +508,9 @@ class DlnaDmrEntity(MediaPlayerEntity): finally: self.check_available = False + # Supported features may have changed + self._attr_supported_features = self._supported_features() + def _on_event( self, service: UpnpService, state_variables: Sequence[UpnpStateVariable] ) -> None: @@ -531,28 +551,13 @@ class DlnaDmrEntity(MediaPlayerEntity): @property def state(self) -> MediaPlayerState | None: """State of the player.""" - if not self._device or not self.available: + if not self._device: return MediaPlayerState.OFF - if self._device.transport_state is None: - return MediaPlayerState.ON - if self._device.transport_state in ( - TransportState.PLAYING, - TransportState.TRANSITIONING, - ): - return MediaPlayerState.PLAYING - if self._device.transport_state in ( - TransportState.PAUSED_PLAYBACK, - TransportState.PAUSED_RECORDING, - ): - return MediaPlayerState.PAUSED - if self._device.transport_state == TransportState.VENDOR_DEFINED: - # Unable to map this state to anything reasonable, so it's "Unknown" - return None + return _TRANSPORT_STATE_TO_MEDIA_PLAYER_STATE.get( + self._device.transport_state, MediaPlayerState.IDLE + ) - return MediaPlayerState.IDLE - - @property - def supported_features(self) -> MediaPlayerEntityFeature: + def _supported_features(self) -> MediaPlayerEntityFeature: """Flag media player features that are supported at this moment. Supported features may change as the device enters different states. diff --git a/tests/components/dlna_dmr/test_media_player.py b/tests/components/dlna_dmr/test_media_player.py index 18f4a7164c0..9ead49f0955 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -1009,6 +1009,7 @@ async def test_shuffle_repeat_modes( dmr_device_mock.async_set_play_mode.reset_mock() dmr_device_mock.play_mode = PlayMode.RANDOM dmr_device_mock.valid_play_modes = {PlayMode.SHUFFLE, PlayMode.RANDOM} + await get_attrs(hass, mock_entity_id) await hass.services.async_call( MP_DOMAIN, ha_const.SERVICE_SHUFFLE_SET, @@ -1022,6 +1023,7 @@ async def test_shuffle_repeat_modes( dmr_device_mock.async_set_play_mode.reset_mock() dmr_device_mock.play_mode = PlayMode.RANDOM dmr_device_mock.valid_play_modes = {PlayMode.REPEAT_ONE, PlayMode.REPEAT_ALL} + await get_attrs(hass, mock_entity_id) await hass.services.async_call( MP_DOMAIN, ha_const.SERVICE_REPEAT_SET, From 06f356a038b8498fcefa429d50f25b24cb7fc45b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 15:27:40 -1000 Subject: [PATCH 1301/1691] Avoid expensive db queries at startup to check if data is migrated (#113773) --- homeassistant/components/recorder/core.py | 63 +++---- .../components/recorder/db_schema.py | 11 ++ .../components/recorder/migration.py | 135 ++++++++++++++- homeassistant/components/recorder/queries.py | 8 + tests/components/recorder/common.py | 10 +- .../recorder/test_migration_from_schema_32.py | 61 ++++++- ..._migration_run_time_migrations_remember.py | 163 ++++++++++++++++++ 7 files changed, 402 insertions(+), 49 deletions(-) create mode 100644 tests/components/recorder/test_migration_run_time_migrations_remember.py diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index fdb593ff27b..98f93f4e69a 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -43,11 +43,9 @@ from homeassistant.util.enum import try_parse_enum from . import migration, statistics from .const import ( - CONTEXT_ID_AS_BINARY_SCHEMA_VERSION, DB_WORKER_PREFIX, DOMAIN, ESTIMATED_QUEUE_ITEM_SIZE, - EVENT_TYPE_IDS_SCHEMA_VERSION, KEEPALIVE_TIME, LEGACY_STATES_EVENT_ID_INDEX_SCHEMA_VERSION, MARIADB_PYMYSQL_URL_PREFIX, @@ -58,7 +56,6 @@ from .const import ( QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY, SQLITE_MAX_BIND_VARS, SQLITE_URL_PREFIX, - STATES_META_SCHEMA_VERSION, STATISTICS_ROWS_SCHEMA_VERSION, SupportedDialect, ) @@ -78,14 +75,15 @@ from .db_schema import ( StatisticsShortTerm, ) from .executor import DBInterruptibleThreadPoolExecutor +from .migration import ( + EntityIDMigration, + EventsContextIDMigration, + EventTypeIDMigration, + StatesContextIDMigration, +) from .models import DatabaseEngine, StatisticData, StatisticMetaData, UnsupportedDialect from .pool import POOL_SIZE, MutexPool, RecorderPool -from .queries import ( - has_entity_ids_to_migrate, - has_event_type_to_migrate, - has_events_context_ids_to_migrate, - has_states_context_ids_to_migrate, -) +from .queries import get_migration_changes from .table_managers.event_data import EventDataManager from .table_managers.event_types import EventTypeManager from .table_managers.recorder_runs import RecorderRunsManager @@ -101,17 +99,13 @@ from .tasks import ( CommitTask, CompileMissingStatisticsTask, DatabaseLockTask, - EntityIDMigrationTask, EntityIDPostMigrationTask, EventIdMigrationTask, - EventsContextIDMigrationTask, - EventTypeIDMigrationTask, ImportStatisticsTask, KeepAliveTask, PerodicCleanupTask, PurgeTask, RecorderTask, - StatesContextIDMigrationTask, StatisticsTask, StopTask, SynchronizeTask, @@ -783,44 +777,35 @@ class Recorder(threading.Thread): def _activate_and_set_db_ready(self) -> None: """Activate the table managers or schedule migrations and mark the db as ready.""" - with session_scope(session=self.get_session(), read_only=True) as session: + with session_scope(session=self.get_session()) as session: # Prime the statistics meta manager as soon as possible # since we want the frontend queries to avoid a thundering # herd of queries to find the statistics meta data if # there are a lot of statistics graphs on the frontend. - if self.schema_version >= STATISTICS_ROWS_SCHEMA_VERSION: + schema_version = self.schema_version + if schema_version >= STATISTICS_ROWS_SCHEMA_VERSION: self.statistics_meta_manager.load(session) - if ( - self.schema_version < CONTEXT_ID_AS_BINARY_SCHEMA_VERSION - or execute_stmt_lambda_element( - session, has_states_context_ids_to_migrate() - ) - ): - self.queue_task(StatesContextIDMigrationTask()) + migration_changes: dict[str, int] = { + row[0]: row[1] + for row in execute_stmt_lambda_element(session, get_migration_changes()) + } - if ( - self.schema_version < CONTEXT_ID_AS_BINARY_SCHEMA_VERSION - or execute_stmt_lambda_element( - session, has_events_context_ids_to_migrate() - ) - ): - self.queue_task(EventsContextIDMigrationTask()) + for migrator_cls in (StatesContextIDMigration, EventsContextIDMigration): + migrator = migrator_cls(session, schema_version, migration_changes) + if migrator.needs_migrate(): + self.queue_task(migrator.task()) - if ( - self.schema_version < EVENT_TYPE_IDS_SCHEMA_VERSION - or execute_stmt_lambda_element(session, has_event_type_to_migrate()) - ): - self.queue_task(EventTypeIDMigrationTask()) + migrator = EventTypeIDMigration(session, schema_version, migration_changes) + if migrator.needs_migrate(): + self.queue_task(migrator.task()) else: _LOGGER.debug("Activating event_types manager as all data is migrated") self.event_type_manager.active = True - if ( - self.schema_version < STATES_META_SCHEMA_VERSION - or execute_stmt_lambda_element(session, has_entity_ids_to_migrate()) - ): - self.queue_task(EntityIDMigrationTask()) + migrator = EntityIDMigration(session, schema_version, migration_changes) + if migrator.needs_migrate(): + self.queue_task(migrator.task()) else: _LOGGER.debug("Activating states_meta manager as all data is migrated") self.states_meta_manager.active = True diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index eb2e0b6ade3..6755e9c5c9b 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -84,6 +84,7 @@ TABLE_STATISTICS = "statistics" TABLE_STATISTICS_META = "statistics_meta" TABLE_STATISTICS_RUNS = "statistics_runs" TABLE_STATISTICS_SHORT_TERM = "statistics_short_term" +TABLE_MIGRATION_CHANGES = "migration_changes" STATISTICS_TABLES = ("statistics", "statistics_short_term") @@ -100,6 +101,7 @@ ALL_TABLES = [ TABLE_EVENT_TYPES, TABLE_RECORDER_RUNS, TABLE_SCHEMA_CHANGES, + TABLE_MIGRATION_CHANGES, TABLE_STATES_META, TABLE_STATISTICS, TABLE_STATISTICS_META, @@ -771,6 +773,15 @@ class RecorderRuns(Base): return self +class MigrationChanges(Base): + """Representation of migration changes.""" + + __tablename__ = TABLE_MIGRATION_CHANGES + + migration_id: Mapped[str] = mapped_column(String(255), primary_key=True) + version: Mapped[int] = mapped_column(SmallInteger) + + class SchemaChanges(Base): """Representation of schema version changes.""" diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 2d8a2976219..8395b88837c 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC, abstractmethod from collections.abc import Callable, Iterable import contextlib from dataclasses import dataclass, replace as dataclass_replace @@ -25,6 +26,7 @@ from sqlalchemy.exc import ( from sqlalchemy.orm.session import Session from sqlalchemy.schema import AddConstraint, DropConstraint from sqlalchemy.sql.expression import true +from sqlalchemy.sql.lambdas import StatementLambdaElement from homeassistant.core import HomeAssistant from homeassistant.util.enum import try_parse_enum @@ -46,7 +48,12 @@ from .auto_repairs.statistics.schema import ( correct_db_schema as statistics_correct_db_schema, validate_db_schema as statistics_validate_db_schema, ) -from .const import SupportedDialect +from .const import ( + CONTEXT_ID_AS_BINARY_SCHEMA_VERSION, + EVENT_TYPE_IDS_SCHEMA_VERSION, + STATES_META_SCHEMA_VERSION, + SupportedDialect, +) from .db_schema import ( CONTEXT_ID_BIN_MAX_LENGTH, DOUBLE_PRECISION_TYPE_SQL, @@ -60,6 +67,7 @@ from .db_schema import ( Base, Events, EventTypes, + MigrationChanges, SchemaChanges, States, StatesMeta, @@ -80,6 +88,10 @@ from .queries import ( find_states_context_ids_to_migrate, find_unmigrated_short_term_statistics_rows, find_unmigrated_statistics_rows, + has_entity_ids_to_migrate, + has_event_type_to_migrate, + has_events_context_ids_to_migrate, + has_states_context_ids_to_migrate, has_used_states_event_ids, migrate_single_short_term_statistics_row_to_timestamp, migrate_single_statistics_row_to_timestamp, @@ -87,11 +99,17 @@ from .queries import ( from .statistics import get_start_time from .tasks import ( CommitTask, + EntityIDMigrationTask, + EventsContextIDMigrationTask, + EventTypeIDMigrationTask, PostSchemaMigrationTask, + RecorderTask, + StatesContextIDMigrationTask, StatisticsTimestampMigrationCleanupTask, ) from .util import ( database_job_retry_wrapper, + execute_stmt_lambda_element, get_index_by_name, retryable_database_job, session_scope, @@ -1478,7 +1496,8 @@ def migrate_states_context_ids(instance: Recorder) -> bool: ) # If there is more work to do return False # so that we can be called again - is_done = not states + if is_done := not states: + _mark_migration_done(session, StatesContextIDMigration) if is_done: _drop_index(session_maker, "states", "ix_states_context_id") @@ -1515,7 +1534,8 @@ def migrate_events_context_ids(instance: Recorder) -> bool: ) # If there is more work to do return False # so that we can be called again - is_done = not events + if is_done := not events: + _mark_migration_done(session, EventsContextIDMigration) if is_done: _drop_index(session_maker, "events", "ix_events_context_id") @@ -1580,7 +1600,8 @@ def migrate_event_type_ids(instance: Recorder) -> bool: # If there is more work to do return False # so that we can be called again - is_done = not events + if is_done := not events: + _mark_migration_done(session, EventTypeIDMigration) if is_done: instance.event_type_manager.active = True @@ -1654,7 +1675,8 @@ def migrate_entity_ids(instance: Recorder) -> bool: # If there is more work to do return False # so that we can be called again - is_done = not states + if is_done := not states: + _mark_migration_done(session, EntityIDMigration) _LOGGER.debug("Migrating entity_ids done=%s", is_done) return is_done @@ -1757,3 +1779,106 @@ def initialize_database(session_maker: Callable[[], Session]) -> bool: except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Error when initialise database: %s", err) return False + + +class BaseRunTimeMigration(ABC): + """Base class for run time migrations.""" + + required_schema_version = 0 + migration_version = 1 + migration_id: str + task: Callable[[], RecorderTask] + + def __init__( + self, session: Session, schema_version: int, migration_changes: dict[str, int] + ) -> None: + """Initialize a new BaseRunTimeMigration.""" + self.schema_version = schema_version + self.session = session + self.migration_changes = migration_changes + + @abstractmethod + def needs_migrate_query(self) -> StatementLambdaElement: + """Return the query to check if the migration needs to run.""" + + def needs_migrate(self) -> bool: + """Return if the migration needs to run. + + If the migration needs to run, it will return True. + + If the migration does not need to run, it will return False and + mark the migration as done in the database if its not already + marked as done. + """ + if self.schema_version < self.required_schema_version: + # Schema is too old, we must have to migrate + return True + if self.migration_changes.get(self.migration_id, -1) >= self.migration_version: + # The migration changes table indicates that the migration has been done + return False + # We do not know if the migration is done from the + # migration changes table so we must check the data + # This is the slow path + if not execute_stmt_lambda_element(self.session, self.needs_migrate_query()): + _mark_migration_done(self.session, self.__class__) + return False + return True + + +class StatesContextIDMigration(BaseRunTimeMigration): + """Migration to migrate states context_ids to binary format.""" + + required_schema_version = CONTEXT_ID_AS_BINARY_SCHEMA_VERSION + migration_id = "state_context_id_as_binary" + task = StatesContextIDMigrationTask + + def needs_migrate_query(self) -> StatementLambdaElement: + """Return the query to check if the migration needs to run.""" + return has_states_context_ids_to_migrate() + + +class EventsContextIDMigration(BaseRunTimeMigration): + """Migration to migrate events context_ids to binary format.""" + + required_schema_version = CONTEXT_ID_AS_BINARY_SCHEMA_VERSION + migration_id = "event_context_id_as_binary" + task = EventsContextIDMigrationTask + + def needs_migrate_query(self) -> StatementLambdaElement: + """Return the query to check if the migration needs to run.""" + return has_events_context_ids_to_migrate() + + +class EventTypeIDMigration(BaseRunTimeMigration): + """Migration to migrate event_type to event_type_ids.""" + + required_schema_version = EVENT_TYPE_IDS_SCHEMA_VERSION + migration_id = "event_type_id_migration" + task = EventTypeIDMigrationTask + + def needs_migrate_query(self) -> StatementLambdaElement: + """Check if the data is migrated.""" + return has_event_type_to_migrate() + + +class EntityIDMigration(BaseRunTimeMigration): + """Migration to migrate entity_ids to states_meta.""" + + required_schema_version = STATES_META_SCHEMA_VERSION + migration_id = "entity_id_migration" + task = EntityIDMigrationTask + + def needs_migrate_query(self) -> StatementLambdaElement: + """Check if the data is migrated.""" + return has_entity_ids_to_migrate() + + +def _mark_migration_done( + session: Session, migration: type[BaseRunTimeMigration] +) -> None: + """Mark a migration as done in the database.""" + session.merge( + MigrationChanges( + migration_id=migration.migration_id, version=migration.migration_version + ) + ) diff --git a/homeassistant/components/recorder/queries.py b/homeassistant/components/recorder/queries.py index fdb15d2d49c..d982576620d 100644 --- a/homeassistant/components/recorder/queries.py +++ b/homeassistant/components/recorder/queries.py @@ -13,6 +13,7 @@ from .db_schema import ( EventData, Events, EventTypes, + MigrationChanges, RecorderRuns, StateAttributes, States, @@ -812,6 +813,13 @@ def find_states_context_ids_to_migrate(max_bind_vars: int) -> StatementLambdaEle ) +def get_migration_changes() -> StatementLambdaElement: + """Query the database for previous migration changes.""" + return lambda_stmt( + lambda: select(MigrationChanges.migration_id, MigrationChanges.version) + ) + + def find_event_types_to_purge() -> StatementLambdaElement: """Find event_type_ids to purge.""" return lambda_stmt( diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index 19ee449ae0b..816378c2f2e 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -20,7 +20,13 @@ from sqlalchemy.orm.session import Session from homeassistant import core as ha from homeassistant.components import recorder -from homeassistant.components.recorder import Recorder, core, get_instance, statistics +from homeassistant.components.recorder import ( + Recorder, + core, + get_instance, + migration, + statistics, +) from homeassistant.components.recorder.db_schema import ( Events, EventTypes, @@ -417,7 +423,7 @@ def old_db_schema(schema_version_postfix: str) -> Iterator[None]: core, "States", old_db_schema.States ), patch.object(core, "Events", old_db_schema.Events), patch.object( core, "StateAttributes", old_db_schema.StateAttributes - ), patch.object(core, "EntityIDMigrationTask", core.RecorderTask), patch( + ), patch.object(migration.EntityIDMigration, "task", core.RecorderTask), patch( CREATE_ENGINE_TARGET, new=partial( create_engine_test_for_schema_version_postfix, diff --git a/tests/components/recorder/test_migration_from_schema_32.py b/tests/components/recorder/test_migration_from_schema_32.py index 2e9a71a2a50..e9f51caaee2 100644 --- a/tests/components/recorder/test_migration_from_schema_32.py +++ b/tests/components/recorder/test_migration_from_schema_32.py @@ -22,7 +22,10 @@ from homeassistant.components.recorder.db_schema import ( StatesMeta, ) from homeassistant.components.recorder.models import process_timestamp -from homeassistant.components.recorder.queries import select_event_type_ids +from homeassistant.components.recorder.queries import ( + get_migration_changes, + select_event_type_ids, +) from homeassistant.components.recorder.tasks import ( EntityIDMigrationTask, EntityIDPostMigrationTask, @@ -30,7 +33,10 @@ from homeassistant.components.recorder.tasks import ( EventTypeIDMigrationTask, StatesContextIDMigrationTask, ) -from homeassistant.components.recorder.util import session_scope +from homeassistant.components.recorder.util import ( + execute_stmt_lambda_element, + session_scope, +) from homeassistant.core import HomeAssistant import homeassistant.util.dt as dt_util from homeassistant.util.ulid import bytes_to_ulid, ulid_at_time, ulid_to_bytes @@ -53,6 +59,11 @@ async def _async_wait_migration_done(hass: HomeAssistant) -> None: await async_recorder_block_till_done(hass) +def _get_migration_id(hass: HomeAssistant) -> dict[str, int]: + with session_scope(hass=hass, read_only=True) as session: + return dict(execute_stmt_lambda_element(session, get_migration_changes())) + + def _create_engine_test(*args, **kwargs): """Test version of create_engine that initializes with old schema. @@ -89,7 +100,7 @@ def db_schema_32(): core, "States", old_db_schema.States ), patch.object(core, "Events", old_db_schema.Events), patch.object( core, "StateAttributes", old_db_schema.StateAttributes - ), patch.object(core, "EntityIDMigrationTask", core.RecorderTask), patch( + ), patch.object(migration.EntityIDMigration, "task", core.RecorderTask), patch( CREATE_ENGINE_TARGET, new=_create_engine_test ): yield @@ -308,6 +319,12 @@ async def test_migrate_events_context_ids( event_with_garbage_context_id_no_time_fired_ts["context_parent_id_bin"] is None ) + migration_changes = await instance.async_add_executor_job(_get_migration_id, hass) + assert ( + migration_changes[migration.EventsContextIDMigration.migration_id] + == migration.EventsContextIDMigration.migration_version + ) + @pytest.mark.parametrize("enable_migrate_context_ids", [True]) async def test_migrate_states_context_ids( @@ -495,6 +512,12 @@ async def test_migrate_states_context_ids( == b"\n\xe2\x97\x99\xeeNOE\x81\x16\xf5\x82\xd7\xd3\xeee" ) + migration_changes = await instance.async_add_executor_job(_get_migration_id, hass) + assert ( + migration_changes[migration.StatesContextIDMigration.migration_id] + == migration.StatesContextIDMigration.migration_version + ) + @pytest.mark.parametrize("enable_migrate_event_type_ids", [True]) async def test_migrate_event_type_ids( @@ -578,6 +601,12 @@ async def test_migrate_event_type_ids( assert mapped["event_type_one"] is not None assert mapped["event_type_two"] is not None + migration_changes = await instance.async_add_executor_job(_get_migration_id, hass) + assert ( + migration_changes[migration.EventTypeIDMigration.migration_id] + == migration.EventTypeIDMigration.migration_version + ) + @pytest.mark.parametrize("enable_migrate_entity_ids", [True]) async def test_migrate_entity_ids( @@ -646,6 +675,12 @@ async def test_migrate_entity_ids( assert len(states_by_entity_id["sensor.two"]) == 2 assert len(states_by_entity_id["sensor.one"]) == 1 + migration_changes = await instance.async_add_executor_job(_get_migration_id, hass) + assert ( + migration_changes[migration.EntityIDMigration.migration_id] + == migration.EntityIDMigration.migration_version + ) + @pytest.mark.parametrize("enable_migrate_entity_ids", [True]) async def test_post_migrate_entity_ids( @@ -771,6 +806,16 @@ async def test_migrate_null_entity_ids( assert len(states_by_entity_id[migration._EMPTY_ENTITY_ID]) == 1000 assert len(states_by_entity_id["sensor.one"]) == 2 + def _get_migration_id(): + with session_scope(hass=hass, read_only=True) as session: + return dict(execute_stmt_lambda_element(session, get_migration_changes())) + + migration_changes = await instance.async_add_executor_job(_get_migration_id) + assert ( + migration_changes[migration.EntityIDMigration.migration_id] + == migration.EntityIDMigration.migration_version + ) + @pytest.mark.parametrize("enable_migrate_event_type_ids", [True]) async def test_migrate_null_event_type_ids( @@ -847,6 +892,16 @@ async def test_migrate_null_event_type_ids( assert len(events_by_type["event_type_one"]) == 2 assert len(events_by_type[migration._EMPTY_EVENT_TYPE]) == 1000 + def _get_migration_id(): + with session_scope(hass=hass, read_only=True) as session: + return dict(execute_stmt_lambda_element(session, get_migration_changes())) + + migration_changes = await instance.async_add_executor_job(_get_migration_id) + assert ( + migration_changes[migration.EventTypeIDMigration.migration_id] + == migration.EventTypeIDMigration.migration_version + ) + async def test_stats_timestamp_conversion_is_reentrant( async_setup_recorder_instance: RecorderInstanceGenerator, diff --git a/tests/components/recorder/test_migration_run_time_migrations_remember.py b/tests/components/recorder/test_migration_run_time_migrations_remember.py new file mode 100644 index 00000000000..770d5d684a9 --- /dev/null +++ b/tests/components/recorder/test_migration_run_time_migrations_remember.py @@ -0,0 +1,163 @@ +"""Test run time migrations are remembered in the migration_changes table.""" + +import importlib +from pathlib import Path +import sys +from unittest.mock import patch + +import pytest +from sqlalchemy import create_engine +from sqlalchemy.orm import Session + +from homeassistant.components import recorder +from homeassistant.components.recorder import core, migration, statistics +from homeassistant.components.recorder.queries import get_migration_changes +from homeassistant.components.recorder.tasks import StatesContextIDMigrationTask +from homeassistant.components.recorder.util import ( + execute_stmt_lambda_element, + session_scope, +) +from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.core import HomeAssistant + +from .common import async_recorder_block_till_done, async_wait_recording_done + +from tests.common import async_test_home_assistant +from tests.typing import RecorderInstanceGenerator + +CREATE_ENGINE_TARGET = "homeassistant.components.recorder.core.create_engine" +SCHEMA_MODULE = "tests.components.recorder.db_schema_32" + + +async def _async_wait_migration_done(hass: HomeAssistant) -> None: + """Wait for the migration to be done.""" + await recorder.get_instance(hass).async_block_till_done() + await async_recorder_block_till_done(hass) + + +def _get_migration_id(hass: HomeAssistant) -> dict[str, int]: + with session_scope(hass=hass, read_only=True) as session: + return dict(execute_stmt_lambda_element(session, get_migration_changes())) + + +def _create_engine_test(*args, **kwargs): + """Test version of create_engine that initializes with old schema. + + This simulates an existing db with the old schema. + """ + importlib.import_module(SCHEMA_MODULE) + old_db_schema = sys.modules[SCHEMA_MODULE] + engine = create_engine(*args, **kwargs) + old_db_schema.Base.metadata.create_all(engine) + with Session(engine) as session: + session.add( + recorder.db_schema.StatisticsRuns(start=statistics.get_start_time()) + ) + session.add( + recorder.db_schema.SchemaChanges( + schema_version=old_db_schema.SCHEMA_VERSION + ) + ) + session.commit() + return engine + + +@pytest.mark.parametrize("enable_migrate_context_ids", [True]) +async def test_migration_changes_prevent_trying_to_migrate_again( + async_setup_recorder_instance: RecorderInstanceGenerator, + tmp_path: Path, + recorder_db_url: str, +) -> None: + """Test that we do not try to migrate when migration_changes indicate its already migrated. + + This test will start Home Assistant 3 times: + + 1. With schema 32 to populate the data + 2. With current schema so the migration happens + 3. With current schema to verify we do not have to query to see if the migration is done + """ + if recorder_db_url.startswith(("mysql://", "postgresql://")): + # This test uses a test database between runs so its + # SQLite specific + return + + config = { + recorder.CONF_DB_URL: "sqlite:///" + str(tmp_path / "pytest.db"), + recorder.CONF_COMMIT_INTERVAL: 1, + } + importlib.import_module(SCHEMA_MODULE) + old_db_schema = sys.modules[SCHEMA_MODULE] + + # Start with db schema that needs migration (version 32) + with patch.object(recorder, "db_schema", old_db_schema), patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), patch.object(core, "StatesMeta", old_db_schema.StatesMeta), patch.object( + core, "EventTypes", old_db_schema.EventTypes + ), patch.object(core, "EventData", old_db_schema.EventData), patch.object( + core, "States", old_db_schema.States + ), patch.object(core, "Events", old_db_schema.Events), patch.object( + core, "StateAttributes", old_db_schema.StateAttributes + ), patch.object(migration.EntityIDMigration, "task", core.RecorderTask), patch( + CREATE_ENGINE_TARGET, new=_create_engine_test + ): + async with async_test_home_assistant() as hass: + await async_setup_recorder_instance(hass, config) + await hass.async_block_till_done() + await async_wait_recording_done(hass) + await _async_wait_migration_done(hass) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + await hass.async_stop() + + # Now start again with current db schema + async with async_test_home_assistant() as hass: + await async_setup_recorder_instance(hass, config) + await hass.async_block_till_done() + await async_wait_recording_done(hass) + await _async_wait_migration_done(hass) + instance = recorder.get_instance(hass) + migration_changes = await instance.async_add_executor_job( + _get_migration_id, hass + ) + assert ( + migration_changes[migration.StatesContextIDMigration.migration_id] + == migration.StatesContextIDMigration.migration_version + ) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + await hass.async_stop() + + original_queue_task = core.Recorder.queue_task + tasks = [] + + def _queue_task(self, task): + tasks.append(task) + original_queue_task(self, task) + + # Finally verify we did not call needs_migrate_query on StatesContextIDMigration + async with async_test_home_assistant() as hass: + with patch( + "homeassistant.components.recorder.core.Recorder.queue_task", _queue_task + ), patch.object( + migration.StatesContextIDMigration, + "needs_migrate_query", + side_effect=RuntimeError("Should not be called"), + ): + await async_setup_recorder_instance(hass, config) + await hass.async_block_till_done() + await async_wait_recording_done(hass) + await _async_wait_migration_done(hass) + instance = recorder.get_instance(hass) + migration_changes = await instance.async_add_executor_job( + _get_migration_id, hass + ) + assert ( + migration_changes[migration.StatesContextIDMigration.migration_id] + == migration.StatesContextIDMigration.migration_version + ) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + await hass.async_stop() + + for task in tasks: + assert not isinstance(task, StatesContextIDMigrationTask) From 510e7ccf7684427540d8fea8d0d5e168ba24ee4e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:40:09 +1300 Subject: [PATCH 1302/1691] Bump aioesphomeapi to 23.2.0 (#113854) --- homeassistant/components/esphome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 5ebc196fe3d..f1a5333c403 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -15,7 +15,7 @@ "iot_class": "local_push", "loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"], "requirements": [ - "aioesphomeapi==23.1.1", + "aioesphomeapi==23.2.0", "esphome-dashboard-api==1.2.3", "bleak-esphome==1.0.0" ], diff --git a/requirements_all.txt b/requirements_all.txt index b10ec3b082d..4d284d60993 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -242,7 +242,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==23.1.1 +aioesphomeapi==23.2.0 # homeassistant.components.flo aioflo==2021.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b9c44739a32..f286caa34d5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -221,7 +221,7 @@ aioelectricitymaps==0.4.0 aioemonitor==1.0.5 # homeassistant.components.esphome -aioesphomeapi==23.1.1 +aioesphomeapi==23.2.0 # homeassistant.components.flo aioflo==2021.11.0 From 23353812a93d711097be22d83dd6a31c169ed17c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 20 Mar 2024 07:06:34 +0100 Subject: [PATCH 1303/1691] Add icon translations to Github (#111614) * Add icon translations to Github * Fix --- homeassistant/components/github/icons.json | 42 ++++++++++++++++++++++ homeassistant/components/github/sensor.py | 5 +-- tests/components/github/test_init.py | 23 ++++++++++-- 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/github/icons.json diff --git a/homeassistant/components/github/icons.json b/homeassistant/components/github/icons.json new file mode 100644 index 00000000000..f8a2eefe0c8 --- /dev/null +++ b/homeassistant/components/github/icons.json @@ -0,0 +1,42 @@ +{ + "entity": { + "sensor": { + "discussions_count": { + "default": "mdi:forum" + }, + "stargazers_count": { + "default": "mdi:star" + }, + "subscribers_count": { + "default": "mdi:glasses" + }, + "forks_count": { + "default": "mdi:source-fork" + }, + "issues_count": { + "default": "mdi:github" + }, + "pulls_count": { + "default": "mdi:source-pull" + }, + "latest_commit": { + "default": "mdi:source-commit" + }, + "latest_discussion": { + "default": "mdi:forum" + }, + "latest_release": { + "default": "mdi:github" + }, + "latest_issue": { + "default": "mdi:github" + }, + "latest_pull_request": { + "default": "mdi:source-pull" + }, + "latest_tag": { + "default": "mdi:tag" + } + } + } +} diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index a0e38862471..a082f888767 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -28,7 +28,7 @@ class GitHubSensorEntityDescription(SensorEntityDescription): """Describes GitHub issue sensor entity.""" value_fn: Callable[[dict[str, Any]], StateType] - icon: str = "mdi:github" + attr_fn: Callable[[dict[str, Any]], Mapping[str, Any] | None] = lambda data: None avabl_fn: Callable[[dict[str, Any]], bool] = lambda data: True @@ -45,7 +45,6 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( GitHubSensorEntityDescription( key="stargazers_count", translation_key="stargazers_count", - icon="mdi:star", native_unit_of_measurement="Stars", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, @@ -54,7 +53,6 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( GitHubSensorEntityDescription( key="subscribers_count", translation_key="subscribers_count", - icon="mdi:glasses", native_unit_of_measurement="Watchers", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, @@ -63,7 +61,6 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( GitHubSensorEntityDescription( key="forks_count", translation_key="forks_count", - icon="mdi:source-fork", native_unit_of_measurement="Forks", entity_category=EntityCategory.DIAGNOSTIC, state_class=SensorStateClass.MEASUREMENT, diff --git a/tests/components/github/test_init.py b/tests/components/github/test_init.py index a25e27df835..c97a940b05c 100644 --- a/tests/components/github/test_init.py +++ b/tests/components/github/test_init.py @@ -4,7 +4,7 @@ import pytest from homeassistant.components.github import CONF_REPOSITORIES from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er, icon from .common import setup_github_integration @@ -21,7 +21,7 @@ async def test_device_registry_cleanup( aioclient_mock: AiohttpClientMocker, caplog: pytest.LogCaptureFixture, ) -> None: - """Test that we remove untracked repositories from the decvice registry.""" + """Test that we remove untracked repositories from the device registry.""" mock_config_entry.add_to_hass(hass) hass.config_entries.async_update_entry( mock_config_entry, @@ -113,3 +113,22 @@ async def test_subscription_setup_polling_disabled( "https://api.github.com/repos/home-assistant/core/events" in x[1] for x in aioclient_mock.mock_calls ) + + +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_sensor_icons( + hass: HomeAssistant, + init_integration: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test to ensure that all sensor entities have an icon definition.""" + entities = er.async_entries_for_config_entry( + entity_registry, + config_entry_id=init_integration.entry_id, + ) + + icons = await icon.async_get_icons(hass, "entity", integrations=["github"]) + for entity in entities: + assert entity.translation_key is not None + assert icons["github"]["sensor"][entity.translation_key] is not None From fff590d7389f1689bdca5a427865f032b7281057 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 20:21:26 -1000 Subject: [PATCH 1304/1691] Fix flapping device tracker test (#113859) seen in https://github.com/home-assistant/core/actions/runs/8353028595/job/22864148890?pr=113854 --- tests/components/device_tracker/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 3166354260b..b77ab1c5f9b 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -121,6 +121,7 @@ async def test_reading_yaml_config( config = (await legacy.async_load_config(yaml_devices, hass, device.consider_home))[ 0 ] + await hass.async_block_till_done() assert device.dev_id == config.dev_id assert device.track == config.track assert device.mac == config.mac From 96694878fbedd3345739d8d2ebe70533042406ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 20 Mar 2024 08:23:16 +0100 Subject: [PATCH 1305/1691] Redact the area of traccar server geofences (#113861) --- homeassistant/components/traccar_server/diagnostics.py | 7 ++++++- .../traccar_server/snapshots/test_diagnostics.ambr | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/traccar_server/diagnostics.py b/homeassistant/components/traccar_server/diagnostics.py index a6ad6084daf..8e9e6490e44 100644 --- a/homeassistant/components/traccar_server/diagnostics.py +++ b/homeassistant/components/traccar_server/diagnostics.py @@ -13,7 +13,12 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from .const import DOMAIN from .coordinator import TraccarServerCoordinator -TO_REDACT = {CONF_ADDRESS, CONF_LATITUDE, CONF_LONGITUDE} +TO_REDACT = { + CONF_ADDRESS, + CONF_LATITUDE, + CONF_LONGITUDE, + "area", # This is the polygon area of a geofence +} async def async_get_config_entry_diagnostics( diff --git a/tests/components/traccar_server/snapshots/test_diagnostics.ambr b/tests/components/traccar_server/snapshots/test_diagnostics.ambr index 1726f1c3d45..20d01e427ea 100644 --- a/tests/components/traccar_server/snapshots/test_diagnostics.ambr +++ b/tests/components/traccar_server/snapshots/test_diagnostics.ambr @@ -34,7 +34,7 @@ 'uniqueId': 'abc123', }), 'geofence': dict({ - 'area': 'string', + 'area': '**REDACTED**', 'attributes': dict({ }), 'calendarId': 0, @@ -134,7 +134,7 @@ 'uniqueId': 'abc123', }), 'geofence': dict({ - 'area': 'string', + 'area': '**REDACTED**', 'attributes': dict({ }), 'calendarId': 0, From c5eacf55c26c85c06a19217ffa01564f7e9e8fb5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 Mar 2024 21:27:05 -1000 Subject: [PATCH 1306/1691] Fix flapping stream hls test (#113858) --- tests/components/stream/test_hls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 23ba2e4ab34..0ec01fd9231 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -327,6 +327,8 @@ async def test_stream_retries( await stream.start() await open_future1 await open_future2 + await hass.async_add_executor_job(stream._thread.join) + stream._thread = None assert av_open.call_count == 2 await hass.async_block_till_done() From 638020f1681a6af132a5c96c8de076512d2c0920 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 20 Mar 2024 08:45:50 +0100 Subject: [PATCH 1307/1691] Remove deprecated `hass.components` from group light tests and use fixture (#113862) --- tests/components/group/test_light.py | 249 ++++++++++++++------------- 1 file changed, 129 insertions(+), 120 deletions(-) diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index b279c38d169..d48474955a5 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -45,6 +45,7 @@ from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import async_capture_events, get_fixture_path +from tests.fixtures.pytest.light import MockLight, SetupLightPlatformCallable async def test_default_state( @@ -261,21 +262,21 @@ async def test_state_reporting_all(hass: HomeAssistant) -> None: async def test_brightness( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test brightness reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.BRIGHTNESS} entity0.color_mode = ColorMode.BRIGHTNESS entity0.brightness = 255 - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.BRIGHTNESS} entity1.color_mode = ColorMode.BRIGHTNESS @@ -333,21 +334,23 @@ async def test_brightness( assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["brightness"] -async def test_color_hs(hass: HomeAssistant, enable_custom_integrations: None) -> None: +async def test_color_hs( + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable +) -> None: """Test hs color reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.HS} entity0.color_mode = ColorMode.HS entity0.brightness = 255 entity0.hs_color = (0, 100) - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.HS} entity1.color_mode = ColorMode.HS @@ -403,21 +406,23 @@ async def test_color_hs(hass: HomeAssistant, enable_custom_integrations: None) - assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 -async def test_color_rgb(hass: HomeAssistant, enable_custom_integrations: None) -> None: +async def test_color_rgb( + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable +) -> None: """Test rgbw color reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGB} entity0.color_mode = ColorMode.RGB entity0.brightness = 255 entity0.rgb_color = (0, 64, 128) - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.RGB} entity1.color_mode = ColorMode.RGB entity1.brightness = 255 @@ -476,22 +481,22 @@ async def test_color_rgb(hass: HomeAssistant, enable_custom_integrations: None) async def test_color_rgbw( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test rgbw color reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGBW} entity0.color_mode = ColorMode.RGBW entity0.brightness = 255 entity0.rgbw_color = (0, 64, 128, 255) - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.RGBW} entity1.color_mode = ColorMode.RGBW entity1.brightness = 255 @@ -550,22 +555,22 @@ async def test_color_rgbw( async def test_color_rgbww( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test rgbww color reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGBWW} entity0.color_mode = ColorMode.RGBWW entity0.brightness = 255 entity0.rgbww_color = (0, 32, 64, 128, 255) - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.RGBWW} entity1.color_mode = ColorMode.RGBWW entity1.brightness = 255 @@ -623,20 +628,22 @@ async def test_color_rgbww( assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 -async def test_white(hass: HomeAssistant, enable_custom_integrations: None) -> None: +async def test_white( + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable +) -> None: """Test white reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_ON)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.HS, ColorMode.WHITE} entity0.color_mode = ColorMode.WHITE entity0.brightness = 255 - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.HS, ColorMode.WHITE} entity1.color_mode = ColorMode.WHITE entity1.brightness = 128 @@ -681,22 +688,22 @@ async def test_white(hass: HomeAssistant, enable_custom_integrations: None) -> N async def test_color_temp( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color temp reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} entity0.color_mode = ColorMode.COLOR_TEMP entity0.brightness = 255 entity0.color_temp_kelvin = 2 - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.COLOR_TEMP} entity1.color_mode = ColorMode.COLOR_TEMP @@ -752,25 +759,25 @@ async def test_color_temp( async def test_emulated_color_temp_group( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test emulated color temperature in a group.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + MockLight("test3", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("test3", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} entity0.color_mode = ColorMode.COLOR_TEMP - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} entity1.color_mode = ColorMode.COLOR_TEMP - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {ColorMode.HS} entity2.color_mode = ColorMode.HS @@ -818,26 +825,26 @@ async def test_emulated_color_temp_group( async def test_min_max_mireds( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test min/max mireds reporting. min/max mireds is reported both when light is on and off """ - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} entity0.color_mode = ColorMode.COLOR_TEMP entity0.color_temp_kelvin = 2 entity0._attr_min_color_temp_kelvin = 2 entity0._attr_max_color_temp_kelvin = 5 - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.COLOR_TEMP} entity1.color_mode = ColorMode.COLOR_TEMP entity1._attr_min_color_temp_kelvin = 1 @@ -999,25 +1006,25 @@ async def test_effect(hass: HomeAssistant) -> None: async def test_supported_color_modes( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test supported_color_modes reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + MockLight("test3", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("test3", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} entity0.color_mode = ColorMode.UNKNOWN - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.RGBW, ColorMode.RGBWW} entity1.color_mode = ColorMode.UNKNOWN - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {ColorMode.BRIGHTNESS} entity2.color_mode = ColorMode.UNKNOWN @@ -1049,25 +1056,25 @@ async def test_supported_color_modes( async def test_color_mode( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test color_mode reporting.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_OFF), + MockLight("test3", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("test3", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} entity0.color_mode = ColorMode.COLOR_TEMP - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} entity1.color_mode = ColorMode.COLOR_TEMP - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} entity2.color_mode = ColorMode.HS @@ -1124,40 +1131,40 @@ async def test_color_mode( async def test_color_mode2( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable ) -> None: """Test onoff color_mode and brightness are given lowest priority.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("test1", STATE_ON), + MockLight("test2", STATE_ON), + MockLight("test3", STATE_ON), + MockLight("test4", STATE_ON), + MockLight("test5", STATE_ON), + MockLight("test6", STATE_ON), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("test1", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test2", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test3", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test4", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test5", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("test6", STATE_ON)) - - entity = platform.ENTITIES[0] + entity = entities[0] entity.supported_color_modes = {ColorMode.COLOR_TEMP} entity.color_mode = ColorMode.COLOR_TEMP - entity = platform.ENTITIES[1] + entity = entities[1] entity.supported_color_modes = {ColorMode.BRIGHTNESS} entity.color_mode = ColorMode.BRIGHTNESS - entity = platform.ENTITIES[2] + entity = entities[2] entity.supported_color_modes = {ColorMode.BRIGHTNESS} entity.color_mode = ColorMode.BRIGHTNESS - entity = platform.ENTITIES[3] + entity = entities[3] entity.supported_color_modes = {ColorMode.ONOFF} entity.color_mode = ColorMode.ONOFF - entity = platform.ENTITIES[4] + entity = entities[4] entity.supported_color_modes = {ColorMode.ONOFF} entity.color_mode = ColorMode.ONOFF - entity = platform.ENTITIES[5] + entity = entities[5] entity.supported_color_modes = {ColorMode.ONOFF} entity.color_mode = ColorMode.ONOFF @@ -1248,29 +1255,31 @@ async def test_supported_features(hass: HomeAssistant) -> None: @pytest.mark.parametrize("supported_color_modes", [ColorMode.HS, ColorMode.RGB]) async def test_service_calls( - hass: HomeAssistant, enable_custom_integrations: None, supported_color_modes + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + supported_color_modes, ) -> None: """Test service calls.""" - platform = getattr(hass.components, "test.light") - platform.init(empty=True) + entities = [ + MockLight("bed_light", STATE_ON), + MockLight("ceiling_lights", STATE_OFF), + MockLight("kitchen_lights", STATE_OFF), + ] + setup_light_platform(entities) - platform.ENTITIES.append(platform.MockLight("bed_light", STATE_ON)) - platform.ENTITIES.append(platform.MockLight("ceiling_lights", STATE_OFF)) - platform.ENTITIES.append(platform.MockLight("kitchen_lights", STATE_OFF)) - - entity0 = platform.ENTITIES[0] + entity0 = entities[0] entity0.supported_color_modes = {supported_color_modes} entity0.color_mode = supported_color_modes entity0.brightness = 255 entity0.rgb_color = (0, 64, 128) - entity1 = platform.ENTITIES[1] + entity1 = entities[1] entity1.supported_color_modes = {supported_color_modes} entity1.color_mode = supported_color_modes entity1.brightness = 255 entity1.rgb_color = (255, 128, 64) - entity2 = platform.ENTITIES[2] + entity2 = entities[2] entity2.supported_color_modes = {supported_color_modes} entity2.color_mode = supported_color_modes entity2.brightness = 255 From d31124d5d4a96a5bd9dc73de72260b34b3319f0f Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 20 Mar 2024 09:40:06 +0100 Subject: [PATCH 1308/1691] Avoid creating unneeded Context and Event objects when firing events (#113798) * Avoid creating unneeded Context and Event objects when firing events * Add test --------- Co-authored-by: J. Nick Koston --- .../homeassistant/triggers/event.py | 39 +++--- .../components/mqtt_statestream/__init__.py | 8 +- homeassistant/components/person/__init__.py | 8 +- .../components/recorder/db_schema.py | 2 +- .../components/recorder/entity_registry.py | 6 +- .../components/tasmota/device_automation.py | 7 +- homeassistant/components/voip/devices.py | 2 +- homeassistant/config_entries.py | 8 +- homeassistant/core.py | 132 ++++++++++++------ homeassistant/helpers/area_registry.py | 5 +- homeassistant/helpers/device_registry.py | 12 +- homeassistant/helpers/entity_registry.py | 9 +- homeassistant/helpers/event.py | 34 ++--- homeassistant/helpers/translation.py | 4 +- homeassistant/scripts/benchmark/__init__.py | 2 +- homeassistant/setup.py | 4 +- tests/components/recorder/test_models.py | 9 +- .../components/recorder/test_v32_migration.py | 8 +- tests/test_core.py | 86 ++++++++++-- 19 files changed, 257 insertions(+), 128 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index e045ece12ba..264e2f9d440 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import ItemsView +from collections.abc import ItemsView, Mapping from typing import Any import voluptuous as vol @@ -101,30 +101,18 @@ async def async_attach_trigger( job = HassJob(action, f"event trigger {trigger_info}") @callback - def filter_event(event: Event) -> bool: + def filter_event(event_data: Mapping[str, Any]) -> bool: """Filter events.""" try: # Check that the event data and context match the configured # schema if one was provided if event_data_items: # Fast path for simple items comparison - if not (event.data.items() >= event_data_items): + if not (event_data.items() >= event_data_items): return False elif event_data_schema: # Slow path for schema validation - event_data_schema(event.data) - - if event_context_items: - # Fast path for simple items comparison - # This is safe because we do not mutate the event context - # pylint: disable-next=protected-access - if not (event.context._as_dict.items() >= event_context_items): - return False - elif event_context_schema: - # Slow path for schema validation - # This is safe because we make a copy of the event context - # pylint: disable-next=protected-access - event_context_schema(dict(event.context._as_dict)) + event_data_schema(event_data) except vol.Invalid: # If event doesn't match, skip event return False @@ -133,6 +121,22 @@ async def async_attach_trigger( @callback def handle_event(event: Event) -> None: """Listen for events and calls the action when data matches.""" + if event_context_items: + # Fast path for simple items comparison + # This is safe because we do not mutate the event context + # pylint: disable-next=protected-access + if not (event.context._as_dict.items() >= event_context_items): + return + elif event_context_schema: + try: + # Slow path for schema validation + # This is safe because we make a copy of the event context + # pylint: disable-next=protected-access + event_context_schema(dict(event.context._as_dict)) + except vol.Invalid: + # If event doesn't match, skip event + return + hass.async_run_hass_job( job, { @@ -146,9 +150,10 @@ async def async_attach_trigger( event.context, ) + event_filter = filter_event if event_data_items or event_data_schema else None removes = [ hass.bus.async_listen( - event_type, handle_event, event_filter=filter_event, run_immediately=True + event_type, handle_event, event_filter=event_filter, run_immediately=True ) for event_type in event_types ] diff --git a/homeassistant/components/mqtt_statestream/__init__.py b/homeassistant/components/mqtt_statestream/__init__.py index 1f9fa2fad0f..a9b86c4bf8f 100644 --- a/homeassistant/components/mqtt_statestream/__init__.py +++ b/homeassistant/components/mqtt_statestream/__init__.py @@ -1,7 +1,9 @@ """Publish simple item state changes via MQTT.""" +from collections.abc import Mapping import json import logging +from typing import Any import voluptuous as vol @@ -90,9 +92,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @callback def _ha_started(hass: HomeAssistant) -> None: @callback - def _event_filter(evt: Event) -> bool: - entity_id: str = evt.data["entity_id"] - new_state: State | None = evt.data["new_state"] + def _event_filter(event_data: Mapping[str, Any]) -> bool: + entity_id: str = event_data["entity_id"] + new_state: State | None = event_data["new_state"] if new_state is None: return False if not publish_filter(entity_id): diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 3b2273f5033..8aa3251641b 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Mapping import logging from typing import Any, Self @@ -248,11 +248,11 @@ class PersonStorageCollection(collection.DictStorageCollection): ) @callback - def _entity_registry_filter(self, event: Event) -> bool: + def _entity_registry_filter(self, event_data: Mapping[str, Any]) -> bool: """Filter entity registry events.""" return ( - event.data["action"] == "remove" - and split_entity_id(event.data[ATTR_ENTITY_ID])[0] == "device_tracker" + event_data["action"] == "remove" + and split_entity_id(event_data[ATTR_ENTITY_ID])[0] == "device_tracker" ) async def _entity_registry_updated(self, event: Event) -> None: diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index 6755e9c5c9b..cc1e0e1d5ec 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -321,7 +321,7 @@ class Events(Base): EventOrigin(self.origin) if self.origin else EVENT_ORIGIN_ORDER[self.origin_idx or 0], - dt_util.utc_from_timestamp(self.time_fired_ts or 0), + self.time_fired_ts or 0, context=context, ) except JSON_DECODE_EXCEPTIONS: diff --git a/homeassistant/components/recorder/entity_registry.py b/homeassistant/components/recorder/entity_registry.py index 89e6864cb06..5bf1856316a 100644 --- a/homeassistant/components/recorder/entity_registry.py +++ b/homeassistant/components/recorder/entity_registry.py @@ -1,6 +1,8 @@ """Recorder entity registry helper.""" +from collections.abc import Mapping import logging +from typing import Any from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import entity_registry as er @@ -29,9 +31,9 @@ def async_setup(hass: HomeAssistant) -> None: ) @callback - def entity_registry_changed_filter(event: Event) -> bool: + def entity_registry_changed_filter(event_data: Mapping[str, Any]) -> bool: """Handle entity_id changed filter.""" - return event.data["action"] == "update" and "old_entity_id" in event.data + return event_data["action"] == "update" and "old_entity_id" in event_data @callback def _setup_entity_registry_event_handler(hass: HomeAssistant) -> None: diff --git a/homeassistant/components/tasmota/device_automation.py b/homeassistant/components/tasmota/device_automation.py index 98c7d1355c3..2fdcad45c81 100644 --- a/homeassistant/components/tasmota/device_automation.py +++ b/homeassistant/components/tasmota/device_automation.py @@ -1,5 +1,8 @@ """Provides device automations for Tasmota.""" +from collections.abc import Mapping +from typing import Any + from hatasmota.const import AUTOMATION_TYPE_TRIGGER from hatasmota.models import DiscoveryHashType from hatasmota.trigger import TasmotaTrigger @@ -27,9 +30,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> N await async_remove_automations(hass, event.data["device_id"]) @callback - def _async_device_removed_filter(event: Event) -> bool: + def _async_device_removed_filter(event_data: Mapping[str, Any]) -> bool: """Filter device registry events.""" - return event.data["action"] == "remove" + return event_data["action"] == "remove" async def async_discover( tasmota_automation: TasmotaTrigger, discovery_hash: DiscoveryHashType diff --git a/homeassistant/components/voip/devices.py b/homeassistant/components/voip/devices.py index aed8e6de740..9acc04f6879 100644 --- a/homeassistant/components/voip/devices.py +++ b/homeassistant/components/voip/devices.py @@ -97,7 +97,7 @@ class VoIPDevices: self.hass.bus.async_listen( dr.EVENT_DEVICE_REGISTRY_UPDATED, async_device_removed, - callback(lambda ev: ev.data.get("action") == "remove"), + callback(lambda event_data: event_data.get("action") == "remove"), ) ) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b73c7b25e41..91986f053fd 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -2519,16 +2519,16 @@ class EntityRegistryDisabledHandler: @callback -def _handle_entry_updated_filter(event: Event) -> bool: +def _handle_entry_updated_filter(event_data: Mapping[str, Any]) -> bool: """Handle entity registry entry update filter. Only handle changes to "disabled_by". If "disabled_by" was CONFIG_ENTRY, reload is not needed. """ if ( - event.data["action"] != "update" - or "disabled_by" not in event.data["changes"] - or event.data["changes"]["disabled_by"] + event_data["action"] != "update" + or "disabled_by" not in event_data["changes"] + or event_data["changes"]["disabled_by"] is entity_registry.RegistryEntryDisabler.CONFIG_ENTRY ): return False diff --git a/homeassistant/core.py b/homeassistant/core.py index a2c14814c99..9e040b82b98 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -67,6 +67,7 @@ from .const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, + EVENT_LOGGING_CHANGED, EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, @@ -1215,24 +1216,24 @@ class Event(Generic[_DataT]): event_type: str, data: _DataT | None = None, origin: EventOrigin = EventOrigin.local, - time_fired: datetime.datetime | None = None, + time_fired_timestamp: float | None = None, context: Context | None = None, ) -> None: """Initialize a new event.""" self.event_type = event_type self.data: _DataT = data or {} # type: ignore[assignment] self.origin = origin - self.time_fired = time_fired or dt_util.utcnow() + self.time_fired_timestamp = time_fired_timestamp or time.time() if not context: - context = Context(id=ulid_at_time(self.time_fired.timestamp())) + context = Context(id=ulid_at_time(self.time_fired_timestamp)) self.context = context if not context.origin_event: context.origin_event = self @cached_property - def time_fired_timestamp(self) -> float: + def time_fired(self) -> datetime.datetime: """Return time fired as a timestamp.""" - return self.time_fired.timestamp() + return dt_util.utc_from_timestamp(self.time_fired_timestamp) @cached_property def _as_dict(self) -> dict[str, Any]: @@ -1282,18 +1283,22 @@ class Event(Generic[_DataT]): def __repr__(self) -> str: """Return the representation.""" - if self.data: - return ( - f"" - ) + return _event_repr(self.event_type, self.origin, self.data) - return f"" + +def _event_repr( + event_type: str, origin: EventOrigin, data: Mapping[str, Any] | None +) -> str: + """Return the representation.""" + if data: + return f"" + + return f"" _FilterableJobType = tuple[ HassJob[[Event[_DataT]], Coroutine[Any, Any, None] | None], # job - Callable[[Event[_DataT]], bool] | None, # event_filter + Callable[[_DataT], bool] | None, # event_filter bool, # run_immediately ] @@ -1325,7 +1330,7 @@ class _OneTimeListener: class EventBus: """Allow the firing of and listening for events.""" - __slots__ = ("_listeners", "_match_all_listeners", "_hass") + __slots__ = ("_debug", "_hass", "_listeners", "_match_all_listeners") def __init__(self, hass: HomeAssistant) -> None: """Initialize a new event bus.""" @@ -1333,6 +1338,15 @@ class EventBus: self._match_all_listeners: list[_FilterableJobType[Any]] = [] self._listeners[MATCH_ALL] = self._match_all_listeners self._hass = hass + self._async_logging_changed() + self.async_listen( + EVENT_LOGGING_CHANGED, self._async_logging_changed, run_immediately=True + ) + + @callback + def _async_logging_changed(self, event: Event | None = None) -> None: + """Handle logging change.""" + self._debug = _LOGGER.isEnabledFor(logging.DEBUG) @callback def async_listeners(self) -> dict[str, int]: @@ -1366,7 +1380,7 @@ class EventBus: event_data: Mapping[str, Any] | None = None, origin: EventOrigin = EventOrigin.local, context: Context | None = None, - time_fired: datetime.datetime | None = None, + time_fired: float | None = None, ) -> None: """Fire an event. @@ -1376,30 +1390,57 @@ class EventBus: raise MaxLengthExceeded( event_type, "event_type", MAX_LENGTH_EVENT_EVENT_TYPE ) + return self._async_fire(event_type, event_data, origin, context, time_fired) - listeners = self._listeners.get(event_type, []) - match_all_listeners = self._match_all_listeners + @callback + def _async_fire( + self, + event_type: str, + event_data: Mapping[str, Any] | None = None, + origin: EventOrigin = EventOrigin.local, + context: Context | None = None, + time_fired: float | None = None, + ) -> None: + """Fire an event. - event = Event(event_type, event_data, origin, time_fired, context) + This method must be run in the event loop. + """ - if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug("Bus:Handling %s", event) - - if not listeners and not match_all_listeners: - return + if self._debug: + _LOGGER.debug( + "Bus:Handling %s", _event_repr(event_type, origin, event_data) + ) + listeners = self._listeners.get(event_type) # EVENT_HOMEASSISTANT_CLOSE should not be sent to MATCH_ALL listeners if event_type != EVENT_HOMEASSISTANT_CLOSE: - listeners = match_all_listeners + listeners + if listeners: + listeners = self._match_all_listeners + listeners + else: + listeners = self._match_all_listeners.copy() + if not listeners: + return + + event: Event | None = None for job, event_filter, run_immediately in listeners: if event_filter is not None: try: - if not event_filter(event): + if event_data is None or not event_filter(event_data): continue except Exception: # pylint: disable=broad-except _LOGGER.exception("Error in event filter") continue + + if not event: + event = Event( + event_type, + event_data, + origin, + time_fired, + context, + ) + if run_immediately: try: self._hass.async_run_hass_job(job, event) @@ -1433,7 +1474,7 @@ class EventBus: self, event_type: str, listener: Callable[[Event[_DataT]], Coroutine[Any, Any, None] | None], - event_filter: Callable[[Event[_DataT]], bool] | None = None, + event_filter: Callable[[_DataT], bool] | None = None, run_immediately: bool = False, ) -> CALLBACK_TYPE: """Listen for all events or events of a specific type. @@ -1952,7 +1993,7 @@ class StateMachine: return False old_state.expire() - self._bus.async_fire( + self._bus._async_fire( # pylint: disable=protected-access EVENT_STATE_CHANGED, {"entity_id": entity_id, "old_state": old_state, "new_state": None}, context=context, @@ -2047,32 +2088,35 @@ class StateMachine: same_attr = old_state.attributes == attributes last_changed = old_state.last_changed if same_state else None + # It is much faster to convert a timestamp to a utc datetime object + # than converting a utc datetime object to a timestamp since cpython + # does not have a fast path for handling the UTC timezone and has to do + # multiple local timezone conversions. + # + # from_timestamp implementation: + # https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L2936 + # + # timestamp implementation: + # https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6387 + # https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6323 + timestamp = time.time() + now = dt_util.utc_from_timestamp(timestamp) + if same_state and same_attr: return if context is None: - # It is much faster to convert a timestamp to a utc datetime object - # than converting a utc datetime object to a timestamp since cpython - # does not have a fast path for handling the UTC timezone and has to do - # multiple local timezone conversions. - # - # from_timestamp implementation: - # https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L2936 - # - # timestamp implementation: - # https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6387 - # https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6323 - timestamp = time.time() - now = dt_util.utc_from_timestamp(timestamp) + if TYPE_CHECKING: + assert timestamp is not None context = Context(id=ulid_at_time(timestamp)) - else: - now = dt_util.utcnow() if same_attr: if TYPE_CHECKING: assert old_state is not None attributes = old_state.attributes + # This is intentionally called with positional only arguments for performance + # reasons state = State( entity_id, new_state, @@ -2086,11 +2130,11 @@ class StateMachine: if old_state is not None: old_state.expire() self._states[entity_id] = state - self._bus.async_fire( + self._bus._async_fire( # pylint: disable=protected-access EVENT_STATE_CHANGED, {"entity_id": entity_id, "old_state": old_state, "new_state": state}, context=context, - time_fired=now, + time_fired=timestamp, ) @@ -2429,7 +2473,7 @@ class ServiceRegistry: domain, service, processed_data, context, return_response ) - self._hass.bus.async_fire( + self._hass.bus._async_fire( # pylint: disable=protected-access EVENT_CALL_SERVICE, { ATTR_DOMAIN: domain, diff --git a/homeassistant/helpers/area_registry.py b/homeassistant/helpers/area_registry.py index 7b65a53d34c..fc535bed610 100644 --- a/homeassistant/helpers/area_registry.py +++ b/homeassistant/helpers/area_registry.py @@ -314,10 +314,11 @@ class AreaRegistry(BaseRegistry): @callback def _removed_from_registry_filter( - event: fr.EventFloorRegistryUpdated | lr.EventLabelRegistryUpdated, + event_data: fr.EventFloorRegistryUpdatedData + | lr.EventLabelRegistryUpdatedData, ) -> bool: """Filter all except for the item removed from registry events.""" - return event.data["action"] == "remove" + return event_data["action"] == "remove" @callback def _handle_floor_registry_update(event: fr.EventFloorRegistryUpdated) -> None: diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 2f421034919..e31c372c18e 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1145,10 +1145,10 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: @callback def _label_removed_from_registry_filter( - event: lr.EventLabelRegistryUpdated, + event_data: lr.EventLabelRegistryUpdatedData, ) -> bool: """Filter all except for the remove action from label registry events.""" - return event.data["action"] == "remove" + return event_data["action"] == "remove" @callback def _handle_label_registry_update(event: lr.EventLabelRegistryUpdated) -> None: @@ -1178,12 +1178,12 @@ def async_setup_cleanup(hass: HomeAssistant, dev_reg: DeviceRegistry) -> None: debounced_cleanup.async_schedule_call() @callback - def entity_registry_changed_filter(event: Event) -> bool: + def entity_registry_changed_filter(event_data: Mapping[str, Any]) -> bool: """Handle entity updated or removed filter.""" if ( - event.data["action"] == "update" - and "device_id" not in event.data["changes"] - ) or event.data["action"] == "create": + event_data["action"] == "update" + and "device_id" not in event_data["changes"] + ) or event_data["action"] == "create": return False return True diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 637734c16ae..5946542b394 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -1431,10 +1431,11 @@ def _async_setup_cleanup(hass: HomeAssistant, registry: EntityRegistry) -> None: @callback def _removed_from_registry_filter( - event: lr.EventLabelRegistryUpdated | cr.EventCategoryRegistryUpdated, + event_data: lr.EventLabelRegistryUpdatedData + | cr.EventCategoryRegistryUpdatedData, ) -> bool: """Filter all except for the remove action from registry events.""" - return event.data["action"] == "remove" + return event_data["action"] == "remove" @callback def _handle_label_registry_update(event: lr.EventLabelRegistryUpdated) -> None: @@ -1488,9 +1489,9 @@ def _async_setup_entity_restore(hass: HomeAssistant, registry: EntityRegistry) - """Set up the entity restore mechanism.""" @callback - def cleanup_restored_states_filter(event: Event) -> bool: + def cleanup_restored_states_filter(event_data: Mapping[str, Any]) -> bool: """Clean up restored states filter.""" - return bool(event.data["action"] == "remove") + return bool(event_data["action"] == "remove") @callback def cleanup_restored_states(event: Event) -> None: diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 44fc2356c83..dd9c038340e 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -109,7 +109,7 @@ class _KeyedEventTracker(Generic[_TypedDictT]): [ HomeAssistant, dict[str, list[HassJob[[Event[_TypedDictT]], Any]]], - Event[_TypedDictT], + _TypedDictT, ], bool, ] @@ -237,11 +237,11 @@ def async_track_state_change( job = HassJob(action, f"track state change {entity_ids} {from_state} {to_state}") @callback - def state_change_filter(event: Event[EventStateChangedData]) -> bool: + def state_change_filter(event_data: EventStateChangedData) -> bool: """Handle specific state changes.""" if from_state is not None: old_state_str: str | None = None - if (old_state := event.data["old_state"]) is not None: + if (old_state := event_data["old_state"]) is not None: old_state_str = old_state.state if not match_from_state(old_state_str): @@ -249,7 +249,7 @@ def async_track_state_change( if to_state is not None: new_state_str: str | None = None - if (new_state := event.data["new_state"]) is not None: + if (new_state := event_data["new_state"]) is not None: new_state_str = new_state.state if not match_to_state(new_state_str): @@ -270,7 +270,7 @@ def async_track_state_change( @callback def state_change_listener(event: Event[EventStateChangedData]) -> None: """Handle specific state changes.""" - if not state_change_filter(event): + if not state_change_filter(event.data): return state_change_dispatcher(event) @@ -341,10 +341,10 @@ def _async_dispatch_entity_id_event( def _async_state_change_filter( hass: HomeAssistant, callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], - event: Event[EventStateChangedData], + event_data: EventStateChangedData, ) -> bool: """Filter state changes by entity_id.""" - return event.data["entity_id"] in callbacks + return event_data["entity_id"] in callbacks _KEYED_TRACK_STATE_CHANGE = _KeyedEventTracker( @@ -473,10 +473,10 @@ def _async_dispatch_old_entity_id_or_entity_id_event( def _async_entity_registry_updated_filter( hass: HomeAssistant, callbacks: dict[str, list[HassJob[[Event[EventEntityRegistryUpdatedData]], Any]]], - event: Event[EventEntityRegistryUpdatedData], + event_data: EventEntityRegistryUpdatedData, ) -> bool: """Filter entity registry updates by entity_id.""" - return event.data.get("old_entity_id", event.data["entity_id"]) in callbacks + return event_data.get("old_entity_id", event_data["entity_id"]) in callbacks _KEYED_TRACK_ENTITY_REGISTRY_UPDATED = _KeyedEventTracker( @@ -512,10 +512,10 @@ def async_track_entity_registry_updated_event( def _async_device_registry_updated_filter( hass: HomeAssistant, callbacks: dict[str, list[HassJob[[Event[EventDeviceRegistryUpdatedData]], Any]]], - event: Event[EventDeviceRegistryUpdatedData], + event_data: EventDeviceRegistryUpdatedData, ) -> bool: """Filter device registry updates by device_id.""" - return event.data["device_id"] in callbacks + return event_data["device_id"] in callbacks @callback @@ -585,12 +585,12 @@ def _async_dispatch_domain_event( def _async_domain_added_filter( hass: HomeAssistant, callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], - event: Event[EventStateChangedData], + event_data: EventStateChangedData, ) -> bool: """Filter state changes by entity_id.""" - return event.data["old_state"] is None and ( + return event_data["old_state"] is None and ( MATCH_ALL in callbacks - or split_entity_id(event.data["entity_id"])[0] in callbacks + or split_entity_id(event_data["entity_id"])[0] in callbacks ) @@ -634,12 +634,12 @@ def _async_track_state_added_domain( def _async_domain_removed_filter( hass: HomeAssistant, callbacks: dict[str, list[HassJob[[Event[EventStateChangedData]], Any]]], - event: Event[EventStateChangedData], + event_data: EventStateChangedData, ) -> bool: """Filter state changes by entity_id.""" - return event.data["new_state"] is None and ( + return event_data["new_state"] is None and ( MATCH_ALL in callbacks - or split_entity_id(event.data["entity_id"])[0] in callbacks + or split_entity_id(event_data["entity_id"])[0] in callbacks ) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index 841226ac584..acc4f146e8b 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -492,11 +492,11 @@ def async_setup(hass: HomeAssistant) -> None: hass.data[TRANSLATION_FLATTEN_CACHE] = cache @callback - def _async_load_translations_filter(event: Event) -> bool: + def _async_load_translations_filter(event_data: Mapping[str, Any]) -> bool: """Filter out unwanted events.""" nonlocal current_language if ( - new_language := event.data.get("language") + new_language := event_data.get("language") ) and new_language != current_language: current_language = new_language return True diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py index c4c47f06418..07f3d06f4cc 100644 --- a/homeassistant/scripts/benchmark/__init__.py +++ b/homeassistant/scripts/benchmark/__init__.py @@ -97,7 +97,7 @@ async def fire_events_with_filter(hass): events_to_fire = 10**6 @core.callback - def event_filter(event): + def event_filter(event_data): """Filter event.""" return False diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 522e7bd94ac..97aa79512f2 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -603,9 +603,9 @@ def _async_when_setup( await when_setup() @callback - def _async_is_component_filter(event: Event[EventComponentLoaded]) -> bool: + def _async_is_component_filter(event_data: EventComponentLoaded) -> bool: """Check if the event is for the component.""" - return event.data[ATTR_COMPONENT] == component + return event_data[ATTR_COMPONENT] == component listeners.append( hass.bus.async_listen( diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index 26c25bbb71f..262fb48af4d 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -98,7 +98,7 @@ def test_repr() -> None: EVENT_STATE_CHANGED, {"entity_id": "sensor.temperature", "old_state": None, "new_state": state}, context=state.context, - time_fired=fixed_time, + time_fired_timestamp=fixed_time.timestamp(), ) assert "2016-07-09 11:00:00+00:00" in repr(States.from_event(event)) assert "2016-07-09 11:00:00+00:00" in repr(Events.from_event(event)) @@ -164,7 +164,7 @@ def test_from_event_to_delete_state() -> None: assert db_state.entity_id == "sensor.temperature" assert db_state.state == "" assert db_state.last_changed_ts is None - assert db_state.last_updated_ts == event.time_fired.timestamp() + assert db_state.last_updated_ts == pytest.approx(event.time_fired.timestamp()) def test_states_from_native_invalid_entity_id() -> None: @@ -247,7 +247,10 @@ async def test_process_timestamp_to_utc_isoformat() -> None: async def test_event_to_db_model() -> None: """Test we can round trip Event conversion.""" event = ha.Event( - "state_changed", {"some": "attr"}, ha.EventOrigin.local, dt_util.utcnow() + "state_changed", + {"some": "attr"}, + ha.EventOrigin.local, + dt_util.utcnow().timestamp(), ) db_event = Events.from_event(event) dialect = SupportedDialect.MYSQL diff --git a/tests/components/recorder/test_v32_migration.py b/tests/components/recorder/test_v32_migration.py index bab28f0f90c..4f2eb4c7585 100644 --- a/tests/components/recorder/test_v32_migration.py +++ b/tests/components/recorder/test_v32_migration.py @@ -78,13 +78,13 @@ async def test_migrate_times(caplog: pytest.LogCaptureFixture, tmp_path: Path) - "new_state": mock_state, }, EventOrigin.local, - time_fired=now, + time_fired_timestamp=now.timestamp(), ) custom_event = Event( "custom_event", {"entity_id": "sensor.custom"}, EventOrigin.local, - time_fired=now, + time_fired_timestamp=now.timestamp(), ) number_of_migrations = 5 @@ -242,13 +242,13 @@ async def test_migrate_can_resume_entity_id_post_migration( "new_state": mock_state, }, EventOrigin.local, - time_fired=now, + time_fired_timestamp=now.timestamp(), ) custom_event = Event( "custom_event", {"entity_id": "sensor.custom"}, EventOrigin.local, - time_fired=now, + time_fired_timestamp=now.timestamp(), ) number_of_migrations = 5 diff --git a/tests/test_core.py b/tests/test_core.py index 0db2ba562ee..5b385700b00 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -836,18 +836,23 @@ def test_event_eq() -> None: data = {"some": "attr"} context = ha.Context() event1, event2 = ( - ha.Event("some_type", data, time_fired=now, context=context) for _ in range(2) + ha.Event( + "some_type", data, time_fired_timestamp=now.timestamp(), context=context + ) + for _ in range(2) ) assert event1.as_dict() == event2.as_dict() -def test_event_time_fired_timestamp() -> None: - """Test time_fired_timestamp.""" +def test_event_time() -> None: + """Test time_fired and time_fired_timestamp.""" now = dt_util.utcnow() - event = ha.Event("some_type", {"some": "attr"}, time_fired=now) - assert event.time_fired_timestamp == now.timestamp() + event = ha.Event( + "some_type", {"some": "attr"}, time_fired_timestamp=now.timestamp() + ) assert event.time_fired_timestamp == now.timestamp() + assert event.time_fired == now def test_event_json_fragment() -> None: @@ -856,7 +861,10 @@ def test_event_json_fragment() -> None: data = {"some": "attr"} context = ha.Context() event1, event2 = ( - ha.Event("some_type", data, time_fired=now, context=context) for _ in range(2) + ha.Event( + "some_type", data, time_fired_timestamp=now.timestamp(), context=context + ) + for _ in range(2) ) # We are testing that the JSON fragments are the same when as_dict is called @@ -898,7 +906,7 @@ def test_event_as_dict() -> None: now = dt_util.utcnow() data = {"some": "attr"} - event = ha.Event(event_type, data, ha.EventOrigin.local, now) + event = ha.Event(event_type, data, ha.EventOrigin.local, now.timestamp()) expected = { "event_type": event_type, "data": data, @@ -1108,9 +1116,9 @@ async def test_eventbus_filtered_listener(hass: HomeAssistant) -> None: calls.append(event) @ha.callback - def filter(event): + def filter(event_data): """Mock filter.""" - return not event.data["filtered"] + return not event_data["filtered"] unsub = hass.bus.async_listen("test", listener, event_filter=filter) @@ -3152,3 +3160,63 @@ async def test_async_add_job_deprecated( "https://developers.home-assistant.io/blog/2024/03/13/deprecate_add_run_job" " for replacement options" ) in caplog.text + + +async def test_eventbus_lazy_object_creation(hass: HomeAssistant) -> None: + """Test we don't create unneeded objects when firing events.""" + calls = [] + + @ha.callback + def listener(event): + """Mock listener.""" + calls.append(event) + + @ha.callback + def filter(event_data): + """Mock filter.""" + return not event_data["filtered"] + + unsub = hass.bus.async_listen("test_1", listener, event_filter=filter) + + # Test lazy creation of Event objects + with patch("homeassistant.core.Event") as mock_event: + # Fire an event which is filtered out by its listener + hass.bus.async_fire("test_1", {"filtered": True}) + await hass.async_block_till_done() + mock_event.assert_not_called() + assert len(calls) == 0 + + # Fire an event which has no listener + hass.bus.async_fire("test_2") + await hass.async_block_till_done() + mock_event.assert_not_called() + assert len(calls) == 0 + + # Fire an event which is not filtered out by its listener + hass.bus.async_fire("test_1", {"filtered": False}) + await hass.async_block_till_done() + mock_event.assert_called_once() + assert len(calls) == 1 + + calls = [] + # Test lazy creation of Context objects + with patch("homeassistant.core.Context") as mock_context: + # Fire an event which is filtered out by its listener + hass.bus.async_fire("test_1", {"filtered": True}) + await hass.async_block_till_done() + mock_context.assert_not_called() + assert len(calls) == 0 + + # Fire an event which has no listener + hass.bus.async_fire("test_2") + await hass.async_block_till_done() + mock_context.assert_not_called() + assert len(calls) == 0 + + # Fire an event which is not filtered out by its listener + hass.bus.async_fire("test_1", {"filtered": False}) + await hass.async_block_till_done() + mock_context.assert_called_once() + assert len(calls) == 1 + + unsub() From ac008a4c6dc817d7c4cc2a095706d22772941bc0 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 20 Mar 2024 09:42:40 +0100 Subject: [PATCH 1309/1691] Deprecate cloud tts gender (#112256) * Deprecate cloud tts gender option * Update http api and prefs * Test migration of prefs to minor version 4 * Adjust breaking date * Add test for bad voice in http api * Flatten tts info * Fix comments * Fix comment date Co-authored-by: Erik Montnemery * Clarify voice validator --------- Co-authored-by: Erik Montnemery --- homeassistant/components/cloud/const.py | 2 +- homeassistant/components/cloud/http_api.py | 23 ++- homeassistant/components/cloud/prefs.py | 27 +++- homeassistant/components/cloud/strings.json | 11 ++ homeassistant/components/cloud/tts.py | 74 ++++++---- tests/components/cloud/test_http_api.py | 68 ++++++--- tests/components/cloud/test_prefs.py | 27 +++- tests/components/cloud/test_tts.py | 150 +++++++++++++++++--- 8 files changed, 304 insertions(+), 78 deletions(-) diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index b134d67403a..1ee7392eccf 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -33,7 +33,7 @@ PREF_GOOGLE_SETTINGS_VERSION = "google_settings_version" PREF_TTS_DEFAULT_VOICE = "tts_default_voice" PREF_GOOGLE_CONNECTED = "google_connected" PREF_REMOTE_ALLOW_REMOTE_ENABLE = "remote_allow_remote_enable" -DEFAULT_TTS_DEFAULT_VOICE = ("en-US", "female") +DEFAULT_TTS_DEFAULT_VOICE = ("en-US", "JennyNeural") DEFAULT_DISABLE_2FA = False DEFAULT_ALEXA_REPORT_STATE = True DEFAULT_GOOGLE_REPORT_STATE = True diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index cb13cd75944..1a8fd7dbea9 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -16,7 +16,7 @@ from aiohttp import web import attr from hass_nabucasa import Cloud, auth, thingtalk from hass_nabucasa.const import STATE_DISCONNECTED -from hass_nabucasa.voice import MAP_VOICE +from hass_nabucasa.voice import TTS_VOICES import voluptuous as vol from homeassistant.components import websocket_api @@ -426,6 +426,16 @@ async def websocket_subscription( async_manage_legacy_subscription_issue(hass, data) +def validate_language_voice(value: tuple[str, str]) -> tuple[str, str]: + """Validate language and voice.""" + language, voice = value + if language not in TTS_VOICES: + raise vol.Invalid(f"Invalid language {language}") + if voice not in TTS_VOICES[language]: + raise vol.Invalid(f"Invalid voice {voice} for language {language}") + return value + + @_require_cloud_login @websocket_api.websocket_command( { @@ -436,7 +446,7 @@ async def websocket_subscription( vol.Optional(PREF_GOOGLE_REPORT_STATE): bool, vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str), vol.Optional(PREF_TTS_DEFAULT_VOICE): vol.All( - vol.Coerce(tuple), vol.In(MAP_VOICE) + vol.Coerce(tuple), validate_language_voice ), vol.Optional(PREF_REMOTE_ALLOW_REMOTE_ENABLE): bool, } @@ -840,5 +850,12 @@ def tts_info( ) -> None: """Fetch available tts info.""" connection.send_result( - msg["id"], {"languages": [(lang, gender.value) for lang, gender in MAP_VOICE]} + msg["id"], + { + "languages": [ + (language, voice) + for language, voices in TTS_VOICES.items() + for voice in voices + ] + }, ) diff --git a/homeassistant/components/cloud/prefs.py b/homeassistant/components/cloud/prefs.py index 0a0989ed4aa..af4e68194d6 100644 --- a/homeassistant/components/cloud/prefs.py +++ b/homeassistant/components/cloud/prefs.py @@ -6,6 +6,8 @@ from collections.abc import Callable, Coroutine from typing import Any import uuid +from hass_nabucasa.voice import MAP_VOICE + from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.auth.models import User from homeassistant.components import webhook @@ -48,7 +50,7 @@ from .const import ( STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 -STORAGE_VERSION_MINOR = 3 +STORAGE_VERSION_MINOR = 4 ALEXA_SETTINGS_VERSION = 3 GOOGLE_SETTINGS_VERSION = 3 @@ -82,6 +84,24 @@ class CloudPreferencesStore(Store): # In HA Core 2024.9, remove the import and also remove the Google # assistant store if it's not been migrated by manual Google assistant old_data.setdefault(PREF_GOOGLE_CONNECTED, await google_connected()) + if old_minor_version < 4: + # Update the default TTS voice to the new default. + # The default tts voice is a tuple. + # The first item is the language, the second item used to be gender. + # The new second item is the voice name. + default_tts_voice = old_data.get(PREF_TTS_DEFAULT_VOICE) + if default_tts_voice and (voice_item_two := default_tts_voice[1]) in ( + "female", + "male", + ): + language: str = default_tts_voice[0] + if voice := MAP_VOICE.get((language, voice_item_two)): + old_data[PREF_TTS_DEFAULT_VOICE] = ( + language, + voice, + ) + else: + old_data[PREF_TTS_DEFAULT_VOICE] = DEFAULT_TTS_DEFAULT_VOICE return old_data @@ -332,7 +352,10 @@ class CloudPreferences: @property def tts_default_voice(self) -> tuple[str, str]: - """Return the default TTS voice.""" + """Return the default TTS voice. + + The return value is a tuple of language and voice. + """ return self._prefs.get(PREF_TTS_DEFAULT_VOICE, DEFAULT_TTS_DEFAULT_VOICE) # type: ignore[no-any-return] async def get_cloud_user(self) -> str: diff --git a/homeassistant/components/cloud/strings.json b/homeassistant/components/cloud/strings.json index 4bef2ac9ba3..30ef88cafda 100644 --- a/homeassistant/components/cloud/strings.json +++ b/homeassistant/components/cloud/strings.json @@ -24,6 +24,17 @@ } }, "issues": { + "deprecated_gender": { + "title": "The '{deprecated_option}' text-to-speech option is deprecated", + "fix_flow": { + "step": { + "confirm": { + "title": "[%key:component::cloud::issues::deprecated_voice::title%]", + "description": "The '{deprecated_option}' option for text-to-speech in the {integration_name} integration is deprecated and will be removed.\nPlease update your automations and scripts to replace the '{deprecated_option}' option with an option for a supported '{replacement_option}' instead." + } + } + } + }, "deprecated_tts_platform_config": { "title": "The Cloud text-to-speech platform configuration is deprecated", "description": "The whole `platform: cloud` entry under the `tts:` section in configuration.yaml is deprecated and should be removed. You can use the UI to change settings for the Cloud text-to-speech platform. Please adjust your configuration.yaml and restart Home Assistant to fix this issue." diff --git a/homeassistant/components/cloud/tts.py b/homeassistant/components/cloud/tts.py index baaec15ac57..7922fc80201 100644 --- a/homeassistant/components/cloud/tts.py +++ b/homeassistant/components/cloud/tts.py @@ -7,7 +7,7 @@ import logging from typing import Any from hass_nabucasa import Cloud -from hass_nabucasa.voice import MAP_VOICE, TTS_VOICES, AudioOutput, VoiceError +from hass_nabucasa.voice import MAP_VOICE, TTS_VOICES, AudioOutput, Gender, VoiceError import voluptuous as vol from homeassistant.components.tts import ( @@ -97,17 +97,7 @@ async def async_get_engine( ) -> CloudProvider: """Set up Cloud speech component.""" cloud: Cloud[CloudClient] = hass.data[DOMAIN] - - language: str | None - gender: str | None - if discovery_info is not None: - language = None - gender = None - else: - language = config[CONF_LANG] - gender = config[ATTR_GENDER] - - cloud_provider = CloudProvider(cloud, language, gender) + cloud_provider = CloudProvider(cloud) if discovery_info is not None: discovery_info["platform_loaded"].set() return cloud_provider @@ -134,11 +124,11 @@ class CloudTTSEntity(TextToSpeechEntity): def __init__(self, cloud: Cloud[CloudClient]) -> None: """Initialize cloud text-to-speech entity.""" self.cloud = cloud - self._language, self._gender = cloud.client.prefs.tts_default_voice + self._language, self._voice = cloud.client.prefs.tts_default_voice async def _sync_prefs(self, prefs: CloudPreferences) -> None: """Sync preferences.""" - self._language, self._gender = prefs.tts_default_voice + self._language, self._voice = prefs.tts_default_voice @property def default_language(self) -> str: @@ -149,8 +139,8 @@ class CloudTTSEntity(TextToSpeechEntity): def default_options(self) -> dict[str, Any]: """Return a dict include default options.""" return { - ATTR_GENDER: self._gender, ATTR_AUDIO_OUTPUT: AudioOutput.MP3, + ATTR_VOICE: self._voice, } @property @@ -161,6 +151,7 @@ class CloudTTSEntity(TextToSpeechEntity): @property def supported_options(self) -> list[str]: """Return list of supported options like voice, emotion.""" + # The gender option is deprecated and will be removed in 2024.10.0. return [ATTR_GENDER, ATTR_VOICE, ATTR_AUDIO_OUTPUT] async def async_added_to_hass(self) -> None: @@ -184,6 +175,8 @@ class CloudTTSEntity(TextToSpeechEntity): self, message: str, language: str, options: dict[str, Any] ) -> TtsAudioType: """Load TTS from Home Assistant Cloud.""" + gender: Gender | str | None = options.get(ATTR_GENDER) + gender = handle_deprecated_gender(self.hass, gender) original_voice: str | None = options.get(ATTR_VOICE) voice = handle_deprecated_voice(self.hass, original_voice) # Process TTS @@ -191,7 +184,7 @@ class CloudTTSEntity(TextToSpeechEntity): data = await self.cloud.voice.process_tts( text=message, language=language, - gender=options.get(ATTR_GENDER), + gender=gender, voice=voice, output=options[ATTR_AUDIO_OUTPUT], ) @@ -205,24 +198,16 @@ class CloudTTSEntity(TextToSpeechEntity): class CloudProvider(Provider): """Home Assistant Cloud speech API provider.""" - def __init__( - self, cloud: Cloud[CloudClient], language: str | None, gender: str | None - ) -> None: + def __init__(self, cloud: Cloud[CloudClient]) -> None: """Initialize cloud provider.""" self.cloud = cloud self.name = "Cloud" - self._language = language - self._gender = gender - - if self._language is not None: - return - - self._language, self._gender = cloud.client.prefs.tts_default_voice + self._language, self._voice = cloud.client.prefs.tts_default_voice cloud.client.prefs.async_listen_updates(self._sync_prefs) async def _sync_prefs(self, prefs: CloudPreferences) -> None: """Sync preferences.""" - self._language, self._gender = prefs.tts_default_voice + self._language, self._voice = prefs.tts_default_voice @property def default_language(self) -> str | None: @@ -237,6 +222,7 @@ class CloudProvider(Provider): @property def supported_options(self) -> list[str]: """Return list of supported options like voice, emotion.""" + # The gender option is deprecated and will be removed in 2024.10.0. return [ATTR_GENDER, ATTR_VOICE, ATTR_AUDIO_OUTPUT] @callback @@ -250,23 +236,25 @@ class CloudProvider(Provider): def default_options(self) -> dict[str, Any]: """Return a dict include default options.""" return { - ATTR_GENDER: self._gender, ATTR_AUDIO_OUTPUT: AudioOutput.MP3, + ATTR_VOICE: self._voice, } async def async_get_tts_audio( self, message: str, language: str, options: dict[str, Any] ) -> TtsAudioType: """Load TTS from Home Assistant Cloud.""" - original_voice: str | None = options.get(ATTR_VOICE) assert self.hass is not None + gender: Gender | str | None = options.get(ATTR_GENDER) + gender = handle_deprecated_gender(self.hass, gender) + original_voice: str | None = options.get(ATTR_VOICE) voice = handle_deprecated_voice(self.hass, original_voice) # Process TTS try: data = await self.cloud.voice.process_tts( text=message, language=language, - gender=options.get(ATTR_GENDER), + gender=gender, voice=voice, output=options[ATTR_AUDIO_OUTPUT], ) @@ -277,6 +265,32 @@ class CloudProvider(Provider): return (str(options[ATTR_AUDIO_OUTPUT].value), data) +@callback +def handle_deprecated_gender( + hass: HomeAssistant, + gender: Gender | str | None, +) -> Gender | None: + """Handle deprecated gender.""" + if gender is None: + return None + async_create_issue( + hass, + DOMAIN, + "deprecated_gender", + is_fixable=True, + is_persistent=True, + severity=IssueSeverity.WARNING, + breaks_in_ha_version="2024.10.0", + translation_key="deprecated_gender", + translation_placeholders={ + "integration_name": "Home Assistant Cloud", + "deprecated_option": "gender", + "replacement_option": "voice", + }, + ) + return Gender(gender) + + @callback def handle_deprecated_voice( hass: HomeAssistant, diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 269b7b5d0c5..40092997c79 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -2,13 +2,15 @@ from copy import deepcopy from http import HTTPStatus +import json from typing import Any from unittest.mock import AsyncMock, MagicMock, Mock, patch import aiohttp -from hass_nabucasa import thingtalk, voice +from hass_nabucasa import thingtalk from hass_nabucasa.auth import Unauthenticated, UnknownError from hass_nabucasa.const import STATE_CONNECTED +from hass_nabucasa.voice import TTS_VOICES import pytest from homeassistant.components.alexa import errors as alexa_errors @@ -17,6 +19,7 @@ from homeassistant.components.assist_pipeline.pipeline import STORAGE_KEY from homeassistant.components.cloud.const import DEFAULT_EXPOSED_DOMAINS, DOMAIN from homeassistant.components.google_assistant.helpers import GoogleEntity from homeassistant.components.homeassistant import exposed_entities +from homeassistant.components.websocket_api import ERR_INVALID_FORMAT from homeassistant.core import HomeAssistant, State from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -774,7 +777,7 @@ async def test_websocket_status( "google_report_state": True, "remote_allow_remote_enable": True, "remote_enabled": False, - "tts_default_voice": ["en-US", "female"], + "tts_default_voice": ["en-US", "JennyNeural"], }, "alexa_entities": { "include_domains": [], @@ -896,14 +899,13 @@ async def test_websocket_update_preferences( client = await hass_ws_client(hass) - await client.send_json( + await client.send_json_auto_id( { - "id": 5, "type": "cloud/update_prefs", "alexa_enabled": False, "google_enabled": False, "google_secure_devices_pin": "1234", - "tts_default_voice": ["en-GB", "male"], + "tts_default_voice": ["en-GB", "RyanNeural"], "remote_allow_remote_enable": False, } ) @@ -914,7 +916,34 @@ async def test_websocket_update_preferences( assert not cloud.client.prefs.alexa_enabled assert cloud.client.prefs.google_secure_devices_pin == "1234" assert cloud.client.prefs.remote_allow_remote_enable is False - assert cloud.client.prefs.tts_default_voice == ("en-GB", "male") + assert cloud.client.prefs.tts_default_voice == ("en-GB", "RyanNeural") + + +@pytest.mark.parametrize( + ("language", "voice"), [("en-GB", "bad_voice"), ("bad_language", "RyanNeural")] +) +async def test_websocket_update_preferences_bad_voice( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + cloud: MagicMock, + setup_cloud: None, + language: str, + voice: str, +) -> None: + """Test updating preference.""" + client = await hass_ws_client(hass) + + await client.send_json_auto_id( + { + "type": "cloud/update_prefs", + "tts_default_voice": [language, voice], + } + ) + response = await client.receive_json() + + assert not response["success"] + assert response["error"]["code"] == ERR_INVALID_FORMAT + assert cloud.client.prefs.tts_default_voice == ("en-US", "JennyNeural") async def test_websocket_update_preferences_alexa_report_state( @@ -1596,24 +1625,23 @@ async def test_tts_info( setup_cloud: None, ) -> None: """Test that we can get TTS info.""" - # Verify the format is as expected - assert voice.MAP_VOICE[("en-US", voice.Gender.FEMALE)] == "JennyNeural" - client = await hass_ws_client(hass) - with patch.dict( - "homeassistant.components.cloud.http_api.MAP_VOICE", - { - ("en-US", voice.Gender.MALE): "GuyNeural", - ("en-US", voice.Gender.FEMALE): "JennyNeural", - }, - clear=True, - ): - await client.send_json({"id": 5, "type": "cloud/tts/info"}) - response = await client.receive_json() + await client.send_json_auto_id({"type": "cloud/tts/info"}) + response = await client.receive_json() assert response["success"] - assert response["result"] == {"languages": [["en-US", "male"], ["en-US", "female"]]} + assert response["result"] == { + "languages": json.loads( + json.dumps( + [ + (language, voice) + for language, voices in TTS_VOICES.items() + for voice in voices + ] + ) + ) + } @pytest.mark.parametrize( diff --git a/tests/components/cloud/test_prefs.py b/tests/components/cloud/test_prefs.py index 86e86a71583..9b0fa4c01d7 100644 --- a/tests/components/cloud/test_prefs.py +++ b/tests/components/cloud/test_prefs.py @@ -1,13 +1,15 @@ """Test Cloud preferences.""" from typing import Any -from unittest.mock import ANY, patch +from unittest.mock import ANY, MagicMock, patch import pytest from homeassistant.auth.const import GROUP_ID_ADMIN +from homeassistant.components.cloud.const import DOMAIN, PREF_TTS_DEFAULT_VOICE from homeassistant.components.cloud.prefs import STORAGE_KEY, CloudPreferences from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component async def test_set_username(hass: HomeAssistant) -> None: @@ -149,3 +151,26 @@ async def test_import_google_assistant_settings( prefs = CloudPreferences(hass) await prefs.async_initialize() assert prefs.google_connected == google_connected + + +@pytest.mark.parametrize( + ("stored_language", "expected_language", "voice"), + [("en-US", "en-US", "GuyNeural"), ("missing_language", "en-US", "JennyNeural")], +) +async def test_tts_default_voice_legacy_gender( + hass: HomeAssistant, + cloud: MagicMock, + hass_storage: dict[str, Any], + stored_language: str, + expected_language: str, + voice: str, +) -> None: + """Test tts with legacy gender as default tts voice setting in storage.""" + hass_storage[STORAGE_KEY] = { + "version": 1, + "data": {PREF_TTS_DEFAULT_VOICE: [stored_language, "male"]}, + } + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + + assert cloud.client.prefs.tts_default_voice == (expected_language, voice) diff --git a/tests/components/cloud/test_tts.py b/tests/components/cloud/test_tts.py index 3797a9784e1..f549f62b889 100644 --- a/tests/components/cloud/test_tts.py +++ b/tests/components/cloud/test_tts.py @@ -6,7 +6,7 @@ from http import HTTPStatus from typing import Any from unittest.mock import AsyncMock, MagicMock, patch -from hass_nabucasa.voice import MAP_VOICE, VoiceError, VoiceTokenError +from hass_nabucasa.voice import TTS_VOICES, VoiceError, VoiceTokenError import pytest import voluptuous as vol @@ -44,7 +44,11 @@ async def internal_url_mock(hass: HomeAssistant) -> None: def test_default_exists() -> None: """Test our default language exists.""" - assert const.DEFAULT_TTS_DEFAULT_VOICE in MAP_VOICE + assert const.DEFAULT_TTS_DEFAULT_VOICE[0] in TTS_VOICES + assert ( + const.DEFAULT_TTS_DEFAULT_VOICE[1] + in TTS_VOICES[const.DEFAULT_TTS_DEFAULT_VOICE[0]] + ) def test_schema() -> None: @@ -105,7 +109,7 @@ async def test_prefs_default_voice( assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() - assert cloud.client.prefs.tts_default_voice == ("en-US", "female") + assert cloud.client.prefs.tts_default_voice == ("en-US", "JennyNeural") on_start_callback = cloud.register_on_start.call_args[0][0] await on_start_callback() @@ -116,13 +120,13 @@ async def test_prefs_default_voice( assert engine is not None # The platform config provider will be overridden by the discovery info provider. assert engine.default_language == "en-US" - assert engine.default_options == {"gender": "female", "audio_output": "mp3"} + assert engine.default_options == {"audio_output": "mp3", "voice": "JennyNeural"} - await set_cloud_prefs({"tts_default_voice": ("nl-NL", "male")}) + await set_cloud_prefs({"tts_default_voice": ("nl-NL", "MaartenNeural")}) await hass.async_block_till_done() assert engine.default_language == "nl-NL" - assert engine.default_options == {"gender": "male", "audio_output": "mp3"} + assert engine.default_options == {"audio_output": "mp3", "voice": "MaartenNeural"} async def test_deprecated_platform_config( @@ -224,11 +228,11 @@ async def test_get_tts_audio( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_e09b5a0968_{expected_url_suffix}.mp3" + f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_e09b5a0968_{expected_url_suffix}.mp3" + f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -237,7 +241,7 @@ async def test_get_tts_audio( assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == "en-US" - assert mock_process_tts.call_args.kwargs["gender"] == "female" + assert mock_process_tts.call_args.kwargs["gender"] is None assert mock_process_tts.call_args.kwargs["output"] == "mp3" @@ -276,11 +280,11 @@ async def test_get_tts_audio_logged_out( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_e09b5a0968_{expected_url_suffix}.mp3" + f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_e09b5a0968_{expected_url_suffix}.mp3" + f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -289,7 +293,7 @@ async def test_get_tts_audio_logged_out( assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == "en-US" - assert mock_process_tts.call_args.kwargs["gender"] == "female" + assert mock_process_tts.call_args.kwargs["gender"] is None assert mock_process_tts.call_args.kwargs["output"] == "mp3" @@ -340,11 +344,11 @@ async def test_tts_entity( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_e09b5a0968_{entity_id}.mp3" + f"_en-us_5c97d21c48_{entity_id}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_e09b5a0968_{entity_id}.mp3" + f"_en-us_5c97d21c48_{entity_id}.mp3" ), } await hass.async_block_till_done() @@ -353,7 +357,7 @@ async def test_tts_entity( assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == "en-US" - assert mock_process_tts.call_args.kwargs["gender"] == "female" + assert mock_process_tts.call_args.kwargs["gender"] is None assert mock_process_tts.call_args.kwargs["output"] == "mp3" state = hass.states.get(entity_id) @@ -480,11 +484,11 @@ async def test_deprecated_voice( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_1c4ec2f170_{expected_url_suffix}.mp3" + f"_{language.lower()}_87567e3e29_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_1c4ec2f170_{expected_url_suffix}.mp3" + f"_{language.lower()}_87567e3e29_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -493,7 +497,7 @@ async def test_deprecated_voice( assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == language - assert mock_process_tts.call_args.kwargs["gender"] == "female" + assert mock_process_tts.call_args.kwargs["gender"] is None assert mock_process_tts.call_args.kwargs["voice"] == replacement_voice assert mock_process_tts.call_args.kwargs["output"] == "mp3" issue = issue_registry.async_get_issue( @@ -513,11 +517,11 @@ async def test_deprecated_voice( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_a1c3b0ac0e_{expected_url_suffix}.mp3" + f"_{language.lower()}_13646b7d32_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_a1c3b0ac0e_{expected_url_suffix}.mp3" + f"_{language.lower()}_13646b7d32_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -526,7 +530,7 @@ async def test_deprecated_voice( assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == language - assert mock_process_tts.call_args.kwargs["gender"] == "female" + assert mock_process_tts.call_args.kwargs["gender"] is None assert mock_process_tts.call_args.kwargs["voice"] == replacement_voice assert mock_process_tts.call_args.kwargs["output"] == "mp3" issue = issue_registry.async_get_issue( @@ -542,3 +546,107 @@ async def test_deprecated_voice( "deprecated_voice": deprecated_voice, "replacement_voice": replacement_voice, } + + +@pytest.mark.parametrize( + ("data", "expected_url_suffix"), + [ + ({"platform": DOMAIN}, DOMAIN), + ({"engine_id": DOMAIN}, DOMAIN), + ({"engine_id": "tts.home_assistant_cloud"}, "tts.home_assistant_cloud"), + ], +) +async def test_deprecated_gender( + hass: HomeAssistant, + issue_registry: IssueRegistry, + cloud: MagicMock, + hass_client: ClientSessionGenerator, + data: dict[str, Any], + expected_url_suffix: str, +) -> None: + """Test we create an issue when a deprecated gender is used for text-to-speech.""" + language = "zh-CN" + gender_option = "male" + mock_process_tts = AsyncMock( + return_value=b"", + ) + cloud.voice.process_tts = mock_process_tts + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + await cloud.login("test-user", "test-pass") + client = await hass_client() + + # Test without deprecated gender option. + url = "/api/tts_get_url" + data |= { + "message": "There is someone at the door.", + "language": language, + } + + req = await client.post(url, json=data) + assert req.status == HTTPStatus.OK + response = await req.json() + + assert response == { + "url": ( + "http://example.local:8123/api/tts_proxy/" + "42f18378fd4393d18c8dd11d03fa9563c1e54491" + f"_{language.lower()}_5c97d21c48_{expected_url_suffix}.mp3" + ), + "path": ( + "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" + f"_{language.lower()}_5c97d21c48_{expected_url_suffix}.mp3" + ), + } + await hass.async_block_till_done() + + assert mock_process_tts.call_count == 1 + assert mock_process_tts.call_args is not None + assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." + assert mock_process_tts.call_args.kwargs["language"] == language + assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" + assert mock_process_tts.call_args.kwargs["output"] == "mp3" + issue = issue_registry.async_get_issue("cloud", "deprecated_gender") + assert issue is None + mock_process_tts.reset_mock() + + # Test with deprecated gender option. + data["options"] = {"gender": gender_option} + + req = await client.post(url, json=data) + assert req.status == HTTPStatus.OK + response = await req.json() + + assert response == { + "url": ( + "http://example.local:8123/api/tts_proxy/" + "42f18378fd4393d18c8dd11d03fa9563c1e54491" + f"_{language.lower()}_5dded72256_{expected_url_suffix}.mp3" + ), + "path": ( + "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" + f"_{language.lower()}_5dded72256_{expected_url_suffix}.mp3" + ), + } + await hass.async_block_till_done() + + assert mock_process_tts.call_count == 1 + assert mock_process_tts.call_args is not None + assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." + assert mock_process_tts.call_args.kwargs["language"] == language + assert mock_process_tts.call_args.kwargs["gender"] == gender_option + assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" + assert mock_process_tts.call_args.kwargs["output"] == "mp3" + issue = issue_registry.async_get_issue("cloud", "deprecated_gender") + assert issue is not None + assert issue.breaks_in_ha_version == "2024.10.0" + assert issue.is_fixable is True + assert issue.is_persistent is True + assert issue.severity == IssueSeverity.WARNING + assert issue.translation_key == "deprecated_gender" + assert issue.translation_placeholders == { + "integration_name": "Home Assistant Cloud", + "deprecated_option": "gender", + "replacement_option": "voice", + } From d2663347e900a77970564a9a7092005f7961a189 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 20 Mar 2024 10:36:02 +0100 Subject: [PATCH 1310/1691] Remove Amcrest camera unique id migration (#113870) --- homeassistant/components/amcrest/camera.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 3c579e37f8a..1cbf5af4b70 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -13,15 +13,11 @@ from amcrest import AmcrestError from haffmpeg.camera import CameraMjpeg import voluptuous as vol -from homeassistant.components.camera import ( - DOMAIN as CAMERA_DOMAIN, - Camera, - CameraEntityFeature, -) +from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv, entity_registry as er +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_stream, async_aiohttp_proxy_web, @@ -37,7 +33,6 @@ from .const import ( COMM_TIMEOUT, DATA_AMCREST, DEVICES, - DOMAIN, RESOLUTION_TO_STREAM, SERVICE_UPDATE, SNAPSHOT_TIMEOUT, @@ -141,18 +136,6 @@ async def async_setup_platform( device = hass.data[DATA_AMCREST][DEVICES][name] entity = AmcrestCam(name, device, get_ffmpeg_manager(hass)) - # 2021.9.0 introduced unique id's for the camera entity, but these were not - # unique for different resolution streams. If any cameras were configured - # with this version, update the old entity with the new unique id. - serial_number = await device.api.async_serial_number - serial_number = serial_number.strip() - registry = er.async_get(hass) - entity_id = registry.async_get_entity_id(CAMERA_DOMAIN, DOMAIN, serial_number) - if entity_id is not None: - _LOGGER.debug("Updating unique id for camera %s", entity_id) - new_unique_id = f"{serial_number}-{device.resolution}-{device.channel}" - registry.async_update_entity(entity_id, new_unique_id=new_unique_id) - async_add_entities([entity], True) From 6552e1216140e68d1a80e3ca440eba58581b3eee Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Wed, 20 Mar 2024 04:57:37 -0500 Subject: [PATCH 1311/1691] Bump aioraven to 0.5.2 (#113714) --- homeassistant/components/rainforest_raven/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rainforest_raven/manifest.json b/homeassistant/components/rainforest_raven/manifest.json index 3e463af9ba4..ad161d32201 100644 --- a/homeassistant/components/rainforest_raven/manifest.json +++ b/homeassistant/components/rainforest_raven/manifest.json @@ -6,7 +6,7 @@ "dependencies": ["usb"], "documentation": "https://www.home-assistant.io/integrations/rainforest_raven", "iot_class": "local_polling", - "requirements": ["aioraven==0.5.1"], + "requirements": ["aioraven==0.5.2"], "usb": [ { "vid": "0403", diff --git a/requirements_all.txt b/requirements_all.txt index 4d284d60993..a25f2eaec8c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -347,7 +347,7 @@ aiopyarr==23.4.0 aioqsw==0.3.5 # homeassistant.components.rainforest_raven -aioraven==0.5.1 +aioraven==0.5.2 # homeassistant.components.recollect_waste aiorecollect==2023.09.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f286caa34d5..571bd2e0ef7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -320,7 +320,7 @@ aiopyarr==23.4.0 aioqsw==0.3.5 # homeassistant.components.rainforest_raven -aioraven==0.5.1 +aioraven==0.5.2 # homeassistant.components.recollect_waste aiorecollect==2023.09.0 From 249f708071576a08338bf91be63fadc75648ffbd Mon Sep 17 00:00:00 2001 From: Floris272 <60342568+Floris272@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:28:27 +0100 Subject: [PATCH 1312/1691] Bump bluecurrent-api to 1.2.2 (#110483) --- .../components/blue_current/__init__.py | 88 ++++++++----------- .../components/blue_current/entity.py | 2 +- .../components/blue_current/manifest.json | 3 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/blue_current/__init__.py | 23 +++-- .../blue_current/test_config_flow.py | 5 ++ tests/components/blue_current/test_init.py | 52 +++++------ 8 files changed, 86 insertions(+), 91 deletions(-) diff --git a/homeassistant/components/blue_current/__init__.py b/homeassistant/components/blue_current/__init__.py index 39c77c1bdfe..55362a7392d 100644 --- a/homeassistant/components/blue_current/__init__.py +++ b/homeassistant/components/blue_current/__init__.py @@ -4,7 +4,6 @@ from __future__ import annotations import asyncio from contextlib import suppress -from datetime import datetime from typing import Any from bluecurrent_api import Client @@ -16,24 +15,17 @@ from bluecurrent_api.exceptions import ( ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_NAME, - CONF_API_TOKEN, - EVENT_HOMEASSISTANT_STOP, - Platform, -) -from homeassistant.core import Event, HomeAssistant +from homeassistant.const import ATTR_NAME, CONF_API_TOKEN, Platform +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.event import async_call_later from .const import DOMAIN, EVSE_ID, LOGGER, MODEL_TYPE PLATFORMS = [Platform.SENSOR] CHARGE_POINTS = "CHARGE_POINTS" DATA = "data" -SMALL_DELAY = 1 -LARGE_DELAY = 20 +DELAY = 5 GRID = "GRID" OBJECT = "object" @@ -48,26 +40,19 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b connector = Connector(hass, config_entry, client) try: - await connector.connect(api_token) + await client.validate_api_token(api_token) except InvalidApiToken as err: raise ConfigEntryAuthFailed("Invalid API token.") from err except BlueCurrentException as err: raise ConfigEntryNotReady from err + config_entry.async_create_background_task( + hass, connector.run_task(), "blue_current-websocket" + ) - hass.async_create_background_task(connector.start_loop(), "blue_current-websocket") - await client.get_charge_points() - - await client.wait_for_response() + await client.wait_for_charge_points() hass.data[DOMAIN][config_entry.entry_id] = connector await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - config_entry.async_on_unload(connector.disconnect) - - async def _async_disconnect_websocket(_: Event) -> None: - await connector.disconnect() - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_disconnect_websocket) - return True @@ -95,12 +80,6 @@ class Connector: self.client = client self.charge_points: dict[str, dict] = {} self.grid: dict[str, Any] = {} - self.available = False - - async def connect(self, token: str) -> None: - """Register on_data and connect to the websocket.""" - await self.client.connect(token) - self.available = True async def on_data(self, message: dict) -> None: """Handle received data.""" @@ -158,34 +137,39 @@ class Connector: """Dispatch a grid signal.""" async_dispatcher_send(self.hass, f"{DOMAIN}_grid_update") - async def start_loop(self) -> None: + async def run_task(self) -> None: """Start the receive loop.""" try: - await self.client.start_loop(self.on_data) - except BlueCurrentException as err: - LOGGER.warning( - "Disconnected from the Blue Current websocket. Retrying to connect in background. %s", - err, - ) + while True: + try: + await self.client.connect(self.on_data) + except RequestLimitReached: + LOGGER.warning( + "Request limit reached. reconnecting at 00:00 (Europe/Amsterdam)" + ) + delay = self.client.get_next_reset_delta().seconds + except WebsocketError: + LOGGER.debug("Disconnected, retrying in background") + delay = DELAY - async_call_later(self.hass, SMALL_DELAY, self.reconnect) + self._on_disconnect() + await asyncio.sleep(delay) + finally: + await self._disconnect() - async def reconnect(self, _event_time: datetime | None = None) -> None: - """Keep trying to reconnect to the websocket.""" - try: - await self.connect(self.config.data[CONF_API_TOKEN]) - LOGGER.debug("Reconnected to the Blue Current websocket") - self.hass.async_create_task(self.start_loop()) - except RequestLimitReached: - self.available = False - async_call_later( - self.hass, self.client.get_next_reset_delta(), self.reconnect - ) - except WebsocketError: - self.available = False - async_call_later(self.hass, LARGE_DELAY, self.reconnect) + def _on_disconnect(self) -> None: + """Dispatch signals to update entity states.""" + for evse_id in self.charge_points: + self.dispatch_value_update_signal(evse_id) + self.dispatch_grid_update_signal() - async def disconnect(self) -> None: + async def _disconnect(self) -> None: """Disconnect from the websocket.""" with suppress(WebsocketError): await self.client.disconnect() + self._on_disconnect() + + @property + def connected(self) -> bool: + """Returns the connection status.""" + return self.client.is_connected() diff --git a/homeassistant/components/blue_current/entity.py b/homeassistant/components/blue_current/entity.py index 547b2410000..ecbbd8f0851 100644 --- a/homeassistant/components/blue_current/entity.py +++ b/homeassistant/components/blue_current/entity.py @@ -40,7 +40,7 @@ class BlueCurrentEntity(Entity): @property def available(self) -> bool: """Return entity availability.""" - return self.connector.available and self.has_value + return self.connector.connected and self.has_value @callback @abstractmethod diff --git a/homeassistant/components/blue_current/manifest.json b/homeassistant/components/blue_current/manifest.json index cadaac30d68..fddd48554e2 100644 --- a/homeassistant/components/blue_current/manifest.json +++ b/homeassistant/components/blue_current/manifest.json @@ -5,5 +5,6 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/blue_current", "iot_class": "cloud_push", - "requirements": ["bluecurrent-api==1.0.6"] + "loggers": ["bluecurrent_api"], + "requirements": ["bluecurrent-api==1.2.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index a25f2eaec8c..a56caf9d960 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -568,7 +568,7 @@ blinkpy==0.22.6 blockchain==1.4.4 # homeassistant.components.blue_current -bluecurrent-api==1.0.6 +bluecurrent-api==1.2.2 # homeassistant.components.bluemaestro bluemaestro-ble==0.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 571bd2e0ef7..738f43a1eca 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -487,7 +487,7 @@ blebox-uniapi==2.2.2 blinkpy==0.22.6 # homeassistant.components.blue_current -bluecurrent-api==1.0.6 +bluecurrent-api==1.2.2 # homeassistant.components.bluemaestro bluemaestro-ble==0.2.3 diff --git a/tests/components/blue_current/__init__.py b/tests/components/blue_current/__init__.py index e020855b59c..b5c15064449 100644 --- a/tests/components/blue_current/__init__.py +++ b/tests/components/blue_current/__init__.py @@ -31,15 +31,21 @@ def create_client_mock( future_container: FutureContainer, started_loop: Event, charge_point: dict, - status: dict | None, - grid: dict | None, + status: dict, + grid: dict, ) -> MagicMock: """Create a mock of the bluecurrent-api Client.""" client_mock = MagicMock(spec=Client) + received_charge_points = Event() - async def start_loop(receiver): + async def wait_for_charge_points(): + """Wait until chargepoints are received.""" + await received_charge_points.wait() + + async def connect(receiver): """Set the receiver and await future.""" client_mock.receiver = receiver + await client_mock.get_charge_points() started_loop.set() started_loop.clear() @@ -50,13 +56,13 @@ def create_client_mock( async def get_charge_points() -> None: """Send a list of charge points to the callback.""" - await started_loop.wait() await client_mock.receiver( { "object": "CHARGE_POINTS", "data": [charge_point], } ) + received_charge_points.set() async def get_status(evse_id: str) -> None: """Send the status of a charge point to the callback.""" @@ -71,7 +77,8 @@ def create_client_mock( """Send the grid status to the callback.""" await client_mock.receiver({"object": "GRID_STATUS", "data": grid}) - client_mock.start_loop.side_effect = start_loop + client_mock.connect.side_effect = connect + client_mock.wait_for_charge_points.side_effect = wait_for_charge_points client_mock.get_charge_points.side_effect = get_charge_points client_mock.get_status.side_effect = get_status client_mock.get_grid_status.side_effect = get_grid_status @@ -92,6 +99,12 @@ async def init_integration( if charge_point is None: charge_point = DEFAULT_CHARGE_POINT + if status is None: + status = {} + + if grid is None: + grid = {} + future_container = FutureContainer(hass.loop.create_future()) started_loop = Event() diff --git a/tests/components/blue_current/test_config_flow.py b/tests/components/blue_current/test_config_flow.py index f56e722d785..2e278af4982 100644 --- a/tests/components/blue_current/test_config_flow.py +++ b/tests/components/blue_current/test_config_flow.py @@ -127,6 +127,11 @@ async def test_reauth( ), patch( "homeassistant.components.blue_current.config_flow.Client.get_email", return_value="test@email.com", + ), patch( + "homeassistant.components.blue_current.config_flow.Client.wait_for_charge_points", + ), patch( + "homeassistant.components.blue_current.Client.connect", + lambda self, on_data: hass.loop.create_future(), ): config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/blue_current/test_init.py b/tests/components/blue_current/test_init.py index 37c14922674..4f570156c82 100644 --- a/tests/components/blue_current/test_init.py +++ b/tests/components/blue_current/test_init.py @@ -29,15 +29,22 @@ async def test_load_unload_entry( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test load and unload entry.""" - with patch("homeassistant.components.blue_current.Client", autospec=True): + with patch( + "homeassistant.components.blue_current.Client.validate_api_token" + ), patch( + "homeassistant.components.blue_current.Client.wait_for_charge_points" + ), patch("homeassistant.components.blue_current.Client.disconnect"), patch( + "homeassistant.components.blue_current.Client.connect", + lambda self, on_data: hass.loop.create_future(), + ): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - assert config_entry.state == ConfigEntryState.LOADED + assert config_entry.state == ConfigEntryState.LOADED - await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - assert config_entry.state == ConfigEntryState.NOT_LOADED + await hass.config_entries.async_unload(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state == ConfigEntryState.NOT_LOADED @pytest.mark.parametrize( @@ -55,43 +62,29 @@ async def test_config_exceptions( ) -> None: """Test if the correct config error is raised when connecting to the api fails.""" with patch( - "homeassistant.components.blue_current.Client.connect", + "homeassistant.components.blue_current.Client.validate_api_token", side_effect=api_error, ), pytest.raises(config_error): config_entry.add_to_hass(hass) await async_setup_entry(hass, config_entry) -async def test_start_loop(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: - """Test start_loop.""" +async def test_connect_websocket_error( + hass: HomeAssistant, config_entry: MockConfigEntry +) -> None: + """Test reconnect when connect throws a WebsocketError.""" - with patch("homeassistant.components.blue_current.SMALL_DELAY", 0): + with patch("homeassistant.components.blue_current.DELAY", 0): mock_client, started_loop, future_container = await init_integration( hass, config_entry ) - future_container.future.set_exception(BlueCurrentException) + future_container.future.set_exception(WebsocketError) await started_loop.wait() assert mock_client.connect.call_count == 2 -async def test_reconnect_websocket_error( - hass: HomeAssistant, config_entry: MockConfigEntry -) -> None: - """Test reconnect when connect throws a WebsocketError.""" - - with patch("homeassistant.components.blue_current.LARGE_DELAY", 0): - mock_client, started_loop, future_container = await init_integration( - hass, config_entry - ) - future_container.future.set_exception(BlueCurrentException) - mock_client.connect.side_effect = [WebsocketError, None] - - await started_loop.wait() - assert mock_client.connect.call_count == 3 - - -async def test_reconnect_request_limit_reached_error( +async def test_connect_request_limit_reached_error( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test reconnect when connect throws a RequestLimitReached.""" @@ -99,10 +92,9 @@ async def test_reconnect_request_limit_reached_error( mock_client, started_loop, future_container = await init_integration( hass, config_entry ) - future_container.future.set_exception(BlueCurrentException) - mock_client.connect.side_effect = [RequestLimitReached, None] + future_container.future.set_exception(RequestLimitReached) mock_client.get_next_reset_delta.return_value = timedelta(seconds=0) await started_loop.wait() assert mock_client.get_next_reset_delta.call_count == 1 - assert mock_client.connect.call_count == 3 + assert mock_client.connect.call_count == 2 From 42873cacf5832f334c6d0bc0e79a973d3a5e7e86 Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Wed, 20 Mar 2024 11:29:15 +0100 Subject: [PATCH 1313/1691] Tado fix water heater (#113464) Co-authored-by: Joostlek --- homeassistant/components/tado/water_heater.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index 87cc93879fd..99172228973 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -3,7 +3,6 @@ import logging from typing import Any -import PyTado import voluptuous as vol from homeassistant.components.water_heater import ( @@ -30,8 +29,6 @@ from .const import ( DATA, DOMAIN, SIGNAL_TADO_UPDATE_RECEIVED, - TADO_DEFAULT_MAX_TEMP, - TADO_DEFAULT_MIN_TEMP, TYPE_HOT_WATER, ) from .entity import TadoZoneEntity @@ -134,8 +131,8 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): zone_name: str, zone_id: int, supports_temperature_control: bool, - min_temp: float | None = None, - max_temp: float | None = None, + min_temp, + max_temp, ) -> None: """Initialize of Tado water heater entity.""" self._tado = tado @@ -147,8 +144,8 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): self._device_is_active = False self._supports_temperature_control = supports_temperature_control - self._min_temperature = min_temp or TADO_DEFAULT_MIN_TEMP - self._max_temperature = max_temp or TADO_DEFAULT_MAX_TEMP + self._min_temperature = min_temp + self._max_temperature = max_temp self._target_temp: float | None = None @@ -158,7 +155,7 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): self._current_tado_hvac_mode = CONST_MODE_SMART_SCHEDULE self._overlay_mode = CONST_MODE_SMART_SCHEDULE - self._tado_zone_data: PyTado.TadoZone = {} + self._tado_zone_data: Any = None async def async_added_to_hass(self) -> None: """Register for sensor updates.""" From 25be71e05bf5cb1d0f37d44544402d0035e0530c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Cla=C3=9Fen?= Date: Wed, 20 Mar 2024 11:42:01 +0100 Subject: [PATCH 1314/1691] Bump numato-gpio to v0.13.0 (#113182) --- .../components/numato/binary_sensor.py | 11 +++- homeassistant/components/numato/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/numato/numato_mock.py | 3 +- tests/components/numato/test_binary_sensor.py | 63 +++++++++++++++---- 6 files changed, 65 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/numato/binary_sensor.py b/homeassistant/components/numato/binary_sensor.py index ad81164d4fc..1f664a372ba 100644 --- a/homeassistant/components/numato/binary_sensor.py +++ b/homeassistant/components/numato/binary_sensor.py @@ -54,7 +54,6 @@ def setup_platform( for port, port_name in ports.items(): try: api.setup_input(device_id, port) - api.edge_detect(device_id, port, partial(read_gpio, device_id)) except NumatoGpioError as err: _LOGGER.error( @@ -68,7 +67,17 @@ def setup_platform( err, ) continue + try: + api.edge_detect(device_id, port, partial(read_gpio, device_id)) + except NumatoGpioError as err: + _LOGGER.info( + "Notification setup failed on device %s, " + "updates on binary sensor %s only in polling mode: %s", + device_id, + port_name, + err, + ) binary_sensors.append( NumatoGpioBinarySensor( port_name, diff --git a/homeassistant/components/numato/manifest.json b/homeassistant/components/numato/manifest.json index e6efcea5315..f7bcf0527c2 100644 --- a/homeassistant/components/numato/manifest.json +++ b/homeassistant/components/numato/manifest.json @@ -6,5 +6,5 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["numato_gpio"], - "requirements": ["numato-gpio==0.12.0"] + "requirements": ["numato-gpio==0.13.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index a56caf9d960..8a0595fd479 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1406,7 +1406,7 @@ nsw-fuel-api-client==1.1.0 nuheat==1.0.1 # homeassistant.components.numato -numato-gpio==0.12.0 +numato-gpio==0.13.0 # homeassistant.components.compensation # homeassistant.components.iqvia diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 738f43a1eca..32f3ea73a97 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1124,7 +1124,7 @@ nsw-fuel-api-client==1.1.0 nuheat==1.0.1 # homeassistant.components.numato -numato-gpio==0.12.0 +numato-gpio==0.13.0 # homeassistant.components.compensation # homeassistant.components.iqvia diff --git a/tests/components/numato/numato_mock.py b/tests/components/numato/numato_mock.py index 04c10911faf..097a785beb1 100644 --- a/tests/components/numato/numato_mock.py +++ b/tests/components/numato/numato_mock.py @@ -62,7 +62,8 @@ class NumatoModuleMock: Ignore the device list argument, mock discovers /dev/ttyACM0. """ - self.devices[0] = NumatoModuleMock.NumatoDeviceMock("/dev/ttyACM0") + if not self.devices: + self.devices[0] = NumatoModuleMock.NumatoDeviceMock("/dev/ttyACM0") def cleanup(self): """Mockup for the numato device cleanup.""" diff --git a/tests/components/numato/test_binary_sensor.py b/tests/components/numato/test_binary_sensor.py index 6b45ef05a0d..e353de5e7df 100644 --- a/tests/components/numato/test_binary_sensor.py +++ b/tests/components/numato/test_binary_sensor.py @@ -1,11 +1,17 @@ """Tests for the numato binary_sensor platform.""" +import logging +from unittest.mock import patch + +import pytest + from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import discovery from homeassistant.setup import async_setup_component from .common import NUMATO_CFG, mockup_raise +from .numato_mock import NumatoGpioError, NumatoModuleMock MOCKUP_ENTITY_IDS = { "binary_sensor.numato_binary_sensor_mock_port2", @@ -25,23 +31,25 @@ async def test_failing_setups_no_entities( assert entity_id not in hass.states.async_entity_ids() -async def test_setup_callbacks( - hass: HomeAssistant, numato_fixture, monkeypatch -) -> None: +async def test_setup_callbacks(hass: HomeAssistant, numato_fixture) -> None: """During setup a callback shall be registered.""" - numato_fixture.discover() + with patch.object( + NumatoModuleMock.NumatoDeviceMock, "add_event_detect" + ) as mock_add_event_detect: + numato_fixture.discover() + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() # wait until services are registered - def mock_add_event_detect(self, port, callback, direction): - assert self == numato_fixture.devices[0] - assert port == 1 - assert callback is callable - assert direction == numato_fixture.BOTH - - monkeypatch.setattr( - numato_fixture.devices[0], "add_event_detect", mock_add_event_detect + mock_add_event_detect.assert_called() + assert {call.args[0] for call in mock_add_event_detect.mock_calls} == { + int(port) + for port in NUMATO_CFG["numato"]["devices"][0]["binary_sensors"]["ports"] + } + assert all(callable(call.args[1]) for call in mock_add_event_detect.mock_calls) + assert all( + call.args[2] == numato_fixture.BOTH for call in mock_add_event_detect.mock_calls ) - assert await async_setup_component(hass, "numato", NUMATO_CFG) async def test_hass_binary_sensor_notification( @@ -73,3 +81,32 @@ async def test_binary_sensor_setup_without_discovery_info( await hass.async_block_till_done() # wait for numato platform to be loaded for entity_id in MOCKUP_ENTITY_IDS: assert entity_id in hass.states.async_entity_ids() + + +async def test_binary_sensor_setup_no_notify( + hass: HomeAssistant, + numato_fixture, + caplog: pytest.LogCaptureFixture, +) -> None: + """Setup of a device without notification capability shall print an info message.""" + caplog.set_level(logging.INFO) + + def raise_notification_error(self, port, callback, direction): + raise NumatoGpioError( + f"{repr(self)} Mockup device doesn't support notifications." + ) + + with patch.object( + NumatoModuleMock.NumatoDeviceMock, + "add_event_detect", + raise_notification_error, + ): + numato_fixture.discover() + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() # wait until services are registered + + assert all( + f"updates on binary sensor numato_binary_sensor_mock_port{port} only in polling mode" + in caplog.text + for port in NUMATO_CFG["numato"]["devices"][0]["binary_sensors"]["ports"] + ) From fc6a83559f37dfa6e7f0851c0794c0d509cec086 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 20 Mar 2024 11:58:04 +0100 Subject: [PATCH 1315/1691] Add floor template functions (#110847) --- homeassistant/helpers/template.py | 56 ++++++++++++ tests/helpers/test_template.py | 142 ++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 20140c28ba1..f7253097f57 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -83,6 +83,7 @@ from . import ( area_registry, device_registry, entity_registry, + floor_registry as fr, issue_registry, location as loc_helper, ) @@ -1387,6 +1388,45 @@ def issue(hass: HomeAssistant, domain: str, issue_id: str) -> dict[str, Any] | N return None +def floors(hass: HomeAssistant) -> Iterable[str | None]: + """Return all floors.""" + floor_registry = fr.async_get(hass) + return [floor.floor_id for floor in floor_registry.async_list_floors()] + + +def floor_id(hass: HomeAssistant, lookup_value: Any) -> str | None: + """Get the floor ID from a floor name.""" + floor_registry = fr.async_get(hass) + if floor := floor_registry.async_get_floor_by_name(str(lookup_value)): + return floor.floor_id + return None + + +def floor_name(hass: HomeAssistant, lookup_value: str) -> str | None: + """Get the floor name from a floor id.""" + floor_registry = fr.async_get(hass) + if floor := floor_registry.async_get_floor(lookup_value): + return floor.name + return None + + +def floor_areas(hass: HomeAssistant, floor_id_or_name: str) -> Iterable[str]: + """Return area IDs for a given floor ID or name.""" + _floor_id: str | None + # If floor_name returns a value, we know the input was an ID, otherwise we + # assume it's a name, and if it's neither, we return early + if floor_name(hass, floor_id_or_name) is not None: + _floor_id = floor_id_or_name + else: + _floor_id = floor_id(hass, floor_id_or_name) + if _floor_id is None: + return [] + + area_reg = area_registry.async_get(hass) + entries = area_registry.async_entries_for_floor(area_reg, _floor_id) + return [entry.id for entry in entries if entry.id] + + def areas(hass: HomeAssistant) -> Iterable[str | None]: """Return all areas.""" area_reg = area_registry.async_get(hass) @@ -2668,6 +2708,18 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["area_devices"] = hassfunction(area_devices) self.filters["area_devices"] = self.globals["area_devices"] + self.globals["floors"] = hassfunction(floors) + self.filters["floors"] = self.globals["floors"] + + self.globals["floor_id"] = hassfunction(floor_id) + self.filters["floor_id"] = self.globals["floor_id"] + + self.globals["floor_name"] = hassfunction(floor_name) + self.filters["floor_name"] = self.globals["floor_name"] + + self.globals["floor_areas"] = hassfunction(floor_areas) + self.filters["floor_areas"] = self.globals["floor_areas"] + self.globals["integration_entities"] = hassfunction(integration_entities) self.filters["integration_entities"] = self.globals["integration_entities"] @@ -2700,6 +2752,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): "device_id", "area_id", "area_name", + "floor_id", + "floor_name", "relative_time", "today_at", ] @@ -2709,6 +2763,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): "device_id", "area_id", "area_name", + "floor_id", + "floor_name", "has_value", ] hass_tests = [ diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 9fb73c524bf..211877bca6b 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -38,6 +38,7 @@ from homeassistant.helpers import ( device_registry as dr, entity, entity_registry as er, + floor_registry as fr, issue_registry as ir, template, translation, @@ -5167,3 +5168,144 @@ async def test_lru_increases_with_many_entities(hass: HomeAssistant) -> None: assert template.CACHED_TEMPLATE_NO_COLLECT_LRU.get_size() == int( round(mock_entity_count * template.ENTITY_COUNT_GROWTH_FACTOR) ) + + +async def test_floors( + hass: HomeAssistant, + floor_registry: fr.FloorRegistry, +) -> None: + """Test floors function.""" + + # Test no floors + info = render_to_info(hass, "{{ floors() }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test one floor + floor1 = floor_registry.async_create("First floor") + info = render_to_info(hass, "{{ floors() }}") + assert_result_info(info, [floor1.floor_id]) + assert info.rate_limit is None + + # Test multiple floors + floor2 = floor_registry.async_create("Second floor") + info = render_to_info(hass, "{{ floors() }}") + assert_result_info(info, [floor1.floor_id, floor2.floor_id]) + assert info.rate_limit is None + + +async def test_floor_id( + hass: HomeAssistant, + floor_registry: fr.FloorRegistry, +) -> None: + """Test floor_id function.""" + + # Test non existing floor name + info = render_to_info(hass, "{{ floor_id('Third floor') }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'Third floor' | floor_id }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ floor_id(42) }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | floor_id }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test with an actual floor + floor = floor_registry.async_create("First floor") + info = render_to_info(hass, "{{ floor_id('First floor') }}") + assert_result_info(info, floor.floor_id) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'First floor' | floor_id }}") + assert_result_info(info, floor.floor_id) + assert info.rate_limit is None + + +async def test_floor_name( + hass: HomeAssistant, + floor_registry: fr.FloorRegistry, +) -> None: + """Test floor_name function.""" + # Test non existing floor ID + info = render_to_info(hass, "{{ floor_name('third_floor') }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'third_floor' | floor_name }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ floor_name(42) }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | floor_name }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test existing floor ID + floor = floor_registry.async_create("First floor") + info = render_to_info(hass, f"{{{{ floor_name('{floor.floor_id}') }}}}") + assert_result_info(info, floor.name) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{floor.floor_id}' | floor_name }}}}") + assert_result_info(info, floor.name) + assert info.rate_limit is None + + +async def test_floor_areas( + hass: HomeAssistant, + floor_registry: fr.FloorRegistry, + area_registry: ar.AreaRegistry, +) -> None: + """Test floor_areas function.""" + + # Test non existing floor ID + info = render_to_info(hass, "{{ floor_areas('skyring') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'skyring' | floor_areas }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ floor_areas(42) }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | floor_areas }}") + assert_result_info(info, []) + assert info.rate_limit is None + + floor = floor_registry.async_create("First floor") + area = area_registry.async_create("Living room") + area_registry.async_update(area.id, floor_id=floor.floor_id) + + # Get areas by floor ID + info = render_to_info(hass, f"{{{{ floor_areas('{floor.floor_id}') }}}}") + assert_result_info(info, [area.id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{floor.floor_id}' | floor_areas }}}}") + assert_result_info(info, [area.id]) + assert info.rate_limit is None + + # Get entities by floor name + info = render_to_info(hass, f"{{{{ floor_areas('{floor.name}') }}}}") + assert_result_info(info, [area.id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{floor.name}' | floor_areas }}}}") + assert_result_info(info, [area.id]) + assert info.rate_limit is None From f8de214560fcb4778984933c8f756606c94f38f4 Mon Sep 17 00:00:00 2001 From: Alin Balutoiu Date: Wed, 20 Mar 2024 10:59:13 +0000 Subject: [PATCH 1316/1691] Make temperature a required parameter for tado climate service (#113872) Make temperature a required parameter --- homeassistant/components/tado/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 47bd2bc16f3..3261b6e212d 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -401,7 +401,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): def set_timer( self, - temperature: float | None = None, + temperature: float, time_period: int | None = None, requested_overlay: str | None = None, ): From dc9f0a55a60c11f83e5e6487f3116cb60cd4a7d1 Mon Sep 17 00:00:00 2001 From: Jonathan Sider Date: Wed, 20 Mar 2024 05:59:44 -0500 Subject: [PATCH 1317/1691] Update Amcrest services.yaml (#111161) --- .../components/amcrest/services.yaml | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/amcrest/services.yaml b/homeassistant/components/amcrest/services.yaml index cdcaf0e2c04..057f0842f30 100644 --- a/homeassistant/components/amcrest/services.yaml +++ b/homeassistant/components/amcrest/services.yaml @@ -3,46 +3,59 @@ enable_recording: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera disable_recording: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera enable_audio: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera disable_audio: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera enable_motion_recording: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera disable_motion_recording: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera goto_preset: fields: entity_id: + example: "camera.house_front" selector: entity: integration: amcrest @@ -59,7 +72,9 @@ set_color_bw: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera color_bw: selector: select: @@ -73,21 +88,27 @@ start_tour: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera stop_tour: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera ptz_control: fields: entity_id: example: "camera.house_front" selector: - text: + entity: + integration: amcrest + domain: camera movement: required: true selector: From eafb4190ef4560a720cd40d3da9079008ec23830 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 20 Mar 2024 12:35:34 +0100 Subject: [PATCH 1318/1691] Change quotes in cloud translations (#113871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Escape quote in cloud translations * Update homeassistant/components/cloud/strings.json Co-authored-by: Joakim Sørensen * Update homeassistant/components/cloud/strings.json Co-authored-by: Joakim Sørensen * Update homeassistant/components/cloud/strings.json --------- Co-authored-by: Joakim Sørensen --- homeassistant/components/cloud/strings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cloud/strings.json b/homeassistant/components/cloud/strings.json index 30ef88cafda..16a82a27c1a 100644 --- a/homeassistant/components/cloud/strings.json +++ b/homeassistant/components/cloud/strings.json @@ -25,12 +25,12 @@ }, "issues": { "deprecated_gender": { - "title": "The '{deprecated_option}' text-to-speech option is deprecated", + "title": "The `{deprecated_option}` text-to-speech option is deprecated", "fix_flow": { "step": { "confirm": { "title": "[%key:component::cloud::issues::deprecated_voice::title%]", - "description": "The '{deprecated_option}' option for text-to-speech in the {integration_name} integration is deprecated and will be removed.\nPlease update your automations and scripts to replace the '{deprecated_option}' option with an option for a supported '{replacement_option}' instead." + "description": "The `{deprecated_option}` option for text-to-speech in the {integration_name} integration is deprecated and will be removed.\nPlease update your automations and scripts to replace the `{deprecated_option}` option with an option for a supported `{replacement_option}` instead." } } } @@ -45,7 +45,7 @@ "step": { "confirm": { "title": "[%key:component::cloud::issues::deprecated_voice::title%]", - "description": "The '{deprecated_voice}' voice is deprecated and will be removed.\nPlease update your automations and scripts to replace the '{deprecated_voice}' with another voice like eg. '{replacement_voice}'." + "description": "The `{deprecated_voice}`voice is deprecated and will be removed.\nPlease update your automations and scripts to replace the `{deprecated_voice}` with another voice like eg. `{replacement_voice}`." } } } From afa95177160ef1925bbc0f0f5b10d420c5752974 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 20 Mar 2024 13:10:35 +0100 Subject: [PATCH 1319/1691] Catch API errors in cast media_player service handlers (#113839) * Catch API errors in cast media_player service handlers * Remove left over debug code * Fix wrapping of coroutine function with api_error --- homeassistant/components/cast/media_player.py | 68 +++++++++++++++++-- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 037e154eb96..eedbd0dd0b1 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -5,9 +5,10 @@ from __future__ import annotations from collections.abc import Callable from contextlib import suppress from datetime import datetime +from functools import wraps import json import logging -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar import pychromecast from pychromecast.controllers.homeassistant import HomeAssistantController @@ -19,6 +20,7 @@ from pychromecast.controllers.media import ( ) from pychromecast.controllers.multizone import MultizoneManager from pychromecast.controllers.receiver import VOLUME_CONTROL_TYPE_FIXED +from pychromecast.error import PyChromecastError from pychromecast.quick_play import quick_play from pychromecast.socket_client import ( CONNECTION_STATUS_CONNECTED, @@ -84,6 +86,34 @@ APP_IDS_UNRELIABLE_MEDIA_INFO = ("Netflix",) CAST_SPLASH = "https://www.home-assistant.io/images/cast/splash.png" +_CastDeviceT = TypeVar("_CastDeviceT", bound="CastDevice") +_R = TypeVar("_R") +_P = ParamSpec("_P") + +_FuncType = Callable[Concatenate[_CastDeviceT, _P], _R] +_ReturnFuncType = Callable[Concatenate[_CastDeviceT, _P], _R] + + +def api_error( + func: _FuncType[_CastDeviceT, _P, _R], +) -> _ReturnFuncType[_CastDeviceT, _P, _R]: + """Handle PyChromecastError and reraise a HomeAssistantError.""" + + @wraps(func) + def wrapper(self: _CastDeviceT, *args: _P.args, **kwargs: _P.kwargs) -> _R: + """Wrap a CastDevice method.""" + try: + return_value = func(self, *args, **kwargs) + except PyChromecastError as err: + raise HomeAssistantError( + f"{self.__class__.__name__}.{func.__name__} Failed: {err}" + ) from err + + return return_value + + return wrapper + + @callback def _async_create_cast_device(hass: HomeAssistant, info: ChromecastInfo): """Create a CastDevice entity or dynamic group from the chromecast object. @@ -478,6 +508,21 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): return media_controller + @api_error + def _quick_play(self, app_name: str, data: dict[str, Any]) -> None: + """Launch the app `app_name` and start playing media defined by `data`.""" + quick_play(self._get_chromecast(), app_name, data) + + @api_error + def _quit_app(self) -> None: + """Quit the currently running app.""" + self._get_chromecast().quit_app() + + @api_error + def _start_app(self, app_id: str) -> None: + """Start an app.""" + self._get_chromecast().start_app(app_id) + def turn_on(self) -> None: """Turn on the cast device.""" @@ -488,52 +533,61 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): if chromecast.app_id is not None: # Quit the previous app before starting splash screen or media player - chromecast.quit_app() + self._quit_app() # The only way we can turn the Chromecast is on is by launching an app if chromecast.cast_type == pychromecast.const.CAST_TYPE_CHROMECAST: app_data = {"media_id": CAST_SPLASH, "media_type": "image/png"} - quick_play(chromecast, "default_media_receiver", app_data) + self._quick_play("default_media_receiver", app_data) else: - chromecast.start_app(pychromecast.config.APP_MEDIA_RECEIVER) + self._start_app(pychromecast.config.APP_MEDIA_RECEIVER) + @api_error def turn_off(self) -> None: """Turn off the cast device.""" self._get_chromecast().quit_app() + @api_error def mute_volume(self, mute: bool) -> None: """Mute the volume.""" self._get_chromecast().set_volume_muted(mute) + @api_error def set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" self._get_chromecast().set_volume(volume) + @api_error def media_play(self) -> None: """Send play command.""" media_controller = self._media_controller() media_controller.play() + @api_error def media_pause(self) -> None: """Send pause command.""" media_controller = self._media_controller() media_controller.pause() + @api_error def media_stop(self) -> None: """Send stop command.""" media_controller = self._media_controller() media_controller.stop() + @api_error def media_previous_track(self) -> None: """Send previous track command.""" media_controller = self._media_controller() media_controller.queue_prev() + @api_error def media_next_track(self) -> None: """Send next track command.""" media_controller = self._media_controller() media_controller.queue_next() + @api_error def media_seek(self, position: float) -> None: """Seek the media to a specific location.""" media_controller = self._media_controller() @@ -646,7 +700,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): if "app_id" in app_data: app_id = app_data.pop("app_id") _LOGGER.info("Starting Cast app by ID %s", app_id) - await self.hass.async_add_executor_job(chromecast.start_app, app_id) + await self.hass.async_add_executor_job(self._start_app, app_id) if app_data: _LOGGER.warning( "Extra keys %s were ignored. Please use app_name to cast media", @@ -657,7 +711,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): app_name = app_data.pop("app_name") try: await self.hass.async_add_executor_job( - quick_play, chromecast, app_name, app_data + self._quick_play, app_name, app_data ) except NotImplementedError: _LOGGER.error("App %s not supported", app_name) @@ -727,7 +781,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): app_data, ) await self.hass.async_add_executor_job( - quick_play, chromecast, "default_media_receiver", app_data + self._quick_play, "default_media_receiver", app_data ) def _media_status(self): From c5f69259480996f03518f77ffe69d9b2485a369b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 20 Mar 2024 15:31:48 +0100 Subject: [PATCH 1320/1691] Add matter device serial number (#113878) --- homeassistant/components/matter/adapter.py | 9 +++++++-- tests/components/matter/test_adapter.py | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/matter/adapter.py b/homeassistant/components/matter/adapter.py index e6185de17a6..a3536435ded 100644 --- a/homeassistant/components/matter/adapter.py +++ b/homeassistant/components/matter/adapter.py @@ -185,10 +185,14 @@ class MatterAdapter: endpoint, ) identifiers = {(DOMAIN, f"{ID_TYPE_DEVICE_ID}_{node_device_id}")} + serial_number: str | None = None # if available, we also add the serialnumber as identifier - if basic_info.serialNumber and "test" not in basic_info.serialNumber.lower(): + if ( + basic_info_serial_number := basic_info.serialNumber + ) and "test" not in basic_info_serial_number.lower(): # prefix identifier with 'serial_' to be able to filter it - identifiers.add((DOMAIN, f"{ID_TYPE_SERIAL}_{basic_info.serialNumber}")) + identifiers.add((DOMAIN, f"{ID_TYPE_SERIAL}_{basic_info_serial_number}")) + serial_number = basic_info_serial_number model = ( get_clean_name(basic_info.productName) or device_type.__name__ @@ -203,6 +207,7 @@ class MatterAdapter: sw_version=basic_info.softwareVersionString, manufacturer=basic_info.vendorName or endpoint.node.device_info.vendorName, model=model, + serial_number=serial_number, via_device=(DOMAIN, bridge_device_id) if bridge_device_id else None, ) diff --git a/tests/components/matter/test_adapter.py b/tests/components/matter/test_adapter.py index 603e984779e..5f6c48dfcc6 100644 --- a/tests/components/matter/test_adapter.py +++ b/tests/components/matter/test_adapter.py @@ -56,6 +56,7 @@ async def test_device_registry_single_node_device( assert entry.model == "Mock Light" assert entry.hw_version == "v1.0" assert entry.sw_version == "v1.0" + assert entry.serial_number == "12345678" # This tests needs to be adjusted to remove lingering tasks @@ -84,6 +85,7 @@ async def test_device_registry_single_node_device_alt( # test serial id NOT present as additional identifier assert (DOMAIN, "serial_TEST_SN") not in entry.identifiers + assert entry.serial_number is None @pytest.mark.skip("Waiting for a new test fixture") From 83cf59e6a8ff9a7f6d5b1ef9e7c503f2b0f105ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 20 Mar 2024 15:40:46 +0100 Subject: [PATCH 1321/1691] Provide better debug capabilities for the Traccar Server integration (#113868) --- .../components/traccar_server/coordinator.py | 37 +++++++-- .../components/traccar_server/diagnostics.py | 20 ++++- .../snapshots/test_diagnostics.ambr | 80 +++++++++++++++++++ .../traccar_server/test_diagnostics.py | 41 +++++++++- 4 files changed, 167 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/traccar_server/coordinator.py b/homeassistant/components/traccar_server/coordinator.py index 8afa245f1e8..3d44b1ecede 100644 --- a/homeassistant/components/traccar_server/coordinator.py +++ b/homeassistant/components/traccar_server/coordinator.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from datetime import datetime +from logging import DEBUG as LOG_LEVEL_DEBUG from typing import TYPE_CHECKING, Any, TypedDict from pytraccar import ( @@ -92,8 +93,18 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat self._geofences = geofences + if self.logger.isEnabledFor(LOG_LEVEL_DEBUG): + self.logger.debug("Received devices: %s", devices) + self.logger.debug("Received positions: %s", positions) + for position in positions: - if (device := get_device(position["deviceId"], devices)) is None: + device_id = position["deviceId"] + if (device := get_device(device_id, devices)) is None: + self.logger.debug( + "Device %s not found for position: %s", + device_id, + position["id"], + ) continue if ( @@ -102,9 +113,14 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat device, position ) ) is None: + self.logger.debug( + "Skipping position update %s for %s due to accuracy filter", + position["id"], + device_id, + ) continue - data[device["id"]] = { + data[device_id] = { "device": device, "geofence": get_first_geofence( geofences, @@ -122,8 +138,8 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat self._should_log_subscription_error = True update_devices = set() for device in data.get("devices") or []: - device_id = device["id"] - if device_id not in self.data: + if (device_id := device["id"]) not in self.data: + self.logger.debug("Device %s not found in data", device_id) continue if ( @@ -139,8 +155,12 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat update_devices.add(device_id) for position in data.get("positions") or []: - device_id = position["deviceId"] - if device_id not in self.data: + if (device_id := position["deviceId"]) not in self.data: + self.logger.debug( + "Device %s for position %s not found in data", + device_id, + position["id"], + ) continue if ( @@ -149,6 +169,11 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat self.data[device_id]["device"], position ) ) is None: + self.logger.debug( + "Skipping position update %s for %s due to accuracy filter", + position["id"], + device_id, + ) continue self.data[device_id]["position"] = position diff --git a/homeassistant/components/traccar_server/diagnostics.py b/homeassistant/components/traccar_server/diagnostics.py index 8e9e6490e44..ea861a9bffa 100644 --- a/homeassistant/components/traccar_server/diagnostics.py +++ b/homeassistant/components/traccar_server/diagnostics.py @@ -21,6 +21,20 @@ TO_REDACT = { } +def _entity_state( + hass: HomeAssistant, + entity: er.RegistryEntry, +) -> dict[str, Any] | None: + return ( + { + "state": state.state, + "attributes": state.attributes, + } + if (state := hass.states.get(entity.entity_id)) + else None + ) + + async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry, @@ -43,10 +57,9 @@ async def async_get_config_entry_diagnostics( { "enity_id": entity.entity_id, "disabled": entity.disabled, - "state": {"state": state.state, "attributes": state.attributes}, + "state": _entity_state(hass, entity), } for entity in entities - if (state := hass.states.get(entity.entity_id)) is not None ], }, TO_REDACT, @@ -77,10 +90,9 @@ async def async_get_device_diagnostics( { "enity_id": entity.entity_id, "disabled": entity.disabled, - "state": {"state": state.state, "attributes": state.attributes}, + "state": _entity_state(hass, entity), } for entity in entities - if (state := hass.states.get(entity.entity_id)) is not None ], }, TO_REDACT, diff --git a/tests/components/traccar_server/snapshots/test_diagnostics.ambr b/tests/components/traccar_server/snapshots/test_diagnostics.ambr index 20d01e427ea..f8fe3cc60f7 100644 --- a/tests/components/traccar_server/snapshots/test_diagnostics.ambr +++ b/tests/components/traccar_server/snapshots/test_diagnostics.ambr @@ -99,6 +99,86 @@ 'subscription_status': 'disconnected', }) # --- +# name: test_device_diagnostics_with_disabled_entity[X-Wing] + dict({ + 'config_entry_options': dict({ + 'custom_attributes': list([ + 'custom_attr_1', + ]), + 'events': list([ + 'device_moving', + ]), + 'max_accuracy': 5.0, + 'skip_accuracy_filter_for': list([ + ]), + }), + 'coordinator_data': dict({ + '0': dict({ + 'attributes': dict({ + 'custom_attr_1': 'custom_attr_1_value', + }), + 'device': dict({ + 'attributes': dict({ + }), + 'category': 'starfighter', + 'contact': None, + 'disabled': False, + 'groupId': 0, + 'id': 0, + 'lastUpdate': '1970-01-01T00:00:00Z', + 'model': '1337', + 'name': 'X-Wing', + 'phone': None, + 'positionId': 0, + 'status': 'unknown', + 'uniqueId': 'abc123', + }), + 'geofence': dict({ + 'area': '**REDACTED**', + 'attributes': dict({ + }), + 'calendarId': 0, + 'description': "A harsh desert world orbiting twin suns in the galaxy's Outer Rim", + 'id': 0, + 'name': 'Tatooine', + }), + 'position': dict({ + 'accuracy': 3.5, + 'address': '**REDACTED**', + 'altitude': 546841384638, + 'attributes': dict({ + 'custom_attr_1': 'custom_attr_1_value', + }), + 'course': 360, + 'deviceId': 0, + 'deviceTime': '1970-01-01T00:00:00Z', + 'fixTime': '1970-01-01T00:00:00Z', + 'geofenceIds': list([ + 0, + ]), + 'id': 0, + 'latitude': '**REDACTED**', + 'longitude': '**REDACTED**', + 'network': dict({ + }), + 'outdated': True, + 'protocol': 'C-3PO', + 'serverTime': '1970-01-01T00:00:00Z', + 'speed': 4568795, + 'valid': True, + }), + }), + }), + 'entities': list([ + dict({ + 'disabled': True, + 'enity_id': 'device_tracker.x_wing', + 'state': None, + }), + ]), + 'subscription_status': 'disconnected', + }) +# --- # name: test_entry_diagnostics[entry] dict({ 'config_entry_options': dict({ diff --git a/tests/components/traccar_server/test_diagnostics.py b/tests/components/traccar_server/test_diagnostics.py index 38c35178b6f..3d112057315 100644 --- a/tests/components/traccar_server/test_diagnostics.py +++ b/tests/components/traccar_server/test_diagnostics.py @@ -6,7 +6,7 @@ from unittest.mock import AsyncMock from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import setup_integration @@ -63,3 +63,42 @@ async def test_device_diagnostics( ) assert result == snapshot(name=device.name) + + +async def test_device_diagnostics_with_disabled_entity( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_traccar_api_client: Generator[AsyncMock, None, None], + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test device diagnostics with disabled entity.""" + await setup_integration(hass, mock_config_entry) + + devices = dr.async_entries_for_config_entry( + hass.helpers.device_registry.async_get(hass), + mock_config_entry.entry_id, + ) + + assert len(devices) == 1 + + for device in dr.async_entries_for_config_entry( + hass.helpers.device_registry.async_get(hass), mock_config_entry.entry_id + ): + for entry in er.async_entries_for_device( + entity_registry, + device.id, + include_disabled_entities=True, + ): + entity_registry.async_update_entity( + entry.entity_id, + disabled_by=er.RegistryEntryDisabler.USER, + ) + + result = await get_diagnostics_for_device( + hass, hass_client, mock_config_entry, device=device + ) + + assert result == snapshot(name=device.name) From 0e7e1cb34f86d888f90af0278a1b6a862d51b2d3 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 20 Mar 2024 16:17:12 +0100 Subject: [PATCH 1322/1691] Remove obsolete issue strings in WAQI (#113884) --- homeassistant/components/waqi/strings.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/homeassistant/components/waqi/strings.json b/homeassistant/components/waqi/strings.json index de287318508..a1feb217249 100644 --- a/homeassistant/components/waqi/strings.json +++ b/homeassistant/components/waqi/strings.json @@ -36,24 +36,6 @@ } } }, - "issues": { - "deprecated_yaml_import_issue_invalid_auth": { - "title": "The World Air Quality Index YAML configuration import failed", - "description": "Configuring World Air Quality Index using YAML is being removed but there was an authentication error importing your YAML configuration.\n\nCorrect the YAML configuration and restart Home Assistant to try again or remove the World Air Quality Index YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." - }, - "deprecated_yaml_import_issue_cannot_connect": { - "title": "The WAQI YAML configuration import failed", - "description": "Configuring World Air Quality Index using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to WAQI works and restart Home Assistant to try again or remove the World Air Quality Index YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." - }, - "deprecated_yaml_import_issue_already_configured": { - "title": "The WAQI YAML configuration import failed", - "description": "Configuring World Air Quality Index using YAML is being removed but the measuring station was already imported when trying to import the YAML configuration.\n\nEnsure the imported configuration is correct and remove the World Air Quality Index YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." - }, - "deprecated_yaml_import_issue_none_found": { - "title": "The WAQI YAML configuration import failed", - "description": "Configuring World Air Quality Index using YAML is being removed but there weren't any stations imported because they couldn't be found.\n\nEnsure the imported configuration is correct and remove the World Air Quality Index YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." - } - }, "entity": { "sensor": { "carbon_monoxide": { From 3b0ac469d4af621e6a2765f8b8be5639894d52e8 Mon Sep 17 00:00:00 2001 From: Matrix Date: Wed, 20 Mar 2024 23:45:21 +0800 Subject: [PATCH 1323/1691] YoLink fix cover incorrect state (#104975) * Fix cover incorrect state * Change entity to unavailable --- homeassistant/components/yolink/cover.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/yolink/cover.py b/homeassistant/components/yolink/cover.py index 03e36a2ba29..b2454bd0d4a 100644 --- a/homeassistant/components/yolink/cover.py +++ b/homeassistant/components/yolink/cover.py @@ -60,11 +60,13 @@ class YoLinkCoverEntity(YoLinkEntity, CoverEntity): """Update HA Entity State.""" if (state_val := state.get("state")) is None: return - if self.coordinator.paired_device is None: + if self.coordinator.paired_device is None or state_val == "error": self._attr_is_closed = None + self._attr_available = False self.async_write_ha_state() elif state_val in ["open", "closed"]: self._attr_is_closed = state_val == "closed" + self._attr_available = True self.async_write_ha_state() async def toggle_garage_state(self) -> None: From bd2ee161f3b6117bc406038977c229d69c02afa9 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Wed, 20 Mar 2024 17:06:16 +0100 Subject: [PATCH 1324/1691] Bump aioshelly to 8.2.0 (#113886) aioshelly bump to 8.2.0 --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 2bf6b4f0b7d..06159cb543b 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_push", "loggers": ["aioshelly"], "quality_scale": "platinum", - "requirements": ["aioshelly==8.1.1"], + "requirements": ["aioshelly==8.2.0"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 8a0595fd479..7e2fac9f7f2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -365,7 +365,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==8.1.1 +aioshelly==8.2.0 # homeassistant.components.skybell aioskybell==22.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 32f3ea73a97..d12d721e9d5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -338,7 +338,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==8.1.1 +aioshelly==8.2.0 # homeassistant.components.skybell aioskybell==22.7.0 From 15ac81aae9a0a4a85a2ca0b9a1d9b02e87db11ef Mon Sep 17 00:00:00 2001 From: uchagani Date: Wed, 20 Mar 2024 12:06:57 -0400 Subject: [PATCH 1325/1691] Bump islamic_prayer_times to 0.0.12 (#113744) --- homeassistant/components/islamic_prayer_times/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/islamic_prayer_times/manifest.json b/homeassistant/components/islamic_prayer_times/manifest.json index 7d2dd178788..5f7e52dd3db 100644 --- a/homeassistant/components/islamic_prayer_times/manifest.json +++ b/homeassistant/components/islamic_prayer_times/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/islamic_prayer_times", "iot_class": "cloud_polling", "loggers": ["prayer_times_calculator"], - "requirements": ["prayer-times-calculator==0.0.10"] + "requirements": ["prayer-times-calculator==0.0.12"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7e2fac9f7f2..6ae4d9df333 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1553,7 +1553,7 @@ poolsense==0.0.8 praw==7.5.0 # homeassistant.components.islamic_prayer_times -prayer-times-calculator==0.0.10 +prayer-times-calculator==0.0.12 # homeassistant.components.proliphix proliphix==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d12d721e9d5..1f8e59bd81e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1221,7 +1221,7 @@ poolsense==0.0.8 praw==7.5.0 # homeassistant.components.islamic_prayer_times -prayer-times-calculator==0.0.10 +prayer-times-calculator==0.0.12 # homeassistant.components.prometheus prometheus-client==0.17.1 From dfbfdf781e12dc39e65c791994c5b53c9694a432 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:13:44 +0100 Subject: [PATCH 1326/1691] Bump home-assistant/builder from 2024.01.0 to 2024.03.5 (#113887) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 9e8e763b347..4718ebbc117 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -207,7 +207,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build base image - uses: home-assistant/builder@2024.01.0 + uses: home-assistant/builder@2024.03.5 with: args: | $BUILD_ARGS \ @@ -284,7 +284,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build base image - uses: home-assistant/builder@2024.01.0 + uses: home-assistant/builder@2024.03.5 with: args: | $BUILD_ARGS \ From e74791083e2448697f8fbd887d639e6e19dfbc2f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 09:14:17 -1000 Subject: [PATCH 1327/1691] Fix duplicate events in live history (#113896) --- homeassistant/components/history/websocket_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/history/websocket_api.py b/homeassistant/components/history/websocket_api.py index 6eb1fec3416..27e490dd8d4 100644 --- a/homeassistant/components/history/websocket_api.py +++ b/homeassistant/components/history/websocket_api.py @@ -5,7 +5,7 @@ from __future__ import annotations import asyncio from collections.abc import Callable, Iterable, MutableMapping from dataclasses import dataclass -from datetime import datetime as dt +from datetime import datetime as dt, timedelta import logging from typing import Any, cast @@ -564,7 +564,10 @@ async def ws_stream( hass, connection, msg_id, - last_event_time or start_time, + # Add one microsecond so we are outside the window of + # the last event we got from the database since otherwise + # we could fetch the same event twice + (last_event_time or start_time) + timedelta(microseconds=1), subscriptions_setup_complete_time, entity_ids, False, # We don't want the start time state again From 426f73b1f41228b842e63fb33f14b8d814d8a918 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 20 Mar 2024 21:05:07 +0100 Subject: [PATCH 1328/1691] Add State.last_reported (#113511) * Add State.last_reported * Update tests * Update test snapshots * Call state_reported listeners when firing state_changed event * Add tests --- .../components/group/reproduce_state.py | 1 + .../components/recorder/db_schema.py | 5 +- homeassistant/const.py | 1 + homeassistant/core.py | 79 +++++++-- homeassistant/helpers/template.py | 6 + .../advantage_air/snapshots/test_climate.ambr | 1 + .../advantage_air/snapshots/test_switch.ambr | 1 + .../airly/snapshots/test_sensor.ambr | 11 ++ .../snapshots/test_sensor.ambr | 4 + .../aosmith/snapshots/test_sensor.ambr | 2 + .../aosmith/snapshots/test_water_heater.ambr | 2 + .../snapshots/test_button.ambr | 19 ++ .../snapshots/test_number.ambr | 2 + .../snapshots/test_select.ambr | 5 + .../snapshots/test_sensor.ambr | 29 ++++ .../snapshots/test_switch.ambr | 4 + .../ccm15/snapshots/test_climate.ambr | 4 + .../co2signal/snapshots/test_sensor.ambr | 2 + .../snapshots/test_binary_sensor.ambr | 3 + .../snapshots/test_climate.ambr | 1 + .../snapshots/test_cover.ambr | 1 + .../snapshots/test_light.ambr | 2 + .../snapshots/test_sensor.ambr | 4 + .../snapshots/test_siren.ambr | 3 + .../snapshots/test_switch.ambr | 1 + .../snapshots/test_binary_sensor.ambr | 1 + .../snapshots/test_button.ambr | 4 + .../snapshots/test_device_tracker.ambr | 1 + .../snapshots/test_sensor.ambr | 5 + .../snapshots/test_switch.ambr | 2 + .../snapshots/test_update.ambr | 1 + .../discovergy/snapshots/test_sensor.ambr | 3 + .../snapshots/test_binary_sensor.ambr | 8 + .../ecovacs/snapshots/test_button.ambr | 4 + .../ecovacs/snapshots/test_number.ambr | 1 + .../ecovacs/snapshots/test_select.ambr | 1 + .../ecovacs/snapshots/test_sensor.ambr | 13 ++ .../ecovacs/snapshots/test_switch.ambr | 10 ++ .../elgato/snapshots/test_button.ambr | 2 + .../elgato/snapshots/test_light.ambr | 3 + .../elgato/snapshots/test_sensor.ambr | 5 + .../elgato/snapshots/test_switch.ambr | 2 + .../energyzero/snapshots/test_sensor.ambr | 6 + .../enphase_envoy/snapshots/test_sensor.ambr | 36 ++++ .../enphase_envoy/test_diagnostics.py | 1 + .../snapshots/test_binary_sensor.ambr | 1 + .../flexit_bacnet/snapshots/test_climate.ambr | 1 + .../flexit_bacnet/snapshots/test_number.ambr | 10 ++ .../flexit_bacnet/snapshots/test_sensor.ambr | 15 ++ .../flexit_bacnet/snapshots/test_switch.ambr | 2 + .../snapshots/test_binary_sensor.ambr | 2 + .../snapshots/test_button.ambr | 2 + .../snapshots/test_number.ambr | 13 ++ .../snapshots/test_sensor.ambr | 7 + .../snapshots/test_switch.ambr | 2 + .../glances/snapshots/test_sensor.ambr | 19 ++ .../gree/snapshots/test_climate.ambr | 1 + .../gree/snapshots/test_switch.ambr | 5 + tests/components/homekit/test_diagnostics.py | 1 + .../homekit_controller/test_diagnostics.py | 8 +- .../homekit_controller/test_init.py | 1 + .../homewizard/snapshots/test_button.ambr | 1 + .../homewizard/snapshots/test_number.ambr | 1 + .../homewizard/snapshots/test_sensor.ambr | 162 ++++++++++++++++++ .../homewizard/snapshots/test_switch.ambr | 7 + .../snapshots/test_binary_sensor.ambr | 3 + .../homeworks/snapshots/test_light.ambr | 2 + .../snapshots/test_binary_sensor.ambr | 3 + .../snapshots/test_device_tracker.ambr | 1 + .../snapshots/test_sensor.ambr | 11 ++ .../snapshots/test_switch.ambr | 1 + .../snapshots/test_lawn_mower.ambr | 5 + .../kitchen_sink/snapshots/test_lock.ambr | 4 + .../kitchen_sink/snapshots/test_sensor.ambr | 5 + .../kitchen_sink/snapshots/test_switch.ambr | 2 + .../snapshots/test_binary_sensor.ambr | 2 + .../lamarzocco/snapshots/test_button.ambr | 1 + .../lamarzocco/snapshots/test_calendar.ambr | 1 + .../lamarzocco/snapshots/test_number.ambr | 27 +++ .../lamarzocco/snapshots/test_select.ambr | 4 + .../lamarzocco/snapshots/test_sensor.ambr | 5 + .../lamarzocco/snapshots/test_switch.ambr | 3 + .../lamarzocco/snapshots/test_update.ambr | 2 + .../snapshots/test_sensor.ambr | 50 ++++++ .../lastfm/snapshots/test_sensor.ambr | 2 + tests/components/logbook/test_init.py | 15 +- .../melissa/snapshots/test_climate.ambr | 1 + .../snapshots/test_binary_sensor.ambr | 4 + .../snapshots/test_sensor.ambr | 30 ++++ tests/components/mqtt/test_diagnostics.py | 2 + .../components/mqtt_eventstream/test_init.py | 7 +- .../netatmo/snapshots/test_camera.ambr | 3 + .../netatmo/snapshots/test_climate.ambr | 5 + .../netatmo/snapshots/test_cover.ambr | 2 + .../netatmo/snapshots/test_fan.ambr | 1 + .../netatmo/snapshots/test_light.ambr | 3 + .../netatmo/snapshots/test_select.ambr | 1 + .../netatmo/snapshots/test_sensor.ambr | 81 +++++++++ .../netatmo/snapshots/test_switch.ambr | 1 + .../snapshots/test_binary_sensor.ambr | 3 + .../netgear_lte/snapshots/test_sensor.ambr | 13 ++ .../nibe_heatpump/snapshots/test_climate.ambr | 20 +++ .../snapshots/test_coordinator.ambr | 8 + .../nibe_heatpump/snapshots/test_number.ambr | 8 + .../onewire/snapshots/test_binary_sensor.ambr | 16 ++ .../onewire/snapshots/test_sensor.ambr | 52 ++++++ .../onewire/snapshots/test_switch.ambr | 37 ++++ .../opensky/snapshots/test_sensor.ambr | 2 + .../ping/snapshots/test_binary_sensor.ambr | 2 + .../ping/snapshots/test_sensor.ambr | 3 + .../components/proximity/test_diagnostics.py | 4 +- .../renault/snapshots/test_binary_sensor.ambr | 52 ++++++ .../renault/snapshots/test_button.ambr | 20 +++ .../snapshots/test_device_tracker.ambr | 6 + .../renault/snapshots/test_select.ambr | 6 + .../renault/snapshots/test_sensor.ambr | 104 +++++++++++ .../rfxtrx/snapshots/test_event.ambr | 3 + .../samsungtv/snapshots/test_init.ambr | 1 + .../sfr_box/snapshots/test_binary_sensor.ambr | 4 + .../sfr_box/snapshots/test_button.ambr | 1 + .../sfr_box/snapshots/test_sensor.ambr | 15 ++ .../snapshots/test_binary_sensor.ambr | 1 + .../snapshots/test_sensor.ambr | 3 + .../snapshots/test_binary_sensor.ambr | 2 + .../tailwind/snapshots/test_button.ambr | 1 + .../tailwind/snapshots/test_cover.ambr | 2 + .../tailwind/snapshots/test_number.ambr | 1 + .../snapshots/test_binary_sensor.ambr | 6 + .../technove/snapshots/test_sensor.ambr | 9 + .../technove/snapshots/test_switch.ambr | 1 + .../tedee/snapshots/test_binary_sensor.ambr | 3 + .../components/tedee/snapshots/test_lock.ambr | 2 + .../tedee/snapshots/test_sensor.ambr | 2 + .../snapshots/test_binary_sensor.ambr | 2 + .../template/snapshots/test_sensor.ambr | 2 + .../teslemetry/snapshots/test_climate.ambr | 1 + .../teslemetry/snapshots/test_sensor.ambr | 96 +++++++++++ .../tessie/snapshots/test_binary_sensors.ambr | 26 +++ .../tessie/snapshots/test_button.ambr | 6 + .../tessie/snapshots/test_climate.ambr | 1 + .../tessie/snapshots/test_cover.ambr | 4 + .../tessie/snapshots/test_device_tracker.ambr | 2 + .../tessie/snapshots/test_lock.ambr | 3 + .../tessie/snapshots/test_media_player.ambr | 2 + .../tessie/snapshots/test_number.ambr | 3 + .../tessie/snapshots/test_select.ambr | 6 + .../tessie/snapshots/test_sensor.ambr | 28 +++ .../tessie/snapshots/test_switch.ambr | 7 + .../tessie/snapshots/test_update.ambr | 1 + .../tplink_omada/snapshots/test_switch.ambr | 11 ++ .../snapshots/test_sensor.ambr | 13 ++ .../twentemilieu/snapshots/test_calendar.ambr | 1 + .../twentemilieu/snapshots/test_sensor.ambr | 5 + .../uptime/snapshots/test_sensor.ambr | 1 + .../components/valve/snapshots/test_init.ambr | 4 + .../vesync/snapshots/test_diagnostics.ambr | 3 + .../components/vesync/snapshots/test_fan.ambr | 4 + .../vesync/snapshots/test_light.ambr | 3 + .../vesync/snapshots/test_sensor.ambr | 15 ++ .../vesync/snapshots/test_switch.ambr | 2 + tests/components/vesync/test_diagnostics.py | 3 + .../vicare/snapshots/test_binary_sensor.ambr | 3 + .../waqi/snapshots/test_sensor.ambr | 12 ++ .../webmin/snapshots/test_sensor.ambr | 7 + .../whois/snapshots/test_sensor.ambr | 10 ++ .../withings/snapshots/test_sensor.ambr | 60 +++++++ .../wled/snapshots/test_binary_sensor.ambr | 1 + .../wled/snapshots/test_button.ambr | 1 + .../wled/snapshots/test_number.ambr | 2 + .../wled/snapshots/test_select.ambr | 4 + .../wled/snapshots/test_switch.ambr | 4 + .../youtube/snapshots/test_sensor.ambr | 4 + tests/syrupy.py | 1 + tests/test_core.py | 50 +++++- 174 files changed, 1645 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/group/reproduce_state.py b/homeassistant/components/group/reproduce_state.py index eb81ab258e9..06d4f95dee3 100644 --- a/homeassistant/components/group/reproduce_state.py +++ b/homeassistant/components/group/reproduce_state.py @@ -25,6 +25,7 @@ async def async_reproduce_states( state.state, state.attributes, last_changed=state.last_changed, + last_reported=state.last_reported, last_updated=state.last_updated, context=state.context, ) diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index cc1e0e1d5ec..61e39e40034 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -536,8 +536,9 @@ class States(Base): # Join the state_attributes table on attributes_id to get the attributes # for newer states attrs, - last_changed, - last_updated, + last_changed=last_changed, + last_reported=last_updated, # Recorder does not yet record last_reported + last_updated=last_updated, context=context, validate_entity_id=validate_entity_id, ) diff --git a/homeassistant/const.py b/homeassistant/const.py index ac934dcc0d6..5c2908dc515 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -306,6 +306,7 @@ EVENT_LOGGING_CHANGED: Final = "logging_changed" EVENT_SERVICE_REGISTERED: Final = "service_registered" EVENT_SERVICE_REMOVED: Final = "service_removed" EVENT_STATE_CHANGED: Final = "state_changed" +EVENT_STATE_REPORTED: Final = "state_reported" EVENT_THEMES_UPDATED: Final = "themes_updated" EVENT_PANELS_UPDATED: Final = "panels_updated" EVENT_LOVELACE_UPDATED: Final = "lovelace_updated" diff --git a/homeassistant/core.py b/homeassistant/core.py index 9e040b82b98..cdf5cbfe6a8 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -71,6 +71,7 @@ from .const import ( EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, + EVENT_STATE_REPORTED, MATCH_ALL, MAX_LENGTH_EVENT_EVENT_TYPE, MAX_LENGTH_STATE_STATE, @@ -175,6 +176,11 @@ TIMEOUT_EVENT_START = 15 MAX_EXPECTED_ENTITY_IDS = 16384 +EVENTS_EXCLUDED_FROM_MATCH_ALL = { + EVENT_HOMEASSISTANT_CLOSE, + EVENT_STATE_REPORTED, +} + _LOGGER = logging.getLogger(__name__) @@ -1327,6 +1333,10 @@ class _OneTimeListener: return f"<_OneTimeListener {self.listener_job.target}>" +# Empty list, used by EventBus._async_fire +EMPTY_LIST: list[Any] = [] + + class EventBus: """Allow the firing of and listening for events.""" @@ -1411,13 +1421,16 @@ class EventBus: "Bus:Handling %s", _event_repr(event_type, origin, event_data) ) - listeners = self._listeners.get(event_type) - # EVENT_HOMEASSISTANT_CLOSE should not be sent to MATCH_ALL listeners - if event_type != EVENT_HOMEASSISTANT_CLOSE: - if listeners: - listeners = self._match_all_listeners + listeners - else: - listeners = self._match_all_listeners.copy() + listeners = self._listeners.get(event_type, EMPTY_LIST) + if event_type not in EVENTS_EXCLUDED_FROM_MATCH_ALL: + match_all_listeners = self._match_all_listeners + else: + match_all_listeners = EMPTY_LIST + if event_type == EVENT_STATE_CHANGED: + aliased_listeners = self._listeners.get(EVENT_STATE_REPORTED, EMPTY_LIST) + else: + aliased_listeners = EMPTY_LIST + listeners = listeners + match_all_listeners + aliased_listeners if not listeners: return @@ -1605,6 +1618,7 @@ class State: state: the state of the entity attributes: extra information on entity and state last_changed: last time the state was changed. + last_reported: last time the state was reported. last_updated: last time the state or attributes were changed. context: Context in which it was created domain: Domain of this state. @@ -1617,6 +1631,7 @@ class State: state: str, attributes: Mapping[str, Any] | None = None, last_changed: datetime.datetime | None = None, + last_reported: datetime.datetime | None = None, last_updated: datetime.datetime | None = None, context: Context | None = None, validate_entity_id: bool | None = True, @@ -1642,7 +1657,8 @@ class State: self.attributes = ReadOnlyDict(attributes or {}) else: self.attributes = attributes - self.last_updated = last_updated or dt_util.utcnow() + self.last_reported = last_reported or dt_util.utcnow() + self.last_updated = last_updated or self.last_reported self.last_changed = last_changed or self.last_updated self.context = context or Context() self.state_info = state_info @@ -1655,16 +1671,21 @@ class State: "_", " " ) - @cached_property - def last_updated_timestamp(self) -> float: - """Timestamp of last update.""" - return self.last_updated.timestamp() - @cached_property def last_changed_timestamp(self) -> float: """Timestamp of last change.""" return self.last_changed.timestamp() + @cached_property + def last_reported_timestamp(self) -> float: + """Timestamp of last report.""" + return self.last_reported.timestamp() + + @cached_property + def last_updated_timestamp(self) -> float: + """Timestamp of last update.""" + return self.last_updated.timestamp() + @cached_property def _as_dict(self) -> dict[str, Any]: """Return a dict representation of the State. @@ -1677,11 +1698,16 @@ class State: last_updated_isoformat = last_changed_isoformat else: last_updated_isoformat = self.last_updated.isoformat() + if self.last_changed == self.last_reported: + last_reported_isoformat = last_changed_isoformat + else: + last_reported_isoformat = self.last_reported.isoformat() return { "entity_id": self.entity_id, "state": self.state, "attributes": self.attributes, "last_changed": last_changed_isoformat, + "last_reported": last_reported_isoformat, "last_updated": last_updated_isoformat, # _as_dict is marked as protected # to avoid callers outside of this module @@ -1776,15 +1802,17 @@ class State: return None last_changed = json_dict.get("last_changed") - if isinstance(last_changed, str): last_changed = dt_util.parse_datetime(last_changed) last_updated = json_dict.get("last_updated") - if isinstance(last_updated, str): last_updated = dt_util.parse_datetime(last_updated) + last_reported = json_dict.get("last_reported") + if isinstance(last_reported, str): + last_reported = dt_util.parse_datetime(last_reported) + if context := json_dict.get("context"): context = Context(id=context.get("id"), user_id=context.get("user_id")) @@ -1792,9 +1820,10 @@ class State: json_dict["entity_id"], json_dict["state"], json_dict.get("attributes"), - last_changed, - last_updated, - context, + last_changed=last_changed, + last_reported=last_reported, + last_updated=last_updated, + context=context, ) def expire(self) -> None: @@ -2103,6 +2132,19 @@ class StateMachine: now = dt_util.utc_from_timestamp(timestamp) if same_state and same_attr: + # mypy does not understand this is only possible if old_state is not None + old_last_reported = old_state.last_reported # type: ignore[union-attr] + old_state.last_reported = now # type: ignore[union-attr] + self._bus._async_fire( # pylint: disable=protected-access + EVENT_STATE_REPORTED, + { + "entity_id": entity_id, + "old_last_reported": old_last_reported, + "new_state": old_state, + }, + context=context, + time_fired=timestamp, + ) return if context is None: @@ -2123,6 +2165,7 @@ class StateMachine: attributes, last_changed, now, + now, context, old_state is None, state_info, diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index f7253097f57..36d481d9e29 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1042,6 +1042,12 @@ class TemplateStateBase(State): self._collect_state() return self._state.last_changed + @property + def last_reported(self) -> datetime: # type: ignore[override] + """Wrap State.last_reported.""" + self._collect_state() + return self._state.last_reported + @property def last_updated(self) -> datetime: # type: ignore[override] """Wrap State.last_updated.""" diff --git a/tests/components/advantage_air/snapshots/test_climate.ambr b/tests/components/advantage_air/snapshots/test_climate.ambr index 6b8c18e7c87..bd1fb431ae1 100644 --- a/tests/components/advantage_air/snapshots/test_climate.ambr +++ b/tests/components/advantage_air/snapshots/test_climate.ambr @@ -54,6 +54,7 @@ 'context': , 'entity_id': 'climate.myauto', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) diff --git a/tests/components/advantage_air/snapshots/test_switch.ambr b/tests/components/advantage_air/snapshots/test_switch.ambr index 2060c0798ed..cc050bb6a4d 100644 --- a/tests/components/advantage_air/snapshots/test_switch.ambr +++ b/tests/components/advantage_air/snapshots/test_switch.ambr @@ -27,6 +27,7 @@ 'context': , 'entity_id': 'switch.myzone_myfan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/airly/snapshots/test_sensor.ambr b/tests/components/airly/snapshots/test_sensor.ambr index 83b90cab3a6..23a4d13cd00 100644 --- a/tests/components/airly/snapshots/test_sensor.ambr +++ b/tests/components/airly/snapshots/test_sensor.ambr @@ -50,6 +50,7 @@ 'context': , 'entity_id': 'sensor.home_carbon_monoxide', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '162.49', }) @@ -103,6 +104,7 @@ 'context': , 'entity_id': 'sensor.home_common_air_quality_index', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '7.29', }) @@ -157,6 +159,7 @@ 'context': , 'entity_id': 'sensor.home_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '68.35', }) @@ -213,6 +216,7 @@ 'context': , 'entity_id': 'sensor.home_nitrogen_dioxide', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '16.04', }) @@ -269,6 +273,7 @@ 'context': , 'entity_id': 'sensor.home_ozone', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '41.52', }) @@ -323,6 +328,7 @@ 'context': , 'entity_id': 'sensor.home_pm1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.83', }) @@ -379,6 +385,7 @@ 'context': , 'entity_id': 'sensor.home_pm10', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.06', }) @@ -435,6 +442,7 @@ 'context': , 'entity_id': 'sensor.home_pm2_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4.37', }) @@ -489,6 +497,7 @@ 'context': , 'entity_id': 'sensor.home_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1019.86', }) @@ -545,6 +554,7 @@ 'context': , 'entity_id': 'sensor.home_sulphur_dioxide', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '13.97', }) @@ -599,6 +609,7 @@ 'context': , 'entity_id': 'sensor.home_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '14.37', }) diff --git a/tests/components/analytics_insights/snapshots/test_sensor.ambr b/tests/components/analytics_insights/snapshots/test_sensor.ambr index 80e53d18e98..d7eeed7955c 100644 --- a/tests/components/analytics_insights/snapshots/test_sensor.ambr +++ b/tests/components/analytics_insights/snapshots/test_sensor.ambr @@ -44,6 +44,7 @@ 'context': , 'entity_id': 'sensor.homeassistant_analytics_hacs_custom', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '157481', }) @@ -93,6 +94,7 @@ 'context': , 'entity_id': 'sensor.homeassistant_analytics_myq', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -142,6 +144,7 @@ 'context': , 'entity_id': 'sensor.homeassistant_analytics_spotify', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '24388', }) @@ -191,6 +194,7 @@ 'context': , 'entity_id': 'sensor.homeassistant_analytics_youtube', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '339', }) diff --git a/tests/components/aosmith/snapshots/test_sensor.ambr b/tests/components/aosmith/snapshots/test_sensor.ambr index 1b3043a7f3e..150e0c2934f 100644 --- a/tests/components/aosmith/snapshots/test_sensor.ambr +++ b/tests/components/aosmith/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.my_water_heater_energy_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '132.825', }) @@ -28,6 +29,7 @@ 'context': , 'entity_id': 'sensor.my_water_heater_hot_water_availability', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'low', }) diff --git a/tests/components/aosmith/snapshots/test_water_heater.ambr b/tests/components/aosmith/snapshots/test_water_heater.ambr index a4be3d107f3..c3740341c17 100644 --- a/tests/components/aosmith/snapshots/test_water_heater.ambr +++ b/tests/components/aosmith/snapshots/test_water_heater.ambr @@ -21,6 +21,7 @@ 'context': , 'entity_id': 'water_heater.my_water_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_pump', }) @@ -41,6 +42,7 @@ 'context': , 'entity_id': 'water_heater.my_water_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'electric', }) diff --git a/tests/components/bmw_connected_drive/snapshots/test_button.ambr b/tests/components/bmw_connected_drive/snapshots/test_button.ambr index 16d70c5f183..17866878ba3 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_button.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_button.ambr @@ -9,6 +9,7 @@ 'context': , 'entity_id': 'button.ix_xdrive50_flash_lights', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'button.ix_xdrive50_sound_horn', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -31,6 +33,7 @@ 'context': , 'entity_id': 'button.ix_xdrive50_activate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -42,6 +45,7 @@ 'context': , 'entity_id': 'button.ix_xdrive50_deactivate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -53,6 +57,7 @@ 'context': , 'entity_id': 'button.ix_xdrive50_find_vehicle', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -64,6 +69,7 @@ 'context': , 'entity_id': 'button.i4_edrive40_flash_lights', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -75,6 +81,7 @@ 'context': , 'entity_id': 'button.i4_edrive40_sound_horn', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -86,6 +93,7 @@ 'context': , 'entity_id': 'button.i4_edrive40_activate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -97,6 +105,7 @@ 'context': , 'entity_id': 'button.i4_edrive40_deactivate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -108,6 +117,7 @@ 'context': , 'entity_id': 'button.i4_edrive40_find_vehicle', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -119,6 +129,7 @@ 'context': , 'entity_id': 'button.m340i_xdrive_flash_lights', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -130,6 +141,7 @@ 'context': , 'entity_id': 'button.m340i_xdrive_sound_horn', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -141,6 +153,7 @@ 'context': , 'entity_id': 'button.m340i_xdrive_activate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -152,6 +165,7 @@ 'context': , 'entity_id': 'button.m340i_xdrive_deactivate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -163,6 +177,7 @@ 'context': , 'entity_id': 'button.m340i_xdrive_find_vehicle', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -174,6 +189,7 @@ 'context': , 'entity_id': 'button.i3_rex_flash_lights', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -185,6 +201,7 @@ 'context': , 'entity_id': 'button.i3_rex_sound_horn', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -196,6 +213,7 @@ 'context': , 'entity_id': 'button.i3_rex_activate_air_conditioning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -207,6 +225,7 @@ 'context': , 'entity_id': 'button.i3_rex_find_vehicle', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), diff --git a/tests/components/bmw_connected_drive/snapshots/test_number.ambr b/tests/components/bmw_connected_drive/snapshots/test_number.ambr index 1e478fafb09..93580ddc7b7 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_number.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_number.ambr @@ -14,6 +14,7 @@ 'context': , 'entity_id': 'number.ix_xdrive50_target_soc', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }), @@ -30,6 +31,7 @@ 'context': , 'entity_id': 'number.i4_edrive40_target_soc', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }), diff --git a/tests/components/bmw_connected_drive/snapshots/test_select.ambr b/tests/components/bmw_connected_drive/snapshots/test_select.ambr index a8b5e4ecca6..e72708345b1 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_select.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_select.ambr @@ -25,6 +25,7 @@ 'context': , 'entity_id': 'select.ix_xdrive50_ac_charging_limit', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '16', }), @@ -40,6 +41,7 @@ 'context': , 'entity_id': 'select.ix_xdrive50_charging_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'IMMEDIATE_CHARGING', }), @@ -67,6 +69,7 @@ 'context': , 'entity_id': 'select.i4_edrive40_ac_charging_limit', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '16', }), @@ -82,6 +85,7 @@ 'context': , 'entity_id': 'select.i4_edrive40_charging_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'IMMEDIATE_CHARGING', }), @@ -97,6 +101,7 @@ 'context': , 'entity_id': 'select.i3_rex_charging_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'DELAYED_CHARGING', }), diff --git a/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr b/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr index b20189f5403..e28b4485af0 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_remaining_range_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '340', }), @@ -24,6 +25,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1121', }), @@ -36,6 +38,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_charging_end_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-06-22T10:40:00+00:00', }), @@ -47,6 +50,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_charging_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'CHARGING', }), @@ -61,6 +65,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_remaining_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }), @@ -74,6 +79,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_remaining_range_electric', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '340', }), @@ -86,6 +92,7 @@ 'context': , 'entity_id': 'sensor.ix_xdrive50_charging_target', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }), @@ -99,6 +106,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_remaining_range_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '472', }), @@ -112,6 +120,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1121', }), @@ -124,6 +133,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_charging_end_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-06-22T10:40:00+00:00', }), @@ -135,6 +145,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_charging_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'NOT_CHARGING', }), @@ -149,6 +160,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_remaining_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }), @@ -162,6 +174,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_remaining_range_electric', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '472', }), @@ -174,6 +187,7 @@ 'context': , 'entity_id': 'sensor.i4_edrive40_charging_target', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }), @@ -187,6 +201,7 @@ 'context': , 'entity_id': 'sensor.m340i_xdrive_remaining_range_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '629', }), @@ -200,6 +215,7 @@ 'context': , 'entity_id': 'sensor.m340i_xdrive_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1121', }), @@ -213,6 +229,7 @@ 'context': , 'entity_id': 'sensor.m340i_xdrive_remaining_fuel', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40', }), @@ -226,6 +243,7 @@ 'context': , 'entity_id': 'sensor.m340i_xdrive_remaining_range_fuel', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '629', }), @@ -239,6 +257,7 @@ 'context': , 'entity_id': 'sensor.m340i_xdrive_remaining_fuel_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }), @@ -252,6 +271,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_remaining_range_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '279', }), @@ -265,6 +285,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '137009', }), @@ -277,6 +298,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_charging_end_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -288,6 +310,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_charging_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'WAITING_FOR_CHARGING', }), @@ -302,6 +325,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_remaining_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '82', }), @@ -315,6 +339,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_remaining_range_electric', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '174', }), @@ -327,6 +352,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_charging_target', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }), @@ -340,6 +366,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_remaining_fuel', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6', }), @@ -353,6 +380,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_remaining_range_fuel', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '105', }), @@ -366,6 +394,7 @@ 'context': , 'entity_id': 'sensor.i3_rex_remaining_fuel_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), diff --git a/tests/components/bmw_connected_drive/snapshots/test_switch.ambr b/tests/components/bmw_connected_drive/snapshots/test_switch.ambr index 009ff8306f0..a3c8ffb6d3b 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_switch.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_switch.ambr @@ -9,6 +9,7 @@ 'context': , 'entity_id': 'switch.ix_xdrive50_climate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'switch.ix_xdrive50_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -31,6 +33,7 @@ 'context': , 'entity_id': 'switch.i4_edrive40_climate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -42,6 +45,7 @@ 'context': , 'entity_id': 'switch.m340i_xdrive_climate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), diff --git a/tests/components/ccm15/snapshots/test_climate.ambr b/tests/components/ccm15/snapshots/test_climate.ambr index 63f6612784b..10423919187 100644 --- a/tests/components/ccm15/snapshots/test_climate.ambr +++ b/tests/components/ccm15/snapshots/test_climate.ambr @@ -141,6 +141,7 @@ 'context': , 'entity_id': 'climate.midea_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -179,6 +180,7 @@ 'context': , 'entity_id': 'climate.midea_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'cool', }) @@ -320,6 +322,7 @@ 'context': , 'entity_id': 'climate.midea_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -353,6 +356,7 @@ 'context': , 'entity_id': 'climate.midea_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) diff --git a/tests/components/co2signal/snapshots/test_sensor.ambr b/tests/components/co2signal/snapshots/test_sensor.ambr index 0d0ae2cedb5..3702521e4c3 100644 --- a/tests/components/co2signal/snapshots/test_sensor.ambr +++ b/tests/components/co2signal/snapshots/test_sensor.ambr @@ -46,6 +46,7 @@ 'context': , 'entity_id': 'sensor.electricity_maps_co2_intensity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '45.9862319009581', }) @@ -97,6 +98,7 @@ 'context': , 'entity_id': 'sensor.electricity_maps_grid_fossil_fuel_percentage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5.4611827419371', }) diff --git a/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr b/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr index 851c1da4c62..0980a550c7b 100644 --- a/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr +++ b/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.test_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -54,6 +55,7 @@ 'context': , 'entity_id': 'binary_sensor.test_overload', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -99,6 +101,7 @@ 'context': , 'entity_id': 'binary_sensor.test_button_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/devolo_home_control/snapshots/test_climate.ambr b/tests/components/devolo_home_control/snapshots/test_climate.ambr index bb6bd7d0f40..be7d6f78142 100644 --- a/tests/components/devolo_home_control/snapshots/test_climate.ambr +++ b/tests/components/devolo_home_control/snapshots/test_climate.ambr @@ -16,6 +16,7 @@ 'context': , 'entity_id': 'climate.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat', }) diff --git a/tests/components/devolo_home_control/snapshots/test_cover.ambr b/tests/components/devolo_home_control/snapshots/test_cover.ambr index 34a149c10c2..7d88d42d5c2 100644 --- a/tests/components/devolo_home_control/snapshots/test_cover.ambr +++ b/tests/components/devolo_home_control/snapshots/test_cover.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'cover.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'open', }) diff --git a/tests/components/devolo_home_control/snapshots/test_light.ambr b/tests/components/devolo_home_control/snapshots/test_light.ambr index 7b156dd894b..959656b52a4 100644 --- a/tests/components/devolo_home_control/snapshots/test_light.ambr +++ b/tests/components/devolo_home_control/snapshots/test_light.ambr @@ -13,6 +13,7 @@ 'context': , 'entity_id': 'light.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -68,6 +69,7 @@ 'context': , 'entity_id': 'light.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/devolo_home_control/snapshots/test_sensor.ambr b/tests/components/devolo_home_control/snapshots/test_sensor.ambr index 1f83877f14b..7f67c70f6ac 100644 --- a/tests/components/devolo_home_control/snapshots/test_sensor.ambr +++ b/tests/components/devolo_home_control/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.test_battery_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25', }) @@ -60,6 +61,7 @@ 'context': , 'entity_id': 'sensor.test_current_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -110,6 +112,7 @@ 'context': , 'entity_id': 'sensor.test_total_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -160,6 +163,7 @@ 'context': , 'entity_id': 'sensor.test_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20', }) diff --git a/tests/components/devolo_home_control/snapshots/test_siren.ambr b/tests/components/devolo_home_control/snapshots/test_siren.ambr index 82422e7f37f..5c94674998c 100644 --- a/tests/components/devolo_home_control/snapshots/test_siren.ambr +++ b/tests/components/devolo_home_control/snapshots/test_siren.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'siren.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -64,6 +65,7 @@ 'context': , 'entity_id': 'siren.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -117,6 +119,7 @@ 'context': , 'entity_id': 'siren.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/devolo_home_control/snapshots/test_switch.ambr b/tests/components/devolo_home_control/snapshots/test_switch.ambr index e756fc26c9a..3e2f6f705d3 100644 --- a/tests/components/devolo_home_control/snapshots/test_switch.ambr +++ b/tests/components/devolo_home_control/snapshots/test_switch.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'switch.test', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/devolo_home_network/snapshots/test_binary_sensor.ambr b/tests/components/devolo_home_network/snapshots/test_binary_sensor.ambr index 7545fff55f1..c0df0d5d5a5 100644 --- a/tests/components/devolo_home_network/snapshots/test_binary_sensor.ambr +++ b/tests/components/devolo_home_network/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.mock_title_connected_to_router', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/devolo_home_network/snapshots/test_button.ambr b/tests/components/devolo_home_network/snapshots/test_button.ambr index 26be12805ce..3e8e4ae2bb3 100644 --- a/tests/components/devolo_home_network/snapshots/test_button.ambr +++ b/tests/components/devolo_home_network/snapshots/test_button.ambr @@ -51,6 +51,7 @@ 'context': , 'entity_id': 'button.mock_title_identify_device_with_a_blinking_led', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -140,6 +141,7 @@ 'context': , 'entity_id': 'button.mock_title_restart_device', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -228,6 +230,7 @@ 'context': , 'entity_id': 'button.mock_title_start_plc_pairing', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -316,6 +319,7 @@ 'context': , 'entity_id': 'button.mock_title_start_wps', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/devolo_home_network/snapshots/test_device_tracker.ambr b/tests/components/devolo_home_network/snapshots/test_device_tracker.ambr index d438aca6a4a..9df6b168f9f 100644 --- a/tests/components/devolo_home_network/snapshots/test_device_tracker.ambr +++ b/tests/components/devolo_home_network/snapshots/test_device_tracker.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'device_tracker.devolo_home_network_1234567890_aa_bb_cc_dd_ee_ff', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'home', }) diff --git a/tests/components/devolo_home_network/snapshots/test_sensor.ambr b/tests/components/devolo_home_network/snapshots/test_sensor.ambr index f961104a14f..fc173da8294 100644 --- a/tests/components/devolo_home_network/snapshots/test_sensor.ambr +++ b/tests/components/devolo_home_network/snapshots/test_sensor.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'sensor.mock_title_connected_plc_devices', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -53,6 +54,7 @@ 'context': , 'entity_id': 'sensor.mock_title_connected_wifi_clients', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -100,6 +102,7 @@ 'context': , 'entity_id': 'sensor.mock_title_neighboring_wifi_networks', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -147,6 +150,7 @@ 'context': , 'entity_id': 'sensor.mock_title_plc_downlink_phy_rate_test2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100.0', }) @@ -197,6 +201,7 @@ 'context': , 'entity_id': 'sensor.mock_title_plc_downlink_phy_rate_test2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100.0', }) diff --git a/tests/components/devolo_home_network/snapshots/test_switch.ambr b/tests/components/devolo_home_network/snapshots/test_switch.ambr index 760e240fee3..09b56efc784 100644 --- a/tests/components/devolo_home_network/snapshots/test_switch.ambr +++ b/tests/components/devolo_home_network/snapshots/test_switch.ambr @@ -93,6 +93,7 @@ 'context': , 'entity_id': 'switch.mock_title_enable_guest_wifi', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -138,6 +139,7 @@ 'context': , 'entity_id': 'switch.mock_title_enable_leds', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/devolo_home_network/snapshots/test_update.ambr b/tests/components/devolo_home_network/snapshots/test_update.ambr index 106b8f9885c..83ca84c82e8 100644 --- a/tests/components/devolo_home_network/snapshots/test_update.ambr +++ b/tests/components/devolo_home_network/snapshots/test_update.ambr @@ -18,6 +18,7 @@ 'context': , 'entity_id': 'update.mock_title_firmware', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/discovergy/snapshots/test_sensor.ambr b/tests/components/discovergy/snapshots/test_sensor.ambr index a107c44840d..b4831d81bda 100644 --- a/tests/components/discovergy/snapshots/test_sensor.ambr +++ b/tests/components/discovergy/snapshots/test_sensor.ambr @@ -84,6 +84,7 @@ 'context': , 'entity_id': 'sensor.electricity_teststrasse_1_total_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '11934.8699715', }) @@ -137,6 +138,7 @@ 'context': , 'entity_id': 'sensor.electricity_teststrasse_1_total_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -226,6 +228,7 @@ 'context': , 'entity_id': 'sensor.gas_teststrasse_1_total_gas_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '21064.8', }) diff --git a/tests/components/drop_connect/snapshots/test_binary_sensor.ambr b/tests/components/drop_connect/snapshots/test_binary_sensor.ambr index 8f39e76538c..c42cdb8cde1 100644 --- a/tests/components/drop_connect/snapshots/test_binary_sensor.ambr +++ b/tests/components/drop_connect/snapshots/test_binary_sensor.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'binary_sensor.hub_drop_1_c0ffee_leak_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -86,6 +87,7 @@ 'context': , 'entity_id': 'binary_sensor.hub_drop_1_c0ffee_notification_unread', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -132,6 +134,7 @@ 'context': , 'entity_id': 'binary_sensor.leak_detector_leak_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -178,6 +181,7 @@ 'context': , 'entity_id': 'binary_sensor.protection_valve_leak_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -224,6 +228,7 @@ 'context': , 'entity_id': 'binary_sensor.pump_controller_leak_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -269,6 +274,7 @@ 'context': , 'entity_id': 'binary_sensor.pump_controller_pump_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -315,6 +321,7 @@ 'context': , 'entity_id': 'binary_sensor.ro_filter_leak_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -360,6 +367,7 @@ 'context': , 'entity_id': 'binary_sensor.softener_reserve_capacity_in_use', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/ecovacs/snapshots/test_button.ambr b/tests/components/ecovacs/snapshots/test_button.ambr index ce28500cb52..816551f7e6a 100644 --- a/tests/components/ecovacs/snapshots/test_button.ambr +++ b/tests/components/ecovacs/snapshots/test_button.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'button.ozmo_950_relocate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2024-01-01T00:00:00+00:00', }) @@ -85,6 +86,7 @@ 'context': , 'entity_id': 'button.ozmo_950_reset_filter_lifespan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2024-01-01T00:00:00+00:00', }) @@ -130,6 +132,7 @@ 'context': , 'entity_id': 'button.ozmo_950_reset_main_brush_lifespan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2024-01-01T00:00:00+00:00', }) @@ -175,6 +178,7 @@ 'context': , 'entity_id': 'button.ozmo_950_reset_side_brushes_lifespan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2024-01-01T00:00:00+00:00', }) diff --git a/tests/components/ecovacs/snapshots/test_number.ambr b/tests/components/ecovacs/snapshots/test_number.ambr index be4234b86ec..da8406491b4 100644 --- a/tests/components/ecovacs/snapshots/test_number.ambr +++ b/tests/components/ecovacs/snapshots/test_number.ambr @@ -49,6 +49,7 @@ 'context': , 'entity_id': 'number.ozmo_950_volume', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) diff --git a/tests/components/ecovacs/snapshots/test_select.ambr b/tests/components/ecovacs/snapshots/test_select.ambr index c9fb352da27..125e7f0cee8 100644 --- a/tests/components/ecovacs/snapshots/test_select.ambr +++ b/tests/components/ecovacs/snapshots/test_select.ambr @@ -53,6 +53,7 @@ 'context': , 'entity_id': 'select.ozmo_950_water_flow_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'ultrahigh', }) diff --git a/tests/components/ecovacs/snapshots/test_sensor.ambr b/tests/components/ecovacs/snapshots/test_sensor.ambr index 58961db3c25..b35310158f2 100644 --- a/tests/components/ecovacs/snapshots/test_sensor.ambr +++ b/tests/components/ecovacs/snapshots/test_sensor.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_area_cleaned', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -88,6 +89,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -138,6 +140,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_cleaning_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5.0', }) @@ -184,6 +187,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_error', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -230,6 +234,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_filter_lifespan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '56', }) @@ -275,6 +280,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_ip_address', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '192.168.0.10', }) @@ -321,6 +327,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_main_brush_lifespan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }) @@ -367,6 +374,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_side_brushes_lifespan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40', }) @@ -416,6 +424,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_total_area_cleaned', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }) @@ -469,6 +478,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_total_cleaning_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40.000', }) @@ -517,6 +527,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_total_cleanings', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123', }) @@ -562,6 +573,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_wi_fi_rssi', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-62', }) @@ -607,6 +619,7 @@ 'context': , 'entity_id': 'sensor.ozmo_950_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Testnetwork', }) diff --git a/tests/components/ecovacs/snapshots/test_switch.ambr b/tests/components/ecovacs/snapshots/test_switch.ambr index ecb5bc0dbe5..59e891bea5e 100644 --- a/tests/components/ecovacs/snapshots/test_switch.ambr +++ b/tests/components/ecovacs/snapshots/test_switch.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'switch.goat_g1_advanced_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -85,6 +86,7 @@ 'context': , 'entity_id': 'switch.goat_g1_border_switch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -130,6 +132,7 @@ 'context': , 'entity_id': 'switch.goat_g1_child_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -175,6 +178,7 @@ 'context': , 'entity_id': 'switch.goat_g1_cross_map_border_warning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -220,6 +224,7 @@ 'context': , 'entity_id': 'switch.goat_g1_move_up_warning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -265,6 +270,7 @@ 'context': , 'entity_id': 'switch.goat_g1_safe_protect', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -310,6 +316,7 @@ 'context': , 'entity_id': 'switch.goat_g1_true_detect', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -355,6 +362,7 @@ 'context': , 'entity_id': 'switch.ozmo_950_advanced_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -400,6 +408,7 @@ 'context': , 'entity_id': 'switch.ozmo_950_carpet_auto_boost_suction', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -445,6 +454,7 @@ 'context': , 'entity_id': 'switch.ozmo_950_continuous_cleaning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/elgato/snapshots/test_button.ambr b/tests/components/elgato/snapshots/test_button.ambr index 36fcfb11801..e7477540f46 100644 --- a/tests/components/elgato/snapshots/test_button.ambr +++ b/tests/components/elgato/snapshots/test_button.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'button.frenck_identify', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -88,6 +89,7 @@ 'context': , 'entity_id': 'button.frenck_restart', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/elgato/snapshots/test_light.ambr b/tests/components/elgato/snapshots/test_light.ambr index a65f5c46b94..6ef773a7304 100644 --- a/tests/components/elgato/snapshots/test_light.ambr +++ b/tests/components/elgato/snapshots/test_light.ambr @@ -32,6 +32,7 @@ 'context': , 'entity_id': 'light.frenck', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -145,6 +146,7 @@ 'context': , 'entity_id': 'light.frenck', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -259,6 +261,7 @@ 'context': , 'entity_id': 'light.frenck', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/elgato/snapshots/test_sensor.ambr b/tests/components/elgato/snapshots/test_sensor.ambr index 0c6e593a2bc..2b52d6b9f23 100644 --- a/tests/components/elgato/snapshots/test_sensor.ambr +++ b/tests/components/elgato/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.frenck_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '78.57', }) @@ -97,6 +98,7 @@ 'context': , 'entity_id': 'sensor.frenck_battery_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.86', }) @@ -187,6 +189,7 @@ 'context': , 'entity_id': 'sensor.frenck_charging_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.008', }) @@ -277,6 +280,7 @@ 'context': , 'entity_id': 'sensor.frenck_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12.66', }) @@ -364,6 +368,7 @@ 'context': , 'entity_id': 'sensor.frenck_charging_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4.208', }) diff --git a/tests/components/elgato/snapshots/test_switch.ambr b/tests/components/elgato/snapshots/test_switch.ambr index 3b31b7bca14..41f3a8f3aaf 100644 --- a/tests/components/elgato/snapshots/test_switch.ambr +++ b/tests/components/elgato/snapshots/test_switch.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'switch.frenck_energy_saving', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -86,6 +87,7 @@ 'context': , 'entity_id': 'switch.frenck_studio_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/energyzero/snapshots/test_sensor.ambr b/tests/components/energyzero/snapshots/test_sensor.ambr index 84a027b89f1..5ffa623fd87 100644 --- a/tests/components/energyzero/snapshots/test_sensor.ambr +++ b/tests/components/energyzero/snapshots/test_sensor.ambr @@ -466,6 +466,7 @@ 'context': , 'entity_id': 'sensor.energyzero_today_energy_average_price', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.37', }) @@ -537,6 +538,7 @@ 'context': , 'entity_id': 'sensor.energyzero_today_energy_current_hour_price', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.49', }) @@ -609,6 +611,7 @@ 'context': , 'entity_id': 'sensor.energyzero_today_energy_highest_price_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2022-12-07T16:00:00+00:00', }) @@ -679,6 +682,7 @@ 'context': , 'entity_id': 'sensor.energyzero_today_energy_hours_priced_equal_or_lower', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '23', }) @@ -749,6 +753,7 @@ 'context': , 'entity_id': 'sensor.energyzero_today_energy_max_price', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.55', }) @@ -820,6 +825,7 @@ 'context': , 'entity_id': 'sensor.energyzero_today_gas_current_hour_price', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.47', }) diff --git a/tests/components/enphase_envoy/snapshots/test_sensor.ambr b/tests/components/enphase_envoy/snapshots/test_sensor.ambr index c3fa2bf23f5..2dde7508ee4 100644 --- a/tests/components/enphase_envoy/snapshots/test_sensor.ambr +++ b/tests/components/enphase_envoy/snapshots/test_sensor.ambr @@ -2572,6 +2572,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_net_power_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.101', }) @@ -2597,6 +2598,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2613,6 +2615,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_consumption_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.324', }) @@ -2629,6 +2632,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_consumption_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.324', }) @@ -2645,6 +2649,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_consumption_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.324', }) @@ -2661,6 +2666,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_production', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2677,6 +2683,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_production_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2693,6 +2700,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_production_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.234', }) @@ -2709,6 +2717,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_current_power_production_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.234', }) @@ -2724,6 +2733,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_last_seven_days', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2739,6 +2749,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_last_seven_days_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.321', }) @@ -2754,6 +2765,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_last_seven_days_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.321', }) @@ -2769,6 +2781,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_last_seven_days_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.321', }) @@ -2785,6 +2798,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2801,6 +2815,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_today_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.323', }) @@ -2817,6 +2832,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_today_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.323', }) @@ -2833,6 +2849,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_consumption_today_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.323', }) @@ -2848,6 +2865,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_last_seven_days', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2863,6 +2881,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_last_seven_days_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.231', }) @@ -2878,6 +2897,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_last_seven_days_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.231', }) @@ -2893,6 +2913,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_last_seven_days_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.231', }) @@ -2909,6 +2930,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.234', }) @@ -2925,6 +2947,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_today_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.233', }) @@ -2941,6 +2964,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_today_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.233', }) @@ -2957,6 +2981,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_energy_production_today_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.233', }) @@ -2985,6 +3010,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.001234', }) @@ -3001,6 +3027,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_consumption_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.001322', }) @@ -3017,6 +3044,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_consumption_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.002322', }) @@ -3033,6 +3061,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_consumption_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.003322', }) @@ -3049,6 +3078,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_production', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.001234', }) @@ -3065,6 +3095,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_production_l1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.001232', }) @@ -3081,6 +3112,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_production_l2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.002232', }) @@ -3097,6 +3129,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_energy_production_l3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.003232', }) @@ -3113,6 +3146,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_net_energy_consumption', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.021234', }) @@ -3138,6 +3172,7 @@ 'context': , 'entity_id': 'sensor.envoy_1234_lifetime_net_energy_production', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.022345', }) @@ -3226,6 +3261,7 @@ 'context': , 'entity_id': 'sensor.inverter_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) diff --git a/tests/components/enphase_envoy/test_diagnostics.py b/tests/components/enphase_envoy/test_diagnostics.py index f1e601a48d7..a3b4f8e0f3c 100644 --- a/tests/components/enphase_envoy/test_diagnostics.py +++ b/tests/components/enphase_envoy/test_diagnostics.py @@ -15,6 +15,7 @@ TO_EXCLUDE = { "via_device_id", "last_updated", "last_changed", + "last_reported", } diff --git a/tests/components/flexit_bacnet/snapshots/test_binary_sensor.ambr b/tests/components/flexit_bacnet/snapshots/test_binary_sensor.ambr index 6d3ef4c1940..f983d834927 100644 --- a/tests/components/flexit_bacnet/snapshots/test_binary_sensor.ambr +++ b/tests/components/flexit_bacnet/snapshots/test_binary_sensor.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'binary_sensor.device_name_air_filter_polluted', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/flexit_bacnet/snapshots/test_climate.ambr b/tests/components/flexit_bacnet/snapshots/test_climate.ambr index 6d5ef2251b8..551c5363e98 100644 --- a/tests/components/flexit_bacnet/snapshots/test_climate.ambr +++ b/tests/components/flexit_bacnet/snapshots/test_climate.ambr @@ -24,6 +24,7 @@ 'context': , 'entity_id': 'climate.device_name', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'fan_only', }) diff --git a/tests/components/flexit_bacnet/snapshots/test_number.ambr b/tests/components/flexit_bacnet/snapshots/test_number.ambr index 1318166fb87..008046bf512 100644 --- a/tests/components/flexit_bacnet/snapshots/test_number.ambr +++ b/tests/components/flexit_bacnet/snapshots/test_number.ambr @@ -51,6 +51,7 @@ 'context': , 'entity_id': 'number.device_name_away_extract_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30', }) @@ -107,6 +108,7 @@ 'context': , 'entity_id': 'number.device_name_away_supply_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40', }) @@ -163,6 +165,7 @@ 'context': , 'entity_id': 'number.device_name_cooker_hood_extract_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '90', }) @@ -219,6 +222,7 @@ 'context': , 'entity_id': 'number.device_name_cooker_hood_supply_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -275,6 +279,7 @@ 'context': , 'entity_id': 'number.device_name_fireplace_extract_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -331,6 +336,7 @@ 'context': , 'entity_id': 'number.device_name_fireplace_supply_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20', }) @@ -387,6 +393,7 @@ 'context': , 'entity_id': 'number.device_name_high_extract_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }) @@ -443,6 +450,7 @@ 'context': , 'entity_id': 'number.device_name_high_supply_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }) @@ -499,6 +507,7 @@ 'context': , 'entity_id': 'number.device_name_home_extract_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }) @@ -555,6 +564,7 @@ 'context': , 'entity_id': 'number.device_name_home_supply_fan_setpoint', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }) diff --git a/tests/components/flexit_bacnet/snapshots/test_sensor.ambr b/tests/components/flexit_bacnet/snapshots/test_sensor.ambr index 55d1c525be6..2c65bd53a6e 100644 --- a/tests/components/flexit_bacnet/snapshots/test_sensor.ambr +++ b/tests/components/flexit_bacnet/snapshots/test_sensor.ambr @@ -47,6 +47,7 @@ 'context': , 'entity_id': 'sensor.device_name_air_filter_operating_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8820.0', }) @@ -97,6 +98,7 @@ 'context': , 'entity_id': 'sensor.device_name_electric_heater_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.396365851163864', }) @@ -146,6 +148,7 @@ 'context': , 'entity_id': 'sensor.device_name_exhaust_air_fan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2606', }) @@ -195,6 +198,7 @@ 'context': , 'entity_id': 'sensor.device_name_exhaust_air_fan_control_signal', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }) @@ -242,6 +246,7 @@ 'context': , 'entity_id': 'sensor.device_name_exhaust_air_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-3.3', }) @@ -289,6 +294,7 @@ 'context': , 'entity_id': 'sensor.device_name_extract_air_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '19.0', }) @@ -342,6 +348,7 @@ 'context': , 'entity_id': 'sensor.device_name_fireplace_ventilation_remaining_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -391,6 +398,7 @@ 'context': , 'entity_id': 'sensor.device_name_heat_exchanger_efficiency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '81', }) @@ -440,6 +448,7 @@ 'context': , 'entity_id': 'sensor.device_name_heat_exchanger_speed', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -487,6 +496,7 @@ 'context': , 'entity_id': 'sensor.device_name_outside_air_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-8.6', }) @@ -540,6 +550,7 @@ 'context': , 'entity_id': 'sensor.device_name_rapid_ventilation_remaining_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.0', }) @@ -587,6 +598,7 @@ 'context': , 'entity_id': 'sensor.device_name_room_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '19.0', }) @@ -636,6 +648,7 @@ 'context': , 'entity_id': 'sensor.device_name_supply_air_fan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2784', }) @@ -685,6 +698,7 @@ 'context': , 'entity_id': 'sensor.device_name_supply_air_fan_control_signal', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '74', }) @@ -732,6 +746,7 @@ 'context': , 'entity_id': 'sensor.device_name_supply_air_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '19.1', }) diff --git a/tests/components/flexit_bacnet/snapshots/test_switch.ambr b/tests/components/flexit_bacnet/snapshots/test_switch.ambr index 5a7350f9728..d054608f1f7 100644 --- a/tests/components/flexit_bacnet/snapshots/test_switch.ambr +++ b/tests/components/flexit_bacnet/snapshots/test_switch.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'switch.device_name_electric_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -54,6 +55,7 @@ 'context': , 'entity_id': 'switch.device_name_electric_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/gardena_bluetooth/snapshots/test_binary_sensor.ambr b/tests/components/gardena_bluetooth/snapshots/test_binary_sensor.ambr index 8a2600dcbb1..0ce39de5894 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_binary_sensor.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.mock_title_valve_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -21,6 +22,7 @@ 'context': , 'entity_id': 'binary_sensor.mock_title_valve_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/gardena_bluetooth/snapshots/test_button.ambr b/tests/components/gardena_bluetooth/snapshots/test_button.ambr index b9cdca0e03c..c1ac96f0809 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_button.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_button.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'button.mock_title_factory_reset', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -19,6 +20,7 @@ 'context': , 'entity_id': 'button.mock_title_factory_reset', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/gardena_bluetooth/snapshots/test_number.ambr b/tests/components/gardena_bluetooth/snapshots/test_number.ambr index 0b39525dc82..c89ead450d2 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_number.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_number.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'number.mock_title_remaining_open_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -29,6 +30,7 @@ 'context': , 'entity_id': 'number.mock_title_manual_watering_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -46,6 +48,7 @@ 'context': , 'entity_id': 'number.mock_title_remaining_open_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -63,6 +66,7 @@ 'context': , 'entity_id': 'number.mock_title_manual_watering_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -80,6 +84,7 @@ 'context': , 'entity_id': 'number.mock_title_sensor_threshold', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -97,6 +102,7 @@ 'context': , 'entity_id': 'number.mock_title_sensor_threshold', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '45.0', }) @@ -114,6 +120,7 @@ 'context': , 'entity_id': 'number.mock_title_remaining_open_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100.0', }) @@ -131,6 +138,7 @@ 'context': , 'entity_id': 'number.mock_title_remaining_open_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -148,6 +156,7 @@ 'context': , 'entity_id': 'number.mock_title_remaining_open_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -165,6 +174,7 @@ 'context': , 'entity_id': 'number.mock_title_remaining_open_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -182,6 +192,7 @@ 'context': , 'entity_id': 'number.mock_title_open_for', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -199,6 +210,7 @@ 'context': , 'entity_id': 'number.mock_title_manual_watering_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100.0', }) @@ -216,6 +228,7 @@ 'context': , 'entity_id': 'number.mock_title_manual_watering_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) diff --git a/tests/components/gardena_bluetooth/snapshots/test_sensor.ambr b/tests/components/gardena_bluetooth/snapshots/test_sensor.ambr index 1c33e8ebab9..3739fc19fd1 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_sensor.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.mock_title_sensor_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -25,6 +26,7 @@ 'context': , 'entity_id': 'sensor.mock_title_sensor_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '45', }) @@ -38,6 +40,7 @@ 'context': , 'entity_id': 'sensor.mock_title_valve_closing', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-01-01T01:01:40+00:00', }) @@ -51,6 +54,7 @@ 'context': , 'entity_id': 'sensor.mock_title_valve_closing', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-01-01T01:01:10+00:00', }) @@ -64,6 +68,7 @@ 'context': , 'entity_id': 'sensor.mock_title_valve_closing', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -79,6 +84,7 @@ 'context': , 'entity_id': 'sensor.mock_title_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -94,6 +100,7 @@ 'context': , 'entity_id': 'sensor.mock_title_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) diff --git a/tests/components/gardena_bluetooth/snapshots/test_switch.ambr b/tests/components/gardena_bluetooth/snapshots/test_switch.ambr index 37dae0bff75..3b55aade60f 100644 --- a/tests/components/gardena_bluetooth/snapshots/test_switch.ambr +++ b/tests/components/gardena_bluetooth/snapshots/test_switch.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'switch.mock_title_open', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -19,6 +20,7 @@ 'context': , 'entity_id': 'switch.mock_title_open', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/glances/snapshots/test_sensor.ambr b/tests/components/glances/snapshots/test_sensor.ambr index a9d3436aac8..23242f66071 100644 --- a/tests/components/glances/snapshots/test_sensor.ambr +++ b/tests/components/glances/snapshots/test_sensor.ambr @@ -43,6 +43,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_containers_active', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -92,6 +93,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_containers_cpu_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '77.2', }) @@ -142,6 +144,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_containers_memory_used', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1149.6', }) @@ -192,6 +195,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_cpu_thermal_1_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '59', }) @@ -242,6 +246,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_err_temp_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -290,6 +295,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_md1_available', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -338,6 +344,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_md1_used', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -386,6 +393,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_md3_available', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -434,6 +442,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_md3_used', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -484,6 +493,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_media_disk_free', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '426.5', }) @@ -533,6 +543,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_media_disk_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.7', }) @@ -583,6 +594,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_media_disk_used', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.7', }) @@ -633,6 +645,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_memory_free', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2745.0', }) @@ -682,6 +695,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_memory_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.6', }) @@ -732,6 +746,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_memory_use', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1047.1', }) @@ -782,6 +797,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_na_temp_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -832,6 +848,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_ssl_disk_free', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '426.5', }) @@ -881,6 +898,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_ssl_disk_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.7', }) @@ -931,6 +949,7 @@ 'context': , 'entity_id': 'sensor.0_0_0_0_ssl_disk_used', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.7', }) diff --git a/tests/components/gree/snapshots/test_climate.ambr b/tests/components/gree/snapshots/test_climate.ambr index e5d733bc6f0..4f62be5cded 100644 --- a/tests/components/gree/snapshots/test_climate.ambr +++ b/tests/components/gree/snapshots/test_climate.ambr @@ -46,6 +46,7 @@ 'context': , 'entity_id': 'climate.fake_device_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), diff --git a/tests/components/gree/snapshots/test_switch.ambr b/tests/components/gree/snapshots/test_switch.ambr index 988771cd517..71c6d3ea71d 100644 --- a/tests/components/gree/snapshots/test_switch.ambr +++ b/tests/components/gree/snapshots/test_switch.ambr @@ -9,6 +9,7 @@ 'context': , 'entity_id': 'switch.fake_device_1_panel_light', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'switch.fake_device_1_quiet', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -31,6 +33,7 @@ 'context': , 'entity_id': 'switch.fake_device_1_fresh_air', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -42,6 +45,7 @@ 'context': , 'entity_id': 'switch.fake_device_1_xfan', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -53,6 +57,7 @@ 'context': , 'entity_id': 'switch.fake_device_1_health_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py index 954e415082f..1d6dec16991 100644 --- a/tests/components/homekit/test_diagnostics.py +++ b/tests/components/homekit/test_diagnostics.py @@ -264,6 +264,7 @@ async def test_config_entry_accessory( "context": {"id": ANY, "parent_id": None, "user_id": None}, "entity_id": "light.demo", "last_changed": ANY, + "last_reported": ANY, "last_updated": ANY, "state": "on", }, diff --git a/tests/components/homekit_controller/test_diagnostics.py b/tests/components/homekit_controller/test_diagnostics.py index c0a9ebbb8d4..f79c875385d 100644 --- a/tests/components/homekit_controller/test_diagnostics.py +++ b/tests/components/homekit_controller/test_diagnostics.py @@ -27,7 +27,9 @@ async def test_config_entry( diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) - assert diag == snapshot(exclude=props("last_updated", "last_changed")) + assert diag == snapshot( + exclude=props("last_changed", "last_reported", "last_updated") + ) async def test_device( @@ -45,4 +47,6 @@ async def test_device( diag = await get_diagnostics_for_device(hass, hass_client, config_entry, device) - assert diag == snapshot(exclude=props("last_updated", "last_changed")) + assert diag == snapshot( + exclude=props("last_changed", "last_reported", "last_updated") + ) diff --git a/tests/components/homekit_controller/test_init.py b/tests/components/homekit_controller/test_init.py index b658b11f2cb..ec3e6216288 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -265,6 +265,7 @@ async def test_snapshots( state_dict = dict(state.as_dict()) state_dict.pop("context", None) state_dict.pop("last_changed", None) + state_dict.pop("last_reported", None) state_dict.pop("last_updated", None) state_dict["attributes"] = dict(state_dict["attributes"]) diff --git a/tests/components/homewizard/snapshots/test_button.ambr b/tests/components/homewizard/snapshots/test_button.ambr index 4bcda8f38ac..5ab108d344c 100644 --- a/tests/components/homewizard/snapshots/test_button.ambr +++ b/tests/components/homewizard/snapshots/test_button.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'button.device_identify', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/homewizard/snapshots/test_number.ambr b/tests/components/homewizard/snapshots/test_number.ambr index 4ac6525dd72..1e0119cf0b9 100644 --- a/tests/components/homewizard/snapshots/test_number.ambr +++ b/tests/components/homewizard/snapshots/test_number.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'number.device_status_light_brightness', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100.0', }) diff --git a/tests/components/homewizard/snapshots/test_sensor.ambr b/tests/components/homewizard/snapshots/test_sensor.ambr index 967b4e5fda7..c9f2000b4c9 100644 --- a/tests/components/homewizard/snapshots/test_sensor.ambr +++ b/tests/components/homewizard/snapshots/test_sensor.ambr @@ -298,6 +298,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '74.052', }) @@ -382,6 +383,7 @@ 'context': , 'entity_id': 'sensor.device_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.273', }) @@ -466,6 +468,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '255.551', }) @@ -550,6 +553,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.705', }) @@ -634,6 +638,7 @@ 'context': , 'entity_id': 'sensor.device_frequency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }) @@ -721,6 +726,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-1058.296', }) @@ -805,6 +811,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '61.1', }) @@ -889,6 +896,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-58.612', }) @@ -973,6 +981,7 @@ 'context': , 'entity_id': 'sensor.device_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '228.472', }) @@ -1052,6 +1061,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -1135,6 +1145,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '92', }) @@ -1219,6 +1230,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '7112.293', }) @@ -1303,6 +1315,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -1387,6 +1400,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3548.879', }) @@ -1471,6 +1485,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3563.414', }) @@ -1555,6 +1570,7 @@ 'context': , 'entity_id': 'sensor.device_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.999', }) @@ -1639,6 +1655,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -1723,6 +1740,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '15.521', }) @@ -1807,6 +1825,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '15.477', }) @@ -1891,6 +1910,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.523', }) @@ -1975,6 +1995,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.101', }) @@ -2059,6 +2080,7 @@ 'context': , 'entity_id': 'sensor.device_frequency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '49.926', }) @@ -2146,6 +2168,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-900.194', }) @@ -2230,6 +2253,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -2314,6 +2338,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99.9', }) @@ -2398,6 +2423,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99.7', }) @@ -2485,6 +2511,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-1058.296', }) @@ -2572,6 +2599,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '158.102', }) @@ -2659,6 +2687,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -2743,6 +2772,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-429.025', }) @@ -2827,6 +2857,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -2911,6 +2942,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-166.675', }) @@ -2995,6 +3027,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-262.35', }) @@ -3079,6 +3112,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '230.751', }) @@ -3163,6 +3197,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '228.391', }) @@ -3247,6 +3282,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '229.612', }) @@ -3326,6 +3362,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -3409,6 +3446,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '92', }) @@ -4627,6 +4665,7 @@ 'context': , 'entity_id': 'sensor.device_average_demand', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123.0', }) @@ -4711,6 +4750,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-4', }) @@ -4795,6 +4835,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -4879,6 +4920,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -4958,6 +5000,7 @@ 'context': , 'entity_id': 'sensor.device_dsmr_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }) @@ -5042,6 +5085,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '13086.777', }) @@ -5126,6 +5170,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4321.333', }) @@ -5210,6 +5255,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8765.444', }) @@ -5294,6 +5340,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8765.444', }) @@ -5378,6 +5425,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8765.444', }) @@ -5462,6 +5510,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '13779.338', }) @@ -5546,6 +5595,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10830.511', }) @@ -5630,6 +5680,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2948.827', }) @@ -5714,6 +5765,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2948.827', }) @@ -5798,6 +5850,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2948.827', }) @@ -5882,6 +5935,7 @@ 'context': , 'entity_id': 'sensor.device_frequency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }) @@ -6036,6 +6090,7 @@ 'context': , 'entity_id': 'sensor.device_long_power_failures_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -6117,6 +6172,7 @@ 'context': , 'entity_id': 'sensor.device_peak_demand_current_month', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1111.0', }) @@ -6204,6 +6260,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-123', }) @@ -6283,6 +6340,7 @@ 'context': , 'entity_id': 'sensor.device_power_failures_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4', }) @@ -6370,6 +6428,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-123', }) @@ -6457,6 +6516,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '456', }) @@ -6544,6 +6604,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123.456', }) @@ -6623,6 +6684,7 @@ 'context': , 'entity_id': 'sensor.device_smart_meter_identifier', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '00112233445566778899AABBCCDDEEFF', }) @@ -6702,6 +6764,7 @@ 'context': , 'entity_id': 'sensor.device_smart_meter_model', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'ISKRA 2M550T-101', }) @@ -6795,6 +6858,7 @@ 'context': , 'entity_id': 'sensor.device_tariff', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -7759,6 +7823,7 @@ 'context': , 'entity_id': 'sensor.device_total_water_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1234.567', }) @@ -7843,6 +7908,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '230.111', }) @@ -7927,6 +7993,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '230.222', }) @@ -8011,6 +8078,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '230.333', }) @@ -8090,6 +8158,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_sags_detected_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -8169,6 +8238,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_sags_detected_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -8248,6 +8318,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_sags_detected_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -8327,6 +8398,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_swells_detected_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4', }) @@ -8406,6 +8478,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_swells_detected_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -8485,6 +8558,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_swells_detected_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6', }) @@ -8568,6 +8642,7 @@ 'context': , 'entity_id': 'sensor.device_water_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12.345', }) @@ -8647,6 +8722,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -8730,6 +8806,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -8882,6 +8959,7 @@ 'context': , 'entity_id': 'sensor.gas_meter_gas', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '111.111', }) @@ -9110,6 +9188,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '444.444', }) @@ -9337,6 +9416,7 @@ 'context': , 'entity_id': 'sensor.inlet_heat_meter_none', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '555.555', }) @@ -9640,6 +9720,7 @@ 'context': , 'entity_id': 'sensor.warm_water_meter_water', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '333.333', }) @@ -9868,6 +9949,7 @@ 'context': , 'entity_id': 'sensor.water_meter_water', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '222.222', }) @@ -10997,6 +11079,7 @@ 'context': , 'entity_id': 'sensor.device_average_demand', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -11081,6 +11164,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -11165,6 +11249,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -11249,6 +11334,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -11333,6 +11419,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11417,6 +11504,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11501,6 +11589,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11585,6 +11674,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11669,6 +11759,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export_tariff_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11753,6 +11844,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11837,6 +11929,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -11921,6 +12014,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -12005,6 +12099,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -12089,6 +12184,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import_tariff_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -12173,6 +12269,7 @@ 'context': , 'entity_id': 'sensor.device_frequency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -12252,6 +12349,7 @@ 'context': , 'entity_id': 'sensor.device_long_power_failures_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -12416,6 +12514,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -12495,6 +12594,7 @@ 'context': , 'entity_id': 'sensor.device_power_failures_detected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -12582,6 +12682,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -12669,6 +12770,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -12756,6 +12858,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -13720,6 +13823,7 @@ 'context': , 'entity_id': 'sensor.device_total_water_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -13804,6 +13908,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -13888,6 +13993,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -13972,6 +14078,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -14051,6 +14158,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_sags_detected_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14130,6 +14238,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_sags_detected_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14209,6 +14318,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_sags_detected_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14288,6 +14398,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_swells_detected_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14367,6 +14478,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_swells_detected_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14446,6 +14558,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_swells_detected_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14529,6 +14642,7 @@ 'context': , 'entity_id': 'sensor.device_water_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -14779,6 +14893,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -14863,6 +14978,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '63.651', }) @@ -14950,6 +15066,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1457.277', }) @@ -15037,6 +15154,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1457.277', }) @@ -15276,6 +15394,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -15359,6 +15478,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '94', }) @@ -15522,6 +15642,7 @@ 'context': , 'entity_id': 'sensor.device_total_water_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '17.014', }) @@ -15605,6 +15726,7 @@ 'context': , 'entity_id': 'sensor.device_water_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -15684,6 +15806,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -15767,6 +15890,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '84', }) @@ -16498,6 +16622,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '74.052', }) @@ -16582,6 +16707,7 @@ 'context': , 'entity_id': 'sensor.device_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.273', }) @@ -16666,6 +16792,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '255.551', }) @@ -16750,6 +16877,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.705', }) @@ -16834,6 +16962,7 @@ 'context': , 'entity_id': 'sensor.device_frequency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }) @@ -16921,6 +17050,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-1058.296', }) @@ -17005,6 +17135,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '61.1', }) @@ -17172,6 +17303,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-58.612', }) @@ -17416,6 +17548,7 @@ 'context': , 'entity_id': 'sensor.device_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '228.472', }) @@ -17495,6 +17628,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -17578,6 +17712,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '92', }) @@ -19517,6 +19652,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '7112.293', }) @@ -19601,6 +19737,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -19685,6 +19822,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3548.879', }) @@ -19769,6 +19907,7 @@ 'context': , 'entity_id': 'sensor.device_apparent_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3563.414', }) @@ -19853,6 +19992,7 @@ 'context': , 'entity_id': 'sensor.device_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.999', }) @@ -19937,6 +20077,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -20021,6 +20162,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '15.521', }) @@ -20105,6 +20247,7 @@ 'context': , 'entity_id': 'sensor.device_current_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '15.477', }) @@ -20189,6 +20332,7 @@ 'context': , 'entity_id': 'sensor.device_energy_export', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.523', }) @@ -20273,6 +20417,7 @@ 'context': , 'entity_id': 'sensor.device_energy_import', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.101', }) @@ -20357,6 +20502,7 @@ 'context': , 'entity_id': 'sensor.device_frequency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '49.926', }) @@ -20444,6 +20590,7 @@ 'context': , 'entity_id': 'sensor.device_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-900.194', }) @@ -20528,6 +20675,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -20612,6 +20760,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99.9', }) @@ -20696,6 +20845,7 @@ 'context': , 'entity_id': 'sensor.device_power_factor_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99.7', }) @@ -20783,6 +20933,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-1058.296', }) @@ -20870,6 +21021,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '158.102', }) @@ -20957,6 +21109,7 @@ 'context': , 'entity_id': 'sensor.device_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -21041,6 +21194,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-429.025', }) @@ -21125,6 +21279,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -21209,6 +21364,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-166.675', }) @@ -21293,6 +21449,7 @@ 'context': , 'entity_id': 'sensor.device_reactive_power_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-262.35', }) @@ -21537,6 +21694,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '230.751', }) @@ -21621,6 +21779,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '228.391', }) @@ -21705,6 +21864,7 @@ 'context': , 'entity_id': 'sensor.device_voltage_phase_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '229.612', }) @@ -21784,6 +21944,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_ssid', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Wi-Fi', }) @@ -21867,6 +22028,7 @@ 'context': , 'entity_id': 'sensor.device_wi_fi_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '92', }) diff --git a/tests/components/homewizard/snapshots/test_switch.ambr b/tests/components/homewizard/snapshots/test_switch.ambr index 55d327c2244..3036cd3bc38 100644 --- a/tests/components/homewizard/snapshots/test_switch.ambr +++ b/tests/components/homewizard/snapshots/test_switch.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'switch.device_cloud_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -86,6 +87,7 @@ 'context': , 'entity_id': 'switch.device_cloud_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -166,6 +168,7 @@ 'context': , 'entity_id': 'switch.device', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -245,6 +248,7 @@ 'context': , 'entity_id': 'switch.device_cloud_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -324,6 +328,7 @@ 'context': , 'entity_id': 'switch.device_switch_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -403,6 +408,7 @@ 'context': , 'entity_id': 'switch.device_cloud_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -482,6 +488,7 @@ 'context': , 'entity_id': 'switch.device_cloud_connection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/homeworks/snapshots/test_binary_sensor.ambr b/tests/components/homeworks/snapshots/test_binary_sensor.ambr index effa4315861..c301347d05d 100644 --- a/tests/components/homeworks/snapshots/test_binary_sensor.ambr +++ b/tests/components/homeworks/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.foyer_keypad_morning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -21,6 +22,7 @@ 'context': , 'entity_id': 'binary_sensor.foyer_keypad_morning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -34,6 +36,7 @@ 'context': , 'entity_id': 'binary_sensor.foyer_keypad_morning', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/homeworks/snapshots/test_light.ambr b/tests/components/homeworks/snapshots/test_light.ambr index 49f7f561bc0..7117a9d9b48 100644 --- a/tests/components/homeworks/snapshots/test_light.ambr +++ b/tests/components/homeworks/snapshots/test_light.ambr @@ -14,6 +14,7 @@ 'context': , 'entity_id': 'light.foyer_sconces', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -33,6 +34,7 @@ 'context': , 'entity_id': 'light.foyer_sconces', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr b/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr index f586cca1ba5..d677f504390 100644 --- a/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_binary_sensor.ambr @@ -177,6 +177,7 @@ 'context': , 'entity_id': 'binary_sensor.test_mower_1_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -222,6 +223,7 @@ 'context': , 'entity_id': 'binary_sensor.test_mower_1_leaving_dock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -267,6 +269,7 @@ 'context': , 'entity_id': 'binary_sensor.test_mower_1_returning_to_dock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr b/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr index 636b6599380..156eee9b8df 100644 --- a/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_device_tracker.ambr @@ -44,6 +44,7 @@ 'context': , 'entity_id': 'device_tracker.test_mower_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'not_home', }) diff --git a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr index 2e6dfcc52f0..77994d7e0d8 100644 --- a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr @@ -45,6 +45,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -98,6 +99,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_cutting_blade_usage_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.034', }) @@ -159,6 +161,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'main_area', }) @@ -205,6 +208,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_next_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-06-05T19:00:00+00:00', }) @@ -253,6 +257,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_number_of_charging_cycles', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1380', }) @@ -301,6 +306,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_number_of_collisions', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '11396', }) @@ -354,6 +360,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_total_charging_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1204.000', }) @@ -407,6 +414,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_total_cutting_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1165.000', }) @@ -460,6 +468,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_total_drive_distance', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1780.272', }) @@ -513,6 +522,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_total_running_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1268.000', }) @@ -566,6 +576,7 @@ 'context': , 'entity_id': 'sensor.test_mower_1_total_searching_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '103.000', }) diff --git a/tests/components/husqvarna_automower/snapshots/test_switch.ambr b/tests/components/husqvarna_automower/snapshots/test_switch.ambr index b1629a4cf99..c54997fcf06 100644 --- a/tests/components/husqvarna_automower/snapshots/test_switch.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_switch.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'switch.test_mower_1_enable_schedule', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr b/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr index 879e78d5534..4189de18ce4 100644 --- a/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr +++ b/tests/components/kitchen_sink/snapshots/test_lawn_mower.ambr @@ -9,6 +9,7 @@ 'context': , 'entity_id': 'lawn_mower.mower_can_do_all', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'docked', }), @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'lawn_mower.mower_can_dock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'mowing', }), @@ -31,6 +33,7 @@ 'context': , 'entity_id': 'lawn_mower.mower_can_mow', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'docked', }), @@ -42,6 +45,7 @@ 'context': , 'entity_id': 'lawn_mower.mower_can_pause', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'docked', }), @@ -53,6 +57,7 @@ 'context': , 'entity_id': 'lawn_mower.mower_is_paused', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'paused', }), diff --git a/tests/components/kitchen_sink/snapshots/test_lock.ambr b/tests/components/kitchen_sink/snapshots/test_lock.ambr index 9303401bdd5..616d778e6fd 100644 --- a/tests/components/kitchen_sink/snapshots/test_lock.ambr +++ b/tests/components/kitchen_sink/snapshots/test_lock.ambr @@ -9,6 +9,7 @@ 'context': , 'entity_id': 'lock.another_basic_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unlocked', }), @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'lock.another_openable_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unlocked', }), @@ -31,6 +33,7 @@ 'context': , 'entity_id': 'lock.basic_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'locked', }), @@ -42,6 +45,7 @@ 'context': , 'entity_id': 'lock.openable_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'locked', }), diff --git a/tests/components/kitchen_sink/snapshots/test_sensor.ambr b/tests/components/kitchen_sink/snapshots/test_sensor.ambr index 776a0c03369..bbf88c84eca 100644 --- a/tests/components/kitchen_sink/snapshots/test_sensor.ambr +++ b/tests/components/kitchen_sink/snapshots/test_sensor.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'sensor.outlet_1_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }), @@ -24,6 +25,7 @@ 'context': , 'entity_id': 'sensor.outlet_2_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1500', }), @@ -36,6 +38,7 @@ 'context': , 'entity_id': 'sensor.statistics_issues_issue_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }), @@ -48,6 +51,7 @@ 'context': , 'entity_id': 'sensor.statistics_issues_issue_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }), @@ -59,6 +63,7 @@ 'context': , 'entity_id': 'sensor.statistics_issues_issue_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }), diff --git a/tests/components/kitchen_sink/snapshots/test_switch.ambr b/tests/components/kitchen_sink/snapshots/test_switch.ambr index 89688e9e54e..1cd903a59d6 100644 --- a/tests/components/kitchen_sink/snapshots/test_switch.ambr +++ b/tests/components/kitchen_sink/snapshots/test_switch.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'switch.outlet_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -112,6 +113,7 @@ 'context': , 'entity_id': 'switch.outlet_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr b/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr index c05610a47a2..f08c2c28851 100644 --- a/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr +++ b/tests/components/lamarzocco/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.gs01234_brewing_active', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -54,6 +55,7 @@ 'context': , 'entity_id': 'binary_sensor.gs01234_water_tank_empty', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/lamarzocco/snapshots/test_button.ambr b/tests/components/lamarzocco/snapshots/test_button.ambr index 5cd38d914b7..023039cc6f7 100644 --- a/tests/components/lamarzocco/snapshots/test_button.ambr +++ b/tests/components/lamarzocco/snapshots/test_button.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'button.gs01234_start_backflush', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/lamarzocco/snapshots/test_calendar.ambr b/tests/components/lamarzocco/snapshots/test_calendar.ambr index ee318a7fc67..676c0f1b2ad 100644 --- a/tests/components/lamarzocco/snapshots/test_calendar.ambr +++ b/tests/components/lamarzocco/snapshots/test_calendar.ambr @@ -97,6 +97,7 @@ 'context': , 'entity_id': 'calendar.gs01234_auto_on_off_schedule', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/lamarzocco/snapshots/test_number.ambr b/tests/components/lamarzocco/snapshots/test_number.ambr index 2ff24c4d5bf..da35bf718f6 100644 --- a/tests/components/lamarzocco/snapshots/test_number.ambr +++ b/tests/components/lamarzocco/snapshots/test_number.ambr @@ -13,6 +13,7 @@ 'context': , 'entity_id': 'number.gs01234_coffee_target_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '95', }) @@ -69,6 +70,7 @@ 'context': , 'entity_id': 'number.gs01234_steam_target_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '128', }) @@ -125,6 +127,7 @@ 'context': , 'entity_id': 'number.gs01234_steam_target_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '128', }) @@ -181,6 +184,7 @@ 'context': , 'entity_id': 'number.gs01234_tea_water_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1023', }) @@ -237,6 +241,7 @@ 'context': , 'entity_id': 'number.gs01234_tea_water_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1023', }) @@ -292,6 +297,7 @@ 'context': , 'entity_id': 'number.gs01234_dose_key_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1023', }) @@ -309,6 +315,7 @@ 'context': , 'entity_id': 'number.gs01234_dose_key_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1023', }) @@ -326,6 +333,7 @@ 'context': , 'entity_id': 'number.gs01234_dose_key_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1023', }) @@ -343,6 +351,7 @@ 'context': , 'entity_id': 'number.gs01234_dose_key_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1023', }) @@ -361,6 +370,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_off_time_key_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -379,6 +389,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_off_time_key_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -397,6 +408,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_off_time_key_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -415,6 +427,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_off_time_key_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -433,6 +446,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_on_time_key_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -451,6 +465,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_on_time_key_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -469,6 +484,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_on_time_key_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -487,6 +503,7 @@ 'context': , 'entity_id': 'number.gs01234_prebrew_on_time_key_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -505,6 +522,7 @@ 'context': , 'entity_id': 'number.gs01234_preinfusion_time_key_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -523,6 +541,7 @@ 'context': , 'entity_id': 'number.gs01234_preinfusion_time_key_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -541,6 +560,7 @@ 'context': , 'entity_id': 'number.gs01234_preinfusion_time_key_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -559,6 +579,7 @@ 'context': , 'entity_id': 'number.gs01234_preinfusion_time_key_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -577,6 +598,7 @@ 'context': , 'entity_id': 'number.lm01234_prebrew_off_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -633,6 +655,7 @@ 'context': , 'entity_id': 'number.mr01234_prebrew_off_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -689,6 +712,7 @@ 'context': , 'entity_id': 'number.lm01234_prebrew_on_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -745,6 +769,7 @@ 'context': , 'entity_id': 'number.mr01234_prebrew_on_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -801,6 +826,7 @@ 'context': , 'entity_id': 'number.lm01234_preinfusion_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -857,6 +883,7 @@ 'context': , 'entity_id': 'number.mr01234_preinfusion_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) diff --git a/tests/components/lamarzocco/snapshots/test_select.ambr b/tests/components/lamarzocco/snapshots/test_select.ambr index e592d25c85c..1ee5ae7115f 100644 --- a/tests/components/lamarzocco/snapshots/test_select.ambr +++ b/tests/components/lamarzocco/snapshots/test_select.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'select.gs01234_prebrew_infusion_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -68,6 +69,7 @@ 'context': , 'entity_id': 'select.lm01234_prebrew_infusion_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -124,6 +126,7 @@ 'context': , 'entity_id': 'select.mr01234_prebrew_infusion_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -180,6 +183,7 @@ 'context': , 'entity_id': 'select.mr01234_steam_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) diff --git a/tests/components/lamarzocco/snapshots/test_sensor.ambr b/tests/components/lamarzocco/snapshots/test_sensor.ambr index a97244f5472..71422b8b850 100644 --- a/tests/components/lamarzocco/snapshots/test_sensor.ambr +++ b/tests/components/lamarzocco/snapshots/test_sensor.ambr @@ -48,6 +48,7 @@ 'context': , 'entity_id': 'sensor.gs01234_current_coffee_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '93', }) @@ -101,6 +102,7 @@ 'context': , 'entity_id': 'sensor.gs01234_current_steam_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '113', }) @@ -151,6 +153,7 @@ 'context': , 'entity_id': 'sensor.gs01234_shot_timer', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -200,6 +203,7 @@ 'context': , 'entity_id': 'sensor.gs01234_total_coffees_made', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '13', }) @@ -249,6 +253,7 @@ 'context': , 'entity_id': 'sensor.gs01234_total_flushes_made', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '69', }) diff --git a/tests/components/lamarzocco/snapshots/test_switch.ambr b/tests/components/lamarzocco/snapshots/test_switch.ambr index 7e6edb148b2..1639e8fce94 100644 --- a/tests/components/lamarzocco/snapshots/test_switch.ambr +++ b/tests/components/lamarzocco/snapshots/test_switch.ambr @@ -37,6 +37,7 @@ 'context': , 'entity_id': 'switch.gs01234', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -82,6 +83,7 @@ 'context': , 'entity_id': 'switch.gs01234_auto_on_off', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -127,6 +129,7 @@ 'context': , 'entity_id': 'switch.gs01234_steam_boiler', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/lamarzocco/snapshots/test_update.ambr b/tests/components/lamarzocco/snapshots/test_update.ambr index bd7ccc2cf59..811b1a6f598 100644 --- a/tests/components/lamarzocco/snapshots/test_update.ambr +++ b/tests/components/lamarzocco/snapshots/test_update.ambr @@ -18,6 +18,7 @@ 'context': , 'entity_id': 'update.gs01234_gateway_firmware', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -74,6 +75,7 @@ 'context': , 'entity_id': 'update.gs01234_machine_firmware', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/landisgyr_heat_meter/snapshots/test_sensor.ambr b/tests/components/landisgyr_heat_meter/snapshots/test_sensor.ambr index 5d8b703cdcd..8def1c83a85 100644 --- a/tests/components/landisgyr_heat_meter/snapshots/test_sensor.ambr +++ b/tests/components/landisgyr_heat_meter/snapshots/test_sensor.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_volume_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '456.0', }), @@ -26,6 +27,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_heat_usage_gj', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123.0', }), @@ -39,6 +41,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_heat_previous_year_gj', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '111.0', }), @@ -52,6 +55,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_volume_usage_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '450.0', }), @@ -63,6 +67,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_ownership_number', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123a', }), @@ -74,6 +79,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_error_number', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }), @@ -85,6 +91,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_device_number', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'abc1', }), @@ -97,6 +104,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_measurement_period_minutes', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }), @@ -109,6 +117,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_power_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.1', }), @@ -121,6 +130,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_power_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.4', }), @@ -133,6 +143,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flowrate_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.744', }), @@ -145,6 +156,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flowrate_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.743', }), @@ -157,6 +169,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_return_temperature_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '96.1', }), @@ -169,6 +182,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_return_temperature_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '96.2', }), @@ -181,6 +195,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flow_temperature_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '98.5', }), @@ -193,6 +208,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flow_temperature_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '98.4', }), @@ -205,6 +221,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_operating_hours', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '115575', }), @@ -217,6 +234,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flow_hours', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30242', }), @@ -229,6 +247,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_fault_hours', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }), @@ -241,6 +260,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_fault_hours_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }), @@ -252,6 +272,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_yearly_set_day', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '01-01', }), @@ -263,6 +284,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_monthly_set_day', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '01', }), @@ -275,6 +297,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_meter_date_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2022-05-19T19:41:17+00:00', }), @@ -287,6 +310,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_measuring_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.5', }), @@ -297,6 +321,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_settings_and_firmware', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0 1 0 0000 CECV CECV 1 5.16 5.16 F 101008 040404 08 0', }), @@ -315,6 +340,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_heat_usage_mwh', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123.0', }), @@ -329,6 +355,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_volume_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '456.0', }), @@ -342,6 +369,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_heat_previous_year_mwh', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '111.0', }), @@ -355,6 +383,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_volume_usage_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '450.0', }), @@ -366,6 +395,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_ownership_number', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123a', }), @@ -377,6 +407,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_error_number', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }), @@ -388,6 +419,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_device_number', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'abc1', }), @@ -400,6 +432,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_measurement_period_minutes', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }), @@ -412,6 +445,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_power_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.1', }), @@ -424,6 +458,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_power_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.4', }), @@ -436,6 +471,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flowrate_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.744', }), @@ -448,6 +484,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flowrate_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.743', }), @@ -460,6 +497,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_return_temperature_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '96.1', }), @@ -472,6 +510,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_return_temperature_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '96.2', }), @@ -484,6 +523,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flow_temperature_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '98.5', }), @@ -496,6 +536,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flow_temperature_max_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '98.4', }), @@ -508,6 +549,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_operating_hours', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '115575', }), @@ -520,6 +562,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_flow_hours', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30242', }), @@ -532,6 +575,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_fault_hours', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }), @@ -544,6 +588,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_fault_hours_previous_year', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }), @@ -555,6 +600,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_yearly_set_day', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '01-01', }), @@ -566,6 +612,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_monthly_set_day', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '01', }), @@ -578,6 +625,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_meter_date_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2022-05-19T19:41:17+00:00', }), @@ -590,6 +638,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_measuring_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.5', }), @@ -600,6 +649,7 @@ 'context': , 'entity_id': 'sensor.heat_meter_settings_and_firmware', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0 1 0 0000 CECV CECV 1 5.16 5.16 F 101008 040404 08 0', }), diff --git a/tests/components/lastfm/snapshots/test_sensor.ambr b/tests/components/lastfm/snapshots/test_sensor.ambr index 33f9b7a56fa..c05c34b24ee 100644 --- a/tests/components/lastfm/snapshots/test_sensor.ambr +++ b/tests/components/lastfm/snapshots/test_sensor.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'sensor.lastfm_testaccount1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'artist - title', }) @@ -29,6 +30,7 @@ 'context': , 'entity_id': 'sensor.lastfm_testaccount1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Not Scrobbling', }) diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 732437107de..d752b896401 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -294,14 +294,25 @@ def create_state_changed_event( state, attributes=None, last_changed=None, + last_reported=None, last_updated=None, ): """Create state changed event.""" old_state = ha.State( - entity_id, "old", attributes, last_changed, last_updated + entity_id, + "old", + attributes, + last_changed=last_changed, + last_reported=last_reported, + last_updated=last_updated, ).as_dict() new_state = ha.State( - entity_id, state, attributes, last_changed, last_updated + entity_id, + state, + attributes, + last_changed=last_changed, + last_reported=last_reported, + last_updated=last_updated, ).as_dict() return create_state_changed_event_from_old_new( diff --git a/tests/components/melissa/snapshots/test_climate.ambr b/tests/components/melissa/snapshots/test_climate.ambr index 40e757d1561..5d0752ccffe 100644 --- a/tests/components/melissa/snapshots/test_climate.ambr +++ b/tests/components/melissa/snapshots/test_climate.ambr @@ -28,6 +28,7 @@ 'context': , 'entity_id': 'climate.melissa_12345678', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat', }) diff --git a/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr b/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr index 00eae5a4bdd..2e4bf49089c 100644 --- a/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr +++ b/tests/components/minecraft_server/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -21,6 +22,7 @@ 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -34,6 +36,7 @@ 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -47,6 +50,7 @@ 'context': , 'entity_id': 'binary_sensor.minecraft_server_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/minecraft_server/snapshots/test_sensor.ambr b/tests/components/minecraft_server/snapshots/test_sensor.ambr index 32cf0f1e935..47d638adf79 100644 --- a/tests/components/minecraft_server/snapshots/test_sensor.ambr +++ b/tests/components/minecraft_server/snapshots/test_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_latency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -21,6 +22,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_online', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -34,6 +36,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -46,6 +49,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_world_message', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy MOTD', }) @@ -58,6 +62,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Version', }) @@ -70,6 +75,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123', }) @@ -82,6 +88,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_map_name', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Map Name', }) @@ -94,6 +101,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_game_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Game Mode', }) @@ -106,6 +114,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_edition', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'MCPE', }) @@ -119,6 +128,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_latency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -137,6 +147,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_online', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -150,6 +161,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -162,6 +174,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_world_message', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy MOTD', }) @@ -174,6 +187,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Version', }) @@ -186,6 +200,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123', }) @@ -199,6 +214,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_latency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -212,6 +228,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_online', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -225,6 +242,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -237,6 +255,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_world_message', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy MOTD', }) @@ -249,6 +268,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Version', }) @@ -261,6 +281,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123', }) @@ -273,6 +294,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_map_name', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Map Name', }) @@ -285,6 +307,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_game_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Game Mode', }) @@ -297,6 +320,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_edition', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'MCPE', }) @@ -310,6 +334,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_latency', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -328,6 +353,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_online', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }) @@ -341,6 +367,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_players_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -353,6 +380,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_world_message', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy MOTD', }) @@ -365,6 +393,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Dummy Version', }) @@ -377,6 +406,7 @@ 'context': , 'entity_id': 'sensor.minecraft_server_protocol_version', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '123', }) diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index 8959ad0f719..f14c1bd5fc4 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -108,6 +108,7 @@ async def test_entry_diagnostics( "attributes": {"friendly_name": "MQTT Sensor"}, "entity_id": "sensor.none_mqtt_sensor", "last_changed": ANY, + "last_reported": ANY, "last_updated": ANY, "state": "unknown", }, @@ -234,6 +235,7 @@ async def test_redact_diagnostics( }, "entity_id": "device_tracker.mqtt_unique", "last_changed": ANY, + "last_reported": ANY, "last_updated": ANY, "state": "home", }, diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index 2045a14bf1f..24b4a83c425 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -104,11 +104,12 @@ async def test_state_changed_event_sends_message( event = {} event["event_type"] = EVENT_STATE_CHANGED new_state = { + "attributes": {}, + "entity_id": e_id, + "last_changed": now.isoformat(), + "last_reported": now.isoformat(), "last_updated": now.isoformat(), "state": "on", - "entity_id": e_id, - "attributes": {}, - "last_changed": now.isoformat(), } event["event_data"] = {"new_state": new_state, "entity_id": e_id} diff --git a/tests/components/netatmo/snapshots/test_camera.ambr b/tests/components/netatmo/snapshots/test_camera.ambr index d989d029aa8..94a5ded5031 100644 --- a/tests/components/netatmo/snapshots/test_camera.ambr +++ b/tests/components/netatmo/snapshots/test_camera.ambr @@ -55,6 +55,7 @@ 'context': , 'entity_id': 'camera.front', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'streaming', }) @@ -115,6 +116,7 @@ 'context': , 'entity_id': 'camera.hall', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'streaming', }) @@ -173,6 +175,7 @@ 'context': , 'entity_id': 'camera.netatmo_doorbell', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'idle', }) diff --git a/tests/components/netatmo/snapshots/test_climate.ambr b/tests/components/netatmo/snapshots/test_climate.ambr index 32f9209c3c2..327595e90a5 100644 --- a/tests/components/netatmo/snapshots/test_climate.ambr +++ b/tests/components/netatmo/snapshots/test_climate.ambr @@ -69,6 +69,7 @@ 'context': , 'entity_id': 'climate.bureau', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -149,6 +150,7 @@ 'context': , 'entity_id': 'climate.cocina', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -228,6 +230,7 @@ 'context': , 'entity_id': 'climate.corridor', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -308,6 +311,7 @@ 'context': , 'entity_id': 'climate.entrada', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -389,6 +393,7 @@ 'context': , 'entity_id': 'climate.livingroom', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) diff --git a/tests/components/netatmo/snapshots/test_cover.ambr b/tests/components/netatmo/snapshots/test_cover.ambr index c05e246c02c..e907985ab39 100644 --- a/tests/components/netatmo/snapshots/test_cover.ambr +++ b/tests/components/netatmo/snapshots/test_cover.ambr @@ -44,6 +44,7 @@ 'context': , 'entity_id': 'cover.bubendorff_blind', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'closed', }) @@ -93,6 +94,7 @@ 'context': , 'entity_id': 'cover.entrance_blinds', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'closed', }) diff --git a/tests/components/netatmo/snapshots/test_fan.ambr b/tests/components/netatmo/snapshots/test_fan.ambr index d750dffb1fe..958a8f79704 100644 --- a/tests/components/netatmo/snapshots/test_fan.ambr +++ b/tests/components/netatmo/snapshots/test_fan.ambr @@ -52,6 +52,7 @@ 'context': , 'entity_id': 'fan.centralized_ventilation_controler', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/netatmo/snapshots/test_light.ambr b/tests/components/netatmo/snapshots/test_light.ambr index ed628e4fd7a..dabc7f8528f 100644 --- a/tests/components/netatmo/snapshots/test_light.ambr +++ b/tests/components/netatmo/snapshots/test_light.ambr @@ -50,6 +50,7 @@ 'context': , 'entity_id': 'light.bathroom_light', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -104,6 +105,7 @@ 'context': , 'entity_id': 'light.front', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -160,6 +162,7 @@ 'context': , 'entity_id': 'light.unknown_00_11_22_33_00_11_45_fe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/netatmo/snapshots/test_select.ambr b/tests/components/netatmo/snapshots/test_select.ambr index 65c98ec29f5..0a95049957e 100644 --- a/tests/components/netatmo/snapshots/test_select.ambr +++ b/tests/components/netatmo/snapshots/test_select.ambr @@ -50,6 +50,7 @@ 'context': , 'entity_id': 'select.myhome', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Default', }) diff --git a/tests/components/netatmo/snapshots/test_sensor.ambr b/tests/components/netatmo/snapshots/test_sensor.ambr index 8983a1ecf13..df92c644588 100644 --- a/tests/components/netatmo/snapshots/test_sensor.ambr +++ b/tests/components/netatmo/snapshots/test_sensor.ambr @@ -48,6 +48,7 @@ 'context': , 'entity_id': 'sensor.baby_bedroom_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1053', }) @@ -97,6 +98,7 @@ 'context': , 'entity_id': 'sensor.baby_bedroom_health', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Fine', }) @@ -150,6 +152,7 @@ 'context': , 'entity_id': 'sensor.baby_bedroom_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '66', }) @@ -203,6 +206,7 @@ 'context': , 'entity_id': 'sensor.baby_bedroom_noise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '45', }) @@ -259,6 +263,7 @@ 'context': , 'entity_id': 'sensor.baby_bedroom_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1021.4', }) @@ -384,6 +389,7 @@ 'context': , 'entity_id': 'sensor.baby_bedroom_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '21.6', }) @@ -507,6 +513,7 @@ 'context': , 'entity_id': 'sensor.bedroom_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -554,6 +561,7 @@ 'context': , 'entity_id': 'sensor.bedroom_health', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -605,6 +613,7 @@ 'context': , 'entity_id': 'sensor.bedroom_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -656,6 +665,7 @@ 'context': , 'entity_id': 'sensor.bedroom_noise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -710,6 +720,7 @@ 'context': , 'entity_id': 'sensor.bedroom_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -833,6 +844,7 @@ 'context': , 'entity_id': 'sensor.bedroom_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -956,6 +968,7 @@ 'context': , 'entity_id': 'sensor.bureau_modulate_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '90', }) @@ -1007,6 +1020,7 @@ 'context': , 'entity_id': 'sensor.cold_water_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -1094,6 +1108,7 @@ 'context': , 'entity_id': 'sensor.consumption_meter_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '476', }) @@ -1181,6 +1196,7 @@ 'context': , 'entity_id': 'sensor.corridor_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '67', }) @@ -1232,6 +1248,7 @@ 'context': , 'entity_id': 'sensor.ecocompteur_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -1319,6 +1336,7 @@ 'context': , 'entity_id': 'sensor.gas_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -1522,6 +1540,7 @@ 'context': , 'entity_id': 'sensor.home_avg_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '63.2', }) @@ -1578,6 +1597,7 @@ 'context': , 'entity_id': 'sensor.home_avg_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1010.4', }) @@ -1631,6 +1651,7 @@ 'context': , 'entity_id': 'sensor.home_avg_rain', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.1', }) @@ -1722,6 +1743,7 @@ 'context': , 'entity_id': 'sensor.home_avg_rain_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '11.3', }) @@ -1775,6 +1797,7 @@ 'context': , 'entity_id': 'sensor.home_avg_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.7', }) @@ -1828,6 +1851,7 @@ 'context': , 'entity_id': 'sensor.home_avg_wind_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '15.0', }) @@ -1995,6 +2019,7 @@ 'context': , 'entity_id': 'sensor.home_max_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '76', }) @@ -2051,6 +2076,7 @@ 'context': , 'entity_id': 'sensor.home_max_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1014.4', }) @@ -2104,6 +2130,7 @@ 'context': , 'entity_id': 'sensor.home_max_rain', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.5', }) @@ -2195,6 +2222,7 @@ 'context': , 'entity_id': 'sensor.home_max_rain_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12.322', }) @@ -2248,6 +2276,7 @@ 'context': , 'entity_id': 'sensor.home_max_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.4', }) @@ -2301,6 +2330,7 @@ 'context': , 'entity_id': 'sensor.home_max_wind_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '15', }) @@ -2352,6 +2382,7 @@ 'context': , 'entity_id': 'sensor.hot_water_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2439,6 +2470,7 @@ 'context': , 'entity_id': 'sensor.kitchen_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2486,6 +2518,7 @@ 'context': , 'entity_id': 'sensor.kitchen_health', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2537,6 +2570,7 @@ 'context': , 'entity_id': 'sensor.kitchen_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2588,6 +2622,7 @@ 'context': , 'entity_id': 'sensor.kitchen_noise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2642,6 +2677,7 @@ 'context': , 'entity_id': 'sensor.kitchen_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2765,6 +2801,7 @@ 'context': , 'entity_id': 'sensor.kitchen_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2888,6 +2925,7 @@ 'context': , 'entity_id': 'sensor.line_1_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2975,6 +3013,7 @@ 'context': , 'entity_id': 'sensor.line_2_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3062,6 +3101,7 @@ 'context': , 'entity_id': 'sensor.line_3_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3149,6 +3189,7 @@ 'context': , 'entity_id': 'sensor.line_4_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3236,6 +3277,7 @@ 'context': , 'entity_id': 'sensor.line_5_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3323,6 +3365,7 @@ 'context': , 'entity_id': 'sensor.livingroom_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '75', }) @@ -3374,6 +3417,7 @@ 'context': , 'entity_id': 'sensor.livingroom_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3421,6 +3465,7 @@ 'context': , 'entity_id': 'sensor.livingroom_health', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3472,6 +3517,7 @@ 'context': , 'entity_id': 'sensor.livingroom_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3523,6 +3569,7 @@ 'context': , 'entity_id': 'sensor.livingroom_noise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3577,6 +3624,7 @@ 'context': , 'entity_id': 'sensor.livingroom_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3700,6 +3748,7 @@ 'context': , 'entity_id': 'sensor.livingroom_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -3825,6 +3874,7 @@ 'context': , 'entity_id': 'sensor.parents_bedroom_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '494', }) @@ -3874,6 +3924,7 @@ 'context': , 'entity_id': 'sensor.parents_bedroom_health', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Fine', }) @@ -3927,6 +3978,7 @@ 'context': , 'entity_id': 'sensor.parents_bedroom_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '63', }) @@ -3980,6 +4032,7 @@ 'context': , 'entity_id': 'sensor.parents_bedroom_noise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '42', }) @@ -4036,6 +4089,7 @@ 'context': , 'entity_id': 'sensor.parents_bedroom_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1014.5', }) @@ -4161,6 +4215,7 @@ 'context': , 'entity_id': 'sensor.parents_bedroom_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20.3', }) @@ -4284,6 +4339,7 @@ 'context': , 'entity_id': 'sensor.prise_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -4371,6 +4427,7 @@ 'context': , 'entity_id': 'sensor.total_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -4458,6 +4515,7 @@ 'context': , 'entity_id': 'sensor.valve1_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '90', }) @@ -4509,6 +4567,7 @@ 'context': , 'entity_id': 'sensor.valve2_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '90', }) @@ -4560,6 +4619,7 @@ 'context': , 'entity_id': 'sensor.villa_bathroom_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '55', }) @@ -4611,6 +4671,7 @@ 'context': , 'entity_id': 'sensor.villa_bathroom_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1930', }) @@ -4662,6 +4723,7 @@ 'context': , 'entity_id': 'sensor.villa_bathroom_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '55', }) @@ -4785,6 +4847,7 @@ 'context': , 'entity_id': 'sensor.villa_bathroom_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '19.4', }) @@ -4872,6 +4935,7 @@ 'context': , 'entity_id': 'sensor.villa_bedroom_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '28', }) @@ -4923,6 +4987,7 @@ 'context': , 'entity_id': 'sensor.villa_bedroom_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1076', }) @@ -4974,6 +5039,7 @@ 'context': , 'entity_id': 'sensor.villa_bedroom_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '53', }) @@ -5097,6 +5163,7 @@ 'context': , 'entity_id': 'sensor.villa_bedroom_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '19.3', }) @@ -5186,6 +5253,7 @@ 'context': , 'entity_id': 'sensor.villa_co2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1339', }) @@ -5275,6 +5343,7 @@ 'context': , 'entity_id': 'sensor.villa_garden_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '85', }) @@ -5322,6 +5391,7 @@ 'context': , 'entity_id': 'sensor.villa_garden_direction', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'SW', }) @@ -5557,6 +5627,7 @@ 'context': , 'entity_id': 'sensor.villa_garden_wind_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4', }) @@ -5610,6 +5681,7 @@ 'context': , 'entity_id': 'sensor.villa_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '45', }) @@ -5663,6 +5735,7 @@ 'context': , 'entity_id': 'sensor.villa_noise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '35', }) @@ -5714,6 +5787,7 @@ 'context': , 'entity_id': 'sensor.villa_outdoor_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -5765,6 +5839,7 @@ 'context': , 'entity_id': 'sensor.villa_outdoor_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -5888,6 +5963,7 @@ 'context': , 'entity_id': 'sensor.villa_outdoor_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -5980,6 +6056,7 @@ 'context': , 'entity_id': 'sensor.villa_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1026.8', }) @@ -6067,6 +6144,7 @@ 'context': , 'entity_id': 'sensor.villa_rain_battery_percent', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '21', }) @@ -6154,6 +6232,7 @@ 'context': , 'entity_id': 'sensor.villa_rain_rain', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.7', }) @@ -6243,6 +6322,7 @@ 'context': , 'entity_id': 'sensor.villa_rain_rain_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.9', }) @@ -6368,6 +6448,7 @@ 'context': , 'entity_id': 'sensor.villa_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '21.1', }) diff --git a/tests/components/netatmo/snapshots/test_switch.ambr b/tests/components/netatmo/snapshots/test_switch.ambr index 06f40bcd379..22c41aefd42 100644 --- a/tests/components/netatmo/snapshots/test_switch.ambr +++ b/tests/components/netatmo/snapshots/test_switch.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'switch.prise', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/netgear_lte/snapshots/test_binary_sensor.ambr b/tests/components/netgear_lte/snapshots/test_binary_sensor.ambr index 6f3950aaabe..7ff30fb5cbc 100644 --- a/tests/components/netgear_lte/snapshots/test_binary_sensor.ambr +++ b/tests/components/netgear_lte/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.netgear_lm1200_mobile_connected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'binary_sensor.netgear_lm1200_roaming', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -33,6 +35,7 @@ 'context': , 'entity_id': 'binary_sensor.netgear_lm1200_wire_connected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/netgear_lte/snapshots/test_sensor.ambr b/tests/components/netgear_lte/snapshots/test_sensor.ambr index e74416895ee..cbeb0bf5b9a 100644 --- a/tests/components/netgear_lte/snapshots/test_sensor.ambr +++ b/tests/components/netgear_lte/snapshots/test_sensor.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_cell_id', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12345678', }) @@ -19,6 +20,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_connection_text', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4G', }) @@ -31,6 +33,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_connection_type', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'IPv4AndIPv6', }) @@ -43,6 +46,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_current_band', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'LTE B4', }) @@ -56,6 +60,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_radio_quality', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '52', }) @@ -68,6 +73,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_register_network_display', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'T-Mobile', }) @@ -82,6 +88,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_rx_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-113', }) @@ -94,6 +101,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_service_type', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'LTE', }) @@ -107,6 +115,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_sms', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -120,6 +129,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_sms_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -134,6 +144,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_tx_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4', }) @@ -146,6 +157,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_upstream', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'LTE', }) @@ -160,6 +172,7 @@ 'context': , 'entity_id': 'sensor.netgear_lm1200_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40.5162000656128', }) diff --git a/tests/components/nibe_heatpump/snapshots/test_climate.ambr b/tests/components/nibe_heatpump/snapshots/test_climate.ambr index f19fd69c47d..0c5cd46f5db 100644 --- a/tests/components/nibe_heatpump/snapshots/test_climate.ambr +++ b/tests/components/nibe_heatpump/snapshots/test_climate.ambr @@ -21,6 +21,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -42,6 +43,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -68,6 +70,7 @@ 'context': , 'entity_id': 'climate.climate_system_s3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -89,6 +92,7 @@ 'context': , 'entity_id': 'climate.climate_system_s3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -121,6 +125,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -147,6 +152,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -173,6 +179,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat', }) @@ -199,6 +206,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -225,6 +233,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -251,6 +260,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -277,6 +287,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -303,6 +314,7 @@ 'context': , 'entity_id': 'climate.climate_system_s2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -329,6 +341,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -355,6 +368,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -381,6 +395,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat', }) @@ -407,6 +422,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -433,6 +449,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -459,6 +476,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'heat_cool', }) @@ -485,6 +503,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) @@ -511,6 +530,7 @@ 'context': , 'entity_id': 'climate.climate_system_s1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'auto', }) diff --git a/tests/components/nibe_heatpump/snapshots/test_coordinator.ambr b/tests/components/nibe_heatpump/snapshots/test_coordinator.ambr index 98e62a833a8..50755533ee5 100644 --- a/tests/components/nibe_heatpump/snapshots/test_coordinator.ambr +++ b/tests/components/nibe_heatpump/snapshots/test_coordinator.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -27,6 +28,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -43,6 +45,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -60,6 +63,7 @@ 'context': , 'entity_id': 'number.min_supply_climate_system_1_40035', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -79,6 +83,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -95,6 +100,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20.0', }) @@ -111,6 +117,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20.0', }) @@ -127,6 +134,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.0', }) diff --git a/tests/components/nibe_heatpump/snapshots/test_number.ambr b/tests/components/nibe_heatpump/snapshots/test_number.ambr index d174c0cc059..343d5569a2d 100644 --- a/tests/components/nibe_heatpump/snapshots/test_number.ambr +++ b/tests/components/nibe_heatpump/snapshots/test_number.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'number.heat_offset_s1_47011', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-10.0', }) @@ -27,6 +28,7 @@ 'context': , 'entity_id': 'number.heat_offset_s1_47011', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -47,6 +49,7 @@ 'context': , 'entity_id': 'number.hw_charge_offset_47062', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-10.0', }) @@ -64,6 +67,7 @@ 'context': , 'entity_id': 'number.hw_charge_offset_47062', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -81,6 +85,7 @@ 'context': , 'entity_id': 'number.hw_charge_offset_47062', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -97,6 +102,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-10.0', }) @@ -113,6 +119,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10.0', }) @@ -129,6 +136,7 @@ 'context': , 'entity_id': 'number.heating_offset_climate_system_1_40031', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) diff --git a/tests/components/onewire/snapshots/test_binary_sensor.ambr b/tests/components/onewire/snapshots/test_binary_sensor.ambr index afdec245ea0..3123dfb6a5e 100644 --- a/tests/components/onewire/snapshots/test_binary_sensor.ambr +++ b/tests/components/onewire/snapshots/test_binary_sensor.ambr @@ -200,6 +200,7 @@ 'context': , 'entity_id': 'binary_sensor.12_111111111111_sensed_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -212,6 +213,7 @@ 'context': , 'entity_id': 'binary_sensor.12_111111111111_sensed_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -820,6 +822,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -832,6 +835,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -844,6 +848,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -856,6 +861,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -868,6 +874,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -880,6 +887,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -892,6 +900,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_6', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -904,6 +913,7 @@ 'context': , 'entity_id': 'binary_sensor.29_111111111111_sensed_7', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1058,6 +1068,7 @@ 'context': , 'entity_id': 'binary_sensor.3a_111111111111_sensed_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1070,6 +1081,7 @@ 'context': , 'entity_id': 'binary_sensor.3a_111111111111_sensed_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1527,6 +1539,7 @@ 'context': , 'entity_id': 'binary_sensor.ef_111111111113_hub_short_on_branch_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1540,6 +1553,7 @@ 'context': , 'entity_id': 'binary_sensor.ef_111111111113_hub_short_on_branch_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1553,6 +1567,7 @@ 'context': , 'entity_id': 'binary_sensor.ef_111111111113_hub_short_on_branch_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1566,6 +1581,7 @@ 'context': , 'entity_id': 'binary_sensor.ef_111111111113_hub_short_on_branch_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), diff --git a/tests/components/onewire/snapshots/test_sensor.ambr b/tests/components/onewire/snapshots/test_sensor.ambr index 989a5997ca8..aa8c914ece5 100644 --- a/tests/components/onewire/snapshots/test_sensor.ambr +++ b/tests/components/onewire/snapshots/test_sensor.ambr @@ -134,6 +134,7 @@ 'context': , 'entity_id': 'sensor.10_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.1', }), @@ -255,6 +256,7 @@ 'context': , 'entity_id': 'sensor.12_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.1', }), @@ -270,6 +272,7 @@ 'context': , 'entity_id': 'sensor.12_111111111111_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1025.1', }), @@ -390,6 +393,7 @@ 'context': , 'entity_id': 'sensor.1d_111111111111_counter_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '251123', }), @@ -404,6 +408,7 @@ 'context': , 'entity_id': 'sensor.1d_111111111111_counter_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '248125', }), @@ -552,6 +557,7 @@ 'context': , 'entity_id': 'sensor.1d_111111111111_counter_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '251123', }), @@ -566,6 +572,7 @@ 'context': , 'entity_id': 'sensor.1d_111111111111_counter_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '248125', }), @@ -654,6 +661,7 @@ 'context': , 'entity_id': 'sensor.22_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1072,6 +1080,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.1', }), @@ -1087,6 +1096,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '72.8', }), @@ -1102,6 +1112,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_hih3600_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '73.8', }), @@ -1117,6 +1128,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_hih4000_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '74.8', }), @@ -1132,6 +1144,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_hih5030_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '75.8', }), @@ -1147,6 +1160,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_htm1735_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1162,6 +1176,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '969.3', }), @@ -1177,6 +1192,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_illuminance', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '65.9', }), @@ -1192,6 +1208,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_vad_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.0', }), @@ -1207,6 +1224,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_vdd_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4.7', }), @@ -1222,6 +1240,7 @@ 'context': , 'entity_id': 'sensor.26_111111111111_vis_voltage_difference', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.1', }), @@ -1310,6 +1329,7 @@ 'context': , 'entity_id': 'sensor.28_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.0', }), @@ -1398,6 +1418,7 @@ 'context': , 'entity_id': 'sensor.28_222222222222_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.0', }), @@ -1486,6 +1507,7 @@ 'context': , 'entity_id': 'sensor.28_222222222223_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.0', }), @@ -1713,6 +1735,7 @@ 'context': , 'entity_id': 'sensor.30_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.0', }), @@ -1728,6 +1751,7 @@ 'context': , 'entity_id': 'sensor.30_111111111111_thermocouple_k_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '173.8', }), @@ -1743,6 +1767,7 @@ 'context': , 'entity_id': 'sensor.30_111111111111_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.0', }), @@ -1758,6 +1783,7 @@ 'context': , 'entity_id': 'sensor.30_111111111111_vis_voltage_gradient', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.1', }), @@ -1886,6 +1912,7 @@ 'context': , 'entity_id': 'sensor.3b_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '28.2', }), @@ -1974,6 +2001,7 @@ 'context': , 'entity_id': 'sensor.42_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '29.1', }), @@ -2161,6 +2189,7 @@ 'context': , 'entity_id': 'sensor.7e_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '13.9', }), @@ -2176,6 +2205,7 @@ 'context': , 'entity_id': 'sensor.7e_111111111111_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1012.2', }), @@ -2191,6 +2221,7 @@ 'context': , 'entity_id': 'sensor.7e_111111111111_illuminance', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '65.9', }), @@ -2206,6 +2237,7 @@ 'context': , 'entity_id': 'sensor.7e_111111111111_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '41.4', }), @@ -2327,6 +2359,7 @@ 'context': , 'entity_id': 'sensor.7e_222222222222_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '13.9', }), @@ -2342,6 +2375,7 @@ 'context': , 'entity_id': 'sensor.7e_222222222222_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1012.2', }), @@ -2760,6 +2794,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.1', }), @@ -2775,6 +2810,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '72.8', }), @@ -2790,6 +2826,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_hih3600_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '73.8', }), @@ -2805,6 +2842,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_hih4000_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '74.8', }), @@ -2820,6 +2858,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_hih5030_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '75.8', }), @@ -2835,6 +2874,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_htm1735_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2850,6 +2890,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '969.3', }), @@ -2865,6 +2906,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_illuminance', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '65.9', }), @@ -2880,6 +2922,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_vad_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3.0', }), @@ -2895,6 +2938,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_vdd_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4.7', }), @@ -2910,6 +2954,7 @@ 'context': , 'entity_id': 'sensor.a6_111111111111_vis_voltage_difference', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.1', }), @@ -3064,6 +3109,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111111_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '67.7', }), @@ -3079,6 +3125,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111111_raw_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '65.5', }), @@ -3094,6 +3141,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111111_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.1', }), @@ -3281,6 +3329,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111112_wetness_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '41.7', }), @@ -3296,6 +3345,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111112_wetness_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '42.5', }), @@ -3311,6 +3361,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111112_moisture_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '43.1', }), @@ -3326,6 +3377,7 @@ 'context': , 'entity_id': 'sensor.ef_111111111112_moisture_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '44.1', }), diff --git a/tests/components/onewire/snapshots/test_switch.ambr b/tests/components/onewire/snapshots/test_switch.ambr index cd3e9708d95..2ac542d203c 100644 --- a/tests/components/onewire/snapshots/test_switch.ambr +++ b/tests/components/onewire/snapshots/test_switch.ambr @@ -89,6 +89,7 @@ 'context': , 'entity_id': 'switch.05_111111111111_programmed_input_output', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -305,6 +306,7 @@ 'context': , 'entity_id': 'switch.12_111111111111_programmed_input_output_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -317,6 +319,7 @@ 'context': , 'entity_id': 'switch.12_111111111111_programmed_input_output_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -329,6 +332,7 @@ 'context': , 'entity_id': 'switch.12_111111111111_latch_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -341,6 +345,7 @@ 'context': , 'entity_id': 'switch.12_111111111111_latch_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -572,6 +577,7 @@ 'context': , 'entity_id': 'switch.26_111111111111_current_a_d_control', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1240,6 +1246,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1252,6 +1259,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1264,6 +1272,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1276,6 +1285,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1288,6 +1298,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1300,6 +1311,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1312,6 +1324,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_6', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1324,6 +1337,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_programmed_input_output_7', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1336,6 +1350,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1348,6 +1363,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1360,6 +1376,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1372,6 +1389,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1384,6 +1402,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_4', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1396,6 +1415,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1408,6 +1428,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_6', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1420,6 +1441,7 @@ 'context': , 'entity_id': 'switch.29_111111111111_latch_7', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1574,6 +1596,7 @@ 'context': , 'entity_id': 'switch.3a_111111111111_programmed_input_output_a', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1586,6 +1609,7 @@ 'context': , 'entity_id': 'switch.3a_111111111111_programmed_input_output_b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1829,6 +1853,7 @@ 'context': , 'entity_id': 'switch.a6_111111111111_current_a_d_control', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2169,6 +2194,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_leaf_sensor_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2181,6 +2207,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_leaf_sensor_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2193,6 +2220,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_leaf_sensor_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2205,6 +2233,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_leaf_sensor_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2217,6 +2246,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_moisture_sensor_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2229,6 +2259,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_moisture_sensor_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2241,6 +2272,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_moisture_sensor_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2253,6 +2285,7 @@ 'context': , 'entity_id': 'switch.ef_111111111112_moisture_sensor_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2429,6 +2462,7 @@ 'context': , 'entity_id': 'switch.ef_111111111113_hub_branch_0', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2441,6 +2475,7 @@ 'context': , 'entity_id': 'switch.ef_111111111113_hub_branch_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2453,6 +2488,7 @@ 'context': , 'entity_id': 'switch.ef_111111111113_hub_branch_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2465,6 +2501,7 @@ 'context': , 'entity_id': 'switch.ef_111111111113_hub_branch_3', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), diff --git a/tests/components/opensky/snapshots/test_sensor.ambr b/tests/components/opensky/snapshots/test_sensor.ambr index ab39746a93f..717927fb461 100644 --- a/tests/components/opensky/snapshots/test_sensor.ambr +++ b/tests/components/opensky/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.opensky', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -25,6 +26,7 @@ 'context': , 'entity_id': 'sensor.opensky', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) diff --git a/tests/components/ping/snapshots/test_binary_sensor.ambr b/tests/components/ping/snapshots/test_binary_sensor.ambr index e4d26fe873e..98ea9a8a847 100644 --- a/tests/components/ping/snapshots/test_binary_sensor.ambr +++ b/tests/components/ping/snapshots/test_binary_sensor.ambr @@ -104,6 +104,7 @@ 'context': , 'entity_id': 'binary_sensor.10_10_10_10', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -121,6 +122,7 @@ 'context': , 'entity_id': 'binary_sensor.10_10_10_10', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/ping/snapshots/test_sensor.ambr b/tests/components/ping/snapshots/test_sensor.ambr index c91dcff687a..d1548f7559c 100644 --- a/tests/components/ping/snapshots/test_sensor.ambr +++ b/tests/components/ping/snapshots/test_sensor.ambr @@ -44,6 +44,7 @@ 'context': , 'entity_id': 'sensor.10_10_10_10_round_trip_time_average', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4.8', }) @@ -93,6 +94,7 @@ 'context': , 'entity_id': 'sensor.10_10_10_10_round_trip_time_maximum', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -148,6 +150,7 @@ 'context': , 'entity_id': 'sensor.10_10_10_10_round_trip_time_minimum', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) diff --git a/tests/components/proximity/test_diagnostics.py b/tests/components/proximity/test_diagnostics.py index e161d175d0b..26d1d293efd 100644 --- a/tests/components/proximity/test_diagnostics.py +++ b/tests/components/proximity/test_diagnostics.py @@ -71,4 +71,6 @@ async def test_entry_diagnostics( assert await get_diagnostics_for_config_entry( hass, hass_client, mock_entry - ) == snapshot(exclude=props("entry_id", "last_changed", "last_updated")) + ) == snapshot( + exclude=props("entry_id", "last_changed", "last_reported", "last_updated") + ) diff --git a/tests/components/renault/snapshots/test_binary_sensor.ambr b/tests/components/renault/snapshots/test_binary_sensor.ambr index da0916cfae0..7f30faac38e 100644 --- a/tests/components/renault/snapshots/test_binary_sensor.ambr +++ b/tests/components/renault/snapshots/test_binary_sensor.ambr @@ -231,6 +231,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -242,6 +243,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hatch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -253,6 +255,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_left_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -264,6 +267,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_right_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -275,6 +279,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -286,6 +291,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -585,6 +591,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_plug', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -596,6 +603,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -607,6 +615,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -618,6 +627,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hatch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -629,6 +639,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_left_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -640,6 +651,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_right_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -651,6 +663,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -662,6 +675,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -806,6 +820,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_plug', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -817,6 +832,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -827,6 +843,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1157,6 +1174,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_plug', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1168,6 +1186,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1178,6 +1197,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1189,6 +1209,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1200,6 +1221,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hatch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1211,6 +1233,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_left_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1222,6 +1245,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_right_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1233,6 +1257,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1244,6 +1269,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1481,6 +1507,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1492,6 +1519,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hatch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1503,6 +1531,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_left_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1514,6 +1543,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_right_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1525,6 +1555,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1536,6 +1567,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1835,6 +1867,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_plug', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1846,6 +1879,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -1857,6 +1891,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1868,6 +1903,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hatch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1879,6 +1915,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_left_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1890,6 +1927,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_right_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1901,6 +1939,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -1912,6 +1951,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2056,6 +2096,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_plug', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2067,6 +2108,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -2077,6 +2119,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2407,6 +2450,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_plug', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2418,6 +2462,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2428,6 +2473,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hvac', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2439,6 +2485,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2450,6 +2497,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_hatch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2461,6 +2509,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_left_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2472,6 +2521,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_rear_right_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2483,6 +2533,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), @@ -2494,6 +2545,7 @@ 'context': , 'entity_id': 'binary_sensor.reg_number_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), diff --git a/tests/components/renault/snapshots/test_button.ambr b/tests/components/renault/snapshots/test_button.ambr index 38f0d0fa106..daef84b5c0a 100644 --- a/tests/components/renault/snapshots/test_button.ambr +++ b/tests/components/renault/snapshots/test_button.ambr @@ -75,6 +75,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -218,6 +219,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -228,6 +230,7 @@ 'context': , 'entity_id': 'button.reg_number_start_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -238,6 +241,7 @@ 'context': , 'entity_id': 'button.reg_number_stop_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -381,6 +385,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -391,6 +396,7 @@ 'context': , 'entity_id': 'button.reg_number_start_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -401,6 +407,7 @@ 'context': , 'entity_id': 'button.reg_number_stop_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -544,6 +551,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -554,6 +562,7 @@ 'context': , 'entity_id': 'button.reg_number_start_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -564,6 +573,7 @@ 'context': , 'entity_id': 'button.reg_number_stop_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -645,6 +655,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -788,6 +799,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -798,6 +810,7 @@ 'context': , 'entity_id': 'button.reg_number_start_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -808,6 +821,7 @@ 'context': , 'entity_id': 'button.reg_number_stop_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -951,6 +965,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -961,6 +976,7 @@ 'context': , 'entity_id': 'button.reg_number_start_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -971,6 +987,7 @@ 'context': , 'entity_id': 'button.reg_number_stop_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1114,6 +1131,7 @@ 'context': , 'entity_id': 'button.reg_number_start_air_conditioner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1124,6 +1142,7 @@ 'context': , 'entity_id': 'button.reg_number_start_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1134,6 +1153,7 @@ 'context': , 'entity_id': 'button.reg_number_stop_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), diff --git a/tests/components/renault/snapshots/test_device_tracker.ambr b/tests/components/renault/snapshots/test_device_tracker.ambr index 3a029617ab3..8fe1713dc0b 100644 --- a/tests/components/renault/snapshots/test_device_tracker.ambr +++ b/tests/components/renault/snapshots/test_device_tracker.ambr @@ -76,6 +76,7 @@ 'context': , 'entity_id': 'device_tracker.reg_number_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -158,6 +159,7 @@ 'context': , 'entity_id': 'device_tracker.reg_number_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -280,6 +282,7 @@ 'context': , 'entity_id': 'device_tracker.reg_number_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -365,6 +368,7 @@ 'context': , 'entity_id': 'device_tracker.reg_number_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'not_home', }), @@ -450,6 +454,7 @@ 'context': , 'entity_id': 'device_tracker.reg_number_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'not_home', }), @@ -575,6 +580,7 @@ 'context': , 'entity_id': 'device_tracker.reg_number_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'not_home', }), diff --git a/tests/components/renault/snapshots/test_select.ambr b/tests/components/renault/snapshots/test_select.ambr index 053cbf48217..7e8356ee070 100644 --- a/tests/components/renault/snapshots/test_select.ambr +++ b/tests/components/renault/snapshots/test_select.ambr @@ -126,6 +126,7 @@ 'context': , 'entity_id': 'select.reg_number_charge_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -218,6 +219,7 @@ 'context': , 'entity_id': 'select.reg_number_charge_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -310,6 +312,7 @@ 'context': , 'entity_id': 'select.reg_number_charge_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -442,6 +445,7 @@ 'context': , 'entity_id': 'select.reg_number_charge_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'always', }), @@ -534,6 +538,7 @@ 'context': , 'entity_id': 'select.reg_number_charge_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'always', }), @@ -626,6 +631,7 @@ 'context': , 'entity_id': 'select.reg_number_charge_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'schedule_mode', }), diff --git a/tests/components/renault/snapshots/test_sensor.ambr b/tests/components/renault/snapshots/test_sensor.ambr index a13b194c5b8..5909c66bc5c 100644 --- a/tests/components/renault/snapshots/test_sensor.ambr +++ b/tests/components/renault/snapshots/test_sensor.ambr @@ -239,6 +239,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -252,6 +253,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -265,6 +267,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_quantity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -276,6 +279,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_location_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -286,6 +290,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -296,6 +301,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -850,6 +856,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -871,6 +878,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charge_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -884,6 +892,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_remaining_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -897,6 +906,7 @@ 'context': , 'entity_id': 'sensor.reg_number_admissible_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -914,6 +924,7 @@ 'context': , 'entity_id': 'sensor.reg_number_plug_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -927,6 +938,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -940,6 +952,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_available_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -953,6 +966,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -964,6 +978,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_battery_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -977,6 +992,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -990,6 +1006,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1003,6 +1020,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_quantity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1014,6 +1032,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_location_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1024,6 +1043,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1034,6 +1054,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1586,6 +1607,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1607,6 +1629,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charge_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1620,6 +1643,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_remaining_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1633,6 +1657,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1650,6 +1675,7 @@ 'context': , 'entity_id': 'sensor.reg_number_plug_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1663,6 +1689,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1676,6 +1703,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_available_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1689,6 +1717,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1700,6 +1729,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_battery_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1713,6 +1743,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1726,6 +1757,7 @@ 'context': , 'entity_id': 'sensor.reg_number_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1737,6 +1769,7 @@ 'context': , 'entity_id': 'sensor.reg_number_hvac_soc_threshold', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1748,6 +1781,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_hvac_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1758,6 +1792,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -1768,6 +1803,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2351,6 +2387,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2372,6 +2409,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charge_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2385,6 +2423,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_remaining_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2398,6 +2437,7 @@ 'context': , 'entity_id': 'sensor.reg_number_admissible_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2415,6 +2455,7 @@ 'context': , 'entity_id': 'sensor.reg_number_plug_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2428,6 +2469,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2441,6 +2483,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_available_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2454,6 +2497,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2465,6 +2509,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_battery_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2478,6 +2523,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2491,6 +2537,7 @@ 'context': , 'entity_id': 'sensor.reg_number_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2502,6 +2549,7 @@ 'context': , 'entity_id': 'sensor.reg_number_hvac_soc_threshold', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2513,6 +2561,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_hvac_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2524,6 +2573,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_location_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2534,6 +2584,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2544,6 +2595,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -2789,6 +2841,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5567', }), @@ -2802,6 +2855,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '35', }), @@ -2815,6 +2869,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_quantity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }), @@ -2826,6 +2881,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_location_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-02-18T16:58:38+00:00', }), @@ -2836,6 +2892,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Stopped, ready for RES', }), @@ -2846,6 +2903,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }), @@ -3400,6 +3458,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }), @@ -3421,6 +3480,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charge_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'charge_in_progress', }), @@ -3434,6 +3494,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_remaining_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '145', }), @@ -3447,6 +3508,7 @@ 'context': , 'entity_id': 'sensor.reg_number_admissible_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.0', }), @@ -3464,6 +3526,7 @@ 'context': , 'entity_id': 'sensor.reg_number_plug_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'plugged', }), @@ -3477,6 +3540,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '141', }), @@ -3490,6 +3554,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_available_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '31', }), @@ -3503,6 +3568,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20', }), @@ -3514,6 +3580,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_battery_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-01-12T21:40:16+00:00', }), @@ -3527,6 +3594,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5567', }), @@ -3540,6 +3608,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '35', }), @@ -3553,6 +3622,7 @@ 'context': , 'entity_id': 'sensor.reg_number_fuel_quantity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3', }), @@ -3564,6 +3634,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_location_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-02-18T16:58:38+00:00', }), @@ -3574,6 +3645,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Stopped, ready for RES', }), @@ -3584,6 +3656,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }), @@ -4136,6 +4209,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }), @@ -4157,6 +4231,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charge_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'charge_in_progress', }), @@ -4170,6 +4245,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_remaining_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '145', }), @@ -4183,6 +4259,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.027', }), @@ -4200,6 +4277,7 @@ 'context': , 'entity_id': 'sensor.reg_number_plug_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'plugged', }), @@ -4213,6 +4291,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '141', }), @@ -4226,6 +4305,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_available_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '31', }), @@ -4239,6 +4319,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20', }), @@ -4250,6 +4331,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_battery_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-01-12T21:40:16+00:00', }), @@ -4263,6 +4345,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '49114', }), @@ -4276,6 +4359,7 @@ 'context': , 'entity_id': 'sensor.reg_number_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8.0', }), @@ -4287,6 +4371,7 @@ 'context': , 'entity_id': 'sensor.reg_number_hvac_soc_threshold', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -4298,6 +4383,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_hvac_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -4308,6 +4394,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -4318,6 +4405,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -4901,6 +4989,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }), @@ -4922,6 +5011,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charge_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'charge_error', }), @@ -4935,6 +5025,7 @@ 'context': , 'entity_id': 'sensor.reg_number_charging_remaining_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -4948,6 +5039,7 @@ 'context': , 'entity_id': 'sensor.reg_number_admissible_charging_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -4965,6 +5057,7 @@ 'context': , 'entity_id': 'sensor.reg_number_plug_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unplugged', }), @@ -4978,6 +5071,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_autonomy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '128', }), @@ -4991,6 +5085,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_available_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }), @@ -5004,6 +5099,7 @@ 'context': , 'entity_id': 'sensor.reg_number_battery_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -5015,6 +5111,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_battery_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-11-17T08:06:48+00:00', }), @@ -5028,6 +5125,7 @@ 'context': , 'entity_id': 'sensor.reg_number_mileage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '49114', }), @@ -5041,6 +5139,7 @@ 'context': , 'entity_id': 'sensor.reg_number_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), @@ -5052,6 +5151,7 @@ 'context': , 'entity_id': 'sensor.reg_number_hvac_soc_threshold', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.0', }), @@ -5063,6 +5163,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_hvac_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-12-03T00:00:00+00:00', }), @@ -5074,6 +5175,7 @@ 'context': , 'entity_id': 'sensor.reg_number_last_location_activity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2020-02-18T16:58:38+00:00', }), @@ -5084,6 +5186,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Stopped, ready for RES', }), @@ -5094,6 +5197,7 @@ 'context': , 'entity_id': 'sensor.reg_number_remote_engine_start_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }), diff --git a/tests/components/rfxtrx/snapshots/test_event.ambr b/tests/components/rfxtrx/snapshots/test_event.ambr index 99bb0195c65..9fb99bacef3 100644 --- a/tests/components/rfxtrx/snapshots/test_event.ambr +++ b/tests/components/rfxtrx/snapshots/test_event.ambr @@ -34,6 +34,7 @@ 'context': , 'entity_id': 'event.arc_c1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -58,6 +59,7 @@ 'context': , 'entity_id': 'event.arc_d1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -115,6 +117,7 @@ 'context': , 'entity_id': 'event.x10_security_d3dc54_32', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/samsungtv/snapshots/test_init.ambr b/tests/components/samsungtv/snapshots/test_init.ambr index 9087c1d95f9..404b9a6b3af 100644 --- a/tests/components/samsungtv/snapshots/test_init.ambr +++ b/tests/components/samsungtv/snapshots/test_init.ambr @@ -14,6 +14,7 @@ 'context': , 'entity_id': 'media_player.any', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/sfr_box/snapshots/test_binary_sensor.ambr b/tests/components/sfr_box/snapshots/test_binary_sensor.ambr index 1333121df87..7422c1395c3 100644 --- a/tests/components/sfr_box/snapshots/test_binary_sensor.ambr +++ b/tests/components/sfr_box/snapshots/test_binary_sensor.ambr @@ -107,6 +107,7 @@ 'context': , 'entity_id': 'binary_sensor.sfr_box_wan_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -118,6 +119,7 @@ 'context': , 'entity_id': 'binary_sensor.sfr_box_dsl_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -231,6 +233,7 @@ 'context': , 'entity_id': 'binary_sensor.sfr_box_wan_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }), @@ -242,6 +245,7 @@ 'context': , 'entity_id': 'binary_sensor.sfr_box_ftth_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }), diff --git a/tests/components/sfr_box/snapshots/test_button.ambr b/tests/components/sfr_box/snapshots/test_button.ambr index 0ca62e8caed..0dfbf187f6d 100644 --- a/tests/components/sfr_box/snapshots/test_button.ambr +++ b/tests/components/sfr_box/snapshots/test_button.ambr @@ -76,6 +76,7 @@ 'context': , 'entity_id': 'button.sfr_box_restart', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }), diff --git a/tests/components/sfr_box/snapshots/test_sensor.ambr b/tests/components/sfr_box/snapshots/test_sensor.ambr index be2ee848029..0f39eed9e60 100644 --- a/tests/components/sfr_box/snapshots/test_sensor.ambr +++ b/tests/components/sfr_box/snapshots/test_sensor.ambr @@ -565,6 +565,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_network_infrastructure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'adsl', }), @@ -577,6 +578,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12251', }), @@ -589,6 +591,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.56', }), @@ -607,6 +610,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_wan_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'adsl_routed', }), @@ -617,6 +621,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_line_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'ADSL2+', }), @@ -627,6 +632,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_counter', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '16', }), @@ -637,6 +643,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_crc', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }), @@ -650,6 +657,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_noise_down', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5.8', }), @@ -663,6 +671,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_noise_up', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.0', }), @@ -676,6 +685,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_attenuation_down', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '28.5', }), @@ -689,6 +699,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_attenuation_up', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20.8', }), @@ -702,6 +713,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_rate_down', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5549', }), @@ -715,6 +727,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_rate_up', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '187', }), @@ -734,6 +747,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_line_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'no_defect', }), @@ -757,6 +771,7 @@ 'context': , 'entity_id': 'sensor.sfr_box_dsl_training', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'showtime', }), diff --git a/tests/components/streamlabswater/snapshots/test_binary_sensor.ambr b/tests/components/streamlabswater/snapshots/test_binary_sensor.ambr index 6c117a18d75..c74df76e71b 100644 --- a/tests/components/streamlabswater/snapshots/test_binary_sensor.ambr +++ b/tests/components/streamlabswater/snapshots/test_binary_sensor.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'binary_sensor.water_monitor_away_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/streamlabswater/snapshots/test_sensor.ambr b/tests/components/streamlabswater/snapshots/test_sensor.ambr index cb11852447c..d54cdcafb93 100644 --- a/tests/components/streamlabswater/snapshots/test_sensor.ambr +++ b/tests/components/streamlabswater/snapshots/test_sensor.ambr @@ -45,6 +45,7 @@ 'context': , 'entity_id': 'sensor.water_monitor_daily_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '200.44691536', }) @@ -95,6 +96,7 @@ 'context': , 'entity_id': 'sensor.water_monitor_monthly_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '420.514099294', }) @@ -145,6 +147,7 @@ 'context': , 'entity_id': 'sensor.water_monitor_yearly_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '65432.389256934', }) diff --git a/tests/components/tailwind/snapshots/test_binary_sensor.ambr b/tests/components/tailwind/snapshots/test_binary_sensor.ambr index 6b09714a27e..ea2a539363d 100644 --- a/tests/components/tailwind/snapshots/test_binary_sensor.ambr +++ b/tests/components/tailwind/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.door_1_operational_problem', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -84,6 +85,7 @@ 'context': , 'entity_id': 'binary_sensor.door_2_operational_problem', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/tailwind/snapshots/test_button.ambr b/tests/components/tailwind/snapshots/test_button.ambr index f293b508808..560d3fe692c 100644 --- a/tests/components/tailwind/snapshots/test_button.ambr +++ b/tests/components/tailwind/snapshots/test_button.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'button.tailwind_iq3_identify', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/tailwind/snapshots/test_cover.ambr b/tests/components/tailwind/snapshots/test_cover.ambr index d349c555945..0ecd172b2ca 100644 --- a/tests/components/tailwind/snapshots/test_cover.ambr +++ b/tests/components/tailwind/snapshots/test_cover.ambr @@ -9,6 +9,7 @@ 'context': , 'entity_id': 'cover.door_1', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'open', }) @@ -86,6 +87,7 @@ 'context': , 'entity_id': 'cover.door_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'open', }) diff --git a/tests/components/tailwind/snapshots/test_number.ambr b/tests/components/tailwind/snapshots/test_number.ambr index e00827ed761..cbd61d31a6c 100644 --- a/tests/components/tailwind/snapshots/test_number.ambr +++ b/tests/components/tailwind/snapshots/test_number.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'number.tailwind_iq3_status_led_brightness', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) diff --git a/tests/components/technove/snapshots/test_binary_sensor.ambr b/tests/components/technove/snapshots/test_binary_sensor.ambr index f90e6e7b442..140526b9391 100644 --- a/tests/components/technove/snapshots/test_binary_sensor.ambr +++ b/tests/components/technove/snapshots/test_binary_sensor.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'binary_sensor.technove_station_battery_protected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -86,6 +87,7 @@ 'context': , 'entity_id': 'binary_sensor.technove_station_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -131,6 +133,7 @@ 'context': , 'entity_id': 'binary_sensor.technove_station_conflict_with_power_sharing_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -176,6 +179,7 @@ 'context': , 'entity_id': 'binary_sensor.technove_station_power_sharing_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -221,6 +225,7 @@ 'context': , 'entity_id': 'binary_sensor.technove_station_static_ip', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -267,6 +272,7 @@ 'context': , 'entity_id': 'binary_sensor.technove_station_update', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/technove/snapshots/test_sensor.ambr b/tests/components/technove/snapshots/test_sensor.ambr index ca676ffdca0..149155519d4 100644 --- a/tests/components/technove/snapshots/test_sensor.ambr +++ b/tests/components/technove/snapshots/test_sensor.ambr @@ -45,6 +45,7 @@ 'context': , 'entity_id': 'sensor.technove_station_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '23.75', }) @@ -95,6 +96,7 @@ 'context': , 'entity_id': 'sensor.technove_station_input_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '238', }) @@ -145,6 +147,7 @@ 'context': , 'entity_id': 'sensor.technove_station_last_session_energy_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12.34', }) @@ -195,6 +198,7 @@ 'context': , 'entity_id': 'sensor.technove_station_max_station_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '32', }) @@ -245,6 +249,7 @@ 'context': , 'entity_id': 'sensor.technove_station_output_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '238', }) @@ -295,6 +300,7 @@ 'context': , 'entity_id': 'sensor.technove_station_signal_strength', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-82', }) @@ -356,6 +362,7 @@ 'context': , 'entity_id': 'sensor.technove_station_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'plugged_charging', }) @@ -406,6 +413,7 @@ 'context': , 'entity_id': 'sensor.technove_station_total_energy_usage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1234', }) @@ -451,6 +459,7 @@ 'context': , 'entity_id': 'sensor.technove_station_wi_fi_network_name', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Connecting...', }) diff --git a/tests/components/technove/snapshots/test_switch.ambr b/tests/components/technove/snapshots/test_switch.ambr index 676646dd347..1a707971fc8 100644 --- a/tests/components/technove/snapshots/test_switch.ambr +++ b/tests/components/technove/snapshots/test_switch.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'switch.technove_station_auto_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/tedee/snapshots/test_binary_sensor.ambr b/tests/components/tedee/snapshots/test_binary_sensor.ambr index 32820496d9f..8c9dca1bd12 100644 --- a/tests/components/tedee/snapshots/test_binary_sensor.ambr +++ b/tests/components/tedee/snapshots/test_binary_sensor.ambr @@ -107,6 +107,7 @@ 'context': , 'entity_id': 'binary_sensor.lock_1a2b_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -119,6 +120,7 @@ 'context': , 'entity_id': 'binary_sensor.lock_1a2b_pullspring_enabled', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -131,6 +133,7 @@ 'context': , 'entity_id': 'binary_sensor.lock_1a2b_semi_locked', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/tedee/snapshots/test_lock.ambr b/tests/components/tedee/snapshots/test_lock.ambr index d232ab243d9..8e4fc464479 100644 --- a/tests/components/tedee/snapshots/test_lock.ambr +++ b/tests/components/tedee/snapshots/test_lock.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'lock.lock_1a2b', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unlocked', }) @@ -84,6 +85,7 @@ 'context': , 'entity_id': 'lock.lock_2c3d', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unlocked', }) diff --git a/tests/components/tedee/snapshots/test_sensor.ambr b/tests/components/tedee/snapshots/test_sensor.ambr index 5a05a7c7d22..d5f4c8361c3 100644 --- a/tests/components/tedee/snapshots/test_sensor.ambr +++ b/tests/components/tedee/snapshots/test_sensor.ambr @@ -80,6 +80,7 @@ 'context': , 'entity_id': 'sensor.lock_1a2b_battery', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }) @@ -95,6 +96,7 @@ 'context': , 'entity_id': 'sensor.lock_1a2b_pullspring_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) diff --git a/tests/components/template/snapshots/test_binary_sensor.ambr b/tests/components/template/snapshots/test_binary_sensor.ambr index 2529021971a..e809c66e644 100644 --- a/tests/components/template/snapshots/test_binary_sensor.ambr +++ b/tests/components/template/snapshots/test_binary_sensor.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'binary_sensor.my_template', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -20,6 +21,7 @@ 'context': , 'entity_id': 'binary_sensor.my_template', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/template/snapshots/test_sensor.ambr b/tests/components/template/snapshots/test_sensor.ambr index 7959959dfa9..344761ae45a 100644 --- a/tests/components/template/snapshots/test_sensor.ambr +++ b/tests/components/template/snapshots/test_sensor.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'sensor.my_template', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.0', }) @@ -22,6 +23,7 @@ 'context': , 'entity_id': 'sensor.my_template', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.0', }) diff --git a/tests/components/teslemetry/snapshots/test_climate.ambr b/tests/components/teslemetry/snapshots/test_climate.ambr index f0027273ece..097df8bde85 100644 --- a/tests/components/teslemetry/snapshots/test_climate.ambr +++ b/tests/components/teslemetry/snapshots/test_climate.ambr @@ -69,6 +69,7 @@ 'context': , 'entity_id': 'climate.test_climate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/teslemetry/snapshots/test_sensor.ambr b/tests/components/teslemetry/snapshots/test_sensor.ambr index ec87e5e09e1..fad04d341c9 100644 --- a/tests/components/teslemetry/snapshots/test_sensor.ambr +++ b/tests/components/teslemetry/snapshots/test_sensor.ambr @@ -51,6 +51,7 @@ 'context': , 'entity_id': 'sensor.energy_site_battery_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5.06', }) @@ -66,6 +67,7 @@ 'context': , 'entity_id': 'sensor.energy_site_battery_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5.06', }) @@ -122,6 +124,7 @@ 'context': , 'entity_id': 'sensor.energy_site_energy_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '38.8964736842105', }) @@ -137,6 +140,7 @@ 'context': , 'entity_id': 'sensor.energy_site_energy_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '38.8964736842105', }) @@ -193,6 +197,7 @@ 'context': , 'entity_id': 'sensor.energy_site_generator_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -208,6 +213,7 @@ 'context': , 'entity_id': 'sensor.energy_site_generator_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -264,6 +270,7 @@ 'context': , 'entity_id': 'sensor.energy_site_grid_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -279,6 +286,7 @@ 'context': , 'entity_id': 'sensor.energy_site_grid_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -335,6 +343,7 @@ 'context': , 'entity_id': 'sensor.energy_site_grid_services_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -350,6 +359,7 @@ 'context': , 'entity_id': 'sensor.energy_site_grid_services_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -406,6 +416,7 @@ 'context': , 'entity_id': 'sensor.energy_site_load_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.245', }) @@ -421,6 +432,7 @@ 'context': , 'entity_id': 'sensor.energy_site_load_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '6.245', }) @@ -467,6 +479,7 @@ 'context': , 'entity_id': 'sensor.energy_site_none', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on_grid', }) @@ -480,6 +493,7 @@ 'context': , 'entity_id': 'sensor.energy_site_none', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on_grid', }) @@ -533,6 +547,7 @@ 'context': , 'entity_id': 'sensor.energy_site_percentage_charged', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '95.5053740373966', }) @@ -548,6 +563,7 @@ 'context': , 'entity_id': 'sensor.energy_site_percentage_charged', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '95.5053740373966', }) @@ -604,6 +620,7 @@ 'context': , 'entity_id': 'sensor.energy_site_solar_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.185', }) @@ -619,6 +636,7 @@ 'context': , 'entity_id': 'sensor.energy_site_solar_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.185', }) @@ -675,6 +693,7 @@ 'context': , 'entity_id': 'sensor.energy_site_total_pack_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40.727', }) @@ -690,6 +709,7 @@ 'context': , 'entity_id': 'sensor.energy_site_total_pack_energy', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40.727', }) @@ -735,6 +755,7 @@ 'context': , 'entity_id': 'sensor.test_battery_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -747,6 +768,7 @@ 'context': , 'entity_id': 'sensor.test_battery_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -792,6 +814,7 @@ 'context': , 'entity_id': 'sensor.test_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -804,6 +827,7 @@ 'context': , 'entity_id': 'sensor.test_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -849,6 +873,7 @@ 'context': , 'entity_id': 'sensor.test_charge_cable', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -861,6 +886,7 @@ 'context': , 'entity_id': 'sensor.test_charge_cable', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -906,6 +932,7 @@ 'context': , 'entity_id': 'sensor.test_charge_energy_added', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -918,6 +945,7 @@ 'context': , 'entity_id': 'sensor.test_charge_energy_added', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -963,6 +991,7 @@ 'context': , 'entity_id': 'sensor.test_charge_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -975,6 +1004,7 @@ 'context': , 'entity_id': 'sensor.test_charge_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1020,6 +1050,7 @@ 'context': , 'entity_id': 'sensor.test_charger_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1032,6 +1063,7 @@ 'context': , 'entity_id': 'sensor.test_charger_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1077,6 +1109,7 @@ 'context': , 'entity_id': 'sensor.test_charger_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1089,6 +1122,7 @@ 'context': , 'entity_id': 'sensor.test_charger_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1134,6 +1168,7 @@ 'context': , 'entity_id': 'sensor.test_charger_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1146,6 +1181,7 @@ 'context': , 'entity_id': 'sensor.test_charger_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1191,6 +1227,7 @@ 'context': , 'entity_id': 'sensor.test_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1203,6 +1240,7 @@ 'context': , 'entity_id': 'sensor.test_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1248,6 +1286,7 @@ 'context': , 'entity_id': 'sensor.test_distance_to_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1260,6 +1299,7 @@ 'context': , 'entity_id': 'sensor.test_distance_to_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1305,6 +1345,7 @@ 'context': , 'entity_id': 'sensor.test_driver_temperature_setting', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1317,6 +1358,7 @@ 'context': , 'entity_id': 'sensor.test_driver_temperature_setting', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1362,6 +1404,7 @@ 'context': , 'entity_id': 'sensor.test_estimate_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1374,6 +1417,7 @@ 'context': , 'entity_id': 'sensor.test_estimate_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1419,6 +1463,7 @@ 'context': , 'entity_id': 'sensor.test_fast_charger_type', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1431,6 +1476,7 @@ 'context': , 'entity_id': 'sensor.test_fast_charger_type', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1476,6 +1522,7 @@ 'context': , 'entity_id': 'sensor.test_ideal_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1488,6 +1535,7 @@ 'context': , 'entity_id': 'sensor.test_ideal_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1533,6 +1581,7 @@ 'context': , 'entity_id': 'sensor.test_inside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1545,6 +1594,7 @@ 'context': , 'entity_id': 'sensor.test_inside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1590,6 +1640,7 @@ 'context': , 'entity_id': 'sensor.test_odometer', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1602,6 +1653,7 @@ 'context': , 'entity_id': 'sensor.test_odometer', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1647,6 +1699,7 @@ 'context': , 'entity_id': 'sensor.test_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1659,6 +1712,7 @@ 'context': , 'entity_id': 'sensor.test_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1704,6 +1758,7 @@ 'context': , 'entity_id': 'sensor.test_passenger_temperature_setting', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1716,6 +1771,7 @@ 'context': , 'entity_id': 'sensor.test_passenger_temperature_setting', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1761,6 +1817,7 @@ 'context': , 'entity_id': 'sensor.test_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1773,6 +1830,7 @@ 'context': , 'entity_id': 'sensor.test_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1818,6 +1876,7 @@ 'context': , 'entity_id': 'sensor.test_shift_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1830,6 +1889,7 @@ 'context': , 'entity_id': 'sensor.test_shift_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1875,6 +1935,7 @@ 'context': , 'entity_id': 'sensor.test_speed', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1887,6 +1948,7 @@ 'context': , 'entity_id': 'sensor.test_speed', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1932,6 +1994,7 @@ 'context': , 'entity_id': 'sensor.test_state_of_charge_at_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1944,6 +2007,7 @@ 'context': , 'entity_id': 'sensor.test_state_of_charge_at_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1990,6 +2054,7 @@ 'context': , 'entity_id': 'sensor.test_time_to_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2024-01-01T00:00:06+00:00', }) @@ -2003,6 +2068,7 @@ 'context': , 'entity_id': 'sensor.test_time_to_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2049,6 +2115,7 @@ 'context': , 'entity_id': 'sensor.test_time_to_full_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2062,6 +2129,7 @@ 'context': , 'entity_id': 'sensor.test_time_to_full_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -2107,6 +2175,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_front_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2119,6 +2188,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_front_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2164,6 +2234,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_front_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2176,6 +2247,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_front_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2221,6 +2293,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2233,6 +2306,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2278,6 +2352,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2290,6 +2365,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2335,6 +2411,7 @@ 'context': , 'entity_id': 'sensor.test_traffic_delay', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2347,6 +2424,7 @@ 'context': , 'entity_id': 'sensor.test_traffic_delay', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2392,6 +2470,7 @@ 'context': , 'entity_id': 'sensor.test_usable_battery_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2404,6 +2483,7 @@ 'context': , 'entity_id': 'sensor.test_usable_battery_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2449,6 +2529,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_fault_state_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2461,6 +2542,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_fault_state_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2506,6 +2588,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_fault_state_code_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2518,6 +2601,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_fault_state_code_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2574,6 +2658,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -2589,6 +2674,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -2645,6 +2731,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_power_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -2660,6 +2747,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_power_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -2705,6 +2793,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_state_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2717,6 +2806,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_state_code', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2762,6 +2852,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_state_code_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2774,6 +2865,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_state_code_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -2819,6 +2911,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_vehicle', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2831,6 +2924,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_vehicle', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2876,6 +2970,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_vehicle_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -2888,6 +2983,7 @@ 'context': , 'entity_id': 'sensor.wall_connector_vehicle_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/tessie/snapshots/test_binary_sensors.ambr b/tests/components/tessie/snapshots/test_binary_sensors.ambr index cbe7fdba5ec..854e1350234 100644 --- a/tests/components/tessie/snapshots/test_binary_sensors.ambr +++ b/tests/components/tessie/snapshots/test_binary_sensors.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'binary_sensor.test_auto_seat_climate_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -85,6 +86,7 @@ 'context': , 'entity_id': 'binary_sensor.test_auto_seat_climate_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -130,6 +132,7 @@ 'context': , 'entity_id': 'binary_sensor.test_auto_steering_wheel_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -176,6 +179,7 @@ 'context': , 'entity_id': 'binary_sensor.test_battery_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -222,6 +226,7 @@ 'context': , 'entity_id': 'binary_sensor.test_cabin_overheat_protection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -268,6 +273,7 @@ 'context': , 'entity_id': 'binary_sensor.test_cabin_overheat_protection_actively_cooling', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -314,6 +320,7 @@ 'context': , 'entity_id': 'binary_sensor.test_charge_cable', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -360,6 +367,7 @@ 'context': , 'entity_id': 'binary_sensor.test_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -406,6 +414,7 @@ 'context': , 'entity_id': 'binary_sensor.test_dashcam', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -452,6 +461,7 @@ 'context': , 'entity_id': 'binary_sensor.test_front_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -498,6 +508,7 @@ 'context': , 'entity_id': 'binary_sensor.test_front_driver_window', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -544,6 +555,7 @@ 'context': , 'entity_id': 'binary_sensor.test_front_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -590,6 +602,7 @@ 'context': , 'entity_id': 'binary_sensor.test_front_passenger_window', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -635,6 +648,7 @@ 'context': , 'entity_id': 'binary_sensor.test_preconditioning_enabled', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -681,6 +695,7 @@ 'context': , 'entity_id': 'binary_sensor.test_rear_driver_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -727,6 +742,7 @@ 'context': , 'entity_id': 'binary_sensor.test_rear_driver_window', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -773,6 +789,7 @@ 'context': , 'entity_id': 'binary_sensor.test_rear_passenger_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -819,6 +836,7 @@ 'context': , 'entity_id': 'binary_sensor.test_rear_passenger_window', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -864,6 +882,7 @@ 'context': , 'entity_id': 'binary_sensor.test_scheduled_charging_pending', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -910,6 +929,7 @@ 'context': , 'entity_id': 'binary_sensor.test_status', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -956,6 +976,7 @@ 'context': , 'entity_id': 'binary_sensor.test_tire_pressure_warning_front_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -1002,6 +1023,7 @@ 'context': , 'entity_id': 'binary_sensor.test_tire_pressure_warning_front_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -1048,6 +1070,7 @@ 'context': , 'entity_id': 'binary_sensor.test_tire_pressure_warning_rear_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -1094,6 +1117,7 @@ 'context': , 'entity_id': 'binary_sensor.test_tire_pressure_warning_rear_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -1139,6 +1163,7 @@ 'context': , 'entity_id': 'binary_sensor.test_trip_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -1185,6 +1210,7 @@ 'context': , 'entity_id': 'binary_sensor.test_user_present', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/tessie/snapshots/test_button.ambr b/tests/components/tessie/snapshots/test_button.ambr index c67c2932995..7757d1f2fea 100644 --- a/tests/components/tessie/snapshots/test_button.ambr +++ b/tests/components/tessie/snapshots/test_button.ambr @@ -40,6 +40,7 @@ 'context': , 'entity_id': 'button.test_flash_lights', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -85,6 +86,7 @@ 'context': , 'entity_id': 'button.test_homelink', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -130,6 +132,7 @@ 'context': , 'entity_id': 'button.test_honk_horn', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -175,6 +178,7 @@ 'context': , 'entity_id': 'button.test_keyless_driving', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -220,6 +224,7 @@ 'context': , 'entity_id': 'button.test_play_fart', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -265,6 +270,7 @@ 'context': , 'entity_id': 'button.test_wake', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/tessie/snapshots/test_climate.ambr b/tests/components/tessie/snapshots/test_climate.ambr index acefc953d3c..959b42cea53 100644 --- a/tests/components/tessie/snapshots/test_climate.ambr +++ b/tests/components/tessie/snapshots/test_climate.ambr @@ -69,6 +69,7 @@ 'context': , 'entity_id': 'climate.test_climate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/tessie/snapshots/test_cover.ambr b/tests/components/tessie/snapshots/test_cover.ambr index 29930677396..ff04c528244 100644 --- a/tests/components/tessie/snapshots/test_cover.ambr +++ b/tests/components/tessie/snapshots/test_cover.ambr @@ -42,6 +42,7 @@ 'context': , 'entity_id': 'cover.test_charge_port_door', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'open', }) @@ -89,6 +90,7 @@ 'context': , 'entity_id': 'cover.test_frunk', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'closed', }) @@ -136,6 +138,7 @@ 'context': , 'entity_id': 'cover.test_trunk', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'closed', }) @@ -183,6 +186,7 @@ 'context': , 'entity_id': 'cover.test_vent_windows', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'closed', }) diff --git a/tests/components/tessie/snapshots/test_device_tracker.ambr b/tests/components/tessie/snapshots/test_device_tracker.ambr index 640b91627d5..61f89db8637 100644 --- a/tests/components/tessie/snapshots/test_device_tracker.ambr +++ b/tests/components/tessie/snapshots/test_device_tracker.ambr @@ -46,6 +46,7 @@ 'context': , 'entity_id': 'device_tracker.test_location', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'not_home', }) @@ -95,6 +96,7 @@ 'context': , 'entity_id': 'device_tracker.test_route', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'not_home', }) diff --git a/tests/components/tessie/snapshots/test_lock.ambr b/tests/components/tessie/snapshots/test_lock.ambr index a01b14bf00a..1eff418b202 100644 --- a/tests/components/tessie/snapshots/test_lock.ambr +++ b/tests/components/tessie/snapshots/test_lock.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'lock.test_charge_cable_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'locked', }) @@ -87,6 +88,7 @@ 'context': , 'entity_id': 'lock.test_lock', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'locked', }) @@ -134,6 +136,7 @@ 'context': , 'entity_id': 'lock.test_speed_limit', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unlocked', }) diff --git a/tests/components/tessie/snapshots/test_media_player.ambr b/tests/components/tessie/snapshots/test_media_player.ambr index b8bf84c726c..d30e6c74aef 100644 --- a/tests/components/tessie/snapshots/test_media_player.ambr +++ b/tests/components/tessie/snapshots/test_media_player.ambr @@ -44,6 +44,7 @@ 'context': , 'entity_id': 'media_player.test_media_player', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'idle', }) @@ -59,6 +60,7 @@ 'context': , 'entity_id': 'media_player.test_media_player', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'idle', }) diff --git a/tests/components/tessie/snapshots/test_number.ambr b/tests/components/tessie/snapshots/test_number.ambr index 33abf438128..c91fb74adeb 100644 --- a/tests/components/tessie/snapshots/test_number.ambr +++ b/tests/components/tessie/snapshots/test_number.ambr @@ -51,6 +51,7 @@ 'context': , 'entity_id': 'number.test_charge_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '32', }) @@ -107,6 +108,7 @@ 'context': , 'entity_id': 'number.test_charge_limit', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }) @@ -163,6 +165,7 @@ 'context': , 'entity_id': 'number.test_speed_limit', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '74.564543', }) diff --git a/tests/components/tessie/snapshots/test_select.ambr b/tests/components/tessie/snapshots/test_select.ambr index 758bb1c91d8..fc076aabf14 100644 --- a/tests/components/tessie/snapshots/test_select.ambr +++ b/tests/components/tessie/snapshots/test_select.ambr @@ -53,6 +53,7 @@ 'context': , 'entity_id': 'select.test_seat_heater_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -111,6 +112,7 @@ 'context': , 'entity_id': 'select.test_seat_heater_rear_center', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -169,6 +171,7 @@ 'context': , 'entity_id': 'select.test_seat_heater_rear_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -227,6 +230,7 @@ 'context': , 'entity_id': 'select.test_seat_heater_rear_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -285,6 +289,7 @@ 'context': , 'entity_id': 'select.test_seat_heater_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -303,6 +308,7 @@ 'context': , 'entity_id': 'select.test_seat_heater_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'low', }) diff --git a/tests/components/tessie/snapshots/test_sensor.ambr b/tests/components/tessie/snapshots/test_sensor.ambr index b05fe3a67d9..48beab6133c 100644 --- a/tests/components/tessie/snapshots/test_sensor.ambr +++ b/tests/components/tessie/snapshots/test_sensor.ambr @@ -45,6 +45,7 @@ 'context': , 'entity_id': 'sensor.test_battery_level', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '75', }) @@ -101,6 +102,7 @@ 'context': , 'entity_id': 'sensor.test_battery_range', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '424.35182592', }) @@ -157,6 +159,7 @@ 'context': , 'entity_id': 'sensor.test_battery_range_estimate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '522.60227712', }) @@ -213,6 +216,7 @@ 'context': , 'entity_id': 'sensor.test_battery_range_ideal', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '424.35182592', }) @@ -266,6 +270,7 @@ 'context': , 'entity_id': 'sensor.test_charge_energy_added', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '18.47', }) @@ -319,6 +324,7 @@ 'context': , 'entity_id': 'sensor.test_charge_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '49.2', }) @@ -369,6 +375,7 @@ 'context': , 'entity_id': 'sensor.test_charger_current', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '32', }) @@ -419,6 +426,7 @@ 'context': , 'entity_id': 'sensor.test_charger_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '7', }) @@ -469,6 +477,7 @@ 'context': , 'entity_id': 'sensor.test_charger_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '224', }) @@ -532,6 +541,7 @@ 'context': , 'entity_id': 'sensor.test_charging', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'charging', }) @@ -577,6 +587,7 @@ 'context': , 'entity_id': 'sensor.test_destination', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Giga Texas', }) @@ -666,6 +677,7 @@ 'context': , 'entity_id': 'sensor.test_distance_to_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '75.168198', }) @@ -719,6 +731,7 @@ 'context': , 'entity_id': 'sensor.test_driver_temperature_setting', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.5', }) @@ -772,6 +785,7 @@ 'context': , 'entity_id': 'sensor.test_inside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.4', }) @@ -828,6 +842,7 @@ 'context': , 'entity_id': 'sensor.test_odometer', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8778.15941765875', }) @@ -881,6 +896,7 @@ 'context': , 'entity_id': 'sensor.test_outside_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30.5', }) @@ -934,6 +950,7 @@ 'context': , 'entity_id': 'sensor.test_passenger_temperature_setting', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '22.5', }) @@ -984,6 +1001,7 @@ 'context': , 'entity_id': 'sensor.test_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '-7', }) @@ -1043,6 +1061,7 @@ 'context': , 'entity_id': 'sensor.test_shift_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1096,6 +1115,7 @@ 'context': , 'entity_id': 'sensor.test_speed', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1146,6 +1166,7 @@ 'context': , 'entity_id': 'sensor.test_state_of_charge_at_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '65', }) @@ -1192,6 +1213,7 @@ 'context': , 'entity_id': 'sensor.test_time_to_arrival', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2024-01-01T00:59:12+00:00', }) @@ -1238,6 +1260,7 @@ 'context': , 'entity_id': 'sensor.test_time_to_full_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -1294,6 +1317,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_front_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '43.1487288094417', }) @@ -1350,6 +1374,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_front_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '43.1487288094417', }) @@ -1406,6 +1431,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_left', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '42.7861344496985', }) @@ -1462,6 +1488,7 @@ 'context': , 'entity_id': 'sensor.test_tire_pressure_rear_right', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '42.7861344496985', }) @@ -1512,6 +1539,7 @@ 'context': , 'entity_id': 'sensor.test_traffic_delay', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) diff --git a/tests/components/tessie/snapshots/test_switch.ambr b/tests/components/tessie/snapshots/test_switch.ambr index a70210f8fbf..db06e028198 100644 --- a/tests/components/tessie/snapshots/test_switch.ambr +++ b/tests/components/tessie/snapshots/test_switch.ambr @@ -41,6 +41,7 @@ 'context': , 'entity_id': 'switch.test_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -87,6 +88,7 @@ 'context': , 'entity_id': 'switch.test_defrost_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -133,6 +135,7 @@ 'context': , 'entity_id': 'switch.test_sentry_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -179,6 +182,7 @@ 'context': , 'entity_id': 'switch.test_steering_wheel_heater', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -225,6 +229,7 @@ 'context': , 'entity_id': 'switch.test_valet_mode', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -238,6 +243,7 @@ 'context': , 'entity_id': 'switch.test_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -251,6 +257,7 @@ 'context': , 'entity_id': 'switch.test_charge', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/tessie/snapshots/test_update.ambr b/tests/components/tessie/snapshots/test_update.ambr index 6690bbf8651..622cf69c7f0 100644 --- a/tests/components/tessie/snapshots/test_update.ambr +++ b/tests/components/tessie/snapshots/test_update.ambr @@ -50,6 +50,7 @@ 'context': , 'entity_id': 'update.test_update', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/tplink_omada/snapshots/test_switch.ambr b/tests/components/tplink_omada/snapshots/test_switch.ambr index 872c9b8eeff..8a08cbc292d 100644 --- a/tests/components/tplink_omada/snapshots/test_switch.ambr +++ b/tests/components/tplink_omada/snapshots/test_switch.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'switch.test_router_port_4_internet_connected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -19,6 +20,7 @@ 'context': , 'entity_id': 'switch.test_router_port_4_internet_connected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -44,6 +46,7 @@ 'context': , 'entity_id': 'switch.test_router_port_4_internet_connected', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -56,6 +59,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_1_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -101,6 +105,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_6_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -146,6 +151,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_7_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -191,6 +197,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_8_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -236,6 +243,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_2_renamed_port_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -281,6 +289,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_3_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -326,6 +335,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_4_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -371,6 +381,7 @@ 'context': , 'entity_id': 'switch.test_poe_switch_port_5_poe', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/trafikverket_train/snapshots/test_sensor.ambr b/tests/components/trafikverket_train/snapshots/test_sensor.ambr index 96a38b828f4..cae0457bbff 100644 --- a/tests/components/trafikverket_train/snapshots/test_sensor.ambr +++ b/tests/components/trafikverket_train/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T12:00:00+00:00', }) @@ -30,6 +31,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on_time', }) @@ -45,6 +47,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time_next', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T17:15:00+00:00', }) @@ -60,6 +63,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time_next_after', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T17:30:00+00:00', }) @@ -75,6 +79,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_actual_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T12:00:00+00:00', }) @@ -89,6 +94,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_other_information', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Some other info', }) @@ -104,6 +110,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time_next', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T12:15:00+00:00', }) @@ -119,6 +126,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time_next_after', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T12:30:00+00:00', }) @@ -134,6 +142,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T17:00:00+00:00', }) @@ -154,6 +163,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_state', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on_time', }) @@ -169,6 +179,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_actual_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T17:00:00+00:00', }) @@ -183,6 +194,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_other_information', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) @@ -197,6 +209,7 @@ 'context': , 'entity_id': 'sensor.stockholm_c_to_uppsala_c_departure_time_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-05-01T11:00:00+00:00', }) diff --git a/tests/components/twentemilieu/snapshots/test_calendar.ambr b/tests/components/twentemilieu/snapshots/test_calendar.ambr index d04cec1a1de..78b2d56afca 100644 --- a/tests/components/twentemilieu/snapshots/test_calendar.ambr +++ b/tests/components/twentemilieu/snapshots/test_calendar.ambr @@ -39,6 +39,7 @@ 'context': , 'entity_id': 'calendar.twente_milieu', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/twentemilieu/snapshots/test_sensor.ambr b/tests/components/twentemilieu/snapshots/test_sensor.ambr index bedad263251..a0f3b75da57 100644 --- a/tests/components/twentemilieu/snapshots/test_sensor.ambr +++ b/tests/components/twentemilieu/snapshots/test_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'sensor.twente_milieu_christmas_tree_pickup', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2022-01-06', }) @@ -84,6 +85,7 @@ 'context': , 'entity_id': 'sensor.twente_milieu_non_recyclable_waste_pickup', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2021-11-01', }) @@ -160,6 +162,7 @@ 'context': , 'entity_id': 'sensor.twente_milieu_organic_waste_pickup', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2021-11-02', }) @@ -236,6 +239,7 @@ 'context': , 'entity_id': 'sensor.twente_milieu_packages_waste_pickup', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2021-11-03', }) @@ -312,6 +316,7 @@ 'context': , 'entity_id': 'sensor.twente_milieu_paper_waste_pickup', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/uptime/snapshots/test_sensor.ambr b/tests/components/uptime/snapshots/test_sensor.ambr index d44108c2151..0e7ae6dceaa 100644 --- a/tests/components/uptime/snapshots/test_sensor.ambr +++ b/tests/components/uptime/snapshots/test_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'sensor.uptime', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2022-03-01T00:00:00+00:00', }) diff --git a/tests/components/valve/snapshots/test_init.ambr b/tests/components/valve/snapshots/test_init.ambr index b46d76b6f0c..815f902afad 100644 --- a/tests/components/valve/snapshots/test_init.ambr +++ b/tests/components/valve/snapshots/test_init.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'valve.valve', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'open', }) @@ -22,6 +23,7 @@ 'context': , 'entity_id': 'valve.valve_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'open', }) @@ -36,6 +38,7 @@ 'context': , 'entity_id': 'valve.valve', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -50,6 +53,7 @@ 'context': , 'entity_id': 'valve.valve_2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) diff --git a/tests/components/vesync/snapshots/test_diagnostics.ambr b/tests/components/vesync/snapshots/test_diagnostics.ambr index b2ae7b53cf5..fcb2cc7b286 100644 --- a/tests/components/vesync/snapshots/test_diagnostics.ambr +++ b/tests/components/vesync/snapshots/test_diagnostics.ambr @@ -207,6 +207,7 @@ }), 'entity_id': 'fan.fan', 'last_changed': str, + 'last_reported': str, 'last_updated': str, 'state': 'unavailable', }), @@ -230,6 +231,7 @@ }), 'entity_id': 'sensor.fan_air_quality', 'last_changed': str, + 'last_reported': str, 'last_updated': str, 'state': 'unavailable', }), @@ -255,6 +257,7 @@ }), 'entity_id': 'sensor.fan_filter_lifetime', 'last_changed': str, + 'last_reported': str, 'last_updated': str, 'state': 'unavailable', }), diff --git a/tests/components/vesync/snapshots/test_fan.ambr b/tests/components/vesync/snapshots/test_fan.ambr index 74c9a916880..59304e92d9d 100644 --- a/tests/components/vesync/snapshots/test_fan.ambr +++ b/tests/components/vesync/snapshots/test_fan.ambr @@ -84,6 +84,7 @@ 'context': , 'entity_id': 'fan.air_purifier_131s', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -178,6 +179,7 @@ 'context': , 'entity_id': 'fan.air_purifier_200s', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -274,6 +276,7 @@ 'context': , 'entity_id': 'fan.air_purifier_400s', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -370,6 +373,7 @@ 'context': , 'entity_id': 'fan.air_purifier_600s', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/vesync/snapshots/test_light.ambr b/tests/components/vesync/snapshots/test_light.ambr index d3a26d5cece..9990395a36c 100644 --- a/tests/components/vesync/snapshots/test_light.ambr +++ b/tests/components/vesync/snapshots/test_light.ambr @@ -226,6 +226,7 @@ 'context': , 'entity_id': 'light.dimmable_light', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -315,6 +316,7 @@ 'context': , 'entity_id': 'light.dimmer_switch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -469,6 +471,7 @@ 'context': , 'entity_id': 'light.temperature_light', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/vesync/snapshots/test_sensor.ambr b/tests/components/vesync/snapshots/test_sensor.ambr index 4caa2220760..268718fb2fe 100644 --- a/tests/components/vesync/snapshots/test_sensor.ambr +++ b/tests/components/vesync/snapshots/test_sensor.ambr @@ -107,6 +107,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_131s_air_quality', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -121,6 +122,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_131s_filter_lifetime', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -204,6 +206,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_200s_filter_lifetime', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99', }) @@ -349,6 +352,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_400s_air_quality', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -363,6 +367,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_400s_filter_lifetime', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99', }) @@ -378,6 +383,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_400s_pm2_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -523,6 +529,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_600s_air_quality', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -537,6 +544,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_600s_filter_lifetime', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '99', }) @@ -552,6 +560,7 @@ 'context': , 'entity_id': 'sensor.air_purifier_600s_pm2_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -889,6 +898,7 @@ 'context': , 'entity_id': 'sensor.outlet_current_power', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.0', }) @@ -904,6 +914,7 @@ 'context': , 'entity_id': 'sensor.outlet_current_voltage', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '120.0', }) @@ -919,6 +930,7 @@ 'context': , 'entity_id': 'sensor.outlet_energy_use_monthly', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -934,6 +946,7 @@ 'context': , 'entity_id': 'sensor.outlet_energy_use_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -949,6 +962,7 @@ 'context': , 'entity_id': 'sensor.outlet_energy_use_weekly', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -964,6 +978,7 @@ 'context': , 'entity_id': 'sensor.outlet_energy_use_yearly', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) diff --git a/tests/components/vesync/snapshots/test_switch.ambr b/tests/components/vesync/snapshots/test_switch.ambr index eb23f749152..3df26f74bcf 100644 --- a/tests/components/vesync/snapshots/test_switch.ambr +++ b/tests/components/vesync/snapshots/test_switch.ambr @@ -306,6 +306,7 @@ 'context': , 'entity_id': 'switch.outlet', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -421,6 +422,7 @@ 'context': , 'entity_id': 'switch.wall_switch', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/vesync/test_diagnostics.py b/tests/components/vesync/test_diagnostics.py index 22968c79730..04696f01631 100644 --- a/tests/components/vesync/test_diagnostics.py +++ b/tests/components/vesync/test_diagnostics.py @@ -93,10 +93,13 @@ async def test_async_get_device_diagnostics__single_fan( matcher=path_type( { "home_assistant.entities.0.state.last_changed": (str,), + "home_assistant.entities.0.state.last_reported": (str,), "home_assistant.entities.0.state.last_updated": (str,), "home_assistant.entities.1.state.last_changed": (str,), + "home_assistant.entities.1.state.last_reported": (str,), "home_assistant.entities.1.state.last_updated": (str,), "home_assistant.entities.2.state.last_changed": (str,), + "home_assistant.entities.2.state.last_reported": (str,), "home_assistant.entities.2.state.last_updated": (str,), } ) diff --git a/tests/components/vicare/snapshots/test_binary_sensor.ambr b/tests/components/vicare/snapshots/test_binary_sensor.ambr index 002031a7491..7454f914435 100644 --- a/tests/components/vicare/snapshots/test_binary_sensor.ambr +++ b/tests/components/vicare/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.model0_burner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -21,6 +22,7 @@ 'context': , 'entity_id': 'binary_sensor.model0_circulation_pump', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -33,6 +35,7 @@ 'context': , 'entity_id': 'binary_sensor.model0_frost_protection', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) diff --git a/tests/components/waqi/snapshots/test_sensor.ambr b/tests/components/waqi/snapshots/test_sensor.ambr index 029b36b3c16..f476514a6c7 100644 --- a/tests/components/waqi/snapshots/test_sensor.ambr +++ b/tests/components/waqi/snapshots/test_sensor.ambr @@ -20,6 +20,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_air_quality_index', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '29', }) @@ -36,6 +37,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_humidity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }) @@ -50,6 +52,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_visbility_using_nephelometry', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '80', }) @@ -73,6 +76,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_dominant_pollutant', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'o3', }) @@ -89,6 +93,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1008.8', }) @@ -105,6 +110,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '16', }) @@ -119,6 +125,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_carbon_monoxide', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.3', }) @@ -133,6 +140,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_nitrogen_dioxide', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.3', }) @@ -147,6 +155,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_ozone', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '29.4', }) @@ -161,6 +170,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_sulphur_dioxide', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2.3', }) @@ -175,6 +185,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_pm10', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '12', }) @@ -189,6 +200,7 @@ 'context': , 'entity_id': 'sensor.de_jongweg_utrecht_pm2_5', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '17', }) diff --git a/tests/components/webmin/snapshots/test_sensor.ambr b/tests/components/webmin/snapshots/test_sensor.ambr index 93f8bcc3709..1813dd354d3 100644 --- a/tests/components/webmin/snapshots/test_sensor.ambr +++ b/tests/components/webmin/snapshots/test_sensor.ambr @@ -43,6 +43,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_load_15m', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.37', }) @@ -91,6 +92,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_load_1m', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.29', }) @@ -139,6 +141,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_load_5m', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.36', }) @@ -195,6 +198,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_memory_free', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '27.2087860107422', }) @@ -251,6 +255,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_memory_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '31.248420715332', }) @@ -307,6 +312,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_swap_free', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.85430908203125', }) @@ -363,6 +369,7 @@ 'context': , 'entity_id': 'sensor.192_168_1_1_swap_total', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1.86260986328125', }) diff --git a/tests/components/whois/snapshots/test_sensor.ambr b/tests/components/whois/snapshots/test_sensor.ambr index 52909edf0b1..61762c36e59 100644 --- a/tests/components/whois/snapshots/test_sensor.ambr +++ b/tests/components/whois/snapshots/test_sensor.ambr @@ -7,6 +7,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_admin', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'admin@example.com', }) @@ -83,6 +84,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_created', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2019-01-01T00:00:00+00:00', }) @@ -163,6 +165,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_days_until_expiration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '364', }) @@ -239,6 +242,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_expires', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2023-01-01T00:00:00+00:00', }) @@ -315,6 +319,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_last_updated', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2021-12-31T23:00:00+00:00', }) @@ -390,6 +395,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_owner', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'owner@example.com', }) @@ -465,6 +471,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_registrant', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'registrant@example.com', }) @@ -540,6 +547,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_registrar', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'My Registrar', }) @@ -615,6 +623,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_reseller', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Top Domains, Low Prices', }) @@ -691,6 +700,7 @@ 'context': , 'entity_id': 'sensor.home_assistant_io_last_updated', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2021-12-31T23:00:00+00:00', }) diff --git a/tests/components/withings/snapshots/test_sensor.ambr b/tests/components/withings/snapshots/test_sensor.ambr index aeaa371b0e5..37635ece403 100644 --- a/tests/components/withings/snapshots/test_sensor.ambr +++ b/tests/components/withings/snapshots/test_sensor.ambr @@ -48,6 +48,7 @@ 'context': , 'entity_id': 'sensor.henk_active_calories_burnt_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '221.132', }) @@ -102,6 +103,7 @@ 'context': , 'entity_id': 'sensor.henk_active_time_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.530', }) @@ -151,6 +153,7 @@ 'context': , 'entity_id': 'sensor.henk_average_heart_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '103', }) @@ -200,6 +203,7 @@ 'context': , 'entity_id': 'sensor.henk_average_respiratory_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '14', }) @@ -250,6 +254,7 @@ 'context': , 'entity_id': 'sensor.henk_body_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40', }) @@ -303,6 +308,7 @@ 'context': , 'entity_id': 'sensor.henk_bone_mass', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -351,6 +357,7 @@ 'context': , 'entity_id': 'sensor.henk_breathing_disturbances_intensity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '9', }) @@ -400,6 +407,7 @@ 'context': , 'entity_id': 'sensor.henk_calories_burnt_last_workout', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '24', }) @@ -450,6 +458,7 @@ 'context': , 'entity_id': 'sensor.henk_deep_sleep', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5820', }) @@ -499,6 +508,7 @@ 'context': , 'entity_id': 'sensor.henk_diastolic_blood_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }) @@ -549,6 +559,7 @@ 'context': , 'entity_id': 'sensor.henk_distance_travelled_last_workout', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '232', }) @@ -603,6 +614,7 @@ 'context': , 'entity_id': 'sensor.henk_distance_travelled_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1020.121', }) @@ -649,6 +661,7 @@ 'context': , 'entity_id': 'sensor.henk_electrodermal_activity_feet', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '102', }) @@ -695,6 +708,7 @@ 'context': , 'entity_id': 'sensor.henk_electrodermal_activity_left_foot', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '102', }) @@ -741,6 +755,7 @@ 'context': , 'entity_id': 'sensor.henk_electrodermal_activity_right_foot', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '102', }) @@ -788,6 +803,7 @@ 'context': , 'entity_id': 'sensor.henk_elevation_change_last_workout', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4', }) @@ -839,6 +855,7 @@ 'context': , 'entity_id': 'sensor.henk_elevation_change_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -889,6 +906,7 @@ 'context': , 'entity_id': 'sensor.henk_extracellular_water', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -942,6 +960,7 @@ 'context': , 'entity_id': 'sensor.henk_fat_free_mass', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }) @@ -995,6 +1014,7 @@ 'context': , 'entity_id': 'sensor.henk_fat_mass', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '5', }) @@ -1047,6 +1067,7 @@ 'context': , 'entity_id': 'sensor.henk_fat_ratio', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.07', }) @@ -1096,6 +1117,7 @@ 'context': , 'entity_id': 'sensor.henk_heart_pulse', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '60', }) @@ -1149,6 +1171,7 @@ 'context': , 'entity_id': 'sensor.henk_height', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2', }) @@ -1199,6 +1222,7 @@ 'context': , 'entity_id': 'sensor.henk_hydration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.95', }) @@ -1253,6 +1277,7 @@ 'context': , 'entity_id': 'sensor.henk_intense_activity_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '7.0', }) @@ -1303,6 +1328,7 @@ 'context': , 'entity_id': 'sensor.henk_intracellular_water', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -1353,6 +1379,7 @@ 'context': , 'entity_id': 'sensor.henk_last_workout_duration', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '4.25', }) @@ -1398,6 +1425,7 @@ 'context': , 'entity_id': 'sensor.henk_last_workout_intensity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '30', }) @@ -1547,6 +1575,7 @@ 'context': , 'entity_id': 'sensor.henk_last_workout_type', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'walk', }) @@ -1597,6 +1626,7 @@ 'context': , 'entity_id': 'sensor.henk_light_sleep', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10440', }) @@ -1646,6 +1676,7 @@ 'context': , 'entity_id': 'sensor.henk_maximum_heart_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '120', }) @@ -1695,6 +1726,7 @@ 'context': , 'entity_id': 'sensor.henk_maximum_respiratory_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20', }) @@ -1744,6 +1776,7 @@ 'context': , 'entity_id': 'sensor.henk_minimum_heart_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }) @@ -1793,6 +1826,7 @@ 'context': , 'entity_id': 'sensor.henk_minimum_respiratory_rate', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10', }) @@ -1847,6 +1881,7 @@ 'context': , 'entity_id': 'sensor.henk_moderate_activity_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '24.8', }) @@ -1900,6 +1935,7 @@ 'context': , 'entity_id': 'sensor.henk_muscle_mass', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '50', }) @@ -1950,6 +1986,7 @@ 'context': , 'entity_id': 'sensor.henk_pause_during_last_workout', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.0', }) @@ -2000,6 +2037,7 @@ 'context': , 'entity_id': 'sensor.henk_pulse_wave_velocity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -2050,6 +2088,7 @@ 'context': , 'entity_id': 'sensor.henk_rem_sleep', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2400', }) @@ -2100,6 +2139,7 @@ 'context': , 'entity_id': 'sensor.henk_skin_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '20', }) @@ -2153,6 +2193,7 @@ 'context': , 'entity_id': 'sensor.henk_sleep_goal', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '8.000', }) @@ -2202,6 +2243,7 @@ 'context': , 'entity_id': 'sensor.henk_sleep_score', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '37', }) @@ -2250,6 +2292,7 @@ 'context': , 'entity_id': 'sensor.henk_snoring', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1080', }) @@ -2298,6 +2341,7 @@ 'context': , 'entity_id': 'sensor.henk_snoring_episode_count', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '18', }) @@ -2352,6 +2396,7 @@ 'context': , 'entity_id': 'sensor.henk_soft_activity_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '25.3', }) @@ -2401,6 +2446,7 @@ 'context': , 'entity_id': 'sensor.henk_spo2', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0.95', }) @@ -2450,6 +2496,7 @@ 'context': , 'entity_id': 'sensor.henk_step_goal', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '10000', }) @@ -2500,6 +2547,7 @@ 'context': , 'entity_id': 'sensor.henk_steps_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1155', }) @@ -2549,6 +2597,7 @@ 'context': , 'entity_id': 'sensor.henk_systolic_blood_pressure', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -2599,6 +2648,7 @@ 'context': , 'entity_id': 'sensor.henk_temperature', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '40', }) @@ -2649,6 +2699,7 @@ 'context': , 'entity_id': 'sensor.henk_time_to_sleep', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '540', }) @@ -2699,6 +2750,7 @@ 'context': , 'entity_id': 'sensor.henk_time_to_wakeup', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1140', }) @@ -2752,6 +2804,7 @@ 'context': , 'entity_id': 'sensor.henk_total_calories_burnt_today', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2444.149', }) @@ -2797,6 +2850,7 @@ 'context': , 'entity_id': 'sensor.henk_vascular_age', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -2842,6 +2896,7 @@ 'context': , 'entity_id': 'sensor.henk_visceral_fat_index', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '102', }) @@ -2891,6 +2946,7 @@ 'context': , 'entity_id': 'sensor.henk_vo2_max', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '100', }) @@ -2940,6 +2996,7 @@ 'context': , 'entity_id': 'sensor.henk_wakeup_count', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '1', }) @@ -2990,6 +3047,7 @@ 'context': , 'entity_id': 'sensor.henk_wakeup_time', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '3060', }) @@ -3043,6 +3101,7 @@ 'context': , 'entity_id': 'sensor.henk_weight', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70', }) @@ -3093,6 +3152,7 @@ 'context': , 'entity_id': 'sensor.henk_weight_goal', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '70.5', }) diff --git a/tests/components/wled/snapshots/test_binary_sensor.ambr b/tests/components/wled/snapshots/test_binary_sensor.ambr index a2d3176cec7..b9a083336d2 100644 --- a/tests/components/wled/snapshots/test_binary_sensor.ambr +++ b/tests/components/wled/snapshots/test_binary_sensor.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'binary_sensor.wled_rgb_light_firmware', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) diff --git a/tests/components/wled/snapshots/test_button.ambr b/tests/components/wled/snapshots/test_button.ambr index e004db77e25..b489bcc0a71 100644 --- a/tests/components/wled/snapshots/test_button.ambr +++ b/tests/components/wled/snapshots/test_button.ambr @@ -8,6 +8,7 @@ 'context': , 'entity_id': 'button.wled_rgb_light_restart', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unknown', }) diff --git a/tests/components/wled/snapshots/test_number.ambr b/tests/components/wled/snapshots/test_number.ambr index 97555c7e850..c3440108148 100644 --- a/tests/components/wled/snapshots/test_number.ambr +++ b/tests/components/wled/snapshots/test_number.ambr @@ -11,6 +11,7 @@ 'context': , 'entity_id': 'number.wled_rgb_light_segment_1_intensity', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '64', }) @@ -99,6 +100,7 @@ 'context': , 'entity_id': 'number.wled_rgb_light_segment_1_speed', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '16', }) diff --git a/tests/components/wled/snapshots/test_select.ambr b/tests/components/wled/snapshots/test_select.ambr index 9881b8f0a00..6d64ec43658 100644 --- a/tests/components/wled/snapshots/test_select.ambr +++ b/tests/components/wled/snapshots/test_select.ambr @@ -12,6 +12,7 @@ 'context': , 'entity_id': 'select.wled_rgb_light_live_override', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '0', }) @@ -149,6 +150,7 @@ 'context': , 'entity_id': 'select.wled_rgb_light_segment_1_color_palette', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Random Cycle', }) @@ -285,6 +287,7 @@ 'context': , 'entity_id': 'select.wled_rgbw_light_playlist', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Playlist 1', }) @@ -373,6 +376,7 @@ 'context': , 'entity_id': 'select.wled_rgbw_light_preset', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'Preset 1', }) diff --git a/tests/components/wled/snapshots/test_switch.ambr b/tests/components/wled/snapshots/test_switch.ambr index fa2e004f994..da69e686f07 100644 --- a/tests/components/wled/snapshots/test_switch.ambr +++ b/tests/components/wled/snapshots/test_switch.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'switch.wled_rgb_light_nightlight', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -89,6 +90,7 @@ 'context': , 'entity_id': 'switch.wled_rgb_light_reverse', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) @@ -169,6 +171,7 @@ 'context': , 'entity_id': 'switch.wled_rgb_light_sync_receive', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'on', }) @@ -249,6 +252,7 @@ 'context': , 'entity_id': 'switch.wled_rgb_light_sync_send', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'off', }) diff --git a/tests/components/youtube/snapshots/test_sensor.ambr b/tests/components/youtube/snapshots/test_sensor.ambr index bea70279dd3..cddfa6f6a3d 100644 --- a/tests/components/youtube/snapshots/test_sensor.ambr +++ b/tests/components/youtube/snapshots/test_sensor.ambr @@ -10,6 +10,7 @@ 'context': , 'entity_id': 'sensor.google_for_developers_latest_upload', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': "What's new in Google Home in less than 1 minute", }) @@ -24,6 +25,7 @@ 'context': , 'entity_id': 'sensor.google_for_developers_subscribers', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2290000', }) @@ -36,6 +38,7 @@ 'context': , 'entity_id': 'sensor.google_for_developers_latest_upload', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': 'unavailable', }) @@ -50,6 +53,7 @@ 'context': , 'entity_id': 'sensor.google_for_developers_subscribers', 'last_changed': , + 'last_reported': , 'last_updated': , 'state': '2290000', }) diff --git a/tests/syrupy.py b/tests/syrupy.py index d489724a940..e5bbf017bb3 100644 --- a/tests/syrupy.py +++ b/tests/syrupy.py @@ -198,6 +198,7 @@ class HomeAssistantSnapshotSerializer(AmberDataSerializer): | { "context": ANY, "last_changed": ANY, + "last_reported": ANY, "last_updated": ANY, } ) diff --git a/tests/test_core.py b/tests/test_core.py index 5b385700b00..b60e6b832ce 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -34,6 +34,7 @@ from homeassistant.const import ( EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_STATE_CHANGED, + EVENT_STATE_REPORTED, MATCH_ALL, __version__, ) @@ -930,8 +931,9 @@ def test_state_as_dict() -> None: "happy.happy", "on", {"pig": "dog"}, - last_updated=last_time, last_changed=last_time, + last_reported=last_time, + last_updated=last_time, ) expected = { "context": { @@ -942,6 +944,7 @@ def test_state_as_dict() -> None: "entity_id": "happy.happy", "attributes": {"pig": "dog"}, "last_changed": last_time.isoformat(), + "last_reported": last_time.isoformat(), "last_updated": last_time.isoformat(), "state": "on", } @@ -962,13 +965,15 @@ def test_state_as_dict_json() -> None: "happy.happy", "on", {"pig": "dog"}, - last_updated=last_time, - last_changed=last_time, context=ha.Context(id="01H0D6K3RFJAYAV2093ZW30PCW"), + last_changed=last_time, + last_reported=last_time, + last_updated=last_time, ) expected = ( b'{"entity_id":"happy.happy","state":"on","attributes":{"pig":"dog"},' - b'"last_changed":"1984-12-08T12:00:00","last_updated":"1984-12-08T12:00:00",' + b'"last_changed":"1984-12-08T12:00:00","last_reported":"1984-12-08T12:00:00",' + b'"last_updated":"1984-12-08T12:00:00",' b'"context":{"id":"01H0D6K3RFJAYAV2093ZW30PCW","parent_id":null,"user_id":null}}' ) as_dict_json_1 = state.as_dict_json @@ -986,9 +991,10 @@ def test_state_json_fragment() -> None: "happy.happy", "on", {"pig": "dog"}, - last_updated=last_time, - last_changed=last_time, context=ha.Context(id="01H0D6K3RFJAYAV2093ZW30PCW"), + last_changed=last_time, + last_reported=last_time, + last_updated=last_time, ) for _ in range(2) ) @@ -1386,7 +1392,7 @@ def test_state_repr() -> None: "happy.happy", "on", {"brightness": 144}, - datetime(1984, 12, 8, 12, 0, 0), + last_changed=datetime(1984, 12, 8, 12, 0, 0), ) ) == "" @@ -2775,11 +2781,14 @@ def test_state_timestamps() -> None: "on", {"brightness": 100}, last_changed=now, + last_reported=now, last_updated=now, context=ha.Context(id="1234"), ) assert state.last_changed_timestamp == now.timestamp() assert state.last_changed_timestamp == now.timestamp() + assert state.last_reported_timestamp == now.timestamp() + assert state.last_reported_timestamp == now.timestamp() assert state.last_updated_timestamp == now.timestamp() assert state.last_updated_timestamp == now.timestamp() @@ -3220,3 +3229,30 @@ async def test_eventbus_lazy_object_creation(hass: HomeAssistant) -> None: assert len(calls) == 1 unsub() + + +async def test_statemachine_report_state(hass: HomeAssistant) -> None: + """Test report state event.""" + hass.states.async_set("light.bowl", "on", {}) + state_changed_events = async_capture_events(hass, EVENT_STATE_CHANGED) + state_reported_events = async_capture_events(hass, EVENT_STATE_REPORTED) + + hass.states.async_set("light.bowl", "on") + await hass.async_block_till_done() + assert len(state_changed_events) == 0 + assert len(state_reported_events) == 1 + + hass.states.async_set("light.bowl", "on", None, True) + await hass.async_block_till_done() + assert len(state_changed_events) == 1 + assert len(state_reported_events) == 2 + + hass.states.async_set("light.bowl", "off") + await hass.async_block_till_done() + assert len(state_changed_events) == 2 + assert len(state_reported_events) == 3 + + hass.states.async_remove("light.bowl") + await hass.async_block_till_done() + assert len(state_changed_events) == 3 + assert len(state_reported_events) == 4 From cf8455336c6e17742f141d560e1ece70ee1c3565 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 10:09:42 -1000 Subject: [PATCH 1329/1691] Small cleanup to unifiprotect subscriptions (#113901) --- homeassistant/components/unifiprotect/data.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 2a4f83b668e..c0a6d65ff7a 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -4,6 +4,7 @@ from __future__ import annotations from collections.abc import Callable, Generator, Iterable from datetime import datetime, timedelta +from functools import partial import logging from typing import Any, cast @@ -280,11 +281,7 @@ class ProtectData: self._hass, self._async_poll, self._update_interval ) self._subscriptions.setdefault(mac, []).append(update_callback) - - def _unsubscribe() -> None: - self.async_unsubscribe_device_id(mac, update_callback) - - return _unsubscribe + return partial(self.async_unsubscribe_device_id, mac, update_callback) @callback def async_unsubscribe_device_id( @@ -301,12 +298,10 @@ class ProtectData: @callback def _async_signal_device_update(self, device: ProtectDeviceType) -> None: """Call the callbacks for a device_id.""" - - if not self._subscriptions.get(device.mac): + if not (subscriptions := self._subscriptions.get(device.mac)): return - _LOGGER.debug("Updating device: %s (%s)", device.name, device.mac) - for update_callback in self._subscriptions[device.mac]: + for update_callback in subscriptions: update_callback(device) From ac175a42401c815b40cad34a13a0383c61f90ccc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 10:39:31 -1000 Subject: [PATCH 1330/1691] Fix flakey test_reading_yaml_config test (#113902) --- tests/components/device_tracker/test_init.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index b77ab1c5f9b..c2b2075468d 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -117,11 +117,21 @@ async def test_reading_yaml_config( await hass.async_add_executor_job( legacy.update_config, yaml_devices, dev_id, device ) - assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) - config = (await legacy.async_load_config(yaml_devices, hass, device.consider_home))[ - 0 - ] - await hass.async_block_till_done() + loaded_config = None + original_async_load_config = legacy.async_load_config + + async def capture_load_config(*args, **kwargs): + nonlocal loaded_config + loaded_config = await original_async_load_config(*args, **kwargs) + return loaded_config + + with patch( + "homeassistant.components.device_tracker.legacy.async_load_config", + capture_load_config, + ): + assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() + config = loaded_config[0] assert device.dev_id == config.dev_id assert device.track == config.track assert device.mac == config.mac From 267fe3dc3458aad832b61f42d531b180f4aba79a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 12:06:40 -1000 Subject: [PATCH 1331/1691] Fix system_info importing hassio in the event loop (#113903) --- homeassistant/helpers/system_info.py | 14 +++++++++++--- tests/helpers/test_system_info.py | 7 ++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index a01c3aad377..ec8badaddc3 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -7,13 +7,15 @@ from getpass import getuser import logging import os import platform -from typing import Any +from typing import TYPE_CHECKING, Any from homeassistant.const import __version__ as current_version from homeassistant.core import HomeAssistant from homeassistant.loader import bind_hass from homeassistant.util.package import is_docker_env, is_virtual_env +from .importlib import async_import_module + _LOGGER = logging.getLogger(__name__) @@ -32,8 +34,14 @@ cached_get_user = cache(getuser) async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]: """Return info about the system.""" # Local import to avoid circular dependencies - # pylint: disable-next=import-outside-toplevel - from homeassistant.components import hassio + # We use the import helper because hassio + # may not be loaded yet and we don't want to + # do blocking I/O in the event loop to import it. + if TYPE_CHECKING: + # pylint: disable-next=import-outside-toplevel + from homeassistant.components import hassio + else: + hassio = await async_import_module(hass, "homeassistant.components.hassio") is_hassio = hassio.is_hassio(hass) diff --git a/tests/helpers/test_system_info.py b/tests/helpers/test_system_info.py index 1b3f26ee763..a7f77bd5cfb 100644 --- a/tests/helpers/test_system_info.py +++ b/tests/helpers/test_system_info.py @@ -6,6 +6,7 @@ from unittest.mock import patch import pytest +from homeassistant.components import hassio from homeassistant.const import __version__ as current_version from homeassistant.core import HomeAssistant from homeassistant.helpers.system_info import async_get_system_info, is_official_image @@ -39,8 +40,8 @@ async def test_get_system_info_supervisor_not_available( "homeassistant.helpers.system_info.is_docker_env", return_value=True ), patch( "homeassistant.helpers.system_info.is_official_image", return_value=True - ), patch("homeassistant.components.hassio.is_hassio", return_value=True), patch( - "homeassistant.components.hassio.get_info", return_value=None + ), patch.object(hassio, "is_hassio", return_value=True), patch.object( + hassio, "get_info", return_value=None ), patch("homeassistant.helpers.system_info.cached_get_user", return_value="root"): info = await async_get_system_info(hass) assert isinstance(info, dict) @@ -57,7 +58,7 @@ async def test_get_system_info_supervisor_not_loaded(hass: HomeAssistant) -> Non "homeassistant.helpers.system_info.is_docker_env", return_value=True ), patch( "homeassistant.helpers.system_info.is_official_image", return_value=True - ), patch("homeassistant.components.hassio.get_info", return_value=None), patch.dict( + ), patch.object(hassio, "get_info", return_value=None), patch.dict( os.environ, {"SUPERVISOR": "127.0.0.1"} ): info = await async_get_system_info(hass) From e9c1753f3a9c35a3befd13f604bfc2e03d6f0df4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 13:35:01 -1000 Subject: [PATCH 1332/1691] Cache parsing the url for the device registry (#113910) * Cache parsing the url for the device registry There are lots of hub integrations that use the same url for every sub-device which results in a lot of url parsing at startup. The logic can be simplified quite a bit here by only using yarl for URLs * fix onvif --- homeassistant/helpers/device_registry.py | 23 +++++++++++------------ tests/components/onvif/__init__.py | 1 + 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index e31c372c18e..9666ad302ad 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -9,7 +9,6 @@ from functools import lru_cache, partial import logging import time from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar, cast -from urllib.parse import urlparse import attr from yarl import URL @@ -212,22 +211,22 @@ def _validate_device_info( return device_info_type +_cached_parse_url = lru_cache(maxsize=512)(URL) +"""Parse a URL and cache the result.""" + + def _validate_configuration_url(value: Any) -> str | None: """Validate and convert configuration_url.""" if value is None: return None - if ( - isinstance(value, URL) - and (value.scheme not in CONFIGURATION_URL_SCHEMES or not value.host) - ) or ( - (parsed_url := urlparse(str(value))) - and ( - parsed_url.scheme not in CONFIGURATION_URL_SCHEMES - or not parsed_url.hostname - ) - ): + + url_as_str = str(value) + url = value if type(value) is URL else _cached_parse_url(url_as_str) + + if url.scheme not in CONFIGURATION_URL_SCHEMES or not url.host: raise ValueError(f"invalid configuration_url '{value}'") - return str(value) + + return url_as_str @attr.s(frozen=True) diff --git a/tests/components/onvif/__init__.py b/tests/components/onvif/__init__.py index 5164939eead..fea27f501e8 100644 --- a/tests/components/onvif/__init__.py +++ b/tests/components/onvif/__init__.py @@ -125,6 +125,7 @@ def setup_mock_onvif_camera( def setup_mock_device(mock_device, capabilities=None): """Prepare mock ONVIFDevice.""" mock_device.async_setup = AsyncMock(return_value=True) + mock_device.port = 80 mock_device.available = True mock_device.name = NAME mock_device.info = DeviceInfo( From 1e545950845fb486729ed0a5023cc8883430f513 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 13:40:23 -1000 Subject: [PATCH 1333/1691] Use faster time compare in history and live logbook (#113897) --- homeassistant/components/history/websocket_api.py | 5 ++++- homeassistant/components/logbook/websocket_api.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/history/websocket_api.py b/homeassistant/components/history/websocket_api.py index 27e490dd8d4..462d8464229 100644 --- a/homeassistant/components/history/websocket_api.py +++ b/homeassistant/components/history/websocket_api.py @@ -331,11 +331,14 @@ async def _async_events_consumer( no_attributes: bool, ) -> None: """Stream events from the queue.""" + subscriptions_setup_complete_timestamp = ( + subscriptions_setup_complete_time.timestamp() + ) while True: events: list[Event] = [await stream_queue.get()] # If the event is older than the last db # event we already sent it so we skip it. - if events[0].time_fired <= subscriptions_setup_complete_time: + if events[0].time_fired_timestamp <= subscriptions_setup_complete_timestamp: continue # We sleep for the EVENT_COALESCE_TIME so # we can group events together to minimize diff --git a/homeassistant/components/logbook/websocket_api.py b/homeassistant/components/logbook/websocket_api.py index b7555d91241..cac58971cde 100644 --- a/homeassistant/components/logbook/websocket_api.py +++ b/homeassistant/components/logbook/websocket_api.py @@ -222,11 +222,14 @@ async def _async_events_consumer( event_processor: EventProcessor, ) -> None: """Stream events from the queue.""" + subscriptions_setup_complete_timestamp = ( + subscriptions_setup_complete_time.timestamp() + ) while True: events: list[Event] = [await stream_queue.get()] # If the event is older than the last db # event we already sent it so we skip it. - if events[0].time_fired <= subscriptions_setup_complete_time: + if events[0].time_fired_timestamp <= subscriptions_setup_complete_timestamp: continue # We sleep for the EVENT_COALESCE_TIME so # we can group events together to minimize From b311fe2a0f6ff6de263cf5fb0fbef623d3304b0c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 13:41:24 -1000 Subject: [PATCH 1334/1691] Reduce overhead to clear cache in button state (#113895) Same optimization as #113136 --- homeassistant/components/button/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/button/__init__.py b/homeassistant/components/button/__init__.py index 6826f681d4d..10589aa461f 100644 --- a/homeassistant/components/button/__init__.py +++ b/homeassistant/components/button/__init__.py @@ -123,10 +123,8 @@ class ButtonEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_ def __set_state(self, state: str | None) -> None: """Set the entity state.""" - try: # noqa: SIM105 suppress is much slower - del self.state - except AttributeError: - pass + # Invalidate the cache of the cached property + self.__dict__.pop("state", None) self.__last_pressed_isoformat = state @final From b57422024704c42a67034fc394e0cfe8a654a9df Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 13:49:37 -1000 Subject: [PATCH 1335/1691] Refactor rate limit helper to track time in seconds (#113898) * Refactor rate limit helper to track time in seconds Currently we created datetime and timedelta objects to enforce the rate limit. When the rate limit was being hit hard, this got expensive. We now use floats everywhere instead as they are much cheaper which is important when we are running up against a rate limit, which is by definition a hot path The rate limit helper is currently only used for templates and we do not have any code in the code base that directly passes in a rate limit so the impact to custom components is expected to be negligible if any * misesd two --- homeassistant/helpers/event.py | 10 ++--- homeassistant/helpers/ratelimit.py | 17 ++++---- homeassistant/helpers/template.py | 6 +-- tests/components/template/test_init.py | 2 +- tests/components/template/test_sensor.py | 2 +- tests/helpers/test_event.py | 50 ++++++++++++------------ tests/helpers/test_ratelimit.py | 34 +++++----------- 7 files changed, 53 insertions(+), 68 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index dd9c038340e..749c6d3e6e4 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -141,7 +141,7 @@ class TrackTemplate: template: Template variables: TemplateVarsType - rate_limit: timedelta | None = None + rate_limit: float | None = None @dataclass(slots=True) @@ -1077,7 +1077,7 @@ class TrackTemplateResultInfo: def _render_template_if_ready( self, track_template_: TrackTemplate, - now: datetime, + now: float, event: Event[EventStateChangedData] | None, ) -> bool | TrackTemplateResult: """Re-render the template if conditions match. @@ -1185,7 +1185,7 @@ class TrackTemplateResultInfo: """ updates: list[TrackTemplateResult] = [] info_changed = False - now = event.time_fired if not replayed and event else dt_util.utcnow() + now = event.time_fired_timestamp if not replayed and event else time.time() block_updates = False super_template = self._track_templates[0] if self._has_super_template else None @@ -1927,7 +1927,7 @@ def _rate_limit_for_event( event: Event[EventStateChangedData], info: RenderInfo, track_template_: TrackTemplate, -) -> timedelta | None: +) -> float | None: """Determine the rate limit for an event.""" # Specifically referenced entities are excluded # from the rate limit @@ -1937,7 +1937,7 @@ def _rate_limit_for_event( if track_template_.rate_limit is not None: return track_template_.rate_limit - rate_limit: timedelta | None = info.rate_limit + rate_limit: float | None = info.rate_limit return rate_limit diff --git a/homeassistant/helpers/ratelimit.py b/homeassistant/helpers/ratelimit.py index 430ec906bdb..516d4134f76 100644 --- a/homeassistant/helpers/ratelimit.py +++ b/homeassistant/helpers/ratelimit.py @@ -4,12 +4,11 @@ from __future__ import annotations import asyncio from collections.abc import Callable, Hashable -from datetime import datetime, timedelta import logging +import time from typing import TypeVarTuple from homeassistant.core import HomeAssistant, callback -import homeassistant.util.dt as dt_util _Ts = TypeVarTuple("_Ts") @@ -25,7 +24,7 @@ class KeyedRateLimit: ) -> None: """Initialize ratelimit tracker.""" self.hass = hass - self._last_triggered: dict[Hashable, datetime] = {} + self._last_triggered: dict[Hashable, float] = {} self._rate_limit_timers: dict[Hashable, asyncio.TimerHandle] = {} @callback @@ -34,10 +33,10 @@ class KeyedRateLimit: return bool(self._rate_limit_timers and key in self._rate_limit_timers) @callback - def async_triggered(self, key: Hashable, now: datetime | None = None) -> None: + def async_triggered(self, key: Hashable, now: float | None = None) -> None: """Call when the action we are tracking was triggered.""" self.async_cancel_timer(key) - self._last_triggered[key] = now or dt_util.utcnow() + self._last_triggered[key] = now or time.time() @callback def async_cancel_timer(self, key: Hashable) -> None: @@ -58,11 +57,11 @@ class KeyedRateLimit: def async_schedule_action( self, key: Hashable, - rate_limit: timedelta | None, - now: datetime, + rate_limit: float | None, + now: float, action: Callable[[*_Ts], None], *args: *_Ts, - ) -> datetime | None: + ) -> float | None: """Check rate limits and schedule an action if we hit the limit. If the rate limit is hit: @@ -97,7 +96,7 @@ class KeyedRateLimit: if key not in self._rate_limit_timers: self._rate_limit_timers[key] = self.hass.loop.call_later( - (next_call_time - now).total_seconds(), + next_call_time - now, action, *args, ) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 36d481d9e29..bb0d868bf3c 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -131,8 +131,8 @@ _T = TypeVar("_T") _R = TypeVar("_R") _P = ParamSpec("_P") -ALL_STATES_RATE_LIMIT = timedelta(minutes=1) -DOMAIN_STATES_RATE_LIMIT = timedelta(seconds=1) +ALL_STATES_RATE_LIMIT = 60 # seconds +DOMAIN_STATES_RATE_LIMIT = 1 # seconds _render_info: ContextVar[RenderInfo | None] = ContextVar("_render_info", default=None) @@ -374,7 +374,7 @@ class RenderInfo: self.domains: collections.abc.Set[str] = set() self.domains_lifecycle: collections.abc.Set[str] = set() self.entities: collections.abc.Set[str] = set() - self.rate_limit: timedelta | None = None + self.rate_limit: float | None = None self.has_time = False def __repr__(self) -> str: diff --git a/tests/components/template/test_init.py b/tests/components/template/test_init.py index ed0426b7e0e..991228623b1 100644 --- a/tests/components/template/test_init.py +++ b/tests/components/template/test_init.py @@ -248,7 +248,7 @@ async def test_reload_sensors_that_reference_other_template_sensors( next_time = dt_util.utcnow() + timedelta(seconds=1.2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 8ce5596a446..c046e961fac 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -974,7 +974,7 @@ async def test_self_referencing_entity_picture_loop( assert len(hass.states.async_all()) == 1 next_time = dt_util.utcnow() + timedelta(seconds=1.2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 9535c714bb3..cf5051e657a 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -1768,7 +1768,7 @@ async def test_track_template_result_complex(hass: HomeAssistant) -> None: info = async_track_template_result( hass, - [TrackTemplate(template_complex, None, timedelta(seconds=0))], + [TrackTemplate(template_complex, None, 0)], specific_run_callback, ) await hass.async_block_till_done() @@ -2179,7 +2179,7 @@ async def test_track_template_result_iterator(hass: HomeAssistant) -> None: hass, ), None, - timedelta(seconds=0), + 0, ) ], iterator_callback, @@ -2210,7 +2210,7 @@ async def test_track_template_result_iterator(hass: HomeAssistant) -> None: hass, ), None, - timedelta(seconds=0), + 0, ) ], filter_callback, @@ -2417,7 +2417,7 @@ async def test_track_template_rate_limit(hass: HomeAssistant) -> None: info = async_track_template_result( hass, - [TrackTemplate(template_refresh, None, timedelta(seconds=0.1))], + [TrackTemplate(template_refresh, None, 0.1)], refresh_listener, ) await hass.async_block_till_done() @@ -2435,7 +2435,7 @@ async def test_track_template_rate_limit(hass: HomeAssistant) -> None: assert refresh_runs == [0, 1] next_time = dt_util.utcnow() + timedelta(seconds=0.125) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2448,7 +2448,7 @@ async def test_track_template_rate_limit(hass: HomeAssistant) -> None: assert refresh_runs == [0, 1, 2] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2485,7 +2485,7 @@ async def test_track_template_rate_limit_super(hass: HomeAssistant) -> None: hass, [ TrackTemplate(template_availability, None), - TrackTemplate(template_refresh, None, timedelta(seconds=0.1)), + TrackTemplate(template_refresh, None, 0.1), ], refresh_listener, has_super_template=True, @@ -2508,7 +2508,7 @@ async def test_track_template_rate_limit_super(hass: HomeAssistant) -> None: assert refresh_runs == [0, 1] next_time = dt_util.utcnow() + timedelta(seconds=0.125) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2525,7 +2525,7 @@ async def test_track_template_rate_limit_super(hass: HomeAssistant) -> None: assert refresh_runs == [0, 1, 4] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2560,8 +2560,8 @@ async def test_track_template_rate_limit_super_2(hass: HomeAssistant) -> None: info = async_track_template_result( hass, [ - TrackTemplate(template_availability, None, timedelta(seconds=0.1)), - TrackTemplate(template_refresh, None, timedelta(seconds=0.1)), + TrackTemplate(template_availability, None, 0.1), + TrackTemplate(template_refresh, None, 0.1), ], refresh_listener, has_super_template=True, @@ -2581,7 +2581,7 @@ async def test_track_template_rate_limit_super_2(hass: HomeAssistant) -> None: assert refresh_runs == [1] next_time = dt_util.utcnow() + timedelta(seconds=0.125) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2597,7 +2597,7 @@ async def test_track_template_rate_limit_super_2(hass: HomeAssistant) -> None: assert refresh_runs == [1] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2632,7 +2632,7 @@ async def test_track_template_rate_limit_super_3(hass: HomeAssistant) -> None: info = async_track_template_result( hass, [ - TrackTemplate(template_availability, None, timedelta(seconds=0.1)), + TrackTemplate(template_availability, None, 0.1), TrackTemplate(template_refresh, None), ], refresh_listener, @@ -2654,7 +2654,7 @@ async def test_track_template_rate_limit_super_3(hass: HomeAssistant) -> None: assert refresh_runs == [1, 2] next_time = dt_util.utcnow() + timedelta(seconds=0.125) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2671,7 +2671,7 @@ async def test_track_template_rate_limit_super_3(hass: HomeAssistant) -> None: assert refresh_runs == [1, 2] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2701,7 +2701,7 @@ async def test_track_template_rate_limit_suppress_listener(hass: HomeAssistant) info = async_track_template_result( hass, - [TrackTemplate(template_refresh, None, timedelta(seconds=0.1))], + [TrackTemplate(template_refresh, None, 0.1)], refresh_listener, ) await hass.async_block_till_done() @@ -2733,7 +2733,7 @@ async def test_track_template_rate_limit_suppress_listener(hass: HomeAssistant) assert refresh_runs == [0, 1] next_time = dt_util.utcnow() + timedelta(seconds=0.125) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2760,7 +2760,7 @@ async def test_track_template_rate_limit_suppress_listener(hass: HomeAssistant) } next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -2801,7 +2801,7 @@ async def test_track_template_rate_limit_five(hass: HomeAssistant) -> None: info = async_track_template_result( hass, - [TrackTemplate(template_refresh, None, timedelta(seconds=5))], + [TrackTemplate(template_refresh, None, 5)], refresh_listener, ) await hass.async_block_till_done() @@ -2928,7 +2928,7 @@ async def test_specifically_referenced_entity_is_not_rate_limited( info = async_track_template_result( hass, - [TrackTemplate(template_refresh, None, timedelta(seconds=5))], + [TrackTemplate(template_refresh, None, 5)], refresh_listener, ) await hass.async_block_till_done() @@ -2976,8 +2976,8 @@ async def test_track_two_templates_with_different_rate_limits( info = async_track_template_result( hass, [ - TrackTemplate(template_one, None, timedelta(seconds=0.1)), - TrackTemplate(template_five, None, timedelta(seconds=5)), + TrackTemplate(template_one, None, 0.1), + TrackTemplate(template_five, None, 5), ], refresh_listener, ) @@ -3001,7 +3001,7 @@ async def test_track_two_templates_with_different_rate_limits( assert refresh_runs[template_five] == [0, 1] next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 1) with patch( - "homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time + "homeassistant.helpers.ratelimit.time.time", return_value=next_time.timestamp() ): async_fire_time_changed(hass, next_time) await hass.async_block_till_done() @@ -3194,7 +3194,7 @@ async def test_async_track_template_result_multiple_templates_mixing_domain( TrackTemplate(template_1, None), TrackTemplate(template_2, None), TrackTemplate(template_3, None), - TrackTemplate(template_4, None, timedelta(seconds=0)), + TrackTemplate(template_4, None, 0), ], refresh_listener, ) diff --git a/tests/helpers/test_ratelimit.py b/tests/helpers/test_ratelimit.py index 9bd18a7e882..a87493b1731 100644 --- a/tests/helpers/test_ratelimit.py +++ b/tests/helpers/test_ratelimit.py @@ -1,11 +1,10 @@ """Tests for ratelimit.""" import asyncio -from datetime import timedelta +import time from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import ratelimit -from homeassistant.util import dt as dt_util async def test_hit(hass: HomeAssistant) -> None: @@ -19,12 +18,10 @@ async def test_hit(hass: HomeAssistant) -> None: refresh_called = True rate_limiter = ratelimit.KeyedRateLimit(hass) - rate_limiter.async_triggered("key1", dt_util.utcnow()) + rate_limiter.async_triggered("key1", time.time()) assert ( - rate_limiter.async_schedule_action( - "key1", timedelta(seconds=0.001), dt_util.utcnow(), _refresh - ) + rate_limiter.async_schedule_action("key1", 0.001, time.time(), _refresh) is not None ) @@ -36,10 +33,7 @@ async def test_hit(hass: HomeAssistant) -> None: assert refresh_called assert ( - rate_limiter.async_schedule_action( - "key2", timedelta(seconds=0.001), dt_util.utcnow(), _refresh - ) - is None + rate_limiter.async_schedule_action("key2", 0.001, time.time(), _refresh) is None ) rate_limiter.async_remove() @@ -56,19 +50,13 @@ async def test_miss(hass: HomeAssistant) -> None: rate_limiter = ratelimit.KeyedRateLimit(hass) assert ( - rate_limiter.async_schedule_action( - "key1", timedelta(seconds=0.1), dt_util.utcnow(), _refresh - ) - is None + rate_limiter.async_schedule_action("key1", 0.1, time.time(), _refresh) is None ) assert not refresh_called assert not rate_limiter.async_has_timer("key1") assert ( - rate_limiter.async_schedule_action( - "key1", timedelta(seconds=0.1), dt_util.utcnow(), _refresh - ) - is None + rate_limiter.async_schedule_action("key1", 0.1, time.time(), _refresh) is None ) assert not refresh_called assert not rate_limiter.async_has_timer("key1") @@ -86,20 +74,18 @@ async def test_no_limit(hass: HomeAssistant) -> None: refresh_called = True rate_limiter = ratelimit.KeyedRateLimit(hass) - rate_limiter.async_triggered("key1", dt_util.utcnow()) + rate_limiter.async_triggered("key1", time.time()) assert ( - rate_limiter.async_schedule_action("key1", None, dt_util.utcnow(), _refresh) - is None + rate_limiter.async_schedule_action("key1", None, time.time(), _refresh) is None ) assert not refresh_called assert not rate_limiter.async_has_timer("key1") - rate_limiter.async_triggered("key1", dt_util.utcnow()) + rate_limiter.async_triggered("key1", time.time()) assert ( - rate_limiter.async_schedule_action("key1", None, dt_util.utcnow(), _refresh) - is None + rate_limiter.async_schedule_action("key1", None, time.time(), _refresh) is None ) assert not refresh_called assert not rate_limiter.async_has_timer("key1") From e015fd2440edbbe142f24785bc53f442d7401bc7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 16:06:59 -1000 Subject: [PATCH 1336/1691] Use intersection for determine_script_action (#113915) --- homeassistant/helpers/config_validation.py | 74 ++++++++-------------- 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index bf666cf2e03..cf65da5917c 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1825,54 +1825,36 @@ SCRIPT_ACTION_WAIT_FOR_TRIGGER = "wait_for_trigger" SCRIPT_ACTION_WAIT_TEMPLATE = "wait_template" +ACTIONS_MAP = { + CONF_DELAY: SCRIPT_ACTION_DELAY, + CONF_WAIT_TEMPLATE: SCRIPT_ACTION_WAIT_TEMPLATE, + CONF_CONDITION: SCRIPT_ACTION_CHECK_CONDITION, + "and": SCRIPT_ACTION_CHECK_CONDITION, + "or": SCRIPT_ACTION_CHECK_CONDITION, + "not": SCRIPT_ACTION_CHECK_CONDITION, + CONF_EVENT: SCRIPT_ACTION_FIRE_EVENT, + CONF_DEVICE_ID: SCRIPT_ACTION_DEVICE_AUTOMATION, + CONF_SCENE: SCRIPT_ACTION_ACTIVATE_SCENE, + CONF_REPEAT: SCRIPT_ACTION_REPEAT, + CONF_CHOOSE: SCRIPT_ACTION_CHOOSE, + CONF_WAIT_FOR_TRIGGER: SCRIPT_ACTION_WAIT_FOR_TRIGGER, + CONF_VARIABLES: SCRIPT_ACTION_VARIABLES, + CONF_IF: SCRIPT_ACTION_IF, + CONF_SERVICE: SCRIPT_ACTION_CALL_SERVICE, + CONF_SERVICE_TEMPLATE: SCRIPT_ACTION_CALL_SERVICE, + CONF_STOP: SCRIPT_ACTION_STOP, + CONF_PARALLEL: SCRIPT_ACTION_PARALLEL, + CONF_SET_CONVERSATION_RESPONSE: SCRIPT_ACTION_SET_CONVERSATION_RESPONSE, +} + +ACTIONS_SET = set(ACTIONS_MAP) + + def determine_script_action(action: dict[str, Any]) -> str: """Determine action type.""" - if CONF_DELAY in action: - return SCRIPT_ACTION_DELAY - - if CONF_WAIT_TEMPLATE in action: - return SCRIPT_ACTION_WAIT_TEMPLATE - - if any(key in action for key in (CONF_CONDITION, "and", "or", "not")): - return SCRIPT_ACTION_CHECK_CONDITION - - if CONF_EVENT in action: - return SCRIPT_ACTION_FIRE_EVENT - - if CONF_DEVICE_ID in action: - return SCRIPT_ACTION_DEVICE_AUTOMATION - - if CONF_SCENE in action: - return SCRIPT_ACTION_ACTIVATE_SCENE - - if CONF_REPEAT in action: - return SCRIPT_ACTION_REPEAT - - if CONF_CHOOSE in action: - return SCRIPT_ACTION_CHOOSE - - if CONF_WAIT_FOR_TRIGGER in action: - return SCRIPT_ACTION_WAIT_FOR_TRIGGER - - if CONF_VARIABLES in action: - return SCRIPT_ACTION_VARIABLES - - if CONF_IF in action: - return SCRIPT_ACTION_IF - - if CONF_SERVICE in action or CONF_SERVICE_TEMPLATE in action: - return SCRIPT_ACTION_CALL_SERVICE - - if CONF_STOP in action: - return SCRIPT_ACTION_STOP - - if CONF_PARALLEL in action: - return SCRIPT_ACTION_PARALLEL - - if CONF_SET_CONVERSATION_RESPONSE in action: - return SCRIPT_ACTION_SET_CONVERSATION_RESPONSE - - raise ValueError("Unable to determine action") + if not (actions := ACTIONS_SET.intersection(action)): + raise ValueError("Unable to determine action") + return ACTIONS_MAP[actions.pop()] ACTION_TYPE_SCHEMAS: dict[str, Callable[[Any], dict]] = { From aebc95b1d25be3c996e7f6f663ab987145184f26 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 16:07:17 -1000 Subject: [PATCH 1337/1691] Reduce overhead to construct and validate entity service schema (#113920) --- homeassistant/helpers/config_validation.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index cf65da5917c..3f5af582424 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -161,15 +161,15 @@ def path(value: Any) -> str: # https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666 def has_at_least_one_key(*keys: Any) -> Callable[[dict], dict]: """Validate that at least one key exists.""" + key_set = set(keys) def validate(obj: dict) -> dict: """Test keys exist in dict.""" if not isinstance(obj, dict): raise vol.Invalid("expected dictionary") - for k in obj: - if k in keys: - return obj + if not key_set.isdisjoint(obj): + return obj expected = ", ".join(str(k) for k in keys) raise vol.Invalid(f"must contain at least one of {expected}.") @@ -1250,6 +1250,9 @@ TARGET_SERVICE_FIELDS = { } +_HAS_ENTITY_SERVICE_FIELD = has_at_least_one_key(*ENTITY_SERVICE_FIELDS) + + def _make_entity_service_schema(schema: dict, extra: int) -> vol.Schema: """Create an entity service schema.""" return vol.Schema( @@ -1263,7 +1266,7 @@ def _make_entity_service_schema(schema: dict, extra: int) -> vol.Schema: }, extra=extra, ), - has_at_least_one_key(*ENTITY_SERVICE_FIELDS), + _HAS_ENTITY_SERVICE_FIELD, ) ) From 6ddef7bbff3fffca9899eb32df5db4f959ff11c4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:59:43 +1300 Subject: [PATCH 1338/1691] ESPHome: Add Time Entity support (#113852) --- .../components/esphome/entry_data.py | 2 + homeassistant/components/esphome/time.py | 46 +++++++++++ tests/components/esphome/test_time.py | 76 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 homeassistant/components/esphome/time.py create mode 100644 tests/components/esphome/test_time.py diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 39bca5031fa..970dc296c95 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -34,6 +34,7 @@ from aioesphomeapi import ( SwitchInfo, TextInfo, TextSensorInfo, + TimeInfo, UserService, build_unique_id, ) @@ -75,6 +76,7 @@ INFO_TYPE_TO_PLATFORM: dict[type[EntityInfo], Platform] = { SwitchInfo: Platform.SWITCH, TextInfo: Platform.TEXT, TextSensorInfo: Platform.SENSOR, + TimeInfo: Platform.TIME, } diff --git a/homeassistant/components/esphome/time.py b/homeassistant/components/esphome/time.py new file mode 100644 index 00000000000..b68decd4252 --- /dev/null +++ b/homeassistant/components/esphome/time.py @@ -0,0 +1,46 @@ +"""Support for esphome times.""" +from __future__ import annotations + +from datetime import time + +from aioesphomeapi import TimeInfo, TimeState + +from homeassistant.components.time import TimeEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .entity import EsphomeEntity, esphome_state_property, platform_async_setup_entry + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up esphome times based on a config entry.""" + await platform_async_setup_entry( + hass, + entry, + async_add_entities, + info_type=TimeInfo, + entity_type=EsphomeTime, + state_type=TimeState, + ) + + +class EsphomeTime(EsphomeEntity[TimeInfo, TimeState], TimeEntity): + """A time implementation for esphome.""" + + @property + @esphome_state_property + def native_value(self) -> time | None: + """Return the state of the entity.""" + state = self._state + if state.missing_state: + return None + return time(state.hour, state.minute, state.second) + + async def async_set_value(self, value: time) -> None: + """Update the current time.""" + self._client.time_command(self._key, value.hour, value.minute, value.second) diff --git a/tests/components/esphome/test_time.py b/tests/components/esphome/test_time.py new file mode 100644 index 00000000000..aaa18c77a47 --- /dev/null +++ b/tests/components/esphome/test_time.py @@ -0,0 +1,76 @@ +"""Test ESPHome times.""" + +from unittest.mock import call + +from aioesphomeapi import APIClient, TimeInfo, TimeState + +from homeassistant.components.time import ( + ATTR_TIME, + DOMAIN as TIME_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + + +async def test_generic_time_entity( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic time entity.""" + entity_info = [ + TimeInfo( + object_id="mytime", + key=1, + name="my time", + unique_id="my_time", + ) + ] + states = [TimeState(key=1, hour=12, minute=34, second=56)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + state = hass.states.get("time.test_mytime") + assert state is not None + assert state.state == "12:34:56" + + await hass.services.async_call( + TIME_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "time.test_mytime", ATTR_TIME: "01:23:45"}, + blocking=True, + ) + mock_client.time_command.assert_has_calls([call(1, 1, 23, 45)]) + mock_client.time_command.reset_mock() + + +async def test_generic_time_missing_state( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic time entity with missing state.""" + entity_info = [ + TimeInfo( + object_id="mytime", + key=1, + name="my time", + unique_id="my_time", + ) + ] + states = [TimeState(key=1, missing_state=True)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + state = hass.states.get("time.test_mytime") + assert state is not None + assert state.state == STATE_UNKNOWN From 8edbf88da1d0b9fd19a750e8b1a23db1ca6b0b93 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 21 Mar 2024 06:53:26 +0100 Subject: [PATCH 1339/1691] Fetch MaxLengthExceeded exception mesage from the translation cache (#113904) * Fetch MaxLengthExceeded exception mesage from the translation cache * Update homeassistant/components/homeassistant/strings.json Co-authored-by: J. Nick Koston * Add case without homeassistant integration * Fix test --------- Co-authored-by: J. Nick Koston --- .../components/homeassistant/strings.json | 3 +++ homeassistant/exceptions.py | 12 ++++++++---- tests/test_core.py | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homeassistant/strings.json b/homeassistant/components/homeassistant/strings.json index 0b20f8698c2..016f3e0580d 100644 --- a/homeassistant/components/homeassistant/strings.json +++ b/homeassistant/components/homeassistant/strings.json @@ -174,6 +174,9 @@ "integration_config_error": { "message": "Failed to process config for integration {domain} due to multiple ({errors}) errors. Check the logs for more information." }, + "max_length_exceeded": { + "message": "Value {value} for property {property_name} has a maximum length of {max_length} characters." + }, "platform_component_load_err": { "message": "Platform error: {domain} - {error}. Check the logs for more information." }, diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 63b95b570e7..e94d5fd6b57 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -281,14 +281,18 @@ class MaxLengthExceeded(HomeAssistantError): """Initialize error.""" super().__init__( self, - ( - f"Value {value} for property {property_name} has a max length of " - f"{max_length} characters" - ), + translation_domain="homeassistant", + translation_key="max_length_exceeded", + translation_placeholders={ + "value": value, + "property_name": property_name, + "max_length": str(max_length), + }, ) self.value = value self.property_name = property_name self.max_length = max_length + self.generate_message = True class DependencyError(HomeAssistantError): diff --git a/tests/test_core.py b/tests/test_core.py index b60e6b832ce..ad67adb78b9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -58,6 +58,7 @@ from homeassistant.exceptions import ( ServiceNotFound, ) from homeassistant.helpers.json import json_dumps +from homeassistant.setup import async_setup_component from homeassistant.util.async_ import create_eager_task import homeassistant.util.dt as dt_util from homeassistant.util.read_only_dict import ReadOnlyDict @@ -1314,9 +1315,26 @@ async def test_eventbus_max_length_exceeded(hass: HomeAssistant) -> None: "this_event_exceeds_the_max_character_length_even_with_the_new_limit" ) + # Without cached translations the translation key is returned with pytest.raises(MaxLengthExceeded) as exc_info: hass.bus.async_fire(long_evt_name) + assert str(exc_info.value) == "max_length_exceeded" + assert exc_info.value.property_name == "event_type" + assert exc_info.value.max_length == 64 + assert exc_info.value.value == long_evt_name + + # Fetch translations + await async_setup_component(hass, "homeassistant", {}) + + # With cached translations the formatted message is returned + with pytest.raises(MaxLengthExceeded) as exc_info: + hass.bus.async_fire(long_evt_name) + + assert ( + str(exc_info.value) + == f"Value {long_evt_name} for property event_type has a maximum length of 64 characters" + ) assert exc_info.value.property_name == "event_type" assert exc_info.value.max_length == 64 assert exc_info.value.value == long_evt_name From dc38d152dfbc70226df8d54859819cedd7662e30 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 21 Mar 2024 06:54:29 +0100 Subject: [PATCH 1340/1691] Fetch ServiceNotFound message from translation cache (#113893) --- homeassistant/exceptions.py | 1 - tests/test_core.py | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index e94d5fd6b57..81856f27c45 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -261,7 +261,6 @@ class ServiceNotFound(HomeAssistantError): """Initialize error.""" super().__init__( self, - f"Service {domain}.{service} not found.", translation_domain="homeassistant", translation_key="service_not_found", translation_placeholders={"domain": domain, "service": service}, diff --git a/tests/test_core.py b/tests/test_core.py index ad67adb78b9..f95a2104ff5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1676,8 +1676,18 @@ async def test_serviceregistry_service_that_not_exists(hass: HomeAssistant) -> N await hass.async_block_till_done() assert len(calls_remove) == 0 - with pytest.raises(ServiceNotFound): + with pytest.raises(ServiceNotFound) as exc: await hass.services.async_call("test_do_not", "exist", {}) + assert exc.value.translation_domain == "homeassistant" + assert exc.value.translation_key == "service_not_found" + assert exc.value.translation_placeholders == { + "domain": "test_do_not", + "service": "exist", + } + assert exc.value.domain == "test_do_not" + assert exc.value.service == "exist" + + assert str(exc.value) == "Service test_do_not.exist not found." async def test_serviceregistry_async_service_raise_exception( From 7758f41c5a56a720b3db8ee1323bbcddfb654cb0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 22:24:39 -1000 Subject: [PATCH 1341/1691] Load system info with base functionality (#113923) --- homeassistant/bootstrap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index f98c029832e..215aadc25c2 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -78,6 +78,7 @@ from .helpers import ( translation, ) from .helpers.dispatcher import async_dispatcher_send +from .helpers.system_info import async_get_system_info from .helpers.typing import ConfigType from .setup import ( BASE_PLATFORMS, @@ -358,6 +359,7 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None: create_eager_task(template.async_load_custom_templates(hass)), create_eager_task(restore_state.async_load(hass)), create_eager_task(hass.config_entries.async_initialize()), + create_eager_task(async_get_system_info(hass)), ) From 0b0d3a2091f398240b825540e372230f4252f09d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 22:32:43 -1000 Subject: [PATCH 1342/1691] Add run_immediately to the trace stop listener (#113922) --- homeassistant/components/trace/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index d0ec4555376..a7ad576e53d 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -68,7 +68,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.error("Error storing traces", exc_info=exc) # Store traces when stopping hass - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_store_traces_at_stop) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_store_traces_at_stop, run_immediately=True + ) return True From f662e7e3cf7763745165e8dd2ec77c38ea7d78ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 22:33:36 -1000 Subject: [PATCH 1343/1691] Only calculate native value once per update in systemmonitor (#113921) --- homeassistant/components/systemmonitor/sensor.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index a8b0d6bd146..b2dacc27327 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -776,6 +776,7 @@ class SystemMonitorSensor(CoordinatorEntity[SystemMonitorCoordinator], SensorEnt self.argument = argument self.value: int | None = None self.update_time: float | None = None + self._attr_native_value = self.entity_description.value_fn(self) async def async_added_to_hass(self) -> None: """When added to hass.""" @@ -791,16 +792,19 @@ class SystemMonitorSensor(CoordinatorEntity[SystemMonitorCoordinator], SensorEnt ].remove(self.entity_id) return await super().async_will_remove_from_hass() - @property - def native_value(self) -> StateType | datetime: - """Return the state.""" - return self.entity_description.value_fn(self) + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + # Set the native value here so we can use it in available property + # without having to recalculate it + self._attr_native_value = self.entity_description.value_fn(self) + super()._handle_coordinator_update() @property def available(self) -> bool: """Return if entity is available.""" if self.entity_description.none_is_unavailable: - return bool( + return ( self.coordinator.last_update_success is True and self.native_value is not None ) From 3b66328591f88b58597e2b95dfe4eeeb002640d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 20 Mar 2024 22:34:33 -1000 Subject: [PATCH 1344/1691] Add a fast path for async_get_platform (#113917) --- homeassistant/config_entries.py | 4 ++-- homeassistant/loader.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 91986f053fd..d33f66538f4 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -515,7 +515,7 @@ class ConfigEntry: if domain_is_integration: try: - await integration.async_get_platforms(("config_flow",)) + await integration.async_get_platform("config_flow") except ImportError as err: _LOGGER.error( ( @@ -2567,7 +2567,7 @@ async def _load_integration( # Make sure requirements and dependencies of component are resolved await async_process_deps_reqs(hass, hass_config, integration) try: - await integration.async_get_platforms(("config_flow",)) + await integration.async_get_platform("config_flow") except ImportError as err: _LOGGER.error( "Error occurred loading flow for integration %s: %s", diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 74250a3a150..7c52787b34a 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1065,7 +1065,11 @@ class Integration: async def async_get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" - platforms = await self.async_get_platforms([platform_name]) + # Fast path for a single platform when its already + # cached. This is the common case. + if platform := self._cache.get(f"{self.domain}.{platform_name}"): + return platform # type: ignore[return-value] + platforms = await self.async_get_platforms((platform_name,)) return platforms[platform_name] async def async_get_platforms( From 8e1f57f663188c69e3d391738f4e7eca6698e249 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:43:27 +0100 Subject: [PATCH 1345/1691] Enable Ruff PT023, disable PT011, PT012, PT018 (#113900) * Enable Ruff PT023 * Set mark parantheses to False * Disable PT011, PT012, PT018 --- pyproject.toml | 8 ++++---- tests/components/mqtt_statestream/test_init.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17fb6307710..9919a3c8f39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -659,6 +659,9 @@ ignore = [ "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target "PT004", # Fixture {fixture} does not return anything, add leading underscore + "PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception + "PT012", # `pytest.raises()` block should contain a single simple statement + "PT018", # Assertion should be broken down into multiple parts "SIM102", # Use a single if statement instead of nested if statements "SIM108", # Use ternary operator {contents} instead of if-else-block "SIM115", # Use context handler for opening files @@ -683,10 +686,6 @@ ignore = [ "PLE0605", # temporarily disabled - "PT011", - "PT018", - "PT012", - "PT023", "PT019" ] @@ -704,6 +703,7 @@ voluptuous = "vol" [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false +mark-parentheses = false [tool.ruff.lint.flake8-tidy-imports.banned-api] "async_timeout".msg = "use asyncio.timeout instead" diff --git a/tests/components/mqtt_statestream/test_init.py b/tests/components/mqtt_statestream/test_init.py index d8abb7158bd..9798477945c 100644 --- a/tests/components/mqtt_statestream/test_init.py +++ b/tests/components/mqtt_statestream/test_init.py @@ -100,7 +100,7 @@ async def test_setup_and_stop_waits_for_ha( # We use xfail with this test because there is an unhandled exception # in a background task in this test. # The exception is raised by mqtt.async_publish. -@pytest.mark.xfail() +@pytest.mark.xfail async def test_startup_no_mqtt( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: From 59c4c85089e734553cb37819e0eeb2450dca7f56 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 21 Mar 2024 09:49:37 +0100 Subject: [PATCH 1346/1691] Upgrade Modbus quality scale to platinum (#113482) Quality scale: Platinum. --- homeassistant/components/modbus/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index 14faad789fe..956961c7e67 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -5,6 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/modbus", "iot_class": "local_polling", "loggers": ["pymodbus"], - "quality_scale": "gold", + "quality_scale": "platinum", "requirements": ["pymodbus==3.6.6"] } From 80c8b94021bce779eacb0ff3097b29f4fea184cf Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:06:40 +0100 Subject: [PATCH 1347/1691] Add diagnostics to Husqvarna Automower (#111857) --- .../husqvarna_automower/diagnostics.py | 46 +++++++ .../snapshots/test_diagnostics.ambr | 129 ++++++++++++++++++ .../husqvarna_automower/test_diagnostics.py | 61 +++++++++ 3 files changed, 236 insertions(+) create mode 100644 homeassistant/components/husqvarna_automower/diagnostics.py create mode 100644 tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr create mode 100644 tests/components/husqvarna_automower/test_diagnostics.py diff --git a/homeassistant/components/husqvarna_automower/diagnostics.py b/homeassistant/components/husqvarna_automower/diagnostics.py new file mode 100644 index 00000000000..7a715292a07 --- /dev/null +++ b/homeassistant/components/husqvarna_automower/diagnostics.py @@ -0,0 +1,46 @@ +"""Diagnostics support for Husqvarna Automower.""" +from __future__ import annotations + +import logging +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntry + +from .const import DOMAIN +from .coordinator import AutomowerDataUpdateCoordinator + +CONF_REFRESH_TOKEN = "refresh_token" +POSITIONS = "positions" + +TO_REDACT = { + CONF_ACCESS_TOKEN, + CONF_REFRESH_TOKEN, + POSITIONS, +} +_LOGGER = logging.getLogger(__name__) + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + return async_redact_data(entry.as_dict(), TO_REDACT) + + +async def async_get_device_diagnostics( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> dict[str, Any]: + """Return diagnostics for a device entry.""" + coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + for identifier in device.identifiers: + if identifier[0] == DOMAIN: + if ( + coordinator.data[identifier[1]].system.serial_number + == device.serial_number + ): + mower_id = identifier[1] + return async_redact_data(coordinator.data[mower_id].to_dict(), TO_REDACT) diff --git a/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..577d49cd026 --- /dev/null +++ b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr @@ -0,0 +1,129 @@ +# serializer version: 1 +# name: test_device_diagnostics + dict({ + 'battery': dict({ + 'battery_percent': 100, + }), + 'calendar': dict({ + 'tasks': list([ + dict({ + 'duration': 300, + 'friday': True, + 'monday': True, + 'saturday': False, + 'start': 1140, + 'sunday': False, + 'thursday': False, + 'tuesday': False, + 'wednesday': True, + 'work_area_id': None, + }), + dict({ + 'duration': 480, + 'friday': False, + 'monday': False, + 'saturday': True, + 'start': 0, + 'sunday': False, + 'thursday': True, + 'tuesday': True, + 'wednesday': False, + 'work_area_id': None, + }), + ]), + }), + 'capabilities': dict({ + 'headlights': True, + 'position': True, + 'stay_out_zones': False, + 'work_areas': False, + }), + 'cutting_height': 4, + 'headlight': dict({ + 'mode': 'EVENING_ONLY', + }), + 'metadata': dict({ + 'connected': True, + 'status_dateteime': '2023-10-18T22:58:52.683000+00:00', + }), + 'mower': dict({ + 'activity': 'PARKED_IN_CS', + 'error_code': 0, + 'error_dateteime': None, + 'error_key': None, + 'mode': 'MAIN_AREA', + 'state': 'RESTRICTED', + }), + 'planner': dict({ + 'next_start_dateteime': '2023-06-05T19:00:00+00:00', + 'override': dict({ + 'action': 'NOT_ACTIVE', + }), + 'restricted_reason': 'WEEK_SCHEDULE', + }), + 'positions': '**REDACTED**', + 'statistics': dict({ + 'cutting_blade_usage_time': 123, + 'number_of_charging_cycles': 1380, + 'number_of_collisions': 11396, + 'total_charging_time': 4334400, + 'total_cutting_time': 4194000, + 'total_drive_distance': 1780272, + 'total_running_time': 4564800, + 'total_searching_time': 370800, + }), + 'stay_out_zones': dict({ + 'dirty': False, + 'zones': dict({ + '81C6EEA2-D139-4FEA-B134-F22A6B3EA403': dict({ + 'enabled': True, + 'name': 'Springflowers', + }), + }), + }), + 'system': dict({ + 'model': '450XH-TEST', + 'name': 'Test Mower 1', + 'serial_number': 123, + }), + 'work_areas': dict({ + '0': dict({ + 'cutting_height': 50, + 'name': None, + }), + '123456': dict({ + 'cutting_height': 50, + 'name': 'Front lawn', + }), + }), + }) +# --- +# name: test_entry_diagnostics + dict({ + 'data': dict({ + 'auth_implementation': 'husqvarna_automower', + 'token': dict({ + 'access_token': '**REDACTED**', + 'expires_at': 1709208000.0, + 'expires_in': 86399, + 'provider': 'husqvarna', + 'refresh_token': '**REDACTED**', + 'scope': 'iam:read amc:api', + 'token_type': 'Bearer', + 'user_id': '123', + }), + }), + 'disabled_by': None, + 'domain': 'husqvarna_automower', + 'entry_id': 'automower_test', + 'minor_version': 1, + 'options': dict({ + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': 'Husqvarna Automower of Erika Mustermann', + 'unique_id': '123', + 'version': 1, + }) +# --- diff --git a/tests/components/husqvarna_automower/test_diagnostics.py b/tests/components/husqvarna_automower/test_diagnostics.py new file mode 100644 index 00000000000..56eb0fdadde --- /dev/null +++ b/tests/components/husqvarna_automower/test_diagnostics.py @@ -0,0 +1,61 @@ +"""Test the Husqvarna Automower Diagnostics.""" +import datetime +from unittest.mock import AsyncMock + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.husqvarna_automower.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from .const import TEST_MOWER_ID + +from tests.common import MockConfigEntry +from tests.components.diagnostics import ( + get_diagnostics_for_config_entry, + get_diagnostics_for_device, +) +from tests.typing import ClientSessionGenerator + + +@pytest.mark.freeze_time(datetime.datetime(2024, 2, 29, 11, tzinfo=datetime.UTC)) +async def test_entry_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test config entry diagnostics.""" + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + result = await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) + assert result == snapshot + + +async def test_device_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test select platform.""" + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + reg_device = device_registry.async_get_device( + identifiers={(DOMAIN, TEST_MOWER_ID)}, + ) + assert reg_device is not None + result = await get_diagnostics_for_device( + hass, hass_client, mock_config_entry, reg_device + ) + assert result == snapshot From e1897906cc6398cf8358b6ae21277ecaa74bb89a Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 21 Mar 2024 10:43:58 +0100 Subject: [PATCH 1348/1691] modbus: Remove PARALLEL_UPDATES from base_platform (#113928) Remove PARALLEL_UPDATES from base_platform. --- homeassistant/components/modbus/base_platform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index c64ef95c163..5c8816dd74e 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -67,7 +67,6 @@ from .const import ( ) from .modbus import ModbusHub -PARALLEL_UPDATES = 1 _LOGGER = logging.getLogger(__name__) From ff6812a7983cdff444a92d3a2fe41e8eccde0699 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 21 Mar 2024 10:49:32 +0100 Subject: [PATCH 1349/1691] Split light fixture from implementation to only import when fixture is actually used (#113892) * Split light fixture from implementation to only import when fixture is actually used * Non-local import --- tests/components/conftest.py | 27 +++- tests/components/group/test_light.py | 28 ++--- tests/components/light/common.py | 92 ++++++++++++++ .../components/light/test_device_condition.py | 5 +- tests/components/light/test_init.py | 48 ++++---- tests/conftest.py | 4 - tests/fixtures/pytest/__init__.py | 1 - tests/fixtures/pytest/light.py | 115 ------------------ 8 files changed, 160 insertions(+), 160 deletions(-) delete mode 100644 tests/fixtures/pytest/__init__.py delete mode 100644 tests/fixtures/pytest/light.py diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 24fdc7743ce..8b21f8cd1a8 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -1,11 +1,16 @@ """Fixtures for component testing.""" from collections.abc import Generator -from typing import Any +from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock, patch import pytest +from homeassistant.const import STATE_OFF, STATE_ON + +if TYPE_CHECKING: + from tests.components.light.common import MockLight, SetupLightPlatformCallable + @pytest.fixture(scope="session", autouse=True) def patch_zeroconf_multiple_catcher() -> Generator[None, None, None]: @@ -101,3 +106,23 @@ def prevent_ffmpeg_subprocess() -> Generator[None, None, None]: "homeassistant.components.ffmpeg.FFVersion.get_version", return_value="6.0" ): yield + + +@pytest.fixture +def mock_light_entities() -> list["MockLight"]: + """Return mocked light entities.""" + from tests.components.light.common import MockLight + + return [ + MockLight("Ceiling", STATE_ON), + MockLight("Ceiling", STATE_OFF), + MockLight(None, STATE_OFF), + ] + + +@pytest.fixture +def setup_light_platform() -> "SetupLightPlatformCallable": + """Return a callable to set up the mock light entity component.""" + from tests.components.light.common import setup_light_platform + + return setup_light_platform diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index d48474955a5..dca6002ccb7 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -45,7 +45,7 @@ from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component from tests.common import async_capture_events, get_fixture_path -from tests.fixtures.pytest.light import MockLight, SetupLightPlatformCallable +from tests.components.light.common import MockLight, SetupLightPlatformCallable async def test_default_state( @@ -269,7 +269,7 @@ async def test_brightness( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.BRIGHTNESS} @@ -342,7 +342,7 @@ async def test_color_hs( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.HS} @@ -414,7 +414,7 @@ async def test_color_rgb( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGB} @@ -488,7 +488,7 @@ async def test_color_rgbw( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGBW} @@ -562,7 +562,7 @@ async def test_color_rgbww( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGBWW} @@ -636,7 +636,7 @@ async def test_white( MockLight("test1", STATE_ON), MockLight("test2", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.HS, ColorMode.WHITE} @@ -695,7 +695,7 @@ async def test_color_temp( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -767,7 +767,7 @@ async def test_emulated_color_temp_group( MockLight("test2", STATE_OFF), MockLight("test3", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -835,7 +835,7 @@ async def test_min_max_mireds( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -1014,7 +1014,7 @@ async def test_supported_color_modes( MockLight("test2", STATE_OFF), MockLight("test3", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} @@ -1064,7 +1064,7 @@ async def test_color_mode( MockLight("test2", STATE_OFF), MockLight("test3", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} @@ -1142,7 +1142,7 @@ async def test_color_mode2( MockLight("test5", STATE_ON), MockLight("test6", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity = entities[0] entity.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -1265,7 +1265,7 @@ async def test_service_calls( MockLight("ceiling_lights", STATE_OFF), MockLight("kitchen_lights", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {supported_color_modes} diff --git a/tests/components/light/common.py b/tests/components/light/common.py index f76f1d4146d..519084d9c34 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ +from collections.abc import Callable from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -21,6 +22,8 @@ from homeassistant.components.light import ( ATTR_WHITE, ATTR_XY_COLOR, DOMAIN, + ColorMode, + LightEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -29,8 +32,13 @@ from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import bind_hass +from tests.common import MockPlatform, MockToggleEntity, mock_platform + @bind_hass def turn_on( @@ -217,3 +225,87 @@ async def async_toggle( } await hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data, blocking=True) + + +TURN_ON_ARG_TO_COLOR_MODE = { + "hs_color": ColorMode.HS, + "xy_color": ColorMode.XY, + "rgb_color": ColorMode.RGB, + "rgbw_color": ColorMode.RGBW, + "rgbww_color": ColorMode.RGBWW, + "color_temp_kelvin": ColorMode.COLOR_TEMP, +} + + +class MockLight(MockToggleEntity, LightEntity): + """Mock light class.""" + + _attr_max_color_temp_kelvin = 6500 + _attr_min_color_temp_kelvin = 2000 + supported_features = 0 + + brightness = None + color_temp_kelvin = None + hs_color = None + rgb_color = None + rgbw_color = None + rgbww_color = None + xy_color = None + + def __init__( + self, + name, + state, + unique_id=None, + supported_color_modes: set[ColorMode] | None = None, + ): + """Initialize the mock light.""" + super().__init__(name, state, unique_id) + if supported_color_modes is None: + supported_color_modes = {ColorMode.ONOFF} + self._attr_supported_color_modes = supported_color_modes + color_mode = ColorMode.UNKNOWN + if len(supported_color_modes) == 1: + color_mode = next(iter(supported_color_modes)) + self._attr_color_mode = color_mode + + def turn_on(self, **kwargs): + """Turn the entity on.""" + super().turn_on(**kwargs) + for key, value in kwargs.items(): + if key in [ + "brightness", + "hs_color", + "xy_color", + "rgb_color", + "rgbw_color", + "rgbww_color", + "color_temp_kelvin", + ]: + setattr(self, key, value) + if key == "white": + setattr(self, "brightness", value) + if key in TURN_ON_ARG_TO_COLOR_MODE: + self._attr_color_mode = TURN_ON_ARG_TO_COLOR_MODE[key] + + +SetupLightPlatformCallable = Callable[[HomeAssistant, list[MockLight]], None] + + +def setup_light_platform(hass: HomeAssistant, entities: list[MockLight]) -> None: + """Set up the mock light entity platform.""" + + async def async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, + ) -> None: + """Set up test light platform.""" + async_add_entities(entities) + + mock_platform( + hass, + f"test.{DOMAIN}", + MockPlatform(async_setup_platform=async_setup_platform), + ) diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 2dd06589966..fe556fc3207 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -22,7 +22,7 @@ from tests.common import ( async_get_device_automations, async_mock_service, ) -from tests.fixtures.pytest.light import SetupLightPlatformCallable +from tests.components.light.common import MockLight, SetupLightPlatformCallable @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -325,6 +325,7 @@ async def test_if_fires_on_for_condition( entity_registry: er.EntityRegistry, calls, setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test for firing if condition is on with delay.""" config_entry = MockConfigEntry(domain="test", data={}) @@ -343,7 +344,7 @@ async def test_if_fires_on_for_condition( point2 = point1 + timedelta(seconds=10) point3 = point2 + timedelta(seconds=10) - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 57bd312421c..b66ae5819c5 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -22,7 +22,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.color as color_util from tests.common import MockEntityPlatform, MockUser, async_mock_service -from tests.fixtures.pytest.light import MockLight, SetupLightPlatformCallable +from tests.components.light.common import MockLight, SetupLightPlatformCallable orig_Profiles = light.Profiles @@ -114,7 +114,7 @@ async def test_services( mock_light_entities: list[MockLight], ) -> None: """Test the provided services.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -515,7 +515,7 @@ async def test_light_profiles( mock_light_entities: list[MockLight], ) -> None: """Test light profiles.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) profile_mock_data = { "test": (0.4, 0.6, 100, 0), @@ -564,7 +564,7 @@ async def test_default_profiles_group( mock_light_entities: list[MockLight], ) -> None: """Test default turn-on light profile for all lights.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -790,7 +790,7 @@ async def test_default_profiles_light( mock_light_entities: list[MockLight], ) -> None: """Test default turn-on light profile for a specific light.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -857,9 +857,10 @@ async def test_light_context( hass: HomeAssistant, hass_admin_user: MockUser, setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test that light context works.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -885,9 +886,10 @@ async def test_light_turn_on_auth( hass: HomeAssistant, hass_read_only_user: MockUser, setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test that light context works.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -916,7 +918,7 @@ async def test_light_brightness_step( MockLight("Test_1", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_features = light.SUPPORT_BRIGHTNESS @@ -987,7 +989,7 @@ async def test_light_brightness_pct_conversion( mock_light_entities: list[MockLight], ) -> None: """Test that light brightness percent conversion.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) entity = mock_light_entities[0] entity.supported_features = light.SUPPORT_BRIGHTNESS @@ -1183,7 +1185,7 @@ async def test_light_backwards_compatibility_supported_color_modes( entity4.supported_color_modes = None entity4.color_mode = None - setup_light_platform(entities) + setup_light_platform(hass, entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1272,7 +1274,7 @@ async def test_light_backwards_compatibility_color_mode( entity4.hs_color = (240, 100) entity4.color_temp_kelvin = 10000 - setup_light_platform(entities) + setup_light_platform(hass, entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1312,7 +1314,7 @@ async def test_light_service_call_rgbw( entity0 = MockLight("Test_rgbw", STATE_ON) entity0.supported_color_modes = {light.ColorMode.RGBW} - setup_light_platform([entity0]) + setup_light_platform(hass, [entity0]) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1345,7 +1347,7 @@ async def test_light_state_off( MockLight("Test_ct", STATE_OFF), MockLight("Test_rgbw", STATE_OFF), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.ONOFF} @@ -1413,7 +1415,7 @@ async def test_light_state_rgbw( ) -> None: """Test rgbw color conversion in state updates.""" entity0 = MockLight("Test_rgbw", STATE_ON) - setup_light_platform([entity0]) + setup_light_platform(hass, [entity0]) entity0.brightness = 255 entity0.supported_color_modes = {light.ColorMode.RGBW} @@ -1446,7 +1448,7 @@ async def test_light_state_rgbww( ) -> None: """Test rgbww color conversion in state updates.""" entity0 = MockLight("Test_rgbww", STATE_ON) - setup_light_platform([entity0]) + setup_light_platform(hass, [entity0]) entity0.supported_color_modes = {light.ColorMode.RGBWW} entity0.color_mode = light.ColorMode.RGBWW @@ -1488,7 +1490,7 @@ async def test_light_service_call_color_conversion( MockLight("Test_rgbww", STATE_ON), MockLight("Test_temperature", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -1932,7 +1934,7 @@ async def test_light_service_call_color_conversion_named_tuple( MockLight("Test_rgbw", STATE_ON), MockLight("Test_rgbww", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -2008,7 +2010,7 @@ async def test_light_service_call_color_temp_emulation( MockLight("Test_hs", STATE_ON), MockLight("Test_hs_white", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.COLOR_TEMP, light.ColorMode.HS} @@ -2067,7 +2069,7 @@ async def test_light_service_call_color_temp_conversion( MockLight("Test_rgbww_ct", STATE_ON), MockLight("Test_rgbww", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = { @@ -2200,7 +2202,7 @@ async def test_light_mired_color_temp_conversion( MockLight("Test_rgbww_ct", STATE_ON), MockLight("Test_rgbww", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = { @@ -2246,7 +2248,7 @@ async def test_light_service_call_white_mode( """Test color_mode white in service calls.""" entity0 = MockLight("Test_white", STATE_ON) entity0.supported_color_modes = {light.ColorMode.HS, light.ColorMode.WHITE} - setup_light_platform([entity0]) + setup_light_platform(hass, [entity0]) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -2351,7 +2353,7 @@ async def test_light_state_color_conversion( MockLight("Test_xy", STATE_ON), MockLight("Test_legacy", STATE_ON), ] - setup_light_platform(entities) + setup_light_platform(hass, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -2416,7 +2418,7 @@ async def test_services_filter_parameters( mock_light_entities: list[MockLight], ) -> None: """Test turn_on and turn_off filters unsupported parameters.""" - setup_light_platform() + setup_light_platform(hass, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} diff --git a/tests/conftest.py b/tests/conftest.py index e21a5e3d92b..4eacb8991c0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -114,10 +114,6 @@ asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False)) # Disable fixtures overriding our beautiful policy asyncio.set_event_loop_policy = lambda policy: None -pytest_plugins = [ - "tests.fixtures.pytest.light", -] - def pytest_addoption(parser: pytest.Parser) -> None: """Register custom pytest options.""" diff --git a/tests/fixtures/pytest/__init__.py b/tests/fixtures/pytest/__init__.py deleted file mode 100644 index f792ac8b29e..00000000000 --- a/tests/fixtures/pytest/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Fixtures for tests.""" diff --git a/tests/fixtures/pytest/light.py b/tests/fixtures/pytest/light.py deleted file mode 100644 index e81d2d9ba94..00000000000 --- a/tests/fixtures/pytest/light.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Fixtures for the light entity component tests.""" -from collections.abc import Callable - -import pytest - -from homeassistant.components.light import DOMAIN, ColorMode, LightEntity -from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType - -from tests.common import MockPlatform, MockToggleEntity, mock_platform - -TURN_ON_ARG_TO_COLOR_MODE = { - "hs_color": ColorMode.HS, - "xy_color": ColorMode.XY, - "rgb_color": ColorMode.RGB, - "rgbw_color": ColorMode.RGBW, - "rgbww_color": ColorMode.RGBWW, - "color_temp_kelvin": ColorMode.COLOR_TEMP, -} - - -class MockLight(MockToggleEntity, LightEntity): - """Mock light class.""" - - _attr_max_color_temp_kelvin = 6500 - _attr_min_color_temp_kelvin = 2000 - supported_features = 0 - - brightness = None - color_temp_kelvin = None - hs_color = None - rgb_color = None - rgbw_color = None - rgbww_color = None - xy_color = None - - def __init__( - self, - name, - state, - unique_id=None, - supported_color_modes: set[ColorMode] | None = None, - ): - """Initialize the mock light.""" - super().__init__(name, state, unique_id) - if supported_color_modes is None: - supported_color_modes = {ColorMode.ONOFF} - self._attr_supported_color_modes = supported_color_modes - color_mode = ColorMode.UNKNOWN - if len(supported_color_modes) == 1: - color_mode = next(iter(supported_color_modes)) - self._attr_color_mode = color_mode - - def turn_on(self, **kwargs): - """Turn the entity on.""" - super().turn_on(**kwargs) - for key, value in kwargs.items(): - if key in [ - "brightness", - "hs_color", - "xy_color", - "rgb_color", - "rgbw_color", - "rgbww_color", - "color_temp_kelvin", - ]: - setattr(self, key, value) - if key == "white": - setattr(self, "brightness", value) - if key in TURN_ON_ARG_TO_COLOR_MODE: - self._attr_color_mode = TURN_ON_ARG_TO_COLOR_MODE[key] - - -SetupLightPlatformCallable = Callable[[list[MockLight] | None], None] - - -@pytest.fixture -async def mock_light_entities() -> list[MockLight]: - """Return mocked light entities.""" - return [ - MockLight("Ceiling", STATE_ON), - MockLight("Ceiling", STATE_OFF), - MockLight(None, STATE_OFF), - ] - - -@pytest.fixture -async def setup_light_platform( - hass: HomeAssistant, mock_light_entities: list[MockLight] -) -> SetupLightPlatformCallable: - """Set up the mock light entity platform.""" - - def _setup(entities: list[MockLight] | None = None) -> None: - """Set up the mock light entity platform.""" - - async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, - ) -> None: - """Set up test light platform.""" - async_add_entities( - entities if entities is not None else mock_light_entities - ) - - mock_platform( - hass, - f"test.{DOMAIN}", - MockPlatform(async_setup_platform=async_setup_platform), - ) - - return _setup From 67a14d04632d76b5c733886ca552dbc79ebd69b9 Mon Sep 17 00:00:00 2001 From: Ingmar Delsink Date: Thu, 21 Mar 2024 11:05:36 +0100 Subject: [PATCH 1350/1691] Add transmission-integration path and protocol (#104334) This updates the config_flow migration from v1.1 to v1.2 including migration tests --- .../components/transmission/__init__.py | 37 +++++++++++++++++++ .../components/transmission/config_flow.py | 14 ++++++- .../components/transmission/const.py | 2 + .../components/transmission/strings.json | 9 ++++- tests/components/transmission/__init__.py | 12 +++++- .../transmission/test_config_flow.py | 5 +-- tests/components/transmission/test_init.py | 30 ++++++++++++++- 7 files changed, 99 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index bbf4bf5d630..4dcc4d41950 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -21,7 +21,9 @@ from homeassistant.const import ( CONF_ID, CONF_NAME, CONF_PASSWORD, + CONF_PATH, CONF_PORT, + CONF_SSL, CONF_USERNAME, Platform, ) @@ -38,6 +40,8 @@ from .const import ( ATTR_TORRENT, CONF_ENTRY_ID, DEFAULT_DELETE_DATA, + DEFAULT_PATH, + DEFAULT_SSL, DOMAIN, SERVICE_ADD_TORRENT, SERVICE_REMOVE_TORRENT, @@ -211,12 +215,43 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return unload_ok +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Migrate an old config entry.""" + _LOGGER.debug( + "Migrating from version %s.%s", + config_entry.version, + config_entry.minor_version, + ) + + if config_entry.version == 1: + # Version 1.2 adds ssl and path + if config_entry.minor_version < 2: + new = {**config_entry.data} + + new[CONF_PATH] = DEFAULT_PATH + new[CONF_SSL] = DEFAULT_SSL + + hass.config_entries.async_update_entry( + config_entry, data=new, version=1, minor_version=2 + ) + + _LOGGER.debug( + "Migration to version %s.%s successful", + config_entry.version, + config_entry.minor_version, + ) + + return True + + async def get_api( hass: HomeAssistant, entry: dict[str, Any] ) -> transmission_rpc.Client: """Get Transmission client.""" + protocol = "https" if entry[CONF_SSL] else "http" host = entry[CONF_HOST] port = entry[CONF_PORT] + path = entry[CONF_PATH] username = entry.get(CONF_USERNAME) password = entry.get(CONF_PASSWORD) @@ -226,8 +261,10 @@ async def get_api( transmission_rpc.Client, username=username, password=password, + protocol=protocol, host=host, port=port, + path=path, ) ) _LOGGER.debug("Successfully connected to %s", host) diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index de3616646bf..62879d2d0af 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -13,7 +13,14 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PATH, + CONF_PORT, + CONF_SSL, + CONF_USERNAME, +) from homeassistant.core import callback from . import get_api @@ -23,7 +30,9 @@ from .const import ( DEFAULT_LIMIT, DEFAULT_NAME, DEFAULT_ORDER, + DEFAULT_PATH, DEFAULT_PORT, + DEFAULT_SSL, DOMAIN, SUPPORTED_ORDER_MODES, ) @@ -31,7 +40,9 @@ from .errors import AuthenticationError, CannotConnect, UnknownError DATA_SCHEMA = vol.Schema( { + vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, vol.Required(CONF_HOST): str, + vol.Required(CONF_PATH, default=DEFAULT_PATH): str, vol.Optional(CONF_USERNAME): str, vol.Optional(CONF_PASSWORD): str, vol.Required(CONF_PORT, default=DEFAULT_PORT): int, @@ -43,6 +54,7 @@ class TransmissionFlowHandler(ConfigFlow, domain=DOMAIN): """Handle Tansmission config flow.""" VERSION = 1 + MINOR_VERSION = 2 _reauth_entry: ConfigEntry | None @staticmethod diff --git a/homeassistant/components/transmission/const.py b/homeassistant/components/transmission/const.py index 98f9184f1af..0dd77fa6aa3 100644 --- a/homeassistant/components/transmission/const.py +++ b/homeassistant/components/transmission/const.py @@ -31,7 +31,9 @@ DEFAULT_DELETE_DATA = False DEFAULT_LIMIT = 10 DEFAULT_ORDER = ORDER_OLDEST_FIRST DEFAULT_NAME = "Transmission" +DEFAULT_SSL = False DEFAULT_PORT = 9091 +DEFAULT_PATH = "/transmission/rpc" DEFAULT_SCAN_INTERVAL = 120 STATE_ATTR_TORRENT_INFO = "torrent_info" diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json index 8a73eb90829..20ae6ca723d 100644 --- a/homeassistant/components/transmission/strings.json +++ b/homeassistant/components/transmission/strings.json @@ -5,9 +5,14 @@ "title": "Set up Transmission Client", "data": { "host": "[%key:common::config_flow::data::host%]", - "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", - "port": "[%key:common::config_flow::data::port%]" + "path": "[%key:common::config_flow::data::path%]", + "port": "[%key:common::config_flow::data::port%]", + "ssl": "[%key:common::config_flow::data::ssl%]", + "username": "[%key:common::config_flow::data::username%]" + }, + "data_description": { + "path": "The RPC request target path. E.g. `/transmission/rpc`" } }, "reauth_confirm": { diff --git a/tests/components/transmission/__init__.py b/tests/components/transmission/__init__.py index e371a3691a2..c4abba7b832 100644 --- a/tests/components/transmission/__init__.py +++ b/tests/components/transmission/__init__.py @@ -7,7 +7,17 @@ OLD_MOCK_CONFIG_DATA = { "password": "pass", "port": 9091, } -MOCK_CONFIG_DATA = { + +MOCK_CONFIG_DATA_VERSION_1_1 = { + "host": "0.0.0.0", + "username": "user", + "password": "pass", + "port": 9091, +} + +MOCK_CONFIG_DATA = { + "ssl": False, + "path": "/transmission/rpc", "host": "0.0.0.0", "username": "user", "password": "pass", diff --git a/tests/components/transmission/test_config_flow.py b/tests/components/transmission/test_config_flow.py index 451d3ec8b78..0e184ffc96b 100644 --- a/tests/components/transmission/test_config_flow.py +++ b/tests/components/transmission/test_config_flow.py @@ -157,10 +157,7 @@ async def test_error_on_connection_failure( async def test_reauth_success(hass: HomeAssistant) -> None: """Test we can reauth.""" - entry = MockConfigEntry( - domain=transmission.DOMAIN, - data=MOCK_CONFIG_DATA, - ) + entry = MockConfigEntry(domain=transmission.DOMAIN, data=MOCK_CONFIG_DATA) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/transmission/test_init.py b/tests/components/transmission/test_init.py index 63b7ac154ed..7efbaad76fb 100644 --- a/tests/components/transmission/test_init.py +++ b/tests/components/transmission/test_init.py @@ -11,12 +11,17 @@ from transmission_rpc.error import ( from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.components.transmission.const import DOMAIN +from homeassistant.components.transmission.const import ( + DEFAULT_PATH, + DEFAULT_SSL, + DOMAIN, +) from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_PATH, CONF_SSL from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from . import MOCK_CONFIG_DATA, OLD_MOCK_CONFIG_DATA +from . import MOCK_CONFIG_DATA, MOCK_CONFIG_DATA_VERSION_1_1, OLD_MOCK_CONFIG_DATA from tests.common import MockConfigEntry @@ -39,6 +44,27 @@ async def test_successful_config_entry(hass: HomeAssistant) -> None: assert entry.state == ConfigEntryState.LOADED +async def test_config_flow_entry_migrate_1_1_to_1_2(hass: HomeAssistant) -> None: + """Test that config flow entry is migrated correctly from v1.1 to v1.2.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_CONFIG_DATA_VERSION_1_1, + version=1, + minor_version=1, + ) + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + # Test that config entry is at the current version. + assert entry.version == 1 + assert entry.minor_version == 2 + + assert entry.data[CONF_SSL] == DEFAULT_SSL + assert entry.data[CONF_PATH] == DEFAULT_PATH + + async def test_setup_failed_connection_error( hass: HomeAssistant, mock_api: MagicMock ) -> None: From 25fe74aec538d5afdf365faee9fce103fa629050 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 21 Mar 2024 11:35:27 +0100 Subject: [PATCH 1351/1691] Remove AUX heating from demo climate (#113929) --- homeassistant/components/demo/climate.py | 22 ----------- tests/components/demo/test_climate.py | 48 ------------------------ 2 files changed, 70 deletions(-) diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index 666323d80d9..a7caede9b2d 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -45,7 +45,6 @@ async def async_setup_entry( swing_mode=None, hvac_mode=HVACMode.HEAT, hvac_action=HVACAction.HEATING, - aux=None, target_temp_high=None, target_temp_low=None, hvac_modes=[HVACMode.HEAT, HVACMode.OFF], @@ -63,7 +62,6 @@ async def async_setup_entry( swing_mode="off", hvac_mode=HVACMode.COOL, hvac_action=HVACAction.COOLING, - aux=False, target_temp_high=None, target_temp_low=None, hvac_modes=[cls for cls in HVACMode if cls != HVACMode.HEAT_COOL], @@ -82,7 +80,6 @@ async def async_setup_entry( swing_mode="auto", hvac_mode=HVACMode.HEAT_COOL, hvac_action=None, - aux=None, target_temp_high=24, target_temp_low=21, hvac_modes=[cls for cls in HVACMode if cls != HVACMode.HEAT], @@ -114,7 +111,6 @@ class DemoClimate(ClimateEntity): swing_mode: str | None, hvac_mode: HVACMode, hvac_action: HVACAction | None, - aux: bool | None, target_temp_high: float | None, target_temp_low: float | None, hvac_modes: list[HVACMode], @@ -133,8 +129,6 @@ class DemoClimate(ClimateEntity): self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY if swing_mode is not None: self._attr_supported_features |= ClimateEntityFeature.SWING_MODE - if aux is not None: - self._attr_supported_features |= ClimateEntityFeature.AUX_HEAT if HVACMode.HEAT_COOL in hvac_modes or HVACMode.AUTO in hvac_modes: self._attr_supported_features |= ( ClimateEntityFeature.TARGET_TEMPERATURE_RANGE @@ -152,7 +146,6 @@ class DemoClimate(ClimateEntity): self._current_fan_mode = fan_mode self._hvac_action = hvac_action self._hvac_mode = hvac_mode - self._aux = aux self._current_swing_mode = swing_mode self._fan_modes = ["on_low", "on_high", "auto_low", "auto_high", "off"] self._hvac_modes = hvac_modes @@ -229,11 +222,6 @@ class DemoClimate(ClimateEntity): """Return preset modes.""" return self._preset_modes - @property - def is_aux_heat(self) -> bool | None: - """Return true if aux heat is on.""" - return self._aux - @property def fan_mode(self) -> str | None: """Return the fan setting.""" @@ -292,13 +280,3 @@ class DemoClimate(ClimateEntity): """Update preset_mode on.""" self._preset = preset_mode self.async_write_ha_state() - - async def async_turn_aux_heat_on(self) -> None: - """Turn auxiliary heater on.""" - self._aux = True - self.async_write_ha_state() - - async def async_turn_aux_heat_off(self) -> None: - """Turn auxiliary heater off.""" - self._aux = False - self.async_write_ha_state() diff --git a/tests/components/demo/test_climate.py b/tests/components/demo/test_climate.py index 11e9b9c01dd..dc2bdc3c720 100644 --- a/tests/components/demo/test_climate.py +++ b/tests/components/demo/test_climate.py @@ -6,7 +6,6 @@ import pytest import voluptuous as vol from homeassistant.components.climate import ( - ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, @@ -25,7 +24,6 @@ from homeassistant.components.climate import ( DOMAIN, PRESET_AWAY, PRESET_ECO, - SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, @@ -40,8 +38,6 @@ from homeassistant.const import ( ATTR_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_OFF, - STATE_ON, Platform, ) from homeassistant.core import HomeAssistant @@ -81,7 +77,6 @@ def test_setup_params(hass: HomeAssistant) -> None: assert state.attributes.get(ATTR_HUMIDITY) == 67 assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 54 assert state.attributes.get(ATTR_SWING_MODE) == "off" - assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, HVACMode.HEAT, @@ -384,49 +379,6 @@ async def test_set_hold_mode_eco(hass: HomeAssistant) -> None: assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO -async def test_set_aux_heat_bad_attr(hass: HomeAssistant) -> None: - """Test setting the auxiliary heater without required attribute.""" - state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF - - with pytest.raises(vol.Invalid): - await hass.services.async_call( - DOMAIN, - SERVICE_SET_AUX_HEAT, - {ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_AUX_HEAT: None}, - blocking=True, - ) - - state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF - - -async def test_set_aux_heat_on(hass: HomeAssistant) -> None: - """Test setting the axillary heater on/true.""" - await hass.services.async_call( - DOMAIN, - SERVICE_SET_AUX_HEAT, - {ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_AUX_HEAT: True}, - blocking=True, - ) - - state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_AUX_HEAT) == STATE_ON - - -async def test_set_aux_heat_off(hass: HomeAssistant) -> None: - """Test setting the auxiliary heater off/false.""" - await hass.services.async_call( - DOMAIN, - SERVICE_SET_AUX_HEAT, - {ATTR_ENTITY_ID: ENTITY_CLIMATE, ATTR_AUX_HEAT: False}, - blocking=True, - ) - - state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_AUX_HEAT) == STATE_OFF - - async def test_turn_on(hass: HomeAssistant) -> None: """Test turn on device.""" await hass.services.async_call( From cd934c21f9279931ed29c8d03df036191ea557ea Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 21 Mar 2024 11:41:32 +0100 Subject: [PATCH 1352/1691] Add Reolink hdd/sd card storage sensor (#110961) --- homeassistant/components/reolink/icons.json | 6 ++ homeassistant/components/reolink/sensor.py | 64 ++++++++++++++++++- homeassistant/components/reolink/strings.json | 6 ++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reolink/icons.json b/homeassistant/components/reolink/icons.json index 15c3c00ecf8..fcf88fb6726 100644 --- a/homeassistant/components/reolink/icons.json +++ b/homeassistant/components/reolink/icons.json @@ -202,6 +202,12 @@ }, "wifi_signal": { "default": "mdi:wifi" + }, + "hdd_storage": { + "default": "mdi:harddisk" + }, + "sd_storage": { + "default": "mdi:micro-sd" } }, "siren": { diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py index 9bc72fc7b18..36363beaf80 100644 --- a/homeassistant/components/reolink/sensor.py +++ b/homeassistant/components/reolink/sensor.py @@ -15,7 +15,7 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EntityCategory +from homeassistant.const import PERCENTAGE, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -37,7 +37,7 @@ class ReolinkSensorEntityDescription( ): """A class that describes sensor entities for a camera channel.""" - value: Callable[[Host, int], int] + value: Callable[[Host, int], int | float] @dataclass(frozen=True, kw_only=True) @@ -75,6 +75,19 @@ HOST_SENSORS = ( ), ) +HDD_SENSORS = ( + ReolinkSensorEntityDescription( + key="storage", + cmd_key="GetHddInfo", + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + value=lambda api, idx: api.hdd_storage(idx), + supported=lambda api, idx: api.supported(None, "hdd"), + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -84,7 +97,9 @@ async def async_setup_entry( """Set up a Reolink IP Camera.""" reolink_data: ReolinkData = hass.data[DOMAIN][config_entry.entry_id] - entities: list[ReolinkSensorEntity | ReolinkHostSensorEntity] = [ + entities: list[ + ReolinkSensorEntity | ReolinkHostSensorEntity | ReolinkHddSensorEntity + ] = [ ReolinkSensorEntity(reolink_data, channel, entity_description) for entity_description in SENSORS for channel in reolink_data.host.api.channels @@ -97,6 +112,14 @@ async def async_setup_entry( if entity_description.supported(reolink_data.host.api) ] ) + entities.extend( + [ + ReolinkHddSensorEntity(reolink_data, hdd_index, entity_description) + for entity_description in HDD_SENSORS + for hdd_index in reolink_data.host.api.hdd_list + if entity_description.supported(reolink_data.host.api, hdd_index) + ] + ) async_add_entities(entities) @@ -139,3 +162,38 @@ class ReolinkHostSensorEntity(ReolinkHostCoordinatorEntity, SensorEntity): def native_value(self) -> StateType | date | datetime | Decimal: """Return the value reported by the sensor.""" return self.entity_description.value(self._host.api) + + +class ReolinkHddSensorEntity(ReolinkHostCoordinatorEntity, SensorEntity): + """Base sensor class for Reolink host sensors.""" + + entity_description: ReolinkSensorEntityDescription + + def __init__( + self, + reolink_data: ReolinkData, + hdd_index: int, + entity_description: ReolinkSensorEntityDescription, + ) -> None: + """Initialize Reolink host sensor.""" + self.entity_description = entity_description + super().__init__(reolink_data) + self._hdd_index = hdd_index + self._attr_translation_placeholders = {"hdd_index": str(hdd_index)} + self._attr_unique_id = ( + f"{self._host.unique_id}_{hdd_index}_{entity_description.key}" + ) + if self._host.api.hdd_type(hdd_index) == "HDD": + self._attr_translation_key = "hdd_storage" + else: + self._attr_translation_key = "sd_storage" + + @property + def native_value(self) -> StateType | date | datetime | Decimal: + """Return the value reported by the sensor.""" + return self.entity_description.value(self._host.api, self._hdd_index) + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._host.api.hdd_available(self._hdd_index) and super().available diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index b795ba16f64..2282289bdbc 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -396,6 +396,12 @@ }, "ptz_pan_position": { "name": "PTZ pan position" + }, + "hdd_storage": { + "name": "HDD {hdd_index} storage" + }, + "sd_storage": { + "name": "SD {hdd_index} storage" } }, "siren": { From ccfe0ba036feaadbb75d35765581c0fc86e67b13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:43:11 +0100 Subject: [PATCH 1353/1691] Bump Wandalen/wretry.action from 1.4.10 to 2.0.0 (#113888) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b771ecb340f..a334127dfcb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1070,7 +1070,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.4.10 + uses: Wandalen/wretry.action@v2.0.0 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1081,7 +1081,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.4.10 + uses: Wandalen/wretry.action@v2.0.0 with: action: codecov/codecov-action@v3.1.3 with: | From e23943debf0c3f29e2868ac77d4a9b519eebab5c Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 21 Mar 2024 11:44:05 +0100 Subject: [PATCH 1354/1691] Remove deprecated `hass.components` from flux switch tests and use fixture (#113875) --- tests/components/flux/test_switch.py | 172 ++++++++++++++++----------- 1 file changed, 104 insertions(+), 68 deletions(-) diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index 1e783c6b649..e594fe5e7ee 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -23,6 +23,7 @@ from tests.common import ( async_mock_service, mock_restore_cache, ) +from tests.components.light.common import MockLight, SetupLightPlatformCallable @pytest.fixture(autouse=True) @@ -136,17 +137,19 @@ async def test_invalid_config_no_lights(hass: HomeAssistant) -> None: async def test_flux_when_switch_is_off( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch when it is off.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -187,17 +190,19 @@ async def test_flux_when_switch_is_off( async def test_flux_before_sunrise( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch before sunrise.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -246,17 +251,19 @@ async def test_flux_before_sunrise( async def test_flux_before_sunrise_known_location( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch before sunrise.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -304,17 +311,19 @@ async def test_flux_before_sunrise_known_location( async def test_flux_after_sunrise_before_sunset( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunrise and before sunset.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -362,17 +371,19 @@ async def test_flux_after_sunrise_before_sunset( async def test_flux_after_sunset_before_stop( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunset and before stop.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -421,17 +432,19 @@ async def test_flux_after_sunset_before_stop( async def test_flux_after_stop_before_sunrise( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after stop and before sunrise.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -479,17 +492,19 @@ async def test_flux_after_stop_before_sunrise( async def test_flux_with_custom_start_stop_times( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux with custom start and stop times.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -539,20 +554,22 @@ async def test_flux_with_custom_start_stop_times( async def test_flux_before_sunrise_stop_next_day( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch before sunrise. This test has the stop_time on the next day (after midnight). """ - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -601,20 +618,22 @@ async def test_flux_before_sunrise_stop_next_day( async def test_flux_after_sunrise_before_sunset_stop_next_day( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunrise and before sunset. This test has the stop_time on the next day (after midnight). """ - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -664,20 +683,23 @@ async def test_flux_after_sunrise_before_sunset_stop_next_day( @pytest.mark.parametrize("x", [0, 1]) async def test_flux_after_sunset_before_midnight_stop_next_day( - hass: HomeAssistant, x, enable_custom_integrations: None + hass: HomeAssistant, + x, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunset and before stop. This test has the stop_time on the next day (after midnight). """ - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -726,20 +748,22 @@ async def test_flux_after_sunset_before_midnight_stop_next_day( async def test_flux_after_sunset_after_midnight_stop_next_day( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunset and before stop. This test has the stop_time on the next day (after midnight). """ - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -788,20 +812,22 @@ async def test_flux_after_sunset_after_midnight_stop_next_day( async def test_flux_after_stop_before_sunrise_stop_next_day( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after stop and before sunrise. This test has the stop_time on the next day (after midnight). """ - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -850,17 +876,19 @@ async def test_flux_after_stop_before_sunrise_stop_next_day( async def test_flux_with_custom_colortemps( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux with custom start and stop colortemps.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -911,17 +939,19 @@ async def test_flux_with_custom_colortemps( async def test_flux_with_custom_brightness( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux with custom start and stop colortemps.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -971,17 +1001,19 @@ async def test_flux_with_custom_brightness( async def test_flux_with_multiple_lights( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch with multiple light entities.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1, ent2, ent3 = platform.ENTITIES + ent1, ent2, ent3 = mock_light_entities await hass.services.async_call( light.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ent2.entity_id}, blocking=True @@ -1052,17 +1084,19 @@ async def test_flux_with_multiple_lights( async def test_flux_with_mired( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch´s mode mired.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) @@ -1109,17 +1143,19 @@ async def test_flux_with_mired( async def test_flux_with_rgb( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], ) -> None: """Test the flux switch´s mode rgb.""" - platform = getattr(hass.components, "test.light") - platform.init() + setup_light_platform(hass, mock_light_entities) + assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) await hass.async_block_till_done() - ent1 = platform.ENTITIES[0] + ent1 = mock_light_entities[0] # Verify initial state of light state = hass.states.get(ent1.entity_id) From e5fa6f01763448dffc0ba1048db46617092ede32 Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:08:11 +0100 Subject: [PATCH 1355/1691] Add Bluetooth support to La Marzocco integration (#108287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init * init tests * linting * checks * tests, linting * pylint * add tests * switch tests * add water heater tests * change icons * extra args cleanup * moar tests * services tests * remove extra platforms * test for unique id * back to single instance * add diagnostics * remove extra platforms * test for unique id * back to single instance * Add better connection management for Idasen Desk (#102135) * Return 'None' for light attributes when off instead of removing them (#101946) * Bump home-assistant-bluetooth to 1.10.4 (#102268) * Bump orjson to 3.9.9 (#102267) * Bump opower to 0.0.37 (#102265) * Bump Python-Roborock to 0.35.0 (#102275) * Add CodeQL CI Job (#102273) * Remove unused dsmr sensors (#102223) * rebase messed up conftest * more tests for init * add client to coveragerc * add client to coveragerc * next lmcloud version * strict typing * more typing * allow multiple machines * remove unneeded var * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/diagnostics.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/__init__.py Co-authored-by: Robert Resch * PR suggestions * remove base exception * Update manifest.json * update lmcloud * update lmcloud * remove ignore * selection bugfix for machines with space in name * bugfix temps * add options flow * send out full user input * remove options flow * split the tests to avoid timeouts * use selectoptionsdict for selection * removing rccoleman * improve test coverage to 100% * Update config_flow.py Co-authored-by: Robert Resch * Update config_flow.py Co-authored-by: Robert Resch * Update config_flow.py Co-authored-by: Robert Resch * autoselect cloud machine for discovered machine * move default values to 3rd party lib * bring property changes from lmcloud * moving things to lmcloud * move validation to method * move more things to lmcloud * remove unused const * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/__init__.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/__init__.py Co-authored-by: Robert Resch * remove callback from coordinator * remove waterheater, add switch * improvement to background task * next lmcloud * adapt to lib changes * Update homeassistant/components/lamarzocco/strings.json Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/strings.json Co-authored-by: Robert Resch * requested changes * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Robert Resch * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Robert Resch * Update tests/components/lamarzocco/test_config_flow.py Co-authored-by: Robert Resch * Update tests/components/lamarzocco/test_config_flow.py Co-authored-by: Robert Resch * some requested changes * changes * requested changes * move steam boiler to controls * fix: remove entities from GS3MP model + tests * remove dataclass decorator * next lmcloud version * improvements * move reauth to user step * improve config flow * remove asserts in favor of runtimeerrors * undo conftest comment * make duc return none * Update homeassistant/components/lamarzocco/switch.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/lamarzocco/config_flow.py Co-authored-by: Joost Lekkerkerker * remove diagnostics, changes * refine config flow * remove runtimeerrors in favor of asserts * move initialization of lm_client to coordinator * remove things from lmclient * remove lm_client * remove lm_client * bump lm version * correctly set initialized for tests * move exception handling inside init + tests * add test for switch without bluetooth on * bump lmcloud * pass httpx client to LMLocalAPI * add call function to reduce code * switch to snapshot testing * remove bluetooth * bump version * cleanup import * remove unused const * set correct integration_type * correct default selection in CF * reduce unnecessary tests by fixture change * use other json loads helpers * move prebrew/infusion to select entity * bump lmcloud * Update coordinator.py Co-authored-by: Joost Lekkerkerker * Update coordinator.py Co-authored-by: Joost Lekkerkerker * Update coordinator.py Co-authored-by: Joost Lekkerkerker * Update entity.py Co-authored-by: Joost Lekkerkerker * Update entity.py Co-authored-by: Joost Lekkerkerker * requested feedback * step description, bump lmcloud * create init integration functino * revert * ruff * remove leftover BT test * make main switch main entity * bump lmcloud * re-add bluetooth * improve * bump firmware (again) * correct test * Update homeassistant/components/lamarzocco/coordinator.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/lamarzocco/entity.py Co-authored-by: Joost Lekkerkerker * Update homeassistant/components/lamarzocco/strings.json Co-authored-by: Joost Lekkerkerker * separate device test * add BT to entites * fix import * docstring * minor * fix rebase * get device from discovered devices * tweak * change tests * switch to dict * switch to options * fix * fix --------- Co-authored-by: Abílio Costa Co-authored-by: Paul Bottein Co-authored-by: J. Nick Koston Co-authored-by: tronikos Co-authored-by: Luke Lashley Co-authored-by: Franck Nijhof Co-authored-by: dupondje Co-authored-by: Robert Resch Co-authored-by: Joost Lekkerkerker --- .../components/lamarzocco/__init__.py | 5 + .../components/lamarzocco/config_flow.py | 100 +++++++++++- homeassistant/components/lamarzocco/const.py | 2 + .../components/lamarzocco/coordinator.py | 66 +++++++- .../components/lamarzocco/manifest.json | 15 ++ homeassistant/components/lamarzocco/number.py | 8 +- homeassistant/components/lamarzocco/select.py | 2 +- .../components/lamarzocco/strings.json | 10 ++ homeassistant/components/lamarzocco/switch.py | 8 +- homeassistant/generated/bluetooth.py | 16 ++ tests/components/lamarzocco/__init__.py | 31 ++++ tests/components/lamarzocco/conftest.py | 47 +++--- .../lamarzocco/snapshots/test_switch.ambr | 146 +++++++++++++++++- .../components/lamarzocco/test_config_flow.py | 138 ++++++++++++++++- tests/components/lamarzocco/test_init.py | 40 ++++- tests/components/lamarzocco/test_number.py | 11 +- tests/components/lamarzocco/test_select.py | 2 +- tests/components/lamarzocco/test_switch.py | 45 +++++- 18 files changed, 633 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/lamarzocco/__init__.py b/homeassistant/components/lamarzocco/__init__.py index 0cdacc8d2e4..d2a7bbb6216 100644 --- a/homeassistant/components/lamarzocco/__init__.py +++ b/homeassistant/components/lamarzocco/__init__.py @@ -29,6 +29,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: + await hass.config_entries.async_reload(entry.entry_id) + + entry.async_on_unload(entry.add_update_listener(update_listener)) + return True diff --git a/homeassistant/components/lamarzocco/config_flow.py b/homeassistant/components/lamarzocco/config_flow.py index de960f364ce..3cacdae1749 100644 --- a/homeassistant/components/lamarzocco/config_flow.py +++ b/homeassistant/components/lamarzocco/config_flow.py @@ -8,8 +8,22 @@ from lmcloud import LMCloud as LaMarzoccoClient from lmcloud.exceptions import AuthFail, RequestNotSuccessful import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.components.bluetooth import BluetoothServiceInfo +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, + OptionsFlowWithConfigEntry, +) +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, +) +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import ( SelectOptionDict, @@ -18,7 +32,7 @@ from homeassistant.helpers.selector import ( SelectSelectorMode, ) -from .const import CONF_MACHINE, DOMAIN +from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -32,6 +46,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): self.reauth_entry: ConfigEntry | None = None self._config: dict[str, Any] = {} self._machines: list[tuple[str, str]] = [] + self._discovered: dict[str, str] = {} async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -47,6 +62,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): data = { **data, **user_input, + **self._discovered, } lm = LaMarzoccoClient() @@ -71,6 +87,18 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): self.reauth_entry.entry_id ) return self.async_abort(reason="reauth_successful") + if self._discovered: + serials = [machine[0] for machine in self._machines] + if self._discovered[CONF_MACHINE] not in serials: + errors["base"] = "machine_not_found" + else: + self._config = data + return self.async_show_form( + step_id="machine_selection", + data_schema=vol.Schema( + {vol.Optional(CONF_HOST): cv.string} + ), + ) if not errors: self._config = data @@ -93,9 +121,12 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): """Let user select machine to connect to.""" errors: dict[str, str] = {} if user_input: - serial_number = user_input[CONF_MACHINE] - await self.async_set_unique_id(serial_number) - self._abort_if_unique_id_configured() + if not self._discovered: + serial_number = user_input[CONF_MACHINE] + await self.async_set_unique_id(serial_number) + self._abort_if_unique_id_configured() + else: + serial_number = self._discovered[CONF_MACHINE] # validate local connection if host is provided if user_input.get(CONF_HOST): @@ -141,6 +172,30 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) + async def async_step_bluetooth( + self, discovery_info: BluetoothServiceInfo + ) -> ConfigFlowResult: + """Handle a flow initialized by discovery over Bluetooth.""" + address = discovery_info.address + name = discovery_info.name + + _LOGGER.debug( + "Discovered La Marzocco machine %s through Bluetooth at address %s", + name, + address, + ) + + self._discovered[CONF_NAME] = name + self._discovered[CONF_MAC] = address + + serial = name.split("_")[1] + self._discovered[CONF_MACHINE] = serial + + await self.async_set_unique_id(serial) + self._abort_if_unique_id_configured() + + return await self.async_step_user() + async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: @@ -165,3 +220,36 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN): ) return await self.async_step_user(user_input) + + @staticmethod + @callback + def async_get_options_flow( + config_entry: ConfigEntry, + ) -> OptionsFlow: + """Create the options flow.""" + return LmOptionsFlowHandler(config_entry) + + +class LmOptionsFlowHandler(OptionsFlowWithConfigEntry): + """Handles options flow for the component.""" + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Manage the options for the custom component.""" + if user_input: + return self.async_create_entry(title="", data=user_input) + + options_schema = vol.Schema( + { + vol.Optional( + CONF_USE_BLUETOOTH, + default=self.options.get(CONF_USE_BLUETOOTH, True), + ): cv.boolean, + } + ) + + return self.async_show_form( + step_id="init", + data_schema=options_schema, + ) diff --git a/homeassistant/components/lamarzocco/const.py b/homeassistant/components/lamarzocco/const.py index 2afd1c4cf48..87878ea5089 100644 --- a/homeassistant/components/lamarzocco/const.py +++ b/homeassistant/components/lamarzocco/const.py @@ -5,3 +5,5 @@ from typing import Final DOMAIN: Final = "lamarzocco" CONF_MACHINE: Final = "machine" + +CONF_USE_BLUETOOTH = "use_bluetooth" diff --git a/homeassistant/components/lamarzocco/coordinator.py b/homeassistant/components/lamarzocco/coordinator.py index 85fb8bb8854..7901b0bb3fa 100644 --- a/homeassistant/components/lamarzocco/coordinator.py +++ b/homeassistant/components/lamarzocco/coordinator.py @@ -5,23 +5,32 @@ from datetime import timedelta import logging from typing import Any +from bleak.backends.device import BLEDevice from lmcloud import LMCloud as LaMarzoccoClient +from lmcloud.const import BT_MODEL_NAMES from lmcloud.exceptions import AuthFail, RequestNotSuccessful +from homeassistant.components.bluetooth import ( + async_ble_device_from_address, + async_discovered_service_info, +) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import CONF_MACHINE, DOMAIN +from .const import CONF_MACHINE, CONF_USE_BLUETOOTH, DOMAIN SCAN_INTERVAL = timedelta(seconds=30) _LOGGER = logging.getLogger(__name__) +NAME_PREFIXES = tuple(BT_MODEL_NAMES) + + class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): """Class to handle fetching data from the La Marzocco API centrally.""" @@ -36,6 +45,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): self.local_connection_configured = ( self.config_entry.data.get(CONF_HOST) is not None ) + self._use_bluetooth = False async def _async_update_data(self) -> None: """Fetch data from API endpoint.""" @@ -80,6 +90,46 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): name="lm_websocket_task", ) + # initialize Bluetooth + if self.config_entry.options.get(CONF_USE_BLUETOOTH, True): + + def bluetooth_configured() -> bool: + return self.config_entry.data.get( + CONF_MAC, "" + ) and self.config_entry.data.get(CONF_NAME, "") + + if not bluetooth_configured(): + machine = self.config_entry.data[CONF_MACHINE] + for discovery_info in async_discovered_service_info(self.hass): + if ( + (name := discovery_info.name) + and name.startswith(NAME_PREFIXES) + and name.split("_")[1] == machine + ): + _LOGGER.debug( + "Found Bluetooth device, configuring with Bluetooth" + ) + # found a device, add MAC address to config entry + self.hass.config_entries.async_update_entry( + self.config_entry, + data={ + **self.config_entry.data, + CONF_MAC: discovery_info.address, + CONF_NAME: discovery_info.name, + }, + ) + break + + if bluetooth_configured(): + # config entry contains BT config + _LOGGER.debug("Initializing with known Bluetooth device") + await self.lm.init_bluetooth_with_known_device( + self.config_entry.data[CONF_USERNAME], + self.config_entry.data.get(CONF_MAC, ""), + self.config_entry.data.get(CONF_NAME, ""), + ) + self._use_bluetooth = True + self.lm.initialized = True async def _async_handle_request( @@ -98,3 +148,15 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]): except RequestNotSuccessful as ex: _LOGGER.debug(ex, exc_info=True) raise UpdateFailed("Querying API failed. Error: %s" % ex) from ex + + def async_get_ble_device(self) -> BLEDevice | None: + """Get a Bleak Client for the machine.""" + # according to HA best practices, we should not reuse the same client + # get a new BLE device from hass and init a new Bleak Client with it + if not self._use_bluetooth: + return None + + return async_ble_device_from_address( + self.hass, + self.lm.lm_bluetooth.address, + ) diff --git a/homeassistant/components/lamarzocco/manifest.json b/homeassistant/components/lamarzocco/manifest.json index 8dd8e1294b0..ec6068e1988 100644 --- a/homeassistant/components/lamarzocco/manifest.json +++ b/homeassistant/components/lamarzocco/manifest.json @@ -1,8 +1,23 @@ { "domain": "lamarzocco", "name": "La Marzocco", + "bluetooth": [ + { + "local_name": "MICRA_*" + }, + { + "local_name": "MINI_*" + }, + { + "local_name": "GS3_*" + }, + { + "local_name": "GS3AV_*" + } + ], "codeowners": ["@zweckj"], "config_flow": true, + "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/lamarzocco", "integration_type": "device", "iot_class": "cloud_polling", diff --git a/homeassistant/components/lamarzocco/number.py b/homeassistant/components/lamarzocco/number.py index 88a06a0c9d0..af5256bc77b 100644 --- a/homeassistant/components/lamarzocco/number.py +++ b/homeassistant/components/lamarzocco/number.py @@ -63,7 +63,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( native_step=PRECISION_TENTHS, native_min_value=85, native_max_value=104, - set_value_fn=lambda coordinator, temp: coordinator.lm.set_coffee_temp(temp), + set_value_fn=lambda coordinator, temp: coordinator.lm.set_coffee_temp( + temp, coordinator.async_get_ble_device() + ), native_value_fn=lambda lm: lm.current_status["coffee_set_temp"], ), LaMarzoccoNumberEntityDescription( @@ -74,7 +76,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = ( native_step=PRECISION_WHOLE, native_min_value=126, native_max_value=131, - set_value_fn=lambda coordinator, temp: coordinator.lm.set_steam_temp(int(temp)), + set_value_fn=lambda coordinator, temp: coordinator.lm.set_steam_temp( + int(temp), coordinator.async_get_ble_device() + ), native_value_fn=lambda lm: lm.current_status["steam_set_temp"], supported_fn=lambda coordinator: coordinator.lm.model_name in ( diff --git a/homeassistant/components/lamarzocco/select.py b/homeassistant/components/lamarzocco/select.py index 1e70000a479..f063f8e6336 100644 --- a/homeassistant/components/lamarzocco/select.py +++ b/homeassistant/components/lamarzocco/select.py @@ -36,7 +36,7 @@ ENTITIES: tuple[LaMarzoccoSelectEntityDescription, ...] = ( translation_key="steam_temp_select", options=["1", "2", "3"], select_option_fn=lambda coordinator, option: coordinator.lm.set_steam_level( - int(option) + int(option), coordinator.async_get_ble_device() ), current_option_fn=lambda lm: lm.current_status["steam_level_set"], supported_fn=lambda coordinator: coordinator.lm.model_name diff --git a/homeassistant/components/lamarzocco/strings.json b/homeassistant/components/lamarzocco/strings.json index 57421dfee83..03ce2eb93e8 100644 --- a/homeassistant/components/lamarzocco/strings.json +++ b/homeassistant/components/lamarzocco/strings.json @@ -42,6 +42,16 @@ } } }, + "options": { + "step": { + "init": { + "data": { + "title": "Update Configuration", + "use_bluetooth": "Use Bluetooth" + } + } + } + }, "entity": { "binary_sensor": { "brew_active": { diff --git a/homeassistant/components/lamarzocco/switch.py b/homeassistant/components/lamarzocco/switch.py index d8f5edec6b9..dd647bf4582 100644 --- a/homeassistant/components/lamarzocco/switch.py +++ b/homeassistant/components/lamarzocco/switch.py @@ -31,7 +31,9 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = ( key="main", translation_key="main", name=None, - control_fn=lambda coordinator, state: coordinator.lm.set_power(state), + control_fn=lambda coordinator, state: coordinator.lm.set_power( + state, coordinator.async_get_ble_device() + ), is_on_fn=lambda coordinator: coordinator.lm.current_status["power"], ), LaMarzoccoSwitchEntityDescription( @@ -47,7 +49,9 @@ ENTITIES: tuple[LaMarzoccoSwitchEntityDescription, ...] = ( LaMarzoccoSwitchEntityDescription( key="steam_boiler_enable", translation_key="steam_boiler", - control_fn=lambda coordinator, state: coordinator.lm.set_steam(state), + control_fn=lambda coordinator, state: coordinator.lm.set_steam( + state, coordinator.async_get_ble_device() + ), is_on_fn=lambda coordinator: coordinator.lm.current_status[ "steam_boiler_enable" ], diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index c0b21c0a81d..d1972e703b4 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -266,6 +266,22 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [ "domain": "keymitt_ble", "local_name": "mib*", }, + { + "domain": "lamarzocco", + "local_name": "MICRA_*", + }, + { + "domain": "lamarzocco", + "local_name": "MINI_*", + }, + { + "domain": "lamarzocco", + "local_name": "GS3_*", + }, + { + "domain": "lamarzocco", + "local_name": "GS3AV_*", + }, { "domain": "ld2410_ble", "local_name": "HLK-LD2410B_*", diff --git a/tests/components/lamarzocco/__init__.py b/tests/components/lamarzocco/__init__.py index 1e7d5ed0148..ed4d2e0990e 100644 --- a/tests/components/lamarzocco/__init__.py +++ b/tests/components/lamarzocco/__init__.py @@ -1,7 +1,10 @@ """Mock inputs for tests.""" +from lmcloud.const import LaMarzoccoModel + from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from tests.common import MockConfigEntry @@ -15,6 +18,13 @@ PASSWORD_SELECTION = { USER_INPUT = PASSWORD_SELECTION | {CONF_USERNAME: "username"} +MODEL_DICT = { + LaMarzoccoModel.GS3_AV: ("GS01234", "GS3 AV"), + LaMarzoccoModel.GS3_MP: ("GS01234", "GS3 MP"), + LaMarzoccoModel.LINEA_MICRA: ("MR01234", "Linea Micra"), + LaMarzoccoModel.LINEA_MINI: ("LM01234", "Linea Mini"), +} + async def async_init_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry @@ -22,3 +32,24 @@ async def async_init_integration( """Set up the La Marzocco integration for testing.""" await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() + + +def get_bluetooth_service_info( + model: LaMarzoccoModel, serial: str +) -> BluetoothServiceInfo: + """Return a mocked BluetoothServiceInfo.""" + if model in (LaMarzoccoModel.GS3_AV, LaMarzoccoModel.GS3_MP): + name = f"GS3_{serial}" + elif model == LaMarzoccoModel.LINEA_MINI: + name = f"MINI_{serial}" + elif model == LaMarzoccoModel.LINEA_MICRA: + name = f"MICRA_{serial}" + return BluetoothServiceInfo( + name=name, + address="aa:bb:cc:dd:ee:ff", + rssi=-63, + manufacturer_data={}, + service_data={}, + service_uuids=[], + source="local", + ) diff --git a/tests/components/lamarzocco/conftest.py b/tests/components/lamarzocco/conftest.py index 17d605a0dde..d76e44d60af 100644 --- a/tests/components/lamarzocco/conftest.py +++ b/tests/components/lamarzocco/conftest.py @@ -7,10 +7,10 @@ from lmcloud.const import LaMarzoccoModel import pytest from homeassistant.components.lamarzocco.const import CONF_MACHINE, DOMAIN -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.core import HomeAssistant -from . import USER_INPUT, async_init_integration +from . import MODEL_DICT, USER_INPUT, async_init_integration from tests.common import ( MockConfigEntry, @@ -28,7 +28,12 @@ def mock_config_entry( title="My LaMarzocco", domain=DOMAIN, data=USER_INPUT - | {CONF_MACHINE: mock_lamarzocco.serial_number, CONF_HOST: "host"}, + | { + CONF_MACHINE: mock_lamarzocco.serial_number, + CONF_HOST: "host", + CONF_NAME: "name", + CONF_MAC: "mac", + }, unique_id=mock_lamarzocco.serial_number, ) entry.add_to_hass(hass) @@ -58,25 +63,17 @@ def mock_lamarzocco( """Return a mocked LM client.""" model_name = device_fixture - if model_name == LaMarzoccoModel.GS3_AV: - serial_number = "GS01234" - true_model_name = "GS3 AV" - elif model_name == LaMarzoccoModel.GS3_MP: - serial_number = "GS01234" - true_model_name = "GS3 MP" - elif model_name == LaMarzoccoModel.LINEA_MICRA: - serial_number = "MR01234" - true_model_name = "Linea Micra" - elif model_name == LaMarzoccoModel.LINEA_MINI: - serial_number = "LM01234" - true_model_name = "Linea Mini" + (serial_number, true_model_name) = MODEL_DICT[model_name] - with patch( - "homeassistant.components.lamarzocco.coordinator.LaMarzoccoClient", - autospec=True, - ) as lamarzocco_mock, patch( - "homeassistant.components.lamarzocco.config_flow.LaMarzoccoClient", - new=lamarzocco_mock, + with ( + patch( + "homeassistant.components.lamarzocco.coordinator.LaMarzoccoClient", + autospec=True, + ) as lamarzocco_mock, + patch( + "homeassistant.components.lamarzocco.config_flow.LaMarzoccoClient", + new=lamarzocco_mock, + ), ): lamarzocco = lamarzocco_mock.return_value @@ -118,6 +115,9 @@ def mock_lamarzocco( lamarzocco.lm_local_api.websocket_connect = websocket_connect_mock + lamarzocco.lm_bluetooth = MagicMock() + lamarzocco.lm_bluetooth.address = "AA:BB:CC:DD:EE:FF" + yield lamarzocco @@ -130,3 +130,8 @@ def remove_local_connection( del data[CONF_HOST] hass.config_entries.async_update_entry(mock_config_entry, data=data) return mock_config_entry + + +@pytest.fixture(autouse=True) +def mock_bluetooth(enable_bluetooth): + """Auto mock bluetooth.""" diff --git a/tests/components/lamarzocco/snapshots/test_switch.ambr b/tests/components/lamarzocco/snapshots/test_switch.ambr index 1639e8fce94..59053c5c478 100644 --- a/tests/components/lamarzocco/snapshots/test_switch.ambr +++ b/tests/components/lamarzocco/snapshots/test_switch.ambr @@ -29,7 +29,7 @@ 'via_device_id': None, }) # --- -# name: test_switches[-set_power] +# name: test_switches[-set_power-args_on0-args_off0] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'GS01234', @@ -42,7 +42,7 @@ 'state': 'on', }) # --- -# name: test_switches[-set_power].1 +# name: test_switches[-set_power-args_on0-args_off0].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -75,7 +75,51 @@ 'unit_of_measurement': None, }) # --- -# name: test_switches[_auto_on_off-set_auto_on_off_global] +# name: test_switches[-set_power-kwargs_on0-kwargs_off0] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234', + 'icon': 'mdi:power', + }), + 'context': , + 'entity_id': 'switch.gs01234', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switches[-set_power-kwargs_on0-kwargs_off0].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.gs01234', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:power', + 'original_name': None, + 'platform': 'lamarzocco', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'GS01234_main', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[_auto_on_off-set_auto_on_off_global-args_on1-args_off1] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'GS01234 Auto on/off', @@ -88,7 +132,7 @@ 'state': 'on', }) # --- -# name: test_switches[_auto_on_off-set_auto_on_off_global].1 +# name: test_switches[_auto_on_off-set_auto_on_off_global-args_on1-args_off1].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -121,7 +165,51 @@ 'unit_of_measurement': None, }) # --- -# name: test_switches[_steam_boiler-set_steam] +# name: test_switches[_auto_on_off-set_auto_on_off_global-kwargs_on1-kwargs_off1] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Auto on/off', + 'icon': 'mdi:alarm', + }), + 'context': , + 'entity_id': 'switch.gs01234_auto_on_off', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switches[_auto_on_off-set_auto_on_off_global-kwargs_on1-kwargs_off1].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.gs01234_auto_on_off', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:alarm', + 'original_name': 'Auto on/off', + 'platform': 'lamarzocco', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'auto_on_off', + 'unique_id': 'GS01234_auto_on_off', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'GS01234 Steam boiler', @@ -134,7 +222,53 @@ 'state': 'on', }) # --- -# name: test_switches[_steam_boiler-set_steam].1 +# name: test_switches[_steam_boiler-set_steam-args_on2-args_off2].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.gs01234_steam_boiler', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Steam boiler', + 'platform': 'lamarzocco', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'steam_boiler', + 'unique_id': 'GS01234_steam_boiler_enable', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[_steam_boiler-set_steam-kwargs_on2-kwargs_off2] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'GS01234 Steam boiler', + 'icon': 'mdi:water-boiler', + }), + 'context': , + 'entity_id': 'switch.gs01234_steam_boiler', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switches[_steam_boiler-set_steam-kwargs_on2-kwargs_off2].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), diff --git a/tests/components/lamarzocco/test_config_flow.py b/tests/components/lamarzocco/test_config_flow.py index ffdf43df3ae..37d9c9a3e95 100644 --- a/tests/components/lamarzocco/test_config_flow.py +++ b/tests/components/lamarzocco/test_config_flow.py @@ -5,13 +5,17 @@ from unittest.mock import MagicMock from lmcloud.exceptions import AuthFail, RequestNotSuccessful from homeassistant import config_entries -from homeassistant.components.lamarzocco.const import CONF_MACHINE, DOMAIN +from homeassistant.components.lamarzocco.const import ( + CONF_MACHINE, + CONF_USE_BLUETOOTH, + DOMAIN, +) from homeassistant.config_entries import SOURCE_REAUTH -from homeassistant.const import CONF_HOST, CONF_PASSWORD +from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PASSWORD from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult, FlowResultType -from . import USER_INPUT +from . import USER_INPUT, async_init_integration, get_bluetooth_service_info from tests.common import MockConfigEntry @@ -233,3 +237,131 @@ async def test_reauth_flow( assert result2["reason"] == "reauth_successful" assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1 assert mock_config_entry.data[CONF_PASSWORD] == "new_password" + + +async def test_bluetooth_discovery( + hass: HomeAssistant, mock_lamarzocco: MagicMock +) -> None: + """Test bluetooth discovery.""" + service_info = get_bluetooth_service_info( + mock_lamarzocco.model_name, mock_lamarzocco.serial_number + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, data=service_info + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + assert result2["type"] == FlowResultType.FORM + assert result2["step_id"] == "machine_selection" + + assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1 + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + CONF_HOST: "192.168.1.1", + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == FlowResultType.CREATE_ENTRY + + assert result3["title"] == mock_lamarzocco.serial_number + assert result3["data"] == { + **USER_INPUT, + CONF_HOST: "192.168.1.1", + CONF_MACHINE: mock_lamarzocco.serial_number, + CONF_NAME: service_info.name, + CONF_MAC: "aa:bb:cc:dd:ee:ff", + } + + assert len(mock_lamarzocco.check_local_connection.mock_calls) == 1 + + +async def test_bluetooth_discovery_errors( + hass: HomeAssistant, mock_lamarzocco: MagicMock +) -> None: + """Test bluetooth discovery errors.""" + service_info = get_bluetooth_service_info( + mock_lamarzocco.model_name, mock_lamarzocco.serial_number + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_BLUETOOTH}, + data=service_info, + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + + mock_lamarzocco.get_all_machines.return_value = [("GS98765", "GS3 MP")] + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + assert result2["type"] == FlowResultType.FORM + assert result2["errors"] == {"base": "machine_not_found"} + assert len(mock_lamarzocco.get_all_machines.mock_calls) == 1 + + mock_lamarzocco.get_all_machines.return_value = [ + (mock_lamarzocco.serial_number, mock_lamarzocco.model_name) + ] + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + USER_INPUT, + ) + assert result2["type"] == FlowResultType.FORM + assert result2["step_id"] == "machine_selection" + assert len(mock_lamarzocco.get_all_machines.mock_calls) == 2 + + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + { + CONF_HOST: "192.168.1.1", + }, + ) + await hass.async_block_till_done() + + assert result3["type"] == FlowResultType.CREATE_ENTRY + + assert result3["title"] == mock_lamarzocco.serial_number + assert result3["data"] == { + **USER_INPUT, + CONF_HOST: "192.168.1.1", + CONF_MACHINE: mock_lamarzocco.serial_number, + CONF_NAME: service_info.name, + CONF_MAC: "aa:bb:cc:dd:ee:ff", + } + + +async def test_options_flow( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test options flow.""" + await async_init_integration(hass, mock_config_entry) + assert mock_config_entry.state is config_entries.ConfigEntryState.LOADED + + result = await hass.config_entries.options.async_init(mock_config_entry.entry_id) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "init" + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_USE_BLUETOOTH: False, + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["data"] == { + CONF_USE_BLUETOOTH: False, + } diff --git a/tests/components/lamarzocco/test_init.py b/tests/components/lamarzocco/test_init.py index 5647129b5a5..a4bc25f64af 100644 --- a/tests/components/lamarzocco/test_init.py +++ b/tests/components/lamarzocco/test_init.py @@ -1,13 +1,16 @@ """Test initialization of lamarzocco.""" -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from lmcloud.exceptions import AuthFail, RequestNotSuccessful from homeassistant.components.lamarzocco.const import DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState +from homeassistant.const import CONF_MAC, CONF_NAME from homeassistant.core import HomeAssistant +from . import async_init_integration, get_bluetooth_service_info + from tests.common import MockConfigEntry @@ -17,8 +20,7 @@ async def test_load_unload_config_entry( mock_lamarzocco: MagicMock, ) -> None: """Test loading and unloading the integration.""" - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() + await async_init_integration(hass, mock_config_entry) assert mock_config_entry.state is ConfigEntryState.LOADED @@ -36,8 +38,7 @@ async def test_config_entry_not_ready( """Test the La Marzocco configuration entry not ready.""" mock_lamarzocco.update_local_machine_status.side_effect = RequestNotSuccessful("") - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() + await async_init_integration(hass, mock_config_entry) assert len(mock_lamarzocco.update_local_machine_status.mock_calls) == 1 assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY @@ -50,8 +51,7 @@ async def test_invalid_auth( ) -> None: """Test auth error during setup.""" mock_lamarzocco.update_local_machine_status.side_effect = AuthFail("") - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() + await async_init_integration(hass, mock_config_entry) assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR assert len(mock_lamarzocco.update_local_machine_status.mock_calls) == 1 @@ -66,3 +66,29 @@ async def test_invalid_auth( assert "context" in flow assert flow["context"].get("source") == SOURCE_REAUTH assert flow["context"].get("entry_id") == mock_config_entry.entry_id + + +async def test_bluetooth_is_set_from_discovery( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_lamarzocco: MagicMock, +) -> None: + """Assert we're not searching for a new BT device when we already found one previously.""" + + # remove the bluetooth configuration from entry + data = mock_config_entry.data.copy() + del data[CONF_NAME] + del data[CONF_MAC] + hass.config_entries.async_update_entry(mock_config_entry, data=data) + + service_info = get_bluetooth_service_info( + mock_lamarzocco.model_name, mock_lamarzocco.serial_number + ) + with patch( + "homeassistant.components.lamarzocco.coordinator.async_discovered_service_info", + return_value=[service_info], + ): + await async_init_integration(hass, mock_config_entry) + mock_lamarzocco.init_bluetooth_with_known_device.assert_called_once() + assert mock_config_entry.data[CONF_NAME] == service_info.name + assert mock_config_entry.data[CONF_MAC] == service_info.address diff --git a/tests/components/lamarzocco/test_number.py b/tests/components/lamarzocco/test_number.py index 86ae1b90126..8cba3d2387d 100644 --- a/tests/components/lamarzocco/test_number.py +++ b/tests/components/lamarzocco/test_number.py @@ -53,7 +53,9 @@ async def test_coffee_boiler( ) assert len(mock_lamarzocco.set_coffee_temp.mock_calls) == 1 - mock_lamarzocco.set_coffee_temp.assert_called_once_with(temperature=95) + mock_lamarzocco.set_coffee_temp.assert_called_once_with( + temperature=95, ble_device=None + ) @pytest.mark.parametrize( @@ -62,7 +64,12 @@ async def test_coffee_boiler( @pytest.mark.parametrize( ("entity_name", "value", "func_name", "kwargs"), [ - ("steam_target_temperature", 131, "set_steam_temp", {"temperature": 131}), + ( + "steam_target_temperature", + 131, + "set_steam_temp", + {"temperature": 131, "ble_device": None}, + ), ("tea_water_duration", 15, "set_dose_hot_water", {"value": 15}), ], ) diff --git a/tests/components/lamarzocco/test_select.py b/tests/components/lamarzocco/test_select.py index 3c96f16de9c..497a95f6d0d 100644 --- a/tests/components/lamarzocco/test_select.py +++ b/tests/components/lamarzocco/test_select.py @@ -50,7 +50,7 @@ async def test_steam_boiler_level( ) assert len(mock_lamarzocco.set_steam_level.mock_calls) == 1 - mock_lamarzocco.set_steam_level.assert_called_once_with(level=1) + mock_lamarzocco.set_steam_level.assert_called_once_with(1, None) @pytest.mark.parametrize( diff --git a/tests/components/lamarzocco/test_switch.py b/tests/components/lamarzocco/test_switch.py index db4bc5541b9..e1924f0a8ca 100644 --- a/tests/components/lamarzocco/test_switch.py +++ b/tests/components/lamarzocco/test_switch.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock import pytest from syrupy import SnapshotAssertion +from homeassistant.components.lamarzocco.const import DOMAIN from homeassistant.components.switch import ( DOMAIN as SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -14,15 +15,22 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er +from tests.common import MockConfigEntry + pytestmark = pytest.mark.usefixtures("init_integration") @pytest.mark.parametrize( - ("entity_name", "method_name"), + ("entity_name", "method_name", "args_on", "args_off"), [ - ("", "set_power"), - ("_auto_on_off", "set_auto_on_off_global"), - ("_steam_boiler", "set_steam"), + ("", "set_power", (True, None), (False, None)), + ( + "_auto_on_off", + "set_auto_on_off_global", + (True,), + (False,), + ), + ("_steam_boiler", "set_steam", (True, None), (False, None)), ], ) async def test_switches( @@ -32,6 +40,8 @@ async def test_switches( snapshot: SnapshotAssertion, entity_name: str, method_name: str, + args_on: tuple, + args_off: tuple, ) -> None: """Test the La Marzocco switches.""" serial_number = mock_lamarzocco.serial_number @@ -56,7 +66,7 @@ async def test_switches( ) assert len(control_fn.mock_calls) == 1 - control_fn.assert_called_once_with(False) + control_fn.assert_called_once_with(*args_off) await hass.services.async_call( SWITCH_DOMAIN, @@ -68,7 +78,7 @@ async def test_switches( ) assert len(control_fn.mock_calls) == 2 - control_fn.assert_called_with(True) + control_fn.assert_called_with(*args_on) async def test_device( @@ -90,3 +100,26 @@ async def test_device( device = device_registry.async_get(entry.device_id) assert device assert device == snapshot + + +async def test_call_without_bluetooth_works( + hass: HomeAssistant, + mock_lamarzocco: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that if not using bluetooth, the switch still works.""" + serial_number = mock_lamarzocco.serial_number + coordinator = hass.data[DOMAIN][mock_config_entry.entry_id] + coordinator._use_bluetooth = False + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: f"switch.{serial_number}_steam_boiler", + }, + blocking=True, + ) + + assert len(mock_lamarzocco.set_steam.mock_calls) == 1 + mock_lamarzocco.set_steam.assert_called_once_with(False, None) From 9289fa23a15f0d06dd1c3a1091f40442112c4117 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 21 Mar 2024 13:13:25 +0100 Subject: [PATCH 1356/1691] Add availability to Scrape config flow (#105516) --- .../components/scrape/config_flow.py | 2 + homeassistant/components/scrape/sensor.py | 5 +- tests/components/scrape/test_sensor.py | 63 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/scrape/config_flow.py b/homeassistant/components/scrape/config_flow.py index fc8124010ce..017b3c707a9 100644 --- a/homeassistant/components/scrape/config_flow.py +++ b/homeassistant/components/scrape/config_flow.py @@ -60,6 +60,7 @@ from homeassistant.helpers.selector import ( TextSelectorConfig, TextSelectorType, ) +from homeassistant.helpers.trigger_template_entity import CONF_AVAILABILITY from . import COMBINED_SCHEMA from .const import ( @@ -105,6 +106,7 @@ SENSOR_SETUP = { ), vol.Optional(CONF_ATTRIBUTE): TextSelector(), vol.Optional(CONF_VALUE_TEMPLATE): TemplateSelector(), + vol.Optional(CONF_AVAILABILITY): TemplateSelector(), vol.Optional(CONF_DEVICE_CLASS): SelectSelector( SelectSelectorConfig( options=[ diff --git a/homeassistant/components/scrape/sensor.py b/homeassistant/components/scrape/sensor.py index 58d90b2399e..61d58ea7bc5 100644 --- a/homeassistant/components/scrape/sensor.py +++ b/homeassistant/components/scrape/sensor.py @@ -113,10 +113,13 @@ async def async_setup_entry( Template(value_string, hass) if value_string is not None else None ) - trigger_entity_config = {CONF_NAME: name} + trigger_entity_config: dict[str, str | Template | None] = {CONF_NAME: name} for key in TRIGGER_ENTITY_OPTIONS: if key not in sensor_config: continue + if key == CONF_AVAILABILITY: + trigger_entity_config[key] = Template(sensor_config[key], hass) + continue trigger_entity_config[key] = sensor_config[key] entities.append( diff --git a/tests/components/scrape/test_sensor.py b/tests/components/scrape/test_sensor.py index 03b85a8944f..b2ee75c2172 100644 --- a/tests/components/scrape/test_sensor.py +++ b/tests/components/scrape/test_sensor.py @@ -5,24 +5,36 @@ from __future__ import annotations from datetime import timedelta from unittest.mock import patch +from freezegun.api import FrozenDateTimeFactory import pytest +from homeassistant.components.rest.const import DEFAULT_METHOD +from homeassistant.components.rest.data import DEFAULT_TIMEOUT from homeassistant.components.scrape.const import ( + CONF_ENCODING, CONF_INDEX, CONF_SELECT, + DEFAULT_ENCODING, DEFAULT_SCAN_INTERVAL, + DEFAULT_VERIFY_SSL, ) from homeassistant.components.sensor import ( CONF_STATE_CLASS, + DOMAIN as SENSOR_DOMAIN, SensorDeviceClass, SensorStateClass, ) from homeassistant.const import ( CONF_DEVICE_CLASS, CONF_ICON, + CONF_METHOD, CONF_NAME, + CONF_RESOURCE, + CONF_TIMEOUT, CONF_UNIQUE_ID, CONF_UNIT_OF_MEASUREMENT, + CONF_VALUE_TEMPLATE, + CONF_VERIFY_SSL, STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfTemperature, @@ -562,3 +574,54 @@ async def test_templates_with_yaml(hass: HomeAssistant) -> None: assert state.state == "Current Version: 2021.12.10" assert state.attributes[CONF_ICON] == "mdi:on" assert state.attributes["entity_picture"] == "/local/picture1.jpg" + + +@pytest.mark.parametrize( + "get_config", + [ + { + CONF_RESOURCE: "https://www.home-assistant.io", + CONF_METHOD: DEFAULT_METHOD, + CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL, + CONF_TIMEOUT: DEFAULT_TIMEOUT, + CONF_ENCODING: DEFAULT_ENCODING, + SENSOR_DOMAIN: [ + { + CONF_SELECT: ".current-version h1", + CONF_NAME: "Current version", + CONF_VALUE_TEMPLATE: "{{ value.split(':')[1] }}", + CONF_INDEX: 0, + CONF_UNIQUE_ID: "3699ef88-69e6-11ed-a1eb-0242ac120002", + CONF_AVAILABILITY: '{{ states("sensor.input1")=="on" }}', + } + ], + } + ], +) +async def test_availability( + hass: HomeAssistant, + loaded_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test availability when setup from config entry.""" + + hass.states.async_set("sensor.input1", "on") + freezer.tick(timedelta(minutes=10)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("sensor.current_version") + assert state.state == "2021.12.10" + + hass.states.async_set("sensor.input1", "off") + await hass.async_block_till_done() + with patch( + "homeassistant.components.command_line.utils.subprocess.check_output", + return_value=b"0", + ): + freezer.tick(timedelta(minutes=10)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("sensor.current_version") + assert state.state == STATE_UNAVAILABLE From b4c36d46768dd45827847625ed5f067067197c7f Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:27:51 +0100 Subject: [PATCH 1357/1691] Bump pytedee_async to 0.2.17 (#113933) --- homeassistant/components/tedee/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json index 1f2a2405a44..db3a88f3113 100644 --- a/homeassistant/components/tedee/manifest.json +++ b/homeassistant/components/tedee/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/tedee", "iot_class": "local_push", "loggers": ["pytedee_async"], - "requirements": ["pytedee-async==0.2.16"] + "requirements": ["pytedee-async==0.2.17"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6ae4d9df333..f4e90398863 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2175,7 +2175,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.16 +pytedee-async==0.2.17 # homeassistant.components.tfiac pytfiac==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1f8e59bd81e..557596dccba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1696,7 +1696,7 @@ pyswitchbee==1.8.0 pytautulli==23.1.1 # homeassistant.components.tedee -pytedee-async==0.2.16 +pytedee-async==0.2.17 # homeassistant.components.motionmount python-MotionMount==0.3.1 From 63221356f61c13bfe9d5b8498b4edaf897f7fa91 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:07:09 +0100 Subject: [PATCH 1358/1691] Add select platform to Husqvarna Automower (#113816) * Add select platform to Husqvarna Automower * docstring * address review * pin headlight_modes list * Apply suggestions from code review Co-authored-by: Joost Lekkerkerker * Apply review --------- Co-authored-by: Joost Lekkerkerker --- .../husqvarna_automower/__init__.py | 1 + .../components/husqvarna_automower/icons.json | 5 + .../components/husqvarna_automower/select.py | 70 ++++++++++++ .../husqvarna_automower/strings.json | 17 ++- .../husqvarna_automower/test_select.py | 102 ++++++++++++++++++ 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/husqvarna_automower/select.py create mode 100644 tests/components/husqvarna_automower/test_select.py diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index a6c9e46dbed..03ab02429bb 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -21,6 +21,7 @@ PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER, Platform.LAWN_MOWER, + Platform.SELECT, Platform.SENSOR, Platform.SWITCH, ] diff --git a/homeassistant/components/husqvarna_automower/icons.json b/homeassistant/components/husqvarna_automower/icons.json index a3e2bd6bb8b..65cc85bd09b 100644 --- a/homeassistant/components/husqvarna_automower/icons.json +++ b/homeassistant/components/husqvarna_automower/icons.json @@ -8,6 +8,11 @@ "default": "mdi:debug-step-into" } }, + "select": { + "headlight_mode": { + "default": "mdi:car-light-high" + } + }, "sensor": { "number_of_charging_cycles": { "default": "mdi:battery-sync-outline" diff --git a/homeassistant/components/husqvarna_automower/select.py b/homeassistant/components/husqvarna_automower/select.py new file mode 100644 index 00000000000..e4376a1bca5 --- /dev/null +++ b/homeassistant/components/husqvarna_automower/select.py @@ -0,0 +1,70 @@ +"""Creates a select entity for the headlight of the mower.""" + +import logging + +from aioautomower.exceptions import ApiException +from aioautomower.model import HeadlightModes + +from homeassistant.components.select import SelectEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import AutomowerDataUpdateCoordinator +from .entity import AutomowerControlEntity + +_LOGGER = logging.getLogger(__name__) + + +HEADLIGHT_MODES: list = [ + HeadlightModes.ALWAYS_OFF.lower(), + HeadlightModes.ALWAYS_ON.lower(), + HeadlightModes.EVENING_AND_NIGHT.lower(), + HeadlightModes.EVENING_ONLY.lower(), +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up select platform.""" + coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + AutomowerSelectEntity(mower_id, coordinator) + for mower_id in coordinator.data + if coordinator.data[mower_id].capabilities.headlights + ) + + +class AutomowerSelectEntity(AutomowerControlEntity, SelectEntity): + """Defining the headlight mode entity.""" + + _attr_options = HEADLIGHT_MODES + _attr_entity_category = EntityCategory.CONFIG + _attr_translation_key = "headlight_mode" + + def __init__( + self, + mower_id: str, + coordinator: AutomowerDataUpdateCoordinator, + ) -> None: + """Set up select platform.""" + super().__init__(mower_id, coordinator) + self._attr_unique_id = f"{mower_id}_headlight_mode" + + @property + def current_option(self) -> str: + """Return the current option for the entity.""" + return self.mower_attributes.headlight.mode.lower() + + async def async_select_option(self, option: str) -> None: + """Change the selected option.""" + try: + await self.coordinator.api.set_headlight_mode(self.mower_id, option.upper()) + except ApiException as exception: + raise HomeAssistantError( + f"Command couldn't be sent to the command queue: {exception}" + ) from exception diff --git a/homeassistant/components/husqvarna_automower/strings.json b/homeassistant/components/husqvarna_automower/strings.json index 4280ea097e8..8032c670404 100644 --- a/homeassistant/components/husqvarna_automower/strings.json +++ b/homeassistant/components/husqvarna_automower/strings.json @@ -37,9 +37,15 @@ "name": "Returning to dock" } }, - "switch": { - "enable_schedule": { - "name": "Enable schedule" + "select": { + "headlight_mode": { + "name": "Headlight mode", + "state": { + "always_on": "Always on", + "always_off": "Always off", + "evening_only": "Evening only", + "evening_and_night": "Evening and night" + } } }, "sensor": { @@ -79,6 +85,11 @@ "demo": "Demo" } } + }, + "switch": { + "enable_schedule": { + "name": "Enable schedule" + } } } } diff --git a/tests/components/husqvarna_automower/test_select.py b/tests/components/husqvarna_automower/test_select.py new file mode 100644 index 00000000000..4283c7d3797 --- /dev/null +++ b/tests/components/husqvarna_automower/test_select.py @@ -0,0 +1,102 @@ +"""Tests for select platform.""" + +from datetime import timedelta +from unittest.mock import AsyncMock + +from aioautomower.exceptions import ApiException +from aioautomower.model import HeadlightModes +from aioautomower.utils import mower_list_to_dictionary_dataclass +from freezegun.api import FrozenDateTimeFactory +import pytest + +from homeassistant.components.husqvarna_automower.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from . import setup_integration +from .const import TEST_MOWER_ID + +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + load_json_value_fixture, +) + + +async def test_select_states( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test states of headlight mode select.""" + values = mower_list_to_dictionary_dataclass( + load_json_value_fixture("mower.json", DOMAIN) + ) + await setup_integration(hass, mock_config_entry) + state = hass.states.get("select.test_mower_1_headlight_mode") + assert state is not None + assert state.state == "evening_only" + + for state, expected_state in [ + ( + HeadlightModes.ALWAYS_OFF, + "always_off", + ), + (HeadlightModes.ALWAYS_ON, "always_on"), + (HeadlightModes.EVENING_AND_NIGHT, "evening_and_night"), + ]: + values[TEST_MOWER_ID].headlight.mode = state + mock_automower_client.get_status.return_value = values + freezer.tick(timedelta(minutes=5)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("select.test_mower_1_headlight_mode") + assert state.state == expected_state + + +@pytest.mark.parametrize( + ("service"), + [ + ("always_on"), + ("always_off"), + ("evening_only"), + ("evening_and_night"), + ], +) +async def test_select_commands( + hass: HomeAssistant, + service: str, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test select commands for headlight mode.""" + await setup_integration(hass, mock_config_entry) + await hass.services.async_call( + domain="select", + service="select_option", + service_data={ + "entity_id": "select.test_mower_1_headlight_mode", + "option": service, + }, + blocking=True, + ) + mocked_method = mock_automower_client.set_headlight_mode + assert len(mocked_method.mock_calls) == 1 + + mocked_method.side_effect = ApiException("Test error") + with pytest.raises(HomeAssistantError) as exc_info: + await hass.services.async_call( + domain="select", + service="select_option", + service_data={ + "entity_id": "select.test_mower_1_headlight_mode", + "option": service, + }, + blocking=True, + ) + assert ( + str(exc_info.value) + == "Command couldn't be sent to the command queue: Test error" + ) + assert len(mocked_method.mock_calls) == 2 From 8141a246b0af1a75d7b6bea24d5a718ca182e8ba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 Mar 2024 08:54:24 -1000 Subject: [PATCH 1359/1691] Remove unrelated patching from scrape test (#113951) https://github.com/home-assistant/core/pull/105516#discussion_r1534459365 The fixture already is designed to go unavailable on the 3rd update --- tests/components/scrape/test_sensor.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/components/scrape/test_sensor.py b/tests/components/scrape/test_sensor.py index b2ee75c2172..41da2eb9a79 100644 --- a/tests/components/scrape/test_sensor.py +++ b/tests/components/scrape/test_sensor.py @@ -615,13 +615,10 @@ async def test_availability( hass.states.async_set("sensor.input1", "off") await hass.async_block_till_done() - with patch( - "homeassistant.components.command_line.utils.subprocess.check_output", - return_value=b"0", - ): - freezer.tick(timedelta(minutes=10)) - async_fire_time_changed(hass) - await hass.async_block_till_done() + + freezer.tick(timedelta(minutes=10)) + async_fire_time_changed(hass) + await hass.async_block_till_done() state = hass.states.get("sensor.current_version") assert state.state == STATE_UNAVAILABLE From 8728057b1b39c647a7c8d449d7fa97acdab4731a Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Thu, 21 Mar 2024 19:58:56 +0100 Subject: [PATCH 1360/1691] Add support for Shelly RPC devices custom TCP port (#110860) * First coding * add port to config_entry + gen1 not supported msg * fix async_step_credentials * strings * fix reauth * fix visit device link * increased MINOR_VERSION * apply review comments * align to latest aioshelly * missing tests * introduce port parameter * update tests * remove leftover * remove "port" data_description key * missing key * apply review comments * apply more review comments * Add tests * apply review comment * apply review comment (part 2) * description update * fine tuning description * fix test patching --------- Co-authored-by: Shay Levy --- homeassistant/components/shelly/__init__.py | 2 + .../components/shelly/config_flow.py | 60 +++++++++++++------ .../components/shelly/coordinator.py | 3 +- homeassistant/components/shelly/strings.json | 9 ++- homeassistant/components/shelly/utils.py | 9 ++- tests/components/shelly/test_config_flow.py | 57 +++++++++++++++--- tests/components/shelly/test_init.py | 48 ++++++++++++++- 7 files changed, 156 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index cfa95162eed..523c6a67433 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -56,6 +56,7 @@ from .utils import ( get_block_device_sleep_period, get_coap_context, get_device_entry_gen, + get_http_port, get_rpc_device_wakeup_period, get_ws_context, ) @@ -249,6 +250,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ConfigEntry) -> boo entry.data.get(CONF_USERNAME), entry.data.get(CONF_PASSWORD), device_mac=entry.unique_id, + port=get_http_port(entry.data), ) ws_context = await get_ws_context(hass) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index ca56552d125..ca1190de708 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -7,8 +7,9 @@ from typing import Any, Final from aioshelly.block_device import BlockDevice from aioshelly.common import ConnectionOptions, get_info -from aioshelly.const import BLOCK_GENERATIONS, RPC_GENERATIONS +from aioshelly.const import BLOCK_GENERATIONS, DEFAULT_HTTP_PORT, RPC_GENERATIONS from aioshelly.exceptions import ( + CustomPortNotSupported, DeviceConnectionError, FirmwareUnsupported, InvalidAuthError, @@ -23,7 +24,7 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig @@ -42,6 +43,7 @@ from .utils import ( get_block_device_sleep_period, get_coap_context, get_device_entry_gen, + get_http_port, get_info_auth, get_info_gen, get_model_name, @@ -50,7 +52,12 @@ from .utils import ( mac_address_from_name, ) -HOST_SCHEMA: Final = vol.Schema({vol.Required(CONF_HOST): str}) +CONFIG_SCHEMA: Final = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_HTTP_PORT): vol.Coerce(int), + } +) BLE_SCANNER_OPTIONS = [ @@ -65,14 +72,20 @@ INTERNAL_WIFI_AP_IP = "192.168.33.1" async def validate_input( hass: HomeAssistant, host: str, + port: int, info: dict[str, Any], data: dict[str, Any], ) -> dict[str, Any]: """Validate the user input allows us to connect. - Data has the keys from HOST_SCHEMA with values provided by the user. + Data has the keys from CONFIG_SCHEMA with values provided by the user. """ - options = ConnectionOptions(host, data.get(CONF_USERNAME), data.get(CONF_PASSWORD)) + options = ConnectionOptions( + ip_address=host, + username=data.get(CONF_USERNAME), + password=data.get(CONF_PASSWORD), + port=port, + ) gen = get_info_gen(info) @@ -114,8 +127,10 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Shelly.""" VERSION = 1 + MINOR_VERSION = 2 host: str = "" + port: int = DEFAULT_HTTP_PORT info: dict[str, Any] = {} device_info: dict[str, Any] = {} entry: ConfigEntry | None = None @@ -126,9 +141,10 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): """Handle the initial step.""" errors: dict[str, str] = {} if user_input is not None: - host: str = user_input[CONF_HOST] + host = user_input[CONF_HOST] + port = user_input[CONF_PORT] try: - self.info = await self._async_get_info(host) + self.info = await self._async_get_info(host, port) except DeviceConnectionError: errors["base"] = "cannot_connect" except FirmwareUnsupported: @@ -140,15 +156,18 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(self.info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: host}) self.host = host + self.port = port if get_info_auth(self.info): return await self.async_step_credentials() try: device_info = await validate_input( - self.hass, self.host, self.info, {} + self.hass, host, port, self.info, {} ) except DeviceConnectionError: errors["base"] = "cannot_connect" + except CustomPortNotSupported: + errors["base"] = "custom_port_not_supported" except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -157,7 +176,8 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_create_entry( title=device_info["title"], data={ - **user_input, + CONF_HOST: user_input[CONF_HOST], + CONF_PORT: user_input[CONF_PORT], CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD], "model": device_info["model"], CONF_GEN: device_info[CONF_GEN], @@ -166,7 +186,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): errors["base"] = "firmware_not_fully_provisioned" return self.async_show_form( - step_id="user", data_schema=HOST_SCHEMA, errors=errors + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors ) async def async_step_credentials( @@ -179,7 +199,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): user_input[CONF_USERNAME] = "admin" try: device_info = await validate_input( - self.hass, self.host, self.info, user_input + self.hass, self.host, self.port, self.info, user_input ) except InvalidAuthError: errors["base"] = "invalid_auth" @@ -195,6 +215,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): data={ **user_input, CONF_HOST: self.host, + CONF_PORT: self.port, CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD], "model": device_info["model"], CONF_GEN: device_info[CONF_GEN], @@ -254,7 +275,9 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): await self._async_discovered_mac(mac, host) try: - self.info = await self._async_get_info(host) + # Devices behind range extender doesn't generate zeroconf packets + # so port is always the default one + self.info = await self._async_get_info(host, DEFAULT_HTTP_PORT) except DeviceConnectionError: return self.async_abort(reason="cannot_connect") except FirmwareUnsupported: @@ -277,7 +300,9 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): return await self.async_step_credentials() try: - self.device_info = await validate_input(self.hass, self.host, self.info, {}) + self.device_info = await validate_input( + self.hass, self.host, self.port, self.info, {} + ) except DeviceConnectionError: return self.async_abort(reason="cannot_connect") @@ -329,17 +354,18 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): errors: dict[str, str] = {} assert self.entry is not None host = self.entry.data[CONF_HOST] + port = get_http_port(self.entry.data) if user_input is not None: try: - info = await self._async_get_info(host) + info = await self._async_get_info(host, port) except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported): return self.async_abort(reason="reauth_unsuccessful") if get_device_entry_gen(self.entry) != 1: user_input[CONF_USERNAME] = "admin" try: - await validate_input(self.hass, host, info, user_input) + await validate_input(self.hass, host, port, info, user_input) except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported): return self.async_abort(reason="reauth_unsuccessful") @@ -361,9 +387,9 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) - async def _async_get_info(self, host: str) -> dict[str, Any]: + async def _async_get_info(self, host: str, port: int) -> dict[str, Any]: """Get info from shelly device.""" - return await get_info(async_get_clientsession(self.hass), host) + return await get_info(async_get_clientsession(self.hass), host, port=port) @staticmethod @callback diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index ebc92a6c6e0..50c352bcb25 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -59,6 +59,7 @@ from .const import ( ) from .utils import ( get_device_entry_gen, + get_http_port, get_rpc_device_wakeup_period, update_device_fw_info, ) @@ -140,7 +141,7 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]): model=MODEL_NAMES.get(self.model, self.model), sw_version=self.sw_version, hw_version=f"gen{get_device_entry_gen(self.entry)} ({self.model})", - configuration_url=f"http://{self.entry.data[CONF_HOST]}", + configuration_url=f"http://{self.entry.data[CONF_HOST]}:{get_http_port(self.entry.data)}", ) self.device_id = device_entry.id diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 9676c24f883..d4a8b117f4c 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -5,10 +5,12 @@ "user": { "description": "Before setup, battery-powered devices must be woken up, you can now wake the device up using a button on it.", "data": { - "host": "[%key:common::config_flow::data::host%]" + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" }, "data_description": { - "host": "The hostname or IP address of the Shelly device to connect to." + "host": "The hostname or IP address of the Shelly device to connect to.", + "port": "The TCP port of the Shelly device to connect to (Gen2+)." } }, "credentials": { @@ -31,7 +33,8 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "unknown": "[%key:common::config_flow::error::unknown%]", - "firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support" + "firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support", + "custom_port_not_supported": "Gen1 device does not support custom port." }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 291fe0cc4ea..dd0e685fd67 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import datetime, timedelta from ipaddress import IPv4Address +from types import MappingProxyType from typing import Any, cast from aiohttp.web import Request, WebSocketResponse @@ -11,6 +12,7 @@ from aioshelly.block_device import COAP, Block, BlockDevice from aioshelly.const import ( BLOCK_GENERATIONS, DEFAULT_COAP_PORT, + DEFAULT_HTTP_PORT, MODEL_1L, MODEL_DIMMER, MODEL_DIMMER_2, @@ -24,7 +26,7 @@ from aioshelly.rpc_device import RpcDevice, WsServer from homeassistant.components import network from homeassistant.components.http import HomeAssistantView from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import issue_registry as ir, singleton from homeassistant.helpers.device_registry import ( @@ -473,3 +475,8 @@ def is_rpc_wifi_stations_disabled( return False return True + + +def get_http_port(data: MappingProxyType[str, Any]) -> int: + """Get port from config entry data.""" + return cast(int, data.get(CONF_PORT, DEFAULT_HTTP_PORT)) diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 1a680bcfc68..99b30062d43 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -6,8 +6,9 @@ from ipaddress import ip_address from typing import Any from unittest.mock import AsyncMock, Mock, patch -from aioshelly.const import MODEL_1, MODEL_PLUS_2PM +from aioshelly.const import DEFAULT_HTTP_PORT, MODEL_1, MODEL_PLUS_2PM from aioshelly.exceptions import ( + CustomPortNotSupported, DeviceConnectionError, FirmwareUnsupported, InvalidAuthError, @@ -54,17 +55,18 @@ DISCOVERY_INFO_WITH_MAC = zeroconf.ZeroconfServiceInfo( @pytest.mark.parametrize( - ("gen", "model"), + ("gen", "model", "port"), [ - (1, MODEL_1), - (2, MODEL_PLUS_2PM), - (3, MODEL_PLUS_2PM), + (1, MODEL_1, DEFAULT_HTTP_PORT), + (2, MODEL_PLUS_2PM, DEFAULT_HTTP_PORT), + (3, MODEL_PLUS_2PM, 11200), ], ) async def test_form( hass: HomeAssistant, gen: int, model: str, + port: int, mock_block_device: Mock, mock_rpc_device: Mock, ) -> None: @@ -72,12 +74,18 @@ async def test_form( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "form" + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} with patch( "homeassistant.components.shelly.config_flow.get_info", - return_value={"mac": "test-mac", "type": MODEL_1, "auth": False, "gen": gen}, + return_value={ + "mac": "test-mac", + "type": MODEL_1, + "auth": False, + "gen": gen, + "port": port, + }, ), patch( "homeassistant.components.shelly.async_setup", return_value=True ) as mock_setup, patch( @@ -86,7 +94,7 @@ async def test_form( ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"host": "1.1.1.1"}, + {"host": "1.1.1.1", "port": port}, ) await hass.async_block_till_done() @@ -94,6 +102,7 @@ async def test_form( assert result2["title"] == "Test name" assert result2["data"] == { "host": "1.1.1.1", + "port": port, "model": model, "sleep_period": 0, "gen": gen, @@ -102,6 +111,33 @@ async def test_form( assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_gen1_custom_port( + hass: HomeAssistant, + mock_block_device: Mock, +) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["errors"] == {} + + with patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={"mac": "test-mac", "type": MODEL_1, "gen": 1}, + ), patch( + "aioshelly.block_device.BlockDevice.create", + side_effect=CustomPortNotSupported, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.1.1.1", "port": "1100"}, + ) + + assert result2["type"] == data_entry_flow.FlowResultType.FORM + assert result2["errors"]["base"] == "custom_port_not_supported" + + @pytest.mark.parametrize( ("gen", "model", "user_input", "username"), [ @@ -168,6 +204,7 @@ async def test_form_auth( assert result3["title"] == "Test name" assert result3["data"] == { "host": "1.1.1.1", + "port": DEFAULT_HTTP_PORT, "model": model, "sleep_period": 0, "gen": gen, @@ -757,6 +794,7 @@ async def test_zeroconf_require_auth( assert result2["title"] == "Test name" assert result2["data"] == { "host": "1.1.1.1", + "port": DEFAULT_HTTP_PORT, "model": MODEL_1, "sleep_period": 0, "gen": 1, @@ -1126,7 +1164,7 @@ async def test_sleeping_device_gen2_with_new_firmware( result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - assert result["type"] == "form" + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} with patch( @@ -1144,6 +1182,7 @@ async def test_sleeping_device_gen2_with_new_firmware( assert result["data"] == { "host": "1.1.1.1", + "port": DEFAULT_HTTP_PORT, "model": MODEL_PLUS_2PM, "sleep_period": 666, "gen": 2, diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index 700b54f153d..eb621a6e044 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -4,6 +4,8 @@ from ipaddress import IPv4Address from unittest.mock import AsyncMock, Mock, call, patch from aioshelly.block_device import COAP +from aioshelly.common import ConnectionOptions +from aioshelly.const import MODEL_PLUS_2PM from aioshelly.exceptions import ( DeviceConnectionError, FirmwareUnsupported, @@ -16,13 +18,14 @@ from homeassistant.components.shelly.const import ( BLOCK_EXPECTED_SLEEP_PERIOD, BLOCK_WRONG_SLEEP_PERIOD, CONF_BLE_SCANNER_MODE, + CONF_GEN, CONF_SLEEP_PERIOD, DOMAIN, MODELS_WITH_WRONG_SLEEP_PERIOD, BLEScannerMode, ) from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState -from homeassistant.const import STATE_ON, STATE_UNAVAILABLE +from homeassistant.const import CONF_HOST, CONF_PORT, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import ( CONNECTION_NETWORK_MAC, @@ -392,6 +395,49 @@ async def test_entry_missing_gen(hass: HomeAssistant, mock_block_device: Mock) - assert hass.states.get("switch.test_name_channel_1").state is STATE_ON +async def test_entry_missing_port(hass: HomeAssistant) -> None: + """Test successful Gen2 device init when port is missing in entry data.""" + data = { + CONF_HOST: "192.168.1.37", + CONF_SLEEP_PERIOD: 0, + "model": MODEL_PLUS_2PM, + CONF_GEN: 2, + } + entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.shelly.RpcDevice.create", return_value=Mock() + ) as rpc_device_mock: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert rpc_device_mock.call_args[0][2] == ConnectionOptions( + ip_address="192.168.1.37", device_mac="123456789ABC", port=80 + ) + + +async def test_rpc_entry_custom_port(hass: HomeAssistant) -> None: + """Test successful Gen2 device init using custom port.""" + data = { + CONF_HOST: "192.168.1.37", + CONF_SLEEP_PERIOD: 0, + "model": MODEL_PLUS_2PM, + CONF_GEN: 2, + CONF_PORT: 8001, + } + entry = MockConfigEntry(domain=DOMAIN, data=data, unique_id=MOCK_MAC) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.shelly.RpcDevice.create", return_value=Mock() + ) as rpc_device_mock: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert rpc_device_mock.call_args[0][2] == ConnectionOptions( + ip_address="192.168.1.37", device_mac="123456789ABC", port=8001 + ) + + @pytest.mark.parametrize(("model"), MODELS_WITH_WRONG_SLEEP_PERIOD) async def test_sleeping_block_device_wrong_sleep_period( hass: HomeAssistant, mock_block_device: Mock, model: str From 63275d61a5d57085b2b9fb4a5fa10e3d36108897 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 21 Mar 2024 21:04:50 +0200 Subject: [PATCH 1361/1691] Add Shelly RGB/RGBW profiles support (#113808) * Add Shelly RGB/RGBW profiles support * Update homeassistant/components/shelly/light.py Co-authored-by: Robert Svensson * Use walrus in rgbw_key_ids * Use walrus in light_key_ids --------- Co-authored-by: Robert Svensson --- homeassistant/components/shelly/const.py | 7 +- homeassistant/components/shelly/light.py | 124 ++++++++++++++------- tests/components/shelly/conftest.py | 4 + tests/components/shelly/test_light.py | 131 +++++++++++++++++++++++ 4 files changed, 227 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 53e827cea72..3580bcf9b38 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -35,8 +35,11 @@ DATA_CONFIG_ENTRY: Final = "config_entry" CONF_COAP_PORT: Final = "coap_port" FIRMWARE_PATTERN: Final = re.compile(r"^(\d{8})") -# max light transition time in milliseconds -MAX_TRANSITION_TIME: Final = 5000 +# max BLOCK light transition time in milliseconds (min=0) +BLOCK_MAX_TRANSITION_TIME_MS: Final = 5000 + +# min RPC light transition time in seconds (max=10800, limited by light entity to 6553) +RPC_MIN_TRANSITION_TIME_SEC = 0.5 RGBW_MODELS: Final = ( MODEL_BULB, diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 7c465e43db0..6c28023a5e3 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -24,14 +24,15 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( + BLOCK_MAX_TRANSITION_TIME_MS, DUAL_MODE_LIGHT_MODELS, KELVIN_MAX_VALUE, KELVIN_MIN_VALUE_COLOR, KELVIN_MIN_VALUE_WHITE, LOGGER, - MAX_TRANSITION_TIME, MODELS_SUPPORTING_LIGHT_TRANSITION, RGBW_MODELS, + RPC_MIN_TRANSITION_TIME_SEC, SHBLB_1_RGB_EFFECTS, STANDARD_RGB_EFFECTS, ) @@ -116,9 +117,16 @@ def async_setup_rpc_entry( ) return - light_key_ids = get_rpc_key_ids(coordinator.device.status, "light") - if light_key_ids: + if light_key_ids := get_rpc_key_ids(coordinator.device.status, "light"): async_add_entities(RpcShellyLight(coordinator, id_) for id_ in light_key_ids) + return + + if rgb_key_ids := get_rpc_key_ids(coordinator.device.status, "rgb"): + async_add_entities(RpcShellyRgbLight(coordinator, id_) for id_ in rgb_key_ids) + return + + if rgbw_key_ids := get_rpc_key_ids(coordinator.device.status, "rgbw"): + async_add_entities(RpcShellyRgbwLight(coordinator, id_) for id_ in rgbw_key_ids) class BlockShellyLight(ShellyBlockEntity, LightEntity): @@ -280,7 +288,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): if ATTR_TRANSITION in kwargs: params["transition"] = min( - int(kwargs[ATTR_TRANSITION] * 1000), MAX_TRANSITION_TIME + int(kwargs[ATTR_TRANSITION] * 1000), BLOCK_MAX_TRANSITION_TIME_MS ) if ATTR_BRIGHTNESS in kwargs and brightness_supported(supported_color_modes): @@ -352,7 +360,7 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): if ATTR_TRANSITION in kwargs: params["transition"] = min( - int(kwargs[ATTR_TRANSITION] * 1000), MAX_TRANSITION_TIME + int(kwargs[ATTR_TRANSITION] * 1000), BLOCK_MAX_TRANSITION_TIME_MS ) self.control_result = await self.set_state(**params) @@ -366,40 +374,14 @@ class BlockShellyLight(ShellyBlockEntity, LightEntity): super()._update_callback() -class RpcShellySwitchAsLight(ShellyRpcEntity, LightEntity): - """Entity that controls a relay as light on RPC based Shelly devices.""" +class RpcShellyLightBase(ShellyRpcEntity, LightEntity): + """Base Entity for RPC based Shelly devices.""" - _attr_color_mode = ColorMode.ONOFF - _attr_supported_color_modes = {ColorMode.ONOFF} + _component: str = "Light" def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None: """Initialize light.""" - super().__init__(coordinator, f"switch:{id_}") - self._id = id_ - - @property - def is_on(self) -> bool: - """If light is on.""" - return bool(self.status["output"]) - - async def async_turn_on(self, **kwargs: Any) -> None: - """Turn on light.""" - await self.call_rpc("Switch.Set", {"id": self._id, "on": True}) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn off light.""" - await self.call_rpc("Switch.Set", {"id": self._id, "on": False}) - - -class RpcShellyLight(ShellyRpcEntity, LightEntity): - """Entity that controls a light on RPC based Shelly devices.""" - - _attr_color_mode = ColorMode.BRIGHTNESS - _attr_supported_color_modes = {ColorMode.BRIGHTNESS} - - def __init__(self, coordinator: ShellyRpcCoordinator, id_: int) -> None: - """Initialize light.""" - super().__init__(coordinator, f"light:{id_}") + super().__init__(coordinator, f"{self._component.lower()}:{id_}") self._id = id_ @property @@ -412,6 +394,16 @@ class RpcShellyLight(ShellyRpcEntity, LightEntity): """Return the brightness of this light between 0..255.""" return percentage_to_brightness(self.status["brightness"]) + @property + def rgb_color(self) -> tuple[int, int, int]: + """Return the rgb color value [int, int, int].""" + return cast(tuple, self.status["rgb"]) + + @property + def rgbw_color(self) -> tuple[int, int, int, int]: + """Return the rgbw color value [int, int, int, int].""" + return (*self.status["rgb"], self.status["white"]) + async def async_turn_on(self, **kwargs: Any) -> None: """Turn on light.""" params: dict[str, Any] = {"id": self._id, "on": True} @@ -419,8 +411,66 @@ class RpcShellyLight(ShellyRpcEntity, LightEntity): if ATTR_BRIGHTNESS in kwargs: params["brightness"] = brightness_to_percentage(kwargs[ATTR_BRIGHTNESS]) - await self.call_rpc("Light.Set", params) + if ATTR_TRANSITION in kwargs: + params["transition_duration"] = max( + kwargs[ATTR_TRANSITION], RPC_MIN_TRANSITION_TIME_SEC + ) + + if ATTR_RGB_COLOR in kwargs: + params["rgb"] = list(kwargs[ATTR_RGB_COLOR]) + + if ATTR_RGBW_COLOR in kwargs: + params["rgb"] = list(kwargs[ATTR_RGBW_COLOR][:-1]) + params["white"] = kwargs[ATTR_RGBW_COLOR][-1] + + await self.call_rpc(f"{self._component}.Set", params) async def async_turn_off(self, **kwargs: Any) -> None: """Turn off light.""" - await self.call_rpc("Light.Set", {"id": self._id, "on": False}) + params: dict[str, Any] = {"id": self._id, "on": False} + + if ATTR_TRANSITION in kwargs: + params["transition_duration"] = max( + kwargs[ATTR_TRANSITION], RPC_MIN_TRANSITION_TIME_SEC + ) + + await self.call_rpc(f"{self._component}.Set", params) + + +class RpcShellySwitchAsLight(RpcShellyLightBase): + """Entity that controls a relay as light on RPC based Shelly devices.""" + + _component = "Switch" + + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + + +class RpcShellyLight(RpcShellyLightBase): + """Entity that controls a light on RPC based Shelly devices.""" + + _component = "Light" + + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_supported_features = LightEntityFeature.TRANSITION + + +class RpcShellyRgbLight(RpcShellyLightBase): + """Entity that controls a RGB light on RPC based Shelly devices.""" + + _component = "RGB" + + _attr_color_mode = ColorMode.RGB + _attr_supported_color_modes = {ColorMode.RGB} + _attr_supported_features = LightEntityFeature.TRANSITION + + +class RpcShellyRgbwLight(RpcShellyLightBase): + """Entity that controls a RGBW light on RPC based Shelly devices.""" + + _component = "RGBW" + + _attr_color_mode = ColorMode.RGBW + _attr_supported_color_modes = {ColorMode.RGBW} + _attr_supported_features = LightEntityFeature.TRANSITION diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index df6a5f41306..9e8dd3999a6 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -169,6 +169,8 @@ MOCK_CONFIG = { "input:1": {"id": 1, "type": "analog", "enable": True}, "input:2": {"id": 2, "name": "Gas", "type": "count", "enable": True}, "light:0": {"name": "test light_0"}, + "rgb:0": {"name": "test rgb_0"}, + "rgbw:0": {"name": "test rgbw_0"}, "switch:0": {"name": "test switch_0"}, "cover:0": {"name": "test cover_0"}, "thermostat:0": { @@ -223,6 +225,8 @@ MOCK_STATUS_RPC = { "input:1": {"id": 1, "percent": 89, "xpercent": 8.9}, "input:2": {"id": 2, "counts": {"total": 56174, "xtotal": 561.74}}, "light:0": {"output": True, "brightness": 53.0}, + "rgb:0": {"output": True, "brightness": 53.0, "rgb": [45, 55, 65]}, + "rgbw:0": {"output": True, "brightness": 53.0, "rgb": [21, 22, 23], "white": 120}, "cloud": {"connected": False}, "cover:0": { "state": "stopped", diff --git a/tests/components/shelly/test_light.py b/tests/components/shelly/test_light.py index 2c7eda3a1e0..cca318c364d 100644 --- a/tests/components/shelly/test_light.py +++ b/tests/components/shelly/test_light.py @@ -539,6 +539,137 @@ async def test_rpc_light( assert state.state == STATE_ON assert state.attributes[ATTR_BRIGHTNESS] == 33 + # Turn on, transition = 10.1 + mock_rpc_device.call_rpc.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 10.1}, + blocking=True, + ) + + mock_rpc_device.mock_update() + + mock_rpc_device.call_rpc.assert_called_once_with( + "Light.Set", {"id": 0, "on": True, "transition_duration": 10.1} + ) + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + # Turn off, transition = 0.4, should be limited to 0.5 + mock_rpc_device.call_rpc.reset_mock() + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id, ATTR_TRANSITION: 0.4}, + blocking=True, + ) + + mutate_rpc_device_status(monkeypatch, mock_rpc_device, "light:0", "output", False) + mock_rpc_device.mock_update() + + mock_rpc_device.call_rpc.assert_called_once_with( + "Light.Set", {"id": 0, "on": False, "transition_duration": 0.5} + ) + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + entry = entity_registry.async_get(entity_id) assert entry assert entry.unique_id == "123456789ABC-light:0" + + +async def test_rpc_device_rgb_profile( + hass: HomeAssistant, + mock_rpc_device: Mock, + entity_registry: EntityRegistry, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test RPC device in RGB profile.""" + monkeypatch.delitem(mock_rpc_device.status, "light:0") + monkeypatch.delitem(mock_rpc_device.status, "rgbw:0") + entity_id = "light.test_rgb_0" + await init_integration(hass, 2) + + # Test initial + state = hass.states.get(entity_id) + attributes = state.attributes + assert state.state == STATE_ON + assert attributes[ATTR_RGB_COLOR] == (45, 55, 65) + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.RGB] + assert attributes[ATTR_SUPPORTED_FEATURES] == LightEntityFeature.TRANSITION + + # Turn on, RGB = [70, 80, 90] + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: [70, 80, 90]}, + blocking=True, + ) + + mutate_rpc_device_status(monkeypatch, mock_rpc_device, "rgb:0", "rgb", [70, 80, 90]) + mock_rpc_device.mock_update() + + mock_rpc_device.call_rpc.assert_called_once_with( + "RGB.Set", {"id": 0, "on": True, "rgb": [70, 80, 90]} + ) + + state = hass.states.get(entity_id) + attributes = state.attributes + assert state.state == STATE_ON + assert attributes[ATTR_COLOR_MODE] == ColorMode.RGB + assert attributes[ATTR_RGB_COLOR] == (70, 80, 90) + + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.unique_id == "123456789ABC-rgb:0" + + +async def test_rpc_device_rgbw_profile( + hass: HomeAssistant, + mock_rpc_device: Mock, + entity_registry: EntityRegistry, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test RPC device in RGBW profile.""" + monkeypatch.delitem(mock_rpc_device.status, "light:0") + monkeypatch.delitem(mock_rpc_device.status, "rgb:0") + entity_id = "light.test_rgbw_0" + await init_integration(hass, 2) + + # Test initial + state = hass.states.get(entity_id) + attributes = state.attributes + assert state.state == STATE_ON + assert attributes[ATTR_RGBW_COLOR] == (21, 22, 23, 120) + assert attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.RGBW] + assert attributes[ATTR_SUPPORTED_FEATURES] == LightEntityFeature.TRANSITION + + # Turn on, RGBW = [72, 82, 92, 128] + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id, ATTR_RGBW_COLOR: [72, 82, 92, 128]}, + blocking=True, + ) + + mutate_rpc_device_status( + monkeypatch, mock_rpc_device, "rgbw:0", "rgb", [72, 82, 92] + ) + mutate_rpc_device_status(monkeypatch, mock_rpc_device, "rgbw:0", "white", 128) + mock_rpc_device.mock_update() + + mock_rpc_device.call_rpc.assert_called_once_with( + "RGBW.Set", {"id": 0, "on": True, "rgb": [72, 82, 92], "white": 128} + ) + + state = hass.states.get(entity_id) + attributes = state.attributes + assert state.state == STATE_ON + assert attributes[ATTR_COLOR_MODE] == ColorMode.RGBW + assert attributes[ATTR_RGBW_COLOR] == (72, 82, 92, 128) + + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.unique_id == "123456789ABC-rgbw:0" From d0708b5b320095bfa9994977f8a541a71fe8641d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 Mar 2024 09:10:47 -1000 Subject: [PATCH 1362/1691] Fix grammar in async_get_platform comment (#113948) https://github.com/home-assistant/core/pull/113917#pullrequestreview-1951203739 --- homeassistant/loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 7c52787b34a..ae823d9a204 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1065,8 +1065,8 @@ class Integration: async def async_get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" - # Fast path for a single platform when its already - # cached. This is the common case. + # Fast path for a single platform when it is already cached. + # This is the common case. if platform := self._cache.get(f"{self.domain}.{platform_name}"): return platform # type: ignore[return-value] platforms = await self.async_get_platforms((platform_name,)) From 9a863638f6fd7ef1ac30d0c6159eed1be6f39e02 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 Mar 2024 09:19:55 -1000 Subject: [PATCH 1363/1691] Avoid writing HomeKit state to disk unless its missing (#111970) --- homeassistant/components/homekit/__init__.py | 24 ++++++++++++++++---- tests/components/homekit/test_homekit.py | 9 +++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index a5c08a3bbd4..7d1297e1307 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -550,8 +550,13 @@ class HomeKit: self._reset_lock = asyncio.Lock() self._cancel_reload_dispatcher: CALLBACK_TYPE | None = None - def setup(self, async_zeroconf_instance: AsyncZeroconf, uuid: str) -> None: - """Set up bridge and accessory driver.""" + def setup(self, async_zeroconf_instance: AsyncZeroconf, uuid: str) -> bool: + """Set up bridge and accessory driver. + + Returns True if data was loaded from disk + + Returns False if the persistent data was not loaded + """ assert self.iid_storage is not None persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) self.driver = HomeDriver( @@ -573,6 +578,9 @@ class HomeKit: # as pyhap uses a random one until state is restored if os.path.exists(persist_file): self.driver.load() + return True + + return False async def async_reset_accessories(self, entity_ids: Iterable[str]) -> None: """Reset the accessory to load the latest configuration.""" @@ -842,7 +850,9 @@ class HomeKit: # Avoid gather here since it will be I/O bound anyways await self.aid_storage.async_initialize() await self.iid_storage.async_initialize() - await self.hass.async_add_executor_job(self.setup, async_zc_instance, uuid) + loaded_from_disk = await self.hass.async_add_executor_job( + self.setup, async_zc_instance, uuid + ) assert self.driver is not None if not await self._async_create_accessories(): @@ -850,8 +860,12 @@ class HomeKit: self._async_register_bridge() _LOGGER.debug("Driver start for %s", self._name) await self.driver.async_start() - async with self.hass.data[PERSIST_LOCK_DATA]: - await self.hass.async_add_executor_job(self.driver.persist) + if not loaded_from_disk: + # If the state was not loaded from disk, it means this is the + # first time the bridge is ever starting up. In this case, we + # need to make sure its persisted to disk. + async with self.hass.data[PERSIST_LOCK_DATA]: + await self.hass.async_add_executor_job(self.driver.persist) self.status = STATUS_RUNNING if self.driver.state.paired: diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index a6748749e1a..5ca5b5e386f 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -765,9 +765,16 @@ async def test_homekit_start( f"{PATH_HOMEKIT}.async_show_setup_message" ) as mock_setup_msg, patch( "pyhap.accessory_driver.AccessoryDriver.async_start" - ) as hk_driver_start: + ) as hk_driver_start, patch( + "pyhap.accessory_driver.AccessoryDriver.load" + ) as load_mock, patch( + "pyhap.accessory_driver.AccessoryDriver.persist" + ) as persist_mock, patch(f"{PATH_HOMEKIT}.os.path.exists", return_value=True): + await homekit.async_stop() await homekit.async_start() + assert load_mock.called + assert not persist_mock.called device = device_registry.async_get_device( identifiers={(DOMAIN, entry.entry_id, BRIDGE_SERIAL_NUMBER)} ) From 0c791051b86cfda8f6220fa1fecacea9a18494e8 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 21 Mar 2024 21:42:42 +0100 Subject: [PATCH 1364/1691] Bump axis to v57 (#113952) --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 346afc4b4fe..44d615bf534 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==56"], + "requirements": ["axis==57"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index f4e90398863..07dab3fc6e2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==56 +axis==57 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 557596dccba..c96de54a87b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -451,7 +451,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==56 +axis==57 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From a6d98c18570c6ce88be414ed6ce2739d1d174758 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:54:45 +0000 Subject: [PATCH 1365/1691] Improve user error messages for generic camera (#112814) * Generic camera: Insufficient error message when configuration fails Fixes #112279 * Add tests * Fix typo in string * Add new error strings to options flow. * Group and improve error strings following PR review --- .../components/generic/config_flow.py | 17 +++++++- homeassistant/components/generic/strings.json | 8 ++++ tests/components/generic/test_config_flow.py | 42 ++++++++++++++++--- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/generic/config_flow.py b/homeassistant/components/generic/config_flow.py index 621b4dd299b..8fdc0143700 100644 --- a/homeassistant/components/generic/config_flow.py +++ b/homeassistant/components/generic/config_flow.py @@ -183,14 +183,27 @@ async def async_test_still( except ( TimeoutError, RequestError, - HTTPStatusError, TimeoutException, ) as err: _LOGGER.error("Error getting camera image from %s: %s", url, type(err).__name__) return {CONF_STILL_IMAGE_URL: "unable_still_load"}, None + except HTTPStatusError as err: + _LOGGER.error( + "Error getting camera image from %s: %s %s", + url, + type(err).__name__, + err.response.text, + ) + if err.response.status_code in [401, 403]: + return {CONF_STILL_IMAGE_URL: "unable_still_load_auth"}, None + if err.response.status_code in [404]: + return {CONF_STILL_IMAGE_URL: "unable_still_load_not_found"}, None + if err.response.status_code in [500, 503]: + return {CONF_STILL_IMAGE_URL: "unable_still_load_server_error"}, None + return {CONF_STILL_IMAGE_URL: "unable_still_load"}, None if not image: - return {CONF_STILL_IMAGE_URL: "unable_still_load"}, None + return {CONF_STILL_IMAGE_URL: "unable_still_load_no_image"}, None fmt = get_image_type(image) _LOGGER.debug( "Still image at '%s' detected format: %s", diff --git a/homeassistant/components/generic/strings.json b/homeassistant/components/generic/strings.json index a1519fa0f48..991a36d49cc 100644 --- a/homeassistant/components/generic/strings.json +++ b/homeassistant/components/generic/strings.json @@ -5,6 +5,10 @@ "unknown": "[%key:common::config_flow::error::unknown%]", "already_exists": "A camera with these URL settings already exists.", "unable_still_load": "Unable to load valid image from still image URL (e.g. invalid host, URL or authentication failure). Review log for more info.", + "unable_still_load_auth": "Unable to load valid image from still image URL: The camera may require a user name and password, or they are not correct.", + "unable_still_load_not_found": "Unable to load valid image from still image URL: The URL was not found on the server.", + "unable_still_load_server_error": "Unable to load valid image from still image URL: The camera replied with a server error.", + "unable_still_load_no_image": "Unable to load valid image from still image URL: No image was returned.", "no_still_image_or_stream_url": "You must specify at least a still image or stream URL", "invalid_still_image": "URL did not return a valid still image", "malformed_url": "Malformed URL", @@ -73,6 +77,10 @@ "unknown": "[%key:common::config_flow::error::unknown%]", "already_exists": "[%key:component::generic::config::error::already_exists%]", "unable_still_load": "[%key:component::generic::config::error::unable_still_load%]", + "unable_still_load_auth": "[%key:component::generic::config::error::unable_still_load_auth%]", + "unable_still_load_not_found": "[%key:component::generic::config::error::unable_still_load_not_found%]", + "unable_still_load_server_error": "[%key:component::generic::config::error::unable_still_load_server_error%]", + "unable_still_load_no_image": "[%key:component::generic::config::error::unable_still_load_no_image%]", "no_still_image_or_stream_url": "[%key:component::generic::config::error::no_still_image_or_stream_url%]", "invalid_still_image": "[%key:component::generic::config::error::invalid_still_image%]", "malformed_url": "[%key:component::generic::config::error::malformed_url%]", diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 6ced5086282..7eee5f1bd9e 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -449,12 +449,42 @@ async def test_form_still_and_stream_not_provided( @respx.mock -async def test_form_image_timeout( - hass: HomeAssistant, user_flow, mock_create_stream +@pytest.mark.parametrize( + ("side_effect", "expected_message"), + [ + (httpx.TimeoutException, {"still_image_url": "unable_still_load"}), + ( + httpx.HTTPStatusError("", request=None, response=httpx.Response(401)), + {"still_image_url": "unable_still_load_auth"}, + ), + ( + httpx.HTTPStatusError("", request=None, response=httpx.Response(403)), + {"still_image_url": "unable_still_load_auth"}, + ), + ( + httpx.HTTPStatusError("", request=None, response=httpx.Response(404)), + {"still_image_url": "unable_still_load_not_found"}, + ), + ( + httpx.HTTPStatusError("", request=None, response=httpx.Response(500)), + {"still_image_url": "unable_still_load_server_error"}, + ), + ( + httpx.HTTPStatusError("", request=None, response=httpx.Response(503)), + {"still_image_url": "unable_still_load_server_error"}, + ), + ( # Errors without specific handler should show the general message. + httpx.HTTPStatusError("", request=None, response=httpx.Response(507)), + {"still_image_url": "unable_still_load"}, + ), + ], +) +async def test_form_image_http_exceptions( + side_effect, expected_message, hass: HomeAssistant, user_flow, mock_create_stream ) -> None: - """Test we handle invalid image timeout.""" + """Test we handle image http exceptions.""" respx.get("http://127.0.0.1/testurl/1").side_effect = [ - httpx.TimeoutException, + side_effect, ] with mock_create_stream: @@ -465,7 +495,7 @@ async def test_form_image_timeout( await hass.async_block_till_done() assert result2["type"] == "form" - assert result2["errors"] == {"still_image_url": "unable_still_load"} + assert result2["errors"] == expected_message @respx.mock @@ -499,7 +529,7 @@ async def test_form_stream_invalidimage2( await hass.async_block_till_done() assert result2["type"] == "form" - assert result2["errors"] == {"still_image_url": "unable_still_load"} + assert result2["errors"] == {"still_image_url": "unable_still_load_no_image"} @respx.mock From 4da701a8e9ee880fbbfbaec24c237e1e6146a509 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 21 Mar 2024 23:12:25 +0100 Subject: [PATCH 1366/1691] Add guard to HomeAssistantError `__str__` method to prevent a recursive loop (#113913) * Add guard to HomeAssistantError `__str__` method to prevent a recursive loop * Use repr of class instance instead * Apply suggestion to explain __str__ method is missing --------- Co-authored-by: J. Nick Koston --- homeassistant/exceptions.py | 9 +++ tests/test_exceptions.py | 156 ++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 81856f27c45..a58f683137b 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -64,6 +64,15 @@ class HomeAssistantError(Exception): return self._message if not self.generate_message: + # Initialize self._message to the string repr of the class + # to prevent a recursive loop. + self._message = ( + f"Parent class {self.__class__.__name__} is missing __str__ method" + ) + # If the there is an other super class involved, + # we want to call its __str__ method. + # If the super().__str__ method is missing in the base_class + # the call will be recursive and we return our initialized default. self._message = super().__str__() return self._message diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index f5b60627ce2..e5fd31c3b44 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -119,3 +119,159 @@ async def test_home_assistant_error( assert str(exc.value) == message # Get string of exception again from the cache assert str(exc.value) == message + + +async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None: + """Test __str__ method on an HomeAssistantError subclass.""" + + class _SubExceptionDefault(HomeAssistantError): + """Sub class, default with generated message.""" + + class _SubExceptionConstructor(HomeAssistantError): + """Sub class with constructor, no generated message.""" + + def __init__( + self, + custom_arg: str, + translation_domain: str | None = None, + translation_key: str | None = None, + translation_placeholders: dict[str, str] | None = None, + ) -> None: + super().__init__( + self, + translation_domain=translation_domain, + translation_key=translation_key, + translation_placeholders=translation_placeholders, + ) + self.custom_arg = custom_arg + + class _SubExceptionConstructorGenerate(HomeAssistantError): + """Sub class with constructor, with generated message.""" + + generate_message: bool = True + + def __init__( + self, + custom_arg: str, + translation_domain: str | None = None, + translation_key: str | None = None, + translation_placeholders: dict[str, str] | None = None, + ) -> None: + super().__init__( + self, + translation_domain=translation_domain, + translation_key=translation_key, + translation_placeholders=translation_placeholders, + ) + self.custom_arg = custom_arg + + class _SubExceptionGenerate(HomeAssistantError): + """Sub class, no generated message.""" + + generate_message: bool = True + + class _SubClassWithExceptionGroup(HomeAssistantError, BaseExceptionGroup): + """Sub class with exception group, no generated message.""" + + class _SubClassWithExceptionGroupGenerate(HomeAssistantError, BaseExceptionGroup): + """Sub class with exception group and generated message.""" + + generate_message: bool = True + + with patch( + "homeassistant.helpers.translation.async_get_cached_translations", + return_value={"component.test.exceptions.bla.message": "{bla} from cache"}, + ): + # A subclass without a constructor generates a message by default + with pytest.raises(HomeAssistantError) as exc: + raise _SubExceptionDefault( + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert str(exc.value) == "Bla from cache" + + # A subclass with a constructor that does not parse `args` to the super class + with pytest.raises(HomeAssistantError) as exc: + raise _SubExceptionConstructor( + "custom arg", + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert ( + str(exc.value) + == "Parent class _SubExceptionConstructor is missing __str__ method" + ) + with pytest.raises(HomeAssistantError) as exc: + raise _SubExceptionConstructor( + "custom arg", + ) + assert ( + str(exc.value) + == "Parent class _SubExceptionConstructor is missing __str__ method" + ) + + # A subclass with a constructor that generates the message + with pytest.raises(HomeAssistantError) as exc: + raise _SubExceptionConstructorGenerate( + "custom arg", + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert str(exc.value) == "Bla from cache" + + # A subclass without overridden constructors and passed args + # defaults to the passed args + with pytest.raises(HomeAssistantError) as exc: + raise _SubExceptionDefault( + ValueError("wrong value"), + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert str(exc.value) == "wrong value" + + # A subclass without overridden constructors and passed args + # and generate_message = True, generates a message + with pytest.raises(HomeAssistantError) as exc: + raise _SubExceptionGenerate( + ValueError("wrong value"), + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert str(exc.value) == "Bla from cache" + + # A subclass with and ExceptionGroup subclass requires a message to be passed. + # As we pass args, we will not generate the message. + # The __str__ constructor defaults to that of the super class. + with pytest.raises(HomeAssistantError) as exc: + raise _SubClassWithExceptionGroup( + "group message", + [ValueError("wrong value"), TypeError("wrong type")], + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert str(exc.value) == "group message (2 sub-exceptions)" + with pytest.raises(HomeAssistantError) as exc: + raise _SubClassWithExceptionGroup( + "group message", + [ValueError("wrong value"), TypeError("wrong type")], + ) + assert str(exc.value) == "group message (2 sub-exceptions)" + + # A subclass with and ExceptionGroup subclass requires a message to be passed. + # The `generate_message` flag is set.` + # The __str__ constructor will return the generated message. + with pytest.raises(HomeAssistantError) as exc: + raise _SubClassWithExceptionGroupGenerate( + "group message", + [ValueError("wrong value"), TypeError("wrong type")], + translation_domain="test", + translation_key="bla", + translation_placeholders={"bla": "Bla"}, + ) + assert str(exc.value) == "Bla from cache" From 5f5d40ed523d4c0fbe97b062753c5666f334fc6d Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Fri, 22 Mar 2024 00:56:31 +0100 Subject: [PATCH 1367/1691] Bump pyenphase to 1.20.0 (#113963) --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 9f437ee9945..7e2d70e914e 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "iot_class": "local_polling", "loggers": ["pyenphase"], - "requirements": ["pyenphase==1.19.2"], + "requirements": ["pyenphase==1.20.0"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index 07dab3fc6e2..cbf6a15a5a0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1787,7 +1787,7 @@ pyedimax==0.2.1 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.19.2 +pyenphase==1.20.0 # homeassistant.components.envisalink pyenvisalink==4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c96de54a87b..2353e3df349 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1386,7 +1386,7 @@ pyeconet==0.1.22 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.19.2 +pyenphase==1.20.0 # homeassistant.components.everlights pyeverlights==0.1.0 From b2cab70cc02d3fe1fc4b656799ef66e3d18c3f17 Mon Sep 17 00:00:00 2001 From: Paul Chanvin Date: Fri, 22 Mar 2024 02:26:11 +0100 Subject: [PATCH 1368/1691] Fix argument name in async_update_ha_state warning message (#113969) Fixed warning message using async_update_ha_state --- homeassistant/helpers/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index a2adee96500..988ce29ade2 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -957,7 +957,7 @@ class Entity( _LOGGER.warning( ( "Entity %s (%s) is using self.async_update_ha_state(), without" - " enabling force_update. Instead it should use" + " enabling force_refresh. Instead it should use" " self.async_write_ha_state(), please %s" ), self.entity_id, From 5b9f40b0f06921db93650002a84c2dec78d61733 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 Mar 2024 15:40:15 -1000 Subject: [PATCH 1369/1691] Pre import mobile app platforms to avoid having to wait on them (#113966) --- homeassistant/components/mobile_app/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 28f66954ca3..20a5448f2be 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -19,7 +19,16 @@ from homeassistant.helpers import ( from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType -from . import websocket_api +# Pre-import the platforms so they get loaded when the integration +# is imported as they are almost always going to be loaded and its +# cheaper to import them all at once. +from . import ( # noqa: F401 + binary_sensor as binary_sensor_pre_import, + device_tracker as device_tracker_pre_import, + notify as notify_pre_import, + sensor as sensor_pre_import, + websocket_api, +) from .const import ( ATTR_DEVICE_NAME, ATTR_MANUFACTURER, From 79f2eaaf41f057ef1decfd50fa0192859b078ede Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 22 Mar 2024 02:48:52 +0100 Subject: [PATCH 1370/1691] Deprecate the panel_iframe integration (#113410) * Deprecate the panel_iframe integration * Address review comments * Customize issue text * Update test --- .../components/panel_iframe/__init__.py | 62 +++++- .../components/panel_iframe/manifest.json | 2 +- .../components/panel_iframe/strings.json | 8 + tests/components/panel_iframe/test_init.py | 182 ++++++++++++------ 4 files changed, 183 insertions(+), 71 deletions(-) create mode 100644 homeassistant/components/panel_iframe/strings.json diff --git a/homeassistant/components/panel_iframe/__init__.py b/homeassistant/components/panel_iframe/__init__.py index c51768952eb..1b6dfebd6b0 100644 --- a/homeassistant/components/panel_iframe/__init__.py +++ b/homeassistant/components/panel_iframe/__init__.py @@ -2,10 +2,13 @@ import voluptuous as vol -from homeassistant.components import frontend +from homeassistant.components import lovelace +from homeassistant.components.lovelace import dashboard from homeassistant.const import CONF_ICON, CONF_URL from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.storage import Store from homeassistant.helpers.typing import ConfigType DOMAIN = "panel_iframe" @@ -37,18 +40,59 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +STORAGE_KEY = DOMAIN +STORAGE_VERSION_MAJOR = 1 + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the iFrame frontend panels.""" + async_create_issue( + hass, + DOMAIN, + "deprecated_yaml", + breaks_in_ha_version="2024.10.0", + is_fixable=False, + is_persistent=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": DOMAIN, + "integration_title": "iframe Panel", + }, + ) + + store: Store[dict[str, bool]] = Store( + hass, + STORAGE_VERSION_MAJOR, + STORAGE_KEY, + ) + data = await store.async_load() + if data: + return True + + dashboards_collection: dashboard.DashboardsCollection = hass.data[lovelace.DOMAIN][ + "dashboards_collection" + ] + for url_path, info in config[DOMAIN].items(): - frontend.async_register_built_in_panel( - hass, - "iframe", - info.get(CONF_TITLE), - info.get(CONF_ICON), - url_path, - {"url": info[CONF_URL]}, - require_admin=info[CONF_REQUIRE_ADMIN], + dashboard_create_data = { + lovelace.CONF_ALLOW_SINGLE_WORD: True, + lovelace.CONF_URL_PATH: url_path, + } + for key in (CONF_ICON, CONF_REQUIRE_ADMIN, CONF_TITLE): + if key in info: + dashboard_create_data[key] = info[key] + + await dashboards_collection.async_create_item(dashboard_create_data) + + dashboard_store: dashboard.LovelaceStorage = hass.data[lovelace.DOMAIN][ + "dashboards" + ][url_path] + await dashboard_store.async_save( + {"strategy": {"type": "iframe", "url": info[CONF_URL]}} ) + await store.async_save({"migrated": True}) + return True diff --git a/homeassistant/components/panel_iframe/manifest.json b/homeassistant/components/panel_iframe/manifest.json index 04eeb93ffa6..7a39e0ba17d 100644 --- a/homeassistant/components/panel_iframe/manifest.json +++ b/homeassistant/components/panel_iframe/manifest.json @@ -2,7 +2,7 @@ "domain": "panel_iframe", "name": "iframe Panel", "codeowners": ["@home-assistant/frontend"], - "dependencies": ["frontend"], + "dependencies": ["frontend", "lovelace"], "documentation": "https://www.home-assistant.io/integrations/panel_iframe", "quality_scale": "internal" } diff --git a/homeassistant/components/panel_iframe/strings.json b/homeassistant/components/panel_iframe/strings.json new file mode 100644 index 00000000000..595b1f04818 --- /dev/null +++ b/homeassistant/components/panel_iframe/strings.json @@ -0,0 +1,8 @@ +{ + "issues": { + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically as a regular dashboard.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + } +} diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index a16d87f1838..0e898fd6266 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -1,11 +1,37 @@ """The tests for the panel_iframe component.""" +from typing import Any + import pytest -from homeassistant.components import frontend +from homeassistant.components.panel_iframe import DOMAIN from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir from homeassistant.setup import async_setup_component +from tests.typing import WebSocketGenerator + +TEST_CONFIG = { + "router": { + "icon": "mdi:network-wireless", + "title": "Router", + "url": "http://192.168.1.1", + "require_admin": True, + }, + "weather": { + "icon": "mdi:weather", + "title": "Weather", + "url": "https://www.wunderground.com/us/ca/san-diego", + "require_admin": True, + }, + "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, + "ftp": { + "icon": "mdi:weather", + "title": "FTP", + "url": "ftp://some/ftp", + }, +} + @pytest.mark.parametrize( "config_to_try", @@ -21,73 +47,107 @@ async def test_wrong_config(hass: HomeAssistant, config_to_try) -> None: ) -async def test_correct_config(hass: HomeAssistant) -> None: - """Test correct config.""" +async def test_import_config( + hass: HomeAssistant, + hass_storage: dict[str, Any], + hass_ws_client: WebSocketGenerator, +) -> None: + """Test import config.""" + client = await hass_ws_client(hass) + assert await async_setup_component( hass, "panel_iframe", - { - "panel_iframe": { - "router": { - "icon": "mdi:network-wireless", - "title": "Router", - "url": "http://192.168.1.1", - "require_admin": True, - }, - "weather": { - "icon": "mdi:weather", - "title": "Weather", - "url": "https://www.wunderground.com/us/ca/san-diego", - "require_admin": True, - }, - "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, - "ftp": { - "icon": "mdi:weather", - "title": "FTP", - "url": "ftp://some/ftp", - }, - } - }, + {"panel_iframe": TEST_CONFIG}, ) - panels = hass.data[frontend.DATA_PANELS] + # List dashboards + await client.send_json_auto_id({"type": "lovelace/dashboards/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [ + { + "icon": "mdi:network-wireless", + "id": "router", + "mode": "storage", + "require_admin": True, + "show_in_sidebar": True, + "title": "Router", + "url_path": "router", + }, + { + "icon": "mdi:weather", + "id": "weather", + "mode": "storage", + "require_admin": True, + "show_in_sidebar": True, + "title": "Weather", + "url_path": "weather", + }, + { + "icon": "mdi:weather", + "id": "api", + "mode": "storage", + "require_admin": False, + "show_in_sidebar": True, + "title": "Api", + "url_path": "api", + }, + { + "icon": "mdi:weather", + "id": "ftp", + "mode": "storage", + "require_admin": False, + "show_in_sidebar": True, + "title": "FTP", + "url_path": "ftp", + }, + ] - assert panels.get("router").to_response() == { - "component_name": "iframe", - "config": {"url": "http://192.168.1.1"}, - "config_panel_domain": None, - "icon": "mdi:network-wireless", - "title": "Router", - "url_path": "router", - "require_admin": True, + for url_path in ["api", "ftp", "router", "weather"]: + await client.send_json_auto_id( + {"type": "lovelace/config", "url_path": url_path} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == { + "strategy": {"type": "iframe", "url": TEST_CONFIG[url_path]["url"]} + } + + assert hass_storage[DOMAIN]["data"] == {"migrated": True} + + +async def test_import_config_once( + hass: HomeAssistant, + hass_storage: dict[str, Any], + hass_ws_client: WebSocketGenerator, +) -> None: + """Test import config only happens once.""" + client = await hass_ws_client(hass) + + hass_storage[DOMAIN] = { + "version": 1, + "minor_version": 1, + "key": "map", + "data": {"migrated": True}, } - assert panels.get("weather").to_response() == { - "component_name": "iframe", - "config": {"url": "https://www.wunderground.com/us/ca/san-diego"}, - "config_panel_domain": None, - "icon": "mdi:weather", - "title": "Weather", - "url_path": "weather", - "require_admin": True, - } + assert await async_setup_component( + hass, + "panel_iframe", + {"panel_iframe": TEST_CONFIG}, + ) - assert panels.get("api").to_response() == { - "component_name": "iframe", - "config": {"url": "/api"}, - "config_panel_domain": None, - "icon": "mdi:weather", - "title": "Api", - "url_path": "api", - "require_admin": False, - } + # List dashboards + await client.send_json_auto_id({"type": "lovelace/dashboards/list"}) + response = await client.receive_json() + assert response["success"] + assert response["result"] == [] - assert panels.get("ftp").to_response() == { - "component_name": "iframe", - "config": {"url": "ftp://some/ftp"}, - "config_panel_domain": None, - "icon": "mdi:weather", - "title": "FTP", - "url_path": "ftp", - "require_admin": False, - } + +async def test_create_issue_when_manually_configured(hass: HomeAssistant) -> None: + """Test creating issue registry issues.""" + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + + issue_registry = ir.async_get(hass) + assert issue_registry.async_get_issue(DOMAIN, "deprecated_yaml") From abb217086f75599ccbb75e652ece0715dd3771d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 Mar 2024 15:59:50 -1000 Subject: [PATCH 1371/1691] Group wemo platform forwards to reduce overhead (#113972) --- homeassistant/components/wemo/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/wemo/__init__.py b/homeassistant/components/wemo/__init__.py index 5cf0566278c..8a9a122c03c 100644 --- a/homeassistant/components/wemo/__init__.py +++ b/homeassistant/components/wemo/__init__.py @@ -193,6 +193,7 @@ class WemoDispatcher: platforms = set(WEMO_MODEL_DISPATCH.get(wemo.model_name, [Platform.SWITCH])) platforms.add(Platform.SENSOR) + platforms_to_load: list[Platform] = [] for platform in platforms: # Three cases: # - Platform is loaded, dispatch discovery @@ -205,11 +206,14 @@ class WemoDispatcher: self._dispatch_backlog[platform].append(coordinator) else: self._dispatch_backlog[platform] = [coordinator] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup( - self._config_entry, platform - ) + platforms_to_load.append(platform) + + if platforms_to_load: + hass.async_create_task( + hass.config_entries.async_forward_entry_setups( + self._config_entry, platforms_to_load ) + ) self._added_serial_numbers.add(wemo.serial_number) self._failed_serial_numbers.discard(wemo.serial_number) From a2143a7c1aae5cfabf4f0e1edd6c665332a81ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 22 Mar 2024 11:20:47 +0100 Subject: [PATCH 1372/1691] Return default repairs flow for cloud TTS issues (#113981) * Set TTS repairs as not fixable * Return default confirm flow for fixable cloud issues * Depend on repairs * Test default repair flow --------- Co-authored-by: Martin Hjelmare --- homeassistant/components/cloud/manifest.json | 2 +- homeassistant/components/cloud/repairs.py | 10 ++- tests/components/cloud/test_tts.py | 91 +++++++++++++++++++- 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index ac9f47935d4..eed2bda421b 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -3,7 +3,7 @@ "name": "Home Assistant Cloud", "after_dependencies": ["assist_pipeline", "google_assistant", "alexa"], "codeowners": ["@home-assistant/cloud"], - "dependencies": ["http", "webhook"], + "dependencies": ["http", "repairs", "webhook"], "documentation": "https://www.home-assistant.io/integrations/cloud", "integration_type": "system", "iot_class": "cloud_push", diff --git a/homeassistant/components/cloud/repairs.py b/homeassistant/components/cloud/repairs.py index 4dd17e1609e..1c5a8f1f86d 100644 --- a/homeassistant/components/cloud/repairs.py +++ b/homeassistant/components/cloud/repairs.py @@ -8,7 +8,11 @@ from typing import Any from hass_nabucasa import Cloud import voluptuous as vol -from homeassistant.components.repairs import RepairsFlow, repairs_flow_manager +from homeassistant.components.repairs import ( + ConfirmRepairFlow, + RepairsFlow, + repairs_flow_manager, +) from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import issue_registry as ir @@ -120,4 +124,6 @@ async def async_create_fix_flow( data: dict[str, str | int | float | None] | None, ) -> RepairsFlow: """Create flow.""" - return LegacySubscriptionRepairFlow() + if issue_id == "legacy_subscription": + return LegacySubscriptionRepairFlow() + return ConfirmRepairFlow() diff --git a/tests/components/cloud/test_tts.py b/tests/components/cloud/test_tts.py index f549f62b889..3fd9ec5e4a4 100644 --- a/tests/components/cloud/test_tts.py +++ b/tests/components/cloud/test_tts.py @@ -526,6 +526,8 @@ async def test_deprecated_voice( } await hass.async_block_till_done() + issue_id = f"deprecated_voice_{deprecated_voice}" + assert mock_process_tts.call_count == 1 assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." @@ -533,9 +535,7 @@ async def test_deprecated_voice( assert mock_process_tts.call_args.kwargs["gender"] is None assert mock_process_tts.call_args.kwargs["voice"] == replacement_voice assert mock_process_tts.call_args.kwargs["output"] == "mp3" - issue = issue_registry.async_get_issue( - "cloud", f"deprecated_voice_{deprecated_voice}" - ) + issue = issue_registry.async_get_issue("cloud", issue_id) assert issue is not None assert issue.breaks_in_ha_version == "2024.8.0" assert issue.is_fixable is True @@ -547,6 +547,46 @@ async def test_deprecated_voice( "replacement_voice": replacement_voice, } + resp = await client.post( + "/api/repairs/issues/fix", + json={"handler": DOMAIN, "issue_id": issue.issue_id}, + ) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "type": "form", + "flow_id": flow_id, + "handler": DOMAIN, + "step_id": "confirm", + "data_schema": [], + "errors": None, + "description_placeholders": { + "deprecated_voice": "XiaoxuanNeural", + "replacement_voice": "XiaozhenNeural", + }, + "last_step": None, + "preview": None, + } + + resp = await client.post(f"/api/repairs/issues/fix/{flow_id}") + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "type": "create_entry", + "flow_id": flow_id, + "handler": DOMAIN, + "description": None, + "description_placeholders": None, + } + + assert not issue_registry.async_get_issue(DOMAIN, issue_id) + @pytest.mark.parametrize( ("data", "expected_url_suffix"), @@ -631,6 +671,8 @@ async def test_deprecated_gender( } await hass.async_block_till_done() + issue_id = "deprecated_gender" + assert mock_process_tts.call_count == 1 assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." @@ -638,7 +680,7 @@ async def test_deprecated_gender( assert mock_process_tts.call_args.kwargs["gender"] == gender_option assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" assert mock_process_tts.call_args.kwargs["output"] == "mp3" - issue = issue_registry.async_get_issue("cloud", "deprecated_gender") + issue = issue_registry.async_get_issue("cloud", issue_id) assert issue is not None assert issue.breaks_in_ha_version == "2024.10.0" assert issue.is_fixable is True @@ -650,3 +692,44 @@ async def test_deprecated_gender( "deprecated_option": "gender", "replacement_option": "voice", } + + resp = await client.post( + "/api/repairs/issues/fix", + json={"handler": DOMAIN, "issue_id": issue.issue_id}, + ) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "type": "form", + "flow_id": flow_id, + "handler": DOMAIN, + "step_id": "confirm", + "data_schema": [], + "errors": None, + "description_placeholders": { + "integration_name": "Home Assistant Cloud", + "deprecated_option": "gender", + "replacement_option": "voice", + }, + "last_step": None, + "preview": None, + } + + resp = await client.post(f"/api/repairs/issues/fix/{flow_id}") + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "type": "create_entry", + "flow_id": flow_id, + "handler": DOMAIN, + "description": None, + "description_placeholders": None, + } + + assert not issue_registry.async_get_issue(DOMAIN, issue_id) From ee2e98b4750e6f47ee148580943462195e702126 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 22 Mar 2024 13:27:06 +0100 Subject: [PATCH 1373/1691] Update cosign to 2.2.3 (#113996) --- .github/workflows/builder.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 4718ebbc117..d1fd9f41eea 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -343,7 +343,7 @@ jobs: - name: Install Cosign uses: sigstore/cosign-installer@v3.4.0 with: - cosign-release: "v2.0.2" + cosign-release: "v2.2.3" - name: Login to DockerHub uses: docker/login-action@v3.1.0 From 9ca253213cb19739fae06d1f13764e1070282453 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 22 Mar 2024 13:39:36 +0100 Subject: [PATCH 1374/1691] Correct HomeAssistantError __str__ implementation and test (#113991) Correct HomeAssistant sub class implementation and test --- homeassistant/exceptions.py | 9 --------- tests/test_exceptions.py | 16 ++++------------ 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index a58f683137b..81856f27c45 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -64,15 +64,6 @@ class HomeAssistantError(Exception): return self._message if not self.generate_message: - # Initialize self._message to the string repr of the class - # to prevent a recursive loop. - self._message = ( - f"Parent class {self.__class__.__name__} is missing __str__ method" - ) - # If the there is an other super class involved, - # we want to call its __str__ method. - # If the super().__str__ method is missing in the base_class - # the call will be recursive and we return our initialized default. self._message = super().__str__() return self._message diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index e5fd31c3b44..5e113d3ba10 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -138,7 +138,6 @@ async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None: translation_placeholders: dict[str, str] | None = None, ) -> None: super().__init__( - self, translation_domain=translation_domain, translation_key=translation_key, translation_placeholders=translation_placeholders, @@ -158,7 +157,6 @@ async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None: translation_placeholders: dict[str, str] | None = None, ) -> None: super().__init__( - self, translation_domain=translation_domain, translation_key=translation_key, translation_placeholders=translation_placeholders, @@ -199,18 +197,12 @@ async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None: translation_key="bla", translation_placeholders={"bla": "Bla"}, ) - assert ( - str(exc.value) - == "Parent class _SubExceptionConstructor is missing __str__ method" - ) + assert str(exc.value) == "Bla from cache" with pytest.raises(HomeAssistantError) as exc: raise _SubExceptionConstructor( "custom arg", ) - assert ( - str(exc.value) - == "Parent class _SubExceptionConstructor is missing __str__ method" - ) + assert str(exc.value) == "" # A subclass with a constructor that generates the message with pytest.raises(HomeAssistantError) as exc: @@ -244,7 +236,7 @@ async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None: ) assert str(exc.value) == "Bla from cache" - # A subclass with and ExceptionGroup subclass requires a message to be passed. + # A subclass with an ExceptionGroup subclass requires a message to be passed. # As we pass args, we will not generate the message. # The __str__ constructor defaults to that of the super class. with pytest.raises(HomeAssistantError) as exc: @@ -263,7 +255,7 @@ async def test_home_assistant_error_subclass(hass: HomeAssistant) -> None: ) assert str(exc.value) == "group message (2 sub-exceptions)" - # A subclass with and ExceptionGroup subclass requires a message to be passed. + # A subclass with an ExceptionGroup subclass requires a message to be passed. # The `generate_message` flag is set.` # The __str__ constructor will return the generated message. with pytest.raises(HomeAssistantError) as exc: From 16283aad49dff763bdec3a57fc26380c1790b3d7 Mon Sep 17 00:00:00 2001 From: Massimo Savazzi Date: Fri, 22 Mar 2024 14:00:10 +0100 Subject: [PATCH 1375/1691] Update JVC Projector polling time to be more responsive for automations (#113765) --- homeassistant/components/jvc_projector/coordinator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/jvc_projector/coordinator.py b/homeassistant/components/jvc_projector/coordinator.py index a63d68781b3..874253b3324 100644 --- a/homeassistant/components/jvc_projector/coordinator.py +++ b/homeassistant/components/jvc_projector/coordinator.py @@ -21,8 +21,8 @@ from .const import NAME _LOGGER = logging.getLogger(__name__) -INTERVAL_SLOW = timedelta(seconds=60) -INTERVAL_FAST = timedelta(seconds=6) +INTERVAL_SLOW = timedelta(seconds=10) +INTERVAL_FAST = timedelta(seconds=5) class JvcProjectorDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str]]): From aae85edb73b4eda48381cb7c18bfcaac48ad7309 Mon Sep 17 00:00:00 2001 From: Federico D'Amico <48856240+FedDam@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:01:36 +0100 Subject: [PATCH 1376/1691] Add binary sensor platform to microBees (#111190) --- .coveragerc | 1 + .../components/microbees/binary_sensor.py | 82 +++++++++++++++++++ homeassistant/components/microbees/const.py | 1 + 3 files changed, 84 insertions(+) create mode 100644 homeassistant/components/microbees/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index 2d16ab0575e..f7f619a1756 100644 --- a/.coveragerc +++ b/.coveragerc @@ -776,6 +776,7 @@ omit = homeassistant/components/microbees/__init__.py homeassistant/components/microbees/api.py homeassistant/components/microbees/application_credentials.py + homeassistant/components/microbees/binary_sensor.py homeassistant/components/microbees/button.py homeassistant/components/microbees/const.py homeassistant/components/microbees/coordinator.py diff --git a/homeassistant/components/microbees/binary_sensor.py b/homeassistant/components/microbees/binary_sensor.py new file mode 100644 index 00000000000..929aae52490 --- /dev/null +++ b/homeassistant/components/microbees/binary_sensor.py @@ -0,0 +1,82 @@ +"""BinarySensor integration microBees.""" +from microBeesPy import Sensor + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import MicroBeesUpdateCoordinator +from .entity import MicroBeesEntity + +BINARYSENSOR_TYPES = { + 12: BinarySensorEntityDescription( + device_class=BinarySensorDeviceClass.MOTION, + key="motion_sensor", + ), + 13: BinarySensorEntityDescription( + device_class=BinarySensorDeviceClass.DOOR, + key="door_sensor", + ), + 19: BinarySensorEntityDescription( + device_class=BinarySensorDeviceClass.MOISTURE, + key="moisture_sensor", + ), + 20: BinarySensorEntityDescription( + device_class=BinarySensorDeviceClass.SMOKE, + key="smoke_sensor", + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the microBees binary sensor platform.""" + coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][ + entry.entry_id + ].coordinator + async_add_entities( + MBBinarySensor(coordinator, entity_description, bee_id, binary_sensor.id) + for bee_id, bee in coordinator.data.bees.items() + for binary_sensor in bee.sensors + if (entity_description := BINARYSENSOR_TYPES.get(binary_sensor.device_type)) + is not None + ) + + +class MBBinarySensor(MicroBeesEntity, BinarySensorEntity): + """Representation of a microBees BinarySensor.""" + + def __init__( + self, + coordinator: MicroBeesUpdateCoordinator, + entity_description: BinarySensorEntityDescription, + bee_id: int, + sensor_id: int, + ) -> None: + """Initialize the microBees BinarySensor.""" + super().__init__(coordinator, bee_id) + self._attr_unique_id = f"{bee_id}_{sensor_id}" + self.sensor_id = sensor_id + self.entity_description = entity_description + + @property + def name(self) -> str: + """Name of the BinarySensor.""" + return self.sensor.name + + @property + def is_on(self) -> bool: + """Return the state of the BinarySensor.""" + return self.sensor.value + + @property + def sensor(self) -> Sensor: + """Return the BinarySensor.""" + return self.coordinator.data.sensors[self.sensor_id] diff --git a/homeassistant/components/microbees/const.py b/homeassistant/components/microbees/const.py index df4d3f63bd0..869b4e59a89 100644 --- a/homeassistant/components/microbees/const.py +++ b/homeassistant/components/microbees/const.py @@ -6,6 +6,7 @@ DOMAIN = "microbees" OAUTH2_AUTHORIZE = "https://dev.microbees.com/oauth/authorize" OAUTH2_TOKEN = "https://dev.microbees.com/oauth/token" PLATFORMS = [ + Platform.BINARY_SENSOR, Platform.BUTTON, Platform.LIGHT, Platform.SENSOR, From 8aa6447a4f390b621828196a5b1aea3fc35864cb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 22 Mar 2024 14:04:13 +0100 Subject: [PATCH 1377/1691] Add icon translations to Wiz (#112358) --- homeassistant/components/wiz/icons.json | 12 ++++++++++++ homeassistant/components/wiz/number.py | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/wiz/icons.json diff --git a/homeassistant/components/wiz/icons.json b/homeassistant/components/wiz/icons.json new file mode 100644 index 00000000000..896f28a8ef1 --- /dev/null +++ b/homeassistant/components/wiz/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "number": { + "effect_speed": { + "default": "mdi:speedometer" + }, + "dual_head_ratio": { + "default": "mdi:floor-lamp-dual" + } + } + } +} diff --git a/homeassistant/components/wiz/number.py b/homeassistant/components/wiz/number.py index ed47aec44c3..46708ac001e 100644 --- a/homeassistant/components/wiz/number.py +++ b/homeassistant/components/wiz/number.py @@ -47,7 +47,6 @@ NUMBERS: tuple[WizNumberEntityDescription, ...] = ( native_min_value=10, native_max_value=200, native_step=1, - icon="mdi:speedometer", value_fn=lambda device: cast(int | None, device.state.get_speed()), set_value_fn=_async_set_speed, required_feature="effect", @@ -59,7 +58,6 @@ NUMBERS: tuple[WizNumberEntityDescription, ...] = ( native_min_value=0, native_max_value=100, native_step=1, - icon="mdi:floor-lamp-dual", value_fn=lambda device: cast(int | None, device.state.get_ratio()), set_value_fn=_async_set_ratio, required_feature="dual_head", From 29cdac381c72fd0d2719563748cfdc18d8aff4b8 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 22 Mar 2024 15:29:49 +0200 Subject: [PATCH 1378/1691] Add reformatting commits to .git-blame-ignore-revs (#110481) --- .git-blame-ignore-revs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..52e0608d5b7 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,11 @@ +# Black +4de97abc3aa83188666336ce0a015a5bab75bc8f + +# Switch formatting from black to ruff-format (#102893) +706add4a57120a93d7b7fe40e722b00d634c76c2 + +# Prettify json (component test fixtures) (#68892) +053c4428a933c3c04c22642f93c93fccba3e8bfd + +# Prettify json (tests) (#68888) +496d90bf00429d9d924caeb0155edc0bf54e86b9 From 5b6361080c00a94fbe5ea235d9555fe39ca2b491 Mon Sep 17 00:00:00 2001 From: Federico D'Amico <48856240+FedDam@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:31:09 +0100 Subject: [PATCH 1379/1691] Add cover platform to microBees (#111135) --- .coveragerc | 1 + homeassistant/components/microbees/const.py | 1 + homeassistant/components/microbees/cover.py | 116 ++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 homeassistant/components/microbees/cover.py diff --git a/.coveragerc b/.coveragerc index f7f619a1756..c58601b2245 100644 --- a/.coveragerc +++ b/.coveragerc @@ -780,6 +780,7 @@ omit = homeassistant/components/microbees/button.py homeassistant/components/microbees/const.py homeassistant/components/microbees/coordinator.py + homeassistant/components/microbees/cover.py homeassistant/components/microbees/entity.py homeassistant/components/microbees/light.py homeassistant/components/microbees/sensor.py diff --git a/homeassistant/components/microbees/const.py b/homeassistant/components/microbees/const.py index 869b4e59a89..ab8637f0f75 100644 --- a/homeassistant/components/microbees/const.py +++ b/homeassistant/components/microbees/const.py @@ -8,6 +8,7 @@ OAUTH2_TOKEN = "https://dev.microbees.com/oauth/token" PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, + Platform.COVER, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH, diff --git a/homeassistant/components/microbees/cover.py b/homeassistant/components/microbees/cover.py new file mode 100644 index 00000000000..5caf57403de --- /dev/null +++ b/homeassistant/components/microbees/cover.py @@ -0,0 +1,116 @@ +"""Cover integration microBees.""" +from typing import Any + +from microBeesPy import Actuator + +from homeassistant.components.cover import ( + CoverDeviceClass, + CoverEntity, + CoverEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.event import async_call_later + +from .const import DOMAIN +from .coordinator import MicroBeesUpdateCoordinator +from .entity import MicroBeesEntity + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the microBees cover platform.""" + coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][ + entry.entry_id + ].coordinator + + async_add_entities( + MBCover( + coordinator, + bee_id, + next(filter(lambda x: x.deviceID == 551, bee.actuators)).id, + next(filter(lambda x: x.deviceID == 552, bee.actuators)).id, + ) + for bee_id, bee in coordinator.data.bees.items() + if bee.productID == 47 + ) + + +class MBCover(MicroBeesEntity, CoverEntity): + """Representation of a microBees cover.""" + + _attr_device_class = CoverDeviceClass.SHUTTER + _attr_supported_features = ( + CoverEntityFeature.OPEN | CoverEntityFeature.STOP | CoverEntityFeature.CLOSE + ) + + def __init__(self, coordinator, bee_id, actuator_up_id, actuator_down_id) -> None: + """Initialize the microBees cover.""" + super().__init__(coordinator, bee_id) + self.actuator_up_id = actuator_up_id + self.actuator_down_id = actuator_down_id + self._attr_is_closed = None + + @property + def name(self) -> str: + """Name of the cover.""" + return self.bee.name + + @property + def actuator_up(self) -> Actuator: + """Return the rolling up actuator.""" + return self.coordinator.data.actuators[self.actuator_up_id] + + @property + def actuator_down(self) -> Actuator: + """Return the rolling down actuator.""" + return self.coordinator.data.actuators[self.actuator_down_id] + + def _reset_open_close(self, *_: Any) -> None: + """Reset the opening and closing state.""" + self._attr_is_opening = False + self._attr_is_closing = False + self.async_write_ha_state() + + async def async_open_cover(self, **kwargs: Any) -> None: + """Open the cover.""" + sendCommand = await self.coordinator.microbees.sendCommand( + self.actuator_up_id, + self.actuator_up.configuration.actuator_timing * 1000, + ) + + if not sendCommand: + raise HomeAssistantError(f"Failed to open {self.name}") + + self._attr_is_opening = True + async_call_later( + self.hass, + self.actuator_down.configuration.actuator_timing, + self._reset_open_close, + ) + + async def async_close_cover(self, **kwargs: Any) -> None: + """Close the cover.""" + sendCommand = await self.coordinator.microbees.sendCommand( + self.actuator_down_id, + self.actuator_down.configuration.actuator_timing * 1000, + ) + if not sendCommand: + raise HomeAssistantError(f"Failed to close {self.name}") + + self._attr_is_closing = True + async_call_later( + self.hass, + self.actuator_down.configuration.actuator_timing, + self._reset_open_close, + ) + + async def async_stop_cover(self, **kwargs: Any) -> None: + """Stop the cover.""" + if self.is_opening: + await self.coordinator.microbees.sendCommand(self.actuator_up_id, 0) + if self.is_closing: + await self.coordinator.microbees.sendCommand(self.actuator_down_id, 0) From a9e857202de36bc70e906fe5aae6afae74d55905 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 22 Mar 2024 15:49:51 +0100 Subject: [PATCH 1380/1691] Axis use entity descripton binary sensor platform (#113705) --- .../components/axis/binary_sensor.py | 260 ++++++++++++------ tests/components/axis/test_binary_sensor.py | 109 ++++++++ 2 files changed, 292 insertions(+), 77 deletions(-) diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 4e724c804bd..e68487a6bb1 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -2,10 +2,12 @@ from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Iterable +from dataclasses import dataclass from datetime import datetime, timedelta +from functools import partial -from axis.models.event import Event, EventGroup, EventOperation, EventTopic +from axis.models.event import Event, EventOperation, EventTopic from axis.vapix.interfaces.applications.fence_guard import FenceGuardHandler from axis.vapix.interfaces.applications.loitering_guard import LoiteringGuardHandler from axis.vapix.interfaces.applications.motion_guard import MotionGuardHandler @@ -14,6 +16,7 @@ from axis.vapix.interfaces.applications.vmd4 import Vmd4Handler from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, + BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -23,26 +26,160 @@ from homeassistant.helpers.event import async_call_later from .entity import AxisEventEntity from .hub import AxisHub -DEVICE_CLASS = { - EventGroup.INPUT: BinarySensorDeviceClass.CONNECTIVITY, - EventGroup.LIGHT: BinarySensorDeviceClass.LIGHT, - EventGroup.MOTION: BinarySensorDeviceClass.MOTION, - EventGroup.SOUND: BinarySensorDeviceClass.SOUND, -} -EVENT_TOPICS = ( - EventTopic.DAY_NIGHT_VISION, - EventTopic.FENCE_GUARD, - EventTopic.LOITERING_GUARD, - EventTopic.MOTION_DETECTION, - EventTopic.MOTION_DETECTION_3, - EventTopic.MOTION_DETECTION_4, - EventTopic.MOTION_GUARD, - EventTopic.OBJECT_ANALYTICS, - EventTopic.PIR, - EventTopic.PORT_INPUT, - EventTopic.PORT_SUPERVISED_INPUT, - EventTopic.SOUND_TRIGGER_LEVEL, +@dataclass(frozen=True, kw_only=True) +class AxisBinarySensorDescription(BinarySensorEntityDescription): + """Axis binary sensor entity description.""" + + event_topic: tuple[EventTopic, ...] | EventTopic + """Event topic that provides state updates.""" + name_fn: Callable[[AxisHub, Event], str] = lambda hub, event: "" + """Function providing the corresponding name to the event ID.""" + supported_fn: Callable[[AxisHub, Event], bool] = lambda hub, event: True + """Function validating if event is supported.""" + + +@callback +def event_id_is_int(event_id: str) -> bool: + """Make sure event ID is int.""" + try: + _ = int(event_id) + except ValueError: + return False + return True + + +@callback +def guard_suite_supported_fn(hub: AxisHub, event: Event) -> bool: + """Validate event ID is int.""" + _, _, profile_id = event.id.partition("Profile") + return event_id_is_int(profile_id) + + +@callback +def object_analytics_supported_fn(hub: AxisHub, event: Event) -> bool: + """Validate event ID is int.""" + _, _, profile_id = event.id.partition("Scenario") + return event_id_is_int(profile_id) + + +@callback +def guard_suite_name_fn( + handler: FenceGuardHandler + | LoiteringGuardHandler + | MotionGuardHandler + | Vmd4Handler, + event: Event, + event_type: str, +) -> str: + """Get guard suite item name.""" + if handler.initialized and (profiles := handler["0"].profiles): + for profile_id, profile in profiles.items(): + camera_id = profile.camera + if event.id == f"Camera{camera_id}Profile{profile_id}": + return f"{event_type} {profile.name}" + return "" + + +@callback +def fence_guard_name_fn(hub: AxisHub, event: Event) -> str: + """Fence guard name.""" + return guard_suite_name_fn(hub.api.vapix.fence_guard, event, "Fence Guard") + + +@callback +def loitering_guard_name_fn(hub: AxisHub, event: Event) -> str: + """Loitering guard name.""" + return guard_suite_name_fn(hub.api.vapix.loitering_guard, event, "Loitering Guard") + + +@callback +def motion_guard_name_fn(hub: AxisHub, event: Event) -> str: + """Motion guard name.""" + return guard_suite_name_fn(hub.api.vapix.motion_guard, event, "Motion Guard") + + +@callback +def motion_detection_4_name_fn(hub: AxisHub, event: Event) -> str: + """Motion detection 4 name.""" + return guard_suite_name_fn(hub.api.vapix.vmd4, event, "VMD4") + + +@callback +def object_analytics_name_fn(hub: AxisHub, event: Event) -> str: + """Get object analytics name.""" + if hub.api.vapix.object_analytics.initialized and ( + scenarios := hub.api.vapix.object_analytics["0"].scenarios + ): + for scenario_id, scenario in scenarios.items(): + device_id = scenario.devices[0]["id"] + if event.id == f"Device{device_id}Scenario{scenario_id}": + return f"Object Analytics {scenario.name}" + return "" + + +ENTITY_DESCRIPTIONS = ( + AxisBinarySensorDescription( + key="Input port state", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + event_topic=(EventTopic.PORT_INPUT, EventTopic.PORT_SUPERVISED_INPUT), + name_fn=lambda hub, event: hub.api.vapix.ports[event.id].name, + supported_fn=lambda hub, event: event_id_is_int(event.id), + ), + AxisBinarySensorDescription( + key="Day/Night vision state", + device_class=BinarySensorDeviceClass.LIGHT, + event_topic=EventTopic.DAY_NIGHT_VISION, + ), + AxisBinarySensorDescription( + key="Sound trigger state", + device_class=BinarySensorDeviceClass.SOUND, + event_topic=EventTopic.SOUND_TRIGGER_LEVEL, + ), + AxisBinarySensorDescription( + key="Motion sensors state", + device_class=BinarySensorDeviceClass.MOTION, + event_topic=( + EventTopic.PIR, + EventTopic.MOTION_DETECTION, + EventTopic.MOTION_DETECTION_3, + ), + ), + AxisBinarySensorDescription( + key="Motion detection 4 state", + device_class=BinarySensorDeviceClass.MOTION, + event_topic=EventTopic.MOTION_DETECTION_4, + name_fn=motion_detection_4_name_fn, + supported_fn=guard_suite_supported_fn, + ), + AxisBinarySensorDescription( + key="Fence guard state", + device_class=BinarySensorDeviceClass.MOTION, + event_topic=EventTopic.FENCE_GUARD, + name_fn=fence_guard_name_fn, + supported_fn=guard_suite_supported_fn, + ), + AxisBinarySensorDescription( + key="Loitering guard state", + device_class=BinarySensorDeviceClass.MOTION, + event_topic=EventTopic.LOITERING_GUARD, + name_fn=loitering_guard_name_fn, + supported_fn=guard_suite_supported_fn, + ), + AxisBinarySensorDescription( + key="Motion guard state", + device_class=BinarySensorDeviceClass.MOTION, + event_topic=EventTopic.MOTION_GUARD, + name_fn=motion_guard_name_fn, + supported_fn=guard_suite_supported_fn, + ), + AxisBinarySensorDescription( + key="Object analytics state", + device_class=BinarySensorDeviceClass.MOTION, + event_topic=EventTopic.OBJECT_ANALYTICS, + name_fn=object_analytics_name_fn, + supported_fn=object_analytics_supported_fn, + ), ) @@ -55,29 +192,40 @@ async def async_setup_entry( hub = AxisHub.get_hub(hass, config_entry) @callback - def async_create_entity(event: Event) -> None: - """Create Axis binary sensor entity.""" - async_add_entities([AxisBinarySensor(event, hub)]) + def register_platform(descriptions: Iterable[AxisBinarySensorDescription]) -> None: + """Register entity platform to create entities on event initialized signal.""" - hub.api.event.subscribe( - async_create_entity, - topic_filter=EVENT_TOPICS, - operation_filter=EventOperation.INITIALIZED, - ) + @callback + def create_entity( + description: AxisBinarySensorDescription, event: Event + ) -> None: + """Create Axis entity.""" + if description.supported_fn(hub, event): + async_add_entities([AxisBinarySensor(hub, description, event)]) + + for description in descriptions: + hub.api.event.subscribe( + partial(create_entity, description), + topic_filter=description.event_topic, + operation_filter=EventOperation.INITIALIZED, + ) + + register_platform(ENTITY_DESCRIPTIONS) class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): """Representation of a binary Axis event.""" - def __init__(self, event: Event, hub: AxisHub) -> None: + def __init__( + self, hub: AxisHub, description: AxisBinarySensorDescription, event: Event + ) -> None: """Initialize the Axis binary sensor.""" super().__init__(event, hub) - self.cancel_scheduled_update: Callable[[], None] | None = None - - self._attr_device_class = DEVICE_CLASS.get(event.group) + self.entity_description = description + self._attr_name = description.name_fn(hub, event) or self._attr_name self._attr_is_on = event.is_tripped - - self._set_name(event) + self._attr_device_class = description.device_class # temporary + self.cancel_scheduled_update: Callable[[], None] | None = None @callback def async_event_callback(self, event: Event) -> None: @@ -103,45 +251,3 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): timedelta(seconds=self.hub.config.trigger_time), scheduled_update, ) - - @callback - def _set_name(self, event: Event) -> None: - """Set binary sensor name.""" - if ( - event.group == EventGroup.INPUT - and event.id in self.hub.api.vapix.ports - and self.hub.api.vapix.ports[event.id].name - ): - self._attr_name = self.hub.api.vapix.ports[event.id].name - - elif event.group == EventGroup.MOTION: - event_data: FenceGuardHandler | LoiteringGuardHandler | MotionGuardHandler | Vmd4Handler | None = None - if event.topic_base == EventTopic.FENCE_GUARD: - event_data = self.hub.api.vapix.fence_guard - elif event.topic_base == EventTopic.LOITERING_GUARD: - event_data = self.hub.api.vapix.loitering_guard - elif event.topic_base == EventTopic.MOTION_GUARD: - event_data = self.hub.api.vapix.motion_guard - elif event.topic_base == EventTopic.MOTION_DETECTION_4: - event_data = self.hub.api.vapix.vmd4 - if ( - event_data - and event_data.initialized - and (profiles := event_data["0"].profiles) - ): - for profile_id, profile in profiles.items(): - camera_id = profile.camera - if event.id == f"Camera{camera_id}Profile{profile_id}": - self._attr_name = f"{self._event_type} {profile.name}" - return - - if ( - event.topic_base == EventTopic.OBJECT_ANALYTICS - and self.hub.api.vapix.object_analytics.initialized - and (scenarios := self.hub.api.vapix.object_analytics["0"].scenarios) - ): - for scenario_id, scenario in scenarios.items(): - device_id = scenario.devices[0]["id"] - if event.id == f"Device{device_id}Scenario{scenario_id}": - self._attr_name = f"{self._event_type} {scenario.name}" - break diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index 64e567889f1..ce387655889 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -52,6 +52,52 @@ async def test_unsupported_binary_sensors( @pytest.mark.parametrize( ("event", "entity"), [ + ( + { + "topic": "tns1:VideoSource/tnsaxis:DayNightVision", + "source_name": "VideoSourceConfigurationToken", + "source_idx": "1", + "data_type": "DayNight", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_daynight_1", + "state": STATE_ON, + "name": f"{NAME} DayNight 1", + "device_class": BinarySensorDeviceClass.LIGHT, + }, + ), + ( + { + "topic": "tns1:AudioSource/tnsaxis:TriggerLevel", + "source_name": "channel", + "source_idx": "1", + "data_type": "Sound", + "data_value": "0", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_sound_1", + "state": STATE_OFF, + "name": f"{NAME} Sound 1", + "device_class": BinarySensorDeviceClass.SOUND, + }, + ), + ( + { + "topic": "tns1:Device/tnsaxis:IO/Port", + "data_type": "state", + "data_value": "0", + "operation": "Initialized", + "source_name": "port", + "source_idx": "0", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_pir_sensor", + "state": STATE_OFF, + "name": f"{NAME} PIR sensor", + "device_class": BinarySensorDeviceClass.CONNECTIVITY, + }, + ), ( { "topic": "tns1:Device/tnsaxis:Sensor/PIR", @@ -147,3 +193,66 @@ async def test_binary_sensors( assert state.state == entity["state"] assert state.name == entity["name"] assert state.attributes["device_class"] == entity["device_class"] + + +@pytest.mark.parametrize( + ("event"), + [ + { + "topic": "tns1:Device/tnsaxis:IO/Port", + "data_type": "state", + "data_value": "0", + "operation": "Initialized", + "source_name": "port", + "source_idx": "-1", + }, + { + "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", + "data_type": "active", + "data_value": "1", + }, + { + "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1ScenarioANY", + "data_type": "active", + "data_value": "1", + }, + ], +) +async def test_unsupported_events( + hass: HomeAssistant, setup_config_entry, mock_rtsp_event, event +) -> None: + """Validate nothing breaks with unsupported events.""" + mock_rtsp_event(**event) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 0 + + +@pytest.mark.parametrize( + ("event", "entity_id"), + [ + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile9", + "data_type": "active", + "data_value": "1", + }, + "binary_sensor.name_vmd4_camera1profile9", + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario8", + "data_type": "active", + "data_value": "1", + }, + "binary_sensor.name_object_analytics_device1scenario8", + ), + ], +) +async def test_no_primary_name_for_event( + hass: HomeAssistant, setup_config_entry, mock_rtsp_event, event, entity_id +) -> None: + """Validate fallback method for getting name works.""" + mock_rtsp_event(**event) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1 + assert hass.states.get(entity_id) From 48553ece68ac70b90ff340212675b59af614f548 Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:10:03 +0100 Subject: [PATCH 1381/1691] Remember entered IP when error happend while setting up HomeWizard (#113815) --- homeassistant/components/homewizard/config_flow.py | 5 ++++- tests/components/homewizard/test_config_flow.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homewizard/config_flow.py b/homeassistant/components/homewizard/config_flow.py index 795edfaf629..70ef47a4f03 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -66,11 +66,14 @@ class HomeWizardConfigFlow(ConfigFlow, domain=DOMAIN): data=user_input, ) + user_input = user_input or {} return self.async_show_form( step_id="user", data_schema=Schema( { - Required(CONF_IP_ADDRESS): str, + Required( + CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS) + ): str, } ), errors=errors, diff --git a/tests/components/homewizard/test_config_flow.py b/tests/components/homewizard/test_config_flow.py index 5eef6978815..f0776877aec 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -290,6 +290,7 @@ async def test_error_flow( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": reason} + assert result["data_schema"]({}) == {CONF_IP_ADDRESS: "127.0.0.1"} # Recover from error mock_homewizardenergy.device.side_effect = None From 4fb11dc4f318ae65aa7bbdc3abb982592c8f2cda Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 22 Mar 2024 16:31:43 +0100 Subject: [PATCH 1382/1691] Remove hourly weather entity from Aemet (#110764) --- homeassistant/components/aemet/const.py | 5 -- homeassistant/components/aemet/weather.py | 42 ++------------ tests/components/aemet/test_weather.py | 69 +---------------------- 3 files changed, 7 insertions(+), 109 deletions(-) diff --git a/homeassistant/components/aemet/const.py b/homeassistant/components/aemet/const.py index 6734ea3d1f1..337b7e0790c 100644 --- a/homeassistant/components/aemet/const.py +++ b/homeassistant/components/aemet/const.py @@ -121,8 +121,3 @@ FORECAST_MAP = { AOD_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED, }, } - -WEATHER_FORECAST_MODES = { - AOD_FORECAST_DAILY: "daily", - AOD_FORECAST_HOURLY: "hourly", -} diff --git a/homeassistant/components/aemet/weather.py b/homeassistant/components/aemet/weather.py index d49b62c9509..0d5abdcf967 100644 --- a/homeassistant/components/aemet/weather.py +++ b/homeassistant/components/aemet/weather.py @@ -14,7 +14,6 @@ from aemet_opendata.const import ( ) from homeassistant.components.weather import ( - DOMAIN as WEATHER_DOMAIN, Forecast, SingleCoordinatorWeatherEntity, WeatherEntityFeature, @@ -27,7 +26,6 @@ from homeassistant.const import ( UnitOfTemperature, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( @@ -36,7 +34,6 @@ from .const import ( DOMAIN, ENTRY_NAME, ENTRY_WEATHER_COORDINATOR, - WEATHER_FORECAST_MODES, ) from .coordinator import WeatherUpdateCoordinator from .entity import AemetEntity @@ -51,31 +48,14 @@ async def async_setup_entry( domain_data = hass.data[DOMAIN][config_entry.entry_id] weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR] - entities = [] - entity_registry = er.async_get(hass) - - # Add daily + hourly entity for legacy config entries, only add daily for new - # config entries. This can be removed in HA Core 2024.3 - if entity_registry.async_get_entity_id( - WEATHER_DOMAIN, - DOMAIN, - f"{config_entry.unique_id} {WEATHER_FORECAST_MODES[AOD_FORECAST_HOURLY]}", - ): - for mode, mode_id in WEATHER_FORECAST_MODES.items(): - name = f"{domain_data[ENTRY_NAME]} {mode_id}" - unique_id = f"{config_entry.unique_id} {mode_id}" - entities.append(AemetWeather(name, unique_id, weather_coordinator, mode)) - else: - entities.append( + async_add_entities( + [ AemetWeather( - domain_data[ENTRY_NAME], - config_entry.unique_id, - weather_coordinator, - AOD_FORECAST_DAILY, + domain_data[ENTRY_NAME], config_entry.unique_id, weather_coordinator ) - ) - - async_add_entities(entities, False) + ], + False, + ) class AemetWeather( @@ -98,14 +78,9 @@ class AemetWeather( name, unique_id, coordinator: WeatherUpdateCoordinator, - forecast_mode, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._forecast_mode = forecast_mode - self._attr_entity_registry_enabled_default = ( - self._forecast_mode == AOD_FORECAST_DAILY - ) self._attr_name = name self._attr_unique_id = unique_id @@ -115,11 +90,6 @@ class AemetWeather( cond = self.get_aemet_value([AOD_WEATHER, AOD_CONDITION]) return CONDITIONS_MAP.get(cond) - @property - def forecast(self) -> list[Forecast]: - """Return the forecast array.""" - return self.get_aemet_forecast(self._forecast_mode) - @callback def _async_forecast_daily(self) -> list[Forecast]: """Return the daily forecast in native units.""" diff --git a/tests/components/aemet/test_weather.py b/tests/components/aemet/test_weather.py index 88427a5a38f..ec2c088fe6d 100644 --- a/tests/components/aemet/test_weather.py +++ b/tests/components/aemet/test_weather.py @@ -7,19 +7,10 @@ from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.components.aemet.const import ATTRIBUTION, DOMAIN +from homeassistant.components.aemet.const import ATTRIBUTION from homeassistant.components.aemet.coordinator import WEATHER_UPDATE_INTERVAL from homeassistant.components.weather import ( ATTR_CONDITION_SNOWY, - ATTR_FORECAST, - ATTR_FORECAST_CONDITION, - ATTR_FORECAST_PRECIPITATION, - ATTR_FORECAST_PRECIPITATION_PROBABILITY, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, - ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, @@ -32,8 +23,6 @@ from homeassistant.components.weather import ( ) from homeassistant.const import ATTR_ATTRIBUTION from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er -import homeassistant.util.dt as dt_util from .util import async_init_integration, mock_api_call @@ -60,62 +49,6 @@ async def test_aemet_weather( assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 122.0 assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 12.2 assert state.attributes[ATTR_WEATHER_WIND_SPEED] == 3.2 - forecast = state.attributes[ATTR_FORECAST][0] - assert forecast[ATTR_FORECAST_CONDITION] == ATTR_CONDITION_SNOWY - assert ATTR_FORECAST_PRECIPITATION not in forecast - assert forecast[ATTR_FORECAST_PRECIPITATION_PROBABILITY] == 0 - assert forecast[ATTR_FORECAST_TEMP] == 2 - assert forecast[ATTR_FORECAST_TEMP_LOW] == -1 - assert ( - forecast[ATTR_FORECAST_TIME] - == dt_util.parse_datetime("2021-01-08 23:00:00+00:00").isoformat() - ) - assert forecast[ATTR_FORECAST_WIND_BEARING] == 90.0 - assert forecast[ATTR_FORECAST_WIND_SPEED] == 0.0 - - state = hass.states.get("weather.aemet_hourly") - assert state is None - - -async def test_aemet_weather_legacy( - hass: HomeAssistant, - freezer: FrozenDateTimeFactory, - entity_registry: er.EntityRegistry, -) -> None: - """Test states of legacy weather.""" - - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "None hourly", - ) - - hass.config.set_time_zone("UTC") - freezer.move_to("2021-01-09 12:00:00+00:00") - await async_init_integration(hass) - - state = hass.states.get("weather.aemet_daily") - assert state - assert state.state == ATTR_CONDITION_SNOWY - assert state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION - assert state.attributes[ATTR_WEATHER_HUMIDITY] == 99.0 - assert state.attributes[ATTR_WEATHER_PRESSURE] == 1004.4 # 100440.0 Pa -> hPa - assert state.attributes[ATTR_WEATHER_TEMPERATURE] == -0.7 - assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 122.0 - assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 12.2 - assert state.attributes[ATTR_WEATHER_WIND_SPEED] == 3.2 - forecast = state.attributes[ATTR_FORECAST][0] - assert forecast[ATTR_FORECAST_CONDITION] == ATTR_CONDITION_SNOWY - assert ATTR_FORECAST_PRECIPITATION not in forecast - assert forecast[ATTR_FORECAST_PRECIPITATION_PROBABILITY] == 0 - assert forecast[ATTR_FORECAST_TEMP] == 2 - assert forecast[ATTR_FORECAST_TEMP_LOW] == -1 - assert ( - forecast[ATTR_FORECAST_TIME] - == dt_util.parse_datetime("2021-01-08 23:00:00+00:00").isoformat() - ) - assert forecast[ATTR_FORECAST_WIND_BEARING] == 90.0 - assert forecast[ATTR_FORECAST_WIND_SPEED] == 0.0 state = hass.states.get("weather.aemet_hourly") assert state is None From 7df0d3b1405707bb03d27028e428b79b1deb4313 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 22 Mar 2024 16:32:29 +0100 Subject: [PATCH 1383/1691] Remove hourly weather entity from NWS (#112503) --- homeassistant/components/nws/weather.py | 30 +- .../nws/snapshots/test_weather.ambr | 319 +----------------- tests/components/nws/test_weather.py | 125 +------ 3 files changed, 34 insertions(+), 440 deletions(-) diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 025a3640ec6..0c76c31887c 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -85,17 +85,15 @@ async def async_setup_entry( entity_registry = er.async_get(hass) nws_data: NWSData = hass.data[DOMAIN][entry.entry_id] - entities = [NWSWeather(entry.data, nws_data, DAYNIGHT)] - - # Add hourly entity to legacy config entries - if entity_registry.async_get_entity_id( + # Remove hourly entity from legacy config entries + if entity_id := entity_registry.async_get_entity_id( WEATHER_DOMAIN, DOMAIN, _calculate_unique_id(entry.data, HOURLY), ): - entities.append(NWSWeather(entry.data, nws_data, HOURLY)) + entity_registry.async_remove(entity_id) - async_add_entities(entities, False) + async_add_entities([NWSWeather(entry.data, nws_data)], False) if TYPE_CHECKING: @@ -130,7 +128,6 @@ class NWSWeather(CoordinatorWeatherEntity): self, entry_data: MappingProxyType[str, Any], nws_data: NWSData, - mode: str, ) -> None: """Initialise the platform with a data instance and station name.""" super().__init__( @@ -143,23 +140,17 @@ class NWSWeather(CoordinatorWeatherEntity): self.nws = nws_data.api latitude = entry_data[CONF_LATITUDE] longitude = entry_data[CONF_LONGITUDE] - if mode == DAYNIGHT: - self.coordinator_forecast_legacy = nws_data.coordinator_forecast - else: - self.coordinator_forecast_legacy = nws_data.coordinator_forecast_hourly + self.coordinator_forecast_legacy = nws_data.coordinator_forecast self.station = self.nws.station - self.mode = mode - self._attr_entity_registry_enabled_default = mode == DAYNIGHT - self.observation: dict[str, Any] | None = None self._forecast_hourly: list[dict[str, Any]] | None = None self._forecast_legacy: list[dict[str, Any]] | None = None self._forecast_twice_daily: list[dict[str, Any]] | None = None - self._attr_unique_id = _calculate_unique_id(entry_data, mode) + self._attr_unique_id = _calculate_unique_id(entry_data, DAYNIGHT) self._attr_device_info = device_info(latitude, longitude) - self._attr_name = f"{self.station} {self.mode.title()}" + self._attr_name = self.station async def async_added_to_hass(self) -> None: """Set up a listener and load data.""" @@ -194,10 +185,7 @@ class NWSWeather(CoordinatorWeatherEntity): @callback def _handle_legacy_forecast_coordinator_update(self) -> None: """Handle updated data from the legacy forecast coordinator.""" - if self.mode == DAYNIGHT: - self._forecast_legacy = self.nws.forecast - else: - self._forecast_legacy = self.nws.forecast_hourly + self._forecast_legacy = self.nws.forecast self.async_write_ha_state() @property @@ -314,7 +302,7 @@ class NWSWeather(CoordinatorWeatherEntity): @property def forecast(self) -> list[Forecast] | None: """Return forecast.""" - return self._forecast(self._forecast_legacy, self.mode) + return self._forecast(self._forecast_legacy, DAYNIGHT) @callback def _async_forecast_hourly(self) -> list[Forecast] | None: diff --git a/tests/components/nws/snapshots/test_weather.ambr b/tests/components/nws/snapshots/test_weather.ambr index 0db2311085c..f4669f47615 100644 --- a/tests/components/nws/snapshots/test_weather.ambr +++ b/tests/components/nws/snapshots/test_weather.ambr @@ -1,213 +1,4 @@ # serializer version: 1 -# name: test_forecast_service - dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }) -# --- -# name: test_forecast_service.1 - dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }) -# --- -# name: test_forecast_service.2 - dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }) -# --- -# name: test_forecast_service.3 - dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }) -# --- -# name: test_forecast_service.4 - dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }) -# --- -# name: test_forecast_service.5 - dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }) -# --- -# name: test_forecast_service[forecast] - dict({ - 'weather.abc_daynight': dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].1 - dict({ - 'weather.abc_daynight': dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].2 - dict({ - 'weather.abc_daynight': dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].3 - dict({ - 'weather.abc_daynight': dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].4 - dict({ - 'weather.abc_daynight': dict({ - 'forecast': list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].5 - dict({ - 'weather.abc_daynight': dict({ - 'forecast': list([ - ]), - }), - }) -# --- # name: test_forecast_service[get_forecast] dict({ 'forecast': list([ @@ -303,7 +94,7 @@ # --- # name: test_forecast_service[get_forecasts] dict({ - 'weather.abc_daynight': dict({ + 'weather.abc': dict({ 'forecast': list([ dict({ 'condition': 'lightning-rainy', @@ -323,7 +114,7 @@ # --- # name: test_forecast_service[get_forecasts].1 dict({ - 'weather.abc_daynight': dict({ + 'weather.abc': dict({ 'forecast': list([ dict({ 'condition': 'lightning-rainy', @@ -342,7 +133,7 @@ # --- # name: test_forecast_service[get_forecasts].2 dict({ - 'weather.abc_daynight': dict({ + 'weather.abc': dict({ 'forecast': list([ dict({ 'condition': 'lightning-rainy', @@ -362,7 +153,7 @@ # --- # name: test_forecast_service[get_forecasts].3 dict({ - 'weather.abc_daynight': dict({ + 'weather.abc': dict({ 'forecast': list([ dict({ 'condition': 'lightning-rainy', @@ -381,7 +172,7 @@ # --- # name: test_forecast_service[get_forecasts].4 dict({ - 'weather.abc_daynight': dict({ + 'weather.abc': dict({ 'forecast': list([ dict({ 'condition': 'lightning-rainy', @@ -400,13 +191,13 @@ # --- # name: test_forecast_service[get_forecasts].5 dict({ - 'weather.abc_daynight': dict({ + 'weather.abc': dict({ 'forecast': list([ ]), }), }) # --- -# name: test_forecast_subscription[hourly-weather.abc_daynight] +# name: test_forecast_subscription[hourly-weather.abc] list([ dict({ 'condition': 'lightning-rainy', @@ -421,7 +212,7 @@ }), ]) # --- -# name: test_forecast_subscription[hourly-weather.abc_daynight].1 +# name: test_forecast_subscription[hourly-weather.abc].1 list([ dict({ 'condition': 'lightning-rainy', @@ -436,97 +227,3 @@ }), ]) # --- -# name: test_forecast_subscription[hourly] - list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]) -# --- -# name: test_forecast_subscription[hourly].1 - list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]) -# --- -# name: test_forecast_subscription[twice_daily-weather.abc_hourly] - list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]) -# --- -# name: test_forecast_subscription[twice_daily-weather.abc_hourly].1 - list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]) -# --- -# name: test_forecast_subscription[twice_daily] - list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]) -# --- -# name: test_forecast_subscription[twice_daily].1 - list([ - dict({ - 'condition': 'lightning-rainy', - 'datetime': '2019-08-12T20:00:00-04:00', - 'detailed_description': 'A detailed forecast.', - 'dew_point': -15.6, - 'humidity': 75, - 'is_daytime': False, - 'precipitation_probability': 89, - 'temperature': -12.2, - 'wind_bearing': 180, - 'wind_speed': 16.09, - }), - ]) -# --- diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index b2c67cb58f9..da365c52ffc 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -59,16 +59,6 @@ async def test_imperial_metric( no_sensor, ) -> None: """Test with imperial and metric units.""" - # enable the hourly entity - registry = er.async_get(hass) - registry.async_get_or_create( - WEATHER_DOMAIN, - nws.DOMAIN, - "35_-75_hourly", - suggested_object_id="abc_hourly", - disabled_by=None, - ) - hass.config.units = units entry = MockConfigEntry( domain=nws.DOMAIN, @@ -78,20 +68,7 @@ async def test_imperial_metric( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("weather.abc_hourly") - - assert state - assert state.state == ATTR_CONDITION_SUNNY - - data = state.attributes - for key, value in result_observation.items(): - assert data.get(key) == value - - forecast = data.get(ATTR_FORECAST) - for key, value in result_forecast.items(): - assert forecast[0].get(key) == value - - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == ATTR_CONDITION_SUNNY @@ -118,7 +95,7 @@ async def test_night_clear(hass: HomeAssistant, mock_simple_nws, no_sensor) -> N await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state.state == ATTR_CONDITION_CLEAR_NIGHT @@ -136,7 +113,7 @@ async def test_none_values(hass: HomeAssistant, mock_simple_nws, no_sensor) -> N await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state.state == STATE_UNKNOWN data = state.attributes for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL: @@ -161,7 +138,7 @@ async def test_none(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == STATE_UNKNOWN @@ -187,8 +164,7 @@ async def test_error_station(hass: HomeAssistant, mock_simple_nws, no_sensor) -> await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert hass.states.get("weather.abc_hourly") is None - assert hass.states.get("weather.abc_daynight") is None + assert hass.states.get("weather.abc") is None async def test_entity_refresh(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: @@ -211,7 +187,7 @@ async def test_entity_refresh(hass: HomeAssistant, mock_simple_nws, no_sensor) - await hass.services.async_call( "homeassistant", "update_entity", - {"entity_id": "weather.abc_daynight"}, + {"entity_id": "weather.abc"}, blocking=True, ) await hass.async_block_till_done() @@ -250,7 +226,7 @@ async def test_error_observation( instance.update_observation.assert_called_once() - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == STATE_UNAVAILABLE @@ -261,7 +237,7 @@ async def test_error_observation( assert instance.update_observation.call_count == 2 - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == ATTR_CONDITION_SUNNY @@ -273,7 +249,7 @@ async def test_error_observation( assert instance.update_observation.call_count == 3 - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == ATTR_CONDITION_SUNNY @@ -281,7 +257,7 @@ async def test_error_observation( increment_time(timedelta(minutes=10)) await hass.async_block_till_done() - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == STATE_UNAVAILABLE @@ -301,7 +277,7 @@ async def test_error_forecast(hass: HomeAssistant, mock_simple_nws, no_sensor) - instance.update_forecast.assert_called_once() - state = hass.states.get("weather.abc_daynight") + state = hass.states.get("weather.abc") assert state assert state.state == STATE_UNAVAILABLE @@ -312,50 +288,7 @@ async def test_error_forecast(hass: HomeAssistant, mock_simple_nws, no_sensor) - assert instance.update_forecast.call_count == 2 - state = hass.states.get("weather.abc_daynight") - assert state - assert state.state == ATTR_CONDITION_SUNNY - - -async def test_error_forecast_hourly( - hass: HomeAssistant, mock_simple_nws, no_sensor -) -> None: - """Test error during update forecast hourly.""" - instance = mock_simple_nws.return_value - instance.update_forecast_hourly.side_effect = aiohttp.ClientError - - # enable the hourly entity - registry = er.async_get(hass) - registry.async_get_or_create( - WEATHER_DOMAIN, - nws.DOMAIN, - "35_-75_hourly", - suggested_object_id="abc_hourly", - disabled_by=None, - ) - - entry = MockConfigEntry( - domain=nws.DOMAIN, - data=NWS_CONFIG, - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get("weather.abc_hourly") - assert state - assert state.state == STATE_UNAVAILABLE - - instance.update_forecast_hourly.assert_called_once() - - instance.update_forecast_hourly.side_effect = None - - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1)) - await hass.async_block_till_done() - - assert instance.update_forecast_hourly.call_count == 2 - - state = hass.states.get("weather.abc_hourly") + state = hass.states.get("weather.abc") assert state assert state.state == ATTR_CONDITION_SUNNY @@ -378,30 +311,6 @@ async def test_new_config_entry(hass: HomeAssistant, no_sensor) -> None: assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 1 -async def test_legacy_config_entry(hass: HomeAssistant, no_sensor) -> None: - """Test the expected entities are created.""" - registry = er.async_get(hass) - # Pre-create the hourly entity - registry.async_get_or_create( - WEATHER_DOMAIN, - nws.DOMAIN, - "35_-75_hourly", - ) - - entry = MockConfigEntry( - domain=nws.DOMAIN, - data=NWS_CONFIG, - ) - entry.add_to_hass(hass) - - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - assert len(hass.states.async_entity_ids("weather")) == 2 - entry = hass.config_entries.async_entries()[0] - assert len(er.async_entries_for_config_entry(registry, entry.entry_id)) == 2 - - @pytest.mark.parametrize( ("service"), [ @@ -437,7 +346,7 @@ async def test_forecast_service( WEATHER_DOMAIN, service, { - "entity_id": "weather.abc_daynight", + "entity_id": "weather.abc", "type": forecast_type, }, blocking=True, @@ -464,7 +373,7 @@ async def test_forecast_service( WEATHER_DOMAIN, service, { - "entity_id": "weather.abc_daynight", + "entity_id": "weather.abc", "type": forecast_type, }, blocking=True, @@ -487,7 +396,7 @@ async def test_forecast_service( WEATHER_DOMAIN, service, { - "entity_id": "weather.abc_daynight", + "entity_id": "weather.abc", "type": "hourly", }, blocking=True, @@ -504,7 +413,7 @@ async def test_forecast_service( WEATHER_DOMAIN, service, { - "entity_id": "weather.abc_daynight", + "entity_id": "weather.abc", "type": "hourly", }, blocking=True, @@ -515,7 +424,7 @@ async def test_forecast_service( @pytest.mark.parametrize( ("forecast_type", "entity_id"), - [("hourly", "weather.abc_daynight"), ("twice_daily", "weather.abc_hourly")], + [("hourly", "weather.abc")], ) async def test_forecast_subscription( hass: HomeAssistant, From cad3be8213cd57242f7783fa5aed57554b584bf9 Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:48:27 +0100 Subject: [PATCH 1384/1691] Add new HomeWizard Energy Socket to tests (#113406) --- .../{HWE-SKT => HWE-SKT-11}/data.json | 0 .../{HWE-SKT => HWE-SKT-11}/device.json | 0 .../{HWE-SKT => HWE-SKT-11}/state.json | 0 .../{HWE-SKT => HWE-SKT-11}/system.json | 0 .../homewizard/fixtures/HWE-SKT-21/data.json | 16 + .../fixtures/HWE-SKT-21/device.json | 7 + .../homewizard/fixtures/HWE-SKT-21/state.json | 5 + .../fixtures/HWE-SKT-21/system.json | 3 + .../snapshots/test_diagnostics.ambr | 89 +- .../homewizard/snapshots/test_number.ambr | 96 +- .../homewizard/snapshots/test_sensor.ambr | 12466 +++------------- .../homewizard/snapshots/test_switch.ambr | 259 +- .../components/homewizard/test_diagnostics.py | 3 +- tests/components/homewizard/test_init.py | 28 +- tests/components/homewizard/test_number.py | 2 +- tests/components/homewizard/test_sensor.py | 75 +- tests/components/homewizard/test_switch.py | 11 +- 17 files changed, 2702 insertions(+), 10358 deletions(-) rename tests/components/homewizard/fixtures/{HWE-SKT => HWE-SKT-11}/data.json (100%) rename tests/components/homewizard/fixtures/{HWE-SKT => HWE-SKT-11}/device.json (100%) rename tests/components/homewizard/fixtures/{HWE-SKT => HWE-SKT-11}/state.json (100%) rename tests/components/homewizard/fixtures/{HWE-SKT => HWE-SKT-11}/system.json (100%) create mode 100644 tests/components/homewizard/fixtures/HWE-SKT-21/data.json create mode 100644 tests/components/homewizard/fixtures/HWE-SKT-21/device.json create mode 100644 tests/components/homewizard/fixtures/HWE-SKT-21/state.json create mode 100644 tests/components/homewizard/fixtures/HWE-SKT-21/system.json diff --git a/tests/components/homewizard/fixtures/HWE-SKT/data.json b/tests/components/homewizard/fixtures/HWE-SKT-11/data.json similarity index 100% rename from tests/components/homewizard/fixtures/HWE-SKT/data.json rename to tests/components/homewizard/fixtures/HWE-SKT-11/data.json diff --git a/tests/components/homewizard/fixtures/HWE-SKT/device.json b/tests/components/homewizard/fixtures/HWE-SKT-11/device.json similarity index 100% rename from tests/components/homewizard/fixtures/HWE-SKT/device.json rename to tests/components/homewizard/fixtures/HWE-SKT-11/device.json diff --git a/tests/components/homewizard/fixtures/HWE-SKT/state.json b/tests/components/homewizard/fixtures/HWE-SKT-11/state.json similarity index 100% rename from tests/components/homewizard/fixtures/HWE-SKT/state.json rename to tests/components/homewizard/fixtures/HWE-SKT-11/state.json diff --git a/tests/components/homewizard/fixtures/HWE-SKT/system.json b/tests/components/homewizard/fixtures/HWE-SKT-11/system.json similarity index 100% rename from tests/components/homewizard/fixtures/HWE-SKT/system.json rename to tests/components/homewizard/fixtures/HWE-SKT-11/system.json diff --git a/tests/components/homewizard/fixtures/HWE-SKT-21/data.json b/tests/components/homewizard/fixtures/HWE-SKT-21/data.json new file mode 100644 index 00000000000..5b68ae0090a --- /dev/null +++ b/tests/components/homewizard/fixtures/HWE-SKT-21/data.json @@ -0,0 +1,16 @@ +{ + "wifi_ssid": "My Wi-Fi", + "wifi_strength": 100, + "total_power_import_kwh": 30.511, + "total_power_import_t1_kwh": 30.511, + "total_power_export_kwh": 85.951, + "total_power_export_t1_kwh": 85.951, + "active_power_w": 543.312, + "active_power_l1_w": 543.312, + "active_voltage_v": 231.539, + "active_current_a": 2.346, + "active_reactive_power_var": 123.456, + "active_apparent_power_va": 666.768, + "active_power_factor": 0.81688, + "active_frequency_hz": 50.005 +} diff --git a/tests/components/homewizard/fixtures/HWE-SKT-21/device.json b/tests/components/homewizard/fixtures/HWE-SKT-21/device.json new file mode 100644 index 00000000000..69b5947351f --- /dev/null +++ b/tests/components/homewizard/fixtures/HWE-SKT-21/device.json @@ -0,0 +1,7 @@ +{ + "product_type": "HWE-SKT", + "product_name": "Energy Socket", + "serial": "3c39e7aabbcc", + "firmware_version": "4.07", + "api_version": "v1" +} diff --git a/tests/components/homewizard/fixtures/HWE-SKT-21/state.json b/tests/components/homewizard/fixtures/HWE-SKT-21/state.json new file mode 100644 index 00000000000..bbc0242ed58 --- /dev/null +++ b/tests/components/homewizard/fixtures/HWE-SKT-21/state.json @@ -0,0 +1,5 @@ +{ + "power_on": true, + "switch_lock": false, + "brightness": 255 +} diff --git a/tests/components/homewizard/fixtures/HWE-SKT-21/system.json b/tests/components/homewizard/fixtures/HWE-SKT-21/system.json new file mode 100644 index 00000000000..362491b3519 --- /dev/null +++ b/tests/components/homewizard/fixtures/HWE-SKT-21/system.json @@ -0,0 +1,3 @@ +{ + "cloud_enabled": true +} diff --git a/tests/components/homewizard/snapshots/test_diagnostics.ambr b/tests/components/homewizard/snapshots/test_diagnostics.ambr index 9e3a468d58f..f42c2abfc95 100644 --- a/tests/components/homewizard/snapshots/test_diagnostics.ambr +++ b/tests/components/homewizard/snapshots/test_diagnostics.ambr @@ -299,7 +299,7 @@ }), }) # --- -# name: test_diagnostics[HWE-SKT] +# name: test_diagnostics[HWE-SKT-11] dict({ 'data': dict({ 'data': dict({ @@ -386,6 +386,93 @@ }), }) # --- +# name: test_diagnostics[HWE-SKT-21] + dict({ + 'data': dict({ + 'data': dict({ + 'active_apparent_power_l1_va': None, + 'active_apparent_power_l2_va': None, + 'active_apparent_power_l3_va': None, + 'active_apparent_power_va': 666.768, + 'active_current_a': 2.346, + 'active_current_l1_a': None, + 'active_current_l2_a': None, + 'active_current_l3_a': None, + 'active_frequency_hz': 50.005, + 'active_liter_lpm': None, + 'active_power_average_w': None, + 'active_power_factor': 0.81688, + 'active_power_factor_l1': None, + 'active_power_factor_l2': None, + 'active_power_factor_l3': None, + 'active_power_l1_w': 543.312, + 'active_power_l2_w': None, + 'active_power_l3_w': None, + 'active_power_w': 543.312, + 'active_reactive_power_l1_var': None, + 'active_reactive_power_l2_var': None, + 'active_reactive_power_l3_var': None, + 'active_reactive_power_var': 123.456, + 'active_tariff': None, + 'active_voltage_l1_v': None, + 'active_voltage_l2_v': None, + 'active_voltage_l3_v': None, + 'active_voltage_v': 231.539, + 'any_power_fail_count': None, + 'external_devices': None, + 'gas_timestamp': None, + 'gas_unique_id': None, + 'long_power_fail_count': None, + 'meter_model': None, + 'monthly_power_peak_timestamp': None, + 'monthly_power_peak_w': None, + 'smr_version': None, + 'total_energy_export_kwh': 85.951, + 'total_energy_export_t1_kwh': 85.951, + 'total_energy_export_t2_kwh': None, + 'total_energy_export_t3_kwh': None, + 'total_energy_export_t4_kwh': None, + 'total_energy_import_kwh': 30.511, + 'total_energy_import_t1_kwh': 30.511, + 'total_energy_import_t2_kwh': None, + 'total_energy_import_t3_kwh': None, + 'total_energy_import_t4_kwh': None, + 'total_gas_m3': None, + 'total_liter_m3': None, + 'unique_meter_id': None, + 'voltage_sag_l1_count': None, + 'voltage_sag_l2_count': None, + 'voltage_sag_l3_count': None, + 'voltage_swell_l1_count': None, + 'voltage_swell_l2_count': None, + 'voltage_swell_l3_count': None, + 'wifi_ssid': '**REDACTED**', + 'wifi_strength': 100, + }), + 'device': dict({ + 'api_version': 'v1', + 'firmware_version': '4.07', + 'product_name': 'Energy Socket', + 'product_type': 'HWE-SKT', + 'serial': '**REDACTED**', + }), + 'state': dict({ + 'brightness': 255, + 'power_on': True, + 'switch_lock': False, + }), + 'system': dict({ + 'cloud_enabled': True, + }), + }), + 'entry': dict({ + 'ip_address': '**REDACTED**', + 'product_name': 'Product name', + 'product_type': 'product_type', + 'serial': '**REDACTED**', + }), + }) +# --- # name: test_diagnostics[HWE-WTR] dict({ 'data': dict({ diff --git a/tests/components/homewizard/snapshots/test_number.ambr b/tests/components/homewizard/snapshots/test_number.ambr index 1e0119cf0b9..a9c9e45098d 100644 --- a/tests/components/homewizard/snapshots/test_number.ambr +++ b/tests/components/homewizard/snapshots/test_number.ambr @@ -1,5 +1,5 @@ # serializer version: 1 -# name: test_number_entities[HWE-SKT] +# name: test_number_entities[HWE-SKT-11] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Status light brightness', @@ -17,7 +17,7 @@ 'state': '100.0', }) # --- -# name: test_number_entities[HWE-SKT].1 +# name: test_number_entities[HWE-SKT-11].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -55,7 +55,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_number_entities[HWE-SKT].2 +# name: test_number_entities[HWE-SKT-11].2 DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -89,3 +89,93 @@ 'via_device_id': None, }) # --- +# name: test_number_entities[HWE-SKT-21] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Status light brightness', + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'number.device_status_light_brightness', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100.0', + }) +# --- +# name: test_number_entities[HWE-SKT-21].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.device_status_light_brightness', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Status light brightness', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'status_light_brightness', + 'unique_id': 'aabbccddeeff_status_light_brightness', + 'unit_of_measurement': '%', + }) +# --- +# name: test_number_entities[HWE-SKT-21].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-SKT', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '4.07', + 'via_device_id': None, + }) +# --- diff --git a/tests/components/homewizard/snapshots/test_sensor.ambr b/tests/components/homewizard/snapshots/test_sensor.ambr index c9f2000b4c9..0503085b7e6 100644 --- a/tests/components/homewizard/snapshots/test_sensor.ambr +++ b/tests/components/homewizard/snapshots/test_sensor.ambr @@ -1,190 +1,4 @@ # serializer version: 1 -# name: test_gas_meter_migrated[HWE-P1-unique_ids0][sensor.homewizard_aabbccddeeff_gas_unique_id:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.homewizard_aabbccddeeff_gas_unique_id', - 'has_entity_name': False, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': 'aabbccddeeff_gas_unique_id', - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'homewizard_01FFEEDDCCBBAA99887766554433221100_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_gas_meter_migrated[HWE-P1-unique_ids0][sensor.homewizard_aabbccddeeff_total_gas_m3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.homewizard_aabbccddeeff_total_gas_m3', - 'has_entity_name': False, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': 'aabbccddeeff_total_gas_m3', - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'homewizard_01FFEEDDCCBBAA99887766554433221100', - 'unit_of_measurement': None, - }) -# --- -# name: test_gas_meter_migrated[aabbccddeeff_gas_unique_id][sensor.homewizard_a:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.homewizard_a', - 'has_entity_name': False, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'a', - 'unit_of_measurement': None, - }) -# --- -# name: test_gas_meter_migrated[aabbccddeeff_gas_unique_id][sensor.homewizard_aabbccddeeff_gas_unique_id:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.homewizard_aabbccddeeff_gas_unique_id', - 'has_entity_name': False, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': 'aabbccddeeff_gas_unique_id', - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'homewizard_01FFEEDDCCBBAA99887766554433221100_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_gas_meter_migrated[aabbccddeeff_total_gas_m3][sensor.homewizard_a:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.homewizard_a', - 'has_entity_name': False, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'a', - 'unit_of_measurement': None, - }) -# --- -# name: test_gas_meter_migrated[aabbccddeeff_total_gas_m3][sensor.homewizard_aabbccddeeff_total_gas_m3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.homewizard_aabbccddeeff_total_gas_m3', - 'has_entity_name': False, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': 'aabbccddeeff_total_gas_m3', - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'homewizard_01FFEEDDCCBBAA99887766554433221100', - 'unit_of_measurement': None, - }) -# --- # name: test_gas_meter_migrated[sensor.homewizard_aabbccddeeff_total_gas_m3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -218,7 +32,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_apparent_power:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_apparent_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -252,7 +66,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_apparent_power:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_apparent_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -287,7 +101,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_apparent_power:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_apparent_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -303,7 +117,7 @@ 'state': '74.052', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_current:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_current:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -337,7 +151,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_current:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_current:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -372,7 +186,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_current:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_current:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -388,7 +202,7 @@ 'state': '0.273', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_energy_export:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_energy_export:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -422,7 +236,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_energy_export:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_energy_export:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -457,7 +271,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_energy_export:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_energy_export:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -473,7 +287,7 @@ 'state': '255.551', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_energy_import:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_energy_import:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -507,7 +321,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_energy_import:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_energy_import:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -542,7 +356,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_energy_import:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_energy_import:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -558,7 +372,7 @@ 'state': '2.705', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_frequency:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_frequency:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -592,7 +406,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_frequency:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_frequency:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -627,7 +441,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_frequency:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_frequency:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'frequency', @@ -643,7 +457,7 @@ 'state': '50', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_power:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -677,7 +491,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_power:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -715,7 +529,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_power:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -731,7 +545,7 @@ 'state': '-1058.296', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_power_factor:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_power_factor:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -765,7 +579,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_power_factor:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_power_factor:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -800,7 +614,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_power_factor:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_power_factor:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -816,7 +630,7 @@ 'state': '61.1', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_reactive_power:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_reactive_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -850,7 +664,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_reactive_power:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_reactive_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -885,7 +699,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_reactive_power:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_reactive_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -901,7 +715,7 @@ 'state': '-58.612', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_voltage:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_voltage:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -935,7 +749,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_voltage:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_voltage:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -970,7 +784,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_voltage:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_voltage:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -986,7 +800,7 @@ 'state': '228.472', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_wi_fi_ssid:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_wi_fi_ssid:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1020,7 +834,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_wi_fi_ssid:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_wi_fi_ssid:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1053,7 +867,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_wi_fi_ssid:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_wi_fi_ssid:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi SSID', @@ -1066,7 +880,7 @@ 'state': 'My Wi-Fi', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_wi_fi_strength:device-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_wi_fi_strength:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1100,7 +914,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_wi_fi_strength:entity-registry] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_wi_fi_strength:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1135,7 +949,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-KWH1-entity_ids6][sensor.device_wi_fi_strength:state] +# name: test_sensors[HWE-KWH1-entity_ids7][sensor.device_wi_fi_strength:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi strength', @@ -1150,7 +964,7 @@ 'state': '92', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1184,7 +998,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1219,7 +1033,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -1235,7 +1049,7 @@ 'state': '7112.293', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_1:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1269,7 +1083,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_1:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1304,7 +1118,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_1:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -1320,7 +1134,7 @@ 'state': '0', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_2:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1354,7 +1168,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_2:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1389,7 +1203,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_2:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -1405,7 +1219,7 @@ 'state': '3548.879', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_3:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1439,7 +1253,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_3:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1474,7 +1288,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_apparent_power_phase_3:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_apparent_power_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -1490,7 +1304,7 @@ 'state': '3563.414', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1524,7 +1338,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1559,7 +1373,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -1575,7 +1389,7 @@ 'state': '30.999', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_1:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1609,7 +1423,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_1:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1644,7 +1458,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_1:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -1660,7 +1474,7 @@ 'state': '0', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_2:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1694,7 +1508,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_2:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1729,7 +1543,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_2:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -1745,7 +1559,7 @@ 'state': '15.521', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_3:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1779,7 +1593,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_3:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1814,7 +1628,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_current_phase_3:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_current_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -1830,7 +1644,7 @@ 'state': '15.477', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_energy_export:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_energy_export:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1864,7 +1678,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_energy_export:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_energy_export:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1899,7 +1713,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_energy_export:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_energy_export:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -1915,7 +1729,7 @@ 'state': '0.523', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_energy_import:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_energy_import:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -1949,7 +1763,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_energy_import:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_energy_import:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -1984,7 +1798,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_energy_import:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_energy_import:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -2000,7 +1814,7 @@ 'state': '0.101', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_frequency:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_frequency:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2034,7 +1848,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_frequency:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_frequency:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2069,7 +1883,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_frequency:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_frequency:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'frequency', @@ -2085,7 +1899,7 @@ 'state': '49.926', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2119,7 +1933,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2157,7 +1971,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -2173,7 +1987,7 @@ 'state': '-900.194', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_1:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2207,7 +2021,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_1:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2242,7 +2056,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_1:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -2258,7 +2072,7 @@ 'state': '100', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_2:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2292,7 +2106,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_2:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2327,7 +2141,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_2:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -2343,7 +2157,7 @@ 'state': '99.9', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_3:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2377,7 +2191,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_3:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2412,7 +2226,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_factor_phase_3:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_factor_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -2428,7 +2242,7 @@ 'state': '99.7', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_1:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2462,7 +2276,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_1:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2500,7 +2314,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_1:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -2516,7 +2330,7 @@ 'state': '-1058.296', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_2:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2550,7 +2364,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_2:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2588,7 +2402,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_2:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -2604,7 +2418,7 @@ 'state': '158.102', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_3:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2638,7 +2452,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_3:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2676,7 +2490,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_power_phase_3:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_power_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -2692,7 +2506,7 @@ 'state': '0.0', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2726,7 +2540,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2761,7 +2575,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -2777,7 +2591,7 @@ 'state': '-429.025', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_1:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2811,7 +2625,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_1:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2846,7 +2660,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_1:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -2862,7 +2676,7 @@ 'state': '0', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_2:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2896,7 +2710,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_2:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2931,7 +2745,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_2:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -2947,7 +2761,7 @@ 'state': '-166.675', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_3:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -2981,7 +2795,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_3:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3016,7 +2830,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_reactive_power_phase_3:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_reactive_power_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -3032,7 +2846,7 @@ 'state': '-262.35', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_1:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -3066,7 +2880,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_1:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3101,7 +2915,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_1:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -3117,7 +2931,7 @@ 'state': '230.751', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_2:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -3151,7 +2965,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_2:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3186,7 +3000,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_2:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -3202,7 +3016,7 @@ 'state': '228.391', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_3:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -3236,7 +3050,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_3:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3271,7 +3085,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_voltage_phase_3:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_voltage_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -3287,7 +3101,7 @@ 'state': '229.612', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_wi_fi_ssid:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_wi_fi_ssid:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -3321,7 +3135,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_wi_fi_ssid:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_wi_fi_ssid:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3354,7 +3168,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_wi_fi_ssid:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_wi_fi_ssid:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi SSID', @@ -3367,7 +3181,7 @@ 'state': 'My Wi-Fi', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_wi_fi_strength:device-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_wi_fi_strength:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -3401,7 +3215,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_wi_fi_strength:entity-registry] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_wi_fi_strength:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3436,7 +3250,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-KWH3-entity_ids7][sensor.device_wi_fi_strength:state] +# name: test_sensors[HWE-KWH3-entity_ids8][sensor.device_wi_fi_strength:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi strength', @@ -3451,1143 +3265,6 @@ 'state': '92', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_average_demand:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_average_demand:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_average_demand', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active average demand', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_average_w', - 'unique_id': 'aabbccddeeff_active_power_average_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_average_demand:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active average demand', - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_average_demand', - 'last_changed': , - 'last_updated': , - 'state': '123.0', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l1_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '-4', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l2_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l3_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_current_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_frequency:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_frequency:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_frequency', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active frequency', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_frequency_hz', - 'unique_id': 'aabbccddeeff_active_frequency_hz', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_frequency:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Device Active frequency', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_frequency', - 'last_changed': , - 'last_updated': , - 'state': '50', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_w', - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power', - 'last_changed': , - 'last_updated': , - 'state': '-123', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '-123', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l2_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '456', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l3_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_power_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '123.456', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_tariff:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_tariff:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'options': list([ - '1', - '2', - '3', - '4', - ]), - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_tariff', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active tariff', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_tariff', - 'unique_id': 'aabbccddeeff_active_tariff', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_tariff:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Device Active tariff', - 'options': list([ - '1', - '2', - '3', - '4', - ]), - }), - 'context': , - 'entity_id': 'sensor.device_active_tariff', - 'last_changed': , - 'last_updated': , - 'state': '2', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active voltage phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_voltage_phase_v', - 'unique_id': 'aabbccddeeff_active_voltage_l1_v', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '230.111', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active voltage phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_voltage_phase_v', - 'unique_id': 'aabbccddeeff_active_voltage_l2_v', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '230.222', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active voltage phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_voltage_phase_v', - 'unique_id': 'aabbccddeeff_active_voltage_l3_v', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_voltage_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '230.333', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Active water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_liter_lpm', - 'unique_id': 'aabbccddeeff_active_liter_lpm', - 'unit_of_measurement': 'l/min', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_active_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Active water usage', - 'state_class': , - 'unit_of_measurement': 'l/min', - }), - 'context': , - 'entity_id': 'sensor.device_active_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '12.345', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.device_average_demand:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -5940,81 +4617,6 @@ 'state': '50', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_gas_meter_identifier:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_gas_meter_identifier:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.device_gas_meter_identifier', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Gas meter identifier', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'gas_unique_id', - 'unique_id': 'aabbccddeeff_gas_unique_id', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_gas_meter_identifier:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Gas meter identifier', - }), - 'context': , - 'entity_id': 'sensor.device_gas_meter_identifier', - 'last_changed': , - 'last_updated': , - 'state': '01FFEEDDCCBBAA99887766554433221100', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.device_long_power_failures_detected:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -6863,886 +5465,6 @@ 'state': '2', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export', - 'last_changed': , - 'last_updated': , - 'state': '13086.777', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t1_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_1', - 'last_changed': , - 'last_updated': , - 'state': '4321.333', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t2_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_2', - 'last_changed': , - 'last_updated': , - 'state': '8765.444', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t3_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_3', - 'last_changed': , - 'last_updated': , - 'state': '8765.444', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_4:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_4:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_4', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 4', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t4_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_export_tariff_4:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 4', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_4', - 'last_changed': , - 'last_updated': , - 'state': '8765.444', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import', - 'last_changed': , - 'last_updated': , - 'state': '13779.338', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t1_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_1', - 'last_changed': , - 'last_updated': , - 'state': '10830.511', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t2_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_2', - 'last_changed': , - 'last_updated': , - 'state': '2948.827', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t3_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_3', - 'last_changed': , - 'last_updated': , - 'state': '2948.827', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_4:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_4:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_4', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 4', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t4_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_energy_import_tariff_4:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 4', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_4', - 'last_changed': , - 'last_updated': , - 'state': '2948.827', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_gas:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_gas:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_gas', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total gas', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_gas_m3', - 'unique_id': 'aabbccddeeff_total_gas_m3', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_gas:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'gas', - 'friendly_name': 'Device Total gas', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_gas', - 'last_changed': , - 'last_updated': , - 'state': '1122.333', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.device_total_water_usage:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -8811,78 +6533,6 @@ 'state': '100', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'G001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Gas meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gas_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_G001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Gas meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.gas_meter', - 'last_changed': , - 'last_updated': , - 'state': 'G001', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter_gas:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -8964,154 +6614,6 @@ 'state': '111.111', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter_total_gas:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'G001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Gas meter', - 'name_by_user': None, - 'serial_number': 'G001', - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter_total_gas:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.gas_meter_total_gas', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total gas', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_gas_m3', - 'unique_id': 'homewizard_G001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.gas_meter_total_gas:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'gas', - 'friendly_name': 'Gas meter Total gas', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.gas_meter_total_gas', - 'last_changed': , - 'last_updated': , - 'state': '111.111', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'H001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Heat meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.heat_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_H001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Heat meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.heat_meter', - 'last_changed': , - 'last_updated': , - 'state': 'H001', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter_energy:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -9193,154 +6695,6 @@ 'state': '444.444', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter_total_heat_energy:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'H001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Heat meter', - 'name_by_user': None, - 'serial_number': 'H001', - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter_total_heat_energy:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.heat_meter_total_heat_energy', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total heat energy', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_gj', - 'unique_id': 'homewizard_H001', - 'unit_of_measurement': 'GJ', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.heat_meter_total_heat_energy:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Heat meter Total heat energy', - 'state_class': , - 'unit_of_measurement': 'GJ', - }), - 'context': , - 'entity_id': 'sensor.heat_meter_total_heat_energy', - 'last_changed': , - 'last_updated': , - 'state': '444.444', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'IH001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Inlet heat meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.inlet_heat_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_IH001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Inlet heat meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.inlet_heat_meter', - 'last_changed': , - 'last_updated': , - 'state': 'IH001', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter_none:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -9421,229 +6775,6 @@ 'state': '555.555', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter_total_heat_energy:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'IH001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Inlet heat meter', - 'name_by_user': None, - 'serial_number': 'IH001', - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter_total_heat_energy:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.inlet_heat_meter_total_heat_energy', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Total heat energy', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_gj', - 'unique_id': 'homewizard_IH001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.inlet_heat_meter_total_heat_energy:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Inlet heat meter Total heat energy', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.inlet_heat_meter_total_heat_energy', - 'last_changed': , - 'last_updated': , - 'state': '555.555', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'WW001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Warm water meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.warm_water_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_WW001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Warm water meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.warm_water_meter', - 'last_changed': , - 'last_updated': , - 'state': 'WW001', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter_total_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'WW001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Warm water meter', - 'name_by_user': None, - 'serial_number': 'WW001', - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter_total_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.warm_water_meter_total_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_liter_m3', - 'unique_id': 'homewizard_WW001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter_total_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'water', - 'friendly_name': 'Warm water meter Total water usage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.warm_water_meter_total_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '333.333', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.warm_water_meter_water:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -9725,154 +6856,6 @@ 'state': '333.333', }) # --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'W001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Water meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.water_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_W001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Water meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.water_meter', - 'last_changed': , - 'last_updated': , - 'state': 'W001', - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter_total_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'W001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Water meter', - 'name_by_user': None, - 'serial_number': 'W001', - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter_total_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.water_meter_total_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_liter_m3', - 'unique_id': 'homewizard_W001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter_total_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'water', - 'friendly_name': 'Water meter Total water usage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.water_meter_total_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '222.222', - }) -# --- # name: test_sensors[HWE-P1-entity_ids0][sensor.water_meter_water:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -9954,1054 +6937,6 @@ 'state': '222.222', }) # --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_average_demand:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_average_demand:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_average_demand', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active average demand', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_average_w', - 'unique_id': 'aabbccddeeff_active_power_average_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_average_demand:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active average demand', - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_average_demand', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l1_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l2_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l3_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_current_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_frequency:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_frequency:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_frequency', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active frequency', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_frequency_hz', - 'unique_id': 'aabbccddeeff_active_frequency_hz', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_frequency:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Device Active frequency', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_frequency', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_w', - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l2_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l3_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_power_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active voltage phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_voltage_phase_v', - 'unique_id': 'aabbccddeeff_active_voltage_l1_v', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active voltage phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_voltage_phase_v', - 'unique_id': 'aabbccddeeff_active_voltage_l2_v', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active voltage phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_voltage_phase_v', - 'unique_id': 'aabbccddeeff_active_voltage_l3_v', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_voltage_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Active water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_liter_lpm', - 'unique_id': 'aabbccddeeff_active_liter_lpm', - 'unit_of_measurement': 'l/min', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_active_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Active water usage', - 'state_class': , - 'unit_of_measurement': 'l/min', - }), - 'context': , - 'entity_id': 'sensor.device_active_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- # name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_average_demand:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -12354,83 +8289,6 @@ 'state': '0', }) # --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_peak_demand_current_month:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_peak_demand_current_month:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_peak_demand_current_month', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Peak demand current month', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'monthly_power_peak_w', - 'unique_id': 'aabbccddeeff_monthly_power_peak_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_peak_demand_current_month:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Peak demand current month', - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_peak_demand_current_month', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- # name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -12863,886 +8721,6 @@ 'state': '0.0', }) # --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t1_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_1', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t2_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_2', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t3_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_3', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_4:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_4:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export_tariff_4', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export tariff 4', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_t4_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_export_tariff_4:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export tariff 4', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export_tariff_4', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t1_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_1', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t2_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_2', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t3_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_3', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_4:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_4:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import_tariff_4', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import tariff 4', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_tariff_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_t4_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_energy_import_tariff_4:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import tariff 4', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import_tariff_4', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_gas:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '4.19', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_gas:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_gas', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total gas', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_gas_m3', - 'unique_id': 'aabbccddeeff_total_gas_m3', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_gas:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'gas', - 'friendly_name': 'Device Total gas', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_gas', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- # name: test_sensors[HWE-P1-zero-values-entity_ids1][sensor.device_total_water_usage:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -14647,173 +9625,7 @@ 'state': '0.0', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_active_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-SKT', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_active_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_w', - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_active_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power', - 'last_changed': , - 'last_updated': , - 'state': '1457.277', - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_active_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-SKT', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_active_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_active_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '1457.277', - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_energy_export:device-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_export:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -14847,7 +9659,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_energy_export:entity-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_export:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -14882,7 +9694,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_energy_export:state] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_export:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -14898,7 +9710,7 @@ 'state': '0', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_energy_import:device-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_import:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -14932,7 +9744,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_energy_import:entity-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_import:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -14967,7 +9779,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_energy_import:state] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_energy_import:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -14983,7 +9795,7 @@ 'state': '63.651', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_power:device-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -15017,7 +9829,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_power:entity-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -15055,7 +9867,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_power:state] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -15071,7 +9883,7 @@ 'state': '1457.277', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_power_phase_1:device-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -15105,7 +9917,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_power_phase_1:entity-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_power_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -15143,7 +9955,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_power_phase_1:state] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_power_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -15159,167 +9971,7 @@ 'state': '1457.277', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_total_energy_export:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-SKT', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_total_energy_export:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_total_energy_export:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_total_energy_import:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-SKT', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_total_energy_import:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_total_energy_import:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import', - 'last_changed': , - 'last_updated': , - 'state': '63.651', - }) -# --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_wi_fi_ssid:device-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_wi_fi_ssid:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -15353,7 +10005,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_wi_fi_ssid:entity-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_wi_fi_ssid:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -15386,7 +10038,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_wi_fi_ssid:state] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_wi_fi_ssid:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi SSID', @@ -15399,7 +10051,7 @@ 'state': 'My Wi-Fi', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_wi_fi_strength:device-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_wi_fi_strength:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -15433,7 +10085,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_wi_fi_strength:entity-registry] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_wi_fi_strength:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -15468,7 +10120,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[HWE-SKT-entity_ids2][sensor.device_wi_fi_strength:state] +# name: test_sensors[HWE-SKT-11-entity_ids2][sensor.device_wi_fi_strength:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi strength', @@ -15483,86 +10135,7 @@ 'state': '94', }) # --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_active_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-WTR', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '2.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_active_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Active water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_liter_lpm', - 'unique_id': 'aabbccddeeff_active_liter_lpm', - 'unit_of_measurement': 'l/min', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_active_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Active water usage', - 'state_class': , - 'unit_of_measurement': 'l/min', - }), - 'context': , - 'entity_id': 'sensor.device_active_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_total_water_usage:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_apparent_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -15587,996 +10160,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'HWE-WTR', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '2.03', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_total_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_liter_m3', - 'unique_id': 'aabbccddeeff_total_liter_m3', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_total_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'water', - 'friendly_name': 'Device Total water usage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_water_usage', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '17.014', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'HWE-WTR', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '2.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_liter_lpm', - 'unique_id': 'aabbccddeeff_active_liter_lpm', - 'unit_of_measurement': 'l/min', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Water usage', - 'state_class': , - 'unit_of_measurement': 'l/min', - }), - 'context': , - 'entity_id': 'sensor.device_water_usage', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_wi_fi_ssid:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'HWE-WTR', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '2.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_wi_fi_ssid:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.device_wi_fi_ssid', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Wi-Fi SSID', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'wifi_ssid', - 'unique_id': 'aabbccddeeff_wifi_ssid', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_wi_fi_ssid:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Wi-Fi SSID', - }), - 'context': , - 'entity_id': 'sensor.device_wi_fi_ssid', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'My Wi-Fi', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_wi_fi_strength:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'HWE-WTR', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '2.03', - 'via_device_id': None, - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_wi_fi_strength:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.device_wi_fi_strength', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Wi-Fi strength', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'wifi_strength', - 'unique_id': 'aabbccddeeff_wifi_strength', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[HWE-WTR-entity_ids3][sensor.device_wi_fi_strength:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Device Wi-Fi strength', - 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.device_wi_fi_strength', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '84', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_apparent_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_apparent_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_apparent_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active apparent power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_apparent_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Active apparent power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_apparent_power', - 'last_changed': , - 'last_updated': , - 'state': '74.052', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_current:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_current:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_a', - 'unique_id': 'aabbccddeeff_active_current_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_current:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current', - 'last_changed': , - 'last_updated': , - 'state': '0.273', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_frequency:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_frequency:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_frequency', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active frequency', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_frequency_hz', - 'unique_id': 'aabbccddeeff_active_frequency_hz', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_frequency:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Device Active frequency', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_frequency', - 'last_changed': , - 'last_updated': , - 'state': '50', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_w', - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power', - 'last_changed': , - 'last_updated': , - 'state': '-1058.296', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power_factor:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power_factor:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_factor', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power factor', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_factor', - 'unique_id': 'aabbccddeeff_active_power_factor', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power_factor:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power_factor', - 'friendly_name': 'Device Active power factor', - 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.device_active_power_factor', - 'last_changed': , - 'last_updated': , - 'state': '61.1', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '-1058.296', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_reactive_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_reactive_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_power', - 'entity_id': 'sensor.device_active_reactive_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active reactive power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_reactive_power_var', - 'unique_id': 'aabbccddeeff_active_reactive_power_var', - 'unit_of_measurement': 'var', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_reactive_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'reactive_power', - 'friendly_name': 'Device Active reactive power', - 'state_class': , - 'unit_of_measurement': 'var', - }), - 'context': , - 'entity_id': 'sensor.device_active_reactive_power', - 'last_changed': , - 'last_updated': , - 'state': '-58.612', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_voltage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_voltage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_active_voltage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage', - 'last_changed': , - 'last_updated': , - 'state': '228.472', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_apparent_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_apparent_power:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_apparent_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -16611,7 +10204,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_apparent_power:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_apparent_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -16624,10 +10217,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '74.052', + 'state': '666.768', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_current:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_current:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -16652,16 +10245,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_current:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_current:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -16696,7 +10289,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_current:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_current:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -16709,10 +10302,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.273', + 'state': '2.346', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_energy_export:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_energy_export:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -16737,16 +10330,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_energy_export:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_energy_export:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -16781,7 +10374,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_energy_export:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_energy_export:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -16794,10 +10387,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '255.551', + 'state': '85.951', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_energy_import:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_energy_import:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -16822,16 +10415,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_energy_import:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_energy_import:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -16866,7 +10459,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_energy_import:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_energy_import:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -16879,10 +10472,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2.705', + 'state': '30.511', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_frequency:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_frequency:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -16907,16 +10500,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_frequency:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_frequency:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -16951,7 +10544,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_frequency:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_frequency:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'frequency', @@ -16964,10 +10557,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '50', + 'state': '50.005', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -16992,16 +10585,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17039,7 +10632,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -17052,10 +10645,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-1058.296', + 'state': '543.312', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power_factor:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power_factor:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -17080,16 +10673,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power_factor:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power_factor:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17124,7 +10717,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power_factor:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power_factor:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -17137,93 +10730,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '61.1', + 'state': '81.688', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '-1058.296', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_reactive_power:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -17248,16 +10758,104 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_reactive_power:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power_phase_1:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_power_phase_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power phase 1', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_power_phase_w', + 'unique_id': 'aabbccddeeff_active_power_l1_w', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_power_phase_1:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Device Power phase 1', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_power_phase_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '543.312', + }) +# --- +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_reactive_power:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-SKT', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '4.07', + 'via_device_id': None, + }) +# --- +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_reactive_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17292,7 +10890,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_reactive_power:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_reactive_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -17305,170 +10903,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-58.612', + 'state': '123.456', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_total_energy_export:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_total_energy_export:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_total_energy_export:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export', - 'last_changed': , - 'last_updated': , - 'state': '255.551', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_total_energy_import:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_total_energy_import:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_total_energy_import:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import', - 'last_changed': , - 'last_updated': , - 'state': '2.705', - }) -# --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_voltage:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_voltage:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -17493,16 +10931,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_voltage:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_voltage:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17537,7 +10975,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_voltage:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_voltage:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -17550,10 +10988,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '228.472', + 'state': '231.539', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_wi_fi_ssid:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_wi_fi_ssid:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -17578,16 +11016,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_wi_fi_ssid:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_wi_fi_ssid:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17620,7 +11058,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_wi_fi_ssid:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_wi_fi_ssid:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi SSID', @@ -17633,7 +11071,7 @@ 'state': 'My Wi-Fi', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_wi_fi_strength:device-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_wi_fi_strength:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -17658,16 +11096,16 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM230-wifi', + 'model': 'HWE-SKT', 'name': 'Device', 'name_by_user': None, 'serial_number': None, 'suggested_area': None, - 'sw_version': '3.06', + 'sw_version': '4.07', 'via_device_id': None, }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_wi_fi_strength:entity-registry] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_wi_fi_strength:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -17702,7 +11140,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[SDM230-entity_ids4][sensor.device_wi_fi_strength:state] +# name: test_sensors[HWE-SKT-21-entity_ids3][sensor.device_wi_fi_strength:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi strength', @@ -17714,1865 +11152,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '92', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_apparent_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active apparent power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Active apparent power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_apparent_power', - 'last_changed': , - 'last_updated': , - 'state': '7112.293', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_apparent_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active apparent power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_phase_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_l1_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Active apparent power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_apparent_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_apparent_power_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active apparent power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_phase_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_l2_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Active apparent power phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_apparent_power_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '3548.879', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_apparent_power_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active apparent power phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_phase_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_l3_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_apparent_power_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Active apparent power phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_apparent_power_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '3563.414', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_a', - 'unique_id': 'aabbccddeeff_active_current_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current', - 'last_changed': , - 'last_updated': , - 'state': '30.999', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l1_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l2_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '15.521', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_current_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active current phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l3_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_current_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Active current phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_current_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '15.477', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_frequency:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_frequency:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_frequency', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active frequency', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_frequency_hz', - 'unique_id': 'aabbccddeeff_active_frequency_hz', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_frequency:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'frequency', - 'friendly_name': 'Device Active frequency', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_frequency', - 'last_changed': , - 'last_updated': , - 'state': '49.926', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_w', - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power', - 'last_changed': , - 'last_updated': , - 'state': '-900.194', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_factor_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power factor phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_factor_phase', - 'unique_id': 'aabbccddeeff_active_power_factor_l1', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power_factor', - 'friendly_name': 'Device Active power factor phase 1', - 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.device_active_power_factor_phase_1', - 'last_changed': , - 'last_updated': , 'state': '100', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_factor_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power factor phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_factor_phase', - 'unique_id': 'aabbccddeeff_active_power_factor_l2', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power_factor', - 'friendly_name': 'Device Active power factor phase 2', - 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.device_active_power_factor_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '99.9', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_factor_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power factor phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_factor_phase', - 'unique_id': 'aabbccddeeff_active_power_factor_l3', - 'unit_of_measurement': '%', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_factor_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power_factor', - 'friendly_name': 'Device Active power factor phase 3', - 'state_class': , - 'unit_of_measurement': '%', - }), - 'context': , - 'entity_id': 'sensor.device_active_power_factor_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '99.7', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '-1058.296', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l2_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '158.102', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_power_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 0, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active power phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l3_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_power_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Device Active power phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_power_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '0.0', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_power', - 'entity_id': 'sensor.device_active_reactive_power', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active reactive power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_reactive_power_var', - 'unique_id': 'aabbccddeeff_active_reactive_power_var', - 'unit_of_measurement': 'var', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'reactive_power', - 'friendly_name': 'Device Active reactive power', - 'state_class': , - 'unit_of_measurement': 'var', - }), - 'context': , - 'entity_id': 'sensor.device_active_reactive_power', - 'last_changed': , - 'last_updated': , - 'state': '-429.025', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_reactive_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active reactive power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_reactive_power_phase_var', - 'unique_id': 'aabbccddeeff_active_reactive_power_l1_var', - 'unit_of_measurement': 'var', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'reactive_power', - 'friendly_name': 'Device Active reactive power phase 1', - 'state_class': , - 'unit_of_measurement': 'var', - }), - 'context': , - 'entity_id': 'sensor.device_active_reactive_power_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_reactive_power_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active reactive power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_reactive_power_phase_var', - 'unique_id': 'aabbccddeeff_active_reactive_power_l2_var', - 'unit_of_measurement': 'var', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'reactive_power', - 'friendly_name': 'Device Active reactive power phase 2', - 'state_class': , - 'unit_of_measurement': 'var', - }), - 'context': , - 'entity_id': 'sensor.device_active_reactive_power_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '-166.675', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_reactive_power_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Active reactive power phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_reactive_power_phase_var', - 'unique_id': 'aabbccddeeff_active_reactive_power_l3_var', - 'unit_of_measurement': 'var', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_reactive_power_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'reactive_power', - 'friendly_name': 'Device Active reactive power phase 3', - 'state_class': , - 'unit_of_measurement': 'var', - }), - 'context': , - 'entity_id': 'sensor.device_active_reactive_power_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '-262.35', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_1:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_active_voltage_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Power', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': 'aabbccddeeff_active_power_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_1', - 'last_changed': , - 'last_updated': , - 'state': '230.751', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_power_phase_1', - 'entity_id': 'sensor.device_active_voltage_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l1_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_2', - 'last_changed': , - 'last_updated': , - 'state': '228.391', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_power_phase_2', - 'entity_id': 'sensor.device_active_voltage_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_power_phase_w', - 'unique_id': 'aabbccddeeff_active_power_l2_w', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_active_voltage_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'voltage', - 'friendly_name': 'Device Active voltage phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_active_voltage_phase_3', - 'last_changed': , - 'last_updated': , - 'state': '229.612', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power:device-registry] +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_total_water_usage:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -19597,7 +11180,340 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', + 'model': 'HWE-WTR', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '2.03', + 'via_device_id': None, + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_total_water_usage:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_total_water_usage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total water usage', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_liter_m3', + 'unique_id': 'aabbccddeeff_total_liter_m3', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_total_water_usage:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': 'Device Total water usage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_total_water_usage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '17.014', + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_water_usage:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-WTR', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '2.03', + 'via_device_id': None, + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_water_usage:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_water_usage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Water usage', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_liter_lpm', + 'unique_id': 'aabbccddeeff_active_liter_lpm', + 'unit_of_measurement': 'l/min', + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_water_usage:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Water usage', + 'state_class': , + 'unit_of_measurement': 'l/min', + }), + 'context': , + 'entity_id': 'sensor.device_water_usage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_wi_fi_ssid:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-WTR', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '2.03', + 'via_device_id': None, + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_wi_fi_ssid:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.device_wi_fi_ssid', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wi-Fi SSID', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wifi_ssid', + 'unique_id': 'aabbccddeeff_wifi_ssid', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_wi_fi_ssid:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Wi-Fi SSID', + }), + 'context': , + 'entity_id': 'sensor.device_wi_fi_ssid', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'My Wi-Fi', + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_wi_fi_strength:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-WTR', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '2.03', + 'via_device_id': None, + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_wi_fi_strength:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.device_wi_fi_strength', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wi-Fi strength', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wifi_strength', + 'unique_id': 'aabbccddeeff_wifi_strength', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[HWE-WTR-entity_ids4][sensor.device_wi_fi_strength:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Wi-Fi strength', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.device_wi_fi_strength', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '84', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_apparent_power:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM230-wifi', 'name': 'Device', 'name_by_user': None, 'serial_number': None, @@ -19606,7 +11522,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power:entity-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_apparent_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -19641,7 +11557,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power:state] +# name: test_sensors[SDM230-entity_ids5][sensor.device_apparent_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', @@ -19654,10 +11570,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '7112.293', + 'state': '74.052', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_1:device-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_current:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -19682,7 +11598,7 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', + 'model': 'SDM230-wifi', 'name': 'Device', 'name_by_user': None, 'serial_number': None, @@ -19691,262 +11607,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_apparent_power_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Apparent power phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_phase_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_l1_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Apparent power phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_apparent_power_phase_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_apparent_power_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Apparent power phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_phase_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_l2_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Apparent power phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_apparent_power_phase_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3548.879', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_apparent_power_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Apparent power phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_apparent_power_phase_va', - 'unique_id': 'aabbccddeeff_active_apparent_power_l3_va', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_apparent_power_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'apparent_power', - 'friendly_name': 'Device Apparent power phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_apparent_power_phase_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '3563.414', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current:entity-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_current:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -19981,7 +11642,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current:state] +# name: test_sensors[SDM230-entity_ids5][sensor.device_current:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', @@ -19994,10 +11655,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '30.999', + 'state': '0.273', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_1:device-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_energy_export:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20022,7 +11683,7 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', + 'model': 'SDM230-wifi', 'name': 'Device', 'name_by_user': None, 'serial_number': None, @@ -20031,262 +11692,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_1:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_current_phase_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Current phase 1', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l1_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_1:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Current phase 1', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_current_phase_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_current_phase_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Current phase 2', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l2_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Current phase 2', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_current_phase_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '15.521', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_3:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_3:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_current_phase_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Current phase 3', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'active_current_phase_a', - 'unique_id': 'aabbccddeeff_active_current_l3_a', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_current_phase_3:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Device Current phase 3', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_current_phase_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '15.477', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_energy_export:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'labels': set({ - }), - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_energy_export:entity-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_energy_export:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20321,7 +11727,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_energy_export:state] +# name: test_sensors[SDM230-entity_ids5][sensor.device_energy_export:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -20334,10 +11740,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.523', + 'state': '255.551', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_energy_import:device-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_energy_import:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20362,7 +11768,7 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', + 'model': 'SDM230-wifi', 'name': 'Device', 'name_by_user': None, 'serial_number': None, @@ -20371,7 +11777,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_energy_import:entity-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_energy_import:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20406,7 +11812,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_energy_import:state] +# name: test_sensors[SDM230-entity_ids5][sensor.device_energy_import:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -20419,10 +11825,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.101', + 'state': '2.705', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_frequency:device-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_frequency:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20447,7 +11853,7 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', + 'model': 'SDM230-wifi', 'name': 'Device', 'name_by_user': None, 'serial_number': None, @@ -20456,7 +11862,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_frequency:entity-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_frequency:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20491,7 +11897,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_frequency:state] +# name: test_sensors[SDM230-entity_ids5][sensor.device_frequency:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'frequency', @@ -20504,10 +11910,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '49.926', + 'state': '50', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power:device-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20532,7 +11938,7 @@ 'labels': set({ }), 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', + 'model': 'SDM230-wifi', 'name': 'Device', 'name_by_user': None, 'serial_number': None, @@ -20541,7 +11947,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power:entity-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20579,7 +11985,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power:state] +# name: test_sensors[SDM230-entity_ids5][sensor.device_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -20592,10 +11998,429 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-900.194', + 'state': '-1058.296', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_1:device-registry] +# name: test_sensors[SDM230-entity_ids5][sensor.device_power_factor:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM230-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_power_factor:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_power_factor', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power factor', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_power_factor', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_power_factor:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power_factor', + 'friendly_name': 'Device Power factor', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.device_power_factor', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '61.1', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_reactive_power:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM230-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_reactive_power:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_reactive_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Reactive power', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_reactive_power_var', + 'unit_of_measurement': 'var', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_reactive_power:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'reactive_power', + 'friendly_name': 'Device Reactive power', + 'state_class': , + 'unit_of_measurement': 'var', + }), + 'context': , + 'entity_id': 'sensor.device_reactive_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-58.612', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_voltage:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM230-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_voltage:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_voltage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Voltage', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_voltage_v', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_voltage:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'voltage', + 'friendly_name': 'Device Voltage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_voltage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '228.472', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_wi_fi_ssid:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM230-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_wi_fi_ssid:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.device_wi_fi_ssid', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wi-Fi SSID', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wifi_ssid', + 'unique_id': 'aabbccddeeff_wifi_ssid', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_wi_fi_ssid:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Wi-Fi SSID', + }), + 'context': , + 'entity_id': 'sensor.device_wi_fi_ssid', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'My Wi-Fi', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_wi_fi_strength:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM230-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_wi_fi_strength:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.device_wi_fi_strength', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Wi-Fi strength', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'wifi_strength', + 'unique_id': 'aabbccddeeff_wifi_strength', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensors[SDM230-entity_ids5][sensor.device_wi_fi_strength:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Wi-Fi strength', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.device_wi_fi_strength', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '92', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20629,7 +12454,1030 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_1:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_apparent_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Apparent power', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_apparent_power_va', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'apparent_power', + 'friendly_name': 'Device Apparent power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_apparent_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '7112.293', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_1:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_1:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_apparent_power_phase_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Apparent power phase 1', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_apparent_power_phase_va', + 'unique_id': 'aabbccddeeff_active_apparent_power_l1_va', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_1:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'apparent_power', + 'friendly_name': 'Device Apparent power phase 1', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_apparent_power_phase_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_2:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_2:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_apparent_power_phase_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Apparent power phase 2', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_apparent_power_phase_va', + 'unique_id': 'aabbccddeeff_active_apparent_power_l2_va', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_2:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'apparent_power', + 'friendly_name': 'Device Apparent power phase 2', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_apparent_power_phase_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3548.879', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_3:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_3:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_apparent_power_phase_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Apparent power phase 3', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_apparent_power_phase_va', + 'unique_id': 'aabbccddeeff_active_apparent_power_l3_va', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_apparent_power_phase_3:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'apparent_power', + 'friendly_name': 'Device Apparent power phase 3', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_apparent_power_phase_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3563.414', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_current', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_current_a', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Device Current', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_current', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '30.999', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_1:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_1:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_current_phase_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current phase 1', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_current_phase_a', + 'unique_id': 'aabbccddeeff_active_current_l1_a', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_1:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Device Current phase 1', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_current_phase_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_2:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_2:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_current_phase_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current phase 2', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_current_phase_a', + 'unique_id': 'aabbccddeeff_active_current_l2_a', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_2:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Device Current phase 2', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_current_phase_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15.521', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_3:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_3:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_current_phase_3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current phase 3', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'active_current_phase_a', + 'unique_id': 'aabbccddeeff_active_current_l3_a', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_current_phase_3:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'current', + 'friendly_name': 'Device Current phase 3', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_current_phase_3', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15.477', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_energy_export:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_energy_export:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_energy_export', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy export', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_energy_export_kwh', + 'unique_id': 'aabbccddeeff_total_power_export_kwh', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_energy_export:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Device Energy export', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_energy_export', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.523', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_energy_import:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_energy_import:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_energy_import', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy import', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_energy_import_kwh', + 'unique_id': 'aabbccddeeff_total_power_import_kwh', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_energy_import:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Device Energy import', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_energy_import', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.101', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_frequency:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_frequency:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_frequency', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Frequency', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_frequency_hz', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_frequency:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'frequency', + 'friendly_name': 'Device Frequency', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_frequency', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '49.926', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_power:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_power:entity-registry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.device_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_active_power_w', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_power:state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Device Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.device_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-900.194', + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_1:device-registry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'SDM630-wifi', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '3.06', + 'via_device_id': None, + }) +# --- +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20664,7 +13512,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_1:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -20680,7 +13528,7 @@ 'state': '100', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_2:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20714,7 +13562,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_2:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20749,7 +13597,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_2:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -20765,7 +13613,7 @@ 'state': '99.9', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_3:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20799,7 +13647,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_3:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20834,7 +13682,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_factor_phase_3:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_factor_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power_factor', @@ -20850,7 +13698,7 @@ 'state': '99.7', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_1:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20884,7 +13732,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_1:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -20922,7 +13770,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_1:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -20938,7 +13786,7 @@ 'state': '-1058.296', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_2:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -20972,7 +13820,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_2:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21010,7 +13858,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_2:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -21026,7 +13874,7 @@ 'state': '158.102', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_3:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21060,7 +13908,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_3:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21098,7 +13946,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_power_phase_3:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_power_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', @@ -21114,7 +13962,7 @@ 'state': '0.0', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21148,7 +13996,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21183,7 +14031,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -21199,7 +14047,7 @@ 'state': '-429.025', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_1:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21233,7 +14081,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_1:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21268,7 +14116,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_1:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -21284,7 +14132,7 @@ 'state': '0', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_2:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21318,7 +14166,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_2:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21353,7 +14201,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_2:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -21369,7 +14217,7 @@ 'state': '-166.675', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_3:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21403,7 +14251,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_3:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21438,7 +14286,7 @@ 'unit_of_measurement': 'var', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_reactive_power_phase_3:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_reactive_power_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'reactive_power', @@ -21454,167 +14302,7 @@ 'state': '-262.35', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_total_energy_export:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_total_energy_export:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_export', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy export', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_export_kwh', - 'unique_id': 'aabbccddeeff_total_power_export_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_total_energy_export:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy export', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_export', - 'last_changed': , - 'last_updated': , - 'state': '0.523', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_total_energy_import:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - tuple( - 'mac', - '3c:39:e7:aa:bb:cc', - ), - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - '3c39e7aabbcc', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'SDM630-wifi', - 'name': 'Device', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': '3.06', - 'via_device_id': None, - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_total_energy_import:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.device_total_energy_import', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total energy import', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_import_kwh', - 'unique_id': 'aabbccddeeff_total_power_import_kwh', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_total_energy_import:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Device Total energy import', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.device_total_energy_import', - 'last_changed': , - 'last_updated': , - 'state': '0.101', - }) -# --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_1:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_1:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21648,7 +14336,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_1:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_1:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21683,7 +14371,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_1:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_1:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -21699,7 +14387,7 @@ 'state': '230.751', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_2:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_2:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21733,7 +14421,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_2:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_2:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21768,7 +14456,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_2:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_2:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -21784,7 +14472,7 @@ 'state': '228.391', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_3:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_3:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21818,7 +14506,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_3:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_3:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21853,7 +14541,7 @@ 'unit_of_measurement': , }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_voltage_phase_3:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_voltage_phase_3:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'voltage', @@ -21869,7 +14557,7 @@ 'state': '229.612', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_wi_fi_ssid:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_wi_fi_ssid:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21903,7 +14591,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_wi_fi_ssid:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_wi_fi_ssid:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -21936,7 +14624,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_wi_fi_ssid:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_wi_fi_ssid:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi SSID', @@ -21949,7 +14637,7 @@ 'state': 'My Wi-Fi', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_wi_fi_strength:device-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_wi_fi_strength:device-registry] DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -21983,7 +14671,7 @@ 'via_device_id': None, }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_wi_fi_strength:entity-registry] +# name: test_sensors[SDM630-entity_ids6][sensor.device_wi_fi_strength:entity-registry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -22018,7 +14706,7 @@ 'unit_of_measurement': '%', }) # --- -# name: test_sensors[SDM630-entity_ids5][sensor.device_wi_fi_strength:state] +# name: test_sensors[SDM630-entity_ids6][sensor.device_wi_fi_strength:state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Wi-Fi strength', @@ -22033,889 +14721,3 @@ 'state': '92', }) # --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'G001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Gas meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gas_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_G001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Gas meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.gas_meter', - 'last_changed': , - 'last_updated': , - 'state': 'G001', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'unknown_unit', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Gas meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.gas_meter_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_unknown_unit_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Gas meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.gas_meter_2', - 'last_changed': , - 'last_updated': , - 'state': 'unknown_unit', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_total_gas:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'G001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Gas meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_total_gas:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.gas_meter_total_gas', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total gas', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_gas_m3', - 'unique_id': 'homewizard_G001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_total_gas:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'gas', - 'friendly_name': 'Gas meter Total gas', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.gas_meter_total_gas', - 'last_changed': , - 'last_updated': , - 'state': '111.111', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_total_gas_2:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'unknown_unit', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Gas meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_total_gas_2:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.gas_meter_total_gas_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Total gas', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_gas_m3', - 'unique_id': 'homewizard_unknown_unit', - 'unit_of_measurement': 'cats', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.gas_meter_total_gas_2:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Gas meter Total gas', - 'state_class': , - 'unit_of_measurement': 'cats', - }), - 'context': , - 'entity_id': 'sensor.gas_meter_total_gas_2', - 'last_changed': , - 'last_updated': , - 'state': '666.666', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.heat_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'H001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Heat meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.heat_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.heat_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_H001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.heat_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Heat meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.heat_meter', - 'last_changed': , - 'last_updated': , - 'state': 'H001', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.heat_meter_total_heat_energy:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'H001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Heat meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.heat_meter_total_heat_energy:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.heat_meter_total_heat_energy', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total heat energy', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_gj', - 'unique_id': 'homewizard_H001', - 'unit_of_measurement': 'GJ', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.heat_meter_total_heat_energy:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Heat meter Total heat energy', - 'state_class': , - 'unit_of_measurement': 'GJ', - }), - 'context': , - 'entity_id': 'sensor.heat_meter_total_heat_energy', - 'last_changed': , - 'last_updated': , - 'state': '444.444', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.inlet_heat_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'IH001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Inlet heat meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.inlet_heat_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.inlet_heat_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_IH001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.inlet_heat_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Inlet heat meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.inlet_heat_meter', - 'last_changed': , - 'last_updated': , - 'state': 'IH001', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.inlet_heat_meter_total_heat_energy:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'IH001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Inlet heat meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.inlet_heat_meter_total_heat_energy:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.inlet_heat_meter_total_heat_energy', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Total heat energy', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_energy_gj', - 'unique_id': 'homewizard_IH001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.inlet_heat_meter_total_heat_energy:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Inlet heat meter Total heat energy', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.inlet_heat_meter_total_heat_energy', - 'last_changed': , - 'last_updated': , - 'state': '555.555', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.warm_water_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'WW001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Warm water meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.warm_water_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.warm_water_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_WW001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.warm_water_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Warm water meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.warm_water_meter', - 'last_changed': , - 'last_updated': , - 'state': 'WW001', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.warm_water_meter_total_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'WW001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Warm water meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.warm_water_meter_total_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.warm_water_meter_total_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_liter_m3', - 'unique_id': 'homewizard_WW001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.warm_water_meter_total_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'water', - 'friendly_name': 'Warm water meter Total water usage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.warm_water_meter_total_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '333.333', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.water_meter:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'W001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Water meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.water_meter:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.water_meter', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': 'mdi:alphabetical-variant', - 'original_name': None, - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'meter_identifier', - 'unique_id': 'homewizard_W001_meter_identifier', - 'unit_of_measurement': None, - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.water_meter:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'friendly_name': 'Water meter', - 'icon': 'mdi:alphabetical-variant', - }), - 'context': , - 'entity_id': 'sensor.water_meter', - 'last_changed': , - 'last_updated': , - 'state': 'W001', - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.water_meter_total_water_usage:device-registry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'homewizard', - 'W001', - ), - }), - 'is_new': False, - 'manufacturer': 'HomeWizard', - 'model': 'HWE-P1', - 'name': 'Water meter', - 'name_by_user': None, - 'serial_number': None, - 'suggested_area': None, - 'sw_version': None, - 'via_device_id': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.water_meter_total_water_usage:entity-registry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.water_meter_total_water_usage', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Total water usage', - 'platform': 'homewizard', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'total_liter_m3', - 'unique_id': 'homewizard_W001', - 'unit_of_measurement': , - }) -# --- -# name: test_sensors_external_devices[HWE-P1-entity_ids0][sensor.water_meter_total_water_usage:state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'water', - 'friendly_name': 'Water meter Total water usage', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.water_meter_total_water_usage', - 'last_changed': , - 'last_updated': , - 'state': '222.222', - }) -# --- diff --git a/tests/components/homewizard/snapshots/test_switch.ambr b/tests/components/homewizard/snapshots/test_switch.ambr index 3036cd3bc38..8877fe01a91 100644 --- a/tests/components/homewizard/snapshots/test_switch.ambr +++ b/tests/components/homewizard/snapshots/test_switch.ambr @@ -159,7 +159,7 @@ 'via_device_id': None, }) # --- -# name: test_switch_entities[HWE-SKT-switch.device-state_set-power_on] +# name: test_switch_entities[HWE-SKT-11-switch.device-state_set-power_on] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'outlet', @@ -173,7 +173,7 @@ 'state': 'on', }) # --- -# name: test_switch_entities[HWE-SKT-switch.device-state_set-power_on].1 +# name: test_switch_entities[HWE-SKT-11-switch.device-state_set-power_on].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -206,7 +206,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_switch_entities[HWE-SKT-switch.device-state_set-power_on].2 +# name: test_switch_entities[HWE-SKT-11-switch.device-state_set-power_on].2 DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -240,7 +240,7 @@ 'via_device_id': None, }) # --- -# name: test_switch_entities[HWE-SKT-switch.device_cloud_connection-system_set-cloud_enabled] +# name: test_switch_entities[HWE-SKT-11-switch.device_cloud_connection-system_set-cloud_enabled] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Cloud connection', @@ -253,7 +253,7 @@ 'state': 'on', }) # --- -# name: test_switch_entities[HWE-SKT-switch.device_cloud_connection-system_set-cloud_enabled].1 +# name: test_switch_entities[HWE-SKT-11-switch.device_cloud_connection-system_set-cloud_enabled].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -286,7 +286,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_switch_entities[HWE-SKT-switch.device_cloud_connection-system_set-cloud_enabled].2 +# name: test_switch_entities[HWE-SKT-11-switch.device_cloud_connection-system_set-cloud_enabled].2 DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -320,7 +320,7 @@ 'via_device_id': None, }) # --- -# name: test_switch_entities[HWE-SKT-switch.device_switch_lock-state_set-switch_lock] +# name: test_switch_entities[HWE-SKT-11-switch.device_switch_lock-state_set-switch_lock] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Device Switch lock', @@ -333,7 +333,7 @@ 'state': 'off', }) # --- -# name: test_switch_entities[HWE-SKT-switch.device_switch_lock-state_set-switch_lock].1 +# name: test_switch_entities[HWE-SKT-11-switch.device_switch_lock-state_set-switch_lock].1 EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -366,7 +366,7 @@ 'unit_of_measurement': None, }) # --- -# name: test_switch_entities[HWE-SKT-switch.device_switch_lock-state_set-switch_lock].2 +# name: test_switch_entities[HWE-SKT-11-switch.device_switch_lock-state_set-switch_lock].2 DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -400,6 +400,247 @@ 'via_device_id': None, }) # --- +# name: test_switch_entities[HWE-SKT-21-switch.device-state_set-power_on] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'outlet', + 'friendly_name': 'Device', + }), + 'context': , + 'entity_id': 'switch.device', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device-state_set-power_on].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.device', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'aabbccddeeff_power_on', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device-state_set-power_on].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-SKT', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '4.07', + 'via_device_id': None, + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device_cloud_connection-system_set-cloud_enabled] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Cloud connection', + }), + 'context': , + 'entity_id': 'switch.device_cloud_connection', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device_cloud_connection-system_set-cloud_enabled].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.device_cloud_connection', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Cloud connection', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'cloud_connection', + 'unique_id': 'aabbccddeeff_cloud_connection', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device_cloud_connection-system_set-cloud_enabled].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-SKT', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '4.07', + 'via_device_id': None, + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device_switch_lock-state_set-switch_lock] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Switch lock', + }), + 'context': , + 'entity_id': 'switch.device_switch_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device_switch_lock-state_set-switch_lock].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.device_switch_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Switch lock', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'switch_lock', + 'unique_id': 'aabbccddeeff_switch_lock', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_entities[HWE-SKT-21-switch.device_switch_lock-state_set-switch_lock].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-SKT', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '4.07', + 'via_device_id': None, + }) +# --- # name: test_switch_entities[SDM230-switch.device_cloud_connection-system_set-cloud_enabled] StateSnapshot({ 'attributes': ReadOnlyDict({ diff --git a/tests/components/homewizard/test_diagnostics.py b/tests/components/homewizard/test_diagnostics.py index 8356c94d164..e3d7f4e6da9 100644 --- a/tests/components/homewizard/test_diagnostics.py +++ b/tests/components/homewizard/test_diagnostics.py @@ -14,7 +14,8 @@ from tests.typing import ClientSessionGenerator "device_fixture", [ "HWE-P1", - "HWE-SKT", + "HWE-SKT-11", + "HWE-SKT-21", "HWE-WTR", "SDM230", "SDM630", diff --git a/tests/components/homewizard/test_init.py b/tests/components/homewizard/test_init.py index d8aeb302aa0..438df8ab869 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -126,12 +126,22 @@ async def test_load_handles_homewizardenergy_exception( ("device_fixture", "old_unique_id", "new_unique_id"), [ ( - "HWE-SKT", + "HWE-SKT-11", "aabbccddeeff_total_power_import_t1_kwh", "aabbccddeeff_total_power_import_kwh", ), ( - "HWE-SKT", + "HWE-SKT-11", + "aabbccddeeff_total_power_export_t1_kwh", + "aabbccddeeff_total_power_export_kwh", + ), + ( + "HWE-SKT-21", + "aabbccddeeff_total_power_import_t1_kwh", + "aabbccddeeff_total_power_import_kwh", + ), + ( + "HWE-SKT-21", "aabbccddeeff_total_power_export_t1_kwh", "aabbccddeeff_total_power_export_kwh", ), @@ -170,12 +180,22 @@ async def test_sensor_migration( ("device_fixture", "old_unique_id", "new_unique_id"), [ ( - "HWE-SKT", + "HWE-SKT-11", "aabbccddeeff_total_power_import_t1_kwh", "aabbccddeeff_total_power_import_kwh", ), ( - "HWE-SKT", + "HWE-SKT-11", + "aabbccddeeff_total_power_export_t1_kwh", + "aabbccddeeff_total_power_export_kwh", + ), + ( + "HWE-SKT-21", + "aabbccddeeff_total_power_import_t1_kwh", + "aabbccddeeff_total_power_import_kwh", + ), + ( + "HWE-SKT-21", "aabbccddeeff_total_power_export_t1_kwh", "aabbccddeeff_total_power_export_kwh", ), diff --git a/tests/components/homewizard/test_number.py b/tests/components/homewizard/test_number.py index dade8f4eef5..ff27fb1b257 100644 --- a/tests/components/homewizard/test_number.py +++ b/tests/components/homewizard/test_number.py @@ -22,7 +22,7 @@ pytestmark = [ ] -@pytest.mark.parametrize("device_fixture", ["HWE-SKT"]) +@pytest.mark.parametrize("device_fixture", ["HWE-SKT-11", "HWE-SKT-21"]) async def test_number_entities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, diff --git a/tests/components/homewizard/test_sensor.py b/tests/components/homewizard/test_sensor.py index a7d018ea35f..5a1b25c69bb 100644 --- a/tests/components/homewizard/test_sensor.py +++ b/tests/components/homewizard/test_sensor.py @@ -112,7 +112,7 @@ pytestmark = [ ], ), ( - "HWE-SKT", + "HWE-SKT-11", [ "sensor.device_energy_export", "sensor.device_energy_import", @@ -122,6 +122,23 @@ pytestmark = [ "sensor.device_wi_fi_strength", ], ), + ( + "HWE-SKT-21", + [ + "sensor.device_apparent_power", + "sensor.device_current", + "sensor.device_energy_export", + "sensor.device_energy_import", + "sensor.device_frequency", + "sensor.device_power_factor", + "sensor.device_power_phase_1", + "sensor.device_power", + "sensor.device_reactive_power", + "sensor.device_voltage", + "sensor.device_wi_fi_ssid", + "sensor.device_wi_fi_strength", + ], + ), ( "HWE-WTR", [ @@ -276,7 +293,13 @@ async def test_sensors( ], ), ( - "HWE-SKT", + "HWE-SKT-11", + [ + "sensor.device_wi_fi_strength", + ], + ), + ( + "HWE-SKT-21", [ "sensor.device_wi_fi_strength", ], @@ -413,7 +436,7 @@ async def test_external_sensors_unreachable( ("device_fixture", "entity_ids"), [ ( - "HWE-SKT", + "HWE-SKT-11", [ "sensor.device_apparent_power_phase_1", "sensor.device_apparent_power_phase_2", @@ -464,6 +487,52 @@ async def test_external_sensors_unreachable( "sensor.device_water_usage", ], ), + ( + "HWE-SKT-21", + [ + "sensor.device_apparent_power_phase_1", + "sensor.device_apparent_power_phase_2", + "sensor.device_apparent_power_phase_3", + "sensor.device_average_demand", + "sensor.device_current_phase_1", + "sensor.device_current_phase_2", + "sensor.device_current_phase_3", + "sensor.device_dsmr_version", + "sensor.device_energy_export_tariff_1", + "sensor.device_energy_export_tariff_2", + "sensor.device_energy_export_tariff_3", + "sensor.device_energy_export_tariff_4", + "sensor.device_energy_import_tariff_1", + "sensor.device_energy_import_tariff_2", + "sensor.device_energy_import_tariff_3", + "sensor.device_energy_import_tariff_4", + "sensor.device_long_power_failures_detected", + "sensor.device_peak_demand_current_month", + "sensor.device_power_factor_phase_1", + "sensor.device_power_factor_phase_2", + "sensor.device_power_factor_phase_3", + "sensor.device_power_failures_detected", + "sensor.device_power_phase_2", + "sensor.device_power_phase_3", + "sensor.device_reactive_power_phase_1", + "sensor.device_reactive_power_phase_2", + "sensor.device_reactive_power_phase_3", + "sensor.device_smart_meter_identifier", + "sensor.device_smart_meter_model", + "sensor.device_tariff", + "sensor.device_total_water_usage", + "sensor.device_voltage_phase_1", + "sensor.device_voltage_phase_2", + "sensor.device_voltage_phase_3", + "sensor.device_voltage_sags_detected_phase_1", + "sensor.device_voltage_sags_detected_phase_2", + "sensor.device_voltage_sags_detected_phase_3", + "sensor.device_voltage_swells_detected_phase_1", + "sensor.device_voltage_swells_detected_phase_2", + "sensor.device_voltage_swells_detected_phase_3", + "sensor.device_water_usage", + ], + ), ( "HWE-WTR", [ diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index 01aa021783f..e9d036a01d8 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -87,9 +87,12 @@ async def test_entities_not_created_for_device( @pytest.mark.parametrize( ("device_fixture", "entity_id", "method", "parameter"), [ - ("HWE-SKT", "switch.device", "state_set", "power_on"), - ("HWE-SKT", "switch.device_switch_lock", "state_set", "switch_lock"), - ("HWE-SKT", "switch.device_cloud_connection", "system_set", "cloud_enabled"), + ("HWE-SKT-11", "switch.device", "state_set", "power_on"), + ("HWE-SKT-11", "switch.device_switch_lock", "state_set", "switch_lock"), + ("HWE-SKT-11", "switch.device_cloud_connection", "system_set", "cloud_enabled"), + ("HWE-SKT-21", "switch.device", "state_set", "power_on"), + ("HWE-SKT-21", "switch.device_switch_lock", "state_set", "switch_lock"), + ("HWE-SKT-21", "switch.device_cloud_connection", "system_set", "cloud_enabled"), ("SDM230", "switch.device_cloud_connection", "system_set", "cloud_enabled"), ("SDM630", "switch.device_cloud_connection", "system_set", "cloud_enabled"), ("HWE-KWH1", "switch.device_cloud_connection", "system_set", "cloud_enabled"), @@ -192,7 +195,7 @@ async def test_switch_entities( ) -@pytest.mark.parametrize("device_fixture", ["HWE-SKT"]) +@pytest.mark.parametrize("device_fixture", ["HWE-SKT-11", "HWE-SKT-21"]) @pytest.mark.parametrize("exception", [RequestError, UnsupportedError]) @pytest.mark.parametrize( ("entity_id", "method"), From c282172252f5238125ecad8c2b47b799fa678f74 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 22 Mar 2024 16:50:07 +0100 Subject: [PATCH 1385/1691] Use uv instead of pip on production images (#112496) --- .github/workflows/builder.yml | 1 + Dockerfile | 25 +++++++++-------- requirements_test.txt | 2 +- script/hassfest/docker.py | 52 +++++++++++++++++++++++++++-------- 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index d1fd9f41eea..09277721e48 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -95,6 +95,7 @@ jobs: packages: write id-token: write strategy: + fail-fast: false matrix: arch: ${{ fromJson(needs.init.outputs.architectures) }} steps: diff --git a/Dockerfile b/Dockerfile index da46f71ad22..700964d93ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,47 +6,50 @@ FROM ${BUILD_FROM} # Synchronize with homeassistant/core.py:async_stop ENV \ - S6_SERVICES_GRACETIME=240000 + S6_SERVICES_GRACETIME=240000 \ + UV_SYSTEM_PYTHON=true ARG QEMU_CPU +# Install uv +RUN pip3 install uv==0.1.22 + WORKDIR /usr/src ## Setup Home Assistant Core dependencies COPY requirements.txt homeassistant/ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/ RUN \ - pip3 install \ - --only-binary=:all: \ + uv pip install \ + --no-build \ -r homeassistant/requirements.txt COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/ RUN \ if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \ - pip3 install homeassistant/home_assistant_frontend-*.whl; \ + uv pip install homeassistant/home_assistant_frontend-*.whl; \ fi \ && if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \ - pip3 install homeassistant/home_assistant_intents-*.whl; \ + uv pip install homeassistant/home_assistant_intents-*.whl; \ fi \ && if [ "${BUILD_ARCH}" = "i386" ]; then \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \ - linux32 pip3 install \ - --only-binary=:all: \ + linux32 uv pip install \ + --no-build \ -r homeassistant/requirements_all.txt; \ else \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \ - pip3 install \ - --only-binary=:all: \ + uv pip install \ + --no-build \ -r homeassistant/requirements_all.txt; \ fi ## Setup Home Assistant Core COPY . homeassistant/ RUN \ - pip3 install \ - --only-binary=:all: \ + uv pip install \ -e ./homeassistant \ && python3 -m compileall \ homeassistant/homeassistant diff --git a/requirements_test.txt b/requirements_test.txt index e189526adaf..e19cfc1a363 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -51,4 +51,4 @@ types-pytz==2023.3.1.1 types-PyYAML==6.0.12.12 types-requests==2.31.0.3 types-xmltodict==0.13.0.3 -uv==0.1.17 \ No newline at end of file +uv==0.1.22 \ No newline at end of file diff --git a/script/hassfest/docker.py b/script/hassfest/docker.py index 49b457f8a74..8a3c3b6937d 100644 --- a/script/hassfest/docker.py +++ b/script/hassfest/docker.py @@ -4,6 +4,7 @@ from homeassistant import core from homeassistant.util import executor, thread from .model import Config, Integration +from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR DOCKERFILE_TEMPLATE = r"""# Automatically generated by hassfest. # @@ -13,47 +14,50 @@ FROM ${{BUILD_FROM}} # Synchronize with homeassistant/core.py:async_stop ENV \ - S6_SERVICES_GRACETIME={timeout} + S6_SERVICES_GRACETIME={timeout} \ + UV_SYSTEM_PYTHON=true ARG QEMU_CPU +# Install uv +RUN pip3 install uv=={uv_version} + WORKDIR /usr/src ## Setup Home Assistant Core dependencies COPY requirements.txt homeassistant/ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/ RUN \ - pip3 install \ - --only-binary=:all: \ + uv pip install \ + --no-build \ -r homeassistant/requirements.txt COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/ RUN \ if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \ - pip3 install homeassistant/home_assistant_frontend-*.whl; \ + uv pip install homeassistant/home_assistant_frontend-*.whl; \ fi \ && if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \ - pip3 install homeassistant/home_assistant_intents-*.whl; \ + uv pip install homeassistant/home_assistant_intents-*.whl; \ fi \ && if [ "${{BUILD_ARCH}}" = "i386" ]; then \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \ - linux32 pip3 install \ - --only-binary=:all: \ + linux32 uv pip install \ + --no-build \ -r homeassistant/requirements_all.txt; \ else \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \ - pip3 install \ - --only-binary=:all: \ + uv pip install \ + --no-build \ -r homeassistant/requirements_all.txt; \ fi ## Setup Home Assistant Core COPY . homeassistant/ RUN \ - pip3 install \ - --only-binary=:all: \ + uv pip install \ -e ./homeassistant \ && python3 -m compileall \ homeassistant/homeassistant @@ -65,6 +69,28 @@ WORKDIR /config """ +def _get_uv_version() -> str: + with open("requirements_test.txt") as fp: + for _, line in enumerate(fp): + if match := PACKAGE_REGEX.match(line): + pkg, sep, version = match.groups() + + if pkg != "uv": + continue + + if sep != "==" or not version: + raise RuntimeError( + 'Requirement uv need to be pinned "uv==".' + ) + + for part in version.split(";", 1)[0].split(","): + version_part = PIP_VERSION_RANGE_SEPARATOR.match(part) + if version_part: + return version_part.group(2) + + raise RuntimeError("Invalid uv requirement in requirements_test.txt") + + def _generate_dockerfile() -> str: timeout = ( core.STOPPING_STAGE_SHUTDOWN_TIMEOUT @@ -75,7 +101,9 @@ def _generate_dockerfile() -> str: + thread.THREADING_SHUTDOWN_TIMEOUT + 10 ) - return DOCKERFILE_TEMPLATE.format(timeout=timeout * 1000) + return DOCKERFILE_TEMPLATE.format( + timeout=timeout * 1000, uv_version=_get_uv_version() + ) def validate(integrations: dict[str, Integration], config: Config) -> None: From 08529b3806a7414babe67d48c8864aacaf8e94de Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 22 Mar 2024 17:35:43 +0100 Subject: [PATCH 1386/1691] Remove deprecated `hass.components` from frontend tests (#114011) --- tests/components/frontend/test_init.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 8bc2cf0606a..d715eb8859d 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -16,6 +16,8 @@ from homeassistant.components.frontend import ( DOMAIN, EVENT_PANELS_UPDATED, THEMES_STORAGE_KEY, + async_register_built_in_panel, + async_remove_panel, ) from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.core import HomeAssistant @@ -417,8 +419,8 @@ async def test_get_panels( resp = await mock_http_client.get("/map") assert resp.status == HTTPStatus.NOT_FOUND - hass.components.frontend.async_register_built_in_panel( - "map", "Map", "mdi:tooltip-account", require_admin=True + async_register_built_in_panel( + hass, "map", "Map", "mdi:tooltip-account", require_admin=True ) resp = await mock_http_client.get("/map") @@ -440,7 +442,7 @@ async def test_get_panels( assert msg["result"]["map"]["title"] == "Map" assert msg["result"]["map"]["require_admin"] is True - hass.components.frontend.async_remove_panel("map") + async_remove_panel(hass, "map") resp = await mock_http_client.get("/map") assert resp.status == HTTPStatus.NOT_FOUND @@ -454,12 +456,10 @@ async def test_get_panels_non_admin( """Test get_panels command.""" hass_admin_user.groups = [] - hass.components.frontend.async_register_built_in_panel( - "map", "Map", "mdi:tooltip-account", require_admin=True - ) - hass.components.frontend.async_register_built_in_panel( - "history", "History", "mdi:history" + async_register_built_in_panel( + hass, "map", "Map", "mdi:tooltip-account", require_admin=True ) + async_register_built_in_panel(hass, "history", "History", "mdi:history") await ws_client.send_json({"id": 5, "type": "get_panels"}) From edc19328c09a041bcb6e9ae7b9ed2b15315feea5 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 22 Mar 2024 17:48:53 +0100 Subject: [PATCH 1387/1691] Remove deprecated `hass.components` from scene tests (#114014) --- tests/components/scene/test_init.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 660be91dca2..ad8e478105e 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -19,14 +19,19 @@ from homeassistant.util import dt as dt_util from homeassistant.util.yaml import loader as yaml_loader from tests.common import async_mock_service, mock_restore_cache +from tests.components.light.common import MockLight, SetupLightPlatformCallable @pytest.fixture(autouse=True) -def entities(hass): +def entities( + hass: HomeAssistant, + setup_light_platform: SetupLightPlatformCallable, + mock_light_entities: list[MockLight], +) -> list[MockLight]: """Initialize the test light.""" - platform = getattr(hass.components, "test.light") - platform.init() - return platform.ENTITIES[0:2] + entities = mock_light_entities[0:2] + setup_light_platform(hass, entities) + return entities async def test_config_yaml_alias_anchor( From 01acbc8bba4c53129b0189feef8d0f53139cd943 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 22 Mar 2024 17:51:39 +0100 Subject: [PATCH 1388/1691] Remove deprecated `hass.components` from zeroconf tests (#114013) --- tests/components/zeroconf/test_init.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index da817c84448..0f70d862c4c 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -188,9 +188,7 @@ async def test_setup(hass: HomeAssistant, mock_async_zeroconf: None) -> None: # Test instance is set. assert "zeroconf" in hass.data - assert ( - await hass.components.zeroconf.async_get_async_instance() is mock_async_zeroconf - ) + assert await zeroconf.async_get_async_instance(hass) is mock_async_zeroconf async def test_setup_with_overly_long_url_and_name( @@ -931,9 +929,7 @@ async def test_info_from_service_can_return_ipv6(hass: HomeAssistant) -> None: async def test_get_instance(hass: HomeAssistant, mock_async_zeroconf: None) -> None: """Test we get an instance.""" assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) - assert ( - await hass.components.zeroconf.async_get_async_instance() is mock_async_zeroconf - ) + assert await zeroconf.async_get_async_instance(hass) is mock_async_zeroconf hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() assert len(mock_async_zeroconf.ha_async_close.mock_calls) == 0 From cd0c9e1c4f5f0545ab321464d6d551f6d664b245 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 22 Mar 2024 17:52:10 +0100 Subject: [PATCH 1389/1691] Remove deprecated `hass.components` from conversation tests and use light setup fixture (#114012) --- tests/components/conversation/test_init.py | 31 +++++++++------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index fd48fe450dc..b2d05e976d6 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -25,6 +25,7 @@ from homeassistant.setup import async_setup_component from . import expose_entity, expose_new from tests.common import MockConfigEntry, MockUser, async_mock_service +from tests.components.light.common import MockLight, SetupLightPlatformCallable from tests.typing import ClientSessionGenerator, WebSocketGenerator AGENT_ID_OPTIONS = [None, conversation.HOME_ASSISTANT_AGENT] @@ -256,7 +257,7 @@ async def test_http_processing_intent_entity_renamed( hass_client: ClientSessionGenerator, hass_admin_user: MockUser, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + setup_light_platform: SetupLightPlatformCallable, snapshot: SnapshotAssertion, ) -> None: """Test processing intent via HTTP API with entities renamed later. @@ -264,13 +265,11 @@ async def test_http_processing_intent_entity_renamed( We want to ensure that renaming an entity later busts the cache so that the new name is used. """ - platform = getattr(hass.components, "test.light") - platform.init(empty=True) - - entity = platform.MockLight("kitchen light", "on") + entity = MockLight("kitchen light", "on") entity._attr_unique_id = "1234" entity.entity_id = "light.kitchen" - platform.ENTITIES.append(entity) + setup_light_platform(hass, [entity]) + assert await async_setup_component( hass, LIGHT_DOMAIN, @@ -347,7 +346,7 @@ async def test_http_processing_intent_entity_exposed( hass_client: ClientSessionGenerator, hass_admin_user: MockUser, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + setup_light_platform: SetupLightPlatformCallable, snapshot: SnapshotAssertion, ) -> None: """Test processing intent via HTTP API with manual expose. @@ -355,13 +354,11 @@ async def test_http_processing_intent_entity_exposed( We want to ensure that manually exposing an entity later busts the cache so that the new setting is used. """ - platform = getattr(hass.components, "test.light") - platform.init(empty=True) - - entity = platform.MockLight("kitchen light", "on") + entity = MockLight("kitchen light", "on") entity._attr_unique_id = "1234" entity.entity_id = "light.kitchen" - platform.ENTITIES.append(entity) + setup_light_platform(hass, [entity]) + assert await async_setup_component( hass, LIGHT_DOMAIN, @@ -452,20 +449,18 @@ async def test_http_processing_intent_conversion_not_expose_new( hass_client: ClientSessionGenerator, hass_admin_user: MockUser, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + setup_light_platform: SetupLightPlatformCallable, snapshot: SnapshotAssertion, ) -> None: """Test processing intent via HTTP API when not exposing new entities.""" # Disable exposing new entities to the default agent expose_new(hass, False) - platform = getattr(hass.components, "test.light") - platform.init(empty=True) - - entity = platform.MockLight("kitchen light", "on") + entity = MockLight("kitchen light", "on") entity._attr_unique_id = "1234" entity.entity_id = "light.kitchen" - platform.ENTITIES.append(entity) + setup_light_platform(hass, [entity]) + assert await async_setup_component( hass, LIGHT_DOMAIN, From c6f2ff8e88af044b9379c9403f2d3250b6e49da6 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 22 Mar 2024 17:52:45 +0100 Subject: [PATCH 1390/1691] Bump axis to v58 (#114008) --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 44d615bf534..f2a2dd40740 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==57"], + "requirements": ["axis==58"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index cbf6a15a5a0..4efa0033902 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==57 +axis==58 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2353e3df349..4ccd8ed4983 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -451,7 +451,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==57 +axis==58 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 From 6800034d5a711a9476713d514ab59fb23aa62a81 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 22 Mar 2024 18:07:33 +0100 Subject: [PATCH 1391/1691] Rename device to api in Axis integration (#113965) --- homeassistant/components/axis/hub/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/axis/hub/api.py b/homeassistant/components/axis/hub/api.py index 0a784dd2c14..dc2e63598b6 100644 --- a/homeassistant/components/axis/hub/api.py +++ b/homeassistant/components/axis/hub/api.py @@ -28,7 +28,7 @@ async def get_axis_api( """Create a Axis device API.""" session = get_async_client(hass, verify_ssl=False) - device = axis.AxisDevice( + api = axis.AxisDevice( Configuration( session, config[CONF_HOST], @@ -41,9 +41,7 @@ async def get_axis_api( try: async with timeout(30): - await device.vapix.initialize() - - return device + await api.vapix.initialize() except axis.Unauthorized as err: LOGGER.warning( @@ -58,3 +56,5 @@ async def get_axis_api( except axis.AxisException as err: LOGGER.exception("Unknown Axis communication error occurred") raise AuthenticationRequired from err + + return api From bbb80caed33e4c3aae110ca7b23dc73e20579e0c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 22 Mar 2024 18:10:07 +0100 Subject: [PATCH 1392/1691] =?UTF-8?q?Tweak=20marking=20private=20methods?= =?UTF-8?q?=20in=20UniFi,=20no=20need=20to=20mark=20inner=20functio?= =?UTF-8?q?=E2=80=A6=20(#113964)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/unifi/hub/entity_loader.py | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index 5ae3e8f789c..2ed2c09d049 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -61,11 +61,11 @@ class UnifiEntityLoader: async def initialize(self) -> None: """Initialize API data and extra client support.""" - await self.refresh_api_data() - self.restore_inactive_clients() + await self._refresh_api_data() + self._restore_inactive_clients() self.wireless_clients.update_clients(set(self.hub.api.clients.values())) - async def refresh_api_data(self) -> None: + async def _refresh_api_data(self) -> None: """Refresh API data from network application.""" results = await asyncio.gather( *[update() for update in self.api_updaters], @@ -76,7 +76,7 @@ class UnifiEntityLoader: LOGGER.warning("Exception on update %s", result) @callback - def restore_inactive_clients(self) -> None: + def _restore_inactive_clients(self) -> None: """Restore inactive clients. Provide inactive clients to device tracker and switch platform. @@ -110,7 +110,7 @@ class UnifiEntityLoader: @callback def load_entities(self) -> None: - """Populate UniFi platforms with entities.""" + """Load entities into the registered UniFi platforms.""" for ( async_add_entities, entity_class, @@ -125,7 +125,7 @@ class UnifiEntityLoader: def _should_add_entity( self, description: UnifiEntityDescription, obj_id: str ) -> bool: - """Check if entity should be added.""" + """Validate if entity is allowed and supported before creating it.""" return bool( (description.key, obj_id) not in self.known_objects and description.allowed_fn(self.hub, obj_id) @@ -139,11 +139,11 @@ class UnifiEntityLoader: descriptions: tuple[UnifiEntityDescription, ...], async_add_entities: AddEntitiesCallback, ) -> None: - """Subscribe to UniFi API handlers and create entities.""" + """Load entities and subscribe for future entities.""" @callback - def _add_unifi_entities() -> None: - """Add UniFi entity.""" + def add_unifi_entities() -> None: + """Add currently known UniFi entities.""" async_add_entities( unifi_platform_entity(obj_id, self.hub, description) for description in descriptions @@ -151,10 +151,20 @@ class UnifiEntityLoader: if self._should_add_entity(description, obj_id) ) - _add_unifi_entities() + add_unifi_entities() + + self.hub.config.entry.async_on_unload( + async_dispatcher_connect( + self.hub.hass, + self.hub.signal_options_update, + add_unifi_entities, + ) + ) + + # Subscribe for future entities @callback - def _create_unifi_entity( + def create_unifi_entity( description: UnifiEntityDescription, event: ItemEvent, obj_id: str ) -> None: """Create new UniFi entity on event.""" @@ -165,13 +175,5 @@ class UnifiEntityLoader: for description in descriptions: description.api_handler_fn(self.hub.api).subscribe( - partial(_create_unifi_entity, description), ItemEvent.ADDED + partial(create_unifi_entity, description), ItemEvent.ADDED ) - - self.hub.config.entry.async_on_unload( - async_dispatcher_connect( - self.hub.hass, - self.hub.signal_options_update, - _add_unifi_entities, - ) - ) From 68e170284fc64797742275126e68504d2512f540 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 22 Mar 2024 07:29:46 -1000 Subject: [PATCH 1393/1691] Speed up recorder startup by making schema query read only (#113987) --- homeassistant/components/recorder/migration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 8395b88837c..6021c7b5f5b 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -181,7 +181,7 @@ def _get_schema_version(session: Session) -> int | None: def get_schema_version(session_maker: Callable[[], Session]) -> int | None: """Get the schema version.""" try: - with session_scope(session=session_maker()) as session: + with session_scope(session=session_maker(), read_only=True) as session: return _get_schema_version(session) except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Error when determining DB schema version: %s", err) @@ -1771,9 +1771,11 @@ def _initialize_database(session: Session) -> bool: def initialize_database(session_maker: Callable[[], Session]) -> bool: """Initialize a new database.""" try: - with session_scope(session=session_maker()) as session: + with session_scope(session=session_maker(), read_only=True) as session: if _get_schema_version(session) is not None: return True + + with session_scope(session=session_maker()) as session: return _initialize_database(session) except Exception as err: # pylint: disable=broad-except From 817d931df0fa0c9264a7c8abbd9cb887b1f3ebc6 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 22 Mar 2024 18:59:07 +0100 Subject: [PATCH 1394/1691] Define and use entity description in Axis entity base class (#114007) Define and use entity description in entity base class --- .../components/axis/binary_sensor.py | 19 +++------- homeassistant/components/axis/entity.py | 38 +++++++++++++++---- homeassistant/components/axis/light.py | 21 ++++------ homeassistant/components/axis/switch.py | 20 ++++------ 4 files changed, 51 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index e68487a6bb1..b6df07ce4ef 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -23,21 +23,14 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later -from .entity import AxisEventEntity +from .entity import AxisEventDescription, AxisEventEntity from .hub import AxisHub @dataclass(frozen=True, kw_only=True) -class AxisBinarySensorDescription(BinarySensorEntityDescription): +class AxisBinarySensorDescription(AxisEventDescription, BinarySensorEntityDescription): """Axis binary sensor entity description.""" - event_topic: tuple[EventTopic, ...] | EventTopic - """Event topic that provides state updates.""" - name_fn: Callable[[AxisHub, Event], str] = lambda hub, event: "" - """Function providing the corresponding name to the event ID.""" - supported_fn: Callable[[AxisHub, Event], bool] = lambda hub, event: True - """Function validating if event is supported.""" - @callback def event_id_is_int(event_id: str) -> bool: @@ -216,15 +209,15 @@ async def async_setup_entry( class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): """Representation of a binary Axis event.""" + entity_description: AxisBinarySensorDescription + def __init__( self, hub: AxisHub, description: AxisBinarySensorDescription, event: Event ) -> None: """Initialize the Axis binary sensor.""" - super().__init__(event, hub) - self.entity_description = description - self._attr_name = description.name_fn(hub, event) or self._attr_name + super().__init__(hub, description, event) + self._attr_is_on = event.is_tripped - self._attr_device_class = description.device_class # temporary self.cancel_scheduled_update: Callable[[], None] | None = None @callback diff --git a/homeassistant/components/axis/entity.py b/homeassistant/components/axis/entity.py index 6542b8c55f5..7980b7217e8 100644 --- a/homeassistant/components/axis/entity.py +++ b/homeassistant/components/axis/entity.py @@ -1,16 +1,23 @@ """Base classes for Axis entities.""" +from __future__ import annotations + from abc import abstractmethod +from collections.abc import Callable +from dataclasses import dataclass +from typing import TYPE_CHECKING from axis.models.event import Event, EventTopic from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import Entity, EntityDescription from .const import DOMAIN as AXIS_DOMAIN -from .hub import AxisHub + +if TYPE_CHECKING: + from .hub import AxisHub TOPIC_TO_EVENT_TYPE = { EventTopic.DAY_NIGHT_VISION: "DayNight", @@ -32,6 +39,18 @@ TOPIC_TO_EVENT_TYPE = { } +@dataclass(frozen=True, kw_only=True) +class AxisEventDescription(EntityDescription): + """Axis event based entity description.""" + + event_topic: tuple[EventTopic, ...] | EventTopic + """Event topic that provides state updates.""" + name_fn: Callable[[AxisHub, Event], str] = lambda hub, event: "" + """Function providing the corresponding name to the event ID.""" + supported_fn: Callable[[AxisHub, Event], bool] = lambda hub, event: True + """Function validating if event is supported.""" + + class AxisEntity(Entity): """Base common to all Axis entities.""" @@ -66,21 +85,26 @@ class AxisEntity(Entity): class AxisEventEntity(AxisEntity): """Base common to all Axis entities from event stream.""" + entity_description: AxisEventDescription + _attr_should_poll = False - def __init__(self, event: Event, hub: AxisHub) -> None: + def __init__( + self, hub: AxisHub, description: AxisEventDescription, event: Event + ) -> None: """Initialize the Axis event.""" super().__init__(hub) + self.entity_description = description + self._event_id = event.id self._event_topic = event.topic_base - self._event_type = TOPIC_TO_EVENT_TYPE[event.topic_base] + event_type = TOPIC_TO_EVENT_TYPE[event.topic_base] + + self._attr_name = description.name_fn(hub, event) or f"{event_type} {event.id}" - self._attr_name = f"{self._event_type} {event.id}" self._attr_unique_id = f"{hub.unique_id}-{event.topic}-{event.id}" - self._attr_device_class = event.group.value - @callback @abstractmethod def async_event_callback(self, event: Event) -> None: diff --git a/homeassistant/components/axis/light.py b/homeassistant/components/axis/light.py index ae7200398bd..1caeac3a247 100644 --- a/homeassistant/components/axis/light.py +++ b/homeassistant/components/axis/light.py @@ -1,6 +1,6 @@ """Support for Axis lights.""" -from collections.abc import Callable, Iterable +from collections.abc import Iterable from dataclasses import dataclass from functools import partial from typing import Any @@ -17,7 +17,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import TOPIC_TO_EVENT_TYPE, AxisEventEntity +from .entity import TOPIC_TO_EVENT_TYPE, AxisEventDescription, AxisEventEntity from .hub import AxisHub @@ -31,16 +31,9 @@ def light_name_fn(hub: AxisHub, event: Event) -> str: @dataclass(frozen=True, kw_only=True) -class AxisLightDescription(LightEntityDescription): +class AxisLightDescription(AxisEventDescription, LightEntityDescription): """Axis light entity description.""" - event_topic: EventTopic - """Event topic that provides state updates.""" - name_fn: Callable[[AxisHub, Event], str] - """Function providing the corresponding name to the event ID.""" - supported_fn: Callable[[AxisHub, Event], bool] - """Function validating if event is supported.""" - ENTITY_DESCRIPTIONS = ( AxisLightDescription( @@ -83,6 +76,8 @@ async def async_setup_entry( class AxisLight(AxisEventEntity, LightEntity): """Representation of an Axis light.""" + entity_description: AxisLightDescription + _attr_should_poll = True _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @@ -91,11 +86,9 @@ class AxisLight(AxisEventEntity, LightEntity): self, hub: AxisHub, description: AxisLightDescription, event: Event ) -> None: """Initialize the Axis light.""" - super().__init__(event, hub) - self.entity_description = description - self._attr_name = description.name_fn(hub, event) - self._attr_is_on = event.is_tripped + super().__init__(hub, description, event) + self._attr_is_on = event.is_tripped self._light_id = f"led{event.id}" self.current_intensity = 0 self.max_intensity = 0 diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index 8364d1317f9..34d2e746c5a 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -1,6 +1,6 @@ """Support for Axis switches.""" -from collections.abc import Callable, Iterable +from collections.abc import Iterable from dataclasses import dataclass from functools import partial from typing import Any @@ -17,21 +17,14 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import AxisEventEntity +from .entity import AxisEventDescription, AxisEventEntity from .hub import AxisHub @dataclass(frozen=True, kw_only=True) -class AxisSwitchDescription(SwitchEntityDescription): +class AxisSwitchDescription(AxisEventDescription, SwitchEntityDescription): """Axis switch entity description.""" - event_topic: EventTopic - """Event topic that provides state updates.""" - name_fn: Callable[[AxisHub, Event], str] - """Function providing the corresponding name to the event ID.""" - supported_fn: Callable[[AxisHub, Event], bool] - """Function validating if event is supported.""" - ENTITY_DESCRIPTIONS = ( AxisSwitchDescription( @@ -76,13 +69,14 @@ async def async_setup_entry( class AxisSwitch(AxisEventEntity, SwitchEntity): """Representation of a Axis switch.""" + entity_description: AxisSwitchDescription + def __init__( self, hub: AxisHub, description: AxisSwitchDescription, event: Event ) -> None: """Initialize the Axis switch.""" - super().__init__(event, hub) - self.entity_description = description - self._attr_name = description.name_fn(hub, event) or self._attr_name + super().__init__(hub, description, event) + self._attr_is_on = event.is_tripped @callback From 205c457a77a6bd9c19d87bba1e485d2a978fe8c3 Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:46:39 +0100 Subject: [PATCH 1395/1691] Add home battery storage entities for enphase_envoy (#114015) --- .../components/enphase_envoy/diagnostics.py | 3 + .../components/enphase_envoy/sensor.py | 130 +++ .../components/enphase_envoy/strings.json | 36 + tests/components/enphase_envoy/conftest.py | 76 +- .../snapshots/test_diagnostics.ambr | 984 +++++++++++++++++- .../enphase_envoy/snapshots/test_sensor.ambr | 971 +++++++++++++++++ 6 files changed, 2192 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/enphase_envoy/diagnostics.py b/homeassistant/components/enphase_envoy/diagnostics.py index 1df7606f8af..28d9690ae70 100644 --- a/homeassistant/components/enphase_envoy/diagnostics.py +++ b/homeassistant/components/enphase_envoy/diagnostics.py @@ -89,8 +89,10 @@ async def async_get_config_entry_diagnostics( "system_production_phases": envoy_data.system_production_phases, "ctmeter_production": envoy_data.ctmeter_production, "ctmeter_consumption": envoy_data.ctmeter_consumption, + "ctmeter_storage": envoy_data.ctmeter_storage, "ctmeter_production_phases": envoy_data.ctmeter_production_phases, "ctmeter_consumption_phases": envoy_data.ctmeter_consumption_phases, + "ctmeter_storage_phases": envoy_data.ctmeter_storage_phases, "dry_contact_status": envoy_data.dry_contact_status, "dry_contact_settings": envoy_data.dry_contact_settings, "inverters": envoy_data.inverters, @@ -108,6 +110,7 @@ async def async_get_config_entry_diagnostics( "ct_count": envoy.ct_meter_count, "ct_consumption_meter": envoy.consumption_meter_type, "ct_production_meter": envoy.production_meter_type, + "ct_storage_meter": envoy.storage_meter_type, } diagnostic_data: dict[str, Any] = { diff --git a/homeassistant/components/enphase_envoy/sensor.py b/homeassistant/components/enphase_envoy/sensor.py index 1b64b3a9b9f..329dc67e9e1 100644 --- a/homeassistant/components/enphase_envoy/sensor.py +++ b/homeassistant/components/enphase_envoy/sensor.py @@ -364,6 +364,87 @@ CT_PRODUCTION_PHASE_SENSORS = { for phase in range(3) } +CT_STORAGE_SENSORS = ( + EnvoyCTSensorEntityDescription( + key="lifetime_battery_discharged", + translation_key="lifetime_battery_discharged", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + state_class=SensorStateClass.TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR, + suggested_display_precision=3, + value_fn=lambda ct: ct.energy_delivered, + on_phase=None, + ), + EnvoyCTSensorEntityDescription( + key="lifetime_battery_charged", + translation_key="lifetime_battery_charged", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + state_class=SensorStateClass.TOTAL_INCREASING, + device_class=SensorDeviceClass.ENERGY, + suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR, + suggested_display_precision=3, + value_fn=lambda ct: ct.energy_received, + on_phase=None, + ), + EnvoyCTSensorEntityDescription( + key="battery_discharge", + translation_key="battery_discharge", + native_unit_of_measurement=UnitOfPower.WATT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.POWER, + suggested_unit_of_measurement=UnitOfPower.KILO_WATT, + suggested_display_precision=3, + value_fn=lambda ct: ct.active_power, + on_phase=None, + ), + EnvoyCTSensorEntityDescription( + key="storage_voltage", + translation_key="storage_ct_voltage", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + suggested_unit_of_measurement=UnitOfElectricPotential.VOLT, + suggested_display_precision=1, + entity_registry_enabled_default=False, + value_fn=lambda ct: ct.voltage, + on_phase=None, + ), + EnvoyCTSensorEntityDescription( + key="storage_ct_metering_status", + translation_key="storage_ct_metering_status", + device_class=SensorDeviceClass.ENUM, + options=list(CtMeterStatus), + entity_registry_enabled_default=False, + value_fn=lambda ct: ct.metering_status, + on_phase=None, + ), + EnvoyCTSensorEntityDescription( + key="storage_ct_status_flags", + translation_key="storage_ct_status_flags", + state_class=None, + entity_registry_enabled_default=False, + value_fn=lambda ct: 0 if ct.status_flags is None else len(ct.status_flags), + on_phase=None, + ), +) + + +CT_STORAGE_PHASE_SENSORS = { + (on_phase := PHASENAMES[phase]): [ + replace( + sensor, + key=f"{sensor.key}_l{phase + 1}", + translation_key=f"{sensor.translation_key}_phase", + entity_registry_enabled_default=False, + on_phase=on_phase, + translation_placeholders={"phase_name": f"l{phase + 1}"}, + ) + for sensor in list(CT_STORAGE_SENSORS) + ] + for phase in range(3) +} + @dataclass(frozen=True, kw_only=True) class EnvoyEnchargeSensorEntityDescription(SensorEntityDescription): @@ -560,6 +641,21 @@ async def async_setup_entry( for description in CT_PRODUCTION_PHASE_SENSORS[use_phase] if phase.measurement_type == CtType.PRODUCTION ) + # Add storage CT entities + if ctmeter := envoy_data.ctmeter_storage: + entities.extend( + EnvoyStorageCTEntity(coordinator, description) + for description in CT_STORAGE_SENSORS + if ctmeter.measurement_type == CtType.STORAGE + ) + # For each storage ct phase reported add storage ct entities + if phase_data := envoy_data.ctmeter_storage_phases: + entities.extend( + EnvoyStorageCTPhaseEntity(coordinator, description) + for use_phase, phase in phase_data.items() + for description in CT_STORAGE_PHASE_SENSORS[use_phase] + if phase.measurement_type == CtType.STORAGE + ) if envoy_data.inverters: entities.extend( @@ -758,6 +854,40 @@ class EnvoyProductionCTPhaseEntity(EnvoySystemSensorEntity): ) +class EnvoyStorageCTEntity(EnvoySystemSensorEntity): + """Envoy net storage CT entity.""" + + entity_description: EnvoyCTSensorEntityDescription + + @property + def native_value( + self, + ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None: + """Return the state of the CT sensor.""" + if (ctmeter := self.data.ctmeter_storage) is None: + return None + return self.entity_description.value_fn(ctmeter) + + +class EnvoyStorageCTPhaseEntity(EnvoySystemSensorEntity): + """Envoy net storage CT phase entity.""" + + entity_description: EnvoyCTSensorEntityDescription + + @property + def native_value( + self, + ) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None: + """Return the state of the CT phase sensor.""" + if TYPE_CHECKING: + assert self.entity_description.on_phase + if (ctmeter := self.data.ctmeter_storage_phases) is None: + return None + return self.entity_description.value_fn( + ctmeter[self.entity_description.on_phase] + ) + + class EnvoyInverterEntity(EnvoySensorBaseEntity): """Envoy inverter entity.""" diff --git a/homeassistant/components/enphase_envoy/strings.json b/homeassistant/components/enphase_envoy/strings.json index b0854f64f24..22112228a37 100644 --- a/homeassistant/components/enphase_envoy/strings.json +++ b/homeassistant/components/enphase_envoy/strings.json @@ -170,6 +170,24 @@ "production_ct_status_flags": { "name": "Meter status flags active production CT" }, + "lifetime_battery_discharged": { + "name": "Lifetime battery energy discharged" + }, + "lifetime_battery_charged": { + "name": "Lifetime battery energy charged" + }, + "battery_discharge": { + "name": "Current battery discharge" + }, + "storage_ct_voltage": { + "name": "Voltage storage CT" + }, + "storage_ct_metering_status": { + "name": "Metering status storage CT" + }, + "storage_ct_status_flags": { + "name": "Meter status flags active storage CT" + }, "lifetime_net_consumption_phase": { "name": "Lifetime net energy consumption {phase_name}" }, @@ -197,6 +215,24 @@ "production_ct_status_flags_phase": { "name": "Meter status flags active production CT {phase_name}" }, + "lifetime_battery_discharged_phase": { + "name": "Lifetime battery energy discharged {phase_name}" + }, + "lifetime_battery_charged_phase": { + "name": "Lifetime battery energy charged {phase_name}" + }, + "battery_discharge_phase": { + "name": "Current battery discharge {phase_name}" + }, + "storage_ct_voltage_phase": { + "name": "Voltage storage CT {phase_name}" + }, + "storage_ct_metering_status_phase": { + "name": "Metering status storage CT {phase_name}" + }, + "storage_ct_status_flags_phase": { + "name": "Meter status flags active storage CT {phase_name}" + }, "reserve_soc": { "name": "Reserve battery level" }, diff --git a/tests/components/enphase_envoy/conftest.py b/tests/components/enphase_envoy/conftest.py index c8ee5e2631f..91a37c7fa7f 100644 --- a/tests/components/enphase_envoy/conftest.py +++ b/tests/components/enphase_envoy/conftest.py @@ -55,15 +55,18 @@ def config_fixture(): @pytest.fixture(name="mock_envoy") -def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth): +def mock_envoy_fixture( + serial_number, + mock_authenticate, + mock_setup, + mock_auth, +): """Define a mocked Envoy fixture.""" mock_envoy = Mock(spec=Envoy) mock_envoy.serial_number = serial_number mock_envoy.firmware = "7.1.2" mock_envoy.part_number = "123456789" - mock_envoy.envoy_model = ( - "Envoy, phases: 3, phase mode: three, net-consumption CT, production CT" - ) + mock_envoy.envoy_model = "Envoy, phases: 3, phase mode: three, net-consumption CT, production CT, storage CT" mock_envoy.authenticate = mock_authenticate mock_envoy.setup = mock_setup mock_envoy.auth = mock_auth @@ -78,9 +81,10 @@ def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth): mock_envoy.phase_mode = EnvoyPhaseMode.THREE mock_envoy.phase_count = 3 mock_envoy.active_phase_count = 3 - mock_envoy.ct_meter_count = 2 + mock_envoy.ct_meter_count = 3 mock_envoy.consumption_meter_type = CtType.NET_CONSUMPTION mock_envoy.production_meter_type = CtType.PRODUCTION + mock_envoy.storage_meter_type = CtType.STORAGE mock_envoy.data = EnvoyData( system_consumption=EnvoySystemConsumption( watt_hours_last_7_days=1234, @@ -167,6 +171,21 @@ def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth): metering_status=CtMeterStatus.NORMAL, status_flags=[], ), + ctmeter_storage=EnvoyMeterData( + eid="100000030", + timestamp=1708006120, + energy_delivered=31234, + energy_received=32345, + active_power=103, + power_factor=0.23, + voltage=113, + current=0.4, + frequency=50.3, + state=CtState.ENABLED, + measurement_type=CtType.STORAGE, + metering_status=CtMeterStatus.NORMAL, + status_flags=[], + ), ctmeter_production_phases={ PhaseNames.PHASE_1: EnvoyMeterData( eid="100000011", @@ -261,6 +280,53 @@ def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth): status_flags=[], ), }, + ctmeter_storage_phases={ + PhaseNames.PHASE_1: EnvoyMeterData( + eid="100000031", + timestamp=1708006121, + energy_delivered=312341, + energy_received=323451, + active_power=22, + power_factor=0.32, + voltage=113, + current=0.4, + frequency=50.3, + state=CtState.ENABLED, + measurement_type=CtType.STORAGE, + metering_status=CtMeterStatus.NORMAL, + status_flags=[], + ), + PhaseNames.PHASE_2: EnvoyMeterData( + eid="100000032", + timestamp=1708006122, + energy_delivered=312342, + energy_received=323452, + active_power=33, + power_factor=0.23, + voltage=112, + current=0.3, + frequency=50.2, + state=CtState.ENABLED, + measurement_type=CtType.STORAGE, + metering_status=CtMeterStatus.NORMAL, + status_flags=[], + ), + PhaseNames.PHASE_3: EnvoyMeterData( + eid="100000033", + timestamp=1708006123, + energy_delivered=312343, + energy_received=323453, + active_power=53, + power_factor=0.24, + voltage=112, + current=0.3, + frequency=50.2, + state=CtState.ENABLED, + measurement_type=CtType.STORAGE, + metering_status=CtMeterStatus.NORMAL, + status_flags=[], + ), + }, inverters={ "1": EnvoyInverter( serial_number="1", diff --git a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr index 0b6e9aebecd..51cda1cc478 100644 --- a/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr +++ b/tests/components/enphase_envoy/snapshots/test_diagnostics.ambr @@ -45,7 +45,7 @@ 'labels': list([ ]), 'manufacturer': 'Enphase', - 'model': 'Envoy, phases: 3, phase mode: three, net-consumption CT, production CT', + 'model': 'Envoy, phases: 3, phase mode: three, net-consumption CT, production CT, storage CT', 'name': 'Envoy <>', 'name_by_user': None, 'serial_number': '<>', @@ -3092,6 +3092,965 @@ }), 'state': None, }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_discharged', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged', + 'unique_id': '<>_lifetime_battery_discharged', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime battery energy discharged', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_discharged', + 'state': '0.03<>', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_charged', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged', + 'unique_id': '<>_lifetime_battery_charged', + 'unit_of_measurement': 'MWh', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy <> Lifetime battery energy charged', + 'icon': 'mdi:flash', + 'state_class': 'total_increasing', + 'unit_of_measurement': 'MWh', + }), + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_charged', + 'state': '0.032345', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_battery_discharge', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge', + 'unique_id': '<>_battery_discharge', + 'unit_of_measurement': 'kW', + }), + 'state': dict({ + 'attributes': dict({ + 'device_class': 'power', + 'friendly_name': 'Envoy <> Current battery discharge', + 'icon': 'mdi:flash', + 'state_class': 'measurement', + 'unit_of_measurement': 'kW', + }), + 'entity_id': 'sensor.envoy_<>_current_battery_discharge', + 'state': '0.103', + }), + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_storage_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage', + 'unique_id': '<>_storage_voltage', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_storage_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status', + 'unique_id': '<>_storage_ct_metering_status', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_storage_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags', + 'unique_id': '<>_storage_ct_status_flags', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_discharged_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged_phase', + 'unique_id': '<>_lifetime_battery_discharged_l1', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_charged_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged_phase', + 'unique_id': '<>_lifetime_battery_charged_l1', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_battery_discharge_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge_phase', + 'unique_id': '<>_battery_discharge_l1', + 'unit_of_measurement': 'kW', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_storage_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage_phase', + 'unique_id': '<>_storage_voltage_l1', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_storage_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status_phase', + 'unique_id': '<>_storage_ct_metering_status_l1', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_storage_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags_phase', + 'unique_id': '<>_storage_ct_status_flags_l1', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_discharged_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged_phase', + 'unique_id': '<>_lifetime_battery_discharged_l2', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_charged_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged_phase', + 'unique_id': '<>_lifetime_battery_charged_l2', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_battery_discharge_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge_phase', + 'unique_id': '<>_battery_discharge_l2', + 'unit_of_measurement': 'kW', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_storage_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage_phase', + 'unique_id': '<>_storage_voltage_l2', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_storage_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status_phase', + 'unique_id': '<>_storage_ct_metering_status_l2', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_storage_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags_phase', + 'unique_id': '<>_storage_ct_status_flags_l2', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_discharged_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged_phase', + 'unique_id': '<>_lifetime_battery_discharged_l3', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'total_increasing', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_lifetime_battery_energy_charged_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'MWh', + }), + }), + 'original_device_class': 'energy', + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged_phase', + 'unique_id': '<>_lifetime_battery_charged_l3', + 'unit_of_measurement': 'MWh', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_current_battery_discharge_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'kW', + }), + }), + 'original_device_class': 'power', + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge_phase', + 'unique_id': '<>_battery_discharge_l3', + 'unit_of_measurement': 'kW', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': 'measurement', + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_voltage_storage_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': 'V', + }), + }), + 'original_device_class': 'voltage', + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage_phase', + 'unique_id': '<>_storage_voltage_l3', + 'unit_of_measurement': 'V', + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'normal', + 'not-metering', + 'check-wiring', + ]), + }), + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_metering_status_storage_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': 'enum', + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status_phase', + 'unique_id': '<>_storage_ct_metering_status_l3', + 'unit_of_measurement': None, + }), + 'state': None, + }), + dict({ + 'entity': dict({ + 'aliases': list([ + ]), + 'area_id': None, + 'capabilities': None, + 'categories': dict({ + }), + 'config_entry_id': '45a36e55aaddb2007c5f6602e0c38e72', + 'device_class': None, + 'disabled_by': 'integration', + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_<>_meter_status_flags_active_storage_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags_phase', + 'unique_id': '<>_storage_ct_status_flags_l3', + 'unit_of_measurement': None, + }), + 'state': None, + }), ]), }), dict({ @@ -3244,6 +4203,24 @@ 'repr': "EnvoyMeterData(eid='100000013', timestamp=1708006113, energy_delivered=112343, energy_received=123453, active_power=50, power_factor=0.14, voltage=111, current=0.2, frequency=50.1, state=, measurement_type=, metering_status=, status_flags=[])", }), }), + 'ctmeter_storage': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000030', timestamp=1708006120, energy_delivered=31234, energy_received=32345, active_power=103, power_factor=0.23, voltage=113, current=0.4, frequency=50.3, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'ctmeter_storage_phases': dict({ + 'L1': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000031', timestamp=1708006121, energy_delivered=312341, energy_received=323451, active_power=22, power_factor=0.32, voltage=113, current=0.4, frequency=50.3, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'L2': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000032', timestamp=1708006122, energy_delivered=312342, energy_received=323452, active_power=33, power_factor=0.23, voltage=112, current=0.3, frequency=50.2, state=, measurement_type=, metering_status=, status_flags=[])", + }), + 'L3': dict({ + '__type': "", + 'repr': "EnvoyMeterData(eid='100000033', timestamp=1708006123, energy_delivered=312343, energy_received=323453, active_power=53, power_factor=0.24, voltage=112, current=0.3, frequency=50.2, state=, measurement_type=, metering_status=, status_flags=[])", + }), + }), 'dry_contact_settings': dict({ }), 'dry_contact_status': dict({ @@ -3299,10 +4276,11 @@ 'envoy_properties': dict({ 'active_phasecount': 3, 'ct_consumption_meter': 'net-consumption', - 'ct_count': 2, + 'ct_count': 3, 'ct_production_meter': 'production', + 'ct_storage_meter': 'storage', 'envoy_firmware': '7.1.2', - 'envoy_model': 'Envoy, phases: 3, phase mode: three, net-consumption CT, production CT', + 'envoy_model': 'Envoy, phases: 3, phase mode: three, net-consumption CT, production CT, storage CT', 'part_number': '123456789', 'phase_count': 3, 'phase_mode': 'three', diff --git a/tests/components/enphase_envoy/snapshots/test_sensor.ambr b/tests/components/enphase_envoy/snapshots/test_sensor.ambr index 2dde7508ee4..4024c43c655 100644 --- a/tests/components/enphase_envoy/snapshots/test_sensor.ambr +++ b/tests/components/enphase_envoy/snapshots/test_sensor.ambr @@ -2494,6 +2494,863 @@ 'unique_id': '1234_production_ct_status_flags_l3', 'unit_of_measurement': None, }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_discharged', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged', + 'unique_id': '1234_lifetime_battery_discharged', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_charged', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged', + 'unique_id': '1234_lifetime_battery_charged', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_current_battery_discharge', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 3, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge', + 'unique_id': '1234_battery_discharge', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_voltage_storage_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage', + 'unique_id': '1234_storage_voltage', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + , + , + , + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_metering_status_storage_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status', + 'unique_id': '1234_storage_ct_metering_status', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_meter_status_flags_active_storage_ct', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags', + 'unique_id': '1234_storage_ct_status_flags', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_discharged_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged_phase', + 'unique_id': '1234_lifetime_battery_discharged_l1', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_charged_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged_phase', + 'unique_id': '1234_lifetime_battery_charged_l1', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_current_battery_discharge_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge_phase', + 'unique_id': '1234_battery_discharge_l1', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_voltage_storage_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage_phase', + 'unique_id': '1234_storage_voltage_l1', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + , + , + , + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_metering_status_storage_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status_phase', + 'unique_id': '1234_storage_ct_metering_status_l1', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_meter_status_flags_active_storage_ct_l1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT l1', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags_phase', + 'unique_id': '1234_storage_ct_status_flags_l1', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_discharged_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged_phase', + 'unique_id': '1234_lifetime_battery_discharged_l2', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_charged_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged_phase', + 'unique_id': '1234_lifetime_battery_charged_l2', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_current_battery_discharge_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge_phase', + 'unique_id': '1234_battery_discharge_l2', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_voltage_storage_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage_phase', + 'unique_id': '1234_storage_voltage_l2', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + , + , + , + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_metering_status_storage_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status_phase', + 'unique_id': '1234_storage_ct_metering_status_l2', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_meter_status_flags_active_storage_ct_l2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT l2', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags_phase', + 'unique_id': '1234_storage_ct_status_flags_l2', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_discharged_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy discharged l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_discharged_phase', + 'unique_id': '1234_lifetime_battery_discharged_l3', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_charged_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Lifetime battery energy charged l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_battery_charged_phase', + 'unique_id': '1234_lifetime_battery_charged_l3', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_current_battery_discharge_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Current battery discharge l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'battery_discharge_phase', + 'unique_id': '1234_battery_discharge_l3', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_voltage_storage_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Voltage storage CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_voltage_phase', + 'unique_id': '1234_storage_voltage_l3', + 'unit_of_measurement': , + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + , + , + , + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_metering_status_storage_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:flash', + 'original_name': 'Metering status storage CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_metering_status_phase', + 'unique_id': '1234_storage_ct_metering_status_l3', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.envoy_1234_meter_status_flags_active_storage_ct_l3', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:flash', + 'original_name': 'Meter status flags active storage CT l3', + 'platform': 'enphase_envoy', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'storage_ct_status_flags_phase', + 'unique_id': '1234_storage_ct_status_flags_l3', + 'unit_of_measurement': None, + }), EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -2560,6 +3417,32 @@ }), ]) # --- +# name: test_sensor[sensor.envoy_1234_current_battery_discharge-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Envoy 1234 Current battery discharge', + 'icon': 'mdi:flash', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.envoy_1234_current_battery_discharge', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.103', + }) +# --- +# name: test_sensor[sensor.envoy_1234_current_battery_discharge_l1-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_current_battery_discharge_l2-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_current_battery_discharge_l3-state] + None +# --- # name: test_sensor[sensor.envoy_1234_current_net_power_consumption-state] StateSnapshot({ 'attributes': ReadOnlyDict({ @@ -2998,6 +3881,58 @@ # name: test_sensor[sensor.envoy_1234_frequency_net_consumption_ct_l3-state] None # --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_charged-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy 1234 Lifetime battery energy charged', + 'icon': 'mdi:flash', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_charged', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.032345', + }) +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_charged_l1-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_charged_l2-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_charged_l3-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_discharged-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Envoy 1234 Lifetime battery energy discharged', + 'icon': 'mdi:flash', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.envoy_1234_lifetime_battery_energy_discharged', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.031234', + }) +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_discharged_l1-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_discharged_l2-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_lifetime_battery_energy_discharged_l3-state] + None +# --- # name: test_sensor[sensor.envoy_1234_lifetime_energy_consumption-state] StateSnapshot({ 'attributes': ReadOnlyDict({ @@ -3210,6 +4145,18 @@ # name: test_sensor[sensor.envoy_1234_meter_status_flags_active_production_ct_l3-state] None # --- +# name: test_sensor[sensor.envoy_1234_meter_status_flags_active_storage_ct-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_meter_status_flags_active_storage_ct_l1-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_meter_status_flags_active_storage_ct_l2-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_meter_status_flags_active_storage_ct_l3-state] + None +# --- # name: test_sensor[sensor.envoy_1234_metering_status_net_consumption_ct-state] None # --- @@ -3237,6 +4184,18 @@ # name: test_sensor[sensor.envoy_1234_metering_status_production_ct_l3-state] None # --- +# name: test_sensor[sensor.envoy_1234_metering_status_storage_ct-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_metering_status_storage_ct_l1-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_metering_status_storage_ct_l2-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_metering_status_storage_ct_l3-state] + None +# --- # name: test_sensor[sensor.envoy_1234_voltage_net_consumption_ct-state] None # --- @@ -3249,6 +4208,18 @@ # name: test_sensor[sensor.envoy_1234_voltage_net_consumption_ct_l3-state] None # --- +# name: test_sensor[sensor.envoy_1234_voltage_storage_ct-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_voltage_storage_ct_l1-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_voltage_storage_ct_l2-state] + None +# --- +# name: test_sensor[sensor.envoy_1234_voltage_storage_ct_l3-state] + None +# --- # name: test_sensor[sensor.inverter_1-state] StateSnapshot({ 'attributes': ReadOnlyDict({ From 7d9fa64a66e676f3fcc6fb82cc150ca0ddd22950 Mon Sep 17 00:00:00 2001 From: kingy444 Date: Sat, 23 Mar 2024 06:34:00 +1100 Subject: [PATCH 1396/1691] Add missing is_closed property to powerview tilt only entities (#113792) * add tilt closed * add current positioning --- .../components/hunterdouglas_powerview/cover.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index a3919aefd7c..ebf636c3ff6 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -520,6 +520,22 @@ class PowerViewShadeTiltOnly(PowerViewShadeWithTiltBase): self._attr_supported_features |= CoverEntityFeature.STOP_TILT self._max_tilt = self._shade.shade_limits.tilt_max + @property + def current_cover_position(self) -> int: + """Return the current position of cover.""" + # allows using parent class with no other alterations + return CLOSED_POSITION + + @property + def transition_steps(self) -> int: + """Return the steps to make a move.""" + return self.positions.tilt + + @property + def is_closed(self) -> bool: + """Return if the cover is closed.""" + return self.positions.tilt <= CLOSED_POSITION + class PowerViewShadeTopDown(PowerViewShadeBase): """Representation of a shade that lowers from the roof to the floor. From d3c68303b0df7753baa8041ce0fe7b8c9bde5a17 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 22 Mar 2024 22:18:22 +0100 Subject: [PATCH 1397/1691] Improve deCONZ test_non_color_light_reports_color (#114021) --- tests/components/deconz/test_light.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index a3a282f4557..5144f222484 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1322,12 +1322,10 @@ async def test_non_color_light_reports_color( await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() - assert hass.states.get("light.group").attributes[ATTR_COLOR_MODE] == ColorMode.XY - # Bug is fixed if we reach this point - # device won't have neither color temp nor color - with pytest.raises(AssertionError): - assert hass.states.get("light.group").attributes.get(ATTR_COLOR_TEMP) is None - assert hass.states.get("light.group").attributes.get(ATTR_HS_COLOR) is None + group = hass.states.get("light.group") + assert group.attributes[ATTR_COLOR_MODE] == ColorMode.XY + assert group.attributes[ATTR_HS_COLOR] == (40.571, 41.176) + assert group.attributes.get(ATTR_COLOR_TEMP) is None async def test_verify_group_supported_features( From 9e86f82a1b42c4ff82f45f19ce357b2e8b3dee01 Mon Sep 17 00:00:00 2001 From: alexsydell <3269831+alexsydell@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:45:54 -0600 Subject: [PATCH 1398/1691] Add ecobee indefinite away preset, remove unusable/broken presets (#108636) * Add ecobee indefinite away preset, remove unusable/broken presets * Revert cleanup of presets which no longer work --- homeassistant/components/ecobee/climate.py | 16 +++++-- homeassistant/components/ecobee/strings.json | 11 +++++ homeassistant/components/ecobee/util.py | 12 +++++- tests/components/ecobee/__init__.py | 12 ++++-- .../ecobee/fixtures/ecobee-data.json | 12 ++++-- tests/components/ecobee/test_climate.py | 42 +++++++++++++++++-- 6 files changed, 88 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index cfb73153f45..3f871dd20dd 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -12,7 +12,6 @@ from homeassistant.components.climate import ( ATTR_TARGET_TEMP_LOW, FAN_AUTO, FAN_ON, - PRESET_AWAY, PRESET_NONE, ClimateEntity, ClimateEntityFeature, @@ -38,7 +37,7 @@ from homeassistant.util.unit_conversion import TemperatureConverter from . import EcobeeData from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER -from .util import ecobee_date, ecobee_time +from .util import ecobee_date, ecobee_time, is_indefinite_hold ATTR_COOL_TEMP = "cool_temp" ATTR_END_DATE = "end_date" @@ -56,6 +55,7 @@ ATTR_AUTO_AWAY = "auto_away" ATTR_FOLLOW_ME = "follow_me" DEFAULT_RESUME_ALL = False +PRESET_AWAY_INDEFINITELY = "away_indefinitely" PRESET_TEMPERATURE = "temp" PRESET_VACATION = "vacation" PRESET_HOLD_NEXT_TRANSITION = "next_transition" @@ -325,6 +325,7 @@ class Thermostat(ClimateEntity): _attr_name = None _attr_has_entity_name = True _enable_turn_on_off_backwards_compatibility = False + _attr_translation_key = "ecobee" def __init__( self, data: EcobeeData, thermostat_index: int, thermostat: dict @@ -481,6 +482,11 @@ class Thermostat(ClimateEntity): continue if event["type"] == "hold": + if event["holdClimateRef"] == "away" and is_indefinite_hold( + event["startDate"], event["endDate"] + ): + return PRESET_AWAY_INDEFINITELY + if event["holdClimateRef"] in self._preset_modes: return self._preset_modes[event["holdClimateRef"]] @@ -577,7 +583,7 @@ class Thermostat(ClimateEntity): if self.preset_mode == PRESET_VACATION: self.data.ecobee.delete_vacation(self.thermostat_index, self.vacation) - if preset_mode == PRESET_AWAY: + if preset_mode == PRESET_AWAY_INDEFINITELY: self.data.ecobee.set_climate_hold( self.thermostat_index, "away", "indefinite", self.hold_hours() ) @@ -625,7 +631,9 @@ class Thermostat(ClimateEntity): @property def preset_modes(self): """Return available preset modes.""" - return list(self._preset_modes.values()) + # Return presets provided by the ecobee API, and an indefinite away + # preset which we handle separately in set_preset_mode(). + return [*self._preset_modes.values(), PRESET_AWAY_INDEFINITELY] def set_auto_temp_hold(self, heat_temp, cool_temp): """Set temperature hold in auto mode.""" diff --git a/homeassistant/components/ecobee/strings.json b/homeassistant/components/ecobee/strings.json index 484d5bf1e1e..b1d1df65417 100644 --- a/homeassistant/components/ecobee/strings.json +++ b/homeassistant/components/ecobee/strings.json @@ -20,6 +20,17 @@ } }, "entity": { + "climate": { + "ecobee": { + "state_attributes": { + "preset_mode": { + "state": { + "away_indefinitely": "Away Indefinitely" + } + } + } + } + }, "number": { "ventilator_min_type_home": { "name": "Ventilator min time home" diff --git a/homeassistant/components/ecobee/util.py b/homeassistant/components/ecobee/util.py index 1eba02f9795..e2e607c84d0 100644 --- a/homeassistant/components/ecobee/util.py +++ b/homeassistant/components/ecobee/util.py @@ -1,6 +1,6 @@ """Validation utility functions for ecobee services.""" -from datetime import datetime +from datetime import date, datetime, timedelta import voluptuous as vol @@ -23,3 +23,13 @@ def ecobee_time(time_string): "Time does not match ecobee 24-hour time format HH:MM:SS" ) from err return time_string + + +def is_indefinite_hold(start_date_string: str, end_date_string: str) -> bool: + """Determine if the given start and end dates from the ecobee API represent an indefinite hold. + + This is not documented in the API, so a rough heuristic is used where a hold over 1 year is considered indefinite. + """ + return date.fromisoformat(end_date_string) - date.fromisoformat( + start_date_string + ) > timedelta(days=365) diff --git a/tests/components/ecobee/__init__.py b/tests/components/ecobee/__init__.py index 3dba80090d4..52c6fcc6a4e 100644 --- a/tests/components/ecobee/__init__.py +++ b/tests/components/ecobee/__init__.py @@ -39,8 +39,10 @@ GENERIC_THERMOSTAT_INFO = { "running": True, "type": "hold", "holdClimateRef": "away", - "endDate": "2022-01-01 10:00:00", - "startDate": "2022-02-02 11:00:00", + "startDate": "2022-02-02", + "startTime": "11:00:00", + "endDate": "2022-01-01", + "endTime": "10:00:00", } ], "remoteSensors": [ @@ -99,8 +101,10 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = { "running": True, "type": "hold", "holdClimateRef": "away", - "endDate": "2022-01-01 10:00:00", - "startDate": "2022-02-02 11:00:00", + "startDate": "2022-02-02", + "startTime": "11:00:00", + "endDate": "2022-01-01", + "endTime": "10:00:00", } ], "remoteSensors": [ diff --git a/tests/components/ecobee/fixtures/ecobee-data.json b/tests/components/ecobee/fixtures/ecobee-data.json index 9fe19af35c6..d9406c20c3b 100644 --- a/tests/components/ecobee/fixtures/ecobee-data.json +++ b/tests/components/ecobee/fixtures/ecobee-data.json @@ -42,8 +42,10 @@ "running": true, "type": "hold", "holdClimateRef": "away", - "endDate": "2022-01-01 10:00:00", - "startDate": "2022-02-02 11:00:00" + "startDate": "2022-02-02", + "startTime": "11:00:00", + "endDate": "2022-01-01", + "endTime": "10:00:00" } ], "remoteSensors": [ @@ -110,8 +112,10 @@ "running": true, "type": "hold", "holdClimateRef": "away", - "endDate": "2022-01-01 10:00:00", - "startDate": "2022-02-02 11:00:00" + "startDate": "2022-02-02", + "startTime": "11:00:00", + "endDate": "2022-01-01", + "endTime": "10:00:00" } ], "remoteSensors": [ diff --git a/tests/components/ecobee/test_climate.py b/tests/components/ecobee/test_climate.py index 7b9a31739dc..0ec4f9cee68 100644 --- a/tests/components/ecobee/test_climate.py +++ b/tests/components/ecobee/test_climate.py @@ -9,7 +9,11 @@ import pytest from homeassistant.components import climate from homeassistant.components.climate import ClimateEntityFeature -from homeassistant.components.ecobee.climate import ECOBEE_AUX_HEAT_ONLY, Thermostat +from homeassistant.components.ecobee.climate import ( + ECOBEE_AUX_HEAT_ONLY, + PRESET_AWAY_INDEFINITELY, + Thermostat, +) import homeassistant.const as const from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_OFF from homeassistant.core import HomeAssistant @@ -31,6 +35,7 @@ def ecobee_fixture(): "climates": [ {"name": "Climate1", "climateRef": "c1"}, {"name": "Climate2", "climateRef": "c2"}, + {"name": "Away", "climateRef": "away"}, ], "currentClimateRef": "c1", }, @@ -56,9 +61,11 @@ def ecobee_fixture(): "name": "Event1", "running": True, "type": "hold", - "holdClimateRef": "away", - "endDate": "2017-01-01 10:00:00", - "startDate": "2017-02-02 11:00:00", + "holdClimateRef": "c1", + "startDate": "2017-02-02", + "startTime": "11:00:00", + "endDate": "2017-01-01", + "endTime": "10:00:00", } ], } @@ -428,3 +435,30 @@ async def test_turn_aux_heat_off(hass: HomeAssistant, mock_ecobee: MagicMock) -> ) assert mock_ecobee.set_hvac_mode.call_count == 1 assert mock_ecobee.set_hvac_mode.call_args == mock.call(0, "auto") + + +async def test_preset_indefinite_away(ecobee_fixture, thermostat) -> None: + """Test indefinite away showing correctly, and not as temporary away.""" + ecobee_fixture["program"]["currentClimateRef"] = "away" + ecobee_fixture["events"][0]["holdClimateRef"] = "away" + assert thermostat.preset_mode == "Away" + + ecobee_fixture["events"][0]["endDate"] = "2999-01-01" + assert thermostat.preset_mode == PRESET_AWAY_INDEFINITELY + + +async def test_set_preset_mode(ecobee_fixture, thermostat, data) -> None: + """Test set preset mode.""" + # Set a preset provided by ecobee. + data.reset_mock() + thermostat.set_preset_mode("Climate2") + data.ecobee.set_climate_hold.assert_has_calls( + [mock.call(1, "c2", thermostat.hold_preference(), thermostat.hold_hours())] + ) + + # Set the indefinite away preset provided by this integration. + data.reset_mock() + thermostat.set_preset_mode(PRESET_AWAY_INDEFINITELY) + data.ecobee.set_climate_hold.assert_has_calls( + [mock.call(1, "away", "indefinite", thermostat.hold_hours())] + ) From 596ddbb6eb31dadbd2ed9694815dc6699c449995 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 22 Mar 2024 23:58:27 +0100 Subject: [PATCH 1399/1691] Update pytest warnings filter (#114036) --- pyproject.toml | 68 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9919a3c8f39..a869024fb75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -456,9 +456,9 @@ filterwarnings = [ # https://github.com/michaeldavie/env_canada/blob/v0.6.1/env_canada/ec_cache.py "ignore:Inheritance class CacheClientSession from ClientSession is discouraged:DeprecationWarning:env_canada.ec_cache", # https://github.com/allenporter/ical/pull/215 - # https://github.com/allenporter/ical/blob/6.1.1/ical/util.py#L20-L22 + # https://github.com/allenporter/ical/blob/7.0.3/ical/util.py#L20-L22 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:ical.util", - # https://github.com/bachya/regenmaschine/blob/2024.01.0/regenmaschine/client.py#L51 + # https://github.com/bachya/regenmaschine/blob/2024.03.0/regenmaschine/client.py#L52 "ignore:ssl.TLSVersion.SSLv3 is deprecated:DeprecationWarning:regenmaschine.client", # -- Setuptools DeprecationWarnings @@ -469,14 +469,10 @@ filterwarnings = [ # -- tracked upstream / open PRs # https://github.com/certbot/certbot/issues/9828 - v2.8.0 "ignore:X509Extension support in pyOpenSSL is deprecated. You should use the APIs in cryptography:DeprecationWarning:acme.crypto_util", - # https://github.com/tschamm/boschshcpy/pull/39 - v0.2.88 - "ignore:pkg_resources is deprecated as an API:DeprecationWarning:boschshcpy.api", # https://github.com/influxdata/influxdb-client-python/issues/603 - v1.37.0 "ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:influxdb_client.client.write.point", # https://github.com/beetbox/mediafile/issues/67 - v0.12.0 "ignore:'imghdr' is deprecated and slated for removal in Python 3.13:DeprecationWarning:mediafile", - # https://github.com/PythonCharmers/python-future/issues/488 - v0.18.3 - "ignore:the imp module is deprecated in favour of importlib and slated for removal in Python 3.12:DeprecationWarning:future.standard_library", # https://github.com/foxel/python_ndms2_client/issues/6 - v0.1.3 # https://github.com/foxel/python_ndms2_client/pull/8 "ignore:'telnetlib' is deprecated and slated for removal in Python 3.13:DeprecationWarning:ndms2_client.connection", @@ -485,31 +481,45 @@ filterwarnings = [ "ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated:DeprecationWarning:xdist.plugin", # -- fixed, waiting for release / update - # https://github.com/mkmer/AIOAladdinConnect/commit/8851fff4473d80d70ac518db2533f0fbef63b69c - >0.1.58 + # https://github.com/mkmer/AIOAladdinConnect/commit/8851fff4473d80d70ac518db2533f0fbef63b69c - >=0.2.0 "ignore:module 'sre_constants' is deprecated:DeprecationWarning:AIOAladdinConnect", + # https://github.com/timmo001/aioazuredevops/commit/7c6a41bed45805396cd96e0696372c79b5416612 - >=1.4.0 + "ignore:\"(is|is not)\" with 'int' literal. Did you mean \"(==|!=)\"?:SyntaxWarning:.*aioazuredevops.client", # https://github.com/ludeeus/aiogithubapi/pull/208 - >=23.9.0 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:aiogithubapi.namespaces.events", # https://github.com/bachya/aiopurpleair/pull/200 - >=2023.10.0 "ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:aiopurpleair.helpers.validators", - # https://github.com/kiorky/croniter/pull/52 - >=2.0.0 - "ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:croniter.croniter", + # https://github.com/rossengeorgiev/aprs-python/commit/5e79c810355fc2df4348581779815f2981493e3f - >=0.7.1 + "ignore:invalid escape sequence:SyntaxWarning:.*aprslib.parsing.weather", + # https://github.com/tschamm/boschshcpy/pull/39 - >=0.2.89 + "ignore:pkg_resources is deprecated as an API:DeprecationWarning:boschshcpy.api", + # https://github.com/DataDog/datadogpy/pull/290 - >=0.23.0 + "ignore:invalid escape sequence:SyntaxWarning:.*datadog.dogstatsd.base", # https://github.com/fwestenberg/devialet/pull/6 - >1.4.5 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:devialet.devialet_api", # https://github.com/jaraco/jaraco.abode/commit/9e3e789efc96cddcaa15f920686bbeb79a7469e0 - update jaraco.abode to >=5.1.0 "ignore:`jaraco.functools.call_aside` is deprecated, use `jaraco.functools.invoke` instead:DeprecationWarning:jaraco.abode.helpers.timeline", + # https://github.com/majuss/lupupy/pull/15 - >0.3.2 + "ignore:\"is not\" with 'str' literal. Did you mean \"!=\"?:SyntaxWarning:.*lupupy.devices.alarm", # https://github.com/nextcord/nextcord/pull/1095 - >2.6.1 "ignore:pkg_resources is deprecated as an API:DeprecationWarning:nextcord.health_check", + # https://github.com/timmo001/ovoenergy/pull/68 - >=1.3.0 + "ignore:\"is not\" with 'int' literal. Did you mean \"!=\"?:SyntaxWarning:.*ovoenergy.ovoenergy", # https://github.com/eclipse/paho.mqtt.python/issues/653 - >=2.0.0 # https://github.com/eclipse/paho.mqtt.python/pull/665 "ignore:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:paho.mqtt.client", # https://github.com/bachya/pytile/pull/280 - >=2023.10.0 "ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:pytile.tile", - # https://github.com/rytilahti/python-miio/pull/1809 - >0.5.12 + # https://github.com/rytilahti/python-miio/pull/1809 - >=0.6.0.dev0 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.protocol", "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.miioprotocol", # https://github.com/hunterjm/python-onvif-zeep-async/pull/51 - >3.1.12 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:onvif.client", - # https://github.com/xeniter/romy/pull/1 - >0.0.7 + # https://github.com/okunishinishi/python-stringcase/commit/6a5c5bbd3fe5337862abc7fd0853a0f36e18b2e1 - >1.2.0 + "ignore:invalid escape sequence:SyntaxWarning:.*stringcase", + # https://github.com/pyudev/pyudev/pull/466 - >=0.24.0 + "ignore:invalid escape sequence:SyntaxWarning:.*pyudev.monitor", + # https://github.com/xeniter/romy/pull/1 - >=0.0.8 "ignore:with timeout\\(\\) is deprecated, use async with timeout\\(\\) instead:DeprecationWarning:romy.utils", # https://github.com/grahamwetzler/smart-meter-texas/pull/143 - >0.5.3 "ignore:ssl.OP_NO_SSL\\*/ssl.OP_NO_TLS\\* options are deprecated:DeprecationWarning:smart_meter_texas", @@ -533,20 +543,35 @@ filterwarnings = [ "ignore:setDaemon\\(\\) is deprecated, set the daemon attribute instead:DeprecationWarning:pylutron", # Wrong stacklevel # https://bugs.launchpad.net/beautifulsoup/+bug/2034451 - "ignore:It looks like you're parsing an XML document using an HTML parser:UserWarning:bs4.builder", + "ignore:It looks like you're parsing an XML document using an HTML parser:UserWarning:html.parser", # New in aiohttp - v3.9.0 "ignore:It is recommended to use web.AppKey instances for keys:UserWarning:(homeassistant|tests|aiohttp_cors)", + # - SyntaxWarnings + # https://pypi.org/project/aprslib/ - v0.7.2 - 2022-07-10 + "ignore:invalid escape sequence:SyntaxWarning:.*aprslib.parsing.common", + # https://pypi.org/project/pyblackbird/ - v0.6 - 2023-03-15 + # https://github.com/koolsb/pyblackbird/pull/9 -> closed + "ignore:invalid escape sequence:SyntaxWarning:.*pyblackbird", + # https://pypi.org/project/pybotvac/ - v0.0.24 - 2023-01-02 + # https://github.com/stianaske/pybotvac/pull/81 -> closed + "ignore:invalid escape sequence:SyntaxWarning:.*pybotvac.robot", + # https://github.com/pkkid/python-plexapi/pull/1244 - v4.15.10 -> new issue same file + "ignore:invalid escape sequence:SyntaxWarning:.*plexapi.base", + # https://pypi.org/project/pyws66i/ - v1.1 - 2022-04-05 + "ignore:invalid escape sequence:SyntaxWarning:.*pyws66i", + # https://pypi.org/project/sleekxmppfs/ - v1.4.1 - 2022-08-18 + "ignore:invalid escape sequence:SyntaxWarning:.*sleekxmppfs.thirdparty.mini_dateutil", # -- unmaintained projects, last release about 2+ years # https://pypi.org/project/agent-py/ - v0.0.23 - 2020-06-04 "ignore:with timeout\\(\\) is deprecated:DeprecationWarning:agent.a", # https://pypi.org/project/aiomodernforms/ - v0.1.8 - 2021-06-27 "ignore:with timeout\\(\\) is deprecated:DeprecationWarning:aiomodernforms.modernforms", + # https://pypi.org/project/alarmdecoder/ - v1.13.11 - 2021-06-01 + "ignore:invalid escape sequence:SyntaxWarning:.*alarmdecoder", # https://pypi.org/project/directv/ - v0.4.0 - 2020-09-12 "ignore:with timeout\\(\\) is deprecated:DeprecationWarning:directv.directv", "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:directv.models", - # https://pypi.org/project/emulated-roku/ - v0.2.1 - 2020-01-23 (archived) - "ignore:loop argument is deprecated:DeprecationWarning:emulated_roku", # https://pypi.org/project/foobot_async/ - v1.0.0 - 2020-11-24 "ignore:with timeout\\(\\) is deprecated:DeprecationWarning:foobot_async", # https://pypi.org/project/influxdb/ - v5.3.1 - 2020-11-11 (archived) @@ -561,10 +586,23 @@ filterwarnings = [ "ignore:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:lomond.session", # https://pypi.org/project/oauth2client/ - v4.1.3 - 2018-09-07 (archived) "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:oauth2client.client", + # https://pypi.org/project/opuslib/ - v3.0.1 - 2018-01-16 + "ignore:\"is not\" with 'int' literal. Did you mean \"!=\"?:SyntaxWarning:.*opuslib.api.decoder", # https://pypi.org/project/passlib/ - v1.7.4 - 2020-10-08 "ignore:'crypt' is deprecated and slated for removal in Python 3.13:DeprecationWarning:passlib.utils", + # https://pypi.org/project/plumlightpad/ - v0.0.11 - 2018-10-16 + "ignore:invalid escape sequence:SyntaxWarning:.*plumlightpad.plumdiscovery", + "ignore:\"is\" with 'int' literal. Did you mean \"==\"?:SyntaxWarning:.*plumlightpad.(lightpad|logicalload)", + # https://pypi.org/project/pure-python-adb/ - v0.3.0.dev0 - 2020-08-05 + "ignore:invalid escape sequence:SyntaxWarning:.*ppadb", + # https://pypi.org/project/pydub/ - v0.25.1 - 2021-03-10 + "ignore:invalid escape sequence:SyntaxWarning:.*pydub.utils", + # https://pypi.org/project/pyiss/ - v1.0.1 - 2016-12-19 + "ignore:\"is\" with 'int' literal. Did you mean \"==\"?:SyntaxWarning:.*pyiss", # https://pypi.org/project/PyMetEireann/ - v2021.8.0 - 2021-08-16 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:meteireann", + # https://pypi.org/project/PyPasser/ - v0.0.5 - 2021-10-21 + "ignore:invalid escape sequence:SyntaxWarning:.*pypasser.utils", # https://pypi.org/project/pyqwikswitch/ - v0.94 - 2019-08-19 "ignore:client.loop property is deprecated:DeprecationWarning:pyqwikswitch.async_", "ignore:with timeout\\(\\) is deprecated:DeprecationWarning:pyqwikswitch.async_", @@ -574,6 +612,8 @@ filterwarnings = [ "ignore:defusedxml.cElementTree is deprecated, import from defusedxml.ElementTree instead:DeprecationWarning:rxv.ssdp", # https://pypi.org/project/vilfo-api-client/ - v0.4.1 - 2021-11-06 "ignore:Function 'semver.compare' is deprecated. Deprecated since version 3.0.0:PendingDeprecationWarning:.*vilfo.client", + # https://pypi.org/project/vobject/ - v0.9.6.1 - 2018-07-18 + "ignore:invalid escape sequence:SyntaxWarning:.*vobject.base", # https://pypi.org/project/webrtcvad/ - v2.0.10 - 2017-01-08 "ignore:pkg_resources is deprecated as an API:DeprecationWarning:webrtcvad", ] From 1dbc94162d7bf94dcb140e1d8080226bc26805f5 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 23 Mar 2024 00:22:15 +0100 Subject: [PATCH 1400/1691] Update byte string formatting (2) (#114039) --- homeassistant/components/mqtt/util.py | 4 +-- tests/components/rfxtrx/test_cover.py | 30 +++++++++---------- tests/components/roku/test_select.py | 2 +- .../xiaomi_ble/test_device_trigger.py | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/mqtt/util.py b/homeassistant/components/mqtt/util.py index 43efc3b8928..a4635d1e4cc 100644 --- a/homeassistant/components/mqtt/util.py +++ b/homeassistant/components/mqtt/util.py @@ -136,9 +136,9 @@ def valid_topic(topic: Any) -> str: ) if "\0" in validated_topic: raise vol.Invalid("MQTT topic name/filter must not contain null character.") - if any(char <= "\u001F" for char in validated_topic): + if any(char <= "\u001f" for char in validated_topic): raise vol.Invalid("MQTT topic name/filter must not contain control characters.") - if any("\u007f" <= char <= "\u009F" for char in validated_topic): + if any("\u007f" <= char <= "\u009f" for char in validated_topic): raise vol.Invalid("MQTT topic name/filter must not contain control characters.") if any("\ufdd0" <= char <= "\ufdef" for char in validated_topic): raise vol.Invalid("MQTT topic name/filter must not contain non-characters.") diff --git a/tests/components/rfxtrx/test_cover.py b/tests/components/rfxtrx/test_cover.py index c5a5d29e693..211209f79e5 100644 --- a/tests/components/rfxtrx/test_cover.py +++ b/tests/components/rfxtrx/test_cover.py @@ -204,9 +204,9 @@ async def test_rfy_cover(hass: HomeAssistant, rfxtrx) -> None: ) assert rfxtrx.transport.send.mock_calls == [ - call(bytearray(b"\x0C\x1a\x00\x00\x01\x02\x03\x01\x00\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x01\x01\x02\x03\x01\x01\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x02\x01\x02\x03\x01\x03\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x00\x01\x02\x03\x01\x00\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x01\x01\x02\x03\x01\x01\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x02\x01\x02\x03\x01\x03\x00\x00\x00\x00")), ] # Test a blind with venetian mode set to US @@ -257,12 +257,12 @@ async def test_rfy_cover(hass: HomeAssistant, rfxtrx) -> None: ) assert rfxtrx.transport.send.mock_calls == [ - call(bytearray(b"\x0C\x1a\x00\x00\x01\x02\x03\x02\x00\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x01\x01\x02\x03\x02\x0F\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x02\x01\x02\x03\x02\x10\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x03\x01\x02\x03\x02\x11\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x04\x01\x02\x03\x02\x12\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x00\x01\x02\x03\x02\x00\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x00\x01\x02\x03\x02\x00\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x01\x01\x02\x03\x02\x0f\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x02\x01\x02\x03\x02\x10\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x03\x01\x02\x03\x02\x11\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x04\x01\x02\x03\x02\x12\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x00\x01\x02\x03\x02\x00\x00\x00\x00\x00")), ] # Test a blind with venetian mode set to EU @@ -313,10 +313,10 @@ async def test_rfy_cover(hass: HomeAssistant, rfxtrx) -> None: ) assert rfxtrx.transport.send.mock_calls == [ - call(bytearray(b"\x0C\x1a\x00\x00\x01\x02\x03\x03\x00\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x01\x01\x02\x03\x03\x11\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x02\x01\x02\x03\x03\x12\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x03\x01\x02\x03\x03\x0F\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x04\x01\x02\x03\x03\x10\x00\x00\x00\x00")), - call(bytearray(b"\x0C\x1a\x00\x00\x01\x02\x03\x03\x00\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x00\x01\x02\x03\x03\x00\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x01\x01\x02\x03\x03\x11\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x02\x01\x02\x03\x03\x12\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x03\x01\x02\x03\x03\x0f\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x04\x01\x02\x03\x03\x10\x00\x00\x00\x00")), + call(bytearray(b"\x0c\x1a\x00\x00\x01\x02\x03\x03\x00\x00\x00\x00\x00")), ] diff --git a/tests/components/roku/test_select.py b/tests/components/roku/test_select.py index a226ae7c191..fa93dfd4b8d 100644 --- a/tests/components/roku/test_select.py +++ b/tests/components/roku/test_select.py @@ -54,7 +54,7 @@ async def test_application_state( "Home", "Amazon Video on Demand", "Free FrameChannel Service", - "MLB.TV" + "\u00AE", + "MLB.TV" + "\u00ae", "Mediafly", "Netflix", "Pandora", diff --git a/tests/components/xiaomi_ble/test_device_trigger.py b/tests/components/xiaomi_ble/test_device_trigger.py index 49619af618e..714f061ecd6 100644 --- a/tests/components/xiaomi_ble/test_device_trigger.py +++ b/tests/components/xiaomi_ble/test_device_trigger.py @@ -522,7 +522,7 @@ async def test_if_fires_on_motion_detected(hass: HomeAssistant, calls) -> None: # Creates the device in the registry inject_bluetooth_service_info_bleak( hass, - make_advertisement(mac, b"@0\xdd\x03$\x0A\x10\x01\x64"), + make_advertisement(mac, b"@0\xdd\x03$\x0a\x10\x01\x64"), ) # wait for the device being created From efc54971d39a2439834f2c1647b4ddb625292f7f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 23 Mar 2024 00:27:57 +0100 Subject: [PATCH 1401/1691] Update empty line formatting after module docstring (#114040) --- homeassistant/components/alarm_control_panel/group.py | 1 - homeassistant/components/config/category_registry.py | 1 + homeassistant/components/control4/media_player.py | 1 + homeassistant/components/downloader/config_flow.py | 1 + homeassistant/components/downloader/const.py | 1 + homeassistant/components/esphome/date.py | 1 + homeassistant/components/esphome/time.py | 1 + homeassistant/components/fyta/__init__.py | 1 + homeassistant/components/fyta/config_flow.py | 1 + homeassistant/components/fyta/const.py | 1 + homeassistant/components/fyta/entity.py | 1 + homeassistant/components/fyta/sensor.py | 1 + homeassistant/components/homeworks/binary_sensor.py | 1 + homeassistant/components/husqvarna_automower/diagnostics.py | 1 + homeassistant/components/ipma/diagnostics.py | 1 + homeassistant/components/lupusec/config_flow.py | 2 +- homeassistant/components/map/__init__.py | 1 + homeassistant/components/microbees/binary_sensor.py | 1 + homeassistant/components/microbees/cover.py | 1 + homeassistant/components/minecraft_server/binary_sensor.py | 1 - homeassistant/components/ring/entity.py | 1 + homeassistant/components/unifi/hub/entity_loader.py | 1 + homeassistant/components/webmin/diagnostics.py | 1 + homeassistant/helpers/category_registry.py | 1 + tests/components/anova/__init__.py | 1 + tests/components/button/conftest.py | 1 + tests/components/button/test_init.py | 1 + tests/components/config/test_category_registry.py | 1 + tests/components/downloader/test_config_flow.py | 1 + tests/components/event/conftest.py | 1 + tests/components/flux_led/test_number.py | 1 - tests/components/fyta/conftest.py | 1 + tests/components/fyta/test_config_flow.py | 1 + tests/components/homeworks/test_binary_sensor.py | 1 + tests/components/homeworks/test_button.py | 1 + tests/components/homeworks/test_init.py | 1 + tests/components/homeworks/test_light.py | 1 + tests/components/image_processing/test_init.py | 1 + tests/components/light/common.py | 1 + tests/components/light/test_init.py | 1 + tests/components/melissa/__init__.py | 1 + tests/components/melissa/test_init.py | 1 - tests/components/ring/test_camera.py | 1 + tests/components/ring/test_light.py | 1 + tests/components/ring/test_siren.py | 1 + tests/components/ring/test_switch.py | 1 + tests/components/rova/test_config_flow.py | 1 + tests/components/teslemetry/test_sensor.py | 1 + tests/components/tplink_omada/test_switch.py | 1 + tests/components/webmin/test_diagnostics.py | 1 + tests/components/whirlpool/__init__.py | 1 + tests/helpers/test_category_registry.py | 1 + .../custom_components/test_package_loaded_loop/__init__.py | 1 + .../custom_components/test_package_loaded_loop/const.py | 1 + 54 files changed, 50 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/group.py b/homeassistant/components/alarm_control_panel/group.py index 3ef1ff96cda..e0806822cef 100644 --- a/homeassistant/components/alarm_control_panel/group.py +++ b/homeassistant/components/alarm_control_panel/group.py @@ -1,6 +1,5 @@ """Describe group states.""" - from typing import TYPE_CHECKING from homeassistant.const import ( diff --git a/homeassistant/components/config/category_registry.py b/homeassistant/components/config/category_registry.py index 045243a355b..5fc705a5844 100644 --- a/homeassistant/components/config/category_registry.py +++ b/homeassistant/components/config/category_registry.py @@ -1,4 +1,5 @@ """Websocket API to interact with the category registry.""" + from typing import Any import voluptuous as vol diff --git a/homeassistant/components/control4/media_player.py b/homeassistant/components/control4/media_player.py index c65004260af..99d8c27face 100644 --- a/homeassistant/components/control4/media_player.py +++ b/homeassistant/components/control4/media_player.py @@ -1,4 +1,5 @@ """Platform for Control4 Rooms Media Players.""" + from __future__ import annotations from dataclasses import dataclass diff --git a/homeassistant/components/downloader/config_flow.py b/homeassistant/components/downloader/config_flow.py index 70557d30e19..69393c04985 100644 --- a/homeassistant/components/downloader/config_flow.py +++ b/homeassistant/components/downloader/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Downloader integration.""" + from __future__ import annotations import os diff --git a/homeassistant/components/downloader/const.py b/homeassistant/components/downloader/const.py index 047dff2b0cc..14160e4cd5d 100644 --- a/homeassistant/components/downloader/const.py +++ b/homeassistant/components/downloader/const.py @@ -1,4 +1,5 @@ """Constants for the Downloader component.""" + import logging _LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/esphome/date.py b/homeassistant/components/esphome/date.py index dbfc7779824..9998eea1a5d 100644 --- a/homeassistant/components/esphome/date.py +++ b/homeassistant/components/esphome/date.py @@ -1,4 +1,5 @@ """Support for esphome dates.""" + from __future__ import annotations from datetime import date diff --git a/homeassistant/components/esphome/time.py b/homeassistant/components/esphome/time.py index b68decd4252..de985a1e1d6 100644 --- a/homeassistant/components/esphome/time.py +++ b/homeassistant/components/esphome/time.py @@ -1,4 +1,5 @@ """Support for esphome times.""" + from __future__ import annotations from datetime import time diff --git a/homeassistant/components/fyta/__init__.py b/homeassistant/components/fyta/__init__.py index 34399ce23ee..febd5b94469 100644 --- a/homeassistant/components/fyta/__init__.py +++ b/homeassistant/components/fyta/__init__.py @@ -1,4 +1,5 @@ """Initialization of FYTA integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fyta/config_flow.py b/homeassistant/components/fyta/config_flow.py index 698686a41a7..67e46f8125e 100644 --- a/homeassistant/components/fyta/config_flow.py +++ b/homeassistant/components/fyta/config_flow.py @@ -1,4 +1,5 @@ """Config flow for FYTA integration.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/fyta/const.py b/homeassistant/components/fyta/const.py index 86c3121089d..f99735dc6fa 100644 --- a/homeassistant/components/fyta/const.py +++ b/homeassistant/components/fyta/const.py @@ -1,2 +1,3 @@ """Const for fyta integration.""" + DOMAIN = "fyta" diff --git a/homeassistant/components/fyta/entity.py b/homeassistant/components/fyta/entity.py index a0bcf0a0084..681a50f4cf5 100644 --- a/homeassistant/components/fyta/entity.py +++ b/homeassistant/components/fyta/entity.py @@ -1,4 +1,5 @@ """Entities for FYTA integration.""" + from typing import Any from homeassistant.components.sensor import SensorEntityDescription diff --git a/homeassistant/components/fyta/sensor.py b/homeassistant/components/fyta/sensor.py index ae1c1bec272..0643c69981e 100644 --- a/homeassistant/components/fyta/sensor.py +++ b/homeassistant/components/fyta/sensor.py @@ -1,4 +1,5 @@ """Summary data from Fyta.""" + from __future__ import annotations from collections.abc import Callable diff --git a/homeassistant/components/homeworks/binary_sensor.py b/homeassistant/components/homeworks/binary_sensor.py index a4d315b6821..9773411d26d 100644 --- a/homeassistant/components/homeworks/binary_sensor.py +++ b/homeassistant/components/homeworks/binary_sensor.py @@ -1,4 +1,5 @@ """Support for Lutron Homeworks binary sensors.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/husqvarna_automower/diagnostics.py b/homeassistant/components/husqvarna_automower/diagnostics.py index 7a715292a07..f5677d4cb4b 100644 --- a/homeassistant/components/husqvarna_automower/diagnostics.py +++ b/homeassistant/components/husqvarna_automower/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Husqvarna Automower.""" + from __future__ import annotations import logging diff --git a/homeassistant/components/ipma/diagnostics.py b/homeassistant/components/ipma/diagnostics.py index 30c22d6481a..948b69ee3e5 100644 --- a/homeassistant/components/ipma/diagnostics.py +++ b/homeassistant/components/ipma/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for IPMA.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/lupusec/config_flow.py b/homeassistant/components/lupusec/config_flow.py index 4129c33eabf..c3fe7295266 100644 --- a/homeassistant/components/lupusec/config_flow.py +++ b/homeassistant/components/lupusec/config_flow.py @@ -1,4 +1,4 @@ -""""Config flow for Lupusec integration.""" +"""Config flow for Lupusec integration.""" from json import JSONDecodeError import logging diff --git a/homeassistant/components/map/__init__.py b/homeassistant/components/map/__init__.py index beddde5df1d..25095e92b93 100644 --- a/homeassistant/components/map/__init__.py +++ b/homeassistant/components/map/__init__.py @@ -1,4 +1,5 @@ """Support for showing device locations.""" + from homeassistant.components import onboarding from homeassistant.components.lovelace import _create_map_dashboard from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant diff --git a/homeassistant/components/microbees/binary_sensor.py b/homeassistant/components/microbees/binary_sensor.py index 929aae52490..551f68ba354 100644 --- a/homeassistant/components/microbees/binary_sensor.py +++ b/homeassistant/components/microbees/binary_sensor.py @@ -1,4 +1,5 @@ """BinarySensor integration microBees.""" + from microBeesPy import Sensor from homeassistant.components.binary_sensor import ( diff --git a/homeassistant/components/microbees/cover.py b/homeassistant/components/microbees/cover.py index 5caf57403de..bdf6e815af1 100644 --- a/homeassistant/components/microbees/cover.py +++ b/homeassistant/components/microbees/cover.py @@ -1,4 +1,5 @@ """Cover integration microBees.""" + from typing import Any from microBeesPy import Actuator diff --git a/homeassistant/components/minecraft_server/binary_sensor.py b/homeassistant/components/minecraft_server/binary_sensor.py index de514754ef8..60f2e00da0e 100644 --- a/homeassistant/components/minecraft_server/binary_sensor.py +++ b/homeassistant/components/minecraft_server/binary_sensor.py @@ -1,6 +1,5 @@ """The Minecraft Server binary sensor platform.""" - from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, diff --git a/homeassistant/components/ring/entity.py b/homeassistant/components/ring/entity.py index 0c738090218..fb617ecd7d1 100644 --- a/homeassistant/components/ring/entity.py +++ b/homeassistant/components/ring/entity.py @@ -1,4 +1,5 @@ """Base class for Ring entity.""" + from collections.abc import Callable from typing import Any, Concatenate, ParamSpec, TypeVar diff --git a/homeassistant/components/unifi/hub/entity_loader.py b/homeassistant/components/unifi/hub/entity_loader.py index 2ed2c09d049..30b5ba6e686 100644 --- a/homeassistant/components/unifi/hub/entity_loader.py +++ b/homeassistant/components/unifi/hub/entity_loader.py @@ -3,6 +3,7 @@ Central point to load entities for the different platforms. Make sure expected clients are available for platforms. """ + from __future__ import annotations import asyncio diff --git a/homeassistant/components/webmin/diagnostics.py b/homeassistant/components/webmin/diagnostics.py index 86e2c9eef62..390db73814a 100644 --- a/homeassistant/components/webmin/diagnostics.py +++ b/homeassistant/components/webmin/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for Webmin.""" + from typing import Any from homeassistant.components.diagnostics import async_redact_data diff --git a/homeassistant/helpers/category_registry.py b/homeassistant/helpers/category_registry.py index 686ddaa69c8..ee0c8c1bb88 100644 --- a/homeassistant/helpers/category_registry.py +++ b/homeassistant/helpers/category_registry.py @@ -1,4 +1,5 @@ """Provide a way to categorize things within a defined scope.""" + from __future__ import annotations from collections.abc import Iterable diff --git a/tests/components/anova/__init__.py b/tests/components/anova/__init__.py index aa58ee5bbb5..fb4c578ba1b 100644 --- a/tests/components/anova/__init__.py +++ b/tests/components/anova/__init__.py @@ -1,4 +1,5 @@ """Tests for the Anova integration.""" + from __future__ import annotations from unittest.mock import patch diff --git a/tests/components/button/conftest.py b/tests/components/button/conftest.py index fa7b153def5..75d5509efc9 100644 --- a/tests/components/button/conftest.py +++ b/tests/components/button/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the button entity component tests.""" + import logging import pytest diff --git a/tests/components/button/test_init.py b/tests/components/button/test_init.py index caddfe93814..0641bbe29dc 100644 --- a/tests/components/button/test_init.py +++ b/tests/components/button/test_init.py @@ -1,4 +1,5 @@ """The tests for the Button component.""" + from collections.abc import Generator from datetime import timedelta from unittest.mock import MagicMock diff --git a/tests/components/config/test_category_registry.py b/tests/components/config/test_category_registry.py index fd762d80c3b..b4d171535b6 100644 --- a/tests/components/config/test_category_registry.py +++ b/tests/components/config/test_category_registry.py @@ -1,4 +1,5 @@ """Test category registry API.""" + import pytest from homeassistant.components.config import category_registry diff --git a/tests/components/downloader/test_config_flow.py b/tests/components/downloader/test_config_flow.py index 597ecb333bf..53c1be69830 100644 --- a/tests/components/downloader/test_config_flow.py +++ b/tests/components/downloader/test_config_flow.py @@ -1,4 +1,5 @@ """Test the Downloader config flow.""" + from unittest.mock import patch import pytest diff --git a/tests/components/event/conftest.py b/tests/components/event/conftest.py index 38c59af117b..2b4a2dbba45 100644 --- a/tests/components/event/conftest.py +++ b/tests/components/event/conftest.py @@ -1,4 +1,5 @@ """Fixtures for the event entity component tests.""" + import logging import pytest diff --git a/tests/components/flux_led/test_number.py b/tests/components/flux_led/test_number.py index 8858e7c7c93..455bad05029 100644 --- a/tests/components/flux_led/test_number.py +++ b/tests/components/flux_led/test_number.py @@ -1,6 +1,5 @@ """Tests for the flux_led number platform.""" - from datetime import timedelta from flux_led.const import COLOR_MODE_RGB as FLUX_COLOR_MODE_RGB diff --git a/tests/components/fyta/conftest.py b/tests/components/fyta/conftest.py index 40ab4925a47..e35012a02e8 100644 --- a/tests/components/fyta/conftest.py +++ b/tests/components/fyta/conftest.py @@ -1,4 +1,5 @@ """Test helpers.""" + from collections.abc import Generator from unittest.mock import AsyncMock, patch diff --git a/tests/components/fyta/test_config_flow.py b/tests/components/fyta/test_config_flow.py index 1fdb9b6109c..b21be5abb90 100644 --- a/tests/components/fyta/test_config_flow.py +++ b/tests/components/fyta/test_config_flow.py @@ -1,4 +1,5 @@ """Test the fyta config flow.""" + from datetime import datetime from unittest.mock import AsyncMock diff --git a/tests/components/homeworks/test_binary_sensor.py b/tests/components/homeworks/test_binary_sensor.py index cc9e580c23c..0b21ae3b773 100644 --- a/tests/components/homeworks/test_binary_sensor.py +++ b/tests/components/homeworks/test_binary_sensor.py @@ -1,4 +1,5 @@ """Tests for the Lutron Homeworks Series 4 and 8 binary sensor.""" + from unittest.mock import ANY, MagicMock from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/homeworks/test_button.py b/tests/components/homeworks/test_button.py index 6f0250d980a..1bf4f93e49a 100644 --- a/tests/components/homeworks/test_button.py +++ b/tests/components/homeworks/test_button.py @@ -1,4 +1,5 @@ """Tests for the Lutron Homeworks Series 4 and 8 button.""" + from unittest.mock import MagicMock from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS diff --git a/tests/components/homeworks/test_init.py b/tests/components/homeworks/test_init.py index a3eb864c9da..566e0b4beb4 100644 --- a/tests/components/homeworks/test_init.py +++ b/tests/components/homeworks/test_init.py @@ -1,4 +1,5 @@ """Tests for the Lutron Homeworks Series 4 and 8 integration.""" + from unittest.mock import ANY, MagicMock from pyhomeworks.pyhomeworks import HW_BUTTON_PRESSED, HW_BUTTON_RELEASED diff --git a/tests/components/homeworks/test_light.py b/tests/components/homeworks/test_light.py index dda88f2a784..a5d94f736d5 100644 --- a/tests/components/homeworks/test_light.py +++ b/tests/components/homeworks/test_light.py @@ -1,4 +1,5 @@ """Tests for the Lutron Homeworks Series 4 and 8 light.""" + from unittest.mock import ANY, MagicMock from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index cf92047b49a..6415e4e2a4e 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -1,4 +1,5 @@ """The tests for the image_processing component.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/light/common.py b/tests/components/light/common.py index 519084d9c34..62b1e199d5d 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -3,6 +3,7 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ + from collections.abc import Callable from homeassistant.components.light import ( diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index b66ae5819c5..2ebf8af52bb 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1,4 +1,5 @@ """The tests for the Light component.""" + from unittest.mock import MagicMock, mock_open, patch import pytest diff --git a/tests/components/melissa/__init__.py b/tests/components/melissa/__init__.py index 24b278b3c3a..870727eea8f 100644 --- a/tests/components/melissa/__init__.py +++ b/tests/components/melissa/__init__.py @@ -1,4 +1,5 @@ """Tests for the melissa component.""" + from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component diff --git a/tests/components/melissa/test_init.py b/tests/components/melissa/test_init.py index 6ad86ba4595..d809f42e409 100644 --- a/tests/components/melissa/test_init.py +++ b/tests/components/melissa/test_init.py @@ -1,6 +1,5 @@ """The test for the Melissa Climate component.""" - from homeassistant.core import HomeAssistant from tests.components.melissa import setup_integration diff --git a/tests/components/ring/test_camera.py b/tests/components/ring/test_camera.py index 5fd05dd8f28..de61d7a1452 100644 --- a/tests/components/ring/test_camera.py +++ b/tests/components/ring/test_camera.py @@ -1,4 +1,5 @@ """The tests for the Ring switch platform.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index 9680c21abfc..621c0b8f1d0 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -1,4 +1,5 @@ """The tests for the Ring light platform.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/ring/test_siren.py b/tests/components/ring/test_siren.py index 132c75996a9..b3d46c601de 100644 --- a/tests/components/ring/test_siren.py +++ b/tests/components/ring/test_siren.py @@ -1,4 +1,5 @@ """The tests for the Ring button platform.""" + from unittest.mock import patch import pytest diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index e8e860cc162..e4ddd7cd855 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -1,4 +1,5 @@ """The tests for the Ring switch platform.""" + from unittest.mock import PropertyMock, patch import pytest diff --git a/tests/components/rova/test_config_flow.py b/tests/components/rova/test_config_flow.py index b5d590f7891..b78e798c120 100644 --- a/tests/components/rova/test_config_flow.py +++ b/tests/components/rova/test_config_flow.py @@ -1,4 +1,5 @@ """Tests for the Rova config flow.""" + from unittest.mock import MagicMock import pytest diff --git a/tests/components/teslemetry/test_sensor.py b/tests/components/teslemetry/test_sensor.py index 59b3cf08bce..be541da6728 100644 --- a/tests/components/teslemetry/test_sensor.py +++ b/tests/components/teslemetry/test_sensor.py @@ -1,4 +1,5 @@ """Test the Teslemetry sensor platform.""" + from datetime import timedelta from freezegun.api import FrozenDateTimeFactory diff --git a/tests/components/tplink_omada/test_switch.py b/tests/components/tplink_omada/test_switch.py index aa5cd5d33c4..bcc727da06e 100644 --- a/tests/components/tplink_omada/test_switch.py +++ b/tests/components/tplink_omada/test_switch.py @@ -1,4 +1,5 @@ """Tests for TP-Link Omada switch entities.""" + from datetime import timedelta from typing import Any from unittest.mock import MagicMock diff --git a/tests/components/webmin/test_diagnostics.py b/tests/components/webmin/test_diagnostics.py index 33a349878d1..5f1df44f4a8 100644 --- a/tests/components/webmin/test_diagnostics.py +++ b/tests/components/webmin/test_diagnostics.py @@ -1,4 +1,5 @@ """Tests for the diagnostics data provided by the Webmin integration.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/whirlpool/__init__.py b/tests/components/whirlpool/__init__.py index ac52031a0fc..ca00975941a 100644 --- a/tests/components/whirlpool/__init__.py +++ b/tests/components/whirlpool/__init__.py @@ -1,4 +1,5 @@ """Tests for the Whirlpool Sixth Sense integration.""" + from homeassistant.components.whirlpool.const import CONF_BRAND, DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant diff --git a/tests/helpers/test_category_registry.py b/tests/helpers/test_category_registry.py index d204ec21e39..a6a36940a68 100644 --- a/tests/helpers/test_category_registry.py +++ b/tests/helpers/test_category_registry.py @@ -1,4 +1,5 @@ """Tests for the category registry.""" + import re from typing import Any diff --git a/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py b/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py index c4d88fd17d7..b9080a2048a 100644 --- a/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py +++ b/tests/testing_config/custom_components/test_package_loaded_loop/__init__.py @@ -1,4 +1,5 @@ """Provide a mock package component.""" + from .const import TEST # noqa: F401 diff --git a/tests/testing_config/custom_components/test_package_loaded_loop/const.py b/tests/testing_config/custom_components/test_package_loaded_loop/const.py index 7e13e04cb47..e55504e7f4f 100644 --- a/tests/testing_config/custom_components/test_package_loaded_loop/const.py +++ b/tests/testing_config/custom_components/test_package_loaded_loop/const.py @@ -1,2 +1,3 @@ """Constants for test_package custom component.""" + TEST = 5 From 26b6bd83fcf0aaa7b9df98442a7711dfb93de233 Mon Sep 17 00:00:00 2001 From: kingy444 Date: Sat, 23 Mar 2024 10:38:33 +1100 Subject: [PATCH 1402/1691] Move powerview timeout logic to the upstream api (#113984) --- .../hunterdouglas_powerview/__init__.py | 27 +++++++++---------- .../hunterdouglas_powerview/config_flow.py | 8 +++--- .../hunterdouglas_powerview/coordinator.py | 20 +++++++------- .../hunterdouglas_powerview/cover.py | 13 +++------ .../hunterdouglas_powerview/manifest.json | 2 +- .../hunterdouglas_powerview/sensor.py | 4 +-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../fixtures/gen2/shades.json | 6 ++--- .../fixtures/gen3/home/shades.json | 26 ++++++++++++++++++ 10 files changed, 61 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 35b28e312bc..d1ac0950c58 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -1,6 +1,4 @@ """The Hunter Douglas PowerView integration.""" - -import asyncio import logging from aiopvapi.helpers.aiorequest import AioRequest @@ -52,11 +50,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hub_address, loop=hass.loop, websession=websession, api_version=api_version ) + # default 15 second timeout for each call in upstream try: - async with asyncio.timeout(10): - hub = Hub(pv_request) - await hub.query_firmware() - device_info = await async_get_device_info(hub) + hub = Hub(pv_request) + await hub.query_firmware() + device_info = await async_get_device_info(hub) except HUB_EXCEPTIONS as err: raise ConfigEntryNotReady( f"Connection error to PowerView hub {hub_address}: {err}" @@ -75,15 +73,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return False try: - async with asyncio.timeout(10): - rooms = Rooms(pv_request) - room_data: PowerviewData = await rooms.get_rooms() - async with asyncio.timeout(10): - scenes = Scenes(pv_request) - scene_data: PowerviewData = await scenes.get_scenes() - async with asyncio.timeout(10): - shades = Shades(pv_request) - shade_data: PowerviewData = await shades.get_shades() + rooms = Rooms(pv_request) + room_data: PowerviewData = await rooms.get_rooms() + + scenes = Scenes(pv_request) + scene_data: PowerviewData = await scenes.get_scenes() + + shades = Shades(pv_request) + shade_data: PowerviewData = await shades.get_shades() except HUB_EXCEPTIONS as err: raise ConfigEntryNotReady( f"Connection error to PowerView hub {hub_address}: {err}" diff --git a/homeassistant/components/hunterdouglas_powerview/config_flow.py b/homeassistant/components/hunterdouglas_powerview/config_flow.py index 2b511144380..7753f4ba94b 100644 --- a/homeassistant/components/hunterdouglas_powerview/config_flow.py +++ b/homeassistant/components/hunterdouglas_powerview/config_flow.py @@ -2,7 +2,6 @@ from __future__ import annotations -import asyncio import logging from typing import TYPE_CHECKING, Any @@ -38,10 +37,9 @@ async def validate_input(hass: HomeAssistant, hub_address: str) -> dict[str, str pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession) try: - async with asyncio.timeout(10): - hub = Hub(pv_request) - await hub.query_firmware() - device_info = await async_get_device_info(hub) + hub = Hub(pv_request) + await hub.query_firmware() + device_info = await async_get_device_info(hub) except HUB_EXCEPTIONS as err: raise CannotConnect from err diff --git a/homeassistant/components/hunterdouglas_powerview/coordinator.py b/homeassistant/components/hunterdouglas_powerview/coordinator.py index 2ffb463dc92..1ea47ca9d1f 100644 --- a/homeassistant/components/hunterdouglas_powerview/coordinator.py +++ b/homeassistant/components/hunterdouglas_powerview/coordinator.py @@ -2,7 +2,6 @@ from __future__ import annotations -import asyncio from datetime import timedelta import logging @@ -36,16 +35,15 @@ class PowerviewShadeUpdateCoordinator(DataUpdateCoordinator[PowerviewShadeData]) async def _async_update_data(self) -> PowerviewShadeData: """Fetch data from shade endpoint.""" - async with asyncio.timeout(10): - try: - shade_entries = await self.shades.get_shades() - except PvApiMaintenance as error: - # hub is undergoing maintenance, pause polling - raise UpdateFailed(error) from error - except HUB_EXCEPTIONS as error: - raise UpdateFailed( - f"Powerview Hub {self.hub.hub_address} did not return any data: {error}" - ) from error + try: + shade_entries = await self.shades.get_shades() + except PvApiMaintenance as error: + # hub is undergoing maintenance, pause polling + raise UpdateFailed(error) from error + except HUB_EXCEPTIONS as error: + raise UpdateFailed( + f"Powerview Hub {self.hub.hub_address} did not return any data: {error}" + ) from error if not shade_entries: raise UpdateFailed("No new shade data was returned") diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index ebf636c3ff6..7ff2fd55958 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -2,9 +2,7 @@ from __future__ import annotations -import asyncio from collections.abc import Callable, Iterable -from contextlib import suppress from dataclasses import replace from datetime import datetime, timedelta import logging @@ -68,11 +66,8 @@ async def async_setup_entry( """ for shade in pv_entry.shade_data.values(): - with suppress(TimeoutError): - # hold off to avoid spamming the hub - async with asyncio.timeout(10): - _LOGGER.debug("Initial refresh of shade: %s", shade.name) - await shade.refresh() + _LOGGER.debug("Initial refresh of shade: %s", shade.name) + await shade.refresh(suppress_timeout=True) # default 15 second timeout entities: list[ShadeEntity] = [] for shade in pv_entry.shade_data.values(): @@ -324,9 +319,7 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity): # error if are already have one in flight return # suppress timeouts caused by hub nightly reboot - with suppress(TimeoutError): - async with asyncio.timeout(10): - await self._shade.refresh() + await self._shade.refresh(suppress_timeout=True) # default 15 second timeout _LOGGER.debug("Process update %s: %s", self.name, self._shade.current_position) self._async_update_shade_data(self._shade.current_position) diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json index 276b10f5e8d..4120c55a7a7 100644 --- a/homeassistant/components/hunterdouglas_powerview/manifest.json +++ b/homeassistant/components/hunterdouglas_powerview/manifest.json @@ -18,6 +18,6 @@ }, "iot_class": "local_polling", "loggers": ["aiopvapi"], - "requirements": ["aiopvapi==3.0.2"], + "requirements": ["aiopvapi==3.1.1"], "zeroconf": ["_powerview._tcp.local.", "_powerview-g3._tcp.local."] } diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py index c1371a1e848..bca87189e56 100644 --- a/homeassistant/components/hunterdouglas_powerview/sensor.py +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -62,7 +62,7 @@ SENSORS: Final = [ native_unit_fn=lambda shade: PERCENTAGE, native_value_fn=lambda shade: shade.get_battery_strength(), create_entity_fn=lambda shade: shade.is_battery_powered(), - update_fn=lambda shade: shade.refresh_battery(), + update_fn=lambda shade: shade.refresh_battery(suppress_timeout=True), ), PowerviewSensorDescription( key="signal", @@ -72,7 +72,7 @@ SENSORS: Final = [ native_unit_fn=get_signal_native_unit, native_value_fn=lambda shade: shade.get_signal_strength(), create_entity_fn=lambda shade: shade.has_signal_strength(), - update_fn=lambda shade: shade.refresh(), + update_fn=lambda shade: shade.refresh(suppress_timeout=True), entity_registry_enabled_default=False, ), ] diff --git a/requirements_all.txt b/requirements_all.txt index 4efa0033902..8a555443f3d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -333,7 +333,7 @@ aiopulse==0.4.4 aiopurpleair==2022.12.1 # homeassistant.components.hunterdouglas_powerview -aiopvapi==3.0.2 +aiopvapi==3.1.1 # homeassistant.components.pvpc_hourly_pricing aiopvpc==4.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4ccd8ed4983..8d4df18f8aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -306,7 +306,7 @@ aiopulse==0.4.4 aiopurpleair==2022.12.1 # homeassistant.components.hunterdouglas_powerview -aiopvapi==3.0.2 +aiopvapi==3.1.1 # homeassistant.components.pvpc_hourly_pricing aiopvpc==4.2.2 diff --git a/tests/components/hunterdouglas_powerview/fixtures/gen2/shades.json b/tests/components/hunterdouglas_powerview/fixtures/gen2/shades.json index 6852c37d883..4ed5e3fc313 100644 --- a/tests/components/hunterdouglas_powerview/fixtures/gen2/shades.json +++ b/tests/components/hunterdouglas_powerview/fixtures/gen2/shades.json @@ -67,7 +67,7 @@ "posKind1": 1, "position1": 0, "posKind2": 3, - "position3": 0 + "position2": 0 }, "name_unicode": "Family Centre", "shade_api_class": "ShadeBottomUpTiltOnClosed180" @@ -102,7 +102,7 @@ "posKind1": 1, "position1": 0, "posKind2": 3, - "position3": 0 + "position2": 0 }, "name_unicode": "Family Right", "shade_api_class": "ShadeBottomUpTiltOnClosed90" @@ -137,7 +137,7 @@ "posKind1": 1, "position1": 0, "posKind2": 3, - "position3": 0 + "position2": 0 }, "name_unicode": "Bed 2", "shade_api_class": "ShadeBottomUpTiltAnywhere" diff --git a/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json b/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json index ef9fdbd5c61..85ad99c1a0f 100644 --- a/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json +++ b/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json @@ -310,5 +310,31 @@ "bleName": "R23:E63C", "shadeGroupIds": [], "shade_api_class": "ShadeDualOverlappedTilt180" + }, + { + "batteryStatus": null, + "bleName": "AUR:881C", + "capabilities": 11, + "firmware": { + "build": 400, + "revision": 3, + "subRevision": 0 + }, + "id": 109, + "motion": null, + "name": "Q2VudGVy", + "positions": { + "light": null, + "primary": null, + "secondary": null, + "tilt": null, + "velocity": null + }, + "powerType": 12, + "ptName": "Center", + "roomId": 9, + "shadeGroupIds": [], + "type": 95, + "shade_api_class": "None" } ] From bf8d880e5f7d06486879c75ebb920af2e01b5d24 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 23 Mar 2024 00:44:06 +0100 Subject: [PATCH 1403/1691] Improve axis tests (#114035) * Combine binary sensor tests into more logical groups * Improve light tests * Clean up switch tests * Improve typing in conftest * Add typing to camera * Improve binary sensor * Improve light * Improve switch --- tests/components/axis/conftest.py | 81 ++++++++----- tests/components/axis/test_binary_sensor.py | 119 ++++++++------------ tests/components/axis/test_camera.py | 13 ++- tests/components/axis/test_hub.py | 26 +++-- tests/components/axis/test_light.py | 35 ++---- tests/components/axis/test_switch.py | 27 ++--- 6 files changed, 142 insertions(+), 159 deletions(-) diff --git a/tests/components/axis/conftest.py b/tests/components/axis/conftest.py index f3c395d092d..b50a28df49f 100644 --- a/tests/components/axis/conftest.py +++ b/tests/components/axis/conftest.py @@ -2,8 +2,10 @@ from __future__ import annotations -from collections.abc import Generator +from collections.abc import Callable, Generator from copy import deepcopy +from types import MappingProxyType +from typing import Any from unittest.mock import AsyncMock, patch from axis.rtsp import Signal, State @@ -11,6 +13,7 @@ import pytest import respx from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_HOST, CONF_MODEL, @@ -19,6 +22,7 @@ from homeassistant.const import ( CONF_PORT, CONF_USERNAME, ) +from homeassistant.core import HomeAssistant from .const import ( API_DISCOVERY_RESPONSE, @@ -42,7 +46,6 @@ from .const import ( ) from tests.common import MockConfigEntry -from tests.components.light.conftest import mock_light_profiles # noqa: F401 @pytest.fixture @@ -58,28 +61,33 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture(name="config_entry") -def config_entry_fixture(hass, config, options, config_entry_version): +def config_entry_fixture( + hass: HomeAssistant, + config_entry_data: MappingProxyType[str, Any], + config_entry_options: MappingProxyType[str, Any], + config_entry_version: int, +) -> ConfigEntry: """Define a config entry fixture.""" - entry = MockConfigEntry( + config_entry = MockConfigEntry( domain=AXIS_DOMAIN, entry_id="676abe5b73621446e6550a2e86ffe3dd", unique_id=FORMATTED_MAC, - data=config, - options=options, + data=config_entry_data, + options=config_entry_options, version=config_entry_version, ) - entry.add_to_hass(hass) - return entry + config_entry.add_to_hass(hass) + return config_entry @pytest.fixture(name="config_entry_version") -def config_entry_version_fixture(request): +def config_entry_version_fixture() -> int: """Define a config entry version fixture.""" return 3 -@pytest.fixture(name="config") -def config_fixture(): +@pytest.fixture(name="config_entry_data") +def config_entry_data_fixture() -> MappingProxyType[str, Any]: """Define a config entry data fixture.""" return { CONF_HOST: DEFAULT_HOST, @@ -91,8 +99,8 @@ def config_fixture(): } -@pytest.fixture(name="options") -def options_fixture(request): +@pytest.fixture(name="config_entry_options") +def config_entry_options_fixture() -> MappingProxyType[str, Any]: """Define a config entry options fixture.""" return {} @@ -102,11 +110,14 @@ def options_fixture(request): @pytest.fixture(name="mock_vapix_requests") def default_request_fixture( - respx_mock, port_management_payload, param_properties_payload, param_ports_payload -): + respx_mock: respx, + port_management_payload: dict[str, Any], + param_properties_payload: dict[str, Any], + param_ports_payload: dict[str, Any], +) -> Callable[[str], None]: """Mock default Vapix requests responses.""" - def __mock_default_requests(host): + def __mock_default_requests(host: str) -> None: respx_mock(base_url=f"http://{host}:80") if host != DEFAULT_HOST: @@ -196,13 +207,13 @@ def default_request_fixture( @pytest.fixture -def api_discovery_items(): +def api_discovery_items() -> dict[str, Any]: """Additional Apidiscovery items.""" return {} @pytest.fixture(autouse=True) -def api_discovery_fixture(api_discovery_items): +def api_discovery_fixture(api_discovery_items: dict[str, Any]) -> None: """Apidiscovery mock response.""" data = deepcopy(API_DISCOVERY_RESPONSE) if api_discovery_items: @@ -211,34 +222,36 @@ def api_discovery_fixture(api_discovery_items): @pytest.fixture(name="port_management_payload") -def io_port_management_data_fixture(): +def io_port_management_data_fixture() -> dict[str, Any]: """Property parameter data.""" return PORT_MANAGEMENT_RESPONSE @pytest.fixture(name="param_properties_payload") -def param_properties_data_fixture(): +def param_properties_data_fixture() -> dict[str, Any]: """Property parameter data.""" return PROPERTIES_RESPONSE @pytest.fixture(name="param_ports_payload") -def param_ports_data_fixture(): +def param_ports_data_fixture() -> dict[str, Any]: """Property parameter data.""" return PORTS_RESPONSE @pytest.fixture(name="setup_default_vapix_requests") -def default_vapix_requests_fixture(mock_vapix_requests): +def default_vapix_requests_fixture(mock_vapix_requests: Callable[[str], None]) -> None: """Mock default Vapix requests responses.""" mock_vapix_requests(DEFAULT_HOST) @pytest.fixture(name="prepare_config_entry") -async def prep_config_entry_fixture(hass, config_entry, setup_default_vapix_requests): +async def prep_config_entry_fixture( + hass: HomeAssistant, config_entry: ConfigEntry, setup_default_vapix_requests: None +) -> Callable[[], ConfigEntry]: """Fixture factory to set up Axis network device.""" - async def __mock_setup_config_entry(): + async def __mock_setup_config_entry() -> ConfigEntry: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() return config_entry @@ -247,7 +260,9 @@ async def prep_config_entry_fixture(hass, config_entry, setup_default_vapix_requ @pytest.fixture(name="setup_config_entry") -async def setup_config_entry_fixture(hass, config_entry, setup_default_vapix_requests): +async def setup_config_entry_fixture( + hass: HomeAssistant, config_entry: ConfigEntry, setup_default_vapix_requests: None +) -> ConfigEntry: """Define a fixture to set up Axis network device.""" assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -258,24 +273,24 @@ async def setup_config_entry_fixture(hass, config_entry, setup_default_vapix_req @pytest.fixture(autouse=True) -def mock_axis_rtspclient(): +def mock_axis_rtspclient() -> Generator[Callable[[dict | None, str], None], None, None]: """No real RTSP communication allowed.""" with patch("axis.stream_manager.RTSPClient") as rtsp_client_mock: rtsp_client_mock.return_value.session.state = State.STOPPED - async def start_stream(): + async def start_stream() -> None: """Set state to playing when calling RTSPClient.start.""" rtsp_client_mock.return_value.session.state = State.PLAYING rtsp_client_mock.return_value.start = start_stream - def stop_stream(): + def stop_stream() -> None: """Set state to stopped when calling RTSPClient.stop.""" rtsp_client_mock.return_value.session.state = State.STOPPED rtsp_client_mock.return_value.stop = stop_stream - def make_rtsp_call(data: dict | None = None, state: str = ""): + def make_rtsp_call(data: dict | None = None, state: str = "") -> None: """Generate a RTSP call.""" axis_streammanager_session_callback = rtsp_client_mock.call_args[0][4] @@ -291,7 +306,9 @@ def mock_axis_rtspclient(): @pytest.fixture(autouse=True) -def mock_rtsp_event(mock_axis_rtspclient): +def mock_rtsp_event( + mock_axis_rtspclient: Callable[[dict | None, str], None], +) -> Callable[[str, str, str, str, str, str], None]: """Fixture to allow mocking received RTSP events.""" def send_event( @@ -342,7 +359,9 @@ def mock_rtsp_event(mock_axis_rtspclient): @pytest.fixture(autouse=True) -def mock_rtsp_signal_state(mock_axis_rtspclient): +def mock_rtsp_signal_state( + mock_axis_rtspclient: Callable[[dict | None, str], None], +) -> Callable[[bool], None]: """Fixture to allow mocking RTSP state signalling.""" def send_signal(connected: bool) -> None: diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index ce387655889..dd7674d7d3f 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -1,54 +1,20 @@ """Axis binary sensor platform tests.""" +from collections.abc import Callable + import pytest -from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component from .const import NAME -async def test_platform_manually_configured(hass: HomeAssistant) -> None: - """Test that nothing happens when platform is manually configured.""" - assert ( - await async_setup_component( - hass, - BINARY_SENSOR_DOMAIN, - {BINARY_SENSOR_DOMAIN: {"platform": AXIS_DOMAIN}}, - ) - is True - ) - - assert AXIS_DOMAIN not in hass.data - - -async def test_no_binary_sensors(hass: HomeAssistant, setup_config_entry) -> None: - """Test that no sensors in Axis results in no sensor entities.""" - assert not hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN) - - -async def test_unsupported_binary_sensors( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event -) -> None: - """Test that unsupported sensors are not loaded.""" - mock_rtsp_event( - topic="tns1:PTZController/tnsaxis:PTZPresets/Channel_1", - data_type="on_preset", - data_value="1", - source_name="PresetToken", - source_idx="0", - ) - await hass.async_block_till_done() - - assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 0 - - @pytest.mark.parametrize( ("event", "entity"), [ @@ -178,10 +144,41 @@ async def test_unsupported_binary_sensors( "device_class": BinarySensorDeviceClass.MOTION, }, ), + # Events with names generated from event ID and topic + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile9", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_vmd4_camera1profile9", + "state": STATE_ON, + "name": f"{NAME} VMD4 Camera1Profile9", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), + ( + { + "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario8", + "data_type": "active", + "data_value": "1", + }, + { + "id": f"{BINARY_SENSOR_DOMAIN}.{NAME}_object_analytics_device1scenario8", + "state": STATE_ON, + "name": f"{NAME} Object Analytics Device1Scenario8", + "device_class": BinarySensorDeviceClass.MOTION, + }, + ), ], ) async def test_binary_sensors( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event, event, entity + hass: HomeAssistant, + setup_config_entry: ConfigEntry, + mock_rtsp_event: Callable[[str, str, str, str, str, str], None], + event: dict[str, str], + entity: dict[str, str], ) -> None: """Test that sensors are loaded properly.""" mock_rtsp_event(**event) @@ -198,6 +195,15 @@ async def test_binary_sensors( @pytest.mark.parametrize( ("event"), [ + # Event with unsupported topic + { + "topic": "tns1:PTZController/tnsaxis:PTZPresets/Channel_1", + "data_type": "on_preset", + "data_value": "1", + "source_name": "PresetToken", + "source_idx": "0", + }, + # Event with unsupported source_idx { "topic": "tns1:Device/tnsaxis:IO/Port", "data_type": "state", @@ -206,6 +212,7 @@ async def test_binary_sensors( "source_name": "port", "source_idx": "-1", }, + # Event with unsupported ID in topic 'ANY' { "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1ProfileANY", "data_type": "active", @@ -219,40 +226,12 @@ async def test_binary_sensors( ], ) async def test_unsupported_events( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event, event + hass: HomeAssistant, + setup_config_entry: ConfigEntry, + mock_rtsp_event: Callable[[str, str, str, str, str, str], None], + event: dict[str, str], ) -> None: """Validate nothing breaks with unsupported events.""" mock_rtsp_event(**event) await hass.async_block_till_done() assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 0 - - -@pytest.mark.parametrize( - ("event", "entity_id"), - [ - ( - { - "topic": "tnsaxis:CameraApplicationPlatform/VMD/Camera1Profile9", - "data_type": "active", - "data_value": "1", - }, - "binary_sensor.name_vmd4_camera1profile9", - ), - ( - { - "topic": "tnsaxis:CameraApplicationPlatform/ObjectAnalytics/Device1Scenario8", - "data_type": "active", - "data_value": "1", - }, - "binary_sensor.name_object_analytics_device1scenario8", - ), - ], -) -async def test_no_primary_name_for_event( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event, event, entity_id -) -> None: - """Validate fallback method for getting name works.""" - mock_rtsp_event(**event) - await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1 - assert hass.states.get(entity_id) diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py index 440bb17b08e..e184f2014b3 100644 --- a/tests/components/axis/test_camera.py +++ b/tests/components/axis/test_camera.py @@ -1,5 +1,7 @@ """Axis camera platform tests.""" +from collections.abc import Callable + import pytest from homeassistant.components import camera @@ -8,6 +10,7 @@ from homeassistant.components.axis.const import ( DOMAIN as AXIS_DOMAIN, ) from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_IDLE from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component @@ -27,7 +30,7 @@ async def test_platform_manually_configured(hass: HomeAssistant) -> None: assert AXIS_DOMAIN not in hass.data -async def test_camera(hass: HomeAssistant, setup_config_entry) -> None: +async def test_camera(hass: HomeAssistant, setup_config_entry: ConfigEntry) -> None: """Test that Axis camera platform is loaded properly.""" assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 1 @@ -46,9 +49,9 @@ async def test_camera(hass: HomeAssistant, setup_config_entry) -> None: ) -@pytest.mark.parametrize("options", [{CONF_STREAM_PROFILE: "profile_1"}]) +@pytest.mark.parametrize("config_entry_options", [{CONF_STREAM_PROFILE: "profile_1"}]) async def test_camera_with_stream_profile( - hass: HomeAssistant, setup_config_entry + hass: HomeAssistant, setup_config_entry: ConfigEntry ) -> None: """Test that Axis camera entity is using the correct path with stream profike.""" assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 1 @@ -83,7 +86,9 @@ root.Properties.System.SerialNumber={MAC} @pytest.mark.parametrize("param_properties_payload", [property_data]) -async def test_camera_disabled(hass: HomeAssistant, prepare_config_entry) -> None: +async def test_camera_disabled( + hass: HomeAssistant, prepare_config_entry: Callable[[], ConfigEntry] +) -> None: """Test that Axis camera platform is loaded properly but does not create camera entity.""" await prepare_config_entry() assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 0 diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index 70b71e45b45..b2fc210f541 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -45,7 +45,7 @@ def hass_mock_forward_entry_setup(hass): async def test_device_setup( hass: HomeAssistant, forward_entry_setup, - config, + config_entry_data, setup_config_entry, device_registry: dr.DeviceRegistry, ) -> None: @@ -63,9 +63,9 @@ async def test_device_setup( assert forward_entry_setup.mock_calls[2][1][1] == "light" assert forward_entry_setup.mock_calls[3][1][1] == "switch" - assert hub.config.host == config[CONF_HOST] - assert hub.config.model == config[CONF_MODEL] - assert hub.config.name == config[CONF_NAME] + assert hub.config.host == config_entry_data[CONF_HOST] + assert hub.config.model == config_entry_data[CONF_MODEL] + assert hub.config.name == config_entry_data[CONF_NAME] assert hub.unique_id == FORMATTED_MAC device_entry = device_registry.async_get_device( @@ -206,11 +206,11 @@ async def test_device_unknown_error( assert hass.data[AXIS_DOMAIN] == {} -async def test_shutdown(config) -> None: +async def test_shutdown(config_entry_data) -> None: """Successful shutdown.""" hass = Mock() entry = Mock() - entry.data = config + entry.data = config_entry_data mock_api = Mock() mock_api.vapix.serial_number = FORMATTED_MAC @@ -221,25 +221,27 @@ async def test_shutdown(config) -> None: assert len(axis_device.api.stream.stop.mock_calls) == 1 -async def test_get_device_fails(hass: HomeAssistant, config) -> None: +async def test_get_device_fails(hass: HomeAssistant, config_entry_data) -> None: """Device unauthorized yields authentication required error.""" with patch( "axis.vapix.vapix.Vapix.initialize", side_effect=axislib.Unauthorized ), pytest.raises(axis.errors.AuthenticationRequired): - await axis.hub.get_axis_api(hass, config) + await axis.hub.get_axis_api(hass, config_entry_data) -async def test_get_device_device_unavailable(hass: HomeAssistant, config) -> None: +async def test_get_device_device_unavailable( + hass: HomeAssistant, config_entry_data +) -> None: """Device unavailable yields cannot connect error.""" with patch( "axis.vapix.vapix.Vapix.request", side_effect=axislib.RequestError ), pytest.raises(axis.errors.CannotConnect): - await axis.hub.get_axis_api(hass, config) + await axis.hub.get_axis_api(hass, config_entry_data) -async def test_get_device_unknown_error(hass: HomeAssistant, config) -> None: +async def test_get_device_unknown_error(hass: HomeAssistant, config_entry_data) -> None: """Device yield unknown error.""" with patch( "axis.vapix.vapix.Vapix.request", side_effect=axislib.AxisException ), pytest.raises(axis.errors.AuthenticationRequired): - await axis.hub.get_axis_api(hass, config) + await axis.hub.get_axis_api(hass, config_entry_data) diff --git a/tests/components/axis/test_light.py b/tests/components/axis/test_light.py index 2b46330bd2d..b8983cd8b9b 100644 --- a/tests/components/axis/test_light.py +++ b/tests/components/axis/test_light.py @@ -1,13 +1,15 @@ """Axis light platform tests.""" +from collections.abc import Callable +from typing import Any from unittest.mock import patch from axis.vapix.models.api import CONTEXT import pytest import respx -from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -16,7 +18,6 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component from .const import DEFAULT_HOST, NAME @@ -28,7 +29,7 @@ API_DISCOVERY_LIGHT_CONTROL = { @pytest.fixture -def light_control_items(): +def light_control_items() -> list[dict[str, Any]]: """Available lights.""" return [ { @@ -47,7 +48,7 @@ def light_control_items(): @pytest.fixture(autouse=True) -def light_control_fixture(light_control_items): +def light_control_fixture(light_control_items: list[dict[str, Any]]) -> None: """Light control mock response.""" data = { "apiVersion": "1.1", @@ -67,24 +68,12 @@ def light_control_fixture(light_control_items): ) -async def test_platform_manually_configured(hass: HomeAssistant) -> None: - """Test that nothing happens when platform is manually configured.""" - assert await async_setup_component( - hass, LIGHT_DOMAIN, {LIGHT_DOMAIN: {"platform": AXIS_DOMAIN}} - ) - - assert AXIS_DOMAIN not in hass.data - - -async def test_no_lights(hass: HomeAssistant, setup_config_entry) -> None: - """Test that no light events in Axis results in no light entities.""" - assert not hass.states.async_entity_ids(LIGHT_DOMAIN) - - @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_LIGHT_CONTROL]) @pytest.mark.parametrize("light_control_items", [[]]) async def test_no_light_entity_without_light_control_representation( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event + hass: HomeAssistant, + setup_config_entry: ConfigEntry, + mock_rtsp_event: Callable[[str, str, str, str, str, str], None], ) -> None: """Verify no lights entities get created without light control representation.""" mock_rtsp_event( @@ -102,10 +91,10 @@ async def test_no_light_entity_without_light_control_representation( @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_LIGHT_CONTROL]) async def test_lights( hass: HomeAssistant, - respx_mock, - setup_config_entry, - mock_rtsp_event, - api_discovery_items, + respx_mock: respx, + setup_config_entry: ConfigEntry, + mock_rtsp_event: Callable[[str, str, str, str, str, str], None], + api_discovery_items: dict[str, Any], ) -> None: """Test that lights are loaded properly.""" # Add light diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index 13411d86943..a2a1088d7ef 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -1,12 +1,13 @@ """Axis switch platform tests.""" +from collections.abc import Callable from unittest.mock import patch from axis.vapix.models.api import CONTEXT import pytest -from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -15,25 +16,9 @@ from homeassistant.const import ( STATE_ON, ) from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component from .const import API_DISCOVERY_PORT_MANAGEMENT, NAME - -async def test_platform_manually_configured(hass: HomeAssistant) -> None: - """Test that nothing happens when platform is manually configured.""" - assert await async_setup_component( - hass, SWITCH_DOMAIN, {SWITCH_DOMAIN: {"platform": AXIS_DOMAIN}} - ) - - assert AXIS_DOMAIN not in hass.data - - -async def test_no_switches(hass: HomeAssistant, setup_config_entry) -> None: - """Test that no output events in Axis results in no switch entities.""" - assert not hass.states.async_entity_ids(SWITCH_DOMAIN) - - PORT_DATA = """root.IOPort.I0.Configurable=yes root.IOPort.I0.Direction=output root.IOPort.I0.Output.Name=Doorbell @@ -47,7 +32,9 @@ root.IOPort.I1.Output.Active=open @pytest.mark.parametrize("param_ports_payload", [PORT_DATA]) async def test_switches_with_port_cgi( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event + hass: HomeAssistant, + setup_config_entry: ConfigEntry, + mock_rtsp_event: Callable[[str, str, str, str, str, str], None], ) -> None: """Test that switches are loaded properly using port.cgi.""" mock_rtsp_event( @@ -130,7 +117,9 @@ PORT_MANAGEMENT_RESPONSE = { @pytest.mark.parametrize("api_discovery_items", [API_DISCOVERY_PORT_MANAGEMENT]) @pytest.mark.parametrize("port_management_payload", [PORT_MANAGEMENT_RESPONSE]) async def test_switches_with_port_management( - hass: HomeAssistant, setup_config_entry, mock_rtsp_event + hass: HomeAssistant, + setup_config_entry: ConfigEntry, + mock_rtsp_event: Callable[[str, str, str, str, str, str], None], ) -> None: """Test that switches are loaded properly using port management.""" mock_rtsp_event( From 952f47ab1861c61652d28587fa2c76e6694a5f18 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 22 Mar 2024 13:50:39 -1000 Subject: [PATCH 1404/1691] Combine recorder and frontend bootstrap step (#113985) --- homeassistant/bootstrap.py | 23 ++++++++----------- .../components/recorder/manifest.json | 1 + tests/components/humidifier/test_recorder.py | 7 ++++-- tests/components/siren/test_recorder.py | 7 ++++-- tests/components/vacuum/test_recorder.py | 7 ++++-- .../components/water_heater/test_recorder.py | 7 ++++-- tests/test_bootstrap.py | 3 +++ 7 files changed, 34 insertions(+), 21 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 215aadc25c2..cc15b03bcc0 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -194,16 +194,14 @@ CRITICAL_INTEGRATIONS = { "frontend", } -SETUP_ORDER = { +SETUP_ORDER = ( # Load logging as soon as possible - "logging": LOGGING_INTEGRATIONS, - # Setup frontend - "frontend": FRONTEND_INTEGRATIONS, - # Setup recorder - "recorder": RECORDER_INTEGRATIONS, + ("logging", LOGGING_INTEGRATIONS), + # Setup frontend and recorder + ("frontend, recorder", {*FRONTEND_INTEGRATIONS, *RECORDER_INTEGRATIONS}), # Start up debuggers. Start these first in case they want to wait. - "debugger": DEBUGGER_INTEGRATIONS, -} + ("debugger", DEBUGGER_INTEGRATIONS), +) async def async_setup_hass( @@ -856,10 +854,9 @@ async def _async_set_up_integrations( if "recorder" in domains_to_setup: recorder.async_initialize_recorder(hass) - pre_stage_domains: dict[str, set[str]] = { - name: domains_to_setup & domain_group - for name, domain_group in SETUP_ORDER.items() - } + pre_stage_domains = [ + (name, domains_to_setup & domain_group) for name, domain_group in SETUP_ORDER + ] # calculate what components to setup in what stage stage_1_domains: set[str] = set() @@ -885,7 +882,7 @@ async def _async_set_up_integrations( stage_2_domains = domains_to_setup - stage_1_domains - for name, domain_group in pre_stage_domains.items(): + for name, domain_group in pre_stage_domains: if domain_group: stage_2_domains -= domain_group _LOGGER.info("Setting up %s: %s", name, domain_group) diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index feeccbab612..feb79319cd9 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -1,6 +1,7 @@ { "domain": "recorder", "name": "Recorder", + "after_dependencies": ["http"], "codeowners": ["@home-assistant/core"], "documentation": "https://www.home-assistant.io/integrations/recorder", "integration_type": "system", diff --git a/tests/components/humidifier/test_recorder.py b/tests/components/humidifier/test_recorder.py index b023b8cc4d9..084effb1af8 100644 --- a/tests/components/humidifier/test_recorder.py +++ b/tests/components/humidifier/test_recorder.py @@ -13,7 +13,7 @@ from homeassistant.components.humidifier import ( from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.history import get_significant_states from homeassistant.const import ATTR_FRIENDLY_NAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, split_entity_id from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -37,7 +37,10 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) ) assert len(states) >= 1 for entity_states in states.values(): - for state in entity_states: + for state in filter( + lambda state: split_entity_id(state.entity_id)[0] == humidifier.DOMAIN, + entity_states, + ): assert ATTR_MIN_HUMIDITY not in state.attributes assert ATTR_MAX_HUMIDITY not in state.attributes assert ATTR_AVAILABLE_MODES not in state.attributes diff --git a/tests/components/siren/test_recorder.py b/tests/components/siren/test_recorder.py index bf9a49a7c76..37161e92ff6 100644 --- a/tests/components/siren/test_recorder.py +++ b/tests/components/siren/test_recorder.py @@ -9,7 +9,7 @@ from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.history import get_significant_states from homeassistant.components.siren import ATTR_AVAILABLE_TONES from homeassistant.const import ATTR_FRIENDLY_NAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, split_entity_id from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -33,6 +33,9 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) ) assert len(states) >= 1 for entity_states in states.values(): - for state in entity_states: + for state in filter( + lambda state: split_entity_id(state.entity_id)[0] == siren.DOMAIN, + entity_states, + ): assert ATTR_AVAILABLE_TONES not in state.attributes assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/vacuum/test_recorder.py b/tests/components/vacuum/test_recorder.py index 884e6943e95..f1a531fe3d0 100644 --- a/tests/components/vacuum/test_recorder.py +++ b/tests/components/vacuum/test_recorder.py @@ -9,7 +9,7 @@ from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.history import get_significant_states from homeassistant.components.vacuum import ATTR_FAN_SPEED_LIST from homeassistant.const import ATTR_FRIENDLY_NAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, split_entity_id from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -33,6 +33,9 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) ) assert len(states) >= 1 for entity_states in states.values(): - for state in entity_states: + for state in filter( + lambda state: split_entity_id(state.entity_id)[0] == vacuum.DOMAIN, + entity_states, + ): assert ATTR_FAN_SPEED_LIST not in state.attributes assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/water_heater/test_recorder.py b/tests/components/water_heater/test_recorder.py index a3f808818ab..3455090718b 100644 --- a/tests/components/water_heater/test_recorder.py +++ b/tests/components/water_heater/test_recorder.py @@ -13,7 +13,7 @@ from homeassistant.components.water_heater import ( ATTR_OPERATION_LIST, ) from homeassistant.const import ATTR_FRIENDLY_NAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, split_entity_id from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -37,7 +37,10 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) ) assert len(states) >= 1 for entity_states in states.values(): - for state in entity_states: + for state in filter( + lambda state: split_entity_id(state.entity_id)[0] == water_heater.DOMAIN, + entity_states, + ): assert ATTR_OPERATION_LIST not in state.attributes assert ATTR_MIN_TEMP not in state.attributes assert ATTR_MAX_TEMP not in state.attributes diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index fee058c4b52..eec24a69344 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -359,6 +359,9 @@ async def test_setup_frontend_before_recorder(hass: HomeAssistant) -> None: MockModule( domain="recorder", async_setup=gen_domain_setup("recorder"), + partial_manifest={ + "after_dependencies": ["http"], + }, ), ) From c2771791a3870350a716f8a1541cb12b147ab779 Mon Sep 17 00:00:00 2001 From: kingy444 Date: Sat, 23 Mar 2024 14:09:36 +1100 Subject: [PATCH 1405/1691] Add Powerview Type 11 (#114047) --- homeassistant/components/hunterdouglas_powerview/cover.py | 5 +++++ .../hunterdouglas_powerview/fixtures/gen3/home/shades.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 7ff2fd55958..78a62e5bb88 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -993,6 +993,11 @@ TYPE_TO_CLASSES = { PowerViewShadeDualOverlappedFront, PowerViewShadeDualOverlappedRear, ), + 11: ( + PowerViewShadeDualOverlappedCombined, + PowerViewShadeDualOverlappedFront, + PowerViewShadeDualOverlappedRear, + ), } diff --git a/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json b/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json index 85ad99c1a0f..5db6065843f 100644 --- a/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json +++ b/tests/components/hunterdouglas_powerview/fixtures/gen3/home/shades.json @@ -335,6 +335,6 @@ "roomId": 9, "shadeGroupIds": [], "type": 95, - "shade_api_class": "None" + "shade_api_class": "ShadeDualOverlappedIlluminated" } ] From 39c44ad5b7b4913c97fa2f3f5dbf8f9a90168e15 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 22 Mar 2024 22:40:34 -1000 Subject: [PATCH 1406/1691] Move setup of legacy device_tracker see service to legacy setup task (#114043) * Move setup of legacy device_tracker see service to legacy setup task device_tracker can setup synchronously now if there are no legacy platforms that have to be import to validate the config The see service is not useful until at least one legacy platform is loaded so there is no reason to wait for it in the base setup. * collapse --- .../components/device_tracker/__init__.py | 10 +++++++++- homeassistant/components/device_tracker/legacy.py | 15 --------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 346459e324e..d453c101a52 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -69,7 +69,15 @@ def is_on(hass: HomeAssistant, entity_id: str) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the device tracker.""" - await async_setup_legacy_integration(hass, config) + # + # Legacy and platforms load in a non-awaited tracked task + # to ensure device tracker setup can continue and config + # entry integrations are not waiting for legacy device + # tracker platforms to be set up. + # + hass.async_create_task( + async_setup_legacy_integration(hass, config), eager_start=True + ) return True diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index 0f0d842c595..c3aec89be90 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -217,21 +217,6 @@ async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> No DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA ) - # - # The platforms load in a non-awaited tracked task - # to ensure device tracker setup can continue and config - # entry integrations are not waiting for legacy device - # tracker platforms to be set up. - # - hass.async_create_task( - _async_setup_legacy_integration(hass, config, tracker), eager_start=True - ) - - -async def _async_setup_legacy_integration( - hass: HomeAssistant, config: ConfigType, tracker: DeviceTracker -) -> None: - """Set up the legacy integration.""" legacy_platforms = await async_extract_config(hass, config) setup_tasks = [ From 4f22c85e398a135315a691f63d896b297f70a8af Mon Sep 17 00:00:00 2001 From: Jeremy TRUFIER Date: Sat, 23 Mar 2024 10:21:20 +0100 Subject: [PATCH 1407/1691] Fix missing linked device on Overkiz integration (#114006) * Fix missing linked device (#112731) * Update homeassistant/components/overkiz/executor.py Co-authored-by: Robert Svensson --------- Co-authored-by: Robert Svensson --- ...heater_with_adjustable_temperature_setpoint.py | 4 +++- .../atlantic_electrical_towel_dryer.py | 4 +++- .../atlantic_heat_recovery_ventilation.py | 4 +++- .../atlantic_pass_apc_heating_zone.py | 4 +++- .../atlantic_pass_apc_zone_control_zone.py | 15 ++++++++++----- .../somfy_heating_temperature_interface.py | 4 +++- .../overkiz/climate_entities/somfy_thermostat.py | 4 +++- .../valve_heating_temperature_interface.py | 4 +++- homeassistant/components/overkiz/executor.py | 4 ++-- 9 files changed, 33 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py index 47dc75710ec..64a7dc1e645 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_heater_with_adjustable_temperature_setpoint.py @@ -143,7 +143,9 @@ class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint( @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return temperature.value_as_float return None diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py index 16568c0b1b7..e49fc4358e9 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_electrical_towel_dryer.py @@ -95,7 +95,9 @@ class AtlanticElectricalTowelDryer(OverkizEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return cast(float, temperature.value) return None diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py b/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py index 698979672ea..f1d96b5687b 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py @@ -69,7 +69,9 @@ class AtlanticHeatRecoveryVentilation(OverkizEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return cast(float, temperature.value) return None diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py index 72a9182974f..bf6aa43644e 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py @@ -108,7 +108,9 @@ class AtlanticPassAPCHeatingZone(OverkizEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return cast(float, temperature.value) return None diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py index c61c92a3c44..261acc2838c 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py @@ -24,7 +24,7 @@ OVERKIZ_MODE_TO_PRESET_MODES: dict[str, str] = { PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_MODE_TO_PRESET_MODES.items()} -TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 1 +TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 20 # Those device depends on a main probe that choose the operating mode (heating, cooling, ...) @@ -65,10 +65,15 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): """Return hvac operation ie. heat, cool, dry, off mode.""" if ( - state := self.zone_control_device.states[ - OverkizState.IO_PASS_APC_OPERATING_MODE - ] - ) is not None and (value := state.value_as_str) is not None: + self.zone_control_device is not None + and ( + state := self.zone_control_device.states[ + OverkizState.IO_PASS_APC_OPERATING_MODE + ] + ) + is not None + and (value := state.value_as_str) is not None + ): return OVERKIZ_TO_HVAC_MODE[value] return HVACMode.OFF diff --git a/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py b/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py index becb807d41a..85ce7ae57e3 100644 --- a/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py +++ b/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py @@ -166,7 +166,9 @@ class SomfyHeatingTemperatureInterface(OverkizEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return temperature.value_as_float return None diff --git a/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py b/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py index 615c618dd40..829a3bad03b 100644 --- a/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py +++ b/homeassistant/components/overkiz/climate_entities/somfy_thermostat.py @@ -104,7 +104,9 @@ class SomfyThermostat(OverkizEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return cast(float, temperature.value) return None diff --git a/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py b/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py index 893e4ca3d8e..e2165e8b6c6 100644 --- a/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py +++ b/homeassistant/components/overkiz/climate_entities/valve_heating_temperature_interface.py @@ -91,7 +91,9 @@ class ValveHeatingTemperatureInterface(OverkizEntity, ClimateEntity): @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + if self.temperature_device is not None and ( + temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE] + ): return temperature.value_as_float return None diff --git a/homeassistant/components/overkiz/executor.py b/homeassistant/components/overkiz/executor.py index e11dc59cc27..94b2c1b25fa 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -41,9 +41,9 @@ class OverkizExecutor: """Return Overkiz device linked to this entity.""" return self.coordinator.data[self.device_url] - def linked_device(self, index: int) -> Device: + def linked_device(self, index: int) -> Device | None: """Return Overkiz device sharing the same base url.""" - return self.coordinator.data[f"{self.base_device_url}#{index}"] + return self.coordinator.data.get(f"{self.base_device_url}#{index}") def select_command(self, *commands: str) -> str | None: """Select first existing command in a list of commands.""" From 900e0c07bf346bad672cf2d3a55ff984dfc99a7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 22 Mar 2024 23:51:03 -1000 Subject: [PATCH 1408/1691] Load light profiles in a task to avoid delaying platform setup (#114038) --- homeassistant/components/light/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 0a41ca2a84e..0045e2324ff 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -403,7 +403,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: await component.async_setup(config) profiles = hass.data[DATA_PROFILES] = Profiles(hass) - await profiles.async_initialize() + # Profiles are loaded in a separate task to avoid delaying the setup + # of the light base platform. + hass.async_create_task(profiles.async_initialize(), eager_start=True) def preprocess_data(data: dict[str, Any]) -> dict[str | vol.Optional, Any]: """Preprocess the service data.""" From ebe6c35b4c8f3cef067e20773003cc13a940a8aa Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 23 Mar 2024 11:01:59 +0100 Subject: [PATCH 1409/1691] Smhi add reconfigure step to config flow (#114044) * Add reconfigure step to SMHI * Check location * Add test * Add test entity and device --- homeassistant/components/smhi/config_flow.py | 69 +++++++++++++- homeassistant/components/smhi/strings.json | 10 ++- homeassistant/strings.json | 1 + tests/components/smhi/test_config_flow.py | 94 ++++++++++++++++++++ 4 files changed, 171 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smhi/config_flow.py b/homeassistant/components/smhi/config_flow.py index 4a28f475ad6..b3350f6bb18 100644 --- a/homeassistant/components/smhi/config_flow.py +++ b/homeassistant/components/smhi/config_flow.py @@ -7,10 +7,15 @@ from typing import Any from smhi.smhi_lib import Smhi, SmhiForecastException import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME from homeassistant.core import HomeAssistant -from homeassistant.helpers import aiohttp_client +from homeassistant.helpers import ( + aiohttp_client, + device_registry as dr, + entity_registry as er, +) from homeassistant.helpers.selector import LocationSelector from .const import DEFAULT_NAME, DOMAIN, HOME_LOCATION_NAME @@ -34,6 +39,7 @@ class SmhiFlowHandler(ConfigFlow, domain=DOMAIN): """Config flow for SMHI component.""" VERSION = 2 + config_entry: ConfigEntry | None async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -74,3 +80,62 @@ class SmhiFlowHandler(ConfigFlow, domain=DOMAIN): ), errors=errors, ) + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a reconfiguration flow initialized by the user.""" + self.config_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + return await self.async_step_reconfigure_confirm() + + async def async_step_reconfigure_confirm( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a reconfiguration flow initialized by the user.""" + errors: dict[str, str] = {} + assert self.config_entry + + if user_input is not None: + lat: float = user_input[CONF_LOCATION][CONF_LATITUDE] + lon: float = user_input[CONF_LOCATION][CONF_LONGITUDE] + if await async_check_location(self.hass, lon, lat): + unique_id = f"{lat}-{lon}" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + old_lat = self.config_entry.data[CONF_LOCATION][CONF_LATITUDE] + old_lon = self.config_entry.data[CONF_LOCATION][CONF_LONGITUDE] + + entity_reg = er.async_get(self.hass) + if entity := entity_reg.async_get_entity_id( + WEATHER_DOMAIN, DOMAIN, f"{old_lat}, {old_lon}" + ): + entity_reg.async_update_entity( + entity, new_unique_id=f"{lat}, {lon}" + ) + + device_reg = dr.async_get(self.hass) + if device := device_reg.async_get_device( + identifiers={(DOMAIN, f"{old_lat}, {old_lon}")} + ): + device_reg.async_update_device( + device.id, new_identifiers={(DOMAIN, f"{lat}, {lon}")} + ) + + return self.async_update_reload_and_abort( + self.config_entry, + unique_id=unique_id, + data={**self.config_entry.data, **user_input}, + reason="reconfigure_successful", + ) + errors["base"] = "wrong_location" + + schema = self.add_suggested_values_to_schema( + vol.Schema({vol.Required(CONF_LOCATION): LocationSelector()}), + self.config_entry.data, + ) + return self.async_show_form( + step_id="reconfigure_confirm", data_schema=schema, errors=errors + ) diff --git a/homeassistant/components/smhi/strings.json b/homeassistant/components/smhi/strings.json index 2b155f58f96..e78fee64a2b 100644 --- a/homeassistant/components/smhi/strings.json +++ b/homeassistant/components/smhi/strings.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "step": { "user": { @@ -10,6 +11,13 @@ "latitude": "[%key:common::config_flow::data::latitude%]", "longitude": "[%key:common::config_flow::data::longitude%]" } + }, + "reconfigure_confirm": { + "title": "Reconfigure your location in Sweden", + "data": { + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" + } } }, "error": { diff --git a/homeassistant/strings.json b/homeassistant/strings.json index 2f112d45dc0..97bba2fb3b7 100644 --- a/homeassistant/strings.json +++ b/homeassistant/strings.json @@ -130,6 +130,7 @@ "oauth2_unauthorized": "OAuth authorization error while obtaining access token.", "oauth2_failed": "Error while obtaining access token.", "reauth_successful": "Re-authentication was successful", + "reconfigure_successful": "Re-configuration was successful", "unknown_authorize_url_generation": "Unknown error generating an authorize URL.", "cloud_not_connected": "Not connected to Home Assistant Cloud." } diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index b8742d0cbf6..783e2d7eec1 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -8,9 +8,11 @@ from smhi.smhi_lib import SmhiForecastException from homeassistant import config_entries from homeassistant.components.smhi.const import DOMAIN +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry @@ -178,3 +180,95 @@ async def test_form_unique_id_exist(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.ABORT assert result2["reason"] == "already_configured" + + +async def test_reconfigure_flow( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test re-configuration flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Home", + unique_id="57.2898-13.6304", + data={"location": {"latitude": 57.2898, "longitude": 13.6304}, "name": "Home"}, + version=2, + ) + entry.add_to_hass(hass) + + entity = entity_registry.async_get_or_create( + WEATHER_DOMAIN, DOMAIN, "57.2898, 13.6304" + ) + device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, "57.2898, 13.6304")}, + manufacturer="SMHI", + model="v2", + name=entry.title, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": entry.entry_id, + }, + ) + assert result["type"] == FlowResultType.FORM + + with patch( + "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", + side_effect=SmhiForecastException, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_LOCATION: { + CONF_LATITUDE: 0.0, + CONF_LONGITUDE: 0.0, + } + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": "wrong_location"} + + with patch( + "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", + return_value={"test": "something", "test2": "something else"}, + ), patch( + "homeassistant.components.smhi.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_LOCATION: { + CONF_LATITUDE: 58.2898, + CONF_LONGITUDE: 14.6304, + } + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + entry = hass.config_entries.async_get_entry(entry.entry_id) + assert entry.title == "Home" + assert entry.unique_id == "58.2898-14.6304" + assert entry.data == { + "location": { + "latitude": 58.2898, + "longitude": 14.6304, + }, + "name": "Home", + } + entity = entity_registry.async_get(entity.entity_id) + assert entity + assert entity.unique_id == "58.2898, 14.6304" + device = device_registry.async_get(device.id) + assert device + assert device.identifiers == {(DOMAIN, "58.2898, 14.6304")} + assert len(mock_setup_entry.mock_calls) == 1 From ac80d38871e655bab88d8dbc70db3011a541e4d7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 00:11:42 -1000 Subject: [PATCH 1410/1691] Fix flakey stream hls test (#114046) --- homeassistant/components/stream/__init__.py | 11 ++++++++-- tests/components/stream/test_hls.py | 23 +++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index c1822d596ec..44cf9177993 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -409,6 +409,13 @@ class Stream: self._fast_restart_once = True self._thread_quit.set() + def _set_state(self, available: bool) -> None: + """Set the stream state by updating the callback.""" + # Call with call_soon_threadsafe since we know _async_update_state is always + # all callback function instead of using add_job which would have to work + # it out each time + self.hass.loop.call_soon_threadsafe(self._async_update_state, available) + def _run_worker(self) -> None: """Handle consuming streams and restart keepalive streams.""" # Keep import here so that we can import stream integration without installing reqs @@ -419,7 +426,7 @@ class Stream: wait_timeout = 0 while not self._thread_quit.wait(timeout=wait_timeout): start_time = time.time() - self.hass.add_job(self._async_update_state, True) + self._set_state(True) self._diagnostics.set_value( "keepalive", self.dynamic_stream_settings.preload_stream ) @@ -451,7 +458,7 @@ class Stream: continue break - self.hass.add_job(self._async_update_state, False) + self._set_state(False) # To avoid excessive restarts, wait before restarting # As the required recovery time may be different for different setups, start # with trying a short wait_timeout and increase it on each reconnection attempt. diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 0ec01fd9231..7f9afaf1234 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -2,14 +2,13 @@ from datetime import timedelta from http import HTTPStatus -import logging from unittest.mock import patch from urllib.parse import urlparse import av import pytest -from homeassistant.components.stream import create_stream +from homeassistant.components.stream import Stream, create_stream from homeassistant.components.stream.const import ( EXT_X_START_LL_HLS, EXT_X_START_NON_LL_HLS, @@ -300,28 +299,22 @@ async def test_stream_retries( open_future1 = hass.loop.create_future() open_future2 = hass.loop.create_future() futures = [open_future2, open_future1] - cur_time = 0 - def time_side_effect(): - logging.info("time side effect") - nonlocal cur_time - if cur_time >= 80: - logging.info("changing return value") + original_set_state = Stream._set_state + + def set_state_wrapper(self, state: bool) -> None: + if state is False: should_retry.return_value = False # Thread should exit and be joinable. - cur_time += 40 - return cur_time + original_set_state(self, state) def av_open_side_effect(*args, **kwargs): hass.loop.call_soon_threadsafe(futures.pop().set_result, None) raise av.error.InvalidDataError(-2, "error") with patch("av.open") as av_open, patch( - "homeassistant.components.stream.time" - ) as mock_time, patch( - "homeassistant.components.stream.STREAM_RESTART_INCREMENT", 0 - ): + "homeassistant.components.stream.Stream._set_state", set_state_wrapper + ), patch("homeassistant.components.stream.STREAM_RESTART_INCREMENT", 0): av_open.side_effect = av_open_side_effect - mock_time.time.side_effect = time_side_effect # Request stream. Enable retries which are disabled by default in tests. should_retry.return_value = True await stream.start() From 31fb02a71d2295feacc7a4cedb5ef4678c4cbd0f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 00:21:56 -1000 Subject: [PATCH 1411/1691] Fix after deps not being considered for integrations before stage 1 (#114045) --- homeassistant/bootstrap.py | 8 ++++++++ tests/test_bootstrap.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index cc15b03bcc0..cd6082d6abc 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -886,6 +886,14 @@ async def _async_set_up_integrations( if domain_group: stage_2_domains -= domain_group _LOGGER.info("Setting up %s: %s", name, domain_group) + to_be_loaded = domain_group.copy() + to_be_loaded.update( + dep + for domain in domain_group + if (integration := integration_cache.get(domain)) is not None + for dep in integration.all_dependencies + ) + async_set_domains_to_be_loaded(hass, to_be_loaded) await async_setup_multi_components(hass, domain_group, config) # Enables after dependencies when setting up stage 1 domains diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index eec24a69344..a4453849b5e 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -379,6 +379,8 @@ async def test_setup_frontend_before_recorder(hass: HomeAssistant) -> None: assert "frontend" in hass.config.components assert "normal_integration" in hass.config.components assert "recorder" in hass.config.components + assert "http" in hass.config.components + assert order == [ "http", "frontend", From ce48e6e574aed5fb10ff7adbd6e8a18d239cc682 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sat, 23 Mar 2024 11:22:38 +0100 Subject: [PATCH 1412/1691] Fix building images for nightlies (#114054) --- .github/workflows/builder.yml | 4 ++-- Dockerfile | 9 +++------ requirements_test.txt | 2 +- script/hassfest/docker.py | 7 ++----- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 09277721e48..09a25eac37a 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -148,7 +148,7 @@ jobs: sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \ homeassistant/package_constraints.txt - python -m script.gen_requirements_all + sed -i "s|home-assistant-frontend==.*||" requirements_all.txt fi if [[ "$(ls home_assistant_intents*.whl)" =~ ^home_assistant_intents-(.*)-py3-none-any.whl$ ]]; then @@ -166,7 +166,7 @@ jobs: sed -i "s|home-assistant-intents==.*|home-assistant-intents==${BASH_REMATCH[1]}|" \ homeassistant/package_constraints.txt - python -m script.gen_requirements_all + sed -i "s|home-assistant-intents==.*||" requirements_all.txt fi - name: Adjustments for armhf diff --git a/Dockerfile b/Dockerfile index 700964d93ea..2a27402be6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ ENV \ ARG QEMU_CPU # Install uv -RUN pip3 install uv==0.1.22 +RUN pip3 install uv==0.1.24 WORKDIR /usr/src @@ -26,11 +26,8 @@ RUN \ COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/ RUN \ - if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \ - uv pip install homeassistant/home_assistant_frontend-*.whl; \ - fi \ - && if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \ - uv pip install homeassistant/home_assistant_intents-*.whl; \ + if ls homeassistant/home_assistant_*.whl 1> /dev/null 2>&1; then \ + uv pip install homeassistant/home_assistant_*.whl; \ fi \ && if [ "${BUILD_ARCH}" = "i386" ]; then \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ diff --git a/requirements_test.txt b/requirements_test.txt index e19cfc1a363..888c39fd239 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -51,4 +51,4 @@ types-pytz==2023.3.1.1 types-PyYAML==6.0.12.12 types-requests==2.31.0.3 types-xmltodict==0.13.0.3 -uv==0.1.22 \ No newline at end of file +uv==0.1.24 \ No newline at end of file diff --git a/script/hassfest/docker.py b/script/hassfest/docker.py index 8a3c3b6937d..4e348d4ae6c 100644 --- a/script/hassfest/docker.py +++ b/script/hassfest/docker.py @@ -34,11 +34,8 @@ RUN \ COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/ RUN \ - if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \ - uv pip install homeassistant/home_assistant_frontend-*.whl; \ - fi \ - && if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \ - uv pip install homeassistant/home_assistant_intents-*.whl; \ + if ls homeassistant/home_assistant_*.whl 1> /dev/null 2>&1; then \ + uv pip install homeassistant/home_assistant_*.whl; \ fi \ && if [ "${{BUILD_ARCH}}" = "i386" ]; then \ LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \ From 7b0abb00aaac63b9a63b2d8dc4bdc53fe1afe336 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 23 Mar 2024 11:24:17 +0100 Subject: [PATCH 1413/1691] Show correct ipv6 address in System Monitor (#114024) --- homeassistant/components/systemmonitor/sensor.py | 6 ++++++ tests/components/systemmonitor/conftest.py | 16 +++++++++++++++- .../snapshots/test_diagnostics.ambr | 2 +- .../systemmonitor/snapshots/test_sensor.ambr | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index b2dacc27327..e20f4703ab8 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -7,6 +7,7 @@ import contextlib from dataclasses import dataclass from datetime import datetime from functools import lru_cache +import ipaddress import logging import socket import sys @@ -136,6 +137,11 @@ def get_ip_address( if entity.argument in addresses: for addr in addresses[entity.argument]: if addr.family == IF_ADDRS_FAMILY[entity.entity_description.key]: + address = ipaddress.ip_address(addr.address) + if address.version == 6 and ( + address.is_link_local or address.is_loopback + ): + continue return addr.address return None diff --git a/tests/components/systemmonitor/conftest.py b/tests/components/systemmonitor/conftest.py index 3994b1cdfba..e6e1b35e66d 100644 --- a/tests/components/systemmonitor/conftest.py +++ b/tests/components/systemmonitor/conftest.py @@ -129,7 +129,21 @@ def mock_psutil(mock_process: list[MockProcess]) -> Generator: "255.255.255.0", "255.255.255.255", None, - ) + ), + snicaddr( + socket.AF_INET6, + "fe80::baf2:8a90:4f78:b1cb%end0", + "ffff:ffff:ffff:ffff::", + None, + None, + ), + snicaddr( + socket.AF_INET6, + "2a00:1f:2103:3a01:3333:2222:1111:0000", + "ffff:ffff:ffff:ffff::", + None, + None, + ), ], "eth1": [ snicaddr( diff --git a/tests/components/systemmonitor/snapshots/test_diagnostics.ambr b/tests/components/systemmonitor/snapshots/test_diagnostics.ambr index 883c90d9d5b..b50e051c816 100644 --- a/tests/components/systemmonitor/snapshots/test_diagnostics.ambr +++ b/tests/components/systemmonitor/snapshots/test_diagnostics.ambr @@ -4,7 +4,7 @@ 'coordinators': dict({ 'data': dict({ 'addresses': dict({ - 'eth0': "[snicaddr(family=, address='192.168.1.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]", + 'eth0': "[snicaddr(family=, address='192.168.1.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None), snicaddr(family=, address='fe80::baf2:8a90:4f78:b1cb%end0', netmask='ffff:ffff:ffff:ffff::', broadcast=None, ptp=None), snicaddr(family=, address='2a00:1f:2103:3a01:3333:2222:1111:0000', netmask='ffff:ffff:ffff:ffff::', broadcast=None, ptp=None)]", 'eth1': "[snicaddr(family=, address='192.168.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]", 'vethxyzxyz': "[snicaddr(family=, address='172.16.10.1', netmask='255.255.255.0', broadcast='255.255.255.255', ptp=None)]", }), diff --git a/tests/components/systemmonitor/snapshots/test_sensor.ambr b/tests/components/systemmonitor/snapshots/test_sensor.ambr index 5374ea886d3..3fe9ae7e809 100644 --- a/tests/components/systemmonitor/snapshots/test_sensor.ambr +++ b/tests/components/systemmonitor/snapshots/test_sensor.ambr @@ -95,7 +95,7 @@ }) # --- # name: test_sensor[System Monitor IPv6 address eth0 - state] - 'unknown' + '2a00:1f:2103:3a01:3333:2222:1111:0000' # --- # name: test_sensor[System Monitor IPv6 address eth1 - attributes] ReadOnlyDict({ From de62b7774f24dc4b9dca3d0ae8ecab755332e7ef Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 00:25:05 -1000 Subject: [PATCH 1414/1691] Reduce sqlalchemy reflection overhead at recorder setup time (#113989) --- homeassistant/components/recorder/auto_repairs/schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/recorder/auto_repairs/schema.py b/homeassistant/components/recorder/auto_repairs/schema.py index 2b533b79e6c..aa2fc1bb8cb 100644 --- a/homeassistant/components/recorder/auto_repairs/schema.py +++ b/homeassistant/components/recorder/auto_repairs/schema.py @@ -6,7 +6,7 @@ from collections.abc import Iterable, Mapping import logging from typing import TYPE_CHECKING -from sqlalchemy import MetaData +from sqlalchemy import MetaData, Table from sqlalchemy.exc import OperationalError from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm.attributes import InstrumentedAttribute @@ -94,9 +94,9 @@ def _validate_table_schema_has_correct_collation( with session_scope(session=instance.get_session(), read_only=True) as session: table = table_object.__tablename__ metadata_obj = MetaData() + reflected_table = Table(table, metadata_obj, autoload_with=instance.engine) connection = session.connection() - metadata_obj.reflect(bind=connection) - dialect_kwargs = metadata_obj.tables[table].dialect_kwargs + dialect_kwargs = reflected_table.dialect_kwargs # Check if the table has a collation set, if its not set than its # using the server default collation for the database From 9451a14e5cbf8ac193c5f3443d4baadcd3dabfd1 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 23 Mar 2024 11:30:38 +0000 Subject: [PATCH 1415/1691] Migrate UpdateCoordinator to its own file in aurora_abb_powerone (#114053) Migrate *UpdateCoordinator to its own file. --- .../aurora_abb_powerone/__init__.py | 76 +---------------- .../aurora_abb_powerone/coordinator.py | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+), 74 deletions(-) create mode 100644 homeassistant/components/aurora_abb_powerone/coordinator.py diff --git a/homeassistant/components/aurora_abb_powerone/__init__.py b/homeassistant/components/aurora_abb_powerone/__init__.py index 05e5819e36e..8d236b30d97 100644 --- a/homeassistant/components/aurora_abb_powerone/__init__.py +++ b/homeassistant/components/aurora_abb_powerone/__init__.py @@ -11,17 +11,13 @@ # sudo chmod 777 /dev/ttyUSB0 import logging -from time import sleep - -from aurorapy.client import AuroraError, AuroraSerialClient, AuroraTimeoutError -from serial import SerialException from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_PORT, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN, SCAN_INTERVAL +from .const import DOMAIN +from .coordinator import AuroraAbbDataUpdateCoordinator PLATFORMS = [Platform.SENSOR] @@ -52,71 +48,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): # pylint: disable=hass-enforce-coordinator-module - """Class to manage fetching AuroraAbbPowerone data.""" - - def __init__(self, hass: HomeAssistant, comport: str, address: int) -> None: - """Initialize the data update coordinator.""" - self.available_prev = False - self.available = False - self.client = AuroraSerialClient(address, comport, parity="N", timeout=1) - super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) - - def _update_data(self) -> dict[str, float]: - """Fetch new state data for the sensor. - - This is the only function that should fetch new data for Home Assistant. - """ - data: dict[str, float] = {} - self.available_prev = self.available - retries: int = 3 - while retries > 0: - try: - self.client.connect() - - # read ADC channel 3 (grid power output) - power_watts = self.client.measure(3, True) - temperature_c = self.client.measure(21) - energy_wh = self.client.cumulated_energy(5) - [alarm, *_] = self.client.alarms() - except AuroraTimeoutError: - self.available = False - _LOGGER.debug("No response from inverter (could be dark)") - retries = 0 - except (SerialException, AuroraError) as error: - self.available = False - retries -= 1 - if retries <= 0: - raise UpdateFailed(error) from error - _LOGGER.debug( - "Exception: %s occurred, %d retries remaining", - repr(error), - retries, - ) - sleep(1) - else: - data["instantaneouspower"] = round(power_watts, 1) - data["temp"] = round(temperature_c, 1) - data["totalenergy"] = round(energy_wh / 1000, 2) - data["alarm"] = alarm - self.available = True - retries = 0 - finally: - if self.available != self.available_prev: - if self.available: - _LOGGER.info("Communication with %s back online", self.name) - else: - _LOGGER.info( - "Communication with %s lost", - self.name, - ) - if self.client.serline.isOpen(): - self.client.close() - - return data - - async def _async_update_data(self) -> dict[str, float]: - """Update inverter data in the executor.""" - return await self.hass.async_add_executor_job(self._update_data) diff --git a/homeassistant/components/aurora_abb_powerone/coordinator.py b/homeassistant/components/aurora_abb_powerone/coordinator.py new file mode 100644 index 00000000000..68888a96603 --- /dev/null +++ b/homeassistant/components/aurora_abb_powerone/coordinator.py @@ -0,0 +1,82 @@ +"""DataUpdateCoordinator for the aurora_abb_powerone integration.""" + +import logging +from time import sleep + +from aurorapy.client import AuroraError, AuroraSerialClient, AuroraTimeoutError +from serial import SerialException + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, SCAN_INTERVAL + +_LOGGER = logging.getLogger(__name__) + + +class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): # pylint: disable=hass-enforce-coordinator-module + """Class to manage fetching AuroraAbbPowerone data.""" + + def __init__(self, hass: HomeAssistant, comport: str, address: int) -> None: + """Initialize the data update coordinator.""" + self.available_prev = False + self.available = False + self.client = AuroraSerialClient(address, comport, parity="N", timeout=1) + super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) + + def _update_data(self) -> dict[str, float]: + """Fetch new state data for the sensors. + + This is the only function that should fetch new data for Home Assistant. + """ + data: dict[str, float] = {} + self.available_prev = self.available + retries: int = 3 + while retries > 0: + try: + self.client.connect() + + # read ADC channel 3 (grid power output) + power_watts = self.client.measure(3, True) + temperature_c = self.client.measure(21) + energy_wh = self.client.cumulated_energy(5) + [alarm, *_] = self.client.alarms() + except AuroraTimeoutError: + self.available = False + _LOGGER.debug("No response from inverter (could be dark)") + retries = 0 + except (SerialException, AuroraError) as error: + self.available = False + retries -= 1 + if retries <= 0: + raise UpdateFailed(error) from error + _LOGGER.debug( + "Exception: %s occurred, %d retries remaining", + repr(error), + retries, + ) + sleep(1) + else: + data["instantaneouspower"] = round(power_watts, 1) + data["temp"] = round(temperature_c, 1) + data["totalenergy"] = round(energy_wh / 1000, 2) + data["alarm"] = alarm + self.available = True + retries = 0 + finally: + if self.available != self.available_prev: + if self.available: + _LOGGER.info("Communication with %s back online", self.name) + else: + _LOGGER.info( + "Communication with %s lost", + self.name, + ) + if self.client.serline.isOpen(): + self.client.close() + + return data + + async def _async_update_data(self) -> dict[str, float]: + """Update inverter data in the executor.""" + return await self.hass.async_add_executor_job(self._update_data) From 8d51ff0f2c8996e1677ebebcc5c8a255bea13a74 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Sat, 23 Mar 2024 13:29:29 +0100 Subject: [PATCH 1416/1691] Create registry matrix to run publishing for images in parallel (#114060) --- .github/workflows/builder.yml | 71 ++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 09a25eac37a..0c25057b1b1 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -337,6 +337,9 @@ jobs: contents: read packages: write id-token: write + strategy: + matrix: + registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"] steps: - name: Checkout the repository uses: actions/checkout@v4.1.2 @@ -347,12 +350,14 @@ jobs: cosign-release: "v2.2.3" - name: Login to DockerHub + if: matrix.registry == 'docker.io/homeassistant' uses: docker/login-action@v3.1.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry + if: matrix.registry == 'ghcr.io/home-assistant' uses: docker/login-action@v3.1.0 with: registry: ghcr.io @@ -367,41 +372,37 @@ jobs: function create_manifest() { local tag_l=${1} local tag_r=${2} + local registry=${{ matrix.registry }} - for registry in "ghcr.io/home-assistant" "docker.io/homeassistant" - do + docker manifest create "${registry}/home-assistant:${tag_l}" \ + "${registry}/amd64-homeassistant:${tag_r}" \ + "${registry}/i386-homeassistant:${tag_r}" \ + "${registry}/armhf-homeassistant:${tag_r}" \ + "${registry}/armv7-homeassistant:${tag_r}" \ + "${registry}/aarch64-homeassistant:${tag_r}" - docker manifest create "${registry}/home-assistant:${tag_l}" \ - "${registry}/amd64-homeassistant:${tag_r}" \ - "${registry}/i386-homeassistant:${tag_r}" \ - "${registry}/armhf-homeassistant:${tag_r}" \ - "${registry}/armv7-homeassistant:${tag_r}" \ - "${registry}/aarch64-homeassistant:${tag_r}" + docker manifest annotate "${registry}/home-assistant:${tag_l}" \ + "${registry}/amd64-homeassistant:${tag_r}" \ + --os linux --arch amd64 - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/amd64-homeassistant:${tag_r}" \ - --os linux --arch amd64 + docker manifest annotate "${registry}/home-assistant:${tag_l}" \ + "${registry}/i386-homeassistant:${tag_r}" \ + --os linux --arch 386 - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/i386-homeassistant:${tag_r}" \ - --os linux --arch 386 + docker manifest annotate "${registry}/home-assistant:${tag_l}" \ + "${registry}/armhf-homeassistant:${tag_r}" \ + --os linux --arch arm --variant=v6 - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/armhf-homeassistant:${tag_r}" \ - --os linux --arch arm --variant=v6 + docker manifest annotate "${registry}/home-assistant:${tag_l}" \ + "${registry}/armv7-homeassistant:${tag_r}" \ + --os linux --arch arm --variant=v7 - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/armv7-homeassistant:${tag_r}" \ - --os linux --arch arm --variant=v7 + docker manifest annotate "${registry}/home-assistant:${tag_l}" \ + "${registry}/aarch64-homeassistant:${tag_r}" \ + --os linux --arch arm64 --variant=v8 - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/aarch64-homeassistant:${tag_r}" \ - --os linux --arch arm64 --variant=v8 - - docker manifest push --purge "${registry}/home-assistant:${tag_l}" - cosign sign --yes "${registry}/home-assistant:${tag_l}" - - done + docker manifest push --purge "${registry}/home-assistant:${tag_l}" + cosign sign --yes "${registry}/home-assistant:${tag_l}" } function validate_image() { @@ -434,12 +435,14 @@ jobs: validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}" validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}" - # Upload images to dockerhub - push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}" - push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}" - push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}" - push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}" - push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}" + if [[ "${{ matrix.registry }}" == "docker.io/homeassistant" ]]; then + # Upload images to dockerhub + push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}" + push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}" + push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}" + push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}" + push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}" + fi # Create version tag create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}" From ebda0d832c923554eb9b5d0c8512bfc26f1156cb Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Sat, 23 Mar 2024 14:27:00 +0100 Subject: [PATCH 1417/1691] Enable turn_on / turn_off features for HitachiAirToAirHeatPump in Overkiz (#112990) Enable turn_on / turn_off features --- .../climate_entities/hitachi_air_to_air_heat_pump_ovp.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py index c1792a8e050..86cde4fc4db 100644 --- a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py +++ b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py @@ -95,6 +95,7 @@ class HitachiAirToAirHeatPumpOVP(OverkizEntity, ClimateEntity): _attr_target_temperature_step = 1.0 _attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_translation_key = DOMAIN + _enable_turn_on_off_backwards_compatibility = False def __init__( self, device_url: str, coordinator: OverkizDataUpdateCoordinator @@ -106,6 +107,8 @@ class HitachiAirToAirHeatPumpOVP(OverkizEntity, ClimateEntity): ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.TURN_OFF + | ClimateEntityFeature.TURN_ON ) if self.device.states.get(OverkizState.OVP_SWING): From 1a6ff5c2d57198d31fc536fdab0bad3c347ba153 Mon Sep 17 00:00:00 2001 From: On Freund Date: Sat, 23 Mar 2024 15:45:17 +0200 Subject: [PATCH 1418/1691] Bump pyrisco to 0.6.0 (#114063) --- homeassistant/components/risco/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/risco/manifest.json b/homeassistant/components/risco/manifest.json index b5d8c4442fd..4c590b95e52 100644 --- a/homeassistant/components/risco/manifest.json +++ b/homeassistant/components/risco/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_push", "loggers": ["pyrisco"], "quality_scale": "platinum", - "requirements": ["pyrisco==0.5.10"] + "requirements": ["pyrisco==0.6.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8a555443f3d..f6811fe23d5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2083,7 +2083,7 @@ pyrecswitch==1.0.2 pyrepetierng==0.1.0 # homeassistant.components.risco -pyrisco==0.5.10 +pyrisco==0.6.0 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8d4df18f8aa..ff5fdfee517 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1616,7 +1616,7 @@ pyqwikswitch==0.93 pyrainbird==4.0.2 # homeassistant.components.risco -pyrisco==0.5.10 +pyrisco==0.6.0 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 From 3ec9312f0e42079786fc352606c51d371d93cc8e Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Sat, 23 Mar 2024 13:53:40 +0000 Subject: [PATCH 1419/1691] Fix bug in roon media player to use correct 'seek position' while playing a track. (#113999) Chance source of 'seek position' to be the one that roon updates while playing! --- homeassistant/components/roon/media_player.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roon/media_player.py b/homeassistant/components/roon/media_player.py index c88b887c10e..3b1735cd2fc 100644 --- a/homeassistant/components/roon/media_player.py +++ b/homeassistant/components/roon/media_player.py @@ -233,12 +233,13 @@ class RoonDevice(MediaPlayerEntity): } now_playing_data = None + media_position = convert(player_data.get("seek_position"), int, 0) + try: now_playing_data = player_data["now_playing"] media_title = now_playing_data["three_line"]["line1"] media_artist = now_playing_data["three_line"]["line2"] media_album_name = now_playing_data["three_line"]["line3"] - media_position = convert(now_playing_data["seek_position"], int, 0) media_duration = convert(now_playing_data.get("length"), int, 0) image_id = now_playing_data.get("image_key") except KeyError: From 3c13a2835758af252b6c5041bada3c466760af86 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 23 Mar 2024 17:24:34 +0100 Subject: [PATCH 1420/1691] Bump holidays to 0.45 (#114069) --- homeassistant/components/holiday/manifest.json | 2 +- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/holiday/manifest.json b/homeassistant/components/holiday/manifest.json index 5f78d961810..f1bc60dece4 100644 --- a/homeassistant/components/holiday/manifest.json +++ b/homeassistant/components/holiday/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/holiday", "iot_class": "local_polling", - "requirements": ["holidays==0.44", "babel==2.13.1"] + "requirements": ["holidays==0.45", "babel==2.13.1"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 96a3b53797c..6b17a980870 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_polling", "loggers": ["holidays"], "quality_scale": "internal", - "requirements": ["holidays==0.44"] + "requirements": ["holidays==0.45"] } diff --git a/requirements_all.txt b/requirements_all.txt index f6811fe23d5..27cdf5a353f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1071,7 +1071,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.44 +holidays==0.45 # homeassistant.components.frontend home-assistant-frontend==20240307.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ff5fdfee517..8ccb0f1dd24 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -870,7 +870,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.44 +holidays==0.45 # homeassistant.components.frontend home-assistant-frontend==20240307.0 From dbb4cf0ee7daef771165920f5077e55e98389b4c Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sat, 23 Mar 2024 13:36:03 -0400 Subject: [PATCH 1421/1691] Add Rachio smart hose timer support (#107901) Co-authored-by: J. Nick Koston --- .coveragerc | 1 + homeassistant/components/rachio/__init__.py | 7 +- homeassistant/components/rachio/const.py | 17 +++ .../components/rachio/coordinator.py | 56 ++++++++ homeassistant/components/rachio/device.py | 53 +++++++- homeassistant/components/rachio/icons.json | 3 +- homeassistant/components/rachio/services.yaml | 11 ++ homeassistant/components/rachio/strings.json | 10 ++ homeassistant/components/rachio/switch.py | 122 ++++++++++++++++-- 9 files changed, 263 insertions(+), 17 deletions(-) create mode 100644 homeassistant/components/rachio/coordinator.py diff --git a/.coveragerc b/.coveragerc index c58601b2245..8b3067313d3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1062,6 +1062,7 @@ omit = homeassistant/components/rabbitair/fan.py homeassistant/components/rachio/__init__.py homeassistant/components/rachio/binary_sensor.py + homeassistant/components/rachio/coordinator.py homeassistant/components/rachio/device.py homeassistant/components/rachio/entity.py homeassistant/components/rachio/switch.py diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index e5a64d25fc8..f91a7b4fa75 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -83,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady from error # Check for Rachio controller devices - if not person.controllers: + if not person.controllers and not person.base_stations: _LOGGER.error("No Rachio devices found in account %s", person.username) return False _LOGGER.info( @@ -91,10 +91,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "%d Rachio device(s) found; The url %s must be accessible from the internet" " in order to receive updates" ), - len(person.controllers), + len(person.controllers) + len(person.base_stations), webhook_url, ) + for base in person.base_stations: + await base.coordinator.async_config_entry_first_refresh() + # Enable platform hass.data.setdefault(DOMAIN, {})[entry.entry_id] = person async_register_webhook(hass, entry) diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index dad044e5049..22c92be2b74 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -26,6 +26,7 @@ KEY_NAME = "name" KEY_MODEL = "model" KEY_ON = "on" KEY_DURATION = "totalDuration" +KEY_DURATION_MINUTES = "duration" KEY_RAIN_DELAY = "rainDelayExpirationDate" KEY_RAIN_DELAY_END = "endTime" KEY_RAIN_SENSOR_TRIPPED = "rainSensorTripped" @@ -47,6 +48,21 @@ KEY_CUSTOM_SHADE = "customShade" KEY_CUSTOM_CROP = "customCrop" KEY_CUSTOM_SLOPE = "customSlope" +# Smart Hose timer +KEY_BASE_STATIONS = "baseStations" +KEY_VALVES = "valves" +KEY_REPORTED_STATE = "reportedState" +KEY_STATE = "state" +KEY_CONNECTED = "connected" +KEY_CURRENT_STATUS = "lastWateringAction" +KEY_DETECT_FLOW = "detectFlow" +KEY_BATTERY_STATUS = "batteryStatus" +KEY_REASON = "reason" +KEY_DEFAULT_RUNTIME = "defaultRuntimeSeconds" +KEY_DURATION_SECONDS = "durationSeconds" +KEY_FLOW_DETECTED = "flowDetected" +KEY_START_TIME = "start" + STATUS_ONLINE = "ONLINE" MODEL_GENERATION_1 = "GENERATION1" @@ -56,6 +72,7 @@ SERVICE_PAUSE_WATERING = "pause_watering" SERVICE_RESUME_WATERING = "resume_watering" SERVICE_STOP_WATERING = "stop_watering" SERVICE_SET_ZONE_MOISTURE = "set_zone_moisture_percent" +SERVICE_START_WATERING = "start_watering" SERVICE_START_MULTIPLE_ZONES = "start_multiple_zone_schedule" SIGNAL_RACHIO_UPDATE = f"{DOMAIN}_update" diff --git a/homeassistant/components/rachio/coordinator.py b/homeassistant/components/rachio/coordinator.py new file mode 100644 index 00000000000..4f8cc87daef --- /dev/null +++ b/homeassistant/components/rachio/coordinator.py @@ -0,0 +1,56 @@ +"""Coordinator object for the Rachio integration.""" + +from datetime import timedelta +import logging +from typing import Any + +from rachiopy import Rachio +from requests.exceptions import Timeout + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, KEY_ID, KEY_VALVES + +_LOGGER = logging.getLogger(__name__) + +UPDATE_DELAY_TIME = 8 + + +class RachioUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): + """Coordinator Class for Rachio Hose Timers.""" + + def __init__( + self, + hass: HomeAssistant, + rachio: Rachio, + base_station, + base_count: int, + ) -> None: + """Initialize the Rachio Update Coordinator.""" + self.hass = hass + self.rachio = rachio + self.base_station = base_station + super().__init__( + hass, + _LOGGER, + name=f"{DOMAIN} update coordinator", + # To avoid exceeding the rate limit, increase polling interval for + # each additional base station on the account + update_interval=timedelta(minutes=(base_count + 1)), + # Debouncer used because the API takes a bit to update state changes + request_refresh_debouncer=Debouncer( + hass, _LOGGER, cooldown=UPDATE_DELAY_TIME, immediate=False + ), + ) + + async def _async_update_data(self) -> dict[str, Any]: + """Update smart hose timer data.""" + try: + data = await self.hass.async_add_executor_job( + self.rachio.valve.list_valves, self.base_station[KEY_ID] + ) + except Timeout as err: + raise UpdateFailed(f"Could not connect to the Rachio API: {err}") from err + return {valve[KEY_ID]: valve for valve in data[1][KEY_VALVES]} diff --git a/homeassistant/components/rachio/device.py b/homeassistant/components/rachio/device.py index dea91fcc6fd..c018d7e6f86 100644 --- a/homeassistant/components/rachio/device.py +++ b/homeassistant/components/rachio/device.py @@ -17,6 +17,7 @@ from homeassistant.helpers import config_validation as cv from .const import ( DOMAIN, + KEY_BASE_STATIONS, KEY_DEVICES, KEY_ENABLED, KEY_EXTERNAL_ID, @@ -37,6 +38,7 @@ from .const import ( SERVICE_STOP_WATERING, WEBHOOK_CONST_ID, ) +from .coordinator import RachioUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -67,6 +69,7 @@ class RachioPerson: self.username = None self._id: str | None = None self._controllers: list[RachioIro] = [] + self._base_stations: list[RachioBaseStation] = [] async def async_setup(self, hass: HomeAssistant) -> None: """Create rachio devices and services.""" @@ -78,30 +81,34 @@ class RachioPerson: can_pause = True break - all_devices = [rachio_iro.name for rachio_iro in self._controllers] + all_controllers = [rachio_iro.name for rachio_iro in self._controllers] def pause_water(service: ServiceCall) -> None: """Service to pause watering on all or specific controllers.""" duration = service.data[ATTR_DURATION] - devices = service.data.get(ATTR_DEVICES, all_devices) + devices = service.data.get(ATTR_DEVICES, all_controllers) for iro in self._controllers: if iro.name in devices: iro.pause_watering(duration) def resume_water(service: ServiceCall) -> None: """Service to resume watering on all or specific controllers.""" - devices = service.data.get(ATTR_DEVICES, all_devices) + devices = service.data.get(ATTR_DEVICES, all_controllers) for iro in self._controllers: if iro.name in devices: iro.resume_watering() def stop_water(service: ServiceCall) -> None: """Service to stop watering on all or specific controllers.""" - devices = service.data.get(ATTR_DEVICES, all_devices) + devices = service.data.get(ATTR_DEVICES, all_controllers) for iro in self._controllers: if iro.name in devices: iro.stop_watering() + # If only hose timers on account, none of these services apply + if not all_controllers: + return + hass.services.async_register( DOMAIN, SERVICE_STOP_WATERING, @@ -145,6 +152,9 @@ class RachioPerson: raise ConfigEntryNotReady(f"API Error: {data}") self.username = data[1][KEY_USERNAME] devices: list[dict[str, Any]] = data[1][KEY_DEVICES] + base_station_data = rachio.valve.list_base_stations(self._id) + base_stations: list[dict[str, Any]] = base_station_data[1][KEY_BASE_STATIONS] + for controller in devices: webhooks = rachio.notification.get_device_webhook(controller[KEY_ID])[1] # The API does not provide a way to tell if a controller is shared @@ -174,6 +184,14 @@ class RachioPerson: rachio_iro.setup() self._controllers.append(rachio_iro) + base_count = len(base_stations) + self._base_stations.extend( + RachioBaseStation( + rachio, base, RachioUpdateCoordinator(hass, rachio, base, base_count) + ) + for base in base_stations + ) + _LOGGER.info('Using Rachio API as user "%s"', self.username) @property @@ -186,6 +204,11 @@ class RachioPerson: """Get a list of controllers managed by this account.""" return self._controllers + @property + def base_stations(self) -> list[RachioBaseStation]: + """List of smart hose timer base stations.""" + return self._base_stations + def start_multiple_zones(self, zones) -> None: """Start multiple zones.""" self.rachio.zone.start_multiple(zones) @@ -321,6 +344,28 @@ class RachioIro: _LOGGER.debug("Resuming watering on %s", self) +class RachioBaseStation: + """Represent a smart hose timer base station.""" + + def __init__( + self, rachio: Rachio, data: dict[str, Any], coordinator: RachioUpdateCoordinator + ) -> None: + """Initialize a hose time base station.""" + self.rachio = rachio + self._id = data[KEY_ID] + self.serial_number = data[KEY_SERIAL_NUMBER] + self.mac_address = data[KEY_MAC_ADDRESS] + self.coordinator = coordinator + + def start_watering(self, valve_id: str, duration: int) -> None: + """Start watering on this valve.""" + self.rachio.valve.start_watering(valve_id, duration) + + def stop_watering(self, valve_id: str) -> None: + """Stop watering on this valve.""" + self.rachio.valve.stop_watering(valve_id) + + def is_invalid_auth_code(http_status_code: int) -> bool: """HTTP status codes that mean invalid auth.""" return http_status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN) diff --git a/homeassistant/components/rachio/icons.json b/homeassistant/components/rachio/icons.json index 3b3ec860514..dfab8788fc8 100644 --- a/homeassistant/components/rachio/icons.json +++ b/homeassistant/components/rachio/icons.json @@ -14,6 +14,7 @@ "start_multiple_zone_schedule": "mdi:play", "pause_watering": "mdi:pause", "resume_watering": "mdi:play", - "stop_watering": "mdi:stop" + "stop_watering": "mdi:stop", + "start_watering": "mdi:water" } } diff --git a/homeassistant/components/rachio/services.yaml b/homeassistant/components/rachio/services.yaml index 6a6a8bf5cf6..72582de870a 100644 --- a/homeassistant/components/rachio/services.yaml +++ b/homeassistant/components/rachio/services.yaml @@ -11,6 +11,17 @@ set_zone_moisture_percent: min: 0 max: 100 unit_of_measurement: "%" +start_watering: + target: + entity: + integration: rachio + domain: switch + fields: + duration: + example: 15 + required: false + selector: + object: start_multiple_zone_schedule: target: entity: diff --git a/homeassistant/components/rachio/strings.json b/homeassistant/components/rachio/strings.json index 560c300db17..2e4de262d21 100644 --- a/homeassistant/components/rachio/strings.json +++ b/homeassistant/components/rachio/strings.json @@ -63,6 +63,16 @@ } } }, + "start_watering": { + "name": "Start watering", + "description": "Start a single zone, a schedule or any number of smart hose timers.", + "fields": { + "duration": { + "name": "Duration", + "description": "Number of minutes to run. For sprinkler zones the maximum duration is 3 hours, or 24 hours for smart hose timers. Leave empty for schedules." + } + } + }, "pause_watering": { "name": "Pause watering", "description": "Pause any currently running zones or schedules.", diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 8b8d10248e0..fe3d455df3c 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -11,19 +11,28 @@ import voluptuous as vol from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ENTITY_ID, ATTR_ID -from homeassistant.core import HomeAssistant, ServiceCall, callback +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, ServiceCall, callback from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers import ( + config_validation as cv, + device_registry as dr, + entity_platform, +) +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_track_point_in_utc_time +from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.dt import as_timestamp, now, parse_datetime, utc_from_timestamp from .const import ( CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS, + DEFAULT_NAME, DOMAIN as DOMAIN_RACHIO, + KEY_CONNECTED, + KEY_CURRENT_STATUS, KEY_CUSTOM_CROP, KEY_CUSTOM_SHADE, KEY_CUSTOM_SLOPE, @@ -36,7 +45,9 @@ from .const import ( KEY_ON, KEY_RAIN_DELAY, KEY_RAIN_DELAY_END, + KEY_REPORTED_STATE, KEY_SCHEDULE_ID, + KEY_STATE, KEY_SUBTYPE, KEY_SUMMARY, KEY_TYPE, @@ -46,6 +57,7 @@ from .const import ( SCHEDULE_TYPE_FLEX, SERVICE_SET_ZONE_MOISTURE, SERVICE_START_MULTIPLE_ZONES, + SERVICE_START_WATERING, SIGNAL_RACHIO_CONTROLLER_UPDATE, SIGNAL_RACHIO_RAIN_DELAY_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, @@ -55,6 +67,7 @@ from .const import ( SLOPE_SLIGHT, SLOPE_STEEP, ) +from .coordinator import RachioUpdateCoordinator from .device import RachioPerson from .entity import RachioDevice from .webhooks import ( @@ -80,6 +93,7 @@ ATTR_SCHEDULE_ENABLED = "Enabled" ATTR_SCHEDULE_DURATION = "Duration" ATTR_SCHEDULE_TYPE = "Type" ATTR_SORT_ORDER = "sortOrder" +ATTR_WATERING_DURATION = "Watering Duration seconds" ATTR_ZONE_NUMBER = "Zone number" ATTR_ZONE_SHADE = "Shade" ATTR_ZONE_SLOPE = "Slope" @@ -141,6 +155,19 @@ async def async_setup_entry( else: raise HomeAssistantError("No matching zones found in given entity_ids") + platform = entity_platform.async_get_current_platform() + platform.async_register_entity_service( + SERVICE_START_WATERING, + { + vol.Optional(ATTR_DURATION): cv.positive_int, + }, + "turn_on", + ) + + # If only hose timers on account, none of these services apply + if not zone_entities: + return + hass.services.async_register( DOMAIN_RACHIO, SERVICE_START_MULTIPLE_ZONES, @@ -176,6 +203,11 @@ def _create_entities(hass: HomeAssistant, config_entry: ConfigEntry) -> list[Ent RachioSchedule(person, controller, schedule, current_schedule) for schedule in schedules + flex_schedules ) + entities.extend( + RachioValve(person, base_station, valve, base_station.coordinator) + for base_station in person.base_stations + for valve in base_station.coordinator.data.values() + ) return entities @@ -246,9 +278,9 @@ class RachioRainDelay(RachioSwitch): _attr_has_entity_name = True _attr_translation_key = "rain_delay" - def __init__(self, controller): + def __init__(self, controller) -> None: """Set up a Rachio rain delay switch.""" - self._cancel_update = None + self._cancel_update: CALLBACK_TYPE | None = None super().__init__(controller) @property @@ -324,7 +356,7 @@ class RachioZone(RachioSwitch): _attr_icon = "mdi:water" - def __init__(self, person, controller, data, current_schedule): + def __init__(self, person, controller, data, current_schedule) -> None: """Initialize a new Rachio Zone.""" self.id = data[KEY_ID] self._attr_name = data[KEY_NAME] @@ -379,11 +411,14 @@ class RachioZone(RachioSwitch): self.turn_off() # Start this zone - manual_run_time = timedelta( - minutes=self._person.config_entry.options.get( - CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS + if ATTR_DURATION in kwargs: + manual_run_time = timedelta(minutes=kwargs[ATTR_DURATION]) + else: + manual_run_time = timedelta( + minutes=self._person.config_entry.options.get( + CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS + ) ) - ) # The API limit is 3 hours, and requires an int be passed self._controller.rachio.zone.start(self.zone_id, manual_run_time.seconds) _LOGGER.debug( @@ -435,7 +470,7 @@ class RachioZone(RachioSwitch): class RachioSchedule(RachioSwitch): """Representation of one fixed schedule on the Rachio Iro.""" - def __init__(self, person, controller, data, current_schedule): + def __init__(self, person, controller, data, current_schedule) -> None: """Initialize a new Rachio Schedule.""" self._schedule_id = data[KEY_ID] self._duration = data[KEY_DURATION] @@ -509,3 +544,70 @@ class RachioSchedule(RachioSwitch): self.hass, SIGNAL_RACHIO_SCHEDULE_UPDATE, self._async_handle_update ) ) + + +class RachioValve(CoordinatorEntity[RachioUpdateCoordinator], SwitchEntity): + """Representation of one smart hose timer valve.""" + + def __init__( + self, person, base, data, coordinator: RachioUpdateCoordinator + ) -> None: + """Initialize a new smart hose valve.""" + super().__init__(coordinator) + self._person = person + self._base = base + self.id = data[KEY_ID] + self._attr_name = data[KEY_NAME] + self._attr_unique_id = f"{self.id}-valve" + self._static_attrs = data[KEY_STATE][KEY_REPORTED_STATE] + self._attr_is_on = KEY_CURRENT_STATUS in self._static_attrs + self._attr_device_info = DeviceInfo( + identifiers={ + ( + DOMAIN_RACHIO, + self.id, + ) + }, + connections={(dr.CONNECTION_NETWORK_MAC, self._base.mac_address)}, + manufacturer=DEFAULT_NAME, + model="Smart Hose Timer", + name=self._attr_name, + configuration_url="https://app.rach.io", + ) + + @property + def available(self) -> bool: + """Return if the valve is available.""" + return super().available and self._static_attrs[KEY_CONNECTED] + + def turn_on(self, **kwargs: Any) -> None: + """Turn on this valve.""" + if ATTR_DURATION in kwargs: + manual_run_time = timedelta(minutes=kwargs[ATTR_DURATION]) + else: + manual_run_time = timedelta( + minutes=self._person.config_entry.options.get( + CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS + ) + ) + + self._base.start_watering(self.id, manual_run_time.seconds) + self._attr_is_on = True + self.schedule_update_ha_state(force_refresh=True) + _LOGGER.debug("Starting valve %s for %s", self.name, str(manual_run_time)) + + def turn_off(self, **kwargs: Any) -> None: + """Turn off this valve.""" + self._base.stop_watering(self.id) + self._attr_is_on = False + self.schedule_update_ha_state(force_refresh=True) + _LOGGER.debug("Stopping watering on valve %s", self.name) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated coordinator data.""" + data = self.coordinator.data[self.id] + + self._static_attrs = data[KEY_STATE][KEY_REPORTED_STATE] + self._attr_is_on = KEY_CURRENT_STATUS in self._static_attrs + super()._handle_coordinator_update() From d75315f2250d2f461f843cc0c8cc0c24aa720284 Mon Sep 17 00:00:00 2001 From: Jiaqi Wu Date: Sat, 23 Mar 2024 10:48:24 -0700 Subject: [PATCH 1422/1691] Add Lutron Serena tilt only wood blinds (#113791) --- .../components/lutron_caseta/cover.py | 108 +++++++++++++----- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index cca04e0a298..aa5c2f4e0b9 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -4,6 +4,7 @@ from typing import Any from homeassistant.components.cover import ( ATTR_POSITION, + ATTR_TILT_POSITION, DOMAIN, CoverDeviceClass, CoverEntity, @@ -18,26 +19,8 @@ from .const import DOMAIN as CASETA_DOMAIN from .models import LutronCasetaData -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the Lutron Caseta cover platform. - - Adds shades from the Caseta bridge associated with the config_entry as - cover entities. - """ - data: LutronCasetaData = hass.data[CASETA_DOMAIN][config_entry.entry_id] - bridge = data.bridge - cover_devices = bridge.get_devices_by_domain(DOMAIN) - async_add_entities( - LutronCasetaCover(cover_device, data) for cover_device in cover_devices - ) - - -class LutronCasetaCover(LutronCasetaDeviceUpdatableEntity, CoverEntity): - """Representation of a Lutron shade.""" +class LutronCasetaShade(LutronCasetaDeviceUpdatableEntity, CoverEntity): + """Representation of a Lutron shade with open/close functionality.""" _attr_supported_features = ( CoverEntityFeature.OPEN @@ -57,16 +40,16 @@ class LutronCasetaCover(LutronCasetaDeviceUpdatableEntity, CoverEntity): """Return the current position of cover.""" return self._device["current_state"] - async def async_stop_cover(self, **kwargs: Any) -> None: - """Top the cover.""" - await self._smartbridge.stop_cover(self.device_id) - async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" await self._smartbridge.lower_cover(self.device_id) await self.async_update() self.async_write_ha_state() + async def async_stop_cover(self, **kwargs: Any) -> None: + """Stop the cover.""" + await self._smartbridge.stop_cover(self.device_id) + async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" await self._smartbridge.raise_cover(self.device_id) @@ -75,6 +58,77 @@ class LutronCasetaCover(LutronCasetaDeviceUpdatableEntity, CoverEntity): async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the shade to a specific position.""" - if ATTR_POSITION in kwargs: - position = kwargs[ATTR_POSITION] - await self._smartbridge.set_value(self.device_id, position) + await self._smartbridge.set_value(self.device_id, kwargs[ATTR_POSITION]) + + +class LutronCasetaTiltOnlyBlind(LutronCasetaDeviceUpdatableEntity, CoverEntity): + """Representation of a Lutron tilt only blind.""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION + | CoverEntityFeature.OPEN_TILT + ) + _attr_device_class = CoverDeviceClass.BLIND + + @property + def is_closed(self) -> bool: + """Return if the blind is closed, either at position 0 or 100.""" + return self._device["tilt"] == 0 or self._device["tilt"] == 100 + + @property + def current_cover_tilt_position(self) -> int: + """Return the current tilt position of blind.""" + return self._device["tilt"] + + async def async_close_cover_tilt(self, **kwargs: Any) -> None: + """Close the blind.""" + await self._smartbridge.set_tilt(self.device_id, 0) + await self.async_update() + self.async_write_ha_state() + + async def async_open_cover_tilt(self, **kwargs: Any) -> None: + """Open the blind.""" + await self._smartbridge.set_tilt(self.device_id, 50) + await self.async_update() + self.async_write_ha_state() + + async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: + """Move the blind to a specific tilt.""" + self._smartbridge.set_tilt(self.device_id, kwargs[ATTR_TILT_POSITION]) + + +PYLUTRON_TYPE_TO_CLASSES = { + "SerenaTiltOnlyWoodBlind": LutronCasetaTiltOnlyBlind, + "SerenaHoneycombShade": LutronCasetaShade, + "SerenaRollerShade": LutronCasetaShade, + "TriathlonHoneycombShade": LutronCasetaShade, + "TriathlonRollerShade": LutronCasetaShade, + "QsWirelessShade": LutronCasetaShade, + "QsWirelessHorizontalSheerBlind": LutronCasetaShade, + "Shade": LutronCasetaShade, + "PalladiomWireFreeShade": LutronCasetaShade, +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Lutron Caseta cover platform. + + Adds shades from the Caseta bridge associated with the config_entry as + cover entities. + """ + data: LutronCasetaData = hass.data[CASETA_DOMAIN][config_entry.entry_id] + bridge = data.bridge + cover_devices = bridge.get_devices_by_domain(DOMAIN) + async_add_entities( + # default to standard LutronCasetaCover type if the pylutron type is not yet mapped + PYLUTRON_TYPE_TO_CLASSES.get(cover_device["type"], LutronCasetaShade)( + cover_device, data + ) + for cover_device in cover_devices + ) From c66162233275c40e471cd3f4e7ffb5cfe3c37c58 Mon Sep 17 00:00:00 2001 From: On Freund Date: Sat, 23 Mar 2024 20:35:12 +0200 Subject: [PATCH 1423/1691] Add Risco system binary sensors (#114062) * Add Risco system binary sensors * Remove leading underscore * Address code review commments --- homeassistant/components/risco/__init__.py | 9 +- .../components/risco/binary_sensor.py | 98 ++++++++++++++++++- homeassistant/components/risco/const.py | 1 + homeassistant/components/risco/strings.json | 24 +++++ tests/components/risco/conftest.py | 8 +- tests/components/risco/test_binary_sensor.py | 71 +++++++++++++- tests/components/risco/util.py | 15 +++ 7 files changed, 221 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/risco/__init__.py b/homeassistant/components/risco/__init__.py index 309cad7610e..531cd982a1e 100644 --- a/homeassistant/components/risco/__init__.py +++ b/homeassistant/components/risco/__init__.py @@ -17,7 +17,7 @@ from pyrisco import ( ) from pyrisco.cloud.alarm import Alarm from pyrisco.cloud.event import Event -from pyrisco.common import Partition, Zone +from pyrisco.common import Partition, System, Zone from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -42,6 +42,7 @@ from .const import ( DEFAULT_SCAN_INTERVAL, DOMAIN, EVENTS_COORDINATOR, + SYSTEM_UPDATE_SIGNAL, TYPE_LOCAL, ) @@ -122,6 +123,12 @@ async def _async_setup_local_entry(hass: HomeAssistant, entry: ConfigEntry) -> b entry.async_on_unload(risco.add_partition_handler(_partition)) + async def _system(system: System) -> None: + _LOGGER.debug("Risco system update") + async_dispatcher_send(hass, SYSTEM_UPDATE_SIGNAL) + + entry.async_on_unload(risco.add_system_handler(_system)) + entry.async_on_unload(entry.add_update_listener(_update_listener)) hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/risco/binary_sensor.py b/homeassistant/components/risco/binary_sensor.py index f2f01202240..afb65ee226f 100644 --- a/homeassistant/components/risco/binary_sensor.py +++ b/homeassistant/components/risco/binary_sensor.py @@ -3,23 +3,71 @@ from __future__ import annotations from collections.abc import Mapping +from itertools import chain from typing import Any from pyrisco.cloud.zone import Zone as CloudZone +from pyrisco.common import System from pyrisco.local.zone import Zone as LocalZone from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, + BinarySensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import LocalData, RiscoDataUpdateCoordinator, is_local -from .const import DATA_COORDINATOR, DOMAIN +from .const import DATA_COORDINATOR, DOMAIN, SYSTEM_UPDATE_SIGNAL from .entity import RiscoCloudZoneEntity, RiscoLocalZoneEntity +SYSTEM_ENTITY_DESCRIPTIONS = [ + BinarySensorEntityDescription( + key="low_battery_trouble", + translation_key="low_battery_trouble", + device_class=BinarySensorDeviceClass.BATTERY, + ), + BinarySensorEntityDescription( + key="ac_trouble", + translation_key="ac_trouble", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + BinarySensorEntityDescription( + key="monitoring_station_1_trouble", + translation_key="monitoring_station_1_trouble", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + BinarySensorEntityDescription( + key="monitoring_station_2_trouble", + translation_key="monitoring_station_2_trouble", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + BinarySensorEntityDescription( + key="monitoring_station_3_trouble", + translation_key="monitoring_station_3_trouble", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + BinarySensorEntityDescription( + key="phone_line_trouble", + translation_key="phone_line_trouble", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + BinarySensorEntityDescription( + key="clock_trouble", + translation_key="clock_trouble", + device_class=BinarySensorDeviceClass.PROBLEM, + ), + BinarySensorEntityDescription( + key="box_tamper", + translation_key="box_tamper", + device_class=BinarySensorDeviceClass.TAMPER, + ), +] + async def async_setup_entry( hass: HomeAssistant, @@ -29,7 +77,7 @@ async def async_setup_entry( """Set up the Risco alarm control panel.""" if is_local(config_entry): local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities( + zone_entities = ( entity for zone_id, zone in local_data.system.zones.items() for entity in ( @@ -38,6 +86,15 @@ async def async_setup_entry( RiscoLocalArmedBinarySensor(local_data.system.id, zone_id, zone), ) ) + + system_entities = ( + RiscoSystemBinarySensor( + local_data.system.id, local_data.system.system, entity_description + ) + for entity_description in SYSTEM_ENTITY_DESCRIPTIONS + ) + + async_add_entities(chain(system_entities, zone_entities)) else: coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][ config_entry.entry_id @@ -128,3 +185,40 @@ class RiscoLocalArmedBinarySensor(RiscoLocalZoneEntity, BinarySensorEntity): def is_on(self) -> bool | None: """Return true if sensor is on.""" return self._zone.armed + + +class RiscoSystemBinarySensor(BinarySensorEntity): + """Risco local system binary sensor class.""" + + _attr_should_poll = False + _attr_has_entity_name = True + + def __init__( + self, + system_id: str, + system: System, + entity_description: BinarySensorEntityDescription, + ) -> None: + """Init the sensor.""" + self._system = system + self._property = entity_description.key + self._attr_unique_id = f"{system_id}_{self._property}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, system_id)}, + manufacturer="Risco", + name=system.name, + ) + self.entity_description = entity_description + + async def async_added_to_hass(self) -> None: + """Subscribe to updates.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, SYSTEM_UPDATE_SIGNAL, self.async_write_ha_state + ) + ) + + @property + def is_on(self) -> bool | None: + """Return true if sensor is on.""" + return getattr(self._system, self._property) diff --git a/homeassistant/components/risco/const.py b/homeassistant/components/risco/const.py index 800003d2384..a27aeae4bf0 100644 --- a/homeassistant/components/risco/const.py +++ b/homeassistant/components/risco/const.py @@ -19,6 +19,7 @@ TYPE_LOCAL = "local" MAX_COMMUNICATION_DELAY = 3 +SYSTEM_UPDATE_SIGNAL = "risco_system_update" CONF_CODE_ARM_REQUIRED = "code_arm_required" CONF_CODE_DISARM_REQUIRED = "code_disarm_required" CONF_RISCO_STATES_TO_HA = "risco_states_to_ha" diff --git a/homeassistant/components/risco/strings.json b/homeassistant/components/risco/strings.json index 13dfd60b5b6..69d7e571f43 100644 --- a/homeassistant/components/risco/strings.json +++ b/homeassistant/components/risco/strings.json @@ -72,6 +72,30 @@ }, "armed": { "name": "Armed" + }, + "low_battery_trouble": { + "name": "Low battery trouble" + }, + "ac_trouble": { + "name": "A/C trouble" + }, + "monitoring_station_1_trouble": { + "name": "Monitoring station 1 trouble" + }, + "monitoring_station_2_trouble": { + "name": "Monitoring station 2 trouble" + }, + "monitoring_station_3_trouble": { + "name": "Monitoring station 3 trouble" + }, + "phone_line_trouble": { + "name": "Phone line trouble" + }, + "clock_trouble": { + "name": "Clock trouble" + }, + "box_tamper": { + "name": "Box tamper" } }, "switch": { diff --git a/tests/components/risco/conftest.py b/tests/components/risco/conftest.py index a27225fce84..6d810ec6abd 100644 --- a/tests/components/risco/conftest.py +++ b/tests/components/risco/conftest.py @@ -14,7 +14,7 @@ from homeassistant.const import ( CONF_USERNAME, ) -from .util import TEST_SITE_NAME, TEST_SITE_UUID, zone_mock +from .util import TEST_SITE_NAME, TEST_SITE_UUID, system_mock, zone_mock from tests.common import MockConfigEntry @@ -63,6 +63,7 @@ def two_zone_cloud(): def two_zone_local(): """Fixture to mock alarm with two zones.""" zone_mocks = {0: zone_mock(), 1: zone_mock()} + system = system_mock() with patch.object( zone_mocks[0], "id", new_callable=PropertyMock(return_value=0) ), patch.object( @@ -83,12 +84,17 @@ def two_zone_local(): zone_mocks[1], "bypassed", new_callable=PropertyMock(return_value=False) ), patch.object( zone_mocks[1], "armed", new_callable=PropertyMock(return_value=False) + ), patch.object( + system, "name", new_callable=PropertyMock(return_value=TEST_SITE_NAME) ), patch( "homeassistant.components.risco.RiscoLocal.partitions", new_callable=PropertyMock(return_value={}), ), patch( "homeassistant.components.risco.RiscoLocal.zones", new_callable=PropertyMock(return_value=zone_mocks), + ), patch( + "homeassistant.components.risco.RiscoLocal.system", + new_callable=PropertyMock(return_value=system), ): yield zone_mocks diff --git a/tests/components/risco/test_binary_sensor.py b/tests/components/risco/test_binary_sensor.py index 22f71ead28d..eae4ef5e472 100644 --- a/tests/components/risco/test_binary_sensor.py +++ b/tests/components/risco/test_binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity_component import async_update_entity -from .util import TEST_SITE_UUID +from .util import TEST_SITE_NAME, TEST_SITE_UUID, system_mock FIRST_ENTITY_ID = "binary_sensor.zone_0" SECOND_ENTITY_ID = "binary_sensor.zone_1" @@ -116,6 +116,10 @@ async def test_local_setup( assert device is not None assert device.manufacturer == "Risco" + device = registry.async_get_device(identifiers={(DOMAIN, TEST_SITE_UUID)}) + assert device is not None + assert device.manufacturer == "Risco" + async def _check_local_state( hass, zones, property, value, entity_id, zone_id, callback @@ -204,3 +208,68 @@ async def test_armed_local_states( await _check_local_state( hass, two_zone_local, "armed", False, SECOND_ARMED_ENTITY_ID, 1, callback ) + + +async def _check_system_state(hass, system, property, value, callback): + with patch.object( + system, + property, + new_callable=PropertyMock(return_value=value), + ): + await callback(system) + await hass.async_block_till_done() + + expected_value = STATE_ON if value else STATE_OFF + if property == "ac_trouble": + property = "a_c_trouble" + entity_id = f"binary_sensor.test_site_name_{property}" + assert hass.states.get(entity_id).state == expected_value + + +@pytest.fixture +def mock_system_handler(): + """Create a mock for add_system_handler.""" + with patch("homeassistant.components.risco.RiscoLocal.add_system_handler") as mock: + yield mock + + +@pytest.fixture +def system_only_local(): + """Fixture to mock a system with no zones or partitions.""" + system = system_mock() + with patch.object( + system, "name", new_callable=PropertyMock(return_value=TEST_SITE_NAME) + ), patch( + "homeassistant.components.risco.RiscoLocal.zones", + new_callable=PropertyMock(return_value={}), + ), patch( + "homeassistant.components.risco.RiscoLocal.partitions", + new_callable=PropertyMock(return_value={}), + ), patch( + "homeassistant.components.risco.RiscoLocal.system", + new_callable=PropertyMock(return_value=system), + ): + yield system + + +async def test_system_states( + hass: HomeAssistant, system_only_local, mock_system_handler, setup_risco_local +) -> None: + """Test the various zone states.""" + callback = mock_system_handler.call_args.args[0] + + assert callback is not None + + properties = [ + "low_battery_trouble", + "ac_trouble", + "monitoring_station_1_trouble", + "monitoring_station_2_trouble", + "monitoring_station_3_trouble", + "phone_line_trouble", + "clock_trouble", + "box_tamper", + ] + for property in properties: + await _check_system_state(hass, system_only_local, property, True, callback) + await _check_system_state(hass, system_only_local, property, False, callback) diff --git a/tests/components/risco/util.py b/tests/components/risco/util.py index db77c112f75..275e9db012d 100644 --- a/tests/components/risco/util.py +++ b/tests/components/risco/util.py @@ -11,3 +11,18 @@ def zone_mock(): return MagicMock( triggered=False, bypassed=False, bypass=AsyncMock(return_value=True) ) + + +def system_mock(): + """Return a mocked system.""" + return MagicMock( + low_battery_trouble=False, + ac_trouble=False, + monitoring_station_1_trouble=False, + monitoring_station_2_trouble=False, + monitoring_station_3_trouble=False, + phone_line_trouble=False, + clock_trouble=False, + box_tamper=False, + programming_mode=False, + ) From a4f52cc622c6bb30bfb63dfadd6269b2fcdd7f33 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 23 Mar 2024 19:58:39 +0100 Subject: [PATCH 1424/1691] Use a constant to reference `homeassistant` domain (#113889) * Use CONF_CORE to reference `homeassistant` domain * Just use DOMAIN * USE DOMAIN for `homeasistant` domain in config_schema.py * Use DOMAIN_HA as constant for homeassistant domain * Rename CONF_CORE to DOMAIN_HA * Rename DOMAIN_HA to HA_DOMAIN * Use relative import * Use direct imports --- .../homeassistant/triggers/homeassistant.py | 8 +++-- homeassistant/config.py | 34 +++++++++--------- homeassistant/helpers/check_config.py | 11 +++--- script/hassfest/config_schema.py | 4 ++- tests/components/rest/test_init.py | 2 +- tests/test_config.py | 35 +++++++++++-------- 6 files changed, 51 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/homeassistant.py b/homeassistant/components/homeassistant/triggers/homeassistant.py index 51e3a947a29..025ca661ac2 100644 --- a/homeassistant/components/homeassistant/triggers/homeassistant.py +++ b/homeassistant/components/homeassistant/triggers/homeassistant.py @@ -8,12 +8,14 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo from homeassistant.helpers.typing import ConfigType +from ..const import DOMAIN + EVENT_START = "start" EVENT_SHUTDOWN = "shutdown" TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( { - vol.Required(CONF_PLATFORM): "homeassistant", + vol.Required(CONF_PLATFORM): DOMAIN, vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), } ) @@ -36,7 +38,7 @@ async def async_attach_trigger( { "trigger": { **trigger_data, - "platform": "homeassistant", + "platform": DOMAIN, "event": event, "description": "Home Assistant stopping", } @@ -51,7 +53,7 @@ async def async_attach_trigger( { "trigger": { **trigger_data, - "platform": "homeassistant", + "platform": DOMAIN, "event": event, "description": "Home Assistant starting", } diff --git a/homeassistant/config.py b/homeassistant/config.py index 8c2935c8b4c..73c38a6c41a 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -58,7 +58,7 @@ from .const import ( LEGACY_CONF_WHITELIST_EXTERNAL_DIRS, __version__, ) -from .core import DOMAIN as CONF_CORE, ConfigSource, HomeAssistant, callback +from .core import DOMAIN as HA_DOMAIN, ConfigSource, HomeAssistant, callback from .exceptions import ConfigValidationError, HomeAssistantError from .generated.currencies import HISTORIC_CURRENCIES from .helpers import config_validation as cv, issue_registry as ir @@ -247,12 +247,12 @@ CUSTOMIZE_CONFIG_SCHEMA = vol.Schema( def _raise_issue_if_historic_currency(hass: HomeAssistant, currency: str) -> None: if currency not in HISTORIC_CURRENCIES: - ir.async_delete_issue(hass, "homeassistant", "historic_currency") + ir.async_delete_issue(hass, HA_DOMAIN, "historic_currency") return ir.async_create_issue( hass, - "homeassistant", + HA_DOMAIN, "historic_currency", is_fixable=False, learn_more_url="homeassistant://config/general", @@ -264,12 +264,12 @@ def _raise_issue_if_historic_currency(hass: HomeAssistant, currency: str) -> Non def _raise_issue_if_no_country(hass: HomeAssistant, country: str | None) -> None: if country is not None: - ir.async_delete_issue(hass, "homeassistant", "country_not_configured") + ir.async_delete_issue(hass, HA_DOMAIN, "country_not_configured") return ir.async_create_issue( hass, - "homeassistant", + HA_DOMAIN, "country_not_configured", is_fixable=False, learn_more_url="homeassistant://config/general", @@ -288,7 +288,7 @@ def _raise_issue_if_legacy_templates( if legacy_templates: ir.async_create_issue( hass, - "homeassistant", + HA_DOMAIN, "legacy_templates_true", is_fixable=False, breaks_in_ha_version="2024.7.0", @@ -297,12 +297,12 @@ def _raise_issue_if_legacy_templates( ) return - ir.async_delete_issue(hass, "homeassistant", "legacy_templates_true") + ir.async_delete_issue(hass, HA_DOMAIN, "legacy_templates_true") if legacy_templates is False: ir.async_create_issue( hass, - "homeassistant", + HA_DOMAIN, "legacy_templates_false", is_fixable=False, breaks_in_ha_version="2024.7.0", @@ -310,7 +310,7 @@ def _raise_issue_if_legacy_templates( translation_key="legacy_templates_false", ) else: - ir.async_delete_issue(hass, "homeassistant", "legacy_templates_false") + ir.async_delete_issue(hass, HA_DOMAIN, "legacy_templates_false") def _validate_currency(data: Any) -> Any: @@ -503,12 +503,12 @@ async def async_hass_config_yaml(hass: HomeAssistant) -> dict: for invalid_domain in invalid_domains: config.pop(invalid_domain) - core_config = config.get(CONF_CORE, {}) + core_config = config.get(HA_DOMAIN, {}) try: await merge_packages_config(hass, config, core_config.get(CONF_PACKAGES, {})) except vol.Invalid as exc: suffix = "" - if annotation := find_annotation(config, [CONF_CORE, CONF_PACKAGES, *exc.path]): + if annotation := find_annotation(config, [HA_DOMAIN, CONF_PACKAGES, *exc.path]): suffix = f" at {_relpath(hass, annotation[0])}, line {annotation[1]}" _LOGGER.error( "Invalid package configuration '%s'%s: %s", CONF_PACKAGES, suffix, exc @@ -731,7 +731,7 @@ def stringify_invalid( ) else: message_prefix = f"Invalid config for '{domain}'" - if domain != CONF_CORE and link: + if domain != HA_DOMAIN and link: message_suffix = f", please check the docs at {link}" else: message_suffix = "" @@ -814,7 +814,7 @@ def format_homeassistant_error( if annotation := find_annotation(config, [domain]): message_prefix += f" at {_relpath(hass, annotation[0])}, line {annotation[1]}" message = f"{message_prefix}: {str(exc) or repr(exc)}" - if domain != CONF_CORE and link: + if domain != HA_DOMAIN and link: message += f", please check the docs at {link}" return message @@ -933,7 +933,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: dict) -> Non cust_glob = OrderedDict(config[CONF_CUSTOMIZE_GLOB]) for name, pkg in config[CONF_PACKAGES].items(): - if (pkg_cust := pkg.get(CONF_CORE)) is None: + if (pkg_cust := pkg.get(HA_DOMAIN)) is None: continue try: @@ -957,7 +957,7 @@ def _log_pkg_error( ) -> None: """Log an error while merging packages.""" message_prefix = f"Setup of package '{package}'" - if annotation := find_annotation(config, [CONF_CORE, CONF_PACKAGES, package]): + if annotation := find_annotation(config, [HA_DOMAIN, CONF_PACKAGES, package]): message_prefix += f" at {_relpath(hass, annotation[0])}, line {annotation[1]}" _LOGGER.error("%s failed: %s", message_prefix, message) @@ -1072,7 +1072,7 @@ async def merge_packages_config( continue for comp_name, comp_conf in pack_conf.items(): - if comp_name == CONF_CORE: + if comp_name == HA_DOMAIN: continue try: domain = cv.domain_key(comp_name) @@ -1305,7 +1305,7 @@ def async_drop_config_annotations( # Don't drop annotations from the homeassistant integration because it may # have configuration for other integrations as packages. - if integration.domain in config and integration.domain != CONF_CORE: + if integration.domain in config and integration.domain != HA_DOMAIN: drop_config_annotations_rec(config[integration.domain]) return config diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 86083b31b2f..8537f442595 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -12,7 +12,6 @@ import voluptuous as vol from homeassistant import loader from homeassistant.config import ( # type: ignore[attr-defined] - CONF_CORE, CONF_PACKAGES, CORE_CONFIG_SCHEMA, YAML_CONFIG_FILE, @@ -23,7 +22,7 @@ from homeassistant.config import ( # type: ignore[attr-defined] load_yaml_config_file, merge_packages_config, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.requirements import ( RequirementsNotFound, @@ -158,10 +157,10 @@ async def async_check_ha_config_file( # noqa: C901 return result.add_error(f"Error loading {config_path}: {err}") # Extract and validate core [homeassistant] config - core_config = config.pop(CONF_CORE, {}) + core_config = config.pop(HA_DOMAIN, {}) try: core_config = CORE_CONFIG_SCHEMA(core_config) - result[CONF_CORE] = core_config + result[HA_DOMAIN] = core_config # Merge packages await merge_packages_config( @@ -169,8 +168,8 @@ async def async_check_ha_config_file( # noqa: C901 ) except vol.Invalid as err: result.add_error( - format_schema_error(hass, err, CONF_CORE, core_config), - CONF_CORE, + format_schema_error(hass, err, HA_DOMAIN, core_config), + HA_DOMAIN, core_config, ) core_config = {} diff --git a/script/hassfest/config_schema.py b/script/hassfest/config_schema.py index 0bc1cbebd93..141b087472b 100644 --- a/script/hassfest/config_schema.py +++ b/script/hassfest/config_schema.py @@ -4,13 +4,15 @@ from __future__ import annotations import ast +from homeassistant.core import DOMAIN as HA_DOMAIN + from .model import Config, Integration CONFIG_SCHEMA_IGNORE = { # Configuration under the homeassistant key is a special case, it's handled by # conf_util.async_process_ha_core_config already during bootstrapping, not by # a schema in the homeassistant integration. - "homeassistant", + HA_DOMAIN, } diff --git a/tests/components/rest/test_init.py b/tests/components/rest/test_init.py index 2c6c32783f1..38a1661a831 100644 --- a/tests/components/rest/test_init.py +++ b/tests/components/rest/test_init.py @@ -468,7 +468,7 @@ async def test_config_schema_via_packages(hass: HomeAssistant) -> None: "pack_11": {"rest": {"resource": "http://url1"}}, "pack_list": {"rest": [{"resource": "http://url2"}]}, } - config = {hass_config.CONF_CORE: {hass_config.CONF_PACKAGES: packages}} + config = {hass_config.HA_DOMAIN: {hass_config.CONF_PACKAGES: packages}} await hass_config.merge_packages_config(hass, config, packages) assert len(config) == 2 diff --git a/tests/test_config.py b/tests/test_config.py index 404cf829a83..efb87a44bdb 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -32,7 +32,12 @@ from homeassistant.const import ( CONF_UNIT_SYSTEM_METRIC, __version__, ) -from homeassistant.core import ConfigSource, HomeAssistant, HomeAssistantError +from homeassistant.core import ( + DOMAIN as HA_DOMAIN, + ConfigSource, + HomeAssistant, + HomeAssistantError, +) from homeassistant.exceptions import ConfigValidationError from homeassistant.helpers import config_validation as cv, issue_registry as ir import homeassistant.helpers.check_config as check_config @@ -1041,7 +1046,7 @@ async def test_check_ha_config_file_wrong(mock_check, hass: HomeAssistant) -> No "hass_config", [ { - config_util.CONF_CORE: { + HA_DOMAIN: { config_util.CONF_PACKAGES: { "pack_dict": {"input_boolean": {"ib1": None}} } @@ -1058,7 +1063,7 @@ async def test_async_hass_config_yaml_merge( conf = await config_util.async_hass_config_yaml(hass) assert merge_log_err.call_count == 0 - assert conf[config_util.CONF_CORE].get(config_util.CONF_PACKAGES) is not None + assert conf[HA_DOMAIN].get(config_util.CONF_PACKAGES) is not None assert len(conf) == 3 assert len(conf["input_boolean"]) == 2 assert len(conf["light"]) == 1 @@ -1086,7 +1091,7 @@ async def test_merge(merge_log_err, hass: HomeAssistant) -> None: }, } config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "input_boolean": {"ib2": None}, "light": {"platform": "test"}, "automation": [], @@ -1113,7 +1118,7 @@ async def test_merge_try_falsy(merge_log_err, hass: HomeAssistant) -> None: "pack_list2": {"light": OrderedDict()}, } config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "automation": {"do": "something"}, "light": {"some": "light"}, } @@ -1136,7 +1141,7 @@ async def test_merge_new(merge_log_err, hass: HomeAssistant) -> None: "api": {}, }, } - config = {config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}} + config = {HA_DOMAIN: {config_util.CONF_PACKAGES: packages}} await config_util.merge_packages_config(hass, config, packages) assert merge_log_err.call_count == 0 @@ -1154,7 +1159,7 @@ async def test_merge_type_mismatch(merge_log_err, hass: HomeAssistant) -> None: "pack_2": {"light": {"ib1": None}}, # light gets merged - ensure_list } config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "input_boolean": {"ib2": None}, "input_select": [{"ib2": None}], "light": [{"platform": "two"}], @@ -1170,13 +1175,13 @@ async def test_merge_type_mismatch(merge_log_err, hass: HomeAssistant) -> None: async def test_merge_once_only_keys(merge_log_err, hass: HomeAssistant) -> None: """Test if we have a merge for a comp that may occur only once. Keys.""" packages = {"pack_2": {"api": None}} - config = {config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, "api": None} + config = {HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "api": None} await config_util.merge_packages_config(hass, config, packages) assert config["api"] == OrderedDict() packages = {"pack_2": {"api": {"key_3": 3}}} config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "api": {"key_1": 1, "key_2": 2}, } await config_util.merge_packages_config(hass, config, packages) @@ -1185,7 +1190,7 @@ async def test_merge_once_only_keys(merge_log_err, hass: HomeAssistant) -> None: # Duplicate keys error packages = {"pack_2": {"api": {"key": 2}}} config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "api": {"key": 1}, } await config_util.merge_packages_config(hass, config, packages) @@ -1200,7 +1205,7 @@ async def test_merge_once_only_lists(hass: HomeAssistant) -> None: } } config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "api": {"list_1": ["item_1"]}, } await config_util.merge_packages_config(hass, config, packages) @@ -1223,7 +1228,7 @@ async def test_merge_once_only_dictionaries(hass: HomeAssistant) -> None: } } config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "api": {"dict_1": {"key_1": 1, "dict_1.1": {"key_1.1": 1.1}}}, } await config_util.merge_packages_config(hass, config, packages) @@ -1257,7 +1262,7 @@ async def test_merge_duplicate_keys(merge_log_err, hass: HomeAssistant) -> None: """Test if keys in dicts are duplicates.""" packages = {"pack_1": {"input_select": {"ib1": None}}} config = { - config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}, + HA_DOMAIN: {config_util.CONF_PACKAGES: packages}, "input_select": {"ib1": 1}, } await config_util.merge_packages_config(hass, config, packages) @@ -1417,7 +1422,7 @@ async def test_merge_split_component_definition(hass: HomeAssistant) -> None: "pack_1": {"light one": {"l1": None}}, "pack_2": {"light two": {"l2": None}, "light three": {"l3": None}}, } - config = {config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages}} + config = {HA_DOMAIN: {config_util.CONF_PACKAGES: packages}} await config_util.merge_packages_config(hass, config, packages) assert len(config) == 4 @@ -2308,7 +2313,7 @@ async def test_packages_schema_validation_error( ] assert error_records == snapshot - assert len(config[config_util.CONF_CORE][config_util.CONF_PACKAGES]) == 0 + assert len(config[HA_DOMAIN][config_util.CONF_PACKAGES]) == 0 def test_extract_domain_configs() -> None: From 4f18f0d902b2c1f2e21990e34a5d59c01cf618ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 09:26:38 -1000 Subject: [PATCH 1425/1691] Fix setup timings when config entry platform loads are not awaited (#113959) * Move setup time logging into the context manager We were fetching the time twice but since the context manager already has the timing, move it there * remove log setup assertions from integration test * tweak logging to give us better data for tracking issues * redundant * adjust * preen * fixes * adjust * make api change internal so nobody uses it * coverage * fix test * fix more tests * coverage * more tests assuming internal calls * fix more * adjust * adjust * fix axis tests * fix broadlink -- it does not call async_forward_entry_setup * missed some * remove useless patch * rename, detect it both ways * clear * debug * try to fix * handle phase finishing out while paused * where its set does not need to know its late as that is an implemenation detail of setup * where its set does not need to know its late as that is an implemenation detail of setup * tweak * simplify * reduce complexity * revert order change as it makes review harder * revert naming changes as it makes review harder * improve comment * improve debug * late dispatch test * test the other way as well * Update setup.py * Update setup.py * Update setup.py * simplify * reduce --- homeassistant/config_entries.py | 18 ++- homeassistant/setup.py | 49 ++++-- tests/components/axis/test_hub.py | 18 +-- tests/components/broadlink/test_device.py | 48 +++--- tests/components/deconz/test_gateway.py | 35 +++-- tests/components/heos/test_init.py | 8 +- tests/components/hue/test_bridge.py | 16 +- tests/components/hue/test_services.py | 8 +- tests/components/minio/test_minio.py | 4 - .../components/owntracks/test_config_flow.py | 8 +- tests/components/smartthings/test_init.py | 18 ++- tests/components/tts/test_legacy.py | 1 + tests/components/unifi/test_hub.py | 22 ++- tests/components/vesync/test_init.py | 19 +-- tests/test_config_entries.py | 7 +- tests/test_setup.py | 142 +++++++++++++++++- 16 files changed, 305 insertions(+), 116 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index d33f66538f4..dd366c524eb 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1858,7 +1858,7 @@ class ConfigEntries: await asyncio.gather( *( create_eager_task( - self.async_forward_entry_setup(entry, platform), + self._async_forward_entry_setup(entry, platform, False), name=f"config entry forward setup {entry.title} {entry.domain} {entry.entry_id} {platform}", ) for platform in platforms @@ -1874,6 +1874,12 @@ class ConfigEntries: component also has related platforms, the component will have to forward the entry to be setup by that component. """ + return await self._async_forward_entry_setup(entry, domain, True) + + async def _async_forward_entry_setup( + self, entry: ConfigEntry, domain: Platform | str, preload_platform: bool + ) -> bool: + """Forward the setup of an entry to a different component.""" # Setup Component if not set up yet if domain not in self.hass.config.components: with async_pause_setup(self.hass, SetupPhases.WAIT_BASE_PLATFORM_SETUP): @@ -1884,8 +1890,16 @@ class ConfigEntries: if not result: return False - integration = await loader.async_get_integration(self.hass, domain) + if preload_platform: + # If this is a late setup, we need to make sure the platform is loaded + # so we do not end up waiting for when the EntityComponent calls + # async_prepare_setup_platform + integration = await loader.async_get_integration(self.hass, entry.domain) + if not integration.platforms_are_loaded((domain,)): + with async_pause_setup(self.hass, SetupPhases.WAIT_IMPORT_PLATFORMS): + await integration.async_get_platform(domain) + integration = await loader.async_get_integration(self.hass, domain) await entry.async_setup(self.hass, integration=integration) return True diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 97aa79512f2..07b7b40deab 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -10,7 +10,6 @@ import contextvars from enum import StrEnum import logging.handlers import time -from timeit import default_timer as timer from types import ModuleType from typing import Any, Final, TypedDict @@ -351,7 +350,6 @@ async def _async_setup_component( # noqa: C901 }, ) - start = timer() _LOGGER.info("Setting up %s", domain) integration_set = {domain} @@ -412,11 +410,8 @@ async def _async_setup_component( # noqa: C901 async_notify_setup_error(hass, domain, integration.documentation) return False finally: - end = timer() if warn_task: warn_task.cancel() - _LOGGER.info("Setup of domain %s took %.1f seconds", domain, end - start) - if result is False: log_error("Integration failed to initialize.") return False @@ -663,6 +658,15 @@ class SetupPhases(StrEnum): """Wait time for the packages to import.""" +def _setup_started( + hass: core.HomeAssistant, +) -> dict[tuple[str, str | None], float]: + """Return the setup started dict.""" + if DATA_SETUP_STARTED not in hass.data: + hass.data[DATA_SETUP_STARTED] = {} + return hass.data[DATA_SETUP_STARTED] # type: ignore[no-any-return] + + @contextlib.contextmanager def async_pause_setup( hass: core.HomeAssistant, phase: SetupPhases @@ -673,7 +677,9 @@ def async_pause_setup( setting up the base components so we can subtract it from the total setup time. """ - if not (running := current_setup_group.get()): + if not (running := current_setup_group.get()) or running not in _setup_started( + hass + ): # This means we are likely in a late platform setup # that is running in a task so we do not want # to subtract out the time later as nothing is waiting @@ -689,6 +695,13 @@ def async_pause_setup( integration, group = running # Add negative time for the time we waited _setup_times(hass)[integration][group][phase] = -time_taken + _LOGGER.debug( + "Adding wait for %s for %s (%s) of %.2f", + phase, + integration, + group, + time_taken, + ) def _setup_times( @@ -726,8 +739,7 @@ def async_start_setup( yield return - setup_started: dict[tuple[str, str | None], float] - setup_started = hass.data.setdefault(DATA_SETUP_STARTED, {}) + setup_started = _setup_started(hass) current = (integration, group) if current in setup_started: # We are already inside another async_start_setup, this like means we @@ -745,7 +757,26 @@ def async_start_setup( finally: time_taken = time.monotonic() - started del setup_started[current] - _setup_times(hass)[integration][group][phase] = time_taken + group_setup_times = _setup_times(hass)[integration][group] + # We may see the phase multiple times if there are multiple + # platforms, but we only care about the longest time. + group_setup_times[phase] = max(group_setup_times[phase], time_taken) + if group is None: + _LOGGER.info( + "Setup of domain %s took %.2f seconds", integration, time_taken + ) + elif _LOGGER.isEnabledFor(logging.DEBUG): + wait_time = -sum(value for value in group_setup_times.values() if value < 0) + calculated_time = time_taken - wait_time + _LOGGER.debug( + "Phase %s for %s (%s) took %.2fs (elapsed=%.2fs) (wait_time=%.2fs)", + phase, + integration, + group, + calculated_time, + time_taken, + wait_time, + ) @callback diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index b2fc210f541..f12665a995d 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -35,16 +35,18 @@ from tests.common import async_fire_mqtt_message from tests.typing import MqttMockHAClient -@pytest.fixture(name="forward_entry_setup") +@pytest.fixture(name="forward_entry_setups") def hass_mock_forward_entry_setup(hass): - """Mock async_forward_entry_setup.""" - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: + """Mock async_forward_entry_setups.""" + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as forward_mock: yield forward_mock async def test_device_setup( hass: HomeAssistant, - forward_entry_setup, + forward_entry_setups, config_entry_data, setup_config_entry, device_registry: dr.DeviceRegistry, @@ -57,11 +59,9 @@ async def test_device_setup( assert hub.api.vapix.product_type == "Network Camera" assert hub.api.vapix.serial_number == "00408C123456" - assert len(forward_entry_setup.mock_calls) == 4 - assert forward_entry_setup.mock_calls[0][1][1] == "binary_sensor" - assert forward_entry_setup.mock_calls[1][1][1] == "camera" - assert forward_entry_setup.mock_calls[2][1][1] == "light" - assert forward_entry_setup.mock_calls[3][1][1] == "switch" + assert len(forward_entry_setups.mock_calls) == 1 + platforms = set(forward_entry_setups.mock_calls[0][1][1]) + assert platforms == {"binary_sensor", "camera", "light", "switch"} assert hub.config.host == config_entry_data[CONF_HOST] assert hub.config.model == config_entry_data[CONF_MODEL] diff --git a/tests/components/broadlink/test_device.py b/tests/components/broadlink/test_device.py index 52aad8f4f63..365d61a9e69 100644 --- a/tests/components/broadlink/test_device.py +++ b/tests/components/broadlink/test_device.py @@ -21,7 +21,7 @@ async def test_device_setup(hass: HomeAssistant) -> None: device = get_device("Office") with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -32,9 +32,9 @@ async def test_device_setup(hass: HomeAssistant) -> None: assert mock_setup.api.get_fwversion.call_count == 1 assert mock_setup.factory.call_count == 1 - forward_entries = {c[1][1] for c in mock_forward.mock_calls} + forward_entries = set(mock_forward.mock_calls[0][1][1]) domains = get_domains(mock_setup.api.type) - assert mock_forward.call_count == len(domains) + assert mock_forward.call_count == 1 assert forward_entries == domains assert mock_init.call_count == 0 @@ -46,7 +46,7 @@ async def test_device_setup_authentication_error(hass: HomeAssistant) -> None: mock_api.auth.side_effect = blke.AuthenticationError() with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -70,7 +70,7 @@ async def test_device_setup_network_timeout(hass: HomeAssistant) -> None: mock_api.auth.side_effect = blke.NetworkTimeoutError() with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -89,7 +89,7 @@ async def test_device_setup_os_error(hass: HomeAssistant) -> None: mock_api.auth.side_effect = OSError() with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -108,7 +108,7 @@ async def test_device_setup_broadlink_exception(hass: HomeAssistant) -> None: mock_api.auth.side_effect = blke.BroadlinkException() with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -127,7 +127,7 @@ async def test_device_setup_update_network_timeout(hass: HomeAssistant) -> None: mock_api.check_sensors.side_effect = blke.NetworkTimeoutError() with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -150,7 +150,7 @@ async def test_device_setup_update_authorization_error(hass: HomeAssistant) -> N ) with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -160,9 +160,9 @@ async def test_device_setup_update_authorization_error(hass: HomeAssistant) -> N assert mock_setup.api.auth.call_count == 2 assert mock_setup.api.check_sensors.call_count == 2 - forward_entries = {c[1][1] for c in mock_forward.mock_calls} + forward_entries = set(mock_forward.mock_calls[0][1][1]) domains = get_domains(mock_api.type) - assert mock_forward.call_count == len(domains) + assert mock_forward.call_count == 1 assert forward_entries == domains assert mock_init.call_count == 0 @@ -175,7 +175,7 @@ async def test_device_setup_update_authentication_error(hass: HomeAssistant) -> mock_api.auth.side_effect = (None, blke.AuthenticationError()) with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -200,7 +200,7 @@ async def test_device_setup_update_broadlink_exception(hass: HomeAssistant) -> N mock_api.check_sensors.side_effect = blke.BroadlinkException() with patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward, patch.object( hass.config_entries.flow, "async_init" ) as mock_init: @@ -221,13 +221,15 @@ async def test_device_setup_get_fwversion_broadlink_exception( mock_api = device.get_mock_api() mock_api.get_fwversion.side_effect = blke.BroadlinkException() - with patch.object(hass.config_entries, "async_forward_entry_setup") as mock_forward: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as mock_forward: mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.LOADED - forward_entries = {c[1][1] for c in mock_forward.mock_calls} + forward_entries = set(mock_forward.mock_calls[0][1][1]) domains = get_domains(mock_setup.api.type) - assert mock_forward.call_count == len(domains) + assert mock_forward.call_count == 1 assert forward_entries == domains @@ -237,13 +239,15 @@ async def test_device_setup_get_fwversion_os_error(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.get_fwversion.side_effect = OSError() - with patch.object(hass.config_entries, "async_forward_entry_setup") as mock_forward: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as mock_forward: mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.LOADED - forward_entries = {c[1][1] for c in mock_forward.mock_calls} + forward_entries = set(mock_forward.mock_calls[0][1][1]) domains = get_domains(mock_setup.api.type) - assert mock_forward.call_count == len(domains) + assert mock_forward.call_count == 1 assert forward_entries == domains @@ -281,7 +285,7 @@ async def test_device_unload_works(hass: HomeAssistant) -> None: """Test we unload the device.""" device = get_device("Office") - with patch.object(hass.config_entries, "async_forward_entry_setup"): + with patch.object(hass.config_entries, "async_forward_entry_setups"): mock_setup = await device.setup_entry(hass) with patch.object( @@ -302,7 +306,7 @@ async def test_device_unload_authentication_error(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.auth.side_effect = blke.AuthenticationError() - with patch.object(hass.config_entries, "async_forward_entry_setup"), patch.object( + with patch.object(hass.config_entries, "async_forward_entry_setups"), patch.object( hass.config_entries.flow, "async_init" ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) @@ -322,7 +326,7 @@ async def test_device_unload_update_failed(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.check_sensors.side_effect = blke.NetworkTimeoutError() - with patch.object(hass.config_entries, "async_forward_entry_setup"): + with patch.object(hass.config_entries, "async_forward_entry_setups"): mock_setup = await device.setup_entry(hass, mock_api=mock_api) with patch.object( diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index d984354adca..447c1406bf4 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -142,7 +142,7 @@ async def test_gateway_setup( ) -> None: """Successful setup.""" with patch( - "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", + "homeassistant.config_entries.ConfigEntries.async_forward_entry_setups", return_value=True, ) as forward_entry_setup: config_entry = await setup_deconz_integration(hass, aioclient_mock) @@ -158,24 +158,23 @@ async def test_gateway_setup( assert forward_entry_setup.mock_calls[0][1] == ( config_entry, - ALARM_CONTROL_PANEL_DOMAIN, + [ + ALARM_CONTROL_PANEL_DOMAIN, + BINARY_SENSOR_DOMAIN, + BUTTON_DOMAIN, + CLIMATE_DOMAIN, + COVER_DOMAIN, + FAN_DOMAIN, + LIGHT_DOMAIN, + LOCK_DOMAIN, + NUMBER_DOMAIN, + SCENE_DOMAIN, + SELECT_DOMAIN, + SENSOR_DOMAIN, + SIREN_DOMAIN, + SWITCH_DOMAIN, + ], ) - assert forward_entry_setup.mock_calls[1][1] == ( - config_entry, - BINARY_SENSOR_DOMAIN, - ) - assert forward_entry_setup.mock_calls[2][1] == (config_entry, BUTTON_DOMAIN) - assert forward_entry_setup.mock_calls[3][1] == (config_entry, CLIMATE_DOMAIN) - assert forward_entry_setup.mock_calls[4][1] == (config_entry, COVER_DOMAIN) - assert forward_entry_setup.mock_calls[5][1] == (config_entry, FAN_DOMAIN) - assert forward_entry_setup.mock_calls[6][1] == (config_entry, LIGHT_DOMAIN) - assert forward_entry_setup.mock_calls[7][1] == (config_entry, LOCK_DOMAIN) - assert forward_entry_setup.mock_calls[8][1] == (config_entry, NUMBER_DOMAIN) - assert forward_entry_setup.mock_calls[9][1] == (config_entry, SCENE_DOMAIN) - assert forward_entry_setup.mock_calls[10][1] == (config_entry, SELECT_DOMAIN) - assert forward_entry_setup.mock_calls[11][1] == (config_entry, SENSOR_DOMAIN) - assert forward_entry_setup.mock_calls[12][1] == (config_entry, SIREN_DOMAIN) - assert forward_entry_setup.mock_calls[13][1] == (config_entry, SWITCH_DOMAIN) gateway_entry = device_registry.async_get_device( identifiers={(DECONZ_DOMAIN, gateway.bridgeid)} diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index 4aca77fd90f..fd453c70ebf 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -80,7 +80,9 @@ async def test_async_setup_entry_loads_platforms( ) -> None: """Test load connects to heos, retrieves players, and loads platforms.""" config_entry.add_to_hass(hass) - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as forward_mock: assert await async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() @@ -107,7 +109,9 @@ async def test_async_setup_entry_not_signed_in_loads_platforms( config_entry.add_to_hass(hass) controller.is_signed_in = False controller.signed_in_username = None - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as forward_mock: assert await async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 946240116d8..ce1b3f34674 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -30,7 +30,7 @@ async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None: ) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward: hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -38,8 +38,8 @@ async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None: assert hue_bridge.api is mock_api_v1 assert isinstance(hue_bridge.api, HueBridgeV1) assert hue_bridge.api_version == 1 - assert len(mock_forward.mock_calls) == 3 - forward_entries = {c[1][1] for c in mock_forward.mock_calls} + assert len(mock_forward.mock_calls) == 1 + forward_entries = set(mock_forward.mock_calls[0][1][1]) assert forward_entries == {"light", "binary_sensor", "sensor"} @@ -51,7 +51,7 @@ async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None: ) with patch.object(bridge, "HueBridgeV2", return_value=mock_api_v2), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward: hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -59,8 +59,8 @@ async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None: assert hue_bridge.api is mock_api_v2 assert isinstance(hue_bridge.api, HueBridgeV2) assert hue_bridge.api_version == 2 - assert len(mock_forward.mock_calls) == 6 - forward_entries = {c[1][1] for c in mock_forward.mock_calls} + assert len(mock_forward.mock_calls) == 1 + forward_entries = set(mock_forward.mock_calls[0][1][1]) assert forward_entries == { "light", "binary_sensor", @@ -115,7 +115,7 @@ async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) -> ) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ) as mock_forward: hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -123,7 +123,7 @@ async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) -> await asyncio.sleep(0) assert len(hass.services.async_services()) == 0 - assert len(mock_forward.mock_calls) == 3 + assert len(mock_forward.mock_calls) == 1 with patch.object( hass.config_entries, "async_forward_entry_unload", return_value=True diff --git a/tests/components/hue/test_services.py b/tests/components/hue/test_services.py index 5b91b84a33a..0e80bb2ea08 100644 --- a/tests/components/hue/test_services.py +++ b/tests/components/hue/test_services.py @@ -63,7 +63,7 @@ async def test_hue_activate_scene(hass: HomeAssistant, mock_api_v1) -> None: mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -100,7 +100,7 @@ async def test_hue_activate_scene_transition(hass: HomeAssistant, mock_api_v1) - mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -139,7 +139,7 @@ async def test_hue_activate_scene_group_not_found( mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -173,7 +173,7 @@ async def test_hue_activate_scene_scene_not_found( mock_api_v1.mock_scene_responses.append({}) with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setup" + hass.config_entries, "async_forward_entry_setups" ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index 746085fafc2..74db0a2fcf9 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -76,8 +76,6 @@ async def test_minio_services( await hass.async_start() await hass.async_block_till_done() - assert "Setup of domain minio took" in caplog.text - # Call services await hass.services.async_call( DOMAIN, @@ -141,8 +139,6 @@ async def test_minio_listen( await hass.async_start() await hass.async_block_till_done() - assert "Setup of domain minio took" in caplog.text - while not events: await asyncio.sleep(0) diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index 038609271f4..d8e700c3754 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -126,7 +126,7 @@ async def test_unload(hass: HomeAssistant) -> None: ) with patch( - "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup" + "homeassistant.config_entries.ConfigEntries.async_forward_entry_setups" ) as mock_forward: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data={} @@ -135,8 +135,7 @@ async def test_unload(hass: HomeAssistant) -> None: assert len(mock_forward.mock_calls) == 1 entry = result["result"] - assert mock_forward.mock_calls[0][1][0] is entry - assert mock_forward.mock_calls[0][1][1] == "device_tracker" + mock_forward.assert_called_once_with(entry, ["device_tracker"]) assert entry.data["webhook_id"] in hass.data["webhook"] with patch( @@ -146,8 +145,7 @@ async def test_unload(hass: HomeAssistant) -> None: assert await hass.config_entries.async_unload(entry.entry_id) assert len(mock_unload.mock_calls) == 1 - assert mock_forward.mock_calls[0][1][0] is entry - assert mock_forward.mock_calls[0][1][1] == "device_tracker" + mock_forward.assert_called_once_with(entry, ["device_tracker"]) assert entry.data["webhook_id"] not in hass.data["webhook"] diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index f960ba15e85..dcce19a68d1 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -179,11 +179,13 @@ async def test_scenes_unauthorized_loads_platforms( ] smartthings_mock.subscriptions.return_value = subscriptions - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as forward_mock: assert await hass.config_entries.async_setup(config_entry.entry_id) # Assert platforms loaded await hass.async_block_till_done() - assert forward_mock.call_count == len(PLATFORMS) + forward_mock.assert_called_once_with(config_entry, PLATFORMS) async def test_config_entry_loads_platforms( @@ -211,11 +213,13 @@ async def test_config_entry_loads_platforms( ] smartthings_mock.subscriptions.return_value = subscriptions - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as forward_mock: assert await hass.config_entries.async_setup(config_entry.entry_id) # Assert platforms loaded await hass.async_block_till_done() - assert forward_mock.call_count == len(PLATFORMS) + forward_mock.assert_called_once_with(config_entry, PLATFORMS) async def test_config_entry_loads_unconnected_cloud( @@ -243,10 +247,12 @@ async def test_config_entry_loads_unconnected_cloud( subscription_factory(capability) for capability in device.capabilities ] smartthings_mock.subscriptions.return_value = subscriptions - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: + with patch.object( + hass.config_entries, "async_forward_entry_setups" + ) as forward_mock: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - assert forward_mock.call_count == len(PLATFORMS) + forward_mock.assert_called_once_with(config_entry, PLATFORMS) async def test_unload_entry(hass: HomeAssistant, config_entry) -> None: diff --git a/tests/components/tts/test_legacy.py b/tests/components/tts/test_legacy.py index 6fe6b0b790a..59194f50d93 100644 --- a/tests/components/tts/test_legacy.py +++ b/tests/components/tts/test_legacy.py @@ -148,6 +148,7 @@ async def test_service_without_cache_config( with assert_setup_component(1, DOMAIN): assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.services.async_call( DOMAIN, diff --git a/tests/components/unifi/test_hub.py b/tests/components/unifi/test_hub.py index 6dce2109771..48aca0b407e 100644 --- a/tests/components/unifi/test_hub.py +++ b/tests/components/unifi/test_hub.py @@ -24,11 +24,11 @@ from homeassistant.components.unifi.const import ( DEFAULT_TRACK_DEVICES, DEFAULT_TRACK_WIRED_CLIENTS, DOMAIN as UNIFI_DOMAIN, - PLATFORMS, UNIFI_WIRELESS_CLIENTS, ) from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.components.unifi.hub import get_unifi_api +from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -248,7 +248,7 @@ async def test_hub_setup( ) -> None: """Successful setup.""" with patch( - "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", + "homeassistant.config_entries.ConfigEntries.async_forward_entry_setups", return_value=True, ) as forward_entry_setup: config_entry = await setup_unifi_integration( @@ -257,12 +257,18 @@ async def test_hub_setup( hub = hass.data[UNIFI_DOMAIN][config_entry.entry_id] entry = hub.config.entry - assert len(forward_entry_setup.mock_calls) == len(PLATFORMS) - assert forward_entry_setup.mock_calls[0][1] == (entry, BUTTON_DOMAIN) - assert forward_entry_setup.mock_calls[1][1] == (entry, TRACKER_DOMAIN) - assert forward_entry_setup.mock_calls[2][1] == (entry, IMAGE_DOMAIN) - assert forward_entry_setup.mock_calls[3][1] == (entry, SENSOR_DOMAIN) - assert forward_entry_setup.mock_calls[4][1] == (entry, SWITCH_DOMAIN) + assert len(forward_entry_setup.mock_calls) == 1 + assert forward_entry_setup.mock_calls[0][1] == ( + entry, + [ + BUTTON_DOMAIN, + TRACKER_DOMAIN, + IMAGE_DOMAIN, + SENSOR_DOMAIN, + SWITCH_DOMAIN, + UPDATE_DOMAIN, + ], + ) assert hub.config.host == ENTRY_CONFIG[CONF_HOST] assert hub.is_admin == (SITE[0]["role"] == "admin") diff --git a/tests/components/vesync/test_init.py b/tests/components/vesync/test_init.py index 005beab4503..fedd7f70a93 100644 --- a/tests/components/vesync/test_init.py +++ b/tests/components/vesync/test_init.py @@ -30,15 +30,12 @@ async def test_async_setup_entry__not_login( with patch.object( hass.config_entries, "async_forward_entry_setups" - ) as setups_mock, patch.object( - hass.config_entries, "async_forward_entry_setup" - ) as setup_mock, patch( + ) as setups_mock, patch( "homeassistant.components.vesync.async_process_devices" ) as process_mock: assert not await async_setup_entry(hass, config_entry) await hass.async_block_till_done() assert setups_mock.call_count == 0 - assert setup_mock.call_count == 0 assert process_mock.call_count == 0 assert manager.login.call_count == 1 @@ -50,18 +47,13 @@ async def test_async_setup_entry__no_devices( hass: HomeAssistant, config_entry: ConfigEntry, manager: VeSync ) -> None: """Test setup connects to vesync and creates empty config when no devices.""" - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as setups_mock, patch.object( - hass.config_entries, "async_forward_entry_setup" - ) as setup_mock: + with patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock: assert await async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() assert setups_mock.call_count == 1 assert setups_mock.call_args.args[0] == config_entry assert setups_mock.call_args.args[1] == [] - assert setup_mock.call_count == 0 assert manager.login.call_count == 1 assert hass.data[DOMAIN][VS_MANAGER] == manager @@ -81,18 +73,13 @@ async def test_async_setup_entry__loads_fans( "fans": fans, } - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as setups_mock, patch.object( - hass.config_entries, "async_forward_entry_setup" - ) as setup_mock: + with patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock: assert await async_setup_entry(hass, config_entry) # Assert platforms loaded await hass.async_block_till_done() assert setups_mock.call_count == 1 assert setups_mock.call_args.args[0] == config_entry assert setups_mock.call_args.args[1] == [Platform.FAN, Platform.SENSOR] - assert setup_mock.call_count == 0 assert manager.login.call_count == 1 assert hass.data[DOMAIN][VS_MANAGER] == manager assert not hass.data[DOMAIN][VS_SWITCHES] diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 544396c237a..da100d8cfb0 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -859,7 +859,7 @@ async def test_forward_entry_sets_up_component(hass: HomeAssistant) -> None: entry = MockConfigEntry(domain="original") mock_original_setup_entry = AsyncMock(return_value=True) - mock_integration( + integration = mock_integration( hass, MockModule("original", async_setup_entry=mock_original_setup_entry) ) @@ -868,7 +868,10 @@ async def test_forward_entry_sets_up_component(hass: HomeAssistant) -> None: hass, MockModule("forwarded", async_setup_entry=mock_forwarded_setup_entry) ) - await hass.config_entries.async_forward_entry_setup(entry, "forwarded") + with patch.object(integration, "async_get_platform") as mock_async_get_platform: + await hass.config_entries.async_forward_entry_setup(entry, "forwarded") + + mock_async_get_platform.assert_called_once_with("forwarded") assert len(mock_original_setup_entry.mock_calls) == 0 assert len(mock_forwarded_setup_entry.mock_calls) == 1 diff --git a/tests/test_setup.py b/tests/test_setup.py index 28366a50a82..b48e7252e65 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -4,6 +4,7 @@ import asyncio import threading from unittest.mock import ANY, AsyncMock, Mock, patch +from freezegun.api import FrozenDateTimeFactory import pytest import voluptuous as vol @@ -16,6 +17,10 @@ from homeassistant.helpers.config_validation import ( PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from .common import ( MockConfigEntry, @@ -739,7 +744,9 @@ async def test_async_start_setup_running(hass: HomeAssistant) -> None: assert not setup_started -async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None: +async def test_async_start_setup_config_entry( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: """Test setup started keeps track of setup times with a config entry.""" hass.set_state(CoreState.not_running) setup_started: dict[tuple[str, str | None], float] @@ -778,6 +785,7 @@ async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None: phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, ): assert isinstance(setup_started[("august", "entry_id")], float) + # Platforms outside of CONFIG_ENTRY_SETUP should be tracked # This simulates a late platform forward assert setup_time["august"] == { @@ -788,6 +796,38 @@ async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None: }, } + shorter_time = setup_time["august"]["entry_id"][ + setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP + ] + # Setup another platform, but make it take longer + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, + ): + freezer.tick(10) + assert isinstance(setup_started[("august", "entry_id")], float) + + longer_time = setup_time["august"]["entry_id"][ + setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP + ] + assert longer_time > shorter_time + # Setup another platform, but make it take shorter + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id")], float) + + # Ensure we keep the longest time + assert ( + setup_time["august"]["entry_id"][setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP] + == longer_time + ) + with setup.async_start_setup( hass, integration="august", @@ -815,6 +855,106 @@ async def test_async_start_setup_config_entry(hass: HomeAssistant) -> None: } +async def test_async_start_setup_config_entry_late_platform( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: + """Test setup started tracks config entry time with a late platform load.""" + hass.set_state(CoreState.not_running) + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) + setup_time = setup._setup_times(hass) + + with setup.async_start_setup( + hass, integration="august", phase=setup.SetupPhases.SETUP + ): + freezer.tick(10) + assert isinstance(setup_started[("august", None)], float) + + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id")], float) + + @callback + def async_late_platform_load(): + with setup.async_pause_setup(hass, setup.SetupPhases.WAIT_IMPORT_PLATFORMS): + freezer.tick(100) + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, + ): + freezer.tick(20) + assert isinstance(setup_started[("august", "entry_id")], float) + + disconnect = async_dispatcher_connect( + hass, "late_platform_load_test", async_late_platform_load + ) + + # Dispatch a late platform load + async_dispatcher_send(hass, "late_platform_load_test") + disconnect() + + # CONFIG_ENTRY_PLATFORM_SETUP is late dispatched, so it should be tracked + # but any waiting time should not be because it's blocking the setup + assert setup_time["august"] == { + None: {setup.SetupPhases.SETUP: 10.0}, + "entry_id": { + setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP: 20.0, + setup.SetupPhases.CONFIG_ENTRY_SETUP: 0.0, + }, + } + + +async def test_async_start_setup_config_entry_platform_wait( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: + """Test setup started tracks wait time when a platform loads inside of config entry setup.""" + hass.set_state(CoreState.not_running) + setup_started: dict[tuple[str, str | None], float] + setup_started = hass.data.setdefault(setup.DATA_SETUP_STARTED, {}) + setup_time = setup._setup_times(hass) + + with setup.async_start_setup( + hass, integration="august", phase=setup.SetupPhases.SETUP + ): + freezer.tick(10) + assert isinstance(setup_started[("august", None)], float) + + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_SETUP, + ): + assert isinstance(setup_started[("august", "entry_id")], float) + + with setup.async_pause_setup(hass, setup.SetupPhases.WAIT_IMPORT_PLATFORMS): + freezer.tick(100) + with setup.async_start_setup( + hass, + integration="august", + group="entry_id", + phase=setup.SetupPhases.CONFIG_ENTRY_PLATFORM_SETUP, + ): + freezer.tick(20) + assert isinstance(setup_started[("august", "entry_id")], float) + + # CONFIG_ENTRY_PLATFORM_SETUP is run inside of CONFIG_ENTRY_SETUP, so it should not + # be tracked, but any wait time should still be tracked because its blocking the setup + assert setup_time["august"] == { + None: {setup.SetupPhases.SETUP: 10.0}, + "entry_id": { + setup.SetupPhases.WAIT_IMPORT_PLATFORMS: -100.0, + setup.SetupPhases.CONFIG_ENTRY_SETUP: 120.0, + }, + } + + async def test_async_start_setup_top_level_yaml(hass: HomeAssistant) -> None: """Test setup started context manager keeps track of setup times with modern yaml.""" hass.set_state(CoreState.not_running) From 82016ff5282ec7db4801468a97b43b3fd99ac43a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 09:41:12 -1000 Subject: [PATCH 1426/1691] Refactor recorder states tests (#114073) https://github.com/home-assistant/core/pull/113985#discussion_r1536596573 --- tests/components/humidifier/test_recorder.py | 19 ++++++++++--------- tests/components/siren/test_recorder.py | 15 ++++++++------- tests/components/vacuum/test_recorder.py | 15 ++++++++------- .../components/water_heater/test_recorder.py | 19 ++++++++++--------- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/tests/components/humidifier/test_recorder.py b/tests/components/humidifier/test_recorder.py index 084effb1af8..733f505d0ab 100644 --- a/tests/components/humidifier/test_recorder.py +++ b/tests/components/humidifier/test_recorder.py @@ -36,12 +36,13 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) get_significant_states, hass, now, None, hass.states.async_entity_ids() ) assert len(states) >= 1 - for entity_states in states.values(): - for state in filter( - lambda state: split_entity_id(state.entity_id)[0] == humidifier.DOMAIN, - entity_states, - ): - assert ATTR_MIN_HUMIDITY not in state.attributes - assert ATTR_MAX_HUMIDITY not in state.attributes - assert ATTR_AVAILABLE_MODES not in state.attributes - assert ATTR_FRIENDLY_NAME in state.attributes + for state in ( + state + for entity_states in states.values() + for state in entity_states + if split_entity_id(state.entity_id)[0] == humidifier.DOMAIN + ): + assert ATTR_MIN_HUMIDITY not in state.attributes + assert ATTR_MAX_HUMIDITY not in state.attributes + assert ATTR_AVAILABLE_MODES not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/siren/test_recorder.py b/tests/components/siren/test_recorder.py index 37161e92ff6..5fb16f73175 100644 --- a/tests/components/siren/test_recorder.py +++ b/tests/components/siren/test_recorder.py @@ -32,10 +32,11 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) get_significant_states, hass, now, None, hass.states.async_entity_ids() ) assert len(states) >= 1 - for entity_states in states.values(): - for state in filter( - lambda state: split_entity_id(state.entity_id)[0] == siren.DOMAIN, - entity_states, - ): - assert ATTR_AVAILABLE_TONES not in state.attributes - assert ATTR_FRIENDLY_NAME in state.attributes + for state in ( + state + for entity_states in states.values() + for state in entity_states + if split_entity_id(state.entity_id)[0] == siren.DOMAIN + ): + assert ATTR_AVAILABLE_TONES not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/vacuum/test_recorder.py b/tests/components/vacuum/test_recorder.py index f1a531fe3d0..c01c47acae0 100644 --- a/tests/components/vacuum/test_recorder.py +++ b/tests/components/vacuum/test_recorder.py @@ -32,10 +32,11 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) get_significant_states, hass, now, None, hass.states.async_entity_ids() ) assert len(states) >= 1 - for entity_states in states.values(): - for state in filter( - lambda state: split_entity_id(state.entity_id)[0] == vacuum.DOMAIN, - entity_states, - ): - assert ATTR_FAN_SPEED_LIST not in state.attributes - assert ATTR_FRIENDLY_NAME in state.attributes + for state in ( + state + for entity_states in states.values() + for state in entity_states + if split_entity_id(state.entity_id)[0] == vacuum.DOMAIN + ): + assert ATTR_FAN_SPEED_LIST not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes diff --git a/tests/components/water_heater/test_recorder.py b/tests/components/water_heater/test_recorder.py index 3455090718b..4b004ce8d5d 100644 --- a/tests/components/water_heater/test_recorder.py +++ b/tests/components/water_heater/test_recorder.py @@ -36,12 +36,13 @@ async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) get_significant_states, hass, now, None, hass.states.async_entity_ids() ) assert len(states) >= 1 - for entity_states in states.values(): - for state in filter( - lambda state: split_entity_id(state.entity_id)[0] == water_heater.DOMAIN, - entity_states, - ): - assert ATTR_OPERATION_LIST not in state.attributes - assert ATTR_MIN_TEMP not in state.attributes - assert ATTR_MAX_TEMP not in state.attributes - assert ATTR_FRIENDLY_NAME in state.attributes + for state in ( + state + for entity_states in states.values() + for state in entity_states + if split_entity_id(state.entity_id)[0] == water_heater.DOMAIN + ): + assert ATTR_OPERATION_LIST not in state.attributes + assert ATTR_MIN_TEMP not in state.attributes + assert ATTR_MAX_TEMP not in state.attributes + assert ATTR_FRIENDLY_NAME in state.attributes From ce12d45b50e9ee591f565c09928d169388bd9593 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 23 Mar 2024 20:57:22 +0100 Subject: [PATCH 1427/1691] Catch Mill timeout error (#114068) * Catch Mill timeout error * Catch Mill timeout error * Catch Mill timeout error --- homeassistant/components/mill/__init__.py | 7 +++++-- tests/components/mill/test_init.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mill/__init__.py b/homeassistant/components/mill/__init__.py index 401a0f9e854..b2f06597563 100644 --- a/homeassistant/components/mill/__init__.py +++ b/homeassistant/components/mill/__init__.py @@ -66,8 +66,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: key = entry.data[CONF_USERNAME] conn_type = CLOUD - if not await mill_data_connection.connect(): - raise ConfigEntryNotReady + try: + if not await mill_data_connection.connect(): + raise ConfigEntryNotReady + except TimeoutError as error: + raise ConfigEntryNotReady from error data_coordinator = MillDataUpdateCoordinator( hass, mill_data_connection=mill_data_connection, diff --git a/tests/components/mill/test_init.py b/tests/components/mill/test_init.py index 8425c980f80..21201e955d7 100644 --- a/tests/components/mill/test_init.py +++ b/tests/components/mill/test_init.py @@ -1,5 +1,6 @@ """Tests for Mill init.""" +import asyncio from unittest.mock import patch from homeassistant.components import mill @@ -45,6 +46,22 @@ async def test_setup_with_cloud_config_fails(hass: HomeAssistant) -> None: assert entry.state is ConfigEntryState.SETUP_RETRY +async def test_setup_with_cloud_config_times_out(hass: HomeAssistant) -> None: + """Test setup of cloud config will retry if timed out.""" + entry = MockConfigEntry( + domain=mill.DOMAIN, + data={ + mill.CONF_USERNAME: "user", + mill.CONF_PASSWORD: "pswd", + mill.CONNECTION_TYPE: mill.CLOUD, + }, + ) + entry.add_to_hass(hass) + with patch("mill.Mill.connect", side_effect=asyncio.TimeoutError): + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state is ConfigEntryState.SETUP_RETRY + + async def test_setup_with_old_cloud_config(hass: HomeAssistant) -> None: """Test setup of old cloud config.""" entry = MockConfigEntry( From ef3ab54f1d97aeccb2b2bebaaecef6d8a94df67c Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 23 Mar 2024 22:44:53 +0200 Subject: [PATCH 1428/1691] Fix Shelly WallDisplay device power (#114071) --- homeassistant/components/shelly/binary_sensor.py | 2 +- homeassistant/components/shelly/sensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index f2d1fe15bbe..04df9fb1adc 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -167,7 +167,7 @@ RPC_SENSORS: Final = { entity_category=EntityCategory.DIAGNOSTIC, ), "external_power": RpcBinarySensorDescription( - key="devicepower:0", + key="devicepower", sub_key="external", name="External power", value=lambda status, _: status["present"], diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index ad25c83a19b..6cdeea9f842 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -934,7 +934,7 @@ RPC_SENSORS: Final = { state_class=SensorStateClass.MEASUREMENT, ), "battery": RpcSensorDescription( - key="devicepower:0", + key="devicepower", sub_key="battery", name="Battery", native_unit_of_measurement=PERCENTAGE, From 4e03d9cd474ecbcbbb86210c95eeaeb864789d81 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 23 Mar 2024 21:52:00 +0100 Subject: [PATCH 1429/1691] Centralize loading Axis entities (#114018) Centralize platform loading --- .../components/axis/binary_sensor.py | 30 ++------ homeassistant/components/axis/entity.py | 2 +- .../components/axis/hub/entity_loader.py | 72 +++++++++++++++++++ homeassistant/components/axis/hub/hub.py | 4 ++ homeassistant/components/axis/light.py | 27 ++----- homeassistant/components/axis/switch.py | 27 ++----- 6 files changed, 90 insertions(+), 72 deletions(-) create mode 100644 homeassistant/components/axis/hub/entity_loader.py diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index b6df07ce4ef..af8c394813a 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -2,12 +2,11 @@ from __future__ import annotations -from collections.abc import Callable, Iterable +from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta -from functools import partial -from axis.models.event import Event, EventOperation, EventTopic +from axis.models.event import Event, EventTopic from axis.vapix.interfaces.applications.fence_guard import FenceGuardHandler from axis.vapix.interfaces.applications.loitering_guard import LoiteringGuardHandler from axis.vapix.interfaces.applications.motion_guard import MotionGuardHandler @@ -182,28 +181,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up a Axis binary sensor.""" - hub = AxisHub.get_hub(hass, config_entry) - - @callback - def register_platform(descriptions: Iterable[AxisBinarySensorDescription]) -> None: - """Register entity platform to create entities on event initialized signal.""" - - @callback - def create_entity( - description: AxisBinarySensorDescription, event: Event - ) -> None: - """Create Axis entity.""" - if description.supported_fn(hub, event): - async_add_entities([AxisBinarySensor(hub, description, event)]) - - for description in descriptions: - hub.api.event.subscribe( - partial(create_entity, description), - topic_filter=description.event_topic, - operation_filter=EventOperation.INITIALIZED, - ) - - register_platform(ENTITY_DESCRIPTIONS) + AxisHub.get_hub(hass, config_entry).entity_loader.register_platform( + async_add_entities, AxisBinarySensor, ENTITY_DESCRIPTIONS + ) class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): diff --git a/homeassistant/components/axis/entity.py b/homeassistant/components/axis/entity.py index 7980b7217e8..b952000cca8 100644 --- a/homeassistant/components/axis/entity.py +++ b/homeassistant/components/axis/entity.py @@ -99,8 +99,8 @@ class AxisEventEntity(AxisEntity): self._event_id = event.id self._event_topic = event.topic_base - event_type = TOPIC_TO_EVENT_TYPE[event.topic_base] + event_type = TOPIC_TO_EVENT_TYPE[event.topic_base] self._attr_name = description.name_fn(hub, event) or f"{event_type} {event.id}" self._attr_unique_id = f"{hub.unique_id}-{event.topic}-{event.id}" diff --git a/homeassistant/components/axis/hub/entity_loader.py b/homeassistant/components/axis/hub/entity_loader.py new file mode 100644 index 00000000000..f86c3c9d6ff --- /dev/null +++ b/homeassistant/components/axis/hub/entity_loader.py @@ -0,0 +1,72 @@ +"""Axis network device entity loader. + +Central point to load entities for the different platforms. +""" +from __future__ import annotations + +from functools import partial +from typing import TYPE_CHECKING + +from axis.models.event import Event, EventOperation + +from homeassistant.core import callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from ..entity import AxisEventDescription, AxisEventEntity + +if TYPE_CHECKING: + from .hub import AxisHub + + +class AxisEntityLoader: + """Axis network device integration handling platforms for entity registration.""" + + def __init__(self, hub: AxisHub) -> None: + """Initialize the UniFi entity loader.""" + self.hub = hub + + self.platforms: list[ + tuple[ + AddEntitiesCallback, + type[AxisEventEntity], + tuple[AxisEventDescription, ...], + ] + ] = [] + + @callback + def register_platform( + self, + async_add_entities: AddEntitiesCallback, + entity_class: type[AxisEventEntity], + descriptions: tuple[AxisEventDescription, ...], + ) -> None: + """Register Axis entity platforms.""" + self.platforms.append((async_add_entities, entity_class, descriptions)) + + @callback + def initialize_platforms(self) -> None: + """Prepare event listeners and platforms.""" + + @callback + def load_entities( + platform_entity: type[AxisEventEntity], + descriptions: tuple[AxisEventDescription, ...], + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up listeners for events.""" + + @callback + def create_entity(description: AxisEventDescription, event: Event) -> None: + """Create Axis entity.""" + if description.supported_fn(self.hub, event): + async_add_entities([platform_entity(self.hub, description, event)]) + + for description in descriptions: + self.hub.api.event.subscribe( + partial(create_entity, description), + topic_filter=description.event_topic, + operation_filter=EventOperation.INITIALIZED, + ) + + for async_add_entities, entity_class, descriptions in self.platforms: + load_entities(entity_class, descriptions, async_add_entities) diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index 4c791ba1809..6252c0e29c5 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -22,6 +22,7 @@ from homeassistant.setup import async_when_setup from ..const import ATTR_MANUFACTURER, DOMAIN as AXIS_DOMAIN from .config import AxisConfig +from .entity_loader import AxisEntityLoader class AxisHub: @@ -33,6 +34,7 @@ class AxisHub: """Initialize the device.""" self.hass = hass self.config = AxisConfig.from_config_entry(config_entry) + self.entity_loader = AxisEntityLoader(self) self.api = api self.available = True @@ -131,6 +133,8 @@ class AxisHub: @callback def setup(self) -> None: """Set up the device events.""" + self.entity_loader.initialize_platforms() + self.api.stream.connection_status_callback.append( self.connection_status_callback ) diff --git a/homeassistant/components/axis/light.py b/homeassistant/components/axis/light.py index 1caeac3a247..af188469a74 100644 --- a/homeassistant/components/axis/light.py +++ b/homeassistant/components/axis/light.py @@ -1,11 +1,9 @@ """Support for Axis lights.""" -from collections.abc import Iterable from dataclasses import dataclass -from functools import partial from typing import Any -from axis.models.event import Event, EventOperation, EventTopic +from axis.models.event import Event, EventTopic from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -51,26 +49,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Axis light platform.""" - hub = AxisHub.get_hub(hass, config_entry) - - @callback - def register_platform(descriptions: Iterable[AxisLightDescription]) -> None: - """Register entity platform to create entities on event initialized signal.""" - - @callback - def create_entity(description: AxisLightDescription, event: Event) -> None: - """Create Axis entity.""" - if description.supported_fn(hub, event): - async_add_entities([AxisLight(hub, description, event)]) - - for description in descriptions: - hub.api.event.subscribe( - partial(create_entity, description), - topic_filter=description.event_topic, - operation_filter=EventOperation.INITIALIZED, - ) - - register_platform(ENTITY_DESCRIPTIONS) + AxisHub.get_hub(hass, config_entry).entity_loader.register_platform( + async_add_entities, AxisLight, ENTITY_DESCRIPTIONS + ) class AxisLight(AxisEventEntity, LightEntity): diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index 34d2e746c5a..895e2a9fa01 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -1,11 +1,9 @@ """Support for Axis switches.""" -from collections.abc import Iterable from dataclasses import dataclass -from functools import partial from typing import Any -from axis.models.event import Event, EventOperation, EventTopic +from axis.models.event import Event, EventTopic from homeassistant.components.switch import ( SwitchDeviceClass, @@ -44,26 +42,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Axis switch platform.""" - hub = AxisHub.get_hub(hass, config_entry) - - @callback - def register_platform(descriptions: Iterable[AxisSwitchDescription]) -> None: - """Register entity platform to create entities on event initialized signal.""" - - @callback - def create_entity(description: AxisSwitchDescription, event: Event) -> None: - """Create Axis entity.""" - if description.supported_fn(hub, event): - async_add_entities([AxisSwitch(hub, description, event)]) - - for description in descriptions: - hub.api.event.subscribe( - partial(create_entity, description), - topic_filter=description.event_topic, - operation_filter=EventOperation.INITIALIZED, - ) - - register_platform(ENTITY_DESCRIPTIONS) + AxisHub.get_hub(hass, config_entry).entity_loader.register_platform( + async_add_entities, AxisSwitch, ENTITY_DESCRIPTIONS + ) class AxisSwitch(AxisEventEntity, SwitchEntity): From 1c1d8d0317e03a0a9f729570288528d23d81bb80 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 11:31:23 -1000 Subject: [PATCH 1430/1691] Avoid creating inner function in EntityComponent setup (#114050) --- homeassistant/helpers/entity_component.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index dc9093b362b..b764a29a686 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -156,16 +156,16 @@ class EntityComponent(Generic[_EntityT]): # Generic discovery listener for loading platform dynamically # Refer to: homeassistant.helpers.discovery.async_load_platform() - async def component_platform_discovered( - platform: str, info: dict[str, Any] | None - ) -> None: - """Handle the loading of a platform.""" - await self.async_setup_platform(platform, {}, info) - discovery.async_listen_platform( - self.hass, self.domain, component_platform_discovered + self.hass, self.domain, self._async_component_platform_discovered ) + async def _async_component_platform_discovered( + self, platform: str, info: dict[str, Any] | None + ) -> None: + """Handle the loading of a platform.""" + await self.async_setup_platform(platform, {}, info) + async def async_setup_entry(self, config_entry: ConfigEntry) -> bool: """Set up a config entry.""" platform_type = config_entry.domain From c82c295eedf9c6745e8465ea4e69ea55c625a952 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sat, 23 Mar 2024 21:52:22 +0000 Subject: [PATCH 1431/1691] Add more sensors to aurora_abb_powerone (#114074) --- .../aurora_abb_powerone/coordinator.py | 8 +++- .../components/aurora_abb_powerone/sensor.py | 29 +++++++++++++++ .../aurora_abb_powerone/strings.json | 6 +++ .../aurora_abb_powerone/test_sensor.py | 37 ++++++++++++++++++- 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/aurora_abb_powerone/coordinator.py b/homeassistant/components/aurora_abb_powerone/coordinator.py index 68888a96603..0b4cea0f8b4 100644 --- a/homeassistant/components/aurora_abb_powerone/coordinator.py +++ b/homeassistant/components/aurora_abb_powerone/coordinator.py @@ -36,8 +36,11 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): try: self.client.connect() - # read ADC channel 3 (grid power output) + # See command 59 in the protocol manual linked in __init__.py + grid_voltage = self.client.measure(1, True) + grid_current = self.client.measure(2, True) power_watts = self.client.measure(3, True) + frequency = self.client.measure(4) temperature_c = self.client.measure(21) energy_wh = self.client.cumulated_energy(5) [alarm, *_] = self.client.alarms() @@ -57,7 +60,10 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): ) sleep(1) else: + data["grid_voltage"] = round(grid_voltage, 1) + data["grid_current"] = round(grid_current, 1) data["instantaneouspower"] = round(power_watts, 1) + data["grid_frequency"] = round(frequency, 1) data["temp"] = round(temperature_c, 1) data["totalenergy"] = round(energy_wh / 1000, 2) data["alarm"] = alarm diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 63e822db44f..49d56eb6b24 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -18,7 +18,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_SERIAL_NUMBER, EntityCategory, + UnitOfElectricCurrent, + UnitOfElectricPotential, UnitOfEnergy, + UnitOfFrequency, UnitOfPower, UnitOfTemperature, ) @@ -42,6 +45,32 @@ _LOGGER = logging.getLogger(__name__) ALARM_STATES = list(AuroraMapping.ALARM_STATES.values()) SENSOR_TYPES = [ + SensorEntityDescription( + key="grid_voltage", + device_class=SensorDeviceClass.VOLTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + translation_key="grid_voltage", + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="grid_current", + device_class=SensorDeviceClass.CURRENT, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + state_class=SensorStateClass.MEASUREMENT, + translation_key="grid_current", + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="grid_frequency", + device_class=SensorDeviceClass.FREQUENCY, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfFrequency.HERTZ, + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), SensorEntityDescription( key="alarm", device_class=SensorDeviceClass.ENUM, diff --git a/homeassistant/components/aurora_abb_powerone/strings.json b/homeassistant/components/aurora_abb_powerone/strings.json index 63ea1cfefd4..551c6dea3ec 100644 --- a/homeassistant/components/aurora_abb_powerone/strings.json +++ b/homeassistant/components/aurora_abb_powerone/strings.json @@ -21,6 +21,12 @@ }, "entity": { "sensor": { + "grid_voltage": { + "name": "Grid voltage" + }, + "grid_current": { + "name": "Grid current" + }, "alarm": { "name": "Alarm status" }, diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index 32e96df7fac..d40f11ef076 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -14,8 +14,10 @@ from homeassistant.components.aurora_abb_powerone.const import ( DOMAIN, SCAN_INTERVAL, ) +from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_SERIAL_NUMBER, CONF_ADDRESS, CONF_PORT from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_registry import EntityRegistry, RegistryEntryDisabler from tests.common import MockConfigEntry, async_fire_time_changed @@ -30,7 +32,10 @@ TEST_CONFIG = { def _simulated_returns(index, global_measure=None): returns = { + 1: 235.9476, # voltage + 2: 2.7894, # current 3: 45.678, # power + 4: 50.789, # frequency 21: 9.876, # temperature 5: 12345, # energy } @@ -56,7 +61,7 @@ def _mock_config_entry(): ) -async def test_sensors(hass: HomeAssistant) -> None: +async def test_sensors(hass: HomeAssistant, entity_registry: EntityRegistry) -> None: """Test data coming back from inverter.""" mock_entry = _mock_config_entry() @@ -97,6 +102,36 @@ async def test_sensors(hass: HomeAssistant) -> None: assert energy assert energy.state == "12.35" + # Test the 'disabled by default' sensors. + sensors = [ + ("sensor.mydevicename_grid_voltage", "235.9"), + ("sensor.mydevicename_grid_current", "2.8"), + ("sensor.mydevicename_frequency", "50.8"), + ] + for entity_id, _ in sensors: + assert not hass.states.get(entity_id) + assert ( + entry := entity_registry.async_get(entity_id) + ), f"Entity registry entry for {entity_id} is missing" + assert entry.disabled + assert entry.disabled_by is RegistryEntryDisabler.INTEGRATION + + # re-enable it + entity_registry.async_update_entity(entity_id=entity_id, disabled_by=None) + + # must reload the integration when enabling an entity + await hass.config_entries.async_unload(mock_entry.entry_id) + await hass.async_block_till_done() + assert mock_entry.state is ConfigEntryState.NOT_LOADED + mock_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + for entity_id, value in sensors: + item = hass.states.get(entity_id) + assert item + assert item.state == value + async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> None: """Test that darkness (no comms) is handled correctly.""" From d4f158d079008038db00caa47232ee15bcb9598f Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 23 Mar 2024 22:55:05 +0100 Subject: [PATCH 1432/1691] Add reconfigure step to holiday (#114057) --- .../components/holiday/config_flow.py | 64 +++++++++- homeassistant/components/holiday/strings.json | 8 +- tests/components/holiday/test_config_flow.py | 111 ++++++++++++++++++ 3 files changed, 181 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/holiday/config_flow.py b/homeassistant/components/holiday/config_flow.py index f9108f02bc9..a9b2f3e9772 100644 --- a/homeassistant/components/holiday/config_flow.py +++ b/homeassistant/components/holiday/config_flow.py @@ -8,7 +8,7 @@ from babel import Locale, UnknownLocaleError from holidays import list_supported_countries import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_COUNTRY from homeassistant.helpers.selector import ( CountrySelector, @@ -27,6 +27,7 @@ class HolidayConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Holiday.""" VERSION = 1 + config_entry: ConfigEntry | None def __init__(self) -> None: """Initialize the config flow.""" @@ -109,3 +110,64 @@ class HolidayConfigFlow(ConfigFlow, domain=DOMAIN): ) return self.async_show_form(step_id="province", data_schema=province_schema) + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the re-configuration of a province.""" + self.config_entry = self.hass.config_entries.async_get_entry( + self.context["entry_id"] + ) + return await self.async_step_reconfigure_confirm() + + async def async_step_reconfigure_confirm( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the re-configuration of a province.""" + assert self.config_entry + + if user_input is not None: + combined_input: dict[str, Any] = {**self.config_entry.data, **user_input} + + country = combined_input[CONF_COUNTRY] + province = combined_input.get(CONF_PROVINCE) + + self._async_abort_entries_match( + { + CONF_COUNTRY: country, + CONF_PROVINCE: province, + } + ) + + try: + locale = Locale.parse(self.hass.config.language, sep="-") + except UnknownLocaleError: + # Default to (US) English if language not recognized by babel + # Mainly an issue with English flavors such as "en-GB" + locale = Locale("en") + province_str = f", {province}" if province else "" + name = f"{locale.territories[country]}{province_str}" + + return self.async_update_reload_and_abort( + self.config_entry, + title=name, + data=combined_input, + reason="reconfigure_successful", + ) + + province_schema = vol.Schema( + { + vol.Optional(CONF_PROVINCE): SelectSelector( + SelectSelectorConfig( + options=SUPPORTED_COUNTRIES[ + self.config_entry.data[CONF_COUNTRY] + ], + mode=SelectSelectorMode.DROPDOWN, + ) + ) + } + ) + + return self.async_show_form( + step_id="reconfigure_confirm", data_schema=province_schema + ) diff --git a/homeassistant/components/holiday/strings.json b/homeassistant/components/holiday/strings.json index 53d403e790e..de013f44d60 100644 --- a/homeassistant/components/holiday/strings.json +++ b/homeassistant/components/holiday/strings.json @@ -2,7 +2,8 @@ "title": "Holiday", "config": { "abort": { - "already_configured": "Already configured. Only a single configuration for country/province combination possible." + "already_configured": "Already configured. Only a single configuration for country/province combination possible.", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "step": { "user": { @@ -14,6 +15,11 @@ "data": { "province": "Province" } + }, + "reconfigure_confirm": { + "data": { + "province": "[%key:component::holiday::config::step::province::data::province%]" + } } } } diff --git a/tests/components/holiday/test_config_flow.py b/tests/components/holiday/test_config_flow.py index eb21480fbb1..44a72f58404 100644 --- a/tests/components/holiday/test_config_flow.py +++ b/tests/components/holiday/test_config_flow.py @@ -219,3 +219,114 @@ async def test_form_babel_replace_dash_with_underscore(hass: HomeAssistant) -> N "country": "DE", "province": "BW", } + + +async def test_reconfigure(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: + """Test reconfigure flow.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Germany, BW", + data={"country": "DE", "province": "BW"}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": entry.entry_id, + }, + ) + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_PROVINCE: "NW", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + entry = hass.config_entries.async_get_entry(entry.entry_id) + assert entry.title == "Germany, NW" + assert entry.data == {"country": "DE", "province": "NW"} + + +async def test_reconfigure_incorrect_language( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test reconfigure flow default to English.""" + hass.config.language = "en-XX" + + entry = MockConfigEntry( + domain=DOMAIN, + title="Germany, BW", + data={"country": "DE", "province": "BW"}, + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": entry.entry_id, + }, + ) + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_PROVINCE: "NW", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + entry = hass.config_entries.async_get_entry(entry.entry_id) + assert entry.title == "Germany, NW" + assert entry.data == {"country": "DE", "province": "NW"} + + +async def test_reconfigure_entry_exists( + hass: HomeAssistant, mock_setup_entry: AsyncMock +) -> None: + """Test reconfigure flow stops if other entry already exist.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Germany, BW", + data={"country": "DE", "province": "BW"}, + ) + entry.add_to_hass(hass) + entry2 = MockConfigEntry( + domain=DOMAIN, + title="Germany, NW", + data={"country": "DE", "province": "NW"}, + ) + entry2.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_RECONFIGURE, + "entry_id": entry.entry_id, + }, + ) + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_PROVINCE: "NW", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + entry = hass.config_entries.async_get_entry(entry.entry_id) + assert entry.title == "Germany, BW" + assert entry.data == {"country": "DE", "province": "BW"} From 4ac439ef88e8178fec4355b800c406ea3231c947 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 12:02:02 -1000 Subject: [PATCH 1433/1691] Migrate nut to use aionut (#114078) --- homeassistant/components/nut/__init__.py | 85 ++++++++++--------- homeassistant/components/nut/config_flow.py | 13 ++- homeassistant/components/nut/device_action.py | 2 +- homeassistant/components/nut/manifest.json | 4 +- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- tests/components/nut/test_config_flow.py | 52 ++++++------ tests/components/nut/test_device_action.py | 10 +-- tests/components/nut/test_init.py | 16 ++-- tests/components/nut/test_sensor.py | 14 +-- tests/components/nut/util.py | 22 ++--- 11 files changed, 123 insertions(+), 107 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index d5a2453f62f..c9067bbb254 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -2,13 +2,12 @@ from __future__ import annotations -import asyncio from dataclasses import dataclass from datetime import timedelta import logging -from typing import cast +from typing import TYPE_CHECKING -from pynut2.nut2 import PyNUTClient, PyNUTError +from aionut import AIONUTClient, NUTError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -19,8 +18,9 @@ from homeassistant.const import ( CONF_RESOURCES, CONF_SCAN_INTERVAL, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -64,13 +64,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data = PyNUTData(host, port, alias, username, password) + entry.async_on_unload(data.async_shutdown) + async def async_update_data() -> dict[str, str]: """Fetch data from NUT.""" - async with asyncio.timeout(10): - await hass.async_add_executor_job(data.update) - if not data.status: - raise UpdateFailed("Error fetching UPS state") - return data.status + try: + return await data.async_update() + except NUTError as err: + raise UpdateFailed(f"Error fetching UPS state: {err}") from err coordinator = DataUpdateCoordinator( hass, @@ -83,6 +84,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Fetch initial data so we have data when entities subscribe await coordinator.async_config_entry_first_refresh() + + # Note that async_listen_once is not used here because the listener + # could be removed after the event is fired. + entry.async_on_unload( + hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, data.async_shutdown) + ) status = coordinator.data _LOGGER.debug("NUT Sensors Available: %s", status) @@ -95,7 +102,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if username is not None and password is not None: user_available_commands = { device_supported_command - for device_supported_command in data.list_commands() or {} + for device_supported_command in await data.async_list_commands() or {} if device_supported_command in INTEGRATION_SUPPORTED_COMMANDS } else: @@ -213,15 +220,14 @@ class PyNUTData: alias: str | None, username: str | None, password: str | None, + persistent: bool = True, ) -> None: """Initialize the data object.""" self._host = host self._alias = alias - # Establish client with persistent=False to open/close connection on - # each update call. This is more reliable with async. - self._client = PyNUTClient(self._host, port, username, password, 5, False) + self._client = AIONUTClient(self._host, port, username, password, 5, persistent) self.ups_list: dict[str, str] | None = None self._status: dict[str, str] | None = None self._device_info: NUTDeviceInfo | None = None @@ -241,11 +247,11 @@ class PyNUTData: """Return the device info for the ups.""" return self._device_info or NUTDeviceInfo() - def _get_alias(self) -> str | None: + async def _async_get_alias(self) -> str | None: """Get the ups alias from NUT.""" try: - ups_list: dict[str, str] = self._client.list_ups() - except PyNUTError as err: + ups_list = await self._client.list_ups() + except NUTError as err: _LOGGER.error("Failure getting NUT ups alias, %s", err) return None @@ -268,42 +274,45 @@ class PyNUTData: return device_info - def _get_status(self) -> dict[str, str] | None: + async def _async_get_status(self) -> dict[str, str]: """Get the ups status from NUT.""" if self._alias is None: - self._alias = self._get_alias() + self._alias = await self._async_get_alias() + if TYPE_CHECKING: + assert self._alias is not None + return await self._client.list_vars(self._alias) - try: - status: dict[str, str] = self._client.list_vars(self._alias) - except (PyNUTError, ConnectionResetError) as err: - _LOGGER.debug("Error getting NUT vars for host %s: %s", self._host, err) - return None - - return status - - def update(self) -> None: + async def async_update(self) -> dict[str, str]: """Fetch the latest status from NUT.""" - self._status = self._get_status() + self._status = await self._async_get_status() if self._device_info is None: self._device_info = self._get_device_info() + return self._status - async def async_run_command( - self, hass: HomeAssistant, command_name: str | None - ) -> None: + async def async_run_command(self, command_name: str) -> None: """Invoke instant command in UPS.""" + if TYPE_CHECKING: + assert self._alias is not None + try: - await hass.async_add_executor_job( - self._client.run_command, self._alias, command_name - ) - except PyNUTError as err: + await self._client.run_command(self._alias, command_name) + except NUTError as err: raise HomeAssistantError( f"Error running command {command_name}, {err}" ) from err - def list_commands(self) -> dict[str, str] | None: + async def async_list_commands(self) -> set[str] | None: """Fetch the list of supported commands.""" + if TYPE_CHECKING: + assert self._alias is not None + try: - return cast(dict[str, str], self._client.list_commands(self._alias)) - except PyNUTError as err: + return await self._client.list_commands(self._alias) + except NUTError as err: _LOGGER.error("Error retrieving supported commands %s", err) return None + + @callback + def async_shutdown(self, _: Event | None = None) -> None: + """Shutdown the client connection.""" + self._client.shutdown() diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 5967aef4dac..3f3de8a126c 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -6,6 +6,7 @@ from collections.abc import Mapping import logging from typing import Any +from aionut import NUTError import voluptuous as vol from homeassistant.components import zeroconf @@ -67,10 +68,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, username = data.get(CONF_USERNAME) password = data.get(CONF_PASSWORD) - nut_data = PyNUTData(host, port, alias, username, password) - await hass.async_add_executor_job(nut_data.update) - if not (status := nut_data.status): - raise CannotConnect + nut_data = PyNUTData(host, port, alias, username, password, persistent=False) + try: + status = await nut_data.async_update() + except NUTError as err: + raise CannotConnect(str(err)) from err + + if not alias and not nut_data.ups_list: + raise CannotConnect("No UPSes found on the NUT server") return {"ups_list": nut_data.ups_list, "available_resources": status} diff --git a/homeassistant/components/nut/device_action.py b/homeassistant/components/nut/device_action.py index bf16b3664f1..0ec58e651b2 100644 --- a/homeassistant/components/nut/device_action.py +++ b/homeassistant/components/nut/device_action.py @@ -58,7 +58,7 @@ async def async_call_action_from_config( device_id: str = config[CONF_DEVICE_ID] entry_id = _get_entry_id_from_device_id(hass, device_id) data: PyNUTData = hass.data[DOMAIN][entry_id][PYNUT_DATA] - await data.async_run_command(hass, command_name) + await data.async_run_command(command_name) def _get_device_action_name(command_name: str) -> str: diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 0303dd70ec1..6cd073a7476 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/nut", "integration_type": "device", "iot_class": "local_polling", - "loggers": ["pynut2"], - "requirements": ["pynut2==2.1.2"], + "loggers": ["aionut"], + "requirements": ["aionut==4.0.0"], "zeroconf": ["_nut._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 27cdf5a353f..7ca3f82dae1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -314,6 +314,9 @@ aionanoleaf==0.2.1 # homeassistant.components.notion aionotion==2024.03.0 +# homeassistant.components.nut +aionut==4.0.0 + # homeassistant.components.oncue aiooncue==0.3.7 @@ -1987,9 +1990,6 @@ pynobo==1.6.0 # homeassistant.components.nuki pynuki==1.6.3 -# homeassistant.components.nut -pynut2==2.1.2 - # homeassistant.components.nws pynws==1.6.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8ccb0f1dd24..eee4500495d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -287,6 +287,9 @@ aionanoleaf==0.2.1 # homeassistant.components.notion aionotion==2024.03.0 +# homeassistant.components.nut +aionut==4.0.0 + # homeassistant.components.oncue aiooncue==0.3.7 @@ -1541,9 +1544,6 @@ pynobo==1.6.0 # homeassistant.components.nuki pynuki==1.6.3 -# homeassistant.components.nut -pynut2==2.1.2 - # homeassistant.components.nws pynws==1.6.0 diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index 6dca92d1294..b6a9590f457 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -3,7 +3,7 @@ from ipaddress import ip_address from unittest.mock import patch -from pynut2.nut2 import PyNUTError +from aionut import NUTError from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components import zeroconf @@ -20,7 +20,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant -from .util import _get_mock_pynutclient +from .util import _get_mock_nutclient from tests.common import MockConfigEntry @@ -51,12 +51,12 @@ async def test_form_zeroconf(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ), patch( "homeassistant.components.nut.async_setup_entry", @@ -89,12 +89,12 @@ async def test_form_user_one_ups(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ), patch( "homeassistant.components.nut.async_setup_entry", @@ -138,13 +138,13 @@ async def test_form_user_multiple_ups(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage"}, list_ups={"ups1": "UPS 1", "ups2": "UPS2"}, ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): result2 = await hass.config_entries.flow.async_configure( @@ -161,7 +161,7 @@ async def test_form_user_multiple_ups(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.FORM with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ), patch( "homeassistant.components.nut.async_setup_entry", @@ -199,12 +199,12 @@ async def test_form_user_one_ups_with_ignored_entry(hass: HomeAssistant) -> None assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ), patch( "homeassistant.components.nut.async_setup_entry", @@ -238,10 +238,10 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - mock_pynut = _get_mock_pynutclient() + mock_pynut = _get_mock_nutclient() with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): result2 = await hass.config_entries.flow.async_configure( @@ -258,11 +258,11 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} with patch( - "homeassistant.components.nut.PyNUTClient.list_ups", - side_effect=PyNUTError, + "homeassistant.components.nut.AIONUTClient.list_ups", + side_effect=NUTError, ), patch( - "homeassistant.components.nut.PyNUTClient.list_vars", - side_effect=PyNUTError, + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTError, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -278,11 +278,11 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} with patch( - "homeassistant.components.nut.PyNUTClient.list_ups", - return_value=["ups1"], + "homeassistant.components.nut.AIONUTClient.list_ups", + return_value={"ups1"}, ), patch( - "homeassistant.components.nut.PyNUTClient.list_vars", - side_effect=TypeError, + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=Exception, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -314,13 +314,13 @@ async def test_abort_if_already_setup(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage"}, list_ups={"ups1": "UPS 1"}, ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): result2 = await hass.config_entries.flow.async_configure( @@ -352,13 +352,13 @@ async def test_abort_if_already_setup_alias(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage"}, list_ups={"ups1": "UPS 1", "ups2": "UPS 2"}, ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): result2 = await hass.config_entries.flow.async_configure( @@ -373,7 +373,7 @@ async def test_abort_if_already_setup_alias(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.FORM with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): result3 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/nut/test_device_action.py b/tests/components/nut/test_device_action.py index 8938de54457..8113b19e313 100644 --- a/tests/components/nut/test_device_action.py +++ b/tests/components/nut/test_device_action.py @@ -1,8 +1,8 @@ """The tests for Network UPS Tools (NUT) device actions.""" -from unittest.mock import MagicMock +from unittest.mock import AsyncMock -from pynut2.nut2 import PyNUTError +from aionut import NUTError import pytest from pytest_unordered import unordered @@ -99,7 +99,7 @@ async def test_list_commands_exception( ) -> None: """Test there are no actions if list_commands raises exception.""" await async_init_integration( - hass, list_vars={"ups.status": "OL"}, list_commands_side_effect=PyNUTError + hass, list_vars={"ups.status": "OL"}, list_commands_side_effect=NUTError ) device_entry = next(device for device in device_registry.devices.values()) @@ -137,7 +137,7 @@ async def test_action(hass: HomeAssistant, device_registry: dr.DeviceRegistry) - "beeper.enable": None, "beeper.disable": None, } - run_command = MagicMock() + run_command = AsyncMock() await async_init_integration( hass, list_ups={"someUps": "Some UPS"}, @@ -196,7 +196,7 @@ async def test_rund_command_exception( list_commands_return_value = {"beeper.enable": None} error_message = "Something wrong happened" - run_command = MagicMock(side_effect=PyNUTError(error_message)) + run_command = AsyncMock(side_effect=NUTError(error_message)) await async_init_integration( hass, list_vars={"ups.status": "OL"}, diff --git a/tests/components/nut/test_init.py b/tests/components/nut/test_init.py index 2cee229897d..d15e9d4b12a 100644 --- a/tests/components/nut/test_init.py +++ b/tests/components/nut/test_init.py @@ -2,12 +2,14 @@ from unittest.mock import patch +from aionut import NUTError + from homeassistant.components.nut.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant -from .util import _get_mock_pynutclient +from .util import _get_mock_nutclient from tests.common import MockConfigEntry @@ -20,12 +22,12 @@ async def test_async_setup_entry(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_ups={"ups1": "UPS 1"}, list_vars={"ups.status": "OL"} ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): await hass.config_entries.async_setup(entry.entry_id) @@ -55,11 +57,11 @@ async def test_config_not_ready(hass: HomeAssistant) -> None: entry.add_to_hass(hass) with patch( - "homeassistant.components.nut.PyNUTClient.list_ups", - return_value=["ups1"], + "homeassistant.components.nut.AIONUTClient.list_ups", + return_value={"ups1"}, ), patch( - "homeassistant.components.nut.PyNUTClient.list_vars", - side_effect=ConnectionResetError, + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTError, ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/nut/test_sensor.py b/tests/components/nut/test_sensor.py index be435fb6e6c..c4a8159b8cc 100644 --- a/tests/components/nut/test_sensor.py +++ b/tests/components/nut/test_sensor.py @@ -15,7 +15,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from .util import _get_mock_pynutclient, async_init_integration +from .util import _get_mock_nutclient, async_init_integration from tests.common import MockConfigEntry @@ -100,12 +100,12 @@ async def test_state_sensors(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_ups={"ups1": "UPS 1"}, list_vars={"ups.status": "OL"} ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): await hass.config_entries.async_setup(entry.entry_id) @@ -125,12 +125,12 @@ async def test_unknown_state_sensors(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_ups={"ups1": "UPS 1"}, list_vars={"ups.status": "OQ"} ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): await hass.config_entries.async_setup(entry.entry_id) @@ -155,12 +155,12 @@ async def test_stale_options(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_ups={"ups1": "UPS 1"}, list_vars={"battery.charge": "10"} ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/nut/util.py b/tests/components/nut/util.py index a0fadf47f19..3bc48764816 100644 --- a/tests/components/nut/util.py +++ b/tests/components/nut/util.py @@ -1,7 +1,7 @@ """Tests for the nut integration.""" import json -from unittest.mock import MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, patch from homeassistant.components.nut.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME @@ -10,25 +10,25 @@ from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture -def _get_mock_pynutclient( +def _get_mock_nutclient( list_vars=None, list_ups=None, list_commands_return_value=None, list_commands_side_effect=None, run_command=None, ): - pynutclient = MagicMock() - type(pynutclient).list_ups = MagicMock(return_value=list_ups) - type(pynutclient).list_vars = MagicMock(return_value=list_vars) + nutclient = MagicMock() + type(nutclient).list_ups = AsyncMock(return_value=list_ups) + type(nutclient).list_vars = AsyncMock(return_value=list_vars) if list_commands_return_value is None: list_commands_return_value = {} - type(pynutclient).list_commands = MagicMock( + type(nutclient).list_commands = AsyncMock( return_value=list_commands_return_value, side_effect=list_commands_side_effect ) if run_command is None: - run_command = MagicMock() - type(pynutclient).run_command = run_command - return pynutclient + run_command = AsyncMock() + type(nutclient).run_command = run_command + return nutclient async def async_init_integration( @@ -52,7 +52,7 @@ async def async_init_integration( if list_vars is None: list_vars = json.loads(load_fixture(ups_fixture)) - mock_pynut = _get_mock_pynutclient( + mock_pynut = _get_mock_nutclient( list_ups=list_ups, list_vars=list_vars, list_commands_return_value=list_commands_return_value, @@ -61,7 +61,7 @@ async def async_init_integration( ) with patch( - "homeassistant.components.nut.PyNUTClient", + "homeassistant.components.nut.AIONUTClient", return_value=mock_pynut, ): entry = MockConfigEntry( From f079c1c236197ed202250c35e6ac358050876ecc Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sun, 24 Mar 2024 08:40:42 +1000 Subject: [PATCH 1434/1691] Allow retry during Config Flow in Advantage Air (#114083) --- homeassistant/components/advantage_air/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/advantage_air/config_flow.py b/homeassistant/components/advantage_air/config_flow.py index 4a4e18301bf..df3ee1c3638 100644 --- a/homeassistant/components/advantage_air/config_flow.py +++ b/homeassistant/components/advantage_air/config_flow.py @@ -45,7 +45,7 @@ class AdvantageAirConfigFlow(ConfigFlow, domain=DOMAIN): port=port, session=async_get_clientsession(self.hass), retry=ADVANTAGE_AIR_RETRY, - ).async_get(1) + ).async_get() except ApiError: errors["base"] = "cannot_connect" else: From de831b6e87397552400ba8c6c78b4c985840fb98 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 18:24:52 -1000 Subject: [PATCH 1435/1691] Small performance improvement to template expand (#114086) * Small performance improvement to template expand - Avoid fetching entity sources each loop - Skip already found entities - Avoid startswith in favor of equality check * unneeded changes --- homeassistant/helpers/template.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index bb0d868bf3c..58a7e27714c 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1252,6 +1252,7 @@ def expand(hass: HomeAssistant, *args: Any) -> Iterable[State]: search = list(args) found = {} + sources = entity_helper.entity_sources(hass) while search: entity = search.pop() if isinstance(entity, str): @@ -1267,14 +1268,17 @@ def expand(hass: HomeAssistant, *args: Any) -> Iterable[State]: # ignore other types continue - if entity_id.startswith(_GROUP_DOMAIN_PREFIX) or ( - (source := entity_helper.entity_sources(hass).get(entity_id)) - and source["domain"] == "group" + if entity_id in found: + continue + + domain = entity.domain + if domain == "group" or ( + (source := sources.get(entity_id)) and source["domain"] == "group" ): # Collect state will be called in here since it's wrapped if group_entities := entity.attributes.get(ATTR_ENTITY_ID): search += group_entities - elif entity_id.startswith(_ZONE_DOMAIN_PREFIX): + elif domain == "zone": if zone_entities := entity.attributes.get(ATTR_PERSONS): search += zone_entities else: From e8cb6a8e29c3d153a521602680b2ab022608eabd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 Mar 2024 22:22:09 -1000 Subject: [PATCH 1436/1691] Optimize loading of translations (#114089) --- homeassistant/bootstrap.py | 8 +++++- .../components/flux_led/discovery.py | 3 ++- .../components/steamist/config_flow.py | 11 ++++---- homeassistant/setup.py | 24 +++++++++-------- tests/components/device_tracker/test_init.py | 10 +++++++ tests/components/tplink/test_init.py | 6 +++-- tests/components/tplink/test_light.py | 26 +++++++++---------- tests/components/tplink/test_sensor.py | 9 ++++--- tests/components/tplink/test_switch.py | 20 +++++++++----- tests/test_bootstrap.py | 13 +++++++++- tests/test_setup.py | 5 ++-- 11 files changed, 88 insertions(+), 47 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index cd6082d6abc..d1e5000cbd7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -734,7 +734,7 @@ async def _async_resolve_domains_to_setup( *chain.from_iterable(platform_integrations.values()), } - translations_to_load = {*domains_to_setup, *additional_manifests_to_load} + translations_to_load = additional_manifests_to_load.copy() # Resolve all dependencies so we know all integrations # that will have to be loaded and start right-away @@ -818,6 +818,12 @@ async def _async_resolve_domains_to_setup( "check installed requirements", eager_start=True, ) + + # + # Only add the domains_to_setup after we finish resolving + # as new domains are likely to added in the process + # + translations_to_load.update(domains_to_setup) # Start loading translations for all integrations we are going to set up # in the background so they are ready when we need them. This avoids a # lot of waiting for the translation load lock and a thundering herd of diff --git a/homeassistant/components/flux_led/discovery.py b/homeassistant/components/flux_led/discovery.py index 9ff1864ec6a..9600f773701 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -29,6 +29,7 @@ from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, discovery_flow +from homeassistant.util.async_ import create_eager_task from homeassistant.util.network import is_ip_address from .const import ( @@ -185,7 +186,7 @@ async def async_discover_devices( for idx, discovered in enumerate( await asyncio.gather( *[ - scanner.async_scan(timeout=timeout, address=address) + create_eager_task(scanner.async_scan(timeout=timeout, address=address)) for address in targets ], return_exceptions=True, diff --git a/homeassistant/components/steamist/config_flow.py b/homeassistant/components/steamist/config_flow.py index aaa2bd9c853..9d2fa5c6c42 100644 --- a/homeassistant/components/steamist/config_flow.py +++ b/homeassistant/components/steamist/config_flow.py @@ -10,7 +10,7 @@ from discovery30303 import Device30303, normalize_mac import voluptuous as vol from homeassistant.components import dhcp -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MODEL, CONF_NAME from homeassistant.core import callback from homeassistant.helpers import device_registry as dr @@ -72,10 +72,11 @@ class SteamistConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(mac) for entry in self._async_current_entries(include_ignore=False): if entry.unique_id == mac or entry.data[CONF_HOST] == host: - if async_update_entry_from_discovery(self.hass, entry, device): - self.hass.async_create_task( - self.hass.config_entries.async_reload(entry.entry_id) - ) + if ( + async_update_entry_from_discovery(self.hass, entry, device) + and entry.state is not ConfigEntryState.SETUP_IN_PROGRESS + ): + self.hass.config_entries.async_schedule_reload(entry.entry_id) return self.async_abort(reason="already_configured") self.context[CONF_HOST] = host for progress in self._async_in_progress(): diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 07b7b40deab..f9c86b23d96 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -285,6 +285,19 @@ async def _async_setup_component( # noqa: C901 log_error(f"Dependency is disabled - {integration.disabled}") return False + integration_set = {domain} + + load_translations_task: asyncio.Task[None] | None = None + if integration.has_translations and not translation.async_translations_loaded( + hass, integration_set + ): + # For most cases we expect the translations are already + # loaded since we try to load them in bootstrap ahead of time. + # If for some reason the background task in bootstrap was too slow + # or the integration was added after bootstrap, we will load them here. + load_translations_task = create_eager_task( + translation.async_load_integrations(hass, integration_set) + ) # Validate all dependencies exist and there are no circular dependencies if not await integration.resolve_dependencies(): return False @@ -351,17 +364,6 @@ async def _async_setup_component( # noqa: C901 ) _LOGGER.info("Setting up %s", domain) - integration_set = {domain} - - load_translations_task: asyncio.Task[None] | None = None - if not translation.async_translations_loaded(hass, integration_set): - # For most cases we expect the translations are already - # loaded since we try to load them in bootstrap ahead of time. - # If for some reason the background task in bootstrap was too slow - # or the integration was added after bootstrap, we will load them here. - load_translations_task = create_eager_task( - translation.async_load_integrations(hass, integration_set) - ) with async_start_setup(hass, integration=domain, phase=SetupPhases.SETUP): if hasattr(component, "PLATFORM_SCHEMA"): diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index c2b2075468d..e14892c565b 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -234,6 +234,8 @@ async def test_discover_platform( ) -> None: """Test discovery of device_tracker demo platform.""" await async_setup_component(hass, "homeassistant", {}) + await async_setup_component(hass, device_tracker.DOMAIN, {}) + await hass.async_block_till_done() with patch("homeassistant.components.device_tracker.legacy.update_config"): await discovery.async_load_platform( hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}} @@ -337,6 +339,7 @@ async def test_see_service( """Test the see service with a unicode dev_id and NO MAC.""" with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() params = { "dev_id": "some_device", "host_name": "example.com", @@ -372,6 +375,7 @@ async def test_see_service_guard_config_entry( mock_registry(hass, {entity_id: mock_entry}) devices = mock_device_tracker_conf assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() params = {"dev_id": dev_id, "gps": [0.3, 0.8]} common.async_see(hass, **params) @@ -388,6 +392,7 @@ async def test_new_device_event_fired( """Test that the device tracker will fire an event.""" with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() test_events = [] @callback @@ -423,6 +428,7 @@ async def test_duplicate_yaml_keys( devices = mock_device_tracker_conf with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() common.async_see(hass, "mac_1", host_name="hello") common.async_see(hass, "mac_2", host_name="hello") @@ -442,6 +448,7 @@ async def test_invalid_dev_id( devices = mock_device_tracker_conf with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() common.async_see(hass, dev_id="hello-world") await hass.async_block_till_done() @@ -454,6 +461,7 @@ async def test_see_state( ) -> None: """Test device tracker see records state correctly.""" assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) + await hass.async_block_till_done() params = { "mac": "AA:BB:CC:DD:EE:FF", @@ -508,6 +516,7 @@ async def test_see_passive_zone_state( } await async_setup_component(hass, zone.DOMAIN, {"zone": zone_info}) + await hass.async_block_till_done() scanner = getattr(hass.components, "test.device_tracker").SCANNER scanner.reset() @@ -603,6 +612,7 @@ async def test_async_added_to_hass(hass: HomeAssistant) -> None: files = {path: "jk:\n name: JK Phone\n track: True"} with patch_yaml_files(files): assert await async_setup_component(hass, device_tracker.DOMAIN, {}) + await hass.async_block_till_done() state = hass.states.get("device_tracker.jk") assert state diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 07d3916cc87..384949c3493 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -70,7 +70,7 @@ async def test_configuring_tplink_causes_discovery( async def test_config_entry_reload(hass: HomeAssistant) -> None: """Test that a config entry can be reloaded.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) with _patch_discovery(), _patch_single_discovery(), _patch_connect(): @@ -266,7 +266,9 @@ async def test_config_entry_errors( async def test_plug_auth_fails(hass: HomeAssistant) -> None: """Test a smart plug auth failure.""" - config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=MAC_ADDRESS) + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS + ) config_entry.add_to_hass(hass) plug = _mocked_plug() with _patch_discovery(device=plug), _patch_connect(device=plug): diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index 193a52c4e2d..6a34afd7260 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -24,7 +24,7 @@ from homeassistant.components.light import ( DOMAIN as LIGHT_DOMAIN, ) from homeassistant.components.tplink.const import DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON +from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -45,7 +45,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed async def test_light_unique_id(hass: HomeAssistant) -> None: """Test a light unique id.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -67,7 +67,7 @@ async def test_color_light( ) -> None: """Test a color light and that all transitions are correctly passed.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb.color_temp = None @@ -147,7 +147,7 @@ async def test_color_light( async def test_color_light_no_temp(hass: HomeAssistant) -> None: """Test a light.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -207,7 +207,7 @@ async def test_color_temp_light( ) -> None: """Test a light.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb.is_color = is_color @@ -286,7 +286,7 @@ async def test_color_temp_light( async def test_brightness_only_light(hass: HomeAssistant) -> None: """Test a light.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -330,7 +330,7 @@ async def test_brightness_only_light(hass: HomeAssistant) -> None: async def test_on_off_light(hass: HomeAssistant) -> None: """Test a light.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -364,7 +364,7 @@ async def test_on_off_light(hass: HomeAssistant) -> None: async def test_off_at_start_light(hass: HomeAssistant) -> None: """Test a light.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -388,7 +388,7 @@ async def test_off_at_start_light(hass: HomeAssistant) -> None: async def test_dimmer_turn_on_fix(hass: HomeAssistant) -> None: """Test a light.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -414,7 +414,7 @@ async def test_dimmer_turn_on_fix(hass: HomeAssistant) -> None: async def test_smart_strip_effects(hass: HomeAssistant) -> None: """Test smart strip effects.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) strip = _mocked_smart_light_strip() @@ -496,7 +496,7 @@ async def test_smart_strip_effects(hass: HomeAssistant) -> None: async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None: """Test smart strip custom random effects.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) strip = _mocked_smart_light_strip() @@ -653,7 +653,7 @@ async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None: async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) -> None: """Test smart strip custom random effects at startup.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) strip = _mocked_smart_light_strip() @@ -686,7 +686,7 @@ async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) -> async def test_smart_strip_custom_sequence_effect(hass: HomeAssistant) -> None: """Test smart strip custom sequence effects.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) strip = _mocked_smart_light_strip() diff --git a/tests/components/tplink/test_sensor.py b/tests/components/tplink/test_sensor.py index b67ed031df3..15bc23837fa 100644 --- a/tests/components/tplink/test_sensor.py +++ b/tests/components/tplink/test_sensor.py @@ -4,6 +4,7 @@ from unittest.mock import Mock from homeassistant.components import tplink from homeassistant.components.tplink.const import DOMAIN +from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -16,7 +17,7 @@ from tests.common import MockConfigEntry async def test_color_light_with_an_emeter(hass: HomeAssistant) -> None: """Test a light with an emeter.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -56,7 +57,7 @@ async def test_color_light_with_an_emeter(hass: HomeAssistant) -> None: async def test_plug_with_an_emeter(hass: HomeAssistant) -> None: """Test a plug with an emeter.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) plug = _mocked_plug() @@ -91,7 +92,7 @@ async def test_plug_with_an_emeter(hass: HomeAssistant) -> None: async def test_color_light_no_emeter(hass: HomeAssistant) -> None: """Test a light without an emeter.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() @@ -120,7 +121,7 @@ async def test_color_light_no_emeter(hass: HomeAssistant) -> None: async def test_sensor_unique_id(hass: HomeAssistant) -> None: """Test a sensor unique ids.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) plug = _mocked_plug() diff --git a/tests/components/tplink/test_switch.py b/tests/components/tplink/test_switch.py index 372651ea250..6326e9bb671 100644 --- a/tests/components/tplink/test_switch.py +++ b/tests/components/tplink/test_switch.py @@ -9,7 +9,13 @@ import pytest from homeassistant.components import tplink from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.tplink.const import DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component @@ -30,7 +36,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed async def test_plug(hass: HomeAssistant) -> None: """Test a smart plug.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) plug = _mocked_plug() @@ -66,7 +72,7 @@ async def test_plug(hass: HomeAssistant) -> None: async def test_led_switch(hass: HomeAssistant, dev, domain: str) -> None: """Test LED setting for plugs, strips and dimmers.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) with _patch_discovery(device=dev), _patch_connect(device=dev): @@ -96,7 +102,7 @@ async def test_led_switch(hass: HomeAssistant, dev, domain: str) -> None: async def test_plug_unique_id(hass: HomeAssistant) -> None: """Test a plug unique id.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) plug = _mocked_plug() @@ -112,7 +118,7 @@ async def test_plug_unique_id(hass: HomeAssistant) -> None: async def test_plug_update_fails(hass: HomeAssistant) -> None: """Test a smart plug update failure.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) plug = _mocked_plug() @@ -134,7 +140,7 @@ async def test_plug_update_fails(hass: HomeAssistant) -> None: async def test_strip(hass: HomeAssistant) -> None: """Test a smart strip.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) strip = _mocked_strip() @@ -182,7 +188,7 @@ async def test_strip(hass: HomeAssistant) -> None: async def test_strip_unique_ids(hass: HomeAssistant) -> None: """Test a strip unique id.""" already_migrated_config_entry = MockConfigEntry( - domain=DOMAIN, data={}, unique_id=MAC_ADDRESS + domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) strip = _mocked_strip() diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index a4453849b5e..90e9017a5c1 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,5 +1,4 @@ """Test the bootstrapping.""" - import asyncio from collections.abc import Generator, Iterable import glob @@ -17,8 +16,10 @@ from homeassistant.const import SIGNAL_BOOTSTRAP_INTEGRATIONS from homeassistant.core import CoreState, HomeAssistant, async_get_hass, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.translation import async_translations_loaded from homeassistant.helpers.typing import ConfigType from homeassistant.loader import Integration +from homeassistant.setup import BASE_PLATFORMS from .common import ( MockConfigEntry, @@ -105,6 +106,16 @@ async def test_empty_setup(hass: HomeAssistant) -> None: assert domain in hass.config.components, domain +@pytest.mark.parametrize("load_registries", [False]) +async def test_preload_translations(hass: HomeAssistant) -> None: + """Test translations are preloaded for all frontend deps and base platforms.""" + await bootstrap.async_from_config_dict({}, hass) + await hass.async_block_till_done(wait_background_tasks=True) + frontend = await loader.async_get_integration(hass, "frontend") + assert async_translations_loaded(hass, set(frontend.all_dependencies)) + assert async_translations_loaded(hass, BASE_PLATFORMS) + + async def test_core_failure_loads_recovery_mode( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: diff --git a/tests/test_setup.py b/tests/test_setup.py index b48e7252e65..387ac19c5d0 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1167,8 +1167,9 @@ async def test_loading_component_loads_translations(hass: HomeAssistant) -> None mock_setup = Mock(return_value=True) mock_integration(hass, MockModule("comp", setup=mock_setup)) - - assert await setup.async_setup_component(hass, "comp", {}) + integration = await loader.async_get_integration(hass, "comp") + with patch.object(integration, "has_translations", True): + assert await setup.async_setup_component(hass, "comp", {}) assert mock_setup.called assert translation.async_translations_loaded(hass, {"comp"}) is True From 2f84183ccde036b97bc459d7cf36f649acc06848 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 24 Mar 2024 01:24:44 -0700 Subject: [PATCH 1437/1691] Bump gcal-sync to 6.0.4 (#114085) --- homeassistant/components/google/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/google/manifest.json b/homeassistant/components/google/manifest.json index ec9fb7018d6..00561cb5fd6 100644 --- a/homeassistant/components/google/manifest.json +++ b/homeassistant/components/google/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/calendar.google", "iot_class": "cloud_polling", "loggers": ["googleapiclient"], - "requirements": ["gcal-sync==6.0.3", "oauth2client==4.1.3", "ical==7.0.3"] + "requirements": ["gcal-sync==6.0.4", "oauth2client==4.1.3", "ical==7.0.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7ca3f82dae1..9114e5ea805 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -911,7 +911,7 @@ gardena-bluetooth==1.4.1 gassist-text==0.0.11 # homeassistant.components.google -gcal-sync==6.0.3 +gcal-sync==6.0.4 # homeassistant.components.geniushub geniushub-client==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eee4500495d..3bbaf55bc42 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -743,7 +743,7 @@ gardena-bluetooth==1.4.1 gassist-text==0.0.11 # homeassistant.components.google -gcal-sync==6.0.3 +gcal-sync==6.0.4 # homeassistant.components.geocaching geocachingapi==0.2.1 From 925efe0a923708f86f18f6b97eaa37588ef2c587 Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Sun, 24 Mar 2024 08:27:54 +0000 Subject: [PATCH 1438/1691] Bump tplink_omada to 1.3.12 for ER7212PC support (#114077) --- homeassistant/components/tplink_omada/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tplink_omada/manifest.json b/homeassistant/components/tplink_omada/manifest.json index 33fc85d7c79..9544470d7a9 100644 --- a/homeassistant/components/tplink_omada/manifest.json +++ b/homeassistant/components/tplink_omada/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/tplink_omada", "integration_type": "hub", "iot_class": "local_polling", - "requirements": ["tplink-omada-client==1.3.11"] + "requirements": ["tplink-omada-client==1.3.12"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9114e5ea805..a33da54e754 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2727,7 +2727,7 @@ total-connect-client==2023.2 tp-connected==0.0.4 # homeassistant.components.tplink_omada -tplink-omada-client==1.3.11 +tplink-omada-client==1.3.12 # homeassistant.components.transmission transmission-rpc==7.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3bbaf55bc42..1fe16e9b6e6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2089,7 +2089,7 @@ toonapi==0.3.0 total-connect-client==2023.2 # homeassistant.components.tplink_omada -tplink-omada-client==1.3.11 +tplink-omada-client==1.3.12 # homeassistant.components.transmission transmission-rpc==7.0.3 From ba5a4a17c4cffc60cf089a8ea1c2c37516ceaf89 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sun, 24 Mar 2024 18:29:10 +1000 Subject: [PATCH 1439/1691] Add vehicle config to coordinator to fix bug in Teslemetry (#113850) --- homeassistant/components/teslemetry/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/teslemetry/coordinator.py b/homeassistant/components/teslemetry/coordinator.py index d8ded1016fa..27ff45f75a3 100644 --- a/homeassistant/components/teslemetry/coordinator.py +++ b/homeassistant/components/teslemetry/coordinator.py @@ -20,6 +20,7 @@ ENDPOINTS = [ VehicleDataEndpoint.DRIVE_STATE, VehicleDataEndpoint.LOCATION_DATA, VehicleDataEndpoint.VEHICLE_STATE, + VehicleDataEndpoint.VEHICLE_CONFIG, ] From d14a442ac3cc089ccbeb5b615f53a9f74bc05945 Mon Sep 17 00:00:00 2001 From: Floris272 <60342568+Floris272@users.noreply.github.com> Date: Sun, 24 Mar 2024 09:35:53 +0100 Subject: [PATCH 1440/1691] Improve blue current integration code (#114004) --- .../components/blue_current/__init__.py | 22 +++--- .../components/blue_current/entity.py | 2 +- .../components/blue_current/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/blue_current/__init__.py | 9 +-- .../blue_current/test_config_flow.py | 71 +++++++++++-------- tests/components/blue_current/test_init.py | 26 ++++--- 8 files changed, 79 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/blue_current/__init__.py b/homeassistant/components/blue_current/__init__.py index 55362a7392d..e852dfc8c6e 100644 --- a/homeassistant/components/blue_current/__init__.py +++ b/homeassistant/components/blue_current/__init__.py @@ -111,9 +111,9 @@ class Connector: entry[EVSE_ID], entry[MODEL_TYPE], entry[ATTR_NAME] ) for entry in charge_points_data - ) + ), + self.client.get_grid_status(charge_points_data[0][EVSE_ID]), ) - await self.client.get_grid_status(charge_points_data[0][EVSE_ID]) async def handle_charge_point(self, evse_id: str, model: str, name: str) -> None: """Add the chargepoint and request their data.""" @@ -127,22 +127,26 @@ class Connector: def update_charge_point(self, evse_id: str, data: dict) -> None: """Update the charge point data.""" self.charge_points[evse_id].update(data) - self.dispatch_value_update_signal(evse_id) + self.dispatch_charge_point_update_signal(evse_id) - def dispatch_value_update_signal(self, evse_id: str) -> None: - """Dispatch a value signal.""" - async_dispatcher_send(self.hass, f"{DOMAIN}_value_update_{evse_id}") + def dispatch_charge_point_update_signal(self, evse_id: str) -> None: + """Dispatch a charge point update signal.""" + async_dispatcher_send(self.hass, f"{DOMAIN}_charge_point_update_{evse_id}") def dispatch_grid_update_signal(self) -> None: - """Dispatch a grid signal.""" + """Dispatch a grid update signal.""" async_dispatcher_send(self.hass, f"{DOMAIN}_grid_update") + async def on_open(self) -> None: + """Fetch data when connection is established.""" + await self.client.get_charge_points() + async def run_task(self) -> None: """Start the receive loop.""" try: while True: try: - await self.client.connect(self.on_data) + await self.client.connect(self.on_data, self.on_open) except RequestLimitReached: LOGGER.warning( "Request limit reached. reconnecting at 00:00 (Europe/Amsterdam)" @@ -160,7 +164,7 @@ class Connector: def _on_disconnect(self) -> None: """Dispatch signals to update entity states.""" for evse_id in self.charge_points: - self.dispatch_value_update_signal(evse_id) + self.dispatch_charge_point_update_signal(evse_id) self.dispatch_grid_update_signal() async def _disconnect(self) -> None: diff --git a/homeassistant/components/blue_current/entity.py b/homeassistant/components/blue_current/entity.py index ecbbd8f0851..cae7d420c99 100644 --- a/homeassistant/components/blue_current/entity.py +++ b/homeassistant/components/blue_current/entity.py @@ -53,7 +53,7 @@ class ChargepointEntity(BlueCurrentEntity): def __init__(self, connector: Connector, evse_id: str) -> None: """Initialize the entity.""" - super().__init__(connector, f"{DOMAIN}_value_update_{evse_id}") + super().__init__(connector, f"{DOMAIN}_charge_point_update_{evse_id}") chargepoint_name = connector.charge_points[evse_id][ATTR_NAME] diff --git a/homeassistant/components/blue_current/manifest.json b/homeassistant/components/blue_current/manifest.json index fddd48554e2..4f277e83656 100644 --- a/homeassistant/components/blue_current/manifest.json +++ b/homeassistant/components/blue_current/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/blue_current", "iot_class": "cloud_push", "loggers": ["bluecurrent_api"], - "requirements": ["bluecurrent-api==1.2.2"] + "requirements": ["bluecurrent-api==1.2.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index a33da54e754..109a1ede4db 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -571,7 +571,7 @@ blinkpy==0.22.6 blockchain==1.4.4 # homeassistant.components.blue_current -bluecurrent-api==1.2.2 +bluecurrent-api==1.2.3 # homeassistant.components.bluemaestro bluemaestro-ble==0.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1fe16e9b6e6..d24d56ce1f7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -490,7 +490,7 @@ blebox-uniapi==2.2.2 blinkpy==0.22.6 # homeassistant.components.blue_current -bluecurrent-api==1.2.2 +bluecurrent-api==1.2.3 # homeassistant.components.bluemaestro bluemaestro-ble==0.2.3 diff --git a/tests/components/blue_current/__init__.py b/tests/components/blue_current/__init__.py index b5c15064449..97acff39a62 100644 --- a/tests/components/blue_current/__init__.py +++ b/tests/components/blue_current/__init__.py @@ -42,10 +42,10 @@ def create_client_mock( """Wait until chargepoints are received.""" await received_charge_points.wait() - async def connect(receiver): + async def connect(receiver, on_open): """Set the receiver and await future.""" client_mock.receiver = receiver - await client_mock.get_charge_points() + await on_open() started_loop.set() started_loop.clear() @@ -112,8 +112,9 @@ async def init_integration( hass, future_container, started_loop, charge_point, status, grid ) - with patch("homeassistant.components.blue_current.PLATFORMS", [platform]), patch( - "homeassistant.components.blue_current.Client", return_value=client_mock + with ( + patch("homeassistant.components.blue_current.PLATFORMS", [platform]), + patch("homeassistant.components.blue_current.Client", return_value=client_mock), ): config_entry.add_to_hass(hass) diff --git a/tests/components/blue_current/test_config_flow.py b/tests/components/blue_current/test_config_flow.py index 2e278af4982..b5dad155618 100644 --- a/tests/components/blue_current/test_config_flow.py +++ b/tests/components/blue_current/test_config_flow.py @@ -36,15 +36,19 @@ async def test_user(hass: HomeAssistant) -> None: assert result["errors"] == {} assert result["type"] == FlowResultType.FORM - with patch( - "homeassistant.components.blue_current.config_flow.Client.validate_api_token", - return_value="1234", - ), patch( - "homeassistant.components.blue_current.config_flow.Client.get_email", - return_value="test@email.com", - ), patch( - "homeassistant.components.blue_current.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.blue_current.config_flow.Client.validate_api_token", + return_value="1234", + ), + patch( + "homeassistant.components.blue_current.config_flow.Client.get_email", + return_value="test@email.com", + ), + patch( + "homeassistant.components.blue_current.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -83,15 +87,19 @@ async def test_flow_fails(hass: HomeAssistant, error: Exception, message: str) - assert result["errors"]["base"] == message assert result["type"] == FlowResultType.FORM - with patch( - "homeassistant.components.blue_current.config_flow.Client.validate_api_token", - return_value="1234", - ), patch( - "homeassistant.components.blue_current.config_flow.Client.get_email", - return_value="test@email.com", - ), patch( - "homeassistant.components.blue_current.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.blue_current.config_flow.Client.validate_api_token", + return_value="1234", + ), + patch( + "homeassistant.components.blue_current.config_flow.Client.get_email", + return_value="test@email.com", + ), + patch( + "homeassistant.components.blue_current.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -121,17 +129,22 @@ async def test_reauth( expected_api_token: str, ) -> None: """Test reauth flow.""" - with patch( - "homeassistant.components.blue_current.config_flow.Client.validate_api_token", - return_value=customer_id, - ), patch( - "homeassistant.components.blue_current.config_flow.Client.get_email", - return_value="test@email.com", - ), patch( - "homeassistant.components.blue_current.config_flow.Client.wait_for_charge_points", - ), patch( - "homeassistant.components.blue_current.Client.connect", - lambda self, on_data: hass.loop.create_future(), + with ( + patch( + "homeassistant.components.blue_current.config_flow.Client.validate_api_token", + return_value=customer_id, + ), + patch( + "homeassistant.components.blue_current.config_flow.Client.get_email", + return_value="test@email.com", + ), + patch( + "homeassistant.components.blue_current.config_flow.Client.wait_for_charge_points", + ), + patch( + "homeassistant.components.blue_current.Client.connect", + lambda self, on_data, on_open: hass.loop.create_future(), + ), ): config_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/blue_current/test_init.py b/tests/components/blue_current/test_init.py index 4f570156c82..06cc6b27c26 100644 --- a/tests/components/blue_current/test_init.py +++ b/tests/components/blue_current/test_init.py @@ -29,13 +29,14 @@ async def test_load_unload_entry( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test load and unload entry.""" - with patch( - "homeassistant.components.blue_current.Client.validate_api_token" - ), patch( - "homeassistant.components.blue_current.Client.wait_for_charge_points" - ), patch("homeassistant.components.blue_current.Client.disconnect"), patch( - "homeassistant.components.blue_current.Client.connect", - lambda self, on_data: hass.loop.create_future(), + with ( + patch("homeassistant.components.blue_current.Client.validate_api_token"), + patch("homeassistant.components.blue_current.Client.wait_for_charge_points"), + patch("homeassistant.components.blue_current.Client.disconnect"), + patch( + "homeassistant.components.blue_current.Client.connect", + lambda self, on_data, on_open: hass.loop.create_future(), + ), ): config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) @@ -61,10 +62,13 @@ async def test_config_exceptions( config_error: IntegrationError, ) -> None: """Test if the correct config error is raised when connecting to the api fails.""" - with patch( - "homeassistant.components.blue_current.Client.validate_api_token", - side_effect=api_error, - ), pytest.raises(config_error): + with ( + patch( + "homeassistant.components.blue_current.Client.validate_api_token", + side_effect=api_error, + ), + pytest.raises(config_error), + ): config_entry.add_to_hass(hass) await async_setup_entry(hass, config_entry) From 579084a21e0e58fda66d44ed5e4ef0409f2dd777 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 24 Mar 2024 11:04:12 +0000 Subject: [PATCH 1441/1691] Add more sensors to aurora_abb_powerone (part 2) (#114097) --- .../aurora_abb_powerone/coordinator.py | 6 +++++ .../components/aurora_abb_powerone/icons.json | 12 +++++++++ .../components/aurora_abb_powerone/sensor.py | 26 +++++++++++++++++++ .../aurora_abb_powerone/strings.json | 9 +++++++ .../aurora_abb_powerone/test_sensor.py | 6 +++++ 5 files changed, 59 insertions(+) create mode 100644 homeassistant/components/aurora_abb_powerone/icons.json diff --git a/homeassistant/components/aurora_abb_powerone/coordinator.py b/homeassistant/components/aurora_abb_powerone/coordinator.py index 0b4cea0f8b4..d6e9b241b86 100644 --- a/homeassistant/components/aurora_abb_powerone/coordinator.py +++ b/homeassistant/components/aurora_abb_powerone/coordinator.py @@ -41,7 +41,10 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): grid_current = self.client.measure(2, True) power_watts = self.client.measure(3, True) frequency = self.client.measure(4) + i_leak_dcdc = self.client.measure(6) + i_leak_inverter = self.client.measure(7) temperature_c = self.client.measure(21) + r_iso = self.client.measure(30) energy_wh = self.client.cumulated_energy(5) [alarm, *_] = self.client.alarms() except AuroraTimeoutError: @@ -64,7 +67,10 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]): data["grid_current"] = round(grid_current, 1) data["instantaneouspower"] = round(power_watts, 1) data["grid_frequency"] = round(frequency, 1) + data["i_leak_dcdc"] = i_leak_dcdc + data["i_leak_inverter"] = i_leak_inverter data["temp"] = round(temperature_c, 1) + data["r_iso"] = r_iso data["totalenergy"] = round(energy_wh / 1000, 2) data["alarm"] = alarm self.available = True diff --git a/homeassistant/components/aurora_abb_powerone/icons.json b/homeassistant/components/aurora_abb_powerone/icons.json new file mode 100644 index 00000000000..3a097df67ce --- /dev/null +++ b/homeassistant/components/aurora_abb_powerone/icons.json @@ -0,0 +1,12 @@ +{ + "entity": { + "sensor": { + "r_iso": { + "default": "mdi:omega" + }, + "alarm": { + "default": "mdi:alert-octagon" + } + } + } +} diff --git a/homeassistant/components/aurora_abb_powerone/sensor.py b/homeassistant/components/aurora_abb_powerone/sensor.py index 49d56eb6b24..6e3ebb5f5c9 100644 --- a/homeassistant/components/aurora_abb_powerone/sensor.py +++ b/homeassistant/components/aurora_abb_powerone/sensor.py @@ -71,6 +71,24 @@ SENSOR_TYPES = [ state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), + SensorEntityDescription( + key="i_leak_dcdc", + device_class=SensorDeviceClass.CURRENT, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + state_class=SensorStateClass.MEASUREMENT, + translation_key="i_leak_dcdc", + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="i_leak_inverter", + device_class=SensorDeviceClass.CURRENT, + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + state_class=SensorStateClass.MEASUREMENT, + translation_key="i_leak_inverter", + entity_registry_enabled_default=False, + ), SensorEntityDescription( key="alarm", device_class=SensorDeviceClass.ENUM, @@ -92,6 +110,14 @@ SENSOR_TYPES = [ native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, ), + SensorEntityDescription( + key="r_iso", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement="MOhms", + state_class=SensorStateClass.MEASUREMENT, + translation_key="r_iso", + entity_registry_enabled_default=False, + ), SensorEntityDescription( key="totalenergy", device_class=SensorDeviceClass.ENERGY, diff --git a/homeassistant/components/aurora_abb_powerone/strings.json b/homeassistant/components/aurora_abb_powerone/strings.json index 551c6dea3ec..319bcb0adc4 100644 --- a/homeassistant/components/aurora_abb_powerone/strings.json +++ b/homeassistant/components/aurora_abb_powerone/strings.json @@ -33,8 +33,17 @@ "power_output": { "name": "Power output" }, + "i_leak_dcdc": { + "name": "DC-DC leak current" + }, + "i_leak_inverter": { + "name": "Inverter leak current" + }, "total_energy": { "name": "Total energy" + }, + "r_iso": { + "name": "Isolation resistance" } } } diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index d40f11ef076..9a8b21cdbd3 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -36,7 +36,10 @@ def _simulated_returns(index, global_measure=None): 2: 2.7894, # current 3: 45.678, # power 4: 50.789, # frequency + 6: 1.2345, # leak dcdc + 7: 2.3456, # leak inverter 21: 9.876, # temperature + 30: 0.1234, # Isolation resistance 5: 12345, # energy } return returns[index] @@ -107,6 +110,9 @@ async def test_sensors(hass: HomeAssistant, entity_registry: EntityRegistry) -> ("sensor.mydevicename_grid_voltage", "235.9"), ("sensor.mydevicename_grid_current", "2.8"), ("sensor.mydevicename_frequency", "50.8"), + ("sensor.mydevicename_dc_dc_leak_current", "1.2345"), + ("sensor.mydevicename_inverter_leak_current", "2.3456"), + ("sensor.mydevicename_isolation_resistance", "0.1234"), ] for entity_id, _ in sensors: assert not hass.states.get(entity_id) From 2e2b40f77e4afa6d92c5ea9bc454de8dbecb605e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 24 Mar 2024 12:48:19 +0100 Subject: [PATCH 1442/1691] Add reconfigure flow to Axis integration (#114067) --- homeassistant/components/axis/config_flow.py | 20 +++++++++- tests/components/axis/test_config_flow.py | 39 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 38b5f5f7422..30bc653c202 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -146,6 +146,14 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): title = f"{model} - {serial}" return self.async_create_entry(title=title, data=self.config) + async def async_step_reconfigure( + self, user_input: Mapping[str, Any] | None = None + ) -> ConfigFlowResult: + """Trigger a reconfiguration flow.""" + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + assert entry + return await self._redo_configuration(entry.data, keep_password=True) + async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: @@ -154,14 +162,22 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN): CONF_NAME: entry_data[CONF_NAME], CONF_HOST: entry_data[CONF_HOST], } + return await self._redo_configuration(entry_data, keep_password=False) + async def _redo_configuration( + self, entry_data: Mapping[str, Any], keep_password: bool + ) -> ConfigFlowResult: + """Re-run configuration step.""" self.discovery_schema = { vol.Required( - CONF_PROTOCOL, default=entry_data.get(CONF_PROTOCOL, DEFAULT_PROTOCOL) + CONF_PROTOCOL, default=entry_data.get(CONF_PROTOCOL, "http") ): str, vol.Required(CONF_HOST, default=entry_data[CONF_HOST]): str, vol.Required(CONF_USERNAME, default=entry_data[CONF_USERNAME]): str, - vol.Required(CONF_PASSWORD): str, + vol.Required( + CONF_PASSWORD, + default=entry_data[CONF_PASSWORD] if keep_password else "", + ): str, vol.Required(CONF_PORT, default=entry_data[CONF_PORT]): int, } diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index e0514250a98..a6a0235b118 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -18,6 +18,7 @@ from homeassistant.config_entries import ( SOURCE_DHCP, SOURCE_IGNORE, SOURCE_REAUTH, + SOURCE_RECONFIGURE, SOURCE_SSDP, SOURCE_USER, SOURCE_ZEROCONF, @@ -259,6 +260,44 @@ async def test_reauth_flow_update_configuration( assert mock_config_entry.data[CONF_PASSWORD] == "pass2" +async def test_reconfiguration_flow_update_configuration( + hass: HomeAssistant, mock_config_entry, mock_vapix_requests +) -> None: + """Test that config flow reconfiguration updates configured device.""" + assert mock_config_entry.data[CONF_HOST] == "1.2.3.4" + assert mock_config_entry.data[CONF_USERNAME] == "root" + assert mock_config_entry.data[CONF_PASSWORD] == "pass" + + result = await hass.config_entries.flow.async_init( + AXIS_DOMAIN, + context={ + "source": SOURCE_RECONFIGURE, + "entry_id": mock_config_entry.entry_id, + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + + mock_vapix_requests("2.3.4.5") + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "2.3.4.5", + CONF_USERNAME: "user", + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + assert mock_config_entry.data[CONF_PROTOCOL] == "http" + assert mock_config_entry.data[CONF_HOST] == "2.3.4.5" + assert mock_config_entry.data[CONF_PORT] == 80 + assert mock_config_entry.data[CONF_USERNAME] == "user" + assert mock_config_entry.data[CONF_PASSWORD] == "pass" + + @pytest.mark.parametrize( ("source", "discovery_info"), [ From d4b180cf75f967875cb8a39a28a9f191ee3a99c8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 02:23:12 -1000 Subject: [PATCH 1443/1691] Fix flapping device_tracker test_bad_platform test (#114102) https://github.com/home-assistant/core/actions/runs/8408079861/job/23023835074 --- tests/components/device_tracker/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index e14892c565b..f926b2f8e5f 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -628,6 +628,7 @@ async def test_bad_platform(hass: HomeAssistant) -> None: config = {"device_tracker": [{"platform": "bad_platform"}]} with assert_setup_component(0, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, config) + await hass.async_block_till_done() assert f"bad_platform.{device_tracker.DOMAIN}" not in hass.config.components From db3eeec78ccdf34050170a0f026907fa0e240cfd Mon Sep 17 00:00:00 2001 From: Niels Perfors Date: Sun, 24 Mar 2024 13:28:09 +0100 Subject: [PATCH 1444/1691] Tado: fix 113982 (#114109) fix-113982 --- homeassistant/components/tado/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index f59f15625c1..5ab7a6f67b8 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -1,6 +1,6 @@ """Support for the (unofficial) Tado API.""" -from datetime import timedelta +from datetime import datetime, timedelta import logging from PyTado.interface import Tado @@ -439,7 +439,8 @@ class TadoConnector: def set_meter_reading(self, reading: int) -> dict[str, str]: """Send meter reading to Tado.""" + dt: str = datetime.now().strftime("%Y-%m-%d") try: - return self.tado.set_eiq_meter_readings(reading=reading) + return self.tado.set_eiq_meter_readings(date=dt, reading=reading) except RequestException as exc: raise HomeAssistantError("Could not set meter reading") from exc From 3a3fb95454627ce197c1e5d14f2db076d63e6228 Mon Sep 17 00:00:00 2001 From: Lars Stegman Date: Sun, 24 Mar 2024 15:12:25 +0100 Subject: [PATCH 1445/1691] Fix launch library translation keys (#114093) Fix translation keys --- homeassistant/components/launch_library/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/launch_library/sensor.py b/homeassistant/components/launch_library/sensor.py index 470c2e678cf..66b1d95ba2a 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -68,7 +68,7 @@ SENSOR_DESCRIPTIONS: tuple[LaunchLibrarySensorEntityDescription, ...] = ( LaunchLibrarySensorEntityDescription( key="launch_probability", icon="mdi:dice-multiple", - translation_key="next_launch", + translation_key="launch_probability", native_unit_of_measurement=PERCENTAGE, value_fn=lambda nl: None if nl.probability == -1 else nl.probability, attributes_fn=lambda nl: None, @@ -76,7 +76,7 @@ SENSOR_DESCRIPTIONS: tuple[LaunchLibrarySensorEntityDescription, ...] = ( LaunchLibrarySensorEntityDescription( key="launch_status", icon="mdi:rocket-launch", - translation_key="next_launch", + translation_key="launch_status", value_fn=lambda nl: nl.status.name, attributes_fn=lambda nl: {"reason": nl.holdreason} if nl.inhold else None, ), From 11faf4adcbb0dc48d16e0c1b5916b8a7bc356f9c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 24 Mar 2024 15:20:00 +0100 Subject: [PATCH 1446/1691] Replace if in range check (#114107) --- homeassistant/components/ecobee/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 3f871dd20dd..0df8a42c566 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -717,7 +717,7 @@ class Thermostat(ClimateEntity): def set_humidity(self, humidity: int) -> None: """Set the humidity level.""" - if humidity not in range(101): + if not (0 <= humidity <= 100): raise ValueError( f"Invalid set_humidity value (must be in range 0-100): {humidity}" ) From c5893f22bf044e2f6d147a9152da85e626acdcbb Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 24 Mar 2024 18:37:25 +0100 Subject: [PATCH 1447/1691] Bump xiaomi-ble to 0.28.0 (#114120) --- homeassistant/components/xiaomi_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 84447e211bb..ef0556b6966 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.27.1"] + "requirements": ["xiaomi-ble==0.28.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 109a1ede4db..d1a39ab1bb0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2865,7 +2865,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.27.1 +xiaomi-ble==0.28.0 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d24d56ce1f7..8f78583d453 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2209,7 +2209,7 @@ wyoming==1.5.3 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.27.1 +xiaomi-ble==0.28.0 # homeassistant.components.knx xknx==2.12.2 From 6371b344b939b299099589f374c6695e37d09ac7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 07:38:05 -1000 Subject: [PATCH 1448/1691] Ensure discovery can setup legacy device tracker platforms (#114101) --- .../components/device_tracker/__init__.py | 10 +--- .../components/device_tracker/legacy.py | 53 ++++++++++++++----- tests/components/device_tracker/test_init.py | 28 +++++++++- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index d453c101a52..ca78b1cbdc5 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -69,15 +69,7 @@ def is_on(hass: HomeAssistant, entity_id: str) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the device tracker.""" - # - # Legacy and platforms load in a non-awaited tracked task - # to ensure device tracker setup can continue and config - # entry integrations are not waiting for legacy device - # tracker platforms to be set up. - # - hass.async_create_task( - async_setup_legacy_integration(hass, config), eager_start=True - ) + async_setup_legacy_integration(hass, config) return True diff --git a/homeassistant/components/device_tracker/legacy.py b/homeassistant/components/device_tracker/legacy.py index c3aec89be90..91cf35f43bd 100644 --- a/homeassistant/components/device_tracker/legacy.py +++ b/homeassistant/components/device_tracker/legacy.py @@ -201,9 +201,47 @@ def see( hass.services.call(DOMAIN, SERVICE_SEE, data) -async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> None: +@callback +def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> None: + """Set up the legacy integration.""" + # The tracker is loaded in the _async_setup_integration task so + # we create a future to avoid waiting on it here so that only + # async_platform_discovered will have to wait in the rare event + # a custom component still uses the legacy device tracker discovery. + tracker_future: asyncio.Future[DeviceTracker] = hass.loop.create_future() + + async def async_platform_discovered( + p_type: str, info: dict[str, Any] | None + ) -> None: + """Load a platform.""" + platform = await async_create_platform_type(hass, config, p_type, {}) + + if platform is None or platform.type != PLATFORM_TYPE_LEGACY: + return + + tracker = await tracker_future + await platform.async_setup_legacy(hass, tracker, info) + + discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) + # + # Legacy and platforms load in a non-awaited tracked task + # to ensure device tracker setup can continue and config + # entry integrations are not waiting for legacy device + # tracker platforms to be set up. + # + hass.async_create_task( + _async_setup_integration(hass, config, tracker_future), eager_start=True + ) + + +async def _async_setup_integration( + hass: HomeAssistant, + config: ConfigType, + tracker_future: asyncio.Future[DeviceTracker], +) -> None: """Set up the legacy integration.""" tracker = await get_tracker(hass, config) + tracker_future.set_result(tracker) async def async_see_service(call: ServiceCall) -> None: """Service to see a device.""" @@ -227,19 +265,6 @@ async def async_setup_integration(hass: HomeAssistant, config: ConfigType) -> No if setup_tasks: await asyncio.wait(setup_tasks) - async def async_platform_discovered( - p_type: str, info: dict[str, Any] | None - ) -> None: - """Load a platform.""" - platform = await async_create_platform_type(hass, config, p_type, {}) - - if platform is None or platform.type != PLATFORM_TYPE_LEGACY: - return - - await platform.async_setup_legacy(hass, tracker, info) - - discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered) - # Clean up stale devices cancel_update_stale = async_track_utc_time_change( hass, tracker.async_update_stale, second=range(0, 60, 5) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index f926b2f8e5f..188b581de40 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -235,7 +235,8 @@ async def test_discover_platform( """Test discovery of device_tracker demo platform.""" await async_setup_component(hass, "homeassistant", {}) await async_setup_component(hass, device_tracker.DOMAIN, {}) - await hass.async_block_till_done() + # async_block_till_done is intentionally missing here so we + # can verify async_load_platform still works without it with patch("homeassistant.components.device_tracker.legacy.update_config"): await discovery.async_load_platform( hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}} @@ -251,6 +252,31 @@ async def test_discover_platform( ) +async def test_discover_platform_missing_platform( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test discovery of device_tracker missing platform.""" + await async_setup_component(hass, "homeassistant", {}) + await async_setup_component(hass, device_tracker.DOMAIN, {}) + # async_block_till_done is intentionally missing here so we + # can verify async_load_platform still works without it + with patch("homeassistant.components.device_tracker.legacy.update_config"): + await discovery.async_load_platform( + hass, + device_tracker.DOMAIN, + "its_not_there", + {"test_key": "test_val"}, + {"bla": {}}, + ) + await hass.async_block_till_done() + assert device_tracker.DOMAIN in hass.config.components + assert ( + "Unable to prepare setup for platform 'its_not_there.device_tracker'" + in caplog.text + ) + # This test should not generate an unhandled exception + + async def test_update_stale( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], From 67ab49b825ce988d9ea7d82b5abfdb275ba11c4c Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 24 Mar 2024 18:41:13 +0100 Subject: [PATCH 1449/1691] Fetch ServiceNotFound message from translation cache and fix super (#114084) * Fetch ServiceNotFound message from translation cache and fix super * Fix tests trace component * Fix script errors --- homeassistant/exceptions.py | 8 +------- tests/components/trace/test_websocket_api.py | 9 ++++++--- tests/helpers/test_script.py | 12 ++++++++---- tests/test_core.py | 3 ++- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index 81856f27c45..e6ed111c13f 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -260,17 +260,13 @@ class ServiceNotFound(HomeAssistantError): def __init__(self, domain: str, service: str) -> None: """Initialize error.""" super().__init__( - self, translation_domain="homeassistant", translation_key="service_not_found", translation_placeholders={"domain": domain, "service": service}, ) self.domain = domain self.service = service - - def __str__(self) -> str: - """Return string representation.""" - return f"Service {self.domain}.{self.service} not found." + self.generate_message = True class MaxLengthExceeded(HomeAssistantError): @@ -279,7 +275,6 @@ class MaxLengthExceeded(HomeAssistantError): def __init__(self, value: str, property_name: str, max_length: int) -> None: """Initialize error.""" super().__init__( - self, translation_domain="homeassistant", translation_key="max_length_exceeded", translation_placeholders={ @@ -300,7 +295,6 @@ class DependencyError(HomeAssistantError): def __init__(self, failed_dependencies: list[str]) -> None: """Initialize error.""" super().__init__( - self, f"Could not setup dependencies: {', '.join(failed_dependencies)}", ) self.failed_dependencies = failed_dependencies diff --git a/tests/components/trace/test_websocket_api.py b/tests/components/trace/test_websocket_api.py index 5e584de2b4f..5c5d882b721 100644 --- a/tests/components/trace/test_websocket_api.py +++ b/tests/components/trace/test_websocket_api.py @@ -132,6 +132,7 @@ async def test_get_trace( enable_custom_integrations: None, ) -> None: """Test tracing a script or automation.""" + await async_setup_component(hass, "homeassistant", {}) id = 1 def next_id(): @@ -206,7 +207,7 @@ async def test_get_trace( _assert_raw_config(domain, sun_config, trace) assert trace["blueprint_inputs"] is None assert trace["context"] - assert trace["error"] == "Service test.automation not found." + assert trace["error"] == "Service test.automation not found" assert trace["state"] == "stopped" assert trace["script_execution"] == "error" assert trace["item_id"] == "sun" @@ -808,6 +809,7 @@ async def test_list_traces( script_execution, ) -> None: """Test listing script and automation traces.""" + await async_setup_component(hass, "homeassistant", {}) id = 1 def next_id(): @@ -894,7 +896,7 @@ async def test_list_traces( assert len(_find_traces(response["result"], domain, "sun")) == 1 trace = _find_traces(response["result"], domain, "sun")[0] assert trace["last_step"] == last_step[0].format(prefix=prefix) - assert trace["error"] == "Service test.automation not found." + assert trace["error"] == "Service test.automation not found" assert trace["state"] == "stopped" assert trace["script_execution"] == script_execution[0] assert trace["timestamp"] @@ -1574,6 +1576,7 @@ async def test_trace_blueprint_automation( enable_custom_integrations: None, ) -> None: """Test trace of blueprint automation.""" + await async_setup_component(hass, "homeassistant", {}) id = 1 def next_id(): @@ -1633,7 +1636,7 @@ async def test_trace_blueprint_automation( assert trace["config"]["id"] == "sun" assert trace["blueprint_inputs"] == sun_config assert trace["context"] - assert trace["error"] == "Service test.automation not found." + assert trace["error"] == "Service test.automation not found" assert trace["state"] == "stopped" assert trace["script_execution"] == "error" assert trace["item_id"] == "sun" diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 0e658f35702..95bb3f813e4 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -3536,6 +3536,7 @@ async def test_parallel_error( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test parallel action failure handling.""" + await async_setup_component(hass, "homeassistant", {}) events = async_capture_events(hass, "test_event") sequence = cv.SCRIPT_SCHEMA( { @@ -3552,10 +3553,10 @@ async def test_parallel_error( assert len(events) == 0 expected_trace = { - "0": [{"error": "Service epic.failure not found."}], + "0": [{"error": "Service epic.failure not found"}], "0/parallel/0/sequence/0": [ { - "error": "Service epic.failure not found.", + "error": "Service epic.failure not found", "result": { "params": { "domain": "epic", @@ -3589,6 +3590,7 @@ async def test_last_triggered(hass: HomeAssistant) -> None: async def test_propagate_error_service_not_found(hass: HomeAssistant) -> None: """Test that a script aborts when a service is not found.""" + await async_setup_component(hass, "homeassistant", {}) event = "test_event" events = async_capture_events(hass, event) sequence = cv.SCRIPT_SCHEMA([{"service": "test.script"}, {"event": event}]) @@ -3603,7 +3605,7 @@ async def test_propagate_error_service_not_found(hass: HomeAssistant) -> None: expected_trace = { "0": [ { - "error": "Service test.script not found.", + "error": "Service test.script not found", "result": { "params": { "domain": "test", @@ -5419,6 +5421,7 @@ async def test_continue_on_error_with_stop(hass: HomeAssistant) -> None: async def test_continue_on_error_automation_issue(hass: HomeAssistant) -> None: """Test continue on error doesn't block action automation errors.""" + await async_setup_component(hass, "homeassistant", {}) sequence = cv.SCRIPT_SCHEMA( [ { @@ -5436,7 +5439,7 @@ async def test_continue_on_error_automation_issue(hass: HomeAssistant) -> None: { "0": [ { - "error": "Service service.not_found not found.", + "error": "Service service.not_found not found", "result": { "params": { "domain": "service", @@ -5455,6 +5458,7 @@ async def test_continue_on_error_automation_issue(hass: HomeAssistant) -> None: async def test_continue_on_error_unknown_error(hass: HomeAssistant) -> None: """Test continue on error doesn't block unknown errors from e.g., libraries.""" + await async_setup_component(hass, "homeassistant", {}) class MyLibraryError(Exception): """My custom library error.""" diff --git a/tests/test_core.py b/tests/test_core.py index f95a2104ff5..61852c69e8b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1670,6 +1670,7 @@ async def test_serviceregistry_remove_service(hass: HomeAssistant) -> None: async def test_serviceregistry_service_that_not_exists(hass: HomeAssistant) -> None: """Test remove service that not exists.""" + await async_setup_component(hass, "homeassistant", {}) calls_remove = async_capture_events(hass, EVENT_SERVICE_REMOVED) assert not hass.services.has_service("test_xxx", "test_yyy") hass.services.async_remove("test_xxx", "test_yyy") @@ -1687,7 +1688,7 @@ async def test_serviceregistry_service_that_not_exists(hass: HomeAssistant) -> N assert exc.value.domain == "test_do_not" assert exc.value.service == "exist" - assert str(exc.value) == "Service test_do_not.exist not found." + assert str(exc.value) == "Service test_do_not.exist not found" async def test_serviceregistry_async_service_raise_exception( From d779333bef73f9d0886b78d3a89571bf0467be32 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sun, 24 Mar 2024 18:52:39 +0100 Subject: [PATCH 1450/1691] Use TypeVarTuple for add_job and run_job methods (#114122) --- homeassistant/components/http/ban.py | 3 +- .../components/meteo_france/__init__.py | 1 + homeassistant/core.py | 36 ++++++++++--------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/http/ban.py b/homeassistant/components/http/ban.py index eec951fb907..b4e949514b8 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -116,7 +116,8 @@ async def process_wrong_login(request: Request) -> None: """ hass = request.app[KEY_HASS] - remote_addr = ip_address(request.remote) # type: ignore[arg-type] + assert request.remote + remote_addr = ip_address(request.remote) remote_host = request.remote with suppress(herror): remote_host, _, _ = await hass.async_add_executor_job( diff --git a/homeassistant/components/meteo_france/__init__.py b/homeassistant/components/meteo_france/__init__.py index e43572ade6c..ddba982934c 100644 --- a/homeassistant/components/meteo_france/__init__.py +++ b/homeassistant/components/meteo_france/__init__.py @@ -54,6 +54,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _async_update_data_alert() -> CurrentPhenomenons: """Fetch data from API endpoint.""" + assert isinstance(department, str) return await hass.async_add_executor_job( client.get_warning_current_phenomenoms, department, 0, True ) diff --git a/homeassistant/core.py b/homeassistant/core.py index cdf5cbfe6a8..533bddb6ceb 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -40,6 +40,7 @@ from typing import ( ParamSpec, Self, TypedDict, + TypeVarTuple, cast, overload, ) @@ -135,6 +136,7 @@ _T = TypeVar("_T") _R = TypeVar("_R") _R_co = TypeVar("_R_co", covariant=True) _P = ParamSpec("_P") +_Ts = TypeVarTuple("_Ts") # Internal; not helpers.typing.UNDEFINED due to circular dependency _UNDEF: dict[Any, Any] = {} _CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) @@ -529,7 +531,7 @@ class HomeAssistant: self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) def add_job( - self, target: Callable[..., Any] | Coroutine[Any, Any, Any], *args: Any + self, target: Callable[[*_Ts], Any] | Coroutine[Any, Any, Any], *args: *_Ts ) -> None: """Add a job to be executed by the event loop or by an executor. @@ -547,7 +549,7 @@ class HomeAssistant: ) return if TYPE_CHECKING: - target = cast(Callable[..., Any], target) + target = cast(Callable[[*_Ts], Any], target) self.loop.call_soon_threadsafe( functools.partial( self.async_add_hass_job, HassJob(target), *args, eager_start=True @@ -558,8 +560,8 @@ class HomeAssistant: @callback def async_add_job( self, - target: Callable[..., Coroutine[Any, Any, _R]], - *args: Any, + target: Callable[[*_Ts], Coroutine[Any, Any, _R]], + *args: *_Ts, eager_start: bool = False, ) -> asyncio.Future[_R] | None: ... @@ -568,8 +570,8 @@ class HomeAssistant: @callback def async_add_job( self, - target: Callable[..., Coroutine[Any, Any, _R] | _R], - *args: Any, + target: Callable[[*_Ts], Coroutine[Any, Any, _R] | _R], + *args: *_Ts, eager_start: bool = False, ) -> asyncio.Future[_R] | None: ... @@ -587,8 +589,9 @@ class HomeAssistant: @callback def async_add_job( self, - target: Callable[..., Coroutine[Any, Any, _R] | _R] | Coroutine[Any, Any, _R], - *args: Any, + target: Callable[[*_Ts], Coroutine[Any, Any, _R] | _R] + | Coroutine[Any, Any, _R], + *args: *_Ts, eager_start: bool = False, ) -> asyncio.Future[_R] | None: """Add a job to be executed by the event loop or by an executor. @@ -623,7 +626,7 @@ class HomeAssistant: # the type used for the cast. For history see: # https://github.com/home-assistant/core/pull/71960 if TYPE_CHECKING: - target = cast(Callable[..., Coroutine[Any, Any, _R] | _R], target) + target = cast(Callable[[*_Ts], Coroutine[Any, Any, _R] | _R], target) return self.async_add_hass_job(HassJob(target), *args, eager_start=eager_start) @overload @@ -772,7 +775,7 @@ class HomeAssistant: @callback def async_add_executor_job( - self, target: Callable[..., _T], *args: Any + self, target: Callable[[*_Ts], _T], *args: *_Ts ) -> asyncio.Future[_T]: """Add an executor job from within the event loop.""" task = self.loop.run_in_executor(None, target, *args) @@ -783,7 +786,7 @@ class HomeAssistant: @callback def async_add_import_executor_job( - self, target: Callable[..., _T], *args: Any + self, target: Callable[[*_Ts], _T], *args: *_Ts ) -> asyncio.Future[_T]: """Add an import executor job from within the event loop.""" task = self.loop.run_in_executor(self.import_executor, target, *args) @@ -844,14 +847,14 @@ class HomeAssistant: @overload @callback def async_run_job( - self, target: Callable[..., Coroutine[Any, Any, _R]], *args: Any + self, target: Callable[[*_Ts], Coroutine[Any, Any, _R]], *args: *_Ts ) -> asyncio.Future[_R] | None: ... @overload @callback def async_run_job( - self, target: Callable[..., Coroutine[Any, Any, _R] | _R], *args: Any + self, target: Callable[[*_Ts], Coroutine[Any, Any, _R] | _R], *args: *_Ts ) -> asyncio.Future[_R] | None: ... @@ -865,8 +868,9 @@ class HomeAssistant: @callback def async_run_job( self, - target: Callable[..., Coroutine[Any, Any, _R] | _R] | Coroutine[Any, Any, _R], - *args: Any, + target: Callable[[*_Ts], Coroutine[Any, Any, _R] | _R] + | Coroutine[Any, Any, _R], + *args: *_Ts, ) -> asyncio.Future[_R] | None: """Run a job from within the event loop. @@ -894,7 +898,7 @@ class HomeAssistant: # the type used for the cast. For history see: # https://github.com/home-assistant/core/pull/71960 if TYPE_CHECKING: - target = cast(Callable[..., Coroutine[Any, Any, _R] | _R], target) + target = cast(Callable[[*_Ts], Coroutine[Any, Any, _R] | _R], target) return self.async_run_hass_job(HassJob(target), *args) def block_till_done(self) -> None: From c94d8c541bca45c700676d4ac6c8f5ba7099a4ee Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 24 Mar 2024 19:05:47 +0100 Subject: [PATCH 1451/1691] Fix google assistant diagnostics test (#114111) Improve google assistant diagnostics test --- tests/components/google_assistant/test_diagnostics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/google_assistant/test_diagnostics.py b/tests/components/google_assistant/test_diagnostics.py index 27f741e3e49..26d91ce7920 100644 --- a/tests/components/google_assistant/test_diagnostics.py +++ b/tests/components/google_assistant/test_diagnostics.py @@ -35,16 +35,17 @@ async def test_diagnostics( ) -> None: """Test diagnostics v1.""" + await async_setup_component(hass, "homeassistant", {}) await setup.async_setup_component( hass, switch.DOMAIN, {"switch": [{"platform": "demo"}]} ) - await async_setup_component(hass, "homeassistant", {}) await async_setup_component( hass, ga.DOMAIN, {"google_assistant": DUMMY_CONFIG}, ) + await hass.async_block_till_done() config_entry = hass.config_entries.async_entries("google_assistant")[0] assert await get_diagnostics_for_config_entry( From d0dc820ced8c31728e4dbb7cf3990cf81b774858 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 09:09:24 -1000 Subject: [PATCH 1452/1691] Cancel config entry setup retry at shutdown (#114124) --- homeassistant/config_entries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index dd366c524eb..2c6cb18deef 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -605,6 +605,7 @@ class ConfigEntry: HassJob( functools.partial(self._async_setup_again, hass), job_type=HassJobType.Callback, + cancel_on_shutdown=True, ), ) else: From 17378ce408ab61e589308d5def157c905ea8dc62 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 09:50:32 -1000 Subject: [PATCH 1453/1691] Bump aionut to 4.1.0 (#114125) Handles auth errors after the password has been sent This means we will be able to have some decent error reporting and reauth support in the future changelog: https://github.com/bdraco/aionut/compare/v4.0.0...v4.1.0 --- homeassistant/components/nut/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 6cd073a7476..323fe96ae62 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -7,6 +7,6 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["aionut"], - "requirements": ["aionut==4.0.0"], + "requirements": ["aionut==4.1.0"], "zeroconf": ["_nut._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index d1a39ab1bb0..d8cab210ce4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -315,7 +315,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.nut -aionut==4.0.0 +aionut==4.1.0 # homeassistant.components.oncue aiooncue==0.3.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8f78583d453..3228658ae45 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -288,7 +288,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.nut -aionut==4.0.0 +aionut==4.1.0 # homeassistant.components.oncue aiooncue==0.3.7 From 76052a5385a090681edef728e3ac8e8f7510ef52 Mon Sep 17 00:00:00 2001 From: Yuxin Wang Date: Sun, 24 Mar 2024 18:40:41 -0400 Subject: [PATCH 1454/1691] Implement diagnostics platform for APCUPSD (#112284) * Implement diagnostic platform for APCUPSd * Put APCUPSd in gold quality scale * Revert quality scale update * Use snapshot testing for apcupsd * Prettify the code --- .../components/apcupsd/diagnostics.py | 22 +++++++++ .../apcupsd/snapshots/test_diagnostics.ambr | 46 +++++++++++++++++++ tests/components/apcupsd/test_diagnostics.py | 18 ++++++++ 3 files changed, 86 insertions(+) create mode 100644 homeassistant/components/apcupsd/diagnostics.py create mode 100644 tests/components/apcupsd/snapshots/test_diagnostics.ambr create mode 100644 tests/components/apcupsd/test_diagnostics.py diff --git a/homeassistant/components/apcupsd/diagnostics.py b/homeassistant/components/apcupsd/diagnostics.py new file mode 100644 index 00000000000..60433dfbdf4 --- /dev/null +++ b/homeassistant/components/apcupsd/diagnostics.py @@ -0,0 +1,22 @@ +"""Diagnostics support for APCUPSD.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import APCUPSdCoordinator, APCUPSdData + +TO_REDACT = {"SERIALNO", "HOSTNAME"} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + coordinator: APCUPSdCoordinator = hass.data[DOMAIN][entry.entry_id] + data: APCUPSdData = coordinator.data + return async_redact_data(data, TO_REDACT) diff --git a/tests/components/apcupsd/snapshots/test_diagnostics.ambr b/tests/components/apcupsd/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..a3c4d16da2f --- /dev/null +++ b/tests/components/apcupsd/snapshots/test_diagnostics.ambr @@ -0,0 +1,46 @@ +# serializer version: 1 +# name: test_diagnostics + dict({ + 'ALARMDEL': '30 Seconds', + 'APC': '001,038,0985', + 'BATTDATE': '1970-01-01', + 'BATTV': '13.7 Volts', + 'BCHARGE': '100.0 Percent', + 'CABLE': 'USB Cable', + 'CUMONBATT': '8 Seconds', + 'DATE': '1970-01-01 00:00:00 0000', + 'DRIVER': 'USB UPS Driver', + 'END APC': '1970-01-01 00:00:00 0000', + 'FIRMWARE': '928.a8 .D USB FW:a8', + 'HITRANS': '139.0 Volts', + 'ITEMP': '34.6 C Internal', + 'LASTSTEST': '1970-01-01 00:00:00 0000', + 'LASTXFER': 'Automatic or explicit self test', + 'LINEV': '124.0 Volts', + 'LOADPCT': '14.0 Percent', + 'LOTRANS': '92.0 Volts', + 'MAXTIME': '0 Seconds', + 'MBATTCHG': '5 Percent', + 'MINTIMEL': '3 Minutes', + 'MODEL': 'Back-UPS ES 600', + 'NOMAPNT': '60.0 VA', + 'NOMBATTV': '12.0 Volts', + 'NOMINV': '120 Volts', + 'NOMPOWER': '330 Watts', + 'NUMXFERS': '1', + 'OUTCURNT': '0.88 Amps', + 'SELFTEST': 'NO', + 'SENSE': 'Medium', + 'SERIALNO': '**REDACTED**', + 'STATFLAG': '0x05000008', + 'STATUS': 'ONLINE', + 'STESTI': '7 days', + 'TIMELEFT': '51.0 Minutes', + 'TONBATT': '0 Seconds', + 'UPSMODE': 'Stand Alone', + 'UPSNAME': 'MyUPS', + 'VERSION': '3.14.14 (31 May 2016) unknown', + 'XOFFBATT': '1970-01-01 00:00:00 0000', + 'XONBATT': '1970-01-01 00:00:00 0000', + }) +# --- diff --git a/tests/components/apcupsd/test_diagnostics.py b/tests/components/apcupsd/test_diagnostics.py new file mode 100644 index 00000000000..56249a7823b --- /dev/null +++ b/tests/components/apcupsd/test_diagnostics.py @@ -0,0 +1,18 @@ +"""Test APCUPSd diagnostics reporting abilities.""" +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from tests.components.apcupsd import async_init_integration +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics.""" + entry = await async_init_integration(hass) + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot From 04b3049a7b2b946213a78fc0f0ba68a6b0ad8c84 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 12:57:58 -1000 Subject: [PATCH 1455/1691] Bump aionut to 4.2.1 (#114132) --- homeassistant/components/nut/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 323fe96ae62..5e222b8f2ff 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -7,6 +7,6 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["aionut"], - "requirements": ["aionut==4.1.0"], + "requirements": ["aionut==4.2.1"], "zeroconf": ["_nut._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index d8cab210ce4..5cc4bbcdffd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -315,7 +315,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.nut -aionut==4.1.0 +aionut==4.2.1 # homeassistant.components.oncue aiooncue==0.3.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3228658ae45..4be78247526 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -288,7 +288,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.nut -aionut==4.1.0 +aionut==4.2.1 # homeassistant.components.oncue aiooncue==0.3.7 From 517826fe484f89975cde9c60e96860d801fbd208 Mon Sep 17 00:00:00 2001 From: Matrix Date: Mon, 25 Mar 2024 09:37:39 +0800 Subject: [PATCH 1456/1691] Bump yolink-api to 0.4.1 fix water depth sensor error when device is not initialize (#113994) --- homeassistant/components/yolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yolink/manifest.json b/homeassistant/components/yolink/manifest.json index fcdab39cb8c..8b3b071161c 100644 --- a/homeassistant/components/yolink/manifest.json +++ b/homeassistant/components/yolink/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["auth", "application_credentials"], "documentation": "https://www.home-assistant.io/integrations/yolink", "iot_class": "cloud_push", - "requirements": ["yolink-api==0.3.9"] + "requirements": ["yolink-api==0.4.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5cc4bbcdffd..ca37a6bea60 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2901,7 +2901,7 @@ yeelight==0.7.14 yeelightsunflower==0.0.10 # homeassistant.components.yolink -yolink-api==0.3.9 +yolink-api==0.4.1 # homeassistant.components.youless youless-api==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4be78247526..9da23f3aa81 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2239,7 +2239,7 @@ yalexs==2.0.0 yeelight==0.7.14 # homeassistant.components.yolink -yolink-api==0.3.9 +yolink-api==0.4.1 # homeassistant.components.youless youless-api==1.0.1 From e8fe3d349b72f838128a2157870d866079a3cc8f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 15:55:13 -1000 Subject: [PATCH 1457/1691] Bump aionut to 4.3.2 (#114142) --- homeassistant/components/nut/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nut/manifest.json b/homeassistant/components/nut/manifest.json index 5e222b8f2ff..1f649a32d7f 100644 --- a/homeassistant/components/nut/manifest.json +++ b/homeassistant/components/nut/manifest.json @@ -7,6 +7,6 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["aionut"], - "requirements": ["aionut==4.2.1"], + "requirements": ["aionut==4.3.2"], "zeroconf": ["_nut._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index ca37a6bea60..c96935e0e23 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -315,7 +315,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.nut -aionut==4.2.1 +aionut==4.3.2 # homeassistant.components.oncue aiooncue==0.3.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9da23f3aa81..5d5824c7f48 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -288,7 +288,7 @@ aionanoleaf==0.2.1 aionotion==2024.03.0 # homeassistant.components.nut -aionut==4.2.1 +aionut==4.3.2 # homeassistant.components.oncue aiooncue==0.3.7 From a5128c214879d0f1b96ed0cf2985ad4f37ec955d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 24 Mar 2024 15:55:40 -1000 Subject: [PATCH 1458/1691] Covert system_log services to be callbacks (#114143) --- .../components/system_log/__init__.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 47351b196ba..77f0b095a30 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -310,23 +310,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: websocket_api.async_register_command(hass, list_errors) - async def async_service_handler(service: ServiceCall) -> None: - """Handle logger services.""" - if service.service == "clear": - handler.records.clear() - return - if service.service == "write": - logger = logging.getLogger( - service.data.get(CONF_LOGGER, f"{__name__}.external") - ) - level = service.data[CONF_LEVEL] - getattr(logger, level)(service.data[CONF_MESSAGE]) + @callback + def _async_clear_service_handler(service: ServiceCall) -> None: + handler.records.clear() + + @callback + def _async_write_service_handler(service: ServiceCall) -> None: + name = service.data.get(CONF_LOGGER, f"{__name__}.external") + logger = logging.getLogger(name) + level = service.data[CONF_LEVEL] + getattr(logger, level)(service.data[CONF_MESSAGE]) hass.services.async_register( - DOMAIN, SERVICE_CLEAR, async_service_handler, schema=SERVICE_CLEAR_SCHEMA + DOMAIN, SERVICE_CLEAR, _async_clear_service_handler, schema=SERVICE_CLEAR_SCHEMA ) hass.services.async_register( - DOMAIN, SERVICE_WRITE, async_service_handler, schema=SERVICE_WRITE_SCHEMA + DOMAIN, SERVICE_WRITE, _async_write_service_handler, schema=SERVICE_WRITE_SCHEMA ) return True From 3e01085c912817f7e003479816c02c64a958467d Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Sun, 24 Mar 2024 23:30:52 -0400 Subject: [PATCH 1459/1691] Add repair for UniFi Protect if RTSP is disabled on camera (#114088) Co-authored-by: J. Nick Koston --- .../components/unifiprotect/camera.py | 55 +++++- .../components/unifiprotect/repairs.py | 125 ++++++++++++- .../components/unifiprotect/strings.json | 30 +++ .../components/unifiprotect/utils.py | 12 ++ tests/components/unifiprotect/test_camera.py | 26 +-- .../unifiprotect/test_media_source.py | 3 +- tests/components/unifiprotect/test_repairs.py | 171 +++++++++++++++++- tests/components/unifiprotect/test_views.py | 2 +- 8 files changed, 391 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index 781653d4ca4..1e99bdff541 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -18,8 +18,10 @@ from pyunifiprotect.data import ( from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.issue_registry import IssueSeverity from .const import ( ATTR_BITRATE, @@ -33,12 +35,40 @@ from .const import ( ) from .data import ProtectData from .entity import ProtectDeviceEntity -from .utils import async_dispatch_id as _ufpd +from .utils import async_dispatch_id as _ufpd, get_camera_base_name _LOGGER = logging.getLogger(__name__) -def get_camera_channels( +@callback +def _create_rtsp_repair( + hass: HomeAssistant, entry: ConfigEntry, data: ProtectData, camera: UFPCamera +) -> None: + edit_key = "readonly" + if camera.can_write(data.api.bootstrap.auth_user): + edit_key = "writable" + + translation_key = f"rtsp_disabled_{edit_key}" + issue_key = f"rtsp_disabled_{camera.id}" + + ir.async_create_issue( + hass, + DOMAIN, + issue_key, + is_fixable=True, + is_persistent=False, + learn_more_url="https://www.home-assistant.io/integrations/unifiprotect/#camera-streams", + severity=IssueSeverity.WARNING, + translation_key=translation_key, + translation_placeholders={"camera": camera.display_name}, + data={"entry_id": entry.entry_id, "camera_id": camera.id}, + ) + + +@callback +def _get_camera_channels( + hass: HomeAssistant, + entry: ConfigEntry, data: ProtectData, ufp_device: UFPCamera | None = None, ) -> Generator[tuple[UFPCamera, CameraChannel, bool], None, None]: @@ -70,15 +100,23 @@ def get_camera_channels( # no RTSP enabled use first channel with no stream if is_default: + _create_rtsp_repair(hass, entry, data, camera) yield camera, camera.channels[0], True + else: + ir.async_delete_issue(hass, DOMAIN, f"rtsp_disabled_{camera.id}") def _async_camera_entities( - data: ProtectData, ufp_device: UFPCamera | None = None + hass: HomeAssistant, + entry: ConfigEntry, + data: ProtectData, + ufp_device: UFPCamera | None = None, ) -> list[ProtectDeviceEntity]: disable_stream = data.disable_stream entities: list[ProtectDeviceEntity] = [] - for camera, channel, is_default in get_camera_channels(data, ufp_device): + for camera, channel, is_default in _get_camera_channels( + hass, entry, data, ufp_device + ): # do not enable streaming for package camera # 2 FPS causes a lot of buferring entities.append( @@ -119,7 +157,7 @@ async def async_setup_entry( if not isinstance(device, UFPCamera): return # type: ignore[unreachable] - entities = _async_camera_entities(data, ufp_device=device) + entities = _async_camera_entities(hass, entry, data, ufp_device=device) async_add_entities(entities) entry.async_on_unload( @@ -129,7 +167,7 @@ async def async_setup_entry( async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_CHANNELS), _add_new_device) ) - entities = _async_camera_entities(data) + entities = _async_camera_entities(hass, entry, data) async_add_entities(entities) @@ -155,12 +193,13 @@ class ProtectCamera(ProtectDeviceEntity, Camera): super().__init__(data, camera) device = self.device + camera_name = get_camera_base_name(channel) if self._secure: self._attr_unique_id = f"{device.mac}_{channel.id}" - self._attr_name = f"{device.display_name} {channel.name}" + self._attr_name = f"{device.display_name} {camera_name}" else: self._attr_unique_id = f"{device.mac}_{channel.id}_insecure" - self._attr_name = f"{device.display_name} {channel.name} Insecure" + self._attr_name = f"{device.display_name} {camera_name} (Insecure)" # only the default (first) channel is enabled by default self._attr_entity_registry_enabled_default = is_default and secure diff --git a/homeassistant/components/unifiprotect/repairs.py b/homeassistant/components/unifiprotect/repairs.py index 254984da515..ddd5dc087a1 100644 --- a/homeassistant/components/unifiprotect/repairs.py +++ b/homeassistant/components/unifiprotect/repairs.py @@ -2,10 +2,10 @@ from __future__ import annotations -import logging from typing import cast from pyunifiprotect import ProtectApiClient +from pyunifiprotect.data import Bootstrap, Camera, ModelType from pyunifiprotect.data.types import FirmwareReleaseChannel import voluptuous as vol @@ -18,8 +18,6 @@ from homeassistant.helpers.issue_registry import async_get as async_get_issue_re from .const import CONF_ALLOW_EA from .utils import async_create_api_client -_LOGGER = logging.getLogger(__name__) - class ProtectRepair(RepairsFlow): """Handler for an issue fixing flow.""" @@ -27,7 +25,7 @@ class ProtectRepair(RepairsFlow): _api: ProtectApiClient _entry: ConfigEntry - def __init__(self, api: ProtectApiClient, entry: ConfigEntry) -> None: + def __init__(self, *, api: ProtectApiClient, entry: ConfigEntry) -> None: """Create flow.""" self._api = api @@ -46,7 +44,7 @@ class ProtectRepair(RepairsFlow): return description_placeholders -class EAConfirm(ProtectRepair): +class EAConfirmRepair(ProtectRepair): """Handler for an issue fixing flow.""" async def async_step_init( @@ -92,7 +90,7 @@ class EAConfirm(ProtectRepair): ) -class CloudAccount(ProtectRepair): +class CloudAccountRepair(ProtectRepair): """Handler for an issue fixing flow.""" async def async_step_init( @@ -119,6 +117,108 @@ class CloudAccount(ProtectRepair): return self.async_create_entry(data={}) +class RTSPRepair(ProtectRepair): + """Handler for an issue fixing flow.""" + + _camera_id: str + _camera: Camera | None + _bootstrap: Bootstrap | None + + def __init__( + self, + *, + api: ProtectApiClient, + entry: ConfigEntry, + camera_id: str, + ) -> None: + """Create flow.""" + + super().__init__(api=api, entry=entry) + self._camera_id = camera_id + self._bootstrap = None + self._camera = None + + @callback + def _async_get_placeholders(self) -> dict[str, str]: + description_placeholders = super()._async_get_placeholders() + if self._camera is not None: + description_placeholders["camera"] = self._camera.display_name + + return description_placeholders + + async def _get_boostrap(self) -> Bootstrap: + if self._bootstrap is None: + self._bootstrap = await self._api.get_bootstrap() + + return self._bootstrap + + async def _get_camera(self) -> Camera: + if self._camera is None: + bootstrap = await self._get_boostrap() + self._camera = bootstrap.cameras.get(self._camera_id) + assert self._camera is not None + return self._camera + + async def _enable_rtsp(self) -> None: + camera = await self._get_camera() + bootstrap = await self._get_boostrap() + user = bootstrap.users.get(bootstrap.auth_user_id) + if not user or not camera.can_write(user): + return + + channel = camera.channels[0] + channel.is_rtsp_enabled = True + await self._api.update_device( + ModelType.CAMERA, camera.id, {"channels": camera.unifi_dict()["channels"]} + ) + + async def async_step_init( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the first step of a fix flow.""" + + return await self.async_step_start() + + async def async_step_start( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the first step of a fix flow.""" + + if user_input is None: + # make sure camera object is loaded for placeholders + await self._get_camera() + placeholders = self._async_get_placeholders() + return self.async_show_form( + step_id="start", + data_schema=vol.Schema({}), + description_placeholders=placeholders, + ) + + updated_camera = await self._api.get_camera(self._camera_id) + if not any(c.is_rtsp_enabled for c in updated_camera.channels): + await self._enable_rtsp() + + updated_camera = await self._api.get_camera(self._camera_id) + if any(c.is_rtsp_enabled for c in updated_camera.channels): + await self.hass.config_entries.async_reload(self._entry.entry_id) + return self.async_create_entry(data={}) + return await self.async_step_confirm() + + async def async_step_confirm( + self, user_input: dict[str, str] | None = None + ) -> data_entry_flow.FlowResult: + """Handle the confirm step of a fix flow.""" + if user_input is not None: + return self.async_create_entry(data={}) + + placeholders = self._async_get_placeholders() + return self.async_show_form( + step_id="confirm", + data_schema=vol.Schema({}), + description_placeholders=placeholders, + ) + + async def async_create_fix_flow( hass: HomeAssistant, issue_id: str, @@ -129,10 +229,19 @@ async def async_create_fix_flow( entry_id = cast(str, data["entry_id"]) if (entry := hass.config_entries.async_get_entry(entry_id)) is not None: api = async_create_api_client(hass, entry) - return EAConfirm(api, entry) + return EAConfirmRepair(api=api, entry=entry) + elif data is not None and issue_id == "cloud_user": entry_id = cast(str, data["entry_id"]) if (entry := hass.config_entries.async_get_entry(entry_id)) is not None: api = async_create_api_client(hass, entry) - return CloudAccount(api, entry) + return CloudAccountRepair(api=api, entry=entry) + + elif data is not None and issue_id.startswith("rtsp_disabled_"): + entry_id = cast(str, data["entry_id"]) + camera_id = cast(str, data["camera_id"]) + if (entry := hass.config_entries.async_get_entry(entry_id)) is not None: + api = async_create_api_client(hass, entry) + return RTSPRepair(api=api, entry=entry, camera_id=camera_id) + return ConfirmRepairFlow() diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index 0b01c8f220c..b83d514f836 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -91,6 +91,36 @@ } } }, + "rtsp_disabled_readonly": { + "title": "RTSPS is disabled on camera {camera}", + "fix_flow": { + "step": { + "start": { + "title": "RTSPS is disabled on camera {camera}", + "description": "RTSPS is disabled on the camera {camera}. RTSPS is required to be able to live stream your camera within Home Assistant. If you do not enable RTSPS, it may create an additional load on your UniFi Protect NVR, as any live video players will default to rapidly pulling snapshots from the camera.\n\nPlease [enable RTSPS]({learn_more}) on the camera and then come back and confirm this repair." + }, + "confirm": { + "title": "[%key:component::unifiprotect::issues::rtsp_disabled_readonly::fix_flow::step::start::title%]", + "description": "Are you sure you want to leave RTSPS disabled for {camera}?" + } + } + } + }, + "rtsp_disabled_writable": { + "title": "RTSPS is disabled on camera {camera}", + "fix_flow": { + "step": { + "start": { + "title": "[%key:component::unifiprotect::issues::rtsp_disabled_readonly::fix_flow::step::start::title%]", + "description": "RTSPS is disabled on the camera {camera}. RTSPS is required to live stream your camera within Home Assistant. If you do not enable RTSPS, it may create an additional load on your UniFi Protect NVR as any live video players will default to rapidly pulling snapshots from the camera.\n\nYou may manually [enable RTSPS]({learn_more}) on your selected camera quality channel or Home Assistant can automatically enable the highest quality channel for you. Confirm this repair once you have enabled the RTSPS channel or if you want Home Assistant to enable the highest quality automatically." + }, + "confirm": { + "title": "[%key:component::unifiprotect::issues::rtsp_disabled_readonly::fix_flow::step::start::title%]", + "description": "[%key:component::unifiprotect::issues::rtsp_disabled_readonly::fix_flow::step::confirm::description%]" + } + } + } + }, "deprecate_hdr_switch": { "title": "HDR Mode Switch Deprecated", "description": "UniFi Protect v3 added a new state for HDR (auto). As a result, the HDR Mode Switch has been replaced with an HDR Mode Select, and it is deprecated.\n\nBelow are the detected automations or scripts that use one or more of the deprecated entities:\n{items}\nThe above list may be incomplete and it does not include any template usages inside of dashboards. Please update any templates, automations or scripts accordingly." diff --git a/homeassistant/components/unifiprotect/utils.py b/homeassistant/components/unifiprotect/utils.py index 00f9f9c0cd1..58474e6a531 100644 --- a/homeassistant/components/unifiprotect/utils.py +++ b/homeassistant/components/unifiprotect/utils.py @@ -13,6 +13,7 @@ from aiohttp import CookieJar from pyunifiprotect import ProtectApiClient from pyunifiprotect.data import ( Bootstrap, + CameraChannel, Light, LightModeEnableType, LightModeType, @@ -146,3 +147,14 @@ def async_create_api_client( ignore_unadopted=False, cache_dir=Path(hass.config.path(STORAGE_DIR, "unifiprotect_cache")), ) + + +@callback +def get_camera_base_name(channel: CameraChannel) -> str: + """Get base name for cameras channel.""" + + camera_name = channel.name + if channel.name != "Package Camera": + camera_name = f"{channel.name} Resolution Channel" + + return camera_name diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index 7b777d711cf..d374f61c2b0 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -21,6 +21,7 @@ from homeassistant.components.unifiprotect.const import ( DEFAULT_ATTRIBUTION, DEFAULT_SCAN_INTERVAL, ) +from homeassistant.components.unifiprotect.utils import get_camera_base_name from homeassistant.const import ( ATTR_ATTRIBUTION, ATTR_ENTITY_ID, @@ -51,7 +52,8 @@ def validate_default_camera_entity( channel = camera_obj.channels[channel_id] - entity_name = f"{camera_obj.name} {channel.name}" + camera_name = get_camera_base_name(channel) + entity_name = f"{camera_obj.name} {camera_name}" unique_id = f"{camera_obj.mac}_{channel.id}" entity_id = f"camera.{entity_name.replace(' ', '_').lower()}" @@ -73,7 +75,7 @@ def validate_rtsps_camera_entity( channel = camera_obj.channels[channel_id] - entity_name = f"{camera_obj.name} {channel.name}" + entity_name = f"{camera_obj.name} {channel.name} Resolution Channel" unique_id = f"{camera_obj.mac}_{channel.id}" entity_id = f"camera.{entity_name.replace(' ', '_').lower()}" @@ -95,9 +97,9 @@ def validate_rtsp_camera_entity( channel = camera_obj.channels[channel_id] - entity_name = f"{camera_obj.name} {channel.name} Insecure" + entity_name = f"{camera_obj.name} {channel.name} Resolution Channel (Insecure)" unique_id = f"{camera_obj.mac}_{channel.id}_insecure" - entity_id = f"camera.{entity_name.replace(' ', '_').lower()}" + entity_id = f"camera.{entity_name.replace(' ', '_').replace('(', '').replace(')', '').lower()}" entity_registry = er.async_get(hass) entity = entity_registry.async_get(entity_id) @@ -314,7 +316,7 @@ async def test_camera_image( ufp.api.get_camera_snapshot = AsyncMock() - await async_get_image(hass, "camera.test_camera_high") + await async_get_image(hass, "camera.test_camera_high_resolution_channel") ufp.api.get_camera_snapshot.assert_called_once() @@ -339,7 +341,7 @@ async def test_camera_generic_update( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" assert await async_setup_component(hass, "homeassistant", {}) @@ -365,7 +367,7 @@ async def test_camera_interval_update( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" state = hass.states.get(entity_id) assert state and state.state == "idle" @@ -388,7 +390,7 @@ async def test_camera_bad_interval_update( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" state = hass.states.get(entity_id) assert state and state.state == "idle" @@ -415,7 +417,7 @@ async def test_camera_ws_update( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" state = hass.states.get(entity_id) assert state and state.state == "idle" @@ -450,7 +452,7 @@ async def test_camera_ws_update_offline( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" state = hass.states.get(entity_id) assert state and state.state == "idle" @@ -492,7 +494,7 @@ async def test_camera_enable_motion( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" camera.__fields__["set_motion_detection"] = Mock(final=False) camera.set_motion_detection = AsyncMock() @@ -514,7 +516,7 @@ async def test_camera_disable_motion( await init_entry(hass, ufp, [camera]) assert_entity_counts(hass, Platform.CAMERA, 2, 1) - entity_id = "camera.test_camera_high" + entity_id = "camera.test_camera_high_resolution_channel" camera.__fields__["set_motion_detection"] = Mock(final=False) camera.set_motion_detection = AsyncMock() diff --git a/tests/components/unifiprotect/test_media_source.py b/tests/components/unifiprotect/test_media_source.py index c79a46daafd..e767909d47e 100644 --- a/tests/components/unifiprotect/test_media_source.py +++ b/tests/components/unifiprotect/test_media_source.py @@ -362,7 +362,8 @@ async def test_browse_media_camera( entity_registry = er.async_get(hass) entity_registry.async_update_entity( - "camera.test_camera_high", disabled_by=er.RegistryEntryDisabler("user") + "camera.test_camera_high_resolution_channel", + disabled_by=er.RegistryEntryDisabler("user"), ) await hass.async_block_till_done() diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index 6ec0b3fe6ca..f4be3164fd5 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -2,11 +2,11 @@ from __future__ import annotations -from copy import copy +from copy import copy, deepcopy from http import HTTPStatus -from unittest.mock import Mock +from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import CloudAccount, Version +from pyunifiprotect.data import Camera, CloudAccount, ModelType, Version from homeassistant.components.repairs.issue_handler import ( async_process_repairs_platforms, @@ -192,3 +192,168 @@ async def test_cloud_user_fix( assert data["type"] == "create_entry" await hass.async_block_till_done() assert any(ufp.entry.async_get_active_flows(hass, {SOURCE_REAUTH})) + + +async def test_rtsp_read_only_ignore( + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test RTSP disabled warning if camera is read-only and it is ignored.""" + + for channel in doorbell.channels: + channel.is_rtsp_enabled = False + for user in ufp.api.bootstrap.users.values(): + user.all_permissions = [] + + ufp.api.get_camera = AsyncMock(return_value=doorbell) + + await init_entry(hass, ufp, [doorbell]) + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + client = await hass_client() + + issue_id = f"rtsp_disabled_{doorbell.id}" + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == issue_id: + issue = i + assert issue is not None + + url = RepairsFlowIndexView.url + resp = await client.post(url, json={"handler": DOMAIN, "issue_id": issue_id}) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["step_id"] == "start" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await client.post(url) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["step_id"] == "confirm" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await client.post(url) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + assert data["type"] == "create_entry" + + +async def test_rtsp_read_only_fix( + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test RTSP disabled warning if camera is read-only and it is fixed.""" + + for channel in doorbell.channels: + channel.is_rtsp_enabled = False + for user in ufp.api.bootstrap.users.values(): + user.all_permissions = [] + + await init_entry(hass, ufp, [doorbell]) + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + client = await hass_client() + + new_doorbell = deepcopy(doorbell) + new_doorbell.channels[1].is_rtsp_enabled = True + ufp.api.get_camera = AsyncMock(return_value=new_doorbell) + issue_id = f"rtsp_disabled_{doorbell.id}" + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == issue_id: + issue = i + assert issue is not None + + url = RepairsFlowIndexView.url + resp = await client.post(url, json={"handler": DOMAIN, "issue_id": issue_id}) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["step_id"] == "start" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await client.post(url) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + assert data["type"] == "create_entry" + + +async def test_rtsp_writable_fix( + hass: HomeAssistant, + ufp: MockUFPFixture, + doorbell: Camera, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test RTSP disabled warning if camera is writable and it is ignored.""" + + for channel in doorbell.channels: + channel.is_rtsp_enabled = False + + await init_entry(hass, ufp, [doorbell]) + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + client = await hass_client() + + new_doorbell = deepcopy(doorbell) + new_doorbell.channels[0].is_rtsp_enabled = True + ufp.api.get_camera = AsyncMock(side_effect=[doorbell, new_doorbell]) + ufp.api.update_device = AsyncMock() + issue_id = f"rtsp_disabled_{doorbell.id}" + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == issue_id: + issue = i + assert issue is not None + + url = RepairsFlowIndexView.url + resp = await client.post(url, json={"handler": DOMAIN, "issue_id": issue_id}) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["step_id"] == "start" + + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await client.post(url) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + assert data["type"] == "create_entry" + + channels = doorbell.unifi_dict()["channels"] + channels[0]["isRtspEnabled"] = True + ufp.api.update_device.assert_called_with( + ModelType.CAMERA, doorbell.id, {"channels": channels} + ) diff --git a/tests/components/unifiprotect/test_views.py b/tests/components/unifiprotect/test_views.py index fb24b399124..f7930e5ff9a 100644 --- a/tests/components/unifiprotect/test_views.py +++ b/tests/components/unifiprotect/test_views.py @@ -483,7 +483,7 @@ async def test_video_entity_id( ) url = async_generate_event_video_url(event) - url = url.replace(camera.id, "camera.test_camera_high") + url = url.replace(camera.id, "camera.test_camera_high_resolution_channel") http_client = await hass_client() response = cast(ClientResponse, await http_client.get(url)) From aa1179ccc459abd4a60852aef72c76bcc6748dd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:55:35 +0100 Subject: [PATCH 1460/1691] Bump github/codeql-action from 3.24.8 to 3.24.9 (#114151) --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c9d97aaadcb..aa9822e0131 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@v4.1.2 - name: Initialize CodeQL - uses: github/codeql-action/init@v3.24.8 + uses: github/codeql-action/init@v3.24.9 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.24.8 + uses: github/codeql-action/analyze@v3.24.9 with: category: "/language:python" From 5c0888ba28c27b97d4c8c72447b4fa6781579137 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:56:33 +0100 Subject: [PATCH 1461/1691] Bump Wandalen/wretry.action from 2.0.0 to 2.1.0 (#114150) --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a334127dfcb..6ff76d50c7f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1070,7 +1070,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov (full coverage) if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v2.0.0 + uses: Wandalen/wretry.action@v2.1.0 with: action: codecov/codecov-action@v3.1.3 with: | @@ -1081,7 +1081,7 @@ jobs: attempt_delay: 30000 - name: Upload coverage to Codecov (partial coverage) if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v2.0.0 + uses: Wandalen/wretry.action@v2.1.0 with: action: codecov/codecov-action@v3.1.3 with: | From 8f0f8f94698a0d7fa903f15cac7952546f600371 Mon Sep 17 00:00:00 2001 From: Emanuel Winblad Date: Mon, 25 Mar 2024 09:00:06 +0100 Subject: [PATCH 1462/1691] Bump vilfo-api-client to 0.5.0 (#114082) --- homeassistant/components/vilfo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vilfo/manifest.json b/homeassistant/components/vilfo/manifest.json index 850d4fecb27..9fa52072ddf 100644 --- a/homeassistant/components/vilfo/manifest.json +++ b/homeassistant/components/vilfo/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/vilfo", "iot_class": "local_polling", "loggers": ["vilfo"], - "requirements": ["vilfo-api-client==0.4.1"] + "requirements": ["vilfo-api-client==0.5.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index c96935e0e23..51614015597 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2795,7 +2795,7 @@ velbus-aio==2023.12.0 venstarcolortouch==0.19 # homeassistant.components.vilfo -vilfo-api-client==0.4.1 +vilfo-api-client==0.5.0 # homeassistant.components.voip voip-utils==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5d5824c7f48..7e9104bc45e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2151,7 +2151,7 @@ velbus-aio==2023.12.0 venstarcolortouch==0.19 # homeassistant.components.vilfo -vilfo-api-client==0.4.1 +vilfo-api-client==0.5.0 # homeassistant.components.voip voip-utils==0.1.0 From 04bc31a7993fa3f91659e5e1f0e6afc218415a8f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 09:35:27 +0100 Subject: [PATCH 1463/1691] Update pre-commit to 3.7.0 (#114154) --- requirements_test.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 888c39fd239..64c65007948 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -12,7 +12,7 @@ coverage==7.4.3 freezegun==1.4.0 mock-open==1.4.0 mypy-dev==1.9.0b1 -pre-commit==3.6.2 +pre-commit==3.7.0 pydantic==1.10.12 pylint==3.1.0 pylint-per-file-ignores==1.3.2 @@ -51,4 +51,4 @@ types-pytz==2023.3.1.1 types-PyYAML==6.0.12.12 types-requests==2.31.0.3 types-xmltodict==0.13.0.3 -uv==0.1.24 \ No newline at end of file +uv==0.1.24 From c8948e3b1c8b1f52df6185695331cf6471c86829 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 25 Mar 2024 09:50:06 +0100 Subject: [PATCH 1464/1691] Use uv and sort jobs in builder.yml (#114153) --- .github/workflows/builder.yml | 79 ++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 0c25057b1b1..f0dd00bdfaf 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -12,6 +12,8 @@ env: BUILD_TYPE: core DEFAULT_PYTHON: "3.12" PIP_TIMEOUT: 60 + UV_HTTP_TIMEOUT: 60 + UV_SYSTEM_PYTHON: "true" jobs: init: @@ -49,42 +51,6 @@ jobs: with: ignore-dev: true - build_python: - name: Build PyPi package - environment: ${{ needs.init.outputs.channel }} - needs: ["init", "build_base"] - runs-on: ubuntu-latest - if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' - steps: - - name: Checkout the repository - uses: actions/checkout@v4.1.2 - - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - - - name: Download Translations - run: python3 -m script.translations download - env: - LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} - - - name: Build package - shell: bash - run: | - # Remove dist, build, and homeassistant.egg-info - # when build locally for testing! - pip install twine build - python -m build - - - name: Upload package - shell: bash - run: | - export TWINE_USERNAME="__token__" - export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" - - twine upload dist/* --skip-existing - build_base: name: Build ${{ matrix.arch }} base core image if: github.repository_owner == 'home-assistant' @@ -134,8 +100,9 @@ jobs: if: needs.init.outputs.channel == 'dev' shell: bash run: | - python3 -m pip install packaging tomli - python3 -m pip install . + python3 -m pip install "$(grep '^uv' < requirements_test.txt)" + uv pip install packaging tomli + uv pip install . version="$(python3 script/version_bump.py nightly)" if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then @@ -463,3 +430,39 @@ jobs: v="${{ needs.init.outputs.version }}" create_manifest "${v%.*}" "${{ needs.init.outputs.version }}" fi + + build_python: + name: Build PyPi package + environment: ${{ needs.init.outputs.channel }} + needs: ["init", "build_base"] + runs-on: ubuntu-latest + if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' + steps: + - name: Checkout the repository + uses: actions/checkout@v4.1.2 + + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + uses: actions/setup-python@v5.0.0 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + + - name: Download Translations + run: python3 -m script.translations download + env: + LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} + + - name: Build package + shell: bash + run: | + # Remove dist, build, and homeassistant.egg-info + # when build locally for testing! + pip install twine build + python -m build + + - name: Upload package + shell: bash + run: | + export TWINE_USERNAME="__token__" + export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" + + twine upload dist/* --skip-existing From a58049554d6ea6bcf9d646a507d6586c9650b8cc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:10:37 +0100 Subject: [PATCH 1465/1691] Update pytest-asyncio to 0.23.6 (#114155) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 64c65007948..a3e576f592c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -17,7 +17,7 @@ pydantic==1.10.12 pylint==3.1.0 pylint-per-file-ignores==1.3.2 pipdeptree==2.15.1 -pytest-asyncio==0.23.5 +pytest-asyncio==0.23.6 pytest-aiohttp==1.0.5 pytest-cov==4.1.0 pytest-freezer==0.4.8 From 19fa39d556e3c8964628c4e6803de88c7aea3cb5 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 25 Mar 2024 10:39:30 +0100 Subject: [PATCH 1466/1691] Generate ConfigValidationError message from English translations (#113844) * Fetch ConfigValidationError message from translation cache * Sync error logmessages with translation cache * More sync * Cleanup * Remove unrelated change * Follow up comments * Rebase and improve contructor * Improve name * Rename to MULTIPLE_INTEGRATION_CONFIG_ERRORS --- .../components/homeassistant/strings.json | 14 +- homeassistant/config.py | 81 +++----- homeassistant/exceptions.py | 9 +- tests/snapshots/test_config.ambr | 95 ++++++---- tests/test_config.py | 179 ++++++++++-------- 5 files changed, 200 insertions(+), 178 deletions(-) diff --git a/homeassistant/components/homeassistant/strings.json b/homeassistant/components/homeassistant/strings.json index 016f3e0580d..37604c0e18e 100644 --- a/homeassistant/components/homeassistant/strings.json +++ b/homeassistant/components/homeassistant/strings.json @@ -163,31 +163,31 @@ "message": "Error importing config platform {domain}: {error}" }, "config_validation_err": { - "message": "Invalid config for integration {domain} at {config_file}, line {line}: {error}. Check the logs for more information." + "message": "Invalid config for integration {domain} at {config_file}, line {line}: {error}." }, "config_validator_unknown_err": { - "message": "Unknown error calling {domain} config validator. Check the logs for more information." + "message": "Unknown error calling {domain} config validator - {error}." }, "config_schema_unknown_err": { - "message": "Unknown error calling {domain} CONFIG_SCHEMA. Check the logs for more information." + "message": "Unknown error calling {domain} CONFIG_SCHEMA - {error}." }, - "integration_config_error": { + "multiple_integration_config_errors": { "message": "Failed to process config for integration {domain} due to multiple ({errors}) errors. Check the logs for more information." }, "max_length_exceeded": { "message": "Value {value} for property {property_name} has a maximum length of {max_length} characters." }, "platform_component_load_err": { - "message": "Platform error: {domain} - {error}. Check the logs for more information." + "message": "Platform error: {domain} - {error}." }, "platform_component_load_exc": { - "message": "Platform error: {domain} - {error}. Check the logs for more information." + "message": "[%key:component::homeassistant::exceptions::platform_component_load_err::message%]" }, "platform_config_validation_err": { "message": "Invalid config for {domain} from integration {p_name} at file {config_file}, line {line}: {error}. Check the logs for more information." }, "platform_schema_validator_err": { - "message": "Unknown error when validating config for {domain} from integration {p_name}" + "message": "Unknown error when validating config for {domain} from integration {p_name} - {error}." }, "service_not_found": { "message": "Service {domain}.{service} not found." diff --git a/homeassistant/config.py b/homeassistant/config.py index 73c38a6c41a..d2390c6c4ff 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -63,6 +63,7 @@ from .exceptions import ConfigValidationError, HomeAssistantError from .generated.currencies import HISTORIC_CURRENCIES from .helpers import config_validation as cv, issue_registry as ir from .helpers.entity_values import EntityValues +from .helpers.translation import async_get_exception_message from .helpers.typing import ConfigType from .loader import ComponentProtocol, Integration, IntegrationNotFound from .requirements import RequirementsNotFound, async_get_integration_with_requirements @@ -130,13 +131,23 @@ class ConfigErrorTranslationKey(StrEnum): CONFIG_PLATFORM_IMPORT_ERR = "config_platform_import_err" CONFIG_VALIDATOR_UNKNOWN_ERR = "config_validator_unknown_err" CONFIG_SCHEMA_UNKNOWN_ERR = "config_schema_unknown_err" - PLATFORM_VALIDATOR_UNKNOWN_ERR = "platform_validator_unknown_err" PLATFORM_COMPONENT_LOAD_ERR = "platform_component_load_err" PLATFORM_COMPONENT_LOAD_EXC = "platform_component_load_exc" PLATFORM_SCHEMA_VALIDATOR_ERR = "platform_schema_validator_err" # translation key in case multiple errors occurred - INTEGRATION_CONFIG_ERROR = "integration_config_error" + MULTIPLE_INTEGRATION_CONFIG_ERRORS = "multiple_integration_config_errors" + + +_CONFIG_LOG_SHOW_STACK_TRACE: dict[ConfigErrorTranslationKey, bool] = { + ConfigErrorTranslationKey.COMPONENT_IMPORT_ERR: False, + ConfigErrorTranslationKey.CONFIG_PLATFORM_IMPORT_ERR: False, + ConfigErrorTranslationKey.CONFIG_VALIDATOR_UNKNOWN_ERR: True, + ConfigErrorTranslationKey.CONFIG_SCHEMA_UNKNOWN_ERR: True, + ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_ERR: False, + ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC: True, + ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR: True, +} @dataclass @@ -1183,48 +1194,16 @@ def _get_log_message_and_stack_print_pref( platform_config = platform_exception.config link = platform_exception.integration_link - placeholders: dict[str, str] = {"domain": domain, "error": str(exception)} - - log_message_mapping: dict[ConfigErrorTranslationKey, tuple[str, bool]] = { - ConfigErrorTranslationKey.COMPONENT_IMPORT_ERR: ( - f"Unable to import {domain}: {exception}", - False, - ), - ConfigErrorTranslationKey.CONFIG_PLATFORM_IMPORT_ERR: ( - f"Error importing config platform {domain}: {exception}", - False, - ), - ConfigErrorTranslationKey.CONFIG_VALIDATOR_UNKNOWN_ERR: ( - f"Unknown error calling {domain} config validator", - True, - ), - ConfigErrorTranslationKey.CONFIG_SCHEMA_UNKNOWN_ERR: ( - f"Unknown error calling {domain} CONFIG_SCHEMA", - True, - ), - ConfigErrorTranslationKey.PLATFORM_VALIDATOR_UNKNOWN_ERR: ( - f"Unknown error validating {platform_path} platform config with {domain} " - "component platform schema", - True, - ), - ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_ERR: ( - f"Platform error: {domain} - {exception}", - False, - ), - ConfigErrorTranslationKey.PLATFORM_COMPONENT_LOAD_EXC: ( - f"Platform error: {domain} - {exception}", - True, - ), - ConfigErrorTranslationKey.PLATFORM_SCHEMA_VALIDATOR_ERR: ( - f"Unknown error validating config for {platform_path} platform " - f"for {domain} component with PLATFORM_SCHEMA", - True, - ), + placeholders: dict[str, str] = { + "domain": domain, + "error": str(exception), + "p_name": platform_path, } - log_message_show_stack_trace = log_message_mapping.get( + + show_stack_trace: bool | None = _CONFIG_LOG_SHOW_STACK_TRACE.get( platform_exception.translation_key ) - if log_message_show_stack_trace is None: + if show_stack_trace is None: # If no pre defined log_message is set, we generate an enriched error # message, so we can notify about it during setup show_stack_trace = False @@ -1247,9 +1226,14 @@ def _get_log_message_and_stack_print_pref( show_stack_trace = True return (log_message, show_stack_trace, placeholders) - assert isinstance(log_message_show_stack_trace, tuple) + # Generate the log message from the English translations + log_message = async_get_exception_message( + HA_DOMAIN, + platform_exception.translation_key, + translation_placeholders=placeholders, + ) - return (*log_message_show_stack_trace, placeholders) + return (log_message, show_stack_trace, placeholders) async def async_process_component_and_handle_errors( @@ -1348,21 +1332,16 @@ def async_handle_component_errors( if len(config_exception_info) == 1: translation_key = platform_exception.translation_key else: - translation_key = ConfigErrorTranslationKey.INTEGRATION_CONFIG_ERROR + translation_key = ConfigErrorTranslationKey.MULTIPLE_INTEGRATION_CONFIG_ERRORS errors = str(len(config_exception_info)) - log_message = ( - f"Failed to process component config for integration {domain} " - f"due to multiple errors ({errors}), check the logs for more information." - ) placeholders = { "domain": domain, "errors": errors, } raise ConfigValidationError( - str(log_message), + translation_key, [platform_exception.exception for platform_exception in config_exception_info], - translation_domain="homeassistant", - translation_key=translation_key, + translation_domain=HA_DOMAIN, translation_placeholders=placeholders, ) diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index e6ed111c13f..f681764b4af 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -87,20 +87,19 @@ class ConfigValidationError(HomeAssistantError, ExceptionGroup[Exception]): def __init__( self, - message: str, + message_translation_key: str, exceptions: list[Exception], translation_domain: str | None = None, - translation_key: str | None = None, translation_placeholders: dict[str, str] | None = None, ) -> None: """Initialize exception.""" super().__init__( - *(message, exceptions), + *(message_translation_key, exceptions), translation_domain=translation_domain, - translation_key=translation_key, + translation_key=message_translation_key, translation_placeholders=translation_placeholders, ) - self._message = message + self.generate_message = True class ServiceValidationError(HomeAssistantError): diff --git a/tests/snapshots/test_config.ambr b/tests/snapshots/test_config.ambr index 34fa5d61797..6fcbce7d8d6 100644 --- a/tests/snapshots/test_config.ambr +++ b/tests/snapshots/test_config.ambr @@ -1,4 +1,19 @@ # serializer version: 1 +# name: test_component_config_error_processing[exception_info_list0-bla-messages0-False-component_import_err] + 'Unable to import test_domain: bla' +# --- +# name: test_component_config_error_processing[exception_info_list1-bla-messages1-True-config_validation_err] + 'Invalid config for integration test_domain at configuration.yaml, line 140: bla' +# --- +# name: test_component_config_error_processing[exception_info_list2-bla @ data['path']-messages2-False-config_validation_err] + "Invalid config for integration test_domain at configuration.yaml, line 140: bla @ data['path']" +# --- +# name: test_component_config_error_processing[exception_info_list3-bla @ data['path']-messages3-False-platform_config_validation_err] + "Invalid config for test_domain from integration test_domain at file configuration.yaml, line 140: bla @ data['path']. Check the logs for more information" +# --- +# name: test_component_config_error_processing[exception_info_list4-bla-messages4-False-platform_component_load_err] + 'Platform error: test_domain - bla' +# --- # name: test_component_config_validation_error[basic] list([ dict({ @@ -63,7 +78,7 @@ }), dict({ 'has_exc_info': True, - 'message': 'Unknown error calling custom_validator_bad_2 config validator', + 'message': 'config_validator_unknown_err', }), ]) # --- @@ -131,7 +146,7 @@ }), dict({ 'has_exc_info': True, - 'message': 'Unknown error calling custom_validator_bad_2 config validator', + 'message': 'config_validator_unknown_err', }), ]) # --- @@ -247,7 +262,7 @@ }), dict({ 'has_exc_info': True, - 'message': 'Unknown error calling custom_validator_bad_2 config validator', + 'message': 'config_validator_unknown_err', }), ]) # --- @@ -291,7 +306,7 @@ }), dict({ 'has_exc_info': True, - 'message': 'Unknown error calling custom_validator_bad_2 config validator', + 'message': 'config_validator_unknown_err', }), dict({ 'has_exc_info': False, @@ -342,7 +357,27 @@ ''', "Invalid config for 'custom_validator_ok_2' at configuration.yaml, line 52: required key 'host' not provided, please check the docs at https://www.home-assistant.io/integrations/custom_validator_ok_2", "Invalid config for 'custom_validator_bad_1' at configuration.yaml, line 55: broken, please check the docs at https://www.home-assistant.io/integrations/custom_validator_bad_1", - 'Unknown error calling custom_validator_bad_2 config validator', + 'config_validator_unknown_err', + ]) +# --- +# name: test_individual_packages_schema_validation_errors[packages_dict] + list([ + "Setup of package 'should_be_a_dict' at configuration.yaml, line 3 failed: Invalid package definition 'should_be_a_dict': expected a dictionary. Package will not be initialized", + ]) +# --- +# name: test_individual_packages_schema_validation_errors[packages_include_dir_named_dict] + list([ + "Setup of package 'should_be_a_dict' at packages/expected_dict.yaml, line 1 failed: Invalid package definition 'should_be_a_dict': expected a dictionary. Package will not be initialized", + ]) +# --- +# name: test_individual_packages_schema_validation_errors[packages_include_dir_named_slug] + list([ + "Setup of package 'this is not a slug but it should be one' at packages/expected_slug.yaml, line 1 failed: Invalid package definition 'this is not a slug but it should be one': invalid slug this is not a slug but it should be one (try this_is_not_a_slug_but_it_should_be_one). Package will not be initialized", + ]) +# --- +# name: test_individual_packages_schema_validation_errors[packages_slug] + list([ + "Setup of package 'this is not a slug but it should be one' at configuration.yaml, line 3 failed: Invalid package definition 'this is not a slug but it should be one': invalid slug this is not a slug but it should be one (try this_is_not_a_slug_but_it_should_be_one). Package will not be initialized", ]) # --- # name: test_package_merge_error[packages] @@ -381,6 +416,21 @@ "Setup of package 'unknown_integration' at integrations/unknown_integration.yaml, line 1 failed: Integration test_domain caused error: ModuleNotFoundError: No module named 'not_installed_something'", ]) # --- +# name: test_packages_schema_validation_error[packages_is_a_list] + list([ + "Invalid package configuration 'packages' at configuration.yaml, line 2: expected a dictionary", + ]) +# --- +# name: test_packages_schema_validation_error[packages_is_a_value] + list([ + "Invalid package configuration 'packages' at configuration.yaml, line 2: expected a dictionary", + ]) +# --- +# name: test_packages_schema_validation_error[packages_is_null] + list([ + "Invalid package configuration 'packages' at configuration.yaml, line 3: expected a dictionary", + ]) +# --- # name: test_yaml_error[basic] ''' mapping values are not allowed here @@ -451,38 +501,3 @@ ''', ]) # --- -# name: test_individual_packages_schema_validation_errors[packages_dict] - list([ - "Setup of package 'should_be_a_dict' at configuration.yaml, line 3 failed: Invalid package definition 'should_be_a_dict': expected a dictionary. Package will not be initialized", - ]) -# --- -# name: test_individual_packages_schema_validation_errors[packages_slug] - list([ - "Setup of package 'this is not a slug but it should be one' at configuration.yaml, line 3 failed: Invalid package definition 'this is not a slug but it should be one': invalid slug this is not a slug but it should be one (try this_is_not_a_slug_but_it_should_be_one). Package will not be initialized", - ]) -# --- -# name: test_individual_packages_schema_validation_errors[packages_include_dir_named_dict] - list([ - "Setup of package 'should_be_a_dict' at packages/expected_dict.yaml, line 1 failed: Invalid package definition 'should_be_a_dict': expected a dictionary. Package will not be initialized", - ]) -# --- -# name: test_individual_packages_schema_validation_errors[packages_include_dir_named_slug] - list([ - "Setup of package 'this is not a slug but it should be one' at packages/expected_slug.yaml, line 1 failed: Invalid package definition 'this is not a slug but it should be one': invalid slug this is not a slug but it should be one (try this_is_not_a_slug_but_it_should_be_one). Package will not be initialized", - ]) -# --- -# name: test_packages_schema_validation_error[packages_is_a_list] - list([ - "Invalid package configuration 'packages' at configuration.yaml, line 2: expected a dictionary", - ]) -# --- -# name: test_packages_schema_validation_error[packages_is_a_value] - list([ - "Invalid package configuration 'packages' at configuration.yaml, line 2: expected a dictionary", - ]) -# --- -# name: test_packages_schema_validation_error[packages_is_null] - list([ - "Invalid package configuration 'packages' at configuration.yaml, line 3: expected a dictionary", - ]) -# --- diff --git a/tests/test_config.py b/tests/test_config.py index efb87a44bdb..c2e134b1cfd 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -44,6 +44,7 @@ import homeassistant.helpers.check_config as check_config from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType from homeassistant.loader import Integration, async_get_integration +from homeassistant.setup import async_setup_component from homeassistant.util.unit_system import ( _CONF_UNIT_SYSTEM_US_CUSTOMARY, METRIC_SYSTEM, @@ -51,6 +52,7 @@ from homeassistant.util.unit_system import ( UnitSystem, ) from homeassistant.util.yaml import SECRET_YAML +from homeassistant.util.yaml.objects import NodeDictClass from .common import ( MockModule, @@ -373,6 +375,14 @@ async def mock_custom_validator_integrations_with_docs( ) +class ConfigTestClass(NodeDictClass): + """Test class for config with wrapper.""" + + __dict__ = {"__config_file__": "configuration.yaml", "__line__": 140} + __line__ = 140 + __config_file__ = "configuration.yaml" + + async def test_create_default_config(hass: HomeAssistant) -> None: """Test creation of default config.""" assert not os.path.isfile(YAML_PATH) @@ -1435,7 +1445,24 @@ async def test_component_config_exceptions( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test unexpected exceptions validating component config.""" - # Config validator + + # Create test config with embedded info + test_config = ConfigTestClass({"test_domain": {}}) + test_platform_config = ConfigTestClass( + {"test_domain": {"platform": "test_platform"}} + ) + test_multi_platform_config = ConfigTestClass( + { + "test_domain": [ + {"platform": "test_platform1"}, + {"platform": "test_platform2"}, + ] + }, + ) + + # Make sure the exception translation cache is loaded + await async_setup_component(hass, "homeassistant", {}) + test_integration = Mock( domain="test_domain", async_get_component=AsyncMock(), @@ -1447,7 +1474,7 @@ async def test_component_config_exceptions( ) assert ( await config_util.async_process_component_and_handle_errors( - hass, {}, integration=test_integration + hass, test_config, integration=test_integration ) is None ) @@ -1456,12 +1483,13 @@ async def test_component_config_exceptions( caplog.clear() with pytest.raises(HomeAssistantError) as ex: await config_util.async_process_component_and_handle_errors( - hass, {}, integration=test_integration, raise_on_failure=True + hass, test_config, integration=test_integration, raise_on_failure=True ) assert "ValueError: broken" in caplog.text assert "Unknown error calling test_domain config validator" in caplog.text - assert str(ex.value) == "Unknown error calling test_domain config validator" - + assert ( + str(ex.value) == "Unknown error calling test_domain config validator - broken" + ) test_integration = Mock( domain="test_domain", async_get_platform=AsyncMock( @@ -1476,17 +1504,23 @@ async def test_component_config_exceptions( caplog.clear() assert ( await config_util.async_process_component_and_handle_errors( - hass, {}, integration=test_integration, raise_on_failure=False + hass, test_config, integration=test_integration, raise_on_failure=False ) is None ) - assert "Invalid config for 'test_domain': broken" in caplog.text + assert ( + "Invalid config for 'test_domain' at ../../configuration.yaml, " + "line 140: broken, please check the docs at" in caplog.text + ) with pytest.raises(HomeAssistantError) as ex: await config_util.async_process_component_and_handle_errors( - hass, {}, integration=test_integration, raise_on_failure=True + hass, test_config, integration=test_integration, raise_on_failure=True ) - assert "Invalid config for 'test_domain': broken" in str(ex.value) - + assert ( + str(ex.value) + == "Invalid config for integration test_domain at configuration.yaml, " + "line 140: broken" + ) # component.CONFIG_SCHEMA caplog.clear() test_integration = Mock( @@ -1499,23 +1533,23 @@ async def test_component_config_exceptions( assert ( await config_util.async_process_component_and_handle_errors( hass, - {}, + test_config, integration=test_integration, raise_on_failure=False, ) is None ) assert "Unknown error calling test_domain CONFIG_SCHEMA" in caplog.text + caplog.clear() with pytest.raises(HomeAssistantError) as ex: await config_util.async_process_component_and_handle_errors( hass, - {}, + test_config, integration=test_integration, raise_on_failure=True, ) assert "Unknown error calling test_domain CONFIG_SCHEMA" in caplog.text - assert str(ex.value) == "Unknown error calling test_domain CONFIG_SCHEMA" - + assert str(ex.value) == "Unknown error calling test_domain CONFIG_SCHEMA - broken" # component.PLATFORM_SCHEMA caplog.clear() test_integration = Mock( @@ -1530,30 +1564,30 @@ async def test_component_config_exceptions( ) assert await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {"platform": "test_platform"}}, + test_platform_config, integration=test_integration, raise_on_failure=False, ) == {"test_domain": []} assert "ValueError: broken" in caplog.text assert ( - "Unknown error validating config for test_platform platform " - "for test_domain component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" ) in caplog.text caplog.clear() with pytest.raises(HomeAssistantError) as ex: await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {"platform": "test_platform"}}, + test_platform_config, integration=test_integration, raise_on_failure=True, ) assert ( - "Unknown error validating config for test_platform platform " - "for test_domain component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" ) in caplog.text assert str(ex.value) == ( - "Unknown error validating config for test_platform platform " - "for test_domain component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" ) # platform.PLATFORM_SCHEMA @@ -1575,78 +1609,65 @@ async def test_component_config_exceptions( ): assert await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {"platform": "test_platform"}}, + test_platform_config, integration=test_integration, raise_on_failure=False, ) == {"test_domain": []} assert "ValueError: broken" in caplog.text assert ( - "Unknown error validating config for test_platform platform for test_domain" - " component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" ) in caplog.text caplog.clear() with pytest.raises(HomeAssistantError) as ex: assert await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {"platform": "test_platform"}}, + test_platform_config, integration=test_integration, raise_on_failure=True, ) assert ( - "Unknown error validating config for test_platform platform for test_domain" - " component with PLATFORM_SCHEMA" - ) in str(ex.value) + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" in str(ex.value) + ) assert "ValueError: broken" in caplog.text assert ( - "Unknown error validating config for test_platform platform for test_domain" - " component with PLATFORM_SCHEMA" in caplog.text + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" in caplog.text ) # Test multiple platform failures assert await config_util.async_process_component_and_handle_errors( hass, - { - "test_domain": [ - {"platform": "test_platform1"}, - {"platform": "test_platform2"}, - ] - }, + test_multi_platform_config, integration=test_integration, raise_on_failure=False, ) == {"test_domain": []} assert "ValueError: broken" in caplog.text assert ( - "Unknown error validating config for test_platform1 platform " - "for test_domain component with PLATFORM_SCHEMA" - ) in caplog.text - assert ( - "Unknown error validating config for test_platform2 platform " - "for test_domain component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform - broken" ) in caplog.text caplog.clear() with pytest.raises(HomeAssistantError) as ex: assert await config_util.async_process_component_and_handle_errors( hass, - { - "test_domain": [ - {"platform": "test_platform1"}, - {"platform": "test_platform2"}, - ] - }, + test_multi_platform_config, integration=test_integration, raise_on_failure=True, ) assert ( - "Failed to process component config for integration test_domain" - " due to multiple errors (2), check the logs for more information." - ) in str(ex.value) + "Failed to process config for integration test_domain " + "due to multiple (2) errors. Check the logs for more information" + in str(ex.value) + ) assert "ValueError: broken" in caplog.text assert ( - "Unknown error validating config for test_platform1 platform " - "for test_domain component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform1 - broken" ) in caplog.text assert ( - "Unknown error validating config for test_platform2 platform " - "for test_domain component with PLATFORM_SCHEMA" + "Unknown error when validating config for test_domain " + "from integration test_platform2 - broken" ) in caplog.text # async_get_platform("domain") raising on ImportError @@ -1668,7 +1689,7 @@ async def test_component_config_exceptions( ): assert await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {"platform": "test_platform"}}, + test_platform_config, integration=test_integration, raise_on_failure=False, ) == {"test_domain": []} @@ -1680,7 +1701,7 @@ async def test_component_config_exceptions( with pytest.raises(HomeAssistantError) as ex: assert await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {"platform": "test_platform"}}, + test_platform_config, integration=test_integration, raise_on_failure=True, ) @@ -1713,7 +1734,7 @@ async def test_component_config_exceptions( assert ( await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {}}, + test_config, integration=test_integration, raise_on_failure=False, ) @@ -1726,7 +1747,7 @@ async def test_component_config_exceptions( with pytest.raises(HomeAssistantError) as ex: await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {}}, + test_config, integration=test_integration, raise_on_failure=True, ) @@ -1751,7 +1772,7 @@ async def test_component_config_exceptions( assert ( await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {}}, + test_config, integration=test_integration, raise_on_failure=False, ) @@ -1761,7 +1782,7 @@ async def test_component_config_exceptions( with pytest.raises(HomeAssistantError) as ex: await config_util.async_process_component_and_handle_errors( hass, - {"test_domain": {}}, + test_config, integration=test_integration, raise_on_failure=True, ) @@ -1778,7 +1799,7 @@ async def test_component_config_exceptions( ImportError("bla"), "component_import_err", "test_domain", - {"test_domain": []}, + ConfigTestClass({"test_domain": []}), "https://example.com", ) ], @@ -1793,13 +1814,14 @@ async def test_component_config_exceptions( HomeAssistantError("bla"), "config_validation_err", "test_domain", - {"test_domain": []}, + ConfigTestClass({"test_domain": []}), "https://example.com", ) ], "bla", [ - "Invalid config for 'test_domain': bla, " + "Invalid config for 'test_domain' at " + "../../configuration.yaml, line 140: bla, " "please check the docs at https://example.com", "bla", ], @@ -1812,14 +1834,15 @@ async def test_component_config_exceptions( vol.Invalid("bla", ["path"]), "config_validation_err", "test_domain", - {"test_domain": []}, + ConfigTestClass({"test_domain": []}), "https://example.com", ) ], "bla @ data['path']", [ - "Invalid config for 'test_domain': bla 'path', got None, " - "please check the docs at https://example.com", + "Invalid config for 'test_domain' at " + "../../configuration.yaml, line 140: bla 'path', " + "got None, please check the docs at https://example.com", "bla", ], False, @@ -1831,14 +1854,15 @@ async def test_component_config_exceptions( vol.Invalid("bla", ["path"]), "platform_config_validation_err", "test_domain", - {"test_domain": []}, + ConfigTestClass({"test_domain": []}), "https://alt.example.com", ) ], "bla @ data['path']", [ - "Invalid config for 'test_domain': bla 'path', got None, " - "please check the docs at https://alt.example.com", + "Invalid config for 'test_domain' at " + "../../configuration.yaml, line 140: bla 'path', " + "got None, please check the docs at https://alt.example.com", "bla", ], False, @@ -1850,7 +1874,7 @@ async def test_component_config_exceptions( ImportError("bla"), "platform_component_load_err", "test_domain", - {"test_domain": []}, + ConfigTestClass({"test_domain": []}), "https://example.com", ) ], @@ -1864,13 +1888,18 @@ async def test_component_config_exceptions( async def test_component_config_error_processing( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - error: str, exception_info_list: list[config_util.ConfigExceptionInfo], + snapshot: SnapshotAssertion, + error: str, messages: list[str], show_stack_trace: bool, translation_key: str, ) -> None: """Test component config error processing.""" + + # Make sure the exception translation cache is loaded + await async_setup_component(hass, "homeassistant", {}) + test_integration = Mock( domain="test_domain", documentation="https://example.com", @@ -1890,7 +1919,7 @@ async def test_component_config_error_processing( records = [record for record in caplog.records if record.msg == messages[0]] assert len(records) == 1 assert (records[0].exc_info is not None) == show_stack_trace - assert str(ex.value) == messages[0] + assert str(ex.value) == snapshot assert ex.value.translation_key == translation_key assert ex.value.translation_domain == "homeassistant" assert ex.value.translation_placeholders["domain"] == "test_domain" @@ -1902,7 +1931,7 @@ async def test_component_config_error_processing( return_value=config_util.IntegrationConfigInfo(None, exception_info_list), ): await config_util.async_process_component_and_handle_errors( - hass, {}, test_integration + hass, ConfigTestClass({}), test_integration ) assert all(message in caplog.text for message in messages) From 33d9947a7045c411e2cc2c681fdd773c8c56c11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Mon, 25 Mar 2024 10:50:04 +0100 Subject: [PATCH 1467/1691] Add climate fan modes for Airzone Cloud Aidoo devices (#103574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * airzone_cloud: climate: add Aidoo speeds support Signed-off-by: Álvaro Fernández Rojas * Add suggested changes Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: rename _set_fan_speeds() Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: drop BASE_FAN_SPEEDS Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: refactor FAN_AUTO speed Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: document standard speeds replacement Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: fix Homekit comment Signed-off-by: Álvaro Fernández Rojas * airzone_cloud: climate: consistent fan speed names Instead of mixing "low", "medium" and "high" with percentages, let's use only percentages if there are more speeds. Signed-off-by: Álvaro Fernández Rojas --------- Signed-off-by: Álvaro Fernández Rojas --- .../components/airzone_cloud/climate.py | 77 +++++++++++++++++++ .../components/airzone_cloud/test_climate.py | 71 +++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/homeassistant/components/airzone_cloud/climate.py b/homeassistant/components/airzone_cloud/climate.py index 5aa03a14ada..8fcdee11535 100644 --- a/homeassistant/components/airzone_cloud/climate.py +++ b/homeassistant/components/airzone_cloud/climate.py @@ -11,6 +11,7 @@ from aioairzone_cloud.const import ( API_PARAMS, API_POWER, API_SETPOINT, + API_SPEED_CONF, API_UNITS, API_VALUE, AZD_ACTION, @@ -24,6 +25,8 @@ from aioairzone_cloud.const import ( AZD_NUM_DEVICES, AZD_NUM_GROUPS, AZD_POWER, + AZD_SPEED, + AZD_SPEEDS, AZD_TEMP, AZD_TEMP_SET, AZD_TEMP_SET_MAX, @@ -34,6 +37,10 @@ from aioairzone_cloud.const import ( from homeassistant.components.climate import ( ATTR_HVAC_MODE, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, ClimateEntity, ClimateEntityFeature, HVACAction, @@ -55,6 +62,22 @@ from .entity import ( AirzoneZoneEntity, ) +FAN_SPEED_AUTO: dict[int, str] = { + 0: FAN_AUTO, +} + +FAN_SPEED_MAPS: Final[dict[int, dict[int, str]]] = { + 2: { + 1: FAN_LOW, + 2: FAN_HIGH, + }, + 3: { + 1: FAN_LOW, + 2: FAN_MEDIUM, + 3: FAN_HIGH, + }, +} + HVAC_ACTION_LIB_TO_HASS: Final[dict[OperationAction, HVACAction]] = { OperationAction.COOLING: HVACAction.COOLING, OperationAction.DRYING: HVACAction.DRYING, @@ -275,6 +298,9 @@ class AirzoneDeviceGroupClimate(AirzoneClimate): class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneDeviceClimate): """Define an Airzone Cloud Aidoo climate.""" + _speeds: dict[int, str] + _speeds_reverse: dict[str, int] + def __init__( self, coordinator: AirzoneUpdateCoordinator, @@ -291,9 +317,52 @@ class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneDeviceClimate): ] if HVACMode.OFF not in self._attr_hvac_modes: self._attr_hvac_modes += [HVACMode.OFF] + if ( + self.get_airzone_value(AZD_SPEED) is not None + and self.get_airzone_value(AZD_SPEEDS) is not None + ): + self._initialize_fan_speeds() self._async_update_attrs() + def _initialize_fan_speeds(self) -> None: + """Initialize Aidoo fan speeds.""" + azd_speeds: dict[int, int] = self.get_airzone_value(AZD_SPEEDS) + max_speed = max(azd_speeds) + + fan_speeds: dict[int, str] + if speeds_map := FAN_SPEED_MAPS.get(max_speed): + fan_speeds = speeds_map + else: + fan_speeds = {} + + for speed in azd_speeds: + if speed != 0: + fan_speeds[speed] = f"{int(round((speed * 100) / max_speed, 0))}%" + + if 0 in azd_speeds: + fan_speeds = FAN_SPEED_AUTO | fan_speeds + + self._speeds = {} + for key, value in fan_speeds.items(): + _key = azd_speeds.get(key) + if _key is not None: + self._speeds[_key] = value + + self._speeds_reverse = {v: k for k, v in self._speeds.items()} + self._attr_fan_modes = list(self._speeds_reverse) + + self._attr_supported_features |= ClimateEntityFeature.FAN_MODE + + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set Aidoo fan mode.""" + params: dict[str, Any] = { + API_SPEED_CONF: { + API_VALUE: self._speeds_reverse.get(fan_mode), + } + } + await self._async_update_params(params) + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set hvac mode.""" params: dict[str, Any] = {} @@ -311,6 +380,14 @@ class AirzoneAidooClimate(AirzoneAidooEntity, AirzoneDeviceClimate): } await self._async_update_params(params) + @callback + def _async_update_attrs(self) -> None: + """Update Aidoo climate attributes.""" + super()._async_update_attrs() + + if self.supported_features & ClimateEntityFeature.FAN_MODE: + self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED)) + class AirzoneGroupClimate(AirzoneGroupEntity, AirzoneDeviceGroupClimate): """Define an Airzone Cloud Group climate.""" diff --git a/tests/components/airzone_cloud/test_climate.py b/tests/components/airzone_cloud/test_climate.py index 848c9b8fb2d..46fc16b07df 100644 --- a/tests/components/airzone_cloud/test_climate.py +++ b/tests/components/airzone_cloud/test_climate.py @@ -9,6 +9,8 @@ import pytest from homeassistant.components.climate import ( ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, ATTR_HVAC_ACTION, ATTR_HVAC_MODE, ATTR_HVAC_MODES, @@ -16,6 +18,11 @@ from homeassistant.components.climate import ( ATTR_MIN_TEMP, ATTR_TARGET_TEMP_STEP, DOMAIN as CLIMATE_DOMAIN, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + SERVICE_SET_FAN_MODE, SERVICE_SET_HVAC_MODE, SERVICE_SET_TEMPERATURE, HVACAction, @@ -43,6 +50,12 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert ATTR_CURRENT_HUMIDITY not in state.attributes assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.0 + assert state.attributes[ATTR_FAN_MODE] == FAN_HIGH + assert state.attributes[ATTR_FAN_MODES] == [ + FAN_LOW, + FAN_MEDIUM, + FAN_HIGH, + ] assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.HEAT_COOL, @@ -61,6 +74,15 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert ATTR_CURRENT_HUMIDITY not in state.attributes assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 20.0 + assert state.attributes[ATTR_FAN_MODE] == "60%" + assert state.attributes[ATTR_FAN_MODES] == [ + FAN_AUTO, + "20%", + "40%", + "60%", + "80%", + "100%", + ] assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.HEAT_COOL, @@ -80,6 +102,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert state.attributes[ATTR_CURRENT_HUMIDITY] == 27 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.COOL, @@ -98,6 +122,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert state.attributes[ATTR_CURRENT_HUMIDITY] == 27 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.5 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.HEAT_COOL, @@ -117,6 +143,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF assert state.attributes[ATTR_CURRENT_HUMIDITY] == 24 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 25.0 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.COOL, @@ -134,6 +162,8 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: assert state.state == HVACMode.COOL assert state.attributes[ATTR_CURRENT_HUMIDITY] == 30 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 20.0 + assert ATTR_FAN_MODE not in state.attributes + assert ATTR_FAN_MODES not in state.attributes assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING assert state.attributes[ATTR_HVAC_MODES] == [ HVACMode.COOL, @@ -270,6 +300,47 @@ async def test_airzone_climate_turn_on_off(hass: HomeAssistant) -> None: assert state.state == HVACMode.OFF +async def test_airzone_climate_set_fan_mode(hass: HomeAssistant) -> None: + """Test setting the fan mode.""" + + await async_init_integration(hass) + + # Aidoos + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: "climate.bron", + ATTR_FAN_MODE: FAN_LOW, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron") + assert state.attributes[ATTR_FAN_MODE] == FAN_LOW + + with patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ): + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_FAN_MODE, + { + ATTR_ENTITY_ID: "climate.bron_pro", + ATTR_FAN_MODE: FAN_AUTO, + }, + blocking=True, + ) + + state = hass.states.get("climate.bron_pro") + assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO + + async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None: """Test setting the HVAC mode.""" From 36fdf65782f24ba2f79930051040caa3ab26c039 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:00:22 +0100 Subject: [PATCH 1468/1691] Update coverage to 7.4.4 (#114158) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index a3e576f592c..5412c6a70cd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,7 +8,7 @@ -c homeassistant/package_constraints.txt -r requirements_test_pre_commit.txt astroid==3.1.0 -coverage==7.4.3 +coverage==7.4.4 freezegun==1.4.0 mock-open==1.4.0 mypy-dev==1.9.0b1 From ac0dc946d34f9a1d44bd3ff70f0899fb2c9b999c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:02:20 +0100 Subject: [PATCH 1469/1691] Update pytest-timeout to 2.3.1 (#114161) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 5412c6a70cd..ce5a0c74d75 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -25,7 +25,7 @@ pytest-github-actions-annotate-failures==0.2.0 pytest-socket==0.7.0 pytest-test-groups==1.0.3 pytest-sugar==1.0.0 -pytest-timeout==2.2.0 +pytest-timeout==2.3.1 pytest-unordered==0.5.2 pytest-picked==0.5.0 pytest-xdist==3.3.1 From b2da7800448457bf0b9ee7187b2fd265be0ec497 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:05:30 +0100 Subject: [PATCH 1470/1691] Update respx to 0.21.0 (#114163) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index ce5a0c74d75..6887d835713 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -31,7 +31,7 @@ pytest-picked==0.5.0 pytest-xdist==3.3.1 pytest==8.1.1 requests-mock==1.11.0 -respx==0.20.2 +respx==0.21.0 syrupy==4.6.1 tqdm==4.66.2 types-aiofiles==23.2.0.20240106 From 3643b324c04c01ecbd551571a0895f2d2fffb567 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:13:25 +0100 Subject: [PATCH 1471/1691] Update pipdeptree to 2.16.1 (#114159) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 6887d835713..f3f570f8f26 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -16,7 +16,7 @@ pre-commit==3.7.0 pydantic==1.10.12 pylint==3.1.0 pylint-per-file-ignores==1.3.2 -pipdeptree==2.15.1 +pipdeptree==2.16.1 pytest-asyncio==0.23.6 pytest-aiohttp==1.0.5 pytest-cov==4.1.0 From ace21c876c14bb7b53683e85a477ad9e2399df51 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:15:33 +0100 Subject: [PATCH 1472/1691] Bump plugwise to v0.37.1 (#113245) --- .../components/plugwise/manifest.json | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/plugwise/conftest.py | 34 ++----------- .../all_data.json | 2 +- .../device_list.json | 20 -------- .../notifications.json | 5 -- .../anna_heatpump_heating/device_list.json | 5 -- .../anna_heatpump_heating/notifications.json | 1 - .../fixtures/m_adam_cooling/device_list.json | 8 --- .../m_adam_cooling/notifications.json | 1 - .../fixtures/m_adam_heating/device_list.json | 8 --- .../m_adam_heating/notifications.json | 1 - .../fixtures/m_adam_jip/all_data.json | 2 +- .../fixtures/m_adam_jip/device_list.json | 13 ----- .../fixtures/m_adam_jip/notifications.json | 1 - .../m_anna_heatpump_cooling/device_list.json | 5 -- .../notifications.json | 1 - .../m_anna_heatpump_idle/device_list.json | 5 -- .../m_anna_heatpump_idle/notifications.json | 1 - .../fixtures/p1v3_full_option/all_data.json | 49 ------------------- .../p1v3_full_option/device_list.json | 1 - .../p1v3_full_option/notifications.json | 1 - .../fixtures/p1v4_442_single/all_data.json | 49 +++++++++++++++++++ .../fixtures/p1v4_442_triple/device_list.json | 1 - .../p1v4_442_triple/notifications.json | 5 -- .../fixtures/stretch_v31/all_data.json | 2 +- .../fixtures/stretch_v31/device_list.json | 10 ---- .../fixtures/stretch_v31/notifications.json | 1 - .../plugwise/snapshots/test_diagnostics.ambr | 2 +- tests/components/plugwise/test_sensor.py | 26 +++------- 31 files changed, 68 insertions(+), 200 deletions(-) delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/device_list.json delete mode 100644 tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json delete mode 100644 tests/components/plugwise/fixtures/anna_heatpump_heating/device_list.json delete mode 100644 tests/components/plugwise/fixtures/anna_heatpump_heating/notifications.json delete mode 100644 tests/components/plugwise/fixtures/m_adam_cooling/device_list.json delete mode 100644 tests/components/plugwise/fixtures/m_adam_cooling/notifications.json delete mode 100644 tests/components/plugwise/fixtures/m_adam_heating/device_list.json delete mode 100644 tests/components/plugwise/fixtures/m_adam_heating/notifications.json delete mode 100644 tests/components/plugwise/fixtures/m_adam_jip/device_list.json delete mode 100644 tests/components/plugwise/fixtures/m_adam_jip/notifications.json delete mode 100644 tests/components/plugwise/fixtures/m_anna_heatpump_cooling/device_list.json delete mode 100644 tests/components/plugwise/fixtures/m_anna_heatpump_cooling/notifications.json delete mode 100644 tests/components/plugwise/fixtures/m_anna_heatpump_idle/device_list.json delete mode 100644 tests/components/plugwise/fixtures/m_anna_heatpump_idle/notifications.json delete mode 100644 tests/components/plugwise/fixtures/p1v3_full_option/all_data.json delete mode 100644 tests/components/plugwise/fixtures/p1v3_full_option/device_list.json delete mode 100644 tests/components/plugwise/fixtures/p1v3_full_option/notifications.json create mode 100644 tests/components/plugwise/fixtures/p1v4_442_single/all_data.json delete mode 100644 tests/components/plugwise/fixtures/p1v4_442_triple/device_list.json delete mode 100644 tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/device_list.json delete mode 100644 tests/components/plugwise/fixtures/stretch_v31/notifications.json diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 9b898305899..888f813760a 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/plugwise", "integration_type": "hub", "iot_class": "local_polling", - "loggers": ["crcmod", "plugwise"], - "requirements": ["plugwise==0.36.3"], + "loggers": ["plugwise"], + "requirements": ["plugwise==0.37.1"], "zeroconf": ["_plugwise._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 51614015597..70ad107d49e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1541,7 +1541,7 @@ plexauth==0.0.6 plexwebsocket==0.0.14 # homeassistant.components.plugwise -plugwise==0.36.3 +plugwise==0.37.1 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7e9104bc45e..7df7000a5de 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1212,7 +1212,7 @@ plexauth==0.0.6 plexwebsocket==0.0.14 # homeassistant.components.plugwise -plugwise==0.36.3 +plugwise==0.37.1 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index 140d56715ef..c211cd0a741 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -87,10 +87,7 @@ def mock_smile_adam() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Adam" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -116,10 +113,7 @@ def mock_smile_adam_2() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Adam" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -145,10 +139,7 @@ def mock_smile_adam_3() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Adam" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -174,10 +165,7 @@ def mock_smile_adam_4() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Adam" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -202,10 +190,7 @@ def mock_smile_anna() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Smile Anna" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -230,10 +215,7 @@ def mock_smile_anna_2() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Smile Anna" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -258,10 +240,7 @@ def mock_smile_anna_3() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Smile Anna" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -273,23 +252,20 @@ def mock_smile_anna_3() -> Generator[None, MagicMock, None]: @pytest.fixture def mock_smile_p1() -> Generator[None, MagicMock, None]: """Create a Mock P1 DSMR environment for testing exceptions.""" - chosen_env = "p1v3_full_option" + chosen_env = "p1v4_442_single" with patch( "homeassistant.components.plugwise.coordinator.Smile", autospec=True ) as smile_mock: smile = smile_mock.return_value - smile.gateway_id = "e950c7d5e1ee407a858e2a8b5016c8b3" + smile.gateway_id = "a455b61e52394b2db5081ce025a430f3" smile.heater_id = None - smile.smile_version = "3.3.9" + smile.smile_version = "4.4.2" smile.smile_type = "power" smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Smile P1" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -314,10 +290,7 @@ def mock_smile_p1_2() -> Generator[None, MagicMock, None]: smile.smile_hostname = "smile98765" smile.smile_model = "Gateway" smile.smile_name = "Smile P1" - smile.connect.return_value = True - - smile.notifications = _read_json(chosen_env, "notifications") all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( all_data["gateway"], all_data["devices"] @@ -342,7 +315,6 @@ def mock_stretch() -> Generator[None, MagicMock, None]: smile.smile_hostname = "stretch98765" smile.smile_model = "Gateway" smile.smile_name = "Stretch" - smile.connect.return_value = True all_data = _read_json(chosen_env, "all_data") smile.async_update.return_value = PlugwiseData( diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json index f97182782e6..47c8e4dceb0 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -28,7 +28,7 @@ "model": "Plug", "name": "Playstation Smart Plug", "sensors": { - "electricity_consumed": 82.6, + "electricity_consumed": 84.1, "electricity_consumed_interval": 8.6, "electricity_produced": 0.0, "electricity_produced_interval": 0.0 diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/device_list.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/device_list.json deleted file mode 100644 index 104a723e463..00000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/device_list.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - "fe799307f1624099878210aa0b9f1475", - "90986d591dcd426cae3ec3e8111ff730", - "df4a4a8169904cdb9c03d61a21f42140", - "b310b72a0e354bfab43089919b9a88bf", - "a2c3583e0a6349358998b760cea82d2a", - "b59bcebaf94b499ea7d46e4a66fb62d8", - "d3da73bde12a47d5a6b8f9dad971f2ec", - "21f2b542c49845e6bb416884c55778d6", - "78d1126fc4c743db81b61c20e88342a7", - "cd0ddb54ef694e11ac18ed1cbce5dbbd", - "4a810418d5394b3f82727340b91ba740", - "02cf28bfec924855854c544690a609ef", - "a28f588dc4a049a483fd03a30361ad3a", - "6a3bf693d05e48e0b460c815a4fdd09d", - "680423ff840043738f42cc7f1ff97a36", - "f1fee6043d3642a9b0a65297455f008e", - "675416a629f343c495449970e2ca37b5", - "e7693eb9582644e5b865dba8d4447cf1" -] diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json deleted file mode 100644 index 8749be4c345..00000000000 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/notifications.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "af82e4ccf9c548528166d38e560662a4": { - "warning": "Node Plug (with MAC address 000D6F000D13CB01, in room 'n.a.') has been unreachable since 23:03 2020-01-18. Please check the connection and restart the device." - } -} diff --git a/tests/components/plugwise/fixtures/anna_heatpump_heating/device_list.json b/tests/components/plugwise/fixtures/anna_heatpump_heating/device_list.json deleted file mode 100644 index ffb8cf62575..00000000000 --- a/tests/components/plugwise/fixtures/anna_heatpump_heating/device_list.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "015ae9ea3f964e668e490fa39da3870b", - "1cbf783bb11e4a7c8a6843dee3a86927", - "3cb70739631c4d17a86b8b12e8a5161b" -] diff --git a/tests/components/plugwise/fixtures/anna_heatpump_heating/notifications.json b/tests/components/plugwise/fixtures/anna_heatpump_heating/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/anna_heatpump_heating/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/m_adam_cooling/device_list.json b/tests/components/plugwise/fixtures/m_adam_cooling/device_list.json deleted file mode 100644 index 35fe367eb34..00000000000 --- a/tests/components/plugwise/fixtures/m_adam_cooling/device_list.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - "da224107914542988a88561b4452b0f6", - "056ee145a816487eaa69243c3280f8bf", - "e2f4322d57924fa090fbbc48b3a140dc", - "ad4838d7d35c4d6ea796ee12ae5aedf8", - "1772a4ea304041adb83f357b751341ff", - "e8ef2a01ed3b4139a53bf749204fe6b4" -] diff --git a/tests/components/plugwise/fixtures/m_adam_cooling/notifications.json b/tests/components/plugwise/fixtures/m_adam_cooling/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/m_adam_cooling/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/m_adam_heating/device_list.json b/tests/components/plugwise/fixtures/m_adam_heating/device_list.json deleted file mode 100644 index 35fe367eb34..00000000000 --- a/tests/components/plugwise/fixtures/m_adam_heating/device_list.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - "da224107914542988a88561b4452b0f6", - "056ee145a816487eaa69243c3280f8bf", - "e2f4322d57924fa090fbbc48b3a140dc", - "ad4838d7d35c4d6ea796ee12ae5aedf8", - "1772a4ea304041adb83f357b751341ff", - "e8ef2a01ed3b4139a53bf749204fe6b4" -] diff --git a/tests/components/plugwise/fixtures/m_adam_heating/notifications.json b/tests/components/plugwise/fixtures/m_adam_heating/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/m_adam_heating/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/m_adam_jip/all_data.json b/tests/components/plugwise/fixtures/m_adam_jip/all_data.json index 915f438c105..378a5e0a760 100644 --- a/tests/components/plugwise/fixtures/m_adam_jip/all_data.json +++ b/tests/components/plugwise/fixtures/m_adam_jip/all_data.json @@ -67,7 +67,7 @@ "name": "Tom Slaapkamer", "sensors": { "setpoint": 13.0, - "temperature": 24.3, + "temperature": 24.2, "temperature_difference": 1.7, "valve_position": 0.0 }, diff --git a/tests/components/plugwise/fixtures/m_adam_jip/device_list.json b/tests/components/plugwise/fixtures/m_adam_jip/device_list.json deleted file mode 100644 index 049845bc828..00000000000 --- a/tests/components/plugwise/fixtures/m_adam_jip/device_list.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - "b5c2386c6f6342669e50fe49dd05b188", - "e4684553153b44afbef2200885f379dc", - "a6abc6a129ee499c88a4d420cc413b47", - "1346fbd8498d4dbcab7e18d51b771f3d", - "833de10f269c4deab58fb9df69901b4e", - "6f3e9d7084214c21b9dfa46f6eeb8700", - "f61f1a2535f54f52ad006a3d18e459ca", - "d4496250d0e942cfa7aea3476e9070d5", - "356b65335e274d769c338223e7af9c33", - "1da4d325838e4ad8aac12177214505c9", - "457ce8414de24596a2d5e7dbc9c7682f" -] diff --git a/tests/components/plugwise/fixtures/m_adam_jip/notifications.json b/tests/components/plugwise/fixtures/m_adam_jip/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/m_adam_jip/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/device_list.json b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/device_list.json deleted file mode 100644 index ffb8cf62575..00000000000 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/device_list.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "015ae9ea3f964e668e490fa39da3870b", - "1cbf783bb11e4a7c8a6843dee3a86927", - "3cb70739631c4d17a86b8b12e8a5161b" -] diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/notifications.json b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_idle/device_list.json b/tests/components/plugwise/fixtures/m_anna_heatpump_idle/device_list.json deleted file mode 100644 index ffb8cf62575..00000000000 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_idle/device_list.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "015ae9ea3f964e668e490fa39da3870b", - "1cbf783bb11e4a7c8a6843dee3a86927", - "3cb70739631c4d17a86b8b12e8a5161b" -] diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_idle/notifications.json b/tests/components/plugwise/fixtures/m_anna_heatpump_idle/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_idle/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json b/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json deleted file mode 100644 index 0a47893c077..00000000000 --- a/tests/components/plugwise/fixtures/p1v3_full_option/all_data.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "devices": { - "cd3e822288064775a7c4afcdd70bdda2": { - "binary_sensors": { - "plugwise_notification": false - }, - "dev_class": "gateway", - "firmware": "3.3.9", - "hardware": "AME Smile 2.0 board", - "location": "cd3e822288064775a7c4afcdd70bdda2", - "mac_address": "012345670001", - "model": "Gateway", - "name": "Smile P1", - "vendor": "Plugwise" - }, - "e950c7d5e1ee407a858e2a8b5016c8b3": { - "available": true, - "dev_class": "smartmeter", - "location": "cd3e822288064775a7c4afcdd70bdda2", - "model": "2M550E-1012", - "name": "P1", - "sensors": { - "electricity_consumed_off_peak_cumulative": 551.09, - "electricity_consumed_off_peak_interval": 0, - "electricity_consumed_off_peak_point": 0, - "electricity_consumed_peak_cumulative": 442.932, - "electricity_consumed_peak_interval": 0, - "electricity_consumed_peak_point": 0, - "electricity_produced_off_peak_cumulative": 154.491, - "electricity_produced_off_peak_interval": 0, - "electricity_produced_off_peak_point": 0, - "electricity_produced_peak_cumulative": 396.559, - "electricity_produced_peak_interval": 0, - "electricity_produced_peak_point": 2816, - "gas_consumed_cumulative": 584.85, - "gas_consumed_interval": 0.0, - "net_electricity_cumulative": 442.972, - "net_electricity_point": -2816 - }, - "vendor": "ISKRAEMECO" - } - }, - "gateway": { - "gateway_id": "cd3e822288064775a7c4afcdd70bdda2", - "item_count": 31, - "notifications": {}, - "smile_name": "Smile P1" - } -} diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/device_list.json b/tests/components/plugwise/fixtures/p1v3_full_option/device_list.json deleted file mode 100644 index 8af35165c7e..00000000000 --- a/tests/components/plugwise/fixtures/p1v3_full_option/device_list.json +++ /dev/null @@ -1 +0,0 @@ -["cd3e822288064775a7c4afcdd70bdda2", "e950c7d5e1ee407a858e2a8b5016c8b3"] diff --git a/tests/components/plugwise/fixtures/p1v3_full_option/notifications.json b/tests/components/plugwise/fixtures/p1v3_full_option/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/p1v3_full_option/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/fixtures/p1v4_442_single/all_data.json b/tests/components/plugwise/fixtures/p1v4_442_single/all_data.json new file mode 100644 index 00000000000..318035a5d2c --- /dev/null +++ b/tests/components/plugwise/fixtures/p1v4_442_single/all_data.json @@ -0,0 +1,49 @@ +{ + "devices": { + "a455b61e52394b2db5081ce025a430f3": { + "binary_sensors": { + "plugwise_notification": false + }, + "dev_class": "gateway", + "firmware": "4.4.2", + "hardware": "AME Smile 2.0 board", + "location": "a455b61e52394b2db5081ce025a430f3", + "mac_address": "012345670001", + "model": "Gateway", + "name": "Smile P1", + "vendor": "Plugwise" + }, + "ba4de7613517478da82dd9b6abea36af": { + "available": true, + "dev_class": "smartmeter", + "location": "a455b61e52394b2db5081ce025a430f3", + "model": "KFM5KAIFA-METER", + "name": "P1", + "sensors": { + "electricity_consumed_off_peak_cumulative": 17643.423, + "electricity_consumed_off_peak_interval": 15, + "electricity_consumed_off_peak_point": 486, + "electricity_consumed_peak_cumulative": 13966.608, + "electricity_consumed_peak_interval": 0, + "electricity_consumed_peak_point": 0, + "electricity_phase_one_consumed": 486, + "electricity_phase_one_produced": 0, + "electricity_produced_off_peak_cumulative": 0.0, + "electricity_produced_off_peak_interval": 0, + "electricity_produced_off_peak_point": 0, + "electricity_produced_peak_cumulative": 0.0, + "electricity_produced_peak_interval": 0, + "electricity_produced_peak_point": 0, + "net_electricity_cumulative": 31610.031, + "net_electricity_point": 486 + }, + "vendor": "SHENZHEN KAIFA TECHNOLOGY \uff08CHENGDU\uff09 CO., LTD." + } + }, + "gateway": { + "gateway_id": "a455b61e52394b2db5081ce025a430f3", + "item_count": 31, + "notifications": {}, + "smile_name": "Smile P1" + } +} diff --git a/tests/components/plugwise/fixtures/p1v4_442_triple/device_list.json b/tests/components/plugwise/fixtures/p1v4_442_triple/device_list.json deleted file mode 100644 index 7b301f50924..00000000000 --- a/tests/components/plugwise/fixtures/p1v4_442_triple/device_list.json +++ /dev/null @@ -1 +0,0 @@ -["03e65b16e4b247a29ae0d75a78cb492e", "b82b6b3322484f2ea4e25e0bd5f3d61f"] diff --git a/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json b/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json deleted file mode 100644 index 49db062035a..00000000000 --- a/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "97a04c0c263049b29350a660b4cdd01e": { - "warning": "The Smile P1 is not connected to a smart meter." - } -} diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json index 6b1012b0d87..f42cde65b39 100644 --- a/tests/components/plugwise/fixtures/stretch_v31/all_data.json +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -23,7 +23,7 @@ "electricity_produced": 0.0 }, "switches": { - "lock": false, + "lock": true, "relay": true }, "vendor": "Plugwise", diff --git a/tests/components/plugwise/fixtures/stretch_v31/device_list.json b/tests/components/plugwise/fixtures/stretch_v31/device_list.json deleted file mode 100644 index b2c839ae9d3..00000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/device_list.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - "0000aaaa0000aaaa0000aaaa0000aa00", - "5871317346d045bc9f6b987ef25ee638", - "e1c884e7dede431dadee09506ec4f859", - "aac7b735042c4832ac9ff33aae4f453b", - "cfe95cf3de1948c0b8955125bf754614", - "059e4d03c7a34d278add5c7a4a781d19", - "d950b314e9d8499f968e6db8d82ef78c", - "d03738edfcc947f7b8f4573571d90d2d" -] diff --git a/tests/components/plugwise/fixtures/stretch_v31/notifications.json b/tests/components/plugwise/fixtures/stretch_v31/notifications.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/tests/components/plugwise/fixtures/stretch_v31/notifications.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/tests/components/plugwise/snapshots/test_diagnostics.ambr b/tests/components/plugwise/snapshots/test_diagnostics.ambr index c2bbea9418a..0fa3df4e660 100644 --- a/tests/components/plugwise/snapshots/test_diagnostics.ambr +++ b/tests/components/plugwise/snapshots/test_diagnostics.ambr @@ -30,7 +30,7 @@ 'model': 'Plug', 'name': 'Playstation Smart Plug', 'sensors': dict({ - 'electricity_consumed': 82.6, + 'electricity_consumed': 84.1, 'electricity_consumed_interval': 8.6, 'electricity_produced': 0.0, 'electricity_produced_interval': 0.0, diff --git a/tests/components/plugwise/test_sensor.py b/tests/components/plugwise/test_sensor.py index 46f31e1458f..d1df8454f4e 100644 --- a/tests/components/plugwise/test_sensor.py +++ b/tests/components/plugwise/test_sensor.py @@ -111,38 +111,28 @@ async def test_anna_as_smt_climate_sensor_entities( assert float(state.state) == 86.0 -async def test_anna_climate_sensor_entities( - hass: HomeAssistant, mock_smile_anna: MagicMock, init_integration: MockConfigEntry -) -> None: - """Test creation of climate related sensor entities.""" - state = hass.states.get("sensor.opentherm_outdoor_air_temperature") - assert state - assert float(state.state) == 3.0 - - async def test_p1_dsmr_sensor_entities( hass: HomeAssistant, mock_smile_p1: MagicMock, init_integration: MockConfigEntry ) -> None: """Test creation of power related sensor entities.""" state = hass.states.get("sensor.p1_net_electricity_point") assert state - assert float(state.state) == -2816.0 + assert int(state.state) == 486 state = hass.states.get("sensor.p1_electricity_consumed_off_peak_cumulative") assert state - assert float(state.state) == 551.09 + assert float(state.state) == 17643.423 state = hass.states.get("sensor.p1_electricity_produced_peak_point") assert state - assert float(state.state) == 2816.0 + assert int(state.state) == 0 state = hass.states.get("sensor.p1_electricity_consumed_peak_cumulative") assert state - assert float(state.state) == 442.932 + assert float(state.state) == 13966.608 state = hass.states.get("sensor.p1_gas_consumed_cumulative") - assert state - assert float(state.state) == 584.85 + assert not state async def test_p1_3ph_dsmr_sensor_entities( @@ -151,15 +141,15 @@ async def test_p1_3ph_dsmr_sensor_entities( """Test creation of power related sensor entities.""" state = hass.states.get("sensor.p1_electricity_phase_one_consumed") assert state - assert float(state.state) == 1763.0 + assert int(state.state) == 1763 state = hass.states.get("sensor.p1_electricity_phase_two_consumed") assert state - assert float(state.state) == 1703.0 + assert int(state.state) == 1703 state = hass.states.get("sensor.p1_electricity_phase_three_consumed") assert state - assert float(state.state) == 2080.0 + assert int(state.state) == 2080 entity_id = "sensor.p1_voltage_phase_one" state = hass.states.get(entity_id) From 18bb33e2d72e8288e3be802f11bf3b9c3633d3d2 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:15:47 +0100 Subject: [PATCH 1473/1691] Update pyOpenSSL to 24.1.0 (#114165) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 3 +-- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 49b7b032dcb..462f4fa0e75 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -45,7 +45,7 @@ pip>=21.3.1 psutil-home-assistant==0.0.1 PyJWT==2.8.0 PyNaCl==1.5.0 -pyOpenSSL==24.0.0 +pyOpenSSL==24.1.0 pyserial==3.5 python-slugify==8.0.4 PyTurboJPEG==1.7.1 diff --git a/pyproject.toml b/pyproject.toml index a869024fb75..88cd4f8897b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,8 +50,7 @@ dependencies = [ # PyJWT has loose dependency. We want the latest one. "cryptography==42.0.5", "Pillow==10.2.0", - # pyOpenSSL 23.2.0 is required to work with cryptography 41+ - "pyOpenSSL==24.0.0", + "pyOpenSSL==24.1.0", "orjson==3.9.15", "packaging>=23.1", "pip>=21.3.1", diff --git a/requirements.txt b/requirements.txt index 82ff6ba3ce4..fc6d986268a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ lru-dict==1.3.0 PyJWT==2.8.0 cryptography==42.0.5 Pillow==10.2.0 -pyOpenSSL==24.0.0 +pyOpenSSL==24.1.0 orjson==3.9.15 packaging>=23.1 pip>=21.3.1 From 5d293f92b2ec46fe121030db887961f65185078c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:22:55 +0100 Subject: [PATCH 1474/1691] Update yamllint to 1.35.1 (#114166) --- .pre-commit-config.yaml | 2 +- requirements_test_pre_commit.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b96b5ee2aa..34c04e634b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - --branch=master - --branch=rc - repo: https://github.com/adrienverge/yamllint.git - rev: v1.32.0 + rev: v1.35.1 hooks: - id: yamllint - repo: https://github.com/pre-commit/mirrors-prettier diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 32d3f9e0c8b..2a2135ef903 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -2,4 +2,4 @@ codespell==2.2.2 ruff==0.2.1 -yamllint==1.32.0 +yamllint==1.35.1 From 1b6d1d58ec18227efd0079c1a74a088041ebe78f Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:23:26 +0100 Subject: [PATCH 1475/1691] Update pytest-cov to 5.0.0 (#114160) * Update pytest-cov to 5.0.0 * Update warnings filter --- pyproject.toml | 3 --- requirements_test.txt | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 88cd4f8897b..dbf58f7b246 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -475,9 +475,6 @@ filterwarnings = [ # https://github.com/foxel/python_ndms2_client/issues/6 - v0.1.3 # https://github.com/foxel/python_ndms2_client/pull/8 "ignore:'telnetlib' is deprecated and slated for removal in Python 3.13:DeprecationWarning:ndms2_client.connection", - # https://github.com/pytest-dev/pytest-cov/issues/557 - v4.1.0 - # Should resolve itself once pytest-xdist 4.0 is released and the option is removed - "ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated:DeprecationWarning:xdist.plugin", # -- fixed, waiting for release / update # https://github.com/mkmer/AIOAladdinConnect/commit/8851fff4473d80d70ac518db2533f0fbef63b69c - >=0.2.0 diff --git a/requirements_test.txt b/requirements_test.txt index f3f570f8f26..8bdde897c8d 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -19,7 +19,7 @@ pylint-per-file-ignores==1.3.2 pipdeptree==2.16.1 pytest-asyncio==0.23.6 pytest-aiohttp==1.0.5 -pytest-cov==4.1.0 +pytest-cov==5.0.0 pytest-freezer==0.4.8 pytest-github-actions-annotate-failures==0.2.0 pytest-socket==0.7.0 From e88ade716d45319e53e21a0f1b6af036341469d9 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 25 Mar 2024 11:25:46 +0100 Subject: [PATCH 1476/1691] Allow debugging tests by default in vscode (#114065) --- .devcontainer/devcontainer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 553f3bbdf0e..83aa88140cc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,6 +21,7 @@ ], // Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json "settings": { + "python.experiments.optOutFrom": ["pythonTestAdapter"], "python.pythonPath": "/usr/local/bin/python", "python.testing.pytestArgs": ["--no-cov"], "editor.formatOnPaste": false, From 188dbfbd2a0db7a592d754429e421e4427a87167 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:27:47 +0100 Subject: [PATCH 1477/1691] Update typing-extensions to 4.11.0rc1 (#114116) --- homeassistant/components/zwave_js/config_flow.py | 2 +- homeassistant/config_entries.py | 14 +++++++------- homeassistant/helpers/data_entry_flow.py | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index ca05dc2117b..3470f64f79f 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -183,7 +183,7 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC): @property @abstractmethod - def flow_manager(self) -> FlowManager[ConfigFlowResult, str]: + def flow_manager(self) -> FlowManager[ConfigFlowResult]: """Return the flow manager of the flow.""" async def async_step_install_addon( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 2c6cb18deef..9ea049c4616 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1071,7 +1071,7 @@ class FlowCancelledError(Exception): """Error to indicate that a flow has been cancelled.""" -class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): +class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): """Manage all the config entry flows that are in progress.""" _flow_result = ConfigFlowResult @@ -1197,7 +1197,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str async def async_finish_flow( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], + flow: data_entry_flow.FlowHandler[ConfigFlowResult], result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish a config flow and add an entry.""" @@ -1319,7 +1319,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str async def async_post_init( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], + flow: data_entry_flow.FlowHandler[ConfigFlowResult], result: ConfigFlowResult, ) -> None: """After a flow is initialised trigger new flow notifications.""" @@ -1987,7 +1987,7 @@ def _async_abort_entries_match( raise data_entry_flow.AbortFlow("already_configured") -class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult, str]): +class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult]): """Base class for config and option flows.""" _flow_result = ConfigFlowResult @@ -2339,7 +2339,7 @@ class ConfigFlow(ConfigEntryBaseFlow): return self.async_abort(reason=reason) -class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): +class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): """Flow to set options for a configuration entry.""" _flow_result = ConfigFlowResult @@ -2369,7 +2369,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): async def async_finish_flow( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], + flow: data_entry_flow.FlowHandler[ConfigFlowResult], result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish an options flow and update options for configuration entry. @@ -2391,7 +2391,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): return result async def _async_setup_preview( - self, flow: data_entry_flow.FlowHandler[ConfigFlowResult, str] + self, flow: data_entry_flow.FlowHandler[ConfigFlowResult] ) -> None: """Set up preview for an option flow handler.""" entry = self._async_get_config_entry(flow.handler) diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 1edeb28d88f..2adab32195b 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -18,7 +18,7 @@ from . import config_validation as cv _FlowManagerT = TypeVar( "_FlowManagerT", - bound="data_entry_flow.FlowManager[Any]", + bound=data_entry_flow.FlowManager[Any], default=data_entry_flow.FlowManager, ) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 462f4fa0e75..03c17397789 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -53,7 +53,7 @@ pyudev==0.23.2 PyYAML==6.0.1 requests==2.31.0 SQLAlchemy==2.0.28 -typing-extensions>=4.10.0,<5.0 +typing-extensions>=4.11.0rc1,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 voluptuous-serialize==2.6.0 diff --git a/pyproject.toml b/pyproject.toml index dbf58f7b246..d6367692b01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ dependencies = [ "PyYAML==6.0.1", "requests==2.31.0", "SQLAlchemy==2.0.28", - "typing-extensions>=4.10.0,<5.0", + "typing-extensions>=4.11.0rc1,<5.0", "ulid-transform==0.9.0", # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 # Temporary setting an upper bound, to prevent compat issues with urllib3>=2 diff --git a/requirements.txt b/requirements.txt index fc6d986268a..acd9b2b51ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ python-slugify==8.0.4 PyYAML==6.0.1 requests==2.31.0 SQLAlchemy==2.0.28 -typing-extensions>=4.10.0,<5.0 +typing-extensions>=4.11.0rc1,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 voluptuous==0.13.1 From ada781025b27c4a9ad7e32c4a80329381dd240f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 00:28:26 -1000 Subject: [PATCH 1478/1691] Avoid homekit random id generation for existing entries (#114144) --- homeassistant/components/homekit/__init__.py | 3 +++ homeassistant/components/homekit/accessories.py | 5 ++++- homeassistant/components/homekit/const.py | 1 + tests/components/homekit/test_accessories.py | 5 ++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 7d1297e1307..51b3f5f376d 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -12,6 +12,7 @@ import socket from typing import Any, cast from aiohttp import web +from pyhap import util as pyhap_util from pyhap.characteristic import Characteristic from pyhap.const import STANDALONE_AID from pyhap.loader import get_loader @@ -580,6 +581,8 @@ class HomeKit: self.driver.load() return True + # If there is no persist file, we need to generate a mac + self.driver.state.mac = pyhap_util.generate_mac() return False async def async_reset_accessories(self, entity_ids: Iterable[str]) -> None: diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index f39bf32744a..63575189946 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -72,6 +72,7 @@ from .const import ( CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, + EMPTY_MAC, EVENT_HOMEKIT_CHANGED, HK_CHARGING, HK_NOT_CHARGABLE, @@ -683,7 +684,9 @@ class HomeDriver(AccessoryDriver): # type: ignore[misc] **kwargs: Any, ) -> None: """Initialize a AccessoryDriver object.""" - super().__init__(**kwargs) + # Always set an empty mac of pyhap will incur + # the cost of generating a new one for every driver + super().__init__(**kwargs, mac=EMPTY_MAC) self.hass = hass self.entry_id = entry_id self._bridge_name = bridge_name diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 5a7ee1d9576..149d2aedd67 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -10,6 +10,7 @@ PERSIST_LOCK_DATA = f"{DOMAIN}_persist_lock" HOMEKIT_FILE = ".homekit.state" SHUTDOWN_TIMEOUT = 30 CONF_ENTRY_INDEX = "index" +EMPTY_MAC = "00:00:00:00:00:00" # ### Codecs #### VIDEO_CODEC_COPY = "copy" diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index a148ab9f61c..5d8b940a7cd 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -28,6 +28,7 @@ from homeassistant.components.homekit.const import ( CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, + EMPTY_MAC, MANUFACTURER, SERV_ACCESSORY_INFO, ) @@ -747,7 +748,9 @@ def test_home_driver(iid_storage) -> None: persist_file=path, ) - mock_driver.assert_called_with(address=ip_address, port=port, persist_file=path) + mock_driver.assert_called_with( + address=ip_address, port=port, persist_file=path, mac=EMPTY_MAC + ) driver.state = Mock(pincode=pin, paired=False) xhm_uri_mock = Mock(return_value="X-HM://0") driver.accessory = Mock(display_name="any", xhm_uri=xhm_uri_mock) From 3ba1b82723ab593b63f510b8922e55212943066b Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 25 Mar 2024 11:30:50 +0100 Subject: [PATCH 1479/1691] Remove hourly weather entity from met_eireann (#112449) --- .../components/met_eireann/weather.py | 22 +++++++------------ tests/components/met_eireann/test_weather.py | 16 -------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/met_eireann/weather.py b/homeassistant/components/met_eireann/weather.py index 7fa94920c74..ed226714c1d 100644 --- a/homeassistant/components/met_eireann/weather.py +++ b/homeassistant/components/met_eireann/weather.py @@ -53,17 +53,15 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][config_entry.entry_id] entity_registry = er.async_get(hass) - entities = [MetEireannWeather(coordinator, config_entry.data, False)] - - # Add hourly entity to legacy config entries - if entity_registry.async_get_entity_id( + # Remove hourly entity from legacy config entries + if entity_id := entity_registry.async_get_entity_id( WEATHER_DOMAIN, DOMAIN, _calculate_unique_id(config_entry.data, True), ): - entities.append(MetEireannWeather(coordinator, config_entry.data, True)) + entity_registry.async_remove(entity_id) - async_add_entities(entities) + async_add_entities([MetEireannWeather(coordinator, config_entry.data)]) def _calculate_unique_id(config: MappingProxyType[str, Any], hourly: bool) -> str: @@ -93,19 +91,15 @@ class MetEireannWeather( self, coordinator: DataUpdateCoordinator[MetEireannWeatherData], config: MappingProxyType[str, Any], - hourly: bool, ) -> None: """Initialise the platform with a data instance and site.""" super().__init__(coordinator) - self._attr_unique_id = _calculate_unique_id(config, hourly) + self._attr_unique_id = _calculate_unique_id(config, False) self._config = config - self._hourly = hourly - name_appendix = " Hourly" if hourly else "" if (name := self._config.get(CONF_NAME)) is not None: - self._attr_name = f"{name}{name_appendix}" + self._attr_name = name else: - self._attr_name = f"{DEFAULT_NAME}{name_appendix}" - self._attr_entity_registry_enabled_default = not hourly + self._attr_name = DEFAULT_NAME self._attr_device_info = DeviceInfo( name="Forecast", entry_type=DeviceEntryType.SERVICE, @@ -182,7 +176,7 @@ class MetEireannWeather( @property def forecast(self) -> list[Forecast]: """Return the forecast array.""" - return self._forecast(self._hourly) + return self._forecast(False) @callback def _async_forecast_daily(self) -> list[Forecast]: diff --git a/tests/components/met_eireann/test_weather.py b/tests/components/met_eireann/test_weather.py index 1c938f97574..a660c18f7b3 100644 --- a/tests/components/met_eireann/test_weather.py +++ b/tests/components/met_eireann/test_weather.py @@ -45,22 +45,6 @@ async def test_new_config_entry( assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 1 -async def test_legacy_config_entry( - hass: HomeAssistant, entity_registry: er.EntityRegistry, mock_weather -) -> None: - """Test the expected entities are created.""" - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "10-20-hourly", - ) - await setup_config_entry(hass) - assert len(hass.states.async_entity_ids("weather")) == 2 - - entry = hass.config_entries.async_entries()[0] - assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 2 - - async def test_weather(hass: HomeAssistant, mock_weather) -> None: """Test weather entity.""" await setup_config_entry(hass) From 8ae2fefcf3be7d4df1022835ffca0e9651fcc7f8 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:32:38 +0100 Subject: [PATCH 1480/1691] Update types packages (#114164) --- requirements_test.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8bdde897c8d..e577b870920 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -34,21 +34,21 @@ requests-mock==1.11.0 respx==0.21.0 syrupy==4.6.1 tqdm==4.66.2 -types-aiofiles==23.2.0.20240106 +types-aiofiles==23.2.0.20240311 types-atomicwrites==1.4.5.1 -types-croniter==1.0.6 -types-beautifulsoup4==4.12.0.20240106 +types-croniter==2.0.0.20240321 +types-beautifulsoup4==4.12.0.20240229 types-caldav==1.3.0.20240106 types-chardet==0.1.5 -types-decorator==5.1.8.20240106 -types-paho-mqtt==1.6.0.20240106 -types-pillow==10.2.0.20240111 +types-decorator==5.1.8.20240310 +types-paho-mqtt==1.6.0.20240321 +types-pillow==10.2.0.20240324 types-protobuf==4.24.0.20240106 -types-psutil==5.9.5.20240106 -types-python-dateutil==2.8.19.20240106 -types-python-slugify==8.0.2.20240127 -types-pytz==2023.3.1.1 -types-PyYAML==6.0.12.12 +types-psutil==5.9.5.20240316 +types-python-dateutil==2.9.0.20240316 +types-python-slugify==8.0.2.20240310 +types-pytz==2024.1.0.20240203 +types-PyYAML==6.0.12.20240311 types-requests==2.31.0.3 types-xmltodict==0.13.0.3 uv==0.1.24 From 9b7cc088bef8a6974197fb854882acca18489c08 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:32:50 +0100 Subject: [PATCH 1481/1691] Fix spelling [runtime] (#114169) --- homeassistant/components/ezviz/button.py | 4 ++-- homeassistant/components/ezviz/light.py | 4 ++-- homeassistant/components/freebox/home_base.py | 4 ++-- homeassistant/components/lacrosse_view/sensor.py | 12 +++++++----- homeassistant/components/mqtt/lawn_mower.py | 2 +- tests/components/backup/test_manager.py | 4 ++-- tests/components/mqtt/test_lawn_mower.py | 4 ++-- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/ezviz/button.py b/homeassistant/components/ezviz/button.py index a4c101a81bc..3c89677da09 100644 --- a/homeassistant/components/ezviz/button.py +++ b/homeassistant/components/ezviz/button.py @@ -82,9 +82,9 @@ async def async_setup_entry( async_add_entities( EzvizButtonEntity(coordinator, camera, entity_description) for camera in coordinator.data - for capibility, value in coordinator.data[camera]["supportExt"].items() + for capability, value in coordinator.data[camera]["supportExt"].items() for entity_description in BUTTON_ENTITIES - if capibility == entity_description.supported_ext + if capability == entity_description.supported_ext if value == "1" ) diff --git a/homeassistant/components/ezviz/light.py b/homeassistant/components/ezviz/light.py index 5c33fbee02a..c35b53b47b7 100644 --- a/homeassistant/components/ezviz/light.py +++ b/homeassistant/components/ezviz/light.py @@ -36,8 +36,8 @@ async def async_setup_entry( async_add_entities( EzvizLight(coordinator, camera) for camera in coordinator.data - for capibility, value in coordinator.data[camera]["supportExt"].items() - if capibility == str(SupportExt.SupportAlarmLight.value) + for capability, value in coordinator.data[camera]["supportExt"].items() + if capability == str(SupportExt.SupportAlarmLight.value) if value == "1" ) diff --git a/homeassistant/components/freebox/home_base.py b/homeassistant/components/freebox/home_base.py index ba7cb69fb99..129186fd50b 100644 --- a/homeassistant/components/freebox/home_base.py +++ b/homeassistant/components/freebox/home_base.py @@ -129,9 +129,9 @@ class FreeboxHomeEntity(Entity): if self._remove_signal_update is not None: self._remove_signal_update() - def remove_signal_update(self, dispacher: Callable[[], None]) -> None: + def remove_signal_update(self, dispatcher: Callable[[], None]) -> None: """Register state update callback.""" - self._remove_signal_update = dispacher + self._remove_signal_update = dispatcher def get_value(self, ep_type: str, name: str): """Get the value.""" diff --git a/homeassistant/components/lacrosse_view/sensor.py b/homeassistant/components/lacrosse_view/sensor.py index 1a42aac9109..b2ad9672504 100644 --- a/homeassistant/components/lacrosse_view/sensor.py +++ b/homeassistant/components/lacrosse_view/sensor.py @@ -165,11 +165,13 @@ async def async_setup_entry( message = ( f"Unsupported sensor field: {field}\nPlease create an issue on " "GitHub." - " https://github.com/home-assistant/core/issues/new?assignees=&la" - "bels=&template=bug_report.yml&integration_name=LaCrosse%20View&integrat" - "ion_link=https://www.home-assistant.io/integrations/lacrosse_view/&addi" - f"tional_information=Field:%20{field}%0ASensor%20Model:%20{sensor.model}&" - f"title=LaCrosse%20View%20Unsupported%20sensor%20field:%20{field}" + " https://github.com/home-assistant/core/issues/new?assignees=" + "&labels=&template=bug_report.yml&integration_name=LaCrosse%20View" + "&integration_link=" + "https://www.home-assistant.io/integrations/lacrosse_view/" + "&additional_information=" + f"Field:%20{field}%0ASensor%20Model:%20{sensor.model}" + f"&title=LaCrosse%20View%20Unsupported%20sensor%20field:%20{field}" ) _LOGGER.warning(message) diff --git a/homeassistant/components/mqtt/lawn_mower.py b/homeassistant/components/mqtt/lawn_mower.py index 541ab9e4aa4..e6dc9125583 100644 --- a/homeassistant/components/mqtt/lawn_mower.py +++ b/homeassistant/components/mqtt/lawn_mower.py @@ -174,7 +174,7 @@ class MqttLawnMower(MqttEntity, LawnMowerEntity, RestoreEntity): self._attr_activity = LawnMowerActivity(payload) except ValueError: _LOGGER.error( - "Invalid activity for %s: '%s' (valid activies: %s)", + "Invalid activity for %s: '%s' (valid activities: %s)", self.entity_id, payload, [option.value for option in LawnMowerActivity], diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index 00581597524..516f8fc1bc4 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -107,11 +107,11 @@ async def test_load_backups_with_exception( """Test loading backups with exception.""" manager = BackupManager(hass) with patch("pathlib.Path.glob", return_value=[TEST_BACKUP.path]), patch( - "tarfile.open", side_effect=OSError("Test ecxeption") + "tarfile.open", side_effect=OSError("Test exception") ): await manager.load_backups() backups = await manager.get_backups() - assert f"Unable to read backup {TEST_BACKUP.path}: Test ecxeption" in caplog.text + assert f"Unable to read backup {TEST_BACKUP.path}: Test exception" in caplog.text assert backups == {} diff --git a/tests/components/mqtt/test_lawn_mower.py b/tests/components/mqtt/test_lawn_mower.py index a95c613cdc4..a258339e9cc 100644 --- a/tests/components/mqtt/test_lawn_mower.py +++ b/tests/components/mqtt/test_lawn_mower.py @@ -731,8 +731,8 @@ async def test_mqtt_payload_not_a_valid_activity_warning( await hass.async_block_till_done() assert ( - "Invalid activity for lawn_mower.test_lawn_mower: 'painting' (valid activies: ['error', 'paused', 'mowing', 'docked'])" - in caplog.text + "Invalid activity for lawn_mower.test_lawn_mower: 'painting' " + "(valid activities: ['error', 'paused', 'mowing', 'docked'])" in caplog.text ) From 81c34ac952d223855dfd89b4184c094338005fdb Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:33:00 +0100 Subject: [PATCH 1482/1691] Fix spelling [docstrings + comments] (#114168) --- homeassistant/components/alexa/capabilities.py | 2 +- homeassistant/components/android_ip_webcam/binary_sensor.py | 2 +- homeassistant/components/automation/__init__.py | 2 +- homeassistant/components/ccm15/climate.py | 2 +- homeassistant/components/cloud/google_config.py | 2 +- homeassistant/components/fjaraskupan/fan.py | 2 +- homeassistant/components/google_assistant/helpers.py | 2 +- homeassistant/components/google_assistant/http.py | 2 +- homeassistant/components/home_connect/switch.py | 2 +- homeassistant/components/image/media_source.py | 2 +- homeassistant/components/insteon/api/properties.py | 2 +- homeassistant/components/nest/config_flow.py | 2 +- homeassistant/components/nexia/binary_sensor.py | 2 +- homeassistant/components/rfxtrx/config_flow.py | 2 +- homeassistant/components/roon/config_flow.py | 2 +- homeassistant/components/script/__init__.py | 2 +- homeassistant/components/synology_dsm/__init__.py | 2 +- homeassistant/components/tado/const.py | 2 +- homeassistant/components/teslemetry/sensor.py | 2 +- homeassistant/components/trace/__init__.py | 2 +- homeassistant/components/velbus/__init__.py | 2 +- homeassistant/components/xiaomi_aqara/config_flow.py | 2 +- homeassistant/components/zwave_js/api.py | 4 ++-- tests/components/calendar/test_trigger.py | 2 +- tests/components/fritz/test_sensor.py | 4 ++-- tests/components/hassio/test_ingress.py | 2 +- tests/components/homekit/test_type_thermostats.py | 2 +- tests/components/mystrom/test_init.py | 2 +- tests/components/nest/test_api.py | 4 ++-- tests/components/nest/test_init.py | 2 +- tests/components/recorder/test_filters_with_entityfilter.py | 4 ++-- .../recorder/test_filters_with_entityfilter_schema_37.py | 4 ++-- tests/components/recorder/test_init.py | 2 +- tests/components/script/test_init.py | 2 +- tests/components/valve/test_init.py | 2 +- tests/components/wemo/entity_test_helpers.py | 2 +- tests/components/wemo/test_fan.py | 2 +- tests/components/wemo/test_light_bridge.py | 2 +- tests/components/wemo/test_light_dimmer.py | 2 +- tests/components/wemo/test_switch.py | 2 +- tests/helpers/test_condition.py | 2 +- 41 files changed, 46 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 1ef1b42b2f8..ecb7d5cb5a8 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1584,7 +1584,7 @@ class AlexaModeController(AlexaCapability): ) modes += 1 - # Alexa requiers at least 2 modes + # Alexa requires at least 2 modes if modes == 1: self._resource.add_mode(f"state.{PRESET_MODE_NA}", [PRESET_MODE_NA]) diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index 85edfc8a6a6..3ec03a59342 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -52,7 +52,7 @@ class IPWebcamBinarySensor(AndroidIPCamBaseEntity, BinarySensorEntity): @property def available(self) -> bool: - """Return avaibility if setting is enabled.""" + """Return availability if setting is enabled.""" return MOTION_ACTIVE in self.cam.enabled_sensors and super().available @property diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7b1b142719a..3942ca6368f 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -408,7 +408,7 @@ class BaseAutomationEntity(ToggleEntity, ABC): class UnavailableAutomationEntity(BaseAutomationEntity): """A non-functional automation entity with its state set to unavailable. - This class is instatiated when an automation fails to validate. + This class is instantiated when an automation fails to validate. """ _attr_should_poll = False diff --git a/homeassistant/components/ccm15/climate.py b/homeassistant/components/ccm15/climate.py index e364bf0af5c..b4038fbbf43 100644 --- a/homeassistant/components/ccm15/climate.py +++ b/homeassistant/components/ccm15/climate.py @@ -132,7 +132,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity): @property def available(self) -> bool: - """Return the avalability of the entity.""" + """Return the availability of the entity.""" return self.data is not None @property diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index c7ac0acd986..991ac88a44f 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -177,7 +177,7 @@ class CloudGoogleConfig(AbstractConfig): def get_local_user_id(self, webhook_id: Any) -> str: """Map webhook ID to a Home Assistant user ID. - Any action inititated by Google Assistant via the local SDK will be attributed + Any action initiated by Google Assistant via the local SDK will be attributed to the returned user ID. """ return self._user diff --git a/homeassistant/components/fjaraskupan/fan.py b/homeassistant/components/fjaraskupan/fan.py index 194a3c1d251..67514eaa411 100644 --- a/homeassistant/components/fjaraskupan/fan.py +++ b/homeassistant/components/fjaraskupan/fan.py @@ -86,7 +86,7 @@ class Fan(CoordinatorEntity[FjaraskupanCoordinator], FanEntity): async def async_set_percentage(self, percentage: int) -> None: """Set speed.""" - # Proactively update percentage to mange successive increases + # Proactively update percentage to manage successive increases self._percentage = percentage async with self.coordinator.async_connect_and_update() as device: diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index 4d0cedc5311..7f8f7a68ffa 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -165,7 +165,7 @@ class AbstractConfig(ABC): def get_local_user_id(self, webhook_id): """Map webhook ID to a Home Assistant user ID. - Any action inititated by Google Assistant via the local SDK will be attributed + Any action initiated by Google Assistant via the local SDK will be attributed to the returned user ID. Return None if no user id is found for the webhook_id. diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 580d0e4a749..95c5bafc2cc 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -124,7 +124,7 @@ class GoogleConfig(AbstractConfig): def get_local_user_id(self, webhook_id): """Map webhook ID to a Home Assistant user ID. - Any action inititated by Google Assistant via the local SDK will be attributed + Any action initiated by Google Assistant via the local SDK will be attributed to the returned user ID. Return None if no user id is found for the webhook_id. diff --git a/homeassistant/components/home_connect/switch.py b/homeassistant/components/home_connect/switch.py index 6dc308ac022..1239395af2b 100644 --- a/homeassistant/components/home_connect/switch.py +++ b/homeassistant/components/home_connect/switch.py @@ -92,7 +92,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity): """Power switch class for Home Connect.""" def __init__(self, device): - """Inititialize the entity.""" + """Initialize the entity.""" super().__init__(device, "Power") async def async_turn_on(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/image/media_source.py b/homeassistant/components/image/media_source.py index 5543ab2e887..e7f240aef5c 100644 --- a/homeassistant/components/image/media_source.py +++ b/homeassistant/components/image/media_source.py @@ -1,4 +1,4 @@ -"""Expose iamges as media sources.""" +"""Expose images as media sources.""" from __future__ import annotations diff --git a/homeassistant/components/insteon/api/properties.py b/homeassistant/components/insteon/api/properties.py index 80a76e482e5..7fac5439f56 100644 --- a/homeassistant/components/insteon/api/properties.py +++ b/homeassistant/components/insteon/api/properties.py @@ -74,7 +74,7 @@ def _read_only_schema(name, value): def get_schema(prop, name, groups): - """Return the correct shema type.""" + """Return the correct schema type.""" if prop.is_read_only: return _read_only_schema(name, prop.value) if name == RAMP_RATE_IN_SEC: diff --git a/homeassistant/components/nest/config_flow.py b/homeassistant/components/nest/config_flow.py index 092d6fe1f22..7b5f5d2c5fb 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -173,7 +173,7 @@ class NestFlowHandler( async def async_step_create_cloud_project( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: - """Handle initial step in app credentails flow.""" + """Handle initial step in app credentials flow.""" implementations = await config_entry_oauth2_flow.async_get_implementations( self.hass, self.DOMAIN ) diff --git a/homeassistant/components/nexia/binary_sensor.py b/homeassistant/components/nexia/binary_sensor.py index 51306584b7a..9b3d9cab986 100644 --- a/homeassistant/components/nexia/binary_sensor.py +++ b/homeassistant/components/nexia/binary_sensor.py @@ -41,7 +41,7 @@ async def async_setup_entry( class NexiaBinarySensor(NexiaThermostatEntity, BinarySensorEntity): - """Provices Nexia BinarySensor support.""" + """Provides Nexia BinarySensor support.""" def __init__(self, coordinator, thermostat, sensor_call, translation_key): """Initialize the nexia sensor.""" diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index aba6c08b06a..837ca554615 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -74,7 +74,7 @@ class DeviceData(TypedDict): def none_or_int(value: str | None, base: int) -> int | None: - """Check if strin is one otherwise convert to int.""" + """Check if string is one otherwise convert to int.""" if value is None: return None return int(value, base) diff --git a/homeassistant/components/roon/config_flow.py b/homeassistant/components/roon/config_flow.py index cb70187b988..d24cdb0c98d 100644 --- a/homeassistant/components/roon/config_flow.py +++ b/homeassistant/components/roon/config_flow.py @@ -155,7 +155,7 @@ class RoonConfigFlow(ConfigFlow, domain=DOMAIN): ) async def async_step_link(self, user_input=None): - """Handle linking and authenticting with the roon server.""" + """Handle linking and authenticating with the roon server.""" errors = {} if user_input is not None: # Do not authenticate if the host is already configured diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 954100c3a71..79ed69d2cdf 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -447,7 +447,7 @@ class BaseScriptEntity(ToggleEntity, ABC): class UnavailableScriptEntity(BaseScriptEntity): """A non-functional script entity with its state set to unavailable. - This class is instatiated when an script fails to validate. + This class is instantiated when an script fails to validate. """ _attr_should_poll = False diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 853459b00aa..a0c3a10774f 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -41,7 +41,7 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Synology DSM sensors.""" - # Migrate device indentifiers + # Migrate device identifiers dev_reg = dr.async_get(hass) devices: list[dr.DeviceEntry] = dr.async_entries_for_config_entry( dev_reg, entry.entry_id diff --git a/homeassistant/components/tado/const.py b/homeassistant/components/tado/const.py index 6f32eb1a05c..c62352a6d95 100644 --- a/homeassistant/components/tado/const.py +++ b/homeassistant/components/tado/const.py @@ -102,7 +102,7 @@ CONST_EXCLUSIVE_OVERLAY_GROUP = ( # Heat always comes first since we get the -# min and max tempatures for the zone from +# min and max temperatures for the zone from # it. # Heat is preferred as it generally has a lower minimum temperature ORDERED_KNOWN_TADO_MODES = [ diff --git a/homeassistant/components/teslemetry/sensor.py b/homeassistant/components/teslemetry/sensor.py index 7e6a35d03e1..6284a0e5368 100644 --- a/homeassistant/components/teslemetry/sensor.py +++ b/homeassistant/components/teslemetry/sensor.py @@ -476,7 +476,7 @@ class TeslemetryVehicleTimeSensorEntity(TeslemetryVehicleEntity, SensorEntity): @property def available(self) -> bool: - """Return the avaliability of the sensor.""" + """Return the availability of the sensor.""" return isinstance(self._value, int | float) and self._value > 0 diff --git a/homeassistant/components/trace/__init__.py b/homeassistant/components/trace/__init__.py index a7ad576e53d..17fdf20368a 100644 --- a/homeassistant/components/trace/__init__.py +++ b/homeassistant/components/trace/__init__.py @@ -175,7 +175,7 @@ async def async_restore_traces(hass: HomeAssistant) -> None: restored_traces = {} for key, traces in restored_traces.items(): - # Add stored traces in reversed order to priorize the newest traces + # Add stored traces in reversed order to prioritize the newest traces for json_trace in reversed(traces): if ( (stored_traces := _get_data(hass).get(key)) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 931888d0f99..ea03c4b15f1 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -54,7 +54,7 @@ async def velbus_connect_task( def _migrate_device_identifiers(hass: HomeAssistant, entry_id: str) -> None: - """Migrate old device indentifiers.""" + """Migrate old device identifiers.""" dev_reg = dr.async_get(hass) devices: list[dr.DeviceEntry] = dr.async_entries_for_config_entry(dev_reg, entry_id) for device in devices: diff --git a/homeassistant/components/xiaomi_aqara/config_flow.py b/homeassistant/components/xiaomi_aqara/config_flow.py index 7021b27e187..8f391c8ddf3 100644 --- a/homeassistant/components/xiaomi_aqara/config_flow.py +++ b/homeassistant/components/xiaomi_aqara/config_flow.py @@ -204,7 +204,7 @@ class XiaomiAqaraFlowHandler(ConfigFlow, domain=DOMAIN): valid_key = True if valid_key: - # format_mac, for a gateway the sid equels the mac address + # format_mac, for a gateway the sid equals the mac address mac_address = format_mac(self.sid) # set unique_id diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index b4c8967f0ad..dfb7442d678 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -2339,7 +2339,7 @@ async def websocket_subscribe_controller_statistics( client: Client, driver: Driver, ) -> None: - """Subsribe to the statistics updates for a controller.""" + """Subscribe to the statistics updates for a controller.""" @callback def async_cleanup() -> None: @@ -2434,7 +2434,7 @@ async def websocket_subscribe_node_statistics( msg: dict[str, Any], node: Node, ) -> None: - """Subsribe to the statistics updates for a node.""" + """Subscribe to the statistics updates for a node.""" @callback def async_cleanup() -> None: diff --git a/tests/components/calendar/test_trigger.py b/tests/components/calendar/test_trigger.py index 6f0ccdece28..050329cd855 100644 --- a/tests/components/calendar/test_trigger.py +++ b/tests/components/calendar/test_trigger.py @@ -57,7 +57,7 @@ class FakeSchedule: """Test fixture class for return events in a specific date range.""" def __init__(self, hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> None: - """Initiailize FakeSchedule.""" + """Initialize FakeSchedule.""" self.hass = hass self.freezer = freezer diff --git a/tests/components/fritz/test_sensor.py b/tests/components/fritz/test_sensor.py index 8ca7b510412..4427fc6961e 100644 --- a/tests/components/fritz/test_sensor.py +++ b/tests/components/fritz/test_sensor.py @@ -100,7 +100,7 @@ SENSOR_STATES: dict[str, dict[str, Any]] = { async def test_sensor_setup(hass: HomeAssistant, fc_class_mock, fh_class_mock) -> None: - """Test setup of Fritz!Tools sesnors.""" + """Test setup of Fritz!Tools sensors.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) entry.add_to_hass(hass) @@ -124,7 +124,7 @@ async def test_sensor_setup(hass: HomeAssistant, fc_class_mock, fh_class_mock) - async def test_sensor_update_fail( hass: HomeAssistant, fc_class_mock, fh_class_mock ) -> None: - """Test failed update of Fritz!Tools sesnors.""" + """Test failed update of Fritz!Tools sensors.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) entry.add_to_hass(hass) diff --git a/tests/components/hassio/test_ingress.py b/tests/components/hassio/test_ingress.py index f6eeecd5f90..27e99f7f596 100644 --- a/tests/components/hassio/test_ingress.py +++ b/tests/components/hassio/test_ingress.py @@ -310,7 +310,7 @@ async def test_ingress_missing_peername( aioclient_mock: AiohttpClientMocker, caplog: pytest.LogCaptureFixture, ) -> None: - """Test hadnling of missing peername.""" + """Test handling of missing peername.""" aioclient_mock.get( "http://127.0.0.1/ingress/lorem/ipsum", text="test", diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 5bcfe1c2038..ca2a02cb440 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -1959,7 +1959,7 @@ async def test_thermostat_with_no_off_after_recheck( async def test_thermostat_with_temp_clamps( hass: HomeAssistant, hk_driver, events ) -> None: - """Test that tempatures are clamped to valid values to prevent homekit crash.""" + """Test that temperatures are clamped to valid values to prevent homekit crash.""" entity_id = "climate.test" base_attrs = { ATTR_SUPPORTED_FEATURES: ClimateEntityFeature.TARGET_TEMPERATURE diff --git a/tests/components/mystrom/test_init.py b/tests/components/mystrom/test_init.py index f849fec7477..0c49eecf88d 100644 --- a/tests/components/mystrom/test_init.py +++ b/tests/components/mystrom/test_init.py @@ -26,7 +26,7 @@ async def init_integration( config_entry: MockConfigEntry, device_type: int, ) -> None: - """Inititialize integration for testing.""" + """Initialize integration for testing.""" with patch( "pymystrom.get_device_info", side_effect=AsyncMock(return_value=get_default_device_response(device_type)), diff --git a/tests/components/nest/test_api.py b/tests/components/nest/test_api.py index 31f5d03dc2b..3e0932c607d 100644 --- a/tests/components/nest/test_api.py +++ b/tests/components/nest/test_api.py @@ -3,7 +3,7 @@ There are two interesting cases to exercise that have different strategies for token refresh and for testing: - API based requests, tested using aioclient_mock -- Pub/sub subcriber initialization, intercepted with patch() +- Pub/sub subscriber initialization, intercepted with patch() The tests below exercise both cases during integration setup. """ @@ -75,7 +75,7 @@ async def test_auth( (method, url, data, headers) = calls[1] assert headers == {"Authorization": f"Bearer {FAKE_TOKEN}"} - # Verify the susbcriber was created with the correct credentials + # Verify the subscriber was created with the correct credentials assert len(new_subscriber_mock.mock_calls) == 1 assert captured_creds creds = captured_creds diff --git a/tests/components/nest/test_init.py b/tests/components/nest/test_init.py index 082f40094aa..2f11aed7969 100644 --- a/tests/components/nest/test_init.py +++ b/tests/components/nest/test_init.py @@ -161,7 +161,7 @@ async def test_subscriber_auth_failure( async def test_setup_missing_subscriber_id( hass: HomeAssistant, warning_caplog, setup_base_platform ) -> None: - """Test missing susbcriber id from configuration.""" + """Test missing subscriber id from configuration.""" await setup_base_platform() assert "Configuration option" in warning_caplog.text diff --git a/tests/components/recorder/test_filters_with_entityfilter.py b/tests/components/recorder/test_filters_with_entityfilter.py index d888a1de52e..1ee127a9989 100644 --- a/tests/components/recorder/test_filters_with_entityfilter.py +++ b/tests/components/recorder/test_filters_with_entityfilter.py @@ -536,7 +536,7 @@ async def test_same_entity_included_excluded_include_domain_wins( async def test_specificly_included_entity_always_wins( recorder_mock: Recorder, hass: HomeAssistant ) -> None: - """Test specificlly included entity always wins.""" + """Test specifically included entity always wins.""" filter_accept = { "media_player.test2", "media_player.test3", @@ -586,7 +586,7 @@ async def test_specificly_included_entity_always_wins( async def test_specificly_included_entity_always_wins_over_glob( recorder_mock: Recorder, hass: HomeAssistant ) -> None: - """Test specificlly included entity always wins over a glob.""" + """Test specifically included entity always wins over a glob.""" filter_accept = { "sensor.apc900va_status", "sensor.apc900va_battery_charge", diff --git a/tests/components/recorder/test_filters_with_entityfilter_schema_37.py b/tests/components/recorder/test_filters_with_entityfilter_schema_37.py index 6794827bd6a..f5eec10f805 100644 --- a/tests/components/recorder/test_filters_with_entityfilter_schema_37.py +++ b/tests/components/recorder/test_filters_with_entityfilter_schema_37.py @@ -553,7 +553,7 @@ async def test_same_entity_included_excluded_include_domain_wins( async def test_specificly_included_entity_always_wins( legacy_recorder_mock: Recorder, hass: HomeAssistant ) -> None: - """Test specificlly included entity always wins.""" + """Test specifically included entity always wins.""" filter_accept = { "media_player.test2", "media_player.test3", @@ -603,7 +603,7 @@ async def test_specificly_included_entity_always_wins( async def test_specificly_included_entity_always_wins_over_glob( legacy_recorder_mock: Recorder, hass: HomeAssistant ) -> None: - """Test specificlly included entity always wins over a glob.""" + """Test specifically included entity always wins over a glob.""" filter_accept = { "sensor.apc900va_status", "sensor.apc900va_battery_charge", diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index f28e379a0c4..3154ebf7ffc 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -2067,7 +2067,7 @@ def test_deduplication_state_attributes_inside_commit_interval( hass.states.set(entity_id, "on", attributes) hass.states.set(entity_id, "off", attributes) - # Now exaust the cache to ensure we go back to the db + # Now exhaust the cache to ensure we go back to the db for attr_id in range(5): hass.states.set(entity_id, "on", {"test_attr": attr_id}) hass.states.set(entity_id, "off", {"test_attr": attr_id}) diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index 02587d7bc98..ba448230c35 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -1164,7 +1164,7 @@ async def test_script_variables( async def test_script_this_var_always( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: - """Test script always has reference to this, even with no variabls are configured.""" + """Test script always has reference to this, even with no variables are configured.""" assert await async_setup_component( hass, diff --git a/tests/components/valve/test_init.py b/tests/components/valve/test_init.py index 23071718724..a00d975f0eb 100644 --- a/tests/components/valve/test_init.py +++ b/tests/components/valve/test_init.py @@ -118,7 +118,7 @@ class MockBinaryValveEntity(ValveEntity): self._attr_is_closed = False def close_valve(self) -> None: - """Mock implementantion for sync close function.""" + """Mock implementation for sync close function.""" self._attr_is_closed = True diff --git a/tests/components/wemo/entity_test_helpers.py b/tests/components/wemo/entity_test_helpers.py index f999de408c0..fd2bbed4371 100644 --- a/tests/components/wemo/entity_test_helpers.py +++ b/tests/components/wemo/entity_test_helpers.py @@ -124,7 +124,7 @@ async def test_async_update_locked_multiple_callbacks( async def test_avaliable_after_update( hass: HomeAssistant, pywemo_registry, pywemo_device, wemo_entity, domain ) -> None: - """Test the avaliability when an On call fails and after an update. + """Test the availability when an On call fails and after an update. This test expects that the pywemo_device Mock has been setup to raise an ActionException when the SERVICE_TURN_ON method is called and that the diff --git a/tests/components/wemo/test_fan.py b/tests/components/wemo/test_fan.py index 7a1bb2c628b..625007a744d 100644 --- a/tests/components/wemo/test_fan.py +++ b/tests/components/wemo/test_fan.py @@ -90,7 +90,7 @@ async def test_fan_update_entity( async def test_available_after_update( hass: HomeAssistant, pywemo_registry, pywemo_device, wemo_entity ) -> None: - """Test the avaliability when an On call fails and after an update.""" + """Test the availability when an On call fails and after an update.""" pywemo_device.set_state.side_effect = ActionException pywemo_device.get_state.return_value = 1 await entity_test_helpers.test_avaliable_after_update( diff --git a/tests/components/wemo/test_light_bridge.py b/tests/components/wemo/test_light_bridge.py index 149fb13d023..48be2823750 100644 --- a/tests/components/wemo/test_light_bridge.py +++ b/tests/components/wemo/test_light_bridge.py @@ -85,7 +85,7 @@ async def test_available_after_update( pywemo_bridge_light, wemo_entity, ) -> None: - """Test the avaliability when an On call fails and after an update.""" + """Test the availability when an On call fails and after an update.""" pywemo_bridge_light.turn_on.side_effect = pywemo.exceptions.ActionException pywemo_bridge_light.state["onoff"] = 1 await entity_test_helpers.test_avaliable_after_update( diff --git a/tests/components/wemo/test_light_dimmer.py b/tests/components/wemo/test_light_dimmer.py index 96d27b56963..a2f69ea57d5 100644 --- a/tests/components/wemo/test_light_dimmer.py +++ b/tests/components/wemo/test_light_dimmer.py @@ -38,7 +38,7 @@ test_async_update_locked_callback_and_update = ( async def test_available_after_update( hass: HomeAssistant, pywemo_registry, pywemo_device, wemo_entity ) -> None: - """Test the avaliability when an On call fails and after an update.""" + """Test the availability when an On call fails and after an update.""" pywemo_device.on.side_effect = ActionException pywemo_device.get_state.return_value = 1 await entity_test_helpers.test_avaliable_after_update( diff --git a/tests/components/wemo/test_switch.py b/tests/components/wemo/test_switch.py index b73844bdb58..ab60f290727 100644 --- a/tests/components/wemo/test_switch.py +++ b/tests/components/wemo/test_switch.py @@ -103,7 +103,7 @@ async def test_switch_update_entity( async def test_available_after_update( hass: HomeAssistant, pywemo_registry, pywemo_device, wemo_entity ) -> None: - """Test the avaliability when an On call fails and after an update.""" + """Test the availability when an On call fails and after an update.""" pywemo_device.on.side_effect = pywemo.exceptions.ActionException pywemo_device.get_state.return_value = 1 await entity_test_helpers.test_avaliable_after_update( diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 44a360af215..1b279fd0f51 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -3397,7 +3397,7 @@ async def test_disabled_condition(hass: HomeAssistant) -> None: hass.states.async_set("binary_sensor.test", "on") assert test(hass) is None - # Still passses, condition is not enabled + # Still passes, condition is not enabled hass.states.async_set("binary_sensor.test", "off") assert test(hass) is None From 56e13ef3fc1ab74337f38d24ff4e20570c4a630c Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:45:07 +0100 Subject: [PATCH 1483/1691] Update pytest-unordered to 0.6.0 (#114162) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e577b870920..84e674b7109 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -26,7 +26,7 @@ pytest-socket==0.7.0 pytest-test-groups==1.0.3 pytest-sugar==1.0.0 pytest-timeout==2.3.1 -pytest-unordered==0.5.2 +pytest-unordered==0.6.0 pytest-picked==0.5.0 pytest-xdist==3.3.1 pytest==8.1.1 From 49a4fe41c6097e02ffbd564c582cca776ce2104b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:33:25 +0100 Subject: [PATCH 1484/1691] Update codespell to 2.2.6 (#114167) --- .pre-commit-config.yaml | 4 ++-- requirements_test_pre_commit.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 34c04e634b4..c77fe1c5076 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,11 +8,11 @@ repos: - id: ruff-format files: ^((homeassistant|pylint|script|tests)/.+)?[^/]+\.py$ - repo: https://github.com/codespell-project/codespell - rev: v2.2.2 + rev: v2.2.6 hooks: - id: codespell args: - - --ignore-words-list=additionals,alle,alot,bund,currenty,datas,farenheit,falsy,fo,haa,hass,iif,incomfort,ines,ist,nam,nd,pres,pullrequests,resset,rime,ser,serie,te,technik,ue,unsecure,withing,zar + - --ignore-words-list=additionals,alle,alot,astroid,bund,caf,convencional,currenty,datas,farenheit,falsy,fo,frequence,haa,hass,iif,incomfort,ines,ist,nam,nd,pres,pullrequests,resset,rime,ser,serie,te,technik,ue,unsecure,vor,withing,zar - --skip="./.*,*.csv,*.json,*.ambr" - --quiet-level=2 exclude_types: [csv, json] diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 2a2135ef903..a68b91e563f 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,5 +1,5 @@ # Automatically generated from .pre-commit-config.yaml by gen_requirements_all.py, do not edit -codespell==2.2.2 +codespell==2.2.6 ruff==0.2.1 yamllint==1.35.1 From 95ddade83caf1a0d8cb35025518bc89e729a4391 Mon Sep 17 00:00:00 2001 From: Matthias Lohr Date: Mon, 25 Mar 2024 12:47:56 +0100 Subject: [PATCH 1485/1691] Use enums to define choices in tolo (#113992) * use enums defined in HA to define choices * move enums to .const --- homeassistant/components/tolo/const.py | 17 +++++++++++++++++ homeassistant/components/tolo/select.py | 10 ++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tolo/const.py b/homeassistant/components/tolo/const.py index 16fcbd04bb7..e4f30eda6d3 100644 --- a/homeassistant/components/tolo/const.py +++ b/homeassistant/components/tolo/const.py @@ -1,7 +1,24 @@ """Constants for the tolo integration.""" +from enum import Enum + +from tololib import AromaTherapySlot as ToloAromaTherapySlot, LampMode as ToloLampMode DOMAIN = "tolo" DEFAULT_NAME = "TOLO Sauna" DEFAULT_RETRY_TIMEOUT = 1 DEFAULT_RETRY_COUNT = 3 + + +class AromaTherapySlot(Enum): + """Mapping to TOLO Aroma Therapy Slot.""" + + A = ToloAromaTherapySlot.A + B = ToloAromaTherapySlot.B + + +class LampMode(Enum): + """Mapping to TOLO Lamp Mode.""" + + MANUAL = ToloLampMode.MANUAL + AUTOMATIC = ToloLampMode.AUTOMATIC diff --git a/homeassistant/components/tolo/select.py b/homeassistant/components/tolo/select.py index f88152d6fa8..96335cecc68 100644 --- a/homeassistant/components/tolo/select.py +++ b/homeassistant/components/tolo/select.py @@ -5,7 +5,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from tololib import AromaTherapySlot, LampMode, ToloClient, ToloSettings +from tololib import ToloClient, ToloSettings from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry @@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator -from .const import DOMAIN +from .const import DOMAIN, AromaTherapySlot, LampMode @dataclass(frozen=True, kw_only=True) @@ -32,7 +32,9 @@ SELECTS = ( translation_key="lamp_mode", options=[lamp_mode.name.lower() for lamp_mode in LampMode], getter=lambda settings: settings.lamp_mode.name.lower(), - setter=lambda client, option: client.set_lamp_mode(LampMode[option.upper()]), + setter=lambda client, option: client.set_lamp_mode( + LampMode[option.upper()].value + ), ), ToloSelectEntityDescription( key="aroma_therapy_slot", @@ -42,7 +44,7 @@ SELECTS = ( ], getter=lambda settings: settings.aroma_therapy_slot.name.lower(), setter=lambda client, option: client.set_aroma_therapy_slot( - AromaTherapySlot[option.upper()] + AromaTherapySlot[option.upper()].value ), ), ) From 3acb5054560e496e4285478b87b54b041c2faa66 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 02:28:56 -1000 Subject: [PATCH 1486/1691] Refactor homekit device linking to use the device index (#114145) Now that we have an index of devices in the entity registry we can avoid generating a lookup for devices we do not care about --- homeassistant/components/homekit/__init__.py | 104 +++++++++---------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 51b3f5f376d..b00631c2249 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -803,18 +803,10 @@ class HomeKit: """Configure accessories for the included states.""" dev_reg = dr.async_get(self.hass) ent_reg = er.async_get(self.hass) - device_lookup = ent_reg.async_get_device_class_lookup( - { - (BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.BATTERY_CHARGING), - (BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.MOTION), - (BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.OCCUPANCY), - (SENSOR_DOMAIN, SensorDeviceClass.BATTERY), - (SENSOR_DOMAIN, SensorDeviceClass.HUMIDITY), - } - ) - + device_lookup: dict[str, dict[tuple[str, str | None], str]] = {} entity_states: list[State] = [] entity_filter = self._filter.get_filter() + entries = ent_reg.entities for state in self.hass.states.async_all(): entity_id = state.entity_id if not entity_filter(entity_id): @@ -830,7 +822,18 @@ class HomeKit: await self._async_set_device_info_attributes( ent_reg_ent, dev_reg, entity_id ) - self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state) + if device_id := ent_reg_ent.device_id: + if device_id not in device_lookup: + device_lookup[device_id] = { + ( + entry.domain, + entry.device_class or entry.original_device_class, + ): entry.entity_id + for entry in entries.get_entries_for_device_id(device_id) + } + self._async_configure_linked_sensors( + ent_reg_ent, device_lookup[device_id], state + ) entity_states.append(state) @@ -1073,64 +1076,59 @@ class HomeKit: def _async_configure_linked_sensors( self, ent_reg_ent: er.RegistryEntry, - device_lookup: dict[str, dict[tuple[str, str | None], str]], + device_lookup: dict[tuple[str, str | None], str], state: State, ) -> None: - if ( - ent_reg_ent is None - or ent_reg_ent.device_id is None - or ent_reg_ent.device_id not in device_lookup - or (ent_reg_ent.device_class or ent_reg_ent.original_device_class) - in (BinarySensorDeviceClass.BATTERY_CHARGING, SensorDeviceClass.BATTERY) + if (ent_reg_ent.device_class or ent_reg_ent.original_device_class) in ( + BinarySensorDeviceClass.BATTERY_CHARGING, + SensorDeviceClass.BATTERY, ): return - if ATTR_BATTERY_CHARGING not in state.attributes: - battery_charging_binary_sensor_entity_id = device_lookup[ - ent_reg_ent.device_id - ].get((BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.BATTERY_CHARGING)) - if battery_charging_binary_sensor_entity_id: - self._config.setdefault(state.entity_id, {}).setdefault( - CONF_LINKED_BATTERY_CHARGING_SENSOR, - battery_charging_binary_sensor_entity_id, - ) + domain = state.domain + attributes = state.attributes - if ATTR_BATTERY_LEVEL not in state.attributes: - battery_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get( + if ATTR_BATTERY_CHARGING not in attributes and ( + battery_charging_binary_sensor_entity_id := device_lookup.get( + (BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.BATTERY_CHARGING) + ) + ): + self._config.setdefault(state.entity_id, {}).setdefault( + CONF_LINKED_BATTERY_CHARGING_SENSOR, + battery_charging_binary_sensor_entity_id, + ) + + if ATTR_BATTERY_LEVEL not in attributes and ( + battery_sensor_entity_id := device_lookup.get( (SENSOR_DOMAIN, SensorDeviceClass.BATTERY) ) - if battery_sensor_entity_id: - self._config.setdefault(state.entity_id, {}).setdefault( - CONF_LINKED_BATTERY_SENSOR, battery_sensor_entity_id - ) + ): + self._config.setdefault(state.entity_id, {}).setdefault( + CONF_LINKED_BATTERY_SENSOR, battery_sensor_entity_id + ) - if state.entity_id.startswith(f"{CAMERA_DOMAIN}."): - motion_binary_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get( + if domain == CAMERA_DOMAIN: + if motion_binary_sensor_entity_id := device_lookup.get( (BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.MOTION) - ) - if motion_binary_sensor_entity_id: + ): self._config.setdefault(state.entity_id, {}).setdefault( - CONF_LINKED_MOTION_SENSOR, - motion_binary_sensor_entity_id, + CONF_LINKED_MOTION_SENSOR, motion_binary_sensor_entity_id ) - doorbell_binary_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get( + if doorbell_binary_sensor_entity_id := device_lookup.get( (BINARY_SENSOR_DOMAIN, BinarySensorDeviceClass.OCCUPANCY) - ) - if doorbell_binary_sensor_entity_id: + ): self._config.setdefault(state.entity_id, {}).setdefault( - CONF_LINKED_DOORBELL_SENSOR, - doorbell_binary_sensor_entity_id, + CONF_LINKED_DOORBELL_SENSOR, doorbell_binary_sensor_entity_id ) - if state.entity_id.startswith(f"{HUMIDIFIER_DOMAIN}."): - current_humidity_sensor_entity_id = device_lookup[ - ent_reg_ent.device_id - ].get((SENSOR_DOMAIN, SensorDeviceClass.HUMIDITY)) - if current_humidity_sensor_entity_id: - self._config.setdefault(state.entity_id, {}).setdefault( - CONF_LINKED_HUMIDITY_SENSOR, - current_humidity_sensor_entity_id, - ) + if domain == HUMIDIFIER_DOMAIN and ( + current_humidity_sensor_entity_id := device_lookup.get( + (SENSOR_DOMAIN, SensorDeviceClass.HUMIDITY) + ) + ): + self._config.setdefault(state.entity_id, {}).setdefault( + CONF_LINKED_HUMIDITY_SENSOR, current_humidity_sensor_entity_id + ) async def _async_set_device_info_attributes( self, From d398eb1f2cc0d1b2c8795f265f8933e614c5061f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 25 Mar 2024 13:59:36 +0100 Subject: [PATCH 1487/1691] Add Withings webhook manager (#106311) --- homeassistant/components/withings/__init__.py | 156 +++++++++++------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 5ca2a610e3d..c14fb465731 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -10,7 +10,7 @@ from collections.abc import Awaitable, Callable import contextlib from dataclasses import dataclass, field from datetime import timedelta -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast from aiohttp.hdrs import METH_POST from aiohttp.web import Request, Response @@ -193,82 +193,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {})[entry.entry_id] = withings_data - register_lock = asyncio.Lock() - webhooks_registered = False - - async def unregister_webhook( - _: Any, - ) -> None: - nonlocal webhooks_registered - async with register_lock: - LOGGER.debug( - "Unregister Withings webhook (%s)", entry.data[CONF_WEBHOOK_ID] - ) - webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID]) - await async_unsubscribe_webhooks(client) - for coordinator in withings_data.coordinators: - coordinator.webhook_subscription_listener(False) - webhooks_registered = False - - async def register_webhook( - _: Any, - ) -> None: - nonlocal webhooks_registered - async with register_lock: - if webhooks_registered: - return - if cloud.async_active_subscription(hass): - webhook_url = await _async_cloudhook_generate_url(hass, entry) - else: - webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID]) - url = URL(webhook_url) - if url.scheme != "https" or url.port != 443: - LOGGER.warning( - "Webhook not registered - " - "https and port 443 is required to register the webhook" - ) - return - - webhook_name = "Withings" - if entry.title != DEFAULT_TITLE: - webhook_name = f"{DEFAULT_TITLE} {entry.title}" - - webhook_register( - hass, - DOMAIN, - webhook_name, - entry.data[CONF_WEBHOOK_ID], - get_webhook_handler(withings_data), - allowed_methods=[METH_POST], - ) - LOGGER.debug("Registered Withings webhook at hass: %s", webhook_url) - - await async_subscribe_webhooks(client, webhook_url) - for coordinator in withings_data.coordinators: - coordinator.webhook_subscription_listener(True) - LOGGER.debug("Registered Withings webhook at Withings: %s", webhook_url) - entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook) - ) - webhooks_registered = True + webhook_manager = WithingsWebhookManager(hass, entry) async def manage_cloudhook(state: cloud.CloudConnectionState) -> None: LOGGER.debug("Cloudconnection state changed to %s", state) if state is cloud.CloudConnectionState.CLOUD_CONNECTED: - await register_webhook(None) + await webhook_manager.register_webhook(None) if state is cloud.CloudConnectionState.CLOUD_DISCONNECTED: - await unregister_webhook(None) - entry.async_on_unload(async_call_later(hass, 30, register_webhook)) + await webhook_manager.unregister_webhook(None) + entry.async_on_unload( + async_call_later(hass, 30, webhook_manager.register_webhook) + ) if cloud.async_active_subscription(hass): if cloud.async_is_connected(hass): - entry.async_on_unload(async_call_later(hass, 1, register_webhook)) + entry.async_on_unload( + async_call_later(hass, 1, webhook_manager.register_webhook) + ) entry.async_on_unload( cloud.async_listen_connection_change(hass, manage_cloudhook) ) else: - entry.async_on_unload(async_call_later(hass, 1, register_webhook)) + entry.async_on_unload( + async_call_later(hass, 1, webhook_manager.register_webhook) + ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @@ -310,6 +259,85 @@ async def async_subscribe_webhooks(client: WithingsClient, webhook_url: str) -> await client.subscribe_notification(webhook_url, notification) +class WithingsWebhookManager: + """Manager that manages the Withings webhooks.""" + + _webhooks_registered = False + _register_lock = asyncio.Lock() + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + """Initialize webhook manager.""" + self.hass = hass + self.entry = entry + + @property + def withings_data(self) -> WithingsData: + """Return Withings data.""" + return cast(WithingsData, self.hass.data[DOMAIN][self.entry.entry_id]) + + async def unregister_webhook( + self, + _: Any, + ) -> None: + """Unregister webhooks at Withings.""" + async with self._register_lock: + LOGGER.debug( + "Unregister Withings webhook (%s)", self.entry.data[CONF_WEBHOOK_ID] + ) + webhook_unregister(self.hass, self.entry.data[CONF_WEBHOOK_ID]) + await async_unsubscribe_webhooks(self.withings_data.client) + for coordinator in self.withings_data.coordinators: + coordinator.webhook_subscription_listener(False) + self._webhooks_registered = False + + async def register_webhook( + self, + _: Any, + ) -> None: + """Register webhooks at Withings.""" + async with self._register_lock: + if self._webhooks_registered: + return + if cloud.async_active_subscription(self.hass): + webhook_url = await _async_cloudhook_generate_url(self.hass, self.entry) + else: + webhook_url = webhook_generate_url( + self.hass, self.entry.data[CONF_WEBHOOK_ID] + ) + url = URL(webhook_url) + if url.scheme != "https" or url.port != 443: + LOGGER.warning( + "Webhook not registered - " + "https and port 443 is required to register the webhook" + ) + return + + webhook_name = "Withings" + if self.entry.title != DEFAULT_TITLE: + webhook_name = f"{DEFAULT_TITLE} {self.entry.title}" + + webhook_register( + self.hass, + DOMAIN, + webhook_name, + self.entry.data[CONF_WEBHOOK_ID], + get_webhook_handler(self.withings_data), + allowed_methods=[METH_POST], + ) + LOGGER.debug("Registered Withings webhook at hass: %s", webhook_url) + + await async_subscribe_webhooks(self.withings_data.client, webhook_url) + for coordinator in self.withings_data.coordinators: + coordinator.webhook_subscription_listener(True) + LOGGER.debug("Registered Withings webhook at Withings: %s", webhook_url) + self.entry.async_on_unload( + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, self.unregister_webhook + ) + ) + self._webhooks_registered = True + + async def async_unsubscribe_webhooks(client: WithingsClient) -> None: """Unsubscribe to all Withings webhooks.""" current_webhooks = await client.list_notification_configurations() From e2a26f64709382c536476c2346d0a95792c7eaad Mon Sep 17 00:00:00 2001 From: Manuel Dipolt Date: Mon, 25 Mar 2024 14:28:07 +0100 Subject: [PATCH 1488/1691] Add base entity to romy integration (#113750) * cherry picked base entity changes from the branches romy_binary_sensor & romy_sensor * Update homeassistant/components/romy/entity.py Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Update homeassistant/components/romy/vacuum.py Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Update homeassistant/components/romy/entity.py Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Update homeassistant/components/romy/vacuum.py Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Update homeassistant/components/romy/vacuum.py Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * code review changes, base entity/coordinator cleanup --------- Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> --- .coveragerc | 1 + homeassistant/components/romy/entity.py | 25 +++++++++++++++++++++++++ homeassistant/components/romy/vacuum.py | 20 ++++---------------- 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 homeassistant/components/romy/entity.py diff --git a/.coveragerc b/.coveragerc index 8b3067313d3..67c5887f2da 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1135,6 +1135,7 @@ omit = homeassistant/components/rocketchat/notify.py homeassistant/components/romy/__init__.py homeassistant/components/romy/coordinator.py + homeassistant/components/romy/entity.py homeassistant/components/romy/vacuum.py homeassistant/components/roomba/__init__.py homeassistant/components/roomba/binary_sensor.py diff --git a/homeassistant/components/romy/entity.py b/homeassistant/components/romy/entity.py new file mode 100644 index 00000000000..3fa6eefbe02 --- /dev/null +++ b/homeassistant/components/romy/entity.py @@ -0,0 +1,25 @@ +"""Base entity for ROMY.""" + + +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import RomyVacuumCoordinator + + +class RomyEntity(CoordinatorEntity[RomyVacuumCoordinator]): + """Base ROMY entity.""" + + _attr_has_entity_name = True + + def __init__(self, coordinator: RomyVacuumCoordinator) -> None: + """Initialize ROMY entity.""" + super().__init__(coordinator) + self.romy = coordinator.romy + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.romy.unique_id)}, + manufacturer="ROMY", + name=self.romy.name, + model=self.romy.model, + ) diff --git a/homeassistant/components/romy/vacuum.py b/homeassistant/components/romy/vacuum.py index 90189da7e2f..de74d371f0e 100644 --- a/homeassistant/components/romy/vacuum.py +++ b/homeassistant/components/romy/vacuum.py @@ -6,17 +6,14 @@ https://home-assistant.io/components/vacuum.romy/. from typing import Any -from romy import RomyRobot - from homeassistant.components.vacuum import StateVacuumEntity, VacuumEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN, LOGGER from .coordinator import RomyVacuumCoordinator +from .entity import RomyEntity FAN_SPEED_NONE = "default" FAN_SPEED_NORMAL = "normal" @@ -55,32 +52,23 @@ async def async_setup_entry( """Set up ROMY vacuum cleaner.""" coordinator: RomyVacuumCoordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities([RomyVacuumEntity(coordinator, coordinator.romy)], True) + async_add_entities([RomyVacuumEntity(coordinator)]) -class RomyVacuumEntity(CoordinatorEntity[RomyVacuumCoordinator], StateVacuumEntity): +class RomyVacuumEntity(RomyEntity, StateVacuumEntity): """Representation of a ROMY vacuum cleaner robot.""" - _attr_has_entity_name = True - _attr_name = None _attr_supported_features = SUPPORT_ROMY_ROBOT _attr_fan_speed_list = FAN_SPEEDS + _attr_name = None def __init__( self, coordinator: RomyVacuumCoordinator, - romy: RomyRobot, ) -> None: """Initialize the ROMY Robot.""" super().__init__(coordinator) - self.romy = romy self._attr_unique_id = self.romy.unique_id - self._device_info = DeviceInfo( - identifiers={(DOMAIN, romy.unique_id)}, - manufacturer="ROMY", - name=romy.name, - model=romy.model, - ) @callback def _handle_coordinator_update(self) -> None: From 7b2166243b5de20ce5e5f7ea32b0a0d1723f01a6 Mon Sep 17 00:00:00 2001 From: Max von Webel Date: Mon, 25 Mar 2024 15:10:16 +0100 Subject: [PATCH 1489/1691] Updated temp offset logging in tado component (#106308) * Updated temp offset logging in tado component The logging is a bit confusing because it's trimming the digits from the value. Hopefully this makes it a bit more clear. * Update homeassistant/components/tado/climate.py Co-authored-by: Jorim Tielemans --------- Co-authored-by: Jorim Tielemans Co-authored-by: Erik Montnemery --- homeassistant/components/tado/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 3261b6e212d..2bd1bc31bb0 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -418,7 +418,7 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): """Set offset on the entity.""" _LOGGER.debug( - "Setting temperature offset for device %s setting to (%d)", + "Setting temperature offset for device %s setting to (%.1f)", self._device_id, offset, ) From 3d6600364b0daaf60e50b4c5f8c5b63ce71396e8 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Mon, 25 Mar 2024 16:08:57 +0100 Subject: [PATCH 1490/1691] Bump pywaze to 1.0.0 (#108613) --- .../components/waze_travel_time/helpers.py | 2 +- .../components/waze_travel_time/manifest.json | 2 +- .../components/waze_travel_time/sensor.py | 43 +++++----- .../components/waze_travel_time/strings.json | 4 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/waze_travel_time/conftest.py | 19 ++++- .../waze_travel_time/test_sensor.py | 79 +++++++++++++------ 8 files changed, 100 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/waze_travel_time/helpers.py b/homeassistant/components/waze_travel_time/helpers.py index c9273c1b774..c6fe4d0c9bd 100644 --- a/homeassistant/components/waze_travel_time/helpers.py +++ b/homeassistant/components/waze_travel_time/helpers.py @@ -20,7 +20,7 @@ async def is_valid_config_entry( httpx_client = get_async_client(hass) client = WazeRouteCalculator(region=region, client=httpx_client) try: - await client.calc_all_routes_info(resolved_origin, resolved_destination) + await client.calc_routes(resolved_origin, resolved_destination) except WRCError as error: _LOGGER.error("Error trying to validate entry: %s", error) return False diff --git a/homeassistant/components/waze_travel_time/manifest.json b/homeassistant/components/waze_travel_time/manifest.json index 728a91e4933..4fc08cf983d 100644 --- a/homeassistant/components/waze_travel_time/manifest.json +++ b/homeassistant/components/waze_travel_time/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/waze_travel_time", "iot_class": "cloud_polling", "loggers": ["pywaze", "homeassistant.helpers.location"], - "requirements": ["pywaze==0.5.1"] + "requirements": ["pywaze==1.0.0"] } diff --git a/homeassistant/components/waze_travel_time/sensor.py b/homeassistant/components/waze_travel_time/sensor.py index e759c06bf10..518de269bc5 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -196,7 +196,7 @@ class WazeTravelTimeData: routes = {} try: - routes = await self.client.calc_all_routes_info( + routes = await self.client.calc_routes( self.origin, self.destination, vehicle_type=vehicle_type, @@ -204,29 +204,37 @@ class WazeTravelTimeData: avoid_subscription_roads=avoid_subscription_roads, avoid_ferries=avoid_ferries, real_time=realtime, + alternatives=3, ) if incl_filter not in {None, ""}: - routes = { - k: v - for k, v in routes.items() - if incl_filter.lower() in k.lower() - } + routes = [ + r + for r in routes + if any( + incl_filter.lower() == street_name.lower() + for street_name in r.street_names + ) + ] if excl_filter not in {None, ""}: - routes = { - k: v - for k, v in routes.items() - if excl_filter.lower() not in k.lower() - } + routes = [ + r + for r in routes + if not any( + excl_filter.lower() == street_name.lower() + for street_name in r.street_names + ) + ] - if routes: - route = list(routes)[0] - else: + if len(routes) < 1: _LOGGER.warning("No routes found") return - self.duration, distance = routes[route] + route = routes[0] + + self.duration = route.duration + distance = route.distance if units == IMPERIAL_UNITS: # Convert to miles. @@ -236,10 +244,7 @@ class WazeTravelTimeData: else: self.distance = distance - self.route = route + self.route = route.name except WRCError as exp: _LOGGER.warning("Error on retrieving data: %s", exp) return - except KeyError: - _LOGGER.error("Error retrieving data from server") - return diff --git a/homeassistant/components/waze_travel_time/strings.json b/homeassistant/components/waze_travel_time/strings.json index 61b93f13f17..2a5017a5b9f 100644 --- a/homeassistant/components/waze_travel_time/strings.json +++ b/homeassistant/components/waze_travel_time/strings.json @@ -26,8 +26,8 @@ "data": { "units": "Units", "vehicle_type": "Vehicle Type", - "incl_filter": "Substring in Description of Selected Route", - "excl_filter": "Substring NOT in Description of Selected Route", + "incl_filter": "Streetname which must be part of the Selected Route", + "excl_filter": "Streetname which must NOT be part of the Selected Route", "realtime": "Realtime Travel Time?", "avoid_toll_roads": "Avoid Toll Roads?", "avoid_ferries": "Avoid Ferries?", diff --git a/requirements_all.txt b/requirements_all.txt index 70ad107d49e..d58d6351d8e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2360,7 +2360,7 @@ pyvlx==0.2.21 pyvolumio==0.1.5 # homeassistant.components.waze_travel_time -pywaze==0.5.1 +pywaze==1.0.0 # homeassistant.components.weatherflow pyweatherflowudp==1.4.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7df7000a5de..601e9770b60 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1821,7 +1821,7 @@ pyvlx==0.2.21 pyvolumio==0.1.5 # homeassistant.components.waze_travel_time -pywaze==0.5.1 +pywaze==1.0.0 # homeassistant.components.weatherflow pyweatherflowudp==1.4.5 diff --git a/tests/components/waze_travel_time/conftest.py b/tests/components/waze_travel_time/conftest.py index 012657bdd8d..01642ace86a 100644 --- a/tests/components/waze_travel_time/conftest.py +++ b/tests/components/waze_travel_time/conftest.py @@ -3,15 +3,28 @@ from unittest.mock import patch import pytest -from pywaze.route_calculator import WRCError +from pywaze.route_calculator import CalcRoutesResponse, WRCError @pytest.fixture(name="mock_update") def mock_update_fixture(): """Mock an update to the sensor.""" with patch( - "pywaze.route_calculator.WazeRouteCalculator.calc_all_routes_info", - return_value={"My route": (150, 300)}, + "pywaze.route_calculator.WazeRouteCalculator.calc_routes", + return_value=[ + CalcRoutesResponse( + distance=300, + duration=150, + name="E1337 - Teststreet", + street_names=["E1337", "IncludeThis", "Teststreet"], + ), + CalcRoutesResponse( + distance=500, + duration=600, + name="E0815 - Otherstreet", + street_names=["E0815", "ExcludeThis", "Otherstreet"], + ), + ], ) as mock_wrc: yield mock_wrc diff --git a/tests/components/waze_travel_time/test_sensor.py b/tests/components/waze_travel_time/test_sensor.py index 0b2ec4297e6..db0ece32cae 100644 --- a/tests/components/waze_travel_time/test_sensor.py +++ b/tests/components/waze_travel_time/test_sensor.py @@ -7,12 +7,15 @@ from homeassistant.components.waze_travel_time.const import ( CONF_AVOID_FERRIES, CONF_AVOID_SUBSCRIPTION_ROADS, CONF_AVOID_TOLL_ROADS, + CONF_EXCL_FILTER, + CONF_INCL_FILTER, CONF_REALTIME, CONF_UNITS, CONF_VEHICLE_TYPE, DEFAULT_OPTIONS, DOMAIN, IMPERIAL_UNITS, + METRIC_UNITS, ) from homeassistant.core import HomeAssistant @@ -22,7 +25,7 @@ from tests.common import MockConfigEntry @pytest.fixture(name="mock_config") -async def mock_config_fixture(hass, data, options): +async def mock_config_fixture(hass: HomeAssistant, data, options): """Mock a Waze Travel Time config entry.""" config_entry = MockConfigEntry( domain=DOMAIN, @@ -42,13 +45,6 @@ def mock_update_wrcerror_fixture(mock_update): return mock_update -@pytest.fixture(name="mock_update_keyerror") -def mock_update_keyerror_fixture(mock_update): - """Mock an update to the sensor failed with KeyError.""" - mock_update.side_effect = KeyError("test") - return mock_update - - @pytest.mark.parametrize( ("data", "options"), [(MOCK_CONFIG, DEFAULT_OPTIONS)], @@ -63,7 +59,10 @@ async def test_sensor(hass: HomeAssistant) -> None: ) assert hass.states.get("sensor.waze_travel_time").attributes["duration"] == 150 assert hass.states.get("sensor.waze_travel_time").attributes["distance"] == 300 - assert hass.states.get("sensor.waze_travel_time").attributes["route"] == "My route" + assert ( + hass.states.get("sensor.waze_travel_time").attributes["route"] + == "E1337 - Teststreet" + ) assert ( hass.states.get("sensor.waze_travel_time").attributes["origin"] == "location1" ) @@ -101,6 +100,52 @@ async def test_imperial(hass: HomeAssistant) -> None: ] == pytest.approx(186.4113) +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_UNITS: METRIC_UNITS, + CONF_REALTIME: True, + CONF_VEHICLE_TYPE: "car", + CONF_AVOID_TOLL_ROADS: True, + CONF_AVOID_SUBSCRIPTION_ROADS: True, + CONF_AVOID_FERRIES: True, + CONF_INCL_FILTER: "IncludeThis", + }, + ) + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_incl_filter(hass: HomeAssistant) -> None: + """Test that incl_filter only includes route with the wanted street name.""" + assert hass.states.get("sensor.waze_travel_time").attributes["distance"] == 300 + + +@pytest.mark.parametrize( + ("data", "options"), + [ + ( + MOCK_CONFIG, + { + CONF_UNITS: METRIC_UNITS, + CONF_REALTIME: True, + CONF_VEHICLE_TYPE: "car", + CONF_AVOID_TOLL_ROADS: True, + CONF_AVOID_SUBSCRIPTION_ROADS: True, + CONF_AVOID_FERRIES: True, + CONF_EXCL_FILTER: "ExcludeThis", + }, + ) + ], +) +@pytest.mark.usefixtures("mock_update", "mock_config") +async def test_excl_filter(hass: HomeAssistant) -> None: + """Test that excl_filter only includes route without the street name.""" + assert hass.states.get("sensor.waze_travel_time").attributes["distance"] == 300 + + @pytest.mark.usefixtures("mock_update_wrcerror") async def test_sensor_failed_wrcerror( hass: HomeAssistant, caplog: pytest.LogCaptureFixture @@ -115,19 +160,3 @@ async def test_sensor_failed_wrcerror( assert hass.states.get("sensor.waze_travel_time").state == "unknown" assert "Error on retrieving data: " in caplog.text - - -@pytest.mark.usefixtures("mock_update_keyerror") -async def test_sensor_failed_keyerror( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture -) -> None: - """Test that sensor update fails with log message.""" - config_entry = MockConfigEntry( - domain=DOMAIN, data=MOCK_CONFIG, options=DEFAULT_OPTIONS, entry_id="test" - ) - config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert hass.states.get("sensor.waze_travel_time").state == "unknown" - assert "Error retrieving data from server" in caplog.text From b71cbeff51c68f6f9d7cb462633457d4e27d8c04 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:11:12 +0100 Subject: [PATCH 1491/1691] Bump aioautomower to 2024.3.4 (#114127) * Bump aioautomower to 2024.3.4 * Update homeassistant/components/husqvarna_automower/sensor.py Co-authored-by: Joost Lekkerkerker * adjust imports --------- Co-authored-by: Joost Lekkerkerker --- .../components/husqvarna_automower/manifest.json | 2 +- homeassistant/components/husqvarna_automower/sensor.py | 10 ++++++---- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../snapshots/test_diagnostics.ambr | 4 ++-- .../husqvarna_automower/snapshots/test_sensor.ambr | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/manifest.json b/homeassistant/components/husqvarna_automower/manifest.json index ed013f2e0c2..e4536ee594d 100644 --- a/homeassistant/components/husqvarna_automower/manifest.json +++ b/homeassistant/components/husqvarna_automower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "iot_class": "cloud_push", "loggers": ["aioautomower"], - "requirements": ["aioautomower==2024.3.3"] + "requirements": ["aioautomower==2024.3.4"] } diff --git a/homeassistant/components/husqvarna_automower/sensor.py b/homeassistant/components/husqvarna_automower/sensor.py index 857d3039e22..e054d02e3ba 100644 --- a/homeassistant/components/husqvarna_automower/sensor.py +++ b/homeassistant/components/husqvarna_automower/sensor.py @@ -2,7 +2,7 @@ from collections.abc import Callable from dataclasses import dataclass -import datetime +from datetime import datetime import logging from aioautomower.model import MowerAttributes, MowerModes @@ -17,6 +17,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfLength, UnitOfTime from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType +from homeassistant.util import dt as dt_util from .const import DOMAIN from .coordinator import AutomowerDataUpdateCoordinator @@ -30,7 +32,7 @@ class AutomowerSensorEntityDescription(SensorEntityDescription): """Describes Automower sensor entity.""" exists_fn: Callable[[MowerAttributes], bool] = lambda _: True - value_fn: Callable[[MowerAttributes], str] + value_fn: Callable[[MowerAttributes], StateType | datetime] SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( @@ -137,7 +139,7 @@ SENSOR_TYPES: tuple[AutomowerSensorEntityDescription, ...] = ( key="next_start_timestamp", translation_key="next_start_timestamp", device_class=SensorDeviceClass.TIMESTAMP, - value_fn=lambda data: data.planner.next_start_dateteime, + value_fn=lambda data: dt_util.as_local(data.planner.next_start_datetime), ), ) @@ -172,6 +174,6 @@ class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity): self._attr_unique_id = f"{mower_id}_{description.key}" @property - def native_value(self) -> str | int | datetime.datetime | None: + def native_value(self) -> StateType | datetime: """Return the state of the sensor.""" return self.entity_description.value_fn(self.mower_attributes) diff --git a/requirements_all.txt b/requirements_all.txt index d58d6351d8e..2765da52873 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -203,7 +203,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.3.3 +aioautomower==2024.3.4 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 601e9770b60..10bfa1ec632 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -182,7 +182,7 @@ aioaseko==0.1.1 aioasuswrt==1.4.0 # homeassistant.components.husqvarna_automower -aioautomower==2024.3.3 +aioautomower==2024.3.4 # homeassistant.components.azure_devops aioazuredevops==1.3.5 diff --git a/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr index 577d49cd026..aea65005fc4 100644 --- a/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr @@ -49,13 +49,13 @@ 'mower': dict({ 'activity': 'PARKED_IN_CS', 'error_code': 0, - 'error_dateteime': None, + 'error_datetime': None, 'error_key': None, 'mode': 'MAIN_AREA', 'state': 'RESTRICTED', }), 'planner': dict({ - 'next_start_dateteime': '2023-06-05T19:00:00+00:00', + 'next_start_datetime': '2023-06-05T19:00:00', 'override': dict({ 'action': 'NOT_ACTIVE', }), diff --git a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr index 77994d7e0d8..ce81098f753 100644 --- a/tests/components/husqvarna_automower/snapshots/test_sensor.ambr +++ b/tests/components/husqvarna_automower/snapshots/test_sensor.ambr @@ -210,7 +210,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2023-06-05T19:00:00+00:00', + 'state': '2023-06-06T02:00:00+00:00', }) # --- # name: test_sensor[sensor.test_mower_1_number_of_charging_cycles-entry] From c1e1db9863cb2080fdf4c3f8db6e6628aea2a1d9 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 25 Mar 2024 17:16:41 +0100 Subject: [PATCH 1492/1691] Adapt to Axis library file structure changes (#114181) * Adapt to axis library file structure changes * Bump axis to v59 --- .../components/axis/binary_sensor.py | 8 +++---- homeassistant/components/axis/hub/api.py | 2 +- homeassistant/components/axis/hub/hub.py | 4 ++-- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/axis/const.py | 2 +- .../axis/snapshots/test_diagnostics.ambr | 2 +- tests/components/axis/test_hub.py | 6 ++--- tests/components/axis/test_light.py | 22 +++++++++++++------ tests/components/axis/test_switch.py | 10 ++++----- 11 files changed, 35 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index af8c394813a..8cd90ba1554 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -6,11 +6,11 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta +from axis.interfaces.applications.fence_guard import FenceGuardHandler +from axis.interfaces.applications.loitering_guard import LoiteringGuardHandler +from axis.interfaces.applications.motion_guard import MotionGuardHandler +from axis.interfaces.applications.vmd4 import Vmd4Handler from axis.models.event import Event, EventTopic -from axis.vapix.interfaces.applications.fence_guard import FenceGuardHandler -from axis.vapix.interfaces.applications.loitering_guard import LoiteringGuardHandler -from axis.vapix.interfaces.applications.motion_guard import MotionGuardHandler -from axis.vapix.interfaces.applications.vmd4 import Vmd4Handler from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, diff --git a/homeassistant/components/axis/hub/api.py b/homeassistant/components/axis/hub/api.py index dc2e63598b6..8e5d7533631 100644 --- a/homeassistant/components/axis/hub/api.py +++ b/homeassistant/components/axis/hub/api.py @@ -5,7 +5,7 @@ from types import MappingProxyType from typing import Any import axis -from axis.configuration import Configuration +from axis.models.configuration import Configuration from homeassistant.const import ( CONF_HOST, diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index 6252c0e29c5..08eb816f6ab 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -6,9 +6,9 @@ from typing import Any import axis from axis.errors import Unauthorized +from axis.interfaces.mqtt import mqtt_json_to_event +from axis.models.mqtt import ClientState from axis.stream_manager import Signal, State -from axis.vapix.interfaces.mqtt import mqtt_json_to_event -from axis.vapix.models.mqtt import ClientState from homeassistant.components import mqtt from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index f2a2dd40740..f47d10df484 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==58"], + "requirements": ["axis==59"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index 2765da52873..dbd15073e62 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==58 +axis==59 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 10bfa1ec632..74e85ab6b2e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -454,7 +454,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==58 +axis==59 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/tests/components/axis/const.py b/tests/components/axis/const.py index 8dffac5866a..7b881ea55e5 100644 --- a/tests/components/axis/const.py +++ b/tests/components/axis/const.py @@ -1,6 +1,6 @@ """Constants for Axis integration tests.""" -from axis.vapix.models.api import CONTEXT +from axis.models.api import CONTEXT MAC = "00408C123456" FORMATTED_MAC = "00:40:8c:12:34:56" diff --git a/tests/components/axis/snapshots/test_diagnostics.ambr b/tests/components/axis/snapshots/test_diagnostics.ambr index 3902cee6a0b..8ea316d00cf 100644 --- a/tests/components/axis/snapshots/test_diagnostics.ambr +++ b/tests/components/axis/snapshots/test_diagnostics.ambr @@ -19,7 +19,7 @@ }), ]), 'basic_device_info': dict({ - '__type': "", + '__type': "", 'repr': "DeviceInformation(id='0', architecture='str', brand='str', build_date='str', firmware_version='9.80.1', hardware_id='str', product_full_name='str', product_number='M1065-LW', product_short_name='str', product_type='Network Camera', product_variant='str', serial_number='00408C123456', soc='str', soc_serial_number='str', web_url='str')", }), 'camera_sources': dict({ diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index f12665a995d..d166aaed733 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -224,7 +224,7 @@ async def test_shutdown(config_entry_data) -> None: async def test_get_device_fails(hass: HomeAssistant, config_entry_data) -> None: """Device unauthorized yields authentication required error.""" with patch( - "axis.vapix.vapix.Vapix.initialize", side_effect=axislib.Unauthorized + "axis.interfaces.vapix.Vapix.initialize", side_effect=axislib.Unauthorized ), pytest.raises(axis.errors.AuthenticationRequired): await axis.hub.get_axis_api(hass, config_entry_data) @@ -234,7 +234,7 @@ async def test_get_device_device_unavailable( ) -> None: """Device unavailable yields cannot connect error.""" with patch( - "axis.vapix.vapix.Vapix.request", side_effect=axislib.RequestError + "axis.interfaces.vapix.Vapix.request", side_effect=axislib.RequestError ), pytest.raises(axis.errors.CannotConnect): await axis.hub.get_axis_api(hass, config_entry_data) @@ -242,6 +242,6 @@ async def test_get_device_device_unavailable( async def test_get_device_unknown_error(hass: HomeAssistant, config_entry_data) -> None: """Device yield unknown error.""" with patch( - "axis.vapix.vapix.Vapix.request", side_effect=axislib.AxisException + "axis.interfaces.vapix.Vapix.request", side_effect=axislib.AxisException ), pytest.raises(axis.errors.AuthenticationRequired): await axis.hub.get_axis_api(hass, config_entry_data) diff --git a/tests/components/axis/test_light.py b/tests/components/axis/test_light.py index b8983cd8b9b..95956450c9e 100644 --- a/tests/components/axis/test_light.py +++ b/tests/components/axis/test_light.py @@ -4,7 +4,7 @@ from collections.abc import Callable from typing import Any from unittest.mock import patch -from axis.vapix.models.api import CONTEXT +from axis.models.api import CONTEXT import pytest import respx @@ -149,8 +149,10 @@ async def test_lights( assert light_0.name == f"{NAME} IR Light 0" # Turn on, set brightness, light already on - with patch("axis.vapix.vapix.LightHandler.activate_light") as mock_activate, patch( - "axis.vapix.vapix.LightHandler.set_manual_intensity" + with patch( + "axis.interfaces.vapix.LightHandler.activate_light" + ) as mock_activate, patch( + "axis.interfaces.vapix.LightHandler.set_manual_intensity" ) as mock_set_intensity: await hass.services.async_call( LIGHT_DOMAIN, @@ -162,7 +164,9 @@ async def test_lights( mock_set_intensity.assert_called_once_with("led0", 29) # Turn off - with patch("axis.vapix.vapix.LightHandler.deactivate_light") as mock_deactivate: + with patch( + "axis.interfaces.vapix.LightHandler.deactivate_light" + ) as mock_deactivate: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, @@ -185,8 +189,10 @@ async def test_lights( assert light_0.state == STATE_OFF # Turn on, set brightness - with patch("axis.vapix.vapix.LightHandler.activate_light") as mock_activate, patch( - "axis.vapix.vapix.LightHandler.set_manual_intensity" + with patch( + "axis.interfaces.vapix.LightHandler.activate_light" + ) as mock_activate, patch( + "axis.interfaces.vapix.LightHandler.set_manual_intensity" ) as mock_set_intensity: await hass.services.async_call( LIGHT_DOMAIN, @@ -198,7 +204,9 @@ async def test_lights( mock_set_intensity.assert_not_called() # Turn off, light already off - with patch("axis.vapix.vapix.LightHandler.deactivate_light") as mock_deactivate: + with patch( + "axis.interfaces.vapix.LightHandler.deactivate_light" + ) as mock_deactivate: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index a2a1088d7ef..b9202d42e25 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -3,7 +3,7 @@ from collections.abc import Callable from unittest.mock import patch -from axis.vapix.models.api import CONTEXT +from axis.models.api import CONTEXT import pytest from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -65,7 +65,7 @@ async def test_switches_with_port_cgi( assert relay_0.state == STATE_OFF assert relay_0.name == f"{NAME} Doorbell" - with patch("axis.vapix.vapix.Ports.close") as mock_turn_on: + with patch("axis.interfaces.vapix.Ports.close") as mock_turn_on: await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, @@ -74,7 +74,7 @@ async def test_switches_with_port_cgi( ) mock_turn_on.assert_called_once_with("0") - with patch("axis.vapix.vapix.Ports.open") as mock_turn_off: + with patch("axis.interfaces.vapix.Ports.open") as mock_turn_off: await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_OFF, @@ -163,7 +163,7 @@ async def test_switches_with_port_management( assert hass.states.get(f"{SWITCH_DOMAIN}.{NAME}_relay_1").state == STATE_ON - with patch("axis.vapix.vapix.IoPortManagement.close") as mock_turn_on: + with patch("axis.interfaces.vapix.IoPortManagement.close") as mock_turn_on: await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, @@ -172,7 +172,7 @@ async def test_switches_with_port_management( ) mock_turn_on.assert_called_once_with("0") - with patch("axis.vapix.vapix.IoPortManagement.open") as mock_turn_off: + with patch("axis.interfaces.vapix.IoPortManagement.open") as mock_turn_off: await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_OFF, From 71a6653f6021690d6dfaf3925cc0b10af0ba3195 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 25 Mar 2024 17:21:21 +0100 Subject: [PATCH 1493/1691] Fix Axis not generate unique IDs (#114115) --- homeassistant/components/axis/hub/entity_loader.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/axis/hub/entity_loader.py b/homeassistant/components/axis/hub/entity_loader.py index f86c3c9d6ff..def22d62bd8 100644 --- a/homeassistant/components/axis/hub/entity_loader.py +++ b/homeassistant/components/axis/hub/entity_loader.py @@ -7,7 +7,7 @@ from __future__ import annotations from functools import partial from typing import TYPE_CHECKING -from axis.models.event import Event, EventOperation +from axis.models.event import Event, EventOperation, EventTopic from homeassistant.core import callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -25,6 +25,7 @@ class AxisEntityLoader: """Initialize the UniFi entity loader.""" self.hub = hub + self.registered_events: set[tuple[str, EventTopic, str]] = set() self.platforms: list[ tuple[ AddEntitiesCallback, @@ -58,6 +59,11 @@ class AxisEntityLoader: @callback def create_entity(description: AxisEventDescription, event: Event) -> None: """Create Axis entity.""" + event_id = (event.topic, event.topic_base, event.id) + if event_id in self.registered_events: + # Device has restarted and all events are initiatlized anew + return + self.registered_events.add(event_id) if description.supported_fn(self.hub, event): async_add_entities([platform_entity(self.hub, description, event)]) From 5e70faca5f738150b7aec45acc43eafe841fe701 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 25 Mar 2024 17:38:02 +0100 Subject: [PATCH 1494/1691] Refactor options flow in dnsip (#114058) * Refactor options flow in dnsip * Mods --- homeassistant/components/dnsip/config_flow.py | 52 +++++++++--------- tests/components/dnsip/test_config_flow.py | 55 +++++++++++++++++++ 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/dnsip/config_flow.py b/homeassistant/components/dnsip/config_flow.py index 8dc62a8343d..f07971d5db5 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -14,7 +14,7 @@ from homeassistant.config_entries import ( ConfigEntry, ConfigFlow, ConfigFlowResult, - OptionsFlow, + OptionsFlowWithConfigEntry, ) from homeassistant.const import CONF_NAME from homeassistant.core import callback @@ -146,47 +146,47 @@ class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN): ) -class DnsIPOptionsFlowHandler(OptionsFlow): +class DnsIPOptionsFlowHandler(OptionsFlowWithConfigEntry): """Handle a option config flow for dnsip integration.""" - def __init__(self, entry: ConfigEntry) -> None: - """Initialize options flow.""" - self.entry = entry - async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Manage the options.""" errors = {} if user_input is not None: + resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER) + resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6) validate = await async_validate_hostname( - self.entry.data[CONF_HOSTNAME], - user_input[CONF_RESOLVER], - user_input[CONF_RESOLVER_IPV6], + self.config_entry.data[CONF_HOSTNAME], + resolver, + resolver_ipv6, ) - if validate[CONF_IPV4] is False and self.entry.data[CONF_IPV4] is True: + if ( + validate[CONF_IPV4] is False + and self.config_entry.data[CONF_IPV4] is True + ): errors[CONF_RESOLVER] = "invalid_resolver" - elif validate[CONF_IPV6] is False and self.entry.data[CONF_IPV6] is True: + elif ( + validate[CONF_IPV6] is False + and self.config_entry.data[CONF_IPV6] is True + ): errors[CONF_RESOLVER_IPV6] = "invalid_resolver" else: - return self.async_create_entry(title=self.entry.title, data=user_input) + return self.async_create_entry( + title=self.config_entry.title, + data={CONF_RESOLVER: resolver, CONF_RESOLVER_IPV6: resolver_ipv6}, + ) - return self.async_show_form( - step_id="init", - data_schema=vol.Schema( + schema = self.add_suggested_values_to_schema( + vol.Schema( { - vol.Optional( - CONF_RESOLVER, - default=self.entry.options.get(CONF_RESOLVER, DEFAULT_RESOLVER), - ): cv.string, - vol.Optional( - CONF_RESOLVER_IPV6, - default=self.entry.options.get( - CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6 - ), - ): cv.string, + vol.Optional(CONF_RESOLVER): cv.string, + vol.Optional(CONF_RESOLVER_IPV6): cv.string, } ), - errors=errors, + self.config_entry.options, ) + + return self.async_show_form(step_id="init", data_schema=schema, errors=errors) diff --git a/tests/components/dnsip/test_config_flow.py b/tests/components/dnsip/test_config_flow.py index 7575efe220d..eb254908dd3 100644 --- a/tests/components/dnsip/test_config_flow.py +++ b/tests/components/dnsip/test_config_flow.py @@ -221,6 +221,61 @@ async def test_options_flow(hass: HomeAssistant) -> None: assert entry.state == config_entries.ConfigEntryState.LOADED +async def test_options_flow_empty_return(hass: HomeAssistant) -> None: + """Test options config flow with empty return from user.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="12345", + data={ + CONF_HOSTNAME: "home-assistant.io", + CONF_NAME: "home-assistant.io", + CONF_IPV4: True, + CONF_IPV6: False, + }, + options={ + CONF_RESOLVER: "8.8.8.8", + CONF_RESOLVER_IPV6: "2620:119:53::1", + }, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", + return_value=RetrieveDNS(), + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={}, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["data"] == { + "resolver": "208.67.222.222", + "resolver_ipv6": "2620:119:53::53", + } + + entry = hass.config_entries.async_get_entry(entry.entry_id) + assert entry.data == { + "hostname": "home-assistant.io", + "ipv4": True, + "ipv6": False, + "name": "home-assistant.io", + } + assert entry.options == { + "resolver": "208.67.222.222", + "resolver_ipv6": "2620:119:53::53", + } + + @pytest.mark.parametrize( "p_input", [ From 135c40cad8738556e6c9418242902e8417731b89 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 25 Mar 2024 18:31:04 +0100 Subject: [PATCH 1495/1691] Add Ecovacs last job event entity (#113826) Co-authored-by: Joost Lekkerkerker --- homeassistant/components/ecovacs/__init__.py | 1 + homeassistant/components/ecovacs/event.py | 65 +++++++++++++ homeassistant/components/ecovacs/icons.json | 5 + homeassistant/components/ecovacs/strings.json | 14 +++ .../ecovacs/snapshots/test_binary_sensor.ambr | 36 ++----- .../ecovacs/snapshots/test_event.ambr | 59 +++++++++++ .../components/ecovacs/test_binary_sensor.py | 2 +- tests/components/ecovacs/test_event.py | 97 +++++++++++++++++++ tests/components/ecovacs/test_init.py | 4 +- 9 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 homeassistant/components/ecovacs/event.py create mode 100644 tests/components/ecovacs/snapshots/test_event.ambr create mode 100644 tests/components/ecovacs/test_event.py diff --git a/homeassistant/components/ecovacs/__init__.py b/homeassistant/components/ecovacs/__init__.py index 229a2ac8ac6..ca4579a31b2 100644 --- a/homeassistant/components/ecovacs/__init__.py +++ b/homeassistant/components/ecovacs/__init__.py @@ -28,6 +28,7 @@ CONFIG_SCHEMA = vol.Schema( PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, + Platform.EVENT, Platform.IMAGE, Platform.LAWN_MOWER, Platform.NUMBER, diff --git a/homeassistant/components/ecovacs/event.py b/homeassistant/components/ecovacs/event.py new file mode 100644 index 00000000000..dbef806fa76 --- /dev/null +++ b/homeassistant/components/ecovacs/event.py @@ -0,0 +1,65 @@ +"""Event module.""" + + +from deebot_client.capabilities import Capabilities, CapabilityEvent +from deebot_client.device import Device +from deebot_client.events import CleanJobStatus, ReportStatsEvent + +from homeassistant.components.event import EventEntity, EventEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .controller import EcovacsController +from .entity import EcovacsEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add entities for passed config_entry in HA.""" + controller: EcovacsController = hass.data[DOMAIN][config_entry.entry_id] + async_add_entities( + EcovacsLastJobEventEntity(device) for device in controller.devices(Capabilities) + ) + + +class EcovacsLastJobEventEntity( + EcovacsEntity[Capabilities, CapabilityEvent[ReportStatsEvent]], + EventEntity, +): + """Ecovacs last job event entity.""" + + entity_description = EventEntityDescription( + key="stats_report", + translation_key="last_job", + entity_category=EntityCategory.DIAGNOSTIC, + event_types=["finished", "finished_with_warnings", "manually_stopped"], + ) + + def __init__(self, device: Device[Capabilities]) -> None: + """Initialize entity.""" + super().__init__(device, device.capabilities.stats.report) + + async def async_added_to_hass(self) -> None: + """Set up the event listeners now that hass is ready.""" + await super().async_added_to_hass() + + async def on_event(event: ReportStatsEvent) -> None: + """Handle event.""" + if event.status in (CleanJobStatus.NO_STATUS, CleanJobStatus.CLEANING): + # we trigger only on job done + return + + event_type = event.status.name.lower() + if event.status == CleanJobStatus.MANUAL_STOPPED: + event_type = "manually_stopped" + + self._trigger_event(event_type) + self.async_write_ha_state() + + self._subscribe(self._capability.event, on_event) diff --git a/homeassistant/components/ecovacs/icons.json b/homeassistant/components/ecovacs/icons.json index 7a57259ca5a..2e2d897c455 100644 --- a/homeassistant/components/ecovacs/icons.json +++ b/homeassistant/components/ecovacs/icons.json @@ -22,6 +22,11 @@ "default": "mdi:broom" } }, + "event": { + "last_job": { + "default": "mdi:history" + } + }, "number": { "clean_count": { "default": "mdi:counter" diff --git a/homeassistant/components/ecovacs/strings.json b/homeassistant/components/ecovacs/strings.json index 1f43b830778..a21f57a7a24 100644 --- a/homeassistant/components/ecovacs/strings.json +++ b/homeassistant/components/ecovacs/strings.json @@ -56,6 +56,20 @@ "name": "Reset side brushes lifespan" } }, + "event": { + "last_job": { + "name": "Last job", + "state_attributes": { + "event_type": { + "state": { + "finished": "Finished", + "finished_with_warnings": "Finished with warnings", + "manually_stopped": "Manually stopped" + } + } + } + } + }, "image": { "map": { "name": "Map" diff --git a/tests/components/ecovacs/snapshots/test_binary_sensor.ambr b/tests/components/ecovacs/snapshots/test_binary_sensor.ambr index 564323e91b2..62b356e379d 100644 --- a/tests/components/ecovacs/snapshots/test_binary_sensor.ambr +++ b/tests/components/ecovacs/snapshots/test_binary_sensor.ambr @@ -33,35 +33,15 @@ }) # --- # name: test_mop_attached[binary_sensor.ozmo_950_mop_attached-state] - EntityRegistryEntrySnapshot({ - 'aliases': set({ + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Ozmo 950 Mop attached', }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'binary_sensor', - 'entity_category': , + 'context': , 'entity_id': 'binary_sensor.ozmo_950_mop_attached', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Mop attached', - 'platform': 'ecovacs', - 'previous_unique_id': None, - 'supported_features': 0, - 'translation_key': 'water_mop_attached', - 'unique_id': 'E1234567890000000001_water_mop_attached', - 'unit_of_measurement': None, + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', }) # --- diff --git a/tests/components/ecovacs/snapshots/test_event.ambr b/tests/components/ecovacs/snapshots/test_event.ambr new file mode 100644 index 00000000000..8f433560cd1 --- /dev/null +++ b/tests/components/ecovacs/snapshots/test_event.ambr @@ -0,0 +1,59 @@ +# serializer version: 1 +# name: test_last_job[event.ozmo_950_last_job-entity_entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'event_types': list([ + 'finished', + 'finished_with_warnings', + 'manually_stopped', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'event', + 'entity_category': , + 'entity_id': 'event.ozmo_950_last_job', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Last job', + 'platform': 'ecovacs', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'last_job', + 'unique_id': 'E1234567890000000001_stats_report', + 'unit_of_measurement': None, + }) +# --- +# name: test_last_job[event.ozmo_950_last_job-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'event_type': 'finished', + 'event_types': list([ + 'finished', + 'finished_with_warnings', + 'manually_stopped', + ]), + 'friendly_name': 'Ozmo 950 Last job', + }), + 'context': , + 'entity_id': 'event.ozmo_950_last_job', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-03-20T00:00:00.000+00:00', + }) +# --- diff --git a/tests/components/ecovacs/test_binary_sensor.py b/tests/components/ecovacs/test_binary_sensor.py index 2ca0100be31..697e57c6def 100644 --- a/tests/components/ecovacs/test_binary_sensor.py +++ b/tests/components/ecovacs/test_binary_sensor.py @@ -49,7 +49,7 @@ async def test_mop_attached( ) assert (state := hass.states.get(state.entity_id)) - assert entity_entry == snapshot(name=f"{entity_id}-state") + assert state == snapshot(name=f"{entity_id}-state") await notify_and_wait( hass, event_bus, WaterInfoEvent(WaterAmount.HIGH, mop_attached=False) diff --git a/tests/components/ecovacs/test_event.py b/tests/components/ecovacs/test_event.py new file mode 100644 index 00000000000..0e7adaad954 --- /dev/null +++ b/tests/components/ecovacs/test_event.py @@ -0,0 +1,97 @@ +"""Tests for Ecovacs event entities.""" + +from datetime import timedelta + +from deebot_client.capabilities import Capabilities +from deebot_client.events import CleanJobStatus, ReportStatsEvent +from freezegun.api import FrozenDateTimeFactory +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.components.ecovacs.const import DOMAIN +from homeassistant.components.ecovacs.controller import EcovacsController +from homeassistant.components.event.const import ATTR_EVENT_TYPE +from homeassistant.const import STATE_UNKNOWN, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from .util import notify_and_wait + +pytestmark = [pytest.mark.usefixtures("init_integration")] + + +@pytest.fixture +def platforms() -> Platform | list[Platform]: + """Platforms, which should be loaded during the test.""" + return Platform.EVENT + + +async def test_last_job( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + freezer: FrozenDateTimeFactory, + controller: EcovacsController, +) -> None: + """Test last job event entity.""" + freezer.move_to("2024-03-20T00:00:00+00:00") + entity_id = "event.ozmo_950_last_job" + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_UNKNOWN + + assert (entity_entry := entity_registry.async_get(state.entity_id)) + assert entity_entry == snapshot(name=f"{entity_id}-entity_entry") + assert entity_entry.device_id + + device = next(controller.devices(Capabilities)) + + assert (device_entry := device_registry.async_get(entity_entry.device_id)) + assert device_entry.identifiers == {(DOMAIN, device.device_info["did"])} + + event_bus = device.events + await notify_and_wait( + hass, + event_bus, + ReportStatsEvent(10, 5, "spotArea", "1", CleanJobStatus.FINISHED, [1, 2]), + ) + + assert (state := hass.states.get(state.entity_id)) + assert state == snapshot(name=f"{entity_id}-state") + + freezer.tick(timedelta(minutes=5)) + await notify_and_wait( + hass, + event_bus, + ReportStatsEvent( + 100, 50, "spotArea", "2", CleanJobStatus.FINISHED_WITH_WARNINGS, [2, 3] + ), + ) + + assert (state := hass.states.get(state.entity_id)) + assert state.state == "2024-03-20T00:05:00.000+00:00" + assert state.attributes[ATTR_EVENT_TYPE] == "finished_with_warnings" + + freezer.tick(timedelta(minutes=5)) + await notify_and_wait( + hass, + event_bus, + ReportStatsEvent(0, 1, "spotArea", "3", CleanJobStatus.MANUAL_STOPPED, [1]), + ) + + assert (state := hass.states.get(state.entity_id)) + assert state.state == "2024-03-20T00:10:00.000+00:00" + assert state.attributes[ATTR_EVENT_TYPE] == "manually_stopped" + + freezer.tick(timedelta(minutes=5)) + for status in (CleanJobStatus.NO_STATUS, CleanJobStatus.CLEANING): + # we should not trigger on these statuses + await notify_and_wait( + hass, + event_bus, + ReportStatsEvent(12, 11, "spotArea", "4", status, [1, 2, 3]), + ) + + assert (state := hass.states.get(state.entity_id)) + assert state.state == "2024-03-20T00:10:00.000+00:00" + assert state.attributes[ATTR_EVENT_TYPE] == "manually_stopped" diff --git a/tests/components/ecovacs/test_init.py b/tests/components/ecovacs/test_init.py index bfaf2005e6d..7780b86d714 100644 --- a/tests/components/ecovacs/test_init.py +++ b/tests/components/ecovacs/test_init.py @@ -121,8 +121,8 @@ async def test_devices_in_dr( @pytest.mark.parametrize( ("device_fixture", "entities"), [ - ("yna5x1", 25), - ("5xu9h3", 19), + ("yna5x1", 26), + ("5xu9h3", 20), ], ) async def test_all_entities_loaded( From c3f4aca4e3a53fef42f8f146aa89ac3d7861a8a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 07:59:46 -1000 Subject: [PATCH 1496/1691] Add reauth support to NUT (#114131) --- homeassistant/components/nut/__init__.py | 15 +- homeassistant/components/nut/config_flow.py | 139 ++++++++++------ homeassistant/components/nut/strings.json | 16 +- tests/components/nut/test_config_flow.py | 170 +++++++++++++++++++- tests/components/nut/test_init.py | 26 ++- 5 files changed, 295 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index c9067bbb254..575def8bf0f 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -7,7 +7,7 @@ from datetime import timedelta import logging from typing import TYPE_CHECKING -from aionut import AIONUTClient, NUTError +from aionut import AIONUTClient, NUTError, NUTLoginError from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -21,7 +21,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import Event, HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -70,6 +70,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Fetch data from NUT.""" try: return await data.async_update() + except NUTLoginError as err: + raise ConfigEntryAuthFailed from err except NUTError as err: raise UpdateFailed(f"Error fetching UPS state: {err}") from err @@ -249,16 +251,9 @@ class PyNUTData: async def _async_get_alias(self) -> str | None: """Get the ups alias from NUT.""" - try: - ups_list = await self._client.list_ups() - except NUTError as err: - _LOGGER.error("Failure getting NUT ups alias, %s", err) - return None - - if not ups_list: + if not (ups_list := await self._client.list_ups()): _LOGGER.error("Empty list while getting NUT ups aliases") return None - self.ups_list = ups_list return list(ups_list)[0] diff --git a/homeassistant/components/nut/config_flow.py b/homeassistant/components/nut/config_flow.py index 3f3de8a126c..f0126ba4894 100644 --- a/homeassistant/components/nut/config_flow.py +++ b/homeassistant/components/nut/config_flow.py @@ -6,7 +6,7 @@ from collections.abc import Mapping import logging from typing import Any -from aionut import NUTError +from aionut import NUTError, NUTLoginError import voluptuous as vol from homeassistant.components import zeroconf @@ -26,28 +26,23 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import HomeAssistantError +from homeassistant.data_entry_flow import AbortFlow from . import PyNUTData from .const import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, DOMAIN _LOGGER = logging.getLogger(__name__) +AUTH_SCHEMA = {vol.Optional(CONF_USERNAME): str, vol.Optional(CONF_PASSWORD): str} -def _base_schema(discovery_info: zeroconf.ZeroconfServiceInfo | None) -> vol.Schema: + +def _base_schema(nut_config: dict[str, Any]) -> vol.Schema: """Generate base schema.""" - base_schema = {} - if not discovery_info: - base_schema.update( - { - vol.Optional(CONF_HOST, default=DEFAULT_HOST): str, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): int, - } - ) - base_schema.update( - {vol.Optional(CONF_USERNAME): str, vol.Optional(CONF_PASSWORD): str} - ) - + base_schema = { + vol.Optional(CONF_HOST, default=nut_config.get(CONF_HOST) or DEFAULT_HOST): str, + vol.Optional(CONF_PORT, default=nut_config.get(CONF_PORT) or DEFAULT_PORT): int, + } + base_schema.update(AUTH_SCHEMA) return vol.Schema(base_schema) @@ -69,13 +64,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, password = data.get(CONF_PASSWORD) nut_data = PyNUTData(host, port, alias, username, password, persistent=False) - try: - status = await nut_data.async_update() - except NUTError as err: - raise CannotConnect(str(err)) from err + status = await nut_data.async_update() if not alias and not nut_data.ups_list: - raise CannotConnect("No UPSes found on the NUT server") + raise AbortFlow("no_ups_found") return {"ups_list": nut_data.ups_list, "available_resources": status} @@ -98,20 +90,20 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the nut config flow.""" self.nut_config: dict[str, Any] = {} - self.discovery_info: zeroconf.ZeroconfServiceInfo | None = None self.ups_list: dict[str, str] | None = None self.title: str | None = None + self.reauth_entry: ConfigEntry | None = None async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo ) -> ConfigFlowResult: """Prepare configuration for a discovered nut device.""" - self.discovery_info = discovery_info await self._async_handle_discovery_without_unique_id() - self.context["title_placeholders"] = { + self.nut_config = { + CONF_HOST: discovery_info.host or DEFAULT_HOST, CONF_PORT: discovery_info.port or DEFAULT_PORT, - CONF_HOST: discovery_info.host, } + self.context["title_placeholders"] = self.nut_config.copy() return await self.async_step_user() async def async_step_user( @@ -119,29 +111,28 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Handle the user input.""" errors: dict[str, str] = {} + placeholders: dict[str, str] = {} + nut_config = self.nut_config if user_input is not None: - if self.discovery_info: - user_input.update( - { - CONF_HOST: self.discovery_info.host, - CONF_PORT: self.discovery_info.port or DEFAULT_PORT, - } - ) - info, errors = await self._async_validate_or_error(user_input) + nut_config.update(user_input) + + info, errors, placeholders = await self._async_validate_or_error(nut_config) if not errors: - self.nut_config.update(user_input) if len(info["ups_list"]) > 1: self.ups_list = info["ups_list"] return await self.async_step_ups() - if self._host_port_alias_already_configured(self.nut_config): + if self._host_port_alias_already_configured(nut_config): return self.async_abort(reason="already_configured") - title = _format_host_port_alias(self.nut_config) - return self.async_create_entry(title=title, data=self.nut_config) + title = _format_host_port_alias(nut_config) + return self.async_create_entry(title=title, data=nut_config) return self.async_show_form( - step_id="user", data_schema=_base_schema(self.discovery_info), errors=errors + step_id="user", + data_schema=_base_schema(nut_config), + errors=errors, + description_placeholders=placeholders, ) async def async_step_ups( @@ -149,20 +140,23 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Handle the picking the ups.""" errors: dict[str, str] = {} + placeholders: dict[str, str] = {} + nut_config = self.nut_config if user_input is not None: self.nut_config.update(user_input) - if self._host_port_alias_already_configured(self.nut_config): + if self._host_port_alias_already_configured(nut_config): return self.async_abort(reason="already_configured") - _, errors = await self._async_validate_or_error(self.nut_config) + _, errors, placeholders = await self._async_validate_or_error(nut_config) if not errors: - title = _format_host_port_alias(self.nut_config) - return self.async_create_entry(title=title, data=self.nut_config) + title = _format_host_port_alias(nut_config) + return self.async_create_entry(title=title, data=nut_config) return self.async_show_form( step_id="ups", data_schema=_ups_schema(self.ups_list or {}), errors=errors, + description_placeholders=placeholders, ) def _host_port_alias_already_configured(self, user_input: dict[str, Any]) -> bool: @@ -176,17 +170,66 @@ class NutConfigFlow(ConfigFlow, domain=DOMAIN): async def _async_validate_or_error( self, config: dict[str, Any] - ) -> tuple[dict[str, Any], dict[str, str]]: - errors = {} - info = {} + ) -> tuple[dict[str, Any], dict[str, str], dict[str, str]]: + errors: dict[str, str] = {} + info: dict[str, Any] = {} + description_placeholders: dict[str, str] = {} try: info = await validate_input(self.hass, config) - except CannotConnect: + except NUTLoginError: + errors[CONF_PASSWORD] = "invalid_auth" + except NUTError as ex: errors[CONF_BASE] = "cannot_connect" + description_placeholders["error"] = str(ex) + except AbortFlow: + raise except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors[CONF_BASE] = "unknown" - return info, errors + return info, errors, description_placeholders + + async def async_step_reauth( + self, entry_data: Mapping[str, Any] + ) -> ConfigFlowResult: + """Handle reauth.""" + entry_id = self.context["entry_id"] + self.reauth_entry = self.hass.config_entries.async_get_entry(entry_id) + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reauth input.""" + errors: dict[str, str] = {} + existing_entry = self.reauth_entry + assert existing_entry + existing_data = existing_entry.data + description_placeholders: dict[str, str] = { + CONF_HOST: existing_data[CONF_HOST], + CONF_PORT: existing_data[CONF_PORT], + } + if user_input is not None: + new_config = { + **existing_data, + # Username/password are optional and some servers + # use ip based authentication and will fail if + # username/password are provided + CONF_USERNAME: user_input.get(CONF_USERNAME), + CONF_PASSWORD: user_input.get(CONF_PASSWORD), + } + _, errors, placeholders = await self._async_validate_or_error(new_config) + if not errors: + return self.async_update_reload_and_abort( + existing_entry, data=new_config + ) + description_placeholders.update(placeholders) + + return self.async_show_form( + description_placeholders=description_placeholders, + step_id="reauth_confirm", + data_schema=vol.Schema(AUTH_SCHEMA), + errors=errors, + ) @staticmethod @callback @@ -220,7 +263,3 @@ class OptionsFlowHandler(OptionsFlow): } return self.async_show_form(step_id="init", data_schema=vol.Schema(base_schema)) - - -class CannotConnect(HomeAssistantError): - """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/nut/strings.json b/homeassistant/components/nut/strings.json index 3c446926fe0..d5b9acbdaad 100644 --- a/homeassistant/components/nut/strings.json +++ b/homeassistant/components/nut/strings.json @@ -18,14 +18,24 @@ "data": { "alias": "Alias" } + }, + "reauth_confirm": { + "description": "Re-authenticate NUT server at {host}:{port}", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } } }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "cannot_connect": "Connection error: {error}", + "unknown": "[%key:common::config_flow::error::unknown%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "no_ups_found": "There are no UPS devices available on the NUT server.", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "options": { diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index b6a9590f457..0fd9949ff37 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -3,7 +3,7 @@ from ipaddress import ip_address from unittest.mock import patch -from aionut import NUTError +from aionut import NUTError, NUTLoginError from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components import zeroconf @@ -232,8 +232,8 @@ async def test_form_user_one_ups_with_ignored_entry(hass: HomeAssistant) -> None assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_cannot_connect(hass: HomeAssistant) -> None: - """Test we handle cannot connect error.""" +async def test_form_no_upses_found(hass: HomeAssistant) -> None: + """Test we abort when the NUT server has not UPSes.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -254,15 +254,22 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: }, ) - assert result2["type"] == data_entry_flow.FlowResultType.FORM - assert result2["errors"] == {"base": "cannot_connect"} + assert result2["type"] is data_entry_flow.FlowResultType.ABORT + assert result2["reason"] == "no_ups_found" + + +async def test_form_cannot_connect(hass: HomeAssistant) -> None: + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) with patch( "homeassistant.components.nut.AIONUTClient.list_ups", - side_effect=NUTError, + side_effect=NUTError("no route to host"), ), patch( "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=NUTError, + side_effect=NUTError("no route to host"), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -276,6 +283,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] == {"base": "cannot_connect"} + assert result2["description_placeholders"] == {"error": "no route to host"} with patch( "homeassistant.components.nut.AIONUTClient.list_ups", @@ -297,6 +305,154 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] == {"base": "unknown"} + mock_pynut = _get_mock_nutclient( + list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] + ) + with patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_PORT: 2222, + }, + ) + await hass.async_block_till_done() + + assert result2["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY + assert result2["title"] == "1.1.1.1:2222" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + CONF_PASSWORD: "test-password", + CONF_PORT: 2222, + CONF_USERNAME: "test-username", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_auth_failures(hass: HomeAssistant) -> None: + """Test authentication failures.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + side_effect=NUTLoginError, + ), patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTLoginError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_PORT: 2222, + }, + ) + + assert result2["type"] is data_entry_flow.FlowResultType.FORM + assert result2["errors"] == {"password": "invalid_auth"} + + mock_pynut = _get_mock_nutclient( + list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] + ) + with patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "1.1.1.1", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_PORT: 2222, + }, + ) + await hass.async_block_till_done() + + assert result2["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY + assert result2["title"] == "1.1.1.1:2222" + assert result2["data"] == { + CONF_HOST: "1.1.1.1", + CONF_PASSWORD: "test-password", + CONF_PORT: 2222, + CONF_USERNAME: "test-username", + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_reauth(hass: HomeAssistant) -> None: + """Test reauth flow.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "1.1.1.1", + CONF_PORT: 123, + CONF_RESOURCES: ["battery.voltage"], + }, + ) + config_entry.add_to_hass(hass) + config_entry.async_start_reauth(hass) + await hass.async_block_till_done() + flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN) + assert len(flows) == 1 + flow = flows[0] + + with patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + side_effect=NUTLoginError, + ), patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTLoginError, + ): + result2 = await hass.config_entries.flow.async_configure( + flow["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + + assert result2["type"] is data_entry_flow.FlowResultType.FORM + assert result2["errors"] == {"password": "invalid_auth"} + + mock_pynut = _get_mock_nutclient( + list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] + ) + with patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + flow["flow_id"], + { + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] is data_entry_flow.FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + assert len(mock_setup_entry.mock_calls) == 1 + async def test_abort_if_already_setup(hass: HomeAssistant) -> None: """Test we abort if component is already setup.""" diff --git a/tests/components/nut/test_init.py b/tests/components/nut/test_init.py index d15e9d4b12a..4dd5f2357e8 100644 --- a/tests/components/nut/test_init.py +++ b/tests/components/nut/test_init.py @@ -2,7 +2,7 @@ from unittest.mock import patch -from aionut import NUTError +from aionut import NUTError, NUTLoginError from homeassistant.components.nut.const import DOMAIN from homeassistant.config_entries import ConfigEntryState @@ -66,3 +66,27 @@ async def test_config_not_ready(hass: HomeAssistant) -> None: await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_auth_fails(hass: HomeAssistant) -> None: + """Test for setup failure if auth has changed.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: "mock", CONF_PORT: "mock"}, + ) + entry.add_to_hass(hass) + + with patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + return_value={"ups1"}, + ), patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTLoginError, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + assert entry.state is ConfigEntryState.SETUP_ERROR + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["context"]["source"] == "reauth" From e2ee623d23bab84118c40aadbda9c284a6cd29a2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 25 Mar 2024 19:16:50 +0100 Subject: [PATCH 1497/1691] Add restrictions for listening to event_reported events (#114183) * Add restrictions for listening to event_reported events * Update homeassistant/core.py Co-authored-by: Martin Hjelmare --------- Co-authored-by: Martin Hjelmare --- homeassistant/core.py | 9 +++++++++ tests/test_core.py | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 533bddb6ceb..561cb45d39f 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1511,6 +1511,15 @@ class EventBus: """ if event_filter is not None and not is_callback_check_partial(event_filter): raise HomeAssistantError(f"Event filter {event_filter} is not a callback") + if event_type == EVENT_STATE_REPORTED: + if not event_filter: + raise HomeAssistantError( + f"Event filter is required for event {event_type}" + ) + if not run_immediately: + raise HomeAssistantError( + f"Run immediately must be set to True for event {event_type}" + ) return self._async_listen_filterable_job( event_type, ( diff --git a/tests/test_core.py b/tests/test_core.py index 61852c69e8b..ab6f0b11270 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3262,9 +3262,22 @@ async def test_eventbus_lazy_object_creation(hass: HomeAssistant) -> None: async def test_statemachine_report_state(hass: HomeAssistant) -> None: """Test report state event.""" + + @ha.callback + def filter(event_data): + """Mock filter.""" + return True + + @callback + def listener(event: ha.Event) -> None: + state_reported_events.append(event) + hass.states.async_set("light.bowl", "on", {}) state_changed_events = async_capture_events(hass, EVENT_STATE_CHANGED) - state_reported_events = async_capture_events(hass, EVENT_STATE_REPORTED) + state_reported_events = [] + hass.bus.async_listen( + EVENT_STATE_REPORTED, listener, event_filter=filter, run_immediately=True + ) hass.states.async_set("light.bowl", "on") await hass.async_block_till_done() @@ -3285,3 +3298,29 @@ async def test_statemachine_report_state(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert len(state_changed_events) == 3 assert len(state_reported_events) == 4 + + +async def test_report_state_listener_restrictions(hass: HomeAssistant) -> None: + """Test we enforce requirements for EVENT_STATE_REPORTED listeners.""" + + @ha.callback + def listener(event): + """Mock listener.""" + + @ha.callback + def filter(event_data): + """Mock filter.""" + return False + + # run_immediately not set + with pytest.raises(HomeAssistantError): + hass.bus.async_listen(EVENT_STATE_REPORTED, listener, event_filter=filter) + + # no filter + with pytest.raises(HomeAssistantError): + hass.bus.async_listen(EVENT_STATE_REPORTED, listener, run_immediately=True) + + # Both filter and run_immediately + hass.bus.async_listen( + EVENT_STATE_REPORTED, listener, event_filter=filter, run_immediately=True + ) From 6ceeb1e41f6f2fc8bf25b6726ff16a77a55eabbe Mon Sep 17 00:00:00 2001 From: Jim Date: Mon, 25 Mar 2024 20:09:34 +0000 Subject: [PATCH 1498/1691] Telegram bot proxy params deprecation (#112778) * Add strings for issues to raise in telegram integration startup * Allow proxy_params to be passed empty Allows migration away from proxy_params whilst retaining a configured proxy. * Raise issues for removing proxy_params config option * Add types to initialize_bot function * Add PR link for learn more URL Update issue message to leave a comment on the PR instead * Apply suggestions from code review Co-authored-by: Martin Hjelmare * hass should always be first argument * Update issues strings to give domain and better direction. * Update breaks_in_ha_version to something saner * Apply strings.json suggestions from code review Co-authored-by: Martin Hjelmare --------- Co-authored-by: Martin Hjelmare --- .../components/telegram_bot/__init__.py | 57 ++++++++++++++++--- .../components/telegram_bot/strings.json | 10 ++++ 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 7534f56ef4d..6338996256b 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -38,7 +38,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import TemplateError -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, issue_registry as ir from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_loaded_integration @@ -366,7 +366,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: for p_config in domain_config: # Each platform config gets its own bot - bot = initialize_bot(p_config) + bot = initialize_bot(hass, p_config) p_type: str = p_config[CONF_PLATFORM] platform = platforms[p_type] @@ -456,16 +456,55 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -def initialize_bot(p_config): +def initialize_bot(hass: HomeAssistant, p_config: dict) -> Bot: """Initialize telegram bot with proxy support.""" - api_key = p_config.get(CONF_API_KEY) - proxy_url = p_config.get(CONF_PROXY_URL) - proxy_params = p_config.get(CONF_PROXY_PARAMS) + api_key: str = p_config[CONF_API_KEY] + proxy_url: str | None = p_config.get(CONF_PROXY_URL) + proxy_params: dict | None = p_config.get(CONF_PROXY_PARAMS) if proxy_url is not None: - # These have been kept for backwards compatibility, they can actually be stuffed into the URL. - # Side note: In the future we should deprecate these and raise a repair issue if we find them here. - auth = proxy_params.pop("username"), proxy_params.pop("password") + auth = None + if proxy_params is None: + # CONF_PROXY_PARAMS has been kept for backwards compatibility. + proxy_params = {} + elif "username" in proxy_params and "password" in proxy_params: + # Auth can actually be stuffed into the URL, but the docs have previously + # indicated to put them here. + auth = proxy_params.pop("username"), proxy_params.pop("password") + ir.async_create_issue( + hass, + DOMAIN, + "proxy_params_auth_deprecation", + breaks_in_ha_version="2024.10.0", + is_persistent=False, + is_fixable=False, + severity=ir.IssueSeverity.WARNING, + translation_placeholders={ + "proxy_params": CONF_PROXY_PARAMS, + "proxy_url": CONF_PROXY_URL, + "telegram_bot": "Telegram bot", + }, + translation_key="proxy_params_auth_deprecation", + learn_more_url="https://github.com/home-assistant/core/pull/112778", + ) + else: + ir.async_create_issue( + hass, + DOMAIN, + "proxy_params_deprecation", + breaks_in_ha_version="2024.10.0", + is_persistent=False, + is_fixable=False, + severity=ir.IssueSeverity.WARNING, + translation_placeholders={ + "proxy_params": CONF_PROXY_PARAMS, + "proxy_url": CONF_PROXY_URL, + "httpx": "httpx", + "telegram_bot": "Telegram bot", + }, + translation_key="proxy_params_deprecation", + learn_more_url="https://github.com/home-assistant/core/pull/112778", + ) proxy = httpx.Proxy(proxy_url, auth=auth, **proxy_params) request = HTTPXRequest(connection_pool_size=8, proxy=proxy) else: diff --git a/homeassistant/components/telegram_bot/strings.json b/homeassistant/components/telegram_bot/strings.json index 2e1d51f31e8..aad42081274 100644 --- a/homeassistant/components/telegram_bot/strings.json +++ b/homeassistant/components/telegram_bot/strings.json @@ -636,5 +636,15 @@ } } } + }, + "issues": { + "proxy_params_auth_deprecation": { + "title": "{telegram_bot}: Proxy authentication should be moved to the URL", + "description": "Authentication details for the the proxy configured in the {telegram_bot} integration should be moved into the {proxy_url} instead. Please update your configuration and restart Home Assistant to fix this issue.\n\nThe {proxy_params} config key will be removed in a future release." + }, + "proxy_params_deprecation": { + "title": "{telegram_bot}: Proxy params option will be removed", + "description": "The {proxy_params} config key for the {telegram_bot} integration will be removed in a future release.\n\nAuthentication can now be provided through the {proxy_url} key.\n\nThe underlying library has changed to {httpx} which is incompatible with previous parameters. If you still need this functionality for other options, please leave a comment on the learn more link.\n\nPlease update your configuration to remove the {proxy_params} key and restart Home Assistant to fix this issue." + } } } From 9b682388f50da097d740edc139acc9b52559dfd7 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 25 Mar 2024 21:36:17 +0100 Subject: [PATCH 1499/1691] Add generic test component platform setup function (#114016) * Add generic entity platform setup fixture * rename fixture * No need for passing hass * Make function instead of fixture * Improve typing * Use Sequence instead * Migrate flux to use the new helper * Use list instead * Use Sequence instead --- tests/common.py | 48 ++++++++++++++++++++++++- tests/components/flux/test_switch.py | 54 ++++++++++------------------ 2 files changed, 66 insertions(+), 36 deletions(-) diff --git a/tests/common.py b/tests/common.py index 2b1db405de7..72b38a64650 100644 --- a/tests/common.py +++ b/tests/common.py @@ -79,8 +79,10 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.json import JSONEncoder, _orjson_default_encoder, json_dumps -from homeassistant.helpers.typing import ConfigType, StateType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType from homeassistant.setup import setup_component from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as dt_util @@ -1646,3 +1648,47 @@ def extract_stack_to_frame(extract_stack: list[Mock]) -> FrameType: current_frame = next_frame return top_frame + + +def setup_test_component_platform( + hass: HomeAssistant, + domain: str, + entities: Sequence[Entity], + from_config_entry: bool = False, +) -> MockPlatform: + """Mock a test component platform for tests.""" + + async def _async_setup_platform( + hass: HomeAssistant, + config: ConfigType, + async_add_entities: AddEntitiesCallback, + discovery_info: DiscoveryInfoType | None = None, + ) -> None: + """Set up a test component platform.""" + async_add_entities(entities) + + platform = MockPlatform( + async_setup_platform=_async_setup_platform, + ) + + # avoid loading config_entry if not needed + if from_config_entry: + from homeassistant.config_entries import ConfigEntry + + async def _async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up a test component platform.""" + async_add_entities(entities) + + platform.async_setup_entry = _async_setup_entry + platform.async_setup_platform = None + + mock_platform( + hass, + f"test.{domain}", + platform, + ) + return platform diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index e594fe5e7ee..b0432e60ce1 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -22,8 +22,9 @@ from tests.common import ( async_fire_time_changed, async_mock_service, mock_restore_cache, + setup_test_component_platform, ) -from tests.components.light.common import MockLight, SetupLightPlatformCallable +from tests.components.light.common import MockLight @pytest.fixture(autouse=True) @@ -138,11 +139,10 @@ async def test_invalid_config_no_lights(hass: HomeAssistant) -> None: async def test_flux_when_switch_is_off( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch when it is off.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -191,11 +191,10 @@ async def test_flux_when_switch_is_off( async def test_flux_before_sunrise( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch before sunrise.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -252,11 +251,10 @@ async def test_flux_before_sunrise( async def test_flux_before_sunrise_known_location( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch before sunrise.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -312,11 +310,10 @@ async def test_flux_before_sunrise_known_location( async def test_flux_after_sunrise_before_sunset( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunrise and before sunset.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -372,11 +369,10 @@ async def test_flux_after_sunrise_before_sunset( async def test_flux_after_sunset_before_stop( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunset and before stop.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -433,11 +429,10 @@ async def test_flux_after_sunset_before_stop( async def test_flux_after_stop_before_sunrise( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after stop and before sunrise.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -493,11 +488,10 @@ async def test_flux_after_stop_before_sunrise( async def test_flux_with_custom_start_stop_times( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux with custom start and stop times.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -555,14 +549,13 @@ async def test_flux_with_custom_start_stop_times( async def test_flux_before_sunrise_stop_next_day( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch before sunrise. This test has the stop_time on the next day (after midnight). """ - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -619,14 +612,13 @@ async def test_flux_before_sunrise_stop_next_day( async def test_flux_after_sunrise_before_sunset_stop_next_day( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunrise and before sunset. This test has the stop_time on the next day (after midnight). """ - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -685,14 +677,13 @@ async def test_flux_after_sunrise_before_sunset_stop_next_day( async def test_flux_after_sunset_before_midnight_stop_next_day( hass: HomeAssistant, x, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunset and before stop. This test has the stop_time on the next day (after midnight). """ - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -749,14 +740,13 @@ async def test_flux_after_sunset_before_midnight_stop_next_day( async def test_flux_after_sunset_after_midnight_stop_next_day( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after sunset and before stop. This test has the stop_time on the next day (after midnight). """ - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -813,14 +803,13 @@ async def test_flux_after_sunset_after_midnight_stop_next_day( async def test_flux_after_stop_before_sunrise_stop_next_day( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch after stop and before sunrise. This test has the stop_time on the next day (after midnight). """ - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -877,11 +866,10 @@ async def test_flux_after_stop_before_sunrise_stop_next_day( async def test_flux_with_custom_colortemps( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux with custom start and stop colortemps.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -940,11 +928,10 @@ async def test_flux_with_custom_colortemps( async def test_flux_with_custom_brightness( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux with custom start and stop colortemps.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -1002,11 +989,10 @@ async def test_flux_with_custom_brightness( async def test_flux_with_multiple_lights( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch with multiple light entities.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -1085,11 +1071,10 @@ async def test_flux_with_multiple_lights( async def test_flux_with_mired( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch´s mode mired.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -1144,11 +1129,10 @@ async def test_flux_with_mired( async def test_flux_with_rgb( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the flux switch´s mode rgb.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} From 121182167f046d1e42a00ae4d58c325072bad7e0 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Mon, 25 Mar 2024 23:27:44 +0200 Subject: [PATCH 1500/1691] Fix Shelly reauth flow (#114180) * Fix Shelly reauth flow * Rename shutdown_device to async_shutdown_device --- homeassistant/components/shelly/__init__.py | 9 +++------ homeassistant/components/shelly/climate.py | 7 +++++-- .../components/shelly/config_flow.py | 13 +++++++++--- .../components/shelly/coordinator.py | 20 ++++++++++++++----- homeassistant/components/shelly/entity.py | 4 ++-- homeassistant/components/shelly/number.py | 2 +- homeassistant/components/shelly/update.py | 4 ++-- homeassistant/components/shelly/utils.py | 8 ++++++++ 8 files changed, 46 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 523c6a67433..7d23a1cd57d 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -53,6 +53,7 @@ from .coordinator import ( ) from .utils import ( async_create_issue_unsupported_firmware, + async_shutdown_device, get_block_device_sleep_period, get_coap_context, get_device_entry_gen, @@ -339,12 +340,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: shelly_entry_data = get_entry_data(hass)[entry.entry_id] # If device is present, block/rpc coordinator is not setup yet - device = shelly_entry_data.device - if isinstance(device, RpcDevice): - await device.shutdown() - return True - if isinstance(device, BlockDevice): - device.shutdown() + if (device := shelly_entry_data.device) is not None: + await async_shutdown_device(device) return True platforms = RPC_SLEEPING_PLATFORMS diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index e155edf4f4c..b368b38820e 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -319,7 +319,7 @@ class BlockSleepingClimate( f" {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -436,7 +436,10 @@ class BlockSleepingClimate( ]["schedule_profile_names"], ] except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + self.hass.async_create_task( + self.coordinator.async_shutdown_device_and_start_reauth(), + eager_start=True, + ) else: self.async_write_ha_state() diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index ca1190de708..24b66e15893 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -24,7 +24,13 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig @@ -84,6 +90,7 @@ async def validate_input( ip_address=host, username=data.get(CONF_USERNAME), password=data.get(CONF_PASSWORD), + device_mac=info[CONF_MAC], port=port, ) @@ -153,7 +160,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - await self.async_set_unique_id(self.info["mac"]) + await self.async_set_unique_id(self.info[CONF_MAC]) self._abort_if_unique_id_configured({CONF_HOST: host}) self.host = host self.port = port @@ -286,7 +293,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): if not mac: # We could not get the mac address from the name # so need to check here since we just got the info - await self._async_discovered_mac(self.info["mac"], host) + await self._async_discovered_mac(self.info[CONF_MAC], host) self.host = host self.context.update( diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 50c352bcb25..c52585c3363 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -58,6 +58,7 @@ from .const import ( BLEScannerMode, ) from .utils import ( + async_shutdown_device, get_device_entry_gen, get_http_port, get_rpc_device_wakeup_period, @@ -151,6 +152,14 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]): LOGGER.debug("Reloading entry %s", self.name) await self.hass.config_entries.async_reload(self.entry.entry_id) + async def async_shutdown_device_and_start_reauth(self) -> None: + """Shutdown Shelly device and start reauth flow.""" + # not running disconnect events since we have auth error + # and won't be able to send commands to the device + self.last_update_success = False + await async_shutdown_device(self.device) + self.entry.async_start_reauth(self.hass) + class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]): """Coordinator for a Shelly block based device.""" @@ -300,7 +309,7 @@ class ShellyBlockCoordinator(ShellyCoordinatorBase[BlockDevice]): except DeviceConnectionError as err: raise UpdateFailed(f"Error fetching data: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() @callback def _async_handle_update( @@ -384,7 +393,7 @@ class ShellyRestCoordinator(ShellyCoordinatorBase[BlockDevice]): except DeviceConnectionError as err: raise UpdateFailed(f"Error fetching data: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() else: update_device_fw_info(self.hass, self.device, self.entry) @@ -540,7 +549,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): except DeviceConnectionError as err: raise UpdateFailed(f"Device disconnected: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() async def _async_disconnected(self) -> None: """Handle device disconnected.""" @@ -633,7 +642,8 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): try: await async_stop_scanner(self.device) except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() + return await self.device.shutdown() await self._async_disconnected() @@ -663,7 +673,7 @@ class ShellyRpcPollingCoordinator(ShellyCoordinatorBase[RpcDevice]): except (DeviceConnectionError, RpcCallError) as err: raise UpdateFailed(f"Device disconnected: {repr(err)}") from err except InvalidAuthError: - self.entry.async_start_reauth(self.hass) + await self.async_shutdown_device_and_start_reauth() def get_block_coordinator_by_device_id( diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index d5202713405..edcee5c2dda 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -344,7 +344,7 @@ class ShellyBlockEntity(CoordinatorEntity[ShellyBlockCoordinator]): f" {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() class ShellyRpcEntity(CoordinatorEntity[ShellyRpcCoordinator]): @@ -397,7 +397,7 @@ class ShellyRpcEntity(CoordinatorEntity[ShellyRpcCoordinator]): f" {params}, error: {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() class ShellyBlockAttributeEntity(ShellyBlockEntity, Entity): diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index 1b1c9c42a11..6fdf05fa9cb 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -126,4 +126,4 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, RestoreNumber): f" {repr(err)}" ) from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index 414bb4d6258..f6a89c5381b 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -200,7 +200,7 @@ class RestUpdateEntity(ShellyRestAttributeEntity, UpdateEntity): except DeviceConnectionError as err: raise HomeAssistantError(f"Error starting OTA update: {repr(err)}") from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() else: LOGGER.debug("Result of OTA update call: %s", result) @@ -289,7 +289,7 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): except RpcCallError as err: raise HomeAssistantError(f"OTA update request error: {repr(err)}") from err except InvalidAuthError: - self.coordinator.entry.async_start_reauth(self.hass) + await self.coordinator.async_shutdown_device_and_start_reauth() else: self._ota_in_progress = True LOGGER.debug("OTA update call successful") diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index dd0e685fd67..d26e3dc11f3 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -480,3 +480,11 @@ def is_rpc_wifi_stations_disabled( def get_http_port(data: MappingProxyType[str, Any]) -> int: """Get port from config entry data.""" return cast(int, data.get(CONF_PORT, DEFAULT_HTTP_PORT)) + + +async def async_shutdown_device(device: BlockDevice | RpcDevice) -> None: + """Shutdown a Shelly device.""" + if isinstance(device, RpcDevice): + await device.shutdown() + if isinstance(device, BlockDevice): + device.shutdown() From 18174ad47ded1368f4d5e7f883b80c9607a88e61 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 25 Mar 2024 22:41:11 +0100 Subject: [PATCH 1501/1691] Improve test coverage mqtt discovery test (#114188) --- tests/components/mqtt/test_discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 3d3baf1307c..baa3542a22d 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1545,6 +1545,7 @@ async def test_mqtt_discovery_unsubscribe_once( async def async_step_mqtt(self, discovery_info: MqttServiceInfo) -> FlowResult: """Test mqtt step.""" + await asyncio.sleep(0.1) return self.async_abort(reason="already_configured") with mock_config_flow("comp", TestFlow): From 2f0cfc8b61cf647edf70156432c06576bfc717fa Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 25 Mar 2024 23:39:31 +0100 Subject: [PATCH 1502/1691] Remove deprecated `hass.components` from system_health tests (#114207) --- tests/components/system_health/test_init.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/components/system_health/test_init.py b/tests/components/system_health/test_init.py index 0767d9c3ff1..e677b7d1d34 100644 --- a/tests/components/system_health/test_init.py +++ b/tests/components/system_health/test_init.py @@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, Mock, patch from aiohttp.client_exceptions import ClientError from homeassistant.components import system_health +from homeassistant.components.system_health import async_register_info from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component @@ -74,7 +75,7 @@ async def test_info_endpoint_register_callback( async def mock_info(hass): return {"storage": "YAML"} - hass.components.system_health.async_register_info("lovelace", mock_info) + async_register_info(hass, "lovelace", mock_info) assert await async_setup_component(hass, "system_health", {}) data = await gather_system_health_info(hass, hass_ws_client) @@ -94,7 +95,7 @@ async def test_info_endpoint_register_callback_timeout( async def mock_info(hass): raise TimeoutError - hass.components.system_health.async_register_info("lovelace", mock_info) + async_register_info(hass, "lovelace", mock_info) assert await async_setup_component(hass, "system_health", {}) data = await gather_system_health_info(hass, hass_ws_client) @@ -111,7 +112,7 @@ async def test_info_endpoint_register_callback_exc( async def mock_info(hass): raise Exception("TEST ERROR") - hass.components.system_health.async_register_info("lovelace", mock_info) + async_register_info(hass, "lovelace", mock_info) assert await async_setup_component(hass, "system_health", {}) data = await gather_system_health_info(hass, hass_ws_client) From 27219b69628adfb6466987ec576127b305b1edbc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 12:52:28 -1000 Subject: [PATCH 1503/1691] Bump anyio to 4.3.0 (#114198) --- homeassistant/package_constraints.txt | 2 +- script/gen_requirements_all.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 03c17397789..7847a6416af 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -105,7 +105,7 @@ regex==2021.8.28 # these requirements are quite loose. As the entire stack has some outstanding issues, and # even newer versions seem to introduce new issues, it's useful for us to pin all these # requirements so we can directly link HA versions to these library versions. -anyio==4.1.0 +anyio==4.3.0 h11==0.14.0 httpcore==1.0.4 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index ddc197ed7a8..6537bc652ac 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -98,7 +98,7 @@ regex==2021.8.28 # these requirements are quite loose. As the entire stack has some outstanding issues, and # even newer versions seem to introduce new issues, it's useful for us to pin all these # requirements so we can directly link HA versions to these library versions. -anyio==4.1.0 +anyio==4.3.0 h11==0.14.0 httpcore==1.0.4 From 6bb4e7d62c60389608acf4a7d7dacd8f029307dd Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 26 Mar 2024 00:02:16 +0100 Subject: [PATCH 1504/1691] Bump ruff to 0.3.4 (#112690) Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- homeassistant/backports/functools.py | 6 +- .../components/airvisual/__init__.py | 12 +- homeassistant/components/amcrest/__init__.py | 7 +- homeassistant/components/amcrest/sensor.py | 24 +- .../components/apcupsd/diagnostics.py | 1 + homeassistant/components/aranet/__init__.py | 16 +- .../components/assist_pipeline/pipeline.py | 12 +- .../components/asuswrt/device_tracker.py | 6 +- homeassistant/components/august/lock.py | 6 +- homeassistant/components/auth/indieauth.py | 7 +- homeassistant/components/awair/config_flow.py | 6 +- .../components/axis/hub/entity_loader.py | 1 + .../components/bluemaestro/__init__.py | 16 +- .../bluetooth/passive_update_processor.py | 6 +- homeassistant/components/bluetooth/util.py | 16 +- .../components/bmw_connected_drive/lock.py | 6 +- .../components/bosch_shc/__init__.py | 6 +- .../components/brottsplatskartan/sensor.py | 6 +- homeassistant/components/bthome/__init__.py | 30 +- .../components/canary/coordinator.py | 6 +- .../components/climate/device_trigger.py | 12 +- homeassistant/components/co2signal/sensor.py | 6 +- homeassistant/components/daikin/climate.py | 6 +- homeassistant/components/debugpy/__init__.py | 4 +- .../components/deconz/deconz_event.py | 7 +- .../components/device_automation/__init__.py | 12 +- .../devolo_home_network/device_tracker.py | 6 +- .../components/diagnostics/__init__.py | 18 +- homeassistant/components/diagnostics/util.py | 3 +- homeassistant/components/ecovacs/event.py | 1 - .../entur_public_transport/sensor.py | 6 +- .../components/esphome/alarm_control_panel.py | 30 +- homeassistant/components/esphome/entity.py | 16 +- .../components/esphome/enum_mapper.py | 6 +- homeassistant/components/esphome/sensor.py | 18 +- .../components/file_upload/__init__.py | 6 +- .../components/forked_daapd/config_flow.py | 6 +- .../components/forked_daapd/media_player.py | 12 +- .../components/fronius/coordinator.py | 12 +- .../components/geo_rss_events/sensor.py | 6 +- .../components/geofency/device_tracker.py | 6 +- .../components/govee_ble/__init__.py | 16 +- .../components/gpslogger/device_tracker.py | 6 +- homeassistant/components/gtfs/sensor.py | 8 +- homeassistant/components/guardian/__init__.py | 20 +- homeassistant/components/hassio/http.py | 6 +- homeassistant/components/heos/media_player.py | 6 +- homeassistant/components/http/decorators.py | 6 +- .../components/humidifier/device_trigger.py | 12 +- .../hunterdouglas_powerview/__init__.py | 1 + homeassistant/components/icloud/account.py | 6 +- homeassistant/components/imap/__init__.py | 6 +- homeassistant/components/influxdb/__init__.py | 6 +- homeassistant/components/inkbird/__init__.py | 16 +- homeassistant/components/iqvia/sensor.py | 18 +- homeassistant/components/isy994/helpers.py | 6 +- homeassistant/components/kegtron/__init__.py | 16 +- homeassistant/components/leaone/__init__.py | 16 +- homeassistant/components/lidarr/sensor.py | 11 +- homeassistant/components/light/__init__.py | 12 +- .../components/locative/device_tracker.py | 6 +- homeassistant/components/london_air/sensor.py | 6 +- homeassistant/components/moat/__init__.py | 16 +- .../components/mobile_app/webhook.py | 6 +- homeassistant/components/modbus/modbus.py | 4 +- homeassistant/components/mopeka/__init__.py | 16 +- homeassistant/components/mqtt/config_flow.py | 18 +- homeassistant/components/mqtt/mixins.py | 18 +- homeassistant/components/mutesync/__init__.py | 16 +- .../components/mysensors/config_flow.py | 6 +- homeassistant/components/mysensors/gateway.py | 6 +- homeassistant/components/netatmo/climate.py | 18 +- .../components/netatmo/config_flow.py | 12 +- .../components/nissan_leaf/__init__.py | 6 +- .../components/nzbget/config_flow.py | 6 +- homeassistant/components/onvif/camera.py | 6 +- homeassistant/components/onvif/parsers.py | 6 +- homeassistant/components/oralb/__init__.py | 28 +- homeassistant/components/overkiz/number.py | 6 +- homeassistant/components/plex/sensor.py | 8 +- homeassistant/components/plex/server.py | 6 +- .../components/progettihwsw/config_flow.py | 14 +- .../components/proximity/coordinator.py | 24 +- homeassistant/components/qingping/__init__.py | 16 +- homeassistant/components/radarr/sensor.py | 11 +- homeassistant/components/rapt_ble/__init__.py | 16 +- .../components/recollect_waste/sensor.py | 6 +- .../components/recorder/migration.py | 26 +- .../components/recorder/models/time.py | 12 +- .../components/rejseplanen/sensor.py | 6 +- .../components/reolink/config_flow.py | 6 +- homeassistant/components/romy/entity.py | 1 - .../components/ruuvitag_ble/__init__.py | 16 +- .../components/sabnzbd/config_flow.py | 6 +- .../components/samsungtv/config_flow.py | 24 +- .../components/sensirion_ble/__init__.py | 16 +- homeassistant/components/sensor/__init__.py | 6 +- .../components/sensorpro/__init__.py | 16 +- .../components/sensorpush/__init__.py | 16 +- homeassistant/components/shelly/entity.py | 6 +- .../components/slimproto/media_player.py | 6 +- homeassistant/components/smtp/notify.py | 6 +- .../components/sonarr/config_flow.py | 6 +- homeassistant/components/sonarr/sensor.py | 6 +- homeassistant/components/sonos/helpers.py | 6 +- homeassistant/components/spotify/__init__.py | 16 +- .../components/squeezebox/media_player.py | 6 +- homeassistant/components/ssdp/__init__.py | 6 +- homeassistant/components/statistics/sensor.py | 6 +- homeassistant/components/stream/recorder.py | 7 +- homeassistant/components/stream/worker.py | 6 +- homeassistant/components/suez_water/sensor.py | 30 +- .../components/switcher_kis/__init__.py | 6 +- .../components/system_bridge/media_player.py | 14 +- .../components/systemmonitor/coordinator.py | 6 +- homeassistant/components/tado/climate.py | 12 +- .../components/tasmota/binary_sensor.py | 12 +- homeassistant/components/tasmota/cover.py | 12 +- .../components/tasmota/device_automation.py | 12 +- homeassistant/components/tasmota/fan.py | 12 +- homeassistant/components/tasmota/light.py | 12 +- homeassistant/components/tasmota/sensor.py | 12 +- homeassistant/components/tasmota/switch.py | 12 +- .../components/template/template_entity.py | 21 +- homeassistant/components/template/weather.py | 18 +- .../components/thermobeacon/__init__.py | 16 +- .../components/thermopro/__init__.py | 16 +- .../components/thermoworks_smoke/sensor.py | 6 +- homeassistant/components/tilt_ble/__init__.py | 16 +- homeassistant/components/tolo/const.py | 1 + .../components/traccar/device_tracker.py | 6 +- homeassistant/components/tuya/base.py | 9 +- .../components/twentemilieu/__init__.py | 16 +- homeassistant/components/unifi/config_flow.py | 18 +- .../components/unifiprotect/entity.py | 6 +- homeassistant/components/upnp/device.py | 6 +- .../components/utility_meter/__init__.py | 12 +- homeassistant/components/verisure/switch.py | 6 +- homeassistant/components/vicare/climate.py | 12 +- .../components/vulcan/config_flow.py | 6 +- homeassistant/components/vulcan/fetch_data.py | 6 +- .../components/wallbox/coordinator.py | 6 +- .../components/websocket_api/auth.py | 8 +- .../components/xiaomi_ble/__init__.py | 40 +- .../zha/core/cluster_handlers/__init__.py | 6 +- .../components/zha/core/discovery.py | 6 +- homeassistant/components/zha/core/gateway.py | 6 +- .../components/zha/core/registries.py | 36 +- homeassistant/components/zwave_js/__init__.py | 6 +- .../components/zwave_js/binary_sensor.py | 4 +- .../components/zwave_js/triggers/event.py | 6 +- homeassistant/config.py | 6 +- homeassistant/config_entries.py | 12 +- homeassistant/core.py | 42 +- homeassistant/exceptions.py | 6 +- .../helpers/config_entry_oauth2_flow.py | 6 +- homeassistant/helpers/config_validation.py | 9 +- homeassistant/helpers/dispatcher.py | 26 +- homeassistant/helpers/entity_platform.py | 6 +- homeassistant/helpers/floor_registry.py | 14 +- homeassistant/helpers/integration_platform.py | 6 +- homeassistant/helpers/redact.py | 6 +- homeassistant/helpers/reload.py | 9 +- .../helpers/schema_config_entry_flow.py | 32 +- homeassistant/helpers/service.py | 12 +- homeassistant/helpers/template.py | 12 +- homeassistant/loader.py | 6 +- homeassistant/setup.py | 6 +- homeassistant/util/dt.py | 9 +- homeassistant/util/logging.py | 6 +- homeassistant/util/variance.py | 9 +- homeassistant/util/yaml/loader.py | 11 +- requirements_test_pre_commit.txt | 2 +- script/translations/migrate.py | 6 +- tests/auth/mfa_modules/test_notify.py | 5 +- tests/auth/test_auth_store.py | 14 +- tests/auth/test_init.py | 18 +- tests/common.py | 94 +- tests/components/abode/common.py | 5 +- tests/components/abode/test_init.py | 34 +- tests/components/accuweather/__init__.py | 24 +- .../accuweather/test_config_flow.py | 44 +- tests/components/accuweather/test_init.py | 17 +- tests/components/accuweather/test_sensor.py | 60 +- tests/components/accuweather/test_weather.py | 67 +- tests/components/adax/test_config_flow.py | 33 +- .../advantage_air/test_config_flow.py | 17 +- tests/components/airnow/conftest.py | 5 +- tests/components/airq/test_config_flow.py | 10 +- .../components/airthings/test_config_flow.py | 17 +- .../airthings_ble/test_config_flow.py | 72 +- .../components/airtouch4/test_config_flow.py | 17 +- tests/components/airvisual/conftest.py | 29 +- tests/components/airvisual_pro/conftest.py | 13 +- tests/components/airzone/test_climate.py | 80 +- tests/components/airzone/test_config_flow.py | 215 +++-- tests/components/airzone/test_coordinator.py | 36 +- tests/components/airzone/test_init.py | 87 +- tests/components/airzone/test_sensor.py | 36 +- tests/components/airzone/test_water_heater.py | 11 +- tests/components/airzone/util.py | 36 +- tests/components/airzone_cloud/conftest.py | 15 +- .../components/airzone_cloud/test_climate.py | 55 +- .../airzone_cloud/test_config_flow.py | 79 +- .../airzone_cloud/test_coordinator.py | 36 +- tests/components/airzone_cloud/test_init.py | 36 +- tests/components/airzone_cloud/util.py | 36 +- .../aladdin_connect/test_config_flow.py | 53 +- tests/components/aladdin_connect/test_init.py | 15 +- .../alarmdecoder/test_config_flow.py | 36 +- tests/components/ambient_station/conftest.py | 11 +- tests/components/analytics/test_analytics.py | 258 +++--- .../components/analytics_insights/conftest.py | 15 +- .../components/androidtv/test_config_flow.py | 57 +- .../components/androidtv/test_media_player.py | 264 +++--- tests/components/anova/__init__.py | 16 +- tests/components/anova/test_config_flow.py | 46 +- tests/components/aosmith/test_config_flow.py | 29 +- tests/components/aosmith/test_init.py | 15 +- tests/components/apache_kafka/test_init.py | 8 +- tests/components/apcupsd/test_diagnostics.py | 1 + tests/components/apprise/test_notify.py | 15 +- tests/components/arcam_fmj/conftest.py | 11 +- .../aseko_pool_live/test_config_flow.py | 17 +- .../assist_pipeline/test_pipeline.py | 7 +- .../assist_pipeline/test_websocket.py | 37 +- tests/components/august/mocks.py | 5 +- tests/components/august/test_config_flow.py | 167 ++-- tests/components/aurora/test_config_flow.py | 17 +- .../aurora_abb_powerone/test_config_flow.py | 69 +- .../aurora_abb_powerone/test_init.py | 30 +- .../aurora_abb_powerone/test_sensor.py | 179 ++-- tests/components/aussie_broadband/common.py | 32 +- .../aussie_broadband/test_config_flow.py | 117 ++- tests/components/auth/test_login_flow.py | 14 +- tests/components/automation/test_init.py | 19 +- tests/components/awair/test_config_flow.py | 89 +- tests/components/aws/test_init.py | 22 +- tests/components/axis/test_hub.py | 32 +- tests/components/axis/test_light.py | 22 +- .../azure_devops/test_config_flow.py | 91 +- tests/components/backup/test_http.py | 16 +- tests/components/backup/test_manager.py | 85 +- tests/components/baf/test_config_flow.py | 22 +- tests/components/balboa/test_config_flow.py | 32 +- tests/components/bang_olufsen/conftest.py | 13 +- tests/components/blink/test_config_flow.py | 178 ++-- tests/components/blueprint/test_models.py | 19 +- tests/components/bluetooth/__init__.py | 15 +- tests/components/bluetooth/conftest.py | 293 +++--- .../components/bluetooth/test_base_scanner.py | 13 +- .../components/bluetooth/test_config_flow.py | 98 +- .../components/bluetooth/test_diagnostics.py | 73 +- tests/components/bluetooth/test_init.py | 311 ++++--- tests/components/bluetooth/test_manager.py | 18 +- tests/components/bluetooth/test_models.py | 40 +- .../test_passive_update_coordinator.py | 14 +- .../test_passive_update_processor.py | 18 +- tests/components/bluetooth/test_scanner.py | 178 ++-- tests/components/bluetooth/test_wrappers.py | 55 +- .../bmw_connected_drive/test_config_flow.py | 55 +- tests/components/bond/common.py | 38 +- tests/components/bond/test_config_flow.py | 133 ++- tests/components/bond/test_cover.py | 15 +- tests/components/bond/test_fan.py | 20 +- tests/components/bond/test_init.py | 147 +-- tests/components/bond/test_light.py | 32 +- tests/components/bond/test_switch.py | 8 +- .../components/bosch_shc/test_config_flow.py | 471 ++++++---- tests/components/braviatv/test_config_flow.py | 85 +- tests/components/braviatv/test_diagnostics.py | 21 +- tests/components/bring/conftest.py | 15 +- .../components/broadlink/test_config_flow.py | 9 +- tests/components/broadlink/test_device.py | 86 +- tests/components/brother/__init__.py | 9 +- tests/components/brother/test_config_flow.py | 88 +- tests/components/brother/test_diagnostics.py | 12 +- tests/components/brother/test_init.py | 5 +- tests/components/brother/test_sensor.py | 26 +- tests/components/bsblan/conftest.py | 7 +- tests/components/bthome/test_config_flow.py | 15 +- tests/components/camera/conftest.py | 33 +- tests/components/camera/test_init.py | 257 +++-- tests/components/camera/test_media_source.py | 9 +- tests/components/canary/conftest.py | 14 +- tests/components/canary/test_config_flow.py | 5 +- tests/components/cast/conftest.py | 50 +- tests/components/cast/test_config_flow.py | 16 +- .../cast/test_home_assistant_cast.py | 7 +- tests/components/cast/test_media_player.py | 73 +- .../cert_expiry/test_config_flow.py | 52 +- tests/components/cert_expiry/test_init.py | 13 +- tests/components/cert_expiry/test_sensors.py | 63 +- tests/components/cloud/conftest.py | 5 +- tests/components/cloud/test_account_link.py | 72 +- tests/components/cloud/test_alexa_config.py | 20 +- tests/components/cloud/test_client.py | 7 +- tests/components/cloud/test_config_flow.py | 15 +- tests/components/cloud/test_google_config.py | 36 +- tests/components/cloud/test_http_api.py | 94 +- tests/components/cloud/test_init.py | 12 +- tests/components/cloudflare/test_init.py | 11 +- tests/components/co2signal/conftest.py | 15 +- tests/components/coinbase/test_config_flow.py | 106 ++- tests/components/coinbase/test_diagnostics.py | 18 +- tests/components/coinbase/test_init.py | 58 +- tests/components/comelit/test_config_flow.py | 61 +- tests/components/config/conftest.py | 28 +- tests/components/config/test_core.py | 18 +- tests/components/control4/test_config_flow.py | 39 +- tests/components/conversation/conftest.py | 5 +- .../components/coolmaster/test_config_flow.py | 17 +- tests/components/datadog/test_init.py | 24 +- tests/components/deconz/test_gateway.py | 11 +- tests/components/deconz/test_init.py | 16 +- tests/components/default_config/test_init.py | 8 +- tests/components/deluge/test_config_flow.py | 23 +- tests/components/demo/test_update.py | 11 +- tests/components/denonavr/test_config_flow.py | 79 +- .../components/denonavr/test_media_player.py | 11 +- .../components/device_automation/test_init.py | 11 +- tests/components/device_tracker/test_init.py | 22 +- .../components/device_tracker/test_legacy.py | 5 +- .../devolo_home_control/conftest.py | 22 +- .../devolo_home_control/test_config_flow.py | 60 +- .../devolo_home_network/test_init.py | 11 +- tests/components/dexcom/__init__.py | 15 +- tests/components/dexcom/test_config_flow.py | 17 +- tests/components/dexcom/test_sensor.py | 15 +- tests/components/dhcp/test_init.py | 153 +-- tests/components/discovergy/conftest.py | 15 +- tests/components/dlna_dmr/test_config_flow.py | 6 +- tests/components/dnsip/test_config_flow.py | 49 +- tests/components/doorbird/test_config_flow.py | 45 +- .../dormakaba_dkey/test_config_flow.py | 32 +- .../components/downloader/test_config_flow.py | 26 +- .../dremel_3d_printer/test_button.py | 11 +- tests/components/dsmr/conftest.py | 59 +- tests/components/dsmr/test_config_flow.py | 7 +- tests/components/dunehd/test_config_flow.py | 10 +- tests/components/dynalite/test_init.py | 34 +- tests/components/ecobee/test_config_flow.py | 36 +- tests/components/econet/test_config_flow.py | 44 +- tests/components/ecovacs/conftest.py | 30 +- .../electrasmart/test_config_flow.py | 52 +- tests/components/elgato/conftest.py | 9 +- tests/components/elkm1/__init__.py | 15 +- tests/components/elkm1/test_config_flow.py | 439 +++++---- tests/components/emonitor/test_config_flow.py | 32 +- tests/components/emulated_hue/test_init.py | 18 +- tests/components/emulated_roku/test_init.py | 22 +- tests/components/enocean/test_config_flow.py | 10 +- tests/components/enphase_envoy/conftest.py | 15 +- tests/components/enphase_envoy/test_sensor.py | 22 +- .../environment_canada/test_config_flow.py | 27 +- .../environment_canada/test_diagnostics.py | 28 +- tests/components/epion/conftest.py | 15 +- tests/components/epson/test_config_flow.py | 24 +- tests/components/epson/test_media_player.py | 15 +- tests/components/escea/test_config_flow.py | 24 +- tests/components/esphome/conftest.py | 12 +- tests/components/esphome/test_dashboard.py | 28 +- tests/components/esphome/test_update.py | 64 +- tests/components/evil_genius_labs/conftest.py | 29 +- .../evil_genius_labs/test_config_flow.py | 31 +- .../components/evil_genius_labs/test_light.py | 9 +- tests/components/ezviz/conftest.py | 16 +- .../components/faa_delays/test_config_flow.py | 11 +- tests/components/feedreader/test_init.py | 28 +- tests/components/ffmpeg/test_init.py | 15 +- tests/components/file/test_notify.py | 7 +- tests/components/file_upload/test_init.py | 56 +- tests/components/filter/test_sensor.py | 30 +- .../fireservicerota/test_config_flow.py | 28 +- tests/components/firmata/test_config_flow.py | 16 +- tests/components/fitbit/conftest.py | 15 +- tests/components/fivem/test_config_flow.py | 17 +- tests/components/flexit_bacnet/conftest.py | 15 +- .../flick_electric/test_config_flow.py | 17 +- tests/components/flume/test_config_flow.py | 108 ++- tests/components/flux/test_switch.py | 144 ++- tests/components/flux_led/__init__.py | 15 +- tests/components/flux_led/test_config_flow.py | 100 +- tests/components/flux_led/test_init.py | 44 +- .../forked_daapd/test_config_flow.py | 17 +- tests/components/foscam/test_config_flow.py | 15 +- tests/components/freebox/conftest.py | 5 +- tests/components/freedompro/conftest.py | 42 +- tests/components/fritz/test_config_flow.py | 168 ++-- tests/components/fritzbox/conftest.py | 5 +- tests/components/fritzbox/test_config_flow.py | 7 +- .../fritzbox_callmonitor/test_config_flow.py | 113 ++- tests/components/fronius/test_config_flow.py | 100 +- .../components/gardena_bluetooth/conftest.py | 11 +- .../gardena_bluetooth/test_number.py | 12 +- .../gardena_bluetooth/test_switch.py | 12 +- tests/components/gdacs/test_geo_location.py | 15 +- tests/components/gdacs/test_sensor.py | 7 +- tests/components/generic/test_camera.py | 5 +- tests/components/generic/test_config_flow.py | 50 +- .../geo_json_events/test_geo_location.py | 7 +- .../geonetnz_quakes/test_config_flow.py | 24 +- .../geonetnz_quakes/test_geo_location.py | 5 +- .../components/geonetnz_quakes/test_sensor.py | 7 +- .../geonetnz_volcano/test_config_flow.py | 24 +- .../geonetnz_volcano/test_sensor.py | 20 +- tests/components/gios/__init__.py | 27 +- tests/components/gios/test_config_flow.py | 49 +- tests/components/gios/test_init.py | 23 +- tests/components/gios/test_sensor.py | 28 +- tests/components/gogogate2/test_init.py | 11 +- tests/components/goodwe/test_config_flow.py | 26 +- .../components/google_assistant/test_http.py | 18 +- .../google_assistant/test_report_state.py | 51 +- .../test_config_flow.py | 15 +- .../test_init.py | 69 +- .../google_mail/test_config_flow.py | 34 +- tests/components/google_sheets/test_init.py | 9 +- .../google_tasks/test_config_flow.py | 9 +- .../components/google_travel_time/conftest.py | 9 +- .../google_travel_time/test_sensor.py | 18 +- tests/components/govee_ble/test_sensor.py | 18 +- .../govee_light_local/test_config_flow.py | 16 +- tests/components/gree/test_init.py | 17 +- .../growatt_server/test_config_flow.py | 46 +- tests/components/guardian/conftest.py | 70 +- tests/components/habitica/test_config_flow.py | 22 +- .../components/hardware/test_websocket_api.py | 26 +- tests/components/harmony/test_config_flow.py | 34 +- tests/components/hassio/conftest.py | 56 +- tests/components/hassio/test_config_flow.py | 15 +- tests/components/hassio/test_discovery.py | 15 +- tests/components/hassio/test_init.py | 138 +-- tests/components/hassio/test_update.py | 67 +- tests/components/heos/conftest.py | 5 +- tests/components/here_travel_time/conftest.py | 43 +- tests/components/hisense_aehw4a1/test_init.py | 51 +- tests/components/history_stats/test_sensor.py | 130 ++- tests/components/hive/test_config_flow.py | 188 ++-- tests/components/hlk_sw16/test_config_flow.py | 44 +- tests/components/homeassistant/test_init.py | 103 +- .../homeassistant_alerts/test_init.py | 66 +- .../homeassistant_hardware/conftest.py | 21 +- .../test_silabs_multiprotocol_addon.py | 17 +- .../homeassistant_sky_connect/conftest.py | 15 +- .../test_config_flow.py | 17 +- .../homeassistant_sky_connect/test_init.py | 121 ++- .../homeassistant_yellow/conftest.py | 21 +- .../homeassistant_yellow/test_init.py | 79 +- tests/components/homekit/conftest.py | 62 +- tests/components/homekit/test_accessories.py | 18 +- tests/components/homekit/test_config_flow.py | 109 ++- tests/components/homekit/test_diagnostics.py | 24 +- tests/components/homekit/test_homekit.py | 347 ++++--- tests/components/homekit/test_init.py | 10 +- tests/components/homekit/test_type_cameras.py | 135 +-- tests/components/homekit/test_util.py | 9 +- .../components/homematicip_cloud/conftest.py | 15 +- .../homematicip_cloud/test_config_flow.py | 128 +-- .../homematicip_cloud/test_device.py | 17 +- .../components/homematicip_cloud/test_hap.py | 41 +- .../components/homematicip_cloud/test_init.py | 13 +- tests/components/homewizard/conftest.py | 15 +- tests/components/homeworks/conftest.py | 12 +- tests/components/honeywell/test_sensor.py | 6 +- tests/components/http/test_ban.py | 17 +- tests/components/http/test_init.py | 47 +- .../components/huawei_lte/test_config_flow.py | 5 +- tests/components/hue/test_bridge.py | 41 +- tests/components/hue/test_config_flow.py | 16 +- tests/components/hue/test_init.py | 21 +- tests/components/hue/test_migration.py | 7 +- tests/components/hue/test_services.py | 20 +- .../components/huisbaasje/test_config_flow.py | 73 +- tests/components/huisbaasje/test_init.py | 40 +- tests/components/huisbaasje/test_sensor.py | 40 +- .../hunterdouglas_powerview/conftest.py | 57 +- .../test_config_flow.py | 15 +- .../husqvarna_automower/test_diagnostics.py | 1 + tests/components/huum/test_config_flow.py | 47 +- .../hvv_departures/test_config_flow.py | 125 ++- tests/components/hyperion/test_config_flow.py | 94 +- tests/components/hyperion/test_light.py | 20 +- tests/components/ialarm/test_config_flow.py | 24 +- tests/components/iaqualink/test_init.py | 123 ++- tests/components/ibeacon/test_coordinator.py | 9 +- .../components/ibeacon/test_device_tracker.py | 36 +- .../idasen_desk/test_config_flow.py | 115 ++- .../ign_sismologia/test_geo_location.py | 11 +- tests/components/image_upload/test_init.py | 5 +- .../components/improv_ble/test_config_flow.py | 81 +- tests/components/influxdb/test_init.py | 5 +- tests/components/influxdb/test_sensor.py | 7 +- tests/components/insteon/test_api_scenes.py | 28 +- tests/components/insteon/test_config_flow.py | 27 +- tests/components/insteon/test_init.py | 30 +- tests/components/insteon/test_lock.py | 18 +- tests/components/iotawatt/test_config_flow.py | 32 +- tests/components/iotawatt/test_sensor.py | 6 +- tests/components/iqvia/conftest.py | 23 +- .../islamic_prayer_times/test_init.py | 44 +- .../islamic_prayer_times/test_sensor.py | 11 +- tests/components/isy994/test_config_flow.py | 66 +- tests/components/izone/test_config_flow.py | 35 +- tests/components/juicenet/test_config_flow.py | 40 +- .../components/justnimbus/test_config_flow.py | 11 +- .../keymitt_ble/test_config_flow.py | 29 +- tests/components/knx/test_config_flow.py | 36 +- tests/components/knx/test_init.py | 7 +- tests/components/knx/test_services.py | 12 +- tests/components/knx/test_websocket.py | 22 +- tests/components/kodi/__init__.py | 16 +- tests/components/kodi/test_config_flow.py | 518 ++++++----- tests/components/kraken/test_config_flow.py | 22 +- tests/components/kraken/test_init.py | 45 +- tests/components/kraken/test_sensor.py | 62 +- tests/components/kulersky/test_config_flow.py | 51 +- .../lacrosse_view/test_config_flow.py | 53 +- .../lacrosse_view/test_diagnostics.py | 5 +- tests/components/lacrosse_view/test_init.py | 41 +- tests/components/lacrosse_view/test_sensor.py | 47 +- tests/components/lametric/conftest.py | 14 +- tests/components/lastfm/test_config_flow.py | 7 +- tests/components/laundrify/conftest.py | 17 +- tests/components/lcn/test_config_flow.py | 8 +- tests/components/lcn/test_init.py | 7 +- .../components/ld2410_ble/test_config_flow.py | 60 +- tests/components/led_ble/test_config_flow.py | 60 +- .../lg_soundbar/test_config_flow.py | 127 ++- tests/components/lifx/__init__.py | 8 +- tests/components/lifx/conftest.py | 9 +- tests/components/lifx/test_binary_sensor.py | 8 +- tests/components/lifx/test_button.py | 16 +- tests/components/lifx/test_config_flow.py | 97 +- tests/components/lifx/test_diagnostics.py | 40 +- tests/components/lifx/test_init.py | 27 +- tests/components/lifx/test_light.py | 144 +-- tests/components/lifx/test_migration.py | 11 +- tests/components/lifx/test_select.py | 56 +- tests/components/lifx/test_sensor.py | 32 +- .../linear_garage_door/test_config_flow.py | 73 +- .../linear_garage_door/test_cover.py | 198 ++-- .../linear_garage_door/test_init.py | 39 +- tests/components/linear_garage_door/util.py | 89 +- tests/components/litterrobot/conftest.py | 14 +- .../litterrobot/test_config_flow.py | 51 +- tests/components/local_file/test_camera.py | 29 +- .../components/logbook/test_websocket_api.py | 5 +- tests/components/logger/test_websocket_api.py | 15 +- tests/components/lookin/test_config_flow.py | 25 +- tests/components/loqed/conftest.py | 7 +- tests/components/loqed/test_config_flow.py | 74 +- tests/components/loqed/test_init.py | 47 +- tests/components/lovelace/test_cast.py | 31 +- tests/components/luftdaten/conftest.py | 12 +- tests/components/lupusec/test_config_flow.py | 45 +- tests/components/lutron/test_config_flow.py | 30 +- .../lutron_caseta/test_config_flow.py | 119 ++- tests/components/lyric/test_config_flow.py | 18 +- .../components/medcom_ble/test_config_flow.py | 55 +- tests/components/media_extractor/test_init.py | 17 +- .../media_player/test_browse_media.py | 9 +- tests/components/melnor/test_number.py | 18 +- tests/components/melnor/test_sensor.py | 32 +- tests/components/melnor/test_switch.py | 18 +- tests/components/melnor/test_time.py | 6 +- tests/components/mfi/test_sensor.py | 11 +- tests/components/mfi/test_switch.py | 11 +- tests/components/microbees/conftest.py | 15 +- tests/components/mikrotik/__init__.py | 5 +- tests/components/mikrotik/test_init.py | 7 +- tests/components/mill/test_init.py | 73 +- .../minecraft_server/test_binary_sensor.py | 45 +- .../minecraft_server/test_config_flow.py | 119 ++- .../minecraft_server/test_diagnostics.py | 15 +- .../components/minecraft_server/test_init.py | 68 +- .../minecraft_server/test_sensor.py | 60 +- tests/components/mobile_app/test_init.py | 19 +- tests/components/mochad/test_switch.py | 5 +- tests/components/modbus/conftest.py | 28 +- tests/components/modbus/test_init.py | 7 +- tests/components/modem_callerid/test_init.py | 12 +- tests/components/modern_forms/test_fan.py | 9 +- tests/components/modern_forms/test_light.py | 9 +- tests/components/modern_forms/test_switch.py | 9 +- .../moehlenhoff_alpha2/test_config_flow.py | 11 +- .../components/monoprice/test_config_flow.py | 17 +- .../motion_blinds/test_config_flow.py | 84 +- .../components/motioneye/test_config_flow.py | 100 +- tests/components/motioneye/test_web_hooks.py | 12 +- .../mqtt/test_alarm_control_panel.py | 10 +- tests/components/mqtt/test_common.py | 19 +- tests/components/mqtt/test_config_flow.py | 21 +- tests/components/mqtt/test_discovery.py | 5 +- tests/components/mqtt/test_event.py | 12 +- tests/components/mullvad/test_config_flow.py | 15 +- tests/components/mutesync/test_config_flow.py | 17 +- tests/components/mysensors/conftest.py | 19 +- .../components/mysensors/test_config_flow.py | 93 +- tests/components/mystrom/test_init.py | 76 +- tests/components/nam/__init__.py | 9 +- tests/components/nam/test_button.py | 9 +- tests/components/nam/test_config_flow.py | 156 ++-- tests/components/nam/test_init.py | 9 +- tests/components/nam/test_sensor.py | 38 +- tests/components/nanoleaf/test_config_flow.py | 118 ++- tests/components/nest/test_events.py | 9 +- tests/components/nest/test_init.py | 22 +- tests/components/netatmo/common.py | 14 +- tests/components/netatmo/test_camera.py | 66 +- tests/components/netatmo/test_diagnostics.py | 16 +- tests/components/netatmo/test_init.py | 199 ++-- tests/components/netatmo/test_light.py | 19 +- tests/components/netgear/test_config_flow.py | 7 +- tests/components/network/test_init.py | 255 +++-- tests/components/nexia/test_config_flow.py | 37 +- tests/components/nexia/util.py | 5 +- tests/components/nextdns/__init__.py | 56 +- tests/components/nextdns/test_button.py | 7 +- tests/components/nextdns/test_config_flow.py | 14 +- tests/components/nextdns/test_sensor.py | 72 +- tests/components/nextdns/test_switch.py | 7 +- tests/components/nibe_heatpump/conftest.py | 5 +- tests/components/nightscout/__init__.py | 44 +- .../components/nightscout/test_config_flow.py | 21 +- tests/components/nina/test_config_flow.py | 82 +- tests/components/nobo_hub/test_config_flow.py | 72 +- tests/components/notion/conftest.py | 22 +- .../test_geo_location.py | 13 +- tests/components/nuheat/test_config_flow.py | 37 +- tests/components/nuki/test_config_flow.py | 49 +- tests/components/nut/test_config_flow.py | 179 ++-- tests/components/nut/test_init.py | 30 +- tests/components/nws/test_weather.py | 7 +- tests/components/nzbget/test_config_flow.py | 14 +- tests/components/nzbget/test_init.py | 9 +- tests/components/octoprint/__init__.py | 33 +- tests/components/octoprint/test_button.py | 14 +- .../components/octoprint/test_config_flow.py | 153 +-- .../components/omnilogic/test_config_flow.py | 17 +- tests/components/onboarding/test_init.py | 7 +- tests/components/onboarding/test_views.py | 80 +- tests/components/oncue/__init__.py | 44 +- tests/components/oncue/test_config_flow.py | 11 +- tests/components/onvif/__init__.py | 16 +- tests/components/onvif/test_config_flow.py | 212 +++-- .../openai_conversation/test_config_flow.py | 15 +- .../openai_conversation/test_init.py | 28 +- .../components/opengarage/test_config_flow.py | 17 +- tests/components/openhome/test_update.py | 7 +- tests/components/opensky/test_config_flow.py | 18 +- .../opentherm_gw/test_config_flow.py | 133 +-- tests/components/opentherm_gw/test_init.py | 22 +- tests/components/openuv/conftest.py | 9 +- tests/components/oralb/test_sensor.py | 18 +- .../components/osoenergy/test_config_flow.py | 15 +- tests/components/otbr/conftest.py | 44 +- tests/components/otbr/test_config_flow.py | 79 +- tests/components/otbr/test_init.py | 122 ++- .../otbr/test_silabs_multiprotocol.py | 41 +- tests/components/otbr/test_util.py | 62 +- tests/components/otbr/test_websocket_api.py | 177 ++-- tests/components/overkiz/test_config_flow.py | 68 +- .../components/ovo_energy/test_config_flow.py | 37 +- .../components/owntracks/test_config_flow.py | 40 +- .../components/p1_monitor/test_config_flow.py | 13 +- tests/components/peco/test_config_flow.py | 11 +- tests/components/peco/test_init.py | 185 ++-- tests/components/peco/test_sensor.py | 50 +- .../pegel_online/test_config_flow.py | 39 +- tests/components/philips_js/conftest.py | 20 +- tests/components/pilight/test_init.py | 19 +- tests/components/ping/conftest.py | 18 +- tests/components/ping/test_device_tracker.py | 9 +- tests/components/plaato/test_config_flow.py | 53 +- tests/components/plex/test_config_flow.py | 111 ++- tests/components/plex/test_init.py | 10 +- tests/components/plex/test_media_search.py | 5 +- tests/components/plex/test_playback.py | 30 +- tests/components/plugwise/test_climate.py | 12 +- .../plum_lightpad/test_config_flow.py | 24 +- tests/components/plum_lightpad/test_init.py | 43 +- .../components/poolsense/test_config_flow.py | 11 +- .../powerwall/test_binary_sensor.py | 26 +- .../components/powerwall/test_config_flow.py | 186 ++-- tests/components/powerwall/test_init.py | 13 +- tests/components/powerwall/test_sensor.py | 52 +- tests/components/profiler/test_init.py | 5 +- tests/components/prosegur/conftest.py | 9 +- tests/components/prosegur/test_camera.py | 7 +- tests/components/prosegur/test_config_flow.py | 34 +- tests/components/prusalink/test_button.py | 17 +- tests/components/prusalink/test_init.py | 29 +- tests/components/prusalink/test_sensor.py | 11 +- tests/components/ps4/test_config_flow.py | 58 +- tests/components/ps4/test_init.py | 47 +- tests/components/ps4/test_media_player.py | 15 +- tests/components/purpleair/conftest.py | 7 +- tests/components/pvoutput/conftest.py | 11 +- tests/components/python_script/test_init.py | 134 ++- .../components/qingping/test_binary_sensor.py | 9 +- tests/components/qingping/test_sensor.py | 9 +- .../qld_bushfire/test_geo_location.py | 11 +- tests/components/qnap_qsw/test_button.py | 17 +- tests/components/qnap_qsw/test_config_flow.py | 58 +- tests/components/qnap_qsw/test_coordinator.py | 80 +- tests/components/qnap_qsw/test_init.py | 44 +- tests/components/qnap_qsw/test_update.py | 24 +- tests/components/qnap_qsw/util.py | 78 +- .../components/rabbitair/test_config_flow.py | 5 +- tests/components/rachio/test_config_flow.py | 16 +- .../components/radiotherm/test_config_flow.py | 32 +- tests/components/rainbird/conftest.py | 15 +- .../rainforest_eagle/test_config_flow.py | 17 +- tests/components/rainmachine/conftest.py | 17 +- .../rainmachine/test_config_flow.py | 24 +- tests/components/raspberry_pi/test_init.py | 11 +- tests/components/recollect_waste/conftest.py | 15 +- .../auto_repairs/events/test_schema.py | 42 +- .../auto_repairs/states/test_schema.py | 53 +- .../statistics/test_duplicates.py | 43 +- .../auto_repairs/statistics/test_schema.py | 42 +- tests/components/recorder/common.py | 31 +- tests/components/recorder/db_schema_22.py | 12 +- tests/components/recorder/db_schema_23.py | 12 +- .../db_schema_23_with_newer_columns.py | 12 +- tests/components/recorder/db_schema_25.py | 12 +- tests/components/recorder/db_schema_28.py | 12 +- tests/components/recorder/db_schema_30.py | 6 +- tests/components/recorder/db_schema_32.py | 6 +- .../table_managers/test_statistics_meta.py | 7 +- tests/components/recorder/test_backup.py | 33 +- .../recorder/test_history_db_schema_30.py | 10 +- .../recorder/test_history_db_schema_32.py | 10 +- tests/components/recorder/test_init.py | 242 +++-- tests/components/recorder/test_migrate.py | 236 +++-- .../recorder/test_migration_from_schema_32.py | 42 +- ..._migration_run_time_migrations_remember.py | 39 +- tests/components/recorder/test_purge.py | 40 +- .../recorder/test_purge_v32_schema.py | 32 +- .../recorder/test_statistics_v23_migration.py | 84 +- .../components/recorder/test_system_health.py | 35 +- tests/components/recorder/test_util.py | 52 +- .../components/recorder/test_v32_migration.py | 94 +- .../components/recorder/test_websocket_api.py | 23 +- tests/components/refoss/test_config_flow.py | 22 +- .../components/remember_the_milk/test_init.py | 31 +- tests/components/renault/conftest.py | 159 ++-- tests/components/renault/test_config_flow.py | 47 +- tests/components/renault/test_services.py | 65 +- tests/components/renson/test_config_flow.py | 17 +- tests/components/reolink/conftest.py | 15 +- tests/components/reolink/test_init.py | 42 +- tests/components/rfxtrx/conftest.py | 5 +- tests/components/ridwell/conftest.py | 15 +- tests/components/risco/conftest.py | 170 ++-- .../risco/test_alarm_control_panel.py | 62 +- tests/components/risco/test_binary_sensor.py | 27 +- tests/components/risco/test_config_flow.py | 136 +-- tests/components/risco/test_sensor.py | 28 +- .../rituals_perfume_genie/test_config_flow.py | 17 +- tests/components/roborock/conftest.py | 79 +- tests/components/roborock/test_image.py | 33 +- tests/components/roborock/test_init.py | 43 +- tests/components/roborock/test_select.py | 11 +- tests/components/roborock/test_vacuum.py | 11 +- tests/components/roomba/test_config_flow.py | 195 ++-- tests/components/roon/test_config_flow.py | 117 ++- tests/components/rova/conftest.py | 11 +- tests/components/rtsp_to_webrtc/conftest.py | 15 +- .../rtsp_to_webrtc/test_config_flow.py | 22 +- .../ruckus_unleashed/test_config_flow.py | 11 +- tests/components/rympro/test_config_flow.py | 111 ++- tests/components/samsungtv/conftest.py | 18 +- .../components/samsungtv/test_config_flow.py | 227 +++-- tests/components/samsungtv/test_init.py | 25 +- .../components/samsungtv/test_media_player.py | 60 +- tests/components/scrape/test_config_flow.py | 9 +- .../screenlogic/test_config_flow.py | 45 +- tests/components/screenlogic/test_data.py | 21 +- .../screenlogic/test_diagnostics.py | 23 +- tests/components/screenlogic/test_init.py | 74 +- tests/components/sensibo/conftest.py | 22 +- tests/components/sensibo/test_button.py | 35 +- tests/components/sensibo/test_climate.py | 549 ++++++----- tests/components/sensibo/test_config_flow.py | 137 +-- tests/components/sensibo/test_coordinator.py | 20 +- tests/components/sensibo/test_entity.py | 11 +- tests/components/sensibo/test_init.py | 84 +- tests/components/sensibo/test_number.py | 30 +- tests/components/sensibo/test_select.py | 55 +- tests/components/sensibo/test_switch.py | 130 +-- tests/components/sensorpush/test_sensor.py | 9 +- tests/components/sentry/test_config_flow.py | 11 +- tests/components/sentry/test_init.py | 32 +- tests/components/sfr_box/test_button.py | 20 +- tests/components/sharkiq/test_config_flow.py | 11 +- tests/components/shell_command/test_init.py | 9 +- tests/components/shelly/conftest.py | 5 +- tests/components/shelly/test_config_flow.py | 207 +++-- tests/components/shelly/test_init.py | 48 +- tests/components/shopping_list/conftest.py | 5 +- .../signal_messenger/test_notify.py | 31 +- tests/components/simplisafe/conftest.py | 34 +- .../components/simplisafe/test_config_flow.py | 27 +- tests/components/simplisafe/test_init.py | 34 +- tests/components/skybell/conftest.py | 11 +- tests/components/sma/conftest.py | 7 +- tests/components/sma/test_config_flow.py | 48 +- tests/components/smappee/test_config_flow.py | 146 +-- tests/components/smappee/test_init.py | 21 +- .../smart_meter_texas/test_config_flow.py | 11 +- tests/components/smartthings/conftest.py | 8 +- .../smartthings/test_config_flow.py | 21 +- tests/components/smartthings/test_init.py | 11 +- tests/components/smhi/test_config_flow.py | 64 +- tests/components/smhi/test_weather.py | 15 +- tests/components/smtp/test_notify.py | 14 +- tests/components/snooz/__init__.py | 11 +- tests/components/solarlog/test_config_flow.py | 17 +- tests/components/solax/test_config_flow.py | 18 +- .../somfy_mylink/test_config_flow.py | 68 +- tests/components/songpal/test_init.py | 5 +- tests/components/sonos/conftest.py | 26 +- tests/components/sonos/test_config_flow.py | 68 +- tests/components/sonos/test_init.py | 32 +- tests/components/sonos/test_plex_playback.py | 16 +- tests/components/sonos/test_services.py | 11 +- tests/components/sonos/test_speaker.py | 7 +- tests/components/spider/test_config_flow.py | 30 +- tests/components/spotify/test_config_flow.py | 14 +- tests/components/sql/test_config_flow.py | 39 +- tests/components/sql/test_sensor.py | 9 +- .../components/squeezebox/test_config_flow.py | 73 +- tests/components/srp_energy/test_sensor.py | 9 +- tests/components/ssdp/conftest.py | 16 +- tests/components/ssdp/test_init.py | 15 +- tests/components/starlink/test_diagnostics.py | 6 +- tests/components/starlink/test_init.py | 12 +- tests/components/statistics/test_sensor.py | 5 +- .../steam_online/test_config_flow.py | 36 +- tests/components/steamist/test_config_flow.py | 101 +- tests/components/steamist/test_init.py | 20 +- tests/components/stream/conftest.py | 40 +- tests/components/stream/test_hls.py | 8 +- tests/components/stream/test_recorder.py | 15 +- tests/components/stream/test_worker.py | 14 +- tests/components/subaru/conftest.py | 95 +- tests/components/subaru/test_config_flow.py | 186 ++-- tests/components/subaru/test_init.py | 15 +- tests/components/subaru/test_lock.py | 5 +- tests/components/subaru/test_sensor.py | 5 +- .../components/suez_water/test_config_flow.py | 30 +- tests/components/sunweg/test_config_flow.py | 32 +- tests/components/sunweg/test_init.py | 12 +- .../components/switchbee/test_config_flow.py | 46 +- .../components/switchbot/test_config_flow.py | 49 +- .../components/syncthing/test_config_flow.py | 13 +- tests/components/syncthru/test_config_flow.py | 5 +- .../synology_dsm/test_config_flow.py | 15 +- tests/components/synology_dsm/test_init.py | 36 +- .../synology_dsm/test_media_source.py | 77 +- .../system_bridge/test_config_flow.py | 245 +++-- tests/components/system_log/test_init.py | 13 +- tests/components/systemmonitor/conftest.py | 9 +- tests/components/tado/test_config_flow.py | 51 +- tests/components/tailwind/conftest.py | 13 +- tests/components/tankerkoenig/conftest.py | 15 +- .../tankerkoenig/test_config_flow.py | 26 +- tests/components/tautulli/test_config_flow.py | 7 +- tests/components/tcp/test_binary_sensor.py | 11 +- tests/components/technove/conftest.py | 11 +- tests/components/tedee/conftest.py | 13 +- tests/components/telegram_bot/conftest.py | 40 +- .../tellduslive/test_config_flow.py | 33 +- tests/components/template/test_sensor.py | 29 +- .../tesla_wall_connector/conftest.py | 43 +- tests/components/teslemetry/test_climate.py | 18 +- tests/components/tessie/common.py | 13 +- tests/components/tessie/test_climate.py | 28 +- tests/components/tessie/test_cover.py | 22 +- tests/components/tessie/test_select.py | 11 +- tests/components/thread/test_config_flow.py | 15 +- tests/components/tibber/test_diagnostics.py | 11 +- tests/components/tile/conftest.py | 9 +- tests/components/todoist/conftest.py | 7 +- tests/components/tomorrowio/conftest.py | 24 +- tests/components/totalconnect/common.py | 11 +- .../totalconnect/test_config_flow.py | 44 +- tests/components/tplink/__init__.py | 8 +- tests/components/tplink/test_config_flow.py | 95 +- tests/components/tplink/test_init.py | 21 +- tests/components/tplink/test_light.py | 8 +- .../tplink_omada/test_config_flow.py | 112 ++- tests/components/traccar_server/conftest.py | 15 +- tests/components/tractive/test_config_flow.py | 22 +- .../trafikverket_camera/test_config_flow.py | 77 +- .../trafikverket_ferry/test_config_flow.py | 41 +- .../components/trafikverket_train/conftest.py | 20 +- .../trafikverket_train/test_config_flow.py | 169 ++-- .../trafikverket_train/test_init.py | 28 +- .../trafikverket_train/test_sensor.py | 45 +- .../test_config_flow.py | 28 +- tests/components/tts/test_init.py | 8 +- tests/components/twentemilieu/conftest.py | 13 +- tests/components/twinkly/test_config_flow.py | 18 +- tests/components/twitch/conftest.py | 15 +- tests/components/uk_transport/test_sensor.py | 5 +- tests/components/unifi/test_hub.py | 25 +- tests/components/unifi/test_init.py | 11 +- tests/components/unifiprotect/conftest.py | 9 +- .../unifiprotect/test_config_flow.py | 180 ++-- tests/components/unifiprotect/test_init.py | 9 +- tests/components/unifiprotect/test_sensor.py | 6 +- .../components/universal/test_media_player.py | 30 +- tests/components/upb/test_config_flow.py | 23 +- tests/components/update/test_init.py | 11 +- tests/components/upnp/conftest.py | 69 +- tests/components/upnp/test_init.py | 15 +- .../uptimerobot/test_config_flow.py | 98 +- tests/components/uptimerobot/test_switch.py | 47 +- tests/components/usb/test_init.py | 270 +++--- .../test_geo_location.py | 20 +- tests/components/uvc/test_camera.py | 12 +- tests/components/vallox/test_config_flow.py | 17 +- tests/components/venstar/test_config_flow.py | 32 +- tests/components/venstar/test_init.py | 79 +- tests/components/vesync/test_init.py | 9 +- tests/components/vilfo/test_config_flow.py | 88 +- tests/components/vizio/conftest.py | 165 ++-- tests/components/vizio/test_media_player.py | 46 +- .../components/vlc_telnet/test_config_flow.py | 111 ++- .../vodafone_station/test_config_flow.py | 110 ++- tests/components/voip/test_sip.py | 14 +- tests/components/voip/test_voip.py | 125 ++- tests/components/volumio/test_config_flow.py | 66 +- .../volvooncall/test_config_flow.py | 20 +- tests/components/vultr/test_switch.py | 22 +- tests/components/waqi/test_config_flow.py | 131 +-- tests/components/watttime/conftest.py | 16 +- tests/components/weather/test_intent.py | 5 +- tests/components/weatherkit/__init__.py | 15 +- tests/components/weatherkit/test_setup.py | 30 +- tests/components/websocket_api/test_http.py | 11 +- tests/components/wemo/conftest.py | 5 +- tests/components/wemo/test_init.py | 16 +- .../components/whirlpool/test_config_flow.py | 151 +-- tests/components/whois/conftest.py | 9 +- tests/components/withings/conftest.py | 15 +- tests/components/withings/test_diagnostics.py | 35 +- tests/components/withings/test_init.py | 138 +-- tests/components/wiz/__init__.py | 9 +- tests/components/wiz/test_config_flow.py | 118 ++- tests/components/wiz/test_init.py | 7 +- tests/components/wled/conftest.py | 9 +- tests/components/wolflink/test_config_flow.py | 22 +- tests/components/ws66i/test_config_flow.py | 15 +- tests/components/wyoming/__init__.py | 15 +- tests/components/wyoming/conftest.py | 15 +- tests/components/wyoming/test_config_flow.py | 11 +- tests/components/wyoming/test_satellite.py | 24 +- tests/components/wyoming/test_stt.py | 11 +- tests/components/wyoming/test_tts.py | 27 +- .../xiaomi_aqara/test_config_flow.py | 20 +- .../xiaomi_ble/test_binary_sensor.py | 27 +- .../components/xiaomi_ble/test_config_flow.py | 15 +- tests/components/xiaomi_ble/test_sensor.py | 27 +- tests/components/xiaomi_miio/test_button.py | 15 +- .../xiaomi_miio/test_config_flow.py | 32 +- tests/components/xiaomi_miio/test_select.py | 17 +- .../yale_smart_alarm/test_config_flow.py | 73 +- .../components/yalexs_ble/test_config_flow.py | 146 +-- .../yamaha_musiccast/test_config_flow.py | 18 +- tests/components/yeelight/test_config_flow.py | 277 +++--- tests/components/yeelight/test_init.py | 137 ++- tests/components/yeelight/test_light.py | 35 +- tests/components/yolink/test_config_flow.py | 18 +- tests/components/youtube/test_config_flow.py | 44 +- tests/components/zeroconf/test_init.py | 876 ++++++++++-------- tests/components/zeroconf/test_usage.py | 50 +- tests/components/zerproc/test_config_flow.py | 51 +- tests/components/zerproc/test_light.py | 12 +- .../components/zeversolar/test_config_flow.py | 20 +- tests/components/zha/conftest.py | 15 +- tests/components/zha/test_cluster_handlers.py | 18 +- tests/components/zha/test_config_flow.py | 44 +- tests/components/zha/test_discover.py | 11 +- tests/components/zha/test_radio_manager.py | 37 +- tests/components/zha/test_repairs.py | 84 +- tests/components/zha/test_sensor.py | 12 +- tests/components/zha/test_siren.py | 51 +- tests/components/zha/test_update.py | 11 +- ...t_convert_device_diagnostics_to_fixture.py | 9 +- tests/components/zwave_js/test_api.py | 36 +- tests/components/zwave_js/test_config_flow.py | 212 +++-- .../zwave_js/test_device_condition.py | 15 +- .../zwave_js/test_device_trigger.py | 15 +- tests/components/zwave_js/test_init.py | 13 +- tests/components/zwave_js/test_services.py | 9 +- tests/components/zwave_me/test_config_flow.py | 30 +- .../zwave_me/test_remove_stale_devices.py | 13 +- tests/conftest.py | 262 +++--- tests/helpers/test_aiohttp_client.py | 98 +- tests/helpers/test_check_config.py | 48 +- tests/helpers/test_config_entry_flow.py | 58 +- .../helpers/test_config_entry_oauth2_flow.py | 7 +- tests/helpers/test_config_validation.py | 5 +- tests/helpers/test_deprecation.py | 148 +-- tests/helpers/test_device_registry.py | 18 +- tests/helpers/test_entity.py | 27 +- tests/helpers/test_entity_platform.py | 5 +- tests/helpers/test_entity_registry.py | 21 +- tests/helpers/test_frame.py | 35 +- tests/helpers/test_httpx_client.py | 98 +- tests/helpers/test_icon.py | 22 +- tests/helpers/test_importlib.py | 20 +- tests/helpers/test_instance_id.py | 20 +- tests/helpers/test_integration_platform.py | 7 +- tests/helpers/test_network.py | 85 +- tests/helpers/test_reload.py | 28 +- tests/helpers/test_script.py | 11 +- tests/helpers/test_service.py | 24 +- tests/helpers/test_significant_change.py | 6 +- tests/helpers/test_storage.py | 28 +- tests/helpers/test_system_info.py | 51 +- tests/helpers/test_translation.py | 88 +- tests/pylint/test_enforce_super_call.py | 42 +- tests/test_bootstrap.py | 91 +- tests/test_config.py | 21 +- tests/test_config_entries.py | 122 ++- tests/test_core.py | 9 +- tests/test_data_entry_flow.py | 5 +- tests/test_loader.py | 249 +++-- tests/test_requirements.py | 162 ++-- tests/test_runner.py | 67 +- tests/test_setup.py | 21 +- .../custom_components/test/number.py | 6 +- .../custom_components/test/sensor.py | 6 +- tests/util/test_async.py | 153 +-- tests/util/test_file.py | 25 +- tests/util/test_logging.py | 19 +- tests/util/test_timeout.py | 33 +- tests/util/yaml/test_init.py | 51 +- 1044 files changed, 24245 insertions(+), 16750 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c77fe1c5076..ef4cdd98efb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.3.4 hooks: - id: ruff args: diff --git a/homeassistant/backports/functools.py b/homeassistant/backports/functools.py index 6271bb87d14..8aab50eeb66 100644 --- a/homeassistant/backports/functools.py +++ b/homeassistant/backports/functools.py @@ -41,12 +41,10 @@ class cached_property(Generic[_T]): ) @overload - def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: - ... + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ... @overload - def __get__(self, instance: Any, owner: type[Any] | None = None) -> _T: - ... + def __get__(self, instance: Any, owner: type[Any] | None = None) -> _T: ... def __get__( self, instance: Any | None, owner: type[Any] | None = None diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index ec3e0ad7028..c0a6b8d38ef 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -162,13 +162,13 @@ def _standardize_geography_config_entry( # about, infer it from the data we have: entry_updates["data"] = {**entry.data} if CONF_CITY in entry.data: - entry_updates["data"][ - CONF_INTEGRATION_TYPE - ] = INTEGRATION_TYPE_GEOGRAPHY_NAME + entry_updates["data"][CONF_INTEGRATION_TYPE] = ( + INTEGRATION_TYPE_GEOGRAPHY_NAME + ) else: - entry_updates["data"][ - CONF_INTEGRATION_TYPE - ] = INTEGRATION_TYPE_GEOGRAPHY_COORDS + entry_updates["data"][CONF_INTEGRATION_TYPE] = ( + INTEGRATION_TYPE_GEOGRAPHY_COORDS + ) if not entry_updates: return diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index 6a5481f0d1a..cb6abff3f89 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -211,9 +211,10 @@ class AmcrestChecker(ApiWrapper): self, *args: Any, **kwargs: Any ) -> AsyncIterator[httpx.Response]: """amcrest.ApiWrapper.command wrapper to catch errors.""" - async with self._async_command_wrapper(), super().async_stream_command( - *args, **kwargs - ) as ret: + async with ( + self._async_command_wrapper(), + super().async_stream_command(*args, **kwargs) as ret, + ): yield ret @asynccontextmanager diff --git a/homeassistant/components/amcrest/sensor.py b/homeassistant/components/amcrest/sensor.py index 6e096d8c764..b54d71c5814 100644 --- a/homeassistant/components/amcrest/sensor.py +++ b/homeassistant/components/amcrest/sensor.py @@ -108,21 +108,21 @@ class AmcrestSensor(SensorEntity): elif sensor_type == SENSOR_SDCARD: storage = await self._api.async_storage_all try: - self._attr_extra_state_attributes[ - "Total" - ] = f"{storage['total'][0]:.2f} {storage['total'][1]}" + self._attr_extra_state_attributes["Total"] = ( + f"{storage['total'][0]:.2f} {storage['total'][1]}" + ) except ValueError: - self._attr_extra_state_attributes[ - "Total" - ] = f"{storage['total'][0]} {storage['total'][1]}" + self._attr_extra_state_attributes["Total"] = ( + f"{storage['total'][0]} {storage['total'][1]}" + ) try: - self._attr_extra_state_attributes[ - "Used" - ] = f"{storage['used'][0]:.2f} {storage['used'][1]}" + self._attr_extra_state_attributes["Used"] = ( + f"{storage['used'][0]:.2f} {storage['used'][1]}" + ) except ValueError: - self._attr_extra_state_attributes[ - "Used" - ] = f"{storage['used'][0]} {storage['used'][1]}" + self._attr_extra_state_attributes["Used"] = ( + f"{storage['used'][0]} {storage['used'][1]}" + ) try: self._attr_native_value = f"{storage['used_percent']:.2f}" except ValueError: diff --git a/homeassistant/components/apcupsd/diagnostics.py b/homeassistant/components/apcupsd/diagnostics.py index 60433dfbdf4..d375a8bc248 100644 --- a/homeassistant/components/apcupsd/diagnostics.py +++ b/homeassistant/components/apcupsd/diagnostics.py @@ -1,4 +1,5 @@ """Diagnostics support for APCUPSD.""" + from __future__ import annotations from typing import Any diff --git a/homeassistant/components/aranet/__init__.py b/homeassistant/components/aranet/__init__.py index 7d9254e4223..3a2bc266653 100644 --- a/homeassistant/components/aranet/__init__.py +++ b/homeassistant/components/aranet/__init__.py @@ -33,14 +33,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=_service_info_to_adv, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=_service_info_to_adv, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/assist_pipeline/pipeline.py b/homeassistant/components/assist_pipeline/pipeline.py index e55f73ce8a5..01a12b3635b 100644 --- a/homeassistant/components/assist_pipeline/pipeline.py +++ b/homeassistant/components/assist_pipeline/pipeline.py @@ -754,9 +754,9 @@ class PipelineRun: raise DuplicateWakeUpDetectedError(result.wake_word_phrase) # Record last wake up time to block duplicate detections - self.hass.data[DATA_LAST_WAKE_UP][ - result.wake_word_phrase - ] = time.monotonic() + self.hass.data[DATA_LAST_WAKE_UP][result.wake_word_phrase] = ( + time.monotonic() + ) if result.queued_audio: # Add audio that was pending at detection. @@ -1375,9 +1375,9 @@ class PipelineInput: raise DuplicateWakeUpDetectedError(self.wake_word_phrase) # Record last wake up time to block duplicate detections - self.run.hass.data[DATA_LAST_WAKE_UP][ - self.wake_word_phrase - ] = time.monotonic() + self.run.hass.data[DATA_LAST_WAKE_UP][self.wake_word_phrase] = ( + time.monotonic() + ) stt_input_stream = stt_processed_stream diff --git a/homeassistant/components/asuswrt/device_tracker.py b/homeassistant/components/asuswrt/device_tracker.py index 65d33500970..059a0eeb3fb 100644 --- a/homeassistant/components/asuswrt/device_tracker.py +++ b/homeassistant/components/asuswrt/device_tracker.py @@ -101,9 +101,9 @@ class AsusWrtDevice(ScannerEntity): self._device = self._router.devices[self._device.mac] self._attr_extra_state_attributes = {} if self._device.last_activity: - self._attr_extra_state_attributes[ - ATTR_LAST_TIME_REACHABLE - ] = self._device.last_activity.isoformat(timespec="seconds") + self._attr_extra_state_attributes[ATTR_LAST_TIME_REACHABLE] = ( + self._device.last_activity.isoformat(timespec="seconds") + ) self.async_write_ha_state() async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index f4b6db55779..e711edd6893 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -141,9 +141,9 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity): ATTR_BATTERY_LEVEL: self._detail.battery_level } if self._detail.keypad is not None: - self._attr_extra_state_attributes[ - "keypad_battery_level" - ] = self._detail.keypad.battery_level + self._attr_extra_state_attributes["keypad_battery_level"] = ( + self._detail.keypad.battery_level + ) async def async_added_to_hass(self) -> None: """Restore ATTR_CHANGED_BY on startup since it is likely no longer in the activity log.""" diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index 0bbfcc01fc7..232f067b673 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -92,9 +92,10 @@ async def fetch_redirect_uris(hass: HomeAssistant, url: str) -> list[str]: parser = LinkTagParser("redirect_uri") chunks = 0 try: - async with aiohttp.ClientSession() as session, session.get( - url, timeout=5 - ) as resp: + async with ( + aiohttp.ClientSession() as session, + session.get(url, timeout=5) as resp, + ): async for data in resp.content.iter_chunked(1024): parser.feed(data.decode()) chunks += 1 diff --git a/homeassistant/components/awair/config_flow.py b/homeassistant/components/awair/config_flow.py index cce447d33f8..d3c4703e89c 100644 --- a/homeassistant/components/awair/config_flow.py +++ b/homeassistant/components/awair/config_flow.py @@ -122,9 +122,9 @@ class AwairFlowHandler(ConfigFlow, domain=DOMAIN): for flow in self._async_in_progress(): if flow["context"]["source"] == SOURCE_ZEROCONF: info = flow["context"]["title_placeholders"] - entries[ - flow["context"]["host"] - ] = f"{info['model']} ({info['device_id']})" + entries[flow["context"]["host"]] = ( + f"{info['model']} ({info['device_id']})" + ) return entries async def async_step_local( diff --git a/homeassistant/components/axis/hub/entity_loader.py b/homeassistant/components/axis/hub/entity_loader.py index def22d62bd8..36523e24d83 100644 --- a/homeassistant/components/axis/hub/entity_loader.py +++ b/homeassistant/components/axis/hub/entity_loader.py @@ -2,6 +2,7 @@ Central point to load entities for the different platforms. """ + from __future__ import annotations from functools import partial diff --git a/homeassistant/components/bluemaestro/__init__.py b/homeassistant/components/bluemaestro/__init__.py index 2b9a2ab15ec..c25ceb44759 100644 --- a/homeassistant/components/bluemaestro/__init__.py +++ b/homeassistant/components/bluemaestro/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = BlueMaestroBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/bluetooth/passive_update_processor.py b/homeassistant/components/bluetooth/passive_update_processor.py index 4e2de196da4..b7a7a165f71 100644 --- a/homeassistant/components/bluetooth/passive_update_processor.py +++ b/homeassistant/components/bluetooth/passive_update_processor.py @@ -129,9 +129,9 @@ class PassiveBluetoothDataUpdate(Generic[_T]): """Generic bluetooth data.""" devices: dict[str | None, DeviceInfo] = dataclasses.field(default_factory=dict) - entity_descriptions: dict[ - PassiveBluetoothEntityKey, EntityDescription - ] = dataclasses.field(default_factory=dict) + entity_descriptions: dict[PassiveBluetoothEntityKey, EntityDescription] = ( + dataclasses.field(default_factory=dict) + ) entity_names: dict[PassiveBluetoothEntityKey, str | None] = dataclasses.field( default_factory=dict ) diff --git a/homeassistant/components/bluetooth/util.py b/homeassistant/components/bluetooth/util.py index 166930d64f3..0faac9a8613 100644 --- a/homeassistant/components/bluetooth/util.py +++ b/homeassistant/components/bluetooth/util.py @@ -29,14 +29,14 @@ def async_load_history_from_system( not (existing_all := connectable_loaded_history.get(address)) or history.advertisement_data.rssi > existing_all.rssi ): - connectable_loaded_history[address] = all_loaded_history[ - address - ] = BluetoothServiceInfoBleak.from_device_and_advertisement_data( - history.device, - history.advertisement_data, - history.source, - now_monotonic, - True, + connectable_loaded_history[address] = all_loaded_history[address] = ( + BluetoothServiceInfoBleak.from_device_and_advertisement_data( + history.device, + history.advertisement_data, + history.source, + now_monotonic, + True, + ) ) # Restore remote adapters diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 3de41bfb911..9529c135280 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -102,8 +102,8 @@ class BMWLock(BMWBaseEntity, LockEntity): LockState.LOCKED, LockState.SECURED, } - self._attr_extra_state_attributes[ - "door_lock_state" - ] = self.vehicle.doors_and_windows.door_lock_state.value + self._attr_extra_state_attributes["door_lock_state"] = ( + self.vehicle.doors_and_windows.door_lock_state.value + ) super()._handle_coordinator_update() diff --git a/homeassistant/components/bosch_shc/__init__.py b/homeassistant/components/bosch_shc/__init__.py index 1ccb31d1dc9..9a00029412d 100644 --- a/homeassistant/components/bosch_shc/__init__.py +++ b/homeassistant/components/bosch_shc/__init__.py @@ -76,9 +76,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.async_add_executor_job(session.stop_polling) await hass.async_add_executor_job(session.start_polling) - hass.data[DOMAIN][entry.entry_id][ - DATA_POLLING_HANDLER - ] = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_polling) + hass.data[DOMAIN][entry.entry_id][DATA_POLLING_HANDLER] = ( + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_polling) + ) return True diff --git a/homeassistant/components/brottsplatskartan/sensor.py b/homeassistant/components/brottsplatskartan/sensor.py index 750b99abb67..6725a32bb40 100644 --- a/homeassistant/components/brottsplatskartan/sensor.py +++ b/homeassistant/components/brottsplatskartan/sensor.py @@ -63,9 +63,9 @@ class BrottsplatskartanSensor(SensorEntity): """Update device state.""" incident_counts: defaultdict[str, int] = defaultdict(int) - get_incidents: dict[str, list] | Literal[ - False - ] = self._brottsplatskartan.get_incidents() + get_incidents: dict[str, list] | Literal[False] = ( + self._brottsplatskartan.get_incidents() + ) if get_incidents is False: LOGGER.debug("Problems fetching incidents") diff --git a/homeassistant/components/bthome/__init__.py b/homeassistant/components/bthome/__init__.py index d677bd28260..8509b5d1d46 100644 --- a/homeassistant/components/bthome/__init__.py +++ b/homeassistant/components/bthome/__init__.py @@ -129,20 +129,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: data = BTHomeBluetoothDeviceData(**kwargs) device_registry = async_get(hass) - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = BTHomePassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=lambda service_info: process_service_info( - hass, entry, data, service_info, device_registry - ), - device_data=data, - discovered_event_classes=set(entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, [])), - connectable=False, - entry=entry, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + BTHomePassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=lambda service_info: process_service_info( + hass, entry, data, service_info, device_registry + ), + device_data=data, + discovered_event_classes=set( + entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, []) + ), + connectable=False, + entry=entry, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/canary/coordinator.py b/homeassistant/components/canary/coordinator.py index 534ca4df9fb..d58d1da0f79 100644 --- a/homeassistant/components/canary/coordinator.py +++ b/homeassistant/components/canary/coordinator.py @@ -46,9 +46,9 @@ class CanaryDataUpdateCoordinator(DataUpdateCoordinator[CanaryData]): for device in location.devices: if device.is_online: - readings_by_device_id[ - device.device_id - ] = self.canary.get_latest_readings(device.device_id) + readings_by_device_id[device.device_id] = ( + self.canary.get_latest_readings(device.device_id) + ) return { "locations": locations_by_id, diff --git a/homeassistant/components/climate/device_trigger.py b/homeassistant/components/climate/device_trigger.py index 30c282344a4..9702c97d0da 100644 --- a/homeassistant/components/climate/device_trigger.py +++ b/homeassistant/components/climate/device_trigger.py @@ -140,13 +140,13 @@ async def async_attach_trigger( } if trigger_type == "current_temperature_changed": - numeric_state_config[ - numeric_state_trigger.CONF_VALUE_TEMPLATE - ] = "{{ state.attributes.current_temperature }}" + numeric_state_config[numeric_state_trigger.CONF_VALUE_TEMPLATE] = ( + "{{ state.attributes.current_temperature }}" + ) else: # trigger_type == "current_humidity_changed" - numeric_state_config[ - numeric_state_trigger.CONF_VALUE_TEMPLATE - ] = "{{ state.attributes.current_humidity }}" + numeric_state_config[numeric_state_trigger.CONF_VALUE_TEMPLATE] = ( + "{{ state.attributes.current_humidity }}" + ) if CONF_ABOVE in config: numeric_state_config[CONF_ABOVE] = config[CONF_ABOVE] diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index 685c03136da..5b11fd85827 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -29,9 +29,9 @@ class CO2SensorEntityDescription(SensorEntityDescription): # For backwards compat, allow description to override unique ID key to use unique_id: str | None = None - unit_of_measurement_fn: Callable[ - [CarbonIntensityResponse], str | None - ] | None = None + unit_of_measurement_fn: Callable[[CarbonIntensityResponse], str | None] | None = ( + None + ) value_fn: Callable[[CarbonIntensityResponse], float | None] diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index f48bedad4ec..34ae8701d5d 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -178,9 +178,9 @@ class DaikinClimate(ClimateEntity): # temperature elif attr == ATTR_TEMPERATURE: try: - values[ - HA_ATTR_TO_DAIKIN[ATTR_TARGET_TEMPERATURE] - ] = format_target_temperature(value) + values[HA_ATTR_TO_DAIKIN[ATTR_TARGET_TEMPERATURE]] = ( + format_target_temperature(value) + ) except ValueError: _LOGGER.error("Invalid temperature %s", value) diff --git a/homeassistant/components/debugpy/__init__.py b/homeassistant/components/debugpy/__init__.py index 4dfad108136..5caf517a483 100644 --- a/homeassistant/components/debugpy/__init__.py +++ b/homeassistant/components/debugpy/__init__.py @@ -6,7 +6,7 @@ from asyncio import Event, get_running_loop import logging from threading import Thread -import debugpy +import debugpy # noqa: T100 import voluptuous as vol from homeassistant.const import CONF_HOST, CONF_PORT @@ -60,7 +60,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ready = Event() def waitfor(): - debugpy.wait_for_client() + debugpy.wait_for_client() # noqa: T100 hass.loop.call_soon_threadsafe(ready.set) Thread(target=waitfor).start() diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 601190eaab7..56cbf47b4e3 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -61,7 +61,12 @@ async def async_setup_events(hub: DeconzHub) -> None: @callback def async_add_sensor(_: EventType, sensor_id: str) -> None: """Create DeconzEvent.""" - new_event: DeconzAlarmEvent | DeconzEvent | DeconzPresenceEvent | DeconzRelativeRotaryEvent + new_event: ( + DeconzAlarmEvent + | DeconzEvent + | DeconzPresenceEvent + | DeconzRelativeRotaryEvent + ) sensor = hub.api.sensors[sensor_id] if isinstance(sensor, Switch): diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 4eaff935dce..6d95d18214e 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -134,8 +134,7 @@ async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: Literal[DeviceAutomationType.TRIGGER], -) -> DeviceAutomationTriggerProtocol: - ... +) -> DeviceAutomationTriggerProtocol: ... @overload @@ -143,8 +142,7 @@ async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: Literal[DeviceAutomationType.CONDITION], -) -> DeviceAutomationConditionProtocol: - ... +) -> DeviceAutomationConditionProtocol: ... @overload @@ -152,15 +150,13 @@ async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: Literal[DeviceAutomationType.ACTION], -) -> DeviceAutomationActionProtocol: - ... +) -> DeviceAutomationActionProtocol: ... @overload async def async_get_device_automation_platform( hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType -) -> DeviceAutomationPlatformType: - ... +) -> DeviceAutomationPlatformType: ... async def async_get_device_automation_platform( diff --git a/homeassistant/components/devolo_home_network/device_tracker.py b/homeassistant/components/devolo_home_network/device_tracker.py index 92ab56ab922..f97a4c36400 100644 --- a/homeassistant/components/devolo_home_network/device_tracker.py +++ b/homeassistant/components/devolo_home_network/device_tracker.py @@ -28,9 +28,9 @@ async def async_setup_entry( ) -> None: """Get all devices and sensors and setup them via config entry.""" device: Device = hass.data[DOMAIN][entry.entry_id]["device"] - coordinators: dict[ - str, DataUpdateCoordinator[list[ConnectedStationInfo]] - ] = hass.data[DOMAIN][entry.entry_id]["coordinators"] + coordinators: dict[str, DataUpdateCoordinator[list[ConnectedStationInfo]]] = ( + hass.data[DOMAIN][entry.entry_id]["coordinators"] + ) registry = er.async_get(hass) tracked = set() diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index 9dec7cc15be..5ada7713c33 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -41,13 +41,17 @@ CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) class DiagnosticsPlatformData: """Diagnostic platform data.""" - config_entry_diagnostics: Callable[ - [HomeAssistant, ConfigEntry], Coroutine[Any, Any, Mapping[str, Any]] - ] | None - device_diagnostics: Callable[ - [HomeAssistant, ConfigEntry, DeviceEntry], - Coroutine[Any, Any, Mapping[str, Any]], - ] | None + config_entry_diagnostics: ( + Callable[[HomeAssistant, ConfigEntry], Coroutine[Any, Any, Mapping[str, Any]]] + | None + ) + device_diagnostics: ( + Callable[ + [HomeAssistant, ConfigEntry, DeviceEntry], + Coroutine[Any, Any, Mapping[str, Any]], + ] + | None + ) @dataclass(slots=True) diff --git a/homeassistant/components/diagnostics/util.py b/homeassistant/components/diagnostics/util.py index 5b30692aa9b..9b33b33f1ed 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -18,8 +18,7 @@ def async_redact_data(data: Mapping, to_redact: Iterable[Any]) -> dict: # type: @overload -def async_redact_data(data: _T, to_redact: Iterable[Any]) -> _T: - ... +def async_redact_data(data: _T, to_redact: Iterable[Any]) -> _T: ... @callback diff --git a/homeassistant/components/ecovacs/event.py b/homeassistant/components/ecovacs/event.py index dbef806fa76..daac4a626ae 100644 --- a/homeassistant/components/ecovacs/event.py +++ b/homeassistant/components/ecovacs/event.py @@ -1,6 +1,5 @@ """Event module.""" - from deebot_client.capabilities import Capabilities, CapabilityEvent from deebot_client.device import Device from deebot_client.events import CleanJobStatus, ReportStatsEvent diff --git a/homeassistant/components/entur_public_transport/sensor.py b/homeassistant/components/entur_public_transport/sensor.py index fed556359bd..70b86d0271f 100644 --- a/homeassistant/components/entur_public_transport/sensor.py +++ b/homeassistant/components/entur_public_transport/sensor.py @@ -238,9 +238,9 @@ class EnturPublicTransportSensor(SensorEntity): self._attributes[ATTR_NEXT_UP_AT] = calls[1].expected_departure_time.strftime( "%H:%M" ) - self._attributes[ - ATTR_NEXT_UP_IN - ] = f"{due_in_minutes(calls[1].expected_departure_time)} min" + self._attributes[ATTR_NEXT_UP_IN] = ( + f"{due_in_minutes(calls[1].expected_departure_time)} min" + ) self._attributes[ATTR_NEXT_UP_REALTIME] = calls[1].is_realtime self._attributes[ATTR_NEXT_UP_DELAY] = calls[1].delay_in_min diff --git a/homeassistant/components/esphome/alarm_control_panel.py b/homeassistant/components/esphome/alarm_control_panel.py index 0c483119b22..54bce4e6015 100644 --- a/homeassistant/components/esphome/alarm_control_panel.py +++ b/homeassistant/components/esphome/alarm_control_panel.py @@ -40,21 +40,21 @@ from .entity import ( ) from .enum_mapper import EsphomeEnumMapper -_ESPHOME_ACP_STATE_TO_HASS_STATE: EsphomeEnumMapper[ - AlarmControlPanelState, str -] = EsphomeEnumMapper( - { - AlarmControlPanelState.DISARMED: STATE_ALARM_DISARMED, - AlarmControlPanelState.ARMED_HOME: STATE_ALARM_ARMED_HOME, - AlarmControlPanelState.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, - AlarmControlPanelState.ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, - AlarmControlPanelState.ARMED_VACATION: STATE_ALARM_ARMED_VACATION, - AlarmControlPanelState.ARMED_CUSTOM_BYPASS: STATE_ALARM_ARMED_CUSTOM_BYPASS, - AlarmControlPanelState.PENDING: STATE_ALARM_PENDING, - AlarmControlPanelState.ARMING: STATE_ALARM_ARMING, - AlarmControlPanelState.DISARMING: STATE_ALARM_DISARMING, - AlarmControlPanelState.TRIGGERED: STATE_ALARM_TRIGGERED, - } +_ESPHOME_ACP_STATE_TO_HASS_STATE: EsphomeEnumMapper[AlarmControlPanelState, str] = ( + EsphomeEnumMapper( + { + AlarmControlPanelState.DISARMED: STATE_ALARM_DISARMED, + AlarmControlPanelState.ARMED_HOME: STATE_ALARM_ARMED_HOME, + AlarmControlPanelState.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, + AlarmControlPanelState.ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, + AlarmControlPanelState.ARMED_VACATION: STATE_ALARM_ARMED_VACATION, + AlarmControlPanelState.ARMED_CUSTOM_BYPASS: STATE_ALARM_ARMED_CUSTOM_BYPASS, + AlarmControlPanelState.PENDING: STATE_ALARM_PENDING, + AlarmControlPanelState.ARMING: STATE_ALARM_ARMING, + AlarmControlPanelState.DISARMING: STATE_ALARM_DISARMING, + AlarmControlPanelState.TRIGGERED: STATE_ALARM_TRIGGERED, + } + ) ) diff --git a/homeassistant/components/esphome/entity.py b/homeassistant/components/esphome/entity.py index c069f93276b..4f32f62ee62 100644 --- a/homeassistant/components/esphome/entity.py +++ b/homeassistant/components/esphome/entity.py @@ -166,14 +166,14 @@ def convert_api_error_ha_error( ICON_SCHEMA = vol.Schema(cv.icon) -ENTITY_CATEGORIES: EsphomeEnumMapper[ - EsphomeEntityCategory, EntityCategory | None -] = EsphomeEnumMapper( - { - EsphomeEntityCategory.NONE: None, - EsphomeEntityCategory.CONFIG: EntityCategory.CONFIG, - EsphomeEntityCategory.DIAGNOSTIC: EntityCategory.DIAGNOSTIC, - } +ENTITY_CATEGORIES: EsphomeEnumMapper[EsphomeEntityCategory, EntityCategory | None] = ( + EsphomeEnumMapper( + { + EsphomeEntityCategory.NONE: None, + EsphomeEntityCategory.CONFIG: EntityCategory.CONFIG, + EsphomeEntityCategory.DIAGNOSTIC: EntityCategory.DIAGNOSTIC, + } + ) ) diff --git a/homeassistant/components/esphome/enum_mapper.py b/homeassistant/components/esphome/enum_mapper.py index fd09f9a05b6..0e59cde8a7e 100644 --- a/homeassistant/components/esphome/enum_mapper.py +++ b/homeassistant/components/esphome/enum_mapper.py @@ -21,12 +21,10 @@ class EsphomeEnumMapper(Generic[_EnumT, _ValT]): self._inverse: dict[_ValT, _EnumT] = {v: k for k, v in mapping.items()} @overload - def from_esphome(self, value: _EnumT) -> _ValT: - ... + def from_esphome(self, value: _EnumT) -> _ValT: ... @overload - def from_esphome(self, value: _EnumT | None) -> _ValT | None: - ... + def from_esphome(self, value: _EnumT | None) -> _ValT | None: ... def from_esphome(self, value: _EnumT | None) -> _ValT | None: """Convert from an esphome int representation to a hass string.""" diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index e89901604b2..4c99463505f 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -52,15 +52,15 @@ async def async_setup_entry( ) -_STATE_CLASSES: EsphomeEnumMapper[ - EsphomeSensorStateClass, SensorStateClass | None -] = EsphomeEnumMapper( - { - EsphomeSensorStateClass.NONE: None, - EsphomeSensorStateClass.MEASUREMENT: SensorStateClass.MEASUREMENT, - EsphomeSensorStateClass.TOTAL_INCREASING: SensorStateClass.TOTAL_INCREASING, - EsphomeSensorStateClass.TOTAL: SensorStateClass.TOTAL, - } +_STATE_CLASSES: EsphomeEnumMapper[EsphomeSensorStateClass, SensorStateClass | None] = ( + EsphomeEnumMapper( + { + EsphomeSensorStateClass.NONE: None, + EsphomeSensorStateClass.MEASUREMENT: SensorStateClass.MEASUREMENT, + EsphomeSensorStateClass.TOTAL_INCREASING: SensorStateClass.TOTAL_INCREASING, + EsphomeSensorStateClass.TOTAL: SensorStateClass.TOTAL, + } + ) ) diff --git a/homeassistant/components/file_upload/__init__.py b/homeassistant/components/file_upload/__init__.py index bb8c34fb28a..60caf0ef7f3 100644 --- a/homeassistant/components/file_upload/__init__.py +++ b/homeassistant/components/file_upload/__init__.py @@ -154,9 +154,9 @@ class FileUploadView(HomeAssistantView): file_upload_data: FileUploadData = hass.data[DOMAIN] file_dir = file_upload_data.file_dir(file_id) - queue: SimpleQueue[ - tuple[bytes, asyncio.Future[None] | None] | None - ] = SimpleQueue() + queue: SimpleQueue[tuple[bytes, asyncio.Future[None] | None] | None] = ( + SimpleQueue() + ) def _sync_queue_consumer() -> None: file_dir.mkdir() diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index 6f7f9f2689a..2440fc82943 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -96,9 +96,9 @@ def fill_in_schema_dict(some_input): schema_dict = {} for field, _type in DATA_SCHEMA_DICT.items(): if some_input.get(str(field)): - schema_dict[ - vol.Optional(str(field), default=some_input[str(field)]) - ] = _type + schema_dict[vol.Optional(str(field), default=some_input[str(field)])] = ( + _type + ) else: schema_dict[field] = _type return schema_dict diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index 2b6101c9c33..44596a448fc 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -127,9 +127,9 @@ async def async_setup_entry( forked_daapd_updater = ForkedDaapdUpdater( hass, forked_daapd_api, config_entry.entry_id ) - hass.data[DOMAIN][config_entry.entry_id][ - HASS_DATA_UPDATER_KEY - ] = forked_daapd_updater + hass.data[DOMAIN][config_entry.entry_id][HASS_DATA_UPDATER_KEY] = ( + forked_daapd_updater + ) await forked_daapd_updater.async_init() @@ -956,9 +956,9 @@ class ForkedDaapdUpdater: if not {"outputs", "volume"}.isdisjoint(update_types): # update outputs if outputs := await self._api.get_request("outputs"): outputs = outputs["outputs"] - update_events[ - "outputs" - ] = asyncio.Event() # only for master, zones should ignore + update_events["outputs"] = ( + asyncio.Event() + ) # only for master, zones should ignore async_dispatcher_send( self.hass, SIGNAL_UPDATE_OUTPUTS.format(self._entry_id), diff --git a/homeassistant/components/fronius/coordinator.py b/homeassistant/components/fronius/coordinator.py index b36f1a653d9..d0a20b25bee 100644 --- a/homeassistant/components/fronius/coordinator.py +++ b/homeassistant/components/fronius/coordinator.py @@ -78,9 +78,9 @@ class FroniusCoordinatorBase( for solar_net_id in data: if solar_net_id not in self.unregistered_descriptors: # id seen for the first time - self.unregistered_descriptors[ - solar_net_id - ] = self.valid_descriptions.copy() + self.unregistered_descriptors[solar_net_id] = ( + self.valid_descriptions.copy() + ) return data @callback @@ -115,9 +115,9 @@ class FroniusCoordinatorBase( solar_net_id=solar_net_id, ) ) - self.unregistered_descriptors[ - solar_net_id - ] = remaining_unregistered_descriptors + self.unregistered_descriptors[solar_net_id] = ( + remaining_unregistered_descriptors + ) async_add_entities(new_entities) _add_entities_for_unregistered_descriptors() diff --git a/homeassistant/components/geo_rss_events/sensor.py b/homeassistant/components/geo_rss_events/sensor.py index d64c08a6917..8c704bcf16a 100644 --- a/homeassistant/components/geo_rss_events/sensor.py +++ b/homeassistant/components/geo_rss_events/sensor.py @@ -162,9 +162,9 @@ class GeoRssServiceSensor(SensorEntity): # And now compute the attributes from the filtered events. matrix = {} for entry in feed_entries: - matrix[ - entry.title - ] = f"{entry.distance_to_home:.0f}{UnitOfLength.KILOMETERS}" + matrix[entry.title] = ( + f"{entry.distance_to_home:.0f}{UnitOfLength.KILOMETERS}" + ) self._state_attributes = matrix elif status == UPDATE_OK_NO_DATA: _LOGGER.debug("Update successful, but no data received from %s", self._feed) diff --git a/homeassistant/components/geofency/device_tracker.py b/homeassistant/components/geofency/device_tracker.py index eff937a2170..178c72d2071 100644 --- a/homeassistant/components/geofency/device_tracker.py +++ b/homeassistant/components/geofency/device_tracker.py @@ -30,9 +30,9 @@ async def async_setup_entry( async_add_entities([GeofencyEntity(device, gps, location_name, attributes)]) - hass.data[GF_DOMAIN]["unsub_device_tracker"][ - config_entry.entry_id - ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + hass.data[GF_DOMAIN]["unsub_device_tracker"][config_entry.entry_id] = ( + async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + ) # Restore previously loaded devices dev_reg = dr.async_get(hass) diff --git a/homeassistant/components/govee_ble/__init__.py b/homeassistant/components/govee_ble/__init__.py index d5c601b55d9..8d074b6f997 100644 --- a/homeassistant/components/govee_ble/__init__.py +++ b/homeassistant/components/govee_ble/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = GoveeBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/gpslogger/device_tracker.py b/homeassistant/components/gpslogger/device_tracker.py index 025183718fc..4a28606662f 100644 --- a/homeassistant/components/gpslogger/device_tracker.py +++ b/homeassistant/components/gpslogger/device_tracker.py @@ -40,9 +40,9 @@ async def async_setup_entry( async_add_entities([GPSLoggerEntity(device, gps, battery, accuracy, attrs)]) - hass.data[GPL_DOMAIN]["unsub_device_tracker"][ - entry.entry_id - ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + hass.data[GPL_DOMAIN]["unsub_device_tracker"][entry.entry_id] = ( + async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + ) # Restore previously loaded devices dev_reg = dr.async_get(hass) diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 8a20755e936..a0a0f0ebc0e 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -737,10 +737,10 @@ class GTFSDepartureSensor(SensorEntity): self._attributes[ATTR_LOCATION_DESTINATION] = LOCATION_TYPE_OPTIONS.get( self._destination.location_type, LOCATION_TYPE_DEFAULT ) - self._attributes[ - ATTR_WHEELCHAIR_DESTINATION - ] = WHEELCHAIR_BOARDING_OPTIONS.get( - self._destination.wheelchair_boarding, WHEELCHAIR_BOARDING_DEFAULT + self._attributes[ATTR_WHEELCHAIR_DESTINATION] = ( + WHEELCHAIR_BOARDING_OPTIONS.get( + self._destination.wheelchair_boarding, WHEELCHAIR_BOARDING_DEFAULT + ) ) # Manage Route metadata diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index 4c6a979cd1d..812c54d76a6 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -139,16 +139,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: (API_VALVE_STATUS, client.valve.status), (API_WIFI_STATUS, client.wifi.status), ): - coordinator = valve_controller_coordinators[ - api - ] = GuardianDataUpdateCoordinator( - hass, - entry=entry, - client=client, - api_name=api, - api_coro=api_coro, - api_lock=api_lock, - valve_controller_uid=entry.data[CONF_UID], + coordinator = valve_controller_coordinators[api] = ( + GuardianDataUpdateCoordinator( + hass, + entry=entry, + client=client, + api_name=api, + api_coro=api_coro, + api_lock=api_lock, + valve_controller_uid=entry.data[CONF_UID], + ) ) init_valve_controller_tasks.append(async_init_coordinator(coordinator)) diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index 0b92ada6c70..ffb67730fa5 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -148,9 +148,9 @@ class HassIOView(HomeAssistantView): return web.Response(status=HTTPStatus.UNAUTHORIZED) if authorized: - headers[ - AUTHORIZATION - ] = f"Bearer {os.environ.get('SUPERVISOR_TOKEN', '')}" + headers[AUTHORIZATION] = ( + f"Bearer {os.environ.get('SUPERVISOR_TOKEN', '')}" + ) if request.method == "POST": headers[CONTENT_TYPE] = request.content_type diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 5fd64ec7d38..564b764bc2e 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -161,9 +161,9 @@ class HeosMediaPlayer(MediaPlayerEntity): async_dispatcher_connect(self.hass, SIGNAL_HEOS_UPDATED, self._heos_updated) ) # Register this player's entity_id so it can be resolved by the group manager - self.hass.data[HEOS_DOMAIN][DATA_ENTITY_ID_MAP][ - self._player.player_id - ] = self.entity_id + self.hass.data[HEOS_DOMAIN][DATA_ENTITY_ID_MAP][self._player.player_id] = ( + self.entity_id + ) async_dispatcher_send(self.hass, SIGNAL_HEOS_PLAYER_ADDED) @log_command_error("clear playlist") diff --git a/homeassistant/components/http/decorators.py b/homeassistant/components/http/decorators.py index bffb42d5817..d2e6121b08e 100644 --- a/homeassistant/components/http/decorators.py +++ b/homeassistant/components/http/decorators.py @@ -29,15 +29,13 @@ def require_admin( ) -> Callable[ [_FuncType[_HomeAssistantViewT, _P, _ResponseT]], _FuncType[_HomeAssistantViewT, _P, _ResponseT], -]: - ... +]: ... @overload def require_admin( _func: _FuncType[_HomeAssistantViewT, _P, _ResponseT], -) -> _FuncType[_HomeAssistantViewT, _P, _ResponseT]: - ... +) -> _FuncType[_HomeAssistantViewT, _P, _ResponseT]: ... def require_admin( diff --git a/homeassistant/components/humidifier/device_trigger.py b/homeassistant/components/humidifier/device_trigger.py index 757b0d9a078..80e0ef8df58 100644 --- a/homeassistant/components/humidifier/device_trigger.py +++ b/homeassistant/components/humidifier/device_trigger.py @@ -126,13 +126,13 @@ async def async_attach_trigger( ), } if trigger_type == "target_humidity_changed": - numeric_state_config[ - numeric_state_trigger.CONF_VALUE_TEMPLATE - ] = "{{ state.attributes.humidity }}" + numeric_state_config[numeric_state_trigger.CONF_VALUE_TEMPLATE] = ( + "{{ state.attributes.humidity }}" + ) else: # trigger_type == "current_humidity_changed" - numeric_state_config[ - numeric_state_trigger.CONF_VALUE_TEMPLATE - ] = "{{ state.attributes.current_humidity }}" + numeric_state_config[numeric_state_trigger.CONF_VALUE_TEMPLATE] = ( + "{{ state.attributes.current_humidity }}" + ) if CONF_ABOVE in config: numeric_state_config[CONF_ABOVE] = config[CONF_ABOVE] diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index d1ac0950c58..106a61e75cc 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -1,4 +1,5 @@ """The Hunter Douglas PowerView integration.""" + import logging from aiopvapi.helpers.aiorequest import AioRequest diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index 7e7f4632b59..015726fbf73 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -150,9 +150,9 @@ class IcloudAccount: self._family_members_fullname = {} if user_info.get("membersInfo") is not None: for prs_id, member in user_info["membersInfo"].items(): - self._family_members_fullname[ - prs_id - ] = f"{member['firstName']} {member['lastName']}" + self._family_members_fullname[prs_id] = ( + f"{member['firstName']} {member['lastName']}" + ) self._devices = {} self.update_devices() diff --git a/homeassistant/components/imap/__init__.py b/homeassistant/components/imap/__init__.py index 091110c5e51..7504446f3fb 100644 --- a/homeassistant/components/imap/__init__.py +++ b/homeassistant/components/imap/__init__.py @@ -63,8 +63,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): - coordinator: ImapPushDataUpdateCoordinator | ImapPollingDataUpdateCoordinator = hass.data[ - DOMAIN - ].pop(entry.entry_id) + coordinator: ( + ImapPushDataUpdateCoordinator | ImapPollingDataUpdateCoordinator + ) = hass.data[DOMAIN].pop(entry.entry_id) await coordinator.shutdown() return unload_ok diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 5c40dac1c9d..b1c0cc53d61 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -513,9 +513,9 @@ class InfluxThread(threading.Thread): def __init__(self, hass, influx, event_to_json, max_tries): """Initialize the listener.""" threading.Thread.__init__(self, name=DOMAIN) - self.queue: queue.SimpleQueue[ - threading.Event | tuple[float, Event] | None - ] = queue.SimpleQueue() + self.queue: queue.SimpleQueue[threading.Event | tuple[float, Event] | None] = ( + queue.SimpleQueue() + ) self.influx = influx self.event_to_json = event_to_json self.max_tries = max_tries diff --git a/homeassistant/components/inkbird/__init__.py b/homeassistant/components/inkbird/__init__.py index f78a6c4e1b8..c715c64599a 100644 --- a/homeassistant/components/inkbird/__init__.py +++ b/homeassistant/components/inkbird/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = INKBIRDBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/iqvia/sensor.py b/homeassistant/components/iqvia/sensor.py index da8556d9218..ba3c288b702 100644 --- a/homeassistant/components/iqvia/sensor.py +++ b/homeassistant/components/iqvia/sensor.py @@ -212,12 +212,12 @@ class ForecastSensor(IQVIAEntity, SensorEntity): if not outlook_coordinator.last_update_success: return - self._attr_extra_state_attributes[ - ATTR_OUTLOOK - ] = outlook_coordinator.data.get("Outlook") - self._attr_extra_state_attributes[ - ATTR_SEASON - ] = outlook_coordinator.data.get("Season") + self._attr_extra_state_attributes[ATTR_OUTLOOK] = ( + outlook_coordinator.data.get("Outlook") + ) + self._attr_extra_state_attributes[ATTR_SEASON] = ( + outlook_coordinator.data.get("Season") + ) class IndexSensor(IQVIAEntity, SensorEntity): @@ -283,8 +283,8 @@ class IndexSensor(IQVIAEntity, SensorEntity): ) elif self.entity_description.key == TYPE_DISEASE_TODAY: for attrs in period["Triggers"]: - self._attr_extra_state_attributes[ - f"{attrs['Name'].lower()}_index" - ] = attrs["Index"] + self._attr_extra_state_attributes[f"{attrs['Name'].lower()}_index"] = ( + attrs["Index"] + ) self._attr_native_value = period["Index"] diff --git a/homeassistant/components/isy994/helpers.py b/homeassistant/components/isy994/helpers.py index 1e496446a30..8b6a4249931 100644 --- a/homeassistant/components/isy994/helpers.py +++ b/homeassistant/components/isy994/helpers.py @@ -317,9 +317,9 @@ def _generate_device_info(node: Node) -> DeviceInfo: and node.zwave_props and node.zwave_props.mfr_id != "0" ): - device_info[ - ATTR_MANUFACTURER - ] = f"Z-Wave MfrID:{int(node.zwave_props.mfr_id):#0{6}x}" + device_info[ATTR_MANUFACTURER] = ( + f"Z-Wave MfrID:{int(node.zwave_props.mfr_id):#0{6}x}" + ) model += ( f"Type:{int(node.zwave_props.prod_type_id):#0{6}x} " f"Product:{int(node.zwave_props.product_id):#0{6}x}" diff --git a/homeassistant/components/kegtron/__init__.py b/homeassistant/components/kegtron/__init__.py index 55b93a0da3d..d7485be0840 100644 --- a/homeassistant/components/kegtron/__init__.py +++ b/homeassistant/components/kegtron/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = KegtronBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/leaone/__init__.py b/homeassistant/components/leaone/__init__.py index 74dfa872ce2..74119cfaa4c 100644 --- a/homeassistant/components/leaone/__init__.py +++ b/homeassistant/components/leaone/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = LeaoneBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/lidarr/sensor.py b/homeassistant/components/lidarr/sensor.py index 9a3dee1a802..c876aec4623 100644 --- a/homeassistant/components/lidarr/sensor.py +++ b/homeassistant/components/lidarr/sensor.py @@ -63,10 +63,13 @@ class LidarrSensorEntityDescription( """Class to describe a Lidarr sensor.""" attributes_fn: Callable[[T], dict[str, str] | None] = lambda _: None - description_fn: Callable[ - [LidarrSensorEntityDescription[T], LidarrRootFolder], - tuple[LidarrSensorEntityDescription[T], str] | None, - ] | None = None + description_fn: ( + Callable[ + [LidarrSensorEntityDescription[T], LidarrRootFolder], + tuple[LidarrSensorEntityDescription[T], str] | None, + ] + | None + ) = None SENSOR_TYPES: dict[str, LidarrSensorEntityDescription[Any]] = { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 0045e2324ff..53c7328ece4 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1218,9 +1218,9 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): color_temp_kelvin = self.color_temp_kelvin data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin if color_temp_kelvin: - data[ - ATTR_COLOR_TEMP - ] = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin) + data[ATTR_COLOR_TEMP] = ( + color_util.color_temperature_kelvin_to_mired(color_temp_kelvin) + ) else: data[ATTR_COLOR_TEMP] = None else: @@ -1233,9 +1233,9 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): color_temp_kelvin = self.color_temp_kelvin data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin if color_temp_kelvin: - data[ - ATTR_COLOR_TEMP - ] = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin) + data[ATTR_COLOR_TEMP] = ( + color_util.color_temperature_kelvin_to_mired(color_temp_kelvin) + ) else: data[ATTR_COLOR_TEMP] = None else: diff --git a/homeassistant/components/locative/device_tracker.py b/homeassistant/components/locative/device_tracker.py index e8135bbd03c..0b5cb32c22b 100644 --- a/homeassistant/components/locative/device_tracker.py +++ b/homeassistant/components/locative/device_tracker.py @@ -24,9 +24,9 @@ async def async_setup_entry( async_add_entities([LocativeEntity(device, location, location_name)]) - hass.data[LT_DOMAIN]["unsub_device_tracker"][ - entry.entry_id - ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + hass.data[LT_DOMAIN]["unsub_device_tracker"][entry.entry_id] = ( + async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + ) class LocativeEntity(TrackerEntity): diff --git a/homeassistant/components/london_air/sensor.py b/homeassistant/components/london_air/sensor.py index 12d52b9a20a..0895e507a85 100644 --- a/homeassistant/components/london_air/sensor.py +++ b/homeassistant/components/london_air/sensor.py @@ -166,9 +166,9 @@ def parse_species(species_data): species_dict["code"] = species["@SpeciesCode"] species_dict["quality"] = species["@AirQualityBand"] species_dict["index"] = species["@AirQualityIndex"] - species_dict[ - "summary" - ] = f"{species_dict['code']} is {species_dict['quality']}" + species_dict["summary"] = ( + f"{species_dict['code']} is {species_dict['quality']}" + ) parsed_species_data.append(species_dict) quality_list.append(species_dict["quality"]) return parsed_species_data, quality_list diff --git a/homeassistant/components/moat/__init__.py b/homeassistant/components/moat/__init__.py index 4c48c03b897..8ee2e294552 100644 --- a/homeassistant/components/moat/__init__.py +++ b/homeassistant/components/moat/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = MoatBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 3e16c21261b..e7cccd0f151 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -602,9 +602,9 @@ async def webhook_register_sensor( async_dispatcher_send(hass, f"{SIGNAL_SENSOR_UPDATE}-{unique_store_key}", data) else: data[CONF_UNIQUE_ID] = unique_store_key - data[ - CONF_NAME - ] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" + data[CONF_NAME] = ( + f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" + ) register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" async_dispatcher_send(hass, register_signal, data) diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index d955c6656f8..0d1848e0d8e 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -258,7 +258,9 @@ class ModbusHub: """Initialize the Modbus hub.""" # generic configuration - self._client: AsyncModbusSerialClient | AsyncModbusTcpClient | AsyncModbusUdpClient | None = None + self._client: ( + AsyncModbusSerialClient | AsyncModbusTcpClient | AsyncModbusUdpClient | None + ) = None self._async_cancel_listener: Callable[[], None] | None = None self._in_error = False self._lock = asyncio.Lock() diff --git a/homeassistant/components/mopeka/__init__.py b/homeassistant/components/mopeka/__init__.py index 09dec3bf3d2..da3ee156683 100644 --- a/homeassistant/components/mopeka/__init__.py +++ b/homeassistant/components/mopeka/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = MopekaIOTBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index b6fcae4d5ec..5bf0c9c1879 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -398,9 +398,9 @@ class MQTTOptionsFlowHandler(OptionsFlow): # build form fields: OrderedDict[vol.Marker, Any] = OrderedDict() fields[vol.Optional(CONF_DISCOVERY, default=discovery)] = BOOLEAN_SELECTOR - fields[ - vol.Optional(CONF_DISCOVERY_PREFIX, default=discovery_prefix) - ] = PUBLISH_TOPIC_SELECTOR + fields[vol.Optional(CONF_DISCOVERY_PREFIX, default=discovery_prefix)] = ( + PUBLISH_TOPIC_SELECTOR + ) # Birth message is disabled if CONF_BIRTH_MESSAGE = {} fields[ @@ -421,9 +421,9 @@ class MQTTOptionsFlowHandler(OptionsFlow): ) ] = TEXT_SELECTOR fields[vol.Optional("birth_qos", default=birth[ATTR_QOS])] = QOS_SELECTOR - fields[ - vol.Optional("birth_retain", default=birth[ATTR_RETAIN]) - ] = BOOLEAN_SELECTOR + fields[vol.Optional("birth_retain", default=birth[ATTR_RETAIN])] = ( + BOOLEAN_SELECTOR + ) # Will message is disabled if CONF_WILL_MESSAGE = {} fields[ @@ -444,9 +444,9 @@ class MQTTOptionsFlowHandler(OptionsFlow): ) ] = TEXT_SELECTOR fields[vol.Optional("will_qos", default=will[ATTR_QOS])] = QOS_SELECTOR - fields[ - vol.Optional("will_retain", default=will[ATTR_RETAIN]) - ] = BOOLEAN_SELECTOR + fields[vol.Optional("will_retain", default=will[ATTR_RETAIN])] = ( + BOOLEAN_SELECTOR + ) return self.async_show_form( step_id="options", diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 84afe26bc97..42ad807d2f1 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1055,16 +1055,16 @@ class MqttDiscoveryUpdate(Entity): if self._discovery_data is not None: discovery_hash: tuple[str, str] = self._discovery_data[ATTR_DISCOVERY_HASH] if self.registry_entry is not None: - self._registry_hooks[ - discovery_hash - ] = async_track_entity_registry_updated_event( - self.hass, - self.entity_id, - partial( - async_clear_discovery_topic_if_entity_removed, + self._registry_hooks[discovery_hash] = ( + async_track_entity_registry_updated_event( self.hass, - self._discovery_data, - ), + self.entity_id, + partial( + async_clear_discovery_topic_if_entity_removed, + self.hass, + self._discovery_data, + ), + ) ) stop_discovery_updates(self.hass, self._discovery_data) send_discovery_done(self.hass, self._discovery_data) diff --git a/homeassistant/components/mutesync/__init__.py b/homeassistant/components/mutesync/__init__.py index 40aba72b0f6..75eefaf6784 100644 --- a/homeassistant/components/mutesync/__init__.py +++ b/homeassistant/components/mutesync/__init__.py @@ -41,14 +41,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return state - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = update_coordinator.DataUpdateCoordinator( - hass, - logging.getLogger(__name__), - name=DOMAIN, - update_interval=UPDATE_INTERVAL_NOT_IN_MEETING, - update_method=update_data, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + update_coordinator.DataUpdateCoordinator( + hass, + logging.getLogger(__name__), + name=DOMAIN, + update_interval=UPDATE_INTERVAL_NOT_IN_MEETING, + update_method=update_data, + ) ) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/mysensors/config_flow.py b/homeassistant/components/mysensors/config_flow.py index 7920e8025cf..b4347a39e12 100644 --- a/homeassistant/components/mysensors/config_flow.py +++ b/homeassistant/components/mysensors/config_flow.py @@ -302,9 +302,9 @@ class MySensorsConfigFlowHandler(ConfigFlow, domain=DOMAIN): except vol.Invalid: errors[CONF_PERSISTENCE_FILE] = "invalid_persistence_file" else: - real_persistence_path = user_input[ - CONF_PERSISTENCE_FILE - ] = self._normalize_persistence_file(user_input[CONF_PERSISTENCE_FILE]) + real_persistence_path = user_input[CONF_PERSISTENCE_FILE] = ( + self._normalize_persistence_file(user_input[CONF_PERSISTENCE_FILE]) + ) for other_entry in self._async_current_entries(): if CONF_PERSISTENCE_FILE not in other_entry.data: continue diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py index 177dbbe4dc9..b932a33d0fa 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -279,10 +279,8 @@ async def _gw_start( gateway.on_conn_made = gateway_connected # Don't use hass.async_create_task to avoid holding up setup indefinitely. - hass.data[DOMAIN][ - MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id) - ] = asyncio.create_task( - gateway.start() + 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 async def stop_this_gw(_: Event) -> None: diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index df9c7f6fee2..15bf3291618 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -267,9 +267,9 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): "name", None, ) - self._attr_extra_state_attributes[ - ATTR_SELECTED_SCHEDULE - ] = self._selected_schedule + self._attr_extra_state_attributes[ATTR_SELECTED_SCHEDULE] = ( + self._selected_schedule + ) self.async_write_ha_state() self.data_handler.async_force_update(self._signal_name) return @@ -430,14 +430,14 @@ class NetatmoThermostat(NetatmoBaseEntity, ClimateEntity): self._selected_schedule = getattr( self._room.home.get_selected_schedule(), "name", None ) - self._attr_extra_state_attributes[ - ATTR_SELECTED_SCHEDULE - ] = self._selected_schedule + self._attr_extra_state_attributes[ATTR_SELECTED_SCHEDULE] = ( + self._selected_schedule + ) if self._model == NA_VALVE: - self._attr_extra_state_attributes[ - ATTR_HEATING_POWER_REQUEST - ] = self._room.heating_power_request + self._attr_extra_state_attributes[ATTR_HEATING_POWER_REQUEST] = ( + self._room.heating_power_request + ) else: for module in self._room.modules.values(): if hasattr(module, "boiler_status"): diff --git a/homeassistant/components/netatmo/config_flow.py b/homeassistant/components/netatmo/config_flow.py index 54ec019f815..b5eae967d56 100644 --- a/homeassistant/components/netatmo/config_flow.py +++ b/homeassistant/components/netatmo/config_flow.py @@ -149,13 +149,13 @@ class NetatmoOptionsFlowHandler(OptionsFlow): async def async_step_public_weather(self, user_input: dict) -> ConfigFlowResult: """Manage configuration of Netatmo public weather sensors.""" if user_input is not None and CONF_NEW_AREA not in user_input: - self.options[CONF_WEATHER_AREAS][ - user_input[CONF_AREA_NAME] - ] = fix_coordinates(user_input) + self.options[CONF_WEATHER_AREAS][user_input[CONF_AREA_NAME]] = ( + fix_coordinates(user_input) + ) - self.options[CONF_WEATHER_AREAS][user_input[CONF_AREA_NAME]][ - CONF_UUID - ] = str(uuid.uuid4()) + self.options[CONF_WEATHER_AREAS][user_input[CONF_AREA_NAME]][CONF_UUID] = ( + str(uuid.uuid4()) + ) return await self.async_step_public_weather_areas() diff --git a/homeassistant/components/nissan_leaf/__init__.py b/homeassistant/components/nissan_leaf/__init__.py index 85ff6876a05..9eaca66119f 100644 --- a/homeassistant/components/nissan_leaf/__init__.py +++ b/homeassistant/components/nissan_leaf/__init__.py @@ -321,9 +321,9 @@ class LeafDataStore: self.data[DATA_RANGE_AC] = None if hasattr(server_response, "cruising_range_ac_off_km"): - self.data[ - DATA_RANGE_AC_OFF - ] = server_response.cruising_range_ac_off_km + self.data[DATA_RANGE_AC_OFF] = ( + server_response.cruising_range_ac_off_km + ) else: self.data[DATA_RANGE_AC_OFF] = None diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index ff89d8090d6..2c549e4ed24 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -82,9 +82,9 @@ class NZBGetConfigFlow(ConfigFlow, domain=DOMAIN): } if self.show_advanced_options: - data_schema[ - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL) - ] = bool + data_schema[vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL)] = ( + bool + ) return self.async_show_form( step_id="user", diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 3fa41d629b2..4b6dfa1a625 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -105,9 +105,9 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): self.stream_options[CONF_RTSP_TRANSPORT] = device.config_entry.options.get( CONF_RTSP_TRANSPORT, next(iter(RTSP_TRANSPORTS)) ) - self.stream_options[ - CONF_USE_WALLCLOCK_AS_TIMESTAMPS - ] = device.config_entry.options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False) + self.stream_options[CONF_USE_WALLCLOCK_AS_TIMESTAMPS] = ( + device.config_entry.options.get(CONF_USE_WALLCLOCK_AS_TIMESTAMPS, False) + ) self._basic_auth = ( device.config_entry.data.get(CONF_SNAPSHOT_AUTH) == HTTP_BASIC_AUTHENTICATION diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index 721ec08cb48..690a3739b4f 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -12,9 +12,9 @@ from homeassistant.util.decorator import Registry from .models import Event -PARSERS: Registry[ - str, Callable[[str, Any], Coroutine[Any, Any, Event | None]] -] = Registry() +PARSERS: Registry[str, Callable[[str, Any], Coroutine[Any, Any, Event | None]]] = ( + Registry() +) VIDEO_SOURCE_MAPPING = { "vsconf": "VideoSourceToken", diff --git a/homeassistant/components/oralb/__init__.py b/homeassistant/components/oralb/__init__.py index 29aa63410e5..71bcb2f2deb 100644 --- a/homeassistant/components/oralb/__init__.py +++ b/homeassistant/components/oralb/__init__.py @@ -65,20 +65,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) return await data.async_poll(connectable_device) - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = ActiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, - needs_poll_method=_needs_poll, - poll_method=_async_poll, - # We will take advertisements from non-connectable devices - # since we will trade the BLEDevice for a connectable one - # if we need to poll it - connectable=False, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + ActiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + needs_poll_method=_needs_poll, + poll_method=_async_poll, + # We will take advertisements from non-connectable devices + # since we will trade the BLEDevice for a connectable one + # if we need to poll it + connectable=False, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index 76a3e5d932b..f81ed82f7b1 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -37,9 +37,9 @@ class OverkizNumberDescription(NumberEntityDescription): min_value_state_name: str | None = None max_value_state_name: str | None = None inverted: bool = False - set_native_value: Callable[ - [float, Callable[..., Awaitable[None]]], Awaitable[None] - ] | None = None + set_native_value: ( + Callable[[float, Callable[..., Awaitable[None]]], Awaitable[None]] | None + ) = None async def _async_set_native_value_boost_mode_duration( diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 30ff8e72f60..eb27f465a7e 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -186,10 +186,10 @@ class PlexLibrarySectionSensor(SensorEntity): libtype=primary_libtype, includeCollections=False ) for libtype in LIBRARY_ATTRIBUTE_TYPES.get(self.library_type, []): - self._attr_extra_state_attributes[ - f"{libtype}s" - ] = self.library_section.totalViewSize( - libtype=libtype, includeCollections=False + self._attr_extra_state_attributes[f"{libtype}s"] = ( + self.library_section.totalViewSize( + libtype=libtype, includeCollections=False + ) ) recent_libtype = LIBRARY_RECENT_LIBTYPE.get( diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index bd2aa7063dd..9e2bf63ce55 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -482,9 +482,9 @@ class PlexServer: continue process_device("session", player) - available_clients[player.machineIdentifier][ - "session" - ] = self.active_sessions[unique_id] + available_clients[player.machineIdentifier]["session"] = ( + self.active_sessions[unique_id] + ) for device in devices: process_device("PMS", device) diff --git a/homeassistant/components/progettihwsw/config_flow.py b/homeassistant/components/progettihwsw/config_flow.py index 91fe41cda52..5a5d0de1a80 100644 --- a/homeassistant/components/progettihwsw/config_flow.py +++ b/homeassistant/components/progettihwsw/config_flow.py @@ -51,13 +51,13 @@ class ProgettiHWSWConfigFlow(ConfigFlow, domain=DOMAIN): relay_modes_schema = {} for i in range(1, int(self.s1_in["relay_count"]) + 1): - relay_modes_schema[ - vol.Required(f"relay_{str(i)}", default="bistable") - ] = vol.In( - { - "bistable": "Bistable (ON/OFF Mode)", - "monostable": "Monostable (Timer Mode)", - } + relay_modes_schema[vol.Required(f"relay_{str(i)}", default="bistable")] = ( + vol.In( + { + "bistable": "Bistable (ON/OFF Mode)", + "monostable": "Monostable (Timer Mode)", + } + ) ) return self.async_show_form( diff --git a/homeassistant/components/proximity/coordinator.py b/homeassistant/components/proximity/coordinator.py index 829e9aaf4a1..ea33c1f8121 100644 --- a/homeassistant/components/proximity/coordinator.py +++ b/homeassistant/components/proximity/coordinator.py @@ -294,15 +294,15 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): old_lat = None old_lon = None - entities_data[state_change_data.entity_id][ - ATTR_DIR_OF_TRAVEL - ] = self._calc_direction_of_travel( - zone_state, - new_state, - old_lat, - old_lon, - new_state.attributes.get(ATTR_LATITUDE), - new_state.attributes.get(ATTR_LONGITUDE), + entities_data[state_change_data.entity_id][ATTR_DIR_OF_TRAVEL] = ( + self._calc_direction_of_travel( + zone_state, + new_state, + old_lat, + old_lon, + new_state.attributes.get(ATTR_LATITUDE), + new_state.attributes.get(ATTR_LONGITUDE), + ) ) # takeover data for legacy proximity entity @@ -337,9 +337,9 @@ class ProximityDataUpdateCoordinator(DataUpdateCoordinator[ProximityData]): if cast(int, nearest_distance_to) == int(distance_to): _LOGGER.debug("set equally close entity_data: %s", entity_data) - proximity_data[ - ATTR_NEAREST - ] = f"{proximity_data[ATTR_NEAREST]}, {str(entity_data[ATTR_NAME])}" + proximity_data[ATTR_NEAREST] = ( + f"{proximity_data[ATTR_NEAREST]}, {str(entity_data[ATTR_NAME])}" + ) return ProximityData(proximity_data, entities_data) diff --git a/homeassistant/components/qingping/__init__.py b/homeassistant/components/qingping/__init__.py index 48cae5c48e6..ac91f314a28 100644 --- a/homeassistant/components/qingping/__init__.py +++ b/homeassistant/components/qingping/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = QingpingBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/radarr/sensor.py b/homeassistant/components/radarr/sensor.py index 0435474e423..e6700fb3637 100644 --- a/homeassistant/components/radarr/sensor.py +++ b/homeassistant/components/radarr/sensor.py @@ -61,10 +61,13 @@ class RadarrSensorEntityDescription( ): """Class to describe a Radarr sensor.""" - description_fn: Callable[ - [RadarrSensorEntityDescription[T], RootFolder], - tuple[RadarrSensorEntityDescription[T], str] | None, - ] | None = None + description_fn: ( + Callable[ + [RadarrSensorEntityDescription[T], RootFolder], + tuple[RadarrSensorEntityDescription[T], str] | None, + ] + | None + ) = None SENSOR_TYPES: dict[str, RadarrSensorEntityDescription[Any]] = { diff --git a/homeassistant/components/rapt_ble/__init__.py b/homeassistant/components/rapt_ble/__init__.py index f5587ba5770..4fd4c32a4cc 100644 --- a/homeassistant/components/rapt_ble/__init__.py +++ b/homeassistant/components/rapt_ble/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = RAPTPillBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 1ae3c296885..36658fb5008 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -89,9 +89,9 @@ class ReCollectWasteSensor(ReCollectWasteEntity, SensorEntity): self._attr_native_value = None else: self._attr_extra_state_attributes[ATTR_AREA_NAME] = event.area_name - self._attr_extra_state_attributes[ - ATTR_PICKUP_TYPES - ] = async_get_pickup_type_names(self._entry, event.pickup_types) + self._attr_extra_state_attributes[ATTR_PICKUP_TYPES] = ( + async_get_pickup_type_names(self._entry, event.pickup_types) + ) self._attr_native_value = event.date super()._handle_coordinator_update() diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 6021c7b5f5b..60373a053c9 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -878,9 +878,10 @@ def _apply_update( # noqa: C901 if engine.dialect.name == SupportedDialect.MYSQL: # Ensure the row format is dynamic or the index # unique will be too large - with contextlib.suppress(SQLAlchemyError), session_scope( - session=session_maker() - ) as session: + with ( + contextlib.suppress(SQLAlchemyError), + session_scope(session=session_maker()) as session, + ): connection = session.connection() # This is safe to run multiple times and fast # since the table is small. @@ -1132,9 +1133,10 @@ def _correct_table_character_set_and_collation( "computers. Please be patient!", table, ) - with contextlib.suppress(SQLAlchemyError), session_scope( - session=session_maker() - ) as session: + with ( + contextlib.suppress(SQLAlchemyError), + session_scope(session=session_maker()) as session, + ): connection = session.connection() connection.execute( # Using LOCK=EXCLUSIVE to prevent the database from corrupting @@ -1579,9 +1581,9 @@ def migrate_event_type_ids(instance: Recorder) -> bool: assert ( db_event_type.event_type is not None ), "event_type should never be None" - event_type_to_id[ - db_event_type.event_type - ] = db_event_type.event_type_id + event_type_to_id[db_event_type.event_type] = ( + db_event_type.event_type_id + ) event_type_manager.clear_non_existent(db_event_type.event_type) session.execute( @@ -1652,9 +1654,9 @@ def migrate_entity_ids(instance: Recorder) -> bool: assert ( db_states_metadata.entity_id is not None ), "entity_id should never be None" - entity_id_to_metadata_id[ - db_states_metadata.entity_id - ] = db_states_metadata.metadata_id + entity_id_to_metadata_id[db_states_metadata.entity_id] = ( + db_states_metadata.metadata_id + ) session.execute( update(States), diff --git a/homeassistant/components/recorder/models/time.py b/homeassistant/components/recorder/models/time.py index af0df4e806d..6295060c8d3 100644 --- a/homeassistant/components/recorder/models/time.py +++ b/homeassistant/components/recorder/models/time.py @@ -16,13 +16,11 @@ EMPTY_JSON_OBJECT = "{}" @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: @@ -36,13 +34,11 @@ def process_timestamp(ts: datetime | None) -> datetime | None: @overload -def process_timestamp_to_utc_isoformat(ts: None) -> None: - ... +def process_timestamp_to_utc_isoformat(ts: None) -> None: ... @overload -def process_timestamp_to_utc_isoformat(ts: datetime) -> str: - ... +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: ... def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: diff --git a/homeassistant/components/rejseplanen/sensor.py b/homeassistant/components/rejseplanen/sensor.py index 6486ba329f4..d95b9e1b271 100644 --- a/homeassistant/components/rejseplanen/sensor.py +++ b/homeassistant/components/rejseplanen/sensor.py @@ -239,9 +239,9 @@ class PublicTransportData: } if real_time_date is not None and real_time_time is not None: - departure_data[ - ATTR_REAL_TIME_AT - ] = f"{real_time_date} {real_time_time}" + departure_data[ATTR_REAL_TIME_AT] = ( + f"{real_time_date} {real_time_time}" + ) if item.get("rtTrack") is not None: departure_data[ATTR_TRACK] = item.get("rtTrack") diff --git a/homeassistant/components/reolink/config_flow.py b/homeassistant/components/reolink/config_flow.py index 3257d183de8..b62a7b7f709 100644 --- a/homeassistant/components/reolink/config_flow.py +++ b/homeassistant/components/reolink/config_flow.py @@ -193,9 +193,9 @@ class ReolinkFlowHandler(ConfigFlow, domain=DOMAIN): errors[CONF_HOST] = "api_error" except ReolinkWebhookException as err: placeholders["error"] = str(err) - placeholders[ - "more_info" - ] = "https://www.home-assistant.io/more-info/no-url-available/#configuring-the-instance-url" + placeholders["more_info"] = ( + "https://www.home-assistant.io/more-info/no-url-available/#configuring-the-instance-url" + ) errors["base"] = "webhook_exception" except (ReolinkError, ReolinkException) as err: placeholders["error"] = str(err) diff --git a/homeassistant/components/romy/entity.py b/homeassistant/components/romy/entity.py index 3fa6eefbe02..ee4e209f158 100644 --- a/homeassistant/components/romy/entity.py +++ b/homeassistant/components/romy/entity.py @@ -1,6 +1,5 @@ """Base entity for ROMY.""" - from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity diff --git a/homeassistant/components/ruuvitag_ble/__init__.py b/homeassistant/components/ruuvitag_ble/__init__.py index 031bed232bd..86a0b2cd40a 100644 --- a/homeassistant/components/ruuvitag_ble/__init__.py +++ b/homeassistant/components/ruuvitag_ble/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = RuuvitagBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/sabnzbd/config_flow.py b/homeassistant/components/sabnzbd/config_flow.py index 12138a8a085..944c3f2936c 100644 --- a/homeassistant/components/sabnzbd/config_flow.py +++ b/homeassistant/components/sabnzbd/config_flow.py @@ -68,7 +68,7 @@ class SABnzbdConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_import(self, import_data): """Import sabnzbd config from configuration.yaml.""" protocol = "https://" if import_data[CONF_SSL] else "http://" - import_data[ - CONF_URL - ] = f"{protocol}{import_data[CONF_HOST]}:{import_data[CONF_PORT]}" + import_data[CONF_URL] = ( + f"{protocol}{import_data[CONF_HOST]}:{import_data[CONF_PORT]}" + ) return await self.async_step_user(import_data) diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index fbaab44ddec..4845fb4fb74 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -184,13 +184,13 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN): if self._model: updates[CONF_MODEL] = self._model if self._ssdp_rendering_control_location: - updates[ - CONF_SSDP_RENDERING_CONTROL_LOCATION - ] = self._ssdp_rendering_control_location + updates[CONF_SSDP_RENDERING_CONTROL_LOCATION] = ( + self._ssdp_rendering_control_location + ) if self._ssdp_main_tv_agent_location: - updates[ - CONF_SSDP_MAIN_TV_AGENT_LOCATION - ] = self._ssdp_main_tv_agent_location + updates[CONF_SSDP_MAIN_TV_AGENT_LOCATION] = ( + self._ssdp_main_tv_agent_location + ) self._abort_if_unique_id_configured(updates=updates, reload_on_update=False) async def _async_create_bridge(self) -> None: @@ -388,13 +388,13 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN): or update_model ): if update_ssdp_rendering_control_location: - data[ - CONF_SSDP_RENDERING_CONTROL_LOCATION - ] = self._ssdp_rendering_control_location + data[CONF_SSDP_RENDERING_CONTROL_LOCATION] = ( + self._ssdp_rendering_control_location + ) if update_ssdp_main_tv_agent_location: - data[ - CONF_SSDP_MAIN_TV_AGENT_LOCATION - ] = self._ssdp_main_tv_agent_location + data[CONF_SSDP_MAIN_TV_AGENT_LOCATION] = ( + self._ssdp_main_tv_agent_location + ) if update_mac: data[CONF_MAC] = self._mac if update_model: diff --git a/homeassistant/components/sensirion_ble/__init__.py b/homeassistant/components/sensirion_ble/__init__.py index 762086d102d..5ea5593004e 100644 --- a/homeassistant/components/sensirion_ble/__init__.py +++ b/homeassistant/components/sensirion_ble/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = SensirionBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 0b2eb911cf2..ce32be40b30 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -872,9 +872,9 @@ class SensorExtraStoredData(ExtraStoredData): def as_dict(self) -> dict[str, Any]: """Return a dict representation of the sensor data.""" - native_value: StateType | date | datetime | Decimal | dict[ - str, str - ] = self.native_value + native_value: StateType | date | datetime | Decimal | dict[str, str] = ( + self.native_value + ) if isinstance(native_value, (date, datetime)): native_value = { "__type": str(type(native_value)), diff --git a/homeassistant/components/sensorpro/__init__.py b/homeassistant/components/sensorpro/__init__.py index a4b5ae351a1..be15b65e0f9 100644 --- a/homeassistant/components/sensorpro/__init__.py +++ b/homeassistant/components/sensorpro/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = SensorProBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/sensorpush/__init__.py b/homeassistant/components/sensorpush/__init__.py index 177b51d135b..1a479caacf2 100644 --- a/homeassistant/components/sensorpush/__init__.py +++ b/homeassistant/components/sensorpush/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = SensorPushBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index edcee5c2dda..accca5f1a64 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -629,9 +629,9 @@ class ShellySleepingRpcAttributeEntity(ShellyRpcAttributeEntity): self._attr_device_info = DeviceInfo( connections={(CONNECTION_NETWORK_MAC, coordinator.mac)} ) - self._attr_unique_id = ( - self._attr_unique_id - ) = f"{coordinator.mac}-{key}-{attribute}" + self._attr_unique_id = self._attr_unique_id = ( + f"{coordinator.mac}-{key}-{attribute}" + ) self._last_value = None if coordinator.device.initialized: diff --git a/homeassistant/components/slimproto/media_player.py b/homeassistant/components/slimproto/media_player.py index f3cbcef2a61..42c50d21e75 100644 --- a/homeassistant/components/slimproto/media_player.py +++ b/homeassistant/components/slimproto/media_player.py @@ -107,9 +107,9 @@ class SlimProtoPlayer(MediaPlayerEntity): ) # PiCore + SqueezeESP32 player has web interface if "-pCP" in self.player.firmware or self.player.device_model == "SqueezeESP32": - self._attr_device_info[ - "configuration_url" - ] = f"http://{self.player.device_address}" + self._attr_device_info["configuration_url"] = ( + f"http://{self.player.device_address}" + ) self.update_attributes() async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/smtp/notify.py b/homeassistant/components/smtp/notify.py index 68be378f367..bac18576f06 100644 --- a/homeassistant/components/smtp/notify.py +++ b/homeassistant/components/smtp/notify.py @@ -287,9 +287,9 @@ def _attach_file(hass, atch_name, content_id=""): atch_name, ) attachment = MIMEApplication(file_bytes, Name=os.path.basename(atch_name)) - attachment[ - "Content-Disposition" - ] = f'attachment; filename="{os.path.basename(atch_name)}"' + attachment["Content-Disposition"] = ( + f'attachment; filename="{os.path.basename(atch_name)}"' + ) else: if content_id: attachment.add_header("Content-ID", f"<{content_id}>") diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index 7d1db21cd53..9e84d040ad1 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -150,9 +150,9 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): } if self.show_advanced_options: - data_schema[ - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL) - ] = bool + data_schema[vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL)] = ( + bool + ) return data_schema diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 6e08d6b4886..bdb647de39c 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -54,9 +54,9 @@ def get_disk_space_attr(disks: list[Diskspace]) -> dict[str, str]: free = disk.freeSpace / 1024**3 total = disk.totalSpace / 1024**3 usage = free / total * 100 - attrs[ - disk.path - ] = f"{free:.2f}/{total:.2f}{UnitOfInformation.GIGABYTES} ({usage:.2f}%)" + attrs[disk.path] = ( + f"{free:.2f}/{total:.2f}{UnitOfInformation.GIGABYTES} ({usage:.2f}%)" + ) return attrs diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py index b855c014e1d..2070d37b1a4 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -39,15 +39,13 @@ _ReturnFuncType = Callable[Concatenate[_T, _P], _R | None] @overload def soco_error( errorcodes: None = ..., -) -> Callable[[_FuncType[_T, _P, _R]], _FuncType[_T, _P, _R]]: - ... +) -> Callable[[_FuncType[_T, _P, _R]], _FuncType[_T, _P, _R]]: ... @overload def soco_error( errorcodes: list[str], -) -> Callable[[_FuncType[_T, _P, _R]], _ReturnFuncType[_T, _P, _R]]: - ... +) -> Callable[[_FuncType[_T, _P, _R]], _ReturnFuncType[_T, _P, _R]]: ... def soco_error( diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index eb3c57eba21..8d5183a459d 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -89,14 +89,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return devices.get("devices", []) - device_coordinator: DataUpdateCoordinator[ - list[dict[str, Any]] - ] = DataUpdateCoordinator( - hass, - LOGGER, - name=f"{entry.title} Devices", - update_interval=timedelta(minutes=5), - update_method=_update_devices, + device_coordinator: DataUpdateCoordinator[list[dict[str, Any]]] = ( + DataUpdateCoordinator( + hass, + LOGGER, + name=f"{entry.title} Devices", + update_interval=timedelta(minutes=5), + update_method=_update_devices, + ) ) await device_coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index f0b474064c2..007d880a263 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -168,9 +168,9 @@ async def async_setup_entry( for player in players: hass.async_create_task(_discovered_player(player)) - hass.data[DOMAIN][config_entry.entry_id][ - PLAYER_DISCOVERY_UNSUB - ] = async_call_later(hass, DISCOVERY_INTERVAL, _discovery) + hass.data[DOMAIN][config_entry.entry_id][PLAYER_DISCOVERY_UNSUB] = ( + async_call_later(hass, DISCOVERY_INTERVAL, _discovery) + ) _LOGGER.debug("Adding player discovery job for LMS server: %s", host) config_entry.async_create_background_task( diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index 75ffac772f6..b34105106e0 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -256,9 +256,9 @@ class IntegrationMatchers: def __init__(self) -> None: """Init optimized integration matching.""" - self._match_by_key: dict[ - str, dict[str, list[tuple[str, dict[str, str]]]] - ] | None = None + self._match_by_key: ( + dict[str, dict[str, list[tuple[str, dict[str, str]]]]] | None + ) = None @core_callback def async_setup( diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index a39137e9d81..d995f529b7d 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -320,9 +320,9 @@ class StatisticsSensor(SensorEntity): self.ages: deque[datetime] = deque(maxlen=self._samples_max_buffer_size) self.attributes: dict[str, StateType] = {} - self._state_characteristic_fn: Callable[ - [], StateType | datetime - ] = self._callable_characteristic_fn(self._state_characteristic) + self._state_characteristic_fn: Callable[[], StateType | datetime] = ( + self._callable_characteristic_fn(self._state_characteristic) + ) self._update_listener: CALLBACK_TYPE | None = None diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 3af581d0e1b..6dfc09891b7 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -155,9 +155,10 @@ class RecorderOutput(StreamOutput): def write_transform_matrix_and_rename(video_path: str) -> None: """Update the transform matrix and write to the desired filename.""" - with open(video_path + ".tmp", mode="rb") as in_file, open( - video_path, mode="wb" - ) as out_file: + with ( + open(video_path + ".tmp", mode="rb") as in_file, + open(video_path, mode="wb") as out_file, + ): init = transform_init( read_init(in_file), self.dynamic_stream_settings.orientation ) diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index e86179a91eb..87d9118f3a5 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -68,9 +68,9 @@ class StreamState: """Initialize StreamState.""" self._stream_id: int = 0 self.hass = hass - self._outputs_callback: Callable[ - [], Mapping[str, StreamOutput] - ] = outputs_callback + self._outputs_callback: Callable[[], Mapping[str, StreamOutput]] = ( + outputs_callback + ) # sequence gets incremented before the first segment so the first segment # has a sequence number of 0. self._sequence = -1 diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 5f89c28808d..7060339250c 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -124,28 +124,28 @@ class SuezSensor(SensorEntity): self._attr_extra_state_attributes["this_month_consumption"] = {} for item in self.client.attributes["thisMonthConsumption"]: - self._attr_extra_state_attributes["this_month_consumption"][ - item - ] = self.client.attributes["thisMonthConsumption"][item] + self._attr_extra_state_attributes["this_month_consumption"][item] = ( + self.client.attributes["thisMonthConsumption"][item] + ) self._attr_extra_state_attributes["previous_month_consumption"] = {} for item in self.client.attributes["previousMonthConsumption"]: self._attr_extra_state_attributes["previous_month_consumption"][ item ] = self.client.attributes["previousMonthConsumption"][item] - self._attr_extra_state_attributes[ - "highest_monthly_consumption" - ] = self.client.attributes["highestMonthlyConsumption"] - self._attr_extra_state_attributes[ - "last_year_overall" - ] = self.client.attributes["lastYearOverAll"] - self._attr_extra_state_attributes[ - "this_year_overall" - ] = self.client.attributes["thisYearOverAll"] + self._attr_extra_state_attributes["highest_monthly_consumption"] = ( + self.client.attributes["highestMonthlyConsumption"] + ) + self._attr_extra_state_attributes["last_year_overall"] = ( + self.client.attributes["lastYearOverAll"] + ) + self._attr_extra_state_attributes["this_year_overall"] = ( + self.client.attributes["thisYearOverAll"] + ) self._attr_extra_state_attributes["history"] = {} for item in self.client.attributes["history"]: - self._attr_extra_state_attributes["history"][ - item - ] = self.client.attributes["history"][item] + self._attr_extra_state_attributes["history"][item] = ( + self.client.attributes["history"][item] + ) except PySuezError: self._attr_available = False diff --git a/homeassistant/components/switcher_kis/__init__.py b/homeassistant/components/switcher_kis/__init__.py index 14aeea09626..b3315bac2ca 100644 --- a/homeassistant/components/switcher_kis/__init__.py +++ b/homeassistant/components/switcher_kis/__init__.py @@ -98,9 +98,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device.device_type.hex_rep, ) - coordinator = hass.data[DOMAIN][DATA_DEVICE][ - device.device_id - ] = SwitcherDataUpdateCoordinator(hass, entry, device) + coordinator = hass.data[DOMAIN][DATA_DEVICE][device.device_id] = ( + SwitcherDataUpdateCoordinator(hass, entry, device) + ) coordinator.async_setup() # Must be ready before dispatcher is called diff --git a/homeassistant/components/system_bridge/media_player.py b/homeassistant/components/system_bridge/media_player.py index d467110e06d..aeff3b22fb2 100644 --- a/homeassistant/components/system_bridge/media_player.py +++ b/homeassistant/components/system_bridge/media_player.py @@ -53,13 +53,13 @@ MEDIA_SET_REPEAT_MAP: Final[dict[RepeatMode, int]] = { RepeatMode.ALL: 2, } -MEDIA_PLAYER_DESCRIPTION: Final[ - MediaPlayerEntityDescription -] = MediaPlayerEntityDescription( - key="media", - translation_key="media", - icon="mdi:volume-high", - device_class=MediaPlayerDeviceClass.RECEIVER, +MEDIA_PLAYER_DESCRIPTION: Final[MediaPlayerEntityDescription] = ( + MediaPlayerEntityDescription( + key="media", + translation_key="media", + icon="mdi:volume-high", + device_class=MediaPlayerDeviceClass.RECEIVER, + ) ) diff --git a/homeassistant/components/systemmonitor/coordinator.py b/homeassistant/components/systemmonitor/coordinator.py index 6ce2836fcde..d12eddbb14a 100644 --- a/homeassistant/components/systemmonitor/coordinator.py +++ b/homeassistant/components/systemmonitor/coordinator.py @@ -87,9 +87,9 @@ class SystemMonitorCoordinator(TimestampDataUpdateCoordinator[SensorData]): self.boot_time: datetime | None = None self._initial_update: bool = True - self.update_subscribers: dict[ - tuple[str, str], set[str] - ] = self.set_subscribers_tuples(arguments) + self.update_subscribers: dict[tuple[str, str], set[str]] = ( + self.set_subscribers_tuples(arguments) + ) def set_subscribers_tuples( self, arguments: list[str] diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 2bd1bc31bb0..621f5a1ad61 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -485,12 +485,12 @@ class TadoClimate(TadoZoneEntity, ClimateEntity): def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return temperature offset.""" state_attr: dict[str, Any] = self._tado_zone_temp_offset - state_attr[ - HA_TERMINATION_TYPE - ] = self._tado_zone_data.default_overlay_termination_type - state_attr[ - HA_TERMINATION_DURATION - ] = self._tado_zone_data.default_overlay_termination_duration + state_attr[HA_TERMINATION_TYPE] = ( + self._tado_zone_data.default_overlay_termination_type + ) + state_attr[HA_TERMINATION_DURATION] = ( + self._tado_zone_data.default_overlay_termination_duration + ) return state_attr def set_swing_mode(self, swing_mode: str) -> None: diff --git a/homeassistant/components/tasmota/binary_sensor.py b/homeassistant/components/tasmota/binary_sensor.py index 450cd833fcf..071cce81880 100644 --- a/homeassistant/components/tasmota/binary_sensor.py +++ b/homeassistant/components/tasmota/binary_sensor.py @@ -43,12 +43,12 @@ async def async_setup_entry( ] ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(binary_sensor.DOMAIN) - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(binary_sensor.DOMAIN), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(binary_sensor.DOMAIN)] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(binary_sensor.DOMAIN), + async_discover, + ) ) diff --git a/homeassistant/components/tasmota/cover.py b/homeassistant/components/tasmota/cover.py index 48ac868d64b..4ab9464e9f9 100644 --- a/homeassistant/components/tasmota/cover.py +++ b/homeassistant/components/tasmota/cover.py @@ -41,12 +41,12 @@ async def async_setup_entry( [TasmotaCover(tasmota_entity=tasmota_entity, discovery_hash=discovery_hash)] ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(COVER_DOMAIN) - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(COVER_DOMAIN), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(COVER_DOMAIN)] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(COVER_DOMAIN), + async_discover, + ) ) diff --git a/homeassistant/components/tasmota/device_automation.py b/homeassistant/components/tasmota/device_automation.py index 2fdcad45c81..ef05585dd87 100644 --- a/homeassistant/components/tasmota/device_automation.py +++ b/homeassistant/components/tasmota/device_automation.py @@ -43,12 +43,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> N hass, tasmota_automation, config_entry, discovery_hash ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation") - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format("device_automation"), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format("device_automation")] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format("device_automation"), + async_discover, + ) ) hass.data[DATA_UNSUB].append( hass.bus.async_listen( diff --git a/homeassistant/components/tasmota/fan.py b/homeassistant/components/tasmota/fan.py index 22502c474b1..cdb0fb8d2f6 100644 --- a/homeassistant/components/tasmota/fan.py +++ b/homeassistant/components/tasmota/fan.py @@ -49,12 +49,12 @@ async def async_setup_entry( [TasmotaFan(tasmota_entity=tasmota_entity, discovery_hash=discovery_hash)] ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(FAN_DOMAIN) - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(FAN_DOMAIN), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(FAN_DOMAIN)] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(FAN_DOMAIN), + async_discover, + ) ) diff --git a/homeassistant/components/tasmota/light.py b/homeassistant/components/tasmota/light.py index 76859311c6d..5effc9c4997 100644 --- a/homeassistant/components/tasmota/light.py +++ b/homeassistant/components/tasmota/light.py @@ -57,12 +57,12 @@ async def async_setup_entry( [TasmotaLight(tasmota_entity=tasmota_entity, discovery_hash=discovery_hash)] ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(light.DOMAIN) - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(light.DOMAIN), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(light.DOMAIN)] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(light.DOMAIN), + async_discover, + ) ) diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index d919cb73cf5..546e3eb4539 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -252,12 +252,12 @@ async def async_setup_entry( ] ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(sensor.DOMAIN) - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(sensor.DOMAIN), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(sensor.DOMAIN)] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(sensor.DOMAIN), + async_discover, + ) ) diff --git a/homeassistant/components/tasmota/switch.py b/homeassistant/components/tasmota/switch.py index 0a80992b1d8..44c45621e09 100644 --- a/homeassistant/components/tasmota/switch.py +++ b/homeassistant/components/tasmota/switch.py @@ -38,12 +38,12 @@ async def async_setup_entry( ] ) - hass.data[ - DATA_REMOVE_DISCOVER_COMPONENT.format(switch.DOMAIN) - ] = async_dispatcher_connect( - hass, - TASMOTA_DISCOVERY_ENTITY_NEW.format(switch.DOMAIN), - async_discover, + hass.data[DATA_REMOVE_DISCOVER_COMPONENT.format(switch.DOMAIN)] = ( + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(switch.DOMAIN), + async_discover, + ) ) diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index a851e547844..735fa7ddd23 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -271,15 +271,18 @@ class TemplateEntity(Entity): self._attr_extra_state_attributes = {} self._self_ref_update_count = 0 self._attr_unique_id = unique_id - self._preview_callback: Callable[ - [ - str | None, - dict[str, Any] | None, - dict[str, bool | set[str]] | None, - str | None, - ], - None, - ] | None = None + self._preview_callback: ( + Callable[ + [ + str | None, + dict[str, Any] | None, + dict[str, bool | set[str]] | None, + str | None, + ], + None, + ] + | None + ) = None if config is None: self._attribute_templates = attribute_templates self._availability_template = availability_template diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index b572f0ecbb8..6f6861830c7 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -574,12 +574,12 @@ class TriggerWeatherEntity(TriggerEntity, WeatherEntity, RestoreEntity): and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) and (weather_data := await self.async_get_last_weather_data()) ): - self._rendered[ - CONF_APPARENT_TEMPERATURE_TEMPLATE - ] = weather_data.last_apparent_temperature - self._rendered[ - CONF_CLOUD_COVERAGE_TEMPLATE - ] = weather_data.last_cloud_coverage + self._rendered[CONF_APPARENT_TEMPERATURE_TEMPLATE] = ( + weather_data.last_apparent_temperature + ) + self._rendered[CONF_CLOUD_COVERAGE_TEMPLATE] = ( + weather_data.last_cloud_coverage + ) self._rendered[CONF_CONDITION_TEMPLATE] = state.state self._rendered[CONF_DEW_POINT_TEMPLATE] = weather_data.last_dew_point self._rendered[CONF_HUMIDITY_TEMPLATE] = weather_data.last_humidity @@ -588,9 +588,9 @@ class TriggerWeatherEntity(TriggerEntity, WeatherEntity, RestoreEntity): self._rendered[CONF_TEMPERATURE_TEMPLATE] = weather_data.last_temperature self._rendered[CONF_VISIBILITY_TEMPLATE] = weather_data.last_visibility self._rendered[CONF_WIND_BEARING_TEMPLATE] = weather_data.last_wind_bearing - self._rendered[ - CONF_WIND_GUST_SPEED_TEMPLATE - ] = weather_data.last_wind_gust_speed + self._rendered[CONF_WIND_GUST_SPEED_TEMPLATE] = ( + weather_data.last_wind_gust_speed + ) self._rendered[CONF_WIND_SPEED_TEMPLATE] = weather_data.last_wind_speed @property diff --git a/homeassistant/components/thermobeacon/__init__.py b/homeassistant/components/thermobeacon/__init__.py index 853d2b3dc65..073ff6bbdc3 100644 --- a/homeassistant/components/thermobeacon/__init__.py +++ b/homeassistant/components/thermobeacon/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = ThermoBeaconBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/thermopro/__init__.py b/homeassistant/components/thermopro/__init__.py index 51bc4123870..2cd207818c5 100644 --- a/homeassistant/components/thermopro/__init__.py +++ b/homeassistant/components/thermopro/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = ThermoProBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/thermoworks_smoke/sensor.py b/homeassistant/components/thermoworks_smoke/sensor.py index b2d6613f2e5..57621ba1055 100644 --- a/homeassistant/components/thermoworks_smoke/sensor.py +++ b/homeassistant/components/thermoworks_smoke/sensor.py @@ -159,9 +159,9 @@ class ThermoworksSmokeSensor(SensorEntity): if key and key not in EXCLUDE_KEYS: self._attr_extra_state_attributes[key] = val # store actual unit because attributes are not converted - self._attr_extra_state_attributes[ - "unit_of_min_max" - ] = self._attr_native_unit_of_measurement + self._attr_extra_state_attributes["unit_of_min_max"] = ( + self._attr_native_unit_of_measurement + ) except (RequestException, ValueError, KeyError): _LOGGER.warning("Could not update status for %s", self.name) diff --git a/homeassistant/components/tilt_ble/__init__.py b/homeassistant/components/tilt_ble/__init__.py index 7770eb3cd20..9ac2cb91aff 100644 --- a/homeassistant/components/tilt_ble/__init__.py +++ b/homeassistant/components/tilt_ble/__init__.py @@ -26,14 +26,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: address = entry.unique_id assert address is not None data = TiltBluetoothDeviceData() - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.ACTIVE, - update_method=data.update, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + PassiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.ACTIVE, + update_method=data.update, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/tolo/const.py b/homeassistant/components/tolo/const.py index e4f30eda6d3..40940c4283c 100644 --- a/homeassistant/components/tolo/const.py +++ b/homeassistant/components/tolo/const.py @@ -1,4 +1,5 @@ """Constants for the tolo integration.""" + from enum import Enum from tololib import AromaTherapySlot as ToloAromaTherapySlot, LampMode as ToloLampMode diff --git a/homeassistant/components/traccar/device_tracker.py b/homeassistant/components/traccar/device_tracker.py index 93ee63df397..d82b922f586 100644 --- a/homeassistant/components/traccar/device_tracker.py +++ b/homeassistant/components/traccar/device_tracker.py @@ -144,9 +144,9 @@ async def async_setup_entry( [TraccarEntity(device, latitude, longitude, battery, accuracy, attrs)] ) - hass.data[DOMAIN]["unsub_device_tracker"][ - entry.entry_id - ] = async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + hass.data[DOMAIN]["unsub_device_tracker"][entry.entry_id] = ( + async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data) + ) # Restore previously loaded devices dev_reg = dr.async_get(hass) diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index 409e0b2e763..8ff7041fd5e 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -166,8 +166,7 @@ class TuyaEntity(Entity): *, prefer_function: bool = False, dptype: Literal[DPType.ENUM], - ) -> EnumTypeData | None: - ... + ) -> EnumTypeData | None: ... @overload def find_dpcode( @@ -176,8 +175,7 @@ class TuyaEntity(Entity): *, prefer_function: bool = False, dptype: Literal[DPType.INTEGER], - ) -> IntegerTypeData | None: - ... + ) -> IntegerTypeData | None: ... @overload def find_dpcode( @@ -185,8 +183,7 @@ class TuyaEntity(Entity): dpcodes: str | DPCode | tuple[DPCode, ...] | None, *, prefer_function: bool = False, - ) -> DPCode | None: - ... + ) -> DPCode | None: ... def find_dpcode( self, diff --git a/homeassistant/components/twentemilieu/__init__.py b/homeassistant/components/twentemilieu/__init__.py index 293bab54e16..d9881b0b2c8 100644 --- a/homeassistant/components/twentemilieu/__init__.py +++ b/homeassistant/components/twentemilieu/__init__.py @@ -34,14 +34,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: session=session, ) - coordinator: DataUpdateCoordinator[ - dict[WasteType, list[date]] - ] = DataUpdateCoordinator( - hass, - LOGGER, - name=DOMAIN, - update_interval=SCAN_INTERVAL, - update_method=twentemilieu.update, + coordinator: DataUpdateCoordinator[dict[WasteType, list[date]]] = ( + DataUpdateCoordinator( + hass, + LOGGER, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + update_method=twentemilieu.update, + ) ) await coordinator.async_config_entry_first_refresh() diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 435d36d8feb..79b5e035f41 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -237,9 +237,9 @@ class UnifiFlowHandler(ConfigFlow, domain=UNIFI_DOMAIN): if (port := MODEL_PORTS.get(model_description)) is not None: self.config[CONF_PORT] = port - self.context[ - "configuration_url" - ] = f"https://{self.config[CONF_HOST]}:{port}" + self.context["configuration_url"] = ( + f"https://{self.config[CONF_HOST]}:{port}" + ) return await self.async_step_user() @@ -279,9 +279,9 @@ class UnifiOptionsFlowHandler(OptionsFlow): clients_to_block = {} for client in self.hub.api.clients.values(): - clients_to_block[ - client.mac - ] = f"{client.name or client.hostname} ({client.mac})" + clients_to_block[client.mac] = ( + f"{client.name or client.hostname} ({client.mac})" + ) return self.async_show_form( step_id="simple_options", @@ -410,9 +410,9 @@ class UnifiOptionsFlowHandler(OptionsFlow): clients_to_block = {} for client in self.hub.api.clients.values(): - clients_to_block[ - client.mac - ] = f"{client.name or client.hostname} ({client.mac})" + clients_to_block[client.mac] = ( + f"{client.name or client.hostname} ({client.mac})" + ) selected_clients_to_block = [ client diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 8366f2b7f04..932cc75b9d0 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -195,9 +195,9 @@ class ProtectDeviceEntity(Entity): super().__init__() self.data: ProtectData = data self.device = device - self._async_get_ufp_enabled: Callable[ - [ProtectAdoptableDeviceModel], bool - ] | None = None + self._async_get_ufp_enabled: ( + Callable[[ProtectAdoptableDeviceModel], bool] | None + ) = None if description is None: self._attr_unique_id = f"{self.device.mac}" diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 04d8b3414bd..4dff753ac6a 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -86,9 +86,9 @@ class Device: """Initialize UPnP/IGD device.""" self.hass = hass self._igd_device = igd_device - self.coordinator: DataUpdateCoordinator[ - dict[str, str | datetime | int | float | None] - ] | None = None + self.coordinator: ( + DataUpdateCoordinator[dict[str, str | datetime | int | float | None]] | None + ) = None self.original_udn: str | None = None async def async_get_mac_address(self) -> str | None: diff --git a/homeassistant/components/utility_meter/__init__.py b/homeassistant/components/utility_meter/__init__.py index aeed9885872..71df488de7e 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -164,9 +164,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) ) - hass.data[DATA_UTILITY][meter][ - CONF_TARIFF_ENTITY - ] = f"{SELECT_DOMAIN}.{meter}" + hass.data[DATA_UTILITY][meter][CONF_TARIFF_ENTITY] = ( + f"{SELECT_DOMAIN}.{meter}" + ) # add one meter for each tariff tariff_confs = {} @@ -213,9 +213,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entity_entry = entity_registry.async_get_or_create( Platform.SELECT, DOMAIN, entry.entry_id, suggested_object_id=entry.title ) - hass.data[DATA_UTILITY][entry.entry_id][ - CONF_TARIFF_ENTITY - ] = entity_entry.entity_id + hass.data[DATA_UTILITY][entry.entry_id][CONF_TARIFF_ENTITY] = ( + entity_entry.entity_id + ) await hass.config_entries.async_forward_entry_setups( entry, (Platform.SELECT, Platform.SENSOR) ) diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index b7dd06ac3a5..e0238097e01 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -90,9 +90,9 @@ class VerisureSmartplug(CoordinatorEntity[VerisureDataUpdateCoordinator], Switch async def async_set_plug_state(self, state: bool) -> None: """Set smartplug state.""" - command: dict[ - str, str | dict[str, str] - ] = self.coordinator.verisure.set_smartplug(self.serial_number, state) + command: dict[str, str | dict[str, str]] = ( + self.coordinator.verisure.set_smartplug(self.serial_number, state) + ) await self.hass.async_add_executor_job( self.coordinator.verisure.request, command, diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 4b231443350..490048190fa 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -199,14 +199,14 @@ class ViCareClimate(ViCareEntity, ClimateEntity): } with suppress(PyViCareNotSupportedFeatureError): - self._attributes[ - "heating_curve_slope" - ] = self._circuit.getHeatingCurveSlope() + self._attributes["heating_curve_slope"] = ( + self._circuit.getHeatingCurveSlope() + ) with suppress(PyViCareNotSupportedFeatureError): - self._attributes[ - "heating_curve_shift" - ] = self._circuit.getHeatingCurveShift() + self._attributes["heating_curve_shift"] = ( + self._circuit.getHeatingCurveShift() + ) with suppress(PyViCareNotSupportedFeatureError): self._attributes["vicare_modes"] = self._circuit.getModes() diff --git a/homeassistant/components/vulcan/config_flow.py b/homeassistant/components/vulcan/config_flow.py index 15749a7bf3d..ae44c507c6a 100644 --- a/homeassistant/components/vulcan/config_flow.py +++ b/homeassistant/components/vulcan/config_flow.py @@ -111,9 +111,9 @@ class VulcanFlowHandler(ConfigFlow, domain=DOMAIN): students = {} if self.students is not None: for student in self.students: - students[ - str(student.pupil.id) - ] = f"{student.pupil.first_name} {student.pupil.last_name}" + students[str(student.pupil.id)] = ( + f"{student.pupil.first_name} {student.pupil.last_name}" + ) if user_input is not None: student_id = user_input["student"] await self.async_set_unique_id(str(student_id)) diff --git a/homeassistant/components/vulcan/fetch_data.py b/homeassistant/components/vulcan/fetch_data.py index c706bfa805f..cd82346d5b7 100644 --- a/homeassistant/components/vulcan/fetch_data.py +++ b/homeassistant/components/vulcan/fetch_data.py @@ -87,9 +87,9 @@ async def get_student_info(client, student_id): if student.pupil.second_name: student_info["second_name"] = student.pupil.second_name student_info["last_name"] = student.pupil.last_name - student_info[ - "full_name" - ] = f"{student.pupil.first_name} {student.pupil.last_name}" + student_info["full_name"] = ( + f"{student.pupil.first_name} {student.pupil.last_name}" + ) student_info["id"] = student.pupil.id student_info["class"] = student.class_ student_info["school"] = student.school.name diff --git a/homeassistant/components/wallbox/coordinator.py b/homeassistant/components/wallbox/coordinator.py index a33d90db700..4725e92ca84 100644 --- a/homeassistant/components/wallbox/coordinator.py +++ b/homeassistant/components/wallbox/coordinator.py @@ -133,9 +133,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): data[CHARGER_ENERGY_PRICE_KEY] = data[CHARGER_DATA_KEY][ CHARGER_ENERGY_PRICE_KEY ] - data[ - CHARGER_CURRENCY_KEY - ] = f"{data[CHARGER_DATA_KEY][CHARGER_CURRENCY_KEY][CODE_KEY]}/kWh" + data[CHARGER_CURRENCY_KEY] = ( + f"{data[CHARGER_DATA_KEY][CHARGER_CURRENCY_KEY][CODE_KEY]}/kWh" + ) data[CHARGER_STATUS_DESCRIPTION_KEY] = CHARGER_STATUS.get( data[CHARGER_STATUS_ID_KEY], ChargerStatus.UNKNOWN diff --git a/homeassistant/components/websocket_api/auth.py b/homeassistant/components/websocket_api/auth.py index 5093ef187c5..a15f76632c1 100644 --- a/homeassistant/components/websocket_api/auth.py +++ b/homeassistant/components/websocket_api/auth.py @@ -90,10 +90,10 @@ class AuthPhase: refresh_token.user, refresh_token, ) - conn.subscriptions[ - "auth" - ] = self._hass.auth.async_register_revoke_token_callback( - refresh_token.id, self._cancel_ws + conn.subscriptions["auth"] = ( + self._hass.auth.async_register_revoke_token_callback( + refresh_token.id, self._cancel_ws + ) ) await self._send_bytes_text(AUTH_OK_MESSAGE) self._logger.debug("Auth OK") diff --git a/homeassistant/components/xiaomi_ble/__init__.py b/homeassistant/components/xiaomi_ble/__init__.py index 1402a15a56d..19c1f3feea1 100644 --- a/homeassistant/components/xiaomi_ble/__init__.py +++ b/homeassistant/components/xiaomi_ble/__init__.py @@ -168,25 +168,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return await data.async_poll(connectable_device) device_registry = async_get(hass) - coordinator = hass.data.setdefault(DOMAIN, {})[ - entry.entry_id - ] = XiaomiActiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, - mode=BluetoothScanningMode.PASSIVE, - update_method=lambda service_info: process_service_info( - hass, entry, data, service_info, device_registry - ), - needs_poll_method=_needs_poll, - device_data=data, - discovered_event_classes=set(entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, [])), - poll_method=_async_poll, - # We will take advertisements from non-connectable devices - # since we will trade the BLEDevice for a connectable one - # if we need to poll it - connectable=False, - entry=entry, + coordinator = hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ( + XiaomiActiveBluetoothProcessorCoordinator( + hass, + _LOGGER, + address=address, + mode=BluetoothScanningMode.PASSIVE, + update_method=lambda service_info: process_service_info( + hass, entry, data, service_info, device_registry + ), + needs_poll_method=_needs_poll, + device_data=data, + discovered_event_classes=set( + entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, []) + ), + poll_method=_async_poll, + # We will take advertisements from non-connectable devices + # since we will trade the BLEDevice for a connectable one + # if we need to poll it + connectable=False, + entry=entry, + ) ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/zha/core/cluster_handlers/__init__.py b/homeassistant/components/zha/core/cluster_handlers/__init__.py index ff880ed3b69..ae7b0945230 100644 --- a/homeassistant/components/zha/core/cluster_handlers/__init__.py +++ b/homeassistant/components/zha/core/cluster_handlers/__init__.py @@ -335,9 +335,9 @@ class ClusterHandler(LogMixin): return for record in res: - event_data[self.cluster.find_attribute(record.attrid).name][ - "status" - ] = record.status.name + event_data[self.cluster.find_attribute(record.attrid).name]["status"] = ( + record.status.name + ) failed = [ self.cluster.find_attribute(record.attrid).name for record in res diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 98d37f1a7db..75e5b51e599 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -293,9 +293,9 @@ class ProbeEndpoint: not in cluster_handler.ZCL_INIT_ATTRS ): init_attrs = cluster_handler.ZCL_INIT_ATTRS.copy() - init_attrs[ - quirk_metadata.entity_metadata.attribute_name - ] = quirk_metadata.attribute_initialized_from_cache + init_attrs[quirk_metadata.entity_metadata.attribute_name] = ( + quirk_metadata.attribute_initialized_from_cache + ) cluster_handler.__dict__[zha_const.ZCL_INIT_ATTRS] = init_attrs endpoint.async_new_entity( diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 6706aef91f3..83aa12fbfa1 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -133,9 +133,9 @@ class ZHAGateway: self._groups: dict[int, ZHAGroup] = {} self.application_controller: ControllerApplication = None self.coordinator_zha_device: ZHADevice = None # type: ignore[assignment] - self._device_registry: collections.defaultdict[ - EUI64, list[EntityReference] - ] = collections.defaultdict(list) + self._device_registry: collections.defaultdict[EUI64, list[EntityReference]] = ( + collections.defaultdict(list) + ) self._log_levels: dict[str, dict[str, int]] = { DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(), DEBUG_LEVEL_CURRENT: async_capture_log_levels(), diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 10e63dd3590..b9110a8dcde 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -108,12 +108,12 @@ DEVICE_CLASS = { DEVICE_CLASS = collections.defaultdict(dict, DEVICE_CLASS) CLUSTER_HANDLER_ONLY_CLUSTERS = SetRegistry() -CLIENT_CLUSTER_HANDLER_REGISTRY: DictRegistry[ - type[ClientClusterHandler] -] = DictRegistry() -ZIGBEE_CLUSTER_HANDLER_REGISTRY: NestedDictRegistry[ - type[ClusterHandler] -] = NestedDictRegistry() +CLIENT_CLUSTER_HANDLER_REGISTRY: DictRegistry[type[ClientClusterHandler]] = ( + DictRegistry() +) +ZIGBEE_CLUSTER_HANDLER_REGISTRY: NestedDictRegistry[type[ClusterHandler]] = ( + NestedDictRegistry() +) WEIGHT_ATTR = attrgetter("weight") @@ -278,9 +278,9 @@ class ZHAEntityRegistry: def __init__(self) -> None: """Initialize Registry instance.""" - self._strict_registry: dict[ - Platform, dict[MatchRule, type[ZhaEntity]] - ] = collections.defaultdict(dict) + self._strict_registry: dict[Platform, dict[MatchRule, type[ZhaEntity]]] = ( + collections.defaultdict(dict) + ) self._multi_entity_registry: dict[ Platform, dict[int | str | None, dict[MatchRule, list[type[ZhaEntity]]]] ] = collections.defaultdict( @@ -292,9 +292,9 @@ class ZHAEntityRegistry: lambda: collections.defaultdict(lambda: collections.defaultdict(list)) ) self._group_registry: dict[str, type[ZhaGroupEntity]] = {} - self.single_device_matches: dict[ - Platform, dict[EUI64, list[str]] - ] = collections.defaultdict(lambda: collections.defaultdict(list)) + self.single_device_matches: dict[Platform, dict[EUI64, list[str]]] = ( + collections.defaultdict(lambda: collections.defaultdict(list)) + ) def get_entity( self, @@ -324,9 +324,9 @@ class ZHAEntityRegistry: dict[Platform, list[EntityClassAndClusterHandlers]], list[ClusterHandler] ]: """Match ZHA cluster handlers to potentially multiple ZHA Entity classes.""" - result: dict[ - Platform, list[EntityClassAndClusterHandlers] - ] = collections.defaultdict(list) + result: dict[Platform, list[EntityClassAndClusterHandlers]] = ( + collections.defaultdict(list) + ) all_claimed: set[ClusterHandler] = set() for component, stop_match_groups in self._multi_entity_registry.items(): for stop_match_grp, matches in stop_match_groups.items(): @@ -357,9 +357,9 @@ class ZHAEntityRegistry: dict[Platform, list[EntityClassAndClusterHandlers]], list[ClusterHandler] ]: """Match ZHA cluster handlers to potentially multiple ZHA Entity classes.""" - result: dict[ - Platform, list[EntityClassAndClusterHandlers] - ] = collections.defaultdict(list) + result: dict[Platform, list[EntityClassAndClusterHandlers]] = ( + collections.defaultdict(list) + ) all_claimed: set[ClusterHandler] = set() for ( component, diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 0fdf7a77895..52c7804bb8f 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -186,9 +186,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Create a task to allow the config entry to be unloaded before the driver is ready. # Unloading the config entry is needed if the client listen task errors. start_client_task = hass.async_create_task(start_client(hass, entry, client)) - hass.data[DOMAIN].setdefault(entry.entry_id, {})[ - DATA_START_CLIENT_TASK - ] = start_client_task + hass.data[DOMAIN].setdefault(entry.entry_id, {})[DATA_START_CLIENT_TASK] = ( + start_client_task + ) return True diff --git a/homeassistant/components/zwave_js/binary_sensor.py b/homeassistant/components/zwave_js/binary_sensor.py index 8137623379a..79181e818a2 100644 --- a/homeassistant/components/zwave_js/binary_sensor.py +++ b/homeassistant/components/zwave_js/binary_sensor.py @@ -270,7 +270,9 @@ async def async_setup_entry( if state_key == "0": continue - notification_description: NotificationZWaveJSEntityDescription | None = None + notification_description: ( + NotificationZWaveJSEntityDescription | None + ) = None for description in NOTIFICATION_SENSOR_MAPPINGS: if ( diff --git a/homeassistant/components/zwave_js/triggers/event.py b/homeassistant/components/zwave_js/triggers/event.py index 28717370d20..6cf4a31c0eb 100644 --- a/homeassistant/components/zwave_js/triggers/event.py +++ b/homeassistant/components/zwave_js/triggers/event.py @@ -197,9 +197,9 @@ async def async_attach_trigger( else: payload["description"] = primary_desc - payload[ - "description" - ] = f"{payload['description']} with event data: {event_data}" + payload["description"] = ( + f"{payload['description']} with event data: {event_data}" + ) hass.async_run_hass_job(job, {"trigger": payload}) diff --git a/homeassistant/config.py b/homeassistant/config.py index d2390c6c4ff..c570e36c6c1 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -1112,9 +1112,9 @@ async def merge_packages_config( continue try: - config_platform: ModuleType | None = ( - await integration.async_get_platform("config") - ) + config_platform: ( + ModuleType | None + ) = await integration.async_get_platform("config") # Test if config platform has a config validator if not hasattr(config_platform, "async_validate_config"): config_platform = None diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 9ea049c4616..15de62179cb 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -372,9 +372,9 @@ class ConfigEntry: self._async_cancel_retry_setup: Callable[[], Any] | None = None # Hold list for actions to call on unload. - self._on_unload: list[ - Callable[[], Coroutine[Any, Any, None] | None] - ] | None = None + self._on_unload: list[Callable[[], Coroutine[Any, Any, None] | None]] | None = ( + None + ) # Reload lock to prevent conflicting reloads self.reload_lock = asyncio.Lock() @@ -1138,9 +1138,9 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): loop = self.hass.loop if context["source"] == SOURCE_IMPORT: - self._pending_import_flows.setdefault(handler, {})[ - flow_id - ] = loop.create_future() + self._pending_import_flows.setdefault(handler, {})[flow_id] = ( + loop.create_future() + ) cancel_init_future = loop.create_future() self._initialize_futures.setdefault(handler, []).append(cancel_init_future) diff --git a/homeassistant/core.py b/homeassistant/core.py index 561cb45d39f..7d229e3727f 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -563,8 +563,7 @@ class HomeAssistant: target: Callable[[*_Ts], Coroutine[Any, Any, _R]], *args: *_Ts, eager_start: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @overload @callback @@ -573,8 +572,7 @@ class HomeAssistant: target: Callable[[*_Ts], Coroutine[Any, Any, _R] | _R], *args: *_Ts, eager_start: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @overload @callback @@ -583,8 +581,7 @@ class HomeAssistant: target: Coroutine[Any, Any, _R], *args: Any, eager_start: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @callback def async_add_job( @@ -637,8 +634,7 @@ class HomeAssistant: *args: Any, eager_start: bool = False, background: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @overload @callback @@ -648,8 +644,7 @@ class HomeAssistant: *args: Any, eager_start: bool = False, background: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @callback def async_add_hass_job( @@ -801,8 +796,7 @@ class HomeAssistant: hassjob: HassJob[..., Coroutine[Any, Any, _R]], *args: Any, background: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @overload @callback @@ -811,8 +805,7 @@ class HomeAssistant: hassjob: HassJob[..., Coroutine[Any, Any, _R] | _R], *args: Any, background: bool = False, - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @callback def async_run_hass_job( @@ -848,22 +841,19 @@ class HomeAssistant: @callback def async_run_job( self, target: Callable[[*_Ts], Coroutine[Any, Any, _R]], *args: *_Ts - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @overload @callback def async_run_job( self, target: Callable[[*_Ts], Coroutine[Any, Any, _R] | _R], *args: *_Ts - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @overload @callback def async_run_job( self, target: Coroutine[Any, Any, _R], *args: Any - ) -> asyncio.Future[_R] | None: - ... + ) -> asyncio.Future[_R] | None: ... @callback def async_run_job( @@ -956,15 +946,13 @@ class HomeAssistant: @callback def async_add_shutdown_job( self, hassjob: HassJob[..., Coroutine[Any, Any, Any]], *args: Any - ) -> CALLBACK_TYPE: - ... + ) -> CALLBACK_TYPE: ... @overload @callback def async_add_shutdown_job( self, hassjob: HassJob[..., Coroutine[Any, Any, Any] | Any], *args: Any - ) -> CALLBACK_TYPE: - ... + ) -> CALLBACK_TYPE: ... @callback def async_add_shutdown_job( @@ -1788,9 +1776,9 @@ class State: COMPRESSED_STATE_LAST_CHANGED: self.last_changed_timestamp, } if self.last_changed != self.last_updated: - compressed_state[ - COMPRESSED_STATE_LAST_UPDATED - ] = self.last_updated_timestamp + compressed_state[COMPRESSED_STATE_LAST_UPDATED] = ( + self.last_updated_timestamp + ) return compressed_state @cached_property diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index f681764b4af..bdf4d8c060b 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -72,9 +72,9 @@ class HomeAssistantError(Exception): assert self.translation_domain is not None if "async_get_exception_message" not in _function_cache: - _function_cache[ - "async_get_exception_message" - ] = import_async_get_exception_message() + _function_cache["async_get_exception_message"] = ( + import_async_get_exception_message() + ) self._message = _function_cache["async_get_exception_message"]( self.translation_domain, self.translation_key, self.translation_placeholders diff --git a/homeassistant/helpers/config_entry_oauth2_flow.py b/homeassistant/helpers/config_entry_oauth2_flow.py index 83e76ca4ebe..caf47432623 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -439,9 +439,9 @@ def async_add_implementation_provider( If no implementation found, return None. """ - hass.data.setdefault(DATA_PROVIDERS, {})[ - provider_domain - ] = async_provide_implementation + hass.data.setdefault(DATA_PROVIDERS, {})[provider_domain] = ( + async_provide_implementation + ) class OAuth2AuthorizeCallbackView(http.HomeAssistantView): diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 3f5af582424..6dd5d7f2bdb 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -284,18 +284,15 @@ def isdir(value: Any) -> str: @overload -def ensure_list(value: None) -> list[Any]: - ... +def ensure_list(value: None) -> list[Any]: ... @overload -def ensure_list(value: list[_T]) -> list[_T]: - ... +def ensure_list(value: list[_T]) -> list[_T]: ... @overload -def ensure_list(value: list[_T] | _T) -> list[_T]: - ... +def ensure_list(value: list[_T] | _T) -> list[_T]: ... def ensure_list(value: _T | None) -> list[_T] | list[Any]: diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index cb812ef071b..22d9c3bbab8 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -53,16 +53,14 @@ _DispatcherDataType = dict[ @bind_hass def dispatcher_connect( hass: HomeAssistant, signal: SignalType[*_Ts], target: Callable[[*_Ts], None] -) -> Callable[[], None]: - ... +) -> Callable[[], None]: ... @overload @bind_hass def dispatcher_connect( hass: HomeAssistant, signal: str, target: Callable[..., None] -) -> Callable[[], None]: - ... +) -> Callable[[], None]: ... @bind_hass # type: ignore[misc] # workaround; exclude typing of 2 overload in func def @@ -108,8 +106,7 @@ def _async_remove_dispatcher( @bind_hass def async_dispatcher_connect( hass: HomeAssistant, signal: SignalType[*_Ts], target: Callable[[*_Ts], Any] -) -> Callable[[], None]: - ... +) -> Callable[[], None]: ... @overload @@ -117,8 +114,7 @@ def async_dispatcher_connect( @bind_hass def async_dispatcher_connect( hass: HomeAssistant, signal: str, target: Callable[..., Any] -) -> Callable[[], None]: - ... +) -> Callable[[], None]: ... @callback @@ -150,14 +146,14 @@ def async_dispatcher_connect( @overload @bind_hass -def dispatcher_send(hass: HomeAssistant, signal: SignalType[*_Ts], *args: *_Ts) -> None: - ... +def dispatcher_send( + hass: HomeAssistant, signal: SignalType[*_Ts], *args: *_Ts +) -> None: ... @overload @bind_hass -def dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: - ... +def dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: ... @bind_hass # type: ignore[misc] # workaround; exclude typing of 2 overload in func def @@ -195,15 +191,13 @@ def _generate_job( @bind_hass def async_dispatcher_send( hass: HomeAssistant, signal: SignalType[*_Ts], *args: *_Ts -) -> None: - ... +) -> None: ... @overload @callback @bind_hass -def async_dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: - ... +def async_dispatcher_send(hass: HomeAssistant, signal: str, *args: Any) -> None: ... @callback diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index b0e094f97b4..1cff472af72 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -163,9 +163,9 @@ class EntityPlatform: # with the child dict indexed by entity_id # # This is usually media_player.yamaha, light.hue, switch.tplink, etc. - domain_platform_entities: dict[ - tuple[str, str], dict[str, Entity] - ] = hass.data.setdefault(DATA_DOMAIN_PLATFORM_ENTITIES, {}) + domain_platform_entities: dict[tuple[str, str], dict[str, Entity]] = ( + hass.data.setdefault(DATA_DOMAIN_PLATFORM_ENTITIES, {}) + ) key = (domain, platform_name) self.domain_platform_entities = domain_platform_entities.setdefault(key, {}) diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index eba3a05cea8..904acc1d805 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -54,13 +54,13 @@ class FloorRegistry(BaseRegistry): def __init__(self, hass: HomeAssistant) -> None: """Initialize the floor registry.""" self.hass = hass - self._store: Store[ - dict[str, list[dict[str, str | int | list[str] | None]]] - ] = Store( - hass, - STORAGE_VERSION_MAJOR, - STORAGE_KEY, - atomic_writes=True, + self._store: Store[dict[str, list[dict[str, str | int | list[str] | None]]]] = ( + Store( + hass, + STORAGE_VERSION_MAJOR, + STORAGE_KEY, + atomic_writes=True, + ) ) @callback diff --git a/homeassistant/helpers/integration_platform.py b/homeassistant/helpers/integration_platform.py index e142f9c2e5a..6d474557748 100644 --- a/homeassistant/helpers/integration_platform.py +++ b/homeassistant/helpers/integration_platform.py @@ -52,9 +52,9 @@ def _async_integration_platform_component_loaded( if component_name in integration_platform.seen_components: continue integration_platform.seen_components.add(component_name) - integration_platforms_by_name[ - integration_platform.platform_name - ] = integration_platform + integration_platforms_by_name[integration_platform.platform_name] = ( + integration_platform + ) if not integration_platforms_by_name: return diff --git a/homeassistant/helpers/redact.py b/homeassistant/helpers/redact.py index 1ee9eeaae42..ad06f58a50a 100644 --- a/homeassistant/helpers/redact.py +++ b/homeassistant/helpers/redact.py @@ -34,15 +34,13 @@ def partial_redact( @overload def async_redact_data( # type: ignore[overload-overlap] data: Mapping, to_redact: Iterable[Any] | Mapping[Any, Callable[[_ValueT], _ValueT]] -) -> dict: - ... +) -> dict: ... @overload def async_redact_data( data: _T, to_redact: Iterable[Any] | Mapping[Any, Callable[[_ValueT], _ValueT]] -) -> _T: - ... +) -> _T: ... @callback diff --git a/homeassistant/helpers/reload.py b/homeassistant/helpers/reload.py index d6338af95c7..ffd6bdeb50d 100644 --- a/homeassistant/helpers/reload.py +++ b/homeassistant/helpers/reload.py @@ -139,8 +139,7 @@ async def _async_reconfig_platform( @overload async def async_integration_yaml_config( hass: HomeAssistant, integration_name: str -) -> ConfigType | None: - ... +) -> ConfigType | None: ... @overload @@ -149,8 +148,7 @@ async def async_integration_yaml_config( integration_name: str, *, raise_on_failure: Literal[True], -) -> ConfigType: - ... +) -> ConfigType: ... @overload @@ -159,8 +157,7 @@ async def async_integration_yaml_config( integration_name: str, *, raise_on_failure: Literal[False] | bool, -) -> ConfigType | None: - ... +) -> ConfigType | None: ... async def async_integration_yaml_config( diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index b5b53c91def..0486c7b6f8c 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -38,9 +38,11 @@ class SchemaFlowStep: class SchemaFlowFormStep(SchemaFlowStep): """Define a config or options flow form step.""" - schema: vol.Schema | Callable[ - [SchemaCommonFlowHandler], Coroutine[Any, Any, vol.Schema | None] - ] | None = None + schema: ( + vol.Schema + | Callable[[SchemaCommonFlowHandler], Coroutine[Any, Any, vol.Schema | None]] + | None + ) = None """Optional voluptuous schema, or function which returns a schema or None, for requesting and validating user input. @@ -50,9 +52,13 @@ class SchemaFlowFormStep(SchemaFlowStep): user input is requested. """ - validate_user_input: Callable[ - [SchemaCommonFlowHandler, dict[str, Any]], Coroutine[Any, Any, dict[str, Any]] - ] | None = None + validate_user_input: ( + Callable[ + [SchemaCommonFlowHandler, dict[str, Any]], + Coroutine[Any, Any, dict[str, Any]], + ] + | None + ) = None """Optional function to validate user input. - The `validate_user_input` function is called if the schema validates successfully. @@ -61,9 +67,9 @@ class SchemaFlowFormStep(SchemaFlowStep): - The `validate_user_input` should raise `SchemaFlowError` if user input is invalid. """ - next_step: Callable[ - [dict[str, Any]], Coroutine[Any, Any, str | None] - ] | str | None = None + next_step: ( + Callable[[dict[str, Any]], Coroutine[Any, Any, str | None]] | str | None + ) = None """Optional property to identify next step. - If `next_step` is a function, it is called if the schema validates successfully or @@ -73,9 +79,11 @@ class SchemaFlowFormStep(SchemaFlowStep): - If `next_step` is None, the flow is ended with `FlowResultType.CREATE_ENTRY`. """ - suggested_values: Callable[ - [SchemaCommonFlowHandler], Coroutine[Any, Any, dict[str, Any]] - ] | None | UndefinedType = UNDEFINED + suggested_values: ( + Callable[[SchemaCommonFlowHandler], Coroutine[Any, Any, dict[str, Any]]] + | None + | UndefinedType + ) = UNDEFINED """Optional property to populate suggested values. - If `suggested_values` is UNDEFINED, each key in the schema will get a suggested diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 34cee93d971..da27df9d139 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -656,9 +656,9 @@ async def async_get_all_descriptions( hass: HomeAssistant, ) -> dict[str, dict[str, Any]]: """Return descriptions (i.e. user documentation) for all service calls.""" - descriptions_cache: dict[ - tuple[str, str], dict[str, Any] | None - ] = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) + descriptions_cache: dict[tuple[str, str], dict[str, Any] | None] = ( + hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) + ) # We don't mutate services here so we avoid calling # async_services which makes a copy of every services @@ -808,9 +808,9 @@ def async_set_service_schema( domain = domain.lower() service = service.lower() - descriptions_cache: dict[ - tuple[str, str], dict[str, Any] | None - ] = hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) + descriptions_cache: dict[tuple[str, str], dict[str, Any] | None] = ( + hass.data.setdefault(SERVICE_DESCRIPTION_CACHE, {}) + ) description = { "name": schema.get("name", ""), diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 58a7e27714c..fc38b821eee 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1208,13 +1208,11 @@ def _resolve_state( @overload -def forgiving_boolean(value: Any) -> bool | object: - ... +def forgiving_boolean(value: Any) -> bool | object: ... @overload -def forgiving_boolean(value: Any, default: _T) -> bool | _T: - ... +def forgiving_boolean(value: Any, default: _T) -> bool | _T: ... def forgiving_boolean( @@ -2846,8 +2844,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): filename: str | None = None, raw: Literal[False] = False, defer_init: bool = False, - ) -> CodeType: - ... + ) -> CodeType: ... @overload def compile( @@ -2857,8 +2854,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): filename: str | None = None, raw: Literal[True] = ..., defer_init: bool = False, - ) -> str: - ... + ) -> str: ... def compile( self, diff --git a/homeassistant/loader.py b/homeassistant/loader.py index ae823d9a204..f462ea16886 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -296,9 +296,9 @@ async def async_get_custom_components( hass: HomeAssistant, ) -> dict[str, Integration]: """Return cached list of custom integrations.""" - comps_or_future: dict[str, Integration] | asyncio.Future[ - dict[str, Integration] - ] | None = hass.data.get(DATA_CUSTOM_COMPONENTS) + comps_or_future: ( + dict[str, Integration] | asyncio.Future[dict[str, Integration]] | None + ) = hass.data.get(DATA_CUSTOM_COMPONENTS) if comps_or_future is None: future = hass.data[DATA_CUSTOM_COMPONENTS] = hass.loop.create_future() diff --git a/homeassistant/setup.py b/homeassistant/setup.py index f9c86b23d96..178ee6425e3 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -33,9 +33,9 @@ from .helpers.issue_registry import IssueSeverity, async_create_issue from .helpers.typing import ConfigType from .util.async_ import create_eager_task -current_setup_group: contextvars.ContextVar[ - tuple[str, str | None] | None -] = contextvars.ContextVar("current_setup_group", default=None) +current_setup_group: contextvars.ContextVar[tuple[str, str | None] | None] = ( + contextvars.ContextVar("current_setup_group", default=None) +) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 5fa27c826b8..39976cce5f7 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -179,20 +179,17 @@ def start_of_local_day(dt_or_d: dt.date | dt.datetime | None = None) -> dt.datet # All rights reserved. # https://github.com/django/django/blob/main/LICENSE @overload -def parse_datetime(dt_str: str) -> dt.datetime | None: - ... +def parse_datetime(dt_str: str) -> dt.datetime | None: ... @overload -def parse_datetime(dt_str: str, *, raise_on_error: Literal[True]) -> dt.datetime: - ... +def parse_datetime(dt_str: str, *, raise_on_error: Literal[True]) -> dt.datetime: ... @overload def parse_datetime( dt_str: str, *, raise_on_error: Literal[False] | bool -) -> dt.datetime | None: - ... +) -> dt.datetime | None: ... def parse_datetime(dt_str: str, *, raise_on_error: bool = False) -> dt.datetime | None: diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index ac85691b7c2..489b6493ef1 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -139,15 +139,13 @@ def _callback_wrapper( @overload def catch_log_exception( func: Callable[[*_Ts], Coroutine[Any, Any, Any]], format_err: Callable[[*_Ts], Any] -) -> Callable[[*_Ts], Coroutine[Any, Any, None]]: - ... +) -> Callable[[*_Ts], Coroutine[Any, Any, None]]: ... @overload def catch_log_exception( func: Callable[[*_Ts], Any], format_err: Callable[[*_Ts], Any] -) -> Callable[[*_Ts], None] | Callable[[*_Ts], Coroutine[Any, Any, None]]: - ... +) -> Callable[[*_Ts], None] | Callable[[*_Ts], Coroutine[Any, Any, None]]: ... def catch_log_exception( diff --git a/homeassistant/util/variance.py b/homeassistant/util/variance.py index 974a0f88e90..b109e5c476c 100644 --- a/homeassistant/util/variance.py +++ b/homeassistant/util/variance.py @@ -14,22 +14,19 @@ _P = ParamSpec("_P") @overload def ignore_variance( func: Callable[_P, int], ignored_variance: int -) -> Callable[_P, int]: - ... +) -> Callable[_P, int]: ... @overload def ignore_variance( func: Callable[_P, float], ignored_variance: float -) -> Callable[_P, float]: - ... +) -> Callable[_P, float]: ... @overload def ignore_variance( func: Callable[_P, datetime], ignored_variance: timedelta -) -> Callable[_P, datetime]: - ... +) -> Callable[_P, datetime]: ... def ignore_variance(func: Callable[_P, _R], ignored_variance: Any) -> Callable[_P, _R]: diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index 0713fd83ee8..28027c97211 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -293,8 +293,7 @@ def _add_reference( obj: list | NodeListClass, loader: LoaderType, node: yaml.nodes.Node, -) -> NodeListClass: - ... +) -> NodeListClass: ... @overload @@ -302,13 +301,13 @@ def _add_reference( obj: str | NodeStrClass, loader: LoaderType, node: yaml.nodes.Node, -) -> NodeStrClass: - ... +) -> NodeStrClass: ... @overload -def _add_reference(obj: _DictT, loader: LoaderType, node: yaml.nodes.Node) -> _DictT: - ... +def _add_reference( + obj: _DictT, loader: LoaderType, node: yaml.nodes.Node +) -> _DictT: ... def _add_reference( # type: ignore[no-untyped-def] diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index a68b91e563f..cb64db20dcd 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -1,5 +1,5 @@ # Automatically generated from .pre-commit-config.yaml by gen_requirements_all.py, do not edit codespell==2.2.6 -ruff==0.2.1 +ruff==0.3.4 yamllint==1.35.1 diff --git a/script/translations/migrate.py b/script/translations/migrate.py index a6cffe28f7f..0f51e49c5a9 100644 --- a/script/translations/migrate.py +++ b/script/translations/migrate.py @@ -269,9 +269,9 @@ def find_frontend_states(): for device_class, dev_class_states in domain_to_write.items(): to_device_class = "_" if device_class == "default" else device_class for key in dev_class_states: - to_migrate[ - f"{from_key_base}::{device_class}::{key}" - ] = f"{to_key_base}::{to_device_class}::{key}" + to_migrate[f"{from_key_base}::{device_class}::{key}"] = ( + f"{to_key_base}::{to_device_class}::{key}" + ) # Rewrite "default" device class to _ if "default" in domain_to_write: diff --git a/tests/auth/mfa_modules/test_notify.py b/tests/auth/mfa_modules/test_notify.py index 662da38b35b..23b8811dbf9 100644 --- a/tests/auth/mfa_modules/test_notify.py +++ b/tests/auth/mfa_modules/test_notify.py @@ -183,8 +183,9 @@ async def test_login_flow_validates_mfa(hass: HomeAssistant) -> None: assert len(notify_calls) == 1 # retry twice - with patch("pyotp.HOTP.verify", return_value=False), patch( - "pyotp.HOTP.at", return_value=MOCK_CODE_2 + with ( + patch("pyotp.HOTP.verify", return_value=False), + patch("pyotp.HOTP.at", return_value=MOCK_CODE_2), ): result = await hass.auth.login_flow.async_configure( result["flow_id"], {"code": "invalid-code"} diff --git a/tests/auth/test_auth_store.py b/tests/auth/test_auth_store.py index 91048cd8568..3d62190eab6 100644 --- a/tests/auth/test_auth_store.py +++ b/tests/auth/test_auth_store.py @@ -201,13 +201,13 @@ async def test_system_groups_store_id_and_name( async def test_loading_only_once(hass: HomeAssistant) -> None: """Test only one storage load is allowed.""" store = auth_store.AuthStore(hass) - with patch( - "homeassistant.helpers.entity_registry.async_get" - ) as mock_ent_registry, patch( - "homeassistant.helpers.device_registry.async_get" - ) as mock_dev_registry, patch( - "homeassistant.helpers.storage.Store.async_load", return_value=None - ) as mock_load: + with ( + patch("homeassistant.helpers.entity_registry.async_get") as mock_ent_registry, + patch("homeassistant.helpers.device_registry.async_get") as mock_dev_registry, + patch( + "homeassistant.helpers.storage.Store.async_load", return_value=None + ) as mock_load, + ): await store.async_load() with pytest.raises(RuntimeError, match="Auth storage is already loaded"): await store.async_load() diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py index 91db3fd51d4..4cf6b2cc5f7 100644 --- a/tests/auth/test_init.py +++ b/tests/auth/test_init.py @@ -556,10 +556,13 @@ async def test_refresh_token_provider_validation(mock_hass) -> None: assert manager.async_create_access_token(refresh_token, ip) is not None - with patch( - "homeassistant.auth.providers.insecure_example.ExampleAuthProvider.async_validate_refresh_token", - side_effect=InvalidAuthError("Invalid access"), - ) as call, pytest.raises(InvalidAuthError): + with ( + patch( + "homeassistant.auth.providers.insecure_example.ExampleAuthProvider.async_validate_refresh_token", + side_effect=InvalidAuthError("Invalid access"), + ) as call, + pytest.raises(InvalidAuthError), + ): manager.async_create_access_token(refresh_token, ip) call.assert_called_with(refresh_token, ip) @@ -1103,9 +1106,10 @@ async def test_async_remove_user_fail_if_remove_credential_fails( """Test removing a user.""" await hass.auth.async_link_user(hass_admin_user, hass_admin_credential) - with patch.object( - hass.auth, "async_remove_credentials", side_effect=ValueError - ), pytest.raises(ValueError): + with ( + patch.object(hass.auth, "async_remove_credentials", side_effect=ValueError), + pytest.raises(ValueError), + ): await hass.auth.async_remove_user(hass_admin_user) diff --git a/tests/common.py b/tests/common.py index 72b38a64650..b04632caea2 100644 --- a/tests/common.py +++ b/tests/common.py @@ -313,28 +313,35 @@ async def async_test_home_assistant( hass ) if load_registries: - with patch.object( - StoreWithoutWriteLoad, "async_load", return_value=None - ), patch( - "homeassistant.helpers.area_registry.AreaRegistryStore", - StoreWithoutWriteLoad, - ), patch( - "homeassistant.helpers.device_registry.DeviceRegistryStore", - StoreWithoutWriteLoad, - ), patch( - "homeassistant.helpers.entity_registry.EntityRegistryStore", - StoreWithoutWriteLoad, - ), patch( - "homeassistant.helpers.storage.Store", # Floor & label registry are different - StoreWithoutWriteLoad, - ), patch( - "homeassistant.helpers.issue_registry.IssueRegistryStore", - StoreWithoutWriteLoad, - ), patch( - "homeassistant.helpers.restore_state.RestoreStateData.async_setup_dump", - return_value=None, - ), patch( - "homeassistant.helpers.restore_state.start.async_at_start", + with ( + patch.object(StoreWithoutWriteLoad, "async_load", return_value=None), + patch( + "homeassistant.helpers.area_registry.AreaRegistryStore", + StoreWithoutWriteLoad, + ), + patch( + "homeassistant.helpers.device_registry.DeviceRegistryStore", + StoreWithoutWriteLoad, + ), + patch( + "homeassistant.helpers.entity_registry.EntityRegistryStore", + StoreWithoutWriteLoad, + ), + patch( + "homeassistant.helpers.storage.Store", # Floor & label registry are different + StoreWithoutWriteLoad, + ), + patch( + "homeassistant.helpers.issue_registry.IssueRegistryStore", + StoreWithoutWriteLoad, + ), + patch( + "homeassistant.helpers.restore_state.RestoreStateData.async_setup_dump", + return_value=None, + ), + patch( + "homeassistant.helpers.restore_state.start.async_at_start", + ), ): await ar.async_load(hass) await cr.async_load(hass) @@ -517,12 +524,15 @@ def _async_fire_time_changed( future_seconds = task.when() - (hass.loop.time() + _MONOTONIC_RESOLUTION) if fire_all or mock_seconds_into_future >= future_seconds: - with patch( - "homeassistant.helpers.event.time_tracker_utcnow", - return_value=utc_datetime, - ), patch( - "homeassistant.helpers.event.time_tracker_timestamp", - return_value=timestamp, + with ( + patch( + "homeassistant.helpers.event.time_tracker_utcnow", + return_value=utc_datetime, + ), + patch( + "homeassistant.helpers.event.time_tracker_timestamp", + return_value=timestamp, + ), ): task._run() task.cancel() @@ -1377,18 +1387,22 @@ def mock_storage( """Remove data.""" data.pop(store.key, None) - with patch( - "homeassistant.helpers.storage.Store._async_load", - side_effect=mock_async_load, - autospec=True, - ), patch( - "homeassistant.helpers.storage.Store._async_write_data", - side_effect=mock_write_data, - autospec=True, - ), patch( - "homeassistant.helpers.storage.Store.async_remove", - side_effect=mock_remove, - autospec=True, + with ( + patch( + "homeassistant.helpers.storage.Store._async_load", + side_effect=mock_async_load, + autospec=True, + ), + patch( + "homeassistant.helpers.storage.Store._async_write_data", + side_effect=mock_write_data, + autospec=True, + ), + patch( + "homeassistant.helpers.storage.Store.async_remove", + side_effect=mock_remove, + autospec=True, + ), ): yield data diff --git a/tests/components/abode/common.py b/tests/components/abode/common.py index 20739cf58a1..22ee95cfa57 100644 --- a/tests/components/abode/common.py +++ b/tests/components/abode/common.py @@ -23,8 +23,9 @@ async def setup_platform(hass: HomeAssistant, platform: str) -> MockConfigEntry: ) mock_entry.add_to_hass(hass) - with patch("homeassistant.components.abode.PLATFORMS", [platform]), patch( - "jaraco.abode.event_controller.sio" + with ( + patch("homeassistant.components.abode.PLATFORMS", [platform]), + patch("jaraco.abode.event_controller.sio"), ): assert await async_setup_component(hass, ABODE_DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/abode/test_init.py b/tests/components/abode/test_init.py index e23fa5aa1b9..e6e5da35a5e 100644 --- a/tests/components/abode/test_init.py +++ b/tests/components/abode/test_init.py @@ -57,9 +57,10 @@ async def test_unload_entry(hass: HomeAssistant) -> None: """Test unloading the Abode entry.""" mock_entry = await setup_platform(hass, ALARM_DOMAIN) - with patch("jaraco.abode.client.Client.logout") as mock_logout, patch( - "jaraco.abode.event_controller.EventController.stop" - ) as mock_events_stop: + with ( + patch("jaraco.abode.client.Client.logout") as mock_logout, + patch("jaraco.abode.event_controller.EventController.stop") as mock_events_stop, + ): assert await hass.config_entries.async_unload(mock_entry.entry_id) mock_logout.assert_called_once() mock_events_stop.assert_called_once() @@ -71,19 +72,22 @@ async def test_unload_entry(hass: HomeAssistant) -> None: async def test_invalid_credentials(hass: HomeAssistant) -> None: """Test Abode credentials changing.""" - with patch( - "homeassistant.components.abode.Abode", - side_effect=AbodeAuthenticationException( - (HTTPStatus.BAD_REQUEST, "auth error") + with ( + patch( + "homeassistant.components.abode.Abode", + side_effect=AbodeAuthenticationException( + (HTTPStatus.BAD_REQUEST, "auth error") + ), ), - ), patch( - "homeassistant.components.abode.config_flow.AbodeFlowHandler.async_step_reauth", - return_value={ - "type": data_entry_flow.FlowResultType.FORM, - "flow_id": "mock_flow", - "step_id": "reauth_confirm", - }, - ) as mock_async_step_reauth: + patch( + "homeassistant.components.abode.config_flow.AbodeFlowHandler.async_step_reauth", + return_value={ + "type": data_entry_flow.FlowResultType.FORM, + "flow_id": "mock_flow", + "step_id": "reauth_confirm", + }, + ) as mock_async_step_reauth, + ): await setup_platform(hass, ALARM_DOMAIN) mock_async_step_reauth.assert_called_once() diff --git a/tests/components/accuweather/__init__.py b/tests/components/accuweather/__init__.py index 51ced2202bd..afaa5bbef25 100644 --- a/tests/components/accuweather/__init__.py +++ b/tests/components/accuweather/__init__.py @@ -38,16 +38,20 @@ async def init_integration( if unsupported_icon: current["WeatherIcon"] = 999 - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ), patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ), patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ), + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", + return_value=forecast, + ), + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/accuweather/test_config_flow.py b/tests/components/accuweather/test_config_flow.py index bb338618ccc..c9d95c34b7c 100644 --- a/tests/components/accuweather/test_config_flow.py +++ b/tests/components/accuweather/test_config_flow.py @@ -119,11 +119,14 @@ async def test_integration_already_exists(hass: HomeAssistant) -> None: async def test_create_entry(hass: HomeAssistant) -> None: """Test that the user step works.""" - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - return_value=load_json_object_fixture("accuweather/location_data.json"), - ), patch( - "homeassistant.components.accuweather.async_setup_entry", return_value=True + with ( + patch( + "homeassistant.components.accuweather.AccuWeather._async_get_data", + return_value=load_json_object_fixture("accuweather/location_data.json"), + ), + patch( + "homeassistant.components.accuweather.async_setup_entry", return_value=True + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -148,20 +151,25 @@ async def test_options_flow(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - return_value=load_json_object_fixture("accuweather/location_data.json"), - ), patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=load_json_object_fixture( - "accuweather/current_conditions_data.json" + with ( + patch( + "homeassistant.components.accuweather.AccuWeather._async_get_data", + return_value=load_json_object_fixture("accuweather/location_data.json"), + ), + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=load_json_object_fixture( + "accuweather/current_conditions_data.json" + ), + ), + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast" + ), + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, ), - ), patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast" - ), patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/accuweather/test_init.py b/tests/components/accuweather/test_init.py index 5f2f9f07a2f..bb5b67e7918 100644 --- a/tests/components/accuweather/test_init.py +++ b/tests/components/accuweather/test_init.py @@ -101,13 +101,16 @@ async def test_update_interval_forecast(hass: HomeAssistant) -> None: forecast = load_json_array_fixture("accuweather/forecast_data.json") future = utcnow() + timedelta(minutes=80) - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ) as mock_current, patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ) as mock_forecast: + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ) as mock_current, + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", + return_value=forecast, + ) as mock_forecast, + ): assert mock_current.call_count == 0 assert mock_forecast.call_count == 0 diff --git a/tests/components/accuweather/test_sensor.py b/tests/components/accuweather/test_sensor.py index 6c93c21ccc0..8e6e01a4578 100644 --- a/tests/components/accuweather/test_sensor.py +++ b/tests/components/accuweather/test_sensor.py @@ -577,15 +577,18 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state == STATE_UNAVAILABLE future = utcnow() + timedelta(minutes=120) - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=load_json_object_fixture( - "accuweather/current_conditions_data.json" + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=load_json_object_fixture( + "accuweather/current_conditions_data.json" + ), + ), + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, ), - ), patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -605,16 +608,20 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: current = load_json_object_fixture("accuweather/current_conditions_data.json") forecast = load_json_array_fixture("accuweather/forecast_data.json") - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ) as mock_current, patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ) as mock_forecast, patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ) as mock_current, + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", + return_value=forecast, + ) as mock_forecast, + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, + ), ): await hass.services.async_call( "homeassistant", @@ -665,13 +672,16 @@ async def test_state_update(hass: HomeAssistant) -> None: ) current_condition["Ceiling"]["Metric"]["Value"] = 3300 - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current_condition, - ), patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current_condition, + ), + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index 2b2c304f948..291c1c1dcdf 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -152,15 +152,18 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state == STATE_UNAVAILABLE future = utcnow() + timedelta(minutes=120) - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=load_json_object_fixture( - "accuweather/current_conditions_data.json" + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=load_json_object_fixture( + "accuweather/current_conditions_data.json" + ), + ), + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, ), - ), patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -180,16 +183,20 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: current = load_json_object_fixture("accuweather/current_conditions_data.json") forecast = load_json_array_fixture("accuweather/forecast_data.json") - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ) as mock_current, patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ) as mock_forecast, patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ) as mock_current, + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", + return_value=forecast, + ) as mock_forecast, + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, + ), ): await hass.services.async_call( "homeassistant", @@ -271,16 +278,20 @@ async def test_forecast_subscription( current = load_json_object_fixture("accuweather/current_conditions_data.json") forecast = load_json_array_fixture("accuweather/forecast_data.json") - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ), patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ), patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, + with ( + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ), + patch( + "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", + return_value=forecast, + ), + patch( + "homeassistant.components.accuweather.AccuWeather.requests_remaining", + new_callable=PropertyMock, + return_value=10, + ), ): freezer.tick(timedelta(minutes=80) + timedelta(seconds=1)) await hass.async_block_till_done() diff --git a/tests/components/adax/test_config_flow.py b/tests/components/adax/test_config_flow.py index 1189258d81d..b2342b7c2a7 100644 --- a/tests/components/adax/test_config_flow.py +++ b/tests/components/adax/test_config_flow.py @@ -42,13 +42,16 @@ async def test_form(hass: HomeAssistant) -> None: ) assert result2["type"] == FlowResultType.FORM - with patch( - "adax.get_adax_token", - return_value="test_token", - ), patch( - "homeassistant.components.adax.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "adax.get_adax_token", + return_value="test_token", + ), + patch( + "homeassistant.components.adax.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], TEST_DATA, @@ -149,12 +152,16 @@ async def test_local_create_entry(hass: HomeAssistant) -> None: WIFI_PSWD: "pswd", } - with patch( - "homeassistant.components.adax.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.adax.config_flow.adax_local.AdaxConfig", autospec=True - ) as mock_client_class: + with ( + patch( + "homeassistant.components.adax.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.adax.config_flow.adax_local.AdaxConfig", + autospec=True, + ) as mock_client_class, + ): client = mock_client_class.return_value client.configure_device.return_value = True client.device_ip = "192.168.1.4" diff --git a/tests/components/advantage_air/test_config_flow.py b/tests/components/advantage_air/test_config_flow.py index f5f972a9884..134cfee9f68 100644 --- a/tests/components/advantage_air/test_config_flow.py +++ b/tests/components/advantage_air/test_config_flow.py @@ -21,13 +21,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result1["step_id"] == "user" assert result1["errors"] == {} - with patch( - "homeassistant.components.advantage_air.config_flow.advantage_air.async_get", - new=AsyncMock(return_value=TEST_SYSTEM_DATA), - ) as mock_get, patch( - "homeassistant.components.advantage_air.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.advantage_air.config_flow.advantage_air.async_get", + new=AsyncMock(return_value=TEST_SYSTEM_DATA), + ) as mock_get, + patch( + "homeassistant.components.advantage_air.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], USER_INPUT, diff --git a/tests/components/airnow/conftest.py b/tests/components/airnow/conftest.py index a374168df6a..1010a45b8fb 100644 --- a/tests/components/airnow/conftest.py +++ b/tests/components/airnow/conftest.py @@ -59,7 +59,8 @@ def mock_api_get_fixture(data): @pytest.fixture(name="setup_airnow") async def setup_airnow_fixture(hass, config, mock_api_get): """Define a fixture to set up AirNow.""" - with patch("pyairnow.WebServiceAPI._get", mock_api_get), patch( - "homeassistant.components.airnow.PLATFORMS", [] + with ( + patch("pyairnow.WebServiceAPI._get", mock_api_get), + patch("homeassistant.components.airnow.PLATFORMS", []), ): yield diff --git a/tests/components/airq/test_config_flow.py b/tests/components/airq/test_config_flow.py index 8069ce4450d..9c5492eaa20 100644 --- a/tests/components/airq/test_config_flow.py +++ b/tests/components/airq/test_config_flow.py @@ -37,8 +37,9 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch("aioairq.AirQ.validate"), patch( - "aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO + with ( + patch("aioairq.AirQ.validate"), + patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -93,8 +94,9 @@ async def test_duplicate_error(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aioairq.AirQ.validate"), patch( - "aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO + with ( + patch("aioairq.AirQ.validate"), + patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_USER_DATA diff --git a/tests/components/airthings/test_config_flow.py b/tests/components/airthings/test_config_flow.py index f685f4c021c..2ea157f09b1 100644 --- a/tests/components/airthings/test_config_flow.py +++ b/tests/components/airthings/test_config_flow.py @@ -27,13 +27,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "airthings.get_token", - return_value="test_token", - ), patch( - "homeassistant.components.airthings.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "airthings.get_token", + return_value="test_token", + ), + patch( + "homeassistant.components.airthings.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_DATA, diff --git a/tests/components/airthings_ble/test_config_flow.py b/tests/components/airthings_ble/test_config_flow.py index 792ac2ad7ab..edeb08abb74 100644 --- a/tests/components/airthings_ble/test_config_flow.py +++ b/tests/components/airthings_ble/test_config_flow.py @@ -26,13 +26,16 @@ from tests.common import MockConfigEntry async def test_bluetooth_discovery(hass: HomeAssistant) -> None: """Test discovery via bluetooth with a valid device.""" - with patch_async_ble_device_from_address(WAVE_SERVICE_INFO), patch_airthings_ble( - AirthingsDevice( - manufacturer="Airthings AS", - model=AirthingsDeviceType.WAVE_PLUS, - name="Airthings Wave Plus", - identifier="123456", - ) + with ( + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble( + AirthingsDevice( + manufacturer="Airthings AS", + model=AirthingsDeviceType.WAVE_PLUS, + name="Airthings Wave Plus", + identifier="123456", + ) + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -74,9 +77,10 @@ async def test_bluetooth_discovery_airthings_ble_update_failed( """Test discovery via bluetooth but there's an exception from airthings-ble.""" for loop in [(Exception(), "unknown"), (BleakError(), "cannot_connect")]: exc, reason = loop - with patch_async_ble_device_from_address( - WAVE_SERVICE_INFO - ), patch_airthings_ble(side_effect=exc): + with ( + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble(side_effect=exc), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_BLUETOOTH}, @@ -105,16 +109,20 @@ async def test_bluetooth_discovery_already_setup(hass: HomeAssistant) -> None: async def test_user_setup(hass: HomeAssistant) -> None: """Test the user initiated form.""" - with patch( - "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", - return_value=[WAVE_SERVICE_INFO], - ), patch_async_ble_device_from_address(WAVE_SERVICE_INFO), patch_airthings_ble( - AirthingsDevice( - manufacturer="Airthings AS", - model=AirthingsDeviceType.WAVE_PLUS, - name="Airthings Wave Plus", - identifier="123456", - ) + with ( + patch( + "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", + return_value=[WAVE_SERVICE_INFO], + ), + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble( + AirthingsDevice( + manufacturer="Airthings AS", + model=AirthingsDeviceType.WAVE_PLUS, + name="Airthings Wave Plus", + identifier="123456", + ) + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -176,11 +184,13 @@ async def test_user_setup_existing_and_unknown_device(hass: HomeAssistant) -> No async def test_user_setup_unknown_error(hass: HomeAssistant) -> None: """Test the user initiated form with an unknown error.""" - with patch( - "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", - return_value=[WAVE_SERVICE_INFO], - ), patch_async_ble_device_from_address(WAVE_SERVICE_INFO), patch_airthings_ble( - None, Exception() + with ( + patch( + "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", + return_value=[WAVE_SERVICE_INFO], + ), + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble(None, Exception()), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -192,11 +202,13 @@ async def test_user_setup_unknown_error(hass: HomeAssistant) -> None: async def test_user_setup_unable_to_connect(hass: HomeAssistant) -> None: """Test the user initiated form with a device that's failing connection.""" - with patch( - "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", - return_value=[WAVE_SERVICE_INFO], - ), patch_async_ble_device_from_address(WAVE_SERVICE_INFO), patch_airthings_ble( - side_effect=BleakError("An error") + with ( + patch( + "homeassistant.components.airthings_ble.config_flow.async_discovered_service_info", + return_value=[WAVE_SERVICE_INFO], + ), + patch_async_ble_device_from_address(WAVE_SERVICE_INFO), + patch_airthings_ble(side_effect=BleakError("An error")), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} diff --git a/tests/components/airtouch4/test_config_flow.py b/tests/components/airtouch4/test_config_flow.py index a75312bc55b..e5e3672f69d 100644 --- a/tests/components/airtouch4/test_config_flow.py +++ b/tests/components/airtouch4/test_config_flow.py @@ -24,13 +24,16 @@ async def test_form(hass: HomeAssistant) -> None: mock_airtouch.GetAcs = Mock(return_value=[mock_ac]) mock_airtouch.GetGroups = Mock(return_value=[mock_groups]) - with patch( - "homeassistant.components.airtouch4.config_flow.AirTouch", - return_value=mock_airtouch, - ), patch( - "homeassistant.components.airtouch4.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.airtouch4.config_flow.AirTouch", + return_value=mock_airtouch, + ), + patch( + "homeassistant.components.airtouch4.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "0.0.0.1"} ) diff --git a/tests/components/airvisual/conftest.py b/tests/components/airvisual/conftest.py index dde75738e80..1538af28a08 100644 --- a/tests/components/airvisual/conftest.py +++ b/tests/components/airvisual/conftest.py @@ -113,18 +113,23 @@ def integration_type_fixture(): @pytest.fixture(name="mock_pyairvisual") async def mock_pyairvisual_fixture(cloud_api, node_samba): """Define a fixture to patch pyairvisual.""" - with patch( - "homeassistant.components.airvisual.CloudAPI", - return_value=cloud_api, - ), patch( - "homeassistant.components.airvisual.config_flow.CloudAPI", - return_value=cloud_api, - ), patch( - "homeassistant.components.airvisual_pro.NodeSamba", - return_value=node_samba, - ), patch( - "homeassistant.components.airvisual_pro.config_flow.NodeSamba", - return_value=node_samba, + with ( + patch( + "homeassistant.components.airvisual.CloudAPI", + return_value=cloud_api, + ), + patch( + "homeassistant.components.airvisual.config_flow.CloudAPI", + return_value=cloud_api, + ), + patch( + "homeassistant.components.airvisual_pro.NodeSamba", + return_value=node_samba, + ), + patch( + "homeassistant.components.airvisual_pro.config_flow.NodeSamba", + return_value=node_samba, + ), ): yield diff --git a/tests/components/airvisual_pro/conftest.py b/tests/components/airvisual_pro/conftest.py index 34426e8e9c8..719b25b3cdf 100644 --- a/tests/components/airvisual_pro/conftest.py +++ b/tests/components/airvisual_pro/conftest.py @@ -75,11 +75,14 @@ def pro_fixture(connect, data, disconnect): @pytest.fixture(name="setup_airvisual_pro") async def setup_airvisual_pro_fixture(hass, config, pro): """Define a fixture to set up AirVisual Pro.""" - with patch( - "homeassistant.components.airvisual_pro.config_flow.NodeSamba", return_value=pro - ), patch( - "homeassistant.components.airvisual_pro.NodeSamba", return_value=pro - ), patch("homeassistant.components.airvisual.PLATFORMS", []): + with ( + patch( + "homeassistant.components.airvisual_pro.config_flow.NodeSamba", + return_value=pro, + ), + patch("homeassistant.components.airvisual_pro.NodeSamba", return_value=pro), + patch("homeassistant.components.airvisual.PLATFORMS", []), + ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() yield diff --git a/tests/components/airzone/test_climate.py b/tests/components/airzone/test_climate.py index 07d7f3127fa..fa972bd3899 100644 --- a/tests/components/airzone/test_climate.py +++ b/tests/components/airzone/test_climate.py @@ -229,18 +229,23 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None: HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MAX_TEMP] = 25 HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MIN_TEMP] = 10 - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK_CHANGED, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - return_value=HVAC_SYSTEMS_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK_CHANGED, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + return_value=HVAC_SYSTEMS_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() @@ -443,18 +448,23 @@ async def test_airzone_climate_set_hvac_mode(hass: HomeAssistant) -> None: HVAC_MOCK_NO_SET_POINT = copy.deepcopy(HVAC_MOCK) del HVAC_MOCK_NO_SET_POINT[API_SYSTEMS][0][API_DATA][0][API_SET_POINT] - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK_NO_SET_POINT, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - return_value=HVAC_SYSTEMS_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK_NO_SET_POINT, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + return_value=HVAC_SYSTEMS_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() @@ -478,10 +488,13 @@ async def test_airzone_climate_set_hvac_slave_error(hass: HomeAssistant) -> None await async_init_integration(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", - return_value=HVAC_MOCK, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", + return_value=HVAC_MOCK, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, @@ -570,10 +583,13 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None: await async_init_integration(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", - side_effect=AirzoneError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", + side_effect=AirzoneError, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, diff --git a/tests/components/airzone/test_config_flow.py b/tests/components/airzone/test_config_flow.py index cd1190c2af5..c47e2b1a3dd 100644 --- a/tests/components/airzone/test_config_flow.py +++ b/tests/components/airzone/test_config_flow.py @@ -46,24 +46,31 @@ TEST_PORT = 3000 async def test_form(hass: HomeAssistant) -> None: """Test that the form is served with valid input.""" - with patch( - "homeassistant.components.airzone.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -95,24 +102,31 @@ async def test_form(hass: HomeAssistant) -> None: async def test_form_invalid_system_id(hass: HomeAssistant) -> None: """Test Invalid System ID 0.""" - with patch( - "homeassistant.components.airzone.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - side_effect=HotWaterNotAvailable, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - side_effect=InvalidSystem, - ) as mock_hvac, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - side_effect=InvalidMethod, + with ( + patch( + "homeassistant.components.airzone.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + side_effect=HotWaterNotAvailable, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + side_effect=InvalidSystem, + ) as mock_hvac, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + side_effect=InvalidMethod, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG @@ -197,24 +211,31 @@ async def test_dhcp_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "discovered_connection" - with patch( - "homeassistant.components.airzone.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -278,24 +299,31 @@ async def test_dhcp_connection_error(hass: HomeAssistant) -> None: assert result["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.airzone.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -334,24 +362,31 @@ async def test_dhcp_invalid_system_id(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "discovered_connection" - with patch( - "homeassistant.components.airzone.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - side_effect=HotWaterNotAvailable, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - side_effect=InvalidSystem, - ) as mock_hvac, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - side_effect=InvalidMethod, + with ( + patch( + "homeassistant.components.airzone.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + side_effect=HotWaterNotAvailable, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + side_effect=InvalidSystem, + ) as mock_hvac, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + side_effect=InvalidMethod, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/airzone/test_coordinator.py b/tests/components/airzone/test_coordinator.py index 62f6a15fe35..06c77bebb81 100644 --- a/tests/components/airzone/test_coordinator.py +++ b/tests/components/airzone/test_coordinator.py @@ -30,21 +30,27 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - side_effect=HotWaterNotAvailable, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ) as mock_hvac, patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - side_effect=InvalidMethod, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + side_effect=HotWaterNotAvailable, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ) as mock_hvac, + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + side_effect=InvalidMethod, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/airzone/test_init.py b/tests/components/airzone/test_init.py index 8936fa3e282..293fc75acb5 100644 --- a/tests/components/airzone/test_init.py +++ b/tests/components/airzone/test_init.py @@ -22,21 +22,27 @@ async def test_unique_id_migrate( config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - side_effect=HotWaterNotAvailable, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - side_effect=InvalidMethod, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + side_effect=HotWaterNotAvailable, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + side_effect=InvalidMethod, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -47,21 +53,27 @@ async def test_unique_id_migrate( == f"{config_entry.entry_id}_1:1_temp" ) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - side_effect=HotWaterNotAvailable, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - side_effect=SystemOutOfRange, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + side_effect=HotWaterNotAvailable, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + side_effect=SystemOutOfRange, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() @@ -83,12 +95,15 @@ async def test_unload_entry(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.validate", - return_value=None, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.update", - return_value=None, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.validate", + return_value=None, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.update", + return_value=None, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/airzone/test_sensor.py b/tests/components/airzone/test_sensor.py index 1511cd4362c..3d4c54522fc 100644 --- a/tests/components/airzone/test_sensor.py +++ b/tests/components/airzone/test_sensor.py @@ -91,21 +91,27 @@ async def test_airzone_sensors_availability( HVAC_MOCK_UNAVAILABLE_ZONE = copy.deepcopy(HVAC_MOCK) del HVAC_MOCK_UNAVAILABLE_ZONE[API_SYSTEMS][0][API_DATA][1] - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK_UNAVAILABLE_ZONE, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - return_value=HVAC_SYSTEMS_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK_UNAVAILABLE_ZONE, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + return_value=HVAC_SYSTEMS_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() diff --git a/tests/components/airzone/test_water_heater.py b/tests/components/airzone/test_water_heater.py index ca7344204cf..fa4507223e2 100644 --- a/tests/components/airzone/test_water_heater.py +++ b/tests/components/airzone/test_water_heater.py @@ -211,10 +211,13 @@ async def test_airzone_water_heater_set_temp_error(hass: HomeAssistant) -> None: await async_init_integration(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", - side_effect=AirzoneError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.put_hvac", + side_effect=AirzoneError, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( WATER_HEATER_DOMAIN, SERVICE_SET_TEMPERATURE, diff --git a/tests/components/airzone/util.py b/tests/components/airzone/util.py index f83eceaae9c..c5c2d5972d4 100644 --- a/tests/components/airzone/util.py +++ b/tests/components/airzone/util.py @@ -322,21 +322,27 @@ async def async_init_integration( ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", - return_value=HVAC_DHW_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", - return_value=HVAC_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", - return_value=HVAC_SYSTEMS_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_version", - return_value=HVAC_VERSION_MOCK, - ), patch( - "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", - return_value=HVAC_WEBSERVER_MOCK, + with ( + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_dhw", + return_value=HVAC_DHW_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac", + return_value=HVAC_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems", + return_value=HVAC_SYSTEMS_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_version", + return_value=HVAC_VERSION_MOCK, + ), + patch( + "homeassistant.components.airzone.AirzoneLocalApi.get_webserver", + return_value=HVAC_WEBSERVER_MOCK, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/airzone_cloud/conftest.py b/tests/components/airzone_cloud/conftest.py index a63ab18d7bc..b289efd3fb9 100644 --- a/tests/components/airzone_cloud/conftest.py +++ b/tests/components/airzone_cloud/conftest.py @@ -8,11 +8,14 @@ import pytest @pytest.fixture(autouse=True) def airzone_cloud_no_websockets(): """Fixture to completely disable Airzone Cloud WebSockets.""" - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi._update_websockets", - return_value=False, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.connect_installation_websockets", - return_value=None, + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi._update_websockets", + return_value=False, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.connect_installation_websockets", + return_value=None, + ), ): yield diff --git a/tests/components/airzone_cloud/test_climate.py b/tests/components/airzone_cloud/test_climate.py index 46fc16b07df..9bfaf5683a1 100644 --- a/tests/components/airzone_cloud/test_climate.py +++ b/tests/components/airzone_cloud/test_climate.py @@ -492,10 +492,13 @@ async def test_airzone_climate_set_hvac_slave_error(hass: HomeAssistant) -> None await async_init_integration(hass) - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", - return_value=None, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + return_value=None, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, @@ -580,10 +583,13 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None: await async_init_integration(hass) # Aidoos - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", - side_effect=AirzoneCloudError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + side_effect=AirzoneCloudError, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -598,10 +604,13 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None: assert state.attributes[ATTR_TEMPERATURE] == 22.0 # Groups - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_put_group", - side_effect=AirzoneCloudError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_put_group", + side_effect=AirzoneCloudError, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -616,10 +625,13 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None: assert state.attributes[ATTR_TEMPERATURE] == 24.0 # Installations - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_put_installation", - side_effect=AirzoneCloudError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_put_installation", + side_effect=AirzoneCloudError, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -634,10 +646,13 @@ async def test_airzone_climate_set_temp_error(hass: HomeAssistant) -> None: assert state.attributes[ATTR_TEMPERATURE] == 23.0 # Zones - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", - side_effect=AirzoneCloudError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_patch_device", + side_effect=AirzoneCloudError, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, diff --git a/tests/components/airzone_cloud/test_config_flow.py b/tests/components/airzone_cloud/test_config_flow.py index ec031d4bf25..e1d31e28d4b 100644 --- a/tests/components/airzone_cloud/test_config_flow.py +++ b/tests/components/airzone_cloud/test_config_flow.py @@ -23,24 +23,31 @@ from .util import ( async def test_form(hass: HomeAssistant) -> None: """Test that the form is served with valid input.""" - with patch( - "homeassistant.components.airzone_cloud.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", - side_effect=mock_get_device_status, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installation", - return_value=GET_INSTALLATION_MOCK, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", - return_value=GET_INSTALLATIONS_MOCK, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", - side_effect=mock_get_webserver, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", - return_value=None, + with ( + patch( + "homeassistant.components.airzone_cloud.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", + side_effect=mock_get_device_status, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installation", + return_value=GET_INSTALLATION_MOCK, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", + return_value=GET_INSTALLATIONS_MOCK, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", + side_effect=mock_get_webserver, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", + return_value=None, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -87,21 +94,27 @@ async def test_form(hass: HomeAssistant) -> None: async def test_installations_list_error(hass: HomeAssistant) -> None: """Test connection error.""" - with patch( - "homeassistant.components.airzone_cloud.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", - side_effect=mock_get_device_status, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", - side_effect=AirzoneCloudError, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", - side_effect=mock_get_webserver, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", - return_value=None, + with ( + patch( + "homeassistant.components.airzone_cloud.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", + side_effect=mock_get_device_status, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", + side_effect=AirzoneCloudError, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", + side_effect=mock_get_webserver, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", + return_value=None, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} diff --git a/tests/components/airzone_cloud/test_coordinator.py b/tests/components/airzone_cloud/test_coordinator.py index 4b42ab7c329..b4b7afd6086 100644 --- a/tests/components/airzone_cloud/test_coordinator.py +++ b/tests/components/airzone_cloud/test_coordinator.py @@ -31,21 +31,27 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", - side_effect=mock_get_device_status, - ) as mock_device_status, patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installation", - return_value=GET_INSTALLATION_MOCK, - ) as mock_installation, patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", - return_value=GET_INSTALLATIONS_MOCK, - ) as mock_installations, patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", - side_effect=mock_get_webserver, - ) as mock_webserver, patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", - return_value=None, + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", + side_effect=mock_get_device_status, + ) as mock_device_status, + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installation", + return_value=GET_INSTALLATION_MOCK, + ) as mock_installation, + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", + return_value=GET_INSTALLATIONS_MOCK, + ) as mock_installations, + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", + side_effect=mock_get_webserver, + ) as mock_webserver, + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", + return_value=None, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/airzone_cloud/test_init.py b/tests/components/airzone_cloud/test_init.py index f8a7a710e08..b5b4bcebaa8 100644 --- a/tests/components/airzone_cloud/test_init.py +++ b/tests/components/airzone_cloud/test_init.py @@ -21,21 +21,27 @@ async def test_unload_entry(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", - return_value=None, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.logout", - return_value=None, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.list_installations", - return_value=[], - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.update_installation", - return_value=None, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.update", - return_value=None, + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", + return_value=None, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.logout", + return_value=None, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.list_installations", + return_value=[], + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.update_installation", + return_value=None, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.update", + return_value=None, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/airzone_cloud/util.py b/tests/components/airzone_cloud/util.py index 98ff7c65478..ea0dbf9f736 100644 --- a/tests/components/airzone_cloud/util.py +++ b/tests/components/airzone_cloud/util.py @@ -432,21 +432,27 @@ async def async_init_integration( ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", - side_effect=mock_get_device_status, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installation", - return_value=GET_INSTALLATION_MOCK, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", - return_value=GET_INSTALLATIONS_MOCK, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", - side_effect=mock_get_webserver, - ), patch( - "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", - return_value=None, + with ( + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_device_status", + side_effect=mock_get_device_status, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installation", + return_value=GET_INSTALLATION_MOCK, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_installations", + return_value=GET_INSTALLATIONS_MOCK, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.api_get_webserver", + side_effect=mock_get_webserver, + ), + patch( + "homeassistant.components.airzone_cloud.AirzoneCloudApi.login", + return_value=None, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/aladdin_connect/test_config_flow.py b/tests/components/aladdin_connect/test_config_flow.py index 2bdc05f57a3..90cf269b3f8 100644 --- a/tests/components/aladdin_connect/test_config_flow.py +++ b/tests/components/aladdin_connect/test_config_flow.py @@ -23,12 +23,16 @@ async def test_form(hass: HomeAssistant, mock_aladdinconnect_api: MagicMock) -> assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient", - return_value=mock_aladdinconnect_api, - ), patch( - "homeassistant.components.aladdin_connect.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient", + return_value=mock_aladdinconnect_api, + ), + patch( + "homeassistant.components.aladdin_connect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -158,12 +162,15 @@ async def test_reauth_flow( assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.aladdin_connect.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient", - return_value=mock_aladdinconnect_api, + with ( + patch( + "homeassistant.components.aladdin_connect.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient", + return_value=mock_aladdinconnect_api, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -206,15 +213,19 @@ async def test_reauth_flow_auth_error( assert result["errors"] == {} mock_aladdinconnect_api.login.return_value = False mock_aladdinconnect_api.login.side_effect = InvalidPasswordError - with patch( - "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient", - return_value=mock_aladdinconnect_api, - ), patch( - "homeassistant.components.aladdin_connect.cover.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.aladdin_connect.cover.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient", + return_value=mock_aladdinconnect_api, + ), + patch( + "homeassistant.components.aladdin_connect.cover.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.aladdin_connect.cover.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/aladdin_connect/test_init.py b/tests/components/aladdin_connect/test_init.py index 8f0b40328e4..c995fb5074d 100644 --- a/tests/components/aladdin_connect/test_init.py +++ b/tests/components/aladdin_connect/test_init.py @@ -26,12 +26,15 @@ async def test_setup_get_doors_errors(hass: HomeAssistant) -> None: unique_id="test-id", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", - return_value=True, - ), patch( - "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", - return_value=None, + with ( + patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login", + return_value=True, + ), + patch( + "homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors", + return_value=None, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) is True await hass.async_block_till_done() diff --git a/tests/components/alarmdecoder/test_config_flow.py b/tests/components/alarmdecoder/test_config_flow.py index ce7fc1ef333..614d055405e 100644 --- a/tests/components/alarmdecoder/test_config_flow.py +++ b/tests/components/alarmdecoder/test_config_flow.py @@ -74,12 +74,14 @@ async def test_setups(hass: HomeAssistant, protocol, connection, title) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "protocol" - with patch("homeassistant.components.alarmdecoder.config_flow.AdExt.open"), patch( - "homeassistant.components.alarmdecoder.config_flow.AdExt.close" - ), patch( - "homeassistant.components.alarmdecoder.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.alarmdecoder.config_flow.AdExt.open"), + patch("homeassistant.components.alarmdecoder.config_flow.AdExt.close"), + patch( + "homeassistant.components.alarmdecoder.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], connection ) @@ -117,20 +119,26 @@ async def test_setup_connection_error(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "protocol" - with patch( - "homeassistant.components.alarmdecoder.config_flow.AdExt.open", - side_effect=NoDeviceError, - ), patch("homeassistant.components.alarmdecoder.config_flow.AdExt.close"): + with ( + patch( + "homeassistant.components.alarmdecoder.config_flow.AdExt.open", + side_effect=NoDeviceError, + ), + patch("homeassistant.components.alarmdecoder.config_flow.AdExt.close"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], connection_settings ) assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.alarmdecoder.config_flow.AdExt.open", - side_effect=Exception, - ), patch("homeassistant.components.alarmdecoder.config_flow.AdExt.close"): + with ( + patch( + "homeassistant.components.alarmdecoder.config_flow.AdExt.open", + side_effect=Exception, + ), + patch("homeassistant.components.alarmdecoder.config_flow.AdExt.close"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], connection_settings ) diff --git a/tests/components/ambient_station/conftest.py b/tests/components/ambient_station/conftest.py index 25b11740db6..adbd6777727 100644 --- a/tests/components/ambient_station/conftest.py +++ b/tests/components/ambient_station/conftest.py @@ -53,10 +53,13 @@ def data_station_fixture(): @pytest.fixture(name="mock_aioambient") async def mock_aioambient_fixture(api): """Define a fixture to patch aioambient.""" - with patch( - "homeassistant.components.ambient_station.config_flow.API", - return_value=api, - ), patch("aioambient.websocket.Websocket.connect"): + with ( + patch( + "homeassistant.components.ambient_station.config_flow.API", + return_value=api, + ), + patch("aioambient.websocket.Websocket.connect"), + ): yield diff --git a/tests/components/analytics/test_analytics.py b/tests/components/analytics/test_analytics.py index 7c380d7bbd3..da8d45d41ad 100644 --- a/tests/components/analytics/test_analytics.py +++ b/tests/components/analytics/test_analytics.py @@ -91,12 +91,15 @@ async def test_load_with_supervisor_diagnostics(hass: HomeAssistant) -> None: """Test loading with a supervisor that has diagnostics enabled.""" analytics = Analytics(hass) assert not analytics.preferences[ATTR_DIAGNOSTICS] - with patch( - "homeassistant.components.hassio.get_supervisor_info", - side_effect=Mock(return_value={"diagnostics": True}), - ), patch( - "homeassistant.components.hassio.is_hassio", - side_effect=Mock(return_value=True), + with ( + patch( + "homeassistant.components.hassio.get_supervisor_info", + side_effect=Mock(return_value={"diagnostics": True}), + ), + patch( + "homeassistant.components.hassio.is_hassio", + side_effect=Mock(return_value=True), + ), ): await analytics.load() assert analytics.preferences[ATTR_DIAGNOSTICS] @@ -109,12 +112,15 @@ async def test_load_with_supervisor_without_diagnostics(hass: HomeAssistant) -> assert analytics.preferences[ATTR_DIAGNOSTICS] - with patch( - "homeassistant.components.hassio.get_supervisor_info", - side_effect=Mock(return_value={"diagnostics": False}), - ), patch( - "homeassistant.components.hassio.is_hassio", - side_effect=Mock(return_value=True), + with ( + patch( + "homeassistant.components.hassio.get_supervisor_info", + side_effect=Mock(return_value={"diagnostics": False}), + ), + patch( + "homeassistant.components.hassio.is_hassio", + side_effect=Mock(return_value=True), + ), ): await analytics.load() @@ -190,23 +196,29 @@ async def test_send_base_with_supervisor( await analytics.save_preferences({ATTR_BASE: True}) assert analytics.preferences[ATTR_BASE] - with patch( - "homeassistant.components.hassio.get_supervisor_info", - side_effect=Mock( - return_value={"supported": True, "healthy": True, "arch": "amd64"} + with ( + patch( + "homeassistant.components.hassio.get_supervisor_info", + side_effect=Mock( + return_value={"supported": True, "healthy": True, "arch": "amd64"} + ), + ), + patch( + "homeassistant.components.hassio.get_os_info", + side_effect=Mock(return_value={"board": "blue", "version": "123"}), + ), + patch( + "homeassistant.components.hassio.get_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.get_host_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.is_hassio", + side_effect=Mock(return_value=True), ), - ), patch( - "homeassistant.components.hassio.get_os_info", - side_effect=Mock(return_value={"board": "blue", "version": "123"}), - ), patch( - "homeassistant.components.hassio.get_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.get_host_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.is_hassio", - side_effect=Mock(return_value=True), ): await analytics.load() @@ -270,38 +282,45 @@ async def test_send_usage_with_supervisor( assert analytics.preferences[ATTR_USAGE] hass.config.components = ["default_config"] - with patch( - "homeassistant.components.hassio.get_supervisor_info", - side_effect=Mock( - return_value={ - "healthy": True, - "supported": True, - "arch": "amd64", - "addons": [{"slug": "test_addon"}], - } + with ( + patch( + "homeassistant.components.hassio.get_supervisor_info", + side_effect=Mock( + return_value={ + "healthy": True, + "supported": True, + "arch": "amd64", + "addons": [{"slug": "test_addon"}], + } + ), ), - ), patch( - "homeassistant.components.hassio.get_os_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.get_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.get_host_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.async_get_addon_info", - side_effect=AsyncMock( - return_value={ - "slug": "test_addon", - "protected": True, - "version": "1", - "auto_update": False, - } + patch( + "homeassistant.components.hassio.get_os_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.get_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.get_host_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.async_get_addon_info", + side_effect=AsyncMock( + return_value={ + "slug": "test_addon", + "protected": True, + "version": "1", + "auto_update": False, + } + ), + ), + patch( + "homeassistant.components.hassio.is_hassio", + side_effect=Mock(return_value=True), ), - ), patch( - "homeassistant.components.hassio.is_hassio", - side_effect=Mock(return_value=True), ): await analytics.send_analytics() @@ -461,9 +480,12 @@ async def test_send_statistics_async_get_integration_unknown_exception( assert analytics.preferences[ATTR_STATISTICS] hass.config.components = ["default_config"] - with pytest.raises(ValueError), patch( - "homeassistant.components.analytics.analytics.async_get_integrations", - return_value={"any": ValueError()}, + with ( + pytest.raises(ValueError), + patch( + "homeassistant.components.analytics.analytics.async_get_integrations", + return_value={"any": ValueError()}, + ), ): await analytics.send_analytics() @@ -482,38 +504,45 @@ async def test_send_statistics_with_supervisor( assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_STATISTICS] - with patch( - "homeassistant.components.hassio.get_supervisor_info", - side_effect=Mock( - return_value={ - "healthy": True, - "supported": True, - "arch": "amd64", - "addons": [{"slug": "test_addon"}], - } + with ( + patch( + "homeassistant.components.hassio.get_supervisor_info", + side_effect=Mock( + return_value={ + "healthy": True, + "supported": True, + "arch": "amd64", + "addons": [{"slug": "test_addon"}], + } + ), ), - ), patch( - "homeassistant.components.hassio.get_os_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.get_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.get_host_info", - side_effect=Mock(return_value={}), - ), patch( - "homeassistant.components.hassio.async_get_addon_info", - side_effect=AsyncMock( - return_value={ - "slug": "test_addon", - "protected": True, - "version": "1", - "auto_update": False, - } + patch( + "homeassistant.components.hassio.get_os_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.get_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.get_host_info", + side_effect=Mock(return_value={}), + ), + patch( + "homeassistant.components.hassio.async_get_addon_info", + side_effect=AsyncMock( + return_value={ + "slug": "test_addon", + "protected": True, + "version": "1", + "auto_update": False, + } + ), + ), + patch( + "homeassistant.components.hassio.is_hassio", + side_effect=Mock(return_value=True), ), - ), patch( - "homeassistant.components.hassio.is_hassio", - side_effect=Mock(return_value=True), ): await analytics.send_analytics() @@ -641,12 +670,16 @@ async def test_send_with_no_energy( await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True}) - with patch( - "homeassistant.components.analytics.analytics.energy_is_configured", AsyncMock() - ) as energy_is_configured, patch( - "homeassistant.components.analytics.analytics.get_recorder_instance", - Mock(), - ) as get_recorder_instance: + with ( + patch( + "homeassistant.components.analytics.analytics.energy_is_configured", + AsyncMock(), + ) as energy_is_configured, + patch( + "homeassistant.components.analytics.analytics.get_recorder_instance", + Mock(), + ) as get_recorder_instance, + ): energy_is_configured.return_value = False get_recorder_instance.return_value = Mock(database_engine=Mock()) await analytics.send_analytics() @@ -849,21 +882,24 @@ async def test_not_check_config_entries_if_yaml( ) mock_config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.analytics.analytics.async_get_integrations", - return_value={ - "default_config": mock_integration( - hass, - MockModule( - "default_config", - async_setup=AsyncMock(return_value=True), - partial_manifest={"config_flow": True}, + with ( + patch( + "homeassistant.components.analytics.analytics.async_get_integrations", + return_value={ + "default_config": mock_integration( + hass, + MockModule( + "default_config", + async_setup=AsyncMock(return_value=True), + partial_manifest={"config_flow": True}, + ), ), - ), - }, - ), patch( - "homeassistant.config.load_yaml_config_file", - return_value={"default_config": {}}, + }, + ), + patch( + "homeassistant.config.load_yaml_config_file", + return_value={"default_config": {}}, + ), ): await analytics.send_analytics() diff --git a/tests/components/analytics_insights/conftest.py b/tests/components/analytics_insights/conftest.py index f5f811c6d26..03bd24faeea 100644 --- a/tests/components/analytics_insights/conftest.py +++ b/tests/components/analytics_insights/conftest.py @@ -29,12 +29,15 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_analytics_client() -> Generator[AsyncMock, None, None]: """Mock a Homeassistant Analytics client.""" - with patch( - "homeassistant.components.analytics_insights.HomeassistantAnalyticsClient", - autospec=True, - ) as mock_client, patch( - "homeassistant.components.analytics_insights.config_flow.HomeassistantAnalyticsClient", - new=mock_client, + with ( + patch( + "homeassistant.components.analytics_insights.HomeassistantAnalyticsClient", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.analytics_insights.config_flow.HomeassistantAnalyticsClient", + new=mock_client, + ), ): client = mock_client.return_value client.get_current_analytics.return_value = CurrentAnalytics.from_json( diff --git a/tests/components/androidtv/test_config_flow.py b/tests/components/androidtv/test_config_flow.py index 7946ba4db99..afebe9903ce 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -108,10 +108,13 @@ async def test_user( assert flow_result["step_id"] == "user" # test with all provided - with patch( - CONNECT_METHOD, - return_value=(MockConfigDevice(eth_mac, wifi_mac), None), - ), PATCH_SETUP_ENTRY as mock_setup_entry: + with ( + patch( + CONNECT_METHOD, + return_value=(MockConfigDevice(eth_mac, wifi_mac), None), + ), + PATCH_SETUP_ENTRY as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( flow_result["flow_id"], user_input=config ) @@ -129,10 +132,15 @@ async def test_user_adbkey(hass: HomeAssistant) -> None: config_data = CONFIG_PYTHON_ADB.copy() config_data[CONF_ADBKEY] = ADBKEY - with patch( - CONNECT_METHOD, - return_value=(MockConfigDevice(), None), - ), PATCH_ISFILE, PATCH_ACCESS, PATCH_SETUP_ENTRY as mock_setup_entry: + with ( + patch( + CONNECT_METHOD, + return_value=(MockConfigDevice(), None), + ), + PATCH_ISFILE, + PATCH_ACCESS, + PATCH_SETUP_ENTRY as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER, "show_advanced_options": True}, @@ -161,10 +169,13 @@ async def test_error_both_key_server(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {"base": "key_and_server"} - with patch( - CONNECT_METHOD, - return_value=(MockConfigDevice(), None), - ), PATCH_SETUP_ENTRY: + with ( + patch( + CONNECT_METHOD, + return_value=(MockConfigDevice(), None), + ), + PATCH_SETUP_ENTRY, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=CONFIG_ADB_SERVER ) @@ -188,10 +199,13 @@ async def test_error_invalid_key(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {"base": "adbkey_not_file"} - with patch( - CONNECT_METHOD, - return_value=(MockConfigDevice(), None), - ), PATCH_SETUP_ENTRY: + with ( + patch( + CONNECT_METHOD, + return_value=(MockConfigDevice(), None), + ), + PATCH_SETUP_ENTRY, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=CONFIG_ADB_SERVER ) @@ -299,10 +313,13 @@ async def test_on_connect_failed(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] == {"base": "unknown"} - with patch( - CONNECT_METHOD, - return_value=(MockConfigDevice(), None), - ), PATCH_SETUP_ENTRY: + with ( + patch( + CONNECT_METHOD, + return_value=(MockConfigDevice(), None), + ), + PATCH_SETUP_ENTRY, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], user_input=CONFIG_ADB_SERVER ) diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index f09e21dd056..63923a57996 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -235,9 +235,10 @@ async def test_reconnect( patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -249,9 +250,10 @@ async def test_reconnect( caplog.clear() caplog.set_level(logging.WARNING) - with patchers.patch_connect(False)[patch_key], patchers.patch_shell(error=True)[ - patch_key - ]: + with ( + patchers.patch_connect(False)[patch_key], + patchers.patch_shell(error=True)[patch_key], + ): for _ in range(5): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) @@ -263,9 +265,11 @@ async def test_reconnect( assert caplog.record_tuples[1][1] == logging.WARNING caplog.set_level(logging.DEBUG) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_STANDBY - )[patch_key], patchers.PATCH_SCREENCAP: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key], + patchers.PATCH_SCREENCAP, + ): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) @@ -293,9 +297,10 @@ async def test_adb_shell_returns_none( patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -304,9 +309,10 @@ async def test_adb_shell_returns_none( assert state is not None assert state.state != STATE_UNAVAILABLE - with patchers.patch_shell(None)[patch_key], patchers.patch_shell(error=True)[ - patch_key - ]: + with ( + patchers.patch_shell(None)[patch_key], + patchers.patch_shell(error=True)[patch_key], + ): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -318,9 +324,11 @@ async def test_setup_with_adbkey(hass: HomeAssistant) -> None: patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_PYTHON_ADB_KEY) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key], patchers.PATCH_ISFILE: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + patchers.PATCH_ISFILE, + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -348,9 +356,10 @@ async def test_sources(hass: HomeAssistant, config: dict[str, Any]) -> None: config_entry.add_to_hass(hass) hass.config_entries.async_update_entry(config_entry, options={CONF_APPS: conf_apps}) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -418,9 +427,10 @@ async def test_exclude_sources( config_entry, options={CONF_EXCLUDE_UNNAMED_APPS: True, CONF_APPS: conf_apps} ) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -462,9 +472,10 @@ async def _test_select_source( config_entry.add_to_hass(hass) hass.config_entries.async_update_entry(config_entry, options={CONF_APPS: conf_apps}) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -569,9 +580,12 @@ async def test_setup_fail( patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.patch_connect(connect)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF, error=True, exc=AdbShellTimeoutException - )[patch_key]: + with ( + patchers.patch_connect(connect)[patch_key], + patchers.patch_shell( + SHELL_RESPONSE_OFF, error=True, exc=AdbShellTimeoutException + )[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) is False await hass.async_block_till_done() @@ -588,9 +602,10 @@ async def test_adb_command(hass: HomeAssistant) -> None: command = "test command" response = "test response" - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -617,9 +632,10 @@ async def test_adb_command_unicode_decode_error(hass: HomeAssistant) -> None: command = "test command" response = b"test response" - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -646,9 +662,10 @@ async def test_adb_command_key(hass: HomeAssistant) -> None: command = "HOME" response = None - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -675,9 +692,10 @@ async def test_adb_command_get_properties(hass: HomeAssistant) -> None: command = "GET_PROPERTIES" response = {"test key": "test value"} - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -704,9 +722,10 @@ async def test_learn_sendevent(hass: HomeAssistant) -> None: config_entry.add_to_hass(hass) response = "sendevent 1 2 3 4" - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -732,9 +751,10 @@ async def test_update_lock_not_acquired(hass: HomeAssistant) -> None: patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -744,18 +764,22 @@ async def test_update_lock_not_acquired(hass: HomeAssistant) -> None: assert state is not None assert state.state == STATE_OFF - with patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", - side_effect=LockNotAcquiredException, - ), patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: + with ( + patch( + "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", + side_effect=LockNotAcquiredException, + ), + patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key], + ): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF - with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[ - patch_key - ], patchers.PATCH_SCREENCAP: + with ( + patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key], + patchers.PATCH_SCREENCAP, + ): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -769,9 +793,10 @@ async def test_download(hass: HomeAssistant) -> None: device_path = "device/path" local_path = "local/path" - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -790,10 +815,9 @@ async def test_download(hass: HomeAssistant) -> None: patch_pull.assert_not_called() # Successful download - with patch( - "androidtv.basetv.basetv_async.BaseTVAsync.adb_pull" - ) as patch_pull, patch.object( - hass.config, "is_allowed_path", return_value=True + with ( + patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_pull") as patch_pull, + patch.object(hass.config, "is_allowed_path", return_value=True), ): await hass.services.async_call( DOMAIN, @@ -815,9 +839,10 @@ async def test_upload(hass: HomeAssistant) -> None: device_path = "device/path" local_path = "local/path" - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -836,10 +861,9 @@ async def test_upload(hass: HomeAssistant) -> None: patch_push.assert_not_called() # Successful upload - with patch( - "androidtv.basetv.basetv_async.BaseTVAsync.adb_push" - ) as patch_push, patch.object( - hass.config, "is_allowed_path", return_value=True + with ( + patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_push") as patch_push, + patch.object(hass.config, "is_allowed_path", return_value=True), ): await hass.services.async_call( DOMAIN, @@ -859,9 +883,10 @@ async def test_androidtv_volume_set(hass: HomeAssistant) -> None: patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -888,15 +913,17 @@ async def test_get_image_http( patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patchers.patch_shell("11")[ - patch_key - ], patchers.PATCH_SCREENCAP as patch_screen_cap: + with ( + patchers.patch_shell("11")[patch_key], + patchers.PATCH_SCREENCAP as patch_screen_cap, + ): await async_update_entity(hass, entity_id) patch_screen_cap.assert_called() @@ -913,20 +940,20 @@ async def test_get_image_http( assert content == b"image" next_update = utcnow() + timedelta(seconds=30) - with patchers.patch_shell("11")[ - patch_key - ], patchers.PATCH_SCREENCAP as patch_screen_cap, patch( - "homeassistant.util.utcnow", return_value=next_update + with ( + patchers.patch_shell("11")[patch_key], + patchers.PATCH_SCREENCAP as patch_screen_cap, + patch("homeassistant.util.utcnow", return_value=next_update), ): async_fire_time_changed(hass, next_update, True) await hass.async_block_till_done() patch_screen_cap.assert_not_called() next_update = utcnow() + timedelta(seconds=60) - with patchers.patch_shell("11")[ - patch_key - ], patchers.PATCH_SCREENCAP as patch_screen_cap, patch( - "homeassistant.util.utcnow", return_value=next_update + with ( + patchers.patch_shell("11")[patch_key], + patchers.PATCH_SCREENCAP as patch_screen_cap, + patch("homeassistant.util.utcnow", return_value=next_update), ): async_fire_time_changed(hass, next_update, True) await hass.async_block_till_done() @@ -939,15 +966,19 @@ async def test_get_image_http_fail(hass: HomeAssistant) -> None: patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patchers.patch_shell("11")[patch_key], patch( - "androidtv.basetv.basetv_async.BaseTVAsync.adb_screencap", - side_effect=ConnectionResetError, + with ( + patchers.patch_shell("11")[patch_key], + patch( + "androidtv.basetv.basetv_async.BaseTVAsync.adb_screencap", + side_effect=ConnectionResetError, + ), ): await async_update_entity(hass, entity_id) @@ -968,9 +999,10 @@ async def test_get_image_disabled(hass: HomeAssistant) -> None: config_entry, options={CONF_SCREENCAP: False} ) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1025,9 +1057,10 @@ async def test_services_androidtv(hass: HomeAssistant) -> None: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[ - patch_key - ], patchers.PATCH_SCREENCAP: + with ( + patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key], + patchers.PATCH_SCREENCAP, + ): await _test_service( hass, entity_id, SERVICE_MEDIA_NEXT_TRACK, "media_next_track" ) @@ -1075,9 +1108,10 @@ async def test_services_firetv(hass: HomeAssistant) -> None: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[ - patch_key - ], patchers.PATCH_SCREENCAP: + with ( + patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key], + patchers.PATCH_SCREENCAP, + ): await _test_service(hass, entity_id, SERVICE_MEDIA_STOP, "back") await _test_service(hass, entity_id, SERVICE_TURN_OFF, "adb_shell") await _test_service(hass, entity_id, SERVICE_TURN_ON, "adb_shell") @@ -1093,9 +1127,10 @@ async def test_volume_mute(hass: HomeAssistant) -> None: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[ - patch_key - ], patchers.PATCH_SCREENCAP: + with ( + patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key], + patchers.PATCH_SCREENCAP, + ): service_data = {ATTR_ENTITY_ID: entity_id, ATTR_MEDIA_VOLUME_MUTED: True} with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.mute_volume", @@ -1133,9 +1168,10 @@ async def test_connection_closed_on_ha_stop(hass: HomeAssistant) -> None: patch_key, _, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1153,9 +1189,10 @@ async def test_exception(hass: HomeAssistant) -> None: patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1183,9 +1220,10 @@ async def test_options_reload(hass: HomeAssistant) -> None: patch_key, entity_id, config_entry = _setup(CONFIG_ANDROID_DEFAULT) config_entry.add_to_hass(hass) - with patchers.patch_connect(True)[patch_key], patchers.patch_shell( - SHELL_RESPONSE_OFF - )[patch_key]: + with ( + patchers.patch_connect(True)[patch_key], + patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key], + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/anova/__init__.py b/tests/components/anova/__init__.py index fb4c578ba1b..03cfb7589d0 100644 --- a/tests/components/anova/__init__.py +++ b/tests/components/anova/__init__.py @@ -47,13 +47,15 @@ async def async_init_integration( error: str | None = None, ) -> ConfigEntry: """Set up the Anova integration in Home Assistant.""" - with patch( - "homeassistant.components.anova.coordinator.AnovaPrecisionCooker.update" - ) as update_patch, patch( - "homeassistant.components.anova.AnovaApi.authenticate" - ), patch( - "homeassistant.components.anova.AnovaApi.get_devices", - ) as device_patch: + with ( + patch( + "homeassistant.components.anova.coordinator.AnovaPrecisionCooker.update" + ) as update_patch, + patch("homeassistant.components.anova.AnovaApi.authenticate"), + patch( + "homeassistant.components.anova.AnovaApi.get_devices", + ) as device_patch, + ): update_patch.return_value = ONLINE_UPDATE device_patch.return_value = [ AnovaPrecisionCooker(None, DEVICE_UNIQUE_ID, "type_sample", None) diff --git a/tests/components/anova/test_config_flow.py b/tests/components/anova/test_config_flow.py index d1255876137..6ea988dc53a 100644 --- a/tests/components/anova/test_config_flow.py +++ b/tests/components/anova/test_config_flow.py @@ -16,15 +16,16 @@ async def test_flow_user( hass: HomeAssistant, ) -> None: """Test user initialized flow.""" - with patch( - "homeassistant.components.anova.config_flow.AnovaApi.authenticate", - ) as auth_patch, patch( - "homeassistant.components.anova.AnovaApi.get_devices" - ) as device_patch, patch( - "homeassistant.components.anova.AnovaApi.authenticate" - ), patch( - "homeassistant.components.anova.config_flow.AnovaApi.get_devices" - ) as config_flow_device_patch: + with ( + patch( + "homeassistant.components.anova.config_flow.AnovaApi.authenticate", + ) as auth_patch, + patch("homeassistant.components.anova.AnovaApi.get_devices") as device_patch, + patch("homeassistant.components.anova.AnovaApi.authenticate"), + patch( + "homeassistant.components.anova.config_flow.AnovaApi.get_devices" + ) as config_flow_device_patch, + ): auth_patch.return_value = True device_patch.return_value = [ AnovaPrecisionCooker(None, DEVICE_UNIQUE_ID, "type_sample", None) @@ -50,13 +51,15 @@ async def test_flow_user( async def test_flow_user_already_configured(hass: HomeAssistant) -> None: """Test user initialized flow with duplicate device.""" - with patch( - "homeassistant.components.anova.config_flow.AnovaApi.authenticate", - ) as auth_patch, patch( - "homeassistant.components.anova.AnovaApi.get_devices" - ) as device_patch, patch( - "homeassistant.components.anova.config_flow.AnovaApi.get_devices" - ) as config_flow_device_patch: + with ( + patch( + "homeassistant.components.anova.config_flow.AnovaApi.authenticate", + ) as auth_patch, + patch("homeassistant.components.anova.AnovaApi.get_devices") as device_patch, + patch( + "homeassistant.components.anova.config_flow.AnovaApi.get_devices" + ) as config_flow_device_patch, + ): auth_patch.return_value = True device_patch.return_value = [ AnovaPrecisionCooker(None, DEVICE_UNIQUE_ID, "type_sample", None) @@ -115,11 +118,12 @@ async def test_flow_unknown_error(hass: HomeAssistant) -> None: async def test_flow_no_devices(hass: HomeAssistant) -> None: """Test unknown error throwing error.""" - with patch( - "homeassistant.components.anova.config_flow.AnovaApi.authenticate" - ), patch( - "homeassistant.components.anova.config_flow.AnovaApi.get_devices", - side_effect=NoDevicesFound(), + with ( + patch("homeassistant.components.anova.config_flow.AnovaApi.authenticate"), + patch( + "homeassistant.components.anova.config_flow.AnovaApi.get_devices", + side_effect=NoDevicesFound(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/aosmith/test_config_flow.py b/tests/components/aosmith/test_config_flow.py index b981047c926..32f259f552c 100644 --- a/tests/components/aosmith/test_config_flow.py +++ b/tests/components/aosmith/test_config_flow.py @@ -124,13 +124,17 @@ async def test_reauth_flow( assert len(flows) == 1 assert flows[0]["step_id"] == "reauth_confirm" - with patch( - "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices", - return_value=[], - ), patch( - "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_energy_use_data", - return_value=[], - ), patch("homeassistant.components.aosmith.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices", + return_value=[], + ), + patch( + "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_energy_use_data", + return_value=[], + ), + patch("homeassistant.components.aosmith.async_setup_entry", return_value=True), + ): result2 = await hass.config_entries.flow.async_configure( flows[0]["flow_id"], {CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD]}, @@ -178,10 +182,13 @@ async def test_reauth_flow_retry( assert result2["errors"] == {"base": "invalid_auth"} # Second attempt at reauth - authentication succeeds - with patch( - "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices", - return_value=[], - ), patch("homeassistant.components.aosmith.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices", + return_value=[], + ), + patch("homeassistant.components.aosmith.async_setup_entry", return_value=True), + ): result3 = await hass.config_entries.flow.async_configure( flows[0]["flow_id"], {CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD]}, diff --git a/tests/components/aosmith/test_init.py b/tests/components/aosmith/test_init.py index 7e081686790..940b0cbc6b5 100644 --- a/tests/components/aosmith/test_init.py +++ b/tests/components/aosmith/test_init.py @@ -59,12 +59,15 @@ async def test_config_entry_not_ready_get_energy_use_data_error( ) ] - with patch( - "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices", - return_value=get_devices_fixture, - ), patch( - "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_energy_use_data", - side_effect=AOSmithUnknownException("Unknown error"), + with ( + patch( + "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_devices", + return_value=get_devices_fixture, + ), + patch( + "homeassistant.components.aosmith.config_flow.AOSmithAPIClient.get_energy_use_data", + side_effect=AOSmithUnknownException("Unknown error"), + ), ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/apache_kafka/test_init.py b/tests/components/apache_kafka/test_init.py index 2a7a92b773b..93d9619f0c3 100644 --- a/tests/components/apache_kafka/test_init.py +++ b/tests/components/apache_kafka/test_init.py @@ -43,9 +43,11 @@ class MockKafkaClient: @pytest.fixture(name="mock_client") def mock_client_fixture(): """Mock the apache kafka client.""" - with patch(f"{PRODUCER_PATH}.start") as start, patch( - f"{PRODUCER_PATH}.send_and_wait" - ) as send_and_wait, patch(f"{PRODUCER_PATH}.__init__", return_value=None) as init: + with ( + patch(f"{PRODUCER_PATH}.start") as start, + patch(f"{PRODUCER_PATH}.send_and_wait") as send_and_wait, + patch(f"{PRODUCER_PATH}.__init__", return_value=None) as init, + ): yield MockKafkaClient(init, start, send_and_wait) diff --git a/tests/components/apcupsd/test_diagnostics.py b/tests/components/apcupsd/test_diagnostics.py index 56249a7823b..5dfce28a989 100644 --- a/tests/components/apcupsd/test_diagnostics.py +++ b/tests/components/apcupsd/test_diagnostics.py @@ -1,4 +1,5 @@ """Test APCUPSd diagnostics reporting abilities.""" + from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index c2b11ccb043..7d37d7a5d99 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -34,12 +34,15 @@ async def test_apprise_config_load_fail02(hass: HomeAssistant) -> None: BASE_COMPONENT: {"name": "test", "platform": "apprise", "config": "/path/"} } - with patch( - "homeassistant.components.apprise.notify.apprise.Apprise.add", - return_value=False, - ), patch( - "homeassistant.components.apprise.notify.apprise.AppriseConfig.add", - return_value=True, + with ( + patch( + "homeassistant.components.apprise.notify.apprise.Apprise.add", + return_value=False, + ), + patch( + "homeassistant.components.apprise.notify.apprise.AppriseConfig.add", + return_value=True, + ), ): assert await async_setup_component(hass, BASE_COMPONENT, config) await hass.async_block_till_done() diff --git a/tests/components/arcam_fmj/conftest.py b/tests/components/arcam_fmj/conftest.py index 7c42fb9e0ff..f5a9ab6315a 100644 --- a/tests/components/arcam_fmj/conftest.py +++ b/tests/components/arcam_fmj/conftest.py @@ -98,9 +98,14 @@ async def player_setup_fixture(hass, state_1, state_2, client): await async_setup_component(hass, "homeassistant", {}) - with patch("homeassistant.components.arcam_fmj.Client", return_value=client), patch( - "homeassistant.components.arcam_fmj.media_player.State", side_effect=state_mock - ), patch("homeassistant.components.arcam_fmj._run_client", return_value=None): + with ( + patch("homeassistant.components.arcam_fmj.Client", return_value=client), + patch( + "homeassistant.components.arcam_fmj.media_player.State", + side_effect=state_mock, + ), + patch("homeassistant.components.arcam_fmj._run_client", return_value=None), + ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() yield MOCK_ENTITY_ID diff --git a/tests/components/aseko_pool_live/test_config_flow.py b/tests/components/aseko_pool_live/test_config_flow.py index 4ebcf0e0c7d..2566dfd61e6 100644 --- a/tests/components/aseko_pool_live/test_config_flow.py +++ b/tests/components/aseko_pool_live/test_config_flow.py @@ -29,13 +29,16 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", - return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), - ), patch( - "homeassistant.components.aseko_pool_live.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", + return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), + ), + patch( + "homeassistant.components.aseko_pool_live.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/assist_pipeline/test_pipeline.py b/tests/components/assist_pipeline/test_pipeline.py index 40b701588a0..3bfe6605839 100644 --- a/tests/components/assist_pipeline/test_pipeline.py +++ b/tests/components/assist_pipeline/test_pipeline.py @@ -401,9 +401,10 @@ async def test_default_pipeline( hass.config.country = ha_country hass.config.language = ha_language - with patch.object( - mock_stt_provider, "_supported_languages", MANY_LANGUAGES - ), patch.object(mock_tts_provider, "_supported_languages", MANY_LANGUAGES): + with ( + patch.object(mock_stt_provider, "_supported_languages", MANY_LANGUAGES), + patch.object(mock_tts_provider, "_supported_languages", MANY_LANGUAGES), + ): assert await async_setup_component(hass, "assist_pipeline", {}) pipeline_data: PipelineData = hass.data[DOMAIN] diff --git a/tests/components/assist_pipeline/test_websocket.py b/tests/components/assist_pipeline/test_websocket.py index 612bf760bd0..0883046f3a1 100644 --- a/tests/components/assist_pipeline/test_websocket.py +++ b/tests/components/assist_pipeline/test_websocket.py @@ -381,12 +381,15 @@ async def test_audio_pipeline_no_wake_word_entity( """Test timeout from a pipeline run with audio input/output + wake word.""" client = await hass_ws_client(hass) - with patch( - "homeassistant.components.wake_word.async_default_entity", - return_value="wake_word.bad-entity-id", - ), patch( - "homeassistant.components.wake_word.async_get_wake_word_detection_entity", - return_value=None, + with ( + patch( + "homeassistant.components.wake_word.async_default_entity", + return_value="wake_word.bad-entity-id", + ), + patch( + "homeassistant.components.wake_word.async_get_wake_word_detection_entity", + return_value=None, + ), ): await client.send_json_auto_id( { @@ -1700,15 +1703,19 @@ async def test_list_pipeline_languages_with_aliases( """Test listing pipeline languages using aliases.""" client = await hass_ws_client(hass) - with patch( - "homeassistant.components.conversation.async_get_conversation_languages", - return_value={"he", "nb"}, - ), patch( - "homeassistant.components.stt.async_get_speech_to_text_languages", - return_value={"he", "no"}, - ), patch( - "homeassistant.components.tts.async_get_text_to_speech_languages", - return_value={"iw", "nb"}, + with ( + patch( + "homeassistant.components.conversation.async_get_conversation_languages", + return_value={"he", "nb"}, + ), + patch( + "homeassistant.components.stt.async_get_speech_to_text_languages", + return_value={"he", "no"}, + ), + patch( + "homeassistant.components.tts.async_get_text_to_speech_languages", + return_value={"iw", "nb"}, + ), ): await client.send_json_auto_id({"type": "assist_pipeline/language/list"}) diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index b819565d4bf..75145df2509 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -76,8 +76,9 @@ async def _mock_setup_august( options={}, ) entry.add_to_hass(hass) - with patch("homeassistant.components.august.async_create_pubnub"), patch( - "homeassistant.components.august.AugustPubNub", return_value=pubnub_mock + with ( + patch("homeassistant.components.august.async_create_pubnub"), + patch("homeassistant.components.august.AugustPubNub", return_value=pubnub_mock), ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index 3eb87c2b096..e1e6f622c2e 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -34,13 +34,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ), patch( - "homeassistant.components.august.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), + patch( + "homeassistant.components.august.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -142,13 +145,16 @@ async def test_form_needs_validate(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - side_effect=RequireValidation, - ), patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", - return_value=True, - ) as mock_send_verification_code: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + side_effect=RequireValidation, + ), + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -164,16 +170,20 @@ async def test_form_needs_validate(hass: HomeAssistant) -> None: assert result2["step_id"] == "validation" # Try with the WRONG verification code give us the form back again - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - side_effect=RequireValidation, - ), patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", - return_value=ValidationResult.INVALID_VERIFICATION_CODE, - ) as mock_validate_verification_code, patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", - return_value=True, - ) as mock_send_verification_code: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + side_effect=RequireValidation, + ), + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", + return_value=ValidationResult.INVALID_VERIFICATION_CODE, + ) as mock_validate_verification_code, + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {VERIFICATION_CODE_KEY: "incorrect"}, @@ -188,18 +198,23 @@ async def test_form_needs_validate(hass: HomeAssistant) -> None: assert result3["step_id"] == "validation" # Try with the CORRECT verification code and we setup - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ), patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", - return_value=ValidationResult.VALIDATED, - ) as mock_validate_verification_code, patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", - return_value=True, - ) as mock_send_verification_code, patch( - "homeassistant.components.august.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", + return_value=ValidationResult.VALIDATED, + ) as mock_validate_verification_code, + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, + patch( + "homeassistant.components.august.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], {VERIFICATION_CODE_KEY: "correct"}, @@ -243,13 +258,16 @@ async def test_form_reauth(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ), patch( - "homeassistant.components.august.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), + patch( + "homeassistant.components.august.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -286,13 +304,16 @@ async def test_form_reauth_with_2fa(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - side_effect=RequireValidation, - ), patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", - return_value=True, - ) as mock_send_verification_code: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + side_effect=RequireValidation, + ), + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -307,18 +328,23 @@ async def test_form_reauth_with_2fa(hass: HomeAssistant) -> None: assert result2["step_id"] == "validation" # Try with the CORRECT verification code and we setup - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ), patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", - return_value=ValidationResult.VALIDATED, - ) as mock_validate_verification_code, patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", - return_value=True, - ) as mock_send_verification_code, patch( - "homeassistant.components.august.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_validate_verification_code", + return_value=ValidationResult.VALIDATED, + ) as mock_validate_verification_code, + patch( + "homeassistant.components.august.gateway.AuthenticatorAsync.async_send_verification_code", + return_value=True, + ) as mock_send_verification_code, + patch( + "homeassistant.components.august.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {VERIFICATION_CODE_KEY: "correct"}, @@ -354,13 +380,16 @@ async def test_switching_brands(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", - return_value=True, - ), patch( - "homeassistant.components.august.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.august.config_flow.AugustGateway.async_authenticate", + return_value=True, + ), + patch( + "homeassistant.components.august.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/aurora/test_config_flow.py b/tests/components/aurora/test_config_flow.py index 087c4f55ee0..d8f42e48842 100644 --- a/tests/components/aurora/test_config_flow.py +++ b/tests/components/aurora/test_config_flow.py @@ -25,13 +25,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.aurora.config_flow.AuroraForecast.get_forecast_data", - return_value=True, - ), patch( - "homeassistant.components.aurora.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.aurora.config_flow.AuroraForecast.get_forecast_data", + return_value=True, + ), + patch( + "homeassistant.components.aurora.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], DATA, diff --git a/tests/components/aurora_abb_powerone/test_config_flow.py b/tests/components/aurora_abb_powerone/test_config_flow.py index 6b7a227ed04..fbeaff2f4f8 100644 --- a/tests/components/aurora_abb_powerone/test_config_flow.py +++ b/tests/components/aurora_abb_powerone/test_config_flow.py @@ -33,25 +33,32 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "aurorapy.client.AuroraSerialClient.connect", - return_value=None, - ), patch( - "aurorapy.client.AuroraSerialClient.serial_number", - return_value="9876543", - ), patch( - "aurorapy.client.AuroraSerialClient.version", - return_value="9.8.7.6", - ), patch( - "aurorapy.client.AuroraSerialClient.pn", - return_value="A.B.C", - ), patch( - "aurorapy.client.AuroraSerialClient.firmware", - return_value="1.234", - ) as mock_setup, patch( - "homeassistant.components.aurora_abb_powerone.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "aurorapy.client.AuroraSerialClient.connect", + return_value=None, + ), + patch( + "aurorapy.client.AuroraSerialClient.serial_number", + return_value="9876543", + ), + patch( + "aurorapy.client.AuroraSerialClient.version", + return_value="9.8.7.6", + ), + patch( + "aurorapy.client.AuroraSerialClient.pn", + return_value="A.B.C", + ), + patch( + "aurorapy.client.AuroraSerialClient.firmware", + return_value="1.234", + ) as mock_setup, + patch( + "homeassistant.components.aurora_abb_powerone.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_PORT: "/dev/ttyUSB7", CONF_ADDRESS: 7}, @@ -135,16 +142,20 @@ async def test_form_invalid_com_ports(hass: HomeAssistant) -> None: ) assert result2["errors"] == {"base": "cannot_connect"} - with patch( - "aurorapy.client.AuroraSerialClient.connect", - side_effect=AuroraError("...Some other message!!!123..."), - return_value=None, - ), patch( - "serial.Serial.isOpen", - return_value=True, - ), patch( - "aurorapy.client.AuroraSerialClient.close", - ) as mock_clientclose: + with ( + patch( + "aurorapy.client.AuroraSerialClient.connect", + side_effect=AuroraError("...Some other message!!!123..."), + return_value=None, + ), + patch( + "serial.Serial.isOpen", + return_value=True, + ), + patch( + "aurorapy.client.AuroraSerialClient.close", + ) as mock_clientclose, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_PORT: "/dev/ttyUSB7", CONF_ADDRESS: 7}, diff --git a/tests/components/aurora_abb_powerone/test_init.py b/tests/components/aurora_abb_powerone/test_init.py index 811a0c84f3d..18b9854773e 100644 --- a/tests/components/aurora_abb_powerone/test_init.py +++ b/tests/components/aurora_abb_powerone/test_init.py @@ -17,18 +17,24 @@ from tests.common import MockConfigEntry async def test_unload_entry(hass: HomeAssistant) -> None: """Test unloading the aurora_abb_powerone entry.""" - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.serial_number", - return_value="9876543", - ), patch( - "aurorapy.client.AuroraSerialClient.version", - return_value="9.8.7.6", - ), patch( - "aurorapy.client.AuroraSerialClient.pn", - return_value="A.B.C", - ), patch( - "aurorapy.client.AuroraSerialClient.firmware", - return_value="1.234", + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.serial_number", + return_value="9876543", + ), + patch( + "aurorapy.client.AuroraSerialClient.version", + return_value="9.8.7.6", + ), + patch( + "aurorapy.client.AuroraSerialClient.pn", + return_value="A.B.C", + ), + patch( + "aurorapy.client.AuroraSerialClient.firmware", + return_value="1.234", + ), ): mock_entry = MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index 9a8b21cdbd3..178cf165f67 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -68,26 +68,33 @@ async def test_sensors(hass: HomeAssistant, entity_registry: EntityRegistry) -> """Test data coming back from inverter.""" mock_entry = _mock_config_entry() - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", - side_effect=_simulated_returns, - ), patch( - "aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"] - ), patch( - "aurorapy.client.AuroraSerialClient.serial_number", - return_value="9876543", - ), patch( - "aurorapy.client.AuroraSerialClient.version", - return_value="9.8.7.6", - ), patch( - "aurorapy.client.AuroraSerialClient.pn", - return_value="A.B.C", - ), patch( - "aurorapy.client.AuroraSerialClient.firmware", - return_value="1.234", - ), patch( - "aurorapy.client.AuroraSerialClient.cumulated_energy", - side_effect=_simulated_returns, + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", + side_effect=_simulated_returns, + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + patch( + "aurorapy.client.AuroraSerialClient.serial_number", + return_value="9876543", + ), + patch( + "aurorapy.client.AuroraSerialClient.version", + return_value="9.8.7.6", + ), + patch( + "aurorapy.client.AuroraSerialClient.pn", + return_value="A.B.C", + ), + patch( + "aurorapy.client.AuroraSerialClient.firmware", + return_value="1.234", + ), + patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=_simulated_returns, + ), ): mock_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_entry.entry_id) @@ -144,25 +151,32 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) mock_entry = _mock_config_entry() # sun is up - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns - ), patch( - "aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"] - ), patch( - "aurorapy.client.AuroraSerialClient.cumulated_energy", - side_effect=_simulated_returns, - ), patch( - "aurorapy.client.AuroraSerialClient.serial_number", - return_value="9876543", - ), patch( - "aurorapy.client.AuroraSerialClient.version", - return_value="9.8.7.6", - ), patch( - "aurorapy.client.AuroraSerialClient.pn", - return_value="A.B.C", - ), patch( - "aurorapy.client.AuroraSerialClient.firmware", - return_value="1.234", + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=_simulated_returns, + ), + patch( + "aurorapy.client.AuroraSerialClient.serial_number", + return_value="9876543", + ), + patch( + "aurorapy.client.AuroraSerialClient.version", + return_value="9.8.7.6", + ), + patch( + "aurorapy.client.AuroraSerialClient.pn", + return_value="A.B.C", + ), + patch( + "aurorapy.client.AuroraSerialClient.firmware", + return_value="1.234", + ), ): mock_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_entry.entry_id) @@ -173,25 +187,35 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) assert power.state == "45.7" # sunset - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", - side_effect=AuroraTimeoutError("No response after 10 seconds"), - ), patch( - "aurorapy.client.AuroraSerialClient.cumulated_energy", - side_effect=AuroraTimeoutError("No response after 3 tries"), - ), patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]): + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", + side_effect=AuroraTimeoutError("No response after 10 seconds"), + ), + patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=AuroraTimeoutError("No response after 3 tries"), + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + ): freezer.tick(SCAN_INTERVAL * 2) async_fire_time_changed(hass) await hass.async_block_till_done() power = hass.states.get("sensor.mydevicename_total_energy") assert power.state == "unknown" # sun rose again - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns - ), patch( - "aurorapy.client.AuroraSerialClient.cumulated_energy", - side_effect=_simulated_returns, - ), patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]): + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns + ), + patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=_simulated_returns, + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + ): freezer.tick(SCAN_INTERVAL * 4) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -199,13 +223,18 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) assert power is not None assert power.state == "45.7" # sunset - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", - side_effect=AuroraTimeoutError("No response after 10 seconds"), - ), patch( - "aurorapy.client.AuroraSerialClient.cumulated_energy", - side_effect=AuroraError("No response after 10 seconds"), - ), patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]): + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", + side_effect=AuroraTimeoutError("No response after 10 seconds"), + ), + patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=AuroraError("No response after 10 seconds"), + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + ): freezer.tick(SCAN_INTERVAL * 6) async_fire_time_changed(hass) await hass.async_block_till_done() @@ -222,24 +251,30 @@ async def test_sensor_unknown_error( mock_entry = _mock_config_entry() # sun is up - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns - ), patch( - "aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"] - ), patch( - "aurorapy.client.AuroraSerialClient.cumulated_energy", - side_effect=_simulated_returns, + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", side_effect=_simulated_returns + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + patch( + "aurorapy.client.AuroraSerialClient.cumulated_energy", + side_effect=_simulated_returns, + ), ): mock_entry.add_to_hass(hass) await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() - with patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), patch( - "aurorapy.client.AuroraSerialClient.measure", - side_effect=AuroraError("another error"), - ), patch( - "aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"] - ), patch("serial.Serial.isOpen", return_value=True): + with ( + patch("aurorapy.client.AuroraSerialClient.connect", return_value=None), + patch( + "aurorapy.client.AuroraSerialClient.measure", + side_effect=AuroraError("another error"), + ), + patch("aurorapy.client.AuroraSerialClient.alarms", return_value=["No alarm"]), + patch("serial.Serial.isOpen", return_value=True), + ): freezer.tick(SCAN_INTERVAL * 2) async_fire_time_changed(hass) await hass.async_block_till_done() diff --git a/tests/components/aussie_broadband/common.py b/tests/components/aussie_broadband/common.py index 1ad56ab47f6..1c992d116d1 100644 --- a/tests/components/aussie_broadband/common.py +++ b/tests/components/aussie_broadband/common.py @@ -50,20 +50,24 @@ async def setup_platform( ) mock_entry.add_to_hass(hass) - with patch("homeassistant.components.aussie_broadband.PLATFORMS", platforms), patch( - "aussiebb.asyncio.AussieBB.__init__", return_value=None - ), patch( - "aussiebb.asyncio.AussieBB.login", - return_value=True, - side_effect=side_effect, - ), patch( - "aussiebb.asyncio.AussieBB.get_services", - return_value=FAKE_SERVICES, - side_effect=side_effect, - ), patch( - "aussiebb.asyncio.AussieBB.get_usage", - return_value=usage, - side_effect=usage_effect, + with ( + patch("homeassistant.components.aussie_broadband.PLATFORMS", platforms), + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch( + "aussiebb.asyncio.AussieBB.login", + return_value=True, + side_effect=side_effect, + ), + patch( + "aussiebb.asyncio.AussieBB.get_services", + return_value=FAKE_SERVICES, + side_effect=side_effect, + ), + patch( + "aussiebb.asyncio.AussieBB.get_usage", + return_value=usage, + side_effect=usage_effect, + ), ): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/aussie_broadband/test_config_flow.py b/tests/components/aussie_broadband/test_config_flow.py index 37839e92cdd..f08b56502b8 100644 --- a/tests/components/aussie_broadband/test_config_flow.py +++ b/tests/components/aussie_broadband/test_config_flow.py @@ -25,14 +25,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result1["type"] == FlowResultType.FORM assert result1["errors"] is None - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch( - "aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES - ), patch( - "homeassistant.components.aussie_broadband.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", return_value=True), + patch("aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES), + patch( + "homeassistant.components.aussie_broadband.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], FAKE_DATA, @@ -52,14 +53,17 @@ async def test_already_configured(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch( - "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] - ), patch( - "homeassistant.components.aussie_broadband.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", return_value=True), + patch( + "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] + ), + patch( + "homeassistant.components.aussie_broadband.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.config_entries.flow.async_configure( result1["flow_id"], FAKE_DATA, @@ -70,14 +74,17 @@ async def test_already_configured(hass: HomeAssistant) -> None: result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch( - "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] - ), patch( - "homeassistant.components.aussie_broadband.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", return_value=True), + patch( + "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] + ), + patch( + "homeassistant.components.aussie_broadband.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], FAKE_DATA, @@ -96,12 +103,15 @@ async def test_no_services(hass: HomeAssistant) -> None: assert result1["type"] == FlowResultType.FORM assert result1["errors"] is None - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch("aussiebb.asyncio.AussieBB.get_services", return_value=[]), patch( - "homeassistant.components.aussie_broadband.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", return_value=True), + patch("aussiebb.asyncio.AussieBB.get_services", return_value=[]), + patch( + "homeassistant.components.aussie_broadband.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], FAKE_DATA, @@ -119,8 +129,9 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException() + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException()), ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], @@ -137,8 +148,9 @@ async def test_form_network_issue(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", side_effect=ClientConnectionError() + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", side_effect=ClientConnectionError()), ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], @@ -157,13 +169,16 @@ async def test_reauth(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=FAKE_DATA ) - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch( - "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] - ), patch( - "homeassistant.components.aussie_broadband.async_setup_entry", - return_value=True, + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", return_value=True), + patch( + "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] + ), + patch( + "homeassistant.components.aussie_broadband.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], @@ -185,9 +200,13 @@ async def test_reauth(hass: HomeAssistant) -> None: ) assert result5["step_id"] == "reauth_confirm" - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException() - ), patch("aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]): + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException()), + patch( + "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] + ), + ): result6 = await hass.config_entries.flow.async_configure( result5["flow_id"], { @@ -199,9 +218,13 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result6["step_id"] == "reauth_confirm" # Test successful reauth - with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch( - "aussiebb.asyncio.AussieBB.login", return_value=True - ), patch("aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]): + with ( + patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), + patch("aussiebb.asyncio.AussieBB.login", return_value=True), + patch( + "aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]] + ), + ): result7 = await hass.config_entries.flow.async_configure( result6["flow_id"], { diff --git a/tests/components/auth/test_login_flow.py b/tests/components/auth/test_login_flow.py index 59c066a4f85..af9a2cf62f1 100644 --- a/tests/components/auth/test_login_flow.py +++ b/tests/components/auth/test_login_flow.py @@ -219,11 +219,15 @@ async def test_invalid_redirect_uri( assert resp.status == HTTPStatus.OK step = await resp.json() - with patch( - "homeassistant.components.auth.indieauth.fetch_redirect_uris", return_value=[] - ), patch( - "homeassistant.components.http.ban.process_wrong_login" - ) as mock_process_wrong_login: + with ( + patch( + "homeassistant.components.auth.indieauth.fetch_redirect_uris", + return_value=[], + ), + patch( + "homeassistant.components.http.ban.process_wrong_login" + ) as mock_process_wrong_login, + ): resp = await client.post( f"/auth/login_flow/{step['flow_id']}", json={ diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 234fc912118..00a7e6980d7 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1102,14 +1102,17 @@ async def test_reload_automation_when_blueprint_changes( blueprint_config["action"] = [blueprint_config["action"]] blueprint_config["action"].append(blueprint_config["action"][-1]) - with patch( - "homeassistant.config.load_yaml_config_file", - autospec=True, - return_value=config, - ), patch( - "homeassistant.components.blueprint.models.yaml.load_yaml_dict", - autospec=True, - return_value=blueprint_config, + with ( + patch( + "homeassistant.config.load_yaml_config_file", + autospec=True, + return_value=config, + ), + patch( + "homeassistant.components.blueprint.models.yaml.load_yaml_dict", + autospec=True, + return_value=blueprint_config, + ), ): await hass.services.async_call( automation.DOMAIN, SERVICE_RELOAD, blocking=True diff --git a/tests/components/awair/test_config_flow.py b/tests/components/awair/test_config_flow.py index d03dde0d867..c8b9ea262a8 100644 --- a/tests/components/awair/test_config_flow.py +++ b/tests/components/awair/test_config_flow.py @@ -155,10 +155,13 @@ async def test_reauth(hass: HomeAssistant, user, cloud_devices) -> None: assert result["step_id"] == "reauth_confirm" assert result["errors"] == {CONF_ACCESS_TOKEN: "invalid_access_token"} - with patch( - "python_awair.AwairClient.query", - side_effect=[user, cloud_devices], - ), patch("homeassistant.components.awair.async_setup_entry", return_value=True): + with ( + patch( + "python_awair.AwairClient.query", + side_effect=[user, cloud_devices], + ), + patch("homeassistant.components.awair.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=CLOUD_CONFIG, @@ -199,12 +202,15 @@ async def test_reauth_error(hass: HomeAssistant) -> None: async def test_create_cloud_entry(hass: HomeAssistant, user, cloud_devices) -> None: """Test overall flow when using cloud api.""" - with patch( - "python_awair.AwairClient.query", - side_effect=[user, cloud_devices], - ), patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, + with ( + patch( + "python_awair.AwairClient.query", + side_effect=[user, cloud_devices], + ), + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ), ): menu_step = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CLOUD_CONFIG @@ -229,9 +235,12 @@ async def test_create_cloud_entry(hass: HomeAssistant, user, cloud_devices) -> N async def test_create_local_entry(hass: HomeAssistant, local_devices) -> None: """Test overall flow when using local API.""" - with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, + with ( + patch("python_awair.AwairClient.query", side_effect=[local_devices]), + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ), ): menu_step = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=LOCAL_CONFIG @@ -287,9 +296,12 @@ async def test_create_local_entry_from_discovery( {}, ) - with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, + with ( + patch("python_awair.AwairClient.query", side_effect=[local_devices]), + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( form_step["flow_id"], @@ -337,9 +349,12 @@ async def test_create_local_entry_awair_error(hass: HomeAssistant) -> None: async def test_create_zeroconf_entry(hass: HomeAssistant, local_devices) -> None: """Test overall flow when using discovery.""" - with patch("python_awair.AwairClient.query", side_effect=[local_devices]), patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, + with ( + patch("python_awair.AwairClient.query", side_effect=[local_devices]), + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ), ): confirm_step = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=ZEROCONF_DISCOVERY @@ -382,14 +397,16 @@ async def test_zeroconf_discovery_update_configuration( ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "python_awair.AwairClient.query", side_effect=[local_devices] - ), patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch("python_awair.AwairClient.query", side_effect=[local_devices]), + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -408,14 +425,16 @@ async def test_zeroconf_during_onboarding( hass: HomeAssistant, local_devices: Any ) -> None: """Test the zeroconf creates an entry during onboarding.""" - with patch( - "homeassistant.components.awair.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "python_awair.AwairClient.query", side_effect=[local_devices] - ), patch( - "homeassistant.components.onboarding.async_is_onboarded", - return_value=False, + with ( + patch( + "homeassistant.components.awair.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch("python_awair.AwairClient.query", side_effect=[local_devices]), + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=ZEROCONF_DISCOVERY diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py index 9cf8966483b..9589ad6c037 100644 --- a/tests/components/aws/test_init.py +++ b/tests/components/aws/test_init.py @@ -161,10 +161,13 @@ async def test_access_key_credential(hass: HomeAssistant) -> None: async def test_notify_credential(hass: HomeAssistant) -> None: """Test notify service can use access key directly.""" mock_session = MockAioSession() - with async_patch( - "homeassistant.components.aws.AioSession", return_value=mock_session - ), async_patch( - "homeassistant.components.aws.notify.AioSession", return_value=mock_session + with ( + async_patch( + "homeassistant.components.aws.AioSession", return_value=mock_session + ), + async_patch( + "homeassistant.components.aws.notify.AioSession", return_value=mock_session + ), ): await async_setup_component( hass, @@ -195,10 +198,13 @@ async def test_notify_credential(hass: HomeAssistant) -> None: async def test_notify_credential_profile(hass: HomeAssistant) -> None: """Test notify service can use profile directly.""" mock_session = MockAioSession() - with async_patch( - "homeassistant.components.aws.AioSession", return_value=mock_session - ), async_patch( - "homeassistant.components.aws.notify.AioSession", return_value=mock_session + with ( + async_patch( + "homeassistant.components.aws.AioSession", return_value=mock_session + ), + async_patch( + "homeassistant.components.aws.notify.AioSession", return_value=mock_session + ), ): await async_setup_component( hass, diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index d166aaed733..3291f88d90a 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -187,9 +187,12 @@ async def test_device_trigger_reauth_flow( hass: HomeAssistant, config_entry, setup_default_vapix_requests ) -> None: """Failed authentication trigger a reauthentication flow.""" - with patch.object( - axis, "get_axis_api", side_effect=axis.errors.AuthenticationRequired - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + with ( + patch.object( + axis, "get_axis_api", side_effect=axis.errors.AuthenticationRequired + ), + patch.object(hass.config_entries.flow, "async_init") as mock_flow_init, + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() mock_flow_init.assert_called_once() @@ -223,9 +226,12 @@ async def test_shutdown(config_entry_data) -> None: async def test_get_device_fails(hass: HomeAssistant, config_entry_data) -> None: """Device unauthorized yields authentication required error.""" - with patch( - "axis.interfaces.vapix.Vapix.initialize", side_effect=axislib.Unauthorized - ), pytest.raises(axis.errors.AuthenticationRequired): + with ( + patch( + "axis.interfaces.vapix.Vapix.initialize", side_effect=axislib.Unauthorized + ), + pytest.raises(axis.errors.AuthenticationRequired), + ): await axis.hub.get_axis_api(hass, config_entry_data) @@ -233,15 +239,17 @@ async def test_get_device_device_unavailable( hass: HomeAssistant, config_entry_data ) -> None: """Device unavailable yields cannot connect error.""" - with patch( - "axis.interfaces.vapix.Vapix.request", side_effect=axislib.RequestError - ), pytest.raises(axis.errors.CannotConnect): + with ( + patch("axis.interfaces.vapix.Vapix.request", side_effect=axislib.RequestError), + pytest.raises(axis.errors.CannotConnect), + ): await axis.hub.get_axis_api(hass, config_entry_data) async def test_get_device_unknown_error(hass: HomeAssistant, config_entry_data) -> None: """Device yield unknown error.""" - with patch( - "axis.interfaces.vapix.Vapix.request", side_effect=axislib.AxisException - ), pytest.raises(axis.errors.AuthenticationRequired): + with ( + patch("axis.interfaces.vapix.Vapix.request", side_effect=axislib.AxisException), + pytest.raises(axis.errors.AuthenticationRequired), + ): await axis.hub.get_axis_api(hass, config_entry_data) diff --git a/tests/components/axis/test_light.py b/tests/components/axis/test_light.py index 95956450c9e..5cde6b74fc4 100644 --- a/tests/components/axis/test_light.py +++ b/tests/components/axis/test_light.py @@ -149,11 +149,12 @@ async def test_lights( assert light_0.name == f"{NAME} IR Light 0" # Turn on, set brightness, light already on - with patch( - "axis.interfaces.vapix.LightHandler.activate_light" - ) as mock_activate, patch( - "axis.interfaces.vapix.LightHandler.set_manual_intensity" - ) as mock_set_intensity: + with ( + patch("axis.interfaces.vapix.LightHandler.activate_light") as mock_activate, + patch( + "axis.interfaces.vapix.LightHandler.set_manual_intensity" + ) as mock_set_intensity, + ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -189,11 +190,12 @@ async def test_lights( assert light_0.state == STATE_OFF # Turn on, set brightness - with patch( - "axis.interfaces.vapix.LightHandler.activate_light" - ) as mock_activate, patch( - "axis.interfaces.vapix.LightHandler.set_manual_intensity" - ) as mock_set_intensity: + with ( + patch("axis.interfaces.vapix.LightHandler.activate_light") as mock_activate, + patch( + "axis.interfaces.vapix.LightHandler.set_manual_intensity" + ) as mock_set_intensity, + ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, diff --git a/tests/components/azure_devops/test_config_flow.py b/tests/components/azure_devops/test_config_flow.py index 08856fccb17..84c2b5d3cca 100644 --- a/tests/components/azure_devops/test_config_flow.py +++ b/tests/components/azure_devops/test_config_flow.py @@ -134,14 +134,18 @@ async def test_reauth_connection_error(hass: HomeAssistant) -> None: async def test_project_error(hass: HomeAssistant) -> None: """Test we show user form on Azure DevOps connection error.""" - with patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", - return_value=True, - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", - return_value=None, + with ( + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", + return_value=True, + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", + return_value=None, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -163,14 +167,18 @@ async def test_project_error(hass: HomeAssistant) -> None: async def test_reauth_project_error(hass: HomeAssistant) -> None: """Test we show user form on Azure DevOps project error.""" - with patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", - return_value=True, - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", - return_value=None, + with ( + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", + return_value=True, + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", + return_value=None, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -213,15 +221,19 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["step_id"] == "reauth" assert result["errors"] == {"base": "invalid_auth"} - with patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", - return_value=True, - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", - return_value=DevOpsProject( - "abcd-abcd-abcd-abcd", FIXTURE_USER_INPUT[CONF_PROJECT] + with ( + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", + return_value=True, + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", + return_value=DevOpsProject( + "abcd-abcd-abcd-abcd", FIXTURE_USER_INPUT[CONF_PROJECT] + ), ), ): result2 = await hass.config_entries.flow.async_configure( @@ -236,18 +248,23 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: async def test_full_flow_implementation(hass: HomeAssistant) -> None: """Test registering an integration and finishing flow works.""" - with patch( - "homeassistant.components.azure_devops.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", - return_value=True, - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", - ), patch( - "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", - return_value=DevOpsProject( - "abcd-abcd-abcd-abcd", FIXTURE_USER_INPUT[CONF_PROJECT] + with ( + patch( + "homeassistant.components.azure_devops.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorized", + return_value=True, + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.authorize", + ), + patch( + "homeassistant.components.azure_devops.config_flow.DevOpsClient.get_project", + return_value=DevOpsProject( + "abcd-abcd-abcd-abcd", FIXTURE_USER_INPUT[CONF_PROJECT] + ), ), ): result = await hass.config_entries.flow.async_init( diff --git a/tests/components/backup/test_http.py b/tests/components/backup/test_http.py index 02e446f5b88..baf1798534a 100644 --- a/tests/components/backup/test_http.py +++ b/tests/components/backup/test_http.py @@ -21,12 +21,16 @@ async def test_downloading_backup( client = await hass_client() - with patch( - "homeassistant.components.backup.http.BackupManager.get_backup", - return_value=TEST_BACKUP, - ), patch("pathlib.Path.exists", return_value=True), patch( - "homeassistant.components.backup.http.FileResponse", - return_value=web.Response(text=""), + with ( + patch( + "homeassistant.components.backup.http.BackupManager.get_backup", + return_value=TEST_BACKUP, + ), + patch("pathlib.Path.exists", return_value=True), + patch( + "homeassistant.components.backup.http.FileResponse", + return_value=web.Response(text=""), + ), ): resp = await client.get("/api/backup/download/abc123") assert resp.status == 200 diff --git a/tests/components/backup/test_manager.py b/tests/components/backup/test_manager.py index 516f8fc1bc4..41749298819 100644 --- a/tests/components/backup/test_manager.py +++ b/tests/components/backup/test_manager.py @@ -30,28 +30,37 @@ async def _mock_backup_generation(manager: BackupManager): Path(".storage"), ] - with patch( - "homeassistant.components.backup.manager.SecureTarFile" - ) as mocked_tarfile, patch("pathlib.Path.iterdir", _mock_iterdir), patch( - "pathlib.Path.stat", MagicMock(st_size=123) - ), patch("pathlib.Path.is_file", lambda x: x.name != ".storage"), patch( - "pathlib.Path.is_dir", - lambda x: x.name == ".storage", - ), patch( - "pathlib.Path.exists", - lambda x: x != manager.backup_dir, - ), patch( - "pathlib.Path.is_symlink", - lambda _: False, - ), patch( - "pathlib.Path.mkdir", - MagicMock(), - ), patch( - "homeassistant.components.backup.manager.json_bytes", - return_value=b"{}", # Empty JSON - ) as mocked_json_bytes, patch( - "homeassistant.components.backup.manager.HAVERSION", - "2025.1.0", + with ( + patch( + "homeassistant.components.backup.manager.SecureTarFile" + ) as mocked_tarfile, + patch("pathlib.Path.iterdir", _mock_iterdir), + patch("pathlib.Path.stat", MagicMock(st_size=123)), + patch("pathlib.Path.is_file", lambda x: x.name != ".storage"), + patch( + "pathlib.Path.is_dir", + lambda x: x.name == ".storage", + ), + patch( + "pathlib.Path.exists", + lambda x: x != manager.backup_dir, + ), + patch( + "pathlib.Path.is_symlink", + lambda _: False, + ), + patch( + "pathlib.Path.mkdir", + MagicMock(), + ), + patch( + "homeassistant.components.backup.manager.json_bytes", + return_value=b"{}", # Empty JSON + ) as mocked_json_bytes, + patch( + "homeassistant.components.backup.manager.HAVERSION", + "2025.1.0", + ), ): await manager.generate_backup() @@ -82,18 +91,21 @@ async def test_constructor(hass: HomeAssistant) -> None: async def test_load_backups(hass: HomeAssistant) -> None: """Test loading backups.""" manager = BackupManager(hass) - with patch("pathlib.Path.glob", return_value=[TEST_BACKUP.path]), patch( - "tarfile.open", return_value=MagicMock() - ), patch( - "homeassistant.components.backup.manager.json_loads_object", - return_value={ - "slug": TEST_BACKUP.slug, - "name": TEST_BACKUP.name, - "date": TEST_BACKUP.date, - }, - ), patch( - "pathlib.Path.stat", - return_value=MagicMock(st_size=TEST_BACKUP.size), + with ( + patch("pathlib.Path.glob", return_value=[TEST_BACKUP.path]), + patch("tarfile.open", return_value=MagicMock()), + patch( + "homeassistant.components.backup.manager.json_loads_object", + return_value={ + "slug": TEST_BACKUP.slug, + "name": TEST_BACKUP.name, + "date": TEST_BACKUP.date, + }, + ), + patch( + "pathlib.Path.stat", + return_value=MagicMock(st_size=TEST_BACKUP.size), + ), ): await manager.load_backups() backups = await manager.get_backups() @@ -106,8 +118,9 @@ async def test_load_backups_with_exception( ) -> None: """Test loading backups with exception.""" manager = BackupManager(hass) - with patch("pathlib.Path.glob", return_value=[TEST_BACKUP.path]), patch( - "tarfile.open", side_effect=OSError("Test exception") + with ( + patch("pathlib.Path.glob", return_value=[TEST_BACKUP.path]), + patch("tarfile.open", side_effect=OSError("Test exception")), ): await manager.load_backups() backups = await manager.get_backups() diff --git a/tests/components/baf/test_config_flow.py b/tests/components/baf/test_config_flow.py index 0369e1e1955..c7c56179839 100644 --- a/tests/components/baf/test_config_flow.py +++ b/tests/components/baf/test_config_flow.py @@ -33,10 +33,13 @@ async def test_form_user(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with _patch_device_config_flow(), patch( - "homeassistant.components.baf.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_device_config_flow(), + patch( + "homeassistant.components.baf.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_IP_ADDRESS: "127.0.0.1"}, @@ -182,10 +185,13 @@ async def test_user_flow_is_not_blocked_by_discovery(hass: HomeAssistant) -> Non assert result["type"] == "form" assert result["errors"] == {} - with _patch_device_config_flow(), patch( - "homeassistant.components.baf.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_device_config_flow(), + patch( + "homeassistant.components.baf.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_IP_ADDRESS: "127.0.0.1"}, diff --git a/tests/components/balboa/test_config_flow.py b/tests/components/balboa/test_config_flow.py index d2a8def9e63..66bc47d23f0 100644 --- a/tests/components/balboa/test_config_flow.py +++ b/tests/components/balboa/test_config_flow.py @@ -26,13 +26,16 @@ async def test_form(hass: HomeAssistant, client: MagicMock) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.balboa.config_flow.SpaClient.__aenter__", - return_value=client, - ), patch( - "homeassistant.components.balboa.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.balboa.config_flow.SpaClient.__aenter__", + return_value=client, + ), + patch( + "homeassistant.components.balboa.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_DATA, @@ -113,12 +116,15 @@ async def test_already_configured(hass: HomeAssistant, client: MagicMock) -> Non assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.balboa.config_flow.SpaClient.__aenter__", - return_value=client, - ), patch( - "homeassistant.components.balboa.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.balboa.config_flow.SpaClient.__aenter__", + return_value=client, + ), + patch( + "homeassistant.components.balboa.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/bang_olufsen/conftest.py b/tests/components/bang_olufsen/conftest.py index b7b6b84b7fd..d076316e36c 100644 --- a/tests/components/bang_olufsen/conftest.py +++ b/tests/components/bang_olufsen/conftest.py @@ -34,11 +34,14 @@ def mock_config_entry(): def mock_mozart_client() -> Generator[AsyncMock, None, None]: """Mock MozartClient.""" - with patch( - "homeassistant.components.bang_olufsen.MozartClient", autospec=True - ) as mock_client, patch( - "homeassistant.components.bang_olufsen.config_flow.MozartClient", - new=mock_client, + with ( + patch( + "homeassistant.components.bang_olufsen.MozartClient", autospec=True + ) as mock_client, + patch( + "homeassistant.components.bang_olufsen.config_flow.MozartClient", + new=mock_client, + ), ): client = mock_client.return_value client.get_beolink_self = AsyncMock() diff --git a/tests/components/blink/test_config_flow.py b/tests/components/blink/test_config_flow.py index 304f1bd97a9..b5fbf19ef9b 100644 --- a/tests/components/blink/test_config_flow.py +++ b/tests/components/blink/test_config_flow.py @@ -19,13 +19,17 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=False, - ), patch( - "homeassistant.components.blink.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=False, + ), + patch( + "homeassistant.components.blink.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "blink@example.com", "password": "example"}, @@ -55,9 +59,12 @@ async def test_form_2fa(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=True, + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -67,18 +74,24 @@ async def test_form_2fa(hass: HomeAssistant) -> None: assert result2["type"] == "form" assert result2["step_id"] == "2fa" - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=False, - ), patch( - "homeassistant.components.blink.config_flow.Auth.send_auth_key", - return_value=True, - ), patch( - "homeassistant.components.blink.config_flow.Blink.setup_urls", - return_value=True, - ), patch( - "homeassistant.components.blink.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=False, + ), + patch( + "homeassistant.components.blink.config_flow.Auth.send_auth_key", + return_value=True, + ), + patch( + "homeassistant.components.blink.config_flow.Blink.setup_urls", + return_value=True, + ), + patch( + "homeassistant.components.blink.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"pin": "1234"} ) @@ -97,9 +110,12 @@ async def test_form_2fa_connect_error(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=True, + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -109,18 +125,24 @@ async def test_form_2fa_connect_error(hass: HomeAssistant) -> None: assert result2["type"] == "form" assert result2["step_id"] == "2fa" - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=False, - ), patch( - "homeassistant.components.blink.config_flow.Auth.send_auth_key", - return_value=True, - ), patch( - "homeassistant.components.blink.config_flow.Blink.setup_urls", - side_effect=BlinkSetupError, - ), patch( - "homeassistant.components.blink.async_setup_entry", - return_value=True, + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=False, + ), + patch( + "homeassistant.components.blink.config_flow.Auth.send_auth_key", + return_value=True, + ), + patch( + "homeassistant.components.blink.config_flow.Blink.setup_urls", + side_effect=BlinkSetupError, + ), + patch( + "homeassistant.components.blink.async_setup_entry", + return_value=True, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"pin": "1234"} @@ -137,9 +159,12 @@ async def test_form_2fa_invalid_key(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=True, + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -149,20 +174,26 @@ async def test_form_2fa_invalid_key(hass: HomeAssistant) -> None: assert result2["type"] == "form" assert result2["step_id"] == "2fa" - with patch( - "homeassistant.components.blink.config_flow.Auth.startup", - ), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=False, - ), patch( - "homeassistant.components.blink.config_flow.Auth.send_auth_key", - return_value=False, - ), patch( - "homeassistant.components.blink.config_flow.Blink.setup_urls", - return_value=True, - ), patch( - "homeassistant.components.blink.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.blink.config_flow.Auth.startup", + ), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=False, + ), + patch( + "homeassistant.components.blink.config_flow.Auth.send_auth_key", + return_value=False, + ), + patch( + "homeassistant.components.blink.config_flow.Blink.setup_urls", + return_value=True, + ), + patch( + "homeassistant.components.blink.async_setup_entry", + return_value=True, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"pin": "1234"} @@ -179,9 +210,12 @@ async def test_form_2fa_unknown_error(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=True, + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -191,18 +225,24 @@ async def test_form_2fa_unknown_error(hass: HomeAssistant) -> None: assert result2["type"] == "form" assert result2["step_id"] == "2fa" - with patch("homeassistant.components.blink.config_flow.Auth.startup"), patch( - "homeassistant.components.blink.config_flow.Auth.check_key_required", - return_value=False, - ), patch( - "homeassistant.components.blink.config_flow.Auth.send_auth_key", - return_value=True, - ), patch( - "homeassistant.components.blink.config_flow.Blink.setup_urls", - side_effect=KeyError, - ), patch( - "homeassistant.components.blink.async_setup_entry", - return_value=True, + with ( + patch("homeassistant.components.blink.config_flow.Auth.startup"), + patch( + "homeassistant.components.blink.config_flow.Auth.check_key_required", + return_value=False, + ), + patch( + "homeassistant.components.blink.config_flow.Auth.send_auth_key", + return_value=True, + ), + patch( + "homeassistant.components.blink.config_flow.Blink.setup_urls", + side_effect=KeyError, + ), + patch( + "homeassistant.components.blink.async_setup_entry", + return_value=True, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"pin": "1234"} diff --git a/tests/components/blueprint/test_models.py b/tests/components/blueprint/test_models.py index 2bc6d60ccda..96e72e2b4cc 100644 --- a/tests/components/blueprint/test_models.py +++ b/tests/components/blueprint/test_models.py @@ -207,14 +207,18 @@ async def test_domain_blueprints_get_blueprint_errors( """Test domain blueprints.""" assert hass.data["blueprint"]["automation"] is domain_bps - with pytest.raises(errors.FailedToLoad), patch( - "homeassistant.util.yaml.load_yaml", side_effect=FileNotFoundError + with ( + pytest.raises(errors.FailedToLoad), + patch("homeassistant.util.yaml.load_yaml", side_effect=FileNotFoundError), ): await domain_bps.async_get_blueprint("non-existing-path") - with patch( - "homeassistant.util.yaml.load_yaml", return_value={"blueprint": "invalid"} - ), pytest.raises(errors.FailedToLoad): + with ( + patch( + "homeassistant.util.yaml.load_yaml", return_value={"blueprint": "invalid"} + ), + pytest.raises(errors.FailedToLoad), + ): await domain_bps.async_get_blueprint("non-existing-path") @@ -240,8 +244,9 @@ async def test_domain_blueprints_inputs_from_config(domain_bps, blueprint_1) -> with pytest.raises(errors.InvalidBlueprintInputs): await domain_bps.async_inputs_from_config({"not-referencing": "use_blueprint"}) - with pytest.raises(errors.MissingInput), patch.object( - domain_bps, "async_get_blueprint", return_value=blueprint_1 + with ( + pytest.raises(errors.MissingInput), + patch.object(domain_bps, "async_get_blueprint", return_value=blueprint_1), ): await domain_bps.async_inputs_from_config( {"use_blueprint": {"path": "bla.yaml", "input": {}}} diff --git a/tests/components/bluetooth/__init__.py b/tests/components/bluetooth/__init__.py index b6d7220da16..675f3de67ee 100644 --- a/tests/components/bluetooth/__init__.py +++ b/tests/components/bluetooth/__init__.py @@ -58,13 +58,14 @@ BLE_DEVICE_DEFAULTS = { @contextmanager def patch_bluetooth_time(mock_time: float) -> None: """Patch the bluetooth time.""" - with patch( - "homeassistant.components.bluetooth.MONOTONIC_TIME", return_value=mock_time - ), patch( - "habluetooth.base_scanner.monotonic_time_coarse", return_value=mock_time - ), patch( - "habluetooth.manager.monotonic_time_coarse", return_value=mock_time - ), patch("habluetooth.scanner.monotonic_time_coarse", return_value=mock_time): + with ( + patch( + "homeassistant.components.bluetooth.MONOTONIC_TIME", return_value=mock_time + ), + patch("habluetooth.base_scanner.monotonic_time_coarse", return_value=mock_time), + patch("habluetooth.manager.monotonic_time_coarse", return_value=mock_time), + patch("habluetooth.scanner.monotonic_time_coarse", return_value=mock_time), + ): yield diff --git a/tests/components/bluetooth/conftest.py b/tests/components/bluetooth/conftest.py index 3c23c7428fb..c1e040ccd49 100644 --- a/tests/components/bluetooth/conftest.py +++ b/tests/components/bluetooth/conftest.py @@ -32,18 +32,21 @@ def disable_bluetooth_auto_recovery(): @pytest.fixture(name="operating_system_85") def mock_operating_system_85(): """Mock running Home Assistant Operating system 8.5.""" - with patch("homeassistant.components.hassio.is_hassio", return_value=True), patch( - "homeassistant.components.hassio.get_os_info", - return_value={ - "version": "8.5", - "version_latest": "10.0.dev20220912", - "update_available": False, - "board": "odroid-n2", - "boot": "B", - "data_disk": "/dev/mmcblk1p4", - }, - ), patch("homeassistant.components.hassio.get_info", return_value={}), patch( - "homeassistant.components.hassio.get_host_info", return_value={} + with ( + patch("homeassistant.components.hassio.is_hassio", return_value=True), + patch( + "homeassistant.components.hassio.get_os_info", + return_value={ + "version": "8.5", + "version_latest": "10.0.dev20220912", + "update_available": False, + "board": "odroid-n2", + "boot": "B", + "data_disk": "/dev/mmcblk1p4", + }, + ), + patch("homeassistant.components.hassio.get_info", return_value={}), + patch("homeassistant.components.hassio.get_host_info", return_value={}), ): yield @@ -51,18 +54,21 @@ def mock_operating_system_85(): @pytest.fixture(name="operating_system_90") def mock_operating_system_90(): """Mock running Home Assistant Operating system 9.0.""" - with patch("homeassistant.components.hassio.is_hassio", return_value=True), patch( - "homeassistant.components.hassio.get_os_info", - return_value={ - "version": "9.0.dev20220912", - "version_latest": "10.0.dev20220912", - "update_available": False, - "board": "odroid-n2", - "boot": "B", - "data_disk": "/dev/mmcblk1p4", - }, - ), patch("homeassistant.components.hassio.get_info", return_value={}), patch( - "homeassistant.components.hassio.get_host_info", return_value={} + with ( + patch("homeassistant.components.hassio.is_hassio", return_value=True), + patch( + "homeassistant.components.hassio.get_os_info", + return_value={ + "version": "9.0.dev20220912", + "version_latest": "10.0.dev20220912", + "update_available": False, + "board": "odroid-n2", + "boot": "B", + "data_disk": "/dev/mmcblk1p4", + }, + ), + patch("homeassistant.components.hassio.get_info", return_value={}), + patch("homeassistant.components.hassio.get_host_info", return_value={}), ): yield @@ -70,46 +76,62 @@ def mock_operating_system_90(): @pytest.fixture(name="macos_adapter") def macos_adapter(): """Fixture that mocks the macos adapter.""" - with patch("bleak.get_platform_scanner_backend_type"), patch( - "homeassistant.components.bluetooth.platform.system", - return_value="Darwin", - ), patch( - "habluetooth.scanner.platform.system", - return_value="Darwin", - ), patch( - "bluetooth_adapters.systems.platform.system", - return_value="Darwin", - ), patch("habluetooth.scanner.SYSTEM", "Darwin"): + with ( + patch("bleak.get_platform_scanner_backend_type"), + patch( + "homeassistant.components.bluetooth.platform.system", + return_value="Darwin", + ), + patch( + "habluetooth.scanner.platform.system", + return_value="Darwin", + ), + patch( + "bluetooth_adapters.systems.platform.system", + return_value="Darwin", + ), + patch("habluetooth.scanner.SYSTEM", "Darwin"), + ): yield @pytest.fixture(name="windows_adapter") def windows_adapter(): """Fixture that mocks the windows adapter.""" - with patch( - "bluetooth_adapters.systems.platform.system", - return_value="Windows", - ), patch("habluetooth.scanner.SYSTEM", "Windows"): + with ( + patch( + "bluetooth_adapters.systems.platform.system", + return_value="Windows", + ), + patch("habluetooth.scanner.SYSTEM", "Windows"), + ): yield @pytest.fixture(name="no_adapters") def no_adapter_fixture(): """Fixture that mocks no adapters on Linux.""" - with patch( - "homeassistant.components.bluetooth.platform.system", - return_value="Linux", - ), patch( - "habluetooth.scanner.platform.system", - return_value="Linux", - ), patch( - "bluetooth_adapters.systems.platform.system", - return_value="Linux", - ), patch("habluetooth.scanner.SYSTEM", "Linux"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.refresh", - ), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - {}, + with ( + patch( + "homeassistant.components.bluetooth.platform.system", + return_value="Linux", + ), + patch( + "habluetooth.scanner.platform.system", + return_value="Linux", + ), + patch( + "bluetooth_adapters.systems.platform.system", + return_value="Linux", + ), + patch("habluetooth.scanner.SYSTEM", "Linux"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.refresh", + ), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + {}, + ), ): yield @@ -117,31 +139,38 @@ def no_adapter_fixture(): @pytest.fixture(name="one_adapter") def one_adapter_fixture(): """Fixture that mocks one adapter on Linux.""" - with patch( - "homeassistant.components.bluetooth.platform.system", - return_value="Linux", - ), patch( - "habluetooth.scanner.platform.system", - return_value="Linux", - ), patch( - "bluetooth_adapters.systems.platform.system", - return_value="Linux", - ), patch("habluetooth.scanner.SYSTEM", "Linux"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.refresh", - ), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - { - "hci0": { - "address": "00:00:00:00:00:01", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": True, - "sw_version": "homeassistant", - "manufacturer": "ACME", - "product": "Bluetooth Adapter 5.0", - "product_id": "aa01", - "vendor_id": "cc01", + with ( + patch( + "homeassistant.components.bluetooth.platform.system", + return_value="Linux", + ), + patch( + "habluetooth.scanner.platform.system", + return_value="Linux", + ), + patch( + "bluetooth_adapters.systems.platform.system", + return_value="Linux", + ), + patch("habluetooth.scanner.SYSTEM", "Linux"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.refresh", + ), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + { + "hci0": { + "address": "00:00:00:00:00:01", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": True, + "sw_version": "homeassistant", + "manufacturer": "ACME", + "product": "Bluetooth Adapter 5.0", + "product_id": "aa01", + "vendor_id": "cc01", + }, }, - }, + ), ): yield @@ -149,39 +178,43 @@ def one_adapter_fixture(): @pytest.fixture(name="two_adapters") def two_adapters_fixture(): """Fixture that mocks two adapters on Linux.""" - with patch( - "homeassistant.components.bluetooth.platform.system", return_value="Linux" - ), patch( - "habluetooth.scanner.platform.system", - return_value="Linux", - ), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.refresh" - ), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - { - "hci0": { - "address": "00:00:00:00:00:01", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", - "manufacturer": "ACME", - "product": "Bluetooth Adapter 5.0", - "product_id": "aa01", - "vendor_id": "cc01", - "connection_slots": 1, + with ( + patch( + "homeassistant.components.bluetooth.platform.system", return_value="Linux" + ), + patch( + "habluetooth.scanner.platform.system", + return_value="Linux", + ), + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + { + "hci0": { + "address": "00:00:00:00:00:01", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + "manufacturer": "ACME", + "product": "Bluetooth Adapter 5.0", + "product_id": "aa01", + "vendor_id": "cc01", + "connection_slots": 1, + }, + "hci1": { + "address": "00:00:00:00:00:02", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": True, + "sw_version": "homeassistant", + "manufacturer": "ACME", + "product": "Bluetooth Adapter 5.0", + "product_id": "aa01", + "vendor_id": "cc01", + "connection_slots": 2, + }, }, - "hci1": { - "address": "00:00:00:00:00:02", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": True, - "sw_version": "homeassistant", - "manufacturer": "ACME", - "product": "Bluetooth Adapter 5.0", - "product_id": "aa01", - "vendor_id": "cc01", - "connection_slots": 2, - }, - }, + ), ): yield @@ -189,27 +222,31 @@ def two_adapters_fixture(): @pytest.fixture(name="one_adapter_old_bluez") def one_adapter_old_bluez(): """Fixture that mocks two adapters on Linux.""" - with patch( - "homeassistant.components.bluetooth.platform.system", return_value="Linux" - ), patch( - "habluetooth.scanner.platform.system", - return_value="Linux", - ), patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.refresh" - ), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - { - "hci0": { - "address": "00:00:00:00:00:01", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", - "manufacturer": "ACME", - "product": "Bluetooth Adapter 5.0", - "product_id": "aa01", - "vendor_id": "cc01", + with ( + patch( + "homeassistant.components.bluetooth.platform.system", return_value="Linux" + ), + patch( + "habluetooth.scanner.platform.system", + return_value="Linux", + ), + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + { + "hci0": { + "address": "00:00:00:00:00:01", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + "manufacturer": "ACME", + "product": "Bluetooth Adapter 5.0", + "product_id": "aa01", + "vendor_id": "cc01", + }, }, - }, + ), ): yield diff --git a/tests/components/bluetooth/test_base_scanner.py b/tests/components/bluetooth/test_base_scanner.py index cd3cea5c33a..0839c9c56a4 100644 --- a/tests/components/bluetooth/test_base_scanner.py +++ b/tests/components/bluetooth/test_base_scanner.py @@ -348,11 +348,14 @@ async def test_restore_history_remote_adapter( if address != "E3:A5:63:3E:5E:23": timestamps[address] = now - with patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.history", - {}, - ), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.refresh", + with ( + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.history", + {}, + ), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.refresh", + ), ): assert await async_setup_component(hass, bluetooth.DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/bluetooth/test_config_flow.py b/tests/components/bluetooth/test_config_flow.py index bc8e93d6b38..9ca674e2d32 100644 --- a/tests/components/bluetooth/test_config_flow.py +++ b/tests/components/bluetooth/test_config_flow.py @@ -55,11 +55,12 @@ async def test_async_step_user_macos(hass: HomeAssistant, macos_adapter: None) - ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "single_adapter" - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) @@ -80,11 +81,12 @@ async def test_async_step_user_linux_one_adapter( ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "single_adapter" - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) @@ -105,11 +107,12 @@ async def test_async_step_user_linux_two_adapters( ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "multiple_adapters" - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_ADAPTER: "hci1"} ) @@ -151,11 +154,12 @@ async def test_async_step_integration_discovery(hass: HomeAssistant) -> None: ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "single_adapter" - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) @@ -176,14 +180,16 @@ async def test_async_step_integration_discovery_during_onboarding_one_adapter( manufacturer="ACME", ) - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.onboarding.async_is_onboarded", - return_value=False, - ) as mock_onboarding: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, @@ -213,14 +219,16 @@ async def test_async_step_integration_discovery_during_onboarding_two_adapters( manufacturer="ACME", ) - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.onboarding.async_is_onboarded", - return_value=False, - ) as mock_onboarding: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, @@ -254,14 +262,16 @@ async def test_async_step_integration_discovery_during_onboarding( manufacturer="ACME", ) - with patch( - "homeassistant.components.bluetooth.async_setup", return_value=True - ), patch( - "homeassistant.components.bluetooth.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.onboarding.async_is_onboarded", - return_value=False, - ) as mock_onboarding: + with ( + patch("homeassistant.components.bluetooth.async_setup", return_value=True), + patch( + "homeassistant.components.bluetooth.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, diff --git a/tests/components/bluetooth/test_diagnostics.py b/tests/components/bluetooth/test_diagnostics.py index 0cd96854320..3d29080d56c 100644 --- a/tests/components/bluetooth/test_diagnostics.py +++ b/tests/components/bluetooth/test_diagnostics.py @@ -57,27 +57,30 @@ async def test_diagnostics( # error if the test is not running on linux since we won't have the correct # deps installed when testing on MacOS. - with patch( - "homeassistant.components.bluetooth.diagnostics.platform.system", - return_value="Linux", - ), patch( - "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects", - return_value={ - "org.bluez": { - "/org/bluez/hci0": { - "org.bluez.Adapter1": { - "Name": "BlueZ 5.63", - "Alias": "BlueZ 5.63", - "Modalias": "usb:v1D6Bp0246d0540", - "Discovering": False, - }, - "org.bluez.AdvertisementMonitorManager1": { - "SupportedMonitorTypes": ["or_patterns"], - "SupportedFeatures": [], - }, + with ( + patch( + "homeassistant.components.bluetooth.diagnostics.platform.system", + return_value="Linux", + ), + patch( + "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects", + return_value={ + "org.bluez": { + "/org/bluez/hci0": { + "org.bluez.Adapter1": { + "Name": "BlueZ 5.63", + "Alias": "BlueZ 5.63", + "Modalias": "usb:v1D6Bp0246d0540", + "Discovering": False, + }, + "org.bluez.AdvertisementMonitorManager1": { + "SupportedMonitorTypes": ["or_patterns"], + "SupportedFeatures": [], + }, + } } - } - }, + }, + ), ): entry2 = MockConfigEntry( domain=bluetooth.DOMAIN, data={}, unique_id="00:00:00:00:00:02" @@ -236,12 +239,15 @@ async def test_diagnostics_macos( local_name="wohand", service_uuids=[], manufacturer_data={1: b"\x01"} ) - with patch( - "homeassistant.components.bluetooth.diagnostics.platform.system", - return_value="Darwin", - ), patch( - "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects", - return_value={}, + with ( + patch( + "homeassistant.components.bluetooth.diagnostics.platform.system", + return_value="Darwin", + ), + patch( + "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects", + return_value={}, + ), ): entry1 = MockConfigEntry( domain=bluetooth.DOMAIN, @@ -414,12 +420,15 @@ async def test_diagnostics_remote_adapter( MONOTONIC_TIME(), ) - with patch( - "homeassistant.components.bluetooth.diagnostics.platform.system", - return_value="Linux", - ), patch( - "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects", - return_value={}, + with ( + patch( + "homeassistant.components.bluetooth.diagnostics.platform.system", + return_value="Linux", + ), + patch( + "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects", + return_value={}, + ), ): entry1 = hass.config_entries.async_entries(bluetooth.DOMAIN)[0] connector = ( diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index 2130e883af4..e9198362d8f 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -66,9 +66,13 @@ async def test_setup_and_stop( mock_bt = [ {"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"} ] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init"): + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init"), + ): assert await async_setup_component( hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} ) @@ -186,12 +190,17 @@ async def test_setup_and_stop_no_bluetooth( mock_bt = [ {"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"} ] - with patch( - "habluetooth.scanner.OriginalBleakScanner", - side_effect=BleakError, - ) as mock_ha_bleak_scanner, patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch("homeassistant.components.bluetooth.discovery_flow.async_create_flow"): + with ( + patch( + "habluetooth.scanner.OriginalBleakScanner", + side_effect=BleakError, + ) as mock_ha_bleak_scanner, + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch("homeassistant.components.bluetooth.discovery_flow.async_create_flow"), + ): await async_setup_with_one_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -207,11 +216,15 @@ async def test_setup_and_stop_broken_bluetooth( ) -> None: """Test we fail gracefully when bluetooth/dbus is broken.""" mock_bt = [] - with patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=BleakError, - ), patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt + with ( + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=BleakError, + ), + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -232,11 +245,16 @@ async def test_setup_and_stop_broken_bluetooth_hanging( async def _mock_hang(): await asyncio.sleep(1) - with patch.object(scanner, "START_TIMEOUT", 0), patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=_mock_hang, - ), patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt + with ( + patch.object(scanner, "START_TIMEOUT", 0), + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=_mock_hang, + ), + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -252,11 +270,15 @@ async def test_setup_and_retry_adapter_not_yet_available( ) -> None: """Test we retry if the adapter is not yet available.""" mock_bt = [] - with patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=BleakError, - ), patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt + with ( + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=BleakError, + ), + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -287,11 +309,15 @@ async def test_no_race_during_manual_reload_in_retry_state( ) -> None: """Test we can successfully reload when the entry is in a retry state.""" mock_bt = [] - with patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=BleakError, - ), patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt + with ( + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=BleakError, + ), + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -323,11 +349,15 @@ async def test_calling_async_discovered_devices_no_bluetooth( ) -> None: """Test we fail gracefully when asking for discovered devices and there is no blueooth.""" mock_bt = [] - with patch( - "habluetooth.scanner.OriginalBleakScanner", - side_effect=FileNotFoundError, - ), patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt + with ( + patch( + "habluetooth.scanner.OriginalBleakScanner", + side_effect=FileNotFoundError, + ), + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -347,9 +377,13 @@ async def test_discovery_match_by_service_uuid( mock_bt = [ {"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"} ] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -444,9 +478,13 @@ async def test_discovery_match_by_service_uuid_connectable( "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b", } ] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -491,9 +529,13 @@ async def test_discovery_match_by_service_uuid_not_connectable( "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b", } ] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -536,9 +578,13 @@ async def test_discovery_match_by_name_connectable_false( "local_name": "Qingping*", } ] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -1063,9 +1109,13 @@ async def test_rediscovery( mock_bt = [ {"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"} ] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -1104,11 +1154,20 @@ async def test_async_discovered_device_api( ) -> None: """Test the async_discovered_device API.""" mock_bt = [] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch( - "bleak.BleakScanner.discovered_devices_and_advertisement_data", # Must patch before we setup - {"44:44:33:11:23:45": (MagicMock(address="44:44:33:11:23:45"), MagicMock())}, + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch( + "bleak.BleakScanner.discovered_devices_and_advertisement_data", # Must patch before we setup + { + "44:44:33:11:23:45": ( + MagicMock(address="44:44:33:11:23:45"), + MagicMock(), + ) + }, + ), ): assert not bluetooth.async_discovered_service_info(hass) assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") @@ -1207,9 +1266,13 @@ async def test_register_callbacks( """Fake subscriber for the BleakScanner.""" callbacks.append((service_info, change)) - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init"): + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init"), + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -1288,9 +1351,13 @@ async def test_register_callbacks_raises_exception( callbacks.append((service_info, change)) raise ValueError - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch.object(hass.config_entries.flow, "async_init"): + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch.object(hass.config_entries.flow, "async_init"), + ): await async_setup_with_default_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -2493,9 +2560,12 @@ async def test_wrapped_instance_with_broken_callbacks( hass: HomeAssistant, mock_bleak_scanner_start: MagicMock, enable_bluetooth: None ) -> None: """Test broken callbacks do not cause the scanner to fail.""" - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=[] - ), patch.object(hass.config_entries.flow, "async_init"): + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", return_value=[] + ), + patch.object(hass.config_entries.flow, "async_init"), + ): await async_setup_with_default_adapter(hass) with patch.object(hass.config_entries.flow, "async_init"): @@ -2675,11 +2745,20 @@ async def test_async_ble_device_from_address( ) -> None: """Test the async_ble_device_from_address api.""" mock_bt = [] - with patch( - "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt - ), patch( - "bleak.BleakScanner.discovered_devices_and_advertisement_data", # Must patch before we setup - {"44:44:33:11:23:45": (MagicMock(address="44:44:33:11:23:45"), MagicMock())}, + with ( + patch( + "homeassistant.components.bluetooth.async_get_bluetooth", + return_value=mock_bt, + ), + patch( + "bleak.BleakScanner.discovered_devices_and_advertisement_data", # Must patch before we setup + { + "44:44:33:11:23:45": ( + MagicMock(address="44:44:33:11:23:45"), + MagicMock(), + ) + }, + ), ): assert not bluetooth.async_discovered_service_info(hass) assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") @@ -2814,11 +2893,13 @@ async def test_auto_detect_bluetooth_adapters_linux_none_found( hass: HomeAssistant, ) -> None: """Test we auto detect bluetooth adapters on linux with no adapters found.""" - with patch( - "bluetooth_adapters.systems.platform.system", return_value="Linux" - ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - {}, + with ( + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + {}, + ), ): assert await async_setup_component(hass, bluetooth.DOMAIN, {}) await hass.async_block_till_done() @@ -2923,24 +3004,26 @@ async def test_discover_new_usb_adapters( saved_callback() assert not hass.config_entries.flow.async_progress(DOMAIN) - with patch( - "bluetooth_adapters.systems.platform.system", return_value="Linux" - ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - { - "hci0": { - "address": "00:00:00:00:00:01", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", + with ( + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + { + "hci0": { + "address": "00:00:00:00:00:01", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + }, + "hci1": { + "address": "00:00:00:00:00:02", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + }, }, - "hci1": { - "address": "00:00:00:00:00:02", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", - }, - }, + ), ): for wait_sec in range(10, 20): async_fire_time_changed( @@ -2979,11 +3062,13 @@ async def test_discover_new_usb_adapters_with_firmware_fallback_delay( saved_callback() assert not hass.config_entries.flow.async_progress(DOMAIN) - with patch( - "bluetooth_adapters.systems.platform.system", return_value="Linux" - ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - {}, + with ( + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + {}, + ), ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS * 2) @@ -2992,24 +3077,26 @@ async def test_discover_new_usb_adapters_with_firmware_fallback_delay( assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 0 - with patch( - "bluetooth_adapters.systems.platform.system", return_value="Linux" - ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - { - "hci0": { - "address": "00:00:00:00:00:01", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", + with ( + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + { + "hci0": { + "address": "00:00:00:00:00:01", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + }, + "hci1": { + "address": "00:00:00:00:00:02", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + }, }, - "hci1": { - "address": "00:00:00:00:00:02", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", - }, - }, + ), ): async_fire_time_changed( hass, diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index b99d47cdba1..cb2be8a0e8d 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -1028,14 +1028,16 @@ async def test_goes_unavailable_dismisses_discovery_and_makes_discoverable( not in non_connectable_scanner.discovered_devices_and_advertisement_data ) monotonic_now = time.monotonic() - with patch.object( - hass.config_entries.flow, - "async_progress_by_init_data_type", - return_value=[{"flow_id": "mock_flow_id"}], - ) as mock_async_progress_by_init_data_type, patch.object( - hass.config_entries.flow, "async_abort" - ) as mock_async_abort, patch_bluetooth_time( - monotonic_now + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, + with ( + patch.object( + hass.config_entries.flow, + "async_progress_by_init_data_type", + return_value=[{"flow_id": "mock_flow_id"}], + ) as mock_async_progress_by_init_data_type, + patch.object(hass.config_entries.flow, "async_abort") as mock_async_abort, + patch_bluetooth_time( + monotonic_now + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, + ), ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) diff --git a/tests/components/bluetooth/test_models.py b/tests/components/bluetooth/test_models.py index 837a95ea2d1..087d443c5a0 100644 --- a/tests/components/bluetooth/test_models.py +++ b/tests/components/bluetooth/test_models.py @@ -116,10 +116,15 @@ async def test_wrapped_bleak_client_local_adapter_only( ) client = HaBleakClientWrapper(switchbot_device) - with patch( - "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect", - return_value=True, - ), patch("bleak.backends.bluezdbus.client.BleakClientBlueZDBus.is_connected", True): + with ( + patch( + "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect", + return_value=True, + ), + patch( + "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.is_connected", True + ), + ): assert await client.connect() is True assert client.is_connected is True client.set_disconnected_callback(lambda client: None) @@ -201,10 +206,15 @@ async def test_wrapped_bleak_client_set_disconnected_callback_after_connected( "esp32_has_connection_slot", ) client = HaBleakClientWrapper(switchbot_proxy_device_has_connection_slot) - with patch( - "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect", - return_value=True, - ), patch("bleak.backends.bluezdbus.client.BleakClientBlueZDBus.is_connected", True): + with ( + patch( + "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect", + return_value=True, + ), + patch( + "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.is_connected", True + ), + ): assert await client.connect() is True assert client.is_connected is True client.set_disconnected_callback(lambda client: None) @@ -240,9 +250,10 @@ async def test_ble_device_with_proxy_client_out_of_connections_no_scanners( ] client = HaBleakClientWrapper(switchbot_proxy_device_no_connection_slot) - with patch( - "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect" - ), pytest.raises(BleakError): + with ( + patch("bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect"), + pytest.raises(BleakError), + ): await client.connect() assert client.is_connected is False client.set_disconnected_callback(lambda client: None) @@ -304,9 +315,10 @@ async def test_ble_device_with_proxy_client_out_of_connections( ] client = HaBleakClientWrapper(switchbot_proxy_device_no_connection_slot) - with patch( - "bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect" - ), pytest.raises(BleakError): + with ( + patch("bleak.backends.bluezdbus.client.BleakClientBlueZDBus.connect"), + pytest.raises(BleakError), + ): await client.connect() assert client.is_connected is False client.set_disconnected_callback(lambda client: None) diff --git a/tests/components/bluetooth/test_passive_update_coordinator.py b/tests/components/bluetooth/test_passive_update_coordinator.py index ba8e150d028..54d4f8d5662 100644 --- a/tests/components/bluetooth/test_passive_update_coordinator.py +++ b/tests/components/bluetooth/test_passive_update_coordinator.py @@ -164,8 +164,9 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable( monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time(monotonic_now), patch_all_discovered_devices( - [MagicMock(address="44:44:33:11:23:45")] + with ( + patch_bluetooth_time(monotonic_now), + patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]), ): async_fire_time_changed( hass, @@ -180,9 +181,12 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable( monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 2 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/bluetooth/test_passive_update_processor.py b/tests/components/bluetooth/test_passive_update_processor.py index d7248e8f405..3578e2e6f6f 100644 --- a/tests/components/bluetooth/test_passive_update_processor.py +++ b/tests/components/bluetooth/test_passive_update_processor.py @@ -473,9 +473,12 @@ async def test_unavailable_after_no_data( assert processor.available is True monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]), + ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) ) @@ -491,9 +494,12 @@ async def test_unavailable_after_no_data( monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 2 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([MagicMock(address="44:44:33:11:23:45")]), + ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS) ) diff --git a/tests/components/bluetooth/test_scanner.py b/tests/components/bluetooth/test_scanner.py index aefc36fe6eb..504122fb671 100644 --- a/tests/components/bluetooth/test_scanner.py +++ b/tests/components/bluetooth/test_scanner.py @@ -66,9 +66,12 @@ async def test_dbus_socket_missing_in_container( ) -> None: """Test we handle dbus being missing in the container.""" - with patch("habluetooth.scanner.is_docker_env", return_value=True), patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=FileNotFoundError, + with ( + patch("habluetooth.scanner.is_docker_env", return_value=True), + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=FileNotFoundError, + ), ): await async_setup_with_one_adapter(hass) @@ -86,9 +89,12 @@ async def test_dbus_socket_missing( ) -> None: """Test we handle dbus being missing.""" - with patch("habluetooth.scanner.is_docker_env", return_value=False), patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=FileNotFoundError, + with ( + patch("habluetooth.scanner.is_docker_env", return_value=False), + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=FileNotFoundError, + ), ): await async_setup_with_one_adapter(hass) @@ -106,9 +112,12 @@ async def test_dbus_broken_pipe_in_container( ) -> None: """Test we handle dbus broken pipe in the container.""" - with patch("habluetooth.scanner.is_docker_env", return_value=True), patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=BrokenPipeError, + with ( + patch("habluetooth.scanner.is_docker_env", return_value=True), + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=BrokenPipeError, + ), ): await async_setup_with_one_adapter(hass) @@ -127,9 +136,12 @@ async def test_dbus_broken_pipe( ) -> None: """Test we handle dbus broken pipe.""" - with patch("habluetooth.scanner.is_docker_env", return_value=False), patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=BrokenPipeError, + with ( + patch("habluetooth.scanner.is_docker_env", return_value=False), + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=BrokenPipeError, + ), ): await async_setup_with_one_adapter(hass) @@ -168,12 +180,15 @@ async def test_adapter_needs_reset_at_start( ) -> None: """Test we cycle the adapter when it needs a restart.""" - with patch( - "habluetooth.scanner.OriginalBleakScanner.start", - side_effect=[BleakError(error), None], - ), patch( - "habluetooth.util.recover_adapter", return_value=True - ) as mock_recover_adapter: + with ( + patch( + "habluetooth.scanner.OriginalBleakScanner.start", + side_effect=[BleakError(error), None], + ), + patch( + "habluetooth.util.recover_adapter", return_value=True + ) as mock_recover_adapter, + ): await async_setup_with_one_adapter(hass) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -299,11 +314,14 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None: scanner = MockBleakScanner() start_time_monotonic = time.monotonic() - with patch_bluetooth_time( - start_time_monotonic, - ), patch( - "habluetooth.scanner.OriginalBleakScanner", - return_value=scanner, + with ( + patch_bluetooth_time( + start_time_monotonic, + ), + patch( + "habluetooth.scanner.OriginalBleakScanner", + return_value=scanner, + ), ): await async_setup_with_one_adapter(hass) @@ -331,13 +349,16 @@ async def test_adapter_recovery(hass: HomeAssistant, one_adapter: None) -> None: assert called_start == 1 # We hit the timer with no detections, so we reset the adapter and restart the scanner - with patch_bluetooth_time( - start_time_monotonic - + SCANNER_WATCHDOG_TIMEOUT - + SCANNER_WATCHDOG_INTERVAL.total_seconds(), - ), patch( - "habluetooth.util.recover_adapter", return_value=True - ) as mock_recover_adapter: + with ( + patch_bluetooth_time( + start_time_monotonic + + SCANNER_WATCHDOG_TIMEOUT + + SCANNER_WATCHDOG_INTERVAL.total_seconds(), + ), + patch( + "habluetooth.util.recover_adapter", return_value=True + ) as mock_recover_adapter, + ): async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL) await hass.async_block_till_done() @@ -384,11 +405,14 @@ async def test_adapter_scanner_fails_to_start_first_time( scanner = MockBleakScanner() start_time_monotonic = time.monotonic() - with patch_bluetooth_time( - start_time_monotonic, - ), patch( - "habluetooth.scanner.OriginalBleakScanner", - return_value=scanner, + with ( + patch_bluetooth_time( + start_time_monotonic, + ), + patch( + "habluetooth.scanner.OriginalBleakScanner", + return_value=scanner, + ), ): await async_setup_with_one_adapter(hass) @@ -416,13 +440,16 @@ async def test_adapter_scanner_fails_to_start_first_time( assert called_start == 1 # We hit the timer with no detections, so we reset the adapter and restart the scanner - with patch_bluetooth_time( - start_time_monotonic - + SCANNER_WATCHDOG_TIMEOUT - + SCANNER_WATCHDOG_INTERVAL.total_seconds(), - ), patch( - "habluetooth.util.recover_adapter", return_value=True - ) as mock_recover_adapter: + with ( + patch_bluetooth_time( + start_time_monotonic + + SCANNER_WATCHDOG_TIMEOUT + + SCANNER_WATCHDOG_INTERVAL.total_seconds(), + ), + patch( + "habluetooth.util.recover_adapter", return_value=True + ) as mock_recover_adapter, + ): async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL) await hass.async_block_till_done() @@ -431,13 +458,16 @@ async def test_adapter_scanner_fails_to_start_first_time( # We hit the timer again the previous start call failed, make sure # we try again - with patch_bluetooth_time( - start_time_monotonic - + SCANNER_WATCHDOG_TIMEOUT - + SCANNER_WATCHDOG_INTERVAL.total_seconds(), - ), patch( - "habluetooth.util.recover_adapter", return_value=True - ) as mock_recover_adapter: + with ( + patch_bluetooth_time( + start_time_monotonic + + SCANNER_WATCHDOG_TIMEOUT + + SCANNER_WATCHDOG_INTERVAL.total_seconds(), + ), + patch( + "habluetooth.util.recover_adapter", return_value=True + ) as mock_recover_adapter, + ): async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL) await hass.async_block_till_done() @@ -490,17 +520,22 @@ async def test_adapter_fails_to_start_and_takes_a_bit_to_init( scanner = MockBleakScanner() start_time_monotonic = time.monotonic() - with patch( - "habluetooth.scanner.ADAPTER_INIT_TIME", - 0, - ), patch_bluetooth_time( - start_time_monotonic, - ), patch( - "habluetooth.scanner.OriginalBleakScanner", - return_value=scanner, - ), patch( - "habluetooth.util.recover_adapter", return_value=True - ) as mock_recover_adapter: + with ( + patch( + "habluetooth.scanner.ADAPTER_INIT_TIME", + 0, + ), + patch_bluetooth_time( + start_time_monotonic, + ), + patch( + "habluetooth.scanner.OriginalBleakScanner", + return_value=scanner, + ), + patch( + "habluetooth.util.recover_adapter", return_value=True + ) as mock_recover_adapter, + ): await async_setup_with_one_adapter(hass) assert called_start == 3 @@ -540,15 +575,20 @@ async def test_restart_takes_longer_than_watchdog_time( scanner = MockBleakScanner() start_time_monotonic = time.monotonic() - with patch( - "habluetooth.scanner.ADAPTER_INIT_TIME", - 0, - ), patch_bluetooth_time( - start_time_monotonic, - ), patch( - "habluetooth.scanner.OriginalBleakScanner", - return_value=scanner, - ), patch("habluetooth.util.recover_adapter", return_value=True): + with ( + patch( + "habluetooth.scanner.ADAPTER_INIT_TIME", + 0, + ), + patch_bluetooth_time( + start_time_monotonic, + ), + patch( + "habluetooth.scanner.OriginalBleakScanner", + return_value=scanner, + ), + patch("habluetooth.util.recover_adapter", return_value=True), + ): await async_setup_with_one_adapter(hass) assert called_start == 1 diff --git a/tests/components/bluetooth/test_wrappers.py b/tests/components/bluetooth/test_wrappers.py index 1fc42ebf187..0630d671038 100644 --- a/tests/components/bluetooth/test_wrappers.py +++ b/tests/components/bluetooth/test_wrappers.py @@ -207,11 +207,12 @@ async def test_test_switch_adapters_when_out_of_slots( hass ) # hci0 has 2 slots, hci1 has 1 slot - with patch.object( - manager.slot_manager, "release_slot" - ) as release_slot_mock, patch.object( - manager.slot_manager, "allocate_slot", return_value=True - ) as allocate_slot_mock: + with ( + patch.object(manager.slot_manager, "release_slot") as release_slot_mock, + patch.object( + manager.slot_manager, "allocate_slot", return_value=True + ) as allocate_slot_mock, + ): ble_device = hci0_device_advs["00:00:00:00:00:01"][0] client = bleak.BleakClient(ble_device) assert await client.connect() is True @@ -219,11 +220,12 @@ async def test_test_switch_adapters_when_out_of_slots( assert release_slot_mock.call_count == 0 # All adapters are out of slots - with patch.object( - manager.slot_manager, "release_slot" - ) as release_slot_mock, patch.object( - manager.slot_manager, "allocate_slot", return_value=False - ) as allocate_slot_mock: + with ( + patch.object(manager.slot_manager, "release_slot") as release_slot_mock, + patch.object( + manager.slot_manager, "allocate_slot", return_value=False + ) as allocate_slot_mock, + ): ble_device = hci0_device_advs["00:00:00:00:00:02"][0] client = bleak.BleakClient(ble_device) with pytest.raises(bleak.exc.BleakError): @@ -237,11 +239,12 @@ async def test_test_switch_adapters_when_out_of_slots( return True return False - with patch.object( - manager.slot_manager, "release_slot" - ) as release_slot_mock, patch.object( - manager.slot_manager, "allocate_slot", _allocate_slot_mock - ) as allocate_slot_mock: + with ( + patch.object(manager.slot_manager, "release_slot") as release_slot_mock, + patch.object( + manager.slot_manager, "allocate_slot", _allocate_slot_mock + ) as allocate_slot_mock, + ): ble_device = hci0_device_advs["00:00:00:00:00:03"][0] client = bleak.BleakClient(ble_device) assert await client.connect() is True @@ -264,11 +267,12 @@ async def test_release_slot_on_connect_failure( hass ) # hci0 has 2 slots, hci1 has 1 slot - with patch.object( - manager.slot_manager, "release_slot" - ) as release_slot_mock, patch.object( - manager.slot_manager, "allocate_slot", return_value=True - ) as allocate_slot_mock: + with ( + patch.object(manager.slot_manager, "release_slot") as release_slot_mock, + patch.object( + manager.slot_manager, "allocate_slot", return_value=True + ) as allocate_slot_mock, + ): ble_device = hci0_device_advs["00:00:00:00:00:01"][0] client = bleak.BleakClient(ble_device) assert await client.connect() is False @@ -292,11 +296,12 @@ async def test_release_slot_on_connect_exception( hass ) # hci0 has 2 slots, hci1 has 1 slot - with patch.object( - manager.slot_manager, "release_slot" - ) as release_slot_mock, patch.object( - manager.slot_manager, "allocate_slot", return_value=True - ) as allocate_slot_mock: + with ( + patch.object(manager.slot_manager, "release_slot") as release_slot_mock, + patch.object( + manager.slot_manager, "allocate_slot", return_value=True + ) as allocate_slot_mock, + ): ble_device = hci0_device_advs["00:00:00:00:00:01"][0] client = bleak.BleakClient(ble_device) with pytest.raises(Exception): diff --git a/tests/components/bmw_connected_drive/test_config_flow.py b/tests/components/bmw_connected_drive/test_config_flow.py index 07c84e09707..ab7366e9da4 100644 --- a/tests/components/bmw_connected_drive/test_config_flow.py +++ b/tests/components/bmw_connected_drive/test_config_flow.py @@ -101,14 +101,17 @@ async def test_api_error(hass: HomeAssistant) -> None: async def test_full_user_flow_implementation(hass: HomeAssistant) -> None: """Test registering an integration and finishing flow works.""" - with patch( - "bimmer_connected.api.authentication.MyBMWAuthentication.login", - side_effect=login_sideeffect, - autospec=True, - ), patch( - "homeassistant.components.bmw_connected_drive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "bimmer_connected.api.authentication.MyBMWAuthentication.login", + side_effect=login_sideeffect, + autospec=True, + ), + patch( + "homeassistant.components.bmw_connected_drive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, @@ -123,13 +126,16 @@ async def test_full_user_flow_implementation(hass: HomeAssistant) -> None: async def test_options_flow_implementation(hass: HomeAssistant) -> None: """Test config flow options.""" - with patch( - "bimmer_connected.account.MyBMWAccount.get_vehicles", - return_value=[], - ), patch( - "homeassistant.components.bmw_connected_drive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "bimmer_connected.account.MyBMWAccount.get_vehicles", + return_value=[], + ), + patch( + "homeassistant.components.bmw_connected_drive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY) config_entry.add_to_hass(hass) @@ -156,14 +162,17 @@ async def test_options_flow_implementation(hass: HomeAssistant) -> None: async def test_reauth(hass: HomeAssistant) -> None: """Test the reauth form.""" - with patch( - "bimmer_connected.api.authentication.MyBMWAuthentication.login", - side_effect=login_sideeffect, - autospec=True, - ), patch( - "homeassistant.components.bmw_connected_drive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "bimmer_connected.api.authentication.MyBMWAuthentication.login", + side_effect=login_sideeffect, + autospec=True, + ), + patch( + "homeassistant.components.bmw_connected_drive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): wrong_password = "wrong" config_entry_with_wrong_password = deepcopy(FIXTURE_CONFIG_ENTRY) diff --git a/tests/components/bond/common.py b/tests/components/bond/common.py index 6f1cd30a484..619aa03572a 100644 --- a/tests/components/bond/common.py +++ b/tests/components/bond/common.py @@ -63,14 +63,16 @@ async def setup_bond_entity( """Set up Bond entity.""" config_entry.add_to_hass(hass) - with patch_start_bpup(), patch_bond_bridge(enabled=patch_bridge), patch_bond_token( - enabled=patch_token - ), patch_bond_version(enabled=patch_version), patch_bond_device_ids( - enabled=patch_device_ids - ), patch_setup_entry("cover", enabled=patch_platforms), patch_setup_entry( - "fan", enabled=patch_platforms - ), patch_setup_entry("light", enabled=patch_platforms), patch_setup_entry( - "switch", enabled=patch_platforms + with ( + patch_start_bpup(), + patch_bond_bridge(enabled=patch_bridge), + patch_bond_token(enabled=patch_token), + patch_bond_version(enabled=patch_version), + patch_bond_device_ids(enabled=patch_device_ids), + patch_setup_entry("cover", enabled=patch_platforms), + patch_setup_entry("fan", enabled=patch_platforms), + patch_setup_entry("light", enabled=patch_platforms), + patch_setup_entry("switch", enabled=patch_platforms), ): return await hass.config_entries.async_setup(config_entry.entry_id) @@ -94,16 +96,16 @@ async def setup_platform( ) mock_entry.add_to_hass(hass) - with patch( - "homeassistant.components.bond.PLATFORMS", [platform] - ), patch_bond_version(return_value=bond_version), patch_bond_bridge( - return_value=bridge - ), patch_bond_token(return_value=token), patch_bond_device_ids( - return_value=[bond_device_id] - ), patch_start_bpup(), patch_bond_device( - return_value=discovered_device - ), patch_bond_device_properties(return_value=props), patch_bond_device_state( - return_value=state + with ( + patch("homeassistant.components.bond.PLATFORMS", [platform]), + patch_bond_version(return_value=bond_version), + patch_bond_bridge(return_value=bridge), + patch_bond_token(return_value=token), + patch_bond_device_ids(return_value=[bond_device_id]), + patch_start_bpup(), + patch_bond_device(return_value=discovered_device), + patch_bond_device_properties(return_value=props), + patch_bond_device_state(return_value=state), ): assert await async_setup_component(hass, BOND_DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/bond/test_config_flow.py b/tests/components/bond/test_config_flow.py index 9e5457197b2..bfe61c536d9 100644 --- a/tests/components/bond/test_config_flow.py +++ b/tests/components/bond/test_config_flow.py @@ -38,11 +38,15 @@ async def test_user_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch_bond_version( - return_value={"bondid": "ZXXX12345"} - ), patch_bond_device_ids( - return_value=["f6776c11", "f6776c12"] - ), patch_bond_bridge(), patch_bond_device_properties(), patch_bond_device(), patch_bond_device_state(), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch_bond_version(return_value={"bondid": "ZXXX12345"}), + patch_bond_device_ids(return_value=["f6776c11", "f6776c12"]), + patch_bond_bridge(), + patch_bond_device_properties(), + patch_bond_device(), + patch_bond_device_state(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -67,17 +71,19 @@ async def test_user_form_with_non_bridge(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch_bond_version( - return_value={"bondid": "KXXX12345"} - ), patch_bond_device_ids( - return_value=["f6776c11"] - ), patch_bond_device_properties(), patch_bond_device( - return_value={ - "name": "New Fan", - } - ), patch_bond_bridge( - return_value={} - ), patch_bond_device_state(), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch_bond_version(return_value={"bondid": "KXXX12345"}), + patch_bond_device_ids(return_value=["f6776c11"]), + patch_bond_device_properties(), + patch_bond_device( + return_value={ + "name": "New Fan", + } + ), + patch_bond_bridge(return_value={}), + patch_bond_device_state(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -99,10 +105,12 @@ async def test_user_form_invalid_auth(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch_bond_version( - return_value={"bond_id": "ZXXX12345"} - ), patch_bond_bridge(), patch_bond_device_ids( - side_effect=ClientResponseError(Mock(), Mock(), status=401), + with ( + patch_bond_version(return_value={"bond_id": "ZXXX12345"}), + patch_bond_bridge(), + patch_bond_device_ids( + side_effect=ClientResponseError(Mock(), Mock(), status=401), + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -119,9 +127,11 @@ async def test_user_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch_bond_version( - side_effect=ClientConnectionError() - ), patch_bond_bridge(), patch_bond_device_ids(): + with ( + patch_bond_version(side_effect=ClientConnectionError()), + patch_bond_bridge(), + patch_bond_device_ids(), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -137,9 +147,11 @@ async def test_user_form_old_firmware(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch_bond_version( - return_value={"no_bond_id": "present"} - ), patch_bond_bridge(), patch_bond_device_ids(): + with ( + patch_bond_version(return_value={"no_bond_id": "present"}), + patch_bond_bridge(), + patch_bond_device_ids(), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -181,9 +193,12 @@ async def test_user_form_one_entry_per_device_allowed(hass: HomeAssistant) -> No DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch_bond_version( - return_value={"bondid": "already-registered-bond-id"} - ), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch_bond_version(return_value={"bondid": "already-registered-bond-id"}), + patch_bond_bridge(), + patch_bond_device_ids(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, @@ -216,9 +231,12 @@ async def test_zeroconf_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch_bond_version( - return_value={"bondid": "ZXXX12345"} - ), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch_bond_version(return_value={"bondid": "ZXXX12345"}), + patch_bond_bridge(), + patch_bond_device_ids(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_ACCESS_TOKEN: "test-token"}, @@ -255,7 +273,12 @@ async def test_zeroconf_form_token_unavailable(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch_bond_version(), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch_bond_version(), + patch_bond_bridge(), + patch_bond_device_ids(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_ACCESS_TOKEN: "test-token"}, @@ -292,7 +315,12 @@ async def test_zeroconf_form_token_times_out(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch_bond_version(), patch_bond_bridge(), patch_bond_device_ids(), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch_bond_version(), + patch_bond_bridge(), + patch_bond_device_ids(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_ACCESS_TOKEN: "test-token"}, @@ -311,11 +339,12 @@ async def test_zeroconf_form_token_times_out(hass: HomeAssistant) -> None: async def test_zeroconf_form_with_token_available(hass: HomeAssistant) -> None: """Test we get the discovery form when we can get the token.""" - with patch_bond_version(return_value={"bondid": "ZXXX12345"}), patch_bond_token( - return_value={"token": "discovered-token"} - ), patch_bond_bridge( - return_value={"name": "discovered-name"} - ), patch_bond_device_ids(): + with ( + patch_bond_version(return_value={"bondid": "ZXXX12345"}), + patch_bond_token(return_value={"token": "discovered-token"}), + patch_bond_bridge(return_value={"name": "discovered-name"}), + patch_bond_device_ids(), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, @@ -354,9 +383,12 @@ async def test_zeroconf_form_with_token_available_name_unavailable( ) -> None: """Test we get the discovery form when we can get the token but the name is unavailable.""" - with patch_bond_version( - side_effect=ClientResponseError(Mock(), (), status=HTTPStatus.BAD_REQUEST) - ), patch_bond_token(return_value={"token": "discovered-token"}): + with ( + patch_bond_version( + side_effect=ClientResponseError(Mock(), (), status=HTTPStatus.BAD_REQUEST) + ), + patch_bond_token(return_value={"token": "discovered-token"}), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, @@ -482,8 +514,9 @@ async def test_zeroconf_already_configured_refresh_token(hass: HomeAssistant) -> await hass.config_entries.async_setup(entry.entry_id) assert entry.state is ConfigEntryState.SETUP_ERROR - with _patch_async_setup_entry() as mock_setup_entry, patch_bond_token( - return_value={"token": "discovered-token"} + with ( + _patch_async_setup_entry() as mock_setup_entry, + patch_bond_token(return_value={"token": "discovered-token"}), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -520,8 +553,9 @@ async def test_zeroconf_already_configured_no_reload_same_host( ) entry.add_to_hass(hass) - with _patch_async_setup_entry() as mock_setup_entry, patch_bond_token( - return_value={"token": "correct-token"} + with ( + _patch_async_setup_entry() as mock_setup_entry, + patch_bond_token(return_value={"token": "correct-token"}), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -576,9 +610,10 @@ async def _help_test_form_unexpected_error( DOMAIN, context={"source": source}, data=initial_input ) - with patch_bond_version( - return_value={"bond_id": "ZXXX12345"} - ), patch_bond_device_ids(side_effect=error): + with ( + patch_bond_version(return_value={"bond_id": "ZXXX12345"}), + patch_bond_device_ids(side_effect=error), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input ) diff --git a/tests/components/bond/test_cover.py b/tests/components/bond/test_cover.py index 4af096f1431..e438a830eb5 100644 --- a/tests/components/bond/test_cover.py +++ b/tests/components/bond/test_cover.py @@ -265,8 +265,9 @@ async def test_set_position_cover(hass: HomeAssistant) -> None: bond_device_id="test-device-id", ) - with patch_bond_action() as mock_hold, patch_bond_device_state( - return_value={"position": 0, "open": 1} + with ( + patch_bond_action() as mock_hold, + patch_bond_device_state(return_value={"position": 0, "open": 1}), ): await hass.services.async_call( COVER_DOMAIN, @@ -282,8 +283,9 @@ async def test_set_position_cover(hass: HomeAssistant) -> None: assert entity_state.state == STATE_OPEN assert entity_state.attributes[ATTR_CURRENT_POSITION] == 100 - with patch_bond_action() as mock_hold, patch_bond_device_state( - return_value={"position": 100, "open": 0} + with ( + patch_bond_action() as mock_hold, + patch_bond_device_state(return_value={"position": 100, "open": 0}), ): await hass.services.async_call( COVER_DOMAIN, @@ -299,8 +301,9 @@ async def test_set_position_cover(hass: HomeAssistant) -> None: assert entity_state.state == STATE_CLOSED assert entity_state.attributes[ATTR_CURRENT_POSITION] == 0 - with patch_bond_action() as mock_hold, patch_bond_device_state( - return_value={"position": 40, "open": 1} + with ( + patch_bond_action() as mock_hold, + patch_bond_device_state(return_value={"position": 40, "open": 1}), ): await hass.services.async_call( COVER_DOMAIN, diff --git a/tests/components/bond/test_fan.py b/tests/components/bond/test_fan.py index 766e1e8ad07..6a0160fbec9 100644 --- a/tests/components/bond/test_fan.py +++ b/tests/components/bond/test_fan.py @@ -253,13 +253,17 @@ async def test_turn_on_fan_preset_mode_not_supported(hass: HomeAssistant) -> Non props={"max_speed": 6}, ) - with patch_bond_action(), patch_bond_device_state(), pytest.raises( - NotValidPresetModeError + with ( + patch_bond_action(), + patch_bond_device_state(), + pytest.raises(NotValidPresetModeError), ): await turn_fan_on(hass, "fan.name_1", preset_mode=PRESET_MODE_BREEZE) - with patch_bond_action(), patch_bond_device_state(), pytest.raises( - NotValidPresetModeError + with ( + patch_bond_action(), + patch_bond_device_state(), + pytest.raises(NotValidPresetModeError), ): await hass.services.async_call( FAN_DOMAIN, @@ -381,9 +385,11 @@ async def test_set_speed_belief_speed_api_error(hass: HomeAssistant) -> None: hass, FAN_DOMAIN, ceiling_fan("name-1"), bond_device_id="test-device-id" ) - with pytest.raises( - HomeAssistantError - ), patch_bond_action_returns_clientresponseerror(), patch_bond_device_state(): + with ( + pytest.raises(HomeAssistantError), + patch_bond_action_returns_clientresponseerror(), + patch_bond_device_state(), + ): await hass.services.async_call( BOND_DOMAIN, SERVICE_SET_FAN_SPEED_TRACKED_STATE, diff --git a/tests/components/bond/test_init.py b/tests/components/bond/test_init.py index db10a842b27..167cd9aa401 100644 --- a/tests/components/bond/test_init.py +++ b/tests/components/bond/test_init.py @@ -89,20 +89,21 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains( data={CONF_HOST: "some host", CONF_ACCESS_TOKEN: "test-token"}, ) - with patch_bond_bridge(), patch_bond_version( - return_value={ - "bondid": "ZXXX12345", - "target": "test-model", - "fw_ver": "test-version", - "mcu_ver": "test-hw-version", - } - ), patch_setup_entry("cover") as mock_cover_async_setup_entry, patch_setup_entry( - "fan" - ) as mock_fan_async_setup_entry, patch_setup_entry( - "light" - ) as mock_light_async_setup_entry, patch_setup_entry( - "switch" - ) as mock_switch_async_setup_entry: + with ( + patch_bond_bridge(), + patch_bond_version( + return_value={ + "bondid": "ZXXX12345", + "target": "test-model", + "fw_ver": "test-version", + "mcu_ver": "test-hw-version", + } + ), + patch_setup_entry("cover") as mock_cover_async_setup_entry, + patch_setup_entry("fan") as mock_fan_async_setup_entry, + patch_setup_entry("light") as mock_light_async_setup_entry, + patch_setup_entry("switch") as mock_switch_async_setup_entry, + ): result = await setup_bond_entity(hass, config_entry, patch_device_ids=True) assert result is True await hass.async_block_till_done() @@ -171,21 +172,25 @@ async def test_old_identifiers_are_removed( name="old", ) - with patch_bond_bridge(), patch_bond_version( - return_value={ - "bondid": "ZXXX12345", - "target": "test-model", - "fw_ver": "test-version", - } - ), patch_start_bpup(), patch_bond_device_ids( - return_value=["bond-device-id", "device_id"] - ), patch_bond_device( - return_value={ - "name": "test1", - "type": DeviceType.GENERIC_DEVICE, - } - ), patch_bond_device_properties(return_value={}), patch_bond_device_state( - return_value={} + with ( + patch_bond_bridge(), + patch_bond_version( + return_value={ + "bondid": "ZXXX12345", + "target": "test-model", + "fw_ver": "test-version", + } + ), + patch_start_bpup(), + patch_bond_device_ids(return_value=["bond-device-id", "device_id"]), + patch_bond_device( + return_value={ + "name": "test1", + "type": DeviceType.GENERIC_DEVICE, + } + ), + patch_bond_device_properties(return_value={}), + patch_bond_device_state(return_value={}), ): assert await hass.config_entries.async_setup(config_entry.entry_id) is True await hass.async_block_till_done() @@ -210,24 +215,26 @@ async def test_smart_by_bond_device_suggested_area( config_entry.add_to_hass(hass) - with patch_bond_bridge( - side_effect=ClientResponseError(Mock(), Mock(), status=404) - ), patch_bond_version( - return_value={ - "bondid": "KXXX12345", - "target": "test-model", - "fw_ver": "test-version", - } - ), patch_start_bpup(), patch_bond_device_ids( - return_value=["bond-device-id", "device_id"] - ), patch_bond_device( - return_value={ - "name": "test1", - "type": DeviceType.GENERIC_DEVICE, - "location": "Den", - } - ), patch_bond_device_properties(return_value={}), patch_bond_device_state( - return_value={} + with ( + patch_bond_bridge(side_effect=ClientResponseError(Mock(), Mock(), status=404)), + patch_bond_version( + return_value={ + "bondid": "KXXX12345", + "target": "test-model", + "fw_ver": "test-version", + } + ), + patch_start_bpup(), + patch_bond_device_ids(return_value=["bond-device-id", "device_id"]), + patch_bond_device( + return_value={ + "name": "test1", + "type": DeviceType.GENERIC_DEVICE, + "location": "Den", + } + ), + patch_bond_device_properties(return_value={}), + patch_bond_device_state(return_value={}), ): assert await hass.config_entries.async_setup(config_entry.entry_id) is True await hass.async_block_till_done() @@ -252,27 +259,31 @@ async def test_bridge_device_suggested_area( config_entry.add_to_hass(hass) - with patch_bond_bridge( - return_value={ - "name": "Office Bridge", - "location": "Office", - } - ), patch_bond_version( - return_value={ - "bondid": "ZXXX12345", - "target": "test-model", - "fw_ver": "test-version", - } - ), patch_start_bpup(), patch_bond_device_ids( - return_value=["bond-device-id", "device_id"] - ), patch_bond_device( - return_value={ - "name": "test1", - "type": DeviceType.GENERIC_DEVICE, - "location": "Bathroom", - } - ), patch_bond_device_properties(return_value={}), patch_bond_device_state( - return_value={} + with ( + patch_bond_bridge( + return_value={ + "name": "Office Bridge", + "location": "Office", + } + ), + patch_bond_version( + return_value={ + "bondid": "ZXXX12345", + "target": "test-model", + "fw_ver": "test-version", + } + ), + patch_start_bpup(), + patch_bond_device_ids(return_value=["bond-device-id", "device_id"]), + patch_bond_device( + return_value={ + "name": "test1", + "type": DeviceType.GENERIC_DEVICE, + "location": "Bathroom", + } + ), + patch_bond_device_properties(return_value={}), + patch_bond_device_state(return_value={}), ): assert await hass.config_entries.async_setup(config_entry.entry_id) is True await hass.async_block_till_done() diff --git a/tests/components/bond/test_light.py b/tests/components/bond/test_light.py index eed8166b1a6..37cd82fc321 100644 --- a/tests/components/bond/test_light.py +++ b/tests/components/bond/test_light.py @@ -330,9 +330,11 @@ async def test_light_set_brightness_belief_api_error(hass: HomeAssistant) -> Non bond_device_id="test-device-id", ) - with pytest.raises( - HomeAssistantError - ), patch_bond_action_returns_clientresponseerror(), patch_bond_device_state(): + with ( + pytest.raises(HomeAssistantError), + patch_bond_action_returns_clientresponseerror(), + patch_bond_device_state(), + ): await hass.services.async_call( DOMAIN, SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE, @@ -374,9 +376,11 @@ async def test_fp_light_set_brightness_belief_api_error(hass: HomeAssistant) -> bond_device_id="test-device-id", ) - with pytest.raises( - HomeAssistantError - ), patch_bond_action_returns_clientresponseerror(), patch_bond_device_state(): + with ( + pytest.raises(HomeAssistantError), + patch_bond_action_returns_clientresponseerror(), + patch_bond_device_state(), + ): await hass.services.async_call( DOMAIN, SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE, @@ -485,9 +489,11 @@ async def test_light_set_power_belief_api_error(hass: HomeAssistant) -> None: bond_device_id="test-device-id", ) - with pytest.raises( - HomeAssistantError - ), patch_bond_action_returns_clientresponseerror(), patch_bond_device_state(): + with ( + pytest.raises(HomeAssistantError), + patch_bond_action_returns_clientresponseerror(), + patch_bond_device_state(), + ): await hass.services.async_call( DOMAIN, SERVICE_SET_LIGHT_POWER_TRACKED_STATE, @@ -529,9 +535,11 @@ async def test_fp_light_set_power_belief_api_error(hass: HomeAssistant) -> None: bond_device_id="test-device-id", ) - with pytest.raises( - HomeAssistantError - ), patch_bond_action_returns_clientresponseerror(), patch_bond_device_state(): + with ( + pytest.raises(HomeAssistantError), + patch_bond_action_returns_clientresponseerror(), + patch_bond_device_state(), + ): await hass.services.async_call( DOMAIN, SERVICE_SET_LIGHT_POWER_TRACKED_STATE, diff --git a/tests/components/bond/test_switch.py b/tests/components/bond/test_switch.py index 6bef33939ab..3d3ad663656 100644 --- a/tests/components/bond/test_switch.py +++ b/tests/components/bond/test_switch.py @@ -112,9 +112,11 @@ async def test_switch_set_power_belief_api_error(hass: HomeAssistant) -> None: hass, SWITCH_DOMAIN, generic_device("name-1"), bond_device_id="test-device-id" ) - with pytest.raises( - HomeAssistantError - ), patch_bond_action_returns_clientresponseerror(), patch_bond_device_state(): + with ( + pytest.raises(HomeAssistantError), + patch_bond_action_returns_clientresponseerror(), + patch_bond_device_state(), + ): await hass.services.async_call( BOND_DOMAIN, SERVICE_SET_POWER_TRACKED_STATE, diff --git a/tests/components/bosch_shc/test_config_flow.py b/tests/components/bosch_shc/test_config_flow.py index 99def2b0f29..2fe2b98308d 100644 --- a/tests/components/bosch_shc/test_config_flow.py +++ b/tests/components/bosch_shc/test_config_flow.py @@ -44,17 +44,21 @@ async def test_form_user(hass: HomeAssistant, mock_zeroconf: None) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -65,19 +69,23 @@ async def test_form_user(hass: HomeAssistant, mock_zeroconf: None) -> None: assert result2["step_id"] == "credentials" assert result2["errors"] == {} - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch("boschshcpy.session.SHCSession.authenticate") as mock_authenticate, patch( - "homeassistant.components.bosch_shc.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch("boschshcpy.session.SHCSession.authenticate") as mock_authenticate, + patch( + "homeassistant.components.bosch_shc.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"password": "test"}, @@ -150,17 +158,21 @@ async def test_form_pairing_error(hass: HomeAssistant, mock_zeroconf: None) -> N DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -192,17 +204,21 @@ async def test_form_user_invalid_auth(hass: HomeAssistant, mock_zeroconf: None) DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -213,18 +229,21 @@ async def test_form_user_invalid_auth(hass: HomeAssistant, mock_zeroconf: None) assert result2["step_id"] == "credentials" assert result2["errors"] == {} - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch( - "boschshcpy.session.SHCSession.authenticate", - side_effect=SHCAuthenticationError, + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch( + "boschshcpy.session.SHCSession.authenticate", + side_effect=SHCAuthenticationError, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -245,17 +264,21 @@ async def test_form_validate_connection_error( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -266,18 +289,21 @@ async def test_form_validate_connection_error( assert result2["step_id"] == "credentials" assert result2["errors"] == {} - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch( - "boschshcpy.session.SHCSession.authenticate", - side_effect=SHCConnectionError, + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch( + "boschshcpy.session.SHCSession.authenticate", + side_effect=SHCConnectionError, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -298,17 +324,21 @@ async def test_form_validate_session_error( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -319,18 +349,21 @@ async def test_form_validate_session_error( assert result2["step_id"] == "credentials" assert result2["errors"] == {} - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch( - "boschshcpy.session.SHCSession.authenticate", - side_effect=SHCSessionError(""), + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch( + "boschshcpy.session.SHCSession.authenticate", + side_effect=SHCSessionError(""), + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -351,17 +384,21 @@ async def test_form_validate_exception( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -372,18 +409,21 @@ async def test_form_validate_exception( assert result2["step_id"] == "credentials" assert result2["errors"] == {} - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch( - "boschshcpy.session.SHCSession.authenticate", - side_effect=Exception, + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch( + "boschshcpy.session.SHCSession.authenticate", + side_effect=Exception, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -410,17 +450,21 @@ async def test_form_already_configured( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -437,17 +481,21 @@ async def test_form_already_configured( async def test_zeroconf(hass: HomeAssistant, mock_zeroconf: None) -> None: """Test we get the form.""" - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -471,21 +519,25 @@ async def test_zeroconf(hass: HomeAssistant, mock_zeroconf: None) -> None: assert result2["type"] == "form" assert result2["step_id"] == "credentials" - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch( - "boschshcpy.session.SHCSession.authenticate", - ), patch( - "homeassistant.components.bosch_shc.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch( + "boschshcpy.session.SHCSession.authenticate", + ), + patch( + "homeassistant.components.bosch_shc.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"password": "test"}, @@ -514,17 +566,21 @@ async def test_zeroconf_already_configured( ) entry.add_to_hass(hass) - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -597,17 +653,21 @@ async def test_reauth(hass: HomeAssistant, mock_zeroconf: None) -> None: assert result["type"] == "form" assert result["step_id"] == "reauth_confirm" - with patch( - "boschshcpy.session.SHCSession.mdns_info", - return_value=SHCInformation, - ), patch( - "boschshcpy.information.SHCInformation.name", - new_callable=PropertyMock, - return_value="shc012345", - ), patch( - "boschshcpy.information.SHCInformation.unique_id", - new_callable=PropertyMock, - return_value="test-mac", + with ( + patch( + "boschshcpy.session.SHCSession.mdns_info", + return_value=SHCInformation, + ), + patch( + "boschshcpy.information.SHCInformation.name", + new_callable=PropertyMock, + return_value="shc012345", + ), + patch( + "boschshcpy.information.SHCInformation.unique_id", + new_callable=PropertyMock, + return_value="test-mac", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -618,19 +678,23 @@ async def test_reauth(hass: HomeAssistant, mock_zeroconf: None) -> None: assert result2["step_id"] == "credentials" assert result2["errors"] == {} - with patch( - "boschshcpy.register_client.SHCRegisterClient.register", - return_value={ - "token": "abc:123", - "cert": b"content_cert", - "key": b"content_key", - }, - ), patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open" - ), patch("boschshcpy.session.SHCSession.authenticate"), patch( - "homeassistant.components.bosch_shc.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "boschshcpy.register_client.SHCRegisterClient.register", + return_value={ + "token": "abc:123", + "cert": b"content_cert", + "key": b"content_key", + }, + ), + patch("os.mkdir"), + patch("homeassistant.components.bosch_shc.config_flow.open"), + patch("boschshcpy.session.SHCSession.authenticate"), + patch( + "homeassistant.components.bosch_shc.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {"password": "test"}, @@ -652,9 +716,12 @@ async def test_tls_assets_writer(hass: HomeAssistant) -> None: "cert": b"content_cert", "key": b"content_key", } - with patch("os.mkdir"), patch( - "homeassistant.components.bosch_shc.config_flow.open", mock_open() - ) as mocked_file: + with ( + patch("os.mkdir"), + patch( + "homeassistant.components.bosch_shc.config_flow.open", mock_open() + ) as mocked_file, + ): write_tls_asset(hass, CONF_SHC_CERT, assets["cert"]) mocked_file.assert_called_with( hass.config.path(DOMAIN, CONF_SHC_CERT), "w", encoding="utf8" diff --git a/tests/components/braviatv/test_config_flow.py b/tests/components/braviatv/test_config_flow.py index 36673cfe011..673344017f7 100644 --- a/tests/components/braviatv/test_config_flow.py +++ b/tests/components/braviatv/test_config_flow.py @@ -108,11 +108,14 @@ async def test_ssdp_discovery(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "confirm" - with patch("pybravia.BraviaClient.connect"), patch( - "pybravia.BraviaClient.pair" - ), patch("pybravia.BraviaClient.set_wol_mode"), patch( - "pybravia.BraviaClient.get_system_info", - return_value=BRAVIA_SYSTEM_INFO, + with ( + patch("pybravia.BraviaClient.connect"), + patch("pybravia.BraviaClient.pair"), + patch("pybravia.BraviaClient.set_wol_mode"), + patch( + "pybravia.BraviaClient.get_system_info", + return_value=BRAVIA_SYSTEM_INFO, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -201,10 +204,13 @@ async def test_user_invalid_host(hass: HomeAssistant) -> None: ) async def test_pin_form_error(hass: HomeAssistant, side_effect, error_message) -> None: """Test that PIN form errors are correct.""" - with patch( - "pybravia.BraviaClient.connect", - side_effect=side_effect, - ), patch("pybravia.BraviaClient.pair"): + with ( + patch( + "pybravia.BraviaClient.connect", + side_effect=side_effect, + ), + patch("pybravia.BraviaClient.pair"), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} ) @@ -273,11 +279,14 @@ async def test_duplicate_error(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch("pybravia.BraviaClient.connect"), patch( - "pybravia.BraviaClient.pair" - ), patch("pybravia.BraviaClient.set_wol_mode"), patch( - "pybravia.BraviaClient.get_system_info", - return_value=BRAVIA_SYSTEM_INFO, + with ( + patch("pybravia.BraviaClient.connect"), + patch("pybravia.BraviaClient.pair"), + patch("pybravia.BraviaClient.set_wol_mode"), + patch( + "pybravia.BraviaClient.get_system_info", + return_value=BRAVIA_SYSTEM_INFO, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} @@ -297,11 +306,14 @@ async def test_create_entry(hass: HomeAssistant) -> None: """Test that entry is added correctly with PIN auth.""" uuid = await instance_id.async_get(hass) - with patch("pybravia.BraviaClient.connect"), patch( - "pybravia.BraviaClient.pair" - ), patch("pybravia.BraviaClient.set_wol_mode"), patch( - "pybravia.BraviaClient.get_system_info", - return_value=BRAVIA_SYSTEM_INFO, + with ( + patch("pybravia.BraviaClient.connect"), + patch("pybravia.BraviaClient.pair"), + patch("pybravia.BraviaClient.set_wol_mode"), + patch( + "pybravia.BraviaClient.get_system_info", + return_value=BRAVIA_SYSTEM_INFO, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} @@ -336,11 +348,13 @@ async def test_create_entry(hass: HomeAssistant) -> None: async def test_create_entry_psk(hass: HomeAssistant) -> None: """Test that entry is added correctly with PSK auth.""" - with patch("pybravia.BraviaClient.connect"), patch( - "pybravia.BraviaClient.set_wol_mode" - ), patch( - "pybravia.BraviaClient.get_system_info", - return_value=BRAVIA_SYSTEM_INFO, + with ( + patch("pybravia.BraviaClient.connect"), + patch("pybravia.BraviaClient.set_wol_mode"), + patch( + "pybravia.BraviaClient.get_system_info", + return_value=BRAVIA_SYSTEM_INFO, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} @@ -392,15 +406,20 @@ async def test_reauth_successful(hass: HomeAssistant, use_psk, new_pin) -> None: ) config_entry.add_to_hass(hass) - with patch("pybravia.BraviaClient.connect"), patch( - "pybravia.BraviaClient.get_power_status", - return_value="active", - ), patch( - "pybravia.BraviaClient.get_external_status", - return_value=BRAVIA_SOURCES, - ), patch( - "pybravia.BraviaClient.send_rest_req", - return_value={}, + with ( + patch("pybravia.BraviaClient.connect"), + patch( + "pybravia.BraviaClient.get_power_status", + return_value="active", + ), + patch( + "pybravia.BraviaClient.get_external_status", + return_value=BRAVIA_SOURCES, + ), + patch( + "pybravia.BraviaClient.send_rest_req", + return_value={}, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/braviatv/test_diagnostics.py b/tests/components/braviatv/test_diagnostics.py index f5c174ee672..13f6c92fb76 100644 --- a/tests/components/braviatv/test_diagnostics.py +++ b/tests/components/braviatv/test_diagnostics.py @@ -56,16 +56,17 @@ async def test_entry_diagnostics( ) config_entry.add_to_hass(hass) - with patch("pybravia.BraviaClient.connect"), patch( - "pybravia.BraviaClient.pair" - ), patch("pybravia.BraviaClient.set_wol_mode"), patch( - "pybravia.BraviaClient.get_system_info", return_value=BRAVIA_SYSTEM_INFO - ), patch("pybravia.BraviaClient.get_power_status", return_value="active"), patch( - "pybravia.BraviaClient.get_external_status", return_value=INPUTS - ), patch("pybravia.BraviaClient.get_volume_info", return_value={}), patch( - "pybravia.BraviaClient.get_playing_info", return_value={} - ), patch("pybravia.BraviaClient.get_app_list", return_value=[]), patch( - "pybravia.BraviaClient.get_content_list_all", return_value=[] + with ( + patch("pybravia.BraviaClient.connect"), + patch("pybravia.BraviaClient.pair"), + patch("pybravia.BraviaClient.set_wol_mode"), + patch("pybravia.BraviaClient.get_system_info", return_value=BRAVIA_SYSTEM_INFO), + patch("pybravia.BraviaClient.get_power_status", return_value="active"), + patch("pybravia.BraviaClient.get_external_status", return_value=INPUTS), + patch("pybravia.BraviaClient.get_volume_info", return_value={}), + patch("pybravia.BraviaClient.get_playing_info", return_value={}), + patch("pybravia.BraviaClient.get_app_list", return_value=[]), + patch("pybravia.BraviaClient.get_content_list_all", return_value=[]), ): assert await async_setup_component(hass, DOMAIN, {}) result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) diff --git a/tests/components/bring/conftest.py b/tests/components/bring/conftest.py index c296642f020..e399e18dfbe 100644 --- a/tests/components/bring/conftest.py +++ b/tests/components/bring/conftest.py @@ -28,12 +28,15 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_bring_client() -> Generator[AsyncMock, None, None]: """Mock a Bring client.""" - with patch( - "homeassistant.components.bring.Bring", - autospec=True, - ) as mock_client, patch( - "homeassistant.components.bring.config_flow.Bring", - new=mock_client, + with ( + patch( + "homeassistant.components.bring.Bring", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.bring.config_flow.Bring", + new=mock_client, + ), ): client = mock_client.return_value client.uuid = UUID diff --git a/tests/components/broadlink/test_config_flow.py b/tests/components/broadlink/test_config_flow.py index 6eb87966082..143742d3a9a 100644 --- a/tests/components/broadlink/test_config_flow.py +++ b/tests/components/broadlink/test_config_flow.py @@ -21,9 +21,12 @@ DEVICE_FACTORY = "homeassistant.components.broadlink.config_flow.blk.gendevice" @pytest.fixture(autouse=True) def broadlink_setup_fixture(): """Mock broadlink entry setup.""" - with patch( - "homeassistant.components.broadlink.async_setup", return_value=True - ), patch("homeassistant.components.broadlink.async_setup_entry", return_value=True): + with ( + patch("homeassistant.components.broadlink.async_setup", return_value=True), + patch( + "homeassistant.components.broadlink.async_setup_entry", return_value=True + ), + ): yield diff --git a/tests/components/broadlink/test_device.py b/tests/components/broadlink/test_device.py index 365d61a9e69..c9f22dbcbf8 100644 --- a/tests/components/broadlink/test_device.py +++ b/tests/components/broadlink/test_device.py @@ -20,11 +20,10 @@ async def test_device_setup(hass: HomeAssistant) -> None: """Test a successful setup.""" device = get_device("Office") - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass) assert mock_setup.entry.state is ConfigEntryState.LOADED @@ -45,11 +44,10 @@ async def test_device_setup_authentication_error(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.auth.side_effect = blke.AuthenticationError() - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_ERROR @@ -69,11 +67,10 @@ async def test_device_setup_network_timeout(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.auth.side_effect = blke.NetworkTimeoutError() - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_RETRY @@ -88,11 +85,10 @@ async def test_device_setup_os_error(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.auth.side_effect = OSError() - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_RETRY @@ -107,11 +103,10 @@ async def test_device_setup_broadlink_exception(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.auth.side_effect = blke.BroadlinkException() - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_ERROR @@ -126,11 +121,10 @@ async def test_device_setup_update_network_timeout(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.check_sensors.side_effect = blke.NetworkTimeoutError() - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_RETRY @@ -149,11 +143,10 @@ async def test_device_setup_update_authorization_error(hass: HomeAssistant) -> N {"temperature": 30}, ) - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.LOADED @@ -174,11 +167,10 @@ async def test_device_setup_update_authentication_error(hass: HomeAssistant) -> mock_api.check_sensors.side_effect = blke.AuthorizationError() mock_api.auth.side_effect = (None, blke.AuthenticationError()) - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_RETRY @@ -199,11 +191,10 @@ async def test_device_setup_update_broadlink_exception(hass: HomeAssistant) -> N mock_api = device.get_mock_api() mock_api.check_sensors.side_effect = blke.BroadlinkException() - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward, patch.object( - hass.config_entries.flow, "async_init" - ) as mock_init: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) assert mock_setup.entry.state is ConfigEntryState.SETUP_RETRY @@ -306,8 +297,9 @@ async def test_device_unload_authentication_error(hass: HomeAssistant) -> None: mock_api = device.get_mock_api() mock_api.auth.side_effect = blke.AuthenticationError() - with patch.object(hass.config_entries, "async_forward_entry_setups"), patch.object( - hass.config_entries.flow, "async_init" + with ( + patch.object(hass.config_entries, "async_forward_entry_setups"), + patch.object(hass.config_entries.flow, "async_init"), ): mock_setup = await device.setup_entry(hass, mock_api=mock_api) diff --git a/tests/components/brother/__init__.py b/tests/components/brother/__init__.py index 591a227c3b5..b5a3f8ed5ef 100644 --- a/tests/components/brother/__init__.py +++ b/tests/components/brother/__init__.py @@ -24,9 +24,12 @@ async def init_integration( entry.add_to_hass(hass) if not skip_setup: - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index dc8e5c4d079..2eff4ed2770 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -33,9 +33,12 @@ async def test_show_form(hass: HomeAssistant) -> None: async def test_create_entry_with_hostname(hass: HomeAssistant) -> None: """Test that the user step works with printer hostname.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -51,9 +54,12 @@ async def test_create_entry_with_hostname(hass: HomeAssistant) -> None: async def test_create_entry_with_ipv4_address(hass: HomeAssistant) -> None: """Test that the user step works with printer IPv4 address.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG @@ -67,9 +73,12 @@ async def test_create_entry_with_ipv4_address(hass: HomeAssistant) -> None: async def test_create_entry_with_ipv6_address(hass: HomeAssistant) -> None: """Test that the user step works with printer IPv6 address.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -97,8 +106,9 @@ async def test_invalid_hostname(hass: HomeAssistant) -> None: @pytest.mark.parametrize("exc", [ConnectionError, TimeoutError]) async def test_connection_error(hass: HomeAssistant, exc: Exception) -> None: """Test connection to host error.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=exc + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data", side_effect=exc), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG @@ -109,8 +119,9 @@ async def test_connection_error(hass: HomeAssistant, exc: Exception) -> None: async def test_snmp_error(hass: HomeAssistant) -> None: """Test SNMP error.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=SnmpError("error") + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data", side_effect=SnmpError("error")), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG @@ -121,8 +132,9 @@ async def test_snmp_error(hass: HomeAssistant) -> None: async def test_unsupported_model_error(hass: HomeAssistant) -> None: """Test unsupported printer model error.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=UnsupportedModelError("error") + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data", side_effect=UnsupportedModelError("error")), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG @@ -134,9 +146,12 @@ async def test_unsupported_model_error(hass: HomeAssistant) -> None: async def test_device_exists_abort(hass: HomeAssistant) -> None: """Test we abort config flow if Brother printer already configured.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG).add_to_hass( hass @@ -152,8 +167,9 @@ async def test_device_exists_abort(hass: HomeAssistant) -> None: @pytest.mark.parametrize("exc", [ConnectionError, TimeoutError, SnmpError("error")]) async def test_zeroconf_exception(hass: HomeAssistant, exc: Exception) -> None: """Test we abort zeroconf flow on exception.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=exc + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data", side_effect=exc), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -175,9 +191,10 @@ async def test_zeroconf_exception(hass: HomeAssistant, exc: Exception) -> None: async def test_zeroconf_unsupported_model(hass: HomeAssistant) -> None: """Test unsupported printer model error.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data" - ) as mock_get_data: + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data") as mock_get_data, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, @@ -199,9 +216,12 @@ async def test_zeroconf_unsupported_model(hass: HomeAssistant) -> None: async def test_zeroconf_device_exists_abort(hass: HomeAssistant) -> None: """Test we abort zeroconf flow if Brother printer already configured.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): entry = MockConfigEntry( domain=DOMAIN, @@ -235,9 +255,10 @@ async def test_zeroconf_no_probe_existing_device(hass: HomeAssistant) -> None: """Test we do not probe the device is the host is already configured.""" entry = MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG) entry.add_to_hass(hass) - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data" - ) as mock_get_data: + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data") as mock_get_data, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, @@ -260,9 +281,12 @@ async def test_zeroconf_no_probe_existing_device(hass: HomeAssistant) -> None: async def test_zeroconf_confirm_create_entry(hass: HomeAssistant) -> None: """Test zeroconf confirmation and create config entry.""" - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/brother/test_diagnostics.py b/tests/components/brother/test_diagnostics.py index c935ae5e50a..2ea9faa151e 100644 --- a/tests/components/brother/test_diagnostics.py +++ b/tests/components/brother/test_diagnostics.py @@ -25,11 +25,13 @@ async def test_entry_diagnostics( entry = await init_integration(hass, skip_setup=True) test_time = datetime(2019, 11, 11, 9, 10, 32, tzinfo=UTC) - with patch("brother.Brother.initialize"), patch( - "brother.datetime", now=Mock(return_value=test_time) - ), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch("brother.datetime", now=Mock(return_value=test_time)), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/brother/test_init.py b/tests/components/brother/test_init.py index 331f022e2a9..582e64c71ae 100644 --- a/tests/components/brother/test_init.py +++ b/tests/components/brother/test_init.py @@ -34,8 +34,9 @@ async def test_config_not_ready(hass: HomeAssistant) -> None: data={CONF_HOST: "localhost", CONF_TYPE: "laser"}, ) - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=ConnectionError() + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data", side_effect=ConnectionError()), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/brother/test_sensor.py b/tests/components/brother/test_sensor.py index 25015b28269..ff29f8cb368 100644 --- a/tests/components/brother/test_sensor.py +++ b/tests/components/brother/test_sensor.py @@ -46,11 +46,13 @@ async def test_sensors(hass: HomeAssistant, entity_registry: er.EntityRegistry) disabled_by=None, ) test_time = datetime(2019, 11, 11, 9, 10, 32, tzinfo=UTC) - with patch("brother.Brother.initialize"), patch( - "brother.datetime", now=Mock(return_value=test_time) - ), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch("brother.datetime", now=Mock(return_value=test_time)), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -394,8 +396,9 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state == "waiting" future = utcnow() + timedelta(minutes=5) - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", side_effect=ConnectionError() + with ( + patch("brother.Brother.initialize"), + patch("brother.Brother._get_data", side_effect=ConnectionError()), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -405,9 +408,12 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state == STATE_UNAVAILABLE future = utcnow() + timedelta(minutes=10) - with patch("brother.Brother.initialize"), patch( - "brother.Brother._get_data", - return_value=json.loads(load_fixture("printer_data.json", "brother")), + with ( + patch("brother.Brother.initialize"), + patch( + "brother.Brother._get_data", + return_value=json.loads(load_fixture("printer_data.json", "brother")), + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() diff --git a/tests/components/bsblan/conftest.py b/tests/components/bsblan/conftest.py index 98e2410383b..a9120832ac4 100644 --- a/tests/components/bsblan/conftest.py +++ b/tests/components/bsblan/conftest.py @@ -43,10 +43,9 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: def mock_bsblan(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]: """Return a mocked BSBLAN client.""" - with patch( - "homeassistant.components.bsblan.BSBLAN", autospec=True - ) as bsblan_mock, patch( - "homeassistant.components.bsblan.config_flow.BSBLAN", new=bsblan_mock + with ( + patch("homeassistant.components.bsblan.BSBLAN", autospec=True) as bsblan_mock, + patch("homeassistant.components.bsblan.config_flow.BSBLAN", new=bsblan_mock), ): bsblan = bsblan_mock.return_value bsblan.info.return_value = Info.parse_raw(load_fixture("info.json", DOMAIN)) diff --git a/tests/components/bthome/test_config_flow.py b/tests/components/bthome/test_config_flow.py index b6a3498d570..1a785858752 100644 --- a/tests/components/bthome/test_config_flow.py +++ b/tests/components/bthome/test_config_flow.py @@ -41,12 +41,15 @@ async def test_async_step_bluetooth_valid_device(hass: HomeAssistant) -> None: async def test_async_step_bluetooth_during_onboarding(hass: HomeAssistant) -> None: """Test discovery via bluetooth during onboarding.""" - with patch( - "homeassistant.components.bthome.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.onboarding.async_is_onboarded", - return_value=False, - ) as mock_onboarding: + with ( + patch( + "homeassistant.components.bthome.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, diff --git a/tests/components/camera/conftest.py b/tests/components/camera/conftest.py index c1d630de126..ee8c5df7d65 100644 --- a/tests/components/camera/conftest.py +++ b/tests/components/camera/conftest.py @@ -63,12 +63,15 @@ async def mock_camera_web_rtc_fixture(hass): ) await hass.async_block_till_done() - with patch( - "homeassistant.components.camera.Camera.frontend_stream_type", - new_callable=PropertyMock(return_value=StreamType.WEB_RTC), - ), patch( - "homeassistant.components.camera.Camera.async_handle_web_rtc_offer", - return_value=WEBRTC_ANSWER, + with ( + patch( + "homeassistant.components.camera.Camera.frontend_stream_type", + new_callable=PropertyMock(return_value=StreamType.WEB_RTC), + ), + patch( + "homeassistant.components.camera.Camera.async_handle_web_rtc_offer", + return_value=WEBRTC_ANSWER, + ), ): yield @@ -85,14 +88,16 @@ async def mock_camera_with_device_fixture(): def __get__(self, obj, obj_type=None): return obj.name - with patch( - "homeassistant.components.camera.Camera.has_entity_name", - new_callable=PropertyMock(return_value=True), - ), patch( - "homeassistant.components.camera.Camera.unique_id", new=UniqueIdMock() - ), patch( - "homeassistant.components.camera.Camera.device_info", - new_callable=PropertyMock(return_value=dev_info), + with ( + patch( + "homeassistant.components.camera.Camera.has_entity_name", + new_callable=PropertyMock(return_value=True), + ), + patch("homeassistant.components.camera.Camera.unique_id", new=UniqueIdMock()), + patch( + "homeassistant.components.camera.Camera.device_info", + new_callable=PropertyMock(return_value=dev_info), + ), ): yield diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 82a2652b8bc..ccec2b6f50c 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -112,14 +112,17 @@ async def test_get_image_from_camera_with_width_height( turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=300, second_height=200 ) - with patch( - "homeassistant.components.camera.img_util.TurboJPEGSingleton.instance", - return_value=turbo_jpeg, - ), patch( - "homeassistant.components.demo.camera.Path.read_bytes", - autospec=True, - return_value=b"Test", - ) as mock_camera: + with ( + patch( + "homeassistant.components.camera.img_util.TurboJPEGSingleton.instance", + return_value=turbo_jpeg, + ), + patch( + "homeassistant.components.demo.camera.Path.read_bytes", + autospec=True, + return_value=b"Test", + ) as mock_camera, + ): image = await camera.async_get_image( hass, "camera.demo_camera", width=640, height=480 ) @@ -136,14 +139,17 @@ async def test_get_image_from_camera_with_width_height_scaled( turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=300, second_height=200 ) - with patch( - "homeassistant.components.camera.img_util.TurboJPEGSingleton.instance", - return_value=turbo_jpeg, - ), patch( - "homeassistant.components.demo.camera.Path.read_bytes", - autospec=True, - return_value=b"Valid jpeg", - ) as mock_camera: + with ( + patch( + "homeassistant.components.camera.img_util.TurboJPEGSingleton.instance", + return_value=turbo_jpeg, + ), + patch( + "homeassistant.components.demo.camera.Path.read_bytes", + autospec=True, + return_value=b"Valid jpeg", + ) as mock_camera, + ): image = await camera.async_get_image( hass, "camera.demo_camera", width=4, height=3 ) @@ -161,14 +167,17 @@ async def test_get_image_from_camera_not_jpeg( turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=300, second_height=200 ) - with patch( - "homeassistant.components.camera.img_util.TurboJPEGSingleton.instance", - return_value=turbo_jpeg, - ), patch( - "homeassistant.components.demo.camera.Path.read_bytes", - autospec=True, - return_value=b"png", - ) as mock_camera: + with ( + patch( + "homeassistant.components.camera.img_util.TurboJPEGSingleton.instance", + return_value=turbo_jpeg, + ), + patch( + "homeassistant.components.demo.camera.Path.read_bytes", + autospec=True, + return_value=b"png", + ) as mock_camera, + ): image = await camera.async_get_image( hass, "camera.demo_camera_png", width=4, height=3 ) @@ -193,28 +202,37 @@ async def test_get_image_without_exists_camera( hass: HomeAssistant, image_mock_url ) -> None: """Try to get image without exists camera.""" - with patch( - "homeassistant.helpers.entity_component.EntityComponent.get_entity", - return_value=None, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.helpers.entity_component.EntityComponent.get_entity", + return_value=None, + ), + pytest.raises(HomeAssistantError), + ): await camera.async_get_image(hass, "camera.demo_camera") async def test_get_image_with_timeout(hass: HomeAssistant, image_mock_url) -> None: """Try to get image with timeout.""" - with patch( - "homeassistant.components.demo.camera.DemoCamera.async_camera_image", - side_effect=TimeoutError, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.async_camera_image", + side_effect=TimeoutError, + ), + pytest.raises(HomeAssistantError), + ): await camera.async_get_image(hass, "camera.demo_camera") async def test_get_image_fails(hass: HomeAssistant, image_mock_url) -> None: """Try to get image with timeout.""" - with patch( - "homeassistant.components.demo.camera.DemoCamera.async_camera_image", - return_value=None, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.async_camera_image", + return_value=None, + ), + pytest.raises(HomeAssistantError), + ): await camera.async_get_image(hass, "camera.demo_camera") @@ -222,9 +240,13 @@ async def test_snapshot_service(hass: HomeAssistant, mock_camera) -> None: """Test snapshot service.""" mopen = mock_open() - with patch("homeassistant.components.camera.open", mopen, create=True), patch( - "homeassistant.components.camera.os.makedirs", - ), patch.object(hass.config, "is_allowed_path", return_value=True): + with ( + patch("homeassistant.components.camera.open", mopen, create=True), + patch( + "homeassistant.components.camera.os.makedirs", + ), + patch.object(hass.config, "is_allowed_path", return_value=True), + ): await hass.services.async_call( camera.DOMAIN, camera.SERVICE_SNAPSHOT, @@ -247,9 +269,13 @@ async def test_snapshot_service_not_allowed_path( """Test snapshot service with a not allowed path.""" mopen = mock_open() - with patch("homeassistant.components.camera.open", mopen, create=True), patch( - "homeassistant.components.camera.os.makedirs", - ), pytest.raises(HomeAssistantError, match="/test/snapshot.jpg"): + with ( + patch("homeassistant.components.camera.open", mopen, create=True), + patch( + "homeassistant.components.camera.os.makedirs", + ), + pytest.raises(HomeAssistantError, match="/test/snapshot.jpg"), + ): await hass.services.async_call( camera.DOMAIN, camera.SERVICE_SNAPSHOT, @@ -286,12 +312,15 @@ async def test_websocket_camera_stream( """Test camera/stream websocket command.""" await async_setup_component(hass, "camera", {}) - with patch( - "homeassistant.components.camera.Stream.endpoint_url", - return_value="http://home.assistant/playlist.m3u8", - ) as mock_stream_view_url, patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="http://example.com", + with ( + patch( + "homeassistant.components.camera.Stream.endpoint_url", + return_value="http://home.assistant/playlist.m3u8", + ) as mock_stream_view_url, + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="http://example.com", + ), ): # Request playlist through WebSocket client = await hass_ws_client(hass) @@ -444,11 +473,14 @@ async def test_handle_play_stream_service( {"external_url": "https://example.com"}, ) await async_setup_component(hass, "media_player", {}) - with patch( - "homeassistant.components.camera.Stream.endpoint_url", - ) as mock_request_stream, patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="http://example.com", + with ( + patch( + "homeassistant.components.camera.Stream.endpoint_url", + ) as mock_request_stream, + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="http://example.com", + ), ): # Call service await hass.services.async_call( @@ -468,15 +500,19 @@ async def test_handle_play_stream_service( async def test_no_preload_stream(hass: HomeAssistant, mock_stream) -> None: """Test camera preload preference.""" demo_settings = camera.DynamicStreamSettings() - with patch( - "homeassistant.components.camera.Stream.endpoint_url", - ) as mock_request_stream, patch( - "homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings", - return_value=demo_settings, - ), patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - new_callable=PropertyMock, - ) as mock_stream_source: + with ( + patch( + "homeassistant.components.camera.Stream.endpoint_url", + ) as mock_request_stream, + patch( + "homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings", + return_value=demo_settings, + ), + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + new_callable=PropertyMock, + ) as mock_stream_source, + ): mock_stream_source.return_value = io.BytesIO() await async_setup_component(hass, "camera", {DOMAIN: {"platform": "demo"}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -487,14 +523,16 @@ async def test_no_preload_stream(hass: HomeAssistant, mock_stream) -> None: async def test_preload_stream(hass: HomeAssistant, mock_stream) -> None: """Test camera preload preference.""" demo_settings = camera.DynamicStreamSettings(preload_stream=True) - with patch( - "homeassistant.components.camera.create_stream" - ) as mock_create_stream, patch( - "homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings", - return_value=demo_settings, - ), patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="http://example.com", + with ( + patch("homeassistant.components.camera.create_stream") as mock_create_stream, + patch( + "homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings", + return_value=demo_settings, + ), + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="http://example.com", + ), ): mock_create_stream.return_value.start = AsyncMock() assert await async_setup_component( @@ -508,9 +546,10 @@ async def test_preload_stream(hass: HomeAssistant, mock_stream) -> None: async def test_record_service_invalid_path(hass: HomeAssistant, mock_camera) -> None: """Test record service with invalid path.""" - with patch.object( - hass.config, "is_allowed_path", return_value=False - ), pytest.raises(HomeAssistantError): + with ( + patch.object(hass.config, "is_allowed_path", return_value=False), + pytest.raises(HomeAssistantError), + ): # Call service await hass.services.async_call( camera.DOMAIN, @@ -525,13 +564,16 @@ async def test_record_service_invalid_path(hass: HomeAssistant, mock_camera) -> async def test_record_service(hass: HomeAssistant, mock_camera, mock_stream) -> None: """Test record service.""" - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="http://example.com", - ), patch( - "homeassistant.components.stream.Stream.async_record", - autospec=True, - ) as mock_record: + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="http://example.com", + ), + patch( + "homeassistant.components.stream.Stream.async_record", + autospec=True, + ) as mock_record, + ): # Call service await hass.services.async_call( camera.DOMAIN, @@ -727,15 +769,19 @@ async def test_stream_unavailable( """Camera state.""" await async_setup_component(hass, "camera", {}) - with patch( - "homeassistant.components.camera.Stream.endpoint_url", - return_value="http://home.assistant/playlist.m3u8", - ), patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="http://example.com", - ), patch( - "homeassistant.components.camera.Stream.set_update_callback", - ) as mock_update_callback: + with ( + patch( + "homeassistant.components.camera.Stream.endpoint_url", + return_value="http://home.assistant/playlist.m3u8", + ), + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="http://example.com", + ), + patch( + "homeassistant.components.camera.Stream.set_update_callback", + ) as mock_update_callback, + ): # Request playlist through WebSocket. We just want to create the stream # but don't care about the result. client = await hass_ws_client(hass) @@ -920,12 +966,15 @@ async def test_use_stream_for_stills( client = await hass_client() - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value=None, - ) as mock_stream_source, patch( - "homeassistant.components.demo.camera.DemoCamera.use_stream_for_stills", - return_value=True, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value=None, + ) as mock_stream_source, + patch( + "homeassistant.components.demo.camera.DemoCamera.use_stream_for_stills", + return_value=True, + ), ): # First test when the integration does not support stream should fail resp = await client.get("/api/camera_proxy/camera.demo_camera_without_stream") @@ -938,14 +987,16 @@ async def test_use_stream_for_stills( mock_stream_source.assert_called_once() assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="rtsp://some_source", - ) as mock_stream_source, patch( - "homeassistant.components.camera.create_stream" - ) as mock_create_stream, patch( - "homeassistant.components.demo.camera.DemoCamera.use_stream_for_stills", - return_value=True, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="rtsp://some_source", + ) as mock_stream_source, + patch("homeassistant.components.camera.create_stream") as mock_create_stream, + patch( + "homeassistant.components.demo.camera.DemoCamera.use_stream_for_stills", + return_value=True, + ), ): # Now test when creating the stream succeeds mock_stream = Mock() diff --git a/tests/components/camera/test_media_source.py b/tests/components/camera/test_media_source.py index bab6d3a236b..3dd0399a710 100644 --- a/tests/components/camera/test_media_source.py +++ b/tests/components/camera/test_media_source.py @@ -124,9 +124,12 @@ async def test_resolving_errors(hass: HomeAssistant, mock_camera_hls) -> None: ) assert str(exc_info.value) == "Could not resolve media item: camera.non_existing" - with pytest.raises(media_source.Unresolvable) as exc_info, patch( - "homeassistant.components.camera.Camera.frontend_stream_type", - new_callable=PropertyMock(return_value=StreamType.WEB_RTC), + with ( + pytest.raises(media_source.Unresolvable) as exc_info, + patch( + "homeassistant.components.camera.Camera.frontend_stream_type", + new_callable=PropertyMock(return_value=StreamType.WEB_RTC), + ), ): await media_source.async_resolve_media( hass, "media-source://camera/camera.demo_camera", None diff --git a/tests/components/canary/conftest.py b/tests/components/canary/conftest.py index 25c9b3c3a4f..336e6577ecc 100644 --- a/tests/components/canary/conftest.py +++ b/tests/components/canary/conftest.py @@ -15,9 +15,10 @@ def mock_ffmpeg(hass): @pytest.fixture def canary(hass): """Mock the CanaryApi for easier testing.""" - with patch.object(Api, "login", return_value=True), patch( - "homeassistant.components.canary.Api" - ) as mock_canary: + with ( + patch.object(Api, "login", return_value=True), + patch("homeassistant.components.canary.Api") as mock_canary, + ): instance = mock_canary.return_value = Api( "test-username", "test-password", @@ -39,9 +40,10 @@ def canary(hass): @pytest.fixture def canary_config_flow(hass): """Mock the CanaryApi for easier config flow testing.""" - with patch.object(Api, "login", return_value=True), patch( - "homeassistant.components.canary.config_flow.Api" - ) as mock_canary: + with ( + patch.object(Api, "login", return_value=True), + patch("homeassistant.components.canary.config_flow.Api") as mock_canary, + ): instance = mock_canary.return_value = Api( "test-username", "test-password", diff --git a/tests/components/canary/test_config_flow.py b/tests/components/canary/test_config_flow.py index 1f28daaeaa3..3c32c683a39 100644 --- a/tests/components/canary/test_config_flow.py +++ b/tests/components/canary/test_config_flow.py @@ -27,7 +27,10 @@ async def test_user_form(hass: HomeAssistant, canary_config_flow) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with _patch_async_setup() as mock_setup, _patch_async_setup_entry() as mock_setup_entry: + with ( + _patch_async_setup() as mock_setup, + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, diff --git a/tests/components/cast/conftest.py b/tests/components/cast/conftest.py index 817c4428098..d9ebb24696e 100644 --- a/tests/components/cast/conftest.py +++ b/tests/components/cast/conftest.py @@ -68,27 +68,35 @@ def cast_mock( """Mock pychromecast.""" ignore_cec_orig = list(pychromecast.IGNORE_CEC) - with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", - castbrowser_mock, - ), patch( - "homeassistant.components.cast.helpers.dial.get_cast_type", - get_cast_type_mock, - ), patch( - "homeassistant.components.cast.helpers.dial.get_multizone_status", - get_multizone_status_mock, - ), patch( - "homeassistant.components.cast.media_player.MultizoneManager", - return_value=mz_mock, - ), patch( - "homeassistant.components.cast.media_player.zeroconf.async_get_instance", - AsyncMock(), - ), patch( - "homeassistant.components.cast.media_player.quick_play", - quick_play_mock, - ), patch( - "homeassistant.components.cast.media_player.pychromecast.get_chromecast_from_cast_info", - get_chromecast_mock, + with ( + patch( + "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", + castbrowser_mock, + ), + patch( + "homeassistant.components.cast.helpers.dial.get_cast_type", + get_cast_type_mock, + ), + patch( + "homeassistant.components.cast.helpers.dial.get_multizone_status", + get_multizone_status_mock, + ), + patch( + "homeassistant.components.cast.media_player.MultizoneManager", + return_value=mz_mock, + ), + patch( + "homeassistant.components.cast.media_player.zeroconf.async_get_instance", + AsyncMock(), + ), + patch( + "homeassistant.components.cast.media_player.quick_play", + quick_play_mock, + ), + patch( + "homeassistant.components.cast.media_player.pychromecast.get_chromecast_from_cast_info", + get_chromecast_mock, + ), ): yield diff --git a/tests/components/cast/test_config_flow.py b/tests/components/cast/test_config_flow.py index 7c7bb64d552..62c21fc95ee 100644 --- a/tests/components/cast/test_config_flow.py +++ b/tests/components/cast/test_config_flow.py @@ -14,13 +14,15 @@ from tests.common import MockConfigEntry async def test_creating_entry_sets_up_media_player(hass: HomeAssistant) -> None: """Test setting up Cast loads the media player.""" - with patch( - "homeassistant.components.cast.media_player.async_setup_entry", - return_value=True, - ) as mock_setup, patch( - "pychromecast.discovery.discover_chromecasts", return_value=(True, None) - ), patch( - "pychromecast.discovery.stop_discovery", + with ( + patch( + "homeassistant.components.cast.media_player.async_setup_entry", + return_value=True, + ) as mock_setup, + patch("pychromecast.discovery.discover_chromecasts", return_value=(True, None)), + patch( + "pychromecast.discovery.stop_discovery", + ), ): result = await hass.config_entries.flow.async_init( cast.DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index aa84afb734c..74ab776ec3b 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -121,9 +121,10 @@ async def test_remove_entry(hass: HomeAssistant, mock_zeroconf: None) -> None: entry.add_to_hass(hass) - with patch( - "pychromecast.discovery.discover_chromecasts", return_value=(True, None) - ), patch("pychromecast.discovery.stop_discovery"): + with ( + patch("pychromecast.discovery.discover_chromecasts", return_value=(True, None)), + patch("pychromecast.discovery.stop_discovery"), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert "cast" in hass.config.components diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 7756a2aacbd..9ef31457d5c 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -191,15 +191,19 @@ async def async_setup_media_player_cast(hass: HomeAssistant, info: ChromecastInf chromecast = get_fake_chromecast(info) zconf = get_fake_zconf(host=info.cast_info.host, port=info.cast_info.port) - with patch( - "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_cast_info", - return_value=chromecast, - ) as get_chromecast, patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", - return_value=browser, - ) as cast_browser, patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf, + with ( + patch( + "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_cast_info", + return_value=chromecast, + ) as get_chromecast, + patch( + "homeassistant.components.cast.discovery.pychromecast.discovery.CastBrowser", + return_value=browser, + ) as cast_browser, + patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf, + ), ): data = {"ignore_cec": [], "known_hosts": [], "uuid": [str(info.uuid)]} entry = MockConfigEntry(data=data, domain="cast") @@ -580,13 +584,16 @@ async def test_discover_dynamic_group( tasks.append(real_create_task(coroutine)) # Discover cast service - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_1, - ), patch.object( - hass, - "async_create_background_task", - wraps=create_task, + with ( + patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ), + patch.object( + hass, + "async_create_background_task", + wraps=create_task, + ), ): discover_cast( pychromecast.discovery.MDNSServiceInfo("service"), @@ -606,13 +613,16 @@ async def test_discover_dynamic_group( ) # Discover other dynamic group cast service - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_2, - ), patch.object( - hass, - "async_create_background_task", - wraps=create_task, + with ( + patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_2, + ), + patch.object( + hass, + "async_create_background_task", + wraps=create_task, + ), ): discover_cast( pychromecast.discovery.MDNSServiceInfo("service"), @@ -632,13 +642,16 @@ async def test_discover_dynamic_group( ) # Get update for cast service - with patch( - "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", - return_value=zconf_1, - ), patch.object( - hass, - "async_create_background_task", - wraps=create_task, + with ( + patch( + "homeassistant.components.cast.discovery.ChromeCastZeroconf.get_zeroconf", + return_value=zconf_1, + ), + patch.object( + hass, + "async_create_background_task", + wraps=create_task, + ), ): discover_cast( pychromecast.discovery.MDNSServiceInfo("service"), diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 64afed59d5a..aa5f32c0ca2 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -65,11 +65,14 @@ async def test_user_with_bad_cert(hass: HomeAssistant) -> None: async def test_import_host_only(hass: HomeAssistant) -> None: """Test import with host only.""" - with patch( - "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" - ), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=future_timestamp(1), + with ( + patch( + "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" + ), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=future_timestamp(1), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -87,11 +90,14 @@ async def test_import_host_only(hass: HomeAssistant) -> None: async def test_import_host_and_port(hass: HomeAssistant) -> None: """Test import with host and port.""" - with patch( - "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" - ), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=future_timestamp(1), + with ( + patch( + "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" + ), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=future_timestamp(1), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -109,11 +115,14 @@ async def test_import_host_and_port(hass: HomeAssistant) -> None: async def test_import_non_default_port(hass: HomeAssistant) -> None: """Test import with host and non-default port.""" - with patch( - "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" - ), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=future_timestamp(1), + with ( + patch( + "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" + ), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=future_timestamp(1), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -131,11 +140,14 @@ async def test_import_non_default_port(hass: HomeAssistant) -> None: async def test_import_with_name(hass: HomeAssistant) -> None: """Test import with name (deprecated).""" - with patch( - "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" - ), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=future_timestamp(1), + with ( + patch( + "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" + ), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=future_timestamp(1), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/cert_expiry/test_init.py b/tests/components/cert_expiry/test_init.py index 360cd118a13..312b87affd3 100644 --- a/tests/components/cert_expiry/test_init.py +++ b/tests/components/cert_expiry/test_init.py @@ -42,11 +42,14 @@ async def test_setup_with_config(hass: HomeAssistant) -> None: next_update = dt_util.utcnow() + timedelta(seconds=20) async_fire_time_changed(hass, next_update) - with patch( - "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" - ), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=future_timestamp(1), + with ( + patch( + "homeassistant.components.cert_expiry.config_flow.get_cert_expiry_timestamp" + ), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=future_timestamp(1), + ), ): await hass.async_block_till_done() diff --git a/tests/components/cert_expiry/test_sensors.py b/tests/components/cert_expiry/test_sensors.py index 01eb1ba6ea6..d68e973e1fa 100644 --- a/tests/components/cert_expiry/test_sensors.py +++ b/tests/components/cert_expiry/test_sensors.py @@ -85,9 +85,12 @@ async def test_update_sensor(hass: HomeAssistant) -> None: starting_time = static_datetime() timestamp = future_timestamp(100) - with freeze_time(starting_time), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=timestamp, + with ( + freeze_time(starting_time), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=timestamp, + ), ): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) @@ -101,9 +104,12 @@ async def test_update_sensor(hass: HomeAssistant) -> None: assert state.attributes.get("is_valid") next_update = starting_time + timedelta(hours=24) - with freeze_time(next_update), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=timestamp, + with ( + freeze_time(next_update), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=timestamp, + ), ): async_fire_time_changed(hass, utcnow() + timedelta(hours=24)) await hass.async_block_till_done() @@ -129,9 +135,12 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None: starting_time = static_datetime() timestamp = future_timestamp(100) - with freeze_time(starting_time), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=timestamp, + with ( + freeze_time(starting_time), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=timestamp, + ), ): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) @@ -146,9 +155,12 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None: next_update = starting_time + timedelta(hours=24) - with freeze_time(next_update), patch( - "homeassistant.components.cert_expiry.helper.async_get_cert", - side_effect=socket.gaierror, + with ( + freeze_time(next_update), + patch( + "homeassistant.components.cert_expiry.helper.async_get_cert", + side_effect=socket.gaierror, + ), ): async_fire_time_changed(hass, utcnow() + timedelta(hours=24)) await hass.async_block_till_done() @@ -158,9 +170,12 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None: state = hass.states.get("sensor.example_com_cert_expiry") assert state.state == STATE_UNAVAILABLE - with freeze_time(next_update), patch( - "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", - return_value=timestamp, + with ( + freeze_time(next_update), + patch( + "homeassistant.components.cert_expiry.coordinator.get_cert_expiry_timestamp", + return_value=timestamp, + ), ): async_fire_time_changed(hass, utcnow() + timedelta(hours=48)) await hass.async_block_till_done() @@ -174,9 +189,12 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None: next_update = starting_time + timedelta(hours=72) - with freeze_time(next_update), patch( - "homeassistant.components.cert_expiry.helper.async_get_cert", - side_effect=ssl.SSLError("something bad"), + with ( + freeze_time(next_update), + patch( + "homeassistant.components.cert_expiry.helper.async_get_cert", + side_effect=ssl.SSLError("something bad"), + ), ): async_fire_time_changed(hass, utcnow() + timedelta(hours=72)) await hass.async_block_till_done() @@ -189,9 +207,12 @@ async def test_update_sensor_network_errors(hass: HomeAssistant) -> None: next_update = starting_time + timedelta(hours=96) - with freeze_time(next_update), patch( - "homeassistant.components.cert_expiry.helper.async_get_cert", - side_effect=Exception(), + with ( + freeze_time(next_update), + patch( + "homeassistant.components.cert_expiry.helper.async_get_cert", + side_effect=Exception(), + ), ): async_fire_time_changed(hass, utcnow() + timedelta(hours=96)) await hass.async_block_till_done() diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index 4350c58e625..0147556a888 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -236,8 +236,9 @@ def mock_cloud_login(hass, mock_cloud_setup): @pytest.fixture(name="mock_auth") def mock_auth_fixture(): """Mock check token.""" - with patch("hass_nabucasa.auth.CognitoAuth.async_check_token"), patch( - "hass_nabucasa.auth.CognitoAuth.async_renew_access_token" + with ( + patch("hass_nabucasa.auth.CognitoAuth.async_check_token"), + patch("hass_nabucasa.auth.CognitoAuth.async_renew_access_token"), ): yield diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index 0708c952ca5..99a21734588 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -54,30 +54,34 @@ async def test_setup_provide_implementation(hass: HomeAssistant) -> None: legacy_entry.add_to_hass(hass) account_link.async_setup(hass) - with patch( - "homeassistant.components.cloud.account_link._get_services", - return_value=[ - {"service": "test", "min_version": "0.1.0"}, - {"service": "too_new", "min_version": "1000000.0.0"}, - {"service": "dev", "min_version": "2022.9.0"}, - { - "service": "deprecated", - "min_version": "0.1.0", - "accepts_new_authorizations": False, - }, - { - "service": "legacy", - "min_version": "0.1.0", - "accepts_new_authorizations": False, - }, - { - "service": "no_cloud", - "min_version": "0.1.0", - "accepts_new_authorizations": False, - }, - ], - ), patch( - "homeassistant.components.cloud.account_link.HA_VERSION", "2022.9.0.dev20220817" + with ( + patch( + "homeassistant.components.cloud.account_link._get_services", + return_value=[ + {"service": "test", "min_version": "0.1.0"}, + {"service": "too_new", "min_version": "1000000.0.0"}, + {"service": "dev", "min_version": "2022.9.0"}, + { + "service": "deprecated", + "min_version": "0.1.0", + "accepts_new_authorizations": False, + }, + { + "service": "legacy", + "min_version": "0.1.0", + "accepts_new_authorizations": False, + }, + { + "service": "no_cloud", + "min_version": "0.1.0", + "accepts_new_authorizations": False, + }, + ], + ), + patch( + "homeassistant.components.cloud.account_link.HA_VERSION", + "2022.9.0.dev20220817", + ), ): assert ( await config_entry_oauth2_flow.async_get_implementations( @@ -132,10 +136,13 @@ async def test_get_services_cached(hass: HomeAssistant) -> None: services = 1 - with patch.object(account_link, "CACHE_TIMEOUT", 0), patch( - "hass_nabucasa.account_link.async_fetch_available_services", - side_effect=lambda _: services, - ) as mock_fetch: + with ( + patch.object(account_link, "CACHE_TIMEOUT", 0), + patch( + "hass_nabucasa.account_link.async_fetch_available_services", + side_effect=lambda _: services, + ) as mock_fetch, + ): assert await account_link._get_services(hass) == 1 services = 2 @@ -159,9 +166,12 @@ async def test_get_services_error(hass: HomeAssistant) -> None: """Test that we cache services.""" hass.data["cloud"] = None - with patch.object(account_link, "CACHE_TIMEOUT", 0), patch( - "hass_nabucasa.account_link.async_fetch_available_services", - side_effect=TimeoutError, + with ( + patch.object(account_link, "CACHE_TIMEOUT", 0), + patch( + "hass_nabucasa.account_link.async_fetch_available_services", + side_effect=TimeoutError, + ), ): assert await account_link._get_services(hass) == [] assert account_link.DATA_SERVICES not in hass.data diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 21fe75b9494..a6b05198ca4 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -331,9 +331,12 @@ def patch_sync_helper(): to_remove.extend([ent_id for ent_id in to_rem if ent_id not in to_remove]) return True - with patch("homeassistant.components.cloud.alexa_config.SYNC_DELAY", 0), patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig._sync_helper", - side_effect=sync_helper, + with ( + patch("homeassistant.components.cloud.alexa_config.SYNC_DELAY", 0), + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig._sync_helper", + side_effect=sync_helper, + ), ): yield to_update, to_remove @@ -485,10 +488,13 @@ async def test_alexa_update_report_state( await conf.async_initialize() await conf.set_authorized(True) - with patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_sync_entities", - ) as mock_sync, patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_enable_proactive_mode", + with ( + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_sync_entities", + ) as mock_sync, + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_enable_proactive_mode", + ), ): await cloud_prefs.async_update(alexa_report_state=True) await hass.async_block_till_done() diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index 6f1552527c4..5e15aa32b6f 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -368,9 +368,10 @@ async def test_system_msg(hass: HomeAssistant) -> None: async def test_cloud_connection_info(hass: HomeAssistant) -> None: """Test connection info msg.""" - with patch("hass_nabucasa.Cloud.initialize"), patch( - "uuid.UUID.hex", new_callable=PropertyMock - ) as hexmock: + with ( + patch("hass_nabucasa.Cloud.initialize"), + patch("uuid.UUID.hex", new_callable=PropertyMock) as hexmock, + ): hexmock.return_value = "12345678901234567890" setup = await async_setup_component(hass, "cloud", {"cloud": {}}) assert setup diff --git a/tests/components/cloud/test_config_flow.py b/tests/components/cloud/test_config_flow.py index 4c9823ee8eb..6b506d6b883 100644 --- a/tests/components/cloud/test_config_flow.py +++ b/tests/components/cloud/test_config_flow.py @@ -11,12 +11,15 @@ from tests.common import MockConfigEntry async def test_config_flow(hass: HomeAssistant) -> None: """Test create cloud entry.""" - with patch( - "homeassistant.components.cloud.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.cloud.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.cloud.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.cloud.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "system"} ) diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index bde2d85de46..5ce390fd9a3 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -69,9 +69,12 @@ async def test_google_update_report_state( mock_conf._cloud.subscription_expired = False - with patch.object(mock_conf, "async_sync_entities") as mock_sync, patch( - "homeassistant.components.google_assistant.report_state.async_enable_report_state" - ) as mock_report_state: + with ( + patch.object(mock_conf, "async_sync_entities") as mock_sync, + patch( + "homeassistant.components.google_assistant.report_state.async_enable_report_state" + ) as mock_report_state, + ): await cloud_prefs.async_update(google_report_state=True) await hass.async_block_till_done() @@ -90,9 +93,12 @@ async def test_google_update_report_state_subscription_expired( assert mock_conf._cloud.subscription_expired - with patch.object(mock_conf, "async_sync_entities") as mock_sync, patch( - "homeassistant.components.google_assistant.report_state.async_enable_report_state" - ) as mock_report_state: + with ( + patch.object(mock_conf, "async_sync_entities") as mock_sync, + patch( + "homeassistant.components.google_assistant.report_state.async_enable_report_state" + ) as mock_report_state, + ): await cloud_prefs.async_update(google_report_state=True) await hass.async_block_till_done() @@ -154,8 +160,9 @@ async def test_google_update_expose_trigger_sync( await hass.async_block_till_done() await config.async_connect_agent_user("mock-user-id") - with patch.object(config, "async_sync_entities") as mock_sync, patch.object( - ga_helpers, "SYNC_DELAY", 0 + with ( + patch.object(config, "async_sync_entities") as mock_sync, + patch.object(ga_helpers, "SYNC_DELAY", 0), ): expose_entity(hass, light_entry.entity_id, True) await hass.async_block_till_done() @@ -164,8 +171,9 @@ async def test_google_update_expose_trigger_sync( assert len(mock_sync.mock_calls) == 1 - with patch.object(config, "async_sync_entities") as mock_sync, patch.object( - ga_helpers, "SYNC_DELAY", 0 + with ( + patch.object(config, "async_sync_entities") as mock_sync, + patch.object(ga_helpers, "SYNC_DELAY", 0), ): expose_entity(hass, light_entry.entity_id, False) expose_entity(hass, binary_sensor_entry.entity_id, True) @@ -194,10 +202,10 @@ async def test_google_entity_registry_sync( await config.async_initialize() await config.async_connect_agent_user("mock-user-id") - with patch.object( - config, "async_schedule_google_sync_all" - ) as mock_sync, patch.object(config, "async_sync_entities_all"), patch.object( - ga_helpers, "SYNC_DELAY", 0 + with ( + patch.object(config, "async_schedule_google_sync_all") as mock_sync, + patch.object(config, "async_sync_entities_all"), + patch.object(ga_helpers, "SYNC_DELAY", 0), ): # Created entity entry = entity_registry.async_get_or_create( diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 40092997c79..0dad7cfa882 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -749,14 +749,17 @@ async def test_websocket_status( cloud.iot.state = STATE_CONNECTED client = await hass_ws_client(hass) - with patch.dict( - "homeassistant.components.google_assistant.const.DOMAIN_TO_GOOGLE_TYPES", - {"light": None}, - clear=True, - ), patch.dict( - "homeassistant.components.alexa.entities.ENTITY_ADAPTERS", - {"switch": None}, - clear=True, + with ( + patch.dict( + "homeassistant.components.google_assistant.const.DOMAIN_TO_GOOGLE_TYPES", + {"light": None}, + clear=True, + ), + patch.dict( + "homeassistant.components.alexa.entities.ENTITY_ADAPTERS", + {"switch": None}, + clear=True, + ), ): await client.send_json({"id": 5, "type": "cloud/status"}) response = await client.receive_json() @@ -954,16 +957,20 @@ async def test_websocket_update_preferences_alexa_report_state( """Test updating alexa_report_state sets alexa authorized.""" client = await hass_ws_client(hass) - with patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_sync_entities" - ), patch( - ( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" - ".async_get_access_token" + with ( + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.async_sync_entities" ), - ), patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" - ) as set_authorized_mock: + patch( + ( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" + ".async_get_access_token" + ), + ), + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" + ) as set_authorized_mock, + ): set_authorized_mock.assert_not_called() await client.send_json( @@ -985,15 +992,18 @@ async def test_websocket_update_preferences_require_relink( """Test updating preference requires relink.""" client = await hass_ws_client(hass) - with patch( - ( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" - ".async_get_access_token" + with ( + patch( + ( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" + ".async_get_access_token" + ), + side_effect=alexa_errors.RequireRelink, ), - side_effect=alexa_errors.RequireRelink, - ), patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" - ) as set_authorized_mock: + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" + ) as set_authorized_mock, + ): set_authorized_mock.assert_not_called() await client.send_json( @@ -1015,15 +1025,18 @@ async def test_websocket_update_preferences_no_token( """Test updating preference no token available.""" client = await hass_ws_client(hass) - with patch( - ( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" - ".async_get_access_token" + with ( + patch( + ( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" + ".async_get_access_token" + ), + side_effect=alexa_errors.NoTokenAvailable, ), - side_effect=alexa_errors.NoTokenAvailable, - ), patch( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" - ) as set_authorized_mock: + patch( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig.set_authorized" + ) as set_authorized_mock, + ): set_authorized_mock.assert_not_called() await client.send_json( @@ -1357,13 +1370,16 @@ async def test_list_alexa_entities( "interfaces": ["Alexa.PowerController", "Alexa.EndpointHealth", "Alexa"], } - with patch( - ( - "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" - ".async_get_access_token" + with ( + patch( + ( + "homeassistant.components.cloud.alexa_config.CloudAlexaConfig" + ".async_get_access_token" + ), + ), + patch( + "homeassistant.components.cloud.alexa_config.alexa_state_report.async_send_add_or_update_message" ), - ), patch( - "homeassistant.components.cloud.alexa_config.alexa_state_report.async_send_add_or_update_message" ): # Add the entity to the entity registry entity_registry.async_get_or_create( diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 9fb8b383501..9cc1324ebc1 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -87,8 +87,9 @@ async def test_remote_services( # Test admin access required non_admin_context = Context(user_id=hass_read_only_user.id) - with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect, pytest.raises( - Unauthorized + with ( + patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect, + pytest.raises(Unauthorized), ): await hass.services.async_call( DOMAIN, "remote_connect", blocking=True, context=non_admin_context @@ -96,9 +97,10 @@ async def test_remote_services( assert mock_connect.called is False - with patch( - "hass_nabucasa.remote.RemoteUI.disconnect" - ) as mock_disconnect, pytest.raises(Unauthorized): + with ( + patch("hass_nabucasa.remote.RemoteUI.disconnect") as mock_disconnect, + pytest.raises(Unauthorized), + ): await hass.services.async_call( DOMAIN, "remote_disconnect", blocking=True, context=non_admin_context ) diff --git a/tests/components/cloudflare/test_init.py b/tests/components/cloudflare/test_init.py index e070be749dd..2d66d3c8752 100644 --- a/tests/components/cloudflare/test_init.py +++ b/tests/components/cloudflare/test_init.py @@ -125,10 +125,13 @@ async def test_integration_services_with_issue(hass: HomeAssistant, cfupdate) -> entry = await init_integration(hass) assert entry.state is ConfigEntryState.LOADED - with patch( - "homeassistant.components.cloudflare.async_detect_location_info", - return_value=None, - ), pytest.raises(HomeAssistantError, match="Could not get external IPv4 address"): + with ( + patch( + "homeassistant.components.cloudflare.async_detect_location_info", + return_value=None, + ), + pytest.raises(HomeAssistantError, match="Could not get external IPv4 address"), + ): await hass.services.async_call( DOMAIN, SERVICE_UPDATE_RECORDS, diff --git a/tests/components/co2signal/conftest.py b/tests/components/co2signal/conftest.py index bcc22ae3d55..64972e6403f 100644 --- a/tests/components/co2signal/conftest.py +++ b/tests/components/co2signal/conftest.py @@ -18,12 +18,15 @@ from tests.components.co2signal import VALID_RESPONSE def mock_electricity_maps() -> Generator[None, MagicMock, None]: """Mock the ElectricityMaps client.""" - with patch( - "homeassistant.components.co2signal.ElectricityMaps", - autospec=True, - ) as electricity_maps, patch( - "homeassistant.components.co2signal.config_flow.ElectricityMaps", - new=electricity_maps, + with ( + patch( + "homeassistant.components.co2signal.ElectricityMaps", + autospec=True, + ) as electricity_maps, + patch( + "homeassistant.components.co2signal.config_flow.ElectricityMaps", + new=electricity_maps, + ), ): client = electricity_maps.return_value client.latest_carbon_intensity_by_coordinates.return_value = VALID_RESPONSE diff --git a/tests/components/coinbase/test_config_flow.py b/tests/components/coinbase/test_config_flow.py index 971b6565d00..79b0115bc7c 100644 --- a/tests/components/coinbase/test_config_flow.py +++ b/tests/components/coinbase/test_config_flow.py @@ -35,18 +35,21 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), - ), patch( - "homeassistant.components.coinbase.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), + patch( + "homeassistant.components.coinbase.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -190,17 +193,20 @@ async def test_form_catch_all_exception(hass: HomeAssistant) -> None: async def test_option_form(hass: HomeAssistant) -> None: """Test we handle a good wallet currency option.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), - ), patch( - "homeassistant.components.coinbase.update_listener" - ) as mock_update_listener: + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), + patch( + "homeassistant.components.coinbase.update_listener" + ) as mock_update_listener, + ): config_entry = await init_mock_coinbase(hass) await hass.async_block_till_done() result = await hass.config_entries.options.async_init(config_entry.entry_id) @@ -220,14 +226,16 @@ async def test_option_form(hass: HomeAssistant) -> None: async def test_form_bad_account_currency(hass: HomeAssistant) -> None: """Test we handle a bad currency option.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): config_entry = await init_mock_coinbase(hass) result = await hass.config_entries.options.async_init(config_entry.entry_id) @@ -247,14 +255,16 @@ async def test_form_bad_account_currency(hass: HomeAssistant) -> None: async def test_form_bad_exchange_rate(hass: HomeAssistant) -> None: """Test we handle a bad exchange rate.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): config_entry = await init_mock_coinbase(hass) result = await hass.config_entries.options.async_init(config_entry.entry_id) @@ -273,14 +283,16 @@ async def test_form_bad_exchange_rate(hass: HomeAssistant) -> None: async def test_option_catch_all_exception(hass: HomeAssistant) -> None: """Test we handle an unknown exception in the option flow.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): config_entry = await init_mock_coinbase(hass) result = await hass.config_entries.options.async_init(config_entry.entry_id) diff --git a/tests/components/coinbase/test_diagnostics.py b/tests/components/coinbase/test_diagnostics.py index 55454a48352..e30bdef30b8 100644 --- a/tests/components/coinbase/test_diagnostics.py +++ b/tests/components/coinbase/test_diagnostics.py @@ -24,14 +24,16 @@ async def test_entry_diagnostics( ) -> None: """Test we handle a and redact a diagnostics request.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): config_entry = await init_mock_coinbase(hass) await hass.async_block_till_done() diff --git a/tests/components/coinbase/test_init.py b/tests/components/coinbase/test_init.py index a2350654d08..5af762f557a 100644 --- a/tests/components/coinbase/test_init.py +++ b/tests/components/coinbase/test_init.py @@ -28,15 +28,19 @@ from .const import ( async def test_unload_entry(hass: HomeAssistant) -> None: """Test successful unload of entry.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", - new=mocked_get_accounts, - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch( + "coinbase.wallet.client.Client.get_accounts", + new=mocked_get_accounts, + ), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): entry = await init_mock_coinbase(hass) @@ -55,14 +59,16 @@ async def test_option_updates( ) -> None: """Test handling option updates.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): config_entry = await init_mock_coinbase(hass) await hass.async_block_till_done() @@ -133,14 +139,16 @@ async def test_ignore_vaults_wallets( ) -> None: """Test vaults are ignored in wallet sensors.""" - with patch( - "coinbase.wallet.client.Client.get_current_user", - return_value=mock_get_current_user(), - ), patch( - "coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts - ), patch( - "coinbase.wallet.client.Client.get_exchange_rates", - return_value=mock_get_exchange_rates(), + with ( + patch( + "coinbase.wallet.client.Client.get_current_user", + return_value=mock_get_current_user(), + ), + patch("coinbase.wallet.client.Client.get_accounts", new=mocked_get_accounts), + patch( + "coinbase.wallet.client.Client.get_exchange_rates", + return_value=mock_get_exchange_rates(), + ), ): config_entry = await init_mock_coinbase(hass, currencies=[GOOD_CURRENCY]) await hass.async_block_till_done() diff --git a/tests/components/comelit/test_config_flow.py b/tests/components/comelit/test_config_flow.py index dda29aaf922..851e179dd95 100644 --- a/tests/components/comelit/test_config_flow.py +++ b/tests/components/comelit/test_config_flow.py @@ -28,11 +28,15 @@ async def test_full_flow( hass: HomeAssistant, class_api: str, user_input: dict[str, Any] ) -> None: """Test starting a flow by user.""" - with patch( - f"aiocomelit.api.{class_api}.login", - ), patch( - f"aiocomelit.api.{class_api}.logout", - ), patch("homeassistant.components.comelit.async_setup_entry") as mock_setup_entry: + with ( + patch( + f"aiocomelit.api.{class_api}.login", + ), + patch( + f"aiocomelit.api.{class_api}.logout", + ), + patch("homeassistant.components.comelit.async_setup_entry") as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) @@ -69,13 +73,17 @@ async def test_exception_connection(hass: HomeAssistant, side_effect, error) -> assert result.get("type") == FlowResultType.FORM assert result.get("step_id") == "user" - with patch( - "aiocomelit.api.ComeliteSerialBridgeApi.login", - side_effect=side_effect, - ), patch( - "aiocomelit.api.ComeliteSerialBridgeApi.logout", - ), patch( - "homeassistant.components.comelit.async_setup_entry", + with ( + patch( + "aiocomelit.api.ComeliteSerialBridgeApi.login", + side_effect=side_effect, + ), + patch( + "aiocomelit.api.ComeliteSerialBridgeApi.logout", + ), + patch( + "homeassistant.components.comelit.async_setup_entry", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_USER_BRIDGE_DATA @@ -93,13 +101,16 @@ async def test_reauth_successful(hass: HomeAssistant) -> None: mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_BRIDGE_DATA) mock_config.add_to_hass(hass) - with patch( - "aiocomelit.api.ComeliteSerialBridgeApi.login", - ), patch( - "aiocomelit.api.ComeliteSerialBridgeApi.logout", - ), patch("homeassistant.components.comelit.async_setup_entry"), patch( - "requests.get" - ) as mock_request_get: + with ( + patch( + "aiocomelit.api.ComeliteSerialBridgeApi.login", + ), + patch( + "aiocomelit.api.ComeliteSerialBridgeApi.logout", + ), + patch("homeassistant.components.comelit.async_setup_entry"), + patch("requests.get") as mock_request_get, + ): mock_request_get.return_value.status_code = 200 result = await hass.config_entries.flow.async_init( @@ -137,11 +148,13 @@ async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_BRIDGE_DATA) mock_config.add_to_hass(hass) - with patch( - "aiocomelit.api.ComeliteSerialBridgeApi.login", side_effect=side_effect - ), patch( - "aiocomelit.api.ComeliteSerialBridgeApi.logout", - ), patch("homeassistant.components.comelit.async_setup_entry"): + with ( + patch("aiocomelit.api.ComeliteSerialBridgeApi.login", side_effect=side_effect), + patch( + "aiocomelit.api.ComeliteSerialBridgeApi.logout", + ), + patch("homeassistant.components.comelit.async_setup_entry"), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id}, diff --git a/tests/components/config/conftest.py b/tests/components/config/conftest.py index 2a0552b997f..ffd2f764922 100644 --- a/tests/components/config/conftest.py +++ b/tests/components/config/conftest.py @@ -51,18 +51,22 @@ def mock_config_store(data=None): _LOGGER.info("Reading data from configuration.yaml: %s", result) return result - with patch( - "homeassistant.components.config.view._read", - side_effect=mock_read, - autospec=True, - ), patch( - "homeassistant.components.config.view._write", - side_effect=mock_write, - autospec=True, - ), patch( - "homeassistant.config.async_hass_config_yaml", - side_effect=mock_async_hass_config_yaml, - autospec=True, + with ( + patch( + "homeassistant.components.config.view._read", + side_effect=mock_read, + autospec=True, + ), + patch( + "homeassistant.components.config.view._write", + side_effect=mock_write, + autospec=True, + ), + patch( + "homeassistant.config.async_hass_config_yaml", + side_effect=mock_async_hass_config_yaml, + autospec=True, + ), ): yield data diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index 408947be00e..da8a60ca6fd 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -120,9 +120,12 @@ async def test_websocket_core_update(hass: HomeAssistant, client) -> None: assert hass.config.country != "SE" assert hass.config.language != "sv" - with patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz, patch( - "homeassistant.components.config.core.async_update_suggested_units" - ) as mock_update_sensor_units: + with ( + patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz, + patch( + "homeassistant.components.config.core.async_update_suggested_units" + ) as mock_update_sensor_units, + ): await client.send_json( { "id": 5, @@ -160,9 +163,12 @@ async def test_websocket_core_update(hass: HomeAssistant, client) -> None: assert len(mock_set_tz.mock_calls) == 1 assert mock_set_tz.mock_calls[0][1][0] == dt_util.get_time_zone("America/New_York") - with patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz, patch( - "homeassistant.components.config.core.async_update_suggested_units" - ) as mock_update_sensor_units: + with ( + patch("homeassistant.util.dt.set_default_time_zone") as mock_set_tz, + patch( + "homeassistant.components.config.core.async_update_suggested_units" + ) as mock_update_sensor_units, + ): await client.send_json( { "id": 6, diff --git a/tests/components/control4/test_config_flow.py b/tests/components/control4/test_config_flow.py index b17a1cd6ea1..8ec6df063e5 100644 --- a/tests/components/control4/test_config_flow.py +++ b/tests/components/control4/test_config_flow.py @@ -53,16 +53,20 @@ async def test_form(hass: HomeAssistant) -> None: c4_account = _get_mock_c4_account() c4_director = _get_mock_c4_director() - with patch( - "homeassistant.components.control4.config_flow.C4Account", - return_value=c4_account, - ), patch( - "homeassistant.components.control4.config_flow.C4Director", - return_value=c4_director, - ), patch( - "homeassistant.components.control4.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.control4.config_flow.C4Account", + return_value=c4_account, + ), + patch( + "homeassistant.components.control4.config_flow.C4Director", + return_value=c4_director, + ), + patch( + "homeassistant.components.control4.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -136,12 +140,15 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.control4.config_flow.Control4Validator.authenticate", - return_value=True, - ), patch( - "homeassistant.components.control4.config_flow.C4Director", - side_effect=Unauthorized("message"), + with ( + patch( + "homeassistant.components.control4.config_flow.Control4Validator.authenticate", + return_value=True, + ), + patch( + "homeassistant.components.control4.config_flow.C4Director", + side_effect=Unauthorized("message"), + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/conversation/conftest.py b/tests/components/conversation/conftest.py index 99794dbea4a..cf6b4567228 100644 --- a/tests/components/conversation/conftest.py +++ b/tests/components/conversation/conftest.py @@ -36,8 +36,9 @@ def mock_agent_support_all(hass): @pytest.fixture(autouse=True) def mock_shopping_list_io(): """Stub out the persistence.""" - with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch( - "homeassistant.components.shopping_list.ShoppingData.async_load" + with ( + patch("homeassistant.components.shopping_list.ShoppingData.save"), + patch("homeassistant.components.shopping_list.ShoppingData.async_load"), ): yield diff --git a/tests/components/coolmaster/test_config_flow.py b/tests/components/coolmaster/test_config_flow.py index 6c62ffa482d..ef7828e126d 100644 --- a/tests/components/coolmaster/test_config_flow.py +++ b/tests/components/coolmaster/test_config_flow.py @@ -24,13 +24,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.coolmaster.config_flow.CoolMasterNet.status", - return_value={"test_id": "test_unit"}, - ), patch( - "homeassistant.components.coolmaster.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.coolmaster.config_flow.CoolMasterNet.status", + return_value={"test_id": "test_unit"}, + ), + patch( + "homeassistant.components.coolmaster.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], _flow_data() ) diff --git a/tests/components/datadog/test_init.py b/tests/components/datadog/test_init.py index cbf77de3a69..51d698186b7 100644 --- a/tests/components/datadog/test_init.py +++ b/tests/components/datadog/test_init.py @@ -23,8 +23,9 @@ async def test_datadog_setup_full(hass: HomeAssistant) -> None: """Test setup with all data.""" config = {datadog.DOMAIN: {"host": "host", "port": 123, "rate": 1, "prefix": "foo"}} - with patch("homeassistant.components.datadog.initialize") as mock_init, patch( - "homeassistant.components.datadog.statsd" + with ( + patch("homeassistant.components.datadog.initialize") as mock_init, + patch("homeassistant.components.datadog.statsd"), ): assert await async_setup_component(hass, datadog.DOMAIN, config) @@ -34,8 +35,9 @@ async def test_datadog_setup_full(hass: HomeAssistant) -> None: async def test_datadog_setup_defaults(hass: HomeAssistant) -> None: """Test setup with defaults.""" - with patch("homeassistant.components.datadog.initialize") as mock_init, patch( - "homeassistant.components.datadog.statsd" + with ( + patch("homeassistant.components.datadog.initialize") as mock_init, + patch("homeassistant.components.datadog.statsd"), ): assert await async_setup_component( hass, @@ -55,9 +57,10 @@ async def test_datadog_setup_defaults(hass: HomeAssistant) -> None: async def test_logbook_entry(hass: HomeAssistant) -> None: """Test event listener.""" - with patch("homeassistant.components.datadog.initialize"), patch( - "homeassistant.components.datadog.statsd" - ) as mock_statsd: + with ( + patch("homeassistant.components.datadog.initialize"), + patch("homeassistant.components.datadog.statsd") as mock_statsd, + ): assert await async_setup_component( hass, datadog.DOMAIN, @@ -85,9 +88,10 @@ async def test_logbook_entry(hass: HomeAssistant) -> None: async def test_state_changed(hass: HomeAssistant) -> None: """Test event listener.""" - with patch("homeassistant.components.datadog.initialize"), patch( - "homeassistant.components.datadog.statsd" - ) as mock_statsd: + with ( + patch("homeassistant.components.datadog.initialize"), + patch("homeassistant.components.datadog.statsd") as mock_statsd, + ): assert await async_setup_component( hass, datadog.DOMAIN, diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index 447c1406bf4..5a55fb64090 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -305,8 +305,11 @@ async def test_get_deconz_api_fails( ) -> None: """Failed call.""" config_entry = MockConfigEntry(domain=DECONZ_DOMAIN, data=ENTRY_CONFIG) - with patch( - "pydeconz.DeconzSession.refresh_state", - side_effect=side_effect, - ), pytest.raises(raised_exception): + with ( + patch( + "pydeconz.DeconzSession.refresh_state", + side_effect=side_effect, + ), + pytest.raises(raised_exception), + ): assert await get_deconz_api(hass, config_entry) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 3405a1973c0..0555f70f5e6 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -31,8 +31,9 @@ ENTRY2_UUID = "789ACE" async def setup_entry(hass, entry): """Test that setup entry works.""" - with patch.object(DeconzHub, "async_setup", return_value=True), patch.object( - DeconzHub, "async_update_device_registry", return_value=True + with ( + patch.object(DeconzHub, "async_setup", return_value=True), + patch.object(DeconzHub, "async_update_device_registry", return_value=True), ): assert await async_setup_entry(hass, entry) is True @@ -61,10 +62,13 @@ async def test_setup_entry_fails_config_entry_not_ready(hass: HomeAssistant) -> async def test_setup_entry_fails_trigger_reauth_flow(hass: HomeAssistant) -> None: """Failed authentication trigger a reauthentication flow.""" - with patch( - "homeassistant.components.deconz.get_deconz_api", - side_effect=AuthenticationRequired, - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + with ( + patch( + "homeassistant.components.deconz.get_deconz_api", + side_effect=AuthenticationRequired, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_flow_init, + ): await setup_deconz_integration(hass) mock_flow_init.assert_called_once() diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index b9ce596465e..222b2b14673 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -18,9 +18,11 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None: @pytest.fixture(autouse=True) def mock_ssdp(): """Mock ssdp.""" - with patch("homeassistant.components.ssdp.Scanner.async_scan"), patch( - "homeassistant.components.ssdp.Server.async_start" - ), patch("homeassistant.components.ssdp.Server.async_stop"): + with ( + patch("homeassistant.components.ssdp.Scanner.async_scan"), + patch("homeassistant.components.ssdp.Server.async_start"), + patch("homeassistant.components.ssdp.Server.async_stop"), + ): yield diff --git a/tests/components/deluge/test_config_flow.py b/tests/components/deluge/test_config_flow.py index 6cbde04b8bd..1e7cecd8850 100644 --- a/tests/components/deluge/test_config_flow.py +++ b/tests/components/deluge/test_config_flow.py @@ -18,8 +18,9 @@ from tests.common import MockConfigEntry @pytest.fixture(name="api") def mock_deluge_api(): """Mock an api.""" - with patch("deluge_client.client.DelugeRPCClient.connect"), patch( - "deluge_client.client.DelugeRPCClient._create_socket" + with ( + patch("deluge_client.client.DelugeRPCClient.connect"), + patch("deluge_client.client.DelugeRPCClient._create_socket"), ): yield @@ -27,19 +28,23 @@ def mock_deluge_api(): @pytest.fixture(name="conn_error") def mock_api_connection_error(): """Mock an api.""" - with patch( - "deluge_client.client.DelugeRPCClient.connect", - side_effect=ConnectionRefusedError("111: Connection refused"), - ), patch("deluge_client.client.DelugeRPCClient._create_socket"): + with ( + patch( + "deluge_client.client.DelugeRPCClient.connect", + side_effect=ConnectionRefusedError("111: Connection refused"), + ), + patch("deluge_client.client.DelugeRPCClient._create_socket"), + ): yield @pytest.fixture(name="unknown_error") def mock_api_unknown_error(): """Mock an api.""" - with patch( - "deluge_client.client.DelugeRPCClient.connect", side_effect=Exception - ), patch("deluge_client.client.DelugeRPCClient._create_socket"): + with ( + patch("deluge_client.client.DelugeRPCClient.connect", side_effect=Exception), + patch("deluge_client.client.DelugeRPCClient._create_socket"), + ): yield diff --git a/tests/components/demo/test_update.py b/tests/components/demo/test_update.py index c8f46ff1b41..d8af9c21c75 100644 --- a/tests/components/demo/test_update.py +++ b/tests/components/demo/test_update.py @@ -174,10 +174,13 @@ async def test_update_with_progress_raising(hass: HomeAssistant) -> None: callback(lambda event: events.append(event)), ) - with patch( - "homeassistant.components.demo.update._fake_install", - side_effect=[None, None, None, None, RuntimeError], - ) as fake_sleep, pytest.raises(RuntimeError): + with ( + patch( + "homeassistant.components.demo.update._fake_install", + side_effect=[None, None, None, None, RuntimeError], + ) as fake_sleep, + pytest.raises(RuntimeError), + ): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, diff --git a/tests/components/denonavr/test_config_flow.py b/tests/components/denonavr/test_config_flow.py index bcadade5eb5..5f5a5c8f17c 100644 --- a/tests/components/denonavr/test_config_flow.py +++ b/tests/components/denonavr/test_config_flow.py @@ -41,33 +41,43 @@ TEST_DISCOVER_2_RECEIVER = [{CONF_HOST: TEST_HOST}, {CONF_HOST: TEST_HOST2}] @pytest.fixture(name="denonavr_connect", autouse=True) def denonavr_connect_fixture(): """Mock denonavr connection and entry setup.""" - with patch( - "homeassistant.components.denonavr.receiver.DenonAVR.async_setup", - return_value=None, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.async_update", - return_value=None, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.support_sound_mode", - return_value=True, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.name", - TEST_NAME, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.model_name", - TEST_MODEL, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.serial_number", - TEST_SERIALNUMBER, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.manufacturer", - TEST_MANUFACTURER, - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.receiver_type", - TEST_RECEIVER_TYPE, - ), patch( - "homeassistant.components.denonavr.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.async_setup", + return_value=None, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.async_update", + return_value=None, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.support_sound_mode", + return_value=True, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.name", + TEST_NAME, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.model_name", + TEST_MODEL, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.serial_number", + TEST_SERIALNUMBER, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.manufacturer", + TEST_MANUFACTURER, + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.receiver_type", + TEST_RECEIVER_TYPE, + ), + patch( + "homeassistant.components.denonavr.async_setup_entry", + return_value=True, + ), ): yield @@ -252,12 +262,15 @@ async def test_config_flow_manual_host_connection_error(hass: HomeAssistant) -> assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.denonavr.receiver.DenonAVR.async_setup", - side_effect=AvrTimoutError("Timeout", "async_setup"), - ), patch( - "homeassistant.components.denonavr.receiver.DenonAVR.receiver_type", - None, + with ( + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.async_setup", + side_effect=AvrTimoutError("Timeout", "async_setup"), + ), + patch( + "homeassistant.components.denonavr.receiver.DenonAVR.receiver_type", + None, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/denonavr/test_media_player.py b/tests/components/denonavr/test_media_player.py index 8836e78ad5a..c294c449518 100644 --- a/tests/components/denonavr/test_media_player.py +++ b/tests/components/denonavr/test_media_player.py @@ -41,11 +41,12 @@ ENTITY_ID = f"{media_player.DOMAIN}.{TEST_NAME}" @pytest.fixture(name="client") def client_fixture(): """Patch of client library for tests.""" - with patch( - "homeassistant.components.denonavr.receiver.DenonAVR", - autospec=True, - ) as mock_client_class, patch( - "homeassistant.components.denonavr.config_flow.denonavr.async_discover" + with ( + patch( + "homeassistant.components.denonavr.receiver.DenonAVR", + autospec=True, + ) as mock_client_class, + patch("homeassistant.components.denonavr.config_flow.denonavr.async_discover"), ): mock_client_class.return_value.name = TEST_NAME mock_client_class.return_value.model_name = TEST_MODEL diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 7de8c5c9adc..1a4488e43cd 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -1727,10 +1727,13 @@ async def test_async_get_device_automations_platform_reraises_exceptions( ) -> None: """Test InvalidDeviceAutomationConfig is raised when async_get_integration_with_requirements fails.""" await async_setup_component(hass, "device_automation", {}) - with patch( - "homeassistant.components.device_automation.async_get_integration_with_requirements", - side_effect=exc, - ), pytest.raises(InvalidDeviceAutomationConfig): + with ( + patch( + "homeassistant.components.device_automation.async_get_integration_with_requirements", + side_effect=exc, + ), + pytest.raises(InvalidDeviceAutomationConfig), + ): await device_automation.async_get_device_automation_platform( hass, "test", device_automation.DeviceAutomationType.TRIGGER ) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 188b581de40..3b95fc9582c 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -292,10 +292,13 @@ async def test_update_stale( register_time = datetime(now.year + 1, 9, 15, 23, tzinfo=dt_util.UTC) scan_time = datetime(now.year + 1, 9, 15, 23, 1, tzinfo=dt_util.UTC) - with patch( - "homeassistant.components.device_tracker.legacy.dt_util.utcnow", - return_value=register_time, - ), assert_setup_component(1, device_tracker.DOMAIN): + with ( + patch( + "homeassistant.components.device_tracker.legacy.dt_util.utcnow", + return_value=register_time, + ), + assert_setup_component(1, device_tracker.DOMAIN), + ): assert await async_setup_component( hass, device_tracker.DOMAIN, @@ -548,10 +551,13 @@ async def test_see_passive_zone_state( scanner.reset() scanner.come_home("dev1") - with patch( - "homeassistant.components.device_tracker.legacy.dt_util.utcnow", - return_value=register_time, - ), assert_setup_component(1, device_tracker.DOMAIN): + with ( + patch( + "homeassistant.components.device_tracker.legacy.dt_util.utcnow", + return_value=register_time, + ), + assert_setup_component(1, device_tracker.DOMAIN), + ): assert await async_setup_component( hass, device_tracker.DOMAIN, diff --git a/tests/components/device_tracker/test_legacy.py b/tests/components/device_tracker/test_legacy.py index e22aa54b45c..dba069c410b 100644 --- a/tests/components/device_tracker/test_legacy.py +++ b/tests/components/device_tracker/test_legacy.py @@ -30,8 +30,9 @@ def test_remove_device_from_config(hass: HomeAssistant): mopen = mock_open() files = {legacy.YAML_DEVICES: dump(yaml_devices)} - with patch_yaml_files(files, True), patch( - "homeassistant.components.device_tracker.legacy.open", mopen + with ( + patch_yaml_files(files, True), + patch("homeassistant.components.device_tracker.legacy.open", mopen), ): legacy.remove_device_from_config(hass, "test") diff --git a/tests/components/devolo_home_control/conftest.py b/tests/components/devolo_home_control/conftest.py index 786652c7753..6ce9b73ff83 100644 --- a/tests/components/devolo_home_control/conftest.py +++ b/tests/components/devolo_home_control/conftest.py @@ -23,15 +23,19 @@ def patch_mydevolo( credentials_valid: bool, maintenance: bool ) -> Generator[None, None, None]: """Fixture to patch mydevolo into a desired state.""" - with patch( - "homeassistant.components.devolo_home_control.Mydevolo.credentials_valid", - return_value=credentials_valid, - ), patch( - "homeassistant.components.devolo_home_control.Mydevolo.maintenance", - return_value=maintenance, - ), patch( - "homeassistant.components.devolo_home_control.Mydevolo.get_gateway_ids", - return_value=["1400000000000001", "1400000000000002"], + with ( + patch( + "homeassistant.components.devolo_home_control.Mydevolo.credentials_valid", + return_value=credentials_valid, + ), + patch( + "homeassistant.components.devolo_home_control.Mydevolo.maintenance", + return_value=maintenance, + ), + patch( + "homeassistant.components.devolo_home_control.Mydevolo.get_gateway_ids", + return_value=["1400000000000001", "1400000000000002"], + ), ): yield diff --git a/tests/components/devolo_home_control/test_config_flow.py b/tests/components/devolo_home_control/test_config_flow.py index 3ace53bfc3b..1aa8e7f829d 100644 --- a/tests/components/devolo_home_control/test_config_flow.py +++ b/tests/components/devolo_home_control/test_config_flow.py @@ -75,12 +75,15 @@ async def test_form_advanced_options(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.devolo_home_control.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.devolo_home_control.Mydevolo.uuid", - return_value="123456", + with ( + patch( + "homeassistant.components.devolo_home_control.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.devolo_home_control.Mydevolo.uuid", + return_value="123456", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -179,12 +182,15 @@ async def test_form_reauth(hass: HomeAssistant) -> None: assert result["step_id"] == "reauth_confirm" assert result["type"] == FlowResultType.FORM - with patch( - "homeassistant.components.devolo_home_control.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.devolo_home_control.Mydevolo.uuid", - return_value="123456", + with ( + patch( + "homeassistant.components.devolo_home_control.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.devolo_home_control.Mydevolo.uuid", + return_value="123456", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -242,12 +248,15 @@ async def test_form_uuid_change_reauth(hass: HomeAssistant) -> None: assert result["step_id"] == "reauth_confirm" assert result["type"] == FlowResultType.FORM - with patch( - "homeassistant.components.devolo_home_control.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.devolo_home_control.Mydevolo.uuid", - return_value="789123", + with ( + patch( + "homeassistant.components.devolo_home_control.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.devolo_home_control.Mydevolo.uuid", + return_value="789123", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -261,12 +270,15 @@ async def test_form_uuid_change_reauth(hass: HomeAssistant) -> None: async def _setup(hass: HomeAssistant, result: FlowResult) -> None: """Finish configuration steps.""" - with patch( - "homeassistant.components.devolo_home_control.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.devolo_home_control.Mydevolo.uuid", - return_value="123456", + with ( + patch( + "homeassistant.components.devolo_home_control.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.devolo_home_control.Mydevolo.uuid", + return_value="123456", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/devolo_home_network/test_init.py b/tests/components/devolo_home_network/test_init.py index 60dfbe4596f..c4a02f9e375 100644 --- a/tests/components/devolo_home_network/test_init.py +++ b/tests/components/devolo_home_network/test_init.py @@ -53,10 +53,13 @@ async def test_setup_without_password(hass: HomeAssistant) -> None: } entry = MockConfigEntry(domain=DOMAIN, data=config) entry.add_to_hass(hass) - with patch( - "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", - return_value=True, - ), patch("homeassistant.core.EventBus.async_listen_once"): + with ( + patch( + "homeassistant.config_entries.ConfigEntries.async_forward_entry_setup", + return_value=True, + ), + patch("homeassistant.core.EventBus.async_listen_once"), + ): assert await hass.config_entries.async_setup(entry.entry_id) assert entry.state is ConfigEntryState.LOADED diff --git a/tests/components/dexcom/__init__.py b/tests/components/dexcom/__init__.py index acf03725d32..e9ca303765b 100644 --- a/tests/components/dexcom/__init__.py +++ b/tests/components/dexcom/__init__.py @@ -28,12 +28,15 @@ async def init_integration(hass) -> MockConfigEntry: data=CONFIG, options=None, ) - with patch( - "homeassistant.components.dexcom.Dexcom.get_current_glucose_reading", - return_value=GLUCOSE_READING, - ), patch( - "homeassistant.components.dexcom.Dexcom.create_session", - return_value="test_session_id", + with ( + patch( + "homeassistant.components.dexcom.Dexcom.get_current_glucose_reading", + return_value=GLUCOSE_READING, + ), + patch( + "homeassistant.components.dexcom.Dexcom.create_session", + return_value="test_session_id", + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/dexcom/test_config_flow.py b/tests/components/dexcom/test_config_flow.py index 802df9513d3..f87f365a7e6 100644 --- a/tests/components/dexcom/test_config_flow.py +++ b/tests/components/dexcom/test_config_flow.py @@ -23,13 +23,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.dexcom.config_flow.Dexcom.create_session", - return_value="test_session_id", - ), patch( - "homeassistant.components.dexcom.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.dexcom.config_flow.Dexcom.create_session", + return_value="test_session_id", + ), + patch( + "homeassistant.components.dexcom.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG, diff --git a/tests/components/dexcom/test_sensor.py b/tests/components/dexcom/test_sensor.py index e79cd0f4e01..1b7f0b026ab 100644 --- a/tests/components/dexcom/test_sensor.py +++ b/tests/components/dexcom/test_sensor.py @@ -69,12 +69,15 @@ async def test_sensors_options_changed(hass: HomeAssistant) -> None: test_username_glucose_trend = hass.states.get("sensor.test_username_glucose_trend") assert test_username_glucose_trend.state == GLUCOSE_READING.trend_description - with patch( - "homeassistant.components.dexcom.Dexcom.get_current_glucose_reading", - return_value=GLUCOSE_READING, - ), patch( - "homeassistant.components.dexcom.Dexcom.create_session", - return_value="test_session_id", + with ( + patch( + "homeassistant.components.dexcom.Dexcom.get_current_glucose_reading", + return_value=GLUCOSE_READING, + ), + patch( + "homeassistant.components.dexcom.Dexcom.create_session", + return_value="test_session_id", + ), ): hass.config_entries.async_update_entry( entry=entry, diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 2ba651307ed..7c652c8ea3e 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -535,11 +535,13 @@ async def test_setup_and_stop(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - with patch.object( - interfaces, - "resolve_iface", - ) as resolve_iface_call, patch("scapy.arch.common.compile_filter"), patch( - "homeassistant.components.dhcp.DiscoverHosts.async_discover" + with ( + patch.object( + interfaces, + "resolve_iface", + ) as resolve_iface_call, + patch("scapy.arch.common.compile_filter"), + patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"), ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -564,11 +566,15 @@ async def test_setup_fails_as_root( wait_event = threading.Event() - with patch("os.geteuid", return_value=0), patch.object( - interfaces, - "resolve_iface", - side_effect=Scapy_Exception, - ), patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"): + with ( + patch("os.geteuid", return_value=0), + patch.object( + interfaces, + "resolve_iface", + side_effect=Scapy_Exception, + ), + patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"), + ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -590,13 +596,16 @@ async def test_setup_fails_non_root( ) await hass.async_block_till_done() - with patch("os.geteuid", return_value=10), patch( - "scapy.arch.common.compile_filter" - ), patch.object( - interfaces, - "resolve_iface", - side_effect=Scapy_Exception, - ), patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"): + with ( + patch("os.geteuid", return_value=10), + patch("scapy.arch.common.compile_filter"), + patch.object( + interfaces, + "resolve_iface", + side_effect=Scapy_Exception, + ), + patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"), + ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) @@ -617,14 +626,16 @@ async def test_setup_fails_with_broken_libpcap( ) await hass.async_block_till_done() - with patch( - "scapy.arch.common.compile_filter", - side_effect=ImportError, - ) as compile_filter, patch.object( - interfaces, - "resolve_iface", - ) as resolve_iface_call, patch( - "homeassistant.components.dhcp.DiscoverHosts.async_discover" + with ( + patch( + "scapy.arch.common.compile_filter", + side_effect=ImportError, + ) as compile_filter, + patch.object( + interfaces, + "resolve_iface", + ) as resolve_iface_call, + patch("homeassistant.components.dhcp.DiscoverHosts.async_discover"), ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -967,15 +978,18 @@ async def test_device_tracker_ignore_self_assigned_ips_before_start( async def test_aiodiscover_finds_new_hosts(hass: HomeAssistant) -> None: """Test aiodiscover finds new host.""" - with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch( - "homeassistant.components.dhcp.DiscoverHosts.async_discover", - return_value=[ - { - dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", - dhcp.DISCOVERY_HOSTNAME: "connect", - dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", - } - ], + with ( + patch.object(hass.config_entries.flow, "async_init") as mock_init, + patch( + "homeassistant.components.dhcp.DiscoverHosts.async_discover", + return_value=[ + { + dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", + dhcp.DISCOVERY_HOSTNAME: "connect", + dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", + } + ], + ), ): device_tracker_watcher = dhcp.NetworkWatcher( hass, @@ -1016,25 +1030,28 @@ async def test_aiodiscover_does_not_call_again_on_shorter_hostname( additional discovery where the hostname is longer and then reject shorter ones. """ - with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch( - "homeassistant.components.dhcp.DiscoverHosts.async_discover", - return_value=[ - { - dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", - dhcp.DISCOVERY_HOSTNAME: "irobot-abc", - dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", - }, - { - dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", - dhcp.DISCOVERY_HOSTNAME: "irobot-abcdef", - dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", - }, - { - dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", - dhcp.DISCOVERY_HOSTNAME: "irobot-abc", - dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", - }, - ], + with ( + patch.object(hass.config_entries.flow, "async_init") as mock_init, + patch( + "homeassistant.components.dhcp.DiscoverHosts.async_discover", + return_value=[ + { + dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", + dhcp.DISCOVERY_HOSTNAME: "irobot-abc", + dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", + }, + { + dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", + dhcp.DISCOVERY_HOSTNAME: "irobot-abcdef", + dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", + }, + { + dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", + dhcp.DISCOVERY_HOSTNAME: "irobot-abc", + dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", + }, + ], + ), ): device_tracker_watcher = dhcp.NetworkWatcher( hass, @@ -1077,9 +1094,12 @@ async def test_aiodiscover_does_not_call_again_on_shorter_hostname( async def test_aiodiscover_finds_new_hosts_after_interval(hass: HomeAssistant) -> None: """Test aiodiscover finds new host after interval.""" - with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch( - "homeassistant.components.dhcp.DiscoverHosts.async_discover", - return_value=[], + with ( + patch.object(hass.config_entries.flow, "async_init") as mock_init, + patch( + "homeassistant.components.dhcp.DiscoverHosts.async_discover", + return_value=[], + ), ): device_tracker_watcher = dhcp.NetworkWatcher( hass, @@ -1099,15 +1119,18 @@ async def test_aiodiscover_finds_new_hosts_after_interval(hass: HomeAssistant) - assert len(mock_init.mock_calls) == 0 - with patch.object(hass.config_entries.flow, "async_init") as mock_init, patch( - "homeassistant.components.dhcp.DiscoverHosts.async_discover", - return_value=[ - { - dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", - dhcp.DISCOVERY_HOSTNAME: "connect", - dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", - } - ], + with ( + patch.object(hass.config_entries.flow, "async_init") as mock_init, + patch( + "homeassistant.components.dhcp.DiscoverHosts.async_discover", + return_value=[ + { + dhcp.DISCOVERY_IP_ADDRESS: "192.168.210.56", + dhcp.DISCOVERY_HOSTNAME: "connect", + dhcp.DISCOVERY_MAC_ADDRESS: "b8b7f16db533", + } + ], + ), ): async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(minutes=65)) await hass.async_block_till_done() diff --git a/tests/components/discovergy/conftest.py b/tests/components/discovergy/conftest.py index 2cf59adca04..d3ab3b831f0 100644 --- a/tests/components/discovergy/conftest.py +++ b/tests/components/discovergy/conftest.py @@ -27,12 +27,15 @@ def _meter_last_reading(meter_id: str) -> Reading: @pytest.fixture(name="discovergy") def mock_discovergy() -> Generator[AsyncMock, None, None]: """Mock the pydiscovergy client.""" - with patch( - "homeassistant.components.discovergy.Discovergy", - autospec=True, - ) as mock_discovergy, patch( - "homeassistant.components.discovergy.config_flow.Discovergy", - new=mock_discovergy, + with ( + patch( + "homeassistant.components.discovergy.Discovergy", + autospec=True, + ) as mock_discovergy, + patch( + "homeassistant.components.discovergy.config_flow.Discovergy", + new=mock_discovergy, + ), ): mock = mock_discovergy.return_value mock.meters.return_value = GET_METERS diff --git a/tests/components/dlna_dmr/test_config_flow.py b/tests/components/dlna_dmr/test_config_flow.py index 912b105bb58..32cfd8ad5a9 100644 --- a/tests/components/dlna_dmr/test_config_flow.py +++ b/tests/components/dlna_dmr/test_config_flow.py @@ -587,9 +587,9 @@ async def test_ssdp_ignore_device(hass: HomeAssistant) -> None: discovery = dataclasses.replace(MOCK_DISCOVERY) discovery.upnp = dict(discovery.upnp) - discovery.upnp[ - ssdp.ATTR_UPNP_DEVICE_TYPE - ] = "urn:schemas-upnp-org:device:ZonePlayer:1" + discovery.upnp[ssdp.ATTR_UPNP_DEVICE_TYPE] = ( + "urn:schemas-upnp-org:device:ZonePlayer:1" + ) result = await hass.config_entries.flow.async_init( DLNA_DOMAIN, context={"source": config_entries.SOURCE_SSDP}, diff --git a/tests/components/dnsip/test_config_flow.py b/tests/components/dnsip/test_config_flow.py index eb254908dd3..5bfa1539d44 100644 --- a/tests/components/dnsip/test_config_flow.py +++ b/tests/components/dnsip/test_config_flow.py @@ -36,13 +36,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["data_schema"] == DATA_SCHEMA assert result["errors"] == {} - with patch( - "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", - return_value=RetrieveDNS(), - ), patch( - "homeassistant.components.dnsip.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", + return_value=RetrieveDNS(), + ), + patch( + "homeassistant.components.dnsip.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -76,13 +79,16 @@ async def test_form_adv(hass: HomeAssistant) -> None: assert result["data_schema"] == DATA_SCHEMA_ADV - with patch( - "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", - return_value=RetrieveDNS(), - ), patch( - "homeassistant.components.dnsip.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", + return_value=RetrieveDNS(), + ), + patch( + "homeassistant.components.dnsip.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -154,12 +160,15 @@ async def test_flow_already_exist(hass: HomeAssistant) -> None: ) dns_mock = RetrieveDNS() - with patch( - "homeassistant.components.dnsip.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", - return_value=dns_mock, + with ( + patch( + "homeassistant.components.dnsip.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.dnsip.config_flow.aiodns.DNSResolver", + return_value=dns_mock, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/doorbird/test_config_flow.py b/tests/components/doorbird/test_config_flow.py index 7df281481ad..4939bada6f8 100644 --- a/tests/components/doorbird/test_config_flow.py +++ b/tests/components/doorbird/test_config_flow.py @@ -52,15 +52,19 @@ async def test_user_form(hass: HomeAssistant) -> None: doorbirdapi = _get_mock_doorbirdapi_return_values( ready=[True], info={"WIFI_MAC_ADDR": "macaddr"} ) - with patch( - "homeassistant.components.doorbird.config_flow.DoorBird", - return_value=doorbirdapi, - ), patch( - "homeassistant.components.doorbird.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.doorbird.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.doorbird.config_flow.DoorBird", + return_value=doorbirdapi, + ), + patch( + "homeassistant.components.doorbird.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.doorbird.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_CONFIG, @@ -195,15 +199,20 @@ async def test_form_zeroconf_correct_oui(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.doorbird.config_flow.DoorBird", - return_value=doorbirdapi, - ), patch("homeassistant.components.logbook.async_setup", return_value=True), patch( - "homeassistant.components.doorbird.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.doorbird.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.doorbird.config_flow.DoorBird", + return_value=doorbirdapi, + ), + patch("homeassistant.components.logbook.async_setup", return_value=True), + patch( + "homeassistant.components.doorbird.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.doorbird.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_CONFIG ) diff --git a/tests/components/dormakaba_dkey/test_config_flow.py b/tests/components/dormakaba_dkey/test_config_flow.py index bcb13cd788e..d29e176bb7e 100644 --- a/tests/components/dormakaba_dkey/test_config_flow.py +++ b/tests/components/dormakaba_dkey/test_config_flow.py @@ -165,13 +165,16 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: async def _test_common_success(hass: HomeAssistant, result: FlowResult) -> None: """Test bluetooth and user flow success paths.""" - with patch( - "homeassistant.components.dormakaba_dkey.config_flow.DKEYLock.associate", - return_value=AssociationData(b"1234", b"AABBCCDD"), - ) as mock_associate, patch( - "homeassistant.components.dormakaba_dkey.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.dormakaba_dkey.config_flow.DKEYLock.associate", + return_value=AssociationData(b"1234", b"AABBCCDD"), + ) as mock_associate, + patch( + "homeassistant.components.dormakaba_dkey.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"activation_code": "1234-1234"} ) @@ -343,12 +346,15 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result["step_id"] == "associate" assert result["errors"] is None - with patch( - "homeassistant.components.dormakaba_dkey.config_flow.DKEYLock.associate", - return_value=AssociationData(b"1234", b"AABBCCDD"), - ) as mock_associate, patch( - "homeassistant.components.dormakaba_dkey.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.dormakaba_dkey.config_flow.DKEYLock.associate", + return_value=AssociationData(b"1234", b"AABBCCDD"), + ) as mock_associate, + patch( + "homeassistant.components.dormakaba_dkey.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"activation_code": "1234-1234"} diff --git a/tests/components/downloader/test_config_flow.py b/tests/components/downloader/test_config_flow.py index 53c1be69830..5e75a9b33ba 100644 --- a/tests/components/downloader/test_config_flow.py +++ b/tests/components/downloader/test_config_flow.py @@ -40,11 +40,14 @@ async def test_user_form(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.downloader.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", - return_value=None, + with ( + patch( + "homeassistant.components.downloader.async_setup_entry", return_value=True + ), + patch( + "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", + return_value=None, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -76,11 +79,14 @@ async def test_single_instance_allowed( async def test_import_flow_success(hass: HomeAssistant) -> None: """Test import flow.""" - with patch( - "homeassistant.components.downloader.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", - return_value=None, + with ( + patch( + "homeassistant.components.downloader.async_setup_entry", return_value=True + ), + patch( + "homeassistant.components.downloader.config_flow.DownloaderConfigFlow._validate_input", + return_value=None, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/dremel_3d_printer/test_button.py b/tests/components/dremel_3d_printer/test_button.py index e0ea0cf9eaf..48b39b09cf1 100644 --- a/tests/components/dremel_3d_printer/test_button.py +++ b/tests/components/dremel_3d_printer/test_button.py @@ -44,10 +44,13 @@ async def test_buttons( ) assert mock.call_count == 1 - with patch( - f"homeassistant.components.dremel_3d_printer.Dremel3DPrinter.{function}_print", - side_effect=RuntimeError, - ) as mock, pytest.raises(HomeAssistantError): + with ( + patch( + f"homeassistant.components.dremel_3d_printer.Dremel3DPrinter.{function}_print", + side_effect=RuntimeError, + ) as mock, + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index 7b9e583253b..05881d9c877 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -30,11 +30,15 @@ async def dsmr_connection_fixture(hass): connection_factory = MagicMock(wraps=connection_factory) - with patch( - "homeassistant.components.dsmr.sensor.create_dsmr_reader", connection_factory - ), patch( - "homeassistant.components.dsmr.sensor.create_tcp_dsmr_reader", - connection_factory, + with ( + patch( + "homeassistant.components.dsmr.sensor.create_dsmr_reader", + connection_factory, + ), + patch( + "homeassistant.components.dsmr.sensor.create_tcp_dsmr_reader", + connection_factory, + ), ): yield (connection_factory, transport, protocol) @@ -52,12 +56,15 @@ async def rfxtrx_dsmr_connection_fixture(hass): connection_factory = MagicMock(wraps=connection_factory) - with patch( - "homeassistant.components.dsmr.sensor.create_rfxtrx_dsmr_reader", - connection_factory, - ), patch( - "homeassistant.components.dsmr.sensor.create_rfxtrx_tcp_dsmr_reader", - connection_factory, + with ( + patch( + "homeassistant.components.dsmr.sensor.create_rfxtrx_dsmr_reader", + connection_factory, + ), + patch( + "homeassistant.components.dsmr.sensor.create_rfxtrx_tcp_dsmr_reader", + connection_factory, + ), ): yield (connection_factory, transport, protocol) @@ -130,12 +137,15 @@ async def dsmr_connection_send_validate_fixture(hass): protocol.wait_closed = wait_closed - with patch( - "homeassistant.components.dsmr.config_flow.create_dsmr_reader", - connection_factory, - ), patch( - "homeassistant.components.dsmr.config_flow.create_tcp_dsmr_reader", - connection_factory, + with ( + patch( + "homeassistant.components.dsmr.config_flow.create_dsmr_reader", + connection_factory, + ), + patch( + "homeassistant.components.dsmr.config_flow.create_tcp_dsmr_reader", + connection_factory, + ), ): yield (connection_factory, transport, protocol) @@ -176,11 +186,14 @@ async def rfxtrx_dsmr_connection_send_validate_fixture(hass): protocol.wait_closed = wait_closed - with patch( - "homeassistant.components.dsmr.config_flow.create_rfxtrx_dsmr_reader", - connection_factory, - ), patch( - "homeassistant.components.dsmr.config_flow.create_rfxtrx_tcp_dsmr_reader", - connection_factory, + with ( + patch( + "homeassistant.components.dsmr.config_flow.create_rfxtrx_dsmr_reader", + connection_factory, + ), + patch( + "homeassistant.components.dsmr.config_flow.create_rfxtrx_tcp_dsmr_reader", + connection_factory, + ), ): yield (connection_factory, transport, protocol) diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 5a677794e46..687c6b4a3bc 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -495,9 +495,10 @@ async def test_options_flow(hass: HomeAssistant) -> None: }, ) - with patch( - "homeassistant.components.dsmr.async_setup_entry", return_value=True - ), patch("homeassistant.components.dsmr.async_unload_entry", return_value=True): + with ( + patch("homeassistant.components.dsmr.async_setup_entry", return_value=True), + patch("homeassistant.components.dsmr.async_unload_entry", return_value=True), + ): assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY await hass.async_block_till_done() diff --git a/tests/components/dunehd/test_config_flow.py b/tests/components/dunehd/test_config_flow.py index 1ef799420a5..bf3137e0204 100644 --- a/tests/components/dunehd/test_config_flow.py +++ b/tests/components/dunehd/test_config_flow.py @@ -69,8 +69,9 @@ async def test_duplicate_error(hass: HomeAssistant) -> None: async def test_create_entry(hass: HomeAssistant) -> None: """Test that the user step works.""" - with patch("homeassistant.components.dunehd.async_setup_entry"), patch( - "pdunehd.DuneHDPlayer.update_state", return_value=DUNEHD_STATE + with ( + patch("homeassistant.components.dunehd.async_setup_entry"), + patch("pdunehd.DuneHDPlayer.update_state", return_value=DUNEHD_STATE), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONFIG_HOSTNAME @@ -83,8 +84,9 @@ async def test_create_entry(hass: HomeAssistant) -> None: async def test_create_entry_with_ipv6_address(hass: HomeAssistant) -> None: """Test that the user step works with device IPv6 address..""" - with patch("homeassistant.components.dunehd.async_setup_entry"), patch( - "pdunehd.DuneHDPlayer.update_state", return_value=DUNEHD_STATE + with ( + patch("homeassistant.components.dunehd.async_setup_entry"), + patch("pdunehd.DuneHDPlayer.update_state", return_value=DUNEHD_STATE), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index 94ff75d4a57..2c15c41e40b 100644 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -85,13 +85,16 @@ async def test_async_setup(hass: HomeAssistant) -> None: async def test_service_request_area_preset(hass: HomeAssistant) -> None: """Test requesting and area preset via service call.""" - with patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", - return_value=True, - ), patch( - "dynalite_devices_lib.dynalite.Dynalite.request_area_preset", - return_value=True, - ) as mock_req_area_pres: + with ( + patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ), + patch( + "dynalite_devices_lib.dynalite.Dynalite.request_area_preset", + return_value=True, + ) as mock_req_area_pres, + ): assert await async_setup_component( hass, dynalite.DOMAIN, @@ -157,13 +160,16 @@ async def test_service_request_area_preset(hass: HomeAssistant) -> None: async def test_service_request_channel_level(hass: HomeAssistant) -> None: """Test requesting the level of a channel via service call.""" - with patch( - "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", - return_value=True, - ), patch( - "dynalite_devices_lib.dynalite.Dynalite.request_channel_level", - return_value=True, - ) as mock_req_chan_lvl: + with ( + patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ), + patch( + "dynalite_devices_lib.dynalite.Dynalite.request_channel_level", + return_value=True, + ) as mock_req_chan_lvl, + ): assert await async_setup_component( hass, dynalite.DOMAIN, diff --git a/tests/components/ecobee/test_config_flow.py b/tests/components/ecobee/test_config_flow.py index 832e3f6c66f..91d9f848ffd 100644 --- a/tests/components/ecobee/test_config_flow.py +++ b/tests/components/ecobee/test_config_flow.py @@ -144,10 +144,13 @@ async def test_import_flow_triggered_with_ecobee_conf_and_valid_data_and_valid_t MOCK_ECOBEE_CONF = {ECOBEE_API_KEY: None, ECOBEE_REFRESH_TOKEN: None} - with patch( - "homeassistant.components.ecobee.config_flow.load_json_object", - return_value=MOCK_ECOBEE_CONF, - ), patch("homeassistant.components.ecobee.config_flow.Ecobee") as mock_ecobee: + with ( + patch( + "homeassistant.components.ecobee.config_flow.load_json_object", + return_value=MOCK_ECOBEE_CONF, + ), + patch("homeassistant.components.ecobee.config_flow.Ecobee") as mock_ecobee, + ): mock_ecobee = mock_ecobee.return_value mock_ecobee.refresh_tokens.return_value = True mock_ecobee.api_key = "test-api-key" @@ -173,10 +176,13 @@ async def test_import_flow_triggered_with_ecobee_conf_and_invalid_data( MOCK_ECOBEE_CONF = {} - with patch( - "homeassistant.components.ecobee.config_flow.load_json_object", - return_value=MOCK_ECOBEE_CONF, - ), patch.object(flow, "async_step_user") as mock_async_step_user: + with ( + patch( + "homeassistant.components.ecobee.config_flow.load_json_object", + return_value=MOCK_ECOBEE_CONF, + ), + patch.object(flow, "async_step_user") as mock_async_step_user, + ): await flow.async_step_import(import_data=None) mock_async_step_user.assert_called_once_with( @@ -194,12 +200,14 @@ async def test_import_flow_triggered_with_ecobee_conf_and_valid_data_and_stale_t MOCK_ECOBEE_CONF = {ECOBEE_API_KEY: None, ECOBEE_REFRESH_TOKEN: None} - with patch( - "homeassistant.components.ecobee.config_flow.load_json_object", - return_value=MOCK_ECOBEE_CONF, - ), patch( - "homeassistant.components.ecobee.config_flow.Ecobee" - ) as mock_ecobee, patch.object(flow, "async_step_user") as mock_async_step_user: + with ( + patch( + "homeassistant.components.ecobee.config_flow.load_json_object", + return_value=MOCK_ECOBEE_CONF, + ), + patch("homeassistant.components.ecobee.config_flow.Ecobee") as mock_ecobee, + patch.object(flow, "async_step_user") as mock_async_step_user, + ): mock_ecobee = mock_ecobee.return_value mock_ecobee.refresh_tokens.return_value = False diff --git a/tests/components/econet/test_config_flow.py b/tests/components/econet/test_config_flow.py index 7cd6d35deeb..7647b77e0a6 100644 --- a/tests/components/econet/test_config_flow.py +++ b/tests/components/econet/test_config_flow.py @@ -23,10 +23,13 @@ async def test_bad_credentials(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "pyeconet.EcoNetApiInterface.login", - side_effect=InvalidCredentialsError(), - ), patch("homeassistant.components.econet.async_setup_entry", return_value=True): + with ( + patch( + "pyeconet.EcoNetApiInterface.login", + side_effect=InvalidCredentialsError(), + ), + patch("homeassistant.components.econet.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -51,10 +54,13 @@ async def test_generic_error_from_library(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "pyeconet.EcoNetApiInterface.login", - side_effect=PyeconetError(), - ), patch("homeassistant.components.econet.async_setup_entry", return_value=True): + with ( + patch( + "pyeconet.EcoNetApiInterface.login", + side_effect=PyeconetError(), + ), + patch("homeassistant.components.econet.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -79,10 +85,13 @@ async def test_auth_worked(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "pyeconet.EcoNetApiInterface.login", - return_value=EcoNetApiInterface, - ), patch("homeassistant.components.econet.async_setup_entry", return_value=True): + with ( + patch( + "pyeconet.EcoNetApiInterface.login", + return_value=EcoNetApiInterface, + ), + patch("homeassistant.components.econet.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -114,10 +123,13 @@ async def test_already_configured(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "pyeconet.EcoNetApiInterface.login", - return_value=EcoNetApiInterface, - ), patch("homeassistant.components.econet.async_setup_entry", return_value=True): + with ( + patch( + "pyeconet.EcoNetApiInterface.login", + return_value=EcoNetApiInterface, + ), + patch("homeassistant.components.econet.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ diff --git a/tests/components/ecovacs/conftest.py b/tests/components/ecovacs/conftest.py index 29649e91573..1a313957c3e 100644 --- a/tests/components/ecovacs/conftest.py +++ b/tests/components/ecovacs/conftest.py @@ -55,12 +55,15 @@ def device_fixture() -> str: @pytest.fixture def mock_authenticator(device_fixture: str) -> Generator[Mock, None, None]: """Mock the authenticator.""" - with patch( - "homeassistant.components.ecovacs.controller.Authenticator", - autospec=True, - ) as mock, patch( - "homeassistant.components.ecovacs.config_flow.Authenticator", - new=mock, + with ( + patch( + "homeassistant.components.ecovacs.controller.Authenticator", + autospec=True, + ) as mock, + patch( + "homeassistant.components.ecovacs.config_flow.Authenticator", + new=mock, + ), ): authenticator = mock.return_value authenticator.authenticate.return_value = Credentials("token", "user_id", 0) @@ -97,12 +100,15 @@ def mock_authenticator_authenticate(mock_authenticator: Mock) -> AsyncMock: @pytest.fixture def mock_mqtt_client(mock_authenticator: Mock) -> Mock: """Mock the MQTT client.""" - with patch( - "homeassistant.components.ecovacs.controller.MqttClient", - autospec=True, - ) as mock, patch( - "homeassistant.components.ecovacs.config_flow.MqttClient", - new=mock, + with ( + patch( + "homeassistant.components.ecovacs.controller.MqttClient", + autospec=True, + ) as mock, + patch( + "homeassistant.components.ecovacs.config_flow.MqttClient", + new=mock, + ), ): client = mock.return_value client._authenticator = mock_authenticator diff --git a/tests/components/electrasmart/test_config_flow.py b/tests/components/electrasmart/test_config_flow.py index 0da8ae4e400..957c140862f 100644 --- a/tests/components/electrasmart/test_config_flow.py +++ b/tests/components/electrasmart/test_config_flow.py @@ -49,15 +49,19 @@ async def test_one_time_password(hass: HomeAssistant): mock_generate_token = loads(load_fixture("generate_token_response.json", DOMAIN)) mock_otp_response = loads(load_fixture("otp_response.json", DOMAIN)) - with patch( - "electrasmart.api.ElectraAPI.generate_new_token", - return_value=mock_generate_token, - ), patch( - "electrasmart.api.ElectraAPI.validate_one_time_password", - return_value=mock_otp_response, - ), patch( - "electrasmart.api.ElectraAPI.fetch_devices", - return_value=[], + with ( + patch( + "electrasmart.api.ElectraAPI.generate_new_token", + return_value=mock_generate_token, + ), + patch( + "electrasmart.api.ElectraAPI.validate_one_time_password", + return_value=mock_otp_response, + ), + patch( + "electrasmart.api.ElectraAPI.fetch_devices", + return_value=[], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -75,12 +79,15 @@ async def test_one_time_password(hass: HomeAssistant): async def test_one_time_password_api_error(hass: HomeAssistant): """Test one time password.""" mock_generate_token = loads(load_fixture("generate_token_response.json", DOMAIN)) - with patch( - "electrasmart.api.ElectraAPI.generate_new_token", - return_value=mock_generate_token, - ), patch( - "electrasmart.api.ElectraAPI.validate_one_time_password", - side_effect=ElectraApiError, + with ( + patch( + "electrasmart.api.ElectraAPI.generate_new_token", + return_value=mock_generate_token, + ), + patch( + "electrasmart.api.ElectraAPI.validate_one_time_password", + side_effect=ElectraApiError, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -144,12 +151,15 @@ async def test_invalid_auth(hass: HomeAssistant): ) mock_invalid_otp_response = loads(load_fixture("invalid_otp_response.json", DOMAIN)) - with patch( - "electrasmart.api.ElectraAPI.generate_new_token", - return_value=mock_generate_token_response, - ), patch( - "electrasmart.api.ElectraAPI.validate_one_time_password", - return_value=mock_invalid_otp_response, + with ( + patch( + "electrasmart.api.ElectraAPI.generate_new_token", + return_value=mock_generate_token_response, + ), + patch( + "electrasmart.api.ElectraAPI.validate_one_time_password", + return_value=mock_invalid_otp_response, + ), ): # test with required result = await hass.config_entries.flow.async_init( diff --git a/tests/components/elgato/conftest.py b/tests/components/elgato/conftest.py index b1dd94ce069..5a783c509c2 100644 --- a/tests/components/elgato/conftest.py +++ b/tests/components/elgato/conftest.py @@ -65,10 +65,11 @@ def mock_elgato( device_fixtures: str, state_variant: str ) -> Generator[None, MagicMock, None]: """Return a mocked Elgato client.""" - with patch( - "homeassistant.components.elgato.coordinator.Elgato", autospec=True - ) as elgato_mock, patch( - "homeassistant.components.elgato.config_flow.Elgato", new=elgato_mock + with ( + patch( + "homeassistant.components.elgato.coordinator.Elgato", autospec=True + ) as elgato_mock, + patch("homeassistant.components.elgato.config_flow.Elgato", new=elgato_mock), ): elgato = elgato_mock.return_value elgato.info.return_value = Info.from_json( diff --git a/tests/components/elkm1/__init__.py b/tests/components/elkm1/__init__.py index cb34f78360a..bec83002f0f 100644 --- a/tests/components/elkm1/__init__.py +++ b/tests/components/elkm1/__init__.py @@ -50,12 +50,15 @@ def _patch_elk(elk=None): @contextmanager def _patcher(): - with patch( - "homeassistant.components.elkm1.config_flow.Elk", - new=_elk, - ), patch( - "homeassistant.components.elkm1.config_flow.Elk", - new=_elk, + with ( + patch( + "homeassistant.components.elkm1.config_flow.Elk", + new=_elk, + ), + patch( + "homeassistant.components.elkm1.config_flow.Elk", + new=_elk, + ), ): yield diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index b69c23bf9cb..592efc16b5e 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -72,12 +72,17 @@ async def test_form_user_with_secure_elk_no_discovery(hass: HomeAssistant) -> No mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -124,12 +129,17 @@ async def test_form_user_with_insecure_elk_skip_discovery(hass: HomeAssistant) - mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -176,12 +186,17 @@ async def test_form_user_with_insecure_elk_no_discovery(hass: HomeAssistant) -> mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -228,12 +243,15 @@ async def test_form_user_with_insecure_elk_times_out(hass: HomeAssistant) -> Non mocked_elk = mock_elk(invalid_auth=False, sync_complete=False) - with patch( - "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", - 0, - ), patch( - "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", 0 - ), _patch_discovery(), _patch_elk(elk=mocked_elk): + with ( + patch( + "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", + 0, + ), + patch("homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", 0), + _patch_discovery(), + _patch_elk(elk=mocked_elk), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -312,12 +330,17 @@ async def test_form_user_with_secure_elk_with_discovery(hass: HomeAssistant) -> ) await hass.async_block_till_done() - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -365,12 +388,17 @@ async def test_form_user_with_secure_elk_with_discovery_pick_manual( ) await hass.async_block_till_done() - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -421,12 +449,17 @@ async def test_form_user_with_secure_elk_with_discovery_pick_manual_direct_disco ) await hass.async_block_till_done() - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -468,12 +501,17 @@ async def test_form_user_with_tls_elk_no_discovery(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -514,12 +552,17 @@ async def test_form_user_with_non_secure_elk_no_discovery(hass: HomeAssistant) - mocked_elk = mock_elk(invalid_auth=None, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -558,12 +601,17 @@ async def test_form_user_with_serial_elk_no_discovery(hass: HomeAssistant) -> No mocked_elk = mock_elk(invalid_auth=None, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -596,12 +644,17 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=None, sync_complete=None) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", - 0, - ), patch( - "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", - 0, + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", + 0, + ), + patch( + "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", + 0, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -627,12 +680,17 @@ async def test_unknown_exception(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=None, sync_complete=None, exception=OSError) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", - 0, - ), patch( - "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", - 0, + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.config_flow.VALIDATE_TIMEOUT", + 0, + ), + patch( + "homeassistant.components.elkm1.config_flow.LOGIN_TIMEOUT", + 0, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -707,12 +765,17 @@ async def test_form_import(hass: HomeAssistant) -> None: """Test we get the form with import source.""" mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -772,12 +835,17 @@ async def test_form_import_device_discovered(hass: HomeAssistant) -> None: """Test we can import with discovery.""" mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -837,12 +905,17 @@ async def test_form_import_non_secure_device_discovered(hass: HomeAssistant) -> """Test we can import non-secure with discovery.""" mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -876,12 +949,17 @@ async def test_form_import_non_secure_non_stanadard_port_device_discovered( """Test we can import non-secure non standard port with discovery.""" mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -1091,12 +1169,17 @@ async def test_discovered_by_discovery(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -1136,12 +1219,17 @@ async def test_discovered_by_discovery_non_standard_port(hass: HomeAssistant) -> mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -1202,12 +1290,17 @@ async def test_discovered_by_dhcp_udp_responds(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -1247,14 +1340,17 @@ async def test_discovered_by_dhcp_udp_responds_with_nonsecure_port( mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(device=ELK_NON_SECURE_DISCOVERY), _patch_elk( - elk=mocked_elk - ), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(device=ELK_NON_SECURE_DISCOVERY), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -1299,12 +1395,17 @@ async def test_discovered_by_dhcp_udp_responds_existing_config_entry( mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "test-username", "password": "test-password"}, @@ -1362,12 +1463,17 @@ async def test_multiple_instances_with_discovery(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - with _patch_discovery(device=elk_discovery_1), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(device=elk_discovery_1), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -1409,10 +1515,14 @@ async def test_multiple_instances_with_discovery(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - with _patch_discovery(device=elk_discovery_2), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(device=elk_discovery_2), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -1447,10 +1557,14 @@ async def test_multiple_instances_with_discovery(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=None, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -1501,12 +1615,17 @@ async def test_multiple_instances_with_tls_v12(hass: HomeAssistant) -> None: assert result2["type"] == "form" assert not result["errors"] assert result2["step_id"] == "discovered_connection" - with _patch_discovery(device=elk_discovery_1), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(device=elk_discovery_1), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -1549,10 +1668,14 @@ async def test_multiple_instances_with_tls_v12(hass: HomeAssistant) -> None: ) await hass.async_block_till_done() - with _patch_discovery(device=elk_discovery_2), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(device=elk_discovery_2), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -1588,10 +1711,14 @@ async def test_multiple_instances_with_tls_v12(hass: HomeAssistant) -> None: mocked_elk = mock_elk(invalid_auth=False, sync_complete=True) - with _patch_discovery(no_device=True), _patch_elk(elk=mocked_elk), patch( - "homeassistant.components.elkm1.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_elk(elk=mocked_elk), + patch( + "homeassistant.components.elkm1.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/emonitor/test_config_flow.py b/tests/components/emonitor/test_config_flow.py index 56474673f70..07809a83d89 100644 --- a/tests/components/emonitor/test_config_flow.py +++ b/tests/components/emonitor/test_config_flow.py @@ -35,13 +35,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.emonitor.config_flow.Emonitor.async_get_status", - return_value=_mock_emonitor(), - ), patch( - "homeassistant.components.emonitor.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.emonitor.config_flow.Emonitor.async_get_status", + return_value=_mock_emonitor(), + ), + patch( + "homeassistant.components.emonitor.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -198,12 +201,15 @@ async def test_user_unique_id_already_exists(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.emonitor.config_flow.Emonitor.async_get_status", - return_value=_mock_emonitor(), - ), patch( - "homeassistant.components.emonitor.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.emonitor.config_flow.Emonitor.async_get_status", + return_value=_mock_emonitor(), + ), + patch( + "homeassistant.components.emonitor.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 9bc29ce4899..6bc99db6e60 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -133,14 +133,16 @@ def test_config_alexa_entity_id_to_number() -> None: async def test_setup_works(hass: HomeAssistant) -> None: """Test setup works.""" hass.config.components.add("network") - with patch( - "homeassistant.components.emulated_hue.async_create_upnp_datagram_endpoint", - AsyncMock(), - ) as mock_create_upnp_datagram_endpoint, patch( - "homeassistant.components.emulated_hue.async_get_source_ip" - ), patch( - "homeassistant.components.emulated_hue.web.TCPSite", - return_value=Mock(spec_set=web.TCPSite), + with ( + patch( + "homeassistant.components.emulated_hue.async_create_upnp_datagram_endpoint", + AsyncMock(), + ) as mock_create_upnp_datagram_endpoint, + patch("homeassistant.components.emulated_hue.async_get_source_ip"), + patch( + "homeassistant.components.emulated_hue.web.TCPSite", + return_value=Mock(spec_set=web.TCPSite), + ), ): mock_create_upnp_datagram_endpoint.return_value = AsyncMock( spec=UPNPResponderProtocol diff --git a/tests/components/emulated_roku/test_init.py b/tests/components/emulated_roku/test_init.py index 17a6e106fc3..00316c66425 100644 --- a/tests/components/emulated_roku/test_init.py +++ b/tests/components/emulated_roku/test_init.py @@ -9,9 +9,12 @@ from homeassistant.setup import async_setup_component async def test_config_required_fields(hass: HomeAssistant, mock_get_source_ip) -> None: """Test that configuration is successful with required fields.""" - with patch.object(emulated_roku, "configured_servers", return_value=[]), patch( - "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", - return_value=Mock(start=AsyncMock(), close=AsyncMock()), + with ( + patch.object(emulated_roku, "configured_servers", return_value=[]), + patch( + "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", + return_value=Mock(start=AsyncMock(), close=AsyncMock()), + ), ): assert ( await async_setup_component( @@ -36,11 +39,14 @@ async def test_config_already_registered_not_configured( hass: HomeAssistant, mock_get_source_ip ) -> None: """Test that an already registered name causes the entry to be ignored.""" - with patch( - "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", - return_value=Mock(start=AsyncMock(), close=AsyncMock()), - ) as instantiate, patch.object( - emulated_roku, "configured_servers", return_value=["Emulated Roku Test"] + with ( + patch( + "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", + return_value=Mock(start=AsyncMock(), close=AsyncMock()), + ) as instantiate, + patch.object( + emulated_roku, "configured_servers", return_value=["Emulated Roku Test"] + ), ): assert ( await async_setup_component( diff --git a/tests/components/enocean/test_config_flow.py b/tests/components/enocean/test_config_flow.py index 0acecd6c0b4..45a4e6e387f 100644 --- a/tests/components/enocean/test_config_flow.py +++ b/tests/components/enocean/test_config_flow.py @@ -75,8 +75,9 @@ async def test_detection_flow_with_custom_path(hass: HomeAssistant) -> None: USER_PROVIDED_PATH = EnOceanFlowHandler.MANUAL_PATH_VALUE FAKE_DONGLE_PATH = "/fake/dongle" - with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)), patch( - DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH]) + with ( + patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=True)), + patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -93,8 +94,9 @@ async def test_detection_flow_with_invalid_path(hass: HomeAssistant) -> None: USER_PROVIDED_PATH = "/invalid/path" FAKE_DONGLE_PATH = "/fake/dongle" - with patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)), patch( - DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH]) + with ( + patch(DONGLE_VALIDATE_PATH_METHOD, Mock(return_value=False)), + patch(DONGLE_DETECT_METHOD, Mock(return_value=[FAKE_DONGLE_PATH])), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/enphase_envoy/conftest.py b/tests/components/enphase_envoy/conftest.py index 91a37c7fa7f..40d409aea8e 100644 --- a/tests/components/enphase_envoy/conftest.py +++ b/tests/components/enphase_envoy/conftest.py @@ -344,12 +344,15 @@ def mock_envoy_fixture( @pytest.fixture(name="setup_enphase_envoy") async def setup_enphase_envoy_fixture(hass, config, mock_envoy): """Define a fixture to set up Enphase Envoy.""" - with patch( - "homeassistant.components.enphase_envoy.config_flow.Envoy", - return_value=mock_envoy, - ), patch( - "homeassistant.components.enphase_envoy.Envoy", - return_value=mock_envoy, + with ( + patch( + "homeassistant.components.enphase_envoy.config_flow.Envoy", + return_value=mock_envoy, + ), + patch( + "homeassistant.components.enphase_envoy.Envoy", + return_value=mock_envoy, + ), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/enphase_envoy/test_sensor.py b/tests/components/enphase_envoy/test_sensor.py index 784fb47e8c1..3d6a0ec5757 100644 --- a/tests/components/enphase_envoy/test_sensor.py +++ b/tests/components/enphase_envoy/test_sensor.py @@ -17,15 +17,19 @@ from tests.common import MockConfigEntry @pytest.fixture(name="setup_enphase_envoy_sensor") async def setup_enphase_envoy_sensor_fixture(hass, config, mock_envoy): """Define a fixture to set up Enphase Envoy with sensor platform only.""" - with patch( - "homeassistant.components.enphase_envoy.config_flow.Envoy", - return_value=mock_envoy, - ), patch( - "homeassistant.components.enphase_envoy.Envoy", - return_value=mock_envoy, - ), patch( - "homeassistant.components.enphase_envoy.PLATFORMS", - [Platform.SENSOR], + with ( + patch( + "homeassistant.components.enphase_envoy.config_flow.Envoy", + return_value=mock_envoy, + ), + patch( + "homeassistant.components.enphase_envoy.Envoy", + return_value=mock_envoy, + ), + patch( + "homeassistant.components.enphase_envoy.PLATFORMS", + [Platform.SENSOR], + ), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/environment_canada/test_config_flow.py b/tests/components/environment_canada/test_config_flow.py index 3b6661b53bc..e9513644947 100644 --- a/tests/components/environment_canada/test_config_flow.py +++ b/tests/components/environment_canada/test_config_flow.py @@ -51,9 +51,12 @@ def mocked_ec( async def test_create_entry(hass: HomeAssistant) -> None: """Test creating an entry.""" - with mocked_ec(), patch( - "homeassistant.components.environment_canada.async_setup_entry", - return_value=True, + with ( + mocked_ec(), + patch( + "homeassistant.components.environment_canada.async_setup_entry", + return_value=True, + ), ): flow = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -76,9 +79,12 @@ async def test_create_same_entry_twice(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with mocked_ec(), patch( - "homeassistant.components.environment_canada.async_setup_entry", - return_value=True, + with ( + mocked_ec(), + patch( + "homeassistant.components.environment_canada.async_setup_entry", + return_value=True, + ), ): flow = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -122,9 +128,12 @@ async def test_exception_handling(hass: HomeAssistant, error) -> None: async def test_lat_lon_not_specified(hass: HomeAssistant) -> None: """Test that the import step works when coordinates are not specified.""" - with mocked_ec(), patch( - "homeassistant.components.environment_canada.async_setup_entry", - return_value=True, + with ( + mocked_ec(), + patch( + "homeassistant.components.environment_canada.async_setup_entry", + return_value=True, + ), ): fake_config = dict(FAKE_CONFIG) del fake_config[CONF_LATITUDE] diff --git a/tests/components/environment_canada/test_diagnostics.py b/tests/components/environment_canada/test_diagnostics.py index 75389defb86..8f800111d39 100644 --- a/tests/components/environment_canada/test_diagnostics.py +++ b/tests/components/environment_canada/test_diagnostics.py @@ -52,17 +52,23 @@ async def init_integration(hass: HomeAssistant) -> MockConfigEntry: radar_mock.image = b"GIF..." radar_mock.timestamp = datetime(2022, 10, 4, tzinfo=UTC) - with patch( - "homeassistant.components.environment_canada.ECWeather", - return_value=weather_mock, - ), patch( - "homeassistant.components.environment_canada.ECAirQuality", - return_value=mock_ec(), - ), patch( - "homeassistant.components.environment_canada.ECRadar", return_value=radar_mock - ), patch( - "homeassistant.components.environment_canada.config_flow.ECWeather", - return_value=weather_mock, + with ( + patch( + "homeassistant.components.environment_canada.ECWeather", + return_value=weather_mock, + ), + patch( + "homeassistant.components.environment_canada.ECAirQuality", + return_value=mock_ec(), + ), + patch( + "homeassistant.components.environment_canada.ECRadar", + return_value=radar_mock, + ), + patch( + "homeassistant.components.environment_canada.config_flow.ECWeather", + return_value=weather_mock, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/epion/conftest.py b/tests/components/epion/conftest.py index 2290d0d4c8f..51fed636ca1 100644 --- a/tests/components/epion/conftest.py +++ b/tests/components/epion/conftest.py @@ -14,12 +14,15 @@ def mock_epion(): "epion/get_current_one_device.json" ) mock_epion_api = MagicMock() - with patch( - "homeassistant.components.epion.config_flow.Epion", - return_value=mock_epion_api, - ) as mock_epion_api, patch( - "homeassistant.components.epion.Epion", - return_value=mock_epion_api, + with ( + patch( + "homeassistant.components.epion.config_flow.Epion", + return_value=mock_epion_api, + ) as mock_epion_api, + patch( + "homeassistant.components.epion.Epion", + return_value=mock_epion_api, + ), ): mock_epion_api.return_value.get_current.return_value = current_one_device_data yield mock_epion_api diff --git a/tests/components/epson/test_config_flow.py b/tests/components/epson/test_config_flow.py index d18d7fe5f4a..c6ca921df0f 100644 --- a/tests/components/epson/test_config_flow.py +++ b/tests/components/epson/test_config_flow.py @@ -20,16 +20,20 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.epson.Projector.get_power", - return_value="01", - ), patch( - "homeassistant.components.epson.Projector.get_serial_number", - return_value="12345", - ), patch( - "homeassistant.components.epson.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.epson.Projector.get_power", + return_value="01", + ), + patch( + "homeassistant.components.epson.Projector.get_serial_number", + return_value="12345", + ), + patch( + "homeassistant.components.epson.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "1.1.1.1", CONF_NAME: "test-epson"}, diff --git a/tests/components/epson/test_media_player.py b/tests/components/epson/test_media_player.py index e5869777482..000071054f1 100644 --- a/tests/components/epson/test_media_player.py +++ b/tests/components/epson/test_media_player.py @@ -34,12 +34,15 @@ async def test_set_unique_id( entity_entry = entity_registry.async_get("media_player.epson") assert entity_entry assert entity_entry.unique_id == entry.entry_id - with patch( - "homeassistant.components.epson.Projector.get_power", return_value="01" - ), patch( - "homeassistant.components.epson.Projector.get_serial_number", return_value="123" - ), patch( - "homeassistant.components.epson.Projector.get_property", + with ( + patch("homeassistant.components.epson.Projector.get_power", return_value="01"), + patch( + "homeassistant.components.epson.Projector.get_serial_number", + return_value="123", + ), + patch( + "homeassistant.components.epson.Projector.get_property", + ), ): freezer.tick(timedelta(seconds=30)) async_fire_time_changed(hass) diff --git a/tests/components/escea/test_config_flow.py b/tests/components/escea/test_config_flow.py index 0bb128f717e..7d467fc50a0 100644 --- a/tests/components/escea/test_config_flow.py +++ b/tests/components/escea/test_config_flow.py @@ -47,10 +47,11 @@ async def test_not_found( ) -> None: """Test not finding any Escea controllers.""" - with patch( - "homeassistant.components.escea.discovery.pescea_discovery_service" - ) as discovery_service, patch( - "homeassistant.components.escea.config_flow.TIMEOUT_DISCOVERY", 0 + with ( + patch( + "homeassistant.components.escea.discovery.pescea_discovery_service" + ) as discovery_service, + patch("homeassistant.components.escea.config_flow.TIMEOUT_DISCOVERY", 0), ): discovery_service.return_value = mock_discovery_service @@ -75,12 +76,15 @@ async def test_found( """Test finding an Escea controller.""" mock_discovery_service.controllers["test-uid"] = mock_controller - with patch( - "homeassistant.components.escea.async_setup_entry", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.escea.discovery.pescea_discovery_service" - ) as discovery_service: + with ( + patch( + "homeassistant.components.escea.async_setup_entry", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.escea.discovery.pescea_discovery_service" + ) as discovery_service, + ): discovery_service.return_value = mock_discovery_service mock_discovery_service.start_discovery.side_effect = _mock_start_discovery( discovery_service, mock_controller diff --git a/tests/components/esphome/conftest.py b/tests/components/esphome/conftest.py index 09a23824054..e51fc663b59 100644 --- a/tests/components/esphome/conftest.py +++ b/tests/components/esphome/conftest.py @@ -152,11 +152,13 @@ def mock_client(mock_device_info) -> APIClient: mock_client.address = "127.0.0.1" mock_client.api_version = APIVersion(99, 99) - with patch( - "homeassistant.components.esphome.manager.ReconnectLogic", - BaseMockReconnectLogic, - ), patch("homeassistant.components.esphome.APIClient", mock_client), patch( - "homeassistant.components.esphome.config_flow.APIClient", mock_client + with ( + patch( + "homeassistant.components.esphome.manager.ReconnectLogic", + BaseMockReconnectLogic, + ), + patch("homeassistant.components.esphome.APIClient", mock_client), + patch("homeassistant.components.esphome.config_flow.APIClient", mock_client), ): yield mock_client diff --git a/tests/components/esphome/test_dashboard.py b/tests/components/esphome/test_dashboard.py index 11234e790c5..51b9b535caa 100644 --- a/tests/components/esphome/test_dashboard.py +++ b/tests/components/esphome/test_dashboard.py @@ -98,11 +98,14 @@ async def test_setup_dashboard_fails_when_already_setup( await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - with patch.object( - dashboard.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError - ) as mock_get_devices, patch( - "homeassistant.components.esphome.async_setup_entry", return_value=True - ) as mock_setup: + with ( + patch.object( + dashboard.ESPHomeDashboardAPI, "get_devices", side_effect=TimeoutError + ) as mock_get_devices, + patch( + "homeassistant.components.esphome.async_setup_entry", return_value=True + ) as mock_setup, + ): await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052) await hass.async_block_till_done() @@ -166,12 +169,15 @@ async def test_new_dashboard_fix_reauth( await dashboard.async_get_dashboard(hass).async_refresh() - with patch( - "homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key", - return_value=VALID_NOISE_PSK, - ) as mock_get_encryption_key, patch( - "homeassistant.components.esphome.async_setup_entry", return_value=True - ) as mock_setup: + with ( + patch( + "homeassistant.components.esphome.dashboard.ESPHomeDashboardAPI.get_encryption_key", + return_value=VALID_NOISE_PSK, + ) as mock_get_encryption_key, + patch( + "homeassistant.components.esphome.async_setup_entry", return_value=True + ) as mock_setup, + ): await dashboard.async_set_dashboard_info(hass, "test-slug", "test-host", 6052) await hass.async_block_till_done() diff --git a/tests/components/esphome/test_update.py b/tests/components/esphome/test_update.py index d17f2f4623a..959ad12876d 100644 --- a/tests/components/esphome/test_update.py +++ b/tests/components/esphome/test_update.py @@ -101,13 +101,17 @@ async def test_update_entity( return # Compile failed, don't try to upload - with patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=False - ) as mock_compile, patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True - ) as mock_upload, pytest.raises( - HomeAssistantError, - match="compiling", + with ( + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=False + ) as mock_compile, + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True + ) as mock_upload, + pytest.raises( + HomeAssistantError, + match="compiling", + ), ): await hass.services.async_call( "update", @@ -122,13 +126,17 @@ async def test_update_entity( assert len(mock_upload.mock_calls) == 0 # Compile success, upload fails - with patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True - ) as mock_compile, patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False - ) as mock_upload, pytest.raises( - HomeAssistantError, - match="OTA", + with ( + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True + ) as mock_compile, + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=False + ) as mock_upload, + pytest.raises( + HomeAssistantError, + match="OTA", + ), ): await hass.services.async_call( "update", @@ -144,11 +152,14 @@ async def test_update_entity( assert mock_upload.mock_calls[0][1][0] == "test.yaml" # Everything works - with patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True - ) as mock_compile, patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True - ) as mock_upload: + with ( + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.compile", return_value=True + ) as mock_compile, + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.upload", return_value=True + ) as mock_upload, + ): await hass.services.async_call( "update", "install", @@ -260,12 +271,15 @@ async def test_update_entity_dashboard_not_available_startup( mock_dashboard, ) -> None: """Test ESPHome update entity when dashboard is not available at startup.""" - with patch( - "homeassistant.components.esphome.update.DomainData.get_entry_data", - return_value=Mock(available=True, device_info=mock_device_info), - ), patch( - "esphome_dashboard_api.ESPHomeDashboardAPI.get_devices", - side_effect=TimeoutError, + with ( + patch( + "homeassistant.components.esphome.update.DomainData.get_entry_data", + return_value=Mock(available=True, device_info=mock_device_info), + ), + patch( + "esphome_dashboard_api.ESPHomeDashboardAPI.get_devices", + side_effect=TimeoutError, + ), ): await async_get_dashboard(hass).async_refresh() assert await hass.config_entries.async_forward_entry_setup( diff --git a/tests/components/evil_genius_labs/conftest.py b/tests/components/evil_genius_labs/conftest.py index dd3f5fe24f4..49092da75c7 100644 --- a/tests/components/evil_genius_labs/conftest.py +++ b/tests/components/evil_genius_labs/conftest.py @@ -42,18 +42,23 @@ async def setup_evil_genius_labs( hass, config_entry, all_fixture, info_fixture, product_fixture, platforms ): """Test up Evil Genius Labs instance.""" - with patch( - "pyevilgenius.EvilGeniusDevice.get_all", - return_value=all_fixture, - ), patch( - "pyevilgenius.EvilGeniusDevice.get_info", - return_value=info_fixture, - ), patch( - "pyevilgenius.EvilGeniusDevice.get_product", - return_value=product_fixture, - ), patch( - "homeassistant.components.evil_genius_labs.PLATFORMS", - platforms, + with ( + patch( + "pyevilgenius.EvilGeniusDevice.get_all", + return_value=all_fixture, + ), + patch( + "pyevilgenius.EvilGeniusDevice.get_info", + return_value=info_fixture, + ), + patch( + "pyevilgenius.EvilGeniusDevice.get_product", + return_value=product_fixture, + ), + patch( + "homeassistant.components.evil_genius_labs.PLATFORMS", + platforms, + ), ): assert await async_setup_component(hass, "evil_genius_labs", {}) await hass.async_block_till_done() diff --git a/tests/components/evil_genius_labs/test_config_flow.py b/tests/components/evil_genius_labs/test_config_flow.py index 0c3ab2298ea..b6bdae940ba 100644 --- a/tests/components/evil_genius_labs/test_config_flow.py +++ b/tests/components/evil_genius_labs/test_config_flow.py @@ -21,19 +21,24 @@ async def test_form( assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "pyevilgenius.EvilGeniusDevice.get_all", - return_value=all_fixture, - ), patch( - "pyevilgenius.EvilGeniusDevice.get_info", - return_value=info_fixture, - ), patch( - "pyevilgenius.EvilGeniusDevice.get_product", - return_value=product_fixture, - ), patch( - "homeassistant.components.evil_genius_labs.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyevilgenius.EvilGeniusDevice.get_all", + return_value=all_fixture, + ), + patch( + "pyevilgenius.EvilGeniusDevice.get_info", + return_value=info_fixture, + ), + patch( + "pyevilgenius.EvilGeniusDevice.get_product", + return_value=product_fixture, + ), + patch( + "homeassistant.components.evil_genius_labs.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/evil_genius_labs/test_light.py b/tests/components/evil_genius_labs/test_light.py index e0ecb5415a7..9b93e8f66cf 100644 --- a/tests/components/evil_genius_labs/test_light.py +++ b/tests/components/evil_genius_labs/test_light.py @@ -29,11 +29,10 @@ async def test_works(hass: HomeAssistant, setup_evil_genius_labs) -> None: @pytest.mark.parametrize("platforms", [("light",)]) async def test_turn_on_color(hass: HomeAssistant, setup_evil_genius_labs) -> None: """Test turning on with a color.""" - with patch( - "pyevilgenius.EvilGeniusDevice.set_path_value" - ) as mock_set_path_value, patch( - "pyevilgenius.EvilGeniusDevice.set_rgb_color" - ) as mock_set_rgb_color: + with ( + patch("pyevilgenius.EvilGeniusDevice.set_path_value") as mock_set_path_value, + patch("pyevilgenius.EvilGeniusDevice.set_rgb_color") as mock_set_rgb_color, + ): await hass.services.async_call( "light", "turn_on", diff --git a/tests/components/ezviz/conftest.py b/tests/components/ezviz/conftest.py index ccfa9616efa..10fd0406a1c 100644 --- a/tests/components/ezviz/conftest.py +++ b/tests/components/ezviz/conftest.py @@ -22,9 +22,12 @@ def mock_ffmpeg(hass): @pytest.fixture def ezviz_test_rtsp_config_flow(hass): """Mock the EzvizApi for easier testing.""" - with patch.object(TestRTSPAuth, "main", return_value=True), patch( - "homeassistant.components.ezviz.config_flow.TestRTSPAuth" - ) as mock_ezviz_test_rtsp: + with ( + patch.object(TestRTSPAuth, "main", return_value=True), + patch( + "homeassistant.components.ezviz.config_flow.TestRTSPAuth" + ) as mock_ezviz_test_rtsp, + ): instance = mock_ezviz_test_rtsp.return_value = TestRTSPAuth( "test-ip", "test-username", @@ -39,9 +42,10 @@ def ezviz_test_rtsp_config_flow(hass): @pytest.fixture def ezviz_config_flow(hass): """Mock the EzvizAPI for easier config flow testing.""" - with patch.object(EzvizClient, "login", return_value=True), patch( - "homeassistant.components.ezviz.config_flow.EzvizClient" - ) as mock_ezviz: + with ( + patch.object(EzvizClient, "login", return_value=True), + patch("homeassistant.components.ezviz.config_flow.EzvizClient") as mock_ezviz, + ): instance = mock_ezviz.return_value = EzvizClient( "test-username", "test-password", diff --git a/tests/components/faa_delays/test_config_flow.py b/tests/components/faa_delays/test_config_flow.py index d164eab918f..92a8929afbf 100644 --- a/tests/components/faa_delays/test_config_flow.py +++ b/tests/components/faa_delays/test_config_flow.py @@ -28,10 +28,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch.object(faadelays.Airport, "update", new=mock_valid_airport), patch( - "homeassistant.components.faa_delays.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch.object(faadelays.Airport, "update", new=mock_valid_airport), + patch( + "homeassistant.components.faa_delays.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index 898e46a7c84..f836d233670 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -198,10 +198,13 @@ async def test_storage_data_writing( """Test writing to storage.""" storage_data: dict[str, str] = {URL: "2018-04-30T05:10:00+00:00"} - with patch( - "feedparser.http.get", - return_value=feed_one_event, - ), patch("homeassistant.components.feedreader.DELAY_SAVE", new=0): + with ( + patch( + "feedparser.http.get", + return_value=feed_one_event, + ), + patch("homeassistant.components.feedreader.DELAY_SAVE", new=0), + ): assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -297,13 +300,16 @@ async def test_feed_identical_timestamps( hass: HomeAssistant, events, feed_identically_timed_events ) -> None: """Test feed with 2 entries with identical timestamps.""" - with patch( - "feedparser.http.get", - return_value=feed_identically_timed_events, - ), patch( - "homeassistant.components.feedreader.StoredData.get_timestamp", - return_value=gmtime( - datetime.fromisoformat("1970-01-01T00:00:00.0+0000").timestamp() + with ( + patch( + "feedparser.http.get", + return_value=feed_identically_timed_events, + ), + patch( + "homeassistant.components.feedreader.StoredData.get_timestamp", + return_value=gmtime( + datetime.fromisoformat("1970-01-01T00:00:00.0+0000").timestamp() + ), ), ): assert await async_setup_component(hass, feedreader.DOMAIN, VALID_CONFIG_2) diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 2d79e593d2c..60d24baa302 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -269,9 +269,13 @@ async def test_legacy_ffmpeg( hass: HomeAssistant, ) -> None: """Test legacy ffmpeg uses the old ffserver content type.""" - with assert_setup_component(1), patch( - "homeassistant.components.ffmpeg.FFVersion.get_version", return_value="3.0" - ), patch("homeassistant.components.ffmpeg.is_official_image", return_value=False): + with ( + assert_setup_component(1), + patch( + "homeassistant.components.ffmpeg.FFVersion.get_version", return_value="3.0" + ), + patch("homeassistant.components.ffmpeg.is_official_image", return_value=False), + ): await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) manager = get_ffmpeg_manager(hass) @@ -282,8 +286,9 @@ async def test_ffmpeg_using_official_image( hass: HomeAssistant, ) -> None: """Test ffmpeg using official image is the new ffmpeg content type.""" - with assert_setup_component(1), patch( - "homeassistant.components.ffmpeg.is_official_image", return_value=True + with ( + assert_setup_component(1), + patch("homeassistant.components.ffmpeg.is_official_image", return_value=True), ): await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) diff --git a/tests/components/file/test_notify.py b/tests/components/file/test_notify.py index 093be77c08f..3077d71bdde 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -56,9 +56,10 @@ async def test_notify_file( freezer.move_to(dt_util.utcnow()) m_open = mock_open() - with patch("homeassistant.components.file.notify.open", m_open, create=True), patch( - "homeassistant.components.file.notify.os.stat" - ) as mock_st: + with ( + patch("homeassistant.components.file.notify.open", m_open, create=True), + patch("homeassistant.components.file.notify.os.stat") as mock_st, + ): mock_st.return_value.st_size = 0 title = ( f"{ATTR_TITLE_DEFAULT} notifications " diff --git a/tests/components/file_upload/test_init.py b/tests/components/file_upload/test_init.py index deff64ff073..1ef238cafd0 100644 --- a/tests/components/file_upload/test_init.py +++ b/tests/components/file_upload/test_init.py @@ -21,11 +21,14 @@ async def uploaded_file_dir(hass: HomeAssistant, hass_client) -> Path: assert await async_setup_component(hass, "file_upload", {}) client = await hass_client() - with patch( - # Patch temp dir name to avoid tests fail running in parallel - "homeassistant.components.file_upload.TEMP_DIR_NAME", - file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", - ), TEST_IMAGE.open("rb") as fp: + with ( + patch( + # Patch temp dir name to avoid tests fail running in parallel + "homeassistant.components.file_upload.TEMP_DIR_NAME", + file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", + ), + TEST_IMAGE.open("rb") as fp, + ): res = await client.post("/api/file_upload", data={"file": fp}) assert res.status == 200 @@ -80,14 +83,17 @@ async def test_upload_large_file( assert await async_setup_component(hass, "file_upload", {}) client = await hass_client() - with patch( - # Patch temp dir name to avoid tests fail running in parallel - "homeassistant.components.file_upload.TEMP_DIR_NAME", - file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", - ), patch( - # Patch one megabyte to 8 bytes to prevent having to use big files in tests - "homeassistant.components.file_upload.ONE_MEGABYTE", - 8, + with ( + patch( + # Patch temp dir name to avoid tests fail running in parallel + "homeassistant.components.file_upload.TEMP_DIR_NAME", + file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", + ), + patch( + # Patch one megabyte to 8 bytes to prevent having to use big files in tests + "homeassistant.components.file_upload.ONE_MEGABYTE", + 8, + ), ): res = await client.post("/api/file_upload", data={"file": large_file_io}) @@ -139,16 +145,20 @@ async def test_upload_large_file_fails( def write(self, data: bytes) -> None: raise OSError("Boom") - with patch( - # Patch temp dir name to avoid tests fail running in parallel - "homeassistant.components.file_upload.TEMP_DIR_NAME", - file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", - ), patch( - # Patch one megabyte to 8 bytes to prevent having to use big files in tests - "homeassistant.components.file_upload.ONE_MEGABYTE", - 8, - ), patch( - "homeassistant.components.file_upload.Path.open", return_value=_mock_open() + with ( + patch( + # Patch temp dir name to avoid tests fail running in parallel + "homeassistant.components.file_upload.TEMP_DIR_NAME", + file_upload.TEMP_DIR_NAME + f"-{getrandbits(10):03x}", + ), + patch( + # Patch one megabyte to 8 bytes to prevent having to use big files in tests + "homeassistant.components.file_upload.ONE_MEGABYTE", + 8, + ), + patch( + "homeassistant.components.file_upload.Path.open", return_value=_mock_open() + ), ): res = await client.post("/api/file_upload", data={"file": large_file_io}) diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 66914bf32a6..67370bbcedc 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -130,12 +130,15 @@ async def test_chain_history( ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - return_value=fake_states, - ), patch( - "homeassistant.components.recorder.history.get_last_state_changes", - return_value=fake_states, + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + return_value=fake_states, + ), + patch( + "homeassistant.components.recorder.history.get_last_state_changes", + return_value=fake_states, + ), ): with assert_setup_component(1, "sensor"): assert await async_setup_component(hass, "sensor", config) @@ -234,12 +237,15 @@ async def test_history_time(recorder_mock: Recorder, hass: HomeAssistant) -> Non State("sensor.test_monitored", "18.2", last_changed=t_2), ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - return_value=fake_states, - ), patch( - "homeassistant.components.recorder.history.get_last_state_changes", - return_value=fake_states, + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + return_value=fake_states, + ), + patch( + "homeassistant.components.recorder.history.get_last_state_changes", + return_value=fake_states, + ), ): with assert_setup_component(1, "sensor"): assert await async_setup_component(hass, "sensor", config) diff --git a/tests/components/fireservicerota/test_config_flow.py b/tests/components/fireservicerota/test_config_flow.py index ffb3ac23f1b..e2bf5911089 100644 --- a/tests/components/fireservicerota/test_config_flow.py +++ b/tests/components/fireservicerota/test_config_flow.py @@ -77,12 +77,15 @@ async def test_invalid_credentials(hass: HomeAssistant) -> None: async def test_step_user(hass: HomeAssistant) -> None: """Test the start of the config flow.""" - with patch( - "homeassistant.components.fireservicerota.config_flow.FireServiceRota" - ) as mock_fsr, patch( - "homeassistant.components.fireservicerota.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.fireservicerota.config_flow.FireServiceRota" + ) as mock_fsr, + patch( + "homeassistant.components.fireservicerota.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): mock_fireservicerota = mock_fsr.return_value mock_fireservicerota.request_tokens.return_value = MOCK_TOKEN_INFO @@ -134,11 +137,14 @@ async def test_reauth(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result["type"] == data_entry_flow.FlowResultType.FORM - with patch( - "homeassistant.components.fireservicerota.config_flow.FireServiceRota" - ) as mock_fsr, patch( - "homeassistant.components.fireservicerota.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.fireservicerota.config_flow.FireServiceRota" + ) as mock_fsr, + patch( + "homeassistant.components.fireservicerota.async_setup_entry", + return_value=True, + ), ): mock_fireservicerota = mock_fsr.return_value mock_fireservicerota.request_tokens.return_value = MOCK_TOKEN_INFO diff --git a/tests/components/firmata/test_config_flow.py b/tests/components/firmata/test_config_flow.py index 4dc0dbc80ca..a3c8ca7e728 100644 --- a/tests/components/firmata/test_config_flow.py +++ b/tests/components/firmata/test_config_flow.py @@ -64,13 +64,15 @@ async def test_import_cannot_connect_serial_timeout(hass: HomeAssistant) -> None async def test_import(hass: HomeAssistant) -> None: """Test we create an entry from config.""" - with patch( - "homeassistant.components.firmata.board.PymataExpress", autospec=True - ), patch( - "homeassistant.components.firmata.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.firmata.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("homeassistant.components.firmata.board.PymataExpress", autospec=True), + patch( + "homeassistant.components.firmata.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.firmata.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, diff --git a/tests/components/fitbit/conftest.py b/tests/components/fitbit/conftest.py index a076be7f63d..a4bfed43cba 100644 --- a/tests/components/fitbit/conftest.py +++ b/tests/components/fitbit/conftest.py @@ -125,12 +125,15 @@ def mock_fitbit_config_setup( ) -> Generator[None, None, None]: """Fixture to mock out fitbit.conf file data loading and persistence.""" has_config = fitbit_config_yaml is not None - with patch( - "homeassistant.components.fitbit.sensor.os.path.isfile", - return_value=has_config, - ), patch( - "homeassistant.components.fitbit.sensor.load_json_object", - return_value=fitbit_config_yaml, + with ( + patch( + "homeassistant.components.fitbit.sensor.os.path.isfile", + return_value=has_config, + ), + patch( + "homeassistant.components.fitbit.sensor.load_json_object", + return_value=fitbit_config_yaml, + ), ): yield diff --git a/tests/components/fivem/test_config_flow.py b/tests/components/fivem/test_config_flow.py index 1596da01648..174078c5420 100644 --- a/tests/components/fivem/test_config_flow.py +++ b/tests/components/fivem/test_config_flow.py @@ -67,13 +67,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "fivem.fivem.FiveM.get_info_raw", - return_value=_mock_fivem_info_success(), - ), patch( - "homeassistant.components.fivem.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "fivem.fivem.FiveM.get_info_raw", + return_value=_mock_fivem_info_success(), + ), + patch( + "homeassistant.components.fivem.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, diff --git a/tests/components/flexit_bacnet/conftest.py b/tests/components/flexit_bacnet/conftest.py index 55941dc44a9..fb4b2237833 100644 --- a/tests/components/flexit_bacnet/conftest.py +++ b/tests/components/flexit_bacnet/conftest.py @@ -32,12 +32,15 @@ async def flow_id(hass: HomeAssistant) -> str: def mock_flexit_bacnet() -> Generator[AsyncMock, None, None]: """Mock data from the device.""" flexit_bacnet = AsyncMock(spec=FlexitBACnet) - with patch( - "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet", - return_value=flexit_bacnet, - ), patch( - "homeassistant.components.flexit_bacnet.coordinator.FlexitBACnet", - return_value=flexit_bacnet, + with ( + patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet", + return_value=flexit_bacnet, + ), + patch( + "homeassistant.components.flexit_bacnet.coordinator.FlexitBACnet", + return_value=flexit_bacnet, + ), ): flexit_bacnet.serial_number = "0000-0001" flexit_bacnet.device_name = "Device Name" diff --git a/tests/components/flick_electric/test_config_flow.py b/tests/components/flick_electric/test_config_flow.py index 123232e8c52..9635f3a1526 100644 --- a/tests/components/flick_electric/test_config_flow.py +++ b/tests/components/flick_electric/test_config_flow.py @@ -31,13 +31,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token", - return_value="123456789abcdef", - ), patch( - "homeassistant.components.flick_electric.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token", + return_value="123456789abcdef", + ), + patch( + "homeassistant.components.flick_electric.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONF, diff --git a/tests/components/flume/test_config_flow.py b/tests/components/flume/test_config_flow.py index 64c85a0b896..8fa66c03258 100644 --- a/tests/components/flume/test_config_flow.py +++ b/tests/components/flume/test_config_flow.py @@ -34,16 +34,20 @@ async def test_form(hass: HomeAssistant) -> None: mock_flume_device_list = _get_mocked_flume_device_list() - with patch( - "homeassistant.components.flume.config_flow.FlumeAuth", - return_value=True, - ), patch( - "homeassistant.components.flume.config_flow.FlumeDeviceList", - return_value=mock_flume_device_list, - ), patch( - "homeassistant.components.flume.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.flume.config_flow.FlumeAuth", + return_value=True, + ), + patch( + "homeassistant.components.flume.config_flow.FlumeDeviceList", + return_value=mock_flume_device_list, + ), + patch( + "homeassistant.components.flume.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -72,12 +76,15 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.flume.config_flow.FlumeAuth", - return_value=True, - ), patch( - "homeassistant.components.flume.config_flow.FlumeDeviceList", - side_effect=Exception, + with ( + patch( + "homeassistant.components.flume.config_flow.FlumeAuth", + return_value=True, + ), + patch( + "homeassistant.components.flume.config_flow.FlumeDeviceList", + side_effect=Exception, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -98,12 +105,15 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.flume.config_flow.FlumeAuth", - return_value=True, - ), patch( - "homeassistant.components.flume.config_flow.FlumeDeviceList", - side_effect=requests.exceptions.ConnectionError(), + with ( + patch( + "homeassistant.components.flume.config_flow.FlumeAuth", + return_value=True, + ), + patch( + "homeassistant.components.flume.config_flow.FlumeDeviceList", + side_effect=requests.exceptions.ConnectionError(), + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -140,12 +150,15 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["step_id"] == "reauth_confirm" - with patch( - "homeassistant.components.flume.config_flow.FlumeAuth", - return_value=True, - ), patch( - "homeassistant.components.flume.config_flow.FlumeDeviceList", - side_effect=Exception, + with ( + patch( + "homeassistant.components.flume.config_flow.FlumeAuth", + return_value=True, + ), + patch( + "homeassistant.components.flume.config_flow.FlumeDeviceList", + side_effect=Exception, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -157,12 +170,15 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result2["type"] == "form" assert result2["errors"] == {"password": "invalid_auth"} - with patch( - "homeassistant.components.flume.config_flow.FlumeAuth", - return_value=True, - ), patch( - "homeassistant.components.flume.config_flow.FlumeDeviceList", - side_effect=requests.exceptions.ConnectionError(), + with ( + patch( + "homeassistant.components.flume.config_flow.FlumeAuth", + return_value=True, + ), + patch( + "homeassistant.components.flume.config_flow.FlumeDeviceList", + side_effect=requests.exceptions.ConnectionError(), + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -176,16 +192,20 @@ async def test_reauth(hass: HomeAssistant) -> None: mock_flume_device_list = _get_mocked_flume_device_list() - with patch( - "homeassistant.components.flume.config_flow.FlumeAuth", - return_value=True, - ), patch( - "homeassistant.components.flume.config_flow.FlumeDeviceList", - return_value=mock_flume_device_list, - ), patch( - "homeassistant.components.flume.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.flume.config_flow.FlumeAuth", + return_value=True, + ), + patch( + "homeassistant.components.flume.config_flow.FlumeDeviceList", + return_value=mock_flume_device_list, + ), + patch( + "homeassistant.components.flume.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], { diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index b0432e60ce1..a3eeec10fa5 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -166,9 +166,12 @@ async def test_flux_when_switch_is_off( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): turn_on_calls = async_mock_service(hass, light.DOMAIN, SERVICE_TURN_ON) assert await async_setup_component( @@ -219,9 +222,12 @@ async def test_flux_before_sunrise( return sunset_time await hass.async_block_till_done() - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -337,9 +343,12 @@ async def test_flux_after_sunrise_before_sunset( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -396,9 +405,12 @@ async def test_flux_after_sunset_before_stop( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -456,9 +468,12 @@ async def test_flux_after_stop_before_sunrise( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -515,9 +530,12 @@ async def test_flux_with_custom_start_stop_times( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -579,9 +597,12 @@ async def test_flux_before_sunrise_stop_next_day( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -642,9 +663,12 @@ async def test_flux_after_sunrise_before_sunset_stop_next_day( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -707,9 +731,12 @@ async def test_flux_after_sunset_before_midnight_stop_next_day( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -770,9 +797,12 @@ async def test_flux_after_sunset_after_midnight_stop_next_day( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -833,9 +863,12 @@ async def test_flux_after_stop_before_sunrise_stop_next_day( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -893,9 +926,12 @@ async def test_flux_with_custom_colortemps( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -955,9 +991,12 @@ async def test_flux_with_custom_brightness( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -1033,9 +1072,12 @@ async def test_flux_with_multiple_lights( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -1097,9 +1139,12 @@ async def test_flux_with_mired( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, @@ -1155,9 +1200,12 @@ async def test_flux_with_rgb( return sunrise_time return sunset_time - with freeze_time(test_time), patch( - "homeassistant.components.flux.switch.get_astral_event_date", - side_effect=event_date, + with ( + freeze_time(test_time), + patch( + "homeassistant.components.flux.switch.get_astral_event_date", + side_effect=event_date, + ), ): assert await async_setup_component( hass, diff --git a/tests/components/flux_led/__init__.py b/tests/components/flux_led/__init__.py index 4b36826728a..d1cb892d548 100644 --- a/tests/components/flux_led/__init__.py +++ b/tests/components/flux_led/__init__.py @@ -241,12 +241,15 @@ def _patch_discovery(device=None, no_device=False): @contextmanager def _patcher(): - with patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan", - new=_discovery, - ), patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo", - return_value=[] if no_device else [device or FLUX_DISCOVERY], + with ( + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan", + new=_discovery, + ), + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo", + return_value=[] if no_device else [device or FLUX_DISCOVERY], + ), ): yield diff --git a/tests/components/flux_led/test_config_flow.py b/tests/components/flux_led/test_config_flow.py index 183164e04a1..63a7a671871 100644 --- a/tests/components/flux_led/test_config_flow.py +++ b/tests/components/flux_led/test_config_flow.py @@ -79,11 +79,12 @@ async def test_discovery(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: MAC_ADDRESS}, @@ -153,11 +154,12 @@ async def test_discovery_legacy(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: MAC_ADDRESS}, @@ -239,9 +241,11 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_wifibulb(), patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: MAC_ADDRESS} ) @@ -313,9 +317,12 @@ async def test_manual_working_discovery(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} # Success - with _patch_discovery(), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ), patch(f"{MODULE}.async_setup_entry", return_value=True): + with ( + _patch_discovery(), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), + ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -357,9 +364,12 @@ async def test_manual_no_discovery_data(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with _patch_discovery(no_device=True), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ), patch(f"{MODULE}.async_setup_entry", return_value=True): + with ( + _patch_discovery(no_device=True), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -425,11 +435,14 @@ async def test_discovered_by_discovery(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -461,11 +474,14 @@ async def test_discovered_by_dhcp_udp_responds(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -497,11 +513,14 @@ async def test_discovered_by_dhcp_no_udp_response(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(no_device=True), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(no_device=True), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -529,11 +548,14 @@ async def test_discovered_by_dhcp_partial_udp_response_fallback_tcp( assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(device=FLUX_DISCOVERY_PARTIAL), _patch_wifibulb(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(device=FLUX_DISCOVERY_PARTIAL), + _patch_wifibulb(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() diff --git a/tests/components/flux_led/test_init.py b/tests/components/flux_led/test_init.py index 7c4917ea4dc..a42ba5dff37 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -47,11 +47,14 @@ from tests.common import MockConfigEntry, async_fire_time_changed @pytest.mark.usefixtures("mock_single_broadcast_address") async def test_configuring_flux_led_causes_discovery(hass: HomeAssistant) -> None: """Test that specifying empty config does discovery.""" - with patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan" - ) as scan, patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo" - ) as discover: + with ( + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan" + ) as scan, + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo" + ) as discover, + ): discover.return_value = [FLUX_DISCOVERY] await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -68,11 +71,14 @@ async def test_configuring_flux_led_causes_discovery_multiple_addresses( hass: HomeAssistant, ) -> None: """Test that specifying empty config does discovery.""" - with patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan" - ) as scan, patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo" - ) as discover: + with ( + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan" + ) as scan, + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo" + ) as discover, + ): discover.return_value = [FLUX_DISCOVERY] await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() @@ -200,13 +206,17 @@ async def test_config_entry_fills_unique_id_with_directed_discovery( nonlocal last_address return [discovery] if last_address == IP_ADDRESS else [] - with patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan", - new=_discovery, - ), patch( - "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo", - new=_mock_getBulbInfo, - ), _patch_wifibulb(): + with ( + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.async_scan", + new=_discovery, + ), + patch( + "homeassistant.components.flux_led.discovery.AIOBulbScanner.getBulbInfo", + new=_mock_getBulbInfo, + ), + _patch_wifibulb(), + ): await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}}) await hass.async_block_till_done() assert config_entry.state == ConfigEntryState.LOADED diff --git a/tests/components/forked_daapd/test_config_flow.py b/tests/components/forked_daapd/test_config_flow.py index 7dc5e1e20f9..a7f0dc3f603 100644 --- a/tests/components/forked_daapd/test_config_flow.py +++ b/tests/components/forked_daapd/test_config_flow.py @@ -69,13 +69,16 @@ async def test_show_form(hass: HomeAssistant) -> None: async def test_config_flow(hass: HomeAssistant, config_entry) -> None: """Test that the user step works.""" - with patch( - "homeassistant.components.forked_daapd.config_flow.ForkedDaapdAPI.test_connection", - new=AsyncMock(), - ) as mock_test_connection, patch( - "homeassistant.components.forked_daapd.media_player.ForkedDaapdAPI.get_request", - autospec=True, - ) as mock_get_request: + with ( + patch( + "homeassistant.components.forked_daapd.config_flow.ForkedDaapdAPI.test_connection", + new=AsyncMock(), + ) as mock_test_connection, + patch( + "homeassistant.components.forked_daapd.media_player.ForkedDaapdAPI.get_request", + autospec=True, + ) as mock_get_request, + ): mock_get_request.return_value = SAMPLE_CONFIG mock_test_connection.return_value = ["ok", "My Music on myhost"] config_data = config_entry.data diff --git a/tests/components/foscam/test_config_flow.py b/tests/components/foscam/test_config_flow.py index fc3bd1d5ad2..64ad2b946da 100644 --- a/tests/components/foscam/test_config_flow.py +++ b/tests/components/foscam/test_config_flow.py @@ -85,12 +85,15 @@ async def test_user_valid(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.foscam.config_flow.FoscamCamera", - ) as mock_foscam_camera, patch( - "homeassistant.components.foscam.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.foscam.config_flow.FoscamCamera", + ) as mock_foscam_camera, + patch( + "homeassistant.components.foscam.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): setup_mock_foscam_camera(mock_foscam_camera) result = await hass.config_entries.flow.async_configure( diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index e223e868d83..cf520043755 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -29,8 +29,9 @@ from tests.common import MockConfigEntry @pytest.fixture(autouse=True) def mock_path(): """Mock path lib.""" - with patch("homeassistant.components.freebox.router.Path"), patch( - "homeassistant.components.freebox.router.os.makedirs" + with ( + patch("homeassistant.components.freebox.router.Path"), + patch("homeassistant.components.freebox.router.os.makedirs"), ): yield diff --git a/tests/components/freedompro/conftest.py b/tests/components/freedompro/conftest.py index 63efbc31ca5..27e6c767223 100644 --- a/tests/components/freedompro/conftest.py +++ b/tests/components/freedompro/conftest.py @@ -28,15 +28,18 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture(autouse=True) def mock_freedompro(): """Mock freedompro get_list and get_states.""" - with patch( - "homeassistant.components.freedompro.coordinator.get_list", - return_value={ - "state": True, - "devices": DEVICES, - }, - ), patch( - "homeassistant.components.freedompro.coordinator.get_states", - return_value=DEVICES_STATE, + with ( + patch( + "homeassistant.components.freedompro.coordinator.get_list", + return_value={ + "state": True, + "devices": DEVICES, + }, + ), + patch( + "homeassistant.components.freedompro.coordinator.get_states", + return_value=DEVICES_STATE, + ), ): yield @@ -72,15 +75,18 @@ async def init_integration_no_state(hass) -> MockConfigEntry: }, ) - with patch( - "homeassistant.components.freedompro.coordinator.get_list", - return_value={ - "state": True, - "devices": DEVICES, - }, - ), patch( - "homeassistant.components.freedompro.coordinator.get_states", - return_value=[], + with ( + patch( + "homeassistant.components.freedompro.coordinator.get_list", + return_value={ + "state": True, + "devices": DEVICES, + }, + ), + patch( + "homeassistant.components.freedompro.coordinator.get_states", + return_value=[], + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/fritz/test_config_flow.py b/tests/components/fritz/test_config_flow.py index 5db70e348dd..fbd9886f468 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -42,21 +42,26 @@ from tests.common import MockConfigEntry async def test_user(hass: HomeAssistant, fc_class_mock, mock_get_source_ip) -> None: """Test starting a flow by user.""" - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=MOCK_FIRMWARE_INFO, - ), patch( - "homeassistant.components.fritz.async_setup_entry" - ) as mock_setup_entry, patch( - "requests.get", - ) as mock_request_get, patch( - "requests.post", - ) as mock_request_post, patch( - "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IPS["fritz.box"], + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", + return_value=MOCK_FIRMWARE_INFO, + ), + patch("homeassistant.components.fritz.async_setup_entry") as mock_setup_entry, + patch( + "requests.get", + ) as mock_request_get, + patch( + "requests.post", + ) as mock_request_post, + patch( + "homeassistant.components.fritz.config_flow.socket.gethostbyname", + return_value=MOCK_IPS["fritz.box"], + ), ): mock_request_get.return_value.status_code = 200 mock_request_get.return_value.content = MOCK_REQUEST @@ -94,19 +99,25 @@ async def test_user_already_configured( mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=MOCK_FIRMWARE_INFO, - ), patch( - "requests.get", - ) as mock_request_get, patch( - "requests.post", - ) as mock_request_post, patch( - "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IPS["fritz.box"], + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", + return_value=MOCK_FIRMWARE_INFO, + ), + patch( + "requests.get", + ) as mock_request_get, + patch( + "requests.post", + ) as mock_request_post, + patch( + "homeassistant.components.fritz.config_flow.socket.gethostbyname", + return_value=MOCK_IPS["fritz.box"], + ), ): mock_request_get.return_value.status_code = 200 mock_request_get.return_value.content = MOCK_REQUEST @@ -207,19 +218,25 @@ async def test_reauth_successful( mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=MOCK_FIRMWARE_INFO, - ), patch( - "homeassistant.components.fritz.async_setup_entry", - ) as mock_setup_entry, patch( - "requests.get", - ) as mock_request_get, patch( - "requests.post", - ) as mock_request_post: + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", + return_value=MOCK_FIRMWARE_INFO, + ), + patch( + "homeassistant.components.fritz.async_setup_entry", + ) as mock_setup_entry, + patch( + "requests.get", + ) as mock_request_get, + patch( + "requests.post", + ) as mock_request_post, + ): mock_request_get.return_value.status_code = 200 mock_request_get.return_value.content = MOCK_REQUEST mock_request_post.return_value.status_code = 200 @@ -302,12 +319,15 @@ async def test_ssdp_already_configured( ) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IPS["fritz.box"], + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.config_flow.socket.gethostbyname", + return_value=MOCK_IPS["fritz.box"], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_SSDP}, data=MOCK_SSDP_DATA @@ -328,12 +348,15 @@ async def test_ssdp_already_configured_host( ) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IPS["fritz.box"], + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.config_flow.socket.gethostbyname", + return_value=MOCK_IPS["fritz.box"], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_SSDP}, data=MOCK_SSDP_DATA @@ -354,12 +377,15 @@ async def test_ssdp_already_configured_host_uuid( ) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.config_flow.socket.gethostbyname", - return_value=MOCK_IPS["fritz.box"], + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.config_flow.socket.gethostbyname", + return_value=MOCK_IPS["fritz.box"], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_SSDP}, data=MOCK_SSDP_DATA @@ -394,17 +420,19 @@ async def test_ssdp_already_in_progress_host( async def test_ssdp(hass: HomeAssistant, fc_class_mock, mock_get_source_ip) -> None: """Test starting a flow from discovery.""" - with patch( - "homeassistant.components.fritz.config_flow.FritzConnection", - side_effect=fc_class_mock, - ), patch( - "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=MOCK_FIRMWARE_INFO, - ), patch( - "homeassistant.components.fritz.async_setup_entry" - ) as mock_setup_entry, patch("requests.get") as mock_request_get, patch( - "requests.post" - ) as mock_request_post: + with ( + patch( + "homeassistant.components.fritz.config_flow.FritzConnection", + side_effect=fc_class_mock, + ), + patch( + "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", + return_value=MOCK_FIRMWARE_INFO, + ), + patch("homeassistant.components.fritz.async_setup_entry") as mock_setup_entry, + patch("requests.get") as mock_request_get, + patch("requests.post") as mock_request_post, + ): mock_request_get.return_value.status_code = 200 mock_request_get.return_value.content = MOCK_REQUEST mock_request_post.return_value.status_code = 200 diff --git a/tests/components/fritzbox/conftest.py b/tests/components/fritzbox/conftest.py index 02a3eed011e..836a8bc127f 100644 --- a/tests/components/fritzbox/conftest.py +++ b/tests/components/fritzbox/conftest.py @@ -8,8 +8,9 @@ import pytest @pytest.fixture(name="fritz") def fritz_fixture() -> Mock: """Patch libraries.""" - with patch("homeassistant.components.fritzbox.Fritzhome") as fritz, patch( - "homeassistant.components.fritzbox.config_flow.Fritzhome" + with ( + patch("homeassistant.components.fritzbox.Fritzhome") as fritz, + patch("homeassistant.components.fritzbox.config_flow.Fritzhome"), ): fritz.return_value.get_prefixed_host.return_value = "http://1.2.3.4" yield fritz diff --git a/tests/components/fritzbox/test_config_flow.py b/tests/components/fritzbox/test_config_flow.py index cf93e74edac..690082085f8 100644 --- a/tests/components/fritzbox/test_config_flow.py +++ b/tests/components/fritzbox/test_config_flow.py @@ -56,9 +56,10 @@ MOCK_SSDP_DATA = { @pytest.fixture(name="fritz") def fritz_fixture() -> Mock: """Patch libraries.""" - with patch("homeassistant.components.fritzbox.async_setup_entry"), patch( - "homeassistant.components.fritzbox.config_flow.Fritzhome" - ) as fritz: + with ( + patch("homeassistant.components.fritzbox.async_setup_entry"), + patch("homeassistant.components.fritzbox.config_flow.Fritzhome") as fritz, + ): yield fritz diff --git a/tests/components/fritzbox_callmonitor/test_config_flow.py b/tests/components/fritzbox_callmonitor/test_config_flow.py index accdad3a70d..33e2d8fb125 100644 --- a/tests/components/fritzbox_callmonitor/test_config_flow.py +++ b/tests/components/fritzbox_callmonitor/test_config_flow.py @@ -93,30 +93,38 @@ async def test_setup_one_phonebook(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.__init__", - return_value=None, - ), patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_ids", - new_callable=PropertyMock, - return_value=[0], - ), patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_info", - return_value=MOCK_PHONEBOOK_INFO_1, - ), patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.modelname", - return_value=MOCK_PHONEBOOK_NAME_1, - ), patch( - "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.__init__", - return_value=None, - ), patch( - "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.updatecheck", - new_callable=PropertyMock, - return_value=MOCK_DEVICE_INFO, - ), patch( - "homeassistant.components.fritzbox_callmonitor.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.__init__", + return_value=None, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_ids", + new_callable=PropertyMock, + return_value=[0], + ), + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_info", + return_value=MOCK_PHONEBOOK_INFO_1, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.modelname", + return_value=MOCK_PHONEBOOK_NAME_1, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.__init__", + return_value=None, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.updatecheck", + new_callable=PropertyMock, + return_value=MOCK_DEVICE_INFO, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_USER_DATA ) @@ -136,23 +144,29 @@ async def test_setup_multiple_phonebooks(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.__init__", - return_value=None, - ), patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_ids", - new_callable=PropertyMock, - return_value=[0, 1], - ), patch( - "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.__init__", - return_value=None, - ), patch( - "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.updatecheck", - new_callable=PropertyMock, - return_value=MOCK_DEVICE_INFO, - ), patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_info", - side_effect=[MOCK_PHONEBOOK_INFO_1, MOCK_PHONEBOOK_INFO_2], + with ( + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.__init__", + return_value=None, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_ids", + new_callable=PropertyMock, + return_value=[0, 1], + ), + patch( + "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.__init__", + return_value=None, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.config_flow.FritzConnection.updatecheck", + new_callable=PropertyMock, + return_value=MOCK_DEVICE_INFO, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.phonebook_info", + side_effect=[MOCK_PHONEBOOK_INFO_1, MOCK_PHONEBOOK_INFO_2], + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_USER_DATA @@ -162,13 +176,16 @@ async def test_setup_multiple_phonebooks(hass: HomeAssistant) -> None: assert result["step_id"] == "phonebook" assert result["errors"] == {} - with patch( - "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.modelname", - return_value=MOCK_PHONEBOOK_NAME_1, - ), patch( - "homeassistant.components.fritzbox_callmonitor.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.fritzbox_callmonitor.base.FritzPhonebook.modelname", + return_value=MOCK_PHONEBOOK_NAME_1, + ), + patch( + "homeassistant.components.fritzbox_callmonitor.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_PHONEBOOK: MOCK_PHONEBOOK_NAME_2}, diff --git a/tests/components/fronius/test_config_flow.py b/tests/components/fronius/test_config_flow.py index d0dd32ad801..c09baeb2d22 100644 --- a/tests/components/fronius/test_config_flow.py +++ b/tests/components/fronius/test_config_flow.py @@ -52,13 +52,16 @@ async def test_form_with_logger(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "pyfronius.Fronius.current_logger_info", - return_value=LOGGER_INFO_RETURN_VALUE, - ), patch( - "homeassistant.components.fronius.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyfronius.Fronius.current_logger_info", + return_value=LOGGER_INFO_RETURN_VALUE, + ), + patch( + "homeassistant.components.fronius.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -84,16 +87,20 @@ async def test_form_with_inverter(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "pyfronius.Fronius.current_logger_info", - side_effect=FroniusError, - ), patch( - "pyfronius.Fronius.inverter_info", - return_value=INVERTER_INFO_RETURN_VALUE, - ), patch( - "homeassistant.components.fronius.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyfronius.Fronius.current_logger_info", + side_effect=FroniusError, + ), + patch( + "pyfronius.Fronius.inverter_info", + return_value=INVERTER_INFO_RETURN_VALUE, + ), + patch( + "homeassistant.components.fronius.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -117,12 +124,15 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "pyfronius.Fronius.current_logger_info", - side_effect=FroniusError, - ), patch( - "pyfronius.Fronius.inverter_info", - side_effect=FroniusError, + with ( + patch( + "pyfronius.Fronius.current_logger_info", + side_effect=FroniusError, + ), + patch( + "pyfronius.Fronius.inverter_info", + side_effect=FroniusError, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -141,12 +151,15 @@ async def test_form_no_device(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "pyfronius.Fronius.current_logger_info", - side_effect=FroniusError, - ), patch( - "pyfronius.Fronius.inverter_info", - return_value={"inverters": []}, + with ( + patch( + "pyfronius.Fronius.current_logger_info", + side_effect=FroniusError, + ), + patch( + "pyfronius.Fronius.inverter_info", + return_value={"inverters": []}, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -260,11 +273,12 @@ async def test_form_updates_host( async def test_dhcp(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None: """Test starting a flow from discovery.""" - with patch( - "homeassistant.components.fronius.config_flow.DHCP_REQUEST_DELAY", 0 - ), patch( - "pyfronius.Fronius.current_logger_info", - return_value=LOGGER_INFO_RETURN_VALUE, + with ( + patch("homeassistant.components.fronius.config_flow.DHCP_REQUEST_DELAY", 0), + patch( + "pyfronius.Fronius.current_logger_info", + return_value=LOGGER_INFO_RETURN_VALUE, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=MOCK_DHCP_DATA @@ -308,14 +322,16 @@ async def test_dhcp_invalid( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test starting a flow from discovery.""" - with patch( - "homeassistant.components.fronius.config_flow.DHCP_REQUEST_DELAY", 0 - ), patch( - "pyfronius.Fronius.current_logger_info", - side_effect=FroniusError, - ), patch( - "pyfronius.Fronius.inverter_info", - side_effect=FroniusError, + with ( + patch("homeassistant.components.fronius.config_flow.DHCP_REQUEST_DELAY", 0), + patch( + "pyfronius.Fronius.current_logger_info", + side_effect=FroniusError, + ), + patch( + "pyfronius.Fronius.inverter_info", + side_effect=FroniusError, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=MOCK_DHCP_DATA diff --git a/tests/components/gardena_bluetooth/conftest.py b/tests/components/gardena_bluetooth/conftest.py index c840ad9bf77..af882e35751 100644 --- a/tests/components/gardena_bluetooth/conftest.py +++ b/tests/components/gardena_bluetooth/conftest.py @@ -101,10 +101,13 @@ def mock_client( client.read_char_raw.side_effect = _read_char_raw client.get_all_characteristics_uuid.side_effect = _all_char - with patch( - "homeassistant.components.gardena_bluetooth.config_flow.Client", - return_value=client, - ), patch("homeassistant.components.gardena_bluetooth.Client", return_value=client): + with ( + patch( + "homeassistant.components.gardena_bluetooth.config_flow.Client", + return_value=client, + ), + patch("homeassistant.components.gardena_bluetooth.Client", return_value=client), + ): yield client diff --git a/tests/components/gardena_bluetooth/test_number.py b/tests/components/gardena_bluetooth/test_number.py index 0bbe2e926cd..4c053fca0fa 100644 --- a/tests/components/gardena_bluetooth/test_number.py +++ b/tests/components/gardena_bluetooth/test_number.py @@ -130,12 +130,12 @@ async def test_bluetooth_error_unavailable( ) -> None: """Verify that a connectivity error makes all entities unavailable.""" - mock_read_char_raw[ - Valve.manual_watering_time.uuid - ] = Valve.manual_watering_time.encode(0) - mock_read_char_raw[ - Valve.remaining_open_time.uuid - ] = Valve.remaining_open_time.encode(0) + mock_read_char_raw[Valve.manual_watering_time.uuid] = ( + Valve.manual_watering_time.encode(0) + ) + mock_read_char_raw[Valve.remaining_open_time.uuid] = ( + Valve.remaining_open_time.encode(0) + ) await setup_entry(hass, mock_entry, [Platform.NUMBER]) assert hass.states.get("number.mock_title_remaining_open_time") == snapshot diff --git a/tests/components/gardena_bluetooth/test_switch.py b/tests/components/gardena_bluetooth/test_switch.py index 8478788de04..4a29f8f91ae 100644 --- a/tests/components/gardena_bluetooth/test_switch.py +++ b/tests/components/gardena_bluetooth/test_switch.py @@ -25,12 +25,12 @@ from tests.common import MockConfigEntry def mock_switch_chars(mock_read_char_raw): """Mock data on device.""" mock_read_char_raw[Valve.state.uuid] = b"\x00" - mock_read_char_raw[ - Valve.remaining_open_time.uuid - ] = Valve.remaining_open_time.encode(0) - mock_read_char_raw[ - Valve.manual_watering_time.uuid - ] = Valve.manual_watering_time.encode(1000) + mock_read_char_raw[Valve.remaining_open_time.uuid] = ( + Valve.remaining_open_time.encode(0) + ) + mock_read_char_raw[Valve.manual_watering_time.uuid] = ( + Valve.manual_watering_time.encode(1000) + ) return mock_read_char_raw diff --git a/tests/components/gdacs/test_geo_location.py b/tests/components/gdacs/test_geo_location.py index 467b6dc2fc4..4ea28bd8fd3 100644 --- a/tests/components/gdacs/test_geo_location.py +++ b/tests/components/gdacs/test_geo_location.py @@ -93,9 +93,10 @@ async def test_setup( # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_georss_client.feed.GeoRssFeed.update" - ) as mock_feed_update: + with ( + freeze_time(utcnow), + patch("aio_georss_client.feed.GeoRssFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1, mock_entry_2, mock_entry_3] config_entry.add_to_hass(hass) @@ -227,10 +228,10 @@ async def test_setup_imperial( # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_georss_client.feed.GeoRssFeed.update" - ) as mock_feed_update, patch( - "aio_georss_client.feed.GeoRssFeed.last_timestamp", create=True + with ( + freeze_time(utcnow), + patch("aio_georss_client.feed.GeoRssFeed.update") as mock_feed_update, + patch("aio_georss_client.feed.GeoRssFeed.last_timestamp", create=True), ): mock_feed_update.return_value = "OK", [mock_entry_1] config_entry.add_to_hass(hass) diff --git a/tests/components/gdacs/test_sensor.py b/tests/components/gdacs/test_sensor.py index a21df176bdd..87b66295006 100644 --- a/tests/components/gdacs/test_sensor.py +++ b/tests/components/gdacs/test_sensor.py @@ -57,9 +57,10 @@ async def test_setup(hass: HomeAssistant) -> None: # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_georss_client.feed.GeoRssFeed.update" - ) as mock_feed_update: + with ( + freeze_time(utcnow), + patch("aio_georss_client.feed.GeoRssFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1, mock_entry_2, mock_entry_3] latitude = 32.87336 longitude = -117.22743 diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index ac882716103..e359ddaca9d 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -273,8 +273,9 @@ async def test_limit_refetch( hass.states.async_set("sensor.temp", "5") - with pytest.raises(aiohttp.ServerTimeoutError), patch( - "asyncio.timeout", side_effect=TimeoutError() + with ( + pytest.raises(aiohttp.ServerTimeoutError), + patch("asyncio.timeout", side_effect=TimeoutError()), ): resp = await client.get("/api/camera_proxy/camera.config_test") diff --git a/tests/components/generic/test_config_flow.py b/tests/components/generic/test_config_flow.py index 7eee5f1bd9e..d9b3c848eb6 100644 --- a/tests/components/generic/test_config_flow.py +++ b/tests/components/generic/test_config_flow.py @@ -74,9 +74,12 @@ async def test_form( """Test the form with a normal set of settings.""" respx.get("http://127.0.0.1/testurl/1").respond(stream=fakeimgbytes_png) - with mock_create_stream as mock_setup, patch( - "homeassistant.components.generic.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + mock_create_stream as mock_setup, + patch( + "homeassistant.components.generic.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result1 = await hass.config_entries.flow.async_configure( user_flow["flow_id"], TESTDATA, @@ -187,10 +190,13 @@ async def test_form_still_preview_cam_off( hass_client: ClientSessionGenerator, ) -> None: """Test camera errors are triggered during preview.""" - with patch( - "homeassistant.components.generic.camera.GenericCamera.is_on", - new_callable=PropertyMock(return_value=False), - ), mock_create_stream: + with ( + patch( + "homeassistant.components.generic.camera.GenericCamera.is_on", + new_callable=PropertyMock(return_value=False), + ), + mock_create_stream, + ): result1 = await hass.config_entries.flow.async_configure( user_flow["flow_id"], TESTDATA, @@ -358,8 +364,9 @@ async def test_form_rtsp_mode( data = TESTDATA.copy() data[CONF_RTSP_TRANSPORT] = "tcp" data[CONF_STREAM_SOURCE] = "rtsp://127.0.0.1/testurl/2" - with mock_create_stream as mock_setup, patch( - "homeassistant.components.generic.async_setup_entry", return_value=True + with ( + mock_create_stream as mock_setup, + patch("homeassistant.components.generic.async_setup_entry", return_value=True), ): result1 = await hass.config_entries.flow.async_configure( user_flow["flow_id"], data @@ -640,10 +647,13 @@ async def test_form_stream_io_error( @respx.mock async def test_form_oserror(hass: HomeAssistant, fakeimg_png, user_flow) -> None: """Test we handle OS error when setting up stream.""" - with patch( - "homeassistant.components.generic.config_flow.create_stream", - side_effect=OSError("Some other OSError"), - ), pytest.raises(OSError): + with ( + patch( + "homeassistant.components.generic.config_flow.create_stream", + side_effect=OSError("Some other OSError"), + ), + pytest.raises(OSError), + ): await hass.config_entries.flow.async_configure( user_flow["flow_id"], TESTDATA, @@ -878,9 +888,10 @@ async def test_use_wallclock_as_timestamps_option( ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "init" - with patch( - "homeassistant.components.generic.async_setup_entry", return_value=True - ), mock_create_stream: + with ( + patch("homeassistant.components.generic.async_setup_entry", return_value=True), + mock_create_stream, + ): result2 = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, @@ -892,9 +903,10 @@ async def test_use_wallclock_as_timestamps_option( ) assert result3["type"] == FlowResultType.FORM assert result3["step_id"] == "init" - with patch( - "homeassistant.components.generic.async_setup_entry", return_value=True - ), mock_create_stream: + with ( + patch("homeassistant.components.generic.async_setup_entry", return_value=True), + mock_create_stream, + ): result4 = await hass.config_entries.options.async_configure( result3["flow_id"], user_input={CONF_USE_WALLCLOCK_AS_TIMESTAMPS: True, **TESTDATA}, diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index ac9f3f2347f..365c4ca27bc 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -66,9 +66,10 @@ async def test_entity_lifecycle( mock_entry_4 = _generate_mock_feed_entry("4567", "Title 4", 12.5, (-31.3, 150.3)) utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_geojson_client.feed.GeoJsonFeed.update" - ) as mock_feed_update: + with ( + freeze_time(utcnow), + patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1, mock_entry_2, mock_entry_3] # Load config entry. diff --git a/tests/components/geonetnz_quakes/test_config_flow.py b/tests/components/geonetnz_quakes/test_config_flow.py index 75e595c9900..d4b406cf054 100644 --- a/tests/components/geonetnz_quakes/test_config_flow.py +++ b/tests/components/geonetnz_quakes/test_config_flow.py @@ -52,9 +52,15 @@ async def test_step_import(hass: HomeAssistant) -> None: CONF_MINIMUM_MAGNITUDE: 2.5, } - with patch( - "homeassistant.components.geonetnz_quakes.async_setup_entry", return_value=True - ), patch("homeassistant.components.geonetnz_quakes.async_setup", return_value=True): + with ( + patch( + "homeassistant.components.geonetnz_quakes.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.geonetnz_quakes.async_setup", return_value=True + ), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=conf ) @@ -77,9 +83,15 @@ async def test_step_user(hass: HomeAssistant) -> None: hass.config.longitude = 174.7 conf = {CONF_RADIUS: 25, CONF_MMI: 4} - with patch( - "homeassistant.components.geonetnz_quakes.async_setup_entry", return_value=True - ), patch("homeassistant.components.geonetnz_quakes.async_setup", return_value=True): + with ( + patch( + "homeassistant.components.geonetnz_quakes.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.geonetnz_quakes.async_setup", return_value=True + ), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=conf ) diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index 19f1954d06a..163bca775c9 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -183,8 +183,9 @@ async def test_setup_imperial( # Patching 'utcnow' to gain more control over the timed update. freezer.move_to(dt_util.utcnow()) - with patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, patch( - "aio_geojson_client.feed.GeoJsonFeed.last_timestamp", create=True + with ( + patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, + patch("aio_geojson_client.feed.GeoJsonFeed.last_timestamp", create=True), ): mock_feed_update.return_value = "OK", [mock_entry_1] assert await async_setup_component(hass, geonetnz_quakes.DOMAIN, CONFIG) diff --git a/tests/components/geonetnz_quakes/test_sensor.py b/tests/components/geonetnz_quakes/test_sensor.py index 02edfc910b2..82143baa374 100644 --- a/tests/components/geonetnz_quakes/test_sensor.py +++ b/tests/components/geonetnz_quakes/test_sensor.py @@ -58,9 +58,10 @@ async def test_setup(hass: HomeAssistant) -> None: # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_geojson_client.feed.GeoJsonFeed.update" - ) as mock_feed_update: + with ( + freeze_time(utcnow), + patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1, mock_entry_2, mock_entry_3] assert await async_setup_component(hass, geonetnz_quakes.DOMAIN, CONFIG) # Artificially trigger update and collect events. diff --git a/tests/components/geonetnz_volcano/test_config_flow.py b/tests/components/geonetnz_volcano/test_config_flow.py index 6bc211e72be..e314896dd6b 100644 --- a/tests/components/geonetnz_volcano/test_config_flow.py +++ b/tests/components/geonetnz_volcano/test_config_flow.py @@ -51,10 +51,14 @@ async def test_step_import(hass: HomeAssistant) -> None: flow = config_flow.GeonetnzVolcanoFlowHandler() flow.hass = hass - with patch( - "homeassistant.components.geonetnz_volcano.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.geonetnz_volcano.async_setup", return_value=True + with ( + patch( + "homeassistant.components.geonetnz_volcano.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.geonetnz_volcano.async_setup", return_value=True + ), ): result = await flow.async_step_import(import_config=conf) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY @@ -77,10 +81,14 @@ async def test_step_user(hass: HomeAssistant) -> None: flow = config_flow.GeonetnzVolcanoFlowHandler() flow.hass = hass - with patch( - "homeassistant.components.geonetnz_volcano.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.geonetnz_volcano.async_setup", return_value=True + with ( + patch( + "homeassistant.components.geonetnz_volcano.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.geonetnz_volcano.async_setup", return_value=True + ), ): result = await flow.async_step_user(user_input=conf) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY diff --git a/tests/components/geonetnz_volcano/test_sensor.py b/tests/components/geonetnz_volcano/test_sensor.py index f97b89053fd..d6ebbcd6582 100644 --- a/tests/components/geonetnz_volcano/test_sensor.py +++ b/tests/components/geonetnz_volcano/test_sensor.py @@ -54,9 +54,12 @@ async def test_setup(hass: HomeAssistant) -> None: # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=AsyncMock - ) as mock_feed_update: + with ( + freeze_time(utcnow), + patch( + "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=AsyncMock + ) as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1, mock_entry_2, mock_entry_3] assert await async_setup_component(hass, geonetnz_volcano.DOMAIN, CONFIG) # Artificially trigger update and collect events. @@ -161,11 +164,12 @@ async def test_setup_imperial( # Patching 'utcnow' to gain more control over the timed update. freezer.move_to(dt_util.utcnow()) - with patch( - "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=AsyncMock - ) as mock_feed_update, patch( - "aio_geojson_client.feed.GeoJsonFeed.__init__" - ) as mock_feed_init: + with ( + patch( + "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=AsyncMock + ) as mock_feed_update, + patch("aio_geojson_client.feed.GeoJsonFeed.__init__") as mock_feed_init, + ): mock_feed_update.return_value = "OK", [mock_entry_1] assert await async_setup_component(hass, geonetnz_volcano.DOMAIN, CONFIG) # Artificially trigger update and collect events. diff --git a/tests/components/gios/__init__.py b/tests/components/gios/__init__.py index 482b58ad7ad..d5c43c8acc0 100644 --- a/tests/components/gios/__init__.py +++ b/tests/components/gios/__init__.py @@ -35,17 +35,22 @@ async def init_integration( if invalid_indexes: indexes = {} - with patch( - "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS - ), patch( - "homeassistant.components.gios.Gios._get_station", - return_value=station, - ), patch( - "homeassistant.components.gios.Gios._get_all_sensors", - return_value=sensors, - ), patch( - "homeassistant.components.gios.Gios._get_indexes", - return_value=indexes, + with ( + patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS + ), + patch( + "homeassistant.components.gios.Gios._get_station", + return_value=station, + ), + patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=sensors, + ), + patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value=indexes, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/gios/test_config_flow.py b/tests/components/gios/test_config_flow.py index a735b45975a..4471cfa64ec 100644 --- a/tests/components/gios/test_config_flow.py +++ b/tests/components/gios/test_config_flow.py @@ -50,14 +50,18 @@ async def test_invalid_station_id(hass: HomeAssistant) -> None: async def test_invalid_sensor_data(hass: HomeAssistant) -> None: """Test that errors are shown when sensor data is invalid.""" - with patch( - "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS - ), patch( - "homeassistant.components.gios.Gios._get_station", - return_value=json.loads(load_fixture("gios/station.json")), - ), patch( - "homeassistant.components.gios.Gios._get_sensor", - return_value={}, + with ( + patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS + ), + patch( + "homeassistant.components.gios.Gios._get_station", + return_value=json.loads(load_fixture("gios/station.json")), + ), + patch( + "homeassistant.components.gios.Gios._get_sensor", + return_value={}, + ), ): flow = config_flow.GiosFlowHandler() flow.hass = hass @@ -84,18 +88,23 @@ async def test_cannot_connect(hass: HomeAssistant) -> None: async def test_create_entry(hass: HomeAssistant) -> None: """Test that the user step works.""" - with patch( - "homeassistant.components.gios.Gios._get_stations", - return_value=STATIONS, - ), patch( - "homeassistant.components.gios.Gios._get_station", - return_value=json.loads(load_fixture("gios/station.json")), - ), patch( - "homeassistant.components.gios.Gios._get_all_sensors", - return_value=json.loads(load_fixture("gios/sensors.json")), - ), patch( - "homeassistant.components.gios.Gios._get_indexes", - return_value=json.loads(load_fixture("gios/indexes.json")), + with ( + patch( + "homeassistant.components.gios.Gios._get_stations", + return_value=STATIONS, + ), + patch( + "homeassistant.components.gios.Gios._get_station", + return_value=json.loads(load_fixture("gios/station.json")), + ), + patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=json.loads(load_fixture("gios/sensors.json")), + ), + patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value=json.loads(load_fixture("gios/indexes.json")), + ), ): flow = config_flow.GiosFlowHandler() flow.hass = hass diff --git a/tests/components/gios/test_init.py b/tests/components/gios/test_init.py index 6082fa8c522..e5f3454bcd9 100644 --- a/tests/components/gios/test_init.py +++ b/tests/components/gios/test_init.py @@ -75,15 +75,20 @@ async def test_migrate_device_and_config_entry( station = json.loads(load_fixture("gios/station.json")) sensors = json.loads(load_fixture("gios/sensors.json")) - with patch( - "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS - ), patch( - "homeassistant.components.gios.Gios._get_station", - return_value=station, - ), patch( - "homeassistant.components.gios.Gios._get_all_sensors", - return_value=sensors, - ), patch("homeassistant.components.gios.Gios._get_indexes", return_value=indexes): + with ( + patch( + "homeassistant.components.gios.Gios._get_stations", return_value=STATIONS + ), + patch( + "homeassistant.components.gios.Gios._get_station", + return_value=station, + ), + patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=sensors, + ), + patch("homeassistant.components.gios.Gios._get_indexes", return_value=indexes), + ): config_entry.add_to_hass(hass) device_entry = device_registry.async_get_or_create( diff --git a/tests/components/gios/test_sensor.py b/tests/components/gios/test_sensor.py index 6714882ad3f..60e8722ba24 100644 --- a/tests/components/gios/test_sensor.py +++ b/tests/components/gios/test_sensor.py @@ -293,12 +293,15 @@ async def test_availability(hass: HomeAssistant) -> None: incomplete_sensors = deepcopy(sensors) incomplete_sensors["pm2.5"] = {} future = utcnow() + timedelta(minutes=120) - with patch( - "homeassistant.components.gios.Gios._get_all_sensors", - return_value=incomplete_sensors, - ), patch( - "homeassistant.components.gios.Gios._get_indexes", - return_value={}, + with ( + patch( + "homeassistant.components.gios.Gios._get_all_sensors", + return_value=incomplete_sensors, + ), + patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value={}, + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -319,11 +322,14 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state == STATE_UNAVAILABLE future = utcnow() + timedelta(minutes=180) - with patch( - "homeassistant.components.gios.Gios._get_all_sensors", return_value=sensors - ), patch( - "homeassistant.components.gios.Gios._get_indexes", - return_value=indexes, + with ( + patch( + "homeassistant.components.gios.Gios._get_all_sensors", return_value=sensors + ), + patch( + "homeassistant.components.gios.Gios._get_indexes", + return_value=indexes, + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() diff --git a/tests/components/gogogate2/test_init.py b/tests/components/gogogate2/test_init.py index abc7e4a2326..f7e58296a43 100644 --- a/tests/components/gogogate2/test_init.py +++ b/tests/components/gogogate2/test_init.py @@ -92,8 +92,11 @@ async def test_api_failure_on_startup(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.gogogate2.common.ISmartGateApi.async_info", - side_effect=TimeoutError, - ), pytest.raises(ConfigEntryNotReady): + with ( + patch( + "homeassistant.components.gogogate2.common.ISmartGateApi.async_info", + side_effect=TimeoutError, + ), + pytest.raises(ConfigEntryNotReady), + ): await async_setup_entry(hass, config_entry) diff --git a/tests/components/goodwe/test_config_flow.py b/tests/components/goodwe/test_config_flow.py index b1aabe0be09..0d0a1249ea1 100644 --- a/tests/components/goodwe/test_config_flow.py +++ b/tests/components/goodwe/test_config_flow.py @@ -36,12 +36,15 @@ async def test_manual_setup(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with patch( - "homeassistant.components.goodwe.config_flow.connect", - return_value=mock_inverter(), - ), patch( - "homeassistant.components.goodwe.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.goodwe.config_flow.connect", + return_value=mock_inverter(), + ), + patch( + "homeassistant.components.goodwe.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: TEST_HOST} ) @@ -69,10 +72,13 @@ async def test_manual_setup_already_exists(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with patch( - "homeassistant.components.goodwe.config_flow.connect", - return_value=mock_inverter(), - ), patch("homeassistant.components.goodwe.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.goodwe.config_flow.connect", + return_value=mock_inverter(), + ), + patch("homeassistant.components.goodwe.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: TEST_HOST} ) diff --git a/tests/components/google_assistant/test_http.py b/tests/components/google_assistant/test_http.py index cc1332a372b..1dac75875a6 100644 --- a/tests/components/google_assistant/test_http.py +++ b/tests/components/google_assistant/test_http.py @@ -99,13 +99,17 @@ async def test_update_access_token(hass: HomeAssistant) -> None: await config.async_initialize() base_time = datetime(2019, 10, 14, tzinfo=UTC) - with patch( - "homeassistant.components.google_assistant.http._get_homegraph_token" - ) as mock_get_token, patch( - "homeassistant.components.google_assistant.http._get_homegraph_jwt" - ) as mock_get_jwt, patch( - "homeassistant.core.dt_util.utcnow", - ) as mock_utcnow: + with ( + patch( + "homeassistant.components.google_assistant.http._get_homegraph_token" + ) as mock_get_token, + patch( + "homeassistant.components.google_assistant.http._get_homegraph_jwt" + ) as mock_get_jwt, + patch( + "homeassistant.core.dt_util.utcnow", + ) as mock_utcnow, + ): mock_utcnow.return_value = base_time mock_get_jwt.return_value = jwt mock_get_token.return_value = MOCK_TOKEN diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index a4829e6140f..758ebf63db9 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -27,9 +27,12 @@ async def test_report_state( "event.doorbell", "unknown", attributes={"device_class": "doorbell"} ) - with patch.object( - BASIC_CONFIG, "async_report_state_all", AsyncMock() - ) as mock_report, patch.object(report_state, "INITIAL_REPORT_DELAY", 0): + with ( + patch.object( + BASIC_CONFIG, "async_report_state_all", AsyncMock() + ) as mock_report, + patch.object(report_state, "INITIAL_REPORT_DELAY", 0), + ): unsub = report_state.async_enable_report_state(hass, BASIC_CONFIG) async_fire_time_changed(hass, utcnow()) @@ -74,10 +77,15 @@ async def test_report_state( } # Test that if serialize returns same value, we don't send - with patch( - "homeassistant.components.google_assistant.helpers.GoogleEntity.query_serialize", - return_value={"same": "info"}, - ), patch.object(BASIC_CONFIG, "async_report_state_all", AsyncMock()) as mock_report: + with ( + patch( + "homeassistant.components.google_assistant.helpers.GoogleEntity.query_serialize", + return_value={"same": "info"}, + ), + patch.object( + BASIC_CONFIG, "async_report_state_all", AsyncMock() + ) as mock_report, + ): # New state, so reported hass.states.async_set("light.double_report", "on") await hass.async_block_till_done() @@ -107,11 +115,14 @@ async def test_report_state( assert len(mock_report.mock_calls) == 0 # Test that entities that we can't query don't report a state - with patch.object( - BASIC_CONFIG, "async_report_state_all", AsyncMock() - ) as mock_report, patch( - "homeassistant.components.google_assistant.helpers.GoogleEntity.query_serialize", - side_effect=error.SmartHomeError("mock-error", "mock-msg"), + with ( + patch.object( + BASIC_CONFIG, "async_report_state_all", AsyncMock() + ) as mock_report, + patch( + "homeassistant.components.google_assistant.helpers.GoogleEntity.query_serialize", + side_effect=error.SmartHomeError("mock-error", "mock-msg"), + ), ): hass.states.async_set("light.kitchen", "off") async_fire_time_changed( @@ -148,9 +159,10 @@ async def test_report_notifications( "event.doorbell", "unknown", attributes={"device_class": "doorbell"} ) - with patch.object( - config, "async_report_state_all", AsyncMock() - ) as mock_report, patch.object(report_state, "INITIAL_REPORT_DELAY", 0): + with ( + patch.object(config, "async_report_state_all", AsyncMock()) as mock_report, + patch.object(report_state, "INITIAL_REPORT_DELAY", 0), + ): report_state.async_enable_report_state(hass, config) async_fire_time_changed( @@ -243,9 +255,12 @@ async def test_report_notifications( # Test disconnecting agent user caplog.clear() - with patch.object( - config, "async_report_state", return_value=HTTPStatus.NOT_FOUND - ) as mock_report_state, patch.object(config, "async_disconnect_agent_user"): + with ( + patch.object( + config, "async_report_state", return_value=HTTPStatus.NOT_FOUND + ) as mock_report_state, + patch.object(config, "async_disconnect_agent_user"), + ): event_time = datetime.fromisoformat("2023-08-01T01:03:57+00:00") epoc_event_time = event_time.timestamp() hass.states.async_set( diff --git a/tests/components/google_generative_ai_conversation/test_config_flow.py b/tests/components/google_generative_ai_conversation/test_config_flow.py index bff560dfb29..6ae42a350e6 100644 --- a/tests/components/google_generative_ai_conversation/test_config_flow.py +++ b/tests/components/google_generative_ai_conversation/test_config_flow.py @@ -39,12 +39,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.google_generative_ai_conversation.config_flow.genai.list_models", - ), patch( - "homeassistant.components.google_generative_ai_conversation.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.google_generative_ai_conversation.config_flow.genai.list_models", + ), + patch( + "homeassistant.components.google_generative_ai_conversation.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/google_generative_ai_conversation/test_init.py b/tests/components/google_generative_ai_conversation/test_init.py index bcf0600373f..b77fa14b4cf 100644 --- a/tests/components/google_generative_ai_conversation/test_init.py +++ b/tests/components/google_generative_ai_conversation/test_init.py @@ -130,9 +130,12 @@ async def test_template_error( "prompt": "talk like a {% if True %}smarthome{% else %}pirate please.", }, ) - with patch( - "google.generativeai.get_model", - ), patch("google.generativeai.GenerativeModel"): + with ( + patch( + "google.generativeai.get_model", + ), + patch("google.generativeai.GenerativeModel"), + ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() result = await conversation.async_converse( @@ -198,11 +201,14 @@ async def test_generate_content_service_with_image( "A mail carrier is at your front door delivering a package" ) - with patch("google.generativeai.GenerativeModel") as mock_model, patch( - "homeassistant.components.google_generative_ai_conversation.Path.read_bytes", - return_value=b"image bytes", - ), patch("pathlib.Path.exists", return_value=True), patch.object( - hass.config, "is_allowed_path", return_value=True + with ( + patch("google.generativeai.GenerativeModel") as mock_model, + patch( + "homeassistant.components.google_generative_ai_conversation.Path.read_bytes", + return_value=b"image bytes", + ), + patch("pathlib.Path.exists", return_value=True), + patch.object(hass.config, "is_allowed_path", return_value=True), ): mock_response = MagicMock() mock_response.text = stubbed_generated_content @@ -232,8 +238,11 @@ async def test_generate_content_service_error( mock_config_entry: MockConfigEntry, ) -> None: """Test generate content service handles errors.""" - with patch("google.generativeai.GenerativeModel") as mock_model, pytest.raises( - HomeAssistantError, match="Error generating content: None reason" + with ( + patch("google.generativeai.GenerativeModel") as mock_model, + pytest.raises( + HomeAssistantError, match="Error generating content: None reason" + ), ): mock_model.return_value.generate_content_async = AsyncMock( side_effect=ClientError("reason") @@ -254,14 +263,16 @@ async def test_generate_content_service_with_image_not_allowed_path( snapshot: SnapshotAssertion, ) -> None: """Test generate content service with an image in a not allowed path.""" - with patch("pathlib.Path.exists", return_value=True), patch.object( - hass.config, "is_allowed_path", return_value=False - ), pytest.raises( - HomeAssistantError, - match=( - "Cannot read `doorbell_snapshot.jpg`, no access to path; " - "`allowlist_external_dirs` may need to be adjusted in " - "`configuration.yaml`" + with ( + patch("pathlib.Path.exists", return_value=True), + patch.object(hass.config, "is_allowed_path", return_value=False), + pytest.raises( + HomeAssistantError, + match=( + "Cannot read `doorbell_snapshot.jpg`, no access to path; " + "`allowlist_external_dirs` may need to be adjusted in " + "`configuration.yaml`" + ), ), ): await hass.services.async_call( @@ -283,10 +294,13 @@ async def test_generate_content_service_with_image_not_exists( snapshot: SnapshotAssertion, ) -> None: """Test generate content service with an image that does not exist.""" - with patch("pathlib.Path.exists", return_value=True), patch.object( - hass.config, "is_allowed_path", return_value=True - ), patch("pathlib.Path.exists", return_value=False), pytest.raises( - HomeAssistantError, match="`doorbell_snapshot.jpg` does not exist" + with ( + patch("pathlib.Path.exists", return_value=True), + patch.object(hass.config, "is_allowed_path", return_value=True), + patch("pathlib.Path.exists", return_value=False), + pytest.raises( + HomeAssistantError, match="`doorbell_snapshot.jpg` does not exist" + ), ): await hass.services.async_call( "google_generative_ai_conversation", @@ -307,10 +321,13 @@ async def test_generate_content_service_with_non_image( snapshot: SnapshotAssertion, ) -> None: """Test generate content service with a non image.""" - with patch("pathlib.Path.exists", return_value=True), patch.object( - hass.config, "is_allowed_path", return_value=True - ), patch("pathlib.Path.exists", return_value=True), pytest.raises( - HomeAssistantError, match="`doorbell_snapshot.mp4` is not an image" + with ( + patch("pathlib.Path.exists", return_value=True), + patch.object(hass.config, "is_allowed_path", return_value=True), + patch("pathlib.Path.exists", return_value=True), + pytest.raises( + HomeAssistantError, match="`doorbell_snapshot.mp4` is not an image" + ), ): await hass.services.async_call( "google_generative_ai_conversation", diff --git a/tests/components/google_mail/test_config_flow.py b/tests/components/google_mail/test_config_flow.py index 6456e38b3fc..62db6603988 100644 --- a/tests/components/google_mail/test_config_flow.py +++ b/tests/components/google_mail/test_config_flow.py @@ -46,13 +46,16 @@ async def test_full_flow( assert resp.status == 200 assert resp.headers["content-type"] == "text/html; charset=utf-8" - with patch( - "homeassistant.components.google_mail.async_setup_entry", return_value=True - ) as mock_setup, patch( - "httplib2.Http.request", - return_value=( - Response({}), - bytes(load_fixture("google_mail/get_profile.json"), encoding="UTF-8"), + with ( + patch( + "homeassistant.components.google_mail.async_setup_entry", return_value=True + ) as mock_setup, + patch( + "httplib2.Http.request", + return_value=( + Response({}), + bytes(load_fixture("google_mail/get_profile.json"), encoding="UTF-8"), + ), ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -141,13 +144,16 @@ async def test_reauth( }, ) - with patch( - "homeassistant.components.google_mail.async_setup_entry", return_value=True - ) as mock_setup, patch( - "httplib2.Http.request", - return_value=( - Response({}), - bytes(load_fixture(f"google_mail/{fixture}.json"), encoding="UTF-8"), + with ( + patch( + "homeassistant.components.google_mail.async_setup_entry", return_value=True + ) as mock_setup, + patch( + "httplib2.Http.request", + return_value=( + Response({}), + bytes(load_fixture(f"google_mail/{fixture}.json"), encoding="UTF-8"), + ), ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) diff --git a/tests/components/google_sheets/test_init.py b/tests/components/google_sheets/test_init.py index 8f7ce7603e8..f474e44e925 100644 --- a/tests/components/google_sheets/test_init.py +++ b/tests/components/google_sheets/test_init.py @@ -229,9 +229,12 @@ async def test_append_sheet_api_error( response = Response() response.status_code = 503 - with pytest.raises(HomeAssistantError), patch( - "homeassistant.components.google_sheets.Client.request", - side_effect=APIError(response), + with ( + pytest.raises(HomeAssistantError), + patch( + "homeassistant.components.google_sheets.Client.request", + side_effect=APIError(response), + ), ): await hass.services.async_call( DOMAIN, diff --git a/tests/components/google_tasks/test_config_flow.py b/tests/components/google_tasks/test_config_flow.py index e92da605697..d7ad21292fc 100644 --- a/tests/components/google_tasks/test_config_flow.py +++ b/tests/components/google_tasks/test_config_flow.py @@ -63,9 +63,12 @@ async def test_full_flow( }, ) - with patch( - "homeassistant.components.google_tasks.async_setup_entry", return_value=True - ) as mock_setup, patch("homeassistant.components.google_tasks.config_flow.build"): + with ( + patch( + "homeassistant.components.google_tasks.async_setup_entry", return_value=True + ) as mock_setup, + patch("homeassistant.components.google_tasks.config_flow.build"), + ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == FlowResultType.CREATE_ENTRY assert len(hass.config_entries.async_entries(DOMAIN)) == 1 diff --git a/tests/components/google_travel_time/conftest.py b/tests/components/google_travel_time/conftest.py index d5a1447f767..141b40eff29 100644 --- a/tests/components/google_travel_time/conftest.py +++ b/tests/components/google_travel_time/conftest.py @@ -48,9 +48,12 @@ def bypass_platform_setup_fixture(): @pytest.fixture(name="validate_config_entry") def validate_config_entry_fixture(): """Return valid config entry.""" - with patch("homeassistant.components.google_travel_time.helpers.Client"), patch( - "homeassistant.components.google_travel_time.helpers.distance_matrix" - ) as distance_matrix_mock: + with ( + patch("homeassistant.components.google_travel_time.helpers.Client"), + patch( + "homeassistant.components.google_travel_time.helpers.distance_matrix" + ) as distance_matrix_mock, + ): distance_matrix_mock.return_value = None yield distance_matrix_mock diff --git a/tests/components/google_travel_time/test_sensor.py b/tests/components/google_travel_time/test_sensor.py index bb1ba722540..a7fb263d4c9 100644 --- a/tests/components/google_travel_time/test_sensor.py +++ b/tests/components/google_travel_time/test_sensor.py @@ -26,9 +26,12 @@ from tests.common import MockConfigEntry @pytest.fixture(name="mock_update") def mock_update_fixture(): """Mock an update to the sensor.""" - with patch("homeassistant.components.google_travel_time.sensor.Client"), patch( - "homeassistant.components.google_travel_time.sensor.distance_matrix" - ) as distance_matrix_mock: + with ( + patch("homeassistant.components.google_travel_time.sensor.Client"), + patch( + "homeassistant.components.google_travel_time.sensor.distance_matrix" + ) as distance_matrix_mock, + ): distance_matrix_mock.return_value = { "rows": [ { @@ -224,9 +227,12 @@ async def test_sensor_unit_system( entry_id="test", ) config_entry.add_to_hass(hass) - with patch("homeassistant.components.google_travel_time.sensor.Client"), patch( - "homeassistant.components.google_travel_time.sensor.distance_matrix" - ) as distance_matrix_mock: + with ( + patch("homeassistant.components.google_travel_time.sensor.Client"), + patch( + "homeassistant.components.google_travel_time.sensor.distance_matrix" + ) as distance_matrix_mock, + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/govee_ble/test_sensor.py b/tests/components/govee_ble/test_sensor.py index 099fff7d413..caed4a5c469 100644 --- a/tests/components/govee_ble/test_sensor.py +++ b/tests/components/govee_ble/test_sensor.py @@ -114,9 +114,12 @@ async def test_gvh5178_multi_sensor(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() @@ -140,9 +143,12 @@ async def test_gvh5178_multi_sensor(hass: HomeAssistant) -> None: assert primary_temp_sensor.state == "1.0" # Fastforward time without BLE advertisements - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/govee_light_local/test_config_flow.py b/tests/components/govee_light_local/test_config_flow.py index b76d13a82fc..79baef33969 100644 --- a/tests/components/govee_light_local/test_config_flow.py +++ b/tests/components/govee_light_local/test_config_flow.py @@ -1,4 +1,5 @@ """Test Govee light local config flow.""" + from unittest.mock import AsyncMock, patch from govee_local_api import GoveeDevice @@ -17,12 +18,15 @@ async def test_creating_entry_has_no_devices( mock_govee_api.devices = [] - with patch( - "homeassistant.components.govee_light_local.config_flow.GoveeController", - return_value=mock_govee_api, - ), patch( - "homeassistant.components.govee_light_local.config_flow.DISCOVERY_TIMEOUT", - 0, + with ( + patch( + "homeassistant.components.govee_light_local.config_flow.GoveeController", + return_value=mock_govee_api, + ), + patch( + "homeassistant.components.govee_light_local.config_flow.DISCOVERY_TIMEOUT", + 0, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/tests/components/gree/test_init.py b/tests/components/gree/test_init.py index 1da965e001b..026660cf2d1 100644 --- a/tests/components/gree/test_init.py +++ b/tests/components/gree/test_init.py @@ -15,13 +15,16 @@ async def test_setup_simple(hass: HomeAssistant) -> None: entry = MockConfigEntry(domain=GREE_DOMAIN) entry.add_to_hass(hass) - with patch( - "homeassistant.components.gree.climate.async_setup_entry", - return_value=True, - ) as climate_setup, patch( - "homeassistant.components.gree.switch.async_setup_entry", - return_value=True, - ) as switch_setup: + with ( + patch( + "homeassistant.components.gree.climate.async_setup_entry", + return_value=True, + ) as climate_setup, + patch( + "homeassistant.components.gree.switch.async_setup_entry", + return_value=True, + ) as switch_setup, + ): assert await async_setup_component(hass, GREE_DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/growatt_server/test_config_flow.py b/tests/components/growatt_server/test_config_flow.py index 5703ed02a8a..57777a57783 100644 --- a/tests/components/growatt_server/test_config_flow.py +++ b/tests/components/growatt_server/test_config_flow.py @@ -84,9 +84,10 @@ async def test_no_plants_on_account(hass: HomeAssistant) -> None: plant_list = deepcopy(GROWATT_PLANT_LIST_RESPONSE) plant_list["data"] = [] - with patch( - "growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE - ), patch("growattServer.GrowattApi.plant_list", return_value=plant_list): + with ( + patch("growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE), + patch("growattServer.GrowattApi.plant_list", return_value=plant_list), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input ) @@ -104,10 +105,13 @@ async def test_multiple_plant_ids(hass: HomeAssistant) -> None: plant_list = deepcopy(GROWATT_PLANT_LIST_RESPONSE) plant_list["data"].append(plant_list["data"][0]) - with patch( - "growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE - ), patch("growattServer.GrowattApi.plant_list", return_value=plant_list), patch( - "homeassistant.components.growatt_server.async_setup_entry", return_value=True + with ( + patch("growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE), + patch("growattServer.GrowattApi.plant_list", return_value=plant_list), + patch( + "homeassistant.components.growatt_server.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input @@ -134,13 +138,16 @@ async def test_one_plant_on_account(hass: HomeAssistant) -> None: ) user_input = FIXTURE_USER_INPUT.copy() - with patch( - "growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE - ), patch( - "growattServer.GrowattApi.plant_list", - return_value=GROWATT_PLANT_LIST_RESPONSE, - ), patch( - "homeassistant.components.growatt_server.async_setup_entry", return_value=True + with ( + patch("growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE), + patch( + "growattServer.GrowattApi.plant_list", + return_value=GROWATT_PLANT_LIST_RESPONSE, + ), + patch( + "homeassistant.components.growatt_server.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input @@ -161,11 +168,12 @@ async def test_existing_plant_configured(hass: HomeAssistant) -> None: ) user_input = FIXTURE_USER_INPUT.copy() - with patch( - "growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE - ), patch( - "growattServer.GrowattApi.plant_list", - return_value=GROWATT_PLANT_LIST_RESPONSE, + with ( + patch("growattServer.GrowattApi.login", return_value=GROWATT_LOGIN_RESPONSE), + patch( + "growattServer.GrowattApi.plant_list", + return_value=GROWATT_PLANT_LIST_RESPONSE, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input diff --git a/tests/components/guardian/conftest.py b/tests/components/guardian/conftest.py index 9f9b0f95e0d..df517aba603 100644 --- a/tests/components/guardian/conftest.py +++ b/tests/components/guardian/conftest.py @@ -107,35 +107,47 @@ async def setup_guardian_fixture( data_wifi_status, ): """Define a fixture to set up Guardian.""" - with patch("aioguardian.client.Client.connect"), patch( - "aioguardian.commands.sensor.SensorCommands.pair_dump", - return_value=data_sensor_pair_dump, - ), patch( - "aioguardian.commands.sensor.SensorCommands.pair_sensor", - return_value=data_sensor_pair_sensor, - ), patch( - "aioguardian.commands.sensor.SensorCommands.paired_sensor_status", - return_value=data_sensor_paired_sensor_status, - ), patch( - "aioguardian.commands.system.SystemCommands.diagnostics", - return_value=data_system_diagnostics, - ), patch( - "aioguardian.commands.system.SystemCommands.onboard_sensor_status", - return_value=data_system_onboard_sensor_status, - ), patch( - "aioguardian.commands.system.SystemCommands.ping", - return_value=data_system_ping, - ), patch( - "aioguardian.commands.valve.ValveCommands.status", - return_value=data_valve_status, - ), patch( - "aioguardian.commands.wifi.WiFiCommands.status", - return_value=data_wifi_status, - ), patch( - "aioguardian.client.Client.disconnect", - ), patch( - "homeassistant.components.guardian.PLATFORMS", - [], + with ( + patch("aioguardian.client.Client.connect"), + patch( + "aioguardian.commands.sensor.SensorCommands.pair_dump", + return_value=data_sensor_pair_dump, + ), + patch( + "aioguardian.commands.sensor.SensorCommands.pair_sensor", + return_value=data_sensor_pair_sensor, + ), + patch( + "aioguardian.commands.sensor.SensorCommands.paired_sensor_status", + return_value=data_sensor_paired_sensor_status, + ), + patch( + "aioguardian.commands.system.SystemCommands.diagnostics", + return_value=data_system_diagnostics, + ), + patch( + "aioguardian.commands.system.SystemCommands.onboard_sensor_status", + return_value=data_system_onboard_sensor_status, + ), + patch( + "aioguardian.commands.system.SystemCommands.ping", + return_value=data_system_ping, + ), + patch( + "aioguardian.commands.valve.ValveCommands.status", + return_value=data_valve_status, + ), + patch( + "aioguardian.commands.wifi.WiFiCommands.status", + return_value=data_wifi_status, + ), + patch( + "aioguardian.client.Client.disconnect", + ), + patch( + "homeassistant.components.guardian.PLATFORMS", + [], + ), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/habitica/test_config_flow.py b/tests/components/habitica/test_config_flow.py index 1fcddb0f766..fe5ddcacdea 100644 --- a/tests/components/habitica/test_config_flow.py +++ b/tests/components/habitica/test_config_flow.py @@ -23,15 +23,19 @@ async def test_form(hass: HomeAssistant) -> None: mock_obj = MagicMock() mock_obj.user.get = AsyncMock() - with patch( - "homeassistant.components.habitica.config_flow.HabitipyAsync", - return_value=mock_obj, - ), patch( - "homeassistant.components.habitica.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.habitica.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.habitica.config_flow.HabitipyAsync", + return_value=mock_obj, + ), + patch( + "homeassistant.components.habitica.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.habitica.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"api_user": "test-api-user", "api_key": "test-api-key"}, diff --git a/tests/components/hardware/test_websocket_api.py b/tests/components/hardware/test_websocket_api.py index fe33d1d0c09..e8099069a9c 100644 --- a/tests/components/hardware/test_websocket_api.py +++ b/tests/components/hardware/test_websocket_api.py @@ -64,14 +64,17 @@ async def test_system_status_subscription( VirtualMem = namedtuple("VirtualMemory", ["available", "percent", "total"]) vmem = VirtualMem(10 * 1024**2, 50, 30 * 1024**2) - with patch.object( - mock_psutil.psutil, - "cpu_percent", - return_value=123, - ), patch.object( - mock_psutil.psutil, - "virtual_memory", - return_value=vmem, + with ( + patch.object( + mock_psutil.psutil, + "cpu_percent", + return_value=123, + ), + patch.object( + mock_psutil.psutil, + "virtual_memory", + return_value=vmem, + ), ): freezer.tick(TEST_TIME_ADVANCE_INTERVAL) await hass.async_block_till_done() @@ -91,9 +94,10 @@ async def test_system_status_subscription( response = await client.receive_json() assert response["success"] - with patch.object(mock_psutil.psutil, "cpu_percent") as cpu_mock, patch.object( - mock_psutil.psutil, "virtual_memory" - ) as vmem_mock: + with ( + patch.object(mock_psutil.psutil, "cpu_percent") as cpu_mock, + patch.object(mock_psutil.psutil, "virtual_memory") as vmem_mock, + ): freezer.tick(TEST_TIME_ADVANCE_INTERVAL) await hass.async_block_till_done() cpu_mock.assert_not_called() diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 2451a59afa3..c2daa98728b 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -32,13 +32,16 @@ async def test_user_form(hass: HomeAssistant) -> None: assert result["errors"] == {} harmonyapi = _get_mock_harmonyapi(connect=True) - with patch( - "homeassistant.components.harmony.util.HarmonyAPI", - return_value=harmonyapi, - ), patch( - "homeassistant.components.harmony.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.harmony.util.HarmonyAPI", + return_value=harmonyapi, + ), + patch( + "homeassistant.components.harmony.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.2.3.4", "name": "friend"}, @@ -84,13 +87,16 @@ async def test_form_ssdp(hass: HomeAssistant) -> None: harmonyapi = _get_mock_harmonyapi(connect=True) - with patch( - "homeassistant.components.harmony.util.HarmonyAPI", - return_value=harmonyapi, - ), patch( - "homeassistant.components.harmony.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.harmony.util.HarmonyAPI", + return_value=harmonyapi, + ), + patch( + "homeassistant.components.harmony.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index f795e19d91f..21eeedb89ad 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -29,12 +29,17 @@ def disable_security_filter(): @pytest.fixture def hassio_env(): """Fixture to inject hassio env.""" - with patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value={"result": "ok", "data": {}}, - ), patch.dict(os.environ, {"SUPERVISOR_TOKEN": SUPERVISOR_TOKEN}), patch( - "homeassistant.components.hassio.HassIO.get_info", - Mock(side_effect=HassioAPIError()), + with ( + patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value={"result": "ok", "data": {}}, + ), + patch.dict(os.environ, {"SUPERVISOR_TOKEN": SUPERVISOR_TOKEN}), + patch( + "homeassistant.components.hassio.HassIO.get_info", + Mock(side_effect=HassioAPIError()), + ), ): yield @@ -42,22 +47,29 @@ def hassio_env(): @pytest.fixture def hassio_stubs(hassio_env, hass, hass_client, aioclient_mock): """Create mock hassio http client.""" - with patch( - "homeassistant.components.hassio.HassIO.update_hass_api", - return_value={"result": "ok"}, - ) as hass_api, patch( - "homeassistant.components.hassio.HassIO.update_hass_timezone", - return_value={"result": "ok"}, - ), patch( - "homeassistant.components.hassio.HassIO.get_info", - side_effect=HassioAPIError(), - ), patch( - "homeassistant.components.hassio.HassIO.get_ingress_panels", - return_value={"panels": []}, - ), patch( - "homeassistant.components.hassio.issues.SupervisorIssues.setup", - ), patch( - "homeassistant.components.hassio.HassIO.refresh_updates", + with ( + patch( + "homeassistant.components.hassio.HassIO.update_hass_api", + return_value={"result": "ok"}, + ) as hass_api, + patch( + "homeassistant.components.hassio.HassIO.update_hass_timezone", + return_value={"result": "ok"}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_info", + side_effect=HassioAPIError(), + ), + patch( + "homeassistant.components.hassio.HassIO.get_ingress_panels", + return_value={"panels": []}, + ), + patch( + "homeassistant.components.hassio.issues.SupervisorIssues.setup", + ), + patch( + "homeassistant.components.hassio.HassIO.refresh_updates", + ), ): hass.set_state(CoreState.starting) hass.loop.run_until_complete(async_setup_component(hass, "hassio", {})) diff --git a/tests/components/hassio/test_config_flow.py b/tests/components/hassio/test_config_flow.py index 4067c9e9afe..1c56f4e25f5 100644 --- a/tests/components/hassio/test_config_flow.py +++ b/tests/components/hassio/test_config_flow.py @@ -9,12 +9,15 @@ from homeassistant.core import HomeAssistant async def test_config_flow(hass: HomeAssistant) -> None: """Test we get the form.""" - with patch( - "homeassistant.components.hassio.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hassio.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.hassio.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hassio.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "system"} ) diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index 7a7d543afb7..0783ee77932 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -125,12 +125,15 @@ async def test_hassio_discovery_startup_done( json={"result": "ok", "data": {"name": "Mosquitto Test"}}, ) - with patch( - "homeassistant.components.hassio.HassIO.update_hass_api", - return_value={"result": "ok"}, - ), patch( - "homeassistant.components.hassio.HassIO.get_info", - Mock(side_effect=HassioAPIError()), + with ( + patch( + "homeassistant.components.hassio.HassIO.update_hass_api", + return_value={"result": "ok"}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_info", + Mock(side_effect=HassioAPIError()), + ), ): await hass.async_start() await async_setup_component(hass, "hassio", {}) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 099b75d3686..da49b8d9f16 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -439,8 +439,9 @@ async def test_setup_hassio_no_additional_data( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test setup with API push default data.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch.dict( - os.environ, {"SUPERVISOR_TOKEN": "123456"} + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch.dict(os.environ, {"SUPERVISOR_TOKEN": "123456"}), ): result = await async_setup_component(hass, "hassio", {"hassio": {}}) await hass.async_block_till_done() @@ -461,9 +462,12 @@ async def test_warn_when_cannot_connect( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Fail warn when we cannot connect.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value=None, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value=None, + ), ): result = await async_setup_component(hass, "hassio", {}) assert result @@ -496,9 +500,12 @@ async def test_service_calls( caplog: pytest.LogCaptureFixture, ) -> None: """Call service and check the API calls behind that.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value=None, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value=None, + ), ): assert await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() @@ -641,9 +648,12 @@ async def test_invalid_service_calls( aioclient_mock: AiohttpClientMocker, ) -> None: """Call service with invalid input and check that it raises.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value=None, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value=None, + ), ): assert await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() @@ -680,12 +690,16 @@ async def test_addon_service_call_with_complex_slug( }, ], } - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value=None, - ), patch( - "homeassistant.components.hassio.HassIO.get_supervisor_info", - return_value=supervisor_mock_data, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value=None, + ), + patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), ): assert await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() @@ -790,12 +804,16 @@ async def test_device_registry_calls(hass: HomeAssistant) -> None: "version_latest": "5.12", } - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.get_supervisor_info", - return_value=supervisor_mock_data, - ), patch( - "homeassistant.components.hassio.HassIO.get_os_info", - return_value=os_mock_data, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), + patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ), ): config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) @@ -823,12 +841,15 @@ async def test_device_registry_calls(hass: HomeAssistant) -> None: } # Test that when addon is removed, next update will remove the add-on and subsequent updates won't - with patch( - "homeassistant.components.hassio.HassIO.get_supervisor_info", - return_value=supervisor_mock_data, - ), patch( - "homeassistant.components.hassio.HassIO.get_os_info", - return_value=os_mock_data, + with ( + patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), + patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ), ): async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1)) await hass.async_block_till_done(wait_background_tasks=True) @@ -870,19 +891,23 @@ async def test_device_registry_calls(hass: HomeAssistant) -> None: # Test that when addon is added, next update will reload the entry so we register # a new device - with patch( - "homeassistant.components.hassio.HassIO.get_supervisor_info", - return_value=supervisor_mock_data, - ), patch( - "homeassistant.components.hassio.HassIO.get_os_info", - return_value=os_mock_data, - ), patch( - "homeassistant.components.hassio.HassIO.get_info", - return_value={ - "supervisor": "222", - "homeassistant": "0.110.0", - "hassos": None, - }, + with ( + patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ), + patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value=os_mock_data, + ), + patch( + "homeassistant.components.hassio.HassIO.get_info", + return_value={ + "supervisor": "222", + "homeassistant": "0.110.0", + "hassos": None, + }, + ), ): async_fire_time_changed(hass, dt_util.now() + timedelta(hours=3)) await hass.async_block_till_done() @@ -894,9 +919,12 @@ async def test_coordinator_updates( ) -> None: """Test coordinator updates.""" await async_setup_component(hass, "homeassistant", {}) - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.refresh_updates" - ) as refresh_updates_mock: + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.refresh_updates" + ) as refresh_updates_mock, + ): config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -968,9 +996,12 @@ async def test_coordinator_updates_stats_entities_enabled( ) -> None: """Test coordinator updates with stats entities enabled.""" await async_setup_component(hass, "homeassistant", {}) - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.refresh_updates" - ) as refresh_updates_mock: + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.refresh_updates" + ) as refresh_updates_mock, + ): config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -1059,10 +1090,13 @@ async def test_setup_hardware_integration( ) -> None: """Test setup initiates hardware integration.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch( - f"homeassistant.components.{integration}.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + f"homeassistant.components.{integration}.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await async_setup_component(hass, "hassio", {"hassio": {}}) await hass.async_block_till_done() diff --git a/tests/components/hassio/test_update.py b/tests/components/hassio/test_update.py index c89636cb65b..f6b61aeedab 100644 --- a/tests/components/hassio/test_update.py +++ b/tests/components/hassio/test_update.py @@ -470,9 +470,12 @@ async def test_release_notes_between_versions( config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.data.get_addons_changelogs", - return_value={"test": "# 2.0.1\nNew updates\n# 2.0.0\nOld updates"}, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.data.get_addons_changelogs", + return_value={"test": "# 2.0.1\nNew updates\n# 2.0.0\nOld updates"}, + ), ): result = await async_setup_component( hass, @@ -506,9 +509,12 @@ async def test_release_notes_full( config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.data.get_addons_changelogs", - return_value={"test": "# 2.0.0\nNew updates\n# 2.0.0\nOld updates"}, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.data.get_addons_changelogs", + return_value={"test": "# 2.0.0\nNew updates\n# 2.0.0\nOld updates"}, + ), ): result = await async_setup_component( hass, @@ -542,9 +548,12 @@ async def test_not_release_notes( config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN) config_entry.add_to_hass(hass) - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.data.get_addons_changelogs", - return_value={"test": None}, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.data.get_addons_changelogs", + return_value={"test": None}, + ), ): result = await async_setup_component( hass, @@ -570,13 +579,16 @@ async def test_not_release_notes( async def test_no_os_entity(hass: HomeAssistant) -> None: """Test handling where there is no os entity.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.get_info", - return_value={ - "supervisor": "222", - "homeassistant": "0.110.0", - "hassos": None, - }, + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.get_info", + return_value={ + "supervisor": "222", + "homeassistant": "0.110.0", + "hassos": None, + }, + ), ): result = await async_setup_component( hass, @@ -594,15 +606,20 @@ async def test_setting_up_core_update_when_addon_fails( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test setting up core update when single addon fails.""" - with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.get_addon_stats", - side_effect=HassioAPIError("add-on is not running"), - ), patch( - "homeassistant.components.hassio.HassIO.get_addon_changelog", - side_effect=HassioAPIError("add-on is not running"), - ), patch( - "homeassistant.components.hassio.HassIO.get_addon_info", - side_effect=HassioAPIError("add-on is not running"), + with ( + patch.dict(os.environ, MOCK_ENVIRON), + patch( + "homeassistant.components.hassio.HassIO.get_addon_stats", + side_effect=HassioAPIError("add-on is not running"), + ), + patch( + "homeassistant.components.hassio.HassIO.get_addon_changelog", + side_effect=HassioAPIError("add-on is not running"), + ), + patch( + "homeassistant.components.hassio.HassIO.get_addon_info", + side_effect=HassioAPIError("add-on is not running"), + ), ): result = await async_setup_component( hass, diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index 785d7c1d619..a12f4c610ad 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -53,8 +53,9 @@ def controller_fixture( mock_heos.create_group.return_value = None mock = Mock(return_value=mock_heos) - with patch("homeassistant.components.heos.Heos", new=mock), patch( - "homeassistant.components.heos.config_flow.Heos", new=mock + with ( + patch("homeassistant.components.heos.Heos", new=mock), + patch("homeassistant.components.heos.config_flow.Heos", new=mock), ): yield mock_heos diff --git a/tests/components/here_travel_time/conftest.py b/tests/components/here_travel_time/conftest.py index 3cbbc6d8c78..f318016315a 100644 --- a/tests/components/here_travel_time/conftest.py +++ b/tests/components/here_travel_time/conftest.py @@ -20,35 +20,40 @@ BIKE_RESPONSE = json.loads(load_fixture("here_travel_time/bike_response.json")) @pytest.fixture(name="valid_response") def valid_response_fixture(): """Return valid api response.""" - with patch( - "here_transit.HERETransitApi.route", return_value=TRANSIT_RESPONSE - ), patch( - "here_routing.HERERoutingApi.route", - return_value=RESPONSE, - ) as mock: + with ( + patch("here_transit.HERETransitApi.route", return_value=TRANSIT_RESPONSE), + patch( + "here_routing.HERERoutingApi.route", + return_value=RESPONSE, + ) as mock, + ): yield mock @pytest.fixture(name="bike_response") def bike_response_fixture(): """Return valid api response.""" - with patch( - "here_transit.HERETransitApi.route", return_value=TRANSIT_RESPONSE - ), patch( - "here_routing.HERERoutingApi.route", - return_value=BIKE_RESPONSE, - ) as mock: + with ( + patch("here_transit.HERETransitApi.route", return_value=TRANSIT_RESPONSE), + patch( + "here_routing.HERERoutingApi.route", + return_value=BIKE_RESPONSE, + ) as mock, + ): yield mock @pytest.fixture(name="no_attribution_response") def no_attribution_response_fixture(): """Return valid api response without attribution.""" - with patch( - "here_transit.HERETransitApi.route", - return_value=NO_ATTRIBUTION_TRANSIT_RESPONSE, - ), patch( - "here_routing.HERERoutingApi.route", - return_value=RESPONSE, - ) as mock: + with ( + patch( + "here_transit.HERETransitApi.route", + return_value=NO_ATTRIBUTION_TRANSIT_RESPONSE, + ), + patch( + "here_routing.HERERoutingApi.route", + return_value=RESPONSE, + ) as mock, + ): yield mock diff --git a/tests/components/hisense_aehw4a1/test_init.py b/tests/components/hisense_aehw4a1/test_init.py index ff49879d344..1abac3421a6 100644 --- a/tests/components/hisense_aehw4a1/test_init.py +++ b/tests/components/hisense_aehw4a1/test_init.py @@ -12,13 +12,16 @@ from homeassistant.setup import async_setup_component async def test_creating_entry_sets_up_climate_discovery(hass: HomeAssistant) -> None: """Test setting up Hisense AEH-W4A1 loads the climate component.""" - with patch( - "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.discovery", - return_value=["1.2.3.4"], - ), patch( - "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.discovery", + return_value=["1.2.3.4"], + ), + patch( + "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", + return_value=True, + ) as mock_setup, + ): result = await hass.config_entries.flow.async_init( hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -36,13 +39,16 @@ async def test_creating_entry_sets_up_climate_discovery(hass: HomeAssistant) -> async def test_configuring_hisense_w4a1_create_entry(hass: HomeAssistant) -> None: """Test that specifying config will create an entry.""" - with patch( - "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", - return_value=True, - ), patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", + return_value=True, + ), + patch( + "homeassistant.components.hisense_aehw4a1.async_setup_entry", + return_value=True, + ) as mock_setup, + ): await async_setup_component( hass, hisense_aehw4a1.DOMAIN, @@ -57,13 +63,16 @@ async def test_configuring_hisense_w4a1_not_creates_entry_for_device_not_found( hass: HomeAssistant, ) -> None: """Test that specifying config will not create an entry.""" - with patch( - "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", - side_effect=exceptions.ConnectionError, - ), patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", + side_effect=exceptions.ConnectionError, + ), + patch( + "homeassistant.components.hisense_aehw4a1.async_setup_entry", + return_value=True, + ) as mock_setup, + ): await async_setup_component( hass, hisense_aehw4a1.DOMAIN, diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index e51fba9990e..1982ec12188 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -611,10 +611,13 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(start_time): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(start_time), + ): await async_setup_component( hass, "sensor", @@ -709,10 +712,13 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_sin ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(start_time): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(start_time), + ): await async_setup_component( hass, "sensor", @@ -823,10 +829,13 @@ async def test_async_start_from_history_and_switch_to_watching_state_changes_mul ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(start_time): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(start_time), + ): await async_setup_component( hass, "sensor", @@ -1050,10 +1059,13 @@ async def test_does_not_work_into_the_future( assert hass.states.get("sensor.sensor2").state == "0.0833333333333333" past_the_window = start_time + timedelta(hours=25) - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - return_value=[], - ), freeze_time(past_the_window): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + return_value=[], + ), + freeze_time(past_the_window), + ): async_fire_time_changed(hass, past_the_window) await hass.async_block_till_done(wait_background_tasks=True) @@ -1072,20 +1084,26 @@ async def test_does_not_work_into_the_future( } past_the_window_with_data = start_time + timedelta(hours=26) - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_off_states, - ), freeze_time(past_the_window_with_data): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_off_states, + ), + freeze_time(past_the_window_with_data), + ): async_fire_time_changed(hass, past_the_window_with_data) await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("sensor.sensor1").state == STATE_UNKNOWN at_the_next_window_with_data = start_time + timedelta(days=1, hours=23) - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_off_states, - ), freeze_time(at_the_next_window_with_data): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_off_states, + ), + freeze_time(at_the_next_window_with_data), + ): async_fire_time_changed(hass, at_the_next_window_with_data) await hass.async_block_till_done(wait_background_tasks=True) @@ -1204,10 +1222,13 @@ async def test_measure_sliding_window( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(start_time): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(start_time), + ): for i in range(1, 5): await async_update_entity(hass, f"sensor.sensor{i}") await hass.async_block_till_done() @@ -1218,10 +1239,13 @@ async def test_measure_sliding_window( assert hass.states.get("sensor.sensor4").state == "41.7" past_next_update = start_time + timedelta(minutes=30) - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(past_next_update): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(past_next_update), + ): async_fire_time_changed(hass, past_next_update) await hass.async_block_till_done() @@ -1253,10 +1277,13 @@ async def test_measure_from_end_going_backwards( ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(start_time): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(start_time), + ): await async_setup_component( hass, "sensor", @@ -1313,10 +1340,13 @@ async def test_measure_from_end_going_backwards( assert hass.states.get("sensor.sensor4").state == "83.3" past_next_update = start_time + timedelta(minutes=30) - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(past_next_update): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(past_next_update), + ): async_fire_time_changed(hass, past_next_update) await hass.async_block_till_done() @@ -1446,9 +1476,12 @@ async def test_end_time_with_microseconds_zeroed( ] } - with freeze_time(time_200), patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, + with ( + freeze_time(time_200), + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), ): await async_setup_component( hass, @@ -1637,10 +1670,13 @@ async def test_history_stats_handles_floored_timestamps( ] } - with patch( - "homeassistant.components.recorder.history.state_changes_during_period", - _fake_states, - ), freeze_time(start_time): + with ( + patch( + "homeassistant.components.recorder.history.state_changes_during_period", + _fake_states, + ), + freeze_time(start_time), + ): await async_setup_component( hass, "sensor", diff --git a/tests/components/hive/test_config_flow.py b/tests/components/hive/test_config_flow.py index 383e4c78ace..b1553b2c485 100644 --- a/tests/components/hive/test_config_flow.py +++ b/tests/components/hive/test_config_flow.py @@ -27,21 +27,25 @@ MFA_INVALID_CODE = "HIVE" async def test_import_flow(hass: HomeAssistant) -> None: """Check import flow.""" - with patch( - "homeassistant.components.hive.config_flow.Auth.login", - return_value={ - "ChallengeName": "SUCCESS", - "AuthenticationResult": { - "RefreshToken": "mock-refresh-token", - "AccessToken": "mock-access-token", + with ( + patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, }, - }, - ), patch( - "homeassistant.components.hive.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + ), + patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -75,21 +79,25 @@ async def test_user_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.hive.config_flow.Auth.login", - return_value={ - "ChallengeName": "SUCCESS", - "AuthenticationResult": { - "RefreshToken": "mock-refresh-token", - "AccessToken": "mock-access-token", + with ( + patch( + "homeassistant.components.hive.config_flow.Auth.login", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, }, - }, - ), patch( - "homeassistant.components.hive.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + ), + patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, @@ -163,22 +171,27 @@ async def test_user_flow_2fa(hass: HomeAssistant) -> None: assert result3["step_id"] == "configuration" assert result3["errors"] == {} - with patch( - "homeassistant.components.hive.config_flow.Auth.device_registration", - return_value=True, - ), patch( - "homeassistant.components.hive.config_flow.Auth.get_device_data", - return_value=[ - "mock-device-group-key", - "mock-device-key", - "mock-device-password", - ], - ), patch( - "homeassistant.components.hive.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.hive.config_flow.Auth.device_registration", + return_value=True, + ), + patch( + "homeassistant.components.hive.config_flow.Auth.get_device_data", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], + ), + patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -317,19 +330,22 @@ async def test_reauth_2fa_flow(hass: HomeAssistant) -> None: }, ) - with patch( - "homeassistant.components.hive.config_flow.Auth.sms_2fa", - return_value={ - "ChallengeName": "SUCCESS", - "AuthenticationResult": { - "RefreshToken": "mock-refresh-token", - "AccessToken": "mock-access-token", + with ( + patch( + "homeassistant.components.hive.config_flow.Auth.sms_2fa", + return_value={ + "ChallengeName": "SUCCESS", + "AuthenticationResult": { + "RefreshToken": "mock-refresh-token", + "AccessToken": "mock-access-token", + }, }, - }, - ), patch( - "homeassistant.components.hive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + ), + patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -446,22 +462,27 @@ async def test_user_flow_2fa_send_new_code(hass: HomeAssistant) -> None: assert result4["step_id"] == "configuration" assert result4["errors"] == {} - with patch( - "homeassistant.components.hive.config_flow.Auth.device_registration", - return_value=True, - ), patch( - "homeassistant.components.hive.config_flow.Auth.get_device_data", - return_value=[ - "mock-device-group-key", - "mock-device-key", - "mock-device-password", - ], - ), patch( - "homeassistant.components.hive.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.hive.config_flow.Auth.device_registration", + return_value=True, + ), + patch( + "homeassistant.components.hive.config_flow.Auth.get_device_data", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], + ), + patch( + "homeassistant.components.hive.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result5 = await hass.config_entries.flow.async_configure( result4["flow_id"], {CONF_DEVICE_NAME: DEVICE_NAME} ) @@ -718,16 +739,19 @@ async def test_user_flow_2fa_unknown_error(hass: HomeAssistant) -> None: assert result3["step_id"] == "configuration" assert result3["errors"] == {} - with patch( - "homeassistant.components.hive.config_flow.Auth.device_registration", - return_value=True, - ), patch( - "homeassistant.components.hive.config_flow.Auth.get_device_data", - return_value=[ - "mock-device-group-key", - "mock-device-key", - "mock-device-password", - ], + with ( + patch( + "homeassistant.components.hive.config_flow.Auth.device_registration", + return_value=True, + ), + patch( + "homeassistant.components.hive.config_flow.Auth.get_device_data", + return_value=[ + "mock-device-group-key", + "mock-device-key", + "mock-device-password", + ], + ), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/hlk_sw16/test_config_flow.py b/tests/components/hlk_sw16/test_config_flow.py index 3079d72baf8..e4770343114 100644 --- a/tests/components/hlk_sw16/test_config_flow.py +++ b/tests/components/hlk_sw16/test_config_flow.py @@ -65,15 +65,19 @@ async def test_form(hass: HomeAssistant) -> None: mock_hlk_sw16_connection = await create_mock_hlk_sw16_connection(False) - with patch( - "homeassistant.components.hlk_sw16.config_flow.create_hlk_sw16_connection", - return_value=mock_hlk_sw16_connection, - ), patch( - "homeassistant.components.hlk_sw16.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hlk_sw16.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.hlk_sw16.config_flow.create_hlk_sw16_connection", + return_value=mock_hlk_sw16_connection, + ), + patch( + "homeassistant.components.hlk_sw16.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hlk_sw16.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], conf, @@ -126,15 +130,19 @@ async def test_import(hass: HomeAssistant) -> None: mock_hlk_sw16_connection = await create_mock_hlk_sw16_connection(False) - with patch( - "homeassistant.components.hlk_sw16.config_flow.connect_client", - return_value=mock_hlk_sw16_connection, - ), patch( - "homeassistant.components.hlk_sw16.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.hlk_sw16.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.hlk_sw16.config_flow.connect_client", + return_value=mock_hlk_sw16_connection, + ), + patch( + "homeassistant.components.hlk_sw16.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.hlk_sw16.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], conf, diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index faa2f34d33c..84319df2888 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -473,10 +473,13 @@ async def test_raises_when_db_upgrade_in_progress( """Test an exception is raised when the database migration is in progress.""" await async_setup_component(hass, "homeassistant", {}) - with pytest.raises(HomeAssistantError), patch( - "homeassistant.helpers.recorder.async_migration_in_progress", - return_value=True, - ) as mock_async_migration_in_progress: + with ( + pytest.raises(HomeAssistantError), + patch( + "homeassistant.helpers.recorder.async_migration_in_progress", + return_value=True, + ) as mock_async_migration_in_progress, + ): await hass.services.async_call( "homeassistant", service, @@ -488,11 +491,12 @@ async def test_raises_when_db_upgrade_in_progress( assert mock_async_migration_in_progress.called caplog.clear() - with patch( - "homeassistant.helpers.recorder.async_migration_in_progress", - return_value=False, - ) as mock_async_migration_in_progress, patch( - "homeassistant.config.async_check_ha_config_file", return_value=None + with ( + patch( + "homeassistant.helpers.recorder.async_migration_in_progress", + return_value=False, + ) as mock_async_migration_in_progress, + patch("homeassistant.config.async_check_ha_config_file", return_value=None), ): await hass.services.async_call( "homeassistant", @@ -511,12 +515,16 @@ async def test_raises_when_config_is_invalid( """Test an exception is raised when the configuration is invalid.""" await async_setup_component(hass, "homeassistant", {}) - with pytest.raises(HomeAssistantError), patch( - "homeassistant.helpers.recorder.async_migration_in_progress", - return_value=False, - ), patch( - "homeassistant.config.async_check_ha_config_file", return_value=["Error 1"] - ) as mock_async_check_ha_config_file: + with ( + pytest.raises(HomeAssistantError), + patch( + "homeassistant.helpers.recorder.async_migration_in_progress", + return_value=False, + ), + patch( + "homeassistant.config.async_check_ha_config_file", return_value=["Error 1"] + ) as mock_async_check_ha_config_file, + ): await hass.services.async_call( "homeassistant", SERVICE_HOMEASSISTANT_RESTART, @@ -529,12 +537,15 @@ async def test_raises_when_config_is_invalid( assert mock_async_check_ha_config_file.called caplog.clear() - with patch( - "homeassistant.helpers.recorder.async_migration_in_progress", - return_value=False, - ), patch( - "homeassistant.config.async_check_ha_config_file", return_value=None - ) as mock_async_check_ha_config_file: + with ( + patch( + "homeassistant.helpers.recorder.async_migration_in_progress", + return_value=False, + ), + patch( + "homeassistant.config.async_check_ha_config_file", return_value=None + ) as mock_async_check_ha_config_file, + ): await hass.services.async_call( "homeassistant", SERVICE_HOMEASSISTANT_RESTART, @@ -553,13 +564,15 @@ async def test_restart_homeassistant( ) -> None: """Test we can restart when there is no configuration error.""" await async_setup_component(hass, "homeassistant", {}) - with patch( - "homeassistant.config.async_check_ha_config_file", return_value=None - ) as mock_check, patch( - "homeassistant.config.async_enable_safe_mode" - ) as mock_safe_mode, patch( - "homeassistant.core.HomeAssistant.async_stop", return_value=None - ) as mock_restart: + with ( + patch( + "homeassistant.config.async_check_ha_config_file", return_value=None + ) as mock_check, + patch("homeassistant.config.async_enable_safe_mode") as mock_safe_mode, + patch( + "homeassistant.core.HomeAssistant.async_stop", return_value=None + ) as mock_restart, + ): await hass.services.async_call( "homeassistant", SERVICE_HOMEASSISTANT_RESTART, @@ -575,11 +588,14 @@ async def test_restart_homeassistant( async def test_stop_homeassistant(hass: HomeAssistant) -> None: """Test we can stop when there is a configuration error.""" await async_setup_component(hass, "homeassistant", {}) - with patch( - "homeassistant.config.async_check_ha_config_file", return_value=None - ) as mock_check, patch( - "homeassistant.core.HomeAssistant.async_stop", return_value=None - ) as mock_restart: + with ( + patch( + "homeassistant.config.async_check_ha_config_file", return_value=None + ) as mock_check, + patch( + "homeassistant.core.HomeAssistant.async_stop", return_value=None + ) as mock_restart, + ): await hass.services.async_call( "homeassistant", SERVICE_HOMEASSISTANT_STOP, @@ -651,16 +667,19 @@ async def test_reload_all( assert len(core_config) == 1 assert len(themes) == 1 - with pytest.raises( - HomeAssistantError, - match=( - "Cannot quick reload all YAML configurations because the configuration is " - "not valid: Oh no, drama!" + with ( + pytest.raises( + HomeAssistantError, + match=( + "Cannot quick reload all YAML configurations because the configuration is " + "not valid: Oh no, drama!" + ), ), - ), patch( - "homeassistant.config.async_check_ha_config_file", - return_value="Oh no, drama!", - ) as mock_async_check_ha_config_file: + patch( + "homeassistant.config.async_check_ha_config_file", + return_value="Oh no, drama!", + ) as mock_async_check_ha_config_file, + ): await hass.services.async_call( "homeassistant", SERVICE_RELOAD_ALL, diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index 7e41169d3d1..761eb5dec13 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -132,15 +132,19 @@ async def test_alerts( if supervisor_info is not None: hass.config.components.add("hassio") - with patch( - "homeassistant.components.homeassistant_alerts.__version__", - ha_version, - ), patch( - "homeassistant.components.homeassistant_alerts.is_hassio", - return_value=supervisor_info is not None, - ), patch( - "homeassistant.components.homeassistant_alerts.get_supervisor_info", - return_value=supervisor_info, + with ( + patch( + "homeassistant.components.homeassistant_alerts.__version__", + ha_version, + ), + patch( + "homeassistant.components.homeassistant_alerts.is_hassio", + return_value=supervisor_info is not None, + ), + patch( + "homeassistant.components.homeassistant_alerts.get_supervisor_info", + return_value=supervisor_info, + ), ): assert await async_setup_component(hass, DOMAIN, {}) @@ -311,15 +315,19 @@ async def test_alerts_refreshed_on_component_load( for domain in initial_components: hass.config.components.add(domain) - with patch( - "homeassistant.components.homeassistant_alerts.__version__", - ha_version, - ), patch( - "homeassistant.components.homeassistant_alerts.is_hassio", - return_value=supervisor_info is not None, - ), patch( - "homeassistant.components.homeassistant_alerts.get_supervisor_info", - return_value=supervisor_info, + with ( + patch( + "homeassistant.components.homeassistant_alerts.__version__", + ha_version, + ), + patch( + "homeassistant.components.homeassistant_alerts.is_hassio", + return_value=supervisor_info is not None, + ), + patch( + "homeassistant.components.homeassistant_alerts.get_supervisor_info", + return_value=supervisor_info, + ), ): assert await async_setup_component(hass, DOMAIN, {}) @@ -351,15 +359,19 @@ async def test_alerts_refreshed_on_component_load( ] } - with patch( - "homeassistant.components.homeassistant_alerts.__version__", - ha_version, - ), patch( - "homeassistant.components.homeassistant_alerts.is_hassio", - return_value=supervisor_info is not None, - ), patch( - "homeassistant.components.homeassistant_alerts.get_supervisor_info", - return_value=supervisor_info, + with ( + patch( + "homeassistant.components.homeassistant_alerts.__version__", + ha_version, + ), + patch( + "homeassistant.components.homeassistant_alerts.is_hassio", + return_value=supervisor_info is not None, + ), + patch( + "homeassistant.components.homeassistant_alerts.get_supervisor_info", + return_value=supervisor_info, + ), ): # Fake component_loaded events and wait for debounce for domain in late_components: diff --git a/tests/components/homeassistant_hardware/conftest.py b/tests/components/homeassistant_hardware/conftest.py index 9656b3d2e60..ae9ee6e1d2e 100644 --- a/tests/components/homeassistant_hardware/conftest.py +++ b/tests/components/homeassistant_hardware/conftest.py @@ -21,14 +21,19 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: MagicMock() ) - with patch( - "bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe - ), patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", - return_value=mock_connect_app, - ), patch( - "homeassistant.components.zha.async_setup_entry", - return_value=True, + with ( + patch( + "bellows.zigbee.application.ControllerApplication.probe", + side_effect=mock_probe, + ), + patch( + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", + return_value=mock_connect_app, + ), + patch( + "homeassistant.components.zha.async_setup_entry", + return_value=True, + ), ): yield diff --git a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py index 6221ac3ecb5..82b6fd0c092 100644 --- a/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py +++ b/tests/components/homeassistant_hardware/test_silabs_multiprotocol_addon.py @@ -1610,13 +1610,16 @@ async def test_active_plaforms( async def test_check_multi_pan_addon_no_hassio(hass: HomeAssistant) -> None: """Test `check_multi_pan_addon` without hassio.""" - with patch( - "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", - return_value=False, - ), patch( - "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.get_multiprotocol_addon_manager", - autospec=True, - ) as mock_get_addon_manager: + with ( + patch( + "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", + return_value=False, + ), + patch( + "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.get_multiprotocol_addon_manager", + autospec=True, + ) as mock_get_addon_manager, + ): await silabs_multiprotocol_addon.check_multi_pan_addon(hass) mock_get_addon_manager.assert_not_called() diff --git a/tests/components/homeassistant_sky_connect/conftest.py b/tests/components/homeassistant_sky_connect/conftest.py index 6ab78fe53ef..de8576e2a0a 100644 --- a/tests/components/homeassistant_sky_connect/conftest.py +++ b/tests/components/homeassistant_sky_connect/conftest.py @@ -25,12 +25,15 @@ def mock_zha(): MagicMock() ) - with patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", - return_value=mock_connect_app, - ), patch( - "homeassistant.components.zha.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", + return_value=mock_connect_app, + ), + patch( + "homeassistant.components.zha.async_setup_entry", + return_value=True, + ), ): yield diff --git a/tests/components/homeassistant_sky_connect/test_config_flow.py b/tests/components/homeassistant_sky_connect/test_config_flow.py index 85a8f8d2668..9374cbc9457 100644 --- a/tests/components/homeassistant_sky_connect/test_config_flow.py +++ b/tests/components/homeassistant_sky_connect/test_config_flow.py @@ -146,13 +146,16 @@ async def test_config_flow_update_device(hass: HomeAssistant) -> None: assert await hass.config_entries.async_setup(config_entry.entry_id) assert len(mock_setup_entry.mock_calls) == 1 - with patch( - "homeassistant.components.homeassistant_sky_connect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.homeassistant_sky_connect.async_unload_entry", - wraps=homeassistant_sky_connect.async_unload_entry, - ) as mock_unload_entry: + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.homeassistant_sky_connect.async_unload_entry", + wraps=homeassistant_sky_connect.async_unload_entry, + ) as mock_unload_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "usb"}, data=usb_data ) diff --git a/tests/components/homeassistant_sky_connect/test_init.py b/tests/components/homeassistant_sky_connect/test_init.py index fd70d1fb696..a1fa4a5c743 100644 --- a/tests/components/homeassistant_sky_connect/test_init.py +++ b/tests/components/homeassistant_sky_connect/test_init.py @@ -43,11 +43,15 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: mock_connect_app = MagicMock() mock_connect_app.__aenter__.return_value.backups.backups = [] - with patch( - "bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe - ), patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", - return_value=mock_connect_app, + with ( + patch( + "bellows.zigbee.application.ControllerApplication.probe", + side_effect=mock_probe, + ), + patch( + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", + return_value=mock_connect_app, + ), ): yield @@ -75,11 +79,15 @@ async def test_setup_entry( title="Home Assistant SkyConnect", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", - return_value=True, - ) as mock_is_plugged_in, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=onboarded + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ) as mock_is_plugged_in, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=onboarded, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -123,11 +131,14 @@ async def test_setup_zha( title="Home Assistant SkyConnect", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", - return_value=True, - ) as mock_is_plugged_in, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ) as mock_is_plugged_in, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -174,14 +185,18 @@ async def test_setup_zha_multipan( title="Home Assistant SkyConnect", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", - return_value=True, - ) as mock_is_plugged_in, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False - ), patch( - "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", - side_effect=Mock(return_value=True), + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ) as mock_is_plugged_in, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), + patch( + "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", + side_effect=Mock(return_value=True), + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -228,14 +243,18 @@ async def test_setup_zha_multipan_other_device( title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", - return_value=True, - ) as mock_is_plugged_in, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False - ), patch( - "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", - side_effect=Mock(return_value=True), + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ) as mock_is_plugged_in, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), + patch( + "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", + side_effect=Mock(return_value=True), + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -306,14 +325,18 @@ async def test_setup_entry_addon_info_fails( title="Home Assistant SkyConnect", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", - return_value=True, - ), patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False - ), patch( - "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", - side_effect=Mock(return_value=True), + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ), + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), + patch( + "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", + side_effect=Mock(return_value=True), + ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -335,14 +358,18 @@ async def test_setup_entry_addon_not_running( title="Home Assistant SkyConnect", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", - return_value=True, - ), patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False - ), patch( - "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", - side_effect=Mock(return_value=True), + with ( + patch( + "homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in", + return_value=True, + ), + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), + patch( + "homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio", + side_effect=Mock(return_value=True), + ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/homeassistant_yellow/conftest.py b/tests/components/homeassistant_yellow/conftest.py index 39ce00c59d4..070047648fc 100644 --- a/tests/components/homeassistant_yellow/conftest.py +++ b/tests/components/homeassistant_yellow/conftest.py @@ -21,14 +21,19 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: MagicMock() ) - with patch( - "bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe - ), patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", - return_value=mock_connect_app, - ), patch( - "homeassistant.components.zha.async_setup_entry", - return_value=True, + with ( + patch( + "bellows.zigbee.application.ControllerApplication.probe", + side_effect=mock_probe, + ), + patch( + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", + return_value=mock_connect_app, + ), + patch( + "homeassistant.components.zha.async_setup_entry", + return_value=True, + ), ): yield diff --git a/tests/components/homeassistant_yellow/test_init.py b/tests/components/homeassistant_yellow/test_init.py index c3a9819c14b..e94dbbc1438 100644 --- a/tests/components/homeassistant_yellow/test_init.py +++ b/tests/components/homeassistant_yellow/test_init.py @@ -33,11 +33,15 @@ async def test_setup_entry( title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_yellow.get_os_info", - return_value={"board": "yellow"}, - ) as mock_get_os_info, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=onboarded + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ) as mock_get_os_info, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=onboarded, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -76,11 +80,14 @@ async def test_setup_zha(hass: HomeAssistant, addon_store_info) -> None: title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_yellow.get_os_info", - return_value={"board": "yellow"}, - ) as mock_get_os_info, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ) as mock_get_os_info, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -127,11 +134,14 @@ async def test_setup_zha_multipan( title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_yellow.get_os_info", - return_value={"board": "yellow"}, - ) as mock_get_os_info, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ) as mock_get_os_info, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -178,11 +188,14 @@ async def test_setup_zha_multipan_other_device( title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_yellow.get_os_info", - return_value={"board": "yellow"}, - ) as mock_get_os_info, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ) as mock_get_os_info, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -300,11 +313,14 @@ async def test_setup_entry_addon_info_fails( title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_yellow.get_os_info", - return_value={"board": "yellow"}, - ), patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ), + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) @@ -327,11 +343,14 @@ async def test_setup_entry_addon_not_running( title="Home Assistant Yellow", ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_yellow.get_os_info", - return_value={"board": "yellow"}, - ), patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.homeassistant_yellow.get_os_info", + return_value={"board": "yellow"}, + ), + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index 39df14a47ef..fcbeafa3b60 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -27,12 +27,14 @@ def run_driver(hass, event_loop, iid_storage): This mock does not mock async_stop, so the driver will not be stopped """ - with patch("pyhap.accessory_driver.AsyncZeroconf"), patch( - "pyhap.accessory_driver.AccessoryEncoder" - ), patch("pyhap.accessory_driver.HAPServer"), patch( - "pyhap.accessory_driver.AccessoryDriver.publish" - ), patch( - "pyhap.accessory_driver.AccessoryDriver.persist", + with ( + patch("pyhap.accessory_driver.AsyncZeroconf"), + patch("pyhap.accessory_driver.AccessoryEncoder"), + patch("pyhap.accessory_driver.HAPServer"), + patch("pyhap.accessory_driver.AccessoryDriver.publish"), + patch( + "pyhap.accessory_driver.AccessoryDriver.persist", + ), ): yield HomeDriver( hass, @@ -49,14 +51,17 @@ def run_driver(hass, event_loop, iid_storage): @pytest.fixture def hk_driver(hass, event_loop, iid_storage): """Return a custom AccessoryDriver instance for HomeKit accessory init.""" - with patch("pyhap.accessory_driver.AsyncZeroconf"), patch( - "pyhap.accessory_driver.AccessoryEncoder" - ), patch("pyhap.accessory_driver.HAPServer.async_stop"), patch( - "pyhap.accessory_driver.HAPServer.async_start" - ), patch( - "pyhap.accessory_driver.AccessoryDriver.publish", - ), patch( - "pyhap.accessory_driver.AccessoryDriver.persist", + with ( + patch("pyhap.accessory_driver.AsyncZeroconf"), + patch("pyhap.accessory_driver.AccessoryEncoder"), + patch("pyhap.accessory_driver.HAPServer.async_stop"), + patch("pyhap.accessory_driver.HAPServer.async_start"), + patch( + "pyhap.accessory_driver.AccessoryDriver.publish", + ), + patch( + "pyhap.accessory_driver.AccessoryDriver.persist", + ), ): yield HomeDriver( hass, @@ -73,18 +78,23 @@ def hk_driver(hass, event_loop, iid_storage): @pytest.fixture def mock_hap(hass, event_loop, iid_storage, mock_zeroconf): """Return a custom AccessoryDriver instance for HomeKit accessory init.""" - with patch("pyhap.accessory_driver.AsyncZeroconf"), patch( - "pyhap.accessory_driver.AccessoryEncoder" - ), patch("pyhap.accessory_driver.HAPServer.async_stop"), patch( - "pyhap.accessory_driver.HAPServer.async_start" - ), patch( - "pyhap.accessory_driver.AccessoryDriver.publish", - ), patch( - "pyhap.accessory_driver.AccessoryDriver.async_start", - ), patch( - "pyhap.accessory_driver.AccessoryDriver.async_stop", - ), patch( - "pyhap.accessory_driver.AccessoryDriver.persist", + with ( + patch("pyhap.accessory_driver.AsyncZeroconf"), + patch("pyhap.accessory_driver.AccessoryEncoder"), + patch("pyhap.accessory_driver.HAPServer.async_stop"), + patch("pyhap.accessory_driver.HAPServer.async_start"), + patch( + "pyhap.accessory_driver.AccessoryDriver.publish", + ), + patch( + "pyhap.accessory_driver.AccessoryDriver.async_start", + ), + patch( + "pyhap.accessory_driver.AccessoryDriver.async_stop", + ), + patch( + "pyhap.accessory_driver.AccessoryDriver.persist", + ), ): yield HomeDriver( hass, diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 5d8b940a7cd..11a2675382a 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -756,18 +756,24 @@ def test_home_driver(iid_storage) -> None: driver.accessory = Mock(display_name="any", xhm_uri=xhm_uri_mock) # pair - with patch("pyhap.accessory_driver.AccessoryDriver.pair") as mock_pair, patch( - "homeassistant.components.homekit.accessories.async_dismiss_setup_message" - ) as mock_dissmiss_msg: + with ( + patch("pyhap.accessory_driver.AccessoryDriver.pair") as mock_pair, + patch( + "homeassistant.components.homekit.accessories.async_dismiss_setup_message" + ) as mock_dissmiss_msg, + ): driver.pair("client_uuid", "client_public", b"1") mock_pair.assert_called_with("client_uuid", "client_public", b"1") mock_dissmiss_msg.assert_called_with("hass", "entry_id") # unpair - with patch("pyhap.accessory_driver.AccessoryDriver.unpair") as mock_unpair, patch( - "homeassistant.components.homekit.accessories.async_show_setup_message" - ) as mock_show_msg: + with ( + patch("pyhap.accessory_driver.AccessoryDriver.unpair") as mock_unpair, + patch( + "homeassistant.components.homekit.accessories.async_show_setup_message" + ) as mock_show_msg, + ): driver.unpair("client_uuid") mock_unpair.assert_called_with("client_uuid") diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index fbcc33b9c7d..b3b8a70b1a1 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -60,15 +60,19 @@ async def test_setup_in_bridge_mode(hass: HomeAssistant, mock_get_source_ip) -> assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["step_id"] == "pairing" - with patch( - "homeassistant.components.homekit.config_flow.async_find_next_available_port", - return_value=12345, - ), patch( - "homeassistant.components.homekit.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.homekit.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.homekit.config_flow.async_find_next_available_port", + return_value=12345, + ), + patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.homekit.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -118,15 +122,19 @@ async def test_setup_in_bridge_mode_name_taken( assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["step_id"] == "pairing" - with patch( - "homeassistant.components.homekit.config_flow.async_find_next_available_port", - return_value=12345, - ), patch( - "homeassistant.components.homekit.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.homekit.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.homekit.config_flow.async_find_next_available_port", + return_value=12345, + ), + patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.homekit.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -200,15 +208,19 @@ async def test_setup_creates_entries_for_accessory_mode_devices( assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["step_id"] == "pairing" - with patch( - "homeassistant.components.homekit.config_flow.async_find_next_available_port", - return_value=12345, - ), patch( - "homeassistant.components.homekit.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.homekit.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.homekit.config_flow.async_find_next_available_port", + return_value=12345, + ), + patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.homekit.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -263,12 +275,15 @@ async def test_import(hass: HomeAssistant, mock_get_source_ip) -> None: assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "port_name_in_use" - with patch( - "homeassistant.components.homekit.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.homekit.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.homekit.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -1286,13 +1301,16 @@ async def test_converting_bridge_to_accessory_mode( # We need to actually setup the config entry or the data # will not get migrated to options - with patch( - "homeassistant.components.homekit.config_flow.async_find_next_available_port", - return_value=12345, - ), patch( - "homeassistant.components.homekit.HomeKit.async_start", - return_value=True, - ) as mock_async_start: + with ( + patch( + "homeassistant.components.homekit.config_flow.async_find_next_available_port", + return_value=12345, + ), + patch( + "homeassistant.components.homekit.HomeKit.async_start", + return_value=True, + ) as mock_async_start, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -1348,11 +1366,12 @@ async def test_converting_bridge_to_accessory_mode( assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["step_id"] == "cameras" - with patch( - "homeassistant.components.homekit.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.homekit.async_port_is_available" + with ( + patch( + "homeassistant.components.homekit.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch("homeassistant.components.homekit.async_port_is_available"), ): result3 = await hass.config_entries.options.async_configure( result2["flow_id"], diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py index 1d6dec16991..9fe4fc6fcc7 100644 --- a/tests/components/homekit/test_diagnostics.py +++ b/tests/components/homekit/test_diagnostics.py @@ -143,9 +143,11 @@ async def test_config_entry_running( "status": 1, } - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - "homeassistant.components.homekit.HomeKit.async_stop" - ), patch("homeassistant.components.homekit.async_port_is_available"): + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch("homeassistant.components.homekit.HomeKit.async_stop"), + patch("homeassistant.components.homekit.async_port_is_available"), + ): assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() @@ -303,9 +305,11 @@ async def test_config_entry_accessory( }, "status": 1, } - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - "homeassistant.components.homekit.HomeKit.async_stop" - ), patch("homeassistant.components.homekit.async_port_is_available"): + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch("homeassistant.components.homekit.HomeKit.async_stop"), + patch("homeassistant.components.homekit.async_port_is_available"), + ): assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() @@ -622,8 +626,10 @@ async def test_config_entry_with_trigger_accessory( "pairing_id": ANY, "status": 1, } - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - "homeassistant.components.homekit.HomeKit.async_stop" - ), patch("homeassistant.components.homekit.async_port_is_available"): + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch("homeassistant.components.homekit.HomeKit.async_stop"), + patch("homeassistant.components.homekit.async_port_is_available"), + ): assert await hass.config_entries.async_unload(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 5ca5b5e386f..e0f0786f15d 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -164,8 +164,12 @@ async def test_setup_min(hass: HomeAssistant, mock_async_zeroconf: None) -> None ) entry.add_to_hass(hass) - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -206,8 +210,12 @@ async def test_removing_entry( ) entry.add_to_hass(hass) - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -729,11 +737,11 @@ async def test_homekit_start( hass.states.async_set("light.demo2", "on") state = hass.states.async_all()[0] - with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ) as hk_driver_start: + with ( + patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, + patch(f"{PATH_HOMEKIT}.async_show_setup_message") as mock_setup_msg, + patch("pyhap.accessory_driver.AccessoryDriver.async_start") as hk_driver_start, + ): await homekit.async_start() await hass.async_block_till_done() @@ -761,15 +769,14 @@ async def test_homekit_start( # Start again to make sure the registry entry is kept homekit.status = STATUS_READY - with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ) as hk_driver_start, patch( - "pyhap.accessory_driver.AccessoryDriver.load" - ) as load_mock, patch( - "pyhap.accessory_driver.AccessoryDriver.persist" - ) as persist_mock, patch(f"{PATH_HOMEKIT}.os.path.exists", return_value=True): + with ( + patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, + patch(f"{PATH_HOMEKIT}.async_show_setup_message") as mock_setup_msg, + patch("pyhap.accessory_driver.AccessoryDriver.async_start") as hk_driver_start, + patch("pyhap.accessory_driver.AccessoryDriver.load") as load_mock, + patch("pyhap.accessory_driver.AccessoryDriver.persist") as persist_mock, + patch(f"{PATH_HOMEKIT}.os.path.exists", return_value=True), + ): await homekit.async_stop() await homekit.async_start() @@ -807,11 +814,11 @@ async def test_homekit_start_with_a_broken_accessory( hass.states.async_set("light.demo", "on") hass.states.async_set("light.broken", "on") - with patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ) as hk_driver_start: + with ( + patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), + patch(f"{PATH_HOMEKIT}.async_show_setup_message") as mock_setup_msg, + patch("pyhap.accessory_driver.AccessoryDriver.async_start") as hk_driver_start, + ): await homekit.async_start() await hass.async_block_till_done() @@ -853,9 +860,10 @@ async def test_homekit_start_with_a_device( homekit.driver = hk_driver homekit.aid_storage = MagicMock() - with patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ) as mock_setup_msg: + with ( + patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), + patch(f"{PATH_HOMEKIT}.async_show_setup_message") as mock_setup_msg, + ): await homekit.async_start() await hass.async_block_till_done() @@ -912,12 +920,12 @@ async def test_homekit_reset_accessories( hass.states.async_set("light.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.config_changed" - ), patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - f"{PATH_HOMEKIT}.accessories.HomeAccessory.run" - ) as mock_run_accessory, patch.object( - homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0 + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory_driver.AccessoryDriver.config_changed"), + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch(f"{PATH_HOMEKIT}.accessories.HomeAccessory.run") as mock_run_accessory, + patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0), ): await async_init_entry(hass, entry) @@ -1064,8 +1072,9 @@ async def test_homekit_unpair( hass.states.async_set("light.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await async_init_entry(hass, entry) @@ -1113,8 +1122,9 @@ async def test_homekit_unpair_missing_device_id( hass.states.async_set("light.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await async_init_entry(hass, entry) @@ -1158,8 +1168,9 @@ async def test_homekit_unpair_not_homekit_device( hass.states.async_set("light.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await async_init_entry(hass, entry) @@ -1206,13 +1217,15 @@ async def test_homekit_reset_accessories_not_supported( hass.states.async_set("not_supported.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory.Bridge.add_accessory" - ) as mock_add_accessory, patch( - "pyhap.accessory_driver.AccessoryDriver.async_update_advertisement" - ) as hk_driver_async_update_advertisement, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ), patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0): + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, + patch( + "pyhap.accessory_driver.AccessoryDriver.async_update_advertisement" + ) as hk_driver_async_update_advertisement, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0), + ): await async_init_entry(hass, entry) acc_mock = MagicMock() @@ -1249,13 +1262,15 @@ async def test_homekit_reset_accessories_state_missing( entity_id = "light.demo" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory.Bridge.add_accessory" - ) as mock_add_accessory, patch( - "pyhap.accessory_driver.AccessoryDriver.config_changed" - ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ), patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0): + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, + patch( + "pyhap.accessory_driver.AccessoryDriver.config_changed" + ) as hk_driver_config_changed, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0), + ): await async_init_entry(hass, entry) acc_mock = MagicMock() @@ -1291,13 +1306,15 @@ async def test_homekit_reset_accessories_not_bridged( entity_id = "light.demo" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory.Bridge.add_accessory" - ) as mock_add_accessory, patch( - "pyhap.accessory_driver.AccessoryDriver.async_update_advertisement" - ) as hk_driver_async_update_advertisement, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ), patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0): + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, + patch( + "pyhap.accessory_driver.AccessoryDriver.async_update_advertisement" + ) as hk_driver_async_update_advertisement, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch.object(homekit_base, "_HOMEKIT_CONFIG_UPDATE_TIME", 0), + ): await async_init_entry(hass, entry) assert hk_driver_async_update_advertisement.call_count == 0 @@ -1337,13 +1354,16 @@ async def test_homekit_reset_single_accessory( hass.states.async_set("light.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.async_update_advertisement" - ) as hk_driver_async_update_advertisement, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ), patch( - f"{PATH_HOMEKIT}.accessories.HomeAccessory.run", - ) as mock_run: + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch( + "pyhap.accessory_driver.AccessoryDriver.async_update_advertisement" + ) as hk_driver_async_update_advertisement, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch( + f"{PATH_HOMEKIT}.accessories.HomeAccessory.run", + ) as mock_run, + ): await async_init_entry(hass, entry) homekit.status = STATUS_RUNNING homekit.driver.aio_stop_event = MagicMock() @@ -1373,10 +1393,12 @@ async def test_homekit_reset_single_accessory_unsupported( hass.states.async_set("not_supported.demo", "on") homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.config_changed" - ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch( + "pyhap.accessory_driver.AccessoryDriver.config_changed" + ) as hk_driver_config_changed, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await async_init_entry(hass, entry) @@ -1411,10 +1433,12 @@ async def test_homekit_reset_single_accessory_state_missing( entity_id = "light.demo" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.config_changed" - ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch( + "pyhap.accessory_driver.AccessoryDriver.config_changed" + ) as hk_driver_config_changed, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await async_init_entry(hass, entry) @@ -1449,10 +1473,12 @@ async def test_homekit_reset_single_accessory_no_match( entity_id = "light.demo" homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY) - with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch( - "pyhap.accessory_driver.AccessoryDriver.config_changed" - ) as hk_driver_config_changed, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), + patch( + "pyhap.accessory_driver.AccessoryDriver.config_changed" + ) as hk_driver_config_changed, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await async_init_entry(hass, entry) @@ -1502,9 +1528,11 @@ async def test_homekit_too_many_accessories( hass.states.async_set("light.demo2", "on") hass.states.async_set("light.demo3", "on") - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch(f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge): + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch(f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge), + ): await homekit.async_start() await hass.async_block_till_done() assert "would exceed" in caplog.text @@ -1564,9 +1592,11 @@ async def test_homekit_finds_linked_batteries( ) hass.states.async_set(light.entity_id, STATE_ON) - with patch(f"{PATH_HOMEKIT}.async_show_setup_message"), patch( - f"{PATH_HOMEKIT}.get_accessory" - ) as mock_get_acc, patch("pyhap.accessory_driver.AccessoryDriver.async_start"): + with ( + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + ): await homekit.async_start() await hass.async_block_till_done() @@ -1638,10 +1668,11 @@ async def test_homekit_async_get_integration_fails( ) hass.states.async_set(light.entity_id, STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch.object(homekit.bridge, "add_accessory"), + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await homekit.async_start() await hass.async_block_till_done() @@ -1674,8 +1705,12 @@ async def test_yaml_updates_update_config_entry_for_name( ) entry.add_to_hass(hass) - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -1719,8 +1754,12 @@ async def test_yaml_can_link_with_default_name( ) entry.add_to_hass(hass) - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -1762,8 +1801,12 @@ async def test_yaml_can_link_with_port( options={}, ) entry3.add_to_hass(hass) - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -1799,9 +1842,11 @@ async def test_homekit_uses_system_zeroconf( assert await async_setup_component(hass, "zeroconf", {"zeroconf": {}}) system_async_zc = await zeroconf.async_get_async_instance(hass) - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - f"{PATH_HOMEKIT}.HomeKit.async_stop" - ), patch(f"{PATH_HOMEKIT}.async_port_is_available"): + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch(f"{PATH_HOMEKIT}.HomeKit.async_stop"), + patch(f"{PATH_HOMEKIT}.async_port_is_available"), + ): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -1881,9 +1926,11 @@ async def test_homekit_ignored_missing_devices( hass.states.async_set(light.entity_id, STATE_ON) hass.states.async_set("light.two", STATE_ON) - with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - f"{PATH_HOMEKIT}.HomeBridge", return_value=homekit.bridge - ), patch("pyhap.accessory_driver.AccessoryDriver.async_start"): + with ( + patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, + patch(f"{PATH_HOMEKIT}.HomeBridge", return_value=homekit.bridge), + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + ): await homekit.async_start() await hass.async_block_till_done() @@ -1943,10 +1990,11 @@ async def test_homekit_finds_linked_motion_sensors( ) hass.states.async_set(camera.entity_id, STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch.object(homekit.bridge, "add_accessory"), + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await homekit.async_start() await hass.async_block_till_done() @@ -2012,10 +2060,11 @@ async def test_homekit_finds_linked_humidity_sensors( ) hass.states.async_set(humidifier.entity_id, STATE_ON) - with patch.object(homekit.bridge, "add_accessory"), patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" + with ( + patch.object(homekit.bridge, "add_accessory"), + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), ): await homekit.async_start() await hass.async_block_till_done() @@ -2046,8 +2095,12 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: ) entry.add_to_hass(hass) - with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -2071,14 +2124,21 @@ async def test_reload(hass: HomeAssistant, mock_async_zeroconf: None) -> None: devices=[], ) yaml_path = get_fixture_path("configuration.yaml", "homekit") - with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), patch( - f"{PATH_HOMEKIT}.HomeKit" - ) as mock_homekit2, patch(f"{PATH_HOMEKIT}.async_show_setup_message"), patch( - f"{PATH_HOMEKIT}.get_accessory", - ), patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True), patch( - "pyhap.accessory_driver.AccessoryDriver.async_start", - ), patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" + with ( + patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), + patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit2, + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch( + f"{PATH_HOMEKIT}.get_accessory", + ), + patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True), + patch( + "pyhap.accessory_driver.AccessoryDriver.async_start", + ), + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), ): mock_homekit2.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() @@ -2124,11 +2184,11 @@ async def test_homekit_start_in_accessory_mode( hass.states.async_set("light.demo", "on") - with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ) as hk_driver_start: + with ( + patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, + patch(f"{PATH_HOMEKIT}.async_show_setup_message") as mock_setup_msg, + patch("pyhap.accessory_driver.AccessoryDriver.async_start") as hk_driver_start, + ): await homekit.async_start() await hass.async_block_till_done() @@ -2169,11 +2229,11 @@ async def test_homekit_start_in_accessory_mode_unsupported_entity( hass.states.async_set("notsupported.demo", "on") - with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ) as mock_setup_msg, patch( - "pyhap.accessory_driver.AccessoryDriver.async_start" - ) as hk_driver_start: + with ( + patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, + patch(f"{PATH_HOMEKIT}.async_show_setup_message") as mock_setup_msg, + patch("pyhap.accessory_driver.AccessoryDriver.async_start") as hk_driver_start, + ): await homekit.async_start() await hass.async_block_till_done() @@ -2201,9 +2261,11 @@ async def test_homekit_start_in_accessory_mode_missing_entity( homekit.driver = hk_driver homekit.driver.accessory = Accessory(hk_driver, "any") - with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( - f"{PATH_HOMEKIT}.async_show_setup_message" - ), patch("pyhap.accessory_driver.AccessoryDriver.async_start"): + with ( + patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, + patch(f"{PATH_HOMEKIT}.async_show_setup_message"), + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + ): await homekit.async_start() await hass.async_block_till_done() @@ -2228,9 +2290,13 @@ async def test_wait_for_port_to_free( ) entry.add_to_hass(hass) - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - f"{PATH_HOMEKIT}.HomeKit.async_stop" - ), patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True) as port_mock: + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch(f"{PATH_HOMEKIT}.HomeKit.async_stop"), + patch( + f"{PATH_HOMEKIT}.async_port_is_available", return_value=True + ) as port_mock, + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) @@ -2238,11 +2304,14 @@ async def test_wait_for_port_to_free( assert "Waiting for the HomeKit server to shutdown" not in caplog.text assert port_mock.called - with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( - f"{PATH_HOMEKIT}.HomeKit.async_stop" - ), patch.object(homekit_base, "PORT_CLEANUP_CHECK_INTERVAL_SECS", 0), patch( - f"{PATH_HOMEKIT}.async_port_is_available", return_value=False - ) as port_mock: + with ( + patch("pyhap.accessory_driver.AccessoryDriver.async_start"), + patch(f"{PATH_HOMEKIT}.HomeKit.async_stop"), + patch.object(homekit_base, "PORT_CLEANUP_CHECK_INTERVAL_SECS", 0), + patch( + f"{PATH_HOMEKIT}.async_port_is_available", return_value=False + ) as port_mock, + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index b56d01bd871..e8fb7e1d92e 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -114,9 +114,13 @@ async def test_bridge_with_triggers( ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4" - ), patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True): + with ( + patch( + "homeassistant.components.network.async_get_source_ip", + return_value="1.2.3.4", + ), + patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index 34770428d89..184ce1b6521 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -173,12 +173,15 @@ async def test_camera_stream_source_configured( working_ffmpeg = _get_working_mock_ffmpeg() session_info = acc.sessions[MOCK_START_STREAM_SESSION_UUID] - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value=None, - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=working_ffmpeg, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value=None, + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=working_ffmpeg, + ), ): await _async_start_streaming(hass, acc) await _async_stop_all_streams(hass, acc) @@ -208,12 +211,15 @@ async def test_camera_stream_source_configured( working_ffmpeg = _get_working_mock_ffmpeg() session_info = acc.sessions[MOCK_START_STREAM_SESSION_UUID] - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="rtsp://example.local", - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=working_ffmpeg, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="rtsp://example.local", + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=working_ffmpeg, + ), ): await _async_start_streaming(hass, acc) await _async_stop_all_streams(hass, acc) @@ -286,12 +292,15 @@ async def test_camera_stream_source_configured_with_failing_ffmpeg( await _async_setup_endpoints(hass, acc) - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="rtsp://example.local", - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=_get_failing_mock_ffmpeg(), + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="rtsp://example.local", + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=_get_failing_mock_ffmpeg(), + ), ): await _async_start_streaming(hass, acc) await _async_stop_all_streams(hass, acc) @@ -330,12 +339,15 @@ async def test_camera_stream_source_found( working_ffmpeg = _get_working_mock_ffmpeg() session_info = acc.sessions[MOCK_START_STREAM_SESSION_UUID] - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="rtsp://example.local", - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=working_ffmpeg, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="rtsp://example.local", + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=working_ffmpeg, + ), ): await _async_start_streaming(hass, acc) await _async_stop_all_streams(hass, acc) @@ -361,12 +373,15 @@ async def test_camera_stream_source_found( working_ffmpeg = _get_working_mock_ffmpeg() session_info = acc.sessions[MOCK_START_STREAM_SESSION_UUID] - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value="rtsp://example2.local", - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=working_ffmpeg, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value="rtsp://example2.local", + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=working_ffmpeg, + ), ): await _async_start_streaming(hass, acc) await _async_stop_all_streams(hass, acc) @@ -410,12 +425,15 @@ async def test_camera_stream_source_fails( await _async_setup_endpoints(hass, acc) - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - side_effect=OSError, - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=_get_working_mock_ffmpeg(), + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + side_effect=OSError, + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=_get_working_mock_ffmpeg(), + ), ): await _async_start_streaming(hass, acc) await _async_stop_all_streams(hass, acc) @@ -493,12 +511,15 @@ async def test_camera_stream_source_configured_and_copy_codec( working_ffmpeg = _get_working_mock_ffmpeg() - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value=None, - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=working_ffmpeg, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value=None, + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=working_ffmpeg, + ), ): await _async_start_streaming(hass, acc) await _async_reconfigure_stream(hass, acc, session_info, {}) @@ -566,12 +587,15 @@ async def test_camera_stream_source_configured_and_override_profile_names( working_ffmpeg = _get_working_mock_ffmpeg() - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value=None, - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=working_ffmpeg, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value=None, + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=working_ffmpeg, + ), ): await _async_start_streaming(hass, acc) await _async_reconfigure_stream(hass, acc, session_info, {}) @@ -638,12 +662,15 @@ async def test_camera_streaming_fails_after_starting_ffmpeg( ffmpeg_with_invalid_pid = _get_exits_after_startup_mock_ffmpeg() - with patch( - "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value=None, - ), patch( - "homeassistant.components.homekit.type_cameras.HAFFmpeg", - return_value=ffmpeg_with_invalid_pid, + with ( + patch( + "homeassistant.components.demo.camera.DemoCamera.stream_source", + return_value=None, + ), + patch( + "homeassistant.components.homekit.type_cameras.HAFFmpeg", + return_value=ffmpeg_with_invalid_pid, + ), ): await _async_start_streaming(hass, acc) await _async_reconfigure_stream(hass, acc, session_info, {}) diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 9d8f5c5417e..c419f7c19e7 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -342,9 +342,12 @@ async def test_port_is_available_skips_existing_entries(hass: HomeAssistant) -> ): assert async_port_is_available(next_port) - with pytest.raises(OSError), patch( - "homeassistant.components.homekit.util.socket.socket", - return_value=_mock_socket(10), + with ( + pytest.raises(OSError), + patch( + "homeassistant.components.homekit.util.socket.socket", + return_value=_mock_socket(10), + ), ): async_find_next_available_port(hass, 65530) diff --git a/tests/components/homematicip_cloud/conftest.py b/tests/components/homematicip_cloud/conftest.py index af663a150ac..88298521f75 100644 --- a/tests/components/homematicip_cloud/conftest.py +++ b/tests/components/homematicip_cloud/conftest.py @@ -145,12 +145,15 @@ def simple_mock_home_fixture(): def mock_connection_init_fixture(): """Return a simple mocked connection.""" - with patch( - "homeassistant.components.homematicip_cloud.hap.AsyncHome.init", - return_value=None, - ), patch( - "homeassistant.components.homematicip_cloud.hap.AsyncAuth.init", - return_value=None, + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.AsyncHome.init", + return_value=None, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.AsyncAuth.init", + return_value=None, + ), ): yield diff --git a/tests/components/homematicip_cloud/test_config_flow.py b/tests/components/homematicip_cloud/test_config_flow.py index d560293dd66..4b0d1c26b8f 100644 --- a/tests/components/homematicip_cloud/test_config_flow.py +++ b/tests/components/homematicip_cloud/test_config_flow.py @@ -22,12 +22,15 @@ IMPORT_CONFIG = {HMIPC_HAPID: "ABC123", HMIPC_AUTHTOKEN: "123", HMIPC_NAME: "hmi async def test_flow_works(hass: HomeAssistant, simple_mock_home) -> None: """Test config flow.""" - with patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", - return_value=False, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.get_auth", - return_value=True, + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", + return_value=False, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.get_auth", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( HMIPC_DOMAIN, @@ -46,17 +49,22 @@ async def test_flow_works(hass: HomeAssistant, simple_mock_home) -> None: ) assert flow["context"]["unique_id"] == "ABC123" - with patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipHAP.async_connect", + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipHAP.async_connect", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -86,15 +94,19 @@ async def test_flow_init_connection_error(hass: HomeAssistant) -> None: async def test_flow_link_connection_error(hass: HomeAssistant) -> None: """Test config flow client registration connection error.""" - with patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", - return_value=False, + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", + return_value=False, + ), ): result = await hass.config_entries.flow.async_init( HMIPC_DOMAIN, @@ -108,12 +120,15 @@ async def test_flow_link_connection_error(hass: HomeAssistant) -> None: async def test_flow_link_press_button(hass: HomeAssistant) -> None: """Test config flow ask for pressing the blue button.""" - with patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", - return_value=False, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", - return_value=True, + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", + return_value=False, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( HMIPC_DOMAIN, @@ -155,17 +170,22 @@ async def test_init_already_configured(hass: HomeAssistant) -> None: async def test_import_config(hass: HomeAssistant, simple_mock_home) -> None: """Test importing a host with an existing config file.""" - with patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipHAP.async_connect", + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipHAP.async_connect", + ), ): result = await hass.config_entries.flow.async_init( HMIPC_DOMAIN, @@ -182,15 +202,19 @@ async def test_import_config(hass: HomeAssistant, simple_mock_home) -> None: async def test_import_existing_config(hass: HomeAssistant) -> None: """Test abort of an existing accesspoint from config.""" MockConfigEntry(domain=HMIPC_DOMAIN, unique_id="ABC123").add_to_hass(hass) - with patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", - return_value=True, - ), patch( - "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", - return_value=True, + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup", + return_value=True, + ), + patch( + "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( HMIPC_DOMAIN, diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index facb12ed8ea..9fc1f518c64 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -97,13 +97,16 @@ async def test_hmip_add_device( assert len(mock_hap.hmip_device_by_entity_id) == pre_mapping_count - 3 reloaded_hap = HomematicipHAP(hass, hmip_config_entry) - with patch( - "homeassistant.components.homematicip_cloud.HomematicipHAP", - return_value=reloaded_hap, - ), patch.object(reloaded_hap, "async_connect"), patch.object( - reloaded_hap, "get_hap", return_value=mock_hap.home - ), patch( - "homeassistant.components.homematicip_cloud.hap.asyncio.sleep", + with ( + patch( + "homeassistant.components.homematicip_cloud.HomematicipHAP", + return_value=reloaded_hap, + ), + patch.object(reloaded_hap, "async_connect"), + patch.object(reloaded_hap, "get_hap", return_value=mock_hap.home), + patch( + "homeassistant.components.homematicip_cloud.hap.asyncio.sleep", + ), ): mock_hap.home.fire_create_event(event_type=EventType.DEVICE_ADDED) await hass.async_block_till_done() diff --git a/tests/components/homematicip_cloud/test_hap.py b/tests/components/homematicip_cloud/test_hap.py index 72bdb5cc0fc..cddade7cec5 100644 --- a/tests/components/homematicip_cloud/test_hap.py +++ b/tests/components/homematicip_cloud/test_hap.py @@ -49,13 +49,13 @@ async def test_auth_auth_check_and_register(hass: HomeAssistant) -> None: hmip_auth = HomematicipAuth(hass, config) hmip_auth.auth = Mock(spec=AsyncAuth) - with patch.object( - hmip_auth.auth, "isRequestAcknowledged", return_value=True - ), patch.object( - hmip_auth.auth, "requestAuthToken", return_value="ABC" - ), patch.object( - hmip_auth.auth, - "confirmAuthToken", + with ( + patch.object(hmip_auth.auth, "isRequestAcknowledged", return_value=True), + patch.object(hmip_auth.auth, "requestAuthToken", return_value="ABC"), + patch.object( + hmip_auth.auth, + "confirmAuthToken", + ), ): assert await hmip_auth.async_checkbutton() assert await hmip_auth.async_register() == "ABC" @@ -66,10 +66,13 @@ async def test_auth_auth_check_and_register_with_exception(hass: HomeAssistant) config = {HMIPC_HAPID: "ABC123", HMIPC_PIN: "123", HMIPC_NAME: "hmip"} hmip_auth = HomematicipAuth(hass, config) hmip_auth.auth = Mock(spec=AsyncAuth) - with patch.object( - hmip_auth.auth, "isRequestAcknowledged", side_effect=HmipConnectionError - ), patch.object( - hmip_auth.auth, "requestAuthToken", side_effect=HmipConnectionError + with ( + patch.object( + hmip_auth.auth, "isRequestAcknowledged", side_effect=HmipConnectionError + ), + patch.object( + hmip_auth.auth, "requestAuthToken", side_effect=HmipConnectionError + ), ): assert not await hmip_auth.async_checkbutton() assert await hmip_auth.async_register() is False @@ -96,8 +99,9 @@ async def test_hap_setup_connection_error() -> None: entry = Mock() entry.data = {HMIPC_HAPID: "ABC123", HMIPC_AUTHTOKEN: "123", HMIPC_NAME: "hmip"} hap = HomematicipHAP(hass, entry) - with patch.object(hap, "get_hap", side_effect=HmipcConnectionError), pytest.raises( - ConfigEntryNotReady + with ( + patch.object(hap, "get_hap", side_effect=HmipcConnectionError), + pytest.raises(ConfigEntryNotReady), ): assert not await hap.async_setup() @@ -146,10 +150,13 @@ async def test_hap_create_exception( ): assert not await hap.async_setup() - with patch( - "homeassistant.components.homematicip_cloud.hap.AsyncHome.get_current_state", - side_effect=HmipConnectionError, - ), pytest.raises(ConfigEntryNotReady): + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.AsyncHome.get_current_state", + side_effect=HmipConnectionError, + ), + pytest.raises(ConfigEntryNotReady), + ): await hap.async_setup() diff --git a/tests/components/homematicip_cloud/test_init.py b/tests/components/homematicip_cloud/test_init.py index d3e89e1bbb9..9303a755e89 100644 --- a/tests/components/homematicip_cloud/test_init.py +++ b/tests/components/homematicip_cloud/test_init.py @@ -121,11 +121,14 @@ async def test_load_entry_fails_due_to_generic_exception( """Test load entry fails due to generic exception.""" hmip_config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homematicip_cloud.hap.AsyncHome.get_current_state", - side_effect=Exception, - ), patch( - "homematicip.aio.connection.AsyncConnection.init", + with ( + patch( + "homeassistant.components.homematicip_cloud.hap.AsyncHome.get_current_state", + side_effect=Exception, + ), + patch( + "homematicip.aio.connection.AsyncConnection.init", + ), ): assert await async_setup_component(hass, HMIPC_DOMAIN, {}) diff --git a/tests/components/homewizard/conftest.py b/tests/components/homewizard/conftest.py index 4f78449af82..bc661da390d 100644 --- a/tests/components/homewizard/conftest.py +++ b/tests/components/homewizard/conftest.py @@ -25,12 +25,15 @@ def mock_homewizardenergy( device_fixture: str, ) -> MagicMock: """Return a mock bridge.""" - with patch( - "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", - autospec=True, - ) as homewizard, patch( - "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", - new=homewizard, + with ( + patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + autospec=True, + ) as homewizard, + patch( + "homeassistant.components.homewizard.config_flow.HomeWizardEnergy", + new=homewizard, + ), ): client = homewizard.return_value diff --git a/tests/components/homeworks/conftest.py b/tests/components/homeworks/conftest.py index b446ce03c5e..ccff56ae3d1 100644 --- a/tests/components/homeworks/conftest.py +++ b/tests/components/homeworks/conftest.py @@ -90,10 +90,14 @@ def mock_empty_config_entry() -> MockConfigEntry: @pytest.fixture def mock_homeworks() -> Generator[None, MagicMock, None]: """Return a mocked Homeworks client.""" - with patch( - "homeassistant.components.homeworks.Homeworks", autospec=True - ) as homeworks_mock, patch( - "homeassistant.components.homeworks.config_flow.Homeworks", new=homeworks_mock + with ( + patch( + "homeassistant.components.homeworks.Homeworks", autospec=True + ) as homeworks_mock, + patch( + "homeassistant.components.homeworks.config_flow.Homeworks", + new=homeworks_mock, + ), ): yield homeworks_mock diff --git a/tests/components/honeywell/test_sensor.py b/tests/components/honeywell/test_sensor.py index 424764847be..ed46fd4cdd2 100644 --- a/tests/components/honeywell/test_sensor.py +++ b/tests/components/honeywell/test_sensor.py @@ -20,9 +20,9 @@ async def test_outdoor_sensor( ) -> None: """Test outdoor temperature sensor.""" device_with_outdoor_sensor.temperature_unit = unit - location.devices_by_id[ - device_with_outdoor_sensor.deviceid - ] = device_with_outdoor_sensor + location.devices_by_id[device_with_outdoor_sensor.deviceid] = ( + device_with_outdoor_sensor + ) config_entry.add_to_hass(hass) await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index ee8f8c80864..a10aa740268 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -36,10 +36,14 @@ BANNED_IPS_WITH_SUPERVISOR = [*BANNED_IPS, SUPERVISOR_IP] @pytest.fixture(name="hassio_env") def hassio_env_fixture(): """Fixture to inject hassio env.""" - with patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value={"result": "ok", "data": {}}, - ), patch.dict(os.environ, {"SUPERVISOR_TOKEN": "123456"}): + with ( + patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value={"result": "ok", "data": {}}, + ), + patch.dict(os.environ, {"SUPERVISOR_TOKEN": "123456"}), + ): yield @@ -231,8 +235,9 @@ async def test_access_from_supervisor_ip( m_open = mock_open() - with patch.dict(os.environ, {"SUPERVISOR": SUPERVISOR_IP}), patch( - "homeassistant.components.http.ban.open", m_open, create=True + with ( + patch.dict(os.environ, {"SUPERVISOR": SUPERVISOR_IP}), + patch("homeassistant.components.http.ban.open", m_open, create=True), ): resp = await client.get("/") assert resp.status == HTTPStatus.UNAUTHORIZED diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 35678c08d38..98e97d0fe57 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -173,10 +173,13 @@ async def test_ssl_profile_defaults_modern(hass: HomeAssistant, tmp_path: Path) _setup_empty_ssl_pem_files, tmp_path ) - with patch("ssl.SSLContext.load_cert_chain"), patch( - "homeassistant.util.ssl.server_context_modern", - side_effect=server_context_modern, - ) as mock_context: + with ( + patch("ssl.SSLContext.load_cert_chain"), + patch( + "homeassistant.util.ssl.server_context_modern", + side_effect=server_context_modern, + ) as mock_context, + ): assert ( await async_setup_component( hass, @@ -200,10 +203,13 @@ async def test_ssl_profile_change_intermediate( _setup_empty_ssl_pem_files, tmp_path ) - with patch("ssl.SSLContext.load_cert_chain"), patch( - "homeassistant.util.ssl.server_context_intermediate", - side_effect=server_context_intermediate, - ) as mock_context: + with ( + patch("ssl.SSLContext.load_cert_chain"), + patch( + "homeassistant.util.ssl.server_context_intermediate", + side_effect=server_context_intermediate, + ) as mock_context, + ): assert ( await async_setup_component( hass, @@ -231,10 +237,13 @@ async def test_ssl_profile_change_modern(hass: HomeAssistant, tmp_path: Path) -> _setup_empty_ssl_pem_files, tmp_path ) - with patch("ssl.SSLContext.load_cert_chain"), patch( - "homeassistant.util.ssl.server_context_modern", - side_effect=server_context_modern, - ) as mock_context: + with ( + patch("ssl.SSLContext.load_cert_chain"), + patch( + "homeassistant.util.ssl.server_context_modern", + side_effect=server_context_modern, + ) as mock_context, + ): assert ( await async_setup_component( hass, @@ -261,12 +270,14 @@ async def test_peer_cert(hass: HomeAssistant, tmp_path: Path) -> None: _setup_empty_ssl_pem_files, tmp_path ) - with patch("ssl.SSLContext.load_cert_chain"), patch( - "ssl.SSLContext.load_verify_locations" - ) as mock_load_verify_locations, patch( - "homeassistant.util.ssl.server_context_modern", - side_effect=server_context_modern, - ) as mock_context: + with ( + patch("ssl.SSLContext.load_cert_chain"), + patch("ssl.SSLContext.load_verify_locations") as mock_load_verify_locations, + patch( + "homeassistant.util.ssl.server_context_modern", + side_effect=server_context_modern, + ) as mock_context, + ): assert ( await async_setup_component( hass, diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index cc4a32a7df5..f8ddaa42ac1 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -239,8 +239,9 @@ async def test_success(hass: HomeAssistant, login_requests_mock, scheme: str) -> f"{user_input[CONF_URL]}api/user/login", text="OK", ) - with patch("homeassistant.components.huawei_lte.async_setup"), patch( - "homeassistant.components.huawei_lte.async_setup_entry" + with ( + patch("homeassistant.components.huawei_lte.async_setup"), + patch("homeassistant.components.huawei_lte.async_setup_entry"), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index ce1b3f34674..5d103e47870 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -29,9 +29,10 @@ async def test_bridge_setup_v1(hass: HomeAssistant, mock_api_v1) -> None: options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, ) - with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward: + with ( + patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -50,9 +51,10 @@ async def test_bridge_setup_v2(hass: HomeAssistant, mock_api_v2) -> None: data={"host": "1.2.3.4", "api_key": "mock-api-key", "api_version": 2}, ) - with patch.object(bridge, "HueBridgeV2", return_value=mock_api_v2), patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward: + with ( + patch.object(bridge, "HueBridgeV2", return_value=mock_api_v2), + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -80,9 +82,10 @@ async def test_bridge_setup_invalid_api_key(hass: HomeAssistant) -> None: ) hue_bridge = bridge.HueBridge(hass, entry) - with patch.object( - hue_bridge.api, "initialize", side_effect=Unauthorized - ), patch.object(hass.config_entries.flow, "async_init") as mock_init: + with ( + patch.object(hue_bridge.api, "initialize", side_effect=Unauthorized), + patch.object(hass.config_entries.flow, "async_init") as mock_init, + ): assert await hue_bridge.async_initialize_bridge() is False assert len(mock_init.mock_calls) == 1 @@ -98,11 +101,14 @@ async def test_bridge_setup_timeout(hass: HomeAssistant) -> None: ) hue_bridge = bridge.HueBridge(hass, entry) - with patch.object( - hue_bridge.api, - "initialize", - side_effect=client_exceptions.ServerDisconnectedError, - ), pytest.raises(ConfigEntryNotReady): + with ( + patch.object( + hue_bridge.api, + "initialize", + side_effect=client_exceptions.ServerDisconnectedError, + ), + pytest.raises(ConfigEntryNotReady), + ): await hue_bridge.async_initialize_bridge() @@ -114,9 +120,10 @@ async def test_reset_unloads_entry_if_setup(hass: HomeAssistant, mock_api_v1) -> options={CONF_ALLOW_HUE_GROUPS: False, CONF_ALLOW_UNREACHABLE: False}, ) - with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as mock_forward: + with ( + patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), + patch.object(hass.config_entries, "async_forward_entry_setups") as mock_forward, + ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 7d7ef0eaf38..74cceb03aba 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -126,8 +126,9 @@ async def test_manual_flow_works(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["step_id"] == "link" - with patch.object(config_flow, "create_app_key", return_value="123456789"), patch( - "homeassistant.components.hue.async_unload_entry", return_value=True + with ( + patch.object(config_flow, "create_app_key", return_value="123456789"), + patch("homeassistant.components.hue.async_unload_entry", return_value=True), ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) @@ -387,10 +388,13 @@ async def test_creating_entry_removes_entries_for_same_host_or_bridge( assert result["type"] == "form" assert result["step_id"] == "link" - with patch( - "homeassistant.components.hue.config_flow.create_app_key", - return_value="123456789", - ), patch("homeassistant.components.hue.async_unload_entry", return_value=True): + with ( + patch( + "homeassistant.components.hue.config_flow.create_app_key", + return_value="123456789", + ), + patch("homeassistant.components.hue.async_unload_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result["type"] == "create_entry" diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index 8f5820baf1c..5ce0d78ead9 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -148,15 +148,18 @@ async def test_security_vuln_check(hass: HomeAssistant) -> None: ) config.name = "Hue" - with patch.object(hue.migration, "is_v2_bridge", return_value=False), patch.object( - hue, - "HueBridge", - Mock( - return_value=Mock( - async_initialize_bridge=AsyncMock(return_value=True), - api=Mock(config=config), - api_version=1, - ) + with ( + patch.object(hue.migration, "is_v2_bridge", return_value=False), + patch.object( + hue, + "HueBridge", + Mock( + return_value=Mock( + async_initialize_bridge=AsyncMock(return_value=True), + api=Mock(config=config), + api_version=1, + ) + ), ), ): assert await async_setup_component(hass, "hue", {}) diff --git a/tests/components/hue/test_migration.py b/tests/components/hue/test_migration.py index 88e7de47b07..adcc582a314 100644 --- a/tests/components/hue/test_migration.py +++ b/tests/components/hue/test_migration.py @@ -33,9 +33,10 @@ async def test_auto_switchover(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch.object(hue.migration, "is_v2_bridge", retun_value=True), patch.object( - hue.migration, "handle_v2_migration" - ) as mock_mig: + with ( + patch.object(hue.migration, "is_v2_bridge", retun_value=True), + patch.object(hue.migration, "handle_v2_migration") as mock_mig, + ): await hue.migration.check_migration(hass, config_entry) assert len(mock_mig.mock_calls) == 1 # the api version should now be version 2 diff --git a/tests/components/hue/test_services.py b/tests/components/hue/test_services.py index 0e80bb2ea08..8139bfa034c 100644 --- a/tests/components/hue/test_services.py +++ b/tests/components/hue/test_services.py @@ -62,8 +62,9 @@ async def test_hue_activate_scene(hass: HomeAssistant, mock_api_v1) -> None: mock_api_v1.mock_group_responses.append(GROUP_RESPONSE) mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) - with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setups" + with ( + patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), + patch.object(hass.config_entries, "async_forward_entry_setups"), ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -99,8 +100,9 @@ async def test_hue_activate_scene_transition(hass: HomeAssistant, mock_api_v1) - mock_api_v1.mock_group_responses.append(GROUP_RESPONSE) mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) - with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setups" + with ( + patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), + patch.object(hass.config_entries, "async_forward_entry_setups"), ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -138,8 +140,9 @@ async def test_hue_activate_scene_group_not_found( mock_api_v1.mock_group_responses.append({}) mock_api_v1.mock_scene_responses.append(SCENE_RESPONSE) - with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setups" + with ( + patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), + patch.object(hass.config_entries, "async_forward_entry_setups"), ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True @@ -172,8 +175,9 @@ async def test_hue_activate_scene_scene_not_found( mock_api_v1.mock_group_responses.append(GROUP_RESPONSE) mock_api_v1.mock_scene_responses.append({}) - with patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), patch.object( - hass.config_entries, "async_forward_entry_setups" + with ( + patch.object(bridge, "HueBridgeV1", return_value=mock_api_v1), + patch.object(hass.config_entries, "async_forward_entry_setups"), ): hue_bridge = bridge.HueBridge(hass, config_entry) assert await hue_bridge.async_initialize_bridge() is True diff --git a/tests/components/huisbaasje/test_config_flow.py b/tests/components/huisbaasje/test_config_flow.py index 0283f4d3fb0..0c65d425d4d 100644 --- a/tests/components/huisbaasje/test_config_flow.py +++ b/tests/components/huisbaasje/test_config_flow.py @@ -24,17 +24,22 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "energyflip.EnergyFlip.authenticate", return_value=None - ) as mock_authenticate, patch( - "energyflip.EnergyFlip.customer_overview", return_value=None - ) as mock_customer_overview, patch( - "energyflip.EnergyFlip.get_user_id", - return_value="test-id", - ) as mock_get_user_id, patch( - "homeassistant.components.huisbaasje.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "energyflip.EnergyFlip.authenticate", return_value=None + ) as mock_authenticate, + patch( + "energyflip.EnergyFlip.customer_overview", return_value=None + ) as mock_customer_overview, + patch( + "energyflip.EnergyFlip.get_user_id", + return_value="test-id", + ) as mock_get_user_id, + patch( + "homeassistant.components.huisbaasje.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): form_result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -129,9 +134,12 @@ async def test_form_customer_overview_cannot_connect(hass: HomeAssistant) -> Non DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("energyflip.EnergyFlip.authenticate", return_value=None), patch( - "energyflip.EnergyFlip.customer_overview", - side_effect=EnergyFlipConnectionException, + with ( + patch("energyflip.EnergyFlip.authenticate", return_value=None), + patch( + "energyflip.EnergyFlip.customer_overview", + side_effect=EnergyFlipConnectionException, + ), ): form_result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -151,9 +159,12 @@ async def test_form_customer_overview_authentication_error(hass: HomeAssistant) DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("energyflip.EnergyFlip.authenticate", return_value=None), patch( - "energyflip.EnergyFlip.customer_overview", - side_effect=EnergyFlipUnauthenticatedException, + with ( + patch("energyflip.EnergyFlip.authenticate", return_value=None), + patch( + "energyflip.EnergyFlip.customer_overview", + side_effect=EnergyFlipUnauthenticatedException, + ), ): form_result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -173,9 +184,12 @@ async def test_form_customer_overview_unknown_error(hass: HomeAssistant) -> None DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("energyflip.EnergyFlip.authenticate", return_value=None), patch( - "energyflip.EnergyFlip.customer_overview", - side_effect=Exception, + with ( + patch("energyflip.EnergyFlip.authenticate", return_value=None), + patch( + "energyflip.EnergyFlip.customer_overview", + side_effect=Exception, + ), ): form_result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -206,14 +220,17 @@ async def test_form_entry_exists(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("energyflip.EnergyFlip.authenticate", return_value=None), patch( - "energyflip.EnergyFlip.customer_overview", return_value=None - ), patch( - "energyflip.EnergyFlip.get_user_id", - return_value="test-id", - ), patch( - "homeassistant.components.huisbaasje.async_setup_entry", - return_value=True, + with ( + patch("energyflip.EnergyFlip.authenticate", return_value=None), + patch("energyflip.EnergyFlip.customer_overview", return_value=None), + patch( + "energyflip.EnergyFlip.get_user_id", + return_value="test-id", + ), + patch( + "homeassistant.components.huisbaasje.async_setup_entry", + return_value=True, + ), ): form_result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/huisbaasje/test_init.py b/tests/components/huisbaasje/test_init.py index c465ae02490..5f1bcb0094d 100644 --- a/tests/components/huisbaasje/test_init.py +++ b/tests/components/huisbaasje/test_init.py @@ -24,14 +24,18 @@ async def test_setup(hass: HomeAssistant) -> None: async def test_setup_entry(hass: HomeAssistant) -> None: """Test for successfully setting a config entry.""" - with patch( - "energyflip.EnergyFlip.authenticate", return_value=None - ) as mock_authenticate, patch( - "energyflip.EnergyFlip.is_authenticated", return_value=True - ) as mock_is_authenticated, patch( - "energyflip.EnergyFlip.current_measurements", - return_value=MOCK_CURRENT_MEASUREMENTS, - ) as mock_current_measurements: + with ( + patch( + "energyflip.EnergyFlip.authenticate", return_value=None + ) as mock_authenticate, + patch( + "energyflip.EnergyFlip.is_authenticated", return_value=True + ) as mock_is_authenticated, + patch( + "energyflip.EnergyFlip.current_measurements", + return_value=MOCK_CURRENT_MEASUREMENTS, + ) as mock_current_measurements, + ): hass.config.components.add(huisbaasje.DOMAIN) config_entry = MockConfigEntry( version=1, @@ -103,14 +107,18 @@ async def test_setup_entry_error(hass: HomeAssistant) -> None: async def test_unload_entry(hass: HomeAssistant) -> None: """Test for successfully unloading the config entry.""" - with patch( - "energyflip.EnergyFlip.authenticate", return_value=None - ) as mock_authenticate, patch( - "energyflip.EnergyFlip.is_authenticated", return_value=True - ) as mock_is_authenticated, patch( - "energyflip.EnergyFlip.current_measurements", - return_value=MOCK_CURRENT_MEASUREMENTS, - ) as mock_current_measurements: + with ( + patch( + "energyflip.EnergyFlip.authenticate", return_value=None + ) as mock_authenticate, + patch( + "energyflip.EnergyFlip.is_authenticated", return_value=True + ) as mock_is_authenticated, + patch( + "energyflip.EnergyFlip.current_measurements", + return_value=MOCK_CURRENT_MEASUREMENTS, + ) as mock_current_measurements, + ): hass.config.components.add(huisbaasje.DOMAIN) config_entry = MockConfigEntry( version=1, diff --git a/tests/components/huisbaasje/test_sensor.py b/tests/components/huisbaasje/test_sensor.py index d04ad8cf35f..02a05c78763 100644 --- a/tests/components/huisbaasje/test_sensor.py +++ b/tests/components/huisbaasje/test_sensor.py @@ -28,14 +28,18 @@ from tests.common import MockConfigEntry async def test_setup_entry(hass: HomeAssistant) -> None: """Test for successfully loading sensor states.""" - with patch( - "energyflip.EnergyFlip.authenticate", return_value=None - ) as mock_authenticate, patch( - "energyflip.EnergyFlip.is_authenticated", return_value=True - ) as mock_is_authenticated, patch( - "energyflip.EnergyFlip.current_measurements", - return_value=MOCK_CURRENT_MEASUREMENTS, - ) as mock_current_measurements: + with ( + patch( + "energyflip.EnergyFlip.authenticate", return_value=None + ) as mock_authenticate, + patch( + "energyflip.EnergyFlip.is_authenticated", return_value=True + ) as mock_is_authenticated, + patch( + "energyflip.EnergyFlip.current_measurements", + return_value=MOCK_CURRENT_MEASUREMENTS, + ) as mock_current_measurements, + ): hass.config.components.add(huisbaasje.DOMAIN) config_entry = MockConfigEntry( version=1, @@ -315,14 +319,18 @@ async def test_setup_entry(hass: HomeAssistant) -> None: async def test_setup_entry_absent_measurement(hass: HomeAssistant) -> None: """Test for successfully loading sensor states when response does not contain all measurements.""" - with patch( - "energyflip.EnergyFlip.authenticate", return_value=None - ) as mock_authenticate, patch( - "energyflip.EnergyFlip.is_authenticated", return_value=True - ) as mock_is_authenticated, patch( - "energyflip.EnergyFlip.current_measurements", - return_value=MOCK_LIMITED_CURRENT_MEASUREMENTS, - ) as mock_current_measurements: + with ( + patch( + "energyflip.EnergyFlip.authenticate", return_value=None + ) as mock_authenticate, + patch( + "energyflip.EnergyFlip.is_authenticated", return_value=True + ) as mock_is_authenticated, + patch( + "energyflip.EnergyFlip.current_measurements", + return_value=MOCK_LIMITED_CURRENT_MEASUREMENTS, + ) as mock_current_measurements, + ): hass.config.components.add(huisbaasje.DOMAIN) config_entry = MockConfigEntry( version=1, diff --git a/tests/components/hunterdouglas_powerview/conftest.py b/tests/components/hunterdouglas_powerview/conftest.py index be7dec42dea..e55e252f670 100644 --- a/tests/components/hunterdouglas_powerview/conftest.py +++ b/tests/components/hunterdouglas_powerview/conftest.py @@ -31,30 +31,39 @@ def mock_hunterdouglas_hub( shades_json: str, ) -> Generator[MagicMock, None, None]: """Return a mocked Powerview Hub with all data populated.""" - with patch( - "homeassistant.components.hunterdouglas_powerview.Hub.request_raw_data", - return_value=load_json_object_fixture(device_json, DOMAIN), - ), patch( - "homeassistant.components.hunterdouglas_powerview.Hub.request_home_data", - return_value=load_json_object_fixture(home_json, DOMAIN), - ), patch( - "homeassistant.components.hunterdouglas_powerview.Hub.request_raw_firmware", - return_value=load_json_object_fixture(firmware_json, DOMAIN), - ), patch( - "homeassistant.components.hunterdouglas_powerview.Rooms.get_resources", - return_value=load_json_value_fixture(rooms_json, DOMAIN), - ), patch( - "homeassistant.components.hunterdouglas_powerview.Scenes.get_resources", - return_value=load_json_value_fixture(scenes_json, DOMAIN), - ), patch( - "homeassistant.components.hunterdouglas_powerview.Shades.get_resources", - return_value=load_json_value_fixture(shades_json, DOMAIN), - ), patch( - "homeassistant.components.hunterdouglas_powerview.cover.BaseShade.refresh", - ), patch( - "homeassistant.components.hunterdouglas_powerview.cover.BaseShade.current_position", - new_callable=PropertyMock, - return_value=ShadePosition(primary=0, secondary=0, tilt=0, velocity=0), + with ( + patch( + "homeassistant.components.hunterdouglas_powerview.Hub.request_raw_data", + return_value=load_json_object_fixture(device_json, DOMAIN), + ), + patch( + "homeassistant.components.hunterdouglas_powerview.Hub.request_home_data", + return_value=load_json_object_fixture(home_json, DOMAIN), + ), + patch( + "homeassistant.components.hunterdouglas_powerview.Hub.request_raw_firmware", + return_value=load_json_object_fixture(firmware_json, DOMAIN), + ), + patch( + "homeassistant.components.hunterdouglas_powerview.Rooms.get_resources", + return_value=load_json_value_fixture(rooms_json, DOMAIN), + ), + patch( + "homeassistant.components.hunterdouglas_powerview.Scenes.get_resources", + return_value=load_json_value_fixture(scenes_json, DOMAIN), + ), + patch( + "homeassistant.components.hunterdouglas_powerview.Shades.get_resources", + return_value=load_json_value_fixture(shades_json, DOMAIN), + ), + patch( + "homeassistant.components.hunterdouglas_powerview.cover.BaseShade.refresh", + ), + patch( + "homeassistant.components.hunterdouglas_powerview.cover.BaseShade.current_position", + new_callable=PropertyMock, + return_value=ShadePosition(primary=0, secondary=0, tilt=0, velocity=0), + ), ): yield diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py index b98af94870b..ac4f6368f38 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -242,12 +242,15 @@ async def test_form_no_data( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.hunterdouglas_powerview.Hub.request_raw_data", - return_value={}, - ), patch( - "homeassistant.components.hunterdouglas_powerview.Hub.request_home_data", - return_value={}, + with ( + patch( + "homeassistant.components.hunterdouglas_powerview.Hub.request_raw_data", + return_value={}, + ), + patch( + "homeassistant.components.hunterdouglas_powerview.Hub.request_home_data", + return_value={}, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/husqvarna_automower/test_diagnostics.py b/tests/components/husqvarna_automower/test_diagnostics.py index 56eb0fdadde..c19345e507e 100644 --- a/tests/components/husqvarna_automower/test_diagnostics.py +++ b/tests/components/husqvarna_automower/test_diagnostics.py @@ -1,4 +1,5 @@ """Test the Husqvarna Automower Diagnostics.""" + import datetime from unittest.mock import AsyncMock diff --git a/tests/components/huum/test_config_flow.py b/tests/components/huum/test_config_flow.py index ce5e2cf37c0..219783079e3 100644 --- a/tests/components/huum/test_config_flow.py +++ b/tests/components/huum/test_config_flow.py @@ -26,13 +26,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.huum.config_flow.Huum.status", - return_value=True, - ), patch( - "homeassistant.components.huum.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.huum.config_flow.Huum.status", + return_value=True, + ), + patch( + "homeassistant.components.huum.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -68,12 +71,15 @@ async def test_signup_flow_already_set_up(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.huum.config_flow.Huum.status", - return_value=True, - ), patch( - "homeassistant.components.huum.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.huum.config_flow.Huum.status", + return_value=True, + ), + patch( + "homeassistant.components.huum.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -119,12 +125,15 @@ async def test_huum_errors( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": error_base} - with patch( - "homeassistant.components.huum.config_flow.Huum.status", - return_value=True, - ), patch( - "homeassistant.components.huum.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.huum.config_flow.Huum.status", + return_value=True, + ), + patch( + "homeassistant.components.huum.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/hvv_departures/test_config_flow.py b/tests/components/hvv_departures/test_config_flow.py index 11e5da00d11..2712e1bbca9 100644 --- a/tests/components/hvv_departures/test_config_flow.py +++ b/tests/components/hvv_departures/test_config_flow.py @@ -31,18 +31,23 @@ FIXTURE_DEPARTURE_LIST = json.loads(load_fixture("hvv_departures/departure_list. async def test_user_flow(hass: HomeAssistant) -> None: """Test that config flow works.""" - with patch( - "homeassistant.components.hvv_departures.hub.GTI.init", - return_value=FIXTURE_INIT, - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.checkName", - return_value=FIXTURE_CHECK_NAME, - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.stationInformation", - return_value=FIXTURE_STATION_INFORMATION, - ), patch( - "homeassistant.components.hvv_departures.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", + return_value=FIXTURE_INIT, + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.checkName", + return_value=FIXTURE_CHECK_NAME, + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.stationInformation", + return_value=FIXTURE_STATION_INFORMATION, + ), + patch( + "homeassistant.components.hvv_departures.async_setup_entry", + return_value=True, + ), ): # step: user @@ -94,15 +99,19 @@ async def test_user_flow(hass: HomeAssistant) -> None: async def test_user_flow_no_results(hass: HomeAssistant) -> None: """Test that config flow works when there are no results.""" - with patch( - "homeassistant.components.hvv_departures.hub.GTI.init", - return_value=FIXTURE_INIT, - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.checkName", - return_value={"returnCode": "OK", "results": []}, - ), patch( - "homeassistant.components.hvv_departures.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", + return_value=FIXTURE_INIT, + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.checkName", + return_value={"returnCode": "OK", "results": []}, + ), + patch( + "homeassistant.components.hvv_departures.async_setup_entry", + return_value=True, + ), ): # step: user @@ -179,12 +188,15 @@ async def test_user_flow_cannot_connect(hass: HomeAssistant) -> None: async def test_user_flow_station(hass: HomeAssistant) -> None: """Test that config flow handles empty data on step station.""" - with patch( - "homeassistant.components.hvv_departures.hub.GTI.init", - return_value=True, - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.checkName", - return_value={"returnCode": "OK", "results": []}, + with ( + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", + return_value=True, + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.checkName", + return_value={"returnCode": "OK", "results": []}, + ), ): # step: user @@ -212,12 +224,15 @@ async def test_user_flow_station(hass: HomeAssistant) -> None: async def test_user_flow_station_select(hass: HomeAssistant) -> None: """Test that config flow handles empty data on step station_select.""" - with patch( - "homeassistant.components.hvv_departures.hub.GTI.init", - return_value=True, - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.checkName", - return_value=FIXTURE_CHECK_NAME, + with ( + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", + return_value=True, + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.checkName", + return_value=FIXTURE_CHECK_NAME, + ), ): result_user = await hass.config_entries.flow.async_init( DOMAIN, @@ -258,12 +273,16 @@ async def test_options_flow(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), patch( - "homeassistant.components.hvv_departures.hub.GTI.init", - return_value=True, - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.departureList", - return_value=FIXTURE_DEPARTURE_LIST, + with ( + patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", + return_value=True, + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.departureList", + return_value=FIXTURE_DEPARTURE_LIST, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -307,11 +326,15 @@ async def test_options_flow_invalid_auth(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), patch( - "homeassistant.components.hvv_departures.hub.GTI.init", return_value=True - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.departureList", - return_value=FIXTURE_DEPARTURE_LIST, + with ( + patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", return_value=True + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.departureList", + return_value=FIXTURE_DEPARTURE_LIST, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -346,11 +369,15 @@ async def test_options_flow_cannot_connect(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), patch( - "homeassistant.components.hvv_departures.hub.GTI.init", return_value=True - ), patch( - "homeassistant.components.hvv_departures.hub.GTI.departureList", - return_value=FIXTURE_DEPARTURE_LIST, + with ( + patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), + patch( + "homeassistant.components.hvv_departures.hub.GTI.init", return_value=True + ), + patch( + "homeassistant.components.hvv_departures.hub.GTI.departureList", + return_value=FIXTURE_DEPARTURE_LIST, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/hyperion/test_config_flow.py b/tests/components/hyperion/test_config_flow.py index 17f635eef4f..86dc4c5c39d 100644 --- a/tests/components/hyperion/test_config_flow.py +++ b/tests/components/hyperion/test_config_flow.py @@ -376,11 +376,15 @@ async def test_auth_create_token_approval_declined(hass: HomeAssistant) -> None: assert result["step_id"] == "auth" client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_FAIL) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch( - "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", - return_value=TEST_AUTH_ID, + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), ): result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: True} @@ -440,11 +444,15 @@ async def test_auth_create_token_approval_declined_task_canceled( task_coro = arg return mock_task - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch( - "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", - return_value=TEST_AUTH_ID, + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), ): result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: True} @@ -482,11 +490,15 @@ async def test_auth_create_token_when_issued_token_fails( assert result["step_id"] == "auth" client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_SUCCESS) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch( - "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", - return_value=TEST_AUTH_ID, + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), ): result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: True} @@ -526,11 +538,15 @@ async def test_auth_create_token_success(hass: HomeAssistant) -> None: assert result["step_id"] == "auth" client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_SUCCESS) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch( - "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", - return_value=TEST_AUTH_ID, + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), ): result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: True} @@ -572,11 +588,15 @@ async def test_auth_create_token_success_but_login_fail( assert result["step_id"] == "auth" client.async_request_token = AsyncMock(return_value=TEST_REQUEST_TOKEN_SUCCESS) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch( - "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", - return_value=TEST_AUTH_ID, + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), ): result = await _configure_flow( hass, result, user_input={CONF_CREATE_TOKEN: True} @@ -684,11 +704,15 @@ async def test_ssdp_failure_bad_port_ui(hass: HomeAssistant) -> None: bad_data = dataclasses.replace(TEST_SSDP_SERVICE_INFO) bad_data.ssdp_location = f"http://{TEST_HOST}:not_a_port/description.xml" - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch( - "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", - return_value=TEST_AUTH_ID, + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch( + "homeassistant.components.hyperion.config_flow.client.generate_random_auth_id", + return_value=TEST_AUTH_ID, + ), ): result = await _init_flow(hass, source=SOURCE_SSDP, data=bad_data) @@ -830,9 +854,13 @@ async def test_reauth_success(hass: HomeAssistant) -> None: client = create_mock_client() client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch("homeassistant.components.hyperion.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch("homeassistant.components.hyperion.async_setup_entry", return_value=True), + ): result = await _init_flow( hass, source=SOURCE_REAUTH, diff --git a/tests/components/hyperion/test_light.py b/tests/components/hyperion/test_light.py index 797107b0c34..e1e7711e702 100644 --- a/tests/components/hyperion/test_light.py +++ b/tests/components/hyperion/test_light.py @@ -713,9 +713,13 @@ async def test_setup_entry_no_token_reauth(hass: HomeAssistant) -> None: config_entry = add_test_config_entry(hass) client.async_is_auth_required = AsyncMock(return_value=TEST_AUTH_REQUIRED_RESP) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_flow_init, + ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert client.async_client_disconnect.called @@ -743,9 +747,13 @@ async def test_setup_entry_bad_token_reauth(hass: HomeAssistant) -> None: # Fail to log in. client.async_client_login = AsyncMock(return_value=False) - with patch( - "homeassistant.components.hyperion.client.HyperionClient", return_value=client - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + with ( + patch( + "homeassistant.components.hyperion.client.HyperionClient", + return_value=client, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_flow_init, + ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert client.async_client_disconnect.called diff --git a/tests/components/ialarm/test_config_flow.py b/tests/components/ialarm/test_config_flow.py index 18fd9d03015..816f03efa9e 100644 --- a/tests/components/ialarm/test_config_flow.py +++ b/tests/components/ialarm/test_config_flow.py @@ -23,16 +23,20 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.ialarm.config_flow.IAlarm.get_status", - return_value=1, - ), patch( - "homeassistant.components.ialarm.config_flow.IAlarm.get_mac", - return_value=TEST_MAC, - ), patch( - "homeassistant.components.ialarm.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.ialarm.config_flow.IAlarm.get_status", + return_value=1, + ), + patch( + "homeassistant.components.ialarm.config_flow.IAlarm.get_mac", + return_value=TEST_MAC, + ), + patch( + "homeassistant.components.ialarm.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_DATA ) diff --git a/tests/components/iaqualink/test_init.py b/tests/components/iaqualink/test_init.py index 8c64b1072d6..d450ced1fd7 100644 --- a/tests/components/iaqualink/test_init.py +++ b/tests/components/iaqualink/test_init.py @@ -68,12 +68,15 @@ async def test_setup_systems_exception(hass: HomeAssistant, config_entry) -> Non """Test setup encountering an exception while retrieving systems.""" config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - side_effect=AqualinkServiceException, + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + side_effect=AqualinkServiceException, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -85,12 +88,15 @@ async def test_setup_no_systems_recognized(hass: HomeAssistant, config_entry) -> """Test setup ending in no systems recognized.""" config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - return_value={}, + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + return_value={}, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -107,16 +113,20 @@ async def test_setup_devices_exception( system = get_aqualink_system(client, cls=IaquaSystem) systems = {system.serial: system} - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - return_value=systems, - ), patch.object( - system, - "get_devices", - ) as mock_get_devices: + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + return_value=systems, + ), + patch.object( + system, + "get_devices", + ) as mock_get_devices, + ): mock_get_devices.side_effect = AqualinkServiceException await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -136,16 +146,20 @@ async def test_setup_all_good_no_recognized_devices( device = get_aqualink_device(system, name="dev_1") devices = {device.name: device} - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - return_value=systems, - ), patch.object( - system, - "get_devices", - ) as mock_get_devices: + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + return_value=systems, + ), + patch.object( + system, + "get_devices", + ) as mock_get_devices, + ): mock_get_devices.return_value = devices await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -184,12 +198,15 @@ async def test_setup_all_good_all_device_types( system.get_devices = AsyncMock(return_value=devices) - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - return_value=systems, + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + return_value=systems, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -221,12 +238,15 @@ async def test_multiple_updates( caplog.set_level(logging.WARNING) - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - return_value=systems, + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + return_value=systems, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -330,12 +350,15 @@ async def test_entity_assumed_and_available( system.get_devices = AsyncMock(return_value=devices) system.update = AsyncMock() - with patch( - "homeassistant.components.iaqualink.AqualinkClient.login", - return_value=None, - ), patch( - "homeassistant.components.iaqualink.AqualinkClient.get_systems", - return_value=systems, + with ( + patch( + "homeassistant.components.iaqualink.AqualinkClient.login", + return_value=None, + ), + patch( + "homeassistant.components.iaqualink.AqualinkClient.get_systems", + return_value=systems, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/ibeacon/test_coordinator.py b/tests/components/ibeacon/test_coordinator.py index f94214fa47a..0880f745ec2 100644 --- a/tests/components/ibeacon/test_coordinator.py +++ b/tests/components/ibeacon/test_coordinator.py @@ -236,9 +236,12 @@ async def test_default_name_allowlisted_restore_late(hass: HomeAssistant) -> Non # Fastforward time until the device is no longer advertised monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/ibeacon/test_device_tracker.py b/tests/components/ibeacon/test_device_tracker.py index 6a8d079ba46..77f8271370e 100644 --- a/tests/components/ibeacon/test_device_tracker.py +++ b/tests/components/ibeacon/test_device_tracker.py @@ -104,9 +104,12 @@ async def test_device_tracker_random_address(hass: HomeAssistant) -> None: assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234" await hass.async_block_till_done() - with patch_all_discovered_devices([]), patch( - "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", - return_value=start_time + UNAVAILABLE_TIMEOUT + 1, + with ( + patch_all_discovered_devices([]), + patch( + "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", + return_value=start_time + UNAVAILABLE_TIMEOUT + 1, + ), ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TIMEOUT) @@ -169,9 +172,12 @@ async def test_device_tracker_random_address_infrequent_changes( assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234" await hass.async_block_till_done() - with patch_all_discovered_devices([]), patch( - "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", - return_value=start_time + UNAVAILABLE_TIMEOUT + 1, + with ( + patch_all_discovered_devices([]), + patch( + "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", + return_value=start_time + UNAVAILABLE_TIMEOUT + 1, + ), ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TIMEOUT) @@ -196,9 +202,12 @@ async def test_device_tracker_random_address_infrequent_changes( ) device = async_ble_device_from_address(hass, "AA:BB:CC:DD:EE:14", False) - with patch_all_discovered_devices([device]), patch( - "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", - return_value=start_time + UPDATE_INTERVAL.total_seconds() + 1, + with ( + patch_all_discovered_devices([device]), + patch( + "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", + return_value=start_time + UPDATE_INTERVAL.total_seconds() + 1, + ), ): async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL) await hass.async_block_till_done() @@ -234,9 +243,12 @@ async def test_device_tracker_random_address_infrequent_changes( == one_day_future ) - with patch_all_discovered_devices([device]), patch( - "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", - return_value=start_time + UNAVAILABLE_TIMEOUT + 1, + with ( + patch_all_discovered_devices([device]), + patch( + "homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME", + return_value=start_time + UNAVAILABLE_TIMEOUT + 1, + ), ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TIMEOUT + 1) diff --git a/tests/components/idasen_desk/test_config_flow.py b/tests/components/idasen_desk/test_config_flow.py index 32ed6b89f30..78eacfb6942 100644 --- a/tests/components/idasen_desk/test_config_flow.py +++ b/tests/components/idasen_desk/test_config_flow.py @@ -30,12 +30,14 @@ async def test_user_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch("homeassistant.components.idasen_desk.config_flow.Desk.connect"), patch( - "homeassistant.components.idasen_desk.config_flow.Desk.disconnect" - ), patch( - "homeassistant.components.idasen_desk.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.idasen_desk.config_flow.Desk.connect"), + patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"), + patch( + "homeassistant.components.idasen_desk.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -103,10 +105,13 @@ async def test_user_step_cannot_connect( assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.idasen_desk.config_flow.Desk.connect", - side_effect=exception, - ), patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"): + with ( + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.connect", + side_effect=exception, + ), + patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -119,12 +124,14 @@ async def test_user_step_cannot_connect( assert result2["step_id"] == "user" assert result2["errors"] == {"base": "cannot_connect"} - with patch("homeassistant.components.idasen_desk.config_flow.Desk.connect"), patch( - "homeassistant.components.idasen_desk.config_flow.Desk.disconnect" - ), patch( - "homeassistant.components.idasen_desk.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.idasen_desk.config_flow.Desk.connect"), + patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"), + patch( + "homeassistant.components.idasen_desk.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -155,10 +162,13 @@ async def test_user_step_auth_failed(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.idasen_desk.config_flow.Desk.connect", - side_effect=AuthFailedError, - ), patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"): + with ( + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.connect", + side_effect=AuthFailedError, + ), + patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -171,12 +181,14 @@ async def test_user_step_auth_failed(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "auth_failed"} - with patch("homeassistant.components.idasen_desk.config_flow.Desk.connect"), patch( - "homeassistant.components.idasen_desk.config_flow.Desk.disconnect" - ), patch( - "homeassistant.components.idasen_desk.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.idasen_desk.config_flow.Desk.connect"), + patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"), + patch( + "homeassistant.components.idasen_desk.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -207,11 +219,14 @@ async def test_user_step_unknown_exception(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.idasen_desk.config_flow.Desk.connect", - side_effect=RuntimeError, - ), patch( - "homeassistant.components.idasen_desk.config_flow.Desk.disconnect", + with ( + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.connect", + side_effect=RuntimeError, + ), + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.disconnect", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -225,14 +240,18 @@ async def test_user_step_unknown_exception(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "unknown"} - with patch( - "homeassistant.components.idasen_desk.config_flow.Desk.connect", - ), patch( - "homeassistant.components.idasen_desk.config_flow.Desk.disconnect", - ), patch( - "homeassistant.components.idasen_desk.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.connect", + ), + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.disconnect", + ), + patch( + "homeassistant.components.idasen_desk.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -261,14 +280,16 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.idasen_desk.config_flow.Desk.connect" - ) as desk_connect, patch( - "homeassistant.components.idasen_desk.config_flow.Desk.disconnect" - ), patch( - "homeassistant.components.idasen_desk.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.idasen_desk.config_flow.Desk.connect" + ) as desk_connect, + patch("homeassistant.components.idasen_desk.config_flow.Desk.disconnect"), + patch( + "homeassistant.components.idasen_desk.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index d9b268ff575..c26eae28086 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -205,10 +205,13 @@ async def test_setup_with_custom_location(hass: HomeAssistant) -> None: # Set up some mock feed entries for this test. mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 20.5, (38.1, -3.1)) - with patch( - "georss_ign_sismologia_client.feed_manager.IgnSismologiaFeed", - wraps=IgnSismologiaFeed, - ) as mock_feed, patch("georss_client.feed.GeoRssFeed.update") as mock_feed_update: + with ( + patch( + "georss_ign_sismologia_client.feed_manager.IgnSismologiaFeed", + wraps=IgnSismologiaFeed, + ) as mock_feed, + patch("georss_client.feed.GeoRssFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1] with assert_setup_component(1, geo_location.DOMAIN): diff --git a/tests/components/image_upload/test_init.py b/tests/components/image_upload/test_init.py index 394c0672485..1117befc7fd 100644 --- a/tests/components/image_upload/test_init.py +++ b/tests/components/image_upload/test_init.py @@ -27,8 +27,9 @@ async def test_upload_image( now = dt_util.utcnow() freezer.move_to(now) - with tempfile.TemporaryDirectory() as tempdir, patch.object( - hass.config, "path", return_value=tempdir + with ( + tempfile.TemporaryDirectory() as tempdir, + patch.object(hass.config, "path", return_value=tempdir), ): assert await async_setup_component(hass, "image_upload", {}) ws_client: ClientWebSocketResponse = await hass_ws_client() diff --git a/tests/components/improv_ble/test_config_flow.py b/tests/components/improv_ble/test_config_flow.py index 81efd10cc3d..bafc32907ab 100644 --- a/tests/components/improv_ble/test_config_flow.py +++ b/tests/components/improv_ble/test_config_flow.py @@ -253,13 +253,16 @@ async def _test_common_success( ) -> None: """Test bluetooth and user flow success paths.""" - with patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", - return_value=False, - ), patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision", - return_value=url, - ) as mock_provision: + with ( + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", + return_value=False, + ), + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision", + return_value=url, + ) as mock_provision, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"ssid": "MyWIFI", "password": "secret"} ) @@ -305,13 +308,16 @@ async def _test_common_success_w_authorize( state_callback(State.AUTHORIZED) return lambda: None - with patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", - return_value=True, - ), patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.subscribe_state_updates", - side_effect=subscribe_state_updates, - ) as mock_subscribe_state_updates: + with ( + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", + return_value=True, + ), + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.subscribe_state_updates", + side_effect=subscribe_state_updates, + ) as mock_subscribe_state_updates, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"ssid": "MyWIFI", "password": "secret"} ) @@ -321,13 +327,16 @@ async def _test_common_success_w_authorize( mock_subscribe_state_updates.assert_awaited_once() await hass.async_block_till_done() - with patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", - return_value=False, - ), patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision", - return_value="http://blabla.local", - ) as mock_provision: + with ( + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", + return_value=False, + ), + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision", + return_value="http://blabla.local", + ) as mock_provision, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == FlowResultType.SHOW_PROGRESS assert result["progress_action"] == "provisioning" @@ -517,12 +526,15 @@ async def test_authorize_fails(hass: HomeAssistant, exc, error) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "provision" - with patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", - return_value=True, - ), patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.subscribe_state_updates", - side_effect=exc, + with ( + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", + return_value=True, + ), + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.subscribe_state_updates", + side_effect=exc, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"ssid": "MyWIFI", "password": "secret"} @@ -557,12 +569,15 @@ async def _test_provision_error(hass: HomeAssistant, exc) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "provision" - with patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", - return_value=False, - ), patch( - f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision", - side_effect=exc, + with ( + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.need_authorization", + return_value=False, + ), + patch( + f"{IMPROV_BLE}.config_flow.ImprovBLEClient.provision", + side_effect=exc, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"ssid": "MyWIFI", "password": "secret"} diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index 883b40ab1df..41f14aa78e3 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -258,8 +258,9 @@ async def test_setup_config_ssl( config = {"influxdb": config_base.copy()} config["influxdb"].update(config_ext) - with patch("os.access", return_value=True), patch( - "os.path.isfile", return_value=True + with ( + patch("os.access", return_value=True), + patch("os.path.isfile", return_value=True), ): assert await async_setup_component(hass, influxdb.DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/influxdb/test_sensor.py b/tests/components/influxdb/test_sensor.py index dc88e04b4cf..395d33004a7 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -93,9 +93,10 @@ def mock_client_fixture(request): @pytest.fixture(autouse=True, scope="module") def mock_client_close(): """Mock close method of clients at module scope.""" - with patch(f"{INFLUXDB_CLIENT_PATH}.close") as close_v1, patch( - f"{INFLUXDB_CLIENT_PATH}V2.close" - ) as close_v2: + with ( + patch(f"{INFLUXDB_CLIENT_PATH}.close") as close_v1, + patch(f"{INFLUXDB_CLIENT_PATH}V2.close") as close_v2, + ): yield (close_v1, close_v2) diff --git a/tests/components/insteon/test_api_scenes.py b/tests/components/insteon/test_api_scenes.py index aa4eb774229..04fc74c89d1 100644 --- a/tests/components/insteon/test_api_scenes.py +++ b/tests/components/insteon/test_api_scenes.py @@ -101,9 +101,10 @@ async def test_save_scene( mock_add_or_update_scene = AsyncMock(return_value=(20, ResponseStatus.SUCCESS)) - with patch.object( - pyinsteon.managers.scene_manager, "devices", devices - ), patch.object(scenes, "async_add_or_update_scene", mock_add_or_update_scene): + with ( + patch.object(pyinsteon.managers.scene_manager, "devices", devices), + patch.object(scenes, "async_add_or_update_scene", mock_add_or_update_scene), + ): scene = await pyinsteon.managers.scene_manager.async_get_scene(20) scene["devices"]["1a1a1a"] = [] links = _scene_to_array(scene) @@ -132,9 +133,10 @@ async def test_save_new_scene( mock_add_or_update_scene = AsyncMock(return_value=(21, ResponseStatus.SUCCESS)) - with patch.object( - pyinsteon.managers.scene_manager, "devices", devices - ), patch.object(scenes, "async_add_or_update_scene", mock_add_or_update_scene): + with ( + patch.object(pyinsteon.managers.scene_manager, "devices", devices), + patch.object(scenes, "async_add_or_update_scene", mock_add_or_update_scene), + ): scene = await pyinsteon.managers.scene_manager.async_get_scene(20) scene["devices"]["1a1a1a"] = [] links = _scene_to_array(scene) @@ -163,9 +165,10 @@ async def test_save_scene_error( mock_add_or_update_scene = AsyncMock(return_value=(20, ResponseStatus.FAILURE)) - with patch.object( - pyinsteon.managers.scene_manager, "devices", devices - ), patch.object(scenes, "async_add_or_update_scene", mock_add_or_update_scene): + with ( + patch.object(pyinsteon.managers.scene_manager, "devices", devices), + patch.object(scenes, "async_add_or_update_scene", mock_add_or_update_scene), + ): scene = await pyinsteon.managers.scene_manager.async_get_scene(20) scene["devices"]["1a1a1a"] = [] links = _scene_to_array(scene) @@ -194,9 +197,10 @@ async def test_delete_scene( mock_delete_scene = AsyncMock(return_value=ResponseStatus.SUCCESS) - with patch.object( - pyinsteon.managers.scene_manager, "devices", devices - ), patch.object(scenes, "async_delete_scene", mock_delete_scene): + with ( + patch.object(pyinsteon.managers.scene_manager, "devices", devices), + patch.object(scenes, "async_delete_scene", mock_delete_scene), + ): await ws_client.send_json( { ID: 1, diff --git a/tests/components/insteon/test_config_flow.py b/tests/components/insteon/test_config_flow.py index f9636488d93..df7430bc254 100644 --- a/tests/components/insteon/test_config_flow.py +++ b/tests/components/insteon/test_config_flow.py @@ -102,13 +102,17 @@ async def _init_form(hass, modem_type): async def _device_form(hass, flow_id, connection, user_input): """Test the PLM, Hub v1 or Hub v2 form.""" - with patch( - PATCH_CONNECTION, - new=connection, - ), patch(PATCH_ASYNC_SETUP, return_value=True) as mock_setup, patch( - PATCH_ASYNC_SETUP_ENTRY, - return_value=True, - ) as mock_setup_entry: + with ( + patch( + PATCH_CONNECTION, + new=connection, + ), + patch(PATCH_ASYNC_SETUP, return_value=True) as mock_setup, + patch( + PATCH_ASYNC_SETUP_ENTRY, + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure(flow_id, user_input) await hass.async_block_till_done() return result, mock_setup, mock_setup_entry @@ -311,10 +315,11 @@ async def _options_form( mock_devices = MockDevices(connected=True) await mock_devices.async_load() mock_devices.modem = mock_devices["AA.AA.AA"] - with patch(PATCH_CONNECTION, new=connection), patch( - PATCH_ASYNC_SETUP_ENTRY, return_value=True - ) as mock_setup_entry, patch(PATCH_DEVICES, mock_devices), patch( - PATCH_CONNECTION_CLOSE + with ( + patch(PATCH_CONNECTION, new=connection), + patch(PATCH_ASYNC_SETUP_ENTRY, return_value=True) as mock_setup_entry, + patch(PATCH_DEVICES, mock_devices), + patch(PATCH_CONNECTION_CLOSE), ): result = await hass.config_entries.options.async_configure(flow_id, user_input) return result, mock_setup_entry diff --git a/tests/components/insteon/test_init.py b/tests/components/insteon/test_init.py index bfc8a8b8cc1..a4e8da03345 100644 --- a/tests/components/insteon/test_init.py +++ b/tests/components/insteon/test_init.py @@ -32,10 +32,10 @@ async def test_setup_entry(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT_PLM) config_entry.add_to_hass(hass) - with patch.object( - insteon, "async_connect", new=mock_successful_connection - ), patch.object(insteon, "async_close") as mock_close, patch.object( - insteon, "devices", new=MockDevices() + with ( + patch.object(insteon, "async_connect", new=mock_successful_connection), + patch.object(insteon, "async_close") as mock_close, + patch.object(insteon, "devices", new=MockDevices()), ): assert await async_setup_component( hass, @@ -56,9 +56,10 @@ async def test_setup_entry_failed_connection( config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT_PLM) config_entry.add_to_hass(hass) - with patch.object( - insteon, "async_connect", new=mock_failed_connection - ), patch.object(insteon, "devices", new=MockDevices(connected=False)): + with ( + patch.object(insteon, "async_connect", new=mock_failed_connection), + patch.object(insteon, "devices", new=MockDevices(connected=False)), + ): assert await async_setup_component( hass, insteon.DOMAIN, @@ -72,13 +73,14 @@ async def test_import_frontend_dev_url(hass: HomeAssistant) -> None: config = {} config[DOMAIN] = {CONF_DEV_PATH: "/some/path"} - with patch.object( - insteon, "async_connect", new=mock_successful_connection - ), patch.object(insteon, "close_insteon_connection"), patch.object( - insteon, "devices", new=MockDevices() - ), patch( - PATCH_CONNECTION, - new=mock_successful_connection, + with ( + patch.object(insteon, "async_connect", new=mock_successful_connection), + patch.object(insteon, "close_insteon_connection"), + patch.object(insteon, "devices", new=MockDevices()), + patch( + PATCH_CONNECTION, + new=mock_successful_connection, + ), ): assert await async_setup_component( hass, diff --git a/tests/components/insteon/test_lock.py b/tests/components/insteon/test_lock.py index 43b50e78cb1..a782e006a62 100644 --- a/tests/components/insteon/test_lock.py +++ b/tests/components/insteon/test_lock.py @@ -43,14 +43,16 @@ def lock_platform_only(): @pytest.fixture(autouse=True) def patch_setup_and_devices(): """Patch the Insteon setup process and devices.""" - with patch.object(insteon, "async_connect", new=mock_connection), patch.object( - insteon, "async_close" - ), patch.object(insteon, "devices", devices), patch.object( - insteon_utils, "devices", devices - ), patch.object( - insteon_entity, - "devices", - devices, + with ( + patch.object(insteon, "async_connect", new=mock_connection), + patch.object(insteon, "async_close"), + patch.object(insteon, "devices", devices), + patch.object(insteon_utils, "devices", devices), + patch.object( + insteon_entity, + "devices", + devices, + ), ): yield diff --git a/tests/components/iotawatt/test_config_flow.py b/tests/components/iotawatt/test_config_flow.py index 6ed781b2f5b..d4980ba978e 100644 --- a/tests/components/iotawatt/test_config_flow.py +++ b/tests/components/iotawatt/test_config_flow.py @@ -19,13 +19,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.iotawatt.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.iotawatt.config_flow.Iotawatt.connect", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.iotawatt.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.iotawatt.config_flow.Iotawatt.connect", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -80,12 +83,15 @@ async def test_form_auth(hass: HomeAssistant) -> None: assert result3["step_id"] == "auth" assert result3["errors"] == {"base": "invalid_auth"} - with patch( - "homeassistant.components.iotawatt.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.iotawatt.config_flow.Iotawatt.connect", - return_value=True, + with ( + patch( + "homeassistant.components.iotawatt.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.iotawatt.config_flow.Iotawatt.connect", + return_value=True, + ), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/iotawatt/test_sensor.py b/tests/components/iotawatt/test_sensor.py index aa6180e306b..ecf2f97c67a 100644 --- a/tests/components/iotawatt/test_sensor.py +++ b/tests/components/iotawatt/test_sensor.py @@ -63,9 +63,9 @@ async def test_sensor_type_output( hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_iotawatt ) -> None: """Tests the sensor type of Output.""" - mock_iotawatt.getSensors.return_value["sensors"][ - "my_watthour_sensor_key" - ] = OUTPUT_SENSOR + mock_iotawatt.getSensors.return_value["sensors"]["my_watthour_sensor_key"] = ( + OUTPUT_SENSOR + ) assert await async_setup_component(hass, "iotawatt", {}) await hass.async_block_till_done() diff --git a/tests/components/iqvia/conftest.py b/tests/components/iqvia/conftest.py index c1d0341aaa6..6fb14ca4d28 100644 --- a/tests/components/iqvia/conftest.py +++ b/tests/components/iqvia/conftest.py @@ -87,18 +87,17 @@ async def setup_iqvia_fixture( data_disease_index, ): """Define a fixture to set up IQVIA.""" - with patch( - "pyiqvia.allergens.Allergens.extended", return_value=data_allergy_forecast - ), patch( - "pyiqvia.allergens.Allergens.current", return_value=data_allergy_index - ), patch( - "pyiqvia.allergens.Allergens.outlook", return_value=data_allergy_outlook - ), patch( - "pyiqvia.asthma.Asthma.extended", return_value=data_asthma_forecast - ), patch("pyiqvia.asthma.Asthma.current", return_value=data_asthma_index), patch( - "pyiqvia.disease.Disease.extended", return_value=data_disease_forecast - ), patch("pyiqvia.disease.Disease.current", return_value=data_disease_index), patch( - "homeassistant.components.iqvia.PLATFORMS", [] + with ( + patch( + "pyiqvia.allergens.Allergens.extended", return_value=data_allergy_forecast + ), + patch("pyiqvia.allergens.Allergens.current", return_value=data_allergy_index), + patch("pyiqvia.allergens.Allergens.outlook", return_value=data_allergy_outlook), + patch("pyiqvia.asthma.Asthma.extended", return_value=data_asthma_forecast), + patch("pyiqvia.asthma.Asthma.current", return_value=data_asthma_index), + patch("pyiqvia.disease.Disease.extended", return_value=data_disease_forecast), + patch("pyiqvia.disease.Disease.current", return_value=data_disease_index), + patch("homeassistant.components.iqvia.PLATFORMS", []), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/islamic_prayer_times/test_init.py b/tests/components/islamic_prayer_times/test_init.py index 33828de41cd..3c7565a37ef 100644 --- a/tests/components/islamic_prayer_times/test_init.py +++ b/tests/components/islamic_prayer_times/test_init.py @@ -89,10 +89,13 @@ async def test_options_listener(hass: HomeAssistant) -> None: entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={}) entry.add_to_hass(hass) - with patch( - "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", - return_value=PRAYER_TIMES, - ) as mock_fetch_prayer_times, freeze_time(NOW): + with ( + patch( + "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", + return_value=PRAYER_TIMES, + ) as mock_fetch_prayer_times, + freeze_time(NOW), + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert mock_fetch_prayer_times.call_count == 1 @@ -109,10 +112,13 @@ async def test_update_failed(hass: HomeAssistant) -> None: entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={}) entry.add_to_hass(hass) - with patch( - "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", - return_value=PRAYER_TIMES, - ), freeze_time(NOW): + with ( + patch( + "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", + return_value=PRAYER_TIMES, + ), + freeze_time(NOW), + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -176,10 +182,13 @@ async def test_migrate_unique_id( ) assert entity.unique_id == old_unique_id - with patch( - "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", - return_value=PRAYER_TIMES, - ), freeze_time(NOW): + with ( + patch( + "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", + return_value=PRAYER_TIMES, + ), + freeze_time(NOW), + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -196,10 +205,13 @@ async def test_migration_from_1_1_to_1_2(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", - return_value=PRAYER_TIMES, - ), freeze_time(NOW): + with ( + patch( + "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", + return_value=PRAYER_TIMES, + ), + freeze_time(NOW), + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index 2e323e4f5d6..22629819e05 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -38,10 +38,13 @@ async def test_islamic_prayer_times_sensors( entry = MockConfigEntry(domain=DOMAIN, data={}) entry.add_to_hass(hass) - with patch( - "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", - return_value=PRAYER_TIMES, - ), freeze_time(NOW): + with ( + patch( + "prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times", + return_value=PRAYER_TIMES, + ), + freeze_time(NOW), + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert hass.states.get(sensor_name).state == PRAYER_TIMES[key] diff --git a/tests/components/isy994/test_config_flow.py b/tests/components/isy994/test_config_flow.py index e20c4f0b185..b29b1dbc775 100644 --- a/tests/components/isy994/test_config_flow.py +++ b/tests/components/isy994/test_config_flow.py @@ -91,10 +91,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( - PATCH_ASYNC_SETUP_ENTRY, - return_value=True, - ) as mock_setup_entry: + with ( + patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), + patch( + PATCH_ASYNC_SETUP_ENTRY, + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -284,10 +287,13 @@ async def test_form_ssdp(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( - PATCH_ASYNC_SETUP_ENTRY, - return_value=True, - ) as mock_setup_entry: + with ( + patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), + patch( + PATCH_ASYNC_SETUP_ENTRY, + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -443,10 +449,13 @@ async def test_form_dhcp(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( - PATCH_ASYNC_SETUP_ENTRY, - return_value=True, - ) as mock_setup_entry: + with ( + patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), + patch( + PATCH_ASYNC_SETUP_ENTRY, + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -480,10 +489,13 @@ async def test_form_dhcp_with_polisy(hass: HomeAssistant) -> None: == "http://1.2.3.4:8080" ) - with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( - PATCH_ASYNC_SETUP_ENTRY, - return_value=True, - ) as mock_setup_entry: + with ( + patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), + patch( + PATCH_ASYNC_SETUP_ENTRY, + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_IOX_USER_INPUT, @@ -517,10 +529,13 @@ async def test_form_dhcp_with_eisy(hass: HomeAssistant) -> None: == "http://1.2.3.4:8080" ) - with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( - PATCH_ASYNC_SETUP_ENTRY, - return_value=True, - ) as mock_setup_entry: + with ( + patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), + patch( + PATCH_ASYNC_SETUP_ENTRY, + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_IOX_USER_INPUT, @@ -666,10 +681,13 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result3["type"] == "form" assert result3["errors"] == {"base": "cannot_connect"} - with patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), patch( - "homeassistant.components.isy994.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch(PATCH_CONNECTION, return_value=MOCK_CONFIG_RESPONSE), + patch( + "homeassistant.components.isy994.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], { diff --git a/tests/components/izone/test_config_flow.py b/tests/components/izone/test_config_flow.py index 7043baae11a..0988640d644 100644 --- a/tests/components/izone/test_config_flow.py +++ b/tests/components/izone/test_config_flow.py @@ -31,12 +31,15 @@ def _mock_start_discovery(hass, mock_disco): async def test_not_found(hass: HomeAssistant, mock_disco) -> None: """Test not finding iZone controller.""" - with patch( - "homeassistant.components.izone.config_flow.async_start_discovery_service" - ) as start_disco, patch( - "homeassistant.components.izone.config_flow.async_stop_discovery_service", - return_value=None, - ) as stop_disco: + with ( + patch( + "homeassistant.components.izone.config_flow.async_start_discovery_service" + ) as start_disco, + patch( + "homeassistant.components.izone.config_flow.async_stop_discovery_service", + return_value=None, + ) as stop_disco, + ): start_disco.side_effect = _mock_start_discovery(hass, mock_disco) result = await hass.config_entries.flow.async_init( IZONE, context={"source": config_entries.SOURCE_USER} @@ -57,14 +60,18 @@ async def test_found(hass: HomeAssistant, mock_disco) -> None: """Test not finding iZone controller.""" mock_disco.pi_disco.controllers["blah"] = object() - with patch( - "homeassistant.components.izone.climate.async_setup_entry", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.izone.config_flow.async_start_discovery_service" - ) as start_disco, patch( - "homeassistant.components.izone.async_start_discovery_service", - return_value=None, + with ( + patch( + "homeassistant.components.izone.climate.async_setup_entry", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.izone.config_flow.async_start_discovery_service" + ) as start_disco, + patch( + "homeassistant.components.izone.async_start_discovery_service", + return_value=None, + ), ): start_disco.side_effect = _mock_start_discovery(hass, mock_disco) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/juicenet/test_config_flow.py b/tests/components/juicenet/test_config_flow.py index 2a2d55549cd..2a4be10b49b 100644 --- a/tests/components/juicenet/test_config_flow.py +++ b/tests/components/juicenet/test_config_flow.py @@ -26,14 +26,18 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - return_value=MagicMock(), - ), patch( - "homeassistant.components.juicenet.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.juicenet.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + return_value=MagicMock(), + ), + patch( + "homeassistant.components.juicenet.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.juicenet.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"} ) @@ -103,14 +107,18 @@ async def test_form_catch_unknown_errors(hass: HomeAssistant) -> None: async def test_import(hass: HomeAssistant) -> None: """Test that import works as expected.""" - with patch( - "homeassistant.components.juicenet.config_flow.Api.get_devices", - return_value=MagicMock(), - ), patch( - "homeassistant.components.juicenet.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.juicenet.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.juicenet.config_flow.Api.get_devices", + return_value=MagicMock(), + ), + patch( + "homeassistant.components.juicenet.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.juicenet.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, diff --git a/tests/components/justnimbus/test_config_flow.py b/tests/components/justnimbus/test_config_flow.py index 2b2caeed929..b96b6c8aa5c 100644 --- a/tests/components/justnimbus/test_config_flow.py +++ b/tests/components/justnimbus/test_config_flow.py @@ -95,10 +95,13 @@ async def test_abort_already_configured(hass: HomeAssistant) -> None: async def _set_up_justnimbus(hass: HomeAssistant, flow_id: str) -> None: """Reusable successful setup of JustNimbus sensor.""" - with patch("justnimbus.JustNimbusClient.get_data"), patch( - "homeassistant.components.justnimbus.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("justnimbus.JustNimbusClient.get_data"), + patch( + "homeassistant.components.justnimbus.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( flow_id=flow_id, user_input=FIXTURE_USER_INPUT, diff --git a/tests/components/keymitt_ble/test_config_flow.py b/tests/components/keymitt_ble/test_config_flow.py index d2c76c14c6d..7e60bdfca53 100644 --- a/tests/components/keymitt_ble/test_config_flow.py +++ b/tests/components/keymitt_ble/test_config_flow.py @@ -131,9 +131,12 @@ async def test_user_setup_already_configured(hass: HomeAssistant) -> None: async def test_user_no_devices(hass: HomeAssistant) -> None: """Test the user initiated form with valid mac.""" - with patch_microbot_api(), patch( - "homeassistant.components.keymitt_ble.config_flow.async_discovered_service_info", - return_value=[], + with ( + patch_microbot_api(), + patch( + "homeassistant.components.keymitt_ble.config_flow.async_discovered_service_info", + return_value=[], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -145,9 +148,12 @@ async def test_user_no_devices(hass: HomeAssistant) -> None: async def test_no_link(hass: HomeAssistant) -> None: """Test the user initiated form with invalid response.""" - with patch_microbot_api(), patch( - "homeassistant.components.keymitt_ble.config_flow.async_discovered_service_info", - return_value=[SERVICE_INFO], + with ( + patch_microbot_api(), + patch( + "homeassistant.components.keymitt_ble.config_flow.async_discovered_service_info", + return_value=[SERVICE_INFO], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -164,10 +170,13 @@ async def test_no_link(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.FORM assert result2["step_id"] == "link" - with patch( - "homeassistant.components.keymitt_ble.config_flow.MicroBotApiClient", - MockMicroBotApiClientFail, - ), patch_async_setup_entry() as mock_setup_entry: + with ( + patch( + "homeassistant.components.keymitt_ble.config_flow.MicroBotApiClient", + MockMicroBotApiClientFail, + ), + patch_async_setup_entry() as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], USER_INPUT, diff --git a/tests/components/knx/test_config_flow.py b/tests/components/knx/test_config_flow.py index 717d362265e..f5b3d9595d0 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -62,26 +62,34 @@ GATEWAY_INDIVIDUAL_ADDRESS = IndividualAddress("1.0.0") @pytest.fixture(name="knx_setup") def fixture_knx_setup(): """Mock KNX entry setup.""" - with patch("homeassistant.components.knx.async_setup", return_value=True), patch( - "homeassistant.components.knx.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + patch("homeassistant.components.knx.async_setup", return_value=True), + patch( + "homeassistant.components.knx.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): yield mock_async_setup_entry @contextmanager def patch_file_upload(return_value=FIXTURE_KEYRING, side_effect=None): """Patch file upload. Yields the Keyring instance (return_value).""" - with patch( - "homeassistant.components.knx.helpers.keyring.process_uploaded_file" - ) as file_upload_mock, patch( - "homeassistant.components.knx.helpers.keyring.sync_load_keyring", - return_value=return_value, - side_effect=side_effect, - ), patch( - "pathlib.Path.mkdir", - ) as mkdir_mock, patch( - "shutil.move", - ) as shutil_move_mock: + with ( + patch( + "homeassistant.components.knx.helpers.keyring.process_uploaded_file" + ) as file_upload_mock, + patch( + "homeassistant.components.knx.helpers.keyring.sync_load_keyring", + return_value=return_value, + side_effect=side_effect, + ), + patch( + "pathlib.Path.mkdir", + ) as mkdir_mock, + patch( + "shutil.move", + ) as shutil_move_mock, + ): file_upload_mock.return_value.__enter__.return_value = Mock() yield return_value if side_effect: diff --git a/tests/components/knx/test_init.py b/tests/components/knx/test_init.py index 5a6770dc92e..2d2889e7718 100644 --- a/tests/components/knx/test_init.py +++ b/tests/components/knx/test_init.py @@ -277,9 +277,10 @@ async def test_async_remove_entry( knx.mock_config_entry = config_entry await knx.setup_integration({}) - with patch("pathlib.Path.unlink") as unlink_mock, patch( - "pathlib.Path.rmdir" - ) as rmdir_mock: + with ( + patch("pathlib.Path.unlink") as unlink_mock, + patch("pathlib.Path.rmdir") as rmdir_mock, + ): assert await hass.config_entries.async_remove(config_entry.entry_id) assert unlink_mock.call_count == 3 rmdir_mock.assert_called_once() diff --git a/tests/components/knx/test_services.py b/tests/components/knx/test_services.py index a6688f7a14c..e93f59ba574 100644 --- a/tests/components/knx/test_services.py +++ b/tests/components/knx/test_services.py @@ -271,11 +271,13 @@ async def test_reload_service( """Test reload service.""" await knx.setup_integration({}) - with patch( - "homeassistant.components.knx.async_unload_entry", wraps=knx_async_unload_entry - ) as mock_unload_entry, patch( - "homeassistant.components.knx.async_setup_entry" - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.knx.async_unload_entry", + wraps=knx_async_unload_entry, + ) as mock_unload_entry, + patch("homeassistant.components.knx.async_setup_entry") as mock_setup_entry, + ): await hass.services.async_call( "knx", "reload", diff --git a/tests/components/knx/test_websocket.py b/tests/components/knx/test_websocket.py index f36d5bb5f7a..78cbb98a7a0 100644 --- a/tests/components/knx/test_websocket.py +++ b/tests/components/knx/test_websocket.py @@ -73,11 +73,12 @@ async def test_knx_project_file_process( "password": _password, } ) - with patch( - "homeassistant.components.knx.project.process_uploaded_file", - ) as file_upload_mock, patch( - "xknxproject.XKNXProj.parse", return_value=_parse_result - ) as parse_mock: + with ( + patch( + "homeassistant.components.knx.project.process_uploaded_file", + ) as file_upload_mock, + patch("xknxproject.XKNXProj.parse", return_value=_parse_result) as parse_mock, + ): file_upload_mock.return_value.__enter__.return_value = "" res = await client.receive_json() @@ -106,11 +107,12 @@ async def test_knx_project_file_process_error( "password": "", } ) - with patch( - "homeassistant.components.knx.project.process_uploaded_file", - ) as file_upload_mock, patch( - "xknxproject.XKNXProj.parse", side_effect=ValueError - ) as parse_mock: + with ( + patch( + "homeassistant.components.knx.project.process_uploaded_file", + ) as file_upload_mock, + patch("xknxproject.XKNXProj.parse", side_effect=ValueError) as parse_mock, + ): file_upload_mock.return_value.__enter__.return_value = "" res = await client.receive_json() parse_mock.assert_called_once_with() diff --git a/tests/components/kodi/__init__.py b/tests/components/kodi/__init__.py index a15d1e6681d..d55a67ba235 100644 --- a/tests/components/kodi/__init__.py +++ b/tests/components/kodi/__init__.py @@ -31,12 +31,16 @@ async def init_integration(hass) -> MockConfigEntry: entry = MockConfigEntry(domain=DOMAIN, data=entry_data, title="name") entry.add_to_hass(hass) - with patch("homeassistant.components.kodi.Kodi.ping", return_value=True), patch( - "homeassistant.components.kodi.Kodi.get_application_properties", - return_value={"version": {"major": 1, "minor": 1}}, - ), patch( - "homeassistant.components.kodi.get_kodi_connection", - return_value=MockConnection(), + with ( + patch("homeassistant.components.kodi.Kodi.ping", return_value=True), + patch( + "homeassistant.components.kodi.Kodi.get_application_properties", + return_value={"version": {"major": 1, "minor": 1}}, + ), + patch( + "homeassistant.components.kodi.get_kodi_connection", + return_value=MockConnection(), + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/kodi/test_config_flow.py b/tests/components/kodi/test_config_flow.py index 83795978522..ecc3bc1f672 100644 --- a/tests/components/kodi/test_config_flow.py +++ b/tests/components/kodi/test_config_flow.py @@ -42,16 +42,20 @@ async def user_flow(hass): async def test_user_flow(hass: HomeAssistant, user_flow) -> None: """Test a successful user initiated flow.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), - ), patch( - "homeassistant.components.kodi.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), + patch( + "homeassistant.components.kodi.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) await hass.async_block_till_done() @@ -71,12 +75,15 @@ async def test_user_flow(hass: HomeAssistant, user_flow) -> None: async def test_form_valid_auth(hass: HomeAssistant, user_flow) -> None: """Test we handle valid auth.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=InvalidAuthError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=InvalidAuthError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -84,16 +91,20 @@ async def test_form_valid_auth(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "credentials" assert result["errors"] == {} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), - ), patch( - "homeassistant.components.kodi.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), + patch( + "homeassistant.components.kodi.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CREDENTIALS ) @@ -114,16 +125,20 @@ async def test_form_valid_auth(hass: HomeAssistant, user_flow) -> None: async def test_form_valid_ws_port(hass: HomeAssistant, user_flow) -> None: """Test we handle valid websocket port.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, - "connect", - AsyncMock(side_effect=CannotConnectError), - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -131,16 +146,20 @@ async def test_form_valid_ws_port(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "ws_port" assert result["errors"] == {} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), - ), patch( - "homeassistant.components.kodi.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), + patch( + "homeassistant.components.kodi.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_WS_PORT ) @@ -162,16 +181,20 @@ async def test_form_valid_ws_port(hass: HomeAssistant, user_flow) -> None: async def test_form_empty_ws_port(hass: HomeAssistant, user_flow) -> None: """Test we handle an empty websocket port input.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, - "connect", - AsyncMock(side_effect=CannotConnectError), - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -204,12 +227,15 @@ async def test_form_empty_ws_port(hass: HomeAssistant, user_flow) -> None: async def test_form_invalid_auth(hass: HomeAssistant, user_flow) -> None: """Test we handle invalid auth.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=InvalidAuthError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=InvalidAuthError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -217,12 +243,15 @@ async def test_form_invalid_auth(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "credentials" assert result["errors"] == {} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=InvalidAuthError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=InvalidAuthError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CREDENTIALS @@ -232,12 +261,15 @@ async def test_form_invalid_auth(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "credentials" assert result["errors"] == {"base": "invalid_auth"} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=CannotConnectError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=CannotConnectError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CREDENTIALS @@ -247,12 +279,15 @@ async def test_form_invalid_auth(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "credentials" assert result["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=Exception, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=Exception, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CREDENTIALS @@ -262,16 +297,20 @@ async def test_form_invalid_auth(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "credentials" assert result["errors"] == {"base": "unknown"} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, - "connect", - AsyncMock(side_effect=CannotConnectError), - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CREDENTIALS @@ -284,12 +323,15 @@ async def test_form_invalid_auth(hass: HomeAssistant, user_flow) -> None: async def test_form_cannot_connect_http(hass: HomeAssistant, user_flow) -> None: """Test we handle cannot connect over HTTP error.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=CannotConnectError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=CannotConnectError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -300,12 +342,15 @@ async def test_form_cannot_connect_http(hass: HomeAssistant, user_flow) -> None: async def test_form_exception_http(hass: HomeAssistant, user_flow) -> None: """Test we handle generic exception over HTTP.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=Exception, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=Exception, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -316,16 +361,20 @@ async def test_form_exception_http(hass: HomeAssistant, user_flow) -> None: async def test_form_cannot_connect_ws(hass: HomeAssistant, user_flow) -> None: """Test we handle cannot connect over WebSocket error.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, - "connect", - AsyncMock(side_effect=CannotConnectError), - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -333,14 +382,18 @@ async def test_form_cannot_connect_ws(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "ws_port" assert result["errors"] == {} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, "connected", new_callable=PropertyMock(return_value=False) - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, "connected", new_callable=PropertyMock(return_value=False) + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_WS_PORT @@ -350,12 +403,15 @@ async def test_form_cannot_connect_ws(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "ws_port" assert result["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=CannotConnectError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=CannotConnectError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_WS_PORT @@ -368,16 +424,20 @@ async def test_form_cannot_connect_ws(hass: HomeAssistant, user_flow) -> None: async def test_form_exception_ws(hass: HomeAssistant, user_flow) -> None: """Test we handle generic exception over WebSocket.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, - "connect", - AsyncMock(side_effect=CannotConnectError), - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure(user_flow, TEST_HOST) @@ -385,14 +445,16 @@ async def test_form_exception_ws(hass: HomeAssistant, user_flow) -> None: assert result["step_id"] == "ws_port" assert result["errors"] == {} - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, "connect", AsyncMock(side_effect=Exception) - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object(MockWSConnection, "connect", AsyncMock(side_effect=Exception)), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_WS_PORT @@ -405,12 +467,15 @@ async def test_form_exception_ws(hass: HomeAssistant, user_flow) -> None: async def test_discovery(hass: HomeAssistant) -> None: """Test discovery flow works.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -446,12 +511,15 @@ async def test_discovery(hass: HomeAssistant) -> None: async def test_discovery_cannot_connect_http(hass: HomeAssistant) -> None: """Test discovery aborts if cannot connect.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=CannotConnectError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=CannotConnectError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -465,16 +533,20 @@ async def test_discovery_cannot_connect_http(hass: HomeAssistant) -> None: async def test_discovery_cannot_connect_ws(hass: HomeAssistant) -> None: """Test discovery aborts if cannot connect to websocket.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch.object( - MockWSConnection, - "connect", - AsyncMock(side_effect=CannotConnectError), - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - new=get_kodi_connection, + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch.object( + MockWSConnection, + "connect", + AsyncMock(side_effect=CannotConnectError), + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + new=get_kodi_connection, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -489,12 +561,15 @@ async def test_discovery_cannot_connect_ws(hass: HomeAssistant) -> None: async def test_discovery_exception_http(hass: HomeAssistant, user_flow) -> None: """Test we handle generic exception during discovery validation.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=Exception, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=Exception, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -508,12 +583,15 @@ async def test_discovery_exception_http(hass: HomeAssistant, user_flow) -> None: async def test_discovery_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth during discovery.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=InvalidAuthError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=InvalidAuthError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -528,12 +606,15 @@ async def test_discovery_invalid_auth(hass: HomeAssistant) -> None: async def test_discovery_duplicate_data(hass: HomeAssistant) -> None: """Test discovery aborts if same mDNS packet arrives.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -588,16 +669,20 @@ async def test_discovery_without_unique_id(hass: HomeAssistant) -> None: async def test_form_import(hass: HomeAssistant) -> None: """Test we get the form with import source.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - return_value=True, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), - ), patch( - "homeassistant.components.kodi.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + return_value=True, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), + patch( + "homeassistant.components.kodi.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -614,12 +699,15 @@ async def test_form_import(hass: HomeAssistant) -> None: async def test_form_import_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth on import.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=InvalidAuthError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=InvalidAuthError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -633,12 +721,15 @@ async def test_form_import_invalid_auth(hass: HomeAssistant) -> None: async def test_form_import_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect on import.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=CannotConnectError, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=CannotConnectError, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -652,12 +743,15 @@ async def test_form_import_cannot_connect(hass: HomeAssistant) -> None: async def test_form_import_exception(hass: HomeAssistant) -> None: """Test we handle unknown exception on import.""" - with patch( - "homeassistant.components.kodi.config_flow.Kodi.ping", - side_effect=Exception, - ), patch( - "homeassistant.components.kodi.config_flow.get_kodi_connection", - return_value=MockConnection(), + with ( + patch( + "homeassistant.components.kodi.config_flow.Kodi.ping", + side_effect=Exception, + ), + patch( + "homeassistant.components.kodi.config_flow.get_kodi_connection", + return_value=MockConnection(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/kraken/test_config_flow.py b/tests/components/kraken/test_config_flow.py index 3d04e3183c3..e6639264291 100644 --- a/tests/components/kraken/test_config_flow.py +++ b/tests/components/kraken/test_config_flow.py @@ -59,15 +59,19 @@ async def test_options(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.kraken.config_flow.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - return_value=TICKER_INFORMATION_RESPONSE, + with ( + patch( + "homeassistant.components.kraken.config_flow.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + return_value=TICKER_INFORMATION_RESPONSE, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/kraken/test_init.py b/tests/components/kraken/test_init.py index 2564fb97b26..8aeb7f18121 100644 --- a/tests/components/kraken/test_init.py +++ b/tests/components/kraken/test_init.py @@ -15,12 +15,15 @@ from tests.common import MockConfigEntry async def test_unload_entry(hass: HomeAssistant) -> None: """Test unload for Kraken.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - return_value=TICKER_INFORMATION_RESPONSE, + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + return_value=TICKER_INFORMATION_RESPONSE, + ), ): entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) @@ -35,12 +38,15 @@ async def test_unknown_error( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test unload for Kraken.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - side_effect=KrakenAPIError("EQuery: Error"), + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + side_effect=KrakenAPIError("EQuery: Error"), + ), ): entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) @@ -54,12 +60,15 @@ async def test_callrate_limit( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test unload for Kraken.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - side_effect=CallRateLimitError(), + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + side_effect=CallRateLimitError(), + ), ): entry = MockConfigEntry(domain=DOMAIN) entry.add_to_hass(hass) diff --git a/tests/components/kraken/test_sensor.py b/tests/components/kraken/test_sensor.py index 398177070df..fd0a1dc72d1 100644 --- a/tests/components/kraken/test_sensor.py +++ b/tests/components/kraken/test_sensor.py @@ -32,12 +32,15 @@ async def test_sensor( entity_registry_enabled_by_default: None, ) -> None: """Test that sensor has a value.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - return_value=TICKER_INFORMATION_RESPONSE, + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + return_value=TICKER_INFORMATION_RESPONSE, + ), ): entry = MockConfigEntry( domain=DOMAIN, @@ -140,12 +143,15 @@ async def test_sensors_available_after_restart( freezer: FrozenDateTimeFactory, ) -> None: """Test that all sensors are added again after a restart.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - return_value=TICKER_INFORMATION_RESPONSE, + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + return_value=TICKER_INFORMATION_RESPONSE, + ), ): entry = MockConfigEntry( domain=DOMAIN, @@ -179,12 +185,15 @@ async def test_sensors_added_after_config_update( hass: HomeAssistant, freezer: FrozenDateTimeFactory ) -> None: """Test that sensors are added when another tracked asset pair is added.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ), patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - return_value=TICKER_INFORMATION_RESPONSE, + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ), + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + return_value=TICKER_INFORMATION_RESPONSE, + ), ): entry = MockConfigEntry( domain=DOMAIN, @@ -224,13 +233,16 @@ async def test_missing_pair_marks_sensor_unavailable( hass: HomeAssistant, freezer: FrozenDateTimeFactory ) -> None: """Test that a missing tradable asset pair marks the sensor unavailable.""" - with patch( - "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", - return_value=TRADEABLE_ASSET_PAIR_RESPONSE, - ) as tradeable_asset_pairs_mock, patch( - "pykrakenapi.KrakenAPI.get_ticker_information", - return_value=TICKER_INFORMATION_RESPONSE, - ) as ticket_information_mock: + with ( + patch( + "pykrakenapi.KrakenAPI.get_tradable_asset_pairs", + return_value=TRADEABLE_ASSET_PAIR_RESPONSE, + ) as tradeable_asset_pairs_mock, + patch( + "pykrakenapi.KrakenAPI.get_ticker_information", + return_value=TICKER_INFORMATION_RESPONSE, + ) as ticket_information_mock, + ): entry = MockConfigEntry( domain=DOMAIN, options={ diff --git a/tests/components/kulersky/test_config_flow.py b/tests/components/kulersky/test_config_flow.py index 5ff3f51f1ad..9638df360c8 100644 --- a/tests/components/kulersky/test_config_flow.py +++ b/tests/components/kulersky/test_config_flow.py @@ -21,13 +21,16 @@ async def test_flow_success(hass: HomeAssistant) -> None: light = MagicMock(spec=pykulersky.Light) light.address = "AA:BB:CC:11:22:33" light.name = "Bedroom" - with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover", - return_value=[light], - ), patch( - "homeassistant.components.kulersky.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kulersky.config_flow.pykulersky.discover", + return_value=[light], + ), + patch( + "homeassistant.components.kulersky.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -50,13 +53,16 @@ async def test_flow_no_devices_found(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover", - return_value=[], - ), patch( - "homeassistant.components.kulersky.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kulersky.config_flow.pykulersky.discover", + return_value=[], + ), + patch( + "homeassistant.components.kulersky.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -77,13 +83,16 @@ async def test_flow_exceptions_caught(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.kulersky.config_flow.pykulersky.discover", - side_effect=pykulersky.PykulerskyException("TEST"), - ), patch( - "homeassistant.components.kulersky.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.kulersky.config_flow.pykulersky.discover", + side_effect=pykulersky.PykulerskyException("TEST"), + ), + patch( + "homeassistant.components.kulersky.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, diff --git a/tests/components/lacrosse_view/test_config_flow.py b/tests/components/lacrosse_view/test_config_flow.py index b80dab1a7ec..195c004179b 100644 --- a/tests/components/lacrosse_view/test_config_flow.py +++ b/tests/components/lacrosse_view/test_config_flow.py @@ -23,12 +23,15 @@ async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "lacrosse_view.LaCrosse.login", - return_value=True, - ), patch( - "lacrosse_view.LaCrosse.get_locations", - return_value=[Location(id=1, name="Test")], + with ( + patch( + "lacrosse_view.LaCrosse.login", + return_value=True, + ), + patch( + "lacrosse_view.LaCrosse.get_locations", + return_value=[Location(id=1, name="Test")], + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -109,8 +112,9 @@ async def test_form_login_first(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_locations", side_effect=LoginError + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch("lacrosse_view.LaCrosse.get_locations", side_effect=LoginError), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -130,9 +134,12 @@ async def test_form_no_locations(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_locations", - return_value=None, + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_locations", + return_value=None, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -192,12 +199,15 @@ async def test_already_configured_device( assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "lacrosse_view.LaCrosse.login", - return_value=True, - ), patch( - "lacrosse_view.LaCrosse.get_locations", - return_value=[Location(id=1, name="Test")], + with ( + patch( + "lacrosse_view.LaCrosse.login", + return_value=True, + ), + patch( + "lacrosse_view.LaCrosse.get_locations", + return_value=[Location(id=1, name="Test")], + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -257,9 +267,12 @@ async def test_reauth(hass: HomeAssistant) -> None: new_username = "new-username" new_password = "new-password" - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_locations", - return_value=[Location(id=1, name="Test")], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_locations", + return_value=[Location(id=1, name="Test")], + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/lacrosse_view/test_diagnostics.py b/tests/components/lacrosse_view/test_diagnostics.py index 9fef7bf5955..08cef64a935 100644 --- a/tests/components/lacrosse_view/test_diagnostics.py +++ b/tests/components/lacrosse_view/test_diagnostics.py @@ -25,8 +25,9 @@ async def test_entry_diagnostics( ) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_SENSOR] + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch("lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_SENSOR]), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/lacrosse_view/test_init.py b/tests/components/lacrosse_view/test_init.py index 7036371d323..cf11e787ad8 100644 --- a/tests/components/lacrosse_view/test_init.py +++ b/tests/components/lacrosse_view/test_init.py @@ -20,9 +20,12 @@ async def test_unload_entry(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[TEST_SENSOR], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_SENSOR], + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -63,8 +66,9 @@ async def test_http_error(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", side_effect=HTTPError + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch("lacrosse_view.LaCrosse.get_sensors", side_effect=HTTPError), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -80,9 +84,12 @@ async def test_new_token(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True) as login, patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[TEST_SENSOR], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True) as login, + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_SENSOR], + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -93,9 +100,12 @@ async def test_new_token(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> assert len(entries) == 1 assert entries[0].state == ConfigEntryState.LOADED - with patch("lacrosse_view.LaCrosse.login", return_value=True) as login, patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[TEST_SENSOR], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True) as login, + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_SENSOR], + ), ): freezer.tick(timedelta(hours=1)) async_fire_time_changed(hass) @@ -111,9 +121,12 @@ async def test_failed_token( config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True) as login, patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[TEST_SENSOR], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True) as login, + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_SENSOR], + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/lacrosse_view/test_sensor.py b/tests/components/lacrosse_view/test_sensor.py index f9e52850685..b9140e6173f 100644 --- a/tests/components/lacrosse_view/test_sensor.py +++ b/tests/components/lacrosse_view/test_sensor.py @@ -32,8 +32,9 @@ async def test_entities_added(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_SENSOR] + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch("lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_SENSOR]), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -53,8 +54,12 @@ async def test_sensor_permission( config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_NO_PERMISSION_SENSOR] + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_NO_PERMISSION_SENSOR], + ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -74,8 +79,11 @@ async def test_field_not_supported( config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_UNSUPPORTED_SENSOR] + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_sensors", return_value=[TEST_UNSUPPORTED_SENSOR] + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -106,9 +114,12 @@ async def test_field_types( config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[test_input], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[test_input], + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -126,9 +137,12 @@ async def test_no_field(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) - config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[TEST_NO_FIELD_SENSOR], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_NO_FIELD_SENSOR], + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -146,9 +160,12 @@ async def test_field_data_missing(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch("lacrosse_view.LaCrosse.login", return_value=True), patch( - "lacrosse_view.LaCrosse.get_sensors", - return_value=[TEST_MISSING_FIELD_DATA_SENSOR], + with ( + patch("lacrosse_view.LaCrosse.login", return_value=True), + patch( + "lacrosse_view.LaCrosse.get_sensors", + return_value=[TEST_MISSING_FIELD_DATA_SENSOR], + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/lametric/conftest.py b/tests/components/lametric/conftest.py index b321041461a..bd2ae275970 100644 --- a/tests/components/lametric/conftest.py +++ b/tests/components/lametric/conftest.py @@ -76,11 +76,15 @@ def device_fixture() -> str: @pytest.fixture def mock_lametric(request, device_fixture: str) -> Generator[MagicMock, None, None]: """Return a mocked LaMetric TIME client.""" - with patch( - "homeassistant.components.lametric.coordinator.LaMetricDevice", autospec=True - ) as lametric_mock, patch( - "homeassistant.components.lametric.config_flow.LaMetricDevice", - new=lametric_mock, + with ( + patch( + "homeassistant.components.lametric.coordinator.LaMetricDevice", + autospec=True, + ) as lametric_mock, + patch( + "homeassistant.components.lametric.config_flow.LaMetricDevice", + new=lametric_mock, + ), ): lametric = lametric_mock.return_value lametric.api_key = "mock-api-key" diff --git a/tests/components/lastfm/test_config_flow.py b/tests/components/lastfm/test_config_flow.py index 0b5906c5268..93fc9e5a206 100644 --- a/tests/components/lastfm/test_config_flow.py +++ b/tests/components/lastfm/test_config_flow.py @@ -141,9 +141,10 @@ async def test_flow_friends_no_friends( hass: HomeAssistant, default_user_no_friends: MockUser ) -> None: """Test options is empty when user has no friends.""" - with patch( - "pylast.User", return_value=default_user_no_friends - ), patch_setup_entry(): + with ( + patch("pylast.User", return_value=default_user_no_friends), + patch_setup_entry(), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, diff --git a/tests/components/laundrify/conftest.py b/tests/components/laundrify/conftest.py index 18884cdfb7b..91aeebf81ee 100644 --- a/tests/components/laundrify/conftest.py +++ b/tests/components/laundrify/conftest.py @@ -42,11 +42,14 @@ def laundrify_validate_token_fixture(): @pytest.fixture(name="laundrify_api_mock", autouse=True) def laundrify_api_fixture(laundrify_exchange_code, laundrify_validate_token): """Mock valid laundrify API responses.""" - with patch( - "laundrify_aio.LaundrifyAPI.get_account_id", - return_value=VALID_ACCOUNT_ID, - ), patch( - "laundrify_aio.LaundrifyAPI.get_machines", - return_value=json.loads(load_fixture("laundrify/machines.json")), - ) as get_machines_mock: + with ( + patch( + "laundrify_aio.LaundrifyAPI.get_account_id", + return_value=VALID_ACCOUNT_ID, + ), + patch( + "laundrify_aio.LaundrifyAPI.get_machines", + return_value=json.loads(load_fixture("laundrify/machines.json")), + ) as get_machines_mock, + ): yield get_machines_mock diff --git a/tests/components/lcn/test_config_flow.py b/tests/components/lcn/test_config_flow.py index 6580fb35d02..aa1b5086e65 100644 --- a/tests/components/lcn/test_config_flow.py +++ b/tests/components/lcn/test_config_flow.py @@ -36,9 +36,11 @@ IMPORT_DATA = { async def test_step_import(hass: HomeAssistant) -> None: """Test for import step.""" - with patch("pypck.connection.PchkConnectionManager.async_connect"), patch( - "homeassistant.components.lcn.async_setup", return_value=True - ), patch("homeassistant.components.lcn.async_setup_entry", return_value=True): + with ( + patch("pypck.connection.PchkConnectionManager.async_connect"), + patch("homeassistant.components.lcn.async_setup", return_value=True), + patch("homeassistant.components.lcn.async_setup_entry", return_value=True), + ): data = IMPORT_DATA.copy() result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=data diff --git a/tests/components/lcn/test_init.py b/tests/components/lcn/test_init.py index cfa518a9367..292ebc045b2 100644 --- a/tests/components/lcn/test_init.py +++ b/tests/components/lcn/test_init.py @@ -127,9 +127,10 @@ async def test_async_setup_entry_raises_timeout_error( async def test_async_setup_from_configuration_yaml(hass: HomeAssistant) -> None: """Test a successful setup using data from configuration.yaml.""" - with patch( - "pypck.connection.PchkConnectionManager", MockPchkConnectionManager - ), patch("homeassistant.components.lcn.async_setup_entry") as async_setup_entry: + with ( + patch("pypck.connection.PchkConnectionManager", MockPchkConnectionManager), + patch("homeassistant.components.lcn.async_setup_entry") as async_setup_entry, + ): await setup_component(hass) assert async_setup_entry.await_count == 2 diff --git a/tests/components/ld2410_ble/test_config_flow.py b/tests/components/ld2410_ble/test_config_flow.py index 1c87b72330d..74e7e8a2c8e 100644 --- a/tests/components/ld2410_ble/test_config_flow.py +++ b/tests/components/ld2410_ble/test_config_flow.py @@ -28,12 +28,15 @@ async def test_user_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", - ), patch( - "homeassistant.components.ld2410_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", + ), + patch( + "homeassistant.components.ld2410_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -114,12 +117,15 @@ async def test_user_step_cannot_connect(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", - ), patch( - "homeassistant.components.ld2410_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", + ), + patch( + "homeassistant.components.ld2410_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -166,12 +172,15 @@ async def test_user_step_unknown_exception(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "unknown"} - with patch( - "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", - ), patch( - "homeassistant.components.ld2410_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", + ), + patch( + "homeassistant.components.ld2410_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -200,12 +209,15 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", - ), patch( - "homeassistant.components.ld2410_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.ld2410_ble.config_flow.LD2410BLE.initialise", + ), + patch( + "homeassistant.components.ld2410_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/led_ble/test_config_flow.py b/tests/components/led_ble/test_config_flow.py index d0274e9a6dc..5ceda954ba8 100644 --- a/tests/components/led_ble/test_config_flow.py +++ b/tests/components/led_ble/test_config_flow.py @@ -32,12 +32,15 @@ async def test_user_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.led_ble.config_flow.LEDBLE.update", - ), patch( - "homeassistant.components.led_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.led_ble.config_flow.LEDBLE.update", + ), + patch( + "homeassistant.components.led_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -118,12 +121,15 @@ async def test_user_step_cannot_connect(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.led_ble.config_flow.LEDBLE.update", - ), patch( - "homeassistant.components.led_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.led_ble.config_flow.LEDBLE.update", + ), + patch( + "homeassistant.components.led_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -170,12 +176,15 @@ async def test_user_step_unknown_exception(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "unknown"} - with patch( - "homeassistant.components.led_ble.config_flow.LEDBLE.update", - ), patch( - "homeassistant.components.led_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.led_ble.config_flow.LEDBLE.update", + ), + patch( + "homeassistant.components.led_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -204,12 +213,15 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.led_ble.config_flow.LEDBLE.update", - ), patch( - "homeassistant.components.led_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.led_ble.config_flow.LEDBLE.update", + ), + patch( + "homeassistant.components.led_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/lg_soundbar/test_config_flow.py b/tests/components/lg_soundbar/test_config_flow.py index e41466f3204..4fab919c555 100644 --- a/tests/components/lg_soundbar/test_config_flow.py +++ b/tests/components/lg_soundbar/test_config_flow.py @@ -61,11 +61,14 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, @@ -99,11 +102,14 @@ async def test_form_mac_info_response_empty(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, @@ -142,11 +148,14 @@ async def test_form_uuid_present_in_both_functions_uuid_q_empty( assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, @@ -187,14 +196,18 @@ async def test_form_uuid_present_in_both_functions_uuid_q_not_empty( assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", - new=0.1, - ), patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", + new=0.1, + ), + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, @@ -230,11 +243,14 @@ async def test_form_uuid_missing_from_mac_info(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, @@ -269,14 +285,18 @@ async def test_form_uuid_not_provided_by_api(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", - new=0.1, - ), patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", + new=0.1, + ), + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, @@ -310,14 +330,18 @@ async def test_form_both_queues_empty(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", - new=0.1, - ), patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal, patch( - "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", + new=0.1, + ), + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + patch( + "homeassistant.components.lg_soundbar.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): setup_mock_temescal(hass=hass, mock_temescal=mock_temescal) result2 = await hass.config_entries.flow.async_configure( @@ -352,12 +376,15 @@ async def test_no_uuid_host_already_configured(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", - new=0.1, - ), patch( - "homeassistant.components.lg_soundbar.config_flow.temescal" - ) as mock_temescal: + with ( + patch( + "homeassistant.components.lg_soundbar.config_flow.QUEUE_TIMEOUT", + new=0.1, + ), + patch( + "homeassistant.components.lg_soundbar.config_flow.temescal" + ) as mock_temescal, + ): setup_mock_temescal( hass=hass, mock_temescal=mock_temescal, info={"s_user_name": "name"} ) diff --git a/tests/components/lifx/__init__.py b/tests/components/lifx/__init__.py index dc730565cc7..505d212a352 100644 --- a/tests/components/lifx/__init__.py +++ b/tests/components/lifx/__init__.py @@ -243,8 +243,12 @@ def _patch_discovery(device: Light | None = None, no_device: bool = False): @contextmanager def _patcher(): - with patch.object(discovery, "DEFAULT_TIMEOUT", 0), patch( - "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery + with ( + patch.object(discovery, "DEFAULT_TIMEOUT", 0), + patch( + "homeassistant.components.lifx.discovery.LifxDiscovery", + MockLifxDiscovery, + ), ): yield diff --git a/tests/components/lifx/conftest.py b/tests/components/lifx/conftest.py index 15fe8898a5f..c126ca20ecd 100644 --- a/tests/components/lifx/conftest.py +++ b/tests/components/lifx/conftest.py @@ -49,10 +49,11 @@ def lifx_mock_get_source_ip(mock_get_source_ip): @pytest.fixture(autouse=True) def lifx_no_wait_for_timeouts(): """Avoid waiting for timeouts in tests.""" - with patch.object(util, "OVERALL_TIMEOUT", 0), patch.object( - config_flow, "OVERALL_TIMEOUT", 0 - ), patch.object(coordinator, "OVERALL_TIMEOUT", 0), patch.object( - coordinator, "MAX_UPDATE_TIME", 0 + with ( + patch.object(util, "OVERALL_TIMEOUT", 0), + patch.object(config_flow, "OVERALL_TIMEOUT", 0), + patch.object(coordinator, "OVERALL_TIMEOUT", 0), + patch.object(coordinator, "MAX_UPDATE_TIME", 0), ): yield diff --git a/tests/components/lifx/test_binary_sensor.py b/tests/components/lifx/test_binary_sensor.py index fa57591e305..9221ac79149 100644 --- a/tests/components/lifx/test_binary_sensor.py +++ b/tests/components/lifx/test_binary_sensor.py @@ -47,9 +47,11 @@ async def test_hev_cycle_state( ) config_entry.add_to_hass(hass) bulb = _mocked_clean_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_button.py b/tests/components/lifx/test_button.py index 4b0a33e8d0c..b2adf4f4c15 100644 --- a/tests/components/lifx/test_button.py +++ b/tests/components/lifx/test_button.py @@ -44,9 +44,11 @@ async def test_button_restart( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -77,9 +79,11 @@ async def test_button_identify( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_config_flow.py b/tests/components/lifx/test_config_flow.py index 78e852e2cab..0a0c26da424 100644 --- a/tests/components/lifx/test_config_flow.py +++ b/tests/components/lifx/test_config_flow.py @@ -65,11 +65,12 @@ async def test_discovery(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_config_flow_try_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_config_flow_try_connect(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: SERIAL}, @@ -168,9 +169,11 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_config_flow_try_connect(), patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_config_flow_try_connect(), + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: SERIAL} ) @@ -205,8 +208,9 @@ async def test_discovery_no_device(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with _patch_discovery(no_device=True), _patch_config_flow_try_connect( - no_device=True + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(no_device=True), ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -225,8 +229,9 @@ async def test_manual(hass: HomeAssistant) -> None: assert not result["errors"] # Cannot connect (timeout) - with _patch_discovery(no_device=True), _patch_config_flow_try_connect( - no_device=True + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(no_device=True), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -238,9 +243,12 @@ async def test_manual(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} # Success - with _patch_discovery(), _patch_config_flow_try_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ), patch(f"{MODULE}.async_setup_entry", return_value=True): + with ( + _patch_discovery(), + _patch_config_flow_try_connect(), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), + ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -255,8 +263,9 @@ async def test_manual(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with _patch_discovery(no_device=True), _patch_config_flow_try_connect( - no_device=True + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(no_device=True), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -291,9 +300,12 @@ async def test_manual_dns_error(hass: HomeAssistant) -> None: """Mock teardown.""" # Cannot connect due to dns error - with _patch_discovery(no_device=True), patch( - "homeassistant.components.lifx.config_flow.LIFXConnection", - MockLifxConnectonDnsError, + with ( + _patch_discovery(no_device=True), + patch( + "homeassistant.components.lifx.config_flow.LIFXConnection", + MockLifxConnectonDnsError, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "does.not.resolve"} @@ -314,9 +326,12 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with _patch_discovery(no_device=True), _patch_config_flow_try_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ), patch(f"{MODULE}.async_setup_entry", return_value=True): + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -365,8 +380,9 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: assert result3["type"] == FlowResultType.ABORT assert result3["reason"] == "already_in_progress" - with _patch_discovery(no_device=True), _patch_config_flow_try_connect( - no_device=True + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(no_device=True), ): result3 = await hass.config_entries.flow.async_init( DOMAIN, @@ -421,11 +437,14 @@ async def test_discovered_by_dhcp_or_discovery( assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_config_flow_try_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_config_flow_try_connect(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -469,8 +488,9 @@ async def test_discovered_by_dhcp_or_discovery_failed_to_get_device( ) -> None: """Test we abort if we cannot get the unique id when discovered from dhcp.""" - with _patch_discovery(no_device=True), _patch_config_flow_try_connect( - no_device=True + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(no_device=True), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data @@ -532,8 +552,9 @@ async def test_refuse_relays(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with _patch_discovery(device=_mocked_relay()), _patch_config_flow_try_connect( - device=_mocked_relay() + with ( + _patch_discovery(device=_mocked_relay()), + _patch_config_flow_try_connect(device=_mocked_relay()), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -572,9 +593,11 @@ async def test_suggested_area( bulb.group = None bulb.get_group = MockLifxCommandGetGroup(bulb, lifx_group="My LIFX Group") - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_diagnostics.py b/tests/components/lifx/test_diagnostics.py index b0fa2bf4d6a..e3588dd3ed1 100644 --- a/tests/components/lifx/test_diagnostics.py +++ b/tests/components/lifx/test_diagnostics.py @@ -35,9 +35,11 @@ async def test_bulb_diagnostics( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -82,9 +84,11 @@ async def test_clean_bulb_diagnostics( ) config_entry.add_to_hass(hass) bulb = _mocked_clean_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -134,9 +138,11 @@ async def test_infrared_bulb_diagnostics( ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -193,9 +199,11 @@ async def test_legacy_multizone_bulb_diagnostics( (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -305,9 +313,11 @@ async def test_multizone_bulb_diagnostics( (46420, 65535, 65535, 3500), (46420, 65535, 65535, 3500), ] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_init.py b/tests/components/lifx/test_init.py index 01003e6e7f1..3d0d127bf5c 100644 --- a/tests/components/lifx/test_init.py +++ b/tests/components/lifx/test_init.py @@ -50,10 +50,12 @@ async def test_configuring_lifx_causes_discovery(hass: HomeAssistant) -> None: def cleanup(self): """Mock cleanup.""" - with _patch_config_flow_try_connect(), patch.object( - discovery, "DEFAULT_TIMEOUT", 0 - ), patch( - "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery + with ( + _patch_config_flow_try_connect(), + patch.object(discovery, "DEFAULT_TIMEOUT", 0), + patch( + "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery + ), ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -97,9 +99,11 @@ async def test_config_entry_retry(hass: HomeAssistant) -> None: domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL ) already_migrated_config_entry.add_to_hass(hass) - with _patch_discovery(no_device=True), _patch_config_flow_try_connect( - no_device=True - ), _patch_device(no_device=True): + with ( + _patch_discovery(no_device=True), + _patch_config_flow_try_connect(no_device=True), + _patch_device(no_device=True), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY @@ -145,9 +149,12 @@ async def test_dns_error_at_startup(hass: HomeAssistant) -> None: """Mock teardown.""" # Cannot connect due to dns error - with _patch_discovery(device=bulb), patch( - "homeassistant.components.lifx.LIFXConnection", - MockLifxConnectonDnsError, + with ( + _patch_discovery(device=bulb), + patch( + "homeassistant.components.lifx.LIFXConnection", + MockLifxConnectonDnsError, + ), ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_light.py b/tests/components/lifx/test_light.py index 452112d2235..56630053cc0 100644 --- a/tests/components/lifx/test_light.py +++ b/tests/components/lifx/test_light.py @@ -92,9 +92,11 @@ async def test_light_unique_id( ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -118,9 +120,11 @@ async def test_light_unique_id_new_firmware( ) already_migrated_config_entry.add_to_hass(hass) bulb = _mocked_bulb_new_firmware() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -141,9 +145,11 @@ async def test_light_strip(hass: HomeAssistant) -> None: bulb = _mocked_light_strip() bulb.power_level = 65535 bulb.color = [65535, 65535, 65535, 65535] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -438,9 +444,11 @@ async def test_extended_multizone_messages(hass: HomeAssistant) -> None: bulb.color = [65535, 65535, 65535, 3500] bulb.color_zones = [(65535, 65535, 65535, 3500)] * 8 bulb.zones_count = 8 - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -673,9 +681,11 @@ async def test_matrix_flame_morph_effects(hass: HomeAssistant) -> None: bulb = _mocked_tile() bulb.power_level = 0 bulb.color = [65535, 65535, 65535, 65535] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -815,9 +825,11 @@ async def test_lightstrip_move_effect(hass: HomeAssistant) -> None: bulb.product = 38 bulb.power_level = 0 bulb.color = [65535, 65535, 65535, 65535] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -912,9 +924,11 @@ async def test_color_light_with_temp( bulb = _mocked_bulb() bulb.power_level = 65535 bulb.color = [65535, 65535, 65535, 65535] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1073,9 +1087,11 @@ async def test_white_bulb(hass: HomeAssistant) -> None: bulb = _mocked_white_bulb() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1254,9 +1270,11 @@ async def test_brightness_bulb(hass: HomeAssistant) -> None: bulb = _mocked_brightness_bulb() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1301,9 +1319,11 @@ async def test_transitions_brightness_only(hass: HomeAssistant) -> None: bulb = _mocked_brightness_bulb() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1368,9 +1388,11 @@ async def test_transitions_color_bulb(hass: HomeAssistant) -> None: bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1479,9 +1501,11 @@ async def test_lifx_set_state_color(hass: HomeAssistant) -> None: bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 2700] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1557,9 +1581,11 @@ async def test_lifx_set_state_kelvin(hass: HomeAssistant) -> None: bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1613,9 +1639,11 @@ async def test_infrared_color_bulb(hass: HomeAssistant) -> None: bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1654,9 +1682,11 @@ async def test_color_bulb_is_actually_off(hass: HomeAssistant) -> None: bulb = _mocked_bulb_new_firmware() bulb.power_level = 65535 bulb.color = [32000, None, 32000, 6000] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1705,9 +1735,11 @@ async def test_clean_bulb(hass: HomeAssistant) -> None: bulb = _mocked_clean_bulb() bulb.power_level = 0 bulb.hev_cycle = {"duration": 7200, "remaining": 0, "last_power": False} - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1735,9 +1767,11 @@ async def test_set_hev_cycle_state_fails_for_color_bulb(hass: HomeAssistant) -> config_entry.add_to_hass(hass) bulb = _mocked_bulb() bulb.power_level = 0 - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -1766,9 +1800,11 @@ async def test_light_strip_zones_not_populated_yet(hass: HomeAssistant) -> None: bulb.color = [65535, 65535, 65535, 65535] assert bulb.get_color_zones.calls == [] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_migration.py b/tests/components/lifx/test_migration.py index 3621eb165fa..0604ee1c8a7 100644 --- a/tests/components/lifx/test_migration.py +++ b/tests/components/lifx/test_migration.py @@ -130,10 +130,13 @@ async def test_discovery_is_more_frequent_during_migration( def cleanup(self): """Mock cleanup.""" - with _patch_device(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), patch.object(discovery, "DEFAULT_TIMEOUT", 0), patch( - "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery + with ( + _patch_device(device=bulb), + _patch_config_flow_try_connect(device=bulb), + patch.object(discovery, "DEFAULT_TIMEOUT", 0), + patch( + "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery + ), ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_select.py b/tests/components/lifx/test_select.py index c639dd441e7..a6338566ed4 100644 --- a/tests/components/lifx/test_select.py +++ b/tests/components/lifx/test_select.py @@ -43,9 +43,11 @@ async def test_theme_select( bulb.product = 38 bulb.power_level = 0 bulb.color = [0, 0, 65535, 3500] - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -79,9 +81,11 @@ async def test_infrared_brightness( ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -109,9 +113,11 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -149,9 +155,11 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -189,9 +197,11 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -229,9 +239,11 @@ async def test_disable_infrared(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -269,9 +281,11 @@ async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) bulb = _mocked_infrared_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/lifx/test_sensor.py b/tests/components/lifx/test_sensor.py index 5a94963ca18..b7ff563bdbc 100644 --- a/tests/components/lifx/test_sensor.py +++ b/tests/components/lifx/test_sensor.py @@ -45,9 +45,11 @@ async def test_rssi_sensor( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -63,9 +65,11 @@ async def test_rssi_sensor( entry.entity_id, disabled_by=None ) - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() @@ -97,9 +101,11 @@ async def test_rssi_sensor_old_firmware( ) config_entry.add_to_hass(hass) bulb = _mocked_bulb_old_firmware() - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await hass.async_block_till_done() @@ -115,9 +121,11 @@ async def test_rssi_sensor_old_firmware( entry.entity_id, disabled_by=None ) - with _patch_discovery(device=bulb), _patch_config_flow_try_connect( - device=bulb - ), _patch_device(device=bulb): + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): await hass.config_entries.async_reload(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/linear_garage_door/test_config_flow.py b/tests/components/linear_garage_door/test_config_flow.py index 5d1ed36ecb7..1851b61fc15 100644 --- a/tests/components/linear_garage_door/test_config_flow.py +++ b/tests/components/linear_garage_door/test_config_flow.py @@ -20,18 +20,23 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", - return_value=[{"id": "test-site-id", "name": "test-site-name"}], - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, - ), patch( - "uuid.uuid4", - return_value="test-uuid", + with ( + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", + return_value=[{"id": "test-site-id", "name": "test-site-name"}], + ), + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.close", + return_value=None, + ), + patch( + "uuid.uuid4", + return_value="test-uuid", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -84,18 +89,23 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result1["type"] == FlowResultType.FORM assert result1["step_id"] == "user" - with patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", - return_value=[{"id": "test-site-id", "name": "test-site-name"}], - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, - ), patch( - "uuid.uuid4", - return_value="test-uuid", + with ( + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", + return_value=[{"id": "test-site-id", "name": "test-site-name"}], + ), + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.close", + return_value=None, + ), + patch( + "uuid.uuid4", + return_value="test-uuid", + ), ): result2 = await hass.config_entries.flow.async_configure( result1["flow_id"], @@ -125,12 +135,15 @@ async def test_form_invalid_login(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", - side_effect=InvalidLoginError, - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, + with ( + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.login", + side_effect=InvalidLoginError, + ), + patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.close", + return_value=None, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/linear_garage_door/test_cover.py b/tests/components/linear_garage_door/test_cover.py index 428411d39e0..e692d1867dc 100644 --- a/tests/components/linear_garage_door/test_cover.py +++ b/tests/components/linear_garage_door/test_cover.py @@ -56,15 +56,19 @@ async def test_open_cover(hass: HomeAssistant) -> None: assert operate_device.call_count == 0 - with patch( - "homeassistant.components.linear_garage_door.cover.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.operate_device", - return_value=None, - ) as operate_device, patch( - "homeassistant.components.linear_garage_door.cover.Linear.close", - return_value=True, + with ( + patch( + "homeassistant.components.linear_garage_door.cover.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.operate_device", + return_value=None, + ) as operate_device, + patch( + "homeassistant.components.linear_garage_door.cover.Linear.close", + return_value=True, + ), ): await hass.services.async_call( COVER_DOMAIN, @@ -74,38 +78,51 @@ async def test_open_cover(hass: HomeAssistant) -> None: ) assert operate_device.call_count == 1 - with patch( - "homeassistant.components.linear_garage_door.cover.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.get_devices", - return_value=[ - {"id": "test1", "name": "Test Garage 1", "subdevices": ["GDO", "Light"]}, - {"id": "test2", "name": "Test Garage 2", "subdevices": ["GDO", "Light"]}, - ], - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.get_device_state", - side_effect=lambda id: { - "test1": { - "GDO": {"Open_B": "true", "Open_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - "test2": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test3": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test4": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - }[id], - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.close", - return_value=True, + with ( + patch( + "homeassistant.components.linear_garage_door.cover.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.get_devices", + return_value=[ + { + "id": "test1", + "name": "Test Garage 1", + "subdevices": ["GDO", "Light"], + }, + { + "id": "test2", + "name": "Test Garage 2", + "subdevices": ["GDO", "Light"], + }, + ], + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.get_device_state", + side_effect=lambda id: { + "test1": { + "GDO": {"Open_B": "true", "Open_P": "100"}, + "Light": {"On_B": "true", "On_P": "100"}, + }, + "test2": { + "GDO": {"Open_B": "false", "Opening_P": "0"}, + "Light": {"On_B": "false", "On_P": "0"}, + }, + "test3": { + "GDO": {"Open_B": "false", "Opening_P": "0"}, + "Light": {"On_B": "false", "On_P": "0"}, + }, + "test4": { + "GDO": {"Open_B": "true", "Opening_P": "100"}, + "Light": {"On_B": "true", "On_P": "100"}, + }, + }[id], + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.close", + return_value=True, + ), ): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=60)) await hass.async_block_till_done() @@ -130,15 +147,19 @@ async def test_close_cover(hass: HomeAssistant) -> None: assert operate_device.call_count == 0 - with patch( - "homeassistant.components.linear_garage_door.cover.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.operate_device", - return_value=None, - ) as operate_device, patch( - "homeassistant.components.linear_garage_door.cover.Linear.close", - return_value=True, + with ( + patch( + "homeassistant.components.linear_garage_door.cover.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.operate_device", + return_value=None, + ) as operate_device, + patch( + "homeassistant.components.linear_garage_door.cover.Linear.close", + return_value=True, + ), ): await hass.services.async_call( COVER_DOMAIN, @@ -148,38 +169,51 @@ async def test_close_cover(hass: HomeAssistant) -> None: ) assert operate_device.call_count == 1 - with patch( - "homeassistant.components.linear_garage_door.cover.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.get_devices", - return_value=[ - {"id": "test1", "name": "Test Garage 1", "subdevices": ["GDO", "Light"]}, - {"id": "test2", "name": "Test Garage 2", "subdevices": ["GDO", "Light"]}, - ], - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.get_device_state", - side_effect=lambda id: { - "test1": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - "test2": { - "GDO": {"Open_B": "false", "Open_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test3": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test4": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - }[id], - ), patch( - "homeassistant.components.linear_garage_door.cover.Linear.close", - return_value=True, + with ( + patch( + "homeassistant.components.linear_garage_door.cover.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.get_devices", + return_value=[ + { + "id": "test1", + "name": "Test Garage 1", + "subdevices": ["GDO", "Light"], + }, + { + "id": "test2", + "name": "Test Garage 2", + "subdevices": ["GDO", "Light"], + }, + ], + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.get_device_state", + side_effect=lambda id: { + "test1": { + "GDO": {"Open_B": "true", "Opening_P": "100"}, + "Light": {"On_B": "true", "On_P": "100"}, + }, + "test2": { + "GDO": {"Open_B": "false", "Open_P": "0"}, + "Light": {"On_B": "false", "On_P": "0"}, + }, + "test3": { + "GDO": {"Open_B": "false", "Opening_P": "0"}, + "Light": {"On_B": "false", "On_P": "0"}, + }, + "test4": { + "GDO": {"Open_B": "true", "Opening_P": "100"}, + "Light": {"On_B": "true", "On_P": "100"}, + }, + }[id], + ), + patch( + "homeassistant.components.linear_garage_door.cover.Linear.close", + return_value=True, + ), ): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=60)) await hass.async_block_till_done() diff --git a/tests/components/linear_garage_door/test_init.py b/tests/components/linear_garage_door/test_init.py index e8d76770050..32ebda7e125 100644 --- a/tests/components/linear_garage_door/test_init.py +++ b/tests/components/linear_garage_door/test_init.py @@ -22,23 +22,28 @@ async def test_unload_entry(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", - return_value=[ - {"id": "test", "name": "Test Garage", "subdevices": ["GDO", "Light"]} - ], - ), patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", - return_value={ - "GDO": {"Open_B": "true", "Open_P": "100"}, - "Light": {"On_B": "true", "On_P": "10"}, - }, - ), patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, + with ( + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", + return_value=[ + {"id": "test", "name": "Test Garage", "subdevices": ["GDO", "Light"]} + ], + ), + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", + return_value={ + "GDO": {"Open_B": "true", "Open_P": "100"}, + "Light": {"On_B": "true", "On_P": "10"}, + }, + ), + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.close", + return_value=True, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/linear_garage_door/util.py b/tests/components/linear_garage_door/util.py index d8348b9bb64..1a849ae2348 100644 --- a/tests/components/linear_garage_door/util.py +++ b/tests/components/linear_garage_door/util.py @@ -21,40 +21,61 @@ async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", - return_value=[ - {"id": "test1", "name": "Test Garage 1", "subdevices": ["GDO", "Light"]}, - {"id": "test2", "name": "Test Garage 2", "subdevices": ["GDO", "Light"]}, - {"id": "test3", "name": "Test Garage 3", "subdevices": ["GDO", "Light"]}, - {"id": "test4", "name": "Test Garage 4", "subdevices": ["GDO", "Light"]}, - ], - ), patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", - side_effect=lambda id: { - "test1": { - "GDO": {"Open_B": "true", "Open_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - "test2": { - "GDO": {"Open_B": "false", "Open_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test3": { - "GDO": {"Open_B": "false", "Opening_P": "0"}, - "Light": {"On_B": "false", "On_P": "0"}, - }, - "test4": { - "GDO": {"Open_B": "true", "Opening_P": "100"}, - "Light": {"On_B": "true", "On_P": "100"}, - }, - }[id], - ), patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.close", - return_value=True, + with ( + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.login", + return_value=True, + ), + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.get_devices", + return_value=[ + { + "id": "test1", + "name": "Test Garage 1", + "subdevices": ["GDO", "Light"], + }, + { + "id": "test2", + "name": "Test Garage 2", + "subdevices": ["GDO", "Light"], + }, + { + "id": "test3", + "name": "Test Garage 3", + "subdevices": ["GDO", "Light"], + }, + { + "id": "test4", + "name": "Test Garage 4", + "subdevices": ["GDO", "Light"], + }, + ], + ), + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.get_device_state", + side_effect=lambda id: { + "test1": { + "GDO": {"Open_B": "true", "Open_P": "100"}, + "Light": {"On_B": "true", "On_P": "100"}, + }, + "test2": { + "GDO": {"Open_B": "false", "Open_P": "0"}, + "Light": {"On_B": "false", "On_P": "0"}, + }, + "test3": { + "GDO": {"Open_B": "false", "Opening_P": "0"}, + "Light": {"On_B": "false", "On_P": "0"}, + }, + "test4": { + "GDO": {"Open_B": "true", "Opening_P": "100"}, + "Light": {"On_B": "true", "On_P": "100"}, + }, + }[id], + ), + patch( + "homeassistant.components.linear_garage_door.coordinator.Linear.close", + return_value=True, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/litterrobot/conftest.py b/tests/components/litterrobot/conftest.py index d8d6ecd171d..181e4fc1a90 100644 --- a/tests/components/litterrobot/conftest.py +++ b/tests/components/litterrobot/conftest.py @@ -124,11 +124,15 @@ async def setup_integration( ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.litterrobot.hub.Account", return_value=mock_account - ), patch( - "homeassistant.components.litterrobot.PLATFORMS_BY_TYPE", - {Robot: (platform_domain,)} if platform_domain else {}, + with ( + patch( + "homeassistant.components.litterrobot.hub.Account", + return_value=mock_account, + ), + patch( + "homeassistant.components.litterrobot.PLATFORMS_BY_TYPE", + {Robot: (platform_domain,)} if platform_domain else {}, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/litterrobot/test_config_flow.py b/tests/components/litterrobot/test_config_flow.py index e2c5290a946..d516a3f14a2 100644 --- a/tests/components/litterrobot/test_config_flow.py +++ b/tests/components/litterrobot/test_config_flow.py @@ -25,13 +25,16 @@ async def test_form(hass: HomeAssistant, mock_account) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.litterrobot.config_flow.Account.connect", - return_value=mock_account, - ), patch( - "homeassistant.components.litterrobot.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.litterrobot.config_flow.Account.connect", + return_value=mock_account, + ), + patch( + "homeassistant.components.litterrobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG[DOMAIN] ) @@ -134,13 +137,16 @@ async def test_step_reauth(hass: HomeAssistant, mock_account: Account) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "reauth_confirm" - with patch( - "homeassistant.components.litterrobot.config_flow.Account.connect", - return_value=mock_account, - ), patch( - "homeassistant.components.litterrobot.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.litterrobot.config_flow.Account.connect", + return_value=mock_account, + ), + patch( + "homeassistant.components.litterrobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_PASSWORD: CONFIG[litterrobot.DOMAIN][CONF_PASSWORD]}, @@ -183,13 +189,16 @@ async def test_step_reauth_failed(hass: HomeAssistant, mock_account: Account) -> assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": "invalid_auth"} - with patch( - "homeassistant.components.litterrobot.config_flow.Account.connect", - return_value=mock_account, - ), patch( - "homeassistant.components.litterrobot.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.litterrobot.config_flow.Account.connect", + return_value=mock_account, + ), + patch( + "homeassistant.components.litterrobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_PASSWORD: CONFIG[litterrobot.DOMAIN][CONF_PASSWORD]}, diff --git a/tests/components/local_file/test_camera.py b/tests/components/local_file/test_camera.py index 38d6bdd84cc..4455d47469c 100644 --- a/tests/components/local_file/test_camera.py +++ b/tests/components/local_file/test_camera.py @@ -16,11 +16,13 @@ async def test_loading_file( hass: HomeAssistant, hass_client: ClientSessionGenerator ) -> None: """Test that it loads image from disk.""" - with mock.patch("os.path.isfile", mock.Mock(return_value=True)), mock.patch( - "os.access", mock.Mock(return_value=True) - ), mock.patch( - "homeassistant.components.local_file.camera.mimetypes.guess_type", - mock.Mock(return_value=(None, None)), + with ( + mock.patch("os.path.isfile", mock.Mock(return_value=True)), + mock.patch("os.access", mock.Mock(return_value=True)), + mock.patch( + "homeassistant.components.local_file.camera.mimetypes.guess_type", + mock.Mock(return_value=(None, None)), + ), ): await async_setup_component( hass, @@ -52,8 +54,9 @@ async def test_file_not_readable( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test a warning is shown setup when file is not readable.""" - with mock.patch("os.path.isfile", mock.Mock(return_value=True)), mock.patch( - "os.access", mock.Mock(return_value=False) + with ( + mock.patch("os.path.isfile", mock.Mock(return_value=True)), + mock.patch("os.access", mock.Mock(return_value=False)), ): await async_setup_component( hass, @@ -142,11 +145,13 @@ async def test_camera_content_type( async def test_update_file_path(hass: HomeAssistant) -> None: """Test update_file_path service.""" # Setup platform - with mock.patch("os.path.isfile", mock.Mock(return_value=True)), mock.patch( - "os.access", mock.Mock(return_value=True) - ), mock.patch( - "homeassistant.components.local_file.camera.mimetypes.guess_type", - mock.Mock(return_value=(None, None)), + with ( + mock.patch("os.path.isfile", mock.Mock(return_value=True)), + mock.patch("os.access", mock.Mock(return_value=True)), + mock.patch( + "homeassistant.components.local_file.camera.mimetypes.guess_type", + mock.Mock(return_value=(None, None)), + ), ): camera_1 = {"platform": "local_file", "file_path": "mock/path.jpg"} camera_2 = { diff --git a/tests/components/logbook/test_websocket_api.py b/tests/components/logbook/test_websocket_api.py index f3689c379c5..1be0e5bd9af 100644 --- a/tests/components/logbook/test_websocket_api.py +++ b/tests/components/logbook/test_websocket_api.py @@ -2400,8 +2400,9 @@ async def test_stream_consumer_stop_processing( after_ws_created_listeners = hass.bus.async_listeners() - with patch.object(websocket_api, "MAX_PENDING_LOGBOOK_EVENTS", 5), patch.object( - websocket_api, "_async_events_consumer" + with ( + patch.object(websocket_api, "MAX_PENDING_LOGBOOK_EVENTS", 5), + patch.object(websocket_api, "_async_events_consumer"), ): await websocket_client.send_json( { diff --git a/tests/components/logger/test_websocket_api.py b/tests/components/logger/test_websocket_api.py index 15252ad2661..c2fcc7f208e 100644 --- a/tests/components/logger/test_websocket_api.py +++ b/tests/components/logger/test_websocket_api.py @@ -102,12 +102,15 @@ async def test_custom_integration_log_level( }, ) - with patch( - "homeassistant.components.logger.helpers.async_get_integration", - return_value=integration, - ), patch( - "homeassistant.components.logger.websocket_api.async_get_integration", - return_value=integration, + with ( + patch( + "homeassistant.components.logger.helpers.async_get_integration", + return_value=integration, + ), + patch( + "homeassistant.components.logger.websocket_api.async_get_integration", + return_value=integration, + ), ): await websocket_client.send_json( { diff --git a/tests/components/lookin/test_config_flow.py b/tests/components/lookin/test_config_flow.py index d489cee8101..18cbe33db3a 100644 --- a/tests/components/lookin/test_config_flow.py +++ b/tests/components/lookin/test_config_flow.py @@ -35,9 +35,10 @@ async def test_manual_setup(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with _patch_get_info(), patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_get_info(), + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -124,9 +125,12 @@ async def test_discovered_zeroconf(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_get_info(), patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_get_info(), + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -139,9 +143,12 @@ async def test_discovered_zeroconf(hass: HomeAssistant) -> None: zc_data_new_ip = dataclasses.replace(ZEROCONF_DATA) zc_data_new_ip.ip_address = ip_address("127.0.0.2") - with _patch_get_info(), patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_get_info(), + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, diff --git a/tests/components/loqed/conftest.py b/tests/components/loqed/conftest.py index 616c0cb0552..b4265873457 100644 --- a/tests/components/loqed/conftest.py +++ b/tests/components/loqed/conftest.py @@ -88,8 +88,11 @@ async def integration_fixture( lock_status = json.loads(load_fixture("loqed/status_ok.json")) - with patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status + with ( + patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status + ), ): await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/loqed/test_config_flow.py b/tests/components/loqed/test_config_flow.py index 87c531b67dc..ed18f0ae40e 100644 --- a/tests/components/loqed/test_config_flow.py +++ b/tests/components/loqed/test_config_flow.py @@ -49,17 +49,23 @@ async def test_create_entry_zeroconf(hass: HomeAssistant) -> None: webhook_id = "Webhook_ID" all_locks_response = json.loads(load_fixture("loqed/get_all_locks.json")) - with patch( - "loqedAPI.cloud_loqed.LoqedCloudAPI.async_get_locks", - return_value=all_locks_response, - ), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock", - return_value=mock_lock, - ), patch( - "homeassistant.components.loqed.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.webhook.async_generate_id", return_value=webhook_id + with ( + patch( + "loqedAPI.cloud_loqed.LoqedCloudAPI.async_get_locks", + return_value=all_locks_response, + ), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock", + return_value=mock_lock, + ), + patch( + "homeassistant.components.loqed.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.webhook.async_generate_id", + return_value=webhook_id, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -104,19 +110,26 @@ async def test_create_entry_user( all_locks_response = json.loads(load_fixture("loqed/get_all_locks.json")) found_lock = all_locks_response["data"][0] - with patch( - "loqedAPI.cloud_loqed.LoqedCloudAPI.async_get_locks", - return_value=all_locks_response, - ), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock", - return_value=mock_lock, - ), patch( - "homeassistant.components.loqed.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.webhook.async_generate_id", return_value=webhook_id - ), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_result + with ( + patch( + "loqedAPI.cloud_loqed.LoqedCloudAPI.async_get_locks", + return_value=all_locks_response, + ), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock", + return_value=mock_lock, + ), + patch( + "homeassistant.components.loqed.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.webhook.async_generate_id", + return_value=webhook_id, + ), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_result + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -208,10 +221,15 @@ async def test_cannot_connect_when_lock_not_reachable( all_locks_response = json.loads(load_fixture("loqed/get_all_locks.json")) - with patch( - "loqedAPI.cloud_loqed.LoqedCloudAPI.async_get_locks", - return_value=all_locks_response, - ), patch("loqedAPI.loqed.LoqedAPI.async_get_lock", side_effect=aiohttp.ClientError): + with ( + patch( + "loqedAPI.cloud_loqed.LoqedCloudAPI.async_get_locks", + return_value=all_locks_response, + ), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock", side_effect=aiohttp.ClientError + ), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_TOKEN: "eyadiuyfasiuasf", CONF_NAME: "MyLock"}, diff --git a/tests/components/loqed/test_init.py b/tests/components/loqed/test_init.py index 47f53a1ad20..3d52feead79 100644 --- a/tests/components/loqed/test_init.py +++ b/tests/components/loqed/test_init.py @@ -50,8 +50,11 @@ async def test_setup_webhook_in_bridge( webhooks_fixture = json.loads(load_fixture("loqed/get_all_webhooks.json")) lock.getWebhooks = AsyncMock(side_effect=[[], webhooks_fixture]) - with patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status + with ( + patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status + ), ): await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() @@ -86,13 +89,19 @@ async def test_setup_cloudhook_in_bridge( webhooks_fixture = json.loads(load_fixture("loqed/get_all_webhooks.json")) lock.getWebhooks = AsyncMock(side_effect=[[], webhooks_fixture]) - with patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value=webhooks_fixture[0]["url"], + with ( + patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status + ), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value=webhooks_fixture[0]["url"], + ), ): await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() @@ -113,13 +122,19 @@ async def test_setup_cloudhook_from_entry_in_bridge( lock.getWebhooks = AsyncMock(side_effect=[[], webhooks_fixture]) - with patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), patch( - "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value=webhooks_fixture[0]["url"], + with ( + patch("loqedAPI.loqed.LoqedAPI.async_get_lock", return_value=lock), + patch( + "loqedAPI.loqed.LoqedAPI.async_get_lock_details", return_value=lock_status + ), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value=webhooks_fixture[0]["url"], + ), ): await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/lovelace/test_cast.py b/tests/components/lovelace/test_cast.py index 5fdce538d4c..f0a193ec705 100644 --- a/tests/components/lovelace/test_cast.py +++ b/tests/components/lovelace/test_cast.py @@ -58,20 +58,23 @@ async def mock_yaml_dashboard(hass): }, ) - with patch( - "homeassistant.components.lovelace.dashboard.load_yaml_dict", - return_value={ - "title": "YAML Title", - "views": [ - { - "title": "Hello", - }, - {"path": "second-view"}, - ], - }, - ), patch( - "homeassistant.components.lovelace.dashboard.os.path.getmtime", - return_value=time() + 10, + with ( + patch( + "homeassistant.components.lovelace.dashboard.load_yaml_dict", + return_value={ + "title": "YAML Title", + "views": [ + { + "title": "Hello", + }, + {"path": "second-view"}, + ], + }, + ), + patch( + "homeassistant.components.lovelace.dashboard.os.path.getmtime", + return_value=time() + 10, + ), ): yield diff --git a/tests/components/luftdaten/conftest.py b/tests/components/luftdaten/conftest.py index e6fb0d37288..e083e8c97c7 100644 --- a/tests/components/luftdaten/conftest.py +++ b/tests/components/luftdaten/conftest.py @@ -37,10 +37,14 @@ def mock_setup_entry() -> Generator[None, None, None]: @pytest.fixture def mock_luftdaten() -> Generator[None, MagicMock, None]: """Return a mocked Luftdaten client.""" - with patch( - "homeassistant.components.luftdaten.Luftdaten", autospec=True - ) as luftdaten_mock, patch( - "homeassistant.components.luftdaten.config_flow.Luftdaten", new=luftdaten_mock + with ( + patch( + "homeassistant.components.luftdaten.Luftdaten", autospec=True + ) as luftdaten_mock, + patch( + "homeassistant.components.luftdaten.config_flow.Luftdaten", + new=luftdaten_mock, + ), ): luftdaten = luftdaten_mock.return_value luftdaten.validate_sensor.return_value = True diff --git a/tests/components/lupusec/test_config_flow.py b/tests/components/lupusec/test_config_flow.py index 89adedb86cb..2ca313139dc 100644 --- a/tests/components/lupusec/test_config_flow.py +++ b/tests/components/lupusec/test_config_flow.py @@ -48,12 +48,15 @@ async def test_form_valid_input(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.lupusec.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.lupusec.config_flow.lupupy.Lupusec", - ) as mock_initialize_lupusec: + with ( + patch( + "homeassistant.components.lupusec.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.lupusec.config_flow.lupupy.Lupusec", + ) as mock_initialize_lupusec, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_DATA_STEP, @@ -101,12 +104,15 @@ async def test_flow_user_init_data_error_and_recover( assert len(mock_initialize_lupusec.mock_calls) == 1 # Recover - with patch( - "homeassistant.components.lupusec.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.lupusec.config_flow.lupupy.Lupusec", - ) as mock_initialize_lupusec: + with ( + patch( + "homeassistant.components.lupusec.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.lupusec.config_flow.lupupy.Lupusec", + ) as mock_initialize_lupusec, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_DATA_STEP, @@ -160,12 +166,15 @@ async def test_flow_source_import( hass: HomeAssistant, mock_import_step, mock_title ) -> None: """Test configuration import from YAML.""" - with patch( - "homeassistant.components.lupusec.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.lupusec.config_flow.lupupy.Lupusec", - ) as mock_initialize_lupusec: + with ( + patch( + "homeassistant.components.lupusec.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.lupusec.config_flow.lupupy.Lupusec", + ) as mock_initialize_lupusec, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, diff --git a/tests/components/lutron/test_config_flow.py b/tests/components/lutron/test_config_flow.py index 0d641e4c2e4..db3faa7f911 100644 --- a/tests/components/lutron/test_config_flow.py +++ b/tests/components/lutron/test_config_flow.py @@ -30,8 +30,9 @@ async def test_full_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> No assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), patch( - "homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901" + with ( + patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), + patch("homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -77,8 +78,9 @@ async def test_flow_failure( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": text_error} - with patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), patch( - "homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901" + with ( + patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), + patch("homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -102,8 +104,9 @@ async def test_flow_incorrect_guid( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), patch( - "homeassistant.components.lutron.config_flow.Lutron.guid", "12345" + with ( + patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), + patch("homeassistant.components.lutron.config_flow.Lutron.guid", "12345"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -113,8 +116,9 @@ async def test_flow_incorrect_guid( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": "cannot_connect"} - with patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), patch( - "homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901" + with ( + patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), + patch("homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -149,8 +153,9 @@ async def test_import( mock_setup_entry: AsyncMock, ) -> None: """Test import flow.""" - with patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), patch( - "homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901" + with ( + patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), + patch("homeassistant.components.lutron.config_flow.Lutron.guid", "12345678901"), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_DATA_IMPORT @@ -189,8 +194,9 @@ async def test_import_flow_failure( async def test_import_flow_guid_failure(hass: HomeAssistant) -> None: """Test handling errors while importing.""" - with patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), patch( - "homeassistant.components.lutron.config_flow.Lutron.guid", "123" + with ( + patch("homeassistant.components.lutron.config_flow.Lutron.load_xml_db"), + patch("homeassistant.components.lutron.config_flow.Lutron.guid", "123"), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_DATA_IMPORT diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 5531796c20a..15a4fca7d33 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -54,15 +54,17 @@ async def test_bridge_import_flow(hass: HomeAssistant) -> None: CONF_CA_CERTS: "", } - with patch( - "homeassistant.components.lutron_caseta.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.lutron_caseta.async_setup", return_value=True - ), patch.object( - Smartbridge, - "create_tls", - ) as create_tls: + with ( + patch( + "homeassistant.components.lutron_caseta.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch("homeassistant.components.lutron_caseta.async_setup", return_value=True), + patch.object( + Smartbridge, + "create_tls", + ) as create_tls, + ): create_tls.return_value = MockBridge(can_connect=True) result = await hass.config_entries.flow.async_init( @@ -218,15 +220,19 @@ async def test_form_user(hass: HomeAssistant, tmp_path: Path) -> None: assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch( - "homeassistant.components.lutron_caseta.config_flow.async_pair", - return_value=MOCK_ASYNC_PAIR_SUCCESS, - ), patch( - "homeassistant.components.lutron_caseta.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.lutron_caseta.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lutron_caseta.config_flow.async_pair", + return_value=MOCK_ASYNC_PAIR_SUCCESS, + ), + patch( + "homeassistant.components.lutron_caseta.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.lutron_caseta.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -268,15 +274,19 @@ async def test_form_user_pairing_fails(hass: HomeAssistant, tmp_path: Path) -> N assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch( - "homeassistant.components.lutron_caseta.config_flow.async_pair", - side_effect=TimeoutError, - ), patch( - "homeassistant.components.lutron_caseta.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.lutron_caseta.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lutron_caseta.config_flow.async_pair", + side_effect=TimeoutError, + ), + patch( + "homeassistant.components.lutron_caseta.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.lutron_caseta.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -314,15 +324,19 @@ async def test_form_user_reuses_existing_assets_when_pairing_again( assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch( - "homeassistant.components.lutron_caseta.config_flow.async_pair", - return_value=MOCK_ASYNC_PAIR_SUCCESS, - ), patch( - "homeassistant.components.lutron_caseta.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.lutron_caseta.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lutron_caseta.config_flow.async_pair", + return_value=MOCK_ASYNC_PAIR_SUCCESS, + ), + patch( + "homeassistant.components.lutron_caseta.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.lutron_caseta.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -368,11 +382,12 @@ async def test_form_user_reuses_existing_assets_when_pairing_again( assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch( - "homeassistant.components.lutron_caseta.async_setup", return_value=True - ), patch( - "homeassistant.components.lutron_caseta.async_setup_entry", - return_value=True, + with ( + patch("homeassistant.components.lutron_caseta.async_setup", return_value=True), + patch( + "homeassistant.components.lutron_caseta.async_setup_entry", + return_value=True, + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], @@ -499,15 +514,19 @@ async def test_zeroconf(hass: HomeAssistant, source, tmp_path: Path) -> None: assert result["type"] == "form" assert result["step_id"] == "link" - with patch( - "homeassistant.components.lutron_caseta.config_flow.async_pair", - return_value=MOCK_ASYNC_PAIR_SUCCESS, - ), patch( - "homeassistant.components.lutron_caseta.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.lutron_caseta.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.lutron_caseta.config_flow.async_pair", + return_value=MOCK_ASYNC_PAIR_SUCCESS, + ), + patch( + "homeassistant.components.lutron_caseta.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.lutron_caseta.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, diff --git a/tests/components/lyric/test_config_flow.py b/tests/components/lyric/test_config_flow.py index 7f1e0549ee8..00d623ea3ce 100644 --- a/tests/components/lyric/test_config_flow.py +++ b/tests/components/lyric/test_config_flow.py @@ -84,9 +84,12 @@ async def test_full_flow( }, ) - with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"), patch( - "homeassistant.components.lyric.async_setup_entry", return_value=True - ) as mock_setup: + with ( + patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"), + patch( + "homeassistant.components.lyric.async_setup_entry", return_value=True + ) as mock_setup, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["data"]["auth_implementation"] == "cred" @@ -152,9 +155,12 @@ async def test_reauthentication_flow( }, ) - with patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"), patch( - "homeassistant.components.lyric.async_setup_entry", return_value=True - ) as mock_setup: + with ( + patch("homeassistant.components.lyric.api.ConfigEntryLyricClient"), + patch( + "homeassistant.components.lyric.async_setup_entry", return_value=True + ) as mock_setup, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == data_entry_flow.FlowResultType.ABORT diff --git a/tests/components/medcom_ble/test_config_flow.py b/tests/components/medcom_ble/test_config_flow.py index 34fc145e7dc..ca75dfd80ab 100644 --- a/tests/components/medcom_ble/test_config_flow.py +++ b/tests/components/medcom_ble/test_config_flow.py @@ -35,14 +35,17 @@ async def test_bluetooth_discovery(hass: HomeAssistant) -> None: assert result["step_id"] == "bluetooth_confirm" assert result["description_placeholders"] == {"name": "InspectorBLE-D9A0"} - with patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), patch_medcom_ble( - MedcomBleDevice( - manufacturer="International Medcom", - model="Inspector BLE", - model_raw="Inspector-BLE", - name="Inspector BLE", - identifier="a0d95a570b00", - ) + with ( + patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), + patch_medcom_ble( + MedcomBleDevice( + manufacturer="International Medcom", + model="Inspector BLE", + model_raw="Inspector-BLE", + name="Inspector BLE", + identifier="a0d95a570b00", + ) + ), ): with patch_async_setup_entry(): result = await hass.config_entries.flow.async_configure( @@ -89,17 +92,21 @@ async def test_user_setup(hass: HomeAssistant) -> None: "a0:d9:5a:57:0b:00": "InspectorBLE-D9A0" } - with patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), patch_medcom_ble( - MedcomBleDevice( - manufacturer="International Medcom", - model="Inspector BLE", - model_raw="Inspector-BLE", - name="Inspector BLE", - identifier="a0d95a570b00", - ) - ), patch( - "homeassistant.components.medcom_ble.async_setup_entry", - return_value=True, + with ( + patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), + patch_medcom_ble( + MedcomBleDevice( + manufacturer="International Medcom", + model="Inspector BLE", + model_raw="Inspector-BLE", + name="Inspector BLE", + identifier="a0d95a570b00", + ) + ), + patch( + "homeassistant.components.medcom_ble.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_ADDRESS: "a0:d9:5a:57:0b:00"} @@ -178,8 +185,9 @@ async def test_user_setup_unknown_error(hass: HomeAssistant) -> None: assert result["errors"] is None assert result["data_schema"] is not None - with patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), patch_medcom_ble( - None, Exception() + with ( + patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), + patch_medcom_ble(None, Exception()), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_ADDRESS: "a0:d9:5a:57:0b:00"} @@ -208,8 +216,9 @@ async def test_user_setup_unable_to_connect(hass: HomeAssistant) -> None: "a0:d9:5a:57:0b:00": "InspectorBLE-D9A0" } - with patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), patch_medcom_ble( - side_effect=BleakError("An error") + with ( + patch_async_ble_device_from_address(MEDCOM_SERVICE_INFO), + patch_medcom_ble(side_effect=BleakError("An error")), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_ADDRESS: "a0:d9:5a:57:0b:00"} diff --git a/tests/components/media_extractor/test_init.py b/tests/components/media_extractor/test_init.py index 35570e3257d..e47f0ae1470 100644 --- a/tests/components/media_extractor/test_init.py +++ b/tests/components/media_extractor/test_init.py @@ -190,12 +190,17 @@ async def test_query_error( ) -> None: """Test handling error with query.""" - with patch( - "homeassistant.components.media_extractor.YoutubeDL.extract_info", - return_value=load_json_object_fixture("media_extractor/youtube_1_info.json"), - ), patch( - "homeassistant.components.media_extractor.YoutubeDL.process_ie_result", - side_effect=DownloadError("Message"), + with ( + patch( + "homeassistant.components.media_extractor.YoutubeDL.extract_info", + return_value=load_json_object_fixture( + "media_extractor/youtube_1_info.json" + ), + ), + patch( + "homeassistant.components.media_extractor.YoutubeDL.process_ie_result", + side_effect=DownloadError("Message"), + ), ): await async_setup_component(hass, DOMAIN, empty_media_extractor_config) await hass.async_block_till_done() diff --git a/tests/components/media_player/test_browse_media.py b/tests/components/media_player/test_browse_media.py index e3c5614d815..2b7e40923bf 100644 --- a/tests/components/media_player/test_browse_media.py +++ b/tests/components/media_player/test_browse_media.py @@ -57,9 +57,12 @@ async def test_process_play_media_url(hass: HomeAssistant, mock_sign_path) -> No async_process_play_media_url(hass, "http://192.168.123.123:8123/path") == "http://192.168.123.123:8123/path?authSig=bla" ) - with pytest.raises(HomeAssistantError), patch( - "homeassistant.components.media_player.browse_media.get_url", - side_effect=NoURLAvailableError, + with ( + pytest.raises(HomeAssistantError), + patch( + "homeassistant.components.media_player.browse_media.get_url", + side_effect=NoURLAvailableError, + ), ): async_process_play_media_url(hass, "/path") diff --git a/tests/components/melnor/test_number.py b/tests/components/melnor/test_number.py index 4b185c3700c..f50d0d79edb 100644 --- a/tests/components/melnor/test_number.py +++ b/tests/components/melnor/test_number.py @@ -17,7 +17,11 @@ async def test_manual_watering_minutes(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device() as device_patch, patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device() as device_patch, + patch_async_register_callback(), + ): device = device_patch.return_value assert await hass.config_entries.async_setup(entry.entry_id) @@ -52,7 +56,11 @@ async def test_frequency_interval_hours(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device() as device_patch, patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device() as device_patch, + patch_async_register_callback(), + ): device = device_patch.return_value assert await hass.config_entries.async_setup(entry.entry_id) @@ -87,7 +95,11 @@ async def test_frequency_duration_minutes(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device() as device_patch, patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device() as device_patch, + patch_async_register_callback(), + ): device = device_patch.return_value assert await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/melnor/test_sensor.py b/tests/components/melnor/test_sensor.py index 0f7eec0bbd4..d04494d44ad 100644 --- a/tests/components/melnor/test_sensor.py +++ b/tests/components/melnor/test_sensor.py @@ -26,7 +26,11 @@ async def test_battery_sensor(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device(), patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device(), + patch_async_register_callback(), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -53,9 +57,12 @@ async def test_minutes_remaining_sensor(hass: HomeAssistant) -> None: device.zone1._end_time = (end_time).timestamp() - with freeze_time(now), patch_async_ble_device_from_address(), patch_melnor_device( - device - ), patch_async_register_callback(): + with ( + freeze_time(now), + patch_async_ble_device_from_address(), + patch_melnor_device(device), + patch_async_register_callback(), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -92,9 +99,12 @@ async def test_schedule_next_cycle_sensor(hass: HomeAssistant) -> None: # we control this mock device.zone1.frequency._next_run_time = next_cycle - with freeze_time(now), patch_async_ble_device_from_address(), patch_melnor_device( - device - ), patch_async_register_callback(): + with ( + freeze_time(now), + patch_async_ble_device_from_address(), + patch_melnor_device(device), + patch_async_register_callback(), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -127,9 +137,11 @@ async def test_rssi_sensor( device = mock_melnor_device() - with patch_async_ble_device_from_address(), patch_melnor_device( - device - ), patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device(device), + patch_async_register_callback(), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/melnor/test_switch.py b/tests/components/melnor/test_switch.py index a5efcaef18e..0da0195fe75 100644 --- a/tests/components/melnor/test_switch.py +++ b/tests/components/melnor/test_switch.py @@ -19,7 +19,11 @@ async def test_manual_watering_switch_metadata(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device(), patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device(), + patch_async_register_callback(), + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -34,7 +38,11 @@ async def test_manual_watering_switch_on_off(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device() as device_patch, patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device() as device_patch, + patch_async_register_callback(), + ): device = device_patch.return_value assert await hass.config_entries.async_setup(entry.entry_id) @@ -77,7 +85,11 @@ async def test_schedule_enabled_switch_on_off(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device() as device_patch, patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device() as device_patch, + patch_async_register_callback(), + ): device = device_patch.return_value assert await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/melnor/test_time.py b/tests/components/melnor/test_time.py index a49999d507b..1d12c3b47f8 100644 --- a/tests/components/melnor/test_time.py +++ b/tests/components/melnor/test_time.py @@ -24,7 +24,11 @@ async def test_schedule_start_time(hass: HomeAssistant) -> None: entry = mock_config_entry(hass) - with patch_async_ble_device_from_address(), patch_melnor_device() as device_patch, patch_async_register_callback(): + with ( + patch_async_ble_device_from_address(), + patch_melnor_device() as device_patch, + patch_async_register_callback(), + ): device = device_patch.return_value assert await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index fda318489fb..24f6a52fa5c 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -93,11 +93,12 @@ async def test_setup_with_tls_disabled(hass: HomeAssistant) -> None: async def test_setup_adds_proper_devices(hass: HomeAssistant) -> None: """Test if setup adds devices.""" - with mock.patch( - "homeassistant.components.mfi.sensor.MFiClient" - ) as mock_client, mock.patch( - "homeassistant.components.mfi.sensor.MfiSensor", side_effect=mfi.MfiSensor - ) as mock_sensor: + with ( + mock.patch("homeassistant.components.mfi.sensor.MFiClient") as mock_client, + mock.patch( + "homeassistant.components.mfi.sensor.MfiSensor", side_effect=mfi.MfiSensor + ) as mock_sensor, + ): ports = { i: mock.MagicMock(model=model, label=f"Port {i}", value=0) for i, model in enumerate(mfi.SENSOR_MODELS) diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index fb4e42fb137..6c69787beef 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -27,11 +27,12 @@ GOOD_CONFIG = { async def test_setup_adds_proper_devices(hass: HomeAssistant) -> None: """Test if setup adds devices.""" - with mock.patch( - "homeassistant.components.mfi.switch.MFiClient" - ) as mock_client, mock.patch( - "homeassistant.components.mfi.switch.MfiSwitch", side_effect=mfi.MfiSwitch - ) as mock_switch: + with ( + mock.patch("homeassistant.components.mfi.switch.MFiClient") as mock_client, + mock.patch( + "homeassistant.components.mfi.switch.MfiSwitch", side_effect=mfi.MfiSwitch + ) as mock_switch, + ): ports = { i: mock.MagicMock( model=model, label=f"Port {i}", output=False, data={}, ident=f"abcd-{i}" diff --git a/tests/components/microbees/conftest.py b/tests/components/microbees/conftest.py index e89d4780ad9..60df0377e4d 100644 --- a/tests/components/microbees/conftest.py +++ b/tests/components/microbees/conftest.py @@ -84,11 +84,14 @@ def mock_microbees(): mock.getBees.return_value = devices mock.getMyProfile.return_value = profile - with patch( - "homeassistant.components.microbees.config_flow.MicroBees", - return_value=mock, - ) as mock, patch( - "homeassistant.components.microbees.MicroBees", - return_value=mock, + with ( + patch( + "homeassistant.components.microbees.config_flow.MicroBees", + return_value=mock, + ) as mock, + patch( + "homeassistant.components.microbees.MicroBees", + return_value=mock, + ), ): yield mock diff --git a/tests/components/mikrotik/__init__.py b/tests/components/mikrotik/__init__.py index acf5fcb2d5e..ad8521c7787 100644 --- a/tests/components/mikrotik/__init__.py +++ b/tests/components/mikrotik/__init__.py @@ -208,8 +208,9 @@ async def setup_mikrotik_entry(hass: HomeAssistant, **kwargs: Any) -> None: ) config_entry.add_to_hass(hass) - with patch("librouteros.connect"), patch.object( - mikrotik.hub.MikrotikData, "command", new=mock_command + with ( + patch("librouteros.connect"), + patch.object(mikrotik.hub.MikrotikData, "command", new=mock_command), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/mikrotik/test_init.py b/tests/components/mikrotik/test_init.py index a42db5f84da..96ec0f5771e 100644 --- a/tests/components/mikrotik/test_init.py +++ b/tests/components/mikrotik/test_init.py @@ -18,9 +18,10 @@ from tests.common import MockConfigEntry @pytest.fixture(autouse=True) def mock_api(): """Mock api.""" - with patch("librouteros.create_transport"), patch( - "librouteros.Api.readResponse" - ) as mock_api: + with ( + patch("librouteros.create_transport"), + patch("librouteros.Api.readResponse") as mock_api, + ): yield mock_api diff --git a/tests/components/mill/test_init.py b/tests/components/mill/test_init.py index 21201e955d7..bf0026b8c6c 100644 --- a/tests/components/mill/test_init.py +++ b/tests/components/mill/test_init.py @@ -22,9 +22,10 @@ async def test_setup_with_cloud_config(hass: HomeAssistant) -> None: }, ) entry.add_to_hass(hass) - with patch( - "mill.Mill.fetch_heater_and_sensor_data", return_value={} - ) as mock_fetch, patch("mill.Mill.connect", return_value=True) as mock_connect: + with ( + patch("mill.Mill.fetch_heater_and_sensor_data", return_value={}) as mock_fetch, + patch("mill.Mill.connect", return_value=True) as mock_connect, + ): assert await async_setup_component(hass, "mill", {}) assert len(mock_fetch.mock_calls) == 1 assert len(mock_connect.mock_calls) == 1 @@ -72,9 +73,10 @@ async def test_setup_with_old_cloud_config(hass: HomeAssistant) -> None: }, ) entry.add_to_hass(hass) - with patch("mill.Mill.fetch_heater_and_sensor_data", return_value={}), patch( - "mill.Mill.connect", return_value=True - ) as mock_connect: + with ( + patch("mill.Mill.fetch_heater_and_sensor_data", return_value={}), + patch("mill.Mill.connect", return_value=True) as mock_connect, + ): assert await async_setup_component(hass, "mill", {}) assert len(mock_connect.mock_calls) == 1 @@ -90,24 +92,27 @@ async def test_setup_with_local_config(hass: HomeAssistant) -> None: }, ) entry.add_to_hass(hass) - with patch( - "mill_local.Mill.fetch_heater_and_sensor_data", - return_value={ - "ambient_temperature": 20, - "set_temperature": 22, - "current_power": 0, - "control_signal": 0, - "raw_ambient_temperature": 19, - }, - ) as mock_fetch, patch( - "mill_local.Mill.connect", - return_value={ - "name": "panel heater gen. 3", - "version": "0x210927", - "operation_key": "", - "status": "ok", - }, - ) as mock_connect: + with ( + patch( + "mill_local.Mill.fetch_heater_and_sensor_data", + return_value={ + "ambient_temperature": 20, + "set_temperature": 22, + "current_power": 0, + "control_signal": 0, + "raw_ambient_temperature": 19, + }, + ) as mock_fetch, + patch( + "mill_local.Mill.connect", + return_value={ + "name": "panel heater gen. 3", + "version": "0x210927", + "operation_key": "", + "status": "ok", + }, + ) as mock_connect, + ): assert await async_setup_component(hass, "mill", {}) assert len(mock_fetch.mock_calls) == 1 @@ -126,15 +131,17 @@ async def test_unload_entry(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch.object( - hass.config_entries, - "async_forward_entry_unload", - return_value=True, - ) as unload_entry, patch( - "mill.Mill.fetch_heater_and_sensor_data", return_value={} - ), patch( - "mill.Mill.connect", - return_value=True, + with ( + patch.object( + hass.config_entries, + "async_forward_entry_unload", + return_value=True, + ) as unload_entry, + patch("mill.Mill.fetch_heater_and_sensor_data", return_value={}), + patch( + "mill.Mill.connect", + return_value=True, + ), ): assert await async_setup_component(hass, "mill", {}) diff --git a/tests/components/minecraft_server/test_binary_sensor.py b/tests/components/minecraft_server/test_binary_sensor.py index 40b8a6c5c0c..6321c91d74a 100644 --- a/tests/components/minecraft_server/test_binary_sensor.py +++ b/tests/components/minecraft_server/test_binary_sensor.py @@ -52,12 +52,15 @@ async def test_binary_sensor( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -95,12 +98,15 @@ async def test_binary_sensor_update( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -140,12 +146,15 @@ async def test_binary_sensor_update_failure( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py index 2a0208f2251..188b68ce5af 100644 --- a/tests/components/minecraft_server/test_config_flow.py +++ b/tests/components/minecraft_server/test_config_flow.py @@ -37,12 +37,15 @@ async def test_show_config_form(hass: HomeAssistant) -> None: async def test_address_validation_failure(hass: HomeAssistant) -> None: """Test error in case of a failed connection.""" - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - side_effect=ValueError, - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - side_effect=ValueError, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + side_effect=ValueError, + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + side_effect=ValueError, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT @@ -54,15 +57,19 @@ async def test_address_validation_failure(hass: HomeAssistant) -> None: async def test_java_connection_failure(hass: HomeAssistant) -> None: """Test error in case of a failed connection to a Java Edition server.""" - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - side_effect=ValueError, - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_status", - side_effect=OSError, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + side_effect=ValueError, + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_status", + side_effect=OSError, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT @@ -74,12 +81,15 @@ async def test_java_connection_failure(hass: HomeAssistant) -> None: async def test_bedrock_connection_failure(hass: HomeAssistant) -> None: """Test error in case of a failed connection to a Bedrock Edition server.""" - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - return_value=BedrockServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.BedrockServer.async_status", - side_effect=OSError, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + return_value=BedrockServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.async_status", + side_effect=OSError, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT @@ -91,15 +101,19 @@ async def test_bedrock_connection_failure(hass: HomeAssistant) -> None: async def test_java_connection(hass: HomeAssistant) -> None: """Test config entry in case of a successful connection to a Java Edition server.""" - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - side_effect=ValueError, - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_status", - return_value=TEST_JAVA_STATUS_RESPONSE, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + side_effect=ValueError, + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_status", + return_value=TEST_JAVA_STATUS_RESPONSE, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT @@ -114,12 +128,15 @@ async def test_java_connection(hass: HomeAssistant) -> None: async def test_bedrock_connection(hass: HomeAssistant) -> None: """Test config entry in case of a successful connection to a Bedrock Edition server.""" - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - return_value=BedrockServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.BedrockServer.async_status", - return_value=TEST_BEDROCK_STATUS_RESPONSE, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + return_value=BedrockServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.async_status", + return_value=TEST_BEDROCK_STATUS_RESPONSE, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT @@ -134,12 +151,15 @@ async def test_bedrock_connection(hass: HomeAssistant) -> None: async def test_recovery(hass: HomeAssistant) -> None: """Test config flow recovery (successful connection after a failed connection).""" - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - side_effect=ValueError, - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - side_effect=ValueError, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + side_effect=ValueError, + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + side_effect=ValueError, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT @@ -147,12 +167,15 @@ async def test_recovery(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.minecraft_server.api.BedrockServer.lookup", - return_value=BedrockServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.BedrockServer.async_status", - return_value=TEST_BEDROCK_STATUS_RESPONSE, + with ( + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.lookup", + return_value=BedrockServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.BedrockServer.async_status", + return_value=TEST_BEDROCK_STATUS_RESPONSE, + ), ): result2 = await hass.config_entries.flow.async_configure( flow_id=result["flow_id"], user_input=USER_INPUT diff --git a/tests/components/minecraft_server/test_diagnostics.py b/tests/components/minecraft_server/test_diagnostics.py index ce51a38b88f..e72d0c5f8db 100644 --- a/tests/components/minecraft_server/test_diagnostics.py +++ b/tests/components/minecraft_server/test_diagnostics.py @@ -49,12 +49,15 @@ async def test_config_entry_diagnostics( lookup_function_name = "lookup" # Setup mock entry. - with patch( - f"mcstatus.server.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"mcstatus.server.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"mcstatus.server.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"mcstatus.server.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/minecraft_server/test_init.py b/tests/components/minecraft_server/test_init.py index 86f592f3d76..9c02fb56d91 100644 --- a/tests/components/minecraft_server/test_init.py +++ b/tests/components/minecraft_server/test_init.py @@ -122,12 +122,15 @@ async def test_setup_and_unload_entry( """Test successful entry setup and unload.""" java_mock_config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_status", - return_value=TEST_JAVA_STATUS_RESPONSE, + with ( + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_status", + return_value=TEST_JAVA_STATUS_RESPONSE, + ), ): assert await hass.config_entries.async_setup(java_mock_config_entry.entry_id) await hass.async_block_till_done() @@ -181,12 +184,15 @@ async def test_setup_entry_not_ready( """Test entry setup not ready.""" java_mock_config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_status", - return_value=OSError, + with ( + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_status", + return_value=OSError, + ), ): assert not await hass.config_entries.async_setup( java_mock_config_entry.entry_id @@ -214,16 +220,19 @@ async def test_entry_migration( ) # Trigger migration. - with patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - side_effect=[ - ValueError, # async_migrate_entry - JavaServer(host=TEST_HOST, port=TEST_PORT), # async_migrate_entry - JavaServer(host=TEST_HOST, port=TEST_PORT), # async_setup_entry - ], - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_status", - return_value=TEST_JAVA_STATUS_RESPONSE, + with ( + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + side_effect=[ + ValueError, # async_migrate_entry + JavaServer(host=TEST_HOST, port=TEST_PORT), # async_migrate_entry + JavaServer(host=TEST_HOST, port=TEST_PORT), # async_setup_entry + ], + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_status", + return_value=TEST_JAVA_STATUS_RESPONSE, + ), ): assert await hass.config_entries.async_setup(v1_mock_config_entry.entry_id) await hass.async_block_till_done() @@ -276,12 +285,15 @@ async def test_entry_migration_host_only( ) # Trigger migration. - with patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", - return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), - ), patch( - "homeassistant.components.minecraft_server.api.JavaServer.async_status", - return_value=TEST_JAVA_STATUS_RESPONSE, + with ( + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_lookup", + return_value=JavaServer(host=TEST_HOST, port=TEST_PORT), + ), + patch( + "homeassistant.components.minecraft_server.api.JavaServer.async_status", + return_value=TEST_JAVA_STATUS_RESPONSE, + ), ): assert await hass.config_entries.async_setup(v1_mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/minecraft_server/test_sensor.py b/tests/components/minecraft_server/test_sensor.py index 512f6bf3f78..ff62f8ddf36 100644 --- a/tests/components/minecraft_server/test_sensor.py +++ b/tests/components/minecraft_server/test_sensor.py @@ -94,12 +94,15 @@ async def test_sensor( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -145,12 +148,15 @@ async def test_sensor_disabled_by_default( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -199,12 +205,15 @@ async def test_sensor_update( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -255,12 +264,15 @@ async def test_sensor_update_failure( mock_config_entry = request.getfixturevalue(mock_config_entry) mock_config_entry.add_to_hass(hass) - with patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", - return_value=server(host=TEST_HOST, port=TEST_PORT), - ), patch( - f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", - return_value=status_response, + with ( + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.{lookup_function_name}", + return_value=server(host=TEST_HOST, port=TEST_PORT), + ), + patch( + f"homeassistant.components.minecraft_server.api.{server.__name__}.async_status", + return_value=status_response, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/mobile_app/test_init.py b/tests/components/mobile_app/test_init.py index c6c724cc20b..15380a0d8d7 100644 --- a/tests/components/mobile_app/test_init.py +++ b/tests/components/mobile_app/test_init.py @@ -84,14 +84,17 @@ async def _test_create_cloud_hook( ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.cloud.async_active_subscription", - return_value=async_active_subscription_return_value, - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_get_or_create_cloudhook", autospec=True - ) as mock_async_get_or_create_cloudhook: + with ( + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=async_active_subscription_return_value, + ), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch( + "homeassistant.components.cloud.async_get_or_create_cloudhook", + autospec=True, + ) as mock_async_get_or_create_cloudhook, + ): cloud_hook = "https://hook-url" mock_async_get_or_create_cloudhook.return_value = cloud_hook diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index e89a9ec6269..96c3ba60b65 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -13,8 +13,9 @@ from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) def pymochad_mock(): """Mock pymochad.""" - with mock.patch("homeassistant.components.mochad.switch.device"), mock.patch( - "homeassistant.components.mochad.switch.MochadException" + with ( + mock.patch("homeassistant.components.mochad.switch.device"), + mock.patch("homeassistant.components.mochad.switch.MochadException"), ): yield diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index 2a81fa3d4fb..f6eff0fd64b 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -52,18 +52,22 @@ def mock_pymodbus_fixture(): """Mock pymodbus.""" mock_pb = mock.AsyncMock() mock_pb.close = mock.MagicMock() - with mock.patch( - "homeassistant.components.modbus.modbus.AsyncModbusTcpClient", - return_value=mock_pb, - autospec=True, - ), mock.patch( - "homeassistant.components.modbus.modbus.AsyncModbusSerialClient", - return_value=mock_pb, - autospec=True, - ), mock.patch( - "homeassistant.components.modbus.modbus.AsyncModbusUdpClient", - return_value=mock_pb, - autospec=True, + with ( + mock.patch( + "homeassistant.components.modbus.modbus.AsyncModbusTcpClient", + return_value=mock_pb, + autospec=True, + ), + mock.patch( + "homeassistant.components.modbus.modbus.AsyncModbusSerialClient", + return_value=mock_pb, + autospec=True, + ), + mock.patch( + "homeassistant.components.modbus.modbus.AsyncModbusUdpClient", + return_value=mock_pb, + autospec=True, + ), ): yield mock_pb diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 598fcd7bea7..0ca4703aa5f 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -1653,9 +1653,10 @@ async def test_integration_reload_failed( caplog.clear() yaml_path = get_fixture_path("configuration.yaml", "modbus") - with mock.patch.object( - hass_config, "YAML_CONFIG_FILE", yaml_path - ), mock.patch.object(mock_modbus, "connect", side_effect=ModbusException("error")): + with ( + mock.patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), + mock.patch.object(mock_modbus, "connect", side_effect=ModbusException("error")), + ): await hass.services.async_call(DOMAIN, SERVICE_RELOAD, blocking=True) await hass.async_block_till_done() diff --git a/tests/components/modem_callerid/test_init.py b/tests/components/modem_callerid/test_init.py index b1edf44acf3..ccf97f60e10 100644 --- a/tests/components/modem_callerid/test_init.py +++ b/tests/components/modem_callerid/test_init.py @@ -21,10 +21,14 @@ async def test_setup_entry(hass: HomeAssistant) -> None: data={CONF_DEVICE: com_port().device}, ) entry.add_to_hass(hass) - with patch("aioserial.AioSerial", autospec=True), patch( - "homeassistant.components.modem_callerid.PhoneModem._get_response", - return_value="OK", - ), patch("phone_modem.PhoneModem._modem_sm"): + with ( + patch("aioserial.AioSerial", autospec=True), + patch( + "homeassistant.components.modem_callerid.PhoneModem._get_response", + return_value="OK", + ), + patch("phone_modem.PhoneModem._modem_sm"), + ): await hass.config_entries.async_setup(entry.entry_id) assert entry.state == ConfigEntryState.LOADED diff --git a/tests/components/modern_forms/test_fan.py b/tests/components/modern_forms/test_fan.py index 92660643bfc..82ab6407c12 100644 --- a/tests/components/modern_forms/test_fan.py +++ b/tests/components/modern_forms/test_fan.py @@ -210,9 +210,12 @@ async def test_fan_connection_error( """Test error handling of the Moder Forms fans.""" await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.modern_forms.ModernFormsDevice.update"), patch( - "homeassistant.components.modern_forms.ModernFormsDevice.fan", - side_effect=ModernFormsConnectionError, + with ( + patch("homeassistant.components.modern_forms.ModernFormsDevice.update"), + patch( + "homeassistant.components.modern_forms.ModernFormsDevice.fan", + side_effect=ModernFormsConnectionError, + ), ): await hass.services.async_call( FAN_DOMAIN, diff --git a/tests/components/modern_forms/test_light.py b/tests/components/modern_forms/test_light.py index 42415214116..3b1cfdd90d2 100644 --- a/tests/components/modern_forms/test_light.py +++ b/tests/components/modern_forms/test_light.py @@ -138,9 +138,12 @@ async def test_light_connection_error( """Test error handling of the Moder Forms lights.""" await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.modern_forms.ModernFormsDevice.update"), patch( - "homeassistant.components.modern_forms.ModernFormsDevice.light", - side_effect=ModernFormsConnectionError, + with ( + patch("homeassistant.components.modern_forms.ModernFormsDevice.update"), + patch( + "homeassistant.components.modern_forms.ModernFormsDevice.light", + side_effect=ModernFormsConnectionError, + ), ): await hass.services.async_call( LIGHT_DOMAIN, diff --git a/tests/components/modern_forms/test_switch.py b/tests/components/modern_forms/test_switch.py index 42a52375c3c..8a2012bbd5f 100644 --- a/tests/components/modern_forms/test_switch.py +++ b/tests/components/modern_forms/test_switch.py @@ -130,9 +130,12 @@ async def test_switch_connection_error( """Test error handling of the Modern Forms switches.""" await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.modern_forms.ModernFormsDevice.update"), patch( - "homeassistant.components.modern_forms.ModernFormsDevice.away", - side_effect=ModernFormsConnectionError, + with ( + patch("homeassistant.components.modern_forms.ModernFormsDevice.update"), + patch( + "homeassistant.components.modern_forms.ModernFormsDevice.away", + side_effect=ModernFormsConnectionError, + ), ): await hass.services.async_call( SWITCH_DOMAIN, diff --git a/tests/components/moehlenhoff_alpha2/test_config_flow.py b/tests/components/moehlenhoff_alpha2/test_config_flow.py index f3b37792287..fcce2d139d2 100644 --- a/tests/components/moehlenhoff_alpha2/test_config_flow.py +++ b/tests/components/moehlenhoff_alpha2/test_config_flow.py @@ -32,10 +32,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert not result["errors"] - with patch("moehlenhoff_alpha2.Alpha2Base.update_data", mock_update_data), patch( - "homeassistant.components.moehlenhoff_alpha2.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("moehlenhoff_alpha2.Alpha2Base.update_data", mock_update_data), + patch( + "homeassistant.components.moehlenhoff_alpha2.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( flow_id=result["flow_id"], user_input={"host": MOCK_BASE_HOST}, diff --git a/tests/components/monoprice/test_config_flow.py b/tests/components/monoprice/test_config_flow.py index ae9ffea921c..74c69078b1d 100644 --- a/tests/components/monoprice/test_config_flow.py +++ b/tests/components/monoprice/test_config_flow.py @@ -34,13 +34,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.monoprice.config_flow.get_monoprice", - return_value=True, - ), patch( - "homeassistant.components.monoprice.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.monoprice.config_flow.get_monoprice", + return_value=True, + ), + patch( + "homeassistant.components.monoprice.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG ) diff --git a/tests/components/motion_blinds/test_config_flow.py b/tests/components/motion_blinds/test_config_flow.py index 5ffb0b356ad..8d290b0b380 100644 --- a/tests/components/motion_blinds/test_config_flow.py +++ b/tests/components/motion_blinds/test_config_flow.py @@ -73,41 +73,55 @@ TEST_INTERFACES = [ @pytest.fixture(name="motion_blinds_connect", autouse=True) def motion_blinds_connect_fixture(mock_get_source_ip): """Mock Motionblinds connection and entry setup.""" - with patch( - "homeassistant.components.motion_blinds.gateway.MotionGateway.GetDeviceList", - return_value=True, - ), patch( - "homeassistant.components.motion_blinds.gateway.MotionGateway.Update", - return_value=True, - ), patch( - "homeassistant.components.motion_blinds.gateway.MotionGateway.Check_gateway_multicast", - return_value=True, - ), patch( - "homeassistant.components.motion_blinds.gateway.MotionGateway.device_list", - TEST_DEVICE_LIST, - ), patch( - "homeassistant.components.motion_blinds.gateway.MotionGateway.mac", - TEST_MAC, - ), patch( - "homeassistant.components.motion_blinds.config_flow.MotionDiscovery.discover", - return_value=TEST_DISCOVERY_1, - ), patch( - "homeassistant.components.motion_blinds.config_flow.MotionGateway.GetDeviceList", - return_value=True, - ), patch( - "homeassistant.components.motion_blinds.config_flow.MotionGateway.available", - True, - ), patch( - "homeassistant.components.motion_blinds.gateway.AsyncMotionMulticast.Start_listen", - return_value=True, - ), patch( - "homeassistant.components.motion_blinds.gateway.AsyncMotionMulticast.Stop_listen", - return_value=True, - ), patch( - "homeassistant.components.motion_blinds.gateway.network.async_get_adapters", - return_value=TEST_INTERFACES, - ), patch( - "homeassistant.components.motion_blinds.async_setup_entry", return_value=True + with ( + patch( + "homeassistant.components.motion_blinds.gateway.MotionGateway.GetDeviceList", + return_value=True, + ), + patch( + "homeassistant.components.motion_blinds.gateway.MotionGateway.Update", + return_value=True, + ), + patch( + "homeassistant.components.motion_blinds.gateway.MotionGateway.Check_gateway_multicast", + return_value=True, + ), + patch( + "homeassistant.components.motion_blinds.gateway.MotionGateway.device_list", + TEST_DEVICE_LIST, + ), + patch( + "homeassistant.components.motion_blinds.gateway.MotionGateway.mac", + TEST_MAC, + ), + patch( + "homeassistant.components.motion_blinds.config_flow.MotionDiscovery.discover", + return_value=TEST_DISCOVERY_1, + ), + patch( + "homeassistant.components.motion_blinds.config_flow.MotionGateway.GetDeviceList", + return_value=True, + ), + patch( + "homeassistant.components.motion_blinds.config_flow.MotionGateway.available", + True, + ), + patch( + "homeassistant.components.motion_blinds.gateway.AsyncMotionMulticast.Start_listen", + return_value=True, + ), + patch( + "homeassistant.components.motion_blinds.gateway.AsyncMotionMulticast.Stop_listen", + return_value=True, + ), + patch( + "homeassistant.components.motion_blinds.gateway.network.async_get_adapters", + return_value=TEST_INTERFACES, + ), + patch( + "homeassistant.components.motion_blinds.async_setup_entry", + return_value=True, + ), ): yield diff --git a/tests/components/motioneye/test_config_flow.py b/tests/components/motioneye/test_config_flow.py index a5b2763182c..7163f2c8152 100644 --- a/tests/components/motioneye/test_config_flow.py +++ b/tests/components/motioneye/test_config_flow.py @@ -39,13 +39,16 @@ async def test_user_success(hass: HomeAssistant) -> None: mock_client = create_mock_motioneye_client() - with patch( - "homeassistant.components.motioneye.MotionEyeClient", - return_value=mock_client, - ), patch( - "homeassistant.components.motioneye.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.motioneye.MotionEyeClient", + return_value=mock_client, + ), + patch( + "homeassistant.components.motioneye.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -95,13 +98,16 @@ async def test_hassio_success(hass: HomeAssistant) -> None: mock_client = create_mock_motioneye_client() - with patch( - "homeassistant.components.motioneye.MotionEyeClient", - return_value=mock_client, - ), patch( - "homeassistant.components.motioneye.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.motioneye.MotionEyeClient", + return_value=mock_client, + ), + patch( + "homeassistant.components.motioneye.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -278,13 +284,16 @@ async def test_reauth(hass: HomeAssistant) -> None: CONF_SURVEILLANCE_PASSWORD: "surveillance-password", } - with patch( - "homeassistant.components.motioneye.MotionEyeClient", - return_value=mock_client, - ), patch( - "homeassistant.components.motioneye.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.motioneye.MotionEyeClient", + return_value=mock_client, + ), + patch( + "homeassistant.components.motioneye.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], new_data, @@ -430,13 +439,16 @@ async def test_hassio_clean_up_on_user_flow(hass: HomeAssistant) -> None: mock_client = create_mock_motioneye_client() - with patch( - "homeassistant.components.motioneye.MotionEyeClient", - return_value=mock_client, - ), patch( - "homeassistant.components.motioneye.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.motioneye.MotionEyeClient", + return_value=mock_client, + ), + patch( + "homeassistant.components.motioneye.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -462,12 +474,15 @@ async def test_options(hass: HomeAssistant) -> None: config_entry = create_mock_motioneye_config_entry(hass) client = create_mock_motioneye_client() - with patch( - "homeassistant.components.motioneye.MotionEyeClient", - return_value=client, - ), patch( - "homeassistant.components.motioneye.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.motioneye.MotionEyeClient", + return_value=client, + ), + patch( + "homeassistant.components.motioneye.async_setup_entry", + return_value=True, + ), ): await hass.async_block_till_done() @@ -495,13 +510,16 @@ async def test_advanced_options(hass: HomeAssistant) -> None: config_entry = create_mock_motioneye_config_entry(hass) mock_client = create_mock_motioneye_client() - with patch( - "homeassistant.components.motioneye.MotionEyeClient", - return_value=mock_client, - ) as mock_setup, patch( - "homeassistant.components.motioneye.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.motioneye.MotionEyeClient", + return_value=mock_client, + ) as mock_setup, + patch( + "homeassistant.components.motioneye.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.options.async_init( diff --git a/tests/components/motioneye/test_web_hooks.py b/tests/components/motioneye/test_web_hooks.py index a828e5964d0..fae7fccbb6d 100644 --- a/tests/components/motioneye/test_web_hooks.py +++ b/tests/components/motioneye/test_web_hooks.py @@ -222,18 +222,18 @@ async def test_setup_camera_with_correct_webhook( cameras = copy.deepcopy(TEST_CAMERAS) cameras[KEY_CAMERAS][0][KEY_WEB_HOOK_NOTIFICATIONS_ENABLED] = True - cameras[KEY_CAMERAS][0][ - KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD - ] = KEY_HTTP_METHOD_POST_JSON + cameras[KEY_CAMERAS][0][KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD] = ( + KEY_HTTP_METHOD_POST_JSON + ) cameras[KEY_CAMERAS][0][KEY_WEB_HOOK_NOTIFICATIONS_URL] = ( "https://internal.url" + URL_WEBHOOK_PATH.format(webhook_id=config_entry.data[CONF_WEBHOOK_ID]) + f"?{WEB_HOOK_MOTION_DETECTED_QUERY_STRING}&device_id={device.id}" ) cameras[KEY_CAMERAS][0][KEY_WEB_HOOK_STORAGE_ENABLED] = True - cameras[KEY_CAMERAS][0][ - KEY_WEB_HOOK_STORAGE_HTTP_METHOD - ] = KEY_HTTP_METHOD_POST_JSON + cameras[KEY_CAMERAS][0][KEY_WEB_HOOK_STORAGE_HTTP_METHOD] = ( + KEY_HTTP_METHOD_POST_JSON + ) cameras[KEY_CAMERAS][0][KEY_WEB_HOOK_STORAGE_URL] = ( "https://internal.url" + URL_WEBHOOK_PATH.format(webhook_id=config_entry.data[CONF_WEBHOOK_ID]) diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 055724a4659..ff78d96d37e 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -1332,9 +1332,13 @@ async def test_reload_after_invalid_config( }, ] } - with patch( - "homeassistant.config.load_yaml_config_file", return_value=invalid_config - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.config.load_yaml_config_file", + return_value=invalid_config, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( "mqtt", SERVICE_RELOAD, diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 6d9f56c531e..9dc52871529 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -385,9 +385,10 @@ async def help_test_default_availability_list_single( ] config[mqtt.DOMAIN][domain]["availability_topic"] = "availability-topic" - with patch( - "homeassistant.config.load_yaml_config_file", return_value=config - ), suppress(vol.MultipleInvalid): + with ( + patch("homeassistant.config.load_yaml_config_file", return_value=config), + suppress(vol.MultipleInvalid), + ): await mqtt_mock_entry() assert ( @@ -590,9 +591,9 @@ async def help_test_setting_attribute_with_template( # Add JSON attributes settings to config config = copy.deepcopy(config) config[mqtt.DOMAIN][domain]["json_attributes_topic"] = "attr-topic" - config[mqtt.DOMAIN][domain][ - "json_attributes_template" - ] = "{{ value_json['Timer1'] | tojson }}" + config[mqtt.DOMAIN][domain]["json_attributes_template"] = ( + "{{ value_json['Timer1'] | tojson }}" + ) with patch("homeassistant.config.load_yaml_config_file", return_value=config): await mqtt_mock_entry() @@ -1698,9 +1699,9 @@ async def help_test_publishing_with_custom_encoding( if test_data["encoding"] is not None: test_config_setup["encoding"] = test_data["encoding"] if template and test_data["cmd_tpl"]: - test_config_setup[ - template - ] = f"{{{{ (('%.1f'|format({tpl_par}))[0] if is_number({tpl_par}) else {tpl_par}[0]) | ord | pack('b') }}}}" + test_config_setup[template] = ( + f"{{{{ (('%.1f'|format({tpl_par}))[0] if is_number({tpl_par}) else {tpl_par}[0]) | ord | pack('b') }}}}" + ) setup_config.append(test_config_setup) # setup service data diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index 329d6077300..719117e59a9 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -55,13 +55,15 @@ def mock_client_key_check_fail() -> Generator[MagicMock, None, None]: @pytest.fixture def mock_ssl_context() -> Generator[dict[str, MagicMock], None, None]: """Mock the SSL context used to load the cert chain and to load verify locations.""" - with patch( - "homeassistant.components.mqtt.config_flow.SSLContext" - ) as mock_context, patch( - "homeassistant.components.mqtt.config_flow.load_pem_private_key" - ) as mock_key_check, patch( - "homeassistant.components.mqtt.config_flow.load_pem_x509_certificate" - ) as mock_cert_check: + with ( + patch("homeassistant.components.mqtt.config_flow.SSLContext") as mock_context, + patch( + "homeassistant.components.mqtt.config_flow.load_pem_private_key" + ) as mock_key_check, + patch( + "homeassistant.components.mqtt.config_flow.load_pem_x509_certificate" + ) as mock_cert_check, + ): yield { "context": mock_context, "load_pem_x509_certificate": mock_cert_check, @@ -123,8 +125,9 @@ def mock_try_connection_time_out() -> Generator[MagicMock, None, None]: """Mock the try connection method with a time out.""" # Patch prevent waiting 5 sec for a timeout - with patch("paho.mqtt.client.Client") as mock_client, patch( - "homeassistant.components.mqtt.config_flow.MQTT_TIMEOUT", 0 + with ( + patch("paho.mqtt.client.Client") as mock_client, + patch("homeassistant.components.mqtt.config_flow.MQTT_TIMEOUT", 0), ): mock_client().loop_start = lambda *args: 1 yield mock_client() diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index baa3542a22d..24891895fad 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1506,8 +1506,9 @@ async def test_mqtt_integration_discovery_subscribe_unsubscribe( wait_unsub.set() return (0, 0) - with mock_config_flow("comp", TestFlow), patch.object( - mqtt_client_mock, "unsubscribe", side_effect=_mock_unsubscribe + with ( + mock_config_flow("comp", TestFlow), + patch.object(mqtt_client_mock, "unsubscribe", side_effect=_mock_unsubscribe), ): async_fire_mqtt_message(hass, "comp/discovery/bla/config", "") await wait_unsub.wait() diff --git a/tests/components/mqtt/test_event.py b/tests/components/mqtt/test_event.py index 488612a0fa3..64a2003606c 100644 --- a/tests/components/mqtt/test_event.py +++ b/tests/components/mqtt/test_event.py @@ -463,12 +463,12 @@ async def test_discovery_update_event_template( config2["name"] = "Milk" config1["state_topic"] = "event/state1" config2["state_topic"] = "event/state1" - config1[ - "value_template" - ] = '{"event_type": "press", "val": "{{ value_json.val | int }}"}' - config2[ - "value_template" - ] = '{"event_type": "press", "val": "{{ value_json.val | int * 2 }}"}' + config1["value_template"] = ( + '{"event_type": "press", "val": "{{ value_json.val | int }}"}' + ) + config2["value_template"] = ( + '{"event_type": "press", "val": "{{ value_json.val | int * 2 }}"}' + ) async_fire_mqtt_message(hass, "homeassistant/event/bla/config", json.dumps(config1)) await hass.async_block_till_done() diff --git a/tests/components/mullvad/test_config_flow.py b/tests/components/mullvad/test_config_flow.py index 2154513d6d1..e1e6570fa67 100644 --- a/tests/components/mullvad/test_config_flow.py +++ b/tests/components/mullvad/test_config_flow.py @@ -21,12 +21,15 @@ async def test_form_user(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert not result["errors"] - with patch( - "homeassistant.components.mullvad.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.mullvad.config_flow.MullvadAPI" - ) as mock_mullvad_api: + with ( + patch( + "homeassistant.components.mullvad.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.mullvad.config_flow.MullvadAPI" + ) as mock_mullvad_api, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, diff --git a/tests/components/mutesync/test_config_flow.py b/tests/components/mutesync/test_config_flow.py index 18cc7e68aa4..f667671da74 100644 --- a/tests/components/mutesync/test_config_flow.py +++ b/tests/components/mutesync/test_config_flow.py @@ -19,13 +19,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "mutesync.authenticate", - return_value="bla", - ), patch( - "homeassistant.components.mutesync.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "mutesync.authenticate", + return_value="bla", + ), + patch( + "homeassistant.components.mutesync.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/mysensors/conftest.py b/tests/components/mysensors/conftest.py index a892730ef6a..bcf852e1368 100644 --- a/tests/components/mysensors/conftest.py +++ b/tests/components/mysensors/conftest.py @@ -55,14 +55,17 @@ async def serial_transport_fixture( is_serial_port: MagicMock, ) -> AsyncGenerator[dict[int, Sensor], None]: """Mock a serial transport.""" - with patch( - "mysensors.gateway_serial.AsyncTransport", autospec=True - ) as transport_class, patch("mysensors.task.OTAFirmware", autospec=True), patch( - "mysensors.task.load_fw", autospec=True - ), patch( - "mysensors.task.Persistence", - autospec=True, - ) as persistence_class: + with ( + patch( + "mysensors.gateway_serial.AsyncTransport", autospec=True + ) as transport_class, + patch("mysensors.task.OTAFirmware", autospec=True), + patch("mysensors.task.load_fw", autospec=True), + patch( + "mysensors.task.Persistence", + autospec=True, + ) as persistence_class, + ): persistence = persistence_class.return_value mock_gateway_features(persistence, transport_class, gateway_nodes) diff --git a/tests/components/mysensors/test_config_flow.py b/tests/components/mysensors/test_config_flow.py index 2506e350e89..f532d09c6bf 100644 --- a/tests/components/mysensors/test_config_flow.py +++ b/tests/components/mysensors/test_config_flow.py @@ -113,15 +113,20 @@ async def test_config_serial(hass: HomeAssistant) -> None: step = await get_form(hass, CONF_GATEWAY_TYPE_SERIAL, "gw_serial") flow_id = step["flow_id"] - with patch( # mock is_serial_port because otherwise the test will be platform dependent (/dev/ttyACMx vs COMx) - "homeassistant.components.mysensors.config_flow.is_serial_port", - return_value=True, - ), patch( - "homeassistant.components.mysensors.config_flow.try_connect", return_value=True - ), patch( - "homeassistant.components.mysensors.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( # mock is_serial_port because otherwise the test will be platform dependent (/dev/ttyACMx vs COMx) + "homeassistant.components.mysensors.config_flow.is_serial_port", + return_value=True, + ), + patch( + "homeassistant.components.mysensors.config_flow.try_connect", + return_value=True, + ), + patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( flow_id, { @@ -150,12 +155,16 @@ async def test_config_tcp(hass: HomeAssistant) -> None: step = await get_form(hass, CONF_GATEWAY_TYPE_TCP, "gw_tcp") flow_id = step["flow_id"] - with patch( - "homeassistant.components.mysensors.config_flow.try_connect", return_value=True - ), patch( - "homeassistant.components.mysensors.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.mysensors.config_flow.try_connect", + return_value=True, + ), + patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( flow_id, { @@ -184,12 +193,16 @@ async def test_fail_to_connect(hass: HomeAssistant) -> None: step = await get_form(hass, CONF_GATEWAY_TYPE_TCP, "gw_tcp") flow_id = step["flow_id"] - with patch( - "homeassistant.components.mysensors.config_flow.try_connect", return_value=False - ), patch( - "homeassistant.components.mysensors.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.mysensors.config_flow.try_connect", + return_value=False, + ), + patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( flow_id, { @@ -341,15 +354,20 @@ async def test_config_invalid( step = await get_form(hass, gateway_type, expected_step_id) flow_id = step["flow_id"] - with patch( - "homeassistant.components.mysensors.config_flow.try_connect", return_value=True - ), patch( - "homeassistant.components.mysensors.gateway.socket.getaddrinfo", - side_effect=OSError, - ), patch( - "homeassistant.components.mysensors.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.mysensors.config_flow.try_connect", + return_value=True, + ), + patch( + "homeassistant.components.mysensors.gateway.socket.getaddrinfo", + side_effect=OSError, + ), + patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( flow_id, user_input, @@ -653,11 +671,16 @@ async def test_duplicate( ) -> None: """Test duplicate detection.""" - with patch("sys.platform", "win32"), patch( - "homeassistant.components.mysensors.config_flow.try_connect", return_value=True - ), patch( - "homeassistant.components.mysensors.async_setup_entry", - return_value=True, + with ( + patch("sys.platform", "win32"), + patch( + "homeassistant.components.mysensors.config_flow.try_connect", + return_value=True, + ), + patch( + "homeassistant.components.mysensors.async_setup_entry", + return_value=True, + ), ): MockConfigEntry(domain=DOMAIN, data=first_input).add_to_hass(hass) diff --git a/tests/components/mystrom/test_init.py b/tests/components/mystrom/test_init.py index 0c49eecf88d..0304a0eb270 100644 --- a/tests/components/mystrom/test_init.py +++ b/tests/components/mystrom/test_init.py @@ -27,15 +27,21 @@ async def init_integration( device_type: int, ) -> None: """Initialize integration for testing.""" - with patch( - "pymystrom.get_device_info", - side_effect=AsyncMock(return_value=get_default_device_response(device_type)), - ), patch( - "homeassistant.components.mystrom._get_mystrom_bulb", - return_value=MyStromBulbMock("6001940376EB", get_default_bulb_state()), - ), patch( - "homeassistant.components.mystrom._get_mystrom_switch", - return_value=MyStromSwitchMock(get_default_switch_state()), + with ( + patch( + "pymystrom.get_device_info", + side_effect=AsyncMock( + return_value=get_default_device_response(device_type) + ), + ), + patch( + "homeassistant.components.mystrom._get_mystrom_bulb", + return_value=MyStromBulbMock("6001940376EB", get_default_bulb_state()), + ), + patch( + "homeassistant.components.mystrom._get_mystrom_switch", + return_value=MyStromSwitchMock(get_default_switch_state()), + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -91,15 +97,18 @@ async def test_init_of_unknown_bulb( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test the initialization of a unknown myStrom bulb.""" - with patch( - "pymystrom.get_device_info", - side_effect=AsyncMock(return_value={"type": 102, "mac": DEVICE_MAC}), - ), patch("pymystrom.bulb.MyStromBulb.get_state", return_value={}), patch( - "pymystrom.bulb.MyStromBulb.bulb_type", "new_type" - ), patch( - "pymystrom.bulb.MyStromBulb.mac", - new_callable=PropertyMock, - return_value=DEVICE_MAC, + with ( + patch( + "pymystrom.get_device_info", + side_effect=AsyncMock(return_value={"type": 102, "mac": DEVICE_MAC}), + ), + patch("pymystrom.bulb.MyStromBulb.get_state", return_value={}), + patch("pymystrom.bulb.MyStromBulb.bulb_type", "new_type"), + patch( + "pymystrom.bulb.MyStromBulb.mac", + new_callable=PropertyMock, + return_value=DEVICE_MAC, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -125,11 +134,13 @@ async def test_init_cannot_connect_because_of_device_info( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test error handling for failing get_device_info.""" - with patch( - "pymystrom.get_device_info", - side_effect=MyStromConnectionError(), - ), patch("pymystrom.switch.MyStromSwitch.get_state", return_value={}), patch( - "pymystrom.bulb.MyStromBulb.get_state", return_value={} + with ( + patch( + "pymystrom.get_device_info", + side_effect=MyStromConnectionError(), + ), + patch("pymystrom.switch.MyStromSwitch.get_state", return_value={}), + patch("pymystrom.bulb.MyStromBulb.get_state", return_value={}), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -141,13 +152,18 @@ async def test_init_cannot_connect_because_of_get_state( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test error handling for failing get_state.""" - with patch( - "pymystrom.get_device_info", - side_effect=AsyncMock(return_value=get_default_device_response(101)), - ), patch( - "pymystrom.switch.MyStromSwitch.get_state", side_effect=MyStromConnectionError() - ), patch( - "pymystrom.bulb.MyStromBulb.get_state", side_effect=MyStromConnectionError() + with ( + patch( + "pymystrom.get_device_info", + side_effect=AsyncMock(return_value=get_default_device_response(101)), + ), + patch( + "pymystrom.switch.MyStromSwitch.get_state", + side_effect=MyStromConnectionError(), + ), + patch( + "pymystrom.bulb.MyStromBulb.get_state", side_effect=MyStromConnectionError() + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/nam/__init__.py b/tests/components/nam/__init__.py index 3d95d6cc7e0..0484fc12bd6 100644 --- a/tests/components/nam/__init__.py +++ b/tests/components/nam/__init__.py @@ -58,9 +58,12 @@ async def init_integration(hass, co2_sensor=True) -> MockConfigEntry: update_response = Mock(json=AsyncMock(return_value=nam_data)) - with patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( - "homeassistant.components.nam.NettigoAirMonitor._async_http_request", - return_value=update_response, + with ( + patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), + patch( + "homeassistant.components.nam.NettigoAirMonitor._async_http_request", + return_value=update_response, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/nam/test_button.py b/tests/components/nam/test_button.py index 32c69c91202..39c37d57f89 100644 --- a/tests/components/nam/test_button.py +++ b/tests/components/nam/test_button.py @@ -30,9 +30,12 @@ async def test_button_press(hass: HomeAssistant) -> None: await init_integration(hass) now = dt_util.utcnow() - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_restart" - ) as mock_restart, patch("homeassistant.core.dt_util.utcnow", return_value=now): + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_restart" + ) as mock_restart, + patch("homeassistant.core.dt_util.utcnow", return_value=now), + ): await hass.services.async_call( BUTTON_DOMAIN, "press", diff --git a/tests/components/nam/test_config_flow.py b/tests/components/nam/test_config_flow.py index 9081bfbb7b5..71bf3cf1525 100644 --- a/tests/components/nam/test_config_flow.py +++ b/tests/components/nam/test_config_flow.py @@ -38,15 +38,19 @@ async def test_form_create_entry_without_auth(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", - ), patch( - "homeassistant.components.nam.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), + patch( + "homeassistant.components.nam.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_CONFIG, @@ -68,15 +72,19 @@ async def test_form_create_entry_with_auth(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG_AUTH, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", - ), patch( - "homeassistant.components.nam.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG_AUTH, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), + patch( + "homeassistant.components.nam.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_CONFIG, @@ -109,12 +117,15 @@ async def test_reauth_successful(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG_AUTH, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG_AUTH, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -178,12 +189,15 @@ async def test_reauth_unsuccessful(hass: HomeAssistant) -> None: async def test_form_with_auth_errors(hass: HomeAssistant, error) -> None: """Test we handle errors when auth is required.""" exc, base_error = error - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - side_effect=AuthFailedError("Auth Error"), - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + side_effect=AuthFailedError("Auth Error"), + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -232,12 +246,15 @@ async def test_form_errors(hass: HomeAssistant, error) -> None: async def test_form_abort(hass: HomeAssistant) -> None: """Test we handle abort after error.""" - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - side_effect=CannotGetMacError("Cannot get MAC address from device"), + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + side_effect=CannotGetMacError("Cannot get MAC address from device"), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -260,12 +277,15 @@ async def test_form_already_configured(hass: HomeAssistant) -> None: DOMAIN, context={"source": SOURCE_USER} ) - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -281,12 +301,15 @@ async def test_form_already_configured(hass: HomeAssistant) -> None: async def test_zeroconf(hass: HomeAssistant) -> None: """Test we get the form.""" - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -322,12 +345,15 @@ async def test_zeroconf(hass: HomeAssistant) -> None: async def test_zeroconf_with_auth(hass: HomeAssistant) -> None: """Test that the zeroconf step with auth works.""" - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - side_effect=AuthFailedError("Auth Error"), - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + side_effect=AuthFailedError("Auth Error"), + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -345,15 +371,19 @@ async def test_zeroconf_with_auth(hass: HomeAssistant) -> None: assert result["errors"] == {} assert context["title_placeholders"]["host"] == "10.10.2.3" - with patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - return_value=DEVICE_CONFIG_AUTH, - ), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", - return_value="aa:bb:cc:dd:ee:ff", - ), patch( - "homeassistant.components.nam.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + return_value=DEVICE_CONFIG_AUTH, + ), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_get_mac_address", + return_value="aa:bb:cc:dd:ee:ff", + ), + patch( + "homeassistant.components.nam.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_AUTH, diff --git a/tests/components/nam/test_init.py b/tests/components/nam/test_init.py index 372c6fb8e66..8b8c3a4835a 100644 --- a/tests/components/nam/test_init.py +++ b/tests/components/nam/test_init.py @@ -54,9 +54,12 @@ async def test_config_not_ready_while_checking_credentials(hass: HomeAssistant) ) entry.add_to_hass(hass) - with patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( - "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", - side_effect=ApiError("API Error"), + with ( + patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), + patch( + "homeassistant.components.nam.NettigoAirMonitor.async_check_credentials", + side_effect=ApiError("API Error"), + ), ): await hass.config_entries.async_setup(entry.entry_id) assert entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/nam/test_sensor.py b/tests/components/nam/test_sensor.py index 44bf6552042..c88a34ae497 100644 --- a/tests/components/nam/test_sensor.py +++ b/tests/components/nam/test_sensor.py @@ -507,9 +507,12 @@ async def test_incompleta_data_after_device_restart(hass: HomeAssistant) -> None future = utcnow() + timedelta(minutes=6) update_response = Mock(json=AsyncMock(return_value=INCOMPLETE_NAM_DATA)) - with patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( - "homeassistant.components.nam.NettigoAirMonitor._async_http_request", - return_value=update_response, + with ( + patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), + patch( + "homeassistant.components.nam.NettigoAirMonitor._async_http_request", + return_value=update_response, + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -529,9 +532,12 @@ async def test_availability(hass: HomeAssistant) -> None: assert state.state == "7.6" future = utcnow() + timedelta(minutes=6) - with patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( - "homeassistant.components.nam.NettigoAirMonitor._async_http_request", - side_effect=ApiError("API Error"), + with ( + patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), + patch( + "homeassistant.components.nam.NettigoAirMonitor._async_http_request", + side_effect=ApiError("API Error"), + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -542,9 +548,12 @@ async def test_availability(hass: HomeAssistant) -> None: future = utcnow() + timedelta(minutes=12) update_response = Mock(json=AsyncMock(return_value=nam_data)) - with patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( - "homeassistant.components.nam.NettigoAirMonitor._async_http_request", - return_value=update_response, + with ( + patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), + patch( + "homeassistant.components.nam.NettigoAirMonitor._async_http_request", + return_value=update_response, + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -562,10 +571,13 @@ async def test_manual_update_entity(hass: HomeAssistant) -> None: await async_setup_component(hass, "homeassistant", {}) update_response = Mock(json=AsyncMock(return_value=nam_data)) - with patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), patch( - "homeassistant.components.nam.NettigoAirMonitor._async_http_request", - return_value=update_response, - ) as mock_get_data: + with ( + patch("homeassistant.components.nam.NettigoAirMonitor.initialize"), + patch( + "homeassistant.components.nam.NettigoAirMonitor._async_http_request", + return_value=update_response, + ) as mock_get_data, + ): await hass.services.async_call( "homeassistant", "update_entity", diff --git a/tests/components/nanoleaf/test_config_flow.py b/tests/components/nanoleaf/test_config_flow.py index 2f3620a6a12..5fe32c81eba 100644 --- a/tests/components/nanoleaf/test_config_flow.py +++ b/tests/components/nanoleaf/test_config_flow.py @@ -109,11 +109,14 @@ async def test_user_error_setup_finish( assert result2["type"] == "form" assert result2["step_id"] == "link" - with patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", - ), patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info", - side_effect=error, + with ( + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf.authorize", + ), + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info", + side_effect=error, + ), ): result3 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result3["type"] == "abort" @@ -124,12 +127,15 @@ async def test_user_not_authorizing_new_tokens_user_step_link_step( hass: HomeAssistant, ) -> None: """Test we handle NotAuthorizingNewTokens in user step and link step.""" - with patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf", - return_value=_mock_nanoleaf(authorize_error=Unauthorized()), - ) as mock_nanoleaf, patch( - "homeassistant.components.nanoleaf.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf", + return_value=_mock_nanoleaf(authorize_error=Unauthorized()), + ) as mock_nanoleaf, + patch( + "homeassistant.components.nanoleaf.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -229,11 +235,14 @@ async def test_discovery_link_unavailable( hass: HomeAssistant, source: type, type_in_discovery_info: str ) -> None: """Test discovery and abort if device is unavailable.""" - with patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info", - ), patch( - "homeassistant.components.nanoleaf.config_flow.load_json_object", - return_value={}, + with ( + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf.get_info", + ), + patch( + "homeassistant.components.nanoleaf.config_flow.load_json_object", + return_value={}, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -277,12 +286,15 @@ async def test_reauth(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf", - return_value=_mock_nanoleaf(), - ), patch( - "homeassistant.components.nanoleaf.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf", + return_value=_mock_nanoleaf(), + ), + patch( + "homeassistant.components.nanoleaf.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -354,22 +366,28 @@ async def test_import_discovery_integration( Test removing the .nanoleaf_conf file if it was the only device in the file. Test updating the .nanoleaf_conf file if it was not the only device in the file. """ - with patch( - "homeassistant.components.nanoleaf.config_flow.load_json_object", - return_value=dict(nanoleaf_conf_file), - ), patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf", - return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN), - ), patch( - "homeassistant.components.nanoleaf.config_flow.save_json", - return_value=None, - ) as mock_save_json, patch( - "homeassistant.components.nanoleaf.config_flow.os.remove", - return_value=None, - ) as mock_remove, patch( - "homeassistant.components.nanoleaf.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nanoleaf.config_flow.load_json_object", + return_value=dict(nanoleaf_conf_file), + ), + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf", + return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN), + ), + patch( + "homeassistant.components.nanoleaf.config_flow.save_json", + return_value=None, + ) as mock_save_json, + patch( + "homeassistant.components.nanoleaf.config_flow.os.remove", + return_value=None, + ) as mock_remove, + patch( + "homeassistant.components.nanoleaf.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, @@ -404,16 +422,20 @@ async def test_import_discovery_integration( async def test_ssdp_discovery(hass: HomeAssistant) -> None: """Test SSDP discovery.""" - with patch( - "homeassistant.components.nanoleaf.config_flow.load_json_object", - return_value={}, - ), patch( - "homeassistant.components.nanoleaf.config_flow.Nanoleaf", - return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN), - ), patch( - "homeassistant.components.nanoleaf.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nanoleaf.config_flow.load_json_object", + return_value={}, + ), + patch( + "homeassistant.components.nanoleaf.config_flow.Nanoleaf", + return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN), + ), + patch( + "homeassistant.components.nanoleaf.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_SSDP}, diff --git a/tests/components/nest/test_events.py b/tests/components/nest/test_events.py index 1bd01f699cf..caa86a3d93b 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -461,9 +461,12 @@ async def test_structure_update_event( }, auth=None, ) - with patch("homeassistant.components.nest.PLATFORMS", [PLATFORM]), patch( - "homeassistant.components.nest.api.GoogleNestSubscriber", - return_value=subscriber, + with ( + patch("homeassistant.components.nest.PLATFORMS", [PLATFORM]), + patch( + "homeassistant.components.nest.api.GoogleNestSubscriber", + return_value=subscriber, + ), ): await subscriber.async_receive_event(message) await hass.async_block_till_done() diff --git a/tests/components/nest/test_init.py b/tests/components/nest/test_init.py index 2f11aed7969..3cac8649c9c 100644 --- a/tests/components/nest/test_init.py +++ b/tests/components/nest/test_init.py @@ -123,11 +123,12 @@ async def test_setup_device_manager_failure( hass: HomeAssistant, caplog, setup_base_platform ) -> None: """Test device manager api failure.""" - with patch( - "homeassistant.components.nest.api.GoogleNestSubscriber.start_async" - ), patch( - "homeassistant.components.nest.api.GoogleNestSubscriber.async_get_device_manager", - side_effect=ApiException(), + with ( + patch("homeassistant.components.nest.api.GoogleNestSubscriber.start_async"), + patch( + "homeassistant.components.nest.api.GoogleNestSubscriber.async_get_device_manager", + side_effect=ApiException(), + ), ): await setup_base_platform() @@ -227,11 +228,12 @@ async def test_remove_entry( assert entry.data.get("subscriber_id") == SUBSCRIBER_ID assert entry.data.get("project_id") == PROJECT_ID - with patch( - "homeassistant.components.nest.api.GoogleNestSubscriber.subscriber_id" - ), patch( - "homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription", - ) as delete: + with ( + patch("homeassistant.components.nest.api.GoogleNestSubscriber.subscriber_id"), + patch( + "homeassistant.components.nest.api.GoogleNestSubscriber.delete_subscription", + ) as delete, + ): assert await hass.config_entries.async_remove(entry.entry_id) assert delete.called diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index ffaddd73bb3..08c8679acf3 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -111,11 +111,13 @@ async def simulate_webhook(hass: HomeAssistant, webhook_id: str, response) -> No @contextmanager def selected_platforms(platforms: list[Platform]) -> AsyncMock: """Restrict loaded platforms to list given.""" - with patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", platforms - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", platforms), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): yield diff --git a/tests/components/netatmo/test_camera.py b/tests/components/netatmo/test_camera.py index 771bd62de1c..c7398d64e1d 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -382,9 +382,10 @@ async def test_service_set_camera_light_invalid_type( "camera_light_mode": "on", } - with patch("pyatmo.home.Home.async_set_state") as mock_set_state, pytest.raises( - HomeAssistantError - ) as excinfo: + with ( + patch("pyatmo.home.Home.async_set_state") as mock_set_state, + pytest.raises(HomeAssistantError) as excinfo, + ): await hass.services.async_call( "netatmo", SERVICE_SET_CAMERA_LIGHT, @@ -409,15 +410,18 @@ async def test_camera_reconnect_webhook( fake_post_hits += 1 return await fake_post_request(*args, **kwargs) - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", ["camera"] - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", - ) as mock_webhook: + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", ["camera"]), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ) as mock_webhook, + ): mock_auth.return_value.async_post_api_request.side_effect = fake_post mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() @@ -505,14 +509,17 @@ async def test_setup_component_no_devices( fake_post_hits += 1 return await fake_post_request(*args, **kwargs) - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", ["camera"] - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", ["camera"]), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_no_data mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() @@ -545,14 +552,17 @@ async def test_camera_image_raises_exception( return await fake_post_request(*args, **kwargs) - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", ["camera"] - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", ["camera"]), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): mock_auth.return_value.async_post_api_request.side_effect = fake_post mock_auth.return_value.async_get_image.side_effect = fake_post diff --git a/tests/components/netatmo/test_diagnostics.py b/tests/components/netatmo/test_diagnostics.py index 7c781e7522c..48f021295e1 100644 --- a/tests/components/netatmo/test_diagnostics.py +++ b/tests/components/netatmo/test_diagnostics.py @@ -22,12 +22,16 @@ async def test_entry_diagnostics( config_entry: MockConfigEntry, ) -> None: """Test config entry diagnostics.""" - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", - ) as mock_auth, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", + ) as mock_auth, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() diff --git a/tests/components/netatmo/test_init.py b/tests/components/netatmo/test_init.py index 4e82d7f1b59..e4869b73e2e 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -62,13 +62,15 @@ async def test_setup_component( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test setup and teardown of the netatmo component.""" - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", - ) as mock_auth, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as mock_impl, patch( - "homeassistant.components.netatmo.webhook_generate_url" - ) as mock_webhook: + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", + ) as mock_auth, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, + patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook, + ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() @@ -104,14 +106,15 @@ async def test_setup_component_with_config( fake_post_hits += 1 return await fake_post_request(*args, **kwargs) - with patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as mock_impl, patch( - "homeassistant.components.netatmo.webhook_generate_url" - ) as mock_webhook, patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", ["sensor"] + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, + patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook, + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", ["sensor"]), ): mock_auth.return_value.async_post_api_request.side_effect = fake_post mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() @@ -169,16 +172,21 @@ async def test_setup_without_https( ) -> None: """Test if set up with cloud link and without https.""" hass.config.components.add("cloud") - with patch( - "homeassistant.helpers.network.get_url", - return_value="http://example.nabu.casa", - ), patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url" - ) as mock_async_generate_url: + with ( + patch( + "homeassistant.helpers.network.get_url", + return_value="http://example.nabu.casa", + ), + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url" + ) as mock_async_generate_url, + ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request mock_async_generate_url.return_value = "http://example.com" assert await async_setup_component( @@ -199,23 +207,27 @@ async def test_setup_with_cloud( await mock_cloud(hass) await hass.async_block_till_done() - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch.object(cloud, "async_is_connected", return_value=True), patch.object( - cloud, "async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ) as fake_create_cloudhook, patch( - "homeassistant.components.cloud.async_delete_cloudhook" - ) as fake_delete_cloudhook, patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", [] - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch.object(cloud, "async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ) as fake_create_cloudhook, + patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ) as fake_delete_cloudhook, + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", []), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request assert await async_setup_component( @@ -263,23 +275,27 @@ async def test_setup_with_cloudhook(hass: HomeAssistant) -> None: await mock_cloud(hass) await hass.async_block_till_done() - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch.object(cloud, "async_active_subscription", return_value=True), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ) as fake_create_cloudhook, patch( - "homeassistant.components.cloud.async_delete_cloudhook" - ) as fake_delete_cloudhook, patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", [] - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ) as fake_create_cloudhook, + patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ) as fake_delete_cloudhook, + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", []), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() @@ -310,18 +326,22 @@ async def test_setup_component_with_delay( """Test setup of the netatmo component with delayed startup.""" hass.set_state(CoreState.not_running) - with patch( - "pyatmo.AbstractAsyncAuth.async_addwebhook", side_effect=AsyncMock() - ) as mock_addwebhook, patch( - "pyatmo.AbstractAsyncAuth.async_dropwebhook", side_effect=AsyncMock() - ) as mock_dropwebhook, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as mock_impl, patch( - "homeassistant.components.netatmo.webhook_generate_url" - ) as mock_webhook, patch( - "pyatmo.AbstractAsyncAuth.async_post_api_request", side_effect=fake_post_request - ) as mock_post_api_request, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", ["light"] + with ( + patch( + "pyatmo.AbstractAsyncAuth.async_addwebhook", side_effect=AsyncMock() + ) as mock_addwebhook, + patch( + "pyatmo.AbstractAsyncAuth.async_dropwebhook", side_effect=AsyncMock() + ) as mock_dropwebhook, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, + patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook, + patch( + "pyatmo.AbstractAsyncAuth.async_post_api_request", + side_effect=fake_post_request, + ) as mock_post_api_request, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", ["light"]), ): assert await async_setup_component( hass, "netatmo", {"netatmo": {"client_id": "123", "client_secret": "abc"}} @@ -385,13 +405,15 @@ async def test_setup_component_invalid_token_scope(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", - ) as mock_auth, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as mock_impl, patch( - "homeassistant.components.netatmo.webhook_generate_url" - ) as mock_webhook: + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", + ) as mock_auth, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, + patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook, + ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() @@ -431,15 +453,18 @@ async def test_setup_component_invalid_token( history=(), ) - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", - ) as mock_auth, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ) as mock_impl, patch( - "homeassistant.components.netatmo.webhook_generate_url" - ) as mock_webhook, patch( - "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session" - ) as mock_session: + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth", + ) as mock_auth, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ) as mock_impl, + patch("homeassistant.components.netatmo.webhook_generate_url") as mock_webhook, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session" + ) as mock_session, + ): mock_auth.return_value.async_post_api_request.side_effect = fake_post_request mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() diff --git a/tests/components/netatmo/test_light.py b/tests/components/netatmo/test_light.py index 5ae368522a3..c90d67e7630 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -123,14 +123,17 @@ async def test_setup_component_no_devices(hass: HomeAssistant, config_entry) -> json={}, ) - with patch( - "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" - ) as mock_auth, patch( - "homeassistant.components.netatmo.data_handler.PLATFORMS", ["light"] - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.netatmo.webhook_generate_url", + with ( + patch( + "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" + ) as mock_auth, + patch("homeassistant.components.netatmo.data_handler.PLATFORMS", ["light"]), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.netatmo.webhook_generate_url", + ), ): mock_auth.return_value.async_post_api_request.side_effect = ( fake_post_request_no_data diff --git a/tests/components/netgear/test_config_flow.py b/tests/components/netgear/test_config_flow.py index 34df3a50c1b..c0649d3646e 100644 --- a/tests/components/netgear/test_config_flow.py +++ b/tests/components/netgear/test_config_flow.py @@ -68,9 +68,10 @@ SSDP_URL_SLL = f"https://{HOST}:{PORT}/rootDesc.xml" @pytest.fixture(name="service") def mock_controller_service(): """Mock a successful service.""" - with patch( - "homeassistant.components.netgear.async_setup_entry", return_value=True - ), patch("homeassistant.components.netgear.router.Netgear") as service_mock: + with ( + patch("homeassistant.components.netgear.async_setup_entry", return_value=True), + patch("homeassistant.components.netgear.router.Netgear") as service_mock, + ): service_mock.return_value.get_info = Mock(return_value=ROUTER_INFOS) service_mock.return_value.port = 80 service_mock.return_value.ssl = False diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index 7aea83a84e2..b02692e5086 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -75,12 +75,15 @@ async def test_async_detect_interfaces_setting_non_loopback_route( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route returns a non-loopback address.""" - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -139,12 +142,15 @@ async def test_async_detect_interfaces_setting_loopback_route( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route returns a loopback address.""" - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_LOOPBACK_IPADDR]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([_LOOPBACK_IPADDR]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -202,12 +208,15 @@ async def test_async_detect_interfaces_setting_empty_route( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route returns nothing.""" - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -265,12 +274,15 @@ async def test_async_detect_interfaces_setting_exception( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Test without default interface config and the route throws an exception.""" - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket_exception(AttributeError), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket_exception(AttributeError), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -333,12 +345,15 @@ async def test_interfaces_configured_from_storage( "key": STORAGE_KEY, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]}, } - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -402,12 +417,15 @@ async def test_interfaces_configured_from_storage_websocket_update( "key": STORAGE_KEY, "data": {ATTR_CONFIGURED_ADAPTERS: ["eth0", "eth1", "vtun0"]}, } - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([_NO_LOOPBACK_IPADDR]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -533,12 +551,15 @@ async def test_async_get_source_ip_matching_interface( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), + with ( + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket(["192.168.1.5"]), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -556,12 +577,15 @@ async def test_async_get_source_ip_interface_not_match( "data": {ATTR_CONFIGURED_ADAPTERS: ["vtun0"]}, } - with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), + with ( + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket(["192.168.1.5"]), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -579,12 +603,15 @@ async def test_async_get_source_ip_cannot_determine_target( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), - ), patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([None]), + with ( + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([None]), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -602,12 +629,15 @@ async def test_async_get_ipv4_broadcast_addresses_default( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket(["192.168.1.5"]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -627,12 +657,15 @@ async def test_async_get_ipv4_broadcast_addresses_multiple( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1", "vtun0"]}, } - with patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([_LOOPBACK_IPADDR]), - ), patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=_generate_mock_adapters(), + with ( + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([_LOOPBACK_IPADDR]), + ), + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=_generate_mock_adapters(), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -654,12 +687,15 @@ async def test_async_get_source_ip_no_enabled_addresses( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=[], - ), patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket(["192.168.1.5"]), + with ( + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], + ), + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket(["192.168.1.5"]), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -679,12 +715,15 @@ async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=[], - ), patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_socket([None]), + with ( + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], + ), + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([None]), + ), ): assert not await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -702,12 +741,15 @@ async def test_async_get_source_ip_no_ip_loopback( "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, } - with patch( - "homeassistant.components.network.util.ifaddr.get_adapters", - return_value=[], - ), patch( - "homeassistant.components.network.util.socket.socket", - return_value=_mock_cond_socket(_LOOPBACK_IPADDR), + with ( + patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], + ), + patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_cond_socket(_LOOPBACK_IPADDR), + ), ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -778,12 +820,15 @@ _ADAPTERS_WITH_MANUAL_CONFIG = [ async def test_async_get_announce_addresses(hass: HomeAssistant) -> None: """Test addresses for mDNS/etc announcement.""" first_ip = "172.16.1.5" - with patch( - "homeassistant.components.network.async_get_source_ip", - return_value=first_ip, - ), patch( - "homeassistant.components.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + with ( + patch( + "homeassistant.components.network.async_get_source_ip", + return_value=first_ip, + ), + patch( + "homeassistant.components.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ), ): actual = await network.async_get_announce_addresses(hass) assert actual[0] == first_ip and actual == [ @@ -795,12 +840,15 @@ async def test_async_get_announce_addresses(hass: HomeAssistant) -> None: ] first_ip = "192.168.1.5" - with patch( - "homeassistant.components.network.async_get_source_ip", - return_value=first_ip, - ), patch( - "homeassistant.components.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + with ( + patch( + "homeassistant.components.network.async_get_source_ip", + return_value=first_ip, + ), + patch( + "homeassistant.components.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ), ): actual = await network.async_get_announce_addresses(hass) @@ -815,12 +863,15 @@ async def test_async_get_announce_addresses(hass: HomeAssistant) -> None: async def test_async_get_announce_addresses_no_source_ip(hass: HomeAssistant) -> None: """Test addresses for mDNS/etc announcement without source ip.""" - with patch( - "homeassistant.components.network.async_get_source_ip", - return_value=None, - ), patch( - "homeassistant.components.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + with ( + patch( + "homeassistant.components.network.async_get_source_ip", + return_value=None, + ), + patch( + "homeassistant.components.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ), ): actual = await network.async_get_announce_addresses(hass) assert actual == [ diff --git a/tests/components/nexia/test_config_flow.py b/tests/components/nexia/test_config_flow.py index 7ed5ecec675..02a3cf06728 100644 --- a/tests/components/nexia/test_config_flow.py +++ b/tests/components/nexia/test_config_flow.py @@ -22,16 +22,20 @@ async def test_form(hass: HomeAssistant, brand) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.nexia.config_flow.NexiaHome.get_name", - return_value="myhouse", - ), patch( - "homeassistant.components.nexia.config_flow.NexiaHome.login", - side_effect=MagicMock(), - ), patch( - "homeassistant.components.nexia.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nexia.config_flow.NexiaHome.get_name", + return_value="myhouse", + ), + patch( + "homeassistant.components.nexia.config_flow.NexiaHome.login", + side_effect=MagicMock(), + ), + patch( + "homeassistant.components.nexia.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_BRAND: brand, CONF_USERNAME: "username", CONF_PASSWORD: "password"}, @@ -54,11 +58,14 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.nexia.config_flow.NexiaHome.login", - ), patch( - "homeassistant.components.nexia.config_flow.NexiaHome.get_name", - return_value=None, + with ( + patch( + "homeassistant.components.nexia.config_flow.NexiaHome.login", + ), + patch( + "homeassistant.components.nexia.config_flow.NexiaHome.get_name", + return_value=None, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/nexia/util.py b/tests/components/nexia/util.py index f49058e0033..98d5312f0a1 100644 --- a/tests/components/nexia/util.py +++ b/tests/components/nexia/util.py @@ -24,8 +24,9 @@ async def async_init_integration( session_fixture = "nexia/session_123456.json" sign_in_fixture = "nexia/sign_in.json" set_fan_speed_fixture = "nexia/set_fan_speed_2293892.json" - with mock_aiohttp_client() as mock_session, patch( - "nexia.home.load_or_create_uuid", return_value=uuid.uuid4() + with ( + mock_aiohttp_client() as mock_session, + patch("nexia.home.load_or_create_uuid", return_value=uuid.uuid4()), ): nexia = NexiaHome(mock_session) if exception: diff --git a/tests/components/nextdns/__init__.py b/tests/components/nextdns/__init__.py index d741811acf0..e4948a9358f 100644 --- a/tests/components/nextdns/__init__.py +++ b/tests/components/nextdns/__init__.py @@ -123,29 +123,39 @@ async def init_integration(hass: HomeAssistant) -> MockConfigEntry: entry_id="d9aa37407ddac7b964a99e86312288d6", ) - with patch( - "homeassistant.components.nextdns.NextDns.get_profiles", return_value=PROFILES - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_status", - return_value=STATUS, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_encryption", - return_value=ENCRYPTION, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_dnssec", - return_value=DNSSEC, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_ip_versions", - return_value=IP_VERSIONS, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_protocols", - return_value=PROTOCOLS, - ), patch( - "homeassistant.components.nextdns.NextDns.get_settings", - return_value=SETTINGS, - ), patch( - "homeassistant.components.nextdns.NextDns.connection_status", - return_value=CONNECTION_STATUS, + with ( + patch( + "homeassistant.components.nextdns.NextDns.get_profiles", + return_value=PROFILES, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_status", + return_value=STATUS, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_encryption", + return_value=ENCRYPTION, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_dnssec", + return_value=DNSSEC, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_ip_versions", + return_value=IP_VERSIONS, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_protocols", + return_value=PROTOCOLS, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_settings", + return_value=SETTINGS, + ), + patch( + "homeassistant.components.nextdns.NextDns.connection_status", + return_value=CONNECTION_STATUS, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/nextdns/test_button.py b/tests/components/nextdns/test_button.py index 66c83169813..b5f7b01aee2 100644 --- a/tests/components/nextdns/test_button.py +++ b/tests/components/nextdns/test_button.py @@ -31,9 +31,10 @@ async def test_button_press(hass: HomeAssistant) -> None: await init_integration(hass) now = dt_util.utcnow() - with patch( - "homeassistant.components.nextdns.NextDns.clear_logs" - ) as mock_clear_logs, patch("homeassistant.core.dt_util.utcnow", return_value=now): + with ( + patch("homeassistant.components.nextdns.NextDns.clear_logs") as mock_clear_logs, + patch("homeassistant.core.dt_util.utcnow", return_value=now), + ): await hass.services.async_call( BUTTON_DOMAIN, "press", diff --git a/tests/components/nextdns/test_config_flow.py b/tests/components/nextdns/test_config_flow.py index e1b669c17e8..4d9961474c5 100644 --- a/tests/components/nextdns/test_config_flow.py +++ b/tests/components/nextdns/test_config_flow.py @@ -23,11 +23,15 @@ async def test_form_create_entry(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.nextdns.NextDns.get_profiles", return_value=PROFILES - ), patch( - "homeassistant.components.nextdns.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nextdns.NextDns.get_profiles", + return_value=PROFILES, + ), + patch( + "homeassistant.components.nextdns.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: "fake_api_key"}, diff --git a/tests/components/nextdns/test_sensor.py b/tests/components/nextdns/test_sensor.py index de303fd6cad..a6d9b4c545f 100644 --- a/tests/components/nextdns/test_sensor.py +++ b/tests/components/nextdns/test_sensor.py @@ -309,21 +309,27 @@ async def test_availability( assert state.state == "90" future = utcnow() + timedelta(minutes=10) - with patch( - "homeassistant.components.nextdns.NextDns.get_analytics_status", - side_effect=ApiError("API Error"), - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_dnssec", - side_effect=ApiError("API Error"), - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_encryption", - side_effect=ApiError("API Error"), - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_ip_versions", - side_effect=ApiError("API Error"), - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_protocols", - side_effect=ApiError("API Error"), + with ( + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_status", + side_effect=ApiError("API Error"), + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_dnssec", + side_effect=ApiError("API Error"), + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_encryption", + side_effect=ApiError("API Error"), + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_ip_versions", + side_effect=ApiError("API Error"), + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_protocols", + side_effect=ApiError("API Error"), + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -349,21 +355,27 @@ async def test_availability( assert state.state == STATE_UNAVAILABLE future = utcnow() + timedelta(minutes=20) - with patch( - "homeassistant.components.nextdns.NextDns.get_analytics_status", - return_value=STATUS, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_encryption", - return_value=ENCRYPTION, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_dnssec", - return_value=DNSSEC, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_ip_versions", - return_value=IP_VERSIONS, - ), patch( - "homeassistant.components.nextdns.NextDns.get_analytics_protocols", - return_value=PROTOCOLS, + with ( + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_status", + return_value=STATUS, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_encryption", + return_value=ENCRYPTION, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_dnssec", + return_value=DNSSEC, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_ip_versions", + return_value=IP_VERSIONS, + ), + patch( + "homeassistant.components.nextdns.NextDns.get_analytics_protocols", + return_value=PROTOCOLS, + ), ): async_fire_time_changed(hass, future) await hass.async_block_till_done() diff --git a/tests/components/nextdns/test_switch.py b/tests/components/nextdns/test_switch.py index 7e1d592c1f4..f51ee32fd10 100644 --- a/tests/components/nextdns/test_switch.py +++ b/tests/components/nextdns/test_switch.py @@ -726,9 +726,10 @@ async def test_switch_failure(hass: HomeAssistant, exc: Exception) -> None: """Tests that the turn on/off service throws HomeAssistantError.""" await init_integration(hass) - with patch( - "homeassistant.components.nextdns.NextDns.set_setting", side_effect=exc - ), pytest.raises(HomeAssistantError): + with ( + patch("homeassistant.components.nextdns.NextDns.set_setting", side_effect=exc), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, diff --git a/tests/components/nibe_heatpump/conftest.py b/tests/components/nibe_heatpump/conftest.py index 010a79980c4..00d4c92c68b 100644 --- a/tests/components/nibe_heatpump/conftest.py +++ b/tests/components/nibe_heatpump/conftest.py @@ -71,8 +71,9 @@ async def fixture_coils(mock_connection: MockConnection): raise CoilNotFoundException return coils_data - with patch.object(HeatPump, "get_coils", new=get_coils), patch.object( - HeatPump, "get_coil_by_address", new=get_coil_by_address + with ( + patch.object(HeatPump, "get_coils", new=get_coils), + patch.object(HeatPump, "get_coil_by_address", new=get_coil_by_address), ): yield mock_connection.coils diff --git a/tests/components/nightscout/__init__.py b/tests/components/nightscout/__init__.py index 3433e889b92..da421d5bba9 100644 --- a/tests/components/nightscout/__init__.py +++ b/tests/components/nightscout/__init__.py @@ -36,12 +36,15 @@ async def init_integration(hass) -> MockConfigEntry: domain=DOMAIN, data={CONF_URL: "https://some.url:1234"}, ) - with patch( - "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", - return_value=GLUCOSE_READINGS, - ), patch( - "homeassistant.components.nightscout.NightscoutAPI.get_server_status", - return_value=SERVER_STATUS, + with ( + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", + return_value=GLUCOSE_READINGS, + ), + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_server_status", + return_value=SERVER_STATUS, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -56,12 +59,15 @@ async def init_integration_unavailable(hass) -> MockConfigEntry: domain=DOMAIN, data={CONF_URL: "https://some.url:1234"}, ) - with patch( - "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", - side_effect=ClientConnectionError(), - ), patch( - "homeassistant.components.nightscout.NightscoutAPI.get_server_status", - return_value=SERVER_STATUS, + with ( + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", + side_effect=ClientConnectionError(), + ), + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_server_status", + return_value=SERVER_STATUS, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -76,11 +82,15 @@ async def init_integration_empty_response(hass) -> MockConfigEntry: domain=DOMAIN, data={CONF_URL: "https://some.url:1234"}, ) - with patch( - "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", return_value=[] - ), patch( - "homeassistant.components.nightscout.NightscoutAPI.get_server_status", - return_value=SERVER_STATUS, + with ( + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", + return_value=[], + ), + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_server_status", + return_value=SERVER_STATUS, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/nightscout/test_config_flow.py b/tests/components/nightscout/test_config_flow.py index 43f238ffa13..c3723596a84 100644 --- a/tests/components/nightscout/test_config_flow.py +++ b/tests/components/nightscout/test_config_flow.py @@ -27,7 +27,11 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with _patch_glucose_readings(), _patch_server_status(), _patch_async_setup_entry() as mock_setup_entry: + with ( + _patch_glucose_readings(), + _patch_server_status(), + _patch_async_setup_entry() as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG, @@ -65,12 +69,15 @@ async def test_user_form_api_key_required(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.nightscout.NightscoutAPI.get_server_status", - return_value=SERVER_STATUS_STATUS_ONLY, - ), patch( - "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", - side_effect=ClientResponseError(None, None, status=HTTPStatus.UNAUTHORIZED), + with ( + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_server_status", + return_value=SERVER_STATUS_STATUS_ONLY, + ), + patch( + "homeassistant.components.nightscout.NightscoutAPI.get_sgvs", + side_effect=ClientResponseError(None, None, status=HTTPStatus.UNAUTHORIZED), + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/nina/test_config_flow.py b/tests/components/nina/test_config_flow.py index 10de928feb8..d3c44258c23 100644 --- a/tests/components/nina/test_config_flow.py +++ b/tests/components/nina/test_config_flow.py @@ -94,12 +94,15 @@ async def test_step_user_unexpected_exception(hass: HomeAssistant) -> None: async def test_step_user(hass: HomeAssistant) -> None: """Test starting a flow by user with valid values.""" - with patch( - "pynina.baseApi.BaseAPI._makeRequest", - wraps=mocked_request_function, - ), patch( - "homeassistant.components.nina.async_setup_entry", - return_value=True, + with ( + patch( + "pynina.baseApi.BaseAPI._makeRequest", + wraps=mocked_request_function, + ), + patch( + "homeassistant.components.nina.async_setup_entry", + return_value=True, + ), ): result: dict[str, Any] = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=deepcopy(DUMMY_DATA) @@ -157,11 +160,12 @@ async def test_options_flow_init(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.nina.async_setup_entry", return_value=True - ), patch( - "pynina.baseApi.BaseAPI._makeRequest", - wraps=mocked_request_function, + with ( + patch("homeassistant.components.nina.async_setup_entry", return_value=True), + patch( + "pynina.baseApi.BaseAPI._makeRequest", + wraps=mocked_request_function, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -211,11 +215,12 @@ async def test_options_flow_with_no_selection(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.nina.async_setup_entry", return_value=True - ), patch( - "pynina.baseApi.BaseAPI._makeRequest", - wraps=mocked_request_function, + with ( + patch("homeassistant.components.nina.async_setup_entry", return_value=True), + patch( + "pynina.baseApi.BaseAPI._makeRequest", + wraps=mocked_request_function, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -252,12 +257,15 @@ async def test_options_flow_connection_error(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "pynina.baseApi.BaseAPI._makeRequest", - side_effect=ApiError("Could not connect to Api"), - ), patch( - "homeassistant.components.nina.async_setup_entry", - return_value=True, + with ( + patch( + "pynina.baseApi.BaseAPI._makeRequest", + side_effect=ApiError("Could not connect to Api"), + ), + patch( + "homeassistant.components.nina.async_setup_entry", + return_value=True, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -277,12 +285,15 @@ async def test_options_flow_unexpected_exception(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "pynina.baseApi.BaseAPI._makeRequest", - side_effect=Exception("DUMMY"), - ), patch( - "homeassistant.components.nina.async_setup_entry", - return_value=True, + with ( + patch( + "pynina.baseApi.BaseAPI._makeRequest", + side_effect=Exception("DUMMY"), + ), + patch( + "homeassistant.components.nina.async_setup_entry", + return_value=True, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -301,12 +312,15 @@ async def test_options_flow_entity_removal(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "pynina.baseApi.BaseAPI._makeRequest", - wraps=mocked_request_function, - ), patch( - "homeassistant.components.nina._async_update_listener" - ) as mock_update_listener: + with ( + patch( + "pynina.baseApi.BaseAPI._makeRequest", + wraps=mocked_request_function, + ), + patch( + "homeassistant.components.nina._async_update_listener" + ) as mock_update_listener, + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/nobo_hub/test_config_flow.py b/tests/components/nobo_hub/test_config_flow.py index eed287abbb0..1d6feb6e28a 100644 --- a/tests/components/nobo_hub/test_config_flow.py +++ b/tests/components/nobo_hub/test_config_flow.py @@ -31,17 +31,19 @@ async def test_configure_with_discover(hass: HomeAssistant) -> None: assert result2["errors"] == {} assert result2["step_id"] == "selected" - with patch( - "pynobo.nobo.async_connect_hub", return_value=True - ) as mock_connect, patch( - "pynobo.nobo.hub_info", - new_callable=PropertyMock, - create=True, - return_value={"name": "My Nobø Ecohub"}, - ), patch( - "homeassistant.components.nobo_hub.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("pynobo.nobo.async_connect_hub", return_value=True) as mock_connect, + patch( + "pynobo.nobo.hub_info", + new_callable=PropertyMock, + create=True, + return_value={"name": "My Nobø Ecohub"}, + ), + patch( + "homeassistant.components.nobo_hub.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -74,17 +76,19 @@ async def test_configure_manual(hass: HomeAssistant) -> None: assert result["errors"] == {} assert result["step_id"] == "manual" - with patch( - "pynobo.nobo.async_connect_hub", return_value=True - ) as mock_connect, patch( - "pynobo.nobo.hub_info", - new_callable=PropertyMock, - create=True, - return_value={"name": "My Nobø Ecohub"}, - ), patch( - "homeassistant.components.nobo_hub.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("pynobo.nobo.async_connect_hub", return_value=True) as mock_connect, + patch( + "pynobo.nobo.hub_info", + new_callable=PropertyMock, + create=True, + return_value={"name": "My Nobø Ecohub"}, + ), + patch( + "homeassistant.components.nobo_hub.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -125,17 +129,19 @@ async def test_configure_user_selected_manual(hass: HomeAssistant) -> None: assert result2["errors"] == {} assert result2["step_id"] == "manual" - with patch( - "pynobo.nobo.async_connect_hub", return_value=True - ) as mock_connect, patch( - "pynobo.nobo.hub_info", - new_callable=PropertyMock, - create=True, - return_value={"name": "My Nobø Ecohub"}, - ), patch( - "homeassistant.components.nobo_hub.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("pynobo.nobo.async_connect_hub", return_value=True) as mock_connect, + patch( + "pynobo.nobo.hub_info", + new_callable=PropertyMock, + create=True, + return_value={"name": "My Nobø Ecohub"}, + ), + patch( + "homeassistant.components.nobo_hub.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/notion/conftest.py b/tests/components/notion/conftest.py index 3f98491ddb6..e69905ed72c 100644 --- a/tests/components/notion/conftest.py +++ b/tests/components/notion/conftest.py @@ -120,15 +120,19 @@ def get_client_fixture(client): @pytest.fixture(name="mock_aionotion") async def mock_aionotion_fixture(client): """Define a fixture to patch aionotion.""" - with patch( - "homeassistant.components.notion.async_get_client_with_credentials", - AsyncMock(return_value=client), - ), patch( - "homeassistant.components.notion.async_get_client_with_refresh_token", - AsyncMock(return_value=client), - ), patch( - "homeassistant.components.notion.config_flow.async_get_client_with_credentials", - AsyncMock(return_value=client), + with ( + patch( + "homeassistant.components.notion.async_get_client_with_credentials", + AsyncMock(return_value=client), + ), + patch( + "homeassistant.components.notion.async_get_client_with_refresh_token", + AsyncMock(return_value=client), + ), + patch( + "homeassistant.components.notion.config_flow.async_get_client_with_credentials", + AsyncMock(return_value=client), + ), ): yield diff --git a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py index f329e456db1..ad987325b97 100644 --- a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py +++ b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py @@ -231,12 +231,13 @@ async def test_setup_with_custom_location(hass: HomeAssistant) -> None: # Set up some mock feed entries for this test. mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 20.5, (-31.1, 150.1)) - with patch( - "aio_geojson_nsw_rfs_incidents.feed_manager.NswRuralFireServiceIncidentsFeed", - wraps=NswRuralFireServiceIncidentsFeed, - ) as mock_feed_manager, patch( - "aio_geojson_client.feed.GeoJsonFeed.update" - ) as mock_feed_update: + with ( + patch( + "aio_geojson_nsw_rfs_incidents.feed_manager.NswRuralFireServiceIncidentsFeed", + wraps=NswRuralFireServiceIncidentsFeed, + ) as mock_feed_manager, + patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1] with assert_setup_component(1, geo_location.DOMAIN): diff --git a/tests/components/nuheat/test_config_flow.py b/tests/components/nuheat/test_config_flow.py index 37e2af0d307..1e7a6215143 100644 --- a/tests/components/nuheat/test_config_flow.py +++ b/tests/components/nuheat/test_config_flow.py @@ -24,15 +24,19 @@ async def test_form_user(hass: HomeAssistant) -> None: mock_thermostat = _get_mock_thermostat_run() - with patch( - "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.authenticate", - return_value=True, - ), patch( - "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.get_thermostat", - return_value=mock_thermostat, - ), patch( - "homeassistant.components.nuheat.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.authenticate", + return_value=True, + ), + patch( + "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.get_thermostat", + return_value=mock_thermostat, + ), + patch( + "homeassistant.components.nuheat.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -103,12 +107,15 @@ async def test_form_invalid_thermostat(hass: HomeAssistant) -> None: response_mock = MagicMock() type(response_mock).status_code = HTTPStatus.INTERNAL_SERVER_ERROR - with patch( - "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.authenticate", - return_value=True, - ), patch( - "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.get_thermostat", - side_effect=requests.HTTPError(response=response_mock), + with ( + patch( + "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.authenticate", + return_value=True, + ), + patch( + "homeassistant.components.nuheat.config_flow.nuheat.NuHeat.get_thermostat", + side_effect=requests.HTTPError(response=response_mock), + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/nuki/test_config_flow.py b/tests/components/nuki/test_config_flow.py index cc0a9b67b21..c7575f71545 100644 --- a/tests/components/nuki/test_config_flow.py +++ b/tests/components/nuki/test_config_flow.py @@ -23,13 +23,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.nuki.config_flow.NukiBridge.info", - return_value=MOCK_INFO, - ), patch( - "homeassistant.components.nuki.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + return_value=MOCK_INFO, + ), + patch( + "homeassistant.components.nuki.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -156,13 +159,16 @@ async def test_dhcp_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.nuki.config_flow.NukiBridge.info", - return_value=MOCK_INFO, - ), patch( - "homeassistant.components.nuki.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + return_value=MOCK_INFO, + ), + patch( + "homeassistant.components.nuki.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -209,12 +215,15 @@ async def test_reauth_success(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "reauth_confirm" - with patch( - "homeassistant.components.nuki.config_flow.NukiBridge.info", - return_value=MOCK_INFO, - ), patch( - "homeassistant.components.nuki.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.nuki.config_flow.NukiBridge.info", + return_value=MOCK_INFO, + ), + patch( + "homeassistant.components.nuki.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index 0fd9949ff37..56a7d7d9089 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -55,13 +55,16 @@ async def test_form_zeroconf(hass: HomeAssistant) -> None: list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}, @@ -93,13 +96,16 @@ async def test_form_user_one_ups(hass: HomeAssistant) -> None: list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -160,13 +166,16 @@ async def test_form_user_multiple_ups(hass: HomeAssistant) -> None: assert result2["step_id"] == "ups" assert result2["type"] == data_entry_flow.FlowResultType.FORM - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {CONF_ALIAS: "ups2"}, @@ -203,13 +212,16 @@ async def test_form_user_one_ups_with_ignored_entry(hass: HomeAssistant) -> None list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -264,12 +276,15 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.nut.AIONUTClient.list_ups", - side_effect=NUTError("no route to host"), - ), patch( - "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=NUTError("no route to host"), + with ( + patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + side_effect=NUTError("no route to host"), + ), + patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTError("no route to host"), + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -285,12 +300,15 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} assert result2["description_placeholders"] == {"error": "no route to host"} - with patch( - "homeassistant.components.nut.AIONUTClient.list_ups", - return_value={"ups1"}, - ), patch( - "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=Exception, + with ( + patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + return_value={"ups1"}, + ), + patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=Exception, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -308,13 +326,16 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -343,12 +364,15 @@ async def test_auth_failures(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.nut.AIONUTClient.list_ups", - side_effect=NUTLoginError, - ), patch( - "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=NUTLoginError, + with ( + patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + side_effect=NUTLoginError, + ), + patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTLoginError, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -366,13 +390,16 @@ async def test_auth_failures(hass: HomeAssistant) -> None: mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -412,12 +439,15 @@ async def test_reauth(hass: HomeAssistant) -> None: assert len(flows) == 1 flow = flows[0] - with patch( - "homeassistant.components.nut.AIONUTClient.list_ups", - side_effect=NUTLoginError, - ), patch( - "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=NUTLoginError, + with ( + patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + side_effect=NUTLoginError, + ), + patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTLoginError, + ), ): result2 = await hass.config_entries.flow.async_configure( flow["flow_id"], @@ -433,13 +463,16 @@ async def test_reauth(hass: HomeAssistant) -> None: mock_pynut = _get_mock_nutclient( list_vars={"battery.voltage": "voltage", "ups.status": "OL"}, list_ups=["ups1"] ) - with patch( - "homeassistant.components.nut.AIONUTClient", - return_value=mock_pynut, - ), patch( - "homeassistant.components.nut.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.nut.AIONUTClient", + return_value=mock_pynut, + ), + patch( + "homeassistant.components.nut.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( flow["flow_id"], { diff --git a/tests/components/nut/test_init.py b/tests/components/nut/test_init.py index 4dd5f2357e8..61a5187407b 100644 --- a/tests/components/nut/test_init.py +++ b/tests/components/nut/test_init.py @@ -56,12 +56,15 @@ async def test_config_not_ready(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.nut.AIONUTClient.list_ups", - return_value={"ups1"}, - ), patch( - "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=NUTError, + with ( + patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + return_value={"ups1"}, + ), + patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTError, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -76,12 +79,15 @@ async def test_auth_fails(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.nut.AIONUTClient.list_ups", - return_value={"ups1"}, - ), patch( - "homeassistant.components.nut.AIONUTClient.list_vars", - side_effect=NUTLoginError, + with ( + patch( + "homeassistant.components.nut.AIONUTClient.list_ups", + return_value={"ups1"}, + ), + patch( + "homeassistant.components.nut.AIONUTClient.list_vars", + side_effect=NUTLoginError, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index da365c52ffc..1d202277e6f 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -201,9 +201,10 @@ async def test_error_observation( ) -> None: """Test error during update observation.""" utc_time = dt_util.utcnow() - with patch("homeassistant.components.nws.utcnow") as mock_utc, patch( - "homeassistant.components.nws.weather.utcnow" - ) as mock_utc_weather: + with ( + patch("homeassistant.components.nws.utcnow") as mock_utc, + patch("homeassistant.components.nws.weather.utcnow") as mock_utc_weather, + ): def increment_time(time): mock_utc.return_value += time diff --git a/tests/components/nzbget/test_config_flow.py b/tests/components/nzbget/test_config_flow.py index 56ca19b0c95..c299d1d6dd5 100644 --- a/tests/components/nzbget/test_config_flow.py +++ b/tests/components/nzbget/test_config_flow.py @@ -31,7 +31,12 @@ async def test_user_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with _patch_version(), _patch_status(), _patch_history(), _patch_async_setup_entry() as mock_setup_entry: + with ( + _patch_version(), + _patch_status(), + _patch_history(), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, @@ -59,7 +64,12 @@ async def test_user_form_show_advanced_options(hass: HomeAssistant) -> None: CONF_VERIFY_SSL: True, } - with _patch_version(), _patch_status(), _patch_history(), _patch_async_setup_entry() as mock_setup_entry: + with ( + _patch_version(), + _patch_status(), + _patch_history(), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input_advanced, diff --git a/tests/components/nzbget/test_init.py b/tests/components/nzbget/test_init.py index a12cacf944e..a119bb953ce 100644 --- a/tests/components/nzbget/test_init.py +++ b/tests/components/nzbget/test_init.py @@ -32,9 +32,12 @@ async def test_async_setup_raises_entry_not_ready(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=ENTRY_CONFIG) config_entry.add_to_hass(hass) - with _patch_version(), patch( - "homeassistant.components.nzbget.coordinator.NZBGetAPI.status", - side_effect=NZBGetAPIException(), + with ( + _patch_version(), + patch( + "homeassistant.components.nzbget.coordinator.NZBGetAPI.status", + side_effect=NZBGetAPIException(), + ), ): await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/octoprint/__init__.py b/tests/components/octoprint/__init__.py index d97e4b1ce21..4a896736329 100644 --- a/tests/components/octoprint/__init__.py +++ b/tests/components/octoprint/__init__.py @@ -46,20 +46,25 @@ async def init_integration( printer_info = OctoprintPrinterInfo(printer) if job is None: job = DEFAULT_JOB - with patch("homeassistant.components.octoprint.PLATFORMS", [platform]), patch( - "pyoctoprintapi.OctoprintClient.get_server_info", return_value={} - ), patch( - "pyoctoprintapi.OctoprintClient.get_printer_info", - return_value=printer_info, - ), patch( - "pyoctoprintapi.OctoprintClient.get_job_info", - return_value=OctoprintJobInfo(job), - ), patch( - "pyoctoprintapi.OctoprintClient.get_tracking_info", - return_value=TrackingSetting({"unique_id": "uuid"}), - ), patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), + with ( + patch("homeassistant.components.octoprint.PLATFORMS", [platform]), + patch("pyoctoprintapi.OctoprintClient.get_server_info", return_value={}), + patch( + "pyoctoprintapi.OctoprintClient.get_printer_info", + return_value=printer_info, + ), + patch( + "pyoctoprintapi.OctoprintClient.get_job_info", + return_value=OctoprintJobInfo(job), + ), + patch( + "pyoctoprintapi.OctoprintClient.get_tracking_info", + return_value=TrackingSetting({"unique_id": "uuid"}), + ), + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), ): config_entry = MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/octoprint/test_button.py b/tests/components/octoprint/test_button.py index 39e8fa5886c..7f272f9927e 100644 --- a/tests/components/octoprint/test_button.py +++ b/tests/components/octoprint/test_button.py @@ -57,9 +57,10 @@ async def test_pause_job(hass: HomeAssistant) -> None: assert len(pause_command.mock_calls) == 0 # Test pausing the printer when it is stopped - with patch( - "pyoctoprintapi.OctoprintClient.pause_job" - ) as pause_command, pytest.raises(InvalidPrinterState): + with ( + patch("pyoctoprintapi.OctoprintClient.pause_job") as pause_command, + pytest.raises(InvalidPrinterState), + ): coordinator.data["printer"] = OctoprintPrinterInfo( { "state": {"flags": {"printing": False, "paused": False}}, @@ -117,9 +118,10 @@ async def test_resume_job(hass: HomeAssistant) -> None: assert len(resume_command.mock_calls) == 0 # Test resuming the printer when it is stopped - with patch( - "pyoctoprintapi.OctoprintClient.resume_job" - ) as resume_command, pytest.raises(InvalidPrinterState): + with ( + patch("pyoctoprintapi.OctoprintClient.resume_job") as resume_command, + pytest.raises(InvalidPrinterState), + ): coordinator.data["printer"] = OctoprintPrinterInfo( { "state": {"flags": {"printing": False, "paused": False}}, diff --git a/tests/components/octoprint/test_config_flow.py b/tests/components/octoprint/test_config_flow.py index 95e0ab0bb2b..4c8e22e524c 100644 --- a/tests/components/octoprint/test_config_flow.py +++ b/tests/components/octoprint/test_config_flow.py @@ -38,18 +38,23 @@ async def test_form(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result["type"] == "progress" - with patch( - "pyoctoprintapi.OctoprintClient.get_server_info", - return_value=True, - ), patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), - ), patch( - "homeassistant.components.octoprint.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.octoprint.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyoctoprintapi.OctoprintClient.get_server_info", + return_value=True, + ), + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), + patch( + "homeassistant.components.octoprint.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.octoprint.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], ) @@ -208,17 +213,20 @@ async def test_show_zerconf_form(hass: HomeAssistant) -> None: assert result["type"] == "progress" - with patch( - "pyoctoprintapi.OctoprintClient.get_server_info", - return_value=True, - ), patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), - ), patch( - "homeassistant.components.octoprint.async_setup", return_value=True - ), patch( - "homeassistant.components.octoprint.async_setup_entry", - return_value=True, + with ( + patch( + "pyoctoprintapi.OctoprintClient.get_server_info", + return_value=True, + ), + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), + patch("homeassistant.components.octoprint.async_setup", return_value=True), + patch( + "homeassistant.components.octoprint.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -274,17 +282,20 @@ async def test_show_ssdp_form(hass: HomeAssistant) -> None: assert result["type"] == "progress" - with patch( - "pyoctoprintapi.OctoprintClient.get_server_info", - return_value=True, - ), patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), - ), patch( - "homeassistant.components.octoprint.async_setup", return_value=True - ), patch( - "homeassistant.components.octoprint.async_setup_entry", - return_value=True, + with ( + patch( + "pyoctoprintapi.OctoprintClient.get_server_info", + return_value=True, + ), + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), + patch("homeassistant.components.octoprint.async_setup", return_value=True), + patch( + "homeassistant.components.octoprint.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -305,19 +316,23 @@ async def test_show_ssdp_form(hass: HomeAssistant) -> None: async def test_import_yaml(hass: HomeAssistant) -> None: """Test that the yaml import works.""" - with patch( - "pyoctoprintapi.OctoprintClient.get_server_info", - return_value=True, - ), patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), - ), patch( - "pyoctoprintapi.OctoprintClient.request_app_key", return_value="test-key" - ), patch( - "homeassistant.components.octoprint.async_setup", return_value=True - ), patch( - "homeassistant.components.octoprint.async_setup_entry", - return_value=True, + with ( + patch( + "pyoctoprintapi.OctoprintClient.get_server_info", + return_value=True, + ), + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), + patch( + "pyoctoprintapi.OctoprintClient.request_app_key", return_value="test-key" + ), + patch("homeassistant.components.octoprint.async_setup", return_value=True), + patch( + "homeassistant.components.octoprint.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -345,12 +360,15 @@ async def test_import_duplicate_yaml(hass: HomeAssistant) -> None: unique_id="uuid", ).add_to_hass(hass) - with patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), - ), patch( - "pyoctoprintapi.OctoprintClient.request_app_key", return_value="test-key" - ) as request_app_key: + with ( + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), + patch( + "pyoctoprintapi.OctoprintClient.request_app_key", return_value="test-key" + ) as request_app_key, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -466,18 +484,23 @@ async def test_user_duplicate_entry(hass: HomeAssistant) -> None: await hass.async_block_till_done() assert result["type"] == "progress" - with patch( - "pyoctoprintapi.OctoprintClient.get_server_info", - return_value=True, - ), patch( - "pyoctoprintapi.OctoprintClient.get_discovery_info", - return_value=DiscoverySettings({"upnpUuid": "uuid"}), - ), patch( - "homeassistant.components.octoprint.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.octoprint.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyoctoprintapi.OctoprintClient.get_server_info", + return_value=True, + ), + patch( + "pyoctoprintapi.OctoprintClient.get_discovery_info", + return_value=DiscoverySettings({"upnpUuid": "uuid"}), + ), + patch( + "homeassistant.components.octoprint.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.octoprint.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], ) diff --git a/tests/components/omnilogic/test_config_flow.py b/tests/components/omnilogic/test_config_flow.py index efcad70e7b6..1c0d6aefcf8 100644 --- a/tests/components/omnilogic/test_config_flow.py +++ b/tests/components/omnilogic/test_config_flow.py @@ -22,13 +22,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.omnilogic.config_flow.OmniLogic.connect", - return_value=True, - ), patch( - "homeassistant.components.omnilogic.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.omnilogic.config_flow.OmniLogic.connect", + return_value=True, + ), + patch( + "homeassistant.components.omnilogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], DATA, diff --git a/tests/components/onboarding/test_init.py b/tests/components/onboarding/test_init.py index f6941098b18..6688ecccb5d 100644 --- a/tests/components/onboarding/test_init.py +++ b/tests/components/onboarding/test_init.py @@ -80,9 +80,10 @@ async def test_having_owner_finishes_user_step( """If owner user already exists, mark user step as complete.""" MockUser(is_owner=True).add_to_hass(hass) - with patch( - "homeassistant.components.onboarding.views.async_setup" - ) as mock_setup, patch.object(onboarding, "STEPS", [onboarding.STEP_USER]): + with ( + patch("homeassistant.components.onboarding.views.async_setup") as mock_setup, + patch.object(onboarding, "STEPS", [onboarding.STEP_USER]), + ): assert await async_setup_component(hass, "onboarding", {}) assert len(mock_setup.mock_calls) == 0 diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 272d0e99773..556b590e746 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -80,30 +80,40 @@ async def mock_supervisor_fixture(hass, aioclient_mock): }, }, ) - with patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), patch( - "homeassistant.components.hassio.HassIO.is_connected", - return_value=True, - ), patch( - "homeassistant.components.hassio.HassIO.get_info", - return_value={}, - ), patch( - "homeassistant.components.hassio.HassIO.get_host_info", - return_value={}, - ), patch( - "homeassistant.components.hassio.HassIO.get_store", - return_value={}, - ), patch( - "homeassistant.components.hassio.HassIO.get_supervisor_info", - return_value={"diagnostics": True}, - ), patch( - "homeassistant.components.hassio.HassIO.get_os_info", - return_value={}, - ), patch( - "homeassistant.components.hassio.HassIO.get_ingress_panels", - return_value={"panels": {}}, - ), patch.dict( - os.environ, - {"SUPERVISOR_TOKEN": "123456"}, + with ( + patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), + patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value=True, + ), + patch( + "homeassistant.components.hassio.HassIO.get_info", + return_value={}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_host_info", + return_value={}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_store", + return_value={}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value={"diagnostics": True}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_os_info", + return_value={}, + ), + patch( + "homeassistant.components.hassio.HassIO.get_ingress_panels", + return_value={"panels": {}}, + ), + patch.dict( + os.environ, + {"SUPERVISOR_TOKEN": "123456"}, + ), ): yield @@ -111,16 +121,18 @@ async def mock_supervisor_fixture(hass, aioclient_mock): @pytest.fixture def mock_default_integrations(): """Mock the default integrations set up during onboarding.""" - with patch( - "homeassistant.components.rpi_power.config_flow.new_under_voltage" - ), patch( - "homeassistant.components.rpi_power.binary_sensor.new_under_voltage" - ), patch( - "homeassistant.components.met.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.radio_browser.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.shopping_list.async_setup_entry", return_value=True + with ( + patch("homeassistant.components.rpi_power.config_flow.new_under_voltage"), + patch("homeassistant.components.rpi_power.binary_sensor.new_under_voltage"), + patch("homeassistant.components.met.async_setup_entry", return_value=True), + patch( + "homeassistant.components.radio_browser.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.shopping_list.async_setup_entry", + return_value=True, + ), ): yield diff --git a/tests/components/oncue/__init__.py b/tests/components/oncue/__init__.py index b90f9df4f89..df1452b176e 100644 --- a/tests/components/oncue/__init__.py +++ b/tests/components/oncue/__init__.py @@ -802,11 +802,14 @@ MOCK_ASYNC_FETCH_ALL_UNAVAILABLE_DEVICE = { def _patch_login_and_data(): @contextmanager def _patcher(): - with patch( - "homeassistant.components.oncue.Oncue.async_login", - ), patch( - "homeassistant.components.oncue.Oncue.async_fetch_all", - return_value=MOCK_ASYNC_FETCH_ALL, + with ( + patch( + "homeassistant.components.oncue.Oncue.async_login", + ), + patch( + "homeassistant.components.oncue.Oncue.async_fetch_all", + return_value=MOCK_ASYNC_FETCH_ALL, + ), ): yield @@ -816,11 +819,14 @@ def _patch_login_and_data(): def _patch_login_and_data_offline_device(): @contextmanager def _patcher(): - with patch( - "homeassistant.components.oncue.Oncue.async_login", - ), patch( - "homeassistant.components.oncue.Oncue.async_fetch_all", - return_value=MOCK_ASYNC_FETCH_ALL_OFFLINE_DEVICE, + with ( + patch( + "homeassistant.components.oncue.Oncue.async_login", + ), + patch( + "homeassistant.components.oncue.Oncue.async_fetch_all", + return_value=MOCK_ASYNC_FETCH_ALL_OFFLINE_DEVICE, + ), ): yield @@ -830,9 +836,12 @@ def _patch_login_and_data_offline_device(): def _patch_login_and_data_unavailable(): @contextmanager def _patcher(): - with patch("homeassistant.components.oncue.Oncue.async_login"), patch( - "homeassistant.components.oncue.Oncue.async_fetch_all", - return_value=MOCK_ASYNC_FETCH_ALL_UNAVAILABLE_DEVICE, + with ( + patch("homeassistant.components.oncue.Oncue.async_login"), + patch( + "homeassistant.components.oncue.Oncue.async_fetch_all", + return_value=MOCK_ASYNC_FETCH_ALL_UNAVAILABLE_DEVICE, + ), ): yield @@ -842,9 +851,12 @@ def _patch_login_and_data_unavailable(): def _patch_login_and_data_unavailable_device(): @contextmanager def _patcher(): - with patch("homeassistant.components.oncue.Oncue.async_login"), patch( - "homeassistant.components.oncue.Oncue.async_fetch_all", - return_value=MOCK_ASYNC_FETCH_ALL_UNAVAILABLE_DEVICE, + with ( + patch("homeassistant.components.oncue.Oncue.async_login"), + patch( + "homeassistant.components.oncue.Oncue.async_fetch_all", + return_value=MOCK_ASYNC_FETCH_ALL_UNAVAILABLE_DEVICE, + ), ): yield diff --git a/tests/components/oncue/test_config_flow.py b/tests/components/oncue/test_config_flow.py index a58bcb98c0b..d757adec771 100644 --- a/tests/components/oncue/test_config_flow.py +++ b/tests/components/oncue/test_config_flow.py @@ -20,10 +20,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch("homeassistant.components.oncue.config_flow.Oncue.async_login"), patch( - "homeassistant.components.oncue.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.oncue.config_flow.Oncue.async_login"), + patch( + "homeassistant.components.oncue.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/onvif/__init__.py b/tests/components/onvif/__init__.py index fea27f501e8..1e7c3273ced 100644 --- a/tests/components/onvif/__init__.py +++ b/tests/components/onvif/__init__.py @@ -187,13 +187,15 @@ async def setup_onvif_integration( ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.wsdiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.wsdiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera, two_profiles=True) # no discovery mock_discovery.return_value = [] diff --git a/tests/components/onvif/test_config_flow.py b/tests/components/onvif/test_config_flow.py index 5640472d6a2..e59db13d3bb 100644 --- a/tests/components/onvif/test_config_flow.py +++ b/tests/components/onvif/test_config_flow.py @@ -110,13 +110,15 @@ async def test_flow_discovered_devices(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera) setup_mock_discovery(mock_discovery) setup_mock_device(mock_device) @@ -181,13 +183,15 @@ async def test_flow_discovered_devices_ignore_configured_manual_input( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera) setup_mock_discovery(mock_discovery, with_mac=True) setup_mock_device(mock_device) @@ -220,13 +224,15 @@ async def test_flow_discovered_no_device(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera) setup_mock_discovery(mock_discovery, no_devices=True) setup_mock_device(mock_device) @@ -263,13 +269,15 @@ async def test_flow_discovery_ignore_existing_and_abort(hass: HomeAssistant) -> assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera) setup_mock_discovery(mock_discovery, with_name=True, with_mac=True) setup_mock_device(mock_device) @@ -307,13 +315,15 @@ async def test_flow_manual_entry(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera, two_profiles=True) # no discovery mock_discovery.return_value = [] @@ -364,13 +374,15 @@ async def test_flow_manual_entry_no_profiles(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera, no_profiles=True) # no discovery mock_discovery.return_value = [] @@ -404,13 +416,15 @@ async def test_flow_manual_entry_no_mac(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera( mock_onvif_camera, with_serial=False, with_interfaces=False ) @@ -446,13 +460,15 @@ async def test_flow_manual_entry_fails(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera( mock_onvif_camera, two_profiles=True, profiles_transient_failure=True ) @@ -554,13 +570,15 @@ async def test_flow_manual_entry_wrong_password(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera, two_profiles=True, auth_fail=True) # no discovery mock_discovery.return_value = [] @@ -764,14 +782,16 @@ async def test_form_reauth(hass: HomeAssistant) -> None: == entry.data[CONF_USERNAME] ) - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device, patch( - "homeassistant.components.onvif.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + patch( + "homeassistant.components.onvif.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): setup_mock_onvif_camera(mock_onvif_camera, auth_failure=True) setup_mock_device(mock_device) @@ -791,14 +811,16 @@ async def test_form_reauth(hass: HomeAssistant) -> None: "error": "not authorized (subcodes:NotAuthorized)" } - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device, patch( - "homeassistant.components.onvif.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + patch( + "homeassistant.components.onvif.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): setup_mock_onvif_camera(mock_onvif_camera) setup_mock_device(mock_device) @@ -831,13 +853,15 @@ async def test_flow_manual_entry_updates_existing_user_password( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera, two_profiles=True) # no discovery mock_discovery.return_value = [] @@ -882,13 +906,15 @@ async def test_flow_manual_entry_wrong_port(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.onvif.config_flow.get_device" - ) as mock_onvif_camera, patch( - "homeassistant.components.onvif.config_flow.WSDiscovery" - ) as mock_discovery, patch( - "homeassistant.components.onvif.ONVIFDevice" - ) as mock_device: + with ( + patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, + patch( + "homeassistant.components.onvif.config_flow.WSDiscovery" + ) as mock_discovery, + patch("homeassistant.components.onvif.ONVIFDevice") as mock_device, + ): setup_mock_onvif_camera(mock_onvif_camera, wrong_port=True) # no discovery mock_discovery.return_value = [] diff --git a/tests/components/openai_conversation/test_config_flow.py b/tests/components/openai_conversation/test_config_flow.py index 85fd70f6b84..659b3825472 100644 --- a/tests/components/openai_conversation/test_config_flow.py +++ b/tests/components/openai_conversation/test_config_flow.py @@ -33,12 +33,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.openai_conversation.config_flow.openai.resources.models.AsyncModels.list", - ), patch( - "homeassistant.components.openai_conversation.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.openai_conversation.config_flow.openai.resources.models.AsyncModels.list", + ), + patch( + "homeassistant.components.openai_conversation.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/openai_conversation/test_init.py b/tests/components/openai_conversation/test_init.py index ebacc08700e..3a8db2a71c0 100644 --- a/tests/components/openai_conversation/test_init.py +++ b/tests/components/openai_conversation/test_init.py @@ -169,11 +169,14 @@ async def test_template_error( "prompt": "talk like a {% if True %}smarthome{% else %}pirate please.", }, ) - with patch( - "openai.resources.models.AsyncModels.list", - ), patch( - "openai.resources.chat.completions.AsyncCompletions.create", - new_callable=AsyncMock, + with ( + patch( + "openai.resources.models.AsyncModels.list", + ), + patch( + "openai.resources.chat.completions.AsyncCompletions.create", + new_callable=AsyncMock, + ), ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -314,12 +317,17 @@ async def test_generate_image_service_error( mock_config_entry: MockConfigEntry, ) -> None: """Test generate image service handles errors.""" - with patch( - "openai.resources.images.AsyncImages.generate", - side_effect=RateLimitError( - response=Response(status_code=None, request=""), body=None, message="Reason" + with ( + patch( + "openai.resources.images.AsyncImages.generate", + side_effect=RateLimitError( + response=Response(status_code=None, request=""), + body=None, + message="Reason", + ), ), - ), pytest.raises(HomeAssistantError, match="Error generating image: Reason"): + pytest.raises(HomeAssistantError, match="Error generating image: Reason"), + ): await hass.services.async_call( "openai_conversation", "generate_image", diff --git a/tests/components/opengarage/test_config_flow.py b/tests/components/opengarage/test_config_flow.py index f730cf95703..7d3e44017b0 100644 --- a/tests/components/opengarage/test_config_flow.py +++ b/tests/components/opengarage/test_config_flow.py @@ -21,13 +21,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "opengarage.OpenGarage.update_state", - return_value={"name": "Name of the device", "mac": "unique"}, - ), patch( - "homeassistant.components.opengarage.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "opengarage.OpenGarage.update_state", + return_value={"name": "Name of the device", "mac": "unique"}, + ), + patch( + "homeassistant.components.opengarage.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "http://1.1.1.1", "device_key": "AfsasdnfkjDD"}, diff --git a/tests/components/openhome/test_update.py b/tests/components/openhome/test_update.py index 74fecdb4ed6..d3a328b9f9e 100644 --- a/tests/components/openhome/test_update.py +++ b/tests/components/openhome/test_update.py @@ -72,9 +72,10 @@ async def setup_integration( ) entry.add_to_hass(hass) - with patch("homeassistant.components.openhome.PLATFORMS", [Platform.UPDATE]), patch( - "homeassistant.components.openhome.Device", MagicMock() - ) as mock_device: + with ( + patch("homeassistant.components.openhome.PLATFORMS", [Platform.UPDATE]), + patch("homeassistant.components.openhome.Device", MagicMock()) as mock_device, + ): mock_device.return_value.init = AsyncMock() mock_device.return_value.uuid = MagicMock(return_value="uuid") mock_device.return_value.manufacturer = MagicMock(return_value="manufacturer") diff --git a/tests/components/opensky/test_config_flow.py b/tests/components/opensky/test_config_flow.py index 43192cdaff2..c3ae876d36e 100644 --- a/tests/components/opensky/test_config_flow.py +++ b/tests/components/opensky/test_config_flow.py @@ -106,9 +106,12 @@ async def test_options_flow_failures( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "init" assert result["errors"]["base"] == error - with patch("python_opensky.OpenSky.authenticate"), patch( - "python_opensky.OpenSky.get_states", - return_value=get_states_response_fixture("opensky/states_1.json"), + with ( + patch("python_opensky.OpenSky.authenticate"), + patch( + "python_opensky.OpenSky.get_states", + return_value=get_states_response_fixture("opensky/states_1.json"), + ), ): result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -140,9 +143,12 @@ async def test_options_flow( entry = hass.config_entries.async_entries(DOMAIN)[0] result = await hass.config_entries.options.async_init(entry.entry_id) await hass.async_block_till_done() - with patch("python_opensky.OpenSky.authenticate"), patch( - "python_opensky.OpenSky.get_states", - return_value=get_states_response_fixture("opensky/states_1.json"), + with ( + patch("python_opensky.OpenSky.authenticate"), + patch( + "python_opensky.OpenSky.get_states", + return_value=get_states_response_fixture("opensky/states_1.json"), + ), ): result = await hass.config_entries.options.async_configure( result["flow_id"], diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 57229d3b49f..c92f23f46b4 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -37,18 +37,22 @@ async def test_form_user(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.opentherm_gw.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS - ) as mock_pyotgw_connect, patch( - "pyotgw.OpenThermGateway.disconnect", return_value=None - ) as mock_pyotgw_disconnect, patch( - "pyotgw.status.StatusManager._process_updates", return_value=None + with ( + patch( + "homeassistant.components.opentherm_gw.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.opentherm_gw.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS + ) as mock_pyotgw_connect, + patch( + "pyotgw.OpenThermGateway.disconnect", return_value=None + ) as mock_pyotgw_disconnect, + patch("pyotgw.status.StatusManager._process_updates", return_value=None), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} @@ -71,18 +75,22 @@ async def test_form_user(hass: HomeAssistant) -> None: async def test_form_import(hass: HomeAssistant) -> None: """Test import from existing config.""" - with patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.opentherm_gw.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS - ) as mock_pyotgw_connect, patch( - "pyotgw.OpenThermGateway.disconnect", return_value=None - ) as mock_pyotgw_disconnect, patch( - "pyotgw.status.StatusManager._process_updates", return_value=None + with ( + patch( + "homeassistant.components.opentherm_gw.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.opentherm_gw.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS + ) as mock_pyotgw_connect, + patch( + "pyotgw.OpenThermGateway.disconnect", return_value=None + ) as mock_pyotgw_disconnect, + patch("pyotgw.status.StatusManager._process_updates", return_value=None), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -115,18 +123,22 @@ async def test_form_duplicate_entries(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.opentherm_gw.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS - ) as mock_pyotgw_connect, patch( - "pyotgw.OpenThermGateway.disconnect", return_value=None - ) as mock_pyotgw_disconnect, patch( - "pyotgw.status.StatusManager._process_updates", return_value=None + with ( + patch( + "homeassistant.components.opentherm_gw.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.opentherm_gw.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS + ) as mock_pyotgw_connect, + patch( + "pyotgw.OpenThermGateway.disconnect", return_value=None + ) as mock_pyotgw_disconnect, + patch("pyotgw.status.StatusManager._process_updates", return_value=None), ): result1 = await hass.config_entries.flow.async_configure( flow1["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} @@ -154,10 +166,11 @@ async def test_form_connection_timeout(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "pyotgw.OpenThermGateway.connect", side_effect=(TimeoutError) - ) as mock_connect, patch( - "pyotgw.status.StatusManager._process_updates", return_value=None + with ( + patch( + "pyotgw.OpenThermGateway.connect", side_effect=(TimeoutError) + ) as mock_connect, + patch("pyotgw.status.StatusManager._process_updates", return_value=None), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -175,10 +188,11 @@ async def test_form_connection_error(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "pyotgw.OpenThermGateway.connect", side_effect=(SerialException) - ) as mock_connect, patch( - "pyotgw.status.StatusManager._process_updates", return_value=None + with ( + patch( + "pyotgw.OpenThermGateway.connect", side_effect=(SerialException) + ) as mock_connect, + patch("pyotgw.status.StatusManager._process_updates", return_value=None), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} @@ -206,15 +220,19 @@ async def test_options_migration(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.connect_and_subscribe", - return_value=True, - ), patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=True, - ), patch( - "pyotgw.status.StatusManager._process_updates", - return_value=None, + with ( + patch( + "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.connect_and_subscribe", + return_value=True, + ), + patch( + "homeassistant.components.opentherm_gw.async_setup", + return_value=True, + ), + patch( + "pyotgw.status.StatusManager._process_updates", + return_value=None, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -251,10 +269,11 @@ async def test_options_form(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.opentherm_gw.async_setup", return_value=True - ), patch( - "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True + with ( + patch("homeassistant.components.opentherm_gw.async_setup", return_value=True), + patch( + "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/opentherm_gw/test_init.py b/tests/components/opentherm_gw/test_init.py index c068686a607..77d43039c2b 100644 --- a/tests/components/opentherm_gw/test_init.py +++ b/tests/components/opentherm_gw/test_init.py @@ -36,10 +36,13 @@ async def test_device_registry_insert(hass: HomeAssistant) -> None: """Test that the device registry is initialized correctly.""" MOCK_CONFIG_ENTRY.add_to_hass(hass) - with patch( - "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.cleanup", - return_value=None, - ), patch("pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS): + with ( + patch( + "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.cleanup", + return_value=None, + ), + patch("pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS), + ): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() @@ -67,10 +70,13 @@ async def test_device_registry_update( sw_version=VERSION_OLD, ) - with patch( - "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.cleanup", - return_value=None, - ), patch("pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS_UPD): + with ( + patch( + "homeassistant.components.opentherm_gw.OpenThermGatewayDevice.cleanup", + return_value=None, + ), + patch("pyotgw.OpenThermGateway.connect", return_value=MINIMAL_STATUS_UPD), + ): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/openuv/conftest.py b/tests/components/openuv/conftest.py index 414a73435fc..5aad7d5b1a6 100644 --- a/tests/components/openuv/conftest.py +++ b/tests/components/openuv/conftest.py @@ -79,9 +79,12 @@ def data_uv_index_fixture(): @pytest.fixture(name="mock_pyopenuv") async def mock_pyopenuv_fixture(client): """Define a fixture to patch pyopenuv.""" - with patch( - "homeassistant.components.openuv.config_flow.Client", return_value=client - ), patch("homeassistant.components.openuv.Client", return_value=client): + with ( + patch( + "homeassistant.components.openuv.config_flow.Client", return_value=client + ), + patch("homeassistant.components.openuv.Client", return_value=client), + ): yield diff --git a/tests/components/oralb/test_sensor.py b/tests/components/oralb/test_sensor.py index b1210712e7a..82f9b86b352 100644 --- a/tests/components/oralb/test_sensor.py +++ b/tests/components/oralb/test_sensor.py @@ -58,9 +58,12 @@ async def test_sensors( # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() @@ -107,9 +110,12 @@ async def test_sensors_io_series_4( # Fast-forward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/osoenergy/test_config_flow.py b/tests/components/osoenergy/test_config_flow.py index 3a2de0e44f5..c88035eef28 100644 --- a/tests/components/osoenergy/test_config_flow.py +++ b/tests/components/osoenergy/test_config_flow.py @@ -26,12 +26,15 @@ async def test_user_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.osoenergy.config_flow.OSOEnergy.get_user_email", - return_value=TEST_USER_EMAIL, - ), patch( - "homeassistant.components.osoenergy.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.osoenergy.config_flow.OSOEnergy.get_user_email", + return_value=TEST_USER_EMAIL, + ), + patch( + "homeassistant.components.osoenergy.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: SUBSCRIPTION_KEY}, diff --git a/tests/components/otbr/conftest.py b/tests/components/otbr/conftest.py index 8b102f32b8e..82f167cdd23 100644 --- a/tests/components/otbr/conftest.py +++ b/tests/components/otbr/conftest.py @@ -28,15 +28,19 @@ async def otbr_config_entry_multipan_fixture(hass): title="Open Thread Border Router", ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, - ), patch( - "homeassistant.components.otbr.util.compute_pskc" + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), + patch("homeassistant.components.otbr.util.compute_pskc"), ): # Patch to speed up tests assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -51,15 +55,19 @@ async def otbr_config_entry_thread_fixture(hass): title="Open Thread Border Router", ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, - ), patch( - "homeassistant.components.otbr.util.compute_pskc" + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), + patch("homeassistant.components.otbr.util.compute_pskc"), ): # Patch to speed up tests assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/otbr/test_config_flow.py b/tests/components/otbr/test_config_flow.py index 18c62257803..e9ad5681549 100644 --- a/tests/components/otbr/test_config_flow.py +++ b/tests/components/otbr/test_config_flow.py @@ -105,13 +105,16 @@ async def test_user_flow_router_not_setup( assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", - return_value=None, - ), patch( - "homeassistant.components.otbr.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", + return_value=None, + ), + patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -249,11 +252,12 @@ async def test_hassio_discovery_flow_yellow( "version": None, } - with patch( - "homeassistant.components.otbr.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.otbr.config_flow.yellow_hardware.async_info" + with ( + patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch("homeassistant.components.otbr.config_flow.yellow_hardware.async_info"), ): result = await hass.config_entries.flow.async_init( otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA @@ -414,13 +418,16 @@ async def test_hassio_discovery_flow_router_not_setup( aioclient_mock.put(f"{url}/node/dataset/active", status=HTTPStatus.CREATED) aioclient_mock.put(f"{url}/node/state", status=HTTPStatus.OK) - with patch( - "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", - return_value=None, - ), patch( - "homeassistant.components.otbr.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", + return_value=None, + ), + patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA ) @@ -468,13 +475,16 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred( aioclient_mock.put(f"{url}/node/dataset/active", status=HTTPStatus.CREATED) aioclient_mock.put(f"{url}/node/state", status=HTTPStatus.OK) - with patch( - "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", - return_value=DATASET_CH15.hex(), - ), patch( - "homeassistant.components.otbr.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", + return_value=DATASET_CH15.hex(), + ), + patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA ) @@ -523,13 +533,16 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred_2( multiprotocol_addon_manager_mock.async_get_channel.return_value = 15 - with patch( - "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", - return_value=DATASET_CH16.hex(), - ), patch( - "homeassistant.components.otbr.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.otbr.config_flow.async_get_preferred_dataset", + return_value=DATASET_CH16.hex(), + ), + patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA ) diff --git a/tests/components/otbr/test_init.py b/tests/components/otbr/test_init.py index 39805728a3e..7fd4ef6b016 100644 --- a/tests/components/otbr/test_init.py +++ b/tests/components/otbr/test_init.py @@ -64,16 +64,22 @@ async def test_import_dataset(hass: HomeAssistant, mock_async_zeroconf: None) -> ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, - ), patch( - "homeassistant.components.thread.dataset_store.BORDER_AGENT_DISCOVERY_TIMEOUT", - 0.1, + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), + patch( + "homeassistant.components.thread.dataset_store.BORDER_AGENT_DISCOVERY_TIMEOUT", + 0.1, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -135,16 +141,22 @@ async def test_import_share_radio_channel_collision( title="My OTBR", ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, - ), patch( - "homeassistant.components.thread.dataset_store.DatasetStore.async_add" - ) as mock_add: + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), + patch( + "homeassistant.components.thread.dataset_store.DatasetStore.async_add" + ) as mock_add, + ): assert await hass.config_entries.async_setup(config_entry.entry_id) mock_add.assert_called_once_with( @@ -178,16 +190,20 @@ async def test_import_share_radio_no_channel_collision( title="My OTBR", ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=dataset - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, - ), patch( - "homeassistant.components.thread.dataset_store.DatasetStore.async_add" - ) as mock_add: + with ( + patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=dataset), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), + patch( + "homeassistant.components.thread.dataset_store.DatasetStore.async_add" + ) as mock_add, + ): assert await hass.config_entries.async_setup(config_entry.entry_id) mock_add.assert_called_once_with( @@ -219,16 +235,20 @@ async def test_import_insecure_dataset(hass: HomeAssistant, dataset: bytes) -> N title="My OTBR", ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=dataset - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, - ), patch( - "homeassistant.components.thread.dataset_store.DatasetStore.async_add" - ) as mock_add: + with ( + patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=dataset), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), + patch( + "homeassistant.components.thread.dataset_store.DatasetStore.async_add" + ) as mock_add, + ): assert await hass.config_entries.async_setup(config_entry.entry_id) mock_add.assert_called_once_with( @@ -274,11 +294,14 @@ async def test_border_agent_id_not_supported(hass: HomeAssistant) -> None: title="My OTBR", ) config_entry.add_to_hass(hass) - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", - side_effect=python_otbr_api.GetBorderAgentIdNotSupportedError, + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + side_effect=python_otbr_api.GetBorderAgentIdNotSupportedError, + ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) @@ -412,10 +435,11 @@ async def test_remove_extra_entries( config_entry1.add_to_hass(hass) config_entry2.add_to_hass(hass) assert len(hass.config_entries.async_entries(otbr.DOMAIN)) == 2 - with patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "homeassistant.components.otbr.util.compute_pskc" + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch("homeassistant.components.otbr.util.compute_pskc"), ): # Patch to speed up tests assert await async_setup_component(hass, otbr.DOMAIN, {}) assert len(hass.config_entries.async_entries(otbr.DOMAIN)) == 1 diff --git a/tests/components/otbr/test_silabs_multiprotocol.py b/tests/components/otbr/test_silabs_multiprotocol.py index 569d507d087..8d7bed13df6 100644 --- a/tests/components/otbr/test_silabs_multiprotocol.py +++ b/tests/components/otbr/test_silabs_multiprotocol.py @@ -41,9 +41,12 @@ async def test_async_change_channel( assert len(store.datasets) == 1 assert list(store.datasets.values())[0].tlv == DATASET_CH16.hex() - with patch("python_otbr_api.OTBR.set_channel") as mock_set_channel, patch( - "python_otbr_api.OTBR.get_pending_dataset_tlvs", - return_value=bytes.fromhex(DATASET_CH16_PENDING), + with ( + patch("python_otbr_api.OTBR.set_channel") as mock_set_channel, + patch( + "python_otbr_api.OTBR.get_pending_dataset_tlvs", + return_value=bytes.fromhex(DATASET_CH16_PENDING), + ), ): await otbr_silabs_multiprotocol.async_change_channel(hass, 15, delay=5 * 300) mock_set_channel.assert_awaited_once_with(15, delay=5 * 300 * 1000) @@ -66,12 +69,16 @@ async def test_async_change_channel_no_pending( assert len(store.datasets) == 1 assert list(store.datasets.values())[0].tlv == DATASET_CH16.hex() - with patch("python_otbr_api.OTBR.set_channel") as mock_set_channel, patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", - return_value=bytes.fromhex(DATASET_CH16_PENDING), - ), patch( - "python_otbr_api.OTBR.get_pending_dataset_tlvs", - return_value=None, + with ( + patch("python_otbr_api.OTBR.set_channel") as mock_set_channel, + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", + return_value=bytes.fromhex(DATASET_CH16_PENDING), + ), + patch( + "python_otbr_api.OTBR.get_pending_dataset_tlvs", + return_value=None, + ), ): await otbr_silabs_multiprotocol.async_change_channel(hass, 15, delay=5 * 300) mock_set_channel.assert_awaited_once_with(15, delay=5 * 300 * 1000) @@ -94,12 +101,16 @@ async def test_async_change_channel_no_update( assert len(store.datasets) == 1 assert list(store.datasets.values())[0].tlv == DATASET_CH16.hex() - with patch("python_otbr_api.OTBR.set_channel") as mock_set_channel, patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", - return_value=None, - ), patch( - "python_otbr_api.OTBR.get_pending_dataset_tlvs", - return_value=None, + with ( + patch("python_otbr_api.OTBR.set_channel") as mock_set_channel, + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", + return_value=None, + ), + patch( + "python_otbr_api.OTBR.get_pending_dataset_tlvs", + return_value=None, + ), ): await otbr_silabs_multiprotocol.async_change_channel(hass, 15, delay=5 * 300) mock_set_channel.assert_awaited_once_with(15, delay=5 * 300 * 1000) diff --git a/tests/components/otbr/test_util.py b/tests/components/otbr/test_util.py index ac072ffd513..3b1edcfeb5b 100644 --- a/tests/components/otbr/test_util.py +++ b/tests/components/otbr/test_util.py @@ -35,9 +35,12 @@ async def test_factory_reset(hass: HomeAssistant, otbr_config_entry_multipan) -> """Test factory_reset.""" data: otbr.OTBRData = hass.data[otbr.DOMAIN] - with patch("python_otbr_api.OTBR.factory_reset") as factory_reset_mock, patch( - "python_otbr_api.OTBR.delete_active_dataset" - ) as delete_active_dataset_mock: + with ( + patch("python_otbr_api.OTBR.factory_reset") as factory_reset_mock, + patch( + "python_otbr_api.OTBR.delete_active_dataset" + ) as delete_active_dataset_mock, + ): await data.factory_reset() delete_active_dataset_mock.assert_not_called() @@ -50,12 +53,15 @@ async def test_factory_reset_not_supported( """Test factory_reset.""" data: otbr.OTBRData = hass.data[otbr.DOMAIN] - with patch( - "python_otbr_api.OTBR.factory_reset", - side_effect=python_otbr_api.FactoryResetNotSupportedError, - ) as factory_reset_mock, patch( - "python_otbr_api.OTBR.delete_active_dataset" - ) as delete_active_dataset_mock: + with ( + patch( + "python_otbr_api.OTBR.factory_reset", + side_effect=python_otbr_api.FactoryResetNotSupportedError, + ) as factory_reset_mock, + patch( + "python_otbr_api.OTBR.delete_active_dataset" + ) as delete_active_dataset_mock, + ): await data.factory_reset() delete_active_dataset_mock.assert_called_once_with() @@ -68,13 +74,17 @@ async def test_factory_reset_error_1( """Test factory_reset.""" data: otbr.OTBRData = hass.data[otbr.DOMAIN] - with patch( - "python_otbr_api.OTBR.factory_reset", - side_effect=python_otbr_api.OTBRError, - ) as factory_reset_mock, patch( - "python_otbr_api.OTBR.delete_active_dataset" - ) as delete_active_dataset_mock, pytest.raises( - HomeAssistantError, + with ( + patch( + "python_otbr_api.OTBR.factory_reset", + side_effect=python_otbr_api.OTBRError, + ) as factory_reset_mock, + patch( + "python_otbr_api.OTBR.delete_active_dataset" + ) as delete_active_dataset_mock, + pytest.raises( + HomeAssistantError, + ), ): await data.factory_reset() @@ -88,14 +98,18 @@ async def test_factory_reset_error_2( """Test factory_reset.""" data: otbr.OTBRData = hass.data[otbr.DOMAIN] - with patch( - "python_otbr_api.OTBR.factory_reset", - side_effect=python_otbr_api.FactoryResetNotSupportedError, - ) as factory_reset_mock, patch( - "python_otbr_api.OTBR.delete_active_dataset", - side_effect=python_otbr_api.OTBRError, - ) as delete_active_dataset_mock, pytest.raises( - HomeAssistantError, + with ( + patch( + "python_otbr_api.OTBR.factory_reset", + side_effect=python_otbr_api.FactoryResetNotSupportedError, + ) as factory_reset_mock, + patch( + "python_otbr_api.OTBR.delete_active_dataset", + side_effect=python_otbr_api.OTBRError, + ) as delete_active_dataset_mock, + pytest.raises( + HomeAssistantError, + ), ): await data.factory_reset() diff --git a/tests/components/otbr/test_websocket_api.py b/tests/components/otbr/test_websocket_api.py index a872206f833..c8ac839f629 100644 --- a/tests/components/otbr/test_websocket_api.py +++ b/tests/components/otbr/test_websocket_api.py @@ -35,16 +35,22 @@ async def test_get_info( ) -> None: """Test async_get_info.""" - with patch( - "python_otbr_api.OTBR.get_active_dataset", - return_value=python_otbr_api.ActiveDataSet(channel=16), - ), patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID - ), patch( - "python_otbr_api.OTBR.get_extended_address", - return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset", + return_value=python_otbr_api.ActiveDataSet(channel=16), + ), + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), + patch( + "python_otbr_api.OTBR.get_extended_address", + return_value=TEST_BORDER_AGENT_EXTENDED_ADDRESS, + ), ): await websocket_client.send_json_auto_id({"type": "otbr/info"}) msg = await websocket_client.receive_json() @@ -81,11 +87,15 @@ async def test_get_info_fetch_fails( websocket_client, ) -> None: """Test async_get_info.""" - with patch( - "python_otbr_api.OTBR.get_active_dataset", - side_effect=python_otbr_api.OTBRError, - ), patch( - "python_otbr_api.OTBR.get_border_agent_id", return_value=TEST_BORDER_AGENT_ID + with ( + patch( + "python_otbr_api.OTBR.get_active_dataset", + side_effect=python_otbr_api.OTBRError, + ), + patch( + "python_otbr_api.OTBR.get_border_agent_id", + return_value=TEST_BORDER_AGENT_ID, + ), ): await websocket_client.send_json_auto_id({"type": "otbr/info"}) msg = await websocket_client.receive_json() @@ -102,19 +112,20 @@ async def test_create_network( ) -> None: """Test create network.""" - with patch( - "python_otbr_api.OTBR.create_active_dataset" - ) as create_dataset_mock, patch( - "python_otbr_api.OTBR.factory_reset" - ) as factory_reset_mock, patch( - "python_otbr_api.OTBR.set_enabled" - ) as set_enabled_mock, patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 - ) as get_active_dataset_tlvs_mock, patch( - "homeassistant.components.thread.dataset_store.DatasetStore.async_add" - ) as mock_add, patch( - "homeassistant.components.otbr.util.random.randint", - return_value=0x1234, + with ( + patch("python_otbr_api.OTBR.create_active_dataset") as create_dataset_mock, + patch("python_otbr_api.OTBR.factory_reset") as factory_reset_mock, + patch("python_otbr_api.OTBR.set_enabled") as set_enabled_mock, + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 + ) as get_active_dataset_tlvs_mock, + patch( + "homeassistant.components.thread.dataset_store.DatasetStore.async_add" + ) as mock_add, + patch( + "homeassistant.components.otbr.util.random.randint", + return_value=0x1234, + ), ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) @@ -175,12 +186,16 @@ async def test_create_network_fails_2( websocket_client, ) -> None: """Test create network.""" - with patch( - "python_otbr_api.OTBR.set_enabled", - ), patch( - "python_otbr_api.OTBR.create_active_dataset", - side_effect=python_otbr_api.OTBRError, - ), patch("python_otbr_api.OTBR.factory_reset"): + with ( + patch( + "python_otbr_api.OTBR.set_enabled", + ), + patch( + "python_otbr_api.OTBR.create_active_dataset", + side_effect=python_otbr_api.OTBRError, + ), + patch("python_otbr_api.OTBR.factory_reset"), + ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -195,13 +210,17 @@ async def test_create_network_fails_3( websocket_client, ) -> None: """Test create network.""" - with patch( - "python_otbr_api.OTBR.set_enabled", - side_effect=[None, python_otbr_api.OTBRError], - ), patch( - "python_otbr_api.OTBR.create_active_dataset", - ), patch( - "python_otbr_api.OTBR.factory_reset", + with ( + patch( + "python_otbr_api.OTBR.set_enabled", + side_effect=[None, python_otbr_api.OTBRError], + ), + patch( + "python_otbr_api.OTBR.create_active_dataset", + ), + patch( + "python_otbr_api.OTBR.factory_reset", + ), ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -217,13 +236,16 @@ async def test_create_network_fails_4( websocket_client, ) -> None: """Test create network.""" - with patch("python_otbr_api.OTBR.set_enabled"), patch( - "python_otbr_api.OTBR.create_active_dataset" - ), patch( - "python_otbr_api.OTBR.get_active_dataset_tlvs", - side_effect=python_otbr_api.OTBRError, - ), patch( - "python_otbr_api.OTBR.factory_reset", + with ( + patch("python_otbr_api.OTBR.set_enabled"), + patch("python_otbr_api.OTBR.create_active_dataset"), + patch( + "python_otbr_api.OTBR.get_active_dataset_tlvs", + side_effect=python_otbr_api.OTBRError, + ), + patch( + "python_otbr_api.OTBR.factory_reset", + ), ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -239,10 +261,11 @@ async def test_create_network_fails_5( websocket_client, ) -> None: """Test create network.""" - with patch("python_otbr_api.OTBR.set_enabled"), patch( - "python_otbr_api.OTBR.create_active_dataset" - ), patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None), patch( - "python_otbr_api.OTBR.factory_reset" + with ( + patch("python_otbr_api.OTBR.set_enabled"), + patch("python_otbr_api.OTBR.create_active_dataset"), + patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None), + patch("python_otbr_api.OTBR.factory_reset"), ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -258,11 +281,14 @@ async def test_create_network_fails_6( websocket_client, ) -> None: """Test create network.""" - with patch("python_otbr_api.OTBR.set_enabled"), patch( - "python_otbr_api.OTBR.create_active_dataset" - ), patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None), patch( - "python_otbr_api.OTBR.factory_reset", - side_effect=python_otbr_api.OTBRError, + with ( + patch("python_otbr_api.OTBR.set_enabled"), + patch("python_otbr_api.OTBR.create_active_dataset"), + patch("python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=None), + patch( + "python_otbr_api.OTBR.factory_reset", + side_effect=python_otbr_api.OTBRError, + ), ): await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) msg = await websocket_client.receive_json() @@ -283,11 +309,12 @@ async def test_set_network( dataset_store = await thread.dataset_store.async_get_store(hass) dataset_id = list(dataset_store.datasets)[1] - with patch( - "python_otbr_api.OTBR.set_active_dataset_tlvs" - ) as set_active_dataset_tlvs_mock, patch( - "python_otbr_api.OTBR.set_enabled" - ) as set_enabled_mock: + with ( + patch( + "python_otbr_api.OTBR.set_active_dataset_tlvs" + ) as set_active_dataset_tlvs_mock, + patch("python_otbr_api.OTBR.set_enabled") as set_enabled_mock, + ): await websocket_client.send_json_auto_id( { "type": "otbr/set_network", @@ -411,11 +438,14 @@ async def test_set_network_fails_2( dataset_store = await thread.dataset_store.async_get_store(hass) dataset_id = list(dataset_store.datasets)[1] - with patch( - "python_otbr_api.OTBR.set_enabled", - ), patch( - "python_otbr_api.OTBR.set_active_dataset_tlvs", - side_effect=python_otbr_api.OTBRError, + with ( + patch( + "python_otbr_api.OTBR.set_enabled", + ), + patch( + "python_otbr_api.OTBR.set_active_dataset_tlvs", + side_effect=python_otbr_api.OTBRError, + ), ): await websocket_client.send_json_auto_id( { @@ -440,11 +470,14 @@ async def test_set_network_fails_3( dataset_store = await thread.dataset_store.async_get_store(hass) dataset_id = list(dataset_store.datasets)[1] - with patch( - "python_otbr_api.OTBR.set_enabled", - side_effect=[None, python_otbr_api.OTBRError], - ), patch( - "python_otbr_api.OTBR.set_active_dataset_tlvs", + with ( + patch( + "python_otbr_api.OTBR.set_enabled", + side_effect=[None, python_otbr_api.OTBRError], + ), + patch( + "python_otbr_api.OTBR.set_active_dataset_tlvs", + ), ): await websocket_client.send_json_auto_id( { diff --git a/tests/components/overkiz/test_config_flow.py b/tests/components/overkiz/test_config_flow.py index 6754452dea1..dbe8c690bc4 100644 --- a/tests/components/overkiz/test_config_flow.py +++ b/tests/components/overkiz/test_config_flow.py @@ -96,9 +96,12 @@ async def test_form_cloud(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> N assert result3["type"] == "form" assert result3["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY_RESPONSE, + ), ): await hass.config_entries.flow.async_configure( result["flow_id"], @@ -128,9 +131,12 @@ async def test_form_only_cloud_supported( assert result2["type"] == "form" assert result2["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY_RESPONSE, + ), ): await hass.config_entries.flow.async_configure( result["flow_id"], @@ -418,9 +424,12 @@ async def test_cloud_abort_on_duplicate_entry( assert result3["type"] == "form" assert result3["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY_RESPONSE, + ), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -525,9 +534,12 @@ async def test_cloud_allow_multiple_unique_entries( assert result3["type"] == "form" assert result3["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY_RESPONSE, + ), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -573,9 +585,12 @@ async def test_cloud_reauth_success(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY_RESPONSE, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -620,9 +635,12 @@ async def test_cloud_reauth_wrong_account(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY2_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY2_RESPONSE, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -779,8 +797,9 @@ async def test_dhcp_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> No {"api_type": "cloud"}, ) - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", return_value=None + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch("pyoverkiz.client.OverkizClient.get_gateways", return_value=None), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -852,9 +871,12 @@ async def test_zeroconf_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) - assert result3["type"] == "form" assert result3["step_id"] == "cloud" - with patch("pyoverkiz.client.OverkizClient.login", return_value=True), patch( - "pyoverkiz.client.OverkizClient.get_gateways", - return_value=MOCK_GATEWAY_RESPONSE, + with ( + patch("pyoverkiz.client.OverkizClient.login", return_value=True), + patch( + "pyoverkiz.client.OverkizClient.get_gateways", + return_value=MOCK_GATEWAY_RESPONSE, + ), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/ovo_energy/test_config_flow.py b/tests/components/ovo_energy/test_config_flow.py index 84b2719ae9b..3975be7cf80 100644 --- a/tests/components/ovo_energy/test_config_flow.py +++ b/tests/components/ovo_energy/test_config_flow.py @@ -82,15 +82,19 @@ async def test_full_flow_implementation(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.ovo_energy.config_flow.OVOEnergy.authenticate", - return_value=True, - ), patch( - "homeassistant.components.ovo_energy.config_flow.OVOEnergy.username", - "some_name", - ), patch( - "homeassistant.components.ovo_energy.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.ovo_energy.config_flow.OVOEnergy.authenticate", + return_value=True, + ), + patch( + "homeassistant.components.ovo_energy.config_flow.OVOEnergy.username", + "some_name", + ), + patch( + "homeassistant.components.ovo_energy.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -175,12 +179,15 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["step_id"] == "reauth" assert result["errors"] == {"base": "authorization_error"} - with patch( - "homeassistant.components.ovo_energy.config_flow.OVOEnergy.authenticate", - return_value=True, - ), patch( - "homeassistant.components.ovo_energy.config_flow.OVOEnergy.username", - return_value=FIXTURE_USER_INPUT[CONF_USERNAME], + with ( + patch( + "homeassistant.components.ovo_energy.config_flow.OVOEnergy.authenticate", + return_value=True, + ), + patch( + "homeassistant.components.ovo_energy.config_flow.OVOEnergy.username", + return_value=FIXTURE_USER_INPUT[CONF_USERNAME], + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index d8e700c3754..8b353789c83 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -153,15 +153,17 @@ async def test_with_cloud_sub(hass: HomeAssistant) -> None: """Test creating a config flow while subscribed.""" assert await async_setup_component(hass, "cloud", {}) - with patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", - return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + with ( + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch( + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data={} @@ -180,15 +182,17 @@ async def test_with_cloud_sub_not_connected(hass: HomeAssistant) -> None: """Test creating a config flow while subscribed.""" assert await async_setup_component(hass, "cloud", {}) - with patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=False - ), patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", - return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + with ( + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=False), + patch( + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data={} diff --git a/tests/components/p1_monitor/test_config_flow.py b/tests/components/p1_monitor/test_config_flow.py index 754fd31f19e..ec1af77a646 100644 --- a/tests/components/p1_monitor/test_config_flow.py +++ b/tests/components/p1_monitor/test_config_flow.py @@ -20,11 +20,14 @@ async def test_full_user_flow(hass: HomeAssistant) -> None: assert result.get("type") == FlowResultType.FORM assert result.get("step_id") == "user" - with patch( - "homeassistant.components.p1_monitor.config_flow.P1Monitor.smartmeter" - ) as mock_p1monitor, patch( - "homeassistant.components.p1_monitor.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.p1_monitor.config_flow.P1Monitor.smartmeter" + ) as mock_p1monitor, + patch( + "homeassistant.components.p1_monitor.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_HOST: "example.com"}, diff --git a/tests/components/peco/test_config_flow.py b/tests/components/peco/test_config_flow.py index 1a91145624a..df178e125e1 100644 --- a/tests/components/peco/test_config_flow.py +++ b/tests/components/peco/test_config_flow.py @@ -49,10 +49,13 @@ async def test_invalid_county(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.peco.async_setup_entry", - return_value=True, - ), pytest.raises(Invalid): + with ( + patch( + "homeassistant.components.peco.async_setup_entry", + return_value=True, + ), + pytest.raises(Invalid), + ): await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/peco/test_init.py b/tests/components/peco/test_init.py index 261624f382f..55dc0a15a4b 100644 --- a/tests/components/peco/test_init.py +++ b/tests/components/peco/test_init.py @@ -28,18 +28,21 @@ async def test_unload_entry(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.get_outage_totals", - return_value=OutageResults( - customers_out=0, - percent_customers_out=0, - outage_count=0, - customers_served=350394, + with ( + patch( + "peco.PecoOutageApi.get_outage_totals", + return_value=OutageResults( + customers_out=0, + percent_customers_out=0, + outage_count=0, + customers_served=350394, + ), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -164,21 +167,25 @@ async def test_unresponsive_meter_error(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=METER_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.meter_check", - side_effect=UnresponsiveMeterError(), - ), patch( - "peco.PecoOutageApi.get_outage_count", - return_value=OutageResults( - customers_out=0, - percent_customers_out=0, - outage_count=0, - customers_served=350394, + with ( + patch( + "peco.PecoOutageApi.meter_check", + side_effect=UnresponsiveMeterError(), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_outage_count", + return_value=OutageResults( + customers_out=0, + percent_customers_out=0, + outage_count=0, + customers_served=350394, + ), + ), + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) @@ -194,21 +201,25 @@ async def test_meter_http_error(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=METER_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.meter_check", - side_effect=HttpError(), - ), patch( - "peco.PecoOutageApi.get_outage_count", - return_value=OutageResults( - customers_out=0, - percent_customers_out=0, - outage_count=0, - customers_served=350394, + with ( + patch( + "peco.PecoOutageApi.meter_check", + side_effect=HttpError(), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_outage_count", + return_value=OutageResults( + customers_out=0, + percent_customers_out=0, + outage_count=0, + customers_served=350394, + ), + ), + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) @@ -224,21 +235,25 @@ async def test_meter_bad_json(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=METER_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.meter_check", - side_effect=BadJSONError(), - ), patch( - "peco.PecoOutageApi.get_outage_count", - return_value=OutageResults( - customers_out=0, - percent_customers_out=0, - outage_count=0, - customers_served=350394, + with ( + patch( + "peco.PecoOutageApi.meter_check", + side_effect=BadJSONError(), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_outage_count", + return_value=OutageResults( + customers_out=0, + percent_customers_out=0, + outage_count=0, + customers_served=350394, + ), + ), + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) @@ -254,21 +269,25 @@ async def test_meter_timeout(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=METER_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.meter_check", - side_effect=TimeoutError(), - ), patch( - "peco.PecoOutageApi.get_outage_count", - return_value=OutageResults( - customers_out=0, - percent_customers_out=0, - outage_count=0, - customers_served=350394, + with ( + patch( + "peco.PecoOutageApi.meter_check", + side_effect=TimeoutError(), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_outage_count", + return_value=OutageResults( + customers_out=0, + percent_customers_out=0, + outage_count=0, + customers_served=350394, + ), + ), + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert not await hass.config_entries.async_setup(config_entry.entry_id) @@ -284,21 +303,25 @@ async def test_meter_data(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data=METER_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.meter_check", - return_value=True, - ), patch( - "peco.PecoOutageApi.get_outage_count", - return_value=OutageResults( - customers_out=0, - percent_customers_out=0, - outage_count=0, - customers_served=350394, + with ( + patch( + "peco.PecoOutageApi.meter_check", + return_value=True, ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_outage_count", + return_value=OutageResults( + customers_out=0, + percent_customers_out=0, + outage_count=0, + customers_served=350394, + ), + ), + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/peco/test_sensor.py b/tests/components/peco/test_sensor.py index 2734ed4b8d1..2546b7c8996 100644 --- a/tests/components/peco/test_sensor.py +++ b/tests/components/peco/test_sensor.py @@ -34,18 +34,21 @@ async def test_sensor_available( config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.get_outage_totals", - return_value=OutageResults( - customers_out=123, - percent_customers_out=15.589, - outage_count=456, - customers_served=789, + with ( + patch( + "peco.PecoOutageApi.get_outage_totals", + return_value=OutageResults( + customers_out=123, + percent_customers_out=15.589, + outage_count=456, + customers_served=789, + ), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -66,18 +69,21 @@ async def test_sensor_available( config_entry = MockConfigEntry(domain=DOMAIN, data=COUNTY_ENTRY_DATA) config_entry.add_to_hass(hass) - with patch( - "peco.PecoOutageApi.get_outage_count", - return_value=OutageResults( - customers_out=123, - percent_customers_out=15.589, - outage_count=456, - customers_served=789, + with ( + patch( + "peco.PecoOutageApi.get_outage_count", + return_value=OutageResults( + customers_out=123, + percent_customers_out=15.589, + outage_count=456, + customers_served=789, + ), ), - ), patch( - "peco.PecoOutageApi.get_map_alerts", - return_value=AlertResults( - alert_content="Testing 1234", alert_title="Testing 4321" + patch( + "peco.PecoOutageApi.get_map_alerts", + return_value=AlertResults( + alert_content="Testing 1234", alert_title="Testing 4321" + ), ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/pegel_online/test_config_flow.py b/tests/components/pegel_online/test_config_flow.py index c9b22174424..fedcba94616 100644 --- a/tests/components/pegel_online/test_config_flow.py +++ b/tests/components/pegel_online/test_config_flow.py @@ -36,11 +36,14 @@ async def test_user(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.pegel_online.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.pegel_online.config_flow.PegelOnline", - ) as pegelonline: + with ( + patch( + "homeassistant.components.pegel_online.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.pegel_online.config_flow.PegelOnline", + ) as pegelonline, + ): pegelonline.return_value = PegelOnlineMock(nearby_stations=MOCK_NEARBY_STATIONS) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_USER_DATA_STEP1 @@ -100,11 +103,14 @@ async def test_connection_error(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.pegel_online.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.pegel_online.config_flow.PegelOnline", - ) as pegelonline: + with ( + patch( + "homeassistant.components.pegel_online.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.pegel_online.config_flow.PegelOnline", + ) as pegelonline, + ): # connection issue during setup pegelonline.return_value = PegelOnlineMock(side_effect=ClientError) result = await hass.config_entries.flow.async_configure( @@ -142,11 +148,14 @@ async def test_user_no_stations(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.pegel_online.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.pegel_online.config_flow.PegelOnline", - ) as pegelonline: + with ( + patch( + "homeassistant.components.pegel_online.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.pegel_online.config_flow.PegelOnline", + ) as pegelonline, + ): # no stations found pegelonline.return_value = PegelOnlineMock(nearby_stations={}) result = await hass.config_entries.flow.async_configure( diff --git a/tests/components/philips_js/conftest.py b/tests/components/philips_js/conftest.py index 15e796ec86e..3591546dfe9 100644 --- a/tests/components/philips_js/conftest.py +++ b/tests/components/philips_js/conftest.py @@ -16,10 +16,13 @@ from tests.common import MockConfigEntry, mock_device_registry @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock, None, None]: """Disable component setup.""" - with patch( - "homeassistant.components.philips_js.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.philips_js.async_unload_entry", return_value=True + with ( + patch( + "homeassistant.components.philips_js.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.philips_js.async_unload_entry", return_value=True + ), ): yield mock_setup_entry @@ -49,9 +52,12 @@ def mock_tv(): tv.ambilight_styles = {} tv.ambilight_cached = {} - with patch( - "homeassistant.components.philips_js.config_flow.PhilipsTV", return_value=tv - ), patch("homeassistant.components.philips_js.PhilipsTV", return_value=tv): + with ( + patch( + "homeassistant.components.philips_js.config_flow.PhilipsTV", return_value=tv + ), + patch("homeassistant.components.philips_js.PhilipsTV", return_value=tv), + ): yield tv diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index e5ff016b5f3..621d002bb62 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -71,9 +71,10 @@ class PilightDaemonSim: @patch("homeassistant.components.pilight._LOGGER.error") async def test_connection_failed_error(mock_error, hass: HomeAssistant) -> None: """Try to connect at 127.0.0.1:5001 with socket error.""" - with assert_setup_component(4), patch( - "pilight.pilight.Client", side_effect=socket.error - ) as mock_client: + with ( + assert_setup_component(4), + patch("pilight.pilight.Client", side_effect=socket.error) as mock_client, + ): assert not await async_setup_component( hass, pilight.DOMAIN, {pilight.DOMAIN: {}} ) @@ -86,9 +87,10 @@ async def test_connection_failed_error(mock_error, hass: HomeAssistant) -> None: @patch("homeassistant.components.pilight._LOGGER.error") async def test_connection_timeout_error(mock_error, hass: HomeAssistant) -> None: """Try to connect at 127.0.0.1:5001 with socket timeout.""" - with assert_setup_component(4), patch( - "pilight.pilight.Client", side_effect=socket.timeout - ) as mock_client: + with ( + assert_setup_component(4), + patch("pilight.pilight.Client", side_effect=socket.timeout) as mock_client, + ): assert not await async_setup_component( hass, pilight.DOMAIN, {pilight.DOMAIN: {}} ) @@ -142,8 +144,9 @@ async def test_send_code(mock_pilight_error, hass: HomeAssistant) -> None: @patch("homeassistant.components.pilight._LOGGER.error") async def test_send_code_fail(mock_pilight_error, hass: HomeAssistant) -> None: """Check IOError exception error message.""" - with assert_setup_component(4), patch( - "pilight.pilight.Client.send_code", side_effect=IOError + with ( + assert_setup_component(4), + patch("pilight.pilight.Client.send_code", side_effect=IOError), ): assert await async_setup_component(hass, pilight.DOMAIN, {pilight.DOMAIN: {}}) diff --git a/tests/components/ping/conftest.py b/tests/components/ping/conftest.py index db5de50e659..9bbbc9e6e32 100644 --- a/tests/components/ping/conftest.py +++ b/tests/components/ping/conftest.py @@ -17,10 +17,13 @@ from tests.common import MockConfigEntry @pytest.fixture def patch_setup(*args, **kwargs): """Patch setup methods.""" - with patch( - "homeassistant.components.ping.async_setup_entry", - return_value=True, - ), patch("homeassistant.components.ping.async_setup", return_value=True): + with ( + patch( + "homeassistant.components.ping.async_setup_entry", + return_value=True, + ), + patch("homeassistant.components.ping.async_setup", return_value=True), + ): yield @@ -29,9 +32,10 @@ async def patch_ping(): """Patch icmplib async_ping.""" mock = Host("10.10.10.10", 5, [10, 1, 2, 5, 6]) - with patch( - "homeassistant.components.ping.helpers.async_ping", return_value=mock - ), patch("homeassistant.components.ping.async_ping", return_value=mock): + with ( + patch("homeassistant.components.ping.helpers.async_ping", return_value=mock), + patch("homeassistant.components.ping.async_ping", return_value=mock), + ): yield mock diff --git a/tests/components/ping/test_device_tracker.py b/tests/components/ping/test_device_tracker.py index 288231a609b..a01bd0fa1bf 100644 --- a/tests/components/ping/test_device_tracker.py +++ b/tests/components/ping/test_device_tracker.py @@ -123,9 +123,12 @@ async def test_import_delete_known_devices( } files = {legacy.YAML_DEVICES: dump(yaml_devices)} - with patch_yaml_files(files, True), patch( - "homeassistant.components.ping.device_tracker.remove_device_from_config" - ) as remove_device_from_config: + with ( + patch_yaml_files(files, True), + patch( + "homeassistant.components.ping.device_tracker.remove_device_from_config" + ) as remove_device_from_config, + ): await async_setup_component( hass, "device_tracker", diff --git a/tests/components/plaato/test_config_flow.py b/tests/components/plaato/test_config_flow.py index 6b61ca8a649..7088b672f69 100644 --- a/tests/components/plaato/test_config_flow.py +++ b/tests/components/plaato/test_config_flow.py @@ -27,10 +27,15 @@ UNIQUE_ID = "plaato_unique_id" @pytest.fixture(name="webhook_id") def mock_webhook_id(): """Mock webhook_id.""" - with patch( - "homeassistant.components.webhook.async_generate_id", return_value=WEBHOOK_ID - ), patch( - "homeassistant.components.webhook.async_generate_url", return_value="hook_id" + with ( + patch( + "homeassistant.components.webhook.async_generate_id", + return_value=WEBHOOK_ID, + ), + patch( + "homeassistant.components.webhook.async_generate_url", + return_value="hook_id", + ), ): yield @@ -101,15 +106,17 @@ async def test_show_config_form_validate_webhook( assert result["step_id"] == "api_method" assert await async_setup_component(hass, "cloud", {}) - with patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", - return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + with ( + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch( + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -147,15 +154,17 @@ async def test_show_config_form_validate_webhook_not_connected( assert result["step_id"] == "api_method" assert await async_setup_component(hass, "cloud", {}) - with patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=False - ), patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", - return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + with ( + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=False), + patch( + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://hooks.nabu.casa/ABCD"}, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 30b20eec4ab..9cfcda1b29d 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -59,10 +59,12 @@ async def test_bad_credentials( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized - ), patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value="BAD TOKEN" + with ( + patch( + "plexapi.myplex.MyPlexAccount", side_effect=plexapi.exceptions.Unauthorized + ), + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value="BAD TOKEN"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -89,11 +91,13 @@ async def test_bad_hostname( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "plexapi.myplex.MyPlexResource.connect", - side_effect=requests.exceptions.ConnectionError, - ), patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch( + "plexapi.myplex.MyPlexResource.connect", + side_effect=requests.exceptions.ConnectionError, + ), + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -120,9 +124,11 @@ async def test_unknown_exception( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexapi.myplex.MyPlexAccount", side_effect=Exception), patch( - "plexauth.PlexAuth.initiate_auth" - ), patch("plexauth.PlexAuth.token", return_value="MOCK_TOKEN"): + with ( + patch("plexapi.myplex.MyPlexAccount", side_effect=Exception), + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value="MOCK_TOKEN"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) @@ -152,8 +158,9 @@ async def test_no_servers_found( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -182,8 +189,9 @@ async def test_single_available_server( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -229,8 +237,9 @@ async def test_multiple_servers_with_selection( "https://plex.tv/api/v2/resources", text=plextv_resources_two_servers, ) - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -294,8 +303,9 @@ async def test_adding_last_unconfigured_server( text=plextv_resources_two_servers, ) - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -353,8 +363,9 @@ async def test_all_available_servers_configured( text=plextv_resources_two_servers, ) - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -478,8 +489,9 @@ async def test_external_timed_out( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=None + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=None), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -506,8 +518,9 @@ async def test_callback_view( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -637,8 +650,9 @@ async def test_manual_config( assert result["step_id"] == "manual_setup" assert result["errors"]["base"] == "ssl_error" - with patch("homeassistant.components.plex.PlexWebsocket", autospec=True), patch( - "homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True) + with ( + patch("homeassistant.components.plex.PlexWebsocket", autospec=True), + patch("homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True)), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MANUAL_SERVER @@ -678,9 +692,10 @@ async def test_manual_config_with_token( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "manual_setup" - with patch( - "homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True) - ), patch("homeassistant.components.plex.PlexWebsocket", autospec=True): + with ( + patch("homeassistant.components.plex.GDM", return_value=MockGDM(disabled=True)), + patch("homeassistant.components.plex.PlexWebsocket", autospec=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_TOKEN: MOCK_TOKEN} ) @@ -741,8 +756,9 @@ async def test_reauth( ) flow_id = result["flow_id"] - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN" + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN"), ): result = await hass.config_entries.flow.async_configure(flow_id, user_input={}) assert result["type"] == FlowResultType.EXTERNAL_STEP @@ -792,8 +808,9 @@ async def test_reauth_multiple_servers_available( flow_id = result["flow_id"] - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN" + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value="BRAND_NEW_TOKEN"), ): result = await hass.config_entries.flow.async_configure(flow_id, user_input={}) assert result["type"] == FlowResultType.EXTERNAL_STEP @@ -826,9 +843,11 @@ async def test_client_request_missing(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=None - ), pytest.raises(RuntimeError): + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=None), + pytest.raises(RuntimeError), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) @@ -848,12 +867,16 @@ async def test_client_header_issues( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=None - ), patch( - "homeassistant.components.http.current_request.get", return_value=MockRequest() - ), pytest.raises( - RuntimeError, + with ( + patch("plexauth.PlexAuth.initiate_auth"), + patch("plexauth.PlexAuth.token", return_value=None), + patch( + "homeassistant.components.http.current_request.get", + return_value=MockRequest(), + ), + pytest.raises( + RuntimeError, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index e514ea7baa3..a1a05db9d9a 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -351,9 +351,13 @@ async def test_trigger_reauth( assert entry.state is ConfigEntryState.LOADED - with patch( - "plexapi.server.PlexServer.clients", side_effect=plexapi.exceptions.Unauthorized - ), patch("plexapi.server.PlexServer", side_effect=plexapi.exceptions.Unauthorized): + with ( + patch( + "plexapi.server.PlexServer.clients", + side_effect=plexapi.exceptions.Unauthorized, + ), + patch("plexapi.server.PlexServer", side_effect=plexapi.exceptions.Unauthorized), + ): trigger_plex_update(mock_websocket) await wait_for_debouncer(hass) diff --git a/tests/components/plex/test_media_search.py b/tests/components/plex/test_media_search.py index 517fdd2f689..5578ecd2550 100644 --- a/tests/components/plex/test_media_search.py +++ b/tests/components/plex/test_media_search.py @@ -41,8 +41,9 @@ async def test_media_lookups( }, True, ) - with pytest.raises(MediaNotFound) as excinfo, patch( - "plexapi.server.PlexServer.fetchItem", side_effect=NotFound + with ( + pytest.raises(MediaNotFound) as excinfo, + patch("plexapi.server.PlexServer.fetchItem", side_effect=NotFound), ): await hass.services.async_call( MEDIA_PLAYER_DOMAIN, diff --git a/tests/components/plex/test_playback.py b/tests/components/plex/test_playback.py index 0006ea49efb..33c8b130749 100644 --- a/tests/components/plex/test_playback.py +++ b/tests/components/plex/test_playback.py @@ -65,11 +65,14 @@ async def test_media_player_playback( # Test media lookup failure payload = '{"library_name": "Movies", "title": "Movie 1" }' - with patch( - "plexapi.library.LibrarySection.search", - return_value=None, - __qualname__="search", - ), pytest.raises(HomeAssistantError) as excinfo: + with ( + patch( + "plexapi.library.LibrarySection.search", + return_value=None, + __qualname__="search", + ), + pytest.raises(HomeAssistantError) as excinfo, + ): await hass.services.async_call( MP_DOMAIN, SERVICE_PLAY_MEDIA, @@ -216,13 +219,16 @@ async def test_media_player_playback( # Test multiple choices with allow_multiple movies = [movie1, movie2, movie3] - with patch( - "plexapi.library.LibrarySection.search", - return_value=movies, - __qualname__="search", - ), patch( - "homeassistant.components.plex.server.PlexServer.create_playqueue" - ) as mock_create_playqueue: + with ( + patch( + "plexapi.library.LibrarySection.search", + return_value=movies, + __qualname__="search", + ), + patch( + "homeassistant.components.plex.server.PlexServer.create_playqueue" + ) as mock_create_playqueue, + ): await hass.services.async_call( MP_DOMAIN, SERVICE_PLAY_MEDIA, diff --git a/tests/components/plugwise/test_climate.py b/tests/components/plugwise/test_climate.py index c5ab3a209c2..8041d2778ef 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -98,9 +98,9 @@ async def test_adam_3_climate_entity_attributes( HVACMode.COOL, ] data = mock_smile_adam_3.async_update.return_value - data.devices["da224107914542988a88561b4452b0f6"][ - "select_regulation_mode" - ] = "heating" + data.devices["da224107914542988a88561b4452b0f6"]["select_regulation_mode"] = ( + "heating" + ) data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["control_state"] = "heating" data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][ "cooling_state" @@ -121,9 +121,9 @@ async def test_adam_3_climate_entity_attributes( HVACMode.HEAT, ] data = mock_smile_adam_3.async_update.return_value - data.devices["da224107914542988a88561b4452b0f6"][ - "select_regulation_mode" - ] = "cooling" + data.devices["da224107914542988a88561b4452b0f6"]["select_regulation_mode"] = ( + "cooling" + ) data.devices["ad4838d7d35c4d6ea796ee12ae5aedf8"]["control_state"] = "cooling" data.devices["056ee145a816487eaa69243c3280f8bf"]["binary_sensors"][ "cooling_state" diff --git a/tests/components/plum_lightpad/test_config_flow.py b/tests/components/plum_lightpad/test_config_flow.py index bfa870dd3b1..37934942734 100644 --- a/tests/components/plum_lightpad/test_config_flow.py +++ b/tests/components/plum_lightpad/test_config_flow.py @@ -20,12 +20,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData" - ), patch( - "homeassistant.components.plum_lightpad.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.plum_lightpad.utils.Plum.loadCloudData"), + patch( + "homeassistant.components.plum_lightpad.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "test-plum-username", "password": "test-plum-password"}, @@ -72,11 +73,12 @@ async def test_form_one_entry_per_email_allowed(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData" - ), patch( - "homeassistant.components.plum_lightpad.async_setup_entry" - ) as mock_setup_entry: + with ( + patch("homeassistant.components.plum_lightpad.utils.Plum.loadCloudData"), + patch( + "homeassistant.components.plum_lightpad.async_setup_entry" + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "test-plum-username", "password": "test-plum-password"}, diff --git a/tests/components/plum_lightpad/test_init.py b/tests/components/plum_lightpad/test_init.py index 6035713a24f..c34ecfd8deb 100644 --- a/tests/components/plum_lightpad/test_init.py +++ b/tests/components/plum_lightpad/test_init.py @@ -28,11 +28,14 @@ async def test_async_setup_entry_sets_up_light(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData" - ) as mock_loadCloudData, patch( - "homeassistant.components.plum_lightpad.light.async_setup_entry" - ) as mock_light_async_setup_entry: + with ( + patch( + "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData" + ) as mock_loadCloudData, + patch( + "homeassistant.components.plum_lightpad.light.async_setup_entry" + ) as mock_light_async_setup_entry, + ): result = await hass.config_entries.async_setup(config_entry.entry_id) assert result is True @@ -50,12 +53,15 @@ async def test_async_setup_entry_handles_auth_error(hass: HomeAssistant) -> None ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData", - side_effect=ContentTypeError(Mock(), None), - ), patch( - "homeassistant.components.plum_lightpad.light.async_setup_entry" - ) as mock_light_async_setup_entry: + with ( + patch( + "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData", + side_effect=ContentTypeError(Mock(), None), + ), + patch( + "homeassistant.components.plum_lightpad.light.async_setup_entry" + ) as mock_light_async_setup_entry, + ): result = await hass.config_entries.async_setup(config_entry.entry_id) assert result is False @@ -70,12 +76,15 @@ async def test_async_setup_entry_handles_http_error(hass: HomeAssistant) -> None ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData", - side_effect=HTTPError, - ), patch( - "homeassistant.components.plum_lightpad.light.async_setup_entry" - ) as mock_light_async_setup_entry: + with ( + patch( + "homeassistant.components.plum_lightpad.utils.Plum.loadCloudData", + side_effect=HTTPError, + ), + patch( + "homeassistant.components.plum_lightpad.light.async_setup_entry" + ) as mock_light_async_setup_entry, + ): result = await hass.config_entries.async_setup(config_entry.entry_id) assert result is False diff --git a/tests/components/poolsense/test_config_flow.py b/tests/components/poolsense/test_config_flow.py index 76766ef1d87..7a91e546a59 100644 --- a/tests/components/poolsense/test_config_flow.py +++ b/tests/components/poolsense/test_config_flow.py @@ -37,11 +37,12 @@ async def test_invalid_credentials(hass: HomeAssistant) -> None: async def test_valid_credentials(hass: HomeAssistant) -> None: """Test we handle invalid credentials.""" - with patch( - "poolsense.PoolSense.test_poolsense_credentials", return_value=True - ), patch( - "homeassistant.components.poolsense.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch("poolsense.PoolSense.test_poolsense_credentials", return_value=True), + patch( + "homeassistant.components.poolsense.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, diff --git a/tests/components/powerwall/test_binary_sensor.py b/tests/components/powerwall/test_binary_sensor.py index 1fc576543c3..6b7e73373ee 100644 --- a/tests/components/powerwall/test_binary_sensor.py +++ b/tests/components/powerwall/test_binary_sensor.py @@ -18,11 +18,14 @@ async def test_sensors(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -85,11 +88,14 @@ async def test_sensors_with_empty_meters(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index 1b78a4a7212..83156ffb170 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -41,13 +41,16 @@ async def test_form_source_user(hass: HomeAssistant) -> None: mock_powerwall = await _mock_powerwall_site_name(hass, "MySite") - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_CONFIG, @@ -193,13 +196,16 @@ async def test_already_configured_with_ignored(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -232,13 +238,16 @@ async def test_dhcp_discovery_manual_configure(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], VALID_CONFIG, @@ -271,13 +280,16 @@ async def test_dhcp_discovery_auto_configure(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -333,13 +345,16 @@ async def test_form_reauth(hass: HomeAssistant) -> None: mock_powerwall = await _mock_powerwall_site_name(hass, "My site") - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -364,12 +379,15 @@ async def test_dhcp_discovery_update_ip_address(hass: HomeAssistant) -> None: mock_powerwall = MagicMock(login=MagicMock(side_effect=PowerwallUnreachableError)) mock_powerwall.__aenter__.return_value = mock_powerwall - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -398,12 +416,15 @@ async def test_dhcp_discovery_does_not_update_ip_when_auth_fails( entry.add_to_hass(hass) mock_powerwall = MagicMock(login=MagicMock(side_effect=AccessDeniedError("any"))) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -432,12 +453,15 @@ async def test_dhcp_discovery_does_not_update_ip_when_auth_successful( entry.add_to_hass(hass) mock_powerwall = MagicMock(login=MagicMock(return_value=True)) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -464,12 +488,15 @@ async def test_dhcp_discovery_updates_unique_id(hass: HomeAssistant) -> None: entry.add_to_hass(hass) mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -500,12 +527,15 @@ async def test_dhcp_discovery_updates_unique_id_when_entry_is_failed( entry.mock_state(hass, config_entries.ConfigEntryState.SETUP_ERROR) mock_powerwall = await _mock_powerwall_site_name(hass, "Some site") - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -535,11 +565,14 @@ async def test_discovered_wifi_does_not_update_ip_if_is_still_online( entry.add_to_hass(hass) mock_powerwall = await _mock_powerwall_with_fixtures(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -572,11 +605,14 @@ async def test_discovered_wifi_does_not_update_ip_online_but_access_denied( mock_powerwall_no_access = await _mock_powerwall_with_fixtures(hass) mock_powerwall_no_access.login.side_effect = AccessDeniedError("any") - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall_no_access, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall_no_access, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/powerwall/test_init.py b/tests/components/powerwall/test_init.py index ed0dc0ebde8..de8da12ccb5 100644 --- a/tests/components/powerwall/test_init.py +++ b/tests/components/powerwall/test_init.py @@ -40,11 +40,14 @@ async def test_update_data_reauthenticate_on_access_denied(hass: HomeAssistant) domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4", CONF_PASSWORD: "password"} ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/powerwall/test_sensor.py b/tests/components/powerwall/test_sensor.py index 8ef577f8eee..2ec9f44bd0e 100644 --- a/tests/components/powerwall/test_sensor.py +++ b/tests/components/powerwall/test_sensor.py @@ -34,11 +34,14 @@ async def test_sensors( config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -203,11 +206,14 @@ async def test_sensor_backup_reserve_unavailable(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -223,11 +229,14 @@ async def test_sensors_with_empty_meters(hass: HomeAssistant) -> None: config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -261,11 +270,14 @@ async def test_unique_id_migrate( ) assert old_mysite_load_power_entity.entity_id == "sensor.mysite_load_power" - with patch( - "homeassistant.components.powerwall.config_flow.Powerwall", - return_value=mock_powerwall, - ), patch( - "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + with ( + patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ), + patch( + "homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 8847e779b9e..1140dc74849 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -337,8 +337,9 @@ async def test_log_object_sources( fake_object2 = FakeObject() - with patch("gc.collect"), patch( - "gc.get_objects", return_value=[fake_object, fake_object2] + with ( + patch("gc.collect"), + patch("gc.get_objects", return_value=[fake_object, fake_object2]), ): caplog.clear() diff --git a/tests/components/prosegur/conftest.py b/tests/components/prosegur/conftest.py index bab871d9952..0b18c2c5e17 100644 --- a/tests/components/prosegur/conftest.py +++ b/tests/components/prosegur/conftest.py @@ -62,9 +62,12 @@ async def init_integration( """Set up the Prosegur integration for testing.""" mock_config_entry.add_to_hass(hass) - with patch( - "pyprosegur.installation.Installation.retrieve", return_value=mock_install - ), patch("pyprosegur.auth.Auth.login"): + with ( + patch( + "pyprosegur.installation.Installation.retrieve", return_value=mock_install + ), + patch("pyprosegur.auth.Auth.login"), + ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/prosegur/test_camera.py b/tests/components/prosegur/test_camera.py index fdf13e30359..ed503d676ff 100644 --- a/tests/components/prosegur/test_camera.py +++ b/tests/components/prosegur/test_camera.py @@ -34,9 +34,10 @@ async def test_camera_fail( return_value=b"ABC", side_effect=ProsegurException() ) - with caplog.at_level( - logging.ERROR, logger="homeassistant.components.prosegur" - ), pytest.raises(HomeAssistantError) as exc: + with ( + caplog.at_level(logging.ERROR, logger="homeassistant.components.prosegur"), + pytest.raises(HomeAssistantError) as exc, + ): await camera.async_get_image(hass, "camera.contract_1234abcd_test_cam") assert "Unable to get image" in str(exc.value) diff --git a/tests/components/prosegur/test_config_flow.py b/tests/components/prosegur/test_config_flow.py index c0a7ffcb1eb..3c3c2468696 100644 --- a/tests/components/prosegur/test_config_flow.py +++ b/tests/components/prosegur/test_config_flow.py @@ -22,13 +22,16 @@ async def test_form(hass: HomeAssistant, mock_list_contracts) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.prosegur.config_flow.Installation.list", - return_value=mock_list_contracts, - ) as mock_retrieve, patch( - "homeassistant.components.prosegur.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.prosegur.config_flow.Installation.list", + return_value=mock_list_contracts, + ) as mock_retrieve, + patch( + "homeassistant.components.prosegur.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -153,13 +156,16 @@ async def test_reauth_flow(hass: HomeAssistant, mock_list_contracts) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.prosegur.config_flow.Installation.list", - return_value=mock_list_contracts, - ) as mock_installation, patch( - "homeassistant.components.prosegur.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.prosegur.config_flow.Installation.list", + return_value=mock_list_contracts, + ) as mock_installation, + patch( + "homeassistant.components.prosegur.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/prusalink/test_button.py b/tests/components/prusalink/test_button.py index e99cf9c2180..54f3854161c 100644 --- a/tests/components/prusalink/test_button.py +++ b/tests/components/prusalink/test_button.py @@ -55,8 +55,9 @@ async def test_button_pause_cancel( assert len(mock_meth.mock_calls) == 1 # Verify it calls correct method + does error handling - with pytest.raises(HomeAssistantError), patch( - f"pyprusalink.PrusaLink.{method}", side_effect=Conflict + with ( + pytest.raises(HomeAssistantError), + patch(f"pyprusalink.PrusaLink.{method}", side_effect=Conflict), ): await hass.services.async_call( "button", @@ -89,8 +90,11 @@ async def test_button_resume_cancel( assert state is not None assert state.state == "unknown" - with patch(f"pyprusalink.PrusaLink.{method}") as mock_meth, patch( - "homeassistant.components.prusalink.PrusaLinkUpdateCoordinator._fetch_data" + with ( + patch(f"pyprusalink.PrusaLink.{method}") as mock_meth, + patch( + "homeassistant.components.prusalink.PrusaLinkUpdateCoordinator._fetch_data" + ), ): await hass.services.async_call( "button", @@ -102,8 +106,9 @@ async def test_button_resume_cancel( assert len(mock_meth.mock_calls) == 1 # Verify it calls correct method + does error handling - with pytest.raises(HomeAssistantError), patch( - f"pyprusalink.PrusaLink.{method}", side_effect=Conflict + with ( + pytest.raises(HomeAssistantError), + patch(f"pyprusalink.PrusaLink.{method}", side_effect=Conflict), ): await hass.services.async_call( "button", diff --git a/tests/components/prusalink/test_init.py b/tests/components/prusalink/test_init.py index a462a64ca31..1160143ea11 100644 --- a/tests/components/prusalink/test_init.py +++ b/tests/components/prusalink/test_init.py @@ -44,18 +44,23 @@ async def test_failed_update( assert await hass.config_entries.async_setup(mock_config_entry.entry_id) assert mock_config_entry.state == ConfigEntryState.LOADED - with patch( - "homeassistant.components.prusalink.PrusaLink.get_version", - side_effect=exception, - ), patch( - "homeassistant.components.prusalink.PrusaLink.get_status", - side_effect=exception, - ), patch( - "homeassistant.components.prusalink.PrusaLink.get_legacy_printer", - side_effect=exception, - ), patch( - "homeassistant.components.prusalink.PrusaLink.get_job", - side_effect=exception, + with ( + patch( + "homeassistant.components.prusalink.PrusaLink.get_version", + side_effect=exception, + ), + patch( + "homeassistant.components.prusalink.PrusaLink.get_status", + side_effect=exception, + ), + patch( + "homeassistant.components.prusalink.PrusaLink.get_legacy_printer", + side_effect=exception, + ), + patch( + "homeassistant.components.prusalink.PrusaLink.get_job", + side_effect=exception, + ), ): async_fire_time_changed(hass, utcnow() + timedelta(seconds=30), fire_all=True) await hass.async_block_till_done() diff --git a/tests/components/prusalink/test_sensor.py b/tests/components/prusalink/test_sensor.py index 366f2d3abc8..bba06f66146 100644 --- a/tests/components/prusalink/test_sensor.py +++ b/tests/components/prusalink/test_sensor.py @@ -27,11 +27,12 @@ from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) def setup_sensor_platform_only(): """Only setup sensor platform.""" - with patch( - "homeassistant.components.prusalink.PLATFORMS", [Platform.SENSOR] - ), patch( - "homeassistant.helpers.entity.Entity.entity_registry_enabled_default", - PropertyMock(return_value=True), + with ( + patch("homeassistant.components.prusalink.PLATFORMS", [Platform.SENSOR]), + patch( + "homeassistant.helpers.entity.Entity.entity_registry_enabled_default", + PropertyMock(return_value=True), + ), ): yield diff --git a/tests/components/ps4/test_config_flow.py b/tests/components/ps4/test_config_flow.py index 61bf01f0a91..db478903d1e 100644 --- a/tests/components/ps4/test_config_flow.py +++ b/tests/components/ps4/test_config_flow.py @@ -127,8 +127,11 @@ async def test_full_flow_implementation(hass: HomeAssistant) -> None: assert result["step_id"] == "link" # User Input results in created entry. - with patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)), patch( - "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] + with ( + patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)), + patch( + "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_CONFIG @@ -169,9 +172,12 @@ async def test_multiple_flow_implementation(hass: HomeAssistant) -> None: assert result["step_id"] == "link" # User Input results in created entry. - with patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)), patch( - "pyps4_2ndscreen.Helper.has_devices", - return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], + with ( + patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)), + patch( + "pyps4_2ndscreen.Helper.has_devices", + return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_CONFIG @@ -191,9 +197,12 @@ async def test_multiple_flow_implementation(hass: HomeAssistant) -> None: # Test additional flow. # User Step Started, results in Step Mode: - with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None), patch( - "pyps4_2ndscreen.Helper.has_devices", - return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], + with ( + patch("pyps4_2ndscreen.Helper.port_bind", return_value=None), + patch( + "pyps4_2ndscreen.Helper.has_devices", + return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -221,10 +230,13 @@ async def test_multiple_flow_implementation(hass: HomeAssistant) -> None: assert result["step_id"] == "link" # Step Link - with patch( - "pyps4_2ndscreen.Helper.has_devices", - return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], - ), patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)): + with ( + patch( + "pyps4_2ndscreen.Helper.has_devices", + return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], + ), + patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_CONFIG_ADDITIONAL ) @@ -341,11 +353,14 @@ async def test_0_pin(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "mode" - with patch( - "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] - ), patch( - "homeassistant.components.ps4.config_flow.location.async_detect_location_info", - return_value=MOCK_LOCATION, + with ( + patch( + "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] + ), + patch( + "homeassistant.components.ps4.config_flow.location.async_detect_location_info", + return_value=MOCK_LOCATION, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_AUTO @@ -355,10 +370,11 @@ async def test_0_pin(hass: HomeAssistant) -> None: mock_config = MOCK_CONFIG mock_config[CONF_CODE] = MOCK_CODE_LEAD_0 - with patch( - "pyps4_2ndscreen.Helper.link", return_value=(True, True) - ) as mock_call, patch( - "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] + with ( + patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)) as mock_call, + patch( + "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], mock_config diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index cc2ec5fde1b..238c3c15112 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -122,10 +122,13 @@ async def test_ps4_integration_setup(hass: HomeAssistant) -> None: async def test_creating_entry_sets_up_media_player(hass: HomeAssistant) -> None: """Test setting up PS4 loads the media player.""" mock_flow = "homeassistant.components.ps4.PlayStation4FlowHandler.async_step_user" - with patch( - "homeassistant.components.ps4.media_player.async_setup_entry", - return_value=True, - ) as mock_setup, patch(mock_flow, return_value=MOCK_FLOW_RESULT): + with ( + patch( + "homeassistant.components.ps4.media_player.async_setup_entry", + return_value=True, + ) as mock_setup, + patch(mock_flow, return_value=MOCK_FLOW_RESULT), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -156,12 +159,15 @@ async def test_config_flow_entry_migrate( assert mock_e_entry.entity_id == mock_entity_id assert mock_e_entry.unique_id == MOCK_UNIQUE_ID - with patch( - "homeassistant.util.location.async_detect_location_info", - return_value=MOCK_LOCATION, - ), patch( - "homeassistant.helpers.entity_registry.async_get", - return_value=entity_registry, + with ( + patch( + "homeassistant.util.location.async_detect_location_info", + return_value=MOCK_LOCATION, + ), + patch( + "homeassistant.helpers.entity_registry.async_get", + return_value=entity_registry, + ), ): await ps4.async_migrate_entry(hass, mock_entry) @@ -205,9 +211,10 @@ def test_games_reformat_to_dict( ) -> None: """Test old data format is converted to new format.""" patch_load_json_object.return_value = MOCK_GAMES_DATA_OLD_STR_FORMAT - with patch( - "homeassistant.components.ps4.save_json", side_effect=MagicMock() - ), patch("os.path.isfile", return_value=True): + with ( + patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), + patch("os.path.isfile", return_value=True), + ): mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) # New format is a nested dict. @@ -226,9 +233,10 @@ def test_games_reformat_to_dict( def test_load_games(hass: HomeAssistant, patch_load_json_object: MagicMock) -> None: """Test that games are loaded correctly.""" patch_load_json_object.return_value = MOCK_GAMES - with patch( - "homeassistant.components.ps4.save_json", side_effect=MagicMock() - ), patch("os.path.isfile", return_value=True): + with ( + patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), + patch("os.path.isfile", return_value=True), + ): mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) assert isinstance(mock_games, dict) @@ -246,9 +254,10 @@ def test_loading_games_returns_dict( ) -> None: """Test that loading games always returns a dict.""" patch_load_json_object.side_effect = HomeAssistantError - with patch( - "homeassistant.components.ps4.save_json", side_effect=MagicMock() - ), patch("os.path.isfile", return_value=True): + with ( + patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), + patch("os.path.isfile", return_value=True), + ): mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) assert isinstance(mock_games, dict) diff --git a/tests/components/ps4/test_media_player.py b/tests/components/ps4/test_media_player.py index a21a0800711..875b049d8c3 100644 --- a/tests/components/ps4/test_media_player.py +++ b/tests/components/ps4/test_media_player.py @@ -461,8 +461,9 @@ async def test_select_source( with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE): mock_entity_id = await setup_mock_component(hass) - with patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, patch( - "homeassistant.components.ps4.media_player.PS4Device.async_update" + with ( + patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, + patch("homeassistant.components.ps4.media_player.PS4Device.async_update"), ): # Test with title name. await hass.services.async_call( @@ -483,8 +484,9 @@ async def test_select_source_caps( with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE): mock_entity_id = await setup_mock_component(hass) - with patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, patch( - "homeassistant.components.ps4.media_player.PS4Device.async_update" + with ( + patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, + patch("homeassistant.components.ps4.media_player.PS4Device.async_update"), ): # Test with title name in caps. await hass.services.async_call( @@ -508,8 +510,9 @@ async def test_select_source_id( with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE): mock_entity_id = await setup_mock_component(hass) - with patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, patch( - "homeassistant.components.ps4.media_player.PS4Device.async_update" + with ( + patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, + patch("homeassistant.components.ps4.media_player.PS4Device.async_update"), ): # Test with title ID. await hass.services.async_call( diff --git a/tests/components/purpleair/conftest.py b/tests/components/purpleair/conftest.py index 7299616ad35..1305c98308d 100644 --- a/tests/components/purpleair/conftest.py +++ b/tests/components/purpleair/conftest.py @@ -74,9 +74,10 @@ def get_sensors_response_fixture(): @pytest.fixture(name="mock_aiopurpleair") async def mock_aiopurpleair_fixture(api): """Define a fixture to patch aiopurpleair.""" - with patch( - "homeassistant.components.purpleair.config_flow.API", return_value=api - ), patch("homeassistant.components.purpleair.coordinator.API", return_value=api): + with ( + patch("homeassistant.components.purpleair.config_flow.API", return_value=api), + patch("homeassistant.components.purpleair.coordinator.API", return_value=api), + ): yield api diff --git a/tests/components/pvoutput/conftest.py b/tests/components/pvoutput/conftest.py index 118bed94d77..122b55ca4c2 100644 --- a/tests/components/pvoutput/conftest.py +++ b/tests/components/pvoutput/conftest.py @@ -38,10 +38,13 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_pvoutput() -> Generator[None, MagicMock, None]: """Return a mocked PVOutput client.""" - with patch( - "homeassistant.components.pvoutput.coordinator.PVOutput", autospec=True - ) as pvoutput_mock, patch( - "homeassistant.components.pvoutput.config_flow.PVOutput", new=pvoutput_mock + with ( + patch( + "homeassistant.components.pvoutput.coordinator.PVOutput", autospec=True + ) as pvoutput_mock, + patch( + "homeassistant.components.pvoutput.config_flow.PVOutput", new=pvoutput_mock + ), ): pvoutput = pvoutput_mock.return_value pvoutput.status.return_value = Status.from_dict( diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index ced279b2f9a..bec94db71f9 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -20,20 +20,28 @@ async def test_setup(hass: HomeAssistant) -> None: "/some/config/dir/python_scripts/hello.py", "/some/config/dir/python_scripts/world_beer.py", ] - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts + ), + ): res = await async_setup_component(hass, "python_script", {}) assert res assert hass.services.has_service("python_script", "hello") assert hass.services.has_service("python_script", "world_beer") - with patch( - "homeassistant.components.python_script.open", - mock_open(read_data="fake source"), - create=True, - ), patch("homeassistant.components.python_script.execute") as mock_ex: + with ( + patch( + "homeassistant.components.python_script.open", + mock_open(read_data="fake source"), + create=True, + ), + patch("homeassistant.components.python_script.execute") as mock_ex, + ): await hass.services.async_call( "python_script", "hello", {"some": "data"}, blocking=True ) @@ -357,9 +365,14 @@ async def test_reload(hass: HomeAssistant) -> None: "/some/config/dir/python_scripts/hello.py", "/some/config/dir/python_scripts/world_beer.py", ] - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts + ), + ): res = await async_setup_component(hass, "python_script", {}) assert res @@ -371,9 +384,14 @@ async def test_reload(hass: HomeAssistant) -> None: "/some/config/dir/python_scripts/hello2.py", "/some/config/dir/python_scripts/world_beer.py", ] - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts + ), + ): await hass.services.async_call("python_script", "reload", {}, blocking=True) assert not hass.services.has_service("python_script", "hello") @@ -403,14 +421,19 @@ async def test_service_descriptions(hass: HomeAssistant) -> None: f"{hass.config.config_dir}/{FOLDER}/services.yaml": service_descriptions1 } - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch( - "homeassistant.components.python_script.glob.iglob", return_value=scripts1 - ), patch( - "homeassistant.components.python_script.os.path.exists", return_value=True - ), patch_yaml_files( - services_yaml1, + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts1 + ), + patch( + "homeassistant.components.python_script.os.path.exists", return_value=True + ), + patch_yaml_files( + services_yaml1, + ), ): await async_setup_component(hass, DOMAIN, {}) @@ -452,14 +475,19 @@ async def test_service_descriptions(hass: HomeAssistant) -> None: f"{hass.config.config_dir}/{FOLDER}/services.yaml": service_descriptions2 } - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch( - "homeassistant.components.python_script.glob.iglob", return_value=scripts2 - ), patch( - "homeassistant.components.python_script.os.path.exists", return_value=True - ), patch_yaml_files( - services_yaml2, + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts2 + ), + patch( + "homeassistant.components.python_script.os.path.exists", return_value=True + ), + patch_yaml_files( + services_yaml2, + ), ): await hass.services.async_call(DOMAIN, "reload", {}, blocking=True) descriptions = await async_get_all_descriptions(hass) @@ -503,9 +531,14 @@ async def test_execute_with_output( scripts = [ "/some/config/dir/python_scripts/hello.py", ] - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts + ), + ): await async_setup_component(hass, "python_script", {}) source = """ @@ -542,9 +575,14 @@ async def test_execute_no_output( scripts = [ "/some/config/dir/python_scripts/hello.py", ] - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts + ), + ): await async_setup_component(hass, "python_script", {}) source = """ @@ -576,20 +614,28 @@ async def test_execute_wrong_output_type(hass: HomeAssistant) -> None: scripts = [ "/some/config/dir/python_scripts/hello.py", ] - with patch( - "homeassistant.components.python_script.os.path.isdir", return_value=True - ), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts): + with ( + patch( + "homeassistant.components.python_script.os.path.isdir", return_value=True + ), + patch( + "homeassistant.components.python_script.glob.iglob", return_value=scripts + ), + ): await async_setup_component(hass, "python_script", {}) source = """ output = f"hello {data.get('name', 'World')}" """ - with patch( - "homeassistant.components.python_script.open", - mock_open(read_data=source), - create=True, - ), pytest.raises(ServiceValidationError): + with ( + patch( + "homeassistant.components.python_script.open", + mock_open(read_data=source), + create=True, + ), + pytest.raises(ServiceValidationError), + ): await hass.services.async_call( "python_script", "hello", diff --git a/tests/components/qingping/test_binary_sensor.py b/tests/components/qingping/test_binary_sensor.py index b05a213ef5c..adf7686b20e 100644 --- a/tests/components/qingping/test_binary_sensor.py +++ b/tests/components/qingping/test_binary_sensor.py @@ -73,9 +73,12 @@ async def test_binary_sensor_restore_state(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/qingping/test_sensor.py b/tests/components/qingping/test_sensor.py index 135eaf296ea..160841d9ff5 100644 --- a/tests/components/qingping/test_sensor.py +++ b/tests/components/qingping/test_sensor.py @@ -83,9 +83,12 @@ async def test_binary_sensor_restore_state(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 997d2f96aab..522c5fabe90 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -199,10 +199,13 @@ async def test_setup_with_custom_location(hass: HomeAssistant) -> None: "1234", "Title 1", 20.5, (38.1, -3.1), category="Category 1" ) - with patch( - "georss_qld_bushfire_alert_client.feed_manager.QldBushfireAlertFeed", - wraps=QldBushfireAlertFeed, - ) as mock_feed, patch("georss_client.feed.GeoRssFeed.update") as mock_feed_update: + with ( + patch( + "georss_qld_bushfire_alert_client.feed_manager.QldBushfireAlertFeed", + wraps=QldBushfireAlertFeed, + ) as mock_feed, + patch("georss_client.feed.GeoRssFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1] with assert_setup_component(1, geo_location.DOMAIN): diff --git a/tests/components/qnap_qsw/test_button.py b/tests/components/qnap_qsw/test_button.py index 27b5fcb075d..01a1951791c 100644 --- a/tests/components/qnap_qsw/test_button.py +++ b/tests/components/qnap_qsw/test_button.py @@ -18,13 +18,16 @@ async def test_qnap_buttons(hass: HomeAssistant) -> None: assert state assert state.state == STATE_UNKNOWN - with patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", - return_value=USERS_VERIFICATION_MOCK, - ) as mock_users_verification, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.post_system_command", - return_value=SYSTEM_COMMAND_MOCK, - ) as mock_post_system_command: + with ( + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", + return_value=USERS_VERIFICATION_MOCK, + ) as mock_users_verification, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_system_command", + return_value=SYSTEM_COMMAND_MOCK, + ) as mock_post_system_command, + ): await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, diff --git a/tests/components/qnap_qsw/test_config_flow.py b/tests/components/qnap_qsw/test_config_flow.py index 57f6094c850..26a6581b207 100644 --- a/tests/components/qnap_qsw/test_config_flow.py +++ b/tests/components/qnap_qsw/test_config_flow.py @@ -31,18 +31,23 @@ TEST_USERNAME = "test-username" async def test_form(hass: HomeAssistant) -> None: """Test that the form is served with valid input.""" - with patch( - "homeassistant.components.qnap_qsw.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_live", - return_value=LIVE_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", - return_value=SYSTEM_BOARD_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", - return_value=USERS_LOGIN_MOCK, + with ( + patch( + "homeassistant.components.qnap_qsw.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_live", + return_value=LIVE_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", + return_value=SYSTEM_BOARD_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", + return_value=USERS_LOGIN_MOCK, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -162,18 +167,23 @@ async def test_dhcp_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "discovered_connection" - with patch( - "homeassistant.components.qnap_qsw.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_live", - return_value=LIVE_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", - return_value=SYSTEM_BOARD_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", - return_value=USERS_LOGIN_MOCK, + with ( + patch( + "homeassistant.components.qnap_qsw.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_live", + return_value=LIVE_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", + return_value=SYSTEM_BOARD_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", + return_value=USERS_LOGIN_MOCK, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/qnap_qsw/test_coordinator.py b/tests/components/qnap_qsw/test_coordinator.py index 893a86b262d..388bab635c8 100644 --- a/tests/components/qnap_qsw/test_coordinator.py +++ b/tests/components/qnap_qsw/test_coordinator.py @@ -39,40 +39,52 @@ async def test_coordinator_client_connector_error( entry = MockConfigEntry(domain=DOMAIN, data=CONFIG) entry.add_to_hass(hass) - with patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition", - return_value=FIRMWARE_CONDITION_MOCK, - ) as mock_firmware_condition, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info", - return_value=FIRMWARE_INFO_MOCK, - ) as mock_firmware_info, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check", - return_value=FIRMWARE_UPDATE_CHECK_MOCK, - ) as mock_firmware_update_check, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_lacp_info", - return_value=LACP_INFO_MOCK, - ) as mock_lacp_info, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_statistics", - return_value=PORTS_STATISTICS_MOCK, - ) as mock_ports_statistics, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_status", - return_value=PORTS_STATUS_MOCK, - ) as mock_ports_status, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", - return_value=SYSTEM_BOARD_MOCK, - ) as mock_system_board, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_sensor", - return_value=SYSTEM_SENSOR_MOCK, - ) as mock_system_sensor, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_time", - return_value=SYSTEM_TIME_MOCK, - ) as mock_system_time, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", - return_value=USERS_VERIFICATION_MOCK, - ) as mock_users_verification, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", - return_value=USERS_LOGIN_MOCK, - ) as mock_users_login: + with ( + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition", + return_value=FIRMWARE_CONDITION_MOCK, + ) as mock_firmware_condition, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info", + return_value=FIRMWARE_INFO_MOCK, + ) as mock_firmware_info, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check", + return_value=FIRMWARE_UPDATE_CHECK_MOCK, + ) as mock_firmware_update_check, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_lacp_info", + return_value=LACP_INFO_MOCK, + ) as mock_lacp_info, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_statistics", + return_value=PORTS_STATISTICS_MOCK, + ) as mock_ports_statistics, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_status", + return_value=PORTS_STATUS_MOCK, + ) as mock_ports_status, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", + return_value=SYSTEM_BOARD_MOCK, + ) as mock_system_board, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_sensor", + return_value=SYSTEM_SENSOR_MOCK, + ) as mock_system_sensor, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_time", + return_value=SYSTEM_TIME_MOCK, + ) as mock_system_time, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", + return_value=USERS_VERIFICATION_MOCK, + ) as mock_users_verification, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", + return_value=USERS_LOGIN_MOCK, + ) as mock_users_login, + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/qnap_qsw/test_init.py b/tests/components/qnap_qsw/test_init.py index fedfdd26543..9b2d10f1ed0 100644 --- a/tests/components/qnap_qsw/test_init.py +++ b/tests/components/qnap_qsw/test_init.py @@ -21,15 +21,19 @@ async def test_firmware_check_error(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.qnap_qsw.QnapQswApi.check_firmware", - side_effect=APIError, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.validate", - return_value=None, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.update", - return_value=None, + with ( + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.check_firmware", + side_effect=APIError, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.validate", + return_value=None, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.update", + return_value=None, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -44,15 +48,19 @@ async def test_unload_entry(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.qnap_qsw.QnapQswApi.check_firmware", - return_value=None, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.validate", - return_value=None, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.update", - return_value=None, + with ( + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.check_firmware", + return_value=None, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.validate", + return_value=None, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.update", + return_value=None, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/qnap_qsw/test_update.py b/tests/components/qnap_qsw/test_update.py index f6eb9705912..df7b4b9af6b 100644 --- a/tests/components/qnap_qsw/test_update.py +++ b/tests/components/qnap_qsw/test_update.py @@ -47,16 +47,20 @@ async def test_qnap_qsw_update(hass: HomeAssistant) -> None: ) assert update.attributes[ATTR_IN_PROGRESS] is False - with patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check", - return_value=FIRMWARE_UPDATE_CHECK_MOCK, - ) as mock_firmware_update_check, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", - return_value=USERS_VERIFICATION_MOCK, - ) as mock_users_verification, patch( - "homeassistant.components.qnap_qsw.QnapQswApi.post_firmware_update_live", - return_value=FIRMWARE_UPDATE_LIVE_MOCK, - ) as mock_firmware_update_live: + with ( + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check", + return_value=FIRMWARE_UPDATE_CHECK_MOCK, + ) as mock_firmware_update_check, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", + return_value=USERS_VERIFICATION_MOCK, + ) as mock_users_verification, + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_firmware_update_live", + return_value=FIRMWARE_UPDATE_LIVE_MOCK, + ) as mock_firmware_update_live, + ): await hass.services.async_call( UPDATE_DOMAIN, SERVICE_INSTALL, diff --git a/tests/components/qnap_qsw/util.py b/tests/components/qnap_qsw/util.py index b0dd5d5bf60..63238bb30a1 100644 --- a/tests/components/qnap_qsw/util.py +++ b/tests/components/qnap_qsw/util.py @@ -503,39 +503,51 @@ async def async_init_integration( ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition", - return_value=FIRMWARE_CONDITION_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info", - return_value=FIRMWARE_INFO_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check", - return_value=FIRMWARE_UPDATE_CHECK_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_lacp_info", - return_value=LACP_INFO_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_statistics", - return_value=PORTS_STATISTICS_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_status", - return_value=PORTS_STATUS_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", - return_value=SYSTEM_BOARD_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_sensor", - return_value=SYSTEM_SENSOR_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_system_time", - return_value=SYSTEM_TIME_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", - return_value=USERS_VERIFICATION_MOCK, - ), patch( - "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", - return_value=USERS_LOGIN_MOCK, + with ( + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition", + return_value=FIRMWARE_CONDITION_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info", + return_value=FIRMWARE_INFO_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check", + return_value=FIRMWARE_UPDATE_CHECK_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_lacp_info", + return_value=LACP_INFO_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_statistics", + return_value=PORTS_STATISTICS_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_ports_status", + return_value=PORTS_STATUS_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", + return_value=SYSTEM_BOARD_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_sensor", + return_value=SYSTEM_SENSOR_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_system_time", + return_value=SYSTEM_TIME_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification", + return_value=USERS_VERIFICATION_MOCK, + ), + patch( + "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", + return_value=USERS_LOGIN_MOCK, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/rabbitair/test_config_flow.py b/tests/components/rabbitair/test_config_flow.py index 5f2295fd7f9..4b5867b441b 100644 --- a/tests/components/rabbitair/test_config_flow.py +++ b/tests/components/rabbitair/test_config_flow.py @@ -45,8 +45,9 @@ def use_mocked_zeroconf(mock_async_zeroconf): @pytest.fixture def rabbitair_connect() -> Generator[None, None, None]: """Mock connection.""" - with patch("rabbitair.UdpClient.get_info", return_value=get_mock_info()), patch( - "rabbitair.UdpClient.get_state", return_value=get_mock_state() + with ( + patch("rabbitair.UdpClient.get_info", return_value=get_mock_info()), + patch("rabbitair.UdpClient.get_state", return_value=get_mock_state()), ): yield diff --git a/tests/components/rachio/test_config_flow.py b/tests/components/rachio/test_config_flow.py index a5a4def94bb..b7325349746 100644 --- a/tests/components/rachio/test_config_flow.py +++ b/tests/components/rachio/test_config_flow.py @@ -39,12 +39,16 @@ async def test_form(hass: HomeAssistant) -> None: info=({"status": 200}, {"id": "myid"}), ) - with patch( - "homeassistant.components.rachio.config_flow.Rachio", return_value=rachio_mock - ), patch( - "homeassistant.components.rachio.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rachio.config_flow.Rachio", + return_value=rachio_mock, + ), + patch( + "homeassistant.components.rachio.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/radiotherm/test_config_flow.py b/tests/components/radiotherm/test_config_flow.py index 8da7c850aa5..7729dfb86b7 100644 --- a/tests/components/radiotherm/test_config_flow.py +++ b/tests/components/radiotherm/test_config_flow.py @@ -33,13 +33,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.radiotherm.data.radiotherm.get_thermostat", - return_value=_mock_radiotherm(), - ), patch( - "homeassistant.components.radiotherm.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.radiotherm.data.radiotherm.get_thermostat", + return_value=_mock_radiotherm(), + ), + patch( + "homeassistant.components.radiotherm.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -209,12 +212,15 @@ async def test_user_unique_id_already_exists(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.radiotherm.data.radiotherm.get_thermostat", - return_value=_mock_radiotherm(), - ), patch( - "homeassistant.components.radiotherm.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.radiotherm.data.radiotherm.get_thermostat", + return_value=_mock_radiotherm(), + ), + patch( + "homeassistant.components.radiotherm.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/rainbird/conftest.py b/tests/components/rainbird/conftest.py index 53df24264df..10101986007 100644 --- a/tests/components/rainbird/conftest.py +++ b/tests/components/rainbird/conftest.py @@ -171,12 +171,15 @@ def aioclient_mock(hass: HomeAssistant) -> Generator[AiohttpClientMocker, None, hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, close_session) return session - with patch( - "homeassistant.components.rainbird.async_create_clientsession", - side_effect=create_session, - ), patch( - "homeassistant.components.rainbird.config_flow.async_create_clientsession", - side_effect=create_session, + with ( + patch( + "homeassistant.components.rainbird.async_create_clientsession", + side_effect=create_session, + ), + patch( + "homeassistant.components.rainbird.config_flow.async_create_clientsession", + side_effect=create_session, + ), ): yield mocker diff --git a/tests/components/rainforest_eagle/test_config_flow.py b/tests/components/rainforest_eagle/test_config_flow.py index 4eaf26cb767..191a7a4793e 100644 --- a/tests/components/rainforest_eagle/test_config_flow.py +++ b/tests/components/rainforest_eagle/test_config_flow.py @@ -25,13 +25,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.rainforest_eagle.data.async_get_type", - return_value=(TYPE_EAGLE_200, "mock-hw"), - ), patch( - "homeassistant.components.rainforest_eagle.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rainforest_eagle.data.async_get_type", + return_value=(TYPE_EAGLE_200, "mock-hw"), + ), + patch( + "homeassistant.components.rainforest_eagle.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/rainmachine/conftest.py b/tests/components/rainmachine/conftest.py index c3724334776..9b0f8f0442a 100644 --- a/tests/components/rainmachine/conftest.py +++ b/tests/components/rainmachine/conftest.py @@ -130,13 +130,16 @@ def data_zones_fixture(): @pytest.fixture(name="setup_rainmachine") async def setup_rainmachine_fixture(hass, client, config): """Define a fixture to set up RainMachine.""" - with patch( - "homeassistant.components.rainmachine.Client", return_value=client - ), patch( - "homeassistant.components.rainmachine.config_flow.Client", return_value=client - ), patch( - "homeassistant.components.rainmachine.PLATFORMS", - [], + with ( + patch("homeassistant.components.rainmachine.Client", return_value=client), + patch( + "homeassistant.components.rainmachine.config_flow.Client", + return_value=client, + ), + patch( + "homeassistant.components.rainmachine.PLATFORMS", + [], + ), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index 8b8104fe7a7..1c065a8f7ce 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -82,10 +82,14 @@ async def test_migrate_1_2( assert entity_entry.entity_id == entity_id assert entity_entry.unique_id == old_unique_id - with patch( - "homeassistant.components.rainmachine.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.rainmachine.config_flow.Client", return_value=client + with ( + patch( + "homeassistant.components.rainmachine.async_setup_entry", return_value=True + ), + patch( + "homeassistant.components.rainmachine.config_flow.Client", + return_value=client, + ), ): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() @@ -235,10 +239,14 @@ async def test_step_homekit_zeroconf_new_controller_when_some_exist( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.rainmachine.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.rainmachine.config_flow.Client", return_value=client + with ( + patch( + "homeassistant.components.rainmachine.async_setup_entry", return_value=True + ), + patch( + "homeassistant.components.rainmachine.config_flow.Client", + return_value=client, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/raspberry_pi/test_init.py b/tests/components/raspberry_pi/test_init.py index ebe04ed8384..80b5eedf2af 100644 --- a/tests/components/raspberry_pi/test_init.py +++ b/tests/components/raspberry_pi/test_init.py @@ -37,11 +37,12 @@ async def test_setup_entry(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) assert not hass.config_entries.async_entries("rpi_power") - with patch( - "homeassistant.components.raspberry_pi.get_os_info", - return_value={"board": "rpi"}, - ) as mock_get_os_info, patch( - "homeassistant.components.rpi_power.config_flow.new_under_voltage" + with ( + patch( + "homeassistant.components.raspberry_pi.get_os_info", + return_value={"board": "rpi"}, + ) as mock_get_os_info, + patch("homeassistant.components.rpi_power.config_flow.new_under_voltage"), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/recollect_waste/conftest.py b/tests/components/recollect_waste/conftest.py index 746a2203992..360dd8aac98 100644 --- a/tests/components/recollect_waste/conftest.py +++ b/tests/components/recollect_waste/conftest.py @@ -56,12 +56,15 @@ def pickup_events_fixture(): @pytest.fixture(name="mock_aiorecollect") async def mock_aiorecollect_fixture(client): """Define a fixture to patch aiorecollect.""" - with patch( - "homeassistant.components.recollect_waste.Client", - return_value=client, - ), patch( - "homeassistant.components.recollect_waste.config_flow.Client", - return_value=client, + with ( + patch( + "homeassistant.components.recollect_waste.Client", + return_value=client, + ), + patch( + "homeassistant.components.recollect_waste.config_flow.Client", + return_value=client, + ), ): yield diff --git a/tests/components/recorder/auto_repairs/events/test_schema.py b/tests/components/recorder/auto_repairs/events/test_schema.py index a09da8a688a..5713e287222 100644 --- a/tests/components/recorder/auto_repairs/events/test_schema.py +++ b/tests/components/recorder/auto_repairs/events/test_schema.py @@ -23,14 +23,18 @@ async def test_validate_db_schema_fix_float_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", - return_value={"events.double precision"}, - ), patch( - "homeassistant.components.recorder.migration._modify_columns" - ) as modify_columns_mock: + with ( + patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine + ), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", + return_value={"events.double precision"}, + ), + patch( + "homeassistant.components.recorder.migration._modify_columns" + ) as modify_columns_mock, + ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -55,11 +59,12 @@ async def test_validate_db_schema_fix_utf8_issue_event_data( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", - return_value={"event_data.4-byte UTF-8"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", + return_value={"event_data.4-byte UTF-8"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -85,11 +90,12 @@ async def test_validate_db_schema_fix_collation_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_has_correct_collation", - return_value={"events.utf8mb4_unicode_ci"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_has_correct_collation", + return_value={"events.utf8mb4_unicode_ci"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) diff --git a/tests/components/recorder/auto_repairs/states/test_schema.py b/tests/components/recorder/auto_repairs/states/test_schema.py index 75d968f19d1..c0a9e930966 100644 --- a/tests/components/recorder/auto_repairs/states/test_schema.py +++ b/tests/components/recorder/auto_repairs/states/test_schema.py @@ -23,14 +23,18 @@ async def test_validate_db_schema_fix_float_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", - return_value={"states.double precision"}, - ), patch( - "homeassistant.components.recorder.migration._modify_columns" - ) as modify_columns_mock: + with ( + patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine + ), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", + return_value={"states.double precision"}, + ), + patch( + "homeassistant.components.recorder.migration._modify_columns" + ) as modify_columns_mock, + ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -56,11 +60,12 @@ async def test_validate_db_schema_fix_utf8_issue_states( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", - return_value={"states.4-byte UTF-8"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", + return_value={"states.4-byte UTF-8"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -85,11 +90,12 @@ async def test_validate_db_schema_fix_utf8_issue_state_attributes( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", - return_value={"state_attributes.4-byte UTF-8"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", + return_value={"state_attributes.4-byte UTF-8"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -115,11 +121,12 @@ async def test_validate_db_schema_fix_collation_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_has_correct_collation", - return_value={"states.utf8mb4_unicode_ci"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_has_correct_collation", + return_value={"states.utf8mb4_unicode_ci"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) diff --git a/tests/components/recorder/auto_repairs/statistics/test_duplicates.py b/tests/components/recorder/auto_repairs/statistics/test_duplicates.py index 5dea61f0795..2a1c3c5d209 100644 --- a/tests/components/recorder/auto_repairs/statistics/test_duplicates.py +++ b/tests/components/recorder/auto_repairs/statistics/test_duplicates.py @@ -78,11 +78,12 @@ def test_duplicate_statistics_handle_integrity_error( } ] - with patch.object( - statistics, "_statistics_exists", return_value=False - ), patch.object( - statistics, "_insert_statistics", wraps=statistics._insert_statistics - ) as insert_statistics_mock: + with ( + patch.object(statistics, "_statistics_exists", return_value=False), + patch.object( + statistics, "_insert_statistics", wraps=statistics._insert_statistics + ) as insert_statistics_mock, + ): async_add_external_statistics( hass, external_energy_metadata_1, external_energy_statistics_1 ) @@ -164,11 +165,17 @@ def test_delete_metadata_duplicates( } # Create some duplicated statistics_meta with schema version 28 - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.core.create_engine", new=_create_engine_28 - ), get_test_home_assistant() as hass: + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch( + "homeassistant.components.recorder.core.create_engine", + new=_create_engine_28, + ), + get_test_home_assistant() as hass, + ): recorder_helper.async_initialize_recorder(hass) setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) wait_recording_done(hass) @@ -256,11 +263,17 @@ def test_delete_metadata_duplicates_many( } # Create some duplicated statistics with schema version 28 - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch( - "homeassistant.components.recorder.core.create_engine", new=_create_engine_28 - ), get_test_home_assistant() as hass: + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch( + "homeassistant.components.recorder.core.create_engine", + new=_create_engine_28, + ), + get_test_home_assistant() as hass, + ): recorder_helper.async_initialize_recorder(hass) setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) wait_recording_done(hass) diff --git a/tests/components/recorder/auto_repairs/statistics/test_schema.py b/tests/components/recorder/auto_repairs/statistics/test_schema.py index c5abb013d99..0badceee0d2 100644 --- a/tests/components/recorder/auto_repairs/statistics/test_schema.py +++ b/tests/components/recorder/auto_repairs/statistics/test_schema.py @@ -21,11 +21,12 @@ async def test_validate_db_schema_fix_utf8_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", - return_value={"statistics_meta.4-byte UTF-8"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_supports_utf8", + return_value={"statistics_meta.4-byte UTF-8"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -55,14 +56,18 @@ async def test_validate_db_schema_fix_float_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", - return_value={f"{table}.double precision"}, - ), patch( - "homeassistant.components.recorder.migration._modify_columns" - ) as modify_columns_mock: + with ( + patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", db_engine + ), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_db_schema_precision", + return_value={f"{table}.double precision"}, + ), + patch( + "homeassistant.components.recorder.migration._modify_columns" + ) as modify_columns_mock, + ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) @@ -94,11 +99,12 @@ async def test_validate_db_schema_fix_collation_issue( Note: The test uses SQLite, the purpose is only to exercise the code. """ - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", "mysql" - ), patch( - "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_has_correct_collation", - return_value={"statistics.utf8mb4_unicode_ci"}, + with ( + patch("homeassistant.components.recorder.core.Recorder.dialect_name", "mysql"), + patch( + "homeassistant.components.recorder.auto_repairs.schema._validate_table_schema_has_correct_collation", + return_value={"statistics.utf8mb4_unicode_ci"}, + ), ): await async_setup_recorder_instance(hass) await async_wait_recording_done(hass) diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index 816378c2f2e..1c44511678e 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -415,19 +415,24 @@ def old_db_schema(schema_version_postfix: str) -> Iterator[None]: importlib.import_module(schema_module) old_db_schema = sys.modules[schema_module] - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch.object(core, "StatesMeta", old_db_schema.StatesMeta), patch.object( - core, "EventTypes", old_db_schema.EventTypes - ), patch.object(core, "EventData", old_db_schema.EventData), patch.object( - core, "States", old_db_schema.States - ), patch.object(core, "Events", old_db_schema.Events), patch.object( - core, "StateAttributes", old_db_schema.StateAttributes - ), patch.object(migration.EntityIDMigration, "task", core.RecorderTask), patch( - CREATE_ENGINE_TARGET, - new=partial( - create_engine_test_for_schema_version_postfix, - schema_version_postfix=schema_version_postfix, + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch.object(core, "StatesMeta", old_db_schema.StatesMeta), + patch.object(core, "EventTypes", old_db_schema.EventTypes), + patch.object(core, "EventData", old_db_schema.EventData), + patch.object(core, "States", old_db_schema.States), + patch.object(core, "Events", old_db_schema.Events), + patch.object(core, "StateAttributes", old_db_schema.StateAttributes), + patch.object(migration.EntityIDMigration, "task", core.RecorderTask), + patch( + CREATE_ENGINE_TARGET, + new=partial( + create_engine_test_for_schema_version_postfix, + schema_version_postfix=schema_version_postfix, + ), ), ): yield diff --git a/tests/components/recorder/db_schema_22.py b/tests/components/recorder/db_schema_22.py index 329e5d262bc..0d336c96403 100644 --- a/tests/components/recorder/db_schema_22.py +++ b/tests/components/recorder/db_schema_22.py @@ -439,13 +439,11 @@ class StatisticsRuns(Base): # type: ignore @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: @@ -459,13 +457,11 @@ def process_timestamp(ts: datetime | None) -> datetime | None: @overload -def process_timestamp_to_utc_isoformat(ts: None) -> None: - ... +def process_timestamp_to_utc_isoformat(ts: None) -> None: ... @overload -def process_timestamp_to_utc_isoformat(ts: datetime) -> str: - ... +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: ... def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: diff --git a/tests/components/recorder/db_schema_23.py b/tests/components/recorder/db_schema_23.py index c145767a838..d4b6e8b0a73 100644 --- a/tests/components/recorder/db_schema_23.py +++ b/tests/components/recorder/db_schema_23.py @@ -429,13 +429,11 @@ class StatisticsRuns(Base): # type: ignore @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: @@ -449,13 +447,11 @@ def process_timestamp(ts: datetime | None) -> datetime | None: @overload -def process_timestamp_to_utc_isoformat(ts: None) -> None: - ... +def process_timestamp_to_utc_isoformat(ts: None) -> None: ... @overload -def process_timestamp_to_utc_isoformat(ts: datetime) -> str: - ... +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: ... def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: diff --git a/tests/components/recorder/db_schema_23_with_newer_columns.py b/tests/components/recorder/db_schema_23_with_newer_columns.py index 92c3f7a75ff..6893a7257f4 100644 --- a/tests/components/recorder/db_schema_23_with_newer_columns.py +++ b/tests/components/recorder/db_schema_23_with_newer_columns.py @@ -553,13 +553,11 @@ class StatisticsRuns(Base): # type: ignore @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: @@ -573,13 +571,11 @@ def process_timestamp(ts: datetime | None) -> datetime | None: @overload -def process_timestamp_to_utc_isoformat(ts: None) -> None: - ... +def process_timestamp_to_utc_isoformat(ts: None) -> None: ... @overload -def process_timestamp_to_utc_isoformat(ts: datetime) -> str: - ... +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: ... def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: diff --git a/tests/components/recorder/db_schema_25.py b/tests/components/recorder/db_schema_25.py index 84c06a95d2e..0d763f91b67 100644 --- a/tests/components/recorder/db_schema_25.py +++ b/tests/components/recorder/db_schema_25.py @@ -489,13 +489,11 @@ class StatisticsRuns(Base): # type: ignore[misc,valid-type] @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: @@ -509,13 +507,11 @@ def process_timestamp(ts: datetime | None) -> datetime | None: @overload -def process_timestamp_to_utc_isoformat(ts: None) -> None: - ... +def process_timestamp_to_utc_isoformat(ts: None) -> None: ... @overload -def process_timestamp_to_utc_isoformat(ts: datetime) -> str: - ... +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: ... def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: diff --git a/tests/components/recorder/db_schema_28.py b/tests/components/recorder/db_schema_28.py index 3593ad37e59..feaf877b36f 100644 --- a/tests/components/recorder/db_schema_28.py +++ b/tests/components/recorder/db_schema_28.py @@ -654,13 +654,11 @@ class StatisticsRuns(Base): # type: ignore[misc,valid-type] @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: @@ -674,13 +672,11 @@ def process_timestamp(ts: datetime | None) -> datetime | None: @overload -def process_timestamp_to_utc_isoformat(ts: None) -> None: - ... +def process_timestamp_to_utc_isoformat(ts: None) -> None: ... @overload -def process_timestamp_to_utc_isoformat(ts: datetime) -> str: - ... +def process_timestamp_to_utc_isoformat(ts: datetime) -> str: ... def process_timestamp_to_utc_isoformat(ts: datetime | None) -> str | None: diff --git a/tests/components/recorder/db_schema_30.py b/tests/components/recorder/db_schema_30.py index facb61a5942..0fba51d588e 100644 --- a/tests/components/recorder/db_schema_30.py +++ b/tests/components/recorder/db_schema_30.py @@ -738,13 +738,11 @@ OLD_STATE = aliased(States, name="old_state") @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: diff --git a/tests/components/recorder/db_schema_32.py b/tests/components/recorder/db_schema_32.py index bd245f9013f..7def9867373 100644 --- a/tests/components/recorder/db_schema_32.py +++ b/tests/components/recorder/db_schema_32.py @@ -739,13 +739,11 @@ OLD_STATE = aliased(States, name="old_state") @overload -def process_timestamp(ts: None) -> None: - ... +def process_timestamp(ts: None) -> None: ... @overload -def process_timestamp(ts: datetime) -> datetime: - ... +def process_timestamp(ts: datetime) -> datetime: ... def process_timestamp(ts: datetime | None) -> datetime | None: diff --git a/tests/components/recorder/table_managers/test_statistics_meta.py b/tests/components/recorder/table_managers/test_statistics_meta.py index a1dc777d80d..66edb84c3ef 100644 --- a/tests/components/recorder/table_managers/test_statistics_meta.py +++ b/tests/components/recorder/table_managers/test_statistics_meta.py @@ -45,8 +45,11 @@ async def test_unsafe_calls_to_statistics_meta_manager( instance = await async_setup_recorder_instance( hass, {recorder.CONF_COMMIT_INTERVAL: 0} ) - with session_scope(session=instance.get_session()) as session, pytest.raises( - RuntimeError, match="Detected unsafe call not in recorder thread" + with ( + session_scope(session=instance.get_session()) as session, + pytest.raises( + RuntimeError, match="Detected unsafe call not in recorder thread" + ), ): instance.statistics_meta_manager.delete( session, diff --git a/tests/components/recorder/test_backup.py b/tests/components/recorder/test_backup.py index 4953504786a..d181c449bbf 100644 --- a/tests/components/recorder/test_backup.py +++ b/tests/components/recorder/test_backup.py @@ -23,10 +23,13 @@ async def test_async_pre_backup_with_timeout( recorder_mock: Recorder, hass: HomeAssistant ) -> None: """Test pre backup with timeout.""" - with patch( - "homeassistant.components.recorder.core.Recorder.lock_database", - side_effect=TimeoutError(), - ) as lock_mock, pytest.raises(TimeoutError): + with ( + patch( + "homeassistant.components.recorder.core.Recorder.lock_database", + side_effect=TimeoutError(), + ) as lock_mock, + pytest.raises(TimeoutError), + ): await async_pre_backup(hass) assert lock_mock.called @@ -35,10 +38,13 @@ async def test_async_pre_backup_with_migration( recorder_mock: Recorder, hass: HomeAssistant ) -> None: """Test pre backup with migration.""" - with patch( - "homeassistant.components.recorder.backup.async_migration_in_progress", - return_value=True, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.recorder.backup.async_migration_in_progress", + return_value=True, + ), + pytest.raises(HomeAssistantError), + ): await async_pre_backup(hass) @@ -55,9 +61,12 @@ async def test_async_post_backup_failure( recorder_mock: Recorder, hass: HomeAssistant ) -> None: """Test post backup failure.""" - with patch( - "homeassistant.components.recorder.core.Recorder.unlock_database", - return_value=False, - ) as unlock_mock, pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.recorder.core.Recorder.unlock_database", + return_value=False, + ) as unlock_mock, + pytest.raises(HomeAssistantError), + ): await async_post_backup(hass) assert unlock_mock.called diff --git a/tests/components/recorder/test_history_db_schema_30.py b/tests/components/recorder/test_history_db_schema_30.py index ed4418deb22..cbe4c3ac5c8 100644 --- a/tests/components/recorder/test_history_db_schema_30.py +++ b/tests/components/recorder/test_history_db_schema_30.py @@ -45,8 +45,9 @@ def test_get_full_significant_states_with_session_entity_no_matches( now = dt_util.utcnow() time_before_recorder_ran = now - timedelta(days=1000) instance = recorder.get_instance(hass) - with session_scope(hass=hass) as session, patch.object( - instance.states_meta_manager, "active", False + with ( + session_scope(hass=hass) as session, + patch.object(instance.states_meta_manager, "active", False), ): assert ( history.get_full_significant_states_with_session( @@ -74,8 +75,9 @@ def test_significant_states_with_session_entity_minimal_response_no_matches( now = dt_util.utcnow() time_before_recorder_ran = now - timedelta(days=1000) instance = recorder.get_instance(hass) - with session_scope(hass=hass) as session, patch.object( - instance.states_meta_manager, "active", False + with ( + session_scope(hass=hass) as session, + patch.object(instance.states_meta_manager, "active", False), ): assert ( history.get_significant_states_with_session( diff --git a/tests/components/recorder/test_history_db_schema_32.py b/tests/components/recorder/test_history_db_schema_32.py index 70f8c636d0e..b926aa1903b 100644 --- a/tests/components/recorder/test_history_db_schema_32.py +++ b/tests/components/recorder/test_history_db_schema_32.py @@ -45,8 +45,9 @@ def test_get_full_significant_states_with_session_entity_no_matches( now = dt_util.utcnow() time_before_recorder_ran = now - timedelta(days=1000) instance = recorder.get_instance(hass) - with session_scope(hass=hass) as session, patch.object( - instance.states_meta_manager, "active", False + with ( + session_scope(hass=hass) as session, + patch.object(instance.states_meta_manager, "active", False), ): assert ( history.get_full_significant_states_with_session( @@ -74,8 +75,9 @@ def test_significant_states_with_session_entity_minimal_response_no_matches( now = dt_util.utcnow() time_before_recorder_ran = now - timedelta(days=1000) instance = recorder.get_instance(hass) - with session_scope(hass=hass) as session, patch.object( - instance.states_meta_manager, "active", False + with ( + session_scope(hass=hass) as session, + patch.object(instance.states_meta_manager, "active", False), ): assert ( history.get_significant_states_with_session( diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 3154ebf7ffc..cde2da3cc83 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -102,8 +102,9 @@ from tests.typing import RecorderInstanceGenerator @pytest.fixture def small_cache_size() -> None: """Patch the default cache size to 8.""" - with patch.object(state_attributes_table_manager, "CACHE_SIZE", 8), patch.object( - states_meta_table_manager, "CACHE_SIZE", 8 + with ( + patch.object(state_attributes_table_manager, "CACHE_SIZE", 8), + patch.object(states_meta_table_manager, "CACHE_SIZE", 8), ): yield @@ -326,8 +327,9 @@ async def test_saving_many_states( entity_id = "test.recorder" attributes = {"test_attr": 5, "test_attr_10": "nice"} - with patch.object(instance.event_session, "expire_all") as expire_all, patch.object( - recorder.core, "EXPIRE_AFTER_COMMITS", 2 + with ( + patch.object(instance.event_session, "expire_all") as expire_all, + patch.object(recorder.core, "EXPIRE_AFTER_COMMITS", 2), ): for _ in range(3): hass.states.async_set(entity_id, "on", attributes) @@ -386,10 +388,13 @@ def test_saving_state_with_exception( "insert the state", "fake params", "forced to fail" ) - with patch("time.sleep"), patch.object( - get_instance(hass).event_session, - "flush", - side_effect=_throw_if_state_in_session, + with ( + patch("time.sleep"), + patch.object( + get_instance(hass).event_session, + "flush", + side_effect=_throw_if_state_in_session, + ), ): hass.states.set(entity_id, "fail", attributes) wait_recording_done(hass) @@ -428,10 +433,13 @@ def test_saving_state_with_sqlalchemy_exception( "insert the state", "fake params", "forced to fail" ) - with patch("time.sleep"), patch.object( - get_instance(hass).event_session, - "flush", - side_effect=_throw_if_state_in_session, + with ( + patch("time.sleep"), + patch.object( + get_instance(hass).event_session, + "flush", + side_effect=_throw_if_state_in_session, + ), ): hass.states.set(entity_id, "fail", attributes) wait_recording_done(hass) @@ -464,11 +472,14 @@ async def test_force_shutdown_with_queue_of_writes_that_generate_exceptions( await async_wait_recording_done(hass) - with patch.object(instance, "db_retry_wait", 0.01), patch.object( - instance.event_session, - "flush", - side_effect=OperationalError( - "insert the state", "fake params", "forced to fail" + with ( + patch.object(instance, "db_retry_wait", 0.01), + patch.object( + instance.event_session, + "flush", + side_effect=OperationalError( + "insert the state", "fake params", "forced to fail" + ), ), ): for _ in range(100): @@ -898,8 +909,9 @@ def test_saving_event_invalid_context_ulid( def test_recorder_setup_failure(hass: HomeAssistant) -> None: """Test some exceptions.""" recorder_helper.async_initialize_recorder(hass) - with patch.object(Recorder, "_setup_connection") as setup, patch( - "homeassistant.components.recorder.core.time.sleep" + with ( + patch.object(Recorder, "_setup_connection") as setup, + patch("homeassistant.components.recorder.core.time.sleep"), ): setup.side_effect = ImportError("driver not found") rec = _default_recorder(hass) @@ -913,10 +925,11 @@ def test_recorder_setup_failure(hass: HomeAssistant) -> None: def test_recorder_validate_schema_failure(hass: HomeAssistant) -> None: """Test some exceptions.""" recorder_helper.async_initialize_recorder(hass) - with patch( - "homeassistant.components.recorder.migration._get_schema_version" - ) as inspect_schema_version, patch( - "homeassistant.components.recorder.core.time.sleep" + with ( + patch( + "homeassistant.components.recorder.migration._get_schema_version" + ) as inspect_schema_version, + patch("homeassistant.components.recorder.core.time.sleep"), ): inspect_schema_version.side_effect = ImportError("driver not found") rec = _default_recorder(hass) @@ -930,8 +943,9 @@ def test_recorder_validate_schema_failure(hass: HomeAssistant) -> None: def test_recorder_setup_failure_without_event_listener(hass: HomeAssistant) -> None: """Test recorder setup failure when the event listener is not setup.""" recorder_helper.async_initialize_recorder(hass) - with patch.object(Recorder, "_setup_connection") as setup, patch( - "homeassistant.components.recorder.core.time.sleep" + with ( + patch.object(Recorder, "_setup_connection") as setup, + patch("homeassistant.components.recorder.core.time.sleep"), ): setup.side_effect = ImportError("driver not found") rec = _default_recorder(hass) @@ -987,11 +1001,14 @@ def test_auto_purge(hass_recorder: Callable[..., HomeAssistant]) -> None: test_time = datetime(now.year + 2, 1, 1, 4, 15, 0, tzinfo=tz) run_tasks_at_time(hass, test_time) - with patch( - "homeassistant.components.recorder.purge.purge_old_data", return_value=True - ) as purge_old_data, patch( - "homeassistant.components.recorder.tasks.periodic_db_cleanups" - ) as periodic_db_cleanups: + with ( + patch( + "homeassistant.components.recorder.purge.purge_old_data", return_value=True + ) as purge_old_data, + patch( + "homeassistant.components.recorder.tasks.periodic_db_cleanups" + ) as periodic_db_cleanups, + ): # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) run_tasks_at_time(hass, test_time) @@ -1047,13 +1064,17 @@ def test_auto_purge_auto_repack_on_second_sunday( test_time = datetime(now.year + 2, 1, 1, 4, 15, 0, tzinfo=tz) run_tasks_at_time(hass, test_time) - with patch( - "homeassistant.components.recorder.core.is_second_sunday", return_value=True - ), patch( - "homeassistant.components.recorder.purge.purge_old_data", return_value=True - ) as purge_old_data, patch( - "homeassistant.components.recorder.tasks.periodic_db_cleanups" - ) as periodic_db_cleanups: + with ( + patch( + "homeassistant.components.recorder.core.is_second_sunday", return_value=True + ), + patch( + "homeassistant.components.recorder.purge.purge_old_data", return_value=True + ) as purge_old_data, + patch( + "homeassistant.components.recorder.tasks.periodic_db_cleanups" + ) as periodic_db_cleanups, + ): # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) run_tasks_at_time(hass, test_time) @@ -1087,13 +1108,17 @@ def test_auto_purge_auto_repack_disabled_on_second_sunday( test_time = datetime(now.year + 2, 1, 1, 4, 15, 0, tzinfo=tz) run_tasks_at_time(hass, test_time) - with patch( - "homeassistant.components.recorder.core.is_second_sunday", return_value=True - ), patch( - "homeassistant.components.recorder.purge.purge_old_data", return_value=True - ) as purge_old_data, patch( - "homeassistant.components.recorder.tasks.periodic_db_cleanups" - ) as periodic_db_cleanups: + with ( + patch( + "homeassistant.components.recorder.core.is_second_sunday", return_value=True + ), + patch( + "homeassistant.components.recorder.purge.purge_old_data", return_value=True + ) as purge_old_data, + patch( + "homeassistant.components.recorder.tasks.periodic_db_cleanups" + ) as periodic_db_cleanups, + ): # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) run_tasks_at_time(hass, test_time) @@ -1127,14 +1152,18 @@ def test_auto_purge_no_auto_repack_on_not_second_sunday( test_time = datetime(now.year + 2, 1, 1, 4, 15, 0, tzinfo=tz) run_tasks_at_time(hass, test_time) - with patch( - "homeassistant.components.recorder.core.is_second_sunday", - return_value=False, - ), patch( - "homeassistant.components.recorder.purge.purge_old_data", return_value=True - ) as purge_old_data, patch( - "homeassistant.components.recorder.tasks.periodic_db_cleanups" - ) as periodic_db_cleanups: + with ( + patch( + "homeassistant.components.recorder.core.is_second_sunday", + return_value=False, + ), + patch( + "homeassistant.components.recorder.purge.purge_old_data", return_value=True + ) as purge_old_data, + patch( + "homeassistant.components.recorder.tasks.periodic_db_cleanups" + ) as periodic_db_cleanups, + ): # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) run_tasks_at_time(hass, test_time) @@ -1165,11 +1194,14 @@ def test_auto_purge_disabled(hass_recorder: Callable[..., HomeAssistant]) -> Non test_time = datetime(now.year + 2, 1, 1, 4, 15, 0, tzinfo=tz) run_tasks_at_time(hass, test_time) - with patch( - "homeassistant.components.recorder.purge.purge_old_data", return_value=True - ) as purge_old_data, patch( - "homeassistant.components.recorder.tasks.periodic_db_cleanups" - ) as periodic_db_cleanups: + with ( + patch( + "homeassistant.components.recorder.purge.purge_old_data", return_value=True + ) as purge_old_data, + patch( + "homeassistant.components.recorder.tasks.periodic_db_cleanups" + ) as periodic_db_cleanups, + ): # Advance one day, and the purge task should run test_time = test_time + timedelta(days=1) run_tasks_at_time(hass, test_time) @@ -1810,9 +1842,11 @@ async def test_database_lock_and_overflow( ) ) - with patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), patch.object( - recorder.core, "DB_LOCK_QUEUE_CHECK_TIMEOUT", 0.01 - ), patch.object(recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 0): + with ( + patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), + patch.object(recorder.core, "DB_LOCK_QUEUE_CHECK_TIMEOUT", 0.01), + patch.object(recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 0), + ): await async_setup_recorder_instance(hass, config) await hass.async_block_till_done() event_type = "EVENT_TEST" @@ -1879,12 +1913,15 @@ async def test_database_lock_and_overflow_checks_available_memory( event_types = (event_type,) await async_wait_recording_done(hass) - with patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), patch.object( - recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 1 - ), patch.object(recorder.core, "DB_LOCK_QUEUE_CHECK_TIMEOUT", 0.01), patch.object( - recorder.core.Recorder, - "_available_memory", - return_value=recorder.core.ESTIMATED_QUEUE_ITEM_SIZE * 4, + with ( + patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), + patch.object(recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 1), + patch.object(recorder.core, "DB_LOCK_QUEUE_CHECK_TIMEOUT", 0.01), + patch.object( + recorder.core.Recorder, + "_available_memory", + return_value=recorder.core.ESTIMATED_QUEUE_ITEM_SIZE * 4, + ), ): instance = get_instance(hass) @@ -2136,10 +2173,11 @@ async def test_disable_echo( callback(None, None) mock_event = MockEvent() - with patch( - "homeassistant.components.recorder.core.create_engine" - ) as create_engine_mock, patch( - "homeassistant.components.recorder.core.sqlalchemy_event", mock_event + with ( + patch( + "homeassistant.components.recorder.core.create_engine" + ) as create_engine_mock, + patch("homeassistant.components.recorder.core.sqlalchemy_event", mock_event), ): await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: db_url}}) create_engine_mock.assert_called_once() @@ -2194,10 +2232,11 @@ async def test_mysql_missing_utf8mb4( callback(None, None) mock_event = MockEvent() - with patch( - "homeassistant.components.recorder.core.create_engine" - ) as create_engine_mock, patch( - "homeassistant.components.recorder.core.sqlalchemy_event", mock_event + with ( + patch( + "homeassistant.components.recorder.core.create_engine" + ) as create_engine_mock, + patch("homeassistant.components.recorder.core.sqlalchemy_event", mock_event), ): await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_DB_URL: config_url}}) create_engine_mock.assert_called_once() @@ -2226,8 +2265,7 @@ async def test_connect_args_priority(hass: HomeAssistant, config_url) -> None: __bases__ = [] _has_events = False - def __init__(*args, **kwargs): - ... + def __init__(*args, **kwargs): ... @property def is_async(self): @@ -2246,33 +2284,29 @@ async def test_connect_args_priority(hass: HomeAssistant, config_url) -> None: return "mysql" @classmethod - def import_dbapi(cls): - ... + def import_dbapi(cls): ... - def engine_created(*args): - ... + def engine_created(*args): ... def get_dialect_pool_class(self, *args): return pool.RecorderPool - def initialize(*args): - ... + def initialize(*args): ... def on_connect_url(self, url): return False - def _builtin_onconnect(self): - ... + def _builtin_onconnect(self): ... class MockEntrypoint: - def engine_created(*_): - ... + def engine_created(*_): ... def get_dialect_cls(*_): return MockDialect - with patch("sqlalchemy.engine.url.URL._get_entrypoint", MockEntrypoint), patch( - "sqlalchemy.engine.create.util.get_cls_kwargs", return_value=["echo"] + with ( + patch("sqlalchemy.engine.url.URL._get_entrypoint", MockEntrypoint), + patch("sqlalchemy.engine.create.util.get_cls_kwargs", return_value=["echo"]), ): await async_setup_component( hass, @@ -2363,8 +2397,9 @@ async def test_clean_shutdown_when_recorder_thread_raises_during_initialize_data hass: HomeAssistant, ) -> None: """Test we still shutdown cleanly when the recorder thread raises during initialize_database.""" - with patch.object(migration, "initialize_database", side_effect=Exception), patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True + with ( + patch.object(migration, "initialize_database", side_effect=Exception), + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), ): if recorder.DOMAIN not in hass.data: recorder_helper.async_initialize_recorder(hass) @@ -2390,8 +2425,9 @@ async def test_clean_shutdown_when_recorder_thread_raises_during_validate_db_sch hass: HomeAssistant, ) -> None: """Test we still shutdown cleanly when the recorder thread raises during validate_db_schema.""" - with patch.object(migration, "validate_db_schema", side_effect=Exception), patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True + with ( + patch.object(migration, "validate_db_schema", side_effect=Exception), + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), ): if recorder.DOMAIN not in hass.data: recorder_helper.async_initialize_recorder(hass) @@ -2415,16 +2451,18 @@ async def test_clean_shutdown_when_recorder_thread_raises_during_validate_db_sch async def test_clean_shutdown_when_schema_migration_fails(hass: HomeAssistant) -> None: """Test we still shutdown cleanly when schema migration fails.""" - with patch.object( - migration, - "validate_db_schema", - return_value=MagicMock(valid=False, current_version=1), - ), patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True - ), patch.object( - migration, - "migrate_schema", - side_effect=Exception, + with ( + patch.object( + migration, + "validate_db_schema", + return_value=MagicMock(valid=False, current_version=1), + ), + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch.object( + migration, + "migrate_schema", + side_effect=Exception, + ), ): if recorder.DOMAIN not in hass.data: recorder_helper.async_initialize_recorder(hass) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index 141ffc31cc0..01d5912a683 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -52,13 +52,17 @@ async def test_schema_update_calls(recorder_db_url: str, hass: HomeAssistant) -> """Test that schema migrations occur in correct order.""" assert recorder.util.async_migration_in_progress(hass) is False - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.core.create_engine", - new=create_engine_test, - ), patch( - "homeassistant.components.recorder.migration._apply_update", - wraps=migration._apply_update, - ) as update: + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), + patch( + "homeassistant.components.recorder.migration._apply_update", + wraps=migration._apply_update, + ) as update, + ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( hass, "recorder", {"recorder": {"db_url": recorder_db_url}} @@ -89,9 +93,12 @@ async def test_migration_in_progress(recorder_db_url: str, hass: HomeAssistant) assert recorder.util.async_migration_in_progress(hass) is False - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.core.create_engine", - new=create_engine_test, + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( @@ -111,18 +118,25 @@ async def test_database_migration_failed( """Test we notify if the migration fails.""" assert recorder.util.async_migration_in_progress(hass) is False - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.core.create_engine", - new=create_engine_test, - ), patch( - "homeassistant.components.recorder.migration._apply_update", - side_effect=ValueError, - ), patch( - "homeassistant.components.persistent_notification.create", side_effect=pn.create - ) as mock_create, patch( - "homeassistant.components.persistent_notification.dismiss", - side_effect=pn.dismiss, - ) as mock_dismiss: + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), + patch( + "homeassistant.components.recorder.migration._apply_update", + side_effect=ValueError, + ), + patch( + "homeassistant.components.persistent_notification.create", + side_effect=pn.create, + ) as mock_create, + patch( + "homeassistant.components.persistent_notification.dismiss", + side_effect=pn.dismiss, + ) as mock_dismiss, + ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( hass, "recorder", {"recorder": {"db_url": recorder_db_url}} @@ -152,16 +166,22 @@ async def test_database_migration_encounters_corruption( sqlite3_exception = DatabaseError("statement", {}, []) sqlite3_exception.__cause__ = sqlite3.DatabaseError() - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.migration._schema_is_current", - side_effect=[False], - ), patch( - "homeassistant.components.recorder.migration.migrate_schema", - side_effect=sqlite3_exception, - ), patch( - "homeassistant.components.recorder.core.move_away_broken_database" - ) as move_away, patch( - "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.migration._schema_is_current", + side_effect=[False], + ), + patch( + "homeassistant.components.recorder.migration.migrate_schema", + side_effect=sqlite3_exception, + ), + patch( + "homeassistant.components.recorder.core.move_away_broken_database" + ) as move_away, + patch( + "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", + ), ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( @@ -181,20 +201,28 @@ async def test_database_migration_encounters_corruption_not_sqlite( """Test we fail on database error when we cannot recover.""" assert recorder.util.async_migration_in_progress(hass) is False - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.migration._schema_is_current", - side_effect=[False], - ), patch( - "homeassistant.components.recorder.migration.migrate_schema", - side_effect=DatabaseError("statement", {}, []), - ), patch( - "homeassistant.components.recorder.core.move_away_broken_database" - ) as move_away, patch( - "homeassistant.components.persistent_notification.create", side_effect=pn.create - ) as mock_create, patch( - "homeassistant.components.persistent_notification.dismiss", - side_effect=pn.dismiss, - ) as mock_dismiss: + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.migration._schema_is_current", + side_effect=[False], + ), + patch( + "homeassistant.components.recorder.migration.migrate_schema", + side_effect=DatabaseError("statement", {}, []), + ), + patch( + "homeassistant.components.recorder.core.move_away_broken_database" + ) as move_away, + patch( + "homeassistant.components.persistent_notification.create", + side_effect=pn.create, + ) as mock_create, + patch( + "homeassistant.components.persistent_notification.dismiss", + side_effect=pn.dismiss, + ) as mock_dismiss, + ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( hass, "recorder", {"recorder": {"db_url": recorder_db_url}} @@ -218,12 +246,15 @@ async def test_events_during_migration_are_queued( assert recorder.util.async_migration_in_progress(hass) is False - with patch( - "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", - True, - ), patch( - "homeassistant.components.recorder.core.create_engine", - new=create_engine_test, + with ( + patch( + "homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", + True, + ), + patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( @@ -254,11 +285,14 @@ async def test_events_during_migration_queue_exhausted( assert recorder.util.async_migration_in_progress(hass) is False - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.core.create_engine", - new=create_engine_test, - ), patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), patch.object( - recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 0 + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), + patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), + patch.object(recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 0), ): recorder_helper.async_initialize_recorder(hass) await async_setup_component( @@ -372,32 +406,42 @@ async def test_schema_migrate( raise mysql_exception real_create_index(*args) - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.core.create_engine", - new=_create_engine_test, - ), patch( - "homeassistant.components.recorder.Recorder._setup_run", - side_effect=_mock_setup_run, - autospec=True, - ) as setup_run, patch( - "homeassistant.components.recorder.migration.migrate_schema", - wraps=_instrument_migrate_schema, - ), patch( - "homeassistant.components.recorder.migration._apply_update", - wraps=_instrument_apply_update, - ) as apply_update_mock, patch( - "homeassistant.components.recorder.util.time.sleep" - ), patch( - "homeassistant.components.recorder.migration._create_index", - wraps=_sometimes_failing_create_index, - ), patch( - "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", - ), patch( - "homeassistant.components.recorder.Recorder._process_state_changed_event_into_session", - ), patch( - "homeassistant.components.recorder.Recorder._process_non_state_changed_event_into_session", - ), patch( - "homeassistant.components.recorder.Recorder._pre_process_startup_events", + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch( + "homeassistant.components.recorder.core.create_engine", + new=_create_engine_test, + ), + patch( + "homeassistant.components.recorder.Recorder._setup_run", + side_effect=_mock_setup_run, + autospec=True, + ) as setup_run, + patch( + "homeassistant.components.recorder.migration.migrate_schema", + wraps=_instrument_migrate_schema, + ), + patch( + "homeassistant.components.recorder.migration._apply_update", + wraps=_instrument_apply_update, + ) as apply_update_mock, + patch("homeassistant.components.recorder.util.time.sleep"), + patch( + "homeassistant.components.recorder.migration._create_index", + wraps=_sometimes_failing_create_index, + ), + patch( + "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", + ), + patch( + "homeassistant.components.recorder.Recorder._process_state_changed_event_into_session", + ), + patch( + "homeassistant.components.recorder.Recorder._process_non_state_changed_event_into_session", + ), + patch( + "homeassistant.components.recorder.Recorder._pre_process_startup_events", + ), ): recorder_helper.async_initialize_recorder(hass) hass.async_create_task( @@ -497,11 +541,14 @@ def test_forgiving_drop_index( instance.get_session, "states", "ix_states_context_id_bin" ) - with patch( - "homeassistant.components.recorder.migration.get_index_by_name", - return_value="ix_states_context_id_bin", - ), patch.object( - session, "connection", side_effect=SQLAlchemyError("connection failure") + with ( + patch( + "homeassistant.components.recorder.migration.get_index_by_name", + return_value="ix_states_context_id_bin", + ), + patch.object( + session, "connection", side_effect=SQLAlchemyError("connection failure") + ), ): migration._drop_index( instance.get_session, "states", "ix_states_context_id_bin" @@ -509,11 +556,14 @@ def test_forgiving_drop_index( assert "Failed to drop index" in caplog.text assert "connection failure" in caplog.text caplog.clear() - with patch( - "homeassistant.components.recorder.migration.get_index_by_name", - return_value="ix_states_context_id_bin", - ), patch.object( - session, "connection", side_effect=SQLAlchemyError("connection failure") + with ( + patch( + "homeassistant.components.recorder.migration.get_index_by_name", + return_value="ix_states_context_id_bin", + ), + patch.object( + session, "connection", side_effect=SQLAlchemyError("connection failure") + ), ): migration._drop_index( instance.get_session, "states", "ix_states_context_id_bin", quiet=True diff --git a/tests/components/recorder/test_migration_from_schema_32.py b/tests/components/recorder/test_migration_from_schema_32.py index e9f51caaee2..646cd338949 100644 --- a/tests/components/recorder/test_migration_from_schema_32.py +++ b/tests/components/recorder/test_migration_from_schema_32.py @@ -92,16 +92,19 @@ def db_schema_32(): importlib.import_module(SCHEMA_MODULE) old_db_schema = sys.modules[SCHEMA_MODULE] - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch.object(core, "StatesMeta", old_db_schema.StatesMeta), patch.object( - core, "EventTypes", old_db_schema.EventTypes - ), patch.object(core, "EventData", old_db_schema.EventData), patch.object( - core, "States", old_db_schema.States - ), patch.object(core, "Events", old_db_schema.Events), patch.object( - core, "StateAttributes", old_db_schema.StateAttributes - ), patch.object(migration.EntityIDMigration, "task", core.RecorderTask), patch( - CREATE_ENGINE_TARGET, new=_create_engine_test + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch.object(core, "StatesMeta", old_db_schema.StatesMeta), + patch.object(core, "EventTypes", old_db_schema.EventTypes), + patch.object(core, "EventData", old_db_schema.EventData), + patch.object(core, "States", old_db_schema.States), + patch.object(core, "Events", old_db_schema.Events), + patch.object(core, "StateAttributes", old_db_schema.StateAttributes), + patch.object(migration.EntityIDMigration, "task", core.RecorderTask), + patch(CREATE_ENGINE_TARGET, new=_create_engine_test), ): yield @@ -1294,14 +1297,17 @@ async def test_stats_timestamp_with_one_by_one_removes_duplicates( one_month_ago = now - datetime.timedelta(days=30) def _do_migration(): - with patch.object( - migration, - "_migrate_statistics_columns_to_timestamp", - side_effect=IntegrityError("test", "test", "test"), - ), patch.object( - migration, - "migrate_single_statistics_row_to_timestamp", - side_effect=IntegrityError("test", "test", "test"), + with ( + patch.object( + migration, + "_migrate_statistics_columns_to_timestamp", + side_effect=IntegrityError("test", "test", "test"), + ), + patch.object( + migration, + "migrate_single_statistics_row_to_timestamp", + side_effect=IntegrityError("test", "test", "test"), + ), ): migration._migrate_statistics_columns_to_timestamp_removing_duplicates( hass, instance, instance.get_session, instance.engine diff --git a/tests/components/recorder/test_migration_run_time_migrations_remember.py b/tests/components/recorder/test_migration_run_time_migrations_remember.py index 770d5d684a9..4f59edb097f 100644 --- a/tests/components/recorder/test_migration_run_time_migrations_remember.py +++ b/tests/components/recorder/test_migration_run_time_migrations_remember.py @@ -89,16 +89,19 @@ async def test_migration_changes_prevent_trying_to_migrate_again( old_db_schema = sys.modules[SCHEMA_MODULE] # Start with db schema that needs migration (version 32) - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch.object(core, "StatesMeta", old_db_schema.StatesMeta), patch.object( - core, "EventTypes", old_db_schema.EventTypes - ), patch.object(core, "EventData", old_db_schema.EventData), patch.object( - core, "States", old_db_schema.States - ), patch.object(core, "Events", old_db_schema.Events), patch.object( - core, "StateAttributes", old_db_schema.StateAttributes - ), patch.object(migration.EntityIDMigration, "task", core.RecorderTask), patch( - CREATE_ENGINE_TARGET, new=_create_engine_test + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch.object(core, "StatesMeta", old_db_schema.StatesMeta), + patch.object(core, "EventTypes", old_db_schema.EventTypes), + patch.object(core, "EventData", old_db_schema.EventData), + patch.object(core, "States", old_db_schema.States), + patch.object(core, "Events", old_db_schema.Events), + patch.object(core, "StateAttributes", old_db_schema.StateAttributes), + patch.object(migration.EntityIDMigration, "task", core.RecorderTask), + patch(CREATE_ENGINE_TARGET, new=_create_engine_test), ): async with async_test_home_assistant() as hass: await async_setup_recorder_instance(hass, config) @@ -136,12 +139,16 @@ async def test_migration_changes_prevent_trying_to_migrate_again( # Finally verify we did not call needs_migrate_query on StatesContextIDMigration async with async_test_home_assistant() as hass: - with patch( - "homeassistant.components.recorder.core.Recorder.queue_task", _queue_task - ), patch.object( - migration.StatesContextIDMigration, - "needs_migrate_query", - side_effect=RuntimeError("Should not be called"), + with ( + patch( + "homeassistant.components.recorder.core.Recorder.queue_task", + _queue_task, + ), + patch.object( + migration.StatesContextIDMigration, + "needs_migrate_query", + side_effect=RuntimeError("Should not be called"), + ), ): await async_setup_recorder_instance(hass, config) await hass.async_block_till_done() diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index a58dd2e91dd..b2da3f1d62f 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -79,9 +79,11 @@ async def test_purge_big_database( await _add_test_states(hass, wait_recording_done=False) await async_wait_recording_done(hass) - with patch.object(instance, "max_bind_vars", 72), patch.object( - instance.database_engine, "max_bind_vars", 72 - ), session_scope(hass=hass) as session: + with ( + patch.object(instance, "max_bind_vars", 72), + patch.object(instance.database_engine, "max_bind_vars", 72), + session_scope(hass=hass) as session, + ): states = session.query(States) state_attributes = session.query(StateAttributes) assert states.count() == 72 @@ -208,11 +210,14 @@ async def test_purge_old_states_encouters_database_corruption( sqlite3_exception = DatabaseError("statement", {}, []) sqlite3_exception.__cause__ = sqlite3.DatabaseError() - with patch( - "homeassistant.components.recorder.core.move_away_broken_database" - ) as move_away, patch( - "homeassistant.components.recorder.purge.purge_old_data", - side_effect=sqlite3_exception, + with ( + patch( + "homeassistant.components.recorder.core.move_away_broken_database" + ) as move_away, + patch( + "homeassistant.components.recorder.purge.purge_old_data", + side_effect=sqlite3_exception, + ), ): await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() @@ -240,12 +245,14 @@ async def test_purge_old_states_encounters_temporary_mysql_error( mysql_exception = OperationalError("statement", {}, []) mysql_exception.orig = Exception(1205, "retryable") - with patch( - "homeassistant.components.recorder.util.time.sleep" - ) as sleep_mock, patch( - "homeassistant.components.recorder.purge._purge_old_recorder_runs", - side_effect=[mysql_exception, None], - ), patch.object(instance.engine.dialect, "name", "mysql"): + with ( + patch("homeassistant.components.recorder.util.time.sleep") as sleep_mock, + patch( + "homeassistant.components.recorder.purge._purge_old_recorder_runs", + side_effect=[mysql_exception, None], + ), + patch.object(instance.engine.dialect, "name", "mysql"), + ): await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() await async_wait_recording_done(hass) @@ -1654,8 +1661,9 @@ async def test_purge_many_old_events( old_events_count = 5 instance = await async_setup_recorder_instance(hass) - with patch.object(instance, "max_bind_vars", old_events_count), patch.object( - instance.database_engine, "max_bind_vars", old_events_count + with ( + patch.object(instance, "max_bind_vars", old_events_count), + patch.object(instance.database_engine, "max_bind_vars", old_events_count), ): await _add_test_events(hass, old_events_count) diff --git a/tests/components/recorder/test_purge_v32_schema.py b/tests/components/recorder/test_purge_v32_schema.py index cc210f2d780..3946d8896f7 100644 --- a/tests/components/recorder/test_purge_v32_schema.py +++ b/tests/components/recorder/test_purge_v32_schema.py @@ -174,11 +174,14 @@ async def test_purge_old_states_encouters_database_corruption( sqlite3_exception = DatabaseError("statement", {}, []) sqlite3_exception.__cause__ = sqlite3.DatabaseError() - with patch( - "homeassistant.components.recorder.core.move_away_broken_database" - ) as move_away, patch( - "homeassistant.components.recorder.purge.purge_old_data", - side_effect=sqlite3_exception, + with ( + patch( + "homeassistant.components.recorder.core.move_away_broken_database" + ) as move_away, + patch( + "homeassistant.components.recorder.purge.purge_old_data", + side_effect=sqlite3_exception, + ), ): await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() @@ -207,12 +210,14 @@ async def test_purge_old_states_encounters_temporary_mysql_error( mysql_exception = OperationalError("statement", {}, []) mysql_exception.orig = Exception(1205, "retryable") - with patch( - "homeassistant.components.recorder.util.time.sleep" - ) as sleep_mock, patch( - "homeassistant.components.recorder.purge._purge_old_recorder_runs", - side_effect=[mysql_exception, None], - ), patch.object(instance.engine.dialect, "name", "mysql"): + with ( + patch("homeassistant.components.recorder.util.time.sleep") as sleep_mock, + patch( + "homeassistant.components.recorder.purge._purge_old_recorder_runs", + side_effect=[mysql_exception, None], + ), + patch.object(instance.engine.dialect, "name", "mysql"), + ): await hass.services.async_call(recorder.DOMAIN, SERVICE_PURGE, {"keep_days": 0}) await hass.async_block_till_done() await async_wait_recording_done(hass) @@ -937,8 +942,9 @@ async def test_purge_many_old_events( await async_attach_db_engine(hass) old_events_count = 5 - with patch.object(instance, "max_bind_vars", old_events_count), patch.object( - instance.database_engine, "max_bind_vars", old_events_count + with ( + patch.object(instance, "max_bind_vars", old_events_count), + patch.object(instance.database_engine, "max_bind_vars", old_events_count), ): await _add_test_events(hass, old_events_count) diff --git a/tests/components/recorder/test_statistics_v23_migration.py b/tests/components/recorder/test_statistics_v23_migration.py index c7b68f29b1c..28c7613e761 100644 --- a/tests/components/recorder/test_statistics_v23_migration.py +++ b/tests/components/recorder/test_statistics_v23_migration.py @@ -160,15 +160,20 @@ def test_delete_duplicates(caplog: pytest.LogCaptureFixture, tmp_path: Path) -> } # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch( - CREATE_ENGINE_TARGET, - new=partial( - create_engine_test_for_schema_version_postfix, - schema_version_postfix=SCHEMA_VERSION_POSTFIX, + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION ), - ), get_test_home_assistant() as hass: + patch( + CREATE_ENGINE_TARGET, + new=partial( + create_engine_test_for_schema_version_postfix, + schema_version_postfix=SCHEMA_VERSION_POSTFIX, + ), + ), + get_test_home_assistant() as hass, + ): recorder_helper.async_initialize_recorder(hass) setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) wait_recording_done(hass) @@ -337,15 +342,20 @@ def test_delete_duplicates_many( } # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch( - CREATE_ENGINE_TARGET, - new=partial( - create_engine_test_for_schema_version_postfix, - schema_version_postfix=SCHEMA_VERSION_POSTFIX, + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION ), - ), get_test_home_assistant() as hass: + patch( + CREATE_ENGINE_TARGET, + new=partial( + create_engine_test_for_schema_version_postfix, + schema_version_postfix=SCHEMA_VERSION_POSTFIX, + ), + ), + get_test_home_assistant() as hass, + ): recorder_helper.async_initialize_recorder(hass) setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) wait_recording_done(hass) @@ -491,15 +501,20 @@ def test_delete_duplicates_non_identical( } # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch( - CREATE_ENGINE_TARGET, - new=partial( - create_engine_test_for_schema_version_postfix, - schema_version_postfix=SCHEMA_VERSION_POSTFIX, + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION ), - ), get_test_home_assistant() as hass: + patch( + CREATE_ENGINE_TARGET, + new=partial( + create_engine_test_for_schema_version_postfix, + schema_version_postfix=SCHEMA_VERSION_POSTFIX, + ), + ), + get_test_home_assistant() as hass, + ): recorder_helper.async_initialize_recorder(hass) setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) wait_recording_done(hass) @@ -600,15 +615,20 @@ def test_delete_duplicates_short_term( } # Create some duplicated statistics with schema version 23 - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch( - CREATE_ENGINE_TARGET, - new=partial( - create_engine_test_for_schema_version_postfix, - schema_version_postfix=SCHEMA_VERSION_POSTFIX, + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION ), - ), get_test_home_assistant() as hass: + patch( + CREATE_ENGINE_TARGET, + new=partial( + create_engine_test_for_schema_version_postfix, + schema_version_postfix=SCHEMA_VERSION_POSTFIX, + ), + ), + get_test_home_assistant() as hass, + ): recorder_helper.async_initialize_recorder(hass) setup_component(hass, "recorder", {"recorder": {"db_url": dburl}}) wait_recording_done(hass) diff --git a/tests/components/recorder/test_system_health.py b/tests/components/recorder/test_system_health.py index 456a9d7de36..ee4217dab69 100644 --- a/tests/components/recorder/test_system_health.py +++ b/tests/components/recorder/test_system_health.py @@ -45,11 +45,14 @@ async def test_recorder_system_health_alternate_dbms( """Test recorder system health.""" assert await async_setup_component(hass, "system_health", {}) await async_wait_recording_done(hass) - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", dialect_name - ), patch( - "sqlalchemy.orm.session.Session.execute", - return_value=Mock(scalar=Mock(return_value=("1048576"))), + with ( + patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", dialect_name + ), + patch( + "sqlalchemy.orm.session.Session.execute", + return_value=Mock(scalar=Mock(return_value=("1048576"))), + ), ): info = await get_system_health_info(hass, "recorder") instance = get_instance(hass) @@ -73,15 +76,19 @@ async def test_recorder_system_health_db_url_missing_host( await async_wait_recording_done(hass) instance = get_instance(hass) - with patch( - "homeassistant.components.recorder.core.Recorder.dialect_name", dialect_name - ), patch.object( - instance, - "db_url", - "postgresql://homeassistant:blabla@/home_assistant?host=/config/socket", - ), patch( - "sqlalchemy.orm.session.Session.execute", - return_value=Mock(scalar=Mock(return_value=("1048576"))), + with ( + patch( + "homeassistant.components.recorder.core.Recorder.dialect_name", dialect_name + ), + patch.object( + instance, + "db_url", + "postgresql://homeassistant:blabla@/home_assistant?host=/config/socket", + ), + patch( + "sqlalchemy.orm.session.Session.execute", + return_value=Mock(scalar=Mock(return_value=("1048576"))), + ), ): info = await get_system_health_info(hass, "recorder") assert info == { diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index c199d980f69..9a5b91fa8f8 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -46,9 +46,11 @@ from tests.typing import RecorderInstanceGenerator def test_session_scope_not_setup(hass_recorder: Callable[..., HomeAssistant]) -> None: """Try to create a session scope when not setup.""" hass = hass_recorder() - with patch.object( - util.get_instance(hass), "get_session", return_value=None - ), pytest.raises(RuntimeError), util.session_scope(hass=hass): + with ( + patch.object(util.get_instance(hass), "get_session", return_value=None), + pytest.raises(RuntimeError), + util.session_scope(hass=hass), + ): pass @@ -65,9 +67,10 @@ def test_recorder_bad_execute(hass_recorder: Callable[..., HomeAssistant]) -> No mck1 = MagicMock() mck1.to_native = to_native - with pytest.raises(SQLAlchemyError), patch( - "homeassistant.components.recorder.core.time.sleep" - ) as e_mock: + with ( + pytest.raises(SQLAlchemyError), + patch("homeassistant.components.recorder.core.time.sleep") as e_mock, + ): util.execute((mck1,), to_native=True) assert e_mock.call_count == 2 @@ -146,12 +149,15 @@ async def test_last_run_was_recently_clean( thirty_min_future_time = dt_util.utcnow() + timedelta(minutes=30) async with async_test_home_assistant() as hass: - with patch( - "homeassistant.components.recorder.util.last_run_was_recently_clean", - wraps=_last_run_was_recently_clean, - ) as last_run_was_recently_clean_mock, patch( - "homeassistant.components.recorder.core.dt_util.utcnow", - return_value=thirty_min_future_time, + with ( + patch( + "homeassistant.components.recorder.util.last_run_was_recently_clean", + wraps=_last_run_was_recently_clean, + ) as last_run_was_recently_clean_mock, + patch( + "homeassistant.components.recorder.core.dt_util.utcnow", + return_value=thirty_min_future_time, + ), ): await async_setup_recorder_instance(hass, config) last_run_was_recently_clean_mock.assert_called_once() @@ -752,17 +758,23 @@ def test_combined_checks( assert "restarted cleanly and passed the basic sanity check" in caplog.text caplog.clear() - with patch( - "homeassistant.components.recorder.util.last_run_was_recently_clean", - side_effect=sqlite3.DatabaseError, - ), pytest.raises(sqlite3.DatabaseError): + with ( + patch( + "homeassistant.components.recorder.util.last_run_was_recently_clean", + side_effect=sqlite3.DatabaseError, + ), + pytest.raises(sqlite3.DatabaseError), + ): util.run_checks_on_open_db("fake_db_path", cursor) caplog.clear() - with patch( - "homeassistant.components.recorder.util.last_run_was_recently_clean", - side_effect=sqlite3.DatabaseError, - ), pytest.raises(sqlite3.DatabaseError): + with ( + patch( + "homeassistant.components.recorder.util.last_run_was_recently_clean", + side_effect=sqlite3.DatabaseError, + ), + pytest.raises(sqlite3.DatabaseError), + ): util.run_checks_on_open_db("fake_db_path", cursor) cursor.execute("DROP TABLE events;") diff --git a/tests/components/recorder/test_v32_migration.py b/tests/components/recorder/test_v32_migration.py index 4f2eb4c7585..a07c63b3376 100644 --- a/tests/components/recorder/test_v32_migration.py +++ b/tests/components/recorder/test_v32_migration.py @@ -92,26 +92,33 @@ async def test_migrate_times(caplog: pytest.LogCaptureFixture, tmp_path: Path) - with session_scope(hass=hass) as session: return inspect(session.connection()).get_indexes("states") - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch.object(core, "StatesMeta", old_db_schema.StatesMeta), patch.object( - core, "EventTypes", old_db_schema.EventTypes - ), patch.object(core, "EventData", old_db_schema.EventData), patch.object( - core, "States", old_db_schema.States - ), patch.object(core, "Events", old_db_schema.Events), patch( - CREATE_ENGINE_TARGET, new=_create_engine_test - ), patch( - "homeassistant.components.recorder.Recorder._migrate_events_context_ids", - ), patch( - "homeassistant.components.recorder.Recorder._migrate_states_context_ids", - ), patch( - "homeassistant.components.recorder.Recorder._migrate_event_type_ids", - ), patch( - "homeassistant.components.recorder.Recorder._migrate_entity_ids", - ), patch( - "homeassistant.components.recorder.Recorder._post_migrate_entity_ids" - ), patch( - "homeassistant.components.recorder.Recorder._cleanup_legacy_states_event_ids" + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch.object(core, "StatesMeta", old_db_schema.StatesMeta), + patch.object(core, "EventTypes", old_db_schema.EventTypes), + patch.object(core, "EventData", old_db_schema.EventData), + patch.object(core, "States", old_db_schema.States), + patch.object(core, "Events", old_db_schema.Events), + patch(CREATE_ENGINE_TARGET, new=_create_engine_test), + patch( + "homeassistant.components.recorder.Recorder._migrate_events_context_ids", + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_states_context_ids", + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_event_type_ids", + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_entity_ids", + ), + patch("homeassistant.components.recorder.Recorder._post_migrate_entity_ids"), + patch( + "homeassistant.components.recorder.Recorder._cleanup_legacy_states_event_ids" + ), ): async with async_test_home_assistant() as hass: recorder_helper.async_initialize_recorder(hass) @@ -256,26 +263,33 @@ async def test_migrate_can_resume_entity_id_post_migration( with session_scope(hass=hass) as session: return inspect(session.connection()).get_indexes("states") - with patch.object(recorder, "db_schema", old_db_schema), patch.object( - recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION - ), patch.object(core, "StatesMeta", old_db_schema.StatesMeta), patch.object( - core, "EventTypes", old_db_schema.EventTypes - ), patch.object(core, "EventData", old_db_schema.EventData), patch.object( - core, "States", old_db_schema.States - ), patch.object(core, "Events", old_db_schema.Events), patch( - CREATE_ENGINE_TARGET, new=_create_engine_test - ), patch( - "homeassistant.components.recorder.Recorder._migrate_events_context_ids", - ), patch( - "homeassistant.components.recorder.Recorder._migrate_states_context_ids", - ), patch( - "homeassistant.components.recorder.Recorder._migrate_event_type_ids", - ), patch( - "homeassistant.components.recorder.Recorder._migrate_entity_ids", - ), patch( - "homeassistant.components.recorder.Recorder._post_migrate_entity_ids" - ), patch( - "homeassistant.components.recorder.Recorder._cleanup_legacy_states_event_ids" + with ( + patch.object(recorder, "db_schema", old_db_schema), + patch.object( + recorder.migration, "SCHEMA_VERSION", old_db_schema.SCHEMA_VERSION + ), + patch.object(core, "StatesMeta", old_db_schema.StatesMeta), + patch.object(core, "EventTypes", old_db_schema.EventTypes), + patch.object(core, "EventData", old_db_schema.EventData), + patch.object(core, "States", old_db_schema.States), + patch.object(core, "Events", old_db_schema.Events), + patch(CREATE_ENGINE_TARGET, new=_create_engine_test), + patch( + "homeassistant.components.recorder.Recorder._migrate_events_context_ids", + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_states_context_ids", + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_event_type_ids", + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_entity_ids", + ), + patch("homeassistant.components.recorder.Recorder._post_migrate_entity_ids"), + patch( + "homeassistant.components.recorder.Recorder._cleanup_legacy_states_event_ids" + ), ): async with async_test_home_assistant() as hass: recorder_helper.async_initialize_recorder(hass) diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 08217d6003c..d594218e9d4 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -2166,16 +2166,19 @@ async def test_recorder_info_migration_queue_exhausted( migration_done.wait() return real_migration(*args) - with patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), patch( - "homeassistant.components.recorder.Recorder.async_periodic_statistics" - ), patch( - "homeassistant.components.recorder.core.create_engine", - new=create_engine_test, - ), patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), patch.object( - recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 0 - ), patch( - "homeassistant.components.recorder.migration._apply_update", - wraps=stalled_migration, + with ( + patch("homeassistant.components.recorder.ALLOW_IN_MEMORY_DB", True), + patch("homeassistant.components.recorder.Recorder.async_periodic_statistics"), + patch( + "homeassistant.components.recorder.core.create_engine", + new=create_engine_test, + ), + patch.object(recorder.core, "MAX_QUEUE_BACKLOG_MIN_VALUE", 1), + patch.object(recorder.core, "QUEUE_PERCENTAGE_ALLOWED_AVAILABLE_MEMORY", 0), + patch( + "homeassistant.components.recorder.migration._apply_update", + wraps=stalled_migration, + ), ): recorder_helper.async_initialize_recorder(hass) hass.create_task( diff --git a/tests/components/refoss/test_config_flow.py b/tests/components/refoss/test_config_flow.py index e16974a9d71..f022c950635 100644 --- a/tests/components/refoss/test_config_flow.py +++ b/tests/components/refoss/test_config_flow.py @@ -14,15 +14,19 @@ async def test_creating_entry_sets_up( hass: HomeAssistant, mock_setup_entry: AsyncMock ) -> None: """Test setting up refoss.""" - with patch( - "homeassistant.components.refoss.util.Discovery", - return_value=FakeDiscovery(), - ), patch( - "homeassistant.components.refoss.bridge.async_build_base_device", - return_value=build_base_device_mock(), - ), patch( - "homeassistant.components.refoss.switch.isinstance", - return_value=True, + with ( + patch( + "homeassistant.components.refoss.util.Discovery", + return_value=FakeDiscovery(), + ), + patch( + "homeassistant.components.refoss.bridge.async_build_base_device", + return_value=build_base_device_mock(), + ), + patch( + "homeassistant.components.refoss.switch.isinstance", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/tests/components/remember_the_milk/test_init.py b/tests/components/remember_the_milk/test_init.py index bda9f406f4b..c68fe14430a 100644 --- a/tests/components/remember_the_milk/test_init.py +++ b/tests/components/remember_the_milk/test_init.py @@ -10,9 +10,11 @@ from .const import JSON_STRING, PROFILE, TOKEN def test_create_new(hass: HomeAssistant) -> None: """Test creating a new config file.""" - with patch("builtins.open", mock_open()), patch( - "os.path.isfile", Mock(return_value=False) - ), patch.object(rtm.RememberTheMilkConfiguration, "save_config"): + with ( + patch("builtins.open", mock_open()), + patch("os.path.isfile", Mock(return_value=False)), + patch.object(rtm.RememberTheMilkConfiguration, "save_config"), + ): config = rtm.RememberTheMilkConfiguration(hass) config.set_token(PROFILE, TOKEN) assert config.get_token(PROFILE) == TOKEN @@ -20,8 +22,9 @@ def test_create_new(hass: HomeAssistant) -> None: def test_load_config(hass: HomeAssistant) -> None: """Test loading an existing token from the file.""" - with patch("builtins.open", mock_open(read_data=JSON_STRING)), patch( - "os.path.isfile", Mock(return_value=True) + with ( + patch("builtins.open", mock_open(read_data=JSON_STRING)), + patch("os.path.isfile", Mock(return_value=True)), ): config = rtm.RememberTheMilkConfiguration(hass) assert config.get_token(PROFILE) == TOKEN @@ -29,8 +32,9 @@ def test_load_config(hass: HomeAssistant) -> None: def test_invalid_data(hass: HomeAssistant) -> None: """Test starts with invalid data and should not raise an exception.""" - with patch("builtins.open", mock_open(read_data="random characters")), patch( - "os.path.isfile", Mock(return_value=True) + with ( + patch("builtins.open", mock_open(read_data="random characters")), + patch("os.path.isfile", Mock(return_value=True)), ): config = rtm.RememberTheMilkConfiguration(hass) assert config is not None @@ -42,9 +46,11 @@ def test_id_map(hass: HomeAssistant) -> None: list_id = "mylist" timeseries_id = "my_timeseries" rtm_id = "rtm-id-4567" - with patch("builtins.open", mock_open()), patch( - "os.path.isfile", Mock(return_value=False) - ), patch.object(rtm.RememberTheMilkConfiguration, "save_config"): + with ( + patch("builtins.open", mock_open()), + patch("os.path.isfile", Mock(return_value=False)), + patch.object(rtm.RememberTheMilkConfiguration, "save_config"), + ): config = rtm.RememberTheMilkConfiguration(hass) assert config.get_rtm_id(PROFILE, hass_id) is None @@ -56,8 +62,9 @@ def test_id_map(hass: HomeAssistant) -> None: def test_load_key_map(hass: HomeAssistant) -> None: """Test loading an existing key map from the file.""" - with patch("builtins.open", mock_open(read_data=JSON_STRING)), patch( - "os.path.isfile", Mock(return_value=True) + with ( + patch("builtins.open", mock_open(read_data=JSON_STRING)), + patch("os.path.isfile", Mock(return_value=True)), ): config = rtm.RememberTheMilkConfiguration(hass) assert config.get_rtm_id(PROFILE, "1234") == ("0", "1", "2") diff --git a/tests/components/renault/conftest.py b/tests/components/renault/conftest.py index c4855742e5b..c06abc8efd0 100644 --- a/tests/components/renault/conftest.py +++ b/tests/components/renault/conftest.py @@ -57,9 +57,12 @@ async def patch_renault_account(hass: HomeAssistant) -> RenaultAccount: MOCK_ACCOUNT_ID, websession=aiohttp_client.async_get_clientsession(hass), ) - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_account", - return_value=renault_account, + with ( + patch("renault_api.renault_session.RenaultSession.login"), + patch( + "renault_api.renault_client.RenaultClient.get_api_account", + return_value=renault_account, + ), ): yield renault_account @@ -125,27 +128,35 @@ def patch_fixtures_with_data(vehicle_type: str): """Mock fixtures.""" mock_fixtures = _get_fixtures(vehicle_type) - with patch( - "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", - return_value=mock_fixtures["battery_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", - return_value=mock_fixtures["charge_mode"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", - return_value=mock_fixtures["cockpit"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", - return_value=mock_fixtures["hvac_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_location", - return_value=mock_fixtures["location"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", - return_value=mock_fixtures["lock_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_res_state", - return_value=mock_fixtures["res_state"], + with ( + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", + return_value=mock_fixtures["battery_status"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", + return_value=mock_fixtures["charge_mode"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", + return_value=mock_fixtures["cockpit"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", + return_value=mock_fixtures["hvac_status"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_location", + return_value=mock_fixtures["location"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", + return_value=mock_fixtures["lock_status"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_res_state", + return_value=mock_fixtures["res_state"], + ), ): yield @@ -155,27 +166,35 @@ def patch_fixtures_with_no_data(): """Mock fixtures.""" mock_fixtures = _get_fixtures("") - with patch( - "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", - return_value=mock_fixtures["battery_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", - return_value=mock_fixtures["charge_mode"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", - return_value=mock_fixtures["cockpit"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", - return_value=mock_fixtures["hvac_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_location", - return_value=mock_fixtures["location"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", - return_value=mock_fixtures["lock_status"], - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_res_state", - return_value=mock_fixtures["res_state"], + with ( + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", + return_value=mock_fixtures["battery_status"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", + return_value=mock_fixtures["charge_mode"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", + return_value=mock_fixtures["cockpit"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", + return_value=mock_fixtures["hvac_status"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_location", + return_value=mock_fixtures["location"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", + return_value=mock_fixtures["lock_status"], + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_res_state", + return_value=mock_fixtures["res_state"], + ), ): yield @@ -183,27 +202,35 @@ def patch_fixtures_with_no_data(): @contextlib.contextmanager def _patch_fixtures_with_side_effect(side_effect: Any): """Mock fixtures.""" - with patch( - "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_location", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", - side_effect=side_effect, - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.get_res_state", - side_effect=side_effect, + with ( + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_battery_status", + side_effect=side_effect, + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charge_mode", + side_effect=side_effect, + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_cockpit", + side_effect=side_effect, + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_hvac_status", + side_effect=side_effect, + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_location", + side_effect=side_effect, + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_lock_status", + side_effect=side_effect, + ), + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_res_state", + side_effect=side_effect, + ), ): yield diff --git a/tests/components/renault/test_config_flow.py b/tests/components/renault/test_config_flow.py index 48b20071525..eca7991a27c 100644 --- a/tests/components/renault/test_config_flow.py +++ b/tests/components/renault/test_config_flow.py @@ -61,11 +61,15 @@ async def test_config_flow_single_account( ) # Account list single - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_account.RenaultAccount.account_id", return_value="123" - ), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[renault_account], + with ( + patch("renault_api.renault_session.RenaultSession.login"), + patch( + "renault_api.renault_account.RenaultAccount.account_id", return_value="123" + ), + patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[renault_account], + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -97,9 +101,12 @@ async def test_config_flow_no_account( assert result["errors"] == {} # Account list empty - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[], + with ( + patch("renault_api.renault_session.RenaultSession.login"), + patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[], + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -136,10 +143,14 @@ async def test_config_flow_multiple_accounts( ) # Multiple accounts - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[renault_account_1, renault_account_2], - ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): + with ( + patch("renault_api.renault_session.RenaultSession.login"), + patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[renault_account_1, renault_account_2], + ), + patch("renault_api.renault_account.RenaultAccount.get_vehicles"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -184,10 +195,14 @@ async def test_config_flow_duplicate( "account_id_1", websession=aiohttp_client.async_get_clientsession(hass), ) - with patch("renault_api.renault_session.RenaultSession.login"), patch( - "renault_api.renault_client.RenaultClient.get_api_accounts", - return_value=[renault_account], - ), patch("renault_api.renault_account.RenaultAccount.get_vehicles"): + with ( + patch("renault_api.renault_session.RenaultSession.login"), + patch( + "renault_api.renault_client.RenaultClient.get_api_accounts", + return_value=[renault_account], + ), + patch("renault_api.renault_account.RenaultAccount.get_vehicles"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ diff --git a/tests/components/renault/test_services.py b/tests/components/renault/test_services.py index 0dba6a9adae..e97988a09f7 100644 --- a/tests/components/renault/test_services.py +++ b/tests/components/renault/test_services.py @@ -90,10 +90,13 @@ async def test_service_set_ac_cancel( ATTR_VEHICLE: get_device_id(hass), } - with patch( - "renault_api.renault_vehicle.RenaultVehicle.set_ac_stop", - side_effect=RenaultException("Didn't work"), - ) as mock_action, pytest.raises(HomeAssistantError, match="Didn't work"): + with ( + patch( + "renault_api.renault_vehicle.RenaultVehicle.set_ac_stop", + side_effect=RenaultException("Didn't work"), + ) as mock_action, + pytest.raises(HomeAssistantError, match="Didn't work"), + ): await hass.services.async_call( DOMAIN, SERVICE_AC_CANCEL, service_data=data, blocking=True ) @@ -172,19 +175,22 @@ async def test_service_set_charge_schedule( ATTR_SCHEDULES: schedules, } - with patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charging_settings", - return_value=schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture("renault/charging_settings.json") - ).get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.set_charge_schedules", - return_value=( - schemas.KamereonVehicleHvacStartActionDataSchema.loads( - load_fixture("renault/action.set_charge_schedules.json") - ) + with ( + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charging_settings", + return_value=schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture("renault/charging_settings.json") + ).get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), ), - ) as mock_action: + patch( + "renault_api.renault_vehicle.RenaultVehicle.set_charge_schedules", + return_value=( + schemas.KamereonVehicleHvacStartActionDataSchema.loads( + load_fixture("renault/action.set_charge_schedules.json") + ) + ), + ) as mock_action, + ): await hass.services.async_call( DOMAIN, SERVICE_CHARGE_SET_SCHEDULES, service_data=data, blocking=True ) @@ -218,19 +224,22 @@ async def test_service_set_charge_schedule_multi( ATTR_SCHEDULES: schedules, } - with patch( - "renault_api.renault_vehicle.RenaultVehicle.get_charging_settings", - return_value=schemas.KamereonVehicleDataResponseSchema.loads( - load_fixture("renault/charging_settings.json") - ).get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), - ), patch( - "renault_api.renault_vehicle.RenaultVehicle.set_charge_schedules", - return_value=( - schemas.KamereonVehicleHvacStartActionDataSchema.loads( - load_fixture("renault/action.set_charge_schedules.json") - ) + with ( + patch( + "renault_api.renault_vehicle.RenaultVehicle.get_charging_settings", + return_value=schemas.KamereonVehicleDataResponseSchema.loads( + load_fixture("renault/charging_settings.json") + ).get_attributes(schemas.KamereonVehicleChargingSettingsDataSchema), ), - ) as mock_action: + patch( + "renault_api.renault_vehicle.RenaultVehicle.set_charge_schedules", + return_value=( + schemas.KamereonVehicleHvacStartActionDataSchema.loads( + load_fixture("renault/action.set_charge_schedules.json") + ) + ), + ) as mock_action, + ): await hass.services.async_call( DOMAIN, SERVICE_CHARGE_SET_SCHEDULES, service_data=data, blocking=True ) diff --git a/tests/components/renson/test_config_flow.py b/tests/components/renson/test_config_flow.py index 185d21aead8..6d51605824e 100644 --- a/tests/components/renson/test_config_flow.py +++ b/tests/components/renson/test_config_flow.py @@ -16,13 +16,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.renson.config_flow.renson", - return_value={"title": "Renson"}, - ), patch( - "homeassistant.components.renson.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.renson.config_flow.renson", + return_value={"title": "Renson"}, + ), + patch( + "homeassistant.components.renson.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index a340e8cebbe..5fd52b97b6b 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -51,12 +51,15 @@ def reolink_connect_class( mock_get_source_ip: None, ) -> Generator[MagicMock, None, None]: """Mock reolink connection and return both the host_mock and host_mock_class.""" - with patch( - "homeassistant.components.reolink.host.webhook.async_register", - return_value=True, - ), patch( - "homeassistant.components.reolink.host.Host", autospec=True - ) as host_mock_class: + with ( + patch( + "homeassistant.components.reolink.host.webhook.async_register", + return_value=True, + ), + patch( + "homeassistant.components.reolink.host.Host", autospec=True + ) as host_mock_class, + ): host_mock = host_mock_class.return_value host_mock.get_host_data.return_value = None host_mock.get_states.return_value = None diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index abf97ee96ca..8ebce5d350e 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -236,12 +236,14 @@ async def test_https_repair_issue( hass, {"country": "GB", "internal_url": "https://test_homeassistant_address"} ) - with patch( - "homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0 - ), patch( - "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 - ), patch( - "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + with ( + patch("homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0), + patch( + "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 + ), + patch( + "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -261,12 +263,14 @@ async def test_ssl_repair_issue( hass, {"country": "GB", "internal_url": "http://test_homeassistant_address"} ) - with patch( - "homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0 - ), patch( - "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 - ), patch( - "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + with ( + patch("homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0), + patch( + "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 + ), + patch( + "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -299,12 +303,14 @@ async def test_webhook_repair_issue( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test repairs issue is raised when the webhook url is unreachable.""" - with patch( - "homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0 - ), patch( - "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 - ), patch( - "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + with ( + patch("homeassistant.components.reolink.host.FIRST_ONVIF_TIMEOUT", new=0), + patch( + "homeassistant.components.reolink.host.FIRST_ONVIF_LONG_POLL_TIMEOUT", new=0 + ), + patch( + "homeassistant.components.reolink.host.ReolinkHost._async_long_polling", + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/rfxtrx/conftest.py b/tests/components/rfxtrx/conftest.py index 536672b66cd..5e0223173f9 100644 --- a/tests/components/rfxtrx/conftest.py +++ b/tests/components/rfxtrx/conftest.py @@ -69,8 +69,9 @@ async def setup_rfx_test_cfg( async def transport_mock(hass): """Fixture that make sure all transports are fake.""" transport = Mock(spec=RFXtrxTransport) - with patch("RFXtrx.PySerialTransport", new=transport), patch( - "RFXtrx.PyNetworkTransport", transport + with ( + patch("RFXtrx.PySerialTransport", new=transport), + patch("RFXtrx.PyNetworkTransport", transport), ): yield transport diff --git a/tests/components/ridwell/conftest.py b/tests/components/ridwell/conftest.py index aed5a00b1a8..32907ac8037 100644 --- a/tests/components/ridwell/conftest.py +++ b/tests/components/ridwell/conftest.py @@ -79,12 +79,15 @@ def config_fixture(hass): @pytest.fixture(name="mock_aioridwell") async def mock_aioridwell_fixture(hass, client, config): """Define a fixture to patch aioridwell.""" - with patch( - "homeassistant.components.ridwell.config_flow.async_get_client", - return_value=client, - ), patch( - "homeassistant.components.ridwell.coordinator.async_get_client", - return_value=client, + with ( + patch( + "homeassistant.components.ridwell.config_flow.async_get_client", + return_value=client, + ), + patch( + "homeassistant.components.ridwell.coordinator.async_get_client", + return_value=client, + ), ): yield diff --git a/tests/components/risco/conftest.py b/tests/components/risco/conftest.py index 6d810ec6abd..ab3b64b245d 100644 --- a/tests/components/risco/conftest.py +++ b/tests/components/risco/conftest.py @@ -36,25 +36,30 @@ def two_zone_cloud(): """Fixture to mock alarm with two zones.""" zone_mocks = {0: zone_mock(), 1: zone_mock()} alarm_mock = MagicMock() - with patch.object( - zone_mocks[0], "id", new_callable=PropertyMock(return_value=0) - ), patch.object( - zone_mocks[0], "name", new_callable=PropertyMock(return_value="Zone 0") - ), patch.object( - zone_mocks[0], "bypassed", new_callable=PropertyMock(return_value=False) - ), patch.object( - zone_mocks[1], "id", new_callable=PropertyMock(return_value=1) - ), patch.object( - zone_mocks[1], "name", new_callable=PropertyMock(return_value="Zone 1") - ), patch.object( - zone_mocks[1], "bypassed", new_callable=PropertyMock(return_value=False) - ), patch.object( - alarm_mock, - "zones", - new_callable=PropertyMock(return_value=zone_mocks), - ), patch( - "homeassistant.components.risco.RiscoCloud.get_state", - return_value=alarm_mock, + with ( + patch.object(zone_mocks[0], "id", new_callable=PropertyMock(return_value=0)), + patch.object( + zone_mocks[0], "name", new_callable=PropertyMock(return_value="Zone 0") + ), + patch.object( + zone_mocks[0], "bypassed", new_callable=PropertyMock(return_value=False) + ), + patch.object(zone_mocks[1], "id", new_callable=PropertyMock(return_value=1)), + patch.object( + zone_mocks[1], "name", new_callable=PropertyMock(return_value="Zone 1") + ), + patch.object( + zone_mocks[1], "bypassed", new_callable=PropertyMock(return_value=False) + ), + patch.object( + alarm_mock, + "zones", + new_callable=PropertyMock(return_value=zone_mocks), + ), + patch( + "homeassistant.components.risco.RiscoCloud.get_state", + return_value=alarm_mock, + ), ): yield zone_mocks @@ -64,37 +69,48 @@ def two_zone_local(): """Fixture to mock alarm with two zones.""" zone_mocks = {0: zone_mock(), 1: zone_mock()} system = system_mock() - with patch.object( - zone_mocks[0], "id", new_callable=PropertyMock(return_value=0) - ), patch.object( - zone_mocks[0], "name", new_callable=PropertyMock(return_value="Zone 0") - ), patch.object( - zone_mocks[0], "alarmed", new_callable=PropertyMock(return_value=False) - ), patch.object( - zone_mocks[0], "bypassed", new_callable=PropertyMock(return_value=False) - ), patch.object( - zone_mocks[0], "armed", new_callable=PropertyMock(return_value=False) - ), patch.object( - zone_mocks[1], "id", new_callable=PropertyMock(return_value=1) - ), patch.object( - zone_mocks[1], "name", new_callable=PropertyMock(return_value="Zone 1") - ), patch.object( - zone_mocks[1], "alarmed", new_callable=PropertyMock(return_value=False) - ), patch.object( - zone_mocks[1], "bypassed", new_callable=PropertyMock(return_value=False) - ), patch.object( - zone_mocks[1], "armed", new_callable=PropertyMock(return_value=False) - ), patch.object( - system, "name", new_callable=PropertyMock(return_value=TEST_SITE_NAME) - ), patch( - "homeassistant.components.risco.RiscoLocal.partitions", - new_callable=PropertyMock(return_value={}), - ), patch( - "homeassistant.components.risco.RiscoLocal.zones", - new_callable=PropertyMock(return_value=zone_mocks), - ), patch( - "homeassistant.components.risco.RiscoLocal.system", - new_callable=PropertyMock(return_value=system), + with ( + patch.object(zone_mocks[0], "id", new_callable=PropertyMock(return_value=0)), + patch.object( + zone_mocks[0], "name", new_callable=PropertyMock(return_value="Zone 0") + ), + patch.object( + zone_mocks[0], "alarmed", new_callable=PropertyMock(return_value=False) + ), + patch.object( + zone_mocks[0], "bypassed", new_callable=PropertyMock(return_value=False) + ), + patch.object( + zone_mocks[0], "armed", new_callable=PropertyMock(return_value=False) + ), + patch.object(zone_mocks[1], "id", new_callable=PropertyMock(return_value=1)), + patch.object( + zone_mocks[1], "name", new_callable=PropertyMock(return_value="Zone 1") + ), + patch.object( + zone_mocks[1], "alarmed", new_callable=PropertyMock(return_value=False) + ), + patch.object( + zone_mocks[1], "bypassed", new_callable=PropertyMock(return_value=False) + ), + patch.object( + zone_mocks[1], "armed", new_callable=PropertyMock(return_value=False) + ), + patch.object( + system, "name", new_callable=PropertyMock(return_value=TEST_SITE_NAME) + ), + patch( + "homeassistant.components.risco.RiscoLocal.partitions", + new_callable=PropertyMock(return_value={}), + ), + patch( + "homeassistant.components.risco.RiscoLocal.zones", + new_callable=PropertyMock(return_value=zone_mocks), + ), + patch( + "homeassistant.components.risco.RiscoLocal.system", + new_callable=PropertyMock(return_value=system), + ), ): yield zone_mocks @@ -137,20 +153,26 @@ def login_with_error(exception): @pytest.fixture async def setup_risco_cloud(hass, cloud_config_entry, events): """Set up a Risco integration for testing.""" - with patch( - "homeassistant.components.risco.RiscoCloud.login", - return_value=True, - ), patch( - "homeassistant.components.risco.RiscoCloud.site_uuid", - new_callable=PropertyMock(return_value=TEST_SITE_UUID), - ), patch( - "homeassistant.components.risco.RiscoCloud.site_name", - new_callable=PropertyMock(return_value=TEST_SITE_NAME), - ), patch( - "homeassistant.components.risco.RiscoCloud.close", - ), patch( - "homeassistant.components.risco.RiscoCloud.get_events", - return_value=events, + with ( + patch( + "homeassistant.components.risco.RiscoCloud.login", + return_value=True, + ), + patch( + "homeassistant.components.risco.RiscoCloud.site_uuid", + new_callable=PropertyMock(return_value=TEST_SITE_UUID), + ), + patch( + "homeassistant.components.risco.RiscoCloud.site_name", + new_callable=PropertyMock(return_value=TEST_SITE_NAME), + ), + patch( + "homeassistant.components.risco.RiscoCloud.close", + ), + patch( + "homeassistant.components.risco.RiscoCloud.get_events", + return_value=events, + ), ): await hass.config_entries.async_setup(cloud_config_entry.entry_id) await hass.async_block_till_done() @@ -181,14 +203,18 @@ def connect_with_error(exception): @pytest.fixture async def setup_risco_local(hass, local_config_entry): """Set up a local Risco integration for testing.""" - with patch( - "homeassistant.components.risco.RiscoLocal.connect", - return_value=True, - ), patch( - "homeassistant.components.risco.RiscoLocal.id", - new_callable=PropertyMock(return_value=TEST_SITE_UUID), - ), patch( - "homeassistant.components.risco.RiscoLocal.disconnect", + with ( + patch( + "homeassistant.components.risco.RiscoLocal.connect", + return_value=True, + ), + patch( + "homeassistant.components.risco.RiscoLocal.id", + new_callable=PropertyMock(return_value=TEST_SITE_UUID), + ), + patch( + "homeassistant.components.risco.RiscoLocal.disconnect", + ), ): await hass.config_entries.async_setup(local_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/risco/test_alarm_control_panel.py b/tests/components/risco/test_alarm_control_panel.py index ee8b844c167..ff831b59062 100644 --- a/tests/components/risco/test_alarm_control_panel.py +++ b/tests/components/risco/test_alarm_control_panel.py @@ -92,17 +92,22 @@ def two_part_cloud_alarm(): """Fixture to mock alarm with two partitions.""" partition_mocks = {0: _partition_mock(), 1: _partition_mock()} alarm_mock = MagicMock() - with patch.object( - partition_mocks[0], "id", new_callable=PropertyMock(return_value=0) - ), patch.object( - partition_mocks[1], "id", new_callable=PropertyMock(return_value=1) - ), patch.object( - alarm_mock, - "partitions", - new_callable=PropertyMock(return_value=partition_mocks), - ), patch( - "homeassistant.components.risco.RiscoCloud.get_state", - return_value=alarm_mock, + with ( + patch.object( + partition_mocks[0], "id", new_callable=PropertyMock(return_value=0) + ), + patch.object( + partition_mocks[1], "id", new_callable=PropertyMock(return_value=1) + ), + patch.object( + alarm_mock, + "partitions", + new_callable=PropertyMock(return_value=partition_mocks), + ), + patch( + "homeassistant.components.risco.RiscoCloud.get_state", + return_value=alarm_mock, + ), ): yield partition_mocks @@ -111,20 +116,27 @@ def two_part_cloud_alarm(): def two_part_local_alarm(): """Fixture to mock alarm with two partitions.""" partition_mocks = {0: _partition_mock(), 1: _partition_mock()} - with patch.object( - partition_mocks[0], "id", new_callable=PropertyMock(return_value=0) - ), patch.object( - partition_mocks[0], "name", new_callable=PropertyMock(return_value="Name 0") - ), patch.object( - partition_mocks[1], "id", new_callable=PropertyMock(return_value=1) - ), patch.object( - partition_mocks[1], "name", new_callable=PropertyMock(return_value="Name 1") - ), patch( - "homeassistant.components.risco.RiscoLocal.zones", - new_callable=PropertyMock(return_value={}), - ), patch( - "homeassistant.components.risco.RiscoLocal.partitions", - new_callable=PropertyMock(return_value=partition_mocks), + with ( + patch.object( + partition_mocks[0], "id", new_callable=PropertyMock(return_value=0) + ), + patch.object( + partition_mocks[0], "name", new_callable=PropertyMock(return_value="Name 0") + ), + patch.object( + partition_mocks[1], "id", new_callable=PropertyMock(return_value=1) + ), + patch.object( + partition_mocks[1], "name", new_callable=PropertyMock(return_value="Name 1") + ), + patch( + "homeassistant.components.risco.RiscoLocal.zones", + new_callable=PropertyMock(return_value={}), + ), + patch( + "homeassistant.components.risco.RiscoLocal.partitions", + new_callable=PropertyMock(return_value=partition_mocks), + ), ): yield partition_mocks diff --git a/tests/components/risco/test_binary_sensor.py b/tests/components/risco/test_binary_sensor.py index eae4ef5e472..ea18c59e236 100644 --- a/tests/components/risco/test_binary_sensor.py +++ b/tests/components/risco/test_binary_sensor.py @@ -237,17 +237,22 @@ def mock_system_handler(): def system_only_local(): """Fixture to mock a system with no zones or partitions.""" system = system_mock() - with patch.object( - system, "name", new_callable=PropertyMock(return_value=TEST_SITE_NAME) - ), patch( - "homeassistant.components.risco.RiscoLocal.zones", - new_callable=PropertyMock(return_value={}), - ), patch( - "homeassistant.components.risco.RiscoLocal.partitions", - new_callable=PropertyMock(return_value={}), - ), patch( - "homeassistant.components.risco.RiscoLocal.system", - new_callable=PropertyMock(return_value=system), + with ( + patch.object( + system, "name", new_callable=PropertyMock(return_value=TEST_SITE_NAME) + ), + patch( + "homeassistant.components.risco.RiscoLocal.zones", + new_callable=PropertyMock(return_value={}), + ), + patch( + "homeassistant.components.risco.RiscoLocal.partitions", + new_callable=PropertyMock(return_value={}), + ), + patch( + "homeassistant.components.risco.RiscoLocal.system", + new_callable=PropertyMock(return_value=system), + ), ): yield system diff --git a/tests/components/risco/test_config_flow.py b/tests/components/risco/test_config_flow.py index d4dd4e3fac5..d031f4e8542 100644 --- a/tests/components/risco/test_config_flow.py +++ b/tests/components/risco/test_config_flow.py @@ -66,18 +66,23 @@ async def test_cloud_form(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {} - with patch( - "homeassistant.components.risco.config_flow.RiscoCloud.login", - return_value=True, - ), patch( - "homeassistant.components.risco.config_flow.RiscoCloud.site_name", - new_callable=PropertyMock(return_value=TEST_SITE_NAME), - ), patch( - "homeassistant.components.risco.config_flow.RiscoCloud.close" - ) as mock_close, patch( - "homeassistant.components.risco.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.login", + return_value=True, + ), + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.site_name", + new_callable=PropertyMock(return_value=TEST_SITE_NAME), + ), + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.close" + ) as mock_close, + patch( + "homeassistant.components.risco.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], TEST_CLOUD_DATA ) @@ -156,18 +161,23 @@ async def test_form_reauth(hass: HomeAssistant, cloud_config_entry) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.risco.config_flow.RiscoCloud.login", - return_value=True, - ), patch( - "homeassistant.components.risco.config_flow.RiscoCloud.site_name", - new_callable=PropertyMock(return_value=TEST_SITE_NAME), - ), patch( - "homeassistant.components.risco.config_flow.RiscoCloud.close", - ), patch( - "homeassistant.components.risco.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.login", + return_value=True, + ), + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.site_name", + new_callable=PropertyMock(return_value=TEST_SITE_NAME), + ), + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.close", + ), + patch( + "homeassistant.components.risco.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {**TEST_CLOUD_DATA, CONF_PASSWORD: "new_password"} ) @@ -192,18 +202,23 @@ async def test_form_reauth_with_new_username( assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.risco.config_flow.RiscoCloud.login", - return_value=True, - ), patch( - "homeassistant.components.risco.config_flow.RiscoCloud.site_name", - new_callable=PropertyMock(return_value=TEST_SITE_NAME), - ), patch( - "homeassistant.components.risco.config_flow.RiscoCloud.close", - ), patch( - "homeassistant.components.risco.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.login", + return_value=True, + ), + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.site_name", + new_callable=PropertyMock(return_value=TEST_SITE_NAME), + ), + patch( + "homeassistant.components.risco.config_flow.RiscoCloud.close", + ), + patch( + "homeassistant.components.risco.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {**TEST_CLOUD_DATA, CONF_USERNAME: "new_user"} ) @@ -230,18 +245,23 @@ async def test_local_form(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {} - with patch( - "homeassistant.components.risco.config_flow.RiscoLocal.connect", - return_value=True, - ), patch( - "homeassistant.components.risco.config_flow.RiscoLocal.id", - new_callable=PropertyMock(return_value=TEST_SITE_NAME), - ), patch( - "homeassistant.components.risco.config_flow.RiscoLocal.disconnect" - ) as mock_close, patch( - "homeassistant.components.risco.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.risco.config_flow.RiscoLocal.connect", + return_value=True, + ), + patch( + "homeassistant.components.risco.config_flow.RiscoLocal.id", + new_callable=PropertyMock(return_value=TEST_SITE_NAME), + ), + patch( + "homeassistant.components.risco.config_flow.RiscoLocal.disconnect" + ) as mock_close, + patch( + "homeassistant.components.risco.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], TEST_LOCAL_DATA ) @@ -302,14 +322,18 @@ async def test_form_local_already_exists(hass: HomeAssistant) -> None: result["flow_id"], {"next_step_id": "local"} ) - with patch( - "homeassistant.components.risco.config_flow.RiscoLocal.connect", - return_value=True, - ), patch( - "homeassistant.components.risco.config_flow.RiscoLocal.id", - new_callable=PropertyMock(return_value=TEST_SITE_NAME), - ), patch( - "homeassistant.components.risco.config_flow.RiscoLocal.disconnect", + with ( + patch( + "homeassistant.components.risco.config_flow.RiscoLocal.connect", + return_value=True, + ), + patch( + "homeassistant.components.risco.config_flow.RiscoLocal.id", + new_callable=PropertyMock(return_value=TEST_SITE_NAME), + ), + patch( + "homeassistant.components.risco.config_flow.RiscoLocal.disconnect", + ), ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], TEST_LOCAL_DATA diff --git a/tests/components/risco/test_sensor.py b/tests/components/risco/test_sensor.py index be909254d70..157eb3e62b5 100644 --- a/tests/components/risco/test_sensor.py +++ b/tests/components/risco/test_sensor.py @@ -111,12 +111,15 @@ CATEGORIES_TO_EVENTS = { @pytest.fixture def _no_zones_and_partitions(): - with patch( - "homeassistant.components.risco.RiscoLocal.zones", - new_callable=PropertyMock(return_value=[]), - ), patch( - "homeassistant.components.risco.RiscoLocal.partitions", - new_callable=PropertyMock(return_value=[]), + with ( + patch( + "homeassistant.components.risco.RiscoLocal.zones", + new_callable=PropertyMock(return_value=[]), + ), + patch( + "homeassistant.components.risco.RiscoLocal.partitions", + new_callable=PropertyMock(return_value=[]), + ), ): yield @@ -188,11 +191,14 @@ async def test_cloud_setup( for category, entity_id in ENTITY_IDS.items(): _check_state(hass, category, entity_id) - with patch( - "homeassistant.components.risco.RiscoCloud.get_events", return_value=[] - ) as events_mock, patch( - "homeassistant.components.risco.Store.async_load", - return_value={LAST_EVENT_TIMESTAMP_KEY: TEST_EVENTS[0].time}, + with ( + patch( + "homeassistant.components.risco.RiscoCloud.get_events", return_value=[] + ) as events_mock, + patch( + "homeassistant.components.risco.Store.async_load", + return_value={LAST_EVENT_TIMESTAMP_KEY: TEST_EVENTS[0].time}, + ), ): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=65)) await hass.async_block_till_done() diff --git a/tests/components/rituals_perfume_genie/test_config_flow.py b/tests/components/rituals_perfume_genie/test_config_flow.py index b601c6c7c47..45f14399f15 100644 --- a/tests/components/rituals_perfume_genie/test_config_flow.py +++ b/tests/components/rituals_perfume_genie/test_config_flow.py @@ -32,13 +32,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.rituals_perfume_genie.config_flow.Account", - side_effect=_mock_account, - ), patch( - "homeassistant.components.rituals_perfume_genie.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rituals_perfume_genie.config_flow.Account", + side_effect=_mock_account, + ), + patch( + "homeassistant.components.rituals_perfume_genie.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/roborock/conftest.py b/tests/components/roborock/conftest.py index b556804e46a..91331a1486a 100644 --- a/tests/components/roborock/conftest.py +++ b/tests/components/roborock/conftest.py @@ -30,41 +30,50 @@ from tests.common import MockConfigEntry @pytest.fixture(name="bypass_api_fixture") def bypass_api_fixture() -> None: """Skip calls to the API.""" - with patch( - "homeassistant.components.roborock.RoborockMqttClient.async_connect" - ), patch( - "homeassistant.components.roborock.RoborockMqttClient._send_command" - ), patch( - "homeassistant.components.roborock.RoborockApiClient.get_home_data", - return_value=HOME_DATA, - ), patch( - "homeassistant.components.roborock.RoborockMqttClient.get_networking", - return_value=NETWORK_INFO, - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", - return_value=PROP, - ), patch( - "homeassistant.components.roborock.coordinator.RoborockMqttClient.get_multi_maps_list", - return_value=MULTI_MAP_LIST, - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_multi_maps_list", - return_value=MULTI_MAP_LIST, - ), patch( - "homeassistant.components.roborock.image.RoborockMapDataParser.parse", - return_value=MAP_DATA, - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message" - ), patch( - "homeassistant.components.roborock.RoborockMqttClient._wait_response" - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient._wait_response" - ), patch( - "roborock.api.AttributeCache.async_value", - ), patch( - "roborock.api.AttributeCache.value", - ), patch( - "homeassistant.components.roborock.image.MAP_SLEEP", - 0, + with ( + patch("homeassistant.components.roborock.RoborockMqttClient.async_connect"), + patch("homeassistant.components.roborock.RoborockMqttClient._send_command"), + patch( + "homeassistant.components.roborock.RoborockApiClient.get_home_data", + return_value=HOME_DATA, + ), + patch( + "homeassistant.components.roborock.RoborockMqttClient.get_networking", + return_value=NETWORK_INFO, + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", + return_value=PROP, + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockMqttClient.get_multi_maps_list", + return_value=MULTI_MAP_LIST, + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_multi_maps_list", + return_value=MULTI_MAP_LIST, + ), + patch( + "homeassistant.components.roborock.image.RoborockMapDataParser.parse", + return_value=MAP_DATA, + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message" + ), + patch("homeassistant.components.roborock.RoborockMqttClient._wait_response"), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient._wait_response" + ), + patch( + "roborock.api.AttributeCache.async_value", + ), + patch( + "roborock.api.AttributeCache.value", + ), + patch( + "homeassistant.components.roborock.image.MAP_SLEEP", + 0, + ), ): yield diff --git a/tests/components/roborock/test_image.py b/tests/components/roborock/test_image.py index b8d1dc408a2..77829e5aaa6 100644 --- a/tests/components/roborock/test_image.py +++ b/tests/components/roborock/test_image.py @@ -35,11 +35,14 @@ async def test_floorplan_image( # Copy the device prop so we don't override it prop = copy.deepcopy(PROP) prop.status.in_cleaning = 1 - with patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", - return_value=prop, - ), patch( - "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now + with ( + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", + return_value=prop, + ), + patch( + "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now + ), ): async_fire_time_changed(hass, now) await hass.async_block_till_done() @@ -63,14 +66,18 @@ async def test_floorplan_image_failed_parse( prop = copy.deepcopy(PROP) prop.status.in_cleaning = 1 # Update image, but get none for parse image. - with patch( - "homeassistant.components.roborock.image.RoborockMapDataParser.parse", - return_value=map_data, - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", - return_value=prop, - ), patch( - "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now + with ( + patch( + "homeassistant.components.roborock.image.RoborockMapDataParser.parse", + return_value=map_data, + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", + return_value=prop, + ), + patch( + "homeassistant.components.roborock.image.dt_util.utcnow", return_value=now + ), ): async_fire_time_changed(hass, now) resp = await client.get("/api/image_proxy/image.roborock_s7_maxv_upstairs") diff --git a/tests/components/roborock/test_init.py b/tests/components/roborock/test_init.py index 8f9bc56d07b..08a3afe6c5e 100644 --- a/tests/components/roborock/test_init.py +++ b/tests/components/roborock/test_init.py @@ -32,11 +32,14 @@ async def test_config_entry_not_ready( hass: HomeAssistant, mock_roborock_entry: MockConfigEntry ) -> None: """Test that when coordinator update fails, entry retries.""" - with patch( - "homeassistant.components.roborock.RoborockApiClient.get_home_data", - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", - side_effect=RoborockException(), + with ( + patch( + "homeassistant.components.roborock.RoborockApiClient.get_home_data", + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", + side_effect=RoborockException(), + ), ): await async_setup_component(hass, DOMAIN, {}) assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY @@ -46,12 +49,15 @@ async def test_config_entry_not_ready_home_data( hass: HomeAssistant, mock_roborock_entry: MockConfigEntry ) -> None: """Test that when we fail to get home data, entry retries.""" - with patch( - "homeassistant.components.roborock.RoborockApiClient.get_home_data", - side_effect=RoborockException(), - ), patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", - side_effect=RoborockException(), + with ( + patch( + "homeassistant.components.roborock.RoborockApiClient.get_home_data", + side_effect=RoborockException(), + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.get_prop", + side_effect=RoborockException(), + ), ): await async_setup_component(hass, DOMAIN, {}) assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY @@ -85,12 +91,15 @@ async def test_cloud_client_fails_props( hass: HomeAssistant, mock_roborock_entry: MockConfigEntry, bypass_api_fixture ) -> None: """Test that if networking succeeds, but we can't communicate with the vacuum, we can't get props, fail.""" - with patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.ping", - side_effect=RoborockException(), - ), patch( - "homeassistant.components.roborock.coordinator.RoborockMqttClient.get_prop", - side_effect=RoborockException(), + with ( + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.ping", + side_effect=RoborockException(), + ), + patch( + "homeassistant.components.roborock.coordinator.RoborockMqttClient.get_prop", + side_effect=RoborockException(), + ), ): await async_setup_component(hass, DOMAIN, {}) assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/roborock/test_select.py b/tests/components/roborock/test_select.py index 67fefee7afb..9310d4e2e9a 100644 --- a/tests/components/roborock/test_select.py +++ b/tests/components/roborock/test_select.py @@ -48,10 +48,13 @@ async def test_update_failure( setup_entry: MockConfigEntry, ) -> None: """Test that changing a value will raise a homeassistanterror when it fails.""" - with patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message", - side_effect=RoborockException(), - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_message", + side_effect=RoborockException(), + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( "select", SERVICE_SELECT_OPTION, diff --git a/tests/components/roborock/test_vacuum.py b/tests/components/roborock/test_vacuum.py index 4b683da88fc..a3d5854edd1 100644 --- a/tests/components/roborock/test_vacuum.py +++ b/tests/components/roborock/test_vacuum.py @@ -141,10 +141,13 @@ async def test_failed_user_command( ) -> None: """Test that when a user sends an invalid command, we raise HomeAssistantError.""" data = {ATTR_ENTITY_ID: ENTITY_ID, "command": "fake_command"} - with patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_command", - side_effect=RoborockException(), - ), pytest.raises(HomeAssistantError, match="Error while calling fake_command"): + with ( + patch( + "homeassistant.components.roborock.coordinator.RoborockLocalClient.send_command", + side_effect=RoborockException(), + ), + pytest.raises(HomeAssistantError, match="Error while calling fake_command"), + ): await hass.services.async_call( Platform.VACUUM, SERVICE_SEND_COMMAND, diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index af82d4a690e..2eaf3b14e38 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -174,16 +174,20 @@ async def test_form_user_discovery_and_password_fetch(hass: HomeAssistant) -> No assert result2["errors"] is None assert result2["step_id"] == "link" - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.config_flow.RoombaPassword", - _mocked_getpassword, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -295,16 +299,20 @@ async def test_form_user_discovery_manual_and_auto_password_fetch( assert result3["type"] == data_entry_flow.FlowResultType.FORM assert result3["errors"] is None - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.config_flow.RoombaPassword", - _mocked_getpassword, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], {}, @@ -428,16 +436,20 @@ async def test_form_user_discovery_no_devices_found_and_auto_password_fetch( assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] is None - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.config_flow.RoombaPassword", - _mocked_getpassword, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, @@ -501,13 +513,16 @@ async def test_form_user_discovery_no_devices_found_and_password_fetch_fails( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], {CONF_PASSWORD: "password"}, @@ -572,13 +587,16 @@ async def test_form_user_discovery_not_devices_found_and_password_fetch_fails_an ) await hass.async_block_till_done() - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], {CONF_PASSWORD: "password"}, @@ -631,13 +649,16 @@ async def test_form_user_discovery_and_password_fetch_gets_connection_refused( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], {CONF_PASSWORD: "password"}, @@ -685,16 +706,20 @@ async def test_dhcp_discovery_and_roomba_discovery_finds( assert result["step_id"] == "link" assert result["description_placeholders"] == {"name": "robot_name"} - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.config_flow.RoombaPassword", - _mocked_getpassword, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -759,16 +784,20 @@ async def test_dhcp_discovery_falls_back_to_manual( assert result3["type"] == data_entry_flow.FlowResultType.FORM assert result3["errors"] is None - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.config_flow.RoombaPassword", - _mocked_getpassword, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], {}, @@ -825,16 +854,20 @@ async def test_dhcp_discovery_no_devices_falls_back_to_manual( assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] is None - with patch( - "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", - return_value=mocked_roomba, - ), patch( - "homeassistant.components.roomba.config_flow.RoombaPassword", - _mocked_getpassword, - ), patch( - "homeassistant.components.roomba.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.roomba.config_flow.RoombaFactory.create_roomba", + return_value=mocked_roomba, + ), + patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), + patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], {}, diff --git a/tests/components/roon/test_config_flow.py b/tests/components/roon/test_config_flow.py index ce7203caf66..c31e689e05b 100644 --- a/tests/components/roon/test_config_flow.py +++ b/tests/components/roon/test_config_flow.py @@ -73,15 +73,19 @@ class RoonDiscoveryFailedMock(RoonDiscoveryMock): async def test_successful_discovery_and_auth(hass: HomeAssistant) -> None: """Test when discovery and auth both work ok.""" - with patch( - "homeassistant.components.roon.config_flow.RoonApi", - return_value=RoonApiMock(), - ), patch( - "homeassistant.components.roon.config_flow.RoonDiscovery", - return_value=RoonDiscoveryMock(), - ), patch( - "homeassistant.components.roon.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.roon.config_flow.RoonApi", + return_value=RoonApiMock(), + ), + patch( + "homeassistant.components.roon.config_flow.RoonDiscovery", + return_value=RoonDiscoveryMock(), + ), + patch( + "homeassistant.components.roon.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -111,15 +115,19 @@ async def test_successful_discovery_and_auth(hass: HomeAssistant) -> None: async def test_unsuccessful_discovery_user_form_and_auth(hass: HomeAssistant) -> None: """Test unsuccessful discover, user adding the host via the form and then successful auth.""" - with patch( - "homeassistant.components.roon.config_flow.RoonApi", - return_value=RoonApiMock(), - ), patch( - "homeassistant.components.roon.config_flow.RoonDiscovery", - return_value=RoonDiscoveryFailedMock(), - ), patch( - "homeassistant.components.roon.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.roon.config_flow.RoonApi", + return_value=RoonApiMock(), + ), + patch( + "homeassistant.components.roon.config_flow.RoonDiscovery", + return_value=RoonDiscoveryFailedMock(), + ), + patch( + "homeassistant.components.roon.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -158,12 +166,15 @@ async def test_duplicate_config(hass: HomeAssistant) -> None: hass ) - with patch( - "homeassistant.components.roon.config_flow.RoonApi", - return_value=RoonApiMock(), - ), patch( - "homeassistant.components.roon.config_flow.RoonDiscovery", - return_value=RoonDiscoveryFailedMock(), + with ( + patch( + "homeassistant.components.roon.config_flow.RoonApi", + return_value=RoonApiMock(), + ), + patch( + "homeassistant.components.roon.config_flow.RoonDiscovery", + return_value=RoonDiscoveryFailedMock(), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -190,21 +201,27 @@ async def test_duplicate_config(hass: HomeAssistant) -> None: async def test_successful_discovery_no_auth(hass: HomeAssistant) -> None: """Test successful discover, but failed auth.""" - with patch( - "homeassistant.components.roon.config_flow.RoonApi", - return_value=RoonApiMockNoToken(), - ), patch( - "homeassistant.components.roon.config_flow.RoonDiscovery", - return_value=RoonDiscoveryMock(), - ), patch( - "homeassistant.components.roon.config_flow.TIMEOUT", - 0, - ), patch( - "homeassistant.components.roon.config_flow.AUTHENTICATE_TIMEOUT", - 0.01, - ), patch( - "homeassistant.components.roon.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.roon.config_flow.RoonApi", + return_value=RoonApiMockNoToken(), + ), + patch( + "homeassistant.components.roon.config_flow.RoonDiscovery", + return_value=RoonDiscoveryMock(), + ), + patch( + "homeassistant.components.roon.config_flow.TIMEOUT", + 0, + ), + patch( + "homeassistant.components.roon.config_flow.AUTHENTICATE_TIMEOUT", + 0.01, + ), + patch( + "homeassistant.components.roon.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -227,15 +244,19 @@ async def test_successful_discovery_no_auth(hass: HomeAssistant) -> None: async def test_unexpected_exception(hass: HomeAssistant) -> None: """Test successful discover, and unexpected exception during auth.""" - with patch( - "homeassistant.components.roon.config_flow.RoonApi", - return_value=RoonApiMockException(), - ), patch( - "homeassistant.components.roon.config_flow.RoonDiscovery", - return_value=RoonDiscoveryMock(), - ), patch( - "homeassistant.components.roon.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.roon.config_flow.RoonApi", + return_value=RoonApiMockException(), + ), + patch( + "homeassistant.components.roon.config_flow.RoonDiscovery", + return_value=RoonDiscoveryMock(), + ), + patch( + "homeassistant.components.roon.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} diff --git a/tests/components/rova/conftest.py b/tests/components/rova/conftest.py index 99dcd29fdf3..51cb94c64f7 100644 --- a/tests/components/rova/conftest.py +++ b/tests/components/rova/conftest.py @@ -10,9 +10,12 @@ def mock_rova(): """Mock a successful Rova API.""" api = MagicMock() - with patch( - "homeassistant.components.rova.config_flow.Rova", - return_value=api, - ) as api, patch("homeassistant.components.rova.Rova", return_value=api): + with ( + patch( + "homeassistant.components.rova.config_flow.Rova", + return_value=api, + ) as api, + patch("homeassistant.components.rova.Rova", return_value=api), + ): api.is_rova_area.return_value = True yield api diff --git a/tests/components/rtsp_to_webrtc/conftest.py b/tests/components/rtsp_to_webrtc/conftest.py index edb8c7c4aca..e968df9d860 100644 --- a/tests/components/rtsp_to_webrtc/conftest.py +++ b/tests/components/rtsp_to_webrtc/conftest.py @@ -45,12 +45,15 @@ async def mock_camera(hass) -> AsyncGenerator[None, None]: hass, "camera", {camera.DOMAIN: {"platform": "demo"}} ) await hass.async_block_till_done() - with patch( - "homeassistant.components.demo.camera.Path.read_bytes", - return_value=b"Test", - ), patch( - "homeassistant.components.camera.Camera.stream_source", - return_value=STREAM_SOURCE, + with ( + patch( + "homeassistant.components.demo.camera.Path.read_bytes", + return_value=b"Test", + ), + patch( + "homeassistant.components.camera.Camera.stream_source", + return_value=STREAM_SOURCE, + ), ): yield diff --git a/tests/components/rtsp_to_webrtc/test_config_flow.py b/tests/components/rtsp_to_webrtc/test_config_flow.py index 13885f06d3e..16d4779a92c 100644 --- a/tests/components/rtsp_to_webrtc/test_config_flow.py +++ b/tests/components/rtsp_to_webrtc/test_config_flow.py @@ -26,10 +26,13 @@ async def test_web_full_flow(hass: HomeAssistant) -> None: assert result.get("step_id") == "user" assert result.get("data_schema").schema.get("server_url") == str assert not result.get("errors") - with patch("rtsp_to_webrtc.client.Client.heartbeat"), patch( - "homeassistant.components.rtsp_to_webrtc.async_setup_entry", - return_value=True, - ) as mock_setup: + with ( + patch("rtsp_to_webrtc.client.Client.heartbeat"), + patch( + "homeassistant.components.rtsp_to_webrtc.async_setup_entry", + return_value=True, + ) as mock_setup, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"server_url": "https://example.com"} ) @@ -131,10 +134,13 @@ async def test_hassio_discovery(hass: HomeAssistant) -> None: assert result.get("step_id") == "hassio_confirm" assert result.get("description_placeholders") == {"addon": "RTSPtoWebRTC"} - with patch("rtsp_to_webrtc.client.Client.heartbeat"), patch( - "homeassistant.components.rtsp_to_webrtc.async_setup_entry", - return_value=True, - ) as mock_setup: + with ( + patch("rtsp_to_webrtc.client.Client.heartbeat"), + patch( + "homeassistant.components.rtsp_to_webrtc.async_setup_entry", + return_value=True, + ) as mock_setup, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} ) diff --git a/tests/components/ruckus_unleashed/test_config_flow.py b/tests/components/ruckus_unleashed/test_config_flow.py index c9f321a1da6..ae0ccb0a9b1 100644 --- a/tests/components/ruckus_unleashed/test_config_flow.py +++ b/tests/components/ruckus_unleashed/test_config_flow.py @@ -40,10 +40,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with RuckusAjaxApiPatchContext(), patch( - "homeassistant.components.ruckus_unleashed.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + RuckusAjaxApiPatchContext(), + patch( + "homeassistant.components.ruckus_unleashed.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG, diff --git a/tests/components/rympro/test_config_flow.py b/tests/components/rympro/test_config_flow.py index e61384107cb..f5591d8e0c7 100644 --- a/tests/components/rympro/test_config_flow.py +++ b/tests/components/rympro/test_config_flow.py @@ -44,16 +44,20 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.rympro.config_flow.RymPro.login", - return_value="test-token", - ), patch( - "homeassistant.components.rympro.config_flow.RymPro.account_info", - return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, - ), patch( - "homeassistant.components.rympro.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rympro.config_flow.RymPro.login", + return_value="test-token", + ), + patch( + "homeassistant.components.rympro.config_flow.RymPro.account_info", + return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, + ), + patch( + "homeassistant.components.rympro.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -98,16 +102,20 @@ async def test_login_error(hass: HomeAssistant, exception, error) -> None: assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": error} - with patch( - "homeassistant.components.rympro.config_flow.RymPro.login", - return_value="test-token", - ), patch( - "homeassistant.components.rympro.config_flow.RymPro.account_info", - return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, - ), patch( - "homeassistant.components.rympro.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rympro.config_flow.RymPro.login", + return_value="test-token", + ), + patch( + "homeassistant.components.rympro.config_flow.RymPro.account_info", + return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, + ), + patch( + "homeassistant.components.rympro.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -129,12 +137,15 @@ async def test_form_already_exists(hass: HomeAssistant, config_entry) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.rympro.config_flow.RymPro.login", - return_value="test-token", - ), patch( - "homeassistant.components.rympro.config_flow.RymPro.account_info", - return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, + with ( + patch( + "homeassistant.components.rympro.config_flow.RymPro.login", + return_value="test-token", + ), + patch( + "homeassistant.components.rympro.config_flow.RymPro.account_info", + return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -163,16 +174,20 @@ async def test_form_reauth(hass: HomeAssistant, config_entry) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.rympro.config_flow.RymPro.login", - return_value="test-token", - ), patch( - "homeassistant.components.rympro.config_flow.RymPro.account_info", - return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, - ), patch( - "homeassistant.components.rympro.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rympro.config_flow.RymPro.login", + return_value="test-token", + ), + patch( + "homeassistant.components.rympro.config_flow.RymPro.account_info", + return_value={"accountNumber": TEST_DATA[CONF_UNIQUE_ID]}, + ), + patch( + "homeassistant.components.rympro.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -202,16 +217,20 @@ async def test_form_reauth_with_new_account(hass: HomeAssistant, config_entry) - assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.rympro.config_flow.RymPro.login", - return_value="test-token", - ), patch( - "homeassistant.components.rympro.config_flow.RymPro.account_info", - return_value={"accountNumber": "new-account-number"}, - ), patch( - "homeassistant.components.rympro.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.rympro.config_flow.RymPro.login", + return_value="test-token", + ), + patch( + "homeassistant.components.rympro.config_flow.RymPro.account_info", + return_value={"accountNumber": "new-account-number"}, + ), + patch( + "homeassistant.components.rympro.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index ad0c3a21db6..8bef7317918 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -41,14 +41,16 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture(autouse=True) async def silent_ssdp_scanner(hass): """Start SSDP component and get Scanner, prevent actual SSDP traffic.""" - with patch( - "homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners" - ), patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), patch( - "homeassistant.components.ssdp.Scanner.async_scan" - ), patch( - "homeassistant.components.ssdp.Server._async_start_upnp_servers", - ), patch( - "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + with ( + patch("homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner.async_scan"), + patch( + "homeassistant.components.ssdp.Server._async_start_upnp_servers", + ), + patch( + "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + ), ): yield diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 78f72dfd299..a300c28b945 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -392,12 +392,15 @@ async def test_user_legacy_not_supported(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("rest_api", "remoteencws_failing") async def test_user_websocket_not_supported(hass: HomeAssistant) -> None: """Test starting a flow by user for not supported device.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=WebSocketProtocolError("Boom"), + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=WebSocketProtocolError("Boom"), + ), ): # websocket device not supported result = await hass.config_entries.flow.async_init( @@ -412,12 +415,15 @@ async def test_user_websocket_access_denied( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test starting a flow by user for not supported device.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=ConnectionClosedError(rcvd=None, sent=frames.Close(1002, "")), + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=ConnectionClosedError(rcvd=None, sent=frames.Close(1002, "")), + ), ): # websocket device not supported result = await hass.config_entries.flow.async_init( @@ -431,12 +437,15 @@ async def test_user_websocket_access_denied( @pytest.mark.usefixtures("rest_api", "remoteencws_failing") async def test_user_websocket_auth_retry(hass: HomeAssistant) -> None: """Test starting a flow by user for not supported device.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=UnauthorizedError, + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=UnauthorizedError, + ), ): # websocket device not supported result = await hass.config_entries.flow.async_init( @@ -445,11 +454,14 @@ async def test_user_websocket_auth_retry(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "pairing" assert result["errors"] == {"base": "auth_missing"} - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={} @@ -466,12 +478,15 @@ async def test_user_websocket_auth_retry(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("rest_api_failing") async def test_user_not_successful(hass: HomeAssistant) -> None: """Test starting a flow by user but no connection found.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=OSError("Boom"), + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=OSError("Boom"), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA @@ -483,12 +498,15 @@ async def test_user_not_successful(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("rest_api_failing") async def test_user_not_successful_2(hass: HomeAssistant) -> None: """Test starting a flow by user but no connection found.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=ConnectionFailure("Boom"), + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=ConnectionFailure("Boom"), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA @@ -757,16 +775,19 @@ async def test_ssdp_encrypted_websocket_not_supported( @pytest.mark.usefixtures("rest_api_failing") async def test_ssdp_websocket_cannot_connect(hass: HomeAssistant) -> None: """Test starting a flow from discovery and we cannot connect.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening", - side_effect=WebSocketProtocolError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote", - ) as remotews, patch.object( - remotews, "open", side_effect=WebSocketProtocolError("Boom") + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening", + side_effect=WebSocketProtocolError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote", + ) as remotews, + patch.object(remotews, "open", side_effect=WebSocketProtocolError("Boom")), ): # device not supported result = await hass.config_entries.flow.async_init( @@ -793,15 +814,19 @@ async def test_ssdp_model_not_supported(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("remoteencws_failing") async def test_ssdp_not_successful(hass: HomeAssistant) -> None: """Test starting a flow from discovery but no device found.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", - return_value=MOCK_DEVICE_INFO, + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", + return_value=MOCK_DEVICE_INFO, + ), ): # confirm to add the entry result = await hass.config_entries.flow.async_init( @@ -821,15 +846,19 @@ async def test_ssdp_not_successful(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("remoteencws_failing") async def test_ssdp_not_successful_2(hass: HomeAssistant) -> None: """Test starting a flow from discovery but no device found.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=ConnectionFailure("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", - return_value=MOCK_DEVICE_INFO, + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=ConnectionFailure("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", + return_value=MOCK_DEVICE_INFO, + ), ): # confirm to add the entry result = await hass.config_entries.flow.async_init( @@ -1039,14 +1068,18 @@ async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("remoteencws_failing") async def test_autodetect_websocket(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote" - ) as remotews, patch( - "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest", - ) as rest_api_class: + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote" + ) as remotews, + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest", + ) as rest_api_class, + ): remote = Mock(SamsungTVWSAsyncRemote) remote.__aenter__ = AsyncMock(return_value=remote) remote.__aexit__ = AsyncMock(return_value=False) @@ -1086,14 +1119,18 @@ async def test_autodetect_websocket(hass: HomeAssistant) -> None: async def test_websocket_no_mac(hass: HomeAssistant, mac_address: Mock) -> None: """Test for send key with autodetection of protocol.""" mac_address.return_value = "gg:ee:tt:mm:aa:cc" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote" - ) as remotews, patch( - "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest", - ) as rest_api_class: + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote" + ) as remotews, + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest", + ) as rest_api_class, + ): remote = Mock(SamsungTVWSAsyncRemote) remote.__aenter__ = AsyncMock(return_value=remote) remote.__aexit__ = AsyncMock(return_value=False) @@ -1189,13 +1226,16 @@ async def test_autodetect_legacy(hass: HomeAssistant) -> None: async def test_autodetect_none(hass: HomeAssistant) -> None: """Test for send key with autodetection of protocol.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", - side_effect=OSError("Boom"), - ) as remote, patch( - "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest.rest_device_info", - side_effect=ResponseError, - ) as rest_device_info: + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=OSError("Boom"), + ) as remote, + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest.rest_device_info", + side_effect=ResponseError, + ) as rest_device_info, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -1618,12 +1658,15 @@ async def test_update_legacy_missing_mac_from_dhcp_no_unique_id( data=MOCK_LEGACY_ENTRY, ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.samsungtv.bridge.Remote.__enter__", - return_value=True, - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening", - side_effect=WebSocketProtocolError("Boom"), + with ( + patch( + "homeassistant.components.samsungtv.bridge.Remote.__enter__", + return_value=True, + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening", + side_effect=WebSocketProtocolError("Boom"), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index fa7aec9a421..5bf8f2cacac 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -76,17 +76,20 @@ async def test_setup(hass: HomeAssistant) -> None: async def test_setup_without_port_device_offline(hass: HomeAssistant) -> None: """Test import from yaml when the device is offline.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening", - side_effect=OSError, - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", - side_effect=OSError, - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", - return_value=None, + with ( + patch("homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVEncryptedWSAsyncRemote.start_listening", + side_effect=OSError, + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote.open", + side_effect=OSError, + ), + patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.async_device_info", + return_value=None, + ), ): await setup_samsungtv_entry(hass, MOCK_CONFIG) diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 4cfb2195310..f874b92305b 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -306,11 +306,14 @@ async def test_update_off_ws_with_power_state( mock_now: datetime, ) -> None: """Testing update tv off.""" - with patch.object( - rest_api, "rest_device_info", side_effect=HttpApiError - ) as mock_device_info, patch.object( - remotews, "start_listening", side_effect=WebSocketException("Boom") - ) as mock_start_listening: + with ( + patch.object( + rest_api, "rest_device_info", side_effect=HttpApiError + ) as mock_device_info, + patch.object( + remotews, "start_listening", side_effect=WebSocketException("Boom") + ) as mock_start_listening, + ): await setup_samsungtv_entry(hass, MOCK_CONFIGWS) mock_device_info.assert_called_once() @@ -434,11 +437,14 @@ async def test_update_ws_connection_failure( """Testing update tv connection failure exception.""" await setup_samsungtv_entry(hass, MOCK_CONFIGWS) - with patch.object( - remotews, - "start_listening", - side_effect=ConnectionFailure('{"event": "ms.voiceApp.hide"}'), - ), patch.object(remotews, "is_alive", return_value=False): + with ( + patch.object( + remotews, + "start_listening", + side_effect=ConnectionFailure('{"event": "ms.voiceApp.hide"}'), + ), + patch.object(remotews, "is_alive", return_value=False), + ): next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) @@ -464,9 +470,12 @@ async def test_update_ws_connection_closed( """Testing update tv connection failure exception.""" await setup_samsungtv_entry(hass, MOCK_CONFIGWS) - with patch.object( - remotews, "start_listening", side_effect=ConnectionClosedError(None, None) - ), patch.object(remotews, "is_alive", return_value=False): + with ( + patch.object( + remotews, "start_listening", side_effect=ConnectionClosedError(None, None) + ), + patch.object(remotews, "is_alive", return_value=False), + ): next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) @@ -486,9 +495,10 @@ async def test_update_ws_unauthorized_error( """Testing update tv unauthorized failure exception.""" await setup_samsungtv_entry(hass, MOCK_CONFIGWS) - with patch.object( - remotews, "start_listening", side_effect=UnauthorizedError - ), patch.object(remotews, "is_alive", return_value=False): + with ( + patch.object(remotews, "start_listening", side_effect=UnauthorizedError), + patch.object(remotews, "is_alive", return_value=False), + ): next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) @@ -1438,9 +1448,12 @@ async def test_upnp_re_subscribe_events( assert dmr_device.async_subscribe_services.call_count == 1 assert dmr_device.async_unsubscribe_services.call_count == 0 - with patch.object( - remotews, "start_listening", side_effect=WebSocketException("Boom") - ), patch.object(remotews, "is_alive", return_value=False): + with ( + patch.object( + remotews, "start_listening", side_effect=WebSocketException("Boom") + ), + patch.object(remotews, "is_alive", return_value=False), + ): next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) @@ -1484,9 +1497,12 @@ async def test_upnp_failed_re_subscribe_events( assert dmr_device.async_subscribe_services.call_count == 1 assert dmr_device.async_unsubscribe_services.call_count == 0 - with patch.object( - remotews, "start_listening", side_effect=WebSocketException("Boom") - ), patch.object(remotews, "is_alive", return_value=False): + with ( + patch.object( + remotews, "start_listening", side_effect=WebSocketException("Boom") + ), + patch.object(remotews, "is_alive", return_value=False), + ): next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) diff --git a/tests/components/scrape/test_config_flow.py b/tests/components/scrape/test_config_flow.py index 6baecae50c5..8e281e148fc 100644 --- a/tests/components/scrape/test_config_flow.py +++ b/tests/components/scrape/test_config_flow.py @@ -331,9 +331,12 @@ async def test_options_add_remove_sensor_flow( assert result["step_id"] == "add_sensor" mocker = MockRestData("test_scrape_sensor2") - with patch("homeassistant.components.rest.RestData", return_value=mocker), patch( - "homeassistant.components.scrape.config_flow.uuid.uuid1", - return_value=uuid.UUID("3699ef88-69e6-11ed-a1eb-0242ac120003"), + with ( + patch("homeassistant.components.rest.RestData", return_value=mocker), + patch( + "homeassistant.components.scrape.config_flow.uuid.uuid1", + return_value=uuid.UUID("3699ef88-69e6-11ed-a1eb-0242ac120003"), + ), ): result = await hass.config_entries.options.async_configure( result["flow_id"], diff --git a/tests/components/screenlogic/test_config_flow.py b/tests/components/screenlogic/test_config_flow.py index 81913e76e27..be1617e3105 100644 --- a/tests/components/screenlogic/test_config_flow.py +++ b/tests/components/screenlogic/test_config_flow.py @@ -100,12 +100,15 @@ async def test_flow_discover_error(hass: HomeAssistant) -> None: assert result["errors"] == {} assert result["step_id"] == "gateway_entry" - with patch( - "homeassistant.components.screenlogic.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.screenlogic.config_flow.login.async_get_mac_address", - return_value="00-C0-33-01-01-01", + with ( + patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.screenlogic.config_flow.login.async_get_mac_address", + return_value="00-C0-33-01-01-01", + ), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -141,12 +144,15 @@ async def test_dhcp(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["step_id"] == "gateway_entry" - with patch( - "homeassistant.components.screenlogic.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.screenlogic.config_flow.login.async_get_mac_address", - return_value="00-C0-33-01-01-01", + with ( + patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.screenlogic.config_flow.login.async_get_mac_address", + return_value="00-C0-33-01-01-01", + ), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -196,12 +202,15 @@ async def test_form_manual_entry(hass: HomeAssistant) -> None: assert result2["errors"] == {} assert result2["step_id"] == "gateway_entry" - with patch( - "homeassistant.components.screenlogic.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.screenlogic.config_flow.login.async_get_mac_address", - return_value="00-C0-33-01-01-01", + with ( + patch( + "homeassistant.components.screenlogic.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.screenlogic.config_flow.login.async_get_mac_address", + return_value="00-C0-33-01-01-01", + ), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/screenlogic/test_data.py b/tests/components/screenlogic/test_data.py index a14b2f93169..d17db6c5b33 100644 --- a/tests/components/screenlogic/test_data.py +++ b/tests/components/screenlogic/test_data.py @@ -53,16 +53,19 @@ async def test_async_cleanup_entries( assert unused_entity assert unused_entity.unique_id == TEST_UNUSED_ENTRY["unique_id"] - with patch( - GATEWAY_DISCOVERY_IMPORT_PATH, - return_value={}, - ), patch.multiple( - ScreenLogicGateway, - async_connect=lambda *args, **kwargs: stub_async_connect( - DATA_MIN_ENTITY_CLEANUP, *args, **kwargs + with ( + patch( + GATEWAY_DISCOVERY_IMPORT_PATH, + return_value={}, + ), + patch.multiple( + ScreenLogicGateway, + async_connect=lambda *args, **kwargs: stub_async_connect( + DATA_MIN_ENTITY_CLEANUP, *args, **kwargs + ), + is_connected=True, + _async_connected_request=DEFAULT, ), - is_connected=True, - _async_connected_request=DEFAULT, ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/screenlogic/test_diagnostics.py b/tests/components/screenlogic/test_diagnostics.py index b66aa26a203..0b587bcd0e5 100644 --- a/tests/components/screenlogic/test_diagnostics.py +++ b/tests/components/screenlogic/test_diagnostics.py @@ -35,17 +35,20 @@ async def test_diagnostics( config_entry_id=mock_config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, MOCK_ADAPTER_MAC)}, ) - with patch( - GATEWAY_DISCOVERY_IMPORT_PATH, - return_value={}, - ), patch.multiple( - ScreenLogicGateway, - async_connect=lambda *args, **kwargs: stub_async_connect( - DATA_FULL_CHEM, *args, **kwargs + with ( + patch( + GATEWAY_DISCOVERY_IMPORT_PATH, + return_value={}, + ), + patch.multiple( + ScreenLogicGateway, + async_connect=lambda *args, **kwargs: stub_async_connect( + DATA_FULL_CHEM, *args, **kwargs + ), + is_connected=True, + _async_connected_request=DEFAULT, + get_debug=lambda self: {}, ), - is_connected=True, - _async_connected_request=DEFAULT, - get_debug=lambda self: {}, ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/screenlogic/test_init.py b/tests/components/screenlogic/test_init.py index 5b75517da6e..9c296fd8afd 100644 --- a/tests/components/screenlogic/test_init.py +++ b/tests/components/screenlogic/test_init.py @@ -157,14 +157,17 @@ async def test_async_migrate_entries( assert entity.unique_id == old_uid assert entity.entity_id == old_eid - with patch( - GATEWAY_DISCOVERY_IMPORT_PATH, - return_value={}, - ), patch.multiple( - ScreenLogicGateway, - async_connect=MIGRATION_CONNECT, - is_connected=True, - _async_connected_request=DEFAULT, + with ( + patch( + GATEWAY_DISCOVERY_IMPORT_PATH, + return_value={}, + ), + patch.multiple( + ScreenLogicGateway, + async_connect=MIGRATION_CONNECT, + is_connected=True, + _async_connected_request=DEFAULT, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -216,23 +219,27 @@ async def test_entity_migration_data( ) # This patch simulates bad data being added to ENTITY_MIGRATIONS - with patch.dict( - "homeassistant.components.screenlogic.data.ENTITY_MIGRATIONS", - { - "missing_device": { - "new_key": "state", - "old_name": "Missing Migration Device", - "new_name": "Bad ENTITY_MIGRATIONS Entry", + with ( + patch.dict( + "homeassistant.components.screenlogic.data.ENTITY_MIGRATIONS", + { + "missing_device": { + "new_key": "state", + "old_name": "Missing Migration Device", + "new_name": "Bad ENTITY_MIGRATIONS Entry", + }, }, - }, - ), patch( - GATEWAY_DISCOVERY_IMPORT_PATH, - return_value={}, - ), patch.multiple( - ScreenLogicGateway, - async_connect=MIGRATION_CONNECT, - is_connected=True, - _async_connected_request=DEFAULT, + ), + patch( + GATEWAY_DISCOVERY_IMPORT_PATH, + return_value={}, + ), + patch.multiple( + ScreenLogicGateway, + async_connect=MIGRATION_CONNECT, + is_connected=True, + _async_connected_request=DEFAULT, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() @@ -264,14 +271,17 @@ async def test_platform_setup( mock_config_entry.add_to_hass(hass) - with patch( - GATEWAY_DISCOVERY_IMPORT_PATH, - return_value={}, - ), patch.multiple( - ScreenLogicGateway, - async_connect=stub_connect, - is_connected=True, - _async_connected_request=DEFAULT, + with ( + patch( + GATEWAY_DISCOVERY_IMPORT_PATH, + return_value={}, + ), + patch.multiple( + ScreenLogicGateway, + async_connect=stub_connect, + is_connected=True, + _async_connected_request=DEFAULT, + ), ): assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/sensibo/conftest.py b/tests/components/sensibo/conftest.py index 01f77d7d40c..d98b19c3833 100644 --- a/tests/components/sensibo/conftest.py +++ b/tests/components/sensibo/conftest.py @@ -34,15 +34,19 @@ async def load_int(hass: HomeAssistant, get_data: SensiboData) -> MockConfigEntr config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/sensibo/test_button.py b/tests/components/sensibo/test_button.py index e94a3c00fd5..6d7ce442562 100644 --- a/tests/components/sensibo/test_button.py +++ b/tests/components/sensibo/test_button.py @@ -42,12 +42,15 @@ async def test_button( today_str = today.isoformat() freezer.move_to(today) - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_reset_filter", - return_value={"status": "success"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_reset_filter", + return_value={"status": "success"}, + ), ): await hass.services.async_call( BUTTON_DOMAIN, @@ -94,14 +97,18 @@ async def test_button_failure( state_button = hass.states.get("button.hallway_reset_filter") - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_reset_filter", - return_value={"status": "failure"}, - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_reset_filter", + return_value={"status": "failure"}, + ), + pytest.raises( + HomeAssistantError, + ), ): await hass.services.async_call( BUTTON_DOMAIN, diff --git a/tests/components/sensibo/test_climate.py b/tests/components/sensibo/test_climate.py index 9634abc66fc..061e31f9771 100644 --- a/tests/components/sensibo/test_climate.py +++ b/tests/components/sensibo/test_climate.py @@ -121,12 +121,15 @@ async def test_climate_fan( state1 = hass.states.get("climate.hallway") assert state1.attributes["fan_mode"] == "high" - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -162,9 +165,12 @@ async def test_climate_fan( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, @@ -188,12 +194,15 @@ async def test_climate_swing( state1 = hass.states.get("climate.hallway") assert state1.attributes["swing_mode"] == "stopped" - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -228,9 +237,12 @@ async def test_climate_swing( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_SWING_MODE, @@ -254,12 +266,15 @@ async def test_climate_temperatures( state1 = hass.states.get("climate.hallway") assert state1.attributes["temperature"] == 25 - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -272,12 +287,15 @@ async def test_climate_temperatures( state2 = hass.states.get("climate.hallway") assert state2.attributes["temperature"] == 20 - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -290,12 +308,15 @@ async def test_climate_temperatures( state2 = hass.states.get("climate.hallway") assert state2.attributes["temperature"] == 16 - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -308,12 +329,15 @@ async def test_climate_temperatures( state2 = hass.states.get("climate.hallway") assert state2.attributes["temperature"] == 19 - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -326,12 +350,15 @@ async def test_climate_temperatures( state2 = hass.states.get("climate.hallway") assert state2.attributes["temperature"] == 20 - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -344,10 +371,13 @@ async def test_climate_temperatures( state2 = hass.states.get("climate.hallway") assert state2.attributes["temperature"] == 20 - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, - ), pytest.raises(MultipleInvalid): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), + pytest.raises(MultipleInvalid), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -381,10 +411,13 @@ async def test_climate_temperatures( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -437,9 +470,12 @@ async def test_climate_temperature_is_none( state1 = hass.states.get("climate.hallway") assert state1.attributes["temperature"] == 25 - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - ), pytest.raises(ServiceValidationError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ), + pytest.raises(ServiceValidationError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -491,12 +527,15 @@ async def test_climate_hvac_mode( state1 = hass.states.get("climate.hallway") assert state1.state == "heat" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -520,12 +559,15 @@ async def test_climate_hvac_mode( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -562,12 +604,15 @@ async def test_climate_on_off( state1 = hass.states.get("climate.hallway") assert state1.state == "heat" - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -580,12 +625,15 @@ async def test_climate_on_off( state2 = hass.states.get("climate.hallway") assert state2.state == "off" - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( CLIMATE_DOMAIN, @@ -622,10 +670,15 @@ async def test_climate_service_failed( state1 = hass.states.get("climate.hallway") assert state1.state == "heat" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Error", "failureReason": "Did not work"}}, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={ + "result": {"status": "Error", "failureReason": "Did not work"} + }, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_OFF, @@ -661,12 +714,15 @@ async def test_climate_assumed_state( state1 = hass.states.get("climate.hallway") assert state1.state == "heat" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( DOMAIN, @@ -736,14 +792,18 @@ async def test_climate_set_timer( state_climate = hass.states.get("climate.hallway") assert hass.states.get("sensor.hallway_timer_end_time").state == STATE_UNKNOWN - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", - return_value={"status": "failure"}, - ), pytest.raises( - MultipleInvalid, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", + return_value={"status": "failure"}, + ), + pytest.raises( + MultipleInvalid, + ), ): await hass.services.async_call( DOMAIN, @@ -755,14 +815,18 @@ async def test_climate_set_timer( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", - return_value={"status": "failure"}, - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", + return_value={"status": "failure"}, + ), + pytest.raises( + HomeAssistantError, + ), ): await hass.services.async_call( DOMAIN, @@ -775,12 +839,15 @@ async def test_climate_set_timer( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", - return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", + return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}}, + ), ): await hass.services.async_call( DOMAIN, @@ -841,12 +908,16 @@ async def test_climate_pure_boost( state2 = hass.states.get("switch.kitchen_pure_boost") assert state2.state == "off" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", - ), pytest.raises( - MultipleInvalid, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", + ), + pytest.raises( + MultipleInvalid, + ), ): await hass.services.async_call( DOMAIN, @@ -861,22 +932,25 @@ async def test_climate_pure_boost( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", - return_value={ - "status": "success", - "result": { - "enabled": True, - "sensitivity": "S", - "measurements_integration": True, - "ac_integration": False, - "geo_integration": False, - "prime_integration": True, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", + return_value={ + "status": "success", + "result": { + "enabled": True, + "sensitivity": "S", + "measurements_integration": True, + "ac_integration": False, + "geo_integration": False, + "prime_integration": True, + }, }, - }, + ), ): await hass.services.async_call( DOMAIN, @@ -943,12 +1017,16 @@ async def test_climate_climate_react( state_climate = hass.states.get("climate.hallway") - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", - ), pytest.raises( - MultipleInvalid, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", + ), + pytest.raises( + MultipleInvalid, + ), ): await hass.services.async_call( DOMAIN, @@ -963,41 +1041,44 @@ async def test_climate_climate_react( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", - return_value={ - "status": "success", - "result": { - "enabled": True, - "deviceUid": "ABC999111", - "highTemperatureState": { - "on": True, - "targetTemperature": 15, - "temperatureUnit": "C", - "mode": "cool", - "fanLevel": "high", - "swing": "stopped", - "horizontalSwing": "stopped", - "light": "on", + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", + return_value={ + "status": "success", + "result": { + "enabled": True, + "deviceUid": "ABC999111", + "highTemperatureState": { + "on": True, + "targetTemperature": 15, + "temperatureUnit": "C", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "highTemperatureThreshold": 30.5, + "lowTemperatureState": { + "on": True, + "targetTemperature": 25, + "temperatureUnit": "C", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "lowTemperatureThreshold": 5.5, + "type": "temperature", }, - "highTemperatureThreshold": 30.5, - "lowTemperatureState": { - "on": True, - "targetTemperature": 25, - "temperatureUnit": "C", - "mode": "heat", - "fanLevel": "low", - "swing": "stopped", - "horizontalSwing": "stopped", - "light": "on", - }, - "lowTemperatureThreshold": 5.5, - "type": "temperature", }, - }, + ), ): await hass.services.async_call( DOMAIN, @@ -1106,41 +1187,44 @@ async def test_climate_climate_react_fahrenheit( state_climate = hass.states.get("climate.hallway") - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", - return_value={ - "status": "success", - "result": { - "enabled": True, - "deviceUid": "ABC999111", - "highTemperatureState": { - "on": True, - "targetTemperature": 65, - "temperatureUnit": "F", - "mode": "cool", - "fanLevel": "high", - "swing": "stopped", - "horizontalSwing": "stopped", - "light": "on", + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_climate_react", + return_value={ + "status": "success", + "result": { + "enabled": True, + "deviceUid": "ABC999111", + "highTemperatureState": { + "on": True, + "targetTemperature": 65, + "temperatureUnit": "F", + "mode": "cool", + "fanLevel": "high", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "highTemperatureThreshold": 77, + "lowTemperatureState": { + "on": True, + "targetTemperature": 85, + "temperatureUnit": "F", + "mode": "heat", + "fanLevel": "low", + "swing": "stopped", + "horizontalSwing": "stopped", + "light": "on", + }, + "lowTemperatureThreshold": 32, + "type": "temperature", }, - "highTemperatureThreshold": 77, - "lowTemperatureState": { - "on": True, - "targetTemperature": 85, - "temperatureUnit": "F", - "mode": "heat", - "fanLevel": "low", - "swing": "stopped", - "horizontalSwing": "stopped", - "light": "on", - }, - "lowTemperatureThreshold": 32, - "type": "temperature", }, - }, + ), ): await hass.services.async_call( DOMAIN, @@ -1250,12 +1334,16 @@ async def test_climate_full_ac_state( state_climate = hass.states.get("climate.hallway") assert state_climate.state == "heat" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_states", - ), pytest.raises( - MultipleInvalid, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_states", + ), + pytest.raises( + MultipleInvalid, + ), ): await hass.services.async_call( DOMAIN, @@ -1268,12 +1356,15 @@ async def test_climate_full_ac_state( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_states", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_states", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( DOMAIN, @@ -1329,9 +1420,12 @@ async def test_climate_fan_mode_and_swing_mode_not_supported( assert state1.attributes["fan_mode"] == "high" assert state1.attributes["swing_mode"] == "stopped" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - ), pytest.raises(ServiceValidationError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ), + pytest.raises(ServiceValidationError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_SWING_MODE, @@ -1339,9 +1433,12 @@ async def test_climate_fan_mode_and_swing_mode_not_supported( blocking=True, ) - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - ), pytest.raises(ServiceValidationError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + ), + pytest.raises(ServiceValidationError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, diff --git a/tests/components/sensibo/test_config_flow.py b/tests/components/sensibo/test_config_flow.py index 12c4e187550..3b1117f0908 100644 --- a/tests/components/sensibo/test_config_flow.py +++ b/tests/components/sensibo/test_config_flow.py @@ -29,16 +29,20 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, - ), patch( - "homeassistant.components.sensibo.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), + patch( + "homeassistant.components.sensibo.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -90,15 +94,19 @@ async def test_flow_fails( assert result2["errors"] == {"base": p_error} - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, - ), patch( - "homeassistant.components.sensibo.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), + patch( + "homeassistant.components.sensibo.async_setup_entry", + return_value=True, + ), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -124,12 +132,15 @@ async def test_flow_get_no_devices(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": []}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": []}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {}}, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -151,12 +162,15 @@ async def test_flow_get_no_username(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {}}, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -191,16 +205,20 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, - ) as mock_sensibo, patch( - "homeassistant.components.sensibo.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ) as mock_sensibo, + patch( + "homeassistant.components.sensibo.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: "1234567891"}, @@ -260,15 +278,19 @@ async def test_reauth_flow_error( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": p_error} - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, - ), patch( - "homeassistant.components.sensibo.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), + patch( + "homeassistant.components.sensibo.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -329,12 +351,15 @@ async def test_flow_reauth_no_username_or_device( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "reauth_confirm" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value=get_devices, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value=get_me, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value=get_devices, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value=get_me, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/sensibo/test_coordinator.py b/tests/components/sensibo/test_coordinator.py index ba07cec2eda..d81b7fd613c 100644 --- a/tests/components/sensibo/test_coordinator.py +++ b/tests/components/sensibo/test_coordinator.py @@ -35,14 +35,18 @@ async def test_coordinator( config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - ) as mock_data, patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + ) as mock_data, + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), ): monkeypatch.setattr(get_data.parsed["ABC999111"], "hvac_mode", "heat") monkeypatch.setattr(get_data.parsed["ABC999111"], "device_on", True) diff --git a/tests/components/sensibo/test_entity.py b/tests/components/sensibo/test_entity.py index dbd6b76d0bf..071e5473e5c 100644 --- a/tests/components/sensibo/test_entity.py +++ b/tests/components/sensibo/test_entity.py @@ -73,10 +73,13 @@ async def test_entity_failed_service_calls( state = hass.states.get("climate.hallway") assert state.attributes["fan_mode"] == "low" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - side_effect=p_error, - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + side_effect=p_error, + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, diff --git a/tests/components/sensibo/test_init.py b/tests/components/sensibo/test_init.py index 49aa629d159..9698d5241cc 100644 --- a/tests/components/sensibo/test_init.py +++ b/tests/components/sensibo/test_init.py @@ -32,15 +32,19 @@ async def test_setup_entry(hass: HomeAssistant, get_data: SensiboData) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -60,15 +64,19 @@ async def test_migrate_entry(hass: HomeAssistant, get_data: SensiboData) -> None ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -90,13 +98,17 @@ async def test_migrate_entry_fails(hass: HomeAssistant, get_data: SensiboData) - ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - side_effect=NoUsernameError("No username returned"), + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + side_effect=NoUsernameError("No username returned"), + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -118,15 +130,19 @@ async def test_unload_entry(hass: HomeAssistant, get_data: SensiboData) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", - return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_me", - return_value={"result": {"username": "username"}}, + with ( + patch( + "homeassistant.components.sensibo.coordinator.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices", + return_value={"result": [{"id": "xyzxyz"}, {"id": "abcabc"}]}, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_me", + return_value={"result": {"username": "username"}}, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/sensibo/test_number.py b/tests/components/sensibo/test_number.py index fc692b8ecc0..e0a5a6a8bde 100644 --- a/tests/components/sensibo/test_number.py +++ b/tests/components/sensibo/test_number.py @@ -63,12 +63,15 @@ async def test_number_set_value( state1 = hass.states.get("number.hallway_temperature_calibration") assert state1.state == "0.1" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", - return_value={"status": "failure"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", + return_value={"status": "failure"}, + ), ): with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -82,12 +85,15 @@ async def test_number_set_value( state2 = hass.states.get("number.hallway_temperature_calibration") assert state2.state == "0.1" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", - return_value={"status": "success"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_calibration", + return_value={"status": "success"}, + ), ): await hass.services.async_call( NUMBER_DOMAIN, diff --git a/tests/components/sensibo/test_select.py b/tests/components/sensibo/test_select.py index 6ff4afa5d47..7a9c89ef612 100644 --- a/tests/components/sensibo/test_select.py +++ b/tests/components/sensibo/test_select.py @@ -84,14 +84,18 @@ async def test_select_set_option( state1 = hass.states.get("select.hallway_horizontal_swing") assert state1.state == "stopped" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "failed"}}, - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "failed"}}, + ), + pytest.raises( + HomeAssistantError, + ), ): await hass.services.async_call( SELECT_DOMAIN, @@ -127,13 +131,19 @@ async def test_select_set_option( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Failed", "failureReason": "No connection"}}, - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={ + "result": {"status": "Failed", "failureReason": "No connection"} + }, + ), + pytest.raises( + HomeAssistantError, + ), ): await hass.services.async_call( SELECT_DOMAIN, @@ -146,12 +156,15 @@ async def test_select_set_option( state2 = hass.states.get("select.hallway_horizontal_swing") assert state2.state == "stopped" - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", - return_value={"result": {"status": "Success"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_ac_state_property", + return_value={"result": {"status": "Success"}}, + ), ): await hass.services.async_call( SELECT_DOMAIN, diff --git a/tests/components/sensibo/test_switch.py b/tests/components/sensibo/test_switch.py index b0f9a8d2a59..cc3c8881bec 100644 --- a/tests/components/sensibo/test_switch.py +++ b/tests/components/sensibo/test_switch.py @@ -37,12 +37,15 @@ async def test_switch_timer( assert state1.attributes["id"] is None assert state1.attributes["turn_on"] is None - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", - return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", + return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}}, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -71,12 +74,15 @@ async def test_switch_timer( assert state1.attributes["id"] == "SzTGE4oZ4D" assert state1.attributes["turn_on"] is False - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_del_timer", - return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_del_timer", + return_value={"status": "success", "result": {"id": "SzTGE4oZ4D"}}, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -115,12 +121,15 @@ async def test_switch_pure_boost( state1 = hass.states.get("switch.kitchen_pure_boost") assert state1.state == STATE_OFF - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", - return_value={"status": "success"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", + return_value={"status": "success"}, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -147,12 +156,15 @@ async def test_switch_pure_boost( state1 = hass.states.get("switch.kitchen_pure_boost") assert state1.state == STATE_ON - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", - return_value={"status": "success"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_pureboost", + return_value={"status": "success"}, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -190,14 +202,18 @@ async def test_switch_command_failure( state1 = hass.states.get("switch.hallway_timer") - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", - return_value={"status": "failure"}, - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_set_timer", + return_value={"status": "failure"}, + ), + pytest.raises( + HomeAssistantError, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -208,14 +224,18 @@ async def test_switch_command_failure( blocking=True, ) - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_del_timer", - return_value={"status": "failure"}, - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_del_timer", + return_value={"status": "failure"}, + ), + pytest.raises( + HomeAssistantError, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -238,12 +258,15 @@ async def test_switch_climate_react( state1 = hass.states.get("switch.hallway_climate_react") assert state1.state == STATE_OFF - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_enable_climate_react", - return_value={"status": "success"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_enable_climate_react", + return_value={"status": "success"}, + ), ): await hass.services.async_call( SWITCH_DOMAIN, @@ -269,12 +292,15 @@ async def test_switch_climate_react( state1 = hass.states.get("switch.hallway_climate_react") assert state1.state == STATE_ON - with patch( - "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", - return_value=get_data, - ), patch( - "homeassistant.components.sensibo.util.SensiboClient.async_enable_climate_react", - return_value={"status": "success"}, + with ( + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_get_devices_data", + return_value=get_data, + ), + patch( + "homeassistant.components.sensibo.util.SensiboClient.async_enable_climate_react", + return_value={"status": "success"}, + ), ): await hass.services.async_call( SWITCH_DOMAIN, diff --git a/tests/components/sensorpush/test_sensor.py b/tests/components/sensorpush/test_sensor.py index 1054f97fb12..cf69fc903f6 100644 --- a/tests/components/sensorpush/test_sensor.py +++ b/tests/components/sensorpush/test_sensor.py @@ -56,9 +56,12 @@ async def test_sensors(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/sentry/test_config_flow.py b/tests/components/sentry/test_config_flow.py index edcd87cd5d9..0c3fc45b68b 100644 --- a/tests/components/sentry/test_config_flow.py +++ b/tests/components/sentry/test_config_flow.py @@ -32,10 +32,13 @@ async def test_full_user_flow_implementation(hass: HomeAssistant) -> None: assert result.get("type") == FlowResultType.FORM assert result.get("errors") == {} - with patch("homeassistant.components.sentry.config_flow.Dsn"), patch( - "homeassistant.components.sentry.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.sentry.config_flow.Dsn"), + patch( + "homeassistant.components.sentry.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"dsn": "http://public@sentry.local/1"}, diff --git a/tests/components/sentry/test_init.py b/tests/components/sentry/test_init.py index b8c5c4cbde9..3dd1dcd9b46 100644 --- a/tests/components/sentry/test_init.py +++ b/tests/components/sentry/test_init.py @@ -30,15 +30,18 @@ async def test_setup_entry(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.sentry.AioHttpIntegration" - ) as sentry_aiohttp_mock, patch( - "homeassistant.components.sentry.SqlalchemyIntegration" - ) as sentry_sqlalchemy_mock, patch( - "homeassistant.components.sentry.LoggingIntegration" - ) as sentry_logging_mock, patch( - "homeassistant.components.sentry.sentry_sdk" - ) as sentry_mock: + with ( + patch( + "homeassistant.components.sentry.AioHttpIntegration" + ) as sentry_aiohttp_mock, + patch( + "homeassistant.components.sentry.SqlalchemyIntegration" + ) as sentry_sqlalchemy_mock, + patch( + "homeassistant.components.sentry.LoggingIntegration" + ) as sentry_logging_mock, + patch("homeassistant.components.sentry.sentry_sdk") as sentry_mock, + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -84,11 +87,12 @@ async def test_setup_entry_with_tracing(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch("homeassistant.components.sentry.AioHttpIntegration"), patch( - "homeassistant.components.sentry.SqlalchemyIntegration" - ), patch("homeassistant.components.sentry.LoggingIntegration"), patch( - "homeassistant.components.sentry.sentry_sdk" - ) as sentry_mock: + with ( + patch("homeassistant.components.sentry.AioHttpIntegration"), + patch("homeassistant.components.sentry.SqlalchemyIntegration"), + patch("homeassistant.components.sentry.LoggingIntegration"), + patch("homeassistant.components.sentry.sentry_sdk") as sentry_mock, + ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/sfr_box/test_button.py b/tests/components/sfr_box/test_button.py index a83c46dbd87..618ad6fc34b 100644 --- a/tests/components/sfr_box/test_button.py +++ b/tests/components/sfr_box/test_button.py @@ -20,9 +20,12 @@ pytestmark = pytest.mark.usefixtures("system_get_info", "dsl_get_info", "wan_get @pytest.fixture(autouse=True) def override_platforms() -> Generator[None, None, None]: """Override PLATFORMS_WITH_AUTH.""" - with patch( - "homeassistant.components.sfr_box.PLATFORMS_WITH_AUTH", [Platform.BUTTON] - ), patch("homeassistant.components.sfr_box.coordinator.SFRBox.authenticate"): + with ( + patch( + "homeassistant.components.sfr_box.PLATFORMS_WITH_AUTH", [Platform.BUTTON] + ), + patch("homeassistant.components.sfr_box.coordinator.SFRBox.authenticate"), + ): yield @@ -73,10 +76,13 @@ async def test_reboot(hass: HomeAssistant, config_entry_with_auth: ConfigEntry) # Reboot failed service_data = {ATTR_ENTITY_ID: "button.sfr_box_restart"} - with patch( - "homeassistant.components.sfr_box.button.SFRBox.system_reboot", - side_effect=SFRBoxError, - ) as mock_action, pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.sfr_box.button.SFRBox.system_reboot", + side_effect=SFRBoxError, + ) as mock_action, + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, service_data=service_data, blocking=True ) diff --git a/tests/components/sharkiq/test_config_flow.py b/tests/components/sharkiq/test_config_flow.py index da185968127..a81c185fd71 100644 --- a/tests/components/sharkiq/test_config_flow.py +++ b/tests/components/sharkiq/test_config_flow.py @@ -44,10 +44,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch("sharkiq.AylaApi.async_sign_in", return_value=True), patch( - "homeassistant.components.sharkiq.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("sharkiq.AylaApi.async_sign_in", return_value=True), + patch( + "homeassistant.components.sharkiq.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG, diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 89fb768db2d..93b06ddf9d8 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -251,9 +251,12 @@ async def test_do_not_run_forever( ) await hass.async_block_till_done() - with patch.object(shell_command, "COMMAND_TIMEOUT", 0.001), patch( - "homeassistant.components.shell_command.asyncio.create_subprocess_shell", - side_effect=mock_create_subprocess_shell, + with ( + patch.object(shell_command, "COMMAND_TIMEOUT", 0.001), + patch( + "homeassistant.components.shell_command.asyncio.create_subprocess_shell", + side_effect=mock_create_subprocess_shell, + ), ): with pytest.raises(TimeoutError): await hass.services.async_call( diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 9e8dd3999a6..9a73252ca6c 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -355,8 +355,9 @@ def _mock_rpc_device(version: str | None = None): @pytest.fixture async def mock_rpc_device(): """Mock rpc (Gen2, Websocket) device with BLE support.""" - with patch("aioshelly.rpc_device.RpcDevice.create") as rpc_device_mock, patch( - "homeassistant.components.shelly.bluetooth.async_start_scanner" + with ( + patch("aioshelly.rpc_device.RpcDevice.create") as rpc_device_mock, + patch("homeassistant.components.shelly.bluetooth.async_start_scanner"), ): def update(): diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 99b30062d43..1e7bbc01d6d 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -77,21 +77,25 @@ async def test_form( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={ - "mac": "test-mac", - "type": MODEL_1, - "auth": False, - "gen": gen, - "port": port, - }, - ), patch( - "homeassistant.components.shelly.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={ + "mac": "test-mac", + "type": MODEL_1, + "auth": False, + "gen": gen, + "port": port, + }, + ), + patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1", "port": port}, @@ -122,12 +126,15 @@ async def test_form_gen1_custom_port( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={"mac": "test-mac", "type": MODEL_1, "gen": 1}, - ), patch( - "aioshelly.block_device.BlockDevice.create", - side_effect=CustomPortNotSupported, + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={"mac": "test-mac", "type": MODEL_1, "gen": 1}, + ), + patch( + "aioshelly.block_device.BlockDevice.create", + side_effect=CustomPortNotSupported, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -189,12 +196,15 @@ async def test_form_auth( assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.shelly.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], user_input ) @@ -333,11 +343,14 @@ async def test_form_errors_test_connection( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={"mac": "test-mac", "auth": False}, - ), patch( - "aioshelly.block_device.BlockDevice.create", new=AsyncMock(side_effect=exc) + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={"mac": "test-mac", "auth": False}, + ), + patch( + "aioshelly.block_device.BlockDevice.create", new=AsyncMock(side_effect=exc) + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -393,15 +406,19 @@ async def test_user_setup_ignored_device( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={"mac": "test-mac", "type": MODEL_1, "auth": False}, - ), patch( - "homeassistant.components.shelly.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={"mac": "test-mac", "type": MODEL_1, "auth": False}, + ), + patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1"}, @@ -554,12 +571,15 @@ async def test_zeroconf( ) assert context["title_placeholders"]["name"] == "shelly1pm-12345" assert context["confirm_only"] is True - with patch( - "homeassistant.components.shelly.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -609,12 +629,15 @@ async def test_zeroconf_sleeping_device( if flow["flow_id"] == result["flow_id"] ) assert context["title_placeholders"]["name"] == "shelly1pm-12345" - with patch( - "homeassistant.components.shelly.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -635,17 +658,20 @@ async def test_zeroconf_sleeping_device( async def test_zeroconf_sleeping_device_error(hass: HomeAssistant) -> None: """Test sleeping device configuration via zeroconf with error.""" - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={ - "mac": "test-mac", - "type": MODEL_1, - "auth": False, - "sleep_mode": True, - }, - ), patch( - "aioshelly.block_device.BlockDevice.create", - new=AsyncMock(side_effect=DeviceConnectionError), + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={ + "mac": "test-mac", + "type": MODEL_1, + "auth": False, + "sleep_mode": True, + }, + ), + patch( + "aioshelly.block_device.BlockDevice.create", + new=AsyncMock(side_effect=DeviceConnectionError), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -778,12 +804,15 @@ async def test_zeroconf_require_auth( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.shelly.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.shelly.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "test username", "password": "test password"}, @@ -865,15 +894,19 @@ async def test_reauth_unsuccessful( ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={"mac": "test-mac", "type": MODEL_1, "auth": True, "gen": gen}, - ), patch( - "aioshelly.block_device.BlockDevice.create", - new=AsyncMock(side_effect=InvalidAuthError), - ), patch( - "aioshelly.rpc_device.RpcDevice.create", - new=AsyncMock(side_effect=InvalidAuthError), + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={"mac": "test-mac", "type": MODEL_1, "auth": True, "gen": gen}, + ), + patch( + "aioshelly.block_device.BlockDevice.create", + new=AsyncMock(side_effect=InvalidAuthError), + ), + patch( + "aioshelly.rpc_device.RpcDevice.create", + new=AsyncMock(side_effect=InvalidAuthError), + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -1167,12 +1200,16 @@ async def test_sleeping_device_gen2_with_new_firmware( assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.shelly.config_flow.get_info", - return_value={"mac": "test-mac", "gen": 2}, - ), patch("homeassistant.components.shelly.async_setup", return_value=True), patch( - "homeassistant.components.shelly.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.shelly.config_flow.get_info", + return_value={"mac": "test-mac", "gen": 2}, + ), + patch("homeassistant.components.shelly.async_setup", return_value=True), + patch( + "homeassistant.components.shelly.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index eb621a6e044..754f1111548 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -58,16 +58,20 @@ async def test_ip_address_with_only_default_interface( hass: HomeAssistant, mock_block_device: Mock, caplog: pytest.LogCaptureFixture ) -> None: """Test more local ip addresses with only the default interface..""" - with patch( - "homeassistant.components.network.async_only_default_interface_enabled", - return_value=True, - ), patch( - "homeassistant.components.network.async_get_enabled_source_ips", - return_value=[IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")], - ), patch( - "homeassistant.components.shelly.utils.COAP", - autospec=COAP, - ) as mock_coap_init: + with ( + patch( + "homeassistant.components.network.async_only_default_interface_enabled", + return_value=True, + ), + patch( + "homeassistant.components.network.async_get_enabled_source_ips", + return_value=[IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")], + ), + patch( + "homeassistant.components.shelly.utils.COAP", + autospec=COAP, + ) as mock_coap_init, + ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {"coap_port": 7632}}) await hass.async_block_till_done() @@ -83,16 +87,20 @@ async def test_ip_address_without_only_default_interface( hass: HomeAssistant, mock_block_device: Mock, caplog: pytest.LogCaptureFixture ) -> None: """Test more local ip addresses without only the default interface..""" - with patch( - "homeassistant.components.network.async_only_default_interface_enabled", - return_value=False, - ), patch( - "homeassistant.components.network.async_get_enabled_source_ips", - return_value=[IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")], - ), patch( - "homeassistant.components.shelly.utils.COAP", - autospec=COAP, - ) as mock_coap_init: + with ( + patch( + "homeassistant.components.network.async_only_default_interface_enabled", + return_value=False, + ), + patch( + "homeassistant.components.network.async_get_enabled_source_ips", + return_value=[IPv4Address("192.168.1.10"), IPv4Address("10.10.10.10")], + ), + patch( + "homeassistant.components.shelly.utils.COAP", + autospec=COAP, + ) as mock_coap_init, + ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: {"coap_port": 7632}}) await hass.async_block_till_done() diff --git a/tests/components/shopping_list/conftest.py b/tests/components/shopping_list/conftest.py index 3af5d30972e..dd1b690e1e3 100644 --- a/tests/components/shopping_list/conftest.py +++ b/tests/components/shopping_list/conftest.py @@ -13,8 +13,9 @@ from tests.common import MockConfigEntry @pytest.fixture(autouse=True) def mock_shopping_list_io(): """Stub out the persistence.""" - with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch( - "homeassistant.components.shopping_list.ShoppingData.async_load" + with ( + patch("homeassistant.components.shopping_list.ShoppingData.save"), + patch("homeassistant.components.shopping_list.ShoppingData.async_load"), ): yield diff --git a/tests/components/signal_messenger/test_notify.py b/tests/components/signal_messenger/test_notify.py index 87b51bdef7c..e2f76d54c87 100644 --- a/tests/components/signal_messenger/test_notify.py +++ b/tests/components/signal_messenger/test_notify.py @@ -71,9 +71,12 @@ def test_send_message_to_api_with_bad_data_throws_error( ) -> None: """Test sending a message with bad data to the API throws an error.""" signal_requests_mock = signal_requests_mock_factory(False) - with caplog.at_level( - logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" - ), pytest.raises(SignalCliRestApiError) as exc: + with ( + caplog.at_level( + logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" + ), + pytest.raises(SignalCliRestApiError) as exc, + ): signal_notification_service.send_message(MESSAGE) assert "Sending signal message" in caplog.text @@ -88,9 +91,12 @@ def test_send_message_with_bad_data_throws_vol_error( caplog: pytest.LogCaptureFixture, ) -> None: """Test sending a message with bad data throws an error.""" - with caplog.at_level( - logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" - ), pytest.raises(vol.Invalid) as exc: + with ( + caplog.at_level( + logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" + ), + pytest.raises(vol.Invalid) as exc, + ): data = {"test": "test"} signal_notification_service.send_message(MESSAGE, data=data) @@ -105,11 +111,14 @@ def test_send_message_with_attachment( ) -> None: """Test send message with attachment.""" signal_requests_mock = signal_requests_mock_factory() - with caplog.at_level( - logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" - ), tempfile.NamedTemporaryFile( - mode="w", suffix=".png", prefix=os.path.basename(__file__) - ) as temp_file: + with ( + caplog.at_level( + logging.DEBUG, logger="homeassistant.components.signal_messenger.notify" + ), + tempfile.NamedTemporaryFile( + mode="w", suffix=".png", prefix=os.path.basename(__file__) + ) as temp_file, + ): temp_file.write("attachment_data") data = {"attachments": [temp_file.name]} signal_notification_service.send_message(MESSAGE, data=data) diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index 0594ac51850..cc387ee765b 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -95,20 +95,26 @@ def reauth_config_fixture(): @pytest.fixture(name="setup_simplisafe") async def setup_simplisafe_fixture(hass, api, config): """Define a fixture to set up SimpliSafe.""" - with patch( - "homeassistant.components.simplisafe.config_flow.API.async_from_auth", - return_value=api, - ), patch( - "homeassistant.components.simplisafe.API.async_from_auth", - return_value=api, - ), patch( - "homeassistant.components.simplisafe.API.async_from_refresh_token", - return_value=api, - ), patch( - "homeassistant.components.simplisafe.SimpliSafe._async_start_websocket_loop" - ), patch( - "homeassistant.components.simplisafe.PLATFORMS", - [], + with ( + patch( + "homeassistant.components.simplisafe.config_flow.API.async_from_auth", + return_value=api, + ), + patch( + "homeassistant.components.simplisafe.API.async_from_auth", + return_value=api, + ), + patch( + "homeassistant.components.simplisafe.API.async_from_refresh_token", + return_value=api, + ), + patch( + "homeassistant.components.simplisafe.SimpliSafe._async_start_websocket_loop" + ), + patch( + "homeassistant.components.simplisafe.PLATFORMS", + [], + ), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index 308f04d12a1..af92833eb5b 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -99,9 +99,12 @@ async def test_step_reauth(config_entry, hass: HomeAssistant, setup_simplisafe) ) assert result["step_id"] == "user" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): + with ( + patch( + "homeassistant.components.simplisafe.async_setup_entry", return_value=True + ), + patch("homeassistant.config_entries.ConfigEntries.async_reload"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_AUTH_CODE: VALID_AUTH_CODE} ) @@ -125,9 +128,12 @@ async def test_step_reauth_wrong_account( ) assert result["step_id"] == "user" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): + with ( + patch( + "homeassistant.components.simplisafe.async_setup_entry", return_value=True + ), + patch("homeassistant.config_entries.ConfigEntries.async_reload"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_AUTH_CODE: VALID_AUTH_CODE} ) @@ -163,9 +169,12 @@ async def test_step_user( ) assert result["step_id"] == "user" - with patch( - "homeassistant.components.simplisafe.async_setup_entry", return_value=True - ), patch("homeassistant.config_entries.ConfigEntries.async_reload"): + with ( + patch( + "homeassistant.components.simplisafe.async_setup_entry", return_value=True + ), + patch("homeassistant.config_entries.ConfigEntries.async_reload"), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_AUTH_CODE: auth_code} ) diff --git a/tests/components/simplisafe/test_init.py b/tests/components/simplisafe/test_init.py index 3a89caeb891..f626f479a2f 100644 --- a/tests/components/simplisafe/test_init.py +++ b/tests/components/simplisafe/test_init.py @@ -23,20 +23,26 @@ async def test_base_station_migration( name="old", ) - with patch( - "homeassistant.components.simplisafe.config_flow.API.async_from_auth", - return_value=api, - ), patch( - "homeassistant.components.simplisafe.API.async_from_auth", - return_value=api, - ), patch( - "homeassistant.components.simplisafe.API.async_from_refresh_token", - return_value=api, - ), patch( - "homeassistant.components.simplisafe.SimpliSafe._async_start_websocket_loop" - ), patch( - "homeassistant.components.simplisafe.PLATFORMS", - [], + with ( + patch( + "homeassistant.components.simplisafe.config_flow.API.async_from_auth", + return_value=api, + ), + patch( + "homeassistant.components.simplisafe.API.async_from_auth", + return_value=api, + ), + patch( + "homeassistant.components.simplisafe.API.async_from_refresh_token", + return_value=api, + ), + patch( + "homeassistant.components.simplisafe.SimpliSafe._async_start_websocket_loop" + ), + patch( + "homeassistant.components.simplisafe.PLATFORMS", + [], + ), ): assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() diff --git a/tests/components/skybell/conftest.py b/tests/components/skybell/conftest.py index 9b95cfd79bb..6120d168572 100644 --- a/tests/components/skybell/conftest.py +++ b/tests/components/skybell/conftest.py @@ -36,10 +36,13 @@ def skybell_mock(): mocked_skybell.async_send_request.return_value = {"id": USER_ID} mocked_skybell.user_id = USER_ID - with patch( - "homeassistant.components.skybell.config_flow.Skybell", - return_value=mocked_skybell, - ), patch("homeassistant.components.skybell.Skybell", return_value=mocked_skybell): + with ( + patch( + "homeassistant.components.skybell.config_flow.Skybell", + return_value=mocked_skybell, + ), + patch("homeassistant.components.skybell.Skybell", return_value=mocked_skybell), + ): yield mocked_skybell diff --git a/tests/components/sma/conftest.py b/tests/components/sma/conftest.py index 2da12c249d7..a98eda673e4 100644 --- a/tests/components/sma/conftest.py +++ b/tests/components/sma/conftest.py @@ -32,8 +32,11 @@ async def init_integration(hass, mock_config_entry): """Create a fake SMA Config Entry.""" mock_config_entry.add_to_hass(hass) - with patch("pysma.SMA.read"), patch( - "pysma.SMA.get_sensors", return_value=Sensors(sensor_map[GENERIC_SENSORS]) + with ( + patch("pysma.SMA.read"), + patch( + "pysma.SMA.get_sensors", return_value=Sensors(sensor_map[GENERIC_SENSORS]) + ), ): await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/sma/test_config_flow.py b/tests/components/sma/test_config_flow.py index afb4da9aff7..d73d8eb9728 100644 --- a/tests/components/sma/test_config_flow.py +++ b/tests/components/sma/test_config_flow.py @@ -25,9 +25,11 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch("pysma.SMA.new_session", return_value=True), patch( - "pysma.SMA.device_info", return_value=MOCK_DEVICE - ), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch("pysma.SMA.new_session", return_value=True), + patch("pysma.SMA.device_info", return_value=MOCK_DEVICE), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -47,9 +49,10 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": SOURCE_USER} ) - with patch( - "pysma.SMA.new_session", side_effect=SmaConnectionException - ), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch("pysma.SMA.new_session", side_effect=SmaConnectionException), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -66,9 +69,10 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: DOMAIN, context={"source": SOURCE_USER} ) - with patch( - "pysma.SMA.new_session", side_effect=SmaAuthenticationException - ), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch("pysma.SMA.new_session", side_effect=SmaAuthenticationException), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -85,9 +89,11 @@ async def test_form_cannot_retrieve_device_info(hass: HomeAssistant) -> None: DOMAIN, context={"source": SOURCE_USER} ) - with patch("pysma.SMA.new_session", return_value=True), patch( - "pysma.SMA.read", side_effect=SmaReadException - ), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch("pysma.SMA.new_session", return_value=True), + patch("pysma.SMA.read", side_effect=SmaReadException), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -104,9 +110,10 @@ async def test_form_unexpected_exception(hass: HomeAssistant) -> None: DOMAIN, context={"source": SOURCE_USER} ) - with patch( - "pysma.SMA.new_session", side_effect=Exception - ), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch("pysma.SMA.new_session", side_effect=Exception), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, @@ -125,11 +132,12 @@ async def test_form_already_configured(hass: HomeAssistant, mock_config_entry) - DOMAIN, context={"source": SOURCE_USER} ) - with patch("pysma.SMA.new_session", return_value=True), patch( - "pysma.SMA.device_info", return_value=MOCK_DEVICE - ), patch( - "pysma.SMA.close_session", return_value=True - ), _patch_async_setup_entry() as mock_setup_entry: + with ( + patch("pysma.SMA.new_session", return_value=True), + patch("pysma.SMA.device_info", return_value=MOCK_DEVICE), + patch("pysma.SMA.close_session", return_value=True), + _patch_async_setup_entry() as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_INPUT, diff --git a/tests/components/smappee/test_config_flow.py b/tests/components/smappee/test_config_flow.py index 794510366e2..b5551c03c77 100644 --- a/tests/components/smappee/test_config_flow.py +++ b/tests/components/smappee/test_config_flow.py @@ -118,8 +118,9 @@ async def test_show_zeroconf_connection_error_form_next_generation( async def test_connection_error(hass: HomeAssistant) -> None: """Test we show user form on Smappee connection error.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value=None), patch( - "pysmappee.mqtt.SmappeeLocalMqtt.start_attempt", return_value=None + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value=None), + patch("pysmappee.mqtt.SmappeeLocalMqtt.start_attempt", return_value=None), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -143,11 +144,13 @@ async def test_connection_error(hass: HomeAssistant) -> None: async def test_user_local_connection_error(hass: HomeAssistant) -> None: """Test we show user form on Smappee connection error in local next generation option.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value=None), patch( - "pysmappee.mqtt.SmappeeLocalMqtt.start_attempt", return_value=True - ), patch("pysmappee.mqtt.SmappeeLocalMqtt.start", return_value=True), patch( - "pysmappee.mqtt.SmappeeLocalMqtt.stop", return_value=True - ), patch("pysmappee.mqtt.SmappeeLocalMqtt.is_config_ready", return_value=None): + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value=None), + patch("pysmappee.mqtt.SmappeeLocalMqtt.start_attempt", return_value=True), + patch("pysmappee.mqtt.SmappeeLocalMqtt.start", return_value=True), + patch("pysmappee.mqtt.SmappeeLocalMqtt.stop", return_value=True), + patch("pysmappee.mqtt.SmappeeLocalMqtt.is_config_ready", return_value=None), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, @@ -190,14 +193,19 @@ async def test_zeroconf_wrong_mdns(hass: HomeAssistant) -> None: async def test_full_user_wrong_mdns(hass: HomeAssistant) -> None: """Test we abort user flow if unsupported mDNS name got resolved.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), patch( - "pysmappee.api.SmappeeLocalApi.load_advanced_config", - return_value=[{"key": "mdnsHostName", "value": "Smappee5100000001"}], - ), patch( - "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] - ), patch( - "pysmappee.api.SmappeeLocalApi.load_instantaneous", - return_value=[{"key": "phase0ActivePower", "value": 0}], + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), + patch( + "pysmappee.api.SmappeeLocalApi.load_advanced_config", + return_value=[{"key": "mdnsHostName", "value": "Smappee5100000001"}], + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_instantaneous", + return_value=[{"key": "phase0ActivePower", "value": 0}], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -221,14 +229,19 @@ async def test_full_user_wrong_mdns(hass: HomeAssistant) -> None: async def test_user_device_exists_abort(hass: HomeAssistant) -> None: """Test we abort user flow if Smappee device already configured.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), patch( - "pysmappee.api.SmappeeLocalApi.load_advanced_config", - return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], - ), patch( - "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] - ), patch( - "pysmappee.api.SmappeeLocalApi.load_instantaneous", - return_value=[{"key": "phase0ActivePower", "value": 0}], + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), + patch( + "pysmappee.api.SmappeeLocalApi.load_advanced_config", + return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_instantaneous", + return_value=[{"key": "phase0ActivePower", "value": 0}], + ), ): config_entry = MockConfigEntry( domain=DOMAIN, @@ -262,14 +275,19 @@ async def test_user_device_exists_abort(hass: HomeAssistant) -> None: async def test_zeroconf_device_exists_abort(hass: HomeAssistant) -> None: """Test we abort zeroconf flow if Smappee device already configured.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), patch( - "pysmappee.api.SmappeeLocalApi.load_advanced_config", - return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], - ), patch( - "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] - ), patch( - "pysmappee.api.SmappeeLocalApi.load_instantaneous", - return_value=[{"key": "phase0ActivePower", "value": 0}], + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), + patch( + "pysmappee.api.SmappeeLocalApi.load_advanced_config", + return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_instantaneous", + return_value=[{"key": "phase0ActivePower", "value": 0}], + ), ): config_entry = MockConfigEntry( domain=DOMAIN, @@ -465,15 +483,21 @@ async def test_full_user_flow( async def test_full_zeroconf_flow(hass: HomeAssistant) -> None: """Test the full zeroconf flow.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), patch( - "pysmappee.api.SmappeeLocalApi.load_advanced_config", - return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], - ), patch( - "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] - ), patch( - "pysmappee.api.SmappeeLocalApi.load_instantaneous", - return_value=[{"key": "phase0ActivePower", "value": 0}], - ), patch("homeassistant.components.smappee.async_setup_entry", return_value=True): + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), + patch( + "pysmappee.api.SmappeeLocalApi.load_advanced_config", + return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_instantaneous", + return_value=[{"key": "phase0ActivePower", "value": 0}], + ), + patch("homeassistant.components.smappee.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, @@ -505,15 +529,21 @@ async def test_full_zeroconf_flow(hass: HomeAssistant) -> None: async def test_full_user_local_flow(hass: HomeAssistant) -> None: """Test the full zeroconf flow.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), patch( - "pysmappee.api.SmappeeLocalApi.load_advanced_config", - return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], - ), patch( - "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] - ), patch( - "pysmappee.api.SmappeeLocalApi.load_instantaneous", - return_value=[{"key": "phase0ActivePower", "value": 0}], - ), patch("homeassistant.components.smappee.async_setup_entry", return_value=True): + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), + patch( + "pysmappee.api.SmappeeLocalApi.load_advanced_config", + return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_instantaneous", + return_value=[{"key": "phase0ActivePower", "value": 0}], + ), + patch("homeassistant.components.smappee.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, @@ -542,14 +572,16 @@ async def test_full_user_local_flow(hass: HomeAssistant) -> None: async def test_full_zeroconf_flow_next_generation(hass: HomeAssistant) -> None: """Test the full zeroconf flow.""" - with patch( - "pysmappee.mqtt.SmappeeLocalMqtt.start_attempt", return_value=True - ), patch( - "pysmappee.mqtt.SmappeeLocalMqtt.start", - return_value=None, - ), patch( - "pysmappee.mqtt.SmappeeLocalMqtt.is_config_ready", - return_value=None, + with ( + patch("pysmappee.mqtt.SmappeeLocalMqtt.start_attempt", return_value=True), + patch( + "pysmappee.mqtt.SmappeeLocalMqtt.start", + return_value=None, + ), + patch( + "pysmappee.mqtt.SmappeeLocalMqtt.is_config_ready", + return_value=None, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/smappee/test_init.py b/tests/components/smappee/test_init.py index 489b60572e3..cc752964a27 100644 --- a/tests/components/smappee/test_init.py +++ b/tests/components/smappee/test_init.py @@ -11,14 +11,19 @@ from tests.common import MockConfigEntry async def test_unload_config_entry(hass: HomeAssistant) -> None: """Test unload config entry flow.""" - with patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), patch( - "pysmappee.api.SmappeeLocalApi.load_advanced_config", - return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], - ), patch( - "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] - ), patch( - "pysmappee.api.SmappeeLocalApi.load_instantaneous", - return_value=[{"key": "phase0ActivePower", "value": 0}], + with ( + patch("pysmappee.api.SmappeeLocalApi.logon", return_value={}), + patch( + "pysmappee.api.SmappeeLocalApi.load_advanced_config", + return_value=[{"key": "mdnsHostName", "value": "Smappee1006000212"}], + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_command_control_config", return_value=[] + ), + patch( + "pysmappee.api.SmappeeLocalApi.load_instantaneous", + return_value=[{"key": "phase0ActivePower", "value": 0}], + ), ): config_entry = MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/smart_meter_texas/test_config_flow.py b/tests/components/smart_meter_texas/test_config_flow.py index e63a7f4530b..53f7a2eb5fd 100644 --- a/tests/components/smart_meter_texas/test_config_flow.py +++ b/tests/components/smart_meter_texas/test_config_flow.py @@ -28,10 +28,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch("smart_meter_texas.Client.authenticate", return_value=True), patch( - "homeassistant.components.smart_meter_texas.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("smart_meter_texas.Client.authenticate", return_value=True), + patch( + "homeassistant.components.smart_meter_texas.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_LOGIN ) diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 283b478fdb0..f15ba85c07e 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -183,9 +183,11 @@ def smartthings_mock_fixture(locations): smartthings_mock = Mock(SmartThings) smartthings_mock.location.side_effect = _location mock = Mock(return_value=smartthings_mock) - with patch(COMPONENT_PREFIX + "SmartThings", new=mock), patch( - COMPONENT_PREFIX + "config_flow.SmartThings", new=mock - ), patch(COMPONENT_PREFIX + "smartapp.SmartThings", new=mock): + with ( + patch(COMPONENT_PREFIX + "SmartThings", new=mock), + patch(COMPONENT_PREFIX + "config_flow.SmartThings", new=mock), + patch(COMPONENT_PREFIX + "smartapp.SmartThings", new=mock), + ): yield smartthings_mock diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index b8a850443d5..e3dcf76bbaf 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -359,15 +359,18 @@ async def test_entry_created_with_cloudhook( request.location_id = location.location_id request.refresh_token = refresh_token - with patch.object( - smartapp.cloud, - "async_active_subscription", - Mock(return_value=True), - ), patch.object( - smartapp.cloud, - "async_create_cloudhook", - AsyncMock(return_value="http://cloud.test"), - ) as mock_create_cloudhook: + with ( + patch.object( + smartapp.cloud, + "async_active_subscription", + Mock(return_value=True), + ), + patch.object( + smartapp.cloud, + "async_create_cloudhook", + AsyncMock(return_value="http://cloud.test"), + ) as mock_create_cloudhook, + ): await smartapp.setup_smartapp_endpoint(hass, True) # Webhook confirmation shown diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index dcce19a68d1..6ff640e012a 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -296,11 +296,12 @@ async def test_remove_entry_cloudhook( config_entry.add_to_hass(hass) hass.data[DOMAIN][CONF_CLOUDHOOK_URL] = "https://test.cloud" # Act - with patch.object( - cloud, "async_is_logged_in", return_value=True - ) as mock_async_is_logged_in, patch.object( - cloud, "async_delete_cloudhook" - ) as mock_async_delete_cloudhook: + with ( + patch.object( + cloud, "async_is_logged_in", return_value=True + ) as mock_async_is_logged_in, + patch.object(cloud, "async_delete_cloudhook") as mock_async_delete_cloudhook, + ): await smartthings.async_remove_entry(hass, config_entry) # Assert assert smartthings_mock.delete_installed_app.call_count == 1 diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index 783e2d7eec1..7d8701eca45 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -29,13 +29,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", - return_value={"test": "something", "test2": "something else"}, - ), patch( - "homeassistant.components.smhi.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", + return_value={"test": "something", "test2": "something else"}, + ), + patch( + "homeassistant.components.smhi.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -62,12 +65,15 @@ async def test_form(hass: HomeAssistant) -> None: result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", - return_value={"test": "something", "test2": "something else"}, - ), patch( - "homeassistant.components.smhi.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", + return_value={"test": "something", "test2": "something else"}, + ), + patch( + "homeassistant.components.smhi.async_setup_entry", + return_value=True, + ), ): result4 = await hass.config_entries.flow.async_configure( result3["flow_id"], @@ -116,12 +122,15 @@ async def test_form_invalid_coordinates(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "wrong_location"} # Continue flow with new coordinates - with patch( - "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", - return_value={"test": "something", "test2": "something else"}, - ), patch( - "homeassistant.components.smhi.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", + return_value={"test": "something", "test2": "something else"}, + ), + patch( + "homeassistant.components.smhi.async_setup_entry", + return_value=True, + ), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -235,13 +244,16 @@ async def test_reconfigure_flow( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": "wrong_location"} - with patch( - "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", - return_value={"test": "something", "test2": "something else"}, - ), patch( - "homeassistant.components.smhi.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.smhi.config_flow.Smhi.async_get_forecast", + return_value={"test": "something", "test2": "something else"}, + ), + patch( + "homeassistant.components.smhi.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 65d3782c2b4..87aae74e71d 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -165,12 +165,15 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None: entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2) entry.add_to_hass(hass) - with patch( - "homeassistant.components.smhi.weather.Smhi.async_get_forecast", - return_value=testdata, - ), patch( - "homeassistant.components.smhi.weather.Smhi.async_get_forecast_hour", - return_value=None, + with ( + patch( + "homeassistant.components.smhi.weather.Smhi.async_get_forecast", + return_value=testdata, + ), + patch( + "homeassistant.components.smhi.weather.Smhi.async_get_forecast_hour", + return_value=None, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/smtp/test_notify.py b/tests/components/smtp/test_notify.py index 73911de7ed4..b27a7c2d863 100644 --- a/tests/components/smtp/test_notify.py +++ b/tests/components/smtp/test_notify.py @@ -51,8 +51,11 @@ async def test_reload_notify(hass: HomeAssistant) -> None: assert hass.services.has_service(notify.DOMAIN, DOMAIN) yaml_path = get_fixture_path("configuration.yaml", "smtp") - with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), patch( - "homeassistant.components.smtp.notify.MailNotificationService.connection_is_valid" + with ( + patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path), + patch( + "homeassistant.components.smtp.notify.MailNotificationService.connection_is_valid" + ), ): await hass.services.async_call( DOMAIN, @@ -166,9 +169,10 @@ def test_sending_insecure_files_fails( """Verify if we cannot send messages with insecure attachments.""" sample_email = "" message.hass = hass - with patch("email.utils.make_msgid", return_value=sample_email), pytest.raises( - ServiceValidationError - ) as exc: + with ( + patch("email.utils.make_msgid", return_value=sample_email), + pytest.raises(ServiceValidationError) as exc, + ): result, _ = message.send_message(message_data, data=data) assert content_type in result assert exc.value.translation_key == "remote_path_not_allowed" diff --git a/tests/components/snooz/__init__.py b/tests/components/snooz/__init__.py index 3f04ddbbc03..c314fde5c90 100644 --- a/tests/components/snooz/__init__.py +++ b/tests/components/snooz/__init__.py @@ -100,11 +100,12 @@ async def create_mock_snooz_config_entry( ) -> MockConfigEntry: """Create a mock config entry.""" - with patch( - "homeassistant.components.snooz.SnoozDevice", return_value=device - ), patch( - "homeassistant.components.snooz.async_ble_device_from_address", - return_value=generate_ble_device(device.address, device.name), + with ( + patch("homeassistant.components.snooz.SnoozDevice", return_value=device), + patch( + "homeassistant.components.snooz.async_ble_device_from_address", + return_value=generate_ble_device(device.address, device.name), + ), ): entry = MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/solarlog/test_config_flow.py b/tests/components/solarlog/test_config_flow.py index e80e49bc9e1..16f25264b9d 100644 --- a/tests/components/solarlog/test_config_flow.py +++ b/tests/components/solarlog/test_config_flow.py @@ -25,13 +25,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.solarlog.config_flow.SolarLogConfigFlow._test_connection", - return_value={"title": "solarlog test 1 2 3"}, - ), patch( - "homeassistant.components.solarlog.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.solarlog.config_flow.SolarLogConfigFlow._test_connection", + return_value={"title": "solarlog test 1 2 3"}, + ), + patch( + "homeassistant.components.solarlog.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": HOST, "name": NAME} ) diff --git a/tests/components/solax/test_config_flow.py b/tests/components/solax/test_config_flow.py index 47e588d34a3..c671fe39cec 100644 --- a/tests/components/solax/test_config_flow.py +++ b/tests/components/solax/test_config_flow.py @@ -30,13 +30,17 @@ async def test_form_success(hass: HomeAssistant) -> None: assert flow["type"] == "form" assert flow["errors"] == {} - with patch( - "homeassistant.components.solax.config_flow.real_time_api", - return_value=__mock_real_time_api_success(), - ), patch("solax.RealTimeAPI.get_data", return_value=__mock_get_data()), patch( - "homeassistant.components.solax.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.solax.config_flow.real_time_api", + return_value=__mock_real_time_api_success(), + ), + patch("solax.RealTimeAPI.get_data", return_value=__mock_get_data()), + patch( + "homeassistant.components.solax.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): entry_result = await hass.config_entries.flow.async_configure( flow["flow_id"], {CONF_IP_ADDRESS: "192.168.1.87", CONF_PORT: 80, CONF_PASSWORD: "password"}, diff --git a/tests/components/somfy_mylink/test_config_flow.py b/tests/components/somfy_mylink/test_config_flow.py index c5d1c726a8f..a01f4d640a1 100644 --- a/tests/components/somfy_mylink/test_config_flow.py +++ b/tests/components/somfy_mylink/test_config_flow.py @@ -26,13 +26,16 @@ async def test_form_user(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", - return_value={"any": "data"}, - ), patch( - "homeassistant.components.somfy_mylink.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", + return_value={"any": "data"}, + ), + patch( + "homeassistant.components.somfy_mylink.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -67,13 +70,16 @@ async def test_form_user_already_configured(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", - return_value={"any": "data"}, - ), patch( - "homeassistant.components.somfy_mylink.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", + return_value={"any": "data"}, + ), + patch( + "homeassistant.components.somfy_mylink.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -243,13 +249,16 @@ async def test_form_user_already_configured_from_dhcp(hass: HomeAssistant) -> No ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", - return_value={"any": "data"}, - ), patch( - "homeassistant.components.somfy_mylink.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", + return_value={"any": "data"}, + ), + patch( + "homeassistant.components.somfy_mylink.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, @@ -301,13 +310,16 @@ async def test_dhcp_discovery(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", - return_value={"any": "data"}, - ), patch( - "homeassistant.components.somfy_mylink.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.somfy_mylink.config_flow.SomfyMyLinkSynergy.status_info", + return_value={"any": "data"}, + ), + patch( + "homeassistant.components.somfy_mylink.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/songpal/test_init.py b/tests/components/songpal/test_init.py index 6bc14e06153..c49b290f805 100644 --- a/tests/components/songpal/test_init.py +++ b/tests/components/songpal/test_init.py @@ -58,8 +58,9 @@ async def test_unload(hass: HomeAssistant) -> None: entry.add_to_hass(hass) mocked_device = _create_mocked_device() - with _patch_config_flow_device(mocked_device), _patch_media_player_device( - mocked_device + with ( + _patch_config_flow_device(mocked_device), + _patch_media_player_device(mocked_device), ): assert await async_setup_component(hass, songpal.DOMAIN, {}) is True await hass.async_block_till_done() diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index d9600b9f1e5..0b3834992d8 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -209,9 +209,11 @@ def soco_factory( factory = SoCoMockFactory( music_library, speaker_info, current_track_info_empty, battery_info, alarm_clock ) - with patch("homeassistant.components.sonos.SoCo", new=factory.get_mock), patch( - "socket.gethostbyname", side_effect=patch_gethostbyname - ), patch("homeassistant.components.sonos.ZGS_SUBSCRIPTION_TIMEOUT", 0): + with ( + patch("homeassistant.components.sonos.SoCo", new=factory.get_mock), + patch("socket.gethostbyname", side_effect=patch_gethostbyname), + patch("homeassistant.components.sonos.ZGS_SUBSCRIPTION_TIMEOUT", 0), + ): yield factory @@ -224,14 +226,16 @@ def soco_fixture(soco_factory): @pytest.fixture(autouse=True) async def silent_ssdp_scanner(hass): """Start SSDP component and get Scanner, prevent actual SSDP traffic.""" - with patch( - "homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners" - ), patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), patch( - "homeassistant.components.ssdp.Scanner.async_scan" - ), patch( - "homeassistant.components.ssdp.Server._async_start_upnp_servers", - ), patch( - "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + with ( + patch("homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner.async_scan"), + patch( + "homeassistant.components.ssdp.Server._async_start_upnp_servers", + ), + patch( + "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + ), ): yield diff --git a/tests/components/sonos/test_config_flow.py b/tests/components/sonos/test_config_flow.py index 2b408f1fa74..186e45e3d84 100644 --- a/tests/components/sonos/test_config_flow.py +++ b/tests/components/sonos/test_config_flow.py @@ -42,13 +42,16 @@ async def test_user_form( ) assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.sonos.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.sonos.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.sonos.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.sonos.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -93,13 +96,16 @@ async def test_zeroconf_form( assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.sonos.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.sonos.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.sonos.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.sonos.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -135,13 +141,16 @@ async def test_ssdp_discovery(hass: HomeAssistant, soco) -> None: assert len(flows) == 1 flow = flows[0] - with patch( - "homeassistant.components.sonos.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.sonos.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.sonos.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.sonos.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( flow["flow_id"], {}, @@ -185,13 +194,16 @@ async def test_zeroconf_sonos_v1(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.sonos.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.sonos.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.sonos.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.sonos.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index 964984e777a..77bf9a5d12b 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -96,21 +96,23 @@ async def test_async_poll_manual_hosts_warnings( await hass.async_block_till_done() manager: SonosDiscoveryManager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] manager.hosts.add("10.10.10.10") - with caplog.at_level(logging.DEBUG), patch.object( - manager, "_async_handle_discovery_message" - ), patch( - "homeassistant.components.sonos.async_call_later" - ) as mock_async_call_later, patch( - "homeassistant.components.sonos.async_dispatcher_send" - ), patch( - "homeassistant.components.sonos.sync_get_visible_zones", - side_effect=[ - OSError(), - OSError(), - [], - [], - OSError(), - ], + with ( + caplog.at_level(logging.DEBUG), + patch.object(manager, "_async_handle_discovery_message"), + patch( + "homeassistant.components.sonos.async_call_later" + ) as mock_async_call_later, + patch("homeassistant.components.sonos.async_dispatcher_send"), + patch( + "homeassistant.components.sonos.sync_get_visible_zones", + side_effect=[ + OSError(), + OSError(), + [], + [], + OSError(), + ], + ), ): # First call fails, it should be logged as a WARNING message caplog.clear() diff --git a/tests/components/sonos/test_plex_playback.py b/tests/components/sonos/test_plex_playback.py index d5d5884bb50..428e970697e 100644 --- a/tests/components/sonos/test_plex_playback.py +++ b/tests/components/sonos/test_plex_playback.py @@ -28,12 +28,16 @@ async def test_plex_play_media(hass: HomeAssistant, async_autosetup_sonos) -> No '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}' ) - with patch( - "homeassistant.components.plex.services.get_plex_server", - return_value=mock_plex_server, - ), patch("soco.plugins.plex.PlexPlugin.add_to_queue") as mock_add_to_queue, patch( - "homeassistant.components.sonos.media_player.SonosMediaPlayerEntity.set_shuffle" - ) as mock_shuffle: + with ( + patch( + "homeassistant.components.plex.services.get_plex_server", + return_value=mock_plex_server, + ), + patch("soco.plugins.plex.PlexPlugin.add_to_queue") as mock_add_to_queue, + patch( + "homeassistant.components.sonos.media_player.SonosMediaPlayerEntity.set_shuffle" + ) as mock_shuffle, + ): # Test successful Plex service call await hass.services.async_call( MP_DOMAIN, diff --git a/tests/components/sonos/test_services.py b/tests/components/sonos/test_services.py index adea75f74ab..da894ff4548 100644 --- a/tests/components/sonos/test_services.py +++ b/tests/components/sonos/test_services.py @@ -28,11 +28,12 @@ async def test_media_player_join(hass: HomeAssistant, async_autosetup_sonos) -> mocked_speaker = Mock() mock_entity_id_mappings = {mocked_entity_id: mocked_speaker} - with patch.dict( - hass.data[DATA_SONOS].entity_id_mappings, mock_entity_id_mappings - ), patch( - "homeassistant.components.sonos.speaker.SonosSpeaker.join_multi" - ) as mock_join_multi: + with ( + patch.dict(hass.data[DATA_SONOS].entity_id_mappings, mock_entity_id_mappings), + patch( + "homeassistant.components.sonos.speaker.SonosSpeaker.join_multi" + ) as mock_join_multi, + ): await hass.services.async_call( MP_DOMAIN, SERVICE_JOIN, diff --git a/tests/components/sonos/test_speaker.py b/tests/components/sonos/test_speaker.py index 8ae1d0bae03..e0fc4c3baf9 100644 --- a/tests/components/sonos/test_speaker.py +++ b/tests/components/sonos/test_speaker.py @@ -23,8 +23,11 @@ async def test_fallback_to_polling( caplog.clear() # Ensure subscriptions are cancelled and polling methods are called when subscriptions time out - with patch("homeassistant.components.sonos.media.SonosMedia.poll_media"), patch( - "homeassistant.components.sonos.speaker.SonosSpeaker.subscription_address" + with ( + patch("homeassistant.components.sonos.media.SonosMedia.poll_media"), + patch( + "homeassistant.components.sonos.speaker.SonosSpeaker.subscription_address" + ), ): async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() diff --git a/tests/components/spider/test_config_flow.py b/tests/components/spider/test_config_flow.py index e0a3e16e350..1fb61573216 100644 --- a/tests/components/spider/test_config_flow.py +++ b/tests/components/spider/test_config_flow.py @@ -37,11 +37,14 @@ async def test_user(hass: HomeAssistant, spider) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.spider.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.spider.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.spider.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.spider.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=SPIDER_USER_DATA ) @@ -60,13 +63,16 @@ async def test_user(hass: HomeAssistant, spider) -> None: async def test_import(hass: HomeAssistant, spider) -> None: """Test import step.""" - with patch( - "homeassistant.components.spider.async_setup", - return_value=True, - ) as mock_setup, patch( - "homeassistant.components.spider.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.spider.async_setup", + return_value=True, + ) as mock_setup, + patch( + "homeassistant.components.spider.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index 9a36942aafd..1ab4e46bd55 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -122,9 +122,10 @@ async def test_full_flow( }, ) - with patch( - "homeassistant.components.spotify.async_setup_entry", return_value=True - ), patch("homeassistant.components.spotify.config_flow.Spotify") as spotify_mock: + with ( + patch("homeassistant.components.spotify.async_setup_entry", return_value=True), + patch("homeassistant.components.spotify.config_flow.Spotify") as spotify_mock, + ): spotify_mock.return_value.current_user.return_value = { "id": "fake_id", "display_name": "frenck", @@ -235,9 +236,10 @@ async def test_reauthentication( }, ) - with patch( - "homeassistant.components.spotify.async_setup_entry", return_value=True - ), patch("homeassistant.components.spotify.config_flow.Spotify") as spotify_mock: + with ( + patch("homeassistant.components.spotify.async_setup_entry", return_value=True), + patch("homeassistant.components.spotify.config_flow.Spotify") as spotify_mock, + ): spotify_mock.return_value.current_user.return_value = {"id": "frenck"} result = await hass.config_entries.flow.async_configure(result["flow_id"]) diff --git a/tests/components/sql/test_config_flow.py b/tests/components/sql/test_config_flow.py index f1dec1871ed..7b3b0aaf350 100644 --- a/tests/components/sql/test_config_flow.py +++ b/tests/components/sql/test_config_flow.py @@ -592,11 +592,14 @@ async def test_options_flow_db_url_empty( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "init" - with patch( - "homeassistant.components.sql.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.sql.config_flow.sqlalchemy.create_engine", + with ( + patch( + "homeassistant.components.sql.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.sql.config_flow.sqlalchemy.create_engine", + ), ): result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -627,11 +630,14 @@ async def test_full_flow_not_recorder_db( assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.sql.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.sql.config_flow.sqlalchemy.create_engine", + with ( + patch( + "homeassistant.components.sql.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.sql.config_flow.sqlalchemy.create_engine", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -660,11 +666,14 @@ async def test_full_flow_not_recorder_db( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "init" - with patch( - "homeassistant.components.sql.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.sql.config_flow.sqlalchemy.create_engine", + with ( + patch( + "homeassistant.components.sql.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.sql.config_flow.sqlalchemy.create_engine", + ), ): result = await hass.config_entries.options.async_configure( result["flow_id"], diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index 9eabffd9421..14442aa5181 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -153,9 +153,12 @@ async def test_query_mssql_no_result( "column": "value", "name": "count_tables", } - with patch("homeassistant.components.sql.sensor.sqlalchemy"), patch( - "homeassistant.components.sql.sensor.sqlalchemy.text", - return_value=sql_text("SELECT TOP 1 5 as value where 1=2"), + with ( + patch("homeassistant.components.sql.sensor.sqlalchemy"), + patch( + "homeassistant.components.sql.sensor.sqlalchemy.text", + return_value=sql_text("SELECT TOP 1 5 as value where 1=2"), + ), ): await init_integration(hass, config) diff --git a/tests/components/squeezebox/test_config_flow.py b/tests/components/squeezebox/test_config_flow.py index af100e28232..dc82b658163 100644 --- a/tests/components/squeezebox/test_config_flow.py +++ b/tests/components/squeezebox/test_config_flow.py @@ -38,14 +38,19 @@ async def patch_async_query_unauthorized(self, *args): async def test_user_form(hass: HomeAssistant) -> None: """Test user-initiated flow, including discovery and the edit step.""" - with patch( - "pysqueezebox.Server.async_query", - return_value={"uuid": UUID}, - ), patch( - "homeassistant.components.squeezebox.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.squeezebox.config_flow.async_discover", mock_discover + with ( + patch( + "pysqueezebox.Server.async_query", + return_value={"uuid": UUID}, + ), + patch( + "homeassistant.components.squeezebox.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.squeezebox.config_flow.async_discover", + mock_discover, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -84,10 +89,13 @@ async def test_user_form(hass: HomeAssistant) -> None: async def test_user_form_timeout(hass: HomeAssistant) -> None: """Test we handle server search timeout.""" - with patch( - "homeassistant.components.squeezebox.config_flow.async_discover", - mock_failed_discover, - ), patch("homeassistant.components.squeezebox.config_flow.TIMEOUT", 0.1): + with ( + patch( + "homeassistant.components.squeezebox.config_flow.async_discover", + mock_failed_discover, + ), + patch("homeassistant.components.squeezebox.config_flow.TIMEOUT", 0.1), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -108,12 +116,16 @@ async def test_user_form_timeout(hass: HomeAssistant) -> None: async def test_user_form_duplicate(hass: HomeAssistant) -> None: """Test duplicate discovered servers are skipped.""" - with patch( - "homeassistant.components.squeezebox.config_flow.async_discover", - mock_discover, - ), patch("homeassistant.components.squeezebox.config_flow.TIMEOUT", 0.1), patch( - "homeassistant.components.squeezebox.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.squeezebox.config_flow.async_discover", + mock_discover, + ), + patch("homeassistant.components.squeezebox.config_flow.TIMEOUT", 0.1), + patch( + "homeassistant.components.squeezebox.async_setup_entry", + return_value=True, + ), ): entry = MockConfigEntry( domain=DOMAIN, @@ -207,11 +219,15 @@ async def test_discovery_no_uuid(hass: HomeAssistant) -> None: async def test_dhcp_discovery(hass: HomeAssistant) -> None: """Test we can process discovery from dhcp.""" - with patch( - "pysqueezebox.Server.async_query", - return_value={"uuid": UUID}, - ), patch( - "homeassistant.components.squeezebox.config_flow.async_discover", mock_discover + with ( + patch( + "pysqueezebox.Server.async_query", + return_value={"uuid": UUID}, + ), + patch( + "homeassistant.components.squeezebox.config_flow.async_discover", + mock_discover, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -228,10 +244,13 @@ async def test_dhcp_discovery(hass: HomeAssistant) -> None: async def test_dhcp_discovery_no_server_found(hass: HomeAssistant) -> None: """Test we can handle dhcp discovery when no server is found.""" - with patch( - "homeassistant.components.squeezebox.config_flow.async_discover", - mock_failed_discover, - ), patch("homeassistant.components.squeezebox.config_flow.TIMEOUT", 0.1): + with ( + patch( + "homeassistant.components.squeezebox.config_flow.async_discover", + mock_failed_discover, + ), + patch("homeassistant.components.squeezebox.config_flow.TIMEOUT", 0.1), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, diff --git a/tests/components/srp_energy/test_sensor.py b/tests/components/srp_energy/test_sensor.py index 70b4eae14df..a46caf904b7 100644 --- a/tests/components/srp_energy/test_sensor.py +++ b/tests/components/srp_energy/test_sensor.py @@ -72,10 +72,11 @@ async def test_srp_entity_timeout( ) -> None: """Test the SrpEntity timing out.""" - with patch( - "homeassistant.components.srp_energy.SrpEnergyClient", autospec=True - ) as srp_energy_mock, patch( - "homeassistant.components.srp_energy.coordinator.TIMEOUT", 0 + with ( + patch( + "homeassistant.components.srp_energy.SrpEnergyClient", autospec=True + ) as srp_energy_mock, + patch("homeassistant.components.srp_energy.coordinator.TIMEOUT", 0), ): client = srp_energy_mock.return_value client.validate.return_value = True diff --git a/tests/components/ssdp/conftest.py b/tests/components/ssdp/conftest.py index 157e6d0daa9..8b06163cd95 100644 --- a/tests/components/ssdp/conftest.py +++ b/tests/components/ssdp/conftest.py @@ -10,9 +10,11 @@ import pytest @pytest.fixture(autouse=True) async def silent_ssdp_listener(): """Patch SsdpListener class, preventing any actual SSDP traffic.""" - with patch("homeassistant.components.ssdp.SsdpListener.async_start"), patch( - "homeassistant.components.ssdp.SsdpListener.async_stop" - ), patch("homeassistant.components.ssdp.SsdpListener.async_search"): + with ( + patch("homeassistant.components.ssdp.SsdpListener.async_start"), + patch("homeassistant.components.ssdp.SsdpListener.async_stop"), + patch("homeassistant.components.ssdp.SsdpListener.async_search"), + ): # Fixtures are initialized before patches. When the component is started here, # certain functions/methods might not be patched in time. yield SsdpListener @@ -21,9 +23,11 @@ async def silent_ssdp_listener(): @pytest.fixture(autouse=True) async def disabled_upnp_server(): """Disable UPnpServer.""" - with patch("homeassistant.components.ssdp.UpnpServer.async_start"), patch( - "homeassistant.components.ssdp.UpnpServer.async_stop" - ), patch("homeassistant.components.ssdp._async_find_next_available_port"): + with ( + patch("homeassistant.components.ssdp.UpnpServer.async_start"), + patch("homeassistant.components.ssdp.UpnpServer.async_stop"), + patch("homeassistant.components.ssdp._async_find_next_available_port"), + ): yield UpnpServer diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 7af2287c893..5131388c4e3 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -861,13 +861,14 @@ async def test_flow_dismiss_on_byebye( mock_ssdp_advertisement["nts"] = "ssdp:byebye" # ssdp:byebye advertisement should dismiss existing flows - with patch.object( - hass.config_entries.flow, - "async_progress_by_init_data_type", - return_value=[{"flow_id": "mock_flow_id"}], - ) as mock_async_progress_by_init_data_type, patch.object( - hass.config_entries.flow, "async_abort" - ) as mock_async_abort: + with ( + patch.object( + hass.config_entries.flow, + "async_progress_by_init_data_type", + return_value=[{"flow_id": "mock_flow_id"}], + ) as mock_async_progress_by_init_data_type, + patch.object(hass.config_entries.flow, "async_abort") as mock_async_abort, + ): ssdp_listener._on_byebye(mock_ssdp_advertisement) await hass.async_block_till_done(wait_background_tasks=True) diff --git a/tests/components/starlink/test_diagnostics.py b/tests/components/starlink/test_diagnostics.py index 22e1d6e84be..c5876e5e9f2 100644 --- a/tests/components/starlink/test_diagnostics.py +++ b/tests/components/starlink/test_diagnostics.py @@ -28,7 +28,11 @@ async def test_diagnostics( data={CONF_IP_ADDRESS: "1.2.3.4:0000"}, ) - with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER, SLEEP_DATA_SUCCESS_PATCHER: + with ( + STATUS_DATA_SUCCESS_PATCHER, + LOCATION_DATA_SUCCESS_PATCHER, + SLEEP_DATA_SUCCESS_PATCHER, + ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/starlink/test_init.py b/tests/components/starlink/test_init.py index 4fdb6afc3ed..03e9787b6c0 100644 --- a/tests/components/starlink/test_init.py +++ b/tests/components/starlink/test_init.py @@ -21,7 +21,11 @@ async def test_successful_entry(hass: HomeAssistant) -> None: data={CONF_IP_ADDRESS: "1.2.3.4:0000"}, ) - with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER, SLEEP_DATA_SUCCESS_PATCHER: + with ( + STATUS_DATA_SUCCESS_PATCHER, + LOCATION_DATA_SUCCESS_PATCHER, + SLEEP_DATA_SUCCESS_PATCHER, + ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -38,7 +42,11 @@ async def test_unload_entry(hass: HomeAssistant) -> None: data={CONF_IP_ADDRESS: "1.2.3.4:0000"}, ) - with STATUS_DATA_SUCCESS_PATCHER, LOCATION_DATA_SUCCESS_PATCHER, SLEEP_DATA_SUCCESS_PATCHER: + with ( + STATUS_DATA_SUCCESS_PATCHER, + LOCATION_DATA_SUCCESS_PATCHER, + SLEEP_DATA_SUCCESS_PATCHER, + ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index d2891cf7b87..fd9a5ca85bd 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -1530,8 +1530,9 @@ async def test_initialize_from_database_with_maxage( await hass.async_block_till_done() await async_wait_recording_done(hass) - with freeze_time(current_time) as freezer, patch.object( - StatisticsSensor, "_purge_old_states", mock_purge + with ( + freeze_time(current_time) as freezer, + patch.object(StatisticsSensor, "_purge_old_states", mock_purge), ): for value in VALUES_NUMERIC: hass.states.async_set( diff --git a/tests/components/steam_online/test_config_flow.py b/tests/components/steam_online/test_config_flow.py index cbe02e1c3b6..00b47ea48bd 100644 --- a/tests/components/steam_online/test_config_flow.py +++ b/tests/components/steam_online/test_config_flow.py @@ -27,9 +27,12 @@ from . import ( async def test_flow_user(hass: HomeAssistant) -> None: """Test user initialized flow.""" - with patch_interface(), patch( - "homeassistant.components.steam_online.async_setup_entry", - return_value=True, + with ( + patch_interface(), + patch( + "homeassistant.components.steam_online.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -139,9 +142,12 @@ async def test_flow_reauth(hass: HomeAssistant) -> None: async def test_options_flow(hass: HomeAssistant) -> None: """Test updating options.""" entry = create_entry(hass) - with patch_interface(), patch( - "homeassistant.components.steam_online.config_flow.MAX_IDS_TO_REQUEST", - return_value=2, + with ( + patch_interface(), + patch( + "homeassistant.components.steam_online.config_flow.MAX_IDS_TO_REQUEST", + return_value=2, + ), ): await hass.config_entries.async_setup(entry.entry_id) result = await hass.config_entries.options.async_init(entry.entry_id) @@ -163,17 +169,23 @@ async def test_options_flow(hass: HomeAssistant) -> None: async def test_options_flow_deselect(hass: HomeAssistant) -> None: """Test deselecting user.""" entry = create_entry(hass) - with patch_interface(), patch( - "homeassistant.components.steam_online.config_flow.MAX_IDS_TO_REQUEST", - return_value=2, + with ( + patch_interface(), + patch( + "homeassistant.components.steam_online.config_flow.MAX_IDS_TO_REQUEST", + return_value=2, + ), ): await hass.config_entries.async_setup(entry.entry_id) result = await hass.config_entries.options.async_init(entry.entry_id) await hass.async_block_till_done() - with patch_interface(), patch( - "homeassistant.components.steam_online.async_setup_entry", - return_value=True, + with ( + patch_interface(), + patch( + "homeassistant.components.steam_online.async_setup_entry", + return_value=True, + ), ): assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "init" diff --git a/tests/components/steamist/test_config_flow.py b/tests/components/steamist/test_config_flow.py index 34dc92495a9..9480703af9f 100644 --- a/tests/components/steamist/test_config_flow.py +++ b/tests/components/steamist/test_config_flow.py @@ -45,12 +45,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with _patch_discovery(no_device=True), patch( - "homeassistant.components.steamist.config_flow.Steamist.async_get_status" - ), patch( - "homeassistant.components.steamist.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(no_device=True), + patch( + "homeassistant.components.steamist.config_flow.Steamist.async_get_status" + ), + patch( + "homeassistant.components.steamist.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -75,12 +79,16 @@ async def test_form_with_discovery(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with _patch_discovery(), patch( - "homeassistant.components.steamist.config_flow.Steamist.async_get_status" - ), patch( - "homeassistant.components.steamist.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + _patch_discovery(), + patch( + "homeassistant.components.steamist.config_flow.Steamist.async_get_status" + ), + patch( + "homeassistant.components.steamist.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -169,11 +177,12 @@ async def test_discovery(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: FORMATTED_MAC_ADDRESS}, @@ -254,11 +263,14 @@ async def test_discovered_by_discovery(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -282,11 +294,14 @@ async def test_discovered_by_dhcp(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -299,8 +314,9 @@ async def test_discovered_by_dhcp(hass: HomeAssistant) -> None: async def test_discovered_by_dhcp_discovery_fails(hass: HomeAssistant) -> None: """Test we can setup when discovered from dhcp but then we cannot get the device name.""" - with _patch_discovery(no_device=True), _patch_status( - MOCK_ASYNC_GET_STATUS_INACTIVE + with ( + _patch_discovery(no_device=True), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -318,8 +334,9 @@ async def test_discovered_by_dhcp_discovery_finds_non_steamist_device( ) -> None: """Test we can setup when discovered from dhcp but its not a steamist device.""" - with _patch_discovery(device=DEVICE_30303_NOT_STEAMIST), _patch_status( - MOCK_ASYNC_GET_STATUS_INACTIVE + with ( + _patch_discovery(device=DEVICE_30303_NOT_STEAMIST), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -346,11 +363,12 @@ async def test_discovered_by_dhcp_or_discovery_adds_missing_unique_id( config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_HOST: DEVICE_IP_ADDRESS}) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) @@ -380,11 +398,12 @@ async def test_discovered_by_dhcp_or_discovery_existing_unique_id_does_not_reloa ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_status(MOCK_ASYNC_GET_STATUS_INACTIVE), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) diff --git a/tests/components/steamist/test_init.py b/tests/components/steamist/test_init.py index a38b14ab15d..32400449d0d 100644 --- a/tests/components/steamist/test_init.py +++ b/tests/components/steamist/test_init.py @@ -92,9 +92,12 @@ async def test_config_entry_fills_unique_id_with_directed_discovery( mock_aio_discovery.async_scan = _async_scan type(mock_aio_discovery).found_devices = found_devices - with _patch_status(MOCK_ASYNC_GET_STATUS_ACTIVE), patch( - "homeassistant.components.steamist.discovery.AIODiscovery30303", - return_value=mock_aio_discovery, + with ( + _patch_status(MOCK_ASYNC_GET_STATUS_ACTIVE), + patch( + "homeassistant.components.steamist.discovery.AIODiscovery30303", + return_value=mock_aio_discovery, + ), ): await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) await hass.async_block_till_done() @@ -124,10 +127,13 @@ async def test_discovery_happens_at_interval( config_entry.add_to_hass(hass) mock_aio_discovery = MagicMock(auto_spec=AIODiscovery30303) mock_aio_discovery.async_scan = AsyncMock() - with patch( - "homeassistant.components.steamist.discovery.AIODiscovery30303", - return_value=mock_aio_discovery, - ), _patch_status(MOCK_ASYNC_GET_STATUS_ACTIVE): + with ( + patch( + "homeassistant.components.steamist.discovery.AIODiscovery30303", + return_value=mock_aio_discovery, + ), + _patch_status(MOCK_ASYNC_GET_STATUS_ACTIVE), + ): await async_setup_component(hass, steamist.DOMAIN, {steamist.DOMAIN: {}}) await hass.async_block_till_done(wait_background_tasks=True) diff --git a/tests/components/stream/conftest.py b/tests/components/stream/conftest.py index dd8b99f2af0..9ce23d99152 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -143,23 +143,29 @@ class HLSSync: def hls_sync(): """Patch HLSOutput to allow test to synchronize playlist requests and responses.""" sync = HLSSync() - with patch( - "homeassistant.components.stream.core.StreamOutput.recv", - side_effect=sync.recv, - autospec=True, - ), patch( - "homeassistant.components.stream.core.StreamOutput.part_recv", - side_effect=sync.part_recv, - autospec=True, - ), patch( - "homeassistant.components.stream.hls.web.HTTPBadRequest", - side_effect=sync.bad_request, - ), patch( - "homeassistant.components.stream.hls.web.HTTPNotFound", - side_effect=sync.not_found, - ), patch( - "homeassistant.components.stream.hls.web.Response", - side_effect=sync.response, + with ( + patch( + "homeassistant.components.stream.core.StreamOutput.recv", + side_effect=sync.recv, + autospec=True, + ), + patch( + "homeassistant.components.stream.core.StreamOutput.part_recv", + side_effect=sync.part_recv, + autospec=True, + ), + patch( + "homeassistant.components.stream.hls.web.HTTPBadRequest", + side_effect=sync.bad_request, + ), + patch( + "homeassistant.components.stream.hls.web.HTTPNotFound", + side_effect=sync.not_found, + ), + patch( + "homeassistant.components.stream.hls.web.Response", + side_effect=sync.response, + ), ): yield sync diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 7f9afaf1234..6a20914250e 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -311,9 +311,11 @@ async def test_stream_retries( hass.loop.call_soon_threadsafe(futures.pop().set_result, None) raise av.error.InvalidDataError(-2, "error") - with patch("av.open") as av_open, patch( - "homeassistant.components.stream.Stream._set_state", set_state_wrapper - ), patch("homeassistant.components.stream.STREAM_RESTART_INCREMENT", 0): + with ( + patch("av.open") as av_open, + patch("homeassistant.components.stream.Stream._set_state", set_state_wrapper), + patch("homeassistant.components.stream.STREAM_RESTART_INCREMENT", 0), + ): av_open.side_effect = av_open_side_effect # Request stream. Enable retries which are disabled by default in tests. should_retry.return_value = True diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 35b8c2442f4..515f3fff82d 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -101,9 +101,10 @@ async def test_record_path_not_allowed(hass: HomeAssistant, h264_video) -> None: """Test where the output path is not allowed by home assistant configuration.""" stream = create_stream(hass, h264_video, {}, dynamic_stream_settings()) - with patch.object( - hass.config, "is_allowed_path", return_value=False - ), pytest.raises(HomeAssistantError): + with ( + patch.object(hass.config, "is_allowed_path", return_value=False), + pytest.raises(HomeAssistantError), + ): await stream.async_record("/example/path") @@ -149,9 +150,11 @@ async def test_recorder_discontinuity( provider_ready.set() return provider - with patch.object(hass.config, "is_allowed_path", return_value=True), patch( - "homeassistant.components.stream.Stream", wraps=MockStream - ), patch("homeassistant.components.stream.recorder.RecorderOutput.recv"): + with ( + patch.object(hass.config, "is_allowed_path", return_value=True), + patch("homeassistant.components.stream.Stream", wraps=MockStream), + patch("homeassistant.components.stream.recorder.RecorderOutput.recv"), + ): stream = create_stream(hass, "blank", {}, dynamic_stream_settings()) make_recording = hass.async_create_task(stream.async_record(filename)) await provider_ready.wait() diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 92221ebb88d..0d47a63a000 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -311,9 +311,12 @@ async def async_decode_stream(hass, packets, py_av=None, stream_settings=None): py_av = MockPyAv() py_av.container.packets = iter(packets) # Can't be rewound - with patch("av.open", new=py_av.open), patch( - "homeassistant.components.stream.core.StreamOutput.put", - side_effect=py_av.capture_buffer.capture_output_segment, + with ( + patch("av.open", new=py_av.open), + patch( + "homeassistant.components.stream.core.StreamOutput.put", + side_effect=py_av.capture_buffer.capture_output_segment, + ), ): try: run_worker(hass, stream, STREAM_SOURCE, stream_settings) @@ -986,8 +989,9 @@ async def test_get_image(hass: HomeAssistant, h264_video, filename) -> None: worker_wake.wait() return temp_av_open(stream_source, *args, **kwargs) - with patch.object(hass.config, "is_allowed_path", return_value=True), patch( - "av.open", new=blocking_open + with ( + patch.object(hass.config, "is_allowed_path", return_value=True), + patch("av.open", new=blocking_open), ): make_recording = hass.async_create_task(stream.async_record(filename)) assert stream._keyframe_converter._image is None diff --git a/tests/components/subaru/conftest.py b/tests/components/subaru/conftest.py index 3f01ad2da18..446f025e077 100644 --- a/tests/components/subaru/conftest.py +++ b/tests/components/subaru/conftest.py @@ -112,46 +112,61 @@ async def setup_subaru_config_entry( fetch_effect=None, ): """Run async_setup with API mocks in place.""" - with patch( - MOCK_API_CONNECT, - return_value=connect_effect is None, - side_effect=connect_effect, - ), patch( - MOCK_API_GET_VEHICLES, - return_value=vehicle_list, - ), patch( - MOCK_API_VIN_TO_NAME, - return_value=vehicle_data[VEHICLE_NAME], - ), patch( - MOCK_API_GET_API_GEN, - return_value=vehicle_data[VEHICLE_API_GEN], - ), patch( - MOCK_API_GET_MODEL_NAME, - return_value=vehicle_data[VEHICLE_MODEL_NAME], - ), patch( - MOCK_API_GET_MODEL_YEAR, - return_value=vehicle_data[VEHICLE_MODEL_YEAR], - ), patch( - MOCK_API_GET_EV_STATUS, - return_value=vehicle_data[VEHICLE_HAS_EV], - ), patch( - MOCK_API_GET_RES_STATUS, - return_value=vehicle_data[VEHICLE_HAS_REMOTE_START], - ), patch( - MOCK_API_GET_REMOTE_STATUS, - return_value=vehicle_data[VEHICLE_HAS_REMOTE_SERVICE], - ), patch( - MOCK_API_GET_SAFETY_STATUS, - return_value=vehicle_data[VEHICLE_HAS_SAFETY_SERVICE], - ), patch( - MOCK_API_GET_SUBSCRIPTION_STATUS, - return_value=True, - ), patch( - MOCK_API_GET_DATA, - return_value=vehicle_status, - ), patch( - MOCK_API_UPDATE, - ), patch(MOCK_API_FETCH, side_effect=fetch_effect): + with ( + patch( + MOCK_API_CONNECT, + return_value=connect_effect is None, + side_effect=connect_effect, + ), + patch( + MOCK_API_GET_VEHICLES, + return_value=vehicle_list, + ), + patch( + MOCK_API_VIN_TO_NAME, + return_value=vehicle_data[VEHICLE_NAME], + ), + patch( + MOCK_API_GET_API_GEN, + return_value=vehicle_data[VEHICLE_API_GEN], + ), + patch( + MOCK_API_GET_MODEL_NAME, + return_value=vehicle_data[VEHICLE_MODEL_NAME], + ), + patch( + MOCK_API_GET_MODEL_YEAR, + return_value=vehicle_data[VEHICLE_MODEL_YEAR], + ), + patch( + MOCK_API_GET_EV_STATUS, + return_value=vehicle_data[VEHICLE_HAS_EV], + ), + patch( + MOCK_API_GET_RES_STATUS, + return_value=vehicle_data[VEHICLE_HAS_REMOTE_START], + ), + patch( + MOCK_API_GET_REMOTE_STATUS, + return_value=vehicle_data[VEHICLE_HAS_REMOTE_SERVICE], + ), + patch( + MOCK_API_GET_SAFETY_STATUS, + return_value=vehicle_data[VEHICLE_HAS_SAFETY_SERVICE], + ), + patch( + MOCK_API_GET_SUBSCRIPTION_STATUS, + return_value=True, + ), + patch( + MOCK_API_GET_DATA, + return_value=vehicle_status, + ), + patch( + MOCK_API_UPDATE, + ), + patch(MOCK_API_FETCH, side_effect=fetch_effect), + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/subaru/test_config_flow.py b/tests/components/subaru/test_config_flow.py index 919c59c6d1b..76bad81bff4 100644 --- a/tests/components/subaru/test_config_flow.py +++ b/tests/components/subaru/test_config_flow.py @@ -102,15 +102,17 @@ async def test_user_form_pin_not_required( hass: HomeAssistant, two_factor_verify_form ) -> None: """Test successful login when no PIN is required.""" - with patch( - MOCK_API_2FA_VERIFY, - return_value=True, - ) as mock_two_factor_verify, patch( - MOCK_API_IS_PIN_REQUIRED, - return_value=False, - ) as mock_is_pin_required, patch( - ASYNC_SETUP_ENTRY, return_value=True - ) as mock_setup_entry: + with ( + patch( + MOCK_API_2FA_VERIFY, + return_value=True, + ) as mock_two_factor_verify, + patch( + MOCK_API_IS_PIN_REQUIRED, + return_value=False, + ) as mock_is_pin_required, + patch(ASYNC_SETUP_ENTRY, return_value=True) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( two_factor_verify_form["flow_id"], user_input={config_flow.CONF_VALIDATION_CODE: "123456"}, @@ -141,9 +143,13 @@ async def test_user_form_pin_not_required( async def test_registered_pin_required(hass: HomeAssistant, user_form) -> None: """Test if the device is already registered and PIN required.""" - with patch(MOCK_API_CONNECT, return_value=True), patch( - MOCK_API_DEVICE_REGISTERED, new_callable=PropertyMock - ) as mock_device_registered, patch(MOCK_API_IS_PIN_REQUIRED, return_value=True): + with ( + patch(MOCK_API_CONNECT, return_value=True), + patch( + MOCK_API_DEVICE_REGISTERED, new_callable=PropertyMock + ) as mock_device_registered, + patch(MOCK_API_IS_PIN_REQUIRED, return_value=True), + ): mock_device_registered.return_value = True await hass.config_entries.flow.async_configure( user_form["flow_id"], user_input=TEST_CREDS @@ -152,9 +158,13 @@ async def test_registered_pin_required(hass: HomeAssistant, user_form) -> None: async def test_registered_no_pin_required(hass: HomeAssistant, user_form) -> None: """Test if the device is already registered and PIN not required.""" - with patch(MOCK_API_CONNECT, return_value=True), patch( - MOCK_API_DEVICE_REGISTERED, new_callable=PropertyMock - ) as mock_device_registered, patch(MOCK_API_IS_PIN_REQUIRED, return_value=False): + with ( + patch(MOCK_API_CONNECT, return_value=True), + patch( + MOCK_API_DEVICE_REGISTERED, new_callable=PropertyMock + ) as mock_device_registered, + patch(MOCK_API_IS_PIN_REQUIRED, return_value=False), + ): mock_device_registered.return_value = True await hass.config_entries.flow.async_configure( user_form["flow_id"], user_input=TEST_CREDS @@ -165,12 +175,13 @@ async def test_two_factor_request_success( hass: HomeAssistant, two_factor_start_form ) -> None: """Test two factor contact method selection.""" - with patch( - MOCK_API_2FA_REQUEST, - return_value=True, - ) as mock_two_factor_request, patch( - MOCK_API_2FA_CONTACTS, new_callable=PropertyMock - ) as mock_contacts: + with ( + patch( + MOCK_API_2FA_REQUEST, + return_value=True, + ) as mock_two_factor_request, + patch(MOCK_API_2FA_CONTACTS, new_callable=PropertyMock) as mock_contacts, + ): mock_contacts.return_value = MOCK_2FA_CONTACTS await hass.config_entries.flow.async_configure( two_factor_start_form["flow_id"], @@ -183,12 +194,13 @@ async def test_two_factor_request_fail( hass: HomeAssistant, two_factor_start_form ) -> None: """Test two factor auth request failure.""" - with patch( - MOCK_API_2FA_REQUEST, - return_value=False, - ) as mock_two_factor_request, patch( - MOCK_API_2FA_CONTACTS, new_callable=PropertyMock - ) as mock_contacts: + with ( + patch( + MOCK_API_2FA_REQUEST, + return_value=False, + ) as mock_two_factor_request, + patch(MOCK_API_2FA_CONTACTS, new_callable=PropertyMock) as mock_contacts, + ): mock_contacts.return_value = MOCK_2FA_CONTACTS result = await hass.config_entries.flow.async_configure( two_factor_start_form["flow_id"], @@ -203,12 +215,13 @@ async def test_two_factor_verify_success( hass: HomeAssistant, two_factor_verify_form ) -> None: """Test two factor verification.""" - with patch( - MOCK_API_2FA_VERIFY, - return_value=True, - ) as mock_two_factor_verify, patch( - MOCK_API_IS_PIN_REQUIRED, return_value=True - ) as mock_is_in_required: + with ( + patch( + MOCK_API_2FA_VERIFY, + return_value=True, + ) as mock_two_factor_verify, + patch(MOCK_API_IS_PIN_REQUIRED, return_value=True) as mock_is_in_required, + ): await hass.config_entries.flow.async_configure( two_factor_verify_form["flow_id"], user_input={config_flow.CONF_VALIDATION_CODE: "123456"}, @@ -221,12 +234,13 @@ async def test_two_factor_verify_bad_format( hass: HomeAssistant, two_factor_verify_form ) -> None: """Test two factor verification bad format.""" - with patch( - MOCK_API_2FA_VERIFY, - return_value=False, - ) as mock_two_factor_verify, patch( - MOCK_API_IS_PIN_REQUIRED, return_value=True - ) as mock_is_pin_required: + with ( + patch( + MOCK_API_2FA_VERIFY, + return_value=False, + ) as mock_two_factor_verify, + patch(MOCK_API_IS_PIN_REQUIRED, return_value=True) as mock_is_pin_required, + ): result = await hass.config_entries.flow.async_configure( two_factor_verify_form["flow_id"], user_input={config_flow.CONF_VALIDATION_CODE: "1234567"}, @@ -240,12 +254,13 @@ async def test_two_factor_verify_fail( hass: HomeAssistant, two_factor_verify_form ) -> None: """Test two factor verification failure.""" - with patch( - MOCK_API_2FA_VERIFY, - return_value=False, - ) as mock_two_factor_verify, patch( - MOCK_API_IS_PIN_REQUIRED, return_value=True - ) as mock_is_pin_required: + with ( + patch( + MOCK_API_2FA_VERIFY, + return_value=False, + ) as mock_two_factor_verify, + patch(MOCK_API_IS_PIN_REQUIRED, return_value=True) as mock_is_pin_required, + ): result = await hass.config_entries.flow.async_configure( two_factor_verify_form["flow_id"], user_input={config_flow.CONF_VALIDATION_CODE: "123456"}, @@ -273,12 +288,15 @@ async def test_pin_form_init(pin_form) -> None: async def test_pin_form_bad_pin_format(hass: HomeAssistant, pin_form) -> None: """Test we handle invalid pin.""" - with patch( - MOCK_API_TEST_PIN, - ) as mock_test_pin, patch( - MOCK_API_UPDATE_SAVED_PIN, - return_value=True, - ) as mock_update_saved_pin: + with ( + patch( + MOCK_API_TEST_PIN, + ) as mock_test_pin, + patch( + MOCK_API_UPDATE_SAVED_PIN, + return_value=True, + ) as mock_update_saved_pin, + ): result = await hass.config_entries.flow.async_configure( pin_form["flow_id"], user_input={CONF_PIN: "abcd"} ) @@ -290,15 +308,17 @@ async def test_pin_form_bad_pin_format(hass: HomeAssistant, pin_form) -> None: async def test_pin_form_success(hass: HomeAssistant, pin_form) -> None: """Test successful PIN entry.""" - with patch( - MOCK_API_TEST_PIN, - return_value=True, - ) as mock_test_pin, patch( - MOCK_API_UPDATE_SAVED_PIN, - return_value=True, - ) as mock_update_saved_pin, patch( - ASYNC_SETUP_ENTRY, return_value=True - ) as mock_setup_entry: + with ( + patch( + MOCK_API_TEST_PIN, + return_value=True, + ) as mock_test_pin, + patch( + MOCK_API_UPDATE_SAVED_PIN, + return_value=True, + ) as mock_update_saved_pin, + patch(ASYNC_SETUP_ENTRY, return_value=True) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( pin_form["flow_id"], user_input={CONF_PIN: TEST_PIN} ) @@ -326,13 +346,16 @@ async def test_pin_form_success(hass: HomeAssistant, pin_form) -> None: async def test_pin_form_incorrect_pin(hass: HomeAssistant, pin_form) -> None: """Test we handle invalid pin.""" - with patch( - MOCK_API_TEST_PIN, - side_effect=InvalidPIN("invalidPin"), - ) as mock_test_pin, patch( - MOCK_API_UPDATE_SAVED_PIN, - return_value=True, - ) as mock_update_saved_pin: + with ( + patch( + MOCK_API_TEST_PIN, + side_effect=InvalidPIN("invalidPin"), + ) as mock_test_pin, + patch( + MOCK_API_UPDATE_SAVED_PIN, + return_value=True, + ) as mock_update_saved_pin, + ): result = await hass.config_entries.flow.async_configure( pin_form["flow_id"], user_input={CONF_PIN: TEST_PIN} ) @@ -375,9 +398,10 @@ async def user_form(hass): @pytest.fixture async def two_factor_start_form(hass, user_form): """Return two factor form for Subaru config flow.""" - with patch(MOCK_API_CONNECT, return_value=True), patch( - MOCK_API_2FA_CONTACTS, new_callable=PropertyMock - ) as mock_contacts: + with ( + patch(MOCK_API_CONNECT, return_value=True), + patch(MOCK_API_2FA_CONTACTS, new_callable=PropertyMock) as mock_contacts, + ): mock_contacts.return_value = MOCK_2FA_CONTACTS return await hass.config_entries.flow.async_configure( user_form["flow_id"], user_input=TEST_CREDS @@ -387,10 +411,13 @@ async def two_factor_start_form(hass, user_form): @pytest.fixture async def two_factor_verify_form(hass, two_factor_start_form): """Return two factor form for Subaru config flow.""" - with patch( - MOCK_API_2FA_REQUEST, - return_value=True, - ), patch(MOCK_API_2FA_CONTACTS, new_callable=PropertyMock) as mock_contacts: + with ( + patch( + MOCK_API_2FA_REQUEST, + return_value=True, + ), + patch(MOCK_API_2FA_CONTACTS, new_callable=PropertyMock) as mock_contacts, + ): mock_contacts.return_value = MOCK_2FA_CONTACTS return await hass.config_entries.flow.async_configure( two_factor_start_form["flow_id"], @@ -401,10 +428,13 @@ async def two_factor_verify_form(hass, two_factor_start_form): @pytest.fixture async def pin_form(hass, two_factor_verify_form): """Return PIN input form for Subaru config flow.""" - with patch( - MOCK_API_2FA_VERIFY, - return_value=True, - ), patch(MOCK_API_IS_PIN_REQUIRED, return_value=True): + with ( + patch( + MOCK_API_2FA_VERIFY, + return_value=True, + ), + patch(MOCK_API_IS_PIN_REQUIRED, return_value=True), + ): return await hass.config_entries.flow.async_configure( two_factor_verify_form["flow_id"], user_input={config_flow.CONF_VALIDATION_CODE: "123456"}, diff --git a/tests/components/subaru/test_init.py b/tests/components/subaru/test_init.py index da5a754bedc..e25a8681bef 100644 --- a/tests/components/subaru/test_init.py +++ b/tests/components/subaru/test_init.py @@ -126,12 +126,15 @@ async def test_update_skip_unsubscribed( async def test_update_disabled(hass: HomeAssistant, ev_entry) -> None: """Test update function disable option.""" - with patch( - MOCK_API_FETCH, - side_effect=SubaruException("403 Error"), - ), patch( - MOCK_API_UPDATE, - ) as mock_update: + with ( + patch( + MOCK_API_FETCH, + side_effect=SubaruException("403 Error"), + ), + patch( + MOCK_API_UPDATE, + ) as mock_update, + ): await hass.services.async_call( HA_DOMAIN, SERVICE_UPDATE_ENTITY, diff --git a/tests/components/subaru/test_lock.py b/tests/components/subaru/test_lock.py index e90a0278ee5..4d19d49579e 100644 --- a/tests/components/subaru/test_lock.py +++ b/tests/components/subaru/test_lock.py @@ -53,8 +53,9 @@ async def test_unlock_cmd(hass: HomeAssistant, ev_entry) -> None: async def test_lock_cmd_fails(hass: HomeAssistant, ev_entry) -> None: """Test subaru lock request that initiates but fails.""" - with patch(MOCK_API_LOCK, return_value=False) as mock_lock, pytest.raises( - HomeAssistantError + with ( + patch(MOCK_API_LOCK, return_value=False) as mock_lock, + pytest.raises(HomeAssistantError), ): await hass.services.async_call( LOCK_DOMAIN, SERVICE_UNLOCK, {ATTR_ENTITY_ID: DEVICE_ID}, blocking=True diff --git a/tests/components/subaru/test_sensor.py b/tests/components/subaru/test_sensor.py index faf57e4a5cf..de1df044d71 100644 --- a/tests/components/subaru/test_sensor.py +++ b/tests/components/subaru/test_sensor.py @@ -35,8 +35,9 @@ async def test_sensors_ev_imperial(hass: HomeAssistant, ev_entry) -> None: """Test sensors supporting imperial units.""" hass.config.units = US_CUSTOMARY_SYSTEM - with patch(MOCK_API_FETCH), patch( - MOCK_API_GET_DATA, return_value=VEHICLE_STATUS_EV + with ( + patch(MOCK_API_FETCH), + patch(MOCK_API_GET_DATA, return_value=VEHICLE_STATUS_EV), ): advance_time_to_next_fetch(hass) await hass.async_block_till_done() diff --git a/tests/components/suez_water/test_config_flow.py b/tests/components/suez_water/test_config_flow.py index 73d3c572498..a4ab52151d1 100644 --- a/tests/components/suez_water/test_config_flow.py +++ b/tests/components/suez_water/test_config_flow.py @@ -49,12 +49,15 @@ async def test_form_invalid_auth( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.suez_water.config_flow.SuezClient.__init__", - return_value=None, - ), patch( - "homeassistant.components.suez_water.config_flow.SuezClient.check_credentials", - return_value=False, + with ( + patch( + "homeassistant.components.suez_water.config_flow.SuezClient.__init__", + return_value=None, + ), + patch( + "homeassistant.components.suez_water.config_flow.SuezClient.check_credentials", + return_value=False, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -179,12 +182,15 @@ async def test_import_error( async def test_importing_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth when importing.""" - with patch( - "homeassistant.components.suez_water.config_flow.SuezClient.__init__", - return_value=None, - ), patch( - "homeassistant.components.suez_water.config_flow.SuezClient.check_credentials", - return_value=False, + with ( + patch( + "homeassistant.components.suez_water.config_flow.SuezClient.__init__", + return_value=None, + ), + patch( + "homeassistant.components.suez_water.config_flow.SuezClient.check_credentials", + return_value=False, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=MOCK_DATA diff --git a/tests/components/sunweg/test_config_flow.py b/tests/components/sunweg/test_config_flow.py index e45e671f752..84957a419dd 100644 --- a/tests/components/sunweg/test_config_flow.py +++ b/tests/components/sunweg/test_config_flow.py @@ -46,8 +46,9 @@ async def test_no_plants_on_account(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch.object(APIHelper, "authenticate", return_value=True), patch.object( - APIHelper, "listPlants", return_value=[] + with ( + patch.object(APIHelper, "authenticate", return_value=True), + patch.object(APIHelper, "listPlants", return_value=[]), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], SUNWEG_USER_INPUT @@ -65,8 +66,9 @@ async def test_multiple_plant_ids(hass: HomeAssistant, plant_fixture) -> None: user_input = SUNWEG_USER_INPUT.copy() plant_list = [plant_fixture, plant_fixture] - with patch.object(APIHelper, "authenticate", return_value=True), patch.object( - APIHelper, "listPlants", return_value=plant_list + with ( + patch.object(APIHelper, "authenticate", return_value=True), + patch.object(APIHelper, "listPlants", return_value=plant_list), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input @@ -93,10 +95,13 @@ async def test_one_plant_on_account(hass: HomeAssistant, plant_fixture) -> None: ) user_input = SUNWEG_USER_INPUT.copy() - with patch.object(APIHelper, "authenticate", return_value=True), patch.object( - APIHelper, - "listPlants", - return_value=[plant_fixture], + with ( + patch.object(APIHelper, "authenticate", return_value=True), + patch.object( + APIHelper, + "listPlants", + return_value=[plant_fixture], + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input @@ -117,10 +122,13 @@ async def test_existing_plant_configured(hass: HomeAssistant, plant_fixture) -> ) user_input = SUNWEG_USER_INPUT.copy() - with patch.object(APIHelper, "authenticate", return_value=True), patch.object( - APIHelper, - "listPlants", - return_value=[plant_fixture], + with ( + patch.object(APIHelper, "authenticate", return_value=True), + patch.object( + APIHelper, + "listPlants", + return_value=[plant_fixture], + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input diff --git a/tests/components/sunweg/test_init.py b/tests/components/sunweg/test_init.py index 5707fd93b99..cc2e880d82e 100644 --- a/tests/components/sunweg/test_init.py +++ b/tests/components/sunweg/test_init.py @@ -21,11 +21,13 @@ async def test_methods(hass: HomeAssistant, plant_fixture, inverter_fixture) -> mock_entry = SUNWEG_MOCK_ENTRY mock_entry.add_to_hass(hass) - with patch.object(APIHelper, "authenticate", return_value=True), patch.object( - APIHelper, "listPlants", return_value=[plant_fixture] - ), patch.object(APIHelper, "plant", return_value=plant_fixture), patch.object( - APIHelper, "inverter", return_value=inverter_fixture - ), patch.object(APIHelper, "complete_inverter"): + with ( + patch.object(APIHelper, "authenticate", return_value=True), + patch.object(APIHelper, "listPlants", return_value=[plant_fixture]), + patch.object(APIHelper, "plant", return_value=plant_fixture), + patch.object(APIHelper, "inverter", return_value=inverter_fixture), + patch.object(APIHelper, "complete_inverter"), + ): assert await async_setup_component(hass, DOMAIN, mock_entry.data) await hass.async_block_till_done() assert await hass.config_entries.async_unload(mock_entry.entry_id) diff --git a/tests/components/switchbee/test_config_flow.py b/tests/components/switchbee/test_config_flow.py index c31ada589c8..99c44365353 100644 --- a/tests/components/switchbee/test_config_flow.py +++ b/tests/components/switchbee/test_config_flow.py @@ -32,15 +32,20 @@ async def test_form(hass: HomeAssistant, test_cucode_in_coordinator_data) -> Non assert result["type"] == "form" assert result["errors"] == {} - with patch( - "switchbee.api.polling.CentralUnitPolling.get_configuration", - return_value=coordinator_data, - ), patch( - "homeassistant.components.switchbee.async_setup_entry", - return_value=True, - ), patch( - "switchbee.api.polling.CentralUnitPolling.fetch_states", return_value=None - ), patch("switchbee.api.polling.CentralUnitPolling._login", return_value=None): + with ( + patch( + "switchbee.api.polling.CentralUnitPolling.get_configuration", + return_value=coordinator_data, + ), + patch( + "homeassistant.components.switchbee.async_setup_entry", + return_value=True, + ), + patch( + "switchbee.api.polling.CentralUnitPolling.fetch_states", return_value=None + ), + patch("switchbee.api.polling.CentralUnitPolling._login", return_value=None), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -149,16 +154,19 @@ async def test_form_entry_exists(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "switchbee.api.polling.CentralUnitPolling._login", return_value=None - ), patch( - "homeassistant.components.switchbee.async_setup_entry", - return_value=True, - ), patch( - "switchbee.api.polling.CentralUnitPolling.get_configuration", - return_value=coordinator_data, - ), patch( - "switchbee.api.polling.CentralUnitPolling.fetch_states", return_value=None + with ( + patch("switchbee.api.polling.CentralUnitPolling._login", return_value=None), + patch( + "homeassistant.components.switchbee.async_setup_entry", + return_value=True, + ), + patch( + "switchbee.api.polling.CentralUnitPolling.get_configuration", + return_value=coordinator_data, + ), + patch( + "switchbee.api.polling.CentralUnitPolling.fetch_states", return_value=None + ), ): form_result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/switchbot/test_config_flow.py b/tests/components/switchbot/test_config_flow.py index 350957db0dd..3d53dd2848e 100644 --- a/tests/components/switchbot/test_config_flow.py +++ b/tests/components/switchbot/test_config_flow.py @@ -129,9 +129,12 @@ async def test_bluetooth_discovery_lock_key(hass: HomeAssistant) -> None: assert result["step_id"] == "lock_key" assert result["errors"] == {"base": "encryption_key_invalid"} - with patch_async_setup_entry() as mock_setup_entry, patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", - return_value=True, + with ( + patch_async_setup_entry() as mock_setup_entry, + patch( + "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -434,9 +437,12 @@ async def test_user_setup_wolock_key(hass: HomeAssistant) -> None: assert result["step_id"] == "lock_key" assert result["errors"] == {"base": "encryption_key_invalid"} - with patch_async_setup_entry() as mock_setup_entry, patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", - return_value=True, + with ( + patch_async_setup_entry() as mock_setup_entry, + patch( + "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -497,15 +503,19 @@ async def test_user_setup_wolock_auth(hass: HomeAssistant) -> None: assert result["errors"] == {"base": "auth_failed"} assert "error from api" in result["description_placeholders"]["error_detail"] - with patch_async_setup_entry() as mock_setup_entry, patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", - return_value=True, - ), patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", - return_value={ - CONF_KEY_ID: "ff", - CONF_ENCRYPTION_KEY: "ffffffffffffffffffffffffffffffff", - }, + with ( + patch_async_setup_entry() as mock_setup_entry, + patch( + "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", + return_value=True, + ), + patch( + "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", + return_value={ + CONF_KEY_ID: "ff", + CONF_ENCRYPTION_KEY: "ffffffffffffffffffffffffffffffff", + }, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -598,9 +608,12 @@ async def test_user_setup_wolock_or_bot(hass: HomeAssistant) -> None: assert result["step_id"] == "lock_key" assert result["errors"] == {} - with patch_async_setup_entry() as mock_setup_entry, patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", - return_value=True, + with ( + patch_async_setup_entry() as mock_setup_entry, + patch( + "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/syncthing/test_config_flow.py b/tests/components/syncthing/test_config_flow.py index c7b3c1a3498..d97226e422c 100644 --- a/tests/components/syncthing/test_config_flow.py +++ b/tests/components/syncthing/test_config_flow.py @@ -37,12 +37,13 @@ async def test_show_setup_form(hass: HomeAssistant) -> None: async def test_flow_successful(hass: HomeAssistant) -> None: """Test with required fields only.""" - with patch( - "aiosyncthing.system.System.status", return_value={"myID": "server-id"} - ), patch( - "homeassistant.components.syncthing.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aiosyncthing.system.System.status", return_value={"myID": "server-id"}), + patch( + "homeassistant.components.syncthing.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "user"}, diff --git a/tests/components/syncthru/test_config_flow.py b/tests/components/syncthru/test_config_flow.py index a413789dc60..90470431ade 100644 --- a/tests/components/syncthru/test_config_flow.py +++ b/tests/components/syncthru/test_config_flow.py @@ -91,8 +91,9 @@ async def test_syncthru_not_supported(hass: HomeAssistant) -> None: async def test_unknown_state(hass: HomeAssistant) -> None: """Test we show user form on unsupported device.""" - with patch.object(SyncThru, "update"), patch.object( - SyncThru, "is_unknown_state", return_value=True + with ( + patch.object(SyncThru, "update"), + patch.object(SyncThru, "is_unknown_state", return_value=True), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index d0fd3d37b13..67da3712983 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -381,12 +381,15 @@ async def test_reconfig_user(hass: HomeAssistant, service: MagicMock) -> None: unique_id=SERIAL, ).add_to_hass(hass) - with patch( - "homeassistant.config_entries.ConfigEntries.async_reload", - return_value=True, - ), patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSM", - return_value=service, + with ( + patch( + "homeassistant.config_entries.ConfigEntries.async_reload", + return_value=True, + ), + patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/synology_dsm/test_init.py b/tests/components/synology_dsm/test_init.py index bc8086b8d38..25c4d69dfee 100644 --- a/tests/components/synology_dsm/test_init.py +++ b/tests/components/synology_dsm/test_init.py @@ -23,10 +23,13 @@ from tests.common import MockConfigEntry async def test_services_registered(hass: HomeAssistant, mock_dsm: MagicMock) -> None: """Test if all services are registered.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=mock_dsm, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=mock_dsm, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -46,17 +49,20 @@ async def test_services_registered(hass: HomeAssistant, mock_dsm: MagicMock) -> async def test_reauth_triggered(hass: HomeAssistant) -> None: """Test if reauthentication flow is triggered.""" - with patch( - "homeassistant.components.synology_dsm.SynoApi.async_setup", - side_effect=SynologyDSMLoginInvalidException(USERNAME), - ), patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSMFlowHandler.async_step_reauth", - return_value={ - "type": data_entry_flow.FlowResultType.FORM, - "flow_id": "mock_flow", - "step_id": "reauth_confirm", - }, - ) as mock_async_step_reauth: + with ( + patch( + "homeassistant.components.synology_dsm.SynoApi.async_setup", + side_effect=SynologyDSMLoginInvalidException(USERNAME), + ), + patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSMFlowHandler.async_step_reauth", + return_value={ + "type": data_entry_flow.FlowResultType.FORM, + "flow_id": "mock_flow", + "step_id": "reauth_confirm", + }, + ) as mock_async_step_reauth, + ): entry = MockConfigEntry( domain=DOMAIN, data={ diff --git a/tests/components/synology_dsm/test_media_source.py b/tests/components/synology_dsm/test_media_source.py index e0aadf9260c..e806014dcd6 100644 --- a/tests/components/synology_dsm/test_media_source.py +++ b/tests/components/synology_dsm/test_media_source.py @@ -130,10 +130,13 @@ async def test_browse_media_album_error( hass: HomeAssistant, dsm_with_photos: MagicMock ) -> None: """Test browse_media with unknown album.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -169,10 +172,13 @@ async def test_browse_media_get_root( hass: HomeAssistant, dsm_with_photos: MagicMock ) -> None: """Test browse_media returning root media sources.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -203,10 +209,13 @@ async def test_browse_media_get_albums( hass: HomeAssistant, dsm_with_photos: MagicMock ) -> None: """Test browse_media returning albums.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -241,10 +250,13 @@ async def test_browse_media_get_items_error( hass: HomeAssistant, dsm_with_photos: MagicMock ) -> None: """Test browse_media returning albums.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -288,10 +300,13 @@ async def test_browse_media_get_items_thumbnail_error( hass: HomeAssistant, dsm_with_photos: MagicMock ) -> None: """Test browse_media returning albums.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -327,10 +342,13 @@ async def test_browse_media_get_items( hass: HomeAssistant, dsm_with_photos: MagicMock ) -> None: """Test browse_media returning albums.""" - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ @@ -376,10 +394,13 @@ async def test_media_view( with pytest.raises(web.HTTPNotFound): await view.get(request, "", "") - with patch( - "homeassistant.components.synology_dsm.common.SynologyDSM", - return_value=dsm_with_photos, - ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): + with ( + patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=dsm_with_photos, + ), + patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]), + ): entry = MockConfigEntry( domain=DOMAIN, data={ diff --git a/tests/components/system_bridge/test_config_flow.py b/tests/components/system_bridge/test_config_flow.py index d9a30610d65..0047cc62365 100644 --- a/tests/components/system_bridge/test_config_flow.py +++ b/tests/components/system_bridge/test_config_flow.py @@ -45,18 +45,23 @@ async def test_user_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - return_value=FIXTURE_DATA_RESPONSE, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, - ), patch( - "homeassistant.components.system_bridge.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + return_value=FIXTURE_DATA_RESPONSE, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), + patch( + "homeassistant.components.system_bridge.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT ) @@ -100,14 +105,18 @@ async def test_form_connection_closed_cannot_connect(hass: HomeAssistant) -> Non assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=ConnectionClosedException, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=ConnectionClosedException, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -128,14 +137,18 @@ async def test_form_timeout_cannot_connect(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=TimeoutError, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=TimeoutError, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -156,14 +169,18 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=AuthenticationException, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=AuthenticationException, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -184,14 +201,18 @@ async def test_form_uuid_error(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=ValueError, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=ValueError, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -212,14 +233,18 @@ async def test_form_unknown_error(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] is None - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=Exception, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=Exception, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_USER_INPUT @@ -240,14 +265,18 @@ async def test_reauth_authorization_error(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "authenticate" - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=AuthenticationException, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=AuthenticationException, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT @@ -281,14 +310,18 @@ async def test_reauth_connection_error(hass: HomeAssistant) -> None: assert result2["step_id"] == "authenticate" assert result2["errors"] == {"base": "cannot_connect"} - with patch( - "systembridgeconnector.websocket_client.WebSocketClient.connect", - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - return_value=None, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "systembridgeconnector.websocket_client.WebSocketClient.connect", + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + return_value=None, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT @@ -309,14 +342,18 @@ async def test_reauth_connection_closed_error(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "authenticate" - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - side_effect=ConnectionClosedException, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + side_effect=ConnectionClosedException, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT @@ -342,17 +379,22 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "authenticate" - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - return_value=FIXTURE_DATA_RESPONSE, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, - ), patch( - "homeassistant.components.system_bridge.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + return_value=FIXTURE_DATA_RESPONSE, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), + patch( + "homeassistant.components.system_bridge.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT @@ -375,18 +417,23 @@ async def test_zeroconf_flow(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert not result["errors"] - with patch( - "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.get_data", - return_value=FIXTURE_DATA_RESPONSE, - ), patch( - "systembridgeconnector.websocket_client.WebSocketClient.listen", - new=mock_data_listener, - ), patch( - "homeassistant.components.system_bridge.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.system_bridge.config_flow.WebSocketClient.connect" + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.get_data", + return_value=FIXTURE_DATA_RESPONSE, + ), + patch( + "systembridgeconnector.websocket_client.WebSocketClient.listen", + new=mock_data_listener, + ), + patch( + "homeassistant.components.system_bridge.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], FIXTURE_AUTH_INPUT ) diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 22cef15b1ab..5e4eda7d643 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -376,11 +376,14 @@ async def async_log_error_from_test_path(hass, path, watcher): call_path_frame = get_frame(call_path, path_frame) logger_frame = get_frame("venv_path/logging/log.py", call_path_frame) - with patch.object( - _LOGGER, "findCaller", MagicMock(return_value=(call_path, 0, None, None)) - ), patch( - "homeassistant.components.system_log.sys._getframe", - return_value=logger_frame, + with ( + patch.object( + _LOGGER, "findCaller", MagicMock(return_value=(call_path, 0, None, None)) + ), + patch( + "homeassistant.components.system_log.sys._getframe", + return_value=logger_frame, + ), ): wait_empty = watcher.add_watcher("error message") _LOGGER.error("error message") diff --git a/tests/components/systemmonitor/conftest.py b/tests/components/systemmonitor/conftest.py index e6e1b35e66d..c8cf614e04d 100644 --- a/tests/components/systemmonitor/conftest.py +++ b/tests/components/systemmonitor/conftest.py @@ -193,11 +193,10 @@ def mock_os() -> Generator: """Mock os.path.isdir.""" return path != "/etc/hosts" - with patch( - "homeassistant.components.systemmonitor.coordinator.os" - ) as mock_os, patch( - "homeassistant.components.systemmonitor.util.os" - ) as mock_os_util: + with ( + patch("homeassistant.components.systemmonitor.coordinator.os") as mock_os, + patch("homeassistant.components.systemmonitor.util.os") as mock_os_util, + ): mock_os_util.name = "nt" mock_os.getloadavg.return_value = (1, 2, 3) mock_os_util.path.isdir = isdir diff --git a/tests/components/tado/test_config_flow.py b/tests/components/tado/test_config_flow.py index 5f20e0e95b3..c2bbe4f37de 100644 --- a/tests/components/tado/test_config_flow.py +++ b/tests/components/tado/test_config_flow.py @@ -62,13 +62,16 @@ async def test_form_exceptions( # Test a retry to recover, upon failure mock_tado_api = _get_mock_tado_api(getMe={"homes": [{"id": 1, "name": "myhome"}]}) - with patch( - "homeassistant.components.tado.config_flow.Tado", - return_value=mock_tado_api, - ), patch( - "homeassistant.components.tado.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.tado.config_flow.Tado", + return_value=mock_tado_api, + ), + patch( + "homeassistant.components.tado.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "test-username", "password": "test-password"}, @@ -129,13 +132,16 @@ async def test_create_entry(hass: HomeAssistant) -> None: mock_tado_api = _get_mock_tado_api(getMe={"homes": [{"id": 1, "name": "myhome"}]}) - with patch( - "homeassistant.components.tado.config_flow.Tado", - return_value=mock_tado_api, - ), patch( - "homeassistant.components.tado.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.tado.config_flow.Tado", + return_value=mock_tado_api, + ), + patch( + "homeassistant.components.tado.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"username": "test-username", "password": "test-password"}, @@ -268,13 +274,16 @@ async def test_import_step(hass: HomeAssistant) -> None: """Test import step.""" mock_tado_api = _get_mock_tado_api(getMe={"homes": [{"id": 1, "name": "myhome"}]}) - with patch( - "homeassistant.components.tado.config_flow.Tado", - return_value=mock_tado_api, - ), patch( - "homeassistant.components.tado.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.tado.config_flow.Tado", + return_value=mock_tado_api, + ), + patch( + "homeassistant.components.tado.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, diff --git a/tests/components/tailwind/conftest.py b/tests/components/tailwind/conftest.py index b03bb0ab0ec..b7443e59581 100644 --- a/tests/components/tailwind/conftest.py +++ b/tests/components/tailwind/conftest.py @@ -47,11 +47,14 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_tailwind(device_fixture: str) -> Generator[MagicMock, None, None]: """Return a mocked Tailwind client.""" - with patch( - "homeassistant.components.tailwind.coordinator.Tailwind", autospec=True - ) as tailwind_mock, patch( - "homeassistant.components.tailwind.config_flow.Tailwind", - new=tailwind_mock, + with ( + patch( + "homeassistant.components.tailwind.coordinator.Tailwind", autospec=True + ) as tailwind_mock, + patch( + "homeassistant.components.tailwind.config_flow.Tailwind", + new=tailwind_mock, + ), ): tailwind = tailwind_mock.return_value tailwind.status.return_value = TailwindDeviceStatus.from_json( diff --git a/tests/components/tankerkoenig/conftest.py b/tests/components/tankerkoenig/conftest.py index 7f71ce8cb19..4400082a45f 100644 --- a/tests/components/tankerkoenig/conftest.py +++ b/tests/components/tankerkoenig/conftest.py @@ -27,12 +27,15 @@ from tests.common import MockConfigEntry @pytest.fixture(name="tankerkoenig") def mock_tankerkoenig() -> Generator[AsyncMock, None, None]: """Mock the aiotankerkoenig client.""" - with patch( - "homeassistant.components.tankerkoenig.coordinator.Tankerkoenig", - autospec=True, - ) as mock_tankerkoenig, patch( - "homeassistant.components.tankerkoenig.config_flow.Tankerkoenig", - new=mock_tankerkoenig, + with ( + patch( + "homeassistant.components.tankerkoenig.coordinator.Tankerkoenig", + autospec=True, + ) as mock_tankerkoenig, + patch( + "homeassistant.components.tankerkoenig.config_flow.Tankerkoenig", + new=mock_tankerkoenig, + ), ): mock = mock_tankerkoenig.return_value mock.station_details.return_value = STATION diff --git a/tests/components/tankerkoenig/test_config_flow.py b/tests/components/tankerkoenig/test_config_flow.py index f7cdc44021e..b954598c12a 100644 --- a/tests/components/tankerkoenig/test_config_flow.py +++ b/tests/components/tankerkoenig/test_config_flow.py @@ -59,11 +59,14 @@ async def test_user(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.tankerkoenig.config_flow.Tankerkoenig.nearby_stations", - return_value=NEARBY_STATIONS, + with ( + patch( + "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.tankerkoenig.config_flow.Tankerkoenig.nearby_stations", + return_value=NEARBY_STATIONS, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=MOCK_USER_DATA @@ -159,11 +162,14 @@ async def test_reauth(hass: HomeAssistant, config_entry: MockConfigEntry) -> Non """Test starting a flow by user to re-auth.""" config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.tankerkoenig.config_flow.Tankerkoenig.nearby_stations", - ) as mock_nearby_stations: + with ( + patch( + "homeassistant.components.tankerkoenig.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.tankerkoenig.config_flow.Tankerkoenig.nearby_stations", + ) as mock_nearby_stations, + ): # re-auth initialized result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/tautulli/test_config_flow.py b/tests/components/tautulli/test_config_flow.py index f867b5d0fb1..e51fbfbad0d 100644 --- a/tests/components/tautulli/test_config_flow.py +++ b/tests/components/tautulli/test_config_flow.py @@ -171,9 +171,10 @@ async def test_flow_reauth( new_conf = {CONF_API_KEY: "efgh"} CONF_DATA[CONF_API_KEY] = "efgh" - with patch_config_flow_tautulli(AsyncMock()), patch( - "homeassistant.components.tautulli.async_setup_entry" - ) as mock_entry: + with ( + patch_config_flow_tautulli(AsyncMock()), + patch("homeassistant.components.tautulli.async_setup_entry") as mock_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=new_conf, diff --git a/tests/components/tcp/test_binary_sensor.py b/tests/components/tcp/test_binary_sensor.py index 089e5748cb7..959c1f050fd 100644 --- a/tests/components/tcp/test_binary_sensor.py +++ b/tests/components/tcp/test_binary_sensor.py @@ -22,11 +22,12 @@ TEST_ENTITY = "binary_sensor.test_name" @pytest.fixture(name="mock_socket") def mock_socket_fixture(): """Mock the socket.""" - with patch( - "homeassistant.components.tcp.common.socket.socket" - ) as mock_socket, patch( - "homeassistant.components.tcp.common.select.select", - return_value=(True, False, False), + with ( + patch("homeassistant.components.tcp.common.socket.socket") as mock_socket, + patch( + "homeassistant.components.tcp.common.select.select", + return_value=(True, False, False), + ), ): # yield the return value of the socket context manager yield mock_socket.return_value.__enter__.return_value diff --git a/tests/components/technove/conftest.py b/tests/components/technove/conftest.py index 4f4e5e4c978..06db6e24f47 100644 --- a/tests/components/technove/conftest.py +++ b/tests/components/technove/conftest.py @@ -51,10 +51,13 @@ def device_fixture() -> TechnoVEStation: @pytest.fixture def mock_technove(device_fixture: TechnoVEStation) -> Generator[MagicMock, None, None]: """Return a mocked TechnoVE client.""" - with patch( - "homeassistant.components.technove.coordinator.TechnoVE", autospec=True - ) as technove_mock, patch( - "homeassistant.components.technove.config_flow.TechnoVE", new=technove_mock + with ( + patch( + "homeassistant.components.technove.coordinator.TechnoVE", autospec=True + ) as technove_mock, + patch( + "homeassistant.components.technove.config_flow.TechnoVE", new=technove_mock + ), ): technove = technove_mock.return_value technove.update.return_value = device_fixture diff --git a/tests/components/tedee/conftest.py b/tests/components/tedee/conftest.py index b5597ee2faa..9f0730992d2 100644 --- a/tests/components/tedee/conftest.py +++ b/tests/components/tedee/conftest.py @@ -43,11 +43,14 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_tedee(request) -> Generator[MagicMock, None, None]: """Return a mocked Tedee client.""" - with patch( - "homeassistant.components.tedee.coordinator.TedeeClient", autospec=True - ) as tedee_mock, patch( - "homeassistant.components.tedee.config_flow.TedeeClient", - new=tedee_mock, + with ( + patch( + "homeassistant.components.tedee.coordinator.TedeeClient", autospec=True + ) as tedee_mock, + patch( + "homeassistant.components.tedee.config_flow.TedeeClient", + new=tedee_mock, + ), ): tedee = tedee_mock.return_value diff --git a/tests/components/telegram_bot/conftest.py b/tests/components/telegram_bot/conftest.py index 864c9eba9ec..0906b6afcbd 100644 --- a/tests/components/telegram_bot/conftest.py +++ b/tests/components/telegram_bot/conftest.py @@ -62,12 +62,15 @@ def config_polling(): @pytest.fixture def mock_register_webhook(): """Mock calls made by telegram_bot when (de)registering webhook.""" - with patch( - "homeassistant.components.telegram_bot.webhooks.PushBot.register_webhook", - return_value=True, - ), patch( - "homeassistant.components.telegram_bot.webhooks.PushBot.deregister_webhook", - return_value=True, + with ( + patch( + "homeassistant.components.telegram_bot.webhooks.PushBot.register_webhook", + return_value=True, + ), + patch( + "homeassistant.components.telegram_bot.webhooks.PushBot.deregister_webhook", + return_value=True, + ), ): yield @@ -76,16 +79,21 @@ def mock_register_webhook(): def mock_external_calls(): """Mock calls that make calls to the live Telegram API.""" test_user = User(123456, "Testbot", True) - with patch( - "telegram.Bot.get_me", - return_value=test_user, - ), patch( - "telegram.Bot._bot_user", - test_user, - ), patch( - "telegram.Bot.bot", - test_user, - ), patch("telegram.ext.Updater._bootstrap"): + with ( + patch( + "telegram.Bot.get_me", + return_value=test_user, + ), + patch( + "telegram.Bot._bot_user", + test_user, + ), + patch( + "telegram.Bot.bot", + test_user, + ), + patch("telegram.ext.Updater._bootstrap"), + ): yield diff --git a/tests/components/tellduslive/test_config_flow.py b/tests/components/tellduslive/test_config_flow.py index cf107477897..3cd157fd8b5 100644 --- a/tests/components/tellduslive/test_config_flow.py +++ b/tests/components/tellduslive/test_config_flow.py @@ -43,11 +43,12 @@ def authorize(): @pytest.fixture def mock_tellduslive(supports_local_api, authorize): """Mock tellduslive.""" - with patch( - "homeassistant.components.tellduslive.config_flow.Session" - ) as Session, patch( - "homeassistant.components.tellduslive.config_flow.supports_local_api" - ) as tellduslive_supports_local_api: + with ( + patch("homeassistant.components.tellduslive.config_flow.Session") as Session, + patch( + "homeassistant.components.tellduslive.config_flow.supports_local_api" + ) as tellduslive_supports_local_api, + ): tellduslive_supports_local_api.return_value = supports_local_api Session().authorize.return_value = authorize Session().access_token = "token" @@ -139,10 +140,13 @@ async def test_step_import_load_json_matching_host( """Test that we add host and trigger user when configuring from import.""" flow = init_config_flow(hass) - with patch( - "homeassistant.components.tellduslive.config_flow.load_json_object", - return_value={"tellduslive": {}}, - ), patch("os.path.isfile"): + with ( + patch( + "homeassistant.components.tellduslive.config_flow.load_json_object", + return_value={"tellduslive": {}}, + ), + patch("os.path.isfile"), + ): result = await flow.async_step_import( {CONF_HOST: "Cloud API", KEY_SCAN_INTERVAL: 0} ) @@ -154,10 +158,13 @@ async def test_step_import_load_json(hass: HomeAssistant, mock_tellduslive) -> N """Test that we create entry when configuring from import.""" flow = init_config_flow(hass) - with patch( - "homeassistant.components.tellduslive.config_flow.load_json_object", - return_value={"localhost": {}}, - ), patch("os.path.isfile"): + with ( + patch( + "homeassistant.components.tellduslive.config_flow.load_json_object", + return_value={"localhost": {}}, + ), + patch("os.path.isfile"), + ): result = await flow.async_step_import( {CONF_HOST: "localhost", KEY_SCAN_INTERVAL: SCAN_INTERVAL} ) diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index c046e961fac..fdcc0587a73 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -394,12 +394,15 @@ async def test_creating_sensor_loads_group(hass: HomeAssistant) -> None: hass.bus.async_listen(EVENT_COMPONENT_LOADED, set_after_dep_event) - with patch( - "homeassistant.components.group.async_setup", - new=async_setup_group, - ), patch( - "homeassistant.components.template.sensor.async_setup_platform", - new=async_setup_template, + with ( + patch( + "homeassistant.components.group.async_setup", + new=async_setup_group, + ), + patch( + "homeassistant.components.template.sensor.async_setup_platform", + new=async_setup_template, + ), ): await async_from_config_dict( {"sensor": {"platform": "template", "sensors": {}}, "group": {}}, hass @@ -1602,11 +1605,15 @@ async def test_entity_last_reset_parsing( # State of timestamp sensors are always in UTC now = dt_util.utcnow() - with patch( - "homeassistant.components.template.sensor._LOGGER.warning" - ) as mocked_warning, patch( - "homeassistant.components.template.template_entity._LOGGER.error" - ) as mocked_error, patch("homeassistant.util.dt.now", return_value=now): + with ( + patch( + "homeassistant.components.template.sensor._LOGGER.warning" + ) as mocked_warning, + patch( + "homeassistant.components.template.template_entity._LOGGER.error" + ) as mocked_error, + patch("homeassistant.util.dt.now", return_value=now), + ): assert await async_setup_component( hass, "template", diff --git a/tests/components/tesla_wall_connector/conftest.py b/tests/components/tesla_wall_connector/conftest.py index 7ff5db504d2..a31df11dcd0 100644 --- a/tests/components/tesla_wall_connector/conftest.py +++ b/tests/components/tesla_wall_connector/conftest.py @@ -63,18 +63,22 @@ async def create_wall_connector_entry( entry.add_to_hass(hass) - with patch( - "tesla_wall_connector.WallConnector.async_get_version", - return_value=get_default_version_data(), - side_effect=side_effect, - ), patch( - "tesla_wall_connector.WallConnector.async_get_vitals", - return_value=vitals_data, - side_effect=side_effect, - ), patch( - "tesla_wall_connector.WallConnector.async_get_lifetime", - return_value=lifetime_data, - side_effect=side_effect, + with ( + patch( + "tesla_wall_connector.WallConnector.async_get_version", + return_value=get_default_version_data(), + side_effect=side_effect, + ), + patch( + "tesla_wall_connector.WallConnector.async_get_vitals", + return_value=vitals_data, + side_effect=side_effect, + ), + patch( + "tesla_wall_connector.WallConnector.async_get_lifetime", + return_value=lifetime_data, + side_effect=side_effect, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -127,12 +131,15 @@ async def _test_sensors( ), f"First update: {entity.entity_id} is expected to have state {entity.first_value} but has {state.state}" # Simulate second data update - with patch( - "tesla_wall_connector.WallConnector.async_get_vitals", - return_value=vitals_second_update, - ), patch( - "tesla_wall_connector.WallConnector.async_get_lifetime", - return_value=lifetime_second_update, + with ( + patch( + "tesla_wall_connector.WallConnector.async_get_vitals", + return_value=vitals_second_update, + ), + patch( + "tesla_wall_connector.WallConnector.async_get_lifetime", + return_value=lifetime_second_update, + ), ): async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=DEFAULT_SCAN_INTERVAL) diff --git a/tests/components/teslemetry/test_climate.py b/tests/components/teslemetry/test_climate.py index 2e791f68b93..e83e9d648cd 100644 --- a/tests/components/teslemetry/test_climate.py +++ b/tests/components/teslemetry/test_climate.py @@ -94,10 +94,13 @@ async def test_errors( await setup_platform(hass, platforms=[Platform.CLIMATE]) entity_id = "climate.test_climate" - with patch( - "homeassistant.components.teslemetry.VehicleSpecific.auto_conditioning_start", - side_effect=InvalidCommand, - ) as mock_on, pytest.raises(HomeAssistantError) as error: + with ( + patch( + "homeassistant.components.teslemetry.VehicleSpecific.auto_conditioning_start", + side_effect=InvalidCommand, + ) as mock_on, + pytest.raises(HomeAssistantError) as error, + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, @@ -148,9 +151,10 @@ async def test_asleep_or_offline( # Run a command but timeout trying to wake up the vehicle mock_wake_up.return_value = WAKE_UP_ASLEEP mock_vehicle.return_value = WAKE_UP_ASLEEP - with patch( - "homeassistant.components.teslemetry.entity.asyncio.sleep" - ), pytest.raises(HomeAssistantError) as error: + with ( + patch("homeassistant.components.teslemetry.entity.asyncio.sleep"), + pytest.raises(HomeAssistantError) as error, + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, diff --git a/tests/components/tessie/common.py b/tests/components/tessie/common.py index c57dbda8b53..2f213c4e798 100644 --- a/tests/components/tessie/common.py +++ b/tests/components/tessie/common.py @@ -57,11 +57,14 @@ async def setup_platform( ) mock_entry.add_to_hass(hass) - with patch( - "homeassistant.components.tessie.get_state_of_all_vehicles", - return_value=TEST_STATE_OF_ALL_VEHICLES, - side_effect=side_effect, - ), patch("homeassistant.components.tessie.PLATFORMS", platforms): + with ( + patch( + "homeassistant.components.tessie.get_state_of_all_vehicles", + return_value=TEST_STATE_OF_ALL_VEHICLES, + side_effect=side_effect, + ), + patch("homeassistant.components.tessie.PLATFORMS", platforms), + ): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/tessie/test_climate.py b/tests/components/tessie/test_climate.py index 88499a7736c..df86f0b2986 100644 --- a/tests/components/tessie/test_climate.py +++ b/tests/components/tessie/test_climate.py @@ -52,13 +52,16 @@ async def test_climate( assert state.state == HVACMode.HEAT_COOL # Test setting climate temp - with patch( - "homeassistant.components.tessie.climate.set_temperature", - return_value=TEST_RESPONSE, - ) as mock_set, patch( - "homeassistant.components.tessie.climate.start_climate_preconditioning", - return_value=TEST_RESPONSE, - ) as mock_set2: + with ( + patch( + "homeassistant.components.tessie.climate.set_temperature", + return_value=TEST_RESPONSE, + ) as mock_set, + patch( + "homeassistant.components.tessie.climate.start_climate_preconditioning", + return_value=TEST_RESPONSE, + ) as mock_set2, + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, @@ -112,10 +115,13 @@ async def test_errors(hass: HomeAssistant) -> None: entity_id = "climate.test_climate" # Test setting climate on with unknown error - with patch( - "homeassistant.components.tessie.climate.stop_climate", - side_effect=ERROR_UNKNOWN, - ) as mock_set, pytest.raises(HomeAssistantError) as error: + with ( + patch( + "homeassistant.components.tessie.climate.stop_climate", + side_effect=ERROR_UNKNOWN, + ) as mock_set, + pytest.raises(HomeAssistantError) as error, + ): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_OFF, diff --git a/tests/components/tessie/test_cover.py b/tests/components/tessie/test_cover.py index c4dbd8cb704..ebf4c503110 100644 --- a/tests/components/tessie/test_cover.py +++ b/tests/components/tessie/test_cover.py @@ -81,10 +81,13 @@ async def test_errors(hass: HomeAssistant) -> None: entity_id = "cover.test_charge_port_door" # Test setting cover open with unknown error - with patch( - "homeassistant.components.tessie.cover.open_unlock_charge_port", - side_effect=ERROR_UNKNOWN, - ) as mock_set, pytest.raises(HomeAssistantError) as error: + with ( + patch( + "homeassistant.components.tessie.cover.open_unlock_charge_port", + side_effect=ERROR_UNKNOWN, + ) as mock_set, + pytest.raises(HomeAssistantError) as error, + ): await hass.services.async_call( COVER_DOMAIN, SERVICE_OPEN_COVER, @@ -95,10 +98,13 @@ async def test_errors(hass: HomeAssistant) -> None: assert error.from_exception == ERROR_UNKNOWN # Test setting cover open with unknown error - with patch( - "homeassistant.components.tessie.cover.open_unlock_charge_port", - return_value=TEST_RESPONSE_ERROR, - ) as mock_set, pytest.raises(HomeAssistantError) as error: + with ( + patch( + "homeassistant.components.tessie.cover.open_unlock_charge_port", + return_value=TEST_RESPONSE_ERROR, + ) as mock_set, + pytest.raises(HomeAssistantError) as error, + ): await hass.services.async_call( COVER_DOMAIN, SERVICE_OPEN_COVER, diff --git a/tests/components/tessie/test_select.py b/tests/components/tessie/test_select.py index 0a6d56621b3..7f79dbe3297 100644 --- a/tests/components/tessie/test_select.py +++ b/tests/components/tessie/test_select.py @@ -53,10 +53,13 @@ async def test_errors(hass: HomeAssistant) -> None: entity_id = "select.test_seat_heater_left" # Test setting cover open with unknown error - with patch( - "homeassistant.components.tessie.select.set_seat_heat", - side_effect=ERROR_UNKNOWN, - ) as mock_set, pytest.raises(HomeAssistantError) as error: + with ( + patch( + "homeassistant.components.tessie.select.set_seat_heat", + side_effect=ERROR_UNKNOWN, + ) as mock_set, + pytest.raises(HomeAssistantError) as error, + ): await hass.services.async_call( SELECT_DOMAIN, SERVICE_SELECT_OPTION, diff --git a/tests/components/thread/test_config_flow.py b/tests/components/thread/test_config_flow.py index e46b5b70961..9f4930947ef 100644 --- a/tests/components/thread/test_config_flow.py +++ b/tests/components/thread/test_config_flow.py @@ -132,12 +132,15 @@ async def test_zeroconf(hass: HomeAssistant) -> None: async def test_zeroconf_setup_onboarding(hass: HomeAssistant) -> None: """Test we automatically finish a zeroconf flow during onboarding.""" - with patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False - ), patch( - "homeassistant.components.thread.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), + patch( + "homeassistant.components.thread.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( thread.DOMAIN, context={"source": "zeroconf"}, data=TEST_ZEROCONF_RECORD ) diff --git a/tests/components/tibber/test_diagnostics.py b/tests/components/tibber/test_diagnostics.py index 7047d995819..34ecb63dfec 100644 --- a/tests/components/tibber/test_diagnostics.py +++ b/tests/components/tibber/test_diagnostics.py @@ -19,10 +19,13 @@ async def test_entry_diagnostics( config_entry, ) -> None: """Test config entry diagnostics.""" - with patch( - "tibber.Tibber.update_info", - return_value=None, - ), patch("homeassistant.components.tibber.discovery.async_load_platform"): + with ( + patch( + "tibber.Tibber.update_info", + return_value=None, + ), + patch("homeassistant.components.tibber.discovery.async_load_platform"), + ): assert await async_setup_component(hass, "tibber", {}) await hass.async_block_till_done() diff --git a/tests/components/tile/conftest.py b/tests/components/tile/conftest.py index 964986ba151..e3b55c49ae7 100644 --- a/tests/components/tile/conftest.py +++ b/tests/components/tile/conftest.py @@ -54,9 +54,12 @@ def data_tile_details_fixture(): @pytest.fixture(name="mock_pytile") async def mock_pytile_fixture(api): """Define a fixture to patch pytile.""" - with patch( - "homeassistant.components.tile.config_flow.async_login", return_value=api - ), patch("homeassistant.components.tile.async_login", return_value=api): + with ( + patch( + "homeassistant.components.tile.config_flow.async_login", return_value=api + ), + patch("homeassistant.components.tile.async_login", return_value=api), + ): yield diff --git a/tests/components/todoist/conftest.py b/tests/components/todoist/conftest.py index 773fc0eb195..4968b6beefb 100644 --- a/tests/components/todoist/conftest.py +++ b/tests/components/todoist/conftest.py @@ -153,9 +153,10 @@ async def mock_setup_integration( """Mock setup of the todoist integration.""" if todoist_config_entry is not None: todoist_config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.todoist.TodoistAPIAsync", return_value=api - ), patch("homeassistant.components.todoist.PLATFORMS", platforms): + with ( + patch("homeassistant.components.todoist.TodoistAPIAsync", return_value=api), + patch("homeassistant.components.todoist.PLATFORMS", platforms), + ): assert await async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() yield diff --git a/tests/components/tomorrowio/conftest.py b/tests/components/tomorrowio/conftest.py index 94adda43f23..3e7095210fe 100644 --- a/tests/components/tomorrowio/conftest.py +++ b/tests/components/tomorrowio/conftest.py @@ -21,16 +21,20 @@ def tomorrowio_config_flow_connect(): @pytest.fixture(name="tomorrowio_config_entry_update", autouse=True) def tomorrowio_config_entry_update_fixture(): """Mock valid tomorrowio config entry setup.""" - with patch( - "homeassistant.components.tomorrowio.TomorrowioV4.realtime_and_all_forecasts", - return_value=json.loads(load_fixture("v4.json", "tomorrowio")), - ) as mock_update, patch( - "homeassistant.components.tomorrowio.TomorrowioV4.max_requests_per_day", - new_callable=PropertyMock, - ) as mock_max_requests_per_day, patch( - "homeassistant.components.tomorrowio.TomorrowioV4.num_api_requests", - new_callable=PropertyMock, - ) as mock_num_api_requests: + with ( + patch( + "homeassistant.components.tomorrowio.TomorrowioV4.realtime_and_all_forecasts", + return_value=json.loads(load_fixture("v4.json", "tomorrowio")), + ) as mock_update, + patch( + "homeassistant.components.tomorrowio.TomorrowioV4.max_requests_per_day", + new_callable=PropertyMock, + ) as mock_max_requests_per_day, + patch( + "homeassistant.components.tomorrowio.TomorrowioV4.num_api_requests", + new_callable=PropertyMock, + ) as mock_num_api_requests, + ): mock_max_requests_per_day.return_value = 100 mock_num_api_requests.return_value = 2 yield mock_update diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py index 2661c18772d..0dde43a9710 100644 --- a/tests/components/totalconnect/common.py +++ b/tests/components/totalconnect/common.py @@ -389,10 +389,13 @@ async def setup_platform(hass, platform): RESPONSE_DISARMED, ] - with patch("homeassistant.components.totalconnect.PLATFORMS", [platform]), patch( - TOTALCONNECT_REQUEST, - side_effect=responses, - ) as mock_request: + with ( + patch("homeassistant.components.totalconnect.PLATFORMS", [platform]), + patch( + TOTALCONNECT_REQUEST, + side_effect=responses, + ) as mock_request, + ): assert await async_setup_component(hass, DOMAIN, {}) assert mock_request.call_count == 5 await hass.async_block_till_done() diff --git a/tests/components/totalconnect/test_config_flow.py b/tests/components/totalconnect/test_config_flow.py index 39ba30a93a8..940542bf3ad 100644 --- a/tests/components/totalconnect/test_config_flow.py +++ b/tests/components/totalconnect/test_config_flow.py @@ -55,11 +55,14 @@ async def test_user_show_locations(hass: HomeAssistant) -> None: RESPONSE_SUCCESS, ] - with patch( - TOTALCONNECT_REQUEST, - side_effect=responses, - ) as mock_request, patch( - "homeassistant.components.totalconnect.async_setup_entry", return_value=True + with ( + patch( + TOTALCONNECT_REQUEST, + side_effect=responses, + ) as mock_request, + patch( + "homeassistant.components.totalconnect.async_setup_entry", return_value=True + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -144,10 +147,13 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "reauth_confirm" - with patch( - "homeassistant.components.totalconnect.config_flow.TotalConnectClient" - ) as client_mock, patch( - "homeassistant.components.totalconnect.async_setup_entry", return_value=True + with ( + patch( + "homeassistant.components.totalconnect.config_flow.TotalConnectClient" + ) as client_mock, + patch( + "homeassistant.components.totalconnect.async_setup_entry", return_value=True + ), ): # first test with an invalid password client_mock.side_effect = AuthenticationError() @@ -181,14 +187,18 @@ async def test_no_locations(hass: HomeAssistant) -> None: RESPONSE_DISARMED, ] - with patch( - TOTALCONNECT_REQUEST, - side_effect=responses, - ) as mock_request, patch( - "homeassistant.components.totalconnect.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.totalconnect.TotalConnectClient.get_number_locations", - return_value=0, + with ( + patch( + TOTALCONNECT_REQUEST, + side_effect=responses, + ) as mock_request, + patch( + "homeassistant.components.totalconnect.async_setup_entry", return_value=True + ), + patch( + "homeassistant.components.totalconnect.TotalConnectClient.get_number_locations", + return_value=0, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 4c188fcddcc..d1454d12e68 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -311,9 +311,11 @@ async def initialize_config_entry_for_device( ) config_entry.add_to_hass(hass) - with _patch_discovery(device=dev), _patch_single_discovery( - device=dev - ), _patch_connect(device=dev): + with ( + _patch_discovery(device=dev), + _patch_single_discovery(device=dev), + _patch_connect(device=dev), + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index 0f5f2e0d542..4c1cc999f16 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -79,11 +79,13 @@ async def test_discovery(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_single_discovery(), + _patch_connect(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: MAC_ADDRESS}, @@ -325,8 +327,10 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_single_discovery(no_device=True), _patch_connect( - no_device=True + with ( + _patch_discovery(), + _patch_single_discovery(no_device=True), + _patch_connect(no_device=True), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -363,9 +367,12 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_single_discovery(), + _patch_connect(), + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: MAC_ADDRESS} ) @@ -417,9 +424,11 @@ async def test_manual(hass: HomeAssistant) -> None: assert not result["errors"] # Cannot connect (timeout) - with _patch_discovery(no_device=True), _patch_single_discovery( - no_device=True - ), _patch_connect(no_device=True): + with ( + _patch_discovery(no_device=True), + _patch_single_discovery(no_device=True), + _patch_connect(no_device=True), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -430,9 +439,13 @@ async def test_manual(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} # Success - with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ), patch(f"{MODULE}.async_setup_entry", return_value=True): + with ( + _patch_discovery(), + _patch_single_discovery(), + _patch_connect(), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), + ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -446,9 +459,11 @@ async def test_manual(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with _patch_discovery(no_device=True), _patch_single_discovery( - no_device=True - ), _patch_connect(no_device=True): + with ( + _patch_discovery(no_device=True), + _patch_single_discovery(no_device=True), + _patch_connect(no_device=True), + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -467,11 +482,13 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert not result["errors"] - with _patch_discovery( - no_device=True - ), _patch_single_discovery(), _patch_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ), patch(f"{MODULE}.async_setup_entry", return_value=True): + with ( + _patch_discovery(no_device=True), + _patch_single_discovery(), + _patch_connect(), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} ) @@ -631,9 +648,11 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: assert result3["type"] is FlowResultType.ABORT assert result3["reason"] == "already_in_progress" - with _patch_discovery(no_device=True), _patch_single_discovery( - no_device=True - ), _patch_connect(no_device=True): + with ( + _patch_discovery(no_device=True), + _patch_single_discovery(no_device=True), + _patch_connect(no_device=True), + ): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_DHCP}, @@ -680,11 +699,15 @@ async def test_discovered_by_dhcp_or_discovery( assert result["type"] is FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_single_discovery(), _patch_connect(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_single_discovery(), + _patch_connect(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -721,9 +744,11 @@ async def test_discovered_by_dhcp_or_discovery_failed_to_get_device( ) -> None: """Test we abort if we cannot get the unique id when discovered from dhcp.""" - with _patch_discovery(no_device=True), _patch_single_discovery( - no_device=True - ), _patch_connect(no_device=True): + with ( + _patch_discovery(no_device=True), + _patch_single_discovery(no_device=True), + _patch_connect(no_device=True), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 384949c3493..176f2aab7ae 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -46,8 +46,9 @@ async def test_configuring_tplink_causes_discovery( hass: HomeAssistant, freezer: FrozenDateTimeFactory ) -> None: """Test that specifying empty config does discovery.""" - with patch("homeassistant.components.tplink.Discover.discover") as discover, patch( - "homeassistant.components.tplink.Discover.discover_single" + with ( + patch("homeassistant.components.tplink.Discover.discover") as discover, + patch("homeassistant.components.tplink.Discover.discover_single"), ): discover.return_value = {MagicMock(): MagicMock()} await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) @@ -88,9 +89,11 @@ async def test_config_entry_retry(hass: HomeAssistant) -> None: domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=MAC_ADDRESS ) already_migrated_config_entry.add_to_hass(hass) - with _patch_discovery(no_device=True), _patch_single_discovery( - no_device=True - ), _patch_connect(no_device=True): + with ( + _patch_discovery(no_device=True), + _patch_single_discovery(no_device=True), + _patch_connect(no_device=True), + ): await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) await hass.async_block_till_done() assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY @@ -120,9 +123,11 @@ async def test_dimmer_switch_unique_id_fix_original_entity_still_exists( original_name="Rollout dimmer", ) - with _patch_discovery(device=dimmer), _patch_single_discovery( - device=dimmer - ), _patch_connect(device=dimmer): + with ( + _patch_discovery(device=dimmer), + _patch_single_discovery(device=dimmer), + _patch_connect(device=dimmer), + ): await setup.async_setup_component(hass, DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index 6a34afd7260..767ff4a122c 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -419,9 +419,11 @@ async def test_smart_strip_effects(hass: HomeAssistant) -> None: already_migrated_config_entry.add_to_hass(hass) strip = _mocked_smart_light_strip() - with _patch_discovery(device=strip), _patch_single_discovery( - device=strip - ), _patch_connect(device=strip): + with ( + _patch_discovery(device=strip), + _patch_single_discovery(device=strip), + _patch_connect(device=strip), + ): await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) await hass.async_block_till_done() diff --git a/tests/components/tplink_omada/test_config_flow.py b/tests/components/tplink_omada/test_config_flow.py index 4c1f05deb67..230f0d2a68e 100644 --- a/tests/components/tplink_omada/test_config_flow.py +++ b/tests/components/tplink_omada/test_config_flow.py @@ -46,15 +46,18 @@ async def test_form_single_site(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.tplink_omada.config_flow._validate_input", - return_value=HubInfo( - "omada_id", "OC200", [OmadaSite("Display Name", "SiteId")] - ), - ) as mocked_validate, patch( - "homeassistant.components.tplink_omada.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.tplink_omada.config_flow._validate_input", + return_value=HubInfo( + "omada_id", "OC200", [OmadaSite("Display Name", "SiteId")] + ), + ) as mocked_validate, + patch( + "homeassistant.components.tplink_omada.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_DATA, @@ -77,17 +80,20 @@ async def test_form_multiple_sites(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.tplink_omada.config_flow._validate_input", - return_value=HubInfo( - "omada_id", - "OC200", - [OmadaSite("Site 1", "first"), OmadaSite("Site 2", "second")], + with ( + patch( + "homeassistant.components.tplink_omada.config_flow._validate_input", + return_value=HubInfo( + "omada_id", + "OC200", + [OmadaSite("Site 1", "first"), OmadaSite("Site 2", "second")], + ), ), - ), patch( - "homeassistant.components.tplink_omada.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + patch( + "homeassistant.components.tplink_omada.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_USER_DATA, @@ -321,12 +327,15 @@ async def test_async_step_reauth_invalid_auth(hass: HomeAssistant) -> None: async def test_validate_input(hass: HomeAssistant) -> None: """Test validate returns HubInfo.""" - with patch( - "tplink_omada_client.omadaclient.OmadaClient", autospec=True - ) as mock_client, patch( - "homeassistant.components.tplink_omada.config_flow.create_omada_client", - return_value=mock_client, - ) as create_mock: + with ( + patch( + "tplink_omada_client.omadaclient.OmadaClient", autospec=True + ) as mock_client, + patch( + "homeassistant.components.tplink_omada.config_flow.create_omada_client", + return_value=mock_client, + ) as create_mock, + ): mock_client.login.return_value = "Id" mock_client.get_controller_name.return_value = "Name" mock_client.get_sites.return_value = [OmadaSite("x", "y")] @@ -344,12 +353,16 @@ async def test_validate_input(hass: HomeAssistant) -> None: async def test_create_omada_client_parses_args(hass: HomeAssistant) -> None: """Test config arguments are passed to Omada client.""" - with patch( - "homeassistant.components.tplink_omada.config_flow.OmadaClient", autospec=True - ) as mock_client, patch( - "homeassistant.components.tplink_omada.config_flow.async_get_clientsession", - return_value="ws", - ) as mock_clientsession: + with ( + patch( + "homeassistant.components.tplink_omada.config_flow.OmadaClient", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.tplink_omada.config_flow.async_get_clientsession", + return_value="ws", + ) as mock_clientsession, + ): result = await create_omada_client(hass, MOCK_USER_DATA) assert result is not None @@ -362,12 +375,16 @@ async def test_create_omada_client_parses_args(hass: HomeAssistant) -> None: async def test_create_omada_client_adds_missing_scheme(hass: HomeAssistant) -> None: """Test config arguments are passed to Omada client.""" - with patch( - "homeassistant.components.tplink_omada.config_flow.OmadaClient", autospec=True - ) as mock_client, patch( - "homeassistant.components.tplink_omada.config_flow.async_get_clientsession", - return_value="ws", - ) as mock_clientsession: + with ( + patch( + "homeassistant.components.tplink_omada.config_flow.OmadaClient", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.tplink_omada.config_flow.async_get_clientsession", + return_value="ws", + ) as mock_clientsession, + ): result = await create_omada_client( hass, { @@ -390,14 +407,19 @@ async def test_create_omada_client_with_ip_creates_clientsession( ) -> None: """Test config arguments are passed to Omada client.""" - with patch( - "homeassistant.components.tplink_omada.config_flow.OmadaClient", autospec=True - ) as mock_client, patch( - "homeassistant.components.tplink_omada.config_flow.CookieJar", autospec=True - ) as mock_jar, patch( - "homeassistant.components.tplink_omada.config_flow.async_create_clientsession", - return_value="ws", - ) as mock_create_clientsession: + with ( + patch( + "homeassistant.components.tplink_omada.config_flow.OmadaClient", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.tplink_omada.config_flow.CookieJar", autospec=True + ) as mock_jar, + patch( + "homeassistant.components.tplink_omada.config_flow.async_create_clientsession", + return_value="ws", + ) as mock_create_clientsession, + ): result = await create_omada_client( hass, { diff --git a/tests/components/traccar_server/conftest.py b/tests/components/traccar_server/conftest.py index c0836455bd8..e5a65bfeabd 100644 --- a/tests/components/traccar_server/conftest.py +++ b/tests/components/traccar_server/conftest.py @@ -32,12 +32,15 @@ from tests.common import ( @pytest.fixture def mock_traccar_api_client() -> Generator[AsyncMock, None, None]: """Mock a Traccar ApiClient client.""" - with patch( - "homeassistant.components.traccar_server.ApiClient", - autospec=True, - ) as mock_client, patch( - "homeassistant.components.traccar_server.config_flow.ApiClient", - new=mock_client, + with ( + patch( + "homeassistant.components.traccar_server.ApiClient", + autospec=True, + ) as mock_client, + patch( + "homeassistant.components.traccar_server.config_flow.ApiClient", + new=mock_client, + ), ): client: ApiClient = mock_client.return_value client.subscription_status = SubscriptionStatus.DISCONNECTED diff --git a/tests/components/tractive/test_config_flow.py b/tests/components/tractive/test_config_flow.py index 014526c9651..45a37730ff4 100644 --- a/tests/components/tractive/test_config_flow.py +++ b/tests/components/tractive/test_config_flow.py @@ -25,10 +25,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch("aiotractive.api.API.user_id", return_value="user_id"), patch( - "homeassistant.components.tractive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aiotractive.api.API.user_id", return_value="user_id"), + patch( + "homeassistant.components.tractive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, @@ -120,10 +123,13 @@ async def test_reauthentication(hass: HomeAssistant) -> None: assert result["errors"] == {} assert result["step_id"] == "reauth_confirm" - with patch("aiotractive.api.API.user_id", return_value="USERID"), patch( - "homeassistant.components.tractive.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("aiotractive.api.API.user_id", return_value="USERID"), + patch( + "homeassistant.components.tractive.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], USER_INPUT, diff --git a/tests/components/trafikverket_camera/test_config_flow.py b/tests/components/trafikverket_camera/test_config_flow.py index 356a0efcfaf..eb14636d6c9 100644 --- a/tests/components/trafikverket_camera/test_config_flow.py +++ b/tests/components/trafikverket_camera/test_config_flow.py @@ -26,13 +26,16 @@ async def test_form(hass: HomeAssistant, get_camera: CameraInfo) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", - return_value=[get_camera], - ), patch( - "homeassistant.components.trafikverket_camera.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", + return_value=[get_camera], + ), + patch( + "homeassistant.components.trafikverket_camera.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -76,13 +79,16 @@ async def test_form_multiple_cameras( ) await hass.async_block_till_done() - with patch( - "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", - return_value=[get_camera2], - ), patch( - "homeassistant.components.trafikverket_camera.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", + return_value=[get_camera2], + ), + patch( + "homeassistant.components.trafikverket_camera.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -112,13 +118,16 @@ async def test_form_no_location_data( assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", - return_value=[get_camera_no_location], - ), patch( - "homeassistant.components.trafikverket_camera.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", + return_value=[get_camera_no_location], + ), + patch( + "homeassistant.components.trafikverket_camera.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -210,11 +219,14 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", - ), patch( - "homeassistant.components.trafikverket_camera.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", + ), + patch( + "homeassistant.components.trafikverket_camera.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -290,11 +302,14 @@ async def test_reauth_flow_error( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {error_key: p_error} - with patch( - "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", - ), patch( - "homeassistant.components.trafikverket_camera.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_camera.config_flow.TrafikverketCamera.async_get_cameras", + ), + patch( + "homeassistant.components.trafikverket_camera.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/trafikverket_ferry/test_config_flow.py b/tests/components/trafikverket_ferry/test_config_flow.py index 228357e666c..2a0a0ae6cd6 100644 --- a/tests/components/trafikverket_ferry/test_config_flow.py +++ b/tests/components/trafikverket_ferry/test_config_flow.py @@ -30,12 +30,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop", - ), patch( - "homeassistant.components.trafikverket_ferry.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop", + ), + patch( + "homeassistant.components.trafikverket_ferry.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -138,11 +141,14 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop", - ), patch( - "homeassistant.components.trafikverket_ferry.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop", + ), + patch( + "homeassistant.components.trafikverket_ferry.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -221,11 +227,14 @@ async def test_reauth_flow_error( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": p_error} - with patch( - "homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop", - ), patch( - "homeassistant.components.trafikverket_ferry.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_ferry.config_flow.TrafikverketFerry.async_get_next_ferry_stop", + ), + patch( + "homeassistant.components.trafikverket_ferry.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/trafikverket_train/conftest.py b/tests/components/trafikverket_train/conftest.py index e299b115014..880701e8bdc 100644 --- a/tests/components/trafikverket_train/conftest.py +++ b/tests/components/trafikverket_train/conftest.py @@ -43,14 +43,18 @@ async def load_integration_from_entry( ) config_entry2.add_to_hass(hass) - with patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - return_value=get_trains, - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", - return_value=get_train_stop, - ), patch( - "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", + with ( + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + return_value=get_trains, + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", + return_value=get_train_stop, + ), + patch( + "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/trafikverket_train/test_config_flow.py b/tests/components/trafikverket_train/test_config_flow.py index 85cbfae61f5..3a5afa7431c 100644 --- a/tests/components/trafikverket_train/test_config_flow.py +++ b/tests/components/trafikverket_train/test_config_flow.py @@ -37,14 +37,18 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", - ), patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), + patch( + "homeassistant.components.trafikverket_train.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -97,13 +101,17 @@ async def test_form_entry_already_exist(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", - ), patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), + patch( + "homeassistant.components.trafikverket_train.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -153,11 +161,14 @@ async def test_flow_fails( assert result["type"] == FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - side_effect=side_effect(), - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + side_effect=side_effect(), + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -195,13 +206,17 @@ async def test_flow_fails_departures( assert result["type"] == FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_next_train_stops", - side_effect=side_effect(), - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_next_train_stops", + side_effect=side_effect(), + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -244,13 +259,17 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", - ), patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), + patch( + "homeassistant.components.trafikverket_train.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -319,11 +338,14 @@ async def test_reauth_flow_error( data=entry.data, ) - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - side_effect=side_effect(), - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + side_effect=side_effect(), + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -335,13 +357,17 @@ async def test_reauth_flow_error( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": p_error} - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", - ), patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), + patch( + "homeassistant.components.trafikverket_train.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -402,11 +428,14 @@ async def test_reauth_flow_error_departures( data=entry.data, ) - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", - side_effect=side_effect(), + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + side_effect=side_effect(), + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -418,13 +447,17 @@ async def test_reauth_flow_error_departures( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": p_error} - with patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", - ), patch( - "homeassistant.components.trafikverket_train.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.config_flow.TrafikverketTrain.async_get_train_stop", + ), + patch( + "homeassistant.components.trafikverket_train.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -464,14 +497,18 @@ async def test_options_flow( ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - return_value=get_trains, - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", - return_value=get_train_stop, + with ( + patch( + "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + return_value=get_trains, + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", + return_value=get_train_stop, + ), ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/trafikverket_train/test_init.py b/tests/components/trafikverket_train/test_init.py index 175a71f42df..f68c32b5b90 100644 --- a/tests/components/trafikverket_train/test_init.py +++ b/tests/components/trafikverket_train/test_init.py @@ -31,12 +31,15 @@ async def test_unload_entry(hass: HomeAssistant, get_trains: list[TrainStop]) -> ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - return_value=get_trains, - ) as mock_tv_train: + with ( + patch( + "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + return_value=get_trains, + ) as mock_tv_train, + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -129,11 +132,14 @@ async def test_migrate_entity_unique_id( original_name="Stockholm C to Uppsala C", ) - with patch( - "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - return_value=get_trains, + with ( + patch( + "homeassistant.components.trafikverket_train.TrafikverketTrain.async_get_train_station", + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + return_value=get_trains, + ), ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/trafikverket_train/test_sensor.py b/tests/components/trafikverket_train/test_sensor.py index 915dd6a6d57..099bcf5ae1e 100644 --- a/tests/components/trafikverket_train/test_sensor.py +++ b/tests/components/trafikverket_train/test_sensor.py @@ -38,12 +38,15 @@ async def test_sensor_next( state = hass.states.get(entity) assert state == snapshot - with patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - return_value=get_trains_next, - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", - return_value=get_train_stop, + with ( + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + return_value=get_trains_next, + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", + return_value=get_train_stop, + ), ): freezer.tick(timedelta(minutes=6)) async_fire_time_changed(hass) @@ -89,12 +92,15 @@ async def test_sensor_update_auth_failure( state = hass.states.get("sensor.stockholm_c_to_uppsala_c_departure_time_2") assert state.state == "2023-05-01T11:00:00+00:00" - with patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - side_effect=InvalidAuthentication, - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", - side_effect=InvalidAuthentication, + with ( + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + side_effect=InvalidAuthentication, + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", + side_effect=InvalidAuthentication, + ), ): freezer.tick(timedelta(minutes=6)) async_fire_time_changed(hass) @@ -119,12 +125,15 @@ async def test_sensor_update_failure( state = hass.states.get("sensor.stockholm_c_to_uppsala_c_departure_time_2") assert state.state == "2023-05-01T11:00:00+00:00" - with patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", - side_effect=NoTrainAnnouncementFound, - ), patch( - "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", - side_effect=NoTrainAnnouncementFound, + with ( + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_next_train_stops", + side_effect=NoTrainAnnouncementFound, + ), + patch( + "homeassistant.components.trafikverket_train.coordinator.TrafikverketTrain.async_get_train_stop", + side_effect=NoTrainAnnouncementFound, + ), ): freezer.tick(timedelta(minutes=6)) async_fire_time_changed(hass) diff --git a/tests/components/trafikverket_weatherstation/test_config_flow.py b/tests/components/trafikverket_weatherstation/test_config_flow.py index 8967f6c73aa..4a1c50cbaf1 100644 --- a/tests/components/trafikverket_weatherstation/test_config_flow.py +++ b/tests/components/trafikverket_weatherstation/test_config_flow.py @@ -31,12 +31,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_weatherstation.config_flow.TrafikverketWeather.async_get_weather", - ), patch( - "homeassistant.components.trafikverket_weatherstation.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.trafikverket_weatherstation.config_flow.TrafikverketWeather.async_get_weather", + ), + patch( + "homeassistant.components.trafikverket_weatherstation.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -125,11 +128,14 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.trafikverket_weatherstation.config_flow.TrafikverketWeather.async_get_weather", - ), patch( - "homeassistant.components.trafikverket_weatherstation.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.trafikverket_weatherstation.config_flow.TrafikverketWeather.async_get_weather", + ), + patch( + "homeassistant.components.trafikverket_weatherstation.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 5626f91b2a3..2c58c25a509 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -1399,10 +1399,10 @@ def test_resolve_engine(hass: HomeAssistant, setup: str, engine_id: str) -> None assert tts.async_resolve_engine(hass, engine_id) == engine_id assert tts.async_resolve_engine(hass, "non-existing") is None - with patch.dict( - hass.data[tts.DATA_TTS_MANAGER].providers, {}, clear=True - ), patch.dict(hass.data[tts.DOMAIN]._platforms, {}, clear=True), patch.dict( - hass.data[tts.DOMAIN]._entities, {}, clear=True + with ( + patch.dict(hass.data[tts.DATA_TTS_MANAGER].providers, {}, clear=True), + patch.dict(hass.data[tts.DOMAIN]._platforms, {}, clear=True), + patch.dict(hass.data[tts.DOMAIN]._entities, {}, clear=True), ): assert tts.async_resolve_engine(hass, None) is None diff --git a/tests/components/twentemilieu/conftest.py b/tests/components/twentemilieu/conftest.py index 5d64f3ad066..670bd648cac 100644 --- a/tests/components/twentemilieu/conftest.py +++ b/tests/components/twentemilieu/conftest.py @@ -49,11 +49,14 @@ def mock_setup_entry() -> Generator[None, None, None]: @pytest.fixture def mock_twentemilieu() -> Generator[MagicMock, None, None]: """Return a mocked Twente Milieu client.""" - with patch( - "homeassistant.components.twentemilieu.TwenteMilieu", autospec=True - ) as twentemilieu_mock, patch( - "homeassistant.components.twentemilieu.config_flow.TwenteMilieu", - new=twentemilieu_mock, + with ( + patch( + "homeassistant.components.twentemilieu.TwenteMilieu", autospec=True + ) as twentemilieu_mock, + patch( + "homeassistant.components.twentemilieu.config_flow.TwenteMilieu", + new=twentemilieu_mock, + ), ): twentemilieu = twentemilieu_mock.return_value twentemilieu.unique_id.return_value = 12345 diff --git a/tests/components/twinkly/test_config_flow.py b/tests/components/twinkly/test_config_flow.py index 2e8337bc860..f797f9b01b6 100644 --- a/tests/components/twinkly/test_config_flow.py +++ b/tests/components/twinkly/test_config_flow.py @@ -39,9 +39,12 @@ async def test_invalid_host(hass: HomeAssistant) -> None: async def test_success_flow(hass: HomeAssistant) -> None: """Test that an entity is created when the flow completes.""" client = ClientMock() - with patch( - "homeassistant.components.twinkly.config_flow.Twinkly", return_value=client - ), patch("homeassistant.components.twinkly.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.twinkly.config_flow.Twinkly", return_value=client + ), + patch("homeassistant.components.twinkly.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_init( TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -89,9 +92,12 @@ async def test_dhcp_can_confirm(hass: HomeAssistant) -> None: async def test_dhcp_success(hass: HomeAssistant) -> None: """Test DHCP discovery flow success.""" client = ClientMock() - with patch( - "homeassistant.components.twinkly.config_flow.Twinkly", return_value=client - ), patch("homeassistant.components.twinkly.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.twinkly.config_flow.Twinkly", return_value=client + ), + patch("homeassistant.components.twinkly.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_init( TWINKLY_DOMAIN, context={"source": config_entries.SOURCE_DHCP}, diff --git a/tests/components/twitch/conftest.py b/tests/components/twitch/conftest.py index bf997314092..1cebc068831 100644 --- a/tests/components/twitch/conftest.py +++ b/tests/components/twitch/conftest.py @@ -101,11 +101,14 @@ def twitch_mock() -> TwitchMock: @pytest.fixture(name="twitch") def mock_twitch(twitch_mock: TwitchMock): """Mock Twitch.""" - with patch( - "homeassistant.components.twitch.Twitch", - return_value=twitch_mock, - ), patch( - "homeassistant.components.twitch.config_flow.Twitch", - return_value=twitch_mock, + with ( + patch( + "homeassistant.components.twitch.Twitch", + return_value=twitch_mock, + ), + patch( + "homeassistant.components.twitch.config_flow.Twitch", + return_value=twitch_mock, + ), ): yield twitch_mock diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index a93ebedd5a8..a4a9aea18c8 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -69,8 +69,9 @@ async def test_bus(hass: HomeAssistant) -> None: async def test_train(hass: HomeAssistant) -> None: """Test for operational uk_transport sensor with proper attributes.""" - with requests_mock.Mocker() as mock_req, patch( - "homeassistant.util.dt.now", return_value=now().replace(hour=13) + with ( + requests_mock.Mocker() as mock_req, + patch("homeassistant.util.dt.now", return_value=now().replace(hour=13)), ): uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + "*") mock_req.get(uri, text=load_fixture("uk_transport/train.json")) diff --git a/tests/components/unifi/test_hub.py b/tests/components/unifi/test_hub.py index 48aca0b407e..1fddb623930 100644 --- a/tests/components/unifi/test_hub.py +++ b/tests/components/unifi/test_hub.py @@ -306,10 +306,13 @@ async def test_hub_not_accessible(hass: HomeAssistant) -> None: async def test_hub_trigger_reauth_flow(hass: HomeAssistant) -> None: """Failed authentication trigger a reauthentication flow.""" - with patch( - "homeassistant.components.unifi.get_unifi_api", - side_effect=AuthenticationRequired, - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + with ( + patch( + "homeassistant.components.unifi.get_unifi_api", + side_effect=AuthenticationRequired, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_flow_init, + ): await setup_unifi_integration(hass) mock_flow_init.assert_called_once() assert hass.data[UNIFI_DOMAIN] == {} @@ -440,9 +443,12 @@ async def test_reconnect_mechanism_exceptions( """Verify async_reconnect calls expected methods.""" await setup_unifi_integration(hass, aioclient_mock) - with patch("aiounifi.Controller.login", side_effect=exception), patch( - "homeassistant.components.unifi.hub.hub.UnifiWebsocket.reconnect" - ) as mock_reconnect: + with ( + patch("aiounifi.Controller.login", side_effect=exception), + patch( + "homeassistant.components.unifi.hub.hub.UnifiWebsocket.reconnect" + ) as mock_reconnect, + ): await websocket_mock.disconnect() await websocket_mock.reconnect() @@ -481,7 +487,8 @@ async def test_get_unifi_api_fails_to_connect( hass: HomeAssistant, side_effect, raised_exception ) -> None: """Check that get_unifi_api can handle UniFi Network being unavailable.""" - with patch("aiounifi.Controller.login", side_effect=side_effect), pytest.raises( - raised_exception + with ( + patch("aiounifi.Controller.login", side_effect=side_effect), + pytest.raises(raised_exception), ): await get_unifi_api(hass, ENTRY_CONFIG) diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index dd0442edb5c..9053b47cbaf 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -42,10 +42,13 @@ async def test_setup_entry_fails_config_entry_not_ready(hass: HomeAssistant) -> async def test_setup_entry_fails_trigger_reauth_flow(hass: HomeAssistant) -> None: """Failed authentication trigger a reauthentication flow.""" - with patch( - "homeassistant.components.unifi.get_unifi_api", - side_effect=AuthenticationRequired, - ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + with ( + patch( + "homeassistant.components.unifi.get_unifi_api", + side_effect=AuthenticationRequired, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_flow_init, + ): await setup_unifi_integration(hass) mock_flow_init.assert_called_once() diff --git a/tests/components/unifiprotect/conftest.py b/tests/components/unifiprotect/conftest.py index 37e7dceecf5..5b3f9653d75 100644 --- a/tests/components/unifiprotect/conftest.py +++ b/tests/components/unifiprotect/conftest.py @@ -132,9 +132,12 @@ def mock_entry( ): """Mock ProtectApiClient for testing.""" - with _patch_discovery(no_device=True), patch( - "homeassistant.components.unifiprotect.utils.ProtectApiClient" - ) as mock_api: + with ( + _patch_discovery(no_device=True), + patch( + "homeassistant.components.unifiprotect.utils.ProtectApiClient" + ) as mock_api, + ): ufp_config_entry.add_to_hass(hass) mock_api.return_value = ufp_client diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index be315b72d17..7c9f584af15 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -67,16 +67,20 @@ async def test_form(hass: HomeAssistant, bootstrap: Bootstrap, nvr: NVR) -> None assert not result["errors"] bootstrap.nvr = nvr - with patch( - "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", - return_value=bootstrap, - ), patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", + return_value=bootstrap, + ), + patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -251,13 +255,16 @@ async def test_form_reauth_auth( assert result2["step_id"] == "reauth_confirm" bootstrap.nvr = nvr - with patch( - "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", - return_value=bootstrap, - ), patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", + return_value=bootstrap, + ), + patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -290,9 +297,12 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) - ) mock_config.add_to_hass(hass) - with _patch_discovery(), patch( - "homeassistant.components.unifiprotect.utils.ProtectApiClient" - ) as mock_api: + with ( + _patch_discovery(), + patch( + "homeassistant.components.unifiprotect.utils.ProtectApiClient" + ) as mock_api, + ): mock_api.return_value = ufp_client await hass.config_entries.async_setup(mock_config.entry_id) @@ -372,16 +382,20 @@ async def test_discovered_by_unifi_discovery_direct_connect( assert not result["errors"] bootstrap.nvr = nvr - with patch( - "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", - return_value=bootstrap, - ), patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", + return_value=bootstrap, + ), + patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -456,9 +470,12 @@ async def test_discovered_by_unifi_discovery_direct_connect_updated_but_not_usin ) mock_config.add_to_hass(hass) - with _patch_discovery(), patch( - "homeassistant.components.unifiprotect.config_flow.async_console_is_alive", - return_value=False, + with ( + _patch_discovery(), + patch( + "homeassistant.components.unifiprotect.config_flow.async_console_is_alive", + return_value=False, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -491,9 +508,12 @@ async def test_discovered_by_unifi_discovery_does_not_update_ip_when_console_is_ ) mock_config.add_to_hass(hass) - with _patch_discovery(), patch( - "homeassistant.components.unifiprotect.config_flow.async_console_is_alive", - return_value=True, + with ( + _patch_discovery(), + patch( + "homeassistant.components.unifiprotect.config_flow.async_console_is_alive", + return_value=True, + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -562,16 +582,20 @@ async def test_discovered_by_unifi_discovery( assert not result["errors"] bootstrap.nvr = nvr - with patch( - "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", - side_effect=[NotAuthorized, bootstrap], - ), patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", + side_effect=[NotAuthorized, bootstrap], + ), + patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -619,16 +643,20 @@ async def test_discovered_by_unifi_discovery_partial( assert not result["errors"] bootstrap.nvr = nvr - with patch( - "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", - return_value=bootstrap, - ), patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", + return_value=bootstrap, + ), + patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -734,10 +762,13 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa other_ip_dict["source_ip"] = "127.0.0.1" other_ip_dict["direct_connect_domain"] = "nomatchsameip.ui.direct" - with _patch_discovery(), patch.object( - hass.loop, - "getaddrinfo", - return_value=[(socket.AF_INET, None, None, None, ("127.0.0.1", 443))], + with ( + _patch_discovery(), + patch.object( + hass.loop, + "getaddrinfo", + return_value=[(socket.AF_INET, None, None, None, ("127.0.0.1", 443))], + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -772,8 +803,9 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa other_ip_dict["source_ip"] = "127.0.0.2" other_ip_dict["direct_connect_domain"] = "nomatchsameip.ui.direct" - with _patch_discovery(), patch.object( - hass.loop, "getaddrinfo", side_effect=OSError + with ( + _patch_discovery(), + patch.object(hass.loop, "getaddrinfo", side_effect=OSError), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -793,16 +825,20 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa assert not result["errors"] bootstrap.nvr = nvr - with patch( - "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", - return_value=bootstrap, - ), patch( - "homeassistant.components.unifiprotect.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.unifiprotect.async_setup", - return_value=True, - ) as mock_setup: + with ( + patch( + "homeassistant.components.unifiprotect.config_flow.ProtectApiClient.get_bootstrap", + return_value=bootstrap, + ), + patch( + "homeassistant.components.unifiprotect.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.unifiprotect.async_setup", + return_value=True, + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 681f22b327e..f123abb9861 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -239,9 +239,12 @@ async def test_setup_starts_discovery( hass: HomeAssistant, ufp_config_entry: ConfigEntry, ufp_client: ProtectApiClient ) -> None: """Test setting up will start discovery.""" - with _patch_discovery(), patch( - "homeassistant.components.unifiprotect.utils.ProtectApiClient" - ) as mock_api: + with ( + _patch_discovery(), + patch( + "homeassistant.components.unifiprotect.utils.ProtectApiClient" + ) as mock_api, + ): ufp_config_entry.add_to_hass(hass) mock_api.return_value = ufp_client ufp = MockUFPFixture(ufp_config_entry, ufp_client) diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index 52c70719acd..1e5eca47b9b 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -538,9 +538,9 @@ async def test_camera_update_licenseplate( new_camera = camera.copy() new_camera.is_smart_detected = True - new_camera.last_smart_detect_event_ids[ - SmartDetectObjectType.LICENSE_PLATE - ] = event.id + new_camera.last_smart_detect_event_ids[SmartDetectObjectType.LICENSE_PLATE] = ( + event.id + ) mock_msg = Mock() mock_msg.changed_data = {} diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index 9fa55ae6725..9df9247900f 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -1123,12 +1123,15 @@ async def test_browse_media(hass: HomeAssistant) -> None: ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) await ump.async_update() - with patch( - "homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features", - MediaPlayerEntityFeature.BROWSE_MEDIA, - ), patch( - "homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media", - return_value=MOCK_BROWSE_MEDIA, + with ( + patch( + "homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features", + MediaPlayerEntityFeature.BROWSE_MEDIA, + ), + patch( + "homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media", + return_value=MOCK_BROWSE_MEDIA, + ), ): result = await ump.async_browse_media() assert result == MOCK_BROWSE_MEDIA @@ -1155,12 +1158,15 @@ async def test_browse_media_override(hass: HomeAssistant) -> None: ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"]) await ump.async_update() - with patch( - "homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features", - MediaPlayerEntityFeature.BROWSE_MEDIA, - ), patch( - "homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media", - return_value=MOCK_BROWSE_MEDIA, + with ( + patch( + "homeassistant.components.demo.media_player.MediaPlayerEntity.supported_features", + MediaPlayerEntityFeature.BROWSE_MEDIA, + ), + patch( + "homeassistant.components.demo.media_player.MediaPlayerEntity.async_browse_media", + return_value=MOCK_BROWSE_MEDIA, + ), ): result = await ump.async_browse_media() assert result == MOCK_BROWSE_MEDIA diff --git a/tests/components/upb/test_config_flow.py b/tests/components/upb/test_config_flow.py index 7641e64fece..b3c3dfce15c 100644 --- a/tests/components/upb/test_config_flow.py +++ b/tests/components/upb/test_config_flow.py @@ -26,8 +26,9 @@ def mocked_upb(sync_complete=True, config_ok=True): async def valid_tcp_flow(hass, sync_complete=True, config_ok=True): """Get result dict that are standard for most tests.""" - with mocked_upb(sync_complete, config_ok), patch( - "homeassistant.components.upb.async_setup_entry", return_value=True + with ( + mocked_upb(sync_complete, config_ok), + patch("homeassistant.components.upb.async_setup_entry", return_value=True), ): flow = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -42,9 +43,12 @@ async def valid_tcp_flow(hass, sync_complete=True, config_ok=True): async def test_full_upb_flow_with_serial_port(hass: HomeAssistant) -> None: """Test a full UPB config flow with serial port.""" - with mocked_upb(), patch( - "homeassistant.components.upb.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + mocked_upb(), + patch( + "homeassistant.components.upb.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): flow = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -111,9 +115,12 @@ async def test_form_user_with_already_configured(hass: HomeAssistant) -> None: async def test_form_import(hass: HomeAssistant) -> None: """Test we get the form with import source.""" - with mocked_upb(), patch( - "homeassistant.components.upb.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + mocked_upb(), + patch( + "homeassistant.components.upb.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, diff --git a/tests/components/update/test_init.py b/tests/components/update/test_init.py index 106273a3b0e..252bac578a1 100644 --- a/tests/components/update/test_init.py +++ b/tests/components/update/test_init.py @@ -636,10 +636,13 @@ async def test_entity_without_progress_support_raising( hass, "update.update_available", callback(lambda event: events.append(event)) ) - with patch( - "homeassistant.components.update.UpdateEntity.async_install", - side_effect=RuntimeError, - ), pytest.raises(RuntimeError): + with ( + patch( + "homeassistant.components.update.UpdateEntity.async_install", + side_effect=RuntimeError, + ), + pytest.raises(RuntimeError), + ): await hass.services.async_call( DOMAIN, SERVICE_INSTALL, diff --git a/tests/components/upnp/conftest.py b/tests/components/upnp/conftest.py index c1d1903878e..0959e8e31da 100644 --- a/tests/components/upnp/conftest.py +++ b/tests/components/upnp/conftest.py @@ -139,14 +139,16 @@ def mock_setup_entry(): @pytest.fixture(autouse=True) async def silent_ssdp_scanner(hass): """Start SSDP component and get Scanner, prevent actual SSDP traffic.""" - with patch( - "homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners" - ), patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), patch( - "homeassistant.components.ssdp.Scanner.async_scan" - ), patch( - "homeassistant.components.ssdp.Server._async_start_upnp_servers", - ), patch( - "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + with ( + patch("homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner.async_scan"), + patch( + "homeassistant.components.ssdp.Server._async_start_upnp_servers", + ), + patch( + "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + ), ): yield @@ -161,13 +163,16 @@ async def ssdp_instant_discovery(): await callback(TEST_DISCOVERY, ssdp.SsdpChange.ALIVE) return MagicMock() - with patch( - "homeassistant.components.ssdp.async_register_callback", - side_effect=register_callback, - ) as mock_register, patch( - "homeassistant.components.ssdp.async_get_discovery_info_by_st", - return_value=[TEST_DISCOVERY], - ) as mock_get_info: + with ( + patch( + "homeassistant.components.ssdp.async_register_callback", + side_effect=register_callback, + ) as mock_register, + patch( + "homeassistant.components.ssdp.async_get_discovery_info_by_st", + return_value=[TEST_DISCOVERY], + ) as mock_get_info, + ): yield (mock_register, mock_get_info) @@ -185,13 +190,16 @@ async def ssdp_instant_discovery_multi_location(): await callback(test_discovery, ssdp.SsdpChange.ALIVE) return MagicMock() - with patch( - "homeassistant.components.ssdp.async_register_callback", - side_effect=register_callback, - ) as mock_register, patch( - "homeassistant.components.ssdp.async_get_discovery_info_by_st", - return_value=[test_discovery], - ) as mock_get_info: + with ( + patch( + "homeassistant.components.ssdp.async_register_callback", + side_effect=register_callback, + ) as mock_register, + patch( + "homeassistant.components.ssdp.async_get_discovery_info_by_st", + return_value=[test_discovery], + ) as mock_get_info, + ): yield (mock_register, mock_get_info) @@ -204,13 +212,16 @@ async def ssdp_no_discovery(): """Don't do callback.""" return MagicMock() - with patch( - "homeassistant.components.ssdp.async_register_callback", - side_effect=register_callback, - ) as mock_register, patch( - "homeassistant.components.ssdp.async_get_discovery_info_by_st", - return_value=[], - ) as mock_get_info: + with ( + patch( + "homeassistant.components.ssdp.async_register_callback", + side_effect=register_callback, + ) as mock_register, + patch( + "homeassistant.components.ssdp.async_get_discovery_info_by_st", + return_value=[], + ) as mock_get_info, + ): yield (mock_register, mock_get_info) diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 5c59ddd9da7..eab279b479e 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -132,12 +132,15 @@ async def test_async_setup_udn_mismatch( await callback(test_discovery, ssdp.SsdpChange.ALIVE) return MagicMock() - with patch( - "homeassistant.components.ssdp.async_register_callback", - side_effect=register_callback, - ), patch( - "homeassistant.components.ssdp.async_get_discovery_info_by_st", - return_value=[test_discovery], + with ( + patch( + "homeassistant.components.ssdp.async_register_callback", + side_effect=register_callback, + ), + patch( + "homeassistant.components.ssdp.async_get_discovery_info_by_st", + return_value=[test_discovery], + ), ): # Load config_entry. entry.add_to_hass(hass) diff --git a/tests/components/uptimerobot/test_config_flow.py b/tests/components/uptimerobot/test_config_flow.py index 721338ee6e7..58faa524d6f 100644 --- a/tests/components/uptimerobot/test_config_flow.py +++ b/tests/components/uptimerobot/test_config_flow.py @@ -33,13 +33,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), - ), patch( - "homeassistant.components.uptimerobot.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, @@ -137,13 +140,16 @@ async def test_user_unique_id_already_exists( assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), - ), patch( - "homeassistant.components.uptimerobot.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_API_KEY: "12345"}, @@ -176,12 +182,15 @@ async def test_reauthentication( assert result["errors"] is None assert result["step_id"] == "reauth_confirm" - with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), - ), patch( - "homeassistant.components.uptimerobot.async_setup_entry", - return_value=True, + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -214,12 +223,15 @@ async def test_reauthentication_failure( assert result["errors"] is None assert result["step_id"] == "reauth_confirm" - with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), - ), patch( - "homeassistant.components.uptimerobot.async_setup_entry", - return_value=True, + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -255,12 +267,15 @@ async def test_reauthentication_failure_no_existing_entry( assert result["errors"] is None assert result["step_id"] == "reauth_confirm" - with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), - ), patch( - "homeassistant.components.uptimerobot.async_setup_entry", - return_value=True, + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -293,15 +308,18 @@ async def test_reauthentication_failure_account_not_matching( assert result["errors"] is None assert result["step_id"] == "reauth_confirm" - with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=mock_uptimerobot_api_response( - key=MockApiResponseKey.ACCOUNT, - data={**MOCK_UPTIMEROBOT_ACCOUNT, "user_id": 1234567891}, + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response( + key=MockApiResponseKey.ACCOUNT, + data={**MOCK_UPTIMEROBOT_ACCOUNT, "user_id": 1234567891}, + ), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, ), - ), patch( - "homeassistant.components.uptimerobot.async_setup_entry", - return_value=True, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/uptimerobot/test_switch.py b/tests/components/uptimerobot/test_switch.py index 82b8ca55831..8c2cffe504a 100644 --- a/tests/components/uptimerobot/test_switch.py +++ b/tests/components/uptimerobot/test_switch.py @@ -44,14 +44,17 @@ async def test_switch_off(hass: HomeAssistant) -> None: mock_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) mock_entry.add_to_hass(hass) - with patch( - "pyuptimerobot.UptimeRobot.async_get_monitors", - return_value=mock_uptimerobot_api_response( - data=[MOCK_UPTIMEROBOT_MONITOR_PAUSED] + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response( + data=[MOCK_UPTIMEROBOT_MONITOR_PAUSED] + ), + ), + patch( + "pyuptimerobot.UptimeRobot.async_edit_monitor", + return_value=mock_uptimerobot_api_response(), ), - ), patch( - "pyuptimerobot.UptimeRobot.async_edit_monitor", - return_value=mock_uptimerobot_api_response(), ): assert await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() @@ -73,12 +76,15 @@ async def test_switch_on(hass: HomeAssistant) -> None: mock_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) mock_entry.add_to_hass(hass) - with patch( - "pyuptimerobot.UptimeRobot.async_get_monitors", - return_value=mock_uptimerobot_api_response(data=[MOCK_UPTIMEROBOT_MONITOR]), - ), patch( - "pyuptimerobot.UptimeRobot.async_edit_monitor", - return_value=mock_uptimerobot_api_response(), + with ( + patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(data=[MOCK_UPTIMEROBOT_MONITOR]), + ), + patch( + "pyuptimerobot.UptimeRobot.async_edit_monitor", + return_value=mock_uptimerobot_api_response(), + ), ): assert await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() @@ -103,12 +109,15 @@ async def test_authentication_error( entity = hass.states.get(UPTIMEROBOT_SWITCH_TEST_ENTITY) assert entity.state == STATE_ON - with patch( - "pyuptimerobot.UptimeRobot.async_edit_monitor", - side_effect=UptimeRobotAuthenticationException, - ), patch( - "homeassistant.config_entries.ConfigEntry.async_start_reauth" - ) as config_entry_reauth: + with ( + patch( + "pyuptimerobot.UptimeRobot.async_edit_monitor", + side_effect=UptimeRobotAuthenticationException, + ), + patch( + "homeassistant.config_entries.ConfigEntry.async_start_reauth" + ) as config_entry_reauth, + ): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, diff --git a/tests/components/usb/test_init.py b/tests/components/usb/test_init.py index 6d04516a26c..c3f7817527c 100644 --- a/tests/components/usb/test_init.py +++ b/tests/components/usb/test_init.py @@ -89,13 +89,14 @@ async def test_observer_discovery( mock_observer = MagicMock() return mock_observer - with patch("pyudev.Context"), patch( - "pyudev.MonitorObserver", new=_create_mock_monitor_observer - ), patch("pyudev.Monitor.filter_by"), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context"), + patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer), + patch("pyudev.Monitor.filter_by"), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -140,13 +141,12 @@ async def test_removal_by_observer_before_started( ) ] - with patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow: + with ( + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() @@ -177,11 +177,12 @@ async def test_discovered_by_websocket_scan( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -215,11 +216,12 @@ async def test_discovered_by_websocket_scan_limited_by_description_matcher( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -254,11 +256,12 @@ async def test_most_targeted_matcher_wins( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -292,11 +295,12 @@ async def test_discovered_by_websocket_scan_rejected_by_description_matcher( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -334,11 +338,12 @@ async def test_discovered_by_websocket_scan_limited_by_serial_number_matcher( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -372,11 +377,12 @@ async def test_discovered_by_websocket_scan_rejected_by_serial_number_matcher( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -414,11 +420,12 @@ async def test_discovered_by_websocket_scan_limited_by_manufacturer_matcher( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -457,11 +464,12 @@ async def test_discovered_by_websocket_scan_rejected_by_manufacturer_matcher( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -494,11 +502,12 @@ async def test_discovered_by_websocket_rejected_with_empty_serial_number_only( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -529,11 +538,12 @@ async def test_discovered_by_websocket_scan_match_vid_only( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -565,11 +575,12 @@ async def test_discovered_by_websocket_scan_match_vid_wrong_pid( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -600,11 +611,12 @@ async def test_discovered_by_websocket_no_vid_pid( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -636,11 +648,12 @@ async def test_non_matching_discovered_by_scanner_after_started( ) ] - with patch("pyudev.Context", side_effect=exception_type), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=exception_type), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -675,11 +688,13 @@ async def test_observer_on_wsl_fallback_without_throwing_exception( ) ] - with patch("pyudev.Context"), patch( - "pyudev.Monitor.filter_by", side_effect=ValueError - ), patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context"), + patch("pyudev.Monitor.filter_by", side_effect=ValueError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -725,17 +740,18 @@ async def test_not_discovered_by_observer_before_started_on_docker( ) ] - with patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer): + with ( + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch("pyudev.MonitorObserver", new=_create_mock_monitor_observer), + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() - with patch("homeassistant.components.usb.comports", return_value=[]), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow: + with ( + patch("homeassistant.components.usb.comports", return_value=[]), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -843,10 +859,11 @@ async def test_async_is_plugged_in( "pid": "3039", } - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch("homeassistant.components.usb.comports", return_value=[]), patch.object( - hass.config_entries.flow, "async_init" + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=[]), + patch.object(hass.config_entries.flow, "async_init"), ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() @@ -854,9 +871,10 @@ async def test_async_is_plugged_in( await hass.async_block_till_done() assert not usb.async_is_plugged_in(hass, matcher) - with patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch.object(hass.config_entries.flow, "async_init"): + with ( + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch.object(hass.config_entries.flow, "async_init"), + ): ws_client = await hass_ws_client(hass) await ws_client.send_json({"id": 1, "type": "usb/scan"}) response = await ws_client.receive_json() @@ -882,10 +900,11 @@ async def test_async_is_plugged_in_case_enforcement( new_usb = [{"domain": "test1", "vid": "ABCD"}] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch("homeassistant.components.usb.comports", return_value=[]), patch.object( - hass.config_entries.flow, "async_init" + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=[]), + patch.object(hass.config_entries.flow, "async_init"), ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() @@ -902,10 +921,11 @@ async def test_web_socket_triggers_discovery_request_callbacks( """Test the websocket call triggers a discovery request callback.""" mock_callback = Mock() - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=[] - ), patch("homeassistant.components.usb.comports", return_value=[]), patch.object( - hass.config_entries.flow, "async_init" + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=[]), + patch("homeassistant.components.usb.comports", return_value=[]), + patch.object(hass.config_entries.flow, "async_init"), ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() @@ -937,10 +957,11 @@ async def test_initial_scan_callback( mock_callback_1 = Mock() mock_callback_2 = Mock() - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=[] - ), patch("homeassistant.components.usb.comports", return_value=[]), patch.object( - hass.config_entries.flow, "async_init" + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=[]), + patch("homeassistant.components.usb.comports", return_value=[]), + patch.object(hass.config_entries.flow, "async_init"), ): assert await async_setup_component(hass, "usb", {"usb": {}}) cancel_1 = usb.async_register_initial_scan_callback(hass, mock_callback_1) @@ -971,10 +992,11 @@ async def test_cancel_initial_scan_callback( """Test it's possible to cancel an initial scan callback.""" mock_callback = Mock() - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=[] - ), patch("homeassistant.components.usb.comports", return_value=[]), patch.object( - hass.config_entries.flow, "async_init" + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=[]), + patch("homeassistant.components.usb.comports", return_value=[]), + patch.object(hass.config_entries.flow, "async_init"), ): assert await async_setup_component(hass, "usb", {"usb": {}}) cancel = usb.async_register_initial_scan_callback(hass, mock_callback) @@ -1007,14 +1029,16 @@ async def test_resolve_serial_by_id( ) ] - with patch("pyudev.Context", side_effect=ImportError), patch( - "homeassistant.components.usb.async_get_usb", return_value=new_usb - ), patch( - "homeassistant.components.usb.comports", return_value=mock_comports - ), patch( - "homeassistant.components.usb.get_serial_by_id", - return_value="/dev/serial/by-id/bla", - ), patch.object(hass.config_entries.flow, "async_init") as mock_config_flow: + with ( + patch("pyudev.Context", side_effect=ImportError), + patch("homeassistant.components.usb.async_get_usb", return_value=new_usb), + patch("homeassistant.components.usb.comports", return_value=mock_comports), + patch( + "homeassistant.components.usb.get_serial_by_id", + return_value="/dev/serial/by-id/bla", + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + ): assert await async_setup_component(hass, "usb", {"usb": {}}) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) diff --git a/tests/components/usgs_earthquakes_feed/test_geo_location.py b/tests/components/usgs_earthquakes_feed/test_geo_location.py index effaa119a07..40d19422ced 100644 --- a/tests/components/usgs_earthquakes_feed/test_geo_location.py +++ b/tests/components/usgs_earthquakes_feed/test_geo_location.py @@ -116,9 +116,10 @@ async def test_setup(hass: HomeAssistant) -> None: # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() - with freeze_time(utcnow), patch( - "aio_geojson_client.feed.GeoJsonFeed.update" - ) as mock_feed_update: + with ( + freeze_time(utcnow), + patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = ( "OK", [mock_entry_1, mock_entry_2, mock_entry_3], @@ -219,12 +220,13 @@ async def test_setup_with_custom_location(hass: HomeAssistant) -> None: # Set up some mock feed entries for this test. mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 20.5, (-31.1, 150.1)) - with patch( - "aio_geojson_usgs_earthquakes.feed_manager.UsgsEarthquakeHazardsProgramFeed", - wraps=UsgsEarthquakeHazardsProgramFeed, - ) as mock_feed, patch( - "aio_geojson_client.feed.GeoJsonFeed.update" - ) as mock_feed_update: + with ( + patch( + "aio_geojson_usgs_earthquakes.feed_manager.UsgsEarthquakeHazardsProgramFeed", + wraps=UsgsEarthquakeHazardsProgramFeed, + ) as mock_feed, + patch("aio_geojson_client.feed.GeoJsonFeed.update") as mock_feed_update, + ): mock_feed_update.return_value = "OK", [mock_entry_1] with assert_setup_component(1, geo_location.DOMAIN): diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index cbbd27b81df..12203a3e222 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -382,9 +382,9 @@ async def test_motion_recording_mode_properties( assert state assert state.state != STATE_RECORDING - mock_remote.return_value.get_camera.return_value[ - "recordingIndicator" - ] = "MOTION_INPROGRESS" + mock_remote.return_value.get_camera.return_value["recordingIndicator"] = ( + "MOTION_INPROGRESS" + ) async_fire_time_changed(hass, now + timedelta(seconds=91)) await hass.async_block_till_done() @@ -394,9 +394,9 @@ async def test_motion_recording_mode_properties( assert state assert state.state == STATE_RECORDING - mock_remote.return_value.get_camera.return_value[ - "recordingIndicator" - ] = "MOTION_FINISHED" + mock_remote.return_value.get_camera.return_value["recordingIndicator"] = ( + "MOTION_FINISHED" + ) async_fire_time_changed(hass, now + timedelta(seconds=121)) await hass.async_block_till_done() diff --git a/tests/components/vallox/test_config_flow.py b/tests/components/vallox/test_config_flow.py index 6d053dd4e8a..00c11854fe2 100644 --- a/tests/components/vallox/test_config_flow.py +++ b/tests/components/vallox/test_config_flow.py @@ -33,13 +33,16 @@ async def test_form_create_entry(hass: HomeAssistant) -> None: assert init["type"] == FlowResultType.FORM assert init["errors"] is None - with patch( - "homeassistant.components.vallox.config_flow.Vallox.fetch_metric_data", - return_value=None, - ), patch( - "homeassistant.components.vallox.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.vallox.config_flow.Vallox.fetch_metric_data", + return_value=None, + ), + patch( + "homeassistant.components.vallox.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( init["flow_id"], {"host": "1.2.3.4"}, diff --git a/tests/components/venstar/test_config_flow.py b/tests/components/venstar/test_config_flow.py index c7a0f0a394f..077f87975f0 100644 --- a/tests/components/venstar/test_config_flow.py +++ b/tests/components/venstar/test_config_flow.py @@ -40,13 +40,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.venstar.config_flow.VenstarColorTouch.update_info", - new=VenstarColorTouchMock.update_info, - ), patch( - "homeassistant.components.venstar.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.venstar.config_flow.VenstarColorTouch.update_info", + new=VenstarColorTouchMock.update_info, + ), + patch( + "homeassistant.components.venstar.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_DATA, @@ -107,12 +110,15 @@ async def test_already_configured(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - with patch( - "homeassistant.components.venstar.VenstarColorTouch.update_info", - new=VenstarColorTouchMock.update_info, - ), patch( - "homeassistant.components.venstar.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_info", + new=VenstarColorTouchMock.update_info, + ), + patch( + "homeassistant.components.venstar.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/venstar/test_init.py b/tests/components/venstar/test_init.py index 14064334faf..75250b52f5b 100644 --- a/tests/components/venstar/test_init.py +++ b/tests/components/venstar/test_init.py @@ -25,24 +25,31 @@ async def test_setup_entry(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.venstar.VenstarColorTouch._request", - new=VenstarColorTouchMock._request, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.update_sensors", - new=VenstarColorTouchMock.update_sensors, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.update_info", - new=VenstarColorTouchMock.update_info, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.update_alerts", - new=VenstarColorTouchMock.update_alerts, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.get_runtimes", - new=VenstarColorTouchMock.get_runtimes, - ), patch( - "homeassistant.components.venstar.VENSTAR_SLEEP", - new=0, + with ( + patch( + "homeassistant.components.venstar.VenstarColorTouch._request", + new=VenstarColorTouchMock._request, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_sensors", + new=VenstarColorTouchMock.update_sensors, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_info", + new=VenstarColorTouchMock.update_info, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_alerts", + new=VenstarColorTouchMock.update_alerts, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.get_runtimes", + new=VenstarColorTouchMock.get_runtimes, + ), + patch( + "homeassistant.components.venstar.VENSTAR_SLEEP", + new=0, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -65,21 +72,27 @@ async def test_setup_entry_exception(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.venstar.VenstarColorTouch._request", - new=VenstarColorTouchMock._request, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.update_sensors", - new=VenstarColorTouchMock.update_sensors, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.update_info", - new=VenstarColorTouchMock.broken_update_info, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.update_alerts", - new=VenstarColorTouchMock.update_alerts, - ), patch( - "homeassistant.components.venstar.VenstarColorTouch.get_runtimes", - new=VenstarColorTouchMock.get_runtimes, + with ( + patch( + "homeassistant.components.venstar.VenstarColorTouch._request", + new=VenstarColorTouchMock._request, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_sensors", + new=VenstarColorTouchMock.update_sensors, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_info", + new=VenstarColorTouchMock.broken_update_info, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.update_alerts", + new=VenstarColorTouchMock.update_alerts, + ), + patch( + "homeassistant.components.venstar.VenstarColorTouch.get_runtimes", + new=VenstarColorTouchMock.get_runtimes, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/vesync/test_init.py b/tests/components/vesync/test_init.py index fedd7f70a93..a089a270c94 100644 --- a/tests/components/vesync/test_init.py +++ b/tests/components/vesync/test_init.py @@ -28,11 +28,10 @@ async def test_async_setup_entry__not_login( """Test setup does not create config entry when not logged in.""" manager.login = Mock(return_value=False) - with patch.object( - hass.config_entries, "async_forward_entry_setups" - ) as setups_mock, patch( - "homeassistant.components.vesync.async_process_devices" - ) as process_mock: + with ( + patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock, + patch("homeassistant.components.vesync.async_process_devices") as process_mock, + ): assert not await async_setup_entry(hass, config_entry) await hass.async_block_till_done() assert setups_mock.call_count == 0 diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index 7077c4f2acc..c1755f95043 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -22,13 +22,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {} - with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.get_board_information", return_value=None - ), patch( - "vilfo.Client.resolve_firmware_version", return_value=firmware_version - ), patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), patch( - "homeassistant.components.vilfo.async_setup_entry" - ) as mock_setup_entry: + with ( + patch("vilfo.Client.ping", return_value=None), + patch("vilfo.Client.get_board_information", return_value=None), + patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), + patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), + patch("homeassistant.components.vilfo.async_setup_entry") as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, @@ -51,11 +51,13 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.resolve_mac_address", return_value=None - ), patch( - "vilfo.Client.get_board_information", - side_effect=vilfo.exceptions.AuthenticationException, + with ( + patch("vilfo.Client.ping", return_value=None), + patch("vilfo.Client.resolve_mac_address", return_value=None), + patch( + "vilfo.Client.get_board_information", + side_effect=vilfo.exceptions.AuthenticationException, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -72,8 +74,9 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), patch( - "vilfo.Client.resolve_mac_address" + with ( + patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), + patch("vilfo.Client.resolve_mac_address"), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -83,8 +86,9 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None: assert result2["type"] == data_entry_flow.FlowResultType.FORM assert result2["errors"] == {"base": "cannot_connect"} - with patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), patch( - "vilfo.Client.resolve_mac_address" + with ( + patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), + patch("vilfo.Client.resolve_mac_address"), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -112,12 +116,15 @@ async def test_form_already_configured(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) firmware_version = "1.1.0" - with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.get_board_information", - return_value=None, - ), patch( - "vilfo.Client.resolve_firmware_version", return_value=firmware_version - ), patch("vilfo.Client.resolve_mac_address", return_value=None): + with ( + patch("vilfo.Client.ping", return_value=None), + patch( + "vilfo.Client.get_board_information", + return_value=None, + ), + patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), + patch("vilfo.Client.resolve_mac_address", return_value=None), + ): first_flow_result2 = await hass.config_entries.flow.async_configure( first_flow_result1["flow_id"], {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, @@ -127,12 +134,15 @@ async def test_form_already_configured(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.get_board_information", - return_value=None, - ), patch( - "vilfo.Client.resolve_firmware_version", return_value=firmware_version - ), patch("vilfo.Client.resolve_mac_address", return_value=None): + with ( + patch("vilfo.Client.ping", return_value=None), + patch( + "vilfo.Client.get_board_information", + return_value=None, + ), + patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), + patch("vilfo.Client.resolve_mac_address", return_value=None), + ): second_flow_result2 = await hass.config_entries.flow.async_configure( second_flow_result1["flow_id"], {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, @@ -169,11 +179,12 @@ async def test_validate_input_returns_data(hass: HomeAssistant) -> None: mock_mac = "FF-00-00-00-00-00" firmware_version = "1.1.0" - with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.get_board_information", return_value=None - ), patch( - "vilfo.Client.resolve_firmware_version", return_value=firmware_version - ), patch("vilfo.Client.resolve_mac_address", return_value=None): + with ( + patch("vilfo.Client.ping", return_value=None), + patch("vilfo.Client.get_board_information", return_value=None), + patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), + patch("vilfo.Client.resolve_mac_address", return_value=None), + ): result = await config_flow.validate_input(hass, data=mock_data) assert result["title"] == mock_data["host"] @@ -181,11 +192,12 @@ async def test_validate_input_returns_data(hass: HomeAssistant) -> None: assert result[CONF_MAC] is None assert result[CONF_ID] == mock_data["host"] - with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.get_board_information", return_value=None - ), patch( - "vilfo.Client.resolve_firmware_version", return_value=firmware_version - ), patch("vilfo.Client.resolve_mac_address", return_value=mock_mac): + with ( + patch("vilfo.Client.ping", return_value=None), + patch("vilfo.Client.get_board_information", return_value=None), + patch("vilfo.Client.resolve_firmware_version", return_value=firmware_version), + patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), + ): result2 = await config_flow.validate_input(hass, data=mock_data) result3 = await config_flow.validate_input(hass, data=mock_data_with_ip) result4 = await config_flow.validate_input(hass, data=mock_data_with_ipv6) diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index 08a6e6715be..6ce36b38c8f 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -93,12 +93,15 @@ def vizio_connect_fixture(): @pytest.fixture(name="vizio_complete_pairing") def vizio_complete_pairing_fixture(): """Mock complete vizio pairing workflow.""" - with patch( - "homeassistant.components.vizio.config_flow.VizioAsync.start_pair", - return_value=MockStartPairingResponse(CH_TYPE, RESPONSE_TOKEN), - ), patch( - "homeassistant.components.vizio.config_flow.VizioAsync.pair", - return_value=MockCompletePairingResponse(ACCESS_TOKEN), + with ( + patch( + "homeassistant.components.vizio.config_flow.VizioAsync.start_pair", + return_value=MockStartPairingResponse(CH_TYPE, RESPONSE_TOKEN), + ), + patch( + "homeassistant.components.vizio.config_flow.VizioAsync.pair", + return_value=MockCompletePairingResponse(ACCESS_TOKEN), + ), ): yield @@ -116,12 +119,15 @@ def vizio_start_pairing_failure_fixture(): @pytest.fixture(name="vizio_invalid_pin_failure") def vizio_invalid_pin_failure_fixture(): """Mock vizio failure due to invalid pin.""" - with patch( - "homeassistant.components.vizio.config_flow.VizioAsync.start_pair", - return_value=MockStartPairingResponse(CH_TYPE, RESPONSE_TOKEN), - ), patch( - "homeassistant.components.vizio.config_flow.VizioAsync.pair", - return_value=None, + with ( + patch( + "homeassistant.components.vizio.config_flow.VizioAsync.start_pair", + return_value=MockStartPairingResponse(CH_TYPE, RESPONSE_TOKEN), + ), + patch( + "homeassistant.components.vizio.config_flow.VizioAsync.pair", + return_value=None, + ), ): yield @@ -136,10 +142,13 @@ def vizio_bypass_setup_fixture(): @pytest.fixture(name="vizio_bypass_update") def vizio_bypass_update_fixture(): """Mock component update.""" - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", - return_value=True, - ), patch("homeassistant.components.vizio.media_player.VizioDevice.async_update"): + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", + return_value=True, + ), + patch("homeassistant.components.vizio.media_player.VizioDevice.async_update"), + ): yield @@ -156,12 +165,15 @@ def vizio_guess_device_type_fixture(): @pytest.fixture(name="vizio_cant_connect") def vizio_cant_connect_fixture(): """Mock vizio device can't connect with valid auth.""" - with patch( - "homeassistant.components.vizio.config_flow.VizioAsync.validate_ha_config", - AsyncMock(return_value=False), - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", - return_value=None, + with ( + patch( + "homeassistant.components.vizio.config_flow.VizioAsync.validate_ha_config", + AsyncMock(return_value=False), + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", + return_value=None, + ), ): yield @@ -169,34 +181,43 @@ def vizio_cant_connect_fixture(): @pytest.fixture(name="vizio_update") def vizio_update_fixture(): """Mock valid updates to vizio device.""" - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", - return_value=True, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings", - return_value={ - "volume": int(MAX_VOLUME[DEVICE_CLASS_SPEAKER] / 2), - "eq": CURRENT_EQ, - "mute": "Off", - }, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_setting_options", - return_value=EQ_LIST, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", - return_value=CURRENT_INPUT, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list", - return_value=get_mock_inputs(INPUT_LIST), - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", - return_value=True, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_model_name", - return_value=MODEL, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_version", - return_value=VERSION, + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.can_connect_with_auth_check", + return_value=True, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings", + return_value={ + "volume": int(MAX_VOLUME[DEVICE_CLASS_SPEAKER] / 2), + "eq": CURRENT_EQ, + "mute": "Off", + }, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_setting_options", + return_value=EQ_LIST, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", + return_value=CURRENT_INPUT, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list", + return_value=get_mock_inputs(INPUT_LIST), + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", + return_value=True, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_model_name", + return_value=MODEL, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_version", + return_value=VERSION, + ), ): yield @@ -204,15 +225,19 @@ def vizio_update_fixture(): @pytest.fixture(name="vizio_update_with_apps") def vizio_update_with_apps_fixture(vizio_update: pytest.fixture): """Mock valid updates to vizio device that supports apps.""" - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list", - return_value=get_mock_inputs(INPUT_LIST_WITH_APPS), - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", - return_value="CAST", - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config", - return_value=AppConfig(**CURRENT_APP_CONFIG), + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list", + return_value=get_mock_inputs(INPUT_LIST_WITH_APPS), + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", + return_value="CAST", + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config", + return_value=AppConfig(**CURRENT_APP_CONFIG), + ), ): yield @@ -220,15 +245,19 @@ def vizio_update_with_apps_fixture(vizio_update: pytest.fixture): @pytest.fixture(name="vizio_update_with_apps_on_input") def vizio_update_with_apps_on_input_fixture(vizio_update: pytest.fixture): """Mock valid updates to vizio device that supports apps but is on a TV input.""" - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list", - return_value=get_mock_inputs(INPUT_LIST_WITH_APPS), - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", - return_value=CURRENT_INPUT, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config", - return_value=AppConfig("unknown", 1, "app"), + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_inputs_list", + return_value=get_mock_inputs(INPUT_LIST_WITH_APPS), + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_current_input", + return_value=CURRENT_INPUT, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config", + return_value=AppConfig("unknown", 1, "app"), + ), ): yield diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 218c16eb939..d5ce18eb8b9 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -129,15 +129,19 @@ async def _cm_for_test_setup_without_apps( all_settings: dict[str, Any], vizio_power_state: bool | None ) -> None: """Context manager to setup test for Vizio devices without including app specific patches.""" - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings", - return_value=all_settings, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_setting_options", - return_value=EQ_LIST, - ), patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", - return_value=vizio_power_state, + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_all_settings", + return_value=all_settings, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_setting_options", + return_value=EQ_LIST, + ), + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", + return_value=vizio_power_state, + ), ): yield @@ -484,10 +488,13 @@ async def _test_update_availability_switch( # Fast forward time to future twice to trigger update and assert vizio log message for i in range(1, 3): future = now + (future_interval * i) - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", - return_value=final_power_state, - ), freeze_time(future): + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.get_power_state", + return_value=final_power_state, + ), + freeze_time(future), + ): async_fire_time_changed(hass, future) await hass.async_block_till_done() if final_power_state is None: @@ -641,11 +648,14 @@ async def test_setup_with_apps_additional_apps_config( ) # Test that invalid app does nothing - with patch( - "homeassistant.components.vizio.media_player.VizioAsync.launch_app" - ) as service_call1, patch( - "homeassistant.components.vizio.media_player.VizioAsync.launch_app_config" - ) as service_call2: + with ( + patch( + "homeassistant.components.vizio.media_player.VizioAsync.launch_app" + ) as service_call1, + patch( + "homeassistant.components.vizio.media_player.VizioAsync.launch_app_config" + ) as service_call2, + ): await hass.services.async_call( MP_DOMAIN, SERVICE_SELECT_SOURCE, diff --git a/tests/components/vlc_telnet/test_config_flow.py b/tests/components/vlc_telnet/test_config_flow.py index 1d75025535a..f5207c52c99 100644 --- a/tests/components/vlc_telnet/test_config_flow.py +++ b/tests/components/vlc_telnet/test_config_flow.py @@ -54,14 +54,15 @@ async def test_user_flow( assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with patch("homeassistant.components.vlc_telnet.config_flow.Client.connect"), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.login" - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.disconnect" - ), patch( - "homeassistant.components.vlc_telnet.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.vlc_telnet.config_flow.Client.connect"), + patch("homeassistant.components.vlc_telnet.config_flow.Client.login"), + patch("homeassistant.components.vlc_telnet.config_flow.Client.disconnect"), + patch( + "homeassistant.components.vlc_telnet.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], input_data, @@ -118,14 +119,18 @@ async def test_errors( DOMAIN, context={"source": source} ) - with patch( - "homeassistant.components.vlc_telnet.config_flow.Client.connect", - side_effect=connect_side_effect, - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.login", - side_effect=login_side_effect, - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.disconnect", + with ( + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.connect", + side_effect=connect_side_effect, + ), + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.login", + side_effect=login_side_effect, + ), + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.disconnect", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -158,14 +163,15 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: data=entry_data, ) - with patch("homeassistant.components.vlc_telnet.config_flow.Client.connect"), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.login" - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.disconnect" - ), patch( - "homeassistant.components.vlc_telnet.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.vlc_telnet.config_flow.Client.connect"), + patch("homeassistant.components.vlc_telnet.config_flow.Client.login"), + patch("homeassistant.components.vlc_telnet.config_flow.Client.disconnect"), + patch( + "homeassistant.components.vlc_telnet.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"password": "new-password"}, @@ -213,14 +219,18 @@ async def test_reauth_errors( data=entry_data, ) - with patch( - "homeassistant.components.vlc_telnet.config_flow.Client.connect", - side_effect=connect_side_effect, - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.login", - side_effect=login_side_effect, - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.disconnect", + with ( + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.connect", + side_effect=connect_side_effect, + ), + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.login", + side_effect=login_side_effect, + ), + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.disconnect", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -233,14 +243,15 @@ async def test_reauth_errors( async def test_hassio_flow(hass: HomeAssistant) -> None: """Test successful hassio flow.""" - with patch("homeassistant.components.vlc_telnet.config_flow.Client.connect"), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.login" - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.disconnect" - ), patch( - "homeassistant.components.vlc_telnet.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("homeassistant.components.vlc_telnet.config_flow.Client.connect"), + patch("homeassistant.components.vlc_telnet.config_flow.Client.login"), + patch("homeassistant.components.vlc_telnet.config_flow.Client.disconnect"), + patch( + "homeassistant.components.vlc_telnet.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): test_data = HassioServiceInfo( config={ "password": "test-password", @@ -310,14 +321,18 @@ async def test_hassio_errors( login_side_effect: Exception | None, ) -> None: """Test we handle hassio errors.""" - with patch( - "homeassistant.components.vlc_telnet.config_flow.Client.connect", - side_effect=connect_side_effect, - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.login", - side_effect=login_side_effect, - ), patch( - "homeassistant.components.vlc_telnet.config_flow.Client.disconnect", + with ( + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.connect", + side_effect=connect_side_effect, + ), + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.login", + side_effect=login_side_effect, + ), + patch( + "homeassistant.components.vlc_telnet.config_flow.Client.disconnect", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/vodafone_station/test_config_flow.py b/tests/components/vodafone_station/test_config_flow.py index 6334979b742..f2619044861 100644 --- a/tests/components/vodafone_station/test_config_flow.py +++ b/tests/components/vodafone_station/test_config_flow.py @@ -20,13 +20,17 @@ from tests.common import MockConfigEntry async def test_user(hass: HomeAssistant) -> None: """Test starting a flow by user.""" - with patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", - ), patch( - "homeassistant.components.vodafone_station.async_setup_entry" - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", + ), + patch( + "homeassistant.components.vodafone_station.async_setup_entry" + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) @@ -79,18 +83,23 @@ async def test_exception_connection(hass: HomeAssistant, side_effect, error) -> assert result["errors"]["base"] == error # Should be recoverable after hits error - with patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.get_devices_data", - return_value={ - "wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G", - "ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;", - }, - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", - ), patch( - "homeassistant.components.vodafone_station.async_setup_entry", + with ( + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.get_devices_data", + return_value={ + "wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G", + "ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;", + }, + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", + ), + patch( + "homeassistant.components.vodafone_station.async_setup_entry", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -117,12 +126,16 @@ async def test_reauth_successful(hass: HomeAssistant) -> None: mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", - ), patch( - "homeassistant.components.vodafone_station.async_setup_entry", + with ( + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", + ), + patch( + "homeassistant.components.vodafone_station.async_setup_entry", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -160,13 +173,17 @@ async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) mock_config.add_to_hass(hass) - with patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", - side_effect=side_effect, - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", - ), patch( - "homeassistant.components.vodafone_station.async_setup_entry", + with ( + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", + side_effect=side_effect, + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", + ), + patch( + "homeassistant.components.vodafone_station.async_setup_entry", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -190,18 +207,23 @@ async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> assert result["errors"]["base"] == error # Should be recoverable after hits error - with patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.get_devices_data", - return_value={ - "wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G", - "ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;", - }, - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", - ), patch( - "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", - ), patch( - "homeassistant.components.vodafone_station.async_setup_entry", + with ( + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.get_devices_data", + return_value={ + "wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G", + "ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;", + }, + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.login", + ), + patch( + "homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi.logout", + ), + patch( + "homeassistant.components.vodafone_station.async_setup_entry", + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/voip/test_sip.py b/tests/components/voip/test_sip.py index 2e144b87ec0..769be768261 100644 --- a/tests/components/voip/test_sip.py +++ b/tests/components/voip/test_sip.py @@ -21,9 +21,10 @@ async def test_create_sip_server(hass: HomeAssistant, socket_enabled) -> None: entry = result["result"] await hass.async_block_till_done() - with pytest.raises(OSError), socket.socket( - socket.AF_INET, socket.SOCK_DGRAM - ) as sock: + with ( + pytest.raises(OSError), + socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock, + ): # Server should have the port sock.bind(("127.0.0.1", 5060)) @@ -41,9 +42,10 @@ async def test_create_sip_server(hass: HomeAssistant, socket_enabled) -> None: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: sock.bind(("127.0.0.1", 5060)) - with pytest.raises(OSError), socket.socket( - socket.AF_INET, socket.SOCK_DGRAM - ) as sock: + with ( + pytest.raises(OSError), + socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock, + ): # Server should now have the new port sock.bind(("127.0.0.1", 5061)) diff --git a/tests/components/voip/test_voip.py b/tests/components/voip/test_voip.py index 3d34218e999..f5c5fde2518 100644 --- a/tests/components/voip/test_voip.py +++ b/tests/components/voip/test_voip.py @@ -95,15 +95,19 @@ async def test_pipeline( assert media_source_id == _MEDIA_ID return ("wav", _empty_wav()) - with patch( - "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", - new=is_speech, - ), patch( - "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", - new=async_pipeline_from_audio_stream, - ), patch( - "homeassistant.components.voip.voip.tts.async_get_media_source_audio", - new=async_get_media_source_audio, + with ( + patch( + "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", + new=is_speech, + ), + patch( + "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", + new=async_pipeline_from_audio_stream, + ), + patch( + "homeassistant.components.voip.voip.tts.async_get_media_source_audio", + new=async_get_media_source_audio, + ), ): rtp_protocol = voip.voip.PipelineRtpDatagramProtocol( hass, @@ -150,12 +154,15 @@ async def test_pipeline_timeout(hass: HomeAssistant, voip_device: VoIPDevice) -> async def async_pipeline_from_audio_stream(*args, **kwargs): await asyncio.sleep(10) - with patch( - "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", - new=async_pipeline_from_audio_stream, - ), patch( - "homeassistant.components.voip.voip.PipelineRtpDatagramProtocol._wait_for_speech", - return_value=True, + with ( + patch( + "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", + new=async_pipeline_from_audio_stream, + ), + patch( + "homeassistant.components.voip.voip.PipelineRtpDatagramProtocol._wait_for_speech", + return_value=True, + ), ): rtp_protocol = voip.voip.PipelineRtpDatagramProtocol( hass, @@ -288,15 +295,19 @@ async def test_tts_timeout( # Should time out immediately return ("wav", _empty_wav()) - with patch( - "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", - new=is_speech, - ), patch( - "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", - new=async_pipeline_from_audio_stream, - ), patch( - "homeassistant.components.voip.voip.tts.async_get_media_source_audio", - new=async_get_media_source_audio, + with ( + patch( + "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", + new=is_speech, + ), + patch( + "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", + new=async_pipeline_from_audio_stream, + ), + patch( + "homeassistant.components.voip.voip.tts.async_get_media_source_audio", + new=async_get_media_source_audio, + ), ): rtp_protocol = voip.voip.PipelineRtpDatagramProtocol( hass, @@ -389,15 +400,19 @@ async def test_tts_wrong_extension( # Should fail because it's not "wav" return ("mp3", b"") - with patch( - "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", - new=is_speech, - ), patch( - "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", - new=async_pipeline_from_audio_stream, - ), patch( - "homeassistant.components.voip.voip.tts.async_get_media_source_audio", - new=async_get_media_source_audio, + with ( + patch( + "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", + new=is_speech, + ), + patch( + "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", + new=async_pipeline_from_audio_stream, + ), + patch( + "homeassistant.components.voip.voip.tts.async_get_media_source_audio", + new=async_get_media_source_audio, + ), ): rtp_protocol = voip.voip.PipelineRtpDatagramProtocol( hass, @@ -487,15 +502,19 @@ async def test_tts_wrong_wav_format( return ("wav", wav_io.getvalue()) - with patch( - "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", - new=is_speech, - ), patch( - "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", - new=async_pipeline_from_audio_stream, - ), patch( - "homeassistant.components.voip.voip.tts.async_get_media_source_audio", - new=async_get_media_source_audio, + with ( + patch( + "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", + new=is_speech, + ), + patch( + "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", + new=async_pipeline_from_audio_stream, + ), + patch( + "homeassistant.components.voip.voip.tts.async_get_media_source_audio", + new=async_get_media_source_audio, + ), ): rtp_protocol = voip.voip.PipelineRtpDatagramProtocol( hass, @@ -569,15 +588,19 @@ async def test_empty_tts_output( ) ) - with patch( - "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", - new=is_speech, - ), patch( - "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", - new=async_pipeline_from_audio_stream, - ), patch( - "homeassistant.components.voip.voip.PipelineRtpDatagramProtocol._send_tts", - ) as mock_send_tts: + with ( + patch( + "homeassistant.components.assist_pipeline.vad.WebRtcVad.is_speech", + new=is_speech, + ), + patch( + "homeassistant.components.voip.voip.async_pipeline_from_audio_stream", + new=async_pipeline_from_audio_stream, + ), + patch( + "homeassistant.components.voip.voip.PipelineRtpDatagramProtocol._send_tts", + ) as mock_send_tts, + ): rtp_protocol = voip.voip.PipelineRtpDatagramProtocol( hass, hass.config.language, diff --git a/tests/components/volumio/test_config_flow.py b/tests/components/volumio/test_config_flow.py index 93469a02205..7d185161d0a 100644 --- a/tests/components/volumio/test_config_flow.py +++ b/tests/components/volumio/test_config_flow.py @@ -46,13 +46,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.volumio.config_flow.Volumio.get_system_info", - return_value=TEST_SYSTEM_INFO, - ), patch( - "homeassistant.components.volumio.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.volumio.config_flow.Volumio.get_system_info", + return_value=TEST_SYSTEM_INFO, + ), + patch( + "homeassistant.components.volumio.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, @@ -84,12 +87,15 @@ async def test_form_updates_unique_id(hass: HomeAssistant) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "homeassistant.components.volumio.config_flow.Volumio.get_system_info", - return_value=TEST_SYSTEM_INFO, - ), patch( - "homeassistant.components.volumio.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.volumio.config_flow.Volumio.get_system_info", + return_value=TEST_SYSTEM_INFO, + ), + patch( + "homeassistant.components.volumio.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -111,13 +117,16 @@ async def test_empty_system_info(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.volumio.config_flow.Volumio.get_system_info", - return_value={}, - ), patch( - "homeassistant.components.volumio.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.volumio.config_flow.Volumio.get_system_info", + return_value={}, + ), + patch( + "homeassistant.components.volumio.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, @@ -181,13 +190,16 @@ async def test_discovery(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data=TEST_DISCOVERY ) - with patch( - "homeassistant.components.volumio.config_flow.Volumio.get_system_info", - return_value=TEST_SYSTEM_INFO, - ), patch( - "homeassistant.components.volumio.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.volumio.config_flow.Volumio.get_system_info", + return_value=TEST_SYSTEM_INFO, + ), + patch( + "homeassistant.components.volumio.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={}, diff --git a/tests/components/volvooncall/test_config_flow.py b/tests/components/volvooncall/test_config_flow.py index e8b9de9a07b..3c866da58ea 100644 --- a/tests/components/volvooncall/test_config_flow.py +++ b/tests/components/volvooncall/test_config_flow.py @@ -20,10 +20,13 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert len(result["errors"]) == 0 - with patch("volvooncall.Connection.get"), patch( - "homeassistant.components.volvooncall.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("volvooncall.Connection.get"), + patch( + "homeassistant.components.volvooncall.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -86,9 +89,12 @@ async def test_flow_already_configured(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert len(result["errors"]) == 0 - with patch("volvooncall.Connection.get"), patch( - "homeassistant.components.volvooncall.async_setup_entry", - return_value=True, + with ( + patch("volvooncall.Connection.get"), + patch( + "homeassistant.components.volvooncall.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index 9f98447af41..f75021efa05 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -99,10 +99,13 @@ def test_switch(hass: HomeAssistant, hass_devices: list[vultr.VultrSwitch]): @pytest.mark.usefixtures("valid_config") def test_turn_on(hass: HomeAssistant, hass_devices: list[vultr.VultrSwitch]): """Test turning a subscription on.""" - with patch( - "vultr.Vultr.server_list", - return_value=json.loads(load_fixture("server_list.json", "vultr")), - ), patch("vultr.Vultr.server_start") as mock_start: + with ( + patch( + "vultr.Vultr.server_list", + return_value=json.loads(load_fixture("server_list.json", "vultr")), + ), + patch("vultr.Vultr.server_start") as mock_start, + ): for device in hass_devices: if device.name == "Failed Server": device.update() @@ -115,10 +118,13 @@ def test_turn_on(hass: HomeAssistant, hass_devices: list[vultr.VultrSwitch]): @pytest.mark.usefixtures("valid_config") def test_turn_off(hass: HomeAssistant, hass_devices: list[vultr.VultrSwitch]): """Test turning a subscription off.""" - with patch( - "vultr.Vultr.server_list", - return_value=json.loads(load_fixture("server_list.json", "vultr")), - ), patch("vultr.Vultr.server_halt") as mock_halt: + with ( + patch( + "vultr.Vultr.server_list", + return_value=json.loads(load_fixture("server_list.json", "vultr")), + ), + patch("vultr.Vultr.server_halt") as mock_halt, + ): for device in hass_devices: if device.name == "A Server": device.update() diff --git a/tests/components/waqi/test_config_flow.py b/tests/components/waqi/test_config_flow.py index f19d0991f46..712ad8dd39e 100644 --- a/tests/components/waqi/test_config_flow.py +++ b/tests/components/waqi/test_config_flow.py @@ -54,12 +54,15 @@ async def test_full_map_flow( ) assert result["type"] == FlowResultType.FORM - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_ip", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + with ( + patch( + "aiowaqi.WAQIClient.authenticate", + ), + patch( + "aiowaqi.WAQIClient.get_by_ip", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), ), ): result = await hass.config_entries.flow.async_configure( @@ -71,17 +74,21 @@ async def test_full_map_flow( assert result["type"] == FlowResultType.FORM assert result["step_id"] == method - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_coordinates", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + with ( + patch( + "aiowaqi.WAQIClient.authenticate", ), - ), patch( - "aiowaqi.WAQIClient.get_by_station_number", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + patch( + "aiowaqi.WAQIClient.get_by_coordinates", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), + ), + patch( + "aiowaqi.WAQIClient.get_by_station_number", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), ), ): result = await hass.config_entries.flow.async_configure( @@ -115,11 +122,14 @@ async def test_flow_errors( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_ip", - side_effect=exception, + with ( + patch( + "aiowaqi.WAQIClient.authenticate", + ), + patch( + "aiowaqi.WAQIClient.get_by_ip", + side_effect=exception, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -130,12 +140,15 @@ async def test_flow_errors( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": error} - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_ip", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + with ( + patch( + "aiowaqi.WAQIClient.authenticate", + ), + patch( + "aiowaqi.WAQIClient.get_by_ip", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), ), ): result = await hass.config_entries.flow.async_configure( @@ -147,12 +160,15 @@ async def test_flow_errors( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "map" - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_coordinates", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + with ( + patch( + "aiowaqi.WAQIClient.authenticate", + ), + patch( + "aiowaqi.WAQIClient.get_by_coordinates", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), ), ): result = await hass.config_entries.flow.async_configure( @@ -217,12 +233,15 @@ async def test_error_in_second_step( ) assert result["type"] == FlowResultType.FORM - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_ip", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + with ( + patch( + "aiowaqi.WAQIClient.authenticate", + ), + patch( + "aiowaqi.WAQIClient.get_by_ip", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), ), ): result = await hass.config_entries.flow.async_configure( @@ -234,10 +253,12 @@ async def test_error_in_second_step( assert result["type"] == FlowResultType.FORM assert result["step_id"] == method - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch("aiowaqi.WAQIClient.get_by_coordinates", side_effect=exception), patch( - "aiowaqi.WAQIClient.get_by_station_number", side_effect=exception + with ( + patch( + "aiowaqi.WAQIClient.authenticate", + ), + patch("aiowaqi.WAQIClient.get_by_coordinates", side_effect=exception), + patch("aiowaqi.WAQIClient.get_by_station_number", side_effect=exception), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -248,17 +269,21 @@ async def test_error_in_second_step( assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": error} - with patch( - "aiowaqi.WAQIClient.authenticate", - ), patch( - "aiowaqi.WAQIClient.get_by_coordinates", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + with ( + patch( + "aiowaqi.WAQIClient.authenticate", ), - ), patch( - "aiowaqi.WAQIClient.get_by_station_number", - return_value=WAQIAirQuality.from_dict( - json.loads(load_fixture("waqi/air_quality_sensor.json")) + patch( + "aiowaqi.WAQIClient.get_by_coordinates", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), + ), + patch( + "aiowaqi.WAQIClient.get_by_station_number", + return_value=WAQIAirQuality.from_dict( + json.loads(load_fixture("waqi/air_quality_sensor.json")) + ), ), ): result = await hass.config_entries.flow.async_configure( diff --git a/tests/components/watttime/conftest.py b/tests/components/watttime/conftest.py index 72bdc2d3419..0b7403d45fc 100644 --- a/tests/components/watttime/conftest.py +++ b/tests/components/watttime/conftest.py @@ -102,12 +102,16 @@ def get_grid_region_fixture(data_grid_region): @pytest.fixture(name="setup_watttime") async def setup_watttime_fixture(hass, client, config_auth, config_coordinates): """Define a fixture to set up WattTime.""" - with patch( - "homeassistant.components.watttime.Client.async_login", return_value=client - ), patch( - "homeassistant.components.watttime.config_flow.Client.async_login", - return_value=client, - ), patch("homeassistant.components.watttime.PLATFORMS", []): + with ( + patch( + "homeassistant.components.watttime.Client.async_login", return_value=client + ), + patch( + "homeassistant.components.watttime.config_flow.Client.async_login", + return_value=client, + ), + patch("homeassistant.components.watttime.PLATFORMS", []), + ): assert await async_setup_component( hass, DOMAIN, {**config_auth, **config_coordinates} ) diff --git a/tests/components/weather/test_intent.py b/tests/components/weather/test_intent.py index a562838edf3..1fde5882d6e 100644 --- a/tests/components/weather/test_intent.py +++ b/tests/components/weather/test_intent.py @@ -103,7 +103,8 @@ async def test_get_weather_no_state(hass: HomeAssistant) -> None: assert response.response_type == intent.IntentResponseType.QUERY_ANSWER # Failure without state - with patch("homeassistant.core.StateMachine.get", return_value=None), pytest.raises( - intent.IntentHandleError + with ( + patch("homeassistant.core.StateMachine.get", return_value=None), + pytest.raises(intent.IntentHandleError), ): await intent.async_handle(hass, "test", weather_intent.INTENT_GET_WEATHER, {}) diff --git a/tests/components/weatherkit/__init__.py b/tests/components/weatherkit/__init__.py index 5450205d579..99c856a7e37 100644 --- a/tests/components/weatherkit/__init__.py +++ b/tests/components/weatherkit/__init__.py @@ -58,12 +58,15 @@ async def init_integration( else: available_data_sets.append(DataSetType.HOURLY_FORECAST) - with patch( - "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", - return_value=weather_response, - ), patch( - "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability", - return_value=available_data_sets, + with ( + patch( + "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", + return_value=weather_response, + ), + patch( + "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability", + return_value=available_data_sets, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/weatherkit/test_setup.py b/tests/components/weatherkit/test_setup.py index 479d08be20d..c121f0cc5c1 100644 --- a/tests/components/weatherkit/test_setup.py +++ b/tests/components/weatherkit/test_setup.py @@ -25,12 +25,15 @@ async def test_auth_error_handling(hass: HomeAssistant) -> None: data=EXAMPLE_CONFIG_DATA, ) - with patch( - "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", - side_effect=WeatherKitApiClientAuthenticationError, - ), patch( - "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability", - side_effect=WeatherKitApiClientAuthenticationError, + with ( + patch( + "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", + side_effect=WeatherKitApiClientAuthenticationError, + ), + patch( + "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability", + side_effect=WeatherKitApiClientAuthenticationError, + ), ): entry.add_to_hass(hass) setup_result = await hass.config_entries.async_setup(entry.entry_id) @@ -48,12 +51,15 @@ async def test_client_error_handling(hass: HomeAssistant) -> None: data=EXAMPLE_CONFIG_DATA, ) - with patch( - "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", - side_effect=WeatherKitApiClientError, - ), patch( - "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability", - side_effect=WeatherKitApiClientError, + with ( + patch( + "homeassistant.components.weatherkit.WeatherKitApiClient.get_weather_data", + side_effect=WeatherKitApiClientError, + ), + patch( + "homeassistant.components.weatherkit.WeatherKitApiClient.get_availability", + side_effect=WeatherKitApiClientError, + ), ): entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index 6c2646f369e..db186e4811b 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -371,10 +371,13 @@ async def test_prepare_fail( caplog: pytest.LogCaptureFixture, ) -> None: """Test failing to prepare.""" - with patch( - "homeassistant.components.websocket_api.http.web.WebSocketResponse.prepare", - side_effect=(TimeoutError, web.WebSocketResponse.prepare), - ), pytest.raises(ServerDisconnectedError): + with ( + patch( + "homeassistant.components.websocket_api.http.web.WebSocketResponse.prepare", + side_effect=(TimeoutError, web.WebSocketResponse.prepare), + ), + pytest.raises(ServerDisconnectedError), + ): await hass_ws_client(hass) assert "Timeout preparing request" in caplog.text diff --git a/tests/components/wemo/conftest.py b/tests/components/wemo/conftest.py index cdf1e7deb68..1316c37b62b 100644 --- a/tests/components/wemo/conftest.py +++ b/tests/components/wemo/conftest.py @@ -82,8 +82,9 @@ def create_pywemo_device(pywemo_registry, pywemo_model): device.switch_state = 0 url = f"http://{MOCK_HOST}:{MOCK_PORT}/setup.xml" - with patch("pywemo.setup_url_for_address", return_value=url), patch( - "pywemo.discovery.device_from_description", return_value=device + with ( + patch("pywemo.setup_url_for_address", return_value=url), + patch("pywemo.discovery.device_from_description", return_value=device), ): yield device diff --git a/tests/components/wemo/test_init.py b/tests/components/wemo/test_init.py index 5512c2ee989..bf41e703190 100644 --- a/tests/components/wemo/test_init.py +++ b/tests/components/wemo/test_init.py @@ -211,13 +211,15 @@ async def test_discovery(hass: HomeAssistant, pywemo_registry) -> None: pywemo_devices = [create_device(0), create_device(1)] # Setup the component and start discovery. - with patch( - "pywemo.discover_devices", return_value=pywemo_devices - ) as mock_discovery, patch( - "homeassistant.components.wemo.WemoDiscovery.discover_statics" - ) as mock_discover_statics, patch( - "homeassistant.components.wemo.binary_sensor.async_wemo_dispatcher_connect", - side_effect=async_connect, + with ( + patch("pywemo.discover_devices", return_value=pywemo_devices) as mock_discovery, + patch( + "homeassistant.components.wemo.WemoDiscovery.discover_statics" + ) as mock_discover_statics, + patch( + "homeassistant.components.wemo.binary_sensor.async_wemo_dispatcher_connect", + side_effect=async_connect, + ), ): assert await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_DISCOVERY: True}} diff --git a/tests/components/whirlpool/test_config_flow.py b/tests/components/whirlpool/test_config_flow.py index d164bff6a61..273f0e6737d 100644 --- a/tests/components/whirlpool/test_config_flow.py +++ b/tests/components/whirlpool/test_config_flow.py @@ -28,20 +28,27 @@ async def test_form(hass: HomeAssistant, region, brand) -> None: assert result["type"] == "form" assert result["step_id"] == config_entries.SOURCE_USER - with patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=True, - ), patch( - "homeassistant.components.whirlpool.config_flow.BackendSelector" - ) as mock_backend_selector, patch( - "homeassistant.components.whirlpool.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.aircons", - return_value=["test"], - ), patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", - return_value=True, + with ( + patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=True, + ), + patch( + "homeassistant.components.whirlpool.config_flow.BackendSelector" + ) as mock_backend_selector, + patch( + "homeassistant.components.whirlpool.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.aircons", + return_value=["test"], + ), + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -66,9 +73,12 @@ async def test_form_invalid_auth(hass: HomeAssistant, region, brand) -> None: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=False, + with ( + patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=False, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -157,15 +167,20 @@ async def test_form_already_configured(hass: HomeAssistant, region, brand) -> No assert result["type"] == "form" assert result["step_id"] == config_entries.SOURCE_USER - with patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=True, - ), patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.aircons", - return_value=["test"], - ), patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", - return_value=True, + with ( + patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=True, + ), + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.aircons", + return_value=["test"], + ), + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -190,12 +205,16 @@ async def test_no_appliances_flow(hass: HomeAssistant, region, brand) -> None: assert result["type"] == FlowResultType.FORM assert result["step_id"] == config_entries.SOURCE_USER - with patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=True, - ), patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", - return_value=True, + with ( + patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=True, + ), + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -230,18 +249,24 @@ async def test_reauth_flow(hass: HomeAssistant, region, brand) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.whirlpool.async_setup_entry", - return_value=True, - ), patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=True, - ), patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.aircons", - return_value=["test"], - ), patch( - "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", - return_value=True, + with ( + patch( + "homeassistant.components.whirlpool.async_setup_entry", + return_value=True, + ), + patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=True, + ), + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.aircons", + return_value=["test"], + ), + patch( + "homeassistant.components.whirlpool.config_flow.AppliancesManager.fetch_appliances", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -287,12 +312,16 @@ async def test_reauth_flow_auth_error(hass: HomeAssistant, region, brand) -> Non assert result["step_id"] == "reauth_confirm" assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.whirlpool.async_setup_entry", - return_value=True, - ), patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=False, + with ( + patch( + "homeassistant.components.whirlpool.async_setup_entry", + return_value=True, + ), + patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=False, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -330,15 +359,19 @@ async def test_reauth_flow_connnection_error( assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.whirlpool.async_setup_entry", - return_value=True, - ), patch( - "homeassistant.components.whirlpool.config_flow.Auth.do_auth", - side_effect=ClientConnectionError, - ), patch( - "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", - return_value=False, + with ( + patch( + "homeassistant.components.whirlpool.async_setup_entry", + return_value=True, + ), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.do_auth", + side_effect=ClientConnectionError, + ), + patch( + "homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid", + return_value=False, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/whois/conftest.py b/tests/components/whois/conftest.py index c9d25e24b13..457c06db598 100644 --- a/tests/components/whois/conftest.py +++ b/tests/components/whois/conftest.py @@ -41,10 +41,11 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]: @pytest.fixture def mock_whois() -> Generator[MagicMock, None, None]: """Return a mocked query.""" - with patch( - "homeassistant.components.whois.whois_query", - ) as whois_mock, patch( - "homeassistant.components.whois.config_flow.whois.query", new=whois_mock + with ( + patch( + "homeassistant.components.whois.whois_query", + ) as whois_mock, + patch("homeassistant.components.whois.config_flow.whois.query", new=whois_mock), ): domain = whois_mock.return_value domain.abuse_contact = "abuse@example.com" diff --git a/tests/components/withings/conftest.py b/tests/components/withings/conftest.py index 781024c86f7..66dd65efccb 100644 --- a/tests/components/withings/conftest.py +++ b/tests/components/withings/conftest.py @@ -174,11 +174,14 @@ def disable_webhook_delay(): """Disable webhook delay.""" mock = AsyncMock() - with patch( - "homeassistant.components.withings.SUBSCRIBE_DELAY", - timedelta(seconds=0), - ), patch( - "homeassistant.components.withings.UNSUBSCRIBE_DELAY", - timedelta(seconds=0), + with ( + patch( + "homeassistant.components.withings.SUBSCRIBE_DELAY", + timedelta(seconds=0), + ), + patch( + "homeassistant.components.withings.UNSUBSCRIBE_DELAY", + timedelta(seconds=0), + ), ): yield mock diff --git a/tests/components/withings/test_diagnostics.py b/tests/components/withings/test_diagnostics.py index 9d8dc03e52d..d607584df7b 100644 --- a/tests/components/withings/test_diagnostics.py +++ b/tests/components/withings/test_diagnostics.py @@ -56,21 +56,26 @@ async def test_diagnostics_cloudhook_instance( freezer: FrozenDateTimeFactory, ) -> None: """Test diagnostics.""" - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch( - "homeassistant.components.cloud.async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.cloud.async_delete_cloudhook", - ), patch( - "homeassistant.components.withings.webhook_generate_url", + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.cloud.async_delete_cloudhook", + ), + patch( + "homeassistant.components.withings.webhook_generate_url", + ), ): await setup_integration(hass, webhook_config_entry) await prepare_webhook_setup(hass, freezer) diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index c8bebe854eb..eb089f44216 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -295,19 +295,21 @@ async def test_setup_with_cloudhook( await mock_cloud(hass) await hass.async_block_till_done() - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch.object(cloud, "async_active_subscription", return_value=True), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ) as fake_create_cloudhook, patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.cloud.async_delete_cloudhook" - ) as fake_delete_cloudhook, patch( - "homeassistant.components.withings.webhook_generate_url" + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ) as fake_create_cloudhook, + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ) as fake_delete_cloudhook, + patch("homeassistant.components.withings.webhook_generate_url"), ): await setup_integration(hass, cloudhook_config_entry) @@ -338,20 +340,24 @@ async def test_removing_entry_with_cloud_unavailable( await mock_cloud(hass) await hass.async_block_till_done() - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch( - "homeassistant.components.cloud.async_is_connected", return_value=True - ), patch.object(cloud, "async_active_subscription", return_value=True), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.cloud.async_delete_cloudhook", - side_effect=CloudNotAvailable(), - ), patch( - "homeassistant.components.withings.webhook_generate_url", + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.cloud.async_delete_cloudhook", + side_effect=CloudNotAvailable(), + ), + patch( + "homeassistant.components.withings.webhook_generate_url", + ), ): await setup_integration(hass, cloudhook_config_entry) @@ -377,19 +383,21 @@ async def test_setup_with_cloud( await mock_cloud(hass) await hass.async_block_till_done() - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch.object(cloud, "async_is_connected", return_value=True), patch.object( - cloud, "async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ) as fake_create_cloudhook, patch( - "homeassistant.components.withings.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.cloud.async_delete_cloudhook" - ) as fake_delete_cloudhook, patch( - "homeassistant.components.withings.webhook_generate_url" + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch.object(cloud, "async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ) as fake_create_cloudhook, + patch( + "homeassistant.components.withings.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.cloud.async_delete_cloudhook" + ) as fake_delete_cloudhook, + patch("homeassistant.components.withings.webhook_generate_url"), ): await setup_integration(hass, webhook_config_entry) await prepare_webhook_setup(hass, freezer) @@ -426,14 +434,18 @@ async def test_setup_no_webhook( ) -> None: """Test if set up with cloud link and without https.""" hass.config.components.add("cloud") - with patch( - "homeassistant.helpers.network.get_url", - return_value="http://example.nabu.casa", - ), patch( - "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.withings.webhook_generate_url" - ) as mock_async_generate_url: + with ( + patch( + "homeassistant.helpers.network.get_url", + return_value="http://example.nabu.casa", + ), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.withings.webhook_generate_url" + ) as mock_async_generate_url, + ): mock_async_generate_url.return_value = url await setup_integration(hass, webhook_config_entry) await prepare_webhook_setup(hass, freezer) @@ -455,19 +467,23 @@ async def test_cloud_disconnect( await mock_cloud(hass) await hass.async_block_till_done() - with patch( - "homeassistant.components.cloud.async_is_logged_in", return_value=True - ), patch.object(cloud, "async_is_connected", return_value=True), patch.object( - cloud, "async_active_subscription", return_value=True - ), patch( - "homeassistant.components.cloud.async_create_cloudhook", - return_value="https://hooks.nabu.casa/ABCD", - ), patch( - "homeassistant.components.withings.async_get_config_entry_implementation", - ), patch( - "homeassistant.components.cloud.async_delete_cloudhook", - ), patch( - "homeassistant.components.withings.webhook_generate_url", + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch.object(cloud, "async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ), + patch( + "homeassistant.components.withings.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.cloud.async_delete_cloudhook", + ), + patch( + "homeassistant.components.withings.webhook_generate_url", + ), ): await setup_integration(hass, webhook_config_entry) await prepare_webhook_setup(hass, freezer) diff --git a/tests/components/wiz/__init__.py b/tests/components/wiz/__init__.py index 165e0557fcd..e80a1ed8249 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -232,9 +232,12 @@ def _patch_wizlight(device=None, extended_white_range=None, bulb_type=None): @contextmanager def _patcher(): bulb = device or _mocked_wizlight(device, extended_white_range, bulb_type) - with patch("homeassistant.components.wiz.wizlight", return_value=bulb), patch( - "homeassistant.components.wiz.config_flow.wizlight", - return_value=bulb, + with ( + patch("homeassistant.components.wiz.wizlight", return_value=bulb), + patch( + "homeassistant.components.wiz.config_flow.wizlight", + return_value=bulb, + ), ): yield diff --git a/tests/components/wiz/test_config_flow.py b/tests/components/wiz/test_config_flow.py index 6ee77e83fb6..1b84a048fd2 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -52,12 +52,16 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} # Patch functions - with _patch_wizlight(), patch( - "homeassistant.components.wiz.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.wiz.async_setup", return_value=True - ) as mock_setup: + with ( + _patch_wizlight(), + patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], TEST_CONNECTION, @@ -90,12 +94,16 @@ async def test_user_flow_enters_dns_name(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": "no_ip"} - with _patch_wizlight(), patch( - "homeassistant.components.wiz.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.wiz.async_setup", return_value=True - ) as mock_setup: + with ( + _patch_wizlight(), + patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], TEST_CONNECTION, @@ -265,14 +273,18 @@ async def test_discovered_by_dhcp_or_integration_discovery( assert result["type"] == FlowResultType.FORM assert result["step_id"] == "discovery_confirm" - with _patch_wizlight( - device=None, extended_white_range=extended_white_range, bulb_type=bulb_type - ), patch( - "homeassistant.components.wiz.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.wiz.async_setup", return_value=True - ) as mock_setup: + with ( + _patch_wizlight( + device=None, extended_white_range=extended_white_range, bulb_type=bulb_type + ), + patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -380,11 +392,15 @@ async def test_setup_via_discovery(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_wizlight(), patch( - "homeassistant.components.wiz.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.wiz.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_wizlight(), + patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.wiz.async_setup_entry", return_value=True + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: FAKE_MAC}, @@ -433,10 +449,13 @@ async def test_setup_via_discovery_cannot_connect(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with patch( - "homeassistant.components.wiz.wizlight.getBulbConfig", - side_effect=WizLightTimeOutError, - ), _patch_discovery(): + with ( + patch( + "homeassistant.components.wiz.wizlight.getBulbConfig", + side_effect=WizLightTimeOutError, + ), + _patch_discovery(), + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: FAKE_MAC}, @@ -489,15 +508,19 @@ async def test_discovery_with_firmware_update(hass: HomeAssistant) -> None: # updates and we now can see its really RGBWW not RGBW since the older # firmwares did not tell us how many white channels exist - with patch( - "homeassistant.components.wiz.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.wiz.async_setup", return_value=True - ) as mock_setup, _patch_wizlight( - device=None, - extended_white_range=FAKE_EXTENDED_WHITE_RANGE, - bulb_type=FAKE_RGBWW_BULB, + with ( + patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, + _patch_wizlight( + device=None, + extended_white_range=FAKE_EXTENDED_WHITE_RANGE, + bulb_type=FAKE_RGBWW_BULB, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -523,13 +546,18 @@ async def test_discovery_with_firmware_update(hass: HomeAssistant) -> None: ) async def test_discovered_during_onboarding(hass: HomeAssistant, source, data) -> None: """Test dhcp or discovery during onboarding creates the config entry.""" - with _patch_wizlight(), patch( - "homeassistant.components.wiz.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.wiz.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + _patch_wizlight(), + patch( + "homeassistant.components.wiz.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.wiz.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data diff --git a/tests/components/wiz/test_init.py b/tests/components/wiz/test_init.py index bd494da95b7..d6813263fcc 100644 --- a/tests/components/wiz/test_init.py +++ b/tests/components/wiz/test_init.py @@ -58,9 +58,10 @@ async def test_cleanup_on_failed_first_update(hass: HomeAssistant) -> None: data={CONF_HOST: FAKE_IP}, ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.wiz.discovery.find_wizlights", return_value=[] - ), _patch_wizlight(device=bulb): + with ( + patch("homeassistant.components.wiz.discovery.find_wizlights", return_value=[]), + _patch_wizlight(device=bulb), + ): await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY diff --git a/tests/components/wled/conftest.py b/tests/components/wled/conftest.py index ce0c7c28f6a..d2f124a517b 100644 --- a/tests/components/wled/conftest.py +++ b/tests/components/wled/conftest.py @@ -53,10 +53,11 @@ def device_fixture() -> str: @pytest.fixture def mock_wled(device_fixture: str) -> Generator[MagicMock, None, None]: """Return a mocked WLED client.""" - with patch( - "homeassistant.components.wled.coordinator.WLED", autospec=True - ) as wled_mock, patch( - "homeassistant.components.wled.config_flow.WLED", new=wled_mock + with ( + patch( + "homeassistant.components.wled.coordinator.WLED", autospec=True + ) as wled_mock, + patch("homeassistant.components.wled.config_flow.WLED", new=wled_mock), ): wled = wled_mock.return_value wled.update.return_value = WLEDDevice( diff --git a/tests/components/wolflink/test_config_flow.py b/tests/components/wolflink/test_config_flow.py index 5760a54f5ce..bee646deae8 100644 --- a/tests/components/wolflink/test_config_flow.py +++ b/tests/components/wolflink/test_config_flow.py @@ -60,10 +60,13 @@ async def test_device_step_form(hass: HomeAssistant) -> None: async def test_create_entry(hass: HomeAssistant) -> None: """Test entity creation from device step.""" - with patch( - "homeassistant.components.wolflink.config_flow.WolfClient.fetch_system_list", - return_value=[DEVICE], - ), patch("homeassistant.components.wolflink.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.wolflink.config_flow.WolfClient.fetch_system_list", + return_value=[DEVICE], + ), + patch("homeassistant.components.wolflink.async_setup_entry", return_value=True), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=INPUT_CONFIG ) @@ -122,10 +125,13 @@ async def test_form_unknown_exception(hass: HomeAssistant) -> None: async def test_already_configured_error(hass: HomeAssistant) -> None: """Test already configured while creating entry.""" - with patch( - "homeassistant.components.wolflink.config_flow.WolfClient.fetch_system_list", - return_value=[DEVICE], - ), patch("homeassistant.components.wolflink.async_setup_entry", return_value=True): + with ( + patch( + "homeassistant.components.wolflink.config_flow.WolfClient.fetch_system_list", + return_value=[DEVICE], + ), + patch("homeassistant.components.wolflink.async_setup_entry", return_value=True), + ): MockConfigEntry( domain=DOMAIN, unique_id=CONFIG[DEVICE_ID], data=CONFIG ).add_to_hass(hass) diff --git a/tests/components/ws66i/test_config_flow.py b/tests/components/ws66i/test_config_flow.py index aa5e9005394..19a329cb913 100644 --- a/tests/components/ws66i/test_config_flow.py +++ b/tests/components/ws66i/test_config_flow.py @@ -32,12 +32,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] == {} - with patch( - "homeassistant.components.ws66i.config_flow.get_ws66i", - ) as mock_ws66i, patch( - "homeassistant.components.ws66i.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.ws66i.config_flow.get_ws66i", + ) as mock_ws66i, + patch( + "homeassistant.components.ws66i.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): ws66i_instance = mock_ws66i.return_value result2 = await hass.config_entries.flow.async_configure( diff --git a/tests/components/wyoming/__init__.py b/tests/components/wyoming/__init__.py index 45fff55a89c..5bfbbfe87b2 100644 --- a/tests/components/wyoming/__init__.py +++ b/tests/components/wyoming/__init__.py @@ -144,12 +144,15 @@ async def reload_satellite( hass: HomeAssistant, config_entry_id: str ) -> SatelliteDevice: """Reload config entry with satellite info and returns new device.""" - with patch( - "homeassistant.components.wyoming.data.load_wyoming_info", - return_value=SATELLITE_INFO, - ), patch( - "homeassistant.components.wyoming.satellite.WyomingSatellite.run" - ) as _run_mock: + with ( + patch( + "homeassistant.components.wyoming.data.load_wyoming_info", + return_value=SATELLITE_INFO, + ), + patch( + "homeassistant.components.wyoming.satellite.WyomingSatellite.run" + ) as _run_mock, + ): # _run_mock: satellite task does not actually run await hass.config_entries.async_reload(config_entry_id) diff --git a/tests/components/wyoming/conftest.py b/tests/components/wyoming/conftest.py index b6111eaddd8..4be12312c7a 100644 --- a/tests/components/wyoming/conftest.py +++ b/tests/components/wyoming/conftest.py @@ -146,12 +146,15 @@ def satellite_config_entry(hass: HomeAssistant) -> ConfigEntry: @pytest.fixture async def init_satellite(hass: HomeAssistant, satellite_config_entry: ConfigEntry): """Initialize Wyoming satellite.""" - with patch( - "homeassistant.components.wyoming.data.load_wyoming_info", - return_value=SATELLITE_INFO, - ), patch( - "homeassistant.components.wyoming.satellite.WyomingSatellite.run" - ) as _run_mock: + with ( + patch( + "homeassistant.components.wyoming.data.load_wyoming_info", + return_value=SATELLITE_INFO, + ), + patch( + "homeassistant.components.wyoming.satellite.WyomingSatellite.run" + ) as _run_mock, + ): # _run_mock: satellite task does not actually run await hass.config_entries.async_setup(satellite_config_entry.entry_id) diff --git a/tests/components/wyoming/test_config_flow.py b/tests/components/wyoming/test_config_flow.py index f0713448e54..c15eb81a1e2 100644 --- a/tests/components/wyoming/test_config_flow.py +++ b/tests/components/wyoming/test_config_flow.py @@ -262,10 +262,13 @@ async def test_zeroconf_discovery_no_port( snapshot: SnapshotAssertion, ) -> None: """Test discovery when the zeroconf service does not have a port.""" - with patch( - "homeassistant.components.wyoming.data.load_wyoming_info", - return_value=SATELLITE_INFO, - ), patch.object(ZEROCONF_DISCOVERY, "port", None): + with ( + patch( + "homeassistant.components.wyoming.data.load_wyoming_info", + return_value=SATELLITE_INFO, + ), + patch.object(ZEROCONF_DISCOVERY, "port", None), + ): result = await hass.config_entries.flow.async_init( DOMAIN, data=ZEROCONF_DISCOVERY, diff --git a/tests/components/wyoming/test_satellite.py b/tests/components/wyoming/test_satellite.py index 76f442ef246..a9d1e73e153 100644 --- a/tests/components/wyoming/test_satellite.py +++ b/tests/components/wyoming/test_satellite.py @@ -185,9 +185,9 @@ async def test_satellite_pipeline(hass: HomeAssistant) -> None: ] pipeline_kwargs: dict[str, Any] = {} - pipeline_event_callback: Callable[ - [assist_pipeline.PipelineEvent], None - ] | None = None + pipeline_event_callback: Callable[[assist_pipeline.PipelineEvent], None] | None = ( + None + ) run_pipeline_called = asyncio.Event() audio_chunk_received = asyncio.Event() @@ -771,9 +771,9 @@ async def test_pipeline_changed(hass: HomeAssistant) -> None: ).event(), ] - pipeline_event_callback: Callable[ - [assist_pipeline.PipelineEvent], None - ] | None = None + pipeline_event_callback: Callable[[assist_pipeline.PipelineEvent], None] | None = ( + None + ) run_pipeline_called = asyncio.Event() pipeline_stopped = asyncio.Event() @@ -845,9 +845,9 @@ async def test_audio_settings_changed(hass: HomeAssistant) -> None: ).event(), ] - pipeline_event_callback: Callable[ - [assist_pipeline.PipelineEvent], None - ] | None = None + pipeline_event_callback: Callable[[assist_pipeline.PipelineEvent], None] | None = ( + None + ) run_pipeline_called = asyncio.Event() pipeline_stopped = asyncio.Event() @@ -981,9 +981,9 @@ async def test_client_stops_pipeline(hass: HomeAssistant) -> None: ).event(), ] - pipeline_event_callback: Callable[ - [assist_pipeline.PipelineEvent], None - ] | None = None + pipeline_event_callback: Callable[[assist_pipeline.PipelineEvent], None] | None = ( + None + ) run_pipeline_called = asyncio.Event() pipeline_stopped = asyncio.Event() diff --git a/tests/components/wyoming/test_stt.py b/tests/components/wyoming/test_stt.py index 4d1ab6dc4d9..900ee8d544c 100644 --- a/tests/components/wyoming/test_stt.py +++ b/tests/components/wyoming/test_stt.py @@ -82,10 +82,13 @@ async def test_streaming_audio_oserror( mock_client = MockAsyncTcpClient([Transcript(text="Hello world").event()]) - with patch( - "homeassistant.components.wyoming.stt.AsyncTcpClient", - mock_client, - ), patch.object(mock_client, "read_event", side_effect=OSError("Boom!")): + with ( + patch( + "homeassistant.components.wyoming.stt.AsyncTcpClient", + mock_client, + ), + patch.object(mock_client, "read_event", side_effect=OSError("Boom!")), + ): result = await entity.async_process_audio_stream(metadata, audio_stream()) assert result.result == stt.SpeechResultState.ERROR diff --git a/tests/components/wyoming/test_tts.py b/tests/components/wyoming/test_tts.py index 895774bf92a..4063418e566 100644 --- a/tests/components/wyoming/test_tts.py +++ b/tests/components/wyoming/test_tts.py @@ -147,10 +147,13 @@ async def test_get_tts_audio_connection_lost( hass: HomeAssistant, init_wyoming_tts ) -> None: """Test streaming audio and losing connection.""" - with patch( - "homeassistant.components.wyoming.tts.AsyncTcpClient", - MockAsyncTcpClient([None]), - ), pytest.raises(HomeAssistantError): + with ( + patch( + "homeassistant.components.wyoming.tts.AsyncTcpClient", + MockAsyncTcpClient([None]), + ), + pytest.raises(HomeAssistantError), + ): await tts.async_get_media_source_audio( hass, tts.generate_media_source_id(hass, "Hello world", "tts.test_tts", "en-US"), @@ -169,13 +172,15 @@ async def test_get_tts_audio_audio_oserror( mock_client = MockAsyncTcpClient(audio_events) - with patch( - "homeassistant.components.wyoming.tts.AsyncTcpClient", - mock_client, - ), patch.object( - mock_client, "read_event", side_effect=OSError("Boom!") - ), pytest.raises( - HomeAssistantError, + with ( + patch( + "homeassistant.components.wyoming.tts.AsyncTcpClient", + mock_client, + ), + patch.object(mock_client, "read_event", side_effect=OSError("Boom!")), + pytest.raises( + HomeAssistantError, + ), ): await tts.async_get_media_source_audio( hass, diff --git a/tests/components/xiaomi_aqara/test_config_flow.py b/tests/components/xiaomi_aqara/test_config_flow.py index a2cf62297ef..67991714203 100644 --- a/tests/components/xiaomi_aqara/test_config_flow.py +++ b/tests/components/xiaomi_aqara/test_config_flow.py @@ -33,14 +33,18 @@ def xiaomi_aqara_fixture(): """Mock xiaomi_aqara discovery and entry setup.""" mock_gateway_discovery = get_mock_discovery([TEST_HOST]) - with patch( - "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGatewayDiscovery", - return_value=mock_gateway_discovery, - ), patch( - "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGateway", - return_value=mock_gateway_discovery.gateways[TEST_HOST], - ), patch( - "homeassistant.components.xiaomi_aqara.async_setup_entry", return_value=True + with ( + patch( + "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGatewayDiscovery", + return_value=mock_gateway_discovery, + ), + patch( + "homeassistant.components.xiaomi_aqara.config_flow.XiaomiGateway", + return_value=mock_gateway_discovery.gateways[TEST_HOST], + ), + patch( + "homeassistant.components.xiaomi_aqara.async_setup_entry", return_value=True + ), ): yield diff --git a/tests/components/xiaomi_ble/test_binary_sensor.py b/tests/components/xiaomi_ble/test_binary_sensor.py index b9e0b24a3cf..e446a8fb66e 100644 --- a/tests/components/xiaomi_ble/test_binary_sensor.py +++ b/tests/components/xiaomi_ble/test_binary_sensor.py @@ -325,9 +325,12 @@ async def test_unavailable(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() @@ -377,9 +380,12 @@ async def test_sleepy_device(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() @@ -429,9 +435,12 @@ async def test_sleepy_device_restore_state(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/xiaomi_ble/test_config_flow.py b/tests/components/xiaomi_ble/test_config_flow.py index 1ae2a75e66b..8b3ff2ef4ab 100644 --- a/tests/components/xiaomi_ble/test_config_flow.py +++ b/tests/components/xiaomi_ble/test_config_flow.py @@ -114,12 +114,15 @@ async def test_async_step_bluetooth_valid_device_but_missing_payload_then_full( async def test_async_step_bluetooth_during_onboarding(hass: HomeAssistant) -> None: """Test discovery via bluetooth during onboarding.""" - with patch( - "homeassistant.components.xiaomi_ble.async_setup_entry", return_value=True - ) as mock_setup_entry, patch( - "homeassistant.components.onboarding.async_is_onboarded", - return_value=False, - ) as mock_onboarding: + with ( + patch( + "homeassistant.components.xiaomi_ble.async_setup_entry", return_value=True + ) as mock_setup_entry, + patch( + "homeassistant.components.onboarding.async_is_onboarded", + return_value=False, + ) as mock_onboarding, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_BLUETOOTH}, diff --git a/tests/components/xiaomi_ble/test_sensor.py b/tests/components/xiaomi_ble/test_sensor.py index 96957bb686f..4d9a29e3111 100644 --- a/tests/components/xiaomi_ble/test_sensor.py +++ b/tests/components/xiaomi_ble/test_sensor.py @@ -693,9 +693,12 @@ async def test_unavailable(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() @@ -739,9 +742,12 @@ async def test_sleepy_device(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() @@ -787,9 +793,12 @@ async def test_sleepy_device_restore_state(hass: HomeAssistant) -> None: # Fastforward time without BLE advertisements monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 1 - with patch_bluetooth_time( - monotonic_now, - ), patch_all_discovered_devices([]): + with ( + patch_bluetooth_time( + monotonic_now, + ), + patch_all_discovered_devices([]), + ): async_fire_time_changed( hass, dt_util.utcnow() diff --git a/tests/components/xiaomi_miio/test_button.py b/tests/components/xiaomi_miio/test_button.py index 098c08d189a..8159d7c49e5 100644 --- a/tests/components/xiaomi_miio/test_button.py +++ b/tests/components/xiaomi_miio/test_button.py @@ -33,12 +33,15 @@ async def setup_test(hass: HomeAssistant): mock_vacuum = MagicMock() - with patch( - "homeassistant.components.xiaomi_miio.get_platforms", - return_value=[ - Platform.BUTTON, - ], - ), patch("homeassistant.components.xiaomi_miio.RoborockVacuum") as mock_vacuum_cls: + with ( + patch( + "homeassistant.components.xiaomi_miio.get_platforms", + return_value=[ + Platform.BUTTON, + ], + ), + patch("homeassistant.components.xiaomi_miio.RoborockVacuum") as mock_vacuum_cls, + ): mock_vacuum_cls.return_value = mock_vacuum yield mock_vacuum diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index 06c3bf15a1e..7645f67732e 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -73,19 +73,25 @@ def xiaomi_miio_connect_fixture(): """Mock miio connection and entry setup.""" mock_info = get_mock_info() - with patch( - "homeassistant.components.xiaomi_miio.device.Device.info", - return_value=mock_info, - ), patch( - "homeassistant.components.xiaomi_miio.config_flow.MiCloud.login", - return_value=True, - ), patch( - "homeassistant.components.xiaomi_miio.config_flow.MiCloud.get_devices", - return_value=TEST_CLOUD_DEVICES_1, - ), patch( - "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.xiaomi_miio.async_unload_entry", return_value=True + with ( + patch( + "homeassistant.components.xiaomi_miio.device.Device.info", + return_value=mock_info, + ), + patch( + "homeassistant.components.xiaomi_miio.config_flow.MiCloud.login", + return_value=True, + ), + patch( + "homeassistant.components.xiaomi_miio.config_flow.MiCloud.get_devices", + return_value=TEST_CLOUD_DEVICES_1, + ), + patch( + "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True + ), + patch( + "homeassistant.components.xiaomi_miio.async_unload_entry", return_value=True + ), ): yield diff --git a/tests/components/xiaomi_miio/test_select.py b/tests/components/xiaomi_miio/test_select.py index 794fbb090e0..f2f04127d75 100644 --- a/tests/components/xiaomi_miio/test_select.py +++ b/tests/components/xiaomi_miio/test_select.py @@ -46,12 +46,17 @@ async def setup_test(hass: HomeAssistant): mock_airfresh.status().display_orientation = DisplayOrientation.Portrait mock_airfresh.status().ptc_level = PtcLevel.Low - with patch( - "homeassistant.components.xiaomi_miio.get_platforms", - return_value=[ - Platform.SELECT, - ], - ), patch("homeassistant.components.xiaomi_miio.AirFreshT2017") as mock_airfresh_cls: + with ( + patch( + "homeassistant.components.xiaomi_miio.get_platforms", + return_value=[ + Platform.SELECT, + ], + ), + patch( + "homeassistant.components.xiaomi_miio.AirFreshT2017" + ) as mock_airfresh_cls, + ): mock_airfresh_cls.return_value = mock_airfresh yield mock_airfresh diff --git a/tests/components/yale_smart_alarm/test_config_flow.py b/tests/components/yale_smart_alarm/test_config_flow.py index 5a7f3bc3d08..5eed34a2423 100644 --- a/tests/components/yale_smart_alarm/test_config_flow.py +++ b/tests/components/yale_smart_alarm/test_config_flow.py @@ -24,12 +24,15 @@ async def test_form(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - ), patch( - "homeassistant.components.yale_smart_alarm.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + ), + patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -85,11 +88,14 @@ async def test_form_invalid_auth( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": p_error} - with patch( - "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - ), patch( - "homeassistant.components.yale_smart_alarm.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + ), + patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -139,12 +145,15 @@ async def test_reauth_flow(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - ) as mock_yale, patch( - "homeassistant.components.yale_smart_alarm.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + ) as mock_yale, + patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -220,12 +229,15 @@ async def test_reauth_flow_error( assert result2["type"] == FlowResultType.FORM assert result2["errors"] == {"base": p_error} - with patch( - "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - return_value="", - ), patch( - "homeassistant.components.yale_smart_alarm.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + return_value="", + ), + patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -261,12 +273,15 @@ async def test_options_flow(hass: HomeAssistant) -> None: ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", - return_value=True, - ), patch( - "homeassistant.components.yale_smart_alarm.async_setup_entry", - return_value=True, + with ( + patch( + "homeassistant.components.yale_smart_alarm.config_flow.YaleSmartAlarmClient", + return_value=True, + ), + patch( + "homeassistant.components.yale_smart_alarm.async_setup_entry", + return_value=True, + ), ): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/yalexs_ble/test_config_flow.py b/tests/components/yalexs_ble/test_config_flow.py index 510e2c8be56..34ffc55ac3f 100644 --- a/tests/components/yalexs_ble/test_config_flow.py +++ b/tests/components/yalexs_ble/test_config_flow.py @@ -61,12 +61,15 @@ async def test_user_step_success(hass: HomeAssistant, slot: int) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -175,12 +178,15 @@ async def test_user_step_invalid_keys(hass: HomeAssistant) -> None: assert result4["step_id"] == "user" assert result4["errors"] == {CONF_SLOT: "invalid_key_index"} - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result5 = await hass.config_entries.flow.async_configure( result4["flow_id"], { @@ -234,12 +240,15 @@ async def test_user_step_cannot_connect(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "cannot_connect"} - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -293,12 +302,15 @@ async def test_user_step_auth_exception(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {CONF_KEY: "invalid_auth"} - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -352,12 +364,15 @@ async def test_user_step_unknown_exception(hass: HomeAssistant) -> None: assert result2["step_id"] == "user" assert result2["errors"] == {"base": "unknown"} - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { @@ -391,12 +406,15 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: assert result["step_id"] == "user" assert result["errors"] == {} - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -579,13 +597,16 @@ async def test_integration_discovery_updates_key_unique_local_name( ) entry.add_to_hass(hass) - with patch( - "homeassistant.components.yalexs_ble.util.async_discovered_service_info", - return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + return_value=[LOCK_DISCOVERY_INFO_UUID_ADDRESS], + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, @@ -853,13 +874,16 @@ async def test_user_is_setting_up_lock_and_discovery_happens_in_the_middle( valdidate_started.set() await user_flow_event.wait() - with patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - side_effect=_wait_for_user_flow, - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + side_effect=_wait_for_user_flow, + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): user_flow_task = asyncio.create_task( hass.config_entries.flow.async_configure( result["flow_id"], @@ -946,15 +970,19 @@ async def test_reauth(hass: HomeAssistant) -> None: assert result2["step_id"] == "reauth_validate" assert result2["errors"] == {"base": "no_longer_in_range"} - with patch( - "homeassistant.components.yalexs_ble.config_flow.async_ble_device_from_address", - return_value=YALE_ACCESS_LOCK_DISCOVERY_INFO, - ), patch( - "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", - ), patch( - "homeassistant.components.yalexs_ble.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.yalexs_ble.config_flow.async_ble_device_from_address", + return_value=YALE_ACCESS_LOCK_DISCOVERY_INFO, + ), + patch( + "homeassistant.components.yalexs_ble.config_flow.PushLock.validate", + ), + patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result2["flow_id"], { diff --git a/tests/components/yamaha_musiccast/test_config_flow.py b/tests/components/yamaha_musiccast/test_config_flow.py index 356dc78ded1..9740bd70a87 100644 --- a/tests/components/yamaha_musiccast/test_config_flow.py +++ b/tests/components/yamaha_musiccast/test_config_flow.py @@ -18,14 +18,16 @@ from tests.common import MockConfigEntry @pytest.fixture(autouse=True) async def silent_ssdp_scanner(hass): """Start SSDP component and get Scanner, prevent actual SSDP traffic.""" - with patch( - "homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners" - ), patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), patch( - "homeassistant.components.ssdp.Scanner.async_scan" - ), patch( - "homeassistant.components.ssdp.Server._async_start_upnp_servers", - ), patch( - "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + with ( + patch("homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), + patch("homeassistant.components.ssdp.Scanner.async_scan"), + patch( + "homeassistant.components.ssdp.Server._async_start_upnp_servers", + ), + patch( + "homeassistant.components.ssdp.Server._async_stop_upnp_servers", + ), ): yield diff --git a/tests/components/yeelight/test_config_flow.py b/tests/components/yeelight/test_config_flow.py index e04dad88da4..41d60c8652a 100644 --- a/tests/components/yeelight/test_config_flow.py +++ b/tests/components/yeelight/test_config_flow.py @@ -89,11 +89,12 @@ async def test_discovery(hass: HomeAssistant) -> None: assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: ID} ) @@ -128,8 +129,11 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No alternate_bulb.capabilities["id"] = "0x000000000099999" alternate_bulb.capabilities["location"] = "yeelight://4.4.4.4" - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=alternate_bulb + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=alternate_bulb), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -166,8 +170,10 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No assert result2["step_id"] == "pick_device" assert not result2["errors"] - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb() + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_DEVICE: ID} @@ -202,9 +208,11 @@ async def test_discovery_no_device(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(): + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) assert result2["type"] == "abort" @@ -224,10 +232,11 @@ async def test_import(hass: HomeAssistant) -> None: # Cannot connect mocked_bulb = _mocked_bulb(cannot_connect=True) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config @@ -237,11 +246,12 @@ async def test_import(hass: HomeAssistant) -> None: # Success mocked_bulb = _mocked_bulb() - with _patch_discovery(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb - ), patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry: + with ( + _patch_discovery(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), + patch(f"{MODULE}.async_setup", return_value=True) as mock_setup, + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config ) @@ -261,8 +271,9 @@ async def test_import(hass: HomeAssistant) -> None: # Duplicate mocked_bulb = _mocked_bulb() - with _patch_discovery(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config @@ -282,10 +293,11 @@ async def test_manual(hass: HomeAssistant) -> None: # Cannot connect (timeout) mocked_bulb = _mocked_bulb(cannot_connect=True) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -295,10 +307,11 @@ async def test_manual(hass: HomeAssistant) -> None: assert result2["errors"] == {"base": "cannot_connect"} # Cannot connect (error) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -307,10 +320,12 @@ async def test_manual(hass: HomeAssistant) -> None: # Success mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_timeout(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb - ), patch(f"{MODULE}.async_setup", return_value=True), patch( - f"{MODULE}.async_setup_entry", return_value=True + with ( + _patch_discovery(), + _patch_discovery_timeout(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), + patch(f"{MODULE}.async_setup", return_value=True), + patch(f"{MODULE}.async_setup_entry", return_value=True), ): result4 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -329,10 +344,11 @@ async def test_manual(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) mocked_bulb = _mocked_bulb() - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -436,16 +452,19 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None: assert not result["errors"] mocked_bulb = _mocked_bulb() - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb - ), patch( - f"{MODULE}.async_setup", - return_value=True, - ), patch( - f"{MODULE}.async_setup_entry", - return_value=True, + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), + patch( + f"{MODULE}.async_setup", + return_value=True, + ), + patch( + f"{MODULE}.async_setup_entry", + return_value=True, + ), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: IP_ADDRESS} @@ -462,8 +481,10 @@ async def test_discovered_by_homekit_and_dhcp(hass: HomeAssistant) -> None: """Test we get the form with homekit and abort for dhcp source when we get both.""" mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -482,8 +503,10 @@ async def test_discovered_by_homekit_and_dhcp(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result2 = await hass.config_entries.flow.async_init( DOMAIN, @@ -496,8 +519,10 @@ async def test_discovered_by_homekit_and_dhcp(hass: HomeAssistant) -> None: assert result2["type"] == FlowResultType.ABORT assert result2["reason"] == "already_in_progress" - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result3 = await hass.config_entries.flow.async_init( DOMAIN, @@ -510,10 +535,11 @@ async def test_discovered_by_homekit_and_dhcp(hass: HomeAssistant) -> None: assert result3["type"] == FlowResultType.ABORT assert result3["reason"] == "already_in_progress" - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", side_effect=CannotConnect + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", side_effect=CannotConnect), ): result3 = await hass.config_entries.flow.async_init( DOMAIN, @@ -554,8 +580,10 @@ async def test_discovered_by_dhcp_or_homekit(hass: HomeAssistant, source, data) """Test we can setup when discovered from dhcp or homekit.""" mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data @@ -565,11 +593,14 @@ async def test_discovered_by_dhcp_or_homekit(hass: HomeAssistant, source, data) assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -582,10 +613,11 @@ async def test_discovered_by_dhcp_or_homekit(hass: HomeAssistant, source, data) assert mock_async_setup.called assert mock_async_setup_entry.called - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", side_effect=CannotConnect + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", side_effect=CannotConnect), ): result3 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data @@ -624,10 +656,11 @@ async def test_discovered_by_dhcp_or_homekit_failed_to_get_id( """Test we abort if we cannot get the unique id when discovered from dhcp or homekit.""" mocked_bulb = _mocked_bulb() - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data @@ -640,8 +673,10 @@ async def test_discovered_ssdp(hass: HomeAssistant) -> None: """Test we can setup when discovered from ssdp.""" mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=SSDP_INFO @@ -651,11 +686,14 @@ async def test_discovered_ssdp(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -669,8 +707,10 @@ async def test_discovered_ssdp(hass: HomeAssistant) -> None: assert mock_async_setup_entry.called mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_SSDP}, data=SSDP_INFO @@ -685,8 +725,10 @@ async def test_discovered_zeroconf(hass: HomeAssistant) -> None: """Test we can setup when discovered from zeroconf.""" mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -698,11 +740,14 @@ async def test_discovered_zeroconf(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.FORM assert result["errors"] is None - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.async_setup", return_value=True - ) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry: + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -716,8 +761,10 @@ async def test_discovered_zeroconf(hass: HomeAssistant) -> None: assert mock_async_setup_entry.called mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -730,8 +777,10 @@ async def test_discovered_zeroconf(hass: HomeAssistant) -> None: assert result["reason"] == "already_configured" mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -752,8 +801,10 @@ async def test_discovery_updates_ip(hass: HomeAssistant) -> None: config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -780,10 +831,11 @@ async def test_discovery_updates_ip_no_reload_setup_in_progress( config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_setup_entry, _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry, + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -804,8 +856,10 @@ async def test_discovery_adds_missing_ip_id_only(hass: HomeAssistant) -> None: config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -845,13 +899,18 @@ async def test_discovery_adds_missing_ip_id_only(hass: HomeAssistant) -> None: async def test_discovered_during_onboarding(hass: HomeAssistant, source, data) -> None: """Test we create a config entry when discovered during onboarding.""" mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb - ), patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, patch( - f"{MODULE}.async_setup_entry", return_value=True - ) as mock_async_setup_entry, patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False - ) as mock_is_onboarded: + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE_CONFIG_FLOW}.AsyncBulb", return_value=mocked_bulb), + patch(f"{MODULE}.async_setup", return_value=True) as mock_async_setup, + patch( + f"{MODULE}.async_setup_entry", return_value=True + ) as mock_async_setup_entry, + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ) as mock_is_onboarded, + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": source}, data=data ) diff --git a/tests/components/yeelight/test_init.py b/tests/components/yeelight/test_init.py index f9ccc7edb8b..af442d1c8d0 100644 --- a/tests/components/yeelight/test_init.py +++ b/tests/components/yeelight/test_init.py @@ -60,9 +60,10 @@ async def test_ip_changes_fallback_discovery(hass: HomeAssistant) -> None: mocked_fail_bulb = _mocked_bulb(cannot_connect=True) mocked_fail_bulb.bulb_type = BulbType.WhiteTempMood - with patch( - f"{MODULE}.AsyncBulb", return_value=mocked_fail_bulb - ), _patch_discovery(): + with ( + patch(f"{MODULE}.AsyncBulb", return_value=mocked_fail_bulb), + _patch_discovery(), + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -162,11 +163,13 @@ async def test_setup_discovery_with_manually_configured_network_adapter( config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + with ( + _patch_discovery(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -218,11 +221,13 @@ async def test_setup_discovery_with_manually_configured_network_adapter_one_fail config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG_ONE_FAILING, + with ( + _patch_discovery(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG_ONE_FAILING, + ), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -339,17 +344,23 @@ async def test_bulb_off_while_adding_in_ha(hass: HomeAssistant) -> None: mocked_bulb = _mocked_bulb(cannot_connect=True) mocked_bulb.bulb_type = BulbType.WhiteTempMood - with patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(): + with ( + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.SETUP_RETRY - with patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(): + with ( + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + ): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2)) await hass.async_block_till_done() @@ -422,8 +433,9 @@ async def test_unload_before_discovery( mocked_bulb = _mocked_bulb(cannot_connect=True) - with _patch_discovery(no_device=True), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(no_device=True), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -442,10 +454,11 @@ async def test_async_listen_error_has_host_with_id(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True) + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True)), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -457,10 +470,11 @@ async def test_async_listen_error_has_host_without_id(hass: HomeAssistant) -> No config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}) config_entry.add_to_hass(hass) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True) + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True)), ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -477,8 +491,11 @@ async def test_async_setup_with_missing_id(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True) + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True)), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -487,8 +504,11 @@ async def test_async_setup_with_missing_id(hass: HomeAssistant) -> None: async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2)) await hass.async_block_till_done() - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb() + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), ): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=4)) await hass.async_block_till_done() @@ -504,8 +524,11 @@ async def test_async_setup_with_missing_unique_id(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True) + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb(cannot_connect=True)), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -514,8 +537,11 @@ async def test_async_setup_with_missing_unique_id(hass: HomeAssistant) -> None: async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2)) await hass.async_block_till_done() - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb() + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), ): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=4)) await hass.async_block_till_done() @@ -533,8 +559,11 @@ async def test_connection_dropped_resyncs_properties(hass: HomeAssistant) -> Non config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -566,8 +595,11 @@ async def test_oserror_on_first_update_results_in_unavailable( mocked_bulb = _mocked_bulb() mocked_bulb.async_get_properties = AsyncMock(side_effect=OSError) - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -593,8 +625,11 @@ async def test_non_oserror_exception_on_first_update( mocked_bulb = _mocked_bulb() mocked_bulb.async_get_properties = AsyncMock(side_effect=exception) - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -612,10 +647,11 @@ async def test_async_setup_with_discovery_not_working(hass: HomeAssistant) -> No ) config_entry.add_to_hass(hass) - with _patch_discovery( - no_device=True - ), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb() + with ( + _patch_discovery(no_device=True), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -636,8 +672,11 @@ async def test_async_setup_retries_with_wrong_device( ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_discovery_timeout(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=_mocked_bulb() + with ( + _patch_discovery(), + _patch_discovery_timeout(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=_mocked_bulb()), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 4fed4f602e4..052b6d3223a 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -153,8 +153,10 @@ async def test_services(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) - config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -542,8 +544,10 @@ async def test_update_errors( config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -590,8 +594,10 @@ async def test_state_already_set_avoid_ratelimit(hass: HomeAssistant) -> None: domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False} ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1412,8 +1418,10 @@ async def test_effects(hass: HomeAssistant) -> None: config_entry.add_to_hass(hass) mocked_bulb = _mocked_bulb() - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1574,8 +1582,9 @@ async def test_ambilight_with_nightlight_disabled(hass: HomeAssistant) -> None: options={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False}, ) config_entry.add_to_hass(hass) - with _patch_discovery(capabilities=capabilities), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(capabilities=capabilities), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1601,8 +1610,10 @@ async def test_state_fails_to_update_triggers_update(hass: HomeAssistant) -> Non domain=DOMAIN, data={**CONFIG_ENTRY_DATA, CONF_NIGHTLIGHT_SWITCH: False} ) config_entry.add_to_hass(hass) - with _patch_discovery(), _patch_discovery_interval(), patch( - f"{MODULE}.AsyncBulb", return_value=mocked_bulb + with ( + _patch_discovery(), + _patch_discovery_interval(), + patch(f"{MODULE}.AsyncBulb", return_value=mocked_bulb), ): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/yolink/test_config_flow.py b/tests/components/yolink/test_config_flow.py index 60b190025c6..f7abda0bc4b 100644 --- a/tests/components/yolink/test_config_flow.py +++ b/tests/components/yolink/test_config_flow.py @@ -87,9 +87,12 @@ async def test_full_flow( }, ) - with patch("homeassistant.components.yolink.api.ConfigEntryAuth"), patch( - "homeassistant.components.yolink.async_setup_entry", return_value=True - ) as mock_setup: + with ( + patch("homeassistant.components.yolink.api.ConfigEntryAuth"), + patch( + "homeassistant.components.yolink.async_setup_entry", return_value=True + ) as mock_setup, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["data"]["auth_implementation"] == DOMAIN @@ -202,9 +205,12 @@ async def test_reauthentication( }, ) - with patch("homeassistant.components.yolink.api.ConfigEntryAuth"), patch( - "homeassistant.components.yolink.async_setup_entry", return_value=True - ) as mock_setup: + with ( + patch("homeassistant.components.yolink.api.ConfigEntryAuth"), + patch( + "homeassistant.components.yolink.async_setup_entry", return_value=True + ) as mock_setup, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) token_data = old_entry.data["token"] assert token_data["access_token"] == "mock-access-token" diff --git a/tests/components/youtube/test_config_flow.py b/tests/components/youtube/test_config_flow.py index c735add840b..c8857626384 100644 --- a/tests/components/youtube/test_config_flow.py +++ b/tests/components/youtube/test_config_flow.py @@ -55,11 +55,14 @@ async def test_full_flow( assert resp.status == 200 assert resp.headers["content-type"] == "text/html; charset=utf-8" - with patch( - "homeassistant.components.youtube.async_setup_entry", return_value=True - ) as mock_setup, patch( - "homeassistant.components.youtube.config_flow.YouTube", - return_value=MockYouTube(), + with ( + patch( + "homeassistant.components.youtube.async_setup_entry", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.youtube.config_flow.YouTube", + return_value=MockYouTube(), + ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == FlowResultType.FORM @@ -112,10 +115,11 @@ async def test_flow_abort_without_channel( assert resp.headers["content-type"] == "text/html; charset=utf-8" service = MockYouTube(channel_fixture="youtube/get_no_channel.json") - with patch( - "homeassistant.components.youtube.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.youtube.config_flow.YouTube", return_value=service + with ( + patch("homeassistant.components.youtube.async_setup_entry", return_value=True), + patch( + "homeassistant.components.youtube.config_flow.YouTube", return_value=service + ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == FlowResultType.ABORT @@ -152,10 +156,11 @@ async def test_flow_abort_without_subscriptions( assert resp.headers["content-type"] == "text/html; charset=utf-8" service = MockYouTube(subscriptions_fixture="youtube/get_no_subscriptions.json") - with patch( - "homeassistant.components.youtube.async_setup_entry", return_value=True - ), patch( - "homeassistant.components.youtube.config_flow.YouTube", return_value=service + with ( + patch("homeassistant.components.youtube.async_setup_entry", return_value=True), + patch( + "homeassistant.components.youtube.config_flow.YouTube", return_value=service + ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) assert result["type"] == FlowResultType.ABORT @@ -282,11 +287,14 @@ async def test_reauth( ) youtube = MockYouTube(channel_fixture=f"youtube/{fixture}.json") - with patch( - "homeassistant.components.youtube.async_setup_entry", return_value=True - ) as mock_setup, patch( - "homeassistant.components.youtube.config_flow.YouTube", - return_value=youtube, + with ( + patch( + "homeassistant.components.youtube.async_setup_entry", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.youtube.config_flow.YouTube", + return_value=youtube, + ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"]) diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 0f70d862c4c..6a21212ed6e 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -160,17 +160,20 @@ async def test_setup(hass: HomeAssistant, mock_async_zeroconf: None) -> None: ], "_Volumio._tcp.local.": [{"domain": "volumio"}], } - with patch.dict( - zc_gen.ZEROCONF, - mock_zc, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch.dict( + zc_gen.ZEROCONF, + mock_zc, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -195,32 +198,36 @@ async def test_setup_with_overly_long_url_and_name( hass: HomeAssistant, mock_async_zeroconf: None, caplog: pytest.LogCaptureFixture ) -> None: """Test we still setup with long urls and names.""" - with patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.get_url", - return_value=( - "https://this.url.is.way.too.long/very/deep/path/that/will/make/us/go/over" - "/the/maximum/string/length/and/would/cause/zeroconf/to/fail/to/startup" - "/because/the/key/and/value/can/only/be/255/bytes/and/this/string/is/a" - "/bit/longer/than/the/maximum/length/that/we/allow/for/a/value" + with ( + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.get_url", + return_value=( + "https://this.url.is.way.too.long/very/deep/path/that/will/make/us/go/over" + "/the/maximum/string/length/and/would/cause/zeroconf/to/fail/to/startup" + "/because/the/key/and/value/can/only/be/255/bytes/and/this/string/is/a" + "/bit/longer/than/the/maximum/length/that/we/allow/for/a/value" + ), ), - ), patch.object( - hass.config, - "location_name", - ( - "\u00dcBER \u00dcber German Umlaut long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string long string long" - " string long string long string long string long string" + patch.object( + hass.config, + "location_name", + ( + "\u00dcBER \u00dcber German Umlaut long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string long string long" + " string long string long string long string long string" + ), + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo.async_request", ), - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo.async_request", ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -234,11 +241,13 @@ async def test_setup_with_defaults( hass: HomeAssistant, mock_zeroconf: None, mock_async_zeroconf: None ) -> None: """Test default interface config.""" - with patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -263,25 +272,28 @@ async def test_zeroconf_match_macaddress( ServiceStateChange.Added, ) - with patch.dict( - zc_gen.ZEROCONF, - { - "_http._tcp.local.": [ - { - "domain": "shelly", - "name": "shelly*", - "properties": {"macaddress": "ffaadd*"}, - } - ] - }, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), + with ( + patch.dict( + zc_gen.ZEROCONF, + { + "_http._tcp.local.": [ + { + "domain": "shelly", + "name": "shelly*", + "properties": {"macaddress": "ffaadd*"}, + } + ] + }, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -307,21 +319,24 @@ async def test_zeroconf_match_manufacturer( ServiceStateChange.Added, ) - with patch.dict( - zc_gen.ZEROCONF, - { - "_airplay._tcp.local.": [ - {"domain": "samsungtv", "properties": {"manufacturer": "samsung*"}} - ] - }, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock_manufacturer("Samsung Electronics"), + with ( + patch.dict( + zc_gen.ZEROCONF, + { + "_airplay._tcp.local.": [ + {"domain": "samsungtv", "properties": {"manufacturer": "samsung*"}} + ] + }, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock_manufacturer("Samsung Electronics"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -346,21 +361,24 @@ async def test_zeroconf_match_model( ServiceStateChange.Added, ) - with patch.dict( - zc_gen.ZEROCONF, - { - "_airplay._tcp.local.": [ - {"domain": "appletv", "properties": {"model": "appletv*"}} - ] - }, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock_model("appletv"), + with ( + patch.dict( + zc_gen.ZEROCONF, + { + "_airplay._tcp.local.": [ + {"domain": "appletv", "properties": {"model": "appletv*"}} + ] + }, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock_model("appletv"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -385,21 +403,24 @@ async def test_zeroconf_match_manufacturer_not_present( ServiceStateChange.Added, ) - with patch.dict( - zc_gen.ZEROCONF, - { - "_airplay._tcp.local.": [ - {"domain": "samsungtv", "properties": {"manufacturer": "samsung*"}} - ] - }, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock("aabbccddeeff"), + with ( + patch.dict( + zc_gen.ZEROCONF, + { + "_airplay._tcp.local.": [ + {"domain": "samsungtv", "properties": {"manufacturer": "samsung*"}} + ] + }, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock("aabbccddeeff"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -423,17 +444,20 @@ async def test_zeroconf_no_match( ServiceStateChange.Added, ) - with patch.dict( - zc_gen.ZEROCONF, - {"_http._tcp.local.": [{"domain": "shelly", "name": "shelly*"}]}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_http._tcp.local.": [{"domain": "shelly", "name": "shelly*"}]}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -457,21 +481,24 @@ async def test_zeroconf_no_match_manufacturer( ServiceStateChange.Added, ) - with patch.dict( - zc_gen.ZEROCONF, - { - "_airplay._tcp.local.": [ - {"domain": "samsungtv", "properties": {"manufacturer": "samsung*"}} - ] - }, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock_manufacturer("Not Samsung Electronics"), + with ( + patch.dict( + zc_gen.ZEROCONF, + { + "_airplay._tcp.local.": [ + {"domain": "samsungtv", "properties": {"manufacturer": "samsung*"}} + ] + }, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock_manufacturer("Not Samsung Electronics"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -485,25 +512,29 @@ async def test_homekit_match_partial_space( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test configured options for a device are loaded via config entry.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"LIFX": {"domain": "lifx", "always_discover": True}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._tcp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"LIFX": {"domain": "lifx", "always_discover": True}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("LIFX bulb", HOMEKIT_STATUS_UNPAIRED), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("LIFX bulb", HOMEKIT_STATUS_UNPAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -523,25 +554,29 @@ async def test_device_with_invalid_name( hass: HomeAssistant, mock_async_zeroconf: None, caplog: pytest.LogCaptureFixture ) -> None: """Test we ignore devices with an invalid name.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"LIFX": {"domain": "lifx", "always_discover": True}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._tcp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"LIFX": {"domain": "lifx", "always_discover": True}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=BadTypeInNameException, ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=BadTypeInNameException, ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -556,25 +591,31 @@ async def test_homekit_match_partial_dash( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test configured options for a device are loaded via config entry.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"Smart Bridge": {"domain": "lutron_caseta", "always_discover": False}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._udp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"Smart Bridge": {"domain": "lutron_caseta", "always_discover": False}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock( + "Smart Bridge-001", HOMEKIT_STATUS_UNPAIRED + ), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("Smart Bridge-001", HOMEKIT_STATUS_UNPAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -589,25 +630,29 @@ async def test_homekit_match_partial_fnmatch( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test matching homekit devices with fnmatch.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"YLDP*": {"domain": "yeelight", "always_discover": False}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._tcp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"YLDP*": {"domain": "yeelight", "always_discover": False}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("YLDP13YL", HOMEKIT_STATUS_UNPAIRED), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("YLDP13YL", HOMEKIT_STATUS_UNPAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -622,25 +667,29 @@ async def test_homekit_match_full( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test configured options for a device are loaded via config entry.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"BSB002": {"domain": "hue", "always_discover": False}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._udp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"BSB002": {"domain": "hue", "always_discover": False}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -655,28 +704,32 @@ async def test_homekit_already_paired( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test that an already paired device is sent to homekit_controller.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - { - "AC02": {"domain": "tado", "always_discover": True}, - "tado": {"domain": "tado", "always_discover": True}, - }, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._tcp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + { + "AC02": {"domain": "tado", "always_discover": True}, + "tado": {"domain": "tado", "always_discover": True}, + }, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("tado", HOMEKIT_STATUS_PAIRED), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("tado", HOMEKIT_STATUS_PAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -692,25 +745,29 @@ async def test_homekit_invalid_paring_status( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test that missing paring data is not sent to homekit_controller.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"Smart Bridge": {"domain": "lutron_caseta", "always_discover": False}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._tcp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"Smart Bridge": {"domain": "lutron_caseta", "always_discover": False}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._tcp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("Smart Bridge", b"invalid"), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("Smart Bridge", b"invalid"), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -725,18 +782,21 @@ async def test_homekit_not_paired( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test that an not paired device is sent to homekit_controller.""" - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock( - "this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._tcp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock( + "this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED + ), ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) @@ -758,25 +818,29 @@ async def test_homekit_controller_still_discovered_unpaired_for_cloud( Since we prefer local control, if the integration that is being discovered is cloud AND the homekit device is unpaired we still want to discovery it """ - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"Rachio": {"domain": "rachio", "always_discover": True}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._udp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"Rachio": {"domain": "rachio", "always_discover": True}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("Rachio-xyz", HOMEKIT_STATUS_UNPAIRED), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("Rachio-xyz", HOMEKIT_STATUS_UNPAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -798,25 +862,29 @@ async def test_homekit_controller_still_discovered_unpaired_for_polling( Since we prefer local push, if the integration that is being discovered is polling AND the homekit device is unpaired we still want to discovery it """ - with patch.dict( - zc_gen.ZEROCONF, - {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, - clear=True, - ), patch.dict( - zc_gen.HOMEKIT, - {"iSmartGate": {"domain": "gogogate2", "always_discover": True}}, - clear=True, - ), patch.object( - hass.config_entries.flow, "async_init" - ) as mock_config_flow, patch.object( - zeroconf, - "AsyncServiceBrowser", - side_effect=lambda *args, **kwargs: service_update_mock( - *args, **kwargs, limit_service="_hap._udp.local." + with ( + patch.dict( + zc_gen.ZEROCONF, + {"_hap._udp.local.": [{"domain": "homekit_controller"}]}, + clear=True, + ), + patch.dict( + zc_gen.HOMEKIT, + {"iSmartGate": {"domain": "gogogate2", "always_discover": True}}, + clear=True, + ), + patch.object(hass.config_entries.flow, "async_init") as mock_config_flow, + patch.object( + zeroconf, + "AsyncServiceBrowser", + side_effect=lambda *args, **kwargs: service_update_mock( + *args, **kwargs, limit_service="_hap._udp.local." + ), + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_homekit_info_mock("iSmartGate", HOMEKIT_STATUS_UNPAIRED), ), - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_homekit_info_mock("iSmartGate", HOMEKIT_STATUS_UNPAIRED), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -964,12 +1032,13 @@ async def test_removed_ignored(hass: HomeAssistant, mock_async_zeroconf: None) - ServiceStateChange.Removed, ) - with patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, - ) as mock_service_info: + with ( + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ) as mock_service_info, + ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() @@ -995,16 +1064,18 @@ async def test_async_detect_interfaces_setting_non_loopback_route( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test without default interface and the route returns a non-loopback address.""" - with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, patch.object( - hass.config_entries.flow, "async_init" - ), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTER_WITH_DEFAULT_ENABLED, - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTER_WITH_DEFAULT_ENABLED, + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -1079,16 +1150,19 @@ async def test_async_detect_interfaces_setting_empty_route_linux( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test without default interface config and the route returns nothing on linux.""" - with patch("homeassistant.components.zeroconf.sys.platform", "linux"), patch( - "homeassistant.components.zeroconf.HaZeroconf" - ) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG, - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch("homeassistant.components.zeroconf.sys.platform", "linux"), + patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -1109,16 +1183,19 @@ async def test_async_detect_interfaces_setting_empty_route_freebsd( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test without default interface and the route returns nothing on freebsd.""" - with patch("homeassistant.components.zeroconf.sys.platform", "freebsd"), patch( - "homeassistant.components.zeroconf.HaZeroconf" - ) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTERS_WITH_MANUAL_CONFIG, - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch("homeassistant.components.zeroconf.sys.platform", "freebsd"), + patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTERS_WITH_MANUAL_CONFIG, + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -1156,16 +1233,19 @@ async def test_async_detect_interfaces_explicitly_set_ipv6_linux( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test interfaces are explicitly set when IPv6 is present on linux.""" - with patch("homeassistant.components.zeroconf.sys.platform", "linux"), patch( - "homeassistant.components.zeroconf.HaZeroconf" - ) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6, - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch("homeassistant.components.zeroconf.sys.platform", "linux"), + patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6, + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -1181,16 +1261,19 @@ async def test_async_detect_interfaces_explicitly_set_ipv6_freebsd( hass: HomeAssistant, mock_async_zeroconf: None ) -> None: """Test interfaces are explicitly set when IPv6 is present on freebsd.""" - with patch("homeassistant.components.zeroconf.sys.platform", "freebsd"), patch( - "homeassistant.components.zeroconf.HaZeroconf" - ) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch( - "homeassistant.components.zeroconf.network.async_get_adapters", - return_value=_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6, - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_service_info_mock, + with ( + patch("homeassistant.components.zeroconf.sys.platform", "freebsd"), + patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch( + "homeassistant.components.zeroconf.network.async_get_adapters", + return_value=_ADAPTER_WITH_DEFAULT_ENABLED_AND_IPV6, + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_service_info_mock, + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) @@ -1219,14 +1302,17 @@ async def test_setup_with_disallowed_characters_in_local_name( hass: HomeAssistant, mock_async_zeroconf: None, caplog: pytest.LogCaptureFixture ) -> None: """Test we still setup with disallowed characters in the location name.""" - with patch.object(hass.config_entries.flow, "async_init"), patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock - ), patch.object( - hass.config, - "location_name", - "My.House", - ), patch( - "homeassistant.components.zeroconf.AsyncServiceInfo.async_request", + with ( + patch.object(hass.config_entries.flow, "async_init"), + patch.object(zeroconf, "AsyncServiceBrowser", side_effect=service_update_mock), + patch.object( + hass.config, + "location_name", + "My.House", + ), + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo.async_request", + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -1260,28 +1346,32 @@ async def test_zeroconf_removed(hass: HomeAssistant, mock_async_zeroconf: None) ServiceStateChange.Removed, ) - with patch.dict( - zc_gen.ZEROCONF, - { - "_http._tcp.local.": [ - { - "domain": "shelly", - "name": "shelly*", - } - ] - }, - clear=True, - ), patch.object( - hass.config_entries.flow, - "async_progress_by_init_data_type", - return_value=[{"flow_id": "mock_flow_id"}], - ) as mock_async_progress_by_init_data_type, patch.object( - hass.config_entries.flow, "async_abort" - ) as mock_async_abort, patch.object( - zeroconf, "AsyncServiceBrowser", side_effect=_device_removed_mock - ) as mock_service_browser, patch( - "homeassistant.components.zeroconf.AsyncServiceInfo", - side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), + with ( + patch.dict( + zc_gen.ZEROCONF, + { + "_http._tcp.local.": [ + { + "domain": "shelly", + "name": "shelly*", + } + ] + }, + clear=True, + ), + patch.object( + hass.config_entries.flow, + "async_progress_by_init_data_type", + return_value=[{"flow_id": "mock_flow_id"}], + ) as mock_async_progress_by_init_data_type, + patch.object(hass.config_entries.flow, "async_abort") as mock_async_abort, + patch.object( + zeroconf, "AsyncServiceBrowser", side_effect=_device_removed_mock + ) as mock_service_browser, + patch( + "homeassistant.components.zeroconf.AsyncServiceInfo", + side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), + ), ): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) diff --git a/tests/components/zeroconf/test_usage.py b/tests/components/zeroconf/test_usage.py index 9162e3eb5af..9f5b68c2956 100644 --- a/tests/components/zeroconf/test_usage.py +++ b/tests/components/zeroconf/test_usage.py @@ -52,29 +52,33 @@ async def test_multiple_zeroconf_instances_gives_shared( lineno="23", line="self.light.is_on", ) - with patch( - "homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/dev/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/dev/homeassistant/components/zeroconf/usage.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/dev/mdns/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value=correct_frame.line, + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/dev/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/dev/homeassistant/components/zeroconf/usage.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/dev/mdns/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): assert zeroconf.Zeroconf() == zeroconf_instance diff --git a/tests/components/zerproc/test_config_flow.py b/tests/components/zerproc/test_config_flow.py index 2d933158c54..e512b2a668e 100644 --- a/tests/components/zerproc/test_config_flow.py +++ b/tests/components/zerproc/test_config_flow.py @@ -18,13 +18,16 @@ async def test_flow_success(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.zerproc.config_flow.pyzerproc.discover", - return_value=["Light1", "Light2"], - ), patch( - "homeassistant.components.zerproc.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zerproc.config_flow.pyzerproc.discover", + return_value=["Light1", "Light2"], + ), + patch( + "homeassistant.components.zerproc.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -47,13 +50,16 @@ async def test_flow_no_devices_found(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.zerproc.config_flow.pyzerproc.discover", - return_value=[], - ), patch( - "homeassistant.components.zerproc.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zerproc.config_flow.pyzerproc.discover", + return_value=[], + ), + patch( + "homeassistant.components.zerproc.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, @@ -74,13 +80,16 @@ async def test_flow_exceptions_caught(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["errors"] is None - with patch( - "homeassistant.components.zerproc.config_flow.pyzerproc.discover", - side_effect=pyzerproc.ZerprocException("TEST"), - ), patch( - "homeassistant.components.zerproc.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zerproc.config_flow.pyzerproc.discover", + side_effect=pyzerproc.ZerprocException("TEST"), + ), + patch( + "homeassistant.components.zerproc.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {}, diff --git a/tests/components/zerproc/test_light.py b/tests/components/zerproc/test_light.py index 56a5590e293..c47f960b182 100644 --- a/tests/components/zerproc/test_light.py +++ b/tests/components/zerproc/test_light.py @@ -53,11 +53,13 @@ async def mock_light(hass, mock_entry): mock_state = pyzerproc.LightState(False, (0, 0, 0)) - with patch( - "homeassistant.components.zerproc.light.pyzerproc.discover", - return_value=[light], - ), patch.object(light, "connect"), patch.object( - light, "get_state", return_value=mock_state + with ( + patch( + "homeassistant.components.zerproc.light.pyzerproc.discover", + return_value=[light], + ), + patch.object(light, "connect"), + patch.object(light, "get_state", return_value=mock_state), ): await hass.config_entries.async_setup(mock_entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/zeversolar/test_config_flow.py b/tests/components/zeversolar/test_config_flow.py index 5e841c9b313..0bfa5ad547d 100644 --- a/tests/components/zeversolar/test_config_flow.py +++ b/tests/components/zeversolar/test_config_flow.py @@ -96,9 +96,12 @@ async def test_abort_already_configured(hass: HomeAssistant) -> None: mock_data = MagicMock() mock_data.serial_number = "test_serial" - with patch("zeversolar.ZeverSolarClient.get_data", return_value=mock_data), patch( - "homeassistant.components.zeversolar.async_setup_entry", - ) as mock_setup_entry: + with ( + patch("zeversolar.ZeverSolarClient.get_data", return_value=mock_data), + patch( + "homeassistant.components.zeversolar.async_setup_entry", + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( flow_id=result["flow_id"], user_input={ @@ -116,10 +119,13 @@ async def _set_up_zeversolar(hass: HomeAssistant, flow_id: str) -> None: """Reusable successful setup of Zeversolar sensor.""" mock_data = MagicMock() mock_data.serial_number = "test_serial" - with patch("zeversolar.ZeverSolarClient.get_data", return_value=mock_data), patch( - "homeassistant.components.zeversolar.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch("zeversolar.ZeverSolarClient.get_data", return_value=mock_data), + patch( + "homeassistant.components.zeversolar.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( flow_id=flow_id, user_input={ diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 4c3c4f82a21..b1ac22d544d 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -227,12 +227,15 @@ def mock_zigpy_connect( zigpy_app_controller: ControllerApplication, ) -> Generator[ControllerApplication, None, None]: """Patch the zigpy radio connection with our mock application.""" - with patch( - "bellows.zigbee.application.ControllerApplication.new", - return_value=zigpy_app_controller, - ), patch( - "bellows.zigbee.application.ControllerApplication", - return_value=zigpy_app_controller, + with ( + patch( + "bellows.zigbee.application.ControllerApplication.new", + return_value=zigpy_app_controller, + ), + patch( + "bellows.zigbee.application.ControllerApplication", + return_value=zigpy_app_controller, + ), ): yield zigpy_app_controller diff --git a/tests/components/zha/test_cluster_handlers.py b/tests/components/zha/test_cluster_handlers.py index c081faab010..60c958f20fe 100644 --- a/tests/components/zha/test_cluster_handlers.py +++ b/tests/components/zha/test_cluster_handlers.py @@ -578,9 +578,10 @@ async def test_ep_cluster_handlers_configure(cluster_handler) -> None: claimed = {ch_1.id: ch_1, ch_2.id: ch_2, ch_3.id: ch_3} client_handlers = {ch_4.id: ch_4, ch_5.id: ch_5} - with mock.patch.dict( - endpoint.claimed_cluster_handlers, claimed, clear=True - ), mock.patch.dict(endpoint.client_cluster_handlers, client_handlers, clear=True): + with ( + mock.patch.dict(endpoint.claimed_cluster_handlers, claimed, clear=True), + mock.patch.dict(endpoint.client_cluster_handlers, client_handlers, clear=True), + ): await endpoint.async_configure() await endpoint.async_initialize(mock.sentinel.from_cache) @@ -870,10 +871,13 @@ async def test_invalid_cluster_handler(hass: HomeAssistant, caplog) -> None: TestZigbeeClusterHandler(cluster, zha_endpoint) # And one is also logged at runtime - with patch.dict( - registries.ZIGBEE_CLUSTER_HANDLER_REGISTRY[cluster.cluster_id], - {None: TestZigbeeClusterHandler}, - ), caplog.at_level(logging.WARNING): + with ( + patch.dict( + registries.ZIGBEE_CLUSTER_HANDLER_REGISTRY[cluster.cluster_id], + {None: TestZigbeeClusterHandler}, + ), + caplog.at_level(logging.WARNING), + ): zha_endpoint.add_all_cluster_handlers() assert "missing_attr" in caplog.text diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index d1c4c6cb507..bbfca1b1a13 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -55,12 +55,15 @@ def disable_platform_only(): @pytest.fixture(autouse=True) def mock_multipan_platform(): """Mock the multipan platform.""" - with patch( - "homeassistant.components.zha.silabs_multiprotocol.async_get_channel", - return_value=None, - ), patch( - "homeassistant.components.zha.silabs_multiprotocol.async_using_multipan", - return_value=False, + with ( + patch( + "homeassistant.components.zha.silabs_multiprotocol.async_get_channel", + return_value=None, + ), + patch( + "homeassistant.components.zha.silabs_multiprotocol.async_using_multipan", + return_value=False, + ), ): yield @@ -1825,9 +1828,10 @@ async def test_config_flow_port_yellow_port_name(hass: HomeAssistant) -> None: port.manufacturer = None port.description = None - with patch( - "homeassistant.components.zha.config_flow.yellow_hardware.async_info" - ), patch("serial.tools.list_ports.comports", MagicMock(return_value=[port])): + with ( + patch("homeassistant.components.zha.config_flow.yellow_hardware.async_info"), + patch("serial.tools.list_ports.comports", MagicMock(return_value=[port])), + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={CONF_SOURCE: SOURCE_USER}, @@ -1842,10 +1846,11 @@ async def test_config_flow_port_yellow_port_name(hass: HomeAssistant) -> None: async def test_config_flow_port_multiprotocol_port_name(hass: HomeAssistant) -> None: """Test config flow serial port name for multiprotocol add-on.""" - with patch( - "homeassistant.components.hassio.addon_manager.AddonManager.async_get_addon_info" - ) as async_get_addon_info, patch( - "serial.tools.list_ports.comports", MagicMock(return_value=[]) + with ( + patch( + "homeassistant.components.hassio.addon_manager.AddonManager.async_get_addon_info" + ) as async_get_addon_info, + patch("serial.tools.list_ports.comports", MagicMock(return_value=[])), ): async_get_addon_info.return_value.state = AddonState.RUNNING async_get_addon_info.return_value.hostname = "core-silabs-multiprotocol" @@ -1886,11 +1891,14 @@ async def test_probe_wrong_firmware_installed(hass: HomeAssistant) -> None: async def test_discovery_wrong_firmware_installed(hass: HomeAssistant) -> None: """Test auto-probing failing because the wrong firmware is installed.""" - with patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type", - return_value=ProbeResult.WRONG_FIRMWARE_INSTALLED, - ), patch( - "homeassistant.components.onboarding.async_is_onboarded", return_value=False + with ( + patch( + "homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type", + return_value=ProbeResult.WRONG_FIRMWARE_INSTALLED, + ), + patch( + "homeassistant.components.onboarding.async_is_onboarded", return_value=False + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index cdf8e137690..debf233de36 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -252,10 +252,13 @@ def test_discover_by_device_type_override() -> None: get_entity_mock = mock.MagicMock( return_value=(mock.sentinel.entity_cls, mock.sentinel.claimed) ) - with mock.patch( - "homeassistant.components.zha.core.registries.ZHA_ENTITIES.get_entity", - get_entity_mock, - ), mock.patch.dict(disc.PROBE._device_configs, overrides, clear=True): + with ( + mock.patch( + "homeassistant.components.zha.core.registries.ZHA_ENTITIES.get_entity", + get_entity_mock, + ), + mock.patch.dict(disc.PROBE._device_configs, overrides, clear=True), + ): disc.PROBE.discover_by_device_type(endpoint) assert get_entity_mock.call_count == 1 assert endpoint.claim_cluster_handlers.call_count == 1 diff --git a/tests/components/zha/test_radio_manager.py b/tests/components/zha/test_radio_manager.py index 5671c9cd465..dbea454ecb0 100644 --- a/tests/components/zha/test_radio_manager.py +++ b/tests/components/zha/test_radio_manager.py @@ -436,12 +436,15 @@ def zha_radio_manager(hass: HomeAssistant) -> ZhaRadioManager: async def test_detect_radio_type_success(radio_manager: ZhaRadioManager) -> None: """Test radio type detection, success.""" - with patch( - "bellows.zigbee.application.ControllerApplication.probe", return_value=False - ), patch( - # Intentionally probe only the second radio type - "zigpy_znp.zigbee.application.ControllerApplication.probe", - return_value=True, + with ( + patch( + "bellows.zigbee.application.ControllerApplication.probe", return_value=False + ), + patch( + # Intentionally probe only the second radio type + "zigpy_znp.zigbee.application.ControllerApplication.probe", + return_value=True, + ), ): assert ( await radio_manager.detect_radio_type() == ProbeResult.RADIO_TYPE_DETECTED @@ -453,11 +456,12 @@ async def test_detect_radio_type_failure_wrong_firmware( radio_manager: ZhaRadioManager, ) -> None: """Test radio type detection, wrong firmware.""" - with patch( - "homeassistant.components.zha.radio_manager.AUTOPROBE_RADIOS", () - ), patch( - "homeassistant.components.zha.radio_manager.repairs.wrong_silabs_firmware.warn_on_wrong_silabs_firmware", - return_value=True, + with ( + patch("homeassistant.components.zha.radio_manager.AUTOPROBE_RADIOS", ()), + patch( + "homeassistant.components.zha.radio_manager.repairs.wrong_silabs_firmware.warn_on_wrong_silabs_firmware", + return_value=True, + ), ): assert ( await radio_manager.detect_radio_type() @@ -470,11 +474,12 @@ async def test_detect_radio_type_failure_no_detect( radio_manager: ZhaRadioManager, ) -> None: """Test radio type detection, no firmware detected.""" - with patch( - "homeassistant.components.zha.radio_manager.AUTOPROBE_RADIOS", () - ), patch( - "homeassistant.components.zha.radio_manager.repairs.wrong_silabs_firmware.warn_on_wrong_silabs_firmware", - return_value=False, + with ( + patch("homeassistant.components.zha.radio_manager.AUTOPROBE_RADIOS", ()), + patch( + "homeassistant.components.zha.radio_manager.repairs.wrong_silabs_firmware.warn_on_wrong_silabs_firmware", + return_value=False, + ), ): assert await radio_manager.detect_radio_type() == ProbeResult.PROBING_FAILED assert radio_manager.radio_type is None diff --git a/tests/components/zha/test_repairs.py b/tests/components/zha/test_repairs.py index 5d5dbe8d147..c164abf9b2d 100644 --- a/tests/components/zha/test_repairs.py +++ b/tests/components/zha/test_repairs.py @@ -86,12 +86,15 @@ def test_detect_radio_hardware(hass: HomeAssistant) -> None: def test_detect_radio_hardware_failure(hass: HomeAssistant) -> None: """Test radio hardware detection failure.""" - with patch( - "homeassistant.components.homeassistant_yellow.hardware.async_info", - side_effect=HomeAssistantError(), - ), patch( - "homeassistant.components.homeassistant_sky_connect.hardware.async_info", - side_effect=HomeAssistantError(), + with ( + patch( + "homeassistant.components.homeassistant_yellow.hardware.async_info", + side_effect=HomeAssistantError(), + ), + patch( + "homeassistant.components.homeassistant_sky_connect.hardware.async_info", + side_effect=HomeAssistantError(), + ), ): assert _detect_radio_hardware(hass, SKYCONNECT_DEVICE) == HardwareType.OTHER @@ -116,16 +119,20 @@ async def test_multipan_firmware_repair( config_entry.add_to_hass(hass) # ZHA fails to set up - with patch( - "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", - side_effect=set_flasher_app_type(ApplicationType.CPC), - autospec=True, - ), patch( - "homeassistant.components.zha.core.gateway.ZHAGateway.async_initialize", - side_effect=RuntimeError(), - ), patch( - "homeassistant.components.zha.repairs.wrong_silabs_firmware._detect_radio_hardware", - return_value=detected_hardware, + with ( + patch( + "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", + side_effect=set_flasher_app_type(ApplicationType.CPC), + autospec=True, + ), + patch( + "homeassistant.components.zha.core.gateway.ZHAGateway.async_initialize", + side_effect=RuntimeError(), + ), + patch( + "homeassistant.components.zha.repairs.wrong_silabs_firmware._detect_radio_hardware", + return_value=detected_hardware, + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -165,13 +172,16 @@ async def test_multipan_firmware_no_repair_on_probe_failure( config_entry.add_to_hass(hass) # ZHA fails to set up - with patch( - "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", - side_effect=set_flasher_app_type(None), - autospec=True, - ), patch( - "homeassistant.components.zha.core.gateway.ZHAGateway.async_initialize", - side_effect=RuntimeError(), + with ( + patch( + "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", + side_effect=set_flasher_app_type(None), + autospec=True, + ), + patch( + "homeassistant.components.zha.core.gateway.ZHAGateway.async_initialize", + side_effect=RuntimeError(), + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -199,13 +209,16 @@ async def test_multipan_firmware_retry_on_probe_ezsp( config_entry.add_to_hass(hass) # ZHA fails to set up - with patch( - "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", - side_effect=set_flasher_app_type(ApplicationType.EZSP), - autospec=True, - ), patch( - "homeassistant.components.zha.core.gateway.ZHAGateway.async_initialize", - side_effect=RuntimeError(), + with ( + patch( + "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", + side_effect=set_flasher_app_type(ApplicationType.EZSP), + autospec=True, + ), + patch( + "homeassistant.components.zha.core.gateway.ZHAGateway.async_initialize", + side_effect=RuntimeError(), + ), ): await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -237,10 +250,13 @@ async def test_no_warn_on_socket(hass: HomeAssistant) -> None: async def test_probe_failure_exception_handling(caplog) -> None: """Test that probe failures are handled gracefully.""" - with patch( - "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", - side_effect=RuntimeError(), - ), caplog.at_level(logging.DEBUG): + with ( + patch( + "homeassistant.components.zha.repairs.wrong_silabs_firmware.Flasher.probe_app_type", + side_effect=RuntimeError(), + ), + caplog.at_level(logging.DEBUG), + ): await probe_silabs_firmware_type("/dev/ttyZigbee") assert "Failed to probe application type" in caplog.text diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index f656ddea71c..a6c4cfbf4ec 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -1096,9 +1096,9 @@ async def test_elec_measurement_sensor_polling( entity_id = ENTITY_ID_PREFIX.format("power") zigpy_dev = elec_measurement_zigpy_dev - zigpy_dev.endpoints[1].electrical_measurement.PLUGGED_ATTR_READS[ - "active_power" - ] = 20 + zigpy_dev.endpoints[1].electrical_measurement.PLUGGED_ATTR_READS["active_power"] = ( + 20 + ) await zha_device_joined_restored(zigpy_dev) @@ -1107,9 +1107,9 @@ async def test_elec_measurement_sensor_polling( assert state.state == "2.0" # update the value for the power reading - zigpy_dev.endpoints[1].electrical_measurement.PLUGGED_ATTR_READS[ - "active_power" - ] = 60 + zigpy_dev.endpoints[1].electrical_measurement.PLUGGED_ATTR_READS["active_power"] = ( + 60 + ) # ensure the state is still 2.0 state = hass.states.get(entity_id) diff --git a/tests/components/zha/test_siren.py b/tests/components/zha/test_siren.py index dae04c45fef..f5486d91c0f 100644 --- a/tests/components/zha/test_siren.py +++ b/tests/components/zha/test_siren.py @@ -86,13 +86,16 @@ async def test_siren(hass: HomeAssistant, siren) -> None: assert hass.states.get(entity_id).state == STATE_OFF # turn on from HA - with patch( - "zigpy.device.Device.request", - return_value=[0x00, zcl_f.Status.SUCCESS], - ), patch( - "zigpy.zcl.Cluster.request", - side_effect=zigpy.zcl.Cluster.request, - autospec=True, + with ( + patch( + "zigpy.device.Device.request", + return_value=[0x00, zcl_f.Status.SUCCESS], + ), + patch( + "zigpy.zcl.Cluster.request", + side_effect=zigpy.zcl.Cluster.request, + autospec=True, + ), ): # turn on via UI await hass.services.async_call( @@ -118,13 +121,16 @@ async def test_siren(hass: HomeAssistant, siren) -> None: assert hass.states.get(entity_id).state == STATE_ON # turn off from HA - with patch( - "zigpy.device.Device.request", - return_value=[0x01, zcl_f.Status.SUCCESS], - ), patch( - "zigpy.zcl.Cluster.request", - side_effect=zigpy.zcl.Cluster.request, - autospec=True, + with ( + patch( + "zigpy.device.Device.request", + return_value=[0x01, zcl_f.Status.SUCCESS], + ), + patch( + "zigpy.zcl.Cluster.request", + side_effect=zigpy.zcl.Cluster.request, + autospec=True, + ), ): # turn off via UI await hass.services.async_call( @@ -150,13 +156,16 @@ async def test_siren(hass: HomeAssistant, siren) -> None: assert hass.states.get(entity_id).state == STATE_OFF # turn on from HA - with patch( - "zigpy.device.Device.request", - return_value=[0x00, zcl_f.Status.SUCCESS], - ), patch( - "zigpy.zcl.Cluster.request", - side_effect=zigpy.zcl.Cluster.request, - autospec=True, + with ( + patch( + "zigpy.device.Device.request", + return_value=[0x00, zcl_f.Status.SUCCESS], + ), + patch( + "zigpy.zcl.Cluster.request", + side_effect=zigpy.zcl.Cluster.request, + autospec=True, + ), ): # turn on via UI await hass.services.async_call( diff --git a/tests/components/zha/test_update.py b/tests/components/zha/test_update.py index 31c28e31646..854c08985ac 100644 --- a/tests/components/zha/test_update.py +++ b/tests/components/zha/test_update.py @@ -516,10 +516,13 @@ async def test_firmware_update_raises( blocking=True, ) - with patch( - "zigpy.device.Device.update_firmware", - AsyncMock(side_effect=DeliveryError("failed to deliver")), - ), pytest.raises(HomeAssistantError): + with ( + patch( + "zigpy.device.Device.update_firmware", + AsyncMock(side_effect=DeliveryError("failed to deliver")), + ), + pytest.raises(HomeAssistantError), + ): await hass.services.async_call( UPDATE_DOMAIN, SERVICE_INSTALL, diff --git a/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py b/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py index 545c56d6f9d..a5c5e4475ce 100644 --- a/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py +++ b/tests/components/zwave_js/scripts/test_convert_device_diagnostics_to_fixture.py @@ -75,9 +75,12 @@ def test_main(capfd: pytest.CaptureFixture[str]) -> None: # Check file dump args.append("--file") - with patch.object(sys, "argv", args), patch( - "homeassistant.components.zwave_js.scripts.convert_device_diagnostics_to_fixture.Path.write_text" - ) as write_text_mock: + with ( + patch.object(sys, "argv", args), + patch( + "homeassistant.components.zwave_js.scripts.convert_device_diagnostics_to_fixture.Path.write_text" + ) as write_text_mock, + ): main() assert len(write_text_mock.call_args_list) == 1 diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index ed84d155e1d..6295dbed8f1 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -3070,13 +3070,17 @@ async def test_firmware_upload_view( """Test the HTTP firmware upload view.""" client = await hass_client() device = get_device(hass, multisensor_6) - with patch( - "homeassistant.components.zwave_js.api.update_firmware", - ) as mock_node_cmd, patch( - "homeassistant.components.zwave_js.api.controller_firmware_update_otw", - ) as mock_controller_cmd, patch.dict( - "homeassistant.components.zwave_js.api.USER_AGENT", - {"HomeAssistant": "0.0.0"}, + with ( + patch( + "homeassistant.components.zwave_js.api.update_firmware", + ) as mock_node_cmd, + patch( + "homeassistant.components.zwave_js.api.controller_firmware_update_otw", + ) as mock_controller_cmd, + patch.dict( + "homeassistant.components.zwave_js.api.USER_AGENT", + {"HomeAssistant": "0.0.0"}, + ), ): data = {"file": firmware_file} data.update(firmware_data) @@ -3107,13 +3111,17 @@ async def test_firmware_upload_view_controller( """Test the HTTP firmware upload view for a controller.""" hass_client = await hass_client() device = get_device(hass, client.driver.controller.nodes[1]) - with patch( - "homeassistant.components.zwave_js.api.update_firmware", - ) as mock_node_cmd, patch( - "homeassistant.components.zwave_js.api.controller_firmware_update_otw", - ) as mock_controller_cmd, patch.dict( - "homeassistant.components.zwave_js.api.USER_AGENT", - {"HomeAssistant": "0.0.0"}, + with ( + patch( + "homeassistant.components.zwave_js.api.update_firmware", + ) as mock_node_cmd, + patch( + "homeassistant.components.zwave_js.api.controller_firmware_update_otw", + ) as mock_controller_cmd, + patch.dict( + "homeassistant.components.zwave_js.api.USER_AGENT", + {"HomeAssistant": "0.0.0"}, + ), ): resp = await hass_client.post( f"/api/zwave_js/firmware/upload/{device.id}", diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index ad066dec992..511fb8d7570 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -114,13 +114,16 @@ def mock_get_server_version(server_version_side_effect, server_version_timeout): min_schema_version=0, max_schema_version=1, ) - with patch( - "homeassistant.components.zwave_js.config_flow.get_server_version", - side_effect=server_version_side_effect, - return_value=version_info, - ) as mock_version, patch( - "homeassistant.components.zwave_js.config_flow.SERVER_VERSION_TIMEOUT", - new=server_version_timeout, + with ( + patch( + "homeassistant.components.zwave_js.config_flow.get_server_version", + side_effect=server_version_side_effect, + return_value=version_info, + ) as mock_version, + patch( + "homeassistant.components.zwave_js.config_flow.SERVER_VERSION_TIMEOUT", + new=server_version_timeout, + ), ): yield mock_version @@ -192,12 +195,15 @@ async def test_manual(hass: HomeAssistant) -> None: ) assert result["type"] == "form" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -348,12 +354,15 @@ async def test_supervisor_discovery( ), ) - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() @@ -440,12 +449,15 @@ async def test_clean_discovery_on_user_create( assert result["type"] == "form" assert result["step_id"] == "manual" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -613,12 +625,15 @@ async def test_usb_discovery( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -703,12 +718,15 @@ async def test_usb_discovery_addon_not_running( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -789,12 +807,15 @@ async def test_discovery_addon_not_running( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -883,12 +904,15 @@ async def test_discovery_addon_not_installed( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -1017,12 +1041,15 @@ async def test_not_addon(hass: HomeAssistant, supervisor) -> None: assert result["type"] == "form" assert result["step_id"] == "manual" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -1069,12 +1096,15 @@ async def test_addon_running( assert result["type"] == "form" assert result["step_id"] == "on_supervisor" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"use_addon": True} ) @@ -1268,12 +1298,15 @@ async def test_addon_installed( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -1645,12 +1678,15 @@ async def test_addon_not_installed( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -2633,12 +2669,15 @@ async def test_import_addon_installed( assert result["type"] == "progress" assert result["step_id"] == "start_addon" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): await hass.async_block_till_done() result = await hass.config_entries.flow.async_configure(result["flow_id"]) await hass.async_block_till_done() @@ -2681,12 +2720,15 @@ async def test_zeroconf(hass: HomeAssistant) -> None: assert result["type"] == "form" assert result["step_id"] == "zeroconf_confirm" - with patch( - "homeassistant.components.zwave_js.async_setup", return_value=True - ) as mock_setup, patch( - "homeassistant.components.zwave_js.async_setup_entry", - return_value=True, - ) as mock_setup_entry: + with ( + patch( + "homeassistant.components.zwave_js.async_setup", return_value=True + ) as mock_setup, + patch( + "homeassistant.components.zwave_js.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) await hass.async_block_till_done() diff --git a/tests/components/zwave_js/test_device_condition.py b/tests/components/zwave_js/test_device_condition.py index 4c3cfd5d8a0..24f756c5042 100644 --- a/tests/components/zwave_js/test_device_condition.py +++ b/tests/components/zwave_js/test_device_condition.py @@ -627,12 +627,15 @@ async def test_failure_scenarios( hass, {"type": "failed.test", "device_id": device.id} ) - with patch( - "homeassistant.components.zwave_js.device_condition.async_get_node_from_device_id", - return_value=None, - ), patch( - "homeassistant.components.zwave_js.device_condition.get_zwave_value_from_config", - return_value=None, + with ( + patch( + "homeassistant.components.zwave_js.device_condition.async_get_node_from_device_id", + return_value=None, + ), + patch( + "homeassistant.components.zwave_js.device_condition.get_zwave_value_from_config", + return_value=None, + ), ): assert ( await device_condition.async_get_condition_capabilities( diff --git a/tests/components/zwave_js/test_device_trigger.py b/tests/components/zwave_js/test_device_trigger.py index 93402219b6c..6818b2d73af 100644 --- a/tests/components/zwave_js/test_device_trigger.py +++ b/tests/components/zwave_js/test_device_trigger.py @@ -1571,12 +1571,15 @@ async def test_failure_scenarios( {}, ) - with patch( - "homeassistant.components.zwave_js.device_trigger.async_get_node_from_device_id", - return_value=None, - ), patch( - "homeassistant.components.zwave_js.helpers.get_zwave_value_from_config", - return_value=None, + with ( + patch( + "homeassistant.components.zwave_js.device_trigger.async_get_node_from_device_id", + return_value=None, + ), + patch( + "homeassistant.components.zwave_js.helpers.get_zwave_value_from_config", + return_value=None, + ), ): assert ( await device_trigger.async_get_trigger_capabilities( diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index ee0e9d61707..822302a9940 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -111,11 +111,14 @@ async def test_noop_statistics(hass: HomeAssistant, client) -> None: entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) entry.add_to_hass(hass) - with patch( - "zwave_js_server.model.driver.Driver.async_enable_statistics" - ) as mock_cmd1, patch( - "zwave_js_server.model.driver.Driver.async_disable_statistics" - ) as mock_cmd2: + with ( + patch( + "zwave_js_server.model.driver.Driver.async_enable_statistics" + ) as mock_cmd1, + patch( + "zwave_js_server.model.driver.Driver.async_disable_statistics" + ) as mock_cmd2, + ): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() assert not mock_cmd1.called diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 633a4dbf7de..5462bcf9946 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -1521,9 +1521,12 @@ async def test_multicast_set_value( diff_network_node = MagicMock() diff_network_node.client.driver.controller.home_id.return_value = "diff_home_id" - with pytest.raises(vol.MultipleInvalid), patch( - "homeassistant.components.zwave_js.helpers.async_get_node_from_device_id", - side_effect=(climate_danfoss_lc_13, diff_network_node), + with ( + pytest.raises(vol.MultipleInvalid), + patch( + "homeassistant.components.zwave_js.helpers.async_get_node_from_device_id", + side_effect=(climate_danfoss_lc_13, diff_network_node), + ), ): await hass.services.async_call( DOMAIN, diff --git a/tests/components/zwave_me/test_config_flow.py b/tests/components/zwave_me/test_config_flow.py index f84b74b2f0e..52d5e8fce6f 100644 --- a/tests/components/zwave_me/test_config_flow.py +++ b/tests/components/zwave_me/test_config_flow.py @@ -29,12 +29,15 @@ MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( async def test_form(hass: HomeAssistant) -> None: """Test we get the form.""" - with patch( - "homeassistant.components.zwave_me.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.zwave_me.helpers.get_uuid", - return_value="test_uuid", + with ( + patch( + "homeassistant.components.zwave_me.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.zwave_me.helpers.get_uuid", + return_value="test_uuid", + ), ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -61,12 +64,15 @@ async def test_form(hass: HomeAssistant) -> None: async def test_zeroconf(hass: HomeAssistant) -> None: """Test starting a flow from zeroconf.""" - with patch( - "homeassistant.components.zwave_me.async_setup_entry", - return_value=True, - ) as mock_setup_entry, patch( - "homeassistant.components.zwave_me.helpers.get_uuid", - return_value="test_uuid", + with ( + patch( + "homeassistant.components.zwave_me.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + patch( + "homeassistant.components.zwave_me.helpers.get_uuid", + return_value="test_uuid", + ), ): result: FlowResult = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/zwave_me/test_remove_stale_devices.py b/tests/components/zwave_me/test_remove_stale_devices.py index 8117237ba15..0a4ae24e0f0 100644 --- a/tests/components/zwave_me/test_remove_stale_devices.py +++ b/tests/components/zwave_me/test_remove_stale_devices.py @@ -51,11 +51,14 @@ async def test_remove_stale_devices( connections={("mac", "12:34:56:AB:CD:EF")}, identifiers={("zwave_me", f"{config_entry.unique_id}-{identifier}")}, ) - with patch( - "homeassistant.components.zwave_me.ZWaveMe.get_connection", - mock_connection, - ), patch( - "homeassistant.components.zwave_me.async_setup_platforms", + with ( + patch( + "homeassistant.components.zwave_me.ZWaveMe.get_connection", + mock_connection, + ), + patch( + "homeassistant.components.zwave_me.async_setup_platforms", + ), ): await hass.config_entries.async_setup(config_entry.entry_id) assert ( diff --git a/tests/conftest.py b/tests/conftest.py index 4eacb8991c0..157e0f2ba59 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -161,9 +161,9 @@ def pytest_runtest_setup() -> None: except ImportError: pass else: - MySQLdb_converters.conversions[ - HAFakeDatetime - ] = MySQLdb_converters.DateTime2literal + MySQLdb_converters.conversions[HAFakeDatetime] = ( + MySQLdb_converters.DateTime2literal + ) def ha_datetime_to_fakedatetime(datetime) -> freezegun.api.FakeDatetime: # type: ignore[name-defined] @@ -622,15 +622,18 @@ def mock_device_tracker_conf() -> Generator[list[Device], None, None]: async def mock_update_config(path: str, dev_id: str, entity: Device) -> None: devices.append(entity) - with patch( - ( - "homeassistant.components.device_tracker.legacy" - ".DeviceTracker.async_update_config" + with ( + patch( + ( + "homeassistant.components.device_tracker.legacy" + ".DeviceTracker.async_update_config" + ), + side_effect=mock_update_config, + ), + patch( + "homeassistant.components.device_tracker.legacy.async_load_config", + side_effect=lambda *args: devices, ), - side_effect=mock_update_config, - ), patch( - "homeassistant.components.device_tracker.legacy.async_load_config", - side_effect=lambda *args: devices, ): yield devices @@ -1124,10 +1127,9 @@ def mock_zeroconf() -> Generator[None, None, None]: """Mock zeroconf.""" from zeroconf import DNSCache # pylint: disable=import-outside-toplevel - with patch( - "homeassistant.components.zeroconf.HaZeroconf", autospec=True - ) as mock_zc, patch( - "homeassistant.components.zeroconf.AsyncServiceBrowser", autospec=True + with ( + patch("homeassistant.components.zeroconf.HaZeroconf", autospec=True) as mock_zc, + patch("homeassistant.components.zeroconf.AsyncServiceBrowser", autospec=True), ): zc = mock_zc.return_value # DNSCache has strong Cython type checks, and MagicMock does not work @@ -1338,38 +1340,47 @@ def hass_recorder( migrate_entity_ids = ( recorder.Recorder._migrate_entity_ids if enable_migrate_entity_ids else None ) - with patch( - "homeassistant.components.recorder.Recorder.async_nightly_tasks", - side_effect=nightly, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder.async_periodic_statistics", - side_effect=stats, - autospec=True, - ), patch( - "homeassistant.components.recorder.migration._find_schema_errors", - side_effect=schema_validate, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_events_context_ids", - side_effect=migrate_events_context_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_states_context_ids", - side_effect=migrate_states_context_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_event_type_ids", - side_effect=migrate_event_type_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_entity_ids", - side_effect=migrate_entity_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", - side_effect=compile_missing, - autospec=True, + with ( + patch( + "homeassistant.components.recorder.Recorder.async_nightly_tasks", + side_effect=nightly, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder.async_periodic_statistics", + side_effect=stats, + autospec=True, + ), + patch( + "homeassistant.components.recorder.migration._find_schema_errors", + side_effect=schema_validate, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_events_context_ids", + side_effect=migrate_events_context_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_states_context_ids", + side_effect=migrate_states_context_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_event_type_ids", + side_effect=migrate_event_type_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_entity_ids", + side_effect=migrate_entity_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", + side_effect=compile_missing, + autospec=True, + ), ): def setup_recorder(config: dict[str, Any] | None = None) -> HomeAssistant: @@ -1462,38 +1473,47 @@ async def async_setup_recorder_instance( migrate_entity_ids = ( recorder.Recorder._migrate_entity_ids if enable_migrate_entity_ids else None ) - with patch( - "homeassistant.components.recorder.Recorder.async_nightly_tasks", - side_effect=nightly, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder.async_periodic_statistics", - side_effect=stats, - autospec=True, - ), patch( - "homeassistant.components.recorder.migration._find_schema_errors", - side_effect=schema_validate, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_events_context_ids", - side_effect=migrate_events_context_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_states_context_ids", - side_effect=migrate_states_context_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_event_type_ids", - side_effect=migrate_event_type_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._migrate_entity_ids", - side_effect=migrate_entity_ids, - autospec=True, - ), patch( - "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", - side_effect=compile_missing, - autospec=True, + with ( + patch( + "homeassistant.components.recorder.Recorder.async_nightly_tasks", + side_effect=nightly, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder.async_periodic_statistics", + side_effect=stats, + autospec=True, + ), + patch( + "homeassistant.components.recorder.migration._find_schema_errors", + side_effect=schema_validate, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_events_context_ids", + side_effect=migrate_events_context_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_states_context_ids", + side_effect=migrate_states_context_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_event_type_ids", + side_effect=migrate_event_type_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._migrate_entity_ids", + side_effect=migrate_entity_ids, + autospec=True, + ), + patch( + "homeassistant.components.recorder.Recorder._schedule_compile_missing_statistics", + side_effect=compile_missing, + autospec=True, + ), ): async def async_setup_recorder( @@ -1540,22 +1560,25 @@ async def mock_enable_bluetooth( @pytest.fixture(scope="session") def mock_bluetooth_adapters() -> Generator[None, None, None]: """Fixture to mock bluetooth adapters.""" - with patch("bluetooth_auto_recovery.recover_adapter"), patch( - "bluetooth_adapters.systems.platform.system", return_value="Linux" - ), patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), patch( - "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", - { - "hci0": { - "address": "00:00:00:00:00:01", - "hw_version": "usb:v1D6Bp0246d053F", - "passive_scan": False, - "sw_version": "homeassistant", - "manufacturer": "ACME", - "product": "Bluetooth Adapter 5.0", - "product_id": "aa01", - "vendor_id": "cc01", + with ( + patch("bluetooth_auto_recovery.recover_adapter"), + patch("bluetooth_adapters.systems.platform.system", return_value="Linux"), + patch("bluetooth_adapters.systems.linux.LinuxAdapters.refresh"), + patch( + "bluetooth_adapters.systems.linux.LinuxAdapters.adapters", + { + "hci0": { + "address": "00:00:00:00:00:01", + "hw_version": "usb:v1D6Bp0246d053F", + "passive_scan": False, + "sw_version": "homeassistant", + "manufacturer": "ACME", + "product": "Bluetooth Adapter 5.0", + "product_id": "aa01", + "vendor_id": "cc01", + }, }, - }, + ), ): yield @@ -1573,10 +1596,13 @@ def mock_bleak_scanner_start() -> Generator[MagicMock, None, None]: # out start and this fixture will expire before the stop method is called # when EVENT_HOMEASSISTANT_STOP is fired. bluetooth_scanner.OriginalBleakScanner.stop = AsyncMock() # type: ignore[assignment] - with patch.object( - bluetooth_scanner.OriginalBleakScanner, - "start", - ) as mock_bleak_scanner_start, patch.object(bluetooth_scanner, "HaScanner"): + with ( + patch.object( + bluetooth_scanner.OriginalBleakScanner, + "start", + ) as mock_bleak_scanner_start, + patch.object(bluetooth_scanner, "HaScanner"), + ): yield mock_bleak_scanner_start @@ -1588,24 +1614,28 @@ def mock_integration_frame() -> Generator[Mock, None, None]: lineno="23", line="self.light.is_on", ) - with patch( - "homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value=correct_frame.line, + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): yield correct_frame diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 73339e9fe3c..69015c80305 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -171,29 +171,32 @@ async def test_warning_close_session_integration( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test log warning message when closing the session from integration context.""" - with patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.close()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): session = client.async_get_clientsession(hass) @@ -212,29 +215,32 @@ async def test_warning_close_session_custom( ) -> None: """Test log warning message when closing the session from custom context.""" mock_integration(hass, MockModule("hue"), built_in=False) - with patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.close()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): session = client.async_get_clientsession(hass) diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index bba9c3f27c4..fd94c453e51 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -121,10 +121,14 @@ async def test_integrationt_requirement_not_found(hass: HomeAssistant) -> None: """Test errors if integration with a requirement not found not found.""" # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "test_custom_component:"} - with patch( - "homeassistant.helpers.check_config.async_get_integration_with_requirements", - side_effect=RequirementsNotFound("test_custom_component", ["any"]), - ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): + with ( + patch( + "homeassistant.helpers.check_config.async_get_integration_with_requirements", + side_effect=RequirementsNotFound("test_custom_component", ["any"]), + ), + patch("os.path.isfile", return_value=True), + patch_yaml_files(files), + ): res = await async_check_ha_config_file(hass) log_ha_config(res) @@ -170,10 +174,14 @@ async def test_integration_import_error(hass: HomeAssistant) -> None: """Test errors if integration with a requirement not found not found.""" # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:"} - with patch( - "homeassistant.loader.Integration.async_get_component", - side_effect=ImportError("blablabla"), - ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): + with ( + patch( + "homeassistant.loader.Integration.async_get_component", + side_effect=ImportError("blablabla"), + ), + patch("os.path.isfile", return_value=True), + patch_yaml_files(files), + ): res = await async_check_ha_config_file(hass) log_ha_config(res) @@ -336,10 +344,14 @@ async def test_config_platform_import_error(hass: HomeAssistant) -> None: """Test errors if config platform fails to import.""" # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: beer"} - with patch( - "homeassistant.loader.Integration.async_get_platform", - side_effect=ImportError("blablabla"), - ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): + with ( + patch( + "homeassistant.loader.Integration.async_get_platform", + side_effect=ImportError("blablabla"), + ), + patch("os.path.isfile", return_value=True), + patch_yaml_files(files), + ): res = await async_check_ha_config_file(hass) log_ha_config(res) @@ -356,10 +368,14 @@ async def test_platform_import_error(hass: HomeAssistant) -> None: """Test errors if platform not found.""" # Make sure they don't exist files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"} - with patch( - "homeassistant.loader.Integration.async_get_platform", - side_effect=[None, ImportError("blablabla")], - ), patch("os.path.isfile", return_value=True), patch_yaml_files(files): + with ( + patch( + "homeassistant.loader.Integration.async_get_platform", + side_effect=[None, ImportError("blablabla")], + ), + patch("os.path.isfile", return_value=True), + patch_yaml_files(files), + ): res = await async_check_ha_config_file(hass) log_ha_config(res) diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 0735c0a1a80..e99cfbb2f58 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -376,18 +376,23 @@ async def test_webhook_create_cloudhook( ) assert result["type"] == data_entry_flow.FlowResultType.FORM - with patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", - return_value={"cloudhook_url": "https://example.com"}, - ) as mock_create, patch( - "hass_nabucasa.Cloud.subscription_expired", - new_callable=PropertyMock(return_value=False), - ), patch( - "hass_nabucasa.Cloud.is_logged_in", - new_callable=PropertyMock(return_value=True), - ), patch( - "hass_nabucasa.iot_base.BaseIoT.connected", - new_callable=PropertyMock(return_value=True), + with ( + patch( + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://example.com"}, + ) as mock_create, + patch( + "hass_nabucasa.Cloud.subscription_expired", + new_callable=PropertyMock(return_value=False), + ), + patch( + "hass_nabucasa.Cloud.is_logged_in", + new_callable=PropertyMock(return_value=True), + ), + patch( + "hass_nabucasa.iot_base.BaseIoT.connected", + new_callable=PropertyMock(return_value=True), + ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) @@ -432,18 +437,23 @@ async def test_webhook_create_cloudhook_aborts_not_connected( ) assert result["type"] == data_entry_flow.FlowResultType.FORM - with patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", - return_value={"cloudhook_url": "https://example.com"}, - ), patch( - "hass_nabucasa.Cloud.subscription_expired", - new_callable=PropertyMock(return_value=False), - ), patch( - "hass_nabucasa.Cloud.is_logged_in", - new_callable=PropertyMock(return_value=True), - ), patch( - "hass_nabucasa.iot_base.BaseIoT.connected", - new_callable=PropertyMock(return_value=False), + with ( + patch( + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://example.com"}, + ), + patch( + "hass_nabucasa.Cloud.subscription_expired", + new_callable=PropertyMock(return_value=False), + ), + patch( + "hass_nabucasa.Cloud.is_logged_in", + new_callable=PropertyMock(return_value=True), + ), + patch( + "hass_nabucasa.iot_base.BaseIoT.connected", + new_callable=PropertyMock(return_value=False), + ), ): result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index 6ad8af5d698..a9e69f542f3 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -104,9 +104,10 @@ def test_inherit_enforces_domain_set() -> None: """Return logger.""" return logging.getLogger(__name__) - with patch.dict( - config_entries.HANDLERS, {TEST_DOMAIN: TestFlowHandler} - ), pytest.raises(TypeError): + with ( + patch.dict(config_entries.HANDLERS, {TEST_DOMAIN: TestFlowHandler}), + pytest.raises(TypeError), + ): TestFlowHandler() diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index c10e82627eb..133e5e80442 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -97,8 +97,9 @@ def test_isfile() -> None: # patching methods that allow us to fake a file existing # with write access - with patch("os.path.isfile", Mock(return_value=True)), patch( - "os.access", Mock(return_value=True) + with ( + patch("os.path.isfile", Mock(return_value=True)), + patch("os.access", Mock(return_value=True)), ): schema("test.txt") diff --git a/tests/helpers/test_deprecation.py b/tests/helpers/test_deprecation.py index 4cca4174e9c..b53a6d5ec1d 100644 --- a/tests/helpers/test_deprecation.py +++ b/tests/helpers/test_deprecation.py @@ -178,29 +178,32 @@ def test_deprecated_function_called_from_built_in_integration( def mock_deprecated_function(): pass - with patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.close()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): mock_deprecated_function() @@ -235,29 +238,32 @@ def test_deprecated_function_called_from_custom_integration( def mock_deprecated_function(): pass - with patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.close()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): mock_deprecated_function() @@ -337,29 +343,33 @@ def test_check_if_deprecated_constant( filename = f"/home/paulus/{module_name.replace('.', '/')}.py" # mock sys.modules for homeassistant/helpers/frame.py#get_integration_frame - with patch.dict(sys.modules, {module_name: Mock(__file__=filename)}), patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.close()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename=filename, - lineno="23", - line="await session.close()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch.dict(sys.modules, {module_name: Mock(__file__=filename)}), + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.close()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename=filename, + lineno="23", + line="await session.close()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): value = check_if_deprecated_constant("TEST_CONSTANT", module_globals) diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 4438ba93861..bed3dea4dc1 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -2453,13 +2453,17 @@ async def test_device_name_translation_placeholders_errors( config_entry_1 = MockConfigEntry() config_entry_1.add_to_hass(hass) - with patch( - "homeassistant.helpers.device_registry.translation.async_get_cached_translations", - side_effect=async_get_cached_translations, - ), patch( - "homeassistant.helpers.device_registry.get_release_channel", - return_value=release_channel, - ), expectation: + with ( + patch( + "homeassistant.helpers.device_registry.translation.async_get_cached_translations", + side_effect=async_get_cached_translations, + ), + patch( + "homeassistant.helpers.device_registry.get_release_channel", + return_value=release_channel, + ), + expectation, + ): device_registry.async_get_or_create( config_entry_id=config_entry_1.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index dab986a7284..dac03f0be67 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -741,12 +741,13 @@ async def test_disabled_in_entity_registry(hass: HomeAssistant) -> None: async def test_capability_attrs(hass: HomeAssistant) -> None: """Test we still include capabilities even when unavailable.""" - with patch.object( - entity.Entity, "available", PropertyMock(return_value=False) - ), patch.object( - entity.Entity, - "capability_attributes", - PropertyMock(return_value={"always": "there"}), + with ( + patch.object(entity.Entity, "available", PropertyMock(return_value=False)), + patch.object( + entity.Entity, + "capability_attributes", + PropertyMock(return_value={"always": "there"}), + ), ): ent = entity.Entity() ent.hass = hass @@ -1328,11 +1329,15 @@ async def test_entity_name_translation_placeholder_errors( caplog.clear() - with patch( - "homeassistant.helpers.entity_platform.translation.async_get_translations", - side_effect=async_get_translations, - ), patch( - "homeassistant.helpers.entity.get_release_channel", return_value=release_channel + with ( + patch( + "homeassistant.helpers.entity_platform.translation.async_get_translations", + side_effect=async_get_translations, + ), + patch( + "homeassistant.helpers.entity.get_release_channel", + return_value=release_channel, + ), ): await entity_platform.async_setup_entry(config_entry) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index efea66c8d77..17b5d634018 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1761,8 +1761,9 @@ async def test_setup_entry_with_entities_that_block_forever( hass, platform_name=config_entry.domain, platform=platform ) - with patch.object(entity_platform, "SLOW_ADD_ENTITY_MAX_WAIT", 0.01), patch.object( - entity_platform, "SLOW_ADD_MIN_TIMEOUT", 0.01 + with ( + patch.object(entity_platform, "SLOW_ADD_ENTITY_MAX_WAIT", 0.01), + patch.object(entity_platform, "SLOW_ADD_MIN_TIMEOUT", 0.01), ): assert await platform.async_setup_entry(config_entry) await hass.async_block_till_done() diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 0974d3bc9de..a9177fc8c83 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -704,9 +704,10 @@ async def test_update_entity_unique_id_conflict( entry2 = entity_registry.async_get_or_create( "light", "hue", "1234", config_entry=mock_config ) - with patch.object( - entity_registry, "async_schedule_save" - ) as mock_schedule_save, pytest.raises(ValueError): + with ( + patch.object(entity_registry, "async_schedule_save") as mock_schedule_save, + pytest.raises(ValueError), + ): entity_registry.async_update_entity( entry.entity_id, new_unique_id=entry2.unique_id ) @@ -752,9 +753,10 @@ async def test_update_entity_entity_id_entity_id( assert entry2.entity_id != state_entity_id # Try updating to a registered entity_id - with patch.object( - entity_registry, "async_schedule_save" - ) as mock_schedule_save, pytest.raises(ValueError): + with ( + patch.object(entity_registry, "async_schedule_save") as mock_schedule_save, + pytest.raises(ValueError), + ): entity_registry.async_update_entity( entry.entity_id, new_entity_id=entry2.entity_id ) @@ -769,9 +771,10 @@ async def test_update_entity_entity_id_entity_id( assert entity_registry.async_get(entry2.entity_id) is entry2 # Try updating to an entity_id which is in the state machine - with patch.object( - entity_registry, "async_schedule_save" - ) as mock_schedule_save, pytest.raises(ValueError): + with ( + patch.object(entity_registry, "async_schedule_save") as mock_schedule_save, + pytest.raises(ValueError), + ): entity_registry.async_update_entity( entry.entity_id, new_entity_id=state_entity_id ) diff --git a/tests/helpers/test_frame.py b/tests/helpers/test_frame.py index 7881c845a77..fe215264f59 100644 --- a/tests/helpers/test_frame.py +++ b/tests/helpers/test_frame.py @@ -107,23 +107,26 @@ async def test_extract_frame_integration_with_excluded_integration( async def test_extract_frame_no_integration(caplog: pytest.LogCaptureFixture) -> None: """Test extracting the current frame without integration context.""" - with patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), - ), pytest.raises(frame.MissingIntegrationFrame): + pytest.raises(frame.MissingIntegrationFrame), + ): frame.get_integration_frame() diff --git a/tests/helpers/test_httpx_client.py b/tests/helpers/test_httpx_client.py index 2d1baf88b16..60bdbe607e3 100644 --- a/tests/helpers/test_httpx_client.py +++ b/tests/helpers/test_httpx_client.py @@ -104,29 +104,32 @@ async def test_warning_close_session_integration( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test log warning message when closing the session from integration context.""" - with patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.aclose()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="await session.aclose()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.aclose()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="await session.aclose()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): httpx_session = client.get_async_client(hass) @@ -146,29 +149,32 @@ async def test_warning_close_session_custom( ) -> None: """Test log warning message when closing the session from custom context.""" mock_integration(hass, MockModule("hue"), built_in=False) - with patch( - "homeassistant.helpers.frame.linecache.getline", - return_value="await session.aclose()", - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="await session.aclose()", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="await session.aclose()", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="await session.aclose()", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): httpx_session = client.get_async_client(hass) diff --git a/tests/helpers/test_icon.py b/tests/helpers/test_icon.py index cf329100d75..e986a07d7d5 100644 --- a/tests/helpers/test_icon.py +++ b/tests/helpers/test_icon.py @@ -161,15 +161,19 @@ async def test_get_icons_while_loading_components(hass: HomeAssistant) -> None: load_count += 1 return {"component1": {"entity": {"climate": {"test": {"icon": "mdi:home"}}}}} - with patch( - "homeassistant.helpers.icon._component_icons_path", - return_value="choochoo.json", - ), patch( - "homeassistant.helpers.icon._load_icons_files", - mock_load_icons_files, - ), patch( - "homeassistant.helpers.icon.async_get_integrations", - return_value={"component1": integration}, + with ( + patch( + "homeassistant.helpers.icon._component_icons_path", + return_value="choochoo.json", + ), + patch( + "homeassistant.helpers.icon._load_icons_files", + mock_load_icons_files, + ), + patch( + "homeassistant.helpers.icon.async_get_integrations", + return_value={"component1": integration}, + ), ): times = 5 all_icons = [await icon.async_get_icons(hass, "entity") for _ in range(times)] diff --git a/tests/helpers/test_importlib.py b/tests/helpers/test_importlib.py index 7f89018ded2..5683dd5cf94 100644 --- a/tests/helpers/test_importlib.py +++ b/tests/helpers/test_importlib.py @@ -38,17 +38,23 @@ async def test_async_import_module_on_helper(hass: HomeAssistant) -> None: async def test_async_import_module_failures(hass: HomeAssistant) -> None: """Test importing a module fails.""" - with patch( - "homeassistant.helpers.importlib.importlib.import_module", - side_effect=ImportError, - ), pytest.raises(ImportError): + with ( + patch( + "homeassistant.helpers.importlib.importlib.import_module", + side_effect=ImportError, + ), + pytest.raises(ImportError), + ): await importlib.async_import_module(hass, "test.module") mock_module = MockModule() # The failure should be cached - with pytest.raises(ImportError), patch( - "homeassistant.helpers.importlib.importlib.import_module", - return_value=mock_module, + with ( + pytest.raises(ImportError), + patch( + "homeassistant.helpers.importlib.importlib.import_module", + return_value=mock_module, + ), ): await importlib.async_import_module(hass, "test.module") diff --git a/tests/helpers/test_instance_id.py b/tests/helpers/test_instance_id.py index 3f1326bcd2e..ad2e4626af5 100644 --- a/tests/helpers/test_instance_id.py +++ b/tests/helpers/test_instance_id.py @@ -41,9 +41,11 @@ async def test_get_id_migrate( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: """Migrate existing file.""" - with patch( - "homeassistant.util.json.load_json", return_value={"uuid": "1234"} - ), patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: + with ( + patch("homeassistant.util.json.load_json", return_value={"uuid": "1234"}), + patch("os.path.isfile", return_value=True), + patch("os.remove") as mock_remove, + ): uuid = await instance_id.async_get(hass) assert uuid == "1234" @@ -59,10 +61,14 @@ async def test_get_id_migrate_fail( hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture ) -> None: """Migrate existing file with error.""" - with patch( - "homeassistant.util.json.load_json", - side_effect=JSONDecodeError("test_error", "test", 1), - ), patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: + with ( + patch( + "homeassistant.util.json.load_json", + side_effect=JSONDecodeError("test_error", "test", 1), + ), + patch("os.path.isfile", return_value=True), + patch("os.remove") as mock_remove, + ): uuid = await instance_id.async_get(hass) assert uuid is not None diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index 2eeae2ff3e0..81eb1f2fd38 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -163,9 +163,10 @@ async def test_process_integration_platforms_import_fails_after_registered( assert processed[0][1] == loaded_platform event_integration = await loader.async_get_integration(hass, "event") - with patch.object( - event_integration, "async_get_platforms", side_effect=ImportError - ), patch.object(event_integration, "get_platform_cached", return_value=None): + with ( + patch.object(event_integration, "async_get_platforms", side_effect=ImportError), + patch.object(event_integration, "get_platform_cached", return_value=None), + ): hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "event"}) await hass.async_block_till_done() diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index 3c0793290d0..caffebf094e 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -75,10 +75,13 @@ async def test_get_url_internal(hass: HomeAssistant) -> None: with pytest.raises(NoURLAvailableError): _get_internal_url(hass, require_current_request=True, require_ssl=True) - with patch( - "homeassistant.helpers.network._get_request_host", - return_value="no_match.example.local", - ), pytest.raises(NoURLAvailableError): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="no_match.example.local", + ), + pytest.raises(NoURLAvailableError), + ): _get_internal_url(hass, require_current_request=True) # Test with internal URL: https://example.local:8123 @@ -275,10 +278,13 @@ async def test_get_url_external(hass: HomeAssistant) -> None: with pytest.raises(NoURLAvailableError): _get_external_url(hass, require_current_request=True, require_ssl=True) - with patch( - "homeassistant.helpers.network._get_request_host", - return_value="no_match.example.com", - ), pytest.raises(NoURLAvailableError): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="no_match.example.com", + ), + pytest.raises(NoURLAvailableError), + ): _get_external_url(hass, require_current_request=True) # Test with external URL: http://example.com:80/ @@ -380,16 +386,22 @@ async def test_get_cloud_url(hass: HomeAssistant) -> None: == "https://example.nabu.casa" ) - with patch( - "homeassistant.helpers.network._get_request_host", - return_value="no_match.nabu.casa", - ), pytest.raises(NoURLAvailableError): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="no_match.nabu.casa", + ), + pytest.raises(NoURLAvailableError), + ): _get_cloud_url(hass, require_current_request=True) - with patch( - "homeassistant.components.cloud.async_remote_ui_url", - side_effect=cloud.CloudNotAvailable, - ), pytest.raises(NoURLAvailableError): + with ( + patch( + "homeassistant.components.cloud.async_remote_ui_url", + side_effect=cloud.CloudNotAvailable, + ), + pytest.raises(NoURLAvailableError), + ): _get_cloud_url(hass) @@ -506,9 +518,13 @@ async def test_get_url(hass: HomeAssistant) -> None: with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True) - with patch( - "homeassistant.helpers.network._get_request_host", return_value="example.com" - ), patch("homeassistant.components.http.current_request"): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="example.com", + ), + patch("homeassistant.components.http.current_request"), + ): assert get_url(hass, require_current_request=True) == "https://example.com" assert ( get_url(hass, require_current_request=True, require_ssl=True) @@ -518,9 +534,13 @@ async def test_get_url(hass: HomeAssistant) -> None: with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, allow_external=False) - with patch( - "homeassistant.helpers.network._get_request_host", return_value="example.local" - ), patch("homeassistant.components.http.current_request"): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="example.local", + ), + patch("homeassistant.components.http.current_request"), + ): assert get_url(hass, require_current_request=True) == "http://example.local" with pytest.raises(NoURLAvailableError): @@ -529,10 +549,13 @@ async def test_get_url(hass: HomeAssistant) -> None: with pytest.raises(NoURLAvailableError): get_url(hass, require_current_request=True, require_ssl=True) - with patch( - "homeassistant.helpers.network._get_request_host", - return_value="no_match.example.com", - ), pytest.raises(NoURLAvailableError): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="no_match.example.com", + ), + pytest.raises(NoURLAvailableError), + ): _get_internal_url(hass, require_current_request=True) # Test allow_ip defaults when SSL specified @@ -615,9 +638,13 @@ async def test_get_current_request_url_with_known_host( get_url(hass, require_current_request=True) == "http://homeassistant:8123" ) - with patch( - "homeassistant.helpers.network._get_request_host", return_value="unknown.local" - ), pytest.raises(NoURLAvailableError): + with ( + patch( + "homeassistant.helpers.network._get_request_host", + return_value="unknown.local", + ), + pytest.raises(NoURLAvailableError), + ): get_url(hass, require_current_request=True) diff --git a/tests/helpers/test_reload.py b/tests/helpers/test_reload.py index 4f7a2053a87..66d4efe9a55 100644 --- a/tests/helpers/test_reload.py +++ b/tests/helpers/test_reload.py @@ -141,10 +141,13 @@ async def test_setup_reload_service_when_async_process_component_config_fails( await async_setup_reload_service(hass, PLATFORM, [DOMAIN]) yaml_path = get_fixture_path("helpers/reload_configuration.yaml") - with patch.object(config, "YAML_CONFIG_FILE", yaml_path), patch.object( - config, - "async_process_component_config", - return_value=config.IntegrationConfigInfo(None, []), + with ( + patch.object(config, "YAML_CONFIG_FILE", yaml_path), + patch.object( + config, + "async_process_component_config", + return_value=config.IntegrationConfigInfo(None, []), + ), ): await hass.services.async_call( PLATFORM, @@ -250,10 +253,14 @@ async def test_async_integration_failing_on_reload(hass: HomeAssistant) -> None: mock_integration(hass, MockModule(DOMAIN)) yaml_path = get_fixture_path(f"helpers/{DOMAIN}_configuration.yaml") - with patch.object(config, "YAML_CONFIG_FILE", yaml_path), patch( - "homeassistant.config.async_process_component_config", - side_effect=HomeAssistantError(), - ), pytest.raises(HomeAssistantError): + with ( + patch.object(config, "YAML_CONFIG_FILE", yaml_path), + patch( + "homeassistant.config.async_process_component_config", + side_effect=HomeAssistantError(), + ), + pytest.raises(HomeAssistantError), + ): # Test fetching yaml config does raise when the raise_on_failure option is set await async_integration_yaml_config(hass, DOMAIN, raise_on_failure=True) @@ -263,7 +270,8 @@ async def test_async_integration_missing_yaml_config(hass: HomeAssistant) -> Non mock_integration(hass, MockModule(DOMAIN)) yaml_path = get_fixture_path("helpers/does_not_exist_configuration.yaml") - with pytest.raises(FileNotFoundError), patch.object( - config, "YAML_CONFIG_FILE", yaml_path + with ( + pytest.raises(FileNotFoundError), + patch.object(config, "YAML_CONFIG_FILE", yaml_path), ): await async_integration_yaml_config(hass, DOMAIN) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 95bb3f813e4..c1462ccfc2f 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -4929,10 +4929,13 @@ async def test_validate_action_config( assert key in expected_templates or key in script.STATIC_VALIDATION_ACTION_TYPES # Verify we raise if we don't know the action type - with patch( - "homeassistant.helpers.config_validation.determine_script_action", - return_value="non-existing", - ), pytest.raises(ValueError): + with ( + patch( + "homeassistant.helpers.config_validation.determine_script_action", + return_value="non-existing", + ), + pytest.raises(ValueError), + ): await script.async_validate_action_config(hass, {}) # Verify each action can validate diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index cf099b3bc23..74b8a86ce7c 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -927,12 +927,15 @@ async def test_async_get_all_descriptions_failing_integration( logger_config = {DOMAIN_LOGGER: {}} await async_setup_component(hass, DOMAIN_LOGGER, logger_config) - with patch( - "homeassistant.helpers.service.async_get_integrations", - return_value={"logger": ImportError}, - ), patch( - "homeassistant.helpers.service.translation.async_get_translations", - return_value={}, + with ( + patch( + "homeassistant.helpers.service.async_get_integrations", + return_value={"logger": ImportError}, + ), + patch( + "homeassistant.helpers.service.translation.async_get_translations", + return_value={}, + ), ): descriptions = await service.async_get_all_descriptions(hass) @@ -1294,9 +1297,12 @@ async def test_call_context_target_specific_no_auth( hass: HomeAssistant, mock_handle_entity_call, mock_entities ) -> None: """Check targeting specific entities without auth.""" - with pytest.raises(exceptions.Unauthorized) as err, patch( - "homeassistant.auth.AuthManager.async_get_user", - return_value=Mock(permissions=PolicyPermissions({}, None), is_admin=False), + with ( + pytest.raises(exceptions.Unauthorized) as err, + patch( + "homeassistant.auth.AuthManager.async_get_user", + return_value=Mock(permissions=PolicyPermissions({}, None), is_admin=False), + ), ): await service.entity_service_call( hass, diff --git a/tests/helpers/test_significant_change.py b/tests/helpers/test_significant_change.py index 8dda63bb72b..e930ff30feb 100644 --- a/tests/helpers/test_significant_change.py +++ b/tests/helpers/test_significant_change.py @@ -18,9 +18,9 @@ async def checker_fixture(hass): ): return abs(float(old_state) - float(new_state)) > 4 - hass.data[significant_change.DATA_FUNCTIONS][ - "test_domain" - ] = async_check_significant_change + hass.data[significant_change.DATA_FUNCTIONS]["test_domain"] = ( + async_check_significant_change + ) return checker diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 0918a9551f4..d04f7902297 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -410,8 +410,9 @@ async def test_migrator_no_existing_config( hass: HomeAssistant, store, hass_storage: dict[str, Any] ) -> None: """Test migrator with no existing config.""" - with patch("os.path.isfile", return_value=False), patch.object( - store, "async_load", return_value={"cur": "config"} + with ( + patch("os.path.isfile", return_value=False), + patch.object(store, "async_load", return_value={"cur": "config"}), ): data = await storage.async_migrator(hass, "old-path", store) @@ -791,14 +792,20 @@ async def test_os_error_is_fatal(tmpdir: py.path.local) -> None: ) await store.async_save({"hello": "world"}) - with pytest.raises(OSError), patch( - "homeassistant.helpers.storage.json_util.load_json", side_effect=OSError + with ( + pytest.raises(OSError), + patch( + "homeassistant.helpers.storage.json_util.load_json", side_effect=OSError + ), ): await store.async_load() # Verify second load is also failing - with pytest.raises(OSError), patch( - "homeassistant.helpers.storage.json_util.load_json", side_effect=OSError + with ( + pytest.raises(OSError), + patch( + "homeassistant.helpers.storage.json_util.load_json", side_effect=OSError + ), ): await store.async_load() @@ -820,9 +827,12 @@ async def test_json_load_failure(tmpdir: py.path.local) -> None: home_assistant_error = HomeAssistantError() home_assistant_error.__cause__ = base_os_error - with pytest.raises(HomeAssistantError), patch( - "homeassistant.helpers.storage.json_util.load_json", - side_effect=home_assistant_error, + with ( + pytest.raises(HomeAssistantError), + patch( + "homeassistant.helpers.storage.json_util.load_json", + side_effect=home_assistant_error, + ), ): await store.async_load() diff --git a/tests/helpers/test_system_info.py b/tests/helpers/test_system_info.py index a7f77bd5cfb..16b5b8b652b 100644 --- a/tests/helpers/test_system_info.py +++ b/tests/helpers/test_system_info.py @@ -36,13 +36,14 @@ async def test_get_system_info_supervisor_not_available( ) -> None: """Test the get system info when supervisor is not available.""" hass.config.components.add("hassio") - with patch("platform.system", return_value="Linux"), patch( - "homeassistant.helpers.system_info.is_docker_env", return_value=True - ), patch( - "homeassistant.helpers.system_info.is_official_image", return_value=True - ), patch.object(hassio, "is_hassio", return_value=True), patch.object( - hassio, "get_info", return_value=None - ), patch("homeassistant.helpers.system_info.cached_get_user", return_value="root"): + with ( + patch("platform.system", return_value="Linux"), + patch("homeassistant.helpers.system_info.is_docker_env", return_value=True), + patch("homeassistant.helpers.system_info.is_official_image", return_value=True), + patch.object(hassio, "is_hassio", return_value=True), + patch.object(hassio, "get_info", return_value=None), + patch("homeassistant.helpers.system_info.cached_get_user", return_value="root"), + ): info = await async_get_system_info(hass) assert isinstance(info, dict) assert info["version"] == current_version @@ -54,12 +55,12 @@ async def test_get_system_info_supervisor_not_available( async def test_get_system_info_supervisor_not_loaded(hass: HomeAssistant) -> None: """Test the get system info when supervisor is not loaded.""" - with patch("platform.system", return_value="Linux"), patch( - "homeassistant.helpers.system_info.is_docker_env", return_value=True - ), patch( - "homeassistant.helpers.system_info.is_official_image", return_value=True - ), patch.object(hassio, "get_info", return_value=None), patch.dict( - os.environ, {"SUPERVISOR": "127.0.0.1"} + with ( + patch("platform.system", return_value="Linux"), + patch("homeassistant.helpers.system_info.is_docker_env", return_value=True), + patch("homeassistant.helpers.system_info.is_official_image", return_value=True), + patch.object(hassio, "get_info", return_value=None), + patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), ): info = await async_get_system_info(hass) assert isinstance(info, dict) @@ -71,19 +72,23 @@ async def test_get_system_info_supervisor_not_loaded(hass: HomeAssistant) -> Non async def test_container_installationtype(hass: HomeAssistant) -> None: """Test container installation type.""" - with patch("platform.system", return_value="Linux"), patch( - "homeassistant.helpers.system_info.is_docker_env", return_value=True - ), patch( - "homeassistant.helpers.system_info.is_official_image", return_value=True - ), patch("homeassistant.helpers.system_info.cached_get_user", return_value="root"): + with ( + patch("platform.system", return_value="Linux"), + patch("homeassistant.helpers.system_info.is_docker_env", return_value=True), + patch("homeassistant.helpers.system_info.is_official_image", return_value=True), + patch("homeassistant.helpers.system_info.cached_get_user", return_value="root"), + ): info = await async_get_system_info(hass) assert info["installation_type"] == "Home Assistant Container" - with patch("platform.system", return_value="Linux"), patch( - "homeassistant.helpers.system_info.is_docker_env", return_value=True - ), patch( - "homeassistant.helpers.system_info.is_official_image", return_value=False - ), patch("homeassistant.helpers.system_info.cached_get_user", return_value="user"): + with ( + patch("platform.system", return_value="Linux"), + patch("homeassistant.helpers.system_info.is_docker_env", return_value=True), + patch( + "homeassistant.helpers.system_info.is_official_image", return_value=False + ), + patch("homeassistant.helpers.system_info.cached_get_user", return_value="user"), + ): info = await async_get_system_info(hass) assert info["installation_type"] == "Unsupported Third Party Container" diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 669fb4bed24..1bba23c51a1 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -222,15 +222,19 @@ async def test_get_translations_loads_config_flows( integration = Mock(file_path=pathlib.Path(__file__)) integration.name = "Component 1" - with patch( - "homeassistant.helpers.translation.component_translation_path", - return_value="bla.json", - ), patch( - "homeassistant.helpers.translation._load_translations_files_by_language", - return_value={"en": {"component1": {"title": "world"}}}, - ), patch( - "homeassistant.helpers.translation.async_get_integrations", - return_value={"component1": integration}, + with ( + patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), + patch( + "homeassistant.helpers.translation._load_translations_files_by_language", + return_value={"en": {"component1": {"title": "world"}}}, + ), + patch( + "homeassistant.helpers.translation.async_get_integrations", + return_value={"component1": integration}, + ), ): translations = await translation.async_get_translations( hass, "en", "title", config_flow=True @@ -251,15 +255,19 @@ async def test_get_translations_loads_config_flows( integration = Mock(file_path=pathlib.Path(__file__)) integration.name = "Component 2" - with patch( - "homeassistant.helpers.translation.component_translation_path", - return_value="bla.json", - ), patch( - "homeassistant.helpers.translation._load_translations_files_by_language", - return_value={"en": {"component2": {"title": "world"}}}, - ), patch( - "homeassistant.helpers.translation.async_get_integrations", - return_value={"component2": integration}, + with ( + patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), + patch( + "homeassistant.helpers.translation._load_translations_files_by_language", + return_value={"en": {"component2": {"title": "world"}}}, + ), + patch( + "homeassistant.helpers.translation.async_get_integrations", + return_value={"component2": integration}, + ), ): translations = await translation.async_get_translations( hass, "en", "title", config_flow=True @@ -301,15 +309,19 @@ async def test_get_translations_while_loading_components(hass: HomeAssistant) -> return {language: {"component1": {"title": "world"}} for language in files} - with patch( - "homeassistant.helpers.translation.component_translation_path", - return_value="bla.json", - ), patch( - "homeassistant.helpers.translation._load_translations_files_by_language", - mock_load_translation_files, - ), patch( - "homeassistant.helpers.translation.async_get_integrations", - return_value={"component1": integration}, + with ( + patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), + patch( + "homeassistant.helpers.translation._load_translations_files_by_language", + mock_load_translation_files, + ), + patch( + "homeassistant.helpers.translation.async_get_integrations", + return_value={"component1": integration}, + ), ): tasks = [ translation.async_get_translations(hass, "en", "title") for _ in range(5) @@ -842,15 +854,19 @@ async def test_get_translations_still_has_title_without_translations_files( integration = Mock(file_path=pathlib.Path(__file__)) integration.name = "Component 1" - with patch( - "homeassistant.helpers.translation.component_translation_path", - return_value="bla.json", - ), patch( - "homeassistant.helpers.translation._load_translations_files_by_language", - return_value={}, - ), patch( - "homeassistant.helpers.translation.async_get_integrations", - return_value={"component1": integration}, + with ( + patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), + patch( + "homeassistant.helpers.translation._load_translations_files_by_language", + return_value={}, + ), + patch( + "homeassistant.helpers.translation.async_get_integrations", + return_value={"component1": integration}, + ), ): translations = await translation.async_get_translations( hass, "en", "title", config_flow=True diff --git a/tests/pylint/test_enforce_super_call.py b/tests/pylint/test_enforce_super_call.py index 39a70369e0c..84d4086fc5c 100644 --- a/tests/pylint/test_enforce_super_call.py +++ b/tests/pylint/test_enforce_super_call.py @@ -124,9 +124,14 @@ def test_enforce_super_call( walker = ASTWalker(linter) walker.add_checker(super_call_checker) - with patch.object( - hass_enforce_super_call, "METHODS", new={"added_to_hass", "async_added_to_hass"} - ), assert_no_messages(linter): + with ( + patch.object( + hass_enforce_super_call, + "METHODS", + new={"added_to_hass", "async_added_to_hass"}, + ), + assert_no_messages(linter), + ): walker.walk(root_node) @@ -204,19 +209,24 @@ def test_enforce_super_call_bad( walker.add_checker(super_call_checker) node = root_node.body[node_idx].body[0] - with patch.object( - hass_enforce_super_call, "METHODS", new={"added_to_hass", "async_added_to_hass"} - ), assert_adds_messages( - linter, - MessageTest( - msg_id="hass-missing-super-call", - node=node, - line=node.lineno, - args=(node.name,), - col_offset=node.col_offset, - end_line=node.position.end_lineno, - end_col_offset=node.position.end_col_offset, - confidence=INFERENCE, + with ( + patch.object( + hass_enforce_super_call, + "METHODS", + new={"added_to_hass", "async_added_to_hass"}, + ), + assert_adds_messages( + linter, + MessageTest( + msg_id="hass-missing-super-call", + node=node, + line=node.lineno, + args=(node.name,), + col_offset=node.col_offset, + end_line=node.position.end_lineno, + end_col_offset=node.position.end_col_offset, + confidence=INFERENCE, + ), ), ): walker.walk(root_node) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 90e9017a5c1..de82aba9911 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,4 +1,5 @@ """Test the bootstrapping.""" + import asyncio from collections.abc import Generator, Iterable import glob @@ -46,9 +47,10 @@ async def apply_stop_hass(stop_hass: None) -> None: @pytest.fixture(scope="session", autouse=True) def mock_http_start_stop() -> Generator[None, None, None]: """Mock HTTP start and stop.""" - with patch( - "homeassistant.components.http.start_http_server_and_save_config" - ), patch("homeassistant.components.http.HomeAssistantHTTP.stop"): + with ( + patch("homeassistant.components.http.start_http_server_and_save_config"), + patch("homeassistant.components.http.HomeAssistantHTTP.stop"), + ): yield @@ -66,11 +68,15 @@ async def test_async_enable_logging( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: """Test to ensure logging is migrated to the queue handlers.""" - with patch("logging.getLogger"), patch( - "homeassistant.bootstrap.async_activate_log_queue_handler" - ) as mock_async_activate_log_queue_handler, patch( - "homeassistant.bootstrap.logging.handlers.RotatingFileHandler.doRollover", - side_effect=OSError, + with ( + patch("logging.getLogger"), + patch( + "homeassistant.bootstrap.async_activate_log_queue_handler" + ) as mock_async_activate_log_queue_handler, + patch( + "homeassistant.bootstrap.logging.handlers.RotatingFileHandler.doRollover", + side_effect=OSError, + ), ): bootstrap.async_enable_logging(hass) mock_async_activate_log_queue_handler.assert_called_once() @@ -636,11 +642,13 @@ async def test_setup_hass_takes_longer_than_log_slow_startup( await asyncio.sleep(0.6) return True - with patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 0.3), patch.object( - bootstrap, "SLOW_STARTUP_CHECK_INTERVAL", 0.05 - ), patch( - "homeassistant.components.frontend.async_setup", - side_effect=_async_setup_that_blocks_startup, + with ( + patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 0.3), + patch.object(bootstrap, "SLOW_STARTUP_CHECK_INTERVAL", 0.05), + patch( + "homeassistant.components.frontend.async_setup", + side_effect=_async_setup_that_blocks_startup, + ), ): await bootstrap.async_setup_hass( runner.RuntimeConfig( @@ -718,9 +726,12 @@ async def test_setup_hass_recovery_mode( mock_process_ha_config_upgrade: Mock, ) -> None: """Test it works.""" - with patch("homeassistant.components.browser.setup") as browser_setup, patch( - "homeassistant.config_entries.ConfigEntries.async_domains", - return_value=["browser"], + with ( + patch("homeassistant.components.browser.setup") as browser_setup, + patch( + "homeassistant.config_entries.ConfigEntries.async_domains", + return_value=["browser"], + ), ): hass = await bootstrap.async_setup_hass( runner.RuntimeConfig( @@ -752,9 +763,12 @@ async def test_setup_hass_safe_mode( caplog: pytest.LogCaptureFixture, ) -> None: """Test it works.""" - with patch("homeassistant.components.browser.setup"), patch( - "homeassistant.config_entries.ConfigEntries.async_domains", - return_value=["browser"], + with ( + patch("homeassistant.components.browser.setup"), + patch( + "homeassistant.config_entries.ConfigEntries.async_domains", + return_value=["browser"], + ), ): hass = await bootstrap.async_setup_hass( runner.RuntimeConfig( @@ -784,9 +798,12 @@ async def test_setup_hass_recovery_mode_and_safe_mode( caplog: pytest.LogCaptureFixture, ) -> None: """Test it works.""" - with patch("homeassistant.components.browser.setup"), patch( - "homeassistant.config_entries.ConfigEntries.async_domains", - return_value=["browser"], + with ( + patch("homeassistant.components.browser.setup"), + patch( + "homeassistant.config_entries.ConfigEntries.async_domains", + return_value=["browser"], + ), ): hass = await bootstrap.async_setup_hass( runner.RuntimeConfig( @@ -1004,10 +1021,12 @@ async def test_tasks_logged_that_block_stage_1( ) original_stage_1 = bootstrap.STAGE_1_INTEGRATIONS - with patch.object(bootstrap, "STAGE_1_TIMEOUT", 0), patch.object( - bootstrap, "COOLDOWN_TIME", 0 - ), patch.object( - bootstrap, "STAGE_1_INTEGRATIONS", [*original_stage_1, "normal_integration"] + with ( + patch.object(bootstrap, "STAGE_1_TIMEOUT", 0), + patch.object(bootstrap, "COOLDOWN_TIME", 0), + patch.object( + bootstrap, "STAGE_1_INTEGRATIONS", [*original_stage_1, "normal_integration"] + ), ): await bootstrap._async_set_up_integrations(hass, {"normal_integration": {}}) await hass.async_block_till_done() @@ -1043,8 +1062,9 @@ async def test_tasks_logged_that_block_stage_2( ), ) - with patch.object(bootstrap, "STAGE_2_TIMEOUT", 0), patch.object( - bootstrap, "COOLDOWN_TIME", 0 + with ( + patch.object(bootstrap, "STAGE_2_TIMEOUT", 0), + patch.object(bootstrap, "COOLDOWN_TIME", 0), ): await bootstrap._async_set_up_integrations(hass, {"normal_integration": {}}) await hass.async_block_till_done() @@ -1191,12 +1211,15 @@ async def test_bootstrap_dependencies( """Mock integrations.""" return {domain: integrations[domain]["integration"] for domain in domains} - with patch( - "homeassistant.setup.loader.async_get_integrations", - side_effect=mock_async_get_integrations, - ), patch( - "homeassistant.config.async_process_component_config", - return_value=config_util.IntegrationConfigInfo({}, []), + with ( + patch( + "homeassistant.setup.loader.async_get_integrations", + side_effect=mock_async_get_integrations, + ), + patch( + "homeassistant.config.async_process_component_config", + return_value=config_util.IntegrationConfigInfo({}, []), + ), ): bootstrap.async_set_domains_to_be_loaded(hass, {integration}) await bootstrap.async_setup_multi_components(hass, {integration}, {}) diff --git a/tests/test_config.py b/tests/test_config.py index c2e134b1cfd..c20e2822592 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -466,8 +466,9 @@ def test_load_yaml_config_raises_error_if_unsafe_yaml() -> None: with open(YAML_PATH, "w") as fp: fp.write("- !!python/object/apply:os.system []") - with patch.object(os, "system") as system_mock, contextlib.suppress( - HomeAssistantError + with ( + patch.object(os, "system") as system_mock, + contextlib.suppress(HomeAssistantError), ): config_util.load_yaml_config_file(YAML_PATH) @@ -652,8 +653,9 @@ def test_process_config_upgrade(hass: HomeAssistant) -> None: ha_version = "0.92.0" mock_open = mock.mock_open() - with patch("homeassistant.config.open", mock_open, create=True), patch.object( - config_util, "__version__", "0.91.0" + with ( + patch("homeassistant.config.open", mock_open, create=True), + patch.object(config_util, "__version__", "0.91.0"), ): opened_file = mock_open.return_value opened_file.readline.return_value = ha_version @@ -1909,10 +1911,13 @@ async def test_component_config_error_processing( ) ), ) - with patch( - "homeassistant.config.async_process_component_config", - return_value=config_util.IntegrationConfigInfo(None, exception_info_list), - ), pytest.raises(ConfigValidationError) as ex: + with ( + patch( + "homeassistant.config.async_process_component_config", + return_value=config_util.IntegrationConfigInfo(None, exception_info_list), + ), + pytest.raises(ConfigValidationError) as ex, + ): await config_util.async_process_component_and_handle_errors( hass, {}, test_integration, raise_on_failure=True ) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index da100d8cfb0..7d564f1cf12 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1791,9 +1791,12 @@ async def test_init_custom_integration(hass: HomeAssistant) -> None: None, {"name": "Hue", "dependencies": [], "requirements": [], "domain": "hue"}, ) - with pytest.raises(data_entry_flow.UnknownHandler), patch( - "homeassistant.loader.async_get_integration", - return_value=integration, + with ( + pytest.raises(data_entry_flow.UnknownHandler), + patch( + "homeassistant.loader.async_get_integration", + return_value=integration, + ), ): await hass.config_entries.flow.async_init("bla", context={"source": "user"}) @@ -1813,9 +1816,12 @@ async def test_init_custom_integration_with_missing_handler( MockModule("hue"), ) mock_platform(hass, "hue.config_flow", None) - with pytest.raises(data_entry_flow.UnknownHandler), patch( - "homeassistant.loader.async_get_integration", - return_value=integration, + with ( + pytest.raises(data_entry_flow.UnknownHandler), + patch( + "homeassistant.loader.async_get_integration", + return_value=integration, + ), ): await hass.config_entries.flow.async_init("bla", context={"source": "user"}) @@ -2013,11 +2019,13 @@ async def test_entry_id_existing_entry( """Test user step.""" return self.async_create_entry(title="mock-title", data={"via": "flow"}) - with pytest.raises(HomeAssistantError), patch.dict( - config_entries.HANDLERS, {"comp": TestFlow} - ), patch( - "homeassistant.config_entries.uuid_util.random_uuid_hex", - return_value=collide_entry_id, + with ( + pytest.raises(HomeAssistantError), + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.uuid_util.random_uuid_hex", + return_value=collide_entry_id, + ), ): await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} @@ -2055,9 +2063,12 @@ async def test_unique_id_update_existing_entry_without_reload( updates={"host": "1.1.1.1"}, reload_on_update=False ) - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -2102,9 +2113,12 @@ async def test_unique_id_update_existing_entry_with_reload( updates=updates, reload_on_update=True ) - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -2119,9 +2133,12 @@ async def test_unique_id_update_existing_entry_with_reload( # Test we don't reload if entry not started updates["host"] = "2.2.2.2" entry._async_set_state(hass, config_entries.ConfigEntryState.NOT_LOADED, None) - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -2173,9 +2190,12 @@ async def test_unique_id_from_discovery_in_setup_retry( self._abort_if_unique_id_configured() # Verify we do not reload from a user source - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -2186,9 +2206,12 @@ async def test_unique_id_from_discovery_in_setup_retry( assert len(async_reload.mock_calls) == 0 # Verify do reload from a discovery source - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): discovery_result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_DHCP}, @@ -2235,9 +2258,12 @@ async def test_unique_id_not_update_existing_entry( updates={"host": "0.0.0.0"}, reload_on_update=True ) - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -2405,9 +2431,12 @@ async def test_manual_add_overrides_ignored_entry( async def async_step_step2(self, user_input=None): raise NotImplementedError - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -3973,9 +4002,12 @@ async def test_unique_id_update_while_setup_in_progress( updates=updates, reload_on_update=True ) - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.config_entries.ConfigEntries.async_reload" - ) as async_reload: + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.config_entries.ConfigEntries.async_reload" + ) as async_reload, + ): result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} ) @@ -4726,10 +4758,13 @@ async def test_avoid_adding_second_config_entry_on_single_config_entry( mock_integration(hass, MockModule("comp")) mock_platform(hass, "comp.config_flow", None) - with patch( - "homeassistant.loader.async_get_integration", - return_value=integration, - ), patch.dict(config_entries.HANDLERS, {"comp": TestFlow}): + with ( + patch( + "homeassistant.loader.async_get_integration", + return_value=integration, + ), + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + ): # Start a flow result = await manager.flow.async_init( "comp", context={"source": config_entries.SOURCE_USER} @@ -4786,9 +4821,12 @@ async def test_in_progress_get_canceled_when_entry_is_created( return self.async_show_form(step_id="user") - with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), patch( - "homeassistant.loader.async_get_integration", - return_value=integration, + with ( + patch.dict(config_entries.HANDLERS, {"comp": TestFlow}), + patch( + "homeassistant.loader.async_get_integration", + return_value=integration, + ), ): # Create one to be in progress result = await manager.flow.async_init( diff --git a/tests/test_core.py b/tests/test_core.py index ab6f0b11270..efeb185a3d6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -705,9 +705,12 @@ async def test_shutdown_calls_block_till_done_after_shutdown_run_callback_thread nonlocal stop_calls stop_calls.append(("shutdown_run_callback_threadsafe", loop)) - with patch.object(hass, "async_block_till_done", _record_block_till_done), patch( - "homeassistant.core.shutdown_run_callback_threadsafe", - _record_shutdown_run_callback_threadsafe, + with ( + patch.object(hass, "async_block_till_done", _record_block_till_done), + patch( + "homeassistant.core.shutdown_run_callback_threadsafe", + _record_shutdown_run_callback_threadsafe, + ), ): await hass.async_stop() diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index c18eae4dd19..5c3ad2a3b39 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -760,8 +760,9 @@ async def test_abort_flow_exception(manager) -> None: async def test_init_unknown_flow(manager) -> None: """Test that UnknownFlow is raised when async_create_flow returns None.""" - with pytest.raises(data_entry_flow.UnknownFlow), patch.object( - manager, "async_create_flow", return_value=None + with ( + pytest.raises(data_entry_flow.UnknownFlow), + patch.object(manager, "async_create_flow", return_value=None), ): await manager.async_init("test") diff --git a/tests/test_loader.py b/tests/test_loader.py index c8a8905cdef..9e191ee9e00 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -252,13 +252,21 @@ async def test_get_integration_exceptions(hass: HomeAssistant) -> None: """Test resolving integration.""" integration = await loader.async_get_integration(hass, "hue") - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ValueError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ValueError("Boom"), + ), ): assert hue == integration.get_component() - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ValueError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ValueError("Boom"), + ), ): assert hue_light == integration.get_platform("light") @@ -269,27 +277,43 @@ async def test_get_platform_caches_failures_when_component_loaded( """Test get_platform cache failures only when the component is loaded.""" integration = await loader.async_get_integration(hass, "hue") - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert integration.get_component() == hue - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert integration.get_platform("light") == hue_light # Hue is not loaded so we should still hit the import_module path - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert integration.get_platform("light") == hue_light assert integration.get_component() == hue # Hue is loaded so we should cache the import_module failure now - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert integration.get_platform("light") == hue_light @@ -304,27 +328,43 @@ async def test_async_get_platform_caches_failures_when_component_loaded( """Test async_get_platform cache failures only when the component is loaded.""" integration = await loader.async_get_integration(hass, "hue") - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert integration.get_component() == hue - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert await integration.async_get_platform("light") == hue_light # Hue is not loaded so we should still hit the import_module path - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert await integration.async_get_platform("light") == hue_light assert integration.get_component() == hue # Hue is loaded so we should cache the import_module failure now - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert await integration.async_get_platform("light") == hue_light @@ -342,27 +382,43 @@ async def test_async_get_platforms_caches_failures_when_component_loaded( """Test async_get_platforms cache failures only when the component is loaded.""" integration = await loader.async_get_integration(hass, "hue") - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert integration.get_component() == hue - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} # Hue is not loaded so we should still hit the import_module path - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} assert integration.get_component() == hue # Hue is loaded so we should cache the import_module failure now - with pytest.raises(ImportError), patch( - "homeassistant.loader.importlib.import_module", side_effect=ImportError("Boom") + with ( + pytest.raises(ImportError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ImportError("Boom"), + ), ): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} @@ -1129,12 +1185,15 @@ async def test_hass_components_use_reported( relative_filename="custom_components/test_integration_frame/__init__.py", ) - with patch( - "homeassistant.helpers.frame.get_integration_frame", - return_value=integration_frame, - ), patch( - "homeassistant.components.http.start_http_server_and_save_config", - return_value=None, + with ( + patch( + "homeassistant.helpers.frame.get_integration_frame", + return_value=integration_frame, + ), + patch( + "homeassistant.components.http.start_http_server_and_save_config", + return_value=None, + ), ): await hass.components.http.start_http_server_and_save_config(hass, [], None) @@ -1162,10 +1221,11 @@ async def test_async_get_component_preloads_config_and_config_flow( platform_exists_calls.append(platforms) return platforms - with patch( - "homeassistant.loader.importlib.import_module" - ) as mock_import, patch.object( - executor_import_integration, "platforms_exists", mock_platforms_exists + with ( + patch("homeassistant.loader.importlib.import_module") as mock_import, + patch.object( + executor_import_integration, "platforms_exists", mock_platforms_exists + ), ): await executor_import_integration.async_get_component() @@ -1218,11 +1278,14 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( modules_without_config_flow = { k: v for k, v in sys.modules.items() if k != config_flow_module_name } - with patch.dict( - "sys.modules", - {**modules_without_config_flow, integration.pkg_path: module_mock}, - clear=True, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + {**modules_without_config_flow, integration.pkg_path: module_mock}, + clear=True, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): module = await integration.async_get_component() # The config flow is missing so we should load @@ -1232,13 +1295,16 @@ async def test_async_get_component_loads_loop_if_already_in_sys_modules( assert module is module_mock caplog.clear() - with patch.dict( - "sys.modules", - { - integration.pkg_path: module_mock, - config_flow_module_name: config_flow_module_mock, - }, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + { + integration.pkg_path: module_mock, + config_flow_module_name: config_flow_module_mock, + }, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): module = await integration.async_get_component() # Everything is already in the integration cache @@ -1285,11 +1351,14 @@ async def test_async_get_component_concurrent_loads( for k, v in sys.modules.items() if k != config_flow_module_name and k != integration.pkg_path } - with patch.dict( - "sys.modules", - {**modules_without_integration}, - clear=True, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + {**modules_without_integration}, + clear=True, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): load_task1 = asyncio.create_task(integration.async_get_component()) load_task2 = asyncio.create_task(integration.async_get_component()) await import_event.wait() # make sure the import is started @@ -1371,9 +1440,10 @@ async def test_async_get_component_raises_after_import_failure( assert "homeassistant.components.executor_import" not in sys.modules assert "custom_components.executor_import" not in sys.modules - with patch( - "homeassistant.loader.importlib.import_module", mock_import - ), pytest.raises(ImportError): + with ( + patch("homeassistant.loader.importlib.import_module", mock_import), + pytest.raises(ImportError), + ): await executor_import_integration.async_get_component() assert ( @@ -1454,9 +1524,10 @@ async def test_async_get_platform_raises_after_import_failure( assert "homeassistant.components.executor_import" not in sys.modules assert "custom_components.executor_import" not in sys.modules - with patch( - "homeassistant.loader.importlib.import_module", mock_import - ), pytest.raises(ImportError): + with ( + patch("homeassistant.loader.importlib.import_module", mock_import), + pytest.raises(ImportError), + ): await executor_import_integration.async_get_platform("config_flow") assert ( @@ -1563,11 +1634,14 @@ async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( modules_without_button = { k: v for k, v in sys.modules.items() if k != button_module_name } - with patch.dict( - "sys.modules", - modules_without_button, - clear=True, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + modules_without_button, + clear=True, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): module = (await integration.async_get_platforms(["button"]))["button"] # The button module is missing so we should load @@ -1577,13 +1651,16 @@ async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( assert module is button_module_mock caplog.clear() - with patch.dict( - "sys.modules", - { - **modules_without_button, - button_module_name: button_module_mock, - }, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + { + **modules_without_button, + button_module_name: button_module_mock, + }, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): module = (await integration.async_get_platforms(["button"]))["button"] # Everything is cached so there should be no logging @@ -1595,11 +1672,14 @@ async def test_async_get_platforms_loads_loop_if_already_in_sys_modules( modules_without_switch = { k: v for k, v in sys.modules.items() if k not in switch_module_name } - with patch.dict( - "sys.modules", - {**modules_without_switch, light_module_name: light_module_mock}, - clear=True, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + {**modules_without_switch, light_module_name: light_module_mock}, + clear=True, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): modules = await integration.async_get_platforms(["button", "switch", "light"]) # The button module is already in the cache so nothing happens @@ -1659,11 +1739,14 @@ async def test_async_get_platforms_concurrent_loads( for k, v in sys.modules.items() if k != button_module_name and k != integration.pkg_path } - with patch.dict( - "sys.modules", - modules_without_button, - clear=True, - ), patch("homeassistant.loader.importlib.import_module", import_module): + with ( + patch.dict( + "sys.modules", + modules_without_button, + clear=True, + ), + patch("homeassistant.loader.importlib.import_module", import_module), + ): load_task1 = asyncio.create_task(integration.async_get_platforms(["button"])) load_task2 = asyncio.create_task(integration.async_get_platforms(["button"])) await import_event.wait() # make sure the import is started diff --git a/tests/test_requirements.py b/tests/test_requirements.py index 87d055e2122..ed04ef8649b 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -31,11 +31,15 @@ def env_without_wheel_links(): async def test_requirement_installed_in_venv(hass: HomeAssistant) -> None: """Test requirement installed in virtual environment.""" - with patch("os.path.dirname", return_value="ha_package_path"), patch( - "homeassistant.util.package.is_virtual_env", return_value=True - ), patch("homeassistant.util.package.is_docker_env", return_value=False), patch( - "homeassistant.util.package.install_package", return_value=True - ) as mock_install, patch.dict(os.environ, env_without_wheel_links(), clear=True): + with ( + patch("os.path.dirname", return_value="ha_package_path"), + patch("homeassistant.util.package.is_virtual_env", return_value=True), + patch("homeassistant.util.package.is_docker_env", return_value=False), + patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_install, + patch.dict(os.environ, env_without_wheel_links(), clear=True), + ): hass.config.skip_pip = False mock_integration(hass, MockModule("comp", requirements=["package==0.0.1"])) assert await setup.async_setup_component(hass, "comp", {}) @@ -49,11 +53,15 @@ async def test_requirement_installed_in_venv(hass: HomeAssistant) -> None: async def test_requirement_installed_in_deps(hass: HomeAssistant) -> None: """Test requirement installed in deps directory.""" - with patch("os.path.dirname", return_value="ha_package_path"), patch( - "homeassistant.util.package.is_virtual_env", return_value=False - ), patch("homeassistant.util.package.is_docker_env", return_value=False), patch( - "homeassistant.util.package.install_package", return_value=True - ) as mock_install, patch.dict(os.environ, env_without_wheel_links(), clear=True): + with ( + patch("os.path.dirname", return_value="ha_package_path"), + patch("homeassistant.util.package.is_virtual_env", return_value=False), + patch("homeassistant.util.package.is_docker_env", return_value=False), + patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_install, + patch.dict(os.environ, env_without_wheel_links(), clear=True), + ): hass.config.skip_pip = False mock_integration(hass, MockModule("comp", requirements=["package==0.0.1"])) assert await setup.async_setup_component(hass, "comp", {}) @@ -75,9 +83,10 @@ async def test_install_existing_package(hass: HomeAssistant) -> None: assert len(mock_inst.mock_calls) == 1 - with patch("homeassistant.util.package.is_installed", return_value=True), patch( - "homeassistant.util.package.install_package" - ) as mock_inst: + with ( + patch("homeassistant.util.package.is_installed", return_value=True), + patch("homeassistant.util.package.install_package") as mock_inst, + ): await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) assert len(mock_inst.mock_calls) == 0 @@ -85,9 +94,12 @@ async def test_install_existing_package(hass: HomeAssistant) -> None: async def test_install_missing_package(hass: HomeAssistant) -> None: """Test an install attempt on an existing package.""" - with patch( - "homeassistant.util.package.install_package", return_value=False - ) as mock_inst, pytest.raises(RequirementsNotFound): + with ( + patch( + "homeassistant.util.package.install_package", return_value=False + ) as mock_inst, + pytest.raises(RequirementsNotFound), + ): await async_process_requirements(hass, "test_component", ["hello==1.0.0"]) assert len(mock_inst.mock_calls) == 3 @@ -134,11 +146,14 @@ async def test_get_integration_with_requirements(hass: HomeAssistant) -> None: ), ) - with patch( - "homeassistant.util.package.is_installed", return_value=False - ) as mock_is_installed, patch( - "homeassistant.util.package.install_package", return_value=True - ) as mock_inst: + with ( + patch( + "homeassistant.util.package.is_installed", return_value=False + ) as mock_is_installed, + patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_inst, + ): integration = await async_get_integration_with_requirements( hass, "test_component" ) @@ -195,13 +210,18 @@ async def test_get_integration_with_requirements_cache(hass: HomeAssistant) -> N ), ) - with patch( - "homeassistant.util.package.is_installed", return_value=False - ) as mock_is_installed, patch( - "homeassistant.util.package.install_package", return_value=True - ) as mock_inst, patch( - "homeassistant.requirements.async_get_integration", wraps=async_get_integration - ) as mock_async_get_integration: + with ( + patch( + "homeassistant.util.package.is_installed", return_value=False + ) as mock_is_installed, + patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_inst, + patch( + "homeassistant.requirements.async_get_integration", + wraps=async_get_integration, + ) as mock_async_get_integration, + ): integration = await async_get_integration_with_requirements( hass, "test_component" ) @@ -323,11 +343,16 @@ async def test_get_integration_with_requirements_pip_install_fails_two_passes( return False # 1st pass - with pytest.raises(RequirementsNotFound), patch( - "homeassistant.util.package.is_installed", return_value=False - ) as mock_is_installed, patch( - "homeassistant.util.package.install_package", side_effect=_mock_install_package - ) as mock_inst: + with ( + pytest.raises(RequirementsNotFound), + patch( + "homeassistant.util.package.is_installed", return_value=False + ) as mock_is_installed, + patch( + "homeassistant.util.package.install_package", + side_effect=_mock_install_package, + ) as mock_inst, + ): integration = await async_get_integration_with_requirements( hass, "test_component" ) @@ -353,11 +378,16 @@ async def test_get_integration_with_requirements_pip_install_fails_two_passes( ] # 2nd pass - with pytest.raises(RequirementsNotFound), patch( - "homeassistant.util.package.is_installed", return_value=False - ) as mock_is_installed, patch( - "homeassistant.util.package.install_package", side_effect=_mock_install_package - ) as mock_inst: + with ( + pytest.raises(RequirementsNotFound), + patch( + "homeassistant.util.package.is_installed", return_value=False + ) as mock_is_installed, + patch( + "homeassistant.util.package.install_package", + side_effect=_mock_install_package, + ) as mock_inst, + ): integration = await async_get_integration_with_requirements( hass, "test_component" ) @@ -371,11 +401,16 @@ async def test_get_integration_with_requirements_pip_install_fails_two_passes( # Now clear the history and so we try again async_clear_install_history(hass) - with pytest.raises(RequirementsNotFound), patch( - "homeassistant.util.package.is_installed", return_value=False - ) as mock_is_installed, patch( - "homeassistant.util.package.install_package", side_effect=_mock_install_package - ) as mock_inst: + with ( + pytest.raises(RequirementsNotFound), + patch( + "homeassistant.util.package.is_installed", return_value=False + ) as mock_is_installed, + patch( + "homeassistant.util.package.install_package", + side_effect=_mock_install_package, + ) as mock_inst, + ): integration = await async_get_integration_with_requirements( hass, "test_component" ) @@ -401,11 +436,14 @@ async def test_get_integration_with_requirements_pip_install_fails_two_passes( # Now clear the history and mock success async_clear_install_history(hass) - with patch( - "homeassistant.util.package.is_installed", return_value=False - ) as mock_is_installed, patch( - "homeassistant.util.package.install_package", return_value=True - ) as mock_inst: + with ( + patch( + "homeassistant.util.package.is_installed", return_value=False + ) as mock_is_installed, + patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_inst, + ): integration = await async_get_integration_with_requirements( hass, "test_component" ) @@ -497,13 +535,15 @@ async def test_install_with_wheels_index(hass: HomeAssistant) -> None: hass.config.skip_pip = False mock_integration(hass, MockModule("comp", requirements=["hello==1.0.0"])) - with patch("homeassistant.util.package.is_installed", return_value=False), patch( - "homeassistant.util.package.is_docker_env", return_value=True - ), patch("homeassistant.util.package.install_package") as mock_inst, patch.dict( - os.environ, {"WHEELS_LINKS": "https://wheels.hass.io/test"} - ), patch( - "os.path.dirname", - ) as mock_dir: + with ( + patch("homeassistant.util.package.is_installed", return_value=False), + patch("homeassistant.util.package.is_docker_env", return_value=True), + patch("homeassistant.util.package.install_package") as mock_inst, + patch.dict(os.environ, {"WHEELS_LINKS": "https://wheels.hass.io/test"}), + patch( + "os.path.dirname", + ) as mock_dir, + ): mock_dir.return_value = "ha_package_path" assert await setup.async_setup_component(hass, "comp", {}) assert "comp" in hass.config.components @@ -520,11 +560,13 @@ async def test_install_on_docker(hass: HomeAssistant) -> None: hass.config.skip_pip = False mock_integration(hass, MockModule("comp", requirements=["hello==1.0.0"])) - with patch("homeassistant.util.package.is_installed", return_value=False), patch( - "homeassistant.util.package.is_docker_env", return_value=True - ), patch("homeassistant.util.package.install_package") as mock_inst, patch( - "os.path.dirname" - ) as mock_dir, patch.dict(os.environ, env_without_wheel_links(), clear=True): + with ( + patch("homeassistant.util.package.is_installed", return_value=False), + patch("homeassistant.util.package.is_docker_env", return_value=True), + patch("homeassistant.util.package.install_package") as mock_inst, + patch("os.path.dirname") as mock_dir, + patch.dict(os.environ, env_without_wheel_links(), clear=True), + ): mock_dir.return_value = "ha_package_path" assert await setup.async_setup_component(hass, "comp", {}) assert "comp" in hass.config.components diff --git a/tests/test_runner.py b/tests/test_runner.py index c49093bd4d6..ab9b0e31e0d 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -38,9 +38,11 @@ async def test_setup_and_run_hass(hass: HomeAssistant, tmpdir: py.path.local) -> test_dir = tmpdir.mkdir("config") default_config = runner.RuntimeConfig(test_dir) - with patch("homeassistant.bootstrap.async_setup_hass", return_value=hass), patch( - "threading._shutdown" - ), patch("homeassistant.core.HomeAssistant.async_run") as mock_run: + with ( + patch("homeassistant.bootstrap.async_setup_hass", return_value=hass), + patch("threading._shutdown"), + patch("homeassistant.core.HomeAssistant.async_run") as mock_run, + ): await runner.setup_and_run_hass(default_config) assert threading._shutdown == thread.deadlock_safe_shutdown @@ -52,11 +54,12 @@ def test_run(hass: HomeAssistant, tmpdir: py.path.local) -> None: test_dir = tmpdir.mkdir("config") default_config = runner.RuntimeConfig(test_dir) - with patch.object(runner, "TASK_CANCELATION_TIMEOUT", 1), patch( - "homeassistant.bootstrap.async_setup_hass", return_value=hass - ), patch("threading._shutdown"), patch( - "homeassistant.core.HomeAssistant.async_run" - ) as mock_run: + with ( + patch.object(runner, "TASK_CANCELATION_TIMEOUT", 1), + patch("homeassistant.bootstrap.async_setup_hass", return_value=hass), + patch("threading._shutdown"), + patch("homeassistant.core.HomeAssistant.async_run") as mock_run, + ): runner.run(default_config) assert mock_run.called @@ -69,16 +72,19 @@ def test_run_executor_shutdown_throws( test_dir = tmpdir.mkdir("config") default_config = runner.RuntimeConfig(test_dir) - with patch.object(runner, "TASK_CANCELATION_TIMEOUT", 1), pytest.raises( - RuntimeError - ), patch("homeassistant.bootstrap.async_setup_hass", return_value=hass), patch( - "threading._shutdown" - ), patch( - "homeassistant.runner.InterruptibleThreadPoolExecutor.shutdown", - side_effect=RuntimeError, - ) as mock_shutdown, patch( - "homeassistant.core.HomeAssistant.async_run", - ) as mock_run: + with ( + patch.object(runner, "TASK_CANCELATION_TIMEOUT", 1), + pytest.raises(RuntimeError), + patch("homeassistant.bootstrap.async_setup_hass", return_value=hass), + patch("threading._shutdown"), + patch( + "homeassistant.runner.InterruptibleThreadPoolExecutor.shutdown", + side_effect=RuntimeError, + ) as mock_shutdown, + patch( + "homeassistant.core.HomeAssistant.async_run", + ) as mock_run, + ): runner.run(default_config) assert mock_shutdown.called @@ -112,10 +118,11 @@ def test_run_does_not_block_forever_with_shielded_task( await asyncio.sleep(0.1) return 0 - with patch.object(runner, "TASK_CANCELATION_TIMEOUT", 1), patch( - "homeassistant.bootstrap.async_setup_hass", return_value=hass - ), patch("threading._shutdown"), patch( - "homeassistant.core.HomeAssistant.async_run", _async_create_tasks + with ( + patch.object(runner, "TASK_CANCELATION_TIMEOUT", 1), + patch("homeassistant.bootstrap.async_setup_hass", return_value=hass), + patch("threading._shutdown"), + patch("homeassistant.core.HomeAssistant.async_run", _async_create_tasks), ): runner.run(default_config) @@ -159,14 +166,22 @@ def test__enable_posix_spawn() -> None: def _mock_sys_tags_musl() -> Iterator[packaging.tags.Tag]: yield from packaging.tags.parse_tag("cp311-cp311-musllinux_1_1_x86_64") - with patch.object(runner.subprocess, "_USE_POSIX_SPAWN", False), patch( - "homeassistant.runner.packaging.tags.sys_tags", side_effect=_mock_sys_tags_musl + with ( + patch.object(runner.subprocess, "_USE_POSIX_SPAWN", False), + patch( + "homeassistant.runner.packaging.tags.sys_tags", + side_effect=_mock_sys_tags_musl, + ), ): runner._enable_posix_spawn() assert runner.subprocess._USE_POSIX_SPAWN is True - with patch.object(runner.subprocess, "_USE_POSIX_SPAWN", False), patch( - "homeassistant.runner.packaging.tags.sys_tags", side_effect=_mock_sys_tags_any + with ( + patch.object(runner.subprocess, "_USE_POSIX_SPAWN", False), + patch( + "homeassistant.runner.packaging.tags.sys_tags", + side_effect=_mock_sys_tags_any, + ), ): runner._enable_posix_spawn() assert runner.subprocess._USE_POSIX_SPAWN is False diff --git a/tests/test_setup.py b/tests/test_setup.py index 387ac19c5d0..e3d9a322862 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -394,9 +394,10 @@ async def test_platform_specific_config_validation(hass: HomeAssistant) -> None: MockPlatform(platform_schema=platform_schema, setup_platform=mock_setup), ) - with assert_setup_component(0, "switch"), patch( - "homeassistant.setup.async_notify_setup_error" - ) as mock_notify: + with ( + assert_setup_component(0, "switch"), + patch("homeassistant.setup.async_notify_setup_error") as mock_notify, + ): assert await setup.async_setup_component( hass, "switch", @@ -409,9 +410,10 @@ async def test_platform_specific_config_validation(hass: HomeAssistant) -> None: hass.data.pop(setup.DATA_SETUP) hass.config.components.remove("switch") - with assert_setup_component(0), patch( - "homeassistant.setup.async_notify_setup_error" - ) as mock_notify: + with ( + assert_setup_component(0), + patch("homeassistant.setup.async_notify_setup_error") as mock_notify, + ): assert await setup.async_setup_component( hass, "switch", @@ -430,9 +432,10 @@ async def test_platform_specific_config_validation(hass: HomeAssistant) -> None: hass.data.pop(setup.DATA_SETUP) hass.config.components.remove("switch") - with assert_setup_component(1, "switch"), patch( - "homeassistant.setup.async_notify_setup_error" - ) as mock_notify: + with ( + assert_setup_component(1, "switch"), + patch("homeassistant.setup.async_notify_setup_error") as mock_notify, + ): assert await setup.async_setup_component( hass, "switch", diff --git a/tests/testing_config/custom_components/test/number.py b/tests/testing_config/custom_components/test/number.py index aa4d6fdcd8f..4de3dce233a 100644 --- a/tests/testing_config/custom_components/test/number.py +++ b/tests/testing_config/custom_components/test/number.py @@ -56,9 +56,9 @@ class MockRestoreNumber(MockNumberEntity, RestoreNumber): self._values["native_max_value"] = last_number_data.native_max_value self._values["native_min_value"] = last_number_data.native_min_value self._values["native_step"] = last_number_data.native_step - self._values[ - "native_unit_of_measurement" - ] = last_number_data.native_unit_of_measurement + self._values["native_unit_of_measurement"] = ( + last_number_data.native_unit_of_measurement + ) self._values["native_value"] = last_number_data.native_value diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/testing_config/custom_components/test/sensor.py index b6040227574..9ebf16b9dcd 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -138,6 +138,6 @@ class MockRestoreSensor(MockSensor, RestoreSensor): if (last_sensor_data := await self.async_get_last_sensor_data()) is None: return self._values["native_value"] = last_sensor_data.native_value - self._values[ - "native_unit_of_measurement" - ] = last_sensor_data.native_unit_of_measurement + self._values["native_unit_of_measurement"] = ( + last_sensor_data.native_unit_of_measurement + ) diff --git a/tests/util/test_async.py b/tests/util/test_async.py index fc0032204d7..1029d670703 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -51,28 +51,33 @@ async def test_check_loop_async() -> None: async def test_check_loop_async_integration(caplog: pytest.LogCaptureFixture) -> None: """Test check_loop detects and raises when called from event loop from integration context.""" - with pytest.raises(RuntimeError), patch( - "homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on" - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + pytest.raises(RuntimeError), + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="self.light.is_on", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): hasync.check_loop(banned_function) @@ -88,28 +93,32 @@ async def test_check_loop_async_integration_non_strict( caplog: pytest.LogCaptureFixture, ) -> None: """Test check_loop detects when called from event loop from integration context.""" - with patch( - "homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on" - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/homeassistant/components/hue/light.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="self.light.is_on", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): hasync.check_loop(banned_function, strict=False) @@ -123,28 +132,33 @@ async def test_check_loop_async_integration_non_strict( async def test_check_loop_async_custom(caplog: pytest.LogCaptureFixture) -> None: """Test check_loop detects when called from event loop with custom component context.""" - with pytest.raises(RuntimeError), patch( - "homeassistant.helpers.frame.linecache.getline", return_value="self.light.is_on" - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - Mock( - filename="/home/paulus/config/custom_components/hue/light.py", - lineno="23", - line="self.light.is_on", - ), - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + pytest.raises(RuntimeError), + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value="self.light.is_on", + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/config/custom_components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): hasync.check_loop(banned_function) @@ -248,9 +262,10 @@ async def test_callback_is_always_scheduled(hass: HomeAssistant) -> None: callback = MagicMock() hasync.shutdown_run_callback_threadsafe(hass.loop) - with patch.object( - hass.loop, "call_soon_threadsafe" - ) as mock_call_soon_threadsafe, pytest.raises(RuntimeError): + with ( + patch.object(hass.loop, "call_soon_threadsafe") as mock_call_soon_threadsafe, + pytest.raises(RuntimeError), + ): hasync.run_callback_threadsafe(hass.loop, callback) mock_call_soon_threadsafe.assert_called_once() diff --git a/tests/util/test_file.py b/tests/util/test_file.py index 64c74b73dc3..2371998b1b9 100644 --- a/tests/util/test_file.py +++ b/tests/util/test_file.py @@ -37,8 +37,11 @@ def test_write_utf8_file_fails_at_creation(tmpdir: py.path.local) -> None: test_dir = tmpdir.mkdir("files") test_file = Path(test_dir / "test.json") - with pytest.raises(WriteError), patch( - "homeassistant.util.file.tempfile.NamedTemporaryFile", side_effect=OSError + with ( + pytest.raises(WriteError), + patch( + "homeassistant.util.file.tempfile.NamedTemporaryFile", side_effect=OSError + ), ): write_utf8_file(test_file, '{"some":"data"}', False) @@ -52,8 +55,9 @@ def test_write_utf8_file_fails_at_rename( test_dir = tmpdir.mkdir("files") test_file = Path(test_dir / "test.json") - with pytest.raises(WriteError), patch( - "homeassistant.util.file.os.replace", side_effect=OSError + with ( + pytest.raises(WriteError), + patch("homeassistant.util.file.os.replace", side_effect=OSError), ): write_utf8_file(test_file, '{"some":"data"}', False) @@ -69,9 +73,11 @@ def test_write_utf8_file_fails_at_rename_and_remove( test_dir = tmpdir.mkdir("files") test_file = Path(test_dir / "test.json") - with pytest.raises(WriteError), patch( - "homeassistant.util.file.os.remove", side_effect=OSError - ), patch("homeassistant.util.file.os.replace", side_effect=OSError): + with ( + pytest.raises(WriteError), + patch("homeassistant.util.file.os.remove", side_effect=OSError), + patch("homeassistant.util.file.os.replace", side_effect=OSError), + ): write_utf8_file(test_file, '{"some":"data"}', False) assert "File replacement cleanup failed" in caplog.text @@ -82,8 +88,9 @@ def test_write_utf8_file_atomic_fails(tmpdir: py.path.local) -> None: test_dir = tmpdir.mkdir("files") test_file = Path(test_dir / "test.json") - with pytest.raises(WriteError), patch( - "homeassistant.util.file.AtomicWriter.open", side_effect=OSError + with ( + pytest.raises(WriteError), + patch("homeassistant.util.file.AtomicWriter.open", side_effect=OSError), ): write_utf8_file_atomic(test_file, '{"some":"data"}', False) diff --git a/tests/util/test_logging.py b/tests/util/test_logging.py index 2473485e103..53342e8d1bd 100644 --- a/tests/util/test_logging.py +++ b/tests/util/test_logging.py @@ -27,8 +27,9 @@ async def test_logging_with_queue_handler() -> None: handler.emit(log_record) - with pytest.raises(asyncio.CancelledError), patch.object( - handler, "enqueue", side_effect=asyncio.CancelledError + with ( + pytest.raises(asyncio.CancelledError), + patch.object(handler, "enqueue", side_effect=asyncio.CancelledError), ): handler.emit(log_record) @@ -36,16 +37,18 @@ async def test_logging_with_queue_handler() -> None: handler.handle(log_record) emit_mock.assert_called_once() - with patch.object(handler, "filter") as filter_mock, patch.object( - handler, "emit" - ) as emit_mock: + with ( + patch.object(handler, "filter") as filter_mock, + patch.object(handler, "emit") as emit_mock, + ): filter_mock.return_value = False handler.handle(log_record) emit_mock.assert_not_called() - with patch.object(handler, "enqueue", side_effect=OSError), patch.object( - handler, "handleError" - ) as mock_handle_error: + with ( + patch.object(handler, "enqueue", side_effect=OSError), + patch.object(handler, "handleError") as mock_handle_error, + ): handler.emit(log_record) mock_handle_error.assert_called_once() diff --git a/tests/util/test_timeout.py b/tests/util/test_timeout.py index 02a06632ff6..d49008d608b 100644 --- a/tests/util/test_timeout.py +++ b/tests/util/test_timeout.py @@ -46,8 +46,9 @@ async def test_simple_zone_timeout_freeze_inside_executor_job( with timeout.freeze("recorder"): time.sleep(0.3) - async with timeout.async_timeout(1.0), timeout.async_timeout( - 0.2, zone_name="recorder" + async with ( + timeout.async_timeout(1.0), + timeout.async_timeout(0.2, zone_name="recorder"), ): await hass.async_add_executor_job(_some_sync_work) @@ -76,8 +77,9 @@ async def test_mix_global_timeout_freeze_and_zone_freeze_inside_executor_job( with timeout.freeze("recorder"): time.sleep(0.3) - async with timeout.async_timeout(0.1), timeout.async_timeout( - 0.2, zone_name="recorder" + async with ( + timeout.async_timeout(0.1), + timeout.async_timeout(0.2, zone_name="recorder"), ): await hass.async_add_executor_job(_some_sync_work) @@ -110,9 +112,10 @@ async def test_mix_global_timeout_freeze_and_zone_freeze_other_zone_inside_execu with pytest.raises(TimeoutError): async with timeout.async_timeout(0.1): - async with timeout.async_timeout( - 0.2, zone_name="recorder" - ), timeout.async_timeout(0.2, zone_name="not_recorder"): + async with ( + timeout.async_timeout(0.2, zone_name="recorder"), + timeout.async_timeout(0.2, zone_name="not_recorder"), + ): await hass.async_add_executor_job(_some_sync_work) @@ -214,9 +217,11 @@ async def test_mix_zone_timeout_freeze_and_global_freeze() -> None: """Test a mix zone timeout freeze and global freeze.""" timeout = TimeoutManager() - async with timeout.async_timeout(0.2, "test"), timeout.async_freeze( - "test" - ), timeout.async_freeze(): + async with ( + timeout.async_timeout(0.2, "test"), + timeout.async_freeze("test"), + timeout.async_freeze(), + ): await asyncio.sleep(0.3) @@ -224,9 +229,11 @@ async def test_mix_global_and_zone_timeout_freeze_() -> None: """Test a mix zone timeout freeze and global freeze.""" timeout = TimeoutManager() - async with timeout.async_timeout( - 0.2, "test" - ), timeout.async_freeze(), timeout.async_freeze("test"): + async with ( + timeout.async_timeout(0.2, "test"), + timeout.async_freeze(), + timeout.async_freeze("test"), + ): await asyncio.sleep(0.3) diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index 185c4cdb22c..dba8e9b8017 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -517,9 +517,9 @@ class TestSecrets(unittest.TestCase): def test_secrets_are_not_dict(self): """Did secrets handle non-dict file.""" - FILES[ - self._secret_path - ] = "- http_pw: pwhttp\n comp1_un: un1\n comp1_pw: pw1\n" + FILES[self._secret_path] = ( + "- http_pw: pwhttp\n comp1_un: un1\n comp1_pw: pw1\n" + ) with pytest.raises(HomeAssistantError): load_yaml( self._yaml_path, @@ -611,24 +611,28 @@ def mock_integration_frame() -> Generator[Mock, None, None]: lineno="23", line="self.light.is_on", ) - with patch( - "homeassistant.helpers.frame.linecache.getline", return_value=correct_frame.line - ), patch( - "homeassistant.helpers.frame.get_current_frame", - return_value=extract_stack_to_frame( - [ - Mock( - filename="/home/paulus/homeassistant/core.py", - lineno="23", - line="do_something()", - ), - correct_frame, - Mock( - filename="/home/paulus/aiohue/lights.py", - lineno="2", - line="something()", - ), - ] + with ( + patch( + "homeassistant.helpers.frame.linecache.getline", + return_value=correct_frame.line, + ), + patch( + "homeassistant.helpers.frame.get_current_frame", + return_value=extract_stack_to_frame( + [ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ] + ), ), ): yield correct_frame @@ -652,8 +656,9 @@ async def test_deprecated_loaders( message: str, ) -> None: """Test instantiating the deprecated yaml loaders logs a warning.""" - with pytest.raises(TypeError), patch( - "homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set() + with ( + pytest.raises(TypeError), + patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()), ): loader_class() assert (f"Detected that integration 'hue' uses deprecated {message}") in caplog.text From 9580adfde90b04235bd338a45cf6c147286a2ae4 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 26 Mar 2024 00:17:16 +0100 Subject: [PATCH 1505/1691] Use new `setup_test_component_platform` helper instead of test fixture for light platform (#114200) --- tests/components/conftest.py | 10 +- tests/components/conversation/test_init.py | 18 +-- tests/components/group/test_light.py | 83 +++++-------- tests/components/light/common.py | 28 +---- .../components/light/test_device_condition.py | 6 +- tests/components/light/test_init.py | 113 +++++++----------- tests/components/scene/test_init.py | 11 +- 7 files changed, 96 insertions(+), 173 deletions(-) diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 8b21f8cd1a8..4669e17c8e7 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -9,7 +9,7 @@ import pytest from homeassistant.const import STATE_OFF, STATE_ON if TYPE_CHECKING: - from tests.components.light.common import MockLight, SetupLightPlatformCallable + from tests.components.light.common import MockLight @pytest.fixture(scope="session", autouse=True) @@ -118,11 +118,3 @@ def mock_light_entities() -> list["MockLight"]: MockLight("Ceiling", STATE_OFF), MockLight(None, STATE_OFF), ] - - -@pytest.fixture -def setup_light_platform() -> "SetupLightPlatformCallable": - """Return a callable to set up the mock light entity component.""" - from tests.components.light.common import setup_light_platform - - return setup_light_platform diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index b2d05e976d6..1ef8c8b30d7 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -24,8 +24,13 @@ from homeassistant.setup import async_setup_component from . import expose_entity, expose_new -from tests.common import MockConfigEntry, MockUser, async_mock_service -from tests.components.light.common import MockLight, SetupLightPlatformCallable +from tests.common import ( + MockConfigEntry, + MockUser, + async_mock_service, + setup_test_component_platform, +) +from tests.components.light.common import MockLight from tests.typing import ClientSessionGenerator, WebSocketGenerator AGENT_ID_OPTIONS = [None, conversation.HOME_ASSISTANT_AGENT] @@ -257,7 +262,6 @@ async def test_http_processing_intent_entity_renamed( hass_client: ClientSessionGenerator, hass_admin_user: MockUser, entity_registry: er.EntityRegistry, - setup_light_platform: SetupLightPlatformCallable, snapshot: SnapshotAssertion, ) -> None: """Test processing intent via HTTP API with entities renamed later. @@ -268,7 +272,7 @@ async def test_http_processing_intent_entity_renamed( entity = MockLight("kitchen light", "on") entity._attr_unique_id = "1234" entity.entity_id = "light.kitchen" - setup_light_platform(hass, [entity]) + setup_test_component_platform(hass, LIGHT_DOMAIN, [entity]) assert await async_setup_component( hass, @@ -346,7 +350,6 @@ async def test_http_processing_intent_entity_exposed( hass_client: ClientSessionGenerator, hass_admin_user: MockUser, entity_registry: er.EntityRegistry, - setup_light_platform: SetupLightPlatformCallable, snapshot: SnapshotAssertion, ) -> None: """Test processing intent via HTTP API with manual expose. @@ -357,7 +360,7 @@ async def test_http_processing_intent_entity_exposed( entity = MockLight("kitchen light", "on") entity._attr_unique_id = "1234" entity.entity_id = "light.kitchen" - setup_light_platform(hass, [entity]) + setup_test_component_platform(hass, LIGHT_DOMAIN, [entity]) assert await async_setup_component( hass, @@ -449,7 +452,6 @@ async def test_http_processing_intent_conversion_not_expose_new( hass_client: ClientSessionGenerator, hass_admin_user: MockUser, entity_registry: er.EntityRegistry, - setup_light_platform: SetupLightPlatformCallable, snapshot: SnapshotAssertion, ) -> None: """Test processing intent via HTTP API when not exposing new entities.""" @@ -459,7 +461,7 @@ async def test_http_processing_intent_conversion_not_expose_new( entity = MockLight("kitchen light", "on") entity._attr_unique_id = "1234" entity.entity_id = "light.kitchen" - setup_light_platform(hass, [entity]) + setup_test_component_platform(hass, LIGHT_DOMAIN, [entity]) assert await async_setup_component( hass, diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index dca6002ccb7..af8556b5450 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -44,8 +44,12 @@ from homeassistant.core import Event, HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component -from tests.common import async_capture_events, get_fixture_path -from tests.components.light.common import MockLight, SetupLightPlatformCallable +from tests.common import ( + async_capture_events, + get_fixture_path, + setup_test_component_platform, +) +from tests.components.light.common import MockLight async def test_default_state( @@ -261,15 +265,13 @@ async def test_state_reporting_all(hass: HomeAssistant) -> None: assert hass.states.get("light.light_group").state == STATE_UNAVAILABLE -async def test_brightness( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_brightness(hass: HomeAssistant) -> None: """Test brightness reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.BRIGHTNESS} @@ -334,15 +336,13 @@ async def test_brightness( assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["brightness"] -async def test_color_hs( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_hs(hass: HomeAssistant) -> None: """Test hs color reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.HS} @@ -406,15 +406,13 @@ async def test_color_hs( assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 -async def test_color_rgb( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_rgb(hass: HomeAssistant) -> None: """Test rgbw color reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGB} @@ -480,15 +478,13 @@ async def test_color_rgb( assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 -async def test_color_rgbw( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_rgbw(hass: HomeAssistant) -> None: """Test rgbw color reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGBW} @@ -554,15 +550,13 @@ async def test_color_rgbw( assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 -async def test_color_rgbww( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_rgbww(hass: HomeAssistant) -> None: """Test rgbww color reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.RGBWW} @@ -628,15 +622,13 @@ async def test_color_rgbww( assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 -async def test_white( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_white(hass: HomeAssistant) -> None: """Test white reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.HS, ColorMode.WHITE} @@ -687,15 +679,13 @@ async def test_white( assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs", "white"] -async def test_color_temp( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_temp(hass: HomeAssistant) -> None: """Test color temp reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -758,16 +748,14 @@ async def test_color_temp( assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp"] -async def test_emulated_color_temp_group( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_emulated_color_temp_group(hass: HomeAssistant) -> None: """Test emulated color temperature in a group.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), MockLight("test3", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -824,9 +812,7 @@ async def test_emulated_color_temp_group( assert state.attributes[ATTR_HS_COLOR] == (27.001, 19.243) -async def test_min_max_mireds( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_min_max_mireds(hass: HomeAssistant) -> None: """Test min/max mireds reporting. min/max mireds is reported both when light is on and off @@ -835,7 +821,7 @@ async def test_min_max_mireds( MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -1005,16 +991,14 @@ async def test_effect(hass: HomeAssistant) -> None: assert state.attributes[ATTR_EFFECT] == "Random" -async def test_supported_color_modes( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_supported_color_modes(hass: HomeAssistant) -> None: """Test supported_color_modes reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), MockLight("test3", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} @@ -1055,16 +1039,14 @@ async def test_supported_color_modes( } -async def test_color_mode( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_mode(hass: HomeAssistant) -> None: """Test color_mode reporting.""" entities = [ MockLight("test1", STATE_ON), MockLight("test2", STATE_OFF), MockLight("test3", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} @@ -1130,9 +1112,7 @@ async def test_color_mode( assert state.attributes[ATTR_COLOR_MODE] == ColorMode.HS -async def test_color_mode2( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_color_mode2(hass: HomeAssistant) -> None: """Test onoff color_mode and brightness are given lowest priority.""" entities = [ MockLight("test1", STATE_ON), @@ -1142,7 +1122,7 @@ async def test_color_mode2( MockLight("test5", STATE_ON), MockLight("test6", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity = entities[0] entity.supported_color_modes = {ColorMode.COLOR_TEMP} @@ -1256,7 +1236,6 @@ async def test_supported_features(hass: HomeAssistant) -> None: @pytest.mark.parametrize("supported_color_modes", [ColorMode.HS, ColorMode.RGB]) async def test_service_calls( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, supported_color_modes, ) -> None: """Test service calls.""" @@ -1265,7 +1244,7 @@ async def test_service_calls( MockLight("ceiling_lights", STATE_OFF), MockLight("kitchen_lights", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, LIGHT_DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {supported_color_modes} diff --git a/tests/components/light/common.py b/tests/components/light/common.py index 62b1e199d5d..1d5ad343bb9 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -4,7 +4,6 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ -from collections.abc import Callable from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -33,12 +32,9 @@ from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, ) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.loader import bind_hass -from tests.common import MockPlatform, MockToggleEntity, mock_platform +from tests.common import MockToggleEntity @bind_hass @@ -288,25 +284,3 @@ class MockLight(MockToggleEntity, LightEntity): setattr(self, "brightness", value) if key in TURN_ON_ARG_TO_COLOR_MODE: self._attr_color_mode = TURN_ON_ARG_TO_COLOR_MODE[key] - - -SetupLightPlatformCallable = Callable[[HomeAssistant, list[MockLight]], None] - - -def setup_light_platform(hass: HomeAssistant, entities: list[MockLight]) -> None: - """Set up the mock light entity platform.""" - - async def async_setup_platform( - hass: HomeAssistant, - config: ConfigType, - async_add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, - ) -> None: - """Set up test light platform.""" - async_add_entities(entities) - - mock_platform( - hass, - f"test.{DOMAIN}", - MockPlatform(async_setup_platform=async_setup_platform), - ) diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index fe556fc3207..16237547bc9 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -21,8 +21,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) -from tests.components.light.common import MockLight, SetupLightPlatformCallable +from tests.components.light.common import MockLight @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -324,7 +325,6 @@ async def test_if_fires_on_for_condition( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test for firing if condition is on with delay.""" @@ -344,7 +344,7 @@ async def test_if_fires_on_for_condition( point2 = point1 + timedelta(seconds=10) point3 = point2 + timedelta(seconds=10) - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, DOMAIN, mock_light_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 2ebf8af52bb..6a04d5e33cc 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -22,8 +22,13 @@ from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.setup import async_setup_component import homeassistant.util.color as color_util -from tests.common import MockEntityPlatform, MockUser, async_mock_service -from tests.components.light.common import MockLight, SetupLightPlatformCallable +from tests.common import ( + MockEntityPlatform, + MockUser, + async_mock_service, + setup_test_component_platform, +) +from tests.components.light.common import MockLight orig_Profiles = light.Profiles @@ -111,11 +116,10 @@ async def test_methods(hass: HomeAssistant) -> None: async def test_services( hass: HomeAssistant, mock_light_profiles, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test the provided services.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -512,11 +516,10 @@ async def test_light_profiles( profile_name, expected_data, last_call, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test light profiles.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) profile_mock_data = { "test": (0.4, 0.6, 100, 0), @@ -561,11 +564,10 @@ async def test_light_profiles( async def test_default_profiles_group( hass: HomeAssistant, mock_light_profiles, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test default turn-on light profile for all lights.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -787,11 +789,10 @@ async def test_default_profiles_light( extra_call_params, expected_params_state_was_off, expected_params_state_was_on, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test default turn-on light profile for a specific light.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} @@ -857,11 +858,10 @@ async def test_default_profiles_light( async def test_light_context( hass: HomeAssistant, hass_admin_user: MockUser, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test that light context works.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -886,11 +886,10 @@ async def test_light_context( async def test_light_turn_on_auth( hass: HomeAssistant, hass_read_only_user: MockUser, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test that light context works.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -910,16 +909,14 @@ async def test_light_turn_on_auth( ) -async def test_light_brightness_step( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_brightness_step(hass: HomeAssistant) -> None: """Test that light context works.""" entities = [ MockLight("Test_0", STATE_ON), MockLight("Test_1", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_features = light.SUPPORT_BRIGHTNESS @@ -986,11 +983,10 @@ async def test_light_brightness_step( async def test_light_brightness_pct_conversion( hass: HomeAssistant, enable_custom_integrations: None, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test that light brightness percent conversion.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) entity = mock_light_entities[0] entity.supported_features = light.SUPPORT_BRIGHTNESS @@ -1147,7 +1143,7 @@ invalid_no_brightness_no_color_no_transition,,, @pytest.mark.parametrize("light_state", [STATE_ON, STATE_OFF]) async def test_light_backwards_compatibility_supported_color_modes( - hass: HomeAssistant, light_state, setup_light_platform: SetupLightPlatformCallable + hass: HomeAssistant, light_state ) -> None: """Test supported_color_modes if not implemented by the entity.""" entities = [ @@ -1186,7 +1182,7 @@ async def test_light_backwards_compatibility_supported_color_modes( entity4.supported_color_modes = None entity4.color_mode = None - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1230,9 +1226,7 @@ async def test_light_backwards_compatibility_supported_color_modes( assert state.attributes["color_mode"] == light.ColorMode.UNKNOWN -async def test_light_backwards_compatibility_color_mode( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_backwards_compatibility_color_mode(hass: HomeAssistant) -> None: """Test color_mode if not implemented by the entity.""" entities = [ MockLight("Test_0", STATE_ON), @@ -1275,7 +1269,7 @@ async def test_light_backwards_compatibility_color_mode( entity4.hs_color = (240, 100) entity4.color_temp_kelvin = 10000 - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1308,14 +1302,12 @@ async def test_light_backwards_compatibility_color_mode( assert state.attributes["color_mode"] == light.ColorMode.HS -async def test_light_service_call_rgbw( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_service_call_rgbw(hass: HomeAssistant) -> None: """Test rgbw functionality in service calls.""" entity0 = MockLight("Test_rgbw", STATE_ON) entity0.supported_color_modes = {light.ColorMode.RGBW} - setup_light_platform(hass, [entity0]) + setup_test_component_platform(hass, light.DOMAIN, [entity0]) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1338,9 +1330,7 @@ async def test_light_service_call_rgbw( assert data == {"brightness": 255, "rgbw_color": (10, 20, 30, 40)} -async def test_light_state_off( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_state_off(hass: HomeAssistant) -> None: """Test rgbw color conversion in state updates.""" entities = [ MockLight("Test_onoff", STATE_OFF), @@ -1348,7 +1338,7 @@ async def test_light_state_off( MockLight("Test_ct", STATE_OFF), MockLight("Test_rgbw", STATE_OFF), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.ONOFF} @@ -1411,12 +1401,10 @@ async def test_light_state_off( } -async def test_light_state_rgbw( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_state_rgbw(hass: HomeAssistant) -> None: """Test rgbw color conversion in state updates.""" entity0 = MockLight("Test_rgbw", STATE_ON) - setup_light_platform(hass, [entity0]) + setup_test_component_platform(hass, light.DOMAIN, [entity0]) entity0.brightness = 255 entity0.supported_color_modes = {light.ColorMode.RGBW} @@ -1444,12 +1432,10 @@ async def test_light_state_rgbw( } -async def test_light_state_rgbww( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_state_rgbww(hass: HomeAssistant) -> None: """Test rgbww color conversion in state updates.""" entity0 = MockLight("Test_rgbww", STATE_ON) - setup_light_platform(hass, [entity0]) + setup_test_component_platform(hass, light.DOMAIN, [entity0]) entity0.supported_color_modes = {light.ColorMode.RGBWW} entity0.color_mode = light.ColorMode.RGBWW @@ -1477,9 +1463,7 @@ async def test_light_state_rgbww( } -async def test_light_service_call_color_conversion( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None: """Test color conversion in service calls.""" entities = [ MockLight("Test_hs", STATE_ON), @@ -1491,7 +1475,7 @@ async def test_light_service_call_color_conversion( MockLight("Test_rgbww", STATE_ON), MockLight("Test_temperature", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -1923,7 +1907,7 @@ async def test_light_service_call_color_conversion( async def test_light_service_call_color_conversion_named_tuple( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable + hass: HomeAssistant, ) -> None: """Test a named tuple (RGBColor) is handled correctly.""" entities = [ @@ -1935,7 +1919,7 @@ async def test_light_service_call_color_conversion_named_tuple( MockLight("Test_rgbw", STATE_ON), MockLight("Test_rgbww", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -2002,16 +1986,14 @@ async def test_light_service_call_color_conversion_named_tuple( assert data == {"brightness": 64, "rgbww_color": (128, 0, 0, 0, 0)} -async def test_light_service_call_color_temp_emulation( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_service_call_color_temp_emulation(hass: HomeAssistant) -> None: """Test color conversion in service calls.""" entities = [ MockLight("Test_hs_ct", STATE_ON), MockLight("Test_hs", STATE_ON), MockLight("Test_hs_white", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.COLOR_TEMP, light.ColorMode.HS} @@ -2062,15 +2044,13 @@ async def test_light_service_call_color_temp_emulation( assert data == {"brightness": 255, "hs_color": (27.001, 19.243)} -async def test_light_service_call_color_temp_conversion( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_service_call_color_temp_conversion(hass: HomeAssistant) -> None: """Test color temp conversion in service calls.""" entities = [ MockLight("Test_rgbww_ct", STATE_ON), MockLight("Test_rgbww", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = { @@ -2195,15 +2175,13 @@ async def test_light_service_call_color_temp_conversion( assert data == {"brightness": 255, "rgbww_color": (0, 0, 0, 66, 189)} -async def test_light_mired_color_temp_conversion( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_mired_color_temp_conversion(hass: HomeAssistant) -> None: """Test color temp conversion from K to legacy mired.""" entities = [ MockLight("Test_rgbww_ct", STATE_ON), MockLight("Test_rgbww", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = { @@ -2243,13 +2221,11 @@ async def test_light_mired_color_temp_conversion( assert state.attributes["color_temp_kelvin"] == 3500 -async def test_light_service_call_white_mode( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_service_call_white_mode(hass: HomeAssistant) -> None: """Test color_mode white in service calls.""" entity0 = MockLight("Test_white", STATE_ON) entity0.supported_color_modes = {light.ColorMode.HS, light.ColorMode.WHITE} - setup_light_platform(hass, [entity0]) + setup_test_component_platform(hass, light.DOMAIN, [entity0]) assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -2344,9 +2320,7 @@ async def test_light_service_call_white_mode( assert data == {"white": 128} -async def test_light_state_color_conversion( - hass: HomeAssistant, setup_light_platform: SetupLightPlatformCallable -) -> None: +async def test_light_state_color_conversion(hass: HomeAssistant) -> None: """Test color conversion in state updates.""" entities = [ MockLight("Test_hs", STATE_ON), @@ -2354,7 +2328,7 @@ async def test_light_state_color_conversion( MockLight("Test_xy", STATE_ON), MockLight("Test_legacy", STATE_ON), ] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) entity0 = entities[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -2415,11 +2389,10 @@ async def test_light_state_color_conversion( async def test_services_filter_parameters( hass: HomeAssistant, mock_light_profiles, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> None: """Test turn_on and turn_off filters unsupported parameters.""" - setup_light_platform(hass, mock_light_entities) + setup_test_component_platform(hass, light.DOMAIN, mock_light_entities) assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index ad8e478105e..a878b27614e 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -18,19 +18,22 @@ from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from homeassistant.util.yaml import loader as yaml_loader -from tests.common import async_mock_service, mock_restore_cache -from tests.components.light.common import MockLight, SetupLightPlatformCallable +from tests.common import ( + async_mock_service, + mock_restore_cache, + setup_test_component_platform, +) +from tests.components.light.common import MockLight @pytest.fixture(autouse=True) def entities( hass: HomeAssistant, - setup_light_platform: SetupLightPlatformCallable, mock_light_entities: list[MockLight], ) -> list[MockLight]: """Initialize the test light.""" entities = mock_light_entities[0:2] - setup_light_platform(hass, entities) + setup_test_component_platform(hass, light.DOMAIN, entities) return entities From 345fa755625713f6f8e09bb47a9a21b1f388d47f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 14:39:16 -1000 Subject: [PATCH 1506/1691] Bump SQLAlchemy to 2.0.29 (#114208) --- homeassistant/components/recorder/manifest.json | 2 +- homeassistant/components/sql/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index feb79319cd9..e5b20cfd3b0 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "quality_scale": "internal", "requirements": [ - "SQLAlchemy==2.0.28", + "SQLAlchemy==2.0.29", "fnv-hash-fast==0.5.0", "psutil-home-assistant==0.0.1" ] diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index 0935b9ae5e5..dd44af89237 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sql", "iot_class": "local_polling", - "requirements": ["SQLAlchemy==2.0.28", "sqlparse==0.4.4"] + "requirements": ["SQLAlchemy==2.0.29", "sqlparse==0.4.4"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7847a6416af..8355bbb50bd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -52,7 +52,7 @@ PyTurboJPEG==1.7.1 pyudev==0.23.2 PyYAML==6.0.1 requests==2.31.0 -SQLAlchemy==2.0.28 +SQLAlchemy==2.0.29 typing-extensions>=4.11.0rc1,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 diff --git a/pyproject.toml b/pyproject.toml index d6367692b01..ac4ad9ffafa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ dependencies = [ "python-slugify==8.0.4", "PyYAML==6.0.1", "requests==2.31.0", - "SQLAlchemy==2.0.28", + "SQLAlchemy==2.0.29", "typing-extensions>=4.11.0rc1,<5.0", "ulid-transform==0.9.0", # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 diff --git a/requirements.txt b/requirements.txt index acd9b2b51ab..0026f39caf0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ psutil-home-assistant==0.0.1 python-slugify==8.0.4 PyYAML==6.0.1 requests==2.31.0 -SQLAlchemy==2.0.28 +SQLAlchemy==2.0.29 typing-extensions>=4.11.0rc1,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 diff --git a/requirements_all.txt b/requirements_all.txt index dbd15073e62..9f87a7f2fe6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -125,7 +125,7 @@ RtmAPI==0.7.2 # homeassistant.components.recorder # homeassistant.components.sql -SQLAlchemy==2.0.28 +SQLAlchemy==2.0.29 # homeassistant.components.tami4 Tami4EdgeAPI==2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 74e85ab6b2e..6d6ca8537ba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -110,7 +110,7 @@ RtmAPI==0.7.2 # homeassistant.components.recorder # homeassistant.components.sql -SQLAlchemy==2.0.28 +SQLAlchemy==2.0.29 # homeassistant.components.tami4 Tami4EdgeAPI==2.1 From cabc4f797ae4f3b839e60248cb6da216acfe22b6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 15:49:54 -1000 Subject: [PATCH 1507/1691] Preload storage for integrations we know we are going to setup (#114192) --- homeassistant/bootstrap.py | 34 ++++ homeassistant/core.py | 9 +- homeassistant/helpers/storage.py | 160 ++++++++++++++- tests/common.py | 6 +- tests/components/light/common.py | 1 - tests/helpers/test_storage.py | 326 +++++++++++++++++++++++++++++-- tests/test_core.py | 22 ++- 7 files changed, 527 insertions(+), 31 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index d1e5000cbd7..03c0de1ff62 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -78,6 +78,7 @@ from .helpers import ( translation, ) from .helpers.dispatcher import async_dispatcher_send +from .helpers.storage import get_internal_store_manager from .helpers.system_info import async_get_system_info from .helpers.typing import ConfigType from .setup import ( @@ -203,6 +204,27 @@ SETUP_ORDER = ( ("debugger", DEBUGGER_INTEGRATIONS), ) +# +# Storage keys we are likely to load during startup +# in order of when we expect to load them. +# +# If they do not exist they will not be loaded +# +PRELOAD_STORAGE = [ + "core.network", + "http.auth", + "image", + "lovelace_dashboards", + "lovelace_resources", + "core.uuid", + "lovelace.map", + "bluetooth.passive_update_processor", + "bluetooth.remote_scanners", + "assist_pipeline.pipelines", + "core.analytics", + "auth_module.totp", +] + async def async_setup_hass( runtime_config: RuntimeConfig, @@ -346,6 +368,7 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None: entity.async_setup(hass) template.async_setup(hass) await asyncio.gather( + create_eager_task(get_internal_store_manager(hass).async_initialize()), create_eager_task(area_registry.async_load(hass)), create_eager_task(category_registry.async_load(hass)), create_eager_task(device_registry.async_load(hass)), @@ -840,6 +863,17 @@ async def _async_resolve_domains_to_setup( eager_start=True, ) + # Preload storage for all integrations we are going to set up + # so we do not have to wait for it to be loaded when we need it + # in the setup process. + hass.async_create_background_task( + get_internal_store_manager(hass).async_preload( + [*PRELOAD_STORAGE, *domains_to_setup] + ), + "preload storage", + eager_start=True, + ) + return domains_to_setup, integration_cache diff --git a/homeassistant/core.py b/homeassistant/core.py index 7d229e3727f..3b52b020957 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -392,6 +392,8 @@ class HomeAssistant: # pylint: disable-next=import-outside-toplevel from . import loader + # This is a dictionary that any component can store any data on. + self.data: dict[str, Any] = {} self.loop = asyncio.get_running_loop() self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() @@ -401,8 +403,6 @@ class HomeAssistant: self.config = Config(self, config_dir) self.components = loader.Components(self) self.helpers = loader.Helpers(self) - # This is a dictionary that any component can store any data on. - self.data: dict[str, Any] = {} self.state: CoreState = CoreState.not_running self.exit_code: int = 0 # If not None, use to signal end-of-loop @@ -2590,7 +2590,7 @@ class Config: """Initialize a new config object.""" self.hass = hass - self._store = self._ConfigStore(self.hass) + self._store = self._ConfigStore(self.hass, config_dir) self.latitude: float = 0 self.longitude: float = 0 @@ -2857,7 +2857,7 @@ class Config: class _ConfigStore(Store[dict[str, Any]]): """Class to help storing Config data.""" - def __init__(self, hass: HomeAssistant) -> None: + def __init__(self, hass: HomeAssistant, config_dir: str) -> None: """Initialize storage class.""" super().__init__( hass, @@ -2866,6 +2866,7 @@ class Config: private=True, atomic_writes=True, minor_version=CORE_STORAGE_MINOR_VERSION, + config_dir=config_dir, ) self._original_unit_system: str | None = None # from old store 1.1 diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index cdbea143e18..2413a53e605 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -3,16 +3,21 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, Mapping, Sequence +from collections.abc import Callable, Iterable, Mapping, Sequence from contextlib import suppress from copy import deepcopy import inspect from json import JSONDecodeError, JSONEncoder import logging import os +from pathlib import Path from typing import TYPE_CHECKING, Any, Generic, TypeVar -from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE +from homeassistant.const import ( + EVENT_HOMEASSISTANT_FINAL_WRITE, + EVENT_HOMEASSISTANT_STARTED, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import ( CALLBACK_TYPE, DOMAIN as HOMEASSISTANT_DOMAIN, @@ -43,7 +48,9 @@ STORAGE_DIR = ".storage" _LOGGER = logging.getLogger(__name__) STORAGE_SEMAPHORE = "storage_semaphore" +STORAGE_MANAGER = "storage_manager" +MANAGER_CLEANUP_DELAY = 60 _T = TypeVar("_T", bound=Mapping[str, Any] | Sequence[Any]) @@ -88,6 +95,147 @@ async def async_migrator( return config +def get_internal_store_manager( + hass: HomeAssistant, config_dir: str | None = None +) -> _StoreManager: + """Get the store manager. + + This function is not part of the API and should only be + used in the Home Assistant core internals. It is not + guaranteed to be stable. + """ + if STORAGE_MANAGER not in hass.data: + manager = _StoreManager(hass, config_dir or hass.config.config_dir) + hass.data[STORAGE_MANAGER] = manager + return hass.data[STORAGE_MANAGER] + + +class _StoreManager: + """Class to help storing data. + + The store manager is used to cache and manage storage files. + """ + + def __init__(self, hass: HomeAssistant, config_dir: str) -> None: + """Initialize storage manager class.""" + self._hass = hass + self._invalidated: set[str] = set() + self._files: set[str] | None = None + self._data_preload: dict[str, json_util.JsonValueType] = {} + self._storage_path: Path = Path(config_dir).joinpath(STORAGE_DIR) + self._cancel_cleanup: asyncio.TimerHandle | None = None + + async def async_initialize(self) -> None: + """Initialize the storage manager.""" + hass = self._hass + await hass.async_add_executor_job(self._initialize_files) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, + self._async_schedule_cleanup, + run_immediately=True, + ) + + @callback + def async_invalidate(self, key: str) -> None: + """Invalidate cache. + + Store calls this when its going to save data + to ensure that the cache is not used after that. + """ + if "/" not in key: + self._invalidated.add(key) + self._data_preload.pop(key, None) + + @callback + def async_fetch( + self, key: str + ) -> tuple[bool, json_util.JsonValueType | None] | None: + """Fetch data from cache.""" + # + # If the key is invalidated, we don't need to check the cache + # If async_initialize has not been called yet, we don't know + # if the file exists or not so its a cache miss + # + # It is very important that we check if self._files is None + # because we do not want to incorrectly return a cache miss + # because async_initialize has not been called yet as it would + # cause the Store to return None when it should not. + # + # The "/" in key check is to prevent the cache from being used + # for subdirs in case we have a key like "hacs/XXX" + # + if "/" in key or key in self._invalidated or self._files is None: + _LOGGER.debug("%s: Cache miss", key) + return None + + # If async_initialize has been called and the key is not in self._files + # then the file does not exist + if key not in self._files: + _LOGGER.debug("%s: Cache hit, does not exist", key) + return (False, None) + + # If the key is in the preload cache, return it + if data := self._data_preload.pop(key, None): + _LOGGER.debug("%s: Cache hit data", key) + return (True, data) + + _LOGGER.debug("%s: Cache miss, not preloaded", key) + return None + + @callback + def _async_schedule_cleanup(self, _event: Event) -> None: + """Schedule the cleanup of old files.""" + self._cancel_cleanup = self._hass.loop.call_later( + MANAGER_CLEANUP_DELAY, self._async_cleanup + ) + # Handle the case where we stop in the first 60s + self._hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, + self._async_cancel_and_cleanup, + run_immediately=True, + ) + + @callback + def _async_cancel_and_cleanup(self, _event: Event) -> None: + """Cancel the cleanup of old files.""" + self._async_cleanup() + if self._cancel_cleanup: + self._cancel_cleanup.cancel() + self._cancel_cleanup = None + + @callback + def _async_cleanup(self) -> None: + """Cleanup unused cache. + + If nothing consumes the cache 60s after startup or when we + stop Home Assistant, we'll clear the cache. + """ + self._data_preload.clear() + + async def async_preload(self, keys: Iterable[str]) -> None: + """Cache the keys.""" + # If async_initialize has not been called yet, we can't preload + if self._files is not None and (existing := self._files.intersection(keys)): + await self._hass.async_add_executor_job(self._preload, existing) + + def _preload(self, keys: Iterable[str]) -> None: + """Cache the keys.""" + storage_path = self._storage_path + data_preload = self._data_preload + for key in keys: + storage_file: Path = storage_path.joinpath(key) + try: + if storage_file.is_file(): + data_preload[key] = json_util.load_json(storage_file) + except Exception as ex: # pylint: disable=broad-except + _LOGGER.debug("Error loading %s: %s", key, ex) + + def _initialize_files(self) -> None: + """Initialize the cache.""" + if self._storage_path.exists(): + self._files = set(os.listdir(self._storage_path)) + + @bind_hass class Store(Generic[_T]): """Class to help storing data.""" @@ -103,6 +251,7 @@ class Store(Generic[_T]): encoder: type[JSONEncoder] | None = None, minor_version: int = 1, read_only: bool = False, + config_dir: str | None = None, ) -> None: """Initialize storage class.""" self.version = version @@ -119,6 +268,7 @@ class Store(Generic[_T]): self._atomic_writes = atomic_writes self._read_only = read_only self._next_write_time = 0.0 + self._manager = get_internal_store_manager(hass, config_dir) @cached_property def path(self): @@ -170,6 +320,10 @@ class Store(Generic[_T]): # We make a copy because code might assume it's safe to mutate loaded data # and we don't want that to mess with what we're trying to store. data = deepcopy(data) + elif cache := self._manager.async_fetch(self.key): + exists, data = cache + if not exists: + return None else: try: data = await self.hass.async_add_executor_job( @@ -366,6 +520,7 @@ class Store(Generic[_T]): async def _async_handle_write_data(self, *_args): """Handle writing the config.""" async with self._write_lock: + self._manager.async_invalidate(self.key) self._async_cleanup_delay_listener() self._async_cleanup_final_write_listener() @@ -409,6 +564,7 @@ class Store(Generic[_T]): async def async_remove(self) -> None: """Remove all data.""" + self._manager.async_invalidate(self.key) self._async_cleanup_delay_listener() self._async_cleanup_final_write_listener() diff --git a/tests/common.py b/tests/common.py index b04632caea2..5743b26ef62 100644 --- a/tests/common.py +++ b/tests/common.py @@ -222,12 +222,10 @@ class StoreWithoutWriteLoad(storage.Store[_T]): async def async_test_home_assistant( event_loop: asyncio.AbstractEventLoop | None = None, load_registries: bool = True, - storage_dir: str | None = None, + config_dir: str | None = None, ) -> AsyncGenerator[HomeAssistant, None]: """Return a Home Assistant object pointing at test config dir.""" - hass = HomeAssistant(get_test_config_dir()) - if storage_dir: - hass.config.config_dir = storage_dir + hass = HomeAssistant(config_dir or get_test_config_dir()) store = auth_store.AuthStore(hass) hass.auth = auth.AuthManager(hass, store, {}, {}) ensure_auth_manager_loaded(hass.auth) diff --git a/tests/components/light/common.py b/tests/components/light/common.py index 1d5ad343bb9..26c4d18706d 100644 --- a/tests/components/light/common.py +++ b/tests/components/light/common.py @@ -4,7 +4,6 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ - from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index d04f7902297..0d574e9811f 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -13,11 +13,14 @@ import pytest from homeassistant.const import ( EVENT_HOMEASSISTANT_FINAL_WRITE, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, CoreState, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import issue_registry as ir, storage +from homeassistant.helpers.json import json_bytes from homeassistant.util import dt as dt_util from homeassistant.util.color import RGBColor @@ -622,10 +625,9 @@ async def test_changing_delayed_written_data( async def test_saving_load_round_trip(tmpdir: py.path.local) -> None: """Test saving and loading round trip.""" - async with async_test_home_assistant() as hass: - hass.config.config_dir = await hass.async_add_executor_job( - tmpdir.mkdir, "temp_storage" - ) + loop = asyncio.get_running_loop() + config_dir = await loop.run_in_executor(None, tmpdir.mkdir, "temp_storage") + async with async_test_home_assistant(config_dir=config_dir) as hass: class NamedTupleSubclass(NamedTuple): """A NamedTuple subclass.""" @@ -669,7 +671,7 @@ async def test_loading_corrupt_core_file( loop = asyncio.get_running_loop() tmp_storage = await loop.run_in_executor(None, tmpdir.mkdir, "temp_storage") - async with async_test_home_assistant(storage_dir=tmp_storage) as hass: + async with async_test_home_assistant(config_dir=tmp_storage) as hass: storage_key = "core.anything" store = storage.Store( hass, MOCK_VERSION_2, storage_key, minor_version=MOCK_MINOR_VERSION_1 @@ -728,7 +730,7 @@ async def test_loading_corrupt_file_known_domain( loop = asyncio.get_running_loop() tmp_storage = await loop.run_in_executor(None, tmpdir.mkdir, "temp_storage") - async with async_test_home_assistant(storage_dir=tmp_storage) as hass: + async with async_test_home_assistant(config_dir=tmp_storage) as hass: hass.config.components.add("testdomain") storage_key = "testdomain.testkey" @@ -783,10 +785,9 @@ async def test_loading_corrupt_file_known_domain( async def test_os_error_is_fatal(tmpdir: py.path.local) -> None: """Test OSError during load is fatal.""" - async with async_test_home_assistant() as hass: - tmp_storage = await hass.async_add_executor_job(tmpdir.mkdir, "temp_storage") - hass.config.config_dir = tmp_storage - + loop = asyncio.get_running_loop() + tmp_storage = await loop.run_in_executor(None, tmpdir.mkdir, "temp_storage") + async with async_test_home_assistant(config_dir=tmp_storage) as hass: store = storage.Store( hass, MOCK_VERSION_2, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1 ) @@ -814,10 +815,9 @@ async def test_os_error_is_fatal(tmpdir: py.path.local) -> None: async def test_json_load_failure(tmpdir: py.path.local) -> None: """Test json load raising HomeAssistantError.""" - async with async_test_home_assistant() as hass: - tmp_storage = await hass.async_add_executor_job(tmpdir.mkdir, "temp_storage") - hass.config.config_dir = tmp_storage - + loop = asyncio.get_running_loop() + tmp_storage = await loop.run_in_executor(None, tmpdir.mkdir, "temp_storage") + async with async_test_home_assistant(config_dir=tmp_storage) as hass: store = storage.Store( hass, MOCK_VERSION_2, MOCK_KEY, minor_version=MOCK_MINOR_VERSION_1 ) @@ -861,3 +861,301 @@ async def test_read_only_store( hass.bus.async_fire(EVENT_HOMEASSISTANT_FINAL_WRITE) await hass.async_block_till_done() assert read_only_store.key not in hass_storage + + +async def test_store_manager_caching( + tmpdir: py.path.local, caplog: pytest.LogCaptureFixture +) -> None: + """Test store manager caching.""" + loop = asyncio.get_running_loop() + + def _setup_mock_storage(): + config_dir = tmpdir.mkdir("temp_config") + tmp_storage = config_dir.mkdir(".storage") + tmp_storage.join("integration1").write_binary( + json_bytes({"data": {"integration1": "integration1"}, "version": 1}) + ) + tmp_storage.join("integration2").write_binary( + json_bytes({"data": {"integration2": "integration2"}, "version": 1}) + ) + tmp_storage.join("broken").write_binary(b"invalid") + return config_dir + + config_dir = await loop.run_in_executor(None, _setup_mock_storage) + + async with async_test_home_assistant(config_dir=config_dir) as hass: + store_manager = storage.get_internal_store_manager(hass) + assert ( + store_manager.async_fetch("integration1") is None + ) # has data but not cached + assert ( + store_manager.async_fetch("integration2") is None + ) # has data but not cached + assert ( + store_manager.async_fetch("integration3") is None + ) # no file not but cached + + await store_manager.async_initialize() + assert ( + store_manager.async_fetch("integration1") is None + ) # has data but not cached + assert ( + store_manager.async_fetch("integration2") is None + ) # has data but not cached + assert ( + store_manager.async_fetch("integration3") is not None + ) # no file and initialized + + result = store_manager.async_fetch("integration3") + assert result is not None + exists, data = result + assert exists is False + assert data is None + + await store_manager.async_preload(["integration3", "integration2", "broken"]) + assert "Error loading broken" in caplog.text + + assert ( + store_manager.async_fetch("integration1") is None + ) # has data but not cached + result = store_manager.async_fetch("integration2") + assert result is not None + exists, data = result + assert exists is True + assert data == {"data": {"integration2": "integration2"}, "version": 1} + + assert ( + store_manager.async_fetch("integration3") is not None + ) # no file and initialized + result = store_manager.async_fetch("integration3") + assert result is not None + exists, data = result + assert exists is False + assert data is None + + integration1 = storage.Store(hass, 1, "integration1") + await integration1.async_save({"integration1": "updated"}) + # Save should invalidate the cache + assert store_manager.async_fetch("integration1") is None # invalidated + + integration2 = storage.Store(hass, 1, "integration2") + integration2.async_delay_save(lambda: {"integration2": "updated"}) + # Delay save should invalidate the cache after it saves + assert "integration2" not in store_manager._invalidated + + # Block twice to flush out the delayed save + await hass.async_block_till_done() + await hass.async_block_till_done() + assert store_manager.async_fetch("integration2") is None # invalidated + + store_manager.async_invalidate("integration3") + assert store_manager.async_fetch("integration1") is None # invalidated by save + assert ( + store_manager.async_fetch("integration2") is None + ) # invalidated by delay save + assert store_manager.async_fetch("integration3") is None # invalidated + + await hass.async_stop(force=True) + + async with async_test_home_assistant(config_dir=config_dir) as hass: + store_manager = storage.get_internal_store_manager(hass) + assert store_manager.async_fetch("integration1") is None + assert store_manager.async_fetch("integration2") is None + assert store_manager.async_fetch("integration3") is None + await store_manager.async_initialize() + await store_manager.async_preload(["integration1", "integration2"]) + result = store_manager.async_fetch("integration1") + assert result is not None + exists, data = result + assert exists is True + assert data["data"] == {"integration1": "updated"} + + integration1 = storage.Store(hass, 1, "integration1") + assert await integration1.async_load() == {"integration1": "updated"} + + # Load should pop the cache + assert store_manager.async_fetch("integration1") is None + + integration2 = storage.Store(hass, 1, "integration2") + assert await integration2.async_load() == {"integration2": "updated"} + + # Load should pop the cache + assert store_manager.async_fetch("integration2") is None + + integration3 = storage.Store(hass, 1, "integration3") + assert await integration3.async_load() is None + + await integration3.async_save({"integration3": "updated"}) + assert await integration3.async_load() == {"integration3": "updated"} + + await hass.async_stop(force=True) + + # Now make sure everything still works when we do not + # manually load the storage manager + async with async_test_home_assistant(config_dir=config_dir) as hass: + integration1 = storage.Store(hass, 1, "integration1") + assert await integration1.async_load() == {"integration1": "updated"} + await integration1.async_save({"integration1": "updated2"}) + assert await integration1.async_load() == {"integration1": "updated2"} + + integration2 = storage.Store(hass, 1, "integration2") + assert await integration2.async_load() == {"integration2": "updated"} + await integration2.async_save({"integration2": "updated2"}) + assert await integration2.async_load() == {"integration2": "updated2"} + + await hass.async_stop(force=True) + + # Now remove the stores + async with async_test_home_assistant(config_dir=config_dir) as hass: + store_manager = storage.get_internal_store_manager(hass) + await store_manager.async_initialize() + await store_manager.async_preload(["integration1", "integration2"]) + + integration1 = storage.Store(hass, 1, "integration1") + assert integration1._manager is store_manager + assert await integration1.async_load() == {"integration1": "updated2"} + + integration2 = storage.Store(hass, 1, "integration2") + assert integration2._manager is store_manager + assert await integration2.async_load() == {"integration2": "updated2"} + + await integration1.async_remove() + await integration2.async_remove() + + assert store_manager.async_fetch("integration1") is None + assert store_manager.async_fetch("integration2") is None + + assert await integration1.async_load() is None + assert await integration2.async_load() is None + + await hass.async_stop(force=True) + + # Now make sure the stores are removed and another run works + async with async_test_home_assistant(config_dir=config_dir) as hass: + store_manager = storage.get_internal_store_manager(hass) + await store_manager.async_initialize() + await store_manager.async_preload(["integration1"]) + result = store_manager.async_fetch("integration1") + assert result is not None + exists, data = result + assert exists is False + assert data is None + await hass.async_stop(force=True) + + +async def test_store_manager_sub_dirs(tmpdir: py.path.local) -> None: + """Test store manager ignores subdirs.""" + loop = asyncio.get_running_loop() + + def _setup_mock_storage(): + config_dir = tmpdir.mkdir("temp_config") + sub_dir_storage = config_dir.mkdir(".storage").mkdir("subdir") + + sub_dir_storage.join("integration1").write_binary( + json_bytes({"data": {"integration1": "integration1"}, "version": 1}) + ) + return config_dir + + config_dir = await loop.run_in_executor(None, _setup_mock_storage) + + async with async_test_home_assistant(config_dir=config_dir) as hass: + store_manager = storage.get_internal_store_manager(hass) + await store_manager.async_initialize() + assert store_manager.async_fetch("subdir/integration1") is None + assert store_manager.async_fetch("subdir/integrationx") is None + integration1 = storage.Store(hass, 1, "subdir/integration1") + assert await integration1.async_load() == {"integration1": "integration1"} + await hass.async_stop(force=True) + + +async def test_store_manager_cleanup_after_started( + tmpdir: py.path.local, freezer: FrozenDateTimeFactory +) -> None: + """Test that the cache is cleaned up after startup.""" + loop = asyncio.get_running_loop() + + def _setup_mock_storage(): + config_dir = tmpdir.mkdir("temp_config") + tmp_storage = config_dir.mkdir(".storage") + tmp_storage.join("integration1").write_binary( + json_bytes({"data": {"integration1": "integration1"}, "version": 1}) + ) + tmp_storage.join("integration2").write_binary( + json_bytes({"data": {"integration2": "integration2"}, "version": 1}) + ) + return config_dir + + config_dir = await loop.run_in_executor(None, _setup_mock_storage) + + async with async_test_home_assistant(config_dir=config_dir) as hass: + hass.set_state(CoreState.not_running) + store_manager = storage.get_internal_store_manager(hass) + await store_manager.async_initialize() + await store_manager.async_preload(["integration1", "integration2"]) + assert "integration1" in store_manager._data_preload + assert "integration2" in store_manager._data_preload + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + assert "integration1" in store_manager._data_preload + assert "integration2" in store_manager._data_preload + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + assert "integration1" in store_manager._data_preload + assert "integration2" in store_manager._data_preload + freezer.tick(storage.MANAGER_CLEANUP_DELAY) + async_fire_time_changed(hass) + await hass.async_block_till_done() + # The cache should be removed after the cleanup delay + # since it means nothing ever loaded it and we want to + # recover the memory + assert "integration1" not in store_manager._data_preload + assert "integration2" not in store_manager._data_preload + assert store_manager.async_fetch("integration1") is None + assert store_manager.async_fetch("integration2") is None + await hass.async_stop(force=True) + + +async def test_store_manager_cleanup_after_stop( + tmpdir: py.path.local, freezer: FrozenDateTimeFactory +) -> None: + """Test that the cache is cleaned up after stop event. + + This should only happen if we stop within the cleanup delay. + """ + loop = asyncio.get_running_loop() + + def _setup_mock_storage(): + config_dir = tmpdir.mkdir("temp_config") + tmp_storage = config_dir.mkdir(".storage") + tmp_storage.join("integration1").write_binary( + json_bytes({"data": {"integration1": "integration1"}, "version": 1}) + ) + tmp_storage.join("integration2").write_binary( + json_bytes({"data": {"integration2": "integration2"}, "version": 1}) + ) + return config_dir + + config_dir = await loop.run_in_executor(None, _setup_mock_storage) + + async with async_test_home_assistant(config_dir=config_dir) as hass: + hass.set_state(CoreState.not_running) + store_manager = storage.get_internal_store_manager(hass) + await store_manager.async_initialize() + await store_manager.async_preload(["integration1", "integration2"]) + assert "integration1" in store_manager._data_preload + assert "integration2" in store_manager._data_preload + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + assert "integration1" in store_manager._data_preload + assert "integration2" in store_manager._data_preload + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + assert "integration1" in store_manager._data_preload + assert "integration2" in store_manager._data_preload + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "integration1" not in store_manager._data_preload + assert "integration2" not in store_manager._data_preload + assert store_manager.async_fetch("integration1") is None + assert store_manager.async_fetch("integration2") is None + await hass.async_stop(force=True) diff --git a/tests/test_core.py b/tests/test_core.py index efeb185a3d6..89b83e15122 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1891,6 +1891,7 @@ async def test_serviceregistry_return_response_optional( async def test_config_defaults() -> None: """Test config defaults.""" hass = Mock() + hass.data = {} config = ha.Config(hass, "/test/ha-config") assert config.hass is hass assert config.latitude == 0 @@ -1918,20 +1919,25 @@ async def test_config_defaults() -> None: async def test_config_path_with_file() -> None: """Test get_config_path method.""" - config = ha.Config(None, "/test/ha-config") + hass = Mock() + hass.data = {} + config = ha.Config(hass, "/test/ha-config") assert config.path("test.conf") == "/test/ha-config/test.conf" async def test_config_path_with_dir_and_file() -> None: """Test get_config_path method.""" - config = ha.Config(None, "/test/ha-config") + hass = Mock() + hass.data = {} + config = ha.Config(hass, "/test/ha-config") assert config.path("dir", "test.conf") == "/test/ha-config/dir/test.conf" async def test_config_as_dict() -> None: """Test as dict.""" - config = ha.Config(None, "/test/ha-config") - config.hass = MagicMock() + hass = Mock() + hass.data = {} + config = ha.Config(hass, "/test/ha-config") type(config.hass.state).value = PropertyMock(return_value="RUNNING") expected = { "latitude": 0, @@ -1962,7 +1968,9 @@ async def test_config_as_dict() -> None: async def test_config_is_allowed_path() -> None: """Test is_allowed_path method.""" - config = ha.Config(None, "/test/ha-config") + hass = Mock() + hass.data = {} + config = ha.Config(hass, "/test/ha-config") with TemporaryDirectory() as tmp_dir: # The created dir is in /tmp. This is a symlink on OS X # causing this test to fail unless we resolve path first. @@ -1994,7 +2002,9 @@ async def test_config_is_allowed_path() -> None: async def test_config_is_allowed_external_url() -> None: """Test is_allowed_external_url method.""" - config = ha.Config(None, "/test/ha-config") + hass = Mock() + hass.data = {} + config = ha.Config(hass, "/test/ha-config") config.allowlist_external_urls = [ "http://x.com/", "https://y.com/bla/", From d0ecad78acfd45197f7d211a4ecb0f5d2381ea91 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 26 Mar 2024 07:44:57 +0100 Subject: [PATCH 1508/1691] Revert "Update typing-extensions to 4.11.0rc1 (#114116)" (#114216) --- homeassistant/components/zwave_js/config_flow.py | 2 +- homeassistant/config_entries.py | 14 +++++++------- homeassistant/helpers/data_entry_flow.py | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index 3470f64f79f..ca05dc2117b 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -183,7 +183,7 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC): @property @abstractmethod - def flow_manager(self) -> FlowManager[ConfigFlowResult]: + def flow_manager(self) -> FlowManager[ConfigFlowResult, str]: """Return the flow manager of the flow.""" async def async_step_install_addon( diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 15de62179cb..5976ed83814 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1071,7 +1071,7 @@ class FlowCancelledError(Exception): """Error to indicate that a flow has been cancelled.""" -class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): +class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): """Manage all the config entry flows that are in progress.""" _flow_result = ConfigFlowResult @@ -1197,7 +1197,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): async def async_finish_flow( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult], + flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish a config flow and add an entry.""" @@ -1319,7 +1319,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): async def async_post_init( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult], + flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], result: ConfigFlowResult, ) -> None: """After a flow is initialised trigger new flow notifications.""" @@ -1987,7 +1987,7 @@ def _async_abort_entries_match( raise data_entry_flow.AbortFlow("already_configured") -class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult]): +class ConfigEntryBaseFlow(data_entry_flow.FlowHandler[ConfigFlowResult, str]): """Base class for config and option flows.""" _flow_result = ConfigFlowResult @@ -2339,7 +2339,7 @@ class ConfigFlow(ConfigEntryBaseFlow): return self.async_abort(reason=reason) -class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): +class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult, str]): """Flow to set options for a configuration entry.""" _flow_result = ConfigFlowResult @@ -2369,7 +2369,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): async def async_finish_flow( self, - flow: data_entry_flow.FlowHandler[ConfigFlowResult], + flow: data_entry_flow.FlowHandler[ConfigFlowResult, str], result: ConfigFlowResult, ) -> ConfigFlowResult: """Finish an options flow and update options for configuration entry. @@ -2391,7 +2391,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]): return result async def _async_setup_preview( - self, flow: data_entry_flow.FlowHandler[ConfigFlowResult] + self, flow: data_entry_flow.FlowHandler[ConfigFlowResult, str] ) -> None: """Set up preview for an option flow handler.""" entry = self._async_get_config_entry(flow.handler) diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 2adab32195b..1edeb28d88f 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -18,7 +18,7 @@ from . import config_validation as cv _FlowManagerT = TypeVar( "_FlowManagerT", - bound=data_entry_flow.FlowManager[Any], + bound="data_entry_flow.FlowManager[Any]", default=data_entry_flow.FlowManager, ) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 8355bbb50bd..0c13da70715 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -53,7 +53,7 @@ pyudev==0.23.2 PyYAML==6.0.1 requests==2.31.0 SQLAlchemy==2.0.29 -typing-extensions>=4.11.0rc1,<5.0 +typing-extensions>=4.10.0,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 voluptuous-serialize==2.6.0 diff --git a/pyproject.toml b/pyproject.toml index ac4ad9ffafa..d6137b3d7ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ dependencies = [ "PyYAML==6.0.1", "requests==2.31.0", "SQLAlchemy==2.0.29", - "typing-extensions>=4.11.0rc1,<5.0", + "typing-extensions>=4.10.0,<5.0", "ulid-transform==0.9.0", # Constrain urllib3 to ensure we deal with CVE-2020-26137 and CVE-2021-33503 # Temporary setting an upper bound, to prevent compat issues with urllib3>=2 diff --git a/requirements.txt b/requirements.txt index 0026f39caf0..1dd9b1811d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ python-slugify==8.0.4 PyYAML==6.0.1 requests==2.31.0 SQLAlchemy==2.0.29 -typing-extensions>=4.11.0rc1,<5.0 +typing-extensions>=4.10.0,<5.0 ulid-transform==0.9.0 urllib3>=1.26.5,<2 voluptuous==0.13.1 From 3f545cb3d368a0ea046603e0120f42883187d02f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 26 Mar 2024 07:55:07 +0100 Subject: [PATCH 1509/1691] Rework Axis entity loader to have a better internal storage structure (#114114) * Make one single subscribe containing all topics at once * Update homeassistant/components/axis/hub/entity_loader.py Co-authored-by: J. Nick Koston --------- Co-authored-by: J. Nick Koston --- .../components/axis/hub/entity_loader.py | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/axis/hub/entity_loader.py b/homeassistant/components/axis/hub/entity_loader.py index 36523e24d83..54815ff9a69 100644 --- a/homeassistant/components/axis/hub/entity_loader.py +++ b/homeassistant/components/axis/hub/entity_loader.py @@ -5,7 +5,6 @@ Central point to load entities for the different platforms. from __future__ import annotations -from functools import partial from typing import TYPE_CHECKING from axis.models.event import Event, EventOperation, EventTopic @@ -23,17 +22,20 @@ class AxisEntityLoader: """Axis network device integration handling platforms for entity registration.""" def __init__(self, hub: AxisHub) -> None: - """Initialize the UniFi entity loader.""" + """Initialize the Axis entity loader.""" self.hub = hub self.registered_events: set[tuple[str, EventTopic, str]] = set() - self.platforms: list[ - tuple[ - AddEntitiesCallback, - type[AxisEventEntity], - tuple[AxisEventDescription, ...], - ] - ] = [] + self.topic_to_entity: dict[ + EventTopic, + list[ + tuple[ + AddEntitiesCallback, + type[AxisEventEntity], + AxisEventDescription, + ] + ], + ] = {} @callback def register_platform( @@ -43,37 +45,39 @@ class AxisEntityLoader: descriptions: tuple[AxisEventDescription, ...], ) -> None: """Register Axis entity platforms.""" - self.platforms.append((async_add_entities, entity_class, descriptions)) + topics: tuple[EventTopic, ...] + for description in descriptions: + if isinstance(description.event_topic, EventTopic): + topics = (description.event_topic,) + else: + topics = description.event_topic + for topic in topics: + self.topic_to_entity.setdefault(topic, []).append( + (async_add_entities, entity_class, description) + ) + + @callback + def _create_entities_from_event(self, event: Event) -> None: + """Create Axis entities from event.""" + event_id = (event.topic, event.topic_base, event.id) + if event_id in self.registered_events: + # Device has restarted and all events are initialized anew + return + self.registered_events.add(event_id) + for ( + async_add_entities, + entity_class, + description, + ) in self.topic_to_entity[event.topic_base]: + if not description.supported_fn(self.hub, event): + continue + async_add_entities([entity_class(self.hub, description, event)]) @callback def initialize_platforms(self) -> None: - """Prepare event listeners and platforms.""" - - @callback - def load_entities( - platform_entity: type[AxisEventEntity], - descriptions: tuple[AxisEventDescription, ...], - async_add_entities: AddEntitiesCallback, - ) -> None: - """Set up listeners for events.""" - - @callback - def create_entity(description: AxisEventDescription, event: Event) -> None: - """Create Axis entity.""" - event_id = (event.topic, event.topic_base, event.id) - if event_id in self.registered_events: - # Device has restarted and all events are initiatlized anew - return - self.registered_events.add(event_id) - if description.supported_fn(self.hub, event): - async_add_entities([platform_entity(self.hub, description, event)]) - - for description in descriptions: - self.hub.api.event.subscribe( - partial(create_entity, description), - topic_filter=description.event_topic, - operation_filter=EventOperation.INITIALIZED, - ) - - for async_add_entities, entity_class, descriptions in self.platforms: - load_entities(entity_class, descriptions, async_add_entities) + """Prepare event listener that can populate platform entities.""" + self.hub.api.event.subscribe( + self._create_entities_from_event, + topic_filter=tuple(self.topic_to_entity.keys()), + operation_filter=EventOperation.INITIALIZED, + ) From 54a69a26877440e1ccccd92ef6ec59fea10d6aad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Mar 2024 22:22:53 -1000 Subject: [PATCH 1510/1691] Remove async_get_device_class_lookup from entity_registry (#114212) This function was only ever used in homekit, and since there is now an index of devices in the entity registry, homekit no longer uses it. I searched github code for all references to async_get_device_class_lookup and the only think I could find using it were forks of core. It seems unlikely that any custom components are affected by removing this function --- homeassistant/helpers/entity_registry.py | 23 ------- tests/helpers/test_entity_registry.py | 79 ------------------------ 2 files changed, 102 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 5946542b394..ef9274c6ceb 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -643,29 +643,6 @@ class EntityRegistry(BaseRegistry): run_immediately=True, ) - @callback - def async_get_device_class_lookup( - self, domain_device_classes: set[tuple[str, str | None]] - ) -> dict[str, dict[tuple[str, str | None], str]]: - """Return a lookup of entity ids for devices which have matching entities. - - Entities must match a set of (domain, device_class) tuples. - The result is indexed by device_id, then by the matching (domain, device_class) - """ - lookup: dict[str, dict[tuple[str, str | None], str]] = {} - for entity in self.entities.values(): - if not entity.device_id: - continue - device_class = entity.device_class or entity.original_device_class - domain_device_class = (entity.domain, device_class) - if domain_device_class not in domain_device_classes: - continue - if entity.device_id not in lookup: - lookup[entity.device_id] = {domain_device_class: entity.entity_id} - else: - lookup[entity.device_id][domain_device_class] = entity.entity_id - return lookup - @callback def async_is_registered(self, entity_id: str) -> bool: """Check if an entity_id is currently registered.""" diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index a9177fc8c83..91c749a0d7f 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -954,85 +954,6 @@ async def test_restore_states( assert hass.states.get("light.all_info_set") is None -async def test_async_get_device_class_lookup( - hass: HomeAssistant, entity_registry: er.EntityRegistry -) -> None: - """Test registry device class lookup.""" - hass.set_state(CoreState.not_running) - - entity_registry.async_get_or_create( - "binary_sensor", - "light", - "battery_charging", - device_id="light_device_entry_id", - original_device_class="battery_charging", - ) - entity_registry.async_get_or_create( - "sensor", - "light", - "battery", - device_id="light_device_entry_id", - original_device_class="battery", - ) - entity_registry.async_get_or_create( - "light", "light", "demo", device_id="light_device_entry_id" - ) - entity_registry.async_get_or_create( - "binary_sensor", - "vacuum", - "battery_charging", - device_id="vacuum_device_entry_id", - original_device_class="battery_charging", - ) - entity_registry.async_get_or_create( - "sensor", - "vacuum", - "battery", - device_id="vacuum_device_entry_id", - original_device_class="battery", - ) - entity_registry.async_get_or_create( - "vacuum", "vacuum", "demo", device_id="vacuum_device_entry_id" - ) - entity_registry.async_get_or_create( - "binary_sensor", - "remote", - "battery_charging", - device_id="remote_device_entry_id", - original_device_class="battery_charging", - ) - entity_registry.async_get_or_create( - "remote", "remote", "demo", device_id="remote_device_entry_id" - ) - - device_lookup = entity_registry.async_get_device_class_lookup( - {("binary_sensor", "battery_charging"), ("sensor", "battery")} - ) - - assert device_lookup == { - "remote_device_entry_id": { - ( - "binary_sensor", - "battery_charging", - ): "binary_sensor.remote_battery_charging" - }, - "light_device_entry_id": { - ( - "binary_sensor", - "battery_charging", - ): "binary_sensor.light_battery_charging", - ("sensor", "battery"): "sensor.light_battery", - }, - "vacuum_device_entry_id": { - ( - "binary_sensor", - "battery_charging", - ): "binary_sensor.vacuum_battery_charging", - ("sensor", "battery"): "sensor.vacuum_battery", - }, - } - - async def test_remove_device_removes_entities( hass: HomeAssistant, entity_registry: er.EntityRegistry, From 63b4fd09c1d6a2b5a7fae73e7f3ad62b6d7165b8 Mon Sep 17 00:00:00 2001 From: Matrix Date: Tue, 26 Mar 2024 16:29:14 +0800 Subject: [PATCH 1511/1691] Add YoLink Water Meter Support (#114148) * Add YS-5006/5007/5008 Water Meter Support * Add YoLink Water Meter Support * Update .coveragerc * fix as suggestion --- .coveragerc | 1 + homeassistant/components/yolink/__init__.py | 1 + homeassistant/components/yolink/const.py | 2 + homeassistant/components/yolink/sensor.py | 15 +++ homeassistant/components/yolink/strings.json | 8 ++ homeassistant/components/yolink/valve.py | 115 +++++++++++++++++++ 6 files changed, 142 insertions(+) create mode 100644 homeassistant/components/yolink/valve.py diff --git a/.coveragerc b/.coveragerc index 67c5887f2da..94b294f71a3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1687,6 +1687,7 @@ omit = homeassistant/components/yolink/services.py homeassistant/components/yolink/siren.py homeassistant/components/yolink/switch.py + homeassistant/components/yolink/valve.py homeassistant/components/youless/__init__.py homeassistant/components/youless/sensor.py homeassistant/components/zabbix/* diff --git a/homeassistant/components/yolink/__init__.py b/homeassistant/components/yolink/__init__.py index f1d2ec6602b..fec678ce435 100644 --- a/homeassistant/components/yolink/__init__.py +++ b/homeassistant/components/yolink/__init__.py @@ -46,6 +46,7 @@ PLATFORMS = [ Platform.SENSOR, Platform.SIREN, Platform.SWITCH, + Platform.VALVE, ] diff --git a/homeassistant/components/yolink/const.py b/homeassistant/components/yolink/const.py index 3d341c8b4fb..110b9cb9810 100644 --- a/homeassistant/components/yolink/const.py +++ b/homeassistant/components/yolink/const.py @@ -14,3 +14,5 @@ ATTR_REPEAT = "repeat" ATTR_TONE = "tone" YOLINK_EVENT = f"{DOMAIN}_event" YOLINK_OFFLINE_TIME = 32400 + +DEV_MODEL_WATER_METER_YS5007 = "YS5007" diff --git a/homeassistant/components/yolink/sensor.py b/homeassistant/components/yolink/sensor.py index e1635465bc1..6badeefbdb3 100644 --- a/homeassistant/components/yolink/sensor.py +++ b/homeassistant/components/yolink/sensor.py @@ -24,6 +24,7 @@ from yolink.const import ( ATTR_DEVICE_THERMOSTAT, ATTR_DEVICE_VIBRATION_SENSOR, ATTR_DEVICE_WATER_DEPTH_SENSOR, + ATTR_DEVICE_WATER_METER_CONTROLLER, ATTR_GARAGE_DOOR_CONTROLLER, ) from yolink.device import YoLinkDevice @@ -41,6 +42,7 @@ from homeassistant.const import ( EntityCategory, UnitOfLength, UnitOfTemperature, + UnitOfVolume, ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -76,6 +78,7 @@ SENSOR_DEVICE_TYPE = [ ATTR_DEVICE_THERMOSTAT, ATTR_DEVICE_VIBRATION_SENSOR, ATTR_DEVICE_WATER_DEPTH_SENSOR, + ATTR_DEVICE_WATER_METER_CONTROLLER, ATTR_DEVICE_LOCK, ATTR_DEVICE_MANIPULATOR, ATTR_DEVICE_CO_SMOKE_SENSOR, @@ -96,6 +99,7 @@ BATTERY_POWER_SENSOR = [ ATTR_DEVICE_MANIPULATOR, ATTR_DEVICE_CO_SMOKE_SENSOR, ATTR_DEVICE_WATER_DEPTH_SENSOR, + ATTR_DEVICE_WATER_METER_CONTROLLER, ] MCU_DEV_TEMPERATURE_SENSOR = [ @@ -202,6 +206,17 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfLength.METERS, exists_fn=lambda device: device.device_type in ATTR_DEVICE_WATER_DEPTH_SENSOR, ), + YoLinkSensorEntityDescription( + key="meter_reading", + translation_key="water_meter_reading", + device_class=SensorDeviceClass.WATER, + icon="mdi:gauge", + native_unit_of_measurement=UnitOfVolume.CUBIC_METERS, + state_class=SensorStateClass.TOTAL_INCREASING, + should_update_entity=lambda value: value is not None, + exists_fn=lambda device: device.device_type + in ATTR_DEVICE_WATER_METER_CONTROLLER, + ), ) diff --git a/homeassistant/components/yolink/strings.json b/homeassistant/components/yolink/strings.json index 83e712328f9..bc8fb435e76 100644 --- a/homeassistant/components/yolink/strings.json +++ b/homeassistant/components/yolink/strings.json @@ -73,12 +73,20 @@ "enabled": "[%key:common::state::enabled%]", "disabled": "[%key:common::state::disabled%]" } + }, + "water_meter_reading": { + "name": "Water meter reading" } }, "number": { "config_volume": { "name": "Volume" } + }, + "valve": { + "meter_valve_state": { + "name": "Valve state" + } } }, "services": { diff --git a/homeassistant/components/yolink/valve.py b/homeassistant/components/yolink/valve.py new file mode 100644 index 00000000000..a24ad7d385d --- /dev/null +++ b/homeassistant/components/yolink/valve.py @@ -0,0 +1,115 @@ +"""YoLink Valve.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from yolink.client_request import ClientRequest +from yolink.const import ATTR_DEVICE_WATER_METER_CONTROLLER +from yolink.device import YoLinkDevice + +from homeassistant.components.valve import ( + ValveDeviceClass, + ValveEntity, + ValveEntityDescription, + ValveEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DEV_MODEL_WATER_METER_YS5007, DOMAIN +from .coordinator import YoLinkCoordinator +from .entity import YoLinkEntity + + +@dataclass(frozen=True) +class YoLinkValveEntityDescription(ValveEntityDescription): + """YoLink ValveEntityDescription.""" + + exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True + value: Callable = lambda state: state + + +DEVICE_TYPES: tuple[YoLinkValveEntityDescription, ...] = ( + YoLinkValveEntityDescription( + key="valve_state", + translation_key="meter_valve_state", + device_class=ValveDeviceClass.WATER, + value=lambda value: value == "closed" if value is not None else None, + exists_fn=lambda device: device.device_type + == ATTR_DEVICE_WATER_METER_CONTROLLER + and not device.device_model_name.startswith(DEV_MODEL_WATER_METER_YS5007), + ), +) + +DEVICE_TYPE = [ATTR_DEVICE_WATER_METER_CONTROLLER] + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up YoLink valve from a config entry.""" + device_coordinators = hass.data[DOMAIN][config_entry.entry_id].device_coordinators + valve_device_coordinators = [ + device_coordinator + for device_coordinator in device_coordinators.values() + if device_coordinator.device.device_type in DEVICE_TYPE + ] + async_add_entities( + YoLinkValveEntity(config_entry, valve_device_coordinator, description) + for valve_device_coordinator in valve_device_coordinators + for description in DEVICE_TYPES + if description.exists_fn(valve_device_coordinator.device) + ) + + +class YoLinkValveEntity(YoLinkEntity, ValveEntity): + """YoLink Valve Entity.""" + + entity_description: YoLinkValveEntityDescription + + def __init__( + self, + config_entry: ConfigEntry, + coordinator: YoLinkCoordinator, + description: YoLinkValveEntityDescription, + ) -> None: + """Init YoLink valve.""" + super().__init__(config_entry, coordinator) + self._attr_supported_features = ( + ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE + ) + self.entity_description = description + self._attr_unique_id = ( + f"{coordinator.device.device_id} {self.entity_description.key}" + ) + + @callback + def update_entity_state(self, state: dict[str, str | list[str]]) -> None: + """Update HA Entity State.""" + if ( + attr_val := self.entity_description.value( + state.get(self.entity_description.key) + ) + ) is None: + return + self._attr_is_closed = attr_val + self.async_write_ha_state() + + async def _async_invoke_device(self, state: str) -> None: + """Call setState api to change valve state.""" + await self.call_device(ClientRequest("setState", {"valve": state})) + self._attr_is_closed = state == "close" + self.async_write_ha_state() + + async def async_open_valve(self) -> None: + """Open the valve.""" + await self._async_invoke_device("open") + + async def async_close_valve(self) -> None: + """Close valve.""" + await self._async_invoke_device("close") From b9fdd56f01244d6151f3031cf095eff8567a1255 Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Tue, 26 Mar 2024 09:42:19 +0100 Subject: [PATCH 1512/1691] Bump pyenphase to 1.20.1 (#114218) --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 7e2d70e914e..597d326968d 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "iot_class": "local_polling", "loggers": ["pyenphase"], - "requirements": ["pyenphase==1.20.0"], + "requirements": ["pyenphase==1.20.1"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index 9f87a7f2fe6..f1fac9b7333 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1790,7 +1790,7 @@ pyedimax==0.2.1 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.20.0 +pyenphase==1.20.1 # homeassistant.components.envisalink pyenvisalink==4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6d6ca8537ba..cbd51c88a26 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1389,7 +1389,7 @@ pyeconet==0.1.22 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.20.0 +pyenphase==1.20.1 # homeassistant.components.everlights pyeverlights==0.1.0 From 70c4fa8475e307bc8c2b27bdde65548bc905d6bd Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Tue, 26 Mar 2024 09:52:04 +0100 Subject: [PATCH 1513/1691] Add Motionblinds BLE integration (#109497) * initial fork * intial tests * Initial test coverage * extra coverage * complete config flow tests * fix generated * Update CODEOWNERS * Move logic to PyPi library and update to pass config_flow test and pre-commit * Remove Button, Select and Sensor platform for initial PR * Update manifest.json * Change info logs to debug in cover * Use _abort_if_unique_id_configured instead of custom loop checking existing entries * Change platforms list to PLATFORMS global Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Remove VERSION from ConfigFlow Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Replace all info logs by debug * Use instance attributes in ConfigFlow Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Add return type and docstring to init in ConfigFlow * Add recovery to tests containing errors * Make NoBluetoothAdapter and NoDevicesFound abort instead of show error * Change info logs to debug * Add and change integration type from hub to device * Use CONF_ADDRESS from homeassistant.const * Move cover attributes initialization out of constructor * Change CONF_ADDRESS in tests from const to homeassistant.const * Remove unused part of tests * Change 'not motion_device' to 'motion_device is None' * Change _attr_connection_type to _connection_type * Add connections to DeviceInfo * Add model to DeviceInfo and change MotionBlindType values * Remove identifiers from DeviceInfo * Move constants from const to library * Move calibration and running to library, re-add all platforms * Remove platforms from init * Remove button platform * Remove select platform * Remove sensor platform * Bump motionblindsble to 0.0.4 * Remove closed, opening and closing attribute default values Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Remove CONFIG_SCHEMA from init Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> * Remove unused platform attributes and icons * Re-add _attr_is_closed to GenericBlind to fix error * Use entry.async_create_background_task for library instead of entry.async_create_task * Move updating of position on disconnect to library * Remove type hints, keep for _attr_is_closed * Use DISPLAY_NAME constant from library for display name * Add TYPE_CHECKING condition to assert in config_flow * Re-add CONFIG_SCHEMA to __init__ to pass hassfest * Change FlowResult type to ConfigFlowResult * Fix import in tests * Fix ruff import * Fix tests by using value of enum * Use lowercase name of MotionBlindType enum for data schema selector and translation * Fix using name instead of value for MotionBlindType * Improve position None handling Co-authored-by: starkillerOG * Improve tilt None handling Co-authored-by: starkillerOG * Change BLIND_TO_ENTITY_TYPE name * Set entity name of cover to None and use DeviceInfo name * Add base entity * Move async_update to base entity * Move unique ID with suffix to base class * Add entity.py to .coveragerc * Remove extra state attribute connection type * Remove separate line hass.data.setdefault(DOMAIN, {}) * Remove use of field for key and translation_key in MotionCoverEntityDescription * Remove entity translation with extra state_attributes from strings.json * Use super().__init__(device, entry) Co-authored-by: Joost Lekkerkerker * Change if block in async_update_running * Use if blocks in async_update_position * Add additional scanner check before show_form * Remove default value of device_class in MotionCoverEntityDescription * Fix entry.data[CONF_BLIND_TYPE] uppercase * Fix device info model name * Bump motionblindsble to 0.0.5 * Fix tests * Move entity_description to MotionblindsBLEEntity * Change double roller blind name * Bump motionblindsble to 0.0.6 * Fix ruff * Use status_query for async_update * Bump motionblindsble to 0.0.7 * Change bluetooth local name * Set kw_only=True for dataclass * Change name of GenericBlind * Change scanner_count conditional * Wrap async_register_callback in entry.async_on_unload * Bump motionblindsble to 0.0.8 * Use set_create_task_factory and set_call_later_factory * Update bluetooth.py generated * Simplify COVER_TYPES dictionary * Move registering callbacks to async_added_to_hass * Remove check for ATTR_POSITION and ATTR_TILT_POSITION in kwargs * Add naming consistency for device and entry * Use if block instead of ternary for _attr_unique_id * Improve errors ternary in config_flow * Use set instead of list for running_type * Improve errors ternary in config_flow * Remove init from MotionblindsBLECoverEntity and move debug log to async_added_to_hass * Update debug log create cover * Fix ruff * Use identity check instead of equals * Use identity check instead of equals * Change MotionblindsBLECoverEntityDescription name * Change debug log text * Remove ATTR_CONNECTION from const * Add types for variables in async_setup_entry * Add types for variables in async_setup_entry * Change PositionBlind class name to PositionCover etc * Improve docstrings * Improve docstrings --------- Co-authored-by: starkillerOG Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com> Co-authored-by: Joost Lekkerkerker --- .coveragerc | 3 + CODEOWNERS | 2 + .../components/motionblinds_ble/__init__.py | 104 +++++++ .../motionblinds_ble/config_flow.py | 214 +++++++++++++++ .../components/motionblinds_ble/const.py | 16 ++ .../components/motionblinds_ble/cover.py | 230 ++++++++++++++++ .../components/motionblinds_ble/entity.py | 52 ++++ .../components/motionblinds_ble/manifest.json | 18 ++ .../components/motionblinds_ble/strings.json | 37 +++ homeassistant/generated/bluetooth.py | 5 + homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/motionblinds_ble/__init__.py | 1 + tests/components/motionblinds_ble/conftest.py | 31 +++ .../motionblinds_ble/test_config_flow.py | 256 ++++++++++++++++++ 17 files changed, 982 insertions(+) create mode 100644 homeassistant/components/motionblinds_ble/__init__.py create mode 100644 homeassistant/components/motionblinds_ble/config_flow.py create mode 100644 homeassistant/components/motionblinds_ble/const.py create mode 100644 homeassistant/components/motionblinds_ble/cover.py create mode 100644 homeassistant/components/motionblinds_ble/entity.py create mode 100644 homeassistant/components/motionblinds_ble/manifest.json create mode 100644 homeassistant/components/motionblinds_ble/strings.json create mode 100644 tests/components/motionblinds_ble/__init__.py create mode 100644 tests/components/motionblinds_ble/conftest.py create mode 100644 tests/components/motionblinds_ble/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 94b294f71a3..1bee5f26b37 100644 --- a/.coveragerc +++ b/.coveragerc @@ -806,6 +806,9 @@ omit = homeassistant/components/motion_blinds/cover.py homeassistant/components/motion_blinds/entity.py homeassistant/components/motion_blinds/sensor.py + homeassistant/components/motionblinds_ble/__init__.py + homeassistant/components/motionblinds_ble/cover.py + homeassistant/components/motionblinds_ble/entity.py homeassistant/components/motionmount/__init__.py homeassistant/components/motionmount/binary_sensor.py homeassistant/components/motionmount/entity.py diff --git a/CODEOWNERS b/CODEOWNERS index 440ddd45cfa..7ba24210f96 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -841,6 +841,8 @@ build.json @home-assistant/supervisor /tests/components/mopeka/ @bdraco /homeassistant/components/motion_blinds/ @starkillerOG /tests/components/motion_blinds/ @starkillerOG +/homeassistant/components/motionblinds_ble/ @LennP @jerrybboy +/tests/components/motionblinds_ble/ @LennP @jerrybboy /homeassistant/components/motioneye/ @dermotduffy /tests/components/motioneye/ @dermotduffy /homeassistant/components/motionmount/ @RJPoelstra diff --git a/homeassistant/components/motionblinds_ble/__init__.py b/homeassistant/components/motionblinds_ble/__init__.py new file mode 100644 index 00000000000..beef6d7d665 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/__init__.py @@ -0,0 +1,104 @@ +"""Motionblinds BLE integration.""" + +from __future__ import annotations + +from functools import partial +import logging + +from motionblindsble.const import MotionBlindType +from motionblindsble.crypt import MotionCrypt +from motionblindsble.device import MotionDevice + +from homeassistant.components.bluetooth import ( + BluetoothCallbackMatcher, + BluetoothChange, + BluetoothScanningMode, + BluetoothServiceInfoBleak, + async_ble_device_from_address, + async_register_callback, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ADDRESS, Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.typing import ConfigType + +from .const import CONF_BLIND_TYPE, CONF_MAC_CODE, DOMAIN + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS: list[Platform] = [ + Platform.COVER, +] + +CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up Motionblinds BLE integration.""" + + _LOGGER.debug("Setting up Motionblinds BLE integration") + + # The correct time is needed for encryption + _LOGGER.debug("Setting timezone for encryption: %s", hass.config.time_zone) + MotionCrypt.set_timezone(hass.config.time_zone) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Motionblinds BLE device from a config entry.""" + + _LOGGER.debug("(%s) Setting up device", entry.data[CONF_MAC_CODE]) + + ble_device = async_ble_device_from_address(hass, entry.data[CONF_ADDRESS]) + device = MotionDevice( + ble_device if ble_device is not None else entry.data[CONF_ADDRESS], + blind_type=MotionBlindType[entry.data[CONF_BLIND_TYPE].upper()], + ) + + # Register Home Assistant functions to use in the library + device.set_create_task_factory( + partial( + entry.async_create_background_task, + hass=hass, + name=device.ble_device.address, + ) + ) + device.set_call_later_factory(partial(async_call_later, hass=hass)) + + # Register a callback that updates the BLEDevice in the library + @callback + def async_update_ble_device( + service_info: BluetoothServiceInfoBleak, change: BluetoothChange + ) -> None: + """Update the BLEDevice.""" + _LOGGER.debug("(%s) New BLE device found", service_info.address) + device.set_ble_device(service_info.device, rssi=service_info.advertisement.rssi) + + entry.async_on_unload( + async_register_callback( + hass, + async_update_ble_device, + BluetoothCallbackMatcher(address=entry.data[CONF_ADDRESS]), + BluetoothScanningMode.ACTIVE, + ) + ) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = device + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + _LOGGER.debug("(%s) Finished setting up device", entry.data[CONF_MAC_CODE]) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Motionblinds BLE device from a config entry.""" + + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/motionblinds_ble/config_flow.py b/homeassistant/components/motionblinds_ble/config_flow.py new file mode 100644 index 00000000000..0282c4d5584 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/config_flow.py @@ -0,0 +1,214 @@ +"""Config flow for Motionblinds BLE integration.""" + +from __future__ import annotations + +import logging +import re +from typing import TYPE_CHECKING, Any + +from bleak.backends.device import BLEDevice +from motionblindsble.const import DISPLAY_NAME, MotionBlindType +import voluptuous as vol + +from homeassistant.components import bluetooth +from homeassistant.components.bluetooth import BluetoothServiceInfoBleak +from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.const import CONF_ADDRESS +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.selector import ( + SelectSelector, + SelectSelectorConfig, + SelectSelectorMode, +) + +from .const import ( + CONF_BLIND_TYPE, + CONF_LOCAL_NAME, + CONF_MAC_CODE, + DOMAIN, + ERROR_COULD_NOT_FIND_MOTOR, + ERROR_INVALID_MAC_CODE, + ERROR_NO_BLUETOOTH_ADAPTER, + ERROR_NO_DEVICES_FOUND, +) + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema({vol.Required(CONF_MAC_CODE): str}) + + +class FlowHandler(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Motionblinds BLE.""" + + def __init__(self) -> None: + """Initialize a ConfigFlow.""" + self._discovery_info: BluetoothServiceInfoBleak | BLEDevice | None = None + self._mac_code: str | None = None + self._display_name: str | None = None + self._blind_type: MotionBlindType | None = None + + async def async_step_bluetooth( + self, discovery_info: BluetoothServiceInfoBleak + ) -> ConfigFlowResult: + """Handle the bluetooth discovery step.""" + _LOGGER.debug( + "Discovered Motionblinds bluetooth device: %s", discovery_info.as_dict() + ) + await self.async_set_unique_id(discovery_info.address) + self._abort_if_unique_id_configured() + + self._discovery_info = discovery_info + self._mac_code = get_mac_from_local_name(discovery_info.name) + self._display_name = DISPLAY_NAME.format(mac_code=self._mac_code) + self.context["local_name"] = discovery_info.name + self.context["title_placeholders"] = {"name": self._display_name} + + return await self.async_step_confirm() + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle a flow initialized by the user.""" + errors: dict[str, str] = {} + if user_input is not None: + mac_code = user_input[CONF_MAC_CODE] + # Discover with BLE + try: + await self.async_discover_motionblind(mac_code) + except NoBluetoothAdapter: + return self.async_abort(reason=EXCEPTION_MAP[NoBluetoothAdapter]) + except NoDevicesFound: + return self.async_abort(reason=EXCEPTION_MAP[NoDevicesFound]) + except tuple(EXCEPTION_MAP.keys()) as e: + errors = {"base": EXCEPTION_MAP.get(type(e), str(type(e)))} + return self.async_show_form( + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors + ) + return await self.async_step_confirm() + + scanner_count = bluetooth.async_scanner_count(self.hass, connectable=True) + if not scanner_count: + _LOGGER.error("No bluetooth adapter found") + return self.async_abort(reason=EXCEPTION_MAP[NoBluetoothAdapter]) + + return self.async_show_form( + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors + ) + + async def async_step_confirm( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Confirm a single device.""" + if user_input is not None: + self._blind_type = user_input[CONF_BLIND_TYPE] + + if TYPE_CHECKING: + assert self._discovery_info is not None + + return self.async_create_entry( + title=str(self._display_name), + data={ + CONF_ADDRESS: self._discovery_info.address, + CONF_LOCAL_NAME: self._discovery_info.name, + CONF_MAC_CODE: self._mac_code, + CONF_BLIND_TYPE: self._blind_type, + }, + ) + + return self.async_show_form( + step_id="confirm", + data_schema=vol.Schema( + { + vol.Required(CONF_BLIND_TYPE): SelectSelector( + SelectSelectorConfig( + options=[ + blind_type.name.lower() + for blind_type in MotionBlindType + ], + translation_key=CONF_BLIND_TYPE, + mode=SelectSelectorMode.DROPDOWN, + ) + ) + } + ), + description_placeholders={"display_name": self._display_name}, + ) + + async def async_discover_motionblind(self, mac_code: str) -> None: + """Discover Motionblinds initialized by the user.""" + if not is_valid_mac(mac_code): + _LOGGER.error("Invalid MAC code: %s", mac_code.upper()) + raise InvalidMACCode + + scanner_count = bluetooth.async_scanner_count(self.hass, connectable=True) + if not scanner_count: + _LOGGER.error("No bluetooth adapter found") + raise NoBluetoothAdapter + + bleak_scanner = bluetooth.async_get_scanner(self.hass) + devices = await bleak_scanner.discover() + + if len(devices) == 0: + _LOGGER.error("Could not find any bluetooth devices") + raise NoDevicesFound + + motion_device: BLEDevice | None = next( + ( + device + for device in devices + if device + and device.name + and f"MOTION_{mac_code.upper()}" in device.name + ), + None, + ) + + if motion_device is None: + _LOGGER.error("Could not find a motor with MAC code: %s", mac_code.upper()) + raise CouldNotFindMotor + + await self.async_set_unique_id(motion_device.address, raise_on_progress=False) + self._abort_if_unique_id_configured() + + self._discovery_info = motion_device + self._mac_code = mac_code.upper() + self._display_name = DISPLAY_NAME.format(mac_code=self._mac_code) + + +def is_valid_mac(data: str) -> bool: + """Validate the provided MAC address.""" + + mac_regex = r"^[0-9A-Fa-f]{4}$" + return bool(re.match(mac_regex, data)) + + +def get_mac_from_local_name(data: str) -> str | None: + """Get the MAC address from the bluetooth local name.""" + + mac_regex = r"^MOTION_([0-9A-Fa-f]{4})$" + match = re.search(mac_regex, data) + return str(match.group(1)) if match else None + + +class CouldNotFindMotor(HomeAssistantError): + """Error to indicate no motor with that MAC code could be found.""" + + +class InvalidMACCode(HomeAssistantError): + """Error to indicate the MAC code is invalid.""" + + +class NoBluetoothAdapter(HomeAssistantError): + """Error to indicate no bluetooth adapter could be found.""" + + +class NoDevicesFound(HomeAssistantError): + """Error to indicate no bluetooth devices could be found.""" + + +EXCEPTION_MAP = { + NoBluetoothAdapter: ERROR_NO_BLUETOOTH_ADAPTER, + NoDevicesFound: ERROR_NO_DEVICES_FOUND, + CouldNotFindMotor: ERROR_COULD_NOT_FIND_MOTOR, + InvalidMACCode: ERROR_INVALID_MAC_CODE, +} diff --git a/homeassistant/components/motionblinds_ble/const.py b/homeassistant/components/motionblinds_ble/const.py new file mode 100644 index 00000000000..1b396dd544d --- /dev/null +++ b/homeassistant/components/motionblinds_ble/const.py @@ -0,0 +1,16 @@ +"""Constants for the Motionblinds BLE integration.""" + +CONF_LOCAL_NAME = "local_name" +CONF_MAC_CODE = "mac_code" +CONF_BLIND_TYPE = "blind_type" + +DOMAIN = "motionblinds_ble" + +ERROR_COULD_NOT_FIND_MOTOR = "could_not_find_motor" +ERROR_INVALID_MAC_CODE = "invalid_mac_code" +ERROR_NO_BLUETOOTH_ADAPTER = "no_bluetooth_adapter" +ERROR_NO_DEVICES_FOUND = "no_devices_found" + +ICON_VERTICAL_BLIND = "mdi:blinds-vertical-closed" + +MANUFACTURER = "Motionblinds" diff --git a/homeassistant/components/motionblinds_ble/cover.py b/homeassistant/components/motionblinds_ble/cover.py new file mode 100644 index 00000000000..c4f14dc5605 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/cover.py @@ -0,0 +1,230 @@ +"""Cover entities for the Motionblinds BLE integration.""" + +from __future__ import annotations + +from dataclasses import dataclass +import logging +from typing import Any + +from motionblindsble.const import MotionBlindType, MotionRunningType +from motionblindsble.device import MotionDevice + +from homeassistant.components.cover import ( + ATTR_POSITION, + ATTR_TILT_POSITION, + CoverDeviceClass, + CoverEntity, + CoverEntityDescription, + CoverEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import CONF_BLIND_TYPE, CONF_MAC_CODE, DOMAIN, ICON_VERTICAL_BLIND +from .entity import MotionblindsBLEEntity + +_LOGGER = logging.getLogger(__name__) + + +@dataclass(frozen=True, kw_only=True) +class MotionblindsBLECoverEntityDescription(CoverEntityDescription): + """Entity description of a cover entity with default values.""" + + key: str = CoverDeviceClass.BLIND.value + translation_key: str = CoverDeviceClass.BLIND.value + + +SHADE_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription( + device_class=CoverDeviceClass.SHADE +) +BLIND_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription( + device_class=CoverDeviceClass.BLIND +) +CURTAIN_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription( + device_class=CoverDeviceClass.CURTAIN +) +VERTICAL_ENTITY_DESCRIPTION = MotionblindsBLECoverEntityDescription( + device_class=CoverDeviceClass.CURTAIN, icon=ICON_VERTICAL_BLIND +) + +BLIND_TYPE_TO_ENTITY_DESCRIPTION: dict[str, MotionblindsBLECoverEntityDescription] = { + MotionBlindType.HONEYCOMB.name: SHADE_ENTITY_DESCRIPTION, + MotionBlindType.ROMAN.name: SHADE_ENTITY_DESCRIPTION, + MotionBlindType.ROLLER.name: SHADE_ENTITY_DESCRIPTION, + MotionBlindType.DOUBLE_ROLLER.name: SHADE_ENTITY_DESCRIPTION, + MotionBlindType.VENETIAN.name: BLIND_ENTITY_DESCRIPTION, + MotionBlindType.VENETIAN_TILT_ONLY.name: BLIND_ENTITY_DESCRIPTION, + MotionBlindType.CURTAIN.name: CURTAIN_ENTITY_DESCRIPTION, + MotionBlindType.VERTICAL.name: VERTICAL_ENTITY_DESCRIPTION, +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up cover entity based on a config entry.""" + + cover_class: type[MotionblindsBLECoverEntity] = BLIND_TYPE_TO_CLASS[ + entry.data[CONF_BLIND_TYPE].upper() + ] + device: MotionDevice = hass.data[DOMAIN][entry.entry_id] + entity_description: MotionblindsBLECoverEntityDescription = ( + BLIND_TYPE_TO_ENTITY_DESCRIPTION[entry.data[CONF_BLIND_TYPE].upper()] + ) + entity: MotionblindsBLECoverEntity = cover_class(device, entry, entity_description) + + async_add_entities([entity]) + + +class MotionblindsBLECoverEntity(MotionblindsBLEEntity, CoverEntity): + """Representation of a cover entity.""" + + _attr_is_closed: bool | None = None + _attr_name = None + + async def async_added_to_hass(self) -> None: + """Register device callbacks.""" + _LOGGER.debug( + "(%s) Added %s cover entity (%s)", + self.entry.data[CONF_MAC_CODE], + MotionBlindType[self.entry.data[CONF_BLIND_TYPE].upper()].value.lower(), + BLIND_TYPE_TO_CLASS[self.entry.data[CONF_BLIND_TYPE].upper()].__name__, + ) + self.device.register_running_callback(self.async_update_running) + self.device.register_position_callback(self.async_update_position) + + async def async_stop_cover(self, **kwargs: Any) -> None: + """Stop moving the cover entity.""" + _LOGGER.debug("(%s) Stopping", self.entry.data[CONF_MAC_CODE]) + await self.device.stop() + + @callback + def async_update_running( + self, running_type: MotionRunningType | None, write_state: bool = True + ) -> None: + """Update the running type (e.g. opening/closing) of the cover entity.""" + if running_type in {None, MotionRunningType.STILL, MotionRunningType.UNKNOWN}: + self._attr_is_opening = False + self._attr_is_closing = False + else: + self._attr_is_opening = running_type is MotionRunningType.OPENING + self._attr_is_closing = running_type is not MotionRunningType.OPENING + if running_type is not MotionRunningType.STILL: + self._attr_is_closed = None + if write_state: + self.async_write_ha_state() + + @callback + def async_update_position( + self, + position: int | None, + tilt: int | None, + ) -> None: + """Update the position of the cover entity.""" + if position is None: + self._attr_current_cover_position = None + self._attr_is_closed = None + else: + self._attr_current_cover_position = 100 - position + self._attr_is_closed = self._attr_current_cover_position == 0 + if tilt is None: + self._attr_current_cover_tilt_position = None + else: + self._attr_current_cover_tilt_position = 100 - round(100 * tilt / 180) + self.async_write_ha_state() + + +class PositionCover(MotionblindsBLECoverEntity): + """Representation of a cover entity with position capability.""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + ) + + async def async_open_cover(self, **kwargs: Any) -> None: + """Open the cover entity.""" + _LOGGER.debug("(%s) Opening", self.entry.data[CONF_MAC_CODE]) + await self.device.open() + + async def async_close_cover(self, **kwargs: Any) -> None: + """Close the cover entity.""" + _LOGGER.debug("(%s) Closing", self.entry.data[CONF_MAC_CODE]) + await self.device.close() + + async def async_set_cover_position(self, **kwargs: Any) -> None: + """Move the cover entity to a specific position.""" + new_position: int = 100 - int(kwargs[ATTR_POSITION]) + + _LOGGER.debug( + "(%s) Setting position to %i", + self.entry.data[CONF_MAC_CODE], + new_position, + ) + await self.device.position(new_position) + + +class TiltCover(MotionblindsBLECoverEntity): + """Representation of a cover entity with tilt capability.""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION + ) + + async def async_open_cover_tilt(self, **kwargs: Any) -> None: + """Tilt the cover entity open.""" + _LOGGER.debug("(%s) Tilt opening", self.entry.data[CONF_MAC_CODE]) + await self.device.open_tilt() + + async def async_close_cover_tilt(self, **kwargs: Any) -> None: + """Tilt the cover entity closed.""" + _LOGGER.debug("(%s) Tilt closing", self.entry.data[CONF_MAC_CODE]) + await self.device.close_tilt() + + async def async_stop_cover_tilt(self, **kwargs: Any) -> None: + """Stop tilting the cover entity.""" + await self.async_stop_cover(**kwargs) + + async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: + """Tilt the cover entity to a specific position.""" + new_tilt: int = 100 - int(kwargs[ATTR_TILT_POSITION]) + + _LOGGER.debug( + "(%s) Setting tilt position to %i", + self.entry.data[CONF_MAC_CODE], + new_tilt, + ) + await self.device.tilt(round(180 * new_tilt / 100)) + + +class PositionTiltCover(PositionCover, TiltCover): + """Representation of a cover entity with position & tilt capabilities.""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION + ) + + +BLIND_TYPE_TO_CLASS: dict[str, type[MotionblindsBLECoverEntity]] = { + MotionBlindType.ROLLER.name: PositionCover, + MotionBlindType.HONEYCOMB.name: PositionCover, + MotionBlindType.ROMAN.name: PositionCover, + MotionBlindType.VENETIAN.name: PositionTiltCover, + MotionBlindType.VENETIAN_TILT_ONLY.name: TiltCover, + MotionBlindType.DOUBLE_ROLLER.name: PositionTiltCover, + MotionBlindType.CURTAIN.name: PositionCover, + MotionBlindType.VERTICAL.name: PositionTiltCover, +} diff --git a/homeassistant/components/motionblinds_ble/entity.py b/homeassistant/components/motionblinds_ble/entity.py new file mode 100644 index 00000000000..5c2b3ae9afb --- /dev/null +++ b/homeassistant/components/motionblinds_ble/entity.py @@ -0,0 +1,52 @@ +"""Base entities for the Motionblinds BLE integration.""" + +import logging + +from motionblindsble.const import MotionBlindType +from motionblindsble.device import MotionDevice + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ADDRESS +from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo +from homeassistant.helpers.entity import Entity, EntityDescription + +from .const import CONF_BLIND_TYPE, CONF_MAC_CODE, MANUFACTURER + +_LOGGER = logging.getLogger(__name__) + + +class MotionblindsBLEEntity(Entity): + """Base class for Motionblinds BLE entities.""" + + _attr_has_entity_name = True + _attr_should_poll = False + + device: MotionDevice + entry: ConfigEntry + + def __init__( + self, + device: MotionDevice, + entry: ConfigEntry, + entity_description: EntityDescription, + unique_id_suffix: str | None = None, + ) -> None: + """Initialize the entity.""" + if unique_id_suffix is None: + self._attr_unique_id = entry.data[CONF_ADDRESS] + else: + self._attr_unique_id = f"{entry.data[CONF_ADDRESS]}_{unique_id_suffix}" + self.device = device + self.entry = entry + self.entity_description = entity_description + self._attr_device_info = DeviceInfo( + connections={(CONNECTION_BLUETOOTH, entry.data[CONF_ADDRESS])}, + manufacturer=MANUFACTURER, + model=MotionBlindType[entry.data[CONF_BLIND_TYPE].upper()].value, + name=device.display_name, + ) + + async def async_update(self) -> None: + """Update state, called by HA if there is a poll interval and by the service homeassistant.update_entity.""" + _LOGGER.debug("(%s) Updating entity", self.entry.data[CONF_MAC_CODE]) + await self.device.status_query() diff --git a/homeassistant/components/motionblinds_ble/manifest.json b/homeassistant/components/motionblinds_ble/manifest.json new file mode 100644 index 00000000000..0bf752a4119 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/manifest.json @@ -0,0 +1,18 @@ +{ + "domain": "motionblinds_ble", + "name": "Motionblinds BLE", + "bluetooth": [ + { + "local_name": "MOTION_*", + "connectable": true + } + ], + "codeowners": ["@LennP", "@jerrybboy"], + "config_flow": true, + "dependencies": ["bluetooth_adapters"], + "documentation": "https://www.home-assistant.io/integrations/motionblinds_ble", + "integration_type": "device", + "iot_class": "assumed_state", + "loggers": ["motionblindsble"], + "requirements": ["motionblindsble==0.0.8"] +} diff --git a/homeassistant/components/motionblinds_ble/strings.json b/homeassistant/components/motionblinds_ble/strings.json new file mode 100644 index 00000000000..e876d64d568 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/strings.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "no_bluetooth_adapter": "No bluetooth adapter found", + "no_devices_found": "Could not find any bluetooth devices" + }, + "error": { + "could_not_find_motor": "Could not find a motor with that MAC code", + "invalid_mac_code": "Invalid MAC code" + }, + "step": { + "user": { + "description": "Fill in the 4-character MAC code of your motor, for example F3ED or E3A6", + "data": { + "mac_code": "MAC code" + } + }, + "confirm": { + "description": "What kind of blind is {display_name}?" + } + } + }, + "selector": { + "blind_type": { + "options": { + "roller": "Roller blind", + "honeycomb": "Honeycomb blind", + "roman": "Roman blind", + "venetian": "Venetian blind", + "venetian_tilt_only": "Venetian blind (tilt-only)", + "double_roller": "Double roller blind", + "curtain": "Curtain blind", + "vertical": "Vertical blind" + } + } + } +} diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index d1972e703b4..cd8174bab1f 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -428,6 +428,11 @@ BLUETOOTH: list[dict[str, bool | str | int | list[int]]] = [ "manufacturer_id": 89, "service_uuid": "0000fee5-0000-1000-8000-00805f9b34fb", }, + { + "connectable": True, + "domain": "motionblinds_ble", + "local_name": "MOTION_*", + }, { "domain": "oralb", "manufacturer_id": 220, diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index fb7b0151bea..d779fbead64 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -326,6 +326,7 @@ FLOWS = { "moon", "mopeka", "motion_blinds", + "motionblinds_ble", "motioneye", "motionmount", "mqtt", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 044c76dc03b..2b4a637dacc 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3734,6 +3734,12 @@ "config_flow": true, "iot_class": "local_push" }, + "motionblinds_ble": { + "name": "Motionblinds BLE", + "integration_type": "device", + "config_flow": true, + "iot_class": "assumed_state" + }, "motioneye": { "name": "motionEye", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index f1fac9b7333..b8eeaebd953 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1327,6 +1327,9 @@ mopeka-iot-ble==0.7.0 # homeassistant.components.motion_blinds motionblinds==0.6.23 +# homeassistant.components.motionblinds_ble +motionblindsble==0.0.8 + # homeassistant.components.motioneye motioneye-client==0.3.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cbd51c88a26..43a92f69d36 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1066,6 +1066,9 @@ mopeka-iot-ble==0.7.0 # homeassistant.components.motion_blinds motionblinds==0.6.23 +# homeassistant.components.motionblinds_ble +motionblindsble==0.0.8 + # homeassistant.components.motioneye motioneye-client==0.3.14 diff --git a/tests/components/motionblinds_ble/__init__.py b/tests/components/motionblinds_ble/__init__.py new file mode 100644 index 00000000000..302c3266ea1 --- /dev/null +++ b/tests/components/motionblinds_ble/__init__.py @@ -0,0 +1 @@ +"""Tests for the Motionblinds BLE integration.""" diff --git a/tests/components/motionblinds_ble/conftest.py b/tests/components/motionblinds_ble/conftest.py new file mode 100644 index 00000000000..d8b0d0e2c56 --- /dev/null +++ b/tests/components/motionblinds_ble/conftest.py @@ -0,0 +1,31 @@ +"""Setup the MotionBlinds BLE tests.""" + +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +TEST_MAC = "abcd" +TEST_NAME = f"MOTION_{TEST_MAC.upper()}" +TEST_ADDRESS = "test_adress" + + +@pytest.fixture(name="motionblinds_ble_connect", autouse=True) +def motion_blinds_connect_fixture(enable_bluetooth): + """Mock motion blinds ble connection and entry setup.""" + device = Mock() + device.name = TEST_NAME + device.address = TEST_ADDRESS + + bleak_scanner = AsyncMock() + bleak_scanner.discover.return_value = [device] + + with patch( + "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_scanner_count", + return_value=1, + ), patch( + "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_get_scanner", + return_value=bleak_scanner, + ), patch( + "homeassistant.components.motionblinds_ble.async_setup_entry", return_value=True + ): + yield bleak_scanner, device diff --git a/tests/components/motionblinds_ble/test_config_flow.py b/tests/components/motionblinds_ble/test_config_flow.py new file mode 100644 index 00000000000..9451e04830a --- /dev/null +++ b/tests/components/motionblinds_ble/test_config_flow.py @@ -0,0 +1,256 @@ +"""Test the MotionBlinds BLE config flow.""" + +from unittest.mock import patch + +from motionblindsble.const import MotionBlindType + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak +from homeassistant.components.motionblinds_ble import const +from homeassistant.const import CONF_ADDRESS +from homeassistant.core import HomeAssistant + +from .conftest import TEST_ADDRESS, TEST_MAC, TEST_NAME + +from tests.components.bluetooth import generate_advertisement_data, generate_ble_device + +TEST_BLIND_TYPE = MotionBlindType.ROLLER.name.lower() + +BLIND_SERVICE_INFO = BluetoothServiceInfoBleak( + name=TEST_NAME, + address=TEST_ADDRESS, + device=generate_ble_device( + address="cc:cc:cc:cc:cc:cc", + name=TEST_NAME, + ), + rssi=-61, + manufacturer_data={000: b"test"}, + service_data={ + "test": bytearray(b"0000"), + }, + service_uuids=[ + "test", + ], + source="local", + advertisement=generate_advertisement_data( + manufacturer_data={000: b"test"}, + service_uuids=["test"], + ), + connectable=True, + time=0, +) + + +async def test_config_flow_manual_success( + hass: HomeAssistant, motionblinds_ble_connect +) -> None: + """Successful flow manually initialized by the user.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: TEST_MAC}, + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, + ) + assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"Motionblind {TEST_MAC.upper()}" + assert result["data"] == { + CONF_ADDRESS: TEST_ADDRESS, + const.CONF_LOCAL_NAME: TEST_NAME, + const.CONF_MAC_CODE: TEST_MAC.upper(), + const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, + } + assert result["options"] == {} + + +async def test_config_flow_manual_error_invalid_mac( + hass: HomeAssistant, motionblinds_ble_connect +) -> None: + """Invalid MAC code error flow manually initialized by the user.""" + + # Initialize + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + # Try invalid MAC code + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: "AABBCC"}, # A MAC code should be 4 characters + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": const.ERROR_INVALID_MAC_CODE} + + # Recover + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: TEST_MAC}, + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "confirm" + + # Finish flow + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, + ) + assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"Motionblind {TEST_MAC.upper()}" + assert result["data"] == { + CONF_ADDRESS: TEST_ADDRESS, + const.CONF_LOCAL_NAME: TEST_NAME, + const.CONF_MAC_CODE: TEST_MAC.upper(), + const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, + } + assert result["options"] == {} + + +async def test_config_flow_manual_error_no_bluetooth_adapter( + hass: HomeAssistant, motionblinds_ble_connect +) -> None: + """No Bluetooth adapter error flow manually initialized by the user.""" + + # Try step_user with zero Bluetooth adapters + with patch( + "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_scanner_count", + return_value=0, + ): + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is data_entry_flow.FlowResultType.ABORT + assert result["reason"] == const.ERROR_NO_BLUETOOTH_ADAPTER + + # Try discovery with zero Bluetooth adapters + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + with patch( + "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_scanner_count", + return_value=0, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: TEST_MAC}, + ) + assert result["type"] is data_entry_flow.FlowResultType.ABORT + assert result["reason"] == const.ERROR_NO_BLUETOOTH_ADAPTER + + +async def test_config_flow_manual_error_could_not_find_motor( + hass: HomeAssistant, motionblinds_ble_connect +) -> None: + """Could not find motor error flow manually initialized by the user.""" + + # Initialize + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + # Try with MAC code that cannot be found + motionblinds_ble_connect[1].name = "WRONG_NAME" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: TEST_MAC}, + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": const.ERROR_COULD_NOT_FIND_MOTOR} + + # Recover + motionblinds_ble_connect[1].name = TEST_NAME + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: TEST_MAC}, + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "confirm" + + # Finish flow + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, + ) + assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"Motionblind {TEST_MAC.upper()}" + assert result["data"] == { + CONF_ADDRESS: TEST_ADDRESS, + const.CONF_LOCAL_NAME: TEST_NAME, + const.CONF_MAC_CODE: TEST_MAC.upper(), + const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, + } + assert result["options"] == {} + + +async def test_config_flow_manual_error_no_devices_found( + hass: HomeAssistant, motionblinds_ble_connect +) -> None: + """No devices found error flow manually initialized by the user.""" + + # Initialize + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "user" + assert result["errors"] == {} + + # Try with zero found bluetooth devices + motionblinds_ble_connect[0].discover.return_value = [] + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_MAC_CODE: TEST_MAC}, + ) + assert result["type"] is data_entry_flow.FlowResultType.ABORT + assert result["reason"] == const.ERROR_NO_DEVICES_FOUND + + +async def test_config_flow_bluetooth_success( + hass: HomeAssistant, motionblinds_ble_connect +) -> None: + """Successful bluetooth discovery flow.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_BLUETOOTH}, + data=BLIND_SERVICE_INFO, + ) + + assert result["type"] is data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {const.CONF_BLIND_TYPE: MotionBlindType.ROLLER.name.lower()}, + ) + + assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY + assert result["title"] == f"Motionblind {TEST_MAC.upper()}" + assert result["data"] == { + CONF_ADDRESS: TEST_ADDRESS, + const.CONF_LOCAL_NAME: TEST_NAME, + const.CONF_MAC_CODE: TEST_MAC.upper(), + const.CONF_BLIND_TYPE: TEST_BLIND_TYPE, + } + assert result["options"] == {} From ddee999843d88aca59f12c235380f4bed1034c6d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 26 Mar 2024 09:57:19 +0100 Subject: [PATCH 1514/1691] Fix motion blinds formatting (#114222) --- tests/components/motionblinds_ble/conftest.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/components/motionblinds_ble/conftest.py b/tests/components/motionblinds_ble/conftest.py index d8b0d0e2c56..8cd1adb1c0e 100644 --- a/tests/components/motionblinds_ble/conftest.py +++ b/tests/components/motionblinds_ble/conftest.py @@ -19,13 +19,18 @@ def motion_blinds_connect_fixture(enable_bluetooth): bleak_scanner = AsyncMock() bleak_scanner.discover.return_value = [device] - with patch( - "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_scanner_count", - return_value=1, - ), patch( - "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_get_scanner", - return_value=bleak_scanner, - ), patch( - "homeassistant.components.motionblinds_ble.async_setup_entry", return_value=True + with ( + patch( + "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_scanner_count", + return_value=1, + ), + patch( + "homeassistant.components.motionblinds_ble.config_flow.bluetooth.async_get_scanner", + return_value=bleak_scanner, + ), + patch( + "homeassistant.components.motionblinds_ble.async_setup_entry", + return_value=True, + ), ): yield bleak_scanner, device From 0338aaf5774e9c8119f31bf512f184cccd99015e Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 26 Mar 2024 10:05:13 +0100 Subject: [PATCH 1515/1691] Allow pre-releases for "Adjust nightly version" only (#114219) --- .github/workflows/builder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index f0dd00bdfaf..ccbe3ba790b 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -99,6 +99,8 @@ jobs: - name: Adjust nightly version if: needs.init.outputs.channel == 'dev' shell: bash + env: + UV_PRERELEASE: allow run: | python3 -m pip install "$(grep '^uv' < requirements_test.txt)" uv pip install packaging tomli From e136847b89c086076c3e36dfa8526255da72366e Mon Sep 17 00:00:00 2001 From: Cyrill Raccaud Date: Tue, 26 Mar 2024 10:17:25 +0100 Subject: [PATCH 1516/1691] Add more timestamp sensors to swiss_public_transport (#107916) * add more timestamp sensors * more generic definition for future sensors * add entity descriptor * use enable property to prevent sensors from getting added * set legacy attribute flag for first sensor * remove departure from extra attributes * remove breaking changes again and keep for next pr * fix multiline statements * outsource the multiline ifs into function --------- Co-authored-by: Joost Lekkerkerker --- .../swiss_public_transport/const.py | 2 + .../swiss_public_transport/coordinator.py | 82 +++++++++---------- .../swiss_public_transport/sensor.py | 80 ++++++++++++++---- .../swiss_public_transport/strings.json | 8 +- 4 files changed, 115 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/swiss_public_transport/const.py b/homeassistant/components/swiss_public_transport/const.py index 6d9fb8bb960..6ae3cc9fd2f 100644 --- a/homeassistant/components/swiss_public_transport/const.py +++ b/homeassistant/components/swiss_public_transport/const.py @@ -7,6 +7,8 @@ CONF_START = "from" DEFAULT_NAME = "Next Destination" +SENSOR_CONNECTIONS_COUNT = 3 + PLACEHOLDERS = { "stationboard_url": "http://transport.opendata.ch/examples/stationboard.html", diff --git a/homeassistant/components/swiss_public_transport/coordinator.py b/homeassistant/components/swiss_public_transport/coordinator.py index d24dc85e3dc..7df593d5667 100644 --- a/homeassistant/components/swiss_public_transport/coordinator.py +++ b/homeassistant/components/swiss_public_transport/coordinator.py @@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed import homeassistant.util.dt as dt_util -from .const import DOMAIN +from .const import DOMAIN, SENSOR_CONNECTIONS_COUNT _LOGGER = logging.getLogger(__name__) @@ -23,8 +23,8 @@ class DataConnection(TypedDict): """A connection data class.""" departure: datetime | None - next_departure: str | None - next_on_departure: str | None + next_departure: datetime | None + next_on_departure: datetime | None duration: str platform: str remaining_time: str @@ -35,7 +35,9 @@ class DataConnection(TypedDict): delay: int -class SwissPublicTransportDataUpdateCoordinator(DataUpdateCoordinator[DataConnection]): +class SwissPublicTransportDataUpdateCoordinator( + DataUpdateCoordinator[list[DataConnection]] +): """A SwissPublicTransport Data Update Coordinator.""" config_entry: ConfigEntry @@ -50,7 +52,22 @@ class SwissPublicTransportDataUpdateCoordinator(DataUpdateCoordinator[DataConnec ) self._opendata = opendata - async def _async_update_data(self) -> DataConnection: + def remaining_time(self, departure) -> timedelta | None: + """Calculate the remaining time for the departure.""" + departure_datetime = dt_util.parse_datetime(departure) + + if departure_datetime: + return departure_datetime - dt_util.as_local(dt_util.utcnow()) + return None + + def nth_departure_time(self, i: int) -> datetime | None: + """Get nth departure time.""" + connections = self._opendata.connections + if len(connections) > i and connections[i] is not None: + return dt_util.parse_datetime(connections[i]["departure"]) + return None + + async def _async_update_data(self) -> list[DataConnection]: try: await self._opendata.async_get_data() except OpendataTransportError as e: @@ -59,41 +76,22 @@ class SwissPublicTransportDataUpdateCoordinator(DataUpdateCoordinator[DataConnec ) raise UpdateFailed from e - departure_time = ( - dt_util.parse_datetime(self._opendata.connections[0]["departure"]) - if self._opendata.connections[0] is not None - else None - ) - next_departure_time = ( - dt_util.parse_datetime(self._opendata.connections[1]["departure"]) - if self._opendata.connections[1] is not None - else None - ) - next_on_departure_time = ( - dt_util.parse_datetime(self._opendata.connections[2]["departure"]) - if self._opendata.connections[2] is not None - else None - ) + connections = self._opendata.connections - if departure_time: - remaining_time = departure_time - dt_util.as_local(dt_util.utcnow()) - else: - remaining_time = None - - return DataConnection( - departure=departure_time, - next_departure=next_departure_time.isoformat() - if next_departure_time is not None - else None, - next_on_departure=next_on_departure_time.isoformat() - if next_on_departure_time is not None - else None, - train_number=self._opendata.connections[0]["number"], - platform=self._opendata.connections[0]["platform"], - transfers=self._opendata.connections[0]["transfers"], - duration=self._opendata.connections[0]["duration"], - start=self._opendata.from_name, - destination=self._opendata.to_name, - remaining_time=f"{remaining_time}", - delay=self._opendata.connections[0]["delay"], - ) + return [ + DataConnection( + departure=self.nth_departure_time(i), + next_departure=self.nth_departure_time(i + 1), + next_on_departure=self.nth_departure_time(i + 2), + train_number=connections[i]["number"], + platform=connections[i]["platform"], + transfers=connections[i]["transfers"], + duration=connections[i]["duration"], + start=self._opendata.from_name, + destination=self._opendata.to_name, + remaining_time=str(self.remaining_time(connections[i]["departure"])), + delay=connections[i]["delay"], + ) + for i in range(SENSOR_CONNECTIONS_COUNT) + if len(connections) > i and connections[i] is not None + ] diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 4bca9aade60..7c712c8c189 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -2,6 +2,8 @@ from __future__ import annotations +from collections.abc import Callable +from dataclasses import dataclass from datetime import datetime, timedelta import logging from typing import TYPE_CHECKING @@ -13,6 +15,7 @@ from homeassistant.components.sensor import ( PLATFORM_SCHEMA, SensorDeviceClass, SensorEntity, + SensorEntityDescription, ) from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_NAME @@ -25,8 +28,15 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import CONF_DESTINATION, CONF_START, DEFAULT_NAME, DOMAIN, PLACEHOLDERS -from .coordinator import SwissPublicTransportDataUpdateCoordinator +from .const import ( + CONF_DESTINATION, + CONF_START, + DEFAULT_NAME, + DOMAIN, + PLACEHOLDERS, + SENSOR_CONNECTIONS_COUNT, +) +from .coordinator import DataConnection, SwissPublicTransportDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -41,6 +51,33 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) +@dataclass(kw_only=True, frozen=True) +class SwissPublicTransportSensorEntityDescription(SensorEntityDescription): + """Describes swiss public transport sensor entity.""" + + exists_fn: Callable[[DataConnection], bool] + value_fn: Callable[[DataConnection], datetime | None] + + index: int + has_legacy_attributes: bool + + +SENSORS: tuple[SwissPublicTransportSensorEntityDescription, ...] = ( + *[ + SwissPublicTransportSensorEntityDescription( + key=f"departure{i or ''}", + translation_key=f"departure{i}", + device_class=SensorDeviceClass.TIMESTAMP, + has_legacy_attributes=i == 0, + value_fn=lambda data_connection: data_connection["departure"], + exists_fn=lambda data_connection: data_connection is not None, + index=i, + ) + for i in range(SENSOR_CONNECTIONS_COUNT) + ], +) + + async def async_setup_entry( hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry, @@ -55,7 +92,8 @@ async def async_setup_entry( assert unique_id async_add_entities( - [SwissPublicTransportSensor(coordinator, unique_id)], + SwissPublicTransportSensor(coordinator, description, unique_id) + for description in SENSORS ) @@ -108,34 +146,51 @@ class SwissPublicTransportSensor( ): """Implementation of a Swiss public transport sensor.""" + entity_description: SwissPublicTransportSensorEntityDescription _attr_attribution = "Data provided by transport.opendata.ch" _attr_has_entity_name = True - _attr_translation_key = "departure" - _attr_device_class = SensorDeviceClass.TIMESTAMP def __init__( self, coordinator: SwissPublicTransportDataUpdateCoordinator, + entity_description: SwissPublicTransportSensorEntityDescription, unique_id: str, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) - self._attr_unique_id = f"{unique_id}_departure" + self.entity_description = entity_description + self._attr_unique_id = f"{unique_id}_{entity_description.key}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, unique_id)}, manufacturer="Opendata.ch", entry_type=DeviceEntryType.SERVICE, ) + @property + def enabled(self) -> bool: + """Enable the sensor if data is available.""" + return self.entity_description.exists_fn( + self.coordinator.data[self.entity_description.index] + ) + + @property + def native_value(self) -> datetime | None: + """Return the state of the sensor.""" + return self.entity_description.value_fn( + self.coordinator.data[self.entity_description.index] + ) + async def async_added_to_hass(self) -> None: """Prepare the extra attributes at start.""" - self._async_update_attrs() + if self.entity_description.has_legacy_attributes: + self._async_update_attrs() await super().async_added_to_hass() @callback def _handle_coordinator_update(self) -> None: """Handle the state update and prepare the extra state attributes.""" - self._async_update_attrs() + if self.entity_description.has_legacy_attributes: + self._async_update_attrs() return super()._handle_coordinator_update() @callback @@ -143,11 +198,8 @@ class SwissPublicTransportSensor( """Update the extra state attributes based on the coordinator data.""" self._attr_extra_state_attributes = { key: value - for key, value in self.coordinator.data.items() + for key, value in self.coordinator.data[ + self.entity_description.index + ].items() if key not in {"departure"} } - - @property - def native_value(self) -> datetime | None: - """Return the state of the sensor.""" - return self.coordinator.data["departure"] diff --git a/homeassistant/components/swiss_public_transport/strings.json b/homeassistant/components/swiss_public_transport/strings.json index 6d0eb53ad11..c0e88f08b8d 100644 --- a/homeassistant/components/swiss_public_transport/strings.json +++ b/homeassistant/components/swiss_public_transport/strings.json @@ -24,8 +24,14 @@ }, "entity": { "sensor": { - "departure": { + "departure0": { "name": "Departure" + }, + "departure1": { + "name": "Departure +1" + }, + "departure2": { + "name": "Departure +2" } } }, From 5c69e0d2c62f5b4eb03c6bf0708f5853a815a7a4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 26 Mar 2024 10:32:29 +0100 Subject: [PATCH 1517/1691] Add label template functions (#111024) --- homeassistant/helpers/template.py | 109 +++++++++ tests/helpers/test_template.py | 361 ++++++++++++++++++++++++++++++ 2 files changed, 470 insertions(+) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index fc38b821eee..a48f0133e84 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -85,6 +85,7 @@ from . import ( entity_registry, floor_registry as fr, issue_registry, + label_registry, location as loc_helper, ) from .singleton import singleton @@ -1560,6 +1561,92 @@ def area_devices(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]: return [entry.id for entry in entries] +def labels(hass: HomeAssistant, lookup_value: Any = None) -> Iterable[str | None]: + """Return all labels, or those from a area ID, device ID, or entity ID.""" + label_reg = label_registry.async_get(hass) + if lookup_value is None: + return [label.label_id for label in label_reg.async_list_labels()] + + ent_reg = entity_registry.async_get(hass) + + # Import here, not at top-level to avoid circular import + from . import config_validation as cv # pylint: disable=import-outside-toplevel + + lookup_value = str(lookup_value) + + try: + cv.entity_id(lookup_value) + except vol.Invalid: + pass + else: + if entity := ent_reg.async_get(lookup_value): + return list(entity.labels) + + # Check if this could be a device ID + dev_reg = device_registry.async_get(hass) + if device := dev_reg.async_get(lookup_value): + return list(device.labels) + + # Check if this could be a area ID + area_reg = area_registry.async_get(hass) + if area := area_reg.async_get_area(lookup_value): + return list(area.labels) + + return [] + + +def label_id(hass: HomeAssistant, lookup_value: Any) -> str | None: + """Get the label ID from a label name.""" + label_reg = label_registry.async_get(hass) + if label := label_reg.async_get_label_by_name(str(lookup_value)): + return label.label_id + return None + + +def label_name(hass: HomeAssistant, lookup_value: str) -> str | None: + """Get the label name from a label ID.""" + label_reg = label_registry.async_get(hass) + if label := label_reg.async_get_label(lookup_value): + return label.name + return None + + +def _label_id_or_name(hass: HomeAssistant, label_id_or_name: str) -> str | None: + """Get the label ID from a label name or ID.""" + # If label_name returns a value, we know the input was an ID, otherwise we + # assume it's a name, and if it's neither, we return early. + if label_name(hass, label_id_or_name) is not None: + return label_id_or_name + return label_id(hass, label_id_or_name) + + +def label_areas(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]: + """Return areas for a given label ID or name.""" + if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None: + return [] + area_reg = area_registry.async_get(hass) + entries = area_registry.async_entries_for_label(area_reg, _label_id) + return [entry.id for entry in entries] + + +def label_devices(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]: + """Return device IDs for a given label ID or name.""" + if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None: + return [] + dev_reg = device_registry.async_get(hass) + entries = device_registry.async_entries_for_label(dev_reg, _label_id) + return [entry.id for entry in entries] + + +def label_entities(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]: + """Return entities for a given label ID or name.""" + if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None: + return [] + ent_reg = entity_registry.async_get(hass) + entries = entity_registry.async_entries_for_label(ent_reg, _label_id) + return [entry.entity_id for entry in entries] + + def closest(hass, *args): """Find closest entity. @@ -2731,6 +2818,24 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): self.globals["integration_entities"] = hassfunction(integration_entities) self.filters["integration_entities"] = self.globals["integration_entities"] + self.globals["labels"] = hassfunction(labels) + self.filters["labels"] = self.globals["labels"] + + self.globals["label_id"] = hassfunction(label_id) + self.filters["label_id"] = self.globals["label_id"] + + self.globals["label_name"] = hassfunction(label_name) + self.filters["label_name"] = self.globals["label_name"] + + self.globals["label_areas"] = hassfunction(label_areas) + self.filters["label_areas"] = self.globals["label_areas"] + + self.globals["label_devices"] = hassfunction(label_devices) + self.filters["label_devices"] = self.globals["label_devices"] + + self.globals["label_entities"] = hassfunction(label_entities) + self.filters["label_entities"] = self.globals["label_entities"] + if limited: # Only device_entities is available to limited templates, mark other # functions and filters as unsupported. @@ -2764,6 +2869,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): "floor_name", "relative_time", "today_at", + "label_id", + "label_name", ] hass_filters = [ "closest", @@ -2774,6 +2881,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment): "floor_id", "floor_name", "has_value", + "label_id", + "label_name", ] hass_tests = [ "has_value", diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 211877bca6b..6f455c3dda4 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -40,6 +40,7 @@ from homeassistant.helpers import ( entity_registry as er, floor_registry as fr, issue_registry as ir, + label_registry as lr, template, translation, ) @@ -5309,3 +5310,363 @@ async def test_floor_areas( info = render_to_info(hass, f"{{{{ '{floor.name}' | floor_areas }}}}") assert_result_info(info, [area.id]) assert info.rate_limit is None + + +async def test_labels( + hass: HomeAssistant, + label_registry: lr.LabelRegistry, + area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test labels function.""" + + # Test no labels + info = render_to_info(hass, "{{ labels() }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test one label + label1 = label_registry.async_create("label1") + info = render_to_info(hass, "{{ labels() }}") + assert_result_info(info, [label1.label_id]) + assert info.rate_limit is None + + # Test multiple label + label2 = label_registry.async_create("label2") + info = render_to_info(hass, "{{ labels() }}") + assert_result_info(info, [label1.label_id, label2.label_id]) + assert info.rate_limit is None + + # Test non-exsting entity ID + info = render_to_info(hass, "{{ labels('sensor.fake') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'sensor.fake' | labels }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test non existing device ID (hex value) + info = render_to_info(hass, "{{ labels('123abc') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ '123abc' | labels }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Create a device & entity for testing + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_entry = entity_registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + device_id=device_entry.id, + ) + + # Test entity, which has no labels + info = render_to_info(hass, f"{{{{ labels('{entity_entry.entity_id}') }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{entity_entry.entity_id}' | labels }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test device, which has no labels + info = render_to_info(hass, f"{{{{ labels('{device_entry.id}') }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{device_entry.id}' | labels }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Add labels to the entity & device + device_entry = device_registry.async_update_device( + device_entry.id, labels=[label1.label_id] + ) + entity_entry = entity_registry.async_update_entity( + entity_entry.entity_id, labels=[label2.label_id] + ) + + # Test entity, which now has a label + info = render_to_info(hass, f"{{{{ '{entity_entry.entity_id}' | labels }}}}") + assert_result_info(info, [label2.label_id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ labels('{entity_entry.entity_id}') }}}}") + assert_result_info(info, [label2.label_id]) + assert info.rate_limit is None + + # Test device, which now has a label + info = render_to_info(hass, f"{{{{ '{device_entry.id}' | labels }}}}") + assert_result_info(info, [label1.label_id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ labels('{device_entry.id}') }}}}") + assert_result_info(info, [label1.label_id]) + assert info.rate_limit is None + + # Create area for testing + area = area_registry.async_create("living room") + + # Test area, which has no labels + info = render_to_info(hass, f"{{{{ '{area.id}' | labels }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ labels('{area.id}') }}}}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Add label to the area + area_registry.async_update(area.id, labels=[label1.label_id, label2.label_id]) + + # Test area, which now has labels + info = render_to_info(hass, f"{{{{ '{area.id}' | labels }}}}") + assert_result_info(info, [label1.label_id, label2.label_id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ labels('{area.id}') }}}}") + assert_result_info(info, [label1.label_id, label2.label_id]) + assert info.rate_limit is None + + +async def test_label_id( + hass: HomeAssistant, + label_registry: lr.LabelRegistry, +) -> None: + """Test label_id function.""" + # Test non existing label name + info = render_to_info(hass, "{{ label_id('non-existing label') }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'non-existing label' | label_id }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ label_id(42) }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | label_id }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test with an actual label + label = label_registry.async_create("existing label") + info = render_to_info(hass, "{{ label_id('existing label') }}") + assert_result_info(info, label.label_id) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'existing label' | label_id }}") + assert_result_info(info, label.label_id) + assert info.rate_limit is None + + +async def test_label_name( + hass: HomeAssistant, + label_registry: lr.LabelRegistry, +) -> None: + """Test label_name function.""" + # Test non existing label ID + info = render_to_info(hass, "{{ label_name('1234567890') }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ '1234567890' | label_name }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ label_name(42) }}") + assert_result_info(info, None) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | label_name }}") + assert_result_info(info, None) + assert info.rate_limit is None + + # Test non existing label ID + label = label_registry.async_create("choo choo") + info = render_to_info(hass, f"{{{{ label_name('{label.label_id}') }}}}") + assert_result_info(info, label.name) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_name }}}}") + assert_result_info(info, label.name) + assert info.rate_limit is None + + +async def test_label_entities( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + label_registry: lr.LabelRegistry, +) -> None: + """Test label_entities function.""" + + # Test non existing device ID + info = render_to_info(hass, "{{ label_entities('deadbeef') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'deadbeef' | label_entities }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ label_entities(42) }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | label_entities }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Create a fake config entry with a entity + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + entity_entry = entity_registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + ) + + # Add a label to the entity + label = label_registry.async_create("Romantic Lights") + entity_registry.async_update_entity(entity_entry.entity_id, labels={label.label_id}) + + # Get entities by label ID + info = render_to_info(hass, f"{{{{ label_entities('{label.label_id}') }}}}") + assert_result_info(info, ["light.hue_5678"]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_entities }}}}") + assert_result_info(info, ["light.hue_5678"]) + assert info.rate_limit is None + + # Get entities by label name + info = render_to_info(hass, f"{{{{ label_entities('{label.name}') }}}}") + assert_result_info(info, ["light.hue_5678"]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.name}' | label_entities }}}}") + assert_result_info(info, ["light.hue_5678"]) + assert info.rate_limit is None + + +async def test_label_devices( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + label_registry: ar.AreaRegistry, +) -> None: + """Test label_devices function.""" + + # Test non existing device ID + info = render_to_info(hass, "{{ label_devices('deadbeef') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'deadbeef' | label_devices }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ label_devices(42) }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | label_devices }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Create a fake config entry with a device + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + + # Add a label to it + label = label_registry.async_create("Romantic Lights") + device_registry.async_update_device(device_entry.id, labels=[label.label_id]) + + # Get the devices from a label by its ID + info = render_to_info(hass, f"{{{{ label_devices('{label.label_id}') }}}}") + assert_result_info(info, [device_entry.id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_devices }}}}") + assert_result_info(info, [device_entry.id]) + assert info.rate_limit is None + + # Get the devices from a label by its name + info = render_to_info(hass, f"{{{{ label_devices('{label.name}') }}}}") + assert_result_info(info, [device_entry.id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.name}' | label_devices }}}}") + assert_result_info(info, [device_entry.id]) + assert info.rate_limit is None + + +async def test_label_areas( + hass: HomeAssistant, + area_registry: ar.AreaRegistry, + label_registry: lr.LabelRegistry, +) -> None: + """Test label_areas function.""" + + # Test non existing area ID + info = render_to_info(hass, "{{ label_areas('deadbeef') }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 'deadbeef' | label_areas }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Test wrong value type + info = render_to_info(hass, "{{ label_areas(42) }}") + assert_result_info(info, []) + assert info.rate_limit is None + + info = render_to_info(hass, "{{ 42 | label_areas }}") + assert_result_info(info, []) + assert info.rate_limit is None + + # Create an area with an label + label = label_registry.async_create("Upstairs") + master_bedroom = area_registry.async_create( + "Master Bedroom", labels=[label.label_id] + ) + + # Get areas by label ID + info = render_to_info(hass, f"{{{{ label_areas('{label.label_id}') }}}}") + assert_result_info(info, [master_bedroom.id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_areas }}}}") + assert_result_info(info, [master_bedroom.id]) + assert info.rate_limit is None + + # Get areas by label name + info = render_to_info(hass, f"{{{{ label_areas('{label.name}') }}}}") + assert_result_info(info, [master_bedroom.id]) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{label.name}' | label_areas }}}}") + assert_result_info(info, [master_bedroom.id]) + assert info.rate_limit is None From 788813aad6dab3686c3bb433265dbc3ce2b9412c Mon Sep 17 00:00:00 2001 From: Duco Sebel <74970928+DCSBL@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:34:36 +0100 Subject: [PATCH 1518/1691] Implement Identify button and Cloud connection toggle for HomeWizard Watermeter (#113814) --- .../components/homewizard/coordinator.py | 12 +-- .../components/homewizard/manifest.json | 2 +- homeassistant/components/homewizard/switch.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../homewizard/fixtures/HWE-WTR/system.json | 3 + .../snapshots/test_diagnostics.ambr | 4 +- .../homewizard/snapshots/test_switch.ambr | 80 +++++++++++++++++++ tests/components/homewizard/test_button.py | 4 +- tests/components/homewizard/test_switch.py | 2 +- 10 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 tests/components/homewizard/fixtures/HWE-WTR/system.json diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index 22133a04234..db41d1dd128 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging from homewizard_energy import HomeWizardEnergy -from homewizard_energy.const import SUPPORTS_IDENTIFY, SUPPORTS_STATE, SUPPORTS_SYSTEM +from homewizard_energy.const import SUPPORTS_IDENTIFY, SUPPORTS_STATE from homewizard_energy.errors import DisabledError, RequestError, UnsupportedError from homewizard_energy.models import Device @@ -53,8 +53,7 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] if self.supports_state(data.device): data.state = await self.api.state() - if self.supports_system(data.device): - data.system = await self.api.system() + data.system = await self.api.system() except UnsupportedError as ex: # Old firmware, ignore @@ -94,13 +93,6 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] return device.product_type in SUPPORTS_STATE - def supports_system(self, device: Device | None = None) -> bool: - """Return True if the device supports system.""" - if device is None: - device = self.data.device - - return device.product_type in SUPPORTS_SYSTEM - def supports_identify(self, device: Device | None = None) -> bool: """Return True if the device supports identify.""" if device is None: diff --git a/homeassistant/components/homewizard/manifest.json b/homeassistant/components/homewizard/manifest.json index 149d5b891f4..7355d9405df 100644 --- a/homeassistant/components/homewizard/manifest.json +++ b/homeassistant/components/homewizard/manifest.json @@ -7,6 +7,6 @@ "iot_class": "local_polling", "loggers": ["homewizard_energy"], "quality_scale": "platinum", - "requirements": ["python-homewizard-energy==4.3.1"], + "requirements": ["python-homewizard-energy==v5.0.0"], "zeroconf": ["_hwenergy._tcp.local."] } diff --git a/homeassistant/components/homewizard/switch.py b/homeassistant/components/homewizard/switch.py index eb954709be0..299eb9e806b 100644 --- a/homeassistant/components/homewizard/switch.py +++ b/homeassistant/components/homewizard/switch.py @@ -57,7 +57,7 @@ SWITCHES = [ key="cloud_connection", translation_key="cloud_connection", entity_category=EntityCategory.CONFIG, - create_fn=lambda coordinator: coordinator.supports_system(), + create_fn=lambda _: True, available_fn=lambda data: data.system is not None, is_on_fn=lambda data: data.system.cloud_enabled if data.system else None, set_fn=lambda api, active: api.system_set(cloud_enabled=active), diff --git a/requirements_all.txt b/requirements_all.txt index b8eeaebd953..dc3c262f3dd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2229,7 +2229,7 @@ python-gitlab==1.6.0 python-homeassistant-analytics==0.6.0 # homeassistant.components.homewizard -python-homewizard-energy==4.3.1 +python-homewizard-energy==v5.0.0 # homeassistant.components.hp_ilo python-hpilo==4.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 43a92f69d36..eb7b2162cc5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1720,7 +1720,7 @@ python-fullykiosk==0.0.12 python-homeassistant-analytics==0.6.0 # homeassistant.components.homewizard -python-homewizard-energy==4.3.1 +python-homewizard-energy==v5.0.0 # homeassistant.components.izone python-izone==1.2.9 diff --git a/tests/components/homewizard/fixtures/HWE-WTR/system.json b/tests/components/homewizard/fixtures/HWE-WTR/system.json new file mode 100644 index 00000000000..362491b3519 --- /dev/null +++ b/tests/components/homewizard/fixtures/HWE-WTR/system.json @@ -0,0 +1,3 @@ +{ + "cloud_enabled": true +} diff --git a/tests/components/homewizard/snapshots/test_diagnostics.ambr b/tests/components/homewizard/snapshots/test_diagnostics.ambr index f42c2abfc95..ed744083373 100644 --- a/tests/components/homewizard/snapshots/test_diagnostics.ambr +++ b/tests/components/homewizard/snapshots/test_diagnostics.ambr @@ -544,7 +544,9 @@ 'serial': '**REDACTED**', }), 'state': None, - 'system': None, + 'system': dict({ + 'cloud_enabled': True, + }), }), 'entry': dict({ 'ip_address': '**REDACTED**', diff --git a/tests/components/homewizard/snapshots/test_switch.ambr b/tests/components/homewizard/snapshots/test_switch.ambr index 8877fe01a91..99a5bcab6cb 100644 --- a/tests/components/homewizard/snapshots/test_switch.ambr +++ b/tests/components/homewizard/snapshots/test_switch.ambr @@ -641,6 +641,86 @@ 'via_device_id': None, }) # --- +# name: test_switch_entities[HWE-WTR-switch.device_cloud_connection-system_set-cloud_enabled] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Device Cloud connection', + }), + 'context': , + 'entity_id': 'switch.device_cloud_connection', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch_entities[HWE-WTR-switch.device_cloud_connection-system_set-cloud_enabled].1 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.device_cloud_connection', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Cloud connection', + 'platform': 'homewizard', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'cloud_connection', + 'unique_id': 'aabbccddeeff_cloud_connection', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch_entities[HWE-WTR-switch.device_cloud_connection-system_set-cloud_enabled].2 + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + tuple( + 'mac', + '3c:39:e7:aa:bb:cc', + ), + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'homewizard', + '3c39e7aabbcc', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'HomeWizard', + 'model': 'HWE-WTR', + 'name': 'Device', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '2.03', + 'via_device_id': None, + }) +# --- # name: test_switch_entities[SDM230-switch.device_cloud_connection-system_set-cloud_enabled] StateSnapshot({ 'attributes': ReadOnlyDict({ diff --git a/tests/components/homewizard/test_button.py b/tests/components/homewizard/test_button.py index 7f7ada37644..928e6f21901 100644 --- a/tests/components/homewizard/test_button.py +++ b/tests/components/homewizard/test_button.py @@ -18,9 +18,7 @@ pytestmark = [ ] -@pytest.mark.parametrize( - "device_fixture", ["HWE-WTR", "SDM230", "SDM630", "HWE-KWH1", "HWE-KWH3"] -) +@pytest.mark.parametrize("device_fixture", ["SDM230", "SDM630", "HWE-KWH1", "HWE-KWH3"]) async def test_identify_button_entity_not_loaded_when_not_available( hass: HomeAssistant, ) -> None: diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index e9d036a01d8..b9e812620e8 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -42,7 +42,6 @@ pytestmark = [ [ "switch.device", "switch.device_switch_lock", - "switch.device_cloud_connection", ], ), ( @@ -93,6 +92,7 @@ async def test_entities_not_created_for_device( ("HWE-SKT-21", "switch.device", "state_set", "power_on"), ("HWE-SKT-21", "switch.device_switch_lock", "state_set", "switch_lock"), ("HWE-SKT-21", "switch.device_cloud_connection", "system_set", "cloud_enabled"), + ("HWE-WTR", "switch.device_cloud_connection", "system_set", "cloud_enabled"), ("SDM230", "switch.device_cloud_connection", "system_set", "cloud_enabled"), ("SDM630", "switch.device_cloud_connection", "system_set", "cloud_enabled"), ("HWE-KWH1", "switch.device_cloud_connection", "system_set", "cloud_enabled"), From bac527f289161dd3d8ab2948230e56cbd8617ef8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 26 Mar 2024 10:35:47 +0100 Subject: [PATCH 1519/1691] Don't allow listening to state_reported in event triggers (#114191) Co-authored-by: Robert Svensson --- .../homeassistant/triggers/event.py | 23 ++++++- .../homeassistant/triggers/test_event.py | 63 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 264e2f9d440..85bd2708d5e 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -7,8 +7,9 @@ from typing import Any import voluptuous as vol -from homeassistant.const import CONF_EVENT_DATA, CONF_PLATFORM +from homeassistant.const import CONF_EVENT_DATA, CONF_PLATFORM, EVENT_STATE_REPORTED from homeassistant.core import CALLBACK_TYPE, Event, HassJob, HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo from homeassistant.helpers.typing import ConfigType @@ -16,10 +17,24 @@ from homeassistant.helpers.typing import ConfigType CONF_EVENT_TYPE = "event_type" CONF_EVENT_CONTEXT = "context" + +def _validate_event_types(value: Any) -> Any: + """Validate the event types. + + If the event types are templated, we check when attaching the trigger. + """ + templates: list[template.Template] = value + if any(tpl.is_static and tpl.template == EVENT_STATE_REPORTED for tpl in templates): + raise vol.Invalid(f"Can't listen to {EVENT_STATE_REPORTED} in event trigger") + return value + + TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( { vol.Required(CONF_PLATFORM): "event", - vol.Required(CONF_EVENT_TYPE): vol.All(cv.ensure_list, [cv.template]), + vol.Required(CONF_EVENT_TYPE): vol.All( + cv.ensure_list, [cv.template], _validate_event_types + ), vol.Optional(CONF_EVENT_DATA): vol.All(dict, cv.template_complex), vol.Optional(CONF_EVENT_CONTEXT): vol.All(dict, cv.template_complex), } @@ -49,6 +64,10 @@ async def async_attach_trigger( event_types = template.render_complex( config[CONF_EVENT_TYPE], variables, limited=True ) + if EVENT_STATE_REPORTED in event_types: + raise HomeAssistantError( + f"Can't listen to {EVENT_STATE_REPORTED} in event trigger" + ) event_data_schema: vol.Schema | None = None event_data_items: ItemsView | None = None if CONF_EVENT_DATA in config: diff --git a/tests/components/homeassistant/triggers/test_event.py b/tests/components/homeassistant/triggers/test_event.py index af781bd1802..a0c1f6cb45d 100644 --- a/tests/components/homeassistant/triggers/test_event.py +++ b/tests/components/homeassistant/triggers/test_event.py @@ -505,3 +505,66 @@ async def test_event_data_with_list(hass: HomeAssistant, calls) -> None: hass.bus.async_fire("test_event", {"some_attr": [1, 2, 3]}) await hass.async_block_till_done() assert len(calls) == 1 + + +@pytest.mark.parametrize( + "event_type", ["state_reported", ["test_event", "state_reported"]] +) +async def test_state_reported_event( + hass: HomeAssistant, calls, caplog, event_type: list[str] +) -> None: + """Test triggering on state reported event.""" + context = Context() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger": {"platform": "event", "event_type": event_type}, + "action": { + "service": "test.automation", + "data_template": {"id": "{{ trigger.id}}"}, + }, + } + }, + ) + + hass.bus.async_fire("test_event", context=context) + await hass.async_block_till_done() + assert len(calls) == 0 + assert ( + "Unnamed automation failed to setup triggers and has been disabled: Can't " + "listen to state_reported in event trigger for dictionary value @ " + "data['event_type']. Got None" in caplog.text + ) + + +async def test_templated_state_reported_event( + hass: HomeAssistant, calls, caplog +) -> None: + """Test triggering on state reported event.""" + context = Context() + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "trigger_variables": {"event_type": "state_reported"}, + "trigger": {"platform": "event", "event_type": "{{event_type}}"}, + "action": { + "service": "test.automation", + "data_template": {"id": "{{ trigger.id}}"}, + }, + } + }, + ) + + hass.bus.async_fire("test_event", context=context) + await hass.async_block_till_done() + assert len(calls) == 0 + assert ( + "Got error 'Can't listen to state_reported in event trigger' " + "when setting up triggers for automation 0" in caplog.text + ) From dd43947ca0725d048a9a64254acea26c6190ba99 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 26 Mar 2024 10:36:08 +0100 Subject: [PATCH 1520/1691] Add test to ensure non callback event filter is rejected (#114182) --- tests/test_core.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 89b83e15122..1aaf417f9eb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3273,6 +3273,21 @@ async def test_eventbus_lazy_object_creation(hass: HomeAssistant) -> None: unsub() +async def test_event_filter_sanity_checks(hass: HomeAssistant) -> None: + """Test raising on bad event filters.""" + + @ha.callback + def listener(event): + """Mock listener.""" + + def bad_filter(event_data): + """Mock filter.""" + return False + + with pytest.raises(HomeAssistantError): + hass.bus.async_listen("test", listener, event_filter=bad_filter) + + async def test_statemachine_report_state(hass: HomeAssistant) -> None: """Test report state event.""" From eb81a4204e1ec32d15519524f93d9dc726298c3e Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:38:29 +0100 Subject: [PATCH 1521/1691] Allow string formatting for dispatcher SignalType (#114174) --- homeassistant/components/mqtt/discovery.py | 11 ++++++++--- homeassistant/helpers/discovery.py | 10 ++++++++-- homeassistant/helpers/dispatcher.py | 18 ++++++++++++++++-- tests/common.py | 5 ++++- tests/helpers/test_dispatcher.py | 22 ++++++++++++++++++++++ 5 files changed, 58 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index 0e1b0a799d6..f50a4e4a3f7 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -18,6 +18,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResultType import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( + SignalTypeFormat, async_dispatcher_connect, async_dispatcher_send, ) @@ -79,10 +80,14 @@ SUPPORTED_COMPONENTS = { "water_heater", } -MQTT_DISCOVERY_UPDATED = "mqtt_discovery_updated_{}" -MQTT_DISCOVERY_NEW = "mqtt_discovery_new_{}_{}" +MQTT_DISCOVERY_UPDATED: SignalTypeFormat[MQTTDiscoveryPayload] = SignalTypeFormat( + "mqtt_discovery_updated_{}" +) +MQTT_DISCOVERY_NEW: SignalTypeFormat[MQTTDiscoveryPayload] = SignalTypeFormat( + "mqtt_discovery_new_{}_{}" +) MQTT_DISCOVERY_NEW_COMPONENT = "mqtt_discovery_new_component" -MQTT_DISCOVERY_DONE = "mqtt_discovery_done_{}" +MQTT_DISCOVERY_DONE: SignalTypeFormat[Any] = SignalTypeFormat("mqtt_discovery_done_{}") TOPIC_BASE = "~" diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 98419ae6bf2..0bad52dff08 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -15,10 +15,16 @@ from homeassistant import core, setup from homeassistant.const import Platform from homeassistant.loader import bind_hass -from .dispatcher import async_dispatcher_connect, async_dispatcher_send +from .dispatcher import ( + SignalTypeFormat, + async_dispatcher_connect, + async_dispatcher_send, +) from .typing import ConfigType, DiscoveryInfoType -SIGNAL_PLATFORM_DISCOVERED = "discovery.platform_discovered_{}" +SIGNAL_PLATFORM_DISCOVERED: SignalTypeFormat[DiscoveryDict] = SignalTypeFormat( + "discovery.platform_discovered_{}" +) EVENT_LOAD_PLATFORM = "load_platform.{}" ATTR_PLATFORM = "platform" ATTR_DISCOVERED = "discovered" diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index 22d9c3bbab8..4633e81c78b 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -20,8 +20,8 @@ DATA_DISPATCHER = "dispatcher" @dataclass(frozen=True) -class SignalType(Generic[*_Ts]): - """Generic string class for signal to improve typing.""" +class _SignalTypeBase(Generic[*_Ts]): + """Generic base class for SignalType.""" name: str @@ -40,6 +40,20 @@ class SignalType(Generic[*_Ts]): return False +@dataclass(frozen=True, eq=False) +class SignalType(_SignalTypeBase[*_Ts]): + """Generic string class for signal to improve typing.""" + + +@dataclass(frozen=True, eq=False) +class SignalTypeFormat(_SignalTypeBase[*_Ts]): + """Generic string class for signal. Requires call to 'format' before use.""" + + def format(self, *args: Any, **kwargs: Any) -> SignalType[*_Ts]: + """Format name and return new SignalType instance.""" + return SignalType(self.name.format(*args, **kwargs)) + + _DispatcherDataType = dict[ SignalType[*_Ts] | str, dict[ diff --git a/tests/common.py b/tests/common.py index 5743b26ef62..c0733a7642b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -76,6 +76,7 @@ from homeassistant.helpers import ( translation, ) from homeassistant.helpers.dispatcher import ( + SignalType, async_dispatcher_connect, async_dispatcher_send, ) @@ -1497,7 +1498,9 @@ def async_capture_events(hass: HomeAssistant, event_name: str) -> list[Event]: @callback -def async_mock_signal(hass: HomeAssistant, signal: str) -> list[tuple[Any]]: +def async_mock_signal( + hass: HomeAssistant, signal: SignalType[Any] | str +) -> list[tuple[Any]]: """Catch all dispatches to a signal.""" calls = [] diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py index 3dd708906b9..1e1abe6e154 100644 --- a/tests/helpers/test_dispatcher.py +++ b/tests/helpers/test_dispatcher.py @@ -7,6 +7,7 @@ import pytest from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import ( SignalType, + SignalTypeFormat, async_dispatcher_connect, async_dispatcher_send, ) @@ -58,6 +59,27 @@ async def test_signal_type(hass: HomeAssistant) -> None: assert calls == [("Hello", 2), ("World", 3), ("x", 4)] +async def test_signal_type_format(hass: HomeAssistant) -> None: + """Test dispatcher with SignalType and format.""" + signal: SignalTypeFormat[str, int] = SignalTypeFormat("test-{}") + calls: list[tuple[str, int]] = [] + + def test_funct(data1: str, data2: int) -> None: + calls.append((data1, data2)) + + async_dispatcher_connect(hass, signal.format("unique-id"), test_funct) + async_dispatcher_send(hass, signal.format("unique-id"), "Hello", 2) + await hass.async_block_till_done() + + assert calls == [("Hello", 2)] + + # Test compatibility with string keys + async_dispatcher_send(hass, "test-{}".format("unique-id"), "x", 4) + await hass.async_block_till_done() + + assert calls == [("Hello", 2), ("x", 4)] + + async def test_simple_function_unsub(hass: HomeAssistant) -> None: """Test simple function (executor) and unsub.""" calls1 = [] From 31f5576b6e7f316b1e29cfa12966d6607d479698 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:16:55 +0100 Subject: [PATCH 1522/1691] Add ruff commit to git-blame-ignore-revs (#114229) --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 52e0608d5b7..cfc34a08694 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -9,3 +9,6 @@ # Prettify json (tests) (#68888) 496d90bf00429d9d924caeb0155edc0bf54e86b9 + +# Bump ruff to 0.3.4 (#112690) +6bb4e7d62c60389608acf4a7d7dacd8f029307dd From 2388e2dda9ef6c1864d3d574a6fa6fe95744c9ac Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 26 Mar 2024 12:51:14 +0100 Subject: [PATCH 1523/1691] Drop Python 3.11 support (#114220) --- .../components/cisco_webex_teams/notify.py | 12 +----- homeassistant/components/sensor/__init__.py | 4 +- homeassistant/const.py | 4 +- homeassistant/util/async_.py | 42 ++++++------------- homeassistant/util/frozen_dataclass_compat.py | 4 +- mypy.ini | 2 +- pyproject.toml | 8 ++-- 7 files changed, 24 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/cisco_webex_teams/notify.py b/homeassistant/components/cisco_webex_teams/notify.py index 60cb4a581c2..30f56ac4712 100644 --- a/homeassistant/components/cisco_webex_teams/notify.py +++ b/homeassistant/components/cisco_webex_teams/notify.py @@ -3,9 +3,9 @@ from __future__ import annotations import logging -import sys import voluptuous as vol +from webexteamssdk import ApiError, WebexTeamsAPI, exceptions from homeassistant.components.notify import ( ATTR_TITLE, @@ -14,14 +14,9 @@ from homeassistant.components.notify import ( ) from homeassistant.const import CONF_TOKEN from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -if sys.version_info < (3, 12): - from webexteamssdk import ApiError, WebexTeamsAPI, exceptions - - _LOGGER = logging.getLogger(__name__) CONF_ROOM_ID = "room_id" @@ -37,11 +32,6 @@ def get_service( discovery_info: DiscoveryInfoType | None = None, ) -> CiscoWebexTeamsNotificationService | None: """Get the CiscoWebexTeams notification service.""" - if sys.version_info >= (3, 12): - raise HomeAssistantError( - "Cisco Webex Teams is not supported on Python 3.12. Please use Python 3.11." - ) - client = WebexTeamsAPI(access_token=config[CONF_TOKEN]) try: # Validate the token & room_id diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index ce32be40b30..92499a05af4 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -11,9 +11,7 @@ from decimal import Decimal, InvalidOperation as DecimalInvalidOperation from functools import partial import logging from math import ceil, floor, isfinite, log10 -from typing import TYPE_CHECKING, Any, Final, Self, cast, final - -from typing_extensions import override +from typing import TYPE_CHECKING, Any, Final, Self, cast, final, override from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( # noqa: F401 diff --git a/homeassistant/const.py b/homeassistant/const.py index 5c2908dc515..6e657469b9a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -20,10 +20,10 @@ MINOR_VERSION: Final = 4 PATCH_VERSION: Final = "0.dev0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" -REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) +REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) REQUIRED_NEXT_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) # Truthy date string triggers showing related deprecation warning messages. -REQUIRED_NEXT_PYTHON_HA_RELEASE: Final = "2024.4" +REQUIRED_NEXT_PYTHON_HA_RELEASE: Final = "" # Format for platform files PLATFORM_FORMAT: Final = "{platform}.{domain}" diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index acf5d8e6639..8c042242e0b 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -8,7 +8,6 @@ import concurrent.futures from contextlib import suppress import functools import logging -import sys import threading from typing import Any, ParamSpec, TypeVar, TypeVarTuple @@ -23,35 +22,20 @@ _R = TypeVar("_R") _P = ParamSpec("_P") _Ts = TypeVarTuple("_Ts") -if sys.version_info >= (3, 12, 0): - def create_eager_task( - coro: Coroutine[Any, Any, _T], - *, - name: str | None = None, - loop: AbstractEventLoop | None = None, - ) -> Task[_T]: - """Create a task from a coroutine and schedule it to run immediately.""" - return Task( - coro, - loop=loop or get_running_loop(), - name=name, - eager_start=True, # type: ignore[call-arg] - ) -else: - - def create_eager_task( - coro: Coroutine[Any, Any, _T], - *, - name: str | None = None, - loop: AbstractEventLoop | None = None, - ) -> Task[_T]: - """Create a task from a coroutine and schedule it to run immediately.""" - return Task( - coro, - loop=loop or get_running_loop(), - name=name, - ) +def create_eager_task( + coro: Coroutine[Any, Any, _T], + *, + name: str | None = None, + loop: AbstractEventLoop | None = None, +) -> Task[_T]: + """Create a task from a coroutine and schedule it to run immediately.""" + return Task( + coro, + loop=loop or get_running_loop(), + name=name, + eager_start=True, + ) def cancelling(task: Future[Any]) -> bool: diff --git a/homeassistant/util/frozen_dataclass_compat.py b/homeassistant/util/frozen_dataclass_compat.py index 68db3cd6832..fa86ce8ff87 100644 --- a/homeassistant/util/frozen_dataclass_compat.py +++ b/homeassistant/util/frozen_dataclass_compat.py @@ -8,9 +8,7 @@ from __future__ import annotations import dataclasses import sys -from typing import Any - -from typing_extensions import dataclass_transform +from typing import Any, dataclass_transform def _class_fields(cls: type, kw_only: bool) -> list[tuple[str, Any, Any]]: diff --git a/mypy.ini b/mypy.ini index a8b146059fc..81f6f553eb6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -3,7 +3,7 @@ # To update, run python3 -m script.hassfest -p mypy_config [mypy] -python_version = 3.11 +python_version = 3.12 plugins = pydantic.mypy show_error_codes = true follow_imports = silent diff --git a/pyproject.toml b/pyproject.toml index d6137b3d7ba..965827f41ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,10 +18,10 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Home Automation", ] -requires-python = ">=3.11.0" +requires-python = ">=3.12.0" dependencies = [ "aiohttp==3.9.3", "aiohttp_cors==0.7.0", @@ -90,7 +90,7 @@ include-package-data = true include = ["homeassistant*"] [tool.pylint.MAIN] -py-version = "3.11" +py-version = "3.12" ignore = [ "tests", ] @@ -705,6 +705,8 @@ ignore = [ "UP007", # keep type annotation style as is # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` + # Ignored due to incompatible with mypy: https://github.com/python/mypy/issues/15238 + "UP040", # Checks for use of TypeAlias annotation for declaring type aliases. # May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "W191", From 53944b260cacb91742f2b62438ef64ced52da81f Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:17:29 +0100 Subject: [PATCH 1524/1691] Bump motionblindsble to 0.0.9 (#114232) --- homeassistant/components/motionblinds_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/motionblinds_ble/manifest.json b/homeassistant/components/motionblinds_ble/manifest.json index 0bf752a4119..2a24dd67483 100644 --- a/homeassistant/components/motionblinds_ble/manifest.json +++ b/homeassistant/components/motionblinds_ble/manifest.json @@ -14,5 +14,5 @@ "integration_type": "device", "iot_class": "assumed_state", "loggers": ["motionblindsble"], - "requirements": ["motionblindsble==0.0.8"] + "requirements": ["motionblindsble==0.0.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index dc3c262f3dd..5244061f178 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1328,7 +1328,7 @@ mopeka-iot-ble==0.7.0 motionblinds==0.6.23 # homeassistant.components.motionblinds_ble -motionblindsble==0.0.8 +motionblindsble==0.0.9 # homeassistant.components.motioneye motioneye-client==0.3.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eb7b2162cc5..c1349c27503 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1067,7 +1067,7 @@ mopeka-iot-ble==0.7.0 motionblinds==0.6.23 # homeassistant.components.motionblinds_ble -motionblindsble==0.0.8 +motionblindsble==0.0.9 # homeassistant.components.motioneye motioneye-client==0.3.14 From c8260a5966a913dbcdb8c94ea0b5559f9e677436 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:52:38 +0100 Subject: [PATCH 1525/1691] Remove test cases for task eager_start <3.12 (#114243) --- tests/test_core.py | 22 ---------------------- tests/util/test_async.py | 27 --------------------------- 2 files changed, 49 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 1aaf417f9eb..11fda50a180 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -9,7 +9,6 @@ import functools import gc import logging import os -import sys from tempfile import TemporaryDirectory import threading import time @@ -334,9 +333,6 @@ async def test_async_create_task_schedule_coroutine() -> None: assert len(hass.add_job.mock_calls) == 0 -@pytest.mark.skipif( - sys.version_info < (3, 12), reason="eager_start is only supported for Python 3.12" -) async def test_async_create_task_eager_start_schedule_coroutine() -> None: """Test that we schedule coroutines and add jobs to the job pool.""" hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop())) @@ -350,24 +346,6 @@ async def test_async_create_task_eager_start_schedule_coroutine() -> None: assert len(hass.add_job.mock_calls) == 0 -@pytest.mark.skipif( - sys.version_info >= (3, 12), reason="eager_start is not supported on < 3.12" -) -async def test_async_create_task_eager_start_fallback_schedule_coroutine() -> None: - """Test that we schedule coroutines and add jobs to the job pool.""" - hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop())) - - async def job(): - pass - - ha.HomeAssistant.async_create_task(hass, job(), eager_start=True) - assert len(hass.loop.call_soon.mock_calls) == 1 - # Should fallback to loop.create_task since 3.11 does - # not support eager_start - assert len(hass.loop.create_task.mock_calls) == 0 - assert len(hass.add_job.mock_calls) == 0 - - async def test_async_create_task_schedule_coroutine_with_name() -> None: """Test that we schedule coroutines and add jobs to the job pool with a name.""" hass = MagicMock(loop=MagicMock(wraps=asyncio.get_running_loop())) diff --git a/tests/util/test_async.py b/tests/util/test_async.py index 1029d670703..50eecec72f6 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -1,7 +1,6 @@ """Tests for async util methods from Python source.""" import asyncio -import sys import time from unittest.mock import MagicMock, Mock, patch @@ -271,7 +270,6 @@ async def test_callback_is_always_scheduled(hass: HomeAssistant) -> None: mock_call_soon_threadsafe.assert_called_once() -@pytest.mark.skipif(sys.version_info < (3, 12), reason="Test requires Python 3.12+") async def test_create_eager_task_312(hass: HomeAssistant) -> None: """Test create_eager_task schedules a task eagerly in the event loop. @@ -294,28 +292,3 @@ async def test_create_eager_task_312(hass: HomeAssistant) -> None: assert events == ["eager", "normal"] await task1 await task2 - - -@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Test requires < Python 3.12") -async def test_create_eager_task_pre_312(hass: HomeAssistant) -> None: - """Test create_eager_task schedules a task in the event loop. - - For older python versions, the task is scheduled normally. - """ - events = [] - - async def _normal_task(): - events.append("normal") - - async def _eager_task(): - events.append("eager") - - task1 = hasync.create_eager_task(_eager_task()) - task2 = asyncio.create_task(_normal_task()) - - assert events == [] - - await asyncio.sleep(0) - assert events == ["eager", "normal"] - await task1 - await task2 From c247534731dd63a2a7e202a6fb0a52d48034d09d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 26 Mar 2024 18:58:25 +0100 Subject: [PATCH 1526/1691] Default floor level to None (and allow unsetting it) (#114249) --- homeassistant/components/config/floor_registry.py | 4 ++-- homeassistant/helpers/floor_registry.py | 4 ++-- tests/components/config/test_floor_registry.py | 8 ++++---- tests/helpers/test_floor_registry.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/config/floor_registry.py b/homeassistant/components/config/floor_registry.py index 081ec59e268..986f772ac53 100644 --- a/homeassistant/components/config/floor_registry.py +++ b/homeassistant/components/config/floor_registry.py @@ -43,7 +43,7 @@ def websocket_list_floors( vol.Required("name"): str, vol.Optional("aliases"): list, vol.Optional("icon"): vol.Any(str, None), - vol.Optional("level"): int, + vol.Optional("level"): vol.Any(int, None), } ) @websocket_api.require_admin @@ -98,7 +98,7 @@ def websocket_delete_floor( vol.Required("floor_id"): str, vol.Optional("aliases"): list, vol.Optional("icon"): vol.Any(str, None), - vol.Optional("level"): int, + vol.Optional("level"): vol.Any(int, None), vol.Optional("name"): str, } ) diff --git a/homeassistant/helpers/floor_registry.py b/homeassistant/helpers/floor_registry.py index 904acc1d805..b168b81c1a9 100644 --- a/homeassistant/helpers/floor_registry.py +++ b/homeassistant/helpers/floor_registry.py @@ -42,7 +42,7 @@ class FloorEntry(NormalizedNameBaseRegistryEntry): aliases: set[str] floor_id: str icon: str | None = None - level: int = 0 + level: int | None = None class FloorRegistry(BaseRegistry): @@ -99,7 +99,7 @@ class FloorRegistry(BaseRegistry): *, aliases: set[str] | None = None, icon: str | None = None, - level: int = 0, + level: int | None = None, ) -> FloorEntry: """Create a new floor.""" if floor := self.async_get_floor_by_name(name): diff --git a/tests/components/config/test_floor_registry.py b/tests/components/config/test_floor_registry.py index 781c6181118..b4e3907bc4d 100644 --- a/tests/components/config/test_floor_registry.py +++ b/tests/components/config/test_floor_registry.py @@ -44,7 +44,7 @@ async def test_list_floors( "icon": None, "floor_id": "first_floor", "name": "First floor", - "level": 0, + "level": None, } assert msg["result"][1] == { "aliases": unordered(["top floor", "attic"]), @@ -72,7 +72,7 @@ async def test_create_floor( "icon": None, "floor_id": "first_floor", "name": "First floor", - "level": 0, + "level": None, } await client.send_json_auto_id( @@ -196,7 +196,7 @@ async def test_update_floor( "name": "First floor", "aliases": [], "icon": None, - "level": 1, + "level": None, "type": "config/floor_registry/update", } ) @@ -209,7 +209,7 @@ async def test_update_floor( "icon": None, "floor_id": floor.floor_id, "name": "First floor", - "level": 1, + "level": None, } diff --git a/tests/helpers/test_floor_registry.py b/tests/helpers/test_floor_registry.py index c5e5b42fafa..faa9eb131a1 100644 --- a/tests/helpers/test_floor_registry.py +++ b/tests/helpers/test_floor_registry.py @@ -134,7 +134,7 @@ async def test_update_floor( assert floor.name == "First floor" assert floor.icon is None assert floor.aliases == set() - assert floor.level == 0 + assert floor.level is None updated_floor = floor_registry.async_update( floor.floor_id, From e1036b3af0169113cb649b6aee4d8511dc7b2172 Mon Sep 17 00:00:00 2001 From: Ron Weikamp <15732230+ronweikamp@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:09:48 +0100 Subject: [PATCH 1527/1691] Refactor Riemann sum integral sensor to prepare for time based trigger (#113932) * Refactor Integration sensor. * Use local simple function to verify the State is numeric. * Merge two methods to one. * Method renaming: _handle_state_change * Move async_write_ha_state to the caller. * Add comment on why attr_icon is set to None * Remove possible None type of State in validation methods. * Use a dict to map method name to method class. * Explain derived unit after integration. * Renaming to _multiply_unit_with_time and elaborate in docstring. * Set integral unit_of_measurement explicitly to None if source unit_of_measurement is None * One function for unit of measurement related steps. * Improve docstring of _multiply_unit_with_time Co-authored-by: Erik Montnemery * Apply f-string suggestions from code review Co-authored-by: Erik Montnemery * Be more clear in comment about removing the sensors icon default. * Apply suggestions from code review Co-authored-by: Diogo Gomes * Update homeassistant/components/integration/sensor.py * Update homeassistant/components/integration/sensor.py * Update homeassistant/components/integration/sensor.py --------- Co-authored-by: Erik Montnemery Co-authored-by: Diogo Gomes --- .../components/integration/sensor.py | 249 ++++++++++-------- tests/components/integration/test_sensor.py | 2 +- 2 files changed, 146 insertions(+), 105 deletions(-) diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 956b868272f..62a0dbdec78 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC, abstractmethod from dataclasses import dataclass from decimal import Decimal, DecimalException, InvalidOperation import logging @@ -27,8 +28,9 @@ from homeassistant.const import ( STATE_UNKNOWN, UnitOfTime, ) -from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers import ( + condition, config_validation as cv, device_registry as dr, entity_registry as er, @@ -89,6 +91,72 @@ PLATFORM_SCHEMA = vol.All( ) +class _IntegrationMethod(ABC): + @staticmethod + def from_name(method_name: str) -> _IntegrationMethod: + return _NAME_TO_INTEGRATION_METHOD[method_name]() + + @abstractmethod + def validate_states(self, left: State, right: State) -> bool: + """Check state requirements for integration.""" + + @abstractmethod + def calculate_area_with_two_states( + self, elapsed_time: float, left: State, right: State + ) -> Decimal: + """Calculate area given two states.""" + + def calculate_area_with_one_state( + self, elapsed_time: float, constant_state: State + ) -> Decimal: + return Decimal(constant_state.state) * Decimal(elapsed_time) + + +class _Trapezoidal(_IntegrationMethod): + def calculate_area_with_two_states( + self, elapsed_time: float, left: State, right: State + ) -> Decimal: + return Decimal(elapsed_time) * (Decimal(left.state) + Decimal(right.state)) / 2 + + def validate_states(self, left: State, right: State) -> bool: + return _is_numeric_state(left) and _is_numeric_state(right) + + +class _Left(_IntegrationMethod): + def calculate_area_with_two_states( + self, elapsed_time: float, left: State, right: State + ) -> Decimal: + return self.calculate_area_with_one_state(elapsed_time, left) + + def validate_states(self, left: State, right: State) -> bool: + return _is_numeric_state(left) + + +class _Right(_IntegrationMethod): + def calculate_area_with_two_states( + self, elapsed_time: float, left: State, right: State + ) -> Decimal: + return self.calculate_area_with_one_state(elapsed_time, right) + + def validate_states(self, left: State, right: State) -> bool: + return _is_numeric_state(right) + + +def _is_numeric_state(state: State) -> bool: + try: + float(state.state) + return True + except (ValueError, TypeError): + return False + + +_NAME_TO_INTEGRATION_METHOD: dict[str, type[_IntegrationMethod]] = { + METHOD_LEFT: _Left, + METHOD_RIGHT: _Right, + METHOD_TRAPEZOIDAL: _Trapezoidal, +} + + @dataclass class IntegrationSensorExtraStoredData(SensorExtraStoredData): """Object to hold extra stored data.""" @@ -231,10 +299,10 @@ class IntegrationSensor(RestoreSensor): self._sensor_source_id = source_entity self._round_digits = round_digits self._state: Decimal | None = None - self._method = integration_method + self._method = _IntegrationMethod.from_name(integration_method) self._attr_name = name if name is not None else f"{source_entity} integral" - self._unit_template = f"{'' if unit_prefix is None else unit_prefix}{{}}" + self._unit_prefix_string = "" if unit_prefix is None else unit_prefix self._unit_of_measurement: str | None = None self._unit_prefix = UNIT_PREFIXES[unit_prefix] self._unit_time = UNIT_TIME[unit_time] @@ -244,15 +312,52 @@ class IntegrationSensor(RestoreSensor): self._last_valid_state: Decimal | None = None self._attr_device_info = device_info - def _unit(self, source_unit: str) -> str: - """Derive unit from the source sensor, SI prefix and time unit.""" + def _calculate_unit(self, source_unit: str) -> str: + """Multiply source_unit with time unit of the integral. + + Possibly cancelling out a time unit in the denominator of the source_unit. + Note that this is a heuristic string manipulation method and might not + transform all source units in a sensible way. + + Examples: + - Speed to distance: 'km/h' and 'h' will be transformed to 'km' + - Power to energy: 'W' and 'h' will be transformed to 'Wh' + + """ unit_time = self._unit_time_str if source_unit.endswith(f"/{unit_time}"): integral_unit = source_unit[0 : (-(1 + len(unit_time)))] else: integral_unit = f"{source_unit}{unit_time}" - return self._unit_template.format(integral_unit) + return f"{self._unit_prefix_string}{integral_unit}" + + def _derive_and_set_attributes_from_state(self, source_state: State) -> None: + source_unit = source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + if source_unit is not None: + self._unit_of_measurement = self._calculate_unit(source_unit) + else: + # If the source has no defined unit we cannot derive a unit for the integral + self._unit_of_measurement = None + + if ( + self.device_class is None + and source_state.attributes.get(ATTR_DEVICE_CLASS) + == SensorDeviceClass.POWER + ): + self._attr_device_class = SensorDeviceClass.ENERGY + self._attr_icon = None # Remove this sensors icon default and allow to fallback to the ENERGY default + + def _update_integral(self, area: Decimal) -> None: + area_scaled = area / (self._unit_prefix * self._unit_time) + if isinstance(self._state, Decimal): + self._state += area_scaled + else: + self._state = area_scaled + _LOGGER.debug( + "area = %s, area_scaled = %s new state = %s", area, area_scaled, self._state + ) + self._last_valid_state = self._state async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" @@ -292,109 +397,45 @@ class IntegrationSensor(RestoreSensor): self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS) self._unit_of_measurement = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - @callback - def calc_integration(event: Event[EventStateChangedData]) -> None: - """Handle the sensor state changes.""" - old_state = event.data["old_state"] - new_state = event.data["new_state"] - - if ( - source_state := self.hass.states.get(self._sensor_source_id) - ) is None or source_state.state == STATE_UNAVAILABLE: - self._attr_available = False - self.async_write_ha_state() - return - - self._attr_available = True - - if old_state is None or new_state is None: - # we can't calculate the elapsed time, so we can't calculate the integral - return - - unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - if unit is not None: - self._unit_of_measurement = self._unit(unit) - - if ( - self.device_class is None - and new_state.attributes.get(ATTR_DEVICE_CLASS) - == SensorDeviceClass.POWER - ): - self._attr_device_class = SensorDeviceClass.ENERGY - self._attr_icon = None - - self.async_write_ha_state() - - try: - # integration as the Riemann integral of previous measures. - elapsed_time = ( - new_state.last_updated - old_state.last_updated - ).total_seconds() - - if ( - self._method == METHOD_TRAPEZOIDAL - and new_state.state - not in ( - STATE_UNKNOWN, - STATE_UNAVAILABLE, - ) - and old_state.state - not in ( - STATE_UNKNOWN, - STATE_UNAVAILABLE, - ) - ): - area = ( - (Decimal(new_state.state) + Decimal(old_state.state)) - * Decimal(elapsed_time) - / 2 - ) - elif self._method == METHOD_LEFT and old_state.state not in ( - STATE_UNKNOWN, - STATE_UNAVAILABLE, - ): - area = Decimal(old_state.state) * Decimal(elapsed_time) - elif self._method == METHOD_RIGHT and new_state.state not in ( - STATE_UNKNOWN, - STATE_UNAVAILABLE, - ): - area = Decimal(new_state.state) * Decimal(elapsed_time) - else: - _LOGGER.debug( - "Could not apply method %s to %s -> %s", - self._method, - old_state.state, - new_state.state, - ) - return - - integral = area / (self._unit_prefix * self._unit_time) - _LOGGER.debug( - "area = %s, integral = %s state = %s", area, integral, self._state - ) - assert isinstance(integral, Decimal) - except ValueError as err: - _LOGGER.warning("While calculating integration: %s", err) - except DecimalException as err: - _LOGGER.warning( - "Invalid state (%s > %s): %s", old_state.state, new_state.state, err - ) - except AssertionError as err: - _LOGGER.error("Could not calculate integral: %s", err) - else: - if isinstance(self._state, Decimal): - self._state += integral - else: - self._state = integral - self._last_valid_state = self._state - self.async_write_ha_state() - self.async_on_remove( async_track_state_change_event( - self.hass, [self._sensor_source_id], calc_integration + self.hass, + [self._sensor_source_id], + self._handle_state_change, ) ) + @callback + def _handle_state_change(self, event: Event[EventStateChangedData]) -> None: + old_state = event.data["old_state"] + new_state = event.data["new_state"] + + if old_state is None or new_state is None: + return + + if condition.state(self.hass, new_state, [STATE_UNAVAILABLE]): + self._attr_available = False + self.async_write_ha_state() + return + + self._attr_available = True + self._derive_and_set_attributes_from_state(new_state) + + if not self._method.validate_states(old_state, new_state): + self.async_write_ha_state() + return + + elapsed_seconds = ( + new_state.last_updated - old_state.last_updated + ).total_seconds() + + area = self._method.calculate_area_with_two_states( + elapsed_seconds, old_state, new_state + ) + + self._update_integral(area) + self.async_write_ha_state() + @property def native_value(self) -> Decimal | None: """Return the state of the sensor.""" diff --git a/tests/components/integration/test_sensor.py b/tests/components/integration/test_sensor.py index 904c31e9896..53763247bdf 100644 --- a/tests/components/integration/test_sensor.py +++ b/tests/components/integration/test_sensor.py @@ -563,7 +563,7 @@ async def test_units(hass: HomeAssistant) -> None: # When source state goes to None / Unknown, expect an early exit without # changes to the state or unit_of_measurement - hass.states.async_set(entity_id, None, None) + hass.states.async_set(entity_id, None, {"unit_of_measurement": UnitOfPower.WATT}) await hass.async_block_till_done() new_state = hass.states.get("sensor.integration") From 9a1906322b14399210a7ed9061bbf23d1d522aaa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 26 Mar 2024 19:41:46 +0100 Subject: [PATCH 1528/1691] Allow numeric state trigger/condition against zone entities (#114221) --- homeassistant/helpers/config_validation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 6dd5d7f2bdb..f7245607be7 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1350,7 +1350,8 @@ SERVICE_SCHEMA = vol.All( ) NUMERIC_STATE_THRESHOLD_SCHEMA = vol.Any( - vol.Coerce(float), vol.All(str, entity_domain(["input_number", "number", "sensor"])) + vol.Coerce(float), + vol.All(str, entity_domain(["input_number", "number", "sensor", "zone"])), ) CONDITION_BASE_SCHEMA = { From dce3bde0ab878439b4430d9d29e5dacc48c3fa70 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 26 Mar 2024 20:20:08 +0100 Subject: [PATCH 1529/1691] Use `setup_test_component_platform` helper for update entity component tests instead of `hass.components` (#114203) * Use `setup_test_component_platform` helper for update entity component tests instead of `hass.components` * Run ruff format --- tests/components/update/common.py | 65 +++++++ tests/components/update/conftest.py | 82 +++++++++ .../components/update/test_device_trigger.py | 20 +-- tests/components/update/test_init.py | 71 ++++---- tests/components/update/test_recorder.py | 10 +- .../custom_components/test/update.py | 159 ------------------ 6 files changed, 190 insertions(+), 217 deletions(-) create mode 100644 tests/components/update/common.py create mode 100644 tests/components/update/conftest.py delete mode 100644 tests/testing_config/custom_components/test/update.py diff --git a/tests/components/update/common.py b/tests/components/update/common.py new file mode 100644 index 00000000000..70b69498f66 --- /dev/null +++ b/tests/components/update/common.py @@ -0,0 +1,65 @@ +"""Common test fixtures for the update component test.""" + +import logging +from typing import Any + +from homeassistant.components.update import UpdateEntity + +from tests.common import MockEntity + +_LOGGER = logging.getLogger(__name__) + + +class MockUpdateEntity(MockEntity, UpdateEntity): + """Mock UpdateEntity class.""" + + @property + def auto_update(self) -> bool: + """Indicate if the device or service has auto update enabled.""" + return self._handle("auto_update") + + @property + def installed_version(self) -> str | None: + """Version currently installed and in use.""" + return self._handle("installed_version") + + @property + def in_progress(self) -> bool | int | None: + """Update installation progress.""" + return self._handle("in_progress") + + @property + def latest_version(self) -> str | None: + """Latest version available for install.""" + return self._handle("latest_version") + + @property + def release_summary(self) -> str | None: + """Summary of the release notes or changelog.""" + return self._handle("release_summary") + + @property + def release_url(self) -> str | None: + """URL to the full release notes of the latest version available.""" + return self._handle("release_url") + + @property + def title(self) -> str | None: + """Title of the software.""" + return self._handle("title") + + def install(self, version: str | None, backup: bool, **kwargs: Any) -> None: + """Install an update.""" + if backup: + _LOGGER.info("Creating backup before installing update") + + if version is not None: + self._values["installed_version"] = version + _LOGGER.info("Installed update with version: %s", version) + else: + self._values["installed_version"] = self.latest_version + _LOGGER.info("Installed latest update") + + def release_notes(self) -> str | None: + """Return the release notes of the latest version.""" + return "Release notes" diff --git a/tests/components/update/conftest.py b/tests/components/update/conftest.py new file mode 100644 index 00000000000..759f243e8db --- /dev/null +++ b/tests/components/update/conftest.py @@ -0,0 +1,82 @@ +"""Fixtures for update component testing.""" + +import pytest + +from homeassistant.components.update import UpdateEntityFeature + +from .common import MockUpdateEntity + + +@pytest.fixture +def mock_update_entities() -> list[MockUpdateEntity]: + """Return a list of mock update entities.""" + return [ + MockUpdateEntity( + name="No Update", + unique_id="no_update", + installed_version="1.0.0", + latest_version="1.0.0", + supported_features=UpdateEntityFeature.INSTALL, + ), + MockUpdateEntity( + name="Update Available", + unique_id="update_available", + installed_version="1.0.0", + latest_version="1.0.1", + supported_features=UpdateEntityFeature.INSTALL, + ), + MockUpdateEntity( + name="Update Unknown", + unique_id="update_unknown", + installed_version="1.0.0", + latest_version=None, + supported_features=UpdateEntityFeature.INSTALL, + ), + MockUpdateEntity( + name="Update Specific Version", + unique_id="update_specific_version", + installed_version="1.0.0", + latest_version="1.0.0", + supported_features=UpdateEntityFeature.INSTALL + | UpdateEntityFeature.SPECIFIC_VERSION, + ), + MockUpdateEntity( + name="Update Backup", + unique_id="update_backup", + installed_version="1.0.0", + latest_version="1.0.1", + supported_features=UpdateEntityFeature.INSTALL + | UpdateEntityFeature.SPECIFIC_VERSION + | UpdateEntityFeature.BACKUP, + ), + MockUpdateEntity( + name="Update Already in Progress", + unique_id="update_already_in_progres", + installed_version="1.0.0", + latest_version="1.0.1", + in_progress=50, + supported_features=UpdateEntityFeature.INSTALL + | UpdateEntityFeature.PROGRESS, + ), + MockUpdateEntity( + name="Update No Install", + unique_id="no_install", + installed_version="1.0.0", + latest_version="1.0.1", + ), + MockUpdateEntity( + name="Update with release notes", + unique_id="with_release_notes", + installed_version="1.0.0", + latest_version="1.0.1", + supported_features=UpdateEntityFeature.RELEASE_NOTES, + ), + MockUpdateEntity( + name="Update with auto update", + unique_id="with_auto_update", + installed_version="1.0.0", + latest_version="1.0.1", + auto_update=True, + supported_features=UpdateEntityFeature.INSTALL, + ), + ] diff --git a/tests/components/update/test_device_trigger.py b/tests/components/update/test_device_trigger.py index 5a22bcec912..31a9ee7b36e 100644 --- a/tests/components/update/test_device_trigger.py +++ b/tests/components/update/test_device_trigger.py @@ -20,7 +20,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) +from tests.components.update.common import MockUpdateEntity @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -180,12 +182,10 @@ async def test_if_fires_on_state_change( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls: list[ServiceCall], - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], ) -> None: """Test for turn_on and turn_off triggers firing.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -282,12 +282,10 @@ async def test_if_fires_on_state_change_legacy( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls: list[ServiceCall], - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], ) -> None: """Test for turn_on and turn_off triggers firing.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -352,12 +350,10 @@ async def test_if_fires_on_state_change_with_for( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls: list[ServiceCall], - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], ) -> None: """Test for triggers firing with delay.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/components/update/test_init.py b/tests/components/update/test_init.py index 252bac578a1..02ca605eed4 100644 --- a/tests/components/update/test_init.py +++ b/tests/components/update/test_init.py @@ -50,6 +50,7 @@ from tests.common import ( mock_integration, mock_platform, mock_restore_cache, + setup_test_component_platform, ) from tests.typing import WebSocketGenerator @@ -166,11 +167,10 @@ async def test_update(hass: HomeAssistant) -> None: async def test_entity_with_no_install( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], ) -> None: """Test entity with no updates.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -232,11 +232,10 @@ async def test_entity_with_no_install( async def test_entity_with_no_updates( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], ) -> None: """Test entity with no updates.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -281,12 +280,11 @@ async def test_entity_with_no_updates( async def test_entity_with_auto_update( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity that has auto update feature.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -333,12 +331,11 @@ async def test_entity_with_auto_update( async def test_entity_with_updates_available( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test basic update entity with updates available.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -387,12 +384,11 @@ async def test_entity_with_updates_available( async def test_entity_with_unknown_version( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity that has an unknown version.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -425,12 +421,11 @@ async def test_entity_with_unknown_version( async def test_entity_with_specific_version( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity that support specific version.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -488,12 +483,11 @@ async def test_entity_with_specific_version( async def test_entity_with_backup_support( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity with backup support.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -548,12 +542,11 @@ async def test_entity_with_backup_support( async def test_entity_already_in_progress( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update install already in progress.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -579,15 +572,14 @@ async def test_entity_already_in_progress( async def test_entity_without_progress_support( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity without progress support. In that case, progress is still handled by Home Assistant. """ - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -618,15 +610,14 @@ async def test_entity_without_progress_support( async def test_entity_without_progress_support_raising( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], caplog: pytest.LogCaptureFixture, ) -> None: """Test update entity without progress support that raises during install. In that case, progress is still handled by Home Assistant. """ - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -665,7 +656,7 @@ async def test_entity_without_progress_support_raising( async def test_restore_state( - hass: HomeAssistant, enable_custom_integrations: None + hass: HomeAssistant, mock_update_entities: list[MockUpdateEntity] ) -> None: """Test we restore skipped version state.""" mock_restore_cache( @@ -681,8 +672,7 @@ async def test_restore_state( ), ) - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -697,12 +687,11 @@ async def test_restore_state( async def test_release_notes( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], hass_ws_client: WebSocketGenerator, ) -> None: """Test getting the release notes over the websocket connection.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -723,12 +712,11 @@ async def test_release_notes( async def test_release_notes_entity_not_found( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], hass_ws_client: WebSocketGenerator, ) -> None: """Test getting the release notes for not found entity.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -750,12 +738,11 @@ async def test_release_notes_entity_not_found( async def test_release_notes_entity_does_not_support_release_notes( hass: HomeAssistant, - enable_custom_integrations: None, + mock_update_entities: list[MockUpdateEntity], hass_ws_client: WebSocketGenerator, ) -> None: """Test getting the release notes for entity that does not support release notes.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/components/update/test_recorder.py b/tests/components/update/test_recorder.py index d8048316900..da63518009e 100644 --- a/tests/components/update/test_recorder.py +++ b/tests/components/update/test_recorder.py @@ -17,17 +17,19 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, setup_test_component_platform from tests.components.recorder.common import async_wait_recording_done +from tests.components.update.common import MockUpdateEntity async def test_exclude_attributes( - recorder_mock: Recorder, hass: HomeAssistant, enable_custom_integrations: None + recorder_mock: Recorder, + hass: HomeAssistant, + mock_update_entities: list[MockUpdateEntity], ) -> None: """Test update attributes to be excluded.""" now = dt_util.utcnow() - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_update_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() state = hass.states.get("update.update_already_in_progress") diff --git a/tests/testing_config/custom_components/test/update.py b/tests/testing_config/custom_components/test/update.py deleted file mode 100644 index 40b5742b220..00000000000 --- a/tests/testing_config/custom_components/test/update.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Provide a mock update platform. - -Call init before using it in your tests to ensure clean test data. -""" - -from __future__ import annotations - -import logging -from typing import Any - -from homeassistant.components.update import UpdateEntity, UpdateEntityFeature - -from tests.common import MockEntity - -ENTITIES = [] - -_LOGGER = logging.getLogger(__name__) - - -class MockUpdateEntity(MockEntity, UpdateEntity): - """Mock UpdateEntity class.""" - - @property - def auto_update(self) -> bool: - """Indicate if the device or service has auto update enabled.""" - return self._handle("auto_update") - - @property - def installed_version(self) -> str | None: - """Version currently installed and in use.""" - return self._handle("installed_version") - - @property - def in_progress(self) -> bool | int | None: - """Update installation progress.""" - return self._handle("in_progress") - - @property - def latest_version(self) -> str | None: - """Latest version available for install.""" - return self._handle("latest_version") - - @property - def release_summary(self) -> str | None: - """Summary of the release notes or changelog.""" - return self._handle("release_summary") - - @property - def release_url(self) -> str | None: - """URL to the full release notes of the latest version available.""" - return self._handle("release_url") - - @property - def title(self) -> str | None: - """Title of the software.""" - return self._handle("title") - - def install(self, version: str | None, backup: bool, **kwargs: Any) -> None: - """Install an update.""" - if backup: - _LOGGER.info("Creating backup before installing update") - - if version is not None: - self._values["installed_version"] = version - _LOGGER.info("Installed update with version: %s", version) - else: - self._values["installed_version"] = self.latest_version - _LOGGER.info("Installed latest update") - - def release_notes(self) -> str | None: - """Return the release notes of the latest version.""" - return "Release notes" - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - [] - if empty - else [ - MockUpdateEntity( - name="No Update", - unique_id="no_update", - installed_version="1.0.0", - latest_version="1.0.0", - supported_features=UpdateEntityFeature.INSTALL, - ), - MockUpdateEntity( - name="Update Available", - unique_id="update_available", - installed_version="1.0.0", - latest_version="1.0.1", - supported_features=UpdateEntityFeature.INSTALL, - ), - MockUpdateEntity( - name="Update Unknown", - unique_id="update_unknown", - installed_version="1.0.0", - latest_version=None, - supported_features=UpdateEntityFeature.INSTALL, - ), - MockUpdateEntity( - name="Update Specific Version", - unique_id="update_specific_version", - installed_version="1.0.0", - latest_version="1.0.0", - supported_features=UpdateEntityFeature.INSTALL - | UpdateEntityFeature.SPECIFIC_VERSION, - ), - MockUpdateEntity( - name="Update Backup", - unique_id="update_backup", - installed_version="1.0.0", - latest_version="1.0.1", - supported_features=UpdateEntityFeature.INSTALL - | UpdateEntityFeature.SPECIFIC_VERSION - | UpdateEntityFeature.BACKUP, - ), - MockUpdateEntity( - name="Update Already in Progress", - unique_id="update_already_in_progres", - installed_version="1.0.0", - latest_version="1.0.1", - in_progress=50, - supported_features=UpdateEntityFeature.INSTALL - | UpdateEntityFeature.PROGRESS, - ), - MockUpdateEntity( - name="Update No Install", - unique_id="no_install", - installed_version="1.0.0", - latest_version="1.0.1", - ), - MockUpdateEntity( - name="Update with release notes", - unique_id="with_release_notes", - installed_version="1.0.0", - latest_version="1.0.1", - supported_features=UpdateEntityFeature.RELEASE_NOTES, - ), - MockUpdateEntity( - name="Update with auto update", - unique_id="with_auto_update", - installed_version="1.0.0", - latest_version="1.0.1", - auto_update=True, - supported_features=UpdateEntityFeature.INSTALL, - ), - ] - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(ENTITIES) From 9a32d1bbd532af6891981605dfb5dc0f6aa8b256 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 26 Mar 2024 20:31:06 +0100 Subject: [PATCH 1530/1691] Add label selector (#111029) Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/selector.py | 32 +++++++++++++++++++++++++++++++ tests/helpers/test_selector.py | 16 ++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 0a893aa87c2..938cc6a9246 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -871,6 +871,38 @@ class IconSelector(Selector[IconSelectorConfig]): return icon +class LabelSelectorConfig(TypedDict, total=False): + """Class to represent a label selector config.""" + + multiple: bool + + +@SELECTORS.register("label") +class LabelSelector(Selector[LabelSelectorConfig]): + """Selector of a single or list of labels.""" + + selector_type = "label" + + CONFIG_SCHEMA = vol.Schema( + { + vol.Optional("multiple", default=False): cv.boolean, + } + ) + + def __init__(self, config: LabelSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + + def __call__(self, data: Any) -> str | list[str]: + """Validate the passed selection.""" + if not self.config["multiple"]: + label_id: str = vol.Schema(str)(data) + return label_id + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [vol.Schema(str)(val) for val in data] + + class LanguageSelectorConfig(TypedDict, total=False): """Class to represent an language selector config.""" diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index d04882d6802..0dc7e570fc5 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -1142,3 +1142,19 @@ def test_trigger_selector_schema(schema, valid_selections, invalid_selections) - def test_qr_code_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test QR code selector.""" _test_selector("qr_code", schema, valid_selections, invalid_selections) + + +@pytest.mark.parametrize( + ("schema", "valid_selections", "invalid_selections"), + [ + ({}, ("abc123",), (None,)), + ( + {"multiple": True}, + ((["abc123", "def456"],)), + (None, "abc123", ["abc123", None]), + ), + ], +) +def test_label_selector_schema(schema, valid_selections, invalid_selections) -> None: + """Test label selector.""" + _test_selector("label", schema, valid_selections, invalid_selections) From f982473e53d9145bda536f497b78a139c8617a49 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 09:40:06 -1000 Subject: [PATCH 1531/1691] Small performance improvements to energy sensors (#114149) --- homeassistant/components/energy/sensor.py | 30 ++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index 638b560a954..cca245f7e8d 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -149,7 +149,7 @@ class SensorManager: async def finish() -> None: if to_add: self.async_add_entities(to_add) - await asyncio.gather(*(ent.add_finished.wait() for ent in to_add)) + await asyncio.wait(ent.add_finished for ent in to_add) for key, entity in to_remove.items(): self.current_entities.pop(key) @@ -219,6 +219,12 @@ class SensorManager: to_add.append(self.current_entities[key]) +def _set_result_unless_done(future: asyncio.Future[None]) -> None: + """Set the result of a future unless it is done.""" + if not future.done(): + future.set_result(None) + + class EnergyCostSensor(SensorEntity): """Calculate costs incurred by consuming energy. @@ -248,7 +254,9 @@ class EnergyCostSensor(SensorEntity): self._last_energy_sensor_state: State | None = None # add_finished is set when either of async_added_to_hass or add_to_platform_abort # is called - self.add_finished = asyncio.Event() + self.add_finished: asyncio.Future[ + None + ] = asyncio.get_running_loop().create_future() def _reset(self, energy_state: State) -> None: """Reset the cost sensor.""" @@ -419,25 +427,25 @@ class EnergyCostSensor(SensorEntity): self._config[self._adapter.stat_energy_key] ] = self.entity_id - @callback - def async_state_changed_listener(*_: Any) -> None: - """Handle child updates.""" - self._update_cost() - self.async_write_ha_state() - self.async_on_remove( async_track_state_change_event( self.hass, cast(str, self._config[self._adapter.stat_energy_key]), - async_state_changed_listener, + self._async_state_changed_listener, ) ) - self.add_finished.set() + _set_result_unless_done(self.add_finished) + + @callback + def _async_state_changed_listener(self, *_: Any) -> None: + """Handle child updates.""" + self._update_cost() + self.async_write_ha_state() @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" - self.add_finished.set() + _set_result_unless_done(self.add_finished) super().add_to_platform_abort() async def async_will_remove_from_hass(self) -> None: From 8955ef7fc61edfe43c0e3d16cc0578b9b05fbc96 Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Tue, 26 Mar 2024 20:53:45 +0100 Subject: [PATCH 1532/1691] Bump bring-api to 0.5.7 (#114252) --- homeassistant/components/bring/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bring/manifest.json b/homeassistant/components/bring/manifest.json index 6b905a61b7d..be2c5633362 100644 --- a/homeassistant/components/bring/manifest.json +++ b/homeassistant/components/bring/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/bring", "integration_type": "service", "iot_class": "cloud_polling", - "requirements": ["bring-api==0.5.6"] + "requirements": ["bring-api==0.5.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5244061f178..3487723b62a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -603,7 +603,7 @@ boschshcpy==0.2.82 boto3==1.33.13 # homeassistant.components.bring -bring-api==0.5.6 +bring-api==0.5.7 # homeassistant.components.broadlink broadlink==0.18.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c1349c27503..2f8aaafee0a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -514,7 +514,7 @@ bond-async==0.2.1 boschshcpy==0.2.82 # homeassistant.components.bring -bring-api==0.5.6 +bring-api==0.5.7 # homeassistant.components.broadlink broadlink==0.18.3 From 2649dfaa118d060fd64c1062ca59a48a8a1f259f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 10:05:55 -1000 Subject: [PATCH 1533/1691] Fix ruff-format on energy sensor (#114261) --- homeassistant/components/energy/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index cca245f7e8d..37930e31af0 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -254,9 +254,9 @@ class EnergyCostSensor(SensorEntity): self._last_energy_sensor_state: State | None = None # add_finished is set when either of async_added_to_hass or add_to_platform_abort # is called - self.add_finished: asyncio.Future[ - None - ] = asyncio.get_running_loop().create_future() + self.add_finished: asyncio.Future[None] = ( + asyncio.get_running_loop().create_future() + ) def _reset(self, energy_state: State) -> None: """Reset the cost sensor.""" From 6599b9a36df26ce4c96b6bacc10b9259995562e1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 10:31:52 -1000 Subject: [PATCH 1534/1691] Restore scapy pin to >=2.5.0 (#114253) --- homeassistant/package_constraints.txt | 3 +++ script/gen_requirements_all.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0c13da70715..fa16d1ddeba 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -187,3 +187,6 @@ chacha20poly1305-reuseable>=0.12.1 # pycountry<23.12.11 imports setuptools at run time # https://github.com/pycountry/pycountry/blob/ea69bab36f00df58624a0e490fdad4ccdc14268b/HISTORY.txt#L39 pycountry>=23.12.11 + +# scapy<2.5.0 will not work with python3.12 +scapy>=2.5.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 6537bc652ac..9a9ff6821c7 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -180,6 +180,9 @@ chacha20poly1305-reuseable>=0.12.1 # pycountry<23.12.11 imports setuptools at run time # https://github.com/pycountry/pycountry/blob/ea69bab36f00df58624a0e490fdad4ccdc14268b/HISTORY.txt#L39 pycountry>=23.12.11 + +# scapy<2.5.0 will not work with python3.12 +scapy>=2.5.0 """ GENERATED_MESSAGE = ( From a0d43aeba24b6ae7e65dd14179b439f339c335bc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 10:32:16 -1000 Subject: [PATCH 1535/1691] Remove unused code in ESPHome entry_data (#114263) --- homeassistant/components/esphome/entry_data.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index 970dc296c95..ae0dca49411 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, Coroutine, Iterable +from collections.abc import Callable, Iterable from dataclasses import dataclass, field from functools import partial import logging @@ -173,15 +173,6 @@ class RuntimeEntryData: """Unsubscribe to when static info is registered.""" callbacks.remove(callback_) - @callback - def _async_unsubscribe_static_key_remove( - self, - callbacks: list[Callable[[], Coroutine[Any, Any, None]]], - callback_: Callable[[], Coroutine[Any, Any, None]], - ) -> None: - """Unsubscribe to when static info is removed.""" - callbacks.remove(callback_) - @callback def async_register_key_static_info_updated_callback( self, From e80d73783caa26bbb4121079887d109aee050e24 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 26 Mar 2024 21:54:09 +0100 Subject: [PATCH 1536/1691] Do not late import config_entry in `setup_test_component_platform` test helper (#114260) --- tests/common.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/common.py b/tests/common.py index c0733a7642b..faf14a55f01 100644 --- a/tests/common.py +++ b/tests/common.py @@ -38,7 +38,7 @@ from homeassistant.components.device_automation import ( # noqa: F401 _async_get_device_automation_capabilities as async_get_device_automation_capabilities, ) from homeassistant.config import async_process_component_config -from homeassistant.config_entries import ConfigFlow +from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import ( DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_CLOSE, @@ -1686,9 +1686,8 @@ def setup_test_component_platform( async_setup_platform=_async_setup_platform, ) - # avoid loading config_entry if not needed + # avoid creating config entry setup if not needed if from_config_entry: - from homeassistant.config_entries import ConfigEntry async def _async_setup_entry( hass: HomeAssistant, From f94f1fb826beb1e7977bdd7fa688d164641cec30 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 26 Mar 2024 21:56:38 +0100 Subject: [PATCH 1537/1691] Use `setup_test_component_platform` func for number entity component tests instead of `hass.components` (#114017) * Use `setup_test_component_platform` fixture for number entity component tests instead of `hass.components` * Now function * Ruff format --- .../number.py => components/number/common.py} | 61 +--------- tests/components/number/conftest.py | 19 +++ tests/components/number/test_init.py | 109 +++++++----------- 3 files changed, 64 insertions(+), 125 deletions(-) rename tests/{testing_config/custom_components/test/number.py => components/number/common.py} (56%) create mode 100644 tests/components/number/conftest.py diff --git a/tests/testing_config/custom_components/test/number.py b/tests/components/number/common.py similarity index 56% rename from tests/testing_config/custom_components/test/number.py rename to tests/components/number/common.py index 4de3dce233a..9843fea7751 100644 --- a/tests/testing_config/custom_components/test/number.py +++ b/tests/components/number/common.py @@ -1,16 +1,9 @@ -"""Provide a mock number platform. - -Call init before using it in your tests to ensure clean test data. -""" +"""Common helper and classes for number entity tests.""" from homeassistant.components.number import NumberEntity, RestoreNumber from tests.common import MockEntity -UNIQUE_NUMBER = "unique_number" - -ENTITIES = [] - class MockNumberEntity(MockEntity, NumberEntity): """Mock number class.""" @@ -60,55 +53,3 @@ class MockRestoreNumber(MockNumberEntity, RestoreNumber): last_number_data.native_unit_of_measurement ) self._values["native_value"] = last_number_data.native_value - - -class LegacyMockNumberEntity(MockEntity, NumberEntity): - """Mock Number class using deprecated features.""" - - @property - def max_value(self): - """Return the native max_value.""" - return self._handle("max_value") - - @property - def min_value(self): - """Return the native min_value.""" - return self._handle("min_value") - - @property - def step(self): - """Return the native step.""" - return self._handle("step") - - @property - def value(self): - """Return the current value.""" - return self._handle("value") - - def set_value(self, value: float) -> None: - """Change the selected option.""" - self._values["value"] = value - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - [] - if empty - else [ - MockNumberEntity( - name="test", - unique_id=UNIQUE_NUMBER, - native_value=50.0, - ), - ] - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(ENTITIES) diff --git a/tests/components/number/conftest.py b/tests/components/number/conftest.py new file mode 100644 index 00000000000..a84ab03611b --- /dev/null +++ b/tests/components/number/conftest.py @@ -0,0 +1,19 @@ +"""Fixtures for the number entity component tests.""" + +import pytest + +from tests.components.number.common import MockNumberEntity + +UNIQUE_NUMBER = "unique_number" + + +@pytest.fixture +def mock_number_entities() -> list[MockNumberEntity]: + """Return a list of mock number entities.""" + return [ + MockNumberEntity( + name="test", + unique_id="unique_number", + native_value=50.0, + ), + ] diff --git a/tests/components/number/test_init.py b/tests/components/number/test_init.py index 8eb84e90e3e..07d2baf4926 100644 --- a/tests/components/number/test_init.py +++ b/tests/components/number/test_init.py @@ -51,7 +51,9 @@ from tests.common import ( mock_integration, mock_platform, mock_restore_cache_with_extra_data, + setup_test_component_platform, ) +from tests.components.number import common TEST_DOMAIN = "test" @@ -332,10 +334,12 @@ async def test_sync_set_value(hass: HomeAssistant) -> None: assert number.set_value.call_args[0][0] == 42 -async def test_set_value(hass: HomeAssistant, enable_custom_integrations: None) -> None: +async def test_set_value( + hass: HomeAssistant, + mock_number_entities: list[MockNumberEntity], +) -> None: """Test we can only set valid values.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_number_entities) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -450,7 +454,6 @@ async def test_set_value(hass: HomeAssistant, enable_custom_integrations: None) ) async def test_temperature_conversion( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, state_unit, @@ -467,21 +470,17 @@ async def test_temperature_conversion( ) -> None: """Test temperature conversion.""" hass.config.units = unit_system - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockNumberEntity( - name="Test", - native_max_value=native_max_value, - native_min_value=native_min_value, - native_step=native_step, - native_unit_of_measurement=native_unit, - native_value=initial_native_value, - device_class=NumberDeviceClass.TEMPERATURE, - ) + entity0 = common.MockNumberEntity( + name="Test", + native_max_value=native_max_value, + native_min_value=native_min_value, + native_step=native_step, + native_unit_of_measurement=native_unit, + native_value=initial_native_value, + device_class=NumberDeviceClass.TEMPERATURE, ) + setup_test_component_platform(hass, DOMAIN, [entity0]) - entity0 = platform.ENTITIES[0] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -544,24 +543,19 @@ RESTORE_DATA = { async def test_restore_number_save_state( hass: HomeAssistant, hass_storage: dict[str, Any], - enable_custom_integrations: None, ) -> None: """Test RestoreNumber.""" - platform = getattr(hass.components, "test.number") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockRestoreNumber( - name="Test", - native_max_value=200.0, - native_min_value=-10.0, - native_step=2.0, - native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, - native_value=123.0, - device_class=NumberDeviceClass.TEMPERATURE, - ) + entity0 = common.MockRestoreNumber( + name="Test", + native_max_value=200.0, + native_min_value=-10.0, + native_step=2.0, + native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, + native_value=123.0, + device_class=NumberDeviceClass.TEMPERATURE, ) + setup_test_component_platform(hass, DOMAIN, [entity0]) - entity0 = platform.ENTITIES[0] assert await async_setup_component(hass, "number", {"number": {"platform": "test"}}) await hass.async_block_till_done() @@ -615,7 +609,6 @@ async def test_restore_number_save_state( ) async def test_restore_number_restore_state( hass: HomeAssistant, - enable_custom_integrations: None, hass_storage: dict[str, Any], native_max_value, native_min_value, @@ -629,17 +622,13 @@ async def test_restore_number_restore_state( """Test RestoreNumber.""" mock_restore_cache_with_extra_data(hass, ((State("number.test", ""), extra_data),)) - platform = getattr(hass.components, "test.number") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockRestoreNumber( - device_class=device_class, - name="Test", - native_value=None, - ) + entity0 = common.MockRestoreNumber( + device_class=device_class, + name="Test", + native_value=None, ) + setup_test_component_platform(hass, DOMAIN, [entity0]) - entity0 = platform.ENTITIES[0] assert await async_setup_component(hass, "number", {"number": {"platform": "test"}}) await hass.async_block_till_done() @@ -708,7 +697,6 @@ async def test_restore_number_restore_state( ) async def test_custom_unit( hass: HomeAssistant, - enable_custom_integrations: None, device_class, native_unit, custom_unit, @@ -725,19 +713,15 @@ async def test_custom_unit( ) await hass.async_block_till_done() - platform = getattr(hass.components, "test.number") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockNumberEntity( - name="Test", - native_value=native_value, - native_unit_of_measurement=native_unit, - device_class=device_class, - unique_id="very_unique", - ) + entity0 = common.MockNumberEntity( + name="Test", + native_value=native_value, + native_unit_of_measurement=native_unit, + device_class=device_class, + unique_id="very_unique", ) + setup_test_component_platform(hass, DOMAIN, [entity0]) - entity0 = platform.ENTITIES[0] assert await async_setup_component(hass, "number", {"number": {"platform": "test"}}) await hass.async_block_till_done() @@ -789,7 +773,6 @@ async def test_custom_unit( ) async def test_custom_unit_change( hass: HomeAssistant, - enable_custom_integrations: None, native_unit, custom_unit, used_custom_unit, @@ -800,19 +783,15 @@ async def test_custom_unit_change( ) -> None: """Test custom unit changes are picked up.""" entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.number") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockNumberEntity( - name="Test", - native_value=native_value, - native_unit_of_measurement=native_unit, - device_class=NumberDeviceClass.TEMPERATURE, - unique_id="very_unique", - ) + entity0 = common.MockNumberEntity( + name="Test", + native_value=native_value, + native_unit_of_measurement=native_unit, + device_class=NumberDeviceClass.TEMPERATURE, + unique_id="very_unique", ) + setup_test_component_platform(hass, DOMAIN, [entity0]) - entity0 = platform.ENTITIES[0] assert await async_setup_component(hass, "number", {"number": {"platform": "test"}}) await hass.async_block_till_done() From 72fed878b4278643f78df6a4bac7886681b7c0ac Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 26 Mar 2024 16:15:20 -0500 Subject: [PATCH 1538/1691] Add Ollama conversation agent (#113962) * Add ollama conversation agent * Change iot class * Much better default template * Slight adjustment to prompt * Make casing consistent * Switch to ollama Python fork * Add prompt to tests * Rename to "ollama" * Download models in config flow * Update homeassistant/components/ollama/config_flow.py --------- Co-authored-by: Paulus Schoutsen --- CODEOWNERS | 2 + homeassistant/components/ollama/__init__.py | 266 +++++++++++++ .../components/ollama/config_flow.py | 245 ++++++++++++ homeassistant/components/ollama/const.py | 114 ++++++ homeassistant/components/ollama/manifest.json | 11 + homeassistant/components/ollama/models.py | 47 +++ homeassistant/components/ollama/strings.json | 33 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 6 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/ollama/__init__.py | 14 + tests/components/ollama/conftest.py | 37 ++ tests/components/ollama/test_config_flow.py | 234 +++++++++++ tests/components/ollama/test_init.py | 366 ++++++++++++++++++ 15 files changed, 1382 insertions(+) create mode 100644 homeassistant/components/ollama/__init__.py create mode 100644 homeassistant/components/ollama/config_flow.py create mode 100644 homeassistant/components/ollama/const.py create mode 100644 homeassistant/components/ollama/manifest.json create mode 100644 homeassistant/components/ollama/models.py create mode 100644 homeassistant/components/ollama/strings.json create mode 100644 tests/components/ollama/__init__.py create mode 100644 tests/components/ollama/conftest.py create mode 100644 tests/components/ollama/test_config_flow.py create mode 100644 tests/components/ollama/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index 7ba24210f96..85603250b7c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -933,6 +933,8 @@ build.json @home-assistant/supervisor /homeassistant/components/octoprint/ @rfleming71 /tests/components/octoprint/ @rfleming71 /homeassistant/components/ohmconnect/ @robbiet480 +/homeassistant/components/ollama/ @synesthesiam +/tests/components/ollama/ @synesthesiam /homeassistant/components/ombi/ @larssont /homeassistant/components/omnilogic/ @oliver84 @djtimca @gentoosu /tests/components/omnilogic/ @oliver84 @djtimca @gentoosu diff --git a/homeassistant/components/ollama/__init__.py b/homeassistant/components/ollama/__init__.py new file mode 100644 index 00000000000..8c9b00f3c9c --- /dev/null +++ b/homeassistant/components/ollama/__init__.py @@ -0,0 +1,266 @@ +"""The Ollama integration.""" + +from __future__ import annotations + +import asyncio +import logging +import time +from typing import Literal + +import httpx +import ollama + +from homeassistant.components import conversation +from homeassistant.components.homeassistant.exposed_entities import async_should_expose +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_URL, MATCH_ALL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady, TemplateError +from homeassistant.helpers import ( + area_registry as ar, + config_validation as cv, + device_registry as dr, + entity_registry as er, + intent, + template, +) +from homeassistant.util import ulid + +from .const import ( + CONF_MAX_HISTORY, + CONF_MODEL, + CONF_PROMPT, + DEFAULT_MAX_HISTORY, + DEFAULT_PROMPT, + DEFAULT_TIMEOUT, + DOMAIN, + KEEP_ALIVE_FOREVER, + MAX_HISTORY_SECONDS, +) +from .models import ExposedEntity, MessageHistory, MessageRole + +_LOGGER = logging.getLogger(__name__) + +__all__ = [ + "CONF_URL", + "CONF_PROMPT", + "CONF_MODEL", + "CONF_MAX_HISTORY", + "MAX_HISTORY_NO_LIMIT", + "DOMAIN", +] + +CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Ollama from a config entry.""" + settings = {**entry.data, **entry.options} + client = ollama.AsyncClient(host=settings[CONF_URL]) + try: + async with asyncio.timeout(DEFAULT_TIMEOUT): + await client.list() + except (TimeoutError, httpx.ConnectError) as err: + raise ConfigEntryNotReady(err) from err + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = client + + conversation.async_set_agent(hass, entry, OllamaAgent(hass, entry)) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Ollama.""" + hass.data[DOMAIN].pop(entry.entry_id) + conversation.async_unset_agent(hass, entry) + return True + + +class OllamaAgent(conversation.AbstractConversationAgent): + """Ollama conversation agent.""" + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + """Initialize the agent.""" + self.hass = hass + self.entry = entry + + # conversation id -> message history + self._history: dict[str, MessageHistory] = {} + + @property + def supported_languages(self) -> list[str] | Literal["*"]: + """Return a list of supported languages.""" + return MATCH_ALL + + async def async_process( + self, user_input: conversation.ConversationInput + ) -> conversation.ConversationResult: + """Process a sentence.""" + settings = {**self.entry.data, **self.entry.options} + + client = self.hass.data[DOMAIN][self.entry.entry_id] + conversation_id = user_input.conversation_id or ulid.ulid_now() + model = settings[CONF_MODEL] + + # Look up message history + message_history: MessageHistory | None = None + message_history = self._history.get(conversation_id) + if message_history is None: + # New history + # + # Render prompt and error out early if there's a problem + raw_prompt = settings.get(CONF_PROMPT, DEFAULT_PROMPT) + try: + prompt = self._generate_prompt(raw_prompt) + _LOGGER.debug("Prompt: %s", prompt) + except TemplateError as err: + _LOGGER.error("Error rendering prompt: %s", err) + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + f"Sorry, I had a problem generating my prompt: {err}", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + message_history = MessageHistory( + timestamp=time.monotonic(), + messages=[ + ollama.Message(role=MessageRole.SYSTEM.value, content=prompt) + ], + ) + self._history[conversation_id] = message_history + else: + # Bump timestamp so this conversation won't get cleaned up + message_history.timestamp = time.monotonic() + + # Clean up old histories + self._prune_old_histories() + + # Trim this message history to keep a maximum number of *user* messages + max_messages = int(settings.get(CONF_MAX_HISTORY, DEFAULT_MAX_HISTORY)) + self._trim_history(message_history, max_messages) + + # Add new user message + message_history.messages.append( + ollama.Message(role=MessageRole.USER.value, content=user_input.text) + ) + + # Get response + try: + response = await client.chat( + model=model, + # Make a copy of the messages because we mutate the list later + messages=list(message_history.messages), + stream=False, + keep_alive=KEEP_ALIVE_FOREVER, + ) + except (ollama.RequestError, ollama.ResponseError) as err: + _LOGGER.error("Unexpected error talking to Ollama server: %s", err) + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + f"Sorry, I had a problem talking to the Ollama server: {err}", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + response_message = response["message"] + message_history.messages.append( + ollama.Message( + role=response_message["role"], content=response_message["content"] + ) + ) + + # Create intent response + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_speech(response_message["content"]) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + def _prune_old_histories(self) -> None: + """Remove old message histories.""" + now = time.monotonic() + self._history = { + conversation_id: message_history + for conversation_id, message_history in self._history.items() + if (now - message_history.timestamp) <= MAX_HISTORY_SECONDS + } + + def _trim_history(self, message_history: MessageHistory, max_messages: int) -> None: + """Trims excess messages from a single history.""" + if max_messages < 1: + # Keep all messages + return + + if message_history.num_user_messages >= max_messages: + # Trim history but keep system prompt (first message). + # Every other message should be an assistant message, so keep 2x + # message objects. + num_keep = 2 * max_messages + drop_index = len(message_history.messages) - num_keep + message_history.messages = [ + message_history.messages[0] + ] + message_history.messages[drop_index:] + + def _generate_prompt(self, raw_prompt: str) -> str: + """Generate a prompt for the user.""" + return template.Template(raw_prompt, self.hass).async_render( + { + "ha_name": self.hass.config.location_name, + "ha_language": self.hass.config.language, + "exposed_entities": self._get_exposed_entities(), + }, + parse_result=False, + ) + + def _get_exposed_entities(self) -> list[ExposedEntity]: + """Get state list of exposed entities.""" + area_registry = ar.async_get(self.hass) + entity_registry = er.async_get(self.hass) + device_registry = dr.async_get(self.hass) + + exposed_entities = [] + exposed_states = [ + state + for state in self.hass.states.async_all() + if async_should_expose(self.hass, conversation.DOMAIN, state.entity_id) + ] + + for state in exposed_states: + entity = entity_registry.async_get(state.entity_id) + names = [state.name] + area_names = [] + + if entity is not None: + # Add aliases + names.extend(entity.aliases) + if entity.area_id and ( + area := area_registry.async_get_area(entity.area_id) + ): + # Entity is in area + area_names.append(area.name) + area_names.extend(area.aliases) + elif entity.device_id and ( + device := device_registry.async_get(entity.device_id) + ): + # Check device area + if device.area_id and ( + area := area_registry.async_get_area(device.area_id) + ): + area_names.append(area.name) + area_names.extend(area.aliases) + + exposed_entities.append( + ExposedEntity( + entity_id=state.entity_id, + state=state, + names=names, + area_names=area_names, + ) + ) + + return exposed_entities diff --git a/homeassistant/components/ollama/config_flow.py b/homeassistant/components/ollama/config_flow.py new file mode 100644 index 00000000000..50d0667803f --- /dev/null +++ b/homeassistant/components/ollama/config_flow.py @@ -0,0 +1,245 @@ +"""Config flow for Ollama integration.""" + +from __future__ import annotations + +import asyncio +import logging +import sys +from types import MappingProxyType +from typing import Any + +import httpx +import ollama +import voluptuous as vol + +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) +from homeassistant.const import CONF_URL +from homeassistant.helpers.selector import ( + NumberSelector, + NumberSelectorConfig, + NumberSelectorMode, + SelectOptionDict, + SelectSelector, + SelectSelectorConfig, + TemplateSelector, + TextSelector, + TextSelectorConfig, + TextSelectorType, +) + +from .const import ( + CONF_MAX_HISTORY, + CONF_MODEL, + CONF_PROMPT, + DEFAULT_MAX_HISTORY, + DEFAULT_MODEL, + DEFAULT_PROMPT, + DEFAULT_TIMEOUT, + DOMAIN, + MODEL_NAMES, +) + +_LOGGER = logging.getLogger(__name__) + + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_URL): TextSelector( + TextSelectorConfig(type=TextSelectorType.URL) + ), + } +) + + +class OllamaConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Ollama.""" + + VERSION = 1 + + def __init__(self) -> None: + """Initialize config flow.""" + self.url: str | None = None + self.model: str | None = None + self.client: ollama.AsyncClient | None = None + self.download_task: asyncio.Task | None = None + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle the initial step.""" + user_input = user_input or {} + self.url = user_input.get(CONF_URL, self.url) + self.model = user_input.get(CONF_MODEL, self.model) + + if self.url is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, last_step=False + ) + + errors = {} + + try: + self.client = ollama.AsyncClient(host=self.url) + async with asyncio.timeout(DEFAULT_TIMEOUT): + response = await self.client.list() + + downloaded_models: set[str] = { + model_info["model"] for model_info in response.get("models", []) + } + except (TimeoutError, httpx.ConnectError): + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if errors: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + if self.model is None: + # Show models that have been downloaded first, followed by all known + # models (only latest tags). + models_to_list = [ + SelectOptionDict(label=f"{m} (downloaded)", value=m) + for m in sorted(downloaded_models) + ] + [ + SelectOptionDict(label=m, value=f"{m}:latest") + for m in sorted(MODEL_NAMES) + if m not in downloaded_models + ] + model_step_schema = vol.Schema( + { + vol.Required( + CONF_MODEL, description={"suggested_value": DEFAULT_MODEL} + ): SelectSelector( + SelectSelectorConfig(options=models_to_list, custom_value=True) + ), + } + ) + + return self.async_show_form( + step_id="user", + data_schema=model_step_schema, + ) + + if self.model not in downloaded_models: + # Ollama server needs to download model first + return await self.async_step_download() + + return self.async_create_entry( + title=_get_title(self.model), + data={CONF_URL: self.url, CONF_MODEL: self.model}, + ) + + async def async_step_download( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Step to wait for Ollama server to download a model.""" + assert self.model is not None + assert self.client is not None + + if self.download_task is None: + # Tell Ollama server to pull the model. + # The task will block until the model and metadata are fully + # downloaded. + self.download_task = self.hass.async_create_background_task( + self.client.pull(self.model), f"Downloading {self.model}" + ) + + if self.download_task.done(): + if err := self.download_task.exception(): + _LOGGER.exception("Unexpected error while downloading model: %s", err) + return self.async_show_progress_done(next_step_id="failed") + + return self.async_show_progress_done(next_step_id="finish") + + return self.async_show_progress( + step_id="download", + progress_action="download", + progress_task=self.download_task, + ) + + async def async_step_finish( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Step after model downloading has succeeded.""" + assert self.url is not None + assert self.model is not None + + return self.async_create_entry( + title=_get_title(self.model), + data={CONF_URL: self.url, CONF_MODEL: self.model}, + ) + + async def async_step_failed( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Step after model downloading has failed.""" + return self.async_abort(reason="download_failed") + + @staticmethod + def async_get_options_flow( + config_entry: ConfigEntry, + ) -> OptionsFlow: + """Create the options flow.""" + return OllamaOptionsFlow(config_entry) + + +class OllamaOptionsFlow(OptionsFlow): + """Ollama options flow.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + self.url: str = self.config_entry.data[CONF_URL] + self.model: str = self.config_entry.data[CONF_MODEL] + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Manage the options.""" + if user_input is not None: + return self.async_create_entry( + title=_get_title(self.model), data=user_input + ) + + options = self.config_entry.options or MappingProxyType({}) + schema = ollama_config_option_schema(options) + return self.async_show_form( + step_id="init", + data_schema=vol.Schema(schema), + ) + + +def ollama_config_option_schema(options: MappingProxyType[str, Any]) -> dict: + """Ollama options schema.""" + return { + vol.Optional( + CONF_PROMPT, + description={"suggested_value": options.get(CONF_PROMPT, DEFAULT_PROMPT)}, + ): TemplateSelector(), + vol.Optional( + CONF_MAX_HISTORY, + description={ + "suggested_value": options.get(CONF_MAX_HISTORY, DEFAULT_MAX_HISTORY) + }, + ): NumberSelector( + NumberSelectorConfig( + min=0, max=sys.maxsize, step=1, mode=NumberSelectorMode.BOX + ) + ), + } + + +def _get_title(model: str) -> str: + """Get title for config entry.""" + if model.endswith(":latest"): + model = model.split(":", maxsplit=1)[0] + + return model diff --git a/homeassistant/components/ollama/const.py b/homeassistant/components/ollama/const.py new file mode 100644 index 00000000000..59f1888cfc7 --- /dev/null +++ b/homeassistant/components/ollama/const.py @@ -0,0 +1,114 @@ +"""Constants for the Ollama integration.""" + +DOMAIN = "ollama" + +CONF_MODEL = "model" +CONF_PROMPT = "prompt" +DEFAULT_PROMPT = """{%- set used_domains = set([ + "binary_sensor", + "climate", + "cover", + "fan", + "light", + "lock", + "sensor", + "switch", + "weather", +]) %} +{%- set used_attributes = set([ + "temperature", + "current_temperature", + "temperature_unit", + "brightness", + "humidity", + "unit_of_measurement", + "device_class", + "current_position", + "percentage", +]) %} + +This smart home is controlled by Home Assistant. +The current time is {{ now().strftime("%X") }}. +Today's date is {{ now().strftime("%x") }}. + +An overview of the areas and the devices in this smart home: +```yaml +{%- for entity in exposed_entities: %} +{%- if entity.domain not in used_domains: %} + {%- continue %} +{%- endif %} + +- domain: {{ entity.domain }} +{%- if entity.names | length == 1: %} + name: {{ entity.names[0] }} +{%- else: %} + names: +{%- for name in entity.names: %} + - {{ name }} +{%- endfor %} +{%- endif %} +{%- if entity.area_names | length == 1: %} + area: {{ entity.area_names[0] }} +{%- elif entity.area_names: %} + areas: +{%- for area_name in entity.area_names: %} + - {{ area_name }} +{%- endfor %} +{%- endif %} + state: {{ entity.state.state }} + {%- set attributes_key_printed = False %} +{%- for attr_name, attr_value in entity.state.attributes.items(): %} + {%- if attr_name in used_attributes: %} + {%- if not attributes_key_printed: %} + attributes: + {%- set attributes_key_printed = True %} + {%- endif %} + {{ attr_name }}: {{ attr_value }} + {%- endif %} +{%- endfor %} +{%- endfor %} +``` + +Answer the user's questions using the information about this smart home. +Keep your answers brief and do not apologize.""" + +KEEP_ALIVE_FOREVER = -1 +DEFAULT_TIMEOUT = 5.0 # seconds + +CONF_MAX_HISTORY = "max_history" +DEFAULT_MAX_HISTORY = 20 + +MAX_HISTORY_SECONDS = 60 * 60 # 1 hour + +MODEL_NAMES = [ # https://ollama.com/library + "gemma", + "llama2", + "mistral", + "mixtral", + "llava", + "neural-chat", + "codellama", + "dolphin-mixtral", + "qwen", + "llama2-uncensored", + "mistral-openorca", + "deepseek-coder", + "nous-hermes2", + "phi", + "orca-mini", + "dolphin-mistral", + "wizard-vicuna-uncensored", + "vicuna", + "tinydolphin", + "llama2-chinese", + "nomic-embed-text", + "openhermes", + "zephyr", + "tinyllama", + "openchat", + "wizardcoder", + "starcoder", + "phind-codellama", + "starcoder2", +] +DEFAULT_MODEL = "llama2:latest" diff --git a/homeassistant/components/ollama/manifest.json b/homeassistant/components/ollama/manifest.json new file mode 100644 index 00000000000..6b16ae667f1 --- /dev/null +++ b/homeassistant/components/ollama/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "ollama", + "name": "Ollama", + "codeowners": ["@synesthesiam"], + "config_flow": true, + "dependencies": ["conversation"], + "documentation": "https://www.home-assistant.io/integrations/ollama", + "integration_type": "service", + "iot_class": "local_polling", + "requirements": ["ollama-hass==0.1.7"] +} diff --git a/homeassistant/components/ollama/models.py b/homeassistant/components/ollama/models.py new file mode 100644 index 00000000000..ce0f858bb8c --- /dev/null +++ b/homeassistant/components/ollama/models.py @@ -0,0 +1,47 @@ +"""Models for Ollama integration.""" + +from dataclasses import dataclass +from enum import StrEnum +from functools import cached_property + +import ollama + +from homeassistant.core import State + + +class MessageRole(StrEnum): + """Role of a chat message.""" + + SYSTEM = "system" # prompt + USER = "user" + + +@dataclass +class MessageHistory: + """Chat message history.""" + + timestamp: float + """Timestamp of last use in seconds.""" + + messages: list[ollama.Message] + """List of message history, including system prompt and assistant responses.""" + + @property + def num_user_messages(self) -> int: + """Return a count of user messages.""" + return sum(m["role"] == MessageRole.USER for m in self.messages) + + +@dataclass(frozen=True) +class ExposedEntity: + """Relevant information about an exposed entity.""" + + entity_id: str + state: State + names: list[str] + area_names: list[str] + + @cached_property + def domain(self) -> str: + """Get domain from entity id.""" + return self.entity_id.split(".", maxsplit=1)[0] diff --git a/homeassistant/components/ollama/strings.json b/homeassistant/components/ollama/strings.json new file mode 100644 index 00000000000..59f48929681 --- /dev/null +++ b/homeassistant/components/ollama/strings.json @@ -0,0 +1,33 @@ +{ + "config": { + "step": { + "user": { + "data": { + "url": "[%key:common::config_flow::data::url%]", + "model": "Model" + } + }, + "download": { + "title": "Downloading model" + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "download_failed": "Model downloading failed", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "progress": { + "download": "Please wait while the model is downloaded, which may take a very long time. Check your Ollama server logs for more details." + } + }, + "options": { + "step": { + "init": { + "data": { + "prompt": "Prompt template", + "max_history": "Max history messages" + } + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index d779fbead64..8d46c8be240 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -360,6 +360,7 @@ FLOWS = { "nzbget", "obihai", "octoprint", + "ollama", "omnilogic", "oncue", "ondilo_ico", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 2b4a637dacc..6cba84431f3 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -4136,6 +4136,12 @@ "config_flow": false, "iot_class": "cloud_polling" }, + "ollama": { + "name": "Ollama", + "integration_type": "service", + "config_flow": true, + "iot_class": "local_polling" + }, "ombi": { "name": "Ombi", "integration_type": "hub", diff --git a/requirements_all.txt b/requirements_all.txt index 3487723b62a..4fef44d80b2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1436,6 +1436,9 @@ odp-amsterdam==6.0.1 # homeassistant.components.oem oemthermostat==1.1.1 +# homeassistant.components.ollama +ollama-hass==0.1.7 + # homeassistant.components.omnilogic omnilogic==0.4.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2f8aaafee0a..75ba113891e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1148,6 +1148,9 @@ objgraph==3.5.0 # homeassistant.components.garages_amsterdam odp-amsterdam==6.0.1 +# homeassistant.components.ollama +ollama-hass==0.1.7 + # homeassistant.components.omnilogic omnilogic==0.4.5 diff --git a/tests/components/ollama/__init__.py b/tests/components/ollama/__init__.py new file mode 100644 index 00000000000..22a576e94a4 --- /dev/null +++ b/tests/components/ollama/__init__.py @@ -0,0 +1,14 @@ +"""Tests for the Ollama integration.""" + +from homeassistant.components import ollama +from homeassistant.components.ollama.const import DEFAULT_PROMPT + +TEST_USER_DATA = { + ollama.CONF_URL: "http://localhost:11434", + ollama.CONF_MODEL: "test model", +} + +TEST_OPTIONS = { + ollama.CONF_PROMPT: DEFAULT_PROMPT, + ollama.CONF_MAX_HISTORY: 2, +} diff --git a/tests/components/ollama/conftest.py b/tests/components/ollama/conftest.py new file mode 100644 index 00000000000..78ecf0766d7 --- /dev/null +++ b/tests/components/ollama/conftest.py @@ -0,0 +1,37 @@ +"""Tests Ollama integration.""" + +from unittest.mock import patch + +import pytest + +from homeassistant.components import ollama +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from . import TEST_OPTIONS, TEST_USER_DATA + +from tests.common import MockConfigEntry + + +@pytest.fixture +def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry: + """Mock a config entry.""" + entry = MockConfigEntry( + domain=ollama.DOMAIN, + data=TEST_USER_DATA, + options=TEST_OPTIONS, + ) + entry.add_to_hass(hass) + return entry + + +@pytest.fixture +async def mock_init_component(hass: HomeAssistant, mock_config_entry: MockConfigEntry): + """Initialize integration.""" + assert await async_setup_component(hass, "homeassistant", {}) + + with patch( + "ollama.AsyncClient.list", + ): + assert await async_setup_component(hass, ollama.DOMAIN, {}) + await hass.async_block_till_done() diff --git a/tests/components/ollama/test_config_flow.py b/tests/components/ollama/test_config_flow.py new file mode 100644 index 00000000000..825f3eac436 --- /dev/null +++ b/tests/components/ollama/test_config_flow.py @@ -0,0 +1,234 @@ +"""Test the Ollama config flow.""" + +import asyncio +from unittest.mock import patch + +from httpx import ConnectError +import pytest + +from homeassistant import config_entries +from homeassistant.components import ollama +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + +TEST_MODEL = "test_model:latest" + + +async def test_form(hass: HomeAssistant) -> None: + """Test flow when the model is already downloaded.""" + # Pretend we already set up a config entry. + hass.config.components.add(ollama.DOMAIN) + MockConfigEntry( + domain=ollama.DOMAIN, + state=config_entries.ConfigEntryState.LOADED, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + ollama.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + with ( + patch( + "homeassistant.components.ollama.config_flow.ollama.AsyncClient.list", + # test model is already "downloaded" + return_value={"models": [{"model": TEST_MODEL}]}, + ), + patch( + "homeassistant.components.ollama.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): + # Step 1: URL + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {ollama.CONF_URL: "http://localhost:11434"} + ) + await hass.async_block_till_done() + + # Step 2: model + assert result2["type"] == FlowResultType.FORM + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {ollama.CONF_MODEL: TEST_MODEL} + ) + await hass.async_block_till_done() + + assert result3["type"] == FlowResultType.CREATE_ENTRY + assert result3["data"] == { + ollama.CONF_URL: "http://localhost:11434", + ollama.CONF_MODEL: TEST_MODEL, + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_need_download(hass: HomeAssistant) -> None: + """Test flow when a model needs to be downloaded.""" + # Pretend we already set up a config entry. + hass.config.components.add(ollama.DOMAIN) + MockConfigEntry( + domain=ollama.DOMAIN, + state=config_entries.ConfigEntryState.LOADED, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + ollama.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == FlowResultType.FORM + assert result["errors"] is None + + pull_ready = asyncio.Event() + pull_called = asyncio.Event() + pull_model: str | None = None + + async def pull(self, model: str, *args, **kwargs) -> None: + nonlocal pull_model + + async with asyncio.timeout(1): + await pull_ready.wait() + + pull_model = model + pull_called.set() + + with ( + patch( + "homeassistant.components.ollama.config_flow.ollama.AsyncClient.list", + # No models are downloaded + return_value={}, + ), + patch( + "homeassistant.components.ollama.config_flow.ollama.AsyncClient.pull", + pull, + ), + patch( + "homeassistant.components.ollama.async_setup_entry", + return_value=True, + ) as mock_setup_entry, + ): + # Step 1: URL + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {ollama.CONF_URL: "http://localhost:11434"} + ) + await hass.async_block_till_done() + + # Step 2: model + assert result2["type"] == FlowResultType.FORM + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {ollama.CONF_MODEL: TEST_MODEL} + ) + await hass.async_block_till_done() + + # Step 3: download + assert result3["type"] == FlowResultType.SHOW_PROGRESS + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], + ) + await hass.async_block_till_done() + + # Run again without the task finishing. + # We should still be downloading. + assert result4["type"] == FlowResultType.SHOW_PROGRESS + result4 = await hass.config_entries.flow.async_configure( + result4["flow_id"], + ) + await hass.async_block_till_done() + assert result4["type"] == FlowResultType.SHOW_PROGRESS + + # Signal fake pull method to complete + pull_ready.set() + async with asyncio.timeout(1): + await pull_called.wait() + + assert pull_model == TEST_MODEL + + # Step 4: finish + result5 = await hass.config_entries.flow.async_configure( + result4["flow_id"], + ) + + assert result5["type"] == FlowResultType.CREATE_ENTRY + assert result5["data"] == { + ollama.CONF_URL: "http://localhost:11434", + ollama.CONF_MODEL: TEST_MODEL, + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_options( + hass: HomeAssistant, mock_config_entry, mock_init_component +) -> None: + """Test the options form.""" + options_flow = await hass.config_entries.options.async_init( + mock_config_entry.entry_id + ) + options = await hass.config_entries.options.async_configure( + options_flow["flow_id"], + {ollama.CONF_PROMPT: "test prompt", ollama.CONF_MAX_HISTORY: 100}, + ) + await hass.async_block_till_done() + assert options["type"] == FlowResultType.CREATE_ENTRY + assert options["data"] == { + ollama.CONF_PROMPT: "test prompt", + ollama.CONF_MAX_HISTORY: 100, + } + + +@pytest.mark.parametrize( + ("side_effect", "error"), + [ + (ConnectError(message=""), "cannot_connect"), + (RuntimeError(), "unknown"), + ], +) +async def test_form_errors(hass: HomeAssistant, side_effect, error) -> None: + """Test we handle errors.""" + result = await hass.config_entries.flow.async_init( + ollama.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.ollama.config_flow.ollama.AsyncClient.list", + side_effect=side_effect, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {ollama.CONF_URL: "http://localhost:11434"} + ) + + assert result2["type"] == FlowResultType.FORM + assert result2["errors"] == {"base": error} + + +async def test_download_error(hass: HomeAssistant) -> None: + """Test we handle errors while downloading a model.""" + result = await hass.config_entries.flow.async_init( + ollama.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with ( + patch( + "homeassistant.components.ollama.config_flow.ollama.AsyncClient.list", + return_value={}, + ), + patch( + "homeassistant.components.ollama.config_flow.ollama.AsyncClient.pull", + side_effect=RuntimeError(), + ), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {ollama.CONF_URL: "http://localhost:11434"} + ) + await hass.async_block_till_done() + + assert result2["type"] == FlowResultType.FORM + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], {ollama.CONF_MODEL: TEST_MODEL} + ) + await hass.async_block_till_done() + + assert result3["type"] == FlowResultType.SHOW_PROGRESS + result4 = await hass.config_entries.flow.async_configure(result3["flow_id"]) + await hass.async_block_till_done() + + assert result4["type"] == FlowResultType.ABORT + assert result4["reason"] == "download_failed" diff --git a/tests/components/ollama/test_init.py b/tests/components/ollama/test_init.py new file mode 100644 index 00000000000..ffe69ca4628 --- /dev/null +++ b/tests/components/ollama/test_init.py @@ -0,0 +1,366 @@ +"""Tests for the Ollama integration.""" + +from unittest.mock import AsyncMock, patch + +from httpx import ConnectError +from ollama import Message, ResponseError +import pytest + +from homeassistant.components import conversation, ollama +from homeassistant.components.homeassistant.exposed_entities import async_expose_entity +from homeassistant.const import ATTR_FRIENDLY_NAME, MATCH_ALL +from homeassistant.core import Context, HomeAssistant +from homeassistant.helpers import ( + area_registry as ar, + device_registry as dr, + entity_registry as er, + intent, +) +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + + +async def test_chat( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component, + area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test that the chat function is called with the appropriate arguments.""" + + # Create some areas, devices, and entities + area_kitchen = area_registry.async_get_or_create("kitchen_id") + area_kitchen = area_registry.async_update(area_kitchen.id, name="kitchen") + area_bedroom = area_registry.async_get_or_create("bedroom_id") + area_bedroom = area_registry.async_update(area_bedroom.id, name="bedroom") + area_office = area_registry.async_get_or_create("office_id") + area_office = area_registry.async_update(area_office.id, name="office") + + entry = MockConfigEntry() + entry.add_to_hass(hass) + kitchen_device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections=set(), + identifiers={("demo", "id-1234")}, + ) + device_registry.async_update_device(kitchen_device.id, area_id=area_kitchen.id) + + kitchen_light = entity_registry.async_get_or_create("light", "demo", "1234") + kitchen_light = entity_registry.async_update_entity( + kitchen_light.entity_id, device_id=kitchen_device.id + ) + hass.states.async_set( + kitchen_light.entity_id, "on", attributes={ATTR_FRIENDLY_NAME: "kitchen light"} + ) + + bedroom_light = entity_registry.async_get_or_create("light", "demo", "5678") + bedroom_light = entity_registry.async_update_entity( + bedroom_light.entity_id, area_id=area_bedroom.id + ) + hass.states.async_set( + bedroom_light.entity_id, "on", attributes={ATTR_FRIENDLY_NAME: "bedroom light"} + ) + + # Hide the office light + office_light = entity_registry.async_get_or_create("light", "demo", "ABCD") + office_light = entity_registry.async_update_entity( + office_light.entity_id, area_id=area_office.id + ) + hass.states.async_set( + office_light.entity_id, "on", attributes={ATTR_FRIENDLY_NAME: "office light"} + ) + async_expose_entity(hass, conversation.DOMAIN, office_light.entity_id, False) + + with patch( + "ollama.AsyncClient.chat", + return_value={"message": {"role": "assistant", "content": "test response"}}, + ) as mock_chat: + result = await conversation.async_converse( + hass, + "test message", + None, + Context(), + agent_id=mock_config_entry.entry_id, + ) + + assert mock_chat.call_count == 1 + args = mock_chat.call_args.kwargs + prompt = args["messages"][0]["content"] + + assert args["model"] == "test model" + assert args["messages"] == [ + Message({"role": "system", "content": prompt}), + Message({"role": "user", "content": "test message"}), + ] + + # Verify only exposed devices/areas are in prompt + assert "kitchen light" in prompt + assert "bedroom light" in prompt + assert "office light" not in prompt + assert "office" not in prompt + + assert ( + result.response.response_type == intent.IntentResponseType.ACTION_DONE + ), result + assert result.response.speech["plain"]["speech"] == "test response" + + +async def test_message_history_trimming( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component +) -> None: + """Test that a single message history is trimmed according to the config.""" + response_idx = 0 + + def response(*args, **kwargs) -> dict: + nonlocal response_idx + response_idx += 1 + return {"message": {"role": "assistant", "content": f"response {response_idx}"}} + + with patch( + "ollama.AsyncClient.chat", + side_effect=response, + ) as mock_chat: + # mock_init_component sets "max_history" to 2 + for i in range(5): + result = await conversation.async_converse( + hass, + f"message {i+1}", + conversation_id="1234", + context=Context(), + agent_id=mock_config_entry.entry_id, + ) + assert ( + result.response.response_type == intent.IntentResponseType.ACTION_DONE + ), result + + assert mock_chat.call_count == 5 + args = mock_chat.call_args_list + prompt = args[0].kwargs["messages"][0]["content"] + + # system + user-1 + assert len(args[0].kwargs["messages"]) == 2 + assert args[0].kwargs["messages"][1]["content"] == "message 1" + + # Full history + # system + user-1 + assistant-1 + user-2 + assert len(args[1].kwargs["messages"]) == 4 + assert args[1].kwargs["messages"][0]["role"] == "system" + assert args[1].kwargs["messages"][0]["content"] == prompt + assert args[1].kwargs["messages"][1]["role"] == "user" + assert args[1].kwargs["messages"][1]["content"] == "message 1" + assert args[1].kwargs["messages"][2]["role"] == "assistant" + assert args[1].kwargs["messages"][2]["content"] == "response 1" + assert args[1].kwargs["messages"][3]["role"] == "user" + assert args[1].kwargs["messages"][3]["content"] == "message 2" + + # Full history + # system + user-1 + assistant-1 + user-2 + assistant-2 + user-3 + assert len(args[2].kwargs["messages"]) == 6 + assert args[2].kwargs["messages"][0]["role"] == "system" + assert args[2].kwargs["messages"][0]["content"] == prompt + assert args[2].kwargs["messages"][1]["role"] == "user" + assert args[2].kwargs["messages"][1]["content"] == "message 1" + assert args[2].kwargs["messages"][2]["role"] == "assistant" + assert args[2].kwargs["messages"][2]["content"] == "response 1" + assert args[2].kwargs["messages"][3]["role"] == "user" + assert args[2].kwargs["messages"][3]["content"] == "message 2" + assert args[2].kwargs["messages"][4]["role"] == "assistant" + assert args[2].kwargs["messages"][4]["content"] == "response 2" + assert args[2].kwargs["messages"][5]["role"] == "user" + assert args[2].kwargs["messages"][5]["content"] == "message 3" + + # Trimmed down to two user messages. + # system + user-2 + assistant-2 + user-3 + assistant-3 + user-4 + assert len(args[3].kwargs["messages"]) == 6 + assert args[3].kwargs["messages"][0]["role"] == "system" + assert args[3].kwargs["messages"][0]["content"] == prompt + assert args[3].kwargs["messages"][1]["role"] == "user" + assert args[3].kwargs["messages"][1]["content"] == "message 2" + assert args[3].kwargs["messages"][2]["role"] == "assistant" + assert args[3].kwargs["messages"][2]["content"] == "response 2" + assert args[3].kwargs["messages"][3]["role"] == "user" + assert args[3].kwargs["messages"][3]["content"] == "message 3" + assert args[3].kwargs["messages"][4]["role"] == "assistant" + assert args[3].kwargs["messages"][4]["content"] == "response 3" + assert args[3].kwargs["messages"][5]["role"] == "user" + assert args[3].kwargs["messages"][5]["content"] == "message 4" + + # Trimmed down to two user messages. + # system + user-3 + assistant-3 + user-4 + assistant-4 + user-5 + assert len(args[3].kwargs["messages"]) == 6 + assert args[4].kwargs["messages"][0]["role"] == "system" + assert args[4].kwargs["messages"][0]["content"] == prompt + assert args[4].kwargs["messages"][1]["role"] == "user" + assert args[4].kwargs["messages"][1]["content"] == "message 3" + assert args[4].kwargs["messages"][2]["role"] == "assistant" + assert args[4].kwargs["messages"][2]["content"] == "response 3" + assert args[4].kwargs["messages"][3]["role"] == "user" + assert args[4].kwargs["messages"][3]["content"] == "message 4" + assert args[4].kwargs["messages"][4]["role"] == "assistant" + assert args[4].kwargs["messages"][4]["content"] == "response 4" + assert args[4].kwargs["messages"][5]["role"] == "user" + assert args[4].kwargs["messages"][5]["content"] == "message 5" + + +async def test_message_history_pruning( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component +) -> None: + """Test that old message histories are pruned.""" + with patch( + "ollama.AsyncClient.chat", + return_value={"message": {"role": "assistant", "content": "test response"}}, + ): + # Create 3 different message histories + conversation_ids: list[str] = [] + for i in range(3): + result = await conversation.async_converse( + hass, + f"message {i+1}", + conversation_id=None, + context=Context(), + agent_id=mock_config_entry.entry_id, + ) + assert ( + result.response.response_type == intent.IntentResponseType.ACTION_DONE + ), result + assert isinstance(result.conversation_id, str) + conversation_ids.append(result.conversation_id) + + agent = await conversation._get_agent_manager(hass).async_get_agent( + mock_config_entry.entry_id + ) + assert isinstance(agent, ollama.OllamaAgent) + assert len(agent._history) == 3 + assert agent._history.keys() == set(conversation_ids) + + # Modify the timestamps of the first 2 histories so they will be pruned + # on the next cycle. + for conversation_id in conversation_ids[:2]: + # Move back 2 hours + agent._history[conversation_id].timestamp -= 2 * 60 * 60 + + # Next cycle + result = await conversation.async_converse( + hass, + "test message", + conversation_id=None, + context=Context(), + agent_id=mock_config_entry.entry_id, + ) + assert ( + result.response.response_type == intent.IntentResponseType.ACTION_DONE + ), result + + # Only the most recent histories should remain + assert len(agent._history) == 2 + assert conversation_ids[-1] in agent._history + assert result.conversation_id in agent._history + + +async def test_message_history_unlimited( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component +) -> None: + """Test that message history is not trimmed when max_history = 0.""" + conversation_id = "1234" + with ( + patch( + "ollama.AsyncClient.chat", + return_value={"message": {"role": "assistant", "content": "test response"}}, + ), + patch.object(mock_config_entry, "options", {ollama.CONF_MAX_HISTORY: 0}), + ): + for i in range(100): + result = await conversation.async_converse( + hass, + f"message {i+1}", + conversation_id=conversation_id, + context=Context(), + agent_id=mock_config_entry.entry_id, + ) + assert ( + result.response.response_type == intent.IntentResponseType.ACTION_DONE + ), result + + agent = await conversation._get_agent_manager(hass).async_get_agent( + mock_config_entry.entry_id + ) + assert isinstance(agent, ollama.OllamaAgent) + + assert len(agent._history) == 1 + assert conversation_id in agent._history + assert agent._history[conversation_id].num_user_messages == 100 + + +async def test_error_handling( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component +) -> None: + """Test error handling during converse.""" + with patch( + "ollama.AsyncClient.chat", + new_callable=AsyncMock, + side_effect=ResponseError("test error"), + ): + result = await conversation.async_converse( + hass, "hello", None, Context(), agent_id=mock_config_entry.entry_id + ) + + assert result.response.response_type == intent.IntentResponseType.ERROR, result + assert result.response.error_code == "unknown", result + + +async def test_template_error( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test that template error handling works.""" + hass.config_entries.async_update_entry( + mock_config_entry, + options={ + "prompt": "talk like a {% if True %}smarthome{% else %}pirate please.", + }, + ) + with patch( + "ollama.AsyncClient.list", + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + result = await conversation.async_converse( + hass, "hello", None, Context(), agent_id=mock_config_entry.entry_id + ) + + assert result.response.response_type == intent.IntentResponseType.ERROR, result + assert result.response.error_code == "unknown", result + + +async def test_conversation_agent( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component, +) -> None: + """Test OllamaAgent.""" + agent = await conversation._get_agent_manager(hass).async_get_agent( + mock_config_entry.entry_id + ) + assert agent.supported_languages == MATCH_ALL + + +@pytest.mark.parametrize( + ("side_effect", "error"), + [ + (ConnectError(message="Connect error"), "Connect error"), + (RuntimeError("Runtime error"), "Runtime error"), + ], +) +async def test_init_error( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, caplog, side_effect, error +) -> None: + """Test initialization errors.""" + with patch( + "ollama.AsyncClient.list", + side_effect=side_effect, + ): + assert await async_setup_component(hass, ollama.DOMAIN, {}) + await hass.async_block_till_done() + assert error in caplog.text From a6fabdc115230f84fe88e41086bd949aac250aca Mon Sep 17 00:00:00 2001 From: tronikos Date: Tue, 26 Mar 2024 14:20:16 -0700 Subject: [PATCH 1539/1691] Fix Opower accounts that report cost less regularly than usage (#114034) * Update coordinator.py * Update coordinator.py --- homeassistant/components/opower/coordinator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opower/coordinator.py b/homeassistant/components/opower/coordinator.py index 22ff9d951d2..d4cce99e1cc 100644 --- a/homeassistant/components/opower/coordinator.py +++ b/homeassistant/components/opower/coordinator.py @@ -109,7 +109,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]): ) last_stat = await get_instance(self.hass).async_add_executor_job( - get_last_statistics, self.hass, 1, consumption_statistic_id, True, set() + get_last_statistics, self.hass, 1, cost_statistic_id, True, set() ) if not last_stat: _LOGGER.debug("Updating statistic for the first time") @@ -119,7 +119,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]): last_stats_time = None else: cost_reads = await self._async_get_recent_cost_reads( - account, last_stat[consumption_statistic_id][0]["start"] + account, last_stat[cost_statistic_id][0]["start"] ) if not cost_reads: _LOGGER.debug("No recent usage/cost data. Skipping update") From 1697b116e4bad65ebf4e87a72040d8c09108ddac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 17:58:46 -1000 Subject: [PATCH 1540/1691] Combine tts cache init executor jobs (#114271) --- homeassistant/components/tts/__init__.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 82055d542ff..c88e0e83334 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -507,24 +507,21 @@ class SpeechManager: self.file_cache: dict[str, str] = {} self.mem_cache: dict[str, TTSCache] = {} - async def async_init_cache(self) -> None: - """Init config folder and load file cache.""" + def _init_cache(self) -> dict[str, str]: + """Init cache folder and fetch files.""" try: - self.cache_dir = await self.hass.async_add_executor_job( - _init_tts_cache_dir, self.hass, self.cache_dir - ) + self.cache_dir = _init_tts_cache_dir(self.hass, self.cache_dir) except OSError as err: raise HomeAssistantError(f"Can't init cache dir {err}") from err try: - cache_files = await self.hass.async_add_executor_job( - _get_cache_files, self.cache_dir - ) + return _get_cache_files(self.cache_dir) except OSError as err: raise HomeAssistantError(f"Can't read cache dir {err}") from err - if cache_files: - self.file_cache.update(cache_files) + async def async_init_cache(self) -> None: + """Init config folder and load file cache.""" + self.file_cache.update(await self.hass.async_add_executor_job(self._init_cache)) async def async_clear_cache(self) -> None: """Read file cache and delete files.""" From 480b2ec84a16085092fb651699a3e8620a05fbf0 Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:02:25 +0100 Subject: [PATCH 1541/1691] Add Motionblinds brand (#114235) --- homeassistant/brands/motionblinds.json | 5 +++++ homeassistant/generated/integrations.json | 25 ++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 homeassistant/brands/motionblinds.json diff --git a/homeassistant/brands/motionblinds.json b/homeassistant/brands/motionblinds.json new file mode 100644 index 00000000000..67013e75966 --- /dev/null +++ b/homeassistant/brands/motionblinds.json @@ -0,0 +1,5 @@ +{ + "domain": "motionblinds", + "name": "Motionblinds", + "integrations": ["motion_blinds", "motionblinds_ble"] +} diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 6cba84431f3..53b885ea853 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3728,17 +3728,22 @@ "config_flow": true, "iot_class": "local_push" }, - "motion_blinds": { + "motionblinds": { "name": "Motionblinds", - "integration_type": "hub", - "config_flow": true, - "iot_class": "local_push" - }, - "motionblinds_ble": { - "name": "Motionblinds BLE", - "integration_type": "device", - "config_flow": true, - "iot_class": "assumed_state" + "integrations": { + "motion_blinds": { + "integration_type": "hub", + "config_flow": true, + "iot_class": "local_push", + "name": "Motionblinds" + }, + "motionblinds_ble": { + "integration_type": "device", + "config_flow": true, + "iot_class": "assumed_state", + "name": "Motionblinds BLE" + } + } }, "motioneye": { "name": "motionEye", From 4d7da61e9982099e9a4ebcb39e20697fa496bd8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:39:56 +0100 Subject: [PATCH 1542/1691] Bump actions/setup-python from 5.0.0 to 5.1.0 (#114276) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/builder.yml | 6 +++--- .github/workflows/ci.yaml | 24 ++++++++++++------------ .github/workflows/translations.yml | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index ccbe3ba790b..62ea44a9df8 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: 0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -92,7 +92,7 @@ jobs: - name: Set up Python ${{ env.DEFAULT_PYTHON }} if: needs.init.outputs.channel == 'dev' - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -444,7 +444,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6ff76d50c7f..4a7e38f0110 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -225,7 +225,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -270,7 +270,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -310,7 +310,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -349,7 +349,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -444,7 +444,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -513,7 +513,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -545,7 +545,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -578,7 +578,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -622,7 +622,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true @@ -704,7 +704,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -851,7 +851,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -973,7 +973,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ matrix.python-version }} id: python - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ matrix.python-version }} check-latest: true diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml index a8cb65c3216..e61eef36f0b 100644 --- a/.github/workflows/translations.yml +++ b/.github/workflows/translations.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4.1.2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v5.0.0 + uses: actions/setup-python@v5.1.0 with: python-version: ${{ env.DEFAULT_PYTHON }} From fbb590ea1f4f8729814c79a131cf95fe3b538390 Mon Sep 17 00:00:00 2001 From: JeromeHXP Date: Wed, 27 Mar 2024 08:41:11 +0100 Subject: [PATCH 1543/1691] Bump ondilo_ico to 0.4.0 (#114022) --- homeassistant/components/ondilo_ico/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ondilo_ico/manifest.json b/homeassistant/components/ondilo_ico/manifest.json index 5a8515ddf2e..1d41eb04d86 100644 --- a/homeassistant/components/ondilo_ico/manifest.json +++ b/homeassistant/components/ondilo_ico/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/ondilo_ico", "iot_class": "cloud_polling", "loggers": ["ondilo"], - "requirements": ["ondilo==0.2.0"] + "requirements": ["ondilo==0.4.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 4fef44d80b2..ae10e886b49 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1443,7 +1443,7 @@ ollama-hass==0.1.7 omnilogic==0.4.5 # homeassistant.components.ondilo_ico -ondilo==0.2.0 +ondilo==0.4.0 # homeassistant.components.onkyo onkyo-eiscp==1.2.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 75ba113891e..0af04ed961e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1155,7 +1155,7 @@ ollama-hass==0.1.7 omnilogic==0.4.5 # homeassistant.components.ondilo_ico -ondilo==0.2.0 +ondilo==0.4.0 # homeassistant.components.onvif onvif-zeep-async==3.1.12 From 1d2c2d2055980b138982fd38d7dae84c6f3356fa Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:41:44 +0100 Subject: [PATCH 1544/1691] Move SignalTypes to util (#114236) --- homeassistant/components/mqtt/discovery.py | 2 +- homeassistant/const.py | 5 ++- homeassistant/helpers/discovery.py | 7 +--- homeassistant/helpers/dispatcher.py | 41 ++------------------- homeassistant/util/signal_type.py | 43 ++++++++++++++++++++++ tests/helpers/test_dispatcher.py | 3 +- 6 files changed, 55 insertions(+), 46 deletions(-) create mode 100644 homeassistant/util/signal_type.py diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index f50a4e4a3f7..13c56a9b48e 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -18,7 +18,6 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResultType import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( - SignalTypeFormat, async_dispatcher_connect, async_dispatcher_send, ) @@ -26,6 +25,7 @@ from homeassistant.helpers.service_info.mqtt import MqttServiceInfo from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.loader import async_get_mqtt from homeassistant.util.json import json_loads_object +from homeassistant.util.signal_type import SignalTypeFormat from .. import mqtt from .abbreviations import ABBREVIATIONS, DEVICE_ABBREVIATIONS, ORIGIN_ABBREVIATIONS diff --git a/homeassistant/const.py b/homeassistant/const.py index 6e657469b9a..ee15cfd72c3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -13,6 +13,7 @@ from .helpers.deprecation import ( check_if_deprecated_constant, dir_with_deprecated_constants, ) +from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 @@ -1609,7 +1610,9 @@ CAST_APP_ID_HOMEASSISTANT_LOVELACE: Final = "A078F6B0" # User used by Supervisor HASSIO_USER_NAME = "Supervisor" -SIGNAL_BOOTSTRAP_INTEGRATIONS = "bootstrap_integrations" +SIGNAL_BOOTSTRAP_INTEGRATIONS: SignalType[dict[str, float]] = SignalType( + "bootstrap_integrations" +) # hass.data key for logging information. diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py index 0bad52dff08..4b5a0117be7 100644 --- a/homeassistant/helpers/discovery.py +++ b/homeassistant/helpers/discovery.py @@ -15,11 +15,8 @@ from homeassistant import core, setup from homeassistant.const import Platform from homeassistant.loader import bind_hass -from .dispatcher import ( - SignalTypeFormat, - async_dispatcher_connect, - async_dispatcher_send, -) +from ..util.signal_type import SignalTypeFormat +from .dispatcher import async_dispatcher_connect, async_dispatcher_send from .typing import ConfigType, DiscoveryInfoType SIGNAL_PLATFORM_DISCOVERED: SignalTypeFormat[DiscoveryDict] = SignalTypeFormat( diff --git a/homeassistant/helpers/dispatcher.py b/homeassistant/helpers/dispatcher.py index 4633e81c78b..c1194c7da01 100644 --- a/homeassistant/helpers/dispatcher.py +++ b/homeassistant/helpers/dispatcher.py @@ -3,57 +3,24 @@ from __future__ import annotations from collections.abc import Callable, Coroutine -from dataclasses import dataclass from functools import partial import logging -from typing import Any, Generic, TypeVarTuple, overload +from typing import Any, TypeVarTuple, overload from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.loader import bind_hass from homeassistant.util.async_ import run_callback_threadsafe from homeassistant.util.logging import catch_log_exception +# Explicit reexport of 'SignalType' for backwards compatibility +from homeassistant.util.signal_type import SignalType as SignalType # noqa: PLC0414 + _Ts = TypeVarTuple("_Ts") _LOGGER = logging.getLogger(__name__) DATA_DISPATCHER = "dispatcher" -@dataclass(frozen=True) -class _SignalTypeBase(Generic[*_Ts]): - """Generic base class for SignalType.""" - - name: str - - def __hash__(self) -> int: - """Return hash of name.""" - - return hash(self.name) - - def __eq__(self, other: Any) -> bool: - """Check equality for dict keys to be compatible with str.""" - - if isinstance(other, str): - return self.name == other - if isinstance(other, SignalType): - return self.name == other.name - return False - - -@dataclass(frozen=True, eq=False) -class SignalType(_SignalTypeBase[*_Ts]): - """Generic string class for signal to improve typing.""" - - -@dataclass(frozen=True, eq=False) -class SignalTypeFormat(_SignalTypeBase[*_Ts]): - """Generic string class for signal. Requires call to 'format' before use.""" - - def format(self, *args: Any, **kwargs: Any) -> SignalType[*_Ts]: - """Format name and return new SignalType instance.""" - return SignalType(self.name.format(*args, **kwargs)) - - _DispatcherDataType = dict[ SignalType[*_Ts] | str, dict[ diff --git a/homeassistant/util/signal_type.py b/homeassistant/util/signal_type.py new file mode 100644 index 00000000000..be634ce6ba9 --- /dev/null +++ b/homeassistant/util/signal_type.py @@ -0,0 +1,43 @@ +"""Define SignalTypes for dispatcher.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Generic, TypeVarTuple + +_Ts = TypeVarTuple("_Ts") + + +@dataclass(frozen=True) +class _SignalTypeBase(Generic[*_Ts]): + """Generic base class for SignalType.""" + + name: str + + def __hash__(self) -> int: + """Return hash of name.""" + + return hash(self.name) + + def __eq__(self, other: Any) -> bool: + """Check equality for dict keys to be compatible with str.""" + + if isinstance(other, str): + return self.name == other + if isinstance(other, SignalType): + return self.name == other.name + return False + + +@dataclass(frozen=True, eq=False) +class SignalType(_SignalTypeBase[*_Ts]): + """Generic string class for signal to improve typing.""" + + +@dataclass(frozen=True, eq=False) +class SignalTypeFormat(_SignalTypeBase[*_Ts]): + """Generic string class for signal. Requires call to 'format' before use.""" + + def format(self, *args: Any, **kwargs: Any) -> SignalType[*_Ts]: + """Format name and return new SignalType instance.""" + return SignalType(self.name.format(*args, **kwargs)) diff --git a/tests/helpers/test_dispatcher.py b/tests/helpers/test_dispatcher.py index 1e1abe6e154..149231a9368 100644 --- a/tests/helpers/test_dispatcher.py +++ b/tests/helpers/test_dispatcher.py @@ -6,11 +6,10 @@ import pytest from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import ( - SignalType, - SignalTypeFormat, async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.util.signal_type import SignalType, SignalTypeFormat async def test_simple_function(hass: HomeAssistant) -> None: From d8acd9037024bee1eb26f39c952e1f963eeeb0ae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 22:25:45 -1000 Subject: [PATCH 1545/1691] Run recorder shutdown tasks eagerly (#113018) --- homeassistant/components/recorder/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 98f93f4e69a..21d42405b75 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -476,8 +476,12 @@ class Recorder(threading.Thread): def async_register(self) -> None: """Post connection initialize.""" bus = self.hass.bus - bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, self._async_close) - bus.async_listen_once(EVENT_HOMEASSISTANT_FINAL_WRITE, self._async_shutdown) + bus.async_listen_once( + EVENT_HOMEASSISTANT_CLOSE, self._async_close, run_immediately=True + ) + bus.async_listen_once( + EVENT_HOMEASSISTANT_FINAL_WRITE, self._async_shutdown, run_immediately=True + ) async_at_started(self.hass, self._async_hass_started) @callback From dd2d79b77eb2ced506c22a6b3bcd4fe819a079c5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 27 Mar 2024 09:39:05 +0100 Subject: [PATCH 1546/1691] Refactor/fix search component, including labels & floors support (#114206) Co-authored-by: TheJulianJES Co-authored-by: Robert Resch --- homeassistant/components/search/__init__.py | 649 +++++++---- tests/components/search/test_init.py | 1166 ++++++++++++------- 2 files changed, 1226 insertions(+), 589 deletions(-) diff --git a/homeassistant/components/search/__init__.py b/homeassistant/components/search/__init__.py index 1eafc137580..71b51210a25 100644 --- a/homeassistant/components/search/__init__.py +++ b/homeassistant/components/search/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations -from collections import defaultdict, deque +from collections import defaultdict +from collections.abc import Iterable +from enum import StrEnum import logging from typing import Any @@ -12,6 +14,7 @@ from homeassistant.components import automation, group, person, script, websocke from homeassistant.components.homeassistant import scene from homeassistant.core import HomeAssistant, callback, split_entity_id from homeassistant.helpers import ( + area_registry as ar, config_validation as cv, device_registry as dr, entity_registry as er, @@ -28,6 +31,25 @@ _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) +# enum of item types +class ItemType(StrEnum): + """Item types.""" + + AREA = "area" + AUTOMATION = "automation" + AUTOMATION_BLUEPRINT = "automation_blueprint" + CONFIG_ENTRY = "config_entry" + DEVICE = "device" + ENTITY = "entity" + FLOOR = "floor" + GROUP = "group" + LABEL = "label" + PERSON = "person" + SCENE = "scene" + SCRIPT = "script" + SCRIPT_BLUEPRINT = "script_blueprint" + + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Search component.""" websocket_api.async_register_command(hass, websocket_search_related) @@ -37,21 +59,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @websocket_api.websocket_command( { vol.Required("type"): "search/related", - vol.Required("item_type"): vol.In( - ( - "area", - "automation", - "automation_blueprint", - "config_entry", - "device", - "entity", - "group", - "person", - "scene", - "script", - "script_blueprint", - ) - ), + vol.Required("item_type"): vol.Coerce(ItemType), vol.Required("item_id"): str, } ) @@ -62,271 +70,520 @@ def websocket_search_related( msg: dict[str, Any], ) -> None: """Handle search.""" - searcher = Searcher( - hass, - dr.async_get(hass), - er.async_get(hass), - get_entity_sources(hass), - ) + searcher = Searcher(hass, get_entity_sources(hass)) connection.send_result( msg["id"], searcher.async_search(msg["item_type"], msg["item_id"]) ) class Searcher: - """Find related things. + """Find related things.""" - Few rules: - Scenes, scripts, automations and config entries will only be expanded if they are - the entry point. They won't be expanded if we process them. This is because they - turn the results into garbage. - """ - - # These types won't be further explored. Config entries + Output types. - DONT_RESOLVE = { - "area", - "automation", - "automation_blueprint", - "config_entry", - "group", - "scene", - "script", - "script_blueprint", - } - # These types exist as an entity and so need cleanup in results EXIST_AS_ENTITY = {"automation", "group", "person", "scene", "script"} def __init__( self, hass: HomeAssistant, - device_reg: dr.DeviceRegistry, - entity_reg: er.EntityRegistry, entity_sources: dict[str, EntityInfo], ) -> None: """Search results.""" self.hass = hass - self._device_reg = device_reg - self._entity_reg = entity_reg - self._sources = entity_sources - self.results: defaultdict[str, set[str]] = defaultdict(set) - self._to_resolve: deque[tuple[str, str]] = deque() + self._area_registry = ar.async_get(hass) + self._device_registry = dr.async_get(hass) + self._entity_registry = er.async_get(hass) + self._entity_sources = entity_sources + self.results: defaultdict[ItemType, set[str]] = defaultdict(set) @callback - def async_search(self, item_type: str, item_id: str) -> dict[str, set[str]]: + def async_search(self, item_type: ItemType, item_id: str) -> dict[str, set[str]]: """Find results.""" _LOGGER.debug("Searching for %s/%s", item_type, item_id) - self.results[item_type].add(item_id) - self._to_resolve.append((item_type, item_id)) + getattr(self, f"_async_search_{item_type}")(item_id) - while self._to_resolve: - search_type, search_id = self._to_resolve.popleft() - getattr(self, f"_resolve_{search_type}")(search_id) - - # Clean up entity_id items, from the general "entity" type result, - # that are also found in the specific entity domain type. - for result_type in self.EXIST_AS_ENTITY: - self.results["entity"] -= self.results[result_type] - - # Remove entry into graph from search results. - to_remove_item_type = item_type - if item_type == "entity": - domain = split_entity_id(item_id)[0] - - if domain in self.EXIST_AS_ENTITY: - to_remove_item_type = domain - - self.results[to_remove_item_type].remove(item_id) + # Remove the original requested item from the results (if present) + if item_type in self.results and item_id in self.results[item_type]: + self.results[item_type].remove(item_id) # Filter out empty sets. return {key: val for key, val in self.results.items() if val} @callback - def _add_or_resolve(self, item_type: str, item_id: str) -> None: - """Add an item to explore.""" - if item_id in self.results[item_type]: + def _add(self, item_type: ItemType, item_id: str | Iterable[str] | None) -> None: + """Add an item (or items) to the results.""" + if item_id is None: return - self.results[item_type].add(item_id) - - if item_type not in self.DONT_RESOLVE: - self._to_resolve.append((item_type, item_id)) + if isinstance(item_id, str): + self.results[item_type].add(item_id) + else: + self.results[item_type].update(item_id) @callback - def _resolve_area(self, area_id: str) -> None: - """Resolve an area.""" - for device in dr.async_entries_for_area(self._device_reg, area_id): - self._add_or_resolve("device", device.id) + def _async_search_area(self, area_id: str, *, entry_point: bool = True) -> None: + """Find results for an area.""" + if not (area_entry := self._async_resolve_up_area(area_id)): + return - for entity_entry in er.async_entries_for_area(self._entity_reg, area_id): - self._add_or_resolve("entity", entity_entry.entity_id) + if entry_point: + # Add labels of this area + self._add(ItemType.LABEL, area_entry.labels) - for entity_id in script.scripts_with_area(self.hass, area_id): - self._add_or_resolve("entity", entity_id) + # Automations referencing this area + self._add( + ItemType.AUTOMATION, automation.automations_with_area(self.hass, area_id) + ) - for entity_id in automation.automations_with_area(self.hass, area_id): - self._add_or_resolve("entity", entity_id) + # Scripts referencing this area + self._add(ItemType.SCRIPT, script.scripts_with_area(self.hass, area_id)) + + # Devices in this area + for device in dr.async_entries_for_area(self._device_registry, area_id): + self._add(ItemType.DEVICE, device.id) + + # Config entries for devices in this area + if device_entry := self._device_registry.async_get(device.id): + self._add(ItemType.CONFIG_ENTRY, device_entry.config_entries) + + # Automations referencing this device + self._add( + ItemType.AUTOMATION, + automation.automations_with_device(self.hass, device.id), + ) + + # Scripts referencing this device + self._add(ItemType.SCRIPT, script.scripts_with_device(self.hass, device.id)) + + # Entities of this device + for entity_entry in er.async_entries_for_device( + self._entity_registry, device.id + ): + # Skip the entity if it's in a different area + if entity_entry.area_id is not None: + continue + self._add(ItemType.ENTITY, entity_entry.entity_id) + + # Entities in this area + for entity_entry in er.async_entries_for_area(self._entity_registry, area_id): + self._add(ItemType.ENTITY, entity_entry.entity_id) + + # If this entity also exists as a resource, we add it. + if entity_entry.domain in self.EXIST_AS_ENTITY: + self._add(ItemType(entity_entry.domain), entity_entry.entity_id) + + # Automations referencing this entity + self._add( + ItemType.AUTOMATION, + automation.automations_with_entity(self.hass, entity_entry.entity_id), + ) + + # Scripts referencing this entity + self._add( + ItemType.SCRIPT, + script.scripts_with_entity(self.hass, entity_entry.entity_id), + ) + + # Groups that have this entity as a member + self._add( + ItemType.GROUP, + group.groups_with_entity(self.hass, entity_entry.entity_id), + ) + + # Persons that use this entity + self._add( + ItemType.PERSON, + person.persons_with_entity(self.hass, entity_entry.entity_id), + ) + + # Scenes that reference this entity + self._add( + ItemType.SCENE, + scene.scenes_with_entity(self.hass, entity_entry.entity_id), + ) + + # Config entries for entities in this area + self._add(ItemType.CONFIG_ENTRY, entity_entry.config_entry_id) @callback - def _resolve_automation(self, automation_entity_id: str) -> None: - """Resolve an automation. + def _async_search_automation(self, automation_entity_id: str) -> None: + """Find results for an automation.""" + # Up resolve the automation entity itself + if entity_entry := self._async_resolve_up_entity(automation_entity_id): + # Add labels of this automation entity + self._add(ItemType.LABEL, entity_entry.labels) - Will only be called if automation is an entry point. - """ - for entity in automation.entities_in_automation( - self.hass, automation_entity_id - ): - self._add_or_resolve("entity", entity) + # Find the blueprint used in this automation + self._add( + ItemType.AUTOMATION_BLUEPRINT, + automation.blueprint_in_automation(self.hass, automation_entity_id), + ) - for device in automation.devices_in_automation(self.hass, automation_entity_id): - self._add_or_resolve("device", device) + # Floors referenced in this automation + self._add( + ItemType.FLOOR, + automation.floors_in_automation(self.hass, automation_entity_id), + ) + # Areas referenced in this automation for area in automation.areas_in_automation(self.hass, automation_entity_id): - self._add_or_resolve("area", area) + self._add(ItemType.AREA, area) + self._async_resolve_up_area(area) - if blueprint := automation.blueprint_in_automation( + # Devices referenced in this automation + for device in automation.devices_in_automation(self.hass, automation_entity_id): + self._add(ItemType.DEVICE, device) + self._async_resolve_up_device(device) + + # Entities referenced in this automation + for entity_id in automation.entities_in_automation( self.hass, automation_entity_id ): - self._add_or_resolve("automation_blueprint", blueprint) + self._add(ItemType.ENTITY, entity_id) + self._async_resolve_up_entity(entity_id) + + # If this entity also exists as a resource, we add it. + domain = split_entity_id(entity_id)[0] + if domain in self.EXIST_AS_ENTITY: + self._add(ItemType(domain), entity_id) + + # For an automation, we want to unwrap the groups, to ensure we + # relate this automation to all those members as well. + if domain == "group": + for group_entity_id in group.get_entity_ids(self.hass, entity_id): + self._add(ItemType.ENTITY, group_entity_id) + self._async_resolve_up_entity(group_entity_id) + + # For an automation, we want to unwrap the scenes, to ensure we + # relate this automation to all referenced entities as well. + if domain == "scene": + for scene_entity_id in scene.entities_in_scene(self.hass, entity_id): + self._add(ItemType.ENTITY, scene_entity_id) + self._async_resolve_up_entity(scene_entity_id) + + # Fully search the script if it is part of an automation. + # This makes the automation return all results of the embedded script. + if domain == "script": + self._async_search_script(entity_id, entry_point=False) @callback - def _resolve_automation_blueprint(self, blueprint_path: str) -> None: - """Resolve an automation blueprint. - - Will only be called if blueprint is an entry point. - """ - for entity_id in automation.automations_with_blueprint( - self.hass, blueprint_path - ): - self._add_or_resolve("automation", entity_id) + def _async_search_automation_blueprint(self, blueprint_path: str) -> None: + """Find results for an automation blueprint.""" + self._add( + ItemType.AUTOMATION, + automation.automations_with_blueprint(self.hass, blueprint_path), + ) @callback - def _resolve_config_entry(self, config_entry_id: str) -> None: - """Resolve a config entry. - - Will only be called if config entry is an entry point. - """ + def _async_search_config_entry(self, config_entry_id: str) -> None: + """Find results for a config entry.""" for device_entry in dr.async_entries_for_config_entry( - self._device_reg, config_entry_id + self._device_registry, config_entry_id ): - self._add_or_resolve("device", device_entry.id) + self._add(ItemType.DEVICE, device_entry.id) + self._async_search_device(device_entry.id, entry_point=False) for entity_entry in er.async_entries_for_config_entry( - self._entity_reg, config_entry_id + self._entity_registry, config_entry_id ): - self._add_or_resolve("entity", entity_entry.entity_id) + self._add(ItemType.ENTITY, entity_entry.entity_id) + self._async_search_entity(entity_entry.entity_id, entry_point=False) @callback - def _resolve_device(self, device_id: str) -> None: - """Resolve a device.""" - device_entry = self._device_reg.async_get(device_id) - # Unlikely entry doesn't exist, but let's guard for bad data. - if device_entry is not None: - if device_entry.area_id: - self._add_or_resolve("area", device_entry.area_id) + def _async_search_device(self, device_id: str, *, entry_point: bool = True) -> None: + """Find results for a device.""" + if not (device_entry := self._async_resolve_up_device(device_id)): + return - for config_entry_id in device_entry.config_entries: - self._add_or_resolve("config_entry", config_entry_id) + if entry_point: + # Add labels of this device + self._add(ItemType.LABEL, device_entry.labels) - # We do not resolve device_entry.via_device_id because that - # device is not related data-wise inside HA. + # Automations referencing this device + self._add( + ItemType.AUTOMATION, + automation.automations_with_device(self.hass, device_id), + ) - for entity_entry in er.async_entries_for_device(self._entity_reg, device_id): - self._add_or_resolve("entity", entity_entry.entity_id) + # Scripts referencing this device + self._add(ItemType.SCRIPT, script.scripts_with_device(self.hass, device_id)) - for entity_id in script.scripts_with_device(self.hass, device_id): - self._add_or_resolve("entity", entity_id) - - for entity_id in automation.automations_with_device(self.hass, device_id): - self._add_or_resolve("entity", entity_id) + # Entities of this device + for entity_entry in er.async_entries_for_device( + self._entity_registry, device_id + ): + self._add(ItemType.ENTITY, entity_entry.entity_id) + # Add all entity information as well + self._async_search_entity(entity_entry.entity_id, entry_point=False) @callback - def _resolve_entity(self, entity_id: str) -> None: - """Resolve an entity.""" - # Extra: Find automations and scripts that reference this entity. + def _async_search_entity(self, entity_id: str, *, entry_point: bool = True) -> None: + """Find results for an entity.""" + # Resolve up the entity itself + entity_entry = self._async_resolve_up_entity(entity_id) - for entity in scene.scenes_with_entity(self.hass, entity_id): - self._add_or_resolve("entity", entity) + if entity_entry and entry_point: + # Add labels of this entity + self._add(ItemType.LABEL, entity_entry.labels) - for entity in group.groups_with_entity(self.hass, entity_id): - self._add_or_resolve("entity", entity) + # Automations referencing this entity + self._add( + ItemType.AUTOMATION, + automation.automations_with_entity(self.hass, entity_id), + ) - for entity in automation.automations_with_entity(self.hass, entity_id): - self._add_or_resolve("entity", entity) + # Scripts referencing this entity + self._add(ItemType.SCRIPT, script.scripts_with_entity(self.hass, entity_id)) - for entity in script.scripts_with_entity(self.hass, entity_id): - self._add_or_resolve("entity", entity) + # Groups that have this entity as a member + self._add(ItemType.GROUP, group.groups_with_entity(self.hass, entity_id)) - for entity in person.persons_with_entity(self.hass, entity_id): - self._add_or_resolve("entity", entity) + # Persons referencing this entity + self._add(ItemType.PERSON, person.persons_with_entity(self.hass, entity_id)) - # Find devices - entity_entry = self._entity_reg.async_get(entity_id) - if entity_entry is not None: - if entity_entry.device_id: - self._add_or_resolve("device", entity_entry.device_id) - - if entity_entry.config_entry_id is not None: - self._add_or_resolve("config_entry", entity_entry.config_entry_id) - else: - source = self._sources.get(entity_id) - if source is not None and "config_entry" in source: - self._add_or_resolve("config_entry", source["config_entry"]) - - domain = split_entity_id(entity_id)[0] - - if domain in self.EXIST_AS_ENTITY: - self._add_or_resolve(domain, entity_id) + # Scenes referencing this entity + self._add(ItemType.SCENE, scene.scenes_with_entity(self.hass, entity_id)) @callback - def _resolve_group(self, group_entity_id: str) -> None: - """Resolve a group. + def _async_search_floor(self, floor_id: str) -> None: + """Find results for a floor.""" + # Automations referencing this floor + self._add( + ItemType.AUTOMATION, + automation.automations_with_floor(self.hass, floor_id), + ) - Will only be called if group is an entry point. + # Scripts referencing this floor + self._add(ItemType.SCRIPT, script.scripts_with_floor(self.hass, floor_id)) + + for area_entry in ar.async_entries_for_floor(self._area_registry, floor_id): + self._add(ItemType.AREA, area_entry.id) + self._async_search_area(area_entry.id, entry_point=False) + + @callback + def _async_search_group(self, group_entity_id: str) -> None: + """Find results for a group. + + Note: We currently only support the classic groups, thus + we don't look up the area/floor for a group entity. """ + # Automations referencing this group + self._add( + ItemType.AUTOMATION, + automation.automations_with_entity(self.hass, group_entity_id), + ) + + # Scripts referencing this group + self._add( + ItemType.SCRIPT, script.scripts_with_entity(self.hass, group_entity_id) + ) + + # Scenes that reference this group + self._add(ItemType.SCENE, scene.scenes_with_entity(self.hass, group_entity_id)) + + # Entities in this group for entity_id in group.get_entity_ids(self.hass, group_entity_id): - self._add_or_resolve("entity", entity_id) + self._add(ItemType.ENTITY, entity_id) + self._async_resolve_up_entity(entity_id) @callback - def _resolve_person(self, person_entity_id: str) -> None: - """Resolve a person. + def _async_search_label(self, label_id: str) -> None: + """Find results for a label.""" - Will only be called if person is an entry point. - """ - for entity in person.entities_in_person(self.hass, person_entity_id): - self._add_or_resolve("entity", entity) + # Areas with this label + for area_entry in ar.async_entries_for_label(self._area_registry, label_id): + self._add(ItemType.AREA, area_entry.id) + + # Devices with this label + for device in dr.async_entries_for_label(self._device_registry, label_id): + self._add(ItemType.DEVICE, device.id) + + # Entities with this label + for entity_entry in er.async_entries_for_label(self._entity_registry, label_id): + self._add(ItemType.ENTITY, entity_entry.entity_id) + + # If this entity also exists as a resource, we add it. + domain = split_entity_id(entity_entry.entity_id)[0] + if domain in self.EXIST_AS_ENTITY: + self._add(ItemType(domain), entity_entry.entity_id) + + # Automations referencing this label + self._add( + ItemType.AUTOMATION, + automation.automations_with_label(self.hass, label_id), + ) + + # Scripts referencing this label + self._add(ItemType.SCRIPT, script.scripts_with_label(self.hass, label_id)) @callback - def _resolve_scene(self, scene_entity_id: str) -> None: - """Resolve a scene. + def _async_search_person(self, person_entity_id: str) -> None: + """Find results for a person.""" + # Up resolve the scene entity itself + if entity_entry := self._async_resolve_up_entity(person_entity_id): + # Add labels of this person entity + self._add(ItemType.LABEL, entity_entry.labels) - Will only be called if scene is an entry point. - """ + # Automations referencing this person + self._add( + ItemType.AUTOMATION, + automation.automations_with_entity(self.hass, person_entity_id), + ) + + # Scripts referencing this person + self._add( + ItemType.SCRIPT, script.scripts_with_entity(self.hass, person_entity_id) + ) + + # Add all member entities of this person + self._add( + ItemType.ENTITY, person.entities_in_person(self.hass, person_entity_id) + ) + + @callback + def _async_search_scene(self, scene_entity_id: str) -> None: + """Find results for a scene.""" + # Up resolve the scene entity itself + if entity_entry := self._async_resolve_up_entity(scene_entity_id): + # Add labels of this scene entity + self._add(ItemType.LABEL, entity_entry.labels) + + # Automations referencing this scene + self._add( + ItemType.AUTOMATION, + automation.automations_with_entity(self.hass, scene_entity_id), + ) + + # Scripts referencing this scene + self._add( + ItemType.SCRIPT, script.scripts_with_entity(self.hass, scene_entity_id) + ) + + # Add all entities in this scene for entity in scene.entities_in_scene(self.hass, scene_entity_id): - self._add_or_resolve("entity", entity) + self._add(ItemType.ENTITY, entity) + self._async_resolve_up_entity(entity) @callback - def _resolve_script(self, script_entity_id: str) -> None: - """Resolve a script. + def _async_search_script( + self, script_entity_id: str, *, entry_point: bool = True + ) -> None: + """Find results for a script.""" + # Up resolve the script entity itself + entity_entry = self._async_resolve_up_entity(script_entity_id) - Will only be called if script is an entry point. - """ - for entity in script.entities_in_script(self.hass, script_entity_id): - self._add_or_resolve("entity", entity) + if entity_entry and entry_point: + # Add labels of this script entity + self._add(ItemType.LABEL, entity_entry.labels) - for device in script.devices_in_script(self.hass, script_entity_id): - self._add_or_resolve("device", device) + # Find the blueprint used in this script + self._add( + ItemType.SCRIPT_BLUEPRINT, + script.blueprint_in_script(self.hass, script_entity_id), + ) + # Floors referenced in this script + self._add(ItemType.FLOOR, script.floors_in_script(self.hass, script_entity_id)) + + # Areas referenced in this script for area in script.areas_in_script(self.hass, script_entity_id): - self._add_or_resolve("area", area) + self._add(ItemType.AREA, area) + self._async_resolve_up_area(area) - if blueprint := script.blueprint_in_script(self.hass, script_entity_id): - self._add_or_resolve("script_blueprint", blueprint) + # Devices referenced in this script + for device in script.devices_in_script(self.hass, script_entity_id): + self._add(ItemType.DEVICE, device) + self._async_resolve_up_device(device) + + # Entities referenced in this script + for entity_id in script.entities_in_script(self.hass, script_entity_id): + self._add(ItemType.ENTITY, entity_id) + self._async_resolve_up_entity(entity_id) + + # If this entity also exists as a resource, we add it. + domain = split_entity_id(entity_id)[0] + if domain in self.EXIST_AS_ENTITY: + self._add(ItemType(domain), entity_id) + + # For an script, we want to unwrap the groups, to ensure we + # relate this script to all those members as well. + if domain == "group": + for group_entity_id in group.get_entity_ids(self.hass, entity_id): + self._add(ItemType.ENTITY, group_entity_id) + self._async_resolve_up_entity(group_entity_id) + + # For an script, we want to unwrap the scenes, to ensure we + # relate this script to all referenced entities as well. + if domain == "scene": + for scene_entity_id in scene.entities_in_scene(self.hass, entity_id): + self._add(ItemType.ENTITY, scene_entity_id) + self._async_resolve_up_entity(scene_entity_id) + + # Fully search the script if it is nested. + # This makes the script return all results of the embedded script. + if domain == "script": + self._async_search_script(entity_id, entry_point=False) @callback - def _resolve_script_blueprint(self, blueprint_path: str) -> None: - """Resolve a script blueprint. + def _async_search_script_blueprint(self, blueprint_path: str) -> None: + """Find results for a script blueprint.""" + self._add( + ItemType.SCRIPT, script.scripts_with_blueprint(self.hass, blueprint_path) + ) - Will only be called if blueprint is an entry point. + @callback + def _async_resolve_up_device(self, device_id: str) -> dr.DeviceEntry | None: + """Resolve up from a device. + + Above a device is an area or floor. + Above a device is also the config entry. """ - for entity_id in script.scripts_with_blueprint(self.hass, blueprint_path): - self._add_or_resolve("script", entity_id) + if device_entry := self._device_registry.async_get(device_id): + if device_entry.area_id: + self._add(ItemType.AREA, device_entry.area_id) + self._async_resolve_up_area(device_entry.area_id) + + self._add(ItemType.CONFIG_ENTRY, device_entry.config_entries) + + return device_entry + + @callback + def _async_resolve_up_entity(self, entity_id: str) -> er.RegistryEntry | None: + """Resolve up from an entity. + + Above an entity is a device, area or floor. + Above an entity is also the config entry. + """ + if entity_entry := self._entity_registry.async_get(entity_id): + # Entity has an overridden area + if entity_entry.area_id: + self._add(ItemType.AREA, entity_entry.area_id) + self._async_resolve_up_area(entity_entry.area_id) + + # Inherit area from device + elif entity_entry.device_id and ( + device_entry := self._device_registry.async_get(entity_entry.device_id) + ): + if device_entry.area_id: + self._add(ItemType.AREA, device_entry.area_id) + self._async_resolve_up_area(device_entry.area_id) + + # Add device that provided this entity + self._add(ItemType.DEVICE, entity_entry.device_id) + + # Add config entry that provided this entity + self._add(ItemType.CONFIG_ENTRY, entity_entry.config_entry_id) + elif source := self._entity_sources.get(entity_id): + # Add config entry that provided this entity + self._add(ItemType.CONFIG_ENTRY, source.get("config_entry")) + + return entity_entry + + @callback + def _async_resolve_up_area(self, area_id: str) -> ar.AreaEntry | None: + """Resolve up from an area. + + Above an area can be a floor. + """ + if area_entry := self._area_registry.async_get_area(area_id): + self._add(ItemType.FLOOR, area_entry.floor_id) + + return area_entry diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index 57b72de2bcd..ee7b60dc9ac 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -1,14 +1,18 @@ """Tests for Search integration.""" import pytest +from pytest_unordered import unordered -from homeassistant.components import search +from homeassistant.components.search import ItemType, Searcher from homeassistant.core import HomeAssistant from homeassistant.helpers import ( area_registry as ar, device_registry as dr, entity_registry as er, + floor_registry as fr, + label_registry as lr, ) +from homeassistant.helpers.entity import EntityInfo from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -20,38 +24,79 @@ def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None: """Stub copying the blueprints to the config folder.""" -MOCK_ENTITY_SOURCES = { - "light.platform_config_source": { - "domain": "wled", - }, - "light.config_entry_source": { - "config_entry": "config_entry_id", - "domain": "wled", - }, -} - - async def test_search( hass: HomeAssistant, area_registry: ar.AreaRegistry, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + floor_registry: fr.FloorRegistry, + label_registry: lr.LabelRegistry, + hass_ws_client: WebSocketGenerator, ) -> None: - """Test that search works.""" - living_room_area = area_registry.async_create("Living Room") + """Test search.""" + assert await async_setup_component(hass, "search", {}) - # Light strip with 2 lights. + # Labels + label_energy = label_registry.async_create("Energy") + label_christmas = label_registry.async_create("Christmas") + label_other = label_registry.async_create("Other") + + # Floors + first_floor = floor_registry.async_create("First Floor") + second_floor = floor_registry.async_create("Second Floor") + + # Areas + bedroom_area = area_registry.async_create( + "Bedroom", floor_id=second_floor.floor_id, labels={label_other.label_id} + ) + kitchen_area = area_registry.async_create("Kitchen", floor_id=first_floor.floor_id) + living_room_area = area_registry.async_create( + "Living Room", floor_id=first_floor.floor_id + ) + + # Config entries + hue_config_entry = MockConfigEntry(domain="hue") + hue_config_entry.add_to_hass(hass) wled_config_entry = MockConfigEntry(domain="wled") wled_config_entry.add_to_hass(hass) + # Devices + hue_device = device_registry.async_get_or_create( + config_entry_id=hue_config_entry.entry_id, + name="Light Strip", + identifiers={("hue", "hue-1")}, + ) + device_registry.async_update_device(hue_device.id, area_id=kitchen_area.id) + wled_device = device_registry.async_get_or_create( config_entry_id=wled_config_entry.entry_id, name="Light Strip", identifiers=({"wled", "wled-1"}), ) + device_registry.async_update_device( + wled_device.id, area_id=living_room_area.id, labels={label_christmas.label_id} + ) - device_registry.async_update_device(wled_device.id, area_id=living_room_area.id) - + # Entities + hue_segment_1_entity = entity_registry.async_get_or_create( + "light", + "hue", + "hue-1-seg-1", + suggested_object_id="hue segment 1", + config_entry=hue_config_entry, + device_id=hue_device.id, + ) + entity_registry.async_update_entity( + hue_segment_1_entity.entity_id, labels={label_energy.label_id} + ) + hue_segment_2_entity = entity_registry.async_get_or_create( + "light", + "hue", + "hue-1-seg-2", + suggested_object_id="hue segment 2", + config_entry=hue_config_entry, + device_id=hue_device.id, + ) wled_segment_1_entity = entity_registry.async_get_or_create( "light", "wled", @@ -68,48 +113,59 @@ async def test_search( config_entry=wled_config_entry, device_id=wled_device.id, ) + entity_registry.async_update_entity( + wled_segment_2_entity.entity_id, area_id=bedroom_area.id + ) + scene_wled_hue_entity = entity_registry.async_get_or_create( + "scene", + "homeassistant", + "wled_hue", + suggested_object_id="scene_wled_hue", + ) + entity_registry.async_update_entity( + scene_wled_hue_entity.entity_id, + area_id=bedroom_area.id, + labels={label_other.label_id}, + ) + + # Persons can technically be assigned to areas + person_paulus_entity = entity_registry.async_get_or_create( + "person", + "person", + "abcd", + suggested_object_id="paulus", + ) + entity_registry.async_update_entity( + person_paulus_entity.entity_id, + area_id=bedroom_area.id, + labels={label_other.label_id}, + ) + + script_scene_entity = entity_registry.async_get_or_create( + "script", + "script", + "scene", + suggested_object_id="scene", + ) + entity_registry.async_update_entity( + script_scene_entity.entity_id, + area_id=bedroom_area.id, + labels={label_other.label_id}, + ) + + # Entity sources entity_sources = { - "light.wled_platform_config_source": { - "domain": "wled", - }, - "light.wled_config_entry_source": { - "config_entry": wled_config_entry.entry_id, - "domain": "wled", - }, + "light.wled_platform_config_source": EntityInfo( + domain="wled", + ), + "light.wled_config_entry_source": EntityInfo( + config_entry=wled_config_entry.entry_id, + domain="wled", + ), } - # Non related info. - kitchen_area = area_registry.async_create("Kitchen") - - hue_config_entry = MockConfigEntry(domain="hue") - hue_config_entry.add_to_hass(hass) - - hue_device = device_registry.async_get_or_create( - config_entry_id=hue_config_entry.entry_id, - name="Light Strip", - identifiers=({"hue", "hue-1"}), - ) - - device_registry.async_update_device(hue_device.id, area_id=kitchen_area.id) - - hue_segment_1_entity = entity_registry.async_get_or_create( - "light", - "hue", - "hue-1-seg-1", - suggested_object_id="hue segment 1", - config_entry=hue_config_entry, - device_id=hue_device.id, - ) - hue_segment_2_entity = entity_registry.async_get_or_create( - "light", - "hue", - "hue-1-seg-2", - suggested_object_id="hue segment 2", - config_entry=hue_config_entry, - device_id=hue_device.id, - ) - + # Groups await async_setup_component( hass, "group", @@ -142,6 +198,22 @@ async def test_search( }, ) + # Persons + assert await async_setup_component( + hass, + "person", + { + "person": [ + { + "id": "abcd", + "name": "Paulus", + "device_trackers": ["device_tracker.paulus_iphone"], + } + ] + }, + ) + + # Scenes await async_setup_component( hass, "scene", @@ -156,6 +228,7 @@ async def test_search( "entities": {hue_segment_1_entity.entity_id: "on"}, }, { + "id": "wled_hue", "name": "scene_wled_hue", "entities": { wled_segment_1_entity.entity_id: "on", @@ -168,11 +241,144 @@ async def test_search( }, ) - await async_setup_component( + # Automations + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + { + "id": "unique_id", + "alias": "blueprint_automation_1", + "trigger": {"platform": "template", "value_template": "true"}, + "use_blueprint": { + "path": "test_event_service.yaml", + "input": { + "trigger_event": "blueprint_event_1", + "service_to_call": "test.automation_1", + "a_number": 5, + }, + }, + }, + { + "alias": "blueprint_automation_2", + "trigger": {"platform": "template", "value_template": "true"}, + "use_blueprint": { + "path": "test_event_service.yaml", + "input": { + "trigger_event": "blueprint_event_2", + "service_to_call": "test.automation_2", + "a_number": 5, + }, + }, + }, + { + "alias": "wled_entity", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "service": "test.script", + "data": {"entity_id": wled_segment_1_entity.entity_id}, + }, + ], + }, + { + "alias": "wled_device", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "domain": "light", + "device_id": wled_device.id, + "entity_id": wled_segment_1_entity.entity_id, + "type": "turn_on", + }, + ], + }, + { + "alias": "floor", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "service": "test.script", + "target": {"floor_id": first_floor.floor_id}, + }, + ], + }, + { + "alias": "area", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "service": "test.script", + "target": {"area_id": kitchen_area.id}, + }, + ], + }, + { + "alias": "group", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "service": "homeassistant.turn_on", + "target": {"entity_id": "group.wled_hue"}, + }, + ], + }, + { + "alias": "scene", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "scene": scene_wled_hue_entity.entity_id, + }, + ], + }, + { + "alias": "script", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "service": "script.turn_on", + "data": {"entity_id": script_scene_entity.entity_id}, + }, + ], + }, + { + "alias": "label", + "trigger": {"platform": "template", "value_template": "true"}, + "action": [ + { + "service": "script.turn_on", + "target": {"label_id": label_christmas.label_id}, + }, + ], + }, + ] + }, + ) + + # Scripts + assert await async_setup_component( hass, "script", { "script": { + "blueprint_script_1": { + "use_blueprint": { + "path": "test_service.yaml", + "input": { + "service_to_call": "test.automation", + }, + } + }, + "blueprint_script_2": { + "use_blueprint": { + "path": "test_service.yaml", + "input": { + "service_to_call": "test.automation", + }, + } + }, "wled": { "sequence": [ { @@ -205,375 +411,532 @@ async def test_search( }, ] }, + "device": { + "sequence": [ + { + "service": "test.script", + "target": {"device_id": hue_device.id}, + }, + ], + }, + "floor": { + "sequence": [ + { + "service": "test.script", + "target": {"floor_id": first_floor.floor_id}, + }, + ], + }, + "area": { + "sequence": [ + { + "service": "test.script", + "target": {"area_id": kitchen_area.id}, + }, + ], + }, + "group": { + "sequence": [ + { + "service": "test.script", + "target": {"entity_id": "group.wled_hue"}, + }, + ], + }, + "scene": { + "sequence": [ + { + "scene": scene_wled_hue_entity.entity_id, + }, + ], + }, + "label": { + "sequence": [ + { + "service": "test.script", + "target": {"label_id": label_other.label_id}, + }, + ], + }, + "nested": { + "sequence": [ + { + "service": "script.turn_on", + "data": {"entity_id": script_scene_entity.entity_id}, + }, + ], + }, } }, ) - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - { - "alias": "wled_entity", - "trigger": {"platform": "template", "value_template": "true"}, - "action": [ - { - "service": "test.script", - "data": {"entity_id": wled_segment_1_entity.entity_id}, - }, - ], - }, - { - "alias": "wled_device", - "trigger": {"platform": "template", "value_template": "true"}, - "action": [ - { - "domain": "light", - "device_id": wled_device.id, - "entity_id": wled_segment_1_entity.entity_id, - "type": "turn_on", - }, - ], - }, - ] + def search(item_type: ItemType, item_id: str) -> dict[str, set[str]]: + """Search.""" + searcher = Searcher(hass, entity_sources) + return searcher.async_search(item_type, item_id) + + # + # Tests + # + assert not search(ItemType.AREA, "unknown") + assert search(ItemType.AREA, bedroom_area.id) == { + ItemType.AUTOMATION: {"automation.scene", "automation.script"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.ENTITY: { + wled_segment_2_entity.entity_id, + scene_wled_hue_entity.entity_id, + script_scene_entity.entity_id, + person_paulus_entity.entity_id, }, - ) - - # Ensure automations set up correctly. - assert hass.states.get("automation.wled_entity") is not None - assert hass.states.get("automation.wled_device") is not None - - # Explore the graph from every node and make sure we find the same results - expected = { - "config_entry": {wled_config_entry.entry_id}, - "area": {living_room_area.id}, - "device": {wled_device.id}, - "entity": {wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id}, - "scene": {"scene.scene_wled_seg_1", "scene.scene_wled_hue"}, - "group": {"group.wled", "group.wled_hue"}, - "script": {"script.wled"}, - "automation": {"automation.wled_entity", "automation.wled_device"}, + ItemType.FLOOR: {second_floor.floor_id}, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.LABEL: {label_other.label_id}, + ItemType.PERSON: {person_paulus_entity.entity_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {script_scene_entity.entity_id, "script.nested"}, + } + assert search(ItemType.AREA, living_room_area.id) == { + ItemType.AUTOMATION: {"automation.wled_device"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.ENTITY: {wled_segment_1_entity.entity_id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.AREA, kitchen_area.id) == { + ItemType.AUTOMATION: {"automation.area"}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.ENTITY: { + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id}, + ItemType.SCRIPT: {"script.area", "script.device"}, } - for search_type, search_id in ( - ("config_entry", wled_config_entry.entry_id), - ("area", living_room_area.id), - ("device", wled_device.id), - ("entity", wled_segment_1_entity.entity_id), - ("entity", wled_segment_2_entity.entity_id), - ("scene", "scene.scene_wled_seg_1"), - ("group", "group.wled"), - ("script", "script.wled"), - ("automation", "automation.wled_entity"), - ("automation", "automation.wled_device"), - ): - searcher = search.Searcher( - hass, device_registry, entity_registry, entity_sources - ) - results = searcher.async_search(search_type, search_id) - # Add the item we searched for, it's omitted from results - results.setdefault(search_type, set()).add(search_id) - - assert ( - results == expected - ), f"Results for {search_type}/{search_id} do not match up" - - # For combined things, needs to return everything. - expected_combined = { - "config_entry": {wled_config_entry.entry_id, hue_config_entry.entry_id}, - "area": {living_room_area.id, kitchen_area.id}, - "device": {wled_device.id, hue_device.id}, - "entity": { + assert not search(ItemType.AUTOMATION, "automation.unknown") + assert search(ItemType.AUTOMATION, "automation.blueprint_automation_1") == { + ItemType.AUTOMATION_BLUEPRINT: {"test_event_service.yaml"}, + ItemType.ENTITY: {"light.kitchen"}, + } + assert search(ItemType.AUTOMATION, "automation.blueprint_automation_2") == { + ItemType.AUTOMATION_BLUEPRINT: {"test_event_service.yaml"}, + ItemType.ENTITY: {"light.kitchen"}, + } + assert search(ItemType.AUTOMATION, "automation.wled_entity") == { + ItemType.AREA: {living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.ENTITY: {wled_segment_1_entity.entity_id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.AUTOMATION, "automation.wled_device") == { + ItemType.AREA: {living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.AUTOMATION, "automation.floor") == { + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.AUTOMATION, "automation.area") == { + ItemType.AREA: {kitchen_area.id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.AUTOMATION, "automation.group") == { + ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + "group.wled_hue", wled_segment_1_entity.entity_id, wled_segment_2_entity.entity_id, hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, - "scene": { - "scene.scene_wled_seg_1", - "scene.scene_hue_seg_1", - "scene.scene_wled_hue", + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.GROUP: {"group.wled_hue"}, + } + assert search(ItemType.AUTOMATION, "automation.scene") == { + ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + scene_wled_hue_entity.entity_id, }, - "group": {"group.wled", "group.hue", "group.wled_hue"}, - "script": {"script.wled", "script.hue"}, - "automation": {"automation.wled_entity", "automation.wled_device"}, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, } - for search_type, search_id in ( - ("scene", "scene.scene_wled_hue"), - ("group", "group.wled_hue"), - ): - searcher = search.Searcher( - hass, device_registry, entity_registry, entity_sources - ) - results = searcher.async_search(search_type, search_id) - # Add the item we searched for, it's omitted from results - results.setdefault(search_type, set()).add(search_id) - assert ( - results == expected_combined - ), f"Results for {search_type}/{search_id} do not match up" - - for search_type, search_id in ( - ("entity", "automation.non_existing"), - ("entity", "scene.non_existing"), - ("entity", "group.non_existing"), - ("entity", "script.non_existing"), - ("entity", "light.non_existing"), - ("area", "non_existing"), - ("config_entry", "non_existing"), - ("device", "non_existing"), - ("group", "group.non_existing"), - ("scene", "scene.non_existing"), - ("script", "script.non_existing"), - ("automation", "automation.non_existing"), - ): - searcher = search.Searcher( - hass, device_registry, entity_registry, entity_sources - ) - assert searcher.async_search(search_type, search_id) == {} - - # Test search of templated script. We can't find referenced areas, devices or - # entities within templated services, but searching them should not raise or - # otherwise fail. - assert hass.states.get("script.script_with_templated_services") - for search_type, search_id in ( - ("area", "script.script_with_templated_services"), - ("device", "script.script_with_templated_services"), - ("entity", "script.script_with_templated_services"), - ): - searcher = search.Searcher( - hass, device_registry, entity_registry, entity_sources - ) - assert searcher.async_search(search_type, search_id) == {} - - searcher = search.Searcher(hass, device_registry, entity_registry, entity_sources) - assert searcher.async_search("entity", "light.wled_config_entry_source") == { - "config_entry": {wled_config_entry.entry_id}, - } - - -async def test_area_lookup( - hass: HomeAssistant, - area_registry: ar.AreaRegistry, - device_registry: dr.DeviceRegistry, - entity_registry: er.EntityRegistry, -) -> None: - """Test area based lookup.""" - living_room_area = area_registry.async_create("Living Room") - - await async_setup_component( - hass, - "script", - { - "script": { - "wled": { - "sequence": [ - { - "service": "light.turn_on", - "target": {"area_id": living_room_area.id}, - }, - ] - }, - } + assert search(ItemType.AUTOMATION, "automation.script") == { + ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + scene_wled_hue_entity.entity_id, + script_scene_entity.entity_id, }, - ) - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - { - "alias": "area_turn_on", - "trigger": {"platform": "template", "value_template": "true"}, - "action": [ - { - "service": "light.turn_on", - "data": { - "area_id": living_room_area.id, - }, - }, - ], - }, - ] - }, - ) - - searcher = search.Searcher( - hass, device_registry, entity_registry, MOCK_ENTITY_SOURCES - ) - assert searcher.async_search("area", living_room_area.id) == { - "script": {"script.wled"}, - "automation": {"automation.area_turn_on"}, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {script_scene_entity.entity_id}, } - searcher = search.Searcher( - hass, device_registry, entity_registry, MOCK_ENTITY_SOURCES - ) - assert searcher.async_search("automation", "automation.area_turn_on") == { - "area": {living_room_area.id}, - } - - -async def test_person_lookup(hass: HomeAssistant) -> None: - """Test searching persons.""" - assert await async_setup_component( - hass, - "person", - { - "person": [ - { - "id": "abcd", - "name": "Paulus", - "device_trackers": ["device_tracker.paulus_iphone"], - } - ] - }, - ) - - device_reg = dr.async_get(hass) - entity_reg = er.async_get(hass) - - searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES) - assert searcher.async_search("entity", "device_tracker.paulus_iphone") == { - "person": {"person.paulus"}, - } - - searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES) - assert searcher.async_search("entity", "person.paulus") == { - "entity": {"device_tracker.paulus_iphone"}, - } - - -async def test_automation_blueprint(hass): - """Test searching for automation blueprints.""" - - assert await async_setup_component( - hass, - "automation", - { - "automation": [ - { - "alias": "blueprint_automation_1", - "trigger": {"platform": "template", "value_template": "true"}, - "use_blueprint": { - "path": "test_event_service.yaml", - "input": { - "trigger_event": "blueprint_event_1", - "service_to_call": "test.automation_1", - "a_number": 5, - }, - }, - }, - { - "alias": "blueprint_automation_2", - "trigger": {"platform": "template", "value_template": "true"}, - "use_blueprint": { - "path": "test_event_service.yaml", - "input": { - "trigger_event": "blueprint_event_2", - "service_to_call": "test.automation_2", - "a_number": 5, - }, - }, - }, - ] - }, - ) - - # Ensure automations set up correctly. - assert hass.states.get("automation.blueprint_automation_1") is not None - assert hass.states.get("automation.blueprint_automation_1") is not None - - device_reg = dr.async_get(hass) - entity_reg = er.async_get(hass) - - searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES) - assert searcher.async_search("automation", "automation.blueprint_automation_1") == { - "automation": {"automation.blueprint_automation_2"}, - "automation_blueprint": {"test_event_service.yaml"}, - "entity": {"light.kitchen"}, - } - - searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES) - assert searcher.async_search("automation_blueprint", "test_event_service.yaml") == { - "automation": { + assert not search(ItemType.AUTOMATION_BLUEPRINT, "unknown.yaml") + assert search(ItemType.AUTOMATION_BLUEPRINT, "test_event_service.yaml") == { + ItemType.AUTOMATION: { "automation.blueprint_automation_1", "automation.blueprint_automation_2", + } + } + + assert not search(ItemType.CONFIG_ENTRY, "unknown") + assert search(ItemType.CONFIG_ENTRY, hue_config_entry.entry_id) == { + ItemType.AREA: {kitchen_area.id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.ENTITY: { + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, }, + ItemType.FLOOR: {first_floor.floor_id}, + ItemType.GROUP: {"group.hue", "group.wled_hue"}, + ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.device", "script.hue"}, } - - -async def test_script_blueprint(hass): - """Test searching for script blueprints.""" - - assert await async_setup_component( - hass, - "script", - { - "script": { - "blueprint_script_1": { - "use_blueprint": { - "path": "test_service.yaml", - "input": { - "service_to_call": "test.automation", - }, - } - }, - "blueprint_script_2": { - "use_blueprint": { - "path": "test_service.yaml", - "input": { - "service_to_call": "test.automation", - }, - } - }, - } + assert search(ItemType.CONFIG_ENTRY, wled_config_entry.entry_id) == { + ItemType.AREA: {bedroom_area.id, living_room_area.id}, + ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"}, + ItemType.DEVICE: {wled_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, }, - ) - - # Ensure automations set up correctly. - assert hass.states.get("script.blueprint_script_1") is not None - assert hass.states.get("script.blueprint_script_1") is not None - - device_reg = dr.async_get(hass) - entity_reg = er.async_get(hass) - - searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES) - assert searcher.async_search("script", "script.blueprint_script_1") == { - "entity": {"light.kitchen"}, - "script": {"script.blueprint_script_2"}, - "script_blueprint": {"test_service.yaml"}, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.wled"}, } - searcher = search.Searcher(hass, device_reg, entity_reg, MOCK_ENTITY_SOURCES) - assert searcher.async_search("script_blueprint", "test_service.yaml") == { - "script": {"script.blueprint_script_1", "script.blueprint_script_2"}, + assert not search(ItemType.DEVICE, "unknown") + assert search(ItemType.DEVICE, wled_device.id) == { + ItemType.AREA: {bedroom_area.id, living_room_area.id}, + ItemType.AUTOMATION: {"automation.wled_entity", "automation.wled_device"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.LABEL: {label_christmas.label_id}, + ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.wled"}, + } + assert search(ItemType.DEVICE, hue_device.id) == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.ENTITY: { + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id}, + ItemType.GROUP: {"group.hue", "group.wled_hue"}, + ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.device", "script.hue"}, } + assert not search(ItemType.ENTITY, "sensor.unknown") + assert search(ItemType.ENTITY, wled_segment_1_entity.entity_id) == { + ItemType.AREA: {living_room_area.id}, + ItemType.AUTOMATION: {"automation.wled_entity"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.FLOOR: {first_floor.floor_id}, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.wled"}, + } + assert search(ItemType.ENTITY, wled_segment_2_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.FLOOR: {second_floor.floor_id}, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + } + assert search(ItemType.ENTITY, hue_segment_1_entity.entity_id) == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.FLOOR: {first_floor.floor_id}, + ItemType.GROUP: {"group.hue", "group.wled_hue"}, + ItemType.LABEL: {label_energy.label_id}, + ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.hue"}, + } + assert search(ItemType.ENTITY, hue_segment_2_entity.entity_id) == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.FLOOR: {first_floor.floor_id}, + ItemType.GROUP: {"group.hue", "group.wled_hue"}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + } + assert not search(ItemType.ENTITY, "automation.wled") + assert search(ItemType.ENTITY, script_scene_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.AUTOMATION: {"automation.script"}, + ItemType.FLOOR: {second_floor.floor_id}, + ItemType.LABEL: {label_other.label_id}, + ItemType.SCRIPT: {"script.nested"}, + } + assert search(ItemType.ENTITY, "group.wled_hue") == { + ItemType.AUTOMATION: {"automation.group"}, + ItemType.SCRIPT: {"script.group"}, + } + assert search(ItemType.ENTITY, person_paulus_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.FLOOR: {second_floor.floor_id}, + ItemType.LABEL: {label_other.label_id}, + } + assert search(ItemType.ENTITY, scene_wled_hue_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.AUTOMATION: {"automation.scene"}, + ItemType.FLOOR: {second_floor.floor_id}, + ItemType.LABEL: {label_other.label_id}, + ItemType.SCRIPT: {script_scene_entity.entity_id}, + } + assert search(ItemType.ENTITY, "device_tracker.paulus_iphone") == { + ItemType.PERSON: {person_paulus_entity.entity_id}, + } + assert search(ItemType.ENTITY, "light.wled_config_entry_source") == { + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + } -async def test_ws_api(hass: HomeAssistant, hass_ws_client: WebSocketGenerator) -> None: - """Test WS API.""" - assert await async_setup_component(hass, "search", {}) + assert not search(ItemType.FLOOR, "unknown") + assert search(ItemType.FLOOR, first_floor.floor_id) == { + ItemType.AREA: {kitchen_area.id, living_room_area.id}, + ItemType.AUTOMATION: { + "automation.area", + "automation.floor", + "automation.wled_device", + }, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id, wled_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id, wled_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.SCRIPT: {"script.device", "script.area", "script.floor"}, + } + assert search(ItemType.FLOOR, second_floor.floor_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.AUTOMATION: {"automation.scene", "automation.script"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.ENTITY: { + wled_segment_2_entity.entity_id, + person_paulus_entity.entity_id, + scene_wled_hue_entity.entity_id, + script_scene_entity.entity_id, + }, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.PERSON: {person_paulus_entity.entity_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {script_scene_entity.entity_id, "script.nested"}, + } - area_reg = ar.async_get(hass) - device_reg = dr.async_get(hass) + assert not search(ItemType.GROUP, "group.unknown") + assert search(ItemType.GROUP, "group.wled") == { + ItemType.AREA: {bedroom_area.id, living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + } + assert search(ItemType.GROUP, "group.hue") == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.ENTITY: { + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.GROUP, "group.wled_hue") == { + ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, + ItemType.AUTOMATION: {"automation.group"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.SCRIPT: {"script.group"}, + } - kitchen_area = area_reg.async_create("Kitchen") + assert not search(ItemType.LABEL, "unknown") + assert search(ItemType.LABEL, label_christmas.label_id) == { + ItemType.AUTOMATION: {"automation.label"}, + ItemType.DEVICE: {wled_device.id}, + } + assert search(ItemType.LABEL, label_energy.label_id) == { + ItemType.ENTITY: {hue_segment_1_entity.entity_id}, + } + assert search(ItemType.LABEL, label_other.label_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.ENTITY: { + scene_wled_hue_entity.entity_id, + person_paulus_entity.entity_id, + script_scene_entity.entity_id, + }, + ItemType.PERSON: {person_paulus_entity.entity_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.label", script_scene_entity.entity_id}, + } - hue_config_entry = MockConfigEntry(domain="hue") - hue_config_entry.add_to_hass(hass) + assert not search(ItemType.PERSON, "person.unknown") + assert search(ItemType.PERSON, person_paulus_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id}, + ItemType.ENTITY: {"device_tracker.paulus_iphone"}, + ItemType.FLOOR: {second_floor.floor_id}, + ItemType.LABEL: {label_other.label_id}, + } - hue_device = device_reg.async_get_or_create( - config_entry_id=hue_config_entry.entry_id, - name="Light Strip", - identifiers=({"hue", "hue-1"}), - ) + assert not search(ItemType.SCENE, "scene.unknown") + assert search(ItemType.SCENE, "scene.scene_wled_seg_1") == { + ItemType.AREA: {living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.ENTITY: {wled_segment_1_entity.entity_id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCENE, "scene.scene_hue_seg_1") == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.ENTITY: {hue_segment_1_entity.entity_id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCENE, scene_wled_hue_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, + ItemType.AUTOMATION: {"automation.scene"}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.LABEL: {label_other.label_id}, + ItemType.SCRIPT: {script_scene_entity.entity_id}, + } - device_reg.async_update_device(hue_device.id, area_id=kitchen_area.id) + assert not search(ItemType.SCRIPT, "script.unknown") + assert search(ItemType.SCRIPT, "script.blueprint_script_1") == { + ItemType.ENTITY: {"light.kitchen"}, + ItemType.SCRIPT_BLUEPRINT: {"test_service.yaml"}, + } + assert search(ItemType.SCRIPT, "script.blueprint_script_2") == { + ItemType.ENTITY: {"light.kitchen"}, + ItemType.SCRIPT_BLUEPRINT: {"test_service.yaml"}, + } + assert search(ItemType.SCRIPT, "script.wled") == { + ItemType.AREA: {living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id}, + ItemType.ENTITY: {wled_segment_1_entity.entity_id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCRIPT, "script.hue") == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.ENTITY: {hue_segment_1_entity.entity_id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCRIPT, "script.script_with_templated_services") == {} + assert search(ItemType.SCRIPT, "script.device") == { + ItemType.AREA: {kitchen_area.id}, + ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id}, + ItemType.DEVICE: {hue_device.id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCRIPT, "script.floor") == { + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCRIPT, "script.area") == { + ItemType.AREA: {kitchen_area.id}, + ItemType.FLOOR: {first_floor.floor_id}, + } + assert search(ItemType.SCRIPT, "script.group") == { + ItemType.AREA: {bedroom_area.id, living_room_area.id, kitchen_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + "group.wled_hue", + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.GROUP: {"group.wled_hue"}, + } + assert search(ItemType.SCRIPT, script_scene_entity.entity_id) == { + ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + scene_wled_hue_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.LABEL: {label_other.label_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + } + assert search(ItemType.SCRIPT, "script.nested") == { + ItemType.AREA: {bedroom_area.id, kitchen_area.id, living_room_area.id}, + ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id, hue_config_entry.entry_id}, + ItemType.DEVICE: {wled_device.id, hue_device.id}, + ItemType.ENTITY: { + wled_segment_1_entity.entity_id, + wled_segment_2_entity.entity_id, + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + scene_wled_hue_entity.entity_id, + script_scene_entity.entity_id, + }, + ItemType.FLOOR: {first_floor.floor_id, second_floor.floor_id}, + ItemType.SCENE: {scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {script_scene_entity.entity_id}, + } + assert not search(ItemType.SCRIPT_BLUEPRINT, "unknown.yaml") + assert search(ItemType.SCRIPT_BLUEPRINT, "test_service.yaml") == { + ItemType.SCRIPT: {"script.blueprint_script_1", "script.blueprint_script_2"}, + } + + # WebSocket client = await hass_ws_client(hass) - await client.send_json( { "id": 1, @@ -585,6 +948,23 @@ async def test_ws_api(hass: HomeAssistant, hass_ws_client: WebSocketGenerator) - response = await client.receive_json() assert response["success"] assert response["result"] == { - "config_entry": [hue_config_entry.entry_id], - "area": [kitchen_area.id], + ItemType.AREA: [kitchen_area.id], + ItemType.ENTITY: unordered( + [ + hue_segment_1_entity.entity_id, + hue_segment_2_entity.entity_id, + ] + ), + ItemType.GROUP: unordered( + [ + "group.hue", + "group.wled_hue", + ] + ), + ItemType.CONFIG_ENTRY: [hue_config_entry.entry_id], + ItemType.FLOOR: [first_floor.floor_id], + ItemType.SCENE: unordered( + ["scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id] + ), + ItemType.SCRIPT: unordered(["script.device", "script.hue"]), } From 03ec1798e4eb9eb15f1800205b2294160a827335 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 27 Mar 2024 09:43:08 +0100 Subject: [PATCH 1547/1691] Bump aiounifi to v73 (#114278) --- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index b0d6b31c7be..63f9f67605e 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["aiounifi"], "quality_scale": "platinum", - "requirements": ["aiounifi==72"], + "requirements": ["aiounifi==73"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index ae10e886b49..fd87aa7592d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -392,7 +392,7 @@ aiotankerkoenig==0.4.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==72 +aiounifi==73 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0af04ed961e..7789460ea4e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -365,7 +365,7 @@ aiotankerkoenig==0.4.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==72 +aiounifi==73 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From 13d6ebaabfe92a6410dc16f8a35920ccd0728438 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 22:55:59 -1000 Subject: [PATCH 1548/1691] Avoid delaying automation/script startup for sample blueprints (#114277) Avoid delaying automation/script startup to check if the blueprint folder exists automations and script both populate sample blueprints if none exist The check to see if the blueprint folder exists always had to create an executor job which would delay startup a bit if the executor was busy. Since we do not need the sample blueprints to be populated until the start event, we can run this in a task. --- homeassistant/components/automation/__init__.py | 9 +++++++-- homeassistant/components/script/__init__.py | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3942ca6368f..0bd2ed87d20 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -298,8 +298,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await _async_process_config(hass, config, component) # Add some default blueprints to blueprints/automation, does nothing - # if blueprints/automation already exists - await async_get_blueprints(hass).async_populate() + # if blueprints/automation already exists but still has to create + # an executor job to check if the folder exists so we run it in a + # separate task to avoid waiting for it to finish setting up + # since a tracked task will be waited at the end of startup + hass.async_create_task( + async_get_blueprints(hass).async_populate(), eager_start=True + ) async def trigger_service_handler( entity: BaseAutomationEntity, service_call: ServiceCall diff --git a/homeassistant/components/script/__init__.py b/homeassistant/components/script/__init__.py index 79ed69d2cdf..82752ed15bc 100644 --- a/homeassistant/components/script/__init__.py +++ b/homeassistant/components/script/__init__.py @@ -223,8 +223,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await _async_process_config(hass, config, component) # Add some default blueprints to blueprints/script, does nothing - # if blueprints/script already exists - await async_get_blueprints(hass).async_populate() + # if blueprints/script already exists but still has to create + # an executor job to check if the folder exists so we run it in a + # separate task to avoid waiting for it to finish setting up + # since a tracked task will be waited at the end of startup + hass.async_create_task( + async_get_blueprints(hass).async_populate(), eager_start=True + ) async def reload_service(service: ServiceCall) -> None: """Call a service to reload scripts.""" From 2421b42f103bc0d76e22e938118a75ee400b6df1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 23:17:35 -1000 Subject: [PATCH 1549/1691] Refactor yeelight scanner to avoid creating tasks to wait for scanner start (#113919) --- homeassistant/components/yeelight/scanner.py | 50 ++++++++++---------- tests/components/yeelight/test_light.py | 1 + 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/yeelight/scanner.py b/homeassistant/components/yeelight/scanner.py index c98ca625029..6ca12e9bd01 100644 --- a/homeassistant/components/yeelight/scanner.py +++ b/homeassistant/components/yeelight/scanner.py @@ -3,9 +3,10 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, ValuesView +from collections.abc import ValuesView import contextlib from datetime import datetime +from functools import partial from ipaddress import IPv4Address import logging from typing import Self @@ -19,6 +20,7 @@ from homeassistant.components import network, ssdp from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback from homeassistant.helpers import discovery_flow from homeassistant.helpers.event import async_call_later, async_track_time_interval +from homeassistant.util.async_ import create_eager_task from .const import ( DISCOVERY_ATTEMPTS, @@ -33,6 +35,12 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +@callback +def _set_future_if_not_done(future: asyncio.Future[None]) -> None: + if not future.done(): + future.set_result(None) + + class YeelightScanner: """Scan for Yeelight devices.""" @@ -54,26 +62,18 @@ class YeelightScanner: self._host_capabilities: dict[str, CaseInsensitiveDict] = {} self._track_interval: CALLBACK_TYPE | None = None self._listeners: list[SsdpSearchListener] = [] - self._connected_events: list[asyncio.Event] = [] + self._setup_future: asyncio.Future[None] | None = None async def async_setup(self) -> None: """Set up the scanner.""" - if self._connected_events: - await self._async_wait_connected() - return - - for idx, source_ip in enumerate(await self._async_build_source_set()): - self._connected_events.append(asyncio.Event()) - - def _wrap_async_connected_idx(idx) -> Callable[[], None]: - """Create a function to capture the idx cell variable.""" - - @callback - def _async_connected() -> None: - self._connected_events[idx].set() - - return _async_connected + if self._setup_future is not None: + return await self._setup_future + self._setup_future = self._hass.loop.create_future() + connected_futures: list[asyncio.Future[None]] = [] + for source_ip in await self._async_build_source_set(): + future = self._hass.loop.create_future() + connected_futures.append(future) source = (str(source_ip), 0) self._listeners.append( SsdpSearchListener( @@ -81,12 +81,15 @@ class YeelightScanner: search_target=SSDP_ST, target=SSDP_TARGET, source=source, - connect_callback=_wrap_async_connected_idx(idx), + connect_callback=partial(_set_future_if_not_done, future), ) ) results = await asyncio.gather( - *(listener.async_start() for listener in self._listeners), + *( + create_eager_task(listener.async_start()) + for listener in self._listeners + ), return_exceptions=True, ) failed_listeners = [] @@ -99,20 +102,17 @@ class YeelightScanner: result, ) failed_listeners.append(self._listeners[idx]) - self._connected_events[idx].set() + _set_future_if_not_done(connected_futures[idx]) for listener in failed_listeners: self._listeners.remove(listener) - await self._async_wait_connected() + await asyncio.wait(connected_futures) self._track_interval = async_track_time_interval( self._hass, self.async_scan, DISCOVERY_INTERVAL, cancel_on_shutdown=True ) self.async_scan() - - async def _async_wait_connected(self): - """Wait for the listeners to be up and connected.""" - await asyncio.gather(*(event.wait() for event in self._connected_events)) + _set_future_if_not_done(self._setup_future) async def _async_build_source_set(self) -> set[IPv4Address]: """Build the list of ssdp sources.""" diff --git a/tests/components/yeelight/test_light.py b/tests/components/yeelight/test_light.py index 052b6d3223a..ff80c2b55b2 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -1413,6 +1413,7 @@ async def test_effects(hass: HomeAssistant) -> None: } }, ) + await hass.async_block_till_done() config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) config_entry.add_to_hass(hass) From 7b56643278ab0ca5655c2bec333cf394a6b06e87 Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:50:04 +0100 Subject: [PATCH 1550/1691] Add select to Motionblinds BLE integration (#114228) Co-authored-by: Robert Resch --- .coveragerc | 1 + .../components/motionblinds_ble/__init__.py | 4 +- .../components/motionblinds_ble/const.py | 2 + .../components/motionblinds_ble/icons.json | 9 +++ .../components/motionblinds_ble/select.py | 79 +++++++++++++++++++ .../components/motionblinds_ble/strings.json | 12 +++ 6 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/motionblinds_ble/icons.json create mode 100644 homeassistant/components/motionblinds_ble/select.py diff --git a/.coveragerc b/.coveragerc index 1bee5f26b37..9da1e6fb0a7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -809,6 +809,7 @@ omit = homeassistant/components/motionblinds_ble/__init__.py homeassistant/components/motionblinds_ble/cover.py homeassistant/components/motionblinds_ble/entity.py + homeassistant/components/motionblinds_ble/select.py homeassistant/components/motionmount/__init__.py homeassistant/components/motionmount/binary_sensor.py homeassistant/components/motionmount/entity.py diff --git a/homeassistant/components/motionblinds_ble/__init__.py b/homeassistant/components/motionblinds_ble/__init__.py index beef6d7d665..bb89b468a5b 100644 --- a/homeassistant/components/motionblinds_ble/__init__.py +++ b/homeassistant/components/motionblinds_ble/__init__.py @@ -28,9 +28,7 @@ from .const import CONF_BLIND_TYPE, CONF_MAC_CODE, DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[Platform] = [ - Platform.COVER, -] +PLATFORMS: list[Platform] = [Platform.COVER, Platform.SELECT] CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) diff --git a/homeassistant/components/motionblinds_ble/const.py b/homeassistant/components/motionblinds_ble/const.py index 1b396dd544d..5feaf59845f 100644 --- a/homeassistant/components/motionblinds_ble/const.py +++ b/homeassistant/components/motionblinds_ble/const.py @@ -1,5 +1,7 @@ """Constants for the Motionblinds BLE integration.""" +ATTR_SPEED = "speed" + CONF_LOCAL_NAME = "local_name" CONF_MAC_CODE = "mac_code" CONF_BLIND_TYPE = "blind_type" diff --git a/homeassistant/components/motionblinds_ble/icons.json b/homeassistant/components/motionblinds_ble/icons.json new file mode 100644 index 00000000000..2ea620d3947 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/icons.json @@ -0,0 +1,9 @@ +{ + "entity": { + "select": { + "speed": { + "default": "mdi:run-fast" + } + } + } +} diff --git a/homeassistant/components/motionblinds_ble/select.py b/homeassistant/components/motionblinds_ble/select.py new file mode 100644 index 00000000000..2ba2b8df2d4 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/select.py @@ -0,0 +1,79 @@ +"""Select entities for the Motionblinds BLE integration.""" + +from __future__ import annotations + +import logging + +from motionblindsble.const import MotionBlindType, MotionSpeedLevel +from motionblindsble.device import MotionDevice + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_SPEED, CONF_MAC_CODE, DOMAIN +from .entity import MotionblindsBLEEntity + +_LOGGER = logging.getLogger(__name__) + +PARALLEL_UPDATES = 0 + + +SELECT_TYPES: dict[str, SelectEntityDescription] = { + ATTR_SPEED: SelectEntityDescription( + key=ATTR_SPEED, + translation_key=ATTR_SPEED, + entity_category=EntityCategory.CONFIG, + options=["1", "2", "3"], + ) +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up select entities based on a config entry.""" + + device: MotionDevice = hass.data[DOMAIN][entry.entry_id] + + if device.blind_type not in {MotionBlindType.CURTAIN, MotionBlindType.VERTICAL}: + async_add_entities([SpeedSelect(device, entry, SELECT_TYPES[ATTR_SPEED])]) + + +class SpeedSelect(MotionblindsBLEEntity, SelectEntity): + """Representation of a speed select entity.""" + + def __init__( + self, + device: MotionDevice, + entry: ConfigEntry, + entity_description: SelectEntityDescription, + ) -> None: + """Initialize the speed select entity.""" + super().__init__( + device, entry, entity_description, unique_id_suffix=entity_description.key + ) + self._attr_current_option = None + + async def async_added_to_hass(self) -> None: + """Register device callbacks.""" + _LOGGER.debug( + "(%s) Setting up speed select entity", + self.entry.data[CONF_MAC_CODE], + ) + self.device.register_speed_callback(self.async_update_speed) + + @callback + def async_update_speed(self, speed_level: MotionSpeedLevel | None) -> None: + """Update the speed sensor value.""" + self._attr_current_option = str(speed_level.value) if speed_level else None + self.async_write_ha_state() + + async def async_select_option(self, option: str) -> None: + """Change the selected speed sensor value.""" + speed_level = MotionSpeedLevel(int(option)) + await self.device.speed(speed_level) + self._attr_current_option = str(speed_level.value) if speed_level else None + self.async_write_ha_state() diff --git a/homeassistant/components/motionblinds_ble/strings.json b/homeassistant/components/motionblinds_ble/strings.json index e876d64d568..bcc06eeb181 100644 --- a/homeassistant/components/motionblinds_ble/strings.json +++ b/homeassistant/components/motionblinds_ble/strings.json @@ -33,5 +33,17 @@ "vertical": "Vertical blind" } } + }, + "entity": { + "select": { + "speed": { + "name": "Speed", + "state": { + "1": "Low", + "2": "Medium", + "3": "High" + } + } + } } } From 6423501498cedc6a11bed9e9a07fde0d3426379b Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:05:26 +0100 Subject: [PATCH 1551/1691] Add buttons to Motionblinds BLE integration (#114227) Co-authored-by: Joost Lekkerkerker Co-authored-by: Robert Resch --- .coveragerc | 1 + .../components/motionblinds_ble/__init__.py | 2 +- .../components/motionblinds_ble/button.py | 88 +++++++++++++++++++ .../components/motionblinds_ble/const.py | 3 + .../components/motionblinds_ble/icon.json | 15 ++++ .../components/motionblinds_ble/strings.json | 11 +++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/motionblinds_ble/button.py create mode 100644 homeassistant/components/motionblinds_ble/icon.json diff --git a/.coveragerc b/.coveragerc index 9da1e6fb0a7..7fd6ab5defe 100644 --- a/.coveragerc +++ b/.coveragerc @@ -807,6 +807,7 @@ omit = homeassistant/components/motion_blinds/entity.py homeassistant/components/motion_blinds/sensor.py homeassistant/components/motionblinds_ble/__init__.py + homeassistant/components/motionblinds_ble/button.py homeassistant/components/motionblinds_ble/cover.py homeassistant/components/motionblinds_ble/entity.py homeassistant/components/motionblinds_ble/select.py diff --git a/homeassistant/components/motionblinds_ble/__init__.py b/homeassistant/components/motionblinds_ble/__init__.py index bb89b468a5b..f70625cd36d 100644 --- a/homeassistant/components/motionblinds_ble/__init__.py +++ b/homeassistant/components/motionblinds_ble/__init__.py @@ -28,7 +28,7 @@ from .const import CONF_BLIND_TYPE, CONF_MAC_CODE, DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS: list[Platform] = [Platform.COVER, Platform.SELECT] +PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.COVER, Platform.SELECT] CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) diff --git a/homeassistant/components/motionblinds_ble/button.py b/homeassistant/components/motionblinds_ble/button.py new file mode 100644 index 00000000000..d3bd22e9276 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/button.py @@ -0,0 +1,88 @@ +"""Button entities for the Motionblinds BLE integration.""" + +from __future__ import annotations + +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +import logging +from typing import Any + +from motionblindsble.device import MotionDevice + +from homeassistant.components.button import ButtonEntity, ButtonEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_CONNECT, ATTR_DISCONNECT, ATTR_FAVORITE, CONF_MAC_CODE, DOMAIN +from .entity import MotionblindsBLEEntity + +_LOGGER = logging.getLogger(__name__) + +PARALLEL_UPDATES = 0 + + +@dataclass(frozen=True, kw_only=True) +class MotionblindsBLEButtonEntityDescription(ButtonEntityDescription): + """Entity description of a button entity with command attribute.""" + + command: Callable[[MotionDevice], Coroutine[Any, Any, None]] + + +BUTTON_TYPES: list[MotionblindsBLEButtonEntityDescription] = [ + MotionblindsBLEButtonEntityDescription( + key=ATTR_CONNECT, + translation_key=ATTR_CONNECT, + entity_category=EntityCategory.CONFIG, + command=lambda device: device.connect(), + ), + MotionblindsBLEButtonEntityDescription( + key=ATTR_DISCONNECT, + translation_key=ATTR_DISCONNECT, + entity_category=EntityCategory.CONFIG, + command=lambda device: device.disconnect(), + ), + MotionblindsBLEButtonEntityDescription( + key=ATTR_FAVORITE, + translation_key=ATTR_FAVORITE, + entity_category=EntityCategory.CONFIG, + command=lambda device: device.favorite(), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up button entities based on a config entry.""" + + device: MotionDevice = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + MotionblindsBLEButtonEntity( + device, + entry, + entity_description, + unique_id_suffix=entity_description.key, + ) + for entity_description in BUTTON_TYPES + ) + + +class MotionblindsBLEButtonEntity(MotionblindsBLEEntity, ButtonEntity): + """Representation of a button entity.""" + + entity_description: MotionblindsBLEButtonEntityDescription + + async def async_added_to_hass(self) -> None: + """Log button entity information.""" + _LOGGER.debug( + "(%s) Setting up %s button entity", + self.entry.data[CONF_MAC_CODE], + self.entity_description.key, + ) + + async def async_press(self) -> None: + """Handle the button press.""" + await self.entity_description.command(self.device) diff --git a/homeassistant/components/motionblinds_ble/const.py b/homeassistant/components/motionblinds_ble/const.py index 5feaf59845f..d2eb5821b9f 100644 --- a/homeassistant/components/motionblinds_ble/const.py +++ b/homeassistant/components/motionblinds_ble/const.py @@ -1,5 +1,8 @@ """Constants for the Motionblinds BLE integration.""" +ATTR_CONNECT = "connect" +ATTR_DISCONNECT = "disconnect" +ATTR_FAVORITE = "favorite" ATTR_SPEED = "speed" CONF_LOCAL_NAME = "local_name" diff --git a/homeassistant/components/motionblinds_ble/icon.json b/homeassistant/components/motionblinds_ble/icon.json new file mode 100644 index 00000000000..109606ab474 --- /dev/null +++ b/homeassistant/components/motionblinds_ble/icon.json @@ -0,0 +1,15 @@ +{ + "entity": { + "button": { + "connect": { + "default": "mdi:bluetooth" + }, + "disconnect": { + "default": "mdi:bluetooth-off" + }, + "favorite": { + "default": "mdi:star" + } + } + } +} diff --git a/homeassistant/components/motionblinds_ble/strings.json b/homeassistant/components/motionblinds_ble/strings.json index bcc06eeb181..0bc9ad4c012 100644 --- a/homeassistant/components/motionblinds_ble/strings.json +++ b/homeassistant/components/motionblinds_ble/strings.json @@ -35,6 +35,17 @@ } }, "entity": { + "button": { + "connect": { + "name": "[%key:common::action::connect%]" + }, + "disconnect": { + "name": "[%key:common::action::disconnect%]" + }, + "favorite": { + "name": "Favorite" + } + }, "select": { "speed": { "name": "Speed", From 69356300f044d23ad29f41e35fe256d78716bac6 Mon Sep 17 00:00:00 2001 From: Shai Ungar Date: Wed, 27 Mar 2024 12:06:13 +0200 Subject: [PATCH 1552/1691] Address late review of 17track config flow (#114283) * some fixes for 17track * remove await * fix test --- .../components/seventeentrack/config_flow.py | 13 ++++++------- homeassistant/components/seventeentrack/sensor.py | 4 ++-- .../components/seventeentrack/strings.json | 12 +++++++----- tests/components/seventeentrack/test_config_flow.py | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/seventeentrack/config_flow.py b/homeassistant/components/seventeentrack/config_flow.py index f42ff184f81..ae31e1962d7 100644 --- a/homeassistant/components/seventeentrack/config_flow.py +++ b/homeassistant/components/seventeentrack/config_flow.py @@ -67,7 +67,7 @@ class SeventeenTrackConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input: - client = await self._get_client() + client = self._get_client() try: if not await client.profile.login( @@ -101,19 +101,17 @@ class SeventeenTrackConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult: """Import 17Track config from configuration.yaml.""" - client = await self._get_client() + client = self._get_client() try: login_result = await client.profile.login( import_data[CONF_USERNAME], import_data[CONF_PASSWORD] ) - except SeventeenTrackError as err: - _LOGGER.error("There was an error while logging in: %s", err) + except SeventeenTrackError: return self.async_abort(reason="cannot_connect") if not login_result: - _LOGGER.error("Invalid username and password provided") - return self.async_abort(reason="invalid_credentials") + return self.async_abort(reason="invalid_auth") account_id = client.profile.account_id @@ -132,6 +130,7 @@ class SeventeenTrackConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - async def _get_client(self): + @callback + def _get_client(self): session = aiohttp_client.async_get_clientsession(self.hass) return SeventeenTrackClient(session=session) diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index bdf4b74ac59..1de627fab39 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -94,12 +94,12 @@ async def async_setup_platform( async_create_issue( hass, DOMAIN, - f"deprecated_yaml_import_issue_${result['reason']}", + f"deprecated_yaml_import_issue_{result['reason']}", breaks_in_ha_version="2024.10.0", is_fixable=False, issue_domain=DOMAIN, severity=IssueSeverity.WARNING, - translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", translation_placeholders=ISSUE_PLACEHOLDER, ) diff --git a/homeassistant/components/seventeentrack/strings.json b/homeassistant/components/seventeentrack/strings.json index 1514509d6c2..39ddb5ef8ef 100644 --- a/homeassistant/components/seventeentrack/strings.json +++ b/homeassistant/components/seventeentrack/strings.json @@ -9,11 +9,13 @@ } }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "error": { - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" } }, "options": { @@ -32,8 +34,8 @@ "title": "The 17Track YAML configuration import cannot connect to server", "description": "Configuring 17Track using YAML is being removed but there was a connection error importing your YAML configuration.\n\nThings you can try:\nMake sure your home assistant can reach the web.\n\nThen restart Home Assistant to try importing this integration again.\n\nAlternatively, you may remove the 17Track configuration from your YAML configuration entirely, restart Home Assistant, and add the 17Track integration manually." }, - "deprecated_yaml_import_issue_invalid_credentials": { - "title": "The 17Track YAML configuration import request failed due to invalid credentials", + "deprecated_yaml_import_issue_invalid_auth": { + "title": "The 17Track YAML configuration import request failed due to invalid authentication", "description": "Configuring 17Track using YAML is being removed but there were invalid credentials provided while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your 17Track credentials are correct and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the 17Track configuration from your YAML configuration entirely, restart Home Assistant, and add the 17Track integration manually." } } diff --git a/tests/components/seventeentrack/test_config_flow.py b/tests/components/seventeentrack/test_config_flow.py index 04e4b50d066..ae48fb6c792 100644 --- a/tests/components/seventeentrack/test_config_flow.py +++ b/tests/components/seventeentrack/test_config_flow.py @@ -125,7 +125,7 @@ async def test_import_flow(hass: HomeAssistant, mock_seventeentrack: AsyncMock) ( False, None, - "invalid_credentials", + "invalid_auth", ), ( True, From b190cdceafc5fe92679a9095f5dee765f39a3be9 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 27 Mar 2024 11:06:31 +0100 Subject: [PATCH 1553/1691] Remove hourly weather entity from metoffice (#112452) --- homeassistant/components/metoffice/weather.py | 36 +- .../metoffice/snapshots/test_weather.ambr | 1960 +---------------- tests/components/metoffice/test_weather.py | 261 +-- 3 files changed, 85 insertions(+), 2172 deletions(-) diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py index ed4268d1722..19df765f1f9 100644 --- a/homeassistant/components/metoffice/weather.py +++ b/homeassistant/components/metoffice/weather.py @@ -45,31 +45,24 @@ async def async_setup_entry( entity_registry = er.async_get(hass) hass_data = hass.data[DOMAIN][entry.entry_id] - entities = [ - MetOfficeWeather( - hass_data[METOFFICE_DAILY_COORDINATOR], - hass_data[METOFFICE_HOURLY_COORDINATOR], - hass_data, - False, - ) - ] - - # Add hourly entity to legacy config entries - if entity_registry.async_get_entity_id( + # Remove hourly entity from legacy config entries + if entity_id := entity_registry.async_get_entity_id( WEATHER_DOMAIN, DOMAIN, _calculate_unique_id(hass_data[METOFFICE_COORDINATES], True), ): - entities.append( + entity_registry.async_remove(entity_id) + + async_add_entities( + [ MetOfficeWeather( hass_data[METOFFICE_DAILY_COORDINATOR], hass_data[METOFFICE_HOURLY_COORDINATOR], hass_data, - True, ) - ) - - async_add_entities(entities, False) + ], + False, + ) def _build_forecast_data(timestep: Timestep) -> Forecast: @@ -119,14 +112,9 @@ class MetOfficeWeather( coordinator_daily: TimestampDataUpdateCoordinator[MetOfficeData], coordinator_hourly: TimestampDataUpdateCoordinator[MetOfficeData], hass_data: dict[str, Any], - use_3hourly: bool, ) -> None: """Initialise the platform with a data instance.""" - self._hourly = use_3hourly - if use_3hourly: - observation_coordinator = coordinator_hourly - else: - observation_coordinator = coordinator_daily + observation_coordinator = coordinator_daily super().__init__( observation_coordinator, daily_coordinator=coordinator_daily, @@ -136,9 +124,9 @@ class MetOfficeWeather( self._attr_device_info = get_device_info( coordinates=hass_data[METOFFICE_COORDINATES], name=hass_data[METOFFICE_NAME] ) - self._attr_name = "3-Hourly" if use_3hourly else "Daily" + self._attr_name = "Daily" self._attr_unique_id = _calculate_unique_id( - hass_data[METOFFICE_COORDINATES], use_3hourly + hass_data[METOFFICE_COORDINATES], False ) @property diff --git a/tests/components/metoffice/snapshots/test_weather.ambr b/tests/components/metoffice/snapshots/test_weather.ambr index 108a9330403..a6991a8631b 100644 --- a/tests/components/metoffice/snapshots/test_weather.ambr +++ b/tests/components/metoffice/snapshots/test_weather.ambr @@ -1,1316 +1,4 @@ # serializer version: 1 -# name: test_forecast_service - dict({ - 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 13.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - ]), - }) -# --- -# name: test_forecast_service.1 - dict({ - 'forecast': list([ - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-25T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 19.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T18:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 17.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 14.0, - 'wind_bearing': 'NW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T00:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 13.0, - 'wind_bearing': 'WSW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T03:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T09:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T15:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T18:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T00:00:00+00:00', - 'precipitation_probability': 11, - 'temperature': 9.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T03:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 8.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T06:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 8.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 4, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T18:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-27T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T00:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 8.0, - 'wind_bearing': 'NNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 7.0, - 'wind_bearing': 'W', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-28T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 6.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-28T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T15:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T18:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NNE', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T00:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'E', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-29T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 8.0, - 'wind_bearing': 'SSE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T06:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 8.0, - 'wind_bearing': 'SE', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T09:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 10.0, - 'wind_bearing': 'SE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 47, - 'temperature': 12.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'pouring', - 'datetime': '2020-04-29T15:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T18:00:00+00:00', - 'precipitation_probability': 39, - 'temperature': 12.0, - 'wind_bearing': 'SSE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T21:00:00+00:00', - 'precipitation_probability': 19, - 'temperature': 11.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - ]), - }) -# --- -# name: test_forecast_service.2 - dict({ - 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 13.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - ]), - }) -# --- -# name: test_forecast_service.3 - dict({ - 'forecast': list([ - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-25T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 19.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T18:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 17.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 14.0, - 'wind_bearing': 'NW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T00:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 13.0, - 'wind_bearing': 'WSW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T03:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T09:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T15:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T18:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T00:00:00+00:00', - 'precipitation_probability': 11, - 'temperature': 9.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T03:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 8.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T06:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 8.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 4, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T18:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-27T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T00:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 8.0, - 'wind_bearing': 'NNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 7.0, - 'wind_bearing': 'W', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-28T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 6.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-28T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T15:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T18:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NNE', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T00:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'E', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-29T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 8.0, - 'wind_bearing': 'SSE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T06:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 8.0, - 'wind_bearing': 'SE', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T09:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 10.0, - 'wind_bearing': 'SE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 47, - 'temperature': 12.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'pouring', - 'datetime': '2020-04-29T15:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T18:00:00+00:00', - 'precipitation_probability': 39, - 'temperature': 12.0, - 'wind_bearing': 'SSE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T21:00:00+00:00', - 'precipitation_probability': 19, - 'temperature': 11.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - ]), - }) -# --- -# name: test_forecast_service[forecast] - dict({ - 'weather.met_office_wavertree_daily': dict({ - 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 13.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].1 - dict({ - 'weather.met_office_wavertree_daily': dict({ - 'forecast': list([ - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-25T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 19.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T18:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 17.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 14.0, - 'wind_bearing': 'NW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T00:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 13.0, - 'wind_bearing': 'WSW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T03:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T09:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T15:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T18:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T00:00:00+00:00', - 'precipitation_probability': 11, - 'temperature': 9.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T03:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 8.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T06:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 8.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 4, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T18:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-27T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T00:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 8.0, - 'wind_bearing': 'NNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 7.0, - 'wind_bearing': 'W', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-28T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 6.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-28T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T15:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T18:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NNE', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T00:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'E', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-29T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 8.0, - 'wind_bearing': 'SSE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T06:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 8.0, - 'wind_bearing': 'SE', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T09:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 10.0, - 'wind_bearing': 'SE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 47, - 'temperature': 12.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'pouring', - 'datetime': '2020-04-29T15:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T18:00:00+00:00', - 'precipitation_probability': 39, - 'temperature': 12.0, - 'wind_bearing': 'SSE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T21:00:00+00:00', - 'precipitation_probability': 19, - 'temperature': 11.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].2 - dict({ - 'weather.met_office_wavertree_daily': dict({ - 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 13.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].3 - dict({ - 'weather.met_office_wavertree_daily': dict({ - 'forecast': list([ - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-25T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 19.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T18:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 17.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 14.0, - 'wind_bearing': 'NW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T00:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 13.0, - 'wind_bearing': 'WSW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T03:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T09:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T15:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T18:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T00:00:00+00:00', - 'precipitation_probability': 11, - 'temperature': 9.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T03:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 8.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T06:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 8.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 4, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T18:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-27T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T00:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 8.0, - 'wind_bearing': 'NNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 7.0, - 'wind_bearing': 'W', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-28T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 6.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-28T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T15:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T18:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NNE', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T00:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'E', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-29T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 8.0, - 'wind_bearing': 'SSE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T06:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 8.0, - 'wind_bearing': 'SE', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T09:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 10.0, - 'wind_bearing': 'SE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 47, - 'temperature': 12.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'pouring', - 'datetime': '2020-04-29T15:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T18:00:00+00:00', - 'precipitation_probability': 39, - 'temperature': 12.0, - 'wind_bearing': 'SSE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T21:00:00+00:00', - 'precipitation_probability': 19, - 'temperature': 11.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - ]), - }), - }) -# --- -# name: test_forecast_service[forecast].4 - dict({ - 'weather.met_office_wavertree_daily': dict({ - 'forecast': list([ - ]), - }), - }) -# --- # name: test_forecast_service[get_forecast] dict({ 'forecast': list([ @@ -2629,7 +1317,7 @@ }), }) # --- -# name: test_forecast_subscription[weather.met_office_wavertree_3_hourly] +# name: test_forecast_subscription[daily] list([ dict({ 'condition': 'cloudy', @@ -2665,7 +1353,7 @@ }), ]) # --- -# name: test_forecast_subscription[weather.met_office_wavertree_3_hourly].1 +# name: test_forecast_subscription[daily].1 list([ dict({ 'condition': 'cloudy', @@ -2701,7 +1389,7 @@ }), ]) # --- -# name: test_forecast_subscription[weather.met_office_wavertree_3_hourly].2 +# name: test_forecast_subscription[hourly] list([ dict({ 'condition': 'sunny', @@ -2985,647 +1673,7 @@ }), ]) # --- -# name: test_forecast_subscription[weather.met_office_wavertree_3_hourly].3 - list([ - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-25T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 19.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T18:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 17.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 14.0, - 'wind_bearing': 'NW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T00:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 13.0, - 'wind_bearing': 'WSW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T03:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T09:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T15:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T18:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T00:00:00+00:00', - 'precipitation_probability': 11, - 'temperature': 9.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T03:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 8.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T06:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 8.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 4, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T18:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-27T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T00:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 8.0, - 'wind_bearing': 'NNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 7.0, - 'wind_bearing': 'W', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-28T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 6.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-28T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T15:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T18:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NNE', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T00:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'E', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-29T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 8.0, - 'wind_bearing': 'SSE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T06:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 8.0, - 'wind_bearing': 'SE', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T09:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 10.0, - 'wind_bearing': 'SE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 47, - 'temperature': 12.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'pouring', - 'datetime': '2020-04-29T15:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T18:00:00+00:00', - 'precipitation_probability': 39, - 'temperature': 12.0, - 'wind_bearing': 'SSE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T21:00:00+00:00', - 'precipitation_probability': 19, - 'temperature': 11.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - ]) -# --- -# name: test_forecast_subscription[weather.met_office_wavertree_daily] - list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 13.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - ]) -# --- -# name: test_forecast_subscription[weather.met_office_wavertree_daily].1 - list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 13.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - ]) -# --- -# name: test_forecast_subscription[weather.met_office_wavertree_daily].2 - list([ - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-25T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 19.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T18:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 17.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-25T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 14.0, - 'wind_bearing': 'NW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T00:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 13.0, - 'wind_bearing': 'WSW', - 'wind_speed': 3.22, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-26T03:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T09:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T12:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 12.0, - 'wind_bearing': 'WNW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T15:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 12.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T18:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 11.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-26T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T00:00:00+00:00', - 'precipitation_probability': 11, - 'temperature': 9.0, - 'wind_bearing': 'WNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T03:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 8.0, - 'wind_bearing': 'WNW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-27T06:00:00+00:00', - 'precipitation_probability': 14, - 'temperature': 8.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-27T12:00:00+00:00', - 'precipitation_probability': 4, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T15:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-27T18:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 10.0, - 'wind_bearing': 'NW', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-27T21:00:00+00:00', - 'precipitation_probability': 1, - 'temperature': 9.0, - 'wind_bearing': 'NW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T00:00:00+00:00', - 'precipitation_probability': 2, - 'temperature': 8.0, - 'wind_bearing': 'NNW', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'clear-night', - 'datetime': '2020-04-28T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 7.0, - 'wind_bearing': 'W', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'sunny', - 'datetime': '2020-04-28T06:00:00+00:00', - 'precipitation_probability': 5, - 'temperature': 6.0, - 'wind_bearing': 'S', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-28T09:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T12:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'ENE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T15:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 12.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T18:00:00+00:00', - 'precipitation_probability': 10, - 'temperature': 11.0, - 'wind_bearing': 'N', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-28T21:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 10.0, - 'wind_bearing': 'NNE', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T00:00:00+00:00', - 'precipitation_probability': 6, - 'temperature': 9.0, - 'wind_bearing': 'E', - 'wind_speed': 6.44, - }), - dict({ - 'condition': 'partlycloudy', - 'datetime': '2020-04-29T03:00:00+00:00', - 'precipitation_probability': 3, - 'temperature': 8.0, - 'wind_bearing': 'SSE', - 'wind_speed': 11.27, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T06:00:00+00:00', - 'precipitation_probability': 9, - 'temperature': 8.0, - 'wind_bearing': 'SE', - 'wind_speed': 14.48, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T09:00:00+00:00', - 'precipitation_probability': 12, - 'temperature': 10.0, - 'wind_bearing': 'SE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T12:00:00+00:00', - 'precipitation_probability': 47, - 'temperature': 12.0, - 'wind_bearing': 'SE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'pouring', - 'datetime': '2020-04-29T15:00:00+00:00', - 'precipitation_probability': 59, - 'temperature': 13.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - dict({ - 'condition': 'rainy', - 'datetime': '2020-04-29T18:00:00+00:00', - 'precipitation_probability': 39, - 'temperature': 12.0, - 'wind_bearing': 'SSE', - 'wind_speed': 17.7, - }), - dict({ - 'condition': 'cloudy', - 'datetime': '2020-04-29T21:00:00+00:00', - 'precipitation_probability': 19, - 'temperature': 11.0, - 'wind_bearing': 'SSE', - 'wind_speed': 20.92, - }), - ]) -# --- -# name: test_forecast_subscription[weather.met_office_wavertree_daily].3 +# name: test_forecast_subscription[hourly].1 list([ dict({ 'condition': 'sunny', diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index b0c88063de3..520759b46b4 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -109,14 +109,6 @@ async def test_site_cannot_update( ) -> None: """Test we handle cannot connect error.""" - # Pre-create the hourly entity - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "53.38374_-2.90929", - suggested_object_id="met_office_wavertree_3_hourly", - ) - entry = MockConfigEntry( domain=DOMAIN, data=METOFFICE_CONFIG_WAVERTREE, @@ -125,9 +117,6 @@ async def test_site_cannot_update( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - weather = hass.states.get("weather.met_office_wavertree_3_hourly") - assert weather - weather = hass.states.get("weather.met_office_wavertree_daily") assert weather @@ -138,9 +127,6 @@ async def test_site_cannot_update( async_fire_time_changed(hass, future_time) await hass.async_block_till_done() - weather = hass.states.get("weather.met_office_wavertree_3_hourly") - assert weather.state == STATE_UNAVAILABLE - weather = hass.states.get("weather.met_office_wavertree_daily") assert weather.state == STATE_UNAVAILABLE @@ -154,14 +140,6 @@ async def test_one_weather_site_running( ) -> None: """Test the Met Office weather platform.""" - # Pre-create the hourly entity - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "53.38374_-2.90929", - suggested_object_id="met_office_wavertree_3_hourly", - ) - entry = MockConfigEntry( domain=DOMAIN, data=METOFFICE_CONFIG_WAVERTREE, @@ -175,30 +153,6 @@ async def test_one_weather_site_running( device_wavertree = dev_reg.async_get_device(identifiers=DEVICE_KEY_WAVERTREE) assert device_wavertree.name == "Met Office Wavertree" - # Wavertree 3-hourly weather platform expected results - weather = hass.states.get("weather.met_office_wavertree_3_hourly") - assert weather - - assert weather.state == "sunny" - assert weather.attributes.get("temperature") == 17 - assert weather.attributes.get("wind_speed") == 14.48 - assert weather.attributes.get("wind_speed_unit") == "km/h" - assert weather.attributes.get("wind_bearing") == "SSE" - assert weather.attributes.get("humidity") == 50 - - # Forecasts added - just pick out 1 entry to check - assert len(weather.attributes.get("forecast")) == 35 - - assert ( - weather.attributes.get("forecast")[26]["datetime"] - == "2020-04-28T21:00:00+00:00" - ) - assert weather.attributes.get("forecast")[26]["condition"] == "cloudy" - assert weather.attributes.get("forecast")[26]["precipitation_probability"] == 9 - assert weather.attributes.get("forecast")[26]["temperature"] == 10 - assert weather.attributes.get("forecast")[26]["wind_speed"] == 6.44 - assert weather.attributes.get("forecast")[26]["wind_bearing"] == "NNE" - # Wavertree daily weather platform expected results weather = hass.states.get("weather.met_office_wavertree_daily") assert weather @@ -232,20 +186,6 @@ async def test_two_weather_sites_running( ) -> None: """Test we handle two different weather sites both running.""" - # Pre-create the hourly entities - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "53.38374_-2.90929", - suggested_object_id="met_office_wavertree_3_hourly", - ) - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "52.75556_0.44231", - suggested_object_id="met_office_king_s_lynn_3_hourly", - ) - # all metoffice test data encapsulated in here mock_json = json.loads(load_fixture("metoffice.json", "metoffice")) kingslynn_hourly = json.dumps(mock_json["kingslynn_hourly"]) @@ -279,30 +219,6 @@ async def test_two_weather_sites_running( device_wavertree = dev_reg.async_get_device(identifiers=DEVICE_KEY_WAVERTREE) assert device_wavertree.name == "Met Office Wavertree" - # Wavertree 3-hourly weather platform expected results - weather = hass.states.get("weather.met_office_wavertree_3_hourly") - assert weather - - assert weather.state == "sunny" - assert weather.attributes.get("temperature") == 17 - assert weather.attributes.get("wind_speed") == 14.48 - assert weather.attributes.get("wind_speed_unit") == "km/h" - assert weather.attributes.get("wind_bearing") == "SSE" - assert weather.attributes.get("humidity") == 50 - - # Forecasts added - just pick out 1 entry to check - assert len(weather.attributes.get("forecast")) == 35 - - assert ( - weather.attributes.get("forecast")[18]["datetime"] - == "2020-04-27T21:00:00+00:00" - ) - assert weather.attributes.get("forecast")[18]["condition"] == "clear-night" - assert weather.attributes.get("forecast")[18]["precipitation_probability"] == 1 - assert weather.attributes.get("forecast")[18]["temperature"] == 9 - assert weather.attributes.get("forecast")[18]["wind_speed"] == 6.44 - assert weather.attributes.get("forecast")[18]["wind_bearing"] == "NW" - # Wavertree daily weather platform expected results weather = hass.states.get("weather.met_office_wavertree_daily") assert weather @@ -327,30 +243,6 @@ async def test_two_weather_sites_running( assert weather.attributes.get("forecast")[3]["wind_speed"] == 20.92 assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE" - # King's Lynn 3-hourly weather platform expected results - weather = hass.states.get("weather.met_office_king_s_lynn_3_hourly") - assert weather - - assert weather.state == "sunny" - assert weather.attributes.get("temperature") == 14 - assert weather.attributes.get("wind_speed") == 3.22 - assert weather.attributes.get("wind_speed_unit") == "km/h" - assert weather.attributes.get("wind_bearing") == "E" - assert weather.attributes.get("humidity") == 60 - - # Also has Forecast added - just pick out 1 entry to check - assert len(weather.attributes.get("forecast")) == 35 - - assert ( - weather.attributes.get("forecast")[18]["datetime"] - == "2020-04-27T21:00:00+00:00" - ) - assert weather.attributes.get("forecast")[18]["condition"] == "cloudy" - assert weather.attributes.get("forecast")[18]["precipitation_probability"] == 9 - assert weather.attributes.get("forecast")[18]["temperature"] == 10 - assert weather.attributes.get("forecast")[18]["wind_speed"] == 11.27 - assert weather.attributes.get("forecast")[18]["wind_bearing"] == "SE" - # King's Lynn daily weather platform expected results weather = hass.states.get("weather.met_office_king_s_lynn_daily") assert weather @@ -395,33 +287,6 @@ async def test_new_config_entry( assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 1 -@pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) -async def test_legacy_config_entry( - hass: HomeAssistant, entity_registry: er.EntityRegistry, no_sensor, wavertree_data -) -> None: - """Test the expected entities are created.""" - # Pre-create the hourly entity - entity_registry.async_get_or_create( - WEATHER_DOMAIN, - DOMAIN, - "53.38374_-2.90929", - suggested_object_id="met_office_wavertree_3_hourly", - ) - - entry = MockConfigEntry( - domain=DOMAIN, - data=METOFFICE_CONFIG_WAVERTREE, - ) - entry.add_to_hass(hass) - - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - assert len(hass.states.async_entity_ids("weather")) == 2 - entry = hass.config_entries.async_entries()[0] - assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 2 - - @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) @pytest.mark.parametrize( ("service"), @@ -513,27 +378,11 @@ async def test_forecast_service( assert response == snapshot -@pytest.mark.parametrize( - "entity_id", - [ - "weather.met_office_wavertree_3_hourly", - "weather.met_office_wavertree_daily", - ], -) @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) -async def test_forecast_subscription( - hass: HomeAssistant, - hass_ws_client: WebSocketGenerator, - entity_registry: er.EntityRegistry, - freezer: FrozenDateTimeFactory, - snapshot: SnapshotAssertion, - no_sensor, - wavertree_data: dict[str, _Matcher], - entity_id: str, +async def test_legacy_config_entry_is_removed( + hass: HomeAssistant, entity_registry: er.EntityRegistry, no_sensor, wavertree_data ) -> None: - """Test multiple forecast.""" - client = await hass_ws_client(hass) - + """Test the expected entities are created.""" # Pre-create the hourly entity entity_registry.async_get_or_create( WEATHER_DOMAIN, @@ -551,47 +400,75 @@ async def test_forecast_subscription( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - for forecast_type in ("daily", "hourly"): - await client.send_json_auto_id( - { - "type": "weather/subscribe_forecast", - "forecast_type": forecast_type, - "entity_id": entity_id, - } - ) - msg = await client.receive_json() - assert msg["success"] - assert msg["result"] is None - subscription_id = msg["id"] + assert len(hass.states.async_entity_ids("weather")) == 1 + entry = hass.config_entries.async_entries()[0] + assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 1 - msg = await client.receive_json() - assert msg["id"] == subscription_id - assert msg["type"] == "event" - forecast1 = msg["event"]["forecast"] - assert forecast1 != [] - assert forecast1 == snapshot +@pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) +@pytest.mark.parametrize("forecast_type", ["daily", "hourly"]) +async def test_forecast_subscription( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, + no_sensor, + wavertree_data: dict[str, _Matcher], + forecast_type: str, +) -> None: + """Test multiple forecast.""" + client = await hass_ws_client(hass) - freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(seconds=1)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - msg = await client.receive_json() + entry = MockConfigEntry( + domain=DOMAIN, + data=METOFFICE_CONFIG_WAVERTREE, + ) + entry.add_to_hass(hass) - assert msg["id"] == subscription_id - assert msg["type"] == "event" - forecast2 = msg["event"]["forecast"] + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() - assert forecast2 != [] - assert forecast2 == snapshot + await client.send_json_auto_id( + { + "type": "weather/subscribe_forecast", + "forecast_type": forecast_type, + "entity_id": "weather.met_office_wavertree_daily", + } + ) + msg = await client.receive_json() + assert msg["success"] + assert msg["result"] is None + subscription_id = msg["id"] - await client.send_json_auto_id( - { - "type": "unsubscribe_events", - "subscription": subscription_id, - } - ) - freezer.tick(timedelta(seconds=1)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - msg = await client.receive_json() - assert msg["success"] + msg = await client.receive_json() + assert msg["id"] == subscription_id + assert msg["type"] == "event" + forecast1 = msg["event"]["forecast"] + + assert forecast1 != [] + assert forecast1 == snapshot + + freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(seconds=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + msg = await client.receive_json() + + assert msg["id"] == subscription_id + assert msg["type"] == "event" + forecast2 = msg["event"]["forecast"] + + assert forecast2 != [] + assert forecast2 == snapshot + + await client.send_json_auto_id( + { + "type": "unsubscribe_events", + "subscription": subscription_id, + } + ) + freezer.tick(timedelta(seconds=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + msg = await client.receive_json() + assert msg["success"] From 44eeb2eb5efd7b60aa6cf4b7be6972f3eb2e7eb2 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:19:20 +0100 Subject: [PATCH 1554/1691] Allow Humidifier.current_humidity to be a float (#111297) * Allow Humidifier.current_humidity to be a float * Code review * Allow climate humidity values to be float * Update demo integration --- homeassistant/components/climate/__init__.py | 14 ++++++------ .../components/comelit/humidifier.py | 4 ++-- homeassistant/components/demo/climate.py | 12 +++++----- homeassistant/components/demo/humidifier.py | 8 +++---- .../components/generic_hygrostat/__init__.py | 6 ++--- .../generic_hygrostat/humidifier.py | 22 +++++++++---------- .../components/homekit_controller/climate.py | 4 ++-- .../components/humidifier/__init__.py | 20 ++++++++--------- pylint/plugins/hass_enforce_type_hints.py | 14 ++++++------ tests/components/demo/test_climate.py | 10 ++++----- tests/components/demo/test_humidifier.py | 10 ++++----- .../google_assistant/test_google_assistant.py | 8 +++---- 12 files changed, 66 insertions(+), 66 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index d96dd163f41..87327945c68 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -292,9 +292,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_hvac_mode: HVACMode | None _attr_hvac_modes: list[HVACMode] _attr_is_aux_heat: bool | None - _attr_max_humidity: int = DEFAULT_MAX_HUMIDITY + _attr_max_humidity: float = DEFAULT_MAX_HUMIDITY _attr_max_temp: float - _attr_min_humidity: int = DEFAULT_MIN_HUMIDITY + _attr_min_humidity: float = DEFAULT_MIN_HUMIDITY _attr_min_temp: float _attr_precision: float _attr_preset_mode: str | None @@ -302,7 +302,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_supported_features: ClimateEntityFeature = ClimateEntityFeature(0) _attr_swing_mode: str | None _attr_swing_modes: list[str] | None - _attr_target_humidity: int | None = None + _attr_target_humidity: float | None = None _attr_target_temperature_high: float | None _attr_target_temperature_low: float | None _attr_target_temperature_step: float | None = None @@ -517,12 +517,12 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): return self._attr_temperature_unit @cached_property - def current_humidity(self) -> int | None: + def current_humidity(self) -> float | None: """Return the current humidity.""" return self._attr_current_humidity @cached_property - def target_humidity(self) -> int | None: + def target_humidity(self) -> float | None: """Return the humidity we try to reach.""" return self._attr_target_humidity @@ -826,12 +826,12 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): return self._attr_max_temp @cached_property - def min_humidity(self) -> int: + def min_humidity(self) -> float: """Return the minimum humidity.""" return self._attr_min_humidity @cached_property - def max_humidity(self) -> int: + def max_humidity(self) -> float: """Return the maximum humidity.""" return self._attr_max_humidity diff --git a/homeassistant/components/comelit/humidifier.py b/homeassistant/components/comelit/humidifier.py index 97cd3beb168..e7857535c78 100644 --- a/homeassistant/components/comelit/humidifier.py +++ b/homeassistant/components/comelit/humidifier.py @@ -148,12 +148,12 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier return self._humidifier[3] == HumidifierComelitMode.AUTO @property - def target_humidity(self) -> int: + def target_humidity(self) -> float: """Set target humidity.""" return self._humidifier[4] / 10 @property - def current_humidity(self) -> int: + def current_humidity(self) -> float: """Return current humidity.""" return self._humidifier[0] / 10 diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index a7caede9b2d..ff0ed5746ca 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -57,8 +57,8 @@ async def async_setup_entry( preset=None, current_temperature=22, fan_mode="on_high", - target_humidity=67, - current_humidity=54, + target_humidity=67.4, + current_humidity=54.2, swing_mode="off", hvac_mode=HVACMode.COOL, hvac_action=HVACAction.COOLING, @@ -106,8 +106,8 @@ class DemoClimate(ClimateEntity): preset: str | None, current_temperature: float, fan_mode: str | None, - target_humidity: int | None, - current_humidity: int | None, + target_humidity: float | None, + current_humidity: float | None, swing_mode: str | None, hvac_mode: HVACMode, hvac_action: HVACAction | None, @@ -188,12 +188,12 @@ class DemoClimate(ClimateEntity): return self._target_temperature_low @property - def current_humidity(self) -> int | None: + def current_humidity(self) -> float | None: """Return the current humidity.""" return self._current_humidity @property - def target_humidity(self) -> int | None: + def target_humidity(self) -> float | None: """Return the humidity we try to reach.""" return self._target_humidity diff --git a/homeassistant/components/demo/humidifier.py b/homeassistant/components/demo/humidifier.py index 37c4f189f96..7245d96eaf0 100644 --- a/homeassistant/components/demo/humidifier.py +++ b/homeassistant/components/demo/humidifier.py @@ -36,8 +36,8 @@ async def async_setup_entry( DemoHumidifier( name="Dehumidifier", mode=None, - target_humidity=54, - current_humidity=59, + target_humidity=54.2, + current_humidity=59.4, action=HumidifierAction.DRYING, device_class=HumidifierDeviceClass.DEHUMIDIFIER, ), @@ -60,8 +60,8 @@ class DemoHumidifier(HumidifierEntity): self, name: str, mode: str | None, - target_humidity: int, - current_humidity: int | None = None, + target_humidity: float, + current_humidity: float | None = None, available_modes: list[str] | None = None, is_on: bool = True, action: HumidifierAction | None = None, diff --git a/homeassistant/components/generic_hygrostat/__init__.py b/homeassistant/components/generic_hygrostat/__init__.py index 3109e9bb563..467a9f0e0c5 100644 --- a/homeassistant/components/generic_hygrostat/__init__.py +++ b/homeassistant/components/generic_hygrostat/__init__.py @@ -36,13 +36,13 @@ HYGROSTAT_SCHEMA = vol.Schema( vol.Optional(CONF_DEVICE_CLASS): vol.In( [HumidifierDeviceClass.HUMIDIFIER, HumidifierDeviceClass.DEHUMIDIFIER] ), - vol.Optional(CONF_MAX_HUMIDITY): vol.Coerce(int), + vol.Optional(CONF_MAX_HUMIDITY): vol.Coerce(float), vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_MIN_HUMIDITY): vol.Coerce(int), + vol.Optional(CONF_MIN_HUMIDITY): vol.Coerce(float), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_DRY_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float), vol.Optional(CONF_WET_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float), - vol.Optional(CONF_TARGET_HUMIDITY): vol.Coerce(int), + vol.Optional(CONF_TARGET_HUMIDITY): vol.Coerce(float), vol.Optional(CONF_KEEP_ALIVE): vol.All(cv.time_period, cv.positive_timedelta), vol.Optional(CONF_INITIAL_STATE): cv.boolean, vol.Optional(CONF_AWAY_HUMIDITY): vol.Coerce(int), diff --git a/homeassistant/components/generic_hygrostat/humidifier.py b/homeassistant/components/generic_hygrostat/humidifier.py index fea00d5cc8b..02641acccae 100644 --- a/homeassistant/components/generic_hygrostat/humidifier.py +++ b/homeassistant/components/generic_hygrostat/humidifier.py @@ -85,9 +85,9 @@ async def async_setup_platform( name: str = config[CONF_NAME] switch_entity_id: str = config[CONF_HUMIDIFIER] sensor_entity_id: str = config[CONF_SENSOR] - min_humidity: int | None = config.get(CONF_MIN_HUMIDITY) - max_humidity: int | None = config.get(CONF_MAX_HUMIDITY) - target_humidity: int | None = config.get(CONF_TARGET_HUMIDITY) + min_humidity: float | None = config.get(CONF_MIN_HUMIDITY) + max_humidity: float | None = config.get(CONF_MAX_HUMIDITY) + target_humidity: float | None = config.get(CONF_TARGET_HUMIDITY) device_class: HumidifierDeviceClass | None = config.get(CONF_DEVICE_CLASS) min_cycle_duration: timedelta | None = config.get(CONF_MIN_DUR) sensor_stale_duration: timedelta | None = config.get(CONF_STALE_DURATION) @@ -133,9 +133,9 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): name: str, switch_entity_id: str, sensor_entity_id: str, - min_humidity: int | None, - max_humidity: int | None, - target_humidity: int | None, + min_humidity: float | None, + max_humidity: float | None, + target_humidity: float | None, device_class: HumidifierDeviceClass | None, min_cycle_duration: timedelta | None, dry_tolerance: float, @@ -264,12 +264,12 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): return self._state @property - def current_humidity(self) -> int | None: + def current_humidity(self) -> float | None: """Return the measured humidity.""" - return int(self._cur_humidity) if self._cur_humidity is not None else None + return self._cur_humidity @property - def target_humidity(self) -> int | None: + def target_humidity(self) -> float | None: """Return the humidity we try to reach.""" return self._target_humidity @@ -326,7 +326,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): self.async_write_ha_state() @property - def min_humidity(self) -> int: + def min_humidity(self) -> float: """Return the minimum humidity.""" if self._min_humidity: return self._min_humidity @@ -335,7 +335,7 @@ class GenericHygrostat(HumidifierEntity, RestoreEntity): return super().min_humidity @property - def max_humidity(self) -> int: + def max_humidity(self) -> float: """Return the maximum humidity.""" if self._max_humidity: return self._max_humidity diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index dda181b31ff..49ae3bb4a42 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -637,7 +637,7 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity): return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET) @property - def min_humidity(self) -> int: + def min_humidity(self) -> float: """Return the minimum humidity.""" min_humidity = self.service[ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET @@ -647,7 +647,7 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity): return super().min_humidity @property - def max_humidity(self) -> int: + def max_humidity(self) -> float: """Return the maximum humidity.""" max_humidity = self.service[ CharacteristicsTypes.RELATIVE_HUMIDITY_TARGET diff --git a/homeassistant/components/humidifier/__init__.py b/homeassistant/components/humidifier/__init__.py index bf3ce16fa92..1af294d8640 100644 --- a/homeassistant/components/humidifier/__init__.py +++ b/homeassistant/components/humidifier/__init__.py @@ -165,18 +165,18 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT entity_description: HumidifierEntityDescription _attr_action: HumidifierAction | None = None _attr_available_modes: list[str] | None - _attr_current_humidity: int | None = None + _attr_current_humidity: float | None = None _attr_device_class: HumidifierDeviceClass | None - _attr_max_humidity: int = DEFAULT_MAX_HUMIDITY - _attr_min_humidity: int = DEFAULT_MIN_HUMIDITY + _attr_max_humidity: float = DEFAULT_MAX_HUMIDITY + _attr_min_humidity: float = DEFAULT_MIN_HUMIDITY _attr_mode: str | None _attr_supported_features: HumidifierEntityFeature = HumidifierEntityFeature(0) - _attr_target_humidity: int | None = None + _attr_target_humidity: float | None = None @property def capability_attributes(self) -> dict[str, Any]: """Return capability attributes.""" - data: dict[str, int | list[str] | None] = { + data: dict[str, Any] = { ATTR_MIN_HUMIDITY: self.min_humidity, ATTR_MAX_HUMIDITY: self.max_humidity, } @@ -199,7 +199,7 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT @property def state_attributes(self) -> dict[str, Any]: """Return the optional state attributes.""" - data: dict[str, int | str | None] = {} + data: dict[str, Any] = {} if self.action is not None: data[ATTR_ACTION] = self.action if self.is_on else HumidifierAction.OFF @@ -221,12 +221,12 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT return self._attr_action @cached_property - def current_humidity(self) -> int | None: + def current_humidity(self) -> float | None: """Return the current humidity.""" return self._attr_current_humidity @cached_property - def target_humidity(self) -> int | None: + def target_humidity(self) -> float | None: """Return the humidity we try to reach.""" return self._attr_target_humidity @@ -263,12 +263,12 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT await self.hass.async_add_executor_job(self.set_mode, mode) @cached_property - def min_humidity(self) -> int: + def min_humidity(self) -> float: """Return the minimum humidity.""" return self._attr_min_humidity @cached_property - def max_humidity(self) -> int: + def max_humidity(self) -> float: """Return the maximum humidity.""" return self._attr_max_humidity diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 678cbb7a22a..4799a3a56fa 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -1025,11 +1025,11 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = { ), TypeHintMatch( function_name="current_humidity", - return_type=["int", None], + return_type=["float", None], ), TypeHintMatch( function_name="target_humidity", - return_type=["int", None], + return_type=["float", None], ), TypeHintMatch( function_name="hvac_mode", @@ -1171,11 +1171,11 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = { ), TypeHintMatch( function_name="min_humidity", - return_type="int", + return_type="float", ), TypeHintMatch( function_name="max_humidity", - return_type="int", + return_type="float", ), ], ), @@ -1549,11 +1549,11 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = { ), TypeHintMatch( function_name="min_humidity", - return_type=["int"], + return_type=["float"], ), TypeHintMatch( function_name="max_humidity", - return_type=["int"], + return_type=["float"], ), TypeHintMatch( function_name="mode", @@ -1565,7 +1565,7 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = { ), TypeHintMatch( function_name="target_humidity", - return_type=["int", None], + return_type=["float", None], ), TypeHintMatch( function_name="set_humidity", diff --git a/tests/components/demo/test_climate.py b/tests/components/demo/test_climate.py index dc2bdc3c720..ff18f9e6a4e 100644 --- a/tests/components/demo/test_climate.py +++ b/tests/components/demo/test_climate.py @@ -74,8 +74,8 @@ def test_setup_params(hass: HomeAssistant) -> None: assert state.attributes.get(ATTR_TEMPERATURE) == 21 assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 22 assert state.attributes.get(ATTR_FAN_MODE) == "on_high" - assert state.attributes.get(ATTR_HUMIDITY) == 67 - assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 54 + assert state.attributes.get(ATTR_HUMIDITY) == 67.4 + assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 54.2 assert state.attributes.get(ATTR_SWING_MODE) == "off" assert state.attributes.get(ATTR_HVAC_MODES) == [ HVACMode.OFF, @@ -219,7 +219,7 @@ async def test_set_temp_with_hvac_mode(hass: HomeAssistant) -> None: async def test_set_target_humidity_bad_attr(hass: HomeAssistant) -> None: """Test setting the target humidity without required attribute.""" state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_HUMIDITY) == 67 + assert state.attributes.get(ATTR_HUMIDITY) == 67.4 with pytest.raises(vol.Invalid): await hass.services.async_call( @@ -230,13 +230,13 @@ async def test_set_target_humidity_bad_attr(hass: HomeAssistant) -> None: ) state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_HUMIDITY) == 67 + assert state.attributes.get(ATTR_HUMIDITY) == 67.4 async def test_set_target_humidity(hass: HomeAssistant) -> None: """Test the setting of the target humidity.""" state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get(ATTR_HUMIDITY) == 67 + assert state.attributes.get(ATTR_HUMIDITY) == 67.4 await hass.services.async_call( DOMAIN, diff --git a/tests/components/demo/test_humidifier.py b/tests/components/demo/test_humidifier.py index 97647f0a90f..0f0fcaf43fd 100644 --- a/tests/components/demo/test_humidifier.py +++ b/tests/components/demo/test_humidifier.py @@ -57,8 +57,8 @@ def test_setup_params(hass: HomeAssistant) -> None: """Test the initial parameters.""" state = hass.states.get(ENTITY_DEHUMIDIFIER) assert state.state == STATE_ON - assert state.attributes.get(ATTR_HUMIDITY) == 54 - assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 59 + assert state.attributes.get(ATTR_HUMIDITY) == 54.2 + assert state.attributes.get(ATTR_CURRENT_HUMIDITY) == 59.4 assert state.attributes.get(ATTR_ACTION) == "drying" @@ -72,7 +72,7 @@ def test_default_setup_params(hass: HomeAssistant) -> None: async def test_set_target_humidity_bad_attr(hass: HomeAssistant) -> None: """Test setting the target humidity without required attribute.""" state = hass.states.get(ENTITY_DEHUMIDIFIER) - assert state.attributes.get(ATTR_HUMIDITY) == 54 + assert state.attributes.get(ATTR_HUMIDITY) == 54.2 with pytest.raises(vol.Invalid): await hass.services.async_call( @@ -84,13 +84,13 @@ async def test_set_target_humidity_bad_attr(hass: HomeAssistant) -> None: await hass.async_block_till_done() state = hass.states.get(ENTITY_DEHUMIDIFIER) - assert state.attributes.get(ATTR_HUMIDITY) == 54 + assert state.attributes.get(ATTR_HUMIDITY) == 54.2 async def test_set_target_humidity(hass: HomeAssistant) -> None: """Test the setting of the target humidity.""" state = hass.states.get(ENTITY_DEHUMIDIFIER) - assert state.attributes.get(ATTR_HUMIDITY) == 54 + assert state.attributes.get(ATTR_HUMIDITY) == 54.2 await hass.services.async_call( DOMAIN, diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py index 94936eaaf3d..4198e648b53 100644 --- a/tests/components/google_assistant/test_google_assistant.py +++ b/tests/components/google_assistant/test_google_assistant.py @@ -254,7 +254,7 @@ async def test_query_climate_request( "thermostatTemperatureSetpoint": 21, "thermostatTemperatureAmbient": 22, "thermostatMode": "cool", - "thermostatHumidityAmbient": 54, + "thermostatHumidityAmbient": 54.2, "currentFanSpeedSetting": "on_high", } @@ -318,7 +318,7 @@ async def test_query_climate_request_f( "thermostatTemperatureSetpoint": -6.1, "thermostatTemperatureAmbient": -5.6, "thermostatMode": "cool", - "thermostatHumidityAmbient": 54, + "thermostatHumidityAmbient": 54.2, "currentFanSpeedSetting": "on_high", } hass_fixture.config.units.temperature_unit = UnitOfTemperature.CELSIUS @@ -363,8 +363,8 @@ async def test_query_humidifier_request( assert devices["humidifier.dehumidifier"] == { "on": True, "online": True, - "humiditySetpointPercent": 54, - "humidityAmbientPercent": 59, + "humiditySetpointPercent": 54.2, + "humidityAmbientPercent": 59.4, } assert devices["humidifier.hygrostat"] == { "on": True, From 5c97049f2ecec169ecf997d09f1b5e8d38769fb2 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 27 Mar 2024 11:36:00 +0100 Subject: [PATCH 1555/1691] Do not override state property in `MockEntity` (#114231) Do not override state in `MockEntity` --- tests/common.py | 7 +------ tests/helpers/test_entity_platform.py | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/common.py b/tests/common.py index faf14a55f01..a7d4cf6b83a 100644 --- a/tests/common.py +++ b/tests/common.py @@ -83,7 +83,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.json import JSONEncoder, _orjson_default_encoder, json_dumps -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.setup import setup_component from homeassistant.util.async_ import run_callback_threadsafe import homeassistant.util.dt as dt_util @@ -1291,11 +1291,6 @@ class MockEntity(entity.Entity): """Return the ste of the polling.""" return self._handle("should_poll") - @property - def state(self) -> StateType: - """Return the state of the entity.""" - return self._handle("state") - @property def supported_features(self) -> int | None: """Info about supported features.""" diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 17b5d634018..31c6f8e6e30 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -1432,9 +1432,9 @@ async def test_override_restored_entities( component = EntityComponent(_LOGGER, DOMAIN, hass) await component.async_setup({}) - await component.async_add_entities( - [MockEntity(unique_id="1234", state="on", entity_id="test_domain.world")], True - ) + ent = MockEntity(unique_id="1234", entity_id="test_domain.world") + ent._attr_state = "on" + await component.async_add_entities([ent], True) state = hass.states.get("test_domain.world") assert state.state == "on" From 3929273b416c45f5bf21f6e58ac7865bae3d173b Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:51:27 +0100 Subject: [PATCH 1556/1691] Allow float for int argument type [pylint plugin] (#114105) --- .../components/xiaomi_miio/humidifier.py | 8 ++--- homeassistant/util/percentage.py | 2 +- pylint/plugins/hass_enforce_type_hints.py | 9 +++++ tests/pylint/test_enforce_type_hints.py | 33 +++++++++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/humidifier.py b/homeassistant/components/xiaomi_miio/humidifier.py index 83eb84f62ce..8367b063102 100644 --- a/homeassistant/components/xiaomi_miio/humidifier.py +++ b/homeassistant/components/xiaomi_miio/humidifier.py @@ -159,7 +159,7 @@ class XiaomiGenericHumidifier(XiaomiCoordinatedMiioEntity, HumidifierEntity): self._state = False self.async_write_ha_state() - def translate_humidity(self, humidity): + def translate_humidity(self, humidity: float) -> float | None: """Translate the target humidity to the first valid step.""" return ( math.ceil(percentage_to_ranged_value((1, self._humidity_steps), humidity)) @@ -240,7 +240,7 @@ class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity): else None ) - async def async_set_humidity(self, humidity: int) -> None: + async def async_set_humidity(self, humidity: float) -> None: """Set the target humidity of the humidifier and set the mode to auto.""" target_humidity = self.translate_humidity(humidity) if not target_humidity: @@ -318,7 +318,7 @@ class XiaomiAirHumidifierMiot(XiaomiAirHumidifier): ) return None - async def async_set_humidity(self, humidity: int) -> None: + async def async_set_humidity(self, humidity: float) -> None: """Set the target humidity of the humidifier and set the mode to auto.""" target_humidity = self.translate_humidity(humidity) if not target_humidity: @@ -393,7 +393,7 @@ class XiaomiAirHumidifierMjjsq(XiaomiAirHumidifier): return self._target_humidity return None - async def async_set_humidity(self, humidity: int) -> None: + async def async_set_humidity(self, humidity: float) -> None: """Set the target humidity of the humidifier and set the mode to Humidity.""" target_humidity = self.translate_humidity(humidity) if not target_humidity: diff --git a/homeassistant/util/percentage.py b/homeassistant/util/percentage.py index 5f2a1193d96..e01af5400f4 100644 --- a/homeassistant/util/percentage.py +++ b/homeassistant/util/percentage.py @@ -81,7 +81,7 @@ def ranged_value_to_percentage( def percentage_to_ranged_value( - low_high_range: tuple[float, float], percentage: int + low_high_range: tuple[float, float], percentage: float ) -> float: """Given a range of low and high values convert a percentage to a single value. diff --git a/pylint/plugins/hass_enforce_type_hints.py b/pylint/plugins/hass_enforce_type_hints.py index 4799a3a56fa..7d48fa4b2e3 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -2968,6 +2968,15 @@ def _is_valid_type( ): return True + # Special case for int in argument type + if ( + expected_type == "int" + and not in_return + and isinstance(node, nodes.Name) + and node.name in ("float", "int") + ): + return True + # Name occurs when a namespace is not used, eg. "HomeAssistant" if isinstance(node, nodes.Name) and node.name == expected_type: return True diff --git a/tests/pylint/test_enforce_type_hints.py b/tests/pylint/test_enforce_type_hints.py index d3b7efae19b..78eb682200a 100644 --- a/tests/pylint/test_enforce_type_hints.py +++ b/tests/pylint/test_enforce_type_hints.py @@ -989,6 +989,39 @@ def test_media_player_entity( type_hint_checker.visit_classdef(class_node) +def test_humidifier_entity( + linter: UnittestLinter, type_hint_checker: BaseChecker +) -> None: + """Ensure valid hints are accepted for humidifier entity.""" + # Set bypass option + type_hint_checker.linter.config.ignore_missing_annotations = False + + # Ensure that `float` and `int` are valid for `int` argument type + class_node = astroid.extract_node( + """ + class Entity(): + pass + + class HumidifierEntity(Entity): + pass + + class MyHumidifier( #@ + HumidifierEntity + ): + def set_humidity(self, humidity: int) -> None: + pass + + def async_set_humidity(self, humidity: float) -> None: + pass + """, + "homeassistant.components.pylint_test.humidifier", + ) + type_hint_checker.visit_module(class_node.parent) + + with assert_no_messages(linter): + type_hint_checker.visit_classdef(class_node) + + def test_number_entity(linter: UnittestLinter, type_hint_checker: BaseChecker) -> None: """Ensure valid hints are accepted for number entity.""" # Set bypass option From 34cf0c5721a1d14dd96025f784ea83de07104766 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 27 Mar 2024 12:20:19 +0100 Subject: [PATCH 1557/1691] Use `setup_test_component_platform` func for cover entity component tests instead of `hass.components` (#114010) * Use `mock_platform` for cover entity component tests instead of `hass.components` * Remove setup fixture and use helper function * Remove is_on from MockCover * Run ruff * Do not override state in `MockCover` in cover tests * Remove is_on from MockCover --- tests/components/cover/common.py | 77 ++++++++ tests/components/cover/conftest.py | 67 +++++++ tests/components/cover/test_device_action.py | 80 ++++----- .../components/cover/test_device_condition.py | 45 +++-- tests/components/cover/test_device_trigger.py | 64 ++++--- tests/components/cover/test_init.py | 17 +- .../custom_components/test/cover.py | 165 ------------------ 7 files changed, 241 insertions(+), 274 deletions(-) create mode 100644 tests/components/cover/common.py create mode 100644 tests/components/cover/conftest.py delete mode 100644 tests/testing_config/custom_components/test/cover.py diff --git a/tests/components/cover/common.py b/tests/components/cover/common.py new file mode 100644 index 00000000000..d9f67e73f17 --- /dev/null +++ b/tests/components/cover/common.py @@ -0,0 +1,77 @@ +"""Collection of helper methods and classes for cover tests.""" + +from typing import Any + +from homeassistant.components.cover import CoverEntity, CoverEntityFeature +from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING + +from tests.common import MockEntity + + +class MockCover(MockEntity, CoverEntity): + """Mock Cover class.""" + + def __init__( + self, reports_opening_closing: bool | None = None, **values: Any + ) -> None: + """Initialize a mock cover entity.""" + + super().__init__(**values) + self._reports_opening_closing = ( + reports_opening_closing + if reports_opening_closing is not None + else CoverEntityFeature.STOP in self.supported_features + ) + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + if "state" in self._values and self._values["state"] == STATE_CLOSED: + return True + + return self.current_cover_position == 0 + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + if "state" in self._values: + return self._values["state"] == STATE_OPENING + + return False + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + if "state" in self._values: + return self._values["state"] == STATE_CLOSING + + return False + + def open_cover(self, **kwargs) -> None: + """Open cover.""" + if self._reports_opening_closing: + self._values["state"] = STATE_OPENING + else: + self._values["state"] = STATE_OPEN + + def close_cover(self, **kwargs) -> None: + """Close cover.""" + if self._reports_opening_closing: + self._values["state"] = STATE_CLOSING + else: + self._values["state"] = STATE_CLOSED + + def stop_cover(self, **kwargs) -> None: + """Stop cover.""" + assert CoverEntityFeature.STOP in self.supported_features + self._values["state"] = STATE_CLOSED if self.is_closed else STATE_OPEN + + @property + def current_cover_position(self): + """Return current position of cover.""" + return self._handle("current_cover_position") + + @property + def current_cover_tilt_position(self): + """Return current position of cover tilt.""" + return self._handle("current_cover_tilt_position") diff --git a/tests/components/cover/conftest.py b/tests/components/cover/conftest.py new file mode 100644 index 00000000000..1fc0de1fc2e --- /dev/null +++ b/tests/components/cover/conftest.py @@ -0,0 +1,67 @@ +"""Fixtures for cover entity components tests.""" + +import pytest + +from homeassistant.components.cover import CoverEntityFeature + +from .common import MockCover + + +@pytest.fixture +def mock_cover_entities() -> list[MockCover]: + """Return a list of MockCover instances.""" + return [ + MockCover( + name="Simple cover", + unique_id="unique_cover", + supported_features=CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE, + ), + MockCover( + name="Set position cover", + unique_id="unique_set_pos_cover", + current_cover_position=50, + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION, + ), + MockCover( + name="Simple tilt cover", + unique_id="unique_tilt_cover", + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT, + ), + MockCover( + name="Set tilt position cover", + unique_id="unique_set_pos_tilt_cover", + current_cover_tilt_position=50, + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION, + ), + MockCover( + name="All functions cover", + unique_id="unique_all_functions_cover", + current_cover_position=50, + current_cover_tilt_position=50, + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.SET_POSITION + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT + | CoverEntityFeature.SET_TILT_POSITION, + ), + MockCover( + name="Simple with opening/closing cover", + unique_id="unique_opening_closing_cover", + supported_features=CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE, + reports_opening_closing=True, + ), + ] diff --git a/tests/components/cover/test_device_action.py b/tests/components/cover/test_device_action.py index 089b5b9052a..43bf7431626 100644 --- a/tests/components/cover/test_device_action.py +++ b/tests/components/cover/test_device_action.py @@ -17,7 +17,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) +from tests.components.cover.common import MockCover @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -145,26 +147,20 @@ async def test_get_action_capabilities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, ) -> None: """Test we get the expected capabilities from a cover action.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockCover( - name="Set position cover", - is_on=True, - unique_id="unique_set_pos_cover", - current_cover_position=50, - supported_features=CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.STOP - | CoverEntityFeature.OPEN_TILT - | CoverEntityFeature.CLOSE_TILT - | CoverEntityFeature.STOP_TILT, - ), + ent = MockCover( + name="Set position cover", + unique_id="unique_set_pos_cover", + current_cover_position=50, + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT, ) - ent = platform.ENTITIES[0] + setup_test_component_platform(hass, DOMAIN, [ent]) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -195,26 +191,20 @@ async def test_get_action_capabilities_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, ) -> None: """Test we get the expected capabilities from a cover action.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init(empty=True) - platform.ENTITIES.append( - platform.MockCover( - name="Set position cover", - is_on=True, - unique_id="unique_set_pos_cover", - current_cover_position=50, - supported_features=CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.STOP - | CoverEntityFeature.OPEN_TILT - | CoverEntityFeature.CLOSE_TILT - | CoverEntityFeature.STOP_TILT, - ), + ent = MockCover( + name="Set position cover", + unique_id="unique_set_pos_cover", + current_cover_position=50, + supported_features=CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + | CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.STOP_TILT, ) - ent = platform.ENTITIES[0] + setup_test_component_platform(hass, DOMAIN, [ent]) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -246,12 +236,11 @@ async def test_get_action_capabilities_set_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover action.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[1] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -297,12 +286,11 @@ async def test_get_action_capabilities_set_tilt_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover action.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[3] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[3] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -354,7 +342,7 @@ async def test_action( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for cover actions.""" config_entry = MockConfigEntry(domain="test", data={}) @@ -441,7 +429,7 @@ async def test_action_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for cover actions.""" config_entry = MockConfigEntry(domain="test", data={}) @@ -488,7 +476,7 @@ async def test_action_tilt( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for cover tilt actions.""" config_entry = MockConfigEntry(domain="test", data={}) @@ -559,7 +547,7 @@ async def test_action_set_position( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for cover set position actions.""" config_entry = MockConfigEntry(domain="test", data={}) diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index 781dbfb107a..a58f94f44f3 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -25,7 +25,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) +from tests.components.cover.common import MockCover @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -174,12 +176,11 @@ async def test_get_condition_capabilities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[0] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[0] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -208,12 +209,11 @@ async def test_get_condition_capabilities_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[0] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[0] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -245,12 +245,11 @@ async def test_get_condition_capabilities_set_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[1] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -302,12 +301,12 @@ async def test_get_condition_capabilities_set_tilt_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[3] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + + ent = mock_cover_entities[3] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -560,12 +559,11 @@ async def test_if_position( entity_registry: er.EntityRegistry, calls, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for position conditions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[1] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -721,12 +719,11 @@ async def test_if_tilt_position( entity_registry: er.EntityRegistry, calls, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for tilt position conditions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[3] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[3] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index 33ddef80d07..5db52b6d618 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -28,7 +28,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) +from tests.components.cover.common import MockCover @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -175,12 +177,11 @@ async def test_get_trigger_capabilities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[0] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[0] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -213,12 +214,11 @@ async def test_get_trigger_capabilities_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[0] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[0] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -252,12 +252,11 @@ async def test_get_trigger_capabilities_set_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[1] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -317,12 +316,11 @@ async def test_get_trigger_capabilities_set_tilt_pos( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test we get the expected capabilities from a cover trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[3] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[3] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -663,13 +661,12 @@ async def test_if_fires_on_position( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + mock_cover_entities: list[MockCover], calls, - enable_custom_integrations: None, ) -> None: """Test for position triggers.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[1] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -780,11 +777,11 @@ async def test_if_fires_on_position( ) == sorted( [ ( - "is_pos_gt_45_lt_90 - device - cover.set_position_cover - closed - open" + f"is_pos_gt_45_lt_90 - device - {entry.entity_id} - closed - open" " - None" ), - "is_pos_lt_90 - device - cover.set_position_cover - closed - open - None", - "is_pos_gt_45 - device - cover.set_position_cover - open - closed - None", + f"is_pos_lt_90 - device - {entry.entity_id} - closed - open - None", + f"is_pos_gt_45 - device - {entry.entity_id} - open - closed - None", ] ) @@ -799,7 +796,7 @@ async def test_if_fires_on_position( assert len(calls) == 4 assert ( calls[3].data["some"] - == "is_pos_lt_90 - device - cover.set_position_cover - closed - closed - None" + == f"is_pos_lt_90 - device - {entry.entity_id} - closed - closed - None" ) hass.states.async_set( @@ -809,7 +806,7 @@ async def test_if_fires_on_position( assert len(calls) == 5 assert ( calls[4].data["some"] - == "is_pos_gt_45 - device - cover.set_position_cover - closed - closed - None" + == f"is_pos_gt_45 - device - {entry.entity_id} - closed - closed - None" ) @@ -818,12 +815,11 @@ async def test_if_fires_on_tilt_position( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_cover_entities: list[MockCover], ) -> None: """Test for tilt position triggers.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() - ent = platform.ENTITIES[1] + setup_test_component_platform(hass, DOMAIN, mock_cover_entities) + ent = mock_cover_entities[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -936,11 +932,11 @@ async def test_if_fires_on_tilt_position( ) == sorted( [ ( - "is_pos_gt_45_lt_90 - device - cover.set_position_cover - closed - open" + f"is_pos_gt_45_lt_90 - device - {entry.entity_id} - closed - open" " - None" ), - "is_pos_lt_90 - device - cover.set_position_cover - closed - open - None", - "is_pos_gt_45 - device - cover.set_position_cover - open - closed - None", + f"is_pos_lt_90 - device - {entry.entity_id} - closed - open - None", + f"is_pos_gt_45 - device - {entry.entity_id} - open - closed - None", ] ) @@ -955,7 +951,7 @@ async def test_if_fires_on_tilt_position( assert len(calls) == 4 assert ( calls[3].data["some"] - == "is_pos_lt_90 - device - cover.set_position_cover - closed - closed - None" + == f"is_pos_lt_90 - device - {entry.entity_id} - closed - closed - None" ) hass.states.async_set( @@ -965,5 +961,5 @@ async def test_if_fires_on_tilt_position( assert len(calls) == 5 assert ( calls[4].data["some"] - == "is_pos_gt_45 - device - cover.set_position_cover - closed - closed - None" + == f"is_pos_gt_45 - device - {entry.entity_id} - closed - closed - None" ) diff --git a/tests/components/cover/test_init.py b/tests/components/cover/test_init.py index 5d6b40a171f..ec090b878f2 100644 --- a/tests/components/cover/test_init.py +++ b/tests/components/cover/test_init.py @@ -17,14 +17,21 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import help_test_all, import_and_test_deprecated_constant_enum +from tests.common import ( + help_test_all, + import_and_test_deprecated_constant_enum, + setup_test_component_platform, +) +from tests.components.cover.common import MockCover -async def test_services(hass: HomeAssistant, enable_custom_integrations: None) -> None: +async def test_services( + hass: HomeAssistant, + mock_cover_entities: list[MockCover], +) -> None: """Test the provided services.""" - platform = getattr(hass.components, "test.cover") + setup_test_component_platform(hass, cover.DOMAIN, mock_cover_entities) - platform.init() assert await async_setup_component( hass, cover.DOMAIN, {cover.DOMAIN: {CONF_PLATFORM: "test"}} ) @@ -36,7 +43,7 @@ async def test_services(hass: HomeAssistant, enable_custom_integrations: None) - # ent4 = cover with all tilt functions but no position # ent5 = cover with all functions # ent6 = cover with only open/close, but also reports opening/closing - ent1, ent2, ent3, ent4, ent5, ent6 = platform.ENTITIES + ent1, ent2, ent3, ent4, ent5, ent6 = mock_cover_entities # Test init all covers should be open assert is_open(hass, ent1) diff --git a/tests/testing_config/custom_components/test/cover.py b/tests/testing_config/custom_components/test/cover.py deleted file mode 100644 index 66ecd1c9dbc..00000000000 --- a/tests/testing_config/custom_components/test/cover.py +++ /dev/null @@ -1,165 +0,0 @@ -"""Provide a mock cover platform. - -Call init before using it in your tests to ensure clean test data. -""" - -from typing import Any - -from homeassistant.components.cover import CoverEntity, CoverEntityFeature -from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING - -from tests.common import MockEntity - -ENTITIES = [] - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - [] - if empty - else [ - MockCover( - name="Simple cover", - is_on=True, - unique_id="unique_cover", - supported_features=CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE, - ), - MockCover( - name="Set position cover", - is_on=True, - unique_id="unique_set_pos_cover", - current_cover_position=50, - supported_features=CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.STOP - | CoverEntityFeature.SET_POSITION, - ), - MockCover( - name="Simple tilt cover", - is_on=True, - unique_id="unique_tilt_cover", - supported_features=CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.OPEN_TILT - | CoverEntityFeature.CLOSE_TILT, - ), - MockCover( - name="Set tilt position cover", - is_on=True, - unique_id="unique_set_pos_tilt_cover", - current_cover_tilt_position=50, - supported_features=CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.OPEN_TILT - | CoverEntityFeature.CLOSE_TILT - | CoverEntityFeature.STOP_TILT - | CoverEntityFeature.SET_TILT_POSITION, - ), - MockCover( - name="All functions cover", - is_on=True, - unique_id="unique_all_functions_cover", - current_cover_position=50, - current_cover_tilt_position=50, - supported_features=CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.STOP - | CoverEntityFeature.SET_POSITION - | CoverEntityFeature.OPEN_TILT - | CoverEntityFeature.CLOSE_TILT - | CoverEntityFeature.STOP_TILT - | CoverEntityFeature.SET_TILT_POSITION, - ), - MockCover( - name="Simple with opening/closing cover", - is_on=True, - unique_id="unique_opening_closing_cover", - supported_features=CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE, - reports_opening_closing=True, - ), - ] - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(ENTITIES) - - -class MockCover(MockEntity, CoverEntity): - """Mock Cover class.""" - - def __init__( - self, reports_opening_closing: bool | None = None, **values: Any - ) -> None: - """Initialize a mock cover entity.""" - - super().__init__(**values) - self._reports_opening_closing = ( - reports_opening_closing - if reports_opening_closing is not None - else CoverEntityFeature.STOP in self.supported_features - ) - - @property - def is_closed(self): - """Return if the cover is closed or not.""" - if "state" in self._values and self._values["state"] == STATE_CLOSED: - return True - - return self.current_cover_position == 0 - - @property - def is_opening(self): - """Return if the cover is opening or not.""" - if "state" in self._values: - return self._values["state"] == STATE_OPENING - - return False - - @property - def is_closing(self): - """Return if the cover is closing or not.""" - if "state" in self._values: - return self._values["state"] == STATE_CLOSING - - return False - - def open_cover(self, **kwargs) -> None: - """Open cover.""" - if self._reports_opening_closing: - self._values["state"] = STATE_OPENING - else: - self._values["state"] = STATE_OPEN - - def close_cover(self, **kwargs) -> None: - """Close cover.""" - if self._reports_opening_closing: - self._values["state"] = STATE_CLOSING - else: - self._values["state"] = STATE_CLOSED - - def stop_cover(self, **kwargs) -> None: - """Stop cover.""" - assert CoverEntityFeature.STOP in self.supported_features - self._values["state"] = STATE_CLOSED if self.is_closed else STATE_OPEN - - @property - def state(self): - """Fake State.""" - return CoverEntity.state.fget(self) - - @property - def current_cover_position(self): - """Return current position of cover.""" - return self._handle("current_cover_position") - - @property - def current_cover_tilt_position(self): - """Return current position of cover tilt.""" - return self._handle("current_cover_tilt_position") From 24168dfba79ddedd966f27e900ad68abd4da6570 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:32:29 +0100 Subject: [PATCH 1558/1691] Update mypy-dev to 1.10.0a3 (#114289) * Update mypy-dev to 1.10.0a3 * Allow TypedDict init from Type --- homeassistant/data_entry_flow.py | 4 ++-- requirements_test.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index ac93851bbda..6a1453c9ff3 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -194,7 +194,7 @@ def _map_error_to_schema_errors( class FlowManager(abc.ABC, Generic[_FlowResultT, _HandlerT]): """Manage all the flows that are in progress.""" - _flow_result: Callable[..., _FlowResultT] = FlowResult # type: ignore[assignment] + _flow_result: type[_FlowResultT] = FlowResult # type: ignore[assignment] def __init__( self, @@ -615,7 +615,7 @@ class FlowManager(abc.ABC, Generic[_FlowResultT, _HandlerT]): class FlowHandler(Generic[_FlowResultT, _HandlerT]): """Handle a data entry flow.""" - _flow_result: Callable[..., _FlowResultT] = FlowResult # type: ignore[assignment] + _flow_result: type[_FlowResultT] = FlowResult # type: ignore[assignment] # Set by flow manager cur_step: _FlowResultT | None = None diff --git a/requirements_test.txt b/requirements_test.txt index 84e674b7109..4dd02246a6e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -11,7 +11,7 @@ astroid==3.1.0 coverage==7.4.4 freezegun==1.4.0 mock-open==1.4.0 -mypy-dev==1.9.0b1 +mypy-dev==1.10.0a3 pre-commit==3.7.0 pydantic==1.10.12 pylint==3.1.0 From a3059fe504b46341219d80578fa32900a92b34bd Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 27 Mar 2024 12:48:06 +0100 Subject: [PATCH 1559/1691] Record state.last_reported (#114201) * Record state.last_reported * Include last_reported in parts of the history API * Use a bulk update * fix refactoring error --------- Co-authored-by: J. Nick Koston --- homeassistant/components/recorder/core.py | 34 ++++++-- .../components/recorder/db_schema.py | 23 ++++-- .../components/recorder/history/modern.py | 47 ++++++++--- .../components/recorder/migration.py | 6 ++ .../components/recorder/models/legacy.py | 25 ++++++ .../components/recorder/models/state.py | 12 +++ .../recorder/table_managers/states.py | 12 +++ .../auto_repairs/states/test_schema.py | 1 + tests/components/recorder/common.py | 1 + tests/components/recorder/db_schema_30.py | 3 + tests/components/recorder/db_schema_32.py | 3 + tests/components/recorder/test_history.py | 79 ++++++++++++++++++- tests/components/recorder/test_util.py | 2 +- 13 files changed, 225 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 21d42405b75..7de9cf46311 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -15,7 +15,7 @@ import time from typing import TYPE_CHECKING, Any, TypeVar, cast import psutil_home_assistant as ha_psutil -from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select +from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select, update from sqlalchemy.engine import Engine from sqlalchemy.engine.interfaces import DBAPIConnection from sqlalchemy.exc import SQLAlchemyError @@ -1090,12 +1090,22 @@ class Recorder(threading.Thread): entity_id = event.data["entity_id"] dbstate = States.from_event(event) + old_state = event.data["old_state"] + + assert self.event_session is not None + session = self.event_session states_manager = self.states_manager - if old_state := states_manager.pop_pending(entity_id): - dbstate.old_state = old_state + if pending_state := states_manager.pop_pending(entity_id): + dbstate.old_state = pending_state + if old_state: + pending_state.last_reported_ts = old_state.last_reported_timestamp elif old_state_id := states_manager.pop_committed(entity_id): dbstate.old_state_id = old_state_id + if old_state: + states_manager.update_pending_last_reported( + old_state_id, old_state.last_reported_timestamp + ) if entity_removed: dbstate.state = None else: @@ -1109,8 +1119,6 @@ class Recorder(threading.Thread): ): return - assert self.event_session is not None - session = self.event_session # Map the entity_id to the StatesMeta table if pending_states_meta := states_meta_manager.get_pending(entity_id): dbstate.states_meta_rel = pending_states_meta @@ -1192,7 +1200,23 @@ class Recorder(threading.Thread): session = self.event_session self._commits_without_expire += 1 + if ( + pending_last_reported + := self.states_manager.get_pending_last_reported_timestamp() + ): + with session.no_autoflush: + session.execute( + update(States), + [ + { + "state_id": state_id, + "last_reported_ts": last_reported_timestamp, + } + for state_id, last_reported_timestamp in pending_last_reported.items() + ], + ) session.commit() + self._event_session_has_pending_writes = False # We just committed the state attributes to the database # and we now know the attributes_ids. We can save diff --git a/homeassistant/components/recorder/db_schema.py b/homeassistant/components/recorder/db_schema.py index 61e39e40034..5b24448211d 100644 --- a/homeassistant/components/recorder/db_schema.py +++ b/homeassistant/components/recorder/db_schema.py @@ -68,7 +68,7 @@ class Base(DeclarativeBase): """Base class for tables.""" -SCHEMA_VERSION = 42 +SCHEMA_VERSION = 43 _LOGGER = logging.getLogger(__name__) @@ -428,6 +428,7 @@ class States(Base): event_id: Mapped[int | None] = mapped_column(UNUSED_LEGACY_INTEGER_COLUMN) last_changed: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) last_changed_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE) + last_reported_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE) last_updated: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) last_updated_ts: Mapped[float | None] = mapped_column( TIMESTAMP_TYPE, default=time.time, index=True @@ -499,6 +500,7 @@ class States(Base): dbstate.state = "" dbstate.last_updated_ts = event.time_fired_timestamp dbstate.last_changed_ts = None + dbstate.last_reported_ts = None return dbstate dbstate.state = state.state @@ -507,6 +509,10 @@ class States(Base): dbstate.last_changed_ts = None else: dbstate.last_changed_ts = state.last_changed_timestamp + if state.last_updated == state.last_reported: + dbstate.last_reported_ts = None + else: + dbstate.last_reported_ts = state.last_reported_timestamp return dbstate @@ -523,13 +529,18 @@ class States(Base): # When json_loads fails _LOGGER.exception("Error converting row to state: %s", self) return None + last_updated = dt_util.utc_from_timestamp(self.last_updated_ts or 0) if self.last_changed_ts is None or self.last_changed_ts == self.last_updated_ts: - last_changed = last_updated = dt_util.utc_from_timestamp( - self.last_updated_ts or 0 - ) + last_changed = dt_util.utc_from_timestamp(self.last_updated_ts or 0) else: - last_updated = dt_util.utc_from_timestamp(self.last_updated_ts or 0) last_changed = dt_util.utc_from_timestamp(self.last_changed_ts or 0) + if ( + self.last_reported_ts is None + or self.last_reported_ts == self.last_updated_ts + ): + last_reported = dt_util.utc_from_timestamp(self.last_updated_ts or 0) + else: + last_reported = dt_util.utc_from_timestamp(self.last_reported_ts or 0) return State( self.entity_id or "", self.state, # type: ignore[arg-type] @@ -537,7 +548,7 @@ class States(Base): # for newer states attrs, last_changed=last_changed, - last_reported=last_updated, # Recorder does not yet record last_reported + last_reported=last_reported, last_updated=last_updated, context=context, validate_entity_id=validate_entity_id, diff --git a/homeassistant/components/recorder/history/modern.py b/homeassistant/components/recorder/history/modern.py index 124a6a43869..a909f799ea9 100644 --- a/homeassistant/components/recorder/history/modern.py +++ b/homeassistant/components/recorder/history/modern.py @@ -52,32 +52,43 @@ _FIELD_MAP = { def _stmt_and_join_attributes( - no_attributes: bool, include_last_changed: bool + no_attributes: bool, + include_last_changed: bool, + include_last_reported: bool, ) -> Select: """Return the statement and if StateAttributes should be joined.""" _select = select(States.metadata_id, States.state, States.last_updated_ts) if include_last_changed: _select = _select.add_columns(States.last_changed_ts) + if include_last_reported: + _select = _select.add_columns(States.last_reported_ts) if not no_attributes: _select = _select.add_columns(SHARED_ATTR_OR_LEGACY_ATTRIBUTES) return _select def _stmt_and_join_attributes_for_start_state( - no_attributes: bool, include_last_changed: bool + no_attributes: bool, + include_last_changed: bool, + include_last_reported: bool, ) -> Select: """Return the statement and if StateAttributes should be joined.""" _select = select(States.metadata_id, States.state) _select = _select.add_columns(literal(value=0).label("last_updated_ts")) if include_last_changed: _select = _select.add_columns(literal(value=0).label("last_changed_ts")) + if include_last_reported: + _select = _select.add_columns(literal(value=0).label("last_reported_ts")) if not no_attributes: _select = _select.add_columns(SHARED_ATTR_OR_LEGACY_ATTRIBUTES) return _select def _select_from_subquery( - subquery: Subquery | CompoundSelect, no_attributes: bool, include_last_changed: bool + subquery: Subquery | CompoundSelect, + no_attributes: bool, + include_last_changed: bool, + include_last_reported: bool, ) -> Select: """Return the statement to select from the union.""" base_select = select( @@ -87,6 +98,8 @@ def _select_from_subquery( ) if include_last_changed: base_select = base_select.add_columns(subquery.c.last_changed_ts) + if include_last_reported: + base_select = base_select.add_columns(subquery.c.last_reported_ts) if no_attributes: return base_select return base_select.add_columns(subquery.c.attributes) @@ -134,7 +147,7 @@ def _significant_states_stmt( ) -> Select | CompoundSelect: """Query the database for significant state changes.""" include_last_changed = not significant_changes_only - stmt = _stmt_and_join_attributes(no_attributes, include_last_changed) + stmt = _stmt_and_join_attributes(no_attributes, include_last_changed, False) if significant_changes_only: # Since we are filtering on entity_id (metadata_id) we can avoid # the join of the states_meta table since we already know which @@ -174,13 +187,17 @@ def _significant_states_stmt( ).subquery(), no_attributes, include_last_changed, + False, + ), + _select_from_subquery( + stmt.subquery(), no_attributes, include_last_changed, False ), - _select_from_subquery(stmt.subquery(), no_attributes, include_last_changed), ).subquery() return _select_from_subquery( unioned_subquery, no_attributes, include_last_changed, + False, ).order_by(unioned_subquery.c.metadata_id, unioned_subquery.c.last_updated_ts) @@ -312,7 +329,7 @@ def _state_changed_during_period_stmt( run_start_ts: float | None, ) -> Select | CompoundSelect: stmt = ( - _stmt_and_join_attributes(no_attributes, False) + _stmt_and_join_attributes(no_attributes, False, True) .filter( ( (States.last_changed_ts == States.last_updated_ts) @@ -344,18 +361,22 @@ def _state_changed_during_period_stmt( single_metadata_id, no_attributes, False, + True, ).subquery(), no_attributes, False, + True, ), _select_from_subquery( stmt.subquery(), no_attributes, False, + True, ), ).subquery(), no_attributes, False, + True, ) @@ -427,7 +448,7 @@ def state_changes_during_period( def _get_last_state_changes_single_stmt(metadata_id: int) -> Select: return ( - _stmt_and_join_attributes(False, False) + _stmt_and_join_attributes(False, False, False) .join( ( lastest_state_for_metadata_id := ( @@ -457,7 +478,7 @@ def _get_last_state_changes_multiple_stmt( number_of_states: int, metadata_id: int ) -> Select: return ( - _stmt_and_join_attributes(False, False) + _stmt_and_join_attributes(False, False, True) .where( States.state_id == ( @@ -530,7 +551,9 @@ def _get_start_time_state_for_entities_stmt( # We got an include-list of entities, accelerate the query by filtering already # in the inner and the outer query. stmt = ( - _stmt_and_join_attributes_for_start_state(no_attributes, include_last_changed) + _stmt_and_join_attributes_for_start_state( + no_attributes, include_last_changed, False + ) .join( ( most_recent_states_for_entities_by_date := ( @@ -598,6 +621,7 @@ def _get_start_time_state_stmt( single_metadata_id, no_attributes, include_last_changed, + False, ) # We have more than one entity to look at so we need to do a query on states # since the last recorder run started. @@ -615,11 +639,14 @@ def _get_single_entity_start_time_stmt( metadata_id: int, no_attributes: bool, include_last_changed: bool, + include_last_reported: bool, ) -> Select: # Use an entirely different (and extremely fast) query if we only # have a single entity id stmt = ( - _stmt_and_join_attributes_for_start_state(no_attributes, include_last_changed) + _stmt_and_join_attributes_for_start_state( + no_attributes, include_last_changed, include_last_reported + ) .filter( States.last_updated_ts < epoch_time, States.metadata_id == metadata_id, diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 60373a053c9..fc2e6ec2b3f 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -1081,6 +1081,12 @@ def _apply_update( # noqa: C901 _migrate_statistics_columns_to_timestamp_removing_duplicates( hass, instance, session_maker, engine ) + elif new_version == 43: + _add_columns( + session_maker, + "states", + [f"last_reported_ts {_column_types.timestamp_type}"], + ) else: raise ValueError(f"No schema migration defined for version {new_version}") diff --git a/homeassistant/components/recorder/models/legacy.py b/homeassistant/components/recorder/models/legacy.py index af9fcf22f70..4b32ae65748 100644 --- a/homeassistant/components/recorder/models/legacy.py +++ b/homeassistant/components/recorder/models/legacy.py @@ -48,6 +48,7 @@ class LegacyLazyStatePreSchema31(State): self.state = self._row.state or "" self._attributes: dict[str, Any] | None = None self._last_changed: datetime | None = start_time + self._last_reported: datetime | None = start_time self._last_updated: datetime | None = start_time self._context: Context | None = None self.attr_cache = attr_cache @@ -93,6 +94,18 @@ class LegacyLazyStatePreSchema31(State): """Set last changed datetime.""" self._last_changed = value + @property + def last_reported(self) -> datetime: + """Last reported datetime.""" + if self._last_reported is None: + self._last_reported = self.last_updated + return self._last_reported + + @last_reported.setter + def last_reported(self, value: datetime) -> None: + """Set last reported datetime.""" + self._last_reported = value + @property def last_updated(self) -> datetime: """Last updated datetime.""" @@ -196,6 +209,7 @@ class LegacyLazyState(State): self._last_changed_ts: float | None = ( self._row.last_changed_ts or self._last_updated_ts ) + self._last_reported_ts: float | None = self._last_updated_ts self._context: Context | None = None self.attr_cache = attr_cache @@ -236,6 +250,17 @@ class LegacyLazyState(State): """Set last changed datetime.""" self._last_changed_ts = process_timestamp(value).timestamp() + @property + def last_reported(self) -> datetime: + """Last reported datetime.""" + assert self._last_reported_ts is not None + return dt_util.utc_from_timestamp(self._last_reported_ts) + + @last_reported.setter + def last_reported(self, value: datetime) -> None: + """Set last reported datetime.""" + self._last_reported_ts = process_timestamp(value).timestamp() + @property def last_updated(self) -> datetime: """Last updated datetime.""" diff --git a/homeassistant/components/recorder/models/state.py b/homeassistant/components/recorder/models/state.py index 9805aa56909..e1f23f32118 100644 --- a/homeassistant/components/recorder/models/state.py +++ b/homeassistant/components/recorder/models/state.py @@ -81,6 +81,18 @@ class LazyState(State): self._last_changed_ts or self._last_updated_ts ) + @cached_property + def _last_reported_ts(self) -> float | None: + """Last reported timestamp.""" + return getattr(self._row, "last_reported_ts", None) + + @cached_property + def last_reported(self) -> datetime: # type: ignore[override] + """Last reported datetime.""" + return dt_util.utc_from_timestamp( + self._last_reported_ts or self._last_updated_ts + ) + @cached_property def last_updated(self) -> datetime: # type: ignore[override] """Last updated datetime.""" diff --git a/homeassistant/components/recorder/table_managers/states.py b/homeassistant/components/recorder/table_managers/states.py index 80d2fcaddaf..d5cef759c54 100644 --- a/homeassistant/components/recorder/table_managers/states.py +++ b/homeassistant/components/recorder/table_managers/states.py @@ -12,6 +12,7 @@ class StatesManager: """Initialize the states manager for linking old_state_id.""" self._pending: dict[str, States] = {} self._last_committed_id: dict[str, int] = {} + self._last_reported: dict[int, float] = {} def pop_pending(self, entity_id: str) -> States | None: """Pop a pending state. @@ -44,6 +45,16 @@ class StatesManager: """ self._pending[entity_id] = state + def update_pending_last_reported( + self, state_id: int, last_reported_timestamp: float + ) -> None: + """Update the last reported timestamp for a state.""" + self._last_reported[state_id] = last_reported_timestamp + + def get_pending_last_reported_timestamp(self) -> dict[int, float]: + """Return the last reported timestamp for all pending states.""" + return self._last_reported + def post_commit_pending(self) -> None: """Call after commit to load the state_id of the new States into committed. @@ -53,6 +64,7 @@ class StatesManager: for entity_id, db_states in self._pending.items(): self._last_committed_id[entity_id] = db_states.state_id self._pending.clear() + self._last_reported.clear() def reset(self) -> None: """Reset after the database has been reset or changed. diff --git a/tests/components/recorder/auto_repairs/states/test_schema.py b/tests/components/recorder/auto_repairs/states/test_schema.py index c0a9e930966..7d14a873bfe 100644 --- a/tests/components/recorder/auto_repairs/states/test_schema.py +++ b/tests/components/recorder/auto_repairs/states/test_schema.py @@ -45,6 +45,7 @@ async def test_validate_db_schema_fix_float_issue( ) modification = [ "last_changed_ts DOUBLE PRECISION", + "last_reported_ts DOUBLE PRECISION", "last_updated_ts DOUBLE PRECISION", ] modify_columns_mock.assert_called_once_with(ANY, ANY, "states", modification) diff --git a/tests/components/recorder/common.py b/tests/components/recorder/common.py index 1c44511678e..e8fd6dbcf53 100644 --- a/tests/components/recorder/common.py +++ b/tests/components/recorder/common.py @@ -193,6 +193,7 @@ def assert_states_equal_without_context(state: State, other: State) -> None: """Assert that two states are equal, ignoring context.""" assert_states_equal_without_context_and_last_changed(state, other) assert state.last_changed == other.last_changed + assert state.last_reported == other.last_reported def assert_states_equal_without_context_and_last_changed( diff --git a/tests/components/recorder/db_schema_30.py b/tests/components/recorder/db_schema_30.py index 0fba51d588e..b82213cbc89 100644 --- a/tests/components/recorder/db_schema_30.py +++ b/tests/components/recorder/db_schema_30.py @@ -375,6 +375,9 @@ class States(Base): # type: ignore[misc,valid-type] last_changed_ts = Column( TIMESTAMP_TYPE ) # *** Not originally in v30, only added for recorder to startup ok + last_reported_ts = Column( + TIMESTAMP_TYPE + ) # *** Not originally in v30, only added for recorder to startup ok last_updated = Column(DATETIME_TYPE, default=dt_util.utcnow, index=True) last_updated_ts = Column( TIMESTAMP_TYPE, default=time.time, index=True diff --git a/tests/components/recorder/db_schema_32.py b/tests/components/recorder/db_schema_32.py index 7def9867373..15b56e2fc86 100644 --- a/tests/components/recorder/db_schema_32.py +++ b/tests/components/recorder/db_schema_32.py @@ -372,6 +372,9 @@ class States(Base): # type: ignore[misc,valid-type] ) last_changed = Column(DATETIME_TYPE) last_changed_ts = Column(TIMESTAMP_TYPE) + last_reported_ts = Column( + TIMESTAMP_TYPE + ) # *** Not originally in v32, only added for recorder to startup ok last_updated = Column(DATETIME_TYPE, default=dt_util.utcnow, index=True) last_updated_ts = Column(TIMESTAMP_TYPE, default=time.time, index=True) old_state_id = Column(Integer, ForeignKey("states.state_id"), index=True) diff --git a/tests/components/recorder/test_history.py b/tests/components/recorder/test_history.py index 40290924d11..d16a6856399 100644 --- a/tests/components/recorder/test_history.py +++ b/tests/components/recorder/test_history.py @@ -246,6 +246,41 @@ def test_state_changes_during_period( assert_multiple_states_equal_without_context(states[:limit], hist[entity_id]) +def test_state_changes_during_period_last_reported( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test state change during period.""" + hass = hass_recorder() + entity_id = "media_player.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() + point1 = start + timedelta(seconds=1) + point2 = point1 + timedelta(seconds=1) + end = point2 + timedelta(seconds=1) + + with freeze_time(start) as freezer: + set_state("idle") + + freezer.move_to(point1) + set_state("YouTube") + + freezer.move_to(point2) + states = [set_state("YouTube")] + + freezer.move_to(end) + set_state("Netflix") + + hist = history.state_changes_during_period(hass, start, end, entity_id) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + def test_state_changes_during_period_descending( hass_recorder: Callable[..., HomeAssistant], ) -> None: @@ -380,6 +415,38 @@ def test_get_last_state_changes(hass_recorder: Callable[..., HomeAssistant]) -> assert_multiple_states_equal_without_context(states, hist[entity_id]) +def test_get_last_state_changes_last_reported( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test number of state changes.""" + hass = hass_recorder() + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + point2 = point + timedelta(minutes=1, seconds=1) + states = [] + + with freeze_time(start) as freezer: + set_state("1") + + freezer.move_to(point) + states.append(set_state("1")) + + freezer.move_to(point2) + states.append(set_state("2")) + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + def test_get_last_state_change(hass_recorder: Callable[..., HomeAssistant]) -> None: """Test getting the last state change for an entity.""" hass = hass_recorder() @@ -577,6 +644,7 @@ def test_get_significant_states_without_initial( ) ) del states["media_player.test2"] + del states["thermostat.test3"] hist = history.get_significant_states( hass, @@ -598,6 +666,7 @@ def test_get_significant_states_entity_id( del states["media_player.test3"] del states["thermostat.test"] del states["thermostat.test2"] + del states["thermostat.test3"] del states["script.can_cancel_this_one"] hist = history.get_significant_states(hass, zero, four, ["media_player.test"]) @@ -745,6 +814,7 @@ def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: mp3 = "media_player.test3" therm = "thermostat.test" therm2 = "thermostat.test2" + therm3 = "thermostat.test3" zone = "zone.home" script_c = "script.can_cancel_this_one" @@ -760,7 +830,7 @@ def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: three = two + timedelta(seconds=1) four = three + timedelta(seconds=1) - states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} + states = {therm: [], therm2: [], therm3: [], mp: [], mp2: [], mp3: [], script_c: []} with freeze_time(one) as freezer: states[mp].append( set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) @@ -774,6 +844,8 @@ def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: states[therm].append( set_state(therm, 20, attributes={"current_temperature": 19.5}) ) + # This state will be updated + set_state(therm3, 20, attributes={"current_temperature": 19.5}) freezer.move_to(one + timedelta(microseconds=1)) states[mp].append( @@ -794,6 +866,8 @@ def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: states[therm2].append( set_state(therm2, 20, attributes={"current_temperature": 19}) ) + # This state will be updated + set_state(therm3, 20, attributes={"current_temperature": 19.5}) freezer.move_to(three) states[mp].append( @@ -806,6 +880,9 @@ def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: states[therm].append( set_state(therm, 21, attributes={"current_temperature": 20}) ) + states[therm3].append( + set_state(therm3, 20, attributes={"current_temperature": 19.5}) + ) return zero, four, states diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index 9a5b91fa8f8..549280efba2 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -925,7 +925,7 @@ def test_execute_stmt_lambda_element( start_time_ts = dt_util.utcnow().timestamp() stmt = lambda_stmt( lambda: _get_single_entity_start_time_stmt( - start_time_ts, metadata_id, False, False + start_time_ts, metadata_id, False, False, False ) ) rows = util.execute_stmt_lambda_element(session, stmt) From 45da6f8f2cd553652e91efc8a68af70c347eb1e5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 13:00:02 +0100 Subject: [PATCH 1560/1691] Add tests to Rova (#114285) --- tests/components/rova/__init__.py | 17 ++ tests/components/rova/conftest.py | 27 +++ .../rova/fixtures/calendar_items.json | 18 ++ .../rova/snapshots/test_sensor.ambr | 193 ++++++++++++++++++ tests/components/rova/test_sensor.py | 34 +++ 5 files changed, 289 insertions(+) create mode 100644 tests/components/rova/fixtures/calendar_items.json create mode 100644 tests/components/rova/snapshots/test_sensor.ambr create mode 100644 tests/components/rova/test_sensor.py diff --git a/tests/components/rova/__init__.py b/tests/components/rova/__init__.py index 631d37c09df..b9b0e68ed3c 100644 --- a/tests/components/rova/__init__.py +++ b/tests/components/rova/__init__.py @@ -1 +1,18 @@ """Tests for the Rova component.""" + +from unittest.mock import patch + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def setup_with_selected_platforms( + hass: HomeAssistant, entry: MockConfigEntry, platforms: list[Platform] +) -> None: + """Set up the Rova integration with the selected platforms.""" + entry.add_to_hass(hass) + with patch("homeassistant.components.rova.PLATFORMS", platforms): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/rova/conftest.py b/tests/components/rova/conftest.py index 51cb94c64f7..ced0baf5662 100644 --- a/tests/components/rova/conftest.py +++ b/tests/components/rova/conftest.py @@ -4,6 +4,15 @@ from unittest.mock import MagicMock, patch import pytest +from homeassistant.components.rova.const import ( + CONF_HOUSE_NUMBER, + CONF_HOUSE_NUMBER_SUFFIX, + CONF_ZIP_CODE, + DOMAIN, +) + +from tests.common import MockConfigEntry, load_json_array_fixture + @pytest.fixture def mock_rova(): @@ -18,4 +27,22 @@ def mock_rova(): patch("homeassistant.components.rova.Rova", return_value=api), ): api.is_rova_area.return_value = True + api.get_calendar_items.return_value = load_json_array_fixture( + "calendar_items.json", DOMAIN + ) yield api + + +@pytest.fixture +def mock_config_entry() -> MockConfigEntry: + """Mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + unique_id="8381BE13", + title="8381BE 13", + data={ + CONF_ZIP_CODE: "8381BE", + CONF_HOUSE_NUMBER: "13", + CONF_HOUSE_NUMBER_SUFFIX: "", + }, + ) diff --git a/tests/components/rova/fixtures/calendar_items.json b/tests/components/rova/fixtures/calendar_items.json new file mode 100644 index 00000000000..168bedb0d50 --- /dev/null +++ b/tests/components/rova/fixtures/calendar_items.json @@ -0,0 +1,18 @@ +[ + { + "GarbageTypeCode": "GFT", + "Date": "2024-02-21T00:00:00" + }, + { + "GarbageTypeCode": "PAPIER", + "Date": "2024-03-06T00:00:00" + }, + { + "GarbageTypeCode": "PMD", + "Date": "2024-03-12T00:00:00" + }, + { + "GarbageTypeCode": "RESTAFVAL", + "Date": "2024-03-12T00:00:00" + } +] diff --git a/tests/components/rova/snapshots/test_sensor.ambr b/tests/components/rova/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..a70b62c3672 --- /dev/null +++ b/tests/components/rova/snapshots/test_sensor.ambr @@ -0,0 +1,193 @@ +# serializer version: 1 +# name: test_all_entities[sensor.8381be13_gft-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.8381be13_gft', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:recycle', + 'original_name': '8381BE13_gft', + 'platform': 'rova', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '8381BE13_gft', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.8381be13_gft-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': '8381BE13_gft', + 'icon': 'mdi:recycle', + }), + 'context': , + 'entity_id': 'sensor.8381be13_gft', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-02-20T23:00:00+00:00', + }) +# --- +# name: test_all_entities[sensor.8381be13_papier-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.8381be13_papier', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:recycle', + 'original_name': '8381BE13_papier', + 'platform': 'rova', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '8381BE13_papier', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.8381be13_papier-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': '8381BE13_papier', + 'icon': 'mdi:recycle', + }), + 'context': , + 'entity_id': 'sensor.8381be13_papier', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-03-05T23:00:00+00:00', + }) +# --- +# name: test_all_entities[sensor.8381be13_pmd-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.8381be13_pmd', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:recycle', + 'original_name': '8381BE13_pmd', + 'platform': 'rova', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '8381BE13_pmd', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.8381be13_pmd-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': '8381BE13_pmd', + 'icon': 'mdi:recycle', + }), + 'context': , + 'entity_id': 'sensor.8381be13_pmd', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-03-11T23:00:00+00:00', + }) +# --- +# name: test_all_entities[sensor.8381be13_restafval-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.8381be13_restafval', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:recycle', + 'original_name': '8381BE13_restafval', + 'platform': 'rova', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '8381BE13_restafval', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.8381be13_restafval-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': '8381BE13_restafval', + 'icon': 'mdi:recycle', + }), + 'context': , + 'entity_id': 'sensor.8381be13_restafval', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2024-03-11T23:00:00+00:00', + }) +# --- diff --git a/tests/components/rova/test_sensor.py b/tests/components/rova/test_sensor.py new file mode 100644 index 00000000000..ae8b64363da --- /dev/null +++ b/tests/components/rova/test_sensor.py @@ -0,0 +1,34 @@ +"""Tests for the Rova component.""" + +from unittest.mock import MagicMock + +from syrupy import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_with_selected_platforms + +from tests.common import MockConfigEntry + + +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_rova: MagicMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + await setup_with_selected_platforms(hass, mock_config_entry, [Platform.SENSOR]) + entity_entries = er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + + assert entity_entries + for entity_entry in entity_entries: + assert hass.states.get(entity_entry.entity_id) == snapshot( + name=f"{entity_entry.entity_id}-state" + ) + assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry") From 09ebca96301b6d0cb91ffda0c46f6c78fa74a13d Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:22:10 +0100 Subject: [PATCH 1561/1691] Use SignalType to improve typing [esphome] (#114296) --- homeassistant/components/esphome/entry_data.py | 5 +++-- homeassistant/components/esphome/update.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index ae0dca49411..da0dae52569 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -47,6 +47,7 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.storage import Store +from homeassistant.util.signal_type import SignalType from .const import DOMAIN from .dashboard import async_get_dashboard @@ -147,9 +148,9 @@ class RuntimeEntryData: ) @property - def signal_static_info_updated(self) -> str: + def signal_static_info_updated(self) -> SignalType[list[EntityInfo]]: """Return the signal to listen to for updates on static info.""" - return f"esphome_{self.entry_id}_on_list" + return SignalType(f"esphome_{self.entry_id}_on_list") @callback def async_register_static_info_callback( diff --git a/homeassistant/components/esphome/update.py b/homeassistant/components/esphome/update.py index 5a565f9914d..3e5a82bbd0b 100644 --- a/homeassistant/components/esphome/update.py +++ b/homeassistant/components/esphome/update.py @@ -139,7 +139,9 @@ class ESPHomeUpdateEntity(CoordinatorEntity[ESPHomeDashboard], UpdateEntity): ) @callback - def _handle_device_update(self, static_info: EntityInfo | None = None) -> None: + def _handle_device_update( + self, static_info: list[EntityInfo] | None = None + ) -> None: """Handle updated data from the device.""" self._update_attrs() self.async_write_ha_state() From 9ea666b5e1536d5ee2b516eb9f3f3577b9734459 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:22:44 +0100 Subject: [PATCH 1562/1691] Use SignalType to improve typing [bthome] (#114295) --- homeassistant/components/bthome/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bthome/__init__.py b/homeassistant/components/bthome/__init__.py index 8509b5d1d46..dab7a7db158 100644 --- a/homeassistant/components/bthome/__init__.py +++ b/homeassistant/components/bthome/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations import logging -from typing import cast from bthome_ble import BTHomeBluetoothDeviceData, SensorUpdate from bthome_ble.parser import EncryptionScheme @@ -22,6 +21,7 @@ from homeassistant.helpers.device_registry import ( async_get, ) from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.util.signal_type import SignalType from .const import ( BTHOME_BLE_EVENT, @@ -94,7 +94,7 @@ def process_service_info( hass, format_discovered_event_class(address), event_class, ble_event ) - hass.bus.async_fire(BTHOME_BLE_EVENT, cast(dict, ble_event)) + hass.bus.async_fire(BTHOME_BLE_EVENT, ble_event) async_dispatcher_send( hass, format_event_dispatcher_name(address, event_class), @@ -108,14 +108,16 @@ def process_service_info( return update -def format_event_dispatcher_name(address: str, event_class: str) -> str: +def format_event_dispatcher_name( + address: str, event_class: str +) -> SignalType[BTHomeBleEvent]: """Format an event dispatcher name.""" - return f"{DOMAIN}_event_{address}_{event_class}" + return SignalType(f"{DOMAIN}_event_{address}_{event_class}") -def format_discovered_event_class(address: str) -> str: +def format_discovered_event_class(address: str) -> SignalType[str, BTHomeBleEvent]: """Format a discovered event class.""" - return f"{DOMAIN}_discovered_event_class_{address}" + return SignalType(f"{DOMAIN}_discovered_event_class_{address}") async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: From aaecbbdc40230cba5ae4ca326585425c350a446f Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 27 Mar 2024 14:12:45 +0100 Subject: [PATCH 1563/1691] Update bump_version.py to append also the time to nightlies and add possibility to set manual a nightly version (#114300) --- .github/workflows/builder.yml | 2 +- script/version_bump.py | 33 +++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 62ea44a9df8..1dc6f7a3938 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -105,7 +105,7 @@ jobs: python3 -m pip install "$(grep '^uv' < requirements_test.txt)" uv pip install packaging tomli uv pip install . - version="$(python3 script/version_bump.py nightly)" + python3 script/version_bump.py nightly --set-nightly-version "${{ needs.init.outputs.version }}" if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}" diff --git a/script/version_bump.py b/script/version_bump.py index da864b45176..a51b5a8fed1 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -24,7 +24,9 @@ def _bump_release(release, bump_type): return major, minor, patch -def bump_version(version, bump_type): +def bump_version( + version: Version, bump_type: str, *, nightly_version: str | None = None +) -> Version: """Return a new version given a current version and action.""" to_change = {} @@ -83,11 +85,14 @@ def bump_version(version, bump_type): to_change["pre"] = ("b", 0) elif bump_type == "nightly": - # Convert 0.70.0d0 to 0.70.0d20190424, fails when run on non dev release + # Convert 0.70.0d0 to 0.70.0d201904241254, fails when run on non dev release if not version.is_devrelease: raise ValueError("Can only be run on dev release") - to_change["dev"] = ("dev", dt_util.utcnow().strftime("%Y%m%d")) + to_change["dev"] = ( + "dev", + nightly_version or dt_util.utcnow().strftime("%Y%m%d%H%M"), + ) else: raise ValueError(f"Unsupported type: {bump_type}") @@ -146,7 +151,7 @@ def write_ci_workflow(version: Version) -> None: fp.write(content) -def main(): +def main() -> None: """Execute script.""" parser = argparse.ArgumentParser(description="Bump version of Home Assistant") parser.add_argument( @@ -157,8 +162,15 @@ def main(): parser.add_argument( "--commit", action="store_true", help="Create a version bump commit." ) + parser.add_argument( + "--set-nightly-version", help="Set the nightly version to", type=str + ) + arguments = parser.parse_args() + if arguments.set_nightly_version and arguments.type != "nightly": + parser.error("--set-nightly-version requires type set to nightly.") + if ( arguments.commit and subprocess.run(["git", "diff", "--quiet"], check=False).returncode == 1 @@ -167,7 +179,9 @@ def main(): return current = Version(const.__version__) - bumped = bump_version(current, arguments.type) + bumped = bump_version( + current, arguments.type, nightly_version=arguments.set_nightly_version + ) assert bumped > current, "BUG! New version is not newer than old version" write_version(bumped) @@ -181,7 +195,7 @@ def main(): subprocess.run(["git", "commit", "-nam", f"Bump version to {bumped}"], check=True) -def test_bump_version(): +def test_bump_version() -> None: """Make sure it all works.""" import pytest @@ -204,10 +218,13 @@ def test_bump_version(): assert bump_version(Version("0.56.0.dev0"), "minor") == Version("0.56.0") assert bump_version(Version("0.56.2.dev0"), "minor") == Version("0.57.0") - today = dt_util.utcnow().strftime("%Y%m%d") + now = dt_util.utcnow().strftime("%Y%m%d%H%M") assert bump_version(Version("0.56.0.dev0"), "nightly") == Version( - f"0.56.0.dev{today}" + f"0.56.0.dev{now}" ) + assert bump_version( + Version("0.56.0.dev0"), "nightly", nightly_version="1234" + ) == Version("0.56.0.dev1234") with pytest.raises(ValueError): assert bump_version(Version("0.56.0"), "nightly") From c222cfd6924d549aa8fbcac820496cdbe6df46ea Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 14:14:49 +0100 Subject: [PATCH 1564/1691] Add coordinator to Rova (#114288) * Add coordinator to Rova * Add coordinator to Rova * Fix * Update homeassistant/components/rova/sensor.py Co-authored-by: Jan-Philipp Benecke * Fix --------- Co-authored-by: Jan-Philipp Benecke --- .coveragerc | 2 - homeassistant/components/rova/__init__.py | 7 +- homeassistant/components/rova/coordinator.py | 42 +++++++++ homeassistant/components/rova/sensor.py | 92 +++++--------------- 4 files changed, 70 insertions(+), 73 deletions(-) create mode 100644 homeassistant/components/rova/coordinator.py diff --git a/.coveragerc b/.coveragerc index 7fd6ab5defe..306d06c4de0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1155,8 +1155,6 @@ omit = homeassistant/components/roon/media_player.py homeassistant/components/roon/server.py homeassistant/components/route53/* - homeassistant/components/rova/__init__.py - homeassistant/components/rova/sensor.py homeassistant/components/rpi_camera/* homeassistant/components/rtorrent/sensor.py homeassistant/components/ruuvi_gateway/__init__.py diff --git a/homeassistant/components/rova/__init__.py b/homeassistant/components/rova/__init__.py index d7ed140dddc..16ec098bb92 100644 --- a/homeassistant/components/rova/__init__.py +++ b/homeassistant/components/rova/__init__.py @@ -11,6 +11,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, DOMAIN +from .coordinator import RovaCoordinator PLATFORMS: list[Platform] = [Platform.SENSOR] @@ -32,7 +33,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if not rova_area: raise ConfigEntryError("Rova does not collect garbage in this area") - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = api + coordinator = RovaCoordinator(hass, api) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/rova/coordinator.py b/homeassistant/components/rova/coordinator.py new file mode 100644 index 00000000000..ef411be19e8 --- /dev/null +++ b/homeassistant/components/rova/coordinator.py @@ -0,0 +1,42 @@ +"""Coordinator for Rova.""" + +from datetime import datetime, timedelta + +from rova.rova import Rova + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util.dt import get_time_zone + +from .const import DOMAIN, LOGGER + + +class RovaCoordinator(DataUpdateCoordinator[dict[str, datetime]]): + """Class to manage fetching Rova data.""" + + def __init__(self, hass: HomeAssistant, api: Rova) -> None: + """Initialize.""" + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_interval=timedelta(hours=12), + ) + self.api = api + + async def _async_update_data(self) -> dict[str, datetime]: + """Fetch data from Rova API.""" + + items = await self.hass.async_add_executor_job(self.api.get_calendar_items) + + data = {} + + for item in items: + date = datetime.strptime(item["Date"], "%Y-%m-%dT%H:%M:%S").replace( + tzinfo=get_time_zone("Europe/Amsterdam") + ) + code = item["GarbageTypeCode"].lower() + if code not in data: + data[code] = date + + return data diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index 352540da1f2..471de335d94 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -2,11 +2,8 @@ from __future__ import annotations -from datetime import datetime, timedelta -from typing import Any +from datetime import datetime -from requests.exceptions import ConnectTimeout, HTTPError -from rova.rova import Rova import voluptuous as vol from homeassistant.components.sensor import ( @@ -23,22 +20,13 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.util import Throttle -from homeassistant.util.dt import get_time_zone +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ( - CONF_HOUSE_NUMBER, - CONF_HOUSE_NUMBER_SUFFIX, - CONF_ZIP_CODE, - DOMAIN, - LOGGER, -) +from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, DOMAIN +from .coordinator import RovaCoordinator ISSUE_PLACEHOLDER = {"url": "/config/integrations/dashboard/add?domain=rova"} -UPDATE_DELAY = timedelta(hours=12) -SCAN_INTERVAL = timedelta(hours=12) - SENSOR_TYPES = { "bio": SensorEntityDescription( key="gft", @@ -125,71 +113,35 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Add Rova entry.""" - # get api from hass - api: Rova = hass.data[DOMAIN][entry.entry_id] + coordinator: RovaCoordinator = hass.data[DOMAIN][entry.entry_id] - # Create rova data service which will retrieve and update the data. - data_service = RovaData(api) + assert entry.unique_id + unique_id = entry.unique_id - # generate unique name for rova integration - name = f"{entry.data[CONF_ZIP_CODE]}{entry.data[CONF_HOUSE_NUMBER]}{entry.data[CONF_HOUSE_NUMBER_SUFFIX]}" - - # Create a new sensor for each garbage type. - entities = [ - RovaSensor(name, description, data_service) + async_add_entities( + RovaSensor(unique_id, description, coordinator) for key, description in SENSOR_TYPES.items() - ] - async_add_entities(entities, True) + ) -class RovaSensor(SensorEntity): +class RovaSensor(CoordinatorEntity[RovaCoordinator], SensorEntity): """Representation of a Rova sensor.""" def __init__( - self, platform_name, description: SensorEntityDescription, data_service + self, + unique_id: str, + description: SensorEntityDescription, + coordinator: RovaCoordinator, ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self.entity_description = description - self.data_service = data_service - self._attr_name = f"{platform_name}_{description.key}" - self._attr_unique_id = f"{platform_name}_{description.key}" + self._attr_name = f"{unique_id}_{description.key}" + self._attr_unique_id = f"{unique_id}_{description.key}" self._attr_device_class = SensorDeviceClass.TIMESTAMP - def update(self) -> None: - """Get the latest data from the sensor and update the state.""" - self.data_service.update() - pickup_date = self.data_service.data.get(self.entity_description.key) - if pickup_date is not None: - self._attr_native_value = pickup_date - - -class RovaData: - """Get and update the latest data from the Rova API.""" - - def __init__(self, api) -> None: - """Initialize the data object.""" - self.api = api - self.data: dict[str, Any] = {} - - @Throttle(UPDATE_DELAY) - def update(self): - """Update the data from the Rova API.""" - - try: - items = self.api.get_calendar_items() - except (ConnectTimeout, HTTPError): - LOGGER.error("Could not retrieve data, retry again later") - return - - self.data = {} - - for item in items: - date = datetime.strptime(item["Date"], "%Y-%m-%dT%H:%M:%S").replace( - tzinfo=get_time_zone("Europe/Amsterdam") - ) - code = item["GarbageTypeCode"].lower() - if code not in self.data: - self.data[code] = date - - LOGGER.debug("Updated Rova calendar: %s", self.data) + @property + def native_value(self) -> datetime | None: + """Return the state of the sensor.""" + return self.coordinator.data.get(self.entity_description.key) From 6313571fbc141326c7fa70a78b37d2f989423aa3 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 27 Mar 2024 14:24:02 +0100 Subject: [PATCH 1565/1691] Use `setup_test_component_platform` helper for binary_sensor entity component tests instead of `hass.components` (#114293) --- tests/components/binary_sensor/common.py | 19 +++++++ tests/components/binary_sensor/conftest.py | 21 ++++++++ .../binary_sensor/test_device_condition.py | 34 ++++++------- .../binary_sensor/test_device_trigger.py | 37 +++++++------- tests/components/binary_sensor/test_init.py | 3 +- .../custom_components/test/binary_sensor.py | 50 ------------------- 6 files changed, 74 insertions(+), 90 deletions(-) create mode 100644 tests/components/binary_sensor/common.py create mode 100644 tests/components/binary_sensor/conftest.py delete mode 100644 tests/testing_config/custom_components/test/binary_sensor.py diff --git a/tests/components/binary_sensor/common.py b/tests/components/binary_sensor/common.py new file mode 100644 index 00000000000..bfa9b8e2d52 --- /dev/null +++ b/tests/components/binary_sensor/common.py @@ -0,0 +1,19 @@ +"""Common test utilities for binary_sensor entity component tests.""" + +from homeassistant.components.binary_sensor import BinarySensorEntity + +from tests.common import MockEntity + + +class MockBinarySensor(MockEntity, BinarySensorEntity): + """Mock Binary Sensor class.""" + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self._handle("is_on") + + @property + def device_class(self): + """Return the class of this sensor.""" + return self._handle("device_class") diff --git a/tests/components/binary_sensor/conftest.py b/tests/components/binary_sensor/conftest.py new file mode 100644 index 00000000000..33fcbc24089 --- /dev/null +++ b/tests/components/binary_sensor/conftest.py @@ -0,0 +1,21 @@ +"""Fixtures for binary_sensor entity component tests.""" + +import pytest + +from homeassistant.components.binary_sensor import BinarySensorDeviceClass + +from .common import MockBinarySensor + + +@pytest.fixture +def mock_binary_sensor_entities() -> dict[str, MockBinarySensor]: + """Return mock binary sensors.""" + return { + device_class: MockBinarySensor( + name=f"{device_class} sensor", + is_on=True, + unique_id=f"unique_{device_class}", + device_class=device_class, + ) + for device_class in BinarySensorDeviceClass + } diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index 904e62ef18d..83451313bad 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -16,11 +16,14 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from .common import MockBinarySensor + from tests.common import ( MockConfigEntry, async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) @@ -39,11 +42,10 @@ async def test_get_conditions( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test we get the expected conditions from a binary_sensor.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() binary_sensor_entries = {} @@ -58,7 +60,7 @@ async def test_get_conditions( binary_sensor_entries[device_class] = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES[device_class].unique_id, + mock_binary_sensor_entities[device_class].unique_id, device_id=device_entry.id, ) @@ -238,12 +240,10 @@ async def test_if_state( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test for turn_on and turn_off conditions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -253,7 +253,7 @@ async def test_if_state( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entry = entity_registry.async_get(platform.ENTITIES["battery"].entity_id) + entry = entity_registry.async_get(mock_binary_sensor_entities["battery"].entity_id) entity_registry.async_update_entity(entry.entity_id, device_id=device_entry.id) assert await async_setup_component( @@ -324,12 +324,10 @@ async def test_if_state_legacy( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test for turn_on and turn_off conditions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -339,7 +337,7 @@ async def test_if_state_legacy( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entry = entity_registry.async_get(platform.ENTITIES["battery"].entity_id) + entry = entity_registry.async_get(mock_binary_sensor_entities["battery"].entity_id) entity_registry.async_update_entity(entry.entity_id, device_id=device_entry.id) assert await async_setup_component( @@ -384,16 +382,14 @@ async def test_if_fires_on_for_condition( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test for firing if condition is on with delay.""" point1 = dt_util.utcnow() point2 = point1 + timedelta(seconds=10) point3 = point2 + timedelta(seconds=10) - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -403,7 +399,7 @@ async def test_if_fires_on_for_condition( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - entry = entity_registry.async_get(platform.ENTITIES["battery"].entity_id) + entry = entity_registry.async_get(mock_binary_sensor_entities["battery"].entity_id) entity_registry.async_update_entity(entry.entity_id, device_id=device_entry.id) with freeze_time(point1) as time_freeze: diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index ae78620b939..ad7bd9c3528 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -15,12 +15,15 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from .common import MockBinarySensor + from tests.common import ( MockConfigEntry, async_fire_time_changed, async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) @@ -39,12 +42,11 @@ async def test_get_triggers( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test we get the expected triggers from a binary_sensor.""" registry_entries: dict[BinarySensorDeviceClass, er.RegistryEntry] = {} - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -58,7 +60,7 @@ async def test_get_triggers( registry_entries[device_class] = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES[device_class].unique_id, + mock_binary_sensor_entities[device_class].unique_id, device_id=device_entry.id, ) @@ -132,11 +134,11 @@ async def test_get_triggers_no_state( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test we get the expected triggers from a binary_sensor.""" registry_entries: dict[BinarySensorDeviceClass, er.RegistryEntry] = {} - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -239,11 +241,10 @@ async def test_if_fires_on_state_change( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test for on and off triggers firing.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -256,7 +257,7 @@ async def test_if_fires_on_state_change( entry = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_binary_sensor_entities["battery"].unique_id, device_id=device_entry.id, ) @@ -341,12 +342,10 @@ async def test_if_fires_on_state_change_with_for( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test for triggers firing with delay.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -359,7 +358,7 @@ async def test_if_fires_on_state_change_with_for( entry = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_binary_sensor_entities["battery"].unique_id, device_id=device_entry.id, ) @@ -418,12 +417,10 @@ async def test_if_fires_on_state_change_legacy( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, calls, - enable_custom_integrations: None, + mock_binary_sensor_entities: dict[str, MockBinarySensor], ) -> None: """Test for triggers firing.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_binary_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -436,7 +433,7 @@ async def test_if_fires_on_state_change_legacy( entry = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_binary_sensor_entities["battery"].unique_id, device_id=device_entry.id, ) diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 059e387c4fd..335b9b40d50 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -11,6 +11,8 @@ from homeassistant.const import STATE_OFF, STATE_ON, EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from .common import MockBinarySensor + from tests.common import ( MockConfigEntry, MockModule, @@ -21,7 +23,6 @@ from tests.common import ( mock_integration, mock_platform, ) -from tests.testing_config.custom_components.test.binary_sensor import MockBinarySensor TEST_DOMAIN = "test" diff --git a/tests/testing_config/custom_components/test/binary_sensor.py b/tests/testing_config/custom_components/test/binary_sensor.py deleted file mode 100644 index e45e614a82c..00000000000 --- a/tests/testing_config/custom_components/test/binary_sensor.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Provide a mock binary sensor platform. - -Call init before using it in your tests to ensure clean test data. -""" - -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity - -from tests.common import MockEntity - -ENTITIES = {} - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - {} - if empty - else { - device_class: MockBinarySensor( - name=f"{device_class} sensor", - is_on=True, - unique_id=f"unique_{device_class}", - device_class=device_class, - ) - for device_class in DEVICE_CLASSES - } - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(list(ENTITIES.values())) - - -class MockBinarySensor(MockEntity, BinarySensorEntity): - """Mock Binary Sensor class.""" - - @property - def is_on(self): - """Return true if the binary sensor is on.""" - return self._handle("is_on") - - @property - def device_class(self): - """Return the class of this sensor.""" - return self._handle("device_class") From 911a31f8605ae6d98b826f1a76b515b2b8bf5a1a Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:25:02 +0100 Subject: [PATCH 1566/1691] Use SignalType to improve typing [core] (#114298) --- homeassistant/components/device_tracker/const.py | 5 ++++- .../components/persistent_notification/__init__.py | 5 ++++- homeassistant/config_entries.py | 6 ++++-- homeassistant/helpers/script.py | 11 ++++++++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/device_tracker/const.py b/homeassistant/components/device_tracker/const.py index 25f82b91dda..964b7faab9b 100644 --- a/homeassistant/components/device_tracker/const.py +++ b/homeassistant/components/device_tracker/const.py @@ -14,6 +14,7 @@ from homeassistant.helpers.deprecation import ( check_if_deprecated_constant, dir_with_deprecated_constants, ) +from homeassistant.util.signal_type import SignalType LOGGER: Final = logging.getLogger(__package__) @@ -68,7 +69,9 @@ ATTR_SOURCE_TYPE: Final = "source_type" ATTR_CONSIDER_HOME: Final = "consider_home" ATTR_IP: Final = "ip" -CONNECTED_DEVICE_REGISTERED: Final = "device_tracker_connected_device_registered" +CONNECTED_DEVICE_REGISTERED = SignalType[dict[str, str | None]]( + "device_tracker_connected_device_registered" +) # These can be removed if no deprecated constant are in this module anymore __getattr__ = partial(check_if_deprecated_constant, module_globals=globals()) diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py index 25e5385cc0d..a785d015ffb 100644 --- a/homeassistant/components/persistent_notification/__init__.py +++ b/homeassistant/components/persistent_notification/__init__.py @@ -21,6 +21,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass import homeassistant.util.dt as dt_util +from homeassistant.util.signal_type import SignalType from homeassistant.util.uuid import random_uuid_hex DOMAIN = "persistent_notification" @@ -50,7 +51,9 @@ class UpdateType(StrEnum): UPDATED = "updated" -SIGNAL_PERSISTENT_NOTIFICATIONS_UPDATED = "persistent_notifications_updated" +SIGNAL_PERSISTENT_NOTIFICATIONS_UPDATED = SignalType[ + UpdateType, dict[str, Notification] +]("persistent_notifications_updated") SCHEMA_SERVICE_NOTIFICATION = vol.Schema( {vol.Required(ATTR_NOTIFICATION_ID): cv.string} diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 5976ed83814..42194641f7f 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -47,7 +47,7 @@ from .exceptions import ( ) from .helpers import device_registry, entity_registry, issue_registry as ir, storage from .helpers.debounce import Debouncer -from .helpers.dispatcher import async_dispatcher_send +from .helpers.dispatcher import SignalType, async_dispatcher_send from .helpers.event import ( RANDOM_MICROSECOND_MAX, RANDOM_MICROSECOND_MIN, @@ -189,7 +189,9 @@ RECONFIGURE_NOTIFICATION_ID = "config_entry_reconfigure" EVENT_FLOW_DISCOVERED = "config_entry_discovered" -SIGNAL_CONFIG_ENTRY_CHANGED = "config_entry_changed" +SIGNAL_CONFIG_ENTRY_CHANGED = SignalType["ConfigEntryChange", "ConfigEntry"]( + "config_entry_changed" +) NO_RESET_TRIES_STATES = { ConfigEntryState.SETUP_RETRY, diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 4f9c1d113ea..560f3227c4f 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -81,10 +81,11 @@ from homeassistant.core import ( from homeassistant.util import slugify from homeassistant.util.async_ import create_eager_task from homeassistant.util.dt import utcnow +from homeassistant.util.signal_type import SignalType, SignalTypeFormat from . import condition, config_validation as cv, service, template from .condition import ConditionCheckerType, trace_condition_function -from .dispatcher import SignalType, async_dispatcher_connect, async_dispatcher_send +from .dispatcher import async_dispatcher_connect, async_dispatcher_send from .event import async_call_later, async_track_template from .script_variables import ScriptVariables from .trace import ( @@ -155,7 +156,9 @@ _SHUTDOWN_MAX_WAIT = 60 ACTION_TRACE_NODE_MAX_LEN = 20 # Max length of a trace node for repeated actions SCRIPT_BREAKPOINT_HIT = SignalType[str, str, str]("script_breakpoint_hit") -SCRIPT_DEBUG_CONTINUE_STOP = "script_debug_continue_stop_{}_{}" +SCRIPT_DEBUG_CONTINUE_STOP: SignalTypeFormat[Literal["continue", "stop"]] = ( + SignalTypeFormat("script_debug_continue_stop_{}_{}") +) SCRIPT_DEBUG_CONTINUE_ALL = "script_debug_continue_all" script_stack_cv: ContextVar[list[int] | None] = ContextVar("script_stack", default=None) @@ -216,7 +219,9 @@ async def trace_action( done = hass.loop.create_future() @callback - def async_continue_stop(command=None): + def async_continue_stop( + command: Literal["continue", "stop"] | None = None, + ) -> None: if command == "stop": _set_result_unless_done(stop) _set_result_unless_done(done) From 5316b9470500e14a23cf1c55a244993ba0805bf6 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 27 Mar 2024 14:36:31 +0100 Subject: [PATCH 1567/1691] Use `setup_test_component_platform` helper for alarm_control_panel entity component tests instead of `hass.components` (#114301) --- .../components/alarm_control_panel/common.py | 62 +++++++++- .../alarm_control_panel/conftest.py | 22 ++++ .../alarm_control_panel/test_device_action.py | 56 +++++---- .../test/alarm_control_panel.py | 112 ------------------ 4 files changed, 115 insertions(+), 137 deletions(-) create mode 100644 tests/components/alarm_control_panel/conftest.py delete mode 100644 tests/testing_config/custom_components/test/alarm_control_panel.py diff --git a/tests/components/alarm_control_panel/common.py b/tests/components/alarm_control_panel/common.py index 99f8a3d24bd..9ec419d8cf0 100644 --- a/tests/components/alarm_control_panel/common.py +++ b/tests/components/alarm_control_panel/common.py @@ -4,7 +4,11 @@ All containing methods are legacy helpers that should not be used by new components. Instead call the service directly. """ -from homeassistant.components.alarm_control_panel import DOMAIN +from homeassistant.components.alarm_control_panel import ( + DOMAIN, + AlarmControlPanelEntity, + AlarmControlPanelEntityFeature, +) from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, @@ -16,8 +20,16 @@ from homeassistant.const import ( SERVICE_ALARM_ARM_VACATION, SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMED_VACATION, + STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, ) +from tests.common import MockEntity + async def async_alarm_disarm(hass, code=None, entity_id=ENTITY_MATCH_ALL): """Send the alarm the command for disarm.""" @@ -98,3 +110,51 @@ async def async_alarm_arm_custom_bypass(hass, code=None, entity_id=ENTITY_MATCH_ await hass.services.async_call( DOMAIN, SERVICE_ALARM_ARM_CUSTOM_BYPASS, data, blocking=True ) + + +class MockAlarm(MockEntity, AlarmControlPanelEntity): + """Mock Alarm control panel class.""" + + _attr_supported_features = ( + AlarmControlPanelEntityFeature.ARM_HOME + | AlarmControlPanelEntityFeature.ARM_AWAY + | AlarmControlPanelEntityFeature.ARM_NIGHT + | AlarmControlPanelEntityFeature.TRIGGER + | AlarmControlPanelEntityFeature.ARM_VACATION + ) + + @property + def code_arm_required(self): + """Whether the code is required for arm actions.""" + return self._handle("code_arm_required") + + def alarm_arm_away(self, code=None): + """Send arm away command.""" + self._attr_state = STATE_ALARM_ARMED_AWAY + self.schedule_update_ha_state() + + def alarm_arm_home(self, code=None): + """Send arm home command.""" + self._attr_state = STATE_ALARM_ARMED_HOME + self.schedule_update_ha_state() + + def alarm_arm_night(self, code=None): + """Send arm night command.""" + self._attr_state = STATE_ALARM_ARMED_NIGHT + self.schedule_update_ha_state() + + def alarm_arm_vacation(self, code=None): + """Send arm night command.""" + self._attr_state = STATE_ALARM_ARMED_VACATION + self.schedule_update_ha_state() + + def alarm_disarm(self, code=None): + """Send disarm command.""" + if code == "1234": + self._attr_state = STATE_ALARM_DISARMED + self.schedule_update_ha_state() + + def alarm_trigger(self, code=None): + """Send alarm trigger command.""" + self._attr_state = STATE_ALARM_TRIGGERED + self.schedule_update_ha_state() diff --git a/tests/components/alarm_control_panel/conftest.py b/tests/components/alarm_control_panel/conftest.py new file mode 100644 index 00000000000..cda3d81b26e --- /dev/null +++ b/tests/components/alarm_control_panel/conftest.py @@ -0,0 +1,22 @@ +"""Fixturs for Alarm Control Panel tests.""" + +import pytest + +from tests.components.alarm_control_panel.common import MockAlarm + + +@pytest.fixture +def mock_alarm_control_panel_entities() -> dict[str, MockAlarm]: + """Mock Alarm control panel class.""" + return { + "arm_code": MockAlarm( + name="Alarm arm code", + code_arm_required=True, + unique_id="unique_arm_code", + ), + "no_arm_code": MockAlarm( + name="Alarm no arm code", + code_arm_required=False, + unique_id="unique_no_arm_code", + ), + } diff --git a/tests/components/alarm_control_panel/test_device_action.py b/tests/components/alarm_control_panel/test_device_action.py index 81aa9c903ba..afcfa0a7a12 100644 --- a/tests/components/alarm_control_panel/test_device_action.py +++ b/tests/components/alarm_control_panel/test_device_action.py @@ -28,7 +28,9 @@ from tests.common import ( MockConfigEntry, async_get_device_automation_capabilities, async_get_device_automations, + setup_test_component_platform, ) +from tests.components.alarm_control_panel.common import MockAlarm @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -223,11 +225,12 @@ async def test_get_action_capabilities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_alarm_control_panel_entities: dict[str, MockAlarm], ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform( + hass, DOMAIN, mock_alarm_control_panel_entities.values() + ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -240,7 +243,7 @@ async def test_get_action_capabilities( entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["no_arm_code"].unique_id, + mock_alarm_control_panel_entities["no_arm_code"].unique_id, device_id=device_entry.id, ) @@ -270,11 +273,12 @@ async def test_get_action_capabilities_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_alarm_control_panel_entities: dict[str, MockAlarm], ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform( + hass, DOMAIN, mock_alarm_control_panel_entities.values() + ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -287,7 +291,7 @@ async def test_get_action_capabilities_legacy( entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["no_arm_code"].unique_id, + mock_alarm_control_panel_entities["no_arm_code"].unique_id, device_id=device_entry.id, ) @@ -318,11 +322,12 @@ async def test_get_action_capabilities_arm_code( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_alarm_control_panel_entities: dict[str, MockAlarm], ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform( + hass, DOMAIN, mock_alarm_control_panel_entities.values() + ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -335,7 +340,7 @@ async def test_get_action_capabilities_arm_code( entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["arm_code"].unique_id, + mock_alarm_control_panel_entities["arm_code"].unique_id, device_id=device_entry.id, ) @@ -373,11 +378,12 @@ async def test_get_action_capabilities_arm_code_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_alarm_control_panel_entities: dict[str, MockAlarm], ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform( + hass, DOMAIN, mock_alarm_control_panel_entities.values() + ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() @@ -390,7 +396,7 @@ async def test_get_action_capabilities_arm_code_legacy( entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["arm_code"].unique_id, + mock_alarm_control_panel_entities["arm_code"].unique_id, device_id=device_entry.id, ) @@ -429,11 +435,12 @@ async def test_action( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_alarm_control_panel_entities: dict[str, MockAlarm], ) -> None: """Test for turn_on and turn_off actions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform( + hass, DOMAIN, mock_alarm_control_panel_entities.values() + ) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -444,7 +451,7 @@ async def test_action( entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["no_arm_code"].unique_id, + mock_alarm_control_panel_entities["no_arm_code"].unique_id, device_id=device_entry.id, ) @@ -560,11 +567,12 @@ async def test_action_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_alarm_control_panel_entities: dict[str, MockAlarm], ) -> None: """Test for turn_on and turn_off actions.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform( + hass, DOMAIN, mock_alarm_control_panel_entities.values() + ) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -575,7 +583,7 @@ async def test_action_legacy( entity_entry = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["no_arm_code"].unique_id, + mock_alarm_control_panel_entities["no_arm_code"].unique_id, device_id=device_entry.id, ) diff --git a/tests/testing_config/custom_components/test/alarm_control_panel.py b/tests/testing_config/custom_components/test/alarm_control_panel.py deleted file mode 100644 index 204281af3d3..00000000000 --- a/tests/testing_config/custom_components/test/alarm_control_panel.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Provide a mock alarm_control_panel platform. - -Call init before using it in your tests to ensure clean test data. -""" - -from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity -from homeassistant.components.alarm_control_panel.const import ( - AlarmControlPanelEntityFeature, -) -from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_NIGHT, - STATE_ALARM_ARMED_VACATION, - STATE_ALARM_DISARMED, - STATE_ALARM_TRIGGERED, -) - -from tests.common import MockEntity - -ENTITIES = {} - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - {} - if empty - else { - "arm_code": MockAlarm( - name="Alarm arm code", - code_arm_required=True, - unique_id="unique_arm_code", - ), - "no_arm_code": MockAlarm( - name="Alarm no arm code", - code_arm_required=False, - unique_id="unique_no_arm_code", - ), - } - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(list(ENTITIES.values())) - - -class MockAlarm(MockEntity, AlarmControlPanelEntity): - """Mock Alarm control panel class.""" - - def __init__(self, **values): - """Init the Mock Alarm Control Panel.""" - self._state = None - - MockEntity.__init__(self, **values) - - @property - def code_arm_required(self): - """Whether the code is required for arm actions.""" - return self._handle("code_arm_required") - - @property - def state(self): - """Return the state of the device.""" - return self._state - - @property - def supported_features(self) -> AlarmControlPanelEntityFeature: - """Return the list of supported features.""" - return ( - AlarmControlPanelEntityFeature.ARM_HOME - | AlarmControlPanelEntityFeature.ARM_AWAY - | AlarmControlPanelEntityFeature.ARM_NIGHT - | AlarmControlPanelEntityFeature.TRIGGER - | AlarmControlPanelEntityFeature.ARM_VACATION - ) - - def alarm_arm_away(self, code=None): - """Send arm away command.""" - self._state = STATE_ALARM_ARMED_AWAY - self.schedule_update_ha_state() - - def alarm_arm_home(self, code=None): - """Send arm home command.""" - self._state = STATE_ALARM_ARMED_HOME - self.schedule_update_ha_state() - - def alarm_arm_night(self, code=None): - """Send arm night command.""" - self._state = STATE_ALARM_ARMED_NIGHT - self.schedule_update_ha_state() - - def alarm_arm_vacation(self, code=None): - """Send arm night command.""" - self._state = STATE_ALARM_ARMED_VACATION - self.schedule_update_ha_state() - - def alarm_disarm(self, code=None): - """Send disarm command.""" - if code == "1234": - self._state = STATE_ALARM_DISARMED - self.schedule_update_ha_state() - - def alarm_trigger(self, code=None): - """Send alarm trigger command.""" - self._state = STATE_ALARM_TRIGGERED - self.schedule_update_ha_state() From ce022a1793aebf2c73aa5393eccf3c2f323c846f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 Mar 2024 03:38:57 -1000 Subject: [PATCH 1568/1691] Fix missing powerview shade data when initial refresh fails (#113033) --- .../hunterdouglas_powerview/cover.py | 1 - .../hunterdouglas_powerview/number.py | 2 +- .../hunterdouglas_powerview/shade_data.py | 67 +++++++++---------- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 78a62e5bb88..453d5c4e920 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -71,7 +71,6 @@ async def async_setup_entry( entities: list[ShadeEntity] = [] for shade in pv_entry.shade_data.values(): - coordinator.data.update_shade_position(shade.id, shade.current_position) room_name = getattr(pv_entry.room_data.get(shade.room_id), ATTR_NAME, "") entities.extend( create_powerview_shade_entity( diff --git a/homeassistant/components/hunterdouglas_powerview/number.py b/homeassistant/components/hunterdouglas_powerview/number.py index b37331c08df..8551a11337e 100644 --- a/homeassistant/components/hunterdouglas_powerview/number.py +++ b/homeassistant/components/hunterdouglas_powerview/number.py @@ -41,7 +41,7 @@ def store_velocity( value: float | None, ) -> None: """Store the desired shade velocity in the coordinator.""" - coordinator.data.update_shade_velocity(shade_id, ShadePosition(velocity=value)) + coordinator.data.update_shade_position(shade_id, ShadePosition(velocity=value)) NUMBERS: Final = ( diff --git a/homeassistant/components/hunterdouglas_powerview/shade_data.py b/homeassistant/components/hunterdouglas_powerview/shade_data.py index 4d780b83ecb..e6b20312f27 100644 --- a/homeassistant/components/hunterdouglas_powerview/shade_data.py +++ b/homeassistant/components/hunterdouglas_powerview/shade_data.py @@ -2,6 +2,7 @@ from __future__ import annotations +from dataclasses import fields import logging from typing import Any @@ -12,74 +13,66 @@ from .util import async_map_data_by_id _LOGGER = logging.getLogger(__name__) +POSITION_FIELDS = fields(ShadePosition) + + +def copy_position_data(source: ShadePosition, target: ShadePosition) -> ShadePosition: + """Copy position data from source to target for None values only.""" + # the hub will always return a velocity of 0 on initial connect, + # separate definition to store consistent value in HA + # this value is purely driven from HA + for field in POSITION_FIELDS: + if (value := getattr(source, field.name)) is not None: + setattr(target, field.name, value) + class PowerviewShadeData: """Coordinate shade data between multiple api calls.""" def __init__(self) -> None: """Init the shade data.""" - self._group_data_by_id: dict[int, dict[str | int, Any]] = {} - self._shade_data_by_id: dict[int, BaseShade] = {} + self._raw_data_by_id: dict[int, dict[str | int, Any]] = {} + self._shade_group_data_by_id: dict[int, BaseShade] = {} self.positions: dict[int, ShadePosition] = {} def get_raw_data(self, shade_id: int) -> dict[str | int, Any]: """Get data for the shade.""" - return self._group_data_by_id[shade_id] + return self._raw_data_by_id[shade_id] def get_all_raw_data(self) -> dict[int, dict[str | int, Any]]: """Get data for all shades.""" - return self._group_data_by_id + return self._raw_data_by_id def get_shade(self, shade_id: int) -> BaseShade: """Get specific shade from the coordinator.""" - return self._shade_data_by_id[shade_id] + return self._shade_group_data_by_id[shade_id] def get_shade_position(self, shade_id: int) -> ShadePosition: """Get positions for a shade.""" if shade_id not in self.positions: - self.positions[shade_id] = ShadePosition() + shade_position = ShadePosition() + # If we have the group data, use it to populate the initial position + if shade := self._shade_group_data_by_id.get(shade_id): + copy_position_data(shade.current_position, shade_position) + self.positions[shade_id] = shade_position return self.positions[shade_id] def update_from_group_data(self, shade_id: int) -> None: """Process an update from the group data.""" - self.update_shade_positions(self._shade_data_by_id[shade_id]) + data = self._shade_group_data_by_id[shade_id] + copy_position_data(data.current_position, self.get_shade_position(data.id)) def store_group_data(self, shade_data: PowerviewData) -> None: """Store data from the all shades endpoint. - This does not update the shades or positions + This does not update the shades or positions (self.positions) as the data may be stale. update_from_group_data with a shade_id will update a specific shade from the group data. """ - self._shade_data_by_id = shade_data.processed - self._group_data_by_id = async_map_data_by_id(shade_data.raw) + self._shade_group_data_by_id = shade_data.processed + self._raw_data_by_id = async_map_data_by_id(shade_data.raw) - def update_shade_position(self, shade_id: int, shade_data: ShadePosition) -> None: + def update_shade_position(self, shade_id: int, new_position: ShadePosition) -> None: """Update a single shades position.""" - if shade_id not in self.positions: - self.positions[shade_id] = ShadePosition() - - # ShadePosition will return None if the value is not set - if shade_data.primary is not None: - self.positions[shade_id].primary = shade_data.primary - if shade_data.secondary is not None: - self.positions[shade_id].secondary = shade_data.secondary - if shade_data.tilt is not None: - self.positions[shade_id].tilt = shade_data.tilt - - def update_shade_velocity(self, shade_id: int, shade_data: ShadePosition) -> None: - """Update a single shades velocity.""" - if shade_id not in self.positions: - self.positions[shade_id] = ShadePosition() - - # the hub will always return a velocity of 0 on initial connect, - # separate definition to store consistent value in HA - # this value is purely driven from HA - if shade_data.velocity is not None: - self.positions[shade_id].velocity = shade_data.velocity - - def update_shade_positions(self, data: BaseShade) -> None: - """Update a shades from data dict.""" - _LOGGER.debug("Raw data update: %s", data.raw_data) - self.update_shade_position(data.id, data.current_position) + copy_position_data(new_position, self.get_shade_position(shade_id)) From 1a64be6da612e89fd6ace35151eef40894a5ef5a Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 27 Mar 2024 14:45:28 +0100 Subject: [PATCH 1569/1691] Fix version_bump --set-nightly-version (#114304) --- script/version_bump.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/script/version_bump.py b/script/version_bump.py index a51b5a8fed1..6c24c40c4e3 100755 --- a/script/version_bump.py +++ b/script/version_bump.py @@ -89,10 +89,16 @@ def bump_version( if not version.is_devrelease: raise ValueError("Can only be run on dev release") - to_change["dev"] = ( - "dev", - nightly_version or dt_util.utcnow().strftime("%Y%m%d%H%M"), - ) + new_dev = dt_util.utcnow().strftime("%Y%m%d%H%M") + if nightly_version: + new_version = Version(nightly_version) + if new_version.release != version.release: + raise ValueError("Nightly version must have the same release version") + if not new_version.is_devrelease: + raise ValueError("Nightly version must be a dev version") + new_dev = new_version.dev + + to_change["dev"] = ("dev", new_dev) else: raise ValueError(f"Unsupported type: {bump_type}") @@ -223,10 +229,22 @@ def test_bump_version() -> None: f"0.56.0.dev{now}" ) assert bump_version( - Version("0.56.0.dev0"), "nightly", nightly_version="1234" - ) == Version("0.56.0.dev1234") - with pytest.raises(ValueError): - assert bump_version(Version("0.56.0"), "nightly") + Version("2024.4.0.dev20240327"), + "nightly", + nightly_version="2024.4.0.dev202403271315", + ) == Version("2024.4.0.dev202403271315") + with pytest.raises(ValueError, match="Can only be run on dev release"): + bump_version(Version("0.56.0"), "nightly") + with pytest.raises( + ValueError, match="Nightly version must have the same release version" + ): + bump_version( + Version("0.56.0.dev0"), + "nightly", + nightly_version="2024.4.0.dev202403271315", + ) + with pytest.raises(ValueError, match="Nightly version must be a dev version"): + bump_version(Version("0.56.0.dev0"), "nightly", nightly_version="0.56.0") if __name__ == "__main__": From a00c1fa24188c6ddaf7cec107b6d8b56ca1315d2 Mon Sep 17 00:00:00 2001 From: Tereza Tomcova Date: Wed, 27 Mar 2024 15:26:44 +0100 Subject: [PATCH 1570/1691] Bump pyprusalink to 2.1.1 and support Prusa MK3 (#114210) Co-authored-by: Robert Resch Co-authored-by: J. Nick Koston --- .../components/prusalink/__init__.py | 6 +- homeassistant/components/prusalink/camera.py | 3 + .../components/prusalink/config_flow.py | 36 ++++-- .../components/prusalink/manifest.json | 2 +- homeassistant/components/prusalink/sensor.py | 12 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/prusalink/conftest.py | 26 +++++ tests/components/prusalink/test_camera.py | 18 +++ .../components/prusalink/test_config_flow.py | 53 +++++++++ tests/components/prusalink/test_sensor.py | 104 ++++++++++++++++++ 11 files changed, 244 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/prusalink/__init__.py b/homeassistant/components/prusalink/__init__.py index e8c930c3157..2ff4601466c 100644 --- a/homeassistant/components/prusalink/__init__.py +++ b/homeassistant/components/prusalink/__init__.py @@ -23,8 +23,8 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryError from homeassistant.helpers import issue_registry as ir -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -44,7 +44,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryError("Please upgrade your printer's firmware.") api = PrusaLink( - async_get_clientsession(hass), + get_async_client(hass), entry.data[CONF_HOST], entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], @@ -81,7 +81,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> password = config_entry.data[CONF_API_KEY] api = PrusaLink( - async_get_clientsession(hass), + get_async_client(hass), config_entry.data[CONF_HOST], username, password, diff --git a/homeassistant/components/prusalink/camera.py b/homeassistant/components/prusalink/camera.py index 48c2fee9cdb..cc625b7ef57 100644 --- a/homeassistant/components/prusalink/camera.py +++ b/homeassistant/components/prusalink/camera.py @@ -2,6 +2,8 @@ from __future__ import annotations +from pyprusalink.types import PrinterState + from homeassistant.components.camera import Camera from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -38,6 +40,7 @@ class PrusaLinkJobPreviewEntity(PrusaLinkEntity, Camera): """Get if camera is available.""" return ( super().available + and self.coordinator.data.get("state") != PrinterState.IDLE.value and (file := self.coordinator.data.get("file")) and file.get("refs", {}).get("thumbnail") ) diff --git a/homeassistant/components/prusalink/config_flow.py b/homeassistant/components/prusalink/config_flow.py index c0c8c797133..b0c7cf2f756 100644 --- a/homeassistant/components/prusalink/config_flow.py +++ b/homeassistant/components/prusalink/config_flow.py @@ -6,17 +6,17 @@ import asyncio import logging from typing import Any -from aiohttp import ClientError from awesomeversion import AwesomeVersion, AwesomeVersionException +from httpx import HTTPError, InvalidURL from pyprusalink import PrusaLink -from pyprusalink.types import InvalidAuth +from pyprusalink.types import InvalidAuth, VersionInfo import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.httpx_client import get_async_client from .const import DOMAIN @@ -34,13 +34,33 @@ STEP_USER_DATA_SCHEMA = vol.Schema( ) +def ensure_printer_is_supported(version: VersionInfo) -> None: + """Raise NotSupported exception if the printer is not supported.""" + + try: + if AwesomeVersion("2.0.0") <= AwesomeVersion(version["api"]): + return + + # Workaround to allow PrusaLink 0.7.2 on MK3 and MK2.5 that supports + # the 2.0.0 API, but doesn't advertise it yet + if version.get("original", "").startswith( + ("PrusaLink I3MK3", "PrusaLink I3MK2") + ) and AwesomeVersion("0.7.2") <= AwesomeVersion(version["server"]): + return + + except AwesomeVersionException as err: + raise NotSupported from err + + raise NotSupported + + async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. """ api = PrusaLink( - async_get_clientsession(hass), + get_async_client(hass), data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD], @@ -50,15 +70,11 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, async with asyncio.timeout(5): version = await api.get_version() - except (TimeoutError, ClientError) as err: + except (TimeoutError, HTTPError, InvalidURL) as err: _LOGGER.error("Could not connect to PrusaLink: %s", err) raise CannotConnect from err - try: - if AwesomeVersion(version["api"]) < AwesomeVersion("2.0.0"): - raise NotSupported - except AwesomeVersionException as err: - raise NotSupported from err + ensure_printer_is_supported(version) return {"title": version["hostname"] or version["text"]} diff --git a/homeassistant/components/prusalink/manifest.json b/homeassistant/components/prusalink/manifest.json index a9d8353690e..6c64419debb 100644 --- a/homeassistant/components/prusalink/manifest.json +++ b/homeassistant/components/prusalink/manifest.json @@ -10,5 +10,5 @@ ], "documentation": "https://www.home-assistant.io/integrations/prusalink", "iot_class": "local_polling", - "requirements": ["pyprusalink==2.0.0"] + "requirements": ["pyprusalink==2.1.1"] } diff --git a/homeassistant/components/prusalink/sensor.py b/homeassistant/components/prusalink/sensor.py index be4230b844c..604b029fc92 100644 --- a/homeassistant/components/prusalink/sensor.py +++ b/homeassistant/components/prusalink/sensor.py @@ -146,13 +146,15 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { translation_key="progress", native_unit_of_measurement=PERCENTAGE, value_fn=lambda data: cast(float, data["progress"]), - available_fn=lambda data: data.get("progress") is not None, + available_fn=lambda data: data.get("progress") is not None + and data.get("state") != PrinterState.IDLE.value, ), PrusaLinkSensorEntityDescription[JobInfo]( key="job.filename", translation_key="filename", value_fn=lambda data: cast(str, data["file"]["display_name"]), - available_fn=lambda data: data.get("file") is not None, + available_fn=lambda data: data.get("file") is not None + and data.get("state") != PrinterState.IDLE.value, ), PrusaLinkSensorEntityDescription[JobInfo]( key="job.start", @@ -162,7 +164,8 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { lambda data: (utcnow() - timedelta(seconds=data["time_printing"])), timedelta(minutes=2), ), - available_fn=lambda data: data.get("time_printing") is not None, + available_fn=lambda data: data.get("time_printing") is not None + and data.get("state") != PrinterState.IDLE.value, ), PrusaLinkSensorEntityDescription[JobInfo]( key="job.finish", @@ -172,7 +175,8 @@ SENSORS: dict[str, tuple[PrusaLinkSensorEntityDescription, ...]] = { lambda data: (utcnow() + timedelta(seconds=data["time_remaining"])), timedelta(minutes=2), ), - available_fn=lambda data: data.get("time_remaining") is not None, + available_fn=lambda data: data.get("time_remaining") is not None + and data.get("state") != PrinterState.IDLE.value, ), ), } diff --git a/requirements_all.txt b/requirements_all.txt index fd87aa7592d..8284e9ea81b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2065,7 +2065,7 @@ pyprof2calltree==1.4.5 pyprosegur==0.0.9 # homeassistant.components.prusalink -pyprusalink==2.0.0 +pyprusalink==2.1.1 # homeassistant.components.ps4 pyps4-2ndscreen==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7789460ea4e..47dc312e903 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1610,7 +1610,7 @@ pyprof2calltree==1.4.5 pyprosegur==0.0.9 # homeassistant.components.prusalink -pyprusalink==2.0.0 +pyprusalink==2.1.1 # homeassistant.components.ps4 pyps4-2ndscreen==1.3.1 diff --git a/tests/components/prusalink/conftest.py b/tests/components/prusalink/conftest.py index f99ccde2094..104e4d47afa 100644 --- a/tests/components/prusalink/conftest.py +++ b/tests/components/prusalink/conftest.py @@ -121,6 +121,32 @@ def mock_job_api_idle(hass): yield resp +@pytest.fixture +def mock_job_api_idle_mk3(hass): + """Mock PrusaLink job API having a job with idle state (MK3).""" + resp = { + "id": 129, + "state": "IDLE", + "progress": 0.0, + "time_remaining": None, + "time_printing": 0, + "file": { + "refs": { + "icon": "/thumb/s/usb/TabletStand3~4.BGC", + "thumbnail": "/thumb/l/usb/TabletStand3~4.BGC", + "download": "/usb/TabletStand3~4.BGC", + }, + "name": "TabletStand3~4.BGC", + "display_name": "TabletStand3.bgcode", + "path": "/usb", + "size": 754535, + "m_timestamp": 1698686881, + }, + } + with patch("pyprusalink.PrusaLink.get_job", return_value=resp): + yield resp + + @pytest.fixture def mock_job_api_printing(hass): """Mock PrusaLink printing.""" diff --git a/tests/components/prusalink/test_camera.py b/tests/components/prusalink/test_camera.py index 94d9c2f8271..c770a7f228d 100644 --- a/tests/components/prusalink/test_camera.py +++ b/tests/components/prusalink/test_camera.py @@ -35,6 +35,24 @@ async def test_camera_no_job( assert resp.status == 500 +async def test_camera_idle_job_mk3( + hass: HomeAssistant, + mock_config_entry, + mock_api, + mock_job_api_idle_mk3, + hass_client: ClientSessionGenerator, +) -> None: + """Test camera while job state is idle (MK3).""" + assert await async_setup_component(hass, "prusalink", {}) + state = hass.states.get("camera.mock_title_preview") + assert state is not None + assert state.state == "unavailable" + + client = await hass_client() + resp = await client.get("/api/camera_proxy/camera.mock_title_preview") + assert resp.status == 500 + + async def test_camera_active_job( hass: HomeAssistant, mock_config_entry, diff --git a/tests/components/prusalink/test_config_flow.py b/tests/components/prusalink/test_config_flow.py index 43f969182b9..e7db5b54dac 100644 --- a/tests/components/prusalink/test_config_flow.py +++ b/tests/components/prusalink/test_config_flow.py @@ -41,6 +41,34 @@ async def test_form(hass: HomeAssistant, mock_version_api) -> None: assert len(mock_setup_entry.mock_calls) == 1 +async def test_form_mk3(hass: HomeAssistant, mock_version_api) -> None: + """Test it works for MK2/MK3.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_version_api["api"] = "0.9.0-legacy" + mock_version_api["server"] = "0.7.2" + mock_version_api["original"] = "PrusaLink I3MK3S" + + with patch( + "homeassistant.components.prusalink.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "http://1.1.1.1/", + "username": "abcdefg", + "password": "abcdefg", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_form_invalid_auth(hass: HomeAssistant) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( @@ -129,6 +157,31 @@ async def test_form_invalid_version_2(hass: HomeAssistant, mock_version_api) -> assert result2["errors"] == {"base": "not_supported"} +async def test_form_invalid_mk3_server_version( + hass: HomeAssistant, mock_version_api +) -> None: + """Test we handle invalid version for MK2/MK3.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_version_api["api"] = "0.7.2" + mock_version_api["server"] = "i am not a version" + mock_version_api["original"] = "PrusaLink I3MK3S" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "host": "1.1.1.1", + "username": "abcdefg", + "password": "abcdefg", + }, + ) + + assert result2["type"] == FlowResultType.FORM + assert result2["errors"] == {"base": "not_supported"} + + async def test_form_cannot_connect(hass: HomeAssistant) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( diff --git a/tests/components/prusalink/test_sensor.py b/tests/components/prusalink/test_sensor.py index bba06f66146..b15e9198da6 100644 --- a/tests/components/prusalink/test_sensor.py +++ b/tests/components/prusalink/test_sensor.py @@ -136,6 +136,110 @@ async def test_sensors_no_job(hass: HomeAssistant, mock_config_entry, mock_api) assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == REVOLUTIONS_PER_MINUTE +async def test_sensors_idle_job_mk3( + hass: HomeAssistant, + mock_config_entry, + mock_api, + mock_job_api_idle_mk3, +) -> None: + """Test sensors while job state is idle (MK3).""" + assert await async_setup_component(hass, "prusalink", {}) + + state = hass.states.get("sensor.mock_title") + assert state is not None + assert state.state == "idle" + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM + assert state.attributes[ATTR_OPTIONS] == [ + "idle", + "busy", + "printing", + "paused", + "finished", + "stopped", + "error", + "attention", + "ready", + ] + + state = hass.states.get("sensor.mock_title_heatbed_temperature") + assert state is not None + assert state.state == "41.9" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.CELSIUS + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + + state = hass.states.get("sensor.mock_title_nozzle_temperature") + assert state is not None + assert state.state == "47.8" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.CELSIUS + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + + state = hass.states.get("sensor.mock_title_heatbed_target_temperature") + assert state is not None + assert state.state == "60.5" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.CELSIUS + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + + state = hass.states.get("sensor.mock_title_nozzle_target_temperature") + assert state is not None + assert state.state == "210.1" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.CELSIUS + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + + state = hass.states.get("sensor.mock_title_z_height") + assert state is not None + assert state.state == "1.8" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfLength.MILLIMETERS + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.DISTANCE + assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + + state = hass.states.get("sensor.mock_title_print_speed") + assert state is not None + assert state.state == "100" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE + + state = hass.states.get("sensor.mock_title_material") + assert state is not None + assert state.state == "PLA" + + state = hass.states.get("sensor.mock_title_print_flow") + assert state is not None + assert state.state == "100" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE + + state = hass.states.get("sensor.mock_title_progress") + assert state is not None + assert state.state == "unavailable" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == "%" + + state = hass.states.get("sensor.mock_title_filename") + assert state is not None + assert state.state == "unavailable" + + state = hass.states.get("sensor.mock_title_print_start") + assert state is not None + assert state.state == "unavailable" + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP + + state = hass.states.get("sensor.mock_title_print_finish") + assert state is not None + assert state.state == "unavailable" + assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP + + state = hass.states.get("sensor.mock_title_hotend_fan") + assert state is not None + assert state.state == "100" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == REVOLUTIONS_PER_MINUTE + + state = hass.states.get("sensor.mock_title_print_fan") + assert state is not None + assert state.state == "75" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == REVOLUTIONS_PER_MINUTE + + async def test_sensors_active_job( hass: HomeAssistant, mock_config_entry, From a9fd4e45cdf853ef62f12dea176bce68c6930e1b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 15:51:49 +0100 Subject: [PATCH 1571/1691] Raise issue if not Rova area anymore (#114309) --- homeassistant/components/rova/__init__.py | 13 +++++++++++ homeassistant/components/rova/strings.json | 4 ++++ tests/components/rova/test_init.py | 25 ++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/components/rova/test_init.py diff --git a/homeassistant/components/rova/__init__.py b/homeassistant/components/rova/__init__.py index 16ec098bb92..64f0e787a4b 100644 --- a/homeassistant/components/rova/__init__.py +++ b/homeassistant/components/rova/__init__.py @@ -9,6 +9,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from .const import CONF_HOUSE_NUMBER, CONF_HOUSE_NUMBER_SUFFIX, CONF_ZIP_CODE, DOMAIN from .coordinator import RovaCoordinator @@ -31,6 +32,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady from ex if not rova_area: + async_create_issue( + hass, + DOMAIN, + f"no_rova_area_{entry.data[CONF_ZIP_CODE]}", + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.ERROR, + translation_key="no_rova_area", + translation_placeholders={ + CONF_ZIP_CODE: entry.data[CONF_ZIP_CODE], + }, + ) raise ConfigEntryError("Rova does not collect garbage in this area") coordinator = RovaCoordinator(hass, api) diff --git a/homeassistant/components/rova/strings.json b/homeassistant/components/rova/strings.json index 8b57c2e5f62..2eb6ba1797f 100644 --- a/homeassistant/components/rova/strings.json +++ b/homeassistant/components/rova/strings.json @@ -28,6 +28,10 @@ "deprecated_yaml_import_issue_invalid_rova_area": { "title": "The Rova YAML configuration import failed", "description": "There was an error when trying to import your Rova YAML configuration.\n\nRova does not collect at this address.\n\nEnsure the imported configuration is correct and remove the Rova YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." + }, + "no_rova_area": { + "title": "Rova does not collect at this address anymore", + "description": "Rova does not collect at {zip_code} anymore.\n\nPlease remove the integration." } } } diff --git a/tests/components/rova/test_init.py b/tests/components/rova/test_init.py new file mode 100644 index 00000000000..33149559f68 --- /dev/null +++ b/tests/components/rova/test_init.py @@ -0,0 +1,25 @@ +"""Tests for the Rova integration init.""" + +from unittest.mock import MagicMock + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.helpers import issue_registry as ir + +from tests.common import MockConfigEntry + + +async def test_issue_if_not_rova_area( + hass: HomeAssistant, + mock_rova: MagicMock, + mock_config_entry: MockConfigEntry, + issue_registry: ir.IssueRegistry, +) -> None: + """Test we create an issue if rova does not collect at the given address.""" + mock_rova.is_rova_area.return_value = False + mock_config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state == ConfigEntryState.SETUP_ERROR + assert len(issue_registry.issues) == 1 From 23e9be756d1091c301ed43f289e607bd2197ad37 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 27 Mar 2024 16:02:47 +0100 Subject: [PATCH 1572/1691] Fix exception when logging out from cloud (#114306) * Fix exception when logging out from cloud * Add test --- .../components/cloud/google_config.py | 6 ++- tests/components/cloud/test_google_config.py | 42 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 991ac88a44f..1ba2fab717f 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -400,7 +400,11 @@ class CloudGoogleConfig(AbstractConfig): @callback def async_get_agent_users(self) -> tuple: """Return known agent users.""" - if not self._prefs.google_connected or not self._cloud.username: + if ( + not self._cloud.is_logged_in # Can't call Cloud.username if not logged in + or not self._prefs.google_connected + or not self._cloud.username + ): return () return (self._cloud.username,) diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 5ce390fd9a3..66530bfa3f8 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,7 +1,7 @@ """Test the Cloud Google Config.""" from http import HTTPStatus -from unittest.mock import Mock, patch +from unittest.mock import Mock, PropertyMock, patch from freezegun import freeze_time import pytest @@ -865,3 +865,43 @@ async def test_google_config_get_agent_user_id( == config.agent_user_id ) assert config.get_agent_user_id_from_webhook("other_id") != config.agent_user_id + + +async def test_google_config_get_agent_users( + hass: HomeAssistant, mock_cloud_login, cloud_prefs +) -> None: + """Test overridden async_get_agent_users method.""" + username_mock = PropertyMock(return_value="blah") + + # We should not call Cloud.username when not logged in + cloud_prefs._prefs["google_connected"] = True + assert cloud_prefs.google_connected + mock_cloud = Mock(is_logged_in=False) + type(mock_cloud).username = username_mock + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, mock_cloud + ) + assert config.async_get_agent_users() == () + username_mock.assert_not_called() + + # We should not call Cloud.username when not connected + cloud_prefs._prefs["google_connected"] = False + assert not cloud_prefs.google_connected + mock_cloud = Mock(is_logged_in=True) + type(mock_cloud).username = username_mock + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, mock_cloud + ) + assert config.async_get_agent_users() == () + username_mock.assert_not_called() + + # Logged in and connected + cloud_prefs._prefs["google_connected"] = True + assert cloud_prefs.google_connected + mock_cloud = Mock(is_logged_in=True) + type(mock_cloud).username = username_mock + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, mock_cloud + ) + assert config.async_get_agent_users() == ("blah",) + username_mock.assert_called() From 834f45397d6a6e3c8d350f635f41bd2843730b7e Mon Sep 17 00:00:00 2001 From: MarkGodwin <10632972+MarkGodwin@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:05:14 +0000 Subject: [PATCH 1573/1691] Add support for PoE control of TP-Link Omada Gateways (#114138) Co-authored-by: Robert Resch Co-authored-by: J. Nick Koston --- .../components/tplink_omada/binary_sensor.py | 5 +- .../components/tplink_omada/entity.py | 8 +- .../components/tplink_omada/switch.py | 274 +++++++++++------- .../components/tplink_omada/update.py | 2 +- .../tplink_omada/snapshots/test_switch.ambr | 13 + tests/components/tplink_omada/test_switch.py | 81 ++++++ 6 files changed, 267 insertions(+), 116 deletions(-) diff --git a/homeassistant/components/tplink_omada/binary_sensor.py b/homeassistant/components/tplink_omada/binary_sensor.py index 7bee6159dd7..c0304c4d1b2 100644 --- a/homeassistant/components/tplink_omada/binary_sensor.py +++ b/homeassistant/components/tplink_omada/binary_sensor.py @@ -8,7 +8,6 @@ from dataclasses import dataclass from tplink_omada_client.definitions import GatewayPortMode, LinkStatus, PoEMode from tplink_omada_client.devices import ( OmadaDevice, - OmadaGateway, OmadaGatewayPortConfig, OmadaGatewayPortStatus, ) @@ -95,7 +94,9 @@ GATEWAY_PORT_SENSORS: list[GatewayPortBinarySensorEntityDescription] = [ ] -class OmadaGatewayPortBinarySensor(OmadaDeviceEntity[OmadaGateway], BinarySensorEntity): +class OmadaGatewayPortBinarySensor( + OmadaDeviceEntity[OmadaGatewayCoordinator], BinarySensorEntity +): """Binary status of a property on an internet gateway.""" entity_description: GatewayPortBinarySensorEntityDescription diff --git a/homeassistant/components/tplink_omada/entity.py b/homeassistant/components/tplink_omada/entity.py index 4ae9dc733d8..a0bb562c652 100644 --- a/homeassistant/components/tplink_omada/entity.py +++ b/homeassistant/components/tplink_omada/entity.py @@ -1,6 +1,6 @@ """Base entity definitions.""" -from typing import Generic, TypeVar +from typing import Any, Generic, TypeVar from tplink_omada_client.devices import OmadaDevice @@ -11,13 +11,13 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .coordinator import OmadaCoordinator -T = TypeVar("T") +T = TypeVar("T", bound="OmadaCoordinator[Any]") -class OmadaDeviceEntity(CoordinatorEntity[OmadaCoordinator[T]], Generic[T]): +class OmadaDeviceEntity(CoordinatorEntity[T], Generic[T]): """Common base class for all entities associated with Omada SDN Devices.""" - def __init__(self, coordinator: OmadaCoordinator[T], device: OmadaDevice) -> None: + def __init__(self, coordinator: T, device: OmadaDevice) -> None: """Initialize the device.""" super().__init__(coordinator) self.device = device diff --git a/homeassistant/components/tplink_omada/switch.py b/homeassistant/components/tplink_omada/switch.py index 30974e829e2..b8abb4cb773 100644 --- a/homeassistant/components/tplink_omada/switch.py +++ b/homeassistant/components/tplink_omada/switch.py @@ -5,17 +5,19 @@ from __future__ import annotations from collections.abc import Awaitable, Callable from dataclasses import dataclass from functools import partial -from typing import Any +from typing import Any, Generic, TypeVar from tplink_omada_client import OmadaSiteClient, SwitchPortOverrides -from tplink_omada_client.definitions import GatewayPortMode, PoEMode +from tplink_omada_client.definitions import GatewayPortMode, PoEMode, PortType from tplink_omada_client.devices import ( OmadaDevice, OmadaGateway, + OmadaGatewayPortConfig, OmadaGatewayPortStatus, OmadaSwitch, OmadaSwitchPortDetails, ) +from tplink_omada_client.omadasiteclient import GatewayPortSettings from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry @@ -29,8 +31,13 @@ from .controller import ( OmadaSiteController, OmadaSwitchPortCoordinator, ) +from .coordinator import OmadaCoordinator from .entity import OmadaDeviceEntity +TPort = TypeVar("TPort") +TDevice = TypeVar("TDevice", bound="OmadaDevice") +TCoordinator = TypeVar("TCoordinator", bound="OmadaCoordinator[Any]") + async def async_setup_entry( hass: HomeAssistant, @@ -51,37 +58,110 @@ async def async_setup_entry( coordinator = controller.get_switch_port_coordinator(switch) await coordinator.async_request_refresh() - for idx, port_id in enumerate(coordinator.data): - if idx < switch.device_capabilities.poe_ports: - entities.append( - OmadaNetworkSwitchPortPoEControl(coordinator, switch, port_id) - ) + entities.extend( + OmadaDevicePortSwitchEntity[ + OmadaSwitchPortCoordinator, OmadaSwitch, OmadaSwitchPortDetails + ]( + coordinator, + switch, + port.port_id, + desc, + port_name=_get_switch_port_base_name(port), + ) + for port in coordinator.data.values() + for desc in SWITCH_PORT_DETAILS_SWITCHES + if desc.exists_func(switch, port) + ) gateway_coordinator = await controller.get_gateway_coordinator() if gateway_coordinator: for gateway in gateway_coordinator.data.values(): entities.extend( - OmadaGatewayPortSwitchEntity( - gateway_coordinator, gateway, p.port_number, desc - ) + OmadaDevicePortSwitchEntity[ + OmadaGatewayCoordinator, OmadaGateway, OmadaGatewayPortStatus + ](gateway_coordinator, gateway, p.port_number, desc) for p in gateway.port_status - for desc in GATEWAY_PORT_SWITCHES - if desc.exists_func(p) + for desc in GATEWAY_PORT_STATUS_SWITCHES + if desc.exists_func(gateway, p) + ) + entities.extend( + OmadaDevicePortSwitchEntity[ + OmadaGatewayCoordinator, OmadaGateway, OmadaGatewayPortConfig + ](gateway_coordinator, gateway, p.port_number, desc) + for p in gateway.port_configs + for desc in GATEWAY_PORT_CONFIG_SWITCHES + if desc.exists_func(gateway, p) ) async_add_entities(entities) -@dataclass(frozen=True, kw_only=True) -class GatewayPortSwitchEntityDescription(SwitchEntityDescription): - """Entity description for a toggle switch derived from a gateway port.""" +def _get_switch_port_base_name(port: OmadaSwitchPortDetails) -> str: + """Get display name for a switch port.""" - exists_func: Callable[[OmadaGatewayPortStatus], bool] = lambda _: True - set_func: Callable[ - [OmadaSiteClient, OmadaDevice, OmadaGatewayPortStatus, bool], - Awaitable[OmadaGatewayPortStatus], + if port.name == f"Port{port.port}": + return str(port.port) + return f"{port.port} ({port.name})" + + +@dataclass(frozen=True, kw_only=True) +class OmadaDevicePortSwitchEntityDescription( + SwitchEntityDescription, Generic[TCoordinator, TDevice, TPort] +): + """Entity description for a toggle switch derived from a network port on an Omada device.""" + + exists_func: Callable[[TDevice, TPort], bool] = lambda _, p: True + coordinator_update_func: Callable[ + [TCoordinator, TDevice, int | str], TPort | None + ] = lambda *_: None + set_func: Callable[[OmadaSiteClient, TDevice, TPort, bool], Awaitable[TPort]] + update_func: Callable[[TPort], bool] + refresh_after_set: bool = False + + +@dataclass(frozen=True, kw_only=True) +class OmadaSwitchPortSwitchEntityDescription( + OmadaDevicePortSwitchEntityDescription[ + OmadaSwitchPortCoordinator, OmadaSwitch, OmadaSwitchPortDetails ] - update_func: Callable[[OmadaGatewayPortStatus], bool] +): + """Entity description for a toggle switch for a feature of a Port on an Omada Switch.""" + + coordinator_update_func: Callable[ + [OmadaSwitchPortCoordinator, OmadaSwitch, int | str], + OmadaSwitchPortDetails | None, + ] = lambda coord, _, port_id: coord.data.get(str(port_id)) + + +@dataclass(frozen=True, kw_only=True) +class OmadaGatewayPortConfigSwitchEntityDescription( + OmadaDevicePortSwitchEntityDescription[ + OmadaGatewayCoordinator, OmadaGateway, OmadaGatewayPortConfig + ] +): + """Entity description for a toggle switch for a configuration of a Port on an Omada Gateway.""" + + coordinator_update_func: Callable[ + [OmadaGatewayCoordinator, OmadaGateway, int | str], + OmadaGatewayPortConfig | None, + ] = lambda coord, device, port_id: next( + p for p in coord.data[device.mac].port_configs if p.port_number == port_id + ) + + +@dataclass(frozen=True, kw_only=True) +class OmadaGatewayPortStatusSwitchEntityDescription( + OmadaDevicePortSwitchEntityDescription[ + OmadaGatewayCoordinator, OmadaGateway, OmadaGatewayPortStatus + ] +): + """Entity description for a toggle switch for a status of a Port on an Omada Gateway.""" + + coordinator_update_func: Callable[ + [OmadaGatewayCoordinator, OmadaGateway, int | str], OmadaGatewayPortStatus + ] = lambda coord, device, port_id: next( + p for p in coord.data[device.mac].port_status if p.port_number == port_id + ) def _wan_connect_disconnect( @@ -96,109 +176,82 @@ def _wan_connect_disconnect( ) -GATEWAY_PORT_SWITCHES: list[GatewayPortSwitchEntityDescription] = [ - GatewayPortSwitchEntityDescription( +SWITCH_PORT_DETAILS_SWITCHES: list[OmadaSwitchPortSwitchEntityDescription] = [ + OmadaSwitchPortSwitchEntityDescription( + key="poe", + translation_key="poe_control", + exists_func=lambda d, p: d.device_capabilities.supports_poe + and p.type != PortType.SFP, + set_func=lambda client, device, port, enable: client.update_switch_port( + device, port, overrides=SwitchPortOverrides(enable_poe=enable) + ), + update_func=lambda p: p.poe_mode != PoEMode.DISABLED, + entity_category=EntityCategory.CONFIG, + ) +] + +GATEWAY_PORT_STATUS_SWITCHES: list[OmadaGatewayPortStatusSwitchEntityDescription] = [ + OmadaGatewayPortStatusSwitchEntityDescription( key="wan_connect_ipv4", translation_key="wan_connect_ipv4", - exists_func=lambda p: p.mode == GatewayPortMode.WAN, + exists_func=lambda _, p: p.mode == GatewayPortMode.WAN, set_func=partial(_wan_connect_disconnect, ipv6=False), update_func=lambda p: p.wan_connected, + refresh_after_set=True, ), - GatewayPortSwitchEntityDescription( + OmadaGatewayPortStatusSwitchEntityDescription( key="wan_connect_ipv6", translation_key="wan_connect_ipv6", - exists_func=lambda p: p.mode == GatewayPortMode.WAN and p.wan_ipv6_enabled, + exists_func=lambda _, p: p.mode == GatewayPortMode.WAN and p.wan_ipv6_enabled, set_func=partial(_wan_connect_disconnect, ipv6=True), update_func=lambda p: p.ipv6_wan_connected, + refresh_after_set=True, + ), +] + +GATEWAY_PORT_CONFIG_SWITCHES: list[OmadaGatewayPortConfigSwitchEntityDescription] = [ + OmadaGatewayPortConfigSwitchEntityDescription( + key="poe", + translation_key="poe_control", + exists_func=lambda _, port: port.poe_mode != PoEMode.NONE, + set_func=lambda client, device, port, enable: client.set_gateway_port_settings( + port.port_number, GatewayPortSettings(enable_poe=enable), device + ), + update_func=lambda p: p.poe_mode != PoEMode.DISABLED, ), ] -def get_port_base_name(port: OmadaSwitchPortDetails) -> str: - """Get display name for a switch port.""" - - if port.name == f"Port{port.port}": - return f"{port.port}" - return f"{port.port} ({port.name})" - - -class OmadaNetworkSwitchPortPoEControl( - OmadaDeviceEntity[OmadaSwitchPortDetails], SwitchEntity +class OmadaDevicePortSwitchEntity( + OmadaDeviceEntity[TCoordinator], + SwitchEntity, + Generic[TCoordinator, TDevice, TPort], ): - """Representation of a PoE control toggle on a single network port on a switch.""" + """Generic toggle switch entity for a Netork Port of an Omada Device.""" _attr_has_entity_name = True - _attr_translation_key = "poe_control" - _attr_entity_category = EntityCategory.CONFIG + _port_details: TPort | None = None + entity_description: OmadaDevicePortSwitchEntityDescription[ + TCoordinator, TDevice, TPort + ] def __init__( self, - coordinator: OmadaSwitchPortCoordinator, - device: OmadaSwitch, - port_id: str, - ) -> None: - """Initialize the PoE switch.""" - super().__init__(coordinator, device) - self.port_id = port_id - self.port_details = coordinator.data[port_id] - self.omada_client = coordinator.omada_client - self._attr_unique_id = f"{device.mac}_{port_id}_poe" - self._attr_translation_placeholders = { - "port_name": get_port_base_name(self.port_details) - } - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - await super().async_added_to_hass() - self._refresh_state() - - async def _async_turn_on_off_poe(self, enable: bool) -> None: - self.port_details = await self.omada_client.update_switch_port( - self.device, - self.port_details, - overrides=SwitchPortOverrides(enable_poe=enable), - ) - self._refresh_state() - - async def async_turn_on(self, **kwargs: Any) -> None: - """Turn the entity on.""" - await self._async_turn_on_off_poe(True) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn the entity off.""" - await self._async_turn_on_off_poe(False) - - def _refresh_state(self) -> None: - self._attr_is_on = self.port_details.poe_mode != PoEMode.DISABLED - self.async_write_ha_state() - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self.port_details = self.coordinator.data[self.port_id] - self._refresh_state() - - -class OmadaGatewayPortSwitchEntity(OmadaDeviceEntity[OmadaGateway], SwitchEntity): - """Generic toggle switch on a Gateway entity.""" - - _attr_has_entity_name = True - _port_details: OmadaGatewayPortStatus | None = None - entity_description: GatewayPortSwitchEntityDescription - - def __init__( - self, - coordinator: OmadaGatewayCoordinator, - device: OmadaGateway, - port_number: int, - entity_description: GatewayPortSwitchEntityDescription, + coordinator: TCoordinator, + device: TDevice, + port_id: int | str, + entity_description: OmadaDevicePortSwitchEntityDescription[ + TCoordinator, TDevice, TPort + ], + port_name: str | None = None, ) -> None: """Initialize the toggle switch.""" super().__init__(coordinator, device) self.entity_description = entity_description - self._port_number = port_number - self._attr_unique_id = f"{device.mac}_{port_number}_{entity_description.key}" - self._attr_translation_placeholders = {"port_name": f"{port_number}"} + self._device = device + self._port_id = port_id + self._attr_unique_id = f"{device.mac}_{port_id}_{entity_description.key}" + self._attr_translation_placeholders = {"port_name": port_name or str(port_id)} async def async_added_to_hass(self) -> None: """When entity is added to hass.""" @@ -208,11 +261,16 @@ class OmadaGatewayPortSwitchEntity(OmadaDeviceEntity[OmadaGateway], SwitchEntity async def _async_turn_on_off(self, enable: bool) -> None: if self._port_details: self._port_details = await self.entity_description.set_func( - self.coordinator.omada_client, self.device, self._port_details, enable + self.coordinator.omada_client, self._device, self._port_details, enable ) - self._attr_is_on = enable - # Refresh to make sure the requested changes stuck - await self.coordinator.async_request_refresh() + + if self.entity_description.refresh_after_set: + # Refresh to make sure the requested changes stuck + self._attr_is_on = enable + await self.coordinator.async_request_refresh() + elif self._port_details: + self._attr_is_on = self.entity_description.update_func(self._port_details) + self.async_write_ha_state() async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" @@ -228,14 +286,12 @@ class OmadaGatewayPortSwitchEntity(OmadaDeviceEntity[OmadaGateway], SwitchEntity return bool( super().available and self._port_details - and self.entity_description.exists_func(self._port_details) + and self.entity_description.exists_func(self._device, self._port_details) ) def _do_update(self) -> None: - gateway = self.coordinator.data[self.device.mac] - - port = next( - p for p in gateway.port_status if p.port_number == self._port_number + port = self.entity_description.coordinator_update_func( + self.coordinator, self._device, self._port_id ) if port: self._port_details = port diff --git a/homeassistant/components/tplink_omada/update.py b/homeassistant/components/tplink_omada/update.py index 8a0d32bda18..5e87d11474b 100644 --- a/homeassistant/components/tplink_omada/update.py +++ b/homeassistant/components/tplink_omada/update.py @@ -88,7 +88,7 @@ async def async_setup_entry( class OmadaDeviceUpdate( - OmadaDeviceEntity[FirmwareUpdateStatus], + OmadaDeviceEntity[OmadaFirmwareUpdateCoodinator], UpdateEntity, ): """Firmware update status for Omada SDN devices.""" diff --git a/tests/components/tplink_omada/snapshots/test_switch.ambr b/tests/components/tplink_omada/snapshots/test_switch.ambr index 8a08cbc292d..282d2a4a6a5 100644 --- a/tests/components/tplink_omada/snapshots/test_switch.ambr +++ b/tests/components/tplink_omada/snapshots/test_switch.ambr @@ -51,6 +51,19 @@ 'state': 'on', }) # --- +# name: test_gateway_port_poe_switch + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Router Port 5 PoE', + }), + 'context': , + 'entity_id': 'switch.test_router_port_5_poe', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_poe_switches StateSnapshot({ 'attributes': ReadOnlyDict({ diff --git a/tests/components/tplink_omada/test_switch.py b/tests/components/tplink_omada/test_switch.py index bcc727da06e..78b22a4e829 100644 --- a/tests/components/tplink_omada/test_switch.py +++ b/tests/components/tplink_omada/test_switch.py @@ -9,6 +9,7 @@ from tplink_omada_client import SwitchPortOverrides from tplink_omada_client.definitions import PoEMode from tplink_omada_client.devices import ( OmadaGateway, + OmadaGatewayPortConfig, OmadaGatewayPortStatus, OmadaSwitch, OmadaSwitchPortDetails, @@ -55,6 +56,17 @@ async def test_poe_switches( ) +async def test_sfp_port_has_no_poe_switch( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test PoE switch SFP ports have no PoE controls.""" + entity = hass.states.get("switch.test_poe_switch_port_9_poe") + assert entity is None + entity = hass.states.get("switch.test_poe_switch_port_8_poe") + assert entity is not None + + async def test_gateway_connect_ipv4_switch( hass: HomeAssistant, mock_omada_site_client: MagicMock, @@ -107,6 +119,70 @@ async def test_gateway_connect_ipv4_switch( assert entity.state == "on" +async def test_gateway_port_poe_switch( + hass: HomeAssistant, + mock_omada_site_client: MagicMock, + init_integration: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test gateway connected switches.""" + gateway_mac = "AA-BB-CC-DD-EE-FF" + + entity_id = "switch.test_router_port_5_poe" + entity = hass.states.get(entity_id) + assert entity == snapshot + + test_gateway = await mock_omada_site_client.get_gateway(gateway_mac) + port_config = test_gateway.port_configs[4] + assert port_config.port_number == 5 + + mock_omada_site_client.set_gateway_port_settings.return_value = ( + OmadaGatewayPortConfig(port_config.raw_data, poe_enabled=False) + ) + await call_service(hass, "turn_off", entity_id) + _assert_gateway_poe_set(mock_omada_site_client, test_gateway, False) + + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(entity_id) + assert entity.state == "off" + + mock_omada_site_client.set_gateway_port_settings.reset_mock() + mock_omada_site_client.set_gateway_port_settings.return_value = port_config + await call_service(hass, "turn_on", entity_id) + _assert_gateway_poe_set(mock_omada_site_client, test_gateway, True) + + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(entity_id) + assert entity.state == "on" + + +async def test_gaateway_wan_port_has_no_poe_switch( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test PoE switch SFP ports have no PoE controls.""" + entity = hass.states.get("switch.test_router_port_1_poe") + assert entity is None + entity = hass.states.get("switch.test_router_port_9_poe") + assert entity is not None + + +def _assert_gateway_poe_set(mock_omada_site_client, test_gateway, poe_enabled: bool): + ( + called_port, + called_settings, + called_gateway, + ) = mock_omada_site_client.set_gateway_port_settings.call_args.args + mock_omada_site_client.set_gateway_port_settings.assert_called_once() + assert called_port == 5 + assert called_settings.enable_poe is poe_enabled + assert called_gateway == test_gateway + + async def test_gateway_api_fail_disables_switch_entities( hass: HomeAssistant, mock_omada_site_client: MagicMock, @@ -188,18 +264,22 @@ async def _test_poe_switch( mock_omada_site_client.update_switch_port.return_value = await _update_port_details( mock_omada_site_client, port_num, False ) + await call_service(hass, "turn_off", entity_id) mock_omada_site_client.update_switch_port.assert_called_once() ( device, switch_port_details, ) = mock_omada_site_client.update_switch_port.call_args.args + assert_update_switch_port( device, switch_port_details, False, **mock_omada_site_client.update_switch_port.call_args.kwargs, ) + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() entity = hass.states.get(entity_id) assert entity.state == "off" @@ -216,6 +296,7 @@ async def _test_poe_switch( True, **mock_omada_site_client.update_switch_port.call_args.kwargs, ) + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) await hass.async_block_till_done() entity = hass.states.get(entity_id) assert entity.state == "on" From c21d508c2d9d14ca136ff808e858833273640958 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 27 Mar 2024 16:10:32 +0100 Subject: [PATCH 1574/1691] Start deprecation of auxiliary heater in ClimateEntity (#112532) * Start deprecation of auxiliary heater in ClimateEntity * No issue for core integrations * Remove unneded strings * Move report to state_attributes --- homeassistant/components/climate/__init__.py | 53 +++ homeassistant/components/climate/strings.json | 10 + tests/components/climate/test_init.py | 308 ++++++++++++++++++ 3 files changed, 371 insertions(+) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 87327945c68..00fd69ce63b 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -24,6 +24,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers import issue_registry as ir import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, @@ -40,6 +41,7 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType +from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue from homeassistant.util.unit_conversion import TemperatureConverter from . import group as group_pre_import # noqa: F401 @@ -309,6 +311,8 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_target_temperature: float | None = None _attr_temperature_unit: str + __climate_reported_legacy_aux = False + __mod_supported_features: ClimateEntityFeature = ClimateEntityFeature(0) # Integrations should set `_enable_turn_on_off_backwards_compatibility` to False # once migrated and set the feature flags TURN_ON/TURN_OFF as needed. @@ -404,6 +408,50 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF ) + def _report_legacy_aux(self) -> None: + """Log warning and create an issue if the entity implements legacy auxiliary heater.""" + + report_issue = async_suggest_report_issue( + self.hass, + integration_domain=self.platform.platform_name, + module=type(self).__module__, + ) + _LOGGER.warning( + ( + "%s::%s implements the `is_aux_heat` property or uses the auxiliary " + "heater methods in a subclass of ClimateEntity which is " + "deprecated and will be unsupported from Home Assistant 2024.10." + " Please %s" + ), + self.platform.platform_name, + self.__class__.__name__, + report_issue, + ) + + translation_placeholders = {"platform": self.platform.platform_name} + translation_key = "deprecated_climate_aux_no_url" + issue_tracker = async_get_issue_tracker( + self.hass, + integration_domain=self.platform.platform_name, + module=type(self).__module__, + ) + if issue_tracker: + translation_placeholders["issue_tracker"] = issue_tracker + translation_key = "deprecated_climate_aux_url_custom" + ir.async_create_issue( + self.hass, + DOMAIN, + f"deprecated_climate_aux_{self.platform.platform_name}", + breaks_in_ha_version="2024.10.0", + is_fixable=False, + is_persistent=False, + issue_domain=self.platform.platform_name, + severity=ir.IssueSeverity.WARNING, + translation_key=translation_key, + translation_placeholders=translation_placeholders, + ) + self.__climate_reported_legacy_aux = True + @final @property def state(self) -> str | None: @@ -508,6 +556,11 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if ClimateEntityFeature.AUX_HEAT in supported_features: data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF + if ( + self.__climate_reported_legacy_aux is False + and "custom_components" in type(self).__module__ + ): + self._report_legacy_aux() return data diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index eb9285b0c4f..c31d22ccbeb 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -238,6 +238,16 @@ } } }, + "issues": { + "deprecated_climate_aux_url_custom": { + "title": "The {platform} custom integration is using deprecated climate auxiliary heater", + "description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease create a bug report at {issue_tracker}.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." + }, + "deprecated_climate_aux_no_url": { + "title": "[%key:component::climate::issues::deprecated_climate_aux_url_custom::title%]", + "description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." + } + }, "exceptions": { "not_valid_preset_mode": { "message": "Preset mode {mode} is not valid. Valid preset modes are: {modes}." diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 65b7287f549..ed942fb1464 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -23,12 +23,14 @@ from homeassistant.components.climate.const import ( SERVICE_SET_FAN_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_SWING_MODE, + SERVICE_SET_TEMPERATURE, ClimateEntityFeature, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.entity_platform import AddEntitiesCallback from tests.common import ( @@ -771,3 +773,309 @@ async def test_sync_toggle(hass: HomeAssistant) -> None: await climate.async_toggle() assert climate.toggle.called + + +ISSUE_TRACKER = "https://blablabla.com" + + +@pytest.mark.parametrize( + ( + "manifest_extra", + "translation_key", + "translation_placeholders_extra", + "report", + "module", + ), + [ + ( + {}, + "deprecated_climate_aux_no_url", + {}, + "report it to the author of the 'test' custom integration", + "custom_components.test.climate", + ), + ( + {"issue_tracker": ISSUE_TRACKER}, + "deprecated_climate_aux_url_custom", + {"issue_tracker": ISSUE_TRACKER}, + "create a bug report at https://blablabla.com", + "custom_components.test.climate", + ), + ], +) +async def test_issue_aux_property_deprecated( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + config_flow_fixture: None, + manifest_extra: dict[str, str], + translation_key: str, + translation_placeholders_extra: dict[str, str], + report: str, + module: str, +) -> None: + """Test the issue is raised on deprecated auxiliary heater attributes.""" + + class MockClimateEntityWithAux(MockClimateEntity): + """Mock climate class with mocked aux heater.""" + + _attr_supported_features = ( + ClimateEntityFeature.AUX_HEAT | ClimateEntityFeature.TARGET_TEMPERATURE + ) + + @property + def is_aux_heat(self) -> bool | None: + """Return true if aux heater. + + Requires ClimateEntityFeature.AUX_HEAT. + """ + return True + + async def async_turn_aux_heat_on(self) -> None: + """Turn auxiliary heater on.""" + await self.hass.async_add_executor_job(self.turn_aux_heat_on) + + async def async_turn_aux_heat_off(self) -> None: + """Turn auxiliary heater off.""" + await self.hass.async_add_executor_job(self.turn_aux_heat_off) + + # Fake the module is custom component or built in + MockClimateEntityWithAux.__module__ = module + + climate_entity = MockClimateEntityWithAux( + name="Testing", + entity_id="climate.testing", + ) + + async def async_setup_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Set up test config entry.""" + await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) + return True + + async def async_setup_entry_climate_platform( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up test weather platform via config entry.""" + async_add_entities([climate_entity]) + + mock_integration( + hass, + MockModule( + "test", + async_setup_entry=async_setup_entry_init, + partial_manifest=manifest_extra, + ), + built_in=False, + ) + mock_platform( + hass, + "test.climate", + MockPlatform(async_setup_entry=async_setup_entry_climate_platform), + ) + + config_entry = MockConfigEntry(domain="test") + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert climate_entity.state == HVACMode.HEAT + + issues = ir.async_get(hass) + issue = issues.async_get_issue("climate", "deprecated_climate_aux_test") + assert issue + assert issue.issue_domain == "test" + assert issue.issue_id == "deprecated_climate_aux_test" + assert issue.translation_key == translation_key + assert ( + issue.translation_placeholders + == {"platform": "test"} | translation_placeholders_extra + ) + + assert ( + "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " + "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " + f"and will be unsupported from Home Assistant 2024.10. Please {report}" + ) in caplog.text + + # Assert we only log warning once + caplog.clear() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.test", + "temperature": "25", + }, + blocking=True, + ) + await hass.async_block_till_done() + + assert ("implements the `is_aux_heat` property") not in caplog.text + + +@pytest.mark.parametrize( + ( + "manifest_extra", + "translation_key", + "translation_placeholders_extra", + "report", + "module", + ), + [ + ( + {"issue_tracker": ISSUE_TRACKER}, + "deprecated_climate_aux_url", + {"issue_tracker": ISSUE_TRACKER}, + "create a bug report at https://blablabla.com", + "homeassistant.components.test.climate", + ), + ], +) +async def test_no_issue_aux_property_deprecated_for_core( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + config_flow_fixture: None, + manifest_extra: dict[str, str], + translation_key: str, + translation_placeholders_extra: dict[str, str], + report: str, + module: str, +) -> None: + """Test the no issue on deprecated auxiliary heater attributes for core integrations.""" + + class MockClimateEntityWithAux(MockClimateEntity): + """Mock climate class with mocked aux heater.""" + + _attr_supported_features = ClimateEntityFeature.AUX_HEAT + + @property + def is_aux_heat(self) -> bool | None: + """Return true if aux heater. + + Requires ClimateEntityFeature.AUX_HEAT. + """ + return True + + async def async_turn_aux_heat_on(self) -> None: + """Turn auxiliary heater on.""" + await self.hass.async_add_executor_job(self.turn_aux_heat_on) + + async def async_turn_aux_heat_off(self) -> None: + """Turn auxiliary heater off.""" + await self.hass.async_add_executor_job(self.turn_aux_heat_off) + + # Fake the module is custom component or built in + MockClimateEntityWithAux.__module__ = module + + climate_entity = MockClimateEntityWithAux( + name="Testing", + entity_id="climate.testing", + ) + + async def async_setup_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Set up test config entry.""" + await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) + return True + + async def async_setup_entry_climate_platform( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up test weather platform via config entry.""" + async_add_entities([climate_entity]) + + mock_integration( + hass, + MockModule( + "test", + async_setup_entry=async_setup_entry_init, + partial_manifest=manifest_extra, + ), + built_in=False, + ) + mock_platform( + hass, + "test.climate", + MockPlatform(async_setup_entry=async_setup_entry_climate_platform), + ) + + config_entry = MockConfigEntry(domain="test") + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert climate_entity.state == HVACMode.HEAT + + issues = ir.async_get(hass) + issue = issues.async_get_issue("climate", "deprecated_climate_aux_test") + assert not issue + + assert ( + "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " + "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " + f"and will be unsupported from Home Assistant 2024.10. Please {report}" + ) not in caplog.text + + +async def test_no_issue_no_aux_property( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + config_flow_fixture: None, +) -> None: + """Test the issue is raised on deprecated auxiliary heater attributes.""" + + climate_entity = MockClimateEntity( + name="Testing", + entity_id="climate.testing", + ) + + async def async_setup_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Set up test config entry.""" + await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) + return True + + async def async_setup_entry_climate_platform( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up test weather platform via config entry.""" + async_add_entities([climate_entity]) + + mock_integration( + hass, + MockModule( + "test", + async_setup_entry=async_setup_entry_init, + ), + built_in=False, + ) + mock_platform( + hass, + "test.climate", + MockPlatform(async_setup_entry=async_setup_entry_climate_platform), + ) + + config_entry = MockConfigEntry(domain="test") + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert climate_entity.state == HVACMode.HEAT + + issues = ir.async_get(hass) + assert len(issues.issues) == 0 + + assert ( + "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " + "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " + "and will be unsupported from Home Assistant 2024.10." + ) not in caplog.text From 4d27f4be51d3325f29a71d7b2bf7809fe3d615c6 Mon Sep 17 00:00:00 2001 From: YogevBokobza Date: Wed, 27 Mar 2024 17:15:49 +0200 Subject: [PATCH 1575/1691] Refactor switcher kis (#114281) * switcher: small refactoring * swithcer: Update switch.py * more refactoring * fix ruff --- homeassistant/components/switcher_kis/cover.py | 2 +- homeassistant/components/switcher_kis/switch.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/switcher_kis/cover.py b/homeassistant/components/switcher_kis/cover.py index 88a3b5050d9..69ec501c4a7 100644 --- a/homeassistant/components/switcher_kis/cover.py +++ b/homeassistant/components/switcher_kis/cover.py @@ -126,6 +126,6 @@ class SwitcherCoverEntity( """Move the cover to a specific position.""" await self._async_call_api(API_SET_POSITON, kwargs[ATTR_POSITION]) - async def async_stop_cover(self, **_kwargs: Any) -> None: + async def async_stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" await self._async_call_api(API_STOP) diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index ba5291d6ec0..b7c79f6dbc3 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -34,6 +34,9 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +API_CONTROL_DEVICE = "control_device" +API_SET_AUTO_SHUTDOWN = "set_auto_shutdown" + SERVICE_SET_AUTO_OFF_SCHEMA = { vol.Required(CONF_AUTO_OFF): cv.time_period_str, } @@ -141,13 +144,13 @@ class SwitcherBaseSwitchEntity( async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - await self._async_call_api("control_device", Command.ON) + await self._async_call_api(API_CONTROL_DEVICE, Command.ON) self.control_result = True self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - await self._async_call_api("control_device", Command.OFF) + await self._async_call_api(API_CONTROL_DEVICE, Command.OFF) self.control_result = False self.async_write_ha_state() @@ -181,11 +184,11 @@ class SwitcherWaterHeaterSwitchEntity(SwitcherBaseSwitchEntity): async def async_set_auto_off_service(self, auto_off: timedelta) -> None: """Use for handling setting device auto-off service calls.""" - await self._async_call_api("set_auto_shutdown", auto_off) + await self._async_call_api(API_SET_AUTO_SHUTDOWN, auto_off) self.async_write_ha_state() async def async_turn_on_with_timer_service(self, timer_minutes: int) -> None: """Use for turning device on with a timer service calls.""" - await self._async_call_api("control_device", Command.ON, timer_minutes) + await self._async_call_api(API_CONTROL_DEVICE, Command.ON, timer_minutes) self.control_result = True self.async_write_ha_state() From 6289082dab2f94a45148c65145dde9732eafa45c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 16:20:51 +0100 Subject: [PATCH 1576/1691] Migrate Rova to has entity name (#114303) --- homeassistant/components/rova/icons.json | 18 +++++ homeassistant/components/rova/sensor.py | 38 +++++----- homeassistant/components/rova/strings.json | 16 ++++ .../rova/snapshots/test_sensor.ambr | 76 +++++++++---------- 4 files changed, 89 insertions(+), 59 deletions(-) create mode 100644 homeassistant/components/rova/icons.json diff --git a/homeassistant/components/rova/icons.json b/homeassistant/components/rova/icons.json new file mode 100644 index 00000000000..dc3e85ca8c1 --- /dev/null +++ b/homeassistant/components/rova/icons.json @@ -0,0 +1,18 @@ +{ + "entity": { + "sensor": { + "bio": { + "default": "mdi:leaf" + }, + "paper": { + "default": "mdi:file" + }, + "plastic": { + "default": "mdi:recycle" + }, + "residual": { + "default": "mdi:delete" + } + } + } +} diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index 471de335d94..0fae976748a 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -17,6 +17,7 @@ from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant from homeassistant.data_entry_flow import FlowResultType import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -27,28 +28,24 @@ from .coordinator import RovaCoordinator ISSUE_PLACEHOLDER = {"url": "/config/integrations/dashboard/add?domain=rova"} -SENSOR_TYPES = { - "bio": SensorEntityDescription( +SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="gft", - name="bio", - icon="mdi:recycle", + translation_key="bio", ), - "paper": SensorEntityDescription( + SensorEntityDescription( key="papier", - name="paper", - icon="mdi:recycle", + translation_key="paper", ), - "plastic": SensorEntityDescription( + SensorEntityDescription( key="pmd", - name="plastic", - icon="mdi:recycle", + translation_key="plastic", ), - "residual": SensorEntityDescription( + SensorEntityDescription( key="restafval", - name="residual", - icon="mdi:recycle", + translation_key="residual", ), -} +) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -119,14 +116,16 @@ async def async_setup_entry( unique_id = entry.unique_id async_add_entities( - RovaSensor(unique_id, description, coordinator) - for key, description in SENSOR_TYPES.items() + RovaSensor(unique_id, description, coordinator) for description in SENSOR_TYPES ) class RovaSensor(CoordinatorEntity[RovaCoordinator], SensorEntity): """Representation of a Rova sensor.""" + _attr_device_class = SensorDeviceClass.TIMESTAMP + _attr_has_entity_name = True + def __init__( self, unique_id: str, @@ -136,10 +135,11 @@ class RovaSensor(CoordinatorEntity[RovaCoordinator], SensorEntity): """Initialize the sensor.""" super().__init__(coordinator) self.entity_description = description - - self._attr_name = f"{unique_id}_{description.key}" self._attr_unique_id = f"{unique_id}_{description.key}" - self._attr_device_class = SensorDeviceClass.TIMESTAMP + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, unique_id)}, + entry_type=DeviceEntryType.SERVICE, + ) @property def native_value(self) -> datetime | None: diff --git a/homeassistant/components/rova/strings.json b/homeassistant/components/rova/strings.json index 2eb6ba1797f..9a63bf27aec 100644 --- a/homeassistant/components/rova/strings.json +++ b/homeassistant/components/rova/strings.json @@ -33,5 +33,21 @@ "title": "Rova does not collect at this address anymore", "description": "Rova does not collect at {zip_code} anymore.\n\nPlease remove the integration." } + }, + "entity": { + "sensor": { + "bio": { + "name": "Bio" + }, + "paper": { + "name": "Paper" + }, + "plastic": { + "name": "Plastic" + }, + "residual": { + "name": "Residual" + } + } } } diff --git a/tests/components/rova/snapshots/test_sensor.ambr b/tests/components/rova/snapshots/test_sensor.ambr index a70b62c3672..866f1c735c1 100644 --- a/tests/components/rova/snapshots/test_sensor.ambr +++ b/tests/components/rova/snapshots/test_sensor.ambr @@ -1,5 +1,5 @@ # serializer version: 1 -# name: test_all_entities[sensor.8381be13_gft-entry] +# name: test_all_entities[sensor.8381be_13_bio-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -11,8 +11,8 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.8381be13_gft', - 'has_entity_name': False, + 'entity_id': 'sensor.8381be_13_bio', + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -22,32 +22,31 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:recycle', - 'original_name': '8381BE13_gft', + 'original_icon': None, + 'original_name': 'Bio', 'platform': 'rova', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'bio', 'unique_id': '8381BE13_gft', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[sensor.8381be13_gft-state] +# name: test_all_entities[sensor.8381be_13_bio-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'timestamp', - 'friendly_name': '8381BE13_gft', - 'icon': 'mdi:recycle', + 'friendly_name': '8381BE 13 Bio', }), 'context': , - 'entity_id': 'sensor.8381be13_gft', + 'entity_id': 'sensor.8381be_13_bio', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2024-02-20T23:00:00+00:00', }) # --- -# name: test_all_entities[sensor.8381be13_papier-entry] +# name: test_all_entities[sensor.8381be_13_paper-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -59,8 +58,8 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.8381be13_papier', - 'has_entity_name': False, + 'entity_id': 'sensor.8381be_13_paper', + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -70,32 +69,31 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:recycle', - 'original_name': '8381BE13_papier', + 'original_icon': None, + 'original_name': 'Paper', 'platform': 'rova', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'paper', 'unique_id': '8381BE13_papier', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[sensor.8381be13_papier-state] +# name: test_all_entities[sensor.8381be_13_paper-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'timestamp', - 'friendly_name': '8381BE13_papier', - 'icon': 'mdi:recycle', + 'friendly_name': '8381BE 13 Paper', }), 'context': , - 'entity_id': 'sensor.8381be13_papier', + 'entity_id': 'sensor.8381be_13_paper', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2024-03-05T23:00:00+00:00', }) # --- -# name: test_all_entities[sensor.8381be13_pmd-entry] +# name: test_all_entities[sensor.8381be_13_plastic-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -107,8 +105,8 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.8381be13_pmd', - 'has_entity_name': False, + 'entity_id': 'sensor.8381be_13_plastic', + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -118,32 +116,31 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:recycle', - 'original_name': '8381BE13_pmd', + 'original_icon': None, + 'original_name': 'Plastic', 'platform': 'rova', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'plastic', 'unique_id': '8381BE13_pmd', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[sensor.8381be13_pmd-state] +# name: test_all_entities[sensor.8381be_13_plastic-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'timestamp', - 'friendly_name': '8381BE13_pmd', - 'icon': 'mdi:recycle', + 'friendly_name': '8381BE 13 Plastic', }), 'context': , - 'entity_id': 'sensor.8381be13_pmd', + 'entity_id': 'sensor.8381be_13_plastic', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2024-03-11T23:00:00+00:00', }) # --- -# name: test_all_entities[sensor.8381be13_restafval-entry] +# name: test_all_entities[sensor.8381be_13_residual-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -155,8 +152,8 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.8381be13_restafval', - 'has_entity_name': False, + 'entity_id': 'sensor.8381be_13_residual', + 'has_entity_name': True, 'hidden_by': None, 'icon': None, 'id': , @@ -166,25 +163,24 @@ 'options': dict({ }), 'original_device_class': , - 'original_icon': 'mdi:recycle', - 'original_name': '8381BE13_restafval', + 'original_icon': None, + 'original_name': 'Residual', 'platform': 'rova', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': None, + 'translation_key': 'residual', 'unique_id': '8381BE13_restafval', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[sensor.8381be13_restafval-state] +# name: test_all_entities[sensor.8381be_13_residual-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'timestamp', - 'friendly_name': '8381BE13_restafval', - 'icon': 'mdi:recycle', + 'friendly_name': '8381BE 13 Residual', }), 'context': , - 'entity_id': 'sensor.8381be13_restafval', + 'entity_id': 'sensor.8381be_13_residual', 'last_changed': , 'last_reported': , 'last_updated': , From fc596cde445a755a14170d939bd7cfeb74aaec79 Mon Sep 17 00:00:00 2001 From: Jeef Date: Wed, 27 Mar 2024 09:24:18 -0600 Subject: [PATCH 1577/1691] Version Bump for WeatherFlow Cloud Backing Lib (#114302) Version bump to support stations going offline for some time --- homeassistant/components/weatherflow_cloud/coordinator.py | 6 +++--- homeassistant/components/weatherflow_cloud/manifest.json | 2 +- homeassistant/components/weatherflow_cloud/weather.py | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/weatherflow_cloud/coordinator.py b/homeassistant/components/weatherflow_cloud/coordinator.py index 600a5e105c9..78b4f3be223 100644 --- a/homeassistant/components/weatherflow_cloud/coordinator.py +++ b/homeassistant/components/weatherflow_cloud/coordinator.py @@ -4,7 +4,7 @@ from datetime import timedelta from aiohttp import ClientResponseError from weatherflow4py.api import WeatherFlowRestAPI -from weatherflow4py.models.rest.unified import WeatherFlowData +from weatherflow4py.models.rest.unified import WeatherFlowDataREST from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed @@ -14,7 +14,7 @@ from .const import DOMAIN, LOGGER class WeatherFlowCloudDataUpdateCoordinator( - DataUpdateCoordinator[dict[int, WeatherFlowData]] + DataUpdateCoordinator[dict[int, WeatherFlowDataREST]] ): """Class to manage fetching REST Based WeatherFlow Forecast data.""" @@ -29,7 +29,7 @@ class WeatherFlowCloudDataUpdateCoordinator( update_interval=timedelta(minutes=15), ) - async def _async_update_data(self) -> dict[int, WeatherFlowData]: + async def _async_update_data(self) -> dict[int, WeatherFlowDataREST]: """Fetch data from WeatherFlow Forecast.""" try: async with self.weather_api: diff --git a/homeassistant/components/weatherflow_cloud/manifest.json b/homeassistant/components/weatherflow_cloud/manifest.json index ec5bd0ffd8a..8376bd1b50d 100644 --- a/homeassistant/components/weatherflow_cloud/manifest.json +++ b/homeassistant/components/weatherflow_cloud/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "iot_class": "cloud_polling", - "requirements": ["weatherflow4py==0.2.13"] + "requirements": ["weatherflow4py==0.2.17"] } diff --git a/homeassistant/components/weatherflow_cloud/weather.py b/homeassistant/components/weatherflow_cloud/weather.py index 8d40fab52cb..23aa6b1a031 100644 --- a/homeassistant/components/weatherflow_cloud/weather.py +++ b/homeassistant/components/weatherflow_cloud/weather.py @@ -2,7 +2,7 @@ from __future__ import annotations -from weatherflow4py.models.rest.unified import WeatherFlowData +from weatherflow4py.models.rest.unified import WeatherFlowDataREST from homeassistant.components.weather import ( Forecast, @@ -79,7 +79,7 @@ class WeatherFlowWeather( ) @property - def local_data(self) -> WeatherFlowData: + def local_data(self) -> WeatherFlowDataREST: """Return the local weather data object for this station.""" return self.coordinator.data[self.station_id] diff --git a/requirements_all.txt b/requirements_all.txt index 8284e9ea81b..0ab9ed75e09 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2838,7 +2838,7 @@ watchdog==2.3.1 waterfurnace==1.1.0 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.2.13 +weatherflow4py==0.2.17 # homeassistant.components.webmin webmin-xmlrpc==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47dc312e903..86242be4db4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2185,7 +2185,7 @@ wallbox==0.6.0 watchdog==2.3.1 # homeassistant.components.weatherflow_cloud -weatherflow4py==0.2.13 +weatherflow4py==0.2.17 # homeassistant.components.webmin webmin-xmlrpc==0.0.2 From 5aabb2a920ab3810e6fcdec4de4e8698b22a960f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 16:36:42 +0100 Subject: [PATCH 1578/1691] Finish ROVA init tests (#114315) * Finish ROVA init tests * Finish ROVA init tests * Finish ROVA init tests --- .../components/rova/snapshots/test_init.ambr | 31 +++++++++ tests/components/rova/test_init.py | 64 ++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 tests/components/rova/snapshots/test_init.ambr diff --git a/tests/components/rova/snapshots/test_init.ambr b/tests/components/rova/snapshots/test_init.ambr new file mode 100644 index 00000000000..340b0e6d472 --- /dev/null +++ b/tests/components/rova/snapshots/test_init.ambr @@ -0,0 +1,31 @@ +# serializer version: 1 +# name: test_service + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': , + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'rova', + '8381BE13', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': None, + 'model': None, + 'name': '8381BE 13', + 'name_by_user': None, + 'serial_number': None, + 'suggested_area': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- diff --git a/tests/components/rova/test_init.py b/tests/components/rova/test_init.py index 33149559f68..3dff0cf4c27 100644 --- a/tests/components/rova/test_init.py +++ b/tests/components/rova/test_init.py @@ -2,11 +2,73 @@ from unittest.mock import MagicMock +import pytest +from requests import ConnectTimeout +from syrupy import SnapshotAssertion + +from homeassistant.components.rova import DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import issue_registry as ir +from homeassistant.helpers import device_registry as dr, issue_registry as ir from tests.common import MockConfigEntry +from tests.components.rova import setup_with_selected_platforms + + +async def test_reload( + hass: HomeAssistant, + mock_rova: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reloading the integration.""" + await setup_with_selected_platforms(hass, mock_config_entry, [Platform.SENSOR]) + + assert mock_config_entry.state == ConfigEntryState.LOADED + + assert await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state == ConfigEntryState.NOT_LOADED + + +async def test_service( + hass: HomeAssistant, + mock_rova: MagicMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test the Rova service.""" + await setup_with_selected_platforms(hass, mock_config_entry, [Platform.SENSOR]) + + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, mock_config_entry.unique_id)} + ) + assert device_entry is not None + assert device_entry == snapshot + + +@pytest.mark.parametrize( + "method", + [ + "is_rova_area", + "get_calendar_items", + ], +) +async def test_retry_after_failure( + hass: HomeAssistant, + mock_rova: MagicMock, + mock_config_entry: MockConfigEntry, + method: str, +) -> None: + """Test we retry after a failure.""" + getattr(mock_rova, method).side_effect = ConnectTimeout + mock_config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state == ConfigEntryState.SETUP_RETRY async def test_issue_if_not_rova_area( From e4d2985589de9f3f00bca5d2a8652018282f5402 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 27 Mar 2024 10:43:15 -0500 Subject: [PATCH 1579/1691] Bump intents to 2024.3.27 (#114312) --- .../components/conversation/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../conversation/test_default_agent.py | 18 +++++++++++------- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index 00f645ea0f3..7f3c4f5894e 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.12"] + "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.27"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fa16d1ddeba..1f89aed7306 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -31,7 +31,7 @@ hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240307.0 -home-assistant-intents==2024.3.12 +home-assistant-intents==2024.3.27 httpx==0.27.0 ifaddr==0.2.0 Jinja2==3.1.3 diff --git a/requirements_all.txt b/requirements_all.txt index 0ab9ed75e09..c3106479617 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1080,7 +1080,7 @@ holidays==0.45 home-assistant-frontend==20240307.0 # homeassistant.components.conversation -home-assistant-intents==2024.3.12 +home-assistant-intents==2024.3.27 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 86242be4db4..efaa3a8ad30 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -879,7 +879,7 @@ holidays==0.45 home-assistant-frontend==20240307.0 # homeassistant.components.conversation -home-assistant-intents==2024.3.12 +home-assistant-intents==2024.3.27 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/tests/components/conversation/test_default_agent.py b/tests/components/conversation/test_default_agent.py index 4b4f9ade3eb..aefb37f427e 100644 --- a/tests/components/conversation/test_default_agent.py +++ b/tests/components/conversation/test_default_agent.py @@ -331,6 +331,7 @@ async def test_device_area_context( # Create 2 lights in each area area_lights = defaultdict(list) + all_lights = [] for area in (area_kitchen, area_bedroom): for i in range(2): light_entity = entity_registry.async_get_or_create( @@ -345,6 +346,7 @@ async def test_device_area_context( attributes={ATTR_FRIENDLY_NAME: f"{area.name} light {i}"}, ) area_lights[area.id].append(light_entity) + all_lights.append(light_entity) # Create voice satellites in each area entry = MockConfigEntry() @@ -412,7 +414,7 @@ async def test_device_area_context( } turn_on_calls.clear() - # Turn off all lights in the area of the otherkj device + # Turn off all lights in the area of the other device result = await conversation.async_converse( hass, "turn lights off", @@ -436,16 +438,18 @@ async def test_device_area_context( } turn_off_calls.clear() - # Not providing a device id should not match + # Turn on/off all lights also works for command in ("on", "off"): result = await conversation.async_converse( hass, f"turn {command} all lights", None, Context(), None ) - assert result.response.response_type == intent.IntentResponseType.ERROR - assert ( - result.response.error_code - == intent.IntentResponseErrorCode.NO_VALID_TARGETS - ) + await hass.async_block_till_done() + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE + + # All lights should have been targeted + assert {s.entity_id for s in result.response.matched_states} == { + e.entity_id for e in all_lights + } async def test_error_no_device(hass: HomeAssistant, init_components) -> None: From 1269031d11771550c0086b9616d5997fd26aef08 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 27 Mar 2024 16:46:42 +0100 Subject: [PATCH 1580/1691] Change min and max humidity for MQTT climate and humidifier (#114292) Allow floats for humidity on mqtt platforms --- homeassistant/components/mqtt/climate.py | 14 +++++++------- homeassistant/components/mqtt/humidifier.py | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index cb1274c7665..972bf02ecea 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -272,12 +272,12 @@ _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( vol.Optional(CONF_FAN_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_HUMIDITY_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_HUMIDITY_COMMAND_TOPIC): valid_publish_topic, - vol.Optional(CONF_HUMIDITY_MIN, default=DEFAULT_MIN_HUMIDITY): vol.Coerce( - float - ), - vol.Optional(CONF_HUMIDITY_MAX, default=DEFAULT_MAX_HUMIDITY): vol.Coerce( - float - ), + vol.Optional( + CONF_HUMIDITY_MIN, default=DEFAULT_MIN_HUMIDITY + ): cv.positive_float, + vol.Optional( + CONF_HUMIDITY_MAX, default=DEFAULT_MAX_HUMIDITY + ): cv.positive_float, vol.Optional(CONF_HUMIDITY_STATE_TEMPLATE): cv.template, vol.Optional(CONF_HUMIDITY_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, @@ -865,7 +865,7 @@ class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): await self.async_set_hvac_mode(operation_mode) await super().async_set_temperature(**kwargs) - async def async_set_humidity(self, humidity: int) -> None: + async def async_set_humidity(self, humidity: float) -> None: """Set new target humidity.""" await self._set_climate_attribute( diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index f3b9cf4c4ff..7c9ba26389c 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -149,10 +149,10 @@ _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( vol.Optional(CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE): cv.template, vol.Optional( CONF_TARGET_HUMIDITY_MAX, default=DEFAULT_MAX_HUMIDITY - ): cv.positive_int, + ): cv.positive_float, vol.Optional( CONF_TARGET_HUMIDITY_MIN, default=DEFAULT_MIN_HUMIDITY - ): cv.positive_int, + ): cv.positive_float, vol.Optional(CONF_TARGET_HUMIDITY_STATE_TEMPLATE): cv.template, vol.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): valid_subscribe_topic, vol.Optional( @@ -485,7 +485,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): self._attr_is_on = False self.async_write_ha_state() - async def async_set_humidity(self, humidity: int) -> None: + async def async_set_humidity(self, humidity: float) -> None: """Set the target humidity of the humidifier. This method is a coroutine. From 65230908c6bd7b9f2dac6e8de41b7bd27d28d1be Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 27 Mar 2024 16:51:29 +0100 Subject: [PATCH 1581/1691] Remove deprecated forecast attribute from WeatherEntity (#110761) * Remove deprecated forecast attribute from WeatherEntity * Fix some * Ruff * ipma * buienradar * some more * Some more * More and more * strings * attr_forecast * Fix nws * Fix * remove from coverage * Remove recorder test * Review comments --- .coveragerc | 2 + .../components/accuweather/weather.py | 11 +- .../components/buienradar/weather.py | 5 +- homeassistant/components/ecobee/weather.py | 5 - .../components/environment_canada/weather.py | 5 - homeassistant/components/ipma/weather.py | 7 - .../components/kitchen_sink/weather.py | 60 -- homeassistant/components/met/weather.py | 5 - .../components/met_eireann/weather.py | 5 - .../components/meteo_france/weather.py | 5 - homeassistant/components/metoffice/weather.py | 8 - homeassistant/components/nws/weather.py | 5 - .../components/open_meteo/weather.py | 11 +- .../components/openweathermap/weather.py | 6 +- homeassistant/components/smhi/weather.py | 29 - homeassistant/components/template/weather.py | 13 - .../components/tomorrowio/weather.py | 5 - homeassistant/components/weather/__init__.py | 102 ---- homeassistant/components/weather/strings.json | 8 - tests/components/accuweather/test_weather.py | 60 +- tests/components/ipma/test_weather.py | 55 -- tests/components/metoffice/test_weather.py | 39 -- tests/components/nws/test_weather.py | 12 - .../smhi/snapshots/test_weather.ambr | 481 ---------------- tests/components/smhi/test_weather.py | 16 +- .../template/snapshots/test_weather.ambr | 30 - tests/components/template/test_weather.py | 10 +- tests/components/tomorrowio/test_weather.py | 37 -- tests/components/weather/test_init.py | 532 +----------------- tests/components/weather/test_recorder.py | 59 -- .../custom_components/test/weather.py | 5 - 31 files changed, 37 insertions(+), 1596 deletions(-) delete mode 100644 tests/components/weather/test_recorder.py diff --git a/.coveragerc b/.coveragerc index 306d06c4de0..b88db04035a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -956,7 +956,9 @@ omit = homeassistant/components/openuv/binary_sensor.py homeassistant/components/openuv/coordinator.py homeassistant/components/openuv/sensor.py + homeassistant/components/openweathermap/__init__.py homeassistant/components/openweathermap/sensor.py + homeassistant/components/openweathermap/weather.py homeassistant/components/openweathermap/weather_update_coordinator.py homeassistant/components/opnsense/__init__.py homeassistant/components/opower/__init__.py diff --git a/homeassistant/components/accuweather/weather.py b/homeassistant/components/accuweather/weather.py index a734847fa18..1f2e606f6ea 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -146,9 +146,9 @@ class AccuWeatherEntity( """Return the UV index.""" return cast(float, self.coordinator.data["UVIndex"]) - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast array.""" + @callback + def _async_forecast_daily(self) -> list[Forecast] | None: + """Return the daily forecast in native units.""" if not self.coordinator.forecast: return None # remap keys from library to keys understood by the weather component @@ -177,8 +177,3 @@ class AccuWeatherEntity( } for item in self.coordinator.data[ATTR_FORECAST] ] - - @callback - def _async_forecast_daily(self) -> list[Forecast] | None: - """Return the daily forecast in native units.""" - return self.forecast diff --git a/homeassistant/components/buienradar/weather.py b/homeassistant/components/buienradar/weather.py index a7a6d3dc02e..02e1f444c9c 100644 --- a/homeassistant/components/buienradar/weather.py +++ b/homeassistant/components/buienradar/weather.py @@ -138,13 +138,14 @@ class BrWeather(WeatherEntity): self._attr_unique_id = ( f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}" ) + self._forecast: list | None = None @callback def data_updated(self, data: BrData) -> None: """Update data.""" self._attr_attribution = data.attribution self._attr_condition = self._calc_condition(data) - self._attr_forecast = self._calc_forecast(data) + self._forecast = self._calc_forecast(data) self._attr_humidity = data.humidity self._attr_name = ( self._stationname or f"BR {data.stationname or '(unknown station)'}" @@ -196,4 +197,4 @@ class BrWeather(WeatherEntity): async def async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" - return self._attr_forecast + return self._forecast diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 743ee12ddc7..b7961f956eb 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -184,11 +184,6 @@ class EcobeeWeather(WeatherEntity): return forecasts return None - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast array.""" - return self._forecast() - async def async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" return self._forecast() diff --git a/homeassistant/components/environment_canada/weather.py b/homeassistant/components/environment_canada/weather.py index 750e3172178..643e7951c23 100644 --- a/homeassistant/components/environment_canada/weather.py +++ b/homeassistant/components/environment_canada/weather.py @@ -174,11 +174,6 @@ class ECWeather(SingleCoordinatorWeatherEntity): return icon_code_to_condition(int(icon_code)) return "" - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast array.""" - return get_forecast(self.ec_data, False) - @callback def _async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" diff --git a/homeassistant/components/ipma/weather.py b/homeassistant/components/ipma/weather.py index cd4c650beef..ff6d8c3e86c 100644 --- a/homeassistant/components/ipma/weather.py +++ b/homeassistant/components/ipma/weather.py @@ -205,13 +205,6 @@ class IPMAWeather(WeatherEntity, IPMADevice): for data_in in forecast ] - @property - def forecast(self) -> list[Forecast]: - """Return the forecast array.""" - return self._forecast( - self._hourly_forecast if self._period == 1 else self._daily_forecast - ) - async def _try_update_forecast( self, forecast_type: Literal["daily", "hourly"], diff --git a/homeassistant/components/kitchen_sink/weather.py b/homeassistant/components/kitchen_sink/weather.py index 0ade3e73889..8a12cb4bdb9 100644 --- a/homeassistant/components/kitchen_sink/weather.py +++ b/homeassistant/components/kitchen_sink/weather.py @@ -61,29 +61,6 @@ async def async_setup_entry( """Set up the Demo config entry.""" async_add_entities( [ - DemoWeather( - "Legacy weather", - "Sunshine", - 21.6414, - 92, - 1099, - 0.5, - UnitOfTemperature.CELSIUS, - UnitOfPressure.HPA, - UnitOfSpeed.METERS_PER_SECOND, - [ - [ATTR_CONDITION_RAINY, 1, 22, 15, 60], - [ATTR_CONDITION_RAINY, 5, 19, 8, 30], - [ATTR_CONDITION_CLOUDY, 0, 15, 9, 10], - [ATTR_CONDITION_SUNNY, 0, 12, 6, 0], - [ATTR_CONDITION_PARTLYCLOUDY, 2, 14, 7, 20], - [ATTR_CONDITION_RAINY, 15, 18, 7, 0], - [ATTR_CONDITION_FOG, 0.2, 21, 12, 100], - ], - None, - None, - None, - ), DemoWeather( "Legacy + daily weather", "Sunshine", @@ -103,15 +80,6 @@ async def async_setup_entry( [ATTR_CONDITION_RAINY, 15, 18, 7, 0], [ATTR_CONDITION_FOG, 0.2, 21, 12, 100], ], - [ - [ATTR_CONDITION_RAINY, 1, 22, 15, 60], - [ATTR_CONDITION_RAINY, 5, 19, 8, 30], - [ATTR_CONDITION_CLOUDY, 0, 15, 9, 10], - [ATTR_CONDITION_SUNNY, 0, 12, 6, 0], - [ATTR_CONDITION_PARTLYCLOUDY, 2, 14, 7, 20], - [ATTR_CONDITION_RAINY, 15, 18, 7, 0], - [ATTR_CONDITION_FOG, 0.2, 21, 12, 100], - ], None, None, ), @@ -125,7 +93,6 @@ async def async_setup_entry( UnitOfTemperature.FAHRENHEIT, UnitOfPressure.INHG, UnitOfSpeed.MILES_PER_HOUR, - None, [ [ATTR_CONDITION_SNOWY, 2, -10, -15, 60], [ATTR_CONDITION_PARTLYCLOUDY, 1, -13, -14, 25], @@ -156,7 +123,6 @@ async def async_setup_entry( UnitOfTemperature.CELSIUS, UnitOfPressure.HPA, UnitOfSpeed.METERS_PER_SECOND, - None, [ [ATTR_CONDITION_RAINY, 1, 22, 15, 60], [ATTR_CONDITION_RAINY, 5, 19, 8, 30], @@ -196,7 +162,6 @@ async def async_setup_entry( UnitOfPressure.HPA, UnitOfSpeed.METERS_PER_SECOND, None, - None, [ [ATTR_CONDITION_CLOUDY, 1, 22, 15, 60], [ATTR_CONDITION_CLOUDY, 5, 19, 8, 30], @@ -226,7 +191,6 @@ async def async_setup_entry( UnitOfTemperature.CELSIUS, UnitOfPressure.HPA, UnitOfSpeed.METERS_PER_SECOND, - None, [ [ATTR_CONDITION_RAINY, 1, 22, 15, 60], [ATTR_CONDITION_RAINY, 5, 19, 8, 30], @@ -268,7 +232,6 @@ class DemoWeather(WeatherEntity): temperature_unit: str, pressure_unit: str, wind_speed_unit: str, - forecast: list[list] | None, forecast_daily: list[list] | None, forecast_hourly: list[list] | None, forecast_twice_daily: list[list] | None, @@ -284,7 +247,6 @@ class DemoWeather(WeatherEntity): self._native_pressure_unit = pressure_unit self._native_wind_speed = wind_speed self._native_wind_speed_unit = wind_speed_unit - self._forecast = forecast self._forecast_daily = forecast_daily self._forecast_hourly = forecast_hourly self._forecast_twice_daily = forecast_twice_daily @@ -360,28 +322,6 @@ class DemoWeather(WeatherEntity): """Return the weather condition.""" return CONDITION_MAP[self._condition.lower()] - @property - def forecast(self) -> list[Forecast]: - """Return legacy forecast.""" - if self._forecast is None: - return [] - reftime = dt_util.now().replace(hour=16, minute=00) - - forecast_data = [] - for entry in self._forecast: - data_dict = Forecast( - datetime=reftime.isoformat(), - condition=entry[0], - precipitation=entry[1], - temperature=entry[2], - templow=entry[3], - precipitation_probability=entry[4], - ) - reftime = reftime + timedelta(hours=24) - forecast_data.append(data_dict) - - return forecast_data - async def async_forecast_daily(self) -> list[Forecast]: """Return the daily forecast.""" if self._forecast_daily is None: diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index 86ef77f03f6..d0ee4f275ea 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -231,11 +231,6 @@ class MetWeather(SingleCoordinatorWeatherEntity[MetDataUpdateCoordinator]): ha_forecast.append(ha_item) # type: ignore[arg-type] return ha_forecast - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast array.""" - return self._forecast(False) - @callback def _async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" diff --git a/homeassistant/components/met_eireann/weather.py b/homeassistant/components/met_eireann/weather.py index ed226714c1d..404ef5d8393 100644 --- a/homeassistant/components/met_eireann/weather.py +++ b/homeassistant/components/met_eireann/weather.py @@ -173,11 +173,6 @@ class MetEireannWeather( ha_forecast.append(ha_item) return ha_forecast - @property - def forecast(self) -> list[Forecast]: - """Return the forecast array.""" - return self._forecast(False) - @callback def _async_forecast_daily(self) -> list[Forecast]: """Return the daily forecast in native units.""" diff --git a/homeassistant/components/meteo_france/weather.py b/homeassistant/components/meteo_france/weather.py index 8454d7672a3..9edc557aafc 100644 --- a/homeassistant/components/meteo_france/weather.py +++ b/homeassistant/components/meteo_france/weather.py @@ -216,11 +216,6 @@ class MeteoFranceWeather( ) return forecast_data - @property - def forecast(self) -> list[Forecast]: - """Return the forecast array.""" - return self._forecast(self._mode) - async def async_forecast_daily(self) -> list[Forecast]: """Return the daily forecast in native units.""" return self._forecast(FORECAST_MODE_DAILY) diff --git a/homeassistant/components/metoffice/weather.py b/homeassistant/components/metoffice/weather.py index 19df765f1f9..33fec874611 100644 --- a/homeassistant/components/metoffice/weather.py +++ b/homeassistant/components/metoffice/weather.py @@ -181,14 +181,6 @@ class MetOfficeWeather( return str(value) if value is not None else None return None - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast array.""" - return [ - _build_forecast_data(timestep) - for timestep in self.coordinator.data.forecast - ] - @callback def _async_forecast_daily(self) -> list[Forecast] | None: """Return the twice daily forecast in native units.""" diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 0c76c31887c..89414f5acf1 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -299,11 +299,6 @@ class NWSWeather(CoordinatorWeatherEntity): forecast.append(data) return forecast - @property - def forecast(self) -> list[Forecast] | None: - """Return forecast.""" - return self._forecast(self._forecast_legacy, DAYNIGHT) - @callback def _async_forecast_hourly(self) -> list[Forecast] | None: """Return the hourly forecast in native units.""" diff --git a/homeassistant/components/open_meteo/weather.py b/homeassistant/components/open_meteo/weather.py index 850ce61bcb5..8ee3edd5183 100644 --- a/homeassistant/components/open_meteo/weather.py +++ b/homeassistant/components/open_meteo/weather.py @@ -88,9 +88,9 @@ class OpenMeteoWeatherEntity( return None return self.coordinator.data.current_weather.wind_direction - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast in native units.""" + @callback + def _async_forecast_daily(self) -> list[Forecast] | None: + """Return the daily forecast in native units.""" if self.coordinator.data.daily is None: return None @@ -124,8 +124,3 @@ class OpenMeteoWeatherEntity( forecasts.append(forecast) return forecasts - - @callback - def _async_forecast_daily(self) -> list[Forecast] | None: - """Return the daily forecast in native units.""" - return self.forecast diff --git a/homeassistant/components/openweathermap/weather.py b/homeassistant/components/openweathermap/weather.py index 063415d0762..62bf18ba813 100644 --- a/homeassistant/components/openweathermap/weather.py +++ b/homeassistant/components/openweathermap/weather.py @@ -185,7 +185,7 @@ class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordina return self.coordinator.data[ATTR_API_WIND_BEARING] @property - def forecast(self) -> list[Forecast] | None: + def _forecast(self) -> list[Forecast] | None: """Return the forecast array.""" api_forecasts = self.coordinator.data[ATTR_API_FORECAST] forecasts = [ @@ -201,9 +201,9 @@ class OpenWeatherMapWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordina @callback def _async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" - return self.forecast + return self._forecast @callback def _async_forecast_hourly(self) -> list[Forecast] | None: """Return the hourly forecast in native units.""" - return self.forecast + return self._forecast diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 9204816c595..bf069f4b26a 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -195,35 +195,6 @@ class SmhiWeather(WeatherEntity): """Retry refresh weather forecast.""" await self.async_update(no_throttle=True) - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - if self._forecast_daily is None or len(self._forecast_daily) < 2: - return None - - data: list[Forecast] = [] - - for forecast in self._forecast_daily[1:]: - condition = CONDITION_MAP.get(forecast.symbol) - - data.append( - { - ATTR_FORECAST_TIME: forecast.valid_time.isoformat(), - ATTR_FORECAST_NATIVE_TEMP: forecast.temperature_max, - ATTR_FORECAST_NATIVE_TEMP_LOW: forecast.temperature_min, - ATTR_FORECAST_NATIVE_PRECIPITATION: forecast.total_precipitation, - ATTR_FORECAST_CONDITION: condition, - ATTR_FORECAST_NATIVE_PRESSURE: forecast.pressure, - ATTR_FORECAST_WIND_BEARING: forecast.wind_direction, - ATTR_FORECAST_NATIVE_WIND_SPEED: forecast.wind_speed, - ATTR_FORECAST_HUMIDITY: forecast.humidity, - ATTR_FORECAST_NATIVE_WIND_GUST_SPEED: forecast.wind_gust, - ATTR_FORECAST_CLOUD_COVERAGE: forecast.cloudiness, - } - ) - - return data - def _get_forecast_data( self, forecast_data: list[SmhiForecast] | None ) -> list[Forecast] | None: diff --git a/homeassistant/components/template/weather.py b/homeassistant/components/template/weather.py index 6f6861830c7..e8981fb33f9 100644 --- a/homeassistant/components/template/weather.py +++ b/homeassistant/components/template/weather.py @@ -118,7 +118,6 @@ WEATHER_SCHEMA = vol.Schema( vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template, vol.Optional(CONF_OZONE_TEMPLATE): cv.template, vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template, - vol.Optional(CONF_FORECAST_TEMPLATE): cv.template, vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template, vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template, vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template, @@ -193,7 +192,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity): self._wind_bearing_template = config.get(CONF_WIND_BEARING_TEMPLATE) self._ozone_template = config.get(CONF_OZONE_TEMPLATE) self._visibility_template = config.get(CONF_VISIBILITY_TEMPLATE) - self._forecast_template = config.get(CONF_FORECAST_TEMPLATE) self._forecast_daily_template = config.get(CONF_FORECAST_DAILY_TEMPLATE) self._forecast_hourly_template = config.get(CONF_FORECAST_HOURLY_TEMPLATE) self._forecast_twice_daily_template = config.get( @@ -227,7 +225,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity): self._cloud_coverage = None self._dew_point = None self._apparent_temperature = None - self._forecast: list[Forecast] = [] self._forecast_daily: list[Forecast] = [] self._forecast_hourly: list[Forecast] = [] self._forecast_twice_daily: list[Forecast] = [] @@ -300,11 +297,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity): """Return the apparent temperature.""" return self._apparent_temperature - @property - def forecast(self) -> list[Forecast]: - """Return the forecast.""" - return self._forecast - async def async_forecast_daily(self) -> list[Forecast]: """Return the daily forecast in native units.""" return self._forecast_daily @@ -394,11 +386,6 @@ class WeatherTemplate(TemplateEntity, WeatherEntity): "_apparent_temperature", self._apparent_temperature_template, ) - if self._forecast_template: - self.add_template_attribute( - "_forecast", - self._forecast_template, - ) if self._forecast_daily_template: self.add_template_attribute( diff --git a/homeassistant/components/tomorrowio/weather.py b/homeassistant/components/tomorrowio/weather.py index cc46fa3dfab..3b60f171bbe 100644 --- a/homeassistant/components/tomorrowio/weather.py +++ b/homeassistant/components/tomorrowio/weather.py @@ -298,11 +298,6 @@ class TomorrowioWeatherEntity(TomorrowioEntity, SingleCoordinatorWeatherEntity): return forecasts - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast array.""" - return self._forecast(self.forecast_type) - @callback def _async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 59e01f03e67..404154ade2b 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -3,7 +3,6 @@ from __future__ import annotations import abc -import asyncio from collections.abc import Callable, Iterable from contextlib import suppress from datetime import timedelta @@ -48,7 +47,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 ) from homeassistant.helpers.entity import ABCCachedProperties, Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.entity_platform import EntityPlatform import homeassistant.helpers.issue_registry as ir from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import ( @@ -56,7 +54,6 @@ from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, TimestampDataUpdateCoordinator, ) -from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue from homeassistant.util.dt import utcnow from homeassistant.util.json import JsonValueType from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM @@ -111,7 +108,6 @@ ATTR_CONDITION_SNOWY_RAINY = "snowy-rainy" ATTR_CONDITION_SUNNY = "sunny" ATTR_CONDITION_WINDY = "windy" ATTR_CONDITION_WINDY_VARIANT = "windy-variant" -ATTR_FORECAST = "forecast" ATTR_FORECAST_IS_DAYTIME: Final = "is_daytime" ATTR_FORECAST_CONDITION: Final = "condition" ATTR_FORECAST_HUMIDITY: Final = "humidity" @@ -306,13 +302,8 @@ CACHED_PROPERTIES_WITH_ATTR_ = { class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """ABC for weather data.""" - _entity_component_unrecorded_attributes = frozenset({ATTR_FORECAST}) - entity_description: WeatherEntityDescription _attr_condition: str | None = None - # _attr_forecast is deprecated, implement async_forecast_daily, - # async_forecast_hourly or async_forecast_twice daily instead - _attr_forecast: list[Forecast] | None = None _attr_humidity: float | None = None _attr_ozone: float | None = None _attr_cloud_coverage: int | None = None @@ -338,8 +329,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A Literal["daily", "hourly", "twice_daily"], list[Callable[[list[JsonValueType] | None], None]], ] - __weather_reported_legacy_forecast = False - __weather_legacy_forecast = False _weather_option_temperature_unit: str | None = None _weather_option_pressure_unit: str | None = None @@ -351,77 +340,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A """Finish initializing.""" self._forecast_listeners = {"daily": [], "hourly": [], "twice_daily": []} - def __init_subclass__(cls, **kwargs: Any) -> None: - """Post initialisation processing.""" - super().__init_subclass__(**kwargs) - if ( - "forecast" in cls.__dict__ - and cls.async_forecast_daily is WeatherEntity.async_forecast_daily - and cls.async_forecast_hourly is WeatherEntity.async_forecast_hourly - and cls.async_forecast_twice_daily - is WeatherEntity.async_forecast_twice_daily - ): - cls.__weather_legacy_forecast = True - - @callback - def add_to_platform_start( - self, - hass: HomeAssistant, - platform: EntityPlatform, - parallel_updates: asyncio.Semaphore | None, - ) -> None: - """Start adding an entity to a platform.""" - super().add_to_platform_start(hass, platform, parallel_updates) - if self.__weather_legacy_forecast: - self._report_legacy_forecast(hass) - - def _report_legacy_forecast(self, hass: HomeAssistant) -> None: - """Log warning and create an issue if the entity imlpements legacy forecast.""" - if "custom_components" not in type(self).__module__: - # Do not report core integrations as they are already fixed or PR is open. - return - - report_issue = async_suggest_report_issue( - hass, - integration_domain=self.platform.platform_name, - module=type(self).__module__, - ) - _LOGGER.warning( - ( - "%s::%s implements the `forecast` property or sets " - "`self._attr_forecast` in a subclass of WeatherEntity, this is " - "deprecated and will be unsupported from Home Assistant 2024.3." - " Please %s" - ), - self.platform.platform_name, - self.__class__.__name__, - report_issue, - ) - - translation_placeholders = {"platform": self.platform.platform_name} - translation_key = "deprecated_weather_forecast_no_url" - issue_tracker = async_get_issue_tracker( - hass, - integration_domain=self.platform.platform_name, - module=type(self).__module__, - ) - if issue_tracker: - translation_placeholders["issue_tracker"] = issue_tracker - translation_key = "deprecated_weather_forecast_url" - ir.async_create_issue( - self.hass, - DOMAIN, - f"deprecated_weather_forecast_{self.platform.platform_name}", - breaks_in_ha_version="2024.3.0", - is_fixable=False, - is_persistent=False, - issue_domain=self.platform.platform_name, - severity=ir.IssueSeverity.WARNING, - translation_key=translation_key, - translation_placeholders=translation_placeholders, - ) - self.__weather_reported_legacy_forecast = True - async def async_internal_added_to_hass(self) -> None: """Call when the weather entity is added to hass.""" await super().async_internal_added_to_hass() @@ -605,23 +523,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A return self._default_visibility_unit - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast in native units. - - Should not be overridden by integrations. Kept for backwards compatibility. - """ - if ( - self._attr_forecast is not None - and type(self).async_forecast_daily is WeatherEntity.async_forecast_daily - and type(self).async_forecast_hourly is WeatherEntity.async_forecast_hourly - and type(self).async_forecast_twice_daily - is WeatherEntity.async_forecast_twice_daily - and not self.__weather_reported_legacy_forecast - ): - self._report_legacy_forecast(self.hass) - return self._attr_forecast - async def async_forecast_daily(self) -> list[Forecast] | None: """Return the daily forecast in native units.""" raise NotImplementedError @@ -804,9 +705,6 @@ class WeatherEntity(Entity, PostInit, cached_properties=CACHED_PROPERTIES_WITH_A data[ATTR_WEATHER_VISIBILITY_UNIT] = self._visibility_unit data[ATTR_WEATHER_PRECIPITATION_UNIT] = self._precipitation_unit - if self.forecast: - data[ATTR_FORECAST] = self._convert_forecast(self.forecast) - return data @final diff --git a/homeassistant/components/weather/strings.json b/homeassistant/components/weather/strings.json index 8879bf158f3..77c9cce864b 100644 --- a/homeassistant/components/weather/strings.json +++ b/homeassistant/components/weather/strings.json @@ -110,14 +110,6 @@ } }, "issues": { - "deprecated_weather_forecast_url": { - "title": "The {platform} custom integration is using deprecated weather forecast", - "description": "The custom integration `{platform}` implements the `forecast` property or sets `self._attr_forecast` in a subclass of WeatherEntity.\n\nPlease create a bug report at {issue_tracker}.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." - }, - "deprecated_weather_forecast_no_url": { - "title": "[%key:component::weather::issues::deprecated_weather_forecast_url::title%]", - "description": "The custom integration `{platform}` implements the `forecast` property or sets `self._attr_forecast` in a subclass of WeatherEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." - }, "deprecated_service_weather_get_forecast": { "title": "Detected use of deprecated service `weather.get_forecast`", "fix_flow": { diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index 291c1c1dcdf..0b9d3e28fb2 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -9,18 +9,7 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.components.accuweather.const import ATTRIBUTION from homeassistant.components.weather import ( - ATTR_FORECAST, - ATTR_FORECAST_APPARENT_TEMP, - ATTR_FORECAST_CLOUD_COVERAGE, ATTR_FORECAST_CONDITION, - ATTR_FORECAST_PRECIPITATION, - ATTR_FORECAST_PRECIPITATION_PROBABILITY, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, - ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_GUST_SPEED, - ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_DEW_POINT, @@ -35,7 +24,6 @@ from homeassistant.components.weather import ( DOMAIN as WEATHER_DOMAIN, LEGACY_SERVICE_GET_FORECAST, SERVICE_GET_FORECASTS, - WeatherEntityFeature, ) from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -58,16 +46,13 @@ from tests.common import ( from tests.typing import WebSocketGenerator -async def test_weather_without_forecast( - hass: HomeAssistant, entity_registry: er.EntityRegistry -) -> None: +async def test_weather(hass: HomeAssistant, entity_registry: er.EntityRegistry) -> None: """Test states of the weather without forecast.""" await init_integration(hass) state = hass.states.get("weather.home") assert state assert state.state == "sunny" - assert not state.attributes.get(ATTR_FORECAST) assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67 assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0 assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 @@ -87,49 +72,6 @@ async def test_weather_without_forecast( assert entry.unique_id == "0123456" -async def test_weather_with_forecast( - hass: HomeAssistant, entity_registry: er.EntityRegistry -) -> None: - """Test states of the weather with forecast.""" - await init_integration(hass, forecast=True) - - state = hass.states.get("weather.home") - assert state - assert state.state == "sunny" - assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67 - assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0 - assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 - assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1 - assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180 - assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 # 4.03 m/s -> km/h - assert state.attributes.get(ATTR_WEATHER_APPARENT_TEMPERATURE) == 22.8 - assert state.attributes.get(ATTR_WEATHER_DEW_POINT) == 16.2 - assert state.attributes.get(ATTR_WEATHER_CLOUD_COVERAGE) == 10 - assert state.attributes.get(ATTR_WEATHER_WIND_GUST_SPEED) == 20.3 - assert state.attributes.get(ATTR_WEATHER_UV_INDEX) == 6 - assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] == WeatherEntityFeature.FORECAST_DAILY - ) - forecast = state.attributes.get(ATTR_FORECAST)[0] - assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy" - assert forecast.get(ATTR_FORECAST_PRECIPITATION) == 2.5 - assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 60 - assert forecast.get(ATTR_FORECAST_TEMP) == 29.5 - assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 15.4 - assert forecast.get(ATTR_FORECAST_TIME) == "2020-07-26T05:00:00+00:00" - assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 166 - assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 13.0 # 3.61 m/s -> km/h - assert forecast.get(ATTR_FORECAST_CLOUD_COVERAGE) == 58 - assert forecast.get(ATTR_FORECAST_APPARENT_TEMP) == 29.8 - assert forecast.get(ATTR_FORECAST_WIND_GUST_SPEED) == 29.6 - assert forecast.get(ATTR_WEATHER_UV_INDEX) == 5 - - entry = entity_registry.async_get("weather.home") - assert entry - assert entry.unique_id == "0123456" - - async def test_availability(hass: HomeAssistant) -> None: """Ensure that we mark the entities unavailable correctly when service is offline.""" await init_integration(hass) diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py index 06f15922a2e..7150286e4f9 100644 --- a/tests/components/ipma/test_weather.py +++ b/tests/components/ipma/test_weather.py @@ -9,14 +9,6 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.components.ipma.const import MIN_TIME_BETWEEN_UPDATES from homeassistant.components.weather import ( - ATTR_FORECAST, - ATTR_FORECAST_CONDITION, - ATTR_FORECAST_PRECIPITATION_PROBABILITY, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, - ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, @@ -84,53 +76,6 @@ async def test_setup_config_flow(hass: HomeAssistant) -> None: assert state.attributes.get("friendly_name") == "HomeTown" -async def test_daily_forecast(hass: HomeAssistant) -> None: - """Test for successfully getting daily forecast.""" - with patch( - "pyipma.location.Location.get", - return_value=MockLocation(), - ): - entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get("weather.hometown") - assert state.state == "rainy" - - forecast = state.attributes.get(ATTR_FORECAST)[0] - assert forecast.get(ATTR_FORECAST_TIME) == datetime.datetime(2020, 1, 16, 0, 0, 0) - assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy" - assert forecast.get(ATTR_FORECAST_TEMP) == 16.2 - assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 10.6 - assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == "100.0" - assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 10.0 - assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S" - - -@pytest.mark.freeze_time("2020-01-14 23:00:00") -async def test_hourly_forecast(hass: HomeAssistant) -> None: - """Test for successfully getting daily forecast.""" - with patch( - "pyipma.location.Location.get", - return_value=MockLocation(), - ): - entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG_HOURLY) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get("weather.hometown") - assert state.state == "rainy" - - forecast = state.attributes.get(ATTR_FORECAST)[0] - assert forecast.get(ATTR_FORECAST_CONDITION) == "rainy" - assert forecast.get(ATTR_FORECAST_TEMP) == 12.0 - assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 80.0 - assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 32.7 - assert forecast.get(ATTR_FORECAST_WIND_BEARING) == "S" - - async def test_failed_get_observation_forecast(hass: HomeAssistant) -> None: """Test for successfully setting up the IPMA platform.""" with patch( diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index 520759b46b4..2aa673d4010 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -163,19 +163,6 @@ async def test_one_weather_site_running( assert weather.attributes.get("wind_bearing") == "SSE" assert weather.attributes.get("humidity") == 50 - # Also has Forecasts added - again, just pick out 1 entry to check - # ensures that daily filters out multiple results per day - assert len(weather.attributes.get("forecast")) == 4 - - assert ( - weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00" - ) - assert weather.attributes.get("forecast")[3]["condition"] == "rainy" - assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59 - assert weather.attributes.get("forecast")[3]["temperature"] == 13 - assert weather.attributes.get("forecast")[3]["wind_speed"] == 20.92 - assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE" - @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) async def test_two_weather_sites_running( @@ -230,19 +217,6 @@ async def test_two_weather_sites_running( assert weather.attributes.get("wind_bearing") == "SSE" assert weather.attributes.get("humidity") == 50 - # Also has Forecasts added - again, just pick out 1 entry to check - # ensures that daily filters out multiple results per day - assert len(weather.attributes.get("forecast")) == 4 - - assert ( - weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00" - ) - assert weather.attributes.get("forecast")[3]["condition"] == "rainy" - assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59 - assert weather.attributes.get("forecast")[3]["temperature"] == 13 - assert weather.attributes.get("forecast")[3]["wind_speed"] == 20.92 - assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE" - # King's Lynn daily weather platform expected results weather = hass.states.get("weather.met_office_king_s_lynn_daily") assert weather @@ -254,19 +228,6 @@ async def test_two_weather_sites_running( assert weather.attributes.get("wind_bearing") == "ESE" assert weather.attributes.get("humidity") == 75 - # All should have Forecast added - again, just picking out 1 entry to check - # ensures daily filters out multiple results per day - assert len(weather.attributes.get("forecast")) == 4 - - assert ( - weather.attributes.get("forecast")[2]["datetime"] == "2020-04-28T12:00:00+00:00" - ) - assert weather.attributes.get("forecast")[2]["condition"] == "cloudy" - assert weather.attributes.get("forecast")[2]["precipitation_probability"] == 14 - assert weather.attributes.get("forecast")[2]["temperature"] == 11 - assert weather.attributes.get("forecast")[2]["wind_speed"] == 11.27 - assert weather.attributes.get("forecast")[2]["wind_bearing"] == "ESE" - @pytest.mark.freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.UTC)) async def test_new_config_entry( diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index 1d202277e6f..0fb5654d7ee 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -12,7 +12,6 @@ from homeassistant.components import nws from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_SUNNY, - ATTR_FORECAST, DOMAIN as WEATHER_DOMAIN, LEGACY_SERVICE_GET_FORECAST, SERVICE_GET_FORECASTS, @@ -77,10 +76,6 @@ async def test_imperial_metric( for key, value in result_observation.items(): assert data.get(key) == value - forecast = data.get(ATTR_FORECAST) - for key, value in result_forecast.items(): - assert forecast[0].get(key) == value - async def test_night_clear(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: """Test with clear-night in observation.""" @@ -119,10 +114,6 @@ async def test_none_values(hass: HomeAssistant, mock_simple_nws, no_sensor) -> N for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL: assert data.get(key) is None - forecast = data.get(ATTR_FORECAST) - for key in EXPECTED_FORECAST_IMPERIAL: - assert forecast[0].get(key) is None - async def test_none(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: """Test with None as observation and forecast.""" @@ -146,9 +137,6 @@ async def test_none(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: for key in WEATHER_EXPECTED_OBSERVATION_IMPERIAL: assert data.get(key) is None - forecast = data.get(ATTR_FORECAST) - assert forecast is None - async def test_error_station(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: """Test error in setting station.""" diff --git a/tests/components/smhi/snapshots/test_weather.ambr b/tests/components/smhi/snapshots/test_weather.ambr index eb7378b5cba..0fef9e19ec3 100644 --- a/tests/components/smhi/snapshots/test_weather.ambr +++ b/tests/components/smhi/snapshots/test_weather.ambr @@ -1,338 +1,4 @@ # serializer version: 1 -# name: test_forecast_daily - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-07T12:00:00', - 'humidity': 96, - 'precipitation': 0.0, - 'pressure': 991.0, - 'temperature': 18.0, - 'templow': 15.0, - 'wind_bearing': 114, - 'wind_gust_speed': 32.76, - 'wind_speed': 10.08, - }) -# --- -# name: test_forecast_daily.1 - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-13T12:00:00', - 'humidity': 59, - 'precipitation': 0.0, - 'pressure': 1013.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 234, - 'wind_gust_speed': 35.64, - 'wind_speed': 14.76, - }) -# --- -# name: test_forecast_daily.2 - dict({ - 'cloud_coverage': 100, - 'condition': 'fog', - 'datetime': '2023-08-07T09:00:00', - 'humidity': 100, - 'precipitation': 0.0, - 'pressure': 992.0, - 'temperature': 18.0, - 'templow': 18.0, - 'wind_bearing': 103, - 'wind_gust_speed': 23.76, - 'wind_speed': 9.72, - }) -# --- -# name: test_forecast_daily.3 - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-07T15:00:00', - 'humidity': 89, - 'precipitation': 0.0, - 'pressure': 991.0, - 'temperature': 16.0, - 'templow': 16.0, - 'wind_bearing': 108, - 'wind_gust_speed': 31.68, - 'wind_speed': 12.24, - }) -# --- -# name: test_forecast_service - dict({ - 'forecast': list([ - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-07T12:00:00', - 'humidity': 96, - 'precipitation': 0.0, - 'pressure': 991.0, - 'temperature': 18.0, - 'templow': 15.0, - 'wind_bearing': 114, - 'wind_gust_speed': 32.76, - 'wind_speed': 10.08, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-08T12:00:00', - 'humidity': 97, - 'precipitation': 10.6, - 'pressure': 984.0, - 'temperature': 15.0, - 'templow': 11.0, - 'wind_bearing': 183, - 'wind_gust_speed': 27.36, - 'wind_speed': 11.16, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-09T12:00:00', - 'humidity': 95, - 'precipitation': 6.3, - 'pressure': 1001.0, - 'temperature': 12.0, - 'templow': 11.0, - 'wind_bearing': 166, - 'wind_gust_speed': 48.24, - 'wind_speed': 18.0, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-10T12:00:00', - 'humidity': 75, - 'precipitation': 4.8, - 'pressure': 1011.0, - 'temperature': 14.0, - 'templow': 10.0, - 'wind_bearing': 174, - 'wind_gust_speed': 29.16, - 'wind_speed': 11.16, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-11T12:00:00', - 'humidity': 69, - 'precipitation': 0.6, - 'pressure': 1015.0, - 'temperature': 18.0, - 'templow': 12.0, - 'wind_bearing': 197, - 'wind_gust_speed': 27.36, - 'wind_speed': 10.08, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-12T12:00:00', - 'humidity': 82, - 'precipitation': 0.0, - 'pressure': 1014.0, - 'temperature': 17.0, - 'templow': 12.0, - 'wind_bearing': 225, - 'wind_gust_speed': 28.08, - 'wind_speed': 8.64, - }), - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-13T12:00:00', - 'humidity': 59, - 'precipitation': 0.0, - 'pressure': 1013.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 234, - 'wind_gust_speed': 35.64, - 'wind_speed': 14.76, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'partlycloudy', - 'datetime': '2023-08-14T12:00:00', - 'humidity': 56, - 'precipitation': 0.0, - 'pressure': 1015.0, - 'temperature': 21.0, - 'templow': 14.0, - 'wind_bearing': 216, - 'wind_gust_speed': 33.12, - 'wind_speed': 13.68, - }), - dict({ - 'cloud_coverage': 88, - 'condition': 'partlycloudy', - 'datetime': '2023-08-15T12:00:00', - 'humidity': 64, - 'precipitation': 3.6, - 'pressure': 1014.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 226, - 'wind_gust_speed': 33.12, - 'wind_speed': 13.68, - }), - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-16T12:00:00', - 'humidity': 61, - 'precipitation': 2.4, - 'pressure': 1014.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 233, - 'wind_gust_speed': 33.48, - 'wind_speed': 14.04, - }), - ]), - }) -# --- -# name: test_forecast_service[forecast] - dict({ - 'weather.smhi_test': dict({ - 'forecast': list([ - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-07T12:00:00', - 'humidity': 96, - 'precipitation': 0.0, - 'pressure': 991.0, - 'temperature': 18.0, - 'templow': 15.0, - 'wind_bearing': 114, - 'wind_gust_speed': 32.76, - 'wind_speed': 10.08, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-08T12:00:00', - 'humidity': 97, - 'precipitation': 10.6, - 'pressure': 984.0, - 'temperature': 15.0, - 'templow': 11.0, - 'wind_bearing': 183, - 'wind_gust_speed': 27.36, - 'wind_speed': 11.16, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-09T12:00:00', - 'humidity': 95, - 'precipitation': 6.3, - 'pressure': 1001.0, - 'temperature': 12.0, - 'templow': 11.0, - 'wind_bearing': 166, - 'wind_gust_speed': 48.24, - 'wind_speed': 18.0, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-10T12:00:00', - 'humidity': 75, - 'precipitation': 4.8, - 'pressure': 1011.0, - 'temperature': 14.0, - 'templow': 10.0, - 'wind_bearing': 174, - 'wind_gust_speed': 29.16, - 'wind_speed': 11.16, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-11T12:00:00', - 'humidity': 69, - 'precipitation': 0.6, - 'pressure': 1015.0, - 'temperature': 18.0, - 'templow': 12.0, - 'wind_bearing': 197, - 'wind_gust_speed': 27.36, - 'wind_speed': 10.08, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-12T12:00:00', - 'humidity': 82, - 'precipitation': 0.0, - 'pressure': 1014.0, - 'temperature': 17.0, - 'templow': 12.0, - 'wind_bearing': 225, - 'wind_gust_speed': 28.08, - 'wind_speed': 8.64, - }), - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-13T12:00:00', - 'humidity': 59, - 'precipitation': 0.0, - 'pressure': 1013.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 234, - 'wind_gust_speed': 35.64, - 'wind_speed': 14.76, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'partlycloudy', - 'datetime': '2023-08-14T12:00:00', - 'humidity': 56, - 'precipitation': 0.0, - 'pressure': 1015.0, - 'temperature': 21.0, - 'templow': 14.0, - 'wind_bearing': 216, - 'wind_gust_speed': 33.12, - 'wind_speed': 13.68, - }), - dict({ - 'cloud_coverage': 88, - 'condition': 'partlycloudy', - 'datetime': '2023-08-15T12:00:00', - 'humidity': 64, - 'precipitation': 3.6, - 'pressure': 1014.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 226, - 'wind_gust_speed': 33.12, - 'wind_speed': 13.68, - }), - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-16T12:00:00', - 'humidity': 61, - 'precipitation': 2.4, - 'pressure': 1014.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 233, - 'wind_gust_speed': 33.48, - 'wind_speed': 14.04, - }), - ]), - }), - }) -# --- # name: test_forecast_service[get_forecast] dict({ 'forecast': list([ @@ -671,138 +337,6 @@ ReadOnlyDict({ 'attribution': 'Swedish weather institute (SMHI)', 'cloud_coverage': 100, - 'forecast': list([ - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-07T12:00:00', - 'humidity': 96, - 'precipitation': 0.0, - 'pressure': 991.0, - 'temperature': 18.0, - 'templow': 15.0, - 'wind_bearing': 114, - 'wind_gust_speed': 32.76, - 'wind_speed': 10.08, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-08T12:00:00', - 'humidity': 97, - 'precipitation': 10.6, - 'pressure': 984.0, - 'temperature': 15.0, - 'templow': 11.0, - 'wind_bearing': 183, - 'wind_gust_speed': 27.36, - 'wind_speed': 11.16, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-09T12:00:00', - 'humidity': 95, - 'precipitation': 6.3, - 'pressure': 1001.0, - 'temperature': 12.0, - 'templow': 11.0, - 'wind_bearing': 166, - 'wind_gust_speed': 48.24, - 'wind_speed': 18.0, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-10T12:00:00', - 'humidity': 75, - 'precipitation': 4.8, - 'pressure': 1011.0, - 'temperature': 14.0, - 'templow': 10.0, - 'wind_bearing': 174, - 'wind_gust_speed': 29.16, - 'wind_speed': 11.16, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-11T12:00:00', - 'humidity': 69, - 'precipitation': 0.6, - 'pressure': 1015.0, - 'temperature': 18.0, - 'templow': 12.0, - 'wind_bearing': 197, - 'wind_gust_speed': 27.36, - 'wind_speed': 10.08, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'cloudy', - 'datetime': '2023-08-12T12:00:00', - 'humidity': 82, - 'precipitation': 0.0, - 'pressure': 1014.0, - 'temperature': 17.0, - 'templow': 12.0, - 'wind_bearing': 225, - 'wind_gust_speed': 28.08, - 'wind_speed': 8.64, - }), - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-13T12:00:00', - 'humidity': 59, - 'precipitation': 0.0, - 'pressure': 1013.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 234, - 'wind_gust_speed': 35.64, - 'wind_speed': 14.76, - }), - dict({ - 'cloud_coverage': 100, - 'condition': 'partlycloudy', - 'datetime': '2023-08-14T12:00:00', - 'humidity': 56, - 'precipitation': 0.0, - 'pressure': 1015.0, - 'temperature': 21.0, - 'templow': 14.0, - 'wind_bearing': 216, - 'wind_gust_speed': 33.12, - 'wind_speed': 13.68, - }), - dict({ - 'cloud_coverage': 88, - 'condition': 'partlycloudy', - 'datetime': '2023-08-15T12:00:00', - 'humidity': 64, - 'precipitation': 3.6, - 'pressure': 1014.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 226, - 'wind_gust_speed': 33.12, - 'wind_speed': 13.68, - }), - dict({ - 'cloud_coverage': 75, - 'condition': 'partlycloudy', - 'datetime': '2023-08-16T12:00:00', - 'humidity': 61, - 'precipitation': 2.4, - 'pressure': 1014.0, - 'temperature': 20.0, - 'templow': 14.0, - 'wind_bearing': 233, - 'wind_gust_speed': 33.48, - 'wind_speed': 14.04, - }), - ]), 'friendly_name': 'test', 'humidity': 100, 'precipitation_unit': , @@ -820,18 +354,3 @@ 'wind_speed_unit': , }) # --- -# name: test_setup_hass.1 - dict({ - 'cloud_coverage': 100, - 'condition': 'rainy', - 'datetime': '2023-08-08T12:00:00', - 'humidity': 97, - 'precipitation': 10.6, - 'pressure': 984.0, - 'temperature': 15.0, - 'templow': 11.0, - 'wind_bearing': 183, - 'wind_gust_speed': 27.36, - 'wind_speed': 11.16, - }) -# --- diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 87aae74e71d..4d187e7c728 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -10,7 +10,6 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.components.smhi.const import ATTR_SMHI_THUNDER_PROBABILITY from homeassistant.components.smhi.weather import CONDITION_CLASSES, RETRY_TIMEOUT from homeassistant.components.weather import ( - ATTR_FORECAST, ATTR_FORECAST_CONDITION, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, @@ -65,10 +64,6 @@ async def test_setup_hass( assert state assert state.state == "fog" assert state.attributes == snapshot - assert len(state.attributes["forecast"]) == 10 - - forecast = state.attributes["forecast"][1] - assert forecast == snapshot async def test_properties_no_data(hass: HomeAssistant) -> None: @@ -95,7 +90,6 @@ async def test_properties_no_data(hass: HomeAssistant) -> None: assert ATTR_WEATHER_VISIBILITY not in state.attributes assert ATTR_WEATHER_WIND_SPEED not in state.attributes assert ATTR_WEATHER_WIND_BEARING not in state.attributes - assert ATTR_FORECAST not in state.attributes assert ATTR_WEATHER_CLOUD_COVERAGE not in state.attributes assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes assert ATTR_WEATHER_WIND_GUST_SPEED not in state.attributes @@ -183,10 +177,16 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None: assert state assert state.name == "test" assert state.state == STATE_UNKNOWN - assert ATTR_FORECAST in state.attributes + response = await hass.services.async_call( + WEATHER_DOMAIN, + SERVICE_GET_FORECASTS, + {"entity_id": ENTITY_ID, "type": "daily"}, + blocking=True, + return_response=True, + ) assert all( forecast[ATTR_FORECAST_CONDITION] is None - for forecast in state.attributes[ATTR_FORECAST] + for forecast in response[ENTITY_ID]["forecast"] ) diff --git a/tests/components/template/snapshots/test_weather.ambr b/tests/components/template/snapshots/test_weather.ambr index 0ee7f967176..9b0cf2b9471 100644 --- a/tests/components/template/snapshots/test_weather.ambr +++ b/tests/components/template/snapshots/test_weather.ambr @@ -55,22 +55,12 @@ # name: test_forecasts[config0-1-weather-get_forecast] dict({ 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2023-02-17T14:00:00+00:00', - 'temperature': 14.2, - }), ]), }) # --- # name: test_forecasts[config0-1-weather-get_forecast].1 dict({ 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2023-02-17T14:00:00+00:00', - 'temperature': 14.2, - }), ]), }) # --- @@ -89,11 +79,6 @@ # name: test_forecasts[config0-1-weather-get_forecast].3 dict({ 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2023-02-17T14:00:00+00:00', - 'temperature': 16.9, - }), ]), }) # --- @@ -101,11 +86,6 @@ dict({ 'weather.forecast': dict({ 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2023-02-17T14:00:00+00:00', - 'temperature': 14.2, - }), ]), }), }) @@ -114,11 +94,6 @@ dict({ 'weather.forecast': dict({ 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2023-02-17T14:00:00+00:00', - 'temperature': 14.2, - }), ]), }), }) @@ -141,11 +116,6 @@ dict({ 'weather.forecast': dict({ 'forecast': list([ - dict({ - 'condition': 'cloudy', - 'datetime': '2023-02-17T14:00:00+00:00', - 'temperature': 16.9, - }), ]), }), }) diff --git a/tests/components/template/test_weather.py b/tests/components/template/test_weather.py index 4165a509ee4..e457f2e263b 100644 --- a/tests/components/template/test_weather.py +++ b/tests/components/template/test_weather.py @@ -6,7 +6,6 @@ import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.components.weather import ( - ATTR_FORECAST, ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_DEW_POINT, @@ -36,6 +35,8 @@ from tests.common import ( mock_restore_cache_with_extra_data, ) +ATTR_FORECAST = "forecast" + @pytest.mark.parametrize(("count", "domain"), [(1, WEATHER_DOMAIN)]) @pytest.mark.parametrize( @@ -49,7 +50,6 @@ from tests.common import ( "name": "test", "attribution_template": "{{ states('sensor.attribution') }}", "condition_template": "sunny", - "forecast_template": "{{ states.weather.demo.attributes.forecast }}", "temperature_template": "{{ states('sensor.temperature') | float }}", "humidity_template": "{{ states('sensor.humidity') | int }}", "pressure_template": "{{ states('sensor.pressure') }}", @@ -111,7 +111,6 @@ async def test_template_state_text(hass: HomeAssistant, start_ha) -> None: "platform": "template", "name": "forecast", "condition_template": "sunny", - "forecast_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_hourly_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}", @@ -238,7 +237,6 @@ async def test_forecasts( "platform": "template", "name": "forecast", "condition_template": "sunny", - "forecast_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_daily_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}", "temperature_template": "{{ states('sensor.temperature') | float }}", @@ -323,7 +321,6 @@ async def test_forecast_invalid( "platform": "template", "name": "forecast", "condition_template": "sunny", - "forecast_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}", "temperature_template": "{{ states('sensor.temperature') | float }}", "humidity_template": "{{ states('sensor.humidity') | int }}", @@ -393,7 +390,6 @@ async def test_forecast_invalid_is_daytime_missing_in_twice_daily( "platform": "template", "name": "forecast", "condition_template": "sunny", - "forecast_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_twice_daily_template": "{{ states.weather.forecast_twice_daily.attributes.forecast }}", "temperature_template": "{{ states('sensor.temperature') | float }}", "humidity_template": "{{ states('sensor.humidity') | int }}", @@ -463,7 +459,6 @@ async def test_forecast_invalid_datetime_missing( "platform": "template", "name": "forecast", "condition_template": "sunny", - "forecast_template": "{{ states.weather.forecast.attributes.forecast }}", "forecast_daily_template": "{{ states.weather.forecast_daily.attributes.forecast }}", "forecast_hourly_template": "{{ states.weather.forecast_hourly.attributes.forecast }}", "temperature_template": "{{ states('sensor.temperature') | float }}", @@ -728,7 +723,6 @@ async def test_trigger_action( "cloud_coverage_template": "{{ my_variable + 1 }}", "dew_point_template": "{{ my_variable + 1 }}", "apparent_temperature_template": "{{ my_variable + 1 }}", - "forecast_template": "{{ var_forecast_daily }}", "forecast_daily_template": "{{ var_forecast_daily }}", "forecast_hourly_template": "{{ var_forecast_hourly }}", "forecast_twice_daily_template": "{{ var_forecast_twice_daily }}", diff --git a/tests/components/tomorrowio/test_weather.py b/tests/components/tomorrowio/test_weather.py index a87c0363b7a..6f5117df9d5 100644 --- a/tests/components/tomorrowio/test_weather.py +++ b/tests/components/tomorrowio/test_weather.py @@ -23,17 +23,6 @@ from homeassistant.components.tomorrowio.const import ( ) from homeassistant.components.weather import ( ATTR_CONDITION_SUNNY, - ATTR_FORECAST, - ATTR_FORECAST_CONDITION, - ATTR_FORECAST_DEW_POINT, - ATTR_FORECAST_HUMIDITY, - ATTR_FORECAST_PRECIPITATION, - ATTR_FORECAST_PRECIPITATION_PROBABILITY, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_TIME, - ATTR_FORECAST_WIND_BEARING, - ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_OZONE, ATTR_WEATHER_PRECIPITATION_UNIT, @@ -216,19 +205,6 @@ async def test_v4_weather(hass: HomeAssistant, tomorrowio_config_entry_update) - assert weather_state.state == ATTR_CONDITION_SUNNY assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION - assert len(weather_state.attributes[ATTR_FORECAST]) == 14 - assert weather_state.attributes[ATTR_FORECAST][0] == { - ATTR_FORECAST_CONDITION: ATTR_CONDITION_SUNNY, - ATTR_FORECAST_TIME: "2021-03-07T11:00:00+00:00", - ATTR_FORECAST_PRECIPITATION: 0, - ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 45.9, - ATTR_FORECAST_TEMP_LOW: 26.1, - ATTR_FORECAST_DEW_POINT: 12.8, - ATTR_FORECAST_HUMIDITY: 58, - ATTR_FORECAST_WIND_BEARING: 239.6, - ATTR_FORECAST_WIND_SPEED: 34.16, # 9.49 m/s -> km/h - } assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io Daily" assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53 @@ -249,19 +225,6 @@ async def test_v4_weather_legacy_entities(hass: HomeAssistant) -> None: weather_state = await _setup_legacy(hass, API_V4_ENTRY_DATA) assert weather_state.state == ATTR_CONDITION_SUNNY assert weather_state.attributes[ATTR_ATTRIBUTION] == ATTRIBUTION - assert len(weather_state.attributes[ATTR_FORECAST]) == 14 - assert weather_state.attributes[ATTR_FORECAST][0] == { - ATTR_FORECAST_CONDITION: ATTR_CONDITION_SUNNY, - ATTR_FORECAST_TIME: "2021-03-07T11:00:00+00:00", - ATTR_FORECAST_DEW_POINT: 12.8, - ATTR_FORECAST_HUMIDITY: 58, - ATTR_FORECAST_PRECIPITATION: 0, - ATTR_FORECAST_PRECIPITATION_PROBABILITY: 0, - ATTR_FORECAST_TEMP: 45.9, - ATTR_FORECAST_TEMP_LOW: 26.1, - ATTR_FORECAST_WIND_BEARING: 239.6, - ATTR_FORECAST_WIND_SPEED: 34.16, # 9.49 m/s -> km/h - } assert weather_state.attributes[ATTR_FRIENDLY_NAME] == "Tomorrow.io Daily" assert weather_state.attributes[ATTR_WEATHER_HUMIDITY] == 23 assert weather_state.attributes[ATTR_WEATHER_OZONE] == 46.53 diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index 3dcb78e3257..195a4c9ef67 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -7,17 +7,6 @@ from syrupy.assertion import SnapshotAssertion from homeassistant.components.weather import ( ATTR_CONDITION_SUNNY, - ATTR_FORECAST, - ATTR_FORECAST_APPARENT_TEMP, - ATTR_FORECAST_DEW_POINT, - ATTR_FORECAST_HUMIDITY, - ATTR_FORECAST_PRECIPITATION, - ATTR_FORECAST_PRESSURE, - ATTR_FORECAST_TEMP, - ATTR_FORECAST_TEMP_LOW, - ATTR_FORECAST_UV_INDEX, - ATTR_FORECAST_WIND_GUST_SPEED, - ATTR_FORECAST_WIND_SPEED, ATTR_WEATHER_APPARENT_TEMPERATURE, ATTR_WEATHER_OZONE, ATTR_WEATHER_PRECIPITATION_UNIT, @@ -79,6 +68,7 @@ class MockWeatherEntity(WeatherEntity): def __init__(self) -> None: """Initiate Entity.""" super().__init__() + self._attr_precision = PRECISION_TENTHS self._attr_condition = ATTR_CONDITION_SUNNY self._attr_native_precipitation_unit = UnitOfLength.MILLIMETERS self._attr_native_pressure = 10 @@ -92,14 +82,6 @@ class MockWeatherEntity(WeatherEntity): self._attr_native_wind_gust_speed = 10 self._attr_native_wind_speed = 3 self._attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND - self._attr_forecast = [ - Forecast( - datetime=datetime(2022, 6, 20, 00, 00, 00, tzinfo=dt_util.UTC), - native_precipitation=1, - native_temperature=20, - native_dew_point=2, - ) - ] self._attr_forecast_twice_daily = [ Forecast( datetime=datetime(2022, 6, 20, 8, 00, 00, tzinfo=dt_util.UTC), @@ -141,14 +123,6 @@ async def test_temperature( dew_point_native_value, native_unit, state_unit ) - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = { "native_temperature": native_value, "native_temperature_unit": native_unit, @@ -156,10 +130,9 @@ async def test_temperature( "native_dew_point": dew_point_native_value, } - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast_daily = state.attributes[ATTR_FORECAST][0] expected = state_value apparent_expected = apparent_state_value @@ -174,20 +147,6 @@ async def test_temperature( dew_point_expected, rel=0.1 ) assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit - assert float(forecast_daily[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1) - assert float(forecast_daily[ATTR_FORECAST_APPARENT_TEMP]) == pytest.approx( - apparent_expected, rel=0.1 - ) - assert float(forecast_daily[ATTR_FORECAST_DEW_POINT]) == pytest.approx( - dew_point_expected, rel=0.1 - ) - assert float(forecast_daily[ATTR_FORECAST_TEMP_LOW]) == pytest.approx( - expected, rel=0.1 - ) - assert float(forecast_daily[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1) - assert float(forecast_daily[ATTR_FORECAST_TEMP_LOW]) == pytest.approx( - expected, rel=0.1 - ) @pytest.mark.parametrize("native_unit", [None]) @@ -214,14 +173,6 @@ async def test_temperature_no_unit( dew_point_state_value = dew_point_native_value apparent_temp_state_value = apparent_temp_native_value - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = { "native_temperature": native_value, "native_temperature_unit": native_unit, @@ -229,10 +180,9 @@ async def test_temperature_no_unit( "native_apparent_temperature": apparent_temp_native_value, } - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected = state_value dew_point_expected = dew_point_state_value @@ -247,14 +197,6 @@ async def test_temperature_no_unit( expected_apparent_temp, rel=0.1 ) assert state.attributes[ATTR_WEATHER_TEMPERATURE_UNIT] == state_unit - assert float(forecast[ATTR_FORECAST_TEMP]) == pytest.approx(expected, rel=0.1) - assert float(forecast[ATTR_FORECAST_DEW_POINT]) == pytest.approx( - dew_point_expected, rel=0.1 - ) - assert float(forecast[ATTR_FORECAST_TEMP_LOW]) == pytest.approx(expected, rel=0.1) - assert float(forecast[ATTR_FORECAST_APPARENT_TEMP]) == pytest.approx( - expected_apparent_temp, rel=0.1 - ) @pytest.mark.parametrize( @@ -276,26 +218,16 @@ async def test_pressure( native_value = 30 state_value = PressureConverter.convert(native_value, native_unit, state_unit) - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = {"native_pressure": native_value, "native_pressure_unit": native_unit} - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected = state_value assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx( expected, rel=1e-2 ) - assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2) @pytest.mark.parametrize("native_unit", [None]) @@ -315,26 +247,16 @@ async def test_pressure_no_unit( native_value = 30 state_value = native_value - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = {"native_pressure": native_value, "native_pressure_unit": native_unit} - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected = state_value assert float(state.attributes[ATTR_WEATHER_PRESSURE]) == pytest.approx( expected, rel=1e-2 ) - assert float(forecast[ATTR_FORECAST_PRESSURE]) == pytest.approx(expected, rel=1e-2) @pytest.mark.parametrize( @@ -364,28 +286,16 @@ async def test_wind_speed( native_value = 10 state_value = SpeedConverter.convert(native_value, native_unit, state_unit) - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = {"native_wind_speed": native_value, "native_wind_speed_unit": native_unit} - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected = state_value assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx( expected, rel=1e-2 ) - assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == pytest.approx( - expected, rel=1e-2 - ) @pytest.mark.parametrize( @@ -415,31 +325,19 @@ async def test_wind_gust_speed( native_value = 10 state_value = SpeedConverter.convert(native_value, native_unit, state_unit) - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = { "native_wind_gust_speed": native_value, "native_wind_speed_unit": native_unit, } - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected = state_value assert float(state.attributes[ATTR_WEATHER_WIND_GUST_SPEED]) == pytest.approx( expected, rel=1e-2 ) - assert float(forecast[ATTR_FORECAST_WIND_GUST_SPEED]) == pytest.approx( - expected, rel=1e-2 - ) @pytest.mark.parametrize("native_unit", [None]) @@ -462,194 +360,16 @@ async def test_wind_speed_no_unit( native_value = 10 state_value = native_value - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = {"native_wind_speed": native_value, "native_wind_speed_unit": native_unit} - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected = state_value assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx( expected, rel=1e-2 ) - assert float(forecast[ATTR_FORECAST_WIND_SPEED]) == pytest.approx( - expected, rel=1e-2 - ) - - -@pytest.mark.parametrize("native_unit", [UnitOfLength.MILES, UnitOfLength.KILOMETERS]) -@pytest.mark.parametrize( - ("state_unit", "unit_system"), - [ - (UnitOfLength.KILOMETERS, METRIC_SYSTEM), - (UnitOfLength.MILES, US_CUSTOMARY_SYSTEM), - ], -) -async def test_visibility( - hass: HomeAssistant, - config_flow_fixture: None, - native_unit: str, - state_unit: str, - unit_system, -) -> None: - """Test visibility.""" - hass.config.units = unit_system - native_value = 10 - state_value = DistanceConverter.convert(native_value, native_unit, state_unit) - - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = {"native_visibility": native_value, "native_visibility_unit": native_unit} - - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) - - state = hass.states.get(entity0.entity_id) - expected = state_value - assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx( - expected, rel=1e-2 - ) - - -@pytest.mark.parametrize("native_unit", [None]) -@pytest.mark.parametrize( - ("state_unit", "unit_system"), - [ - (UnitOfLength.KILOMETERS, METRIC_SYSTEM), - (UnitOfLength.MILES, US_CUSTOMARY_SYSTEM), - ], -) -async def test_visibility_no_unit( - hass: HomeAssistant, - config_flow_fixture: None, - native_unit: str, - state_unit: str, - unit_system, -) -> None: - """Test visibility when the entity does not declare a native unit.""" - hass.config.units = unit_system - native_value = 10 - state_value = native_value - - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = {"native_visibility": native_value, "native_visibility_unit": native_unit} - - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) - - state = hass.states.get(entity0.entity_id) - expected = state_value - assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx( - expected, rel=1e-2 - ) - - -@pytest.mark.parametrize("native_unit", [UnitOfLength.INCHES, UnitOfLength.MILLIMETERS]) -@pytest.mark.parametrize( - ("state_unit", "unit_system"), - [ - (UnitOfLength.MILLIMETERS, METRIC_SYSTEM), - (UnitOfLength.INCHES, US_CUSTOMARY_SYSTEM), - ], -) -async def test_precipitation( - hass: HomeAssistant, - config_flow_fixture: None, - native_unit: str, - state_unit: str, - unit_system, -) -> None: - """Test precipitation.""" - hass.config.units = unit_system - native_value = 30 - state_value = DistanceConverter.convert(native_value, native_unit, state_unit) - - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = { - "native_precipitation": native_value, - "native_precipitation_unit": native_unit, - } - - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) - - state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] - - expected = state_value - assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx( - expected, rel=1e-2 - ) - - -@pytest.mark.parametrize("native_unit", [None]) -@pytest.mark.parametrize( - ("state_unit", "unit_system"), - [ - (UnitOfLength.MILLIMETERS, METRIC_SYSTEM), - (UnitOfLength.INCHES, US_CUSTOMARY_SYSTEM), - ], -) -async def test_precipitation_no_unit( - hass: HomeAssistant, - config_flow_fixture: None, - native_unit: str, - state_unit: str, - unit_system, -) -> None: - """Test precipitation when the entity does not declare a native unit.""" - hass.config.units = unit_system - native_value = 30 - state_value = native_value - - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = { - "native_precipitation": native_value, - "native_precipitation_unit": native_unit, - } - - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) - - state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] - - expected = state_value - assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx( - expected, rel=1e-2 - ) async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index( @@ -662,14 +382,6 @@ async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index( cloud_coverage = 75 uv_index = 1.2 - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = { "wind_bearing": wind_bearing_value, "ozone": ozone_value, @@ -677,15 +389,13 @@ async def test_wind_bearing_ozone_and_cloud_coverage_and_uv_index( "uv_index": uv_index, } - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] assert float(state.attributes[ATTR_WEATHER_WIND_BEARING]) == 180 assert float(state.attributes[ATTR_WEATHER_OZONE]) == 10 assert float(state.attributes[ATTR_WEATHER_CLOUD_COVERAGE]) == 75 assert float(state.attributes[ATTR_WEATHER_UV_INDEX]) == 1.2 - assert float(forecast[ATTR_FORECAST_UV_INDEX]) == 1.2 async def test_humidity( @@ -695,55 +405,12 @@ async def test_humidity( """Test humidity.""" humidity_value = 80.2 - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = {"humidity": humidity_value} - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] assert float(state.attributes[ATTR_WEATHER_HUMIDITY]) == 80 - assert float(forecast[ATTR_FORECAST_HUMIDITY]) == 80 - - -async def test_none_forecast( - hass: HomeAssistant, - config_flow_fixture: None, -) -> None: - """Test that conversion with None values succeeds.""" - - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = { - "native_pressure": None, - "native_pressure_unit": UnitOfPressure.INHG, - "native_wind_speed": None, - "native_wind_speed_unit": UnitOfSpeed.METERS_PER_SECOND, - "native_precipitation": None, - "native_precipitation_unit": UnitOfLength.MILLIMETERS, - } - - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) - - state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] - - assert forecast.get(ATTR_FORECAST_PRESSURE) is None - assert forecast.get(ATTR_FORECAST_WIND_SPEED) is None - assert forecast.get(ATTR_FORECAST_PRECIPITATION) is None async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> None: @@ -773,14 +440,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N entity_registry.async_update_entity_options(entry.entity_id, "weather", set_options) await hass.async_block_till_done() - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = { "native_temperature": temperature_value, "native_temperature_unit": temperature_unit, @@ -796,10 +455,9 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N "unique_id": "very_unique", } - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) state = hass.states.get(entity0.entity_id) - forecast = state.attributes[ATTR_FORECAST][0] expected_wind_speed = round( SpeedConverter.convert( @@ -820,12 +478,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N ), ROUNDING_PRECISION, ) - expected_precipitation = round( - DistanceConverter.convert( - precipitation_value, precipitation_unit, UnitOfLength.INCHES - ), - ROUNDING_PRECISION, - ) assert float(state.attributes[ATTR_WEATHER_WIND_SPEED]) == pytest.approx( expected_wind_speed @@ -839,9 +491,6 @@ async def test_custom_units(hass: HomeAssistant, config_flow_fixture: None) -> N assert float(state.attributes[ATTR_WEATHER_VISIBILITY]) == pytest.approx( expected_visibility ) - assert float(forecast[ATTR_FORECAST_PRECIPITATION]) == pytest.approx( - expected_precipitation, rel=1e-2 - ) assert ( state.attributes[ATTR_WEATHER_PRECIPITATION_UNIT] @@ -925,14 +574,6 @@ async def test_forecast_twice_daily_missing_is_daytime( ) -> None: """Test forecast_twice_daily missing mandatory attribute is_daytime.""" - class MockWeatherMock(MockWeatherTest): - """Mock weather class.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - kwargs = { "native_temperature": 38, "native_temperature_unit": UnitOfTemperature.CELSIUS, @@ -940,7 +581,7 @@ async def test_forecast_twice_daily_missing_is_daytime( "supported_features": WeatherEntityFeature.FORECAST_TWICE_DAILY, } - entity0 = await create_entity(hass, MockWeatherMock, None, **kwargs) + entity0 = await create_entity(hass, MockWeatherTest, None, **kwargs) client = await hass_ws_client(hass) @@ -1147,155 +788,6 @@ async def test_get_forecast_unsupported( ISSUE_TRACKER = "https://blablabla.com" -@pytest.mark.parametrize( - ("manifest_extra", "translation_key", "translation_placeholders_extra", "report"), - [ - ( - {}, - "deprecated_weather_forecast_no_url", - {}, - "report it to the author of the 'test' custom integration", - ), - ( - {"issue_tracker": ISSUE_TRACKER}, - "deprecated_weather_forecast_url", - {"issue_tracker": ISSUE_TRACKER}, - "create a bug report at https://blablabla.com", - ), - ], -) -async def test_issue_forecast_property_deprecated( - hass: HomeAssistant, - caplog: pytest.LogCaptureFixture, - config_flow_fixture: None, - manifest_extra: dict[str, str], - translation_key: str, - translation_placeholders_extra: dict[str, str], - report: str, -) -> None: - """Test the issue is raised on deprecated forecast attributes.""" - - class MockWeatherMockLegacyForecastOnly(MockWeatherTest): - """Mock weather class with mocked legacy forecast.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - # Fake that the class belongs to a custom integration - MockWeatherMockLegacyForecastOnly.__module__ = "custom_components.test.weather" - - kwargs = { - "native_temperature": 38, - "native_temperature_unit": UnitOfTemperature.CELSIUS, - } - weather_entity = await create_entity( - hass, MockWeatherMockLegacyForecastOnly, manifest_extra, **kwargs - ) - - assert weather_entity.state == ATTR_CONDITION_SUNNY - - issues = ir.async_get(hass) - issue = issues.async_get_issue("weather", "deprecated_weather_forecast_test") - assert issue - assert issue.issue_domain == "test" - assert issue.issue_id == "deprecated_weather_forecast_test" - assert issue.translation_key == translation_key - assert ( - issue.translation_placeholders - == {"platform": "test"} | translation_placeholders_extra - ) - - assert ( - "test::MockWeatherMockLegacyForecastOnly implements the `forecast` property or " - "sets `self._attr_forecast` in a subclass of WeatherEntity, this is deprecated " - f"and will be unsupported from Home Assistant 2024.3. Please {report}" - ) in caplog.text - - -async def test_issue_forecast_attr_deprecated( - hass: HomeAssistant, - issue_registry: ir.IssueRegistry, - config_flow_fixture: None, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the issue is raised on deprecated forecast attributes.""" - - class MockWeatherMockLegacyForecast(MockWeatherTest): - """Mock weather class with legacy forecast.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = { - "native_temperature": 38, - "native_temperature_unit": UnitOfTemperature.CELSIUS, - } - - # Fake that the class belongs to a custom integration - MockWeatherMockLegacyForecast.__module__ = "custom_components.test.weather" - - weather_entity = await create_entity( - hass, MockWeatherMockLegacyForecast, None, **kwargs - ) - - assert weather_entity.state == ATTR_CONDITION_SUNNY - - issue = issue_registry.async_get_issue( - "weather", "deprecated_weather_forecast_test" - ) - assert issue - assert issue.issue_domain == "test" - assert issue.issue_id == "deprecated_weather_forecast_test" - assert issue.translation_key == "deprecated_weather_forecast_no_url" - assert issue.translation_placeholders == {"platform": "test"} - - assert ( - "test::MockWeatherMockLegacyForecast implements the `forecast` property or " - "sets `self._attr_forecast` in a subclass of WeatherEntity, this is deprecated " - "and will be unsupported from Home Assistant 2024.3. Please report it to the " - "author of the 'test' custom integration" - ) in caplog.text - - -async def test_issue_forecast_deprecated_no_logging( - hass: HomeAssistant, - config_flow_fixture: None, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the no issue is raised on deprecated forecast attributes if new methods exist.""" - - class MockWeatherMockForecast(MockWeatherTest): - """Mock weather class with mocked new method and legacy forecast.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - async def async_forecast_daily(self) -> list[Forecast] | None: - """Return the forecast_daily.""" - return self.forecast_list - - kwargs = { - "native_temperature": 38, - "native_temperature_unit": UnitOfTemperature.CELSIUS, - } - - weather_entity = await create_entity(hass, MockWeatherMockForecast, None, **kwargs) - - assert weather_entity.state == ATTR_CONDITION_SUNNY - - assert "Setting up test.weather" in caplog.text - assert ( - "custom_components.test_weather.weather::weather.test is using a forecast attribute on an instance of WeatherEntity" - not in caplog.text - ) - - async def test_issue_deprecated_service_weather_get_forecast( hass: HomeAssistant, issue_registry: ir.IssueRegistry, diff --git a/tests/components/weather/test_recorder.py b/tests/components/weather/test_recorder.py deleted file mode 100644 index 2986ec19a8c..00000000000 --- a/tests/components/weather/test_recorder.py +++ /dev/null @@ -1,59 +0,0 @@ -"""The tests for weather recorder.""" - -from __future__ import annotations - -from datetime import timedelta - -from homeassistant.components.recorder import Recorder -from homeassistant.components.recorder.history import get_significant_states -from homeassistant.components.weather import ATTR_FORECAST, Forecast -from homeassistant.const import UnitOfTemperature -from homeassistant.core import HomeAssistant -from homeassistant.util import dt as dt_util -from homeassistant.util.unit_system import METRIC_SYSTEM - -from . import MockWeatherTest, create_entity - -from tests.common import async_fire_time_changed -from tests.components.recorder.common import async_wait_recording_done - - -async def test_exclude_attributes( - recorder_mock: Recorder, - hass: HomeAssistant, - config_flow_fixture: None, -) -> None: - """Test weather attributes to be excluded.""" - now = dt_util.utcnow() - - class MockWeatherMockForecast(MockWeatherTest): - """Mock weather class with mocked legacy forecast.""" - - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - - kwargs = { - "native_temperature": 38, - "native_temperature_unit": UnitOfTemperature.CELSIUS, - } - weather_entity = await create_entity(hass, MockWeatherMockForecast, None, **kwargs) - hass.config.units = METRIC_SYSTEM - await hass.async_block_till_done() - - state = hass.states.get(weather_entity.entity_id) - assert state.attributes[ATTR_FORECAST] - - await hass.async_block_till_done() - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() - await async_wait_recording_done(hass) - - states = await hass.async_add_executor_job( - get_significant_states, hass, now, None, hass.states.async_entity_ids() - ) - assert len(states) >= 1 - for entity_states in states.values(): - for state in entity_states: - assert ATTR_FORECAST not in state.attributes diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index a9ba1c99872..0e99ef48680 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -168,11 +168,6 @@ class MockWeatherMockForecast(MockWeather): } ] - @property - def forecast(self) -> list[Forecast] | None: - """Return the forecast.""" - return self.forecast_list - async def async_forecast_daily(self) -> list[Forecast] | None: """Return the forecast_daily.""" return self.forecast_list From c518acfef355e83020d72e5391e2b9fab1882be2 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 27 Mar 2024 12:48:43 -0400 Subject: [PATCH 1582/1691] Defensively validate ZHA quirks v2 supplied entity metadata (#112643) --- homeassistant/components/zha/binary_sensor.py | 25 +- homeassistant/components/zha/button.py | 36 +- homeassistant/components/zha/core/const.py | 4 +- .../components/zha/core/discovery.py | 29 +- homeassistant/components/zha/core/helpers.py | 108 +++++- homeassistant/components/zha/entity.py | 33 +- homeassistant/components/zha/manifest.json | 2 +- homeassistant/components/zha/number.py | 48 +-- homeassistant/components/zha/select.py | 17 +- homeassistant/components/zha/sensor.py | 56 ++-- homeassistant/components/zha/switch.py | 27 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zha/test_discover.py | 315 +++++++++++++++++- tests/components/zha/test_helpers.py | 29 +- tests/components/zha/test_select.py | 9 +- tests/components/zha/test_sensor.py | 4 +- 17 files changed, 602 insertions(+), 144 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 195b0a1dcf5..6ffb6d6f909 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -3,8 +3,9 @@ from __future__ import annotations import functools +import logging -from zigpy.quirks.v2 import BinarySensorMetadata, EntityMetadata +from zigpy.quirks.v2 import BinarySensorMetadata import zigpy.types as t from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasZone @@ -27,11 +28,11 @@ from .core.const import ( CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, CLUSTER_HANDLER_ZONE, - QUIRK_METADATA, + ENTITY_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) -from .core.helpers import get_zha_data +from .core.helpers import get_zha_data, validate_device_class from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity @@ -51,6 +52,8 @@ CONFIG_DIAGNOSTIC_MATCH = functools.partial( ZHA_ENTITIES.config_diagnostic_match, Platform.BINARY_SENSOR ) +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, @@ -79,15 +82,21 @@ class BinarySensor(ZhaEntity, BinarySensorEntity): def __init__(self, unique_id, zha_device, cluster_handlers, **kwargs) -> None: """Initialize the ZHA binary sensor.""" self._cluster_handler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata(self, entity_metadata: BinarySensorMetadata) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - binary_sensor_metadata: BinarySensorMetadata = entity_metadata.entity_metadata - self._attribute_name = binary_sensor_metadata.attribute_name + self._attribute_name = entity_metadata.attribute_name + if entity_metadata.device_class is not None: + self._attr_device_class = validate_device_class( + BinarySensorDeviceClass, + entity_metadata.device_class, + Platform.BINARY_SENSOR.value, + _LOGGER, + ) async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" diff --git a/homeassistant/components/zha/button.py b/homeassistant/components/zha/button.py index 48b27ee6892..33102062443 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -6,11 +6,7 @@ import functools import logging from typing import TYPE_CHECKING, Any, Self -from zigpy.quirks.v2 import ( - EntityMetadata, - WriteAttributeButtonMetadata, - ZCLCommandButtonMetadata, -) +from zigpy.quirks.v2 import WriteAttributeButtonMetadata, ZCLCommandButtonMetadata from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry @@ -20,7 +16,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .core import discovery -from .core.const import CLUSTER_HANDLER_IDENTIFY, QUIRK_METADATA, SIGNAL_ADD_ENTITIES +from .core.const import CLUSTER_HANDLER_IDENTIFY, ENTITY_METADATA, SIGNAL_ADD_ENTITIES from .core.helpers import get_zha_data from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity @@ -76,17 +72,18 @@ class ZHAButton(ZhaEntity, ButtonEntity): ) -> None: """Init this button.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata( + self, entity_metadata: ZCLCommandButtonMetadata + ) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - button_metadata: ZCLCommandButtonMetadata = entity_metadata.entity_metadata - self._command_name = button_metadata.command_name - self._args = button_metadata.args - self._kwargs = button_metadata.kwargs + self._command_name = entity_metadata.command_name + self._args = entity_metadata.args + self._kwargs = entity_metadata.kwargs def get_args(self) -> list[Any]: """Return the arguments to use in the command.""" @@ -148,16 +145,17 @@ class ZHAAttributeButton(ZhaEntity, ButtonEntity): ) -> None: """Init this button.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata( + self, entity_metadata: WriteAttributeButtonMetadata + ) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - button_metadata: WriteAttributeButtonMetadata = entity_metadata.entity_metadata - self._attribute_name = button_metadata.attribute_name - self._attribute_value = button_metadata.attribute_value + self._attribute_name = entity_metadata.attribute_name + self._attribute_value = entity_metadata.attribute_value async def async_press(self) -> None: """Write attribute with defined value.""" diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 8d56652d8ee..74110d390ed 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -220,6 +220,8 @@ DISCOVERY_KEY = "zha_discovery_info" DOMAIN = "zha" +ENTITY_METADATA = "entity_metadata" + GROUP_ID = "group_id" GROUP_IDS = "group_ids" GROUP_NAME = "group_name" @@ -233,8 +235,6 @@ PRESET_SCHEDULE = "Schedule" PRESET_COMPLEX = "Complex" PRESET_TEMP_MANUAL = "Temporary manual" -QUIRK_METADATA = "quirk_metadata" - ZCL_INIT_ATTRS = "ZCL_INIT_ATTRS" ZHA_ALARM_OPTIONS = "zha_alarm_options" diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index 75e5b51e599..3c342d14060 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -85,12 +85,18 @@ QUIRKS_ENTITY_META_TO_ENTITY_CLASS = { WriteAttributeButtonMetadata, EntityType.CONFIG, ): button.ZHAAttributeButton, + ( + Platform.BUTTON, + WriteAttributeButtonMetadata, + EntityType.STANDARD, + ): button.ZHAAttributeButton, (Platform.BUTTON, ZCLCommandButtonMetadata, EntityType.CONFIG): button.ZHAButton, ( Platform.BUTTON, ZCLCommandButtonMetadata, EntityType.DIAGNOSTIC, ): button.ZHAButton, + (Platform.BUTTON, ZCLCommandButtonMetadata, EntityType.STANDARD): button.ZHAButton, ( Platform.BINARY_SENSOR, BinarySensorMetadata, @@ -111,6 +117,7 @@ QUIRKS_ENTITY_META_TO_ENTITY_CLASS = { (Platform.SENSOR, ZCLSensorMetadata, EntityType.DIAGNOSTIC): sensor.Sensor, (Platform.SENSOR, ZCLSensorMetadata, EntityType.STANDARD): sensor.Sensor, (Platform.SELECT, ZCLEnumMetadata, EntityType.CONFIG): select.ZCLEnumSelectEntity, + (Platform.SELECT, ZCLEnumMetadata, EntityType.STANDARD): select.ZCLEnumSelectEntity, ( Platform.SELECT, ZCLEnumMetadata, @@ -224,7 +231,7 @@ class ProbeEndpoint: for ( cluster_details, - quirk_metadata_list, + entity_metadata_list, ) in zigpy_device.exposes_metadata.items(): endpoint_id, cluster_id, cluster_type = cluster_details @@ -265,11 +272,11 @@ class ProbeEndpoint: ) assert cluster_handler - for quirk_metadata in quirk_metadata_list: - platform = Platform(quirk_metadata.entity_platform.value) - metadata_type = type(quirk_metadata.entity_metadata) + for entity_metadata in entity_metadata_list: + platform = Platform(entity_metadata.entity_platform.value) + metadata_type = type(entity_metadata) entity_class = QUIRKS_ENTITY_META_TO_ENTITY_CLASS.get( - (platform, metadata_type, quirk_metadata.entity_type) + (platform, metadata_type, entity_metadata.entity_type) ) if entity_class is None: @@ -280,7 +287,7 @@ class ProbeEndpoint: device.name, { zha_const.CLUSTER_DETAILS: cluster_details, - zha_const.QUIRK_METADATA: quirk_metadata, + zha_const.ENTITY_METADATA: entity_metadata, }, ) continue @@ -288,13 +295,13 @@ class ProbeEndpoint: # automatically add the attribute to ZCL_INIT_ATTRS for the cluster # handler if it is not already in the list if ( - hasattr(quirk_metadata.entity_metadata, "attribute_name") - and quirk_metadata.entity_metadata.attribute_name + hasattr(entity_metadata, "attribute_name") + and entity_metadata.attribute_name not in cluster_handler.ZCL_INIT_ATTRS ): init_attrs = cluster_handler.ZCL_INIT_ATTRS.copy() - init_attrs[quirk_metadata.entity_metadata.attribute_name] = ( - quirk_metadata.attribute_initialized_from_cache + init_attrs[entity_metadata.attribute_name] = ( + entity_metadata.attribute_initialized_from_cache ) cluster_handler.__dict__[zha_const.ZCL_INIT_ATTRS] = init_attrs @@ -303,7 +310,7 @@ class ProbeEndpoint: entity_class, endpoint.unique_id, [cluster_handler], - quirk_metadata=quirk_metadata, + entity_metadata=entity_metadata, ) _LOGGER.debug( diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 22efe995954..1a001cab381 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -14,7 +14,7 @@ from dataclasses import dataclass import enum import logging import re -from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar +from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, overload import voluptuous as vol import zigpy.exceptions @@ -24,8 +24,33 @@ import zigpy.zcl from zigpy.zcl.foundation import CommandSchema import zigpy.zdo.types as zdo_types +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.number import NumberDeviceClass +from homeassistant.components.sensor import SensorDeviceClass from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import ( + Platform, + UnitOfApparentPower, + UnitOfDataRate, + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfEnergy, + UnitOfFrequency, + UnitOfInformation, + UnitOfIrradiance, + UnitOfLength, + UnitOfMass, + UnitOfPower, + UnitOfPrecipitationDepth, + UnitOfPressure, + UnitOfSoundPressure, + UnitOfSpeed, + UnitOfTemperature, + UnitOfTime, + UnitOfVolume, + UnitOfVolumeFlowRate, + UnitOfVolumetricFlux, +) from homeassistant.core import HomeAssistant, State, callback from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.typing import ConfigType @@ -218,7 +243,7 @@ def async_get_zha_config_value( ) -def async_cluster_exists(hass, cluster_id, skip_coordinator=True): +def async_cluster_exists(hass: HomeAssistant, cluster_id, skip_coordinator=True): """Determine if a device containing the specified in cluster is paired.""" zha_gateway = get_zha_gateway(hass) zha_devices = zha_gateway.devices.values() @@ -424,3 +449,80 @@ def get_zha_gateway(hass: HomeAssistant) -> ZHAGateway: raise ValueError("No gateway object exists") return zha_gateway + + +UNITS_OF_MEASURE = { + UnitOfApparentPower.__name__: UnitOfApparentPower, + UnitOfPower.__name__: UnitOfPower, + UnitOfEnergy.__name__: UnitOfEnergy, + UnitOfElectricCurrent.__name__: UnitOfElectricCurrent, + UnitOfElectricPotential.__name__: UnitOfElectricPotential, + UnitOfTemperature.__name__: UnitOfTemperature, + UnitOfTime.__name__: UnitOfTime, + UnitOfLength.__name__: UnitOfLength, + UnitOfFrequency.__name__: UnitOfFrequency, + UnitOfPressure.__name__: UnitOfPressure, + UnitOfSoundPressure.__name__: UnitOfSoundPressure, + UnitOfVolume.__name__: UnitOfVolume, + UnitOfVolumeFlowRate.__name__: UnitOfVolumeFlowRate, + UnitOfMass.__name__: UnitOfMass, + UnitOfIrradiance.__name__: UnitOfIrradiance, + UnitOfVolumetricFlux.__name__: UnitOfVolumetricFlux, + UnitOfPrecipitationDepth.__name__: UnitOfPrecipitationDepth, + UnitOfSpeed.__name__: UnitOfSpeed, + UnitOfInformation.__name__: UnitOfInformation, + UnitOfDataRate.__name__: UnitOfDataRate, +} + + +def validate_unit(quirks_unit: enum.Enum) -> enum.Enum: + """Validate and return a unit of measure.""" + return UNITS_OF_MEASURE[type(quirks_unit).__name__](quirks_unit.value) + + +@overload +def validate_device_class( + device_class_enum: type[BinarySensorDeviceClass], + metadata_value, + platform: str, + logger: logging.Logger, +) -> BinarySensorDeviceClass | None: ... + + +@overload +def validate_device_class( + device_class_enum: type[SensorDeviceClass], + metadata_value, + platform: str, + logger: logging.Logger, +) -> SensorDeviceClass | None: ... + + +@overload +def validate_device_class( + device_class_enum: type[NumberDeviceClass], + metadata_value, + platform: str, + logger: logging.Logger, +) -> NumberDeviceClass | None: ... + + +def validate_device_class( + device_class_enum: type[BinarySensorDeviceClass] + | type[SensorDeviceClass] + | type[NumberDeviceClass], + metadata_value: enum.Enum, + platform: str, + logger: logging.Logger, +) -> BinarySensorDeviceClass | SensorDeviceClass | NumberDeviceClass | None: + """Validate and return a device class.""" + try: + return device_class_enum(metadata_value.value) + except ValueError as ex: + logger.warning( + "Quirks provided an invalid device class: %s for platform %s: %s", + metadata_value, + platform, + ex, + ) + return None diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index 842450f9279..f9f63321d44 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -182,25 +182,28 @@ class ZhaEntity(BaseZhaEntity, RestoreEntity): if entity_metadata.initially_disabled: self._attr_entity_registry_enabled_default = False - if entity_metadata.translation_key: - self._attr_translation_key = entity_metadata.translation_key - - if hasattr(entity_metadata.entity_metadata, "attribute_name"): - if not entity_metadata.translation_key: - self._attr_translation_key = ( - entity_metadata.entity_metadata.attribute_name - ) - self._unique_id_suffix = entity_metadata.entity_metadata.attribute_name - elif hasattr(entity_metadata.entity_metadata, "command_name"): - if not entity_metadata.translation_key: - self._attr_translation_key = ( - entity_metadata.entity_metadata.command_name - ) - self._unique_id_suffix = entity_metadata.entity_metadata.command_name + has_device_class = hasattr(entity_metadata, "device_class") + has_attribute_name = hasattr(entity_metadata, "attribute_name") + has_command_name = hasattr(entity_metadata, "command_name") + if not has_device_class or ( + has_device_class and entity_metadata.device_class is None + ): + if entity_metadata.translation_key: + self._attr_translation_key = entity_metadata.translation_key + elif has_attribute_name: + self._attr_translation_key = entity_metadata.attribute_name + elif has_command_name: + self._attr_translation_key = entity_metadata.command_name + if has_attribute_name: + self._unique_id_suffix = entity_metadata.attribute_name + elif has_command_name: + self._unique_id_suffix = entity_metadata.command_name if entity_metadata.entity_type is EntityType.CONFIG: self._attr_entity_category = EntityCategory.CONFIG elif entity_metadata.entity_type is EntityType.DIAGNOSTIC: self._attr_entity_category = EntityCategory.DIAGNOSTIC + else: + self._attr_entity_category = None @property def available(self) -> bool: diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 95a4feadc19..e85966e870f 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -26,7 +26,7 @@ "pyserial-asyncio==0.6", "zha-quirks==0.0.112", "zigpy-deconz==0.23.1", - "zigpy==0.63.4", + "zigpy==0.63.5", "zigpy-xbee==0.20.1", "zigpy-zigate==0.12.0", "zigpy-znp==0.12.1", diff --git a/homeassistant/components/zha/number.py b/homeassistant/components/zha/number.py index 3ae261cb572..8af2fe178c8 100644 --- a/homeassistant/components/zha/number.py +++ b/homeassistant/components/zha/number.py @@ -6,10 +6,10 @@ import functools import logging from typing import TYPE_CHECKING, Any, Self -from zigpy.quirks.v2 import EntityMetadata, NumberMetadata +from zigpy.quirks.v2 import NumberMetadata from zigpy.zcl.clusters.hvac import Thermostat -from homeassistant.components.number import NumberEntity, NumberMode +from homeassistant.components.number import NumberDeviceClass, NumberEntity, NumberMode from homeassistant.config_entries import ConfigEntry from homeassistant.const import EntityCategory, Platform, UnitOfMass, UnitOfTemperature from homeassistant.core import HomeAssistant, callback @@ -26,11 +26,11 @@ from .core.const import ( CLUSTER_HANDLER_LEVEL, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_THERMOSTAT, - QUIRK_METADATA, + ENTITY_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) -from .core.helpers import get_zha_data +from .core.helpers import get_zha_data, validate_device_class, validate_unit from .core.registries import ZHA_ENTITIES from .entity import ZhaEntity @@ -403,7 +403,7 @@ class ZHANumberConfigurationEntity(ZhaEntity, NumberEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if QUIRK_METADATA not in kwargs and ( + if ENTITY_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name or cluster_handler.cluster.get(cls._attribute_name) is None @@ -426,26 +426,34 @@ class ZHANumberConfigurationEntity(ZhaEntity, NumberEntity): ) -> None: """Init this number configuration entity.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata(self, entity_metadata: NumberMetadata) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - number_metadata: NumberMetadata = entity_metadata.entity_metadata - self._attribute_name = number_metadata.attribute_name + self._attribute_name = entity_metadata.attribute_name - if number_metadata.min is not None: - self._attr_native_min_value = number_metadata.min - if number_metadata.max is not None: - self._attr_native_max_value = number_metadata.max - if number_metadata.step is not None: - self._attr_native_step = number_metadata.step - if number_metadata.unit is not None: - self._attr_native_unit_of_measurement = number_metadata.unit - if number_metadata.multiplier is not None: - self._attr_multiplier = number_metadata.multiplier + if entity_metadata.min is not None: + self._attr_native_min_value = entity_metadata.min + if entity_metadata.max is not None: + self._attr_native_max_value = entity_metadata.max + if entity_metadata.step is not None: + self._attr_native_step = entity_metadata.step + if entity_metadata.multiplier is not None: + self._attr_multiplier = entity_metadata.multiplier + if entity_metadata.device_class is not None: + self._attr_device_class = validate_device_class( + NumberDeviceClass, + entity_metadata.device_class, + Platform.NUMBER.value, + _LOGGER, + ) + if entity_metadata.device_class is None and entity_metadata.unit is not None: + self._attr_native_unit_of_measurement = validate_unit( + entity_metadata.unit + ).value @property def native_value(self) -> float: diff --git a/homeassistant/components/zha/select.py b/homeassistant/components/zha/select.py index c3c62e6173d..98d5debd999 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -11,7 +11,7 @@ from zhaquirks.quirk_ids import TUYA_PLUG_MANUFACTURER, TUYA_PLUG_ONOFF from zhaquirks.xiaomi.aqara.magnet_ac01 import OppleCluster as MagnetAC01OppleCluster from zhaquirks.xiaomi.aqara.switch_acn047 import OppleCluster as T2RelayOppleCluster from zigpy import types -from zigpy.quirks.v2 import EntityMetadata, ZCLEnumMetadata +from zigpy.quirks.v2 import ZCLEnumMetadata from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasWd @@ -29,7 +29,7 @@ from .core.const import ( CLUSTER_HANDLER_INOVELLI, CLUSTER_HANDLER_OCCUPANCY, CLUSTER_HANDLER_ON_OFF, - QUIRK_METADATA, + ENTITY_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, Strobe, @@ -179,7 +179,7 @@ class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if QUIRK_METADATA not in kwargs and ( + if ENTITY_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name or cluster_handler.cluster.get(cls._attribute_name) is None @@ -202,17 +202,16 @@ class ZCLEnumSelectEntity(ZhaEntity, SelectEntity): ) -> None: """Init this select entity.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) self._attr_options = [entry.name.replace("_", " ") for entry in self._enum] super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata(self, entity_metadata: ZCLEnumMetadata) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - zcl_enum_metadata: ZCLEnumMetadata = entity_metadata.entity_metadata - self._attribute_name = zcl_enum_metadata.attribute_name - self._enum = zcl_enum_metadata.enum + self._attribute_name = entity_metadata.attribute_name + self._enum = entity_metadata.enum @property def current_option(self) -> str | None: diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 1a11cd99593..91fe302291a 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -13,7 +13,7 @@ import random from typing import TYPE_CHECKING, Any, Self from zigpy import types -from zigpy.quirks.v2 import EntityMetadata, ZCLEnumMetadata, ZCLSensorMetadata +from zigpy.quirks.v2 import ZCLEnumMetadata, ZCLSensorMetadata from zigpy.state import Counter, State from zigpy.zcl.clusters.closures import WindowCovering from zigpy.zcl.clusters.general import Basic @@ -71,11 +71,11 @@ from .core.const import ( CLUSTER_HANDLER_TEMPERATURE, CLUSTER_HANDLER_THERMOSTAT, DATA_ZHA, - QUIRK_METADATA, + ENTITY_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) -from .core.helpers import get_zha_data +from .core.helpers import get_zha_data, validate_device_class, validate_unit from .core.registries import SMARTTHINGS_HUMIDITY_CLUSTER, ZHA_ENTITIES from .entity import BaseZhaEntity, ZhaEntity @@ -154,7 +154,7 @@ class Sensor(ZhaEntity, SensorEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if QUIRK_METADATA not in kwargs and ( + if ENTITY_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name ): @@ -176,21 +176,29 @@ class Sensor(ZhaEntity, SensorEntity): ) -> None: """Init this sensor.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata(self, entity_metadata: ZCLSensorMetadata) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - sensor_metadata: ZCLSensorMetadata = entity_metadata.entity_metadata - self._attribute_name = sensor_metadata.attribute_name - if sensor_metadata.divisor is not None: - self._divisor = sensor_metadata.divisor - if sensor_metadata.multiplier is not None: - self._multiplier = sensor_metadata.multiplier - if sensor_metadata.unit is not None: - self._attr_native_unit_of_measurement = sensor_metadata.unit + self._attribute_name = entity_metadata.attribute_name + if entity_metadata.divisor is not None: + self._divisor = entity_metadata.divisor + if entity_metadata.multiplier is not None: + self._multiplier = entity_metadata.multiplier + if entity_metadata.device_class is not None: + self._attr_device_class = validate_device_class( + SensorDeviceClass, + entity_metadata.device_class, + Platform.SENSOR.value, + _LOGGER, + ) + if entity_metadata.device_class is None and entity_metadata.unit is not None: + self._attr_native_unit_of_measurement = validate_unit( + entity_metadata.unit + ).value async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" @@ -355,12 +363,22 @@ class EnumSensor(Sensor): _attr_device_class: SensorDeviceClass = SensorDeviceClass.ENUM _enum: type[enum.Enum] - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def __init__( + self, + unique_id: str, + zha_device: ZHADevice, + cluster_handlers: list[ClusterHandler], + **kwargs: Any, + ) -> None: + """Init this sensor.""" + super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) + self._attr_options = [e.name for e in self._enum] + + def _init_from_quirks_metadata(self, entity_metadata: ZCLEnumMetadata) -> None: """Init this entity from the quirks metadata.""" ZhaEntity._init_from_quirks_metadata(self, entity_metadata) # pylint: disable=protected-access - sensor_metadata: ZCLEnumMetadata = entity_metadata.entity_metadata - self._attribute_name = sensor_metadata.attribute_name - self._enum = sensor_metadata.enum + self._attribute_name = entity_metadata.attribute_name + self._enum = entity_metadata.enum def formatter(self, value: int) -> str | None: """Use name of enum.""" diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 0561efbb2f2..14da2344cd4 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -7,7 +7,7 @@ import logging from typing import TYPE_CHECKING, Any, Self from zhaquirks.quirk_ids import TUYA_PLUG_ONOFF -from zigpy.quirks.v2 import EntityMetadata, SwitchMetadata +from zigpy.quirks.v2 import SwitchMetadata from zigpy.zcl.clusters.closures import ConfigStatus, WindowCovering, WindowCoveringMode from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import Status @@ -25,7 +25,7 @@ from .core.const import ( CLUSTER_HANDLER_COVER, CLUSTER_HANDLER_INOVELLI, CLUSTER_HANDLER_ON_OFF, - QUIRK_METADATA, + ENTITY_METADATA, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, ) @@ -192,7 +192,7 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): Return entity if it is a supported configuration, otherwise return None """ cluster_handler = cluster_handlers[0] - if QUIRK_METADATA not in kwargs and ( + if ENTITY_METADATA not in kwargs and ( cls._attribute_name in cluster_handler.cluster.unsupported_attributes or cls._attribute_name not in cluster_handler.cluster.attributes_by_name or cluster_handler.cluster.get(cls._attribute_name) is None @@ -215,21 +215,20 @@ class ZHASwitchConfigurationEntity(ZhaEntity, SwitchEntity): ) -> None: """Init this number configuration entity.""" self._cluster_handler: ClusterHandler = cluster_handlers[0] - if QUIRK_METADATA in kwargs: - self._init_from_quirks_metadata(kwargs[QUIRK_METADATA]) + if ENTITY_METADATA in kwargs: + self._init_from_quirks_metadata(kwargs[ENTITY_METADATA]) super().__init__(unique_id, zha_device, cluster_handlers, **kwargs) - def _init_from_quirks_metadata(self, entity_metadata: EntityMetadata) -> None: + def _init_from_quirks_metadata(self, entity_metadata: SwitchMetadata) -> None: """Init this entity from the quirks metadata.""" super()._init_from_quirks_metadata(entity_metadata) - switch_metadata: SwitchMetadata = entity_metadata.entity_metadata - self._attribute_name = switch_metadata.attribute_name - if switch_metadata.invert_attribute_name: - self._inverter_attribute_name = switch_metadata.invert_attribute_name - if switch_metadata.force_inverted: - self._force_inverted = switch_metadata.force_inverted - self._off_value = switch_metadata.off_value - self._on_value = switch_metadata.on_value + self._attribute_name = entity_metadata.attribute_name + if entity_metadata.invert_attribute_name: + self._inverter_attribute_name = entity_metadata.invert_attribute_name + if entity_metadata.force_inverted: + self._force_inverted = entity_metadata.force_inverted + self._off_value = entity_metadata.off_value + self._on_value = entity_metadata.on_value async def async_added_to_hass(self) -> None: """Run when about to be added to hass.""" diff --git a/requirements_all.txt b/requirements_all.txt index c3106479617..a22b8169363 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2952,7 +2952,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.63.4 +zigpy==0.63.5 # homeassistant.components.zoneminder zm-py==0.5.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index efaa3a8ad30..de5f355f742 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2281,7 +2281,7 @@ zigpy-zigate==0.12.0 zigpy-znp==0.12.1 # homeassistant.components.zha -zigpy==0.63.4 +zigpy==0.63.5 # homeassistant.components.zwave_js zwave-js-server-python==0.55.3 diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index debf233de36..f9242eb1d96 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -1,6 +1,8 @@ """Test ZHA device discovery.""" from collections.abc import Callable +import enum +import itertools import re from typing import Any from unittest import mock @@ -20,7 +22,16 @@ from zhaquirks.xiaomi.aqara.driver_curtain_e1 import ( from zigpy.const import SIG_ENDPOINTS, SIG_MANUFACTURER, SIG_MODEL, SIG_NODE_DESC import zigpy.profiles.zha import zigpy.quirks -from zigpy.quirks.v2 import EntityType, add_to_registry_v2 +from zigpy.quirks.v2 import ( + BinarySensorMetadata, + EntityMetadata, + EntityType, + NumberMetadata, + QuirksV2RegistryEntry, + ZCLCommandButtonMetadata, + ZCLSensorMetadata, + add_to_registry_v2, +) from zigpy.quirks.v2.homeassistant import UnitOfTime import zigpy.types from zigpy.zcl import ClusterType @@ -40,6 +51,7 @@ from homeassistant.const import STATE_OFF, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import EntityPlatform +from homeassistant.util.json import load_json from .common import find_entity_id, update_attribute_cache from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE @@ -520,6 +532,7 @@ async def test_quirks_v2_entity_discovery( step=1, unit=UnitOfTime.SECONDS, multiplier=1, + translation_key="on_off_transition_time", ) ) @@ -618,7 +631,11 @@ async def test_quirks_v2_entity_discovery_e1_curtain( entity_platform=Platform.SENSOR, entity_type=EntityType.DIAGNOSTIC, ) - .binary_sensor("error_detected", FakeXiaomiAqaraDriverE1.cluster_id) + .binary_sensor( + "error_detected", + FakeXiaomiAqaraDriverE1.cluster_id, + translation_key="valve_alarm", + ) ) aqara_E1_device = zigpy.quirks._DEVICE_REGISTRY.get_device(aqara_E1_device) @@ -683,7 +700,13 @@ async def test_quirks_v2_entity_discovery_e1_curtain( assert state.state == STATE_OFF -def _get_test_device(zigpy_device_mock, manufacturer: str, model: str): +def _get_test_device( + zigpy_device_mock, + manufacturer: str, + model: str, + augment_method: Callable[[QuirksV2RegistryEntry], QuirksV2RegistryEntry] + | None = None, +): zigpy_device = zigpy_device_mock( { 1: { @@ -703,7 +726,7 @@ def _get_test_device(zigpy_device_mock, manufacturer: str, model: str): model=model, ) - ( + v2_quirk = ( add_to_registry_v2(manufacturer, model, zigpy.quirks._DEVICE_REGISTRY) .replaces(PowerConfig1CRCluster) .replaces(ScenesCluster, cluster_type=ClusterType.Client) @@ -716,6 +739,7 @@ def _get_test_device(zigpy_device_mock, manufacturer: str, model: str): step=1, unit=UnitOfTime.SECONDS, multiplier=1, + translation_key="on_off_transition_time", ) .number( zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, @@ -725,14 +749,19 @@ def _get_test_device(zigpy_device_mock, manufacturer: str, model: str): step=1, unit=UnitOfTime.SECONDS, multiplier=1, + translation_key="on_off_transition_time", ) .sensor( zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, zigpy.zcl.clusters.general.OnOff.cluster_id, entity_type=EntityType.CONFIG, + translation_key="analog_input", ) ) + if augment_method: + v2_quirk = augment_method(v2_quirk) + zigpy_device = zigpy.quirks._DEVICE_REGISTRY.get_device(zigpy_device) zigpy_device.endpoints[1].power.PLUGGED_ATTR_READS = { "battery_voltage": 3, @@ -792,14 +821,13 @@ async def test_quirks_v2_entity_discovery_errors( # fmt: off entity_details = ( - "{'cluster_details': (1, 6, ), " - "'quirk_metadata': EntityMetadata(entity_metadata=ZCLSensorMetadata(" - "attribute_name='off_wait_time', divisor=1, multiplier=1, unit=None, " - "device_class=None, state_class=None), entity_platform=, entity_type=, " - "cluster_id=6, endpoint_id=1, cluster_type=, " - "initially_disabled=False, attribute_initialized_from_cache=True, " - "translation_key=None)}" + "{'cluster_details': (1, 6, ), 'entity_metadata': " + "ZCLSensorMetadata(entity_platform=, " + "entity_type=, cluster_id=6, endpoint_id=1, " + "cluster_type=, initially_disabled=False, " + "attribute_initialized_from_cache=True, translation_key='analog_input', " + "attribute_name='off_wait_time', divisor=1, multiplier=1, " + "unit=None, device_class=None, state_class=None)}" ) # fmt: on @@ -807,3 +835,266 @@ async def test_quirks_v2_entity_discovery_errors( m2 = f"details: {entity_details} that does not have an entity class mapping - " m3 = "unable to create entity" assert f"{m1}{m2}{m3}" in caplog.text + + +DEVICE_CLASS_TYPES = [NumberMetadata, BinarySensorMetadata, ZCLSensorMetadata] + + +def validate_device_class_unit( + quirk: QuirksV2RegistryEntry, + entity_metadata: EntityMetadata, + platform: Platform, + translations: dict, +) -> None: + """Ensure device class and unit are used correctly.""" + if ( + hasattr(entity_metadata, "unit") + and entity_metadata.unit is not None + and hasattr(entity_metadata, "device_class") + and entity_metadata.device_class is not None + ): + m1 = "device_class and unit are both set - unit: " + m2 = f"{entity_metadata.unit} device_class: " + m3 = f"{entity_metadata.device_class} for {platform.name} " + raise ValueError(f"{m1}{m2}{m3}{quirk}") + + +def validate_translation_keys( + quirk: QuirksV2RegistryEntry, + entity_metadata: EntityMetadata, + platform: Platform, + translations: dict, +) -> None: + """Ensure translation keys exist for all v2 quirks.""" + if isinstance(entity_metadata, ZCLCommandButtonMetadata): + default_translation_key = entity_metadata.command_name + else: + default_translation_key = entity_metadata.attribute_name + translation_key = entity_metadata.translation_key or default_translation_key + + if ( + translation_key is not None + and translation_key not in translations["entity"][platform] + ): + raise ValueError( + f"Missing translation key: {translation_key} for {platform.name} {quirk}" + ) + + +def validate_translation_keys_device_class( + quirk: QuirksV2RegistryEntry, + entity_metadata: EntityMetadata, + platform: Platform, + translations: dict, +) -> None: + """Validate translation keys and device class usage.""" + if isinstance(entity_metadata, ZCLCommandButtonMetadata): + default_translation_key = entity_metadata.command_name + else: + default_translation_key = entity_metadata.attribute_name + translation_key = entity_metadata.translation_key or default_translation_key + + metadata_type = type(entity_metadata) + if metadata_type in DEVICE_CLASS_TYPES: + device_class = entity_metadata.device_class + if device_class is not None and translation_key is not None: + m1 = "translation_key and device_class are both set - translation_key: " + m2 = f"{translation_key} device_class: {device_class} for {platform.name} " + raise ValueError(f"{m1}{m2}{quirk}") + + +def validate_metadata(validator: Callable) -> None: + """Ensure v2 quirks metadata does not violate HA rules.""" + all_v2_quirks = itertools.chain.from_iterable( + zigpy.quirks._DEVICE_REGISTRY._registry_v2.values() + ) + translations = load_json("homeassistant/components/zha/strings.json") + for quirk in all_v2_quirks: + for entity_metadata in quirk.entity_metadata: + platform = Platform(entity_metadata.entity_platform.value) + validator(quirk, entity_metadata, platform, translations) + + +def bad_translation_key(v2_quirk: QuirksV2RegistryEntry) -> QuirksV2RegistryEntry: + """Introduce a bad translation key.""" + return v2_quirk.sensor( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + entity_type=EntityType.CONFIG, + translation_key="missing_translation_key", + ) + + +def bad_device_class_unit_combination( + v2_quirk: QuirksV2RegistryEntry, +) -> QuirksV2RegistryEntry: + """Introduce a bad device class and unit combination.""" + return v2_quirk.sensor( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + entity_type=EntityType.CONFIG, + unit="invalid", + device_class="invalid", + translation_key="analog_input", + ) + + +def bad_device_class_translation_key_usage( + v2_quirk: QuirksV2RegistryEntry, +) -> QuirksV2RegistryEntry: + """Introduce a bad device class and translation key combination.""" + return v2_quirk.sensor( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + entity_type=EntityType.CONFIG, + translation_key="invalid", + device_class="invalid", + ) + + +@pytest.mark.parametrize( + ("augment_method", "validate_method", "expected_exception_string"), + [ + ( + bad_translation_key, + validate_translation_keys, + "Missing translation key: missing_translation_key", + ), + ( + bad_device_class_unit_combination, + validate_device_class_unit, + "cannot have both unit and device_class", + ), + ( + bad_device_class_translation_key_usage, + validate_translation_keys_device_class, + "cannot have both a translation_key and a device_class", + ), + ], +) +async def test_quirks_v2_metadata_errors( + hass: HomeAssistant, + zigpy_device_mock, + zha_device_joined, + augment_method: Callable[[QuirksV2RegistryEntry], QuirksV2RegistryEntry], + validate_method: Callable, + expected_exception_string: str, +) -> None: + """Ensure all v2 quirks translation keys exist.""" + + # no error yet + validate_metadata(validate_method) + + # ensure the error is caught and raised + with pytest.raises(ValueError, match=expected_exception_string): + try: + # introduce an error + zigpy_device = _get_test_device( + zigpy_device_mock, + "Ikea of Sweden4", + "TRADFRI remote control4", + augment_method=augment_method, + ) + await zha_device_joined(zigpy_device) + + validate_metadata(validate_method) + # if the device was created we remove it + # so we don't pollute the rest of the tests + zigpy.quirks._DEVICE_REGISTRY.remove(zigpy_device) + except ValueError as e: + # if the device was not created we remove it + # so we don't pollute the rest of the tests + zigpy.quirks._DEVICE_REGISTRY._registry_v2.pop( + ( + "Ikea of Sweden4", + "TRADFRI remote control4", + ) + ) + raise e + + +class BadDeviceClass(enum.Enum): + """Bad device class.""" + + BAD = "bad" + + +def bad_binary_sensor_device_class( + v2_quirk: QuirksV2RegistryEntry, +) -> QuirksV2RegistryEntry: + """Introduce a bad device class on a binary sensor.""" + + return v2_quirk.binary_sensor( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.on_off.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + device_class=BadDeviceClass.BAD, + ) + + +def bad_sensor_device_class( + v2_quirk: QuirksV2RegistryEntry, +) -> QuirksV2RegistryEntry: + """Introduce a bad device class on a sensor.""" + + return v2_quirk.sensor( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.off_wait_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + device_class=BadDeviceClass.BAD, + ) + + +def bad_number_device_class( + v2_quirk: QuirksV2RegistryEntry, +) -> QuirksV2RegistryEntry: + """Introduce a bad device class on a number.""" + + return v2_quirk.number( + zigpy.zcl.clusters.general.OnOff.AttributeDefs.on_time.name, + zigpy.zcl.clusters.general.OnOff.cluster_id, + device_class=BadDeviceClass.BAD, + ) + + +ERROR_ROOT = "Quirks provided an invalid device class" + + +@pytest.mark.parametrize( + ("augment_method", "expected_exception_string"), + [ + ( + bad_binary_sensor_device_class, + f"{ERROR_ROOT}: BadDeviceClass.BAD for platform binary_sensor", + ), + ( + bad_sensor_device_class, + f"{ERROR_ROOT}: BadDeviceClass.BAD for platform sensor", + ), + ( + bad_number_device_class, + f"{ERROR_ROOT}: BadDeviceClass.BAD for platform number", + ), + ], +) +async def test_quirks_v2_metadata_bad_device_classes( + hass: HomeAssistant, + zigpy_device_mock, + zha_device_joined, + caplog: pytest.LogCaptureFixture, + augment_method: Callable[[QuirksV2RegistryEntry], QuirksV2RegistryEntry], + expected_exception_string: str, +) -> None: + """Test bad quirks v2 device classes.""" + + # introduce an error + zigpy_device = _get_test_device( + zigpy_device_mock, + "Ikea of Sweden4", + "TRADFRI remote control4", + augment_method=augment_method, + ) + await zha_device_joined(zigpy_device) + + assert expected_exception_string in caplog.text + + # remove the device so we don't pollute the rest of the tests + zigpy.quirks._DEVICE_REGISTRY.remove(zigpy_device) diff --git a/tests/components/zha/test_helpers.py b/tests/components/zha/test_helpers.py index b97a2d0fa5d..fed8fe5bb91 100644 --- a/tests/components/zha/test_helpers.py +++ b/tests/components/zha/test_helpers.py @@ -1,11 +1,13 @@ """Tests for ZHA helpers.""" +import enum import logging from unittest.mock import patch import pytest import voluptuous_serialize import zigpy.profiles.zha as zha +from zigpy.quirks.v2.homeassistant import UnitOfPower as QuirksUnitOfPower from zigpy.types.basic import uint16_t import zigpy.zcl.clusters.general as general import zigpy.zcl.clusters.lighting as lighting @@ -13,8 +15,9 @@ import zigpy.zcl.clusters.lighting as lighting from homeassistant.components.zha.core.helpers import ( cluster_command_schema_to_vol_schema, convert_to_zcl_values, + validate_unit, ) -from homeassistant.const import Platform +from homeassistant.const import Platform, UnitOfPower from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv @@ -40,7 +43,7 @@ def light_platform_only(): @pytest.fixture -async def device_light(hass, zigpy_device_mock, zha_device_joined): +async def device_light(hass: HomeAssistant, zigpy_device_mock, zha_device_joined): """Test light.""" zigpy_device = zigpy_device_mock( @@ -211,3 +214,25 @@ async def test_zcl_schema_conversions(hass: HomeAssistant, device_light) -> None # No flags are passed through assert converted_data["update_flags"] == 0 + + +def test_unit_validation() -> None: + """Test unit validation.""" + + assert validate_unit(QuirksUnitOfPower.WATT) == UnitOfPower.WATT + + class FooUnit(enum.Enum): + """Foo unit.""" + + BAR = "bar" + + class UnitOfMass(enum.Enum): + """UnitOfMass.""" + + BAR = "bar" + + with pytest.raises(KeyError): + validate_unit(FooUnit.BAR) + + with pytest.raises(ValueError): + validate_unit(UnitOfMass.BAR) diff --git a/tests/components/zha/test_select.py b/tests/components/zha/test_select.py index e044281d2a1..bb1c5ca270a 100644 --- a/tests/components/zha/test_select.py +++ b/tests/components/zha/test_select.py @@ -435,7 +435,7 @@ async def test_on_off_select_attribute_report( "motion_sensitivity_disabled", AqaraMotionSensitivities, MotionSensitivityQuirk.OppleCluster.cluster_id, - translation_key="motion_sensitivity_translation_key", + translation_key="motion_sensitivity", initially_disabled=True, ) ) @@ -491,9 +491,8 @@ async def test_on_off_select_attribute_report_v2( assert hass.states.get(entity_id).state == AqaraMotionSensitivities.Low.name entity_registry = er.async_get(hass) - # none in id because the translation key does not exist - entity_entry = entity_registry.async_get("select.fake_manufacturer_fake_model_none") + entity_entry = entity_registry.async_get(entity_id) assert entity_entry assert entity_entry.entity_category == EntityCategory.CONFIG - assert entity_entry.disabled is True - assert entity_entry.translation_key == "motion_sensitivity_translation_key" + assert entity_entry.disabled is False + assert entity_entry.translation_key == "motion_sensitivity" diff --git a/tests/components/zha/test_sensor.py b/tests/components/zha/test_sensor.py index a6c4cfbf4ec..8d0ef8107e3 100644 --- a/tests/components/zha/test_sensor.py +++ b/tests/components/zha/test_sensor.py @@ -1260,10 +1260,10 @@ async def test_last_feeding_size_sensor_v2( assert entity_id is not None await send_attributes_report(hass, cluster, {0x010C: 1}) - assert_state(hass, entity_id, "1.0", UnitOfMass.GRAMS) + assert_state(hass, entity_id, "1.0", UnitOfMass.GRAMS.value) await send_attributes_report(hass, cluster, {0x010C: 5}) - assert_state(hass, entity_id, "5.0", UnitOfMass.GRAMS) + assert_state(hass, entity_id, "5.0", UnitOfMass.GRAMS.value) @pytest.fixture From 544215a609760800e7ae3191ddb4b2ad9d9b8abb Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 17:56:22 +0100 Subject: [PATCH 1583/1691] Handle TypeError in Spotify (#114317) --- homeassistant/components/spotify/util.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/spotify/util.py b/homeassistant/components/spotify/util.py index c1baec0b498..98bce980e5b 100644 --- a/homeassistant/components/spotify/util.py +++ b/homeassistant/components/spotify/util.py @@ -21,10 +21,10 @@ def resolve_spotify_media_type(media_content_type: str) -> str: def fetch_image_url(item: dict[str, Any], key="images") -> str | None: """Fetch image url.""" - try: - return item.get(key, [])[0].get("url") - except IndexError: - return None + source = item.get(key, []) + if isinstance(source, list) and source: + return source[0].get("url") + return None def spotify_uri_from_media_browser_url(media_content_id: str) -> str: From 6f1b4fad01fbbbec85a82ca16c68c6d9f6d10a5c Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:00:18 +0100 Subject: [PATCH 1584/1691] Fix icons for Motionblinds BLE integration (#114321) Remove icon.json and add to icons.json --- .../components/motionblinds_ble/icon.json | 15 --------------- .../components/motionblinds_ble/icons.json | 11 +++++++++++ 2 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 homeassistant/components/motionblinds_ble/icon.json diff --git a/homeassistant/components/motionblinds_ble/icon.json b/homeassistant/components/motionblinds_ble/icon.json deleted file mode 100644 index 109606ab474..00000000000 --- a/homeassistant/components/motionblinds_ble/icon.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "entity": { - "button": { - "connect": { - "default": "mdi:bluetooth" - }, - "disconnect": { - "default": "mdi:bluetooth-off" - }, - "favorite": { - "default": "mdi:star" - } - } - } -} diff --git a/homeassistant/components/motionblinds_ble/icons.json b/homeassistant/components/motionblinds_ble/icons.json index 2ea620d3947..c8d2b085d75 100644 --- a/homeassistant/components/motionblinds_ble/icons.json +++ b/homeassistant/components/motionblinds_ble/icons.json @@ -1,5 +1,16 @@ { "entity": { + "button": { + "connect": { + "default": "mdi:bluetooth" + }, + "disconnect": { + "default": "mdi:bluetooth-off" + }, + "favorite": { + "default": "mdi:star" + } + }, "select": { "speed": { "default": "mdi:run-fast" From e626cd12aa3fab3d02478773f738da8a9b59e335 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 27 Mar 2024 18:01:07 +0100 Subject: [PATCH 1585/1691] Fix feedback in ROVA (#114308) --- homeassistant/components/rova/sensor.py | 4 ++-- homeassistant/components/rova/strings.json | 2 +- tests/components/rova/test_config_flow.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/rova/sensor.py b/homeassistant/components/rova/sensor.py index 0fae976748a..e510bcf0caf 100644 --- a/homeassistant/components/rova/sensor.py +++ b/homeassistant/components/rova/sensor.py @@ -94,12 +94,12 @@ async def async_setup_platform( async_create_issue( hass, DOMAIN, - f"deprecated_yaml_import_issue_${result['reason']}", + f"deprecated_yaml_import_issue_{result['reason']}", breaks_in_ha_version="2024.10.0", is_fixable=False, issue_domain=DOMAIN, severity=IssueSeverity.WARNING, - translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", translation_placeholders=ISSUE_PLACEHOLDER, ) diff --git a/homeassistant/components/rova/strings.json b/homeassistant/components/rova/strings.json index 9a63bf27aec..709e5450411 100644 --- a/homeassistant/components/rova/strings.json +++ b/homeassistant/components/rova/strings.json @@ -23,7 +23,7 @@ "issues": { "deprecated_yaml_import_issue_cannot_connect": { "title": "The Rova YAML configuration import failed", - "description": "Configuring Rova using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Rova works and restart Home Assistant to try again or remove the Rova YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." + "description": "Configuring Rova using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to Rova works and restart Home Assistant to try again or remove the Rova YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." }, "deprecated_yaml_import_issue_invalid_rova_area": { "title": "The Rova YAML configuration import failed", diff --git a/tests/components/rova/test_config_flow.py b/tests/components/rova/test_config_flow.py index b78e798c120..357cd9eb344 100644 --- a/tests/components/rova/test_config_flow.py +++ b/tests/components/rova/test_config_flow.py @@ -49,10 +49,10 @@ async def test_user(hass: HomeAssistant, mock_rova: MagicMock) -> None: assert data[CONF_HOUSE_NUMBER_SUFFIX] == HOUSE_NUMBER_SUFFIX -async def test_abort_if_not_rova_area( +async def test_error_if_not_rova_area( hass: HomeAssistant, mock_rova: MagicMock ) -> None: - """Test we abort if rova does not collect at the given address.""" + """Test we raise errors if rova does not collect at the given address.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} ) From 63e28f958d12daedb3b7afd4efd0d9171b9944e6 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:05:01 +0100 Subject: [PATCH 1586/1691] Use SignalType to improve typing [homekit] (#114297) --- homeassistant/components/homekit/__init__.py | 3 ++- homeassistant/components/homekit/accessories.py | 3 ++- homeassistant/components/homekit/const.py | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index b00631c2249..294dc7f33a6 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -121,6 +121,7 @@ from .const import ( SERVICE_HOMEKIT_RESET_ACCESSORY, SERVICE_HOMEKIT_UNPAIR, SHUTDOWN_TIMEOUT, + SIGNAL_RELOAD_ENTITIES, ) from .iidmanager import AccessoryIIDStorage from .models import HomeKitEntryData @@ -846,7 +847,7 @@ class HomeKit: self.status = STATUS_WAIT self._cancel_reload_dispatcher = async_dispatcher_connect( self.hass, - f"homekit_reload_entities_{self._entry_id}", + SIGNAL_RELOAD_ENTITIES.format(self._entry_id), self.async_reload_accessories, ) async_zc_instance = await zeroconf.async_get_async_instance(self.hass) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 63575189946..39fa62e3445 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -84,6 +84,7 @@ from .const import ( MAX_VERSION_LENGTH, SERV_ACCESSORY_INFO, SERV_BATTERY_SERVICE, + SIGNAL_RELOAD_ENTITIES, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, @@ -621,7 +622,7 @@ class HomeAccessory(Accessory): # type: ignore[misc] """Reload and recreate an accessory and update the c# value in the mDNS record.""" async_dispatcher_send( self.hass, - f"homekit_reload_entities_{self.driver.entry_id}", + SIGNAL_RELOAD_ENTITIES.format(self.driver.entry_id), (self.entity_id,), ) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 149d2aedd67..9f44e2ab616 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -1,6 +1,9 @@ """Constants used be the HomeKit component.""" +from __future__ import annotations + from homeassistant.const import CONF_DEVICES +from homeassistant.util.signal_type import SignalTypeFormat # #### Misc #### DEBOUNCE_TIMEOUT = 0.5 @@ -11,6 +14,9 @@ HOMEKIT_FILE = ".homekit.state" SHUTDOWN_TIMEOUT = 30 CONF_ENTRY_INDEX = "index" EMPTY_MAC = "00:00:00:00:00:00" +SIGNAL_RELOAD_ENTITIES: SignalTypeFormat[tuple[str, ...]] = SignalTypeFormat( + "homekit_reload_entities_{}" +) # ### Codecs #### VIDEO_CODEC_COPY = "copy" From fc34453caa36ddd54eaf494f66a65fd16fd8f43b Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:20:48 -0400 Subject: [PATCH 1587/1691] Support the Home Assistant Connect ZBT-1 (#114213) --- .../homeassistant_sky_connect/__init__.py | 5 +- .../homeassistant_sky_connect/config_flow.py | 19 +- .../homeassistant_sky_connect/const.py | 38 ++++ .../homeassistant_sky_connect/hardware.py | 4 +- .../homeassistant_sky_connect/manifest.json | 6 + .../homeassistant_sky_connect/util.py | 7 + homeassistant/components/otbr/config_flow.py | 3 + homeassistant/generated/usb.py | 6 + .../test_config_flow.py | 171 ++++++++++-------- .../homeassistant_sky_connect/test_const.py | 27 +++ .../test_hardware.py | 48 ++--- tests/components/otbr/test_config_flow.py | 32 ++-- tests/components/zha/test_repairs.py | 17 ++ 13 files changed, 268 insertions(+), 115 deletions(-) create mode 100644 tests/components/homeassistant_sky_connect/test_const.py diff --git a/homeassistant/components/homeassistant_sky_connect/__init__.py b/homeassistant/components/homeassistant_sky_connect/__init__.py index 763c421350f..1ee4710769b 100644 --- a/homeassistant/components/homeassistant_sky_connect/__init__.py +++ b/homeassistant/components/homeassistant_sky_connect/__init__.py @@ -14,7 +14,7 @@ from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import discovery_flow from .const import DOMAIN -from .util import get_usb_service_info +from .util import get_hardware_variant, get_usb_service_info async def _async_usb_scan_done(hass: HomeAssistant, entry: ConfigEntry) -> None: @@ -46,8 +46,9 @@ async def _async_usb_scan_done(hass: HomeAssistant, entry: ConfigEntry) -> None: ) return + hw_variant = get_hardware_variant(entry) hw_discovery_data = { - "name": "SkyConnect Multiprotocol", + "name": f"{hw_variant.short_name} Multiprotocol", "port": { "path": get_zigbee_socket(), }, diff --git a/homeassistant/components/homeassistant_sky_connect/config_flow.py b/homeassistant/components/homeassistant_sky_connect/config_flow.py index 4e4cbfe0a03..3a3d32c2888 100644 --- a/homeassistant/components/homeassistant_sky_connect/config_flow.py +++ b/homeassistant/components/homeassistant_sky_connect/config_flow.py @@ -9,8 +9,8 @@ from homeassistant.components.homeassistant_hardware import silabs_multiprotocol from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.core import callback -from .const import DOMAIN -from .util import get_usb_service_info +from .const import DOMAIN, HardwareVariant +from .util import get_hardware_variant, get_usb_service_info class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN): @@ -39,8 +39,12 @@ class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN): unique_id = f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}" if await self.async_set_unique_id(unique_id): self._abort_if_unique_id_configured(updates={"device": device}) + + assert description is not None + hw_variant = HardwareVariant.from_usb_product_name(description) + return self.async_create_entry( - title="Home Assistant SkyConnect", + title=hw_variant.full_name, data={ "device": device, "vid": vid, @@ -76,10 +80,15 @@ class HomeAssistantSkyConnectOptionsFlow(silabs_multiprotocol_addon.OptionsFlowH """ return {"usb": get_usb_service_info(self.config_entry)} + @property + def _hw_variant(self) -> HardwareVariant: + """Return the hardware variant.""" + return get_hardware_variant(self.config_entry) + def _zha_name(self) -> str: """Return the ZHA name.""" - return "SkyConnect Multiprotocol" + return f"{self._hw_variant.short_name} Multiprotocol" def _hardware_name(self) -> str: """Return the name of the hardware.""" - return "Home Assistant SkyConnect" + return self._hw_variant.full_name diff --git a/homeassistant/components/homeassistant_sky_connect/const.py b/homeassistant/components/homeassistant_sky_connect/const.py index c504cead9cb..1dd1471c470 100644 --- a/homeassistant/components/homeassistant_sky_connect/const.py +++ b/homeassistant/components/homeassistant_sky_connect/const.py @@ -1,3 +1,41 @@ """Constants for the Home Assistant SkyConnect integration.""" +import dataclasses +import enum +from typing import Self + DOMAIN = "homeassistant_sky_connect" + + +@dataclasses.dataclass(frozen=True) +class VariantInfo: + """Hardware variant information.""" + + usb_product_name: str + short_name: str + full_name: str + + +class HardwareVariant(VariantInfo, enum.Enum): + """Hardware variants.""" + + SKYCONNECT = ( + "SkyConnect v1.0", + "SkyConnect", + "Home Assistant SkyConnect", + ) + + CONNECT_ZBT1 = ( + "Home Assistant Connect ZBT-1", + "Connect ZBT-1", + "Home Assistant Connect ZBT-1", + ) + + @classmethod + def from_usb_product_name(cls, usb_product_name: str) -> Self: + """Get the hardware variant from the USB product name.""" + for variant in cls: + if variant.value.usb_product_name == usb_product_name: + return variant + + raise ValueError(f"Unknown SkyConnect product name: {usb_product_name}") diff --git a/homeassistant/components/homeassistant_sky_connect/hardware.py b/homeassistant/components/homeassistant_sky_connect/hardware.py index 1c32bce531b..a9abeb27737 100644 --- a/homeassistant/components/homeassistant_sky_connect/hardware.py +++ b/homeassistant/components/homeassistant_sky_connect/hardware.py @@ -6,9 +6,9 @@ from homeassistant.components.hardware.models import HardwareInfo, USBInfo from homeassistant.core import HomeAssistant, callback from .const import DOMAIN +from .util import get_hardware_variant DOCUMENTATION_URL = "https://skyconnect.home-assistant.io/documentation/" -DONGLE_NAME = "Home Assistant SkyConnect" @callback @@ -27,7 +27,7 @@ def async_info(hass: HomeAssistant) -> list[HardwareInfo]: manufacturer=entry.data["manufacturer"], description=entry.data["description"], ), - name=DONGLE_NAME, + name=get_hardware_variant(entry).full_name, url=DOCUMENTATION_URL, ) for entry in entries diff --git a/homeassistant/components/homeassistant_sky_connect/manifest.json b/homeassistant/components/homeassistant_sky_connect/manifest.json index 31685183a61..f56fd24de61 100644 --- a/homeassistant/components/homeassistant_sky_connect/manifest.json +++ b/homeassistant/components/homeassistant_sky_connect/manifest.json @@ -12,6 +12,12 @@ "pid": "EA60", "description": "*skyconnect v1.0*", "known_devices": ["SkyConnect v1.0"] + }, + { + "vid": "10C4", + "pid": "EA60", + "description": "*home assistant connect zbt-1*", + "known_devices": ["Home Assistant Connect ZBT-1"] } ] } diff --git a/homeassistant/components/homeassistant_sky_connect/util.py b/homeassistant/components/homeassistant_sky_connect/util.py index 84aa858c847..e1de1d3b442 100644 --- a/homeassistant/components/homeassistant_sky_connect/util.py +++ b/homeassistant/components/homeassistant_sky_connect/util.py @@ -5,6 +5,8 @@ from __future__ import annotations from homeassistant.components import usb from homeassistant.config_entries import ConfigEntry +from .const import HardwareVariant + def get_usb_service_info(config_entry: ConfigEntry) -> usb.UsbServiceInfo: """Return UsbServiceInfo.""" @@ -16,3 +18,8 @@ def get_usb_service_info(config_entry: ConfigEntry) -> usb.UsbServiceInfo: manufacturer=config_entry.data["manufacturer"], description=config_entry.data["description"], ) + + +def get_hardware_variant(config_entry: ConfigEntry) -> HardwareVariant: + """Get the hardware variant from the config entry.""" + return HardwareVariant.from_usb_product_name(config_entry.data["description"]) diff --git a/homeassistant/components/otbr/config_flow.py b/homeassistant/components/otbr/config_flow.py index 9a70970bdb6..8342a965bd3 100644 --- a/homeassistant/components/otbr/config_flow.py +++ b/homeassistant/components/otbr/config_flow.py @@ -59,6 +59,9 @@ async def _title(hass: HomeAssistant, discovery_info: HassioServiceInfo) -> str: if device and "SkyConnect" in device: return f"Home Assistant SkyConnect ({discovery_info.name})" + if device and "Connect_ZBT-1" in device: + return f"Home Assistant Connect ZBT-1 ({discovery_info.name})" + return discovery_info.name diff --git a/homeassistant/generated/usb.py b/homeassistant/generated/usb.py index faf8abb775c..e66a5861d18 100644 --- a/homeassistant/generated/usb.py +++ b/homeassistant/generated/usb.py @@ -10,6 +10,12 @@ USB = [ "pid": "EA60", "vid": "10C4", }, + { + "description": "*home assistant connect zbt-1*", + "domain": "homeassistant_sky_connect", + "pid": "EA60", + "vid": "10C4", + }, { "domain": "insteon", "vid": "10BF", diff --git a/tests/components/homeassistant_sky_connect/test_config_flow.py b/tests/components/homeassistant_sky_connect/test_config_flow.py index 9374cbc9457..957a407cc0e 100644 --- a/tests/components/homeassistant_sky_connect/test_config_flow.py +++ b/tests/components/homeassistant_sky_connect/test_config_flow.py @@ -19,13 +19,22 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, MockModule, mock_integration -USB_DATA = usb.UsbServiceInfo( - device="bla_device", - vid="bla_vid", - pid="bla_pid", - serial_number="bla_serial_number", - manufacturer="bla_manufacturer", - description="bla_description", +USB_DATA_SKY = usb.UsbServiceInfo( + device="/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0", + vid="10C4", + pid="EA60", + serial_number="9e2adbd75b8beb119fe564a0f320645d", + manufacturer="Nabu Casa", + description="SkyConnect v1.0", +) + +USB_DATA_ZBT1 = usb.UsbServiceInfo( + device="/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0", + vid="10C4", + pid="EA60", + serial_number="9e2adbd75b8beb119fe564a0f320645d", + manufacturer="Nabu Casa", + description="Home Assistant Connect ZBT-1", ) @@ -38,27 +47,36 @@ def config_flow_handler(hass: HomeAssistant) -> Generator[None, None, None]: yield -async def test_config_flow(hass: HomeAssistant) -> None: - """Test the config flow.""" +@pytest.mark.parametrize( + ("usb_data", "title"), + [ + (USB_DATA_SKY, "Home Assistant SkyConnect"), + (USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"), + ], +) +async def test_config_flow( + usb_data: usb.UsbServiceInfo, title: str, hass: HomeAssistant +) -> None: + """Test the config flow for SkyConnect.""" with patch( "homeassistant.components.homeassistant_sky_connect.async_setup_entry", return_value=True, ) as mock_setup_entry: result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "usb"}, data=USB_DATA + DOMAIN, context={"source": "usb"}, data=usb_data ) expected_data = { - "device": USB_DATA.device, - "vid": USB_DATA.vid, - "pid": USB_DATA.pid, - "serial_number": USB_DATA.serial_number, - "manufacturer": USB_DATA.manufacturer, - "description": USB_DATA.description, + "device": usb_data.device, + "vid": usb_data.vid, + "pid": usb_data.pid, + "serial_number": usb_data.serial_number, + "manufacturer": usb_data.manufacturer, + "description": usb_data.description, } assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Home Assistant SkyConnect" + assert result["title"] == title assert result["data"] == expected_data assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 @@ -66,51 +84,35 @@ async def test_config_flow(hass: HomeAssistant) -> None: config_entry = hass.config_entries.async_entries(DOMAIN)[0] assert config_entry.data == expected_data assert config_entry.options == {} - assert config_entry.title == "Home Assistant SkyConnect" + assert config_entry.title == title assert ( config_entry.unique_id - == f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}" + == f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}" ) -async def test_config_flow_unique_id(hass: HomeAssistant) -> None: - """Test only a single entry is allowed for a dongle.""" - # Setup an existing config entry - config_entry = MockConfigEntry( - data={}, - domain=DOMAIN, - options={}, - title="Home Assistant SkyConnect", - unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}", - ) - config_entry.add_to_hass(hass) - - with patch( - "homeassistant.components.homeassistant_sky_connect.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "usb"}, data=USB_DATA - ) - - assert result["type"] == FlowResultType.ABORT - assert result["reason"] == "already_configured" - mock_setup_entry.assert_not_called() - - -async def test_config_flow_multiple_entries(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + ("usb_data", "title"), + [ + (USB_DATA_SKY, "Home Assistant SkyConnect"), + (USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"), + ], +) +async def test_config_flow_multiple_entries( + usb_data: usb.UsbServiceInfo, title: str, hass: HomeAssistant +) -> None: """Test multiple entries are allowed.""" # Setup an existing config entry config_entry = MockConfigEntry( data={}, domain=DOMAIN, options={}, - title="Home Assistant SkyConnect", - unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}", + title=title, + unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}", ) config_entry.add_to_hass(hass) - usb_data = copy.copy(USB_DATA) + usb_data = copy.copy(usb_data) usb_data.serial_number = "bla_serial_number_2" with patch( @@ -124,19 +126,28 @@ async def test_config_flow_multiple_entries(hass: HomeAssistant) -> None: assert result["type"] == FlowResultType.CREATE_ENTRY -async def test_config_flow_update_device(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + ("usb_data", "title"), + [ + (USB_DATA_SKY, "Home Assistant SkyConnect"), + (USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"), + ], +) +async def test_config_flow_update_device( + usb_data: usb.UsbServiceInfo, title: str, hass: HomeAssistant +) -> None: """Test updating device path.""" # Setup an existing config entry config_entry = MockConfigEntry( data={}, domain=DOMAIN, options={}, - title="Home Assistant SkyConnect", - unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}", + title=title, + unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}", ) config_entry.add_to_hass(hass) - usb_data = copy.copy(USB_DATA) + usb_data = copy.copy(usb_data) usb_data.device = "bla_device_2" with patch( @@ -167,7 +178,16 @@ async def test_config_flow_update_device(hass: HomeAssistant) -> None: assert len(mock_unload_entry.mock_calls) == 1 +@pytest.mark.parametrize( + ("usb_data", "title"), + [ + (USB_DATA_SKY, "Home Assistant SkyConnect"), + (USB_DATA_ZBT1, "Home Assistant ZBT-1"), + ], +) async def test_option_flow_install_multi_pan_addon( + usb_data: usb.UsbServiceInfo, + title: str, hass: HomeAssistant, addon_store_info, addon_info, @@ -182,17 +202,17 @@ async def test_option_flow_install_multi_pan_addon( # Setup the config entry config_entry = MockConfigEntry( data={ - "device": USB_DATA.device, - "vid": USB_DATA.vid, - "pid": USB_DATA.pid, - "serial_number": USB_DATA.serial_number, - "manufacturer": USB_DATA.manufacturer, - "description": USB_DATA.description, + "device": usb_data.device, + "vid": usb_data.vid, + "pid": usb_data.pid, + "serial_number": usb_data.serial_number, + "manufacturer": usb_data.manufacturer, + "description": usb_data.description, }, domain=DOMAIN, options={}, - title="Home Assistant SkyConnect", - unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}", + title=title, + unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}", ) config_entry.add_to_hass(hass) @@ -226,7 +246,7 @@ async def test_option_flow_install_multi_pan_addon( { "options": { "autoflash_firmware": True, - "device": "bla_device", + "device": usb_data.device, "baudrate": "115200", "flow_control": True, } @@ -254,11 +274,20 @@ def mock_detect_radio_type(radio_type=RadioType.ezsp, ret=True): return detect +@pytest.mark.parametrize( + ("usb_data", "title"), + [ + (USB_DATA_SKY, "Home Assistant SkyConnect"), + (USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"), + ], +) @patch( "homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type", mock_detect_radio_type(), ) async def test_option_flow_install_multi_pan_addon_zha( + usb_data: usb.UsbServiceInfo, + title: str, hass: HomeAssistant, addon_store_info, addon_info, @@ -273,22 +302,22 @@ async def test_option_flow_install_multi_pan_addon_zha( # Setup the config entry config_entry = MockConfigEntry( data={ - "device": USB_DATA.device, - "vid": USB_DATA.vid, - "pid": USB_DATA.pid, - "serial_number": USB_DATA.serial_number, - "manufacturer": USB_DATA.manufacturer, - "description": USB_DATA.description, + "device": usb_data.device, + "vid": usb_data.vid, + "pid": usb_data.pid, + "serial_number": usb_data.serial_number, + "manufacturer": usb_data.manufacturer, + "description": usb_data.description, }, domain=DOMAIN, options={}, - title="Home Assistant SkyConnect", - unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}", + title=title, + unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}", ) config_entry.add_to_hass(hass) zha_config_entry = MockConfigEntry( - data={"device": {"path": "bla_device"}, "radio_type": "ezsp"}, + data={"device": {"path": usb_data.device}, "radio_type": "ezsp"}, domain=ZHA_DOMAIN, options={}, title="Yellow", @@ -325,7 +354,7 @@ async def test_option_flow_install_multi_pan_addon_zha( { "options": { "autoflash_firmware": True, - "device": "bla_device", + "device": usb_data.device, "baudrate": "115200", "flow_control": True, } diff --git a/tests/components/homeassistant_sky_connect/test_const.py b/tests/components/homeassistant_sky_connect/test_const.py new file mode 100644 index 00000000000..24a39270061 --- /dev/null +++ b/tests/components/homeassistant_sky_connect/test_const.py @@ -0,0 +1,27 @@ +"""Test the Home Assistant SkyConnect constants.""" + +import pytest + +from homeassistant.components.homeassistant_sky_connect.const import HardwareVariant + + +@pytest.mark.parametrize( + ("usb_product_name", "expected_variant"), + [ + ("SkyConnect v1.0", HardwareVariant.SKYCONNECT), + ("Home Assistant Connect ZBT-1", HardwareVariant.CONNECT_ZBT1), + ], +) +def test_hardware_variant( + usb_product_name: str, expected_variant: HardwareVariant +) -> None: + """Test hardware variant parsing.""" + assert HardwareVariant.from_usb_product_name(usb_product_name) == expected_variant + + +def test_hardware_variant_invalid(): + """Test hardware variant parsing with an invalid product.""" + with pytest.raises( + ValueError, match=r"^Unknown SkyConnect product name: Some other product$" + ): + HardwareVariant.from_usb_product_name("Some other product") diff --git a/tests/components/homeassistant_sky_connect/test_hardware.py b/tests/components/homeassistant_sky_connect/test_hardware.py index 079b03bbb92..6b283378045 100644 --- a/tests/components/homeassistant_sky_connect/test_hardware.py +++ b/tests/components/homeassistant_sky_connect/test_hardware.py @@ -10,21 +10,21 @@ from tests.common import MockConfigEntry from tests.typing import WebSocketGenerator CONFIG_ENTRY_DATA = { - "device": "bla_device", - "vid": "bla_vid", - "pid": "bla_pid", - "serial_number": "bla_serial_number", - "manufacturer": "bla_manufacturer", - "description": "bla_description", + "device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0", + "vid": "10C4", + "pid": "EA60", + "serial_number": "9e2adbd75b8beb119fe564a0f320645d", + "manufacturer": "Nabu Casa", + "description": "SkyConnect v1.0", } CONFIG_ENTRY_DATA_2 = { - "device": "bla_device_2", - "vid": "bla_vid_2", - "pid": "bla_pid_2", - "serial_number": "bla_serial_number_2", - "manufacturer": "bla_manufacturer_2", - "description": "bla_description_2", + "device": "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0", + "vid": "10C4", + "pid": "EA60", + "serial_number": "9e2adbd75b8beb119fe564a0f320645d", + "manufacturer": "Nabu Casa", + "description": "Home Assistant Connect ZBT-1", } @@ -48,7 +48,7 @@ async def test_hardware_info( data=CONFIG_ENTRY_DATA_2, domain=DOMAIN, options={}, - title="Home Assistant SkyConnect", + title="Home Assistant Connect ZBT-1", unique_id="unique_2", ) config_entry_2.add_to_hass(hass) @@ -72,11 +72,11 @@ async def test_hardware_info( "board": None, "config_entries": [config_entry.entry_id], "dongle": { - "vid": "bla_vid", - "pid": "bla_pid", - "serial_number": "bla_serial_number", - "manufacturer": "bla_manufacturer", - "description": "bla_description", + "vid": "10C4", + "pid": "EA60", + "serial_number": "9e2adbd75b8beb119fe564a0f320645d", + "manufacturer": "Nabu Casa", + "description": "SkyConnect v1.0", }, "name": "Home Assistant SkyConnect", "url": "https://skyconnect.home-assistant.io/documentation/", @@ -85,13 +85,13 @@ async def test_hardware_info( "board": None, "config_entries": [config_entry_2.entry_id], "dongle": { - "vid": "bla_vid_2", - "pid": "bla_pid_2", - "serial_number": "bla_serial_number_2", - "manufacturer": "bla_manufacturer_2", - "description": "bla_description_2", + "vid": "10C4", + "pid": "EA60", + "serial_number": "9e2adbd75b8beb119fe564a0f320645d", + "manufacturer": "Nabu Casa", + "description": "Home Assistant Connect ZBT-1", }, - "name": "Home Assistant SkyConnect", + "name": "Home Assistant Connect ZBT-1", "url": "https://skyconnect.home-assistant.io/documentation/", }, ] diff --git a/tests/components/otbr/test_config_flow.py b/tests/components/otbr/test_config_flow.py index e9ad5681549..81dcb894be6 100644 --- a/tests/components/otbr/test_config_flow.py +++ b/tests/components/otbr/test_config_flow.py @@ -280,8 +280,25 @@ async def test_hassio_discovery_flow_yellow( assert config_entry.unique_id == HASSIO_DATA.uuid +@pytest.mark.parametrize( + ("device", "title"), + [ + ( + "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0", + "Home Assistant SkyConnect (Silicon Labs Multiprotocol)", + ), + ( + "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0", + "Home Assistant Connect ZBT-1 (Silicon Labs Multiprotocol)", + ), + ], +) async def test_hassio_discovery_flow_sky_connect( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info + device: str, + title: str, + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + addon_info, ) -> None: """Test the hassio discovery flow.""" url = "http://core-silabs-multiprotocol:8081" @@ -290,12 +307,7 @@ async def test_hassio_discovery_flow_sky_connect( addon_info.return_value = { "available": True, "hostname": None, - "options": { - "device": ( - "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_" - "9e2adbd75b8beb119fe564a0f320645d-if00-port0" - ) - }, + "options": {"device": device}, "state": None, "update_available": False, "version": None, @@ -314,7 +326,7 @@ async def test_hassio_discovery_flow_sky_connect( } assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Home Assistant SkyConnect (Silicon Labs Multiprotocol)" + assert result["title"] == title assert result["data"] == expected_data assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 @@ -322,9 +334,7 @@ async def test_hassio_discovery_flow_sky_connect( config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0] assert config_entry.data == expected_data assert config_entry.options == {} - assert ( - config_entry.title == "Home Assistant SkyConnect (Silicon Labs Multiprotocol)" - ) + assert config_entry.title == title assert config_entry.unique_id == HASSIO_DATA.uuid diff --git a/tests/components/zha/test_repairs.py b/tests/components/zha/test_repairs.py index c164abf9b2d..fea68be86cb 100644 --- a/tests/components/zha/test_repairs.py +++ b/tests/components/zha/test_repairs.py @@ -38,6 +38,7 @@ from tests.common import MockConfigEntry from tests.typing import ClientSessionGenerator SKYCONNECT_DEVICE = "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0" +CONNECT_ZBT1_DEVICE = "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0" def set_flasher_app_type(app_type: ApplicationType) -> Callable[[Flasher], None]: @@ -66,6 +67,22 @@ def test_detect_radio_hardware(hass: HomeAssistant) -> None: ) skyconnect_config_entry.add_to_hass(hass) + connect_zbt1_config_entry = MockConfigEntry( + data={ + "device": CONNECT_ZBT1_DEVICE, + "vid": "10C4", + "pid": "EA60", + "serial_number": "3c0ed67c628beb11b1cd64a0f320645d", + "manufacturer": "Nabu Casa", + "description": "Home Assistant Connect ZBT-1", + }, + domain=SKYCONNECT_DOMAIN, + options={}, + title="Home Assistant Connect ZBT-1", + ) + connect_zbt1_config_entry.add_to_hass(hass) + + assert _detect_radio_hardware(hass, CONNECT_ZBT1_DEVICE) == HardwareType.SKYCONNECT assert _detect_radio_hardware(hass, SKYCONNECT_DEVICE) == HardwareType.SKYCONNECT assert ( _detect_radio_hardware(hass, SKYCONNECT_DEVICE + "_foo") == HardwareType.OTHER From 6af0ccfa8d6fe2e1bc96f16b99de8eb320f9e538 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 27 Mar 2024 18:21:45 +0100 Subject: [PATCH 1588/1691] Update frontend to 20240327.0 (#114322) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index f49a632cae2..10917bb7f70 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240307.0"] + "requirements": ["home-assistant-frontend==20240327.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1f89aed7306..9af8c2f3e0a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240307.0 +home-assistant-frontend==20240327.0 home-assistant-intents==2024.3.27 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index a22b8169363..1aedcb73671 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240307.0 +home-assistant-frontend==20240327.0 # homeassistant.components.conversation home-assistant-intents==2024.3.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de5f355f742..9795ef99481 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240307.0 +home-assistant-frontend==20240327.0 # homeassistant.components.conversation home-assistant-intents==2024.3.27 From b2e7808114eecb40c72556ddea222216741d9143 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 27 Mar 2024 18:27:38 +0100 Subject: [PATCH 1589/1691] Bump version to 2024.4.0b0 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ee15cfd72c3..d458a66b865 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0.dev0" +PATCH_VERSION: Final = "0b0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 965827f41ea..c84405c2764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0.dev0" +version = "2024.4.0b0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 9319528e0e31ebf6cde2b4db8f73048e2a7955e0 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 28 Mar 2024 15:44:50 +0100 Subject: [PATCH 1590/1691] Use fallback voice for selected language in cloud (#114246) Co-authored-by: Erik Montnemery --- homeassistant/components/cloud/tts.py | 24 +++++- tests/components/cloud/test_tts.py | 109 ++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/cloud/tts.py b/homeassistant/components/cloud/tts.py index 7922fc80201..42e4b94a189 100644 --- a/homeassistant/components/cloud/tts.py +++ b/homeassistant/components/cloud/tts.py @@ -140,7 +140,6 @@ class CloudTTSEntity(TextToSpeechEntity): """Return a dict include default options.""" return { ATTR_AUDIO_OUTPUT: AudioOutput.MP3, - ATTR_VOICE: self._voice, } @property @@ -178,7 +177,18 @@ class CloudTTSEntity(TextToSpeechEntity): gender: Gender | str | None = options.get(ATTR_GENDER) gender = handle_deprecated_gender(self.hass, gender) original_voice: str | None = options.get(ATTR_VOICE) + if original_voice is None and language == self._language: + original_voice = self._voice voice = handle_deprecated_voice(self.hass, original_voice) + if voice not in TTS_VOICES[language]: + default_voice = TTS_VOICES[language][0] + _LOGGER.debug( + "Unsupported voice %s detected, falling back to default %s for %s", + voice, + default_voice, + language, + ) + voice = default_voice # Process TTS try: data = await self.cloud.voice.process_tts( @@ -237,7 +247,6 @@ class CloudProvider(Provider): """Return a dict include default options.""" return { ATTR_AUDIO_OUTPUT: AudioOutput.MP3, - ATTR_VOICE: self._voice, } async def async_get_tts_audio( @@ -248,7 +257,18 @@ class CloudProvider(Provider): gender: Gender | str | None = options.get(ATTR_GENDER) gender = handle_deprecated_gender(self.hass, gender) original_voice: str | None = options.get(ATTR_VOICE) + if original_voice is None and language == self._language: + original_voice = self._voice voice = handle_deprecated_voice(self.hass, original_voice) + if voice not in TTS_VOICES[language]: + default_voice = TTS_VOICES[language][0] + _LOGGER.debug( + "Unsupported voice %s detected, falling back to default %s for %s", + voice, + default_voice, + language, + ) + voice = default_voice # Process TTS try: data = await self.cloud.voice.process_tts( diff --git a/tests/components/cloud/test_tts.py b/tests/components/cloud/test_tts.py index 3fd9ec5e4a4..06dbcf174a7 100644 --- a/tests/components/cloud/test_tts.py +++ b/tests/components/cloud/test_tts.py @@ -12,10 +12,20 @@ import voluptuous as vol from homeassistant.components.assist_pipeline.pipeline import STORAGE_KEY from homeassistant.components.cloud import DOMAIN, const, tts -from homeassistant.components.tts import DOMAIN as TTS_DOMAIN +from homeassistant.components.media_player import ( + ATTR_MEDIA_CONTENT_ID, + DOMAIN as DOMAIN_MP, + SERVICE_PLAY_MEDIA, +) +from homeassistant.components.tts import ( + ATTR_LANGUAGE, + ATTR_MEDIA_PLAYER_ENTITY_ID, + ATTR_MESSAGE, + DOMAIN as TTS_DOMAIN, +) from homeassistant.components.tts.helper import get_engine_instance from homeassistant.config import async_process_ha_core_config -from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_registry import EntityRegistry from homeassistant.helpers.issue_registry import IssueRegistry, IssueSeverity @@ -23,6 +33,8 @@ from homeassistant.setup import async_setup_component from . import PIPELINE_DATA +from tests.common import async_mock_service +from tests.components.tts.common import get_media_source_url from tests.typing import ClientSessionGenerator @@ -120,13 +132,13 @@ async def test_prefs_default_voice( assert engine is not None # The platform config provider will be overridden by the discovery info provider. assert engine.default_language == "en-US" - assert engine.default_options == {"audio_output": "mp3", "voice": "JennyNeural"} + assert engine.default_options == {"audio_output": "mp3"} await set_cloud_prefs({"tts_default_voice": ("nl-NL", "MaartenNeural")}) await hass.async_block_till_done() assert engine.default_language == "nl-NL" - assert engine.default_options == {"audio_output": "mp3", "voice": "MaartenNeural"} + assert engine.default_options == {"audio_output": "mp3"} async def test_deprecated_platform_config( @@ -228,11 +240,11 @@ async def test_get_tts_audio( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" + f"_en-us_6e8b81ac47_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" + f"_en-us_6e8b81ac47_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -242,6 +254,7 @@ async def test_get_tts_audio( assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == "en-US" assert mock_process_tts.call_args.kwargs["gender"] is None + assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" assert mock_process_tts.call_args.kwargs["output"] == "mp3" @@ -280,11 +293,11 @@ async def test_get_tts_audio_logged_out( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" + f"_en-us_6e8b81ac47_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_5c97d21c48_{expected_url_suffix}.mp3" + f"_en-us_6e8b81ac47_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -294,6 +307,7 @@ async def test_get_tts_audio_logged_out( assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == "en-US" assert mock_process_tts.call_args.kwargs["gender"] is None + assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" assert mock_process_tts.call_args.kwargs["output"] == "mp3" @@ -344,11 +358,11 @@ async def test_tts_entity( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_5c97d21c48_{entity_id}.mp3" + f"_en-us_6e8b81ac47_{entity_id}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_en-us_5c97d21c48_{entity_id}.mp3" + f"_en-us_6e8b81ac47_{entity_id}.mp3" ), } await hass.async_block_till_done() @@ -358,6 +372,7 @@ async def test_tts_entity( assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == "en-US" assert mock_process_tts.call_args.kwargs["gender"] is None + assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" assert mock_process_tts.call_args.kwargs["output"] == "mp3" state = hass.states.get(entity_id) @@ -632,11 +647,11 @@ async def test_deprecated_gender( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_5c97d21c48_{expected_url_suffix}.mp3" + f"_{language.lower()}_6e8b81ac47_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_5c97d21c48_{expected_url_suffix}.mp3" + f"_{language.lower()}_6e8b81ac47_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -645,7 +660,7 @@ async def test_deprecated_gender( assert mock_process_tts.call_args is not None assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == language - assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" + assert mock_process_tts.call_args.kwargs["voice"] == "XiaoxiaoNeural" assert mock_process_tts.call_args.kwargs["output"] == "mp3" issue = issue_registry.async_get_issue("cloud", "deprecated_gender") assert issue is None @@ -662,11 +677,11 @@ async def test_deprecated_gender( "url": ( "http://example.local:8123/api/tts_proxy/" "42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_5dded72256_{expected_url_suffix}.mp3" + f"_{language.lower()}_dd0e95eb04_{expected_url_suffix}.mp3" ), "path": ( "/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491" - f"_{language.lower()}_5dded72256_{expected_url_suffix}.mp3" + f"_{language.lower()}_dd0e95eb04_{expected_url_suffix}.mp3" ), } await hass.async_block_till_done() @@ -678,7 +693,7 @@ async def test_deprecated_gender( assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." assert mock_process_tts.call_args.kwargs["language"] == language assert mock_process_tts.call_args.kwargs["gender"] == gender_option - assert mock_process_tts.call_args.kwargs["voice"] == "JennyNeural" + assert mock_process_tts.call_args.kwargs["voice"] == "XiaoxiaoNeural" assert mock_process_tts.call_args.kwargs["output"] == "mp3" issue = issue_registry.async_get_issue("cloud", issue_id) assert issue is not None @@ -733,3 +748,65 @@ async def test_deprecated_gender( } assert not issue_registry.async_get_issue(DOMAIN, issue_id) + + +@pytest.mark.parametrize( + ("service", "service_data"), + [ + ( + "speak", + { + ATTR_ENTITY_ID: "tts.home_assistant_cloud", + ATTR_LANGUAGE: "id-ID", + ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something", + ATTR_MESSAGE: "There is someone at the door.", + }, + ), + ( + "cloud_say", + { + ATTR_ENTITY_ID: "media_player.something", + ATTR_LANGUAGE: "id-ID", + ATTR_MESSAGE: "There is someone at the door.", + }, + ), + ], +) +async def test_tts_services( + hass: HomeAssistant, + cloud: MagicMock, + hass_client: ClientSessionGenerator, + service: str, + service_data: dict[str, Any], +) -> None: + """Test tts services.""" + calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) + mock_process_tts = AsyncMock(return_value=b"") + cloud.voice.process_tts = mock_process_tts + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + await cloud.login("test-user", "test-pass") + client = await hass_client() + + await hass.services.async_call( + domain=TTS_DOMAIN, + service=service, + service_data=service_data, + blocking=True, + ) + + assert len(calls) == 1 + + url = await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID]) + await hass.async_block_till_done() + response = await client.get(url) + assert response.status == HTTPStatus.OK + await hass.async_block_till_done() + + assert mock_process_tts.call_count == 1 + assert mock_process_tts.call_args is not None + assert mock_process_tts.call_args.kwargs["text"] == "There is someone at the door." + assert mock_process_tts.call_args.kwargs["language"] == service_data[ATTR_LANGUAGE] + assert mock_process_tts.call_args.kwargs["voice"] == "GadisNeural" + assert mock_process_tts.call_args.kwargs["output"] == "mp3" From e2710184cb11cf58a461120039550ada1b41ea43 Mon Sep 17 00:00:00 2001 From: TheJulianJES Date: Wed, 27 Mar 2024 18:53:18 +0100 Subject: [PATCH 1591/1691] Bump zha-quirks to 0.0.113 (#114311) --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index e85966e870f..e9d75584064 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -24,7 +24,7 @@ "bellows==0.38.1", "pyserial==3.5", "pyserial-asyncio==0.6", - "zha-quirks==0.0.112", + "zha-quirks==0.0.113", "zigpy-deconz==0.23.1", "zigpy==0.63.5", "zigpy-xbee==0.20.1", diff --git a/requirements_all.txt b/requirements_all.txt index 1aedcb73671..42e92c3de6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2931,7 +2931,7 @@ zeroconf==0.131.0 zeversolar==0.3.1 # homeassistant.components.zha -zha-quirks==0.0.112 +zha-quirks==0.0.113 # homeassistant.components.zhong_hong zhong-hong-hvac==1.0.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9795ef99481..3548eb7fadc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2266,7 +2266,7 @@ zeroconf==0.131.0 zeversolar==0.3.1 # homeassistant.components.zha -zha-quirks==0.0.112 +zha-quirks==0.0.113 # homeassistant.components.zha zigpy-deconz==0.23.1 From a18184a4c08621e111ac154d6f622d4215a2d546 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Thu, 28 Mar 2024 00:43:34 +0100 Subject: [PATCH 1592/1691] Bump pyduotecno to 2024.3.2 (#114320) --- homeassistant/components/duotecno/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/duotecno/manifest.json b/homeassistant/components/duotecno/manifest.json index 7b33784a612..0c8eab8f0a0 100644 --- a/homeassistant/components/duotecno/manifest.json +++ b/homeassistant/components/duotecno/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_push", "loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"], "quality_scale": "silver", - "requirements": ["pyDuotecno==2024.1.2"] + "requirements": ["pyDuotecno==2024.3.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 42e92c3de6f..ace129c69f2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1648,7 +1648,7 @@ pyCEC==0.5.2 pyControl4==1.1.0 # homeassistant.components.duotecno -pyDuotecno==2024.1.2 +pyDuotecno==2024.3.2 # homeassistant.components.electrasmart pyElectra==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3548eb7fadc..b01a7ca8ba4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1298,7 +1298,7 @@ pyCEC==0.5.2 pyControl4==1.1.0 # homeassistant.components.duotecno -pyDuotecno==2024.1.2 +pyDuotecno==2024.3.2 # homeassistant.components.electrasmart pyElectra==1.2.0 From 00993a6be3dd454828c944f5e9db8e03df5209da Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 27 Mar 2024 22:35:08 +0100 Subject: [PATCH 1593/1691] Fix Matter airconditioner discovery of climate platform (#114326) * Discover Thermostat platform for Room Airconditioner device * add test * Adjust docstring Co-authored-by: TheJulianJES --------- Co-authored-by: Martin Hjelmare Co-authored-by: TheJulianJES --- homeassistant/components/matter/climate.py | 2 +- homeassistant/components/matter/switch.py | 1 + .../fixtures/nodes/room-airconditioner.json | 256 ++++++++++++++++++ tests/components/matter/test_climate.py | 25 ++ 4 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 tests/components/matter/fixtures/nodes/room-airconditioner.json diff --git a/homeassistant/components/matter/climate.py b/homeassistant/components/matter/climate.py index 5ae1f7ca486..1b949d3ebfb 100644 --- a/homeassistant/components/matter/climate.py +++ b/homeassistant/components/matter/climate.py @@ -313,6 +313,6 @@ DISCOVERY_SCHEMAS = [ clusters.Thermostat.Attributes.UnoccupiedCoolingSetpoint, clusters.Thermostat.Attributes.UnoccupiedHeatingSetpoint, ), - device_type=(device_types.Thermostat,), + device_type=(device_types.Thermostat, device_types.RoomAirConditioner), ), ] diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index 91a28bdab8c..9bc858d40c0 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -86,6 +86,7 @@ DISCOVERY_SCHEMAS = [ device_types.ColorDimmerSwitch, device_types.DimmerSwitch, device_types.Thermostat, + device_types.RoomAirConditioner, ), ), ] diff --git a/tests/components/matter/fixtures/nodes/room-airconditioner.json b/tests/components/matter/fixtures/nodes/room-airconditioner.json new file mode 100644 index 00000000000..11c29b0d8f4 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/room-airconditioner.json @@ -0,0 +1,256 @@ +{ + "node_id": 36, + "date_commissioned": "2024-03-27T17:31:23.745932", + "last_interview": "2024-03-27T17:31:23.745939", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 1 + } + ], + "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63], + "0/29/2": [], + "0/29/3": [1, 2], + "0/29/65532": 0, + "0/29/65533": 2, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/31/0": [ + { + "254": 5 + }, + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 6 + } + ], + "0/31/1": [], + "0/31/2": 4, + "0/31/3": 3, + "0/31/4": 4, + "0/31/65532": 0, + "0/31/65533": 1, + "0/31/65528": [], + "0/31/65529": [], + "0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/40/0": 17, + "0/40/1": "TEST_VENDOR", + "0/40/2": 65521, + "0/40/3": "Room AirConditioner", + "0/40/4": 32774, + "0/40/5": "", + "0/40/6": "**REDACTED**", + "0/40/7": 0, + "0/40/8": "TEST_VERSION", + "0/40/9": 1, + "0/40/10": "1.0", + "0/40/11": "20200101", + "0/40/12": "", + "0/40/13": "", + "0/40/14": "", + "0/40/15": "TEST_SN", + "0/40/16": false, + "0/40/18": "E47F334E22A56610", + "0/40/19": { + "0": 3, + "1": 3 + }, + "0/40/65532": 0, + "0/40/65533": 1, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 65528, + 65529, 65531, 65532, 65533 + ], + "0/48/0": 0, + "0/48/1": { + "0": 60, + "1": 900 + }, + "0/48/2": 0, + "0/48/3": 0, + "0/48/4": true, + "0/48/65532": 0, + "0/48/65533": 1, + "0/48/65528": [1, 3, 5], + "0/48/65529": [0, 2, 4], + "0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/49/0": 0, + "0/49/1": null, + "0/49/2": 0, + "0/49/3": 0, + "0/49/4": false, + "0/49/5": 0, + "0/49/6": "", + "0/49/7": 0, + "0/49/65532": 2, + "0/49/65533": 1, + "0/49/65528": [1, 5, 7], + "0/49/65529": [0, 2, 3, 4, 6, 8], + "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], + "0/51/0": [], + "0/51/1": 0, + "0/51/8": false, + "0/51/65532": 0, + "0/51/65533": 1, + "0/51/65528": [], + "0/51/65529": [], + "0/51/65531": [0, 1, 8, 65528, 65529, 65531, 65532, 65533], + "0/60/0": 0, + "0/60/1": null, + "0/60/2": null, + "0/60/65532": 0, + "0/60/65533": 1, + "0/60/65528": [], + "0/60/65529": [0, 1, 2], + "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "0/62/0": [ + { + "254": 5 + }, + { + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRJBgkBwEkCAEwCUEE7pKHHHlljFuw2MAQJFOAzVR5tPPIXOjxHrLr7el8KqThQ6CuCFwdmNztUaIQgBcPZm6QRoEn6OGoFoAG8vB0KTcKNQEoARgkAgE2AwQCBAEYMAQUEvPPXEC80Bhik9ZDF3HK0Jo0RG0wBRQ2kjqIaJL5W4CHyhTHPUFcjBrNmxgwC0BJN+cSZw9fkFlIZGzsfS4WYFxzouEZ6LXLjqJXqwhi6uoQqoEhHPITp6sQ8u1ZF7OuQ35q0tZBwt84ZvAo+i59GA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEB0u1A8srBwhdMy9S5+W8C38qv6l9JxhOaVO1E8f3FHDpv6eTSEDWXvUKEOxZcce5cGUF/9tdW2z5M+pwjt2B9jcKNQEpARgkAmAwBBQ2kjqIaJL5W4CHyhTHPUFcjBrNmzAFFJOvH2V2J30vUkl3ZbhqhwBP2wVXGDALQJHZ9heIDcBg2DGc2b18rirq/5aZ2rsyP9BAE1zeTqSYj/pqKyeMS+hCx69jOqh/eAeDpeAzvL7JmKVLB0JLV1sY", + "254": 6 + } + ], + "0/62/1": [ + { + "1": "BER19ZLOakFRLvKKC9VsWzN+xv5V5yHHBFdX7ip/cNhnzVfnaNLLHKGU/DtcNZtU/YH+8kUcWKYvknk1TCcrG4k=", + "2": 24582, + "3": 9865010379846957597, + "4": 3118002441518404838, + "5": "", + "254": 5 + }, + { + "1": "BJUrvCXfXiwdfapIXt1qCtJoem+s2gZJ2KBDQZcPVP1cAYECu6Fjjz2MhMy6OW8ASGmWuke+YavIzIZWYEd6BJU=", + "2": 4939, + "3": 2, + "4": 36, + "5": "", + "254": 6 + } + ], + "0/62/2": 5, + "0/62/3": 2, + "0/62/4": [ + "FTABAQEkAgE3AycU3rGzlMtTrxYYJgQAus0sJgUAwGVSNwYnFN6xs5TLU68WGCQHASQIATAJQQREdfWSzmpBUS7yigvVbFszfsb+VechxwRXV+4qf3DYZ81X52jSyxyhlPw7XDWbVP2B/vJFHFimL5J5NUwnKxuJNwo1ASkBGCQCYDAEFMurIH6818tAIcTnwEZO5c+1WAH8MAUUy6sgfrzXy0AhxOfARk7lz7VYAfwYMAtAM2db17wMsM+JMtR4c2Iaz8nHLI4mVbsPGILOBujrzguB2C7p8Q9x8Cw0NgJP7hDV52F9j7IfHjO37aXZA4LqqBg=", + "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEElSu8Jd9eLB19qkhe3WoK0mh6b6zaBknYoENBlw9U/VwBgQK7oWOPPYyEzLo5bwBIaZa6R75hq8jMhlZgR3oElTcKNQEpARgkAmAwBBSTrx9ldid9L1JJd2W4aocAT9sFVzAFFJOvH2V2J30vUkl3ZbhqhwBP2wVXGDALQPMYkhQcsrqT5v1vgN1LXJr9skDJ6nnuG0QWfs8SVODLGjU73iO1aQVq+Ir5et9RTD/4VrfnI63DW9RA0N+qgCkY" + ], + "0/62/5": 6, + "0/62/65532": 0, + "0/62/65533": 1, + "0/62/65528": [1, 3, 5, 8], + "0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11], + "0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533], + "0/63/0": [], + "0/63/1": [], + "0/63/2": 4, + "0/63/3": 3, + "0/63/65532": 0, + "0/63/65533": 2, + "0/63/65528": [2, 5], + "0/63/65529": [0, 1, 3, 4], + "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/3/0": 0, + "1/3/1": 0, + "1/3/65532": 0, + "1/3/65533": 4, + "1/3/65528": [], + "1/3/65529": [0, 64], + "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "1/6/0": false, + "1/6/65532": 0, + "1/6/65533": 5, + "1/6/65528": [], + "1/6/65529": [0, 1, 2], + "1/6/65531": [0, 65528, 65529, 65531, 65532, 65533], + "1/29/0": [ + { + "0": 114, + "1": 1 + } + ], + "1/29/1": [3, 6, 29, 513, 514], + "1/29/2": [], + "1/29/3": [2], + "1/29/65532": 0, + "1/29/65533": 2, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/513/0": 2000, + "1/513/3": 1600, + "1/513/4": 3200, + "1/513/5": 1600, + "1/513/6": 3200, + "1/513/17": 2600, + "1/513/18": 2000, + "1/513/25": 0, + "1/513/27": 4, + "1/513/28": 1, + "1/513/65532": 35, + "1/513/65533": 6, + "1/513/65528": [], + "1/513/65529": [0], + "1/513/65531": [ + 0, 3, 4, 5, 6, 17, 18, 25, 27, 28, 65528, 65529, 65531, 65532, 65533 + ], + "1/514/0": 0, + "1/514/1": 2, + "1/514/2": 0, + "1/514/3": 0, + "1/514/4": 3, + "1/514/5": 0, + "1/514/6": 0, + "1/514/9": 1, + "1/514/10": 0, + "1/514/65532": 11, + "1/514/65533": 4, + "1/514/65528": [], + "1/514/65529": [], + "1/514/65531": [ + 0, 1, 2, 3, 4, 5, 6, 9, 10, 65528, 65529, 65531, 65532, 65533 + ], + "2/3/0": 0, + "2/3/1": 0, + "2/3/65532": 0, + "2/3/65533": 4, + "2/3/65528": [], + "2/3/65529": [0, 64], + "2/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "2/29/0": [ + { + "0": 770, + "1": 1 + } + ], + "2/29/1": [3, 29, 1026], + "2/29/2": [], + "2/29/3": [], + "2/29/65532": 0, + "2/29/65533": 2, + "2/29/65528": [], + "2/29/65529": [], + "2/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "2/1026/0": 0, + "2/1026/1": -500, + "2/1026/2": 6000, + "2/1026/65532": 0, + "2/1026/65533": 1, + "2/1026/65528": [], + "2/1026/65529": [], + "2/1026/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/test_climate.py b/tests/components/matter/test_climate.py index 80e2d1b72da..de4626ef3d1 100644 --- a/tests/components/matter/test_climate.py +++ b/tests/components/matter/test_climate.py @@ -25,6 +25,16 @@ async def thermostat_fixture( return await setup_integration_with_node_fixture(hass, "thermostat", matter_client) +@pytest.fixture(name="room_airconditioner") +async def room_airconditioner( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a room air conditioner node.""" + return await setup_integration_with_node_fixture( + hass, "room-airconditioner", matter_client + ) + + # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) async def test_thermostat( @@ -387,3 +397,18 @@ async def test_thermostat( clusters.Thermostat.Enums.SetpointAdjustMode.kCool, -40 ), ) + + +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_room_airconditioner( + hass: HomeAssistant, + matter_client: MagicMock, + room_airconditioner: MatterNode, +) -> None: + """Test if a climate entity is created for a Room Airconditioner device.""" + state = hass.states.get("climate.room_airconditioner") + assert state + assert state.attributes["current_temperature"] == 20 + assert state.attributes["min_temp"] == 16 + assert state.attributes["max_temp"] == 32 From f8edab0c12628b2724fd6d540e67efc0cfbc06a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Thu, 28 Mar 2024 13:31:55 +0100 Subject: [PATCH 1594/1691] =?UTF-8?q?Avoid=20changing=20local=20time=20on?= =?UTF-8?q?=20Nob=C3=B8=20Ecohub=20(#114332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nobo_hub: Pass timezone to avoid changing local time on Nobø Ecohub in handshake --- homeassistant/components/nobo_hub/__init__.py | 9 ++++++++- homeassistant/components/nobo_hub/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/nobo_hub/__init__.py b/homeassistant/components/nobo_hub/__init__.py index 15a4b48c315..f9d2ce2e3da 100644 --- a/homeassistant/components/nobo_hub/__init__.py +++ b/homeassistant/components/nobo_hub/__init__.py @@ -7,6 +7,7 @@ from pynobo import nobo from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_IP_ADDRESS, EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant +import homeassistant.util.dt as dt_util from .const import CONF_AUTO_DISCOVERED, CONF_SERIAL, DOMAIN @@ -19,7 +20,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: serial = entry.data[CONF_SERIAL] discover = entry.data[CONF_AUTO_DISCOVERED] ip_address = None if discover else entry.data[CONF_IP_ADDRESS] - hub = nobo(serial=serial, ip=ip_address, discover=discover, synchronous=False) + hub = nobo( + serial=serial, + ip=ip_address, + discover=discover, + synchronous=False, + timezone=dt_util.DEFAULT_TIME_ZONE, + ) await hub.connect() hass.data.setdefault(DOMAIN, {}) diff --git a/homeassistant/components/nobo_hub/manifest.json b/homeassistant/components/nobo_hub/manifest.json index 9ddbed7dadc..4741eb39e29 100644 --- a/homeassistant/components/nobo_hub/manifest.json +++ b/homeassistant/components/nobo_hub/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/nobo_hub", "integration_type": "hub", "iot_class": "local_push", - "requirements": ["pynobo==1.6.0"] + "requirements": ["pynobo==1.8.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index ace129c69f2..a93913d7272 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1991,7 +1991,7 @@ pynetgear==0.10.10 pynetio==0.1.9.1 # homeassistant.components.nobo_hub -pynobo==1.6.0 +pynobo==1.8.0 # homeassistant.components.nuki pynuki==1.6.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b01a7ca8ba4..106b8debcdf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1545,7 +1545,7 @@ pymysensors==0.24.0 pynetgear==0.10.10 # homeassistant.components.nobo_hub -pynobo==1.6.0 +pynobo==1.8.0 # homeassistant.components.nuki pynuki==1.6.3 From a91c03b16472db1d716b922c326fc194310aaa1d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 28 Mar 2024 00:44:01 +0100 Subject: [PATCH 1595/1691] Don't access States.last_reported_ts before it's added (#114333) --- homeassistant/components/recorder/const.py | 1 + homeassistant/components/recorder/core.py | 3 +- .../components/recorder/history/modern.py | 27 +- tests/components/recorder/db_schema_42.py | 838 +++++++++++ .../recorder/test_history_db_schema_42.py | 1278 +++++++++++++++++ 5 files changed, 2138 insertions(+), 9 deletions(-) create mode 100644 tests/components/recorder/db_schema_42.py create mode 100644 tests/components/recorder/test_history_db_schema_42.py diff --git a/homeassistant/components/recorder/const.py b/homeassistant/components/recorder/const.py index 66d46c0c20e..1869bb32239 100644 --- a/homeassistant/components/recorder/const.py +++ b/homeassistant/components/recorder/const.py @@ -53,6 +53,7 @@ STATISTICS_ROWS_SCHEMA_VERSION = 23 CONTEXT_ID_AS_BINARY_SCHEMA_VERSION = 36 EVENT_TYPE_IDS_SCHEMA_VERSION = 37 STATES_META_SCHEMA_VERSION = 38 +LAST_REPORTED_SCHEMA_VERSION = 43 LEGACY_STATES_EVENT_ID_INDEX_SCHEMA_VERSION = 28 diff --git a/homeassistant/components/recorder/core.py b/homeassistant/components/recorder/core.py index 7de9cf46311..0e404ce4da0 100644 --- a/homeassistant/components/recorder/core.py +++ b/homeassistant/components/recorder/core.py @@ -47,6 +47,7 @@ from .const import ( DOMAIN, ESTIMATED_QUEUE_ITEM_SIZE, KEEPALIVE_TIME, + LAST_REPORTED_SCHEMA_VERSION, LEGACY_STATES_EVENT_ID_INDEX_SCHEMA_VERSION, MARIADB_PYMYSQL_URL_PREFIX, MARIADB_URL_PREFIX, @@ -1203,7 +1204,7 @@ class Recorder(threading.Thread): if ( pending_last_reported := self.states_manager.get_pending_last_reported_timestamp() - ): + ) and self.schema_version >= LAST_REPORTED_SCHEMA_VERSION: with session.no_autoflush: session.execute( update(States), diff --git a/homeassistant/components/recorder/history/modern.py b/homeassistant/components/recorder/history/modern.py index a909f799ea9..5fd4f415e02 100644 --- a/homeassistant/components/recorder/history/modern.py +++ b/homeassistant/components/recorder/history/modern.py @@ -27,6 +27,7 @@ from homeassistant.core import HomeAssistant, State, split_entity_id import homeassistant.util.dt as dt_util from ... import recorder +from ..const import LAST_REPORTED_SCHEMA_VERSION from ..db_schema import SHARED_ATTR_OR_LEGACY_ATTRIBUTES, StateAttributes, States from ..filters import Filters from ..models import ( @@ -327,9 +328,10 @@ def _state_changed_during_period_stmt( limit: int | None, include_start_time_state: bool, run_start_ts: float | None, + include_last_reported: bool, ) -> Select | CompoundSelect: stmt = ( - _stmt_and_join_attributes(no_attributes, False, True) + _stmt_and_join_attributes(no_attributes, False, include_last_reported) .filter( ( (States.last_changed_ts == States.last_updated_ts) @@ -361,22 +363,22 @@ def _state_changed_during_period_stmt( single_metadata_id, no_attributes, False, - True, + include_last_reported, ).subquery(), no_attributes, False, - True, + include_last_reported, ), _select_from_subquery( stmt.subquery(), no_attributes, False, - True, + include_last_reported, ), ).subquery(), no_attributes, False, - True, + include_last_reported, ) @@ -391,6 +393,9 @@ def state_changes_during_period( include_start_time_state: bool = True, ) -> MutableMapping[str, list[State]]: """Return states changes during UTC period start_time - end_time.""" + has_last_reported = ( + recorder.get_instance(hass).schema_version >= LAST_REPORTED_SCHEMA_VERSION + ) if not entity_id: raise ValueError("entity_id must be provided") entity_ids = [entity_id.lower()] @@ -423,12 +428,14 @@ def state_changes_during_period( limit, include_start_time_state, run_start_ts, + has_last_reported, ), track_on=[ bool(end_time_ts), no_attributes, bool(limit), include_start_time_state, + has_last_reported, ], ) return cast( @@ -475,10 +482,10 @@ def _get_last_state_changes_single_stmt(metadata_id: int) -> Select: def _get_last_state_changes_multiple_stmt( - number_of_states: int, metadata_id: int + number_of_states: int, metadata_id: int, include_last_reported: bool ) -> Select: return ( - _stmt_and_join_attributes(False, False, True) + _stmt_and_join_attributes(False, False, include_last_reported) .where( States.state_id == ( @@ -500,6 +507,9 @@ def get_last_state_changes( hass: HomeAssistant, number_of_states: int, entity_id: str ) -> MutableMapping[str, list[State]]: """Return the last number_of_states.""" + has_last_reported = ( + recorder.get_instance(hass).schema_version >= LAST_REPORTED_SCHEMA_VERSION + ) entity_id_lower = entity_id.lower() entity_ids = [entity_id_lower] @@ -524,8 +534,9 @@ def get_last_state_changes( else: stmt = lambda_stmt( lambda: _get_last_state_changes_multiple_stmt( - number_of_states, metadata_id + number_of_states, metadata_id, has_last_reported ), + track_on=[has_last_reported], ) states = list(execute_stmt_lambda_element(session, stmt, orm_rows=False)) return cast( diff --git a/tests/components/recorder/db_schema_42.py b/tests/components/recorder/db_schema_42.py new file mode 100644 index 00000000000..b8e49aef592 --- /dev/null +++ b/tests/components/recorder/db_schema_42.py @@ -0,0 +1,838 @@ +"""Models for SQLAlchemy. + +This file contains the model definitions for schema version 42. +It is used to test the schema migration logic. +""" + +from __future__ import annotations + +from collections.abc import Callable +from datetime import datetime, timedelta +import logging +import time +from typing import Any, Self, cast + +import ciso8601 +from fnv_hash_fast import fnv1a_32 +from sqlalchemy import ( + CHAR, + JSON, + BigInteger, + Boolean, + ColumnElement, + DateTime, + Float, + ForeignKey, + Identity, + Index, + Integer, + LargeBinary, + SmallInteger, + String, + Text, + case, + type_coerce, +) +from sqlalchemy.dialects import mysql, oracle, postgresql, sqlite +from sqlalchemy.engine.interfaces import Dialect +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.orm import DeclarativeBase, Mapped, aliased, mapped_column, relationship +from sqlalchemy.types import TypeDecorator + +from homeassistant.components.recorder.const import ( + ALL_DOMAIN_EXCLUDE_ATTRS, + SupportedDialect, +) +from homeassistant.components.recorder.models import ( + StatisticData, + StatisticDataTimestamp, + StatisticMetaData, + bytes_to_ulid_or_none, + bytes_to_uuid_hex_or_none, + datetime_to_timestamp_or_none, + process_timestamp, + ulid_to_bytes_or_none, + uuid_hex_to_bytes_or_none, +) +from homeassistant.const import ( + MAX_LENGTH_EVENT_EVENT_TYPE, + MAX_LENGTH_STATE_ENTITY_ID, + MAX_LENGTH_STATE_STATE, +) +from homeassistant.core import Context, Event, EventOrigin, State +from homeassistant.helpers.json import JSON_DUMP, json_bytes, json_bytes_strip_null +import homeassistant.util.dt as dt_util +from homeassistant.util.json import ( + JSON_DECODE_EXCEPTIONS, + json_loads, + json_loads_object, +) + + +# SQLAlchemy Schema +class Base(DeclarativeBase): + """Base class for tables.""" + + +SCHEMA_VERSION = 42 + +_LOGGER = logging.getLogger(__name__) + +TABLE_EVENTS = "events" +TABLE_EVENT_DATA = "event_data" +TABLE_EVENT_TYPES = "event_types" +TABLE_STATES = "states" +TABLE_STATE_ATTRIBUTES = "state_attributes" +TABLE_STATES_META = "states_meta" +TABLE_RECORDER_RUNS = "recorder_runs" +TABLE_SCHEMA_CHANGES = "schema_changes" +TABLE_STATISTICS = "statistics" +TABLE_STATISTICS_META = "statistics_meta" +TABLE_STATISTICS_RUNS = "statistics_runs" +TABLE_STATISTICS_SHORT_TERM = "statistics_short_term" + +STATISTICS_TABLES = ("statistics", "statistics_short_term") + +MAX_STATE_ATTRS_BYTES = 16384 +MAX_EVENT_DATA_BYTES = 32768 + +PSQL_DIALECT = SupportedDialect.POSTGRESQL + +ALL_TABLES = [ + TABLE_STATES, + TABLE_STATE_ATTRIBUTES, + TABLE_EVENTS, + TABLE_EVENT_DATA, + TABLE_EVENT_TYPES, + TABLE_RECORDER_RUNS, + TABLE_SCHEMA_CHANGES, + TABLE_STATES_META, + TABLE_STATISTICS, + TABLE_STATISTICS_META, + TABLE_STATISTICS_RUNS, + TABLE_STATISTICS_SHORT_TERM, +] + +TABLES_TO_CHECK = [ + TABLE_STATES, + TABLE_EVENTS, + TABLE_RECORDER_RUNS, + TABLE_SCHEMA_CHANGES, +] + +LAST_UPDATED_INDEX_TS = "ix_states_last_updated_ts" +METADATA_ID_LAST_UPDATED_INDEX_TS = "ix_states_metadata_id_last_updated_ts" +EVENTS_CONTEXT_ID_BIN_INDEX = "ix_events_context_id_bin" +STATES_CONTEXT_ID_BIN_INDEX = "ix_states_context_id_bin" +LEGACY_STATES_EVENT_ID_INDEX = "ix_states_event_id" +LEGACY_STATES_ENTITY_ID_LAST_UPDATED_INDEX = "ix_states_entity_id_last_updated_ts" +CONTEXT_ID_BIN_MAX_LENGTH = 16 + +MYSQL_COLLATE = "utf8mb4_unicode_ci" +MYSQL_DEFAULT_CHARSET = "utf8mb4" +MYSQL_ENGINE = "InnoDB" + +_DEFAULT_TABLE_ARGS = { + "mysql_default_charset": MYSQL_DEFAULT_CHARSET, + "mysql_collate": MYSQL_COLLATE, + "mysql_engine": MYSQL_ENGINE, + "mariadb_default_charset": MYSQL_DEFAULT_CHARSET, + "mariadb_collate": MYSQL_COLLATE, + "mariadb_engine": MYSQL_ENGINE, +} + + +class UnusedDateTime(DateTime): + """An unused column type that behaves like a datetime.""" + + +class Unused(CHAR): + """An unused column type that behaves like a string.""" + + +@compiles(UnusedDateTime, "mysql", "mariadb", "sqlite") # type: ignore[misc,no-untyped-call] +@compiles(Unused, "mysql", "mariadb", "sqlite") # type: ignore[misc,no-untyped-call] +def compile_char_zero(type_: TypeDecorator, compiler: Any, **kw: Any) -> str: + """Compile UnusedDateTime and Unused as CHAR(0) on mysql, mariadb, and sqlite.""" + return "CHAR(0)" # Uses 1 byte on MySQL (no change on sqlite) + + +@compiles(Unused, "postgresql") # type: ignore[misc,no-untyped-call] +def compile_char_one(type_: TypeDecorator, compiler: Any, **kw: Any) -> str: + """Compile Unused as CHAR(1) on postgresql.""" + return "CHAR(1)" # Uses 1 byte + + +class FAST_PYSQLITE_DATETIME(sqlite.DATETIME): + """Use ciso8601 to parse datetimes instead of sqlalchemy built-in regex.""" + + def result_processor(self, dialect, coltype): # type: ignore[no-untyped-def] + """Offload the datetime parsing to ciso8601.""" + return lambda value: None if value is None else ciso8601.parse_datetime(value) + + +class NativeLargeBinary(LargeBinary): + """A faster version of LargeBinary for engines that support python bytes natively.""" + + def result_processor(self, dialect, coltype): # type: ignore[no-untyped-def] + """No conversion needed for engines that support native bytes.""" + return None + + +# For MariaDB and MySQL we can use an unsigned integer type since it will fit 2**32 +# for sqlite and postgresql we use a bigint +UINT_32_TYPE = BigInteger().with_variant( + mysql.INTEGER(unsigned=True), # type: ignore[no-untyped-call] + "mysql", + "mariadb", +) +JSON_VARIANT_CAST = Text().with_variant( + postgresql.JSON(none_as_null=True), # type: ignore[no-untyped-call] + "postgresql", +) +JSONB_VARIANT_CAST = Text().with_variant( + postgresql.JSONB(none_as_null=True), # type: ignore[no-untyped-call] + "postgresql", +) +DATETIME_TYPE = ( + DateTime(timezone=True) + .with_variant(mysql.DATETIME(timezone=True, fsp=6), "mysql", "mariadb") # type: ignore[no-untyped-call] + .with_variant(FAST_PYSQLITE_DATETIME(), "sqlite") # type: ignore[no-untyped-call] +) +DOUBLE_TYPE = ( + Float() + .with_variant(mysql.DOUBLE(asdecimal=False), "mysql", "mariadb") # type: ignore[no-untyped-call] + .with_variant(oracle.DOUBLE_PRECISION(), "oracle") + .with_variant(postgresql.DOUBLE_PRECISION(), "postgresql") +) +UNUSED_LEGACY_COLUMN = Unused(0) +UNUSED_LEGACY_DATETIME_COLUMN = UnusedDateTime(timezone=True) +UNUSED_LEGACY_INTEGER_COLUMN = SmallInteger() +DOUBLE_PRECISION_TYPE_SQL = "DOUBLE PRECISION" +CONTEXT_BINARY_TYPE = LargeBinary(CONTEXT_ID_BIN_MAX_LENGTH).with_variant( + NativeLargeBinary(CONTEXT_ID_BIN_MAX_LENGTH), "mysql", "mariadb", "sqlite" +) + +TIMESTAMP_TYPE = DOUBLE_TYPE + + +class JSONLiteral(JSON): + """Teach SA how to literalize json.""" + + def literal_processor(self, dialect: Dialect) -> Callable[[Any], str]: + """Processor to convert a value to JSON.""" + + def process(value: Any) -> str: + """Dump json.""" + return JSON_DUMP(value) + + return process + + +EVENT_ORIGIN_ORDER = [EventOrigin.local, EventOrigin.remote] +EVENT_ORIGIN_TO_IDX = {origin: idx for idx, origin in enumerate(EVENT_ORIGIN_ORDER)} + + +class Events(Base): + """Event history data.""" + + __table_args__ = ( + # Used for fetching events at a specific time + # see logbook + Index( + "ix_events_event_type_id_time_fired_ts", "event_type_id", "time_fired_ts" + ), + Index( + EVENTS_CONTEXT_ID_BIN_INDEX, + "context_id_bin", + mysql_length=CONTEXT_ID_BIN_MAX_LENGTH, + mariadb_length=CONTEXT_ID_BIN_MAX_LENGTH, + ), + _DEFAULT_TABLE_ARGS, + ) + __tablename__ = TABLE_EVENTS + event_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + event_type: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + event_data: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + origin: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + origin_idx: Mapped[int | None] = mapped_column(SmallInteger) + time_fired: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) + time_fired_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE, index=True) + context_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + context_user_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + context_parent_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + data_id: Mapped[int | None] = mapped_column( + Integer, ForeignKey("event_data.data_id"), index=True + ) + context_id_bin: Mapped[bytes | None] = mapped_column(CONTEXT_BINARY_TYPE) + context_user_id_bin: Mapped[bytes | None] = mapped_column(CONTEXT_BINARY_TYPE) + context_parent_id_bin: Mapped[bytes | None] = mapped_column(CONTEXT_BINARY_TYPE) + event_type_id: Mapped[int | None] = mapped_column( + Integer, ForeignKey("event_types.event_type_id") + ) + event_data_rel: Mapped[EventData | None] = relationship("EventData") + event_type_rel: Mapped[EventTypes | None] = relationship("EventTypes") + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + "" + ) + + @property + def _time_fired_isotime(self) -> str | None: + """Return time_fired as an isotime string.""" + date_time: datetime | None + if self.time_fired_ts is not None: + date_time = dt_util.utc_from_timestamp(self.time_fired_ts) + else: + date_time = process_timestamp(self.time_fired) + if date_time is None: + return None + return date_time.isoformat(sep=" ", timespec="seconds") + + @staticmethod + def from_event(event: Event) -> Events: + """Create an event database object from a native event.""" + return Events( + event_type=None, + event_data=None, + origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin), + time_fired=None, + time_fired_ts=event.time_fired_timestamp, + context_id=None, + context_id_bin=ulid_to_bytes_or_none(event.context.id), + context_user_id=None, + context_user_id_bin=uuid_hex_to_bytes_or_none(event.context.user_id), + context_parent_id=None, + context_parent_id_bin=ulid_to_bytes_or_none(event.context.parent_id), + ) + + def to_native(self, validate_entity_id: bool = True) -> Event | None: + """Convert to a native HA Event.""" + context = Context( + id=bytes_to_ulid_or_none(self.context_id_bin), + user_id=bytes_to_uuid_hex_or_none(self.context_user_id_bin), + parent_id=bytes_to_ulid_or_none(self.context_parent_id_bin), + ) + try: + return Event( + self.event_type or "", + json_loads_object(self.event_data) if self.event_data else {}, + EventOrigin(self.origin) + if self.origin + else EVENT_ORIGIN_ORDER[self.origin_idx or 0], + dt_util.utc_from_timestamp(self.time_fired_ts or 0), + context=context, + ) + except JSON_DECODE_EXCEPTIONS: + # When json_loads fails + _LOGGER.exception("Error converting to event: %s", self) + return None + + +class EventData(Base): + """Event data history.""" + + __table_args__ = (_DEFAULT_TABLE_ARGS,) + __tablename__ = TABLE_EVENT_DATA + data_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + hash: Mapped[int | None] = mapped_column(UINT_32_TYPE, index=True) + # Note that this is not named attributes to avoid confusion with the states table + shared_data: Mapped[str | None] = mapped_column( + Text().with_variant(mysql.LONGTEXT, "mysql", "mariadb") + ) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + "" + ) + + @staticmethod + def shared_data_bytes_from_event( + event: Event, dialect: SupportedDialect | None + ) -> bytes: + """Create shared_data from an event.""" + if dialect == SupportedDialect.POSTGRESQL: + bytes_result = json_bytes_strip_null(event.data) + bytes_result = json_bytes(event.data) + if len(bytes_result) > MAX_EVENT_DATA_BYTES: + _LOGGER.warning( + "Event data for %s exceed maximum size of %s bytes. " + "This can cause database performance issues; Event data " + "will not be stored", + event.event_type, + MAX_EVENT_DATA_BYTES, + ) + return b"{}" + return bytes_result + + @staticmethod + def hash_shared_data_bytes(shared_data_bytes: bytes) -> int: + """Return the hash of json encoded shared data.""" + return fnv1a_32(shared_data_bytes) + + def to_native(self) -> dict[str, Any]: + """Convert to an event data dictionary.""" + shared_data = self.shared_data + if shared_data is None: + return {} + try: + return cast(dict[str, Any], json_loads(shared_data)) + except JSON_DECODE_EXCEPTIONS: + _LOGGER.exception("Error converting row to event data: %s", self) + return {} + + +class EventTypes(Base): + """Event type history.""" + + __table_args__ = (_DEFAULT_TABLE_ARGS,) + __tablename__ = TABLE_EVENT_TYPES + event_type_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + event_type: Mapped[str | None] = mapped_column( + String(MAX_LENGTH_EVENT_EVENT_TYPE), index=True, unique=True + ) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + "" + ) + + +class States(Base): + """State change history.""" + + __table_args__ = ( + # Used for fetching the state of entities at a specific time + # (get_states in history.py) + Index(METADATA_ID_LAST_UPDATED_INDEX_TS, "metadata_id", "last_updated_ts"), + Index( + STATES_CONTEXT_ID_BIN_INDEX, + "context_id_bin", + mysql_length=CONTEXT_ID_BIN_MAX_LENGTH, + mariadb_length=CONTEXT_ID_BIN_MAX_LENGTH, + ), + _DEFAULT_TABLE_ARGS, + ) + __tablename__ = TABLE_STATES + state_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + entity_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + state: Mapped[str | None] = mapped_column(String(MAX_LENGTH_STATE_STATE)) + attributes: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + event_id: Mapped[int | None] = mapped_column(UNUSED_LEGACY_INTEGER_COLUMN) + last_changed: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) + last_changed_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE) + last_updated: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) + last_updated_ts: Mapped[float | None] = mapped_column( + TIMESTAMP_TYPE, default=time.time, index=True + ) + old_state_id: Mapped[int | None] = mapped_column( + Integer, ForeignKey("states.state_id"), index=True + ) + attributes_id: Mapped[int | None] = mapped_column( + Integer, ForeignKey("state_attributes.attributes_id"), index=True + ) + context_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + context_user_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + context_parent_id: Mapped[str | None] = mapped_column(UNUSED_LEGACY_COLUMN) + origin_idx: Mapped[int | None] = mapped_column( + SmallInteger + ) # 0 is local, 1 is remote + old_state: Mapped[States | None] = relationship("States", remote_side=[state_id]) + state_attributes: Mapped[StateAttributes | None] = relationship("StateAttributes") + context_id_bin: Mapped[bytes | None] = mapped_column(CONTEXT_BINARY_TYPE) + context_user_id_bin: Mapped[bytes | None] = mapped_column(CONTEXT_BINARY_TYPE) + context_parent_id_bin: Mapped[bytes | None] = mapped_column(CONTEXT_BINARY_TYPE) + metadata_id: Mapped[int | None] = mapped_column( + Integer, ForeignKey("states_meta.metadata_id") + ) + states_meta_rel: Mapped[StatesMeta | None] = relationship("StatesMeta") + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @property + def _last_updated_isotime(self) -> str | None: + """Return last_updated as an isotime string.""" + date_time: datetime | None + if self.last_updated_ts is not None: + date_time = dt_util.utc_from_timestamp(self.last_updated_ts) + else: + date_time = process_timestamp(self.last_updated) + if date_time is None: + return None + return date_time.isoformat(sep=" ", timespec="seconds") + + @staticmethod + def from_event(event: Event) -> States: + """Create object from a state_changed event.""" + entity_id = event.data["entity_id"] + state: State | None = event.data.get("new_state") + dbstate = States( + entity_id=entity_id, + attributes=None, + context_id=None, + context_id_bin=ulid_to_bytes_or_none(event.context.id), + context_user_id=None, + context_user_id_bin=uuid_hex_to_bytes_or_none(event.context.user_id), + context_parent_id=None, + context_parent_id_bin=ulid_to_bytes_or_none(event.context.parent_id), + origin_idx=EVENT_ORIGIN_TO_IDX.get(event.origin), + last_updated=None, + last_changed=None, + ) + # None state means the state was removed from the state machine + if state is None: + dbstate.state = "" + dbstate.last_updated_ts = event.time_fired_timestamp + dbstate.last_changed_ts = None + return dbstate + + dbstate.state = state.state + dbstate.last_updated_ts = state.last_updated_timestamp + if state.last_updated == state.last_changed: + dbstate.last_changed_ts = None + else: + dbstate.last_changed_ts = state.last_changed_timestamp + + return dbstate + + def to_native(self, validate_entity_id: bool = True) -> State | None: + """Convert to an HA state object.""" + context = Context( + id=bytes_to_ulid_or_none(self.context_id_bin), + user_id=bytes_to_uuid_hex_or_none(self.context_user_id_bin), + parent_id=bytes_to_ulid_or_none(self.context_parent_id_bin), + ) + try: + attrs = json_loads_object(self.attributes) if self.attributes else {} + except JSON_DECODE_EXCEPTIONS: + # When json_loads fails + _LOGGER.exception("Error converting row to state: %s", self) + return None + if self.last_changed_ts is None or self.last_changed_ts == self.last_updated_ts: + last_changed = last_updated = dt_util.utc_from_timestamp( + self.last_updated_ts or 0 + ) + else: + last_updated = dt_util.utc_from_timestamp(self.last_updated_ts or 0) + last_changed = dt_util.utc_from_timestamp(self.last_changed_ts or 0) + return State( + self.entity_id or "", + self.state, # type: ignore[arg-type] + # Join the state_attributes table on attributes_id to get the attributes + # for newer states + attrs, + last_changed, + last_updated, + context=context, + validate_entity_id=validate_entity_id, + ) + + +class StateAttributes(Base): + """State attribute change history.""" + + __table_args__ = (_DEFAULT_TABLE_ARGS,) + __tablename__ = TABLE_STATE_ATTRIBUTES + attributes_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + hash: Mapped[int | None] = mapped_column(UINT_32_TYPE, index=True) + # Note that this is not named attributes to avoid confusion with the states table + shared_attrs: Mapped[str | None] = mapped_column( + Text().with_variant(mysql.LONGTEXT, "mysql", "mariadb") + ) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + @staticmethod + def shared_attrs_bytes_from_event( + event: Event, + dialect: SupportedDialect | None, + ) -> bytes: + """Create shared_attrs from a state_changed event.""" + state: State | None = event.data.get("new_state") + # None state means the state was removed from the state machine + if state is None: + return b"{}" + if state_info := state.state_info: + exclude_attrs = { + *ALL_DOMAIN_EXCLUDE_ATTRS, + *state_info["unrecorded_attributes"], + } + else: + exclude_attrs = ALL_DOMAIN_EXCLUDE_ATTRS + encoder = json_bytes_strip_null if dialect == PSQL_DIALECT else json_bytes + bytes_result = encoder( + {k: v for k, v in state.attributes.items() if k not in exclude_attrs} + ) + if len(bytes_result) > MAX_STATE_ATTRS_BYTES: + _LOGGER.warning( + "State attributes for %s exceed maximum size of %s bytes. " + "This can cause database performance issues; Attributes " + "will not be stored", + state.entity_id, + MAX_STATE_ATTRS_BYTES, + ) + return b"{}" + return bytes_result + + @staticmethod + def hash_shared_attrs_bytes(shared_attrs_bytes: bytes) -> int: + """Return the hash of json encoded shared attributes.""" + return fnv1a_32(shared_attrs_bytes) + + def to_native(self) -> dict[str, Any]: + """Convert to a state attributes dictionary.""" + shared_attrs = self.shared_attrs + if shared_attrs is None: + return {} + try: + return cast(dict[str, Any], json_loads(shared_attrs)) + except JSON_DECODE_EXCEPTIONS: + # When json_loads fails + _LOGGER.exception("Error converting row to state attributes: %s", self) + return {} + + +class StatesMeta(Base): + """Metadata for states.""" + + __table_args__ = (_DEFAULT_TABLE_ARGS,) + __tablename__ = TABLE_STATES_META + metadata_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + entity_id: Mapped[str | None] = mapped_column( + String(MAX_LENGTH_STATE_ENTITY_ID), index=True, unique=True + ) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + "" + ) + + +class StatisticsBase: + """Statistics base class.""" + + id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + created: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) + created_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE, default=time.time) + metadata_id: Mapped[int | None] = mapped_column( + Integer, + ForeignKey(f"{TABLE_STATISTICS_META}.id", ondelete="CASCADE"), + ) + start: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) + start_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE, index=True) + mean: Mapped[float | None] = mapped_column(DOUBLE_TYPE) + min: Mapped[float | None] = mapped_column(DOUBLE_TYPE) + max: Mapped[float | None] = mapped_column(DOUBLE_TYPE) + last_reset: Mapped[datetime | None] = mapped_column(UNUSED_LEGACY_DATETIME_COLUMN) + last_reset_ts: Mapped[float | None] = mapped_column(TIMESTAMP_TYPE) + state: Mapped[float | None] = mapped_column(DOUBLE_TYPE) + sum: Mapped[float | None] = mapped_column(DOUBLE_TYPE) + + duration: timedelta + + @classmethod + def from_stats(cls, metadata_id: int, stats: StatisticData) -> Self: + """Create object from a statistics with datatime objects.""" + return cls( # type: ignore[call-arg] + metadata_id=metadata_id, + created=None, + created_ts=time.time(), + start=None, + start_ts=dt_util.utc_to_timestamp(stats["start"]), + mean=stats.get("mean"), + min=stats.get("min"), + max=stats.get("max"), + last_reset=None, + last_reset_ts=datetime_to_timestamp_or_none(stats.get("last_reset")), + state=stats.get("state"), + sum=stats.get("sum"), + ) + + @classmethod + def from_stats_ts(cls, metadata_id: int, stats: StatisticDataTimestamp) -> Self: + """Create object from a statistics with timestamps.""" + return cls( # type: ignore[call-arg] + metadata_id=metadata_id, + created=None, + created_ts=time.time(), + start=None, + start_ts=stats["start_ts"], + mean=stats.get("mean"), + min=stats.get("min"), + max=stats.get("max"), + last_reset=None, + last_reset_ts=stats.get("last_reset_ts"), + state=stats.get("state"), + sum=stats.get("sum"), + ) + + +class Statistics(Base, StatisticsBase): + """Long term statistics.""" + + duration = timedelta(hours=1) + + __table_args__ = ( + # Used for fetching statistics for a certain entity at a specific time + Index( + "ix_statistics_statistic_id_start_ts", + "metadata_id", + "start_ts", + unique=True, + ), + ) + __tablename__ = TABLE_STATISTICS + + +class StatisticsShortTerm(Base, StatisticsBase): + """Short term statistics.""" + + duration = timedelta(minutes=5) + + __table_args__ = ( + # Used for fetching statistics for a certain entity at a specific time + Index( + "ix_statistics_short_term_statistic_id_start_ts", + "metadata_id", + "start_ts", + unique=True, + ), + ) + __tablename__ = TABLE_STATISTICS_SHORT_TERM + + +class StatisticsMeta(Base): + """Statistics meta data.""" + + __table_args__ = (_DEFAULT_TABLE_ARGS,) + __tablename__ = TABLE_STATISTICS_META + id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + statistic_id: Mapped[str | None] = mapped_column( + String(255), index=True, unique=True + ) + source: Mapped[str | None] = mapped_column(String(32)) + unit_of_measurement: Mapped[str | None] = mapped_column(String(255)) + has_mean: Mapped[bool | None] = mapped_column(Boolean) + has_sum: Mapped[bool | None] = mapped_column(Boolean) + name: Mapped[str | None] = mapped_column(String(255)) + + @staticmethod + def from_meta(meta: StatisticMetaData) -> StatisticsMeta: + """Create object from meta data.""" + return StatisticsMeta(**meta) + + +class RecorderRuns(Base): + """Representation of recorder run.""" + + __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) + __tablename__ = TABLE_RECORDER_RUNS + run_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + start: Mapped[datetime] = mapped_column(DATETIME_TYPE, default=dt_util.utcnow) + end: Mapped[datetime | None] = mapped_column(DATETIME_TYPE) + closed_incorrect: Mapped[bool] = mapped_column(Boolean, default=False) + created: Mapped[datetime] = mapped_column(DATETIME_TYPE, default=dt_util.utcnow) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + end = ( + f"'{self.end.isoformat(sep=' ', timespec='seconds')}'" if self.end else None + ) + return ( + f"" + ) + + def to_native(self, validate_entity_id: bool = True) -> Self: + """Return self, native format is this model.""" + return self + + +class SchemaChanges(Base): + """Representation of schema version changes.""" + + __tablename__ = TABLE_SCHEMA_CHANGES + change_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + schema_version: Mapped[int | None] = mapped_column(Integer) + changed: Mapped[datetime] = mapped_column(DATETIME_TYPE, default=dt_util.utcnow) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + "" + ) + + +class StatisticsRuns(Base): + """Representation of statistics run.""" + + __tablename__ = TABLE_STATISTICS_RUNS + run_id: Mapped[int] = mapped_column(Integer, Identity(), primary_key=True) + start: Mapped[datetime] = mapped_column(DATETIME_TYPE, index=True) + + def __repr__(self) -> str: + """Return string representation of instance for debugging.""" + return ( + f"" + ) + + +EVENT_DATA_JSON = type_coerce( + EventData.shared_data.cast(JSONB_VARIANT_CAST), JSONLiteral(none_as_null=True) +) +OLD_FORMAT_EVENT_DATA_JSON = type_coerce( + Events.event_data.cast(JSONB_VARIANT_CAST), JSONLiteral(none_as_null=True) +) + +SHARED_ATTRS_JSON = type_coerce( + StateAttributes.shared_attrs.cast(JSON_VARIANT_CAST), JSON(none_as_null=True) +) +OLD_FORMAT_ATTRS_JSON = type_coerce( + States.attributes.cast(JSON_VARIANT_CAST), JSON(none_as_null=True) +) + +ENTITY_ID_IN_EVENT: ColumnElement = EVENT_DATA_JSON["entity_id"] +OLD_ENTITY_ID_IN_EVENT: ColumnElement = OLD_FORMAT_EVENT_DATA_JSON["entity_id"] +DEVICE_ID_IN_EVENT: ColumnElement = EVENT_DATA_JSON["device_id"] +OLD_STATE = aliased(States, name="old_state") + +SHARED_ATTR_OR_LEGACY_ATTRIBUTES = case( + (StateAttributes.shared_attrs.is_(None), States.attributes), + else_=StateAttributes.shared_attrs, +).label("attributes") +SHARED_DATA_OR_LEGACY_EVENT_DATA = case( + (EventData.shared_data.is_(None), Events.event_data), else_=EventData.shared_data +).label("event_data") diff --git a/tests/components/recorder/test_history_db_schema_42.py b/tests/components/recorder/test_history_db_schema_42.py new file mode 100644 index 00000000000..98ed6089de6 --- /dev/null +++ b/tests/components/recorder/test_history_db_schema_42.py @@ -0,0 +1,1278 @@ +"""The tests the History component.""" + +from __future__ import annotations + +from collections.abc import Callable +from copy import copy +from datetime import datetime, timedelta +import json +from unittest.mock import patch, sentinel + +from freezegun import freeze_time +import pytest +from sqlalchemy import text + +from homeassistant.components import recorder +from homeassistant.components.recorder import Recorder, get_instance, history +from homeassistant.components.recorder.filters import Filters +from homeassistant.components.recorder.history import legacy +from homeassistant.components.recorder.models import process_timestamp +from homeassistant.components.recorder.models.legacy import ( + LegacyLazyState, + LegacyLazyStatePreSchema31, +) +from homeassistant.components.recorder.util import session_scope +import homeassistant.core as ha +from homeassistant.core import HomeAssistant, State +from homeassistant.helpers.json import JSONEncoder +import homeassistant.util.dt as dt_util + +from .common import ( + assert_dict_of_states_equal_without_context_and_last_changed, + assert_multiple_states_equal_without_context, + assert_multiple_states_equal_without_context_and_last_changed, + assert_states_equal_without_context, + async_recorder_block_till_done, + async_wait_recording_done, + old_db_schema, + wait_recording_done, +) +from .db_schema_42 import Events, RecorderRuns, StateAttributes, States, StatesMeta + +from tests.typing import RecorderInstanceGenerator + + +@pytest.fixture(autouse=True) +def db_schema_42(): + """Fixture to initialize the db with the old schema 42.""" + with old_db_schema("42"): + yield + + +async def _async_get_states( + hass: HomeAssistant, + utc_point_in_time: datetime, + entity_ids: list[str] | None = None, + run: RecorderRuns | None = None, + no_attributes: bool = False, +): + """Get states from the database.""" + + def _get_states_with_session(): + with session_scope(hass=hass, read_only=True) as session: + attr_cache = {} + pre_31_schema = get_instance(hass).schema_version < 31 + return [ + LegacyLazyStatePreSchema31(row, attr_cache, None) + if pre_31_schema + else LegacyLazyState( + row, + attr_cache, + None, + row.entity_id, + ) + for row in legacy._get_rows_with_session( + hass, + session, + utc_point_in_time, + entity_ids, + run, + no_attributes, + ) + ] + + return await recorder.get_instance(hass).async_add_executor_job( + _get_states_with_session + ) + + +def _add_db_entries( + hass: ha.HomeAssistant, point: datetime, entity_ids: list[str] +) -> None: + with session_scope(hass=hass) as session: + for idx, entity_id in enumerate(entity_ids): + session.add( + Events( + event_id=1001 + idx, + event_type="state_changed", + event_data="{}", + origin="LOCAL", + time_fired=point, + ) + ) + session.add( + States( + entity_id=entity_id, + state="on", + attributes='{"name":"the light"}', + last_changed=None, + last_updated=point, + event_id=1001 + idx, + attributes_id=1002 + idx, + ) + ) + session.add( + StateAttributes( + shared_attrs='{"name":"the shared light"}', + hash=1234 + idx, + attributes_id=1002 + idx, + ) + ) + + +def test_get_full_significant_states_with_session_entity_no_matches( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test getting states at a specific point in time for entities that never have been recorded.""" + hass = hass_recorder() + now = dt_util.utcnow() + time_before_recorder_ran = now - timedelta(days=1000) + with session_scope(hass=hass, read_only=True) as session: + assert ( + history.get_full_significant_states_with_session( + hass, session, time_before_recorder_ran, now, entity_ids=["demo.id"] + ) + == {} + ) + assert ( + history.get_full_significant_states_with_session( + hass, + session, + time_before_recorder_ran, + now, + entity_ids=["demo.id", "demo.id2"], + ) + == {} + ) + + +def test_significant_states_with_session_entity_minimal_response_no_matches( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test getting states at a specific point in time for entities that never have been recorded.""" + hass = hass_recorder() + now = dt_util.utcnow() + time_before_recorder_ran = now - timedelta(days=1000) + with session_scope(hass=hass, read_only=True) as session: + assert ( + history.get_significant_states_with_session( + hass, + session, + time_before_recorder_ran, + now, + entity_ids=["demo.id"], + minimal_response=True, + ) + == {} + ) + assert ( + history.get_significant_states_with_session( + hass, + session, + time_before_recorder_ran, + now, + entity_ids=["demo.id", "demo.id2"], + minimal_response=True, + ) + == {} + ) + + +def test_significant_states_with_session_single_entity( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test get_significant_states_with_session with a single entity.""" + hass = hass_recorder() + hass.states.set("demo.id", "any", {"attr": True}) + hass.states.set("demo.id", "any2", {"attr": True}) + wait_recording_done(hass) + now = dt_util.utcnow() + with session_scope(hass=hass, read_only=True) as session: + states = history.get_significant_states_with_session( + hass, + session, + now - timedelta(days=1), + now, + entity_ids=["demo.id"], + minimal_response=False, + ) + assert len(states["demo.id"]) == 2 + + +@pytest.mark.parametrize( + ("attributes", "no_attributes", "limit"), + [ + ({"attr": True}, False, 5000), + ({}, True, 5000), + ({"attr": True}, False, 3), + ({}, True, 3), + ], +) +def test_state_changes_during_period( + hass_recorder: Callable[..., HomeAssistant], attributes, no_attributes, limit +) -> None: + """Test state change during period.""" + hass = hass_recorder() + entity_id = "media_player.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state, attributes) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() + point = start + timedelta(seconds=1) + end = point + timedelta(seconds=1) + + with freeze_time(start) as freezer: + set_state("idle") + set_state("YouTube") + + freezer.move_to(point) + states = [ + set_state("idle"), + set_state("Netflix"), + set_state("Plex"), + set_state("YouTube"), + ] + + freezer.move_to(end) + set_state("Netflix") + set_state("Plex") + + hist = history.state_changes_during_period( + hass, start, end, entity_id, no_attributes, limit=limit + ) + + assert_multiple_states_equal_without_context(states[:limit], hist[entity_id]) + + +def test_state_changes_during_period_last_reported( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test state change during period.""" + hass = hass_recorder() + entity_id = "media_player.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return ha.State.from_dict(hass.states.get(entity_id).as_dict()) + + start = dt_util.utcnow() + point1 = start + timedelta(seconds=1) + point2 = point1 + timedelta(seconds=1) + end = point2 + timedelta(seconds=1) + + with freeze_time(start) as freezer: + set_state("idle") + + freezer.move_to(point1) + states = [set_state("YouTube")] + + freezer.move_to(point2) + set_state("YouTube") + + freezer.move_to(end) + set_state("Netflix") + + hist = history.state_changes_during_period(hass, start, end, entity_id) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + +def test_state_changes_during_period_descending( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test state change during period descending.""" + hass = hass_recorder() + entity_id = "media_player.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state, {"any": 1}) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow().replace(microsecond=0) + point = start + timedelta(seconds=1) + point2 = start + timedelta(seconds=1, microseconds=100) + point3 = start + timedelta(seconds=1, microseconds=200) + point4 = start + timedelta(seconds=1, microseconds=300) + end = point + timedelta(seconds=1, microseconds=400) + + with freeze_time(start) as freezer: + set_state("idle") + set_state("YouTube") + + freezer.move_to(point) + states = [set_state("idle")] + + freezer.move_to(point2) + states.append(set_state("Netflix")) + + freezer.move_to(point3) + states.append(set_state("Plex")) + + freezer.move_to(point4) + states.append(set_state("YouTube")) + + freezer.move_to(end) + set_state("Netflix") + set_state("Plex") + + hist = history.state_changes_during_period( + hass, start, end, entity_id, no_attributes=False, descending=False + ) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + hist = history.state_changes_during_period( + hass, start, end, entity_id, no_attributes=False, descending=True + ) + assert_multiple_states_equal_without_context( + states, list(reversed(list(hist[entity_id]))) + ) + + start_time = point2 + timedelta(microseconds=10) + hist = history.state_changes_during_period( + hass, + start_time, # Pick a point where we will generate a start time state + end, + entity_id, + no_attributes=False, + descending=True, + include_start_time_state=True, + ) + hist_states = list(hist[entity_id]) + assert hist_states[-1].last_updated == start_time + assert hist_states[-1].last_changed == start_time + assert len(hist_states) == 3 + # Make sure they are in descending order + assert ( + hist_states[0].last_updated + > hist_states[1].last_updated + > hist_states[2].last_updated + ) + assert ( + hist_states[0].last_changed + > hist_states[1].last_changed + > hist_states[2].last_changed + ) + hist = history.state_changes_during_period( + hass, + start_time, # Pick a point where we will generate a start time state + end, + entity_id, + no_attributes=False, + descending=False, + include_start_time_state=True, + ) + hist_states = list(hist[entity_id]) + assert hist_states[0].last_updated == start_time + assert hist_states[0].last_changed == start_time + assert len(hist_states) == 3 + # Make sure they are in ascending order + assert ( + hist_states[0].last_updated + < hist_states[1].last_updated + < hist_states[2].last_updated + ) + assert ( + hist_states[0].last_changed + < hist_states[1].last_changed + < hist_states[2].last_changed + ) + + +def test_get_last_state_changes(hass_recorder: Callable[..., HomeAssistant]) -> None: + """Test number of state changes.""" + hass = hass_recorder() + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + point2 = point + timedelta(minutes=1, seconds=1) + states = [] + + with freeze_time(start) as freezer: + set_state("1") + + freezer.move_to(point) + states.append(set_state("2")) + + freezer.move_to(point2) + states.append(set_state("3")) + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + +def test_get_last_state_changes_last_reported( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test number of state changes.""" + hass = hass_recorder() + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return ha.State.from_dict(hass.states.get(entity_id).as_dict()) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + point2 = point + timedelta(minutes=1, seconds=1) + states = [] + + with freeze_time(start) as freezer: + states.append(set_state("1")) + + freezer.move_to(point) + set_state("1") + + freezer.move_to(point2) + states.append(set_state("2")) + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + +def test_get_last_state_change(hass_recorder: Callable[..., HomeAssistant]) -> None: + """Test getting the last state change for an entity.""" + hass = hass_recorder() + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + point2 = point + timedelta(minutes=1, seconds=1) + states = [] + + with freeze_time(start) as freezer: + set_state("1") + + freezer.move_to(point) + set_state("2") + + freezer.move_to(point2) + states.append(set_state("3")) + + hist = history.get_last_state_changes(hass, 1, entity_id) + + assert_multiple_states_equal_without_context(states, hist[entity_id]) + + +def test_ensure_state_can_be_copied( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Ensure a state can pass though copy(). + + The filter integration uses copy() on states + from history. + """ + hass = hass_recorder() + entity_id = "sensor.test" + + def set_state(state): + """Set the state.""" + hass.states.set(entity_id, state) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=2) + point = start + timedelta(minutes=1) + + with freeze_time(start) as freezer: + set_state("1") + + freezer.move_to(point) + set_state("2") + + hist = history.get_last_state_changes(hass, 2, entity_id) + + assert_states_equal_without_context(copy(hist[entity_id][0]), hist[entity_id][0]) + assert_states_equal_without_context(copy(hist[entity_id][1]), hist[entity_id][1]) + + +def test_get_significant_states(hass_recorder: Callable[..., HomeAssistant]) -> None: + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_recorder() + zero, four, states = record_states(hass) + hist = history.get_significant_states(hass, zero, four, entity_ids=list(states)) + assert_dict_of_states_equal_without_context_and_last_changed(states, hist) + + +def test_get_significant_states_minimal_response( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test that only significant states are returned. + + When minimal responses is set only the first and + last states return a complete state. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_recorder() + zero, four, states = record_states(hass) + hist = history.get_significant_states( + hass, zero, four, minimal_response=True, entity_ids=list(states) + ) + entites_with_reducable_states = [ + "media_player.test", + "media_player.test3", + ] + + # All states for media_player.test state are reduced + # down to last_changed and state when minimal_response + # is set except for the first state. + # is set. We use JSONEncoder to make sure that are + # pre-encoded last_changed is always the same as what + # will happen with encoding a native state + for entity_id in entites_with_reducable_states: + entity_states = states[entity_id] + for state_idx in range(1, len(entity_states)): + input_state = entity_states[state_idx] + orig_last_changed = orig_last_changed = json.dumps( + process_timestamp(input_state.last_changed), + cls=JSONEncoder, + ).replace('"', "") + orig_state = input_state.state + entity_states[state_idx] = { + "last_changed": orig_last_changed, + "state": orig_state, + } + + assert len(hist) == len(states) + assert_states_equal_without_context( + states["media_player.test"][0], hist["media_player.test"][0] + ) + assert states["media_player.test"][1] == hist["media_player.test"][1] + assert states["media_player.test"][2] == hist["media_player.test"][2] + + assert_multiple_states_equal_without_context( + states["media_player.test2"], hist["media_player.test2"] + ) + assert_states_equal_without_context( + states["media_player.test3"][0], hist["media_player.test3"][0] + ) + assert states["media_player.test3"][1] == hist["media_player.test3"][1] + + assert_multiple_states_equal_without_context( + states["script.can_cancel_this_one"], hist["script.can_cancel_this_one"] + ) + assert_multiple_states_equal_without_context_and_last_changed( + states["thermostat.test"], hist["thermostat.test"] + ) + assert_multiple_states_equal_without_context_and_last_changed( + states["thermostat.test2"], hist["thermostat.test2"] + ) + + +@pytest.mark.parametrize("time_zone", ["Europe/Berlin", "US/Hawaii", "UTC"]) +def test_get_significant_states_with_initial( + time_zone, hass_recorder: Callable[..., HomeAssistant] +) -> None: + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_recorder() + hass.config.set_time_zone(time_zone) + zero, four, states = record_states(hass) + one_and_half = zero + timedelta(seconds=1.5) + for entity_id in states: + if entity_id == "media_player.test": + states[entity_id] = states[entity_id][1:] + for state in states[entity_id]: + # If the state is recorded before the start time + # start it will have its last_updated and last_changed + # set to the start time. + if state.last_updated < one_and_half: + state.last_updated = one_and_half + state.last_changed = one_and_half + + hist = history.get_significant_states( + hass, one_and_half, four, include_start_time_state=True, entity_ids=list(states) + ) + assert_dict_of_states_equal_without_context_and_last_changed(states, hist) + + +def test_get_significant_states_without_initial( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test that only significant states are returned. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + hass = hass_recorder() + zero, four, states = record_states(hass) + one = zero + timedelta(seconds=1) + one_with_microsecond = zero + timedelta(seconds=1, microseconds=1) + one_and_half = zero + timedelta(seconds=1.5) + for entity_id in states: + states[entity_id] = list( + filter( + lambda s: s.last_changed != one + and s.last_changed != one_with_microsecond, + states[entity_id], + ) + ) + del states["media_player.test2"] + del states["thermostat.test3"] + + hist = history.get_significant_states( + hass, + one_and_half, + four, + include_start_time_state=False, + entity_ids=list(states), + ) + assert_dict_of_states_equal_without_context_and_last_changed(states, hist) + + +def test_get_significant_states_entity_id( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test that only significant states are returned for one entity.""" + hass = hass_recorder() + zero, four, states = record_states(hass) + del states["media_player.test2"] + del states["media_player.test3"] + del states["thermostat.test"] + del states["thermostat.test2"] + del states["thermostat.test3"] + del states["script.can_cancel_this_one"] + + hist = history.get_significant_states(hass, zero, four, ["media_player.test"]) + assert_dict_of_states_equal_without_context_and_last_changed(states, hist) + + +def test_get_significant_states_multiple_entity_ids( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test that only significant states are returned for one entity.""" + hass = hass_recorder() + zero, four, states = record_states(hass) + + hist = history.get_significant_states( + hass, + zero, + four, + ["media_player.test", "thermostat.test"], + ) + + assert_multiple_states_equal_without_context_and_last_changed( + states["media_player.test"], hist["media_player.test"] + ) + assert_multiple_states_equal_without_context_and_last_changed( + states["thermostat.test"], hist["thermostat.test"] + ) + + +def test_get_significant_states_are_ordered( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test order of results from get_significant_states. + + When entity ids are given, the results should be returned with the data + in the same order. + """ + hass = hass_recorder() + zero, four, _states = record_states(hass) + entity_ids = ["media_player.test", "media_player.test2"] + hist = history.get_significant_states(hass, zero, four, entity_ids) + assert list(hist.keys()) == entity_ids + entity_ids = ["media_player.test2", "media_player.test"] + hist = history.get_significant_states(hass, zero, four, entity_ids) + assert list(hist.keys()) == entity_ids + + +def test_get_significant_states_only( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test significant states when significant_states_only is set.""" + hass = hass_recorder() + entity_id = "sensor.test" + + def set_state(state, **kwargs): + """Set the state.""" + hass.states.set(entity_id, state, **kwargs) + wait_recording_done(hass) + return hass.states.get(entity_id) + + start = dt_util.utcnow() - timedelta(minutes=4) + points = [start + timedelta(minutes=i) for i in range(1, 4)] + + states = [] + with freeze_time(start) as freezer: + set_state("123", attributes={"attribute": 10.64}) + + freezer.move_to(points[0]) + # Attributes are different, state not + states.append(set_state("123", attributes={"attribute": 21.42})) + + freezer.move_to(points[1]) + # state is different, attributes not + states.append(set_state("32", attributes={"attribute": 21.42})) + + freezer.move_to(points[2]) + # everything is different + states.append(set_state("412", attributes={"attribute": 54.23})) + + hist = history.get_significant_states( + hass, + start, + significant_changes_only=True, + entity_ids=list({state.entity_id for state in states}), + ) + + assert len(hist[entity_id]) == 2 + assert not any( + state.last_updated == states[0].last_updated for state in hist[entity_id] + ) + assert any( + state.last_updated == states[1].last_updated for state in hist[entity_id] + ) + assert any( + state.last_updated == states[2].last_updated for state in hist[entity_id] + ) + + hist = history.get_significant_states( + hass, + start, + significant_changes_only=False, + entity_ids=list({state.entity_id for state in states}), + ) + + assert len(hist[entity_id]) == 3 + assert_multiple_states_equal_without_context_and_last_changed( + states, hist[entity_id] + ) + + +async def test_get_significant_states_only_minimal_response( + recorder_mock: Recorder, hass: HomeAssistant +) -> None: + """Test significant states when significant_states_only is True.""" + now = dt_util.utcnow() + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "on", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "attr"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "changed"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "off", attributes={"any": "again"}) + await async_recorder_block_till_done(hass) + hass.states.async_set("sensor.test", "on", attributes={"any": "attr"}) + await async_wait_recording_done(hass) + + hist = history.get_significant_states( + hass, + now, + minimal_response=True, + significant_changes_only=False, + entity_ids=["sensor.test"], + ) + assert len(hist["sensor.test"]) == 3 + + +def record_states(hass) -> tuple[datetime, datetime, dict[str, list[State]]]: + """Record some test states. + + We inject a bunch of state updates from media player, zone and + thermostat. + """ + mp = "media_player.test" + mp2 = "media_player.test2" + mp3 = "media_player.test3" + therm = "thermostat.test" + therm2 = "thermostat.test2" + therm3 = "thermostat.test3" + zone = "zone.home" + script_c = "script.can_cancel_this_one" + + def set_state(entity_id, state, **kwargs): + """Set the state.""" + hass.states.set(entity_id, state, **kwargs) + wait_recording_done(hass) + return hass.states.get(entity_id) + + zero = dt_util.utcnow() + one = zero + timedelta(seconds=1) + two = one + timedelta(seconds=1) + three = two + timedelta(seconds=1) + four = three + timedelta(seconds=1) + + states = {therm: [], therm2: [], therm3: [], mp: [], mp2: [], mp3: [], script_c: []} + with freeze_time(one) as freezer: + states[mp].append( + set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) + ) + states[mp2].append( + set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) + ) + states[mp3].append( + set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) + ) + states[therm].append( + set_state(therm, 20, attributes={"current_temperature": 19.5}) + ) + # This state will be updated + set_state(therm3, 20, attributes={"current_temperature": 19.5}) + + freezer.move_to(one + timedelta(microseconds=1)) + states[mp].append( + set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) + ) + + freezer.move_to(two) + # This state will be skipped only different in time + set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) + # This state will be skipped because domain is excluded + set_state(zone, "zoning") + states[script_c].append( + set_state(script_c, "off", attributes={"can_cancel": True}) + ) + states[therm].append( + set_state(therm, 21, attributes={"current_temperature": 19.8}) + ) + states[therm2].append( + set_state(therm2, 20, attributes={"current_temperature": 19}) + ) + # This state will be updated + set_state(therm3, 20, attributes={"current_temperature": 19.5}) + + freezer.move_to(three) + states[mp].append( + set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) + ) + states[mp3].append( + set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) + ) + # Attributes changed even though state is the same + states[therm].append( + set_state(therm, 21, attributes={"current_temperature": 20}) + ) + states[therm3].append( + set_state(therm3, 20, attributes={"current_temperature": 19.5}) + ) + + return zero, four, states + + +async def test_state_changes_during_period_query_during_migration_to_schema_25( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, + recorder_db_url: str, +) -> None: + """Test we can query data prior to schema 25 and during migration to schema 25.""" + if recorder_db_url.startswith(("mysql://", "postgresql://")): + # This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop table state_attributes + return + + instance = await async_setup_recorder_instance(hass, {}) + + with patch.object(instance.states_meta_manager, "active", False): + start = dt_util.utcnow() + point = start + timedelta(seconds=1) + end = point + timedelta(seconds=1) + entity_id = "light.test" + await recorder.get_instance(hass).async_add_executor_job( + _add_db_entries, hass, point, [entity_id] + ) + + no_attributes = True + hist = history.state_changes_during_period( + hass, start, end, entity_id, no_attributes, include_start_time_state=False + ) + state = hist[entity_id][0] + assert state.attributes == {} + + no_attributes = False + hist = history.state_changes_during_period( + hass, start, end, entity_id, no_attributes, include_start_time_state=False + ) + state = hist[entity_id][0] + assert state.attributes == {"name": "the shared light"} + + with instance.engine.connect() as conn: + conn.execute(text("update states set attributes_id=NULL;")) + conn.execute(text("drop table state_attributes;")) + conn.commit() + + with patch.object(instance, "schema_version", 24): + instance.states_meta_manager.active = False + no_attributes = True + hist = history.state_changes_during_period( + hass, + start, + end, + entity_id, + no_attributes, + include_start_time_state=False, + ) + state = hist[entity_id][0] + assert state.attributes == {} + + no_attributes = False + hist = history.state_changes_during_period( + hass, + start, + end, + entity_id, + no_attributes, + include_start_time_state=False, + ) + state = hist[entity_id][0] + assert state.attributes == {"name": "the light"} + + +async def test_get_states_query_during_migration_to_schema_25( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, + recorder_db_url: str, +) -> None: + """Test we can query data prior to schema 25 and during migration to schema 25.""" + if recorder_db_url.startswith(("mysql://", "postgresql://")): + # This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop table state_attributes + return + + instance = await async_setup_recorder_instance(hass, {}) + + start = dt_util.utcnow() + point = start + timedelta(seconds=1) + end = point + timedelta(seconds=1) + entity_id = "light.test" + await instance.async_add_executor_job(_add_db_entries, hass, point, [entity_id]) + assert instance.states_meta_manager.active + + no_attributes = True + hist = await _async_get_states(hass, end, [entity_id], no_attributes=no_attributes) + state = hist[0] + assert state.attributes == {} + + no_attributes = False + hist = await _async_get_states(hass, end, [entity_id], no_attributes=no_attributes) + state = hist[0] + assert state.attributes == {"name": "the shared light"} + + with instance.engine.connect() as conn: + conn.execute(text("update states set attributes_id=NULL;")) + conn.execute(text("drop table state_attributes;")) + conn.commit() + + with patch.object(instance, "schema_version", 24): + instance.states_meta_manager.active = False + no_attributes = True + hist = await _async_get_states( + hass, end, [entity_id], no_attributes=no_attributes + ) + state = hist[0] + assert state.attributes == {} + + no_attributes = False + hist = await _async_get_states( + hass, end, [entity_id], no_attributes=no_attributes + ) + state = hist[0] + assert state.attributes == {"name": "the light"} + + +async def test_get_states_query_during_migration_to_schema_25_multiple_entities( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, + recorder_db_url: str, +) -> None: + """Test we can query data prior to schema 25 and during migration to schema 25.""" + if recorder_db_url.startswith(("mysql://", "postgresql://")): + # This test doesn't run on MySQL / MariaDB / Postgresql; we can't drop table state_attributes + return + + instance = await async_setup_recorder_instance(hass, {}) + + start = dt_util.utcnow() + point = start + timedelta(seconds=1) + end = point + timedelta(seconds=1) + entity_id_1 = "light.test" + entity_id_2 = "switch.test" + entity_ids = [entity_id_1, entity_id_2] + + await instance.async_add_executor_job(_add_db_entries, hass, point, entity_ids) + assert instance.states_meta_manager.active + + no_attributes = True + hist = await _async_get_states(hass, end, entity_ids, no_attributes=no_attributes) + assert hist[0].attributes == {} + assert hist[1].attributes == {} + + no_attributes = False + hist = await _async_get_states(hass, end, entity_ids, no_attributes=no_attributes) + assert hist[0].attributes == {"name": "the shared light"} + assert hist[1].attributes == {"name": "the shared light"} + + with instance.engine.connect() as conn: + conn.execute(text("update states set attributes_id=NULL;")) + conn.execute(text("drop table state_attributes;")) + conn.commit() + + with patch.object(instance, "schema_version", 24): + instance.states_meta_manager.active = False + no_attributes = True + hist = await _async_get_states( + hass, end, entity_ids, no_attributes=no_attributes + ) + assert hist[0].attributes == {} + assert hist[1].attributes == {} + + no_attributes = False + hist = await _async_get_states( + hass, end, entity_ids, no_attributes=no_attributes + ) + assert hist[0].attributes == {"name": "the light"} + assert hist[1].attributes == {"name": "the light"} + + +async def test_get_full_significant_states_handles_empty_last_changed( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, +) -> None: + """Test getting states when last_changed is null.""" + await async_setup_recorder_instance(hass, {}) + + now = dt_util.utcnow() + hass.states.async_set("sensor.one", "on", {"attr": "original"}) + state0 = hass.states.get("sensor.one") + await hass.async_block_till_done() + hass.states.async_set("sensor.one", "on", {"attr": "new"}) + state1 = hass.states.get("sensor.one") + + assert state0.last_changed == state1.last_changed + assert state0.last_updated != state1.last_updated + await async_wait_recording_done(hass) + + def _get_entries(): + with session_scope(hass=hass, read_only=True) as session: + return history.get_full_significant_states_with_session( + hass, + session, + now, + dt_util.utcnow(), + entity_ids=["sensor.one"], + significant_changes_only=False, + ) + + states = await recorder.get_instance(hass).async_add_executor_job(_get_entries) + sensor_one_states: list[State] = states["sensor.one"] + assert_states_equal_without_context(sensor_one_states[0], state0) + assert_states_equal_without_context(sensor_one_states[1], state1) + assert sensor_one_states[0].last_changed == sensor_one_states[1].last_changed + assert sensor_one_states[0].last_updated != sensor_one_states[1].last_updated + + def _fetch_native_states() -> list[State]: + with session_scope(hass=hass, read_only=True) as session: + native_states = [] + db_state_attributes = { + state_attributes.attributes_id: state_attributes + for state_attributes in session.query(StateAttributes) + } + metadata_id_to_entity_id = { + states_meta.metadata_id: states_meta + for states_meta in session.query(StatesMeta) + } + for db_state in session.query(States): + db_state.entity_id = metadata_id_to_entity_id[ + db_state.metadata_id + ].entity_id + state = db_state.to_native() + state.attributes = db_state_attributes[ + db_state.attributes_id + ].to_native() + native_states.append(state) + return native_states + + native_sensor_one_states = await recorder.get_instance(hass).async_add_executor_job( + _fetch_native_states + ) + assert_states_equal_without_context(native_sensor_one_states[0], state0) + assert_states_equal_without_context(native_sensor_one_states[1], state1) + assert ( + native_sensor_one_states[0].last_changed + == native_sensor_one_states[1].last_changed + ) + assert ( + native_sensor_one_states[0].last_updated + != native_sensor_one_states[1].last_updated + ) + + def _fetch_db_states() -> list[States]: + with session_scope(hass=hass, read_only=True) as session: + states = list(session.query(States)) + session.expunge_all() + return states + + db_sensor_one_states = await recorder.get_instance(hass).async_add_executor_job( + _fetch_db_states + ) + assert db_sensor_one_states[0].last_changed is None + assert db_sensor_one_states[0].last_changed_ts is None + + assert ( + process_timestamp( + dt_util.utc_from_timestamp(db_sensor_one_states[1].last_changed_ts) + ) + == state0.last_changed + ) + assert db_sensor_one_states[0].last_updated_ts is not None + assert db_sensor_one_states[1].last_updated_ts is not None + assert ( + db_sensor_one_states[0].last_updated_ts + != db_sensor_one_states[1].last_updated_ts + ) + + +def test_state_changes_during_period_multiple_entities_single_test( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test state change during period with multiple entities in the same test. + + This test ensures the sqlalchemy query cache does not + generate incorrect results. + """ + hass = hass_recorder() + start = dt_util.utcnow() + test_entites = {f"sensor.{i}": str(i) for i in range(30)} + for entity_id, value in test_entites.items(): + hass.states.set(entity_id, value) + + wait_recording_done(hass) + end = dt_util.utcnow() + + for entity_id, value in test_entites.items(): + hist = history.state_changes_during_period(hass, start, end, entity_id) + assert len(hist) == 1 + assert hist[entity_id][0].state == value + + +@pytest.mark.freeze_time("2039-01-19 03:14:07.555555-00:00") +async def test_get_full_significant_states_past_year_2038( + async_setup_recorder_instance: RecorderInstanceGenerator, + hass: HomeAssistant, +) -> None: + """Test we can store times past year 2038.""" + await async_setup_recorder_instance(hass, {}) + past_2038_time = dt_util.parse_datetime("2039-01-19 03:14:07.555555-00:00") + hass.states.async_set("sensor.one", "on", {"attr": "original"}) + state0 = hass.states.get("sensor.one") + await hass.async_block_till_done() + + hass.states.async_set("sensor.one", "on", {"attr": "new"}) + state1 = hass.states.get("sensor.one") + + await async_wait_recording_done(hass) + + def _get_entries(): + with session_scope(hass=hass, read_only=True) as session: + return history.get_full_significant_states_with_session( + hass, + session, + past_2038_time - timedelta(days=365), + past_2038_time + timedelta(days=365), + entity_ids=["sensor.one"], + significant_changes_only=False, + ) + + states = await recorder.get_instance(hass).async_add_executor_job(_get_entries) + sensor_one_states: list[State] = states["sensor.one"] + assert_states_equal_without_context(sensor_one_states[0], state0) + assert_states_equal_without_context(sensor_one_states[1], state1) + assert sensor_one_states[0].last_changed == past_2038_time + assert sensor_one_states[0].last_updated == past_2038_time + + +def test_get_significant_states_without_entity_ids_raises( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test at least one entity id is required for get_significant_states.""" + hass = hass_recorder() + now = dt_util.utcnow() + with pytest.raises(ValueError, match="entity_ids must be provided"): + history.get_significant_states(hass, now, None) + + +def test_state_changes_during_period_without_entity_ids_raises( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test at least one entity id is required for state_changes_during_period.""" + hass = hass_recorder() + now = dt_util.utcnow() + with pytest.raises(ValueError, match="entity_id must be provided"): + history.state_changes_during_period(hass, now, None) + + +def test_get_significant_states_with_filters_raises( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test passing filters is no longer supported.""" + hass = hass_recorder() + now = dt_util.utcnow() + with pytest.raises(NotImplementedError, match="Filters are no longer supported"): + history.get_significant_states( + hass, now, None, ["media_player.test"], Filters() + ) + + +def test_get_significant_states_with_non_existent_entity_ids_returns_empty( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test get_significant_states returns an empty dict when entities not in the db.""" + hass = hass_recorder() + now = dt_util.utcnow() + assert history.get_significant_states(hass, now, None, ["nonexistent.entity"]) == {} + + +def test_state_changes_during_period_with_non_existent_entity_ids_returns_empty( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test state_changes_during_period returns an empty dict when entities not in the db.""" + hass = hass_recorder() + now = dt_util.utcnow() + assert ( + history.state_changes_during_period(hass, now, None, "nonexistent.entity") == {} + ) + + +def test_get_last_state_changes_with_non_existent_entity_ids_returns_empty( + hass_recorder: Callable[..., HomeAssistant], +) -> None: + """Test get_last_state_changes returns an empty dict when entities not in the db.""" + hass = hass_recorder() + assert history.get_last_state_changes(hass, 1, "nonexistent.entity") == {} From 53cc4b8c37feae46f89cf1c0c09ea9e4e5eb8f30 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 27 Mar 2024 22:52:02 +0100 Subject: [PATCH 1596/1691] Download translations only once in the build pipeline (#114335) --- .github/workflows/builder.yml | 63 ++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 1dc6f7a3938..5dc01eee21e 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -51,6 +51,32 @@ jobs: with: ignore-dev: true + - name: Fail if translations files are checked in + run: | + files=$(find homeassistant/components/*/translations -type f) + + if [ -n "$files" ]; then + echo "Translations files are checked in, please remove the following files:" + echo "$files" + exit 1 + fi + + - name: Download Translations + run: python3 -m script.translations download + env: + LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} + + - name: Archive translations + shell: bash + run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T - + + - name: Upload translations + uses: actions/upload-artifact@v4.3.1 + with: + name: translations + path: translations.tar.gz + if-no-files-found: error + build_base: name: Build ${{ matrix.arch }} base core image if: github.repository_owner == 'home-assistant' @@ -159,10 +185,15 @@ jobs: # are not available. sed -i "s|aiohttp-zlib-ng|aiohttp-zlib-ng\[isal\]|g" requirements_all.txt - - name: Download Translations - run: python3 -m script.translations download - env: - LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} + - name: Download translations + uses: actions/download-artifact@v4.1.4 + with: + name: translations + + - name: Extract translations + run: | + tar xvf translations.tar.gz + rm translations.tar.gz - name: Write meta info file shell: bash @@ -186,17 +217,6 @@ jobs: --target /data \ --generic ${{ needs.init.outputs.version }} - - name: Archive translations - shell: bash - run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T - - - - name: Upload translations - uses: actions/upload-artifact@v3 - with: - name: translations - path: translations.tar.gz - if-no-files-found: error - build_machine: name: Build ${{ matrix.machine }} machine core image if: github.repository_owner == 'home-assistant' @@ -448,10 +468,15 @@ jobs: with: python-version: ${{ env.DEFAULT_PYTHON }} - - name: Download Translations - run: python3 -m script.translations download - env: - LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} + - name: Download translations + uses: actions/download-artifact@v4.1.4 + with: + name: translations + + - name: Extract translations + run: | + tar xvf translations.tar.gz + rm translations.tar.gz - name: Build package shell: bash From 824d6afa249fcffaa75c3053f401a7305c300d12 Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 27 Mar 2024 22:33:06 +0100 Subject: [PATCH 1597/1691] Remove checked in translations (#114336) --- .../components/devialet/translations/en.json | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 homeassistant/components/devialet/translations/en.json diff --git a/homeassistant/components/devialet/translations/en.json b/homeassistant/components/devialet/translations/en.json deleted file mode 100644 index af0cfc4c122..00000000000 --- a/homeassistant/components/devialet/translations/en.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Service is already configured" - }, - "error": { - "cannot_connect": "Failed to connect" - }, - "flow_title": "{title}", - "step": { - "confirm": { - "description": "Do you want to set up Devialet device {device}?" - }, - "user": { - "data": { - "host": "Host" - }, - "description": "Please enter the host name or IP address of the Devialet device." - } - } - } -} \ No newline at end of file From 541a6c5f64dad743dec43f07cd0f745462f840db Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 Mar 2024 20:58:07 -1000 Subject: [PATCH 1598/1691] Revert velocity change in powerview (#114337) --- .../components/hunterdouglas_powerview/number.py | 2 +- .../hunterdouglas_powerview/shade_data.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/number.py b/homeassistant/components/hunterdouglas_powerview/number.py index 8551a11337e..b37331c08df 100644 --- a/homeassistant/components/hunterdouglas_powerview/number.py +++ b/homeassistant/components/hunterdouglas_powerview/number.py @@ -41,7 +41,7 @@ def store_velocity( value: float | None, ) -> None: """Store the desired shade velocity in the coordinator.""" - coordinator.data.update_shade_position(shade_id, ShadePosition(velocity=value)) + coordinator.data.update_shade_velocity(shade_id, ShadePosition(velocity=value)) NUMBERS: Final = ( diff --git a/homeassistant/components/hunterdouglas_powerview/shade_data.py b/homeassistant/components/hunterdouglas_powerview/shade_data.py index e6b20312f27..fd2f0466467 100644 --- a/homeassistant/components/hunterdouglas_powerview/shade_data.py +++ b/homeassistant/components/hunterdouglas_powerview/shade_data.py @@ -13,14 +13,11 @@ from .util import async_map_data_by_id _LOGGER = logging.getLogger(__name__) -POSITION_FIELDS = fields(ShadePosition) +POSITION_FIELDS = [field for field in fields(ShadePosition) if field.name != "velocity"] def copy_position_data(source: ShadePosition, target: ShadePosition) -> ShadePosition: """Copy position data from source to target for None values only.""" - # the hub will always return a velocity of 0 on initial connect, - # separate definition to store consistent value in HA - # this value is purely driven from HA for field in POSITION_FIELDS: if (value := getattr(source, field.name)) is not None: setattr(target, field.name, value) @@ -76,3 +73,11 @@ class PowerviewShadeData: def update_shade_position(self, shade_id: int, new_position: ShadePosition) -> None: """Update a single shades position.""" copy_position_data(new_position, self.get_shade_position(shade_id)) + + def update_shade_velocity(self, shade_id: int, shade_data: ShadePosition) -> None: + """Update a single shades velocity.""" + # the hub will always return a velocity of 0 on initial connect, + # separate definition to store consistent value in HA + # this value is purely driven from HA + if shade_data.velocity is not None: + self.get_shade_position(shade_id).velocity = shade_data.velocity From 04bfb1de3cb3e826a7c534327cb661e9e5a929db Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 27 Mar 2024 17:19:34 -0500 Subject: [PATCH 1599/1691] Add more Ollama models (#114339) Add more models --- homeassistant/components/ollama/const.py | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/homeassistant/components/ollama/const.py b/homeassistant/components/ollama/const.py index 59f1888cfc7..853370066dc 100644 --- a/homeassistant/components/ollama/const.py +++ b/homeassistant/components/ollama/const.py @@ -110,5 +110,46 @@ MODEL_NAMES = [ # https://ollama.com/library "starcoder", "phind-codellama", "starcoder2", + "yi", + "orca2", + "falcon", + "wizard-math", + "dolphin-phi", + "starling-lm", + "nous-hermes", + "stable-code", + "medllama2", + "bakllava", + "codeup", + "wizardlm-uncensored", + "solar", + "everythinglm", + "sqlcoder", + "dolphincoder", + "nous-hermes2-mixtral", + "stable-beluga", + "yarn-mistral", + "stablelm2", + "samantha-mistral", + "meditron", + "stablelm-zephyr", + "magicoder", + "yarn-llama2", + "llama-pro", + "deepseek-llm", + "wizard-vicuna", + "codebooga", + "mistrallite", + "all-minilm", + "nexusraven", + "open-orca-platypus2", + "goliath", + "notux", + "megadolphin", + "alfred", + "xwinlm", + "wizardlm", + "duckdb-nsql", + "notus", ] DEFAULT_MODEL = "llama2:latest" From f141be73c77619e39a9dd576f34fd5be4f16e4cc Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 28 Mar 2024 06:57:02 +0100 Subject: [PATCH 1600/1691] Bump fjaraskupan to 2.3.0 (#114344) Update fjarakupen to 2.3.0 - Support delayed disconnection - Speed up on/off transitions --- homeassistant/components/fjaraskupan/light.py | 5 +++-- homeassistant/components/fjaraskupan/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/fjaraskupan/light.py b/homeassistant/components/fjaraskupan/light.py index 7f33d7806ee..b33904c805d 100644 --- a/homeassistant/components/fjaraskupan/light.py +++ b/homeassistant/components/fjaraskupan/light.py @@ -54,13 +54,14 @@ class Light(CoordinatorEntity[FjaraskupanCoordinator], LightEntity): async with self.coordinator.async_connect_and_update() as device: if ATTR_BRIGHTNESS in kwargs: await device.send_dim(int(kwargs[ATTR_BRIGHTNESS] * (100.0 / 255.0))) - elif not self.is_on: - await device.send_command(COMMAND_LIGHT_ON_OFF) + else: + await device.send_dim(100) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" if self.is_on: async with self.coordinator.async_connect_and_update() as device: + await device.send_dim(0) await device.send_command(COMMAND_LIGHT_ON_OFF) @property diff --git a/homeassistant/components/fjaraskupan/manifest.json b/homeassistant/components/fjaraskupan/manifest.json index f7ad701a756..91c74b68e01 100644 --- a/homeassistant/components/fjaraskupan/manifest.json +++ b/homeassistant/components/fjaraskupan/manifest.json @@ -14,5 +14,5 @@ "documentation": "https://www.home-assistant.io/integrations/fjaraskupan", "iot_class": "local_polling", "loggers": ["bleak", "fjaraskupan"], - "requirements": ["fjaraskupan==2.2.0"] + "requirements": ["fjaraskupan==2.3.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index a93913d7272..810a73b7fc7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -864,7 +864,7 @@ fivem-api==0.1.2 fixerio==1.0.0a0 # homeassistant.components.fjaraskupan -fjaraskupan==2.2.0 +fjaraskupan==2.3.0 # homeassistant.components.flexit_bacnet flexit_bacnet==2.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 106b8debcdf..6f67e8c8b12 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -702,7 +702,7 @@ fitbit==0.3.1 fivem-api==0.1.2 # homeassistant.components.fjaraskupan -fjaraskupan==2.2.0 +fjaraskupan==2.3.0 # homeassistant.components.flexit_bacnet flexit_bacnet==2.1.0 From f204faf20203b333789ba84b2535191200521dc8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 Mar 2024 18:29:43 -1000 Subject: [PATCH 1601/1691] Fix empty delays in script helper (#114346) fixes ``` Logger: homeassistant.components.automation.kamermaster_knop_4_acties_licht Bron: components/automation/__init__.py:726 integratie: Automatisering (documentatie, problemen) Eerst voorgekomen: 22:17:29 (5 gebeurtenissen) Laatst gelogd: 22:59:24 While executing automation automation.kamermaster_knop_4_acties_licht Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/automation/__init__.py", line 726, in async_trigger return await self.action_script.async_run( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1645, in async_run return await asyncio.shield(create_eager_task(run.async_run())) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 454, in async_run await self._async_step(log_exceptions=False) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 506, in _async_step self._handle_exception( File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 536, in _handle_exception raise exception File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 504, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 626, in _async_delay_step if timeout_future.done(): ^^^^^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'done' ``` --- homeassistant/helpers/script.py | 5 +++++ tests/helpers/test_script.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 560f3227c4f..a86df259f11 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -615,6 +615,11 @@ class _ScriptRun: delay = delay_delta.total_seconds() self._changed() + if not delay: + # Handle an empty delay + trace_set_result(delay=delay, done=True) + return + trace_set_result(delay=delay, done=False) futures, timeout_handle, timeout_future = self._async_futures_with_timeout( delay diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index c1462ccfc2f..86fb84eb582 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -672,6 +672,31 @@ async def test_delay_basic(hass: HomeAssistant) -> None: ) +async def test_empty_delay(hass: HomeAssistant) -> None: + """Test an empty delay.""" + delay_alias = "delay step" + sequence = cv.SCRIPT_SCHEMA({"delay": {"seconds": 0}, "alias": delay_alias}) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + delay_started_flag = async_watch_for_action(script_obj, delay_alias) + + try: + await script_obj.async_run(context=Context()) + await asyncio.wait_for(delay_started_flag.wait(), 1) + except (AssertionError, TimeoutError): + await script_obj.async_stop() + raise + else: + await hass.async_block_till_done() + assert not script_obj.is_running + assert script_obj.last_action is None + + assert_action_trace( + { + "0": [{"result": {"delay": 0.0, "done": True}}], + } + ) + + async def test_multiple_runs_delay(hass: HomeAssistant) -> None: """Test multiple runs with delay in script.""" event = "test_event" From 737e5e70ec2df9576e807f309711d28f49f7ac21 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Thu, 28 Mar 2024 13:48:51 -0400 Subject: [PATCH 1602/1691] Bump pyunifiprotect to 5.1.2 (#114348) --- homeassistant/components/unifiprotect/config_flow.py | 3 ++- homeassistant/components/unifiprotect/manifest.json | 2 +- homeassistant/components/unifiprotect/utils.py | 3 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 555ddcb8d5e..19561a6003d 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -261,7 +261,8 @@ class ProtectFlowHandler(ConfigFlow, domain=DOMAIN): username=user_input[CONF_USERNAME], password=user_input[CONF_PASSWORD], verify_ssl=verify_ssl, - cache_dir=Path(self.hass.config.path(STORAGE_DIR, "unifiprotect_cache")), + cache_dir=Path(self.hass.config.path(STORAGE_DIR, "unifiprotect")), + config_dir=Path(self.hass.config.path(STORAGE_DIR, "unifiprotect")), ) errors = {} diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 7cfb0ddcc9e..a26fab2e80b 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -41,7 +41,7 @@ "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], "quality_scale": "platinum", - "requirements": ["pyunifiprotect==5.0.2", "unifi-discovery==1.1.8"], + "requirements": ["pyunifiprotect==5.1.2", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/components/unifiprotect/utils.py b/homeassistant/components/unifiprotect/utils.py index 58474e6a531..8199d729943 100644 --- a/homeassistant/components/unifiprotect/utils.py +++ b/homeassistant/components/unifiprotect/utils.py @@ -145,7 +145,8 @@ def async_create_api_client( override_connection_host=entry.options.get(CONF_OVERRIDE_CHOST, False), ignore_stats=not entry.options.get(CONF_ALL_UPDATES, False), ignore_unadopted=False, - cache_dir=Path(hass.config.path(STORAGE_DIR, "unifiprotect_cache")), + cache_dir=Path(hass.config.path(STORAGE_DIR, "unifiprotect")), + config_dir=Path(hass.config.path(STORAGE_DIR, "unifiprotect")), ) diff --git a/requirements_all.txt b/requirements_all.txt index 810a73b7fc7..ffa57398564 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2339,7 +2339,7 @@ pytrydan==0.4.0 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==5.0.2 +pyunifiprotect==5.1.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6f67e8c8b12..b35ef776b47 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1806,7 +1806,7 @@ pytrydan==0.4.0 pyudev==0.23.2 # homeassistant.components.unifiprotect -pyunifiprotect==5.0.2 +pyunifiprotect==5.1.2 # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 From 21bff95bd7ad7160802979791666f0643361173a Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Thu, 28 Mar 2024 09:11:02 +0100 Subject: [PATCH 1603/1691] Fix script for checking on existing translations (#114354) --- .github/workflows/builder.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 5dc01eee21e..217093793d1 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -53,11 +53,9 @@ jobs: - name: Fail if translations files are checked in run: | - files=$(find homeassistant/components/*/translations -type f) - - if [ -n "$files" ]; then + if [ -n "$(find homeassistant/components/*/translations -type f)" ]; then echo "Translations files are checked in, please remove the following files:" - echo "$files" + find homeassistant/components/*/translations -type f exit 1 fi From 42580a1113271911da4b953d9522efc116abeb74 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 28 Mar 2024 10:42:52 +0100 Subject: [PATCH 1604/1691] Improve utility meter restore state tests (#114356) --- tests/components/utility_meter/test_sensor.py | 91 +++++++++++-------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index c250a66b87a..13b367b1fb7 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -610,7 +610,7 @@ async def test_device_class( "utility_meter": { "energy_bill": { "source": "sensor.energy", - "tariffs": ["onpeak", "midpeak", "offpeak", "superpeak"], + "tariffs": ["tariff1", "tariff2", "tariff3", "tariff4"], } } }, @@ -626,7 +626,7 @@ async def test_device_class( "offset": 0, "periodically_resetting": True, "source": "sensor.energy", - "tariffs": ["onpeak", "midpeak", "offpeak", "superpeak"], + "tariffs": ["tariff1", "tariff2", "tariff3", "tariff4"], }, ), ], @@ -638,82 +638,89 @@ async def test_restore_state( # Home assistant is not runnit yet hass.set_state(CoreState.not_running) - last_reset = "2020-12-21T00:00:00.013073+00:00" + last_reset_1 = "2020-12-21T00:00:00.013073+00:00" + last_reset_2 = "2020-12-22T00:00:00.013073+00:00" mock_restore_cache_with_extra_data( hass, [ + # sensor.energy_bill_tariff1 is restored as expected ( State( - "sensor.energy_bill_onpeak", - "3", + "sensor.energy_bill_tariff1", + "1.1", attributes={ ATTR_STATUS: PAUSED, - ATTR_LAST_RESET: last_reset, - ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + ATTR_LAST_RESET: last_reset_1, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.MEGA_WATT_HOUR, }, ), { "native_value": { "__type": "", - "decimal_str": "3", + "decimal_str": "1.2", }, "native_unit_of_measurement": "kWh", - "last_reset": last_reset, - "last_period": "7", - "last_valid_state": "None", + "last_reset": last_reset_2, + "last_period": "1.3", + "last_valid_state": None, "status": "paused", }, ), + # sensor.energy_bill_tariff2 has missing keys and falls back to + # saved state ( State( - "sensor.energy_bill_midpeak", - "5", + "sensor.energy_bill_tariff2", + "2.1", attributes={ ATTR_STATUS: PAUSED, - ATTR_LAST_RESET: last_reset, + ATTR_LAST_RESET: last_reset_1, ATTR_LAST_VALID_STATE: None, - ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.MEGA_WATT_HOUR, }, ), { "native_value": { "__type": "", - "decimal_str": "3", + "decimal_str": "2.2", }, "native_unit_of_measurement": "kWh", "last_valid_state": "None", }, ), + # sensor.energy_bill_tariff3 has invalid data and falls back to + # saved state ( State( - "sensor.energy_bill_offpeak", - "6", + "sensor.energy_bill_tariff3", + "3.1", attributes={ ATTR_STATUS: COLLECTING, - ATTR_LAST_RESET: last_reset, + ATTR_LAST_RESET: last_reset_1, ATTR_LAST_VALID_STATE: None, - ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.MEGA_WATT_HOUR, }, ), { "native_value": { "__type": "", - "decimal_str": "3f", + "decimal_str": "3f", # Invalid }, "native_unit_of_measurement": "kWh", "last_valid_state": "None", }, ), + # No extra saved data, fall back to saved state ( State( - "sensor.energy_bill_superpeak", + "sensor.energy_bill_tariff4", "error", attributes={ ATTR_STATUS: COLLECTING, - ATTR_LAST_RESET: last_reset, + ATTR_LAST_RESET: last_reset_1, ATTR_LAST_VALID_STATE: None, - ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.MEGA_WATT_HOUR, }, ), {}, @@ -736,25 +743,28 @@ async def test_restore_state( await hass.async_block_till_done() # restore from cache - state = hass.states.get("sensor.energy_bill_onpeak") - assert state.state == "3" + state = hass.states.get("sensor.energy_bill_tariff1") + assert state.state == "1.2" assert state.attributes.get("status") == PAUSED - assert state.attributes.get("last_reset") == last_reset + assert state.attributes.get("last_reset") == last_reset_2 assert state.attributes.get("last_valid_state") == "None" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.KILO_WATT_HOUR - state = hass.states.get("sensor.energy_bill_midpeak") - assert state.state == "5" + state = hass.states.get("sensor.energy_bill_tariff2") + assert state.state == "2.1" + assert state.attributes.get("status") == PAUSED + assert state.attributes.get("last_reset") == last_reset_1 assert state.attributes.get("last_valid_state") == "None" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.MEGA_WATT_HOUR - state = hass.states.get("sensor.energy_bill_offpeak") - assert state.state == "6" + state = hass.states.get("sensor.energy_bill_tariff3") + assert state.state == "3.1" assert state.attributes.get("status") == COLLECTING - assert state.attributes.get("last_reset") == last_reset + assert state.attributes.get("last_reset") == last_reset_1 assert state.attributes.get("last_valid_state") == "None" - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.KILO_WATT_HOUR + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.MEGA_WATT_HOUR - state = hass.states.get("sensor.energy_bill_superpeak") + state = hass.states.get("sensor.energy_bill_tariff4") assert state.state == STATE_UNKNOWN # utility_meter is loaded, now set sensors according to utility_meter: @@ -764,13 +774,18 @@ async def test_restore_state( await hass.async_block_till_done() state = hass.states.get("select.energy_bill") - assert state.state == "onpeak" + assert state.state == "tariff1" - state = hass.states.get("sensor.energy_bill_onpeak") + state = hass.states.get("sensor.energy_bill_tariff1") assert state.attributes.get("status") == COLLECTING - state = hass.states.get("sensor.energy_bill_offpeak") - assert state.attributes.get("status") == PAUSED + for entity_id in ( + "sensor.energy_bill_tariff2", + "sensor.energy_bill_tariff3", + "sensor.energy_bill_tariff4", + ): + state = hass.states.get(entity_id) + assert state.attributes.get("status") == PAUSED @pytest.mark.parametrize( From b143390d8802b0ab69136785ebd3b5d44db78211 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 28 Mar 2024 13:24:44 +0100 Subject: [PATCH 1605/1691] Improve device class of utility meter (#114368) --- .../components/utility_meter/sensor.py | 37 +++-- tests/components/utility_meter/test_sensor.py | 138 +++++++++++++++--- 2 files changed, 146 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 4e9be403cf7..26582df1b44 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Mapping from dataclasses import dataclass from datetime import datetime, timedelta from decimal import Decimal, DecimalException, InvalidOperation @@ -13,6 +14,7 @@ import voluptuous as vol from homeassistant.components.sensor import ( ATTR_LAST_RESET, + DEVICE_CLASS_UNITS, RestoreSensor, SensorDeviceClass, SensorExtraStoredData, @@ -21,12 +23,12 @@ from homeassistant.components.sensor import ( from homeassistant.components.sensor.recorder import _suggest_report_issue from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + ATTR_DEVICE_CLASS, ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_UNIQUE_ID, STATE_UNAVAILABLE, STATE_UNKNOWN, - UnitOfEnergy, ) from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers import ( @@ -47,6 +49,7 @@ from homeassistant.helpers.template import is_number from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import slugify import homeassistant.util.dt as dt_util +from homeassistant.util.enum import try_parse_enum from .const import ( ATTR_CRON_PATTERN, @@ -97,12 +100,6 @@ ATTR_LAST_PERIOD = "last_period" ATTR_LAST_VALID_STATE = "last_valid_state" ATTR_TARIFF = "tariff" -DEVICE_CLASS_MAP = { - UnitOfEnergy.WATT_HOUR: SensorDeviceClass.ENERGY, - UnitOfEnergy.KILO_WATT_HOUR: SensorDeviceClass.ENERGY, -} - - PRECISION = 3 PAUSED = "paused" COLLECTING = "collecting" @@ -313,6 +310,7 @@ class UtilitySensorExtraStoredData(SensorExtraStoredData): last_reset: datetime | None last_valid_state: Decimal | None status: str + input_device_class: SensorDeviceClass | None def as_dict(self) -> dict[str, Any]: """Return a dict representation of the utility sensor data.""" @@ -324,6 +322,7 @@ class UtilitySensorExtraStoredData(SensorExtraStoredData): str(self.last_valid_state) if self.last_valid_state else None ) data["status"] = self.status + data["input_device_class"] = str(self.input_device_class) return data @@ -343,6 +342,9 @@ class UtilitySensorExtraStoredData(SensorExtraStoredData): else None ) status: str = restored["status"] + input_device_class = try_parse_enum( + SensorDeviceClass, restored.get("input_device_class") + ) except KeyError: # restored is a dict, but does not have all values return None @@ -357,6 +359,7 @@ class UtilitySensorExtraStoredData(SensorExtraStoredData): last_reset, last_valid_state, status, + input_device_class, ) @@ -397,6 +400,7 @@ class UtilityMeterSensor(RestoreSensor): self._last_valid_state = None self._collecting = None self._name = name + self._input_device_class = None self._unit_of_measurement = None self._period = meter_type if meter_type is not None: @@ -416,9 +420,10 @@ class UtilityMeterSensor(RestoreSensor): self._tariff = tariff self._tariff_entity = tariff_entity - def start(self, unit): + def start(self, attributes: Mapping[str, Any]) -> None: """Initialize unit and state upon source initial update.""" - self._unit_of_measurement = unit + self._input_device_class = attributes.get(ATTR_DEVICE_CLASS) + self._unit_of_measurement = attributes.get(ATTR_UNIT_OF_MEASUREMENT) self._state = 0 self.async_write_ha_state() @@ -482,6 +487,7 @@ class UtilityMeterSensor(RestoreSensor): new_state = event.data["new_state"] if new_state is None: return + new_state_attributes: Mapping[str, Any] = new_state.attributes or {} # First check if the new_state is valid (see discussion in PR #88446) if (new_state_val := self._validate_state(new_state)) is None: @@ -498,7 +504,7 @@ class UtilityMeterSensor(RestoreSensor): for sensor in self.hass.data[DATA_UTILITY][self._parent_meter][ DATA_TARIFF_SENSORS ]: - sensor.start(new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)) + sensor.start(new_state_attributes) if self._unit_of_measurement is None: _LOGGER.warning( "Source sensor %s has no unit of measurement. Please %s", @@ -512,7 +518,8 @@ class UtilityMeterSensor(RestoreSensor): # If net_consumption is off, the adjustment must be non-negative self._state += adjustment # type: ignore[operator] # self._state will be set to by the start function if it is None, therefore it always has a valid Decimal value at this line - self._unit_of_measurement = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + self._input_device_class = new_state_attributes.get(ATTR_DEVICE_CLASS) + self._unit_of_measurement = new_state_attributes.get(ATTR_UNIT_OF_MEASUREMENT) self._last_valid_state = new_state_val self.async_write_ha_state() @@ -600,6 +607,7 @@ class UtilityMeterSensor(RestoreSensor): if (last_sensor_data := await self.async_get_last_sensor_data()) is not None: # new introduced in 2022.04 self._state = last_sensor_data.native_value + self._input_device_class = last_sensor_data.input_device_class self._unit_of_measurement = last_sensor_data.native_unit_of_measurement self._last_period = last_sensor_data.last_period self._last_reset = last_sensor_data.last_reset @@ -693,7 +701,11 @@ class UtilityMeterSensor(RestoreSensor): @property def device_class(self): """Return the device class of the sensor.""" - return DEVICE_CLASS_MAP.get(self._unit_of_measurement) + if self._input_device_class is not None: + return self._input_device_class + if self._unit_of_measurement in DEVICE_CLASS_UNITS[SensorDeviceClass.ENERGY]: + return SensorDeviceClass.ENERGY + return None @property def state_class(self): @@ -744,6 +756,7 @@ class UtilityMeterSensor(RestoreSensor): self._last_reset, self._last_valid_state, PAUSED if self._collecting is None else COLLECTING, + self._input_device_class, ) async def async_get_last_sensor_data(self) -> UtilitySensorExtraStoredData | None: diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 13b367b1fb7..99a63809329 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -40,6 +40,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfEnergy, + UnitOfVolume, ) from homeassistant.core import CoreState, HomeAssistant, State from homeassistant.helpers import device_registry as dr, entity_registry as er @@ -553,8 +554,66 @@ async def test_entity_name(hass: HomeAssistant, yaml_config, entity_id, name) -> ), ], ) +@pytest.mark.parametrize( + ( + "energy_sensor_attributes", + "gas_sensor_attributes", + "energy_meter_attributes", + "gas_meter_attributes", + ), + [ + ( + {ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR}, + {ATTR_UNIT_OF_MEASUREMENT: "some_archaic_unit"}, + { + ATTR_DEVICE_CLASS: SensorDeviceClass.ENERGY, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + }, + { + ATTR_DEVICE_CLASS: None, + ATTR_UNIT_OF_MEASUREMENT: "some_archaic_unit", + }, + ), + ( + {}, + {}, + { + ATTR_DEVICE_CLASS: None, + ATTR_UNIT_OF_MEASUREMENT: None, + }, + { + ATTR_DEVICE_CLASS: None, + ATTR_UNIT_OF_MEASUREMENT: None, + }, + ), + ( + { + ATTR_DEVICE_CLASS: SensorDeviceClass.GAS, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + }, + { + ATTR_DEVICE_CLASS: SensorDeviceClass.WATER, + ATTR_UNIT_OF_MEASUREMENT: "some_archaic_unit", + }, + { + ATTR_DEVICE_CLASS: SensorDeviceClass.GAS, + ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR, + }, + { + ATTR_DEVICE_CLASS: SensorDeviceClass.WATER, + ATTR_UNIT_OF_MEASUREMENT: "some_archaic_unit", + }, + ), + ], +) async def test_device_class( - hass: HomeAssistant, yaml_config, config_entry_configs + hass: HomeAssistant, + yaml_config, + config_entry_configs, + energy_sensor_attributes, + gas_sensor_attributes, + energy_meter_attributes, + gas_meter_attributes, ) -> None: """Test utility device_class.""" if yaml_config: @@ -579,27 +638,23 @@ async def test_device_class( await hass.async_block_till_done() - hass.states.async_set( - entity_id_energy, 2, {ATTR_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR} - ) - hass.states.async_set( - entity_id_gas, 2, {ATTR_UNIT_OF_MEASUREMENT: "some_archaic_unit"} - ) + hass.states.async_set(entity_id_energy, 2, energy_sensor_attributes) + hass.states.async_set(entity_id_gas, 2, gas_sensor_attributes) await hass.async_block_till_done() state = hass.states.get("sensor.energy_meter") assert state is not None assert state.state == "0" - assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.KILO_WATT_HOUR + for attr, value in energy_meter_attributes.items(): + assert state.attributes.get(attr) == value state = hass.states.get("sensor.gas_meter") assert state is not None assert state.state == "0" - assert state.attributes.get(ATTR_DEVICE_CLASS) is None assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL_INCREASING - assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "some_archaic_unit" + for attr, value in gas_meter_attributes.items(): + assert state.attributes.get(attr) == value @pytest.mark.parametrize( @@ -610,7 +665,13 @@ async def test_device_class( "utility_meter": { "energy_bill": { "source": "sensor.energy", - "tariffs": ["tariff1", "tariff2", "tariff3", "tariff4"], + "tariffs": [ + "tariff0", + "tariff1", + "tariff2", + "tariff3", + "tariff4", + ], } } }, @@ -626,7 +687,13 @@ async def test_device_class( "offset": 0, "periodically_resetting": True, "source": "sensor.energy", - "tariffs": ["tariff1", "tariff2", "tariff3", "tariff4"], + "tariffs": [ + "tariff0", + "tariff1", + "tariff2", + "tariff3", + "tariff4", + ], }, ), ], @@ -644,7 +711,33 @@ async def test_restore_state( mock_restore_cache_with_extra_data( hass, [ - # sensor.energy_bill_tariff1 is restored as expected + # sensor.energy_bill_tariff0 is restored as expected, including device + # class + ( + State( + "sensor.energy_bill_tariff0", + "0.1", + attributes={ + ATTR_STATUS: PAUSED, + ATTR_LAST_RESET: last_reset_1, + ATTR_UNIT_OF_MEASUREMENT: UnitOfVolume.CUBIC_METERS, + }, + ), + { + "native_value": { + "__type": "", + "decimal_str": "0.2", + }, + "native_unit_of_measurement": "gal", + "last_reset": last_reset_2, + "last_period": "1.3", + "last_valid_state": None, + "status": "collecting", + "input_device_class": "water", + }, + ), + # sensor.energy_bill_tariff1 is restored as expected, except device + # class ( State( "sensor.energy_bill_tariff1", @@ -743,12 +836,21 @@ async def test_restore_state( await hass.async_block_till_done() # restore from cache + state = hass.states.get("sensor.energy_bill_tariff0") + assert state.state == "0.2" + assert state.attributes.get("status") == COLLECTING + assert state.attributes.get("last_reset") == last_reset_2 + assert state.attributes.get("last_valid_state") == "None" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfVolume.GALLONS + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.WATER + state = hass.states.get("sensor.energy_bill_tariff1") assert state.state == "1.2" assert state.attributes.get("status") == PAUSED assert state.attributes.get("last_reset") == last_reset_2 assert state.attributes.get("last_valid_state") == "None" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.KILO_WATT_HOUR + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY state = hass.states.get("sensor.energy_bill_tariff2") assert state.state == "2.1" @@ -756,6 +858,7 @@ async def test_restore_state( assert state.attributes.get("last_reset") == last_reset_1 assert state.attributes.get("last_valid_state") == "None" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.MEGA_WATT_HOUR + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY state = hass.states.get("sensor.energy_bill_tariff3") assert state.state == "3.1" @@ -763,6 +866,7 @@ async def test_restore_state( assert state.attributes.get("last_reset") == last_reset_1 assert state.attributes.get("last_valid_state") == "None" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfEnergy.MEGA_WATT_HOUR + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY state = hass.states.get("sensor.energy_bill_tariff4") assert state.state == STATE_UNKNOWN @@ -770,16 +874,16 @@ async def test_restore_state( # utility_meter is loaded, now set sensors according to utility_meter: hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) - await hass.async_block_till_done() state = hass.states.get("select.energy_bill") - assert state.state == "tariff1" + assert state.state == "tariff0" - state = hass.states.get("sensor.energy_bill_tariff1") + state = hass.states.get("sensor.energy_bill_tariff0") assert state.attributes.get("status") == COLLECTING for entity_id in ( + "sensor.energy_bill_tariff1", "sensor.energy_bill_tariff2", "sensor.energy_bill_tariff3", "sensor.energy_bill_tariff4", From 1c6689be41f0342b2b0918307242483c11128163 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:37:57 +0100 Subject: [PATCH 1606/1691] Update pytile to 2023.12.0 (#114370) --- homeassistant/components/tile/manifest.json | 2 +- pyproject.toml | 2 -- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/tile/manifest.json b/homeassistant/components/tile/manifest.json index 6f311fc5593..8dceddcb77f 100644 --- a/homeassistant/components/tile/manifest.json +++ b/homeassistant/components/tile/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "iot_class": "cloud_polling", "loggers": ["pytile"], - "requirements": ["pytile==2023.04.0"] + "requirements": ["pytile==2023.12.0"] } diff --git a/pyproject.toml b/pyproject.toml index c84405c2764..40b1f36a58b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -504,8 +504,6 @@ filterwarnings = [ # https://github.com/eclipse/paho.mqtt.python/issues/653 - >=2.0.0 # https://github.com/eclipse/paho.mqtt.python/pull/665 "ignore:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:paho.mqtt.client", - # https://github.com/bachya/pytile/pull/280 - >=2023.10.0 - "ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:pytile.tile", # https://github.com/rytilahti/python-miio/pull/1809 - >=0.6.0.dev0 "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.protocol", "ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.miioprotocol", diff --git a/requirements_all.txt b/requirements_all.txt index ffa57398564..37fcf49a5e5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2311,7 +2311,7 @@ python-vlc==3.0.18122 pythonegardia==1.0.52 # homeassistant.components.tile -pytile==2023.04.0 +pytile==2023.12.0 # homeassistant.components.tomorrowio pytomorrowio==0.3.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b35ef776b47..60ac93d37c5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1781,7 +1781,7 @@ python-technove==1.2.2 python-telegram-bot[socks]==21.0.1 # homeassistant.components.tile -pytile==2023.04.0 +pytile==2023.12.0 # homeassistant.components.tomorrowio pytomorrowio==0.3.6 From 5e0a0718e32b70c6f3d9c029f0a8a4e0c22d449e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 28 Mar 2024 11:09:59 +0100 Subject: [PATCH 1607/1691] Fix streamlabswater feedback (#114371) --- homeassistant/components/streamlabswater/__init__.py | 4 ++-- homeassistant/components/streamlabswater/strings.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/streamlabswater/__init__.py b/homeassistant/components/streamlabswater/__init__.py index c3bbe5a96d4..46acc443d2e 100644 --- a/homeassistant/components/streamlabswater/__init__.py +++ b/homeassistant/components/streamlabswater/__init__.py @@ -81,12 +81,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async_create_issue( hass, DOMAIN, - f"deprecated_yaml_import_issue_${result['reason']}", + f"deprecated_yaml_import_issue_{result['reason']}", breaks_in_ha_version="2024.7.0", is_fixable=False, issue_domain=DOMAIN, severity=IssueSeverity.WARNING, - translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", translation_placeholders=ISSUE_PLACEHOLDER, ) return True diff --git a/homeassistant/components/streamlabswater/strings.json b/homeassistant/components/streamlabswater/strings.json index 204f7e831ef..872a0d1f6ac 100644 --- a/homeassistant/components/streamlabswater/strings.json +++ b/homeassistant/components/streamlabswater/strings.json @@ -52,7 +52,7 @@ "issues": { "deprecated_yaml_import_issue_cannot_connect": { "title": "The Streamlabs water YAML configuration import failed", - "description": "Configuring Streamlabs water using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Streamlabs water works and restart Home Assistant to try again or remove the Streamlabs water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." + "description": "Configuring Streamlabs water using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to Streamlabs water works and restart Home Assistant to try again or remove the Streamlabs water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." }, "deprecated_yaml_import_issue_unknown": { "title": "The Streamlabs water YAML configuration import failed", From ba12652cbc663c2d6c398e299fdc5591cfde923b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 28 Mar 2024 11:10:46 +0100 Subject: [PATCH 1608/1691] Fix Suez water feedback (#114372) --- homeassistant/components/suez_water/sensor.py | 4 ++-- homeassistant/components/suez_water/strings.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/suez_water/sensor.py b/homeassistant/components/suez_water/sensor.py index 7060339250c..f48e78bb153 100644 --- a/homeassistant/components/suez_water/sensor.py +++ b/homeassistant/components/suez_water/sensor.py @@ -74,12 +74,12 @@ async def async_setup_platform( async_create_issue( hass, DOMAIN, - f"deprecated_yaml_import_issue_${result['reason']}", + f"deprecated_yaml_import_issue_{result['reason']}", breaks_in_ha_version="2024.7.0", is_fixable=False, issue_domain=DOMAIN, severity=IssueSeverity.WARNING, - translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", translation_placeholders=ISSUE_PLACEHOLDER, ) diff --git a/homeassistant/components/suez_water/strings.json b/homeassistant/components/suez_water/strings.json index b4b81a788b5..fd85565d297 100644 --- a/homeassistant/components/suez_water/strings.json +++ b/homeassistant/components/suez_water/strings.json @@ -32,7 +32,7 @@ }, "deprecated_yaml_import_issue_cannot_connect": { "title": "The Suez water YAML configuration import failed", - "description": "Configuring Suez water using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Suez water works and restart Home Assistant to try again or remove the Suez water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." + "description": "Configuring Suez water using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to Suez water works and restart Home Assistant to try again or remove the Suez water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually." }, "deprecated_yaml_import_issue_unknown": { "title": "The Suez water YAML configuration import failed", From 99282d27c6c031be948e6c420f53a4f205c74be5 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 28 Mar 2024 11:11:28 +0100 Subject: [PATCH 1609/1691] Fix Swiss public transport feedback (#114373) --- homeassistant/components/swiss_public_transport/sensor.py | 4 ++-- homeassistant/components/swiss_public_transport/strings.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 7c712c8c189..a4a9605a603 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -131,12 +131,12 @@ async def async_setup_platform( async_create_issue( hass, DOMAIN, - f"deprecated_yaml_import_issue_${result['reason']}", + f"deprecated_yaml_import_issue_{result['reason']}", breaks_in_ha_version="2024.7.0", is_fixable=False, issue_domain=DOMAIN, severity=IssueSeverity.WARNING, - translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", translation_placeholders=PLACEHOLDERS, ) diff --git a/homeassistant/components/swiss_public_transport/strings.json b/homeassistant/components/swiss_public_transport/strings.json index c0e88f08b8d..c080e785f2c 100644 --- a/homeassistant/components/swiss_public_transport/strings.json +++ b/homeassistant/components/swiss_public_transport/strings.json @@ -38,7 +38,7 @@ "issues": { "deprecated_yaml_import_issue_cannot_connect": { "title": "The swiss public transport YAML configuration import cannot connect to server", - "description": "Configuring swiss public transport using YAML is being removed but there was an connection error importing your YAML configuration.\n\nMake sure your home assistant can reach the [opendata server]({opendata_url}). In case the server is down, try again later." + "description": "Configuring swiss public transport using YAML is being removed but there was a connection error importing your YAML configuration.\n\nMake sure your home assistant can reach the [opendata server]({opendata_url}). In case the server is down, try again later." }, "deprecated_yaml_import_issue_bad_config": { "title": "The swiss public transport YAML configuration import request failed due to bad config", From 80273b4873a6ca9d02f64cb93154556eaac2445d Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 28 Mar 2024 11:12:02 +0100 Subject: [PATCH 1610/1691] Fix Lupusec feedback (#114374) --- homeassistant/components/lupusec/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lupusec/__init__.py b/homeassistant/components/lupusec/__init__.py index c471902813a..51bba44aef0 100644 --- a/homeassistant/components/lupusec/__init__.py +++ b/homeassistant/components/lupusec/__init__.py @@ -79,12 +79,12 @@ async def handle_async_init_result(hass: HomeAssistant, domain: str, conf: dict) async_create_issue( hass, DOMAIN, - f"deprecated_yaml_import_issue_${result['reason']}", + f"deprecated_yaml_import_issue_{result['reason']}", breaks_in_ha_version="2024.8.0", is_fixable=False, issue_domain=DOMAIN, severity=IssueSeverity.WARNING, - translation_key=f"deprecated_yaml_import_issue_${result['reason']}", + translation_key=f"deprecated_yaml_import_issue_{result['reason']}", translation_placeholders=ISSUE_PLACEHOLDER, ) From aa301942493c8f5c81ea7c6b0b1711a6b9bdf15a Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 28 Mar 2024 13:25:01 +0100 Subject: [PATCH 1611/1691] Adapt Tractive integration the latest API changes (#114380) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/tractive/__init__.py | 15 ++------------- homeassistant/components/tractive/const.py | 1 - homeassistant/components/tractive/sensor.py | 5 ++--- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/tractive/__init__.py b/homeassistant/components/tractive/__init__.py index 41e691f783e..136e8b3632a 100644 --- a/homeassistant/components/tractive/__init__.py +++ b/homeassistant/components/tractive/__init__.py @@ -40,7 +40,6 @@ from .const import ( SERVER_UNAVAILABLE, SWITCH_KEY_MAP, TRACKABLES, - TRACKER_ACTIVITY_STATUS_UPDATED, TRACKER_HARDWARE_STATUS_UPDATED, TRACKER_POSITION_UPDATED, TRACKER_SWITCH_STATUS_UPDATED, @@ -220,9 +219,6 @@ class TractiveClient: if server_was_unavailable: _LOGGER.debug("Tractive is back online") server_was_unavailable = False - if event["message"] == "activity_update": - self._send_activity_update(event) - continue if event["message"] == "wellness_overview": self._send_wellness_update(event) continue @@ -291,15 +287,6 @@ class TractiveClient: TRACKER_SWITCH_STATUS_UPDATED, event["tracker_id"], payload ) - def _send_activity_update(self, event: dict[str, Any]) -> None: - payload = { - ATTR_MINUTES_ACTIVE: event["progress"]["achieved_minutes"], - ATTR_DAILY_GOAL: event["progress"]["goal_minutes"], - } - self._dispatch_tracker_event( - TRACKER_ACTIVITY_STATUS_UPDATED, event["pet_id"], payload - ) - def _send_wellness_update(self, event: dict[str, Any]) -> None: sleep_day = None sleep_night = None @@ -309,6 +296,8 @@ class TractiveClient: payload = { ATTR_ACTIVITY_LABEL: event["wellness"].get("activity_label"), ATTR_CALORIES: event["activity"]["calories"], + ATTR_DAILY_GOAL: event["activity"]["minutes_goal"], + ATTR_MINUTES_ACTIVE: event["activity"]["minutes_active"], ATTR_MINUTES_DAY_SLEEP: sleep_day, ATTR_MINUTES_NIGHT_SLEEP: sleep_night, ATTR_MINUTES_REST: event["activity"]["minutes_rest"], diff --git a/homeassistant/components/tractive/const.py b/homeassistant/components/tractive/const.py index acb4f6f7487..f26c0ee2345 100644 --- a/homeassistant/components/tractive/const.py +++ b/homeassistant/components/tractive/const.py @@ -26,7 +26,6 @@ CLIENT_ID = "625e5349c3c3b41c28a669f1" CLIENT = "client" TRACKABLES = "trackables" -TRACKER_ACTIVITY_STATUS_UPDATED = f"{DOMAIN}_tracker_activity_updated" TRACKER_HARDWARE_STATUS_UPDATED = f"{DOMAIN}_tracker_hardware_status_updated" TRACKER_POSITION_UPDATED = f"{DOMAIN}_tracker_position_updated" TRACKER_SWITCH_STATUS_UPDATED = f"{DOMAIN}_tracker_switch_updated" diff --git a/homeassistant/components/tractive/sensor.py b/homeassistant/components/tractive/sensor.py index b73b5faba05..5e2f3288f57 100644 --- a/homeassistant/components/tractive/sensor.py +++ b/homeassistant/components/tractive/sensor.py @@ -37,7 +37,6 @@ from .const import ( CLIENT, DOMAIN, TRACKABLES, - TRACKER_ACTIVITY_STATUS_UPDATED, TRACKER_HARDWARE_STATUS_UPDATED, TRACKER_WELLNESS_STATUS_UPDATED, ) @@ -118,7 +117,7 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( key=ATTR_MINUTES_ACTIVE, translation_key="activity_time", native_unit_of_measurement=UnitOfTime.MINUTES, - signal_prefix=TRACKER_ACTIVITY_STATUS_UPDATED, + signal_prefix=TRACKER_WELLNESS_STATUS_UPDATED, state_class=SensorStateClass.TOTAL, ), TractiveSensorEntityDescription( @@ -139,7 +138,7 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( key=ATTR_DAILY_GOAL, translation_key="daily_goal", native_unit_of_measurement=UnitOfTime.MINUTES, - signal_prefix=TRACKER_ACTIVITY_STATUS_UPDATED, + signal_prefix=TRACKER_WELLNESS_STATUS_UPDATED, ), TractiveSensorEntityDescription( key=ATTR_MINUTES_DAY_SLEEP, From 8cd871885519c50ac89b8da86faaa8a0276b3203 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Mar 2024 16:20:20 +0100 Subject: [PATCH 1612/1691] Fix hassfest service icons check for custom integrations (#114389) --- script/hassfest/services.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/hassfest/services.py b/script/hassfest/services.py index 34f9b906fb5..c962d84e6e1 100644 --- a/script/hassfest/services.py +++ b/script/hassfest/services.py @@ -168,7 +168,8 @@ def validate_services(config: Config, integration: Integration) -> None: # 2. Check if the service has an icon set in icons.json. # raise an error if not., for service_name, service_schema in services.items(): - if service_name not in service_icons: + if integration.core and service_name not in service_icons: + # This is enforced for Core integrations only integration.add_error( "services", f"Service {service_name} has no icon in icons.json.", From c81e9447f9ccd334936d1d0164fd3459d8e1cd80 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 28 Mar 2024 11:09:15 -0500 Subject: [PATCH 1613/1691] Filter preferred TTS format options if not supported (#114392) Filter preferred format options if not supported --- homeassistant/components/tts/__init__.py | 74 ++++++++---- tests/components/assist_pipeline/conftest.py | 3 +- tests/components/assist_pipeline/test_init.py | 105 ++++++++++++++++-- 3 files changed, 150 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index c88e0e83334..8ea4617bbf3 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -16,7 +16,7 @@ import os import re import subprocess import tempfile -from typing import Any, TypedDict, final +from typing import Any, Final, TypedDict, final from aiohttp import web import mutagen @@ -99,6 +99,13 @@ ATTR_PREFERRED_SAMPLE_CHANNELS = "preferred_sample_channels" ATTR_MEDIA_PLAYER_ENTITY_ID = "media_player_entity_id" ATTR_VOICE = "voice" +_DEFAULT_FORMAT = "mp3" +_PREFFERED_FORMAT_OPTIONS: Final[set[str]] = { + ATTR_PREFERRED_FORMAT, + ATTR_PREFERRED_SAMPLE_RATE, + ATTR_PREFERRED_SAMPLE_CHANNELS, +} + CONF_LANG = "language" SERVICE_CLEAR_CACHE = "clear_cache" @@ -569,25 +576,23 @@ class SpeechManager: ): raise HomeAssistantError(f"Language '{language}' not supported") + options = options or {} + supported_options = engine_instance.supported_options or [] + # Update default options with provided options + invalid_opts: list[str] = [] merged_options = dict(engine_instance.default_options or {}) - merged_options.update(options or {}) + for option_name, option_value in options.items(): + # Only count an option as invalid if it's not a "preferred format" + # option. These are used as hints to the TTS system if supported, + # and otherwise as parameters to ffmpeg conversion. + if (option_name in supported_options) or ( + option_name in _PREFFERED_FORMAT_OPTIONS + ): + merged_options[option_name] = option_value + else: + invalid_opts.append(option_name) - supported_options = list(engine_instance.supported_options or []) - - # ATTR_PREFERRED_* options are always "supported" since they're used to - # convert audio after the TTS has run (if necessary). - supported_options.extend( - ( - ATTR_PREFERRED_FORMAT, - ATTR_PREFERRED_SAMPLE_RATE, - ATTR_PREFERRED_SAMPLE_CHANNELS, - ) - ) - - invalid_opts = [ - opt_name for opt_name in merged_options if opt_name not in supported_options - ] if invalid_opts: raise HomeAssistantError(f"Invalid options found: {invalid_opts}") @@ -687,10 +692,31 @@ class SpeechManager: This method is a coroutine. """ - options = options or {} + options = dict(options or {}) + supported_options = engine_instance.supported_options or [] - # Default to MP3 unless a different format is preferred - final_extension = options.get(ATTR_PREFERRED_FORMAT, "mp3") + # Extract preferred format options. + # + # These options are used by Assist pipelines, etc. to get a format that + # the voice satellite will support. + # + # The TTS system ideally supports options directly so we won't have + # to convert with ffmpeg later. If not, we pop the options here and + # perform the conversation after receiving the audio. + if ATTR_PREFERRED_FORMAT in supported_options: + final_extension = options.get(ATTR_PREFERRED_FORMAT, _DEFAULT_FORMAT) + else: + final_extension = options.pop(ATTR_PREFERRED_FORMAT, _DEFAULT_FORMAT) + + if ATTR_PREFERRED_SAMPLE_RATE in supported_options: + sample_rate = options.get(ATTR_PREFERRED_SAMPLE_RATE) + else: + sample_rate = options.pop(ATTR_PREFERRED_SAMPLE_RATE, None) + + if ATTR_PREFERRED_SAMPLE_CHANNELS in supported_options: + sample_channels = options.get(ATTR_PREFERRED_SAMPLE_CHANNELS) + else: + sample_channels = options.pop(ATTR_PREFERRED_SAMPLE_CHANNELS, None) async def get_tts_data() -> str: """Handle data available.""" @@ -716,8 +742,8 @@ class SpeechManager: # rate/format/channel count is requested. needs_conversion = ( (final_extension != extension) - or (ATTR_PREFERRED_SAMPLE_RATE in options) - or (ATTR_PREFERRED_SAMPLE_CHANNELS in options) + or (sample_rate is not None) + or (sample_channels is not None) ) if needs_conversion: @@ -726,8 +752,8 @@ class SpeechManager: extension, data, to_extension=final_extension, - to_sample_rate=options.get(ATTR_PREFERRED_SAMPLE_RATE), - to_sample_channels=options.get(ATTR_PREFERRED_SAMPLE_CHANNELS), + to_sample_rate=sample_rate, + to_sample_channels=sample_channels, ) # Create file infos diff --git a/tests/components/assist_pipeline/conftest.py b/tests/components/assist_pipeline/conftest.py index 8c5cfe9d599..9f098150288 100644 --- a/tests/components/assist_pipeline/conftest.py +++ b/tests/components/assist_pipeline/conftest.py @@ -111,6 +111,7 @@ class MockTTSProvider(tts.Provider): tts.Voice("fran_drescher", "Fran Drescher"), ] } + _supported_options = ["voice", "age", tts.ATTR_AUDIO_OUTPUT] @property def default_language(self) -> str: @@ -130,7 +131,7 @@ class MockTTSProvider(tts.Provider): @property def supported_options(self) -> list[str]: """Return list of supported options like voice, emotions.""" - return ["voice", "age", tts.ATTR_AUDIO_OUTPUT] + return self._supported_options def get_tts_audio( self, message: str, language: str, options: dict[str, Any] diff --git a/tests/components/assist_pipeline/test_init.py b/tests/components/assist_pipeline/test_init.py index 81347e96235..c6f45044cb3 100644 --- a/tests/components/assist_pipeline/test_init.py +++ b/tests/components/assist_pipeline/test_init.py @@ -11,7 +11,7 @@ import wave import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.components import assist_pipeline, stt, tts +from homeassistant.components import assist_pipeline, media_source, stt, tts from homeassistant.components.assist_pipeline.const import ( CONF_DEBUG_RECORDING_DIR, DOMAIN, @@ -19,9 +19,14 @@ from homeassistant.components.assist_pipeline.const import ( from homeassistant.core import Context, HomeAssistant from homeassistant.setup import async_setup_component -from .conftest import MockSttProvider, MockSttProviderEntity, MockWakeWordEntity +from .conftest import ( + MockSttProvider, + MockSttProviderEntity, + MockTTSProvider, + MockWakeWordEntity, +) -from tests.typing import WebSocketGenerator +from tests.typing import ClientSessionGenerator, WebSocketGenerator BYTES_ONE_SECOND = 16000 * 2 @@ -729,15 +734,17 @@ def test_pipeline_run_equality(hass: HomeAssistant, init_components) -> None: async def test_tts_audio_output( hass: HomeAssistant, - mock_stt_provider: MockSttProvider, + hass_client: ClientSessionGenerator, + mock_tts_provider: MockTTSProvider, init_components, pipeline_data: assist_pipeline.pipeline.PipelineData, snapshot: SnapshotAssertion, ) -> None: """Test using tts_audio_output with wav sets options correctly.""" + client = await hass_client() + assert await async_setup_component(hass, media_source.DOMAIN, {}) - def event_callback(event): - pass + events: list[assist_pipeline.PipelineEvent] = [] pipeline_store = pipeline_data.pipeline_store pipeline_id = pipeline_store.async_get_preferred_item() @@ -753,7 +760,7 @@ async def test_tts_audio_output( pipeline=pipeline, start_stage=assist_pipeline.PipelineStage.TTS, end_stage=assist_pipeline.PipelineStage.TTS, - event_callback=event_callback, + event_callback=events.append, tts_audio_output="wav", ), ) @@ -764,3 +771,87 @@ async def test_tts_audio_output( assert pipeline_input.run.tts_options.get(tts.ATTR_PREFERRED_FORMAT) == "wav" assert pipeline_input.run.tts_options.get(tts.ATTR_PREFERRED_SAMPLE_RATE) == 16000 assert pipeline_input.run.tts_options.get(tts.ATTR_PREFERRED_SAMPLE_CHANNELS) == 1 + + with patch.object(mock_tts_provider, "get_tts_audio") as mock_get_tts_audio: + await pipeline_input.execute() + + for event in events: + if event.type == assist_pipeline.PipelineEventType.TTS_END: + # We must fetch the media URL to trigger the TTS + assert event.data + media_id = event.data["tts_output"]["media_id"] + resolved = await media_source.async_resolve_media(hass, media_id, None) + await client.get(resolved.url) + + # Ensure that no unsupported options were passed in + assert mock_get_tts_audio.called + options = mock_get_tts_audio.call_args_list[0].kwargs["options"] + extra_options = set(options).difference(mock_tts_provider.supported_options) + assert len(extra_options) == 0, extra_options + + +async def test_tts_supports_preferred_format( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_tts_provider: MockTTSProvider, + init_components, + pipeline_data: assist_pipeline.pipeline.PipelineData, + snapshot: SnapshotAssertion, +) -> None: + """Test that preferred format options are given to the TTS system if supported.""" + client = await hass_client() + assert await async_setup_component(hass, media_source.DOMAIN, {}) + + events: list[assist_pipeline.PipelineEvent] = [] + + pipeline_store = pipeline_data.pipeline_store + pipeline_id = pipeline_store.async_get_preferred_item() + pipeline = assist_pipeline.pipeline.async_get_pipeline(hass, pipeline_id) + + pipeline_input = assist_pipeline.pipeline.PipelineInput( + tts_input="This is a test.", + conversation_id=None, + device_id=None, + run=assist_pipeline.pipeline.PipelineRun( + hass, + context=Context(), + pipeline=pipeline, + start_stage=assist_pipeline.PipelineStage.TTS, + end_stage=assist_pipeline.PipelineStage.TTS, + event_callback=events.append, + tts_audio_output="wav", + ), + ) + await pipeline_input.validate() + + # Make the TTS provider support preferred format options + supported_options = list(mock_tts_provider.supported_options or []) + supported_options.extend( + [ + tts.ATTR_PREFERRED_FORMAT, + tts.ATTR_PREFERRED_SAMPLE_RATE, + tts.ATTR_PREFERRED_SAMPLE_CHANNELS, + ] + ) + + with ( + patch.object(mock_tts_provider, "_supported_options", supported_options), + patch.object(mock_tts_provider, "get_tts_audio") as mock_get_tts_audio, + ): + await pipeline_input.execute() + + for event in events: + if event.type == assist_pipeline.PipelineEventType.TTS_END: + # We must fetch the media URL to trigger the TTS + assert event.data + media_id = event.data["tts_output"]["media_id"] + resolved = await media_source.async_resolve_media(hass, media_id, None) + await client.get(resolved.url) + + assert mock_get_tts_audio.called + options = mock_get_tts_audio.call_args_list[0].kwargs["options"] + + # We should have received preferred format options in get_tts_audio + assert tts.ATTR_PREFERRED_FORMAT in options + assert tts.ATTR_PREFERRED_SAMPLE_RATE in options + assert tts.ATTR_PREFERRED_SAMPLE_CHANNELS in options From 53ba732ed04fb51b05cb6f30cf01c865008b5ef8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Mar 2024 16:57:29 +0100 Subject: [PATCH 1614/1691] Fix area search for entities of devices (#114394) --- homeassistant/components/search/__init__.py | 9 +++++--- tests/components/search/test_init.py | 24 ++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/search/__init__.py b/homeassistant/components/search/__init__.py index 71b51210a25..a85a21e8102 100644 --- a/homeassistant/components/search/__init__.py +++ b/homeassistant/components/search/__init__.py @@ -136,6 +136,9 @@ class Searcher: # Scripts referencing this area self._add(ItemType.SCRIPT, script.scripts_with_area(self.hass, area_id)) + # Entity in this area, will extend this with the entities of the devices in this area + entity_entries = er.async_entries_for_area(self._entity_registry, area_id) + # Devices in this area for device in dr.async_entries_for_area(self._device_registry, area_id): self._add(ItemType.DEVICE, device.id) @@ -160,10 +163,10 @@ class Searcher: # Skip the entity if it's in a different area if entity_entry.area_id is not None: continue - self._add(ItemType.ENTITY, entity_entry.entity_id) + entity_entries.append(entity_entry) - # Entities in this area - for entity_entry in er.async_entries_for_area(self._entity_registry, area_id): + # Process entities in this area + for entity_entry in entity_entries: self._add(ItemType.ENTITY, entity_entry.entity_id) # If this entity also exists as a resource, we add it. diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index ee7b60dc9ac..a817fbfc39e 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -496,11 +496,14 @@ async def test_search( ItemType.SCRIPT: {script_scene_entity.entity_id, "script.nested"}, } assert search(ItemType.AREA, living_room_area.id) == { - ItemType.AUTOMATION: {"automation.wled_device"}, + ItemType.AUTOMATION: {"automation.wled_device", "automation.wled_entity"}, ItemType.CONFIG_ENTRY: {wled_config_entry.entry_id}, ItemType.DEVICE: {wled_device.id}, ItemType.ENTITY: {wled_segment_1_entity.entity_id}, ItemType.FLOOR: {first_floor.floor_id}, + ItemType.GROUP: {"group.wled", "group.wled_hue"}, + ItemType.SCENE: {"scene.scene_wled_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.wled"}, } assert search(ItemType.AREA, kitchen_area.id) == { ItemType.AUTOMATION: {"automation.area"}, @@ -511,7 +514,9 @@ async def test_search( hue_segment_2_entity.entity_id, }, ItemType.FLOOR: {first_floor.floor_id}, - ItemType.SCRIPT: {"script.area", "script.device"}, + ItemType.GROUP: {"group.hue", "group.wled_hue"}, + ItemType.SCENE: {"scene.scene_hue_seg_1", scene_wled_hue_entity.entity_id}, + ItemType.SCRIPT: {"script.area", "script.device", "script.hue"}, } assert not search(ItemType.AUTOMATION, "automation.unknown") @@ -726,6 +731,7 @@ async def test_search( "automation.area", "automation.floor", "automation.wled_device", + "automation.wled_entity", }, ItemType.CONFIG_ENTRY: {hue_config_entry.entry_id, wled_config_entry.entry_id}, ItemType.DEVICE: {hue_device.id, wled_device.id}, @@ -734,7 +740,19 @@ async def test_search( hue_segment_1_entity.entity_id, hue_segment_2_entity.entity_id, }, - ItemType.SCRIPT: {"script.device", "script.area", "script.floor"}, + ItemType.GROUP: {"group.hue", "group.wled", "group.wled_hue"}, + ItemType.SCENE: { + "scene.scene_hue_seg_1", + "scene.scene_wled_seg_1", + scene_wled_hue_entity.entity_id, + }, + ItemType.SCRIPT: { + "script.device", + "script.area", + "script.floor", + "script.hue", + "script.wled", + }, } assert search(ItemType.FLOOR, second_floor.floor_id) == { ItemType.AREA: {bedroom_area.id}, From 8e4cf4e4a7fbcefa17b7b8861d4edaf6e550a6de Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Mar 2024 20:38:12 +0100 Subject: [PATCH 1615/1691] Update frontend to 20240328.0 (#114396) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 10917bb7f70..9e86436bd68 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240327.0"] + "requirements": ["home-assistant-frontend==20240328.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9af8c2f3e0a..b7db1514cba 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240327.0 +home-assistant-frontend==20240328.0 home-assistant-intents==2024.3.27 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 37fcf49a5e5..9c9e18a6ff3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240327.0 +home-assistant-frontend==20240328.0 # homeassistant.components.conversation home-assistant-intents==2024.3.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 60ac93d37c5..e3cd33e994e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240327.0 +home-assistant-frontend==20240328.0 # homeassistant.components.conversation home-assistant-intents==2024.3.27 From 7a53ea4b92a7e810a73915f37c834af55cb1b72b Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Thu, 28 Mar 2024 12:52:17 -0500 Subject: [PATCH 1616/1691] Bump aioraven to 0.5.3 (#114397) --- homeassistant/components/rainforest_raven/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rainforest_raven/manifest.json b/homeassistant/components/rainforest_raven/manifest.json index ad161d32201..a2717f0e886 100644 --- a/homeassistant/components/rainforest_raven/manifest.json +++ b/homeassistant/components/rainforest_raven/manifest.json @@ -6,7 +6,7 @@ "dependencies": ["usb"], "documentation": "https://www.home-assistant.io/integrations/rainforest_raven", "iot_class": "local_polling", - "requirements": ["aioraven==0.5.2"], + "requirements": ["aioraven==0.5.3"], "usb": [ { "vid": "0403", diff --git a/requirements_all.txt b/requirements_all.txt index 9c9e18a6ff3..10c044009be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -350,7 +350,7 @@ aiopyarr==23.4.0 aioqsw==0.3.5 # homeassistant.components.rainforest_raven -aioraven==0.5.2 +aioraven==0.5.3 # homeassistant.components.recollect_waste aiorecollect==2023.09.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e3cd33e994e..dd555cfebc8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -323,7 +323,7 @@ aiopyarr==23.4.0 aioqsw==0.3.5 # homeassistant.components.rainforest_raven -aioraven==0.5.2 +aioraven==0.5.3 # homeassistant.components.recollect_waste aiorecollect==2023.09.0 From 612988cf3e07c56ebd67293335ebf3ee378b8b9e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Mar 2024 20:43:23 +0100 Subject: [PATCH 1617/1691] Bump version to 2024.4.0b1 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index d458a66b865..f9a9b6324f8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b0" +PATCH_VERSION: Final = "0b1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 40b1f36a58b..48f520a878c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b0" +version = "2024.4.0b1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From b8a2c148131a3b1913871aea07d3cd4e43a9317a Mon Sep 17 00:00:00 2001 From: Jeremy TRUFIER Date: Fri, 29 Mar 2024 14:51:44 +0100 Subject: [PATCH 1618/1691] Follow real AtlanticPassAPCZoneControlZone physical mode on Overkiz (HEAT, COOL or HEAT_COOL) (#111830) * Support HEAT_COOL when mode is Auto on overkiz AtlanticPassAPCZoneControlZone * Refactor ZoneControlZone to simplify usic by only using a single hvac mode * Fix linting issues * Makes more sense to use halves there * Fix PR feedback --- homeassistant/components/overkiz/climate.py | 25 +- .../atlantic_pass_apc_heating_zone.py | 4 +- .../atlantic_pass_apc_zone_control_zone.py | 394 ++++++++++++++---- 3 files changed, 325 insertions(+), 98 deletions(-) diff --git a/homeassistant/components/overkiz/climate.py b/homeassistant/components/overkiz/climate.py index e23403c2162..b569d05d2d7 100644 --- a/homeassistant/components/overkiz/climate.py +++ b/homeassistant/components/overkiz/climate.py @@ -7,6 +7,7 @@ from typing import cast from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import HomeAssistantOverkizData @@ -27,15 +28,16 @@ async def async_setup_entry( """Set up the Overkiz climate from a config entry.""" data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] - async_add_entities( + # Match devices based on the widget. + entities_based_on_widget: list[Entity] = [ WIDGET_TO_CLIMATE_ENTITY[device.widget](device.device_url, data.coordinator) for device in data.platforms[Platform.CLIMATE] if device.widget in WIDGET_TO_CLIMATE_ENTITY - ) + ] - # Match devices based on the widget and controllableName - # This is for example used for Atlantic APC, where devices with different functionality share the same uiClass and widget. - async_add_entities( + # Match devices based on the widget and controllableName. + # ie Atlantic APC + entities_based_on_widget_and_controllable: list[Entity] = [ WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget][ cast(Controllable, device.controllable_name) ](device.device_url, data.coordinator) @@ -43,14 +45,21 @@ async def async_setup_entry( if device.widget in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY and device.controllable_name in WIDGET_AND_CONTROLLABLE_TO_CLIMATE_ENTITY[device.widget] - ) + ] - # Hitachi Air To Air Heat Pumps - async_add_entities( + # Match devices based on the widget and protocol. + # #ie Hitachi Air To Air Heat Pumps + entities_based_on_widget_and_protocol: list[Entity] = [ WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget][device.protocol]( device.device_url, data.coordinator ) for device in data.platforms[Platform.CLIMATE] if device.widget in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY and device.protocol in WIDGET_AND_PROTOCOL_TO_CLIMATE_ENTITY[device.widget] + ] + + async_add_entities( + entities_based_on_widget + + entities_based_on_widget_and_controllable + + entities_based_on_widget_and_protocol ) diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py index bf6aa43644e..3da2ccc922b 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_zone.py @@ -159,7 +159,7 @@ class AtlanticPassAPCHeatingZone(OverkizEntity, ClimateEntity): await self.async_set_heating_mode(PRESET_MODES_TO_OVERKIZ[preset_mode]) @property - def preset_mode(self) -> str: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" heating_mode = cast( str, self.executor.select_state(OverkizState.IO_PASS_APC_HEATING_MODE) @@ -179,7 +179,7 @@ class AtlanticPassAPCHeatingZone(OverkizEntity, ClimateEntity): return OVERKIZ_TO_PRESET_MODES[heating_mode] @property - def target_temperature(self) -> float: + def target_temperature(self) -> float | None: """Return hvac target temperature.""" current_heating_profile = self.current_heating_profile if current_heating_profile in OVERKIZ_TEMPERATURE_STATE_BY_PROFILE: diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py index 261acc2838c..f18edd0cfe6 100644 --- a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_zone_control_zone.py @@ -3,16 +3,24 @@ from __future__ import annotations from asyncio import sleep +from functools import cached_property from typing import Any, cast from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState -from homeassistant.components.climate import PRESET_NONE, HVACMode -from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.components.climate import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + PRESET_NONE, + ClimateEntityFeature, + HVACAction, + HVACMode, +) +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES from ..coordinator import OverkizDataUpdateCoordinator +from ..executor import OverkizExecutor from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone -from .atlantic_pass_apc_zone_control import OVERKIZ_TO_HVAC_MODE PRESET_SCHEDULE = "schedule" PRESET_MANUAL = "manual" @@ -24,32 +32,127 @@ OVERKIZ_MODE_TO_PRESET_MODES: dict[str, str] = { PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_MODE_TO_PRESET_MODES.items()} -TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 20 +# Maps the HVAC current ZoneControl system operating mode. +OVERKIZ_TO_HVAC_ACTION: dict[str, HVACAction] = { + OverkizCommandParam.COOLING: HVACAction.COOLING, + OverkizCommandParam.DRYING: HVACAction.DRYING, + OverkizCommandParam.HEATING: HVACAction.HEATING, + # There is no known way to differentiate OFF from Idle. + OverkizCommandParam.STOP: HVACAction.OFF, +} + +HVAC_ACTION_TO_OVERKIZ_PROFILE_STATE: dict[HVACAction, OverkizState] = { + HVACAction.COOLING: OverkizState.IO_PASS_APC_COOLING_PROFILE, + HVACAction.HEATING: OverkizState.IO_PASS_APC_HEATING_PROFILE, +} + +HVAC_ACTION_TO_OVERKIZ_MODE_STATE: dict[HVACAction, OverkizState] = { + HVACAction.COOLING: OverkizState.IO_PASS_APC_COOLING_MODE, + HVACAction.HEATING: OverkizState.IO_PASS_APC_HEATING_MODE, +} + +TEMPERATURE_ZONECONTROL_DEVICE_INDEX = 1 + +SUPPORTED_FEATURES: ClimateEntityFeature = ( + ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.TURN_OFF + | ClimateEntityFeature.TURN_ON +) + +OVERKIZ_THERMAL_CONFIGURATION_TO_HVAC_MODE: dict[ + OverkizCommandParam, tuple[HVACMode, ClimateEntityFeature] +] = { + OverkizCommandParam.COOLING: ( + HVACMode.COOL, + SUPPORTED_FEATURES | ClimateEntityFeature.TARGET_TEMPERATURE, + ), + OverkizCommandParam.HEATING: ( + HVACMode.HEAT, + SUPPORTED_FEATURES | ClimateEntityFeature.TARGET_TEMPERATURE, + ), + OverkizCommandParam.HEATING_AND_COOLING: ( + HVACMode.HEAT_COOL, + SUPPORTED_FEATURES | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, + ), +} -# Those device depends on a main probe that choose the operating mode (heating, cooling, ...) +# Those device depends on a main probe that choose the operating mode (heating, cooling, ...). class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): """Representation of Atlantic Pass APC Heating And Cooling Zone Control.""" + _attr_target_temperature_step = PRECISION_HALVES + def __init__( self, device_url: str, coordinator: OverkizDataUpdateCoordinator ) -> None: """Init method.""" super().__init__(device_url, coordinator) - # There is less supported functions, because they depend on the ZoneControl. - if not self.is_using_derogated_temperature_fallback: - # Modes are not configurable, they will follow current HVAC Mode of Zone Control. - self._attr_hvac_modes = [] + # When using derogated temperature, we fallback to legacy behavior. + if self.is_using_derogated_temperature_fallback: + return - # Those are available and tested presets on Shogun. - self._attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] + self._attr_hvac_modes = [] + self._attr_supported_features = ClimateEntityFeature(0) + + # Modes depends on device capabilities. + if (thermal_configuration := self.thermal_configuration) is not None: + ( + device_hvac_mode, + climate_entity_feature, + ) = thermal_configuration + self._attr_hvac_modes = [device_hvac_mode, HVACMode.OFF] + self._attr_supported_features = climate_entity_feature + + # Those are available and tested presets on Shogun. + self._attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] # Those APC Heating and Cooling probes depends on the zone control device (main probe). # Only the base device (#1) can be used to get/set some states. # Like to retrieve and set the current operating mode (heating, cooling, drying, off). - self.zone_control_device = self.executor.linked_device( - TEMPERATURE_ZONECONTROL_DEVICE_INDEX + + self.zone_control_executor: OverkizExecutor | None = None + + if ( + zone_control_device := self.executor.linked_device( + TEMPERATURE_ZONECONTROL_DEVICE_INDEX + ) + ) is not None: + self.zone_control_executor = OverkizExecutor( + zone_control_device.device_url, + coordinator, + ) + + @cached_property + def thermal_configuration(self) -> tuple[HVACMode, ClimateEntityFeature] | None: + """Retrieve thermal configuration for this devices.""" + + if ( + ( + state_thermal_configuration := cast( + OverkizCommandParam | None, + self.executor.select_state(OverkizState.CORE_THERMAL_CONFIGURATION), + ) + ) + is not None + and state_thermal_configuration + in OVERKIZ_THERMAL_CONFIGURATION_TO_HVAC_MODE + ): + return OVERKIZ_THERMAL_CONFIGURATION_TO_HVAC_MODE[ + state_thermal_configuration + ] + + return None + + @cached_property + def device_hvac_mode(self) -> HVACMode | None: + """ZoneControlZone device has a single possible mode.""" + + return ( + None + if self.thermal_configuration is None + else self.thermal_configuration[0] ) @property @@ -61,21 +164,37 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): ) @property - def zone_control_hvac_mode(self) -> HVACMode: + def zone_control_hvac_action(self) -> HVACAction: """Return hvac operation ie. heat, cool, dry, off mode.""" - if ( - self.zone_control_device is not None - and ( - state := self.zone_control_device.states[ + if self.zone_control_executor is not None and ( + ( + state := self.zone_control_executor.select_state( OverkizState.IO_PASS_APC_OPERATING_MODE - ] + ) ) is not None - and (value := state.value_as_str) is not None ): - return OVERKIZ_TO_HVAC_MODE[value] - return HVACMode.OFF + return OVERKIZ_TO_HVAC_ACTION[cast(str, state)] + + return HVACAction.OFF + + @property + def hvac_action(self) -> HVACAction | None: + """Return the current running hvac operation.""" + + # When ZoneControl action is heating/cooling but Zone is stopped, means the zone is idle. + if ( + hvac_action := self.zone_control_hvac_action + ) in HVAC_ACTION_TO_OVERKIZ_PROFILE_STATE and cast( + str, + self.executor.select_state( + HVAC_ACTION_TO_OVERKIZ_PROFILE_STATE[hvac_action] + ), + ) == OverkizCommandParam.STOP: + return HVACAction.IDLE + + return hvac_action @property def hvac_mode(self) -> HVACMode: @@ -84,30 +203,32 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): if self.is_using_derogated_temperature_fallback: return super().hvac_mode - zone_control_hvac_mode = self.zone_control_hvac_mode + if (device_hvac_mode := self.device_hvac_mode) is None: + return HVACMode.OFF - # Should be same, because either thermostat or this integration change both. - on_off_state = cast( + cooling_is_off = cast( str, - self.executor.select_state( - OverkizState.CORE_COOLING_ON_OFF - if zone_control_hvac_mode == HVACMode.COOL - else OverkizState.CORE_HEATING_ON_OFF - ), - ) + self.executor.select_state(OverkizState.CORE_COOLING_ON_OFF), + ) in (OverkizCommandParam.OFF, None) + + heating_is_off = cast( + str, + self.executor.select_state(OverkizState.CORE_HEATING_ON_OFF), + ) in (OverkizCommandParam.OFF, None) # Device is Stopped, it means the air flux is flowing but its venting door is closed. - if on_off_state == OverkizCommandParam.OFF: - hvac_mode = HVACMode.OFF - else: - hvac_mode = zone_control_hvac_mode + if ( + (device_hvac_mode == HVACMode.COOL and cooling_is_off) + or (device_hvac_mode == HVACMode.HEAT and heating_is_off) + or ( + device_hvac_mode == HVACMode.HEAT_COOL + and cooling_is_off + and heating_is_off + ) + ): + return HVACMode.OFF - # It helps keep it consistent with the Zone Control, within the interface. - if self._attr_hvac_modes != [zone_control_hvac_mode, HVACMode.OFF]: - self._attr_hvac_modes = [zone_control_hvac_mode, HVACMode.OFF] - self.async_write_ha_state() - - return hvac_mode + return device_hvac_mode async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" @@ -118,46 +239,49 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): # They are mainly managed by the Zone Control device # However, it make sense to map the OFF Mode to the Overkiz STOP Preset - if hvac_mode == HVACMode.OFF: - await self.executor.async_execute_command( - OverkizCommand.SET_COOLING_ON_OFF, - OverkizCommandParam.OFF, - ) - await self.executor.async_execute_command( - OverkizCommand.SET_HEATING_ON_OFF, - OverkizCommandParam.OFF, - ) - else: - await self.executor.async_execute_command( - OverkizCommand.SET_COOLING_ON_OFF, - OverkizCommandParam.ON, - ) - await self.executor.async_execute_command( - OverkizCommand.SET_HEATING_ON_OFF, - OverkizCommandParam.ON, - ) + on_off_target_command_param = ( + OverkizCommandParam.OFF + if hvac_mode == HVACMode.OFF + else OverkizCommandParam.ON + ) + + await self.executor.async_execute_command( + OverkizCommand.SET_COOLING_ON_OFF, + on_off_target_command_param, + ) + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_ON_OFF, + on_off_target_command_param, + ) await self.async_refresh_modes() @property - def preset_mode(self) -> str: + def preset_mode(self) -> str | None: """Return the current preset mode, e.g., schedule, manual.""" if self.is_using_derogated_temperature_fallback: return super().preset_mode - mode = OVERKIZ_MODE_TO_PRESET_MODES[ - cast( - str, - self.executor.select_state( - OverkizState.IO_PASS_APC_COOLING_MODE - if self.zone_control_hvac_mode == HVACMode.COOL - else OverkizState.IO_PASS_APC_HEATING_MODE - ), + if ( + self.zone_control_hvac_action in HVAC_ACTION_TO_OVERKIZ_MODE_STATE + and ( + mode_state := HVAC_ACTION_TO_OVERKIZ_MODE_STATE[ + self.zone_control_hvac_action + ] ) - ] + and ( + ( + mode := OVERKIZ_MODE_TO_PRESET_MODES[ + cast(str, self.executor.select_state(mode_state)) + ] + ) + is not None + ) + ): + return mode - return mode if mode is not None else PRESET_NONE + return PRESET_NONE async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" @@ -178,13 +302,18 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): await self.async_refresh_modes() @property - def target_temperature(self) -> float: + def target_temperature(self) -> float | None: """Return hvac target temperature.""" if self.is_using_derogated_temperature_fallback: return super().target_temperature - if self.zone_control_hvac_mode == HVACMode.COOL: + device_hvac_mode = self.device_hvac_mode + + if device_hvac_mode == HVACMode.HEAT_COOL: + return None + + if device_hvac_mode == HVACMode.COOL: return cast( float, self.executor.select_state( @@ -192,7 +321,7 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): ), ) - if self.zone_control_hvac_mode == HVACMode.HEAT: + if device_hvac_mode == HVACMode.HEAT: return cast( float, self.executor.select_state( @@ -204,32 +333,73 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): float, self.executor.select_state(OverkizState.CORE_TARGET_TEMPERATURE) ) + @property + def target_temperature_high(self) -> float | None: + """Return the highbound target temperature we try to reach (cooling).""" + + if self.device_hvac_mode != HVACMode.HEAT_COOL: + return None + + return cast( + float, + self.executor.select_state(OverkizState.CORE_COOLING_TARGET_TEMPERATURE), + ) + + @property + def target_temperature_low(self) -> float | None: + """Return the lowbound target temperature we try to reach (heating).""" + + if self.device_hvac_mode != HVACMode.HEAT_COOL: + return None + + return cast( + float, + self.executor.select_state(OverkizState.CORE_HEATING_TARGET_TEMPERATURE), + ) + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new temperature.""" if self.is_using_derogated_temperature_fallback: return await super().async_set_temperature(**kwargs) - temperature = kwargs[ATTR_TEMPERATURE] + target_temperature = kwargs.get(ATTR_TEMPERATURE) + target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + hvac_mode = self.hvac_mode + + if hvac_mode == HVACMode.HEAT_COOL: + if target_temp_low is not None: + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_TARGET_TEMPERATURE, + target_temp_low, + ) + + if target_temp_high is not None: + await self.executor.async_execute_command( + OverkizCommand.SET_COOLING_TARGET_TEMPERATURE, + target_temp_high, + ) + + elif target_temperature is not None: + if hvac_mode == HVACMode.HEAT: + await self.executor.async_execute_command( + OverkizCommand.SET_HEATING_TARGET_TEMPERATURE, + target_temperature, + ) + + elif hvac_mode == HVACMode.COOL: + await self.executor.async_execute_command( + OverkizCommand.SET_COOLING_TARGET_TEMPERATURE, + target_temperature, + ) - # Change both (heating/cooling) temperature is a good way to have consistency - await self.executor.async_execute_command( - OverkizCommand.SET_HEATING_TARGET_TEMPERATURE, - temperature, - ) - await self.executor.async_execute_command( - OverkizCommand.SET_COOLING_TARGET_TEMPERATURE, - temperature, - ) await self.executor.async_execute_command( OverkizCommand.SET_DEROGATION_ON_OFF_STATE, - OverkizCommandParam.OFF, + OverkizCommandParam.ON, ) - # Target temperature may take up to 1 minute to get refreshed. - await self.executor.async_execute_command( - OverkizCommand.REFRESH_TARGET_TEMPERATURE - ) + await self.async_refresh_modes() async def async_refresh_modes(self) -> None: """Refresh the device modes to have new states.""" @@ -256,3 +426,51 @@ class AtlanticPassAPCZoneControlZone(AtlanticPassAPCHeatingZone): await self.executor.async_execute_command( OverkizCommand.REFRESH_TARGET_TEMPERATURE ) + + @property + def min_temp(self) -> float: + """Return Minimum Temperature for AC of this group.""" + + device_hvac_mode = self.device_hvac_mode + + if device_hvac_mode in (HVACMode.HEAT, HVACMode.HEAT_COOL): + return cast( + float, + self.executor.select_state( + OverkizState.CORE_MINIMUM_HEATING_TARGET_TEMPERATURE + ), + ) + + if device_hvac_mode == HVACMode.COOL: + return cast( + float, + self.executor.select_state( + OverkizState.CORE_MINIMUM_COOLING_TARGET_TEMPERATURE + ), + ) + + return super().min_temp + + @property + def max_temp(self) -> float: + """Return Max Temperature for AC of this group.""" + + device_hvac_mode = self.device_hvac_mode + + if device_hvac_mode == HVACMode.HEAT: + return cast( + float, + self.executor.select_state( + OverkizState.CORE_MAXIMUM_HEATING_TARGET_TEMPERATURE + ), + ) + + if device_hvac_mode in (HVACMode.COOL, HVACMode.HEAT_COOL): + return cast( + float, + self.executor.select_state( + OverkizState.CORE_MAXIMUM_COOLING_TARGET_TEMPERATURE + ), + ) + + return super().max_temp From 65d25bd7803619123eaabb6aeef6bf87ff894083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexey=20ALERT=20Rubash=D1=91ff?= Date: Fri, 29 Mar 2024 15:07:22 +0200 Subject: [PATCH 1619/1691] Add overkiz heating status, absence mode, and boost mode binary sensors for Atlantic Water Heater (#114184) * Adds heating status, absense mode, and boost mode binary sensors for Atlantic water heater * Renamed absence mode and boost mode binary sensors * Update homeassistant/components/overkiz/binary_sensor.py Co-authored-by: TheJulianJES * Update homeassistant/components/overkiz/binary_sensor.py Co-authored-by: TheJulianJES * Update homeassistant/components/overkiz/binary_sensor.py Co-authored-by: TheJulianJES --------- Co-authored-by: TheJulianJES --- .../components/overkiz/binary_sensor.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/homeassistant/components/overkiz/binary_sensor.py b/homeassistant/components/overkiz/binary_sensor.py index 871a70b3e0a..c37afc9cb0c 100644 --- a/homeassistant/components/overkiz/binary_sensor.py +++ b/homeassistant/components/overkiz/binary_sensor.py @@ -105,6 +105,22 @@ BINARY_SENSOR_DESCRIPTIONS: list[OverkizBinarySensorDescription] = [ ) == 1, ), + OverkizBinarySensorDescription( + key=OverkizState.CORE_HEATING_STATUS, + name="Heating status", + device_class=BinarySensorDeviceClass.HEAT, + value_fn=lambda state: state == OverkizCommandParam.ON, + ), + OverkizBinarySensorDescription( + key=OverkizState.MODBUSLINK_DHW_ABSENCE_MODE, + name="Absence mode", + value_fn=lambda state: state == OverkizCommandParam.ON, + ), + OverkizBinarySensorDescription( + key=OverkizState.MODBUSLINK_DHW_BOOST_MODE, + name="Boost mode", + value_fn=lambda state: state == OverkizCommandParam.ON, + ), ] SUPPORTED_STATES = { From 35e582a240edb66ed93d5c93d88a76bee9f6c383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexey=20ALERT=20Rubash=D1=91ff?= Date: Fri, 29 Mar 2024 20:33:13 +0200 Subject: [PATCH 1620/1691] Add overkiz water targets temperature numbers for Atlantic water heater (#114185) * Adds water targets temperature numbers for Atlantic water heater * Update homeassistant/components/overkiz/number.py Co-authored-by: Mick Vleeshouwer * Update homeassistant/components/overkiz/number.py Co-authored-by: Mick Vleeshouwer * ruff formatting reverted * Update homeassistant/components/overkiz/number.py Co-authored-by: TheJulianJES * Update homeassistant/components/overkiz/number.py Co-authored-by: TheJulianJES * changed command hardcode to a constant --------- Co-authored-by: Mick Vleeshouwer Co-authored-by: TheJulianJES --- homeassistant/components/overkiz/number.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py index f81ed82f7b1..494d430c393 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -97,6 +97,28 @@ NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ max_value_state_name=OverkizState.CORE_MAXIMAL_SHOWER_MANUAL_MODE, entity_category=EntityCategory.CONFIG, ), + OverkizNumberDescription( + key=OverkizState.CORE_TARGET_DWH_TEMPERATURE, + name="Target temperature", + device_class=NumberDeviceClass.TEMPERATURE, + command=OverkizCommand.SET_TARGET_DHW_TEMPERATURE, + native_min_value=50, + native_max_value=65, + min_value_state_name=OverkizState.CORE_MINIMAL_TEMPERATURE_MANUAL_MODE, + max_value_state_name=OverkizState.CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE, + entity_category=EntityCategory.CONFIG, + ), + OverkizNumberDescription( + key=OverkizState.CORE_WATER_TARGET_TEMPERATURE, + name="Water target temperature", + device_class=NumberDeviceClass.TEMPERATURE, + command=OverkizCommand.SET_WATER_TARGET_TEMPERATURE, + native_min_value=50, + native_max_value=65, + min_value_state_name=OverkizState.CORE_MINIMAL_TEMPERATURE_MANUAL_MODE, + max_value_state_name=OverkizState.CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE, + entity_category=EntityCategory.CONFIG, + ), # SomfyHeatingTemperatureInterface OverkizNumberDescription( key=OverkizState.CORE_ECO_ROOM_TEMPERATURE, From bf4e527f4485e9b4b85103b4d0ed229dce51b7ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexey=20ALERT=20Rubash=D1=91ff?= Date: Fri, 29 Mar 2024 15:05:18 +0200 Subject: [PATCH 1621/1691] Add overkiz bottom tank water temperature and core control water temperature for Atlantic Water Heater (#114186) * Adds bottom tank water temperature and core conrol water temperature sensors for Atlantic water heater * Update homeassistant/components/overkiz/sensor.py Co-authored-by: TheJulianJES * Update homeassistant/components/overkiz/sensor.py Co-authored-by: TheJulianJES --------- Co-authored-by: TheJulianJES --- homeassistant/components/overkiz/sensor.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/homeassistant/components/overkiz/sensor.py b/homeassistant/components/overkiz/sensor.py index 2b0a222f96f..c62840eea97 100644 --- a/homeassistant/components/overkiz/sensor.py +++ b/homeassistant/components/overkiz/sensor.py @@ -399,6 +399,20 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [ native_unit_of_measurement=UnitOfTime.SECONDS, entity_category=EntityCategory.DIAGNOSTIC, ), + OverkizSensorDescription( + key=OverkizState.CORE_BOTTOM_TANK_WATER_TEMPERATURE, + name="Bottom tank water temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + ), + OverkizSensorDescription( + key=OverkizState.CORE_CONTROL_WATER_TARGET_TEMPERATURE, + name="Control water target temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + ), # Cover OverkizSensorDescription( key=OverkizState.CORE_TARGET_CLOSURE, From bc740f95c9fb64ec56bf04dfb2ff07ea4fa29f51 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Mar 2024 05:20:26 -1000 Subject: [PATCH 1622/1691] Avoid concurrent radio operations with powerview hubs (#114399) Co-authored-by: kingy444 --- .../components/hunterdouglas_powerview/button.py | 3 ++- .../components/hunterdouglas_powerview/coordinator.py | 5 +++++ .../components/hunterdouglas_powerview/cover.py | 11 ++++++++--- .../components/hunterdouglas_powerview/select.py | 3 ++- .../components/hunterdouglas_powerview/sensor.py | 3 ++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/button.py b/homeassistant/components/hunterdouglas_powerview/button.py index f7c90f3420b..ecb71f9653a 100644 --- a/homeassistant/components/hunterdouglas_powerview/button.py +++ b/homeassistant/components/hunterdouglas_powerview/button.py @@ -119,4 +119,5 @@ class PowerviewShadeButton(ShadeEntity, ButtonEntity): async def async_press(self) -> None: """Handle the button press.""" - await self.entity_description.press_action(self._shade) + async with self.coordinator.radio_operation_lock: + await self.entity_description.press_action(self._shade) diff --git a/homeassistant/components/hunterdouglas_powerview/coordinator.py b/homeassistant/components/hunterdouglas_powerview/coordinator.py index 1ea47ca9d1f..f074b06b2bc 100644 --- a/homeassistant/components/hunterdouglas_powerview/coordinator.py +++ b/homeassistant/components/hunterdouglas_powerview/coordinator.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from datetime import timedelta import logging @@ -25,6 +26,10 @@ class PowerviewShadeUpdateCoordinator(DataUpdateCoordinator[PowerviewShadeData]) """Initialize DataUpdateCoordinator to gather data for specific Powerview Hub.""" self.shades = shades self.hub = hub + # The hub tends to crash if there are multiple radio operations at the same time + # but it seems to handle all other requests that do not use RF without issue + # so we have a lock to prevent multiple radio operations at the same time + self.radio_operation_lock = asyncio.Lock() super().__init__( hass, _LOGGER, diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 453d5c4e920..57409f37ac9 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -67,7 +67,8 @@ async def async_setup_entry( for shade in pv_entry.shade_data.values(): _LOGGER.debug("Initial refresh of shade: %s", shade.name) - await shade.refresh(suppress_timeout=True) # default 15 second timeout + async with coordinator.radio_operation_lock: + await shade.refresh(suppress_timeout=True) # default 15 second timeout entities: list[ShadeEntity] = [] for shade in pv_entry.shade_data.values(): @@ -207,7 +208,8 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity): async def _async_execute_move(self, move: ShadePosition) -> None: """Execute a move that can affect multiple positions.""" _LOGGER.debug("Move request %s: %s", self.name, move) - response = await self._shade.move(move) + async with self.coordinator.radio_operation_lock: + response = await self._shade.move(move) _LOGGER.debug("Move response %s: %s", self.name, response) # Process the response from the hub (including new positions) @@ -318,7 +320,10 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity): # error if are already have one in flight return # suppress timeouts caused by hub nightly reboot - await self._shade.refresh(suppress_timeout=True) # default 15 second timeout + async with self.coordinator.radio_operation_lock: + await self._shade.refresh( + suppress_timeout=True + ) # default 15 second timeout _LOGGER.debug("Process update %s: %s", self.name, self._shade.current_position) self._async_update_shade_data(self._shade.current_position) diff --git a/homeassistant/components/hunterdouglas_powerview/select.py b/homeassistant/components/hunterdouglas_powerview/select.py index 66207f6da7c..f1e9c491659 100644 --- a/homeassistant/components/hunterdouglas_powerview/select.py +++ b/homeassistant/components/hunterdouglas_powerview/select.py @@ -114,5 +114,6 @@ class PowerViewSelect(ShadeEntity, SelectEntity): """Change the selected option.""" await self.entity_description.select_fn(self._shade, option) # force update data to ensure new info is in coordinator - await self._shade.refresh() + async with self.coordinator.radio_operation_lock: + await self._shade.refresh(suppress_timeout=True) self.async_write_ha_state() diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py index bca87189e56..b24193ac438 100644 --- a/homeassistant/components/hunterdouglas_powerview/sensor.py +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -153,5 +153,6 @@ class PowerViewSensor(ShadeEntity, SensorEntity): async def async_update(self) -> None: """Refresh sensor entity.""" - await self.entity_description.update_fn(self._shade) + async with self.coordinator.radio_operation_lock: + await self.entity_description.update_fn(self._shade) self.async_write_ha_state() From 906febadef43171da7df58e570f2134073209fa0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 28 Mar 2024 20:36:33 -1000 Subject: [PATCH 1623/1691] Cleanup some plex tasks that delayed startup (#114418) --- homeassistant/components/plex/__init__.py | 19 ++++--------------- homeassistant/components/plex/const.py | 1 - homeassistant/components/plex/helpers.py | 2 -- homeassistant/components/plex/server.py | 1 + 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 4e17e4032aa..eb57dc46727 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -42,7 +42,6 @@ from .const import ( DOMAIN, INVALID_TOKEN_MESSAGE, PLATFORMS, - PLATFORMS_COMPLETED, PLEX_SERVER_CONFIG, PLEX_UPDATE_LIBRARY_SIGNAL, PLEX_UPDATE_PLATFORMS_SIGNAL, @@ -94,18 +93,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: gdm.scan(scan_for_clients=True) debouncer = Debouncer[None]( - hass, - _LOGGER, - cooldown=10, - immediate=True, - function=gdm_scan, + hass, _LOGGER, cooldown=10, immediate=True, function=gdm_scan, background=True ).async_call hass_data = PlexData( servers={}, dispatchers={}, websockets={}, - platforms_completed={}, gdm_scanner=gdm, gdm_debouncer=debouncer, ) @@ -180,7 +174,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: server_id = plex_server.machine_identifier hass_data = get_plex_data(hass) hass_data[SERVERS][server_id] = plex_server - hass_data[PLATFORMS_COMPLETED][server_id] = set() entry.add_update_listener(async_options_updated) @@ -233,11 +226,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) hass_data[WEBSOCKETS][server_id] = websocket - def start_websocket_session(platform): - hass_data[PLATFORMS_COMPLETED][server_id].add(platform) - if hass_data[PLATFORMS_COMPLETED][server_id] == PLATFORMS: - hass.loop.create_task(websocket.listen()) - def close_websocket_session(_): websocket.close() @@ -248,8 +236,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - for platform in PLATFORMS: - start_websocket_session(platform) + entry.async_create_background_task( + hass, websocket.listen(), f"plex websocket listener {entry.entry_id}" + ) async_cleanup_plex_devices(hass, entry) diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index 8dc75a447af..d5d70219471 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -24,7 +24,6 @@ GDM_SCANNER: Final = "gdm_scanner" PLATFORMS = frozenset( [Platform.BUTTON, Platform.MEDIA_PLAYER, Platform.SENSOR, Platform.UPDATE] ) -PLATFORMS_COMPLETED: Final = "platforms_completed" PLAYER_SOURCE = "player_source" SERVERS: Final = "servers" WEBSOCKETS: Final = "websockets" diff --git a/homeassistant/components/plex/helpers.py b/homeassistant/components/plex/helpers.py index f51350ac597..3c7ff8180c8 100644 --- a/homeassistant/components/plex/helpers.py +++ b/homeassistant/components/plex/helpers.py @@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any, TypedDict from plexapi.gdm import GDM from plexwebsocket import PlexWebsocket -from homeassistant.const import Platform from homeassistant.core import CALLBACK_TYPE, HomeAssistant from .const import DOMAIN, SERVERS @@ -23,7 +22,6 @@ class PlexData(TypedDict): servers: dict[str, PlexServer] dispatchers: dict[str, list[CALLBACK_TYPE]] websockets: dict[str, PlexWebsocket] - platforms_completed: dict[str, set[Platform]] gdm_scanner: GDM gdm_debouncer: Callable[[], Coroutine[Any, Any, None]] diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 9e2bf63ce55..584378d51f9 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -97,6 +97,7 @@ class PlexServer: cooldown=DEBOUNCE_TIMEOUT, immediate=True, function=self._async_update_platforms, + background=True, ).async_call self.thumbnail_cache = {} From db7d0a0ee91e7dcf60ed31c95bd3907fc3ed4db9 Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:47:21 +0000 Subject: [PATCH 1624/1691] Bump python-ring-doorbell to 0.8.8 (#114431) * Bump ring_doorbell to 0.8.8 * Fix intercom history test for new library version --------- Co-authored-by: Joost Lekkerkerker --- homeassistant/components/ring/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/ring/test_sensor.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json index 0390db640e5..764557a3a1d 100644 --- a/homeassistant/components/ring/manifest.json +++ b/homeassistant/components/ring/manifest.json @@ -13,5 +13,5 @@ "documentation": "https://www.home-assistant.io/integrations/ring", "iot_class": "cloud_polling", "loggers": ["ring_doorbell"], - "requirements": ["ring-doorbell[listen]==0.8.7"] + "requirements": ["ring-doorbell[listen]==0.8.8"] } diff --git a/requirements_all.txt b/requirements_all.txt index 10c044009be..60681ce093f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2444,7 +2444,7 @@ rfk101py==0.0.1 rflink==0.0.66 # homeassistant.components.ring -ring-doorbell[listen]==0.8.7 +ring-doorbell[listen]==0.8.8 # homeassistant.components.fleetgo ritassist==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dd555cfebc8..68e82ca22e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1887,7 +1887,7 @@ reolink-aio==0.8.9 rflink==0.0.66 # homeassistant.components.ring -ring-doorbell[listen]==0.8.7 +ring-doorbell[listen]==0.8.8 # homeassistant.components.roku rokuecp==0.19.2 diff --git a/tests/components/ring/test_sensor.py b/tests/components/ring/test_sensor.py index aadea6f0ba1..2c866586c6c 100644 --- a/tests/components/ring/test_sensor.py +++ b/tests/components/ring/test_sensor.py @@ -87,7 +87,7 @@ async def test_history( assert front_door_last_activity_state.state == "2017-03-05T15:03:40+00:00" ingress_last_activity_state = hass.states.get("sensor.ingress_last_activity") - assert ingress_last_activity_state.state == "unknown" + assert ingress_last_activity_state.state == "2024-02-02T11:21:24+00:00" async def test_only_chime_devices( From c7ce53cc4954190e9b7a6b11537190a565599416 Mon Sep 17 00:00:00 2001 From: Mick Vleeshouwer Date: Fri, 29 Mar 2024 17:46:21 +0100 Subject: [PATCH 1625/1691] Bump pyoverkiz to 1.13.9 (#114442) --- homeassistant/components/overkiz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index db24a299f2a..2ef0f0ebef4 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -19,7 +19,7 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"], - "requirements": ["pyoverkiz==1.13.8"], + "requirements": ["pyoverkiz==1.13.9"], "zeroconf": [ { "type": "_kizbox._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 60681ce093f..bc52a8a1c54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2035,7 +2035,7 @@ pyotgw==2.1.3 pyotp==2.8.0 # homeassistant.components.overkiz -pyoverkiz==1.13.8 +pyoverkiz==1.13.9 # homeassistant.components.openweathermap pyowm==3.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 68e82ca22e1..855eebb9912 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1583,7 +1583,7 @@ pyotgw==2.1.3 pyotp==2.8.0 # homeassistant.components.overkiz -pyoverkiz==1.13.8 +pyoverkiz==1.13.9 # homeassistant.components.openweathermap pyowm==3.2.0 From cdd7ce435ab71ddcae228199dcce42f1fe240da0 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:20:43 +0100 Subject: [PATCH 1626/1691] Log warnings in Renault initialisation (#114445) --- homeassistant/components/renault/renault_vehicle.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/renault/renault_vehicle.py b/homeassistant/components/renault/renault_vehicle.py index 55a5574a444..59e1826ce1b 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -125,16 +125,16 @@ class RenaultVehicleProxy: coordinator = self.coordinators[key] if coordinator.not_supported: # Remove endpoint as it is not supported for this vehicle. - LOGGER.info( - "Ignoring endpoint %s as it is not supported for this vehicle: %s", + LOGGER.warning( + "Ignoring endpoint %s as it is not supported: %s", coordinator.name, coordinator.last_exception, ) del self.coordinators[key] elif coordinator.access_denied: # Remove endpoint as it is denied for this vehicle. - LOGGER.info( - "Ignoring endpoint %s as it is denied for this vehicle: %s", + LOGGER.warning( + "Ignoring endpoint %s as it is denied: %s", coordinator.name, coordinator.last_exception, ) From e4d973e8a2439f0fa23377eaa445f300b213418a Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Fri, 29 Mar 2024 19:08:07 +0100 Subject: [PATCH 1627/1691] Bump async-upnp-client to 0.38.3 (#114447) --- homeassistant/components/dlna_dmr/manifest.json | 2 +- homeassistant/components/dlna_dms/manifest.json | 2 +- homeassistant/components/samsungtv/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/components/upnp/manifest.json | 2 +- homeassistant/components/yeelight/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index 128822cf289..41fa49f1a94 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -8,7 +8,7 @@ "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", "iot_class": "local_push", "loggers": ["async_upnp_client"], - "requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"], + "requirements": ["async-upnp-client==0.38.3", "getmac==0.9.4"], "ssdp": [ { "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index aaa6e1ee7de..c87e5e87779 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -8,7 +8,7 @@ "documentation": "https://www.home-assistant.io/integrations/dlna_dms", "iot_class": "local_polling", "quality_scale": "platinum", - "requirements": ["async-upnp-client==0.38.2"], + "requirements": ["async-upnp-client==0.38.3"], "ssdp": [ { "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 00b8fec8e6a..460e191828e 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -39,7 +39,7 @@ "samsungctl[websocket]==0.7.1", "samsungtvws[async,encrypted]==2.6.0", "wakeonlan==2.1.0", - "async-upnp-client==0.38.2" + "async-upnp-client==0.38.3" ], "ssdp": [ { diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index a9ef8af8c90..5e549c31806 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["async_upnp_client"], "quality_scale": "internal", - "requirements": ["async-upnp-client==0.38.2"] + "requirements": ["async-upnp-client==0.38.3"] } diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index edfde84a2ac..7d353a475c7 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -8,7 +8,7 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["async_upnp_client"], - "requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"], + "requirements": ["async-upnp-client==0.38.3", "getmac==0.9.4"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index 20f8ed3ed4d..e9f304d38cb 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -17,7 +17,7 @@ "iot_class": "local_push", "loggers": ["async_upnp_client", "yeelight"], "quality_scale": "platinum", - "requirements": ["yeelight==0.7.14", "async-upnp-client==0.38.2"], + "requirements": ["yeelight==0.7.14", "async-upnp-client==0.38.3"], "zeroconf": [ { "type": "_miio._udp.local.", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b7db1514cba..c757aceee3c 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,7 +8,7 @@ aiohttp==3.9.3 aiohttp_cors==0.7.0 astral==2.2 async-interrupt==1.1.1 -async-upnp-client==0.38.2 +async-upnp-client==0.38.3 atomicwrites-homeassistant==1.4.1 attrs==23.2.0 awesomeversion==24.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index bc52a8a1c54..f28d2bf3e25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -487,7 +487,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.38.2 +async-upnp-client==0.38.3 # homeassistant.components.keyboard_remote asyncinotify==4.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 855eebb9912..987f0be178b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -442,7 +442,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.38.2 +async-upnp-client==0.38.3 # homeassistant.components.sleepiq asyncsleepiq==1.5.2 From 84901f1983fa6158da1037d307e07031bf591c0a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 29 Mar 2024 19:34:16 +0100 Subject: [PATCH 1628/1691] Update frontend to 20240329.0 (#114452) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 9e86436bd68..a8f14187d48 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240328.0"] + "requirements": ["home-assistant-frontend==20240329.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c757aceee3c..2ebb82d2c75 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240328.0 +home-assistant-frontend==20240329.0 home-assistant-intents==2024.3.27 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index f28d2bf3e25..dd59129a7e3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240328.0 +home-assistant-frontend==20240329.0 # homeassistant.components.conversation home-assistant-intents==2024.3.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 987f0be178b..c46bf2e5a59 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240328.0 +home-assistant-frontend==20240329.0 # homeassistant.components.conversation home-assistant-intents==2024.3.27 From e53672250fe7a88d0139e383ca820fc825b88bc9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 29 Mar 2024 19:35:52 +0100 Subject: [PATCH 1629/1691] Bump version to 2024.4.0b2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index f9a9b6324f8..4fca9fa50c3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b1" +PATCH_VERSION: Final = "0b2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 48f520a878c..790ee03d76b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b1" +version = "2024.4.0b2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 953ceb0d8d275b13ce3df6466f1e7f673aaecbac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Mar 2024 11:18:21 -1000 Subject: [PATCH 1630/1691] Avoid tracking import executor jobs (#114453) --- homeassistant/core.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 3b52b020957..2ed4de35925 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -783,11 +783,11 @@ class HomeAssistant: def async_add_import_executor_job( self, target: Callable[[*_Ts], _T], *args: *_Ts ) -> asyncio.Future[_T]: - """Add an import executor job from within the event loop.""" - task = self.loop.run_in_executor(self.import_executor, target, *args) - self._tasks.add(task) - task.add_done_callback(self._tasks.remove) - return task + """Add an import executor job from within the event loop. + + The future returned from this method must be awaited in the event loop. + """ + return self.loop.run_in_executor(self.import_executor, target, *args) @overload @callback From 4f761c25d83cf6a575835b3e6aac9b7b2b28a20a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 29 Mar 2024 22:13:31 +0100 Subject: [PATCH 1631/1691] Update frontend to 20240329.1 (#114459) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index a8f14187d48..7864801a986 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240329.0"] + "requirements": ["home-assistant-frontend==20240329.1"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2ebb82d2c75..1d60b74f18f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240329.0 +home-assistant-frontend==20240329.1 home-assistant-intents==2024.3.27 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index dd59129a7e3..0a94eb110e1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240329.0 +home-assistant-frontend==20240329.1 # homeassistant.components.conversation home-assistant-intents==2024.3.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c46bf2e5a59..3a3ebbbb077 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240329.0 +home-assistant-frontend==20240329.1 # homeassistant.components.conversation home-assistant-intents==2024.3.27 From 11b8b01cde0424c3592dd22bd6f6ace14827bc6a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 29 Mar 2024 22:22:45 +0100 Subject: [PATCH 1632/1691] Bump version to 2024.4.0b3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4fca9fa50c3..f56ce656157 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b2" +PATCH_VERSION: Final = "0b3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 790ee03d76b..73bfdd6d5d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b2" +version = "2024.4.0b3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From e8ee2fd25cd705369f913db98259f7b44ed46584 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 30 Mar 2024 18:48:57 +0300 Subject: [PATCH 1633/1691] Cleanup Shelly RGBW light entities (#114410) --- homeassistant/components/shelly/const.py | 2 + homeassistant/components/shelly/light.py | 17 +++++ homeassistant/components/shelly/utils.py | 12 ++++ tests/components/shelly/__init__.py | 12 ++++ tests/components/shelly/conftest.py | 6 ++ tests/components/shelly/test_light.py | 88 +++++++++++++++++++++++- 6 files changed, 134 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 3580bcf9b38..2ac0416bb6c 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -234,3 +234,5 @@ DEVICES_WITHOUT_FIRMWARE_CHANGELOG = ( ) CONF_GEN = "gen" + +SHELLY_PLUS_RGBW_CHANNELS = 4 diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index 6c28023a5e3..d0590fc7c20 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -14,6 +14,7 @@ from homeassistant.components.light import ( ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_TRANSITION, + DOMAIN as LIGHT_DOMAIN, ColorMode, LightEntity, LightEntityFeature, @@ -34,12 +35,14 @@ from .const import ( RGBW_MODELS, RPC_MIN_TRANSITION_TIME_SEC, SHBLB_1_RGB_EFFECTS, + SHELLY_PLUS_RGBW_CHANNELS, STANDARD_RGB_EFFECTS, ) from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator, get_entry_data from .entity import ShellyBlockEntity, ShellyRpcEntity from .utils import ( async_remove_shelly_entity, + async_remove_shelly_rpc_entities, brightness_to_percentage, get_device_entry_gen, get_rpc_key_ids, @@ -118,14 +121,28 @@ def async_setup_rpc_entry( return if light_key_ids := get_rpc_key_ids(coordinator.device.status, "light"): + # Light mode remove RGB & RGBW entities, add light entities + async_remove_shelly_rpc_entities( + hass, LIGHT_DOMAIN, coordinator.mac, ["rgb:0", "rgbw:0"] + ) async_add_entities(RpcShellyLight(coordinator, id_) for id_ in light_key_ids) return + light_keys = [f"light:{i}" for i in range(SHELLY_PLUS_RGBW_CHANNELS)] + if rgb_key_ids := get_rpc_key_ids(coordinator.device.status, "rgb"): + # RGB mode remove light & RGBW entities, add RGB entity + async_remove_shelly_rpc_entities( + hass, LIGHT_DOMAIN, coordinator.mac, [*light_keys, "rgbw:0"] + ) async_add_entities(RpcShellyRgbLight(coordinator, id_) for id_ in rgb_key_ids) return if rgbw_key_ids := get_rpc_key_ids(coordinator.device.status, "rgbw"): + # RGBW mode remove light & RGB entities, add RGBW entity + async_remove_shelly_rpc_entities( + hass, LIGHT_DOMAIN, coordinator.mac, [*light_keys, "rgb:0"] + ) async_add_entities(RpcShellyRgbwLight(coordinator, id_) for id_ in rgbw_key_ids) diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index d26e3dc11f3..ce98e0d5c12 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -488,3 +488,15 @@ async def async_shutdown_device(device: BlockDevice | RpcDevice) -> None: await device.shutdown() if isinstance(device, BlockDevice): device.shutdown() + + +@callback +def async_remove_shelly_rpc_entities( + hass: HomeAssistant, domain: str, mac: str, keys: list[str] +) -> None: + """Remove RPC based Shelly entity.""" + entity_reg = er_async_get(hass) + for key in keys: + if entity_id := entity_reg.async_get_entity_id(domain, DOMAIN, f"{mac}-{key}"): + LOGGER.debug("Removing entity: %s", entity_id) + entity_reg.async_remove(entity_id) diff --git a/tests/components/shelly/__init__.py b/tests/components/shelly/__init__.py index 2dc9012d863..348b1115a6f 100644 --- a/tests/components/shelly/__init__.py +++ b/tests/components/shelly/__init__.py @@ -126,6 +126,18 @@ def register_entity( return f"{domain}.{object_id}" +def get_entity( + hass: HomeAssistant, + domain: str, + unique_id: str, +) -> str | None: + """Get Shelly entity.""" + entity_registry = async_get(hass) + return entity_registry.async_get_entity_id( + domain, DOMAIN, f"{MOCK_MAC}-{unique_id}" + ) + + def get_entity_state(hass: HomeAssistant, entity_id: str) -> str: """Return entity state.""" entity = hass.states.get(entity_id) diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 9a73252ca6c..3cd27101f76 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -169,6 +169,9 @@ MOCK_CONFIG = { "input:1": {"id": 1, "type": "analog", "enable": True}, "input:2": {"id": 2, "name": "Gas", "type": "count", "enable": True}, "light:0": {"name": "test light_0"}, + "light:1": {"name": "test light_1"}, + "light:2": {"name": "test light_2"}, + "light:3": {"name": "test light_3"}, "rgb:0": {"name": "test rgb_0"}, "rgbw:0": {"name": "test rgbw_0"}, "switch:0": {"name": "test switch_0"}, @@ -225,6 +228,9 @@ MOCK_STATUS_RPC = { "input:1": {"id": 1, "percent": 89, "xpercent": 8.9}, "input:2": {"id": 2, "counts": {"total": 56174, "xtotal": 561.74}}, "light:0": {"output": True, "brightness": 53.0}, + "light:1": {"output": True, "brightness": 53.0}, + "light:2": {"output": True, "brightness": 53.0}, + "light:3": {"output": True, "brightness": 53.0}, "rgb:0": {"output": True, "brightness": 53.0, "rgb": [45, 55, 65]}, "rgbw:0": {"output": True, "brightness": 53.0, "rgb": [21, 22, 23], "white": 120}, "cloud": {"connected": False}, diff --git a/tests/components/shelly/test_light.py b/tests/components/shelly/test_light.py index cca318c364d..2c464a8c39c 100644 --- a/tests/components/shelly/test_light.py +++ b/tests/components/shelly/test_light.py @@ -29,6 +29,7 @@ from homeassistant.components.light import ( ColorMode, LightEntityFeature, ) +from homeassistant.components.shelly.const import SHELLY_PLUS_RGBW_CHANNELS from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, @@ -38,7 +39,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_registry import EntityRegistry -from . import init_integration, mutate_rpc_device_status +from . import get_entity, init_integration, mutate_rpc_device_status, register_entity from .conftest import mock_white_light_set_state RELAY_BLOCK_ID = 0 @@ -587,7 +588,8 @@ async def test_rpc_device_rgb_profile( monkeypatch: pytest.MonkeyPatch, ) -> None: """Test RPC device in RGB profile.""" - monkeypatch.delitem(mock_rpc_device.status, "light:0") + for i in range(SHELLY_PLUS_RGBW_CHANNELS): + monkeypatch.delitem(mock_rpc_device.status, f"light:{i}") monkeypatch.delitem(mock_rpc_device.status, "rgbw:0") entity_id = "light.test_rgb_0" await init_integration(hass, 2) @@ -633,7 +635,8 @@ async def test_rpc_device_rgbw_profile( monkeypatch: pytest.MonkeyPatch, ) -> None: """Test RPC device in RGBW profile.""" - monkeypatch.delitem(mock_rpc_device.status, "light:0") + for i in range(SHELLY_PLUS_RGBW_CHANNELS): + monkeypatch.delitem(mock_rpc_device.status, f"light:{i}") monkeypatch.delitem(mock_rpc_device.status, "rgb:0") entity_id = "light.test_rgbw_0" await init_integration(hass, 2) @@ -673,3 +676,82 @@ async def test_rpc_device_rgbw_profile( entry = entity_registry.async_get(entity_id) assert entry assert entry.unique_id == "123456789ABC-rgbw:0" + + +async def test_rpc_rgbw_device_light_mode_remove_others( + hass: HomeAssistant, + mock_rpc_device: Mock, + entity_registry: EntityRegistry, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test Shelly RPC RGBW device in light mode removes RGB/RGBW entities.""" + # register lights + monkeypatch.delitem(mock_rpc_device.status, "rgb:0") + monkeypatch.delitem(mock_rpc_device.status, "rgbw:0") + register_entity(hass, LIGHT_DOMAIN, "test_rgb_0", "rgb:0") + register_entity(hass, LIGHT_DOMAIN, "test_rgbw_0", "rgbw:0") + + # verify RGB & RGBW entities created + assert get_entity(hass, LIGHT_DOMAIN, "rgb:0") is not None + assert get_entity(hass, LIGHT_DOMAIN, "rgbw:0") is not None + + # init to remove RGB & RGBW + await init_integration(hass, 2) + + # verify we have 4 lights + for i in range(SHELLY_PLUS_RGBW_CHANNELS): + entity_id = f"light.test_light_{i}" + assert hass.states.get(entity_id).state == STATE_ON + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.unique_id == f"123456789ABC-light:{i}" + + # verify RGB & RGBW entities removed + assert get_entity(hass, LIGHT_DOMAIN, "rgb:0") is None + assert get_entity(hass, LIGHT_DOMAIN, "rgbw:0") is None + + +@pytest.mark.parametrize( + ("active_mode", "removed_mode"), + [ + ("rgb", "rgbw"), + ("rgbw", "rgb"), + ], +) +async def test_rpc_rgbw_device_rgb_w_modes_remove_others( + hass: HomeAssistant, + mock_rpc_device: Mock, + entity_registry: EntityRegistry, + monkeypatch: pytest.MonkeyPatch, + active_mode: str, + removed_mode: str, +) -> None: + """Test Shelly RPC RGBW device in RGB/W modes other lights.""" + removed_key = f"{removed_mode}:0" + + # register lights + for i in range(SHELLY_PLUS_RGBW_CHANNELS): + monkeypatch.delitem(mock_rpc_device.status, f"light:{i}") + entity_id = f"light.test_light_{i}" + register_entity(hass, LIGHT_DOMAIN, entity_id, f"light:{i}") + monkeypatch.delitem(mock_rpc_device.status, f"{removed_mode}:0") + register_entity(hass, LIGHT_DOMAIN, f"test_{removed_key}", removed_key) + + # verify lights entities created + for i in range(SHELLY_PLUS_RGBW_CHANNELS): + assert get_entity(hass, LIGHT_DOMAIN, f"light:{i}") is not None + assert get_entity(hass, LIGHT_DOMAIN, removed_key) is not None + + await init_integration(hass, 2) + + # verify we have RGB/w light + entity_id = f"light.test_{active_mode}_0" + assert hass.states.get(entity_id).state == STATE_ON + entry = entity_registry.async_get(entity_id) + assert entry + assert entry.unique_id == f"123456789ABC-{active_mode}:0" + + # verify light & RGB/W entities removed + for i in range(SHELLY_PLUS_RGBW_CHANNELS): + assert get_entity(hass, LIGHT_DOMAIN, f"light:{i}") is None + assert get_entity(hass, LIGHT_DOMAIN, removed_key) is None From 286a09d737ef3caacf7c9d9c8a317fb719968d0e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Mar 2024 18:16:53 -1000 Subject: [PATCH 1634/1691] Mark executor jobs as background unless created from a tracked task (#114450) * Mark executor jobs as background unless created from a tracked task If the current task is not tracked the executor job should not be a background task to avoid delaying startup and shutdown. Currently any executor job created in a untracked task or background task would end up being tracked and delaying startup/shutdown * import exec has the same issue * Avoid tracking import executor jobs There is no reason to track these jobs as they are always awaited and we do not want to support fire and forget import executor jobs * fix xiaomi_miio * lots of fire time changed without background await * revert changes moved to other PR * more * more * more * m * m * p * fix fire and forget tests * scrape * sonos * system * more * capture callback before block * coverage * more * more races * more races * more * missed some * more fixes * missed some more * fix * remove unneeded * one more race * two --- homeassistant/core.py | 7 +++- .../aurora_abb_powerone/test_sensor.py | 8 ++-- tests/components/cast/test_config_flow.py | 4 +- tests/components/cast/test_media_player.py | 8 ++-- tests/components/fritz/test_image.py | 4 +- tests/components/fritz/test_sensor.py | 2 +- .../components/fritzbox/test_binary_sensor.py | 6 +-- tests/components/fritzbox/test_button.py | 2 +- tests/components/fritzbox/test_climate.py | 12 +++--- tests/components/fritzbox/test_cover.py | 2 +- tests/components/fritzbox/test_light.py | 6 +-- tests/components/fritzbox/test_sensor.py | 6 +-- tests/components/fritzbox/test_switch.py | 6 +-- .../components/geo_rss_events/test_sensor.py | 4 +- tests/components/google_mail/test_sensor.py | 4 +- .../maxcube/test_maxcube_binary_sensor.py | 6 +-- .../maxcube/test_maxcube_climate.py | 22 +++++----- tests/components/metoffice/test_weather.py | 10 ++--- .../mikrotik/test_device_tracker.py | 8 ++-- .../components/monoprice/test_media_player.py | 18 ++++----- .../panasonic_viera/test_media_player.py | 4 +- tests/components/pjlink/test_media_player.py | 4 +- tests/components/profiler/test_init.py | 10 ++--- tests/components/ps4/test_media_player.py | 2 + tests/components/python_script/test_init.py | 40 +++++++++---------- .../components/samsungtv/test_media_player.py | 10 ++--- .../components/schlage/test_binary_sensor.py | 4 +- tests/components/schlage/test_lock.py | 2 +- tests/components/scrape/test_sensor.py | 12 +++--- .../components/solaredge/test_coordinator.py | 10 ++--- tests/components/sonos/conftest.py | 3 +- tests/components/sonos/test_repairs.py | 5 ++- tests/components/sonos/test_sensor.py | 29 +++++++++----- tests/components/sonos/test_speaker.py | 16 +++++++- .../soundtouch/test_media_player.py | 2 +- tests/components/speedtestdotnet/test_init.py | 2 +- .../systemmonitor/test_binary_sensor.py | 2 +- tests/components/systemmonitor/test_sensor.py | 22 +++++----- tests/components/tcp/test_binary_sensor.py | 2 +- tests/components/temper/test_sensor.py | 2 +- .../totalconnect/test_alarm_control_panel.py | 10 ++--- tests/components/uvc/test_camera.py | 12 +++--- tests/components/ws66i/test_media_player.py | 20 +++++----- tests/components/xiaomi_miio/test_vacuum.py | 4 +- .../yale_smart_alarm/test_coordinator.py | 12 +++--- tests/test_core.py | 40 +++++++++++++++++++ 46 files changed, 246 insertions(+), 180 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 2ed4de35925..4794b284fd2 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -774,8 +774,11 @@ class HomeAssistant: ) -> asyncio.Future[_T]: """Add an executor job from within the event loop.""" task = self.loop.run_in_executor(None, target, *args) - self._tasks.add(task) - task.add_done_callback(self._tasks.remove) + + tracked = asyncio.current_task() in self._tasks + task_bucket = self._tasks if tracked else self._background_tasks + task_bucket.add(task) + task.add_done_callback(task_bucket.remove) return task diff --git a/tests/components/aurora_abb_powerone/test_sensor.py b/tests/components/aurora_abb_powerone/test_sensor.py index 178cf165f67..4bc5a5d3086 100644 --- a/tests/components/aurora_abb_powerone/test_sensor.py +++ b/tests/components/aurora_abb_powerone/test_sensor.py @@ -201,7 +201,7 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) ): freezer.tick(SCAN_INTERVAL * 2) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) power = hass.states.get("sensor.mydevicename_total_energy") assert power.state == "unknown" # sun rose again @@ -218,7 +218,7 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) ): freezer.tick(SCAN_INTERVAL * 4) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) power = hass.states.get("sensor.mydevicename_power_output") assert power is not None assert power.state == "45.7" @@ -237,7 +237,7 @@ async def test_sensor_dark(hass: HomeAssistant, freezer: FrozenDateTimeFactory) ): freezer.tick(SCAN_INTERVAL * 6) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) power = hass.states.get("sensor.mydevicename_power_output") assert power.state == "unknown" # should this be 'available'? @@ -277,7 +277,7 @@ async def test_sensor_unknown_error( ): freezer.tick(SCAN_INTERVAL * 2) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ( "Exception: AuroraError('another error') occurred, 2 retries remaining" in caplog.text diff --git a/tests/components/cast/test_config_flow.py b/tests/components/cast/test_config_flow.py index 62c21fc95ee..a7b9311e88b 100644 --- a/tests/components/cast/test_config_flow.py +++ b/tests/components/cast/test_config_flow.py @@ -278,7 +278,7 @@ async def test_known_hosts(hass: HomeAssistant, castbrowser_mock) -> None: result["flow_id"], {"known_hosts": "192.168.0.1, 192.168.0.2"} ) assert result["type"] == "create_entry" - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) config_entry = hass.config_entries.async_entries("cast")[0] assert castbrowser_mock.return_value.start_discovery.call_count == 1 @@ -291,7 +291,7 @@ async def test_known_hosts(hass: HomeAssistant, castbrowser_mock) -> None: user_input={"known_hosts": "192.168.0.11, 192.168.0.12"}, ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) castbrowser_mock.return_value.start_discovery.assert_not_called() castbrowser_mock.assert_not_called() diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 9ef31457d5c..8381f27398a 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -137,8 +137,8 @@ async def async_setup_cast_internal_discovery(hass, config=None): return_value=browser, ) as cast_browser: add_entities = await async_setup_cast(hass, config) - await hass.async_block_till_done() - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) + await hass.async_block_till_done(wait_background_tasks=True) assert browser.start_discovery.call_count == 1 @@ -209,8 +209,8 @@ async def async_setup_media_player_cast(hass: HomeAssistant, info: ChromecastInf entry = MockConfigEntry(data=data, domain="cast") entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) + await hass.async_block_till_done(wait_background_tasks=True) discovery_callback = cast_browser.call_args[0][0].add_cast diff --git a/tests/components/fritz/test_image.py b/tests/components/fritz/test_image.py index 85d02eff153..5d6b9265760 100644 --- a/tests/components/fritz/test_image.py +++ b/tests/components/fritz/test_image.py @@ -199,7 +199,7 @@ async def test_image_update_unavailable( # fritzbox becomes unavailable fc_class_mock().call_action_side_effect(ReadTimeout) async_fire_time_changed(hass, utcnow() + timedelta(seconds=60)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("image.mock_title_guestwifi") assert state.state == STATE_UNKNOWN @@ -207,7 +207,7 @@ async def test_image_update_unavailable( # fritzbox is available again fc_class_mock().call_action_side_effect(None) async_fire_time_changed(hass, utcnow() + timedelta(seconds=60)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("image.mock_title_guestwifi") assert state.state != STATE_UNKNOWN diff --git a/tests/components/fritz/test_sensor.py b/tests/components/fritz/test_sensor.py index 4427fc6961e..37116e66719 100644 --- a/tests/components/fritz/test_sensor.py +++ b/tests/components/fritz/test_sensor.py @@ -134,7 +134,7 @@ async def test_sensor_update_fail( fc_class_mock().call_action_side_effect(FritzConnectionException) async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=300)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) sensors = hass.states.async_all(SENSOR_DOMAIN) for sensor in sensors: diff --git a/tests/components/fritzbox/test_binary_sensor.py b/tests/components/fritzbox/test_binary_sensor.py index 3828cedc67f..3e1a2691f67 100644 --- a/tests/components/fritzbox/test_binary_sensor.py +++ b/tests/components/fritzbox/test_binary_sensor.py @@ -104,7 +104,7 @@ async def test_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 2 assert fritz().login.call_count == 1 @@ -123,7 +123,7 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 2 assert fritz().login.call_count == 1 @@ -146,7 +146,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_device_alarm") assert state diff --git a/tests/components/fritzbox/test_button.py b/tests/components/fritzbox/test_button.py index f254b2e0710..89e8d8357dd 100644 --- a/tests/components/fritzbox/test_button.py +++ b/tests/components/fritzbox/test_button.py @@ -65,7 +65,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_template") assert state diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index a201eab3665..073a67f22c1 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -145,7 +145,7 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_next_scheduled_preset") assert state @@ -203,7 +203,7 @@ async def test_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert fritz().update_devices.call_count == 2 @@ -243,7 +243,7 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 4 assert fritz().login.call_count == 4 @@ -386,7 +386,7 @@ async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert fritz().update_devices.call_count == 2 @@ -397,7 +397,7 @@ async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert fritz().update_devices.call_count == 3 @@ -422,7 +422,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_climate") assert state diff --git a/tests/components/fritzbox/test_cover.py b/tests/components/fritzbox/test_cover.py index b723ac97d06..6c301fc8f46 100644 --- a/tests/components/fritzbox/test_cover.py +++ b/tests/components/fritzbox/test_cover.py @@ -108,7 +108,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_climate") assert state diff --git a/tests/components/fritzbox/test_light.py b/tests/components/fritzbox/test_light.py index b750a2e9275..45920c7c3ee 100644 --- a/tests/components/fritzbox/test_light.py +++ b/tests/components/fritzbox/test_light.py @@ -237,7 +237,7 @@ async def test_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 2 assert fritz().login.call_count == 1 @@ -259,7 +259,7 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 4 assert fritz().login.call_count == 4 @@ -294,7 +294,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_light") assert state diff --git a/tests/components/fritzbox/test_sensor.py b/tests/components/fritzbox/test_sensor.py index 48b769eaac2..63d0b67d7f4 100644 --- a/tests/components/fritzbox/test_sensor.py +++ b/tests/components/fritzbox/test_sensor.py @@ -87,7 +87,7 @@ async def test_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 2 assert fritz().login.call_count == 1 @@ -105,7 +105,7 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 4 assert fritz().login.call_count == 4 @@ -128,7 +128,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_device_temperature") assert state diff --git a/tests/components/fritzbox/test_switch.py b/tests/components/fritzbox/test_switch.py index 67393bc09a5..417b355b396 100644 --- a/tests/components/fritzbox/test_switch.py +++ b/tests/components/fritzbox/test_switch.py @@ -151,7 +151,7 @@ async def test_update(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 2 assert fritz().login.call_count == 1 @@ -169,7 +169,7 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert fritz().update_devices.call_count == 4 assert fritz().login.call_count == 4 @@ -207,7 +207,7 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: next_update = dt_util.utcnow() + timedelta(seconds=200) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(f"{DOMAIN}.new_switch") assert state diff --git a/tests/components/geo_rss_events/test_sensor.py b/tests/components/geo_rss_events/test_sensor.py index 76f1709bd75..d19262c3339 100644 --- a/tests/components/geo_rss_events/test_sensor.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -99,7 +99,7 @@ async def test_setup( # so no changes to entities. mock_feed.return_value.update.return_value = "OK_NO_DATA", None async_fire_time_changed(hass, utcnow + geo_rss_events.SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 1 @@ -109,7 +109,7 @@ async def test_setup( # Simulate an update - empty data, removes all entities mock_feed.return_value.update.return_value = "ERROR", None async_fire_time_changed(hass, utcnow + 2 * geo_rss_events.SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) all_states = hass.states.async_all() assert len(all_states) == 1 diff --git a/tests/components/google_mail/test_sensor.py b/tests/components/google_mail/test_sensor.py index e0b072d4b7d..6f2f1a4ec32 100644 --- a/tests/components/google_mail/test_sensor.py +++ b/tests/components/google_mail/test_sensor.py @@ -46,7 +46,7 @@ async def test_sensors( ): next_update = dt_util.utcnow() + timedelta(minutes=15) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(SENSOR) assert state.state == result @@ -61,7 +61,7 @@ async def test_sensor_reauth_trigger( with patch(TOKEN, side_effect=RefreshError): next_update = dt_util.utcnow() + timedelta(minutes=15) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) flows = hass.config_entries.flow.async_progress() diff --git a/tests/components/maxcube/test_maxcube_binary_sensor.py b/tests/components/maxcube/test_maxcube_binary_sensor.py index cc86f389884..32ec4e92ee1 100644 --- a/tests/components/maxcube/test_maxcube_binary_sensor.py +++ b/tests/components/maxcube/test_maxcube_binary_sensor.py @@ -43,7 +43,7 @@ async def test_window_shuttler( windowshutter.is_open = False async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_OFF @@ -68,12 +68,12 @@ async def test_window_shuttler_battery( windowshutter.battery = 1 # maxcube-api MAX_DEVICE_BATTERY_LOW async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(BATTERY_ENTITY_ID) assert state.state == STATE_ON # on means low windowshutter.battery = 0 # maxcube-api MAX_DEVICE_BATTERY_OK async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(BATTERY_ENTITY_ID) assert state.state == STATE_OFF # off means normal diff --git a/tests/components/maxcube/test_maxcube_climate.py b/tests/components/maxcube/test_maxcube_climate.py index cb4dc510605..e1e7dc57c47 100644 --- a/tests/components/maxcube/test_maxcube_climate.py +++ b/tests/components/maxcube/test_maxcube_climate.py @@ -140,7 +140,7 @@ async def test_thermostat_set_hvac_mode_off( thermostat.valve_position = 0 async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.OFF @@ -168,8 +168,8 @@ async def test_thermostat_set_hvac_mode_heat( thermostat.mode = MAX_DEVICE_MODE_MANUAL async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.HEAT @@ -204,7 +204,7 @@ async def test_thermostat_set_temperature( thermostat.valve_position = 0 async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.AUTO @@ -248,7 +248,7 @@ async def test_thermostat_set_preset_on( thermostat.target_temperature = ON_TEMPERATURE async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.HEAT @@ -273,7 +273,7 @@ async def test_thermostat_set_preset_comfort( thermostat.target_temperature = thermostat.comfort_temperature async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.HEAT @@ -298,7 +298,7 @@ async def test_thermostat_set_preset_eco( thermostat.target_temperature = thermostat.eco_temperature async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.HEAT @@ -323,7 +323,7 @@ async def test_thermostat_set_preset_away( thermostat.target_temperature = thermostat.eco_temperature async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.HEAT @@ -348,7 +348,7 @@ async def test_thermostat_set_preset_boost( thermostat.target_temperature = thermostat.eco_temperature async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == HVACMode.AUTO @@ -401,7 +401,7 @@ async def test_wallthermostat_set_hvac_mode_heat( wallthermostat.target_temperature = MIN_TEMPERATURE async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(WALL_ENTITY_ID) assert state.state == HVACMode.HEAT @@ -425,7 +425,7 @@ async def test_wallthermostat_set_hvac_mode_auto( wallthermostat.target_temperature = 23.0 async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(WALL_ENTITY_ID) assert state.state == HVACMode.AUTO diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index 2aa673d4010..64a85897738 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -125,7 +125,7 @@ async def test_site_cannot_update( future_time = utcnow() + timedelta(minutes=20) async_fire_time_changed(hass, future_time) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) weather = hass.states.get("weather.met_office_wavertree_daily") assert weather.state == STATE_UNAVAILABLE @@ -297,7 +297,7 @@ async def test_forecast_service( # Trigger data refetch freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert wavertree_data["wavertree_daily_mock"].call_count == 2 assert wavertree_data["wavertree_hourly_mock"].call_count == 1 @@ -324,7 +324,7 @@ async def test_forecast_service( freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) response = await hass.services.async_call( WEATHER_DOMAIN, @@ -412,7 +412,7 @@ async def test_forecast_subscription( freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) msg = await client.receive_json() assert msg["id"] == subscription_id @@ -430,6 +430,6 @@ async def test_forecast_subscription( ) freezer.tick(timedelta(seconds=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) msg = await client.receive_json() assert msg["success"] diff --git a/tests/components/mikrotik/test_device_tracker.py b/tests/components/mikrotik/test_device_tracker.py index 47ddc038f69..89dc37fd781 100644 --- a/tests/components/mikrotik/test_device_tracker.py +++ b/tests/components/mikrotik/test_device_tracker.py @@ -88,7 +88,7 @@ async def test_device_trackers( WIRELESS_DATA.append(DEVICE_2_WIRELESS) async_fire_time_changed(hass, utcnow() + timedelta(seconds=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) device_2 = hass.states.get("device_tracker.device_2") assert device_2 @@ -101,7 +101,7 @@ async def test_device_trackers( del WIRELESS_DATA[1] # device 2 is removed from wireless list with freeze_time(utcnow() + timedelta(minutes=4)): async_fire_time_changed(hass, utcnow() + timedelta(minutes=4)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) device_2 = hass.states.get("device_tracker.device_2") assert device_2 @@ -110,7 +110,7 @@ async def test_device_trackers( # test state changes to away if last_seen past consider_home_interval with freeze_time(utcnow() + timedelta(minutes=6)): async_fire_time_changed(hass, utcnow() + timedelta(minutes=6)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) device_2 = hass.states.get("device_tracker.device_2") assert device_2 @@ -266,7 +266,7 @@ async def test_update_failed(hass: HomeAssistant, mock_device_registry_devices) mikrotik.hub.MikrotikData, "command", side_effect=mikrotik.errors.CannotConnect ): async_fire_time_changed(hass, utcnow() + timedelta(seconds=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) device_1 = hass.states.get("device_tracker.device_1") assert device_1 diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index a0afd37f3b2..f7d88692cf5 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -183,7 +183,7 @@ async def test_service_calls_with_entity_id(hass: HomeAssistant) -> None: # Restoring other media player to its previous state # The zone should not be restored await _call_monoprice_service(hass, SERVICE_RESTORE, {"entity_id": ZONE_2_ID}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Checking that values were not (!) restored state = hass.states.get(ZONE_1_ID) @@ -193,7 +193,7 @@ async def test_service_calls_with_entity_id(hass: HomeAssistant) -> None: # Restoring media player to its previous state await _call_monoprice_service(hass, SERVICE_RESTORE, {"entity_id": ZONE_1_ID}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -226,7 +226,7 @@ async def test_service_calls_with_all_entities(hass: HomeAssistant) -> None: # Restoring media player to its previous state await _call_monoprice_service(hass, SERVICE_RESTORE, {"entity_id": "all"}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -259,7 +259,7 @@ async def test_service_calls_without_relevant_entities(hass: HomeAssistant) -> N # Restoring media player to its previous state await _call_monoprice_service(hass, SERVICE_RESTORE, {"entity_id": "light.demo"}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -273,7 +273,7 @@ async def test_restore_without_snapshort(hass: HomeAssistant) -> None: with patch.object(MockMonoprice, "restore_zone") as method_call: await _call_monoprice_service(hass, SERVICE_RESTORE, {"entity_id": ZONE_1_ID}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert not method_call.called @@ -295,7 +295,7 @@ async def test_update(hass: HomeAssistant) -> None: monoprice.set_volume(11, 38) await async_update_entity(hass, ZONE_1_ID) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -321,7 +321,7 @@ async def test_failed_update(hass: HomeAssistant) -> None: with patch.object(MockMonoprice, "zone_status", side_effect=SerialException): await async_update_entity(hass, ZONE_1_ID) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -347,7 +347,7 @@ async def test_empty_update(hass: HomeAssistant) -> None: with patch.object(MockMonoprice, "zone_status", return_value=None): await async_update_entity(hass, ZONE_1_ID) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -418,7 +418,7 @@ async def test_unknown_source(hass: HomeAssistant) -> None: monoprice.set_source(11, 5) await async_update_entity(hass, ZONE_1_ID) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) diff --git a/tests/components/panasonic_viera/test_media_player.py b/tests/components/panasonic_viera/test_media_player.py index 1203bf1ed51..dab56542e6a 100644 --- a/tests/components/panasonic_viera/test_media_player.py +++ b/tests/components/panasonic_viera/test_media_player.py @@ -23,7 +23,7 @@ async def test_media_player_handle_URLerror( mock_remote.get_mute = Mock(side_effect=URLError(None, None)) async_fire_time_changed(hass, utcnow() + timedelta(minutes=2)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state_tv = hass.states.get("media_player.panasonic_viera_tv") assert state_tv.state == STATE_UNAVAILABLE @@ -41,7 +41,7 @@ async def test_media_player_handle_HTTPError( mock_remote.get_mute = Mock(side_effect=HTTPError(None, 400, None, None, None)) async_fire_time_changed(hass, utcnow() + timedelta(minutes=2)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state_tv = hass.states.get("media_player.panasonic_viera_tv") assert state_tv.state == STATE_OFF diff --git a/tests/components/pjlink/test_media_player.py b/tests/components/pjlink/test_media_player.py index a6d17233450..d44bc942290 100644 --- a/tests/components/pjlink/test_media_player.py +++ b/tests/components/pjlink/test_media_player.py @@ -208,7 +208,7 @@ async def test_update_unavailable(projector_from_address, hass: HomeAssistant) - projector_from_address.side_effect = socket.timeout async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("media_player.test") assert state.state == "unavailable" @@ -237,7 +237,7 @@ async def test_unavailable_time(mocked_projector, hass: HomeAssistant) -> None: mocked_projector.get_power.side_effect = ProjectorError("unavailable time") async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("media_player.test") assert state.state == "off" diff --git a/tests/components/profiler/test_init.py b/tests/components/profiler/test_init.py index 1140dc74849..3cade465347 100644 --- a/tests/components/profiler/test_init.py +++ b/tests/components/profiler/test_init.py @@ -332,7 +332,7 @@ async def test_log_object_sources( caplog.clear() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=11)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "No new object growth found" in caplog.text fake_object2 = FakeObject() @@ -344,7 +344,7 @@ async def test_log_object_sources( caplog.clear() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=21)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "New object FakeObject (1/2)" in caplog.text many_objects = [FakeObject() for _ in range(30)] @@ -352,7 +352,7 @@ async def test_log_object_sources( caplog.clear() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "New object FakeObject (2/30)" in caplog.text assert "New objects overflowed by {'FakeObject': 25}" in caplog.text @@ -362,7 +362,7 @@ async def test_log_object_sources( caplog.clear() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=41)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "FakeObject" not in caplog.text assert "No new object growth found" not in caplog.text @@ -370,7 +370,7 @@ async def test_log_object_sources( await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=51)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "FakeObject" not in caplog.text assert "No new object growth found" not in caplog.text diff --git a/tests/components/ps4/test_media_player.py b/tests/components/ps4/test_media_player.py index 875b049d8c3..6adcad03016 100644 --- a/tests/components/ps4/test_media_player.py +++ b/tests/components/ps4/test_media_player.py @@ -234,6 +234,7 @@ async def test_media_attributes_are_fetched(hass: HomeAssistant) -> None: with patch(mock_func, return_value=mock_result) as mock_fetch: await mock_ddp_response(hass, MOCK_STATUS_PLAYING) + await hass.async_block_till_done(wait_background_tasks=True) mock_state = hass.states.get(mock_entity_id) mock_attrs = dict(mock_state.attributes) @@ -255,6 +256,7 @@ async def test_media_attributes_are_fetched(hass: HomeAssistant) -> None: with patch(mock_func, return_value=mock_result) as mock_fetch_app: await mock_ddp_response(hass, MOCK_STATUS_PLAYING) + await hass.async_block_till_done(wait_background_tasks=True) mock_state = hass.states.get(mock_entity_id) mock_attrs = dict(mock_state.attributes) diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index bec94db71f9..1c6fead6c4a 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -78,7 +78,7 @@ hass.states.set('test.entity', data.get('name', 'not set')) """ hass.async_add_executor_job(execute, hass, "test.py", source, {"name": "paulus"}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.is_state("test.entity", "paulus") @@ -96,7 +96,7 @@ print("This triggers warning.") """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Don't use print() inside scripts." in caplog.text @@ -111,7 +111,7 @@ logger.info('Logging from inside script') """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Logging from inside script" in caplog.text @@ -126,7 +126,7 @@ this is not valid Python """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "Error loading script test.py" in caplog.text @@ -140,8 +140,8 @@ async def test_execute_runtime_error( raise Exception('boom') """ - hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_add_executor_job(execute, hass, "test.py", source, {}) + await hass.async_block_till_done(wait_background_tasks=True) assert "Error executing script: boom" in caplog.text @@ -153,7 +153,7 @@ raise Exception('boom') """ task = hass.async_add_executor_job(execute, hass, "test.py", source, {}, True) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert type(task.exception()) == HomeAssistantError assert "Error executing script (Exception): boom" in str(task.exception()) @@ -168,7 +168,7 @@ async def test_accessing_async_methods( hass.async_stop() """ - hass.async_add_executor_job(execute, hass, "test.py", source, {}) + await hass.async_add_executor_job(execute, hass, "test.py", source, {}) await hass.async_block_till_done() assert "Not allowed to access async methods" in caplog.text @@ -181,7 +181,7 @@ hass.async_stop() """ task = hass.async_add_executor_job(execute, hass, "test.py", source, {}, True) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert type(task.exception()) == ServiceValidationError assert "Not allowed to access async methods" in str(task.exception()) @@ -198,7 +198,7 @@ mylist = [1, 2, 3, 4] logger.info('Logging from inside script: %s %s' % (mydict["a"], mylist[2])) """ - hass.async_add_executor_job(execute, hass, "test.py", source, {}) + await hass.async_add_executor_job(execute, hass, "test.py", source, {}) await hass.async_block_till_done() assert "Logging from inside script: 1 3" in caplog.text @@ -217,7 +217,7 @@ async def test_accessing_forbidden_methods( "time.tzset()": "TimeWrapper.tzset", }.items(): caplog.records.clear() - hass.async_add_executor_job(execute, hass, "test.py", source, {}) + await hass.async_add_executor_job(execute, hass, "test.py", source, {}) await hass.async_block_till_done() assert f"Not allowed to access {name}" in caplog.text @@ -231,7 +231,7 @@ async def test_accessing_forbidden_methods_with_response(hass: HomeAssistant) -> "time.tzset()": "TimeWrapper.tzset", }.items(): task = hass.async_add_executor_job(execute, hass, "test.py", source, {}, True) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert type(task.exception()) == ServiceValidationError assert f"Not allowed to access {name}" in str(task.exception()) @@ -244,7 +244,7 @@ for i in [1, 2]: hass.states.set('hello.{}'.format(i), 'world') """ - hass.async_add_executor_job(execute, hass, "test.py", source, {}) + await hass.async_add_executor_job(execute, hass, "test.py", source, {}) await hass.async_block_till_done() assert hass.states.is_state("hello.1", "world") @@ -279,7 +279,7 @@ hass.states.set('hello.ab_list', '{}'.format(ab_list)) """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.is_state("hello.a", "1") assert hass.states.is_state("hello.b", "2") @@ -302,7 +302,7 @@ hass.states.set('hello.b', a[1]) hass.states.set('hello.c', a[2]) """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.is_state("hello.a", "1") assert hass.states.is_state("hello.b", "2") @@ -325,7 +325,7 @@ hass.states.set('module.datetime', """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.is_state("module.time", "1986") assert hass.states.is_state("module.time_strptime", "12:34") @@ -351,7 +351,7 @@ def b(): b() """ hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.is_state("hello.a", "one") assert hass.states.is_state("hello.b", "two") @@ -517,7 +517,7 @@ time.sleep(5) with patch("homeassistant.components.python_script.time.sleep"): hass.async_add_executor_job(execute, hass, "test.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert caplog.text.count("time.sleep") == 1 @@ -664,7 +664,7 @@ hass.states.set('hello.c', c) """ hass.async_add_executor_job(execute, hass, "aug_assign.py", source, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get("hello.a").state == str(((10 + 20) * 5) - 8) assert hass.states.get("hello.b").state == ("foo" + "bar") * 2 @@ -686,5 +686,5 @@ async def test_prohibited_augmented_assignment_operations( ) -> None: """Test that prohibited augmented assignment operations raise an error.""" hass.async_add_executor_job(execute, hass, "aug_assign_prohibited.py", case, {}) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert error in caplog.text diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index f874b92305b..db4f3f0e41f 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -200,7 +200,7 @@ async def test_setup_websocket_2( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(entity_id) assert state @@ -225,7 +225,7 @@ async def test_setup_encrypted_websocket( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state @@ -242,7 +242,7 @@ async def test_update_on( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_ON @@ -262,7 +262,7 @@ async def test_update_off( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_UNAVAILABLE @@ -290,7 +290,7 @@ async def test_update_off_ws_no_power_state( next_update = mock_now + timedelta(minutes=5) freezer.move_to(next_update) async_fire_time_changed(hass, next_update) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ENTITY_ID) assert state.state == STATE_OFF diff --git a/tests/components/schlage/test_binary_sensor.py b/tests/components/schlage/test_binary_sensor.py index 4673f263c8c..97f11577b86 100644 --- a/tests/components/schlage/test_binary_sensor.py +++ b/tests/components/schlage/test_binary_sensor.py @@ -22,7 +22,7 @@ async def test_keypad_disabled_binary_sensor( # Make the coordinator refresh data. async_fire_time_changed(hass, utcnow() + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled") assert keypad is not None @@ -43,7 +43,7 @@ async def test_keypad_disabled_binary_sensor_use_previous_logs_on_failure( # Make the coordinator refresh data. async_fire_time_changed(hass, utcnow() + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled") assert keypad is not None diff --git a/tests/components/schlage/test_lock.py b/tests/components/schlage/test_lock.py index 0972aa97033..5b26da7b27e 100644 --- a/tests/components/schlage/test_lock.py +++ b/tests/components/schlage/test_lock.py @@ -59,7 +59,7 @@ async def test_changed_by( # Make the coordinator refresh data. async_fire_time_changed(hass, utcnow() + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mock_lock.last_changed_by.assert_called_once_with() lock_device = hass.states.get("lock.vault_door") diff --git a/tests/components/scrape/test_sensor.py b/tests/components/scrape/test_sensor.py index 41da2eb9a79..4d9c2b732dc 100644 --- a/tests/components/scrape/test_sensor.py +++ b/tests/components/scrape/test_sensor.py @@ -261,7 +261,7 @@ async def test_scrape_sensor_no_data_refresh(hass: HomeAssistant) -> None: mocker.payload = "test_scrape_sensor_no_data" async_fire_time_changed(hass, dt_util.utcnow() + DEFAULT_SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.ha_version") assert state is not None @@ -541,7 +541,7 @@ async def test_templates_with_yaml(hass: HomeAssistant) -> None: hass, dt_util.utcnow() + timedelta(minutes=10), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.get_values_with_template") assert state.state == "Current Version: 2021.12.10" @@ -555,7 +555,7 @@ async def test_templates_with_yaml(hass: HomeAssistant) -> None: hass, dt_util.utcnow() + timedelta(minutes=20), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.get_values_with_template") assert state.state == STATE_UNAVAILABLE @@ -568,7 +568,7 @@ async def test_templates_with_yaml(hass: HomeAssistant) -> None: hass, dt_util.utcnow() + timedelta(minutes=30), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.get_values_with_template") assert state.state == "Current Version: 2021.12.10" @@ -608,7 +608,7 @@ async def test_availability( hass.states.async_set("sensor.input1", "on") freezer.tick(timedelta(minutes=10)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.current_version") assert state.state == "2021.12.10" @@ -618,7 +618,7 @@ async def test_availability( freezer.tick(timedelta(minutes=10)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.current_version") assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/solaredge/test_coordinator.py b/tests/components/solaredge/test_coordinator.py index 4bd9dee930c..b1496d18d93 100644 --- a/tests/components/solaredge/test_coordinator.py +++ b/tests/components/solaredge/test_coordinator.py @@ -53,7 +53,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity( mock_solaredge().get_overview.return_value = mock_overview_data freezer.tick(OVERVIEW_UPDATE_DELAY) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.solaredge_lifetime_energy") assert state assert state.state == str(mock_overview_data["overview"]["lifeTimeData"]["energy"]) @@ -63,7 +63,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity( mock_solaredge().get_overview.return_value = mock_overview_data freezer.tick(OVERVIEW_UPDATE_DELAY) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.solaredge_lifetime_energy") assert state @@ -74,7 +74,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity( mock_solaredge().get_overview.return_value = mock_overview_data freezer.tick(OVERVIEW_UPDATE_DELAY) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.solaredge_lifetime_energy") assert state @@ -85,7 +85,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity( mock_solaredge().get_overview.return_value = mock_overview_data freezer.tick(OVERVIEW_UPDATE_DELAY) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.solaredge_energy_this_year") assert state @@ -103,7 +103,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity( mock_solaredge().get_overview.return_value = mock_overview_data freezer.tick(OVERVIEW_UPDATE_DELAY) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.solaredge_lifetime_energy") assert state diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 0b3834992d8..00858a180a3 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -94,8 +94,9 @@ def async_setup_sonos(hass, config_entry, fire_zgs_event): async def _wrapper(): config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) await fire_zgs_event() + await hass.async_block_till_done(wait_background_tasks=True) return _wrapper diff --git a/tests/components/sonos/test_repairs.py b/tests/components/sonos/test_repairs.py index cc1f59c5cd0..cf64912e498 100644 --- a/tests/components/sonos/test_repairs.py +++ b/tests/components/sonos/test_repairs.py @@ -28,10 +28,12 @@ async def test_subscription_repair_issues( config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() + await hass.async_block_till_done() # Ensure an issue is registered on subscription failure + sub_callback = subscription.callback async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert issue_registry.async_get_issue(DOMAIN, SUB_FAIL_ISSUE_ID) # Ensure the issue still exists after reload @@ -42,7 +44,6 @@ async def test_subscription_repair_issues( # Ensure the issue has been removed after a successful subscription callback variables = {"ZoneGroupState": zgs_discovery} event = SonosMockEvent(soco, soco.zoneGroupTopology, variables) - sub_callback = subscription.callback sub_callback(event) await hass.async_block_till_done() assert not issue_registry.async_get_issue(DOMAIN, SUB_FAIL_ISSUE_ID) diff --git a/tests/components/sonos/test_sensor.py b/tests/components/sonos/test_sensor.py index 6e4461e5397..1f4ba8d22cd 100644 --- a/tests/components/sonos/test_sensor.py +++ b/tests/components/sonos/test_sensor.py @@ -26,6 +26,7 @@ async def test_entity_registry_unsupported( soco.get_battery_info.side_effect = NotSupportedException await async_setup_sonos() + await hass.async_block_till_done(wait_background_tasks=True) assert "media_player.zone_a" in entity_registry.entities assert "sensor.zone_a_battery" not in entity_registry.entities @@ -36,6 +37,8 @@ async def test_entity_registry_supported( hass: HomeAssistant, async_autosetup_sonos, soco, entity_registry: er.EntityRegistry ) -> None: """Test sonos device with battery registered in the device registry.""" + await hass.async_block_till_done(wait_background_tasks=True) + assert "media_player.zone_a" in entity_registry.entities assert "sensor.zone_a_battery" in entity_registry.entities assert "binary_sensor.zone_a_charging" in entity_registry.entities @@ -69,6 +72,7 @@ async def test_battery_on_s1( soco.get_battery_info.return_value = {} await async_setup_sonos() + await hass.async_block_till_done(wait_background_tasks=True) subscription = soco.deviceProperties.subscribe.return_value sub_callback = subscription.callback @@ -78,7 +82,7 @@ async def test_battery_on_s1( # Update the speaker with a callback event sub_callback(device_properties_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) battery = entity_registry.entities["sensor.zone_a_battery"] battery_state = hass.states.get(battery.entity_id) @@ -101,6 +105,7 @@ async def test_device_payload_without_battery( soco.get_battery_info.return_value = None await async_setup_sonos() + await hass.async_block_till_done(wait_background_tasks=True) subscription = soco.deviceProperties.subscribe.return_value sub_callback = subscription.callback @@ -109,7 +114,7 @@ async def test_device_payload_without_battery( device_properties_event.variables["more_info"] = bad_payload sub_callback(device_properties_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert bad_payload in caplog.text @@ -125,6 +130,7 @@ async def test_device_payload_without_battery_and_ignored_keys( soco.get_battery_info.return_value = None await async_setup_sonos() + await hass.async_block_till_done(wait_background_tasks=True) subscription = soco.deviceProperties.subscribe.return_value sub_callback = subscription.callback @@ -133,7 +139,7 @@ async def test_device_payload_without_battery_and_ignored_keys( device_properties_event.variables["more_info"] = ignored_payload sub_callback(device_properties_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ignored_payload not in caplog.text @@ -150,7 +156,7 @@ async def test_audio_input_sensor( subscription = soco.avTransport.subscribe.return_value sub_callback = subscription.callback sub_callback(tv_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) audio_input_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"] audio_input_state = hass.states.get(audio_input_sensor.entity_id) @@ -161,7 +167,7 @@ async def test_audio_input_sensor( type(soco).soundbar_audio_input_format = no_input_mock async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) no_input_mock.assert_called_once() audio_input_state = hass.states.get(audio_input_sensor.entity_id) @@ -169,13 +175,13 @@ async def test_audio_input_sensor( # Ensure state is not polled when source is not TV and state is already "No input" sub_callback(no_media_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) unpolled_mock = PropertyMock(return_value="Will not be polled") type(soco).soundbar_audio_input_format = unpolled_mock async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) unpolled_mock.assert_not_called() audio_input_state = hass.states.get(audio_input_sensor.entity_id) @@ -199,7 +205,7 @@ async def test_microphone_binary_sensor( # Update the speaker with a callback event subscription = soco.deviceProperties.subscribe.return_value subscription.callback(device_properties_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) mic_binary_sensor_state = hass.states.get(mic_binary_sensor.entity_id) assert mic_binary_sensor_state.state == STATE_ON @@ -225,17 +231,18 @@ async def test_favorites_sensor( empty_event = SonosMockEvent(soco, service, {}) subscription = service.subscribe.return_value subscription.callback(event=empty_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Reload the integration to enable the sensor async_fire_time_changed( hass, dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Trigger subscription callback for speaker discovery await fire_zgs_event() + await hass.async_block_till_done(wait_background_tasks=True) favorites_updated_event = SonosMockEvent( soco, service, {"favorites_update_id": "2", "container_update_i_ds": "FV:2,2"} @@ -245,4 +252,4 @@ async def test_favorites_sensor( return_value=True, ): subscription.callback(event=favorites_updated_event) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) diff --git a/tests/components/sonos/test_speaker.py b/tests/components/sonos/test_speaker.py index e0fc4c3baf9..2c4357060be 100644 --- a/tests/components/sonos/test_speaker.py +++ b/tests/components/sonos/test_speaker.py @@ -12,9 +12,20 @@ from tests.common import async_fire_time_changed async def test_fallback_to_polling( - hass: HomeAssistant, async_autosetup_sonos, soco, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + config_entry, + soco, + fire_zgs_event, + caplog: pytest.LogCaptureFixture, ) -> None: """Test that polling fallback works.""" + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + # Do not wait on background tasks here because the + # subscription callback will fire an unsub the polling check + await hass.async_block_till_done() + await fire_zgs_event() + speaker = list(hass.data[DATA_SONOS].discovered.values())[0] assert speaker.soco is soco assert speaker._subscriptions @@ -30,7 +41,7 @@ async def test_fallback_to_polling( ), ): async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert not speaker._subscriptions assert speaker.subscriptions_failed @@ -46,6 +57,7 @@ async def test_subscription_creation_fails( side_effect=ConnectionError("Took too long"), ): await async_setup_sonos() + await hass.async_block_till_done(wait_background_tasks=True) speaker = list(hass.data[DATA_SONOS].discovered.values())[0] assert not speaker._subscriptions diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index 94e6965a571..61d0c7b4ea5 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -665,7 +665,7 @@ async def test_zone_attributes( hass, dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) entity_1_state = hass.states.get(DEVICE_1_ENTITY_ID) assert entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["is_master"] diff --git a/tests/components/speedtestdotnet/test_init.py b/tests/components/speedtestdotnet/test_init.py index 5083f56a8e2..2b0f803eb6f 100644 --- a/tests/components/speedtestdotnet/test_init.py +++ b/tests/components/speedtestdotnet/test_init.py @@ -74,7 +74,7 @@ async def test_server_not_found(hass: HomeAssistant, mock_api: MagicMock) -> Non hass, dt_util.utcnow() + timedelta(minutes=61), ) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("sensor.speedtest_ping") assert state is not None assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/systemmonitor/test_binary_sensor.py b/tests/components/systemmonitor/test_binary_sensor.py index 51c8fc87a3a..e3fbdedc081 100644 --- a/tests/components/systemmonitor/test_binary_sensor.py +++ b/tests/components/systemmonitor/test_binary_sensor.py @@ -97,7 +97,7 @@ async def test_sensor_process_fails( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) process_sensor = hass.states.get("binary_sensor.system_monitor_process_python3") assert process_sensor is not None diff --git a/tests/components/systemmonitor/test_sensor.py b/tests/components/systemmonitor/test_sensor.py index 11dd002c2f7..a11112d8f86 100644 --- a/tests/components/systemmonitor/test_sensor.py +++ b/tests/components/systemmonitor/test_sensor.py @@ -232,7 +232,7 @@ async def test_sensor_updating( mock_psutil.virtual_memory.side_effect = Exception("Failed to update") freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) memory_sensor = hass.states.get("sensor.system_monitor_memory_free") assert memory_sensor is not None @@ -248,7 +248,7 @@ async def test_sensor_updating( ) freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) memory_sensor = hass.states.get("sensor.system_monitor_memory_free") assert memory_sensor is not None @@ -293,7 +293,7 @@ async def test_sensor_process_fails( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) process_sensor = hass.states.get("sensor.system_monitor_process_python3") assert process_sensor is not None @@ -330,7 +330,7 @@ async def test_sensor_network_sensors( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) network_out_sensor = hass.states.get("sensor.system_monitor_network_out_eth1") packets_out_sensor = hass.states.get("sensor.system_monitor_packets_out_eth1") @@ -362,7 +362,7 @@ async def test_sensor_network_sensors( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) network_out_sensor = hass.states.get("sensor.system_monitor_network_out_eth1") packets_out_sensor = hass.states.get("sensor.system_monitor_packets_out_eth1") @@ -470,7 +470,7 @@ async def test_exception_handling_disk_sensor( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "OS error for /" in caplog.text @@ -483,7 +483,7 @@ async def test_exception_handling_disk_sensor( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert "OS error for /" in caplog.text @@ -498,7 +498,7 @@ async def test_exception_handling_disk_sensor( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) disk_sensor = hass.states.get("sensor.system_monitor_disk_free") assert disk_sensor is not None @@ -528,7 +528,7 @@ async def test_cpu_percentage_is_zero_returns_unknown( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") assert cpu_sensor is not None @@ -538,7 +538,7 @@ async def test_cpu_percentage_is_zero_returns_unknown( freezer.tick(timedelta(minutes=1)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") assert cpu_sensor is not None @@ -573,7 +573,7 @@ async def test_remove_obsolete_entities( ) freezer.tick(timedelta(minutes=5)) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Fake an entity which should be removed as not supported and disabled entity_registry.async_get_or_create( diff --git a/tests/components/tcp/test_binary_sensor.py b/tests/components/tcp/test_binary_sensor.py index 959c1f050fd..05aa2a471db 100644 --- a/tests/components/tcp/test_binary_sensor.py +++ b/tests/components/tcp/test_binary_sensor.py @@ -79,7 +79,7 @@ async def test_state(hass: HomeAssistant, mock_socket, now) -> None: mock_socket.recv.return_value = b"on" async_fire_time_changed(hass, now + timedelta(seconds=45)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(TEST_ENTITY) diff --git a/tests/components/temper/test_sensor.py b/tests/components/temper/test_sensor.py index 94c44cc4296..d1e74f1ab0f 100644 --- a/tests/components/temper/test_sensor.py +++ b/tests/components/temper/test_sensor.py @@ -29,7 +29,7 @@ async def test_temperature_readback(hass: HomeAssistant) -> None: await hass.async_block_till_done() async_fire_time_changed(hass, utcnow + timedelta(seconds=70)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) temperature = hass.states.get("sensor.mydevicename") assert temperature diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py index 7ac6540f1ff..fa2e997756d 100644 --- a/tests/components/totalconnect/test_alarm_control_panel.py +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -548,30 +548,30 @@ async def test_other_update_failures(hass: HomeAssistant) -> None: # then an error: ServiceUnavailable --> UpdateFailed async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE assert mock_request.call_count == 2 # works again async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED assert mock_request.call_count == 3 # then an error: TotalConnectError --> UpdateFailed async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 3) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE assert mock_request.call_count == 4 # works again async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 4) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(ENTITY_ID).state == STATE_ALARM_DISARMED assert mock_request.call_count == 5 # unknown TotalConnect status via ValueError async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 5) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE assert mock_request.call_count == 6 diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 12203a3e222..522448ecfc4 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -278,7 +278,7 @@ async def test_setup_nvr_errors_during_indexing( mock_remote.return_value.index.side_effect = None async_fire_time_changed(hass, now + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) camera_states = hass.states.async_all("camera") @@ -313,7 +313,7 @@ async def test_setup_nvr_errors_during_initialization( mock_remote.side_effect = None async_fire_time_changed(hass, now + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) camera_states = hass.states.async_all("camera") @@ -362,7 +362,7 @@ async def test_motion_recording_mode_properties( ] = True async_fire_time_changed(hass, now + timedelta(seconds=31)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("camera.front") @@ -375,7 +375,7 @@ async def test_motion_recording_mode_properties( mock_remote.return_value.get_camera.return_value["recordingIndicator"] = "DISABLED" async_fire_time_changed(hass, now + timedelta(seconds=61)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("camera.front") @@ -387,7 +387,7 @@ async def test_motion_recording_mode_properties( ) async_fire_time_changed(hass, now + timedelta(seconds=91)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("camera.front") @@ -399,7 +399,7 @@ async def test_motion_recording_mode_properties( ) async_fire_time_changed(hass, now + timedelta(seconds=121)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get("camera.front") diff --git a/tests/components/ws66i/test_media_player.py b/tests/components/ws66i/test_media_player.py index eec6bf191f7..c13f6cbd738 100644 --- a/tests/components/ws66i/test_media_player.py +++ b/tests/components/ws66i/test_media_player.py @@ -195,7 +195,7 @@ async def test_update(hass: HomeAssistant, freezer: FrozenDateTimeFactory) -> No with patch.object(MockWs66i, "open") as method_call: freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert not method_call.called @@ -226,13 +226,13 @@ async def test_failed_update( freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # Failed update, close called with patch.object(MockWs66i, "zone_status", return_value=None): freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert hass.states.is_state(ZONE_1_ID, STATE_UNAVAILABLE) @@ -240,12 +240,12 @@ async def test_failed_update( with patch.object(MockWs66i, "zone_status", return_value=None): freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # A connection re-attempt succeeds freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # confirm entity is back on state = hass.states.get(ZONE_1_ID) @@ -315,7 +315,7 @@ async def test_source_select( freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) state = hass.states.get(ZONE_1_ID) @@ -370,14 +370,14 @@ async def test_volume_up_down( ) freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # should not go below zero assert ws66i.zones[11].volume == 0 await _call_media_player_service(hass, SERVICE_VOLUME_UP, {"entity_id": ZONE_1_ID}) freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ws66i.zones[11].volume == 1 await _call_media_player_service( @@ -385,14 +385,14 @@ async def test_volume_up_down( ) freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert ws66i.zones[11].volume == MAX_VOL await _call_media_player_service(hass, SERVICE_VOLUME_UP, {"entity_id": ZONE_1_ID}) freezer.tick(POLL_INTERVAL) async_fire_time_changed(hass) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) # should not go above 38 (MAX_VOL) assert ws66i.zones[11].volume == MAX_VOL diff --git a/tests/components/xiaomi_miio/test_vacuum.py b/tests/components/xiaomi_miio/test_vacuum.py index c5345386777..2cfc3a4f294 100644 --- a/tests/components/xiaomi_miio/test_vacuum.py +++ b/tests/components/xiaomi_miio/test_vacuum.py @@ -238,7 +238,7 @@ async def test_xiaomi_exceptions(hass: HomeAssistant, mock_mirobo_is_on) -> None mock_mirobo_is_on.status.side_effect = DeviceException("dummy exception") future = dt_util.utcnow() + timedelta(seconds=60) async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert not is_available() @@ -247,7 +247,7 @@ async def test_xiaomi_exceptions(hass: HomeAssistant, mock_mirobo_is_on) -> None mock_mirobo_is_on.status.reset_mock() future += timedelta(seconds=60) async_fire_time_changed(hass, future) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert not is_available() assert mock_mirobo_is_on.status.call_count == 1 diff --git a/tests/components/yale_smart_alarm/test_coordinator.py b/tests/components/yale_smart_alarm/test_coordinator.py index 5125c817567..6f1125fcf65 100644 --- a/tests/components/yale_smart_alarm/test_coordinator.py +++ b/tests/components/yale_smart_alarm/test_coordinator.py @@ -76,7 +76,7 @@ async def test_coordinator_setup_and_update_errors( client.get_all.side_effect = ConnectionError("Could not connect") async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) client.get_all.assert_called_once() state = hass.states.get("alarm_control_panel.yale_smart_alarm") assert state.state == STATE_UNAVAILABLE @@ -84,7 +84,7 @@ async def test_coordinator_setup_and_update_errors( client.get_all.side_effect = ConnectionError("Could not connect") async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=2)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) client.get_all.assert_called_once() state = hass.states.get("alarm_control_panel.yale_smart_alarm") assert state.state == STATE_UNAVAILABLE @@ -92,7 +92,7 @@ async def test_coordinator_setup_and_update_errors( client.get_all.side_effect = TimeoutError("Could not connect") async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=3)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) client.get_all.assert_called_once() state = hass.states.get("alarm_control_panel.yale_smart_alarm") assert state.state == STATE_UNAVAILABLE @@ -100,7 +100,7 @@ async def test_coordinator_setup_and_update_errors( client.get_all.side_effect = UnknownError("info") async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=4)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) client.get_all.assert_called_once() state = hass.states.get("alarm_control_panel.yale_smart_alarm") assert state.state == STATE_UNAVAILABLE @@ -110,7 +110,7 @@ async def test_coordinator_setup_and_update_errors( client.get_all.return_value = load_json client.get_armed_status.return_value = YALE_STATE_ARM_FULL async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) client.get_all.assert_called_once() state = hass.states.get("alarm_control_panel.yale_smart_alarm") assert state.state == STATE_ALARM_ARMED_AWAY @@ -118,7 +118,7 @@ async def test_coordinator_setup_and_update_errors( client.get_all.side_effect = AuthenticationError("Can not authenticate") async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=6)) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) client.get_all.assert_called_once() state = hass.states.get("alarm_control_panel.yale_smart_alarm") assert state.state == STATE_UNAVAILABLE diff --git a/tests/test_core.py b/tests/test_core.py index 11fda50a180..a0a197096cd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -588,6 +588,46 @@ async def test_async_get_hass_can_be_called(hass: HomeAssistant) -> None: my_job_create_task.join() +async def test_async_add_executor_job_background(hass: HomeAssistant) -> None: + """Test running an executor job in the background.""" + calls = [] + + def job(): + time.sleep(0.01) + calls.append(1) + + async def _async_add_executor_job(): + await hass.async_add_executor_job(job) + + task = hass.async_create_background_task( + _async_add_executor_job(), "background", eager_start=True + ) + await hass.async_block_till_done() + assert len(calls) == 0 + await hass.async_block_till_done(wait_background_tasks=True) + assert len(calls) == 1 + await task + + +async def test_async_add_executor_job(hass: HomeAssistant) -> None: + """Test running an executor job.""" + calls = [] + + def job(): + time.sleep(0.01) + calls.append(1) + + async def _async_add_executor_job(): + await hass.async_add_executor_job(job) + + task = hass.async_create_task( + _async_add_executor_job(), "background", eager_start=True + ) + await hass.async_block_till_done() + assert len(calls) == 1 + await task + + async def test_stage_shutdown(hass: HomeAssistant) -> None: """Simulate a shutdown, test calling stuff.""" test_stop = async_capture_events(hass, EVENT_HOMEASSISTANT_STOP) From f2edc156874511b8a921043a65249519c0bb74e9 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Sat, 30 Mar 2024 15:59:20 -0500 Subject: [PATCH 1635/1691] Add initial support for floors to intents (#114456) * Add initial support for floors to intents * Fix climate intent * More tests * No return value * Add requested changes * Reuse event handler --- homeassistant/components/climate/intent.py | 2 + .../components/conversation/default_agent.py | 46 +++++++- .../components/conversation/manifest.json | 2 +- homeassistant/helpers/intent.py | 110 ++++++++++++++---- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../conversation/test_default_agent.py | 73 +++++++++++- .../test_default_agent_intents.py | 105 ++++++++++++++++- tests/helpers/test_intent.py | 76 +++++++++++- 10 files changed, 384 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/climate/intent.py b/homeassistant/components/climate/intent.py index db263451f0b..3073d3e3c26 100644 --- a/homeassistant/components/climate/intent.py +++ b/homeassistant/components/climate/intent.py @@ -58,6 +58,7 @@ class GetTemperatureIntent(intent.IntentHandler): raise intent.NoStatesMatchedError( name=entity_text or entity_name, area=area_name or area_id, + floor=None, domains={DOMAIN}, device_classes=None, ) @@ -75,6 +76,7 @@ class GetTemperatureIntent(intent.IntentHandler): raise intent.NoStatesMatchedError( name=entity_name, area=None, + floor=None, domains={DOMAIN}, device_classes=None, ) diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 96b0565ebd3..c0307c68908 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -34,6 +34,7 @@ from homeassistant.helpers import ( area_registry as ar, device_registry as dr, entity_registry as er, + floor_registry as fr, intent, start, template, @@ -163,7 +164,12 @@ class DefaultAgent(AbstractConversationAgent): self.hass.bus.async_listen( ar.EVENT_AREA_REGISTRY_UPDATED, - self._async_handle_area_registry_changed, + self._async_handle_area_floor_registry_changed, + run_immediately=True, + ) + self.hass.bus.async_listen( + fr.EVENT_FLOOR_REGISTRY_UPDATED, + self._async_handle_area_floor_registry_changed, run_immediately=True, ) self.hass.bus.async_listen( @@ -696,10 +702,13 @@ class DefaultAgent(AbstractConversationAgent): return lang_intents @core.callback - def _async_handle_area_registry_changed( - self, event: core.Event[ar.EventAreaRegistryUpdatedData] + def _async_handle_area_floor_registry_changed( + self, + event: core.Event[ + ar.EventAreaRegistryUpdatedData | fr.EventFloorRegistryUpdatedData + ], ) -> None: - """Clear area area cache when the area registry has changed.""" + """Clear area/floor list cache when the area registry has changed.""" self._slot_lists = None @core.callback @@ -773,6 +782,8 @@ class DefaultAgent(AbstractConversationAgent): # Default name entity_names.append((state.name, state.name, context)) + _LOGGER.debug("Exposed entities: %s", entity_names) + # Expose all areas. # # We pass in area id here with the expectation that no two areas will @@ -788,11 +799,25 @@ class DefaultAgent(AbstractConversationAgent): area_names.append((alias, area.id)) - _LOGGER.debug("Exposed entities: %s", entity_names) + # Expose all floors. + # + # We pass in floor id here with the expectation that no two floors will + # share the same name or alias. + floors = fr.async_get(self.hass) + floor_names = [] + for floor in floors.async_list_floors(): + floor_names.append((floor.name, floor.floor_id)) + if floor.aliases: + for alias in floor.aliases: + if not alias.strip(): + continue + + floor_names.append((alias, floor.floor_id)) self._slot_lists = { "area": TextSlotList.from_tuples(area_names, allow_template=False), "name": TextSlotList.from_tuples(entity_names, allow_template=False), + "floor": TextSlotList.from_tuples(floor_names, allow_template=False), } return self._slot_lists @@ -953,6 +978,10 @@ def _get_unmatched_response(result: RecognizeResult) -> tuple[ErrorKey, dict[str # area only return ErrorKey.NO_AREA, {"area": unmatched_area} + if unmatched_floor := unmatched_text.get("floor"): + # floor only + return ErrorKey.NO_FLOOR, {"floor": unmatched_floor} + # Area may still have matched matched_area: str | None = None if matched_area_entity := result.entities.get("area"): @@ -1000,6 +1029,13 @@ def _get_no_states_matched_response( "area": no_states_error.area, } + if no_states_error.floor: + # domain in floor + return ErrorKey.NO_DOMAIN_IN_FLOOR, { + "domain": domain, + "floor": no_states_error.floor, + } + # domain only return ErrorKey.NO_DOMAIN, {"domain": domain} diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index 7f3c4f5894e..7f463483bf9 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.27"] + "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.29"] } diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 63214cb135b..fcebf91b854 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -24,7 +24,13 @@ from homeassistant.core import Context, HomeAssistant, State, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass -from . import area_registry, config_validation as cv, device_registry, entity_registry +from . import ( + area_registry, + config_validation as cv, + device_registry, + entity_registry, + floor_registry, +) _LOGGER = logging.getLogger(__name__) _SlotsType = dict[str, Any] @@ -144,16 +150,18 @@ class NoStatesMatchedError(IntentError): def __init__( self, - name: str | None, - area: str | None, - domains: set[str] | None, - device_classes: set[str] | None, + name: str | None = None, + area: str | None = None, + floor: str | None = None, + domains: set[str] | None = None, + device_classes: set[str] | None = None, ) -> None: """Initialize error.""" super().__init__() self.name = name self.area = area + self.floor = floor self.domains = domains self.device_classes = device_classes @@ -220,12 +228,35 @@ def _find_area( return None -def _filter_by_area( +def _find_floor( + id_or_name: str, floors: floor_registry.FloorRegistry +) -> floor_registry.FloorEntry | None: + """Find an floor by id or name, checking aliases too.""" + floor = floors.async_get_floor(id_or_name) or floors.async_get_floor_by_name( + id_or_name + ) + if floor is not None: + return floor + + # Check floor aliases + for maybe_floor in floors.floors.values(): + if not maybe_floor.aliases: + continue + + for floor_alias in maybe_floor.aliases: + if id_or_name == floor_alias.casefold(): + return maybe_floor + + return None + + +def _filter_by_areas( states_and_entities: list[tuple[State, entity_registry.RegistryEntry | None]], - area: area_registry.AreaEntry, + areas: Iterable[area_registry.AreaEntry], devices: device_registry.DeviceRegistry, ) -> Iterable[tuple[State, entity_registry.RegistryEntry | None]]: """Filter state/entity pairs by an area.""" + filter_area_ids: set[str | None] = {a.id for a in areas} entity_area_ids: dict[str, str | None] = {} for _state, entity in states_and_entities: if entity is None: @@ -241,7 +272,7 @@ def _filter_by_area( entity_area_ids[entity.id] = device.area_id for state, entity in states_and_entities: - if (entity is not None) and (entity_area_ids.get(entity.id) == area.id): + if (entity is not None) and (entity_area_ids.get(entity.id) in filter_area_ids): yield (state, entity) @@ -252,11 +283,14 @@ def async_match_states( name: str | None = None, area_name: str | None = None, area: area_registry.AreaEntry | None = None, + floor_name: str | None = None, + floor: floor_registry.FloorEntry | None = None, domains: Collection[str] | None = None, device_classes: Collection[str] | None = None, states: Iterable[State] | None = None, entities: entity_registry.EntityRegistry | None = None, areas: area_registry.AreaRegistry | None = None, + floors: floor_registry.FloorRegistry | None = None, devices: device_registry.DeviceRegistry | None = None, assistant: str | None = None, ) -> Iterable[State]: @@ -268,6 +302,15 @@ def async_match_states( if entities is None: entities = entity_registry.async_get(hass) + if devices is None: + devices = device_registry.async_get(hass) + + if areas is None: + areas = area_registry.async_get(hass) + + if floors is None: + floors = floor_registry.async_get(hass) + # Gather entities states_and_entities: list[tuple[State, entity_registry.RegistryEntry | None]] = [] for state in states: @@ -294,20 +337,35 @@ def async_match_states( if _is_device_class(state, entity, device_classes) ] + filter_areas: list[area_registry.AreaEntry] = [] + + if (floor is None) and (floor_name is not None): + # Look up floor by name + floor = _find_floor(floor_name, floors) + if floor is None: + _LOGGER.warning("Floor not found: %s", floor_name) + return + + if floor is not None: + filter_areas = [ + a for a in areas.async_list_areas() if a.floor_id == floor.floor_id + ] + if (area is None) and (area_name is not None): # Look up area by name - if areas is None: - areas = area_registry.async_get(hass) - area = _find_area(area_name, areas) - assert area is not None, f"No area named {area_name}" + if area is None: + _LOGGER.warning("Area not found: %s", area_name) + return if area is not None: - # Filter by states/entities by area - if devices is None: - devices = device_registry.async_get(hass) + filter_areas = [area] - states_and_entities = list(_filter_by_area(states_and_entities, area, devices)) + if filter_areas: + # Filter by states/entities by area + states_and_entities = list( + _filter_by_areas(states_and_entities, filter_areas, devices) + ) if assistant is not None: # Filter by exposure @@ -318,9 +376,6 @@ def async_match_states( ] if name is not None: - if devices is None: - devices = device_registry.async_get(hass) - # Filter by name name = name.casefold() @@ -389,7 +444,7 @@ class DynamicServiceIntentHandler(IntentHandler): """ slot_schema = { - vol.Any("name", "area"): cv.string, + vol.Any("name", "area", "floor"): cv.string, vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string]), vol.Optional("device_class"): vol.All(cv.ensure_list, [cv.string]), } @@ -453,7 +508,7 @@ class DynamicServiceIntentHandler(IntentHandler): # Don't match on name if targeting all entities entity_name = None - # Look up area first to fail early + # Look up area to fail early area_slot = slots.get("area", {}) area_id = area_slot.get("value") area_name = area_slot.get("text") @@ -464,6 +519,17 @@ class DynamicServiceIntentHandler(IntentHandler): if area is None: raise IntentHandleError(f"No area named {area_name}") + # Look up floor to fail early + floor_slot = slots.get("floor", {}) + floor_id = floor_slot.get("value") + floor_name = floor_slot.get("text") + floor: floor_registry.FloorEntry | None = None + if floor_id is not None: + floors = floor_registry.async_get(hass) + floor = floors.async_get_floor(floor_id) + if floor is None: + raise IntentHandleError(f"No floor named {floor_name}") + # Optional domain/device class filters. # Convert to sets for speed. domains: set[str] | None = None @@ -480,6 +546,7 @@ class DynamicServiceIntentHandler(IntentHandler): hass, name=entity_name, area=area, + floor=floor, domains=domains, device_classes=device_classes, assistant=intent_obj.assistant, @@ -491,6 +558,7 @@ class DynamicServiceIntentHandler(IntentHandler): raise NoStatesMatchedError( name=entity_text or entity_name, area=area_name or area_id, + floor=floor_name or floor_id, domains=domains, device_classes=device_classes, ) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1d60b74f18f..bdfaa8fcf45 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -31,7 +31,7 @@ hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240329.1 -home-assistant-intents==2024.3.27 +home-assistant-intents==2024.3.29 httpx==0.27.0 ifaddr==0.2.0 Jinja2==3.1.3 diff --git a/requirements_all.txt b/requirements_all.txt index 0a94eb110e1..da752f00279 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1080,7 +1080,7 @@ holidays==0.45 home-assistant-frontend==20240329.1 # homeassistant.components.conversation -home-assistant-intents==2024.3.27 +home-assistant-intents==2024.3.29 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3a3ebbbb077..4889e9de781 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -879,7 +879,7 @@ holidays==0.45 home-assistant-frontend==20240329.1 # homeassistant.components.conversation -home-assistant-intents==2024.3.27 +home-assistant-intents==2024.3.29 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/tests/components/conversation/test_default_agent.py b/tests/components/conversation/test_default_agent.py index aefb37f427e..8f38459a8da 100644 --- a/tests/components/conversation/test_default_agent.py +++ b/tests/components/conversation/test_default_agent.py @@ -17,6 +17,7 @@ from homeassistant.helpers import ( device_registry as dr, entity, entity_registry as er, + floor_registry as fr, intent, ) from homeassistant.setup import async_setup_component @@ -480,6 +481,20 @@ async def test_error_no_area(hass: HomeAssistant, init_components) -> None: ) +async def test_error_no_floor(hass: HomeAssistant, init_components) -> None: + """Test error message when floor is missing.""" + result = await conversation.async_converse( + hass, "turn on all the lights on missing floor", None, Context(), None + ) + + assert result.response.response_type == intent.IntentResponseType.ERROR + assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS + assert ( + result.response.speech["plain"]["speech"] + == "Sorry, I am not aware of any floor called missing" + ) + + async def test_error_no_device_in_area( hass: HomeAssistant, init_components, area_registry: ar.AreaRegistry ) -> None: @@ -549,6 +564,48 @@ async def test_error_no_domain_in_area( ) +async def test_error_no_domain_in_floor( + hass: HomeAssistant, + init_components, + area_registry: ar.AreaRegistry, + floor_registry: fr.FloorRegistry, +) -> None: + """Test error message when no devices/entities for a domain exist on a floor.""" + floor_ground = floor_registry.async_create("ground") + area_kitchen = area_registry.async_get_or_create("kitchen_id") + area_kitchen = area_registry.async_update( + area_kitchen.id, name="kitchen", floor_id=floor_ground.floor_id + ) + result = await conversation.async_converse( + hass, "turn on all lights on the ground floor", None, Context(), None + ) + + assert result.response.response_type == intent.IntentResponseType.ERROR + assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS + assert ( + result.response.speech["plain"]["speech"] + == "Sorry, I am not aware of any light on the ground floor" + ) + + # Add a new floor/area to trigger registry event handlers + floor_upstairs = floor_registry.async_create("upstairs") + area_bedroom = area_registry.async_get_or_create("bedroom_id") + area_bedroom = area_registry.async_update( + area_bedroom.id, name="bedroom", floor_id=floor_upstairs.floor_id + ) + + result = await conversation.async_converse( + hass, "turn on all lights upstairs", None, Context(), None + ) + + assert result.response.response_type == intent.IntentResponseType.ERROR + assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS + assert ( + result.response.speech["plain"]["speech"] + == "Sorry, I am not aware of any light on the upstairs floor" + ) + + async def test_error_no_device_class(hass: HomeAssistant, init_components) -> None: """Test error message when no entities of a device class exist.""" @@ -736,7 +793,7 @@ async def test_no_states_matched_default_error( with patch( "homeassistant.components.conversation.default_agent.intent.async_handle", - side_effect=intent.NoStatesMatchedError(None, None, None, None), + side_effect=intent.NoStatesMatchedError(), ): result = await conversation.async_converse( hass, "turn on lights in the kitchen", None, Context(), None @@ -759,11 +816,16 @@ async def test_empty_aliases( area_registry: ar.AreaRegistry, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + floor_registry: fr.FloorRegistry, ) -> None: """Test that empty aliases are not added to slot lists.""" + floor_1 = floor_registry.async_create("first floor", aliases={" "}) + area_kitchen = area_registry.async_get_or_create("kitchen_id") area_kitchen = area_registry.async_update(area_kitchen.id, name="kitchen") - area_kitchen = area_registry.async_update(area_kitchen.id, aliases={" "}) + area_kitchen = area_registry.async_update( + area_kitchen.id, aliases={" "}, floor_id=floor_1 + ) entry = MockConfigEntry() entry.add_to_hass(hass) @@ -799,7 +861,7 @@ async def test_empty_aliases( slot_lists = mock_recognize_all.call_args[0][2] # Slot lists should only contain non-empty text - assert slot_lists.keys() == {"area", "name"} + assert slot_lists.keys() == {"area", "name", "floor"} areas = slot_lists["area"] assert len(areas.values) == 1 assert areas.values[0].value_out == area_kitchen.id @@ -810,6 +872,11 @@ async def test_empty_aliases( assert names.values[0].value_out == kitchen_light.name assert names.values[0].text_in.text == kitchen_light.name + floors = slot_lists["floor"] + assert len(floors.values) == 1 + assert floors.values[0].value_out == floor_1.floor_id + assert floors.values[0].text_in.text == floor_1.name + async def test_all_domains_loaded(hass: HomeAssistant, init_components) -> None: """Test that sentences for all domains are always loaded.""" diff --git a/tests/components/conversation/test_default_agent_intents.py b/tests/components/conversation/test_default_agent_intents.py index c57d93d8cef..9636ac07f63 100644 --- a/tests/components/conversation/test_default_agent_intents.py +++ b/tests/components/conversation/test_default_agent_intents.py @@ -2,14 +2,26 @@ import pytest -from homeassistant.components import conversation, cover, media_player, vacuum, valve +from homeassistant.components import ( + conversation, + cover, + light, + media_player, + vacuum, + valve, +) from homeassistant.components.cover import intent as cover_intent from homeassistant.components.homeassistant.exposed_entities import async_expose_entity from homeassistant.components.media_player import intent as media_player_intent from homeassistant.components.vacuum import intent as vaccum_intent from homeassistant.const import STATE_CLOSED from homeassistant.core import Context, HomeAssistant -from homeassistant.helpers import intent +from homeassistant.helpers import ( + area_registry as ar, + entity_registry as er, + floor_registry as fr, + intent, +) from homeassistant.setup import async_setup_component from tests.common import async_mock_service @@ -244,3 +256,92 @@ async def test_media_player_intents( "entity_id": entity_id, media_player.ATTR_MEDIA_VOLUME_LEVEL: 0.75, } + + +async def test_turn_floor_lights_on_off( + hass: HomeAssistant, + init_components, + entity_registry: er.EntityRegistry, + area_registry: ar.AreaRegistry, + floor_registry: fr.FloorRegistry, +) -> None: + """Test that we can turn lights on/off for an entire floor.""" + floor_ground = floor_registry.async_create("ground", aliases={"downstairs"}) + floor_upstairs = floor_registry.async_create("upstairs") + + # Kitchen and living room are on the ground floor + area_kitchen = area_registry.async_get_or_create("kitchen_id") + area_kitchen = area_registry.async_update( + area_kitchen.id, name="kitchen", floor_id=floor_ground.floor_id + ) + + area_living_room = area_registry.async_get_or_create("living_room_id") + area_living_room = area_registry.async_update( + area_living_room.id, name="living_room", floor_id=floor_ground.floor_id + ) + + # Bedroom is upstairs + area_bedroom = area_registry.async_get_or_create("bedroom_id") + area_bedroom = area_registry.async_update( + area_bedroom.id, name="bedroom", floor_id=floor_upstairs.floor_id + ) + + # One light per area + kitchen_light = entity_registry.async_get_or_create( + "light", "demo", "kitchen_light" + ) + kitchen_light = entity_registry.async_update_entity( + kitchen_light.entity_id, area_id=area_kitchen.id + ) + hass.states.async_set(kitchen_light.entity_id, "off") + + living_room_light = entity_registry.async_get_or_create( + "light", "demo", "living_room_light" + ) + living_room_light = entity_registry.async_update_entity( + living_room_light.entity_id, area_id=area_living_room.id + ) + hass.states.async_set(living_room_light.entity_id, "off") + + bedroom_light = entity_registry.async_get_or_create( + "light", "demo", "bedroom_light" + ) + bedroom_light = entity_registry.async_update_entity( + bedroom_light.entity_id, area_id=area_bedroom.id + ) + hass.states.async_set(bedroom_light.entity_id, "off") + + # Target by floor + on_calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON) + result = await conversation.async_converse( + hass, "turn on all lights downstairs", None, Context(), None + ) + + assert len(on_calls) == 2 + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE + assert {s.entity_id for s in result.response.matched_states} == { + kitchen_light.entity_id, + living_room_light.entity_id, + } + + on_calls.clear() + result = await conversation.async_converse( + hass, "upstairs lights on", None, Context(), None + ) + + assert len(on_calls) == 1 + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE + assert {s.entity_id for s in result.response.matched_states} == { + bedroom_light.entity_id + } + + off_calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_OFF) + result = await conversation.async_converse( + hass, "turn upstairs lights off", None, Context(), None + ) + + assert len(off_calls) == 1 + assert result.response.response_type == intent.IntentResponseType.ACTION_DONE + assert {s.entity_id for s in result.response.matched_states} == { + bedroom_light.entity_id + } diff --git a/tests/helpers/test_intent.py b/tests/helpers/test_intent.py index 1bc01c28cf2..d77eb698205 100644 --- a/tests/helpers/test_intent.py +++ b/tests/helpers/test_intent.py @@ -15,6 +15,7 @@ from homeassistant.helpers import ( config_validation as cv, device_registry as dr, entity_registry as er, + floor_registry as fr, intent, ) from homeassistant.setup import async_setup_component @@ -34,12 +35,25 @@ async def test_async_match_states( hass: HomeAssistant, area_registry: ar.AreaRegistry, entity_registry: er.EntityRegistry, + floor_registry: fr.FloorRegistry, ) -> None: """Test async_match_state helper.""" area_kitchen = area_registry.async_get_or_create("kitchen") - area_registry.async_update(area_kitchen.id, aliases={"food room"}) + area_kitchen = area_registry.async_update(area_kitchen.id, aliases={"food room"}) area_bedroom = area_registry.async_get_or_create("bedroom") + # Kitchen is on the first floor + floor_1 = floor_registry.async_create("first floor", aliases={"ground floor"}) + area_kitchen = area_registry.async_update( + area_kitchen.id, floor_id=floor_1.floor_id + ) + + # Bedroom is on the second floor + floor_2 = floor_registry.async_create("second floor") + area_bedroom = area_registry.async_update( + area_bedroom.id, floor_id=floor_2.floor_id + ) + state1 = State( "light.kitchen", "on", attributes={ATTR_FRIENDLY_NAME: "kitchen light"} ) @@ -94,6 +108,13 @@ async def test_async_match_states( ) ) + # Invalid area + assert not list( + intent.async_match_states( + hass, area_name="invalid area", states=[state1, state2] + ) + ) + # Domain + area assert list( intent.async_match_states( @@ -111,6 +132,35 @@ async def test_async_match_states( ) ) == [state2] + # Floor + assert list( + intent.async_match_states( + hass, floor_name="first floor", states=[state1, state2] + ) + ) == [state1] + + assert list( + intent.async_match_states( + # Check alias + hass, + floor_name="ground floor", + states=[state1, state2], + ) + ) == [state1] + + assert list( + intent.async_match_states( + hass, floor_name="second floor", states=[state1, state2] + ) + ) == [state2] + + # Invalid floor + assert not list( + intent.async_match_states( + hass, floor_name="invalid floor", states=[state1, state2] + ) + ) + async def test_match_device_area( hass: HomeAssistant, @@ -300,3 +350,27 @@ async def test_validate_then_run_in_background(hass: HomeAssistant) -> None: assert len(calls) == 1 assert calls[0].data == {"entity_id": "light.kitchen"} + + +async def test_invalid_area_floor_names(hass: HomeAssistant) -> None: + """Test that we throw an intent handle error with invalid area/floor names.""" + handler = intent.ServiceIntentHandler( + "TestType", "light", "turn_on", "Turned {} on" + ) + intent.async_register(hass, handler) + + with pytest.raises(intent.IntentHandleError): + await intent.async_handle( + hass, + "test", + "TestType", + slots={"area": {"value": "invalid area"}}, + ) + + with pytest.raises(intent.IntentHandleError): + await intent.async_handle( + hass, + "test", + "TestType", + slots={"floor": {"value": "invalid floor"}}, + ) From bdf51553eff11b42eb2d06121b320471f9531d68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 06:34:47 -1000 Subject: [PATCH 1636/1691] Improve sonos test synchronization (#114468) --- tests/components/sonos/conftest.py | 35 +++++++++++++++++++++++--- tests/components/sonos/test_repairs.py | 12 ++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 00858a180a3..576c9a80799 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -1,16 +1,20 @@ """Configuration for Sonos tests.""" +import asyncio +from collections.abc import Callable from copy import copy from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from soco import SoCo +from soco.events_base import Event as SonosEvent from homeassistant.components import ssdp, zeroconf from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.sonos import DOMAIN from homeassistant.const import CONF_HOSTS +from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture @@ -30,6 +34,31 @@ class SonosMockSubscribe: """Initialize the mock subscriber.""" self.event_listener = SonosMockEventListener(ip_address) self.service = Mock() + self.callback_future: asyncio.Future[Callable[[SonosEvent], None]] = None + self._callback: Callable[[SonosEvent], None] | None = None + + @property + def callback(self) -> Callable[[SonosEvent], None] | None: + """Return the callback.""" + return self._callback + + @callback.setter + def callback(self, callback: Callable[[SonosEvent], None]) -> None: + """Set the callback.""" + self._callback = callback + future = self._get_callback_future() + if not future.done(): + future.set_result(callback) + + def _get_callback_future(self) -> asyncio.Future[Callable[[SonosEvent], None]]: + """Get the callback future.""" + if not self.callback_future: + self.callback_future = asyncio.get_running_loop().create_future() + return self.callback_future + + async def wait_for_callback_to_be_set(self) -> Callable[[SonosEvent], None]: + """Wait for the callback to be set.""" + return await self._get_callback_future() async def unsubscribe(self) -> None: """Unsubscribe mock.""" @@ -456,14 +485,14 @@ def zgs_discovery_fixture(): @pytest.fixture(name="fire_zgs_event") -def zgs_event_fixture(hass, soco, zgs_discovery): +def zgs_event_fixture(hass: HomeAssistant, soco: SoCo, zgs_discovery: str): """Create alarm_event fixture.""" variables = {"ZoneGroupState": zgs_discovery} async def _wrapper(): event = SonosMockEvent(soco, soco.zoneGroupTopology, variables) - subscription = soco.zoneGroupTopology.subscribe.return_value - sub_callback = subscription.callback + subscription: SonosMockSubscribe = soco.zoneGroupTopology.subscribe.return_value + sub_callback = await subscription.wait_for_callback_to_be_set() sub_callback(event) await hass.async_block_till_done() diff --git a/tests/components/sonos/test_repairs.py b/tests/components/sonos/test_repairs.py index cf64912e498..49b87b272d6 100644 --- a/tests/components/sonos/test_repairs.py +++ b/tests/components/sonos/test_repairs.py @@ -2,6 +2,8 @@ from unittest.mock import Mock +from soco import SoCo + from homeassistant.components.sonos.const import ( DOMAIN, SCAN_INTERVAL, @@ -11,27 +13,25 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.issue_registry import async_get as async_get_issue_registry from homeassistant.util import dt as dt_util -from .conftest import SonosMockEvent +from .conftest import SonosMockEvent, SonosMockSubscribe from tests.common import MockConfigEntry, async_fire_time_changed async def test_subscription_repair_issues( - hass: HomeAssistant, config_entry: MockConfigEntry, soco, zgs_discovery + hass: HomeAssistant, config_entry: MockConfigEntry, soco: SoCo, zgs_discovery ) -> None: """Test repair issues handling for failed subscriptions.""" issue_registry = async_get_issue_registry(hass) - subscription = soco.zoneGroupTopology.subscribe.return_value + subscription: SonosMockSubscribe = soco.zoneGroupTopology.subscribe.return_value subscription.event_listener = Mock(address=("192.168.4.2", 1400)) config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - await hass.async_block_till_done() # Ensure an issue is registered on subscription failure - sub_callback = subscription.callback + sub_callback = await subscription.wait_for_callback_to_be_set() async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done(wait_background_tasks=True) assert issue_registry.async_get_issue(DOMAIN, SUB_FAIL_ISSUE_ID) From c373d40e34af020a9ef74b1949bee93f6a9ef173 Mon Sep 17 00:00:00 2001 From: dotvav Date: Sun, 31 Mar 2024 11:38:59 +0200 Subject: [PATCH 1637/1691] Fix Overkiz Hitachi OVP air-to-air heat pump (#114487) Unpack command parameters instead of passing a list --- .../climate_entities/hitachi_air_to_air_heat_pump_ovp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py index 86cde4fc4db..b4d6ab788a1 100644 --- a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py +++ b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py @@ -357,5 +357,5 @@ class HitachiAirToAirHeatPumpOVP(OverkizEntity, ClimateEntity): ] await self.executor.async_execute_command( - OverkizCommand.GLOBAL_CONTROL, command_data + OverkizCommand.GLOBAL_CONTROL, *command_data ) From 008e4413b5e579c426efb3cb2adcc8b400010049 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 08:56:01 -1000 Subject: [PATCH 1638/1691] Fix late load of anyio doing blocking I/O in the event loop (#114491) * Fix late load of anyio doing blocking I/O in the event loop httpx loads anyio which loads the asyncio backend in the event loop as soon as httpx makes the first request * tweak --- homeassistant/bootstrap.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 03c0de1ff62..5b805b6138e 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -93,6 +93,11 @@ from .util.async_ import create_eager_task from .util.logging import async_activate_log_queue_handler from .util.package import async_get_user_site, is_virtual_env +with contextlib.suppress(ImportError): + # Ensure anyio backend is imported to avoid it being imported in the event loop + from anyio._backends import _asyncio # noqa: F401 + + if TYPE_CHECKING: from .runner import RuntimeConfig From e8afdd67d0efff4d0d0752cf2baead5545036045 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Mar 2024 08:05:14 -1000 Subject: [PATCH 1639/1691] Fix workday doing blocking I/O in the event loop (#114492) --- homeassistant/components/workday/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/workday/__init__.py b/homeassistant/components/workday/__init__.py index 195221ef088..077a6710b8d 100644 --- a/homeassistant/components/workday/__init__.py +++ b/homeassistant/components/workday/__init__.py @@ -2,6 +2,8 @@ from __future__ import annotations +from functools import partial + from holidays import HolidayBase, country_holidays from homeassistant.config_entries import ConfigEntry @@ -13,7 +15,7 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss from .const import CONF_PROVINCE, DOMAIN, PLATFORMS -def _validate_country_and_province( +async def _async_validate_country_and_province( hass: HomeAssistant, entry: ConfigEntry, country: str | None, province: str | None ) -> None: """Validate country and province.""" @@ -21,7 +23,7 @@ def _validate_country_and_province( if not country: return try: - country_holidays(country) + await hass.async_add_executor_job(country_holidays, country) except NotImplementedError as ex: async_create_issue( hass, @@ -39,7 +41,9 @@ def _validate_country_and_province( if not province: return try: - country_holidays(country, subdiv=province) + await hass.async_add_executor_job( + partial(country_holidays, country, subdiv=province) + ) except NotImplementedError as ex: async_create_issue( hass, @@ -66,10 +70,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: country: str | None = entry.options.get(CONF_COUNTRY) province: str | None = entry.options.get(CONF_PROVINCE) - _validate_country_and_province(hass, entry, country, province) + await _async_validate_country_and_province(hass, entry, country, province) if country and CONF_LANGUAGE not in entry.options: - cls: HolidayBase = country_holidays(country, subdiv=province) + cls: HolidayBase = await hass.async_add_executor_job( + partial(country_holidays, country, subdiv=province) + ) default_language = cls.default_language new_options = entry.options.copy() new_options[CONF_LANGUAGE] = default_language From ef97255d9c0f67f4b79d02095f7eca7d44660705 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 31 Mar 2024 20:08:43 +0200 Subject: [PATCH 1640/1691] Fix server update from breaking setup in Speedtest.NET (#114524) --- homeassistant/components/speedtestdotnet/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 831e66d1c4e..3c15f2fb820 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -25,10 +25,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b partial(speedtest.Speedtest, secure=True) ) coordinator = SpeedTestDataCoordinator(hass, config_entry, api) - await hass.async_add_executor_job(coordinator.update_servers) except speedtest.SpeedtestException as err: raise ConfigEntryNotReady from err + hass.data[DOMAIN] = coordinator + async def _async_finish_startup(hass: HomeAssistant) -> None: """Run this only when HA has finished its startup.""" await coordinator.async_config_entry_first_refresh() @@ -36,8 +37,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b # Don't start a speedtest during startup async_at_started(hass, _async_finish_startup) - hass.data[DOMAIN] = coordinator - await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) From 14dfb6a2552587e2ad2c6059616b5d4c5750f0b5 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 1 Apr 2024 00:24:41 +0200 Subject: [PATCH 1641/1691] Bump axis to v60 (#114544) * Improve Axis MQTT support * Bump axis to v60 --- homeassistant/components/axis/hub/hub.py | 5 +++-- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/axis/const.py | 1 + tests/components/axis/test_hub.py | 4 ++-- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index 08eb816f6ab..4abd1358417 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -116,7 +116,7 @@ class AxisHub: if status.status.state == ClientState.ACTIVE: self.config.entry.async_on_unload( await mqtt.async_subscribe( - hass, f"{self.api.vapix.serial_number}/#", self.mqtt_message + hass, f"{status.config.device_topic_prefix}/#", self.mqtt_message ) ) @@ -124,7 +124,8 @@ class AxisHub: def mqtt_message(self, message: ReceiveMessage) -> None: """Receive Axis MQTT message.""" self.disconnect_from_stream() - + if message.topic.endswith("event/connection"): + return event = mqtt_json_to_event(message.payload) self.api.event.handler(event) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index f47d10df484..1065783d957 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -26,7 +26,7 @@ "iot_class": "local_push", "loggers": ["axis"], "quality_scale": "platinum", - "requirements": ["axis==59"], + "requirements": ["axis==60"], "ssdp": [ { "manufacturer": "AXIS" diff --git a/requirements_all.txt b/requirements_all.txt index da752f00279..1ef594ea50b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -514,7 +514,7 @@ aurorapy==0.2.7 # avion==0.10 # homeassistant.components.axis -axis==59 +axis==60 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4889e9de781..fc19ead8e95 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -454,7 +454,7 @@ auroranoaa==0.0.3 aurorapy==0.2.7 # homeassistant.components.axis -axis==59 +axis==60 # homeassistant.components.azure_event_hub azure-eventhub==5.11.1 diff --git a/tests/components/axis/const.py b/tests/components/axis/const.py index 7b881ea55e5..16b9d17f99e 100644 --- a/tests/components/axis/const.py +++ b/tests/components/axis/const.py @@ -74,6 +74,7 @@ MQTT_CLIENT_RESPONSE = { "status": {"state": "active", "connectionStatus": "Connected"}, "config": { "server": {"protocol": "tcp", "host": "192.168.0.90", "port": 1883}, + "deviceTopicPrefix": f"axis/{MAC}", }, }, } diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index 3291f88d90a..1ae6db05427 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -91,9 +91,9 @@ async def test_device_support_mqtt( hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_config_entry ) -> None: """Successful setup.""" - mqtt_mock.async_subscribe.assert_called_with(f"{MAC}/#", mock.ANY, 0, "utf-8") + mqtt_mock.async_subscribe.assert_called_with(f"axis/{MAC}/#", mock.ANY, 0, "utf-8") - topic = f"{MAC}/event/tns:onvif/Device/tns:axis/Sensor/PIR/$source/sensor/0" + topic = f"axis/{MAC}/event/tns:onvif/Device/tns:axis/Sensor/PIR/$source/sensor/0" message = ( b'{"timestamp": 1590258472044, "topic": "onvif:Device/axis:Sensor/PIR",' b' "message": {"source": {"sensor": "0"}, "key": {}, "data": {"state": "1"}}}' From 71588b5c22a9f3bd2191cb680fb57b4a0fa61c3c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 1 Apr 2024 15:53:14 +0200 Subject: [PATCH 1642/1691] Fix wrong icons (#114567) * Fix wrong icons * Fix wrong icons --- homeassistant/components/ffmpeg/icons.json | 2 +- homeassistant/components/input_select/icons.json | 2 +- homeassistant/components/media_player/icons.json | 2 +- homeassistant/components/synology_dsm/icons.json | 2 +- homeassistant/components/timer/icons.json | 2 +- homeassistant/components/xiaomi_miio/icons.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/ffmpeg/icons.json b/homeassistant/components/ffmpeg/icons.json index 3017b7dc0da..a23f024599c 100644 --- a/homeassistant/components/ffmpeg/icons.json +++ b/homeassistant/components/ffmpeg/icons.json @@ -1,7 +1,7 @@ { "services": { "restart": "mdi:restart", - "start": "mdi:start", + "start": "mdi:play", "stop": "mdi:stop" } } diff --git a/homeassistant/components/input_select/icons.json b/homeassistant/components/input_select/icons.json index 894b6be60dd..03b477ddb36 100644 --- a/homeassistant/components/input_select/icons.json +++ b/homeassistant/components/input_select/icons.json @@ -1,6 +1,6 @@ { "services": { - "select_next": "mdi:skip", + "select_next": "mdi:skip-next", "select_option": "mdi:check", "select_previous": "mdi:skip-previous", "select_first": "mdi:skip-backward", diff --git a/homeassistant/components/media_player/icons.json b/homeassistant/components/media_player/icons.json index e2769085833..847ce5989d6 100644 --- a/homeassistant/components/media_player/icons.json +++ b/homeassistant/components/media_player/icons.json @@ -52,7 +52,7 @@ "unjoin": "mdi:ungroup", "volume_down": "mdi:volume-minus", "volume_mute": "mdi:volume-mute", - "volume_set": "mdi:volume", + "volume_set": "mdi:volume-medium", "volume_up": "mdi:volume-plus" } } diff --git a/homeassistant/components/synology_dsm/icons.json b/homeassistant/components/synology_dsm/icons.json index bbdbc9d2c96..8b4fad457d5 100644 --- a/homeassistant/components/synology_dsm/icons.json +++ b/homeassistant/components/synology_dsm/icons.json @@ -75,7 +75,7 @@ } }, "services": { - "reboot": "mdi:reboot", + "reboot": "mdi:restart", "shutdown": "mdi:power" } } diff --git a/homeassistant/components/timer/icons.json b/homeassistant/components/timer/icons.json index 4cad5c119bd..1e352f7280b 100644 --- a/homeassistant/components/timer/icons.json +++ b/homeassistant/components/timer/icons.json @@ -1,6 +1,6 @@ { "services": { - "start": "mdi:start", + "start": "mdi:play", "pause": "mdi:pause", "cancel": "mdi:cancel", "finish": "mdi:check", diff --git a/homeassistant/components/xiaomi_miio/icons.json b/homeassistant/components/xiaomi_miio/icons.json index a9daaf9a61c..bbd3f6607d7 100644 --- a/homeassistant/components/xiaomi_miio/icons.json +++ b/homeassistant/components/xiaomi_miio/icons.json @@ -17,7 +17,7 @@ "switch_set_wifi_led_off": "mdi:wifi-off", "switch_set_power_price": "mdi:currency-usd", "switch_set_power_mode": "mdi:power", - "vacuum_remote_control_start": "mdi:start", + "vacuum_remote_control_start": "mdi:play", "vacuum_remote_control_stop": "mdi:stop", "vacuum_remote_control_move": "mdi:remote", "vacuum_remote_control_move_step": "mdi:remote", From fc24b61859cc6c1caa97e630364dfd1ff2c55d88 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Mon, 1 Apr 2024 14:49:14 +0200 Subject: [PATCH 1643/1691] Bump velbusaio to 2024.4.0 (#114569) Bump valbusaio to 2024.4.0 --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index c5f9ccd3563..1c51c58d238 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -13,7 +13,7 @@ "velbus-packet", "velbus-protocol" ], - "requirements": ["velbus-aio==2023.12.0"], + "requirements": ["velbus-aio==2024.4.0"], "usb": [ { "vid": "10CF", diff --git a/requirements_all.txt b/requirements_all.txt index 1ef594ea50b..3222dc2460d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2795,7 +2795,7 @@ vallox-websocket-api==5.1.1 vehicle==2.2.1 # homeassistant.components.velbus -velbus-aio==2023.12.0 +velbus-aio==2024.4.0 # homeassistant.components.venstar venstarcolortouch==0.19 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc19ead8e95..27e6f21027e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2151,7 +2151,7 @@ vallox-websocket-api==5.1.1 vehicle==2.2.1 # homeassistant.components.velbus -velbus-aio==2023.12.0 +velbus-aio==2024.4.0 # homeassistant.components.venstar venstarcolortouch==0.19 From 25c611ffc4e397cf5259eb81e6240d9d0f6c831b Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:59:06 +0200 Subject: [PATCH 1644/1691] Reduce usage of executer threads in AVM Fritz!Tools (#114570) * call entity state update calls in one executer task * remove not needed wrapping * mark as "non-public" method * add guard against changes on _entity_update_functions --- homeassistant/components/fritz/common.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 8e773e74c75..5815f9abfc1 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -311,6 +311,17 @@ class FritzBoxTools( ) return unregister_entity_updates + def _entity_states_update(self) -> dict: + """Run registered entity update calls.""" + entity_states = {} + for key in list(self._entity_update_functions): + if (update_fn := self._entity_update_functions.get(key)) is not None: + _LOGGER.debug("update entity %s", key) + entity_states[key] = update_fn( + self.fritz_status, self.data["entity_states"].get(key) + ) + return entity_states + async def _async_update_data(self) -> UpdateCoordinatorDataType: """Update FritzboxTools data.""" entity_data: UpdateCoordinatorDataType = { @@ -319,15 +330,9 @@ class FritzBoxTools( } try: await self.async_scan_devices() - for key in list(self._entity_update_functions): - _LOGGER.debug("update entity %s", key) - entity_data["entity_states"][ - key - ] = await self.hass.async_add_executor_job( - self._entity_update_functions[key], - self.fritz_status, - self.data["entity_states"].get(key), - ) + entity_data["entity_states"] = await self.hass.async_add_executor_job( + self._entity_states_update + ) if self.has_call_deflections: entity_data[ "call_deflections" From bd6890ab83392f845b8b30ba2d26d3778b3c1f17 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 2 Apr 2024 03:48:26 +0200 Subject: [PATCH 1645/1691] Filter out ignored entries in ssdp step of AVM Fritz!SmartHome (#114574) filter out ignored entries in ssdp step --- homeassistant/components/fritzbox/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 377d46eceff..e32f27969a1 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -141,7 +141,7 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_in_progress") # update old and user-configured config entries - for entry in self._async_current_entries(): + for entry in self._async_current_entries(include_ignore=False): if entry.data[CONF_HOST] == host: if uuid and not entry.unique_id: self.hass.config_entries.async_update_entry(entry, unique_id=uuid) From bb33725e7f966f63eb6595d825d2e028f55b8712 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Mon, 1 Apr 2024 13:28:39 -0400 Subject: [PATCH 1646/1691] Bump plexapi to 4.15.11 (#114581) --- homeassistant/components/plex/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index e33cbc2e0c1..85362371715 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["plexapi", "plexwebsocket"], "requirements": [ - "PlexAPI==4.15.10", + "PlexAPI==4.15.11", "plexauth==0.0.6", "plexwebsocket==0.0.14" ], diff --git a/requirements_all.txt b/requirements_all.txt index 3222dc2460d..ced8c6dfec5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -45,7 +45,7 @@ Mastodon.py==1.8.1 Pillow==10.2.0 # homeassistant.components.plex -PlexAPI==4.15.10 +PlexAPI==4.15.11 # homeassistant.components.progettihwsw ProgettiHWSW==0.1.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27e6f21027e..b94616bd07b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -39,7 +39,7 @@ HATasmota==0.8.0 Pillow==10.2.0 # homeassistant.components.plex -PlexAPI==4.15.10 +PlexAPI==4.15.11 # homeassistant.components.progettihwsw ProgettiHWSW==0.1.3 From ea13f102e01f35a662b0a03f207cf8d0d6f0f659 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Apr 2024 00:14:13 +0200 Subject: [PATCH 1647/1691] Fix reolink media source data access (#114593) * Add test * Fix reolink media source data access --- homeassistant/components/reolink/media_source.py | 16 ++++++++++------ tests/components/reolink/test_media_source.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/reolink/media_source.py b/homeassistant/components/reolink/media_source.py index 84c844a0f92..c22a0fc28e7 100644 --- a/homeassistant/components/reolink/media_source.py +++ b/homeassistant/components/reolink/media_source.py @@ -46,7 +46,6 @@ class ReolinkVODMediaSource(MediaSource): """Initialize ReolinkVODMediaSource.""" super().__init__(DOMAIN) self.hass = hass - self.data: dict[str, ReolinkData] = hass.data[DOMAIN] async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: """Resolve media to a url.""" @@ -57,7 +56,8 @@ class ReolinkVODMediaSource(MediaSource): _, config_entry_id, channel_str, stream_res, filename = identifier channel = int(channel_str) - host = self.data[config_entry_id].host + data: dict[str, ReolinkData] = self.hass.data[DOMAIN] + host = data[config_entry_id].host vod_type = VodRequestType.RTMP if host.api.is_nvr: @@ -130,7 +130,8 @@ class ReolinkVODMediaSource(MediaSource): if config_entry.state != ConfigEntryState.LOADED: continue channels: list[str] = [] - host = self.data[config_entry.entry_id].host + data: dict[str, ReolinkData] = self.hass.data[DOMAIN] + host = data[config_entry.entry_id].host entities = er.async_entries_for_config_entry( entity_reg, config_entry.entry_id ) @@ -187,7 +188,8 @@ class ReolinkVODMediaSource(MediaSource): self, config_entry_id: str, channel: int ) -> BrowseMediaSource: """Allow the user to select the high or low playback resolution, (low loads faster).""" - host = self.data[config_entry_id].host + data: dict[str, ReolinkData] = self.hass.data[DOMAIN] + host = data[config_entry_id].host main_enc = await host.api.get_encoding(channel, "main") if main_enc == "h265": @@ -236,7 +238,8 @@ class ReolinkVODMediaSource(MediaSource): self, config_entry_id: str, channel: int, stream: str ) -> BrowseMediaSource: """Return all days on which recordings are available for a reolink camera.""" - host = self.data[config_entry_id].host + data: dict[str, ReolinkData] = self.hass.data[DOMAIN] + host = data[config_entry_id].host # We want today of the camera, not necessarily today of the server now = host.api.time() or await host.api.async_get_time() @@ -288,7 +291,8 @@ class ReolinkVODMediaSource(MediaSource): day: int, ) -> BrowseMediaSource: """Return all recording files on a specific day of a Reolink camera.""" - host = self.data[config_entry_id].host + data: dict[str, ReolinkData] = self.hass.data[DOMAIN] + host = data[config_entry_id].host start = dt.datetime(year, month, day, hour=0, minute=0, second=0) end = dt.datetime(year, month, day, hour=23, minute=59, second=59) diff --git a/tests/components/reolink/test_media_source.py b/tests/components/reolink/test_media_source.py index 9c5aebed222..1eb45945eee 100644 --- a/tests/components/reolink/test_media_source.py +++ b/tests/components/reolink/test_media_source.py @@ -65,6 +65,17 @@ async def setup_component(hass: HomeAssistant) -> None: assert await async_setup_component(hass, MEDIA_STREAM_DOMAIN, {}) +async def test_platform_loads_before_config_entry( + hass: HomeAssistant, + mock_setup_entry: AsyncMock, +) -> None: + """Test that the platform can be loaded before the config entry.""" + # Fake that the config entry is not loaded before the media_source platform + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert mock_setup_entry.call_count == 0 + + async def test_resolve( hass: HomeAssistant, reolink_connect: MagicMock, From 112aab47fb4b0657fee8c388631f4652aa67a864 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Apr 2024 11:18:26 -1000 Subject: [PATCH 1648/1691] Bump zeroconf to 0.132.0 (#114596) changelog: https://github.com/python-zeroconf/python-zeroconf/compare/0.131.0...0.132.0 --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index aecc88968f3..7c489517dd7 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["zeroconf"], "quality_scale": "internal", - "requirements": ["zeroconf==0.131.0"] + "requirements": ["zeroconf==0.132.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bdfaa8fcf45..9621137d855 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -60,7 +60,7 @@ voluptuous-serialize==2.6.0 voluptuous==0.13.1 webrtc-noise-gain==1.2.3 yarl==1.9.4 -zeroconf==0.131.0 +zeroconf==0.132.0 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 diff --git a/requirements_all.txt b/requirements_all.txt index ced8c6dfec5..77c6247e856 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2925,7 +2925,7 @@ zamg==0.3.6 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.131.0 +zeroconf==0.132.0 # homeassistant.components.zeversolar zeversolar==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b94616bd07b..648fd38f1cb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2260,7 +2260,7 @@ yt-dlp==2024.03.10 zamg==0.3.6 # homeassistant.components.zeroconf -zeroconf==0.131.0 +zeroconf==0.132.0 # homeassistant.components.zeversolar zeversolar==0.3.1 From 43631d5944ea13125eab8ec69710307adae7d2de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Apr 2024 15:37:30 -1000 Subject: [PATCH 1649/1691] Add missing platforms_exist guard to check_config (#114600) * Add missing platforms_exist guard to check_config related issue #112811 When the exception hits, the config will end up being saved in the traceback so the memory is never released. This matches the check_config code to homeassistant.config to avoid having the exception thrown. * patch * merge branch --- homeassistant/helpers/check_config.py | 19 ++++++++++--------- tests/helpers/test_check_config.py | 2 ++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/helpers/check_config.py b/homeassistant/helpers/check_config.py index 8537f442595..78dddb12381 100644 --- a/homeassistant/helpers/check_config.py +++ b/homeassistant/helpers/check_config.py @@ -198,15 +198,16 @@ async def async_check_ha_config_file( # noqa: C901 # Check if the integration has a custom config validator config_validator = None - try: - config_validator = await integration.async_get_platform("config") - except ImportError as err: - # Filter out import error of the config platform. - # If the config platform contains bad imports, make sure - # that still fails. - if err.name != f"{integration.pkg_path}.config": - result.add_error(f"Error importing config platform {domain}: {err}") - continue + if integration.platforms_exists(("config",)): + try: + config_validator = await integration.async_get_platform("config") + except ImportError as err: + # Filter out import error of the config platform. + # If the config platform contains bad imports, make sure + # that still fails. + if err.name != f"{integration.pkg_path}.config": + result.add_error(f"Error importing config platform {domain}: {err}") + continue if config_validator is not None and hasattr( config_validator, "async_validate_config" diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index fd94c453e51..de7edf42dc2 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -350,6 +350,7 @@ async def test_config_platform_import_error(hass: HomeAssistant) -> None: side_effect=ImportError("blablabla"), ), patch("os.path.isfile", return_value=True), + patch("homeassistant.loader.Integration.platforms_exists", return_value=True), patch_yaml_files(files), ): res = await async_check_ha_config_file(hass) @@ -373,6 +374,7 @@ async def test_platform_import_error(hass: HomeAssistant) -> None: "homeassistant.loader.Integration.async_get_platform", side_effect=[None, ImportError("blablabla")], ), + patch("homeassistant.loader.Integration.platforms_exists", return_value=True), patch("os.path.isfile", return_value=True), patch_yaml_files(files), ): From 623d85ecaac3e5a8955c9719d101d5c92931a164 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Apr 2024 15:36:48 -1000 Subject: [PATCH 1650/1691] Fix memory leak when importing a platform fails (#114602) * Fix memory leak when importing a platform fails re-raising ImportError would trigger a memory leak * fixes, coverage * Apply suggestions from code review --- homeassistant/loader.py | 31 ++++++------ tests/test_loader.py | 107 +++++++++++++++++++++++++++++++--------- 2 files changed, 98 insertions(+), 40 deletions(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index f462ea16886..48fd3cd54c2 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -750,9 +750,7 @@ class Integration: self._import_futures: dict[str, asyncio.Future[ModuleType]] = {} cache: dict[str, ModuleType | ComponentProtocol] = hass.data[DATA_COMPONENTS] self._cache = cache - missing_platforms_cache: dict[str, ImportError] = hass.data[ - DATA_MISSING_PLATFORMS - ] + missing_platforms_cache: dict[str, bool] = hass.data[DATA_MISSING_PLATFORMS] self._missing_platforms_cache = missing_platforms_cache self._top_level_files = top_level_files or set() _LOGGER.info("Loaded %s from %s", self.domain, pkg_path) @@ -1085,8 +1083,7 @@ class Integration: import_futures: list[tuple[str, asyncio.Future[ModuleType]]] = [] for platform_name in platform_names: - full_name = f"{domain}.{platform_name}" - if platform := self._get_platform_cached_or_raise(full_name): + if platform := self._get_platform_cached_or_raise(platform_name): platforms[platform_name] = platform continue @@ -1095,6 +1092,7 @@ class Integration: in_progress_imports[platform_name] = future continue + full_name = f"{domain}.{platform_name}" if ( self.import_executor and full_name not in self.hass.config.components @@ -1166,14 +1164,18 @@ class Integration: return platforms - def _get_platform_cached_or_raise(self, full_name: str) -> ModuleType | None: + def _get_platform_cached_or_raise(self, platform_name: str) -> ModuleType | None: """Return a platform for an integration from cache.""" + full_name = f"{self.domain}.{platform_name}" if full_name in self._cache: # the cache is either a ModuleType or a ComponentProtocol # but we only care about the ModuleType here return self._cache[full_name] # type: ignore[return-value] if full_name in self._missing_platforms_cache: - raise self._missing_platforms_cache[full_name] + raise ModuleNotFoundError( + f"Platform {full_name} not found", + name=f"{self.pkg_path}.{platform_name}", + ) return None def platforms_are_loaded(self, platform_names: Iterable[str]) -> bool: @@ -1189,9 +1191,7 @@ class Integration: def get_platform(self, platform_name: str) -> ModuleType: """Return a platform for an integration.""" - if platform := self._get_platform_cached_or_raise( - f"{self.domain}.{platform_name}" - ): + if platform := self._get_platform_cached_or_raise(platform_name): return platform return self._load_platform(platform_name) @@ -1212,10 +1212,7 @@ class Integration: ): existing_platforms.append(platform_name) continue - missing_platforms[full_name] = ModuleNotFoundError( - f"Platform {full_name} not found", - name=f"{self.pkg_path}.{platform_name}", - ) + missing_platforms[full_name] = True return existing_platforms @@ -1233,11 +1230,13 @@ class Integration: cache: dict[str, ModuleType] = self.hass.data[DATA_COMPONENTS] try: cache[full_name] = self._import_platform(platform_name) - except ImportError as ex: + except ModuleNotFoundError: if self.domain in cache: # If the domain is loaded, cache that the platform # does not exist so we do not try to load it again - self._missing_platforms_cache[full_name] = ex + self._missing_platforms_cache[full_name] = True + raise + except ImportError: raise except RuntimeError as err: # _DeadlockError inherits from RuntimeError diff --git a/tests/test_loader.py b/tests/test_loader.py index 9e191ee9e00..6685bb4f2ac 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -274,7 +274,61 @@ async def test_get_integration_exceptions(hass: HomeAssistant) -> None: async def test_get_platform_caches_failures_when_component_loaded( hass: HomeAssistant, ) -> None: - """Test get_platform cache failures only when the component is loaded.""" + """Test get_platform caches failures only when the component is loaded. + + Only ModuleNotFoundError is cached, ImportError is not cached. + """ + integration = await loader.async_get_integration(hass, "hue") + + with ( + pytest.raises(ModuleNotFoundError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ModuleNotFoundError("Boom"), + ), + ): + assert integration.get_component() == hue + + with ( + pytest.raises(ModuleNotFoundError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ModuleNotFoundError("Boom"), + ), + ): + assert integration.get_platform("light") == hue_light + + # Hue is not loaded so we should still hit the import_module path + with ( + pytest.raises(ModuleNotFoundError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ModuleNotFoundError("Boom"), + ), + ): + assert integration.get_platform("light") == hue_light + + assert integration.get_component() == hue + + # Hue is loaded so we should cache the import_module failure now + with ( + pytest.raises(ModuleNotFoundError), + patch( + "homeassistant.loader.importlib.import_module", + side_effect=ModuleNotFoundError("Boom"), + ), + ): + assert integration.get_platform("light") == hue_light + + # Hue is loaded and the last call should have cached the import_module failure + with pytest.raises(ModuleNotFoundError): + assert integration.get_platform("light") == hue_light + + +async def test_get_platform_only_cached_module_not_found_when_component_loaded( + hass: HomeAssistant, +) -> None: + """Test get_platform cache only cache module not found when the component is loaded.""" integration = await loader.async_get_integration(hass, "hue") with ( @@ -317,41 +371,43 @@ async def test_get_platform_caches_failures_when_component_loaded( ): assert integration.get_platform("light") == hue_light - # Hue is loaded and the last call should have cached the import_module failure - with pytest.raises(ImportError): - assert integration.get_platform("light") == hue_light + # ImportError is not cached because we only cache ModuleNotFoundError + assert integration.get_platform("light") == hue_light async def test_async_get_platform_caches_failures_when_component_loaded( hass: HomeAssistant, ) -> None: - """Test async_get_platform cache failures only when the component is loaded.""" + """Test async_get_platform caches failures only when the component is loaded. + + Only ModuleNotFoundError is cached, ImportError is not cached. + """ integration = await loader.async_get_integration(hass, "hue") with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert integration.get_component() == hue with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert await integration.async_get_platform("light") == hue_light # Hue is not loaded so we should still hit the import_module path with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert await integration.async_get_platform("light") == hue_light @@ -360,16 +416,16 @@ async def test_async_get_platform_caches_failures_when_component_loaded( # Hue is loaded so we should cache the import_module failure now with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert await integration.async_get_platform("light") == hue_light # Hue is loaded and the last call should have cached the import_module failure - with pytest.raises(ImportError): + with pytest.raises(ModuleNotFoundError): assert await integration.async_get_platform("light") == hue_light # The cache should never be filled because the import error is remembered @@ -379,33 +435,36 @@ async def test_async_get_platform_caches_failures_when_component_loaded( async def test_async_get_platforms_caches_failures_when_component_loaded( hass: HomeAssistant, ) -> None: - """Test async_get_platforms cache failures only when the component is loaded.""" + """Test async_get_platforms cache failures only when the component is loaded. + + Only ModuleNotFoundError is cached, ImportError is not cached. + """ integration = await loader.async_get_integration(hass, "hue") with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert integration.get_component() == hue with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} # Hue is not loaded so we should still hit the import_module path with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} @@ -414,16 +473,16 @@ async def test_async_get_platforms_caches_failures_when_component_loaded( # Hue is loaded so we should cache the import_module failure now with ( - pytest.raises(ImportError), + pytest.raises(ModuleNotFoundError), patch( "homeassistant.loader.importlib.import_module", - side_effect=ImportError("Boom"), + side_effect=ModuleNotFoundError("Boom"), ), ): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} # Hue is loaded and the last call should have cached the import_module failure - with pytest.raises(ImportError): + with pytest.raises(ModuleNotFoundError): assert await integration.async_get_platforms(["light"]) == {"light": hue_light} # The cache should never be filled because the import error is remembered From 52612b10fdd6cf32e7f06a04ce5dd1fbfc66512d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Apr 2024 15:35:38 -1000 Subject: [PATCH 1651/1691] Avoid storing raw extracted traceback in system_log (#114603) This is never actually used and takes up quite a bit of ram --- homeassistant/components/system_log/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 77f0b095a30..423f5c6f5d8 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -166,7 +166,6 @@ class LogEntry: "level", "message", "exception", - "extracted_tb", "root_cause", "source", "count", @@ -200,7 +199,6 @@ class LogEntry: else: self.source = (record.pathname, record.lineno) self.count = 1 - self.extracted_tb = extracted_tb self.key = (self.name, self.source, self.root_cause) def to_dict(self) -> dict[str, Any]: From bc21836e7eeadcb593cb3b37107e60912cf16b7a Mon Sep 17 00:00:00 2001 From: mkmer Date: Mon, 1 Apr 2024 21:47:30 -0400 Subject: [PATCH 1652/1691] Bump whirlpool-sixth-sense to 0.18.7 (#114606) Bump sixth-sense to 0.18.7 --- homeassistant/components/whirlpool/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/whirlpool/manifest.json b/homeassistant/components/whirlpool/manifest.json index 0c46580ceeb..ee7861588ed 100644 --- a/homeassistant/components/whirlpool/manifest.json +++ b/homeassistant/components/whirlpool/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "iot_class": "cloud_push", "loggers": ["whirlpool"], - "requirements": ["whirlpool-sixth-sense==0.18.6"] + "requirements": ["whirlpool-sixth-sense==0.18.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 77c6247e856..8fb57f0834a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2847,7 +2847,7 @@ webmin-xmlrpc==0.0.2 webrtc-noise-gain==1.2.3 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.18.6 +whirlpool-sixth-sense==0.18.7 # homeassistant.components.whois whois==0.9.27 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 648fd38f1cb..6eb3c89e09b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2194,7 +2194,7 @@ webmin-xmlrpc==0.0.2 webrtc-noise-gain==1.2.3 # homeassistant.components.whirlpool -whirlpool-sixth-sense==0.18.6 +whirlpool-sixth-sense==0.18.7 # homeassistant.components.whois whois==0.9.27 From 7164993562d0f4bd008da234bc9954915a7af158 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 2 Apr 2024 01:51:42 +0000 Subject: [PATCH 1653/1691] Bump version to 2024.4.0b4 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index f56ce656157..a69c4c84e27 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b3" +PATCH_VERSION: Final = "0b4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 73bfdd6d5d7..11007e624dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b3" +version = "2024.4.0b4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From a6076a0d33326f467f59c017a8f4d9d0d452ff7e Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Tue, 2 Apr 2024 04:11:45 -0400 Subject: [PATCH 1654/1691] Display sonos album title with URL encoding (#113693) * unescape the title When extracting the title from the item_id, it needs to be unescaped. * sort imports --- .../components/sonos/media_browser.py | 2 +- tests/components/sonos/test_media_browser.py | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 tests/components/sonos/test_media_browser.py diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 17327bf4be1..9d3ef5d353b 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -201,7 +201,7 @@ def build_item_response( if not title: try: - title = payload["idstring"].split("/")[1] + title = urllib.parse.unquote(payload["idstring"].split("/")[1]) except IndexError: title = LIBRARY_TITLES_MAPPING[payload["idstring"]] diff --git a/tests/components/sonos/test_media_browser.py b/tests/components/sonos/test_media_browser.py new file mode 100644 index 00000000000..cb6303c800d --- /dev/null +++ b/tests/components/sonos/test_media_browser.py @@ -0,0 +1,96 @@ +"""Tests for the Sonos Media Browser.""" + +from functools import partial + +from homeassistant.components.media_player.browse_media import BrowseMedia +from homeassistant.components.media_player.const import MediaClass, MediaType +from homeassistant.components.sonos.media_browser import ( + build_item_response, + get_thumbnail_url_full, +) +from homeassistant.core import HomeAssistant + +from .conftest import SoCoMockFactory + + +class MockMusicServiceItem: + """Mocks a Soco MusicServiceItem.""" + + def __init__( + self, + title: str, + item_id: str, + parent_id: str, + item_class: str, + ) -> None: + """Initialize the mock item.""" + self.title = title + self.item_id = item_id + self.item_class = item_class + self.parent_id = parent_id + + def get_uri(self) -> str: + """Return URI.""" + return self.item_id.replace("S://", "x-file-cifs://") + + +def mock_browse_by_idstring( + search_type: str, idstring: str, start=0, max_items=100, full_album_art_uri=False +) -> list[MockMusicServiceItem]: + """Mock the call to browse_by_id_string.""" + if search_type == "albums" and ( + idstring == "A:ALBUM/Abbey%20Road" or idstring == "A:ALBUM/Abbey Road" + ): + return [ + MockMusicServiceItem( + "Come Together", + "S://192.168.42.10/music/The%20Beatles/Abbey%20Road/01%20Come%20Together.mp3", + "A:ALBUM/Abbey%20Road", + "object.item.audioItem.musicTrack", + ), + MockMusicServiceItem( + "Something", + "S://192.168.42.10/music/The%20Beatles/Abbey%20Road/03%20Something.mp3", + "A:ALBUM/Abbey%20Road", + "object.item.audioItem.musicTrack", + ), + ] + return None + + +async def test_build_item_response( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, +) -> None: + """Test building a browse item response.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + soco_mock.music_library.browse_by_idstring = mock_browse_by_idstring + browse_item: BrowseMedia = build_item_response( + soco_mock.music_library, + {"search_type": MediaType.ALBUM, "idstring": "A:ALBUM/Abbey%20Road"}, + partial( + get_thumbnail_url_full, + soco_mock.music_library, + True, + None, + ), + ) + assert browse_item.title == "Abbey Road" + assert browse_item.media_class == MediaClass.ALBUM + assert browse_item.media_content_id == "A:ALBUM/Abbey%20Road" + assert len(browse_item.children) == 2 + assert browse_item.children[0].media_class == MediaClass.TRACK + assert browse_item.children[0].title == "Come Together" + assert ( + browse_item.children[0].media_content_id + == "x-file-cifs://192.168.42.10/music/The%20Beatles/Abbey%20Road/01%20Come%20Together.mp3" + ) + assert browse_item.children[1].media_class == MediaClass.TRACK + assert browse_item.children[1].title == "Something" + assert ( + browse_item.children[1].media_content_id + == "x-file-cifs://192.168.42.10/music/The%20Beatles/Abbey%20Road/03%20Something.mp3" + ) From 18b6de567d4f481c65368452c49e483117eec721 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Tue, 2 Apr 2024 11:15:52 +0200 Subject: [PATCH 1655/1691] Bump roombapy to 1.8.1 (#114478) * Bump roombapy to 1.7.0 * Bump * Bump * Fix --- homeassistant/components/roomba/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/roomba/test_config_flow.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index ae08d8f6a1f..a697680b379 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -24,7 +24,7 @@ "documentation": "https://www.home-assistant.io/integrations/roomba", "iot_class": "local_push", "loggers": ["paho_mqtt", "roombapy"], - "requirements": ["roombapy==1.6.13"], + "requirements": ["roombapy==1.8.1"], "zeroconf": [ { "type": "_amzn-alexa._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 8fb57f0834a..a98a76af9b0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2462,7 +2462,7 @@ rokuecp==0.19.2 romy==0.0.7 # homeassistant.components.roomba -roombapy==1.6.13 +roombapy==1.8.1 # homeassistant.components.roon roonapi==0.1.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6eb3c89e09b..3937eae1e53 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1896,7 +1896,7 @@ rokuecp==0.19.2 romy==0.0.7 # homeassistant.components.roomba -roombapy==1.6.13 +roombapy==1.8.1 # homeassistant.components.roon roonapi==0.1.6 diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index 2eaf3b14e38..282884c0be3 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -99,12 +99,12 @@ def _mocked_discovery(*_): roomba = RoombaInfo( hostname="irobot-BLID", - robotname="robot_name", + robot_name="robot_name", ip=MOCK_IP, mac="mac", - sw="firmware", + firmware="firmware", sku="sku", - cap={"cap": 1}, + capabilities={"cap": 1}, ) roomba_discovery.get_all = MagicMock(return_value=[roomba]) From 7b84e86f898efa14dca4e74323d3aecff1f0e703 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 2 Apr 2024 10:15:58 +0200 Subject: [PATCH 1656/1691] Improve Shelly RPC device update progress (#114566) Co-authored-by: Shay Levy Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/shelly/update.py | 16 ++++++++++------ tests/components/shelly/test_update.py | 19 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/shelly/update.py b/homeassistant/components/shelly/update.py index f6a89c5381b..56ad1f2ef67 100644 --- a/homeassistant/components/shelly/update.py +++ b/homeassistant/components/shelly/update.py @@ -222,7 +222,7 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): ) -> None: """Initialize update entity.""" super().__init__(coordinator, key, attribute, description) - self._ota_in_progress: bool = False + self._ota_in_progress: bool | int = False self._attr_release_url = get_release_url( coordinator.device.gen, coordinator.model, description.beta ) @@ -237,14 +237,13 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): @callback def _ota_progress_callback(self, event: dict[str, Any]) -> None: """Handle device OTA progress.""" - if self._ota_in_progress: + if self.in_progress is not False: event_type = event["event"] if event_type == OTA_BEGIN: - self._attr_in_progress = 0 + self._ota_in_progress = 0 elif event_type == OTA_PROGRESS: - self._attr_in_progress = event["progress_percent"] + self._ota_in_progress = event["progress_percent"] elif event_type in (OTA_ERROR, OTA_SUCCESS): - self._attr_in_progress = False self._ota_in_progress = False self.async_write_ha_state() @@ -262,6 +261,11 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): return self.installed_version + @property + def in_progress(self) -> bool | int: + """Update installation in progress.""" + return self._ota_in_progress + async def async_install( self, version: str | None, backup: bool, **kwargs: Any ) -> None: @@ -292,7 +296,7 @@ class RpcUpdateEntity(ShellyRpcAttributeEntity, UpdateEntity): await self.coordinator.async_shutdown_device_and_start_reauth() else: self._ota_in_progress = True - LOGGER.debug("OTA update call successful") + LOGGER.info("OTA update call for %s successful", self.coordinator.name) class RpcSleepingUpdateEntity( diff --git a/tests/components/shelly/test_update.py b/tests/components/shelly/test_update.py index 387dc93e33e..f3960620a21 100644 --- a/tests/components/shelly/test_update.py +++ b/tests/components/shelly/test_update.py @@ -255,6 +255,16 @@ async def test_rpc_update( {ATTR_ENTITY_ID: entity_id}, blocking=True, ) + + assert mock_rpc_device.trigger_ota_update.call_count == 1 + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_INSTALLED_VERSION] == "1" + assert state.attributes[ATTR_LATEST_VERSION] == "2" + assert state.attributes[ATTR_IN_PROGRESS] is True + assert state.attributes[ATTR_RELEASE_URL] == GEN2_RELEASE_URL + inject_rpc_device_event( monkeypatch, mock_rpc_device, @@ -270,14 +280,7 @@ async def test_rpc_update( }, ) - assert mock_rpc_device.trigger_ota_update.call_count == 1 - - state = hass.states.get(entity_id) - assert state.state == STATE_ON - assert state.attributes[ATTR_INSTALLED_VERSION] == "1" - assert state.attributes[ATTR_LATEST_VERSION] == "2" - assert state.attributes[ATTR_IN_PROGRESS] == 0 - assert state.attributes[ATTR_RELEASE_URL] == GEN2_RELEASE_URL + assert hass.states.get(entity_id).attributes[ATTR_IN_PROGRESS] == 0 inject_rpc_device_event( monkeypatch, From e5a620545c49a9181627f606a0a431dc4df9dd46 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Tue, 2 Apr 2024 20:23:08 +1000 Subject: [PATCH 1657/1691] Fix battery heater in Tessie (#114568) --- homeassistant/components/tessie/binary_sensor.py | 2 +- homeassistant/components/tessie/strings.json | 2 +- tests/components/tessie/snapshots/test_binary_sensors.ambr | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tessie/binary_sensor.py b/homeassistant/components/tessie/binary_sensor.py index 015fa63736f..9b7d6861dfb 100644 --- a/homeassistant/components/tessie/binary_sensor.py +++ b/homeassistant/components/tessie/binary_sensor.py @@ -34,7 +34,7 @@ DESCRIPTIONS: tuple[TessieBinarySensorEntityDescription, ...] = ( is_on=lambda x: x == TessieState.ONLINE, ), TessieBinarySensorEntityDescription( - key="charge_state_battery_heater_on", + key="climate_state_battery_heater", device_class=BinarySensorDeviceClass.HEAT, entity_category=EntityCategory.DIAGNOSTIC, ), diff --git a/homeassistant/components/tessie/strings.json b/homeassistant/components/tessie/strings.json index 62de4f276f4..8e1e47f934f 100644 --- a/homeassistant/components/tessie/strings.json +++ b/homeassistant/components/tessie/strings.json @@ -252,7 +252,7 @@ "state": { "name": "Status" }, - "charge_state_battery_heater_on": { + "climate_state_battery_heater": { "name": "Battery heater" }, "charge_state_charge_enable_request": { diff --git a/tests/components/tessie/snapshots/test_binary_sensors.ambr b/tests/components/tessie/snapshots/test_binary_sensors.ambr index 854e1350234..7bc191de6ed 100644 --- a/tests/components/tessie/snapshots/test_binary_sensors.ambr +++ b/tests/components/tessie/snapshots/test_binary_sensors.ambr @@ -165,8 +165,8 @@ 'platform': 'tessie', 'previous_unique_id': None, 'supported_features': 0, - 'translation_key': 'charge_state_battery_heater_on', - 'unique_id': 'VINVINVIN-charge_state_battery_heater_on', + 'translation_key': 'climate_state_battery_heater', + 'unique_id': 'VINVINVIN-climate_state_battery_heater', 'unit_of_measurement': None, }) # --- From 384d10a51d298f7bddbd6c16dfc07bc3c4bef165 Mon Sep 17 00:00:00 2001 From: mkmer Date: Tue, 2 Apr 2024 03:41:40 -0400 Subject: [PATCH 1658/1691] Add diagnostic platform to Whirlpool (#114578) * Add diagnostic platform and tests * lowercase variable * Correc doc string --- .../components/whirlpool/diagnostics.py | 49 +++++++++++++++++++ .../whirlpool/snapshots/test_diagnostics.ambr | 44 +++++++++++++++++ .../components/whirlpool/test_diagnostics.py | 32 ++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 homeassistant/components/whirlpool/diagnostics.py create mode 100644 tests/components/whirlpool/snapshots/test_diagnostics.ambr create mode 100644 tests/components/whirlpool/test_diagnostics.py diff --git a/homeassistant/components/whirlpool/diagnostics.py b/homeassistant/components/whirlpool/diagnostics.py new file mode 100644 index 00000000000..9b1dd00e7bd --- /dev/null +++ b/homeassistant/components/whirlpool/diagnostics.py @@ -0,0 +1,49 @@ +"""Diagnostics support for Whirlpool.""" + +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import WhirlpoolData +from .const import DOMAIN + +TO_REDACT = { + "SERIAL_NUMBER", + "macaddress", + "username", + "password", + "token", + "unique_id", + "SAID", +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, + config_entry: ConfigEntry, +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + + whirlpool: WhirlpoolData = hass.data[DOMAIN][config_entry.entry_id] + diagnostics_data = { + "Washer_dryers": { + wd["NAME"]: dict(wd.items()) + for wd in whirlpool.appliances_manager.washer_dryers + }, + "aircons": { + ac["NAME"]: dict(ac.items()) for ac in whirlpool.appliances_manager.aircons + }, + "ovens": { + oven["NAME"]: dict(oven.items()) + for oven in whirlpool.appliances_manager.ovens + }, + } + + return { + "config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT), + "appliances": async_redact_data(diagnostics_data, TO_REDACT), + } diff --git a/tests/components/whirlpool/snapshots/test_diagnostics.ambr b/tests/components/whirlpool/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..5a0beb112e6 --- /dev/null +++ b/tests/components/whirlpool/snapshots/test_diagnostics.ambr @@ -0,0 +1,44 @@ +# serializer version: 1 +# name: test_entry_diagnostics + dict({ + 'appliances': dict({ + 'Washer_dryers': dict({ + 'dryer': dict({ + 'NAME': 'dryer', + 'SAID': '**REDACTED**', + }), + 'washer': dict({ + 'NAME': 'washer', + 'SAID': '**REDACTED**', + }), + }), + 'aircons': dict({ + 'TestZone': dict({ + 'NAME': 'TestZone', + 'SAID': '**REDACTED**', + }), + }), + 'ovens': dict({ + }), + }), + 'config_entry': dict({ + 'data': dict({ + 'brand': 'Whirlpool', + 'password': '**REDACTED**', + 'region': 'EU', + 'username': '**REDACTED**', + }), + 'disabled_by': None, + 'domain': 'whirlpool', + 'minor_version': 1, + 'options': dict({ + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': 'Mock Title', + 'unique_id': None, + 'version': 1, + }), + }) +# --- diff --git a/tests/components/whirlpool/test_diagnostics.py b/tests/components/whirlpool/test_diagnostics.py new file mode 100644 index 00000000000..6cfc1b76e38 --- /dev/null +++ b/tests/components/whirlpool/test_diagnostics.py @@ -0,0 +1,32 @@ +"""Test Blink diagnostics.""" + +from unittest.mock import MagicMock + +from syrupy import SnapshotAssertion +from syrupy.filters import props + +from homeassistant.core import HomeAssistant + +from . import init_integration + +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + +YAML_CONFIG = {"username": "test-user", "password": "test-password"} + + +async def test_entry_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, + mock_appliances_manager_api: MagicMock, + mock_aircon1_api: MagicMock, + mock_aircon_api_instances: MagicMock, +) -> None: + """Test config entry diagnostics.""" + + mock_entry = await init_integration(hass) + + result = await get_diagnostics_for_config_entry(hass, hass_client, mock_entry) + + assert result == snapshot(exclude=props("entry_id")) From 559fe65471fa29339b005f610c1133fa50c3259a Mon Sep 17 00:00:00 2001 From: Jack Boswell Date: Tue, 2 Apr 2024 21:45:46 +1300 Subject: [PATCH 1659/1691] Catch potential ValueError when getting or setting Starlink sleep values (#114607) --- homeassistant/components/starlink/time.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/starlink/time.py b/homeassistant/components/starlink/time.py index 4d9e2d06675..6475610564d 100644 --- a/homeassistant/components/starlink/time.py +++ b/homeassistant/components/starlink/time.py @@ -10,6 +10,7 @@ import math from homeassistant.components.time import TimeEntity, TimeEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN @@ -62,14 +63,22 @@ class StarlinkTimeEntity(StarlinkEntity, TimeEntity): def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time: hour = math.floor(utc_minutes / 60) minute = utc_minutes % 60 - utc = datetime.now(UTC).replace(hour=hour, minute=minute, second=0, microsecond=0) + try: + utc = datetime.now(UTC).replace( + hour=hour, minute=minute, second=0, microsecond=0 + ) + except ValueError as exc: + raise HomeAssistantError from exc return utc.astimezone(timezone).time() def _time_to_utc_minutes(t: time, timezone: tzinfo) -> int: - zoned_time = datetime.now(timezone).replace( - hour=t.hour, minute=t.minute, second=0, microsecond=0 - ) + try: + zoned_time = datetime.now(timezone).replace( + hour=t.hour, minute=t.minute, second=0, microsecond=0 + ) + except ValueError as exc: + raise HomeAssistantError from exc utc_time = zoned_time.astimezone(UTC).time() return (utc_time.hour * 60) + utc_time.minute From 230c29edbed885a39ba7fd38d098b40e84c74ded Mon Sep 17 00:00:00 2001 From: max2697 <143563471+max2697@users.noreply.github.com> Date: Tue, 2 Apr 2024 02:04:28 -0500 Subject: [PATCH 1660/1691] Bump opower to 0.4.2 (#114608) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index bc6f8796d50..879aeb0327b 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", "loggers": ["opower"], - "requirements": ["opower==0.4.1"] + "requirements": ["opower==0.4.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index a98a76af9b0..a6f4d51d89b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1482,7 +1482,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.4.1 +opower==0.4.2 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3937eae1e53..fdb4c92e442 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1176,7 +1176,7 @@ openerz-api==0.3.0 openhomedevice==2.2.0 # homeassistant.components.opower -opower==0.4.1 +opower==0.4.2 # homeassistant.components.oralb oralb-ble==0.17.6 From 92dfec3c98359d1ecabc7d42871812c56eebb537 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Apr 2024 10:43:14 +0200 Subject: [PATCH 1661/1691] Add floor selector (#114614) --- homeassistant/helpers/selector.py | 42 +++++++++++++++++++ tests/helpers/test_selector.py | 67 +++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 938cc6a9246..c4db601fac6 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -844,6 +844,48 @@ class EntitySelector(Selector[EntitySelectorConfig]): return cast(list, vol.Schema([validate])(data)) # Output is a list +class FloorSelectorConfig(TypedDict, total=False): + """Class to represent an floor selector config.""" + + entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] + device: DeviceFilterSelectorConfig | list[DeviceFilterSelectorConfig] + multiple: bool + + +@SELECTORS.register("floor") +class FloorSelector(Selector[AreaSelectorConfig]): + """Selector of a single or list of floors.""" + + selector_type = "floor" + + CONFIG_SCHEMA = vol.Schema( + { + vol.Optional("entity"): vol.All( + cv.ensure_list, + [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA], + ), + vol.Optional("device"): vol.All( + cv.ensure_list, + [DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], + ), + vol.Optional("multiple", default=False): cv.boolean, + } + ) + + def __init__(self, config: FloorSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + + def __call__(self, data: Any) -> str | list[str]: + """Validate the passed selection.""" + if not self.config["multiple"]: + floor_id: str = vol.Schema(str)(data) + return floor_id + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [vol.Schema(str)(val) for val in data] + + class IconSelectorConfig(TypedDict, total=False): """Class to represent an icon selector config.""" diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 0dc7e570fc5..8864edc7386 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -1158,3 +1158,70 @@ def test_qr_code_selector_schema(schema, valid_selections, invalid_selections) - def test_label_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test label selector.""" _test_selector("label", schema, valid_selections, invalid_selections) + + +@pytest.mark.parametrize( + ("schema", "valid_selections", "invalid_selections"), + [ + ({}, ("abc123",), (None,)), + ({"entity": {}}, ("abc123",), (None,)), + ({"entity": {"domain": "light"}}, ("abc123",), (None,)), + ( + {"entity": {"domain": "binary_sensor", "device_class": "motion"}}, + ("abc123",), + (None,), + ), + ( + { + "entity": { + "domain": "binary_sensor", + "device_class": "motion", + "integration": "demo", + } + }, + ("abc123",), + (None,), + ), + ( + { + "entity": [ + {"domain": "light"}, + {"domain": "binary_sensor", "device_class": "motion"}, + ] + }, + ("abc123",), + (None,), + ), + ( + {"device": {"integration": "demo", "model": "mock-model"}}, + ("abc123",), + (None,), + ), + ( + { + "device": [ + {"integration": "demo", "model": "mock-model"}, + {"integration": "other-demo", "model": "other-mock-model"}, + ] + }, + ("abc123",), + (None,), + ), + ( + { + "entity": {"domain": "binary_sensor", "device_class": "motion"}, + "device": {"integration": "demo", "model": "mock-model"}, + }, + ("abc123",), + (None,), + ), + ( + {"multiple": True}, + ((["abc123", "def456"],)), + (None, "abc123", ["abc123", None]), + ), + ], +) +def test_floor_selector_schema(schema, valid_selections, invalid_selections) -> None: + """Test floor selector.""" + _test_selector("floor", schema, valid_selections, invalid_selections) From ca31479d298490d9a19e55dace29648e3e92dba4 Mon Sep 17 00:00:00 2001 From: Fexiven <48439988+Fexiven@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:04:07 +0200 Subject: [PATCH 1662/1691] Fix Starlink integration startup issue (#114615) --- homeassistant/components/starlink/coordinator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/starlink/coordinator.py b/homeassistant/components/starlink/coordinator.py index 9c597fbb033..ff33b3ecc41 100644 --- a/homeassistant/components/starlink/coordinator.py +++ b/homeassistant/components/starlink/coordinator.py @@ -58,14 +58,14 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]): async def _async_update_data(self) -> StarlinkData: async with asyncio.timeout(4): try: - status, location, sleep = await asyncio.gather( - self.hass.async_add_executor_job(status_data, self.channel_context), - self.hass.async_add_executor_job( - location_data, self.channel_context - ), - self.hass.async_add_executor_job( - get_sleep_config, self.channel_context - ), + status = await self.hass.async_add_executor_job( + status_data, self.channel_context + ) + location = await self.hass.async_add_executor_job( + location_data, self.channel_context + ) + sleep = await self.hass.async_add_executor_job( + get_sleep_config, self.channel_context ) return StarlinkData(location, sleep, *status) except GrpcError as exc: From b539b25682754f9d4abb7ef4361184a7054c52ac Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Apr 2024 12:17:52 +0200 Subject: [PATCH 1663/1691] Update frontend to 20240402.0 (#114627) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 7864801a986..5eaa6e94769 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240329.1"] + "requirements": ["home-assistant-frontend==20240402.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9621137d855..eb6d347a479 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240329.1 +home-assistant-frontend==20240402.0 home-assistant-intents==2024.3.29 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index a6f4d51d89b..adfcc1322e3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240329.1 +home-assistant-frontend==20240402.0 # homeassistant.components.conversation home-assistant-intents==2024.3.29 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fdb4c92e442..85fb1c70471 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240329.1 +home-assistant-frontend==20240402.0 # homeassistant.components.conversation home-assistant-intents==2024.3.29 From 5af5f3694ef2816e34c585f04ff9862f929b7278 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Apr 2024 12:28:20 +0200 Subject: [PATCH 1664/1691] Bump version to 2024.4.0b5 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a69c4c84e27..0cac1ae45a3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b4" +PATCH_VERSION: Final = "0b5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 11007e624dc..62c9166cd22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b4" +version = "2024.4.0b5" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 4e0d6f287ed716edcf9a39267ce08dce4b4cd1da Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 2 Apr 2024 08:16:59 -0700 Subject: [PATCH 1665/1691] Reduce ZHA OTA logbook entries and extraneous updates (#114591) --- .../components/zha/core/cluster_handlers/general.py | 7 +++++++ homeassistant/components/zha/update.py | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zha/core/cluster_handlers/general.py b/homeassistant/components/zha/core/cluster_handlers/general.py index 478f41da3b7..438fc6b1723 100644 --- a/homeassistant/components/zha/core/cluster_handlers/general.py +++ b/homeassistant/components/zha/core/cluster_handlers/general.py @@ -553,6 +553,13 @@ class OtaClientClusterHandler(ClientClusterHandler): Ota.AttributeDefs.current_file_version.name: True, } + @callback + def attribute_updated(self, attrid: int, value: Any, timestamp: Any) -> None: + """Handle an attribute updated on this cluster.""" + # We intentionally avoid the `ClientClusterHandler` attribute update handler: + # it emits a logbook event on every update, which pollutes the logbook + ClusterHandler.attribute_updated(self, attrid, value, timestamp) + @property def current_file_version(self) -> int | None: """Return cached value of current_file_version attribute.""" diff --git a/homeassistant/components/zha/update.py b/homeassistant/components/zha/update.py index 7ceba4fc924..0cb80d13119 100644 --- a/homeassistant/components/zha/update.py +++ b/homeassistant/components/zha/update.py @@ -130,14 +130,9 @@ class ZHAFirmwareUpdateEntity( def _get_cluster_version(self) -> str | None: """Synchronize current file version with the cluster.""" - device = self._ota_cluster_handler._endpoint.device # pylint: disable=protected-access - if self._ota_cluster_handler.current_file_version is not None: return f"0x{self._ota_cluster_handler.current_file_version:08x}" - if device.sw_version is not None: - return device.sw_version - return None @callback From d53848aae44d4c25849fdaea29ad22b408a77400 Mon Sep 17 00:00:00 2001 From: dotvav Date: Tue, 2 Apr 2024 13:08:53 +0200 Subject: [PATCH 1666/1691] Fix Overkiz Hitachi OVP air-to-air heat pump (#114611) --- .../climate_entities/hitachi_air_to_air_heat_pump_ovp.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py index b4d6ab788a1..b31ecf91ec0 100644 --- a/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py +++ b/homeassistant/components/overkiz/climate_entities/hitachi_air_to_air_heat_pump_ovp.py @@ -298,6 +298,11 @@ class HitachiAirToAirHeatPumpOVP(OverkizEntity, ClimateEntity): OverkizState.OVP_FAN_SPEED, OverkizCommandParam.AUTO, ) + # Sanitize fan mode: Overkiz is sometimes providing a state that + # cannot be used as a command. Convert it to HA space and back to Overkiz + if fan_mode not in FAN_MODES_TO_OVERKIZ.values(): + fan_mode = FAN_MODES_TO_OVERKIZ[OVERKIZ_TO_FAN_MODES[fan_mode]] + hvac_mode = self._control_backfill( hvac_mode, OverkizState.OVP_MODE_CHANGE, From 5bd52da13a595f9bb41690bfe2096a01d427afce Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:17:47 +0100 Subject: [PATCH 1667/1691] Bump ring_doorbell integration to 0.8.9 (#114631) --- homeassistant/components/ring/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ring/manifest.json b/homeassistant/components/ring/manifest.json index 764557a3a1d..67e2cfcdc78 100644 --- a/homeassistant/components/ring/manifest.json +++ b/homeassistant/components/ring/manifest.json @@ -13,5 +13,5 @@ "documentation": "https://www.home-assistant.io/integrations/ring", "iot_class": "cloud_polling", "loggers": ["ring_doorbell"], - "requirements": ["ring-doorbell[listen]==0.8.8"] + "requirements": ["ring-doorbell[listen]==0.8.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index adfcc1322e3..fd44076ef24 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2444,7 +2444,7 @@ rfk101py==0.0.1 rflink==0.0.66 # homeassistant.components.ring -ring-doorbell[listen]==0.8.8 +ring-doorbell[listen]==0.8.9 # homeassistant.components.fleetgo ritassist==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 85fb1c70471..70c27940562 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1887,7 +1887,7 @@ reolink-aio==0.8.9 rflink==0.0.66 # homeassistant.components.ring -ring-doorbell[listen]==0.8.8 +ring-doorbell[listen]==0.8.9 # homeassistant.components.roku rokuecp==0.19.2 From 8cbedbe26b6e588f652c566073716ef76d081aa6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Apr 2024 17:15:24 +0200 Subject: [PATCH 1668/1691] Update frontend to 20240402.1 (#114646) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 5eaa6e94769..2010a9985b3 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240402.0"] + "requirements": ["home-assistant-frontend==20240402.1"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index eb6d347a479..cf0aa0848af 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240402.0 +home-assistant-frontend==20240402.1 home-assistant-intents==2024.3.29 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index fd44076ef24..5bb02a079fd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240402.0 +home-assistant-frontend==20240402.1 # homeassistant.components.conversation home-assistant-intents==2024.3.29 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 70c27940562..e25784596a3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.45 # homeassistant.components.frontend -home-assistant-frontend==20240402.0 +home-assistant-frontend==20240402.1 # homeassistant.components.conversation home-assistant-intents==2024.3.29 From 85fb4a27a3a728d1bf178b300ed4055f956dd28c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Apr 2024 17:35:01 +0200 Subject: [PATCH 1669/1691] Bump version to 2024.4.0b6 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 0cac1ae45a3..fd3b1257df2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b5" +PATCH_VERSION: Final = "0b6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 62c9166cd22..73d03f0f92c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b5" +version = "2024.4.0b6" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 2ce784105d40890e36eceb6a12d2eb28df663668 Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:10:15 -0400 Subject: [PATCH 1670/1691] Fix Sonos play imported playlists (#113934) --- .../components/sonos/media_browser.py | 14 +++ .../components/sonos/media_player.py | 12 +- tests/components/sonos/test_media_player.py | 117 ++++++++++++++++++ 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 9d3ef5d353b..87ee3ed3b4d 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -493,6 +493,20 @@ def get_media( """Fetch media/album.""" search_type = MEDIA_TYPES_TO_SONOS.get(search_type, search_type) + if search_type == "playlists": + # Format is S:TITLE or S:ITEM_ID + splits = item_id.split(":") + title = splits[1] if len(splits) > 1 else None + playlist = next( + ( + p + for p in media_library.get_playlists() + if (item_id == p.item_id or title == p.title) + ), + None, + ) + return playlist + if not item_id.startswith("A:ALBUM") and search_type == SONOS_ALBUM: item_id = "A:ALBUMARTIST/" + "/".join(item_id.split("/")[2:]) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 12e8b44652a..581bdaad37d 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -626,13 +626,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): soco.play_uri(media_id, force_radio=is_radio) elif media_type == MediaType.PLAYLIST: if media_id.startswith("S:"): - item = media_browser.get_media(self.media.library, media_id, media_type) - soco.play_uri(item.get_uri()) - return - try: + playlist = media_browser.get_media( + self.media.library, media_id, media_type + ) + else: playlists = soco.get_sonos_playlists(complete_result=True) - playlist = next(p for p in playlists if p.title == media_id) - except StopIteration: + playlist = next((p for p in playlists if p.title == media_id), None) + if not playlist: _LOGGER.error('Could not find a Sonos playlist named "%s"', media_id) else: soco.clear_queue() diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index d89a1076db3..c181520b85d 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -1,5 +1,13 @@ """Tests for the Sonos Media Player platform.""" +import logging + +import pytest + +from homeassistant.components.media_player import ( + DOMAIN as MP_DOMAIN, + SERVICE_PLAY_MEDIA, +) from homeassistant.const import STATE_IDLE from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import ( @@ -8,6 +16,8 @@ from homeassistant.helpers.device_registry import ( DeviceRegistry, ) +from .conftest import SoCoMockFactory + async def test_device_registry( hass: HomeAssistant, device_registry: DeviceRegistry, async_autosetup_sonos, soco @@ -53,3 +63,110 @@ async def test_entity_basic( assert attributes["friendly_name"] == "Zone A" assert attributes["is_volume_muted"] is False assert attributes["volume_level"] == 0.19 + + +class _MockMusicServiceItem: + """Mocks a Soco MusicServiceItem.""" + + def __init__( + self, + title: str, + item_id: str, + parent_id: str, + item_class: str, + ) -> None: + """Initialize the mock item.""" + self.title = title + self.item_id = item_id + self.item_class = item_class + self.parent_id = parent_id + + def get_uri(self) -> str: + """Return URI.""" + return self.item_id.replace("S://", "x-file-cifs://") + + +_mock_playlists = [ + _MockMusicServiceItem( + "playlist1", + "S://192.168.1.68/music/iTunes/iTunes%20Music%20Library.xml#GUID_1", + "A:PLAYLISTS", + "object.container.playlistContainer", + ), + _MockMusicServiceItem( + "playlist2", + "S://192.168.1.68/music/iTunes/iTunes%20Music%20Library.xml#GUID_2", + "A:PLAYLISTS", + "object.container.playlistContainer", + ), +] + + +@pytest.mark.parametrize( + ("media_content_id", "expected_item_id"), + [ + ( + _mock_playlists[0].item_id, + _mock_playlists[0].item_id, + ), + ( + f"S:{_mock_playlists[1].title}", + _mock_playlists[1].item_id, + ), + ], +) +async def test_play_media_music_library_playlist( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + discover, + media_content_id, + expected_item_id, +) -> None: + """Test that playlists can be found by id or title.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + soco_mock.music_library.get_playlists.return_value = _mock_playlists + + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + "entity_id": "media_player.zone_a", + "media_content_type": "playlist", + "media_content_id": media_content_id, + }, + blocking=True, + ) + + assert soco_mock.clear_queue.call_count == 1 + assert soco_mock.add_to_queue.call_count == 1 + assert soco_mock.add_to_queue.call_args_list[0].args[0].item_id == expected_item_id + assert soco_mock.play_from_queue.call_count == 1 + + +async def test_play_media_music_library_playlist_dne( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test error handling when attempting to play a non-existent playlist .""" + media_content_id = "S:nonexistent" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + soco_mock.music_library.get_playlists.return_value = _mock_playlists + + with caplog.at_level(logging.ERROR): + caplog.clear() + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + "entity_id": "media_player.zone_a", + "media_content_type": "playlist", + "media_content_id": media_content_id, + }, + blocking=True, + ) + assert soco_mock.play_uri.call_count == 0 + assert media_content_id in caplog.text + assert "playlist" in caplog.text From fa2f49693c206e2cec938bfb8763cfda21ddef01 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 2 Apr 2024 18:33:12 +0200 Subject: [PATCH 1671/1691] Bump aiounifi to v74 (#114649) --- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 63f9f67605e..05dc2189908 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["aiounifi"], "quality_scale": "platinum", - "requirements": ["aiounifi==73"], + "requirements": ["aiounifi==74"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 5bb02a079fd..4bc0c15d06d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -392,7 +392,7 @@ aiotankerkoenig==0.4.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==73 +aiounifi==74 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e25784596a3..0c363f73167 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -365,7 +365,7 @@ aiotankerkoenig==0.4.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==73 +aiounifi==74 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From 4e0290ce0e91286c54769b02eada309ce30017dd Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 2 Apr 2024 18:28:52 +0200 Subject: [PATCH 1672/1691] Add missing state to the Tractive tracker state sensor (#114654) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/tractive/sensor.py | 1 + homeassistant/components/tractive/strings.json | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/tractive/sensor.py b/homeassistant/components/tractive/sensor.py index 5e2f3288f57..1edee71467b 100644 --- a/homeassistant/components/tractive/sensor.py +++ b/homeassistant/components/tractive/sensor.py @@ -107,6 +107,7 @@ SENSOR_TYPES: tuple[TractiveSensorEntityDescription, ...] = ( entity_category=EntityCategory.DIAGNOSTIC, device_class=SensorDeviceClass.ENUM, options=[ + "inaccurate_position", "not_reporting", "operational", "system_shutdown_user", diff --git a/homeassistant/components/tractive/strings.json b/homeassistant/components/tractive/strings.json index 82b7ecc295c..0690328c99c 100644 --- a/homeassistant/components/tractive/strings.json +++ b/homeassistant/components/tractive/strings.json @@ -70,6 +70,7 @@ "tracker_state": { "name": "Tracker state", "state": { + "inaccurate_position": "Inaccurate position", "not_reporting": "Not reporting", "operational": "Operational", "system_shutdown_user": "System shutdown user", From 02dee343380fe2b78fd10971e3eb28bfd62c621c Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 2 Apr 2024 20:58:18 +0300 Subject: [PATCH 1673/1691] Bump holidays to 0.46 (#114657) --- homeassistant/components/holiday/manifest.json | 2 +- homeassistant/components/workday/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/holiday/manifest.json b/homeassistant/components/holiday/manifest.json index f1bc60dece4..5a1edcd3c3f 100644 --- a/homeassistant/components/holiday/manifest.json +++ b/homeassistant/components/holiday/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/holiday", "iot_class": "local_polling", - "requirements": ["holidays==0.45", "babel==2.13.1"] + "requirements": ["holidays==0.46", "babel==2.13.1"] } diff --git a/homeassistant/components/workday/manifest.json b/homeassistant/components/workday/manifest.json index 6b17a980870..314f4c6bcf4 100644 --- a/homeassistant/components/workday/manifest.json +++ b/homeassistant/components/workday/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_polling", "loggers": ["holidays"], "quality_scale": "internal", - "requirements": ["holidays==0.45"] + "requirements": ["holidays==0.46"] } diff --git a/requirements_all.txt b/requirements_all.txt index 4bc0c15d06d..6776b8daa0b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1074,7 +1074,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.45 +holidays==0.46 # homeassistant.components.frontend home-assistant-frontend==20240402.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0c363f73167..5140cbaa5e4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -873,7 +873,7 @@ hole==0.8.0 # homeassistant.components.holiday # homeassistant.components.workday -holidays==0.45 +holidays==0.46 # homeassistant.components.frontend home-assistant-frontend==20240402.1 From 639c4a843bd3b4926c05d83e9ebc22b330ad59c6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Apr 2024 11:22:40 -1000 Subject: [PATCH 1674/1691] Avoid trying to load platform that are known to not exist in async_prepare_setup_platform (#114659) --- homeassistant/setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 178ee6425e3..2e64fefee77 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -504,6 +504,12 @@ async def async_prepare_setup_platform( log_error(f"Unable to import the component ({exc}).") return None + if not integration.platforms_exists((domain,)): + log_error( + f"Platform not found (No module named '{integration.pkg_path}.{domain}')" + ) + return None + try: platform = await integration.async_get_platform(domain) except ImportError as exc: From f676448f27141ba21af81e05b747ef94b19e013c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Apr 2024 00:12:31 +0200 Subject: [PATCH 1675/1691] Update frontend to 20240402.2 (#114683) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 2010a9985b3..3ac7efa9fab 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240402.1"] + "requirements": ["home-assistant-frontend==20240402.2"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cf0aa0848af..80cea24b817 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240402.1 +home-assistant-frontend==20240402.2 home-assistant-intents==2024.3.29 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 6776b8daa0b..2be277f422a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.46 # homeassistant.components.frontend -home-assistant-frontend==20240402.1 +home-assistant-frontend==20240402.2 # homeassistant.components.conversation home-assistant-intents==2024.3.29 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5140cbaa5e4..b742a3fa589 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.46 # homeassistant.components.frontend -home-assistant-frontend==20240402.1 +home-assistant-frontend==20240402.2 # homeassistant.components.conversation home-assistant-intents==2024.3.29 From 8bdb27c88b7e1a837000116e5cb9231ef8f152a2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Apr 2024 00:14:07 +0200 Subject: [PATCH 1676/1691] Bump version to 2024.4.0b7 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index fd3b1257df2..7dc1177c62f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b6" +PATCH_VERSION: Final = "0b7" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 73d03f0f92c..7bf5f806dd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b6" +version = "2024.4.0b7" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 79fa7caa41c54162c431ed6ae42f54df5e328b32 Mon Sep 17 00:00:00 2001 From: Lenn <78048721+LennP@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:11:46 +0200 Subject: [PATCH 1677/1691] Rename Motionblinds BLE integration to Motionblinds Bluetooth (#114584) --- homeassistant/components/motionblinds_ble/__init__.py | 10 +++++----- homeassistant/components/motionblinds_ble/button.py | 2 +- .../components/motionblinds_ble/config_flow.py | 4 ++-- homeassistant/components/motionblinds_ble/const.py | 2 +- homeassistant/components/motionblinds_ble/cover.py | 2 +- homeassistant/components/motionblinds_ble/entity.py | 4 ++-- .../components/motionblinds_ble/manifest.json | 2 +- homeassistant/components/motionblinds_ble/select.py | 2 +- homeassistant/generated/integrations.json | 2 +- tests/components/motionblinds_ble/__init__.py | 2 +- tests/components/motionblinds_ble/conftest.py | 2 +- tests/components/motionblinds_ble/test_config_flow.py | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/motionblinds_ble/__init__.py b/homeassistant/components/motionblinds_ble/__init__.py index f70625cd36d..3c6df12e878 100644 --- a/homeassistant/components/motionblinds_ble/__init__.py +++ b/homeassistant/components/motionblinds_ble/__init__.py @@ -1,4 +1,4 @@ -"""Motionblinds BLE integration.""" +"""Motionblinds Bluetooth integration.""" from __future__ import annotations @@ -34,9 +34,9 @@ CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up Motionblinds BLE integration.""" + """Set up Motionblinds Bluetooth integration.""" - _LOGGER.debug("Setting up Motionblinds BLE integration") + _LOGGER.debug("Setting up Motionblinds Bluetooth integration") # The correct time is needed for encryption _LOGGER.debug("Setting timezone for encryption: %s", hass.config.time_zone) @@ -46,7 +46,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Motionblinds BLE device from a config entry.""" + """Set up Motionblinds Bluetooth device from a config entry.""" _LOGGER.debug("(%s) Setting up device", entry.data[CONF_MAC_CODE]) @@ -94,7 +94,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Unload Motionblinds BLE device from a config entry.""" + """Unload Motionblinds Bluetooth device from a config entry.""" if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/motionblinds_ble/button.py b/homeassistant/components/motionblinds_ble/button.py index d3bd22e9276..a099276cd85 100644 --- a/homeassistant/components/motionblinds_ble/button.py +++ b/homeassistant/components/motionblinds_ble/button.py @@ -1,4 +1,4 @@ -"""Button entities for the Motionblinds BLE integration.""" +"""Button entities for the Motionblinds Bluetooth integration.""" from __future__ import annotations diff --git a/homeassistant/components/motionblinds_ble/config_flow.py b/homeassistant/components/motionblinds_ble/config_flow.py index 0282c4d5584..23302ae9624 100644 --- a/homeassistant/components/motionblinds_ble/config_flow.py +++ b/homeassistant/components/motionblinds_ble/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow for Motionblinds BLE integration.""" +"""Config flow for Motionblinds Bluetooth integration.""" from __future__ import annotations @@ -38,7 +38,7 @@ CONFIG_SCHEMA = vol.Schema({vol.Required(CONF_MAC_CODE): str}) class FlowHandler(ConfigFlow, domain=DOMAIN): - """Handle a config flow for Motionblinds BLE.""" + """Handle a config flow for Motionblinds Bluetooth.""" def __init__(self) -> None: """Initialize a ConfigFlow.""" diff --git a/homeassistant/components/motionblinds_ble/const.py b/homeassistant/components/motionblinds_ble/const.py index d2eb5821b9f..bd88927559e 100644 --- a/homeassistant/components/motionblinds_ble/const.py +++ b/homeassistant/components/motionblinds_ble/const.py @@ -1,4 +1,4 @@ -"""Constants for the Motionblinds BLE integration.""" +"""Constants for the Motionblinds Bluetooth integration.""" ATTR_CONNECT = "connect" ATTR_DISCONNECT = "disconnect" diff --git a/homeassistant/components/motionblinds_ble/cover.py b/homeassistant/components/motionblinds_ble/cover.py index c4f14dc5605..afeeb5b0d70 100644 --- a/homeassistant/components/motionblinds_ble/cover.py +++ b/homeassistant/components/motionblinds_ble/cover.py @@ -1,4 +1,4 @@ -"""Cover entities for the Motionblinds BLE integration.""" +"""Cover entities for the Motionblinds Bluetooth integration.""" from __future__ import annotations diff --git a/homeassistant/components/motionblinds_ble/entity.py b/homeassistant/components/motionblinds_ble/entity.py index 5c2b3ae9afb..0b8171e7acd 100644 --- a/homeassistant/components/motionblinds_ble/entity.py +++ b/homeassistant/components/motionblinds_ble/entity.py @@ -1,4 +1,4 @@ -"""Base entities for the Motionblinds BLE integration.""" +"""Base entities for the Motionblinds Bluetooth integration.""" import logging @@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__) class MotionblindsBLEEntity(Entity): - """Base class for Motionblinds BLE entities.""" + """Base class for Motionblinds Bluetooth entities.""" _attr_has_entity_name = True _attr_should_poll = False diff --git a/homeassistant/components/motionblinds_ble/manifest.json b/homeassistant/components/motionblinds_ble/manifest.json index 2a24dd67483..aa727be13f8 100644 --- a/homeassistant/components/motionblinds_ble/manifest.json +++ b/homeassistant/components/motionblinds_ble/manifest.json @@ -1,6 +1,6 @@ { "domain": "motionblinds_ble", - "name": "Motionblinds BLE", + "name": "Motionblinds Bluetooth", "bluetooth": [ { "local_name": "MOTION_*", diff --git a/homeassistant/components/motionblinds_ble/select.py b/homeassistant/components/motionblinds_ble/select.py index 2ba2b8df2d4..c297c887910 100644 --- a/homeassistant/components/motionblinds_ble/select.py +++ b/homeassistant/components/motionblinds_ble/select.py @@ -1,4 +1,4 @@ -"""Select entities for the Motionblinds BLE integration.""" +"""Select entities for the Motionblinds Bluetooth integration.""" from __future__ import annotations diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 53b885ea853..b8abac5145b 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3741,7 +3741,7 @@ "integration_type": "device", "config_flow": true, "iot_class": "assumed_state", - "name": "Motionblinds BLE" + "name": "Motionblinds Bluetooth" } } }, diff --git a/tests/components/motionblinds_ble/__init__.py b/tests/components/motionblinds_ble/__init__.py index 302c3266ea1..c2385555dbf 100644 --- a/tests/components/motionblinds_ble/__init__.py +++ b/tests/components/motionblinds_ble/__init__.py @@ -1 +1 @@ -"""Tests for the Motionblinds BLE integration.""" +"""Tests for the Motionblinds Bluetooth integration.""" diff --git a/tests/components/motionblinds_ble/conftest.py b/tests/components/motionblinds_ble/conftest.py index 8cd1adb1c0e..ae487957302 100644 --- a/tests/components/motionblinds_ble/conftest.py +++ b/tests/components/motionblinds_ble/conftest.py @@ -1,4 +1,4 @@ -"""Setup the MotionBlinds BLE tests.""" +"""Setup the Motionblinds Bluetooth tests.""" from unittest.mock import AsyncMock, Mock, patch diff --git a/tests/components/motionblinds_ble/test_config_flow.py b/tests/components/motionblinds_ble/test_config_flow.py index 9451e04830a..f540fdf421c 100644 --- a/tests/components/motionblinds_ble/test_config_flow.py +++ b/tests/components/motionblinds_ble/test_config_flow.py @@ -1,4 +1,4 @@ -"""Test the MotionBlinds BLE config flow.""" +"""Test the Motionblinds Bluetooth config flow.""" from unittest.mock import patch From 43562289e401af6881539a2ae646be4afd3d6f63 Mon Sep 17 00:00:00 2001 From: Jonas Fors Lellky Date: Wed, 3 Apr 2024 09:23:06 +0200 Subject: [PATCH 1678/1691] Bump flexit_bacnet to 2.2.1 (#114641) --- homeassistant/components/flexit_bacnet/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flexit_bacnet/manifest.json b/homeassistant/components/flexit_bacnet/manifest.json index d230e4ebb7a..40390162ce6 100644 --- a/homeassistant/components/flexit_bacnet/manifest.json +++ b/homeassistant/components/flexit_bacnet/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/flexit_bacnet", "integration_type": "device", "iot_class": "local_polling", - "requirements": ["flexit_bacnet==2.1.0"] + "requirements": ["flexit_bacnet==2.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2be277f422a..792e8b53e96 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -867,7 +867,7 @@ fixerio==1.0.0a0 fjaraskupan==2.3.0 # homeassistant.components.flexit_bacnet -flexit_bacnet==2.1.0 +flexit_bacnet==2.2.1 # homeassistant.components.flipr flipr-api==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b742a3fa589..c771a0e4b54 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -705,7 +705,7 @@ fivem-api==0.1.2 fjaraskupan==2.3.0 # homeassistant.components.flexit_bacnet -flexit_bacnet==2.1.0 +flexit_bacnet==2.2.1 # homeassistant.components.flipr flipr-api==1.5.1 From 7cb603a2268ba66990f91f2cc7f07bed5db4a183 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Wed, 3 Apr 2024 03:12:00 -0400 Subject: [PATCH 1679/1691] Import zha quirks in the executor (#114685) --- homeassistant/components/zha/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index ef603a4ea71..de761138ce1 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -124,8 +124,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b zha_data = get_zha_data(hass) if zha_data.yaml_config.get(CONF_ENABLE_QUIRKS, True): - setup_quirks( - custom_quirks_path=zha_data.yaml_config.get(CONF_CUSTOM_QUIRKS_PATH) + await hass.async_add_import_executor_job( + setup_quirks, zha_data.yaml_config.get(CONF_CUSTOM_QUIRKS_PATH) ) # Load and cache device trigger information early From 7a2f6ce4305f0b26db8619143ec24002de5653de Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 3 Apr 2024 09:56:19 +0200 Subject: [PATCH 1680/1691] Fix Downloader config flow (#114718) --- homeassistant/components/downloader/config_flow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/downloader/config_flow.py b/homeassistant/components/downloader/config_flow.py index 69393c04985..635c241edc4 100644 --- a/homeassistant/components/downloader/config_flow.py +++ b/homeassistant/components/downloader/config_flow.py @@ -55,8 +55,9 @@ class DownloaderConfigFlow(ConfigFlow, domain=DOMAIN): async def _validate_input(self, user_input: dict[str, Any]) -> None: """Validate the user input if the directory exists.""" - if not os.path.isabs(user_input[CONF_DOWNLOAD_DIR]): - download_path = self.hass.config.path(user_input[CONF_DOWNLOAD_DIR]) + download_path = user_input[CONF_DOWNLOAD_DIR] + if not os.path.isabs(download_path): + download_path = self.hass.config.path(download_path) if not os.path.isdir(download_path): _LOGGER.error( From 35ff633d999096ffbbbdf7e4012acc8aac730f0f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 3 Apr 2024 13:50:34 +0200 Subject: [PATCH 1681/1691] Avoid blocking IO in downloader config flow (#114741) --- homeassistant/components/downloader/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/downloader/config_flow.py b/homeassistant/components/downloader/config_flow.py index 635c241edc4..15af8b56163 100644 --- a/homeassistant/components/downloader/config_flow.py +++ b/homeassistant/components/downloader/config_flow.py @@ -59,7 +59,7 @@ class DownloaderConfigFlow(ConfigFlow, domain=DOMAIN): if not os.path.isabs(download_path): download_path = self.hass.config.path(download_path) - if not os.path.isdir(download_path): + if not await self.hass.async_add_executor_job(os.path.isdir, download_path): _LOGGER.error( "Download path %s does not exist. File Downloader not active", download_path, From 0ca3700c16a58502d8a68be4a61d21df3598a2e2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Apr 2024 15:19:49 +0200 Subject: [PATCH 1682/1691] Update frontend to 20240403.0 (#114747) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 3ac7efa9fab..e2826fdb185 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240402.2"] + "requirements": ["home-assistant-frontend==20240403.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 80cea24b817..07b3aa76ebd 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240402.2 +home-assistant-frontend==20240403.0 home-assistant-intents==2024.3.29 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 792e8b53e96..143e9e1fa53 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.46 # homeassistant.components.frontend -home-assistant-frontend==20240402.2 +home-assistant-frontend==20240403.0 # homeassistant.components.conversation home-assistant-intents==2024.3.29 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c771a0e4b54..2ea289f7bd3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.46 # homeassistant.components.frontend -home-assistant-frontend==20240402.2 +home-assistant-frontend==20240403.0 # homeassistant.components.conversation home-assistant-intents==2024.3.29 From 0aa134459be89790358fc2bd73f00863495d88ab Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Apr 2024 15:35:53 +0200 Subject: [PATCH 1683/1691] Bump version to 2024.4.0b8 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 7dc1177c62f..95fab13bb5e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b7" +PATCH_VERSION: Final = "0b8" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 7bf5f806dd2..031afa09704 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b7" +version = "2024.4.0b8" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From b2df1b1c03069b6f20a026aae31acf40d737679c Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 3 Apr 2024 16:33:58 +0200 Subject: [PATCH 1684/1691] Allow passing area/device/entity IDs to floor_id and floor_name (#114748) --- homeassistant/helpers/template.py | 16 ++++ tests/helpers/test_template.py | 125 +++++++++++++++++++++++------- 2 files changed, 115 insertions(+), 26 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index a48f0133e84..5f692e0de89 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1408,6 +1408,12 @@ def floor_id(hass: HomeAssistant, lookup_value: Any) -> str | None: floor_registry = fr.async_get(hass) if floor := floor_registry.async_get_floor_by_name(str(lookup_value)): return floor.floor_id + + if aid := area_id(hass, lookup_value): + area_reg = area_registry.async_get(hass) + if area := area_reg.async_get_area(aid): + return area.floor_id + return None @@ -1416,6 +1422,16 @@ def floor_name(hass: HomeAssistant, lookup_value: str) -> str | None: floor_registry = fr.async_get(hass) if floor := floor_registry.async_get_floor(lookup_value): return floor.name + + if aid := area_id(hass, lookup_value): + area_reg = area_registry.async_get(hass) + if ( + (area := area_reg.async_get_area(aid)) + and area.floor_id + and (floor := floor_registry.async_get_floor(area.floor_id)) + ): + return floor.name + return None diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 6f455c3dda4..54fdf0368eb 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -5198,17 +5198,23 @@ async def test_floors( async def test_floor_id( hass: HomeAssistant, floor_registry: fr.FloorRegistry, + area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, ) -> None: """Test floor_id function.""" - # Test non existing floor name - info = render_to_info(hass, "{{ floor_id('Third floor') }}") - assert_result_info(info, None) - assert info.rate_limit is None + def test(value: str, expected: str | None) -> None: + info = render_to_info(hass, f"{{{{ floor_id('{value}') }}}}") + assert_result_info(info, expected) + assert info.rate_limit is None - info = render_to_info(hass, "{{ 'Third floor' | floor_id }}") - assert_result_info(info, None) - assert info.rate_limit is None + info = render_to_info(hass, f"{{{{ '{value}' | floor_id }}}}") + assert_result_info(info, expected) + assert info.rate_limit is None + + # Test non existing floor name + test("Third floor", None) # Test wrong value type info = render_to_info(hass, "{{ floor_id(42) }}") @@ -5221,28 +5227,65 @@ async def test_floor_id( # Test with an actual floor floor = floor_registry.async_create("First floor") - info = render_to_info(hass, "{{ floor_id('First floor') }}") - assert_result_info(info, floor.floor_id) - assert info.rate_limit is None + test("First floor", floor.floor_id) - info = render_to_info(hass, "{{ 'First floor' | floor_id }}") - assert_result_info(info, floor.floor_id) - assert info.rate_limit is None + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + area_entry_hex = area_registry.async_get_or_create("123abc") + + # Create area, device, entity and assign area to device and entity + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_entry = entity_registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + device_id=device_entry.id, + ) + device_entry = device_registry.async_update_device( + device_entry.id, area_id=area_entry_hex.id + ) + entity_entry = entity_registry.async_update_entity( + entity_entry.entity_id, area_id=area_entry_hex.id + ) + + test(area_entry_hex.id, None) + test(device_entry.id, None) + test(entity_entry.entity_id, None) + + # Add floor to area + area_entry_hex = area_registry.async_update( + area_entry_hex.id, floor_id=floor.floor_id + ) + + test(area_entry_hex.id, floor.floor_id) + test(device_entry.id, floor.floor_id) + test(entity_entry.entity_id, floor.floor_id) async def test_floor_name( hass: HomeAssistant, floor_registry: fr.FloorRegistry, + area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, ) -> None: """Test floor_name function.""" - # Test non existing floor ID - info = render_to_info(hass, "{{ floor_name('third_floor') }}") - assert_result_info(info, None) - assert info.rate_limit is None - info = render_to_info(hass, "{{ 'third_floor' | floor_name }}") - assert_result_info(info, None) - assert info.rate_limit is None + def test(value: str, expected: str | None) -> None: + info = render_to_info(hass, f"{{{{ floor_name('{value}') }}}}") + assert_result_info(info, expected) + assert info.rate_limit is None + + info = render_to_info(hass, f"{{{{ '{value}' | floor_name }}}}") + assert_result_info(info, expected) + assert info.rate_limit is None + + # Test non existing floor name + test("Third floor", None) # Test wrong value type info = render_to_info(hass, "{{ floor_name(42) }}") @@ -5255,13 +5298,43 @@ async def test_floor_name( # Test existing floor ID floor = floor_registry.async_create("First floor") - info = render_to_info(hass, f"{{{{ floor_name('{floor.floor_id}') }}}}") - assert_result_info(info, floor.name) - assert info.rate_limit is None + test(floor.floor_id, floor.name) - info = render_to_info(hass, f"{{{{ '{floor.floor_id}' | floor_name }}}}") - assert_result_info(info, floor.name) - assert info.rate_limit is None + config_entry = MockConfigEntry(domain="light") + config_entry.add_to_hass(hass) + area_entry_hex = area_registry.async_get_or_create("123abc") + + # Create area, device, entity and assign area to device and entity + device_entry = device_registry.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + entity_entry = entity_registry.async_get_or_create( + "light", + "hue", + "5678", + config_entry=config_entry, + device_id=device_entry.id, + ) + device_entry = device_registry.async_update_device( + device_entry.id, area_id=area_entry_hex.id + ) + entity_entry = entity_registry.async_update_entity( + entity_entry.entity_id, area_id=area_entry_hex.id + ) + + test(area_entry_hex.id, None) + test(device_entry.id, None) + test(entity_entry.entity_id, None) + + # Add floor to area + area_entry_hex = area_registry.async_update( + area_entry_hex.id, floor_id=floor.floor_id + ) + + test(area_entry_hex.id, floor.name) + test(device_entry.id, floor.name) + test(entity_entry.entity_id, floor.name) async def test_floor_areas( From 4302c5c273e56ee12487c49f1bccfee48a5733d6 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 3 Apr 2024 10:27:26 -0500 Subject: [PATCH 1685/1691] Bump intents (#114755) --- homeassistant/components/conversation/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index 7f463483bf9..612e9b25c06 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -7,5 +7,5 @@ "integration_type": "system", "iot_class": "local_push", "quality_scale": "internal", - "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.3.29"] + "requirements": ["hassil==1.6.1", "home-assistant-intents==2024.4.3"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 07b3aa76ebd..4f5bc0f5a63 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -31,7 +31,7 @@ hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 home-assistant-frontend==20240403.0 -home-assistant-intents==2024.3.29 +home-assistant-intents==2024.4.3 httpx==0.27.0 ifaddr==0.2.0 Jinja2==3.1.3 diff --git a/requirements_all.txt b/requirements_all.txt index 143e9e1fa53..7980e71d5d2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1080,7 +1080,7 @@ holidays==0.46 home-assistant-frontend==20240403.0 # homeassistant.components.conversation -home-assistant-intents==2024.3.29 +home-assistant-intents==2024.4.3 # homeassistant.components.home_connect homeconnect==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2ea289f7bd3..2097ed21f55 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -879,7 +879,7 @@ holidays==0.46 home-assistant-frontend==20240403.0 # homeassistant.components.conversation -home-assistant-intents==2024.3.29 +home-assistant-intents==2024.4.3 # homeassistant.components.home_connect homeconnect==0.7.2 From 33f07ce035f01484857181086a7dd39d952966e1 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Apr 2024 17:32:26 +0200 Subject: [PATCH 1686/1691] Update frontend to 20240403.1 (#114756) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index e2826fdb185..1890572bf5a 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240403.0"] + "requirements": ["home-assistant-frontend==20240403.1"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 4f5bc0f5a63..6bb6bd4d2d3 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -30,7 +30,7 @@ habluetooth==2.4.2 hass-nabucasa==0.79.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240403.0 +home-assistant-frontend==20240403.1 home-assistant-intents==2024.4.3 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 7980e71d5d2..76dc587d6b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1077,7 +1077,7 @@ hole==0.8.0 holidays==0.46 # homeassistant.components.frontend -home-assistant-frontend==20240403.0 +home-assistant-frontend==20240403.1 # homeassistant.components.conversation home-assistant-intents==2024.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2097ed21f55..6f329b782aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -876,7 +876,7 @@ hole==0.8.0 holidays==0.46 # homeassistant.components.frontend -home-assistant-frontend==20240403.0 +home-assistant-frontend==20240403.1 # homeassistant.components.conversation home-assistant-intents==2024.4.3 From 6a7fad0228a94191ad46fad807dfde330fe4ee1c Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:53:44 +0200 Subject: [PATCH 1687/1691] Fix Synology DSM setup in case no Surveillance Station permission (#114757) --- homeassistant/components/synology_dsm/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index a0c3a10774f..ec93c92a698 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -105,6 +105,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if ( SynoSurveillanceStation.INFO_API_KEY in available_apis and SynoSurveillanceStation.HOME_MODE_API_KEY in available_apis + and api.surveillance_station is not None ): coordinator_switches = SynologyDSMSwitchUpdateCoordinator(hass, entry, api) await coordinator_switches.async_config_entry_first_refresh() From 3845523a27d245ee686aeeaec5c427ba2086bfdf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Apr 2024 17:55:24 +0200 Subject: [PATCH 1688/1691] Bump version to 2024.4.0b9 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 95fab13bb5e..514124e8b2e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b8" +PATCH_VERSION: Final = "0b9" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 031afa09704..ff96331c1e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b8" +version = "2024.4.0b9" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From aa33da546df167da6157442f73c934fa5f409242 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Apr 2024 19:09:39 +0200 Subject: [PATCH 1689/1691] Bump version to 2024.4.0 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 514124e8b2e..6e08c49f970 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -18,7 +18,7 @@ from .util.signal_type import SignalType APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 4 -PATCH_VERSION: Final = "0b9" +PATCH_VERSION: Final = "0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index ff96331c1e1..e0f07fac6b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.4.0b9" +version = "2024.4.0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 9ba4d26abd7d082ddb55af4a2ac456307ddaf09e Mon Sep 17 00:00:00 2001 From: IngoK1 <45150614+IngoK1@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:07:02 +0200 Subject: [PATCH 1690/1691] Fix for Sonos URL encoding problem #102557 (#109518) * Fix for URL encoding problem #102557 Fixes the problem "Cannot play media with spaces in folder names to Sonos #102557" removing the encoding of the strings in the music library. * Fix type casting problem * Update media_browser.py to fix pr check findings Added required casting for all unquote statements to avoid further casting findings in the pr checks * Update media_browser.py Checked on linting, lets give it another try * Update media_browser.py Updated ruff run * Update media_browser.py - added version run through ruff * Update media_browser.py - ruff changes * Apply ruff formatting * Update homeassistant/components/sonos/media_browser.py Co-authored-by: jjlawren * Update homeassistant/components/sonos/media_browser.py Co-authored-by: jjlawren * Update homeassistant/components/sonos/media_browser.py Co-authored-by: jjlawren * Update homeassistant/components/sonos/media_browser.py Co-authored-by: jjlawren --------- Co-authored-by: computeq-admin <51021172+computeq-admin@users.noreply.github.com> Co-authored-by: Jason Lawrence --- homeassistant/components/sonos/media_browser.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index 87ee3ed3b4d..6e6f388ed50 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -7,6 +7,7 @@ from contextlib import suppress from functools import partial import logging from typing import cast +import urllib.parse from soco.data_structures import DidlObject from soco.ms_data_structures import MusicServiceItem @@ -60,12 +61,14 @@ def get_thumbnail_url_full( media_content_id, media_content_type, ) - return getattr(item, "album_art_uri", None) + return urllib.parse.unquote(getattr(item, "album_art_uri", "")) - return get_browse_image_url( - media_content_type, - media_content_id, - media_image_id, + return urllib.parse.unquote( + get_browse_image_url( + media_content_type, + media_content_id, + media_image_id, + ) ) @@ -166,6 +169,7 @@ def build_item_response( payload["idstring"] = "A:ALBUMARTIST/" + "/".join( payload["idstring"].split("/")[2:] ) + payload["idstring"] = urllib.parse.unquote(payload["idstring"]) try: search_type = MEDIA_TYPES_TO_SONOS[payload["search_type"]] @@ -510,7 +514,7 @@ def get_media( if not item_id.startswith("A:ALBUM") and search_type == SONOS_ALBUM: item_id = "A:ALBUMARTIST/" + "/".join(item_id.split("/")[2:]) - search_term = item_id.split("/")[-1] + search_term = urllib.parse.unquote(item_id.split("/")[-1]) matches = media_library.get_music_library_information( search_type, search_term=search_term, full_album_art_uri=True ) From 590546a9a56eb817739c789ed19c3ceec6ca3653 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 28 Mar 2024 12:07:55 +0100 Subject: [PATCH 1691/1691] Use `setup_test_component_platform` helper for sensor entity component tests instead of `hass.components` (#114316) * Use `setup_test_component_platform` helper for sensor entity component tests instead of `hass.components` * Missing file * Fix import * Remove invalid device class --- tests/common.py | 14 +- tests/components/conftest.py | 9 + tests/components/mqtt/test_init.py | 14 +- .../sensor.py => components/sensor/common.py} | 49 +-- .../sensor/test_device_condition.py | 32 +- .../components/sensor/test_device_trigger.py | 32 +- tests/components/sensor/test_init.py | 360 +++++++----------- tests/components/sensor/test_recorder.py | 15 +- 8 files changed, 219 insertions(+), 306 deletions(-) rename tests/{testing_config/custom_components/test/sensor.py => components/sensor/common.py} (84%) diff --git a/tests/common.py b/tests/common.py index a7d4cf6b83a..0ac0ee4556b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1461,7 +1461,10 @@ def mock_integration( def mock_platform( - hass: HomeAssistant, platform_path: str, module: Mock | MockPlatform | None = None + hass: HomeAssistant, + platform_path: str, + module: Mock | MockPlatform | None = None, + built_in=True, ) -> None: """Mock a platform. @@ -1472,7 +1475,7 @@ def mock_platform( module_cache = hass.data[loader.DATA_COMPONENTS] if domain not in integration_cache: - mock_integration(hass, MockModule(domain)) + mock_integration(hass, MockModule(domain), built_in=built_in) integration_cache[domain]._top_level_files.add(f"{platform_name}.py") _LOGGER.info("Adding mock integration platform: %s", platform_path) @@ -1665,6 +1668,7 @@ def setup_test_component_platform( domain: str, entities: Sequence[Entity], from_config_entry: bool = False, + built_in: bool = True, ) -> MockPlatform: """Mock a test component platform for tests.""" @@ -1695,9 +1699,5 @@ def setup_test_component_platform( platform.async_setup_entry = _async_setup_entry platform.async_setup_platform = None - mock_platform( - hass, - f"test.{domain}", - platform, - ) + mock_platform(hass, f"test.{domain}", platform, built_in=built_in) return platform diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 4669e17c8e7..d84fb3600ab 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -10,6 +10,7 @@ from homeassistant.const import STATE_OFF, STATE_ON if TYPE_CHECKING: from tests.components.light.common import MockLight + from tests.components.sensor.common import MockSensor @pytest.fixture(scope="session", autouse=True) @@ -118,3 +119,11 @@ def mock_light_entities() -> list["MockLight"]: MockLight("Ceiling", STATE_OFF), MockLight(None, STATE_OFF), ] + + +@pytest.fixture +def mock_sensor_entities() -> dict[str, "MockSensor"]: + """Return mocked sensor entities.""" + from tests.components.sensor.common import get_mock_sensor_entities + + return get_mock_sensor_entities() diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 3459e6fc058..a9f2ba4354b 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -23,6 +23,7 @@ from homeassistant.components.mqtt.models import ( MqttValueTemplateException, ReceiveMessage, ) +from homeassistant.components.sensor import SensorDeviceClass from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState from homeassistant.const import ( ATTR_ASSUMED_STATE, @@ -52,10 +53,9 @@ from tests.common import ( async_fire_mqtt_message, async_fire_time_changed, mock_restore_cache, + setup_test_component_platform, ) -from tests.testing_config.custom_components.test.sensor import ( # type: ignore[attr-defined] - DEVICE_CLASSES, -) +from tests.components.sensor.common import MockSensor from tests.typing import ( MqttMockHAClient, MqttMockHAClientGenerator, @@ -3142,12 +3142,12 @@ async def test_debug_info_non_mqtt( device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, mqtt_mock_entry: MqttMockHAClientGenerator, + mock_sensor_entities: dict[str, MockSensor], ) -> None: """Test we get empty debug_info for a device with non MQTT entities.""" await mqtt_mock_entry() domain = "sensor" - platform = getattr(hass.components, f"test.{domain}") - platform.init() + setup_test_component_platform(hass, domain, mock_sensor_entities) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -3155,11 +3155,11 @@ async def test_debug_info_non_mqtt( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) - for device_class in DEVICE_CLASSES: + for device_class in SensorDeviceClass: entity_registry.async_get_or_create( domain, "test", - platform.ENTITIES[device_class].unique_id, + mock_sensor_entities[device_class].unique_id, device_id=device_entry.id, ) diff --git a/tests/testing_config/custom_components/test/sensor.py b/tests/components/sensor/common.py similarity index 84% rename from tests/testing_config/custom_components/test/sensor.py rename to tests/components/sensor/common.py index 9ebf16b9dcd..53a93b73da3 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/components/sensor/common.py @@ -1,10 +1,6 @@ -"""Provide a mock sensor platform. - -Call init before using it in your tests to ensure clean test data. -""" +"""Common test utilities for sensor entity component tests.""" from homeassistant.components.sensor import ( - DEVICE_CLASSES, RestoreSensor, SensorDeviceClass, SensorEntity, @@ -24,8 +20,6 @@ from homeassistant.const import ( from tests.common import MockEntity -DEVICE_CLASSES.append("none") - UNITS_OF_MEASUREMENT = { SensorDeviceClass.APPARENT_POWER: UnitOfApparentPower.VOLT_AMPERE, # apparent power (VA) SensorDeviceClass.BATTERY: PERCENTAGE, # % of battery that is left @@ -56,34 +50,6 @@ UNITS_OF_MEASUREMENT = { SensorDeviceClass.GAS: UnitOfVolume.CUBIC_METERS, # gas (m³) } -ENTITIES = {} - - -def init(empty=False): - """Initialize the platform with entities.""" - global ENTITIES - - ENTITIES = ( - {} - if empty - else { - device_class: MockSensor( - name=f"{device_class} sensor", - unique_id=f"unique_{device_class}", - device_class=device_class, - native_unit_of_measurement=UNITS_OF_MEASUREMENT.get(device_class), - ) - for device_class in DEVICE_CLASSES - } - ) - - -async def async_setup_platform( - hass, config, async_add_entities_callback, discovery_info=None -): - """Return mock entities.""" - async_add_entities_callback(list(ENTITIES.values())) - class MockSensor(MockEntity, SensorEntity): """Mock Sensor class.""" @@ -141,3 +107,16 @@ class MockRestoreSensor(MockSensor, RestoreSensor): self._values["native_unit_of_measurement"] = ( last_sensor_data.native_unit_of_measurement ) + + +def get_mock_sensor_entities() -> dict[str, MockSensor]: + """Get mock sensor entities.""" + return { + device_class: MockSensor( + name=f"{device_class} sensor", + unique_id=f"unique_{device_class}", + device_class=device_class, + native_unit_of_measurement=UNITS_OF_MEASUREMENT.get(device_class), + ) + for device_class in SensorDeviceClass + } diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 7263154c1dc..b633c744205 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -26,8 +26,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) -from tests.testing_config.custom_components.test.sensor import UNITS_OF_MEASUREMENT +from tests.components.sensor.common import UNITS_OF_MEASUREMENT, MockSensor @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -85,11 +86,10 @@ async def test_get_conditions( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_sensor_entities: dict[str, MockSensor], ) -> None: """Test we get the expected conditions from a sensor.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() sensor_entries = {} @@ -104,7 +104,7 @@ async def test_get_conditions( sensor_entries[device_class] = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES[device_class].unique_id, + mock_sensor_entities[device_class].unique_id, device_id=device_entry.id, ) @@ -284,6 +284,7 @@ async def test_get_condition_capabilities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + mock_sensor_entities: dict[str, MockSensor], set_state, device_class_reg, device_class_state, @@ -291,8 +292,7 @@ async def test_get_condition_capabilities( unit_state, ) -> None: """Test we get the expected capabilities from a sensor condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_sensor_entities.values()) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -303,7 +303,7 @@ async def test_get_condition_capabilities( entity_id = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_sensor_entities["battery"].unique_id, device_id=device_entry.id, original_device_class=device_class_reg, unit_of_measurement=unit_reg, @@ -353,6 +353,7 @@ async def test_get_condition_capabilities_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + mock_sensor_entities: dict[str, MockSensor], set_state, device_class_reg, device_class_state, @@ -360,8 +361,7 @@ async def test_get_condition_capabilities_legacy( unit_state, ) -> None: """Test we get the expected capabilities from a sensor condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_sensor_entities.values()) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -372,7 +372,7 @@ async def test_get_condition_capabilities_legacy( entity_id = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_sensor_entities["battery"].unique_id, device_id=device_entry.id, original_device_class=device_class_reg, unit_of_measurement=unit_reg, @@ -417,11 +417,13 @@ async def test_get_condition_capabilities_legacy( async def test_get_condition_capabilities_none( hass: HomeAssistant, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, ) -> None: """Test we get the expected capabilities from a sensor condition.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + entity = MockSensor( + name="none sensor", + unique_id="unique_none", + ) + setup_test_component_platform(hass, DOMAIN, [entity]) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -429,7 +431,7 @@ async def test_get_condition_capabilities_none( entry_none = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["none"].unique_id, + entity.unique_id, ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index 4193adc9299..98bea960fcc 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -30,8 +30,9 @@ from tests.common import ( async_get_device_automation_capabilities, async_get_device_automations, async_mock_service, + setup_test_component_platform, ) -from tests.testing_config.custom_components.test.sensor import UNITS_OF_MEASUREMENT +from tests.components.sensor.common import UNITS_OF_MEASUREMENT, MockSensor @pytest.fixture(autouse=True, name="stub_blueprint_populate") @@ -87,11 +88,10 @@ async def test_get_triggers( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, + mock_sensor_entities: dict[str, MockSensor], ) -> None: """Test we get the expected triggers from a sensor.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_sensor_entities.values()) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) await hass.async_block_till_done() sensor_entries: dict[SensorDeviceClass, er.RegistryEntry] = {} @@ -106,7 +106,7 @@ async def test_get_triggers( sensor_entries[device_class] = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES[device_class].unique_id, + mock_sensor_entities[device_class].unique_id, device_id=device_entry.id, ) @@ -241,6 +241,7 @@ async def test_get_trigger_capabilities( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + mock_sensor_entities: dict[str, MockSensor], set_state, device_class_reg, device_class_state, @@ -248,8 +249,7 @@ async def test_get_trigger_capabilities( unit_state, ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_sensor_entities) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -260,7 +260,7 @@ async def test_get_trigger_capabilities( entity_id = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_sensor_entities["battery"].unique_id, device_id=device_entry.id, original_device_class=device_class_reg, unit_of_measurement=unit_reg, @@ -311,6 +311,7 @@ async def test_get_trigger_capabilities_legacy( hass: HomeAssistant, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, + mock_sensor_entities: dict[str, MockSensor], set_state, device_class_reg, device_class_state, @@ -318,8 +319,7 @@ async def test_get_trigger_capabilities_legacy( unit_state, ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + setup_test_component_platform(hass, DOMAIN, mock_sensor_entities) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -330,7 +330,7 @@ async def test_get_trigger_capabilities_legacy( entity_id = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["battery"].unique_id, + mock_sensor_entities["battery"].unique_id, device_id=device_entry.id, original_device_class=device_class_reg, unit_of_measurement=unit_reg, @@ -374,11 +374,13 @@ async def test_get_trigger_capabilities_legacy( async def test_get_trigger_capabilities_none( hass: HomeAssistant, entity_registry: er.EntityRegistry, - enable_custom_integrations: None, ) -> None: """Test we get the expected capabilities from a sensor trigger.""" - platform = getattr(hass.components, f"test.{DOMAIN}") - platform.init() + entity = MockSensor( + name="none sensor", + unique_id="unique_none", + ) + setup_test_component_platform(hass, DOMAIN, [entity]) config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -386,7 +388,7 @@ async def test_get_trigger_capabilities_none( entry_none = entity_registry.async_get_or_create( DOMAIN, "test", - platform.ENTITIES["none"].unique_id, + entity.unique_id, ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 59df07bb0b9..0ecb4b9c60f 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -63,7 +63,9 @@ from tests.common import ( mock_integration, mock_platform, mock_restore_cache_with_extra_data, + setup_test_component_platform, ) +from tests.components.sensor.common import MockRestoreSensor, MockSensor TEST_DOMAIN = "test" @@ -103,7 +105,6 @@ TEST_DOMAIN = "test" ) async def test_temperature_conversion( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, state_unit, @@ -112,16 +113,14 @@ async def test_temperature_conversion( ) -> None: """Test temperature conversion.""" hass.config.units = unit_system - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=str(native_value), native_unit_of_measurement=native_unit, device_class=SensorDeviceClass.TEMPERATURE, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -132,19 +131,17 @@ async def test_temperature_conversion( @pytest.mark.parametrize("device_class", [None, SensorDeviceClass.PRESSURE]) async def test_temperature_conversion_wrong_device_class( - hass: HomeAssistant, device_class, enable_custom_integrations: None + hass: HomeAssistant, device_class ) -> None: """Test temperatures are not converted if the sensor has wrong device class.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value="0.0", native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, device_class=device_class, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -158,21 +155,19 @@ async def test_temperature_conversion_wrong_device_class( async def test_deprecated_last_reset( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, state_class, ) -> None: """Test warning on deprecated last reset.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", state_class=state_class, last_reset=dt_util.utc_from_timestamp(0) ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() assert ( - "Entity sensor.test () " + "Entity sensor.test () " f"with state_class {state_class} has set last_reset. Setting last_reset for " "entities with state_class other than 'total' is not supported. Please update " "your configuration if state_class is manually configured." @@ -185,7 +180,6 @@ async def test_deprecated_last_reset( async def test_datetime_conversion( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test conversion of datetime.""" test_timestamp = datetime(2017, 12, 19, 18, 29, 42, tzinfo=UTC) @@ -193,51 +187,49 @@ async def test_datetime_conversion( dt_util.get_time_zone("Europe/Amsterdam") ) test_date = date(2017, 12, 19) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( - name="Test", - native_value=test_timestamp, - device_class=SensorDeviceClass.TIMESTAMP, - ) - platform.ENTITIES["1"] = platform.MockSensor( - name="Test", native_value=test_date, device_class=SensorDeviceClass.DATE - ) - platform.ENTITIES["2"] = platform.MockSensor( - name="Test", native_value=None, device_class=SensorDeviceClass.TIMESTAMP - ) - platform.ENTITIES["3"] = platform.MockSensor( - name="Test", native_value=None, device_class=SensorDeviceClass.DATE - ) - platform.ENTITIES["4"] = platform.MockSensor( - name="Test", - native_value=test_local_timestamp, - device_class=SensorDeviceClass.TIMESTAMP, - ) + entities = [ + MockSensor( + name="Test", + native_value=test_timestamp, + device_class=SensorDeviceClass.TIMESTAMP, + ), + MockSensor( + name="Test", native_value=test_date, device_class=SensorDeviceClass.DATE + ), + MockSensor( + name="Test", native_value=None, device_class=SensorDeviceClass.TIMESTAMP + ), + MockSensor(name="Test", native_value=None, device_class=SensorDeviceClass.DATE), + MockSensor( + name="Test", + native_value=test_local_timestamp, + device_class=SensorDeviceClass.TIMESTAMP, + ), + ] + setup_test_component_platform(hass, sensor.DOMAIN, entities) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() - state = hass.states.get(platform.ENTITIES["0"].entity_id) + state = hass.states.get(entities[0].entity_id) assert state.state == test_timestamp.isoformat() - state = hass.states.get(platform.ENTITIES["1"].entity_id) + state = hass.states.get(entities[1].entity_id) assert state.state == test_date.isoformat() - state = hass.states.get(platform.ENTITIES["2"].entity_id) + state = hass.states.get(entities[2].entity_id) assert state.state == STATE_UNKNOWN - state = hass.states.get(platform.ENTITIES["3"].entity_id) + state = hass.states.get(entities[3].entity_id) assert state.state == STATE_UNKNOWN - state = hass.states.get(platform.ENTITIES["4"].entity_id) + state = hass.states.get(entities[4].entity_id) assert state.state == test_timestamp.isoformat() async def test_a_sensor_with_a_non_numeric_device_class( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test that a sensor with a non numeric device class will be non numeric. @@ -249,29 +241,29 @@ async def test_a_sensor_with_a_non_numeric_device_class( dt_util.get_time_zone("Europe/Amsterdam") ) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( - name="Test", - native_value=test_local_timestamp, - native_unit_of_measurement="", - device_class=SensorDeviceClass.TIMESTAMP, - ) - - platform.ENTITIES["1"] = platform.MockSensor( - name="Test", - native_value=test_local_timestamp, - state_class="", - device_class=SensorDeviceClass.TIMESTAMP, - ) + entities = [ + MockSensor( + name="Test", + native_value=test_local_timestamp, + native_unit_of_measurement="", + device_class=SensorDeviceClass.TIMESTAMP, + ), + MockSensor( + name="Test", + native_value=test_local_timestamp, + state_class="", + device_class=SensorDeviceClass.TIMESTAMP, + ), + ] + setup_test_component_platform(hass, sensor.DOMAIN, entities) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() - state = hass.states.get(platform.ENTITIES["0"].entity_id) + state = hass.states.get(entities[0].entity_id) assert state.state == test_timestamp.isoformat() - state = hass.states.get(platform.ENTITIES["1"].entity_id) + state = hass.states.get(entities[1].entity_id) assert state.state == test_timestamp.isoformat() @@ -285,17 +277,15 @@ async def test_a_sensor_with_a_non_numeric_device_class( async def test_deprecated_datetime_str( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, device_class, state_value, provides, ) -> None: """Test warning on deprecated str for a date(time) value.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=state_value, device_class=device_class ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -309,17 +299,15 @@ async def test_deprecated_datetime_str( async def test_reject_timezoneless_datetime_str( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test rejection of timezone-less datetime objects as timestamp.""" test_timestamp = datetime(2017, 12, 19, 18, 29, 42, tzinfo=None) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=test_timestamp, device_class=SensorDeviceClass.TIMESTAMP, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -403,7 +391,6 @@ RESTORE_DATA = { ) async def test_restore_sensor_save_state( hass: HomeAssistant, - enable_custom_integrations: None, hass_storage: dict[str, Any], native_value, native_value_type, @@ -412,16 +399,14 @@ async def test_restore_sensor_save_state( uom, ) -> None: """Test RestoreSensor.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockRestoreSensor( + entity0 = MockRestoreSensor( name="Test", native_value=native_value, native_unit_of_measurement=uom, device_class=device_class, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -472,7 +457,6 @@ async def test_restore_sensor_save_state( ) async def test_restore_sensor_restore_state( hass: HomeAssistant, - enable_custom_integrations: None, hass_storage: dict[str, Any], native_value, native_value_type, @@ -483,14 +467,12 @@ async def test_restore_sensor_restore_state( """Test RestoreSensor.""" mock_restore_cache_with_extra_data(hass, ((State("sensor.test", ""), extra_data),)) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockRestoreSensor( + entity0 = MockRestoreSensor( name="Test", device_class=device_class, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -621,7 +603,6 @@ async def test_restore_sensor_restore_state( ) async def test_custom_unit( hass: HomeAssistant, - enable_custom_integrations: None, device_class, native_unit, custom_unit, @@ -638,17 +619,15 @@ async def test_custom_unit( ) await hass.async_block_till_done() - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=str(native_value), native_unit_of_measurement=native_unit, device_class=device_class, unique_id="very_unique", ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -884,7 +863,6 @@ async def test_custom_unit( ) async def test_custom_unit_change( hass: HomeAssistant, - enable_custom_integrations: None, native_unit, custom_unit, state_unit, @@ -895,17 +873,15 @@ async def test_custom_unit_change( ) -> None: """Test custom unit changes are picked up.""" entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=str(native_value), native_unit_of_measurement=native_unit, device_class=device_class, unique_id="very_unique", ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) - entity0 = platform.ENTITIES["0"] assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -972,7 +948,6 @@ async def test_custom_unit_change( ) async def test_unit_conversion_priority( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, automatic_unit, @@ -990,27 +965,21 @@ async def test_unit_conversion_priority( hass.config.units = unit_system entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] - - platform.ENTITIES["1"] = platform.MockSensor( + entity1 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), ) - entity1 = platform.ENTITIES["1"] - - platform.ENTITIES["2"] = platform.MockSensor( + entity2 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1018,16 +987,23 @@ async def test_unit_conversion_priority( suggested_unit_of_measurement=suggested_unit, unique_id="very_unique_2", ) - entity2 = platform.ENTITIES["2"] - - platform.ENTITIES["3"] = platform.MockSensor( + entity3 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), suggested_unit_of_measurement=suggested_unit, ) - entity3 = platform.ENTITIES["3"] + setup_test_component_platform( + hass, + sensor.DOMAIN, + [ + entity0, + entity1, + entity2, + entity3, + ], + ) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1119,7 +1095,6 @@ async def test_unit_conversion_priority( ) async def test_unit_conversion_priority_precision( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, automatic_unit, @@ -1138,10 +1113,8 @@ async def test_unit_conversion_priority_precision( hass.config.units = unit_system entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1149,18 +1122,14 @@ async def test_unit_conversion_priority_precision( suggested_display_precision=suggested_precision, unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] - - platform.ENTITIES["1"] = platform.MockSensor( + entity1 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), suggested_display_precision=suggested_precision, ) - entity1 = platform.ENTITIES["1"] - - platform.ENTITIES["2"] = platform.MockSensor( + entity2 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1169,9 +1138,7 @@ async def test_unit_conversion_priority_precision( suggested_unit_of_measurement=suggested_unit, unique_id="very_unique_2", ) - entity2 = platform.ENTITIES["2"] - - platform.ENTITIES["3"] = platform.MockSensor( + entity3 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1179,7 +1146,16 @@ async def test_unit_conversion_priority_precision( suggested_display_precision=suggested_precision, suggested_unit_of_measurement=suggested_unit, ) - entity3 = platform.ENTITIES["3"] + setup_test_component_platform( + hass, + sensor.DOMAIN, + [ + entity0, + entity1, + entity2, + entity3, + ], + ) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1280,7 +1256,6 @@ async def test_unit_conversion_priority_precision( ) async def test_unit_conversion_priority_suggested_unit_change( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, original_unit, @@ -1294,8 +1269,6 @@ async def test_unit_conversion_priority_suggested_unit_change( hass.config.units = unit_system entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) # Pre-register entities entry = entity_registry.async_get_or_create( @@ -1315,16 +1288,14 @@ async def test_unit_conversion_priority_suggested_unit_change( {"suggested_unit_of_measurement": original_unit}, ) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] - - platform.ENTITIES["1"] = platform.MockSensor( + entity1 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1332,7 +1303,7 @@ async def test_unit_conversion_priority_suggested_unit_change( suggested_unit_of_measurement=suggested_unit, unique_id="very_unique_2", ) - entity1 = platform.ENTITIES["1"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0, entity1]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1392,7 +1363,6 @@ async def test_unit_conversion_priority_suggested_unit_change( ) async def test_unit_conversion_priority_suggested_unit_change_2( hass: HomeAssistant, - enable_custom_integrations: None, native_unit_1, native_unit_2, suggested_unit, @@ -1405,8 +1375,6 @@ async def test_unit_conversion_priority_suggested_unit_change_2( hass.config.units = METRIC_SYSTEM entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) # Pre-register entities entity_registry.async_get_or_create( @@ -1416,16 +1384,14 @@ async def test_unit_conversion_priority_suggested_unit_change_2( "sensor", "test", "very_unique_2", unit_of_measurement=native_unit_1 ) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit_2, native_value=str(native_value), unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] - - platform.ENTITIES["1"] = platform.MockSensor( + entity1 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit_2, @@ -1433,7 +1399,7 @@ async def test_unit_conversion_priority_suggested_unit_change_2( suggested_unit_of_measurement=suggested_unit, unique_id="very_unique_2", ) - entity1 = platform.ENTITIES["1"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0, entity1]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1496,7 +1462,6 @@ async def test_unit_conversion_priority_suggested_unit_change_2( ) async def test_suggested_precision_option( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, integration_suggested_precision, @@ -1510,10 +1475,7 @@ async def test_suggested_precision_option( hass.config.units = unit_system entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1521,7 +1483,7 @@ async def test_suggested_precision_option( suggested_display_precision=integration_suggested_precision, unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1574,7 +1536,6 @@ async def test_suggested_precision_option( ) async def test_suggested_precision_option_update( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, suggested_unit, @@ -1590,8 +1551,6 @@ async def test_suggested_precision_option_update( hass.config.units = unit_system entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) # Pre-register entities entry = entity_registry.async_get_or_create("sensor", "test", "very_unique") @@ -1610,7 +1569,7 @@ async def test_suggested_precision_option_update( }, ) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, @@ -1618,7 +1577,7 @@ async def test_suggested_precision_option_update( suggested_display_precision=new_precision, unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1666,7 +1625,6 @@ async def test_suggested_precision_option_update( ) async def test_unit_conversion_priority_legacy_conversion_removed( hass: HomeAssistant, - enable_custom_integrations: None, unit_system, native_unit, original_unit, @@ -1679,22 +1637,20 @@ async def test_unit_conversion_priority_legacy_conversion_removed( hass.config.units = unit_system entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) # Pre-register entities entity_registry.async_get_or_create( "sensor", "test", "very_unique", unit_of_measurement=original_unit ) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=native_unit, native_value=str(native_value), unique_id="very_unique", ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1715,17 +1671,15 @@ def test_device_classes_aligned() -> None: async def test_value_unknown_in_enumeration( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test warning on invalid enum value.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value="invalid_option", device_class=SensorDeviceClass.ENUM, options=["option1", "option2"], ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1739,17 +1693,15 @@ async def test_value_unknown_in_enumeration( async def test_invalid_enumeration_entity_with_device_class( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test warning on entities that provide an enum with a device class.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=21, device_class=SensorDeviceClass.POWER, options=["option1", "option2"], ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1763,16 +1715,14 @@ async def test_invalid_enumeration_entity_with_device_class( async def test_invalid_enumeration_entity_without_device_class( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test warning on entities that provide an enum without a device class.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=21, options=["option1", "option2"], ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1794,19 +1744,17 @@ async def test_invalid_enumeration_entity_without_device_class( async def test_non_numeric_device_class_with_unit_of_measurement( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, device_class: SensorDeviceClass, ) -> None: """Test error on numeric entities that provide an unit of measurement.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=None, device_class=device_class, native_unit_of_measurement=UnitOfTemperature.CELSIUS, options=["option1", "option2"], ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1869,18 +1817,16 @@ async def test_non_numeric_device_class_with_unit_of_measurement( async def test_device_classes_with_invalid_unit_of_measurement( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, device_class: SensorDeviceClass, ) -> None: """Test error when unit of measurement is not valid for used device class.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value="1.0", device_class=device_class, native_unit_of_measurement="INVALID!", ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) units = [ str(unit) if unit else "no unit of measurement" for unit in DEVICE_CLASS_UNITS.get(device_class, set()) @@ -1920,7 +1866,6 @@ async def test_device_classes_with_invalid_unit_of_measurement( async def test_non_numeric_validation_error( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, native_value: Any, problem: str, device_class: SensorDeviceClass | None, @@ -1928,16 +1873,14 @@ async def test_non_numeric_validation_error( unit: str | None, ) -> None: """Test error on expected numeric entities.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=native_value, device_class=device_class, native_unit_of_measurement=unit, state_class=state_class, ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -1966,7 +1909,6 @@ async def test_non_numeric_validation_error( async def test_non_numeric_validation_raise( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, native_value: Any, expected: str, device_class: SensorDeviceClass | None, @@ -1975,9 +1917,7 @@ async def test_non_numeric_validation_raise( precision, ) -> None: """Test error on expected numeric entities.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", device_class=device_class, native_unit_of_measurement=unit, @@ -1985,7 +1925,7 @@ async def test_non_numeric_validation_raise( state_class=state_class, suggested_display_precision=precision, ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -2018,7 +1958,6 @@ async def test_non_numeric_validation_raise( async def test_numeric_validation( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, native_value: Any, expected: str, device_class: SensorDeviceClass | None, @@ -2026,16 +1965,14 @@ async def test_numeric_validation( unit: str | None, ) -> None: """Test does not error on expected numeric entities.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=native_value, device_class=device_class, native_unit_of_measurement=unit, state_class=state_class, ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -2052,18 +1989,15 @@ async def test_numeric_validation( async def test_numeric_validation_ignores_custom_device_class( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, ) -> None: """Test does not error on expected numeric entities.""" native_value = "Three elephants" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=native_value, device_class="custom__deviceclass", ) - entity0 = platform.ENTITIES["0"] + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -2084,18 +2018,16 @@ async def test_numeric_validation_ignores_custom_device_class( async def test_device_classes_with_invalid_state_class( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, device_class: SensorDeviceClass, ) -> None: """Test error when unit of measurement is not valid for used device class.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=None, state_class="INVALID!", device_class=device_class, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -2133,7 +2065,6 @@ async def test_device_classes_with_invalid_state_class( async def test_numeric_state_expected_helper( hass: HomeAssistant, caplog: pytest.LogCaptureFixture, - enable_custom_integrations: None, device_class: SensorDeviceClass | None, state_class: SensorStateClass | None, native_unit_of_measurement: str | None, @@ -2141,9 +2072,7 @@ async def test_numeric_state_expected_helper( is_numeric: bool, ) -> None: """Test numeric_state_expected helper.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( name="Test", native_value=None, device_class=device_class, @@ -2151,11 +2080,11 @@ async def test_numeric_state_expected_helper( native_unit_of_measurement=native_unit_of_measurement, suggested_display_precision=suggested_precision, ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() - entity0 = platform.ENTITIES["0"] state = hass.states.get(entity0.entity_id) assert state is not None @@ -2199,7 +2128,6 @@ async def test_numeric_state_expected_helper( ) async def test_unit_conversion_update( hass: HomeAssistant, - enable_custom_integrations: None, unit_system_1, unit_system_2, native_unit, @@ -2219,9 +2147,8 @@ async def test_unit_conversion_update( hass.config.units = unit_system_1 entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - entity0 = platform.MockSensor( + entity0 = MockSensor( name="Test 0", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2229,7 +2156,7 @@ async def test_unit_conversion_update( unique_id="very_unique", ) - entity1 = platform.MockSensor( + entity1 = MockSensor( name="Test 1", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2237,7 +2164,7 @@ async def test_unit_conversion_update( unique_id="very_unique_1", ) - entity2 = platform.MockSensor( + entity2 = MockSensor( name="Test 2", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2246,7 +2173,7 @@ async def test_unit_conversion_update( unique_id="very_unique_2", ) - entity3 = platform.MockSensor( + entity3 = MockSensor( name="Test 3", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2255,7 +2182,7 @@ async def test_unit_conversion_update( unique_id="very_unique_3", ) - entity4 = platform.MockSensor( + entity4 = MockSensor( name="Test 4", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2544,11 +2471,8 @@ async def test_entity_category_config_raises_error( caplog: pytest.LogCaptureFixture, ) -> None: """Test error is raised when entity category is set to config.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( - name="Test", entity_category=EntityCategory.CONFIG - ) + entity0 = MockSensor(name="Test", entity_category=EntityCategory.CONFIG) + setup_test_component_platform(hass, sensor.DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -2644,13 +2568,11 @@ async def test_suggested_unit_guard_invalid_unit( An invalid suggested unit creates a log entry and the suggested unit will be ignored. """ entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) state_value = 10 invalid_suggested_unit = "invalid_unit" - entity = platform.ENTITIES["0"] = platform.MockSensor( + entity = MockSensor( name="Invalid", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2658,6 +2580,7 @@ async def test_suggested_unit_guard_invalid_unit( native_value=str(state_value), unique_id="invalid", ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() @@ -2674,10 +2597,10 @@ async def test_suggested_unit_guard_invalid_unit( "homeassistant.components.sensor", logging.WARNING, ( - " sets an" - " invalid suggested_unit_of_measurement. Please report it to the author" - " of the 'test' custom integration. This warning will become an error in" - " Home Assistant Core 2024.5" + " sets an" + " invalid suggested_unit_of_measurement. Please create a bug report at " + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+test%22." + " This warning will become an error in Home Assistant Core 2024.5" ), ) in caplog.record_tuples @@ -2715,10 +2638,8 @@ async def test_suggested_unit_guard_valid_unit( in the entity registry. """ entity_registry = er.async_get(hass) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - entity = platform.ENTITIES["0"] = platform.MockSensor( + entity = MockSensor( name="Valid", device_class=device_class, native_unit_of_measurement=native_unit, @@ -2726,6 +2647,7 @@ async def test_suggested_unit_guard_valid_unit( suggested_unit_of_measurement=suggested_unit, unique_id="valid", ) + setup_test_component_platform(hass, sensor.DOMAIN, [entity]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index 40b38b2e57a..8084fe69e89 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -33,13 +33,14 @@ from homeassistant.components.recorder.statistics import ( list_statistic_ids, ) from homeassistant.components.recorder.util import get_instance, session_scope -from homeassistant.components.sensor import ATTR_OPTIONS, SensorDeviceClass +from homeassistant.components.sensor import ATTR_OPTIONS, DOMAIN, SensorDeviceClass from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant, State from homeassistant.setup import async_setup_component, setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import METRIC_SYSTEM, US_CUSTOMARY_SYSTEM +from tests.common import setup_test_component_platform from tests.components.recorder.common import ( assert_dict_of_states_equal_without_context_and_last_changed, assert_multiple_states_equal_without_context_and_last_changed, @@ -49,6 +50,7 @@ from tests.components.recorder.common import ( statistics_during_period, wait_recording_done, ) +from tests.components.sensor.common import MockSensor from tests.typing import WebSocketGenerator BATTERY_SENSOR_ATTRIBUTES = { @@ -1363,11 +1365,9 @@ def test_compile_hourly_sum_statistics_negative_state( hass = hass_recorder() hass.data.pop(loader.DATA_CUSTOM_COMPONENTS) - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - mocksensor = platform.MockSensor(name="custom_sensor") + mocksensor = MockSensor(name="custom_sensor") mocksensor._attr_should_poll = False - platform.ENTITIES["custom_sensor"] = mocksensor + setup_test_component_platform(hass, DOMAIN, [mocksensor], built_in=False) setup_component(hass, "homeassistant", {}) setup_component( @@ -5178,9 +5178,7 @@ async def test_exclude_attributes( recorder_mock: Recorder, hass: HomeAssistant, enable_custom_integrations: None ) -> None: """Test sensor attributes to be excluded.""" - platform = getattr(hass.components, "test.sensor") - platform.init(empty=True) - platform.ENTITIES["0"] = platform.MockSensor( + entity0 = MockSensor( has_entity_name=True, unique_id="test", name="Test", @@ -5188,6 +5186,7 @@ async def test_exclude_attributes( device_class=SensorDeviceClass.ENUM, options=["option1", "option2"], ) + setup_test_component_platform(hass, DOMAIN, [entity0]) assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}}) await hass.async_block_till_done() await async_wait_recording_done(hass)